Help
RSS
API
Feed
Maltego
Contact
Domain > blog.ray-realms.com
×
More information on this domain is in
AlienVault OTX
Is this malicious?
Yes
No
DNS Resolutions
Date
IP Address
2025-02-01
104.21.79.35
(
ClassC
)
2025-11-22
43.199.8.212
(
ClassC
)
Port 443
HTTP/1.1 200 OKAlt-Svc: h3:443; ma86400Cache-Control: s-maxage3600, stale-while-revalidate31532400Content-Type: text/html; charsetutf-8Date: Sat, 22 Nov 2025 17:30:45 GMTEtag: mb9e7s0iv8cbzeVary: rsc, next-router-state-tree, next-router-prefetch, next-router-segment-prefetch, Accept-EncodingX-Nextjs-Cache: STALEX-Nextjs-Prerender: 1X-Nextjs-Stale-Time: 300X-Zeabur-Ip-Country: USX-Zeabur-Request-Id: hkg1::abdbecf0-a0d3-4a47-9e5f-1b72d3d24684Transfer-Encoding: chunked !DOCTYPE html>html langzh-TW>head>meta charSetutf-8/>meta nameviewport contentwidthdevice-width, initial-scale1/>link relpreload href/_next/static/media/493c3cff6f1bd7f1-s.p.woff2 asfont crossorigin typefont/woff2/>link relpreload href/_next/static/media/e4af272ccee01ff0-s.p.woff2 asfont crossorigin typefont/woff2/>link relpreload asimage href/ray.png/>link relstylesheet href/_next/static/css/081a0afca5a9bd20.css data-precedencenext/>link relstylesheet href/_next/static/css/31f1fbdb5c9c2d2d.css data-precedencenext/>link relstylesheet href/_next/static/css/36c8165c3c750958.css data-precedencenext/>link relstylesheet href/_next/static/css/a72ea4d34b725941.css data-precedencenext/>link relstylesheet href/_next/static/css/39c55841169ff4a9.css data-precedencenext/>link relpreload asscript fetchPrioritylow href/_next/static/chunks/webpack-f2c0234491d2b65b.js/>script src/_next/static/chunks/a594fd83-405d52fc7887ddea.js async>/script>script src/_next/static/chunks/8635-ca4f5699b03f1c46.js async>/script>script src/_next/static/chunks/main-app-adf6c89a65aa20af.js async>/script>script src/_next/static/chunks/c3536442-5b720253f760e76c.js async>/script>script src/_next/static/chunks/e0b518d6-c6ee72fbc9b3cbf2.js async>/script>script src/_next/static/chunks/9db99176-68080c29ff87e383.js async>/script>script src/_next/static/chunks/261ab18d-a828c13849e44ee4.js async>/script>script src/_next/static/chunks/2783-b9a383cf8d2da686.js async>/script>script src/_next/static/chunks/7125-7adaf07f9588b340.js async>/script>script src/_next/static/chunks/6044-6a843023a95c7b69.js async>/script>script src/_next/static/chunks/6046-a15a9c22edfc2449.js async>/script>script src/_next/static/chunks/4705-0f8c0b1c3eb3293d.js async>/script>script src/_next/static/chunks/2391-8ec1f698fe165feb.js async>/script>script src/_next/static/chunks/1009-f7f224d177e229d1.js async>/script>script src/_next/static/chunks/9006-8fe15af56f6d3c05.js async>/script>script src/_next/static/chunks/5216-7e0407f0148493a0.js async>/script>script src/_next/static/chunks/8968-15c5d78452c480e9.js async>/script>script src/_next/static/chunks/3913-8d17ea812b32af8a.js async>/script>script src/_next/static/chunks/5356-ace1dbdd0cac554a.js async>/script>script src/_next/static/chunks/8681-581ccb3c782bf8ae.js async>/script>script src/_next/static/chunks/1467-632b567137ecbb7b.js async>/script>script src/_next/static/chunks/7970-3125462d67a87e21.js async>/script>script src/_next/static/chunks/app/layout-a77ba2f807a0be7c.js async>/script>script src/_next/static/chunks/app/template-b17f1369099947c1.js async>/script>script src/_next/static/chunks/ae80f326-2d1ce9f306c3e117.js async>/script>script src/_next/static/chunks/40f94348-3665981d394436a4.js async>/script>script src/_next/static/chunks/cd2bc502-dccf3f627b79fb7b.js async>/script>script src/_next/static/chunks/d5504597-14b1fdf76ee20456.js async>/script>script src/_next/static/chunks/0b0944fb-9fdc250e44912132.js async>/script>script src/_next/static/chunks/438-376903fca45b8284.js async>/script>script src/_next/static/chunks/5942-ea32bc250b281e90.js async>/script>script src/_next/static/chunks/app/page-7344702a57691eb0.js async>/script>meta namenext-size-adjust content/>title>首頁/title>meta namedescription content探索 Ray 貓的技術筆記與創業思考。包含 AI、軟體工程、系統架構、創業經驗等深度文章。/>link relauthor hrefhttps://ray-realms.com/>meta nameauthor contentRay 貓/>link relmanifest href/manifest.json/>meta namekeywords contentAI,Software Engineering,Startup,軟體工程,新創,技術部落格,程式設計,Ray 貓/>meta namecreator contentRay 貓/>meta namepublisher contentRay 貓/>meta namerobots contentindex, follow/>meta namegooglebot contentindex, follow, max-video-preview:-1, max-image-preview:large, max-snippet:-1/>meta namemobile-web-app-capable contentyes/>link relcanonical hrefhttps://blog.ray-realms.com/>meta nameformat-detection contenttelephoneno/>meta namegoogle-site-verification contentgoogle-site-verification-code/>meta namemobile-web-app-capable contentyes/>meta nameapple-mobile-web-app-title contentRay 貓的部落格/>meta nameapple-mobile-web-app-status-bar-style contentdefault/>meta propertyog:title contentRay 貓的部落格 | AI × Software Engineering × Startup Notes/>meta propertyog:description content探索 Ray 貓的技術筆記與創業思考。包含 AI、軟體工程、系統架構、創業經驗等深度文章。/>meta propertyog:url contenthttps://blog.ray-realms.com/>meta propertyog:type contentwebsite/>meta nametwitter:card contentsummary_large_image/>meta nametwitter:title contentRay 貓的部落格 | AI × Software Engineering × Startup Notes/>meta nametwitter:description content探索 Ray 貓的技術筆記與創業思考。包含 AI、軟體工程、系統架構、創業經驗等深度文章。/>link relicon href/favicon.ico typeimage/x-icon sizes32x32/>link relicon href/favicon.ico sizesany/>link relicon href/icon.svg typeimage/svg+xml/>link relicon href/icon-192.png sizes192x192 typeimage/png/>link relicon href/icon-512.png sizes512x512 typeimage/png/>link relapple-touch-icon href/apple-touch-icon.png sizes180x180/>script src/_next/static/chunks/polyfills-42372ed130431b0a.js noModule>/script>/head>body class__variable_f367f3 __variable_8b92e0>div hidden>!--$-->!--/$-->/div>div data-overlay-containertrue>header classfixed left-0 right-0 top-0 z-50 bg-white/50 backdrop-blur dark:bg-gray-900/50>div classflex items-center justify-between gap-4 px-6 py-2>div tabindex-1 classinline-flex items-center justify-center gap-2 rounded-small outline-none data-focus-visibletrue:z-10 data-focus-visibletrue:outline-2 data-focus-visibletrue:outline-focus data-focus-visibletrue:outline-offset-2 cursor-pointer>span tabindex-1 classflex relative justify-center items-center box-border overflow-hidden align-middle z-0 outline-none data-focus-visibletrue:z-10 data-focus-visibletrue:outline-2 data-focus-visibletrue:outline-focus data-focus-visibletrue:outline-offset-2 w-10 h-10 text-tiny bg-default text-default-foreground rounded-full>img src/ray.png classflex object-cover w-full h-full transition-opacity !duration-500 opacity-0 data-loadedtrue:opacity-100 altRay 貓/>/span>div classinline-flex flex-col items-start>span classtext-small text-inherit>Ray 貓/span>span classtext-tiny text-foreground-400>a classrelative inline-flex items-center tap-highlight-transparent outline-none data-focus-visibletrue:z-10 data-focus-visibletrue:outline-2 data-focus-visibletrue:outline-focus data-focus-visibletrue:outline-offset-2 text-small text-primary no-underline hover:opacity-80 active:opacity-disabled transition-opacity hrefhttps://www.threads.net/@ray.realms target_blank relnoopener noreferrer tabindex0 rolelink>@ray-realms/a>/span>/div>/div>div classflex items-center gap-2>div classgroup inline-flex flex-column w-full max-w-xs>div classgroup flex flex-col w-full data-slotbase data-filledtrue data-filled-withintrue data-has-elementstrue data-has-labeltrue data-has-valuetrue>div data-slotinput-wrapper classrelative w-full inline-flex tap-highlight-transparent shadow-sm px-3 bg-default-100 data-hovertrue:bg-default-200 group-data-focustrue:bg-default-100 min-h-10 rounded-medium flex-col items-start justify-center gap-0 transition-background motion-reduce:transition-none !duration-150 outline-none group-data-focus-visibletrue:z-10 group-data-focus-visibletrue:ring-2 group-data-focus-visibletrue:ring-focus group-data-focus-visibletrue:ring-offset-2 group-data-focus-visibletrue:ring-offset-background h-14 py-2 stylecursor:text>label data-slotlabel classabsolute z-10 pointer-events-none origin-top-left rtl:origin-top-right subpixel-antialiased block text-foreground-500 cursor-text will-change-auto !duration-200 !ease-out motion-reduce:transition-none transition-transform,color,left,opacity group-data-filled-withintrue:text-default-600 group-data-filled-withintrue:pointer-events-auto group-data-filled-withintrue:scale-85 text-small group-data-filled-withintrue:-translate-y-calc(50%_+_theme(fontSize.small)/2_-_6px) pe-2 max-w-full text-ellipsis overflow-hidden idreact-aria-_R_r7bH1_ forreact-aria-_R_37bH2_>搜尋文章/label>div data-slotinner-wrapper classinline-flex w-full items-center h-full box-border group-data-has-labeltrue:items-end pb-0.5>svg strokecurrentColor fillcurrentColor stroke-width0 version1.1 idsearch x0px y0px viewBox0 0 24 24 classpointer-events-none mb-0.5 flex-shrink-0 text-black/50 text-slate-400 dark:text-white/90 height1em width1em xmlnshttp://www.w3.org/2000/svg>g>path dM20.031,20.79c0.46,0.46,1.17-0.25,0.71-0.7l-3.75-3.76c1.27-1.41,2.04-3.27,2.04-5.31 c0-4.39-3.57-7.96-7.96-7.96s-7.96,3.57-7.96,7.96c0,4.39,3.57,7.96,7.96,7.96c1.98,0,3.81-0.73,5.21-1.94L20.031,20.79z M4.11,11.02c0-3.84,3.13-6.96,6.96-6.96c3.84,0,6.96,3.12,6.96,6.96c0,3.84-3.12,6.96-6.96,6.96C7.24,17.98,4.11,14.86,4.11,11.02 z>/path>/g>/svg>input data-slotinput data-has-start-contenttrue data-has-end-contenttrue classw-full font-normal bg-transparent !outline-none placeholder:text-foreground-500 focus-visible:outline-none data-has-start-contenttrue:ps-1.5 data-has-end-contenttrue:pe-1.5 text-small group-data-has-valuetrue:text-default-foreground idreact-aria-_R_37bH2_ aria-label搜尋文章 aria-labelledbyreact-aria-_R_37bH3_ aria-describedbyreact-aria-_R_37bH5_ react-aria-_R_37bH6_ typetext aria-autocompletelist autoCompleteoff placeholder查詢關鍵字: react 教學... rolecombobox aria-expandedfalse spellCheckfalse value/>div classrelative flex h-full items-center -mr-2>button classz-0 group relative inline-flex items-center justify-center box-border appearance-none select-none whitespace-nowrap font-normal subpixel-antialiased overflow-hidden tap-highlight-transparent data-pressedtrue:scale-0.97 outline-none data-focus-visibletrue:z-10 data-focus-visibletrue:outline-2 data-focus-visibletrue:outline-focus data-focus-visibletrue:outline-offset-2 gap-2 rounded-full px-0 !gap-0 transition-transform-colors-opacity motion-reduce:transition-none bg-transparent data-hovertrue:bg-default/40 min-w-8 w-8 h-8 text-medium translate-x-1 cursor-text opacity-0 text-default-500 group-data-invalidtrue:text-danger data-visibletrue:opacity-100 data-visibletrue:cursor-pointer sm:data-visibletrue:opacity-0 sm:group-data-hovertrue:data-visibletrue:opacity-100 typebutton tabindex-1 idreact-aria-_R_37b_ aria-labelShow suggestions aria-labelledbyreact-aria-_R_37b_ react-aria-_R_37bH3_ data-visiblefalse aria-haspopuplistbox aria-expandedfalse>svg aria-hiddentrue fillnone focusablefalse height1em rolepresentation strokecurrentColor stroke-linecapround stroke-linejoinround stroke-width2 viewBox0 0 24 24 width1em>path dM18 6L6 18M6 6l12 12>/path>/svg>/button>button classz-0 group relative inline-flex items-center justify-center box-border appearance-none select-none whitespace-nowrap font-normal subpixel-antialiased overflow-hidden tap-highlight-transparent data-pressedtrue:scale-0.97 outline-none data-focus-visibletrue:z-10 data-focus-visibletrue:outline-2 data-focus-visibletrue:outline-focus data-focus-visibletrue:outline-offset-2 gap-2 rounded-full opacity-disabled pointer-events-none px-0 !gap-0 transition-transform-colors-opacity bg-transparent text-default-foreground data-hovertrue:bg-default/40 min-w-8 w-8 h-8 text-medium transition-transform duration-150 ease motion-reduce:transition-none data-opentrue:rotate-180 data-disabledtrue data-loadingtrue typebutton disabled idreact-aria-_R_37b_ aria-labelShow suggestions aria-labelledbyreact-aria-_R_37b_ react-aria-_R_37bH3_ aria-haspopuplistbox aria-expandedfalse>div aria-labelLoading classrelative inline-flex flex-col gap-2 items-center justify-center>div classrelative flex w-5 h-5>i classabsolute w-full h-full rounded-full animate-spinner-ease-spin border-solid border-t-transparent border-l-transparent border-r-transparent border-2 border-b-current>/i>i classabsolute w-full h-full rounded-full opacity-75 animate-spinner-linear-spin border-dotted border-t-transparent border-l-transparent border-r-transparent border-2 border-b-current>/i>/div>/div>/button>/div>/div>/div>/div>div classhidden>/div>/div>button classz-0 group relative inline-flex items-center justify-center box-border appearance-none select-none whitespace-nowrap font-normal subpixel-antialiased overflow-hidden tap-highlight-transparent data-pressedtrue:scale-0.97 outline-none data-focus-visibletrue:z-10 data-focus-visibletrue:outline-2 data-focus-visibletrue:outline-focus data-focus-visibletrue:outline-offset-2 text-small gap-2 rounded-medium px-0 !gap-0 transition-transform-colors-opacity motion-reduce:transition-none bg-default text-default-foreground min-w-10 w-10 h-10 data-hovertrue:opacity-hover typebutton>svg strokecurrentColor fillcurrentColor stroke-width0 viewBox0 0 24 24 height1em width1em xmlnshttp://www.w3.org/2000/svg>path fillnone dM0 0h24v24H0z>/path>path dM12 3a9 9 0 1 0 9 9c0-.46-.04-.92-.1-1.36a5.389 5.389 0 0 1-4.4 2.26 5.403 5.403 0 0 1-3.14-9.8c-.44-.06-.9-.1-1.36-.1z>/path>/svg>/button>/div>/div>/header>div styleopacity:0>div classmin-h-screen>script typeapplication/ld+json>{@context:https://schema.org,@type:Blog,name:Ray 貓的部落格,description:探索 Ray 貓的技術筆記與創業思考。包含 AI、軟體工程、系統架構、創業經驗等深度文章。,url:https://blog.ray-realms.com,author:{@type:Person,name:Ray 貓,url:https://ray-realms.com,sameAs:https://ray-realms.com},blogPost:{@type:BlogPosting,headline:簡單理解怎麼造一個千萬人流量的網站,description:簡單理解,面對超大流量,該如何避免網站掛掉的方式!\n本文以普發現金網站為例,深入淺出解析高併發系統設計:身分證分流策略、負載平衡、Redis快取系統、資料庫分片技術、訊息佇列等核心概念。\n從解決瞬間流量峰值、資料庫瓶頸到資料一致性問題,用生活化比喻帶你理解大型網站架構,適合後端工程師與系統設計初學者。,url:https://blog.ray-realms.com/article/0490bb2c-8e7a-442e-b68b-d9bf80589528,datePublished:2025-11-06T02:33:55.186Z,dateModified:2025-11-22T00:42:27.679Z,author:{@type:Person,name:Ray 貓}},{@type:BlogPosting,headline:MCP 開發,ChatGPT 報錯 Connector is not safe 解法,description:開發 MCP 伺服器整合 ChatGPT 遇到「Connector is not safe」錯誤? 立即了解此常見 OpenAI 錯誤 的核心成因(與隱私描述有關)與最新實用解法! 快速解決連接器安全問題,讓您的應用程式順利上線。,url:https://blog.ray-realms.com/article/cb2d23e3-0e0a-430e-ae5f-6c5375468f95,datePublished:2025-11-01T03:44:23.030Z,dateModified:2025-11-22T02:18:09.887Z,author:{@type:Person,name:Ray 貓}},{@type:BlogPosting,headline:打造你的第一個 MCP Server:從概念到實作,description:想讓 AI 助手存取你的本地筆記、檔案或資料庫?\n本文從實際痛點出發,完整解析 MCP(Model Context Protocol)如何成為 AI 與工具溝通的標準協定。\n透過手把手的程式碼教學,帶你打造第一個 MCP Server,讓 ChatGPT 或 Claude 能自動讀取你的個人知識庫。包含完整實作、ngr...,url:https://blog.ray-realms.com/article/73b7d4c5-fc5e-49e2-a7df-c675c4703300,datePublished:2025-10-23T23:12:13.316Z,dateModified:2025-11-22T13:26:36.468Z,author:{@type:Person,name:Ray 貓}},{@type:BlogPosting,headline:智能合約 Solidity 教學 (下) - Web3.js 實際開發一個 DApp 去中心化應用前端,description:這篇文章繼承了上一篇 智能合約 Solidity 教學 (中)\n我們開發一個 DApp 的前端,理解如何透過 Web3.js 連接區塊鏈上我們所部署的合約。,url:https://blog.ray-realms.com/article/8b0bf0c6-6a00-4764-b686-d0c9c2a4ca28,datePublished:2025-01-29T16:01:29.623Z,dateModified:2025-11-21T22:47:26.050Z,author:{@type:Person,name:Ray 貓}},{@type:BlogPosting,headline:智能合約 Solidity 教學 (中) - MetaMask 與以太坊測試網,description:這篇文章繼承了上一篇 智能合約 Solidity 教學 (上)\n將嘗試使用 MetaMask 連接測試網,如何領取測試幣\n並且在前端整合:透過 Web3.js 連接你的 DApp 與合約。\n,url:https://blog.ray-realms.com/article/e9e78b6b-e7fa-4d70-b892-059ccc057ecf,datePublished:2025-01-29T13:34:30.496Z,dateModified:2025-11-22T08:04:57.599Z,author:{@type:Person,name:Ray 貓}},{@type:BlogPosting,headline:智能合約 Solidity 教學 (上) - Solidity 基礎語法教學,description:在這篇文章中,我們會從 0 基礎開始,逐步介紹智能合約的概念、Solidity 語法,以及如何部署你的第一個智能合約。\n適合對區塊鏈開發有興趣但還沒有 Solidity 經驗的開發者!,url:https://blog.ray-realms.com/article/96e61275-7bde-480a-9a61-fca3c5a8657d,datePublished:2025-01-29T06:09:37.907Z,dateModified:2025-11-22T13:58:27.306Z,author:{@type:Person,name:Ray 貓}},{@type:BlogPosting,headline:COSCUP - 一起來開發一個 notion 吧,多人即時共編筆記分享,description:如何開發一個共編筆記的開發技術,淺談 CRDT、OT 的概念,分享當前生態系與資源\n著重在多人即時共編時如何做到不衝突資料變更\n最終開發一個可以部署的超簡單版本 notion 筆記,url:https://blog.ray-realms.com/article/9455bacb-e12f-4da3-9e5c-3641f720f849,datePublished:2024-08-02T16:41:11.270Z,dateModified:2025-11-22T04:23:36.420Z,author:{@type:Person,name:Ray 貓}},{@type:BlogPosting,headline:技術共學|來開發一個即時的多人聊天網頁與繪圖白板吧!,description:,url:https://blog.ray-realms.com/article/7e7d0745-4276-42ce-8272-091039c381e0,datePublished:2024-07-29T12:19:27.832Z,dateModified:2025-11-20T13:50:19.017Z,author:{@type:Person,name:Ray 貓}},{@type:BlogPosting,headline:VRoid 的模型添增動畫並導入 ThreeJs 網頁,description:,url:https://blog.ray-realms.com/article/4763da84-e8bd-4f64-a3b2-747c6f399733,datePublished:2024-07-29T11:50:42.720Z,dateModified:2025-11-21T23:36:22.104Z,author:{@type:Person,name:Ray 貓}},{@type:BlogPosting,headline:第四部分:進階 JavaScript 與網頁視覺動畫教學大綱,description:在這個章節中,我們將介紹如何讓 JS 操作網頁的資料\n比如說取得使用者讀取的資料、送出文件、修改文字之類的,url:https://blog.ray-realms.com/article/e4d3e885-909a-422b-82dc-1041ecad0aa7,datePublished:2024-06-24T00:11:39.048Z,dateModified:2025-11-22T10:52:05.095Z,author:{@type:Person,name:Ray 貓}}}/script>section classrelative h-screen w-full overflow-hidden>div classabsolute inset-0 z-0>div classw-full h-full relative overflow-hidden aria-labelPixelBlast interactive background>/div>/div>div classpointer-events-none relative z-10 flex h-full flex-col items-center justify-center px-6 text-center transition-all duration-1000 styleopacity:0;transform:translateY(30px)>h1 classmb-8 text-6xl font-bold tracking-tight text-gray-900 dark:text-white md:text-8xl>Ray 貓/h1>p>p classinline-block whitespace-normal break-words will-change-transform uppercase text-4rem leading-none invisible mb-2 text-xs font-medium text-gray-700 dark:text-gray-300 md:text-3xl styletext-align:center;font-family:'Press Start 2P', sans-serif>AI × Software Engineering × Startup Notes/p>/p>p classmax-w-2xl text-base text-gray-600 dark:text-gray-400 md:text-lg>用程式與實驗,記錄打造 AI 產品的每一步。/p>button classpointer-events-auto mt-8 flex items-center justify-between gap-2 rounded-2xl border border-gray-800 px-8 py-2 text-black transition-all hover:bg-gray-800 hover:text-white dark:border-gray-200 dark:text-white dark:hover:bg-gray-200 dark:hover:text-black>span classtext-2xl>svg strokecurrentColor fillcurrentColor stroke-width0 viewBox0 0 20 20 aria-hiddentrue height1em width1em xmlnshttp://www.w3.org/2000/svg>path dM2.003 5.884L10 9.882l7.997-3.998A2 2 0 0016 4H4a2 2 0 00-1.997 1.884z>/path>path dM18 8.118l-8 4-8-4V14a2 2 0 002 2h12a2 2 0 002-2V8.118z>/path>/svg>/span>h3 classfont-bold>訂閱電子報/h3>/button>button classpointer-events-auto absolute bottom-12 animate-bounce transition-transform hover:scale-110 aria-labelScroll to content>svg strokecurrentColor fillcurrentColor stroke-width0 viewBox0 0 24 24 classh-12 w-12 text-gray-600 dark:text-gray-400 height1em width1em xmlnshttp://www.w3.org/2000/svg>path fillnone dM0 0h24v24H0V0z>/path>path dM7.41 8.59 12 13.17l4.59-4.58L18 10l-6 6-6-6 1.41-1.41z>/path>/svg>/button>/div>/section>section classw-full bg-gray-50 py-20 dark:bg-gray-900>div classcontainer mx-auto px-6>div classmb-12 styleopacity:0;transform:translateY(-20px)>h2 classmb-2 text-4xl font-bold text-gray-900 dark:text-white>最新文章/h2>p classtext-gray-600 dark:text-gray-400>持續更新的技術筆記與思考/p>/div>div classspace-y-4 styleopacity:0>article classgroup flex cursor-pointer items-center gap-6 rounded-lg border border-gray-200 bg-white p-6 transition-all duration-300 hover:-translate-x-2 hover:border-gray-400 hover:shadow-lg dark:border-gray-800 dark:bg-gray-950 dark:hover:border-gray-600 styleopacity:0;transform:translateY(20px)>div classflex-shrink-0 rounded-lg bg-gray-100 px-4 py-3 text-center dark:bg-gray-800>div classtext-xs font-medium text-gray-600 dark:text-gray-400>2025/div>div classtext-lg font-bold text-gray-900 dark:text-white>11!-- -->.!-- -->06/div>/div>div classflex-1>h3 classmb-2 text-xl font-bold text-gray-900 dark:text-white>簡單理解怎麼造一個千萬人流量的網站/h3>div classflex items-center gap-3>div classflex items-center gap-1>svg strokecurrentColor fillcurrentColor stroke-width0 viewBox0 0 24 24 classh-3 w-3 text-blue-500 height1em width1em xmlnshttp://www.w3.org/2000/svg>path fillnone dM0 0h24v24H0z>/path>path dM12 2C6.47 2 2 6.47 2 12s4.47 10 10 10 10-4.47 10-10S17.53 2 12 2z>/path>/svg>span classtext-sm font-medium text-gray-600 dark:text-gray-400>Software/span>/div>span classtext-sm text-gray-500 dark:text-gray-500>287!-- --> 次閱讀/span>/div>/div>div classflex-shrink-0 text-gray-400 opacity-0 transition-all duration-300 group-hover:translate-x-2 group-hover:opacity-100 dark:text-gray-600>→/div>/article>article classgroup flex cursor-pointer items-center gap-6 rounded-lg border border-gray-200 bg-white p-6 transition-all duration-300 hover:-translate-x-2 hover:border-gray-400 hover:shadow-lg dark:border-gray-800 dark:bg-gray-950 dark:hover:border-gray-600 styleopacity:0;transform:translateY(20px)>div classflex-shrink-0 rounded-lg bg-gray-100 px-4 py-3 text-center dark:bg-gray-800>div classtext-xs font-medium text-gray-600 dark:text-gray-400>2025/div>div classtext-lg font-bold text-gray-900 dark:text-white>11!-- -->.!-- -->01/div>/div>div classflex-1>h3 classmb-2 text-xl font-bold text-gray-900 dark:text-white>MCP 開發,ChatGPT 報錯 Connector is not safe 解法/h3>div classflex items-center gap-3>div classflex items-center gap-1>svg strokecurrentColor fillcurrentColor stroke-width0 viewBox0 0 24 24 classh-3 w-3 text-blue-500 height1em width1em xmlnshttp://www.w3.org/2000/svg>path fillnone dM0 0h24v24H0z>/path>path dM12 2C6.47 2 2 6.47 2 12s4.47 10 10 10 10-4.47 10-10S17.53 2 12 2z>/path>/svg>span classtext-sm font-medium text-gray-600 dark:text-gray-400>Software/span>/div>span classtext-sm text-gray-500 dark:text-gray-500>133!-- --> 次閱讀/span>/div>/div>div classflex-shrink-0 text-gray-400 opacity-0 transition-all duration-300 group-hover:translate-x-2 group-hover:opacity-100 dark:text-gray-600>→/div>/article>article classgroup flex cursor-pointer items-center gap-6 rounded-lg border border-gray-200 bg-white p-6 transition-all duration-300 hover:-translate-x-2 hover:border-gray-400 hover:shadow-lg dark:border-gray-800 dark:bg-gray-950 dark:hover:border-gray-600 styleopacity:0;transform:translateY(20px)>div classflex-shrink-0 rounded-lg bg-gray-100 px-4 py-3 text-center dark:bg-gray-800>div classtext-xs font-medium text-gray-600 dark:text-gray-400>2025/div>div classtext-lg font-bold text-gray-900 dark:text-white>10!-- -->.!-- -->23/div>/div>div classflex-1>h3 classmb-2 text-xl font-bold text-gray-900 dark:text-white>打造你的第一個 MCP Server:從概念到實作/h3>div classflex items-center gap-3>div classflex items-center gap-1>svg strokecurrentColor fillcurrentColor stroke-width0 viewBox0 0 24 24 classh-3 w-3 text-blue-500 height1em width1em xmlnshttp://www.w3.org/2000/svg>path fillnone dM0 0h24v24H0z>/path>path dM12 2C6.47 2 2 6.47 2 12s4.47 10 10 10 10-4.47 10-10S17.53 2 12 2z>/path>/svg>span classtext-sm font-medium text-gray-600 dark:text-gray-400>Software/span>/div>span classtext-sm text-gray-500 dark:text-gray-500>1890!-- --> 次閱讀/span>/div>/div>div classflex-shrink-0 text-gray-400 opacity-0 transition-all duration-300 group-hover:translate-x-2 group-hover:opacity-100 dark:text-gray-600>→/div>/article>article classgroup flex cursor-pointer items-center gap-6 rounded-lg border border-gray-200 bg-white p-6 transition-all duration-300 hover:-translate-x-2 hover:border-gray-400 hover:shadow-lg dark:border-gray-800 dark:bg-gray-950 dark:hover:border-gray-600 styleopacity:0;transform:translateY(20px)>div classflex-shrink-0 rounded-lg bg-gray-100 px-4 py-3 text-center dark:bg-gray-800>div classtext-xs font-medium text-gray-600 dark:text-gray-400>2025/div>div classtext-lg font-bold text-gray-900 dark:text-white>01!-- -->.!-- -->29/div>/div>div classflex-1>h3 classmb-2 text-xl font-bold text-gray-900 dark:text-white>智能合約 Solidity 教學 (下) - Web3.js 實際開發一個 DApp 去中心化應用前端/h3>div classflex items-center gap-3>div classflex items-center gap-1>svg strokecurrentColor fillcurrentColor stroke-width0 viewBox0 0 24 24 classh-3 w-3 text-blue-500 height1em width1em xmlnshttp://www.w3.org/2000/svg>path fillnone dM0 0h24v24H0z>/path>path dM12 2C6.47 2 2 6.47 2 12s4.47 10 10 10 10-4.47 10-10S17.53 2 12 2z>/path>/svg>span classtext-sm font-medium text-gray-600 dark:text-gray-400>Software/span>/div>span classtext-sm text-gray-500 dark:text-gray-500>750!-- --> 次閱讀/span>/div>/div>div classflex-shrink-0 text-gray-400 opacity-0 transition-all duration-300 group-hover:translate-x-2 group-hover:opacity-100 dark:text-gray-600>→/div>/article>article classgroup flex cursor-pointer items-center gap-6 rounded-lg border border-gray-200 bg-white p-6 transition-all duration-300 hover:-translate-x-2 hover:border-gray-400 hover:shadow-lg dark:border-gray-800 dark:bg-gray-950 dark:hover:border-gray-600 styleopacity:0;transform:translateY(20px)>div classflex-shrink-0 rounded-lg bg-gray-100 px-4 py-3 text-center dark:bg-gray-800>div classtext-xs font-medium text-gray-600 dark:text-gray-400>2025/div>div classtext-lg font-bold text-gray-900 dark:text-white>01!-- -->.!-- -->29/div>/div>div classflex-1>h3 classmb-2 text-xl font-bold text-gray-900 dark:text-white>智能合約 Solidity 教學 (中) - MetaMask 與以太坊測試網/h3>div classflex items-center gap-3>div classflex items-center gap-1>svg strokecurrentColor fillcurrentColor stroke-width0 viewBox0 0 24 24 classh-3 w-3 text-blue-500 height1em width1em xmlnshttp://www.w3.org/2000/svg>path fillnone dM0 0h24v24H0z>/path>path dM12 2C6.47 2 2 6.47 2 12s4.47 10 10 10 10-4.47 10-10S17.53 2 12 2z>/path>/svg>span classtext-sm font-medium text-gray-600 dark:text-gray-400>Software/span>/div>span classtext-sm text-gray-500 dark:text-gray-500>322!-- --> 次閱讀/span>/div>/div>div classflex-shrink-0 text-gray-400 opacity-0 transition-all duration-300 group-hover:translate-x-2 group-hover:opacity-100 dark:text-gray-600>→/div>/article>article classgroup flex cursor-pointer items-center gap-6 rounded-lg border border-gray-200 bg-white p-6 transition-all duration-300 hover:-translate-x-2 hover:border-gray-400 hover:shadow-lg dark:border-gray-800 dark:bg-gray-950 dark:hover:border-gray-600 styleopacity:0;transform:translateY(20px)>div classflex-shrink-0 rounded-lg bg-gray-100 px-4 py-3 text-center dark:bg-gray-800>div classtext-xs font-medium text-gray-600 dark:text-gray-400>2025/div>div classtext-lg font-bold text-gray-900 dark:text-white>01!-- -->.!-- -->29/div>/div>div classflex-1>h3 classmb-2 text-xl font-bold text-gray-900 dark:text-white>智能合約 Solidity 教學 (上) - Solidity 基礎語法教學/h3>div classflex items-center gap-3>div classflex items-center gap-1>svg strokecurrentColor fillcurrentColor stroke-width0 viewBox0 0 24 24 classh-3 w-3 text-blue-500 height1em width1em xmlnshttp://www.w3.org/2000/svg>path fillnone dM0 0h24v24H0z>/path>path dM12 2C6.47 2 2 6.47 2 12s4.47 10 10 10 10-4.47 10-10S17.53 2 12 2z>/path>/svg>span classtext-sm font-medium text-gray-600 dark:text-gray-400>Software/span>/div>span classtext-sm text-gray-500 dark:text-gray-500>1562!-- --> 次閱讀/span>/div>/div>div classflex-shrink-0 text-gray-400 opacity-0 transition-all duration-300 group-hover:translate-x-2 group-hover:opacity-100 dark:text-gray-600>→/div>/article>article classgroup flex cursor-pointer items-center gap-6 rounded-lg border border-gray-200 bg-white p-6 transition-all duration-300 hover:-translate-x-2 hover:border-gray-400 hover:shadow-lg dark:border-gray-800 dark:bg-gray-950 dark:hover:border-gray-600 styleopacity:0;transform:translateY(20px)>div classflex-shrink-0 rounded-lg bg-gray-100 px-4 py-3 text-center dark:bg-gray-800>div classtext-xs font-medium text-gray-600 dark:text-gray-400>2024/div>div classtext-lg font-bold text-gray-900 dark:text-white>08!-- -->.!-- -->02/div>/div>div classflex-1>h3 classmb-2 text-xl font-bold text-gray-900 dark:text-white>COSCUP - 一起來開發一個 notion 吧,多人即時共編筆記分享/h3>div classflex items-center gap-3>div classflex items-center gap-1>svg strokecurrentColor fillcurrentColor stroke-width0 viewBox0 0 24 24 classh-3 w-3 text-blue-500 height1em width1em xmlnshttp://www.w3.org/2000/svg>path fillnone dM0 0h24v24H0z>/path>path dM12 2C6.47 2 2 6.47 2 12s4.47 10 10 10 10-4.47 10-10S17.53 2 12 2z>/path>/svg>span classtext-sm font-medium text-gray-600 dark:text-gray-400>Software/span>/div>span classtext-sm text-gray-500 dark:text-gray-500>1843!-- --> 次閱讀/span>/div>/div>div classflex-shrink-0 text-gray-400 opacity-0 transition-all duration-300 group-hover:translate-x-2 group-hover:opacity-100 dark:text-gray-600>→/div>/article>article classgroup flex cursor-pointer items-center gap-6 rounded-lg border border-gray-200 bg-white p-6 transition-all duration-300 hover:-translate-x-2 hover:border-gray-400 hover:shadow-lg dark:border-gray-800 dark:bg-gray-950 dark:hover:border-gray-600 styleopacity:0;transform:translateY(20px)>div classflex-shrink-0 rounded-lg bg-gray-100 px-4 py-3 text-center dark:bg-gray-800>div classtext-xs font-medium text-gray-600 dark:text-gray-400>2024/div>div classtext-lg font-bold text-gray-900 dark:text-white>07!-- -->.!-- -->29/div>/div>div classflex-1>h3 classmb-2 text-xl font-bold text-gray-900 dark:text-white>技術共學|來開發一個即時的多人聊天網頁與繪圖白板吧!/h3>div classflex items-center gap-3>div classflex items-center gap-1>svg strokecurrentColor fillcurrentColor stroke-width0 viewBox0 0 24 24 classh-3 w-3 text-blue-500 height1em width1em xmlnshttp://www.w3.org/2000/svg>path fillnone dM0 0h24v24H0z>/path>path dM12 2C6.47 2 2 6.47 2 12s4.47 10 10 10 10-4.47 10-10S17.53 2 12 2z>/path>/svg>span classtext-sm font-medium text-gray-600 dark:text-gray-400>Software/span>/div>span classtext-sm text-gray-500 dark:text-gray-500>88!-- --> 次閱讀/span>/div>/div>div classflex-shrink-0 text-gray-400 opacity-0 transition-all duration-300 group-hover:translate-x-2 group-hover:opacity-100 dark:text-gray-600>→/div>/article>article classgroup flex cursor-pointer items-center gap-6 rounded-lg border border-gray-200 bg-white p-6 transition-all duration-300 hover:-translate-x-2 hover:border-gray-400 hover:shadow-lg dark:border-gray-800 dark:bg-gray-950 dark:hover:border-gray-600 styleopacity:0;transform:translateY(20px)>div classflex-shrink-0 rounded-lg bg-gray-100 px-4 py-3 text-center dark:bg-gray-800>div classtext-xs font-medium text-gray-600 dark:text-gray-400>2024/div>div classtext-lg font-bold text-gray-900 dark:text-white>07!-- -->.!-- -->29/div>/div>div classflex-1>h3 classmb-2 text-xl font-bold text-gray-900 dark:text-white>VRoid 的模型添增動畫並導入 ThreeJs 網頁/h3>div classflex items-center gap-3>div classflex items-center gap-1>svg strokecurrentColor fillcurrentColor stroke-width0 viewBox0 0 24 24 classh-3 w-3 text-blue-500 height1em width1em xmlnshttp://www.w3.org/2000/svg>path fillnone dM0 0h24v24H0z>/path>path dM12 2C6.47 2 2 6.47 2 12s4.47 10 10 10 10-4.47 10-10S17.53 2 12 2z>/path>/svg>span classtext-sm font-medium text-gray-600 dark:text-gray-400>Software/span>/div>span classtext-sm text-gray-500 dark:text-gray-500>199!-- --> 次閱讀/span>/div>/div>div classflex-shrink-0 text-gray-400 opacity-0 transition-all duration-300 group-hover:translate-x-2 group-hover:opacity-100 dark:text-gray-600>→/div>/article>article classgroup flex cursor-pointer items-center gap-6 rounded-lg border border-gray-200 bg-white p-6 transition-all duration-300 hover:-translate-x-2 hover:border-gray-400 hover:shadow-lg dark:border-gray-800 dark:bg-gray-950 dark:hover:border-gray-600 styleopacity:0;transform:translateY(20px)>div classflex-shrink-0 rounded-lg bg-gray-100 px-4 py-3 text-center dark:bg-gray-800>div classtext-xs font-medium text-gray-600 dark:text-gray-400>2024/div>div classtext-lg font-bold text-gray-900 dark:text-white>06!-- -->.!-- -->24/div>/div>div classflex-1>h3 classmb-2 text-xl font-bold text-gray-900 dark:text-white>第四部分:進階 JavaScript 與網頁視覺動畫教學大綱/h3>div classflex items-center gap-3>div classflex items-center gap-1>svg strokecurrentColor fillcurrentColor stroke-width0 viewBox0 0 24 24 classh-3 w-3 text-blue-500 height1em width1em xmlnshttp://www.w3.org/2000/svg>path fillnone dM0 0h24v24H0z>/path>path dM12 2C6.47 2 2 6.47 2 12s4.47 10 10 10 10-4.47 10-10S17.53 2 12 2z>/path>/svg>span classtext-sm font-medium text-gray-600 dark:text-gray-400>Software/span>/div>span classtext-sm text-gray-500 dark:text-gray-500>90!-- --> 次閱讀/span>/div>/div>div classflex-shrink-0 text-gray-400 opacity-0 transition-all duration-300 group-hover:translate-x-2 group-hover:opacity-100 dark:text-gray-600>→/div>/article>article classgroup flex cursor-pointer items-center gap-6 rounded-lg border border-gray-200 bg-white p-6 transition-all duration-300 hover:-translate-x-2 hover:border-gray-400 hover:shadow-lg dark:border-gray-800 dark:bg-gray-950 dark:hover:border-gray-600 styleopacity:0;transform:translateY(20px)>div classflex-shrink-0 rounded-lg bg-gray-100 px-4 py-3 text-center dark:bg-gray-800>div classtext-xs font-medium text-gray-600 dark:text-gray-400>2024/div>div classtext-lg font-bold text-gray-900 dark:text-white>06!-- -->.!-- -->23/div>/div>div classflex-1>h3 classmb-2 text-xl font-bold text-gray-900 dark:text-white>第五部分:後端 Node.js 與 Socket.IO/h3>div classflex items-center gap-3>div classflex items-center gap-1>svg strokecurrentColor fillcurrentColor stroke-width0 viewBox0 0 24 24 classh-3 w-3 text-blue-500 height1em width1em xmlnshttp://www.w3.org/2000/svg>path fillnone dM0 0h24v24H0z>/path>path dM12 2C6.47 2 2 6.47 2 12s4.47 10 10 10 10-4.47 10-10S17.53 2 12 2z>/path>/svg>span classtext-sm font-medium text-gray-600 dark:text-gray-400>Software/span>/div>span classtext-sm text-gray-500 dark:text-gray-500>114!-- --> 次閱讀/span>/div>/div>div classflex-shrink-0 text-gray-400 opacity-0 transition-all duration-300 group-hover:translate-x-2 group-hover:opacity-100 dark:text-gray-600>→/div>/article>article classgroup flex cursor-pointer items-center gap-6 rounded-lg border border-gray-200 bg-white p-6 transition-all duration-300 hover:-translate-x-2 hover:border-gray-400 hover:shadow-lg dark:border-gray-800 dark:bg-gray-950 dark:hover:border-gray-600 styleopacity:0;transform:translateY(20px)>div classflex-shrink-0 rounded-lg bg-gray-100 px-4 py-3 text-center dark:bg-gray-800>div classtext-xs font-medium text-gray-600 dark:text-gray-400>2024/div>div classtext-lg font-bold text-gray-900 dark:text-white>06!-- -->.!-- -->23/div>/div>div classflex-1>h3 classmb-2 text-xl font-bold text-gray-900 dark:text-white>第三部分 邏輯操作者 JavaScript/h3>div classflex items-center gap-3>div classflex items-center gap-1>svg strokecurrentColor fillcurrentColor stroke-width0 viewBox0 0 24 24 classh-3 w-3 text-blue-500 height1em width1em xmlnshttp://www.w3.org/2000/svg>path fillnone dM0 0h24v24H0z>/path>path dM12 2C6.47 2 2 6.47 2 12s4.47 10 10 10 10-4.47 10-10S17.53 2 12 2z>/path>/svg>span classtext-sm font-medium text-gray-600 dark:text-gray-400>Software/span>/div>span classtext-sm text-gray-500 dark:text-gray-500>104!-- --> 次閱讀/span>/div>/div>div classflex-shrink-0 text-gray-400 opacity-0 transition-all duration-300 group-hover:translate-x-2 group-hover:opacity-100 dark:text-gray-600>→/div>/article>article classgroup flex cursor-pointer items-center gap-6 rounded-lg border border-gray-200 bg-white p-6 transition-all duration-300 hover:-translate-x-2 hover:border-gray-400 hover:shadow-lg dark:border-gray-800 dark:bg-gray-950 dark:hover:border-gray-600 styleopacity:0;transform:translateY(20px)>div classflex-shrink-0 rounded-lg bg-gray-100 px-4 py-3 text-center dark:bg-gray-800>div classtext-xs font-medium text-gray-600 dark:text-gray-400>2024/div>div classtext-lg font-bold text-gray-900 dark:text-white>06!-- -->.!-- -->12/div>/div>div classflex-1>h3 classmb-2 text-xl font-bold text-gray-900 dark:text-white>第二部分:視覺前端設計師 CSS/h3>div classflex items-center gap-3>div classflex items-center gap-1>svg strokecurrentColor fillcurrentColor stroke-width0 viewBox0 0 24 24 classh-3 w-3 text-blue-500 height1em width1em xmlnshttp://www.w3.org/2000/svg>path fillnone dM0 0h24v24H0z>/path>path dM12 2C6.47 2 2 6.47 2 12s4.47 10 10 10 10-4.47 10-10S17.53 2 12 2z>/path>/svg>span classtext-sm font-medium text-gray-600 dark:text-gray-400>Software/span>/div>span classtext-sm text-gray-500 dark:text-gray-500>256!-- --> 次閱讀/span>/div>/div>div classflex-shrink-0 text-gray-400 opacity-0 transition-all duration-300 group-hover:translate-x-2 group-hover:opacity-100 dark:text-gray-600>→/div>/article>article classgroup flex cursor-pointer items-center gap-6 rounded-lg border border-gray-200 bg-white p-6 transition-all duration-300 hover:-translate-x-2 hover:border-gray-400 hover:shadow-lg dark:border-gray-800 dark:bg-gray-950 dark:hover:border-gray-600 styleopacity:0;transform:translateY(20px)>div classflex-shrink-0 rounded-lg bg-gray-100 px-4 py-3 text-center dark:bg-gray-800>div classtext-xs font-medium text-gray-600 dark:text-gray-400>2024/div>div classtext-lg font-bold text-gray-900 dark:text-white>06!-- -->.!-- -->10/div>/div>div classflex-1>h3 classmb-2 text-xl font-bold text-gray-900 dark:text-white>第一部分:傳統前端大解密 HTML/h3>div classflex items-center gap-3>div classflex items-center gap-1>svg strokecurrentColor fillcurrentColor stroke-width0 viewBox0 0 24 24 classh-3 w-3 text-blue-500 height1em width1em xmlnshttp://www.w3.org/2000/svg>path fillnone dM0 0h24v24H0z>/path>path dM12 2C6.47 2 2 6.47 2 12s4.47 10 10 10 10-4.47 10-10S17.53 2 12 2z>/path>/svg>span classtext-sm font-medium text-gray-600 dark:text-gray-400>Software/span>/div>span classtext-sm text-gray-500 dark:text-gray-500>1703!-- --> 次閱讀/span>/div>/div>div classflex-shrink-0 text-gray-400 opacity-0 transition-all duration-300 group-hover:translate-x-2 group-hover:opacity-100 dark:text-gray-600>→/div>/article>/div>/div>/section>section classw-full bg-white py-20 dark:bg-gray-950>div classcontainer mx-auto px-6>div classmb-12 text-center>h2 classmb-2 text-4xl font-bold text-gray-900 dark:text-white>關於我/h2>p classtext-gray-600 dark:text-gray-400>正在用程式碼探索 AI 的可能性/p>/div>div classmx-auto flex max-w-md justify-center>div classpc-card-wrapper style--icon:url(<Placeholder for icon URL>);--grain:url(/r.png);--behind-gradient:radial-gradient(farthest-side circle at var(--pointer-x) var(--pointer-y),hsla(266,100%,90%,var(--card-opacity)) 4%,hsla(266,50%,80%,calc(var(--card-opacity)*0.75)) 10%,hsla(266,25%,70%,calc(var(--card-opacity)*0.5)) 50%,hsla(266,0%,60%,0) 100%),radial-gradient(35% 52% at 55% 20%,#00ffaac4 0%,#073aff00 100%),radial-gradient(100% 100% at 50% 50%,#00c1ffff 1%,#073aff00 76%),conic-gradient(from 124deg at 50% 50%,#c137ffff 0%,#07c6ffff 40%,#07c6ffff 60%,#c137ffff 100%);--inner-gradient:linear-gradient(145deg,#60496e8c 0%,#71C4FF44 100%)>section classpc-card>div classpc-inside>div classpc-shine>/div>div classpc-glare>/div>div classpc-content pc-avatar-content>img classavatar src/ray.png altUser avatar loadinglazy/>div classpc-user-info>div classpc-user-details>div classpc-mini-avatar>img src/ray.png altUser mini avatar loadinglazy/>/div>div classpc-user-text>div classpc-handle>@!-- -->ray948787/div>div classpc-status>Online/div>/div>/div>button classpc-contact-btn stylepointer-events:auto typebutton aria-labelContact user>Contact/button>/div>/div>div classpc-content>div classpc-details>h3>/h3>p>/p>/div>/div>/div>/section>/div>/div>/div>/section>footer classborder-t border-gray-200 bg-white py-12 dark:border-gray-800 dark:bg-gray-950>div classcontainer mx-auto px-6>div classflex flex-col items-center justify-between gap-6 md:flex-row>div classtext-center md:text-left>p classtext-gray-600 dark:text-gray-400>© 2024 Ray 貓的部落格 · AI × Software Engineering × Startup Notes/p>/div>div classflex flex-col gap-3 sm:flex-row sm:gap-4>button classflex items-center gap-2 rounded-full border border-gray-300 px-6 py-2 text-sm font-medium text-gray-700 transition-all hover:border-gray-900 hover:bg-gray-900 hover:text-white dark:border-gray-700 dark:text-gray-300 dark:hover:border-white dark:hover:bg-white dark:hover:text-gray-900>svg strokecurrentColor fillcurrentColor stroke-width0 viewBox0 0 24 24 classh-5 w-5 height1em width1em xmlnshttp://www.w3.org/2000/svg>path fillnone dM0 0h24v24H0z>/path>path dM20 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 4-8 5-8-5V6l8 5 8-5v2z>/path>/svg>訂閱電子報/button>a classflex items-center gap-2 rounded-full border border-gray-300 px-6 py-2 text-sm font-medium text-gray-700 transition-all hover:border-gray-900 hover:bg-gray-900 hover:text-white dark:border-gray-700 dark:text-gray-300 dark:hover:border-white dark:hover:bg-white dark:hover:text-gray-900 href/feed.xml>svg strokecurrentColor fillcurrentColor stroke-width0 viewBox0 0 24 24 classh-5 w-5 height1em width1em xmlnshttp://www.w3.org/2000/svg>path fillnone dM0 0h24v24H0z>/path>circle cx6.18 cy17.82 r2.18>/circle>path dM4 4.44v2.83c7.03 0 12.73 5.7 12.73 12.73h2.83c0-8.59-6.97-15.56-15.56-15.56zm0 5.66v2.83c3.9 0 7.07 3.17 7.07 7.07h2.83c0-5.47-4.43-9.9-9.9-9.9z>/path>/svg>RSS 訂閱/a>/div>/div>/div>/footer>/div>!--$-->!--/$-->/div>/div>script src/_next/static/chunks/webpack-f2c0234491d2b65b.js id_R_ async>/script>script>(self.__next_fself.__next_f||).push(0)/script>script>self.__next_f.push(1,1:\$Sreact.fragment\\n)/script>script>self.__next_f.push(1,2:I77684,\9717\,\static/chunks/c3536442-5b720253f760e76c.js\,\1258\,\static/chunks/e0b518d6-c6ee72fbc9b3cbf2.js\,\6422\,\static/chunks/9db99176-68080c29ff87e383.js\,\1942\,\static/chunks/261ab18d-a828c13849e44ee4.js\,\2783\,\static/chunks/2783-b9a383cf8d2da686.js\,\7125\,\static/chunks/7125-7adaf07f9588b340.js\,\6044\,\static/chunks/6044-6a843023a95c7b69.js\,\6046\,\static/chunks/6046-a15a9c22edfc2449.js\,\4705\,\static/chunks/4705-0f8c0b1c3eb3293d.js\,\2391\,\static/chunks/2391-8ec1f698fe165feb.js\,\1009\,\static/chunks/1009-f7f224d177e229d1.js\,\9006\,\static/chunks/9006-8fe15af56f6d3c05.js\,\5216\,\static/chunks/5216-7e0407f0148493a0.js\,\8968\,\static/chunks/8968-15c5d78452c480e9.js\,\3913\,\static/chunks/3913-8d17ea812b32af8a.js\,\5356\,\static/chunks/5356-ace1dbdd0cac554a.js\,\8681\,\static/chunks/8681-581ccb3c782bf8ae.js\,\1467\,\static/chunks/1467-632b567137ecbb7b.js\,\7970\,\static/chunks/7970-3125462d67a87e21.js\,\7177\,\static/chunks/app/layout-a77ba2f807a0be7c.js\,\default\\n)/script>script>self.__next_f.push(1,3:I88637,\9717\,\static/chunks/c3536442-5b720253f760e76c.js\,\1258\,\static/chunks/e0b518d6-c6ee72fbc9b3cbf2.js\,\6422\,\static/chunks/9db99176-68080c29ff87e383.js\,\1942\,\static/chunks/261ab18d-a828c13849e44ee4.js\,\2783\,\static/chunks/2783-b9a383cf8d2da686.js\,\7125\,\static/chunks/7125-7adaf07f9588b340.js\,\6044\,\static/chunks/6044-6a843023a95c7b69.js\,\6046\,\static/chunks/6046-a15a9c22edfc2449.js\,\4705\,\static/chunks/4705-0f8c0b1c3eb3293d.js\,\2391\,\static/chunks/2391-8ec1f698fe165feb.js\,\1009\,\static/chunks/1009-f7f224d177e229d1.js\,\9006\,\static/chunks/9006-8fe15af56f6d3c05.js\,\5216\,\static/chunks/5216-7e0407f0148493a0.js\,\8968\,\static/chunks/8968-15c5d78452c480e9.js\,\3913\,\static/chunks/3913-8d17ea812b32af8a.js\,\5356\,\static/chunks/5356-ace1dbdd0cac554a.js\,\8681\,\static/chunks/8681-581ccb3c782bf8ae.js\,\1467\,\static/chunks/1467-632b567137ecbb7b.js\,\7970\,\static/chunks/7970-3125462d67a87e21.js\,\7177\,\static/chunks/app/layout-a77ba2f807a0be7c.js\,\NextUIProvider\\n)/script>script>self.__next_f.push(1,4:I4705,\9717\,\static/chunks/c3536442-5b720253f760e76c.js\,\1258\,\static/chunks/e0b518d6-c6ee72fbc9b3cbf2.js\,\6422\,\static/chunks/9db99176-68080c29ff87e383.js\,\1942\,\static/chunks/261ab18d-a828c13849e44ee4.js\,\2783\,\static/chunks/2783-b9a383cf8d2da686.js\,\7125\,\static/chunks/7125-7adaf07f9588b340.js\,\6044\,\static/chunks/6044-6a843023a95c7b69.js\,\6046\,\static/chunks/6046-a15a9c22edfc2449.js\,\4705\,\static/chunks/4705-0f8c0b1c3eb3293d.js\,\2391\,\static/chunks/2391-8ec1f698fe165feb.js\,\1009\,\static/chunks/1009-f7f224d177e229d1.js\,\9006\,\static/chunks/9006-8fe15af56f6d3c05.js\,\5216\,\static/chunks/5216-7e0407f0148493a0.js\,\8968\,\static/chunks/8968-15c5d78452c480e9.js\,\3913\,\static/chunks/3913-8d17ea812b32af8a.js\,\5356\,\static/chunks/5356-ace1dbdd0cac554a.js\,\8681\,\static/chunks/8681-581ccb3c782bf8ae.js\,\1467\,\static/chunks/1467-632b567137ecbb7b.js\,\7970\,\static/chunks/7970-3125462d67a87e21.js\,\7177\,\static/chunks/app/layout-a77ba2f807a0be7c.js\,\Toaster\\n)/script>script>self.__next_f.push(1,5:I39625,\9717\,\static/chunks/c3536442-5b720253f760e76c.js\,\1258\,\static/chunks/e0b518d6-c6ee72fbc9b3cbf2.js\,\6422\,\static/chunks/9db99176-68080c29ff87e383.js\,\1942\,\static/chunks/261ab18d-a828c13849e44ee4.js\,\2783\,\static/chunks/2783-b9a383cf8d2da686.js\,\7125\,\static/chunks/7125-7adaf07f9588b340.js\,\6044\,\static/chunks/6044-6a843023a95c7b69.js\,\6046\,\static/chunks/6046-a15a9c22edfc2449.js\,\4705\,\static/chunks/4705-0f8c0b1c3eb3293d.js\,\2391\,\static/chunks/2391-8ec1f698fe165feb.js\,\1009\,\static/chunks/1009-f7f224d177e229d1.js\,\9006\,\static/chunks/9006-8fe15af56f6d3c05.js\,\5216\,\static/chunks/5216-7e0407f0148493a0.js\,\8968\,\static/chunks/8968-15c5d78452c480e9.js\,\3913\,\static/chunks/3913-8d17ea812b32af8a.js\,\5356\,\static/chunks/5356-ace1dbdd0cac554a.js\,\8681\,\static/chunks/8681-581ccb3c782bf8ae.js\,\1467\,\static/chunks/1467-632b567137ecbb7b.js\,\7970\,\static/chunks/7970-3125462d67a87e21.js\,\7177\,\static/chunks/app/layout-a77ba2f807a0be7c.js\,\WebVitals\\n)/script>script>self.__next_f.push(1,6:I14260,\9717\,\static/chunks/c3536442-5b720253f760e76c.js\,\1258\,\static/chunks/e0b518d6-c6ee72fbc9b3cbf2.js\,\6422\,\static/chunks/9db99176-68080c29ff87e383.js\,\1942\,\static/chunks/261ab18d-a828c13849e44ee4.js\,\2783\,\static/chunks/2783-b9a383cf8d2da686.js\,\7125\,\static/chunks/7125-7adaf07f9588b340.js\,\6044\,\static/chunks/6044-6a843023a95c7b69.js\,\6046\,\static/chunks/6046-a15a9c22edfc2449.js\,\4705\,\static/chunks/4705-0f8c0b1c3eb3293d.js\,\2391\,\static/chunks/2391-8ec1f698fe165feb.js\,\1009\,\static/chunks/1009-f7f224d177e229d1.js\,\9006\,\static/chunks/9006-8fe15af56f6d3c05.js\,\5216\,\static/chunks/5216-7e0407f0148493a0.js\,\8968\,\static/chunks/8968-15c5d78452c480e9.js\,\3913\,\static/chunks/3913-8d17ea812b32af8a.js\,\5356\,\static/chunks/5356-ace1dbdd0cac554a.js\,\8681\,\static/chunks/8681-581ccb3c782bf8ae.js\,\1467\,\static/chunks/1467-632b567137ecbb7b.js\,\7970\,\static/chunks/7970-3125462d67a87e21.js\,\7177\,\static/chunks/app/layout-a77ba2f807a0be7c.js\,\default\\n)/script>script>self.__next_f.push(1,7:I32031,,\\\n8:I69314,\2783\,\static/chunks/2783-b9a383cf8d2da686.js\,\5216\,\static/chunks/5216-7e0407f0148493a0.js\,\6397\,\static/chunks/app/template-b17f1369099947c1.js\,\default\\n9:I57347,,\\\nb:I35798,,\OutletBoundary\\nd:I47563,,\AsyncMetadataOutlet\\nf:I35798,,\ViewportBoundary\\n11:I35798,,\MetadataBoundary\\n12:\$Sreact.suspense\\n14:I88357,,\\\n:HL\/_next/static/media/493c3cff6f1bd7f1-s.p.woff2\,\font\,{\crossOrigin\:\\,\type\:\font/woff2\}\n:HL\/_next/static/media/e4af272ccee01ff0-s.p.woff2\,\font\,{\crossOrigin\:\\,\type\:\font/woff2\}\n:HL\/_next/static/css/081a0afca5a9bd20.css\,\style\\n:HL\/_next/static/css/31f1fbdb5c9c2d2d.css\,\style\\n:HL\/_next/static/css/36c8165c3c750958.css\,\style\\n:HL\/_next/static/css/a72ea4d34b725941.css\,\style\\n:HL\/_next/static/css/39c55841169ff4a9.css\,\style\\n)/script>script>self.__next_f.push(1,0:{\P\:null,\b\:\nZ0RUrK8HesL3EXqKMCv9\,\p\:\\,\c\:\\,\\,\i\:false,\f\:\\,{\children\:\__PAGE__\,{}},\$undefined\,\$undefined\,true,\\,\$\,\$1\,\c\,{\children\:\$\,\link\,\0\,{\rel\:\stylesheet\,\href\:\/_next/static/css/081a0afca5a9bd20.css\,\precedence\:\next\,\crossOrigin\:\$undefined\,\nonce\:\$undefined\},\$\,\link\,\1\,{\rel\:\stylesheet\,\href\:\/_next/static/css/31f1fbdb5c9c2d2d.css\,\precedence\:\next\,\crossOrigin\:\$undefined\,\nonce\:\$undefined\},\$\,\link\,\2\,{\rel\:\stylesheet\,\href\:\/_next/static/css/36c8165c3c750958.css\,\precedence\:\next\,\crossOrigin\:\$undefined\,\nonce\:\$undefined\},\$\,\link\,\3\,{\rel\:\stylesheet\,\href\:\/_next/static/css/a72ea4d34b725941.css\,\precedence\:\next\,\crossOrigin\:\$undefined\,\nonce\:\$undefined\},\$\,\html\,null,{\lang\:\zh-TW\,\children\:\$\,\body\,null,{\className\:\__variable_f367f3 __variable_8b92e0\,\children\:\$\,\$L2\,null,{\children\:\$\,\$L3\,null,{\children\:\$\,\$L4\,null,{},\$\,\$L5\,null,{},\$\,\$L6\,null,{},\$\,\$L7\,null,{\parallelRouterKey\:\children\,\error\:\$undefined\,\errorStyles\:\$undefined\,\errorScripts\:\$undefined\,\template\:\$\,\$L8\,null,{\children\:\$\,\$L9\,null,{}},\templateStyles\:,\templateScripts\:,\notFound\:\$\,\title\,null,{\children\:\404: This page could not be found.\},\$\,\div\,null,{\style\:{\fontFamily\:\system-ui,\\\Segoe UI\\\,Roboto,Helvetica,Arial,sans-serif,\\\Apple Color Emoji\\\,\\\Segoe UI Emoji\\\\,\height\:\100vh\,\textAlign\:\center\,\display\:\flex\,\flexDirection\:\column\,\alignItems\:\center\,\justifyContent\:\center\},\children\:\$\,\div\,null,{\children\:\$\,\style\,null,{\dangerouslySetInnerHTML\:{\__html\:\body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}\}},\$\,\h1\,null,{\className\:\next-error-h1\,\style\:{\display\:\inline-block\,\margin\:\0 20px 0 0\,\padding\:\0 23px 0 0\,\fontSize\:24,\fontWeight\:500,\verticalAlign\:\top\,\lineHeight\:\49px\},\children\:404},\$\,\div\,null,{\style\:{\display\:\inline-block\},\children\:\$\,\h2\,null,{\style\:{\fontSize\:14,\fontWeight\:400,\lineHeight\:\49px\,\margin\:0},\children\:\This page could not be found.\}}}},,\forbidden\:\$undefined\,\unauthorized\:\$undefined\}}}}}},{\children\:\__PAGE__\,\$\,\$1\,\c\,{\children\:\$La\,\$\,\link\,\0\,{\rel\:\stylesheet\,\href\:\/_next/static/css/39c55841169ff4a9.css\,\precedence\:\next\,\crossOrigin\:\$undefined\,\nonce\:\$undefined\},\$\,\$Lb\,null,{\children\:\$Lc\,\$\,\$Ld\,null,{\promise\:\$@e\}}},{},null,false},null,false,\$\,\$1\,\h\,{\children\:null,\$\,\$Lf\,null,{\children\:\$L10\},\$\,\meta\,null,{\name\:\next-size-adjust\,\content\:\\},\$\,\$L11\,null,{\children\:\$\,\div\,null,{\hidden\:true,\children\:\$\,\$12\,null,{\fallback\:null,\children\:\$L13\}}}},false,\m\:\$undefined\,\G\:\$14\,,\s\:false,\S\:true}\n)/script>script>self.__next_f.push(1,10:\$\,\meta\,\0\,{\charSet\:\utf-8\},\$\,\meta\,\1\,{\name\:\viewport\,\content\:\widthdevice-width, initial-scale1\}\nc:null\n)/script>script>self.__next_f.push(1,15:I59507,,\IconMark\\n)/script>script>self.__next_f.push(1,e:{\metadata\:\$\,\title\,\0\,{\children\:\首頁\},\$\,\meta\,\1\,{\name\:\description\,\content\:\探索 Ray 貓的技術筆記與創業思考。包含 AI、軟體工程、系統架構、創業經驗等深度文章。\},\$\,\link\,\2\,{\rel\:\author\,\href\:\https://ray-realms.com\},\$\,\meta\,\3\,{\name\:\author\,\content\:\Ray 貓\},\$\,\link\,\4\,{\rel\:\manifest\,\href\:\/manifest.json\,\crossOrigin\:\$undefined\},\$\,\meta\,\5\,{\name\:\keywords\,\content\:\AI,Software Engineering,Startup,軟體工程,新創,技術部落格,程式設計,Ray 貓\},\$\,\meta\,\6\,{\name\:\creator\,\content\:\Ray 貓\},\$\,\meta\,\7\,{\name\:\publisher\,\content\:\Ray 貓\},\$\,\meta\,\8\,{\name\:\robots\,\content\:\index, follow\},\$\,\meta\,\9\,{\name\:\googlebot\,\content\:\index, follow, max-video-preview:-1, max-image-preview:large, max-snippet:-1\},\$\,\meta\,\10\,{\name\:\mobile-web-app-capable\,\content\:\yes\},\$\,\link\,\11\,{\rel\:\canonical\,\href\:\https://blog.ray-realms.com\},\$\,\meta\,\12\,{\name\:\format-detection\,\content\:\telephoneno\},\$\,\meta\,\13\,{\name\:\google-site-verification\,\content\:\google-site-verification-code\},\$\,\meta\,\14\,{\name\:\mobile-web-app-capable\,\content\:\yes\},\$\,\meta\,\15\,{\name\:\apple-mobile-web-app-title\,\content\:\Ray 貓的部落格\},\$\,\meta\,\16\,{\name\:\apple-mobile-web-app-status-bar-style\,\content\:\default\},\$\,\meta\,\17\,{\property\:\og:title\,\content\:\Ray 貓的部落格 | AI × Software Engineering × Startup Notes\},\$\,\meta\,\18\,{\property\:\og:description\,\content\:\探索 Ray 貓的技術筆記與創業思考。包含 AI、軟體工程、系統架構、創業經驗等深度文章。\},\$\,\meta\,\19\,{\property\:\og:url\,\content\:\https://blog.ray-realms.com\},\$\,\meta\,\20\,{\property\:\og:type\,\content\:\website\},\$\,\meta\,\21\,{\name\:\twitter:card\,\content\:\summary_large_image\},\$\,\meta\,\22\,{\name\:\twitter:title\,\content\:\Ray 貓的部落格 | AI × Software Engineering × Startup Notes\},\$\,\meta\,\23\,{\name\:\twitter:description\,\content\:\探索 Ray 貓的技術筆記與創業思考。包含 AI、軟體工程、系統架構、創業經驗等深度文章。\},\$\,\link\,\24\,{\rel\:\icon\,\href\:\/favicon.ico\,\type\:\image/x-icon\,\sizes\:\32x32\},\$\,\link\,\25\,{\rel\:\icon\,\href\:\/favicon.ico\,\sizes\:\any\},\$\,\link\,\26\,{\rel\:\icon\,\href\:\/icon.svg\,\type\:\image/svg+xml\},\$\,\link\,\27\,{\rel\:\icon\,\href\:\/icon-192.png\,\sizes\:\192x192\,\type\:\image/png\},\$\,\link\,\28\,{\rel\:\icon\,\href\:\/icon-512.png\,\sizes\:\512x512\,\type\:\image/png\},\$\,\link\,\29\,{\rel\:\apple-touch-icon\,\href\:\/apple-touch-icon.png\,\sizes\:\180x180\},\$\,\$L15\,\30\,{},\error\:null,\digest\:\$undefined\}\n)/script>script>self.__next_f.push(1,13:\$e:metadata\\n)/script>script>self.__next_f.push(1,16:T1661,)/script>script>self.__next_f.push(1,{\@context\:\https://schema.org\,\@type\:\Blog\,\name\:\Ray 貓的部落格\,\description\:\探索 Ray 貓的技術筆記與創業思考。包含 AI、軟體工程、系統架構、創業經驗等深度文章。\,\url\:\https://blog.ray-realms.com\,\author\:{\@type\:\Person\,\name\:\Ray 貓\,\url\:\https://ray-realms.com\,\sameAs\:\https://ray-realms.com\},\blogPost\:{\@type\:\BlogPosting\,\headline\:\簡單理解怎麼造一個千萬人流量的網站\,\description\:\簡單理解,面對超大流量,該如何避免網站掛掉的方式!\\n本文以普發現金網站為例,深入淺出解析高併發系統設計:身分證分流策略、負載平衡、Redis快取系統、資料庫分片技術、訊息佇列等核心概念。\\n從解決瞬間流量峰值、資料庫瓶頸到資料一致性問題,用生活化比喻帶你理解大型網站架構,適合後端工程師與系統設計初學者。\,\url\:\https://blog.ray-realms.com/article/0490bb2c-8e7a-442e-b68b-d9bf80589528\,\datePublished\:\2025-11-06T02:33:55.186Z\,\dateModified\:\2025-11-22T00:42:27.679Z\,\author\:{\@type\:\Person\,\name\:\Ray 貓\}},{\@type\:\BlogPosting\,\headline\:\MCP 開發,ChatGPT 報錯 Connector is not safe 解法\,\description\:\開發 MCP 伺服器整合 ChatGPT 遇到「Connector is not safe」錯誤? 立即了解此常見 OpenAI 錯誤 的核心成因(與隱私描述有關)與最新實用解法! 快速解決連接器安全問題,讓您的應用程式順利上線。\,\url\:\https://blog.ray-realms.com/article/cb2d23e3-0e0a-430e-ae5f-6c5375468f95\,\datePublished\:\2025-11-01T03:44:23.030Z\,\dateModified\:\2025-11-22T02:18:09.887Z\,\author\:{\@type\:\Person\,\name\:\Ray 貓\}},{\@type\:\BlogPosting\,\headline\:\打造你的第一個 MCP Server:從概念到實作\,\description\:\想讓 AI 助手存取你的本地筆記、檔案或資料庫?\\n本文從實際痛點出發,完整解析 MCP(Model Context Protocol)如何成為 AI 與工具溝通的標準協定。\\n透過手把手的程式碼教學,帶你打造第一個 MCP Server,讓 ChatGPT 或 Claude 能自動讀取你的個人知識庫。包含完整實作、ngr...\,\url\:\https://blog.ray-realms.com/article/73b7d4c5-fc5e-49e2-a7df-c675c4703300\,\datePublished\:\2025-10-23T23:12:13.316Z\,\dateModified\:\2025-11-22T13:26:36.468Z\,\author\:{\@type\:\Person\,\name\:\Ray 貓\}},{\@type\:\BlogPosting\,\headline\:\智能合約 Solidity 教學 (下) - Web3.js 實際開發一個 DApp 去中心化應用前端\,\description\:\這篇文章繼承了上一篇 智能合約 Solidity 教學 (中)\\n我們開發一個 DApp 的前端,理解如何透過 Web3.js 連接區塊鏈上我們所部署的合約。\,\url\:\https://blog.ray-realms.com/article/8b0bf0c6-6a00-4764-b686-d0c9c2a4ca28\,\datePublished\:\2025-01-29T16:01:29.623Z\,\dateModified\:\2025-11-21T22:47:26.050Z\,\author\:{\@type\:\Person\,\name\:\Ray 貓\}},{\@type\:\BlogPosting\,\headline\:\智能合約 Solidity 教學 (中) - MetaMask 與以太坊測試網\,\description\:\這篇文章繼承了上一篇 智能合約 Solidity 教學 (上)\\n將嘗試使用 MetaMask 連接測試網,如何領取測試幣\\n並且在前端整合:透過 Web3.js 連接你的 DApp 與合約。\\n\,\url\:\https://blog.ray-realms.com/article/e9e78b6b-e7fa-4d70-b892-059ccc057ecf\,\datePublished\:\2025-01-29T13:34:30.496Z\,\dateModified\:\2025-11-22T08:04:57.599Z\,\author\:{\@type\:\Person\,\name\:\Ray 貓\}},{\@type\:\BlogPosting\,\headline\:\智能合約 Solidity 教學 (上) - Solidity 基礎語法教學\,\description\:\在這篇文章中,我們會從 0 基礎開始,逐步介紹智能合約的概念、Solidity 語法,以及如何部署你的第一個智能合約。\\n適合對區塊鏈開發有興趣但還沒有 Solidity 經驗的開發者!\,\url\:\https://blog.ray-realms.com/article/96e61275-7bde-480a-9a61-fca3c5a8657d\,\datePublished\:\2025-01-29T06:09:37.907Z\,\dateModified\:\2025-11-22T13:58:27.306Z\,\author\:{\@type\:\Person\,\name\:\Ray 貓\}},{\@type\:\BlogPosting\,\headline\:\COSCUP - 一起來開發一個 notion 吧,多人即時共編筆記分享\,\description\:\如何開發一個共編筆記的開發技術,淺談 CRDT、OT 的概念,分享當前生態系與資源\\n著重在多人即時共編時如何做到不衝突資料變更\\n最終開發一個可以部署的超簡單版本 notion 筆記\,\url\:\https://blog.ray-realms.com/article/9455bacb-e12f-4da3-9e5c-3641f720f849\,\datePublished\:\2024-08-02T16:41:11.270Z\,\dateModified\:\2025-11-22T04:23:36.420Z\,\author\:{\@type\:\Person\,\name\:\Ray 貓\}},{\@type\:\BlogPosting\,\headline\:\技術共學|來開發一個即時的多人聊天網頁與繪圖白板吧!\,\description\:\\,\url\:\https://blog.ray-realms.com/article/7e7d0745-4276-42ce-8272-091039c381e0\,\datePublished\:\2024-07-29T12:19:27.832Z\,\dateModified\:\2025-11-20T13:50:19.017Z\,\author\:{\@type\:\Person\,\name\:\Ray 貓\}},{\@type\:\BlogPosting\,\headline\:\VRoid 的模型添增動畫並導入 ThreeJs 網頁\,\description\:\\,\url\:\https://blog.ray-realms.com/article/4763da84-e8bd-4f64-a3b2-747c6f399733\,\datePublished\:\2024-07-29T11:50:42.720Z\,\dateModified\:\2025-11-21T23:36:22.104Z\,\author\:{\@type\:\Person\,\name\:\Ray 貓\}},{\@type\:\BlogPosting\,\headline\:\第四部分:進階 JavaScript 與網頁視覺動畫教學大綱\,\description\:\在這個章節中,我們將介紹如何讓 JS 操作網頁的資料\\n比如說取得使用者讀取的資料、送出文件、修改文字之類的\,\url\:\https://blog.ray-realms.com/article/e4d3e885-909a-422b-82dc-1041ecad0aa7\,\datePublished\:\2024-06-24T00:11:39.048Z\,\dateModified\:\2025-11-22T10:52:05.095Z\,\author\:{\@type\:\Person\,\name\:\Ray 貓\}}})/script>script>self.__next_f.push(1,a:\$\,\div\,null,{\className\:\min-h-screen\,\children\:\$\,\script\,null,{\type\:\application/ld+json\,\dangerouslySetInnerHTML\:{\__html\:\$16\}},\$L17\,\$L18\,\$L19\,\$L1a\}\n)/script>script>self.__next_f.push(1,1b:I57346,\9717\,\static/chunks/c3536442-5b720253f760e76c.js\,\6124\,\static/chunks/ae80f326-2d1ce9f306c3e117.js\,\3471\,\static/chunks/40f94348-3665981d394436a4.js\,\2808\,\static/chunks/cd2bc502-dccf3f627b79fb7b.js\,\1064\,\static/chunks/d5504597-14b1fdf76ee20456.js\,\9340\,\static/chunks/0b0944fb-9fdc250e44912132.js\,\2783\,\static/chunks/2783-b9a383cf8d2da686.js\,\7125\,\static/chunks/7125-7adaf07f9588b340.js\,\6044\,\static/chunks/6044-6a843023a95c7b69.js\,\6046\,\static/chunks/6046-a15a9c22edfc2449.js\,\4705\,\static/chunks/4705-0f8c0b1c3eb3293d.js\,\438\,\static/chunks/438-376903fca45b8284.js\,\5216\,\static/chunks/5216-7e0407f0148493a0.js\,\5942\,\static/chunks/5942-ea32bc250b281e90.js\,\8974\,\static/chunks/app/page-7344702a57691eb0.js\,\default\\n)/script>script>self.__next_f.push(1,1c:I89372,\9717\,\static/chunks/c3536442-5b720253f760e76c.js\,\6124\,\static/chunks/ae80f326-2d1ce9f306c3e117.js\,\3471\,\static/chunks/40f94348-3665981d394436a4.js\,\2808\,\static/chunks/cd2bc502-dccf3f627b79fb7b.js\,\1064\,\static/chunks/d5504597-14b1fdf76ee20456.js\,\9340\,\static/chunks/0b0944fb-9fdc250e44912132.js\,\2783\,\static/chunks/2783-b9a383cf8d2da686.js\,\7125\,\static/chunks/7125-7adaf07f9588b340.js\,\6044\,\static/chunks/6044-6a843023a95c7b69.js\,\6046\,\static/chunks/6046-a15a9c22edfc2449.js\,\4705\,\static/chunks/4705-0f8c0b1c3eb3293d.js\,\438\,\static/chunks/438-376903fca45b8284.js\,\5216\,\static/chunks/5216-7e0407f0148493a0.js\,\5942\,\static/chunks/5942-ea32bc250b281e90.js\,\8974\,\static/chunks/app/page-7344702a57691eb0.js\,\default\\n)/script>script>self.__next_f.push(1,59:I3836,\9717\,\static/chunks/c3536442-5b720253f760e76c.js\,\6124\,\static/chunks/ae80f326-2d1ce9f306c3e117.js\,\3471\,\static/chunks/40f94348-3665981d394436a4.js\,\2808\,\static/chunks/cd2bc502-dccf3f627b79fb7b.js\,\1064\,\static/chunks/d5504597-14b1fdf76ee20456.js\,\9340\,\static/chunks/0b0944fb-9fdc250e44912132.js\,\2783\,\static/chunks/2783-b9a383cf8d2da686.js\,\7125\,\static/chunks/7125-7adaf07f9588b340.js\,\6044\,\static/chunks/6044-6a843023a95c7b69.js\,\6046\,\static/chunks/6046-a15a9c22edfc2449.js\,\4705\,\static/chunks/4705-0f8c0b1c3eb3293d.js\,\438\,\static/chunks/438-376903fca45b8284.js\,\5216\,\static/chunks/5216-7e0407f0148493a0.js\,\5942\,\static/chunks/5942-ea32bc250b281e90.js\,\8974\,\static/chunks/app/page-7344702a57691eb0.js\,\default\\n)/script>script>self.__next_f.push(1,5a:I81258,\9717\,\static/chunks/c3536442-5b720253f760e76c.js\,\6124\,\static/chunks/ae80f326-2d1ce9f306c3e117.js\,\3471\,\static/chunks/40f94348-3665981d394436a4.js\,\2808\,\static/chunks/cd2bc502-dccf3f627b79fb7b.js\,\1064\,\static/chunks/d5504597-14b1fdf76ee20456.js\,\9340\,\static/chunks/0b0944fb-9fdc250e44912132.js\,\2783\,\static/chunks/2783-b9a383cf8d2da686.js\,\7125\,\static/chunks/7125-7adaf07f9588b340.js\,\6044\,\static/chunks/6044-6a843023a95c7b69.js\,\6046\,\static/chunks/6046-a15a9c22edfc2449.js\,\4705\,\static/chunks/4705-0f8c0b1c3eb3293d.js\,\438\,\static/chunks/438-376903fca45b8284.js\,\5216\,\static/chunks/5216-7e0407f0148493a0.js\,\5942\,\static/chunks/5942-ea32bc250b281e90.js\,\8974\,\static/chunks/app/page-7344702a57691eb0.js\,\default\\n)/script>script>self.__next_f.push(1,17:\$\,\$L1b\,null,{}\n1d:T1501,)/script>script>self.__next_f.push(1,恭喜你成爲了政府網站的後端工程師,你的第一份工作很簡單\n\n明天有 2300 萬人要同時進去普發 1 萬網站,不要讓網站掛掉\n先不要急得辭職,我們可以先分析一些簡單的方法\n\n## 分析問題\n\n寫任何程式的第一步就是分析你要解決什麼問題,在你眼前擺著三座大山\n\n**1. 瞬間流量峰值**\n當所有人在同一時間點湧入,伺服器的 CPU、記憶體、網路頻寬都會瞬間達到極限。\n\n**2. 資料庫瓶頸**\n無論你有多少台應用伺服器,最終所有請求都要訪問同一個資料庫。資料庫的讀寫速度成為整個系統的致命瓶頸。\n\n**3. 資料一致性問題**\n2300 萬人不只是查詢資料,還要登記、查詢餘額、確認資格。如何在高併發情況下保證資料的正確性?這是最困難的挑戰。\n\n## 一、首先,分而治之「身分證尾碼分流」\n\n我想大家都遇到過演唱會訂票、剛開學的搶課、電商促銷時\n如果人流很大,那些網站很容易直接掛掉,那就不要讓他們同時進來\n\n透過身份證尾號,把全國人口平均分成十組。尾碼 0 和 1 的人星期一進來,2 和 3 的星期二,以此類推。原本一天要承受 2300 萬人的壓力,現在分散到五天,每天只需要處理約 460 萬人。這個數字看起來還是很嚇人,但至少比一次處理全部要可行得多。\n\n## 二、再來,不要讓單一伺服器累死「負載平衡」\n\n我之前用我家的電腦開網站,人少的時候是沒什麼事情\n但人一多,回頭一看你的電腦正在著火\n\n該怎麼辦呢?多買兩塊電腦\n今天有流量進來,我把它會交給比較閒的每台,大幅降低單台電腦的工作量\n\n當然電腦再多,資料庫終就只有一個\n聰明如你馬上想到用 Redis,做一個快取大大降低資料庫的壓力\n\n但 2300萬人不是只看資料,他們還要寫進去登記、查詢餘額、確認資格,更不用說同時更改了,你要怎麼保證資料一致呢?\n\n## 三、快取系統\n\n開發快取系統,眼前又有三座大山需要解決\n\n**快取穿透**:當有人一直查詢一個根本不存在的資料,比如身分證號碼「Z999999999」。因為資料不存在,快取裡當然也沒有,所以每次查詢都會直接打到資料庫。如果有惡意使用者不斷送出這種請求,資料庫就會被拖垮。解決方法是即使查詢結果是「不存在」,也在快取裡記錄這個結果,避免重複查詢。\n\n**快取擊穿**:當一個超熱門的資料剛好過期時,會有大量請求同時發現快取沒有這個資料,於是全部衝向資料庫。這就像演唱會門票剛開賣的那一瞬間,所有人同時按下購買鍵。解決方法是使用「互斥鎖」機制,確保同一時間只有一個請求去資料庫更新快取。\n\n**快取雪崩**:如果大量快取資料同時過期,就會造成資料庫瞬間被大量請求淹沒。解決方法是讓快取的過期時間隨機化,避免集體失效。\n\n## 四、將整個資料庫切成10塊小倉庫,「資料庫分片」\n\n即便有了快取系統,使用者還是需要寫入資料的。\n這時候就需要**資料庫分片**技術了。簡單說,就是把一個大資料庫切成很多小資料庫。\n\n還是用身分證尾碼來舉例。我們可以建立十個小資料庫,尾碼是 0 或 1 的人,資料存在第一個資料庫;2 或 3 的存在第二個;以此類推。原本 2300 萬筆資料擠在一個資料庫裡,現在分散到十個資料庫,每個只管理約 230 萬筆資料。\n\n這樣做的好處是什麼?當尾碼 3 的使用者在查詢他的申請進度時,他只會去第二個資料庫查詢,不會跟尾碼 7 的使用者搶資源。\n\n但分片也帶來了新的挑戰。比如說,如果政府想要統計「目前總共有多少人完成登記」,就必須去十個資料庫分別查詢,再把結果加總起來。這比從單一資料庫查詢複雜多了。\n\n## 五、訊息佇列,創建緩衝區\n\n想像一家熱門餐廳,如果同一大堆人點餐,廚房肯定會大亂。\n聰明的做法是什麼?讓客人在櫃台點餐,服務生把訂單寫下來依序交給廚房,廚房按照順序一份一份做。這就是訊息佇列的概念,寫一堆「待處理的訂單」,慢慢處理。\n\n對於普發現金網站,當使用者送出「我要登記領取現金」的請求時,系統不需要立刻完成所有處理。它可以先告訴使用者「收到了,請稍候」,然後把這個請求放進佇列裡,後端系統再慢慢處理。\n\n這樣做有幾個好處:首先,使用者不用傻傻等待,提交完就可以關掉網頁去做別的事。其次,後端系統可以按照自己的節奏處理請求,不會因為瞬間湧入太多請求而崩潰。最後,如果處理過程中出現錯誤,系統可以重試,不會讓使用者的申請就這樣消失了。\n\n訊息佇列就像是一個緩衝墊,把「瞬間的超大流量」轉換成「持續的穩定處理」。原本一秒鐘內湧入的一萬個請求,可能會讓系統直接掛掉;但如果用訊息佇列接住,系統可以用一分鐘甚至更長時間慢慢處理完這些請求。\n\n### 恭喜你,你現在可以從從容容、游刃有餘的休息了,你用你聰明的大腦成功守住你的工作)/script>script>self.__next_f.push(1,1e:T1844,)/script>script>self.__next_f.push(1,## 開場:為什麼需要 MCP?從真實痛點出發\n\n你是一位熱愛學習的工程師,這些年累積了超過 1000 篇個人筆記,散落在本地資料夾裡。涵蓋前端框架、後端架構、演算法、系統設計⋯⋯每一篇都是你花時間整理的寶貴知識。\n\n某天,你想問 ChatGPT:「幫我找所有關於 React Hooks 的筆記,並整理出我還沒掌握的進階用法。」\n\n這個需求聽起來很合理,對吧?但實際上,要讓 AI 助手做到這件事,困難重重。\n\n\u003cimg height\246\ width\246\ alt\AI對大檔案感到困難重重\ title\AI對大檔案感到困難重重\ src\/api/object/1761487449486-zoy78.png\ /\u003e\n\n### 現有方案都不夠好\n\n讓我們快速看幾個現有的處理方式:\n\n**手動複製貼上?** 效率極差,每次都要重複,而且 AI 只能看到你貼的部分,無法主動探索更多相關筆記。\n\n**批次上傳檔案?** 跟剛剛一樣的問題,你得先猜哪些檔案相關,容易遺漏,而且每個新問題都要重新上傳。\n\n\u003cimg height\188\ width\188\ alt\手動貼上一堆檔案很麻煩\ title\手動貼上一堆檔案很麻煩\ src\/api/object/1761487495091-pztgmn.png\ /\u003e\n\n**用 Claude Code 或 Cursor?** 它們的能力被綁死在特定工具裡,而且這些工具就是很會 Coding,不太會討論、發想創意。更重要的是,缺乏標準化 —— 每個工具都自己搞一套。\n\n\u003cimg height\209\ width\209\ alt\claude\ title\claude\ src\/api/object/1761487569148-cfeq3o.png\ /\u003e\n\n**寫個 REST API?** 這是最接近的方案,但有個致命問題:**REST API 是為「程式」設計的,不是為「LLM」設計的。** LLM 不知道你有哪些 API,需要每次對話都重新被告知規格,還得自己猜該呼叫哪個 endpoint。\n\n這就是 MCP 要解決的問題。\n\n***\n\n## MCP 是什麼?AI 義肢製作手冊\n\n不知道你有沒有看過 **Cyberpunk 2077**?\n\n在這個世界觀上,大部分的人都會安裝一推超酷的義肢,更強壯的手臂、更壯的機械腿...,但這些義肢要能正常運作,有個前提:**義肢的接口必須符合標準規格**。如果每個義肢廠商都自己搞一套接口,那你的手臂根本裝不上去。\n\n**MCP 就像是一本「AI 義肢製作手冊」。**\n\nChatGPT、Claude 這些 AI 會根據這本手冊打造對應的「安裝槽」,而任何人都可以根據這份手冊,設計給 AI 使用的「義肢」,然後直接裝上去。\n\n重點來了:**MCP 只是一本製作手冊**,用嚴謹的話來講就是:\n\n* AI 與工具溝通的標準協定\n* 讓 AI 能自動發現工具的機制\n* 標準化的權限和安全邊界\n\n它只決定「接口的部分長怎樣」,確保你做出來的義肢可以順利裝到其他 AI 身上。至於這個義肢有什麼功能?讀檔案、查資料庫、發 email、控制你的智慧家居⋯⋯**隨便你,完全自由**。\n\n### 與 REST API 的差異\n\n\u003e MCP 是為 LLM 設計的標準化協定,讓 AI 能夠自動發現和使用你的工具。\n\n關鍵差異:\n\n| 特性 | REST API | MCP |\n| ---- | -------- | -------------- |\n| 設計對象 | 給「開發者」用 | 給「LLM」用 |\n| 發現機制 | 需要讀文件 | AI 自動發現工具 |\n| 使用方式 | 需要寫程式呼叫 | AI 自動選擇並呼叫 |\n| 參數格式 | 需要查文件 | 設計 MCP 時,都會定義好 |\n\n\u003cimg height\291\ width\291\ alt\MCP vs REST API\ title\MCP vs REST API\ src\/api/object/1761487782694-gjsgvk.png\ /\u003e\n\n## 在提到 MCP 之前,必須得提一下什麼是 「Agent」?\n\n這裡快速釐清一下概念。\n\n**AI(嚴格來說是 LLM)就是一個只會思考、無法行動的大腦。**\n對,他只是一顆大腦,沒手沒腳,無法行動。他可以思考得很深入、很精彩,但你問他「幫我查一下天氣」,他只能回答「抱歉我查不了」。\n\n**那什麼是 Agent 呢**\n簡單說,Agent AI 大腦 + 能力(工具)。給 AI 裝上「手」(查天氣的工具)、「腳」(控制智慧家居的工具),他就從一顆只會想的大腦,變成一個能實際做事的 Agent。\n\nMCP 就是定義「怎麼給 AI 裝義肢」的標準。\n\n\u003e 如果你對 AI Agent 的概念想要更深入了解,推薦閱讀我之前撰寫的書籍《從零開始,打造一個生成式 AI 平台》https://www.books.com.tw/products/0011029234 ,裡頭有更完整的講解。\n\n***\n\n## MCP 的核心概念:Tools — AI 的「義肢」\n\n如果說 AI 的大腦是語言模型,那麼 **Tools 就是 AI 的義肢**。\n\n沒有義肢,大腦再聰明也只能思考,無法行動。有了 Tools,AI 才能:\n\n* **獲取資訊** — 讀取你的檔案、查詢資料庫、呼叫 API\n* **執行動作** — 發送郵件、建立文件、修改資料\n* **改變狀態** — 更新設定、記錄日誌、觸發流程\n\n### Tools 的三個關鍵特性\n\n#### 1. AI 自己決定什麼時候用\n\n這是 Tools 最重要的特性。你不需要明確指示「請呼叫 XXX 工具」,AI 會根據對話情境自己判斷。\nAI 自己決定:\n\n* 什麼時候用工具\n* 用哪個工具\n* 傳什麼參數\n\n#### 2. Tool 本質就是一個 Function\n\nTools 就是一個 Function,他執行完後的結果,會送回給 LLM\n就像下面這三個 Function\n\n* `list_notes()`→ 返回:\\note1.md, note2.md, note3.md\n* `read_note(path: \note1.md\)`→ 返回:筆記的完整內容\n* `search_notes(query: \React\)`→ 返回:包含 \React\ 的筆記列表和摘要\n\n每個 Tool 的呼叫都會產生結果,AI 會根據結果決定下一步動作。\n這就像是你問助理「幫我查一下檔案」,他真的會去翻檔案櫃。\n\n#### 3. 有明確的規格書(Schema-Defined)\n\n每個 Tool 都有清楚的說明書,你必須替每一個工具定義好以下參數\n\n* 工具名稱,這個工具叫什麼\n* 工具說明,這個工具是做什麼的\n* 輸入參數,這個工具可以傳入什麼,需要提供什麼參數\n* 輸出參數,這個工具會回傳什麼\n\n基本上還是剛剛提到的,Tool 的本質就是一個 Function,只是你必須詳細交代 LLM 該怎麼用、什麼時會去用這個 Function)/script>script>self.__next_f.push(1,1f:T503,)/script>script>self.__next_f.push(1,import express from express;\nimport { McpServer } from @modelcontextprotocol/sdk/server/mcp.js;\nimport { z } from zod;\n\nconst app express();\n\napp.use(express.json());\n\n// 建立 MCP Server\nconst server new McpServer({\n name: my-notes-server,\n version: 1.0.0\n});\n\nserver.registerTool(\n list_files,\n {\n title: 列出檔案,\n description: 列出所有 Markdown 檔案,\n inputSchema: {}, // 這裡不需要傳入任何參數 \n outputSchema: {\n files: z.array(z.string())\n }\n },\n async () \u003e {/* @cat-caption */\n return {/* @cat-caption */\n content: // ← 必須是陣列/* @cat-caption */\n {/* @cat-caption */\n type: text, // ← 內容類型/* @cat-caption */\n text: 目前還沒有檔案 // ← 實際內容/* @cat-caption */\n }/* @cat-caption */\n /* @cat-caption */\n };/* @cat-caption */\n }/* @cat-caption */\n);\n\napp.get(/health, (req, res) \u003e {\n res.json({ \n status: ok,\n message: Server is running! \n });\n});\n\nconst port 8080;\n\napp.listen(port, () \u003e {\n console.log(`🚀 伺服器運行中:http://localhost:${port}`);\n console.log(`📍 健康檢查:http://localhost:${port}/health`);\n});)/script>script>self.__next_f.push(1,20:T4db,)/script>script>self.__next_f.push(1,import express from \express\;\nimport { McpServer } from \@modelcontextprotocol/sdk/server/mcp.js\;\nimport { z } from \zod\;\n\nconst app express();\n\napp.use(express.json());\n\n// 建立 MCP Server\nconst server new McpServer({\n name: \my-notes-server\,\n version: \1.0.0\,\n});\n\nserver.registerTool(\n \list_files\,\n {\n title: \列出檔案\,\n description: \列出所有 Markdown 檔案\,\n inputSchema: {}, // 這裡不需要傳入任何參數\n outputSchema: {\n files: z.array(z.string()),\n },\n },\n async () \u003e {\n return {\n content: \n {\n type: \text\,\n text: \目前還沒有檔案\,\n },\n {// 簡單展示一下如果要回傳圖片該怎麼做 /* @cat-caption */\n type: \image\,/* @cat-caption */\n data: \base64 編碼的圖片\,/* @cat-caption */\n mimeType: \image/png\,/* @cat-caption */\n },/* @cat-caption */\n ,\n };\n }\n);\n\napp.get(\/health\, (req, res) \u003e {\n res.json({\n status: \ok\,\n message: \Server is running!\,\n });\n});\n\nconst port 8080;\n\napp.listen(port, () \u003e {\n console.log(`🚀 伺服器運行中:http://localhost:${port}`);\n console.log(`📍 健康檢查:http://localhost:${port}/health`);\n});\n)/script>script>self.__next_f.push(1,21:T4a3,)/script>script>self.__next_f.push(1,import express from \express\;\nimport { McpServer } from \@modelcontextprotocol/sdk/server/mcp.js\;\nimport { z } from \zod\;\n\nconst app express();\n\napp.use(express.json());\n\n// 建立 MCP Server\nconst server new McpServer({\n name: \my-notes-server\,\n version: \1.0.0\,\n});\n\nserver.registerTool(\n \list_files\,\n {\n title: \列出檔案\,\n description: \列出所有 Markdown 檔案\,\n inputSchema: {}, // 這裡不需要傳入任何參數\n outputSchema: {\n files: z.array(z.string()),\n },\n },\n async () \u003e {\n const output { files: }; // ← 先用空陣列/* @cat-caption */\n\n return {\n content: \n {\n type: \text\,\n text: JSON.stringify(output, null, 2),/* @cat-caption */\n }\n ,\n structuredContent: output // 同時提供結構化資料/* @cat-caption */\n };\n }\n);\n\napp.get(\/health\, (req, res) \u003e {\n res.json({\n status: \ok\,\n message: \Server is running!\,\n });\n});\n\nconst port 8080;\n\napp.listen(port, () \u003e {\n console.log(`🚀 伺服器運行中:http://localhost:${port}`);\n console.log(`📍 健康檢查:http://localhost:${port}/health`);\n});\n)/script>script>self.__next_f.push(1,22:T802,)/script>script>self.__next_f.push(1,import express from express;\nimport { McpServer } from @modelcontextprotocol/sdk/server/mcp.js;\nimport { StreamableHTTPServerTransport } from @modelcontextprotocol/sdk/server/streamableHttp.js;\nimport { z } from zod;\n\nconst app express();\napp.use(express.json());\n\nconst server new McpServer({\n name: my-notes-server,\n version: 1.0.0\n});\n\nserver.registerTool(\n list_files,\n {\n title: 列出檔案,\n description: 列出所有 Markdown 檔案,\n inputSchema: {},\n outputSchema: { files: z.array(z.string()) }\n },\n async () \u003e {\n const output { files: };\n\n return {\n content: \n {\n type: \text\,\n text: JSON.stringify(output, null, 2),\n }\n ,\n structuredContent: output \n };\n }\n);\n\n// MCP endpoint/* @cat-caption */\napp.post(/mcp, async (req, res) \u003e {/* @cat-caption */\n try {/* @cat-caption */\n const transport new StreamableHTTPServerTransport({/* @cat-caption */\n sessionIdGenerator: undefined,/* @cat-caption */\n enableJsonResponse: true/* @cat-caption */\n });/* @cat-caption */\n /* @cat-caption */\n res.on(close, () \u003e transport.close());/* @cat-caption */\n /* @cat-caption */\n await server.connect(transport);/* @cat-caption */\n await transport.handleRequest(req, res, req.body);/* @cat-caption */\n } catch (error) {/* @cat-caption */\n console.error(MCP 請求錯誤:, error);/* @cat-caption */\n if (!res.headersSent) {/* @cat-caption */\n res.status(500).json({/* @cat-caption */\n jsonrpc: 2.0,/* @cat-caption */\n error: { code: -32603, message: Internal server error },/* @cat-caption */\n id: null/* @cat-caption */\n });/* @cat-caption */\n }/* @cat-caption */\n }/* @cat-caption */\n});/* @cat-caption */\n\napp.get(/health, (req, res) \u003e {\n res.json({ status: ok });\n});\n\nconst port 8080;\napp.listen(port, () \u003e {\n console.log(`🚀 伺服器運行中:http://localhost:${port}`);\n console.log(`📍 MCP endpoint:http://localhost:${port}/mcp`);\n});)/script>script>self.__next_f.push(1,23:T9ce,)/script>script>self.__next_f.push(1,import express from express;\nimport { McpServer } from @modelcontextprotocol/sdk/server/mcp.js;\nimport { StreamableHTTPServerTransport } from @modelcontextprotocol/sdk/server/streamableHttp.js;\nimport { z } from zod;\nimport fs from fs/promises; /* @cat-caption */\nimport path from path;/* @cat-caption */\n\nconst app express();\napp.use(express.json());\n\n// 筆記目錄/* @cat-caption */\nconst NOTES_DIR ./notes;/* @cat-caption */\n\nconst server new McpServer({\n name: my-notes-server,\n version: 1.0.0\n});\n\nserver.registerTool(\n list_files,\n {\n title: 列出檔案,\n description: 列出所有 Markdown 檔案,\n inputSchema: {},\n outputSchema: { files: z.array(z.string()) }\n },\n async () \u003e {\n try {/* @cat-caption */\n const entries await fs.readdir(NOTES_DIR, { withFileTypes: true });/* @cat-caption */\n const files entries/* @cat-caption */\n .filter(entry \u003e entry.isFile() \u0026\u0026 entry.name.endsWith(.md))/* @cat-caption */\n .map(entry \u003e entry.name);/* @cat-caption */\n /* @cat-caption */\n const output { files };/* @cat-caption */\n /* @cat-caption */\n return {/* @cat-caption */\n content: { type: text, text: JSON.stringify(output, null, 2) },/* @cat-caption */\n structuredContent: output/* @cat-caption */\n };/* @cat-caption */\n } catch (error) {/* @cat-caption */\n return {/* @cat-caption */\n content: { type: text, text: `錯誤:${(error as Error).message}` },/* @cat-caption */\n isError: true/* @cat-caption */\n };/* @cat-caption */\n }/* @cat-caption */\n }\n);\n\napp.post(/mcp, async (req, res) \u003e {\n try {\n const transport new StreamableHTTPServerTransport({\n sessionIdGenerator: undefined,\n enableJsonResponse: true\n });\n res.on(close, () \u003e transport.close());\n await server.connect(transport);\n await transport.handleRequest(req, res, req.body);\n } catch (error) {\n console.error(MCP 請求錯誤:, error);\n if (!res.headersSent) {\n res.status(500).json({\n jsonrpc: 2.0,\n error: { code: -32603, message: Internal server error },\n id: null\n });\n }\n }\n});\n\napp.get(/health, (req, res) \u003e {\n res.json({ status: ok });\n});\n\nconst port 8080;\napp.listen(port, () \u003e {\n console.log(`🚀 伺服器運行中:http://localhost:${port}`);\n console.log(`📍 MCP endpoint:http://localhost:${port}/mcp`);\n console.log(`📂 筆記目錄:${NOTES_DIR}`);\n});)/script>script>self.__next_f.push(1,24:T849,)/script>script>self.__next_f.push(1,## 目錄\n\n* 智能合約 Solidity 教學 (上) - Solidity 基礎語法教學(/article/96e61275-7bde-480a-9a61-fca3c5a8657d)\n* 智能合約 Solidity 教學 (中) - MetaMask 與以太坊測試網(/article/e9e78b6b-e7fa-4d70-b892-059ccc057ecf)\n* 智能合約 Solidity 教學 (下) - Web3.js 實際開發一個 DApp 去中心化應用前端(/article/8b0bf0c6-6a00-4764-b686-d0c9c2a4ca28)\n\n## **開發 Web3 應用的前端**\n\n## **零**、前言\n\n在過去兩章節中,我們已經完成了智能合約,並且將其部署到測試鏈 Sepolia 上\n\nSepolia 是**全球公開的以太坊測試網絡**,已經算是個半公開環境了\n\n在實際項目開發中,我們會使用 **Ganache** 之類的程序,來架設自己的私人區塊鏈,並且在上頭測試合約,以獲得更高的調測性與開發效率。\n\n在本教學中則因為時間問題,省略了這一部分。\n\n## 一、開發環境準備\n\n1. **安裝 Node.js 及 npm**\n * 如果你的電腦還沒有安裝 Node.js,請至 Node.js 官方網站(https://nodejs.org/)下載並安裝最新版本的 Node.js,即可同時安裝 npm(Node Package Manager)。\n2. **安裝 Vite**此時 Vite 會自動幫你建立一個基本的前端專案結構。\n * 透過 npm 方式安裝 Vite(其實也可以直接使用 `npm create vite@latest` 來初始化專案)。\n * 這裡建議使用簡單的安裝方式快速開始:npm create vite@latest\n * 在互動式的 CLI 中選擇:\n 1. 專案名稱 (如 `my-web3-project`)\n 2. 框架選擇:**Vanilla**\n 3. 選擇 **JavaScript**(非 TypeScript)\n3. **進入專案並安裝依賴**\n * 切換到專案資料夾:`cd my-web3-project`\n * 安裝 web3.js:`npm install web3`\n\n***\n\n## 二、專案結構簡介\n\n執行完 `npm create vite@latest` 之後,你的專案結構大致會長這樣:\n\nmy-web3-project\n├─ index.html\n├─ package.json\n├─ vite.config.js\n└─ src\n\u0026#x20; ├─ main.js\n\u0026#x20; └─ style.css\n\n為了方便教學,所有的程式碼都會放在 `index.html` 中來展示結果。\n\n***\n\n## 三、拿到合約的 ABI 與合約地址)/script>script>self.__next_f.push(1,25:Tb54d,)/script>script>self.__next_f.push(1,{\id\:\l5rh7f\,\code\:\\u003c!DOCTYPE html\u003e\\n\u003chtml lang\\\en\\\\u003e\\n\u003chead\u003e\\n \u003cmeta charset\\\UTF-8\\\\u003e\\n \u003cmeta name\\\viewport\\\ content\\\widthdevice-width, initial-scale1.0\\\\u003e\\n \u003ctitle\u003eDocument\u003c/title\u003e\\n\u003c/head\u003e\\n\u003cbody\u003e\\n \\n\u003c/body\u003e\\n\u003c/html\u003e\,\caption\:\打開 `index.html` \\n替換成最基本的 HTML,我們一步一步來建構 Web3 應用\,\language\:\html\},{\id\:\4ca7hc\,\code\:\\u003c!DOCTYPE html\u003e\\n\u003chtml lang\\\en\\\\u003e\\n\\n\u003chead\u003e\\n \u003cmeta charset\\\UTF-8\\\\u003e\\n \u003cmeta name\\\viewport\\\ content\\\widthdevice-width, initial-scale1.0\\\\u003e\\n \u003ctitle\u003eDocument\u003c/title\u003e\\n \u003cscript type\\\module\\\\u003e/* @cat-caption */\\n // ... 之後的程式碼就放這吧/* @cat-caption */\\n \u003c/script\u003e/* @cat-caption */\\n\u003c/head\u003e\\n\\n\u003cbody\u003e\\n\\n\u003c/body\u003e\\n\\n\u003c/html\u003e\,\caption\:\為了方便教學,我會將 JavaScript 直接寫在 HTML 中\\n在正式專案開發時,這樣做程式碼會長到很難閱讀,要特別注意\,\language\:\html\},{\id\:\i1xm1\,\code\:\\u003c!DOCTYPE html\u003e\\n\u003chtml lang\\\en\\\\u003e\\n\\n\u003chead\u003e\\n \u003cmeta charset\\\UTF-8\\\\u003e\\n \u003cmeta name\\\viewport\\\ content\\\widthdevice-width, initial-scale1.0\\\\u003e\\n \u003ctitle\u003eDocument\u003c/title\u003e\\n \u003cscript type\\\module\\\\u003e\\n import Web3 from \\\web3\\\;/* @cat-caption */\\n \u003c/script\u003e\\n\u003c/head\u003e\\n\\n\u003cbody\u003e\\n\\n\u003c/body\u003e\\n\\n\u003c/html\u003e\,\caption\:\還記得我們剛剛安裝的 web3.js 嗎?\\n這是別人已經設計、封裝好的程式碼,我們可以簡單的使用 import 來把它加入我們的程式碼\\n\,\language\:\html\},{\id\:\iap70s\,\code\:\\u003c!DOCTYPE html\u003e\\n\u003chtml lang\\\en\\\\u003e\\n\\n\u003chead\u003e\\n \u003cmeta charset\\\UTF-8\\\\u003e\\n \u003cmeta name\\\viewport\\\ content\\\widthdevice-width, initial-scale1.0\\\\u003e\\n \u003ctitle\u003eDocument\u003c/title\u003e\\n \u003cscript type\\\module\\\\u003e\\n import Web3 from \\\web3\\\;\\n const contractAddress \\\0xFEA4f2C9B99Df0B2b7da67e60371BE4275C3d749\\\;/* @cat-caption */\\n\\n const contractABI /* @cat-caption */\\n {/* @cat-caption */\\n inputs: /* @cat-caption */\\n ..../* @cat-caption */\\n ,/* @cat-caption */\\n stateMutability: \\\nonpayable\\\,/* @cat-caption */\\n type: \\\constructor\\\,/* @cat-caption */\\n },/* @cat-caption */\\n .../* @cat-caption */\\n ;/* @cat-caption */\\n \u003c/script\u003e\\n\u003c/head\u003e\\n\\n\u003cbody\u003e\\n\\n\u003c/body\u003e\\n\\n\u003c/html\u003e\,\caption\:\將剛剛獲得的合約地址與合約 ABI 貼上去\\n這個程式碼會瞬間變超級長\,\language\:\html\},{\id\:\r0xbg4\,\code\:\// abi.js\\nexport const contractABI /* @cat-caption */\\n {\\n inputs: \\n ....\\n ,\\n stateMutability: \\\nonpayable\\\,\\n type: \\\constructor\\\,\\n },\\n ...\\n;\,\caption\:\contractABI 真的太長了!!\\n讓我們把它獨立成一個檔案吧\\n\\n在 src 資料夾中創建新的檔案 `abi.js`\\n並貼上剛剛的 合約 ABI\\n記得在最前面加上 export\\n這表示允許其他程式檔案使用這個變數\,\language\:\javascript\,\image\:{\url\:\/api/object/1738170024095-it7ta.png\,\position\:\bottom-right\,\width\:200}},{\id\:\zzvt5\,\code\:\\u003c!DOCTYPE html\u003e\\n\u003chtml lang\\\en\\\\u003e\\n\\n\u003chead\u003e\\n \u003cmeta charset\\\UTF-8\\\\u003e\\n \u003cmeta name\\\viewport\\\ content\\\widthdevice-width, initial-scale1.0\\\\u003e\\n \u003ctitle\u003eDocument\u003c/title\u003e\\n \u003cscript type\\\module\\\\u003e\\n import Web3 from \\\web3\\\;\\n import { contractABI } from \\\./src/abi.js\\\;/* @cat-caption */\\n\\n const contractAddress \\\0xFEA4f2C9B99Df0B2b7da67e60371BE4275C3d749\\\;\\n \u003c/script\u003e\\n\u003c/head\u003e\\n\\n\u003cbody\u003e\\n\\n\u003c/body\u003e\\n\\n\u003c/html\u003e\,\caption\:\回到 index.html\\n透過 import 將剛剛宣告的變數導進來\,\language\:\html\},{\id\:\srv12j\,\code\:\\u003c!DOCTYPE html\u003e\\n\u003chtml lang\\\en\\\\u003e\\n\\n\u003chead\u003e\\n \u003cmeta charset\\\UTF-8\\\\u003e\\n \u003cmeta name\\\viewport\\\ content\\\widthdevice-width, initial-scale1.0\\\\u003e\\n \u003ctitle\u003eDocument\u003c/title\u003e\\n \u003cscript type\\\module\\\\u003e\\n import Web3 from \\\web3\\\;\\n import { contractABI } from \\\./src/abi.js\\\;\\n\\n const contractAddress \\\0xFEA4f2C9B99Df0B2b7da67e60371BE4275C3d749\\\;\\n\\n \u003c/script\u003e\\n\u003c/head\u003e\\n\\n\u003cbody\u003e\\n \u003cdiv\u003e/* @cat-caption */\\n \u003cbutton id\\\connectButton\\\\u003e連線錢包\u003c/button\u003e/* @cat-caption */\\n \u003c/div\u003e/* @cat-caption */\\n\u003c/body\u003e\\n\\n\u003c/html\u003e\,\caption\:\設計一個按鈕來連結用戶錢包\,\language\:\html\},{\id\:\59r8bt\,\code\:\\u003c!DOCTYPE html\u003e\\n\u003chtml lang\\\en\\\\u003e\\n\\n\u003chead\u003e\\n \u003cmeta charset\\\UTF-8\\\\u003e\\n \u003cmeta name\\\viewport\\\ content\\\widthdevice-width, initial-scale1.0\\\\u003e\\n \u003ctitle\u003eDocument\u003c/title\u003e\\n \u003cscript type\\\module\\\\u003e\\n import Web3 from \\\web3\\\;\\n import { contractABI } from \\\./src/abi.js\\\;\\n\\n const contractAddress \\\0xFEA4f2C9B99Df0B2b7da67e60371BE4275C3d749\\\;\\n\\n // 找到 網頁 中的按鈕 \\n const connectButton document.getElementById(\\\connectButton\\\);/* @cat-caption */\\n connectButton.addEventListener(\\\click\\\, async () \u003e { /* @cat-caption */\\n // ...點擊後要做的事情 /* @cat-caption */\\n }); /* @cat-caption */\\n \u003c/script\u003e\\n\u003c/head\u003e\\n\\n\u003cbody\u003e\\n \u003cdiv\u003e\\n \u003cbutton id\\\connectButton\\\\u003e連線錢包\u003c/button\u003e\\n \u003c/div\u003e\\n\u003c/body\u003e\\n\\n\u003c/html\u003e\,\caption\:\在 JS 中設定該按鈕點擊後要做什麼\,\language\:\html\},{\id\:\8yvbi7\,\code\:\\u003c!DOCTYPE html\u003e\\n\u003chtml lang\\\en\\\\u003e\\n\\n\u003chead\u003e\\n \u003cmeta charset\\\UTF-8\\\\u003e\\n \u003cmeta name\\\viewport\\\ content\\\widthdevice-width, initial-scale1.0\\\\u003e\\n \u003ctitle\u003eDocument\u003c/title\u003e\\n \u003cscript type\\\module\\\\u003e\\n import Web3 from \\\web3\\\;\\n import { contractABI } from \\\./src/abi.js\\\;\\n\\n const contractAddress \\\0xFEA4f2C9B99Df0B2b7da67e60371BE4275C3d749\\\;\\n\\n const connectButton document.getElementById(\\\connectButton\\\);/* @cat-caption */\\n connectButton.addEventListener(\\\click\\\, async () \u003e {/* @cat-caption */\\n // 檢查瀏覽器是否安裝了 MetaMask(或其他注入 web3 的錢包)/* @cat-caption */\\n if (typeof window.ethereum ! undefined) {/* @cat-caption */\\n // ... 設定錢包\\n } else {/* @cat-caption */\\n alert(請先安裝 MetaMask 或其他以太坊錢包外掛!);/* @cat-caption */\\n }/* @cat-caption */\\n });/* @cat-caption */\\n \u003c/script\u003e\\n\u003c/head\u003e\\n\\n\u003cbody\u003e\\n \u003cdiv\u003e\\n \u003cbutton id\\\connectButton\\\\u003e連線錢包\u003c/button\u003e\\n \u003c/div\u003e\\n\u003c/body\u003e\\n\\n\u003c/html\u003e\,\caption\:\點擊按鈕後,要先檢查瀏覽器是否安裝了 MetaMask(或其他注入 web3 的錢包)\\n(如果沒裝任何錢包 window.ethereum 不會有東西\,\language\:\html\},{\id\:\oo2wxm\,\code\:\\u003c!DOCTYPE html\u003e\\n\u003chtml lang\\\en\\\\u003e\\n\\n\u003chead\u003e\\n \u003cmeta charset\\\UTF-8\\\\u003e\\n \u003cmeta name\\\viewport\\\ content\\\widthdevice-width, initial-scale1.0\\\\u003e\\n \u003ctitle\u003eDocument\u003c/title\u003e\\n \u003cscript type\\\module\\\\u003e\\n import Web3 from \\\web3\\\;\\n import { contractABI } from \\\./src/abi.js\\\;\\n\\n const contractAddress \\\0xFEA4f2C9B99Df0B2b7da67e60371BE4275C3d749\\\;\\n\\n const connectButton document.getElementById(\\\connectButton\\\);\\n connectButton.addEventListener(\\\click\\\, async () \u003e {\\n // 檢查瀏覽器是否安裝了 MetaMask(或其他注入 web3 的錢包)\\n if (typeof window.ethereum ! undefined) {\\n try {/* @cat-caption */\\n // 請求錢包授權/* @cat-caption */\\n await window.ethereum.request({ method: eth_requestAccounts });/* @cat-caption */\\n } catch (error) {/* @cat-caption */\\n console.error(error);/* @cat-caption */\\n alert(\\\連線錢包失敗,請查看控制台訊息或再次嘗試。\\\);/* @cat-caption */\\n }/* @cat-caption */\\n } else {\\n alert(請先安裝 MetaMask 或其他以太坊錢包外掛!);\\n }\\n });\\n \u003c/script\u003e\\n\u003c/head\u003e\\n\\n\u003cbody\u003e\\n \u003cdiv\u003e\\n \u003cbutton id\\\connectButton\\\\u003e連線錢包\u003c/button\u003e\\n \u003c/div\u003e\\n\u003c/body\u003e\\n\\n\u003c/html\u003e\,\caption\:\請求使用者透過 MetaMask 授權並連接錢包,讓 DApp 可以存取使用者的 Ethereum 帳戶地址。\,\language\:\html\},{\id\:\xnr3\,\code\:\\u003c!DOCTYPE html\u003e\\n\u003chtml lang\\\en\\\\u003e\\n\\n\u003chead\u003e\\n \u003cmeta charset\\\UTF-8\\\\u003e\\n \u003cmeta name\\\viewport\\\ content\\\widthdevice-width, initial-scale1.0\\\\u003e\\n \u003ctitle\u003eDocument\u003c/title\u003e\\n \u003cscript type\\\module\\\\u003e\\n import Web3 from \\\web3\\\;\\n import { contractABI } from \\\./src/abi.js\\\;\\n\\n const contractAddress \\\0xFEA4f2C9B99Df0B2b7da67e60371BE4275C3d749\\\;\\n\\n let web3; // 用來存放 Web3 物件實例/* @cat-caption */\\n const connectButton document.getElementById(\\\connectButton\\\);\\n connectButton.addEventListener(\\\click\\\, async () \u003e {\\n // 檢查瀏覽器是否安裝了 MetaMask(或其他注入 web3 的錢包)\\n if (typeof window.ethereum ! undefined) {\\n try {\\n // 請求錢包授權\\n await window.ethereum.request({ method: eth_requestAccounts });\\n \\n // 建立 web3 物件(連到當前 MetaMask 所指向的網路)/* @cat-caption */\\n web3 new Web3(window.ethereum);/* @cat-caption */\\n } catch (error) {\\n console.error(error);\\n alert(\\\連線錢包失敗,請查看控制台訊息或再次嘗試。\\\);\\n }\\n } else {\\n alert(請先安裝 MetaMask 或其他以太坊錢包外掛!);\\n }\\n });\\n \u003c/script\u003e\\n\u003c/head\u003e\\n\\n\u003cbody\u003e\\n \u003cdiv\u003e\\n \u003cbutton id\\\connectButton\\\\u003e連線錢包\u003c/button\u003e\\n \u003c/div\u003e\\n\u003c/body\u003e\\n\\n\u003c/html\u003e\,\caption\:\將 MetaMask 作為 Web3 的提供者 (Provider),讓 DApp 透過 MetaMask 與區塊鏈互動。\,\language\:\html\},{\id\:\73cihd\,\code\:\\u003c!DOCTYPE html\u003e\\n\u003chtml lang\\\en\\\\u003e\\n\\n\u003chead\u003e\\n \u003cmeta charset\\\UTF-8\\\\u003e\\n \u003cmeta name\\\viewport\\\ content\\\widthdevice-width, initial-scale1.0\\\\u003e\\n \u003ctitle\u003eDocument\u003c/title\u003e\\n \u003cscript type\\\module\\\\u003e\\n import Web3 from \\\web3\\\;\\n import { contractABI } from \\\./src/abi.js\\\;\\n\\n const contractAddress \\\0xFEA4f2C9B99Df0B2b7da67e60371BE4275C3d749\\\;\\n\\n let web3; // 用來存放 Web3 物件實例\\n const connectButton document.getElementById(\\\connectButton\\\);\\n connectButton.addEventListener(\\\click\\\, async () \u003e {\\n // 檢查瀏覽器是否安裝了 MetaMask(或其他注入 web3 的錢包)\\n if (typeof window.ethereum ! undefined) {\\n try {\\n // 請求錢包授權\\n await window.ethereum.request({ method: eth_requestAccounts });\\n \\n // 建立 web3 物件(連到當前 MetaMask 所指向的網路)\\n web3 new Web3(window.ethereum);\\n\\n // 印出當前使用的帳戶/* @cat-caption */\\n const accounts await web3.eth.getAccounts();/* @cat-caption */\\n console.log(已連線帳戶:, accounts0);/* @cat-caption */\\n } catch (error) {\\n console.error(error);\\n alert(\\\連線錢包失敗,請查看控制台訊息或再次嘗試。\\\);\\n }\\n } else {\\n alert(請先安裝 MetaMask 或其他以太坊錢包外掛!);\\n }\\n });\\n \u003c/script\u003e\\n\u003c/head\u003e\\n\\n\u003cbody\u003e\\n \u003cdiv\u003e\\n \u003cbutton id\\\connectButton\\\\u003e連線錢包\u003c/button\u003e\\n \u003c/div\u003e\\n\u003c/body\u003e\\n\\n\u003c/html\u003e\,\caption\:\可以印出來看看有沒有成功連結帳號\,\language\:\html\},{\id\:\e6dif1b\,\code\:\\u003c!DOCTYPE html\u003e\\n\u003chtml lang\\\en\\\\u003e\\n\\n\u003chead\u003e\\n \u003cmeta charset\\\UTF-8\\\\u003e\\n \u003cmeta name\\\viewport\\\ content\\\widthdevice-width, initial-scale1.0\\\\u003e\\n \u003ctitle\u003eDocument\u003c/title\u003e\\n \u003cscript type\\\module\\\\u003e\\n import Web3 from \\\web3\\\;\\n import { contractABI } from \\\./src/abi.js\\\;\\n\\n const contractAddress \\\0xFEA4f2C9B99Df0B2b7da67e60371BE4275C3d749\\\;\\n\\n let web3; // 用來存放 Web3 物件實例\\n let contractInstance; // 用來存放合約實例\\n const connectButton document.getElementById(\\\connectButton\\\);\\n connectButton.addEventListener(\\\click\\\, async () \u003e {\\n // 檢查瀏覽器是否安裝了 MetaMask(或其他注入 web3 的錢包)\\n if (typeof window.ethereum ! undefined) {\\n try {\\n // 請求錢包授權\\n await window.ethereum.request({ method: eth_requestAccounts });\\n \\n // 建立 web3 物件(連到當前 MetaMask 所指向的網路)\\n web3 new Web3(window.ethereum);\\n\\n // 印出當前使用的帳戶\\n const accounts await web3.eth.getAccounts();\\n console.log(已連線帳戶:, accounts0);\\n\\n // 建立合約實例/* @cat-caption */\\n contractInstance new web3.eth.Contract(contractABI, contractAddress);/* @cat-caption */\\n\\n alert(\\\錢包連線成功!\\\);\\n } catch (error) {\\n console.error(error);\\n alert(\\\連線錢包失敗,請查看控制台訊息或再次嘗試。\\\);\\n }\\n } else {\\n alert(請先安裝 MetaMask 或其他以太坊錢包外掛!);\\n }\\n });\\n \u003c/script\u003e\\n\u003c/head\u003e\\n\\n\u003cbody\u003e\\n \u003cdiv\u003e\\n \u003cbutton id\\\connectButton\\\\u003e連線錢包\u003c/button\u003e\\n \u003c/div\u003e\\n\u003c/body\u003e\\n\\n\u003c/html\u003e\,\caption\:\因為你可能沒學過物件導向,所以讓我用更簡單的方式來解釋這段程式碼。\\n\\n你可以想像 Web3.js 會幫我們創建一個「合約操作員」,這個操作員負責幫我們與區塊鏈上的智能合約互動。\\n\\n這個 合約操作員 具備三個關鍵能力:\\n- 擁有你的錢包地址的操控權(當然,它不會擅自動用,你需要授權)。\\n- 知道你要操作哪個智能合約(透過 合約地址)。\\n- 知道該怎麼操作這份合約(透過 ABI(操作指南))。\\n\\n當我們想與合約互動時,只需要告訴這個「操作員」我們要做什麼,這位 操作員 會幫你處理所有的技術細節,確保你的指令能順利送進區塊鏈。\\n\,\language\:\html\},{\id\:\stfs1\,\code\:\\u003c!DOCTYPE html\u003e\\n\u003chtml lang\\\en\\\\u003e\\n\\n\u003chead\u003e\\n \u003cmeta charset\\\UTF-8\\\\u003e\\n \u003cmeta name\\\viewport\\\ content\\\widthdevice-width, initial-scale1.0\\\\u003e\\n \u003ctitle\u003eDocument\u003c/title\u003e\\n \u003cscript type\\\module\\\\u003e\\n import Web3 from \\\web3\\\;\\n import { contractABI } from \\\./src/abi.js\\\;\\n\\n const contractAddress \\\0xFEA4f2C9B99Df0B2b7da67e60371BE4275C3d749\\\;\\n\\n let web3; // 用來存放 Web3 物件實例\\n let contractInstance; // 用來存放合約實例\\n const connectButton document.getElementById(\\\connectButton\\\);\\n connectButton.addEventListener(\\\click\\\, async () \u003e {\\n // 檢查瀏覽器是否安裝了 MetaMask(或其他注入 web3 的錢包)\\n if (typeof window.ethereum ! undefined) {\\n try {\\n // 請求錢包授權\\n await window.ethereum.request({ method: eth_requestAccounts });\\n \\n // 建立 web3 物件(連到當前 MetaMask 所指向的網路)\\n web3 new Web3(window.ethereum);\\n\\n // 印出當前使用的帳戶\\n const accounts await web3.eth.getAccounts();\\n console.log(已連線帳戶:, accounts0);\\n\\n // 建立合約實例\\n contractInstance new web3.eth.Contract(contractABI, contractAddress);\\n\\n alert(\\\錢包連線成功!\\\);\\n } catch (error) {\\n console.error(error);\\n alert(\\\連線錢包失敗,請查看控制台訊息或再次嘗試。\\\);\\n }\\n } else {\\n alert(請先安裝 MetaMask 或其他以太坊錢包外掛!);\\n }\\n });\\n \u003c/script\u003e\\n\u003c/head\u003e\\n\\n\u003cbody\u003e\\n \u003cdiv\u003e\\n \u003cbutton id\\\connectButton\\\\u003e連線錢包\u003c/button\u003e\\n \u003c/div\u003e\\n\\n \u003cdiv\u003e/* @cat-caption */\\n \u003cp\u003e目前合約訊息:\u003cspan id\\\currentMessage\\\\u003e---\u003c/span\u003e\u003c/p\u003e/* @cat-caption */\\n \u003cbutton id\\\readMessageButton\\\\u003e讀取合約訊息\u003c/button\u003e/* @cat-caption */\\n \u003c/div\u003e/* @cat-caption */\\n\\n\u003c/body\u003e\\n\\n\u003c/html\u003e\,\caption\:\在 HTML 中顯示合約中的 message 變數\,\language\:\html\},{\id\:\fsxrzr\,\code\:\\u003c!DOCTYPE html\u003e\\n\u003chtml lang\\\en\\\\u003e\\n\\n\u003chead\u003e\\n \u003cmeta charset\\\UTF-8\\\\u003e\\n \u003cmeta name\\\viewport\\\ content\\\widthdevice-width, initial-scale1.0\\\\u003e\\n \u003ctitle\u003eDocument\u003c/title\u003e\\n \u003cscript type\\\module\\\\u003e\\n import Web3 from \\\web3\\\;\\n import { contractABI } from \\\./src/abi.js\\\;\\n\\n const contractAddress \\\0xFEA4f2C9B99Df0B2b7da67e60371BE4275C3d749\\\;\\n\\n let web3; // 用來存放 Web3 物件實例\\n let contractInstance; // 用來存放合約實例\\n const connectButton document.getElementById(\\\connectButton\\\);\\n connectButton.addEventListener(\\\click\\\, async () \u003e {\\n // 檢查瀏覽器是否安裝了 MetaMask(或其他注入 web3 的錢包)\\n if (typeof window.ethereum ! undefined) {\\n try {\\n // 請求錢包授權\\n await window.ethereum.request({ method: eth_requestAccounts });\\n \\n // 建立 web3 物件(連到當前 MetaMask 所指向的網路)\\n web3 new Web3(window.ethereum);\\n\\n // 印出當前使用的帳戶\\n const accounts await web3.eth.getAccounts();\\n console.log(已連線帳戶:, accounts0);\\n\\n // 建立合約實例\\n contractInstance new web3.eth.Contract(contractABI, contractAddress);\\n\\n alert(\\\錢包連線成功!\\\);\\n } catch (error) {\\n console.error(error);\\n alert(\\\連線錢包失敗,請查看控制台訊息或再次嘗試。\\\);\\n }\\n } else {\\n alert(請先安裝 MetaMask 或其他以太坊錢包外掛!);\\n }\\n });\\n\\n const readMessageButton document.getElementById(\\\readMessageButton\\\);\\n\\n readMessageButton.addEventListener(\\\click\\\, async () \u003e {\\n if (!contractInstance) {/* @cat-caption */\\n alert(\\\請先按 連線錢包 按鈕,再進行操作。\\\);/* @cat-caption */\\n return;/* @cat-caption */\\n }/* @cat-caption */\\n });\\n \u003c/script\u003e\\n\u003c/head\u003e\\n\\n\u003cbody\u003e\\n \u003cdiv\u003e\\n \u003cbutton id\\\connectButton\\\\u003e連線錢包\u003c/button\u003e\\n \u003c/div\u003e\\n\\n \u003cdiv\u003e\\n \u003cp\u003e目前合約訊息:\u003cspan id\\\currentMessage\\\\u003e---\u003c/span\u003e\u003c/p\u003e\\n \u003cbutton id\\\readMessageButton\\\\u003e讀取合約訊息\u003c/button\u003e\\n \u003c/div\u003e\\n\\n\u003c/body\u003e\\n\\n\u003c/html\u003e\,\caption\:\在 JS 中設定該按鈕點擊後要做什麼\\n讀取區塊鏈的資料錢,要先連結錢包在進行操作\,\language\:\html\},{\id\:\na3i39\,\code\:\\u003c!DOCTYPE html\u003e\\n\u003chtml lang\\\en\\\\u003e\\n\\n\u003chead\u003e\\n \u003cmeta charset\\\UTF-8\\\\u003e\\n \u003cmeta name\\\viewport\\\ content\\\widthdevice-width, initial-scale1.0\\\\u003e\\n \u003ctitle\u003eDocument\u003c/title\u003e\\n \u003cscript type\\\module\\\\u003e\\n import Web3 from \\\web3\\\;\\n import { contractABI } from \\\./src/abi.js\\\;\\n\\n const contractAddress \\\0xFEA4f2C9B99Df0B2b7da67e60371BE4275C3d749\\\;\\n\\n let web3; // 用來存放 Web3 物件實例\\n let contractInstance; // 用來存放合約實例\\n const connectButton document.getElementById(\\\connectButton\\\);\\n connectButton.addEventListener(\\\click\\\, async () \u003e {\\n // 檢查瀏覽器是否安裝了 MetaMask(或其他注入 web3 的錢包)\\n if (typeof window.ethereum ! undefined) {\\n try {\\n // 請求錢包授權\\n await window.ethereum.request({ method: eth_requestAccounts });\\n \\n // 建立 web3 物件(連到當前 MetaMask 所指向的網路)\\n web3 new Web3(window.ethereum);\\n\\n // 印出當前使用的帳戶\\n const accounts await web3.eth.getAccounts();\\n console.log(已連線帳戶:, accounts0);\\n\\n // 建立合約實例\\n contractInstance new web3.eth.Contract(contractABI, contractAddress);\\n\\n alert(\\\錢包連線成功!\\\);\\n } catch (error) {\\n console.error(error);\\n alert(\\\連線錢包失敗,請查看控制台訊息或再次嘗試。\\\);\\n }\\n } else {\\n alert(請先安裝 MetaMask 或其他以太坊錢包外掛!);\\n }\\n });\\n\\n const readMessageButton document.getElementById(\\\readMessageButton\\\);\\n\\n readMessageButton.addEventListener(\\\click\\\, async () \u003e {\\n if (!contractInstance) {\\n alert(\\\請先按 連線錢包 按鈕,再進行操作。\\\);\\n return;\\n }\\n try {/* @cat-caption */\\n const message await contractInstance.methods.message().call();/* @cat-caption */\\n console.log(\\\合約訊息為:\\\, message);/* @cat-caption */\\n } catch (error) {/* @cat-caption */\\n console.error(error);/* @cat-caption */\\n alert(\\\讀取合約訊息失敗!\\\);/* @cat-caption */\\n }/* @cat-caption */\\n });\\n \u003c/script\u003e\\n\u003c/head\u003e\\n\\n\u003cbody\u003e\\n \u003cdiv\u003e\\n \u003cbutton id\\\connectButton\\\\u003e連線錢包\u003c/button\u003e\\n \u003c/div\u003e\\n\\n \u003cdiv\u003e\\n \u003cp\u003e目前合約訊息:\u003cspan id\\\currentMessage\\\\u003e---\u003c/span\u003e\u003c/p\u003e\\n \u003cbutton id\\\readMessageButton\\\\u003e讀取合約訊息\u003c/button\u003e\\n \u003c/div\u003e\\n\\n\u003c/body\u003e\\n\\n\u003c/html\u003e\,\caption\:\從智能合約中讀取 message 變數的值,而且不會發送交易,只會查詢區塊鏈上的數據。\,\language\:\html\},{\id\:\e54dbj\,\code\:\\u003c!DOCTYPE html\u003e\\n\u003chtml lang\\\en\\\\u003e\\n\\n\u003chead\u003e\\n \u003cmeta charset\\\UTF-8\\\\u003e\\n \u003cmeta name\\\viewport\\\ content\\\widthdevice-width, initial-scale1.0\\\\u003e\\n \u003ctitle\u003eDocument\u003c/title\u003e\\n \u003cscript type\\\module\\\\u003e\\n import Web3 from \\\web3\\\;\\n import { contractABI } from \\\./src/abi.js\\\;\\n\\n const contractAddress \\\0xFEA4f2C9B99Df0B2b7da67e60371BE4275C3d749\\\;\\n\\n let web3; // 用來存放 Web3 物件實例\\n let contractInstance; // 用來存放合約實例\\n const connectButton document.getElementById(\\\connectButton\\\);\\n connectButton.addEventListener(\\\click\\\, async () \u003e {\\n // 檢查瀏覽器是否安裝了 MetaMask(或其他注入 web3 的錢包)\\n if (typeof window.ethereum ! undefined) {\\n try {\\n // 請求錢包授權\\n await window.ethereum.request({ method: eth_requestAccounts });\\n \\n // 建立 web3 物件(連到當前 MetaMask 所指向的網路)\\n web3 new Web3(window.ethereum);\\n\\n // 印出當前使用的帳戶\\n const accounts await web3.eth.getAccounts();\\n console.log(已連線帳戶:, accounts0);\\n\\n // 建立合約實例\\n contractInstance new web3.eth.Contract(contractABI, contractAddress);\\n\\n alert(\\\錢包連線成功!\\\);\\n } catch (error) {\\n console.error(error);\\n alert(\\\連線錢包失敗,請查看控制台訊息或再次嘗試。\\\);\\n }\\n } else {\\n alert(請先安裝 MetaMask 或其他以太坊錢包外掛!);\\n }\\n });\\n\\n const readMessageButton document.getElementById(\\\readMessageButton\\\);\\n const currentMessageSpan document.getElementById(\\\currentMessage\\\);\\n\\n readMessageButton.addEventListener(\\\click\\\, async () \u003e {\\n if (!contractInstance) {\\n alert(\\\請先按 連線錢包 按鈕,再進行操作。\\\);\\n return;\\n }\\n try {\\n const message await contractInstance.methods.message().call();\\n console.log(\\\合約訊息為:\\\, message);\\n currentMessageSpan.textContent message;/* @cat-caption */\\n } catch (error) {\\n console.error(error);\\n alert(\\\讀取合約訊息失敗!\\\);\\n }\\n });\\n \u003c/script\u003e\\n\u003c/head\u003e\\n\\n\u003cbody\u003e\\n \u003cdiv\u003e\\n \u003cbutton id\\\connectButton\\\\u003e連線錢包\u003c/button\u003e\\n \u003c/div\u003e\\n\\n \u003cdiv\u003e\\n \u003cp\u003e目前合約訊息:\u003cspan id\\\currentMessage\\\\u003e---\u003c/span\u003e\u003c/p\u003e\\n \u003cbutton id\\\readMessageButton\\\\u003e讀取合約訊息\u003c/button\u003e\\n \u003c/div\u003e\\n\\n\u003c/body\u003e\\n\\n\u003c/html\u003e\,\caption\:\將從區塊鏈上取得的資料寫入網頁\,\language\:\html\},{\id\:\b2kohc\,\code\:\\u003c!DOCTYPE html\u003e\\n\u003chtml lang\\\en\\\\u003e\\n\\n\u003chead\u003e\\n \u003cmeta charset\\\UTF-8\\\\u003e\\n \u003cmeta name\\\viewport\\\ content\\\widthdevice-width, initial-scale1.0\\\\u003e\\n \u003ctitle\u003eDocument\u003c/title\u003e\\n \u003cscript type\\\module\\\\u003e\\n import Web3 from \\\web3\\\;\\n import { contractABI } from \\\./src/abi.js\\\;\\n\\n const contractAddress \\\0xFEA4f2C9B99Df0B2b7da67e60371BE4275C3d749\\\;\\n\\n let web3; // 用來存放 Web3 物件實例\\n let contractInstance; // 用來存放合約實例\\n const connectButton document.getElementById(\\\connectButton\\\);\\n connectButton.addEventListener(\\\click\\\, async () \u003e {\\n // 檢查瀏覽器是否安裝了 MetaMask(或其他注入 web3 的錢包)\\n if (typeof window.ethereum ! undefined) {\\n try {\\n // 請求錢包授權\\n await window.ethereum.request({ method: eth_requestAccounts });\\n \\n // 建立 web3 物件(連到當前 MetaMask 所指向的網路)\\n web3 new Web3(window.ethereum);\\n\\n // 印出當前使用的帳戶\\n const accounts await web3.eth.getAccounts();\\n console.log(已連線帳戶:, accounts0);\\n\\n // 建立合約實例\\n contractInstance new web3.eth.Contract(contractABI, contractAddress);\\n\\n alert(\\\錢包連線成功!\\\);\\n } catch (error) {\\n console.error(error);\\n alert(\\\連線錢包失敗,請查看控制台訊息或再次嘗試。\\\);\\n }\\n } else {\\n alert(請先安裝 MetaMask 或其他以太坊錢包外掛!);\\n }\\n });\\n\\n const readMessageButton document.getElementById(\\\readMessageButton\\\);\\n const currentMessageSpan document.getElementById(\\\currentMessage\\\);\\n\\n readMessageButton.addEventListener(\\\click\\\, async () \u003e {\\n if (!contractInstance) {\\n alert(\\\請先按 連線錢包 按鈕,再進行操作。\\\);\\n return;\\n }\\n try {\\n const message await contractInstance.methods.message().call();\\n console.log(\\\合約訊息為:\\\, message);\\n currentMessageSpan.textContent message;\\n } catch (error) {\\n console.error(error);\\n alert(\\\讀取合約訊息失敗!\\\);\\n }\\n });\\n \u003c/script\u003e\\n\u003c/head\u003e\\n\\n\u003cbody\u003e\\n \u003cdiv\u003e\\n \u003cbutton id\\\connectButton\\\\u003e連線錢包\u003c/button\u003e\\n \u003c/div\u003e\\n\\n \u003cdiv\u003e\\n \u003cp\u003e目前合約訊息:\u003cspan id\\\currentMessage\\\\u003e---\u003c/span\u003e\u003c/p\u003e\\n \u003cbutton id\\\readMessageButton\\\\u003e讀取合約訊息\u003c/button\u003e\\n \u003c/div\u003e\\n\\n \u003cdiv\u003e/* @cat-caption */\\n \u003cinput type\\\text\\\ id\\\newMessageInput\\\ placeholder\\\輸入新的訊息\\\ /\u003e/* @cat-caption */\\n \u003cbutton id\\\setMessageButton\\\\u003e寫入合約訊息\u003c/button\u003e/* @cat-caption */\\n \u003c/div\u003e/* @cat-caption */\\n\u003c/body\u003e\\n\\n\u003c/html\u003e\,\caption\:\再來就是要來實踐如何觸發 setMessage 的 function 啦\\n先從 HTML 介面開始\,\language\:\html\},{\id\:\d0egv\,\code\:\\u003c!DOCTYPE html\u003e\\n\u003chtml lang\\\en\\\\u003e\\n\\n\u003chead\u003e\\n \u003cmeta charset\\\UTF-8\\\\u003e\\n \u003cmeta name\\\viewport\\\ content\\\widthdevice-width, initial-scale1.0\\\\u003e\\n \u003ctitle\u003eDocument\u003c/title\u003e\\n \u003cscript type\\\module\\\\u003e\\n import Web3 from \\\web3\\\;\\n import { contractABI } from \\\./src/abi.js\\\;\\n\\n const contractAddress \\\0xFEA4f2C9B99Df0B2b7da67e60371BE4275C3d749\\\;\\n\\n let web3; // 用來存放 Web3 物件實例\\n let contractInstance; // 用來存放合約實例\\n const connectButton document.getElementById(\\\connectButton\\\);\\n connectButton.addEventListener(\\\click\\\, async () \u003e {\\n // 檢查瀏覽器是否安裝了 MetaMask(或其他注入 web3 的錢包)\\n if (typeof window.ethereum ! undefined) {\\n try {\\n // 請求錢包授權\\n await window.ethereum.request({ method: eth_requestAccounts });\\n \\n // 建立 web3 物件(連到當前 MetaMask 所指向的網路)\\n web3 new Web3(window.ethereum);\\n\\n // 印出當前使用的帳戶\\n const accounts await web3.eth.getAccounts();\\n console.log(已連線帳戶:, accounts0);\\n\\n // 建立合約實例\\n contractInstance new web3.eth.Contract(contractABI, contractAddress);\\n\\n alert(\\\錢包連線成功!\\\);\\n } catch (error) {\\n console.error(error);\\n alert(\\\連線錢包失敗,請查看控制台訊息或再次嘗試。\\\);\\n }\\n } else {\\n alert(請先安裝 MetaMask 或其他以太坊錢包外掛!);\\n }\\n });\\n\\n const readMessageButton document.getElementById(\\\readMessageButton\\\);\\n const currentMessageSpan document.getElementById(\\\currentMessage\\\);\\n\\n readMessageButton.addEventListener(\\\click\\\, async () \u003e {\\n if (!contractInstance) {\\n alert(\\\請先按 連線錢包 按鈕,再進行操作。\\\);\\n return;\\n }\\n try {\\n const message await contractInstance.methods.message().call();\\n console.log(\\\合約訊息為:\\\, message);\\n currentMessageSpan.textContent message;\\n } catch (error) {\\n console.error(error);\\n alert(\\\讀取合約訊息失敗!\\\);\\n }\\n });\\n\\n // 5. 寫入合約訊息\\n const setMessageButton document.getElementById(\\\setMessageButton\\\);/* @cat-caption */\\n const newMessageInput document.getElementById(\\\newMessageInput\\\);/* @cat-caption */\\n\\n setMessageButton.addEventListener(\\\click\\\, async () \u003e {/* @cat-caption */\\n if (!contractInstance) {/* @cat-caption */\\n alert(\\\請先按 連線錢包 按鈕,再進行操作。\\\);/* @cat-caption */\\n return;/* @cat-caption */\\n }/* @cat-caption */\\n // ... 點擊按鈕並確認連線錢包後要做的事情/* @cat-caption */\\n });/* @cat-caption */\\n \u003c/script\u003e\\n\u003c/head\u003e\\n\\n\u003cbody\u003e\\n \u003cdiv\u003e\\n \u003cbutton id\\\connectButton\\\\u003e連線錢包\u003c/button\u003e\\n \u003c/div\u003e\\n\\n \u003cdiv\u003e\\n \u003cp\u003e目前合約訊息:\u003cspan id\\\currentMessage\\\\u003e---\u003c/span\u003e\u003c/p\u003e\\n \u003cbutton id\\\readMessageButton\\\\u003e讀取合約訊息\u003c/button\u003e\\n \u003c/div\u003e\\n\\n \u003cdiv\u003e\\n \u003cinput type\\\text\\\ id\\\newMessageInput\\\ placeholder\\\輸入新的訊息\\\ /\u003e\\n \u003cbutton id\\\setMessageButton\\\\u003e寫入合約訊息\u003c/button\u003e\\n \u003c/div\u003e\\n\u003c/body\u003e\\n\\n\u003c/html\u003e\,\caption\:\處理 JS 的事件綁定與錢包檢查\,\language\:\html\},{\id\:\jpy5i\,\code\:\\u003c!DOCTYPE html\u003e\\n\u003chtml lang\\\en\\\\u003e\\n\\n\u003chead\u003e\\n \u003cmeta charset\\\UTF-8\\\\u003e\\n \u003cmeta name\\\viewport\\\ content\\\widthdevice-width, initial-scale1.0\\\\u003e\\n \u003ctitle\u003eDocument\u003c/title\u003e\\n \u003cscript type\\\module\\\\u003e\\n import Web3 from \\\web3\\\;\\n import { contractABI } from \\\./src/abi.js\\\;\\n\\n const contractAddress \\\0xFEA4f2C9B99Df0B2b7da67e60371BE4275C3d749\\\;\\n\\n let web3; // 用來存放 Web3 物件實例\\n let contractInstance; // 用來存放合約實例\\n const connectButton document.getElementById(\\\connectButton\\\);\\n connectButton.addEventListener(\\\click\\\, async () \u003e {\\n // 檢查瀏覽器是否安裝了 MetaMask(或其他注入 web3 的錢包)\\n if (typeof window.ethereum ! undefined) {\\n try {\\n // 請求錢包授權\\n await window.ethereum.request({ method: eth_requestAccounts });\\n \\n // 建立 web3 物件(連到當前 MetaMask 所指向的網路)\\n web3 new Web3(window.ethereum);\\n\\n // 印出當前使用的帳戶\\n const accounts await web3.eth.getAccounts();\\n console.log(已連線帳戶:, accounts0);\\n\\n // 建立合約實例\\n contractInstance new web3.eth.Contract(contractABI, contractAddress);\\n\\n alert(\\\錢包連線成功!\\\);\\n } catch (error) {\\n console.error(error);\\n alert(\\\連線錢包失敗,請查看控制台訊息或再次嘗試。\\\);\\n }\\n } else {\\n alert(請先安裝 MetaMask 或其他以太坊錢包外掛!);\\n }\\n });\\n\\n const readMessageButton document.getElementById(\\\readMessageButton\\\);\\n const currentMessageSpan document.getElementById(\\\currentMessage\\\);\\n\\n readMessageButton.addEventListener(\\\click\\\, async () \u003e {\\n if (!contractInstance) {\\n alert(\\\請先按 連線錢包 按鈕,再進行操作。\\\);\\n return;\\n }\\n try {\\n const message await contractInstance.methods.message().call();\\n console.log(\\\合約訊息為:\\\, message);\\n currentMessageSpan.textContent message;\\n } catch (error) {\\n console.error(error);\\n alert(\\\讀取合約訊息失敗!\\\);\\n }\\n });\\n\\n // 5. 寫入合約訊息\\n const setMessageButton document.getElementById(\\\setMessageButton\\\);\\n const newMessageInput document.getElementById(\\\newMessageInput\\\);\\n\\n setMessageButton.addEventListener(\\\click\\\, async () \u003e {\\n if (!contractInstance) {\\n alert(\\\請先按 連線錢包 按鈕,再進行操作。\\\);\\n return;\\n }\\n const newMessage newMessageInput.value;/* @cat-caption */\\n if (!newMessage) {/* @cat-caption */\\n alert(\\\請輸入新的訊息再嘗試。\\\);/* @cat-caption */\\n return;/* @cat-caption */\\n }/* @cat-caption */\\n });\\n \u003c/script\u003e\\n\u003c/head\u003e\\n\\n\u003cbody\u003e\\n \u003cdiv\u003e\\n \u003cbutton id\\\connectButton\\\\u003e連線錢包\u003c/button\u003e\\n \u003c/div\u003e\\n\\n \u003cdiv\u003e\\n \u003cp\u003e目前合約訊息:\u003cspan id\\\currentMessage\\\\u003e---\u003c/span\u003e\u003c/p\u003e\\n \u003cbutton id\\\readMessageButton\\\\u003e讀取合約訊息\u003c/button\u003e\\n \u003c/div\u003e\\n\\n \u003cdiv\u003e\\n \u003cinput type\\\text\\\ id\\\newMessageInput\\\ placeholder\\\輸入新的訊息\\\ /\u003e\\n \u003cbutton id\\\setMessageButton\\\\u003e寫入合約訊息\u003c/button\u003e\\n \u003c/div\u003e\\n\u003c/body\u003e\\n\\n\u003c/html\u003e\,\caption\:\從輸入框取得當前輸入的數值,檢查一下有沒有輸入\,\language\:\html\},{\id\:\gupi9c\,\code\:\\u003c!DOCTYPE html\u003e\\n\u003chtml lang\\\en\\\\u003e\\n\\n\u003chead\u003e\\n \u003cmeta charset\\\UTF-8\\\\u003e\\n \u003cmeta name\\\viewport\\\ content\\\widthdevice-width, initial-scale1.0\\\\u003e\\n \u003ctitle\u003eDocument\u003c/title\u003e\\n \u003cscript type\\\module\\\\u003e\\n import Web3 from \\\web3\\\;\\n import { contractABI } from \\\./src/abi.js\\\;\\n\\n const contractAddress \\\0xFEA4f2C9B99Df0B2b7da67e60371BE4275C3d749\\\;\\n\\n let web3; // 用來存放 Web3 物件實例\\n let contractInstance; // 用來存放合約實例\\n const connectButton document.getElementById(\\\connectButton\\\);\\n connectButton.addEventListener(\\\click\\\, async () \u003e {\\n // 檢查瀏覽器是否安裝了 MetaMask(或其他注入 web3 的錢包)\\n if (typeof window.ethereum ! undefined) {\\n try {\\n // 請求錢包授權\\n await window.ethereum.request({ method: eth_requestAccounts });\\n \\n // 建立 web3 物件(連到當前 MetaMask 所指向的網路)\\n web3 new Web3(window.ethereum);\\n\\n // 印出當前使用的帳戶\\n const accounts await web3.eth.getAccounts();\\n console.log(已連線帳戶:, accounts0);\\n\\n // 建立合約實例\\n contractInstance new web3.eth.Contract(contractABI, contractAddress);\\n\\n alert(\\\錢包連線成功!\\\);\\n } catch (error) {\\n console.error(error);\\n alert(\\\連線錢包失敗,請查看控制台訊息或再次嘗試。\\\);\\n }\\n } else {\\n alert(請先安裝 MetaMask 或其他以太坊錢包外掛!);\\n }\\n });\\n\\n const readMessageButton document.getElementById(\\\readMessageButton\\\);\\n const currentMessageSpan document.getElementById(\\\currentMessage\\\);\\n\\n readMessageButton.addEventListener(\\\click\\\, async () \u003e {\\n if (!contractInstance) {\\n alert(\\\請先按 連線錢包 按鈕,再進行操作。\\\);\\n return;\\n }\\n try {\\n const message await contractInstance.methods.message().call();\\n console.log(\\\合約訊息為:\\\, message);\\n currentMessageSpan.textContent message;\\n } catch (error) {\\n console.error(error);\\n alert(\\\讀取合約訊息失敗!\\\);\\n }\\n });\\n\\n // 5. 寫入合約訊息\\n const setMessageButton document.getElementById(\\\setMessageButton\\\);\\n const newMessageInput document.getElementById(\\\newMessageInput\\\);\\n\\n setMessageButton.addEventListener(\\\click\\\, async () \u003e {\\n if (!contractInstance) {\\n alert(\\\請先按 連線錢包 按鈕,再進行操作。\\\);\\n return;\\n }\\n const newMessage newMessageInput.value;\\n if (!newMessage) {\\n alert(\\\請輸入新的訊息再嘗試。\\\);\\n return;\\n }\\n try {\\n // 發送交易需要指定哪個帳戶做為 from/* @cat-caption */\\n const accounts await web3.eth.getAccounts();/* @cat-caption */\\n const usedAccount accounts0/* @cat-caption */\\n \\n } catch (error) {\\n console.error(error);\\n alert(\\\寫入合約訊息失敗,請查看控制台訊息。\\\);\\n }\\n });\\n \u003c/script\u003e\\n\u003c/head\u003e\\n\\n\u003cbody\u003e\\n \u003cdiv\u003e\\n \u003cbutton id\\\connectButton\\\\u003e連線錢包\u003c/button\u003e\\n \u003c/div\u003e\\n\\n \u003cdiv\u003e\\n \u003cp\u003e目前合約訊息:\u003cspan id\\\currentMessage\\\\u003e---\u003c/span\u003e\u003c/p\u003e\\n \u003cbutton id\\\readMessageButton\\\\u003e讀取合約訊息\u003c/button\u003e\\n \u003c/div\u003e\\n\\n \u003cdiv\u003e\\n \u003cinput type\\\text\\\ id\\\newMessageInput\\\ placeholder\\\輸入新的訊息\\\ /\u003e\\n \u003cbutton id\\\setMessageButton\\\\u003e寫入合約訊息\u003c/button\u003e\\n \u003c/div\u003e\\n\u003c/body\u003e\\n\\n\u003c/html\u003e\,\caption\:\取得剛剛綁定好的錢包,由於用戶可能有多個錢包\\n使用首個錢包\,\language\:\html\},{\id\:\4sz1c9n\,\code\:\\u003c!DOCTYPE html\u003e\\n\u003chtml lang\\\en\\\\u003e\\n\\n\u003chead\u003e\\n \u003cmeta charset\\\UTF-8\\\\u003e\\n \u003cmeta name\\\viewport\\\ content\\\widthdevice-width, initial-scale1.0\\\\u003e\\n \u003ctitle\u003eDocument\u003c/title\u003e\\n \u003cscript type\\\module\\\\u003e\\n import Web3 from \\\web3\\\;\\n import { contractABI } from \\\./src/abi.js\\\;\\n\\n const contractAddress \\\0xFEA4f2C9B99Df0B2b7da67e60371BE4275C3d749\\\;\\n\\n let web3; // 用來存放 Web3 物件實例\\n let contractInstance; // 用來存放合約實例\\n const connectButton document.getElementById(\\\connectButton\\\);\\n connectButton.addEventListener(\\\click\\\, async () \u003e {\\n // 檢查瀏覽器是否安裝了 MetaMask(或其他注入 web3 的錢包)\\n if (typeof window.ethereum ! undefined) {\\n try {\\n // 請求錢包授權\\n await window.ethereum.request({ method: eth_requestAccounts });\\n \\n // 建立 web3 物件(連到當前 MetaMask 所指向的網路)\\n web3 new Web3(window.ethereum);\\n\\n // 印出當前使用的帳戶\\n const accounts await web3.eth.getAccounts();\\n console.log(已連線帳戶:, accounts0);\\n\\n // 建立合約實例\\n contractInstance new web3.eth.Contract(contractABI, contractAddress);\\n\\n alert(\\\錢包連線成功!\\\);\\n } catch (error) {\\n console.error(error);\\n alert(\\\連線錢包失敗,請查看控制台訊息或再次嘗試。\\\);\\n }\\n } else {\\n alert(請先安裝 MetaMask 或其他以太坊錢包外掛!);\\n }\\n });\\n\\n const readMessageButton document.getElementById(\\\readMessageButton\\\);\\n const currentMessageSpan document.getElementById(\\\currentMessage\\\);\\n\\n readMessageButton.addEventListener(\\\click\\\, async () \u003e {\\n if (!contractInstance) {\\n alert(\\\請先按 連線錢包 按鈕,再進行操作。\\\);\\n return;\\n }\\n try {\\n const message await contractInstance.methods.message().call();\\n console.log(\\\合約訊息為:\\\, message);\\n currentMessageSpan.textContent message;\\n } catch (error) {\\n console.error(error);\\n alert(\\\讀取合約訊息失敗!\\\);\\n }\\n });\\n\\n // 5. 寫入合約訊息\\n const setMessageButton document.getElementById(\\\setMessageButton\\\);\\n const newMessageInput document.getElementById(\\\newMessageInput\\\);\\n\\n setMessageButton.addEventListener(\\\click\\\, async () \u003e {\\n if (!contractInstance) {\\n alert(\\\請先按 連線錢包 按鈕,再進行操作。\\\);\\n return;\\n }\\n const newMessage newMessageInput.value;\\n if (!newMessage) {\\n alert(\\\請輸入新的訊息再嘗試。\\\);\\n return;\\n }\\n try {\\n // 發送交易需要指定哪個帳戶做為 from\\n const accounts await web3.eth.getAccounts();\\n const usedAccount accounts0\\n const receipt await contractInstance.methods/* @cat-caption */\\n .setMessage(newMessage)/* @cat-caption */\\n .send({ from: usedAccount });/* @cat-caption */\\n\\n console.log(\\\交易回執:\\\, receipt);/* @cat-caption */\\n alert(\\\寫入訊息成功,請再度點擊 讀取合約訊息 查看更新。\\\);/* @cat-caption */\\n } catch (error) {\\n console.error(error);\\n alert(\\\寫入合約訊息失敗,請查看控制台訊息。\\\);\\n }\\n });\\n \u003c/script\u003e\\n\u003c/head\u003e\\n\\n\u003cbody\u003e\\n \u003cdiv\u003e\\n \u003cbutton id\\\connectButton\\\\u003e連線錢包\u003c/button\u003e\\n \u003c/div\u003e\\n\\n \u003cdiv\u003e\\n \u003cp\u003e目前合約訊息:\u003cspan id\\\currentMessage\\\\u003e---\u003c/span\u003e\u003c/p\u003e\\n \u003cbutton id\\\readMessageButton\\\\u003e讀取合約訊息\u003c/button\u003e\\n \u003c/div\u003e\\n\\n \u003cdiv\u003e\\n \u003cinput type\\\text\\\ id\\\newMessageInput\\\ placeholder\\\輸入新的訊息\\\ /\u003e\\n \u003cbutton id\\\setMessageButton\\\\u003e寫入合約訊息\u003c/button\u003e\\n \u003c/div\u003e\\n\u003c/body\u003e\\n\\n\u003c/html\u003e\,\caption\:\正式發起 setMessage 的合約請求,並且以我剛剛選定好的錢包來當作付款對象。\,\language\:\html\},{\id\:\ckpcff\,\code\:\\,\caption\:\最後打開測試網站 http://localhost:5173/\\n跑跑看吧\,\language\:\html\,\image\:{\url\:\/api/object/1738172756488-9g6j4h.png\,\position\:\center\,\fullWidth\:true,\width\:200}},{\id\:\gae9dc\,\code\:\\,\caption\:\到 Remix 看看,你會發現數值也變了\,\language\:\html\,\image\:{\url\:\/api/object/1738172826454-2abrg.png\,\position\:\center\,\fullWidth\:true,\width\:200}})/script>script>self.__next_f.push(1,26:T553,)/script>script>self.__next_f.push(1,\u003c!DOCTYPE html\u003e\n\u003chtml lang\en\\u003e\n\n\u003chead\u003e\n \u003cmeta charset\UTF-8\\u003e\n \u003cmeta name\viewport\ content\widthdevice-width, initial-scale1.0\\u003e\n \u003ctitle\u003eDocument\u003c/title\u003e\n \u003cscript type\module\\u003e\n import Web3 from \web3\;\n import { contractABI } from \./src/abi.js\;\n\n const contractAddress \0xFEA4f2C9B99Df0B2b7da67e60371BE4275C3d749\;\n\n const connectButton document.getElementById(\connectButton\);\n connectButton.addEventListener(\click\, async () \u003e {\n // 檢查瀏覽器是否安裝了 MetaMask(或其他注入 web3 的錢包)\n if (typeof window.ethereum ! undefined) {\n try {/* @cat-caption */\n // 請求錢包授權/* @cat-caption */\n await window.ethereum.request({ method: eth_requestAccounts });/* @cat-caption */\n } catch (error) {/* @cat-caption */\n console.error(error);/* @cat-caption */\n alert(\連線錢包失敗,請查看控制台訊息或再次嘗試。\);/* @cat-caption */\n }/* @cat-caption */\n } else {\n alert(請先安裝 MetaMask 或其他以太坊錢包外掛!);\n }\n });\n \u003c/script\u003e\n\u003c/head\u003e\n\n\u003cbody\u003e\n \u003cdiv\u003e\n \u003cbutton id\connectButton\\u003e連線錢包\u003c/button\u003e\n \u003c/div\u003e\n\u003c/body\u003e\n\n\u003c/html\u003e)/script>script>self.__next_f.push(1,27:T5f0,)/script>script>self.__next_f.push(1,\u003c!DOCTYPE html\u003e\n\u003chtml lang\en\\u003e\n\n\u003chead\u003e\n \u003cmeta charset\UTF-8\\u003e\n \u003cmeta name\viewport\ content\widthdevice-width, initial-scale1.0\\u003e\n \u003ctitle\u003eDocument\u003c/title\u003e\n \u003cscript type\module\\u003e\n import Web3 from \web3\;\n import { contractABI } from \./src/abi.js\;\n\n const contractAddress \0xFEA4f2C9B99Df0B2b7da67e60371BE4275C3d749\;\n\n let web3; // 用來存放 Web3 物件實例/* @cat-caption */\n const connectButton document.getElementById(\connectButton\);\n connectButton.addEventListener(\click\, async () \u003e {\n // 檢查瀏覽器是否安裝了 MetaMask(或其他注入 web3 的錢包)\n if (typeof window.ethereum ! undefined) {\n try {\n // 請求錢包授權\n await window.ethereum.request({ method: eth_requestAccounts });\n \n // 建立 web3 物件(連到當前 MetaMask 所指向的網路)/* @cat-caption */\n web3 new Web3(window.ethereum);/* @cat-caption */\n } catch (error) {\n console.error(error);\n alert(\連線錢包失敗,請查看控制台訊息或再次嘗試。\);\n }\n } else {\n alert(請先安裝 MetaMask 或其他以太坊錢包外掛!);\n }\n });\n \u003c/script\u003e\n\u003c/head\u003e\n\n\u003cbody\u003e\n \u003cdiv\u003e\n \u003cbutton id\connectButton\\u003e連線錢包\u003c/button\u003e\n \u003c/div\u003e\n\u003c/body\u003e\n\n\u003c/html\u003e)/script>script>self.__next_f.push(1,28:T6ab,)/script>script>self.__next_f.push(1,\u003c!DOCTYPE html\u003e\n\u003chtml lang\en\\u003e\n\n\u003chead\u003e\n \u003cmeta charset\UTF-8\\u003e\n \u003cmeta name\viewport\ content\widthdevice-width, initial-scale1.0\\u003e\n \u003ctitle\u003eDocument\u003c/title\u003e\n \u003cscript type\module\\u003e\n import Web3 from \web3\;\n import { contractABI } from \./src/abi.js\;\n\n const contractAddress \0xFEA4f2C9B99Df0B2b7da67e60371BE4275C3d749\;\n\n let web3; // 用來存放 Web3 物件實例\n const connectButton document.getElementById(\connectButton\);\n connectButton.addEventListener(\click\, async () \u003e {\n // 檢查瀏覽器是否安裝了 MetaMask(或其他注入 web3 的錢包)\n if (typeof window.ethereum ! undefined) {\n try {\n // 請求錢包授權\n await window.ethereum.request({ method: eth_requestAccounts });\n \n // 建立 web3 物件(連到當前 MetaMask 所指向的網路)\n web3 new Web3(window.ethereum);\n\n // 印出當前使用的帳戶/* @cat-caption */\n const accounts await web3.eth.getAccounts();/* @cat-caption */\n console.log(已連線帳戶:, accounts0);/* @cat-caption */\n } catch (error) {\n console.error(error);\n alert(\連線錢包失敗,請查看控制台訊息或再次嘗試。\);\n }\n } else {\n alert(請先安裝 MetaMask 或其他以太坊錢包外掛!);\n }\n });\n \u003c/script\u003e\n\u003c/head\u003e\n\n\u003cbody\u003e\n \u003cdiv\u003e\n \u003cbutton id\connectButton\\u003e連線錢包\u003c/button\u003e\n \u003c/div\u003e\n\u003c/body\u003e\n\n\u003c/html\u003e)/script>script>self.__next_f.push(1,29:T791,)/script>script>self.__next_f.push(1,\u003c!DOCTYPE html\u003e\n\u003chtml lang\en\\u003e\n\n\u003chead\u003e\n \u003cmeta charset\UTF-8\\u003e\n \u003cmeta name\viewport\ content\widthdevice-width, initial-scale1.0\\u003e\n \u003ctitle\u003eDocument\u003c/title\u003e\n \u003cscript type\module\\u003e\n import Web3 from \web3\;\n import { contractABI } from \./src/abi.js\;\n\n const contractAddress \0xFEA4f2C9B99Df0B2b7da67e60371BE4275C3d749\;\n\n let web3; // 用來存放 Web3 物件實例\n let contractInstance; // 用來存放合約實例\n const connectButton document.getElementById(\connectButton\);\n connectButton.addEventListener(\click\, async () \u003e {\n // 檢查瀏覽器是否安裝了 MetaMask(或其他注入 web3 的錢包)\n if (typeof window.ethereum ! undefined) {\n try {\n // 請求錢包授權\n await window.ethereum.request({ method: eth_requestAccounts });\n \n // 建立 web3 物件(連到當前 MetaMask 所指向的網路)\n web3 new Web3(window.ethereum);\n\n // 印出當前使用的帳戶\n const accounts await web3.eth.getAccounts();\n console.log(已連線帳戶:, accounts0);\n\n // 建立合約實例/* @cat-caption */\n contractInstance new web3.eth.Contract(contractABI, contractAddress);/* @cat-caption */\n\n alert(\錢包連線成功!\);\n } catch (error) {\n console.error(error);\n alert(\連線錢包失敗,請查看控制台訊息或再次嘗試。\);\n }\n } else {\n alert(請先安裝 MetaMask 或其他以太坊錢包外掛!);\n }\n });\n \u003c/script\u003e\n\u003c/head\u003e\n\n\u003cbody\u003e\n \u003cdiv\u003e\n \u003cbutton id\connectButton\\u003e連線錢包\u003c/button\u003e\n \u003c/div\u003e\n\u003c/body\u003e\n\n\u003c/html\u003e)/script>script>self.__next_f.push(1,2a:T858,)/script>script>self.__next_f.push(1,\u003c!DOCTYPE html\u003e\n\u003chtml lang\en\\u003e\n\n\u003chead\u003e\n \u003cmeta charset\UTF-8\\u003e\n \u003cmeta name\viewport\ content\widthdevice-width, initial-scale1.0\\u003e\n \u003ctitle\u003eDocument\u003c/title\u003e\n \u003cscript type\module\\u003e\n import Web3 from \web3\;\n import { contractABI } from \./src/abi.js\;\n\n const contractAddress \0xFEA4f2C9B99Df0B2b7da67e60371BE4275C3d749\;\n\n let web3; // 用來存放 Web3 物件實例\n let contractInstance; // 用來存放合約實例\n const connectButton document.getElementById(\connectButton\);\n connectButton.addEventListener(\click\, async () \u003e {\n // 檢查瀏覽器是否安裝了 MetaMask(或其他注入 web3 的錢包)\n if (typeof window.ethereum ! undefined) {\n try {\n // 請求錢包授權\n await window.ethereum.request({ method: eth_requestAccounts });\n \n // 建立 web3 物件(連到當前 MetaMask 所指向的網路)\n web3 new Web3(window.ethereum);\n\n // 印出當前使用的帳戶\n const accounts await web3.eth.getAccounts();\n console.log(已連線帳戶:, accounts0);\n\n // 建立合約實例\n contractInstance new web3.eth.Contract(contractABI, contractAddress);\n\n alert(\錢包連線成功!\);\n } catch (error) {\n console.error(error);\n alert(\連線錢包失敗,請查看控制台訊息或再次嘗試。\);\n }\n } else {\n alert(請先安裝 MetaMask 或其他以太坊錢包外掛!);\n }\n });\n \u003c/script\u003e\n\u003c/head\u003e\n\n\u003cbody\u003e\n \u003cdiv\u003e\n \u003cbutton id\connectButton\\u003e連線錢包\u003c/button\u003e\n \u003c/div\u003e\n\n \u003cdiv\u003e/* @cat-caption */\n \u003cp\u003e目前合約訊息:\u003cspan id\currentMessage\\u003e---\u003c/span\u003e\u003c/p\u003e/* @cat-caption */\n \u003cbutton id\readMessageButton\\u003e讀取合約訊息\u003c/button\u003e/* @cat-caption */\n \u003c/div\u003e/* @cat-caption */\n\n\u003c/body\u003e\n\n\u003c/html\u003e)/script>script>self.__next_f.push(1,2b:T992,)/script>script>self.__next_f.push(1,\u003c!DOCTYPE html\u003e\n\u003chtml lang\en\\u003e\n\n\u003chead\u003e\n \u003cmeta charset\UTF-8\\u003e\n \u003cmeta name\viewport\ content\widthdevice-width, initial-scale1.0\\u003e\n \u003ctitle\u003eDocument\u003c/title\u003e\n \u003cscript type\module\\u003e\n import Web3 from \web3\;\n import { contractABI } from \./src/abi.js\;\n\n const contractAddress \0xFEA4f2C9B99Df0B2b7da67e60371BE4275C3d749\;\n\n let web3; // 用來存放 Web3 物件實例\n let contractInstance; // 用來存放合約實例\n const connectButton document.getElementById(\connectButton\);\n connectButton.addEventListener(\click\, async () \u003e {\n // 檢查瀏覽器是否安裝了 MetaMask(或其他注入 web3 的錢包)\n if (typeof window.ethereum ! undefined) {\n try {\n // 請求錢包授權\n await window.ethereum.request({ method: eth_requestAccounts });\n \n // 建立 web3 物件(連到當前 MetaMask 所指向的網路)\n web3 new Web3(window.ethereum);\n\n // 印出當前使用的帳戶\n const accounts await web3.eth.getAccounts();\n console.log(已連線帳戶:, accounts0);\n\n // 建立合約實例\n contractInstance new web3.eth.Contract(contractABI, contractAddress);\n\n alert(\錢包連線成功!\);\n } catch (error) {\n console.error(error);\n alert(\連線錢包失敗,請查看控制台訊息或再次嘗試。\);\n }\n } else {\n alert(請先安裝 MetaMask 或其他以太坊錢包外掛!);\n }\n });\n\n const readMessageButton document.getElementById(\readMessageButton\);\n\n readMessageButton.addEventListener(\click\, async () \u003e {\n if (!contractInstance) {/* @cat-caption */\n alert(\請先按 連線錢包 按鈕,再進行操作。\);/* @cat-caption */\n return;/* @cat-caption */\n }/* @cat-caption */\n });\n \u003c/script\u003e\n\u003c/head\u003e\n\n\u003cbody\u003e\n \u003cdiv\u003e\n \u003cbutton id\connectButton\\u003e連線錢包\u003c/button\u003e\n \u003c/div\u003e\n\n \u003cdiv\u003e\n \u003cp\u003e目前合約訊息:\u003cspan id\currentMessage\\u003e---\u003c/span\u003e\u003c/p\u003e\n \u003cbutton id\readMessageButton\\u003e讀取合約訊息\u003c/button\u003e\n \u003c/div\u003e\n\n\u003c/body\u003e\n\n\u003c/html\u003e)/script>script>self.__next_f.push(1,2c:Taef,)/script>script>self.__next_f.push(1,\u003c!DOCTYPE html\u003e\n\u003chtml lang\en\\u003e\n\n\u003chead\u003e\n \u003cmeta charset\UTF-8\\u003e\n \u003cmeta name\viewport\ content\widthdevice-width, initial-scale1.0\\u003e\n \u003ctitle\u003eDocument\u003c/title\u003e\n \u003cscript type\module\\u003e\n import Web3 from \web3\;\n import { contractABI } from \./src/abi.js\;\n\n const contractAddress \0xFEA4f2C9B99Df0B2b7da67e60371BE4275C3d749\;\n\n let web3; // 用來存放 Web3 物件實例\n let contractInstance; // 用來存放合約實例\n const connectButton document.getElementById(\connectButton\);\n connectButton.addEventListener(\click\, async () \u003e {\n // 檢查瀏覽器是否安裝了 MetaMask(或其他注入 web3 的錢包)\n if (typeof window.ethereum ! undefined) {\n try {\n // 請求錢包授權\n await window.ethereum.request({ method: eth_requestAccounts });\n \n // 建立 web3 物件(連到當前 MetaMask 所指向的網路)\n web3 new Web3(window.ethereum);\n\n // 印出當前使用的帳戶\n const accounts await web3.eth.getAccounts();\n console.log(已連線帳戶:, accounts0);\n\n // 建立合約實例\n contractInstance new web3.eth.Contract(contractABI, contractAddress);\n\n alert(\錢包連線成功!\);\n } catch (error) {\n console.error(error);\n alert(\連線錢包失敗,請查看控制台訊息或再次嘗試。\);\n }\n } else {\n alert(請先安裝 MetaMask 或其他以太坊錢包外掛!);\n }\n });\n\n const readMessageButton document.getElementById(\readMessageButton\);\n\n readMessageButton.addEventListener(\click\, async () \u003e {\n if (!contractInstance) {\n alert(\請先按 連線錢包 按鈕,再進行操作。\);\n return;\n }\n try {/* @cat-caption */\n const message await contractInstance.methods.message().call();/* @cat-caption */\n console.log(\合約訊息為:\, message);/* @cat-caption */\n } catch (error) {/* @cat-caption */\n console.error(error);/* @cat-caption */\n alert(\讀取合約訊息失敗!\);/* @cat-caption */\n }/* @cat-caption */\n });\n \u003c/script\u003e\n\u003c/head\u003e\n\n\u003cbody\u003e\n \u003cdiv\u003e\n \u003cbutton id\connectButton\\u003e連線錢包\u003c/button\u003e\n \u003c/div\u003e\n\n \u003cdiv\u003e\n \u003cp\u003e目前合約訊息:\u003cspan id\currentMessage\\u003e---\u003c/span\u003e\u003c/p\u003e\n \u003cbutton id\readMessageButton\\u003e讀取合約訊息\u003c/button\u003e\n \u003c/div\u003e\n\n\u003c/body\u003e\n\n\u003c/html\u003e)/script>script>self.__next_f.push(1,2d:Tb0b,)/script>script>self.__next_f.push(1,\u003c!DOCTYPE html\u003e\n\u003chtml lang\en\\u003e\n\n\u003chead\u003e\n \u003cmeta charset\UTF-8\\u003e\n \u003cmeta name\viewport\ content\widthdevice-width, initial-scale1.0\\u003e\n \u003ctitle\u003eDocument\u003c/title\u003e\n \u003cscript type\module\\u003e\n import Web3 from \web3\;\n import { contractABI } from \./src/abi.js\;\n\n const contractAddress \0xFEA4f2C9B99Df0B2b7da67e60371BE4275C3d749\;\n\n let web3; // 用來存放 Web3 物件實例\n let contractInstance; // 用來存放合約實例\n const connectButton document.getElementById(\connectButton\);\n connectButton.addEventListener(\click\, async () \u003e {\n // 檢查瀏覽器是否安裝了 MetaMask(或其他注入 web3 的錢包)\n if (typeof window.ethereum ! undefined) {\n try {\n // 請求錢包授權\n await window.ethereum.request({ method: eth_requestAccounts });\n \n // 建立 web3 物件(連到當前 MetaMask 所指向的網路)\n web3 new Web3(window.ethereum);\n\n // 印出當前使用的帳戶\n const accounts await web3.eth.getAccounts();\n console.log(已連線帳戶:, accounts0);\n\n // 建立合約實例\n contractInstance new web3.eth.Contract(contractABI, contractAddress);\n\n alert(\錢包連線成功!\);\n } catch (error) {\n console.error(error);\n alert(\連線錢包失敗,請查看控制台訊息或再次嘗試。\);\n }\n } else {\n alert(請先安裝 MetaMask 或其他以太坊錢包外掛!);\n }\n });\n\n const readMessageButton document.getElementById(\readMessageButton\);\n const currentMessageSpan document.getElementById(\currentMessage\);\n\n readMessageButton.addEventListener(\click\, async () \u003e {\n if (!contractInstance) {\n alert(\請先按 連線錢包 按鈕,再進行操作。\);\n return;\n }\n try {\n const message await contractInstance.methods.message().call();\n console.log(\合約訊息為:\, message);\n currentMessageSpan.textContent message;/* @cat-caption */\n } catch (error) {\n console.error(error);\n alert(\讀取合約訊息失敗!\);\n }\n });\n \u003c/script\u003e\n\u003c/head\u003e\n\n\u003cbody\u003e\n \u003cdiv\u003e\n \u003cbutton id\connectButton\\u003e連線錢包\u003c/button\u003e\n \u003c/div\u003e\n\n \u003cdiv\u003e\n \u003cp\u003e目前合約訊息:\u003cspan id\currentMessage\\u003e---\u003c/span\u003e\u003c/p\u003e\n \u003cbutton id\readMessageButton\\u003e讀取合約訊息\u003c/button\u003e\n \u003c/div\u003e\n\n\u003c/body\u003e\n\n\u003c/html\u003e)/script>script>self.__next_f.push(1,2e:Tbec,)/script>script>self.__next_f.push(1,\u003c!DOCTYPE html\u003e\n\u003chtml lang\en\\u003e\n\n\u003chead\u003e\n \u003cmeta charset\UTF-8\\u003e\n \u003cmeta name\viewport\ content\widthdevice-width, initial-scale1.0\\u003e\n \u003ctitle\u003eDocument\u003c/title\u003e\n \u003cscript type\module\\u003e\n import Web3 from \web3\;\n import { contractABI } from \./src/abi.js\;\n\n const contractAddress \0xFEA4f2C9B99Df0B2b7da67e60371BE4275C3d749\;\n\n let web3; // 用來存放 Web3 物件實例\n let contractInstance; // 用來存放合約實例\n const connectButton document.getElementById(\connectButton\);\n connectButton.addEventListener(\click\, async () \u003e {\n // 檢查瀏覽器是否安裝了 MetaMask(或其他注入 web3 的錢包)\n if (typeof window.ethereum ! undefined) {\n try {\n // 請求錢包授權\n await window.ethereum.request({ method: eth_requestAccounts });\n \n // 建立 web3 物件(連到當前 MetaMask 所指向的網路)\n web3 new Web3(window.ethereum);\n\n // 印出當前使用的帳戶\n const accounts await web3.eth.getAccounts();\n console.log(已連線帳戶:, accounts0);\n\n // 建立合約實例\n contractInstance new web3.eth.Contract(contractABI, contractAddress);\n\n alert(\錢包連線成功!\);\n } catch (error) {\n console.error(error);\n alert(\連線錢包失敗,請查看控制台訊息或再次嘗試。\);\n }\n } else {\n alert(請先安裝 MetaMask 或其他以太坊錢包外掛!);\n }\n });\n\n const readMessageButton document.getElementById(\readMessageButton\);\n const currentMessageSpan document.getElementById(\currentMessage\);\n\n readMessageButton.addEventListener(\click\, async () \u003e {\n if (!contractInstance) {\n alert(\請先按 連線錢包 按鈕,再進行操作。\);\n return;\n }\n try {\n const message await contractInstance.methods.message().call();\n console.log(\合約訊息為:\, message);\n currentMessageSpan.textContent message;\n } catch (error) {\n console.error(error);\n alert(\讀取合約訊息失敗!\);\n }\n });\n \u003c/script\u003e\n\u003c/head\u003e\n\n\u003cbody\u003e\n \u003cdiv\u003e\n \u003cbutton id\connectButton\\u003e連線錢包\u003c/button\u003e\n \u003c/div\u003e\n\n \u003cdiv\u003e\n \u003cp\u003e目前合約訊息:\u003cspan id\currentMessage\\u003e---\u003c/span\u003e\u003c/p\u003e\n \u003cbutton id\readMessageButton\\u003e讀取合約訊息\u003c/button\u003e\n \u003c/div\u003e\n\n \u003cdiv\u003e/* @cat-caption */\n \u003cinput type\text\ id\newMessageInput\ placeholder\輸入新的訊息\ /\u003e/* @cat-caption */\n \u003cbutton id\setMessageButton\\u003e寫入合約訊息\u003c/button\u003e/* @cat-caption */\n \u003c/div\u003e/* @cat-caption */\n\u003c/body\u003e\n\n\u003c/html\u003e)/script>script>self.__next_f.push(1,2f:Te31,)/script>script>self.__next_f.push(1,\u003c!DOCTYPE html\u003e\n\u003chtml lang\en\\u003e\n\n\u003chead\u003e\n \u003cmeta charset\UTF-8\\u003e\n \u003cmeta name\viewport\ content\widthdevice-width, initial-scale1.0\\u003e\n \u003ctitle\u003eDocument\u003c/title\u003e\n \u003cscript type\module\\u003e\n import Web3 from \web3\;\n import { contractABI } from \./src/abi.js\;\n\n const contractAddress \0xFEA4f2C9B99Df0B2b7da67e60371BE4275C3d749\;\n\n let web3; // 用來存放 Web3 物件實例\n let contractInstance; // 用來存放合約實例\n const connectButton document.getElementById(\connectButton\);\n connectButton.addEventListener(\click\, async () \u003e {\n // 檢查瀏覽器是否安裝了 MetaMask(或其他注入 web3 的錢包)\n if (typeof window.ethereum ! undefined) {\n try {\n // 請求錢包授權\n await window.ethereum.request({ method: eth_requestAccounts });\n \n // 建立 web3 物件(連到當前 MetaMask 所指向的網路)\n web3 new Web3(window.ethereum);\n\n // 印出當前使用的帳戶\n const accounts await web3.eth.getAccounts();\n console.log(已連線帳戶:, accounts0);\n\n // 建立合約實例\n contractInstance new web3.eth.Contract(contractABI, contractAddress);\n\n alert(\錢包連線成功!\);\n } catch (error) {\n console.error(error);\n alert(\連線錢包失敗,請查看控制台訊息或再次嘗試。\);\n }\n } else {\n alert(請先安裝 MetaMask 或其他以太坊錢包外掛!);\n }\n });\n\n const readMessageButton document.getElementById(\readMessageButton\);\n const currentMessageSpan document.getElementById(\currentMessage\);\n\n readMessageButton.addEventListener(\click\, async () \u003e {\n if (!contractInstance) {\n alert(\請先按 連線錢包 按鈕,再進行操作。\);\n return;\n }\n try {\n const message await contractInstance.methods.message().call();\n console.log(\合約訊息為:\, message);\n currentMessageSpan.textContent message;\n } catch (error) {\n console.error(error);\n alert(\讀取合約訊息失敗!\);\n }\n });\n\n // 5. 寫入合約訊息\n const setMessageButton document.getElementById(\setMessageButton\);/* @cat-caption */\n const newMessageInput document.getElementById(\newMessageInput\);/* @cat-caption */\n\n setMessageButton.addEventListener(\click\, async () \u003e {/* @cat-caption */\n if (!contractInstance) {/* @cat-caption */\n alert(\請先按 連線錢包 按鈕,再進行操作。\);/* @cat-caption */\n return;/* @cat-caption */\n }/* @cat-caption */\n // ... 點擊按鈕並確認連線錢包後要做的事情/* @cat-caption */\n });/* @cat-caption */\n \u003c/script\u003e\n\u003c/head\u003e\n\n\u003cbody\u003e\n \u003cdiv\u003e\n \u003cbutton id\connectButton\\u003e連線錢包\u003c/button\u003e\n \u003c/div\u003e\n\n \u003cdiv\u003e\n \u003cp\u003e目前合約訊息:\u003cspan id\currentMessage\\u003e---\u003c/span\u003e\u003c/p\u003e\n \u003cbutton id\readMessageButton\\u003e讀取合約訊息\u003c/button\u003e\n \u003c/div\u003e\n\n \u003cdiv\u003e\n \u003cinput type\text\ id\newMessageInput\ placeholder\輸入新的訊息\ /\u003e\n \u003cbutton id\setMessageButton\\u003e寫入合約訊息\u003c/button\u003e\n \u003c/div\u003e\n\u003c/body\u003e\n\n\u003c/html\u003e)/script>script>self.__next_f.push(1,30:Te59,)/script>script>self.__next_f.push(1,\u003c!DOCTYPE html\u003e\n\u003chtml lang\en\\u003e\n\n\u003chead\u003e\n \u003cmeta charset\UTF-8\\u003e\n \u003cmeta name\viewport\ content\widthdevice-width, initial-scale1.0\\u003e\n \u003ctitle\u003eDocument\u003c/title\u003e\n \u003cscript type\module\\u003e\n import Web3 from \web3\;\n import { contractABI } from \./src/abi.js\;\n\n const contractAddress \0xFEA4f2C9B99Df0B2b7da67e60371BE4275C3d749\;\n\n let web3; // 用來存放 Web3 物件實例\n let contractInstance; // 用來存放合約實例\n const connectButton document.getElementById(\connectButton\);\n connectButton.addEventListener(\click\, async () \u003e {\n // 檢查瀏覽器是否安裝了 MetaMask(或其他注入 web3 的錢包)\n if (typeof window.ethereum ! undefined) {\n try {\n // 請求錢包授權\n await window.ethereum.request({ method: eth_requestAccounts });\n \n // 建立 web3 物件(連到當前 MetaMask 所指向的網路)\n web3 new Web3(window.ethereum);\n\n // 印出當前使用的帳戶\n const accounts await web3.eth.getAccounts();\n console.log(已連線帳戶:, accounts0);\n\n // 建立合約實例\n contractInstance new web3.eth.Contract(contractABI, contractAddress);\n\n alert(\錢包連線成功!\);\n } catch (error) {\n console.error(error);\n alert(\連線錢包失敗,請查看控制台訊息或再次嘗試。\);\n }\n } else {\n alert(請先安裝 MetaMask 或其他以太坊錢包外掛!);\n }\n });\n\n const readMessageButton document.getElementById(\readMessageButton\);\n const currentMessageSpan document.getElementById(\currentMessage\);\n\n readMessageButton.addEventListener(\click\, async () \u003e {\n if (!contractInstance) {\n alert(\請先按 連線錢包 按鈕,再進行操作。\);\n return;\n }\n try {\n const message await contractInstance.methods.message().call();\n console.log(\合約訊息為:\, message);\n currentMessageSpan.textContent message;\n } catch (error) {\n console.error(error);\n alert(\讀取合約訊息失敗!\);\n }\n });\n\n // 5. 寫入合約訊息\n const setMessageButton document.getElementById(\setMessageButton\);\n const newMessageInput document.getElementById(\newMessageInput\);\n\n setMessageButton.addEventListener(\click\, async () \u003e {\n if (!contractInstance) {\n alert(\請先按 連線錢包 按鈕,再進行操作。\);\n return;\n }\n const newMessage newMessageInput.value;/* @cat-caption */\n if (!newMessage) {/* @cat-caption */\n alert(\請輸入新的訊息再嘗試。\);/* @cat-caption */\n return;/* @cat-caption */\n }/* @cat-caption */\n });\n \u003c/script\u003e\n\u003c/head\u003e\n\n\u003cbody\u003e\n \u003cdiv\u003e\n \u003cbutton id\connectButton\\u003e連線錢包\u003c/button\u003e\n \u003c/div\u003e\n\n \u003cdiv\u003e\n \u003cp\u003e目前合約訊息:\u003cspan id\currentMessage\\u003e---\u003c/span\u003e\u003c/p\u003e\n \u003cbutton id\readMessageButton\\u003e讀取合約訊息\u003c/button\u003e\n \u003c/div\u003e\n\n \u003cdiv\u003e\n \u003cinput type\text\ id\newMessageInput\ placeholder\輸入新的訊息\ /\u003e\n \u003cbutton id\setMessageButton\\u003e寫入合約訊息\u003c/button\u003e\n \u003c/div\u003e\n\u003c/body\u003e\n\n\u003c/html\u003e)/script>script>self.__next_f.push(1,31:Tfad,)/script>script>self.__next_f.push(1,\u003c!DOCTYPE html\u003e\n\u003chtml lang\en\\u003e\n\n\u003chead\u003e\n \u003cmeta charset\UTF-8\\u003e\n \u003cmeta name\viewport\ content\widthdevice-width, initial-scale1.0\\u003e\n \u003ctitle\u003eDocument\u003c/title\u003e\n \u003cscript type\module\\u003e\n import Web3 from \web3\;\n import { contractABI } from \./src/abi.js\;\n\n const contractAddress \0xFEA4f2C9B99Df0B2b7da67e60371BE4275C3d749\;\n\n let web3; // 用來存放 Web3 物件實例\n let contractInstance; // 用來存放合約實例\n const connectButton document.getElementById(\connectButton\);\n connectButton.addEventListener(\click\, async () \u003e {\n // 檢查瀏覽器是否安裝了 MetaMask(或其他注入 web3 的錢包)\n if (typeof window.ethereum ! undefined) {\n try {\n // 請求錢包授權\n await window.ethereum.request({ method: eth_requestAccounts });\n \n // 建立 web3 物件(連到當前 MetaMask 所指向的網路)\n web3 new Web3(window.ethereum);\n\n // 印出當前使用的帳戶\n const accounts await web3.eth.getAccounts();\n console.log(已連線帳戶:, accounts0);\n\n // 建立合約實例\n contractInstance new web3.eth.Contract(contractABI, contractAddress);\n\n alert(\錢包連線成功!\);\n } catch (error) {\n console.error(error);\n alert(\連線錢包失敗,請查看控制台訊息或再次嘗試。\);\n }\n } else {\n alert(請先安裝 MetaMask 或其他以太坊錢包外掛!);\n }\n });\n\n const readMessageButton document.getElementById(\readMessageButton\);\n const currentMessageSpan document.getElementById(\currentMessage\);\n\n readMessageButton.addEventListener(\click\, async () \u003e {\n if (!contractInstance) {\n alert(\請先按 連線錢包 按鈕,再進行操作。\);\n return;\n }\n try {\n const message await contractInstance.methods.message().call();\n console.log(\合約訊息為:\, message);\n currentMessageSpan.textContent message;\n } catch (error) {\n console.error(error);\n alert(\讀取合約訊息失敗!\);\n }\n });\n\n // 5. 寫入合約訊息\n const setMessageButton document.getElementById(\setMessageButton\);\n const newMessageInput document.getElementById(\newMessageInput\);\n\n setMessageButton.addEventListener(\click\, async () \u003e {\n if (!contractInstance) {\n alert(\請先按 連線錢包 按鈕,再進行操作。\);\n return;\n }\n const newMessage newMessageInput.value;\n if (!newMessage) {\n alert(\請輸入新的訊息再嘗試。\);\n return;\n }\n try {\n // 發送交易需要指定哪個帳戶做為 from/* @cat-caption */\n const accounts await web3.eth.getAccounts();/* @cat-caption */\n const usedAccount accounts0/* @cat-caption */\n \n } catch (error) {\n console.error(error);\n alert(\寫入合約訊息失敗,請查看控制台訊息。\);\n }\n });\n \u003c/script\u003e\n\u003c/head\u003e\n\n\u003cbody\u003e\n \u003cdiv\u003e\n \u003cbutton id\connectButton\\u003e連線錢包\u003c/button\u003e\n \u003c/div\u003e\n\n \u003cdiv\u003e\n \u003cp\u003e目前合約訊息:\u003cspan id\currentMessage\\u003e---\u003c/span\u003e\u003c/p\u003e\n \u003cbutton id\readMessageButton\\u003e讀取合約訊息\u003c/button\u003e\n \u003c/div\u003e\n\n \u003cdiv\u003e\n \u003cinput type\text\ id\newMessageInput\ placeholder\輸入新的訊息\ /\u003e\n \u003cbutton id\setMessageButton\\u003e寫入合約訊息\u003c/button\u003e\n \u003c/div\u003e\n\u003c/body\u003e\n\n\u003c/html\u003e)/script>script>self.__next_f.push(1,32:T10fb,)/script>script>self.__next_f.push(1,\u003c!DOCTYPE html\u003e\n\u003chtml lang\en\\u003e\n\n\u003chead\u003e\n \u003cmeta charset\UTF-8\\u003e\n \u003cmeta name\viewport\ content\widthdevice-width, initial-scale1.0\\u003e\n \u003ctitle\u003eDocument\u003c/title\u003e\n \u003cscript type\module\\u003e\n import Web3 from \web3\;\n import { contractABI } from \./src/abi.js\;\n\n const contractAddress \0xFEA4f2C9B99Df0B2b7da67e60371BE4275C3d749\;\n\n let web3; // 用來存放 Web3 物件實例\n let contractInstance; // 用來存放合約實例\n const connectButton document.getElementById(\connectButton\);\n connectButton.addEventListener(\click\, async () \u003e {\n // 檢查瀏覽器是否安裝了 MetaMask(或其他注入 web3 的錢包)\n if (typeof window.ethereum ! undefined) {\n try {\n // 請求錢包授權\n await window.ethereum.request({ method: eth_requestAccounts });\n \n // 建立 web3 物件(連到當前 MetaMask 所指向的網路)\n web3 new Web3(window.ethereum);\n\n // 印出當前使用的帳戶\n const accounts await web3.eth.getAccounts();\n console.log(已連線帳戶:, accounts0);\n\n // 建立合約實例\n contractInstance new web3.eth.Contract(contractABI, contractAddress);\n\n alert(\錢包連線成功!\);\n } catch (error) {\n console.error(error);\n alert(\連線錢包失敗,請查看控制台訊息或再次嘗試。\);\n }\n } else {\n alert(請先安裝 MetaMask 或其他以太坊錢包外掛!);\n }\n });\n\n const readMessageButton document.getElementById(\readMessageButton\);\n const currentMessageSpan document.getElementById(\currentMessage\);\n\n readMessageButton.addEventListener(\click\, async () \u003e {\n if (!contractInstance) {\n alert(\請先按 連線錢包 按鈕,再進行操作。\);\n return;\n }\n try {\n const message await contractInstance.methods.message().call();\n console.log(\合約訊息為:\, message);\n currentMessageSpan.textContent message;\n } catch (error) {\n console.error(error);\n alert(\讀取合約訊息失敗!\);\n }\n });\n\n // 5. 寫入合約訊息\n const setMessageButton document.getElementById(\setMessageButton\);\n const newMessageInput document.getElementById(\newMessageInput\);\n\n setMessageButton.addEventListener(\click\, async () \u003e {\n if (!contractInstance) {\n alert(\請先按 連線錢包 按鈕,再進行操作。\);\n return;\n }\n const newMessage newMessageInput.value;\n if (!newMessage) {\n alert(\請輸入新的訊息再嘗試。\);\n return;\n }\n try {\n // 發送交易需要指定哪個帳戶做為 from\n const accounts await web3.eth.getAccounts();\n const usedAccount accounts0\n const receipt await contractInstance.methods/* @cat-caption */\n .setMessage(newMessage)/* @cat-caption */\n .send({ from: usedAccount });/* @cat-caption */\n\n console.log(\交易回執:\, receipt);/* @cat-caption */\n alert(\寫入訊息成功,請再度點擊 讀取合約訊息 查看更新。\);/* @cat-caption */\n } catch (error) {\n console.error(error);\n alert(\寫入合約訊息失敗,請查看控制台訊息。\);\n }\n });\n \u003c/script\u003e\n\u003c/head\u003e\n\n\u003cbody\u003e\n \u003cdiv\u003e\n \u003cbutton id\connectButton\\u003e連線錢包\u003c/button\u003e\n \u003c/div\u003e\n\n \u003cdiv\u003e\n \u003cp\u003e目前合約訊息:\u003cspan id\currentMessage\\u003e---\u003c/span\u003e\u003c/p\u003e\n \u003cbutton id\readMessageButton\\u003e讀取合約訊息\u003c/button\u003e\n \u003c/div\u003e\n\n \u003cdiv\u003e\n \u003cinput type\text\ id\newMessageInput\ placeholder\輸入新的訊息\ /\u003e\n \u003cbutton id\setMessageButton\\u003e寫入合約訊息\u003c/button\u003e\n \u003c/div\u003e\n\u003c/body\u003e\n\n\u003c/html\u003e)/script>script>self.__next_f.push(1,33:Te4b,)/script>script>self.__next_f.push(1,## 目錄\n\n* 智能合約 Solidity 教學 (上) - Solidity 基礎語法教學(/article/96e61275-7bde-480a-9a61-fca3c5a8657d)\n* 智能合約 Solidity 教學 (中) - MetaMask 與以太坊測試網(/article/e9e78b6b-e7fa-4d70-b892-059ccc057ecf)\n* 智能合約 Solidity 教學 (下) - Web3.js 實際開發一個 DApp 去中心化應用前端(/article/8b0bf0c6-6a00-4764-b686-d0c9c2a4ca28)\n\n**MetaMask 使用教學:從安裝到與智能合約互動**\n\n## 一、MetaMask 錢包\n\n錢包,是 Web3 世界的身分證也是通行證\n基本上所有與 Web3 互動都需要依賴錢包\n\n今天我們將從最多人使用 MetaMask 開始\n\n## **1. 什麼是 MetaMask?**\n\nMetaMask 是一款 **以太坊數位錢包 (Ethereum Wallet)**,可以讓你:\n\n* **管理你的 ETH** 和其他代幣 (ERC-20, ERC-721 NFT)\n* **連接 DApp**,讓瀏覽器與區塊鏈互動\n* **簽署交易**,進行資金轉移或呼叫智能合約\n* **切換測試網**,在開發時使用「假幣」測試智能合約\n\n它既是一個 **Chrome/Firefox 擴充插件**,也有 **行動版 App** 可用。\n\n***\n\n## **2. 安裝 MetaMask**\n\n#### **Step 1:下載並安裝**\n\n1. 打開官方網站 👉 https://metamask.io/(https://metamask.io/)\n2. 點擊「**Download**」,選擇 **Chrome/Firefox** 擴充功能或 **手機版 App**。\n3. 安裝後,你會在 **瀏覽器右上角** 看到 MetaMask 狐狸頭 🦊。\n\n#### **Step 2:建立新錢包**\n\n1. 點擊 **「開始使用」**\n2. **「創建新錢包」**\n3. 設定 **密碼**(這個密碼只用來解鎖你的 MetaMask)\n4. **保存助記詞(Secret Recovery Phrase)**\n * 這是你的「錢包私鑰」,**一定要抄下來並存放在安全地方!**\n * **千萬不要與他人分享,丟失後無法恢復!**\n\n#### **Step 3:完成設定**\n\nMetaMask 會自動連接 **以太坊主網 (Ethereum Mainnet)**,\n但我們在開發時,通常會用 **測試網** 來避免消耗真實 ETH。\n\n***\n\n## **3. 切換測試網 (Goerli / Sepolia / Polygon Testnet)**\n\n以太坊提供 **測試網**(Testnets),允許開發者使用「假幣」進行測試,而不會消耗真實資金。\n目前常見的測試網:\n\n* **Sepolia Testnet**(官方推薦)\n* **Goerli Testnet**(即將淘汰)\n* **Polygon Mumbai Testnet**(Polygon 測試鏈)\n\n#### **如何開啟測試網**\n\n1. 打開 MetaMask,點擊 **右上角的「網路選單」**\n2. 點擊 **「顯示/隱藏測試網路」**\n3. **開啟「顯示測試網路」**\n4. 選擇 **Sepolia Testnet**\n\n現在,你的 MetaMask 已切換到 **測試網**,可以進行開發測試!\n\n***\n\n## **4. 如何領取測試幣 (Faucet)**\n\n測試網上的 ETH **不是實際貨幣**,而是開發者測試時使用的「假幣」,\n我們可以從 **Faucet(水龍頭)** 領取測試 ETH。\n目前有 Alchemy Faucet(https://www.alchemy.com/faucets/ethereum-sepolia) 或 Infura Faucet(https://www.infura.io/faucet) 他們能一次領取較多,但需要綁定帳號\n所以我們從最簡單的 Google Cloud for Web3 來領取\n\n#### **如何領取 Sepolia 測試 ETH**\n\n1. 打開 https://cloud.google.com/application/web3/faucet/ethereum/sepolia(https://cloud.google.com/application/web3/faucet/ethereum/sepolia)\n2. 輸入你的 **錢包地址**(MetaMask 內複製)\n3. 點擊「Request ETH」領取測試幣\n4. **等候 1-2 分鐘**,ETH 會發送到你的測試錢包\n\n💡 **測試幣用途**:\n\n* 部署測試合約\n* 測試智能合約功能\n* 測試 DApp 與合約互動\n\n***\n\n## **5. 如何讓智能合約與 MetaMask 互動**\n\n現在我們要將剛剛的 **HelloWorld.sol** 合約,透過 **MetaMask** 來 **與前端互動**!)/script>script>self.__next_f.push(1,34:T82b,)/script>script>self.__next_f.push(1,{\id\:\3zq7d\,\code\:\// SPDX-License-Identifier: MIT\\npragma solidity ^0.8.0;\\n\\ncontract HelloWorld {\\n // 儲存訊息的變數\\n string public message;\\n\\n // 建構函式,合約部署時會自動執行\\n constructor() {\\n message \\\Hello, Solidity!\\\;\\n }\\n\\n // 設定新的訊息\\n function setMessage(string memory _newMessage) public {\\n message _newMessage;\\n }\\n}\\n\,\caption\:\我們使用上次教學使用的智能合約程式碼來部署\,\language\:\solidity\},{\id\:\umu2dh\,\code\:\\,\caption\:\在 Remix IDE \\n點擊 Deploy \u0026 Run Transactions 面板\\n選擇 WalletConnect\\n\,\language\:\typescript\,\image\:{\url\:\/api/object/1738165535353-6ey1yq.png\,\position\:\center\,\fullWidth\:true,\width\:200}},{\id\:\qbzqao\,\code\:\\,\caption\:\點擊連結錢包\,\language\:\typescript\,\image\:{\url\:\/api/object/1738165578843-25tz78.png\,\position\:\center\,\fullWidth\:true,\width\:200}},{\id\:\zhoxlp\,\code\:\\,\caption\:\選擇我們剛剛創辦好的 MetaMask\,\language\:\typescript\,\image\:{\url\:\/api/object/1738165600940-eck8g.png\,\position\:\center\,\fullWidth\:true,\width\:200}},{\id\:\x20tnh\,\code\:\\,\caption\:\以這個錢包來部署該合約\\n這個合約會被部署到 Sepolia 網路上\\n\,\language\:\typescript\,\image\:{\url\:\/api/object/1738165630405-zqd9cf.png\,\position\:\center\,\fullWidth\:true,\width\:200}},{\id\:\m8uylg4\,\code\:\\,\caption\:\由於這是真的會消耗到你錢包中的錢錢!!!\\n所以會跳出 MetaMask 的提示框\\n\\n本次部署合約將消耗快 6 美元(超貴\\n請注意是使用 Sepolia 來支付,那才是免費的測試幣\,\language\:\typescript\,\image\:{\url\:\/api/object/1738165706994-14yk4t.png\,\position\:\center\,\fullWidth\:true,\width\:200}},{\id\:\ofogaa\,\code\:\\,\caption\:\跟剛剛一樣,部署好以後\\n我們可以打開部署好的合約\\n然後重新 SetMessage\\n一樣要花錢 (0.3 美元)\,\language\:\typescript\,\image\:{\url\:\/api/object/1738165867299-k91i3v.png\,\position\:\center\,\fullWidth\:true,\width\:200}})/script>script>self.__next_f.push(1,35:Tb40,)/script>script>self.__next_f.push(1,{\id\:\9b8q2j\,\code\:\\,\caption\:\開啟 Remix (https://remix.ethereum.org)\,\language\:\plaintext\,\image\:{\url\:\/api/object/1738133312491-3b4tcx.png\,\position\:\center\,\fullWidth\:true,\width\:200}},{\id\:\gyocve\,\code\:\\,\caption\:\創建第一個合約:HelloWorld\\n在 Remix 建立新檔 HelloWorld.sol\,\language\:\plaintext\,\image\:{\url\:\/api/object/1738133531581-cyuacvi.png\,\position\:\center\,\fullWidth\:true,\width\:200}},{\id\:\0omwz8\,\code\:\// SPDX-License-Identifier: MIT\\npragma solidity ^0.8.0;\\n\\ncontract HelloWorld {\\n // 儲存訊息的變數\\n string public message;\\n\\n // 建構函式,合約部署時會自動執行\\n constructor() {\\n message \\\Hello, Solidity!\\\;\\n }\\n\\n // 設定新的訊息\\n function setMessage(string memory _newMessage) public {\\n message _newMessage;\\n }\\n}\\n\,\caption\:\貼上我們的第一個範本,接下來我們將逐行介紹各個概念\,\language\:\solidity\},{\id\:\dpb6j\,\code\:\// SPDX-License-Identifier: MIT\\npragma solidity ^0.8.0;/* @cat-caption */\\n\,\caption\:\選擇程式語言的版本\\n因為 Solidity 會不斷更新,新舊程式可能不通用,所以需要指定版本\\n\\n- 指定使用 **Solidity 0.8.0** 以上版本。\\n- `^0.8.0` 代表兼容 0.8.x 的版本。\,\language\:\solidity\},{\id\:\1bg8e8\,\code\:\// SPDX-License-Identifier: MIT\\npragma solidity ^0.8.0;\\n\\ncontract HelloWorld {/* @cat-caption */\\n\\n}/* @cat-caption */\\n\,\caption\:\定義一個名為 HelloWorld 的智能合約\\n\\n- 使用 `contract` 關鍵字定義一個 **智能合約**。\\n- 所有函式與變數都放在 `{}` 內。\,\language\:\solidity\},{\id\:\gjcul\,\code\:\// SPDX-License-Identifier: MIT\\npragma solidity ^0.8.0;\\n\\ncontract HelloWorld {\\n // 儲存訊息的變數/* @cat-caption */\\n string public message;/* @cat-caption */\\n\\n // 建構函式,合約部署時會自動執行/* @cat-caption */\\n constructor() {/* @cat-caption */\\n message \\\Hello, Solidity!\\\;/* @cat-caption */\\n }/* @cat-caption */\\n}\\n\,\caption\:\建構函式,這是一種在合約部署時會自動執行一次的特殊函式。\\n\\n我們讓他在合約部署的時候,將 message 變數設定成 \\\Hello, Solidity\\\\,\language\:\solidity\},{\id\:\np725\,\code\:\// SPDX-License-Identifier: MIT\\npragma solidity ^0.8.0;\\n\\ncontract HelloWorld {\\n // 儲存訊息的變數\\n string public message;\\n\\n // 建構函式,合約部署時會自動執行\\n constructor() {\\n message \\\Hello, Solidity!\\\;\\n }\\n\\n // 設定新的訊息/* @cat-caption */\\n function setMessage(string memory _newMessage) public {/* @cat-caption */\\n message _newMessage;/* @cat-caption */\\n }/* @cat-caption */\\n}\\n\,\caption\:\定義一個公開函式,讓使用者呼叫來更新 message。\,\language\:\solidity\})/script>script>self.__next_f.push(1,36:T9b8,)/script>script>self.__next_f.push(1,{\id\:\paysf7\,\code\:\\,\caption\:\點擊 Remix 右側面板 `Solidity Compiler`\,\language\:\markdown\,\image\:{\url\:\/api/object/1738138940269-t39cz.png\,\position\:\center\,\fullWidth\:true,\width\:200}},{\id\:\t8cdwk\,\code\:\\,\caption\:\- 選擇正確版本 (0.8.x)\\n- 按下 **`Compile HelloWorld.sol`** 按鈕\,\language\:\markdown\,\image\:{\url\:\/api/object/1738139007049-3zgityr.png\,\position\:\center\,\fullWidth\:true,\width\:200}},{\id\:\cazn9i\,\code\:\\,\caption\:\沒有錯誤就會顯示綠色打勾\,\language\:\markdown\,\image\:{\url\:\/api/object/1738139024138-7f7po.png\,\position\:\center\,\fullWidth\:true,\width\:200}},{\id\:\jz35j\,\code\:\\,\caption\:\轉到 Deploy \u0026 Run Transactions 面板\,\language\:\markdown\,\image\:{\url\:\/api/object/1738139383399-jh9lsw.png\,\position\:\center\,\fullWidth\:true,\width\:200}},{\id\:\73ikd\,\code\:\\,\caption\:\- Environment 記得選擇 Remix VM (Cancun)\\n他是專門給你測試用的區塊鏈\\n- Contract 記得別部署錯\\n- 最後點擊 Deploy \\n\\n\,\language\:\markdown\,\image\:{\url\:\/api/object/1738139402367-fo2xac.png\,\position\:\center\,\fullWidth\:true,\width\:200}},{\id\:\xp7n8\,\code\:\\,\caption\:\下方出現 `Deployed Contracts` 就表示部署成功\\n另外你也會發現上頭的 Account 默默的被扣了點以太幣\\n\\n部署合約是會花真金白銀的,不過由於目前是測試環境,所以扣的也只是可無限使用的測試用以太幣\\n\\n另外部署合約,這份合約將永遠的存在於區塊鏈上頭\\n無法撤銷、無法移除,請務必注意安全性。\,\language\:\markdown\,\image\:{\url\:\/api/object/1738139555578-hucog2.png\,\position\:\center\,\fullWidth\:true,\width\:200}},{\id\:\bt7lh\,\code\:\\,\caption\:\- 展開 `HelloWorld` 合約介面\\n- 按 `message` 便可以查看此時此刻區塊鏈上 message 的數值是多少\\n\\n(查看數值是免費的\,\language\:\markdown\,\image\:{\url\:\/api/object/1738139846600-3j0oms.png\,\position\:\center\,\fullWidth\:true,\width\:200}},{\id\:\q2egom\,\code\:\\,\caption\:\在 setMessage 文字框輸入新訊息,如 \\\貓貓真可愛\\\\\n再來點擊 SetMessage 的按鈕,便可以觸發合約中的該函數\\n可以重新再點 message 一次,你會發現區塊鏈中的 message 變數也發生了更新\\n\\n(修改區塊鏈上頭的數值是需要花錢的\,\language\:\markdown\,\image\:{\url\:\/api/object/1738139930036-x1g28.png\,\position\:\center\,\fullWidth\:true,\width\:200}})/script>script>self.__next_f.push(1,37:T725,)/script>script>self.__next_f.push(1,{\id\:\xyikai\,\code\:\bool public myBool true;/* @cat-caption */\\n\,\caption\:\我們來介紹一下各種 Solidity 常用的型別\\n先從布林(bool) 開始\\n布林型別只允許存 `true` 或 `false` 兩種型別\\n\,\language\:\solidity\},{\id\:\qguxe\,\code\:\uint256 public myUint 123;/* @cat-caption */\\nint public myInt -123;/* @cat-caption */\\n\,\caption\:\**整數 (int, uint)**\\n\\n - `int`:可以為正或負\\n - `uint`:只能是正整數 (0, 1, 2, ...)\\n\\n - 常見長度:`int8`, `int16`, ..., `int256` (以 8 為增量),`int` 等同 `int256`\\n\\n - 同理 `uint8`, `uint16`, ..., `uint256`\,\language\:\solidity\},{\id\:\dvs4jq\,\code\:\address public myAddress 0x1234567890123456789012345678901234567890;\\nmyAddress.balance // 取得該錢包目前剩餘的存款\,\caption\:\地址 (address)\\n用於存放以太坊地址(錢包或合約地址)\,\language\:\solidity\},{\id\:\f3368q\,\code\:\string public myString \\\Hello\\\;\\n\\nmyString.length; //error\\nmyString0; //error\,\caption\:\字串 (string)\\n就是一段文字\\n\\n不過不像其他程式語言,在 Solidity 中,字串並沒有 length 屬性,也不能用 index 的方式取得字元\,\language\:\solidity\},{\id\:\r3le84\,\code\:\uint public numbers; // 動態大小的陣列\\nuint5 public fixedSizeNumbers; // 大小固定為 5 的陣列\\n\\nnumbers.push(123);\,\caption\:\陣列\,\language\:\solidity\},{\id\:\qsi54o\,\code\:\struct Person {\\n string name;\\n uint age;\\n}\\n\\nPerson public alice Person(\\\Alice\\\, 30);\\n\,\caption\:\struct\\n可以自定義一個資料結構\,\language\:\solidity\},{\id\:\fpjfng\,\code\:\enum Status { Pending, Shipped, Completed, Rejected }\\n\\nStatus public currentStatus;\\n\,\caption\:\enum (列舉)\\n- 用於定義一組狀態,方便管理\,\language\:\solidity\})/script>script>self.__next_f.push(1,38:T1a8f,)/script>script>self.__next_f.push(1,## **六**、**變數類型:Local、State、Global**\n\n在 Solidity 中的變數根據其宣告位置可以分為 **三大類**:\n\n1. **Local 變數(函式內部變數)**\n2. **State 變數(合約狀態變數)**\n3. **Global 變數(區塊鏈資訊變數)**\n\n***\n\n### **1. Local 變數**\n\n**定義:**\n\n* **宣告在函式內部**\n* **僅存於函式執行期間**(不會永久儲存到區塊鏈)\n* **默認存於記憶體(memory)**\n* **不會影響智能合約的狀態,因此不會產生 Gas 費用**\n\n***\n\n### **2. State 變數**\n\n**定義:**\n\n* **宣告在函式之外,屬於整個合約**\n* **儲存在區塊鏈上(storage)**\n* **修改 State 變數時,會消耗 Gas**(因為改變了區塊鏈狀態)\n\n### **3. Global 變數**\n\n**定義:**\n\n* **提供區塊鏈上的資訊**\n* **不需要手動宣告,直接可用**\n* **儲存方式根據類型不同,有些來自 storage,有些只是暫時變數**\n\n**常見 Global 變數:**\n\n`msg.sender`呼叫合約的發送者地址\n`msg.value`交易發送的 ETH 數量\n`block.timestamp`當前區塊的時間戳\n`block.number`當前區塊號\n`gasleft()`剩餘 Gas 數量\n`tx.origin`原始交易發起者\n`address(this).balance`合約帳戶的 ETH 餘額\n\n***\n\n## **七**、**Data Locations:memory、storage、calldata**\n\n除了變數的類型(local、state、global),Solidity 也提供了 **三種資料儲存方式**\n適用於陣列 (`array`)、結構 (`struct`)、字串 (`string`)、映射 (`mapping`) 等**參考型別 (Reference Types)**。\n\n`storage`表示這個資料**永遠**儲存於區塊鏈上頭,\\*\\*改變會消耗 Gas,\\*\\*剛剛提到的 State 變數就屬於這一類\n\n`memory`表示這個資料只是短暫出現在記憶體上,\\*\\*交易結束後消失,\\*\\*剛剛提到的 Local 變數就屬於這一類\n\n`calldata`只讀記憶體(交易輸入)**外部函式參數專用**,不允許修改`external` 函式的輸入參數\n\n## 八、瓦斯費,什麼是 Gas?\n\n眼尖的同學應該發現,如果你修改了區塊鏈的資料是要花錢的!\n不僅如此,在以太坊上,每當你執行一筆交易(例如呼叫智能合約、轉帳等),都需要向網路支付一筆 **交易手續費**。\n這筆費用的計算方式並不是固定的,基本上對區塊鏈做越多操作會越貴。\n而以太坊使用一種叫做 **Gas** 的單位來衡量交易在網路上執行所需的運算資源。\n\n* 付款時使用的是 **以太幣 (ETH)**,當然1以太幣快 3000 美元,單位太大了,所以瓦斯費習慣以以 **Gwei**(10^-9 ETH)為單位計價。\n* 哪怕是相同的操作,在不同時間段的瓦斯費都不同,在尖峰時刻的瓦斯費會比較昂貴。\n\n### 計算交易手續費 (以下僅供參考)\n\n1. **Gas Used**:某次交易實際執行用掉多少 Gas\n2. **Gas Price**:每單位 Gas 的價格,通常以 **Gwei** 為單位\n3. **交易手續費 (Tx Fee)** Gas Used × Gas Price\n\n舉例:\n\n* 你的交易執行消耗了 **50,000** Gas\n* 當前 Gas Price **20 Gwei** 20 × 10−9^{-9} ETH\n* 交易費用 50,000 × (20 × 10^-9) ETH 0.001 ETH\n\n***\n\n### 哪些操作會消耗 Gas?\n\n在智能合約中,**大多數操作都會消耗 Gas**,尤其是對區塊鏈「狀態 (state)」造成變化的操作。\n下面列出一些常見「高 Gas 消耗」的動作:\n\n1. **寫入/修改區塊鏈上儲存 (storage)**\n * **最昂貴** 的操作之一就是對合約的 `storage` 做 **寫入** 或 **更新**。\n * 例如:改變一個 state variable。\n * 原因:永久儲存到區塊鏈資料庫,需要網路中所有節點同步保存。\n2. **新增資料到 mapping 或 array**\n * 與一般的 `storage` 更新類似,只要有寫入都會消耗較多 Gas。\n * 動態陣列的 `push()` 也會往合約的永續狀態中寫入新元素。\n3. **刪除資料 (釋放 storage)**\n * 相較寫入,可以回收部分 Gas(因為釋放空間會有 Gas Refund),但一般來說整體仍需付出操作成本。\n4. **合約部署**\n * 部署(Deploy)是一項非常昂貴的操作,因為需要將整個智能合約程式碼上傳到區塊鏈。\n * 智能合約一旦部署,就成為鏈上的永久程式碼。\n5. **呼叫其他合約**\n * 在合約中透過 `call`、`delegatecall` 或 `interface` 呼叫其他合約函式,需要額外的 Gas 來執行被呼叫的邏輯。\n6. **觸發事件 (Event)**\n * 在合約中 `emit Event` 會產生一筆鏈上日誌 (Log),也會消耗 Gas。(一般比寫 `storage` 便宜,但還是要考量頻繁記錄事件帶來的額外開銷)\n7. **迴圈操作 (for/while loop)**\n * 每次迭代都需要額外的計算成本。\n * 如果迴圈裡包含寫入 `storage`、呼叫其他合約、更複雜的邏輯,Gas 量會隨迴圈次數成倍增加。\n\n### 哪些操作相對不會「直接」消耗 Gas?\n\n1. **只讀取狀態變數 (view function)**\n * 用錢包或前端呼叫 `view` 或 `pure` 函式,不會執行交易,也不會在鏈上留下任何記錄,因此不需支付額外 Gas。\n * 但若在合約**內部**使用 `view` 或 `pure` 函式,仍然要計入該交易整體的執行成本——只是相對來說,只有計算的成本,但沒有對 `storage` 寫入的成本。\n2. **在函式內做記憶體層面的運算 (memory)**\n * 在 `memory` 或 `calldata` 中的操作只會產生交易執行中計算的 Gas,通常比修改 `storage` 要便宜許多,因為它不需要永久寫入區塊鏈。\n\n\u003e **注意**: 雖然「讀取數據」或「單純做計算」比寫入便宜很多,但「執行交易」本身仍有基礎費用,因為必須產生交易並在鏈上被礦工/驗證者驗證。\n\n***\n\n### 如何節省 Gas?\n\n1. **減少對 `storage` 的寫入**\n * 能在 `memory`、`calldata` 中處理就盡量不要寫進 `storage`。\n * 例如先在 `memory` 中計算好,再一次性寫回 `storage`,或只在需要時更新 `storage`。\n2. **考慮資料結構選擇**\n * `mapping` 在查詢方面通常比 `array` 高效(尤其針對索引操作);\n * 但要根據具體邏輯權衡。\n3. **避免在迴圈中使用寫入或呼叫**\n * 如果可以把大量資料處理切分成多筆交易,或使用批量更新/分段的方式,讓單次交易的迴圈不至於過大。\n4. **事件 (Event) 的使用**\n * 雖然事件會消耗 Gas,但通常比 `storage` 寫入便宜且可以提供鏈上日誌。\n * 取代在鏈上保存大型文字資料,而是用事件紀錄,日後可從鏈下檢索到。\n5. **版本與編譯器優化**\n * 使用新版本 Solidity 通常會有更多優化(如 0.8.x),\n * 在 Remix 或 Hardhat 中開啟編譯器優化 `optimizer`(如設置 `runs 200` 或其他數值)。\n\n***\n\n## **九**、控制流程)/script>script>self.__next_f.push(1,39:T4f2,)/script>script>self.__next_f.push(1,{\id\:\1chadh\,\code\:\// SPDX-License-Identifier: MIT\\npragma solidity ^0.8.0;\\n\\ncontract HelloWorld {\\n string public result \\\\\\;\\n\\n constructor() {\\n int256 val 1;\\n if (val 0) {\\n result \\\Value is zero\\\;\\n } else if (val 1) {\\n result \\\Value is one\\\;\\n } else {\\n result \\\Value is something else\\\;\\n }\\n }\\n}\\n\,\caption\:\if / else if / else\,\language\:\solidity\},{\id\:\54o6me\,\code\:\// SPDX-License-Identifier: MIT\\npragma solidity ^0.8.0;\\n\\ncontract HelloWorld {\\n uint public sum 0;\\n\\n constructor() {\\n uint max 100;\\n for (uint i 0; i \u003c max; i++) {\\n sum + i;\\n }\\n }\\n}\\n\,\caption\:\for 迴圈\,\language\:\solidity\},{\id\:\iu751b\,\code\:\// SPDX-License-Identifier: MIT\\npragma solidity ^0.8.0;\\n\\ncontract HelloWorld {\\n uint public sum 0;\\n\\n constructor() {\\n uint max 100;\\n uint i 0;/* @cat-caption */\\n while (i \u003c max) {/* @cat-caption */\\n sum + i;/* @cat-caption */\\n i ++;/* @cat-caption */\\n }/* @cat-caption */\\n }\\n}\\n\,\caption\:\while 迴圈\\n範例程式碼的功能與上頭完全相同\,\language\:\solidity\})/script>script>self.__next_f.push(1,3a:Tf83,)/script>script>self.__next_f.push(1,{\id\:\bz3sd\,\code\:\// SPDX-License-Identifier: MIT\\npragma solidity ^0.8.0;\\n\\ncontract HelloWorld {\\n uint public count;\\n\\n // 這是一個 public 函式/* @cat-caption */\\n function increment() public {/* @cat-caption */\\n count + 1;/* @cat-caption */\\n }/* @cat-caption */\\n}\\n\,\caption\:\`public` 函式\\n所有人(包括外部帳戶和其他合約)都可以呼叫該函式。\\n\,\language\:\solidity\},{\id\:\i4uj3\,\code\:\// SPDX-License-Identifier: MIT\\npragma solidity ^0.8.0;\\n\\ncontract HelloWorld {\\n uint public count;\\n\\n // 外部無法直接訪問該變數/* @cat-caption */\\n // 請注意!該資料仍然公開於區塊鏈上頭,仍然可以爬取到該變數/* @cat-caption */\\n uint private innerCount;/* @cat-caption */\\n\\n // 這是一個 public 函式\\n function increment() public {\\n _increment(); // ✅ 合約內部可存取\\n }\\n\\n // 這是一個 private 函式,習慣命名上最前面加上 _ 來表示這是 private\\n function _increment() private {\\n count + 1;\\n }\\n}\\n\,\caption\:\`private` 函式\\n只能在合約內部使用\\n外部或者其他合約無法訪問\\n\\n* 變數也可以設定成 external\,\language\:\solidity\},{\id\:\qke0o\,\code\:\// SPDX-License-Identifier: MIT\\npragma solidity ^0.8.0;\\n\\ncontract HelloWorld {\\n uint public count;\\n\\n // 這是一個 public 函式\\n function increment() public {\\n this.externalIncrement(); // 內部調用必須透過 `this`\\n }\\n\\n // 這是一個 external 函式\\n function externalIncrement() external {/* @cat-caption */\\n count + 1;\\n }\\n}\\n\,\caption\:\`external` 函式\\n只能從合約外部存取,內部不可直接存取\\n\\n* 不過還是可以間接地利用 `this.函式名()` 來存取\\n* 不適用於變數:變數無法標記為 external。\,\language\:\solidity\},{\id\:\lj7zt\,\code\:\\,\caption\:\上面都是偏向描述權限\\n接下來則是針對功能\,\language\:\solidity\},{\id\:\8gt5t\,\code\:\// SPDX-License-Identifier: MIT\\npragma solidity ^0.8.0;\\n\\ncontract ViewExample {\\n uint public number 42; // 狀態變數\\n\\n // 這個函式只是讀取狀態變數,並沒有改變它\\n function getNumber() public view returns (uint) {/* @cat-caption */\\n return number;\\n }\\n}\\n\,\caption\:\`view` 函式\\nview 函式不會修改區塊鏈上的狀態\\n\\n- 不能修改合約內的狀態變數\\n- 不能發送以太幣(ETH)\\n- 不能調用其他會修改狀態的函式\,\language\:\solidity\},{\id\:\r7rzp7\,\code\:\// SPDX-License-Identifier: MIT\\npragma solidity ^0.8.0;\\n\\ncontract PureExample {\\n // 這個函式純粹進行計算,沒有存取合約內的狀態變數\\n function add(uint a, uint b) public pure returns (uint) {/* @cat-caption */\\n return a + b;\\n }\\n}\\n\,\caption\:\`pure` 函式\\npure 純計算,不讀取也不修改狀態\\n\\n- pure 函式不能讀取也不能修改區塊鏈上的狀態變數。\\n- 只能使用函式內的變數或傳入的參數進行運算。\\n- 適合用來做純計算,例如數學運算。\,\language\:\solidity\},{\id\:\jux6as\,\code\:\// SPDX-License-Identifier: MIT\\npragma solidity ^0.8.0;\\n\\ncontract PayableExample {\\n uint public balance;\\n\\n // 這個函式允許用戶發送 ETH 到合約\\n function deposit() public payable {/* @cat-caption */\\n balance + msg.value; // msg.value 是發送的 ETH 數量/* @cat-caption */\\n }\\n\\n // 這個函式回傳合約內的 ETH 餘額\\n function getBalance() public view returns (uint) {\\n return address(this).balance;\\n }\\n}\\n\,\caption\:\`payable` 函式\\npayable 函式允許該函式接收 ETH\\n\\n- 沒有 payable 的函式無法接收 ETH,如果用戶試圖發送 ETH 會報錯。\\n- 有 payable 的函式可以讓合約接收 ETH,常用於:\\n - 付款交易\\n - 捐款功能\\n - NFT 或其他商品購買\\n\,\language\:\solidity\})/script>script>self.__next_f.push(1,3b:T4c54,)/script>script>self.__next_f.push(1,{\id\:\6maicl\,\code\:\\,\caption\:\講者介紹\,\language\:\typescript\,\image\:{\url\:\/api/object/1722648403224-ls5wu.png\,\position\:\center\,\fullWidth\:true,\width\:200}},{\id\:\4wxmgo\,\code\:\\,\caption\:\如何開發一個\\n多人即時共編筆記\,\language\:\typescript\,\image\:{\url\:\/api/object/1722648488757-cy100fp.png\,\position\:\center\,\fullWidth\:true,\width\:200}},{\id\:\50aza\,\code\:\\,\caption\:\一些能夠多人協作的平台\\nex. Google 文件、HackMd、Notion\,\language\:\typescript\,\image\:{\url\:\/api/object/1722648542302-jysgw4.png\,\position\:\center\,\fullWidth\:true,\width\:200}},{\id\:\eb9e3\,\code\:\\,\caption\:\版本控制\,\language\:\typescript\,\image\:{\url\:\/api/object/1722648612380-75tnf.png\,\position\:\center\,\fullWidth\:true,\width\:200}},{\id\:\9w7h2n\,\code\:\\,\caption\:\如果今天只有一個 branch\\n如果有任何對文件的修改,都可以創建一個 comment\,\language\:\typescript\,\image\:{\url\:\/api/object/1722648638582-zjc5l9.png\,\position\:\center\,\fullWidth\:true,\width\:200}},{\id\:\syq16t\,\code\:\\,\caption\:\每個 commit 都相當於是一個能夠隨時回退的版本\,\language\:\typescript\,\image\:{\url\:\/api/object/1722648766296-bbqtso.png\,\position\:\center\,\fullWidth\:true,\width\:200}},{\id\:\24qfog\,\code\:\\,\caption\:\透過 commit 你也能看到一個專案的發展史\,\language\:\typescript\,\image\:{\url\:\/api/object/1722648865952-uyawnl.png\,\position\:\center\,\fullWidth\:true,\width\:200}},{\id\:\3cf587\,\code\:\\,\caption\:\或者你同事的偷懶史\,\language\:\typescript\,\image\:{\url\:\/api/object/1722648936837-psx1hl.png\,\position\:\center\,\fullWidth\:true,\width\:200}},{\id\:\6171ii\,\code\:\\,\caption\:\多人開發\\n\,\language\:\typescript\,\image\:{\url\:\/api/object/1722649047496-9965ld.png\,\position\:\center\,\fullWidth\:true,\width\:200}},{\id\:\me3hrf\,\code\:\\,\caption\:\將大家辛苦工作的結果合併回去\,\language\:\typescript\,\image\:{\url\:\/api/object/1722649052251-rhzei.png\,\position\:\center\,\fullWidth\:true,\width\:200}},{\id\:\53odlb\,\code\:\\,\caption\:\那當然,很常就會發生衝突 conflict\,\language\:\typescript\,\image\:{\url\:\/api/object/1722649123432-ae33e7.png\,\position\:\center\,\fullWidth\:true,\width\:200}},{\id\:\oljkvc\,\code\:\\,\caption\:\偶爾也有那種感覺休假半年才來上班的同事\\n將半年前的 branch 進行合併的魔幻操作\,\language\:\typescript\,\image\:{\url\:\/api/object/1722649208535-lpyxmb.png\,\position\:\center\,\fullWidth\:true,\width\:200}},{\id\:\wllwvd\,\code\:\\,\caption\:\OT 與 CRDT\,\language\:\typescript\,\image\:{\url\:\/api/object/1722649239157-woa15.png\,\position\:\center\,\fullWidth\:true,\width\:200}},{\id\:\ivw4hs\,\code\:\\,\caption\:\來用一個簡單的例子講解\\nOT 的概念\\n假設有兩個人同時在編輯一串文字\,\language\:\typescript\,\image\:{\url\:\/api/object/1722650376584-hk3yt.png\,\position\:\center\,\fullWidth\:true,\width\:200}},{\id\:\kgv9r6\,\code\:\\,\caption\:\OT 的做法就是同步操作\\n也就是將用戶的操作廣播出去\,\language\:\typescript\,\image\:{\url\:\/api/object/1722650397381-anhrvf.png\,\position\:\center\,\fullWidth\:true,\width\:200}},{\id\:\b28r0p\,\code\:\\,\caption\:\當然,因為網路延遲\\n這個傳播會有時間差\,\language\:\typescript\,\image\:{\url\:\/api/object/1722650422663-hz9nud.png\,\position\:\center\,\fullWidth\:true,\width\:200}},{\id\:\dt1e2v\,\code\:\\,\caption\:\當其他人收到這個廣播時\\n便會自動去套用這個操作\\n好讓兩個人的畫面一致\,\language\:\typescript\,\image\:{\url\:\/api/object/1722650437802-0g3xs.png\,\position\:\center\,\fullWidth\:true,\width\:200}},{\id\:\au6u6g\,\code\:\\,\caption\:\但換個會衝突的例子\,\language\:\typescript\,\image\:{\url\:\/api/object/1722650484027-rx7ll4.png\,\position\:\center\,\fullWidth\:true,\width\:200}},{\id\:\x6ull4\,\code\:\\,\caption\:\但兩個人的操作非常接近\,\language\:\typescript\,\image\:{\url\:\/api/object/1722650493408-wss6m7.png\,\position\:\center\,\fullWidth\:true,\width\:200}},{\id\:\ymth7h\,\code\:\\,\caption\:\這時因為網路延遲的關西\\n\,\language\:\typescript\,\image\:{\url\:\/api/object/1722650510171-lscmwe.png\,\position\:\center\,\fullWidth\:true,\width\:200}},{\id\:\9157r\,\code\:\\,\caption\:\會導致兩邊的數據不一致\,\language\:\typescript\,\image\:{\url\:\/api/object/1722650533178-jsxl7d.png\,\position\:\center\,\fullWidth\:true,\width\:200}},{\id\:\7at8gc\,\code\:\\,\caption\:\所以我們會在廣播時附上時間戳\,\language\:\typescript\,\image\:{\url\:\/api/object/1722650593088-8ecwv7.png\,\position\:\center\,\fullWidth\:true,\width\:200}},{\id\:\mex7r\,\code\:\\,\caption\:\這邊就要來講到 OT 當中的 T\\nTransformation\\n透過時間戳的關西,轉換操作\\n使兩者最後結果一致\,\language\:\typescript\,\image\:{\url\:\/api/object/1722650602402-kqi2f.png\,\position\:\center\,\fullWidth\:true,\width\:200}},{\id\:\22e0hl\,\code\:\\,\caption\:\再來講講 CRDT\\n與 OT 不同,CRTD 主要是關注於資料上\,\language\:\typescript\,\image\:{\url\:\/api/object/1722650623458-41057a.png\,\position\:\center\,\fullWidth\:true,\width\:200}},{\id\:\m80nt\,\code\:\\,\caption\:\他會\,\language\:\typescript\,\image\:{\url\:\/api/object/1722650639515-pgrpdg.png\,\position\:\center\,\fullWidth\:true,\width\:200}},{\id\:\eebg8m\,\code\:\\,\caption\:\CRDT\,\language\:\typescript\,\image\:{\url\:\/api/object/1722650652537-q724o.png\,\position\:\center\,\fullWidth\:true,\width\:200}},{\id\:\oxclne\,\code\:\\,\caption\:\CRDT\,\language\:\typescript\,\image\:{\url\:\/api/object/1722650675472-os0bqi.png\,\position\:\center\,\fullWidth\:true,\width\:200}},{\id\:\qmx9ic\,\code\:\\,\caption\:\CRDT\,\language\:\typescript\,\image\:{\url\:\/api/object/1722650690332-pzbjpe.png\,\position\:\center\,\fullWidth\:true,\width\:200}},{\id\:\qv7kg\,\code\:\\,\caption\:\CRDT\,\language\:\typescript\,\image\:{\url\:\/api/object/1722650745121-7faygb.png\,\position\:\center\,\fullWidth\:true,\width\:200}},{\id\:\2jt88d\,\code\:\\,\caption\:\CRDT\,\language\:\typescript\,\image\:{\url\:\/api/object/1722650749678-ugoq7f.png\,\position\:\center\,\fullWidth\:true,\width\:200}},{\id\:\em9hif\,\code\:\\,\caption\:\CRDT 選擇理由\,\language\:\typescript\,\image\:{\url\:\/api/object/1722650766556-sst5p.png\,\position\:\center\,\fullWidth\:true,\width\:200}},{\id\:\qc8w4e\,\code\:\\,\caption\:\\,\language\:\typescript\,\image\:{\url\:\/api/object/1722650787048-m5uwpm.png\,\position\:\center\,\fullWidth\:true,\width\:200}},{\id\:\n147mb\,\code\:\\,\caption\:\Demo\,\language\:\typescript\,\image\:{\url\:\/api/object/1722650803554-nf4y8m.png\,\position\:\center\,\fullWidth\:true,\width\:200}},{\id\:\ny2r2m\,\code\:\npm init -y\\r\\nnpm install ws y-websocket yjs\,\caption\:\首先,設定 Node.js 伺服器,安裝所需的套件:\,\language\:\plaintext\},{\id\:\t93gmi\,\code\:\\,\caption\:\創建一個 server.js 文件\,\language\:\plaintext\},{\id\:\f6dsap\,\code\:\import * as Y from \\\yjs\\\;/* @cat-caption */\,\caption\:\先引入 yjs\,\language\:\javascript\},{\id\:\8q00u\,\code\:\import * as Y from \\\yjs\\\;\\r\\n\\r\\nconst doc new Y.Doc(); /* @cat-caption */\\r\\n\,\caption\:\創建一個 Doc 物件\\n這個是 yjs 的核心資料結構之一\\n他會自己追蹤其他用戶的更改、自己同步資料\,\language\:\javascript\},{\id\:\rvmflf\,\code\:\import * as Y from \\\yjs\\\;\\r\\nimport { WebsocketProvider } from \\\y-websocket\\\;/* @cat-caption */\\r\\nimport ws from \\\ws\\\;/* @cat-caption */\\r\\n\\r\\nconst doc new Y.Doc();\\r\\nconst wsProvider new WebsocketProvider(/* @cat-caption */\\r\\n \\\ws://localhost:1234\\\,/* @cat-caption */\\r\\n \\\my-roomname\\\,/* @cat-caption */\\r\\n doc,/* @cat-caption */\\r\\n { WebSocketPolyfill: ws }/* @cat-caption */\\r\\n);/* @cat-caption */\,\caption\:\我們可以在透過 y-websocket\\n讓 Doc 透過 websocket 取得來自其他客戶的更新資訊\,\language\:\javascript\},{\id\:\x6dw6g\,\code\:\npx create-react-app frontend\\r\\ncd frontend\\r\\nnpm install yjs y-websocket\,\caption\:\再來是前端的部分\\n直接創建一個新的 react 專案\,\language\:\plaintext\},{\id\:\9053xs\,\code\:\import React from \\\react\\\;\\r\\n\\r\\nfunction App() {\\r\\n return (\\r\\n \u003cdiv className\\\App\\\\u003e\\r\\n\\r\\n \u003c/div\u003e\\r\\n );\\r\\n}\\r\\n\\r\\nexport default App;\\r\\n\,\caption\:\在 App.js 裏頭\,\language\:\javascript\},{\id\:\wtthf\,\code\:\import React, { useEffect } from \\\react\\\;/* @cat-caption */\\r\\nimport * as Y from \\\yjs\\\;/* @cat-caption */\\r\\nimport { WebsocketProvider } from \\\y-websocket\\\;/* @cat-caption */\\r\\n\\r\\nfunction App() {\\r\\n useEffect(() \u003e {/* @cat-caption */\\r\\n const ydoc new Y.Doc();/* @cat-caption */\\r\\n const provider new WebsocketProvider(/* @cat-caption */\\r\\n \\\ws://localhost:1234\\\,/* @cat-caption */\\r\\n \\\my-roomname\\\,/* @cat-caption */\\r\\n ydoc/* @cat-caption */\\r\\n );/* @cat-caption */\\r\\n\\r\\n return () \u003e {/* @cat-caption */\\r\\n provider.disconnect();/* @cat-caption */\\r\\n };/* @cat-caption */\\r\\n }, );/* @cat-caption */\\r\\n\\r\\n return (\\r\\n \u003cdiv className\\\App\\\\u003e\\r\\n\\r\\n \u003c/div\u003e\\r\\n );\\r\\n}\\r\\n\\r\\nexport default App;\\r\\n\,\caption\:\一樣在前端,一模的創建一個 Doc\\n並且創建 websocket 連線\\n基本上此時,前端的 doc 已經會與後端自動同步了\,\language\:\javascript\},{\id\:\zc3w5m\,\code\:\import React, { useEffect, useRef } from \\\react\\\;\\r\\nimport * as Y from \\\yjs\\\;\\r\\nimport { WebsocketProvider } from \\\y-websocket\\\;\\r\\n\\r\\nfunction App() {\\r\\n useEffect(() \u003e {\\r\\n const ydoc new Y.Doc();\\r\\n const provider new WebsocketProvider(\\r\\n \\\ws://localhost:1234\\\,\\r\\n \\\my-roomname\\\,\\r\\n ydoc\\r\\n );\\r\\n\\r\\n return () \u003e {\\r\\n provider.disconnect();\\r\\n };\\r\\n }, );\\r\\n\\r\\n const textareaRef useRef(null);/* @cat-caption */\\r\\n\\r\\n return (\\r\\n \u003cdiv className\\\App\\\\u003e\\r\\n \u003ctextarea/* @cat-caption */\\r\\n ref{textareaRef}/* @cat-caption */\\r\\n style{{ width: \\\100%\\\, height: \\\90vh\\\ }}/* @cat-caption */\\r\\n \u003e\u003c/textarea\u003e/* @cat-caption */\\r\\n \u003c/div\u003e\\r\\n );\\r\\n}\\r\\n\\r\\nexport default App;\\r\\n\,\caption\:\創建一個輸入框\\n並透過 ref 好方便我們操作他\,\language\:\javascript\},{\id\:\vab2rf\,\code\:\import React, { useEffect, useRef } from \\\react\\\;\\r\\nimport * as Y from \\\yjs\\\;\\r\\nimport { WebsocketProvider } from \\\y-websocket\\\;\\r\\n\\r\\nfunction App() {\\r\\n useEffect(() \u003e {\\r\\n const ydoc new Y.Doc();\\r\\n const provider new WebsocketProvider(\\r\\n \\\ws://localhost:1234\\\,\\r\\n \\\my-roomname\\\,\\r\\n ydoc\\r\\n );\\r\\n\\r\\n const yText ydoc.getText(\\\textarea\\\); /* @cat-caption */\\r\\n\\r\\n return () \u003e {\\r\\n provider.disconnect();\\r\\n };\\r\\n }, );\\r\\n\\r\\n const textareaRef useRef(null);\\r\\n\\r\\n return (\\r\\n \u003cdiv className\\\App\\\\u003e\\r\\n \u003ctextarea\\r\\n ref{textareaRef}\\r\\n style{{ width: \\\100%\\\, height: \\\90vh\\\ }}\\r\\n \u003e\u003c/textarea\u003e\\r\\n \u003c/div\u003e\\r\\n );\\r\\n}\\r\\n\\r\\nexport default App;\\r\\n\,\caption\:\Doc 有點類似 Dictionary\\n可以放置很多不同的資料在裏頭\,\language\:\javascript\},{\id\:\2wewkf\,\code\:\import React, { useEffect, useRef } from \\\react\\\;\\r\\nimport * as Y from \\\yjs\\\;\\r\\nimport { WebsocketProvider } from \\\y-websocket\\\;\\r\\n\\r\\nfunction App() {\\r\\n const textareaRef useRef(null);\\r\\n\\r\\n useEffect(() \u003e {\\r\\n const ydoc new Y.Doc();\\r\\n const provider new WebsocketProvider(\\r\\n \\\ws://localhost:1234\\\,\\r\\n \\\my-roomname\\\,\\r\\n ydoc\\r\\n );\\r\\n\\r\\n const yText ydoc.getText(\\\textarea\\\); \\r\\n\\r\\n function handleInput(event) {/* @cat-caption */\\r\\n if (event.inputType \\\insertText\\\) {/* @cat-caption */\\r\\n const data event.data || \\\\\\;/* @cat-caption */\\r\\n const position textareaRef.current.selectionStart - 1;/* @cat-caption */\\r\\n yText.insert(position, data);/* @cat-caption */\\r\\n textareaRef.current.value yText.toString();/* @cat-caption */\\r\\n }/* @cat-caption */\\r\\n }/* @cat-caption */\\r\\n\\r\\n textareaRef.current.addEventListener(\\\input\\\, handleInput);/* @cat-caption */\\r\\n\\r\\n return () \u003e {\\r\\n provider.disconnect();\\r\\n textareaRef.current.removeEventListener(\\\input\\\, handleInput);/* @cat-caption */\\r\\n };\\r\\n }, );\\r\\n\\r\\n return (\\r\\n \u003cdiv className\\\App\\\\u003e\\r\\n \u003ctextarea\\r\\n ref{textareaRef}\\r\\n style{{ width: \\\100%\\\, height: \\\90vh\\\ }}\\r\\n \u003e\u003c/textarea\u003e\\r\\n \u003c/div\u003e\\r\\n );\\r\\n}\\r\\n\\r\\nexport default App;\\r\\n\,\caption\:\我們將輸入框一旦輸入文字\\n則直接把這串字插入進 yText 中\\nyText 會處理好自動同步問題\,\language\:\javascript\},{\id\:\29q7g6\,\code\:\import React, { useEffect, useRef } from \\\react\\\;\\nimport * as Y from \\\yjs\\\;\\nimport { WebsocketProvider } from \\\y-websocket\\\;\\n\\nfunction App() {\\n const textareaRef useRef(null);\\n\\n useEffect(() \u003e {\\n const ydoc new Y.Doc();\\n const provider new WebsocketProvider(\\n \\\ws://localhost:1234\\\,\\n \\\my-roomname\\\,\\n ydoc\\n );\\n\\n \\n const yText ydoc.getText(\\\textarea\\\); \\n\\n yText.observe(() \u003e {/* @cat-caption */\\n const textarea textareaRef.current;/* @cat-caption */\\n\\n const currentValue textarea.value;/* @cat-caption */\\n const newValue yText.toString();/* @cat-caption */\\n\\n if (currentValue ! newValue) {/* @cat-caption */\\n textarea.value newValue;/* @cat-caption */\\n }/* @cat-caption */\\n });/* @cat-caption */\\n\\n function handleInput(event) {\\n if (event.inputType \\\insertText\\\) {\\n const data event.data || \\\\\\;\\n const position textareaRef.current.selectionStart - 1;\\n yText.insert(position, data);\\n textareaRef.current.value yText.toString();\\n }\\n }\\n\\n textareaRef.current.addEventListener(\\\input\\\, handleInput);\\n\\n return () \u003e {\\n provider.disconnect();\\n textareaRef.current.removeEventListener(\\\input\\\, handleInput);\\n };\\n }, );\\n\\n return (\\n \u003cdiv className\\\App\\\\u003e\\n \u003ctextarea\\n ref{textareaRef}\\n style{{ width: \\\100%\\\, height: \\\90vh\\\ }}\\n \u003e\u003c/textarea\u003e\\n \u003c/div\u003e\\n );\\n}\\n\\nexport default App;\\n\,\caption\:\這邊 yjs 提供一個監聽的接口\\n當檢測到 yText 被遠端同步更新後\\n會自動更新輸入框\,\language\:\javascript\},{\id\:\fdw28\,\code\:\import React, { useEffect, useRef } from \\\react\\\;\\nimport * as Y from \\\yjs\\\;\\nimport { WebsocketProvider } from \\\y-websocket\\\;\\n\\nfunction App() {\\n const textareaRef useRef(null);\\n\\n useEffect(() \u003e {\\n const ydoc new Y.Doc();\\n const provider new WebsocketProvider(\\n \\\ws://localhost:1234\\\,\\n \\\my-roomname\\\,\\n ydoc\\n );\\n const yText ydoc.getText(\\\textarea\\\);\\n\\n yText.observe(() \u003e {\\n const textarea textareaRef.current;\\n\\n const currentValue textarea.value;\\n const newValue yText.toString();\\n\\n if (currentValue ! newValue) {\\n textarea.value newValue;\\n }\\n });\\n\\n function handleInput(event) {\\n if (event.inputType \\\insertText\\\) {\\n const data event.data || \\\\\\;\\n const position textareaRef.current.selectionStart - 1;\\n yText.insert(position, data);\\n textareaRef.current.value yText.toString();\\n } else if (event.inputType \\\insertLineBreak\\\) {/* @cat-caption */\\n const position textareaRef.current.selectionStart;/* @cat-caption */\\n yText.insert(position, \\\\\\\n\\\);/* @cat-caption */\\n textareaRef.current.value yText.toString();/* @cat-caption */\\n } else if (event.inputType \\\deleteContentBackward\\\) {/* @cat-caption */\\n const position textareaRef.current.selectionStart;/* @cat-caption */\\n yText.delete(position, 1);/* @cat-caption */\\n textareaRef.current.value yText.toString();/* @cat-caption */\\n }/* @cat-caption */\\n }\\n\\n textareaRef.current.addEventListener(\\\input\\\, handleInput);\\n\\n return () \u003e {\\n provider.disconnect();\\n textareaRef.current.removeEventListener(\\\input\\\, handleInput);\\n };\\n }, );\\n\\n return (\\n \u003cdiv className\\\App\\\\u003e\\n \u003ctextarea\\n ref{textareaRef}\\n style{{ width: \\\100%\\\, height: \\\90vh\\\ }}\\n \u003e\u003c/textarea\u003e\\n \u003c/div\u003e\\n );\\n}\\n\\nexport default App;\,\caption\:\這邊加上刪除、換行功能\,\language\:\javascript\},{\id\:\hs54o\,\code\:\import React, { useEffect, useRef } from \\\react\\\;\\nimport * as Y from \\\yjs\\\;\\nimport { WebsocketProvider } from \\\y-websocket\\\;\\n\\nfunction App() {\\n const textareaRef useRef(null);\\n\\n useEffect(() \u003e {\\n const ydoc new Y.Doc();\\n const provider new WebsocketProvider(\\n \\\ws://localhost:1234\\\,\\n \\\my-roomname\\\,\\n ydoc\\n );\\n const yText ydoc.getText(\\\textarea\\\);\\n\\n yText.observe(() \u003e {\\n const textarea textareaRef.current;\\n const cursorPosition textarea.selectionStart; /* @cat-caption */\\n\\n const currentValue textarea.value;\\n const newValue yText.toString();\\n\\n if (currentValue ! newValue) {\\n textarea.value newValue;\\n textarea.setSelectionRange(cursorPosition, cursorPosition);/* @cat-caption */\\n }\\n });\\n\\n function handleInput(event) {\\n if (event.inputType \\\insertText\\\) {\\n const data event.data || \\\\\\;\\n const position textareaRef.current.selectionStart - 1;\\n yText.insert(position, data);\\n textareaRef.current.value yText.toString();\\n } else if (event.inputType \\\insertLineBreak\\\) {\\n const position textareaRef.current.selectionStart;\\n yText.insert(position, \\\\\\\n\\\);\\n textareaRef.current.value yText.toString();\\n } else if (event.inputType \\\deleteContentBackward\\\) {\\n const position textareaRef.current.selectionStart;\\n yText.delete(position, 1);\\n textareaRef.current.value yText.toString();\\n }\\n }\\n\\n textareaRef.current.addEventListener(\\\input\\\, handleInput);\\n\\n return () \u003e {\\n provider.disconnect();\\n textareaRef.current.removeEventListener(\\\input\\\, handleInput);\\n };\\n }, );\\n\\n return (\\n \u003cdiv className\\\App\\\\u003e\\n \u003ctextarea\\n ref{textareaRef}\\n style{{ width: \\\100%\\\, height: \\\90vh\\\ }}\\n \u003e\u003c/textarea\u003e\\n \u003c/div\u003e\\n );\\n}\\n\\nexport default App;\,\caption\:\解決光標沒有自動更新\,\language\:\javascript\},{\id\:\43uj9s\,\code\:\\,\caption\:\編輯器套件分享\,\language\:\javascript\,\image\:{\url\:\/api/object/1722651920286-r8lnls.png\,\position\:\center\,\fullWidth\:true,\width\:200}},{\id\:\8k4w5o\,\code\:\\,\caption\:\編輯器套件分享\,\language\:\javascript\,\image\:{\url\:\/api/object/1722651933269-81w3zf.png\,\position\:\center\,\fullWidth\:true,\width\:200}},{\id\:\b8fvlo\,\code\:\\,\caption\:\編輯器套件分享\,\language\:\javascript\,\image\:{\url\:\/api/object/1722651948383-3g0mik.png\,\position\:\center\,\fullWidth\:true,\width\:200}},{\id\:\36omdb\,\code\:\\,\caption\:\Ziphus 分享\,\language\:\javascript\,\image\:{\url\:\/api/object/1722651963129-johpai.png\,\position\:\center\,\fullWidth\:true,\width\:200}})/script>script>self.__next_f.push(1,3c:T50c,)/script>script>self.__next_f.push(1,import React, { useEffect, useRef } from \react\;\r\nimport * as Y from \yjs\;\r\nimport { WebsocketProvider } from \y-websocket\;\r\n\r\nfunction App() {\r\n const textareaRef useRef(null);\r\n\r\n useEffect(() \u003e {\r\n const ydoc new Y.Doc();\r\n const provider new WebsocketProvider(\r\n \ws://localhost:1234\,\r\n \my-roomname\,\r\n ydoc\r\n );\r\n\r\n const yText ydoc.getText(\textarea\); \r\n\r\n function handleInput(event) {/* @cat-caption */\r\n if (event.inputType \insertText\) {/* @cat-caption */\r\n const data event.data || \\;/* @cat-caption */\r\n const position textareaRef.current.selectionStart - 1;/* @cat-caption */\r\n yText.insert(position, data);/* @cat-caption */\r\n textareaRef.current.value yText.toString();/* @cat-caption */\r\n }/* @cat-caption */\r\n }/* @cat-caption */\r\n\r\n textareaRef.current.addEventListener(\input\, handleInput);/* @cat-caption */\r\n\r\n return () \u003e {\r\n provider.disconnect();\r\n textareaRef.current.removeEventListener(\input\, handleInput);/* @cat-caption */\r\n };\r\n }, );\r\n\r\n return (\r\n \u003cdiv className\App\\u003e\r\n \u003ctextarea\r\n ref{textareaRef}\r\n style{{ width: \100%\, height: \90vh\ }}\r\n \u003e\u003c/textarea\u003e\r\n \u003c/div\u003e\r\n );\r\n}\r\n\r\nexport default App;\r\n)/script>script>self.__next_f.push(1,3d:T5b7,)/script>script>self.__next_f.push(1,import React, { useEffect, useRef } from \react\;\nimport * as Y from \yjs\;\nimport { WebsocketProvider } from \y-websocket\;\n\nfunction App() {\n const textareaRef useRef(null);\n\n useEffect(() \u003e {\n const ydoc new Y.Doc();\n const provider new WebsocketProvider(\n \ws://localhost:1234\,\n \my-roomname\,\n ydoc\n );\n\n \n const yText ydoc.getText(\textarea\); \n\n yText.observe(() \u003e {/* @cat-caption */\n const textarea textareaRef.current;/* @cat-caption */\n\n const currentValue textarea.value;/* @cat-caption */\n const newValue yText.toString();/* @cat-caption */\n\n if (currentValue ! newValue) {/* @cat-caption */\n textarea.value newValue;/* @cat-caption */\n }/* @cat-caption */\n });/* @cat-caption */\n\n function handleInput(event) {\n if (event.inputType \insertText\) {\n const data event.data || \\;\n const position textareaRef.current.selectionStart - 1;\n yText.insert(position, data);\n textareaRef.current.value yText.toString();\n }\n }\n\n textareaRef.current.addEventListener(\input\, handleInput);\n\n return () \u003e {\n provider.disconnect();\n textareaRef.current.removeEventListener(\input\, handleInput);\n };\n }, );\n\n return (\n \u003cdiv className\App\\u003e\n \u003ctextarea\n ref{textareaRef}\n style{{ width: \100%\, height: \90vh\ }}\n \u003e\u003c/textarea\u003e\n \u003c/div\u003e\n );\n}\n\nexport default App;\n)/script>script>self.__next_f.push(1,3e:T76a,)/script>script>self.__next_f.push(1,import React, { useEffect, useRef } from \react\;\nimport * as Y from \yjs\;\nimport { WebsocketProvider } from \y-websocket\;\n\nfunction App() {\n const textareaRef useRef(null);\n\n useEffect(() \u003e {\n const ydoc new Y.Doc();\n const provider new WebsocketProvider(\n \ws://localhost:1234\,\n \my-roomname\,\n ydoc\n );\n const yText ydoc.getText(\textarea\);\n\n yText.observe(() \u003e {\n const textarea textareaRef.current;\n\n const currentValue textarea.value;\n const newValue yText.toString();\n\n if (currentValue ! newValue) {\n textarea.value newValue;\n }\n });\n\n function handleInput(event) {\n if (event.inputType \insertText\) {\n const data event.data || \\;\n const position textareaRef.current.selectionStart - 1;\n yText.insert(position, data);\n textareaRef.current.value yText.toString();\n } else if (event.inputType \insertLineBreak\) {/* @cat-caption */\n const position textareaRef.current.selectionStart;/* @cat-caption */\n yText.insert(position, \\\n\);/* @cat-caption */\n textareaRef.current.value yText.toString();/* @cat-caption */\n } else if (event.inputType \deleteContentBackward\) {/* @cat-caption */\n const position textareaRef.current.selectionStart;/* @cat-caption */\n yText.delete(position, 1);/* @cat-caption */\n textareaRef.current.value yText.toString();/* @cat-caption */\n }/* @cat-caption */\n }\n\n textareaRef.current.addEventListener(\input\, handleInput);\n\n return () \u003e {\n provider.disconnect();\n textareaRef.current.removeEventListener(\input\, handleInput);\n };\n }, );\n\n return (\n \u003cdiv className\App\\u003e\n \u003ctextarea\n ref{textareaRef}\n style{{ width: \100%\, height: \90vh\ }}\n \u003e\u003c/textarea\u003e\n \u003c/div\u003e\n );\n}\n\nexport default App;)/script>script>self.__next_f.push(1,3f:T767,)/script>script>self.__next_f.push(1,import React, { useEffect, useRef } from \react\;\nimport * as Y from \yjs\;\nimport { WebsocketProvider } from \y-websocket\;\n\nfunction App() {\n const textareaRef useRef(null);\n\n useEffect(() \u003e {\n const ydoc new Y.Doc();\n const provider new WebsocketProvider(\n \ws://localhost:1234\,\n \my-roomname\,\n ydoc\n );\n const yText ydoc.getText(\textarea\);\n\n yText.observe(() \u003e {\n const textarea textareaRef.current;\n const cursorPosition textarea.selectionStart; /* @cat-caption */\n\n const currentValue textarea.value;\n const newValue yText.toString();\n\n if (currentValue ! newValue) {\n textarea.value newValue;\n textarea.setSelectionRange(cursorPosition, cursorPosition);/* @cat-caption */\n }\n });\n\n function handleInput(event) {\n if (event.inputType \insertText\) {\n const data event.data || \\;\n const position textareaRef.current.selectionStart - 1;\n yText.insert(position, data);\n textareaRef.current.value yText.toString();\n } else if (event.inputType \insertLineBreak\) {\n const position textareaRef.current.selectionStart;\n yText.insert(position, \\\n\);\n textareaRef.current.value yText.toString();\n } else if (event.inputType \deleteContentBackward\) {\n const position textareaRef.current.selectionStart;\n yText.delete(position, 1);\n textareaRef.current.value yText.toString();\n }\n }\n\n textareaRef.current.addEventListener(\input\, handleInput);\n\n return () \u003e {\n provider.disconnect();\n textareaRef.current.removeEventListener(\input\, handleInput);\n };\n }, );\n\n return (\n \u003cdiv className\App\\u003e\n \u003ctextarea\n ref{textareaRef}\n style{{ width: \100%\, height: \90vh\ }}\n \u003e\u003c/textarea\u003e\n \u003c/div\u003e\n );\n}\n\nexport default App;)/script>script>self.__next_f.push(1,40:Tce3,)/script>script>self.__next_f.push(1,## 簡介\n\n不知道你有沒有開發多人線上遊戲的夢呢?然後即時同步的問題卻卡住了你\n\n如何將所有人的資料同步給所有人的電腦上,在該如何實現呢?\n\n又或你已經略知一二,卻不知如何下手。\n\n如果你有上述問題,那這堂課正是為你而準備!\n\n我們將帶你深入淺出有關於 SocketIO 的基礎知識\n\n從 0 到 100 搭出一個簡單的即時多人聊天網頁,為你的 sideproject 添上一筆。\n\n## 課程影片\n\n本課程以剪輯成全字幕教學\n\n建議透過該影片學習\n\nhttps://www.youtube.com/watch?vssmEkPq8txA(https://www.youtube.com/watch?vssmEkPq8txA)\n\n## 課程程式碼\n\nhttps://github.com/SR0725/socketio-course(https://github.com/SR0725/socketio-course)\n\n## 課程成品預覽\n\nhttps://socketio-course.ray0725.repl.co/(https://socketio-course.ray0725.repl.co/)\n\n## 課程使用技術\n\n1\\. JavaScript\n\n2\\. SocketIO\n\n3\\. Canvas\n\n4\\. 非常少量的 ExpressJs\n\n## 課前提醒\n\n我預估 7: 50 附近,會暫時離開 10 分鐘,就當是休息時間 XD\n\n## 第一章\n\n### 何謂 SocketIO\n\n如果想讓前端跟後端互相傳送資料,我們可能會用 fetch 或 axios 來取得或傳送資料給後端\n\n但如果想做到雙方即時的不間段通訊,SocketIO 會是當中一個非常好的技術來實踐這件事情\n\n如果想理解更多,可以參考 socket.io 的通訊協定基礎 WebSocket。\n\n### 開始\n\n先建立一個空資料夾來前端與後端的檔案,打開資料夾,並在上方的位置列上輸入 cmd\n\n\u003cimg height\0\ width\0\ src\https://res.cloudinary.com/diwnwuvcu/image/upload/f_auto,c_limit,w_1125,q_auto/nmfpofekpby91xwvggeq\ /\u003e\n\n點擊 Enter 即可開啟指向該路徑的小黑窗\n\n\u003cimg height\0\ width\0\ src\https://res.cloudinary.com/diwnwuvcu/image/upload/f_auto,c_limit,w_537,q_auto/dsnho6mqdehxzraos1gb\ /\u003e\n\n我們現在要來 \\*\\*Vite\\*\\* 這個工具建立前端的專案檔\n\n### 何謂 Vite\n\n在前端開發的路上,荊棘不斷,前人設計了各種工具好讓後輩在這條路上走得更順利點\n\n與過往純粹寫 HTML、JS 檔案不同,今天的課程將使用 Vite 這個前端建置工具\n\n有關於 Vite 是什麼?他的細節,這個是可以單獨再用三個小時去解釋的,這並不會在本堂課的內容裏頭。\n\n今天,我只會帶大家去使用這個工具,並且直接去感受 Vite 帶給我們的美好\n\n### 建置前端專案\n\n在剛剛開起的小黑窗中輸入\n\n`npm create vite@latest`\n\n他會跳出以下訊息,這邊是要你輸入專案名稱,這邊我使用 frontend 來作為我的專案名稱\n\n`? Project name: » vite-project`\n\n再來會詢問你使用什麼框架,我們沒有要使用任何框架,選擇\\`Vanilla\\`表示原生的 JS\n\n\u003cimg height\0\ width\0\ src\https://res.cloudinary.com/diwnwuvcu/image/upload/f_auto,c_limit,w_551,q_auto/hopga5opm1ar4vldkxxx\ /\u003e\n\n這裡是詢問使用什麼語言,我們這堂課不會使用 TypeScript,所以選擇 JavaScript\n\n\u003cimg height\0\ width\0\ src\https://res.cloudinary.com/diwnwuvcu/image/upload/f_auto,c_limit,w_547,q_auto/rpr8swsmj88h9uksqsfx\ /\u003e\n\n最後他會建立一個叫做 frontend 的資料夾並跳出以下訊息\n\n\\`\\`\\`\n\nDone. Now run:\n\n\u0026#x20; cd frontend\n\n\u0026#x20; npm install\n\n\u0026#x20; npm run dev)/script>script>self.__next_f.push(1,41:T1988,)/script>script>self.__next_f.push(1,## 文章大綱\n\n這是一篇技術教學文章,你可以理解如何使用如何替 VRoid Studio 做出來的人物模型透過 mixamo 加上動畫,並且導入進 threejs 的網頁中\u0026#x20;\n\n## 背景故事\n\n因為朋友最近在玩 VRoid Studio,順手丟給了我 VRoid Studio 的人物模型檔案 (vrm)\n\n而我目前也剛好在開發一項 3D 網頁案子(簡單講就是網頁版 VRChat)\u0026#x20;\n\n下意識的我決定嘗試能否把它變成玩家的 Avatar\u0026#x20;\n\n簡易爬文後,我使用了 three-vrm 這個套件也很順利的把東西載入進來\n\n正當我覺得沒有意外時,意外就來了\n\n!(https://res.cloudinary.com/diwnwuvcu/image/upload/f_auto,c_limit,w_730,q_auto/ollhlic7gt9dm3e1w8me)\n\n非常好看,也完美被讀入網頁當中,诶等等?動作呢?\n\n仔細研究後發現 VRoid Studio 的動作是有版權的,不能隨意使用,這導致了我必須要用外部動畫檔\n\n## 實際操作\n\n### 1. 開始 (VRoid)\n\n首先我們會需要先使用 VRoid 設計出屬於你自己的 Avatar\n\n因為 VRoid 的操作相對簡單,而且並不是本文章所要討論的技術,所以我並不會講解 VRoid 的使用方式\n\n所以這邊我們假設各位已經設計好了屬於自己的 Avatar,請直接輸出 vrm 檔案\n\n!(https://res.cloudinary.com/diwnwuvcu/image/upload/f_auto,c_limit,w_730,q_auto/u4jwfchygvo4exfda0nh)\n\n### 2. 轉檔 (Blender)\n\n這邊我使用開源建模軟體 Blender 來進行轉檔與動作處理(因為他好用而且免費)\n\n首先請各位先都載好 Blender\n\n再來由於 Blender 原生是不支援 vrm 檔案的,好消息是由於 vrm 檔案跟 gltf 可說是攣生兄弟,更準確來說 vrm 就是 gltf 魔改過來得\n\n所以這邊你可以直接當我們剛剛的 Avatar 人物模型檔案的副檔名 vrm 直接改成 glb 檔案\n\n並且由 Blender 直接 import gltf 檔案\u0026#x20;\n\n!(https://res.cloudinary.com/diwnwuvcu/image/upload/f_auto,c_limit,w_730,q_auto/lzximr0asutlzdprbpxb)\n\n你會發現他幾乎完美的被引入進 Blender 裏頭,並且也能看到顏色\n\n### 3. 動作 (Mixamo)\n\n在前一個步驟裏頭我們已經將人物模型引入進 Blender 裏頭了\n\n再來就是重頭戲,替我們的人物加上多個人物動作\n\n這邊我們使用 Adobe 所推出的人物動作網站,裏頭有大量被設計好的人物動作可以直接使用\n\nhttps://www.mixamo.com/(https://www.mixamo.com/)\n\n但這個網頁只允許讀 FBX 格式的檔案,沒事,問題不大,咱們可是有 Blender 呢!\n\n這邊我們直接用 Blender 輸出 FBX 模型檔案\n\n設定上,保持默認設定即可\n\n直接輸出 FBX 檔案\n\n獲得 FBX 檔案後,直接打開 Mixamo\u0026#x20;\n\n在左上角的地方,應該會有 UPLOAD CHARCTER 的按鈕,點擊後會跳出以下畫面\n\n!(https://res.cloudinary.com/diwnwuvcu/image/upload/f_auto,c_limit,w_730,q_auto/p2zcxz8jsizpgyxwnwwm)\n\n這邊會花上一段時間來讀取\n\n順利的話,就可以看到我們可愛的人物模型了\n\n如果發現無法正常讀取人物的檔案,我的經驗是,重開 Blender 重開新的專案\n\n然後重新 import 人物模型檔案,再重新輸出 fbx 檔案,通常就好了\n\n原因不明,如果知道原因的歡迎私訊我 XD\n\n!(https://res.cloudinary.com/diwnwuvcu/image/upload/f_auto,c_limit,w_730,q_auto/cvtkh7wyrkjsaxpcxuo4)\n\n再來選擇我們要的人物動作,你可以一次找好幾個動作,並且將各個動作的檔案下載下來\n\n會建議將 skin 的設定設置為 Without Skin\n\n!(https://res.cloudinary.com/diwnwuvcu/image/upload/f_auto,c_limit,w_730,q_auto/xggvzhmytguc8gaqu5pq)\n\n這邊我下載了 Walking 跟 Jump 兩個動作,各位可根據自己需求來做下載\n\n### 4. 合成動作 (Blender)\n\n再來我們要將剛剛下載下來的兩個檔案融合進我們的人物中\n\n回到 Blender 並且透過 import fbx 檔案來直接 import 我們剛剛的動作檔案\n\n此時你會發現右上角出現了導入的模型檔案,但是只有骨架\n\n點擊上方的 Animatiom\n\n!(https://res.cloudinary.com/diwnwuvcu/image/upload/f_auto,c_limit,w_730,q_auto/xkjrk8f7dby1uoylbc4w)\n\n下方時間表切換成 Action Editor\n\n!(https://res.cloudinary.com/diwnwuvcu/image/upload/f_auto,c_limit,w_730,q_auto/aoex5vpbbv2gtopmx5vk)\n\n點擊我們的人物模型(注意不要點到剛剛引入的動作骨架了)\n\n並且在點擊中下方的動作列表,而這裡將呈現\所有\的動作,也就是不管是不是這個角色的動作,都會被顯示在這個按鈕中\n\n你能在這裡發現我們剛剛引入的動作會被放置在這裡,名字可能很詭異叫做 \Armature.001|mixamo.com|Layer0\\n\n直接點擊,便能將這個動作強行套用在我們的人物模型上頭\n\n!(https://res.cloudinary.com/diwnwuvcu/image/upload/f_auto,c_limit,w_730,q_auto/liaykv9ajaanpcewlina)\n\n到這邊,我們就能把剛剛引入的動作模型檔案給直接移除掉了,我們再也不需要它了\n\n如果你有多個動作就重複以上行為\n\n1. 引入動作模型 -\u003e\u0026#x20;\n2. 切進 Animatiom 的 Action Editor -\u003e\u0026#x20;\n3. 點擊人物 -\u003e\u0026#x20;\n4. 點擊動作列表 -\u003e\u0026#x20;\n5. 將剛剛引入的動作套用至人物上 -\u003e\u0026#x20;\n6. 刪掉剛剛引入的動作模型\n\n這邊我將兩個動作都套用至人物模型上了,並且命名成我所想要的名字,這個動作名字會直接呈現在 threejs 上頭,會影響到程式碼風格,務必注意\n\n!(https://res.cloudinary.com/diwnwuvcu/image/upload/f_auto,c_limit,w_730,q_auto/ltphzcraufob6hnahmqp)\n\n這邊直接輸出成 gltf 檔案即可\n\n!(https://res.cloudinary.com/diwnwuvcu/image/upload/f_auto,c_limit,w_730,q_auto/k38ail9s3i2hydysv21k)\n\n### 5. 應用於 threejs 中\n\n基本上直接用 GLTFLoader 讀取模型並引入即可\n\n但如果你是 react-three-fiber 使用者的話\n\n應該會使用 gltfjsx(https://github.com/pmndrs/gltfjsx) 來將剛剛的人物模型檔直接轉成容易操作的 tsx 檔案\n\n這邊有個大坑務必注意閃躲\n\n雖然在使用 gltfjsx 檔案時我們習慣加上 `--transform`來壓縮檔案\n\n但是千萬不要對人物模型做壓縮\n\n被壓縮後的人物模型檔案是無法使用`SkeletonUtils.clone(scene)`來做到角色複製\n\n原因不明,推測是 gltfjsx 的 bug\n\n## 結語\n\n成果在這\n\nhttps://www.youtube.com/embed/HGRhBCZE1L4(https://www.youtube.com/embed/HGRhBCZE1L4)\n\n最後工商一下我的個人網頁\n\nhttps://ray-realms.com/(https://ray-realms.com/)\n\n**如果有問題歡迎私訊My discord: @ray.realms**\n(https://discord.com/settings/hypesquad-online))/script>script>self.__next_f.push(1,42:T3179,)/script>script>self.__next_f.push(1,{\id\:\ts5wx5\,\code\:\\u003c!DOCTYPE html\u003e\\r\\n\u003chtml\u003e\\r\\n\\r\\n\u003chead\u003e\\r\\n \u003ctitle\u003e我的網頁\u003c/title\u003e\\r\\n\u003c/head\u003e\\r\\n\\r\\n\u003cbody\u003e\\r\\n \u003ch1\u003e這是一個標題\u003c/h1\u003e\\r\\n \u003cdiv\u003e\\r\\n \u003ch2\u003e這是一個小標題\u003c/h2\u003e\\r\\n \u003cp\u003e這是一段文字。\u003c/p\u003e\\r\\n \u003c/div\u003e\\r\\n\u003c/body\u003e\\r\\n\\r\\n\u003c/html\u003e\,\caption\:\# 網頁 DOM 操作\\n\\nDOM 結構與樹狀結構\\n\\n基本上我們剛剛撰寫的 HTML 只是純粹的文字\\n瀏覽器為了方便程式計算,會先將 HTML 轉換成一個巨大的 object\\n這就是 DOM(Document Object Model)\\n可以很好的方便程式操作\\n\\nDOM 就像是網頁的地圖,讓我們可以找到網頁上的每個元素。可以把 DOM 想像成一棵大樹,每個元素都是這棵樹上的一個節點。\\n\\n\,\language\:\html\,\image\:{\url\:\/api/object/1719227447885-hvezpm.png\,\position\:\bottom-right\,\width\:600}},{\id\:\gibglm\,\code\:\\u003c!DOCTYPE html\u003e\\r\\n\u003chtml\u003e\\r\\n\\r\\n\u003chead\u003e\\r\\n \u003ctitle\u003e我的網頁\u003c/title\u003e\\r\\n\u003c/head\u003e\\r\\n\\r\\n\u003cbody\u003e\\r\\n \u003ch1\u003e這是一個標題\u003c/h1\u003e\\r\\n \u003cdiv\u003e\\r\\n \u003ch2 id\\\smallTitle\\\\u003e這是一個小標題\u003c/h2\u003e/* @cat-caption */\\r\\n \u003cp\u003e這是一段文字。\u003c/p\u003e\\r\\n \u003c/div\u003e\\r\\n\u003c/body\u003e\\r\\n\\r\\n\u003c/html\u003e\,\caption\:\我們先來看一下如何讓 JS 取得某個元素\\n\\n請先替指定元素加上 id 屬性\\nid,你可以想像就是替元素貼上一張訂製標籤\\n方便程式找到他\\n每個元素的 id 都應該是獨一無二的\,\language\:\html\},{\id\:\gmvdmu\,\code\:\\u003c!DOCTYPE html\u003e\\r\\n\u003chtml\u003e\\r\\n\\r\\n\u003chead\u003e\\r\\n \u003ctitle\u003e我的網頁\u003c/title\u003e\\r\\n\u003c/head\u003e\\r\\n\\r\\n\u003cbody\u003e\\r\\n \u003ch1\u003e這是一個標題\u003c/h1\u003e\\r\\n \u003cdiv\u003e\\r\\n \u003ch2 id\\\smallTitle\\\\u003e這是一個小標題\u003c/h2\u003e\\r\\n \u003cp\u003e這是一段文字。\u003c/p\u003e\\r\\n \u003c/div\u003e\\r\\n \u003cscript\u003e/* @cat-caption */\\r\\n console.log(Hi!)/* @cat-caption */\\r\\n \u003c/script\u003e/* @cat-caption */\\r\\n\u003c/body\u003e\\r\\n\\r\\n\u003c/html\u003e\,\caption\:\除了在 F12 當中直接運行 JavaScript\\n你可以透過 \u003cscript\u003e 元素插入 JavaScript 程式碼在裏頭\\n他會在網頁打開的瞬間被自動執行\,\language\:\html\,\image\:{\url\:\/api/object/1719241589772-jg0d0n.png\,\position\:\bottom-right\,\width\:600}},{\id\:\e1qcl7m\,\code\:\\u003c!DOCTYPE html\u003e\\r\\n\u003chtml\u003e\\r\\n\\r\\n\u003chead\u003e\\r\\n \u003ctitle\u003e我的網頁\u003c/title\u003e\\r\\n\u003c/head\u003e\\r\\n\\r\\n\u003cbody\u003e\\r\\n \u003ch1\u003e這是一個標題\u003c/h1\u003e\\r\\n \u003cdiv\u003e\\r\\n \u003ch2 id\\\smallTitle\\\\u003e這是一個小標題\u003c/h2\u003e\\r\\n \u003cp\u003e這是一段文字。\u003c/p\u003e\\r\\n \u003c/div\u003e\\r\\n \u003cscript\u003e\\r\\n let smallTitleElement document.getElementById(\\\smallTitle\\\);/* @cat-caption */\\r\\n \u003c/script\u003e\\r\\n\u003c/body\u003e\\r\\n\\r\\n\u003c/html\u003e\,\caption\:\獲取元素(getElementById)\\n\\n我們可以用 JavaScript 來找到網頁上的元素,就像在樹上找到一片特定的葉子。我們可以用 getElementById 來找元素。\,\language\:\html\,\image\:{\url\:\/api/object/1719241915416-nztzq.png\,\position\:\bottom-right\,\width\:301}},{\id\:\kpmwsx\,\code\:\\u003c!DOCTYPE html\u003e\\r\\n\u003chtml\u003e\\r\\n\\r\\n\u003chead\u003e\\r\\n \u003ctitle\u003e我的網頁\u003c/title\u003e\\r\\n\u003c/head\u003e\\r\\n\\r\\n\u003cbody\u003e\\r\\n \u003ch1\u003e這是一個標題\u003c/h1\u003e\\r\\n \u003cdiv\u003e\\r\\n \u003ch2 id\\\smallTitle\\\\u003e這是一個小標題\u003c/h2\u003e\\r\\n \u003cp\u003e這是一段文字。\u003c/p\u003e\\r\\n \u003c/div\u003e\\r\\n \u003cscript\u003e\\r\\n let smallTitleElement document.getElementById(\\\smallTitle\\\);\\r\\n smallTitleElement.style.color \\\red\\\;/* @cat-caption */\\r\\n \u003c/script\u003e\\r\\n\u003c/body\u003e\\r\\n\\r\\n\u003c/html\u003e\,\caption\:\接下來就可以很好的去對該元素讀取或操作\\n比如說將他的 style 的 color 直接改成紅色\,\language\:\html\,\image\:{\url\:\/api/object/1719241938952-ioraja.png\,\position\:\bottom-right\,\width\:300}},{\id\:\p3q2l\,\code\:\\u003c!DOCTYPE html\u003e\\r\\n\u003chtml\u003e\\r\\n\\r\\n\u003chead\u003e\\r\\n \u003ctitle\u003e我的網頁\u003c/title\u003e\\r\\n\u003c/head\u003e\\r\\n\\r\\n\u003cbody\u003e\\r\\n \u003ch1\u003e這是一個標題\u003c/h1\u003e\\r\\n \u003cdiv\u003e\\r\\n \u003ch2 id\\\smallTitle\\\\u003e這是一個小標題\u003c/h2\u003e\\r\\n \u003cp\u003e這是一段文字。\u003c/p\u003e\\r\\n \u003c/div\u003e\\r\\n \u003cscript\u003e\\r\\n let smallTitleElement document.getElementById(\\\smallTitle\\\);\\r\\n smallTitleElement.style.color \\\red\\\;\\r\\n smallTitleElement.innerText \\\這是一個紅色小標題\\\/* @cat-caption */\\r\\n \u003c/script\u003e\\r\\n\u003c/body\u003e\\r\\n\\r\\n\u003c/html\u003e\,\caption\:\將元素裏頭的文字改變\,\language\:\html\,\image\:{\url\:\/api/object/1719241953660-ncbs1o.png\,\position\:\bottom-right\,\width\:301}},{\id\:\44j8bc\,\code\:\\u003c!DOCTYPE html\u003e\\r\\n\u003chtml\u003e\\r\\n\\r\\n\u003chead\u003e\\r\\n \u003ctitle\u003e我的網頁\u003c/title\u003e\\r\\n\u003c/head\u003e\\r\\n\\r\\n\u003cbody\u003e\\r\\n \u003ch1\u003e這是一個標題\u003c/h1\u003e\\r\\n \u003cdiv\u003e\\r\\n \u003ch2 id\\\smallTitle\\\\u003e這是一個小標題\u003c/h2\u003e\\r\\n \u003cp\u003e這是一段文字。\u003c/p\u003e\\r\\n \u003cbutton id\\\myButton\\\\u003e這是一個按鈕\u003c/button\u003e/* @cat-caption */\\r\\n \u003c/div\u003e\\r\\n \u003cscript\u003e\\r\\n let smallTitleElement document.getElementById(\\\smallTitle\\\);\\r\\n smallTitleElement.style.color \\\red\\\;\\r\\n smallTitleElement.innerText \\\這是一個紅色小標題\\\;\\r\\n \u003c/script\u003e\\r\\n\u003c/body\u003e\\r\\n\\r\\n\u003c/html\u003e\,\caption\:\事件\\n也就是使用者可以對這些元素做的事情\\n好比說我們可以\\\點擊\\\按鈕、可以\\\輸入\\\文字\\n點擊、輸入 這些都是一個一個的事件\\n\\n我們先新增一個按鈕元素來教學\,\language\:\html\,\image\:{\url\:\/api/object/1719242121616-9j34t8.png\,\position\:\bottom-right\,\width\:300}},{\id\:\100rl5\,\code\:\\u003c!DOCTYPE html\u003e\\r\\n\u003chtml\u003e\\r\\n\\r\\n\u003chead\u003e\\r\\n \u003ctitle\u003e我的網頁\u003c/title\u003e\\r\\n\u003c/head\u003e\\r\\n\\r\\n\u003cbody\u003e\\r\\n \u003ch1\u003e這是一個標題\u003c/h1\u003e\\r\\n \u003cdiv\u003e\\r\\n \u003ch2 id\\\smallTitle\\\\u003e這是一個小標題\u003c/h2\u003e\\r\\n \u003cp\u003e這是一段文字。\u003c/p\u003e\\r\\n \u003cbutton id\\\myButton\\\\u003e這是一個按鈕\u003c/button\u003e\\r\\n \u003c/div\u003e\\r\\n \u003cscript\u003e\\r\\n let smallTitleElement document.getElementById(\\\smallTitle\\\);\\r\\n smallTitleElement.style.color \\\red\\\;\\r\\n smallTitleElement.innerText \\\這是一個紅色小標題\\\;\\r\\n\\r\\n let button document.getElementById(myButton); /* @cat-caption */\\r\\n \u003c/script\u003e\\r\\n\u003c/body\u003e\\r\\n\\r\\n\u003c/html\u003e\,\caption\:\如何知道使用者有沒有點擊這個按鈕呢?\\n第一步,仍然是讓程式碼找到這個按鈕的存在\,\language\:\html\,\image\:{\url\:\/api/object/1719242840588-q33r6l.png\,\position\:\bottom-right\,\width\:300}},{\id\:\2wo10h\,\code\:\\u003c!DOCTYPE html\u003e\\r\\n\u003chtml\u003e\\r\\n\\r\\n\u003chead\u003e\\r\\n \u003ctitle\u003e我的網頁\u003c/title\u003e\\r\\n\u003c/head\u003e\\r\\n\\r\\n\u003cbody\u003e\\r\\n \u003ch1\u003e這是一個標題\u003c/h1\u003e\\r\\n \u003cdiv\u003e\\r\\n \u003ch2 id\\\smallTitle\\\\u003e這是一個小標題\u003c/h2\u003e\\r\\n \u003cp\u003e這是一段文字。\u003c/p\u003e\\r\\n \u003cbutton id\\\myButton\\\\u003e這是一個按鈕\u003c/button\u003e\\r\\n \u003c/div\u003e\\r\\n \u003cscript\u003e\\r\\n let smallTitleElement document.getElementById(\\\smallTitle\\\);\\r\\n smallTitleElement.style.color \\\red\\\;\\r\\n smallTitleElement.innerText \\\這是一個紅色小標題\\\;\\r\\n\\r\\n let button document.getElementById(\\\myButton\\\);\\r\\n button.addEventListener(\\\click\\\, () \u003e {/* @cat-caption */\\r\\n smallTitleElement.style.color \\\blue\\\;/* @cat-caption */\\r\\n smallTitleElement.innerText \\\這是一個藍色小標題\\\;/* @cat-caption */\\r\\n });/* @cat-caption */\\r\\n \u003c/script\u003e\\r\\n\u003c/body\u003e\\r\\n\\r\\n\u003c/html\u003e\,\caption\:\第二步,創建事件 addEventListener \\n我們可以設定按鈕,當什麼動作發生時,就執行什麼指令\\n\\n以旁邊的例子來說變是,當點擊動作發生時\\n就執行將 小標題元素 顏色改成藍色,並改變文字\\n\\n\\\click\\\ 便是指點擊動作\\n動作都是規範好的,每一個都是關鍵字\\n不能自己蝦打,需要實際去搜尋看什麼動作對應什麼關鍵字\\n\,\language\:\html\,\image\:{\url\:\/api/object/1719242865832-ulg46.png\,\position\:\bottom-right\,\width\:301}},{\id\:\76dde\,\code\:\\u003c!DOCTYPE html\u003e\\r\\n\u003chtml\u003e\\r\\n\\r\\n\u003chead\u003e\\r\\n \u003ctitle\u003e我的網頁\u003c/title\u003e\\r\\n\u003c/head\u003e\\r\\n\\r\\n\u003cbody\u003e\\r\\n \u003ch1\u003e這是一個標題\u003c/h1\u003e\\r\\n \u003cdiv\u003e\\r\\n \u003ch2 id\\\smallTitle\\\\u003e這是一個小標題\u003c/h2\u003e\\r\\n \u003cp\u003e這是一段文字。\u003c/p\u003e\\r\\n \u003cbutton id\\\myButton\\\\u003e這是一個按鈕\u003c/button\u003e\\r\\n \u003cinput id\\\myInput\\\ type\\\text\\\ placeholder\\\這是一個輸入框\\\ /\u003e/* @cat-caption */\\r\\n \u003c/div\u003e\\r\\n \u003cscript\u003e\\r\\n let smallTitleElement document.getElementById(\\\smallTitle\\\);\\r\\n smallTitleElement.style.color \\\red\\\;\\r\\n smallTitleElement.innerText \\\這是一個紅色小標題\\\;\\r\\n\\r\\n let button document.getElementById(\\\myButton\\\);\\r\\n button.addEventListener(\\\click\\\, () \u003e {\\r\\n smallTitleElement.style.color \\\blue\\\;\\r\\n smallTitleElement.innerText \\\這是一個藍色小標題\\\;\\r\\n });\\r\\n \u003c/script\u003e\\r\\n\u003c/body\u003e\\r\\n\\r\\n\u003c/html\u003e\,\caption\:\最後來講一下讀取輸入框好了\\n我們創建一個輸入框\,\language\:\html\,\image\:{\url\:\/api/object/1719242888386-4p6t3k.png\,\position\:\bottom-right\,\width\:300}},{\id\:\is3ec9\,\code\:\\u003c!DOCTYPE html\u003e\\r\\n\u003chtml\u003e\\r\\n\\r\\n\u003chead\u003e\\r\\n \u003ctitle\u003e我的網頁\u003c/title\u003e\\r\\n\u003c/head\u003e\\r\\n\\r\\n\u003cbody\u003e\\r\\n \u003ch1\u003e這是一個標題\u003c/h1\u003e\\r\\n \u003cdiv\u003e\\r\\n \u003ch2 id\\\smallTitle\\\\u003e這是一個小標題\u003c/h2\u003e\\r\\n \u003cp\u003e這是一段文字。\u003c/p\u003e\\r\\n \u003cbutton id\\\myButton\\\\u003e這是一個按鈕\u003c/button\u003e\\r\\n \u003cinput id\\\myInput\\\ type\\\text\\\ placeholder\\\這是一個輸入框\\\ /\u003e\\r\\n \u003c/div\u003e\\r\\n \u003cscript\u003e\\r\\n let smallTitleElement document.getElementById(\\\smallTitle\\\);\\r\\n smallTitleElement.style.color \\\red\\\;\\r\\n smallTitleElement.innerText \\\這是一個紅色小標題\\\;\\r\\n\\r\\n let myInputElement document.getElementById(\\\myInput\\\);/* @cat-caption */\\r\\n\\r\\n let button document.getElementById(\\\myButton\\\);\\r\\n button.addEventListener(\\\click\\\, () \u003e {\\r\\n smallTitleElement.style.color \\\blue\\\;\\r\\n smallTitleElement.innerText \\\這是一個藍色小標題\\\;\\r\\n });\\r\\n \u003c/script\u003e\\r\\n\u003c/body\u003e\\r\\n\\r\\n\u003c/html\u003e\,\caption\:\一樣先讓程式碼能抓取該輸入框元素\,\language\:\html\,\image\:{\url\:\/api/object/1719242897496-0i567.png\,\position\:\bottom-right\,\width\:300}},{\id\:\sv57vu\,\code\:\\u003c!DOCTYPE html\u003e\\r\\n\u003chtml\u003e\\r\\n\\r\\n\u003chead\u003e\\r\\n \u003ctitle\u003e我的網頁\u003c/title\u003e\\r\\n\u003c/head\u003e\\r\\n\\r\\n\u003cbody\u003e\\r\\n \u003ch1\u003e這是一個標題\u003c/h1\u003e\\r\\n \u003cdiv\u003e\\r\\n \u003ch2 id\\\smallTitle\\\\u003e這是一個小標題\u003c/h2\u003e\\r\\n \u003cp\u003e這是一段文字。\u003c/p\u003e\\r\\n \u003cbutton id\\\myButton\\\\u003e這是一個按鈕\u003c/button\u003e\\r\\n \u003cinput id\\\myInput\\\ type\\\text\\\ placeholder\\\這是一個輸入框\\\ /\u003e\\r\\n \u003c/div\u003e\\r\\n \u003cscript\u003e\\r\\n let smallTitleElement document.getElementById(\\\smallTitle\\\);\\r\\n smallTitleElement.style.color \\\red\\\;\\r\\n smallTitleElement.innerText \\\這是一個紅色小標題\\\;\\r\\n\\r\\n let myInputElement document.getElementById(\\\myInput\\\);\\r\\n\\r\\n let button document.getElementById(\\\myButton\\\);\\r\\n button.addEventListener(\\\click\\\, () \u003e {\\r\\n let inputValue myInputElement.value;/* @cat-caption */\\r\\n smallTitleElement.innerText inputValue;/* @cat-caption */\\r\\n });\\r\\n \u003c/script\u003e\\r\\n\u003c/body\u003e\\r\\n\\r\\n\u003c/html\u003e\,\caption\:\我們修改一下剛剛按鈕的點擊命令\\n把它改成,如果被點擊時\\n1. 取得使用者的輸入數值\\n2. 把 smallTitleElement 顯示的文字變成該數值\,\language\:\html\,\image\:{\url\:\/api/object/1719242922424-vsnaab.png\,\position\:\bottom-right\,\width\:300}},{\id\:\jcjpy7\,\code\:\\u003c!DOCTYPE html\u003e\\r\\n\u003chtml\u003e\\r\\n\\r\\n\u003chead\u003e\\r\\n \u003ctitle\u003e我的網頁\u003c/title\u003e\\r\\n\u003c/head\u003e\\r\\n\\r\\n\u003cbody\u003e\\r\\n \u003ch1\u003e這是一個標題\u003c/h1\u003e\\r\\n \u003cdiv\u003e\\r\\n \u003ch2 id\\\smallTitle\\\\u003e這是一個小標題\u003c/h2\u003e\\r\\n \u003cp\u003e這是一段文字。\u003c/p\u003e\\r\\n \u003cbutton id\\\myButton\\\\u003e這是一個按鈕\u003c/button\u003e\\r\\n \u003cinput id\\\myInput\\\ type\\\text\\\ placeholder\\\這是一個輸入框\\\ /\u003e\\r\\n \u003c/div\u003e\\r\\n \u003cscript\u003e\\r\\n let smallTitleElement document.getElementById(\\\smallTitle\\\);\\r\\n smallTitleElement.style.color \\\red\\\;\\r\\n smallTitleElement.innerText \\\這是一個紅色小標題\\\;\\r\\n\\r\\n let myInputElement document.getElementById(\\\myInput\\\);\\r\\n\\r\\n let button document.getElementById(\\\myButton\\\);\\r\\n button.addEventListener(\\\click\\\, () \u003e {\\r\\n let inputValue myInputElement.value;\\r\\n smallTitleElement.innerText inputValue;\\r\\n myInputElement.value \\\\\\;/* @cat-caption */\\r\\n });\\r\\n \u003c/script\u003e\\r\\n\u003c/body\u003e\\r\\n\\r\\n\u003c/html\u003e\,\caption\:\當然你也可以把輸入框給清空,或修改成你想要的數值\,\language\:\html\,\image\:{\url\:\/api/object/1719242964114-4t1cim.png\,\position\:\bottom-right\,\width\:300}})/script>script>self.__next_f.push(1,43:Tbf1,)/script>script>self.__next_f.push(1,{\id\:\3vs4b\,\code\:\\u003c!DOCTYPE html\u003e\\r\\n\u003chtml\u003e\\r\\n\\r\\n\u003chead\u003e\\r\\n \u003ctitle\u003eCanvas 動畫\u003c/title\u003e\\r\\n\u003c/head\u003e\\r\\n\\r\\n\u003cbody\u003e\\r\\n \u003ccanvas id\\\myCanvas\\\ width\\\400\\\ height\\\400\\\\u003e\u003c/canvas\u003e/* @cat-caption */\\r\\n\u003c/body\u003e\\r\\n\\r\\n\u003c/html\u003e\,\caption\:\先替 Html 加上 canvas \\n你可以指定他的寬度跟高度\,\language\:\html\,\image\:{\url\:\/api/object/1719245856878-ia1ct.png\,\position\:\bottom-right\,\width\:200}},{\id\:\6ls6f\,\code\:\\u003c!DOCTYPE html\u003e\\r\\n\u003chtml\u003e\\r\\n \u003chead\u003e\\r\\n \u003ctitle\u003eCanvas 動畫\u003c/title\u003e\\r\\n \u003c/head\u003e\\r\\n \u003cbody\u003e\\r\\n \u003ccanvas id\\\myCanvas\\\ width\\\400\\\ height\\\400\\\\u003e\u003c/canvas\u003e\\r\\n \u003cscript\u003e\\r\\n const canvas document.getElementById(\\\myCanvas\\\);/* @cat-caption */\\r\\n \u003c/script\u003e\\r\\n \u003c/body\u003e\\r\\n\u003c/html\u003e\\r\\n\,\caption\:\先透過 getElementById 取得該元素\,\language\:\html\,\image\:{\url\:\/api/object/1719245854025-ebvg4l.png\,\position\:\bottom-right\,\width\:200}},{\id\:\g4ahzh\,\code\:\\u003c!DOCTYPE html\u003e\\r\\n\u003chtml\u003e\\r\\n\\r\\n\u003chead\u003e\\r\\n \u003ctitle\u003eCanvas 動畫\u003c/title\u003e\\r\\n\u003c/head\u003e\\r\\n\\r\\n\u003cbody\u003e\\r\\n \u003ccanvas id\\\myCanvas\\\ width\\\400\\\ height\\\400\\\\u003e\u003c/canvas\u003e\\r\\n \u003cscript\u003e\\r\\n const canvas document.getElementById(\\\myCanvas\\\);\\r\\n const ctx canvas.getContext(\\\2d\\\);/* @cat-caption */\\r\\n \u003c/script\u003e\\r\\n\u003c/body\u003e\\r\\n\\r\\n\u003c/html\u003e\,\caption\:\再來第二步,canvas 提供了一種名為 getContext(2d) 的方法,可以讓我們取得他的畫筆\\n如果要繪製這個畫布都必須透過這個畫筆\,\language\:\html\,\image\:{\url\:\/api/object/1719245851293-8yr6co.png\,\position\:\bottom-right\,\width\:200}},{\id\:\5y7lvm\,\code\:\\u003c!DOCTYPE html\u003e\\r\\n\u003chtml\u003e\\r\\n\\r\\n\u003chead\u003e\\r\\n \u003ctitle\u003eCanvas 動畫\u003c/title\u003e\\r\\n\u003c/head\u003e\\r\\n\\r\\n\u003cbody\u003e\\r\\n \u003ccanvas id\\\myCanvas\\\ width\\\400\\\ height\\\400\\\\u003e\u003c/canvas\u003e\\r\\n \u003cscript\u003e\\r\\n const canvas document.getElementById(\\\myCanvas\\\);\\r\\n const ctx canvas.getContext(\\\2d\\\);\\r\\n\\r\\n ctx.fillStyle \\\blue\\\;/* @cat-caption */\\r\\n \u003c/script\u003e\\r\\n\u003c/body\u003e\\r\\n\\r\\n\u003c/html\u003e\,\caption\:\畫筆的操作就如同小畫家一樣\\n只是把所有的動作用一行一行的程式碼表示出來\\n\\n首先先將畫筆的顏色變成藍色\,\language\:\html\,\image\:{\url\:\/api/object/1719245849110-dv2sal.png\,\position\:\bottom-right\,\width\:200}},{\id\:\wp3ll\,\code\:\\u003c!DOCTYPE html\u003e\\r\\n\u003chtml\u003e\\r\\n\\r\\n\u003chead\u003e\\r\\n \u003ctitle\u003eCanvas 動畫\u003c/title\u003e\\r\\n\u003c/head\u003e\\r\\n\\r\\n\u003cbody\u003e\\r\\n \u003ccanvas id\\\myCanvas\\\ width\\\400\\\ height\\\400\\\\u003e\u003c/canvas\u003e\\r\\n \u003cscript\u003e\\r\\n const canvas document.getElementById(\\\myCanvas\\\);\\r\\n const ctx canvas.getContext(\\\2d\\\);\\r\\n\\r\\n ctx.fillStyle \\\blue\\\;\\r\\n ctx.fillRect(50, 50, 150, 150);/* @cat-caption */\\r\\n \u003c/script\u003e\\r\\n\u003c/body\u003e\\r\\n\\r\\n\u003c/html\u003e\,\caption\:\再來,繪製一個正方形\\n他的格式長這樣\\nctx.fillRect(x, y, width, height)\\n\\n也就是從 (x,y) 這個座標點繪製一個寬 width 高 height 的正方形\,\language\:\html\,\image\:{\url\:\/api/object/1719245831280-yh584c.png\,\position\:\bottom-right\,\width\:200}})/script>script>self.__next_f.push(1,44:T89f,)/script>script>self.__next_f.push(1,{\id\:\8sk8ml\,\code\:\npm init -y\\r\\n\,\caption\:\建立新專案\\n\\n1. 在命令行中導航到你想要建立專案的目錄。\\n2. 輸入右邊命令來初始化專案\\n\\n這會自動生成一個 package.json 文件,這個文件用來記錄專案的配置信息和依賴。\\n\\n\,\language\:\plaintext\},{\id\:\7gl84a\,\code\:\console.log(\\\Hello World\\\);\,\caption\:\建立一個 index.js 檔案\\n裏頭會放置我們伺服器的程式碼\\n\\n你可以在終端機中輸入 node index.js\\n來直接運行這個程式碼\,\language\:\javascript\},{\id\:\czrz\,\code\:\function add(a, b){/* @cat-caption */\\r\\n return a + b;/* @cat-caption */\\r\\n}/* @cat-caption */\,\caption\:\Node.js 模組系統\\n\\nNode.js 有一個強大的模組系統,可以讓我們把程式碼分成不同的部分,然後需要的時候再引用。\\n\\n首先我們先創建第二個檔案叫做 add.js\,\language\:\javascript\},{\id\:\jxd4zq\,\code\:\export function add(a, b){/* @cat-caption */\\r\\n return a + b;\\r\\n}\,\caption\:\再來,如果我們希望可以讓其他檔案使用我們這個 add function \\n我們必須使用 export 關鍵字放在我們想開發的 function 前\\n\\n不只只是 function,變數阿,都是可以被導出的\\n\\n我更喜歡說這叫做共享程式碼\\n允許這個變數被別人共享\\n\,\language\:\javascript\},{\id\:\egemif\,\code\:\import { add } from \\\./add.js\\\;/* @cat-caption */\\r\\n\\r\\nconsole.log(add(1, 2)); // 3 /* @cat-caption */\,\caption\:\回到 index.js\\n我們可以透過 import 關鍵字導入我們剛剛共享出去的 function\,\language\:\javascript\},{\id\:\xuet4b\,\code\:\import http from \\\http\\\;\\r\\n\\r\\n// 我們等等會來解釋這段程式碼的功能\\r\\nconst server http.createServer((req, res) \u003e {\\r\\n res.statusCode 200; // 你\\r\\n res.setHeader(\\\Content-Type\\\, \\\text/plain\\\);\\r\\n res.end(\\\Hello World\\\\n\\\);\\r\\n});\\r\\n\\r\\nserver.listen(3000, () \u003e {\\r\\n console.log(\\\伺服器運行在 http://127.0.0.1:3000/\\\);\\r\\n});\\r\\n\,\caption\:\除了我們自己設計的模組外\\nNodeJs 幫我們設計了很多原生官方的實用模組\\n可以讓我們不用安裝的情況下直接 import 使用\,\language\:\javascript\})/script>script>self.__next_f.push(1,45:T205c,)/script>script>self.__next_f.push(1,{\id\:\lxid8sr\,\code\:\npm install express /* @cat-caption */\,\caption\:\首先,透過 npm 安裝 express 套件\,\language\:\plaintext\},{\id\:\df39ib\,\code\:\import http from \\\http\\\;\\nimport express from \\\express\\\;/* @cat-caption */\\n\\n// const server http.createServer((req, res) \u003e {\\n// res.statusCode 200; // 你\\n// res.setHeader(\\\Content-Type\\\, \\\text/plain\\\);\\n// res.end(\\\Hello World\\\\n\\\);\\n// });\\n\\n// server.listen(3000, () \u003e {\\n// console.log(\\\伺服器運行在 http://127.0.0.1:3000/\\\);\\n// });\\n\,\caption\:\安裝成功後,我們就能在我們的程式碼引入他了\\n\\n我們先將原本的 http server 註解掉\,\language\:\javascript\},{\id\:\pgvhhw\,\code\:\import http from \\\http\\\;\\nimport express from \\\express\\\;\\nconst app express();/* @cat-caption */\\n\\napp.get(/, (req, res) \u003e {/* @cat-caption */\\n res.send(Hello, World!);/* @cat-caption */\\n});/* @cat-caption */\\n\\napp.listen(3000, () \u003e {/* @cat-caption */\\n console.log(\\\伺服器運行在 http://127.0.0.1:3000/\\\);/* @cat-caption */\\n});/* @cat-caption */\\n\\n// const server http.createServer((req, res) \u003e {\\n// res.statusCode 200; // 你\\n// res.setHeader(\\\Content-Type\\\, \\\text/plain\\\);\\n// res.end(\\\Hello World\\\\n\\\);\\n// });\\n\\n// server.listen(3000, () \u003e {\\n// console.log(\\\伺服器運行在 http://127.0.0.1:3000/\\\);\\n// });\\n\,\caption\:\利用 express 創建一個伺服器\\n\,\language\:\javascript\},{\id\:\nt51pv\,\code\:\import http from \\\http\\\;\\nimport express from \\\express\\\;\\nconst app express();\\n\\napp.get(/, (req, res) \u003e {\\n res.send(Hello, World!);\\n});\\n\\napp.listen(3000, () \u003e {\\n console.log(\\\伺服器運行在 http://127.0.0.1:3000/\\\);\\n});\\n\\n// const server http.createServer((req, res) \u003e {/* @cat-caption */\\n// res.statusCode 200; // 你/* @cat-caption */\\n// res.setHeader(\\\Content-Type\\\, \\\text/plain\\\);/* @cat-caption */\\n// res.end(\\\Hello World\\\\n\\\);/* @cat-caption */\\n// });/* @cat-caption */\\n\\n// server.listen(3000, () \u003e {/* @cat-caption */\\n// console.log(\\\伺服器運行在 http://127.0.0.1:3000/\\\);/* @cat-caption */\\n// });/* @cat-caption */\\n\,\caption\:\注意看我們註解掉的程式碼(原生 Nodejs 的 http),跟 express 的程式碼\\n他們的功能是一模一樣的\\n只是 express 更加簡單易懂\\n所以接下來都會使用 express 來講解\,\language\:\javascript\},{\id\:\pao9n\,\code\:\import http from \\\http\\\;\\nimport express from \\\express\\\;\\nconst app express();\\n\\napp.get(/, (req, res) \u003e {/* @cat-caption */\\n res.send(Hello, World!);/* @cat-caption */\\n});/* @cat-caption */\\n\\napp.listen(3000, () \u003e {\\n console.log(\\\伺服器運行在 http://127.0.0.1:3000/\\\);\\n});\,\caption\:\右邊的程式碼的意思是\\n當有人向 \\\http://127.0.0.1:3000/\\\ 這個網址發起 GET 請求時,則回傳 Hello World\,\language\:\javascript\},{\id\:\pfuni\,\code\:\import http from \\\http\\\;\\nimport express from \\\express\\\;\\nconst app express();\\n\\napp.get(/, (req, res) \u003e {\\n res.send(Hello, World!);\\n});\\n\\napp.get(/cat, (req, res) \u003e {/* @cat-caption */\\n res.send(Cat is Cute!);/* @cat-caption */\\n});/* @cat-caption */\\n\\napp.listen(3000, () \u003e {\\n console.log(\\\伺服器運行在 http://127.0.0.1:3000/\\\);\\n});\,\caption\:\右邊的程式碼的意思是\\n當有人向 \\\http://127.0.0.1:3000/cat\\\ 這個網址發起 GET 請求時,則回傳 Cat is Cute!\,\language\:\javascript\},{\id\:\82x7l8\,\code\:\import http from \\\http\\\;\\nimport express from \\\express\\\;\\nconst app express();\\n\\napp.get(/, (req, res) \u003e {\\n res.send(Hello, World!);\\n});\\n\\napp.get(/cat, (req, res) \u003e {\\n res.send(Cat is Cute!);\\n});\\n\\napp.post(/cat, (req, res) \u003e {/* @cat-caption */\\n res.send(Create A Cat!);/* @cat-caption */\\n});/* @cat-caption */\\n\\napp.listen(3000, () \u003e {\\n console.log(\\\伺服器運行在 http://127.0.0.1:3000/\\\);\\n});\,\caption\:\右邊的程式碼的意思是\\n當有人向 \\\http://127.0.0.1:3000/cat\\\ 這個網址發起 POST 請求時,則回傳 Create A Cat!\,\language\:\javascript\},{\id\:\k2ilzr\,\code\:\import http from \\\http\\\;\\nimport express from \\\express\\\;\\nconst app express();\\n\\n// 創建一個陣列來儲存貓貓\\nlet cats ;/* @cat-caption */\\n\\napp.get(/cat, (req, res) \u003e {/* @cat-caption */\\n /** 回傳貓貓的資料 *//* @cat-caption */\\n});/* @cat-caption */\\n\\napp.post(/cat, (req, res) \u003e {/* @cat-caption */\\n /** 接收貓貓的資料 *//* @cat-caption */\\n});/* @cat-caption */\\n\\napp.listen(3000, () \u003e {\\n console.log(\\\伺服器運行在 http://127.0.0.1:3000/\\\);\\n});\,\caption\:\我們帶一點實際例子吧\\n設計一個貓貓名稱儲存器\\n用戶可以新增貓貓,並且得所有的貓貓ㄇ\,\language\:\javascript\},{\id\:\pslk7q\,\code\:\import http from \\\http\\\;\\nimport express from \\\express\\\;\\nimport cors from \\\cors\\\;/* @cat-caption */\\nconst app express();\\napp.use(cors());/* @cat-caption */\\n\\n// 創建一個陣列來儲存貓貓\\nlet cats ;\\n\\napp.get(/cat, (req, res) \u003e {\\n /** 回傳貓貓的資料 */\\n});\\n\\napp.post(/cat, (req, res) \u003e {\\n /** 接收貓貓的資料 */\\n});\\n\\napp.listen(3000, () \u003e {\\n console.log(\\\伺服器運行在 http://127.0.0.1:3000/\\\);\\n});\,\caption\:\首先,Express 伺服器有安全保護\\n他不允許任何人都可以來訪問我\\n我們先安裝 cors 這個套件\\n暫時卸除一下這個安全保護\\nnpm i cors\\n\,\language\:\javascript\},{\id\:\8uxg2q\,\code\:\import http from \\\http\\\;\\nimport express from \\\express\\\;\\nimport cors from \\\cors\\\;/* @cat-caption */\\nconst app express();\\napp.use(cors());/* @cat-caption */\\napp.use(express.json()); // 解析 JSON 格式的請求/* @cat-caption */\\n\\n// 創建一個陣列來儲存貓貓\\nlet cats ;\\n\\napp.get(/cat, (req, res) \u003e {\\n /** 回傳貓貓的資料 */\\n});\\n\\napp.post(/cat, (req, res) \u003e {\\n /** 接收貓貓的資料 */\\n});\\n\\napp.listen(3000, () \u003e {\\n console.log(\\\伺服器運行在 http://127.0.0.1:3000/\\\);\\n});\,\caption\:\再來,因為我們前端要傳送 json 物件的資料給 express\\n設定一下,讓 express 能理解 json 格式長怎樣\,\language\:\javascript\},{\id\:\85sare\,\code\:\import express from \\\express\\\;\\nimport cors from \\\cors\\\;\\n\\nconst app express();\\napp.use(cors());\\napp.use(express.json()); // 解析 JSON 格式的請求\\n\\n// 創建一個陣列來儲存貓貓\\nlet cats ;\\n\\napp.get(\\\/cat\\\, (req, res) \u003e {\\n res.send(JSON.stringify(cats));/* @cat-caption */\\n});\\n\\napp.post(\\\/cat\\\, (req, res) \u003e {\\n cats.push(req.body.cat);/* @cat-caption */\\n res.send(\\\successfully add cat\\\);/* @cat-caption */\\n});\\n\\napp.listen(3000, () \u003e {\\n console.log(\\\伺服器運行在 http://127.0.0.1:3001/\\\);\\n});\\n\,\caption\:\開始實作細節啦\,\language\:\javascript\},{\id\:\9izb0z\,\code\:\// 這裡是前端的程式碼\\nconst response await fetch(\\\http://127.0.0.1:3001/cat\\\, {\\n method: \\\POST\\\,\\n headers: {\\n Content-Type: application/json // 告訴 express,我送過去的資料格式,是 json 格式\\n },\\n body: JSON.stringify({cat:test}), // 請注意,送過去的資料必須先透過 JSON.stringify 轉成字串才能傳送\\n \\n}) // 以 GET 方式發送請求\\n\\nconst data await response.text(); // 將取得的資料以純文字解析\\nconsole.log(data) // 印出來\,\caption\:\我們來看一下前端 JavaScript 要怎樣發送請求比較好\\n請在瀏覽器的 F12 試試看\\n右邊是 前端 向 後端請求取得貓貓的資料\,\language\:\javascript\},{\id\:\5p6vb\,\code\:\// 這裡是前端的程式碼\\nconst response await fetch(\\\http://127.0.0.1:3001/cat\\\, {\\n method: \\\POST\\\,\\n headers: {\\n Content-Type: application/json // 告訴 express,我送過去的資料格式,是 json 格式\\n },\\n body: JSON.stringify({cat:test}), // 請注意,送過去的資料必須先透過 JSON.stringify 轉成字串才能傳送\\n \\n}) // 以 GET 方式發送請求\\n\\nconst data await response.text(); // 將取得的資料以純文字解析\\nconsole.log(data) // 印出來\,\caption\:\右邊是 前端 向 後端請求新增貓貓的資料\,\language\:\javascript\})/script>script>self.__next_f.push(1,46:T1e93,)/script>script>self.__next_f.push(1,{\id\:\8vttlba\,\code\:\npm install mongoose\\n\,\caption\:\為了在 Node.js 中使用 MongoDB,我們將使用 Mongoose,它是一個方便的工具,可以讓我們輕鬆地與 MongoDB 進行互動。\\n\,\language\:\plaintext\},{\id\:\evs48p\,\code\:\import mongoose from mongoose;\\n\\nmongoose.connect(mongodb://localhost:27017/restaurant, {\\n useNewUrlParser: true,\\n useUnifiedTopology: true\\n});\\n\\nconst db mongoose.connection;\\ndb.on(error, console.error.bind(console, MongoDB 連接錯誤:));\\ndb.once(open, () \u003e {\\n console.log(已成功連接到 MongoDB);\\n});\,\caption\:\連接到 MongoDB\\n首先,我們需要連接到 MongoDB:\,\language\:\javascript\},{\id\:\ystg9\,\code\:\import mongoose from mongoose;\\n\\nmongoose.connect(mongodb://localhost:27017/restaurant, {\\n useNewUrlParser: true,\\n useUnifiedTopology: true\\n});\\n\\nconst db mongoose.connection;\\ndb.on(error, console.error.bind(console, MongoDB 連接錯誤:));\\ndb.once(open, () \u003e {\\n console.log(已成功連接到 MongoDB);\\n});\\n\\nconst catSchema new mongoose.Schema({/* @cat-caption */\\n name: String,/* @cat-caption */\\n age: Number/* @cat-caption */\\n});/* @cat-caption */\\n\\nconst Cat mongoose.model(Cat, catSchema);/* @cat-caption */\,\caption\:\定義資料模型\\n接下來,我們需要定義要儲存的資料模型,也就是他的格式該長怎樣:\,\language\:\javascript\},{\id\:\x77916\,\code\:\import express from express;\\nimport cors from cors;\\nimport mongoose from mongoose;\\n\\nconst app express();\\napp.use(cors());\\napp.use(express.json()); // 解析 JSON 格式的請求\\n\\n// 連接到 MongoDB\\nmongoose.connect(mongodb://localhost:27017/restaurant, {\\n useNewUrlParser: true,\\n useUnifiedTopology: true\\n});\\n\\nconst db mongoose.connection;\\ndb.on(error, console.error.bind(console, MongoDB 連接錯誤:));\\ndb.once(open, () \u003e {\\n console.log(已成功連接到 MongoDB);\\n});\\n\\n// 定義資料模型\\nconst catSchema new mongoose.Schema({\\n name: String,\\n age: Number\\n});\\n\\nconst Cat mongoose.model(Cat, catSchema);\\n\\n// 查看所有貓咪\\napp.get(/cat, async (req, res) \u003e {\\n try {\\n const cats await Cat.find();\\n res.json(cats);\\n } catch (err) {\\n res.status(500).json({ message: err.message });\\n }\\n});\\n\\n// 新增一隻貓咪\\napp.post(/cat, async (req, res) \u003e {\\n const newCat new Cat({\\n name: req.body.name,\\n age: req.body.age\\n });\\n\\n try {\\n const savedCat await newCat.save();\\n res.status(201).json(savedCat); // 201 表示資源已創建\\n } catch (err) {\\n res.status(400).json({ message: err.message });\\n }\\n});\\n\\napp.listen(3000, () \u003e {\\n console.log(伺服器運行在 /);\\n});\\n\,\caption\:\我們融合之前的程式碼,不要讓貓貓直接儲存在陣列裡頭\\n而是儲存在 mongodb 中\,\language\:\javascript\},{\id\:\283atr\,\code\:\import express from express;\\nimport cors from cors;\\nimport mongoose from mongoose;\\n\\nconst app express();\\napp.use(cors());\\napp.use(express.json()); // 解析 JSON 格式的請求\\n\\n// 連接到 MongoDB\\nmongoose.connect(mongodb://localhost:27017/restaurant, {\\n useNewUrlParser: true,\\n useUnifiedTopology: true\\n});\\n\\nconst db mongoose.connection;\\ndb.on(error, console.error.bind(console, MongoDB 連接錯誤:));\\ndb.once(open, () \u003e {\\n console.log(已成功連接到 MongoDB);\\n});\\n\\n// 定義資料模型\\nconst catSchema new mongoose.Schema({\\n name: String,\\n age: Number\\n});\\n\\nconst Cat mongoose.model(Cat, catSchema);\\n\\n// 查看所有貓咪\\napp.get(/cat, async (req, res) \u003e {\\n try {\\n const cats await Cat.find();\\n res.json(cats);\\n } catch (err) {\\n res.status(500).json({ message: err.message });\\n }\\n});\\n\\n// 新增一隻貓咪\\napp.post(/cat, async (req, res) \u003e {\\n const newCat new Cat({\\n name: req.body.name,\\n age: req.body.age\\n });\\n\\n try {\\n const savedCat await newCat.save();\\n res.status(201).json(savedCat); // 201 表示資源已創建\\n } catch (err) {\\n res.status(400).json({ message: err.message });\\n }\\n});\\n\\n// 修改貓咪的名字\\napp.put(/cat, async (req, res) \u003e {/* @cat-caption */\\n const { originalName, newName } req.body;/* @cat-caption */\\n\\n try {/* @cat-caption */\\n const updatedCat await Cat.findOneAndUpdate(/* @cat-caption */\\n { name: originalName },/* @cat-caption */\\n { name: newName },/* @cat-caption */\\n { new: true }/* @cat-caption */\\n );/* @cat-caption */\\n if (updatedCat) {/* @cat-caption */\\n res.json(updatedCat);/* @cat-caption */\\n } else {/* @cat-caption */\\n res.status(404).json({ message: 找不到該名稱的貓咪 });/* @cat-caption */\\n }/* @cat-caption */\\n } catch (err) {/* @cat-caption */\\n res.status(400).json({ message: err.message });/* @cat-caption */\\n }/* @cat-caption */\\n});/* @cat-caption */\\n\\napp.listen(3000, () \u003e {\\n console.log(伺服器運行在 /);\\n});\\n\,\caption\:\當然 mongodb 還支援修改 update\\n\\n我們可以新增一個修改貓咪名稱的功能,允許傳入原本貓咪的名字和新名字來更新資料庫中的資料。\,\language\:\javascript\},{\id\:\6ypaw4\,\code\:\import express from express;\\nimport cors from cors;\\nimport mongoose from mongoose;\\n\\nconst app express();\\napp.use(cors());\\napp.use(express.json()); // 解析 JSON 格式的請求\\n\\n// 連接到 MongoDB\\nmongoose.connect(mongodb://localhost:27017/restaurant, {\\n useNewUrlParser: true,\\n useUnifiedTopology: true\\n});\\n\\nconst db mongoose.connection;\\ndb.on(error, console.error.bind(console, MongoDB 連接錯誤:));\\ndb.once(open, () \u003e {\\n console.log(已成功連接到 MongoDB);\\n});\\n\\n// 定義資料模型\\nconst catSchema new mongoose.Schema({\\n name: String,\\n age: Number\\n});\\n\\nconst Cat mongoose.model(Cat, catSchema);\\n\\n// 查看所有貓咪\\napp.get(/cat, async (req, res) \u003e {\\n try {\\n const cats await Cat.find();\\n res.json(cats);\\n } catch (err) {\\n res.status(500).json({ message: err.message });\\n }\\n});\\n\\n// 新增一隻貓咪\\napp.post(/cat, async (req, res) \u003e {\\n const newCat new Cat({\\n name: req.body.name,\\n age: req.body.age\\n });\\n\\n try {\\n const savedCat await newCat.save();\\n res.status(201).json(savedCat); // 201 表示資源已創建\\n } catch (err) {\\n res.status(400).json({ message: err.message });\\n }\\n});\\n\\n// 修改貓咪的名字\\napp.put(/cat, async (req, res) \u003e {\\n const { originalName, newName } req.body;\\n\\n try {\\n const updatedCat await Cat.findOneAndUpdate(\\n { name: originalName },\\n { name: newName },\\n { new: true }\\n );\\n if (updatedCat) {\\n res.json(updatedCat);\\n } else {\\n res.status(404).json({ message: 找不到該名稱的貓咪 });\\n }\\n } catch (err) {\\n res.status(400).json({ message: err.message });\\n }\\n});\\n\\n// 刪除某個名字的貓咪\\napp.delete(/cat, async (req, res) \u003e {/* @cat-caption */\\n const { name } req.body;/* @cat-caption */\\n\\n try {/* @cat-caption */\\n const deletedCat await Cat.findOneAndDelete({ name: name });/* @cat-caption */\\n if (deletedCat) {/* @cat-caption */\\n res.json({ message: `貓咪 ${name} 已成功刪除` });/* @cat-caption */\\n } else {/* @cat-caption */\\n res.status(404).json({ message: 找不到該名稱的貓咪 });/* @cat-caption */\\n }/* @cat-caption */\\n } catch (err) {/* @cat-caption */\\n res.status(500).json({ message: err.message });/* @cat-caption */\\n }/* @cat-caption */\\n});/* @cat-caption */\\n\\napp.listen(3000, () \u003e {\\n console.log(伺服器運行在 /);\\n});\\n\,\caption\:\最後再增加一個刪除貓貓的功能\,\language\:\javascript\})/script>script>self.__next_f.push(1,47:T4e4,)/script>script>self.__next_f.push(1,import express from express;\nimport cors from cors;\nimport mongoose from mongoose;\n\nconst app express();\napp.use(cors());\napp.use(express.json()); // 解析 JSON 格式的請求\n\n// 連接到 MongoDB\nmongoose.connect(mongodb://localhost:27017/restaurant, {\n useNewUrlParser: true,\n useUnifiedTopology: true\n});\n\nconst db mongoose.connection;\ndb.on(error, console.error.bind(console, MongoDB 連接錯誤:));\ndb.once(open, () \u003e {\n console.log(已成功連接到 MongoDB);\n});\n\n// 定義資料模型\nconst catSchema new mongoose.Schema({\n name: String,\n age: Number\n});\n\nconst Cat mongoose.model(Cat, catSchema);\n\n// 查看所有貓咪\napp.get(/cat, async (req, res) \u003e {\n try {\n const cats await Cat.find();\n res.json(cats);\n } catch (err) {\n res.status(500).json({ message: err.message });\n }\n});\n\n// 新增一隻貓咪\napp.post(/cat, async (req, res) \u003e {\n const newCat new Cat({\n name: req.body.name,\n age: req.body.age\n });\n\n try {\n const savedCat await newCat.save();\n res.status(201).json(savedCat); // 201 表示資源已創建\n } catch (err) {\n res.status(400).json({ message: err.message });\n }\n});\n\napp.listen(3000, () \u003e {\n console.log(伺服器運行在 /);\n});\n)/script>script>self.__next_f.push(1,48:T7ed,)/script>script>self.__next_f.push(1,import express from express;\nimport cors from cors;\nimport mongoose from mongoose;\n\nconst app express();\napp.use(cors());\napp.use(express.json()); // 解析 JSON 格式的請求\n\n// 連接到 MongoDB\nmongoose.connect(mongodb://localhost:27017/restaurant, {\n useNewUrlParser: true,\n useUnifiedTopology: true\n});\n\nconst db mongoose.connection;\ndb.on(error, console.error.bind(console, MongoDB 連接錯誤:));\ndb.once(open, () \u003e {\n console.log(已成功連接到 MongoDB);\n});\n\n// 定義資料模型\nconst catSchema new mongoose.Schema({\n name: String,\n age: Number\n});\n\nconst Cat mongoose.model(Cat, catSchema);\n\n// 查看所有貓咪\napp.get(/cat, async (req, res) \u003e {\n try {\n const cats await Cat.find();\n res.json(cats);\n } catch (err) {\n res.status(500).json({ message: err.message });\n }\n});\n\n// 新增一隻貓咪\napp.post(/cat, async (req, res) \u003e {\n const newCat new Cat({\n name: req.body.name,\n age: req.body.age\n });\n\n try {\n const savedCat await newCat.save();\n res.status(201).json(savedCat); // 201 表示資源已創建\n } catch (err) {\n res.status(400).json({ message: err.message });\n }\n});\n\n// 修改貓咪的名字\napp.put(/cat, async (req, res) \u003e {/* @cat-caption */\n const { originalName, newName } req.body;/* @cat-caption */\n\n try {/* @cat-caption */\n const updatedCat await Cat.findOneAndUpdate(/* @cat-caption */\n { name: originalName },/* @cat-caption */\n { name: newName },/* @cat-caption */\n { new: true }/* @cat-caption */\n );/* @cat-caption */\n if (updatedCat) {/* @cat-caption */\n res.json(updatedCat);/* @cat-caption */\n } else {/* @cat-caption */\n res.status(404).json({ message: 找不到該名稱的貓咪 });/* @cat-caption */\n }/* @cat-caption */\n } catch (err) {/* @cat-caption */\n res.status(400).json({ message: err.message });/* @cat-caption */\n }/* @cat-caption */\n});/* @cat-caption */\n\napp.listen(3000, () \u003e {\n console.log(伺服器運行在 /);\n});\n)/script>script>self.__next_f.push(1,49:T955,)/script>script>self.__next_f.push(1,import express from express;\nimport cors from cors;\nimport mongoose from mongoose;\n\nconst app express();\napp.use(cors());\napp.use(express.json()); // 解析 JSON 格式的請求\n\n// 連接到 MongoDB\nmongoose.connect(mongodb://localhost:27017/restaurant, {\n useNewUrlParser: true,\n useUnifiedTopology: true\n});\n\nconst db mongoose.connection;\ndb.on(error, console.error.bind(console, MongoDB 連接錯誤:));\ndb.once(open, () \u003e {\n console.log(已成功連接到 MongoDB);\n});\n\n// 定義資料模型\nconst catSchema new mongoose.Schema({\n name: String,\n age: Number\n});\n\nconst Cat mongoose.model(Cat, catSchema);\n\n// 查看所有貓咪\napp.get(/cat, async (req, res) \u003e {\n try {\n const cats await Cat.find();\n res.json(cats);\n } catch (err) {\n res.status(500).json({ message: err.message });\n }\n});\n\n// 新增一隻貓咪\napp.post(/cat, async (req, res) \u003e {\n const newCat new Cat({\n name: req.body.name,\n age: req.body.age\n });\n\n try {\n const savedCat await newCat.save();\n res.status(201).json(savedCat); // 201 表示資源已創建\n } catch (err) {\n res.status(400).json({ message: err.message });\n }\n});\n\n// 修改貓咪的名字\napp.put(/cat, async (req, res) \u003e {\n const { originalName, newName } req.body;\n\n try {\n const updatedCat await Cat.findOneAndUpdate(\n { name: originalName },\n { name: newName },\n { new: true }\n );\n if (updatedCat) {\n res.json(updatedCat);\n } else {\n res.status(404).json({ message: 找不到該名稱的貓咪 });\n }\n } catch (err) {\n res.status(400).json({ message: err.message });\n }\n});\n\n// 刪除某個名字的貓咪\napp.delete(/cat, async (req, res) \u003e {/* @cat-caption */\n const { name } req.body;/* @cat-caption */\n\n try {/* @cat-caption */\n const deletedCat await Cat.findOneAndDelete({ name: name });/* @cat-caption */\n if (deletedCat) {/* @cat-caption */\n res.json({ message: `貓咪 ${name} 已成功刪除` });/* @cat-caption */\n } else {/* @cat-caption */\n res.status(404).json({ message: 找不到該名稱的貓咪 });/* @cat-caption */\n }/* @cat-caption */\n } catch (err) {/* @cat-caption */\n res.status(500).json({ message: err.message });/* @cat-caption */\n }/* @cat-caption */\n});/* @cat-caption */\n\napp.listen(3000, () \u003e {\n console.log(伺服器運行在 /);\n});\n)/script>script>self.__next_f.push(1,4a:Ta30,)/script>script>self.__next_f.push(1,{\id\:\8getr\,\code\:\import express from express;\\nimport http from http;\\nimport { Server } from socket.io;\\n\\nconst app express();\\nconst server http.createServer(app);\\nconst io new Server(server);\\n\\napp.get(/, (req, res) \u003e {\\n res.sendFile(__dirname + /index.html);\\n});\\n\\nio.on(connection, (socket) \u003e {\\n console.log(一位使用者已連接);\\n\\n socket.on(chat message, (msg) \u003e {\\n console.log(訊息: + msg);\\n io.emit(chat message, msg);\\n });\\n\\n socket.on(disconnect, () \u003e {\\n console.log(一位使用者已離開);\\n });\\n});\\n\\nserver.listen(3000, () \u003e {\\n console.log(伺服器運行在 );\\n});\\n\,\caption\:\建立 Socket.IO 伺服器\\n接下來,我們將使用 Express 和 Socket.IO 建立一個簡單的即時通訊伺服器。\,\language\:\javascript\},{\id\:\owqlid\,\code\:\\u003c!DOCTYPE html\u003e\\n\u003chtml\u003e\\n \u003chead\u003e\\n \u003ctitle\u003e即時聊天\u003c/title\u003e\\n \u003cstyle\u003e\\n ul {\\n list-style-type: none;\\n padding: 0;\\n }\\n li {\\n padding: 8px;\\n margin-bottom: 10px;\\n background-color: #f3f3f3;\\n border-radius: 4px;\\n }\\n #messages {\\n max-width: 300px;\\n margin: auto;\\n }\\n #form {\\n display: flex;\\n justify-content: center;\\n margin-top: 10px;\\n }\\n #input {\\n width: 200px;\\n padding: 10px;\\n }\\n #button {\\n padding: 10px;\\n }\\n \u003c/style\u003e\\n \u003c/head\u003e\\n \u003cbody\u003e\\n \u003cul id\\\messages\\\\u003e\u003c/ul\u003e\\n \u003cform id\\\form\\\ action\\\\\\\u003e\\n \u003cinput id\\\input\\\ autocomplete\\\off\\\ /\u003e\u003cbutton id\\\button\\\\u003e送出\u003c/button\u003e\\n \u003c/form\u003e\\n \u003cscript src\\\/socket.io/socket.io.js\\\\u003e\u003c/script\u003e\\n \u003cscript\u003e\\n var socket io();\\n var form document.getElementById(form);\\n var input document.getElementById(input);\\n var messages document.getElementById(messages);\\n\\n form.addEventListener(submit, function (e) {\\n e.preventDefault();\\n if (input.value) {\\n socket.emit(chat message, input.value);\\n input.value ;\\n }\\n });\\n\\n socket.on(chat message, function (msg) {\\n var item document.createElement(li);\\n item.textContent msg;\\n messages.appendChild(item);\\n window.scrollTo(0, document.body.scrollHeight);\\n });\\n \u003c/script\u003e\\n \u003c/body\u003e\\n\u003c/html\u003e\\n\,\caption\:\客戶端程式碼\\n在這個示例中,我們需要一個簡單的 HTML 文件來與伺服器進行互動。創建一個 index.html 文件,並添加以下內容\,\language\:\html\})/script>script>self.__next_f.push(1,4b:T5c8,)/script>script>self.__next_f.push(1,\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\n \u003chead\u003e\n \u003ctitle\u003e即時聊天\u003c/title\u003e\n \u003cstyle\u003e\n ul {\n list-style-type: none;\n padding: 0;\n }\n li {\n padding: 8px;\n margin-bottom: 10px;\n background-color: #f3f3f3;\n border-radius: 4px;\n }\n #messages {\n max-width: 300px;\n margin: auto;\n }\n #form {\n display: flex;\n justify-content: center;\n margin-top: 10px;\n }\n #input {\n width: 200px;\n padding: 10px;\n }\n #button {\n padding: 10px;\n }\n \u003c/style\u003e\n \u003c/head\u003e\n \u003cbody\u003e\n \u003cul id\messages\\u003e\u003c/ul\u003e\n \u003cform id\form\ action\\\u003e\n \u003cinput id\input\ autocomplete\off\ /\u003e\u003cbutton id\button\\u003e送出\u003c/button\u003e\n \u003c/form\u003e\n \u003cscript src\/socket.io/socket.io.js\\u003e\u003c/script\u003e\n \u003cscript\u003e\n var socket io();\n var form document.getElementById(form);\n var input document.getElementById(input);\n var messages document.getElementById(messages);\n\n form.addEventListener(submit, function (e) {\n e.preventDefault();\n if (input.value) {\n socket.emit(chat message, input.value);\n input.value ;\n }\n });\n\n socket.on(chat message, function (msg) {\n var item document.createElement(li);\n item.textContent msg;\n messages.appendChild(item);\n window.scrollTo(0, document.body.scrollHeight);\n });\n \u003c/script\u003e\n \u003c/body\u003e\n\u003c/html\u003e\n)/script>script>self.__next_f.push(1,4c:T674,)/script>script>self.__next_f.push(1,{\id\:\xkzjg\,\code\:\let myBox;\,\caption\:\宣告一個盒子:\\n就像我們先準備一個盒子一樣\\n\\n在程式裡,我們要先透過 let 關鍵字\\n告訴電腦我們要一個盒子。這就叫做“宣告變數”。\,\language\:\javascript\},{\id\:\z852lb\,\code\:\let myBox;\,\caption\:\給盒子取名字:\\n我們給這個盒子取名叫 myBox。這樣我們以後就可以用 myBox 這個名字來找這個盒子。\,\language\:\javascript\},{\id\:\q7hm5\,\code\:\let myBox;\\r\\nmyBox 10; // 把數字 10 放進盒子/* @cat-caption */\\r\\nmyBox \\\Hello\\\; // 把字串 \\\Hello\\\ 放進盒子,原本盒子內的 10 會直接消失掉喔!/* @cat-caption */\,\caption\:\往盒子裡放東西:\\n我們可以把一些東西放進這個盒子裡,比如一個數字或一個字串。\,\language\:\javascript\},{\id\:\zamhq\,\code\:\let myBox;\\r\\nmyBox 10; // 把數字 10 放進盒子\\r\\nmyBox \\\Hello\\\; // 把字串 \\\Hello\\\ 放進盒子,原本盒子內的 10 會直接消失掉喔!\\r\\nconsole.log(myBox); // 顯示盒子裡的東西,這裡會顯示 \\\Hello\\\/* @cat-caption */\\r\\n\,\caption\:\使用盒子裡的東西:\\n當我們需要用到這些東西時,只需要拿出這個盒子來用就行了。\,\language\:\javascript\},{\id\:\p6znj9\,\code\:\let name \\\Ray\\\; // 這是裝名字的盒子\\r\\nlet age 20; // 這是裝年齡的盒子\\r\\n\\r\\nconsole.log(name); // 顯示名字,\\\Ray\\\\\r\\nconsole.log(age); // 顯示年齡,20\\r\\n\,\caption\:\再舉一個例子\\n假設我們要記住一個人的名字和年齡,我們可以用兩個變數來做到這件事:\,\language\:\javascript\})/script>script>self.__next_f.push(1,4d:Td52,)/script>script>self.__next_f.push(1,{\id\:\t3uuve\,\code\:\let myString \\\Hello, world!\\\;\\r\\nconsole.log(myString); // 顯示 \\\Hello, world!\\\\\r\\n\,\caption\:\字串(String)\\n字串是一串文字,可以是字母、數字、符號等,通常用引號括起來。就像一本書中的文字內容。\,\language\:\javascript\},{\id\:\uuu281\,\code\:\let myNumber 42;\\r\\nconsole.log(myNumber); // 顯示 42\\r\\n\\r\\nlet myDecimal 3.14;\\r\\nconsole.log(myDecimal); // 顯示 3.14\\r\\n\,\caption\:\數字(Number)\\n數字可以是整數或小數。數字可以用來做數學運算,就像我們在算數學題目一樣。\,\language\:\javascript\},{\id\:\iyf93\,\code\:\let isStudent true;\\r\\nconsole.log(isStudent); // 顯示 true\\r\\n\\r\\nlet hasGraduated false;\\r\\nconsole.log(hasGraduated); // 顯示 false\\r\\n\,\caption\:\布林值(Boolean)\\n布林值只有兩種:true(真)或 false(假)。這種資料型別通常用來做判斷,就像我們在問“是”或“不是”的問題。\,\language\:\javascript\},{\id\:\u5b4k\,\code\:\let colors \\\red\\\, \\\green\\\, \\\blue\\\; // 裏頭一次放入三個字串 \,\caption\:\陣列(Array)\\n陣列就像排隊的隊伍,可以放入很多資料。\,\language\:\javascript\},{\id\:\0tfse\,\code\:\let colors \\\red\\\, \\\green\\\, \\\blue\\\;\\r\\nconsole.log(colors0); // 取得第 0 個元素的內容,顯示 \\\red\\\\\r\\n\\r\\ncolors0 \\\orange\\\ // 修改第 0 個元素的內容\\r\\nconsole.log(colors0); // 重新取得第 0 個元素的內容,顯示 \\\orange\\\\,\caption\:\陣列(Array)\\n我們當然可以透過 變數名稱位置 來讀取或修改內容\\n值得一提的是, JavaScript 或者說大部分的程式語言\\n陣列都是從 0 開始計算\,\language\:\javascript\},{\id\:\cmu30s\,\code\:\let person {\\r\\n name: \\\Ray\\\,\\r\\n age: 20,\\r\\n isStudent: true\\r\\n};\,\caption\:\物件(Object)\\n物件是一種可以存放多個相關資料的結構。\\n可以想像成一個包含很多小盒子的盒子。\\n當然物件中的每個小盒子都需要有獨立的名字\,\language\:\javascript\},{\id\:\nmnrtm\,\code\:\let person {\\r\\n name: \\\Ray\\\,\\r\\n age: 20,\\r\\n isStudent: true\\r\\n};\\r\\n\\r\\nconsole.log(person.name); // 顯示 \\\Ray\\\ /* @cat-caption */\\r\\nconsole.log(person.age); // 顯示 20 /* @cat-caption */\\r\\nconsole.log(person.isStudent); // 顯示 true /* @cat-caption */\\r\\n\\r\\nperson.age 21; // 更新 age 為 21 /* @cat-caption */\\r\\nconsole.log(person.age); // 顯示 21 /* @cat-caption */\,\caption\:\透過 變數名稱.小盒子的名字 能單獨讀取小盒子的內容\,\language\:\javascript\},{\id\:\zhe45\,\code\:\let person {\\r\\n name: \\\Ray\\\,\\r\\n age: 20,\\r\\n isStudent: true\\r\\n};\\r\\n\\r\\nconsole.log(person.name); // 顯示 \\\Ray\\\\\r\\nconsole.log(person.age); // 顯示 20\\r\\nconsole.log(person.isStudent); // 顯示 true\\r\\n\\r\\nperson.age 21; // 更新 age 為 21\\r\\nconsole.log(person.age); // 顯示 21\\r\\n\\r\\npersonlikeCat true; // 新增 likeCat 為 true /* @cat-caption */\\r\\nconsole.log(person.likeCat); // 顯示 true /* @cat-caption */\\r\\n\\r\\n/*\\r\\n此時 person 內長這樣,likeCat 被放進去\\r\\n{\\r\\n name: \\\Ray\\\,\\r\\n age: 20,\\r\\n isStudent: true,\\r\\n likeCat: true\\r\\n};\\r\\n */\,\caption\:\如果我們要塞入新的小盒子\\n可以透過變數名稱新的小盒子名稱來創造新的小盒子\,\language\:\javascript\})/script>script>self.__next_f.push(1,4e:T70c,)/script>script>self.__next_f.push(1,{\id\:\hsbrtb\,\code\:\let isRaining true;\\r\\n\\r\\nif (isRaining) {\\r\\n console.log(\\\記得帶傘\\\); // 顯示 \\\記得帶傘\\\\\r\\n}\\r\\n\\r\\n// 這裡我們問:“如果正在下雨?” 如果答案是“是”,那我們就提醒自己要帶雨傘。\,\caption\:\if 語句\\nif 語句就像在問問題:\\n“如果這個條件是對的,那麼我們就做這件事。” \\n\\n比如說如果今天是下雨天,我們就要帶雨傘。\,\language\:\typescript\},{\id\:\qqy6go\,\code\:\let grade 85;\\r\\n\\r\\nif (grade \u003e 60) {\\r\\n console.log(\\\你及格了\\\); // 顯示 \\\你及格了\\\\\r\\n} else {\\r\\n console.log(\\\你沒及格 QQ\\\); // 顯示 \\\你沒及格 QQ\\\\\r\\n}\\r\\n\\r\\n\\r\\n// 最後會輸出 \\\你及格了\\\\,\caption\:\if...else 語句\\nif...else 語句就像在問兩個問題:\\n如果這個條件是對的,我們就做這件事;\\n如果不是,我們就做另外一件事。\\n\\n 比如在學校,如果考試及格,我們就可以玩;\\n如果不及格,我們就要多學習。\,\language\:\typescript\},{\id\:\i84kcr\,\code\:\let temperature 25;\\r\\n\\r\\nif (temperature \u003e 30) { // 如果溫度大於 30 度\\r\\n console.log(\\\超級熱\\\); // 顯示 超級熱\\r\\n} else if (temperature \u003e 26) { // 如果上面的條件沒達成,就會繼續往下問,如果溫度大於 26 度\\r\\n console.log(\\\好熱\\\); // 顯示 好熱\\r\\n} else if (temperature \u003e 20) {\\r\\n console.log(\\\溫度剛剛好\\\); // 顯示 溫度剛剛好\\r\\n} else {\\r\\n console.log(\\\好冷\\\); // 顯示 好冷\\r\\n}\\r\\n\\r\\n// 最後顯示 溫度剛剛好\,\caption\:\if...else if...else 語句\\nif...else if...else 語句可以處理更多的條件,就像在問一連串的問題。\\n\\n比如,我們在家裡根據溫度來決定穿什麼衣服。\,\language\:\typescript\})/script>script>self.__next_f.push(1,4f:T73b,)/script>script>self.__next_f.push(1,{\id\:\0y8gof\,\code\:\let age 20;\\r\\nlet isStudent true;\\r\\n\\r\\nif (age \u003e 18 \u0026\u0026 isStudent) {\\r\\n console.log(\\\你是一個年滿 18 歲的學生\\\); // 顯示 \\\你是一個年滿 18 歲的學生\\\\\r\\n}\\r\\n\\r\\n\\r\\n\\r\\nif (age \u003e 25 \u0026\u0026 isStudent) {\\r\\n console.log(\\\你是一個年滿 25 歲的學生\\\); // 有一個條件不滿足,不顯示\\r\\n}\\r\\n\\r\\n\,\caption\:\and(\u0026\u0026):這個運算符用來判斷兩個條件是否都達成。如果兩個條件都達成,整個表達式才能算是達成。\,\language\:\typescript\},{\id\:\el6wtj\,\code\:\let age 16;\\r\\nlet hasPermission true;\\r\\n\\r\\nif (age \u003e 18 || hasPermission) {\\r\\n console.log(\\\你可以參加活動\\\); // 顯示 \\\你可以參加活動\\\\\r\\n}\,\caption\:\or(||):這個運算符用來判斷兩個條件中是否有一個達成。如果有一個條件達成,整個表達式就為真。\,\language\:\typescript\},{\id\:\yfuc0p\,\code\:\let age 6;\\r\\n\\r\\nswitch (true) {\\r\\n case (age \u003e 0 \u0026\u0026 age \u003c 5):\\r\\n console.log(\\\你可以免費入場!\\\); // 顯示 \\\你可以免費入場!\\\\\r\\n break;\\r\\n case (age \u003e 6 \u0026\u0026 age \u003c 12):\\r\\n console.log(\\\你可以買兒童票。\\\); // 顯示 \\\你可以買兒童票。\\\\\r\\n break;\\r\\n case (age \u003e 13 \u0026\u0026 age \u003c 18):\\r\\n console.log(\\\你可以買學生票。\\\); // 顯示 \\\你可以買學生票。\\\\\r\\n break;\\r\\n default:\\r\\n console.log(\\\你可以買普通票。\\\); // 顯示 \\\你可以買普通票。\\\\\r\\n}\\r\\n\\r\\n// 你可以買兒童票。!\,\caption\:\如果今天有一堆的問題跟情況\\n除了 if...else if...else 語句\\n我們有一個更簡單的語法\\n\\nswitch 語句\\nswitch 語句就像是多選一的題目,根據不同的情況選擇要做什麼。比如在遊樂園,不同的票價對應不同的年齡段。\,\language\:\typescript\})/script>script>self.__next_f.push(1,50:T8d9,)/script>script>self.__next_f.push(1,{\id\:\zs09sn\,\code\:\for (let i 0; i \u003c 5; i++) {\\r\\n console.log(\\\這是第 \\\ + i + \\\ 次\\\);\\r\\n}\\r\\n\,\caption\:\for 迴圈\\nfor 迴圈就像是在數數,我們可以告訴電腦從哪個數字開始,數到哪個數字結束,每次數多少。這樣我們就可以讓電腦做很多次相同的事情。\,\language\:\typescript\},{\id\:\7wws7m\,\code\:\for (let i 0; i \u003c 5; i++) { /* @cat-caption */\\r\\n console.log(\\\這是第 \\\ + i + \\\ 次\\\);\\r\\n}\\r\\n\\r\\n// 這是第 0 次\\r\\n// 這是第 1 次\\r\\n// 這是第 2 次\\r\\n// 這是第 3 次\\r\\n// 這是第 4 次\,\caption\:\這裡我們告訴電腦:\\n\\n從 0 開始(let i 0)。\\n停止條件,如果 i 比 5 小那就繼續執行下去(i \u003c 5)。\\n每次執行完後做的事,次數 1(i++,每次把 i 加 1)。\,\language\:\typescript\},{\id\:\fvzncs\,\code\:\let i 0;\\r\\n\\r\\nwhile (i \u003c 5) {\\r\\n console.log(\\\這是第 \\\ + i + \\\ 次\\\);\\r\\n i++;\\r\\n}\\r\\n\\r\\n// 這是第 0 次\\r\\n// 這是第 1 次\\r\\n// 這是第 2 次\\r\\n// 這是第 3 次\\r\\n// 這是第 4 次\,\caption\:\while 迴圈\\nwhile 迴圈就像是在問問題,只要答案是“是”,電腦就會一直重複做這件事。當答案變成“不是”時,電腦就會停止。\,\language\:\typescript\},{\id\:\bzjprd\,\code\:\let i 0;\\r\\n\\r\\nwhile (i \u003c 5) {\\r\\n console.log(\\\這是第 \\\ + i + \\\ 次\\\);\\r\\n i++;\\r\\n}\\r\\n\\r\\n// 這是第 0 次\\r\\n// 這是第 1 次\\r\\n// 這是第 2 次\\r\\n// 這是第 3 次\\r\\n// 這是第 4 次\,\caption\:\這裡我們告訴電腦:\\n\\n開始的時候 i 是 0(let i 0)\\n停止條件,如果 i 比 5 小那就繼續執行下去(i \u003c 5)。\\n每次執行完後做的事,次數 1(i++,每次把 i 加 1)。\,\language\:\typescript\},{\id\:\zayf7\,\code\:\let i 0;\\r\\n\\r\\ndo {\\r\\n console.log(\\\這是第 \\\ + i + \\\ 次\\\);\\r\\n i++;\\r\\n} while (i \u003c 5);\\r\\n\\r\\n// 這是第 0 次\\r\\n// 這是第 1 次\\r\\n// 這是第 2 次\\r\\n// 這是第 3 次\\r\\n// 這是第 4 次\,\caption\:\do...while 迴圈\\ndo...while 迴圈有點像 while 迴圈,但是它會先做一次,然後再問問題。如果答案是“是”,就繼續做;\\n如果答案是“不是”,就停止。\\n可以理解成先斬後奏\,\language\:\typescript\})/script>script>self.__next_f.push(1,51:Tc14,)/script>script>self.__next_f.push(1,{\id\:\1c4of\,\code\:\function sayHello() {\\r\\n console.log(\\\Hello World\\\);\\r\\n}\\r\\n\,\caption\:\定義函數\\n我們先來看看怎麼定義一個函數。定義函數就是告訴電腦這個功能盒子裡面有什麼指令。\,\language\:\javascript\},{\id\:\xq7szt\,\code\:\function sayHello() {\\r\\n console.log(\\\Hello World\\\);\\r\\n}\\r\\n\,\caption\:\\\n函數的定義與調用\\n函數就像是一個魔法盒子,我們可以把一系列的指令放進這個盒子裡,給它取一個名字,然後以後只要叫這個名字,就可以讓這些指令執行。\\n\\n定義函數\\n我們先來看看怎麼定義一個函數。定義函數就是告訴電腦這個魔法盒子裡面有什麼指令。\\n\\n例子:\\n\\njavascript\\n複製程式碼\\nfunction sayHello() {\\n console.log(\\\你好,世界!\\\);\\n}\\n這裡我們做了什麼:\\n\\n用 function 關鍵字開始:\\n這告訴電腦我們要定義一個函數。\\n給函數取名字:這裡我們叫它 sayHello。\\n\\n在大括號 {} 內寫指令:\\n這些指令就是我們要放進魔法盒子裡的東西。\,\language\:\javascript\},{\id\:\b6hyqs\,\code\:\function sayHello() {\\r\\n console.log(\\\Hello World\\\);\\r\\n}\\r\\n\\r\\nsayHello(); \\r\\n\,\caption\:\調用函數\\n現在我們已經有了這個功能盒子(函數),我們可以用它來執行裡面的指令。這叫做“調用函數”。\\n\\n只要在之後呼叫函數名稱() 就會視同調用函數\\n請注意要加上括號 () 才會執行裡面的內容\,\language\:\javascript\},{\id\:\ztpk6l\,\code\:\function greet(name) { /* @cat-caption */\\r\\n console.log(\\\你好,\\\ + name + \\\!\\\);\\r\\n}\\r\\n\,\caption\:\帶參數的函數\\n我們也可以讓函數更靈活,給它一些“參數”,這樣我們可以傳不同的東西給它,它會根據我們給的東西來做不同的事情。\,\language\:\javascript\},{\id\:\dqdqvue\,\code\:\function greet(name) { /* @cat-caption */\\r\\n console.log(\\\你好,\\\ + name + \\\!\\\);\\r\\n}\\r\\n\\r\\n\\r\\ngreet(\\\小明\\\); // 你好,小明!\\r\\ngreet(\\\小紅\\\); // 你好,小紅!\\r\\n\,\caption\:\這裡我們做了什麼:\\n\\n1. 給函數一個參數 name:\\n這個 name 就像是一個盒子,我們可以把名字放進去。\\n\\n2. 在指令裡使用這個參數:\\n我們把 name 和 “你好,” 拼在一起,這樣每次傳不同的名字,函數就會顯示不同的問候語。\\n\\n調用這個函數時,我們可以傳一個名字給它。\,\language\:\javascript\},{\id\:\yogvj\,\code\:\function add(a, b) {\\r\\n return a + b;\\r\\n}\\r\\n\,\caption\:\帶返回值的函數\\n函數還可以把一些結果返回給我們,就像把魔法盒子打開,看看裡面變出了什麼東西。\,\language\:\javascript\},{\id\:\ryelbe\,\code\:\let sum add(3, 4);\\r\\nconsole.log(sum); // 顯示 7\\r\\n\,\caption\:\這裡我們做了什麼:\\n\\n定義函數 add,可以傳入兩個參數 a 和 b。\\n使用 return 關鍵字:這告訴電腦我們要把 a + b 的結果返回出來。\\n\\n調用這個函數時,我們可以得到計算的結果。\,\language\:\javascript\})/script>script>self.__next_f.push(1,52:Td31,)/script>script>self.__next_f.push(1,{\id\:\cs6y7\,\code\:\\u003cp style\\\color: blue;\\\\u003e這是一段藍色文字。\u003c/p\u003e\\r\\n\,\caption\:\內聯樣式 (Inline Styles): 直接在 HTML 標籤中使用 style 屬性。\\n\\n\,\language\:\html\,\image\:{\url\:\/api/object/1723117032251-jt64.png\,\position\:\bottom-right\,\width\:200}},{\id\:\0t2o3\,\code\:\\u003chead\u003e\\r\\n \u003cstyle\u003e\\r\\n p { color: blue; }\\r\\n \u003c/style\u003e\\r\\n\u003c/head\u003e\\r\\n\\r\\n\u003cbody\u003e\\r\\n \u003cp\u003e這是一段藍色文字。\u003c/p\u003e\\r\\n\u003c/body\u003e\\r\\n\,\caption\:\內部樣式 (Internal Styles): 在 HTML 文件的 \u003chead\u003e 區域中使用 \u003cstyle\u003e 標籤。\\n\\n\,\language\:\html\,\image\:{\url\:\/api/object/1723117053167-wihbm.png\,\position\:\bottom-right\,\width\:200}},{\id\:\jc5h88\,\code\:\// styles.css,這是另一個檔案\\r\\np {\\r\\n color: blue;\\r\\n}\\r\\n\\r\\n\\r\\n// index.html,這是主檔案\\r\\n\u003chead\u003e\\r\\n \u003clink rel\\\stylesheet\\\ type\\\text/css\\\ href\\\styles.css\\\\u003e\\r\\n\u003c/head\u003e\\r\\n\\r\\n\u003cbody\u003e\\r\\n \u003cp\u003e這是一段藍色文字。\u003c/p\u003e\\r\\n\u003c/body\u003e\\r\\n\,\caption\:\外部樣式 (External Styles): 使用外部 CSS 文件,並在 HTML 文件中通過 \u003clink\u003e 標籤引用。\\n\\n\,\language\:\html\,\image\:{\url\:\/api/object/1723117066220-kql5ne.png\,\position\:\bottom-right\,\width\:200}},{\id\:\7aonzm\,\code\:\p {\\r\\n color: blue;\\r\\n}\\r\\n\,\caption\:\CSS 的語法很簡單,分成兩個部分\\n目標 與 效果\\n以右邊的程式碼為例\\n目標是網頁中所有的 p 元素\\n效果是變成藍色文字\\n\\n另外目標的專有名詞是 選擇器 (selector) \\n而效果的專有名詞是 宣告塊 (declaration block) \,\language\:\css\,\image\:{\url\:\/api/object/1723117103212-qqkkl.png\,\position\:\bottom-right\,\width\:200}},{\id\:\8wg9em\,\code\:\p {\\r\\n color: blue;\\r\\n font-size: 24px;\\r\\n}\\r\\n\,\caption\:\這段 CSS 語法會將所有 \u003cp\u003e 元素的文字顏色設為藍色,字體大小設為 16 像素。\\n\\n\,\language\:\css\,\image\:{\url\:\/api/object/1723117170105-kintt.png\,\position\:\bottom-right\,\width\:300}},{\id\:\urkv59\,\code\:\\u003c!DOCTYPE html\u003e\\r\\n\u003chtml lang\\\en\\\\u003e\\r\\n \u003chead\u003e\\r\\n \u003cstyle\u003e\\r\\n .blue-text {\\r\\n color: blue;\\r\\n font-size: 24px;\\r\\n }\\r\\n \u003c/style\u003e\\r\\n \u003c/head\u003e\\r\\n \u003cbody\u003e\\r\\n \u003cp\u003e這是一段一般的文字\u003c/p\u003e\\r\\n \u003cp class\\\blue-text\\\\u003e這是一段藍色的文字\u003c/p\u003e\\r\\n \u003c/body\u003e\\r\\n\u003c/html\u003e\\r\\n\,\caption\:\除了一個一個定義元素的樣式外,或者批量全部修改外\\n也可以使用 class 類別\\n類別(class) 就像是幫元素貼上一個「分類」,可以重複使用。\\n所以被貼上該分類的元素,都會取得該分類的效果\\n\\n類別只能在 style 中宣告,或者在獨立的 css 檔案中宣告\\n\,\language\:\html\,\image\:{\url\:\/api/object/1723117185012-b9chaa.png\,\position\:\bottom-right\,\width\:300}},{\id\:\nwss3\,\code\:\.blue-text {\\r\\n color: blue;\\r\\n font-size: 24px;\\r\\n}\,\caption\:\css 中的寫法是句點「.」+「類別名稱」\\n\\n類別名稱基本上能自由定義\\n(不過用詞上只能使用英文與 - )\,\language\:\css\},{\id\:\q3w1w3\,\code\:\\u003cp class\\\blue-text\\\\u003e這是一段藍色的文字\u003c/p\u003e\,\caption\:\html 中的寫法則是替需要指定元素中的屬性加上 class\\nclass\\\類別名稱\\\\\n注意 html 的類別不要加點「.」\,\language\:\html\,\image\:{\url\:\/api/object/1723117238792-wms508.png\,\position\:\bottom-right\,\width\:300}})/script>script>self.__next_f.push(1,53:T16c8,)/script>script>self.__next_f.push(1,{\id\:\s4sl5j\,\code\:\p {\\r\\n color: red;\\r\\n}\\r\\n\,\caption\:\color: 設定文字顏色。\,\language\:\css\,\image\:{\url\:\/api/object/1723117576669-xq56sf.png\,\position\:\bottom-right\,\width\:200}},{\id\:\7d17h\,\code\:\p {\\r\\n color: red;\\r\\n font-size: 32px;\\r\\n}\\r\\n\,\caption\:\font-size: 設定文字大小。\\n\\n\,\language\:\css\,\image\:{\url\:\/api/object/1723117588039-5unhsq.png\,\position\:\bottom-right\,\width\:400}},{\id\:\rm6hvq\,\code\:\p {\\r\\n color: red;\\r\\n font-size: 32px;\\r\\n text-align: center;\\r\\n}\\r\\n\,\caption\:\text-align: 設定文字對齊方式。\,\language\:\css\,\image\:{\url\:\/api/object/1723117614317-oi35bb.png\,\position\:\bottom-right\,\width\:400}},{\id\:\j896as\,\code\:\p {\\r\\n color: red;\\r\\n font-size: 32px;\\r\\n text-align: right;\\r\\n}\\r\\n\,\caption\:\我們可以試試看置右\,\language\:\css\,\image\:{\url\:\/api/object/1723117644360-pbkcn.png\,\position\:\bottom-right\,\width\:400}},{\id\:\9kass\,\code\:\p {\\r\\n color: red;\\r\\n font-size: 32px;\\r\\n text-align: right;\\r\\n background-color: lightgray;\\r\\n}\\r\\n\,\caption\:\background-color: 設定背景顏色。\\n\\n\,\language\:\css\,\image\:{\url\:\/api/object/1723117666158-1uniod.png\,\position\:\bottom-right\,\width\:400}},{\id\:\6iv7e9\,\code\:\p {\\r\\n color: red;\\r\\n font-size: 32px;\\r\\n text-align: right;\\r\\n background-image: url(example.jpg);\\r\\n}\\r\\n\,\caption\:\background-image: 設定背景圖片。\\n\\n\,\language\:\css\,\image\:{\url\:\/api/object/1723117837284-wsllgr.png\,\position\:\bottom-right\,\width\:400}},{\id\:\f5iii\,\code\:\p {\\r\\n color: red;\\r\\n font-size: 32px;\\r\\n text-align: right;\\r\\n background-image: url(example.jpg);\\r\\n border: 10px solid black;\\r\\n}\\r\\n\,\caption\:\border: 設定元素的邊框。\\n\\n\,\language\:\css\,\image\:{\url\:\/api/object/1723117882397-8lgm2k.png\,\position\:\bottom-right\,\width\:400}},{\id\:\cz8azd\,\code\:\p {\\r\\n color: red;\\r\\n font-size: 32px;\\r\\n text-align: right;\\r\\n background-image: url(example.jpg);\\r\\n border: 1px solid black;\\r\\n border-radius: 50%;\\r\\n}\\r\\n\,\caption\:\border-radius: 圓邊\,\language\:\css\,\image\:{\url\:\/api/object/1723117940011-czy74s.png\,\position\:\bottom-right\,\width\:398}},{\id\:\04h93n\,\code\:\p {\\r\\n color: red;\\r\\n font-size: 32px;\\r\\n text-align: right;\\r\\n background-image: url(example.jpg);\\r\\n border: 1px solid black;\\r\\n border-radius: 50%;\\r\\n padding: 128px;\\r\\n}\\r\\n\,\caption\:\目前你會發現文字距離邊界太近了\\n這是我們可以設定 padding\\n他可以限制內容與邊框的距離(內距)\,\language\:\css\,\image\:{\url\:\/api/object/1723117999467-89ht1.png\,\position\:\bottom-right\,\width\:400}},{\id\:\04fv9m\,\code\:\\u003c!DOCTYPE html\u003e\\r\\n\u003chtml lang\\\en\\\\u003e\\r\\n \u003chead\u003e\\r\\n \u003cmeta charset\\\UTF-8\\\ /\u003e\\r\\n \u003cmeta name\\\viewport\\\ content\\\widthdevice-width, initial-scale1.0\\\ /\u003e\\r\\n \u003ctitle\u003eDocument\u003c/title\u003e\\r\\n \u003cstyle\u003e\\r\\n p {\\r\\n color: red;\\r\\n font-size: 32px;\\r\\n text-align: right;\\r\\n background-image: url(\\\example.jpg\\\);\\r\\n border: 1px solid black;\\r\\n border-radius: 50%;\\r\\n padding: 128px;\\r\\n }\\r\\n \u003c/style\u003e\\r\\n \u003c/head\u003e\\r\\n \u003cbody\u003e\\r\\n \u003cp\u003e這是一個大貓貓\u003c/p\u003e\\r\\n \u003cp\u003e這是一個大貓貓\u003c/p\u003e\\r\\n \u003c/body\u003e\\r\\n\u003c/html\u003e\\r\\n\,\caption\:\我們來創造第二個元素看看\\n我們順利創造了兩張老人圖!\,\language\:\html\,\image\:{\url\:\/api/object/1723118035393-s70m8f.png\,\position\:\bottom-right\,\width\:400}},{\id\:\cywdmj\,\code\:\\u003c!DOCTYPE html\u003e\\r\\n\u003chtml lang\\\en\\\\u003e\\r\\n \u003chead\u003e\\r\\n \u003cmeta charset\\\UTF-8\\\ /\u003e\\r\\n \u003cmeta name\\\viewport\\\ content\\\widthdevice-width, initial-scale1.0\\\ /\u003e\\r\\n \u003ctitle\u003eDocument\u003c/title\u003e\\r\\n \u003cstyle\u003e\\r\\n p {\\r\\n color: red;\\r\\n font-size: 32px;\\r\\n text-align: right;\\r\\n background-image: url(\\\example.jpg\\\);\\r\\n border: 1px solid black;\\r\\n border-radius: 50%;\\r\\n padding: 128px;\\r\\n margin: 128px;\\r\\n }\\r\\n \u003c/style\u003e\\r\\n \u003c/head\u003e\\r\\n \u003cbody\u003e\\r\\n \u003cp\u003e這是一個大貓貓\u003c/p\u003e\\r\\n \u003cp\u003e這是一個大貓貓\u003c/p\u003e\\r\\n \u003c/body\u003e\\r\\n\u003c/html\u003e\\r\\n\,\caption\:\我們可以透過 margin 調整元素與其他元素之間的距離(外距)\\n你可以想像成設定其他元素與我之間的社交距離\,\language\:\html\,\image\:{\url\:\/api/object/1723118078642-aqsk0a.png\,\position\:\bottom-right\,\width\:400}},{\id\:\0mapyo\,\code\:\\u003c!DOCTYPE html\u003e\\r\\n\u003chtml lang\\\en\\\\u003e\\r\\n \u003chead\u003e\\r\\n \u003cmeta charset\\\UTF-8\\\ /\u003e\\r\\n \u003cmeta name\\\viewport\\\ content\\\widthdevice-width, initial-scale1.0\\\ /\u003e\\r\\n \u003ctitle\u003eDocument\u003c/title\u003e\\r\\n \u003cstyle\u003e\\r\\n p {\\r\\n height: 200px; /* @cat-caption */\\r\\n background-color: lightgray; \\r\\n }\\r\\n \u003c/style\u003e\\r\\n \u003c/head\u003e\\r\\n \u003cbody\u003e\\r\\n \u003cp\u003e這是一個大貓貓\u003c/p\u003e\\r\\n \u003cp\u003e這是一個大貓貓\u003c/p\u003e\\r\\n \u003c/body\u003e\\r\\n\u003c/html\u003e\\r\\n\,\caption\:\順帶一提,你可以設定元素的高度\,\language\:\html\,\image\:{\url\:\/api/object/1723118180803-7z90j.png\,\position\:\bottom-right\,\width\:400}},{\id\:\f9u7bf\,\code\:\\u003c!DOCTYPE html\u003e\\r\\n\u003chtml lang\\\en\\\\u003e\\r\\n \u003chead\u003e\\r\\n \u003cmeta charset\\\UTF-8\\\ /\u003e\\r\\n \u003cmeta name\\\viewport\\\ content\\\widthdevice-width, initial-scale1.0\\\ /\u003e\\r\\n \u003ctitle\u003eDocument\u003c/title\u003e\\r\\n \u003cstyle\u003e\\r\\n p {\\r\\n height: 200px; \\r\\n width: 200px; /* @cat-caption */\\r\\n }\\r\\n \u003c/style\u003e\\r\\n \u003c/head\u003e\\r\\n \u003cbody\u003e\\r\\n \u003cp\u003e這是一個大貓貓\u003c/p\u003e\\r\\n \u003cp\u003e這是一個大貓貓\u003c/p\u003e\\r\\n \u003c/body\u003e\\r\\n\u003c/html\u003e\\r\\n\,\caption\:\設定元素的寬度\,\language\:\html\,\image\:{\url\:\/api/object/1723118246368-r26dth.png\,\position\:\bottom-right\,\width\:200}})/script>script>self.__next_f.push(1,54:T3a68,)/script>script>self.__next_f.push(1,{\id\:\oq5mp8\,\code\:\\u003cp\u003e這是一個大貓貓\u003c/p\u003e\\r\\n\u003cp style\\\display: block\\\\u003e大家默認都是 block\u003c/p\u003e\\r\\n\u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\\r\\n\u003cp\u003e這是一個大貓貓\u003c/p\u003e\,\caption\:\正常排版下\\n一個區塊會占滿完整一行\\ndiv、p 這些元素都是一個一個獨立區塊(block)\\n\\n那如果我想做到同一行內放好幾個 div 呢?\,\language\:\html\,\image\:{\url\:\/api/object/1719179891638-hbendu.png\,\position\:\top-right\,\width\:200}},{\id\:\adg7vq\,\code\:\\u003cp\u003e這是一個大貓貓\u003c/p\u003e\\r\\n\u003cp style\\\display: block\\\\u003e大家默認都是 block\u003c/p\u003e\\r\\n\u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\\r\\n\u003cp\u003e這是一個大貓貓\u003c/p\u003e\\r\\n\u003cdiv\u003e/* @cat-caption */\\r\\n \u003cdiv style\\\display: inline\\\\u003e這是一個大貓貓\u003c/div\u003e/* @cat-caption */\\r\\n \u003cdiv style\\\display: inline\\\\u003e這是一個大貓貓\u003c/div\u003e/* @cat-caption */\\r\\n \u003cdiv style\\\display: inline\\\\u003e這是一個大貓貓\u003c/div\u003e/* @cat-caption */\\r\\n\u003c/div\u003e/* @cat-caption */\,\caption\:\這邊就可以修改元素顯示方式 display\\n將其改成 inline\\ninline 的顯示方式讓 div 不用占滿一整行\,\language\:\html\,\image\:{\url\:\/api/object/1719179917981-0nq7wk.png\,\position\:\top-right\,\width\:200}},{\id\:\ksn9mme\,\code\:\\u003cp\u003e這是一個大貓貓\u003c/p\u003e\\r\\n\u003cp style\\\display: block\\\\u003e大家默認都是 block\u003c/p\u003e\\r\\n\u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\\r\\n\u003cp\u003e這是一個大貓貓\u003c/p\u003e\\r\\n\u003cdiv\u003e\\r\\n \u003cdiv style\\\display: inline\\\\u003e這是一個大貓貓\u003c/div\u003e\\r\\n \u003cdiv style\\\display: inline\\\\u003e這是一個大貓貓\u003c/div\u003e\\r\\n \u003cdiv style\\\display: inline\\\\u003e這是一個大貓貓\u003c/div\u003e\\r\\n\u003c/div\u003e\,\caption\:\那如果我們想做到更複雜的呢?\\n比如說能夠置右呢?\\n或比如說能夠平均分散呢?\,\language\:\html\,\image\:{\url\:\/api/object/1719179931563-f5qlp8.png\,\position\:\top-right\,\width\:200}},{\id\:\2f7dsb\,\code\:\\u003cp\u003e這是一個大貓貓\u003c/p\u003e\\r\\n\u003cp style\\\display: block\\\\u003e大家默認都是 block\u003c/p\u003e\\r\\n\u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\\r\\n\u003cp\u003e這是一個大貓貓\u003c/p\u003e\\r\\n\u003cdiv style\\\display: flex;\\\\u003e /* @cat-caption */\\r\\n \u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\\r\\n \u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\\r\\n \u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\\r\\n\u003c/div\u003e\,\caption\:\display:flex (彈性盒子)\\n可以算是 css 數一數二重要的屬性\\n正如他的名字所說,它可以讓元素的排列充滿彈性\\n\\n值得注意的是 flex 控制的是元素小孩們的排列\\n默認情況下,它會讓他的小孩全部在同一行,哪怕他的小孩是一個 block\\n\,\language\:\html\,\image\:{\url\:\/api/object/1719180024755-ha1878.png\,\position\:\bottom-right\,\width\:600}},{\id\:\hpoh7h\,\code\:\\u003cp\u003e這是一個大貓貓\u003c/p\u003e\\r\\n\u003cp style\\\display: block\\\\u003e大家默認都是 block\u003c/p\u003e\\r\\n\u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\\r\\n\u003cp\u003e這是一個大貓貓\u003c/p\u003e\\r\\n\u003cdiv style\\\display: flex;\\\\u003e\\r\\n \u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\\r\\n \u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\\r\\n \u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\\r\\n\u003c/div\u003e\\r\\n\u003cdiv\u003e這是一個大貓貓\u003c/div\u003e /* @cat-caption */\,\caption\:\而就是這個元素本身還是會占用一行喔,這點要特別注意\,\language\:\html\,\image\:{\url\:\/api/object/1719180044292-2dj6w.png\,\position\:\bottom-right\,\width\:600}},{\id\:\stnnwj\,\code\:\\u003cp\u003e這是一個大貓貓\u003c/p\u003e\\r\\n\u003cp style\\\display: block\\\\u003e大家默認都是 block\u003c/p\u003e\\r\\n\u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\\r\\n\u003cp\u003e這是一個大貓貓\u003c/p\u003e\\r\\n\u003cdiv style\\\display: flex;justify-content: end\\\\u003e/* @cat-caption */\\r\\n \u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\\r\\n \u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\\r\\n \u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\\r\\n\u003c/div\u003e\\r\\n\u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\,\caption\:\justify-content\\n透過第二個 css 屬性,我們可以設定小孩們怎麼排列\\njustify-content: end 將小孩全部推到末端(右側)\,\language\:\html\,\image\:{\url\:\/api/object/1719179115993-3uxirp.png\,\position\:\bottom-right\,\width\:600}},{\id\:\m8kumya\,\code\:\\u003cp\u003e這是一個大貓貓\u003c/p\u003e\\r\\n\u003cp style\\\display: block\\\\u003e大家默認都是 block\u003c/p\u003e\\r\\n\u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\\r\\n\u003cp\u003e這是一個大貓貓\u003c/p\u003e\\r\\n\u003cdiv style\\\display: flex;justify-content: start\\\\u003e/* @cat-caption */\\r\\n \u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\\r\\n \u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\\r\\n \u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\\r\\n\u003c/div\u003e\\r\\n\u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\,\caption\:\justify-content: start 將小孩全部推到開始端(左側)\,\language\:\html\,\image\:{\url\:\/api/object/1719180087332-zuzhbl.png\,\position\:\bottom-right\,\width\:600}},{\id\:\awcqba\,\code\:\\u003cp\u003e這是一個大貓貓\u003c/p\u003e\\r\\n\u003cp style\\\display: block\\\\u003e大家默認都是 block\u003c/p\u003e\\r\\n\u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\\r\\n\u003cp\u003e這是一個大貓貓\u003c/p\u003e\\r\\n\u003cdiv style\\\display: flex;justify-content: space-between\\\\u003e/* @cat-caption */\\r\\n \u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\\r\\n \u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\\r\\n \u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\\r\\n\u003c/div\u003e\\r\\n\u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\,\caption\:\justify-content: space-between \\n將小孩平均分散在整行中\,\language\:\html\,\image\:{\url\:\/api/object/1719179382978-ryyi89.png\,\position\:\bottom-right\,\width\:600}},{\id\:\d33go\,\code\:\\u003cp\u003e這是一個大貓貓\u003c/p\u003e\\r\\n\u003cp style\\\display: block\\\\u003e大家默認都是 block\u003c/p\u003e\\r\\n\u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\\r\\n\u003cp\u003e這是一個大貓貓\u003c/p\u003e\\r\\n\u003cdiv style\\\display: flex; justify-content: space-evenly\\\\u003e/* @cat-caption */\\r\\n \u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\\r\\n \u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\\r\\n \u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\\r\\n\u003c/div\u003e\\r\\n\u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\,\caption\:\justify-content: space-evenly\\n將小孩平均分散在整行中\\n但是最前面跟最後面會保留空間\,\language\:\html\,\image\:{\url\:\/api/object/1719179439198-gkztzq.png\,\position\:\bottom-right\,\width\:601}},{\id\:\oao76t\,\code\:\\u003cp\u003e這是一個大貓貓\u003c/p\u003e\\r\\n\u003cp style\\\display: block\\\\u003e大家默認都是 block\u003c/p\u003e\\r\\n\u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\\r\\n\u003cp\u003e這是一個大貓貓\u003c/p\u003e\\r\\n\u003cdiv/* @cat-caption */\\r\\n style\\\ /* @cat-caption */\\r\\n display: flex;/* @cat-caption */\\r\\n justify-content: space-evenly;/* @cat-caption */\\r\\n background: blue;/* @cat-caption */\\r\\n height: 200px;/* @cat-caption */\\r\\n \\\/* @cat-caption */\\r\\n\u003e/* @cat-caption */\\r\\n \u003cdiv style\\\background: red; height: 30px\\\\u003e這是一個大貓貓\u003c/div\u003e/* @cat-caption */\\r\\n \u003cdiv style\\\background: red; height: 90px\\\\u003e這是一個大貓貓\u003c/div\u003e/* @cat-caption */\\r\\n \u003cdiv style\\\background: red; height: 60px\\\\u003e這是一個大貓貓\u003c/div\u003e/* @cat-caption */\\r\\n\u003c/div\u003e\\r\\n\u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\,\caption\:\我們替元素染個色、設定高度方便接下來觀察\,\language\:\html\,\image\:{\url\:\/api/object/1719180405981-vkssh.png\,\position\:\bottom-right\,\fullWidth\:false,\width\:600}},{\id\:\ogj2nn\,\code\:\\u003cp\u003e這是一個大貓貓\u003c/p\u003e\\r\\n\u003cp style\\\display: block\\\\u003e大家默認都是 block\u003c/p\u003e\\r\\n\u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\\r\\n\u003cp\u003e這是一個大貓貓\u003c/p\u003e\\r\\n\u003cdiv\\r\\n style\\\\\r\\n display: flex;\\r\\n justify-content: space-evenly;\\r\\n align-items: end;/* @cat-caption */\\r\\n background: blue;\\r\\n height: 200px;\\r\\n \\\\\r\\n\u003e\\r\\n \u003cdiv style\\\background: red; height: 30px\\\\u003e這是一個大貓貓\u003c/div\u003e\\r\\n \u003cdiv style\\\background: red; height: 90px\\\\u003e這是一個大貓貓\u003c/div\u003e\\r\\n \u003cdiv style\\\background: red; height: 60px\\\\u003e這是一個大貓貓\u003c/div\u003e\\r\\n\u003c/div\u003e\\r\\n\u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\,\caption\:\align-items 則是控制\\\所有\\\子元素的 y 軸排列\\nalign-items: end; 讓所有的子元素全部靠在底部\\n\,\language\:\html\,\image\:{\url\:\/api/object/1719180468851-ramyp.png\,\position\:\bottom-right\,\width\:600}},{\id\:\wix3n4\,\code\:\\u003cp\u003e這是一個大貓貓\u003c/p\u003e\\r\\n\u003cp style\\\display: block\\\\u003e大家默認都是 block\u003c/p\u003e\\r\\n\u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\\r\\n\u003cp\u003e這是一個大貓貓\u003c/p\u003e\\r\\n\u003cdiv\\r\\n style\\\\\r\\n display: flex;\\r\\n justify-content: space-evenly;\\r\\n align-items: center;/* @cat-caption */\\r\\n background: blue;\\r\\n height: 200px;\\r\\n \\\\\r\\n\u003e\\r\\n \u003cdiv style\\\background: red; height: 30px\\\\u003e這是一個大貓貓\u003c/div\u003e\\r\\n \u003cdiv style\\\background: red; height: 90px\\\\u003e這是一個大貓貓\u003c/div\u003e\\r\\n \u003cdiv style\\\background: red; height: 60px\\\\u003e這是一個大貓貓\u003c/div\u003e\\r\\n\u003c/div\u003e\\r\\n\u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\,\caption\:\align-items: center; 讓所有的子元素全部靠在中間\\n\,\language\:\html\,\image\:{\url\:\/api/object/1719180498398-q8gwe.png\,\position\:\bottom-right\,\width\:600}},{\id\:\gbdz8\,\code\:\\u003cp\u003e這是一個大貓貓\u003c/p\u003e\\r\\n\u003cp style\\\display: block\\\\u003e大家默認都是 block\u003c/p\u003e\\r\\n\u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\\r\\n\u003cp\u003e這是一個大貓貓\u003c/p\u003e\\r\\n\u003cdiv\\r\\n style\\\\\r\\n display: flex;\\r\\n justify-content: space-evenly;\\r\\n align-items: start;/* @cat-caption */\\r\\n background: blue;\\r\\n height: 200px;\\r\\n \\\\\r\\n\u003e\\r\\n \u003cdiv style\\\background: red; height: 30px\\\\u003e這是一個大貓貓\u003c/div\u003e\\r\\n \u003cdiv style\\\background: red; height: 90px\\\\u003e這是一個大貓貓\u003c/div\u003e\\r\\n \u003cdiv style\\\background: red; height: 60px\\\\u003e這是一個大貓貓\u003c/div\u003e\\r\\n\u003c/div\u003e\\r\\n\u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\,\caption\:\align-items: start; 讓所有的子元素全部靠在上面\\n\,\language\:\html\,\image\:{\url\:\/api/object/1719180567261-yw00g.png\,\position\:\bottom-right\,\width\:600}},{\id\:\uh93yb\,\code\:\\u003cp\u003e這是一個大貓貓\u003c/p\u003e\\r\\n\u003cp style\\\display: block\\\\u003e大家默認都是 block\u003c/p\u003e\\r\\n\u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\\r\\n\u003cp\u003e這是一個大貓貓\u003c/p\u003e\\r\\n\u003cdiv\\r\\n style\\\\\r\\n display: flex;\\r\\n flex-direction: column; /* @cat-caption */\\r\\n background: blue;\\r\\n height: 200px;\\r\\n \\\\\r\\n\u003e\\r\\n \u003cdiv style\\\background: red; height: 30px\\\\u003e這是一個大貓貓\u003c/div\u003e/* @cat-caption */\\r\\n \u003cdiv style\\\background: green; height: 90px\\\\u003e這是一個大貓貓\u003c/div\u003e/* @cat-caption */\\r\\n \u003cdiv style\\\background: orange; height: 60px\\\\u003e這是一個大貓貓\u003c/div\u003e/* @cat-caption */\\r\\n\u003c/div\u003e\\r\\n\u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\,\caption\:\flex 最強大的地方還有\\n他可以立起來,原本元素都是橫向 (x 軸) 排列\\n可以透過 flex-direction 修改成直向 (y 軸) 排列\\nflex-direction: column; y 軸排列\,\language\:\html\,\image\:{\url\:\/api/object/1719180801456-b7fiti.png\,\position\:\bottom-right\,\width\:601}},{\id\:\edwpu\,\code\:\\u003cp\u003e這是一個大貓貓\u003c/p\u003e\\r\\n\u003cp style\\\display: block\\\\u003e大家默認都是 block\u003c/p\u003e\\r\\n\u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\\r\\n\u003cp\u003e這是一個大貓貓\u003c/p\u003e\\r\\n\u003cdiv\\r\\n style\\\\\r\\n display: flex;\\r\\n flex-direction: column-reverse; /* @cat-caption */\\r\\n background: blue;\\r\\n height: 200px;\\r\\n \\\\\r\\n\u003e\\r\\n \u003cdiv style\\\background: red; height: 30px\\\\u003e這是一個大貓貓\u003c/div\u003e\\r\\n \u003cdiv style\\\background: green; height: 90px\\\\u003e這是一個大貓貓\u003c/div\u003e\\r\\n \u003cdiv style\\\background: orange; height: 60px\\\\u003e這是一個大貓貓\u003c/div\u003e\\r\\n\u003c/div\u003e\\r\\n\u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\,\caption\:\flex-direction: column-reverse;\\n一樣是直向排列,不過前後顛倒\\n注意看紅色的地方\\n他跑去最後面了\,\language\:\html\,\image\:{\url\:\/api/object/1719180878524-a4zdte.png\,\position\:\bottom-right\,\width\:600}},{\id\:\em4jc\,\code\:\\u003cp\u003e這是一個大貓貓\u003c/p\u003e\\r\\n\u003cp style\\\display: block\\\\u003e大家默認都是 block\u003c/p\u003e\\r\\n\u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\\r\\n\u003cp\u003e這是一個大貓貓\u003c/p\u003e\\r\\n\u003cdiv\\r\\n style\\\\\r\\n display: flex;\\r\\n flex-direction: row; /* @cat-caption */\\r\\n background: blue;\\r\\n height: 200px;\\r\\n \\\\\r\\n\u003e\\r\\n \u003cdiv style\\\background: red; height: 30px\\\\u003e這是一個大貓貓\u003c/div\u003e\\r\\n \u003cdiv style\\\background: green; height: 90px\\\\u003e這是一個大貓貓\u003c/div\u003e\\r\\n \u003cdiv style\\\background: orange; height: 60px\\\\u003e這是一個大貓貓\u003c/div\u003e\\r\\n\u003c/div\u003e\\r\\n\u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\,\caption\:\flex-direction: row;\\n可以當成默認的橫向排列\,\language\:\html\,\image\:{\url\:\/api/object/1719180962231-riwqp9.png\,\position\:\bottom-right\,\width\:600}},{\id\:\7511d4\,\code\:\\u003cp\u003e這是一個大貓貓\u003c/p\u003e\\r\\n\u003cp style\\\display: block\\\\u003e大家默認都是 block\u003c/p\u003e\\r\\n\u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\\r\\n\u003cp\u003e這是一個大貓貓\u003c/p\u003e\\r\\n\u003cdiv\\r\\n style\\\\\r\\n display: flex;\\r\\n flex-direction: row-reverse; /* @cat-caption */\\r\\n background: blue;\\r\\n height: 200px;\\r\\n \\\\\r\\n\u003e\\r\\n \u003cdiv style\\\background: red; height: 30px\\\\u003e這是一個大貓貓\u003c/div\u003e\\r\\n \u003cdiv style\\\background: green; height: 90px\\\\u003e這是一個大貓貓\u003c/div\u003e\\r\\n \u003cdiv style\\\background: orange; height: 60px\\\\u003e這是一個大貓貓\u003c/div\u003e\\r\\n\u003c/div\u003e\\r\\n\u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\,\caption\:\flex-direction: row-reverse;\\n當然也可以前後顛倒啦\\n注意看紅色的地方\\n他跑去最後面了\,\language\:\html\,\image\:{\url\:\/api/object/1719181042557-bkg4ct.png\,\position\:\bottom-right\,\width\:600}},{\id\:\2t5ur\,\code\:\\u003cp\u003e這是一個大貓貓\u003c/p\u003e\\r\\n\u003cp style\\\display: block\\\\u003e大家默認都是 block\u003c/p\u003e\\r\\n\u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\\r\\n\u003cp\u003e這是一個大貓貓\u003c/p\u003e\\r\\n\u003cdiv\\r\\n style\\\\\r\\n display: flex;\\r\\n justify-content: space-between;\\r\\n background: blue;\\r\\n height: 200px;\\r\\n \\\\\r\\n\u003e\\r\\n \u003cdiv/* @cat-caption */\\r\\n style\\\/* @cat-caption */\\r\\n display: flex;/* @cat-caption */\\r\\n justify-content: start;/* @cat-caption */\\r\\n flex-direction: column;/* @cat-caption */\\r\\n height: 100%;/* @cat-caption */\\r\\n \\\/* @cat-caption */\\r\\n \u003e/* @cat-caption */\\r\\n \u003cdiv style\\\background: red; height: 30px\\\\u003e這是一個大貓貓\u003c/div\u003e/* @cat-caption */\\r\\n \u003c/div\u003e/* @cat-caption */\\r\\n \u003cdiv/* @cat-caption */\\r\\n style\\\/* @cat-caption */\\r\\n display: flex;/* @cat-caption */\\r\\n justify-content: center;/* @cat-caption */\\r\\n flex-direction: column;/* @cat-caption */\\r\\n height: 100%;/* @cat-caption */\\r\\n \\\/* @cat-caption */\\r\\n \u003e/* @cat-caption */\\r\\n \u003cdiv style\\\background: green; height: 90px\\\\u003e這是一個大貓貓\u003c/div\u003e/* @cat-caption */\\r\\n \u003c/div\u003e/* @cat-caption */\\r\\n\\r\\n \u003cdiv/* @cat-caption */\\r\\n style\\\/* @cat-caption */\\r\\n display: flex;/* @cat-caption */\\r\\n justify-content: end;/* @cat-caption */\\r\\n flex-direction: column;/* @cat-caption */\\r\\n height: 100%;/* @cat-caption */\\r\\n \\\/* @cat-caption */\\r\\n \u003e/* @cat-caption */\\r\\n \u003cdiv style\\\background: orange; height: 60px\\\\u003e這是一個大貓貓\u003c/div\u003e/* @cat-caption */\\r\\n \u003c/div\u003e/* @cat-caption */\\r\\n\u003c/div\u003e\\r\\n\u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\,\caption\:\透過多個 flex 元素的結合\\n比如說第一個 flex 負責 x 軸的位置\\n第二個 flex 負責 y 的位置\\n可以做出很複雜的效果\,\language\:\html\,\image\:{\url\:\/api/object/1719181574727-9p6qb.png\,\position\:\bottom-right\,\width\:599}})/script>script>self.__next_f.push(1,55:T637,)/script>script>self.__next_f.push(1,\u003cp\u003e這是一個大貓貓\u003c/p\u003e\r\n\u003cp style\display: block\\u003e大家默認都是 block\u003c/p\u003e\r\n\u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\r\n\u003cp\u003e這是一個大貓貓\u003c/p\u003e\r\n\u003cdiv\r\n style\\r\n display: flex;\r\n justify-content: space-between;\r\n background: blue;\r\n height: 200px;\r\n \\r\n\u003e\r\n \u003cdiv/* @cat-caption */\r\n style\/* @cat-caption */\r\n display: flex;/* @cat-caption */\r\n justify-content: start;/* @cat-caption */\r\n flex-direction: column;/* @cat-caption */\r\n height: 100%;/* @cat-caption */\r\n \/* @cat-caption */\r\n \u003e/* @cat-caption */\r\n \u003cdiv style\background: red; height: 30px\\u003e這是一個大貓貓\u003c/div\u003e/* @cat-caption */\r\n \u003c/div\u003e/* @cat-caption */\r\n \u003cdiv/* @cat-caption */\r\n style\/* @cat-caption */\r\n display: flex;/* @cat-caption */\r\n justify-content: center;/* @cat-caption */\r\n flex-direction: column;/* @cat-caption */\r\n height: 100%;/* @cat-caption */\r\n \/* @cat-caption */\r\n \u003e/* @cat-caption */\r\n \u003cdiv style\background: green; height: 90px\\u003e這是一個大貓貓\u003c/div\u003e/* @cat-caption */\r\n \u003c/div\u003e/* @cat-caption */\r\n\r\n \u003cdiv/* @cat-caption */\r\n style\/* @cat-caption */\r\n display: flex;/* @cat-caption */\r\n justify-content: end;/* @cat-caption */\r\n flex-direction: column;/* @cat-caption */\r\n height: 100%;/* @cat-caption */\r\n \/* @cat-caption */\r\n \u003e/* @cat-caption */\r\n \u003cdiv style\background: orange; height: 60px\\u003e這是一個大貓貓\u003c/div\u003e/* @cat-caption */\r\n \u003c/div\u003e/* @cat-caption */\r\n\u003c/div\u003e\r\n\u003cdiv\u003e這是一個大貓貓\u003c/div\u003e)/script>script>self.__next_f.push(1,56:Td3e,)/script>script>self.__next_f.push(1,{\id\:\iij29s\,\code\:\\,\caption\:\我們先從最基本的概念開始\\n\\n先來理解一下 什麼是網頁\\n為啥我打開瀏覽器,就可以跑出那麼漂亮的畫面\,\language\:\typescript\,\image\:{\url\:\/api/object/1718181894090-h3n0us.png\,\position\:\center\,\fullWidth\:true,\width\:200}},{\id\:\yshnxo\,\code\:\\,\caption\:\什麼是網頁呢?\\n當你在瀏覽器中打開一個網址時,顯示出來漂亮的畫面就是網頁\\n\\n最早網頁只是為了方便將資料傳給別人看\\n所以將一些文字、圖片,放在自己的電腦上,然後只要別人連線到我的電腦時,我就把這些文字、圖片傳給他\,\language\:\typescript\,\image\:{\url\:\/api/object/1718181934699-v2r9z.png\,\position\:\center\,\fullWidth\:true,\width\:200}},{\id\:\f5115\,\code\:\\,\caption\:\我們直觀的用右邊的圖表來展示一下網頁的連線過程吧\\n\\n先有個先備知識,全球網際網路可以先想像成一個超級郵差,它可以幫你傳訊息。\\n而且大家如果想傳訊息都會找這個郵差。\\n\\n而為了方便郵差送信,所有連上網路的電腦都有一個地址像是新竹市東區光復路二段101號 之類的,讓郵差或其他人可以很好的寄信給你\\n\\n這個地址也就被稱之為 IP 地址\,\language\:\typescript\,\image\:{\url\:\/api/object/1718182154235-s88zbw.png\,\position\:\center\,\fullWidth\:true,\width\:200}},{\id\:\6lui69\,\code\:\\,\caption\:\今天如果你想看 youtube,你會在瀏覽器當中輸入 youtube.com 這個網域\\n\\n網域?其實就是地址太難記啦!所以郵差開放大家註冊自己的縮寫\\n好比說 新竹市東區光復路二段101號 的縮寫就是 清華大學\,\language\:\typescript\,\image\:{\url\:\/api/object/1718182103128-ruqfvm.png\,\position\:\center\,\fullWidth\:true,\width\:200}},{\id\:\bhojto\,\code\:\\,\caption\:\回到剛剛的 youtube,當你想看 youtube 時\\n瀏覽器會幫妳寫好一封信,寄給 youtube.com\\n內容就是希望能請求取得網頁內容\\n然後寄給這個郵差,請他幫忙轉交給 youtube\,\language\:\typescript\,\image\:{\url\:\/api/object/1718182695904-d8zzee.gif\,\position\:\center\,\fullWidth\:true,\width\:200}},{\id\:\eenl2\,\code\:\\,\caption\:\當郵差收到這封要寄的信,看了一眼 youtube.com\\n他會去查之前跟他註冊的網域清單,然後就能知道\\n youtube.com 的真實 IP 142.250.65.206\\n然後就會轉寄給 Youtube 的電腦\,\language\:\typescript\,\image\:{\url\:\/api/object/1718182963372-2g7e4u.gif\,\position\:\center\,\fullWidth\:true,\width\:200}},{\id\:\tbg70m\,\code\:\\,\caption\:\Youtube 從郵差那邊收到信以後,馬上就寫了一封回信,將整個網頁的內容放進去\\n然後再請郵差回傳給你\,\language\:\typescript\,\image\:{\url\:\/api/object/1718183254318-pij9uf.gif\,\position\:\center\,\fullWidth\:true,\width\:200}},{\id\:\mcxkv\,\code\:\\,\caption\:\最後你收到了回信,回信的內容就是網頁啦~\\n你就可以看到漂亮的網站了\\n\\n值得一提的是,剛剛的整個過程時間不到 0.1 秒\\n\\n那問題來了,回信的內容是網頁,那到底是什麼?\\n一大坨的文字嗎?還是?\\n這就是我們今天要學的東西的 網頁程式開發\,\language\:\typescript\,\image\:{\url\:\/api/object/1718183391840-4anxn.gif\,\position\:\center\,\fullWidth\:true,\width\:200}})/script>script>self.__next_f.push(1,57:Tc60,)/script>script>self.__next_f.push(1,{\id\:\vgc26v\,\code\:\\,\caption\:\HTML\\n喝杯咖啡,這東西真的很容易,就是寫寫文字!\,\language\:\typescript\,\image\:{\url\:\/api/object/1718186922910-k6b8ka.png\,\position\:\center\,\fullWidth\:true,\width\:200}},{\id\:\ifdt4n\,\code\:\\,\caption\:\我們將用 Visual Studio Code ( VSCode ) 這個文字編輯器來進行開發\\n請各位安裝 Live Server 這個 VSCode 內的延伸模組\\n可以提高開發效率\,\language\:\typescript\,\image\:{\url\:\/api/object/1718191696805-j0weaa.png\,\position\:\center\,\fullWidth\:true,\width\:200}},{\id\:\czjcx9\,\code\:\\,\caption\:\創建一個新的資料夾,來放置我們的網站程式碼\\n\,\language\:\typescript\,\image\:{\url\:\/api/object/1718192974718-kngyn4.png\,\position\:\center\,\fullWidth\:true,\width\:200}},{\id\:\ah6ias\,\code\:\\,\caption\:\將這個資料夾拉入 VSCode 中\,\language\:\typescript\,\image\:{\url\:\/api/object/1718193187287-nld86f.png\,\position\:\center\,\fullWidth\:true,\width\:200}},{\id\:\jj2qv\,\code\:\\,\caption\:\透過 VSCode 創建一個檔案\\n命名為 index.html\,\language\:\typescript\,\image\:{\url\:\/api/object/1718193212165-d8jl8u.png\,\position\:\center\,\fullWidth\:true,\width\:200}},{\id\:\j6qqc\,\code\:\\,\caption\:\點開這個檔案,輸入 !\,\language\:\typescript\,\image\:{\url\:\/api/object/1718193233962-w8e03.png\,\position\:\center\,\fullWidth\:true,\width\:200}},{\id\:\i0jtf\,\code\:\\,\caption\:\按下 enter,能直接生成一整份 HTML 範本\,\language\:\typescript\,\image\:{\url\:\/api/object/1718193255384-gc3389.png\,\position\:\center\,\fullWidth\:true,\width\:200}},{\id\:\3ybwo8\,\code\:\\,\caption\:\點擊右下角的 Go Live\\n能即時預覽這個 Html 實際的網頁長什麼樣子\,\language\:\typescript\,\image\:{\url\:\/api/object/1718193282288-o22wq.png\,\position\:\center\,\fullWidth\:true,\width\:200}},{\id\:\7jzl0b\,\code\:\\,\caption\:\在網頁中,元素就像是組成房子的磚塊。\\n\\n每個元素代表一個網頁上的部分,比如一段文字、一張圖片或一個按鈕。\\n\\n這些元素透過 HTML 標籤來表示,每個標籤都有不同的功能,讓我們可以有條理地建構和設計網頁。\\n\\n換句話說,元素是網頁內容的基本單位,所有的內容都是由這些元素組成的。\\n\\n另外大多數的元素都會有 開頭 與 結尾 包起來,才能成為一個真正的元素\,\language\:\typescript\,\image\:{\url\:\/api/object/1718193664228-il8ioh.png\,\position\:\center\,\fullWidth\:true,\width\:200}},{\id\:\2skw49\,\code\:\\,\caption\:\單純的一塊磚頭,沒辦法表達太多東西\\n所以我們替他加上了【屬性】\\n\\n屬性就像是為這些磚塊添加的裝飾或說明。\\n它們提供額外的訊息或修改元素的外觀和行為。\\n\\nAttributes 永遠都放在開頭標籤\\n\\n例如,連結元素有一個 href 告訴網頁點擊這個連結後實際要跳轉到哪裡\\n\\n屬性幫助我們更細緻地控制和定義元素,讓網頁變得更加豐富和有趣。\,\language\:\typescript\,\image\:{\url\:\/api/object/1718194715329-ky2gq.png\,\position\:\center\,\fullWidth\:true,\width\:200}})/script>script>self.__next_f.push(1,58:T16dc,)/script>script>self.__next_f.push(1,{\id\:\fa2sk\,\code\:\\u003ch1\u003e超大標題字\u003c/h1\u003e\\r\\n\u003ch2\u003e大標題字\u003c/h2\u003e\\r\\n\u003ch3\u003e普通標題字\u003c/h3\u003e\\r\\n\u003ch4\u003e小標題字\u003c/h4\u003e\\r\\n\u003ch5\u003e更小標題字\u003c/h5\u003e\\r\\n\u003ch6\u003e超級小標題字\u003c/h6\u003e\,\caption\:\標題,顧名思義就是標題字\\n特別大、特別顯眼\\n能很好的分級和組織網頁內容\\n根據重要性,有 h1 ~ h6 六種標題\,\language\:\html\,\image\:{\url\:\/api/object/1718209865121-p7w9r.png\,\position\:\bottom-right\,\width\:200}},{\id\:\jrzr8a\,\code\:\/* @cat-caption */\u003cp\u003e段落,會直接開始於新的一行。通常是用來放一段文字\u003c/p\u003e\,\caption\:\段落(Paragraphs)\\n\\n段落用來放一段文字,使用 \u003cp\u003e 標籤。\,\language\:\html\,\image\:{\url\:\/api/object/1718218024798-ebg22g.png\,\position\:\bottom-right\,\width\:500}},{\id\:\8a7dd\,\code\:\\u003cp\u003e段落,會直接開始於新的一行。通常是用來放一段文字\u003c/p\u003e\\r\\n/* @cat-caption */\u003cp\u003e段落內有很多空白,也只會被視為一個空白的,你必須透過關鍵字 \u0026nbsp; 才能放置多個空白\u003c/p\u003e\,\caption\:\段落(Paragraphs)\\n\\n需要注意的是,HTML 中段落內的空白不會顯示,可以使用 \u0026nbsp; 關鍵字來創建多個空白。\,\language\:\html\,\image\:{\url\:\/api/object/1718218061296-a5uz3h.png\,\position\:\bottom-right\,\width\:500}},{\id\:\8oo5dr\,\code\:\\u003cp\u003e段落,會直接開始於新的一行。通常是用來放一段文字\u003c/p\u003e\\r\\n\u003cp\u003e段落內有很多空白,也只會被視為一個空白的,你必須透過關鍵字 \u0026nbsp; 才能放置多個空白\u003c/p\u003e\\r\\n/* @cat-caption */\u003cp\u003e\\r\\n/* @cat-caption */ 換行在段落中是沒有用的,你可以透過 br 標籤\u003cbr/\u003e\\r\\n/* @cat-caption */ 來創建換行,\u003cbr/\\r\\n/* @cat-caption */ \u003e因為功能單純,可以將開始標籤與結束標籤省略為同個東西\\r\\n/* @cat-caption */\u003c/p\u003e\,\caption\:\段落(Paragraphs)\\n\\nHTML 中段落內的換行不會顯示,可以使用 \u003cbr\u003e 標籤來添加換行效果。\,\language\:\html\,\image\:{\url\:\/api/object/1718218108772-5rqtk.png\,\position\:\bottom-right\,\width\:500}},{\id\:\6022f9\,\code\:\\,\caption\:\為了撰寫複雜的文件格式\\n所以有很多專用的元素,來做類似的效果\\n你可以想像成用文字的方法,操作 word\\n撰寫文章\,\language\:\html\,\image\:{\url\:\/api/object/1718218307164-mm055.png\,\position\:\center\,\fullWidth\:true,\width\:200}},{\id\:\qw85pc\,\code\:\/* @cat-caption */\u003cem\u003e可愛的貓貓\u003c/em\u003e\,\caption\:\有意思的是,多個元素包再一起時\\n效果是會疊加的喔\,\language\:\html\,\image\:{\url\:\/api/object/1718218410050-cqstpd.png\,\position\:\top-right\,\width\:200}},{\id\:\ul7pi6\,\code\:\/* @cat-caption */\u003cstrong\u003e\\r\\n \u003cem\u003e可愛的貓貓\u003c/em\u003e\\r\\n/* @cat-caption */\u003c/strong\u003e\,\caption\:\我們替他加上粗體字效果\\n\\n斜體與粗體被疊加了!\,\language\:\html\,\image\:{\url\:\/api/object/1718218470754-awa7ju.png\,\position\:\top-right\,\width\:200}},{\id\:\0d5fc\,\code\:\/* @cat-caption */\u003cdel\u003e\\r\\n \u003cstrong\u003e\\r\\n \u003cem\u003e可愛的貓貓\u003c/em\u003e\\r\\n \u003c/strong\u003e\\r\\n/* @cat-caption */\u003c/del\u003e\,\caption\:\再疊加上一個刪除符號\,\language\:\html\,\image\:{\url\:\/api/object/1718218560231-fyshz.png\,\position\:\top-right\,\width\:200}},{\id\:\tuxqti\,\code\:\\u003cimg \\r\\n src\\\https://ray-realms-blog.zeabur.app/api/object/1718132244050-eq5mnk.jpg\\\\\r\\n/\u003e\,\caption\:\Image (img)\\n在網頁當中放置圖片\\n你可以透過 src 指定圖片網址\\n也一樣因為 img 功能單純,可以將開始標籤與結束標籤省略為同個東西\\n\,\language\:\html\,\image\:{\url\:\/api/object/1718219918020-ydrjhd.png\,\position\:\bottom-right\,\width\:200}},{\id\:\y7amkg\,\code\:\\u003cimg\\r\\n src\\\https://ray-realms-blog.zeabur.app/api/object/1718132244050-eq5mnk.jpg\\\\\r\\n /* @cat-caption */width\\\300px\\\\\r\\n /* @cat-caption */height\\\600px\\\\\r\\n/\u003e\,\caption\:\Image (img)\\n\\n你可以透過 width 與 height 指定圖片寬度與高度\,\language\:\html\,\image\:{\url\:\/api/object/1718219897501-z6bq.png\,\position\:\bottom-right\,\width\:200}},{\id\:\bl1a7q\,\code\:\\u003cimg\\r\\n src\\\https://ray-realms-blog.zeabur.app/api/object/1718132244050-eq5mnk.jpg\\\\\r\\n width\\\300px\\\\\r\\n height\\\600px\\\\\r\\n /* @cat-caption */alt\\\描述圖片的替代文字\\\\\r\\n/\u003e\,\caption\:\Image (img)\\n\\n另外 alt 屬性可以寫下圖片的替代文字\\n當圖片載入失敗時、或者有視覺障礙的人聆聽時便會顯示這段文字\,\language\:\html\,\image\:{\url\:\/api/object/1718220644224-92m5td.png\,\position\:\bottom-right\,\width\:200}},{\id\:\7ysw2\,\code\:\/* @cat-caption */\u003ca href\\\https://ray-realms.com/\\\\u003e這是一個點了會挑轉到 Ray 個人網站的連結\u003c/a\u003e\,\caption\:\Links(連結)\\n\\n連結用來導向其他網頁或資源,使用 \u003ca\u003e 標籤。href 屬性指定目標網址。\,\language\:\html\,\image\:{\url\:\/api/object/1718220683824-ia908m.png\,\position\:\bottom-right\,\width\:500}},{\id\:\4lot1nn\,\code\:\\u003cdiv\u003e\\r\\n \u003ch3\u003e區塊一標題\u003c/h3\u003e\\r\\n \u003cp\u003e區塊通常是方便美術排版的\u003c/p\u003e\\r\\n\u003c/div\u003e\\r\\n\\r\\n\u003cdiv\u003e\\r\\n \u003ch3\u003e區塊二標題\u003c/h3\u003e\\r\\n \u003cp\u003e區塊通常是方便程式碼的分類\u003c/p\u003e\\r\\n\u003c/div\u003e\,\caption\:\Div(區塊)\\n\\n區塊的概念很簡單,就是用來分類程式碼或方便美術排版的\,\language\:\html\,\image\:{\url\:\/api/object/1718220707202-mpd2c9.png\,\position\:\bottom-right\,\width\:400}},{\id\:\0xpj6i\,\code\:\\u003ch3\u003e 這是一個被無視的故事\u003c/h3\u003e\\r\\n\u003c!-- 我被無視了 QAQ --\u003e\\r\\n\u003cp\u003e\\r\\n 任何放在註解中的文字,都不會顯示在網頁上\u003cbr\u003e\\r\\n 註解只是給開發者看的文字\\r\\n\u003c/p\u003e\,\caption\:\Comments(註解)\\n\\n\u003c!-- 註解文字,並不會被顯示出來,快捷鍵 ctrl + / --\u003e\,\language\:\html\,\image\:{\url\:\/api/object/1718220733816-d3jgyc.png\,\position\:\bottom-right\,\width\:400}})/script>script>self.__next_f.push(1,18:\$\,\$L1c\,null,{\articles\:{\id\:\0490bb2c-8e7a-442e-b68b-d9bf80589528\,\title\:\簡單理解怎麼造一個千萬人流量的網站\,\content\:\簡單理解,面對超大流量,該如何避免網站掛掉的方式!\\n本文以普發現金網站為例,深入淺出解析高併發系統設計:身分證分流策略、負載平衡、Redis快取系統、資料庫分片技術、訊息佇列等核心概念。\\n從解決瞬間流量峰值、資料庫瓶頸到資料一致性問題,用生活化比喻帶你理解大型網站架構,適合後端工程師與系統設計初學者。\,\blockIndexIds\:\f21e2224-94a0-470b-a128-6f600447df8d\,\slug\:\beginner-guide-high-traffic-website\,\metaDescription\:null,\ogImage\:null,\privacy\:\PUBLIC\,\allowedEmails\:,\createdAt\:\2025-11-06T02:33:55.186Z\,\updatedAt\:\2025-11-22T00:42:27.679Z\,\authorId\:\24c3f178-4a5b-447d-b586-16d7d459c606\,\viewCount\:287,\newsletterSentAt\:\2025-11-12T09:23:07.004Z\,\blocks\:{\id\:\f21e2224-94a0-470b-a128-6f600447df8d\,\articleId\:\0490bb2c-8e7a-442e-b68b-d9bf80589528\,\noteBlock\:{\id\:\f21e2224-94a0-470b-a128-6f600447df8d\,\content\:\$1d\},\codeBlock\:null,\questionBlock\:null}},{\id\:\cb2d23e3-0e0a-430e-ae5f-6c5375468f95\,\title\:\MCP 開發,ChatGPT 報錯 Connector is not safe 解法\,\content\:\開發 MCP 伺服器整合 ChatGPT 遇到「Connector is not safe」錯誤? 立即了解此常見 OpenAI 錯誤 的核心成因(與隱私描述有關)與最新實用解法! 快速解決連接器安全問題,讓您的應用程式順利上線。\,\blockIndexIds\:\4742e6f4-7f07-4223-9bb4-a6e286305d2c\,\slug\:null,\metaDescription\:null,\ogImage\:null,\privacy\:\PUBLIC\,\allowedEmails\:,\createdAt\:\2025-11-01T03:44:23.030Z\,\updatedAt\:\2025-11-22T02:18:09.887Z\,\authorId\:\24c3f178-4a5b-447d-b586-16d7d459c606\,\viewCount\:133,\newsletterSentAt\:\2025-11-05T02:21:57.395Z\,\blocks\:{\id\:\4742e6f4-7f07-4223-9bb4-a6e286305d2c\,\articleId\:\cb2d23e3-0e0a-430e-ae5f-6c5375468f95\,\noteBlock\:{\id\:\4742e6f4-7f07-4223-9bb4-a6e286305d2c\,\content\:\## 問題:ChatGPT 報錯 Connector is not safe?\\n\\n最近很常遇到開發 MCP 伺服器時,想綁到 ChatGPT 的應用程式與連接器\\n結果報錯 \\\Connector is not safe\\\\u0026#x20;\\n\\n!(/api/object/1761968838335-l77czq.png \\\ChatGPT 報錯 Connector is not safe\\\)\\n明明我的程式碼都符合最佳實踐,版本也在最新\\n到底是哪裡不安全?!\\n\\n## 我來告訴你怎麼解決 👇\\n\\n「工具描述」\\n只要你的某個工具描述中包含「個人資料」或者說要「取得用戶資訊」或者其他嘗試觸犯用戶隱私的文字時,就會出現該問題\\n\\n由於他們是用 LLM 判斷,所以這裡的文字是很模稜兩可,只是 LLM 覺得侵犯隱私那就沒救。\\n\\n### 提示詞範例\\n\\n(X)不要說取得用戶資訊\\n(O) 取得用戶授權的可公開資料\\n\\n!(/api/object/1761969108238-ag52fl.png \\\錯誤提示詞\\\)\\n!(/api/object/1761969118703-trdvpe.png)\\n\\n經過剛剛隨意地實驗才發現這件事 \\n\\n我被這個問題卡了很久,他們論壇也很多人在討論這個問題,OpenAI 官方沒回應\},\codeBlock\:null,\questionBlock\:null}},{\id\:\73b7d4c5-fc5e-49e2-a7df-c675c4703300\,\title\:\打造你的第一個 MCP Server:從概念到實作\,\content\:\想讓 AI 助手存取你的本地筆記、檔案或資料庫?\\n本文從實際痛點出發,完整解析 MCP(Model Context Protocol)如何成為 AI 與工具溝通的標準協定。\\n透過手把手的程式碼教學,帶你打造第一個 MCP Server,讓 ChatGPT 或 Claude 能自動讀取你的個人知識庫。包含完整實作、ngrok 部署步驟與實際對話測試,讓你快速掌握 AI Agent 開發的核心技術。\,\blockIndexIds\:\e5492a33-d7de-4af7-8fad-274f42fed826\,\61e6b5bb-d308-4aec-b638-42903345b970\,\3af68e2d-b71e-49cf-972e-c284cfde3ae0\,\3696f865-4df8-46ec-bad4-ec16075ec486\,\dcaec109-f4f1-4081-b56f-840163b8660b\,\20b4434e-d333-4f0a-8e72-21e06caf87ff\,\0f839051-64ff-4ea6-8bc6-f2980b572420\,\4b3ce204-3729-484f-8ff9-bf13c25592fd\,\c76d32a8-efce-43fe-8487-f7ffa4b373c7\,\1e4ed363-900f-41d2-b797-bbb4c1c59f4c\,\b4b382e5-446d-47ba-b214-aa91814e684a\,\75f79304-4d7e-4e20-8a44-69c9283225c1\,\cc44c8d8-ffed-4046-9105-fb6be6e92740\,\fe0a4b58-3f6c-4277-af98-4b7677eae530\,\9c5a4db9-f0f5-4f76-887e-2470cb2685bc\,\081534ff-b5ef-44a1-89c6-78b4c2849cd4\,\37d7c27b-17ce-485d-860c-5ca3b730d2f2\,\3ba6f0e8-5f12-46f0-84c7-cc1940c43750\,\slug\:null,\metaDescription\:null,\ogImage\:null,\privacy\:\PUBLIC\,\allowedEmails\:,\createdAt\:\2025-10-23T23:12:13.316Z\,\updatedAt\:\2025-11-22T13:26:36.468Z\,\authorId\:\24c3f178-4a5b-447d-b586-16d7d459c606\,\viewCount\:1890,\newsletterSentAt\:\2025-10-29T02:01:03.174Z\,\blocks\:{\id\:\e5492a33-d7de-4af7-8fad-274f42fed826\,\articleId\:\73b7d4c5-fc5e-49e2-a7df-c675c4703300\,\noteBlock\:{\id\:\e5492a33-d7de-4af7-8fad-274f42fed826\,\content\:\$1e\},\codeBlock\:null,\questionBlock\:null},{\id\:\61e6b5bb-d308-4aec-b638-42903345b970\,\articleId\:\73b7d4c5-fc5e-49e2-a7df-c675c4703300\,\noteBlock\:null,\codeBlock\:null,\questionBlock\:{\id\:\61e6b5bb-d308-4aec-b638-42903345b970\,\question\:\你正在開發一個「個人知識庫」的 MCP Server。下列哪個功能「不應該」設計成 Tool?\,\options\:\搜尋筆記內容(根據關鍵字找出相關筆記) \,\筆記分類規則(說明你如何組織筆記的 markdown 文件) \,\建立新筆記(在指定目錄建立 .md 檔案) \,\統計筆記數量(計算各分類有多少筆記)\,\answer\:\筆記分類規則(說明你如何組織筆記的 markdown 文件) \}},{\id\:\3af68e2d-b71e-49cf-972e-c284cfde3ae0\,\articleId\:\73b7d4c5-fc5e-49e2-a7df-c675c4703300\,\noteBlock\:{\id\:\3af68e2d-b71e-49cf-972e-c284cfde3ae0\,\content\:\### 單選題解析:你正在開發一個「個人知識庫」的 MCP Server。下列哪個功能「不應該」設計成 Tool?\\n\\n工具是一個 Function,是一個可以被執行的行為\\n筆記分類規則 ,其實就是很單純的文字、命令告訴 LLM 該怎麼做\},\codeBlock\:null,\questionBlock\:null},{\id\:\3696f865-4df8-46ec-bad4-ec16075ec486\,\articleId\:\73b7d4c5-fc5e-49e2-a7df-c675c4703300\,\noteBlock\:{\id\:\3696f865-4df8-46ec-bad4-ec16075ec486\,\content\:\## 目標:實作第一個 Tool:列出所有筆記\\n\\n我們要解決什麼問題?\\n現在我們要讓 AI 能夠「看到」你電腦裡有哪些筆記檔案。\\n這個工具很簡單:\\n\\n* **輸入**:不需要參數(列出所有筆記就好)\\n* **輸出**:每個筆記的名稱 + 路徑\\n* **目的**:讓 AI 知道有哪些筆記可以讀取\\n\\n## 第一步,創建一個簡單的 HTTP 伺服器\},\codeBlock\:null,\questionBlock\:null},{\id\:\dcaec109-f4f1-4081-b56f-840163b8660b\,\articleId\:\73b7d4c5-fc5e-49e2-a7df-c675c4703300\,\noteBlock\:null,\codeBlock\:{\id\:\dcaec109-f4f1-4081-b56f-840163b8660b\,\content\:null,\chunks\:{\id\:\hablwa\,\codeBlockId\:\dcaec109-f4f1-4081-b56f-840163b8660b\,\code\:\\,\language\:\bash\,\caption\:\首先,創建一個資料夾當作專案資料夾\\n我這邊就叫做「my-notes-mcp-server」\,\imageUrl\:\/api/object/1761485815234-3cn6za.png\,\imagePosition\:\center\,\imageFullWidth\:false,\imageWidth\:200,\imageHeight\:null,\order\:0,\createdAt\:\2025-10-26T23:11:11.215Z\,\updatedAt\:\2025-10-26T23:11:11.215Z\},{\id\:\qqpsqr\,\codeBlockId\:\dcaec109-f4f1-4081-b56f-840163b8660b\,\code\:\npm init -y\\nnpm install @modelcontextprotocol/sdk express zod\\nnpm install -D @types/express @types/node tsx typescript\,\language\:\bash\,\caption\:\接下來打開終端機,進入該資料夾中直接貼上這三行命令\\n\\n初始化 Nodejs 專案同時安裝必要套件\,\imageUrl\:\/api/object/1761486223802-w3f63l.png\,\imagePosition\:\center\,\imageFullWidth\:null,\imageWidth\:600,\imageHeight\:null,\order\:1,\createdAt\:\2025-10-26T23:11:11.215Z\,\updatedAt\:\2025-10-26T23:11:11.215Z\},{\id\:\v93lk0l\,\codeBlockId\:\dcaec109-f4f1-4081-b56f-840163b8660b\,\code\:\import express from express;\\n\\n// 1. 建立 Express 應用程式\\nconst app express();\\n\\napp.use(express.json());\\n\\n// 2. 設定路由\\n// 健康檢查 endpoint\\napp.get(/health, (req, res) \u003e {\\n res.json({ \\n status: ok,\\n message: Server is running! \\n });\\n});\\n\\n// 3. 啟動伺服器\\nconst port 8080;\\n\\napp.listen(port, () \u003e {\\n console.log(`🚀 伺服器運行中:http://localhost:${port}`);\\n console.log(`📍 健康檢查:http://localhost:${port}/health`);\\n});\,\language\:\javascript\,\caption\:\創建一個 index.ts\\n這是一個非常簡單的 HTTP 伺服器\,\imageUrl\:\/api/object/1761486751474-n54a6h.png\,\imagePosition\:\top-right\,\imageFullWidth\:null,\imageWidth\:300,\imageHeight\:null,\order\:2,\createdAt\:\2025-10-26T23:11:11.215Z\,\updatedAt\:\2025-10-26T23:11:11.215Z\},{\id\:\lbedul\,\codeBlockId\:\dcaec109-f4f1-4081-b56f-840163b8660b\,\code\:\{\\n \\\name\\\: \\\my-notes-mcp-server\\\,\\n \\\version\\\: \\\1.0.0\\\,\\n \\\description\\\: \\\\\\,\\n \\\main\\\: \\\index.js\\\,\\n \\\scripts\\\: {\\n \\\dev\\\: \\\tsx watch index.ts\\\,/* @cat-caption */\\n \\\start\\\: \\\tsx index.ts\\\,/* @cat-caption */\\n \\\build\\\: \\\tsc\\\,/* @cat-caption */\\n \\\serve\\\: \\\node dist/index.js\\\/* @cat-caption */\\n },\\n \\\keywords\\\: ,\\n \\\author\\\: \\\\\\,\\n \\\license\\\: \\\ISC\\\,\\n \\\type\\\: \\\commonjs\\\,\\n \\\dependencies\\\: {\\n \\\@modelcontextprotocol/sdk\\\: \\\^1.20.2\\\,\\n \\\express\\\: \\\^5.1.0\\\,\\n \\\zod\\\: \\\^3.25.76\\\\\n },\\n \\\devDependencies\\\: {\\n \\\@types/express\\\: \\\^5.0.4\\\,\\n \\\@types/node\\\: \\\^24.9.1\\\,\\n \\\tsx\\\: \\\^4.20.6\\\,\\n \\\typescript\\\: \\\^5.9.3\\\\\n }\\n}\\n\,\language\:\json\,\caption\:\打開 package.json\\n簡單設定一下一些常用命令\,\imageUrl\:\/api/object/1761486757700-mpcpk.png\,\imagePosition\:\top-right\,\imageFullWidth\:null,\imageWidth\:301,\imageHeight\:null,\order\:3,\createdAt\:\2025-10-26T23:11:11.215Z\,\updatedAt\:\2025-10-26T23:11:11.215Z\},{\id\:\dbibsd\,\codeBlockId\:\dcaec109-f4f1-4081-b56f-840163b8660b\,\code\:\{\\n \\\compilerOptions\\\: {\\n \\\target\\\: \\\ES2022\\\,\\n \\\module\\\: \\\ESNext\\\,\\n \\\moduleResolution\\\: \\\bundler\\\,\\n \\\esModuleInterop\\\: true,\\n \\\strict\\\: true,\\n \\\skipLibCheck\\\: true,\\n \\\outDir\\\: \\\./dist\\\\\n },\\n \\\include\\\: \\\*.ts\\\\\n}\,\language\:\json\,\caption\:\我打算使用 TypeScript\\n所以需要創建一個 tsconfig.json\\n確保 TypeScript 能正確被處理\,\imageUrl\:\/api/object/1761486980011-m3w3d.png\,\imagePosition\:\top-right\,\imageFullWidth\:null,\imageWidth\:300,\imageHeight\:null,\order\:4,\createdAt\:\2025-10-26T23:11:11.215Z\,\updatedAt\:\2025-10-26T23:11:11.215Z\},{\id\:\ztf8fa\,\codeBlockId\:\dcaec109-f4f1-4081-b56f-840163b8660b\,\code\:\\,\language\:\json\,\caption\:\OK! 現在在終端機輸入 npm run dev\\n應該能夠在瀏覽器中打開 http://localhost:8080/health\,\imageUrl\:\/api/object/1761486880277-2srt7m.png\,\imagePosition\:\top-right\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:5,\createdAt\:\2025-10-26T23:11:11.215Z\,\updatedAt\:\2025-10-26T23:11:11.215Z\}},\questionBlock\:null},{\id\:\20b4434e-d333-4f0a-8e72-21e06caf87ff\,\articleId\:\73b7d4c5-fc5e-49e2-a7df-c675c4703300\,\noteBlock\:{\id\:\20b4434e-d333-4f0a-8e72-21e06caf87ff\,\content\:\## 第二步,開始打造一個最簡單的 MCP 伺服器\},\codeBlock\:null,\questionBlock\:null},{\id\:\0f839051-64ff-4ea6-8bc6-f2980b572420\,\articleId\:\73b7d4c5-fc5e-49e2-a7df-c675c4703300\,\noteBlock\:null,\codeBlock\:{\id\:\0f839051-64ff-4ea6-8bc6-f2980b572420\,\content\:null,\chunks\:{\id\:\bkt7vo\,\codeBlockId\:\0f839051-64ff-4ea6-8bc6-f2980b572420\,\code\:\import express from express;\\nimport { McpServer } from @modelcontextprotocol/sdk/server/mcp.js;/* @cat-caption */\\n\\nconst app express();\\n\\napp.use(express.json());\\n\\n// 建立 MCP Server/* @cat-caption */\\nconst server new McpServer({/* @cat-caption */\\n name: my-notes-server,/* @cat-caption */\\n version: 1.0.0/* @cat-caption */\\n});/* @cat-caption */\\n\\napp.get(/health, (req, res) \u003e {\\n res.json({ \\n status: ok,\\n message: Server is running! \\n });\\n});\\n\\nconst port 8080;\\n\\napp.listen(port, () \u003e {\\n console.log(`🚀 伺服器運行中:http://localhost:${port}`);\\n console.log(`📍 健康檢查:http://localhost:${port}/health`);\\n});\,\language\:\typescript\,\caption\:\首先,創建 MCP Server\\n他是整個系統的核心,負責連接管理、訊息傳輸、主持著各種各樣的設定記載\\n\\n這邊的話只需要定義伺服器的名稱跟版本即可,他會顯示在客戶端上\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:0,\createdAt\:\2025-10-26T21:58:59.031Z\,\updatedAt\:\2025-10-26T21:58:59.031Z\},{\id\:\udxpgi\,\codeBlockId\:\0f839051-64ff-4ea6-8bc6-f2980b572420\,\code\:\import express from express;\\nimport { McpServer } from @modelcontextprotocol/sdk/server/mcp.js;\\n\\nconst app express();\\n\\napp.use(express.json());\\n\\n// 建立 MCP Server\\nconst server new McpServer({\\n name: my-notes-server,\\n version: 1.0.0\\n});\\n\\nserver.registerTool(/* @cat-caption */\\n list_files // 工具名稱 /* @cat-caption */\\n);/* @cat-caption */\\n\\napp.get(/health, (req, res) \u003e {\\n res.json({ \\n status: ok,\\n message: Server is running! \\n });\\n});\\n\\nconst port 8080;\\n\\napp.listen(port, () \u003e {\\n console.log(`🚀 伺服器運行中:http://localhost:${port}`);\\n console.log(`📍 健康檢查:http://localhost:${port}/health`);\\n});\,\language\:\typescript\,\caption\:\接著,我們就可以來定義第一個工具,讓 AI 能知道並使用他\\n\\nmcpServer 提供 registerTool 來註冊一個新工具\\n必須先傳入工具的名稱,這裡的名稱應當直覺、能夠被 LLM 直接理解他的功能\\n\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:1,\createdAt\:\2025-10-26T21:58:59.031Z\,\updatedAt\:\2025-10-26T21:58:59.031Z\},{\id\:\jzxaog\,\codeBlockId\:\0f839051-64ff-4ea6-8bc6-f2980b572420\,\code\:\import express from express;\\nimport { McpServer } from @modelcontextprotocol/sdk/server/mcp.js;\\nimport { z } from zod;/* @cat-caption */\\n\\nconst app express();\\n\\napp.use(express.json());\\n\\n// 建立 MCP Server\\nconst server new McpServer({\\n name: my-notes-server,\\n version: 1.0.0\\n});\\n\\nserver.registerTool(\\n list_files,\\n {/* @cat-caption */\\n title: 列出檔案,/* @cat-caption */\\n description: 列出所有 Markdown 檔案,/* @cat-caption */\\n inputSchema: {}, // 這裡不需要傳入任何參數 /* @cat-caption */\\n outputSchema: {/* @cat-caption */\\n files: z.array(z.string())/* @cat-caption */\\n }/* @cat-caption */\\n },\\n);\\n\\napp.get(/health, (req, res) \u003e {\\n res.json({ \\n status: ok,\\n message: Server is running! \\n });\\n});\\n\\nconst port 8080;\\n\\napp.listen(port, () \u003e {\\n console.log(`🚀 伺服器運行中:http://localhost:${port}`);\\n console.log(`📍 健康檢查:http://localhost:${port}/health`);\\n});\,\language\:\typescript\,\caption\:\再來, registerTool 的第二個參數傳入該工具的規格書\\n\\n- title,這個工具叫什麼\\n- description,這個工具是做什麼的\\n- inputSchema,這個工具需要傳入什麼\\n- outputSchema,這個工具會回傳什麼\\n\\n特別值得一提,inputSchema、outputSchema 使用 zod 來定義型別\\n以右邊程式碼為例\\n該工具應該會回傳一個物件\\n{\\n files: string\\n}\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:2,\createdAt\:\2025-10-26T21:58:59.031Z\,\updatedAt\:\2025-10-26T21:58:59.031Z\},{\id\:\kuxz5s\,\codeBlockId\:\0f839051-64ff-4ea6-8bc6-f2980b572420\,\code\:\$1f\,\language\:\typescript\,\caption\:\再來, registerTool 的第三個參數傳入該工具的實際執行函數\\n一但工具被調用,該 function 會被執行,回傳數值便會直接送回給 LLM。\\n\\n回傳數值必然會有 content ,他是一個陣列來告知 LLM 工具執行的結果\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:3,\createdAt\:\2025-10-26T21:58:59.031Z\,\updatedAt\:\2025-10-26T21:58:59.031Z\},{\id\:\cnrg3b\,\codeBlockId\:\0f839051-64ff-4ea6-8bc6-f2980b572420\,\code\:\$20\,\language\:\typescript\,\caption\:\回傳數值中的 content,不只可以回傳文字、還可以加入圖片、音訊、檔案等內容\\n\\n(右側程式碼僅為舉例)\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:4,\createdAt\:\2025-10-26T21:58:59.031Z\,\updatedAt\:\2025-10-26T21:58:59.031Z\},{\id\:\4p40tf\,\codeBlockId\:\0f839051-64ff-4ea6-8bc6-f2980b572420\,\code\:\$21\,\language\:\typescript\,\caption\:\回傳數值除了 content,還有 structuredContent\\n\\ncontent:給 AI「閱讀」的文字內容\\nstructuredContent:給程式「解析」的結構化資料\\n\\n通常來說,structuredContent 跟 content 的內容是一摩一樣的\\n只是 content 的內容可能透過 JSON.stringify 轉成純文字,讓 LLM 可以直接閱讀\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:5,\createdAt\:\2025-10-26T21:58:59.031Z\,\updatedAt\:\2025-10-26T21:58:59.031Z\},{\id\:\8pzf1\,\codeBlockId\:\0f839051-64ff-4ea6-8bc6-f2980b572420\,\code\:\$22\,\language\:\typescript\,\caption\:\現在,將整個 MCP 伺服器透過 Express 對外開放\\n讓 AI 客戶端可以連接\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:6,\createdAt\:\2025-10-26T21:58:59.031Z\,\updatedAt\:\2025-10-26T21:58:59.031Z\}},\questionBlock\:null},{\id\:\4b3ce204-3729-484f-8ff9-bf13c25592fd\,\articleId\:\73b7d4c5-fc5e-49e2-a7df-c675c4703300\,\noteBlock\:{\id\:\4b3ce204-3729-484f-8ff9-bf13c25592fd\,\content\:\## 第三步,測試看看 MCP 伺服器有沒有建立成功\},\codeBlock\:null,\questionBlock\:null},{\id\:\c76d32a8-efce-43fe-8487-f7ffa4b373c7\,\articleId\:\73b7d4c5-fc5e-49e2-a7df-c675c4703300\,\noteBlock\:null,\codeBlock\:{\id\:\c76d32a8-efce-43fe-8487-f7ffa4b373c7\,\content\:null,\chunks\:{\id\:\3gfrtp\,\codeBlockId\:\c76d32a8-efce-43fe-8487-f7ffa4b373c7\,\code\:\npx @modelcontextprotocol/inspector http://localhost:8080/mcp\,\language\:\bash\,\caption\:\這邊可以在終端機中直接打入一串指令,開啟 mcp 測試工具\,\imageUrl\:\/api/object/1761509352281-w752v.png\,\imagePosition\:\bottom-right\,\imageFullWidth\:null,\imageWidth\:600,\imageHeight\:null,\order\:0,\createdAt\:\2025-10-26T21:59:12.272Z\,\updatedAt\:\2025-10-26T21:59:12.272Z\},{\id\:\bkaqpq\,\codeBlockId\:\c76d32a8-efce-43fe-8487-f7ffa4b373c7\,\code\:\\,\language\:\bash\,\caption\:\輸入完成後,他會開啟一個網站\,\imageUrl\:\/api/object/1761509529108-fi8kxg.png\,\imagePosition\:\top-right\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:1,\createdAt\:\2025-10-26T21:59:12.272Z\,\updatedAt\:\2025-10-26T21:59:12.272Z\},{\id\:\pnll4\,\codeBlockId\:\c76d32a8-efce-43fe-8487-f7ffa4b373c7\,\code\:\\,\language\:\bash\,\caption\:\確保 Transport Type 是 Streamable HTTP\,\imageUrl\:\/api/object/1761509536230-xki4fd.png\,\imagePosition\:\top-right\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:2,\createdAt\:\2025-10-26T21:59:12.272Z\,\updatedAt\:\2025-10-26T21:59:12.272Z\},{\id\:\m3xa3\,\codeBlockId\:\c76d32a8-efce-43fe-8487-f7ffa4b373c7\,\code\:\\,\language\:\bash\,\caption\:\確保 URL 是 http://localhost:8080/mcp \,\imageUrl\:\/api/object/1761509542587-ab8cr.png\,\imagePosition\:\top-right\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:3,\createdAt\:\2025-10-26T21:59:12.272Z\,\updatedAt\:\2025-10-26T21:59:12.272Z\},{\id\:\p95aod\,\codeBlockId\:\c76d32a8-efce-43fe-8487-f7ffa4b373c7\,\code\:\\,\language\:\bash\,\caption\:\按下 Connect\,\imageUrl\:\/api/object/1761509547648-6cqpe.png\,\imagePosition\:\top-right\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:4,\createdAt\:\2025-10-26T21:59:12.272Z\,\updatedAt\:\2025-10-26T21:59:12.272Z\},{\id\:\nppe6f\,\codeBlockId\:\c76d32a8-efce-43fe-8487-f7ffa4b373c7\,\code\:\\,\language\:\bash\,\caption\:\你應該可以看到連接成功,你可以點擊中間的 List Tools\,\imageUrl\:\/api/object/1761509638381-0eq9v4.png\,\imagePosition\:\top-right\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:5,\createdAt\:\2025-10-26T21:59:12.272Z\,\updatedAt\:\2025-10-26T21:59:12.272Z\},{\id\:\04c7mo\,\codeBlockId\:\c76d32a8-efce-43fe-8487-f7ffa4b373c7\,\code\:\\,\language\:\bash\,\caption\:\應該可以看到我們創建的工具「list files」\,\imageUrl\:\/api/object/1761509776288-c3un4f.png\,\imagePosition\:\top-right\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:6,\createdAt\:\2025-10-26T21:59:12.272Z\,\updatedAt\:\2025-10-26T21:59:12.272Z\}},\questionBlock\:null},{\id\:\1e4ed363-900f-41d2-b797-bbb4c1c59f4c\,\articleId\:\73b7d4c5-fc5e-49e2-a7df-c675c4703300\,\noteBlock\:{\id\:\1e4ed363-900f-41d2-b797-bbb4c1c59f4c\,\content\:\## 第四步,實作檔案系統讀取\\n\\n我們來把 list\\\\_files 的功能給實際做出來\\n由於本教學的重點是 MCP,我會用很快的速度帶過去,就不特別提細節了\},\codeBlock\:null,\questionBlock\:null},{\id\:\b4b382e5-446d-47ba-b214-aa91814e684a\,\articleId\:\73b7d4c5-fc5e-49e2-a7df-c675c4703300\,\noteBlock\:null,\codeBlock\:{\id\:\b4b382e5-446d-47ba-b214-aa91814e684a\,\content\:null,\chunks\:{\id\:\g6z65\,\codeBlockId\:\b4b382e5-446d-47ba-b214-aa91814e684a\,\code\:\\,\language\:\typescript\,\caption\:\為了方便測試,我建議在專案資料夾中建立一個 notes 資料夾\\n我們的 mcp 工具會把該資料夾當作我們的筆記目錄\,\imageUrl\:\/api/object/1761510076118-84d9y9.png\,\imagePosition\:\top-right\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:0,\createdAt\:\2025-10-26T20:36:04.578Z\,\updatedAt\:\2025-10-26T20:36:04.578Z\},{\id\:\hija9\,\codeBlockId\:\b4b382e5-446d-47ba-b214-aa91814e684a\,\code\:\$23\,\language\:\typescript\,\caption\:\我這邊使用 fs.readdir() 讀取目錄\\n過濾出 .md 檔案\\n然後回傳給 LLM\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:1,\createdAt\:\2025-10-26T20:36:04.578Z\,\updatedAt\:\2025-10-26T20:36:04.578Z\},{\id\:\7syhdx\,\codeBlockId\:\b4b382e5-446d-47ba-b214-aa91814e684a\,\code\:\\,\language\:\typescript\,\caption\:\在 /notes 資料夾中,放一些檔案方便測試\,\imageUrl\:\/api/object/1761510340232-hlw1x.png\,\imagePosition\:\top-right\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:2,\createdAt\:\2025-10-26T20:36:04.578Z\,\updatedAt\:\2025-10-26T20:36:04.578Z\},{\id\:\y0nkb\,\codeBlockId\:\b4b382e5-446d-47ba-b214-aa91814e684a\,\code\:\\,\language\:\typescript\,\caption\:\回到剛剛提到的 mcp 測試網站(來自 第三步,測試看看 MCP 伺服器有沒有建立成功)\\n\\n你可以執行看看 list_files 工具\\n應該可以看到所有檔案\,\imageUrl\:\/api/object/1761510448096-cs6gqc.png\,\imagePosition\:\top-right\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:3,\createdAt\:\2025-10-26T20:36:04.578Z\,\updatedAt\:\2025-10-26T20:36:04.578Z\}},\questionBlock\:null},{\id\:\75f79304-4d7e-4e20-8a44-69c9283225c1\,\articleId\:\73b7d4c5-fc5e-49e2-a7df-c675c4703300\,\noteBlock\:{\id\:\75f79304-4d7e-4e20-8a44-69c9283225c1\,\content\:\## 第五步,接到 ChatGPT 或者 Claude 的前置作業:Ngrok\\n\\n恭喜你!現在我們的 MCP Server 已經跑起來了,但有個問題:**ChatGPT 在雲端,怎麼連到你電腦上的 localhost:3000?**\\n\\n(不知道為什麼讓我想到這個工程師老梗\\n\\n\u003cimg height\\\442\\\ width\\\474\\\ alt\\\Threads 笑話\\\ title\\\Threads 笑話\\\ src\\\/api/object/1761511143046-jx4kjg.png\\\ /\u003e\\n\\n外部的電腦是沒辦法連線到我 localhost 的網站,該怎麼辦呢?\\n難道只能部署到雲端嗎?\\n\\n這邊推薦給大家一個超讚的免費服務 **ngrok**\u0026#x20;\\n他可以開了一個「臨時的公開網址」,外面的人可以把信件寄到這公開網址,而 ngrok 會把收到的信再轉交給你家裡的電腦。\\n\\n這樣就可以讓 ChatGPT、Claude 直接連到你的電腦的 MCP 伺服器了\},\codeBlock\:null,\questionBlock\:null},{\id\:\cc44c8d8-ffed-4046-9105-fb6be6e92740\,\articleId\:\73b7d4c5-fc5e-49e2-a7df-c675c4703300\,\noteBlock\:null,\codeBlock\:{\id\:\cc44c8d8-ffed-4046-9105-fb6be6e92740\,\content\:null,\chunks\:{\id\:\9a2iis\,\codeBlockId\:\cc44c8d8-ffed-4046-9105-fb6be6e92740\,\code\:\\,\language\:\typescript\,\caption\:\首先,打開 ngrok 的服務\\nhttps://ngrok.com/\\n並且登入\,\imageUrl\:\/api/object/1761511968013-aht4ph.png\,\imagePosition\:\top-right\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:0,\createdAt\:\2025-10-26T21:59:33.559Z\,\updatedAt\:\2025-10-26T21:59:33.559Z\},{\id\:\l475xh\,\codeBlockId\:\cc44c8d8-ffed-4046-9105-fb6be6e92740\,\code\:\\,\language\:\typescript\,\caption\:\造著他的方式安裝他,以 macos homebrew 為例\\n\\n打開終端機\\n- 先輸入 brew install ngrok 安裝整個程式碼\\n- 然後輸入ngrok config add-authtoken XXX 登入你的帳號\\n\\n這樣就完成安裝了\,\imageUrl\:\/api/object/1761511962223-g0gsqt.png\,\imagePosition\:\top-right\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:1,\createdAt\:\2025-10-26T21:59:33.559Z\,\updatedAt\:\2025-10-26T21:59:33.559Z\},{\id\:\ha3fkf\,\codeBlockId\:\cc44c8d8-ffed-4046-9105-fb6be6e92740\,\code\:\ngrok http 8080\,\language\:\bash\,\caption\:\接著重點來了\\n我們要將本地端的 HTTP 服務,8080 Port 給公開到網路上\\n請輸入右邊這個指令到終端機中\,\imageUrl\:\/api/object/1761512133770-5c69bk.png\,\imagePosition\:\top-right\,\imageFullWidth\:null,\imageWidth\:300,\imageHeight\:null,\order\:2,\createdAt\:\2025-10-26T21:59:33.559Z\,\updatedAt\:\2025-10-26T21:59:33.559Z\},{\id\:\8iafea\,\codeBlockId\:\cc44c8d8-ffed-4046-9105-fb6be6e92740\,\code\:\\,\language\:\bash\,\caption\:\Ok! 他接下來會生成一個網址\\n恭喜你,這就是你 MCP 的網址,把它複製下來\\n終端機不要關掉,一但關掉這個網址會失效\\n\\n任何針對這個網址的請求,都會直接送到你電腦中的 localhost:8080\,\imageUrl\:\/api/object/1761512224769-6lf9wo.png\,\imagePosition\:\top-right\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:3,\createdAt\:\2025-10-26T21:59:33.559Z\,\updatedAt\:\2025-10-26T21:59:33.559Z\}},\questionBlock\:null},{\id\:\fe0a4b58-3f6c-4277-af98-4b7677eae530\,\articleId\:\73b7d4c5-fc5e-49e2-a7df-c675c4703300\,\noteBlock\:{\id\:\fe0a4b58-3f6c-4277-af98-4b7677eae530\,\content\:\## 第六步之一,接到 ChatGPT\\n\\n**請注意!ChatGPT 的 MCP 功能需要訂閱(每個月 20 美金方案以上)才可以使用**\},\codeBlock\:null,\questionBlock\:null},{\id\:\9c5a4db9-f0f5-4f76-887e-2470cb2685bc\,\articleId\:\73b7d4c5-fc5e-49e2-a7df-c675c4703300\,\noteBlock\:null,\codeBlock\:{\id\:\9c5a4db9-f0f5-4f76-887e-2470cb2685bc\,\content\:null,\chunks\:{\id\:\v6jakp\,\codeBlockId\:\9c5a4db9-f0f5-4f76-887e-2470cb2685bc\,\code\:\\,\language\:\typescript\,\caption\:\打開你的 ChatGPT,並點擊左下角\,\imageUrl\:\/api/object/1761512803188-neus5u.png\,\imagePosition\:\top-right\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:0,\createdAt\:\2025-10-26T21:18:03.859Z\,\updatedAt\:\2025-10-26T21:18:03.859Z\},{\id\:\ihu0m6\,\codeBlockId\:\9c5a4db9-f0f5-4f76-887e-2470cb2685bc\,\code\:\\,\language\:\typescript\,\caption\:\選擇「設定」\,\imageUrl\:\/api/object/1761512911016-kvof2x.png\,\imagePosition\:\top-right\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:1,\createdAt\:\2025-10-26T21:18:03.859Z\,\updatedAt\:\2025-10-26T21:18:03.859Z\},{\id\:\czcoyt\,\codeBlockId\:\9c5a4db9-f0f5-4f76-887e-2470cb2685bc\,\code\:\\,\language\:\typescript\,\caption\:\選擇「應用程式和連接器」\,\imageUrl\:\/api/object/1761512956221-f32pke.png\,\imagePosition\:\top-right\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:2,\createdAt\:\2025-10-26T21:18:03.859Z\,\updatedAt\:\2025-10-26T21:18:03.859Z\},{\id\:\21zgbr\,\codeBlockId\:\9c5a4db9-f0f5-4f76-887e-2470cb2685bc\,\code\:\\,\language\:\typescript\,\caption\:\滑到最底下,選擇「進階設定」\,\imageUrl\:\/api/object/1761512977478-qhbhql.png\,\imagePosition\:\top-right\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:3,\createdAt\:\2025-10-26T21:18:03.859Z\,\updatedAt\:\2025-10-26T21:18:03.859Z\},{\id\:\9byy9a\,\codeBlockId\:\9c5a4db9-f0f5-4f76-887e-2470cb2685bc\,\code\:\\,\language\:\typescript\,\caption\:\開啟「開發者模式」,並回到上一步\,\imageUrl\:\/api/object/1761513005018-wz903g.png\,\imagePosition\:\top-right\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:4,\createdAt\:\2025-10-26T21:18:03.859Z\,\updatedAt\:\2025-10-26T21:18:03.859Z\},{\id\:\j6v2pg\,\codeBlockId\:\9c5a4db9-f0f5-4f76-887e-2470cb2685bc\,\code\:\\,\language\:\typescript\,\caption\:\點擊「建立」\,\imageUrl\:\/api/object/1761513037423-plqua.png\,\imagePosition\:\top-right\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:5,\createdAt\:\2025-10-26T21:18:03.859Z\,\updatedAt\:\2025-10-26T21:18:03.859Z\},{\id\:\50ia6b\,\codeBlockId\:\9c5a4db9-f0f5-4f76-887e-2470cb2685bc\,\code\:\\,\language\:\typescript\,\caption\:\輸入這個 MCP 伺服器的名稱\,\imageUrl\:\/api/object/1761513116852-kuf84.png\,\imagePosition\:\top-right\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:6,\createdAt\:\2025-10-26T21:18:03.859Z\,\updatedAt\:\2025-10-26T21:18:03.859Z\},{\id\:\0uc8a7\,\codeBlockId\:\9c5a4db9-f0f5-4f76-887e-2470cb2685bc\,\code\:\\,\language\:\typescript\,\caption\:\重點來了!\\n貼上 ngrok 的網址\\n記得!!最後要加上 /mcp\,\imageUrl\:\/api/object/1761513135409-a6yfdw.png\,\imagePosition\:\top-right\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:7,\createdAt\:\2025-10-26T21:18:03.859Z\,\updatedAt\:\2025-10-26T21:18:03.859Z\},{\id\:\vjvcpf\,\codeBlockId\:\9c5a4db9-f0f5-4f76-887e-2470cb2685bc\,\code\:\\,\language\:\typescript\,\caption\:\把「驗證」改成「無驗證」\,\imageUrl\:\/api/object/1761513168499-jgc48.png\,\imagePosition\:\top-right\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:8,\createdAt\:\2025-10-26T21:18:03.859Z\,\updatedAt\:\2025-10-26T21:18:03.859Z\},{\id\:\hatewe\,\codeBlockId\:\9c5a4db9-f0f5-4f76-887e-2470cb2685bc\,\code\:\\,\language\:\typescript\,\caption\:\點擊「我了解並繼續」,最後選擇建立\,\imageUrl\:\/api/object/1761513187883-w1prey.png\,\imagePosition\:\top-right\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:9,\createdAt\:\2025-10-26T21:18:03.859Z\,\updatedAt\:\2025-10-26T21:18:03.859Z\},{\id\:\tf2heo\,\codeBlockId\:\9c5a4db9-f0f5-4f76-887e-2470cb2685bc\,\code\:\\,\language\:\typescript\,\caption\:\順利的話,就可以看到我們的 MCP 服務\,\imageUrl\:\/api/object/1761513255821-oonrvr.png\,\imagePosition\:\top-right\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:10,\createdAt\:\2025-10-26T21:18:03.859Z\,\updatedAt\:\2025-10-26T21:18:03.859Z\},{\id\:\hmm7kj\,\codeBlockId\:\9c5a4db9-f0f5-4f76-887e-2470cb2685bc\,\code\:\\,\language\:\typescript\,\caption\:\到這邊就可以直接跟 ChatGPT 聊天,順利的話,恭喜成功串接 MCP 到 ChatGPT 上頭!\,\imageUrl\:\/api/object/1761513457153-r7sdyg.png\,\imagePosition\:\top-right\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:11,\createdAt\:\2025-10-26T21:18:03.859Z\,\updatedAt\:\2025-10-26T21:18:03.859Z\}},\questionBlock\:null},{\id\:\081534ff-b5ef-44a1-89c6-78b4c2849cd4\,\articleId\:\73b7d4c5-fc5e-49e2-a7df-c675c4703300\,\noteBlock\:{\id\:\081534ff-b5ef-44a1-89c6-78b4c2849cd4\,\content\:\## 第六步之二,接到 **Claude**\\n\\n**請注意!Claude 的 MCP 功能需要訂閱(每個月 20 美金方案以上)才可以使用**\\n\\n**Claude 的邏輯跟 ChatGPT 很像,**\},\codeBlock\:null,\questionBlock\:null},{\id\:\37d7c27b-17ce-485d-860c-5ca3b730d2f2\,\articleId\:\73b7d4c5-fc5e-49e2-a7df-c675c4703300\,\noteBlock\:null,\codeBlock\:{\id\:\37d7c27b-17ce-485d-860c-5ca3b730d2f2\,\content\:null,\chunks\:{\id\:\vdj45\,\codeBlockId\:\37d7c27b-17ce-485d-860c-5ca3b730d2f2\,\code\:\\,\language\:\typescript\,\caption\:\打開你的 Claude,並點擊左下角\\n\,\imageUrl\:\/api/object/1761513925185-c1km7.png\,\imagePosition\:\top-right\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:0,\createdAt\:\2025-10-26T21:27:58.953Z\,\updatedAt\:\2025-10-26T21:27:58.953Z\},{\id\:\hhi82\,\codeBlockId\:\37d7c27b-17ce-485d-860c-5ca3b730d2f2\,\code\:\\,\language\:\typescript\,\caption\:\選擇「Settings」\\n\,\imageUrl\:\/api/object/1761513933350-sgvzk.png\,\imagePosition\:\top-right\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:1,\createdAt\:\2025-10-26T21:27:58.953Z\,\updatedAt\:\2025-10-26T21:27:58.953Z\},{\id\:\l2x9v\,\codeBlockId\:\37d7c27b-17ce-485d-860c-5ca3b730d2f2\,\code\:\\,\language\:\typescript\,\caption\:\選擇「Connectors」並點擊「Add custom connector」\,\imageUrl\:\/api/object/1761513954161-tdtm1.png\,\imagePosition\:\top-right\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:2,\createdAt\:\2025-10-26T21:27:58.953Z\,\updatedAt\:\2025-10-26T21:27:58.953Z\},{\id\:\5s6p4\,\codeBlockId\:\37d7c27b-17ce-485d-860c-5ca3b730d2f2\,\code\:\\,\language\:\typescript\,\caption\:\輸入這個 MCP 伺服器的名稱\,\imageUrl\:\/api/object/1761513994611-89x6d.png\,\imagePosition\:\top-right\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:3,\createdAt\:\2025-10-26T21:27:58.953Z\,\updatedAt\:\2025-10-26T21:27:58.953Z\},{\id\:\9w14jc\,\codeBlockId\:\37d7c27b-17ce-485d-860c-5ca3b730d2f2\,\code\:\\,\language\:\typescript\,\caption\:\重點來了!\\n貼上 ngrok 的網址\\n記得!!最後要加上 /mcp\\n最後按下 Add\,\imageUrl\:\/api/object/1761514015782-6yn6bt.png\,\imagePosition\:\top-right\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:4,\createdAt\:\2025-10-26T21:27:58.953Z\,\updatedAt\:\2025-10-26T21:27:58.953Z\},{\id\:\jvakn\,\codeBlockId\:\37d7c27b-17ce-485d-860c-5ca3b730d2f2\,\code\:\\,\language\:\typescript\,\caption\:\順利的話,就可以看到我們的 MCP 服務\\n\,\imageUrl\:\/api/object/1761514027712-k4tfzp.png\,\imagePosition\:\top-right\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:5,\createdAt\:\2025-10-26T21:27:58.953Z\,\updatedAt\:\2025-10-26T21:27:58.953Z\},{\id\:\fv1qwm\,\codeBlockId\:\37d7c27b-17ce-485d-860c-5ca3b730d2f2\,\code\:\\,\language\:\typescript\,\caption\:\到這邊就可以直接跟 ChatGPT 聊天,順利的話,恭喜成功串接 MCP 到 Claude 上頭!\,\imageUrl\:\/api/object/1761514062601-4aap12i.png\,\imagePosition\:\top-right\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:6,\createdAt\:\2025-10-26T21:27:58.953Z\,\updatedAt\:\2025-10-26T21:27:58.953Z\}},\questionBlock\:null},{\id\:\3ba6f0e8-5f12-46f0-84c7-cc1940c43750\,\articleId\:\73b7d4c5-fc5e-49e2-a7df-c675c4703300\,\noteBlock\:{\id\:\3ba6f0e8-5f12-46f0-84c7-cc1940c43750\,\content\:\## 你已經學會了\\n\\n**理解 MCP 解決的核心問題**\\n\\n* 為什麼 REST API 不夠\\n* AI 需要自動發現和使用工具的能力\\n* 標準化協定的重要性\\n\\n**掌握 Tools 的設計哲學**\\n\\n* Model-Controlled:AI 決定何時使用\\n* Action-Oriented:執行動作、產生結果\\n* Schema-Defined:明確的輸入輸出規格\\n\\n**建立第一個實用的 MCP Server**\\n\\n* 完整的 list\\\\_notes tool 實作\\n* 錯誤處理和友善的訊息\\n\\n**整合到真實的 AI 應用**\\n\\n* 使用 ngrok 建立公開 URL\\n* 連接到 ChatGPT、Claude\\n* 實際對話測試\\n\\n## 但這只是開始⋯⋯\\n\\n你現在擁有了一個可運作的 MCP Server,但它還很基礎。\\n下一篇則會更深入 MCP 的精髓,講解 Resources 和 Prompts 的功能\\n我會分享很多開發 MCP 必定會遇到大問題以及解法,好比身份驗證、很多必須避開的坑\\n\\n\u003e *在 AI 時代,重點不是寫多少 code,而是理解問題、設計方案、驗證效果。Code 可以讓 AI 幫你寫,思考無法委託。*\\n\u003e 準備好了嗎?讓我們在下篇繼續深入 MCP 的世界!\},\codeBlock\:null,\questionBlock\:null}},{\id\:\8b0bf0c6-6a00-4764-b686-d0c9c2a4ca28\,\title\:\智能合約 Solidity 教學 (下) - Web3.js 實際開發一個 DApp 去中心化應用前端\,\content\:\這篇文章繼承了上一篇 智能合約 Solidity 教學 (中)\\n我們開發一個 DApp 的前端,理解如何透過 Web3.js 連接區塊鏈上我們所部署的合約。\,\blockIndexIds\:\d67d580e-30a9-4e29-b9dd-bb29bf5371e3\,\ff560ada-b1f7-4638-95cf-c7fca8d84ccc\,\dd69ce64-ff10-43d4-8604-9aa20cd2e9be\,\7ccae90a-9e30-4260-a632-57603b8b99c7\,\slug\:null,\metaDescription\:null,\ogImage\:null,\privacy\:\PUBLIC\,\allowedEmails\:,\createdAt\:\2025-01-29T16:01:29.623Z\,\updatedAt\:\2025-11-21T22:47:26.050Z\,\authorId\:\24c3f178-4a5b-447d-b586-16d7d459c606\,\viewCount\:750,\newsletterSentAt\:\2025-10-19T09:55:10.552Z\,\blocks\:{\id\:\d67d580e-30a9-4e29-b9dd-bb29bf5371e3\,\articleId\:\8b0bf0c6-6a00-4764-b686-d0c9c2a4ca28\,\noteBlock\:{\id\:\d67d580e-30a9-4e29-b9dd-bb29bf5371e3\,\content\:\$24\},\codeBlock\:null,\questionBlock\:null},{\id\:\ff560ada-b1f7-4638-95cf-c7fca8d84ccc\,\articleId\:\8b0bf0c6-6a00-4764-b686-d0c9c2a4ca28\,\noteBlock\:null,\codeBlock\:{\id\:\ff560ada-b1f7-4638-95cf-c7fca8d84ccc\,\content\:\{\\\id\\\:\\\paypd\\\,\\\code\\\:\\\\\\,\\\caption\\\:\\\在上次的教學範例中,HelloWorld 合約已經部署在 Sepolia 網路上。\\\\n我們仍然需要兩項關鍵資訊才能在前端與之互動\\\,\\\language\\\:\\\typescript\\\,\\\image\\\:{\\\url\\\:\\\/api/object/1738168285382-bj4aue.png\\\,\\\position\\\:\\\center\\\,\\\fullWidth\\\:true,\\\width\\\:200}},{\\\id\\\:\\\mc9vh\\\,\\\code\\\:\\\\\\,\\\caption\\\:\\\首先是合約 ABI (Application Binary Interface)\\\\n可以在 Remix 部署成功後,點開「Solidity Compiler」下方的「ABI」標籤,取得 ABI 資訊(是一段 JSON 格式)。\\\,\\\language\\\:\\\typescript\\\,\\\image\\\:{\\\url\\\:\\\/api/object/1738168337439-1qlaz.png\\\,\\\position\\\:\\\center\\\,\\\fullWidth\\\:true,\\\width\\\:200}},{\\\id\\\:\\\5gb2gr\\\,\\\code\\\:\\\\\\,\\\caption\\\:\\\再來就是合約地址\\\\n你可以在我們剛剛 Deploy 好的合約中,點擊複製按鈕\\\\n\\\,\\\language\\\:\\\typescript\\\,\\\image\\\:{\\\url\\\:\\\/api/object/1738168485361-3s8n9.png\\\,\\\position\\\:\\\center\\\,\\\fullWidth\\\:true,\\\width\\\:200}}\,\chunks\:{\id\:\paypd\,\codeBlockId\:\ff560ada-b1f7-4638-95cf-c7fca8d84ccc\,\code\:\\,\language\:\typescript\,\caption\:\在上次的教學範例中,HelloWorld 合約已經部署在 Sepolia 網路上。\\n我們仍然需要兩項關鍵資訊才能在前端與之互動\,\imageUrl\:\/api/object/1738168285382-bj4aue.png\,\imagePosition\:\center\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:0,\createdAt\:\2025-10-19T01:46:54.206Z\,\updatedAt\:\2025-10-19T01:46:54.206Z\},{\id\:\mc9vh\,\codeBlockId\:\ff560ada-b1f7-4638-95cf-c7fca8d84ccc\,\code\:\\,\language\:\typescript\,\caption\:\首先是合約 ABI (Application Binary Interface)\\n可以在 Remix 部署成功後,點開「Solidity Compiler」下方的「ABI」標籤,取得 ABI 資訊(是一段 JSON 格式)。\,\imageUrl\:\/api/object/1738168337439-1qlaz.png\,\imagePosition\:\center\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:1,\createdAt\:\2025-10-19T01:46:54.206Z\,\updatedAt\:\2025-10-19T01:46:54.206Z\},{\id\:\5gb2gr\,\codeBlockId\:\ff560ada-b1f7-4638-95cf-c7fca8d84ccc\,\code\:\\,\language\:\typescript\,\caption\:\再來就是合約地址\\n你可以在我們剛剛 Deploy 好的合約中,點擊複製按鈕\\n\,\imageUrl\:\/api/object/1738168485361-3s8n9.png\,\imagePosition\:\center\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:2,\createdAt\:\2025-10-19T01:46:54.206Z\,\updatedAt\:\2025-10-19T01:46:54.206Z\}},\questionBlock\:null},{\id\:\dd69ce64-ff10-43d4-8604-9aa20cd2e9be\,\articleId\:\8b0bf0c6-6a00-4764-b686-d0c9c2a4ca28\,\noteBlock\:{\id\:\dd69ce64-ff10-43d4-8604-9aa20cd2e9be\,\content\:\## 四、撰寫前端程式碼\},\codeBlock\:null,\questionBlock\:null},{\id\:\7ccae90a-9e30-4260-a632-57603b8b99c7\,\articleId\:\8b0bf0c6-6a00-4764-b686-d0c9c2a4ca28\,\noteBlock\:null,\codeBlock\:{\id\:\7ccae90a-9e30-4260-a632-57603b8b99c7\,\content\:\$25\,\chunks\:{\id\:\l5rh7f\,\codeBlockId\:\7ccae90a-9e30-4260-a632-57603b8b99c7\,\code\:\\u003c!DOCTYPE html\u003e\\n\u003chtml lang\\\en\\\\u003e\\n\u003chead\u003e\\n \u003cmeta charset\\\UTF-8\\\\u003e\\n \u003cmeta name\\\viewport\\\ content\\\widthdevice-width, initial-scale1.0\\\\u003e\\n \u003ctitle\u003eDocument\u003c/title\u003e\\n\u003c/head\u003e\\n\u003cbody\u003e\\n \\n\u003c/body\u003e\\n\u003c/html\u003e\,\language\:\html\,\caption\:\打開 `index.html` \\n替換成最基本的 HTML,我們一步一步來建構 Web3 應用\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:0,\createdAt\:\2025-10-19T01:46:57.036Z\,\updatedAt\:\2025-10-19T01:46:57.036Z\},{\id\:\4ca7hc\,\codeBlockId\:\7ccae90a-9e30-4260-a632-57603b8b99c7\,\code\:\\u003c!DOCTYPE html\u003e\\n\u003chtml lang\\\en\\\\u003e\\n\\n\u003chead\u003e\\n \u003cmeta charset\\\UTF-8\\\\u003e\\n \u003cmeta name\\\viewport\\\ content\\\widthdevice-width, initial-scale1.0\\\\u003e\\n \u003ctitle\u003eDocument\u003c/title\u003e\\n \u003cscript type\\\module\\\\u003e/* @cat-caption */\\n // ... 之後的程式碼就放這吧/* @cat-caption */\\n \u003c/script\u003e/* @cat-caption */\\n\u003c/head\u003e\\n\\n\u003cbody\u003e\\n\\n\u003c/body\u003e\\n\\n\u003c/html\u003e\,\language\:\html\,\caption\:\為了方便教學,我會將 JavaScript 直接寫在 HTML 中\\n在正式專案開發時,這樣做程式碼會長到很難閱讀,要特別注意\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:1,\createdAt\:\2025-10-19T01:46:57.036Z\,\updatedAt\:\2025-10-19T01:46:57.036Z\},{\id\:\i1xm1\,\codeBlockId\:\7ccae90a-9e30-4260-a632-57603b8b99c7\,\code\:\\u003c!DOCTYPE html\u003e\\n\u003chtml lang\\\en\\\\u003e\\n\\n\u003chead\u003e\\n \u003cmeta charset\\\UTF-8\\\\u003e\\n \u003cmeta name\\\viewport\\\ content\\\widthdevice-width, initial-scale1.0\\\\u003e\\n \u003ctitle\u003eDocument\u003c/title\u003e\\n \u003cscript type\\\module\\\\u003e\\n import Web3 from \\\web3\\\;/* @cat-caption */\\n \u003c/script\u003e\\n\u003c/head\u003e\\n\\n\u003cbody\u003e\\n\\n\u003c/body\u003e\\n\\n\u003c/html\u003e\,\language\:\html\,\caption\:\還記得我們剛剛安裝的 web3.js 嗎?\\n這是別人已經設計、封裝好的程式碼,我們可以簡單的使用 import 來把它加入我們的程式碼\\n\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:2,\createdAt\:\2025-10-19T01:46:57.036Z\,\updatedAt\:\2025-10-19T01:46:57.036Z\},{\id\:\iap70s\,\codeBlockId\:\7ccae90a-9e30-4260-a632-57603b8b99c7\,\code\:\\u003c!DOCTYPE html\u003e\\n\u003chtml lang\\\en\\\\u003e\\n\\n\u003chead\u003e\\n \u003cmeta charset\\\UTF-8\\\\u003e\\n \u003cmeta name\\\viewport\\\ content\\\widthdevice-width, initial-scale1.0\\\\u003e\\n \u003ctitle\u003eDocument\u003c/title\u003e\\n \u003cscript type\\\module\\\\u003e\\n import Web3 from \\\web3\\\;\\n const contractAddress \\\0xFEA4f2C9B99Df0B2b7da67e60371BE4275C3d749\\\;/* @cat-caption */\\n\\n const contractABI /* @cat-caption */\\n {/* @cat-caption */\\n inputs: /* @cat-caption */\\n ..../* @cat-caption */\\n ,/* @cat-caption */\\n stateMutability: \\\nonpayable\\\,/* @cat-caption */\\n type: \\\constructor\\\,/* @cat-caption */\\n },/* @cat-caption */\\n .../* @cat-caption */\\n ;/* @cat-caption */\\n \u003c/script\u003e\\n\u003c/head\u003e\\n\\n\u003cbody\u003e\\n\\n\u003c/body\u003e\\n\\n\u003c/html\u003e\,\language\:\html\,\caption\:\將剛剛獲得的合約地址與合約 ABI 貼上去\\n這個程式碼會瞬間變超級長\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:3,\createdAt\:\2025-10-19T01:46:57.036Z\,\updatedAt\:\2025-10-19T01:46:57.036Z\},{\id\:\r0xbg4\,\codeBlockId\:\7ccae90a-9e30-4260-a632-57603b8b99c7\,\code\:\// abi.js\\nexport const contractABI /* @cat-caption */\\n {\\n inputs: \\n ....\\n ,\\n stateMutability: \\\nonpayable\\\,\\n type: \\\constructor\\\,\\n },\\n ...\\n;\,\language\:\javascript\,\caption\:\contractABI 真的太長了!!\\n讓我們把它獨立成一個檔案吧\\n\\n在 src 資料夾中創建新的檔案 `abi.js`\\n並貼上剛剛的 合約 ABI\\n記得在最前面加上 export\\n這表示允許其他程式檔案使用這個變數\,\imageUrl\:\/api/object/1738170024095-it7ta.png\,\imagePosition\:\bottom-right\,\imageFullWidth\:null,\imageWidth\:200,\imageHeight\:null,\order\:4,\createdAt\:\2025-10-19T01:46:57.036Z\,\updatedAt\:\2025-10-19T01:46:57.036Z\},{\id\:\zzvt5\,\codeBlockId\:\7ccae90a-9e30-4260-a632-57603b8b99c7\,\code\:\\u003c!DOCTYPE html\u003e\\n\u003chtml lang\\\en\\\\u003e\\n\\n\u003chead\u003e\\n \u003cmeta charset\\\UTF-8\\\\u003e\\n \u003cmeta name\\\viewport\\\ content\\\widthdevice-width, initial-scale1.0\\\\u003e\\n \u003ctitle\u003eDocument\u003c/title\u003e\\n \u003cscript type\\\module\\\\u003e\\n import Web3 from \\\web3\\\;\\n import { contractABI } from \\\./src/abi.js\\\;/* @cat-caption */\\n\\n const contractAddress \\\0xFEA4f2C9B99Df0B2b7da67e60371BE4275C3d749\\\;\\n \u003c/script\u003e\\n\u003c/head\u003e\\n\\n\u003cbody\u003e\\n\\n\u003c/body\u003e\\n\\n\u003c/html\u003e\,\language\:\html\,\caption\:\回到 index.html\\n透過 import 將剛剛宣告的變數導進來\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:5,\createdAt\:\2025-10-19T01:46:57.036Z\,\updatedAt\:\2025-10-19T01:46:57.036Z\},{\id\:\srv12j\,\codeBlockId\:\7ccae90a-9e30-4260-a632-57603b8b99c7\,\code\:\\u003c!DOCTYPE html\u003e\\n\u003chtml lang\\\en\\\\u003e\\n\\n\u003chead\u003e\\n \u003cmeta charset\\\UTF-8\\\\u003e\\n \u003cmeta name\\\viewport\\\ content\\\widthdevice-width, initial-scale1.0\\\\u003e\\n \u003ctitle\u003eDocument\u003c/title\u003e\\n \u003cscript type\\\module\\\\u003e\\n import Web3 from \\\web3\\\;\\n import { contractABI } from \\\./src/abi.js\\\;\\n\\n const contractAddress \\\0xFEA4f2C9B99Df0B2b7da67e60371BE4275C3d749\\\;\\n\\n \u003c/script\u003e\\n\u003c/head\u003e\\n\\n\u003cbody\u003e\\n \u003cdiv\u003e/* @cat-caption */\\n \u003cbutton id\\\connectButton\\\\u003e連線錢包\u003c/button\u003e/* @cat-caption */\\n \u003c/div\u003e/* @cat-caption */\\n\u003c/body\u003e\\n\\n\u003c/html\u003e\,\language\:\html\,\caption\:\設計一個按鈕來連結用戶錢包\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:6,\createdAt\:\2025-10-19T01:46:57.036Z\,\updatedAt\:\2025-10-19T01:46:57.036Z\},{\id\:\59r8bt\,\codeBlockId\:\7ccae90a-9e30-4260-a632-57603b8b99c7\,\code\:\\u003c!DOCTYPE html\u003e\\n\u003chtml lang\\\en\\\\u003e\\n\\n\u003chead\u003e\\n \u003cmeta charset\\\UTF-8\\\\u003e\\n \u003cmeta name\\\viewport\\\ content\\\widthdevice-width, initial-scale1.0\\\\u003e\\n \u003ctitle\u003eDocument\u003c/title\u003e\\n \u003cscript type\\\module\\\\u003e\\n import Web3 from \\\web3\\\;\\n import { contractABI } from \\\./src/abi.js\\\;\\n\\n const contractAddress \\\0xFEA4f2C9B99Df0B2b7da67e60371BE4275C3d749\\\;\\n\\n // 找到 網頁 中的按鈕 \\n const connectButton document.getElementById(\\\connectButton\\\);/* @cat-caption */\\n connectButton.addEventListener(\\\click\\\, async () \u003e { /* @cat-caption */\\n // ...點擊後要做的事情 /* @cat-caption */\\n }); /* @cat-caption */\\n \u003c/script\u003e\\n\u003c/head\u003e\\n\\n\u003cbody\u003e\\n \u003cdiv\u003e\\n \u003cbutton id\\\connectButton\\\\u003e連線錢包\u003c/button\u003e\\n \u003c/div\u003e\\n\u003c/body\u003e\\n\\n\u003c/html\u003e\,\language\:\html\,\caption\:\在 JS 中設定該按鈕點擊後要做什麼\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:7,\createdAt\:\2025-10-19T01:46:57.036Z\,\updatedAt\:\2025-10-19T01:46:57.036Z\},{\id\:\8yvbi7\,\codeBlockId\:\7ccae90a-9e30-4260-a632-57603b8b99c7\,\code\:\\u003c!DOCTYPE html\u003e\\n\u003chtml lang\\\en\\\\u003e\\n\\n\u003chead\u003e\\n \u003cmeta charset\\\UTF-8\\\\u003e\\n \u003cmeta name\\\viewport\\\ content\\\widthdevice-width, initial-scale1.0\\\\u003e\\n \u003ctitle\u003eDocument\u003c/title\u003e\\n \u003cscript type\\\module\\\\u003e\\n import Web3 from \\\web3\\\;\\n import { contractABI } from \\\./src/abi.js\\\;\\n\\n const contractAddress \\\0xFEA4f2C9B99Df0B2b7da67e60371BE4275C3d749\\\;\\n\\n const connectButton document.getElementById(\\\connectButton\\\);/* @cat-caption */\\n connectButton.addEventListener(\\\click\\\, async () \u003e {/* @cat-caption */\\n // 檢查瀏覽器是否安裝了 MetaMask(或其他注入 web3 的錢包)/* @cat-caption */\\n if (typeof window.ethereum ! undefined) {/* @cat-caption */\\n // ... 設定錢包\\n } else {/* @cat-caption */\\n alert(請先安裝 MetaMask 或其他以太坊錢包外掛!);/* @cat-caption */\\n }/* @cat-caption */\\n });/* @cat-caption */\\n \u003c/script\u003e\\n\u003c/head\u003e\\n\\n\u003cbody\u003e\\n \u003cdiv\u003e\\n \u003cbutton id\\\connectButton\\\\u003e連線錢包\u003c/button\u003e\\n \u003c/div\u003e\\n\u003c/body\u003e\\n\\n\u003c/html\u003e\,\language\:\html\,\caption\:\點擊按鈕後,要先檢查瀏覽器是否安裝了 MetaMask(或其他注入 web3 的錢包)\\n(如果沒裝任何錢包 window.ethereum 不會有東西\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:8,\createdAt\:\2025-10-19T01:46:57.036Z\,\updatedAt\:\2025-10-19T01:46:57.036Z\},{\id\:\oo2wxm\,\codeBlockId\:\7ccae90a-9e30-4260-a632-57603b8b99c7\,\code\:\$26\,\language\:\html\,\caption\:\請求使用者透過 MetaMask 授權並連接錢包,讓 DApp 可以存取使用者的 Ethereum 帳戶地址。\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:9,\createdAt\:\2025-10-19T01:46:57.036Z\,\updatedAt\:\2025-10-19T01:46:57.036Z\},{\id\:\xnr3\,\codeBlockId\:\7ccae90a-9e30-4260-a632-57603b8b99c7\,\code\:\$27\,\language\:\html\,\caption\:\將 MetaMask 作為 Web3 的提供者 (Provider),讓 DApp 透過 MetaMask 與區塊鏈互動。\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:10,\createdAt\:\2025-10-19T01:46:57.036Z\,\updatedAt\:\2025-10-19T01:46:57.036Z\},{\id\:\73cihd\,\codeBlockId\:\7ccae90a-9e30-4260-a632-57603b8b99c7\,\code\:\$28\,\language\:\html\,\caption\:\可以印出來看看有沒有成功連結帳號\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:11,\createdAt\:\2025-10-19T01:46:57.036Z\,\updatedAt\:\2025-10-19T01:46:57.036Z\},{\id\:\e6dif1b\,\codeBlockId\:\7ccae90a-9e30-4260-a632-57603b8b99c7\,\code\:\$29\,\language\:\html\,\caption\:\因為你可能沒學過物件導向,所以讓我用更簡單的方式來解釋這段程式碼。\\n\\n你可以想像 Web3.js 會幫我們創建一個「合約操作員」,這個操作員負責幫我們與區塊鏈上的智能合約互動。\\n\\n這個 合約操作員 具備三個關鍵能力:\\n- 擁有你的錢包地址的操控權(當然,它不會擅自動用,你需要授權)。\\n- 知道你要操作哪個智能合約(透過 合約地址)。\\n- 知道該怎麼操作這份合約(透過 ABI(操作指南))。\\n\\n當我們想與合約互動時,只需要告訴這個「操作員」我們要做什麼,這位 操作員 會幫你處理所有的技術細節,確保你的指令能順利送進區塊鏈。\\n\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:12,\createdAt\:\2025-10-19T01:46:57.036Z\,\updatedAt\:\2025-10-19T01:46:57.036Z\},{\id\:\stfs1\,\codeBlockId\:\7ccae90a-9e30-4260-a632-57603b8b99c7\,\code\:\$2a\,\language\:\html\,\caption\:\在 HTML 中顯示合約中的 message 變數\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:13,\createdAt\:\2025-10-19T01:46:57.036Z\,\updatedAt\:\2025-10-19T01:46:57.036Z\},{\id\:\fsxrzr\,\codeBlockId\:\7ccae90a-9e30-4260-a632-57603b8b99c7\,\code\:\$2b\,\language\:\html\,\caption\:\在 JS 中設定該按鈕點擊後要做什麼\\n讀取區塊鏈的資料錢,要先連結錢包在進行操作\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:14,\createdAt\:\2025-10-19T01:46:57.036Z\,\updatedAt\:\2025-10-19T01:46:57.036Z\},{\id\:\na3i39\,\codeBlockId\:\7ccae90a-9e30-4260-a632-57603b8b99c7\,\code\:\$2c\,\language\:\html\,\caption\:\從智能合約中讀取 message 變數的值,而且不會發送交易,只會查詢區塊鏈上的數據。\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:15,\createdAt\:\2025-10-19T01:46:57.036Z\,\updatedAt\:\2025-10-19T01:46:57.036Z\},{\id\:\e54dbj\,\codeBlockId\:\7ccae90a-9e30-4260-a632-57603b8b99c7\,\code\:\$2d\,\language\:\html\,\caption\:\將從區塊鏈上取得的資料寫入網頁\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:16,\createdAt\:\2025-10-19T01:46:57.036Z\,\updatedAt\:\2025-10-19T01:46:57.036Z\},{\id\:\b2kohc\,\codeBlockId\:\7ccae90a-9e30-4260-a632-57603b8b99c7\,\code\:\$2e\,\language\:\html\,\caption\:\再來就是要來實踐如何觸發 setMessage 的 function 啦\\n先從 HTML 介面開始\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:17,\createdAt\:\2025-10-19T01:46:57.036Z\,\updatedAt\:\2025-10-19T01:46:57.036Z\},{\id\:\d0egv\,\codeBlockId\:\7ccae90a-9e30-4260-a632-57603b8b99c7\,\code\:\$2f\,\language\:\html\,\caption\:\處理 JS 的事件綁定與錢包檢查\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:18,\createdAt\:\2025-10-19T01:46:57.036Z\,\updatedAt\:\2025-10-19T01:46:57.036Z\},{\id\:\jpy5i\,\codeBlockId\:\7ccae90a-9e30-4260-a632-57603b8b99c7\,\code\:\$30\,\language\:\html\,\caption\:\從輸入框取得當前輸入的數值,檢查一下有沒有輸入\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:19,\createdAt\:\2025-10-19T01:46:57.036Z\,\updatedAt\:\2025-10-19T01:46:57.036Z\},{\id\:\gupi9c\,\codeBlockId\:\7ccae90a-9e30-4260-a632-57603b8b99c7\,\code\:\$31\,\language\:\html\,\caption\:\取得剛剛綁定好的錢包,由於用戶可能有多個錢包\\n使用首個錢包\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:20,\createdAt\:\2025-10-19T01:46:57.036Z\,\updatedAt\:\2025-10-19T01:46:57.036Z\},{\id\:\4sz1c9n\,\codeBlockId\:\7ccae90a-9e30-4260-a632-57603b8b99c7\,\code\:\$32\,\language\:\html\,\caption\:\正式發起 setMessage 的合約請求,並且以我剛剛選定好的錢包來當作付款對象。\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:21,\createdAt\:\2025-10-19T01:46:57.036Z\,\updatedAt\:\2025-10-19T01:46:57.036Z\},{\id\:\ckpcff\,\codeBlockId\:\7ccae90a-9e30-4260-a632-57603b8b99c7\,\code\:\\,\language\:\html\,\caption\:\最後打開測試網站 http://localhost:5173/\\n跑跑看吧\,\imageUrl\:\/api/object/1738172756488-9g6j4h.png\,\imagePosition\:\center\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:22,\createdAt\:\2025-10-19T01:46:57.036Z\,\updatedAt\:\2025-10-19T01:46:57.036Z\},{\id\:\gae9dc\,\codeBlockId\:\7ccae90a-9e30-4260-a632-57603b8b99c7\,\code\:\\,\language\:\html\,\caption\:\到 Remix 看看,你會發現數值也變了\,\imageUrl\:\/api/object/1738172826454-2abrg.png\,\imagePosition\:\center\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:23,\createdAt\:\2025-10-19T01:46:57.036Z\,\updatedAt\:\2025-10-19T01:46:57.036Z\}},\questionBlock\:null}},{\id\:\e9e78b6b-e7fa-4d70-b892-059ccc057ecf\,\title\:\智能合約 Solidity 教學 (中) - MetaMask 與以太坊測試網\,\content\:\這篇文章繼承了上一篇 智能合約 Solidity 教學 (上)\\n將嘗試使用 MetaMask 連接測試網,如何領取測試幣\\n並且在前端整合:透過 Web3.js 連接你的 DApp 與合約。\\n\,\blockIndexIds\:\9bc49a67-a959-40f6-a002-d5bc4b404b56\,\8f237401-e23c-4716-b7f5-2d79cc4d8125\,\c983878b-acd2-4428-910b-4558a3fef1b3\,\slug\:null,\metaDescription\:null,\ogImage\:null,\privacy\:\PUBLIC\,\allowedEmails\:,\createdAt\:\2025-01-29T13:34:30.496Z\,\updatedAt\:\2025-11-22T08:04:57.599Z\,\authorId\:\24c3f178-4a5b-447d-b586-16d7d459c606\,\viewCount\:322,\newsletterSentAt\:\2025-10-19T09:55:10.552Z\,\blocks\:{\id\:\9bc49a67-a959-40f6-a002-d5bc4b404b56\,\articleId\:\e9e78b6b-e7fa-4d70-b892-059ccc057ecf\,\noteBlock\:{\id\:\9bc49a67-a959-40f6-a002-d5bc4b404b56\,\content\:\$33\},\codeBlock\:null,\questionBlock\:null},{\id\:\8f237401-e23c-4716-b7f5-2d79cc4d8125\,\articleId\:\e9e78b6b-e7fa-4d70-b892-059ccc057ecf\,\noteBlock\:null,\codeBlock\:{\id\:\8f237401-e23c-4716-b7f5-2d79cc4d8125\,\content\:\$34\,\chunks\:{\id\:\3zq7d\,\codeBlockId\:\8f237401-e23c-4716-b7f5-2d79cc4d8125\,\code\:\// SPDX-License-Identifier: MIT\\npragma solidity ^0.8.0;\\n\\ncontract HelloWorld {\\n // 儲存訊息的變數\\n string public message;\\n\\n // 建構函式,合約部署時會自動執行\\n constructor() {\\n message \\\Hello, Solidity!\\\;\\n }\\n\\n // 設定新的訊息\\n function setMessage(string memory _newMessage) public {\\n message _newMessage;\\n }\\n}\\n\,\language\:\solidity\,\caption\:\我們使用上次教學使用的智能合約程式碼來部署\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:0,\createdAt\:\2025-10-19T01:46:53.686Z\,\updatedAt\:\2025-10-19T01:46:53.686Z\},{\id\:\umu2dh\,\codeBlockId\:\8f237401-e23c-4716-b7f5-2d79cc4d8125\,\code\:\\,\language\:\typescript\,\caption\:\在 Remix IDE \\n點擊 Deploy \u0026 Run Transactions 面板\\n選擇 WalletConnect\\n\,\imageUrl\:\/api/object/1738165535353-6ey1yq.png\,\imagePosition\:\center\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:1,\createdAt\:\2025-10-19T01:46:53.686Z\,\updatedAt\:\2025-10-19T01:46:53.686Z\},{\id\:\qbzqao\,\codeBlockId\:\8f237401-e23c-4716-b7f5-2d79cc4d8125\,\code\:\\,\language\:\typescript\,\caption\:\點擊連結錢包\,\imageUrl\:\/api/object/1738165578843-25tz78.png\,\imagePosition\:\center\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:2,\createdAt\:\2025-10-19T01:46:53.686Z\,\updatedAt\:\2025-10-19T01:46:53.686Z\},{\id\:\zhoxlp\,\codeBlockId\:\8f237401-e23c-4716-b7f5-2d79cc4d8125\,\code\:\\,\language\:\typescript\,\caption\:\選擇我們剛剛創辦好的 MetaMask\,\imageUrl\:\/api/object/1738165600940-eck8g.png\,\imagePosition\:\center\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:3,\createdAt\:\2025-10-19T01:46:53.686Z\,\updatedAt\:\2025-10-19T01:46:53.686Z\},{\id\:\x20tnh\,\codeBlockId\:\8f237401-e23c-4716-b7f5-2d79cc4d8125\,\code\:\\,\language\:\typescript\,\caption\:\以這個錢包來部署該合約\\n這個合約會被部署到 Sepolia 網路上\\n\,\imageUrl\:\/api/object/1738165630405-zqd9cf.png\,\imagePosition\:\center\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:4,\createdAt\:\2025-10-19T01:46:53.686Z\,\updatedAt\:\2025-10-19T01:46:53.686Z\},{\id\:\m8uylg4\,\codeBlockId\:\8f237401-e23c-4716-b7f5-2d79cc4d8125\,\code\:\\,\language\:\typescript\,\caption\:\由於這是真的會消耗到你錢包中的錢錢!!!\\n所以會跳出 MetaMask 的提示框\\n\\n本次部署合約將消耗快 6 美元(超貴\\n請注意是使用 Sepolia 來支付,那才是免費的測試幣\,\imageUrl\:\/api/object/1738165706994-14yk4t.png\,\imagePosition\:\center\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:5,\createdAt\:\2025-10-19T01:46:53.686Z\,\updatedAt\:\2025-10-19T01:46:53.686Z\},{\id\:\ofogaa\,\codeBlockId\:\8f237401-e23c-4716-b7f5-2d79cc4d8125\,\code\:\\,\language\:\typescript\,\caption\:\跟剛剛一樣,部署好以後\\n我們可以打開部署好的合約\\n然後重新 SetMessage\\n一樣要花錢 (0.3 美元)\,\imageUrl\:\/api/object/1738165867299-k91i3v.png\,\imagePosition\:\center\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:6,\createdAt\:\2025-10-19T01:46:53.686Z\,\updatedAt\:\2025-10-19T01:46:53.686Z\}},\questionBlock\:null},{\id\:\c983878b-acd2-4428-910b-4558a3fef1b3\,\articleId\:\e9e78b6b-e7fa-4d70-b892-059ccc057ecf\,\noteBlock\:{\id\:\c983878b-acd2-4428-910b-4558a3fef1b3\,\content\:\## 下一篇文章(/article/8b0bf0c6-6a00-4764-b686-d0c9c2a4ca28)\},\codeBlock\:null,\questionBlock\:null}},{\id\:\96e61275-7bde-480a-9a61-fca3c5a8657d\,\title\:\智能合約 Solidity 教學 (上) - Solidity 基礎語法教學\,\content\:\在這篇文章中,我們會從 0 基礎開始,逐步介紹智能合約的概念、Solidity 語法,以及如何部署你的第一個智能合約。\\n適合對區塊鏈開發有興趣但還沒有 Solidity 經驗的開發者!\,\blockIndexIds\:\2f699183-9fc1-47fa-a2f0-b8cf6b6f6350\,\ebcf44e2-f61f-42bc-98f3-92dfe0262050\,\33c09724-24f2-4bdf-8241-5d2d4850c2a2\,\a566ba1a-45ab-4b2f-97db-f36afcd249c3\,\97bd9998-8a35-4f95-81ea-a71f3a62b025\,\eb4ba74f-7cb5-4b8b-8872-34a6ec6a8678\,\3cb81f87-2f2a-4613-8627-1aae3b1555b8\,\061d6926-3170-4038-8ed8-5acb8e837a8f\,\271082fa-af97-4652-a206-62dd805f573f\,\59c6c000-f224-4bf0-a056-0b34e6fbf36d\,\eaa630af-ed7c-4664-a730-cdc6102d9402\,\5704c6c4-4152-4425-9607-151d2b9c9647\,\ecae1138-fdc1-45a7-9e6d-76ef247a82e2\,\35d756e9-a1b9-4479-8bf1-a5d76fb27ab4\,\slug\:null,\metaDescription\:null,\ogImage\:null,\privacy\:\PUBLIC\,\allowedEmails\:,\createdAt\:\2025-01-29T06:09:37.907Z\,\updatedAt\:\2025-11-22T13:58:27.306Z\,\authorId\:\24c3f178-4a5b-447d-b586-16d7d459c606\,\viewCount\:1562,\newsletterSentAt\:\2025-10-19T09:55:10.552Z\,\blocks\:{\id\:\2f699183-9fc1-47fa-a2f0-b8cf6b6f6350\,\articleId\:\96e61275-7bde-480a-9a61-fca3c5a8657d\,\noteBlock\:{\id\:\2f699183-9fc1-47fa-a2f0-b8cf6b6f6350\,\content\:\## 目錄\\n\\n* 智能合約 Solidity 教學 (上) - Solidity 基礎語法教學(/article/96e61275-7bde-480a-9a61-fca3c5a8657d)\\n* 智能合約 Solidity 教學 (中) - MetaMask 與以太坊測試網(/article/e9e78b6b-e7fa-4d70-b892-059ccc057ecf)\\n* 智能合約 Solidity 教學 (下) - Web3.js 實際開發一個 DApp 去中心化應用前端(/article/8b0bf0c6-6a00-4764-b686-d0c9c2a4ca28)\\n\\n## **一**、**Solidity 0 到 1:智能合約入門**\\n\\n### **什麼是 Solidity?**\\n\\nSolidity 是一種**面向智能合約的編程語言**,專門用於在 **Ethereum(以太坊)** 和其他 **EVM(Ethereum Virtual Machine)兼容的區塊鏈**上開發去中心化應用(DApps)。\\n\\n它受到了 JavaScript、Python 和 C++ 的影響,語法簡潔且適合初學者入門。\\n\\n### **智能合約是什麼?**\\n\\n智能合約(Smart Contract)是一種 **自動執行的合約**,當滿足特定條件時,程式會**自動執行約定的內容**,無需中間人。\\n\\n例如:\\n\\n* **去中心化交易所(DEX)** – 允許用戶直接交易代幣\\n* **NFT 合約** – 用於發行、管理和交易 NFT\\n* **去中心化金融(DeFi)應用** – 如貸款、流動性挖礦等\\n\\n當然不僅僅是這種與金融有關的產業,任何可以去中心化的應用都可以採用智能合約,好比說有些產業為了更好的紀錄各個生產鏈上下的資料,便可以用智能合約,公正無私的紀錄資料。\},\codeBlock\:null,\questionBlock\:null},{\id\:\ebcf44e2-f61f-42bc-98f3-92dfe0262050\,\articleId\:\96e61275-7bde-480a-9a61-fca3c5a8657d\,\noteBlock\:{\id\:\ebcf44e2-f61f-42bc-98f3-92dfe0262050\,\content\:\## **二**、**開始使用 Solidity**\\n\\n### **1. 安裝開發環境**\\n\\n目前最簡單的方式是使用線上編輯器 \\\\*\\\\*Remix IDE\\\\*\\\\*:\\n\\n* **網址**:https://remix.ethereum.org/(https://remix.ethereum.org/)\\n* **不需要安裝**,打開瀏覽器即可編寫與部署智能合約。\\n\\n如果你想在本地開發,可以使用:\\n\\n* **Node.js**(安裝 npm/pnpm)\\n* **Hardhat 或 Truffle**(Solidity 開發框架)\\n\\n另外你會需要一個加密貨幣錢包來與合約互動(放心,在本教學中不會花到任何一毛錢)\\n\\n* **MetaMask 錢包**(測試和與合約互動)\\n\\n## **三**、**第一個合約 Hello World**\},\codeBlock\:null,\questionBlock\:null},{\id\:\33c09724-24f2-4bdf-8241-5d2d4850c2a2\,\articleId\:\96e61275-7bde-480a-9a61-fca3c5a8657d\,\noteBlock\:null,\codeBlock\:{\id\:\33c09724-24f2-4bdf-8241-5d2d4850c2a2\,\content\:\$35\,\chunks\:{\id\:\9b8q2j\,\codeBlockId\:\33c09724-24f2-4bdf-8241-5d2d4850c2a2\,\code\:\\,\language\:\plaintext\,\caption\:\開啟 Remix (https://remix.ethereum.org)\,\imageUrl\:\/api/object/1738133312491-3b4tcx.png\,\imagePosition\:\center\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:0,\createdAt\:\2025-10-19T01:46:55.532Z\,\updatedAt\:\2025-10-19T01:46:55.532Z\},{\id\:\gyocve\,\codeBlockId\:\33c09724-24f2-4bdf-8241-5d2d4850c2a2\,\code\:\\,\language\:\plaintext\,\caption\:\創建第一個合約:HelloWorld\\n在 Remix 建立新檔 HelloWorld.sol\,\imageUrl\:\/api/object/1738133531581-cyuacvi.png\,\imagePosition\:\center\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:1,\createdAt\:\2025-10-19T01:46:55.532Z\,\updatedAt\:\2025-10-19T01:46:55.532Z\},{\id\:\0omwz8\,\codeBlockId\:\33c09724-24f2-4bdf-8241-5d2d4850c2a2\,\code\:\// SPDX-License-Identifier: MIT\\npragma solidity ^0.8.0;\\n\\ncontract HelloWorld {\\n // 儲存訊息的變數\\n string public message;\\n\\n // 建構函式,合約部署時會自動執行\\n constructor() {\\n message \\\Hello, Solidity!\\\;\\n }\\n\\n // 設定新的訊息\\n function setMessage(string memory _newMessage) public {\\n message _newMessage;\\n }\\n}\\n\,\language\:\solidity\,\caption\:\貼上我們的第一個範本,接下來我們將逐行介紹各個概念\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:2,\createdAt\:\2025-10-19T01:46:55.532Z\,\updatedAt\:\2025-10-19T01:46:55.532Z\},{\id\:\dpb6j\,\codeBlockId\:\33c09724-24f2-4bdf-8241-5d2d4850c2a2\,\code\:\// SPDX-License-Identifier: MIT\\npragma solidity ^0.8.0;/* @cat-caption */\\n\,\language\:\solidity\,\caption\:\選擇程式語言的版本\\n因為 Solidity 會不斷更新,新舊程式可能不通用,所以需要指定版本\\n\\n- 指定使用 **Solidity 0.8.0** 以上版本。\\n- `^0.8.0` 代表兼容 0.8.x 的版本。\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:3,\createdAt\:\2025-10-19T01:46:55.532Z\,\updatedAt\:\2025-10-19T01:46:55.532Z\},{\id\:\1bg8e8\,\codeBlockId\:\33c09724-24f2-4bdf-8241-5d2d4850c2a2\,\code\:\// SPDX-License-Identifier: MIT\\npragma solidity ^0.8.0;\\n\\ncontract HelloWorld {/* @cat-caption */\\n\\n}/* @cat-caption */\\n\,\language\:\solidity\,\caption\:\定義一個名為 HelloWorld 的智能合約\\n\\n- 使用 `contract` 關鍵字定義一個 **智能合約**。\\n- 所有函式與變數都放在 `{}` 內。\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:4,\createdAt\:\2025-10-19T01:46:55.532Z\,\updatedAt\:\2025-10-19T01:46:55.532Z\},{\id\:\gjcul\,\codeBlockId\:\33c09724-24f2-4bdf-8241-5d2d4850c2a2\,\code\:\// SPDX-License-Identifier: MIT\\npragma solidity ^0.8.0;\\n\\ncontract HelloWorld {\\n // 儲存訊息的變數/* @cat-caption */\\n string public message;/* @cat-caption */\\n\\n // 建構函式,合約部署時會自動執行/* @cat-caption */\\n constructor() {/* @cat-caption */\\n message \\\Hello, Solidity!\\\;/* @cat-caption */\\n }/* @cat-caption */\\n}\\n\,\language\:\solidity\,\caption\:\建構函式,這是一種在合約部署時會自動執行一次的特殊函式。\\n\\n我們讓他在合約部署的時候,將 message 變數設定成 \\\Hello, Solidity\\\\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:5,\createdAt\:\2025-10-19T01:46:55.532Z\,\updatedAt\:\2025-10-19T01:46:55.532Z\},{\id\:\np725\,\codeBlockId\:\33c09724-24f2-4bdf-8241-5d2d4850c2a2\,\code\:\// SPDX-License-Identifier: MIT\\npragma solidity ^0.8.0;\\n\\ncontract HelloWorld {\\n // 儲存訊息的變數\\n string public message;\\n\\n // 建構函式,合約部署時會自動執行\\n constructor() {\\n message \\\Hello, Solidity!\\\;\\n }\\n\\n // 設定新的訊息/* @cat-caption */\\n function setMessage(string memory _newMessage) public {/* @cat-caption */\\n message _newMessage;/* @cat-caption */\\n }/* @cat-caption */\\n}\\n\,\language\:\solidity\,\caption\:\定義一個公開函式,讓使用者呼叫來更新 message。\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:6,\createdAt\:\2025-10-19T01:46:55.532Z\,\updatedAt\:\2025-10-19T01:46:55.532Z\}},\questionBlock\:null},{\id\:\a566ba1a-45ab-4b2f-97db-f36afcd249c3\,\articleId\:\96e61275-7bde-480a-9a61-fca3c5a8657d\,\noteBlock\:{\id\:\a566ba1a-45ab-4b2f-97db-f36afcd249c3\,\content\:\## 四、編譯與部署\},\codeBlock\:null,\questionBlock\:null},{\id\:\97bd9998-8a35-4f95-81ea-a71f3a62b025\,\articleId\:\96e61275-7bde-480a-9a61-fca3c5a8657d\,\noteBlock\:null,\codeBlock\:{\id\:\97bd9998-8a35-4f95-81ea-a71f3a62b025\,\content\:\$36\,\chunks\:{\id\:\paysf7\,\codeBlockId\:\97bd9998-8a35-4f95-81ea-a71f3a62b025\,\code\:\\,\language\:\markdown\,\caption\:\點擊 Remix 右側面板 `Solidity Compiler`\,\imageUrl\:\/api/object/1738138940269-t39cz.png\,\imagePosition\:\center\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:0,\createdAt\:\2025-10-19T01:47:00.768Z\,\updatedAt\:\2025-10-19T01:47:00.768Z\},{\id\:\t8cdwk\,\codeBlockId\:\97bd9998-8a35-4f95-81ea-a71f3a62b025\,\code\:\\,\language\:\markdown\,\caption\:\- 選擇正確版本 (0.8.x)\\n- 按下 **`Compile HelloWorld.sol`** 按鈕\,\imageUrl\:\/api/object/1738139007049-3zgityr.png\,\imagePosition\:\center\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:1,\createdAt\:\2025-10-19T01:47:00.768Z\,\updatedAt\:\2025-10-19T01:47:00.768Z\},{\id\:\cazn9i\,\codeBlockId\:\97bd9998-8a35-4f95-81ea-a71f3a62b025\,\code\:\\,\language\:\markdown\,\caption\:\沒有錯誤就會顯示綠色打勾\,\imageUrl\:\/api/object/1738139024138-7f7po.png\,\imagePosition\:\center\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:2,\createdAt\:\2025-10-19T01:47:00.768Z\,\updatedAt\:\2025-10-19T01:47:00.768Z\},{\id\:\jz35j\,\codeBlockId\:\97bd9998-8a35-4f95-81ea-a71f3a62b025\,\code\:\\,\language\:\markdown\,\caption\:\轉到 Deploy \u0026 Run Transactions 面板\,\imageUrl\:\/api/object/1738139383399-jh9lsw.png\,\imagePosition\:\center\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:3,\createdAt\:\2025-10-19T01:47:00.768Z\,\updatedAt\:\2025-10-19T01:47:00.768Z\},{\id\:\73ikd\,\codeBlockId\:\97bd9998-8a35-4f95-81ea-a71f3a62b025\,\code\:\\,\language\:\markdown\,\caption\:\- Environment 記得選擇 Remix VM (Cancun)\\n他是專門給你測試用的區塊鏈\\n- Contract 記得別部署錯\\n- 最後點擊 Deploy \\n\\n\,\imageUrl\:\/api/object/1738139402367-fo2xac.png\,\imagePosition\:\center\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:4,\createdAt\:\2025-10-19T01:47:00.768Z\,\updatedAt\:\2025-10-19T01:47:00.768Z\},{\id\:\xp7n8\,\codeBlockId\:\97bd9998-8a35-4f95-81ea-a71f3a62b025\,\code\:\\,\language\:\markdown\,\caption\:\下方出現 `Deployed Contracts` 就表示部署成功\\n另外你也會發現上頭的 Account 默默的被扣了點以太幣\\n\\n部署合約是會花真金白銀的,不過由於目前是測試環境,所以扣的也只是可無限使用的測試用以太幣\\n\\n另外部署合約,這份合約將永遠的存在於區塊鏈上頭\\n無法撤銷、無法移除,請務必注意安全性。\,\imageUrl\:\/api/object/1738139555578-hucog2.png\,\imagePosition\:\center\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:5,\createdAt\:\2025-10-19T01:47:00.768Z\,\updatedAt\:\2025-10-19T01:47:00.768Z\},{\id\:\bt7lh\,\codeBlockId\:\97bd9998-8a35-4f95-81ea-a71f3a62b025\,\code\:\\,\language\:\markdown\,\caption\:\- 展開 `HelloWorld` 合約介面\\n- 按 `message` 便可以查看此時此刻區塊鏈上 message 的數值是多少\\n\\n(查看數值是免費的\,\imageUrl\:\/api/object/1738139846600-3j0oms.png\,\imagePosition\:\center\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:6,\createdAt\:\2025-10-19T01:47:00.768Z\,\updatedAt\:\2025-10-19T01:47:00.768Z\},{\id\:\q2egom\,\codeBlockId\:\97bd9998-8a35-4f95-81ea-a71f3a62b025\,\code\:\\,\language\:\markdown\,\caption\:\在 setMessage 文字框輸入新訊息,如 \\\貓貓真可愛\\\\\n再來點擊 SetMessage 的按鈕,便可以觸發合約中的該函數\\n可以重新再點 message 一次,你會發現區塊鏈中的 message 變數也發生了更新\\n\\n(修改區塊鏈上頭的數值是需要花錢的\,\imageUrl\:\/api/object/1738139930036-x1g28.png\,\imagePosition\:\center\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:7,\createdAt\:\2025-10-19T01:47:00.768Z\,\updatedAt\:\2025-10-19T01:47:00.768Z\}},\questionBlock\:null},{\id\:\eb4ba74f-7cb5-4b8b-8872-34a6ec6a8678\,\articleId\:\96e61275-7bde-480a-9a61-fca3c5a8657d\,\noteBlock\:{\id\:\eb4ba74f-7cb5-4b8b-8872-34a6ec6a8678\,\content\:\## 五、Solidity 中的變數型別\},\codeBlock\:null,\questionBlock\:null},{\id\:\3cb81f87-2f2a-4613-8627-1aae3b1555b8\,\articleId\:\96e61275-7bde-480a-9a61-fca3c5a8657d\,\noteBlock\:null,\codeBlock\:{\id\:\3cb81f87-2f2a-4613-8627-1aae3b1555b8\,\content\:\$37\,\chunks\:{\id\:\xyikai\,\codeBlockId\:\3cb81f87-2f2a-4613-8627-1aae3b1555b8\,\code\:\bool public myBool true;/* @cat-caption */\,\language\:\solidity\,\caption\:\我們來介紹一下各種 Solidity 常用的型別\\n先從布林(bool) 開始\\n布林型別只允許存 `true` 或 `false` 兩種型別\\n\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:0,\createdAt\:\2025-10-23T23:11:09.990Z\,\updatedAt\:\2025-10-23T23:11:09.990Z\},{\id\:\qguxe\,\codeBlockId\:\3cb81f87-2f2a-4613-8627-1aae3b1555b8\,\code\:\uint256 public myUint 123;/* @cat-caption */\\nint public myInt -123;/* @cat-caption */\\n\,\language\:\solidity\,\caption\:\**整數 (int, uint)**\\n\\n - `int`:可以為正或負\\n - `uint`:只能是正整數 (0, 1, 2, ...)\\n\\n - 常見長度:`int8`, `int16`, ..., `int256` (以 8 為增量),`int` 等同 `int256`\\n\\n - 同理 `uint8`, `uint16`, ..., `uint256`\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:1,\createdAt\:\2025-10-23T23:11:09.990Z\,\updatedAt\:\2025-10-23T23:11:09.990Z\},{\id\:\dvs4jq\,\codeBlockId\:\3cb81f87-2f2a-4613-8627-1aae3b1555b8\,\code\:\address public myAddress 0x1234567890123456789012345678901234567890;\\nmyAddress.balance // 取得該錢包目前剩餘的存款\,\language\:\solidity\,\caption\:\地址 (address)\\n用於存放以太坊地址(錢包或合約地址)\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:2,\createdAt\:\2025-10-23T23:11:09.990Z\,\updatedAt\:\2025-10-23T23:11:09.990Z\},{\id\:\f3368q\,\codeBlockId\:\3cb81f87-2f2a-4613-8627-1aae3b1555b8\,\code\:\string public myString \\\Hello\\\;\\n\\nmyString.length; //error\\nmyString0; //error\,\language\:\solidity\,\caption\:\字串 (string)\\n就是一段文字\\n\\n不過不像其他程式語言,在 Solidity 中,字串並沒有 length 屬性,也不能用 index 的方式取得字元\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:3,\createdAt\:\2025-10-23T23:11:09.990Z\,\updatedAt\:\2025-10-23T23:11:09.990Z\},{\id\:\r3le84\,\codeBlockId\:\3cb81f87-2f2a-4613-8627-1aae3b1555b8\,\code\:\uint public numbers; // 動態大小的陣列\\nuint5 public fixedSizeNumbers; // 大小固定為 5 的陣列\\n\\nnumbers.push(123);\,\language\:\solidity\,\caption\:\陣列\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:4,\createdAt\:\2025-10-23T23:11:09.990Z\,\updatedAt\:\2025-10-23T23:11:09.990Z\},{\id\:\qsi54o\,\codeBlockId\:\3cb81f87-2f2a-4613-8627-1aae3b1555b8\,\code\:\struct Person {\\n string name;\\n uint age;\\n}\\n\\nPerson public alice Person(\\\Alice\\\, 30);\\n\,\language\:\solidity\,\caption\:\struct\\n可以自定義一個資料結構\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:5,\createdAt\:\2025-10-23T23:11:09.990Z\,\updatedAt\:\2025-10-23T23:11:09.990Z\},{\id\:\fpjfng\,\codeBlockId\:\3cb81f87-2f2a-4613-8627-1aae3b1555b8\,\code\:\enum Status { Pending, Shipped, Completed, Rejected }\\n\\nStatus public currentStatus;\\n\,\language\:\solidity\,\caption\:\enum (列舉)\\n- 用於定義一組狀態,方便管理\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:6,\createdAt\:\2025-10-23T23:11:09.990Z\,\updatedAt\:\2025-10-23T23:11:09.990Z\}},\questionBlock\:null},{\id\:\061d6926-3170-4038-8ed8-5acb8e837a8f\,\articleId\:\96e61275-7bde-480a-9a61-fca3c5a8657d\,\noteBlock\:{\id\:\061d6926-3170-4038-8ed8-5acb8e837a8f\,\content\:\$38\},\codeBlock\:null,\questionBlock\:null},{\id\:\271082fa-af97-4652-a206-62dd805f573f\,\articleId\:\96e61275-7bde-480a-9a61-fca3c5a8657d\,\noteBlock\:null,\codeBlock\:{\id\:\271082fa-af97-4652-a206-62dd805f573f\,\content\:\$39\,\chunks\:{\id\:\1chadh\,\codeBlockId\:\271082fa-af97-4652-a206-62dd805f573f\,\code\:\// SPDX-License-Identifier: MIT\\npragma solidity ^0.8.0;\\n\\ncontract HelloWorld {\\n string public result \\\\\\;\\n\\n constructor() {\\n int256 val 1;\\n if (val 0) {\\n result \\\Value is zero\\\;\\n } else if (val 1) {\\n result \\\Value is one\\\;\\n } else {\\n result \\\Value is something else\\\;\\n }\\n }\\n}\\n\,\language\:\solidity\,\caption\:\if / else if / else\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:0,\createdAt\:\2025-10-19T01:47:01.348Z\,\updatedAt\:\2025-10-19T01:47:01.348Z\},{\id\:\54o6me\,\codeBlockId\:\271082fa-af97-4652-a206-62dd805f573f\,\code\:\// SPDX-License-Identifier: MIT\\npragma solidity ^0.8.0;\\n\\ncontract HelloWorld {\\n uint public sum 0;\\n\\n constructor() {\\n uint max 100;\\n for (uint i 0; i \u003c max; i++) {\\n sum + i;\\n }\\n }\\n}\\n\,\language\:\solidity\,\caption\:\for 迴圈\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:1,\createdAt\:\2025-10-19T01:47:01.348Z\,\updatedAt\:\2025-10-19T01:47:01.348Z\},{\id\:\iu751b\,\codeBlockId\:\271082fa-af97-4652-a206-62dd805f573f\,\code\:\// SPDX-License-Identifier: MIT\\npragma solidity ^0.8.0;\\n\\ncontract HelloWorld {\\n uint public sum 0;\\n\\n constructor() {\\n uint max 100;\\n uint i 0;/* @cat-caption */\\n while (i \u003c max) {/* @cat-caption */\\n sum + i;/* @cat-caption */\\n i ++;/* @cat-caption */\\n }/* @cat-caption */\\n }\\n}\\n\,\language\:\solidity\,\caption\:\while 迴圈\\n範例程式碼的功能與上頭完全相同\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:2,\createdAt\:\2025-10-19T01:47:01.348Z\,\updatedAt\:\2025-10-19T01:47:01.348Z\}},\questionBlock\:null},{\id\:\59c6c000-f224-4bf0-a056-0b34e6fbf36d\,\articleId\:\96e61275-7bde-480a-9a61-fca3c5a8657d\,\noteBlock\:{\id\:\59c6c000-f224-4bf0-a056-0b34e6fbf36d\,\content\:\## 十、函式 (Function)\\n\\n函數就像是一個魔法盒子,我們可以把一系列的指令放進這個盒子裡,替這個盒子取一個名字\\n然後以後只要叫這個名字,就可以讓這些指令執行。\\n\\n在 Solidity 中為了更方便使用者開發,我可以替函式加上一些設定\},\codeBlock\:null,\questionBlock\:null},{\id\:\eaa630af-ed7c-4664-a730-cdc6102d9402\,\articleId\:\96e61275-7bde-480a-9a61-fca3c5a8657d\,\noteBlock\:null,\codeBlock\:{\id\:\eaa630af-ed7c-4664-a730-cdc6102d9402\,\content\:\$3a\,\chunks\:{\id\:\bz3sd\,\codeBlockId\:\eaa630af-ed7c-4664-a730-cdc6102d9402\,\code\:\// SPDX-License-Identifier: MIT\\npragma solidity ^0.8.0;\\n\\ncontract HelloWorld {\\n uint public count;\\n\\n // 這是一個 public 函式/* @cat-caption */\\n function increment() public {/* @cat-caption */\\n count + 1;/* @cat-caption */\\n }/* @cat-caption */\\n}\\n\,\language\:\solidity\,\caption\:\`public` 函式\\n所有人(包括外部帳戶和其他合約)都可以呼叫該函式。\\n\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:0,\createdAt\:\2025-10-19T01:46:52.166Z\,\updatedAt\:\2025-10-19T01:46:52.166Z\},{\id\:\i4uj3\,\codeBlockId\:\eaa630af-ed7c-4664-a730-cdc6102d9402\,\code\:\// SPDX-License-Identifier: MIT\\npragma solidity ^0.8.0;\\n\\ncontract HelloWorld {\\n uint public count;\\n\\n // 外部無法直接訪問該變數/* @cat-caption */\\n // 請注意!該資料仍然公開於區塊鏈上頭,仍然可以爬取到該變數/* @cat-caption */\\n uint private innerCount;/* @cat-caption */\\n\\n // 這是一個 public 函式\\n function increment() public {\\n _increment(); // ✅ 合約內部可存取\\n }\\n\\n // 這是一個 private 函式,習慣命名上最前面加上 _ 來表示這是 private\\n function _increment() private {\\n count + 1;\\n }\\n}\\n\,\language\:\solidity\,\caption\:\`private` 函式\\n只能在合約內部使用\\n外部或者其他合約無法訪問\\n\\n* 變數也可以設定成 external\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:1,\createdAt\:\2025-10-19T01:46:52.166Z\,\updatedAt\:\2025-10-19T01:46:52.166Z\},{\id\:\qke0o\,\codeBlockId\:\eaa630af-ed7c-4664-a730-cdc6102d9402\,\code\:\// SPDX-License-Identifier: MIT\\npragma solidity ^0.8.0;\\n\\ncontract HelloWorld {\\n uint public count;\\n\\n // 這是一個 public 函式\\n function increment() public {\\n this.externalIncrement(); // 內部調用必須透過 `this`\\n }\\n\\n // 這是一個 external 函式\\n function externalIncrement() external {/* @cat-caption */\\n count + 1;\\n }\\n}\\n\,\language\:\solidity\,\caption\:\`external` 函式\\n只能從合約外部存取,內部不可直接存取\\n\\n* 不過還是可以間接地利用 `this.函式名()` 來存取\\n* 不適用於變數:變數無法標記為 external。\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:2,\createdAt\:\2025-10-19T01:46:52.166Z\,\updatedAt\:\2025-10-19T01:46:52.166Z\},{\id\:\lj7zt\,\codeBlockId\:\eaa630af-ed7c-4664-a730-cdc6102d9402\,\code\:\\,\language\:\solidity\,\caption\:\上面都是偏向描述權限\\n接下來則是針對功能\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:3,\createdAt\:\2025-10-19T01:46:52.166Z\,\updatedAt\:\2025-10-19T01:46:52.166Z\},{\id\:\8gt5t\,\codeBlockId\:\eaa630af-ed7c-4664-a730-cdc6102d9402\,\code\:\// SPDX-License-Identifier: MIT\\npragma solidity ^0.8.0;\\n\\ncontract ViewExample {\\n uint public number 42; // 狀態變數\\n\\n // 這個函式只是讀取狀態變數,並沒有改變它\\n function getNumber() public view returns (uint) {/* @cat-caption */\\n return number;\\n }\\n}\\n\,\language\:\solidity\,\caption\:\`view` 函式\\nview 函式不會修改區塊鏈上的狀態\\n\\n- 不能修改合約內的狀態變數\\n- 不能發送以太幣(ETH)\\n- 不能調用其他會修改狀態的函式\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:4,\createdAt\:\2025-10-19T01:46:52.166Z\,\updatedAt\:\2025-10-19T01:46:52.166Z\},{\id\:\r7rzp7\,\codeBlockId\:\eaa630af-ed7c-4664-a730-cdc6102d9402\,\code\:\// SPDX-License-Identifier: MIT\\npragma solidity ^0.8.0;\\n\\ncontract PureExample {\\n // 這個函式純粹進行計算,沒有存取合約內的狀態變數\\n function add(uint a, uint b) public pure returns (uint) {/* @cat-caption */\\n return a + b;\\n }\\n}\\n\,\language\:\solidity\,\caption\:\`pure` 函式\\npure 純計算,不讀取也不修改狀態\\n\\n- pure 函式不能讀取也不能修改區塊鏈上的狀態變數。\\n- 只能使用函式內的變數或傳入的參數進行運算。\\n- 適合用來做純計算,例如數學運算。\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:5,\createdAt\:\2025-10-19T01:46:52.166Z\,\updatedAt\:\2025-10-19T01:46:52.166Z\},{\id\:\jux6as\,\codeBlockId\:\eaa630af-ed7c-4664-a730-cdc6102d9402\,\code\:\// SPDX-License-Identifier: MIT\\npragma solidity ^0.8.0;\\n\\ncontract PayableExample {\\n uint public balance;\\n\\n // 這個函式允許用戶發送 ETH 到合約\\n function deposit() public payable {/* @cat-caption */\\n balance + msg.value; // msg.value 是發送的 ETH 數量/* @cat-caption */\\n }\\n\\n // 這個函式回傳合約內的 ETH 餘額\\n function getBalance() public view returns (uint) {\\n return address(this).balance;\\n }\\n}\\n\,\language\:\solidity\,\caption\:\`payable` 函式\\npayable 函式允許該函式接收 ETH\\n\\n- 沒有 payable 的函式無法接收 ETH,如果用戶試圖發送 ETH 會報錯。\\n- 有 payable 的函式可以讓合約接收 ETH,常用於:\\n - 付款交易\\n - 捐款功能\\n - NFT 或其他商品購買\\n\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:6,\createdAt\:\2025-10-19T01:46:52.166Z\,\updatedAt\:\2025-10-19T01:46:52.166Z\}},\questionBlock\:null},{\id\:\5704c6c4-4152-4425-9607-151d2b9c9647\,\articleId\:\96e61275-7bde-480a-9a61-fca3c5a8657d\,\noteBlock\:{\id\:\5704c6c4-4152-4425-9607-151d2b9c9647\,\content\:\## 十一、錯誤處理 (Error Handling)\},\codeBlock\:null,\questionBlock\:null},{\id\:\ecae1138-fdc1-45a7-9e6d-76ef247a82e2\,\articleId\:\96e61275-7bde-480a-9a61-fca3c5a8657d\,\noteBlock\:null,\codeBlock\:{\id\:\ecae1138-fdc1-45a7-9e6d-76ef247a82e2\,\content\:\{\\\id\\\:\\\oiblxu\\\,\\\code\\\:\\\// SPDX-License-Identifier: MIT\\\\npragma solidity ^0.8.0;\\\\n\\\\ncontract PayableExample {\\\\n uint public balance;\\\\n\\\\n function withdraw(uint _amount) public {\\\\n // _amount 須小於 balance 的金額,否則報錯\\\\n require(_amount \u003c balance, \\\\\\\合約中資金不足\\\\\\\);/* @cat-caption */\\\\n balance - _amount;\\\\n }\\\\n\\\\n}\\\\n\\\,\\\caption\\\:\\\require()\\\\n- 用於檢查條件,不符合時將 revert 並顯示錯誤訊息。\\\\n- 常用於權限判斷或輸入參數檢查。\\\,\\\language\\\:\\\solidity\\\},{\\\id\\\:\\\zoo29j\\\,\\\code\\\:\\\function doSomething() public {\\\\n if (someConditionNotMet()) {\\\\n revert(\\\\\\\Condition not met\\\\\\\);/* @cat-caption */\\\\n }\\\\n // ...\\\\n}\\\\n\\\,\\\caption\\\:\\\revert()\\\\n- 直接報錯,\\\,\\\language\\\:\\\solidity\\\},{\\\id\\\:\\\wnps8c\\\,\\\code\\\:\\\function testAssert(uint _x) public pure {\\\\n assert(_x ! 0); // _x 絕對不能為 0/* @cat-caption */\\\\n}\\\\n\\\,\\\caption\\\:\\\assert()\\\\n- 用於檢查不應該發生的狀況(通常是內部錯誤或不變量)。\\\,\\\language\\\:\\\solidity\\\}\,\chunks\:{\id\:\oiblxu\,\codeBlockId\:\ecae1138-fdc1-45a7-9e6d-76ef247a82e2\,\code\:\// SPDX-License-Identifier: MIT\\npragma solidity ^0.8.0;\\n\\ncontract PayableExample {\\n uint public balance;\\n\\n function withdraw(uint _amount) public {\\n // _amount 須小於 balance 的金額,否則報錯\\n require(_amount \u003c balance, \\\合約中資金不足\\\);/* @cat-caption */\\n balance - _amount;\\n }\\n\\n}\\n\,\language\:\solidity\,\caption\:\require()\\n- 用於檢查條件,不符合時將 revert 並顯示錯誤訊息。\\n- 常用於權限判斷或輸入參數檢查。\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:0,\createdAt\:\2025-10-19T01:47:02.096Z\,\updatedAt\:\2025-10-19T01:47:02.096Z\},{\id\:\zoo29j\,\codeBlockId\:\ecae1138-fdc1-45a7-9e6d-76ef247a82e2\,\code\:\function doSomething() public {\\n if (someConditionNotMet()) {\\n revert(\\\Condition not met\\\);/* @cat-caption */\\n }\\n // ...\\n}\\n\,\language\:\solidity\,\caption\:\revert()\\n- 直接報錯,\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:1,\createdAt\:\2025-10-19T01:47:02.096Z\,\updatedAt\:\2025-10-19T01:47:02.096Z\},{\id\:\wnps8c\,\codeBlockId\:\ecae1138-fdc1-45a7-9e6d-76ef247a82e2\,\code\:\function testAssert(uint _x) public pure {\\n assert(_x ! 0); // _x 絕對不能為 0/* @cat-caption */\\n}\\n\,\language\:\solidity\,\caption\:\assert()\\n- 用於檢查不應該發生的狀況(通常是內部錯誤或不變量)。\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:2,\createdAt\:\2025-10-19T01:47:02.096Z\,\updatedAt\:\2025-10-19T01:47:02.096Z\}},\questionBlock\:null},{\id\:\35d756e9-a1b9-4479-8bf1-a5d76fb27ab4\,\articleId\:\96e61275-7bde-480a-9a61-fca3c5a8657d\,\noteBlock\:{\id\:\35d756e9-a1b9-4479-8bf1-a5d76fb27ab4\,\content\:\## 進一步學習建議\\n\\n1. **掌握測試網部署**:嘗試使用 MetaMask 連接測試網 (如 Sepolia、Goerli) 進行部署。\\n2. **熟悉 Hardhat / Truffle**:在本地進行單元測試與合約部署管理。\\n3. **安全性考量**:了解常見攻擊手法(Reentrancy, Integer Overflow 等)。\\n4. **前端整合**:透過 Web3.js 或 Ethers.js 連接你的 DApp 與合約。\\n\\n## 下一篇文章(/article/e9e78b6b-e7fa-4d70-b892-059ccc057ecf)\},\codeBlock\:null,\questionBlock\:null}},{\id\:\9455bacb-e12f-4da3-9e5c-3641f720f849\,\title\:\COSCUP - 一起來開發一個 notion 吧,多人即時共編筆記分享\,\content\:\如何開發一個共編筆記的開發技術,淺談 CRDT、OT 的概念,分享當前生態系與資源\\n著重在多人即時共編時如何做到不衝突資料變更\\n最終開發一個可以部署的超簡單版本 notion 筆記\,\blockIndexIds\:\4dac11dd-4133-4b8c-b24b-ca853d5668f3\,\slug\:null,\metaDescription\:null,\ogImage\:null,\privacy\:\PUBLIC\,\allowedEmails\:,\createdAt\:\2024-08-02T16:41:11.270Z\,\updatedAt\:\2025-11-22T04:23:36.420Z\,\authorId\:\24c3f178-4a5b-447d-b586-16d7d459c606\,\viewCount\:1843,\newsletterSentAt\:\2025-10-19T09:55:10.552Z\,\blocks\:{\id\:\4dac11dd-4133-4b8c-b24b-ca853d5668f3\,\articleId\:\9455bacb-e12f-4da3-9e5c-3641f720f849\,\noteBlock\:null,\codeBlock\:{\id\:\4dac11dd-4133-4b8c-b24b-ca853d5668f3\,\content\:\$3b\,\chunks\:{\id\:\6maicl\,\codeBlockId\:\4dac11dd-4133-4b8c-b24b-ca853d5668f3\,\code\:\\,\language\:\typescript\,\caption\:\講者介紹\,\imageUrl\:\/api/object/1722648403224-ls5wu.png\,\imagePosition\:\center\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:0,\createdAt\:\2025-10-19T01:46:57.832Z\,\updatedAt\:\2025-10-19T01:46:57.832Z\},{\id\:\4wxmgo\,\codeBlockId\:\4dac11dd-4133-4b8c-b24b-ca853d5668f3\,\code\:\\,\language\:\typescript\,\caption\:\如何開發一個\\n多人即時共編筆記\,\imageUrl\:\/api/object/1722648488757-cy100fp.png\,\imagePosition\:\center\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:1,\createdAt\:\2025-10-19T01:46:57.832Z\,\updatedAt\:\2025-10-19T01:46:57.832Z\},{\id\:\50aza\,\codeBlockId\:\4dac11dd-4133-4b8c-b24b-ca853d5668f3\,\code\:\\,\language\:\typescript\,\caption\:\一些能夠多人協作的平台\\nex. Google 文件、HackMd、Notion\,\imageUrl\:\/api/object/1722648542302-jysgw4.png\,\imagePosition\:\center\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:2,\createdAt\:\2025-10-19T01:46:57.832Z\,\updatedAt\:\2025-10-19T01:46:57.832Z\},{\id\:\eb9e3\,\codeBlockId\:\4dac11dd-4133-4b8c-b24b-ca853d5668f3\,\code\:\\,\language\:\typescript\,\caption\:\版本控制\,\imageUrl\:\/api/object/1722648612380-75tnf.png\,\imagePosition\:\center\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:3,\createdAt\:\2025-10-19T01:46:57.832Z\,\updatedAt\:\2025-10-19T01:46:57.832Z\},{\id\:\9w7h2n\,\codeBlockId\:\4dac11dd-4133-4b8c-b24b-ca853d5668f3\,\code\:\\,\language\:\typescript\,\caption\:\如果今天只有一個 branch\\n如果有任何對文件的修改,都可以創建一個 comment\,\imageUrl\:\/api/object/1722648638582-zjc5l9.png\,\imagePosition\:\center\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:4,\createdAt\:\2025-10-19T01:46:57.832Z\,\updatedAt\:\2025-10-19T01:46:57.832Z\},{\id\:\syq16t\,\codeBlockId\:\4dac11dd-4133-4b8c-b24b-ca853d5668f3\,\code\:\\,\language\:\typescript\,\caption\:\每個 commit 都相當於是一個能夠隨時回退的版本\,\imageUrl\:\/api/object/1722648766296-bbqtso.png\,\imagePosition\:\center\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:5,\createdAt\:\2025-10-19T01:46:57.832Z\,\updatedAt\:\2025-10-19T01:46:57.832Z\},{\id\:\24qfog\,\codeBlockId\:\4dac11dd-4133-4b8c-b24b-ca853d5668f3\,\code\:\\,\language\:\typescript\,\caption\:\透過 commit 你也能看到一個專案的發展史\,\imageUrl\:\/api/object/1722648865952-uyawnl.png\,\imagePosition\:\center\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:6,\createdAt\:\2025-10-19T01:46:57.832Z\,\updatedAt\:\2025-10-19T01:46:57.832Z\},{\id\:\3cf587\,\codeBlockId\:\4dac11dd-4133-4b8c-b24b-ca853d5668f3\,\code\:\\,\language\:\typescript\,\caption\:\或者你同事的偷懶史\,\imageUrl\:\/api/object/1722648936837-psx1hl.png\,\imagePosition\:\center\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:7,\createdAt\:\2025-10-19T01:46:57.832Z\,\updatedAt\:\2025-10-19T01:46:57.832Z\},{\id\:\6171ii\,\codeBlockId\:\4dac11dd-4133-4b8c-b24b-ca853d5668f3\,\code\:\\,\language\:\typescript\,\caption\:\多人開發\\n\,\imageUrl\:\/api/object/1722649047496-9965ld.png\,\imagePosition\:\center\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:8,\createdAt\:\2025-10-19T01:46:57.832Z\,\updatedAt\:\2025-10-19T01:46:57.832Z\},{\id\:\me3hrf\,\codeBlockId\:\4dac11dd-4133-4b8c-b24b-ca853d5668f3\,\code\:\\,\language\:\typescript\,\caption\:\將大家辛苦工作的結果合併回去\,\imageUrl\:\/api/object/1722649052251-rhzei.png\,\imagePosition\:\center\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:9,\createdAt\:\2025-10-19T01:46:57.832Z\,\updatedAt\:\2025-10-19T01:46:57.832Z\},{\id\:\53odlb\,\codeBlockId\:\4dac11dd-4133-4b8c-b24b-ca853d5668f3\,\code\:\\,\language\:\typescript\,\caption\:\那當然,很常就會發生衝突 conflict\,\imageUrl\:\/api/object/1722649123432-ae33e7.png\,\imagePosition\:\center\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:10,\createdAt\:\2025-10-19T01:46:57.832Z\,\updatedAt\:\2025-10-19T01:46:57.832Z\},{\id\:\oljkvc\,\codeBlockId\:\4dac11dd-4133-4b8c-b24b-ca853d5668f3\,\code\:\\,\language\:\typescript\,\caption\:\偶爾也有那種感覺休假半年才來上班的同事\\n將半年前的 branch 進行合併的魔幻操作\,\imageUrl\:\/api/object/1722649208535-lpyxmb.png\,\imagePosition\:\center\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:11,\createdAt\:\2025-10-19T01:46:57.832Z\,\updatedAt\:\2025-10-19T01:46:57.832Z\},{\id\:\wllwvd\,\codeBlockId\:\4dac11dd-4133-4b8c-b24b-ca853d5668f3\,\code\:\\,\language\:\typescript\,\caption\:\OT 與 CRDT\,\imageUrl\:\/api/object/1722649239157-woa15.png\,\imagePosition\:\center\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:12,\createdAt\:\2025-10-19T01:46:57.832Z\,\updatedAt\:\2025-10-19T01:46:57.832Z\},{\id\:\ivw4hs\,\codeBlockId\:\4dac11dd-4133-4b8c-b24b-ca853d5668f3\,\code\:\\,\language\:\typescript\,\caption\:\來用一個簡單的例子講解\\nOT 的概念\\n假設有兩個人同時在編輯一串文字\,\imageUrl\:\/api/object/1722650376584-hk3yt.png\,\imagePosition\:\center\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:13,\createdAt\:\2025-10-19T01:46:57.832Z\,\updatedAt\:\2025-10-19T01:46:57.832Z\},{\id\:\kgv9r6\,\codeBlockId\:\4dac11dd-4133-4b8c-b24b-ca853d5668f3\,\code\:\\,\language\:\typescript\,\caption\:\OT 的做法就是同步操作\\n也就是將用戶的操作廣播出去\,\imageUrl\:\/api/object/1722650397381-anhrvf.png\,\imagePosition\:\center\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:14,\createdAt\:\2025-10-19T01:46:57.832Z\,\updatedAt\:\2025-10-19T01:46:57.832Z\},{\id\:\b28r0p\,\codeBlockId\:\4dac11dd-4133-4b8c-b24b-ca853d5668f3\,\code\:\\,\language\:\typescript\,\caption\:\當然,因為網路延遲\\n這個傳播會有時間差\,\imageUrl\:\/api/object/1722650422663-hz9nud.png\,\imagePosition\:\center\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:15,\createdAt\:\2025-10-19T01:46:57.832Z\,\updatedAt\:\2025-10-19T01:46:57.832Z\},{\id\:\dt1e2v\,\codeBlockId\:\4dac11dd-4133-4b8c-b24b-ca853d5668f3\,\code\:\\,\language\:\typescript\,\caption\:\當其他人收到這個廣播時\\n便會自動去套用這個操作\\n好讓兩個人的畫面一致\,\imageUrl\:\/api/object/1722650437802-0g3xs.png\,\imagePosition\:\center\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:16,\createdAt\:\2025-10-19T01:46:57.832Z\,\updatedAt\:\2025-10-19T01:46:57.832Z\},{\id\:\au6u6g\,\codeBlockId\:\4dac11dd-4133-4b8c-b24b-ca853d5668f3\,\code\:\\,\language\:\typescript\,\caption\:\但換個會衝突的例子\,\imageUrl\:\/api/object/1722650484027-rx7ll4.png\,\imagePosition\:\center\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:17,\createdAt\:\2025-10-19T01:46:57.832Z\,\updatedAt\:\2025-10-19T01:46:57.832Z\},{\id\:\x6ull4\,\codeBlockId\:\4dac11dd-4133-4b8c-b24b-ca853d5668f3\,\code\:\\,\language\:\typescript\,\caption\:\但兩個人的操作非常接近\,\imageUrl\:\/api/object/1722650493408-wss6m7.png\,\imagePosition\:\center\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:18,\createdAt\:\2025-10-19T01:46:57.832Z\,\updatedAt\:\2025-10-19T01:46:57.832Z\},{\id\:\ymth7h\,\codeBlockId\:\4dac11dd-4133-4b8c-b24b-ca853d5668f3\,\code\:\\,\language\:\typescript\,\caption\:\這時因為網路延遲的關西\\n\,\imageUrl\:\/api/object/1722650510171-lscmwe.png\,\imagePosition\:\center\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:19,\createdAt\:\2025-10-19T01:46:57.832Z\,\updatedAt\:\2025-10-19T01:46:57.832Z\},{\id\:\9157r\,\codeBlockId\:\4dac11dd-4133-4b8c-b24b-ca853d5668f3\,\code\:\\,\language\:\typescript\,\caption\:\會導致兩邊的數據不一致\,\imageUrl\:\/api/object/1722650533178-jsxl7d.png\,\imagePosition\:\center\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:20,\createdAt\:\2025-10-19T01:46:57.832Z\,\updatedAt\:\2025-10-19T01:46:57.832Z\},{\id\:\7at8gc\,\codeBlockId\:\4dac11dd-4133-4b8c-b24b-ca853d5668f3\,\code\:\\,\language\:\typescript\,\caption\:\所以我們會在廣播時附上時間戳\,\imageUrl\:\/api/object/1722650593088-8ecwv7.png\,\imagePosition\:\center\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:21,\createdAt\:\2025-10-19T01:46:57.832Z\,\updatedAt\:\2025-10-19T01:46:57.832Z\},{\id\:\mex7r\,\codeBlockId\:\4dac11dd-4133-4b8c-b24b-ca853d5668f3\,\code\:\\,\language\:\typescript\,\caption\:\這邊就要來講到 OT 當中的 T\\nTransformation\\n透過時間戳的關西,轉換操作\\n使兩者最後結果一致\,\imageUrl\:\/api/object/1722650602402-kqi2f.png\,\imagePosition\:\center\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:22,\createdAt\:\2025-10-19T01:46:57.832Z\,\updatedAt\:\2025-10-19T01:46:57.832Z\},{\id\:\22e0hl\,\codeBlockId\:\4dac11dd-4133-4b8c-b24b-ca853d5668f3\,\code\:\\,\language\:\typescript\,\caption\:\再來講講 CRDT\\n與 OT 不同,CRTD 主要是關注於資料上\,\imageUrl\:\/api/object/1722650623458-41057a.png\,\imagePosition\:\center\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:23,\createdAt\:\2025-10-19T01:46:57.832Z\,\updatedAt\:\2025-10-19T01:46:57.832Z\},{\id\:\m80nt\,\codeBlockId\:\4dac11dd-4133-4b8c-b24b-ca853d5668f3\,\code\:\\,\language\:\typescript\,\caption\:\他會\,\imageUrl\:\/api/object/1722650639515-pgrpdg.png\,\imagePosition\:\center\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:24,\createdAt\:\2025-10-19T01:46:57.832Z\,\updatedAt\:\2025-10-19T01:46:57.832Z\},{\id\:\eebg8m\,\codeBlockId\:\4dac11dd-4133-4b8c-b24b-ca853d5668f3\,\code\:\\,\language\:\typescript\,\caption\:\CRDT\,\imageUrl\:\/api/object/1722650652537-q724o.png\,\imagePosition\:\center\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:25,\createdAt\:\2025-10-19T01:46:57.832Z\,\updatedAt\:\2025-10-19T01:46:57.832Z\},{\id\:\oxclne\,\codeBlockId\:\4dac11dd-4133-4b8c-b24b-ca853d5668f3\,\code\:\\,\language\:\typescript\,\caption\:\CRDT\,\imageUrl\:\/api/object/1722650675472-os0bqi.png\,\imagePosition\:\center\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:26,\createdAt\:\2025-10-19T01:46:57.832Z\,\updatedAt\:\2025-10-19T01:46:57.832Z\},{\id\:\qmx9ic\,\codeBlockId\:\4dac11dd-4133-4b8c-b24b-ca853d5668f3\,\code\:\\,\language\:\typescript\,\caption\:\CRDT\,\imageUrl\:\/api/object/1722650690332-pzbjpe.png\,\imagePosition\:\center\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:27,\createdAt\:\2025-10-19T01:46:57.832Z\,\updatedAt\:\2025-10-19T01:46:57.832Z\},{\id\:\qv7kg\,\codeBlockId\:\4dac11dd-4133-4b8c-b24b-ca853d5668f3\,\code\:\\,\language\:\typescript\,\caption\:\CRDT\,\imageUrl\:\/api/object/1722650745121-7faygb.png\,\imagePosition\:\center\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:28,\createdAt\:\2025-10-19T01:46:57.832Z\,\updatedAt\:\2025-10-19T01:46:57.832Z\},{\id\:\2jt88d\,\codeBlockId\:\4dac11dd-4133-4b8c-b24b-ca853d5668f3\,\code\:\\,\language\:\typescript\,\caption\:\CRDT\,\imageUrl\:\/api/object/1722650749678-ugoq7f.png\,\imagePosition\:\center\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:29,\createdAt\:\2025-10-19T01:46:57.832Z\,\updatedAt\:\2025-10-19T01:46:57.832Z\},{\id\:\em9hif\,\codeBlockId\:\4dac11dd-4133-4b8c-b24b-ca853d5668f3\,\code\:\\,\language\:\typescript\,\caption\:\CRDT 選擇理由\,\imageUrl\:\/api/object/1722650766556-sst5p.png\,\imagePosition\:\center\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:30,\createdAt\:\2025-10-19T01:46:57.832Z\,\updatedAt\:\2025-10-19T01:46:57.832Z\},{\id\:\qc8w4e\,\codeBlockId\:\4dac11dd-4133-4b8c-b24b-ca853d5668f3\,\code\:\\,\language\:\typescript\,\caption\:\\,\imageUrl\:\/api/object/1722650787048-m5uwpm.png\,\imagePosition\:\center\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:31,\createdAt\:\2025-10-19T01:46:57.832Z\,\updatedAt\:\2025-10-19T01:46:57.832Z\},{\id\:\n147mb\,\codeBlockId\:\4dac11dd-4133-4b8c-b24b-ca853d5668f3\,\code\:\\,\language\:\typescript\,\caption\:\Demo\,\imageUrl\:\/api/object/1722650803554-nf4y8m.png\,\imagePosition\:\center\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:32,\createdAt\:\2025-10-19T01:46:57.832Z\,\updatedAt\:\2025-10-19T01:46:57.832Z\},{\id\:\ny2r2m\,\codeBlockId\:\4dac11dd-4133-4b8c-b24b-ca853d5668f3\,\code\:\npm init -y\\r\\nnpm install ws y-websocket yjs\,\language\:\plaintext\,\caption\:\首先,設定 Node.js 伺服器,安裝所需的套件:\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:33,\createdAt\:\2025-10-19T01:46:57.832Z\,\updatedAt\:\2025-10-19T01:46:57.832Z\},{\id\:\t93gmi\,\codeBlockId\:\4dac11dd-4133-4b8c-b24b-ca853d5668f3\,\code\:\\,\language\:\plaintext\,\caption\:\創建一個 server.js 文件\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:34,\createdAt\:\2025-10-19T01:46:57.832Z\,\updatedAt\:\2025-10-19T01:46:57.832Z\},{\id\:\f6dsap\,\codeBlockId\:\4dac11dd-4133-4b8c-b24b-ca853d5668f3\,\code\:\import * as Y from \\\yjs\\\;/* @cat-caption */\,\language\:\javascript\,\caption\:\先引入 yjs\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:35,\createdAt\:\2025-10-19T01:46:57.832Z\,\updatedAt\:\2025-10-19T01:46:57.832Z\},{\id\:\8q00u\,\codeBlockId\:\4dac11dd-4133-4b8c-b24b-ca853d5668f3\,\code\:\import * as Y from \\\yjs\\\;\\r\\n\\r\\nconst doc new Y.Doc(); /* @cat-caption */\\r\\n\,\language\:\javascript\,\caption\:\創建一個 Doc 物件\\n這個是 yjs 的核心資料結構之一\\n他會自己追蹤其他用戶的更改、自己同步資料\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:36,\createdAt\:\2025-10-19T01:46:57.832Z\,\updatedAt\:\2025-10-19T01:46:57.832Z\},{\id\:\rvmflf\,\codeBlockId\:\4dac11dd-4133-4b8c-b24b-ca853d5668f3\,\code\:\import * as Y from \\\yjs\\\;\\r\\nimport { WebsocketProvider } from \\\y-websocket\\\;/* @cat-caption */\\r\\nimport ws from \\\ws\\\;/* @cat-caption */\\r\\n\\r\\nconst doc new Y.Doc();\\r\\nconst wsProvider new WebsocketProvider(/* @cat-caption */\\r\\n \\\ws://localhost:1234\\\,/* @cat-caption */\\r\\n \\\my-roomname\\\,/* @cat-caption */\\r\\n doc,/* @cat-caption */\\r\\n { WebSocketPolyfill: ws }/* @cat-caption */\\r\\n);/* @cat-caption */\,\language\:\javascript\,\caption\:\我們可以在透過 y-websocket\\n讓 Doc 透過 websocket 取得來自其他客戶的更新資訊\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:37,\createdAt\:\2025-10-19T01:46:57.832Z\,\updatedAt\:\2025-10-19T01:46:57.832Z\},{\id\:\x6dw6g\,\codeBlockId\:\4dac11dd-4133-4b8c-b24b-ca853d5668f3\,\code\:\npx create-react-app frontend\\r\\ncd frontend\\r\\nnpm install yjs y-websocket\,\language\:\plaintext\,\caption\:\再來是前端的部分\\n直接創建一個新的 react 專案\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:38,\createdAt\:\2025-10-19T01:46:57.832Z\,\updatedAt\:\2025-10-19T01:46:57.832Z\},{\id\:\9053xs\,\codeBlockId\:\4dac11dd-4133-4b8c-b24b-ca853d5668f3\,\code\:\import React from \\\react\\\;\\r\\n\\r\\nfunction App() {\\r\\n return (\\r\\n \u003cdiv className\\\App\\\\u003e\\r\\n\\r\\n \u003c/div\u003e\\r\\n );\\r\\n}\\r\\n\\r\\nexport default App;\\r\\n\,\language\:\javascript\,\caption\:\在 App.js 裏頭\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:39,\createdAt\:\2025-10-19T01:46:57.832Z\,\updatedAt\:\2025-10-19T01:46:57.832Z\},{\id\:\wtthf\,\codeBlockId\:\4dac11dd-4133-4b8c-b24b-ca853d5668f3\,\code\:\import React, { useEffect } from \\\react\\\;/* @cat-caption */\\r\\nimport * as Y from \\\yjs\\\;/* @cat-caption */\\r\\nimport { WebsocketProvider } from \\\y-websocket\\\;/* @cat-caption */\\r\\n\\r\\nfunction App() {\\r\\n useEffect(() \u003e {/* @cat-caption */\\r\\n const ydoc new Y.Doc();/* @cat-caption */\\r\\n const provider new WebsocketProvider(/* @cat-caption */\\r\\n \\\ws://localhost:1234\\\,/* @cat-caption */\\r\\n \\\my-roomname\\\,/* @cat-caption */\\r\\n ydoc/* @cat-caption */\\r\\n );/* @cat-caption */\\r\\n\\r\\n return () \u003e {/* @cat-caption */\\r\\n provider.disconnect();/* @cat-caption */\\r\\n };/* @cat-caption */\\r\\n }, );/* @cat-caption */\\r\\n\\r\\n return (\\r\\n \u003cdiv className\\\App\\\\u003e\\r\\n\\r\\n \u003c/div\u003e\\r\\n );\\r\\n}\\r\\n\\r\\nexport default App;\\r\\n\,\language\:\javascript\,\caption\:\一樣在前端,一模的創建一個 Doc\\n並且創建 websocket 連線\\n基本上此時,前端的 doc 已經會與後端自動同步了\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:40,\createdAt\:\2025-10-19T01:46:57.832Z\,\updatedAt\:\2025-10-19T01:46:57.832Z\},{\id\:\zc3w5m\,\codeBlockId\:\4dac11dd-4133-4b8c-b24b-ca853d5668f3\,\code\:\import React, { useEffect, useRef } from \\\react\\\;\\r\\nimport * as Y from \\\yjs\\\;\\r\\nimport { WebsocketProvider } from \\\y-websocket\\\;\\r\\n\\r\\nfunction App() {\\r\\n useEffect(() \u003e {\\r\\n const ydoc new Y.Doc();\\r\\n const provider new WebsocketProvider(\\r\\n \\\ws://localhost:1234\\\,\\r\\n \\\my-roomname\\\,\\r\\n ydoc\\r\\n );\\r\\n\\r\\n return () \u003e {\\r\\n provider.disconnect();\\r\\n };\\r\\n }, );\\r\\n\\r\\n const textareaRef useRef(null);/* @cat-caption */\\r\\n\\r\\n return (\\r\\n \u003cdiv className\\\App\\\\u003e\\r\\n \u003ctextarea/* @cat-caption */\\r\\n ref{textareaRef}/* @cat-caption */\\r\\n style{{ width: \\\100%\\\, height: \\\90vh\\\ }}/* @cat-caption */\\r\\n \u003e\u003c/textarea\u003e/* @cat-caption */\\r\\n \u003c/div\u003e\\r\\n );\\r\\n}\\r\\n\\r\\nexport default App;\\r\\n\,\language\:\javascript\,\caption\:\創建一個輸入框\\n並透過 ref 好方便我們操作他\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:41,\createdAt\:\2025-10-19T01:46:57.832Z\,\updatedAt\:\2025-10-19T01:46:57.832Z\},{\id\:\vab2rf\,\codeBlockId\:\4dac11dd-4133-4b8c-b24b-ca853d5668f3\,\code\:\import React, { useEffect, useRef } from \\\react\\\;\\r\\nimport * as Y from \\\yjs\\\;\\r\\nimport { WebsocketProvider } from \\\y-websocket\\\;\\r\\n\\r\\nfunction App() {\\r\\n useEffect(() \u003e {\\r\\n const ydoc new Y.Doc();\\r\\n const provider new WebsocketProvider(\\r\\n \\\ws://localhost:1234\\\,\\r\\n \\\my-roomname\\\,\\r\\n ydoc\\r\\n );\\r\\n\\r\\n const yText ydoc.getText(\\\textarea\\\); /* @cat-caption */\\r\\n\\r\\n return () \u003e {\\r\\n provider.disconnect();\\r\\n };\\r\\n }, );\\r\\n\\r\\n const textareaRef useRef(null);\\r\\n\\r\\n return (\\r\\n \u003cdiv className\\\App\\\\u003e\\r\\n \u003ctextarea\\r\\n ref{textareaRef}\\r\\n style{{ width: \\\100%\\\, height: \\\90vh\\\ }}\\r\\n \u003e\u003c/textarea\u003e\\r\\n \u003c/div\u003e\\r\\n );\\r\\n}\\r\\n\\r\\nexport default App;\\r\\n\,\language\:\javascript\,\caption\:\Doc 有點類似 Dictionary\\n可以放置很多不同的資料在裏頭\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:42,\createdAt\:\2025-10-19T01:46:57.832Z\,\updatedAt\:\2025-10-19T01:46:57.832Z\},{\id\:\2wewkf\,\codeBlockId\:\4dac11dd-4133-4b8c-b24b-ca853d5668f3\,\code\:\$3c\,\language\:\javascript\,\caption\:\我們將輸入框一旦輸入文字\\n則直接把這串字插入進 yText 中\\nyText 會處理好自動同步問題\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:43,\createdAt\:\2025-10-19T01:46:57.832Z\,\updatedAt\:\2025-10-19T01:46:57.832Z\},{\id\:\29q7g6\,\codeBlockId\:\4dac11dd-4133-4b8c-b24b-ca853d5668f3\,\code\:\$3d\,\language\:\javascript\,\caption\:\這邊 yjs 提供一個監聽的接口\\n當檢測到 yText 被遠端同步更新後\\n會自動更新輸入框\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:44,\createdAt\:\2025-10-19T01:46:57.832Z\,\updatedAt\:\2025-10-19T01:46:57.832Z\},{\id\:\fdw28\,\codeBlockId\:\4dac11dd-4133-4b8c-b24b-ca853d5668f3\,\code\:\$3e\,\language\:\javascript\,\caption\:\這邊加上刪除、換行功能\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:45,\createdAt\:\2025-10-19T01:46:57.832Z\,\updatedAt\:\2025-10-19T01:46:57.832Z\},{\id\:\hs54o\,\codeBlockId\:\4dac11dd-4133-4b8c-b24b-ca853d5668f3\,\code\:\$3f\,\language\:\javascript\,\caption\:\解決光標沒有自動更新\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:46,\createdAt\:\2025-10-19T01:46:57.832Z\,\updatedAt\:\2025-10-19T01:46:57.832Z\},{\id\:\43uj9s\,\codeBlockId\:\4dac11dd-4133-4b8c-b24b-ca853d5668f3\,\code\:\\,\language\:\javascript\,\caption\:\編輯器套件分享\,\imageUrl\:\/api/object/1722651920286-r8lnls.png\,\imagePosition\:\center\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:47,\createdAt\:\2025-10-19T01:46:57.832Z\,\updatedAt\:\2025-10-19T01:46:57.832Z\},{\id\:\8k4w5o\,\codeBlockId\:\4dac11dd-4133-4b8c-b24b-ca853d5668f3\,\code\:\\,\language\:\javascript\,\caption\:\編輯器套件分享\,\imageUrl\:\/api/object/1722651933269-81w3zf.png\,\imagePosition\:\center\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:48,\createdAt\:\2025-10-19T01:46:57.832Z\,\updatedAt\:\2025-10-19T01:46:57.832Z\},{\id\:\b8fvlo\,\codeBlockId\:\4dac11dd-4133-4b8c-b24b-ca853d5668f3\,\code\:\\,\language\:\javascript\,\caption\:\編輯器套件分享\,\imageUrl\:\/api/object/1722651948383-3g0mik.png\,\imagePosition\:\center\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:49,\createdAt\:\2025-10-19T01:46:57.832Z\,\updatedAt\:\2025-10-19T01:46:57.832Z\},{\id\:\36omdb\,\codeBlockId\:\4dac11dd-4133-4b8c-b24b-ca853d5668f3\,\code\:\\,\language\:\javascript\,\caption\:\Ziphus 分享\,\imageUrl\:\/api/object/1722651963129-johpai.png\,\imagePosition\:\center\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:50,\createdAt\:\2025-10-19T01:46:57.832Z\,\updatedAt\:\2025-10-19T01:46:57.832Z\}},\questionBlock\:null}},{\id\:\7e7d0745-4276-42ce-8272-091039c381e0\,\title\:\技術共學|來開發一個即時的多人聊天網頁與繪圖白板吧!\,\content\:\\,\blockIndexIds\:\78eb5a45-514f-44e4-8f02-d80355a20221\,\slug\:null,\metaDescription\:null,\ogImage\:null,\privacy\:\PUBLIC\,\allowedEmails\:,\createdAt\:\2024-07-29T12:19:27.832Z\,\updatedAt\:\2025-11-20T13:50:19.017Z\,\authorId\:\24c3f178-4a5b-447d-b586-16d7d459c606\,\viewCount\:88,\newsletterSentAt\:\2025-10-19T09:55:10.552Z\,\blocks\:{\id\:\78eb5a45-514f-44e4-8f02-d80355a20221\,\articleId\:\7e7d0745-4276-42ce-8272-091039c381e0\,\noteBlock\:{\id\:\78eb5a45-514f-44e4-8f02-d80355a20221\,\content\:\$40\},\codeBlock\:null,\questionBlock\:null}},{\id\:\4763da84-e8bd-4f64-a3b2-747c6f399733\,\title\:\VRoid 的模型添增動畫並導入 ThreeJs 網頁\,\content\:\\,\blockIndexIds\:\48d87729-5576-497d-9bcf-cdd25fcccb3b\,\slug\:null,\metaDescription\:null,\ogImage\:null,\privacy\:\PUBLIC\,\allowedEmails\:,\createdAt\:\2024-07-29T11:50:42.720Z\,\updatedAt\:\2025-11-21T23:36:22.104Z\,\authorId\:\24c3f178-4a5b-447d-b586-16d7d459c606\,\viewCount\:199,\newsletterSentAt\:\2025-10-19T10:04:57.377Z\,\blocks\:{\id\:\48d87729-5576-497d-9bcf-cdd25fcccb3b\,\articleId\:\4763da84-e8bd-4f64-a3b2-747c6f399733\,\noteBlock\:{\id\:\48d87729-5576-497d-9bcf-cdd25fcccb3b\,\content\:\$41\},\codeBlock\:null,\questionBlock\:null}},{\id\:\e4d3e885-909a-422b-82dc-1041ecad0aa7\,\title\:\第四部分:進階 JavaScript 與網頁視覺動畫教學大綱\,\content\:\在這個章節中,我們將介紹如何讓 JS 操作網頁的資料\\n比如說取得使用者讀取的資料、送出文件、修改文字之類的\,\blockIndexIds\:\86f7d21f-476c-4d16-8be0-2af71e093d00\,\d45a5fc7-6ab8-483f-9237-3ec39920428e\,\ba4106ac-d68d-409a-80c7-718d1f027f3b\,\2f7627b3-227a-4967-a115-e52c18a006e7\,\a80a46f9-389a-4015-b7d6-e17333f54c20\,\slug\:null,\metaDescription\:null,\ogImage\:null,\privacy\:\PUBLIC\,\allowedEmails\:,\createdAt\:\2024-06-24T00:11:39.048Z\,\updatedAt\:\2025-11-22T10:52:05.095Z\,\authorId\:\24c3f178-4a5b-447d-b586-16d7d459c606\,\viewCount\:90,\newsletterSentAt\:\2025-10-19T09:55:10.552Z\,\blocks\:{\id\:\86f7d21f-476c-4d16-8be0-2af71e093d00\,\articleId\:\e4d3e885-909a-422b-82dc-1041ecad0aa7\,\noteBlock\:null,\codeBlock\:{\id\:\86f7d21f-476c-4d16-8be0-2af71e093d00\,\content\:\$42\,\chunks\:{\id\:\ts5wx5\,\codeBlockId\:\86f7d21f-476c-4d16-8be0-2af71e093d00\,\code\:\\u003c!DOCTYPE html\u003e\\r\\n\u003chtml\u003e\\r\\n\\r\\n\u003chead\u003e\\r\\n \u003ctitle\u003e我的網頁\u003c/title\u003e\\r\\n\u003c/head\u003e\\r\\n\\r\\n\u003cbody\u003e\\r\\n \u003ch1\u003e這是一個標題\u003c/h1\u003e\\r\\n \u003cdiv\u003e\\r\\n \u003ch2\u003e這是一個小標題\u003c/h2\u003e\\r\\n \u003cp\u003e這是一段文字。\u003c/p\u003e\\r\\n \u003c/div\u003e\\r\\n\u003c/body\u003e\\r\\n\\r\\n\u003c/html\u003e\,\language\:\html\,\caption\:\# 網頁 DOM 操作\\n\\nDOM 結構與樹狀結構\\n\\n基本上我們剛剛撰寫的 HTML 只是純粹的文字\\n瀏覽器為了方便程式計算,會先將 HTML 轉換成一個巨大的 object\\n這就是 DOM(Document Object Model)\\n可以很好的方便程式操作\\n\\nDOM 就像是網頁的地圖,讓我們可以找到網頁上的每個元素。可以把 DOM 想像成一棵大樹,每個元素都是這棵樹上的一個節點。\\n\\n\,\imageUrl\:\/api/object/1719227447885-hvezpm.png\,\imagePosition\:\bottom-right\,\imageFullWidth\:null,\imageWidth\:600,\imageHeight\:null,\order\:0,\createdAt\:\2025-10-19T01:46:56.338Z\,\updatedAt\:\2025-10-19T01:46:56.338Z\},{\id\:\gibglm\,\codeBlockId\:\86f7d21f-476c-4d16-8be0-2af71e093d00\,\code\:\\u003c!DOCTYPE html\u003e\\r\\n\u003chtml\u003e\\r\\n\\r\\n\u003chead\u003e\\r\\n \u003ctitle\u003e我的網頁\u003c/title\u003e\\r\\n\u003c/head\u003e\\r\\n\\r\\n\u003cbody\u003e\\r\\n \u003ch1\u003e這是一個標題\u003c/h1\u003e\\r\\n \u003cdiv\u003e\\r\\n \u003ch2 id\\\smallTitle\\\\u003e這是一個小標題\u003c/h2\u003e/* @cat-caption */\\r\\n \u003cp\u003e這是一段文字。\u003c/p\u003e\\r\\n \u003c/div\u003e\\r\\n\u003c/body\u003e\\r\\n\\r\\n\u003c/html\u003e\,\language\:\html\,\caption\:\我們先來看一下如何讓 JS 取得某個元素\\n\\n請先替指定元素加上 id 屬性\\nid,你可以想像就是替元素貼上一張訂製標籤\\n方便程式找到他\\n每個元素的 id 都應該是獨一無二的\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:1,\createdAt\:\2025-10-19T01:46:56.338Z\,\updatedAt\:\2025-10-19T01:46:56.338Z\},{\id\:\gmvdmu\,\codeBlockId\:\86f7d21f-476c-4d16-8be0-2af71e093d00\,\code\:\\u003c!DOCTYPE html\u003e\\r\\n\u003chtml\u003e\\r\\n\\r\\n\u003chead\u003e\\r\\n \u003ctitle\u003e我的網頁\u003c/title\u003e\\r\\n\u003c/head\u003e\\r\\n\\r\\n\u003cbody\u003e\\r\\n \u003ch1\u003e這是一個標題\u003c/h1\u003e\\r\\n \u003cdiv\u003e\\r\\n \u003ch2 id\\\smallTitle\\\\u003e這是一個小標題\u003c/h2\u003e\\r\\n \u003cp\u003e這是一段文字。\u003c/p\u003e\\r\\n \u003c/div\u003e\\r\\n \u003cscript\u003e/* @cat-caption */\\r\\n console.log(Hi!)/* @cat-caption */\\r\\n \u003c/script\u003e/* @cat-caption */\\r\\n\u003c/body\u003e\\r\\n\\r\\n\u003c/html\u003e\,\language\:\html\,\caption\:\除了在 F12 當中直接運行 JavaScript\\n你可以透過 \u003cscript\u003e 元素插入 JavaScript 程式碼在裏頭\\n他會在網頁打開的瞬間被自動執行\,\imageUrl\:\/api/object/1719241589772-jg0d0n.png\,\imagePosition\:\bottom-right\,\imageFullWidth\:null,\imageWidth\:600,\imageHeight\:null,\order\:2,\createdAt\:\2025-10-19T01:46:56.338Z\,\updatedAt\:\2025-10-19T01:46:56.338Z\},{\id\:\e1qcl7m\,\codeBlockId\:\86f7d21f-476c-4d16-8be0-2af71e093d00\,\code\:\\u003c!DOCTYPE html\u003e\\r\\n\u003chtml\u003e\\r\\n\\r\\n\u003chead\u003e\\r\\n \u003ctitle\u003e我的網頁\u003c/title\u003e\\r\\n\u003c/head\u003e\\r\\n\\r\\n\u003cbody\u003e\\r\\n \u003ch1\u003e這是一個標題\u003c/h1\u003e\\r\\n \u003cdiv\u003e\\r\\n \u003ch2 id\\\smallTitle\\\\u003e這是一個小標題\u003c/h2\u003e\\r\\n \u003cp\u003e這是一段文字。\u003c/p\u003e\\r\\n \u003c/div\u003e\\r\\n \u003cscript\u003e\\r\\n let smallTitleElement document.getElementById(\\\smallTitle\\\);/* @cat-caption */\\r\\n \u003c/script\u003e\\r\\n\u003c/body\u003e\\r\\n\\r\\n\u003c/html\u003e\,\language\:\html\,\caption\:\獲取元素(getElementById)\\n\\n我們可以用 JavaScript 來找到網頁上的元素,就像在樹上找到一片特定的葉子。我們可以用 getElementById 來找元素。\,\imageUrl\:\/api/object/1719241915416-nztzq.png\,\imagePosition\:\bottom-right\,\imageFullWidth\:null,\imageWidth\:301,\imageHeight\:null,\order\:3,\createdAt\:\2025-10-19T01:46:56.338Z\,\updatedAt\:\2025-10-19T01:46:56.338Z\},{\id\:\kpmwsx\,\codeBlockId\:\86f7d21f-476c-4d16-8be0-2af71e093d00\,\code\:\\u003c!DOCTYPE html\u003e\\r\\n\u003chtml\u003e\\r\\n\\r\\n\u003chead\u003e\\r\\n \u003ctitle\u003e我的網頁\u003c/title\u003e\\r\\n\u003c/head\u003e\\r\\n\\r\\n\u003cbody\u003e\\r\\n \u003ch1\u003e這是一個標題\u003c/h1\u003e\\r\\n \u003cdiv\u003e\\r\\n \u003ch2 id\\\smallTitle\\\\u003e這是一個小標題\u003c/h2\u003e\\r\\n \u003cp\u003e這是一段文字。\u003c/p\u003e\\r\\n \u003c/div\u003e\\r\\n \u003cscript\u003e\\r\\n let smallTitleElement document.getElementById(\\\smallTitle\\\);\\r\\n smallTitleElement.style.color \\\red\\\;/* @cat-caption */\\r\\n \u003c/script\u003e\\r\\n\u003c/body\u003e\\r\\n\\r\\n\u003c/html\u003e\,\language\:\html\,\caption\:\接下來就可以很好的去對該元素讀取或操作\\n比如說將他的 style 的 color 直接改成紅色\,\imageUrl\:\/api/object/1719241938952-ioraja.png\,\imagePosition\:\bottom-right\,\imageFullWidth\:null,\imageWidth\:300,\imageHeight\:null,\order\:4,\createdAt\:\2025-10-19T01:46:56.338Z\,\updatedAt\:\2025-10-19T01:46:56.338Z\},{\id\:\p3q2l\,\codeBlockId\:\86f7d21f-476c-4d16-8be0-2af71e093d00\,\code\:\\u003c!DOCTYPE html\u003e\\r\\n\u003chtml\u003e\\r\\n\\r\\n\u003chead\u003e\\r\\n \u003ctitle\u003e我的網頁\u003c/title\u003e\\r\\n\u003c/head\u003e\\r\\n\\r\\n\u003cbody\u003e\\r\\n \u003ch1\u003e這是一個標題\u003c/h1\u003e\\r\\n \u003cdiv\u003e\\r\\n \u003ch2 id\\\smallTitle\\\\u003e這是一個小標題\u003c/h2\u003e\\r\\n \u003cp\u003e這是一段文字。\u003c/p\u003e\\r\\n \u003c/div\u003e\\r\\n \u003cscript\u003e\\r\\n let smallTitleElement document.getElementById(\\\smallTitle\\\);\\r\\n smallTitleElement.style.color \\\red\\\;\\r\\n smallTitleElement.innerText \\\這是一個紅色小標題\\\/* @cat-caption */\\r\\n \u003c/script\u003e\\r\\n\u003c/body\u003e\\r\\n\\r\\n\u003c/html\u003e\,\language\:\html\,\caption\:\將元素裏頭的文字改變\,\imageUrl\:\/api/object/1719241953660-ncbs1o.png\,\imagePosition\:\bottom-right\,\imageFullWidth\:null,\imageWidth\:301,\imageHeight\:null,\order\:5,\createdAt\:\2025-10-19T01:46:56.338Z\,\updatedAt\:\2025-10-19T01:46:56.338Z\},{\id\:\44j8bc\,\codeBlockId\:\86f7d21f-476c-4d16-8be0-2af71e093d00\,\code\:\\u003c!DOCTYPE html\u003e\\r\\n\u003chtml\u003e\\r\\n\\r\\n\u003chead\u003e\\r\\n \u003ctitle\u003e我的網頁\u003c/title\u003e\\r\\n\u003c/head\u003e\\r\\n\\r\\n\u003cbody\u003e\\r\\n \u003ch1\u003e這是一個標題\u003c/h1\u003e\\r\\n \u003cdiv\u003e\\r\\n \u003ch2 id\\\smallTitle\\\\u003e這是一個小標題\u003c/h2\u003e\\r\\n \u003cp\u003e這是一段文字。\u003c/p\u003e\\r\\n \u003cbutton id\\\myButton\\\\u003e這是一個按鈕\u003c/button\u003e/* @cat-caption */\\r\\n \u003c/div\u003e\\r\\n \u003cscript\u003e\\r\\n let smallTitleElement document.getElementById(\\\smallTitle\\\);\\r\\n smallTitleElement.style.color \\\red\\\;\\r\\n smallTitleElement.innerText \\\這是一個紅色小標題\\\;\\r\\n \u003c/script\u003e\\r\\n\u003c/body\u003e\\r\\n\\r\\n\u003c/html\u003e\,\language\:\html\,\caption\:\事件\\n也就是使用者可以對這些元素做的事情\\n好比說我們可以\\\點擊\\\按鈕、可以\\\輸入\\\文字\\n點擊、輸入 這些都是一個一個的事件\\n\\n我們先新增一個按鈕元素來教學\,\imageUrl\:\/api/object/1719242121616-9j34t8.png\,\imagePosition\:\bottom-right\,\imageFullWidth\:null,\imageWidth\:300,\imageHeight\:null,\order\:6,\createdAt\:\2025-10-19T01:46:56.338Z\,\updatedAt\:\2025-10-19T01:46:56.338Z\},{\id\:\100rl5\,\codeBlockId\:\86f7d21f-476c-4d16-8be0-2af71e093d00\,\code\:\\u003c!DOCTYPE html\u003e\\r\\n\u003chtml\u003e\\r\\n\\r\\n\u003chead\u003e\\r\\n \u003ctitle\u003e我的網頁\u003c/title\u003e\\r\\n\u003c/head\u003e\\r\\n\\r\\n\u003cbody\u003e\\r\\n \u003ch1\u003e這是一個標題\u003c/h1\u003e\\r\\n \u003cdiv\u003e\\r\\n \u003ch2 id\\\smallTitle\\\\u003e這是一個小標題\u003c/h2\u003e\\r\\n \u003cp\u003e這是一段文字。\u003c/p\u003e\\r\\n \u003cbutton id\\\myButton\\\\u003e這是一個按鈕\u003c/button\u003e\\r\\n \u003c/div\u003e\\r\\n \u003cscript\u003e\\r\\n let smallTitleElement document.getElementById(\\\smallTitle\\\);\\r\\n smallTitleElement.style.color \\\red\\\;\\r\\n smallTitleElement.innerText \\\這是一個紅色小標題\\\;\\r\\n\\r\\n let button document.getElementById(myButton); /* @cat-caption */\\r\\n \u003c/script\u003e\\r\\n\u003c/body\u003e\\r\\n\\r\\n\u003c/html\u003e\,\language\:\html\,\caption\:\如何知道使用者有沒有點擊這個按鈕呢?\\n第一步,仍然是讓程式碼找到這個按鈕的存在\,\imageUrl\:\/api/object/1719242840588-q33r6l.png\,\imagePosition\:\bottom-right\,\imageFullWidth\:null,\imageWidth\:300,\imageHeight\:null,\order\:7,\createdAt\:\2025-10-19T01:46:56.338Z\,\updatedAt\:\2025-10-19T01:46:56.338Z\},{\id\:\2wo10h\,\codeBlockId\:\86f7d21f-476c-4d16-8be0-2af71e093d00\,\code\:\\u003c!DOCTYPE html\u003e\\r\\n\u003chtml\u003e\\r\\n\\r\\n\u003chead\u003e\\r\\n \u003ctitle\u003e我的網頁\u003c/title\u003e\\r\\n\u003c/head\u003e\\r\\n\\r\\n\u003cbody\u003e\\r\\n \u003ch1\u003e這是一個標題\u003c/h1\u003e\\r\\n \u003cdiv\u003e\\r\\n \u003ch2 id\\\smallTitle\\\\u003e這是一個小標題\u003c/h2\u003e\\r\\n \u003cp\u003e這是一段文字。\u003c/p\u003e\\r\\n \u003cbutton id\\\myButton\\\\u003e這是一個按鈕\u003c/button\u003e\\r\\n \u003c/div\u003e\\r\\n \u003cscript\u003e\\r\\n let smallTitleElement document.getElementById(\\\smallTitle\\\);\\r\\n smallTitleElement.style.color \\\red\\\;\\r\\n smallTitleElement.innerText \\\這是一個紅色小標題\\\;\\r\\n\\r\\n let button document.getElementById(\\\myButton\\\);\\r\\n button.addEventListener(\\\click\\\, () \u003e {/* @cat-caption */\\r\\n smallTitleElement.style.color \\\blue\\\;/* @cat-caption */\\r\\n smallTitleElement.innerText \\\這是一個藍色小標題\\\;/* @cat-caption */\\r\\n });/* @cat-caption */\\r\\n \u003c/script\u003e\\r\\n\u003c/body\u003e\\r\\n\\r\\n\u003c/html\u003e\,\language\:\html\,\caption\:\第二步,創建事件 addEventListener \\n我們可以設定按鈕,當什麼動作發生時,就執行什麼指令\\n\\n以旁邊的例子來說變是,當點擊動作發生時\\n就執行將 小標題元素 顏色改成藍色,並改變文字\\n\\n\\\click\\\ 便是指點擊動作\\n動作都是規範好的,每一個都是關鍵字\\n不能自己蝦打,需要實際去搜尋看什麼動作對應什麼關鍵字\\n\,\imageUrl\:\/api/object/1719242865832-ulg46.png\,\imagePosition\:\bottom-right\,\imageFullWidth\:null,\imageWidth\:301,\imageHeight\:null,\order\:8,\createdAt\:\2025-10-19T01:46:56.338Z\,\updatedAt\:\2025-10-19T01:46:56.338Z\},{\id\:\76dde\,\codeBlockId\:\86f7d21f-476c-4d16-8be0-2af71e093d00\,\code\:\\u003c!DOCTYPE html\u003e\\r\\n\u003chtml\u003e\\r\\n\\r\\n\u003chead\u003e\\r\\n \u003ctitle\u003e我的網頁\u003c/title\u003e\\r\\n\u003c/head\u003e\\r\\n\\r\\n\u003cbody\u003e\\r\\n \u003ch1\u003e這是一個標題\u003c/h1\u003e\\r\\n \u003cdiv\u003e\\r\\n \u003ch2 id\\\smallTitle\\\\u003e這是一個小標題\u003c/h2\u003e\\r\\n \u003cp\u003e這是一段文字。\u003c/p\u003e\\r\\n \u003cbutton id\\\myButton\\\\u003e這是一個按鈕\u003c/button\u003e\\r\\n \u003cinput id\\\myInput\\\ type\\\text\\\ placeholder\\\這是一個輸入框\\\ /\u003e/* @cat-caption */\\r\\n \u003c/div\u003e\\r\\n \u003cscript\u003e\\r\\n let smallTitleElement document.getElementById(\\\smallTitle\\\);\\r\\n smallTitleElement.style.color \\\red\\\;\\r\\n smallTitleElement.innerText \\\這是一個紅色小標題\\\;\\r\\n\\r\\n let button document.getElementById(\\\myButton\\\);\\r\\n button.addEventListener(\\\click\\\, () \u003e {\\r\\n smallTitleElement.style.color \\\blue\\\;\\r\\n smallTitleElement.innerText \\\這是一個藍色小標題\\\;\\r\\n });\\r\\n \u003c/script\u003e\\r\\n\u003c/body\u003e\\r\\n\\r\\n\u003c/html\u003e\,\language\:\html\,\caption\:\最後來講一下讀取輸入框好了\\n我們創建一個輸入框\,\imageUrl\:\/api/object/1719242888386-4p6t3k.png\,\imagePosition\:\bottom-right\,\imageFullWidth\:null,\imageWidth\:300,\imageHeight\:null,\order\:9,\createdAt\:\2025-10-19T01:46:56.338Z\,\updatedAt\:\2025-10-19T01:46:56.338Z\},{\id\:\is3ec9\,\codeBlockId\:\86f7d21f-476c-4d16-8be0-2af71e093d00\,\code\:\\u003c!DOCTYPE html\u003e\\r\\n\u003chtml\u003e\\r\\n\\r\\n\u003chead\u003e\\r\\n \u003ctitle\u003e我的網頁\u003c/title\u003e\\r\\n\u003c/head\u003e\\r\\n\\r\\n\u003cbody\u003e\\r\\n \u003ch1\u003e這是一個標題\u003c/h1\u003e\\r\\n \u003cdiv\u003e\\r\\n \u003ch2 id\\\smallTitle\\\\u003e這是一個小標題\u003c/h2\u003e\\r\\n \u003cp\u003e這是一段文字。\u003c/p\u003e\\r\\n \u003cbutton id\\\myButton\\\\u003e這是一個按鈕\u003c/button\u003e\\r\\n \u003cinput id\\\myInput\\\ type\\\text\\\ placeholder\\\這是一個輸入框\\\ /\u003e\\r\\n \u003c/div\u003e\\r\\n \u003cscript\u003e\\r\\n let smallTitleElement document.getElementById(\\\smallTitle\\\);\\r\\n smallTitleElement.style.color \\\red\\\;\\r\\n smallTitleElement.innerText \\\這是一個紅色小標題\\\;\\r\\n\\r\\n let myInputElement document.getElementById(\\\myInput\\\);/* @cat-caption */\\r\\n\\r\\n let button document.getElementById(\\\myButton\\\);\\r\\n button.addEventListener(\\\click\\\, () \u003e {\\r\\n smallTitleElement.style.color \\\blue\\\;\\r\\n smallTitleElement.innerText \\\這是一個藍色小標題\\\;\\r\\n });\\r\\n \u003c/script\u003e\\r\\n\u003c/body\u003e\\r\\n\\r\\n\u003c/html\u003e\,\language\:\html\,\caption\:\一樣先讓程式碼能抓取該輸入框元素\,\imageUrl\:\/api/object/1719242897496-0i567.png\,\imagePosition\:\bottom-right\,\imageFullWidth\:null,\imageWidth\:300,\imageHeight\:null,\order\:10,\createdAt\:\2025-10-19T01:46:56.338Z\,\updatedAt\:\2025-10-19T01:46:56.338Z\},{\id\:\sv57vu\,\codeBlockId\:\86f7d21f-476c-4d16-8be0-2af71e093d00\,\code\:\\u003c!DOCTYPE html\u003e\\r\\n\u003chtml\u003e\\r\\n\\r\\n\u003chead\u003e\\r\\n \u003ctitle\u003e我的網頁\u003c/title\u003e\\r\\n\u003c/head\u003e\\r\\n\\r\\n\u003cbody\u003e\\r\\n \u003ch1\u003e這是一個標題\u003c/h1\u003e\\r\\n \u003cdiv\u003e\\r\\n \u003ch2 id\\\smallTitle\\\\u003e這是一個小標題\u003c/h2\u003e\\r\\n \u003cp\u003e這是一段文字。\u003c/p\u003e\\r\\n \u003cbutton id\\\myButton\\\\u003e這是一個按鈕\u003c/button\u003e\\r\\n \u003cinput id\\\myInput\\\ type\\\text\\\ placeholder\\\這是一個輸入框\\\ /\u003e\\r\\n \u003c/div\u003e\\r\\n \u003cscript\u003e\\r\\n let smallTitleElement document.getElementById(\\\smallTitle\\\);\\r\\n smallTitleElement.style.color \\\red\\\;\\r\\n smallTitleElement.innerText \\\這是一個紅色小標題\\\;\\r\\n\\r\\n let myInputElement document.getElementById(\\\myInput\\\);\\r\\n\\r\\n let button document.getElementById(\\\myButton\\\);\\r\\n button.addEventListener(\\\click\\\, () \u003e {\\r\\n let inputValue myInputElement.value;/* @cat-caption */\\r\\n smallTitleElement.innerText inputValue;/* @cat-caption */\\r\\n });\\r\\n \u003c/script\u003e\\r\\n\u003c/body\u003e\\r\\n\\r\\n\u003c/html\u003e\,\language\:\html\,\caption\:\我們修改一下剛剛按鈕的點擊命令\\n把它改成,如果被點擊時\\n1. 取得使用者的輸入數值\\n2. 把 smallTitleElement 顯示的文字變成該數值\,\imageUrl\:\/api/object/1719242922424-vsnaab.png\,\imagePosition\:\bottom-right\,\imageFullWidth\:null,\imageWidth\:300,\imageHeight\:null,\order\:11,\createdAt\:\2025-10-19T01:46:56.338Z\,\updatedAt\:\2025-10-19T01:46:56.338Z\},{\id\:\jcjpy7\,\codeBlockId\:\86f7d21f-476c-4d16-8be0-2af71e093d00\,\code\:\\u003c!DOCTYPE html\u003e\\r\\n\u003chtml\u003e\\r\\n\\r\\n\u003chead\u003e\\r\\n \u003ctitle\u003e我的網頁\u003c/title\u003e\\r\\n\u003c/head\u003e\\r\\n\\r\\n\u003cbody\u003e\\r\\n \u003ch1\u003e這是一個標題\u003c/h1\u003e\\r\\n \u003cdiv\u003e\\r\\n \u003ch2 id\\\smallTitle\\\\u003e這是一個小標題\u003c/h2\u003e\\r\\n \u003cp\u003e這是一段文字。\u003c/p\u003e\\r\\n \u003cbutton id\\\myButton\\\\u003e這是一個按鈕\u003c/button\u003e\\r\\n \u003cinput id\\\myInput\\\ type\\\text\\\ placeholder\\\這是一個輸入框\\\ /\u003e\\r\\n \u003c/div\u003e\\r\\n \u003cscript\u003e\\r\\n let smallTitleElement document.getElementById(\\\smallTitle\\\);\\r\\n smallTitleElement.style.color \\\red\\\;\\r\\n smallTitleElement.innerText \\\這是一個紅色小標題\\\;\\r\\n\\r\\n let myInputElement document.getElementById(\\\myInput\\\);\\r\\n\\r\\n let button document.getElementById(\\\myButton\\\);\\r\\n button.addEventListener(\\\click\\\, () \u003e {\\r\\n let inputValue myInputElement.value;\\r\\n smallTitleElement.innerText inputValue;\\r\\n myInputElement.value \\\\\\;/* @cat-caption */\\r\\n });\\r\\n \u003c/script\u003e\\r\\n\u003c/body\u003e\\r\\n\\r\\n\u003c/html\u003e\,\language\:\html\,\caption\:\當然你也可以把輸入框給清空,或修改成你想要的數值\,\imageUrl\:\/api/object/1719242964114-4t1cim.png\,\imagePosition\:\bottom-right\,\imageFullWidth\:null,\imageWidth\:300,\imageHeight\:null,\order\:12,\createdAt\:\2025-10-19T01:46:56.338Z\,\updatedAt\:\2025-10-19T01:46:56.338Z\}},\questionBlock\:null},{\id\:\d45a5fc7-6ab8-483f-9237-3ec39920428e\,\articleId\:\e4d3e885-909a-422b-82dc-1041ecad0aa7\,\noteBlock\:{\id\:\d45a5fc7-6ab8-483f-9237-3ec39920428e\,\content\:\#### 作業時間\\n\\n試試看搭配 if else 語法做一個成績驗證系統\\n\\n如果使用者輸入 大於等於 60 ,按下按鈕後會顯示文字 及格\\n\\n如果使用者輸入 小於 60 ,按下按鈕後會顯示文字 不及格\},\codeBlock\:null,\questionBlock\:null},{\id\:\ba4106ac-d68d-409a-80c7-718d1f027f3b\,\articleId\:\e4d3e885-909a-422b-82dc-1041ecad0aa7\,\noteBlock\:null,\codeBlock\:{\id\:\ba4106ac-d68d-409a-80c7-718d1f027f3b\,\content\:\{\\\id\\\:\\\9ybizh\\\,\\\code\\\:\\\\u003c!DOCTYPE html\u003e\\\\r\\\\n\u003chtml\u003e\\\\r\\\\n\\\\r\\\\n\u003chead\u003e\\\\r\\\\n \u003ctitle\u003e我的網頁\u003c/title\u003e\\\\r\\\\n\u003c/head\u003e\\\\r\\\\n\\\\r\\\\n\u003cbody\u003e\\\\r\\\\n \u003cinput id\\\\\\\scoreInput\\\\\\\ type\\\\\\\text\\\\\\\ placeholder\\\\\\\輸入成績\\\\\\\ /\u003e\\\\r\\\\n \u003cbutton id\\\\\\\checkButton\\\\\\\\u003e檢查分數\u003c/button\u003e\\\\r\\\\n \u003cdiv id\\\\\\\myScoreState\\\\\\\\u003e\u003c/div\u003e\\\\r\\\\n\\\\r\\\\n \u003cscript\u003e\\\\r\\\\n let scoreInput document.getElementById(\\\\\\\scoreInput\\\\\\\);\\\\r\\\\n let checkButton document.getElementById(\\\\\\\checkButton\\\\\\\);\\\\r\\\\n let myScoreState document.getElementById(\\\\\\\myScoreState\\\\\\\);\\\\r\\\\n\\\\r\\\\n checkButton.addEventListener(\\\\\\\click\\\\\\\, function () {\\\\r\\\\n let score parseInt(scoreInput.value);\\\\r\\\\n if (score \u003e 60) {\\\\r\\\\n myScoreState.textContent \\\\\\\恭喜你及格了!\\\\\\\;\\\\r\\\\n } else {\\\\r\\\\n myScoreState.textContent \\\\\\\抱歉你不及格!\\\\\\\;\\\\r\\\\n }\\\\r\\\\n });\\\\r\\\\n \u003c/script\u003e\\\\r\\\\n\u003c/body\u003e\\\\r\\\\n\\\\r\\\\n\u003c/html\u003e\\\,\\\caption\\\:\\\作業時間答案\\\,\\\language\\\:\\\html\\\}\,\chunks\:{\id\:\9ybizh\,\codeBlockId\:\ba4106ac-d68d-409a-80c7-718d1f027f3b\,\code\:\\u003c!DOCTYPE html\u003e\\r\\n\u003chtml\u003e\\r\\n\\r\\n\u003chead\u003e\\r\\n \u003ctitle\u003e我的網頁\u003c/title\u003e\\r\\n\u003c/head\u003e\\r\\n\\r\\n\u003cbody\u003e\\r\\n \u003cinput id\\\scoreInput\\\ type\\\text\\\ placeholder\\\輸入成績\\\ /\u003e\\r\\n \u003cbutton id\\\checkButton\\\\u003e檢查分數\u003c/button\u003e\\r\\n \u003cdiv id\\\myScoreState\\\\u003e\u003c/div\u003e\\r\\n\\r\\n \u003cscript\u003e\\r\\n let scoreInput document.getElementById(\\\scoreInput\\\);\\r\\n let checkButton document.getElementById(\\\checkButton\\\);\\r\\n let myScoreState document.getElementById(\\\myScoreState\\\);\\r\\n\\r\\n checkButton.addEventListener(\\\click\\\, function () {\\r\\n let score parseInt(scoreInput.value);\\r\\n if (score \u003e 60) {\\r\\n myScoreState.textContent \\\恭喜你及格了!\\\;\\r\\n } else {\\r\\n myScoreState.textContent \\\抱歉你不及格!\\\;\\r\\n }\\r\\n });\\r\\n \u003c/script\u003e\\r\\n\u003c/body\u003e\\r\\n\\r\\n\u003c/html\u003e\,\language\:\html\,\caption\:\作業時間答案\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:0,\createdAt\:\2025-10-19T01:46:51.696Z\,\updatedAt\:\2025-10-19T01:46:51.696Z\}},\questionBlock\:null},{\id\:\2f7627b3-227a-4967-a115-e52c18a006e7\,\articleId\:\e4d3e885-909a-422b-82dc-1041ecad0aa7\,\noteBlock\:{\id\:\2f7627b3-227a-4967-a115-e52c18a006e7\,\content\:\### Canvas\\n\\nCanvas 是 Html 的元素 \\\\\u003ccanvas\u003e\\n\\n我們可以用 Canvas 來繪製圖形,就像用畫筆在畫布上畫畫一樣。\},\codeBlock\:null,\questionBlock\:null},{\id\:\a80a46f9-389a-4015-b7d6-e17333f54c20\,\articleId\:\e4d3e885-909a-422b-82dc-1041ecad0aa7\,\noteBlock\:null,\codeBlock\:{\id\:\a80a46f9-389a-4015-b7d6-e17333f54c20\,\content\:\$43\,\chunks\:{\id\:\3vs4b\,\codeBlockId\:\a80a46f9-389a-4015-b7d6-e17333f54c20\,\code\:\\u003c!DOCTYPE html\u003e\\r\\n\u003chtml\u003e\\r\\n\\r\\n\u003chead\u003e\\r\\n \u003ctitle\u003eCanvas 動畫\u003c/title\u003e\\r\\n\u003c/head\u003e\\r\\n\\r\\n\u003cbody\u003e\\r\\n \u003ccanvas id\\\myCanvas\\\ width\\\400\\\ height\\\400\\\\u003e\u003c/canvas\u003e/* @cat-caption */\\r\\n\u003c/body\u003e\\r\\n\\r\\n\u003c/html\u003e\,\language\:\html\,\caption\:\先替 Html 加上 canvas \\n你可以指定他的寬度跟高度\,\imageUrl\:\/api/object/1719245856878-ia1ct.png\,\imagePosition\:\bottom-right\,\imageFullWidth\:null,\imageWidth\:200,\imageHeight\:null,\order\:0,\createdAt\:\2025-10-19T01:46:53.970Z\,\updatedAt\:\2025-10-19T01:46:53.970Z\},{\id\:\6ls6f\,\codeBlockId\:\a80a46f9-389a-4015-b7d6-e17333f54c20\,\code\:\\u003c!DOCTYPE html\u003e\\r\\n\u003chtml\u003e\\r\\n \u003chead\u003e\\r\\n \u003ctitle\u003eCanvas 動畫\u003c/title\u003e\\r\\n \u003c/head\u003e\\r\\n \u003cbody\u003e\\r\\n \u003ccanvas id\\\myCanvas\\\ width\\\400\\\ height\\\400\\\\u003e\u003c/canvas\u003e\\r\\n \u003cscript\u003e\\r\\n const canvas document.getElementById(\\\myCanvas\\\);/* @cat-caption */\\r\\n \u003c/script\u003e\\r\\n \u003c/body\u003e\\r\\n\u003c/html\u003e\\r\\n\,\language\:\html\,\caption\:\先透過 getElementById 取得該元素\,\imageUrl\:\/api/object/1719245854025-ebvg4l.png\,\imagePosition\:\bottom-right\,\imageFullWidth\:null,\imageWidth\:200,\imageHeight\:null,\order\:1,\createdAt\:\2025-10-19T01:46:53.970Z\,\updatedAt\:\2025-10-19T01:46:53.970Z\},{\id\:\g4ahzh\,\codeBlockId\:\a80a46f9-389a-4015-b7d6-e17333f54c20\,\code\:\\u003c!DOCTYPE html\u003e\\r\\n\u003chtml\u003e\\r\\n\\r\\n\u003chead\u003e\\r\\n \u003ctitle\u003eCanvas 動畫\u003c/title\u003e\\r\\n\u003c/head\u003e\\r\\n\\r\\n\u003cbody\u003e\\r\\n \u003ccanvas id\\\myCanvas\\\ width\\\400\\\ height\\\400\\\\u003e\u003c/canvas\u003e\\r\\n \u003cscript\u003e\\r\\n const canvas document.getElementById(\\\myCanvas\\\);\\r\\n const ctx canvas.getContext(\\\2d\\\);/* @cat-caption */\\r\\n \u003c/script\u003e\\r\\n\u003c/body\u003e\\r\\n\\r\\n\u003c/html\u003e\,\language\:\html\,\caption\:\再來第二步,canvas 提供了一種名為 getContext(2d) 的方法,可以讓我們取得他的畫筆\\n如果要繪製這個畫布都必須透過這個畫筆\,\imageUrl\:\/api/object/1719245851293-8yr6co.png\,\imagePosition\:\bottom-right\,\imageFullWidth\:null,\imageWidth\:200,\imageHeight\:null,\order\:2,\createdAt\:\2025-10-19T01:46:53.970Z\,\updatedAt\:\2025-10-19T01:46:53.970Z\},{\id\:\5y7lvm\,\codeBlockId\:\a80a46f9-389a-4015-b7d6-e17333f54c20\,\code\:\\u003c!DOCTYPE html\u003e\\r\\n\u003chtml\u003e\\r\\n\\r\\n\u003chead\u003e\\r\\n \u003ctitle\u003eCanvas 動畫\u003c/title\u003e\\r\\n\u003c/head\u003e\\r\\n\\r\\n\u003cbody\u003e\\r\\n \u003ccanvas id\\\myCanvas\\\ width\\\400\\\ height\\\400\\\\u003e\u003c/canvas\u003e\\r\\n \u003cscript\u003e\\r\\n const canvas document.getElementById(\\\myCanvas\\\);\\r\\n const ctx canvas.getContext(\\\2d\\\);\\r\\n\\r\\n ctx.fillStyle \\\blue\\\;/* @cat-caption */\\r\\n \u003c/script\u003e\\r\\n\u003c/body\u003e\\r\\n\\r\\n\u003c/html\u003e\,\language\:\html\,\caption\:\畫筆的操作就如同小畫家一樣\\n只是把所有的動作用一行一行的程式碼表示出來\\n\\n首先先將畫筆的顏色變成藍色\,\imageUrl\:\/api/object/1719245849110-dv2sal.png\,\imagePosition\:\bottom-right\,\imageFullWidth\:null,\imageWidth\:200,\imageHeight\:null,\order\:3,\createdAt\:\2025-10-19T01:46:53.970Z\,\updatedAt\:\2025-10-19T01:46:53.970Z\},{\id\:\wp3ll\,\codeBlockId\:\a80a46f9-389a-4015-b7d6-e17333f54c20\,\code\:\\u003c!DOCTYPE html\u003e\\r\\n\u003chtml\u003e\\r\\n\\r\\n\u003chead\u003e\\r\\n \u003ctitle\u003eCanvas 動畫\u003c/title\u003e\\r\\n\u003c/head\u003e\\r\\n\\r\\n\u003cbody\u003e\\r\\n \u003ccanvas id\\\myCanvas\\\ width\\\400\\\ height\\\400\\\\u003e\u003c/canvas\u003e\\r\\n \u003cscript\u003e\\r\\n const canvas document.getElementById(\\\myCanvas\\\);\\r\\n const ctx canvas.getContext(\\\2d\\\);\\r\\n\\r\\n ctx.fillStyle \\\blue\\\;\\r\\n ctx.fillRect(50, 50, 150, 150);/* @cat-caption */\\r\\n \u003c/script\u003e\\r\\n\u003c/body\u003e\\r\\n\\r\\n\u003c/html\u003e\,\language\:\html\,\caption\:\再來,繪製一個正方形\\n他的格式長這樣\\nctx.fillRect(x, y, width, height)\\n\\n也就是從 (x,y) 這個座標點繪製一個寬 width 高 height 的正方形\,\imageUrl\:\/api/object/1719245831280-yh584c.png\,\imagePosition\:\bottom-right\,\imageFullWidth\:null,\imageWidth\:200,\imageHeight\:null,\order\:4,\createdAt\:\2025-10-19T01:46:53.970Z\,\updatedAt\:\2025-10-19T01:46:53.970Z\}},\questionBlock\:null}},{\id\:\9f7de1e5-b4f1-40cb-9322-5cc6a141db25\,\title\:\第五部分:後端 Node.js 與 Socket.IO\,\content\:\\,\blockIndexIds\:\0872c989-f1fa-43e6-923c-d75bc5f96dbc\,\5676f9b5-0e1a-43c0-9705-def5765410f2\,\52c75bed-1529-4e1b-a81d-f7b4b1f1a61e\,\9bc7e136-3017-4db8-ac38-d95500ebd836\,\d86c794c-bbc1-4598-8bc1-86181699fad6\,\9585ff07-585f-46ce-bfdc-0501fd678a40\,\87a0ac3d-e8b6-49b2-8f23-2155250e386a\,\e635222f-282b-4e1f-899c-f9a20c2bf4a8\,\slug\:null,\metaDescription\:null,\ogImage\:null,\privacy\:\PUBLIC\,\allowedEmails\:,\createdAt\:\2024-06-23T23:49:14.426Z\,\updatedAt\:\2025-11-17T23:54:50.254Z\,\authorId\:\24c3f178-4a5b-447d-b586-16d7d459c606\,\viewCount\:114,\newsletterSentAt\:\2025-10-19T09:55:10.552Z\,\blocks\:{\id\:\0872c989-f1fa-43e6-923c-d75bc5f96dbc\,\articleId\:\9f7de1e5-b4f1-40cb-9322-5cc6a141db25\,\noteBlock\:{\id\:\0872c989-f1fa-43e6-923c-d75bc5f96dbc\,\content\:\### Node.js 基本介紹\\n\\n##### 什麼是 Node.js?\\n\\nJavaScript 一直以來都只能在瀏覽器上運行\\n在過去他並不像 C、C++、Python 能獨立在你電腦上運行\\n\\n這導致他無法讀取、修改你電腦上的檔案\\n進行更複雜的操作\\n\\n而 Node.js 就是為了打破這個窘境,他允許你在你的電腦直接執行 JavaScript\\n這使得 JavaScript 變得異常強大,能做出其他程式語言能做的事情\\n\\n**Node.js 的優勢**:\\n\\n* **速度快**:Node.js 可以很快地處理很多事情。\\n* **模組系統**:Node.js 有很多模組,就像樂高積木一樣,可以讓我們組裝出各種不同的功能。\\n\\n**Node.js 的應用場景**:\\n\\n* **即時應用**:例如聊天應用和即時遊戲,Node.js 可以讓我們很快地傳遞訊息。\\n* **API 伺服器**:我們可以用 Node.js 來建立 API,讓不同的應用程式互相溝通。\\n\\n##### 安裝與配置 Node.js 環境\\n\\n就像我們要用新的玩具,需要先安裝它們一樣,我們也需要安裝 Node.js。\\n\\n1. **下載 Node.js**:從 Node.js 的官網(Node.js 官網(https://nodejs.org/))下載對應自己電腦的版本。\\n2. **安裝 Node.js**:打開下載的安裝包,按照提示一步步安裝。安裝完成後,在命令行中輸入 `node -v`,如果顯示版本號,說明安裝成功。\\n\\n#### NPM\\n\\nNodeJs 最強大的莫過於 npm 這個巨大的套件管理系統\\n你可以把她想像成他是一個程式庫商店\\n任何的人,都可以將他寫好的程式碼上架上去\\n\\n而今天如果你需要的功能,剛好別人已經寫好了\\n擬就可以直接下載下來,直接使用\\n\\n俗話說的好,輪子不要重頭造\\n要善用前人寫好的工具,人類的科技疊代才有意義\\n\\nNPM 除了提供程式庫商店這個大平台以外,他還會協助你管理你下載下來的套件\\n甚至可以幫你做一些簡單的專案設定\},\codeBlock\:null,\questionBlock\:null},{\id\:\5676f9b5-0e1a-43c0-9705-def5765410f2\,\articleId\:\9f7de1e5-b4f1-40cb-9322-5cc6a141db25\,\noteBlock\:null,\codeBlock\:{\id\:\5676f9b5-0e1a-43c0-9705-def5765410f2\,\content\:\$44\,\chunks\:{\id\:\8sk8ml\,\codeBlockId\:\5676f9b5-0e1a-43c0-9705-def5765410f2\,\code\:\npm init -y\\r\\n\,\language\:\plaintext\,\caption\:\建立新專案\\n\\n1. 在命令行中導航到你想要建立專案的目錄。\\n2. 輸入右邊命令來初始化專案\\n\\n這會自動生成一個 package.json 文件,這個文件用來記錄專案的配置信息和依賴。\\n\\n\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:0,\createdAt\:\2025-10-19T01:46:55.824Z\,\updatedAt\:\2025-10-19T01:46:55.824Z\},{\id\:\7gl84a\,\codeBlockId\:\5676f9b5-0e1a-43c0-9705-def5765410f2\,\code\:\console.log(\\\Hello World\\\);\,\language\:\javascript\,\caption\:\建立一個 index.js 檔案\\n裏頭會放置我們伺服器的程式碼\\n\\n你可以在終端機中輸入 node index.js\\n來直接運行這個程式碼\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:1,\createdAt\:\2025-10-19T01:46:55.824Z\,\updatedAt\:\2025-10-19T01:46:55.824Z\},{\id\:\czrz\,\codeBlockId\:\5676f9b5-0e1a-43c0-9705-def5765410f2\,\code\:\function add(a, b){/* @cat-caption */\\r\\n return a + b;/* @cat-caption */\\r\\n}/* @cat-caption */\,\language\:\javascript\,\caption\:\Node.js 模組系統\\n\\nNode.js 有一個強大的模組系統,可以讓我們把程式碼分成不同的部分,然後需要的時候再引用。\\n\\n首先我們先創建第二個檔案叫做 add.js\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:2,\createdAt\:\2025-10-19T01:46:55.824Z\,\updatedAt\:\2025-10-19T01:46:55.824Z\},{\id\:\jxd4zq\,\codeBlockId\:\5676f9b5-0e1a-43c0-9705-def5765410f2\,\code\:\export function add(a, b){/* @cat-caption */\\r\\n return a + b;\\r\\n}\,\language\:\javascript\,\caption\:\再來,如果我們希望可以讓其他檔案使用我們這個 add function \\n我們必須使用 export 關鍵字放在我們想開發的 function 前\\n\\n不只只是 function,變數阿,都是可以被導出的\\n\\n我更喜歡說這叫做共享程式碼\\n允許這個變數被別人共享\\n\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:3,\createdAt\:\2025-10-19T01:46:55.824Z\,\updatedAt\:\2025-10-19T01:46:55.824Z\},{\id\:\egemif\,\codeBlockId\:\5676f9b5-0e1a-43c0-9705-def5765410f2\,\code\:\import { add } from \\\./add.js\\\;/* @cat-caption */\\r\\n\\r\\nconsole.log(add(1, 2)); // 3 /* @cat-caption */\,\language\:\javascript\,\caption\:\回到 index.js\\n我們可以透過 import 關鍵字導入我們剛剛共享出去的 function\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:4,\createdAt\:\2025-10-19T01:46:55.824Z\,\updatedAt\:\2025-10-19T01:46:55.824Z\},{\id\:\xuet4b\,\codeBlockId\:\5676f9b5-0e1a-43c0-9705-def5765410f2\,\code\:\import http from \\\http\\\;\\r\\n\\r\\n// 我們等等會來解釋這段程式碼的功能\\r\\nconst server http.createServer((req, res) \u003e {\\r\\n res.statusCode 200; // 你\\r\\n res.setHeader(\\\Content-Type\\\, \\\text/plain\\\);\\r\\n res.end(\\\Hello World\\\\n\\\);\\r\\n});\\r\\n\\r\\nserver.listen(3000, () \u003e {\\r\\n console.log(\\\伺服器運行在 http://127.0.0.1:3000/\\\);\\r\\n});\\r\\n\,\language\:\javascript\,\caption\:\除了我們自己設計的模組外\\nNodeJs 幫我們設計了很多原生官方的實用模組\\n可以讓我們不用安裝的情況下直接 import 使用\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:5,\createdAt\:\2025-10-19T01:46:55.824Z\,\updatedAt\:\2025-10-19T01:46:55.824Z\}},\questionBlock\:null},{\id\:\52c75bed-1529-4e1b-a81d-f7b4b1f1a61e\,\articleId\:\9f7de1e5-b4f1-40cb-9322-5cc6a141db25\,\noteBlock\:{\id\:\52c75bed-1529-4e1b-a81d-f7b4b1f1a61e\,\content\:\## Node.js HTTP 伺服器的基本概念\\n\\nHTTP 伺服器就像是一個專門接收客人訂單的櫃檯。\\n\\n以上面的程式碼為例子就像是開了一家簡單的餐廳,每當有客人來訪(發送請求),我們都會回應他們一句 \\\Hello, World!\\\。\\n\\n### RESTful 架構設計\\n\\n什麼是 RESTful 架構?\\n\\n為了管理好我們的餐廳,我們設計了ㄧ個設計準則來管理我們的餐廳\\n\\n想像我們的餐廳不只賣一種食物,我們有菜單(資源),客人可以對菜單上的食物進行不同的操作(CRUD:建立、讀取、更新、刪除)。\\n\\n### RESTful API 的基礎操作\\n\\n* **建立(Create)**:客人點了一道新菜(POST)\\n* **讀取(Read)**:客人查看菜單上的菜(GET)\\n* **更新(Update)**:客人修改點的菜(PUT)\\n* **刪除(Delete)**:客人取消點的菜(DELETE)\\n\\n基本上,每一次客人來訪時,都會很明確的聲明目的地\\n\\n舉例來講,當你用瀏覽器打開某個網站,好比說 https://www.google.com/(https://www.google.com/)\\n\\n其實你就是使用瀏覽器對 https://www.google.com/(https://www.google.com/) 這個網址發起了一次 GET 請求\\n\\n而 Google 的伺服器在收到你的請求後,將整個網站 HTML 傳給你\\n\\n你就能看到他的網站\\n\\n### Express 套件\\n\\nNodejs 有一個別人寫好的套件, Express.js\u0026#x20;\\n\\n他可以更簡單地去設定當客人傳什麼請求時,就做什麼事情\\n\\n我們直接來看看吧\},\codeBlock\:null,\questionBlock\:null},{\id\:\9bc7e136-3017-4db8-ac38-d95500ebd836\,\articleId\:\9f7de1e5-b4f1-40cb-9322-5cc6a141db25\,\noteBlock\:null,\codeBlock\:{\id\:\9bc7e136-3017-4db8-ac38-d95500ebd836\,\content\:\$45\,\chunks\:{\id\:\lxid8sr\,\codeBlockId\:\9bc7e136-3017-4db8-ac38-d95500ebd836\,\code\:\npm install express /* @cat-caption */\,\language\:\plaintext\,\caption\:\首先,透過 npm 安裝 express 套件\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:0,\createdAt\:\2025-10-19T01:46:52.814Z\,\updatedAt\:\2025-10-19T01:46:52.814Z\},{\id\:\df39ib\,\codeBlockId\:\9bc7e136-3017-4db8-ac38-d95500ebd836\,\code\:\import http from \\\http\\\;\\nimport express from \\\express\\\;/* @cat-caption */\\n\\n// const server http.createServer((req, res) \u003e {\\n// res.statusCode 200; // 你\\n// res.setHeader(\\\Content-Type\\\, \\\text/plain\\\);\\n// res.end(\\\Hello World\\\\n\\\);\\n// });\\n\\n// server.listen(3000, () \u003e {\\n// console.log(\\\伺服器運行在 http://127.0.0.1:3000/\\\);\\n// });\\n\,\language\:\javascript\,\caption\:\安裝成功後,我們就能在我們的程式碼引入他了\\n\\n我們先將原本的 http server 註解掉\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:1,\createdAt\:\2025-10-19T01:46:52.814Z\,\updatedAt\:\2025-10-19T01:46:52.814Z\},{\id\:\pgvhhw\,\codeBlockId\:\9bc7e136-3017-4db8-ac38-d95500ebd836\,\code\:\import http from \\\http\\\;\\nimport express from \\\express\\\;\\nconst app express();/* @cat-caption */\\n\\napp.get(/, (req, res) \u003e {/* @cat-caption */\\n res.send(Hello, World!);/* @cat-caption */\\n});/* @cat-caption */\\n\\napp.listen(3000, () \u003e {/* @cat-caption */\\n console.log(\\\伺服器運行在 http://127.0.0.1:3000/\\\);/* @cat-caption */\\n});/* @cat-caption */\\n\\n// const server http.createServer((req, res) \u003e {\\n// res.statusCode 200; // 你\\n// res.setHeader(\\\Content-Type\\\, \\\text/plain\\\);\\n// res.end(\\\Hello World\\\\n\\\);\\n// });\\n\\n// server.listen(3000, () \u003e {\\n// console.log(\\\伺服器運行在 http://127.0.0.1:3000/\\\);\\n// });\\n\,\language\:\javascript\,\caption\:\利用 express 創建一個伺服器\\n\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:2,\createdAt\:\2025-10-19T01:46:52.814Z\,\updatedAt\:\2025-10-19T01:46:52.814Z\},{\id\:\nt51pv\,\codeBlockId\:\9bc7e136-3017-4db8-ac38-d95500ebd836\,\code\:\import http from \\\http\\\;\\nimport express from \\\express\\\;\\nconst app express();\\n\\napp.get(/, (req, res) \u003e {\\n res.send(Hello, World!);\\n});\\n\\napp.listen(3000, () \u003e {\\n console.log(\\\伺服器運行在 http://127.0.0.1:3000/\\\);\\n});\\n\\n// const server http.createServer((req, res) \u003e {/* @cat-caption */\\n// res.statusCode 200; // 你/* @cat-caption */\\n// res.setHeader(\\\Content-Type\\\, \\\text/plain\\\);/* @cat-caption */\\n// res.end(\\\Hello World\\\\n\\\);/* @cat-caption */\\n// });/* @cat-caption */\\n\\n// server.listen(3000, () \u003e {/* @cat-caption */\\n// console.log(\\\伺服器運行在 http://127.0.0.1:3000/\\\);/* @cat-caption */\\n// });/* @cat-caption */\\n\,\language\:\javascript\,\caption\:\注意看我們註解掉的程式碼(原生 Nodejs 的 http),跟 express 的程式碼\\n他們的功能是一模一樣的\\n只是 express 更加簡單易懂\\n所以接下來都會使用 express 來講解\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:3,\createdAt\:\2025-10-19T01:46:52.814Z\,\updatedAt\:\2025-10-19T01:46:52.814Z\},{\id\:\pao9n\,\codeBlockId\:\9bc7e136-3017-4db8-ac38-d95500ebd836\,\code\:\import http from \\\http\\\;\\nimport express from \\\express\\\;\\nconst app express();\\n\\napp.get(/, (req, res) \u003e {/* @cat-caption */\\n res.send(Hello, World!);/* @cat-caption */\\n});/* @cat-caption */\\n\\napp.listen(3000, () \u003e {\\n console.log(\\\伺服器運行在 http://127.0.0.1:3000/\\\);\\n});\,\language\:\javascript\,\caption\:\右邊的程式碼的意思是\\n當有人向 \\\http://127.0.0.1:3000/\\\ 這個網址發起 GET 請求時,則回傳 Hello World\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:4,\createdAt\:\2025-10-19T01:46:52.814Z\,\updatedAt\:\2025-10-19T01:46:52.814Z\},{\id\:\pfuni\,\codeBlockId\:\9bc7e136-3017-4db8-ac38-d95500ebd836\,\code\:\import http from \\\http\\\;\\nimport express from \\\express\\\;\\nconst app express();\\n\\napp.get(/, (req, res) \u003e {\\n res.send(Hello, World!);\\n});\\n\\napp.get(/cat, (req, res) \u003e {/* @cat-caption */\\n res.send(Cat is Cute!);/* @cat-caption */\\n});/* @cat-caption */\\n\\napp.listen(3000, () \u003e {\\n console.log(\\\伺服器運行在 http://127.0.0.1:3000/\\\);\\n});\,\language\:\javascript\,\caption\:\右邊的程式碼的意思是\\n當有人向 \\\http://127.0.0.1:3000/cat\\\ 這個網址發起 GET 請求時,則回傳 Cat is Cute!\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:5,\createdAt\:\2025-10-19T01:46:52.814Z\,\updatedAt\:\2025-10-19T01:46:52.814Z\},{\id\:\82x7l8\,\codeBlockId\:\9bc7e136-3017-4db8-ac38-d95500ebd836\,\code\:\import http from \\\http\\\;\\nimport express from \\\express\\\;\\nconst app express();\\n\\napp.get(/, (req, res) \u003e {\\n res.send(Hello, World!);\\n});\\n\\napp.get(/cat, (req, res) \u003e {\\n res.send(Cat is Cute!);\\n});\\n\\napp.post(/cat, (req, res) \u003e {/* @cat-caption */\\n res.send(Create A Cat!);/* @cat-caption */\\n});/* @cat-caption */\\n\\napp.listen(3000, () \u003e {\\n console.log(\\\伺服器運行在 http://127.0.0.1:3000/\\\);\\n});\,\language\:\javascript\,\caption\:\右邊的程式碼的意思是\\n當有人向 \\\http://127.0.0.1:3000/cat\\\ 這個網址發起 POST 請求時,則回傳 Create A Cat!\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:6,\createdAt\:\2025-10-19T01:46:52.814Z\,\updatedAt\:\2025-10-19T01:46:52.814Z\},{\id\:\k2ilzr\,\codeBlockId\:\9bc7e136-3017-4db8-ac38-d95500ebd836\,\code\:\import http from \\\http\\\;\\nimport express from \\\express\\\;\\nconst app express();\\n\\n// 創建一個陣列來儲存貓貓\\nlet cats ;/* @cat-caption */\\n\\napp.get(/cat, (req, res) \u003e {/* @cat-caption */\\n /** 回傳貓貓的資料 *//* @cat-caption */\\n});/* @cat-caption */\\n\\napp.post(/cat, (req, res) \u003e {/* @cat-caption */\\n /** 接收貓貓的資料 *//* @cat-caption */\\n});/* @cat-caption */\\n\\napp.listen(3000, () \u003e {\\n console.log(\\\伺服器運行在 http://127.0.0.1:3000/\\\);\\n});\,\language\:\javascript\,\caption\:\我們帶一點實際例子吧\\n設計一個貓貓名稱儲存器\\n用戶可以新增貓貓,並且得所有的貓貓ㄇ\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:7,\createdAt\:\2025-10-19T01:46:52.814Z\,\updatedAt\:\2025-10-19T01:46:52.814Z\},{\id\:\pslk7q\,\codeBlockId\:\9bc7e136-3017-4db8-ac38-d95500ebd836\,\code\:\import http from \\\http\\\;\\nimport express from \\\express\\\;\\nimport cors from \\\cors\\\;/* @cat-caption */\\nconst app express();\\napp.use(cors());/* @cat-caption */\\n\\n// 創建一個陣列來儲存貓貓\\nlet cats ;\\n\\napp.get(/cat, (req, res) \u003e {\\n /** 回傳貓貓的資料 */\\n});\\n\\napp.post(/cat, (req, res) \u003e {\\n /** 接收貓貓的資料 */\\n});\\n\\napp.listen(3000, () \u003e {\\n console.log(\\\伺服器運行在 http://127.0.0.1:3000/\\\);\\n});\,\language\:\javascript\,\caption\:\首先,Express 伺服器有安全保護\\n他不允許任何人都可以來訪問我\\n我們先安裝 cors 這個套件\\n暫時卸除一下這個安全保護\\nnpm i cors\\n\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:8,\createdAt\:\2025-10-19T01:46:52.814Z\,\updatedAt\:\2025-10-19T01:46:52.814Z\},{\id\:\8uxg2q\,\codeBlockId\:\9bc7e136-3017-4db8-ac38-d95500ebd836\,\code\:\import http from \\\http\\\;\\nimport express from \\\express\\\;\\nimport cors from \\\cors\\\;/* @cat-caption */\\nconst app express();\\napp.use(cors());/* @cat-caption */\\napp.use(express.json()); // 解析 JSON 格式的請求/* @cat-caption */\\n\\n// 創建一個陣列來儲存貓貓\\nlet cats ;\\n\\napp.get(/cat, (req, res) \u003e {\\n /** 回傳貓貓的資料 */\\n});\\n\\napp.post(/cat, (req, res) \u003e {\\n /** 接收貓貓的資料 */\\n});\\n\\napp.listen(3000, () \u003e {\\n console.log(\\\伺服器運行在 http://127.0.0.1:3000/\\\);\\n});\,\language\:\javascript\,\caption\:\再來,因為我們前端要傳送 json 物件的資料給 express\\n設定一下,讓 express 能理解 json 格式長怎樣\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:9,\createdAt\:\2025-10-19T01:46:52.814Z\,\updatedAt\:\2025-10-19T01:46:52.814Z\},{\id\:\85sare\,\codeBlockId\:\9bc7e136-3017-4db8-ac38-d95500ebd836\,\code\:\import express from \\\express\\\;\\nimport cors from \\\cors\\\;\\n\\nconst app express();\\napp.use(cors());\\napp.use(express.json()); // 解析 JSON 格式的請求\\n\\n// 創建一個陣列來儲存貓貓\\nlet cats ;\\n\\napp.get(\\\/cat\\\, (req, res) \u003e {\\n res.send(JSON.stringify(cats));/* @cat-caption */\\n});\\n\\napp.post(\\\/cat\\\, (req, res) \u003e {\\n cats.push(req.body.cat);/* @cat-caption */\\n res.send(\\\successfully add cat\\\);/* @cat-caption */\\n});\\n\\napp.listen(3000, () \u003e {\\n console.log(\\\伺服器運行在 http://127.0.0.1:3001/\\\);\\n});\\n\,\language\:\javascript\,\caption\:\開始實作細節啦\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:10,\createdAt\:\2025-10-19T01:46:52.814Z\,\updatedAt\:\2025-10-19T01:46:52.814Z\},{\id\:\9izb0z\,\codeBlockId\:\9bc7e136-3017-4db8-ac38-d95500ebd836\,\code\:\// 這裡是前端的程式碼\\nconst response await fetch(\\\http://127.0.0.1:3001/cat\\\, {\\n method: \\\POST\\\,\\n headers: {\\n Content-Type: application/json // 告訴 express,我送過去的資料格式,是 json 格式\\n },\\n body: JSON.stringify({cat:test}), // 請注意,送過去的資料必須先透過 JSON.stringify 轉成字串才能傳送\\n \\n}) // 以 GET 方式發送請求\\n\\nconst data await response.text(); // 將取得的資料以純文字解析\\nconsole.log(data) // 印出來\,\language\:\javascript\,\caption\:\我們來看一下前端 JavaScript 要怎樣發送請求比較好\\n請在瀏覽器的 F12 試試看\\n右邊是 前端 向 後端請求取得貓貓的資料\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:11,\createdAt\:\2025-10-19T01:46:52.814Z\,\updatedAt\:\2025-10-19T01:46:52.814Z\},{\id\:\5p6vb\,\codeBlockId\:\9bc7e136-3017-4db8-ac38-d95500ebd836\,\code\:\// 這裡是前端的程式碼\\nconst response await fetch(\\\http://127.0.0.1:3001/cat\\\, {\\n method: \\\POST\\\,\\n headers: {\\n Content-Type: application/json // 告訴 express,我送過去的資料格式,是 json 格式\\n },\\n body: JSON.stringify({cat:test}), // 請注意,送過去的資料必須先透過 JSON.stringify 轉成字串才能傳送\\n \\n}) // 以 GET 方式發送請求\\n\\nconst data await response.text(); // 將取得的資料以純文字解析\\nconsole.log(data) // 印出來\,\language\:\javascript\,\caption\:\右邊是 前端 向 後端請求新增貓貓的資料\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:12,\createdAt\:\2025-10-19T01:46:52.814Z\,\updatedAt\:\2025-10-19T01:46:52.814Z\}},\questionBlock\:null},{\id\:\d86c794c-bbc1-4598-8bc1-86181699fad6\,\articleId\:\9f7de1e5-b4f1-40cb-9322-5cc6a141db25\,\noteBlock\:{\id\:\d86c794c-bbc1-4598-8bc1-86181699fad6\,\content\:\## MongoDB 簡介\\n\\n### 什麼是 MongoDB?\\n\\nMongoDB 是一種 NoSQL 資料庫。可以想像成一個巨大的數位檔案櫃,我們可以把資料(例如菜單上的菜)存儲在其中,並且隨時讀取、更新或刪除。\\n\\n當中最大的特色便是他的資料是永久儲存\\n\\n不會因為電腦關機、程市關閉資料就消失\\n\\n### 安裝與設置\\n\\n首先,我們需要安裝 MongoDB。可以從 MongoDB 官網(https://www.mongodb.com/) 下載並安裝。安裝完後,可以用以下指令來啟動 MongoDB\},\codeBlock\:null,\questionBlock\:null},{\id\:\9585ff07-585f-46ce-bfdc-0501fd678a40\,\articleId\:\9f7de1e5-b4f1-40cb-9322-5cc6a141db25\,\noteBlock\:null,\codeBlock\:{\id\:\9585ff07-585f-46ce-bfdc-0501fd678a40\,\content\:\$46\,\chunks\:{\id\:\8vttlba\,\codeBlockId\:\9585ff07-585f-46ce-bfdc-0501fd678a40\,\code\:\npm install mongoose\\n\,\language\:\plaintext\,\caption\:\為了在 Node.js 中使用 MongoDB,我們將使用 Mongoose,它是一個方便的工具,可以讓我們輕鬆地與 MongoDB 進行互動。\\n\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:0,\createdAt\:\2025-10-19T01:46:56.072Z\,\updatedAt\:\2025-10-19T01:46:56.072Z\},{\id\:\evs48p\,\codeBlockId\:\9585ff07-585f-46ce-bfdc-0501fd678a40\,\code\:\import mongoose from mongoose;\\n\\nmongoose.connect(mongodb://localhost:27017/restaurant, {\\n useNewUrlParser: true,\\n useUnifiedTopology: true\\n});\\n\\nconst db mongoose.connection;\\ndb.on(error, console.error.bind(console, MongoDB 連接錯誤:));\\ndb.once(open, () \u003e {\\n console.log(已成功連接到 MongoDB);\\n});\,\language\:\javascript\,\caption\:\連接到 MongoDB\\n首先,我們需要連接到 MongoDB:\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:1,\createdAt\:\2025-10-19T01:46:56.072Z\,\updatedAt\:\2025-10-19T01:46:56.072Z\},{\id\:\ystg9\,\codeBlockId\:\9585ff07-585f-46ce-bfdc-0501fd678a40\,\code\:\import mongoose from mongoose;\\n\\nmongoose.connect(mongodb://localhost:27017/restaurant, {\\n useNewUrlParser: true,\\n useUnifiedTopology: true\\n});\\n\\nconst db mongoose.connection;\\ndb.on(error, console.error.bind(console, MongoDB 連接錯誤:));\\ndb.once(open, () \u003e {\\n console.log(已成功連接到 MongoDB);\\n});\\n\\nconst catSchema new mongoose.Schema({/* @cat-caption */\\n name: String,/* @cat-caption */\\n age: Number/* @cat-caption */\\n});/* @cat-caption */\\n\\nconst Cat mongoose.model(Cat, catSchema);/* @cat-caption */\,\language\:\javascript\,\caption\:\定義資料模型\\n接下來,我們需要定義要儲存的資料模型,也就是他的格式該長怎樣:\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:2,\createdAt\:\2025-10-19T01:46:56.072Z\,\updatedAt\:\2025-10-19T01:46:56.072Z\},{\id\:\x77916\,\codeBlockId\:\9585ff07-585f-46ce-bfdc-0501fd678a40\,\code\:\$47\,\language\:\javascript\,\caption\:\我們融合之前的程式碼,不要讓貓貓直接儲存在陣列裡頭\\n而是儲存在 mongodb 中\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:3,\createdAt\:\2025-10-19T01:46:56.072Z\,\updatedAt\:\2025-10-19T01:46:56.072Z\},{\id\:\283atr\,\codeBlockId\:\9585ff07-585f-46ce-bfdc-0501fd678a40\,\code\:\$48\,\language\:\javascript\,\caption\:\當然 mongodb 還支援修改 update\\n\\n我們可以新增一個修改貓咪名稱的功能,允許傳入原本貓咪的名字和新名字來更新資料庫中的資料。\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:4,\createdAt\:\2025-10-19T01:46:56.072Z\,\updatedAt\:\2025-10-19T01:46:56.072Z\},{\id\:\6ypaw4\,\codeBlockId\:\9585ff07-585f-46ce-bfdc-0501fd678a40\,\code\:\$49\,\language\:\javascript\,\caption\:\最後再增加一個刪除貓貓的功能\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:5,\createdAt\:\2025-10-19T01:46:56.072Z\,\updatedAt\:\2025-10-19T01:46:56.072Z\}},\questionBlock\:null},{\id\:\87a0ac3d-e8b6-49b2-8f23-2155250e386a\,\articleId\:\9f7de1e5-b4f1-40cb-9322-5cc6a141db25\,\noteBlock\:{\id\:\87a0ac3d-e8b6-49b2-8f23-2155250e386a\,\content\:\### SocketIO\\n\\n前面的 HTTP 伺服器其實有個小問題\\n\\n那就是伺服器只能被動的等待客戶端請求\\n\\n如果今天有什麼事情要告訴客戶端,那也只能等客戶端來問,伺服器才做回應\\n\\n所以就有了 WebSocket 這個通訊方法\\n\\n基本上就是可以做到雙向通訊\\n\\nSocketIO 便是一個對 WebSocket 的封裝套件,他使得程式碼的撰寫變得更簡單(就如同 express 之於 http 套件一樣)\},\codeBlock\:null,\questionBlock\:null},{\id\:\e635222f-282b-4e1f-899c-f9a20c2bf4a8\,\articleId\:\9f7de1e5-b4f1-40cb-9322-5cc6a141db25\,\noteBlock\:null,\codeBlock\:{\id\:\e635222f-282b-4e1f-899c-f9a20c2bf4a8\,\content\:\$4a\,\chunks\:{\id\:\8getr\,\codeBlockId\:\e635222f-282b-4e1f-899c-f9a20c2bf4a8\,\code\:\import express from express;\\nimport http from http;\\nimport { Server } from socket.io;\\n\\nconst app express();\\nconst server http.createServer(app);\\nconst io new Server(server);\\n\\napp.get(/, (req, res) \u003e {\\n res.sendFile(__dirname + /index.html);\\n});\\n\\nio.on(connection, (socket) \u003e {\\n console.log(一位使用者已連接);\\n\\n socket.on(chat message, (msg) \u003e {\\n console.log(訊息: + msg);\\n io.emit(chat message, msg);\\n });\\n\\n socket.on(disconnect, () \u003e {\\n console.log(一位使用者已離開);\\n });\\n});\\n\\nserver.listen(3000, () \u003e {\\n console.log(伺服器運行在 );\\n});\\n\,\language\:\javascript\,\caption\:\建立 Socket.IO 伺服器\\n接下來,我們將使用 Express 和 Socket.IO 建立一個簡單的即時通訊伺服器。\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:0,\createdAt\:\2025-10-19T01:47:00.068Z\,\updatedAt\:\2025-10-19T01:47:00.068Z\},{\id\:\owqlid\,\codeBlockId\:\e635222f-282b-4e1f-899c-f9a20c2bf4a8\,\code\:\$4b\,\language\:\html\,\caption\:\客戶端程式碼\\n在這個示例中,我們需要一個簡單的 HTML 文件來與伺服器進行互動。創建一個 index.html 文件,並添加以下內容\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:1,\createdAt\:\2025-10-19T01:47:00.068Z\,\updatedAt\:\2025-10-19T01:47:00.068Z\}},\questionBlock\:null}},{\id\:\fd22b20e-6358-4a1b-be9c-9ee108c99102\,\title\:\第三部分 邏輯操作者 JavaScript\,\content\:\\,\blockIndexIds\:\8ad1cffd-2626-461f-82a4-473a6b4825f1\,\76e2a6cb-0ef0-49d3-b3b0-ee65bac4254f\,\53ba84bb-951d-48fb-a50a-fc34558bb285\,\0665d382-cdb4-4861-93cf-4f775a06e5a7\,\6a68302f-f42d-4b39-bbe7-7e9cff44dd15\,\ccd46ed0-b141-49b2-8cf1-e3857401adbb\,\391a4217-4db1-41c7-888d-93a20e72eff5\,\d345ec94-07e7-46e9-82eb-3d3c34c3afd9\,\b42fbc32-8bdb-42c3-9762-31035fa24b97\,\288fbb0e-88be-4aef-b60a-3c749e355633\,\9c979f79-2d89-452c-82be-37bcff308a33\,\a81bf49c-603c-452b-b687-ba7bd79c4a95\,\768465e3-9169-4ca8-a8ef-a2aa8509e9e0\,\081b444d-287b-4225-a700-5108204b18d9\,\2aa6b436-df5c-4d53-8834-fad726ecfc64\,\d4da6339-352d-4f80-8bd9-a7c2d91436e0\,\25e741f5-fe81-4f0d-8119-7519c455bb0d\,\1d87294a-df51-4d4f-b58e-4f69976fd6b9\,\slug\:null,\metaDescription\:null,\ogImage\:null,\privacy\:\PUBLIC\,\allowedEmails\:,\createdAt\:\2024-06-23T22:44:21.124Z\,\updatedAt\:\2025-11-20T21:39:30.790Z\,\authorId\:\24c3f178-4a5b-447d-b586-16d7d459c606\,\viewCount\:104,\newsletterSentAt\:\2025-10-19T09:55:10.552Z\,\blocks\:{\id\:\8ad1cffd-2626-461f-82a4-473a6b4825f1\,\articleId\:\fd22b20e-6358-4a1b-be9c-9ee108c99102\,\noteBlock\:{\id\:\8ad1cffd-2626-461f-82a4-473a6b4825f1\,\content\:\### JavaScript 基本介紹\\n\\n#### 什麼是 JavaScript?\\n\\nJavaScript 是一種程式語言,主要用於網頁開發\\n它可以讓網頁動態更新內容,處理使用者輸入並執行複雜的邏輯操作。\\n在網頁中,JavaScript 與 HTML、CSS 一起工作,使網頁變得更加互動和動態。\\n\\n#### JavaScript 的特點包括:\\n\\n* **跨平台**:JavaScript 可以在多種操作系統和瀏覽器上執行。\\n* **動態類型**:變數在執行時可以改變類型。\\n* **事件驅動**:JavaScript 可以處理各種使用者事件,如點擊、移動滑鼠、鍵盤輸入等。\\n\\n#### JavaScript 與 Java 是壓根不同的兩個程式語言\\n\\n毫無相關\},\codeBlock\:null,\questionBlock\:null},{\id\:\6a68302f-f42d-4b39-bbe7-7e9cff44dd15\,\articleId\:\fd22b20e-6358-4a1b-be9c-9ee108c99102\,\noteBlock\:null,\codeBlock\:{\id\:\6a68302f-f42d-4b39-bbe7-7e9cff44dd15\,\content\:\$4c\,\chunks\:{\id\:\xkzjg\,\codeBlockId\:\6a68302f-f42d-4b39-bbe7-7e9cff44dd15\,\code\:\let myBox;\,\language\:\javascript\,\caption\:\宣告一個盒子:\\n就像我們先準備一個盒子一樣\\n\\n在程式裡,我們要先透過 let 關鍵字\\n告訴電腦我們要一個盒子。這就叫做“宣告變數”。\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:0,\createdAt\:\2025-10-19T01:46:56.806Z\,\updatedAt\:\2025-10-19T01:46:56.806Z\},{\id\:\z852lb\,\codeBlockId\:\6a68302f-f42d-4b39-bbe7-7e9cff44dd15\,\code\:\let myBox;\,\language\:\javascript\,\caption\:\給盒子取名字:\\n我們給這個盒子取名叫 myBox。這樣我們以後就可以用 myBox 這個名字來找這個盒子。\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:1,\createdAt\:\2025-10-19T01:46:56.806Z\,\updatedAt\:\2025-10-19T01:46:56.806Z\},{\id\:\q7hm5\,\codeBlockId\:\6a68302f-f42d-4b39-bbe7-7e9cff44dd15\,\code\:\let myBox;\\r\\nmyBox 10; // 把數字 10 放進盒子/* @cat-caption */\\r\\nmyBox \\\Hello\\\; // 把字串 \\\Hello\\\ 放進盒子,原本盒子內的 10 會直接消失掉喔!/* @cat-caption */\,\language\:\javascript\,\caption\:\往盒子裡放東西:\\n我們可以把一些東西放進這個盒子裡,比如一個數字或一個字串。\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:2,\createdAt\:\2025-10-19T01:46:56.806Z\,\updatedAt\:\2025-10-19T01:46:56.806Z\},{\id\:\zamhq\,\codeBlockId\:\6a68302f-f42d-4b39-bbe7-7e9cff44dd15\,\code\:\let myBox;\\r\\nmyBox 10; // 把數字 10 放進盒子\\r\\nmyBox \\\Hello\\\; // 把字串 \\\Hello\\\ 放進盒子,原本盒子內的 10 會直接消失掉喔!\\r\\nconsole.log(myBox); // 顯示盒子裡的東西,這裡會顯示 \\\Hello\\\/* @cat-caption */\\r\\n\,\language\:\javascript\,\caption\:\使用盒子裡的東西:\\n當我們需要用到這些東西時,只需要拿出這個盒子來用就行了。\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:3,\createdAt\:\2025-10-19T01:46:56.806Z\,\updatedAt\:\2025-10-19T01:46:56.806Z\},{\id\:\p6znj9\,\codeBlockId\:\6a68302f-f42d-4b39-bbe7-7e9cff44dd15\,\code\:\let name \\\Ray\\\; // 這是裝名字的盒子\\r\\nlet age 20; // 這是裝年齡的盒子\\r\\n\\r\\nconsole.log(name); // 顯示名字,\\\Ray\\\\\r\\nconsole.log(age); // 顯示年齡,20\\r\\n\,\language\:\javascript\,\caption\:\再舉一個例子\\n假設我們要記住一個人的名字和年齡,我們可以用兩個變數來做到這件事:\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:4,\createdAt\:\2025-10-19T01:46:56.806Z\,\updatedAt\:\2025-10-19T01:46:56.806Z\}},\questionBlock\:null},{\id\:\0665d382-cdb4-4861-93cf-4f775a06e5a7\,\articleId\:\fd22b20e-6358-4a1b-be9c-9ee108c99102\,\noteBlock\:{\id\:\0665d382-cdb4-4861-93cf-4f775a06e5a7\,\content\:\### 什麼是變數?\\n\\n變數就像一個標籤盒子,裡面可以放東西。\\n\\n我們可以把各種不同的東西放進這些盒子裡,然後給每個盒子取個名字。\\n\\n這樣我們就可以很方便地找到和使用這些東西。\},\codeBlock\:null,\questionBlock\:null},{\id\:\53ba84bb-951d-48fb-a50a-fc34558bb285\,\articleId\:\fd22b20e-6358-4a1b-be9c-9ee108c99102\,\noteBlock\:null,\codeBlock\:{\id\:\53ba84bb-951d-48fb-a50a-fc34558bb285\,\content\:\{\\\id\\\:\\\ea6p97\\\,\\\code\\\:\\\console.log(\\\\\\\Hello, World!\\\\\\\); \\\\r\\\\n\\\,\\\caption\\\:\\\console.log(數值) \\\\n可以列印、顯示數值\\\,\\\language\\\:\\\javascript\\\}\,\chunks\:{\id\:\ea6p97\,\codeBlockId\:\53ba84bb-951d-48fb-a50a-fc34558bb285\,\code\:\console.log(\\\Hello, World!\\\); \\r\\n\,\language\:\javascript\,\caption\:\console.log(數值) \\n可以列印、顯示數值\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:0,\createdAt\:\2025-10-19T01:46:54.355Z\,\updatedAt\:\2025-10-19T01:46:54.355Z\}},\questionBlock\:null},{\id\:\76e2a6cb-0ef0-49d3-b3b0-ee65bac4254f\,\articleId\:\fd22b20e-6358-4a1b-be9c-9ee108c99102\,\noteBlock\:null,\codeBlock\:{\id\:\76e2a6cb-0ef0-49d3-b3b0-ee65bac4254f\,\content\:\{\\\id\\\:\\\rjzhy\\\,\\\code\\\:\\\\\\,\\\caption\\\:\\\開啟簡單的 JavaScript 執行環境\\\,\\\language\\\:\\\typescript\\\}\,\chunks\:{\id\:\rjzhy\,\codeBlockId\:\76e2a6cb-0ef0-49d3-b3b0-ee65bac4254f\,\code\:\\,\language\:\typescript\,\caption\:\開啟簡單的 JavaScript 執行環境\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:0,\createdAt\:\2025-10-19T01:46:55.298Z\,\updatedAt\:\2025-10-19T01:46:55.298Z\}},\questionBlock\:null},{\id\:\ccd46ed0-b141-49b2-8cf1-e3857401adbb\,\articleId\:\fd22b20e-6358-4a1b-be9c-9ee108c99102\,\noteBlock\:{\id\:\ccd46ed0-b141-49b2-8cf1-e3857401adbb\,\content\:\#### 什麼是資料型別?\\n\\n資料型別就是告訴電腦我們放在變數(盒子)裡的東西是什麼種類。不同種類的資料可以做不同的事情。\},\codeBlock\:null,\questionBlock\:null},{\id\:\391a4217-4db1-41c7-888d-93a20e72eff5\,\articleId\:\fd22b20e-6358-4a1b-be9c-9ee108c99102\,\noteBlock\:null,\codeBlock\:{\id\:\391a4217-4db1-41c7-888d-93a20e72eff5\,\content\:\$4d\,\chunks\:{\id\:\t3uuve\,\codeBlockId\:\391a4217-4db1-41c7-888d-93a20e72eff5\,\code\:\let myString \\\Hello, world!\\\;\\r\\nconsole.log(myString); // 顯示 \\\Hello, world!\\\\\r\\n\,\language\:\javascript\,\caption\:\字串(String)\\n字串是一串文字,可以是字母、數字、符號等,通常用引號括起來。就像一本書中的文字內容。\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:0,\createdAt\:\2025-10-19T01:46:59.478Z\,\updatedAt\:\2025-10-19T01:46:59.478Z\},{\id\:\uuu281\,\codeBlockId\:\391a4217-4db1-41c7-888d-93a20e72eff5\,\code\:\let myNumber 42;\\r\\nconsole.log(myNumber); // 顯示 42\\r\\n\\r\\nlet myDecimal 3.14;\\r\\nconsole.log(myDecimal); // 顯示 3.14\\r\\n\,\language\:\javascript\,\caption\:\數字(Number)\\n數字可以是整數或小數。數字可以用來做數學運算,就像我們在算數學題目一樣。\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:1,\createdAt\:\2025-10-19T01:46:59.478Z\,\updatedAt\:\2025-10-19T01:46:59.478Z\},{\id\:\iyf93\,\codeBlockId\:\391a4217-4db1-41c7-888d-93a20e72eff5\,\code\:\let isStudent true;\\r\\nconsole.log(isStudent); // 顯示 true\\r\\n\\r\\nlet hasGraduated false;\\r\\nconsole.log(hasGraduated); // 顯示 false\\r\\n\,\language\:\javascript\,\caption\:\布林值(Boolean)\\n布林值只有兩種:true(真)或 false(假)。這種資料型別通常用來做判斷,就像我們在問“是”或“不是”的問題。\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:2,\createdAt\:\2025-10-19T01:46:59.478Z\,\updatedAt\:\2025-10-19T01:46:59.478Z\},{\id\:\u5b4k\,\codeBlockId\:\391a4217-4db1-41c7-888d-93a20e72eff5\,\code\:\let colors \\\red\\\, \\\green\\\, \\\blue\\\; // 裏頭一次放入三個字串 \,\language\:\javascript\,\caption\:\陣列(Array)\\n陣列就像排隊的隊伍,可以放入很多資料。\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:3,\createdAt\:\2025-10-19T01:46:59.478Z\,\updatedAt\:\2025-10-19T01:46:59.478Z\},{\id\:\0tfse\,\codeBlockId\:\391a4217-4db1-41c7-888d-93a20e72eff5\,\code\:\let colors \\\red\\\, \\\green\\\, \\\blue\\\;\\r\\nconsole.log(colors0); // 取得第 0 個元素的內容,顯示 \\\red\\\\\r\\n\\r\\ncolors0 \\\orange\\\ // 修改第 0 個元素的內容\\r\\nconsole.log(colors0); // 重新取得第 0 個元素的內容,顯示 \\\orange\\\\,\language\:\javascript\,\caption\:\陣列(Array)\\n我們當然可以透過 變數名稱位置 來讀取或修改內容\\n值得一提的是, JavaScript 或者說大部分的程式語言\\n陣列都是從 0 開始計算\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:4,\createdAt\:\2025-10-19T01:46:59.478Z\,\updatedAt\:\2025-10-19T01:46:59.478Z\},{\id\:\cmu30s\,\codeBlockId\:\391a4217-4db1-41c7-888d-93a20e72eff5\,\code\:\let person {\\r\\n name: \\\Ray\\\,\\r\\n age: 20,\\r\\n isStudent: true\\r\\n};\,\language\:\javascript\,\caption\:\物件(Object)\\n物件是一種可以存放多個相關資料的結構。\\n可以想像成一個包含很多小盒子的盒子。\\n當然物件中的每個小盒子都需要有獨立的名字\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:5,\createdAt\:\2025-10-19T01:46:59.478Z\,\updatedAt\:\2025-10-19T01:46:59.478Z\},{\id\:\nmnrtm\,\codeBlockId\:\391a4217-4db1-41c7-888d-93a20e72eff5\,\code\:\let person {\\r\\n name: \\\Ray\\\,\\r\\n age: 20,\\r\\n isStudent: true\\r\\n};\\r\\n\\r\\nconsole.log(person.name); // 顯示 \\\Ray\\\ /* @cat-caption */\\r\\nconsole.log(person.age); // 顯示 20 /* @cat-caption */\\r\\nconsole.log(person.isStudent); // 顯示 true /* @cat-caption */\\r\\n\\r\\nperson.age 21; // 更新 age 為 21 /* @cat-caption */\\r\\nconsole.log(person.age); // 顯示 21 /* @cat-caption */\,\language\:\javascript\,\caption\:\透過 變數名稱.小盒子的名字 能單獨讀取小盒子的內容\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:6,\createdAt\:\2025-10-19T01:46:59.478Z\,\updatedAt\:\2025-10-19T01:46:59.478Z\},{\id\:\zhe45\,\codeBlockId\:\391a4217-4db1-41c7-888d-93a20e72eff5\,\code\:\let person {\\r\\n name: \\\Ray\\\,\\r\\n age: 20,\\r\\n isStudent: true\\r\\n};\\r\\n\\r\\nconsole.log(person.name); // 顯示 \\\Ray\\\\\r\\nconsole.log(person.age); // 顯示 20\\r\\nconsole.log(person.isStudent); // 顯示 true\\r\\n\\r\\nperson.age 21; // 更新 age 為 21\\r\\nconsole.log(person.age); // 顯示 21\\r\\n\\r\\npersonlikeCat true; // 新增 likeCat 為 true /* @cat-caption */\\r\\nconsole.log(person.likeCat); // 顯示 true /* @cat-caption */\\r\\n\\r\\n/*\\r\\n此時 person 內長這樣,likeCat 被放進去\\r\\n{\\r\\n name: \\\Ray\\\,\\r\\n age: 20,\\r\\n isStudent: true,\\r\\n likeCat: true\\r\\n};\\r\\n */\,\language\:\javascript\,\caption\:\如果我們要塞入新的小盒子\\n可以透過變數名稱新的小盒子名稱來創造新的小盒子\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:7,\createdAt\:\2025-10-19T01:46:59.478Z\,\updatedAt\:\2025-10-19T01:46:59.478Z\}},\questionBlock\:null},{\id\:\d345ec94-07e7-46e9-82eb-3d3c34c3afd9\,\articleId\:\fd22b20e-6358-4a1b-be9c-9ee108c99102\,\noteBlock\:{\id\:\d345ec94-07e7-46e9-82eb-3d3c34c3afd9\,\content\:\#### 基本運算符\\n\\n基本運算符就像我們在數學課上學到的符號。\\n\\n可以用來對數字進行加減乘除等運算。\\n\\n這些符號告訴電腦應該怎麼處理我們的數字。\},\codeBlock\:null,\questionBlock\:null},{\id\:\b42fbc32-8bdb-42c3-9762-31035fa24b97\,\articleId\:\fd22b20e-6358-4a1b-be9c-9ee108c99102\,\noteBlock\:null,\codeBlock\:{\id\:\b42fbc32-8bdb-42c3-9762-31035fa24b97\,\content\:\{\\\id\\\:\\\fi65rb\\\,\\\code\\\:\\\let apples 5 + 3;\\\\r\\\\nconsole.log(apples); // 顯示 8\\\\r\\\\n\\\,\\\caption\\\:\\\加法運算符(+)\\\\n加法運算符用來把兩個數字相加。\\\,\\\language\\\:\\\typescript\\\},{\\\id\\\:\\\4doft\\\,\\\code\\\:\\\let candies 10 - 4;\\\\r\\\\nconsole.log(candies); // 顯示 6\\\\r\\\\n\\\,\\\caption\\\:\\\減法運算符(-)\\\\n減法運算符用來從一個數字中減去另一個數字。\\\,\\\language\\\:\\\typescript\\\},{\\\id\\\:\\\rdt5bs\\\,\\\code\\\:\\\let balls 4 * 2;\\\\r\\\\nconsole.log(balls); // 顯示 8\\\,\\\caption\\\:\\\乘法運算符(*)\\\\n乘法運算符用來把兩個數字相乘。\\\,\\\language\\\:\\\typescript\\\},{\\\id\\\:\\\4z7wif\\\,\\\code\\\:\\\let chocolates 12 / 3;\\\\r\\\\nconsole.log(chocolates); // 顯示 4\\\\r\\\\n\\\,\\\caption\\\:\\\除法運算符(/)\\\\n乘法運算符用來把兩個數字相除。\\\,\\\language\\\:\\\typescript\\\},{\\\id\\\:\\\9wvkss\\\,\\\code\\\:\\\let remainder 10 % 3;\\\\r\\\\nconsole.log(remainder); // 顯示 1\\\\r\\\\n\\\,\\\caption\\\:\\\取餘運算符(%)\\\\n取餘運算符用來計算兩個數字相除後剩下的餘數。\\\,\\\language\\\:\\\typescript\\\},{\\\id\\\:\\\oc3rgv\\\,\\\code\\\:\\\let x 25;\\\\r\\\\nlet y 4;\\\\r\\\\nlet answer x * y;\\\\r\\\\nconsole.log(answer); // 100\\\,\\\caption\\\:\\\另外也可以進行變數和變數的加減乘除\\\,\\\language\\\:\\\typescript\\\}\,\chunks\:{\id\:\fi65rb\,\codeBlockId\:\b42fbc32-8bdb-42c3-9762-31035fa24b97\,\code\:\let apples 5 + 3;\\r\\nconsole.log(apples); // 顯示 8\\r\\n\,\language\:\typescript\,\caption\:\加法運算符(+)\\n加法運算符用來把兩個數字相加。\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:0,\createdAt\:\2025-10-19T01:47:00.212Z\,\updatedAt\:\2025-10-19T01:47:00.212Z\},{\id\:\4doft\,\codeBlockId\:\b42fbc32-8bdb-42c3-9762-31035fa24b97\,\code\:\let candies 10 - 4;\\r\\nconsole.log(candies); // 顯示 6\\r\\n\,\language\:\typescript\,\caption\:\減法運算符(-)\\n減法運算符用來從一個數字中減去另一個數字。\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:1,\createdAt\:\2025-10-19T01:47:00.212Z\,\updatedAt\:\2025-10-19T01:47:00.212Z\},{\id\:\rdt5bs\,\codeBlockId\:\b42fbc32-8bdb-42c3-9762-31035fa24b97\,\code\:\let balls 4 * 2;\\r\\nconsole.log(balls); // 顯示 8\,\language\:\typescript\,\caption\:\乘法運算符(*)\\n乘法運算符用來把兩個數字相乘。\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:2,\createdAt\:\2025-10-19T01:47:00.212Z\,\updatedAt\:\2025-10-19T01:47:00.212Z\},{\id\:\4z7wif\,\codeBlockId\:\b42fbc32-8bdb-42c3-9762-31035fa24b97\,\code\:\let chocolates 12 / 3;\\r\\nconsole.log(chocolates); // 顯示 4\\r\\n\,\language\:\typescript\,\caption\:\除法運算符(/)\\n乘法運算符用來把兩個數字相除。\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:3,\createdAt\:\2025-10-19T01:47:00.212Z\,\updatedAt\:\2025-10-19T01:47:00.212Z\},{\id\:\9wvkss\,\codeBlockId\:\b42fbc32-8bdb-42c3-9762-31035fa24b97\,\code\:\let remainder 10 % 3;\\r\\nconsole.log(remainder); // 顯示 1\\r\\n\,\language\:\typescript\,\caption\:\取餘運算符(%)\\n取餘運算符用來計算兩個數字相除後剩下的餘數。\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:4,\createdAt\:\2025-10-19T01:47:00.212Z\,\updatedAt\:\2025-10-19T01:47:00.212Z\},{\id\:\oc3rgv\,\codeBlockId\:\b42fbc32-8bdb-42c3-9762-31035fa24b97\,\code\:\let x 25;\\r\\nlet y 4;\\r\\nlet answer x * y;\\r\\nconsole.log(answer); // 100\,\language\:\typescript\,\caption\:\另外也可以進行變數和變數的加減乘除\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:5,\createdAt\:\2025-10-19T01:47:00.212Z\,\updatedAt\:\2025-10-19T01:47:00.212Z\}},\questionBlock\:null},{\id\:\288fbb0e-88be-4aef-b60a-3c749e355633\,\articleId\:\fd22b20e-6358-4a1b-be9c-9ee108c99102\,\noteBlock\:{\id\:\288fbb0e-88be-4aef-b60a-3c749e355633\,\content\:\#### 條件語句\\n\\n條件語句就像在做選擇題。\\n\\n根據不同的條件來決定要做什麼事。\\n\\n這些條件語句幫助我們在程式裡做出決定。\},\codeBlock\:null,\questionBlock\:null},{\id\:\9c979f79-2d89-452c-82be-37bcff308a33\,\articleId\:\fd22b20e-6358-4a1b-be9c-9ee108c99102\,\noteBlock\:null,\codeBlock\:{\id\:\9c979f79-2d89-452c-82be-37bcff308a33\,\content\:\$4e\,\chunks\:{\id\:\hsbrtb\,\codeBlockId\:\9c979f79-2d89-452c-82be-37bcff308a33\,\code\:\let isRaining true;\\r\\n\\r\\nif (isRaining) {\\r\\n console.log(\\\記得帶傘\\\); // 顯示 \\\記得帶傘\\\\\r\\n}\\r\\n\\r\\n// 這裡我們問:“如果正在下雨?” 如果答案是“是”,那我們就提醒自己要帶雨傘。\,\language\:\typescript\,\caption\:\if 語句\\nif 語句就像在問問題:\\n“如果這個條件是對的,那麼我們就做這件事。” \\n\\n比如說如果今天是下雨天,我們就要帶雨傘。\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:0,\createdAt\:\2025-10-19T01:46:52.642Z\,\updatedAt\:\2025-10-19T01:46:52.642Z\},{\id\:\qqy6go\,\codeBlockId\:\9c979f79-2d89-452c-82be-37bcff308a33\,\code\:\let grade 85;\\r\\n\\r\\nif (grade \u003e 60) {\\r\\n console.log(\\\你及格了\\\); // 顯示 \\\你及格了\\\\\r\\n} else {\\r\\n console.log(\\\你沒及格 QQ\\\); // 顯示 \\\你沒及格 QQ\\\\\r\\n}\\r\\n\\r\\n\\r\\n// 最後會輸出 \\\你及格了\\\\,\language\:\typescript\,\caption\:\if...else 語句\\nif...else 語句就像在問兩個問題:\\n如果這個條件是對的,我們就做這件事;\\n如果不是,我們就做另外一件事。\\n\\n 比如在學校,如果考試及格,我們就可以玩;\\n如果不及格,我們就要多學習。\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:1,\createdAt\:\2025-10-19T01:46:52.642Z\,\updatedAt\:\2025-10-19T01:46:52.642Z\},{\id\:\i84kcr\,\codeBlockId\:\9c979f79-2d89-452c-82be-37bcff308a33\,\code\:\let temperature 25;\\r\\n\\r\\nif (temperature \u003e 30) { // 如果溫度大於 30 度\\r\\n console.log(\\\超級熱\\\); // 顯示 超級熱\\r\\n} else if (temperature \u003e 26) { // 如果上面的條件沒達成,就會繼續往下問,如果溫度大於 26 度\\r\\n console.log(\\\好熱\\\); // 顯示 好熱\\r\\n} else if (temperature \u003e 20) {\\r\\n console.log(\\\溫度剛剛好\\\); // 顯示 溫度剛剛好\\r\\n} else {\\r\\n console.log(\\\好冷\\\); // 顯示 好冷\\r\\n}\\r\\n\\r\\n// 最後顯示 溫度剛剛好\,\language\:\typescript\,\caption\:\if...else if...else 語句\\nif...else if...else 語句可以處理更多的條件,就像在問一連串的問題。\\n\\n比如,我們在家裡根據溫度來決定穿什麼衣服。\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:2,\createdAt\:\2025-10-19T01:46:52.642Z\,\updatedAt\:\2025-10-19T01:46:52.642Z\}},\questionBlock\:null},{\id\:\a81bf49c-603c-452b-b687-ba7bd79c4a95\,\articleId\:\fd22b20e-6358-4a1b-be9c-9ee108c99102\,\noteBlock\:{\id\:\a81bf49c-603c-452b-b687-ba7bd79c4a95\,\content\:\#### 邏輯運算符(\u0026\u0026 和 ||)\\n\\n如果你今天想在同個 if 括號內同時判斷多個情況\\n\\n你可以用邏輯運算符\},\codeBlock\:null,\questionBlock\:null},{\id\:\768465e3-9169-4ca8-a8ef-a2aa8509e9e0\,\articleId\:\fd22b20e-6358-4a1b-be9c-9ee108c99102\,\noteBlock\:null,\codeBlock\:{\id\:\768465e3-9169-4ca8-a8ef-a2aa8509e9e0\,\content\:\$4f\,\chunks\:{\id\:\0y8gof\,\codeBlockId\:\768465e3-9169-4ca8-a8ef-a2aa8509e9e0\,\code\:\let age 20;\\r\\nlet isStudent true;\\r\\n\\r\\nif (age \u003e 18 \u0026\u0026 isStudent) {\\r\\n console.log(\\\你是一個年滿 18 歲的學生\\\); // 顯示 \\\你是一個年滿 18 歲的學生\\\\\r\\n}\\r\\n\\r\\n\\r\\n\\r\\nif (age \u003e 25 \u0026\u0026 isStudent) {\\r\\n console.log(\\\你是一個年滿 25 歲的學生\\\); // 有一個條件不滿足,不顯示\\r\\n}\\r\\n\\r\\n\,\language\:\typescript\,\caption\:\and(\u0026\u0026):這個運算符用來判斷兩個條件是否都達成。如果兩個條件都達成,整個表達式才能算是達成。\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:0,\createdAt\:\2025-10-19T01:46:55.150Z\,\updatedAt\:\2025-10-19T01:46:55.150Z\},{\id\:\el6wtj\,\codeBlockId\:\768465e3-9169-4ca8-a8ef-a2aa8509e9e0\,\code\:\let age 16;\\r\\nlet hasPermission true;\\r\\n\\r\\nif (age \u003e 18 || hasPermission) {\\r\\n console.log(\\\你可以參加活動\\\); // 顯示 \\\你可以參加活動\\\\\r\\n}\,\language\:\typescript\,\caption\:\or(||):這個運算符用來判斷兩個條件中是否有一個達成。如果有一個條件達成,整個表達式就為真。\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:1,\createdAt\:\2025-10-19T01:46:55.150Z\,\updatedAt\:\2025-10-19T01:46:55.150Z\},{\id\:\yfuc0p\,\codeBlockId\:\768465e3-9169-4ca8-a8ef-a2aa8509e9e0\,\code\:\let age 6;\\r\\n\\r\\nswitch (true) {\\r\\n case (age \u003e 0 \u0026\u0026 age \u003c 5):\\r\\n console.log(\\\你可以免費入場!\\\); // 顯示 \\\你可以免費入場!\\\\\r\\n break;\\r\\n case (age \u003e 6 \u0026\u0026 age \u003c 12):\\r\\n console.log(\\\你可以買兒童票。\\\); // 顯示 \\\你可以買兒童票。\\\\\r\\n break;\\r\\n case (age \u003e 13 \u0026\u0026 age \u003c 18):\\r\\n console.log(\\\你可以買學生票。\\\); // 顯示 \\\你可以買學生票。\\\\\r\\n break;\\r\\n default:\\r\\n console.log(\\\你可以買普通票。\\\); // 顯示 \\\你可以買普通票。\\\\\r\\n}\\r\\n\\r\\n// 你可以買兒童票。!\,\language\:\typescript\,\caption\:\如果今天有一堆的問題跟情況\\n除了 if...else if...else 語句\\n我們有一個更簡單的語法\\n\\nswitch 語句\\nswitch 語句就像是多選一的題目,根據不同的情況選擇要做什麼。比如在遊樂園,不同的票價對應不同的年齡段。\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:2,\createdAt\:\2025-10-19T01:46:55.150Z\,\updatedAt\:\2025-10-19T01:46:55.150Z\}},\questionBlock\:null},{\id\:\081b444d-287b-4225-a700-5108204b18d9\,\articleId\:\fd22b20e-6358-4a1b-be9c-9ee108c99102\,\noteBlock\:{\id\:\081b444d-287b-4225-a700-5108204b18d9\,\content\:\#### 迴圈\\n\\n迴圈就像是在做重複的事情,\\n\\n我們可以用迴圈來讓電腦一次又一次地執行相同的代碼,\\n\\n直到我們告訴它停止。\},\codeBlock\:null,\questionBlock\:null},{\id\:\2aa6b436-df5c-4d53-8834-fad726ecfc64\,\articleId\:\fd22b20e-6358-4a1b-be9c-9ee108c99102\,\noteBlock\:null,\codeBlock\:{\id\:\2aa6b436-df5c-4d53-8834-fad726ecfc64\,\content\:\$50\,\chunks\:{\id\:\zs09sn\,\codeBlockId\:\2aa6b436-df5c-4d53-8834-fad726ecfc64\,\code\:\for (let i 0; i \u003c 5; i++) {\\r\\n console.log(\\\這是第 \\\ + i + \\\ 次\\\);\\r\\n}\\r\\n\,\language\:\typescript\,\caption\:\for 迴圈\\nfor 迴圈就像是在數數,我們可以告訴電腦從哪個數字開始,數到哪個數字結束,每次數多少。這樣我們就可以讓電腦做很多次相同的事情。\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:0,\createdAt\:\2025-10-19T01:46:54.920Z\,\updatedAt\:\2025-10-19T01:46:54.920Z\},{\id\:\7wws7m\,\codeBlockId\:\2aa6b436-df5c-4d53-8834-fad726ecfc64\,\code\:\for (let i 0; i \u003c 5; i++) { /* @cat-caption */\\r\\n console.log(\\\這是第 \\\ + i + \\\ 次\\\);\\r\\n}\\r\\n\\r\\n// 這是第 0 次\\r\\n// 這是第 1 次\\r\\n// 這是第 2 次\\r\\n// 這是第 3 次\\r\\n// 這是第 4 次\,\language\:\typescript\,\caption\:\這裡我們告訴電腦:\\n\\n從 0 開始(let i 0)。\\n停止條件,如果 i 比 5 小那就繼續執行下去(i \u003c 5)。\\n每次執行完後做的事,次數 1(i++,每次把 i 加 1)。\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:1,\createdAt\:\2025-10-19T01:46:54.920Z\,\updatedAt\:\2025-10-19T01:46:54.920Z\},{\id\:\fvzncs\,\codeBlockId\:\2aa6b436-df5c-4d53-8834-fad726ecfc64\,\code\:\let i 0;\\r\\n\\r\\nwhile (i \u003c 5) {\\r\\n console.log(\\\這是第 \\\ + i + \\\ 次\\\);\\r\\n i++;\\r\\n}\\r\\n\\r\\n// 這是第 0 次\\r\\n// 這是第 1 次\\r\\n// 這是第 2 次\\r\\n// 這是第 3 次\\r\\n// 這是第 4 次\,\language\:\typescript\,\caption\:\while 迴圈\\nwhile 迴圈就像是在問問題,只要答案是“是”,電腦就會一直重複做這件事。當答案變成“不是”時,電腦就會停止。\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:2,\createdAt\:\2025-10-19T01:46:54.920Z\,\updatedAt\:\2025-10-19T01:46:54.920Z\},{\id\:\bzjprd\,\codeBlockId\:\2aa6b436-df5c-4d53-8834-fad726ecfc64\,\code\:\let i 0;\\r\\n\\r\\nwhile (i \u003c 5) {\\r\\n console.log(\\\這是第 \\\ + i + \\\ 次\\\);\\r\\n i++;\\r\\n}\\r\\n\\r\\n// 這是第 0 次\\r\\n// 這是第 1 次\\r\\n// 這是第 2 次\\r\\n// 這是第 3 次\\r\\n// 這是第 4 次\,\language\:\typescript\,\caption\:\這裡我們告訴電腦:\\n\\n開始的時候 i 是 0(let i 0)\\n停止條件,如果 i 比 5 小那就繼續執行下去(i \u003c 5)。\\n每次執行完後做的事,次數 1(i++,每次把 i 加 1)。\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:3,\createdAt\:\2025-10-19T01:46:54.920Z\,\updatedAt\:\2025-10-19T01:46:54.920Z\},{\id\:\zayf7\,\codeBlockId\:\2aa6b436-df5c-4d53-8834-fad726ecfc64\,\code\:\let i 0;\\r\\n\\r\\ndo {\\r\\n console.log(\\\這是第 \\\ + i + \\\ 次\\\);\\r\\n i++;\\r\\n} while (i \u003c 5);\\r\\n\\r\\n// 這是第 0 次\\r\\n// 這是第 1 次\\r\\n// 這是第 2 次\\r\\n// 這是第 3 次\\r\\n// 這是第 4 次\,\language\:\typescript\,\caption\:\do...while 迴圈\\ndo...while 迴圈有點像 while 迴圈,但是它會先做一次,然後再問問題。如果答案是“是”,就繼續做;\\n如果答案是“不是”,就停止。\\n可以理解成先斬後奏\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:4,\createdAt\:\2025-10-19T01:46:54.920Z\,\updatedAt\:\2025-10-19T01:46:54.920Z\}},\questionBlock\:null},{\id\:\d4da6339-352d-4f80-8bd9-a7c2d91436e0\,\articleId\:\fd22b20e-6358-4a1b-be9c-9ee108c99102\,\noteBlock\:{\id\:\d4da6339-352d-4f80-8bd9-a7c2d91436e0\,\content\:\### 函數的定義與調用\\n\\n函數就像是一個功能盒子,\\n\\n我們可以把一系列的指令放進這個盒子裡,\\n\\n給它取一個名字,\\n\\n然後以後只要叫這個名字,就可以讓這些指令執行。\},\codeBlock\:null,\questionBlock\:null},{\id\:\25e741f5-fe81-4f0d-8119-7519c455bb0d\,\articleId\:\fd22b20e-6358-4a1b-be9c-9ee108c99102\,\noteBlock\:null,\codeBlock\:{\id\:\25e741f5-fe81-4f0d-8119-7519c455bb0d\,\content\:\$51\,\chunks\:{\id\:\1c4of\,\codeBlockId\:\25e741f5-fe81-4f0d-8119-7519c455bb0d\,\code\:\function sayHello() {\\r\\n console.log(\\\Hello World\\\);\\r\\n}\\r\\n\,\language\:\javascript\,\caption\:\定義函數\\n我們先來看看怎麼定義一個函數。定義函數就是告訴電腦這個功能盒子裡面有什麼指令。\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:0,\createdAt\:\2025-10-19T01:46:59.776Z\,\updatedAt\:\2025-10-19T01:46:59.776Z\},{\id\:\xq7szt\,\codeBlockId\:\25e741f5-fe81-4f0d-8119-7519c455bb0d\,\code\:\function sayHello() {\\r\\n console.log(\\\Hello World\\\);\\r\\n}\\r\\n\,\language\:\javascript\,\caption\:\\\n函數的定義與調用\\n函數就像是一個魔法盒子,我們可以把一系列的指令放進這個盒子裡,給它取一個名字,然後以後只要叫這個名字,就可以讓這些指令執行。\\n\\n定義函數\\n我們先來看看怎麼定義一個函數。定義函數就是告訴電腦這個魔法盒子裡面有什麼指令。\\n\\n例子:\\n\\njavascript\\n複製程式碼\\nfunction sayHello() {\\n console.log(\\\你好,世界!\\\);\\n}\\n這裡我們做了什麼:\\n\\n用 function 關鍵字開始:\\n這告訴電腦我們要定義一個函數。\\n給函數取名字:這裡我們叫它 sayHello。\\n\\n在大括號 {} 內寫指令:\\n這些指令就是我們要放進魔法盒子裡的東西。\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:1,\createdAt\:\2025-10-19T01:46:59.776Z\,\updatedAt\:\2025-10-19T01:46:59.776Z\},{\id\:\b6hyqs\,\codeBlockId\:\25e741f5-fe81-4f0d-8119-7519c455bb0d\,\code\:\function sayHello() {\\r\\n console.log(\\\Hello World\\\);\\r\\n}\\r\\n\\r\\nsayHello(); \\r\\n\,\language\:\javascript\,\caption\:\調用函數\\n現在我們已經有了這個功能盒子(函數),我們可以用它來執行裡面的指令。這叫做“調用函數”。\\n\\n只要在之後呼叫函數名稱() 就會視同調用函數\\n請注意要加上括號 () 才會執行裡面的內容\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:2,\createdAt\:\2025-10-19T01:46:59.776Z\,\updatedAt\:\2025-10-19T01:46:59.776Z\},{\id\:\ztpk6l\,\codeBlockId\:\25e741f5-fe81-4f0d-8119-7519c455bb0d\,\code\:\function greet(name) { /* @cat-caption */\\r\\n console.log(\\\你好,\\\ + name + \\\!\\\);\\r\\n}\\r\\n\,\language\:\javascript\,\caption\:\帶參數的函數\\n我們也可以讓函數更靈活,給它一些“參數”,這樣我們可以傳不同的東西給它,它會根據我們給的東西來做不同的事情。\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:3,\createdAt\:\2025-10-19T01:46:59.776Z\,\updatedAt\:\2025-10-19T01:46:59.776Z\},{\id\:\dqdqvue\,\codeBlockId\:\25e741f5-fe81-4f0d-8119-7519c455bb0d\,\code\:\function greet(name) { /* @cat-caption */\\r\\n console.log(\\\你好,\\\ + name + \\\!\\\);\\r\\n}\\r\\n\\r\\n\\r\\ngreet(\\\小明\\\); // 你好,小明!\\r\\ngreet(\\\小紅\\\); // 你好,小紅!\\r\\n\,\language\:\javascript\,\caption\:\這裡我們做了什麼:\\n\\n1. 給函數一個參數 name:\\n這個 name 就像是一個盒子,我們可以把名字放進去。\\n\\n2. 在指令裡使用這個參數:\\n我們把 name 和 “你好,” 拼在一起,這樣每次傳不同的名字,函數就會顯示不同的問候語。\\n\\n調用這個函數時,我們可以傳一個名字給它。\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:4,\createdAt\:\2025-10-19T01:46:59.776Z\,\updatedAt\:\2025-10-19T01:46:59.776Z\},{\id\:\yogvj\,\codeBlockId\:\25e741f5-fe81-4f0d-8119-7519c455bb0d\,\code\:\function add(a, b) {\\r\\n return a + b;\\r\\n}\\r\\n\,\language\:\javascript\,\caption\:\帶返回值的函數\\n函數還可以把一些結果返回給我們,就像把魔法盒子打開,看看裡面變出了什麼東西。\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:5,\createdAt\:\2025-10-19T01:46:59.776Z\,\updatedAt\:\2025-10-19T01:46:59.776Z\},{\id\:\ryelbe\,\codeBlockId\:\25e741f5-fe81-4f0d-8119-7519c455bb0d\,\code\:\let sum add(3, 4);\\r\\nconsole.log(sum); // 顯示 7\\r\\n\,\language\:\javascript\,\caption\:\這裡我們做了什麼:\\n\\n定義函數 add,可以傳入兩個參數 a 和 b。\\n使用 return 關鍵字:這告訴電腦我們要把 a + b 的結果返回出來。\\n\\n調用這個函數時,我們可以得到計算的結果。\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:6,\createdAt\:\2025-10-19T01:46:59.776Z\,\updatedAt\:\2025-10-19T01:46:59.776Z\}},\questionBlock\:null},{\id\:\1d87294a-df51-4d4f-b58e-4f69976fd6b9\,\articleId\:\fd22b20e-6358-4a1b-be9c-9ee108c99102\,\noteBlock\:{\id\:\1d87294a-df51-4d4f-b58e-4f69976fd6b9\,\content\:\#### 總結\\n\\n函數就像是一個功能盒子。裡面裝著我們定義的指令。\\n\\n我們可以給它取名字。傳不同的參數給它。\\n\\n甚至可以讓它返回一些結果。\\n\\n學會使用函數可以讓我們的程式更加簡潔、有組織,也更容易理解和維護。\},\codeBlock\:null,\questionBlock\:null}},{\id\:\6683d763-5f17-4913-bdaa-576933e48737\,\title\:\第二部分:視覺前端設計師 CSS\,\content\:\\\n歡迎再次回到我們的網頁開發課程,今天我們將進一步學習網頁設計中的另一個重要部分 - \\nCSS。CSS,即層疊樣式表 (Cascading Style Sheets),是用來控制網頁的外觀和排版的語言。\\n透過 CSS,我們可以讓網頁變得更加美觀和易於使用。\,\blockIndexIds\:\80046a71-1df8-4135-9870-1db6260fec3d\,\21e92975-530b-437c-9fc5-9f3c15dca211\,\6bae3382-8359-4a6d-8fb1-06feff4fb5ce\,\a9496778-0614-49c7-b830-d100a2b8336a\,\618db12b-4d70-480f-beee-fceb5a55f912\,\e1480867-a172-4155-9e5c-c1fdec695c29\,\98f65dd8-a399-4ebc-b527-59b57b999dee\,\131a5b50-1e5f-4148-8326-1b16c0ca11af\,\52336ecc-15e8-4563-b2df-71a6676b2aaa\,\slug\:null,\metaDescription\:null,\ogImage\:null,\privacy\:\PUBLIC\,\allowedEmails\:,\createdAt\:\2024-06-12T19:37:16.742Z\,\updatedAt\:\2025-11-20T03:01:23.740Z\,\authorId\:\24c3f178-4a5b-447d-b586-16d7d459c606\,\viewCount\:256,\newsletterSentAt\:\2025-10-19T10:04:57.377Z\,\blocks\:{\id\:\80046a71-1df8-4135-9870-1db6260fec3d\,\articleId\:\6683d763-5f17-4913-bdaa-576933e48737\,\noteBlock\:{\id\:\80046a71-1df8-4135-9870-1db6260fec3d\,\content\:\### 今天的目標\\n\\n* 什麼是 CSS?\\n* 如何在 HTML 中使用 CSS?\\n* CSS 基本語法\\n* 常見的 CSS 屬性與範例\\n\\n### 基礎介紹\},\codeBlock\:null,\questionBlock\:null},{\id\:\21e92975-530b-437c-9fc5-9f3c15dca211\,\articleId\:\6683d763-5f17-4913-bdaa-576933e48737\,\noteBlock\:null,\codeBlock\:{\id\:\21e92975-530b-437c-9fc5-9f3c15dca211\,\content\:\{\\\id\\\:\\\oij2q\\\,\\\code\\\:\\\\u003ch1\u003e這是一個大標題\u003c/h1\u003e\\\,\\\caption\\\:\\\什麼是 CSS?\\\\n\\\\nCSS 是用來描述 HTML 文件的呈現方式的語言。透過 CSS,我們可以設定文字顏色、字體、間距、對齊方式、邊框等。換句話說,HTML 負責網頁的內容,而 CSS 負責網頁的樣式。\\\,\\\language\\\:\\\html\\\,\\\image\\\:{\\\url\\\:\\\/api/object/1718251850674-co9wed.png\\\,\\\position\\\:\\\top-right\\\,\\\width\\\:200}},{\\\id\\\:\\\tabhri\\\,\\\code\\\:\\\\u003ch1 style\\\\\\\color: red\\\\\\\\u003e這是一個紅色大標題\u003c/h1\u003e/* @cat-caption */\\\,\\\caption\\\:\\\我們可以在元素中加入 style 的屬性\\\\n直接修改他的風格\\\\n好比說 color: red 就是將顏色文字換成紅色\\\,\\\language\\\:\\\html\\\,\\\image\\\:{\\\url\\\:\\\/api/object/1718251875548-a2r6mg.png\\\,\\\position\\\:\\\top-right\\\,\\\width\\\:200}},{\\\id\\\:\\\jg8d8n\\\,\\\code\\\:\\\\u003ch1 style\\\\\\\color: rgb(255, 128, 0);\\\\\\\\u003e這是一個橘色大標題\u003c/h1\u003e/* @cat-caption */\\\,\\\caption\\\:\\\當然,你可以用更複雜的三原色比例色值調整顏色\\\\n rgb(255, 128, 0)\\\\n就是 完全紅色 + 一半的綠色\\\,\\\language\\\:\\\html\\\,\\\image\\\:{\\\url\\\:\\\/api/object/1718251956240-j6b34r.png\\\,\\\position\\\:\\\top-right\\\,\\\width\\\:200}}\,\chunks\:{\id\:\oij2q\,\codeBlockId\:\21e92975-530b-437c-9fc5-9f3c15dca211\,\code\:\\u003ch1\u003e這是一個大標題\u003c/h1\u003e\,\language\:\html\,\caption\:\什麼是 CSS?\\n\\nCSS 是用來描述 HTML 文件的呈現方式的語言。透過 CSS,我們可以設定文字顏色、字體、間距、對齊方式、邊框等。換句話說,HTML 負責網頁的內容,而 CSS 負責網頁的樣式。\,\imageUrl\:\/api/object/1718251850674-co9wed.png\,\imagePosition\:\top-right\,\imageFullWidth\:null,\imageWidth\:200,\imageHeight\:null,\order\:0,\createdAt\:\2025-10-19T01:46:52.468Z\,\updatedAt\:\2025-10-19T01:46:52.468Z\},{\id\:\tabhri\,\codeBlockId\:\21e92975-530b-437c-9fc5-9f3c15dca211\,\code\:\\u003ch1 style\\\color: red\\\\u003e這是一個紅色大標題\u003c/h1\u003e/* @cat-caption */\,\language\:\html\,\caption\:\我們可以在元素中加入 style 的屬性\\n直接修改他的風格\\n好比說 color: red 就是將顏色文字換成紅色\,\imageUrl\:\/api/object/1718251875548-a2r6mg.png\,\imagePosition\:\top-right\,\imageFullWidth\:null,\imageWidth\:200,\imageHeight\:null,\order\:1,\createdAt\:\2025-10-19T01:46:52.468Z\,\updatedAt\:\2025-10-19T01:46:52.468Z\},{\id\:\jg8d8n\,\codeBlockId\:\21e92975-530b-437c-9fc5-9f3c15dca211\,\code\:\\u003ch1 style\\\color: rgb(255, 128, 0);\\\\u003e這是一個橘色大標題\u003c/h1\u003e/* @cat-caption */\,\language\:\html\,\caption\:\當然,你可以用更複雜的三原色比例色值調整顏色\\n rgb(255, 128, 0)\\n就是 完全紅色 + 一半的綠色\,\imageUrl\:\/api/object/1718251956240-j6b34r.png\,\imagePosition\:\top-right\,\imageFullWidth\:null,\imageWidth\:200,\imageHeight\:null,\order\:2,\createdAt\:\2025-10-19T01:46:52.468Z\,\updatedAt\:\2025-10-19T01:46:52.468Z\}},\questionBlock\:null},{\id\:\6bae3382-8359-4a6d-8fb1-06feff4fb5ce\,\articleId\:\6683d763-5f17-4913-bdaa-576933e48737\,\noteBlock\:{\id\:\6bae3382-8359-4a6d-8fb1-06feff4fb5ce\,\content\:\### 如何在 HTML 中使用 CSS?\\n\\n有三種方式可以在 HTML 中使用 CSS:\},\codeBlock\:null,\questionBlock\:null},{\id\:\a9496778-0614-49c7-b830-d100a2b8336a\,\articleId\:\6683d763-5f17-4913-bdaa-576933e48737\,\noteBlock\:null,\codeBlock\:{\id\:\a9496778-0614-49c7-b830-d100a2b8336a\,\content\:\$52\,\chunks\:{\id\:\cs6y7\,\codeBlockId\:\a9496778-0614-49c7-b830-d100a2b8336a\,\code\:\\u003cp style\\\color: blue;\\\\u003e這是一段藍色文字。\u003c/p\u003e\\r\\n\,\language\:\html\,\caption\:\內聯樣式 (Inline Styles): 直接在 HTML 標籤中使用 style 屬性。\\n\\n\,\imageUrl\:\/api/object/1723117032251-jt64.png\,\imagePosition\:\bottom-right\,\imageFullWidth\:null,\imageWidth\:200,\imageHeight\:null,\order\:0,\createdAt\:\2025-10-19T01:46:51.829Z\,\updatedAt\:\2025-10-19T01:46:51.829Z\},{\id\:\0t2o3\,\codeBlockId\:\a9496778-0614-49c7-b830-d100a2b8336a\,\code\:\\u003chead\u003e\\r\\n \u003cstyle\u003e\\r\\n p { color: blue; }\\r\\n \u003c/style\u003e\\r\\n\u003c/head\u003e\\r\\n\\r\\n\u003cbody\u003e\\r\\n \u003cp\u003e這是一段藍色文字。\u003c/p\u003e\\r\\n\u003c/body\u003e\\r\\n\,\language\:\html\,\caption\:\內部樣式 (Internal Styles): 在 HTML 文件的 \u003chead\u003e 區域中使用 \u003cstyle\u003e 標籤。\\n\\n\,\imageUrl\:\/api/object/1723117053167-wihbm.png\,\imagePosition\:\bottom-right\,\imageFullWidth\:null,\imageWidth\:200,\imageHeight\:null,\order\:1,\createdAt\:\2025-10-19T01:46:51.829Z\,\updatedAt\:\2025-10-19T01:46:51.829Z\},{\id\:\jc5h88\,\codeBlockId\:\a9496778-0614-49c7-b830-d100a2b8336a\,\code\:\// styles.css,這是另一個檔案\\r\\np {\\r\\n color: blue;\\r\\n}\\r\\n\\r\\n\\r\\n// index.html,這是主檔案\\r\\n\u003chead\u003e\\r\\n \u003clink rel\\\stylesheet\\\ type\\\text/css\\\ href\\\styles.css\\\\u003e\\r\\n\u003c/head\u003e\\r\\n\\r\\n\u003cbody\u003e\\r\\n \u003cp\u003e這是一段藍色文字。\u003c/p\u003e\\r\\n\u003c/body\u003e\\r\\n\,\language\:\html\,\caption\:\外部樣式 (External Styles): 使用外部 CSS 文件,並在 HTML 文件中通過 \u003clink\u003e 標籤引用。\\n\\n\,\imageUrl\:\/api/object/1723117066220-kql5ne.png\,\imagePosition\:\bottom-right\,\imageFullWidth\:null,\imageWidth\:200,\imageHeight\:null,\order\:2,\createdAt\:\2025-10-19T01:46:51.829Z\,\updatedAt\:\2025-10-19T01:46:51.829Z\},{\id\:\7aonzm\,\codeBlockId\:\a9496778-0614-49c7-b830-d100a2b8336a\,\code\:\p {\\r\\n color: blue;\\r\\n}\\r\\n\,\language\:\css\,\caption\:\CSS 的語法很簡單,分成兩個部分\\n目標 與 效果\\n以右邊的程式碼為例\\n目標是網頁中所有的 p 元素\\n效果是變成藍色文字\\n\\n另外目標的專有名詞是 選擇器 (selector) \\n而效果的專有名詞是 宣告塊 (declaration block) \,\imageUrl\:\/api/object/1723117103212-qqkkl.png\,\imagePosition\:\bottom-right\,\imageFullWidth\:null,\imageWidth\:200,\imageHeight\:null,\order\:3,\createdAt\:\2025-10-19T01:46:51.829Z\,\updatedAt\:\2025-10-19T01:46:51.829Z\},{\id\:\8wg9em\,\codeBlockId\:\a9496778-0614-49c7-b830-d100a2b8336a\,\code\:\p {\\r\\n color: blue;\\r\\n font-size: 24px;\\r\\n}\\r\\n\,\language\:\css\,\caption\:\這段 CSS 語法會將所有 \u003cp\u003e 元素的文字顏色設為藍色,字體大小設為 16 像素。\\n\\n\,\imageUrl\:\/api/object/1723117170105-kintt.png\,\imagePosition\:\bottom-right\,\imageFullWidth\:null,\imageWidth\:300,\imageHeight\:null,\order\:4,\createdAt\:\2025-10-19T01:46:51.829Z\,\updatedAt\:\2025-10-19T01:46:51.829Z\},{\id\:\urkv59\,\codeBlockId\:\a9496778-0614-49c7-b830-d100a2b8336a\,\code\:\\u003c!DOCTYPE html\u003e\\r\\n\u003chtml lang\\\en\\\\u003e\\r\\n \u003chead\u003e\\r\\n \u003cstyle\u003e\\r\\n .blue-text {\\r\\n color: blue;\\r\\n font-size: 24px;\\r\\n }\\r\\n \u003c/style\u003e\\r\\n \u003c/head\u003e\\r\\n \u003cbody\u003e\\r\\n \u003cp\u003e這是一段一般的文字\u003c/p\u003e\\r\\n \u003cp class\\\blue-text\\\\u003e這是一段藍色的文字\u003c/p\u003e\\r\\n \u003c/body\u003e\\r\\n\u003c/html\u003e\\r\\n\,\language\:\html\,\caption\:\除了一個一個定義元素的樣式外,或者批量全部修改外\\n也可以使用 class 類別\\n類別(class) 就像是幫元素貼上一個「分類」,可以重複使用。\\n所以被貼上該分類的元素,都會取得該分類的效果\\n\\n類別只能在 style 中宣告,或者在獨立的 css 檔案中宣告\\n\,\imageUrl\:\/api/object/1723117185012-b9chaa.png\,\imagePosition\:\bottom-right\,\imageFullWidth\:null,\imageWidth\:300,\imageHeight\:null,\order\:5,\createdAt\:\2025-10-19T01:46:51.829Z\,\updatedAt\:\2025-10-19T01:46:51.829Z\},{\id\:\nwss3\,\codeBlockId\:\a9496778-0614-49c7-b830-d100a2b8336a\,\code\:\.blue-text {\\r\\n color: blue;\\r\\n font-size: 24px;\\r\\n}\,\language\:\css\,\caption\:\css 中的寫法是句點「.」+「類別名稱」\\n\\n類別名稱基本上能自由定義\\n(不過用詞上只能使用英文與 - )\,\imageUrl\:null,\imagePosition\:null,\imageFullWidth\:null,\imageWidth\:null,\imageHeight\:null,\order\:6,\createdAt\:\2025-10-19T01:46:51.829Z\,\updatedAt\:\2025-10-19T01:46:51.829Z\},{\id\:\q3w1w3\,\codeBlockId\:\a9496778-0614-49c7-b830-d100a2b8336a\,\code\:\\u003cp class\\\blue-text\\\\u003e這是一段藍色的文字\u003c/p\u003e\,\language\:\html\,\caption\:\html 中的寫法則是替需要指定元素中的屬性加上 class\\nclass\\\類別名稱\\\\\n注意 html 的類別不要加點「.」\,\imageUrl\:\/api/object/1723117238792-wms508.png\,\imagePosition\:\bottom-right\,\imageFullWidth\:null,\imageWidth\:300,\imageHeight\:null,\order\:7,\createdAt\:\2025-10-19T01:46:51.829Z\,\updatedAt\:\2025-10-19T01:46:51.829Z\}},\questionBlock\:null},{\id\:\618db12b-4d70-480f-beee-fceb5a55f912\,\articleId\:\6683d763-5f17-4913-bdaa-576933e48737\,\noteBlock\:{\id\:\618db12b-4d70-480f-beee-fceb5a55f912\,\content\:\### 常見的 CSS 屬性與範例\},\codeBlock\:null,\questionBlock\:null},{\id\:\e1480867-a172-4155-9e5c-c1fdec695c29\,\articleId\:\6683d763-5f17-4913-bdaa-576933e48737\,\noteBlock\:null,\codeBlock\:{\id\:\e1480867-a172-4155-9e5c-c1fdec695c29\,\content\:\$53\,\chunks\:{\id\:\s4sl5j\,\codeBlockId\:\e1480867-a172-4155-9e5c-c1fdec695c29\,\code\:\p {\\r\\n color: red;\\r\\n}\\r\\n\,\language\:\css\,\caption\:\color: 設定文字顏色。\,\imageUrl\:\/api/object/1723117576669-xq56sf.png\,\imagePosition\:\bottom-right\,\imageFullWidth\:null,\imageWidth\:200,\imageHeight\:null,\order\:0,\createdAt\:\2025-10-19T01:46:53.252Z\,\updatedAt\:\2025-10-19T01:46:53.252Z\},{\id\:\7d17h\,\codeBlockId\:\e1480867-a172-4155-9e5c-c1fdec695c29\,\code\:\p {\\r\\n color: red;\\r\\n font-size: 32px;\\r\\n}\\r\\n\,\language\:\css\,\caption\:\font-size: 設定文字大小。\\n\\n\,\imageUrl\:\/api/object/1723117588039-5unhsq.png\,\imagePosition\:\bottom-right\,\imageFullWidth\:null,\imageWidth\:400,\imageHeight\:null,\order\:1,\createdAt\:\2025-10-19T01:46:53.252Z\,\updatedAt\:\2025-10-19T01:46:53.252Z\},{\id\:\rm6hvq\,\codeBlockId\:\e1480867-a172-4155-9e5c-c1fdec695c29\,\code\:\p {\\r\\n color: red;\\r\\n font-size: 32px;\\r\\n text-align: center;\\r\\n}\\r\\n\,\language\:\css\,\caption\:\text-align: 設定文字對齊方式。\,\imageUrl\:\/api/object/1723117614317-oi35bb.png\,\imagePosition\:\bottom-right\,\imageFullWidth\:null,\imageWidth\:400,\imageHeight\:null,\order\:2,\createdAt\:\2025-10-19T01:46:53.252Z\,\updatedAt\:\2025-10-19T01:46:53.252Z\},{\id\:\j896as\,\codeBlockId\:\e1480867-a172-4155-9e5c-c1fdec695c29\,\code\:\p {\\r\\n color: red;\\r\\n font-size: 32px;\\r\\n text-align: right;\\r\\n}\\r\\n\,\language\:\css\,\caption\:\我們可以試試看置右\,\imageUrl\:\/api/object/1723117644360-pbkcn.png\,\imagePosition\:\bottom-right\,\imageFullWidth\:null,\imageWidth\:400,\imageHeight\:null,\order\:3,\createdAt\:\2025-10-19T01:46:53.252Z\,\updatedAt\:\2025-10-19T01:46:53.252Z\},{\id\:\9kass\,\codeBlockId\:\e1480867-a172-4155-9e5c-c1fdec695c29\,\code\:\p {\\r\\n color: red;\\r\\n font-size: 32px;\\r\\n text-align: right;\\r\\n background-color: lightgray;\\r\\n}\\r\\n\,\language\:\css\,\caption\:\background-color: 設定背景顏色。\\n\\n\,\imageUrl\:\/api/object/1723117666158-1uniod.png\,\imagePosition\:\bottom-right\,\imageFullWidth\:null,\imageWidth\:400,\imageHeight\:null,\order\:4,\createdAt\:\2025-10-19T01:46:53.252Z\,\updatedAt\:\2025-10-19T01:46:53.252Z\},{\id\:\6iv7e9\,\codeBlockId\:\e1480867-a172-4155-9e5c-c1fdec695c29\,\code\:\p {\\r\\n color: red;\\r\\n font-size: 32px;\\r\\n text-align: right;\\r\\n background-image: url(example.jpg);\\r\\n}\\r\\n\,\language\:\css\,\caption\:\background-image: 設定背景圖片。\\n\\n\,\imageUrl\:\/api/object/1723117837284-wsllgr.png\,\imagePosition\:\bottom-right\,\imageFullWidth\:null,\imageWidth\:400,\imageHeight\:null,\order\:5,\createdAt\:\2025-10-19T01:46:53.252Z\,\updatedAt\:\2025-10-19T01:46:53.252Z\},{\id\:\f5iii\,\codeBlockId\:\e1480867-a172-4155-9e5c-c1fdec695c29\,\code\:\p {\\r\\n color: red;\\r\\n font-size: 32px;\\r\\n text-align: right;\\r\\n background-image: url(example.jpg);\\r\\n border: 10px solid black;\\r\\n}\\r\\n\,\language\:\css\,\caption\:\border: 設定元素的邊框。\\n\\n\,\imageUrl\:\/api/object/1723117882397-8lgm2k.png\,\imagePosition\:\bottom-right\,\imageFullWidth\:null,\imageWidth\:400,\imageHeight\:null,\order\:6,\createdAt\:\2025-10-19T01:46:53.252Z\,\updatedAt\:\2025-10-19T01:46:53.252Z\},{\id\:\cz8azd\,\codeBlockId\:\e1480867-a172-4155-9e5c-c1fdec695c29\,\code\:\p {\\r\\n color: red;\\r\\n font-size: 32px;\\r\\n text-align: right;\\r\\n background-image: url(example.jpg);\\r\\n border: 1px solid black;\\r\\n border-radius: 50%;\\r\\n}\\r\\n\,\language\:\css\,\caption\:\border-radius: 圓邊\,\imageUrl\:\/api/object/1723117940011-czy74s.png\,\imagePosition\:\bottom-right\,\imageFullWidth\:null,\imageWidth\:398,\imageHeight\:null,\order\:7,\createdAt\:\2025-10-19T01:46:53.252Z\,\updatedAt\:\2025-10-19T01:46:53.252Z\},{\id\:\04h93n\,\codeBlockId\:\e1480867-a172-4155-9e5c-c1fdec695c29\,\code\:\p {\\r\\n color: red;\\r\\n font-size: 32px;\\r\\n text-align: right;\\r\\n background-image: url(example.jpg);\\r\\n border: 1px solid black;\\r\\n border-radius: 50%;\\r\\n padding: 128px;\\r\\n}\\r\\n\,\language\:\css\,\caption\:\目前你會發現文字距離邊界太近了\\n這是我們可以設定 padding\\n他可以限制內容與邊框的距離(內距)\,\imageUrl\:\/api/object/1723117999467-89ht1.png\,\imagePosition\:\bottom-right\,\imageFullWidth\:null,\imageWidth\:400,\imageHeight\:null,\order\:8,\createdAt\:\2025-10-19T01:46:53.252Z\,\updatedAt\:\2025-10-19T01:46:53.252Z\},{\id\:\04fv9m\,\codeBlockId\:\e1480867-a172-4155-9e5c-c1fdec695c29\,\code\:\\u003c!DOCTYPE html\u003e\\r\\n\u003chtml lang\\\en\\\\u003e\\r\\n \u003chead\u003e\\r\\n \u003cmeta charset\\\UTF-8\\\ /\u003e\\r\\n \u003cmeta name\\\viewport\\\ content\\\widthdevice-width, initial-scale1.0\\\ /\u003e\\r\\n \u003ctitle\u003eDocument\u003c/title\u003e\\r\\n \u003cstyle\u003e\\r\\n p {\\r\\n color: red;\\r\\n font-size: 32px;\\r\\n text-align: right;\\r\\n background-image: url(\\\example.jpg\\\);\\r\\n border: 1px solid black;\\r\\n border-radius: 50%;\\r\\n padding: 128px;\\r\\n }\\r\\n \u003c/style\u003e\\r\\n \u003c/head\u003e\\r\\n \u003cbody\u003e\\r\\n \u003cp\u003e這是一個大貓貓\u003c/p\u003e\\r\\n \u003cp\u003e這是一個大貓貓\u003c/p\u003e\\r\\n \u003c/body\u003e\\r\\n\u003c/html\u003e\\r\\n\,\language\:\html\,\caption\:\我們來創造第二個元素看看\\n我們順利創造了兩張老人圖!\,\imageUrl\:\/api/object/1723118035393-s70m8f.png\,\imagePosition\:\bottom-right\,\imageFullWidth\:null,\imageWidth\:400,\imageHeight\:null,\order\:9,\createdAt\:\2025-10-19T01:46:53.252Z\,\updatedAt\:\2025-10-19T01:46:53.252Z\},{\id\:\cywdmj\,\codeBlockId\:\e1480867-a172-4155-9e5c-c1fdec695c29\,\code\:\\u003c!DOCTYPE html\u003e\\r\\n\u003chtml lang\\\en\\\\u003e\\r\\n \u003chead\u003e\\r\\n \u003cmeta charset\\\UTF-8\\\ /\u003e\\r\\n \u003cmeta name\\\viewport\\\ content\\\widthdevice-width, initial-scale1.0\\\ /\u003e\\r\\n \u003ctitle\u003eDocument\u003c/title\u003e\\r\\n \u003cstyle\u003e\\r\\n p {\\r\\n color: red;\\r\\n font-size: 32px;\\r\\n text-align: right;\\r\\n background-image: url(\\\example.jpg\\\);\\r\\n border: 1px solid black;\\r\\n border-radius: 50%;\\r\\n padding: 128px;\\r\\n margin: 128px;\\r\\n }\\r\\n \u003c/style\u003e\\r\\n \u003c/head\u003e\\r\\n \u003cbody\u003e\\r\\n \u003cp\u003e這是一個大貓貓\u003c/p\u003e\\r\\n \u003cp\u003e這是一個大貓貓\u003c/p\u003e\\r\\n \u003c/body\u003e\\r\\n\u003c/html\u003e\\r\\n\,\language\:\html\,\caption\:\我們可以透過 margin 調整元素與其他元素之間的距離(外距)\\n你可以想像成設定其他元素與我之間的社交距離\,\imageUrl\:\/api/object/1723118078642-aqsk0a.png\,\imagePosition\:\bottom-right\,\imageFullWidth\:null,\imageWidth\:400,\imageHeight\:null,\order\:10,\createdAt\:\2025-10-19T01:46:53.252Z\,\updatedAt\:\2025-10-19T01:46:53.252Z\},{\id\:\0mapyo\,\codeBlockId\:\e1480867-a172-4155-9e5c-c1fdec695c29\,\code\:\\u003c!DOCTYPE html\u003e\\r\\n\u003chtml lang\\\en\\\\u003e\\r\\n \u003chead\u003e\\r\\n \u003cmeta charset\\\UTF-8\\\ /\u003e\\r\\n \u003cmeta name\\\viewport\\\ content\\\widthdevice-width, initial-scale1.0\\\ /\u003e\\r\\n \u003ctitle\u003eDocument\u003c/title\u003e\\r\\n \u003cstyle\u003e\\r\\n p {\\r\\n height: 200px; /* @cat-caption */\\r\\n background-color: lightgray; \\r\\n }\\r\\n \u003c/style\u003e\\r\\n \u003c/head\u003e\\r\\n \u003cbody\u003e\\r\\n \u003cp\u003e這是一個大貓貓\u003c/p\u003e\\r\\n \u003cp\u003e這是一個大貓貓\u003c/p\u003e\\r\\n \u003c/body\u003e\\r\\n\u003c/html\u003e\\r\\n\,\language\:\html\,\caption\:\順帶一提,你可以設定元素的高度\,\imageUrl\:\/api/object/1723118180803-7z90j.png\,\imagePosition\:\bottom-right\,\imageFullWidth\:null,\imageWidth\:400,\imageHeight\:null,\order\:11,\createdAt\:\2025-10-19T01:46:53.252Z\,\updatedAt\:\2025-10-19T01:46:53.252Z\},{\id\:\f9u7bf\,\codeBlockId\:\e1480867-a172-4155-9e5c-c1fdec695c29\,\code\:\\u003c!DOCTYPE html\u003e\\r\\n\u003chtml lang\\\en\\\\u003e\\r\\n \u003chead\u003e\\r\\n \u003cmeta charset\\\UTF-8\\\ /\u003e\\r\\n \u003cmeta name\\\viewport\\\ content\\\widthdevice-width, initial-scale1.0\\\ /\u003e\\r\\n \u003ctitle\u003eDocument\u003c/title\u003e\\r\\n \u003cstyle\u003e\\r\\n p {\\r\\n height: 200px; \\r\\n width: 200px; /* @cat-caption */\\r\\n }\\r\\n \u003c/style\u003e\\r\\n \u003c/head\u003e\\r\\n \u003cbody\u003e\\r\\n \u003cp\u003e這是一個大貓貓\u003c/p\u003e\\r\\n \u003cp\u003e這是一個大貓貓\u003c/p\u003e\\r\\n \u003c/body\u003e\\r\\n\u003c/html\u003e\\r\\n\,\language\:\html\,\caption\:\設定元素的寬度\,\imageUrl\:\/api/object/1723118246368-r26dth.png\,\imagePosition\:\bottom-right\,\imageFullWidth\:null,\imageWidth\:200,\imageHeight\:null,\order\:12,\createdAt\:\2025-10-19T01:46:53.252Z\,\updatedAt\:\2025-10-19T01:46:53.252Z\}},\questionBlock\:null},{\id\:\98f65dd8-a399-4ebc-b527-59b57b999dee\,\articleId\:\6683d763-5f17-4913-bdaa-576933e48737\,\noteBlock\:{\id\:\98f65dd8-a399-4ebc-b527-59b57b999dee\,\content\:\### 常用的排版方法\\n\\n他可以讓我們簡單的規定各個元素的位置\},\codeBlock\:null,\questionBlock\:null},{\id\:\131a5b50-1e5f-4148-8326-1b16c0ca11af\,\articleId\:\6683d763-5f17-4913-bdaa-576933e48737\,\noteBlock\:null,\codeBlock\:{\id\:\131a5b50-1e5f-4148-8326-1b16c0ca11af\,\content\:\$54\,\chunks\:{\id\:\oq5mp8\,\codeBlockId\:\131a5b50-1e5f-4148-8326-1b16c0ca11af\,\code\:\\u003cp\u003e這是一個大貓貓\u003c/p\u003e\\r\\n\u003cp style\\\display: block\\\\u003e大家默認都是 block\u003c/p\u003e\\r\\n\u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\\r\\n\u003cp\u003e這是一個大貓貓\u003c/p\u003e\,\language\:\html\,\caption\:\正常排版下\\n一個區塊會占滿完整一行\\ndiv、p 這些元素都是一個一個獨立區塊(block)\\n\\n那如果我想做到同一行內放好幾個 div 呢?\,\imageUrl\:\/api/object/1719179891638-hbendu.png\,\imagePosition\:\top-right\,\imageFullWidth\:null,\imageWidth\:200,\imageHeight\:null,\order\:0,\createdAt\:\2025-10-19T01:47:01.522Z\,\updatedAt\:\2025-10-19T01:47:01.522Z\},{\id\:\adg7vq\,\codeBlockId\:\131a5b50-1e5f-4148-8326-1b16c0ca11af\,\code\:\\u003cp\u003e這是一個大貓貓\u003c/p\u003e\\r\\n\u003cp style\\\display: block\\\\u003e大家默認都是 block\u003c/p\u003e\\r\\n\u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\\r\\n\u003cp\u003e這是一個大貓貓\u003c/p\u003e\\r\\n\u003cdiv\u003e/* @cat-caption */\\r\\n \u003cdiv style\\\display: inline\\\\u003e這是一個大貓貓\u003c/div\u003e/* @cat-caption */\\r\\n \u003cdiv style\\\display: inline\\\\u003e這是一個大貓貓\u003c/div\u003e/* @cat-caption */\\r\\n \u003cdiv style\\\display: inline\\\\u003e這是一個大貓貓\u003c/div\u003e/* @cat-caption */\\r\\n\u003c/div\u003e/* @cat-caption */\,\language\:\html\,\caption\:\這邊就可以修改元素顯示方式 display\\n將其改成 inline\\ninline 的顯示方式讓 div 不用占滿一整行\,\imageUrl\:\/api/object/1719179917981-0nq7wk.png\,\imagePosition\:\top-right\,\imageFullWidth\:null,\imageWidth\:200,\imageHeight\:null,\order\:1,\createdAt\:\2025-10-19T01:47:01.522Z\,\updatedAt\:\2025-10-19T01:47:01.522Z\},{\id\:\ksn9mme\,\codeBlockId\:\131a5b50-1e5f-4148-8326-1b16c0ca11af\,\code\:\\u003cp\u003e這是一個大貓貓\u003c/p\u003e\\r\\n\u003cp style\\\display: block\\\\u003e大家默認都是 block\u003c/p\u003e\\r\\n\u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\\r\\n\u003cp\u003e這是一個大貓貓\u003c/p\u003e\\r\\n\u003cdiv\u003e\\r\\n \u003cdiv style\\\display: inline\\\\u003e這是一個大貓貓\u003c/div\u003e\\r\\n \u003cdiv style\\\display: inline\\\\u003e這是一個大貓貓\u003c/div\u003e\\r\\n \u003cdiv style\\\display: inline\\\\u003e這是一個大貓貓\u003c/div\u003e\\r\\n\u003c/div\u003e\,\language\:\html\,\caption\:\那如果我們想做到更複雜的呢?\\n比如說能夠置右呢?\\n或比如說能夠平均分散呢?\,\imageUrl\:\/api/object/1719179931563-f5qlp8.png\,\imagePosition\:\top-right\,\imageFullWidth\:null,\imageWidth\:200,\imageHeight\:null,\order\:2,\createdAt\:\2025-10-19T01:47:01.522Z\,\updatedAt\:\2025-10-19T01:47:01.522Z\},{\id\:\2f7dsb\,\codeBlockId\:\131a5b50-1e5f-4148-8326-1b16c0ca11af\,\code\:\\u003cp\u003e這是一個大貓貓\u003c/p\u003e\\r\\n\u003cp style\\\display: block\\\\u003e大家默認都是 block\u003c/p\u003e\\r\\n\u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\\r\\n\u003cp\u003e這是一個大貓貓\u003c/p\u003e\\r\\n\u003cdiv style\\\display: flex;\\\\u003e /* @cat-caption */\\r\\n \u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\\r\\n \u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\\r\\n \u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\\r\\n\u003c/div\u003e\,\language\:\html\,\caption\:\display:flex (彈性盒子)\\n可以算是 css 數一數二重要的屬性\\n正如他的名字所說,它可以讓元素的排列充滿彈性\\n\\n值得注意的是 flex 控制的是元素小孩們的排列\\n默認情況下,它會讓他的小孩全部在同一行,哪怕他的小孩是一個 block\\n\,\imageUrl\:\/api/object/1719180024755-ha1878.png\,\imagePosition\:\bottom-right\,\imageFullWidth\:null,\imageWidth\:600,\imageHeight\:null,\order\:3,\createdAt\:\2025-10-19T01:47:01.522Z\,\updatedAt\:\2025-10-19T01:47:01.522Z\},{\id\:\hpoh7h\,\codeBlockId\:\131a5b50-1e5f-4148-8326-1b16c0ca11af\,\code\:\\u003cp\u003e這是一個大貓貓\u003c/p\u003e\\r\\n\u003cp style\\\display: block\\\\u003e大家默認都是 block\u003c/p\u003e\\r\\n\u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\\r\\n\u003cp\u003e這是一個大貓貓\u003c/p\u003e\\r\\n\u003cdiv style\\\display: flex;\\\\u003e\\r\\n \u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\\r\\n \u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\\r\\n \u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\\r\\n\u003c/div\u003e\\r\\n\u003cdiv\u003e這是一個大貓貓\u003c/div\u003e /* @cat-caption */\,\language\:\html\,\caption\:\而就是這個元素本身還是會占用一行喔,這點要特別注意\,\imageUrl\:\/api/object/1719180044292-2dj6w.png\,\imagePosition\:\bottom-right\,\imageFullWidth\:null,\imageWidth\:600,\imageHeight\:null,\order\:4,\createdAt\:\2025-10-19T01:47:01.522Z\,\updatedAt\:\2025-10-19T01:47:01.522Z\},{\id\:\stnnwj\,\codeBlockId\:\131a5b50-1e5f-4148-8326-1b16c0ca11af\,\code\:\\u003cp\u003e這是一個大貓貓\u003c/p\u003e\\r\\n\u003cp style\\\display: block\\\\u003e大家默認都是 block\u003c/p\u003e\\r\\n\u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\\r\\n\u003cp\u003e這是一個大貓貓\u003c/p\u003e\\r\\n\u003cdiv style\\\display: flex;justify-content: end\\\\u003e/* @cat-caption */\\r\\n \u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\\r\\n \u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\\r\\n \u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\\r\\n\u003c/div\u003e\\r\\n\u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\,\language\:\html\,\caption\:\justify-content\\n透過第二個 css 屬性,我們可以設定小孩們怎麼排列\\njustify-content: end 將小孩全部推到末端(右側)\,\imageUrl\:\/api/object/1719179115993-3uxirp.png\,\imagePosition\:\bottom-right\,\imageFullWidth\:null,\imageWidth\:600,\imageHeight\:null,\order\:5,\createdAt\:\2025-10-19T01:47:01.522Z\,\updatedAt\:\2025-10-19T01:47:01.522Z\},{\id\:\m8kumya\,\codeBlockId\:\131a5b50-1e5f-4148-8326-1b16c0ca11af\,\code\:\\u003cp\u003e這是一個大貓貓\u003c/p\u003e\\r\\n\u003cp style\\\display: block\\\\u003e大家默認都是 block\u003c/p\u003e\\r\\n\u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\\r\\n\u003cp\u003e這是一個大貓貓\u003c/p\u003e\\r\\n\u003cdiv style\\\display: flex;justify-content: start\\\\u003e/* @cat-caption */\\r\\n \u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\\r\\n \u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\\r\\n \u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\\r\\n\u003c/div\u003e\\r\\n\u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\,\language\:\html\,\caption\:\justify-content: start 將小孩全部推到開始端(左側)\,\imageUrl\:\/api/object/1719180087332-zuzhbl.png\,\imagePosition\:\bottom-right\,\imageFullWidth\:null,\imageWidth\:600,\imageHeight\:null,\order\:6,\createdAt\:\2025-10-19T01:47:01.522Z\,\updatedAt\:\2025-10-19T01:47:01.522Z\},{\id\:\awcqba\,\codeBlockId\:\131a5b50-1e5f-4148-8326-1b16c0ca11af\,\code\:\\u003cp\u003e這是一個大貓貓\u003c/p\u003e\\r\\n\u003cp style\\\display: block\\\\u003e大家默認都是 block\u003c/p\u003e\\r\\n\u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\\r\\n\u003cp\u003e這是一個大貓貓\u003c/p\u003e\\r\\n\u003cdiv style\\\display: flex;justify-content: space-between\\\\u003e/* @cat-caption */\\r\\n \u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\\r\\n \u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\\r\\n \u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\\r\\n\u003c/div\u003e\\r\\n\u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\,\language\:\html\,\caption\:\justify-content: space-between \\n將小孩平均分散在整行中\,\imageUrl\:\/api/object/1719179382978-ryyi89.png\,\imagePosition\:\bottom-right\,\imageFullWidth\:null,\imageWidth\:600,\imageHeight\:null,\order\:7,\createdAt\:\2025-10-19T01:47:01.522Z\,\updatedAt\:\2025-10-19T01:47:01.522Z\},{\id\:\d33go\,\codeBlockId\:\131a5b50-1e5f-4148-8326-1b16c0ca11af\,\code\:\\u003cp\u003e這是一個大貓貓\u003c/p\u003e\\r\\n\u003cp style\\\display: block\\\\u003e大家默認都是 block\u003c/p\u003e\\r\\n\u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\\r\\n\u003cp\u003e這是一個大貓貓\u003c/p\u003e\\r\\n\u003cdiv style\\\display: flex; justify-content: space-evenly\\\\u003e/* @cat-caption */\\r\\n \u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\\r\\n \u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\\r\\n \u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\\r\\n\u003c/div\u003e\\r\\n\u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\,\language\:\html\,\caption\:\justify-content: space-evenly\\n將小孩平均分散在整行中\\n但是最前面跟最後面會保留空間\,\imageUrl\:\/api/object/1719179439198-gkztzq.png\,\imagePosition\:\bottom-right\,\imageFullWidth\:null,\imageWidth\:601,\imageHeight\:null,\order\:8,\createdAt\:\2025-10-19T01:47:01.522Z\,\updatedAt\:\2025-10-19T01:47:01.522Z\},{\id\:\oao76t\,\codeBlockId\:\131a5b50-1e5f-4148-8326-1b16c0ca11af\,\code\:\\u003cp\u003e這是一個大貓貓\u003c/p\u003e\\r\\n\u003cp style\\\display: block\\\\u003e大家默認都是 block\u003c/p\u003e\\r\\n\u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\\r\\n\u003cp\u003e這是一個大貓貓\u003c/p\u003e\\r\\n\u003cdiv/* @cat-caption */\\r\\n style\\\ /* @cat-caption */\\r\\n display: flex;/* @cat-caption */\\r\\n justify-content: space-evenly;/* @cat-caption */\\r\\n background: blue;/* @cat-caption */\\r\\n height: 200px;/* @cat-caption */\\r\\n \\\/* @cat-caption */\\r\\n\u003e/* @cat-caption */\\r\\n \u003cdiv style\\\background: red; height: 30px\\\\u003e這是一個大貓貓\u003c/div\u003e/* @cat-caption */\\r\\n \u003cdiv style\\\background: red; height: 90px\\\\u003e這是一個大貓貓\u003c/div\u003e/* @cat-caption */\\r\\n \u003cdiv style\\\background: red; height: 60px\\\\u003e這是一個大貓貓\u003c/div\u003e/* @cat-caption */\\r\\n\u003c/div\u003e\\r\\n\u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\,\language\:\html\,\caption\:\我們替元素染個色、設定高度方便接下來觀察\,\imageUrl\:\/api/object/1719180405981-vkssh.png\,\imagePosition\:\bottom-right\,\imageFullWidth\:false,\imageWidth\:600,\imageHeight\:null,\order\:9,\createdAt\:\2025-10-19T01:47:01.522Z\,\updatedAt\:\2025-10-19T01:47:01.522Z\},{\id\:\ogj2nn\,\codeBlockId\:\131a5b50-1e5f-4148-8326-1b16c0ca11af\,\code\:\\u003cp\u003e這是一個大貓貓\u003c/p\u003e\\r\\n\u003cp style\\\display: block\\\\u003e大家默認都是 block\u003c/p\u003e\\r\\n\u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\\r\\n\u003cp\u003e這是一個大貓貓\u003c/p\u003e\\r\\n\u003cdiv\\r\\n style\\\\\r\\n display: flex;\\r\\n justify-content: space-evenly;\\r\\n align-items: end;/* @cat-caption */\\r\\n background: blue;\\r\\n height: 200px;\\r\\n \\\\\r\\n\u003e\\r\\n \u003cdiv style\\\background: red; height: 30px\\\\u003e這是一個大貓貓\u003c/div\u003e\\r\\n \u003cdiv style\\\background: red; height: 90px\\\\u003e這是一個大貓貓\u003c/div\u003e\\r\\n \u003cdiv style\\\background: red; height: 60px\\\\u003e這是一個大貓貓\u003c/div\u003e\\r\\n\u003c/div\u003e\\r\\n\u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\,\language\:\html\,\caption\:\align-items 則是控制\\\所有\\\子元素的 y 軸排列\\nalign-items: end; 讓所有的子元素全部靠在底部\\n\,\imageUrl\:\/api/object/1719180468851-ramyp.png\,\imagePosition\:\bottom-right\,\imageFullWidth\:null,\imageWidth\:600,\imageHeight\:null,\order\:10,\createdAt\:\2025-10-19T01:47:01.522Z\,\updatedAt\:\2025-10-19T01:47:01.522Z\},{\id\:\wix3n4\,\codeBlockId\:\131a5b50-1e5f-4148-8326-1b16c0ca11af\,\code\:\\u003cp\u003e這是一個大貓貓\u003c/p\u003e\\r\\n\u003cp style\\\display: block\\\\u003e大家默認都是 block\u003c/p\u003e\\r\\n\u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\\r\\n\u003cp\u003e這是一個大貓貓\u003c/p\u003e\\r\\n\u003cdiv\\r\\n style\\\\\r\\n display: flex;\\r\\n justify-content: space-evenly;\\r\\n align-items: center;/* @cat-caption */\\r\\n background: blue;\\r\\n height: 200px;\\r\\n \\\\\r\\n\u003e\\r\\n \u003cdiv style\\\background: red; height: 30px\\\\u003e這是一個大貓貓\u003c/div\u003e\\r\\n \u003cdiv style\\\background: red; height: 90px\\\\u003e這是一個大貓貓\u003c/div\u003e\\r\\n \u003cdiv style\\\background: red; height: 60px\\\\u003e這是一個大貓貓\u003c/div\u003e\\r\\n\u003c/div\u003e\\r\\n\u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\,\language\:\html\,\caption\:\align-items: center; 讓所有的子元素全部靠在中間\\n\,\imageUrl\:\/api/object/1719180498398-q8gwe.png\,\imagePosition\:\bottom-right\,\imageFullWidth\:null,\imageWidth\:600,\imageHeight\:null,\order\:11,\createdAt\:\2025-10-19T01:47:01.522Z\,\updatedAt\:\2025-10-19T01:47:01.522Z\},{\id\:\gbdz8\,\codeBlockId\:\131a5b50-1e5f-4148-8326-1b16c0ca11af\,\code\:\\u003cp\u003e這是一個大貓貓\u003c/p\u003e\\r\\n\u003cp style\\\display: block\\\\u003e大家默認都是 block\u003c/p\u003e\\r\\n\u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\\r\\n\u003cp\u003e這是一個大貓貓\u003c/p\u003e\\r\\n\u003cdiv\\r\\n style\\\\\r\\n display: flex;\\r\\n justify-content: space-evenly;\\r\\n align-items: start;/* @cat-caption */\\r\\n background: blue;\\r\\n height: 200px;\\r\\n \\\\\r\\n\u003e\\r\\n \u003cdiv style\\\background: red; height: 30px\\\\u003e這是一個大貓貓\u003c/div\u003e\\r\\n \u003cdiv style\\\background: red; height: 90px\\\\u003e這是一個大貓貓\u003c/div\u003e\\r\\n \u003cdiv style\\\background: red; height: 60px\\\\u003e這是一個大貓貓\u003c/div\u003e\\r\\n\u003c/div\u003e\\r\\n\u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\,\language\:\html\,\caption\:\align-items: start; 讓所有的子元素全部靠在上面\\n\,\imageUrl\:\/api/object/1719180567261-yw00g.png\,\imagePosition\:\bottom-right\,\imageFullWidth\:null,\imageWidth\:600,\imageHeight\:null,\order\:12,\createdAt\:\2025-10-19T01:47:01.522Z\,\updatedAt\:\2025-10-19T01:47:01.522Z\},{\id\:\uh93yb\,\codeBlockId\:\131a5b50-1e5f-4148-8326-1b16c0ca11af\,\code\:\\u003cp\u003e這是一個大貓貓\u003c/p\u003e\\r\\n\u003cp style\\\display: block\\\\u003e大家默認都是 block\u003c/p\u003e\\r\\n\u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\\r\\n\u003cp\u003e這是一個大貓貓\u003c/p\u003e\\r\\n\u003cdiv\\r\\n style\\\\\r\\n display: flex;\\r\\n flex-direction: column; /* @cat-caption */\\r\\n background: blue;\\r\\n height: 200px;\\r\\n \\\\\r\\n\u003e\\r\\n \u003cdiv style\\\background: red; height: 30px\\\\u003e這是一個大貓貓\u003c/div\u003e/* @cat-caption */\\r\\n \u003cdiv style\\\background: green; height: 90px\\\\u003e這是一個大貓貓\u003c/div\u003e/* @cat-caption */\\r\\n \u003cdiv style\\\background: orange; height: 60px\\\\u003e這是一個大貓貓\u003c/div\u003e/* @cat-caption */\\r\\n\u003c/div\u003e\\r\\n\u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\,\language\:\html\,\caption\:\flex 最強大的地方還有\\n他可以立起來,原本元素都是橫向 (x 軸) 排列\\n可以透過 flex-direction 修改成直向 (y 軸) 排列\\nflex-direction: column; y 軸排列\,\imageUrl\:\/api/object/1719180801456-b7fiti.png\,\imagePosition\:\bottom-right\,\imageFullWidth\:null,\imageWidth\:601,\imageHeight\:null,\order\:13,\createdAt\:\2025-10-19T01:47:01.522Z\,\updatedAt\:\2025-10-19T01:47:01.522Z\},{\id\:\edwpu\,\codeBlockId\:\131a5b50-1e5f-4148-8326-1b16c0ca11af\,\code\:\\u003cp\u003e這是一個大貓貓\u003c/p\u003e\\r\\n\u003cp style\\\display: block\\\\u003e大家默認都是 block\u003c/p\u003e\\r\\n\u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\\r\\n\u003cp\u003e這是一個大貓貓\u003c/p\u003e\\r\\n\u003cdiv\\r\\n style\\\\\r\\n display: flex;\\r\\n flex-direction: column-reverse; /* @cat-caption */\\r\\n background: blue;\\r\\n height: 200px;\\r\\n \\\\\r\\n\u003e\\r\\n \u003cdiv style\\\background: red; height: 30px\\\\u003e這是一個大貓貓\u003c/div\u003e\\r\\n \u003cdiv style\\\background: green; height: 90px\\\\u003e這是一個大貓貓\u003c/div\u003e\\r\\n \u003cdiv style\\\background: orange; height: 60px\\\\u003e這是一個大貓貓\u003c/div\u003e\\r\\n\u003c/div\u003e\\r\\n\u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\,\language\:\html\,\caption\:\flex-direction: column-reverse;\\n一樣是直向排列,不過前後顛倒\\n注意看紅色的地方\\n他跑去最後面了\,\imageUrl\:\/api/object/1719180878524-a4zdte.png\,\imagePosition\:\bottom-right\,\imageFullWidth\:null,\imageWidth\:600,\imageHeight\:null,\order\:14,\createdAt\:\2025-10-19T01:47:01.522Z\,\updatedAt\:\2025-10-19T01:47:01.522Z\},{\id\:\em4jc\,\codeBlockId\:\131a5b50-1e5f-4148-8326-1b16c0ca11af\,\code\:\\u003cp\u003e這是一個大貓貓\u003c/p\u003e\\r\\n\u003cp style\\\display: block\\\\u003e大家默認都是 block\u003c/p\u003e\\r\\n\u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\\r\\n\u003cp\u003e這是一個大貓貓\u003c/p\u003e\\r\\n\u003cdiv\\r\\n style\\\\\r\\n display: flex;\\r\\n flex-direction: row; /* @cat-caption */\\r\\n background: blue;\\r\\n height: 200px;\\r\\n \\\\\r\\n\u003e\\r\\n \u003cdiv style\\\background: red; height: 30px\\\\u003e這是一個大貓貓\u003c/div\u003e\\r\\n \u003cdiv style\\\background: green; height: 90px\\\\u003e這是一個大貓貓\u003c/div\u003e\\r\\n \u003cdiv style\\\background: orange; height: 60px\\\\u003e這是一個大貓貓\u003c/div\u003e\\r\\n\u003c/div\u003e\\r\\n\u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\,\language\:\html\,\caption\:\flex-direction: row;\\n可以當成默認的橫向排列\,\imageUrl\:\/api/object/1719180962231-riwqp9.png\,\imagePosition\:\bottom-right\,\imageFullWidth\:null,\imageWidth\:600,\imageHeight\:null,\order\:15,\createdAt\:\2025-10-19T01:47:01.522Z\,\updatedAt\:\2025-10-19T01:47:01.522Z\},{\id\:\7511d4\,\codeBlockId\:\131a5b50-1e5f-4148-8326-1b16c0ca11af\,\code\:\\u003cp\u003e這是一個大貓貓\u003c/p\u003e\\r\\n\u003cp style\\\display: block\\\\u003e大家默認都是 block\u003c/p\u003e\\r\\n\u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\\r\\n\u003cp\u003e這是一個大貓貓\u003c/p\u003e\\r\\n\u003cdiv\\r\\n style\\\\\r\\n display: flex;\\r\\n flex-direction: row-reverse; /* @cat-caption */\\r\\n background: blue;\\r\\n height: 200px;\\r\\n \\\\\r\\n\u003e\\r\\n \u003cdiv style\\\background: red; height: 30px\\\\u003e這是一個大貓貓\u003c/div\u003e\\r\\n \u003cdiv style\\\background: green; height: 90px\\\\u003e這是一個大貓貓\u003c/div\u003e\\r\\n \u003cdiv style\\\background: orange; height: 60px\\\\u003e這是一個大貓貓\u003c/div\u003e\\r\\n\u003c/div\u003e\\r\\n\u003cdiv\u003e這是一個大貓貓\u003c/div\u003e\,\language\:\html\,\caption\:\flex-direction: row-reverse;\\n當然也可以前後顛倒啦\\n注意看紅色的地方\\n他跑去最後面了\,\imageUrl\:\/api/object/1719181042557-bkg4ct.png\,\imagePosition\:\bottom-right\,\imageFullWidth\:null,\imageWidth\:600,\imageHeight\:null,\order\:16,\createdAt\:\2025-10-19T01:47:01.522Z\,\updatedAt\:\2025-10-19T01:47:01.522Z\},{\id\:\2t5ur\,\codeBlockId\:\131a5b50-1e5f-4148-8326-1b16c0ca11af\,\code\:\$55\,\language\:\html\,\caption\:\透過多個 flex 元素的結合\\n比如說第一個 flex 負責 x 軸的位置\\n第二個 flex 負責 y 的位置\\n可以做出很複雜的效果\,\imageUrl\:\/api/object/1719181574727-9p6qb.png\,\imagePosition\:\bottom-right\,\imageFullWidth\:null,\imageWidth\:599,\imageHeight\:null,\order\:17,\createdAt\:\2025-10-19T01:47:01.522Z\,\updatedAt\:\2025-10-19T01:47:01.522Z\}},\questionBlock\:null},{\id\:\52336ecc-15e8-4563-b2df-71a6676b2aaa\,\articleId\:\6683d763-5f17-4913-bdaa-576933e48737\,\noteBlock\:{\id\:\52336ecc-15e8-4563-b2df-71a6676b2aaa\,\content\:\### display:gird(網格)\\n\\n也是 css 數一數二重要的屬性\\n\\ngird 控制的也是元素小孩們的排列\\n\\n他可以將容器切成如同 excel 般的網格\\n\\n你可以調整每一行的高度 每一列的高度\\n\\n元素會根據你網格的設計來自動排列\\n\\n詳細內容可以參見文章\\n\\nhttps://medium.com/enjoy-life-enjoy-coding/css-%E6%89%80%E4%BB%A5%E6%88%91%E8%AA%AA%E9%82%A3%E5%80%8B%E7%89%88%E8%83%BD%E4%B8%8D%E8%83%BD%E5%A5%BD%E5%88%87%E4%B8%80%E9%BB%9E-grid-%E5%9F%BA%E6%9C%AC%E7%94%A8%E6%B3%95-cd763091cf70(https://medium.com/enjoy-life-enjoy-coding/css-%E6%89%80%E4%BB%A5%E6%88%91%E8%AA%AA%E9%82%A3%E5%80%8B%E7%89%88%E8%83%BD%E4%B8%8D%E8%83%BD%E5%A5%BD%E5%88%87%E4%B8%80%E9%BB%9E-grid-%E5%9F%BA%E6%9C%AC%E7%94%A8%E6%B3%95-cd763091cf70)\},\codeBlock\:null,\questionBlock\:null}},{\id\:\9ee7f4ed-c936-461c-90f3-15d8ad626c26\,\title\:\第一部分:傳統前端大解密 HTML\,\content\:\我將攜手帶你踏入這個網頁開發的殿堂世界\\n從零開始,一步一步解密網頁開發的三大基石:HTML 和 CSS、JavaScript。\\n從最基本的概念開始,一步一步擴展視野\\n暸解什麼是前端、後端\\n暸解如何寫出一個專屬於自己的個人網頁\\n\\n這是網頁開發的第一步,無論您是初學者還是有一定基礎的開發者,都能從中受益。\\n\\n這個是本系列的第一篇章 HTML\,\blockIndexIds\:\bee03225-0951-4830-9957-305bb5640483\,\cfdb942c-be15-450d-bbae-3bf8dd22ec5a\,\798936e8-529f-4fdb-9f59-97a8ed3f44f8\,\25387f70-b5b7-4e76-b4dd-293f7e44a9a9\,\1f4a1e34-ac39-4d4f-a5ed-6c5083e877bd\,\ba0ad077-36eb-45d9-9621-8f3f9318ff62\,\d5114f77-683c-443b-b75c-4d10634950db\,\618e7e6c-1433-4e1e-b62f-ac504b1392f0\,\e429286f-70cf-4f30-8774-5d172a6467e4\,\7eff2ebd-b3a0-428d-acd2-c34de267847e\,\slug\:null,\metaDescription\:null,\ogImage\:null,\privacy\:\PUBLIC\,\allowedEmails\:,\createdAt\:\2024-06-10T14:04:03.773Z\,\updatedAt\:\2025-11-18T21:23:10.210Z\,\authorId\:\24c3f178-4a5b-447d-b586-16d7d459c606\,\viewCount\:1703,\newsletterSentAt\:\2025-10-19T09:55:10.552Z\,\blocks\:{\id\:\bee03225-0951-4830-9957-305bb5640483\,\articleId\:\9ee7f4ed-c936-461c-90f3-15d8ad626c26\,\noteBlock\:{\id\:\bee03225-0951-4830-9957-305bb5640483\,\content\:\### About me\\n\\n\u003cimg height\\\322\\\ width\\\215\\\ src\\\/api/object/1718132244050-eq5mnk.jpg\\\ /\u003e\\n\\n* **吳睿誠(Ray)**\\n* **COSCUP 準講者**\\n* **台灣大學課程網全端工程師**\\n* **Crypto Arsenal 全端工程師**\\n* **創業家、自由工作者**\\n* **喜歡貓貓**\\n\\n### 今天的目標\\n\\n* **網頁概念**\\n * 什麼是網頁?前端後端?語言與技術?\\n* **HTML**\\n * 喝杯咖啡,這東西真的很容易,就是寫寫文字!\},\codeBlock\:null,\questionBlock\:null},{\id\:\cfdb942c-be15-450d-bbae-3bf8dd22ec5a\,\articleId\:\9ee7f4ed-c936-461c-90f3-15d8ad626c26\,\noteBlock\:null,\codeBlock\:{\id\:\cfdb942c-be15-450d-bbae-3bf8dd22ec5a\,\content\:\$56\,\chunks\:{\id\:\iij29s\,\codeBlockId\:\cfdb942c-be15-450d-bbae-3bf8dd22ec5a\,\code\:\\,\language\:\typescript\,\caption\:\我們先從最基本的概念開始\\n\\n先來理解一下 什麼是網頁\\n為啥我打開瀏覽器,就可以跑出那麼漂亮的畫面\,\imageUrl\:\/api/object/1718181894090-h3n0us.png\,\imagePosition\:\center\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:0,\createdAt\:\2025-10-19T01:47:00.464Z\,\updatedAt\:\2025-10-19T01:47:00.464Z\},{\id\:\yshnxo\,\codeBlockId\:\cfdb942c-be15-450d-bbae-3bf8dd22ec5a\,\code\:\\,\language\:\typescript\,\caption\:\什麼是網頁呢?\\n當你在瀏覽器中打開一個網址時,顯示出來漂亮的畫面就是網頁\\n\\n最早網頁只是為了方便將資料傳給別人看\\n所以將一些文字、圖片,放在自己的電腦上,然後只要別人連線到我的電腦時,我就把這些文字、圖片傳給他\,\imageUrl\:\/api/object/1718181934699-v2r9z.png\,\imagePosition\:\center\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:1,\createdAt\:\2025-10-19T01:47:00.464Z\,\updatedAt\:\2025-10-19T01:47:00.464Z\},{\id\:\f5115\,\codeBlockId\:\cfdb942c-be15-450d-bbae-3bf8dd22ec5a\,\code\:\\,\language\:\typescript\,\caption\:\我們直觀的用右邊的圖表來展示一下網頁的連線過程吧\\n\\n先有個先備知識,全球網際網路可以先想像成一個超級郵差,它可以幫你傳訊息。\\n而且大家如果想傳訊息都會找這個郵差。\\n\\n而為了方便郵差送信,所有連上網路的電腦都有一個地址像是新竹市東區光復路二段101號 之類的,讓郵差或其他人可以很好的寄信給你\\n\\n這個地址也就被稱之為 IP 地址\,\imageUrl\:\/api/object/1718182154235-s88zbw.png\,\imagePosition\:\center\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:2,\createdAt\:\2025-10-19T01:47:00.464Z\,\updatedAt\:\2025-10-19T01:47:00.464Z\},{\id\:\6lui69\,\codeBlockId\:\cfdb942c-be15-450d-bbae-3bf8dd22ec5a\,\code\:\\,\language\:\typescript\,\caption\:\今天如果你想看 youtube,你會在瀏覽器當中輸入 youtube.com 這個網域\\n\\n網域?其實就是地址太難記啦!所以郵差開放大家註冊自己的縮寫\\n好比說 新竹市東區光復路二段101號 的縮寫就是 清華大學\,\imageUrl\:\/api/object/1718182103128-ruqfvm.png\,\imagePosition\:\center\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:3,\createdAt\:\2025-10-19T01:47:00.464Z\,\updatedAt\:\2025-10-19T01:47:00.464Z\},{\id\:\bhojto\,\codeBlockId\:\cfdb942c-be15-450d-bbae-3bf8dd22ec5a\,\code\:\\,\language\:\typescript\,\caption\:\回到剛剛的 youtube,當你想看 youtube 時\\n瀏覽器會幫妳寫好一封信,寄給 youtube.com\\n內容就是希望能請求取得網頁內容\\n然後寄給這個郵差,請他幫忙轉交給 youtube\,\imageUrl\:\/api/object/1718182695904-d8zzee.gif\,\imagePosition\:\center\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:4,\createdAt\:\2025-10-19T01:47:00.464Z\,\updatedAt\:\2025-10-19T01:47:00.464Z\},{\id\:\eenl2\,\codeBlockId\:\cfdb942c-be15-450d-bbae-3bf8dd22ec5a\,\code\:\\,\language\:\typescript\,\caption\:\當郵差收到這封要寄的信,看了一眼 youtube.com\\n他會去查之前跟他註冊的網域清單,然後就能知道\\n youtube.com 的真實 IP 142.250.65.206\\n然後就會轉寄給 Youtube 的電腦\,\imageUrl\:\/api/object/1718182963372-2g7e4u.gif\,\imagePosition\:\center\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:5,\createdAt\:\2025-10-19T01:47:00.464Z\,\updatedAt\:\2025-10-19T01:47:00.464Z\},{\id\:\tbg70m\,\codeBlockId\:\cfdb942c-be15-450d-bbae-3bf8dd22ec5a\,\code\:\\,\language\:\typescript\,\caption\:\Youtube 從郵差那邊收到信以後,馬上就寫了一封回信,將整個網頁的內容放進去\\n然後再請郵差回傳給你\,\imageUrl\:\/api/object/1718183254318-pij9uf.gif\,\imagePosition\:\center\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:6,\createdAt\:\2025-10-19T01:47:00.464Z\,\updatedAt\:\2025-10-19T01:47:00.464Z\},{\id\:\mcxkv\,\codeBlockId\:\cfdb942c-be15-450d-bbae-3bf8dd22ec5a\,\code\:\\,\language\:\typescript\,\caption\:\最後你收到了回信,回信的內容就是網頁啦~\\n你就可以看到漂亮的網站了\\n\\n值得一提的是,剛剛的整個過程時間不到 0.1 秒\\n\\n那問題來了,回信的內容是網頁,那到底是什麼?\\n一大坨的文字嗎?還是?\\n這就是我們今天要學的東西的 網頁程式開發\,\imageUrl\:\/api/object/1718183391840-4anxn.gif\,\imagePosition\:\center\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:7,\createdAt\:\2025-10-19T01:47:00.464Z\,\updatedAt\:\2025-10-19T01:47:00.464Z\}},\questionBlock\:null},{\id\:\798936e8-529f-4fdb-9f59-97a8ed3f44f8\,\articleId\:\9ee7f4ed-c936-461c-90f3-15d8ad626c26\,\noteBlock\:null,\codeBlock\:null,\questionBlock\:{\id\:\798936e8-529f-4fdb-9f59-97a8ed3f44f8\,\question\:\快問快答 - 請問我應該如何連上 Ray 的個人網站?\,\options\:\A、打開瀏覽器,並輸入 https://ray-realms.com/ 網址,按下 Enter\,\B、打開瀏覽器,並輸入 https://www.google.com/ 網址,按下 Enter\,\C、用念力默念芝麻開網站\,\D、放聲大哭,把媽媽吵過來\,\answer\:\A、打開瀏覽器,並輸入 https://ray-realms.com/ 網址,按下 Enter\}},{\id\:\25387f70-b5b7-4e76-b4dd-293f7e44a9a9\,\articleId\:\9ee7f4ed-c936-461c-90f3-15d8ad626c26\,\noteBlock\:null,\codeBlock\:{\id\:\25387f70-b5b7-4e76-b4dd-293f7e44a9a9\,\content\:\{\\\id\\\:\\\g5kad4\\\,\\\code\\\:\\\\\\,\\\caption\\\:\\\收到網頁的資料了!可是有什麼資料?\\\\n\\\\n在任何的網頁中,你都可以按 F12 去察看網頁真實的資料\\\\n你甚至可以修改他\\\\n\\\\n(不過你修改的只是你所收到的回信而已\\\,\\\language\\\:\\\typescript\\\,\\\image\\\:{\\\url\\\:\\\/api/object/1718186625194-045q1v9.png\\\,\\\position\\\:\\\center\\\,\\\fullWidth\\\:true,\\\width\\\:200}},{\\\id\\\:\\\litwlf\\\,\\\code\\\:\\\\\\,\\\caption\\\:\\\所有的網頁都只由這三種語言所構成\\\\n- HTML 司掌架構、定義了網頁的內容\\\\n- CSS 司掌美術、讓網頁變得好看\\\\n- JavaScript 司掌邏輯、小從數學運算、大致邏輯操作\\\\n\\\\n而我們剛剛 F12 第一眼看到的便是他的 HTML\\\,\\\language\\\:\\\typescript\\\,\\\image\\\:{\\\url\\\:\\\/api/object/1718186659667-de8hrf.png\\\,\\\position\\\:\\\center\\\,\\\fullWidth\\\:true,\\\width\\\:200}}\,\chunks\:{\id\:\g5kad4\,\codeBlockId\:\25387f70-b5b7-4e76-b4dd-293f7e44a9a9\,\code\:\\,\language\:\typescript\,\caption\:\收到網頁的資料了!可是有什麼資料?\\n\\n在任何的網頁中,你都可以按 F12 去察看網頁真實的資料\\n你甚至可以修改他\\n\\n(不過你修改的只是你所收到的回信而已\,\imageUrl\:\/api/object/1718186625194-045q1v9.png\,\imagePosition\:\center\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:0,\createdAt\:\2025-10-19T01:46:59.340Z\,\updatedAt\:\2025-10-19T01:46:59.340Z\},{\id\:\litwlf\,\codeBlockId\:\25387f70-b5b7-4e76-b4dd-293f7e44a9a9\,\code\:\\,\language\:\typescript\,\caption\:\所有的網頁都只由這三種語言所構成\\n- HTML 司掌架構、定義了網頁的內容\\n- CSS 司掌美術、讓網頁變得好看\\n- JavaScript 司掌邏輯、小從數學運算、大致邏輯操作\\n\\n而我們剛剛 F12 第一眼看到的便是他的 HTML\,\imageUrl\:\/api/object/1718186659667-de8hrf.png\,\imagePosition\:\center\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:1,\createdAt\:\2025-10-19T01:46:59.340Z\,\updatedAt\:\2025-10-19T01:46:59.340Z\}},\questionBlock\:null},{\id\:\1f4a1e34-ac39-4d4f-a5ed-6c5083e877bd\,\articleId\:\9ee7f4ed-c936-461c-90f3-15d8ad626c26\,\noteBlock\:null,\codeBlock\:null,\questionBlock\:{\id\:\1f4a1e34-ac39-4d4f-a5ed-6c5083e877bd\,\question\:\快問快答 - 透過網頁 F12 開發人員工具,編輯 HTML,我可以輕易竄改任何聊天資訊\,\options\:\A、可以啊,謝謝老師教我駭進 Facebook 的網頁\,\B、完全沒有辦法\,\C、可以,但是只能自嗨,重新整理後一切正常\,\answer\:\C、可以,但是只能自嗨,重新整理後一切正常\}},{\id\:\ba0ad077-36eb-45d9-9621-8f3f9318ff62\,\articleId\:\9ee7f4ed-c936-461c-90f3-15d8ad626c26\,\noteBlock\:null,\codeBlock\:{\id\:\ba0ad077-36eb-45d9-9621-8f3f9318ff62\,\content\:\$57\,\chunks\:{\id\:\vgc26v\,\codeBlockId\:\ba0ad077-36eb-45d9-9621-8f3f9318ff62\,\code\:\\,\language\:\typescript\,\caption\:\HTML\\n喝杯咖啡,這東西真的很容易,就是寫寫文字!\,\imageUrl\:\/api/object/1718186922910-k6b8ka.png\,\imagePosition\:\center\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:0,\createdAt\:\2025-10-19T01:46:51.301Z\,\updatedAt\:\2025-10-19T01:46:51.301Z\},{\id\:\ifdt4n\,\codeBlockId\:\ba0ad077-36eb-45d9-9621-8f3f9318ff62\,\code\:\\,\language\:\typescript\,\caption\:\我們將用 Visual Studio Code ( VSCode ) 這個文字編輯器來進行開發\\n請各位安裝 Live Server 這個 VSCode 內的延伸模組\\n可以提高開發效率\,\imageUrl\:\/api/object/1718191696805-j0weaa.png\,\imagePosition\:\center\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:1,\createdAt\:\2025-10-19T01:46:51.301Z\,\updatedAt\:\2025-10-19T01:46:51.301Z\},{\id\:\czjcx9\,\codeBlockId\:\ba0ad077-36eb-45d9-9621-8f3f9318ff62\,\code\:\\,\language\:\typescript\,\caption\:\創建一個新的資料夾,來放置我們的網站程式碼\\n\,\imageUrl\:\/api/object/1718192974718-kngyn4.png\,\imagePosition\:\center\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:2,\createdAt\:\2025-10-19T01:46:51.301Z\,\updatedAt\:\2025-10-19T01:46:51.301Z\},{\id\:\ah6ias\,\codeBlockId\:\ba0ad077-36eb-45d9-9621-8f3f9318ff62\,\code\:\\,\language\:\typescript\,\caption\:\將這個資料夾拉入 VSCode 中\,\imageUrl\:\/api/object/1718193187287-nld86f.png\,\imagePosition\:\center\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:3,\createdAt\:\2025-10-19T01:46:51.301Z\,\updatedAt\:\2025-10-19T01:46:51.301Z\},{\id\:\jj2qv\,\codeBlockId\:\ba0ad077-36eb-45d9-9621-8f3f9318ff62\,\code\:\\,\language\:\typescript\,\caption\:\透過 VSCode 創建一個檔案\\n命名為 index.html\,\imageUrl\:\/api/object/1718193212165-d8jl8u.png\,\imagePosition\:\center\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:4,\createdAt\:\2025-10-19T01:46:51.301Z\,\updatedAt\:\2025-10-19T01:46:51.301Z\},{\id\:\j6qqc\,\codeBlockId\:\ba0ad077-36eb-45d9-9621-8f3f9318ff62\,\code\:\\,\language\:\typescript\,\caption\:\點開這個檔案,輸入 !\,\imageUrl\:\/api/object/1718193233962-w8e03.png\,\imagePosition\:\center\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:5,\createdAt\:\2025-10-19T01:46:51.301Z\,\updatedAt\:\2025-10-19T01:46:51.301Z\},{\id\:\i0jtf\,\codeBlockId\:\ba0ad077-36eb-45d9-9621-8f3f9318ff62\,\code\:\\,\language\:\typescript\,\caption\:\按下 enter,能直接生成一整份 HTML 範本\,\imageUrl\:\/api/object/1718193255384-gc3389.png\,\imagePosition\:\center\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:6,\createdAt\:\2025-10-19T01:46:51.301Z\,\updatedAt\:\2025-10-19T01:46:51.301Z\},{\id\:\3ybwo8\,\codeBlockId\:\ba0ad077-36eb-45d9-9621-8f3f9318ff62\,\code\:\\,\language\:\typescript\,\caption\:\點擊右下角的 Go Live\\n能即時預覽這個 Html 實際的網頁長什麼樣子\,\imageUrl\:\/api/object/1718193282288-o22wq.png\,\imagePosition\:\center\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:7,\createdAt\:\2025-10-19T01:46:51.301Z\,\updatedAt\:\2025-10-19T01:46:51.301Z\},{\id\:\7jzl0b\,\codeBlockId\:\ba0ad077-36eb-45d9-9621-8f3f9318ff62\,\code\:\\,\language\:\typescript\,\caption\:\在網頁中,元素就像是組成房子的磚塊。\\n\\n每個元素代表一個網頁上的部分,比如一段文字、一張圖片或一個按鈕。\\n\\n這些元素透過 HTML 標籤來表示,每個標籤都有不同的功能,讓我們可以有條理地建構和設計網頁。\\n\\n換句話說,元素是網頁內容的基本單位,所有的內容都是由這些元素組成的。\\n\\n另外大多數的元素都會有 開頭 與 結尾 包起來,才能成為一個真正的元素\,\imageUrl\:\/api/object/1718193664228-il8ioh.png\,\imagePosition\:\center\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:8,\createdAt\:\2025-10-19T01:46:51.301Z\,\updatedAt\:\2025-10-19T01:46:51.301Z\},{\id\:\2skw49\,\codeBlockId\:\ba0ad077-36eb-45d9-9621-8f3f9318ff62\,\code\:\\,\language\:\typescript\,\caption\:\單純的一塊磚頭,沒辦法表達太多東西\\n所以我們替他加上了【屬性】\\n\\n屬性就像是為這些磚塊添加的裝飾或說明。\\n它們提供額外的訊息或修改元素的外觀和行為。\\n\\nAttributes 永遠都放在開頭標籤\\n\\n例如,連結元素有一個 href 告訴網頁點擊這個連結後實際要跳轉到哪裡\\n\\n屬性幫助我們更細緻地控制和定義元素,讓網頁變得更加豐富和有趣。\,\imageUrl\:\/api/object/1718194715329-ky2gq.png\,\imagePosition\:\center\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:9,\createdAt\:\2025-10-19T01:46:51.301Z\,\updatedAt\:\2025-10-19T01:46:51.301Z\}},\questionBlock\:null},{\id\:\d5114f77-683c-443b-b75c-4d10634950db\,\articleId\:\9ee7f4ed-c936-461c-90f3-15d8ad626c26\,\noteBlock\:null,\codeBlock\:null,\questionBlock\:{\id\:\d5114f77-683c-443b-b75c-4d10634950db\,\question\:\快問快答 - 請問以下 HTML 是否符合格式 \u003ca\u003e Ray 的個人官網 \u003c/a href\\\https://ray-realms.com/\\\\u003e\,\options\:\A、是,符合格式\,\B、否,屬性不是只能放在開頭標籤嗎?\,\C、很明顯他處在一個既對又錯的不穩定態中\,\answer\:\B、否,屬性不是只能放在開頭標籤嗎?\}},{\id\:\618e7e6c-1433-4e1e-b62f-ac504b1392f0\,\articleId\:\9ee7f4ed-c936-461c-90f3-15d8ad626c26\,\noteBlock\:null,\codeBlock\:{\id\:\618e7e6c-1433-4e1e-b62f-ac504b1392f0\,\content\:\$58\,\chunks\:{\id\:\fa2sk\,\codeBlockId\:\618e7e6c-1433-4e1e-b62f-ac504b1392f0\,\code\:\\u003ch1\u003e超大標題字\u003c/h1\u003e\\r\\n\u003ch2\u003e大標題字\u003c/h2\u003e\\r\\n\u003ch3\u003e普通標題字\u003c/h3\u003e\\r\\n\u003ch4\u003e小標題字\u003c/h4\u003e\\r\\n\u003ch5\u003e更小標題字\u003c/h5\u003e\\r\\n\u003ch6\u003e超級小標題字\u003c/h6\u003e\,\language\:\html\,\caption\:\標題,顧名思義就是標題字\\n特別大、特別顯眼\\n能很好的分級和組織網頁內容\\n根據重要性,有 h1 ~ h6 六種標題\,\imageUrl\:\/api/object/1718209865121-p7w9r.png\,\imagePosition\:\bottom-right\,\imageFullWidth\:null,\imageWidth\:200,\imageHeight\:null,\order\:0,\createdAt\:\2025-10-19T01:46:54.450Z\,\updatedAt\:\2025-10-19T01:46:54.450Z\},{\id\:\jrzr8a\,\codeBlockId\:\618e7e6c-1433-4e1e-b62f-ac504b1392f0\,\code\:\/* @cat-caption */\u003cp\u003e段落,會直接開始於新的一行。通常是用來放一段文字\u003c/p\u003e\,\language\:\html\,\caption\:\段落(Paragraphs)\\n\\n段落用來放一段文字,使用 \u003cp\u003e 標籤。\,\imageUrl\:\/api/object/1718218024798-ebg22g.png\,\imagePosition\:\bottom-right\,\imageFullWidth\:null,\imageWidth\:500,\imageHeight\:null,\order\:1,\createdAt\:\2025-10-19T01:46:54.450Z\,\updatedAt\:\2025-10-19T01:46:54.450Z\},{\id\:\8a7dd\,\codeBlockId\:\618e7e6c-1433-4e1e-b62f-ac504b1392f0\,\code\:\\u003cp\u003e段落,會直接開始於新的一行。通常是用來放一段文字\u003c/p\u003e\\r\\n/* @cat-caption */\u003cp\u003e段落內有很多空白,也只會被視為一個空白的,你必須透過關鍵字 \u0026nbsp; 才能放置多個空白\u003c/p\u003e\,\language\:\html\,\caption\:\段落(Paragraphs)\\n\\n需要注意的是,HTML 中段落內的空白不會顯示,可以使用 \u0026nbsp; 關鍵字來創建多個空白。\,\imageUrl\:\/api/object/1718218061296-a5uz3h.png\,\imagePosition\:\bottom-right\,\imageFullWidth\:null,\imageWidth\:500,\imageHeight\:null,\order\:2,\createdAt\:\2025-10-19T01:46:54.450Z\,\updatedAt\:\2025-10-19T01:46:54.450Z\},{\id\:\8oo5dr\,\codeBlockId\:\618e7e6c-1433-4e1e-b62f-ac504b1392f0\,\code\:\\u003cp\u003e段落,會直接開始於新的一行。通常是用來放一段文字\u003c/p\u003e\\r\\n\u003cp\u003e段落內有很多空白,也只會被視為一個空白的,你必須透過關鍵字 \u0026nbsp; 才能放置多個空白\u003c/p\u003e\\r\\n/* @cat-caption */\u003cp\u003e\\r\\n/* @cat-caption */ 換行在段落中是沒有用的,你可以透過 br 標籤\u003cbr/\u003e\\r\\n/* @cat-caption */ 來創建換行,\u003cbr/\\r\\n/* @cat-caption */ \u003e因為功能單純,可以將開始標籤與結束標籤省略為同個東西\\r\\n/* @cat-caption */\u003c/p\u003e\,\language\:\html\,\caption\:\段落(Paragraphs)\\n\\nHTML 中段落內的換行不會顯示,可以使用 \u003cbr\u003e 標籤來添加換行效果。\,\imageUrl\:\/api/object/1718218108772-5rqtk.png\,\imagePosition\:\bottom-right\,\imageFullWidth\:null,\imageWidth\:500,\imageHeight\:null,\order\:3,\createdAt\:\2025-10-19T01:46:54.450Z\,\updatedAt\:\2025-10-19T01:46:54.450Z\},{\id\:\6022f9\,\codeBlockId\:\618e7e6c-1433-4e1e-b62f-ac504b1392f0\,\code\:\\,\language\:\html\,\caption\:\為了撰寫複雜的文件格式\\n所以有很多專用的元素,來做類似的效果\\n你可以想像成用文字的方法,操作 word\\n撰寫文章\,\imageUrl\:\/api/object/1718218307164-mm055.png\,\imagePosition\:\center\,\imageFullWidth\:true,\imageWidth\:200,\imageHeight\:null,\order\:4,\createdAt\:\2025-10-19T01:46:54.450Z\,\updatedAt\:\2025-10-19T01:46:54.450Z\},{\id\:\qw85pc\,\codeBlockId\:\618e7e6c-1433-4e1e-b62f-ac504b1392f0\,\code\:\/* @cat-caption */\u003cem\u003e可愛的貓貓\u003c/em\u003e\,\language\:\html\,\caption\:\有意思的是,多個元素包再一起時\\n效果是會疊加的喔\,\imageUrl\:\/api/object/1718218410050-cqstpd.png\,\imagePosition\:\top-right\,\imageFullWidth\:null,\imageWidth\:200,\imageHeight\:null,\order\:5,\createdAt\:\2025-10-19T01:46:54.450Z\,\updatedAt\:\2025-10-19T01:46:54.450Z\},{\id\:\ul7pi6\,\codeBlockId\:\618e7e6c-1433-4e1e-b62f-ac504b1392f0\,\code\:\/* @cat-caption */\u003cstrong\u003e\\r\\n \u003cem\u003e可愛的貓貓\u003c/em\u003e\\r\\n/* @cat-caption */\u003c/strong\u003e\,\language\:\html\,\caption\:\我們替他加上粗體字效果\\n\\n斜體與粗體被疊加了!\,\imageUrl\:\/api/object/1718218470754-awa7ju.png\,\imagePosition\:\top-right\,\imageFullWidth\:null,\imageWidth\:200,\imageHeight\:null,\order\:6,\createdAt\:\2025-10-19T01:46:54.450Z\,\updatedAt\:\2025-10-19T01:46:54.450Z\},{\id\:\0d5fc\,\codeBlockId\:\618e7e6c-1433-4e1e-b62f-ac504b1392f0\,\code\:\/* @cat-caption */\u003cdel\u003e\\r\\n \u003cstrong\u003e\\r\\n \u003cem\u003e可愛的貓貓\u003c/em\u003e\\r\\n \u003c/strong\u003e\\r\\n/* @cat-caption */\u003c/del\u003e\,\language\:\html\,\caption\:\再疊加上一個刪除符號\,\imageUrl\:\/api/object/1718218560231-fyshz.png\,\imagePosition\:\top-right\,\imageFullWidth\:null,\imageWidth\:200,\imageHeight\:null,\order\:7,\createdAt\:\2025-10-19T01:46:54.450Z\,\updatedAt\:\2025-10-19T01:46:54.450Z\},{\id\:\tuxqti\,\codeBlockId\:\618e7e6c-1433-4e1e-b62f-ac504b1392f0\,\code\:\\u003cimg \\r\\n src\\\https://ray-realms-blog.zeabur.app/api/object/1718132244050-eq5mnk.jpg\\\\\r\\n/\u003e\,\language\:\html\,\caption\:\Image (img)\\n在網頁當中放置圖片\\n你可以透過 src 指定圖片網址\\n也一樣因為 img 功能單純,可以將開始標籤與結束標籤省略為同個東西\\n\,\imageUrl\:\/api/object/1718219918020-ydrjhd.png\,\imagePosition\:\bottom-right\,\imageFullWidth\:null,\imageWidth\:200,\imageHeight\:null,\order\:8,\createdAt\:\2025-10-19T01:46:54.450Z\,\updatedAt\:\2025-10-19T01:46:54.450Z\},{\id\:\y7amkg\,\codeBlockId\:\618e7e6c-1433-4e1e-b62f-ac504b1392f0\,\code\:\\u003cimg\\r\\n src\\\https://ray-realms-blog.zeabur.app/api/object/1718132244050-eq5mnk.jpg\\\\\r\\n /* @cat-caption */width\\\300px\\\\\r\\n /* @cat-caption */height\\\600px\\\\\r\\n/\u003e\,\language\:\html\,\caption\:\Image (img)\\n\\n你可以透過 width 與 height 指定圖片寬度與高度\,\imageUrl\:\/api/object/1718219897501-z6bq.png\,\imagePosition\:\bottom-right\,\imageFullWidth\:null,\imageWidth\:200,\imageHeight\:null,\order\:9,\createdAt\:\2025-10-19T01:46:54.450Z\,\updatedAt\:\2025-10-19T01:46:54.450Z\},{\id\:\bl1a7q\,\codeBlockId\:\618e7e6c-1433-4e1e-b62f-ac504b1392f0\,\code\:\\u003cimg\\r\\n src\\\https://ray-realms-blog.zeabur.app/api/object/1718132244050-eq5mnk.jpg\\\\\r\\n width\\\300px\\\\\r\\n height\\\600px\\\\\r\\n /* @cat-caption */alt\\\描述圖片的替代文字\\\\\r\\n/\u003e\,\language\:\html\,\caption\:\Image (img)\\n\\n另外 alt 屬性可以寫下圖片的替代文字\\n當圖片載入失敗時、或者有視覺障礙的人聆聽時便會顯示這段文字\,\imageUrl\:\/api/object/1718220644224-92m5td.png\,\imagePosition\:\bottom-right\,\imageFullWidth\:null,\imageWidth\:200,\imageHeight\:null,\order\:10,\createdAt\:\2025-10-19T01:46:54.450Z\,\updatedAt\:\2025-10-19T01:46:54.450Z\},{\id\:\7ysw2\,\codeBlockId\:\618e7e6c-1433-4e1e-b62f-ac504b1392f0\,\code\:\/* @cat-caption */\u003ca href\\\https://ray-realms.com/\\\\u003e這是一個點了會挑轉到 Ray 個人網站的連結\u003c/a\u003e\,\language\:\html\,\caption\:\Links(連結)\\n\\n連結用來導向其他網頁或資源,使用 \u003ca\u003e 標籤。href 屬性指定目標網址。\,\imageUrl\:\/api/object/1718220683824-ia908m.png\,\imagePosition\:\bottom-right\,\imageFullWidth\:null,\imageWidth\:500,\imageHeight\:null,\order\:11,\createdAt\:\2025-10-19T01:46:54.450Z\,\updatedAt\:\2025-10-19T01:46:54.450Z\},{\id\:\4lot1nn\,\codeBlockId\:\618e7e6c-1433-4e1e-b62f-ac504b1392f0\,\code\:\\u003cdiv\u003e\\r\\n \u003ch3\u003e區塊一標題\u003c/h3\u003e\\r\\n \u003cp\u003e區塊通常是方便美術排版的\u003c/p\u003e\\r\\n\u003c/div\u003e\\r\\n\\r\\n\u003cdiv\u003e\\r\\n \u003ch3\u003e區塊二標題\u003c/h3\u003e\\r\\n \u003cp\u003e區塊通常是方便程式碼的分類\u003c/p\u003e\\r\\n\u003c/div\u003e\,\language\:\html\,\caption\:\Div(區塊)\\n\\n區塊的概念很簡單,就是用來分類程式碼或方便美術排版的\,\imageUrl\:\/api/object/1718220707202-mpd2c9.png\,\imagePosition\:\bottom-right\,\imageFullWidth\:null,\imageWidth\:400,\imageHeight\:null,\order\:12,\createdAt\:\2025-10-19T01:46:54.450Z\,\updatedAt\:\2025-10-19T01:46:54.450Z\},{\id\:\0xpj6i\,\codeBlockId\:\618e7e6c-1433-4e1e-b62f-ac504b1392f0\,\code\:\\u003ch3\u003e 這是一個被無視的故事\u003c/h3\u003e\\r\\n\u003c!-- 我被無視了 QAQ --\u003e\\r\\n\u003cp\u003e\\r\\n 任何放在註解中的文字,都不會顯示在網頁上\u003cbr\u003e\\r\\n 註解只是給開發者看的文字\\r\\n\u003c/p\u003e\,\language\:\html\,\caption\:\Comments(註解)\\n\\n\u003c!-- 註解文字,並不會被顯示出來,快捷鍵 ctrl + / --\u003e\,\imageUrl\:\/api/object/1718220733816-d3jgyc.png\,\imagePosition\:\bottom-right\,\imageFullWidth\:null,\imageWidth\:400,\imageHeight\:null,\order\:13,\createdAt\:\2025-10-19T01:46:54.450Z\,\updatedAt\:\2025-10-19T01:46:54.450Z\}},\questionBlock\:null},{\id\:\e429286f-70cf-4f30-8774-5d172a6467e4\,\articleId\:\9ee7f4ed-c936-461c-90f3-15d8ad626c26\,\noteBlock\:{\id\:\e429286f-70cf-4f30-8774-5d172a6467e4\,\content\:\### 挑戰題\\n\\n嘗試運用標題字、段落、粗體字或其他格式\\n\\n來撰寫一小段文字\\n\\n(以下是範本,想不到寫什麼可以照抄)\\n\\n### **這是一個貓貓的故事**\\n\\n從前有一隻貓貓,他叫**燒賣**\\n\\n他住在北科旁邊的公園旁邊,*大家有空要去看看他喔!*\},\codeBlock\:null,\questionBlock\:null},{\id\:\7eff2ebd-b3a0-428d-acd2-c34de267847e\,\articleId\:\9ee7f4ed-c936-461c-90f3-15d8ad626c26\,\noteBlock\:null,\codeBlock\:{\id\:\7eff2ebd-b3a0-428d-acd2-c34de267847e\,\content\:\{\\\id\\\:\\\8cb9fo\\\,\\\code\\\:\\\/* @cat-caption */\u003ch1\u003e\\\\r\\\\n/* @cat-caption */ 這是一個貓貓的故事\\\\r\\\\n/* @cat-caption */\u003c/h1\u003e\\\,\\\caption\\\:\\\挑戰題參考答案\\\\n大標題\\\,\\\language\\\:\\\html\\\,\\\image\\\:{\\\url\\\:\\\/api/object/1718220874599-x3c5t8.png\\\,\\\position\\\:\\\bottom-right\\\,\\\width\\\:500}},{\\\id\\\:\\\8o1kz\\\,\\\code\\\:\\\\u003ch1\u003e\\\\r\\\\n 這是一個貓貓的故事\\\\r\\\\n\u003c/h1\u003e\\\\r\\\\n\\\\r\\\\n/* @cat-caption */\u003cp\u003e\\\\r\\\\n/* @cat-caption */ 從前有一隻貓貓,他叫\u003cstrong\u003e燒賣\u003c/strong\u003e\u003cbr/\u003e\\\\r\\\\n/* @cat-caption */ 他住在北科旁邊的公園旁邊,\u003cem\u003e大家有空要去看看他喔!\u003c/em\u003e\\\\r\\\\n/* @cat-caption */\u003c/p\u003e\\\,\\\caption\\\:\\\內文\\\,\\\language\\\:\\\html\\\,\\\image\\\:{\\\url\\\:\\\/api/object/1718220561467-6ilmt.png\\\,\\\position\\\:\\\bottom-right\\\,\\\width\\\:500}}\,\chunks\:{\id\:\8cb9fo\,\codeBlockId\:\7eff2ebd-b3a0-428d-acd2-c34de267847e\,\code\:\/* @cat-caption */\u003ch1\u003e\\r\\n/* @cat-caption */ 這是一個貓貓的故事\\r\\n/* @cat-caption */\u003c/h1\u003e\,\language\:\html\,\caption\:\挑戰題參考答案\\n大標題\,\imageUrl\:\/api/object/1718220874599-x3c5t8.png\,\imagePosition\:\bottom-right\,\imageFullWidth\:null,\imageWidth\:500,\imageHeight\:null,\order\:0,\createdAt\:\2025-10-19T01:46:55.388Z\,\updatedAt\:\2025-10-19T01:46:55.388Z\},{\id\:\8o1kz\,\codeBlockId\:\7eff2ebd-b3a0-428d-acd2-c34de267847e\,\code\:\\u003ch1\u003e\\r\\n 這是一個貓貓的故事\\r\\n\u003c/h1\u003e\\r\\n\\r\\n/* @cat-caption */\u003cp\u003e\\r\\n/* @cat-caption */ 從前有一隻貓貓,他叫\u003cstrong\u003e燒賣\u003c/strong\u003e\u003cbr/\u003e\\r\\n/* @cat-caption */ 他住在北科旁邊的公園旁邊,\u003cem\u003e大家有空要去看看他喔!\u003c/em\u003e\\r\\n/* @cat-caption */\u003c/p\u003e\,\language\:\html\,\caption\:\內文\,\imageUrl\:\/api/object/1718220561467-6ilmt.png\,\imagePosition\:\bottom-right\,\imageFullWidth\:null,\imageWidth\:500,\imageHeight\:null,\order\:1,\createdAt\:\2025-10-19T01:46:55.388Z\,\updatedAt\:\2025-10-19T01:46:55.388Z\}},\questionBlock\:null}}}\n)/script>script>self.__next_f.push(1,19:\$\,\$L59\,null,{}\n1a:\$\,\$L5a\,null,{}\n)/script>/body>/html>
View on OTX
|
View on ThreatMiner
Please enable JavaScript to view the
comments powered by Disqus.
Data with thanks to
AlienVault OTX
,
VirusTotal
,
Malwr
and
others
. [
Sitemap
]