[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"blog-categories":3,"blog-category-vue":78},[4,10,14,19,24,30,36,40,45,51,55,58,62,68,73],{"id":5,"databaseId":6,"name":7,"slug":8,"count":9},"dGVybTo1NQ==",55,"AI","ai",2,{"id":11,"databaseId":12,"name":13,"slug":13,"count":9},"dGVybTo2MQ==",61,"backend",{"id":15,"databaseId":16,"name":17,"slug":18,"count":16},"dGVybToyMA==",20,"Devops","devops",{"id":20,"databaseId":21,"name":22,"slug":22,"count":23},"dGVybToxNQ==",15,"frontend",25,{"id":25,"databaseId":26,"name":27,"slug":28,"count":29},"dGVybTo2MA==",60,"GAS","gas",1,{"id":31,"databaseId":32,"name":33,"slug":34,"count":35},"dGVybTo5",9,"HTML","html",4,{"id":37,"databaseId":38,"name":39,"slug":39,"count":29},"dGVybToyMg==",22,"mac",{"id":41,"databaseId":42,"name":43,"slug":43,"count":44},"dGVybTo2",6,"node",18,{"id":46,"databaseId":47,"name":48,"slug":49,"count":50},"dGVybTo2OQ==",69,"Nuxt","nuxt",3,{"id":52,"databaseId":53,"name":54,"slug":54,"count":50},"dGVybTo1NA==",54,"office",{"id":56,"databaseId":9,"name":57,"slug":57,"count":21},"dGVybToy","omekas",{"id":59,"databaseId":60,"name":61,"slug":61,"count":16},"dGVybToxNg==",16,"vue",{"id":63,"databaseId":64,"name":65,"slug":66,"count":67},"dGVybTo1",5,"WordPress","wordpress",13,{"id":69,"databaseId":29,"name":70,"slug":71,"count":72},"dGVybTox","無","uncategorized",null,{"id":74,"databaseId":75,"name":76,"slug":77,"count":9},"dGVybTo3MA==",70,"生活","life",[79,93,104,116,129,141,154,166,180,193,205,217,230,242,255,267,279,293,305,317],{"id":80,"slug":81,"title":82,"description":83,"content":84,"date":85,"image":86,"imageAlt":82,"author":87,"categories":90,"path":92,"minRead":29},3738,"vue3-建立輸入自動完成autocomplete","Vue3 建立輸入自動完成(autocomplete)","Vue 建立輸入自動完成(autocomplete) 參考資料：https:\u002F\u002Fstevencotterill […]","\u003Ch1>Vue 建立輸入自動完成(autocomplete)\u003C\u002Fh1>\n\u003Cp>參考資料：\u003Ca href=\"https:\u002F\u002Fstevencotterill.com\u002Farticles\u002Fhow-to-build-an-autocomplete-field-with-vue-3\">https:\u002F\u002Fstevencotterill.com\u002Farticles\u002Fhow-to-build-an-autocomplete-field-with-vue-3\u003C\u002Fa>\u003C\u002Fp>\n\u003Cp>紀錄在 Vue3 中建立自動完成(autocomplete)功能的基礎作法。我先假設是一個關於不同國家的輸入欄。\u003C\u002Fp>\n\u003Ch2>準備國家資料\u003C\u002Fh2>\n\u003Cp>首先，我們需要一個國家名稱的數。在這個例子中，我們將使用一個靜態的 JSON 文件 countries.json，其中包含了一系列國家名稱：\u003Cstrong>countries.json\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-json\">[\n  {&quot;name&quot;: &quot;Albania&quot;},\n  {&quot;name&quot;: &quot;Åland Islands&quot;},\n  {&quot;name&quot;: &quot;Algeria&quot;},\n  {&quot;name&quot;: &quot;American Samoa&quot;},\n  {&quot;name&quot;: &quot;Andorra&quot;},\n  {&quot;name&quot;: &quot;Angola&quot;},\n  {&quot;name&quot;: &quot;Anguilla&quot;},\n  {&quot;name&quot;: &quot;Antarctica&quot;},\n  {&quot;name&quot;: &quot;Antigua and Barbuda&quot;},\n  {&quot;name&quot;: &quot;Argentina&quot;},\n  {&quot;name&quot;: &quot;Armenia&quot;},\n  {&quot;name&quot;: &quot;Aruba&quot;},\n  {&quot;name&quot;: &quot;Australia&quot;},\n  {&quot;name&quot;: &quot;Austria&quot;},\n]\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2>建立輸入元件\u003C\u002Fh2>\n\u003Cp>接著，我們在 Vue 組件中建立一個輸入框，可以取得使用者輸入\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-html\">&lt;input\n  type=&quot;text&quot;\n  id=&quot;search&quot;\n  v-model=&quot;searchTerm&quot;\n  placeholder=&quot;Type here...&quot;\n  class=&quot;p-3 mb-0.5 w-full border border-gray-300 rounded&quot;\n>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2>實現自動完成下拉菜單\u003C\u002Fh2>\n\u003Cp>當使用者開始輸入時，我們希望顯示一個下拉選單列出文件中的國家名稱。\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-html\">&lt;ul\n  v-if=&quot;searchCountries.length&quot;\n  class=&quot;w-full rounded bg-white border border-gray-300 px-4 py-2 space-y-1 absolute z-10&quot;\n>\n  &lt;li class=&quot;px-1 pt-1 pb-2 font-bold border-b border-gray-200&quot;&gt;\n    Showing {{ searchCountries.length }} of {{ countries.length }} results\n  &lt;\u002Fli&gt;\n  &lt;li\n    v-for=&quot;country in searchCountries&quot;\n    :key=&quot;country.name&quot;\n    @click=&quot;selectCountry(country.name)&quot;\n    class=&quot;cursor-pointer hover:bg-gray-100 p-1&quot;\n  &gt;\n    {{ country.name }}\n  &lt;\u002Fli&gt;\n&lt;\u002Ful&gt;\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2>取得配對成功的國家\u003C\u002Fh2>\n\u003Cp>為了配對國家，使用一個函式 searchCountries，它根據目前的 searchTerm 篩選國家列表：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-javascript\">\u002F\u002F輸入的資料\nlet searchTerm = ref(&#039;&#039;)\n\n\u002F\u002F即時檢查輸入資料與偵測資料的 computed\nconst searchCountries = computed(() =&gt; {\n  if (searchTerm.value === &#039;&#039;) {\n    return []\n  }\n\n  let matches = 0\n\n  return countries.filter(country =&gt; {\n    if (country.name.toLowerCase().includes(searchTerm.value.toLowerCase()) &amp;&amp; matches &lt; 10) {\n      matches++\n      return country\n    }\n  })\n});\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2>完成\u003C\u002Fh2>\n\u003Cp>這樣基本上就可以建立一個有自動完成(autocomplete)功能的輸入欄囉～\u003C\u002Fp>\n","2024-04-22T10:34:29","\u002Fapi\u002Fproxy-image?url=https%3A%2F%2Fpost.billxu.net%2Fwp-content%2Fuploads%2F2022%2F11%2F256px-Vue.js_Logo_2.svg_.png",{"name":88,"link":89},"Chih Fan","http:\u002F\u002Fpost.billxu.net",[91],{"name":61,"slug":61},"\u002Fblog\u002Fvue3-建立輸入自動完成autocomplete",{"id":94,"slug":95,"title":96,"description":97,"content":98,"date":99,"image":86,"imageAlt":96,"author":100,"categories":101,"path":103,"minRead":9},3705,"在-github-page-上部署-vue-spa-時-vue-route-失效的問題","在 github page 上部署 vue SPA 時 vue-route 失效的問題","在 github page 上部署 vue SPA 時 vue-route 失效的問題 參考資料: Vue R […]","\u003Ch1>在 github page 上部署 vue SPA 時 vue-route 失效的問題\u003C\u002Fh1>\n\u003Cp>參考資料:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\n\u003Cp>\u003Ca href=\"https:\u002F\u002Fstackoverflow.com\u002Fquestions\u002F65501787\u002Fvue-router-github-pages-and-custom-domain-not-working-with-routed-links\">Vue Router, GitHub Pages, and Custom Domain Not Working With Routed Links\u003C\u002Fa>\u003C\u002Fp>\n\u003C\u002Fli>\n\u003Cli>\n\u003Cp>\u003Ca href=\"https:\u002F\u002Fhuishun-medium-com.translate.goog\u002Fhow-to-deploy-a-vue-js-application-with-dynamic-routing-on-github-pages-3d36f4644e54?_x_tr_sl=en&amp;_x_tr_tl=zh-TW&amp;_x_tr_hl=zh-TW&amp;_x_tr_pto=sc&amp;_x_tr_hist=true\">如何在 GitHub 頁面上部署具有動態路由的 Vue.js 應用程式\u003C\u002Fa>\u003C\u002Fp>\n\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>跟上次部署在 AWS 的問題很類似=&gt;\u003Ca href=\"https:\u002F\u002Fpost.billxu.net\u002Fblog\u002F2024\u002F03\u002F19\u002Faws-cloudfront-%e9%83%a8%e7%bd%b2%e9%9d%9c%e6%85%8b%e7%b6%b2%e7%ab%99%e5%87%ba%e7%8f%be-x-cache-error-from-cloudfront-%e8%a8%8a%e6%81%af\u002F\" title=\"AWS Cloudfront 部署靜態網站出現 X-cache: Error from cloudfront 訊息\">AWS Cloudfront 部署靜態網站出現 X-cache: Error from cloudfront 訊息\u003C\u002Fa>，當 vue 的 SPA 部署在 github page ，並且設定自訂網域，如果是用 vue-route ，例如\u003Cdomain>\u002Fpage，可能會出現後端辨識錯誤，當訪問 \u002Fpage 時依然會導向到 \u002F 。\u003Cbr \u002F>\n找了幾種做法，這個解決法最簡易直覺，分享一下。\u003C\u002Fp>\n\u003Ch2>硬導向\u003C\u002Fh2>\n\u003Cp>這做法就是在部署後產生的 index.html 直接重新導向自己，包含 404.html。\u003Cbr \u002F>\n我的理解是，當訪問 \u003Cdomain>\u002Fpage 時，因為辨識不到實際的 \u002Fpage 中 index.html ，因此會跳錯誤轉向到 404.html ，因此 404.html 中需要導回到原來的  index.html 。\u003Cbr \u002F>\n記得加上檔案 public\u002F404.html \u003C\u002Fp>\n\u003Cpre>\u003Ccode>&lt;!DOCTYPE html&gt;\n&lt;html&gt;\n  &lt;head&gt;\n    &lt;meta charset=&quot;utf-8&quot;&gt;\n    &lt;title&gt;CHANGE THIS TO YOUR TITLE&lt;\u002Ftitle&gt;\n    &lt;script type=&quot;text\u002Fjavascript&quot;&gt;\n      var pathSegmentsToKeep = 0;\n      var l = window.location;\n      l.replace(\n        l.protocol + &#039;\u002F\u002F&#039; + l.hostname + (l.port ? &#039;:&#039; + l.port : &#039;&#039;) +\n        l.pathname.split(&#039;\u002F&#039;).slice(0, 1 + pathSegmentsToKeep).join(&#039;\u002F&#039;) + &#039;\u002F?\u002F&#039; +\n        l.pathname.slice(1).split(&#039;\u002F&#039;).slice(pathSegmentsToKeep).join(&#039;\u002F&#039;).replace(\u002F&amp;\u002Fg, &#039;~and~&#039;) +\n        (l.search ? &#039;&amp;&#039; + l.search.slice(1).replace(\u002F&amp;\u002Fg, &#039;~and~&#039;) : &#039;&#039;) +\n        l.hash\n      );\n\n    &lt;\u002Fscript&gt;\n  &lt;\u002Fhead&gt;\n  &lt;body&gt;\n  &lt;\u002Fbody&gt;\n&lt;\u002Fhtml&gt;\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>index.html 則需要再加上自我轉址\u003C\u002Fp>\n\u003Cpre>\u003Ccode>&lt;script type=&quot;text\u002Fjavascript&quot;&gt;\n\u002F\u002F MIT License\n\u002F\u002F https:\u002F\u002Fgithub.com\u002Frafgraph\u002Fspa-github-pages\n\u002F\u002F This script checks to see if a redirect is present in the query string,\n\u002F\u002F converts it back into the correct url and adds it to the\n\u002F\u002F browser&#039;s history using window.history.replaceState(...),\n\u002F\u002F which won&#039;t cause the browser to attempt to load the new url.\n\u002F\u002F When the single page app is loaded further down in this file,\n\u002F\u002F the correct url will be waiting in the browser&#039;s history for\n\u002F\u002F the single page app to route accordingly.\n(function(l) {\n    if (l.search[1] === &#039;\u002F&#039; ) {\n    var decoded = l.search.slice(1).split(&#039;&amp;&#039;).map(function(s) { \n        return s.replace(\u002F~and~\u002Fg, &#039;&amp;&#039;)\n    }).join(&#039;?&#039;);\n    window.history.replaceState(null, null,\n        l.pathname.slice(0, -1) + decoded + l.hash\n    );\n    }\n}(window.location))\n&lt;\u002Fscript&gt;\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>強制讓瀏覽器寫入原位置\u003Cbr \u002F>\n基本上就可以運作了，但由於這是取巧的做法，可能會影響 SEO ，使用時要考慮一下喔。\u003C\u002Fp>\n","2024-04-08T10:30:34",{"name":88,"link":89},[102],{"name":61,"slug":61},"\u002Fblog\u002F在-github-page-上部署-vue-spa-時-vue-route-失效的問題",{"id":105,"slug":106,"title":107,"description":108,"content":109,"date":110,"image":111,"imageAlt":107,"author":112,"categories":113,"path":115,"minRead":9},3700,"nuxt-3-的-pwa-設定方式","Nuxt 3 的 PWA 設定方式","Nuxt 3 的 PWA 設定方式 Nuxt 3 內就有基本的 PWA 設定，因為我只需要用到主畫面連結，因此 […]","\u003Ch1>Nuxt 3 的 PWA 設定方式\u003C\u002Fh1>\n\u003Cp>Nuxt 3 內就有基本的 PWA 設定，因為我只需要用到主畫面連結，因此用這做法就足夠了\u003C\u002Fp>\n\u003Cp>首先是設定基本的資訊\u003C\u002Fp>\n\u003Cp>nuxt.config.ts\u003C\u002Fp>\n\u003Cpre>\u003Ccode>import { defineNuxtConfig } from &#039;nuxt3&#039;\n\nexport default defineNuxtConfig({\n  meta: {\n    title: &#039;Nuxt3 PWA&#039;,\n    charset: &#039;utf-8&#039;,\n    meta: [\n      { name: &#039;theme-color&#039;, content: &#039;#ffdd67&#039; }\n    ],\n    link: [\n      { hid: &#039;icon&#039;, rel: &#039;icon&#039;, type: &#039;image\u002Fpng&#039;, href: &#039;\u002Flogo_x32.png&#039; },\n      { hid: &#039;apple-touch-icon&#039;, rel: &#039;apple-touch-icon&#039;, href: &#039;\u002Flogo_x180.png&#039; },\n      { rel: &#039;manifest&#039;, href: &#039;\u002Fmanifest.json&#039; }\n    ]\n  }\n})\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>再來於 public  裡面加入manifest.json 以及顯示的圖檔\u003C\u002Fp>\n\u003Cp>manifest.json\u003C\u002Fp>\n\u003Cpre>\u003Ccode>&quot;name&quot;: &quot;Nuxt3 PWA&quot;,\n    &quot;icons&quot;: [\n        {\n            &quot;src&quot;: &quot;\u002Flogo_x512.png&quot;,\n            &quot;types&quot;: &quot;img\u002Fpng&quot;,\n            &quot;sizes&quot;: &quot;512x512&quot;,\n            &quot;purpose&quot;: &quot;maskable&quot;\n        },\n        {\n            &quot;src&quot;: &quot;\u002Flogo_x384.png&quot;,\n            &quot;types&quot;: &quot;img\u002Fpng&quot;,\n            &quot;sizes&quot;: &quot;384x384&quot;,\n            &quot;purpose&quot;: &quot;maskable&quot;\n        },\n        {\n            &quot;src&quot;: &quot;\u002Flogo_x192.png&quot;,\n            &quot;types&quot;: &quot;img\u002Fpng&quot;,\n            &quot;sizes&quot;: &quot;192x192&quot;,\n            &quot;purpose&quot;: &quot;maskable&quot;\n        },\n        {\n            &quot;src&quot;: &quot;\u002Flogo_x128.png&quot;,\n            &quot;types&quot;: &quot;img\u002Fpng&quot;,\n            &quot;sizes&quot;: &quot;128x128&quot;,\n            &quot;purpose&quot;: &quot;maskable&quot;\n        },\n        {\n            &quot;src&quot;: &quot;\u002Flogo_x96.png&quot;,\n            &quot;types&quot;: &quot;img\u002Fpng&quot;,\n            &quot;sizes&quot;: &quot;96x96&quot;,\n            &quot;purpose&quot;: &quot;maskable&quot;\n        },\n        {\n            &quot;src&quot;: &quot;\u002Flogo_x72.png&quot;,\n            &quot;types&quot;: &quot;img\u002Fpng&quot;,\n            &quot;sizes&quot;: &quot;72x72&quot;,\n            &quot;purpose&quot;: &quot;maskable&quot;\n        },\n        {\n            &quot;src&quot;: &quot;\u002Flogo_x48.png&quot;,\n            &quot;types&quot;: &quot;img\u002Fpng&quot;,\n            &quot;sizes&quot;: &quot;48x48&quot;,\n            &quot;purpose&quot;: &quot;any&quot;\n        },\n        {\n            &quot;src&quot;: &quot;\u002Flogo_x180.png&quot;,\n            &quot;types&quot;: &quot;img\u002Fpng&quot;,\n            &quot;sizes&quot;: &quot;180x180&quot;,\n            &quot;purpose&quot;: &quot;any&quot;\n        },\n        {\n            &quot;src&quot;: &quot;\u002Flogo_x120.png&quot;,\n            &quot;types&quot;: &quot;img\u002Fpng&quot;,\n            &quot;sizes&quot;: &quot;120x120&quot;,\n            &quot;purpose&quot;: &quot;any&quot;\n        }\n    ],\n    &quot;start_url&quot;: &quot;\u002F?source=pwa&quot;,\n    &quot;display&quot;: &quot;fullscreen&quot;,\n    &quot;background_color&quot;: &quot;#000000&quot;,\n    &quot;theme_color&quot;: &quot;#000000&quot;\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>app.vue 最好加個防呆\u003C\u002Fp>\n\u003Cpre>\u003Ccode>if (process.client) {\n  window.addEventListener(&#039;load&#039;, () =&gt; {\n    if (!(&#039;serviceWorker&#039; in navigator)) {\n      throw new Error(&#039;serviceWorker is not supported in current browser!&#039;)\n    }\n\n    navigator.serviceWorker.register(&#039;\u002Fsw.js&#039;)\n  })\n}\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>大致上使用 PWA 時就吃到資料囉～\u003C\u002Fp>\n","2024-04-01T10:04:39","\u002Fapi\u002Fproxy-image?url=https%3A%2F%2Fpost.billxu.net%2Fwp-content%2Fuploads%2F2024%2F03%2Ficon-green.svg",{"name":88,"link":89},[114],{"name":61,"slug":61},"\u002Fblog\u002Fnuxt-3-的-pwa-設定方式",{"id":117,"slug":118,"title":118,"description":119,"content":120,"date":121,"image":122,"imageAlt":118,"author":123,"categories":124,"path":128,"minRead":29},3664,"在手機上進行前端網頁的debug","在手機上進行前端網頁的debug 說來真是羞愧，最近才知道有這個神兵協助手機上的除錯XD 當專案越寫越多樣化時 […]","\u003Ch1>在手機上進行前端網頁的debug\u003C\u002Fh1>\n\u003Cp>說來真是羞愧，最近才知道有這個神兵協助手機上的除錯XD 當專案越寫越多樣化時，常常會遇到一種狀況，例如專案在手機的某個 app 內建瀏覽器上打開時才會出現錯誤、或是顯示異常，由於沒有萬能的 F12 可以看 console，通常得靠經驗通靈，其實效率滿差的。\u003C\u002Fp>\n\u003Cp>如果這種狀況依然能打開 console 的話，就能加速除錯時間了，最近才知道有這樣的神兵利器啊！那就是 vconsole ！\u003C\u002Fp>\n\u003Cp>以 vite + vue 為例，就照以下方式：\u003C\u002Fp>\n\u003Ch2>安裝\u003C\u002Fh2>\n\u003Cpre>\u003Ccode class=\"language-jsx\">npm install vconsole\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2>引入\u003C\u002Fh2>\n\u003Cp>我的作法有點偷懶，直接在 main.js 中引入\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-jsx\">import VConsole from &#039;vconsole&#039;;\n\n\u002F\u002F因為要限制只在開發期才顯示\nif (import.meta.env.MODE === &#039;development&#039;) {\n  const vConsole = new VConsole();\n}\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>其實這樣就可以了\u003C\u002Fp>\n\u003Cp>正常來說會有個按鈕「VCONSOLE」\u003Cbr \u002F>\n\u003Cimg decoding=\"async\" src=\"\u002Fapi\u002Fproxy-image?url=https%3A%2F%2Fpost.billxu.net%2Fwp-content%2Fuploads%2F2024%2F03%2F%E6%88%AA%E5%9C%96-2024-03-07-%E6%99%9A%E4%B8%8A10.01.43.png\" alt=\"\" \u002F>\u003C\u002Fp>\n\u003Cp>打開來就有個 視窗可以看資訊囉～～\u003Cbr \u002F>\n\u003Cimg decoding=\"async\" src=\"\u002Fapi\u002Fproxy-image?url=https%3A%2F%2Fpost.billxu.net%2Fwp-content%2Fuploads%2F2024%2F03%2F%E6%88%AA%E5%9C%96-2024-03-07-%E6%99%9A%E4%B8%8A10.02.38-1024x349.png\" alt=\"\" \u002F>\u003C\u002Fp>\n","2024-03-10T10:04:14","\u002Fapi\u002Fproxy-image?url=https%3A%2F%2Fpost.billxu.net%2Fwp-content%2Fuploads%2F2023%2F01%2F640px-Node.js_logo.svg.png",{"name":88,"link":89},[125,126,127],{"name":22,"slug":22},{"name":43,"slug":43},{"name":61,"slug":61},"\u002Fblog\u002F在手機上進行前端網頁的debug",{"id":130,"slug":131,"title":132,"description":133,"content":134,"date":135,"image":136,"imageAlt":132,"author":137,"categories":138,"path":140,"minRead":29},3650,"vite-部署在-github-page","Vite 部署在 github page","Vite 部署在 github page 請參考官方文件：https:\u002F\u002Fvitejs.dev\u002Fguide\u002Fs […]","\u003Ch1>Vite 部署在 github page\u003C\u002Fh1>\n\u003Cp>請參考官方文件：\u003Ca href=\"https:\u002F\u002Fvitejs.dev\u002Fguide\u002Fstatic-deploy\">https:\u002F\u002Fvitejs.dev\u002Fguide\u002Fstatic-deploy\u003C\u002Fa>\u003C\u002Fp>\n\u003Cp>一開始因為 vue route 設定問題，部署到github page 上時不知道什麼原因，vue文檔中的\u003Crouter-view>會無法正確顯示在 index.html 上，幾番折騰後發現Vite官方就有最新版本的部署方式，這邊就記錄一下。(2024年)\u003C\u002Fp>\n\u003Cp>首先是 vite.config.js 中的需要先設定基礎位置，如果是使用https:\u002F\u002F\u003CUSERNAME>.github.io\u002F遠矢的位置，那\u003Ccode>base\u003C\u002Fcode>要設定為\u003Ccode>&#039;\u002F&#039;\u003C\u002Fcode>。如果是 https:\u002F\u002F\u003CUSERNAME>.github.io\u002F\u003CREPO>\u002F 有個 REPO位置的話，則\u003Ccode>base\u003C\u002Fcode> 設定為\u003Ccode>&#039;\u002F&lt;REPO&gt;\u002F&#039;\u003C\u002Fcode>。\u003C\u002Fp>\n\u003Cp>再來 setting 的 page 那邊， source 須要設定為 GitHub Actions。並且使用以下 workflow 的 yml\u003C\u002Fp>\n\u003Cpre>\u003Ccode># Simple workflow for deploying static content to GitHub Pages\nname: Deploy static content to Pages\n\non:\n  # Runs on pushes targeting the default branch\n  push:\n    branches: [&#039;main&#039;]\n\n  # Allows you to run this workflow manually from the Actions tab\n  workflow_dispatch:\n\n# Sets the GITHUB_TOKEN permissions to allow deployment to GitHub Pages\npermissions:\n  contents: read\n  pages: write\n  id-token: write\n\n# Allow one concurrent deployment\nconcurrency:\n  group: &#039;pages&#039;\n  cancel-in-progress: true\n\njobs:\n  # Single deploy job since we&#039;re just deploying\n  deploy:\n    environment:\n      name: github-pages\n      url: ${{ steps.deployment.outputs.page_url }}\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout\n        uses: actions\u002Fcheckout@v4\n      - name: Set up Node\n        uses: actions\u002Fsetup-node@v3\n        with:\n          node-version: 18\n          cache: &#039;npm&#039;\n      - name: Install dependencies\n        run: npm install\n      - name: Build\n        run: npm run build\n      - name: Setup Pages\n        uses: actions\u002Fconfigure-pages@v3\n      - name: Upload artifact\n        uses: actions\u002Fupload-pages-artifact@v2\n        with:\n          # Upload dist repository\n          path: &#039;.\u002Fdist&#039;\n      - name: Deploy to GitHub Pages\n        id: deployment\n        uses: actions\u002Fdeploy-pages@v2\u003C\u002Fcode>\u003C\u002Fpre>\n","2024-02-18T10:20:22","\u002Fapi\u002Fproxy-image?url=https%3A%2F%2Fpost.billxu.net%2Fwp-content%2Fuploads%2F2024%2F02%2F%E4%B8%8B%E8%BC%89.jpeg",{"name":88,"link":89},[139],{"name":61,"slug":61},"\u002Fblog\u002Fvite-部署在-github-page",{"id":142,"slug":143,"title":144,"description":145,"content":146,"date":147,"image":148,"imageAlt":144,"author":149,"categories":150,"path":153,"minRead":50},3630,"使用-github-gas-建立-liff-問卷","使用 Github + GAS 建立 LIFF 問卷","使用 Github + GAS 建立 LIFF 問卷 之前有個專案需求是透過 LINE bot + Googl […]","\u003Ch1>使用 Github + GAS 建立 LIFF 問卷\u003C\u002Fh1>\n\u003Cp>之前有個專案需求是透過 LINE bot + Google Form 建立一套可以進行線上預約的LINE OA(官方帳號)， 但為了進行 UID 的對應設計一套迂迴的流程，實在不爽快。現在有機會透過 LIFF 導入，能簡化這邊的流程。\u003C\u002Fp>\n\u003Cp>由於團隊適應關係，繼續沿用 Google sheet 來做管理，因此需要使用 GAS 來開發一組 API 。\u003C\u002Fp>\n\u003Cp>基礎概念很簡單：\u003C\u002Fp>\n\u003Col>\n\u003Cli>先部署 LIFF 頁面\u003C\u002Fli>\n\u003Cli>製作 LIFF 頁面的問卷\u003C\u002Fli>\n\u003Cli>部署 GAS 為基礎的 API\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch1>LIFF 頁面部署\u003C\u002Fh1>\n\u003Cp>這邊使用的開發環境為 Vite + Vue3.3 + Tailwind + axios。\u003C\u002Fp>\n\u003Ch3>1. 宣告 LIFF 導入\u003C\u002Fh3>\n\u003Cp>LIFF 的導入可以參考\u003Ca href=\"https:\u002F\u002Fpost.billxu.net\u002Fblog\u002F2023\u002F01\u002F10\u002Fvue%e5%88%9d%e6%8e%a2liff%e7%9a%84%e4%b8%80%e4%ba%9b%e7%ad%86%e8%a8%98-%e9%96%8b%e7%99%bc%e7%af%87\u002F\" title=\"這篇\">這篇\u003C\u002Fa>\u003Cbr \u002F>\n宣告 import liff 後，在 onMounted 時宣告 init，如下\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-jsx\">import liff from &quot;@line\u002Fliff&quot;;\n\nonMounted(async () =&gt; {\n  liff\n  .init({\n    liffId: &quot;&lt;LIFF ID&gt;&quot;, \u002F\u002F 從LIFF頁面中拿到的ID\n  })\n  .then(() =&gt; { \n    if(liff.isLoggedIn()){\n      \u002F\u002F做你想要做的\n      isLogin.value = liff.isLoggedIn();\n      userid.value = liff.getDecodedIDToken().sub; \u002F\u002F這是UID\n    }\n\n  })\n  .catch((err) =&gt; {\n    console.log(err.code, err.message);\n  });\n})\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>2.在問卷頁中加入送出表單\u003C\u002Fh3>\n\u003Cp>就是一個 submitForm 。裡面預留 API 的位置，已經撰寫問卷本體。\u003Cbr \u002F>\n到這邊 LIFF 大致上就做完了。接下來要部署到 Github Page 上\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-jsx\">&lt;form class=&quot;flex flex-col items-center&quot; @submit.prevent=&quot;submitForm&quot;&gt;\n&lt;label for=&quot;name&quot; class=&quot;block mt-4&quot;&gt;請問您的姓名：&lt;span class=&quot;text-red-500 text-sm&quot;&gt;＊（請輸入文字）&lt;\u002Fspan&gt;&lt;\u002Flabel&gt;\n  &lt;input type=&quot;text&quot; id=&quot;name&quot; v-model=&quot;name&quot; placeholder=&quot;姓名&quot; class=&quot;border border-gray-300 rounded-md px-2 py-1 mt-1&quot; pattern=&quot;[A-Za-z]+&quot; title=&quot;請輸入有效的姓名（只能包含字母）&quot; required \u002F&gt;\n\n  &lt;label for=&quot;phone&quot; class=&quot;block mt-4&quot;&gt;請問您的連絡電話：&lt;span class=&quot;text-red-500 text-sm&quot;&gt;＊（請輸入數字）&lt;\u002Fspan&gt;&lt;\u002Flabel&gt;\n  &lt;input type=&quot;text&quot; id=&quot;phone&quot; v-model=&quot;phone&quot; placeholder=&quot;連絡電話&quot; class=&quot;border border-gray-300 rounded-md px-2 py-1 mt-1&quot; pattern=&quot;[0-9]+&quot; title=&quot;請輸入有效的連絡電話（只能包含數字）&quot; required \u002F&gt;\n\n  &lt;label for=&quot;purchaseDate&quot; class=&quot;block mt-4&quot;&gt;請問您的購買時間：&lt;\u002Flabel&gt;\n  &lt;input type=&quot;date&quot; id=&quot;purchaseDate&quot; v-model=&quot;purchaseDate&quot; class=&quot;border border-gray-300 rounded-md px-2 py-1 mt-1&quot; required \u002F&gt;\n\n  &lt;label for=&quot;storeAddress&quot; class=&quot;block mt-4&quot;&gt;請問您購買的經銷門市地址：&lt;\u002Flabel&gt;\n  &lt;select id=&quot;storeAddress&quot; v-model=&quot;storeAddress&quot; class=&quot;border border-gray-300 rounded-md px-2 py-1 mt-1&quot; required&gt;\n      &lt;option value=&quot;&quot;&gt;請選擇&lt;\u002Foption&gt;\n      &lt;option value=&quot;address1&quot;&gt;A門市&lt;\u002Foption&gt;\n      &lt;option value=&quot;address2&quot;&gt;B門市&lt;\u002Foption&gt;\n      &lt;option value=&quot;address3&quot;&gt;C門市&lt;\u002Foption&gt;\n  &lt;\u002Fselect&gt;\n&lt;\u002Fform&gt;\n\nconst submitForm = () =&gt; {\n\n    fconst formData = new FormData();\n  formData.append(&#039;name&#039;, name.value);\n  formData.append(&#039;phone&#039;, phone.value);\n  formData.append(&#039;userid&#039;, user.userid);\n\n    axios({\n            method: &#039;post&#039;,\n            url: &#039;&lt;GAS api&gt;&#039;,\n            data: formData,\n            headers: { &#039;Content-Type&#039;: &#039;multipart\u002Fform-data&#039; }\n        })\n        .then(response =&gt; {\n            \u002F\u002F 成功\n            console.log(response);\n        })\n        .catch(error =&gt; {\n            \u002F\u002F 失敗\n\n        });\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2>使用  Github Actions 部署\u003C\u002Fh2>\n\u003Cp>為了省成本而部署在 Github Page 上，使用 Github Actions 來進行，請參考\u003Ca href=\"https:\u002F\u002Fpost.billxu.net\u002Fblog\u002F2024\u002F01\u002F08\u002F%e4%bd%bf%e7%94%a8-github-action-%e9%83%a8%e7%bd%b2-github-pages\u002F\" title=\"這篇\">這篇\u003C\u002Fa>。\u003Cbr \u002F>\ngh-pages-deploy.yml\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-yaml\">name: Deploy\n\non:\n  push:\n    branches:\n      - main  # 將此處更改為你的默認分支名稱，例如：master\n\njobs:\n  build-and-deploy:\n    runs-on: ubuntu-latest\n\n    steps:\n    - name: Checkout\n      uses: actions\u002Fcheckout@v2\n\n    - name: Install &amp; Build # 執行指令\n      run: |\n        npm install\n        npm run build \n\n    - name: Deploy\n      uses: JamesIves\u002Fgithub-pages-deploy-action@4.1.5\n      with:\n        branch: gh-pages  # GitHub Pages 的分支\n        folder: dist  # build 後的檔案夾\n\n    - name: Debug git error\n      run: git status\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2>登記為 LIFF\u003C\u002Fh2>\n\u003Cp>部署完後，記得要去 LINE 開發者功能中登記 LIFF ，可以參考\u003Ca href=\"https:\u002F\u002Fpost.billxu.net\u002Fblog\u002F2023\u002F01\u002F10\u002Fline%e5%88%9d%e6%8e%a2liff%e7%9a%84%e4%b8%80%e4%ba%9b%e7%ad%86%e8%a8%98-%e7%94%b3%e8%ab%8b%e7%af%87\u002F\" title=\"這篇\">這篇\u003C\u002Fa>。\u003Cbr \u002F>\n就拿 Github Page 的 url 就可以。\u003C\u002Fp>\n\u003Ch1>GAS 部署\u003C\u002Fh1>\n\u003Cp>接著要製作 GAS 的 API ，請參考\u003Ca href=\"https:\u002F\u002Fwww.google.com\u002Fscript\u002Fstart\u002F\">官方文件\u003C\u002Fa>。\u003C\u002Fp>\n\u003Ch3>建立 POST 接口\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-jsx\">function doPost(e) {\n  var param = e.parameter;\n\n  const data = {\n    name: param.name,\n    phone: param.phone,\n    userID: param.userid\n  }\n  setData(data); \u002F\u002F將資料放進表單\n  return ContentService.createTextOutput(JSON.stringify({param}))\n                       .setMimeType(ContentService.MimeType.JSON) \u002F\u002F為了debug 先回傳資料\n}\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>將資料放進表單\u003C\u002Fh3>\n\u003Cp>因為我是在別的文件開啟GAS，所以另外指定了 fileId 。簡單說就是看你的 Google sheet 位置，像這樣：\u003Ca href=\"https:\u002F\u002Fdocs.google.com\u002Fspreadsheets\u002Fd\u002F\">https:\u002F\u002Fdocs.google.com\u002Fspreadsheets\u002Fd\u002F\u003C\u002Fa>\u003CID>，取用\u003CID>那段就對了，記得要還要指定工作表喔。\u003C\u002Fp>\n\u003Cp>如果是同一個文件，可以使用\u003Ccode>getActiveSpreadsheet\u003C\u002Fcode>\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-jsx\">function setData(data){\n    var fileId = &#039;&lt;ID&gt;&#039;; \u002F\u002F 請用你的檔案ID替換此處\n    \u002F\u002F 透過ID開啟另一個檔案\n    var file = SpreadsheetApp.openById(fileId);\n    \u002F\u002F 獲取另一個檔案的工作表\n    var sheet = file.getSheetByName(&#039;&lt;工作表名稱&gt;&#039;);\n\n    const userID = data.userID;\n    var lastRow = sheet.getLastRow();\n\n        \u002F\u002F 如果是第一筆，先做標題\n    if (lastRow == 0) {\n      var headerRow = [&#039;name&#039;,&#039;phone&#039;,&#039;UID&#039;];\n      sheet.appendRow(headerRow);\n    }\n\n    \u002F\u002F 整理資料成陣列(注意是二維陣列)\n    var values = [[data.name,data.phone,userID]];\n\n    \u002F\u002F 放資料\n    sheet.getRange(lastRow + 1, 1, values.length, values[0].length).setValues(values);\n}\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>部署位置\u003C\u002Fh3>\n\u003Cp>前面 LIFF 部署時，有個 \u003CGAS api> ，就是這時 GAS 部署完後會有組位置像這樣：\u003Ca href=\"https:\u002F\u002Fscript.google.com\u002Fmacros\u002Fs\u002Fxxxxxxxx\u002Fexec，將這串貼到\">https:\u002F\u002Fscript.google.com\u002Fmacros\u002Fs\u002Fxxxxxxxx\u002Fexec，將這串貼到\u003C\u002Fa> \u003CGAS api> 。\u003C\u002Fp>\n\u003Cp>這樣就可以建立一個可以取得 UID 的 LIFF 頁面，並且透過 GAS 存到 Google sheet 中，但記得 GAS API 有一些\u003Ca href=\"https:\u002F\u002Fdevelopers.google.com\u002Fapps-script\u002Fguides\u002Fservices\u002Fquotas?hl=zh-tw\">使用上限制\u003C\u002Fa>，用來建立微型服務絕對沒問題，但如果有用量上考量，建議不要這樣玩XD\u003C\u002Fp>\n","2024-01-16T16:20:22","\u002Fapi\u002Fproxy-image?url=https%3A%2F%2Fpost.billxu.net%2Fwp-content%2Fuploads%2F2024%2F01%2Fb40e6ed5-cbed-4701-b2c9-68e84f0754d4.jpeg",{"name":88,"link":89},[151,152],{"name":43,"slug":43},{"name":61,"slug":61},"\u002Fblog\u002F使用-github-gas-建立-liff-問卷",{"id":155,"slug":156,"title":157,"description":158,"content":159,"date":160,"image":86,"imageAlt":157,"author":161,"categories":162,"path":165,"minRead":9},3543,"gasgoogle-app-script上實作-vue3-的網頁部署","GAS(google app script)上實作 vue3 的網頁部署","GAS(google app script)上實作 vue3 的網頁部署 GAS(google app scr […]","\u003Ch1>GAS(google app script)上實作 vue3 的網頁部署\u003C\u002Fh1>\n\u003Cp>GAS(google app script) 是 google 服務提供的腳本平台，最廣為熟知的就是可以透過 script 將 google sheet 擴充許多自動化功能，甚至可以部署網頁等。若有興趣可以查查看，可以多查查看，如果你有大量的 google 相依服務，就能考慮使用 GAS 提供工作效率。\u003C\u002Fp>\n\u003Cp>這次的需求就是將 google sheet 當作是簡易資料表，然後輸出到 GAS 的網頁做檢視，考慮到未來可能的擴充性，決定導入 Vue3 的 CDN 版本來實作。\u003C\u002Fp>\n\u003Ch2>啟用 GAS(google app script)\u003C\u002Fh2>\n\u003Cp>首先就是啟用 GAS ，這邊就不特別教學，請參考原本的\u003Ca href=\"https:\u002F\u002Fwww.google.com\u002Fscript\u002Fstart\u002F\" title=\"教學\">教學\u003C\u002Fa>。\u003C\u002Fp>\n\u003Ch2>部署網頁\u003C\u002Fh2>\n\u003Cp>首先新增一個網頁，我取名為 output.html 。\u003Cbr \u002F>\n\u003Cimg decoding=\"async\" src=\"\u002Fapi\u002Fproxy-image?url=https%3A%2F%2Fpost.billxu.net%2Fwp-content%2Fuploads%2F2023%2F10%2F%E6%88%AA%E5%9C%96-2023-10-01-%E4%B8%8B%E5%8D%8810.02.10-300x204.png\" alt=\"\" \u002F>\u003Cbr \u002F>\n再來新增一個 code.gs 。*.gs 是 GAS中專用的 script 副檔名。\u003Cbr \u002F>\n\u003Cimg decoding=\"async\" src=\"\u002Fapi\u002Fproxy-image?url=https%3A%2F%2Fpost.billxu.net%2Fwp-content%2Fuploads%2F2023%2F10%2F%E6%88%AA%E5%9C%96-2023-10-01-%E4%B8%8B%E5%8D%8810.03.41-300x237.png\" alt=\"\" \u002F>\u003C\u002Fp>\n\u003Cp>在 code.gs 中加上以下程式碼。\u003C\u002Fp>\n\u003Cpre>\u003Ccode>function doGet(){\n    let template = HtmlService.createTemplateFromFile(&#039;output&#039;);\n    let dataObj = {};\n    template.data = dataObj;\n    return template.evaluate().setTitle(&quot;Vuejs Demo&quot;);\n}\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Cem>HtmlService\u003C\u002Fem> 是 GAS 中建立網頁的函式，這樣就能將資料輸出至 output.html ，並且設定標題 \u003Cem>Vuejs Demo\u003C\u002Fem> 。一定要取名為doGet\u003Cbr \u002F>\n並且能把名為 dataObj 的物件丟到該文檔中。\u003C\u002Fp>\n\u003Cp>output.html 這隻檔案則就加入一般的 html 文件，例如\u003C\u002Fp>\n\u003Cpre>\u003Ccode>\u003C!DOCTYPE html>\n\u003Chtml>\n  \u003Chead>\n  ...\n  \u003C\u002Fhead>\n  \u003Cbody>\n  ...\n  \u003C\u002Fbody>\n\u003C\u002Fhtml>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2>引入 Vue3\u003C\u002Fh2>\n\u003Cp>接下來就是建立 Vue3 的環境。我們使用 CDN 引入，可以直接像這樣\u003C\u002Fp>\n\u003Cpre>\u003Ccode>&lt;html&gt;\n  &lt;head&gt;\n    &lt;script src=&quot;https:\u002F\u002Fcdn.jsdelivr.net\u002Fnpm\u002Fvue@3.2.1\u002Fdist\u002Fvue.global.js&quot;&gt;&lt;\u002Fscript&gt;\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>接著要宣告 Vue 實體，為了方便管理我另外開了一隻檔案 app-js.html\u003Cbr \u002F>\n\u003Cimg decoding=\"async\" src=\"\u002Fapi\u002Fproxy-image?url=https%3A%2F%2Fpost.billxu.net%2Fwp-content%2Fuploads%2F2023%2F10%2F%E6%88%AA%E5%9C%96-2023-10-01-%E4%B8%8B%E5%8D%8810.13.03-300x264.png\" alt=\"\" \u002F>\u003Cbr \u002F>\n裡面其實就是宣告建立 Vue\u003C\u002Fp>\n\u003Cpre>\u003Ccode>&lt;script&gt;\n\n    const { createApp, ref } = Vue\n\n    const app = createApp({\n      components: {  },\n      data() {\n        return {\n\n        }\n      },\n      watch:{\n\n      },\n      methods: {\n\n      },\n      created: function(){\n\n    });\n    app.mount(&quot;#app&quot;);\n\n  &lt;\u002Fscript&gt;\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>然後記得 output.html 需要預留一個可以建立 Vue 的位置，並且還要引入剛剛那隻 app-js.html\u003C\u002Fp>\n\u003Cpre>\u003Ccode>&lt;body&gt;\n&lt;div class=&quot;&quot; id=&quot;app&quot;&gt;\n....\n\n    &lt;?!= include(&#039;app-js&#039;); ?&gt;\n&lt;\u002Fbody&gt;\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>這樣就快完成了，只需要部署就可以使用囉～\u003C\u002Fp>\n\u003Ch2>取用資料\u003C\u002Fh2>\n\u003Cp>如果需要取用 google sheet 內的資料，則需要在 code.gs 中另外使用函式與資料表溝通。以下為範例。\u003C\u002Fp>\n\u003Cpre>\u003Ccode>function doGet(){\n    let template = HtmlService.createTemplateFromFile(&#039;output&#039;);\n    let dataObj = {};\n    dataObj.Data = getDB(); \u002F\u002F 這是取得資料的函式\n    template.data = dataObj;\n    return template.evaluate().setTitle(&quot;Vuejs and SpreadSheet Demo&quot;).setSandboxMode(HtmlService.SandboxMode.IFRAME);\n}\n\nfunction getDB(){\n    const activeSheet = SpreadsheetApp.getActiveSheet();\n    let dataItems = activeSheet.getDataRange().getValues().slice(1);\n    return dataItems;\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>這是最簡單的寫法， SpreadsheetApp 就是使用 GAS 跟 google sheet 溝通，有興趣就詳見 GAS 的各種應用。\u003C\u002Fp>\n\u003Ch2>部署\u003C\u002Fh2>\n\u003Cp>在 GAS 的管理介面中，右上角可以看到部署的按鈕。如果開發時可以使用「測試部署作業」，如果是正式使用則要「新增部署作業」，兩者差異在於「測試部署作業」會給予一個....\u002Fdev結尾的位置，並且每次修改儲存後會立即生效，「新增部署作業」的話會以該次程式碼版本部署，會給予一個....\u002Fexec的位置，但記得程式碼修改後不會立即生效，要使用每次部署後取得的位置才有用。\u003Cimg decoding=\"async\" src=\"\u002Fapi\u002Fproxy-image?url=https%3A%2F%2Fpost.billxu.net%2Fwp-content%2Fuploads%2F2023%2F10%2F%E6%88%AA%E5%9C%96-2023-10-01-%E4%B8%8B%E5%8D%8810.19.34-297x300.png\" alt=\"\" \u002F>\u003C\u002Fp>\n","2023-10-02T10:26:52",{"name":88,"link":89},[163,164],{"name":22,"slug":22},{"name":61,"slug":61},"\u002Fblog\u002Fgasgoogle-app-script上實作-vue3-的網頁部署",{"id":167,"slug":168,"title":169,"description":170,"content":171,"date":172,"image":173,"imageAlt":169,"author":174,"categories":175,"path":179,"minRead":9},3325,"vue初探liff的一些筆記-開發篇","[Vue]初探LIFF的一些筆記-開發篇","初探LIFF的一些筆記 最近因為chatGPT很紅因素，很多人將chatGPT與line整合一起，在玩這類專案 […]","\u003Ch1>初探LIFF的一些筆記\u003C\u002Fh1>\n\u003Cp>最近因為chatGPT很紅因素，很多人將chatGPT與line整合一起，在玩這類專案時剛好碰到了line的API服務於是認識到LIFF，今天花點時間整理關於開發LIFF遇到的狀況。\u003Cbr \u002F>\nLIFF ( LINE Front-end Framework ) 是Line推出的一種前端框架，可以將功能與Line直接整合，藉此取得使用者user資料、進行部分操作。\u003Cbr \u002F>\n用這玩意能完美的與Line bot連動，透過使用 Line Login 或 Account Link 的網站進行使用者資料串接，進而製作更全面性的整合行銷服務。\u003Cbr \u002F>\n我用Vue3進行串接開發，使用setup script。\u003C\u002Fp>\n\u003Ch2>使用LIFF\u003C\u002Fh2>\n\u003Cp>請參考\u003Ca href=\"https:\u002F\u002Fpost.billxu.net\u002Fline初探liff的一些筆記-申請篇\u002F\" title=\"liff的一些筆記-申請篇\">LIFF的一些筆記-申請篇\u003C\u002Fa>\u003C\u002Fp>\n\u003Ch2>載入\u003C\u002Fh2>\n\u003Cp>使用LIFF SDK可以直接用CDN引入\u003C\u002Fp>\n\u003Cpre>\u003Ccode>&lt;script src=&quot;https:\u002F\u002Fstatic.line-scdn.net\u002Fliff\u002Fedge\u002F2\u002Fsdk.js&quot;&gt;&lt;\u002Fscript&gt;\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>在Vue話，我是先透過npm安裝\u003C\u002Fp>\n\u003Cpre>\u003Ccode>npm install vue-router@next @line\u002Fliff\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>使用Vue3的setup script，載入SDK\u003C\u002Fp>\n\u003Cpre>\u003Ccode>&lt;script setup&gt;\nimport liff from &quot;@line\u002Fliff&quot;;\n&lt;\u002Fscript&gt;\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>後面就可以直接用囉。\u003C\u002Fp>\n\u003Ch2>初始化\u003C\u002Fh2>\n\u003Cp>啟動 LIFF 前需要先初始化(init)，在 Vue 的話就在 Mounted 實作這件事情。\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-javascript\">onMounted(async () =&gt; {\n  liff\n  .init({\n    liffId: &quot;&lt;LIFF ID&gt;&quot;, \u002F\u002F 從LIFF頁面中拿到的ID\n  })\n  .then(() =&gt; { \n  \u002F\u002F做你想要做的\n  })\n  .catch((err) =&gt; {\n    console.log(err.code, err.message);\n  });\n})\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2>登入與登出\u003C\u002Fh2>\n\u003Cp>登入後的redirectUri最好是跟LIFF頁面上設定的Callback URL相同，當然你可以依照需求轉到你要的地方。\u003Cbr \u002F>\n\u003Cimg decoding=\"async\" src=\"\u002Fapi\u002Fproxy-image?url=https%3A%2F%2Fpost.billxu.net%2Fwp-content%2Fuploads%2F2022%2F12%2F%E6%88%AA%E5%9C%96-2022-12-21-%E4%B8%8B%E5%8D%8812.17.53.png\" alt=\"\" \u002F>\u003C\u002Fp>\n\u003Cpre>\u003Ccode>if(!sysdata.isLoggedIn) {\n    liff.login({\n      redirectUri: window.location.href \u002F\u002F登入後轉址到需要的地方\n    });\n  }\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>登出就比較單純\u003C\u002Fp>\n\u003Cpre>\u003Ccode> if(sysdata.isLoggedIn) {\n    liff.logout();\n    window.location.reload(); \u002F\u002F 登出後重整一次頁面\n  }\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2>取得基本資訊\u003C\u002Fh2>\n\u003Cp>我將取得資訊跟初始化寫在一起，範例如下。\u003C\u002Fp>\n\u003Cul>\n\u003Cli>打開頁面中的使用語系（lang）\u003C\u002Fli>\n\u003Cli>LIFF SDK 的版本\u003C\u002Fli>\n\u003Cli>目前 LIFF 是否是在 LINE App 中開啟\u003C\u002Fli>\n\u003Cli>使用者有沒有登入 LINE 帳號\u003C\u002Fli>\n\u003Cli>使用者的作業系統：web、android、ios\u003C\u002Fli>\n\u003Cli>\n\u003Cp>使用者用的 LINE App 版本\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-javascript\">onMounted(async () =&gt; {\nliff\n.init({\nliffId: &quot;&quot;, \u002F\u002F 從LIFF頁面中拿到的ID\n})\n.then(() =&gt; {\nsysdata.language = liff.getLanguage(); \u002F\u002F String。引用 LIFF SDK 的頁面，頁面中的 lang 資料\nsysdata.version = liff.getVersion(); \u002F\u002F String。取得LIFF SDK 的版本\nsysdata.isInClient = liff.isInClient(); \u002F\u002F Boolean。回傳是否由 LINE App 存取\nsysdata.isLoggedIn = liff.isLoggedIn(); \u002F\u002F Boolean。檢查使用者是否登入 LINE 帳號狀態。true 時，可呼叫需要 Access Token 的 API\nsysdata.os = liff.getOS(); \u002F\u002F String。取得使用者的作業系統：ios、android、web\nsysdata.lineVersion = liff.getLineVersion(); \u002F\u002F 使用者的 LINE 版本\nconsole.log(sysdata.language,sysdata.version,sysdata.isInClient,sysdata.isLoggedIn,sysdata.os,sysdata.lineVersion);\n\n})\n.catch((err) =&gt; {\nconsole.log(err.code, err.message);\n});\n})\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>這邊要再講一下，申請完LIFF後會有個原來的Endpoint URL和LIFF上的LIFF URL，兩邊的LIFF SDK都會作用，但取得的權限不同(見下段落)，如果需要在LINE做一些操作，最好使用LIFF URL給使用者打開。\u003C\u002Fp>\n\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch2>取得使用者資訊\u003C\u002Fh2>\n\u003Cp>取得使用者資訊使用這個api，取得後可以檢查使用者賦予的權限和一些資料，必須要使用者必須登入才能使用。\u003C\u002Fp>\n\u003Cpre>\u003Ccode>userdata.context = liff.getContext();\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>回傳有以下幾種資料\u003C\u002Fp>\n\u003Cul>\n\u003Cli>type: utou(一對一聊天)、group(群組)、room(多人聊天室)、external(外部，通常是瀏覽器)、none(除以上外)。\u003C\u002Fli>\n\u003Cli>viewType：compact、tall、full。目前視窗的尺寸，可以在LIFF中設定。\u003C\u002Fli>\n\u003Cli>userId、groupId、roomId：不同的 type 會有不同的 ID。\u003C\u002Fli>\n\u003Cli>availability.shareTargetPicker.permission：能不能使用 shareTargetPicker 功能，可以在LIFF中打開。\u003C\u002Fli>\n\u003Cli>availability.shareTargetPicker.minVer：使用 shareTargetPicker 功能的需求版本是多少。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>這邊就要提醒一下，Endpoint URL和LIFF URL打開來得到的使用者資訊是不同的，前者type為external後者為utou(如果是bot聊天)。\u003C\u002Fp>\n\u003Ch2>取得使用者公開資料\u003C\u002Fh2>\n\u003Cp>必須要使用者必須登入才能使用。回傳資料有四個：userId、暱稱、照片的圖片位置、狀態消息。除了userId外皆屬於完全公開資訊。\u003C\u002Fp>\n\u003Cpre>\u003Ccode>liff.getProfile()\n    .then(function(profile) {\n    userdata.profile = profile;\n    console.log(userdata.profile);\n});\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>取得使用者 email\u003C\u002Fh3>\n\u003Cp>必須要使用者必須登入才能使用。也必須先在LIFF打開權限，並且使用者登入時需授權。\u003Cbr \u002F>\n\u003Cimg decoding=\"async\" src=\"\u002Fapi\u002Fproxy-image?url=https%3A%2F%2Fpost.billxu.net%2Fwp-content%2Fuploads%2F2022%2F12%2F%E6%88%AA%E5%9C%96-2022-12-21-%E4%B8%8B%E5%8D%8812.01.58.png\" alt=\"\" \u002F>\u003Cbr \u002F>\n還要打開LIFF的Scopes權限(email*)\u003Cbr \u002F>\n\u003Cimg decoding=\"async\" src=\"\u002Fapi\u002Fproxy-image?url=https%3A%2F%2Fpost.billxu.net%2Fwp-content%2Fuploads%2F2022%2F12%2F%E6%88%AA%E5%9C%96-2022-12-21-%E4%B8%8B%E5%8D%8812.05.21.png\" alt=\"\" \u002F>\u003C\u002Fp>\n\u003Cpre>\u003Ccode> const user = liff.getDecodedIDToken();\n    userdata.user = user\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>發送訊息\u003C\u002Fh3>\n\u003Cp>使用者必須登入才能使用，分為對自己和對其他人。要先打開LIFF的Scopes權限(chat_message.write)，並且使用者在登入時要授權。\u003Cbr \u002F>\n\u003Cimg decoding=\"async\" src=\"\u002Fapi\u002Fproxy-image?url=https%3A%2F%2Fpost.billxu.net%2Fwp-content%2Fuploads%2F2022%2F12%2F%E6%88%AA%E5%9C%96-2022-12-21-%E4%B8%8B%E5%8D%8812.05.21.png\" alt=\"\" \u002F>\u003C\u002Fp>\n\u003Ch3>對自己發送\u003C\u002Fh3>\n\u003Cp>只能在Line APP才能使用此功能，而且需要一對一聊天室，使用者資料的type須為utou，如果為group、room沒測試不知道能不能使用。external則不行。\u003C\u002Fp>\n\u003Cpre>\u003Ccode>const sendMessegeToSelf = () =&gt; {\n  \u002F\u002F 傳送訊息給自己\n  \u002F\u002F type 的可用值說明：https:\u002F\u002Fdevelopers.line.biz\u002Fen\u002Freference\u002Fliff\u002F#send-messages\n  liff.sendMessages([\n    {\n      type: &#039;text&#039;,\n      text: &lt;發送訊息&gt;\n    }\n  ])\n  .then(res =&gt; window.alert(res.status))\n  .catch(error =&gt; window.alert(error));\n}\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>對其他人發送\u003C\u002Fh3>\n\u003Cp>type為external也可以使用，但記得要先開啟\u003Cem>shareTargetPicker\u003C\u002Fem>。\u003Cbr \u002F>\n\u003Cimg decoding=\"async\" src=\"\u002Fapi\u002Fproxy-image?url=https%3A%2F%2Fpost.billxu.net%2Fwp-content%2Fuploads%2F2022%2F12%2F%E6%88%AA%E5%9C%96-2022-12-21-%E4%B8%8B%E5%8D%8812.08.03.png\" alt=\"\" \u002F>\u003C\u002Fp>\n\u003Cpre>\u003Ccode>const sendMessegeToOther = () =&gt; {\n  \u002F\u002F 傳送訊息給朋友\n  \u002F\u002F 發訊息的可用參數：https:\u002F\u002Fdevelopers.line.biz\u002Fen\u002Freference\u002Fliff\u002F#share-target-picker\n  if(sysdata.isLoggedIn &amp;&amp; liff.isApiAvailable(&#039;shareTargetPicker&#039;)) {\n    liff.shareTargetPicker([\n      {\n        type: &quot;text&quot;,\n        text: &lt;發送訊息&gt;\n      }\n    ])\n    .then(res =&gt; window.alert(res.status))\n    .catch(error =&gt; window.alert(error))\n  }\n}\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2>QRcode\u003C\u002Fh2>\n\u003Cp>很多人都會介紹，但IOS目前不能用，所以先筆記一下\u003C\u002Fp>\n\u003Cpre>\u003Ccode>liff.scanCode()\n  .then(function(res) {\n    console.log(res);\n  })\n  .catch(function(error) {\n    console.log(error);\n  });\u003C\u002Fcode>\u003C\u002Fpre>\n","2023-01-10T12:05:31","\u002Fapi\u002Fproxy-image?url=https%3A%2F%2Fpost.billxu.net%2Fwp-content%2Fuploads%2F2022%2F12%2FLINE_logo.svg.png",{"name":88,"link":89},[176,177,178],{"name":22,"slug":22},{"name":33,"slug":34},{"name":61,"slug":61},"\u002Fblog\u002Fvue初探liff的一些筆記-開發篇",{"id":181,"slug":182,"title":183,"description":184,"content":185,"date":186,"image":187,"imageAlt":183,"author":188,"categories":189,"path":192,"minRead":9},218,"vueionic的vue使用pwa","[Vue]ionic的vue使用PWA","Ionic的vue使用PWA 其實在Ionic中使用vue的方式在原本的技術文件中就有說明很清楚，可以先參考原 […]","\u003Ch1>Ionic的vue使用PWA\u003C\u002Fh1>\n\u003Cp>其實在Ionic中使用vue的方式在原本的技術文件中就有說明很清楚，可以先參考原本的\u003Ca href=\"https:\u002F\u002Fionicframework.com\u002Fdocs\u002Fvue\u002Fpwa\" title=\"說明\">說明\u003C\u002Fa>。\u003Cbr \u002F>\n簡單翻譯一下\u003C\u002Fp>\n\u003Ch2>首先於指令端加入PWA\u003C\u002Fh2>\n\u003Cpre>\u003Ccode>vue add pwa\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2>在main.js中宣告ServiceWorker\u003C\u002Fh2>\n\u003Cp>如果上面步驟成功，會在src中找到registerServiceWorker.js，如果使用TS則會看到registerServiceWorker.ts，這時於main.js中加上宣告。\u003C\u002Fp>\n\u003Cpre>\u003Ccode>import { createApp } from &#039;vue&#039;;\nimport App from &#039;.\u002FApp.vue&#039;;\nimport router from &#039;.\u002Frouter&#039;;\n\u002F\u002F Added by the CLI\nimport &#039;.\u002FregisterServiceWorker&#039;;\n\ncreateApp(App).use(router).mount(&#039;#app&#039;);\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>registerServiceWorker.js裡面長這樣，文件有說明這是基於 Workbox's webpack 產生的。\u003C\u002Fp>\n\u003Cpre>\u003Ccode>import { register } from &#039;register-service-worker&#039;;\n\nif (process.env.NODE_ENV === &#039;production&#039;) {\n  register(`${process.env.BASE_URL}service-worker.js`, {\n    ready() {\n      console.log(\n        &#039;App is being served from cache by a service worker.\\n&#039; + &#039;For more details, visit https:\u002F\u002Fgoo.gl\u002FAFskqB&#039;\n      );\n    },\n    registered() {\n      console.log(&#039;Service worker has been registered.&#039;);\n    },\n    cached() {\n      console.log(&#039;Content has been cached for offline use.&#039;);\n    },\n    updatefound() {\n      console.log(&#039;New content is downloading.&#039;);\n    },\n    updated() {\n      console.log(&#039;New content is available; please refresh.&#039;);\n    },\n    offline() {\n      console.log(&#039;No internet connection found. App is running in offline mode.&#039;);\n    },\n    error(error) {\n      console.error(&#039;Error during service worker registration:&#039;, error);\n    },\n  });\n}\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>其實做到這步，PWA的設定大致上完成了，部署後就有PWA效果囉。\u003Cbr \u002F>\n但當然不能這樣水一篇\u003C\u002Fp>\n\u003Ch2>Manifest組態設定\u003C\u002Fh2>\n\u003Cp>文件中有提到可以找到一份Manifest，但我找半天發現寫在模組的基本設定中，總不可能要我改模組內容吧，查了一下，在vue裡如果要設定PWA的Manifest，可以寫在vue的config中，請在vue.config.js中加入\u003C\u002Fp>\n\u003Cpre>\u003Ccode>  pwa:{\n    &quot;name&quot;: &quot;一般稱呼&quot;,\n    &quot;short_name&quot;: &quot;短稱&quot;,\n    &quot;theme_color&quot;: &quot;#FFFFFF&quot;,\n    &quot;lang&quot;: &quot;zh-Hant-TW&quot;,\n    &quot;description&quot;: &quot;這是具有PWA設定的網站&quot;,\n    &quot;icons&quot;: [\n      {\n        &quot;src&quot;: &quot;.\u002Fimg\u002Ficons\u002Fandroid-chrome-512x512.png&quot;,\n        &quot;sizes&quot;: &quot;512x512&quot;,\n        &quot;type&quot;: &quot;image\u002Fpng&quot;\n      }\n    ],\n    &quot;start_url&quot;: &quot;.&quot;,\n    &quot;display&quot;: &quot;standalone&quot;,\n    &quot;background_color&quot;: &quot;#000000&quot;\n  }\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>寫法參考google一下就能找到很多，如果懶得找可以參考\u003Ca href=\"https:\u002F\u002Fwww.letswrite.tw\u002Fpwa-manifest\u002F\" title=\"這篇\">這篇\u003C\u002Fa>\u003C\u002Fp>\n\u003Ch2>觸發安裝PWA\u003C\u002Fh2>\n\u003Cp>如果希望主動觸發安裝效果，可以參考以下做法：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>setup() {\n    const states = reactive({\n      deferredPrompt: null,\n    });\n    onMounted(() =&gt; {\n      window.addEventListener(&quot;beforeinstallprompt&quot;, e =&gt; {\n        e.preventDefault();\n        states.deferredPrompt = e;\n      });\n      window.addEventListener(&quot;appinstalled&quot;, () =&gt; {\n        states.deferredPrompt = null;\n      });\n      document.querySelector(&quot;#app&quot;).addEventListener(&quot;click&quot;, () =&gt; { \n        if (states.deferredPrompt) {\n          states.deferredPrompt.prompt();\n          states.deferredPrompt = null;\n        }\n      });\n    });\n}\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>這是在app的\u003Cstrong>onMounted\u003C\u002Fstrong>完成後，點擊畫面中id為app元素時觸發PWA安裝詢問。\u003Cbr \u002F>\n原理為\u003C\u002Fp>\n\u003Col>\n\u003Cli>監聽 \u003Cem>beforeinstallprompt\u003C\u002Fem> 這個事件\u003C\u002Fli>\n\u003Cli>監聽到後把事件存起來\u003Cbr \u002F>\n3.監聽id為app的元素，當點擊事件發生時告知使用者這個 App 可以被安裝\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch2>備註\u003C\u002Fh2>\n\u003Cp>以上應該就能讓PWA正常運作，但記得此方式在IOS\u002FAndroid會有所不同。\u003Cbr \u002F>\nIOS不支援安裝提示，且Chrome不支援PWA安裝，哈哈，只能使用safari &gt; 分享 &gt; 加入主畫面，來使用PWA\u003Cbr \u002F>\nAndroid則都可以用，如果是IOS使用者可以先透過PC的Chrome來測試。\u003Cbr \u002F>\n很多人都說PWA是未來，但搞了這麼多年IOS支援度還是半屌子真令人憂心啊\u003Cbr \u002F>\n如果不清楚PWA可以參考這\u003Ca href=\"https:\u002F\u002Fithelp.ithome.com.tw\u002Fusers\u002F20071512\u002Fironman\u002F1222\" title=\"系列\">系列\u003C\u002Fa>\u003C\u002Fp>\n","2022-12-25T10:00:27","\u002Fapi\u002Fproxy-image?url=https%3A%2F%2Fpost.billxu.net%2Fwp-content%2Fuploads%2F2022%2F09%2Fframework_ionic.jpeg",{"name":88,"link":89},[190,191],{"name":22,"slug":22},{"name":61,"slug":61},"\u002Fblog\u002Fvueionic的vue使用pwa",{"id":194,"slug":195,"title":196,"description":197,"content":198,"date":199,"image":187,"imageAlt":196,"author":200,"categories":201,"path":204,"minRead":29},170,"vue透過ionic部署ios-app的幾點筆記","[Vue]透過ionic部署ios app的幾點筆記","透過ionic部署ios app的幾點筆記 ionic可以直接透過指令來部署android \u002F ios的app […]","\u003Ch1>透過ionic部署ios app的幾點筆記\u003C\u002Fh1>\n\u003Cp>ionic可以直接透過指令來部署android \u002F ios的app，這篇筆記一下部署過程遇到的一些問題。基本上還是透過Capacitor(似乎基還是cordova)實現跨平台部署，ionic裡面已經包你先包裝好了。流程指令可以參考\u003Ca href=\"https:\u002F\u002Fionicframework.com\u002Fdocs\u002Fvue\u002Fyour-first-app\u002Fdeploying-mobile\" title=\"此篇\">此篇\u003C\u002Fa>。\u003Cbr \u002F>\n首先先打包專案\u003C\u002Fp>\n\u003Cpre>\u003Ccode>ionic build\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>選擇要部署的裝置類型，因為這次是ios的實作所以使用了ios的指令\u003C\u002Fp>\n\u003Cpre>\u003Ccode>ionic cap add ios\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>注意喔，官方文件有說明\u003C\u002Fp>\n\u003Cblockquote>\n\u003Cp>Every time you perform a build (e.g. ionic build) that updates your web directory (default: build), you'll need to copy those changes into your native projects:\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Cp>因為cordova會將你的「網頁」專案放一份到app專案裡面，所以有內容更新要下此指令\u003C\u002Fp>\n\u003Cpre>\u003Ccode>ionic cap copy\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cblockquote>\n\u003Cp>Note: After making updates to the native portion of the code (such as adding a new plugin), use the sync command:\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Cp>或是使用同步指令，老實說我不確定兩者有什麼差異，這部分還得研究一下\u003C\u002Fp>\n\u003Cpre>\u003Ccode>ionic cap sync\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>到這邊後，要開始設定ios上的細節，部署ios一定得透過xcode，輸入指令打開xcode\u003C\u002Fp>\n\u003Cpre>\u003Ccode>ionic cap open ios\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>在xcode中，找到以下畫面\u003Cbr \u002F>\n\u003Cimg decoding=\"async\" src=\"\u002Fapi\u002Fproxy-image?url=https%3A%2F%2Fpost.billxu.net%2Fwp-content%2Fuploads%2F2022%2F10%2F%E6%88%AA%E5%9C%96-2022-10-29-%E4%B8%8B%E5%8D%889.03.11.png\" alt=\"\" \u002F>\u003Cbr \u002F>\n\u003Cem>signing &amp; capabilities\u003C\u002Fem>中設定\u003Cem>Team\u003C\u002Fem>和\u003Cem>Bundle Identifer\u003C\u002Fem>要設定，這邊基本上有設定好，就可以按xcode左上角的三角形進行編譯看看有沒有bug，若有就看log來除錯\u003Cbr \u002F>\n還不太懂的話可以參考\u003Ca href=\"https:\u002F\u002Fwww.cnblogs.com\u002Fchunzhulovefeiyue\u002Fp\u002F7512853.html\" title=\"此篇\">此篇\u003C\u002Fa>\u003C\u002Fp>\n\u003Ch3>live demo\u003C\u002Fh3>\n\u003Cp>到這邊成功的話，會發現專案中出現了ios資料夾，進入ios資料夾，可以進行live demo，將ios裝置接上mac\u003C\u002Fp>\n\u003Cpre>\u003Ccode>ionic cap run ios -l --external\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>這時應該可以在指令中看到自己的ios裝置名稱來進行部署，成功的話就能在ios裝置上看到自己的app囉\u003C\u002Fp>\n\u003Ch3>遇到問題\u003C\u002Fh3>\n\u003Cp>\u003Cem>Cocoapods No  'Podfile' found in the project directory.\u003C\u002Fem>\u003Cbr \u002F>\nCocoapods應該是Objective-C與Swift上使用的一種什麼東西，但我目前還不懂他是什麼XD\u003Ca href=\"https:\u002F\u002Fwww.appcoda.com.tw\u002Fcocoapods\u002F\" title=\"參考\">參考\u003C\u002Fa>\u003C\u002Fp>\n\u003Cp>查了一下，解決方式很簡單，輸入以下三個指令就好\u003C\u002Fp>\n\u003Cpre>\u003Ccode>pod init\n\nopen Podfile\n\npod install\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>但沒這麼容易XD我遇到了一些錯誤，這邊推薦老方式，重裝cocoapods(啊哈)\u003C\u002Fp>\n\u003Cpre>\u003Ccode>gem uninstall cocoapods\nbrew install cocoapods \u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>到這邊我就順利成功建立了cocoapods的podfile\u003C\u002Fp>\n","2022-12-14T14:00:52",{"name":88,"link":89},[202,203],{"name":22,"slug":22},{"name":61,"slug":61},"\u002Fblog\u002Fvue透過ionic部署ios-app的幾點筆記",{"id":206,"slug":207,"title":208,"description":209,"content":210,"date":211,"image":86,"imageAlt":208,"author":212,"categories":213,"path":216,"minRead":29},165,"vuevue3重新渲染元件的方式-vue3-rerender-a-component","[Vue]Vue3重新渲染元件的方式-Vue3 rerender a component","Vue3重新渲染元件的方式-Vue3 rerender a component 研究了一下vue3中如果要重新 […]","\u003Ch1>Vue3重新渲染元件的方式-Vue3 rerender a component\u003C\u002Fh1>\n\u003Cp>研究了一下vue3中如果要重新讀取元件(component)的作法，有時候真的無法靠資料更新之類的達到目的時，就必須重新讀取\u002F渲染元件。一開始要實作這件事時還真的沒什麼頭緒，直覺想到用v-if來實作，大概長這樣。\u003C\u002Fp>\n\u003Ch3>v-if法\u003C\u002Fh3>\n\u003Cp>簡單來說就是用renderComponent變數來控制component的起動\u003C\u002Fp>\n\u003Cpre>\u003Ccode>&lt;template&gt;\n    &lt;my-component v-if=&quot;renderComponent&quot; \u002F&gt;\n&lt;\u002Ftemplate&gt;\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>在setup中設定，nextTick是當畫面渲染完後再執行\u003C\u002Fp>\n\u003Cpre>\u003Ccode>import { nextTick, ref } from &#039;vue&#039;;\nconst renderComponent = ref(true);\n\nconst forceRerender = async () =&gt; {\n  \u002F\u002F 移除component的DOM\n  renderComponent.value = false;\n\n    \u002F\u002F 等待主畫面\n    await nextTick();\n\n    \u002F\u002F 重新加入這個component\n  renderComponent.value = true;\n};\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>再找個地方執行forceRerender就可以，基本原理就是將component移除、重新創建、掛載。\u003Cbr \u002F>\n但查了一下資料後，發現有更漂亮的作法。\u003C\u002Fp>\n\u003Ch3>鍵值改變驅動Key-Changing\u003C\u002Fh3>\n\u003Cp>我們只要新增一個屬性在component上，透過屬性值的改變就能觸發vue更新component。\u003C\u002Fp>\n\u003Cpre>\u003Ccode>&lt;template&gt;\n    &lt;my-component :key=&quot;componentKey&quot; \u002F&gt;\n&lt;\u002Ftemplate&gt;\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>透過forceRerender來更改componentKey的值\u003C\u002Fp>\n\u003Cpre>\u003Ccode>import { ref } from &#039;vue&#039;;\nconst componentKey = ref(0);\n\nconst forceRerender = () =&gt; {\n  componentKey.value += 1;\n};\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>接下來只要觸發forceRerender，就能更新component。\u003Cbr \u002F>\n但老實說我其實還不太確定為什麼這個做法較為優秀，歡迎留言討論XD\u003C\u002Fp>\n\u003Cp>很推薦參考這篇\u003Ca href=\"https:\u002F\u002Fmichaelnthiessen.com\u002Fforce-re-render\u002F\">參考資料\u003C\u002Fa>\u003C\u002Fp>\n","2022-12-10T10:00:13",{"name":88,"link":89},[214,215],{"name":22,"slug":22},{"name":61,"slug":61},"\u002Fblog\u002Fvuevue3重新渲染元件的方式-vue3-rerender-a-component",{"id":218,"slug":219,"title":220,"description":221,"content":222,"date":223,"image":86,"imageAlt":220,"author":224,"categories":225,"path":229,"minRead":29},225,"vue使用vue3打造trello式的工作看板","[Vue]使用Vue3打造trello式的工作看板","使用vue3打造trello式的工作看板 今年參加了{Laravel x Vue}Conf Taiwan，第一 […]","\u003Ch1>使用vue3打造trello式的工作看板\u003C\u002Fh1>\n\u003Cp>今年參加了\u003Ca href=\"https:\u002F\u002Flaravelconf.tw\u002F\" title=\"{Laravel x Vue}Conf Taiwan\">{Laravel x Vue}Conf Taiwan\u003C\u002Fa>，第一天工作坊主題是使用Vue3打造類似trello的工作看板，藉此重新檢視自己在使用Vue3的習慣與詢問一些自學想不透的問題。\u003Cbr \u002F>\n很感謝Kuro的推廣與分享，幫忙打廣告一下，有興趣學Vue的人請務必購買「\u003Ca href=\"https:\u002F\u002Fwww.tenlong.com.tw\u002Fproducts\u002F9789864345687\" title=\"重新認識 Vue.js：008天絕對看不完的 Vue.js 3 指南\">重新認識 Vue.js：008天絕對看不完的 Vue.js 3 指南\u003C\u002Fa>」XDDDDD\u003Cbr \u002F>\n先附上本次工作坊的\u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fbillxu0521\u002Fvue-trello-app\" title=\"repo\">repo\u003C\u002Fa>與\u003Ca href=\"https:\u002F\u002Fvue-trello-app.vercel.app\" title=\"demo\">demo\u003C\u002Fa>。\u003C\u002Fp>\n\u003Ch2>專案目標\u003C\u002Fh2>\n\u003Cp>本次專案是打造一個類似trello的工作記錄看板，工作坊的學習目標為以下：\u003C\u002Fp>\n\u003Col>\n\u003Cli>認識 vite 與 Vue.js 開發環境設定\u003C\u002Fli>\n\u003Cli>熟悉 Vue.js 3.x Compontions 與 Setup script\u003C\u002Fli>\n\u003Cli>使用 Tailwind CSS 結合 Vue.js 開發網頁介面\u003C\u002Fli>\n\u003Cli>使用 Pinia 管理跨元件狀態\u003C\u002Fli>\n\u003Cli>使用 Vue Router 實作前端路由功能 (edited)\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>使用技術如下：\u003C\u002Fp>\n\u003Cul>\n\u003Cli>Vite 3.1.8\u003C\u002Fli>\n\u003Cli>Vue.js 3.2\u003C\u002Fli>\n\u003Cli>Vue Router 4.1.5\u003C\u002Fli>\n\u003Cli>Pinia 2.0\u003C\u002Fli>\n\u003Cli>Vue Use\u003C\u002Fli>\n\u003Cli>Tailwindcss 3.2.14\u003C\u002Fli>\n\u003Cli>Vue.draggable.next\u003C\u002Fli>\n\u003Cli>unplugin-vue-components\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>這邊就不做製作步驟，主要紀錄自己學到的重點，有問題的人可以留言給我。\u003C\u002Fp>\n\u003Ch2>工作坊筆記\u003C\u002Fh2>\n\u003Ch3>script setup 寫法\u003C\u002Fh3>\n\u003Cp>Vue3提供了setup的寫法，通常有兩種一種是傳統的export default\u003C\u002Fp>\n\u003Cpre>\u003Ccode>export default  {\n  setup() {\n  \u002F\u002F內容\n  }\n  }\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>另一種為script setup 寫法\u003C\u002Fp>\n\u003Cpre>\u003Ccode>&lt;script setup&gt;\n\u002F\u002F內容\n&lt;\u002Fscript&gt;\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>這邊建議是盡可能採用script setup 寫法，官方也較推薦這種撰寫方式，雖然要習慣一下，但好處是可以簡化程式碼，實際寫過真的感覺很清爽！\u003C\u002Fp>\n\u003Ch3>Vue Use\u003C\u002Fh3>\n\u003Cp>這是一個滿實用的Vue工作組，在這個專案中導入是為了 focus 與 onClickOutside這兩支api，可以協助我們快速處理元素的聚焦與脫離，像是在trello中，點擊某個卡片會跳出燈箱(lightbox)以及點擊燈箱後方的遮罩可以關閉燈箱這種流程。\u003C\u002Fp>\n\u003Ch3>v-bind傳遞物件到子元件\u003C\u002Fh3>\n\u003Cp>之前我會使用prop傳遞資料到子元件像是這樣\u003C\u002Fp>\n\u003Cpre>\u003Ccode>&lt;TaskItem\n          :id=&quot;element.id&quot;\n          :title=&quot;element.title&quot;\n          :tasks=&quot;element.tasks&quot;\n        \u002F&gt;\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>但其實物件可以用v-bind來傳遞寫法如下：\u003C\u002Fp>\n\u003Cpre>\u003Ccode> &lt;TaskItem\n          v-bind=&quot;element&quot;\n        \u002F&gt;\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>而在子元件中需要這樣接值\u003C\u002Fp>\n\u003Cpre>\u003Ccode>&lt;script setup&gt;\nimport { ref, computed } from &quot;vue&quot;;\nconst props = defineProps({\n  id: String,\n  title: String,\n  content: String,\n}); \n&lt;\u002Fscript&gt;\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>如果會對資料修改，那記得要再宣告一個變數來接避免污染父元件的物件喔\u003C\u002Fp>\n\u003Cpre>\u003Ccode>const title = ref(props.title);\n\u002F\u002F修改title\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>Pinia\u003C\u002Fh3>\n\u003Cp>類似Vuex的狀態管理套件，超好用啊，我其實沒用過Vuex，只有用過原生的依賴注入，Pinia的使用真的方便很多。\u003Cbr \u002F>\n這邊就不寫使用方法了，請參考\u003Ca href=\"https:\u002F\u002Fpinia.vuejs.org\u002F\" title=\"官方資料\">官方資料\u003C\u002Fa>\u003C\u002Fp>\n\u003Ch4>取用函式與值\u003C\u002Fh4>\n\u003Cp>要注要透過Pinia取用函式與值的作法\u003Cbr \u002F>\n如果closeEditTask是函式，那可以用解構方式來引用，但這樣的話就無法觀察到物件中的狀態或值\u003C\u002Fp>\n\u003Cpre>\u003Ccode>以下兩種相同\nconst { closeEditTask } = store;  \u002F\u002F物件解構\nconst { closeEditTask } = store.closeEditTask;  \n解構無法被觀察\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>如果是狀態或值，則需要computed\u003C\u002Fp>\n\u003Cpre>\u003Ccode>const currentEditTask = computed(() =&gt; store.currentEditTask); \u002F\u002FcurrentEditTask這是狀態\n因為要關注狀態所以用computed\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>在script中取的DOM\u003C\u002Fh3>\n\u003Cp>想取得DOM內容的話可以用ref，像這樣\u003C\u002Fp>\n\u003Cpre>\u003Ccode>const target = ref();\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>然後網頁元入記得宣告\u003C\u002Fp>\n\u003Cpre>\u003Ccode>&lt;textarea\n      ref=&quot;target&quot;    \n    &gt;&lt;\u002Ftextarea&gt;\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>這樣就可以透過target來操作textarea的內容\u003C\u002Fp>\n\u003Ch3>偵測自己的click\u003C\u002Fh3>\n\u003Cp>有點像以前寫jQuery的監聽，對某元素監聽時要加上this不然會監聽到所有同元素，vue的@click也是\u003C\u002Fp>\n\u003Cpre>\u003Ccode>&lt;div\n    @click.self=&quot;closeEditTask&quot;\n  &gt;\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>src中的compoment與views命名習慣\u003C\u002Fh3>\n\u003Cp>這不是強制，習慣上只會在某地方特定使用一次的可以放在\u003Cstrong>views\u003C\u002Fstrong>中，例如首頁，如果是重複使用的元件則用\u003Cstrong>compoment\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Ch3>Vue.draggable.next\u003C\u002Fh3>\n\u003Cp>支援拖曳效果的套件，附上\u003Ca href=\"https:\u002F\u002Fsortablejs.github.io\u002Fvue.draggable.next\u002F#\u002Fsimple\" title=\"官方說明\">官方說明\u003C\u002Fa>\u003C\u002Fp>\n\u003Ch3>unplugin-vue-components\u003C\u002Fh3>\n\u003Cp>超棒的玩意，可以直接以命名呼叫compoment，以往我們要呼叫compoment通常要先引入\u003C\u002Fp>\n\u003Cpre>\u003Ccode>import {xxx} from  &quot;xxx&quot;\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>但這個套件可以直接省略import，以原名就能直接呼叫，太棒啦！\u003C\u002Fp>\n","2022-12-04T13:36:23",{"name":88,"link":89},[226,227,228],{"name":22,"slug":22},{"name":43,"slug":43},{"name":61,"slug":61},"\u002Fblog\u002Fvue使用vue3打造trello式的工作看板",{"id":231,"slug":232,"title":233,"description":234,"content":235,"date":236,"image":86,"imageAlt":233,"author":237,"categories":238,"path":241,"minRead":29},160,"vuevue3引入vue-meta的方式","[Vue]Vue3引入vue-meta的方式","Vue3引入vue-meta的方式 如果要在vue中動態引入Html meta tag，可以使用vue-met […]","\u003Ch2>Vue3引入vue-meta的方式\u003C\u002Fh2>\n\u003Cp>如果要在vue中動態引入Html meta tag，可以使用vue-meta，但vue3需要使用3.0.0，預設安裝會是2.4.0，這邊記錄一下實作過程。其實可以參考官方說明：\u003Ca href=\"https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002Fvue-meta\u002Fv\u002F3.0.0-alpha.10\" title=\"參考文件\">參考文件\u003C\u002Fa>，流程如下：\u003C\u002Fp>\n\u003Ch3>調整版本\u003C\u002Fh3>\n\u003Cp>調整vue-meta版本，於\u003Cem>package.json\u003C\u002Fem>中修改\u003C\u002Fp>\n\u003Cpre>\u003Ccode>- &quot;vue-meta&quot;: &quot;^2.4.0&quot;,\n+ &quot;vue-meta&quot;: &quot;^3.0.0-alpha.7&quot;,\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>修改完後記得重新安裝套件\u003C\u002Fp>\n\u003Cpre>\u003Ccode>npm install\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>為Vue App引入\u003C\u002Fh3>\n\u003Cp>在main.js中加入createMetaManager\u003C\u002Fp>\n\u003Cpre>\u003Ccode>import { createMetaManager } from &#039;vue-meta&#039;\n\nconst app = createApp(App)\n  .use(router)\n  .use(store)\n  .use(createMetaManager()) \u002F\u002F add this line\n\nawait router.isReady()\napp.mount(&#039;#app&#039;)\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>在App.vue中加入顯示tag\u003C\u002Fh3>\n\u003Cp>於App.vue中加入顯示tag，並且加上顯示tag的範本，以title為例子\u003C\u002Fp>\n\u003Cpre>\u003Ccode>&lt;template&gt;\n  &lt;metainfo&gt;\n    &lt;template v-slot:title=&quot;{ content }&quot;&gt;{{ content ? `${content} | SITE_NAME` : `SITE_NAME` }}&lt;\u002Ftemplate&gt;\n  &lt;\u002Fmetainfo&gt;\n  &lt;header \u002F&gt;\n  &lt;router-view \u002F&gt;\n  &lt;footer \u002F&gt;\n&lt;\u002Ftemplate&gt;\n\n&lt;script&gt;\nimport { useMeta } from &#039;vue-meta&#039;\n\nexport default {\n  setup () {\n    useMeta({\n      title: &#039;&#039;,\n      htmlAttrs: { lang: &#039;zh-tw&#039; }\n    })\n  }\n}\n&lt;\u002Fscript&gt;\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>到這邊應該可以看到預設顯示的字串了「SITE_NAME」。\u003C\u002Fp>\n\u003Ch3>為個別元件加上tag\u003C\u002Fh3>\n\u003Cp>在不同元件加上tag，切換過去後會顯示\u003C\u002Fp>\n\u003Cpre>\u003Ccode>import { useMeta } from &#039;vue-meta&#039;\n\nexport default {\n  setup () {\n    useMeta({ title: &#039;Single Page&#039; })\n  }\n}\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>設定成功的話，在個別元件中會看到標題已經是「Single Page｜SITE_NAME」。\u003Ca href=\"https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002Fvue-meta\u002Fv\u002F3.0.0-alpha.10\" title=\"參考文件\">參考文件\u003C\u002Fa>\u003C\u002Fp>\n","2022-11-28T20:00:34",{"name":88,"link":89},[239,240],{"name":22,"slug":22},{"name":61,"slug":61},"\u002Fblog\u002Fvuevue3引入vue-meta的方式",{"id":243,"slug":244,"title":245,"description":246,"content":247,"date":248,"image":86,"imageAlt":245,"author":249,"categories":250,"path":254,"minRead":29},158,"vue使用vue-gtag串接google-analytics","[Vue]使用vue-gtag串接google analytics","使用vue-gtag串接google analytics 其實這篇只是暫時紀錄，因為我還是搞不動為什麼可以運作 […]","\u003Ch1>使用vue-gtag串接google analytics\u003C\u002Fh1>\n\u003Cp>其實這篇只是暫時紀錄，因為我還是搞不動為什麼可以運作....XD與其講Vue不如說是GTM和GA4串接上的一些狀況整理。\u003C\u002Fp>\n\u003Cp>總之呢，準備要在Vue專案上串接GA(google analytics)，找了一下，通常會推薦vue-gtag(vue-analytics已經不維護了，建議用vue-gtag)。於是參考了https:\u002F\u002Fmatteo-gabriele.gitbook.io\u002Fvue-gtag\u002F\u003Ca href=\"https:\u002F\u002Fmatteo-gabriele.gitbook.io\u002Fvue-gtag\u002F\" title=\"原作者說明\">原作者說明\u003C\u002Fa>就開始實作。\u003C\u002Fp>\n\u003Ch2>vue-gtag基本安裝\u003C\u002Fh2>\n\u003Cp>按照原說明文件，先安裝vue-gtag\u003C\u002Fp>\n\u003Cpre>\u003Ccode>$ npm add vue-gtag\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>然後在main.js引入vuegtag，\u003Cstrong>GA_MEASUREMENT_ID\u003C\u002Fstrong>就使用從GA服務開啟後取得的「評估ID」\u003C\u002Fp>\n\u003Cpre>\u003Ccode>import { createApp } from &quot;vue&quot;;\nimport App from &quot;.\u002FApp.vue&quot;;\nimport VueGtag from &quot;vue-gtag&quot;;\n\ncreateApp(App).use(VueGtag, {\n  config: { id: &quot;GA_MEASUREMENT_ID&quot; }\n}).mount(&quot;#app&quot;);\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>理論上這樣就完成了。然而GA4的串流提示老樣子可能會等數小時才有反應，於是我就等了.....48小時，然後依然沒反應，登愣\u003Cbr \u002F>\n由於GA4我還沒有用程式碼引入方式串接過，想說那誒啊捏，變這麼難接喔。\u003Cbr \u002F>\n稍微查了一下，結果....毫無收穫XDDD\u003Cbr \u002F>\n好吧，只好換方向來處理，既然GA4的很多說明跟GTM(google tag manager)有關係，那就用GTM來實作看看好了。\u003C\u002Fp>\n\u003Ch2>GTM串接GA4\u003C\u002Fh2>\n\u003Cp>基本流程如下：\u003C\u002Fp>\n\u003Col>\n\u003Cli>在GTM中開啟一個工作區\u003C\u002Fli>\n\u003Cli>加入代碼：選擇GA4設定\u003C\u002Fli>\n\u003Cli>評估ID填入GA中取得的「評估ID」\u003C\u002Fli>\n\u003Cli>觸發條件選擇：All page\u003C\u002Fli>\n\u003Cli>儲存設定，再來發布容器\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>回到Vue專案中，你申請完GTM工作區後應該會取的一組gtag，會長這樣：GTM-XXXXXX，將這個識別ID加回到到main.js，沒錯就是這串\u003C\u002Fp>\n\u003Cpre>\u003Ccode>import { createApp } from &quot;vue&quot;;\nimport App from &quot;.\u002FApp.vue&quot;;\nimport VueGtag from &quot;vue-gtag&quot;;\n\ncreateApp(App).use(VueGtag, {\n  config: { id: &quot;GA_MEASUREMENT_ID&quot; } &lt;---這個\n}).mount(&quot;#app&quot;);\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>設定完後，可以開啟GTM中的預覽來檢查，建議chrome可以裝一個擴充「Tag Assistant」以方便檢視是否有安裝順利。\u003C\u002Fp>\n\u003Ch3>遇到問題\u003C\u002Fh3>\n\u003Cp>其實上述做完後，我在GTM的預覽模式中還是會失敗，「Tag Assistant」提示我代碼有放，但會無法取得一個測試用的js，目前尚未找到原因，但在GA的即時模式中已經可以看到資料進來了，只好慢慢找問題與調整了，如果有人遇到類似問題歡迎一起討論。\u003C\u002Fp>\n","2022-11-20T10:00:15",{"name":88,"link":89},[251,252,253],{"name":22,"slug":22},{"name":43,"slug":43},{"name":61,"slug":61},"\u002Fblog\u002Fvue使用vue-gtag串接google-analytics",{"id":256,"slug":257,"title":258,"description":259,"content":260,"date":261,"image":86,"imageAlt":258,"author":262,"categories":263,"path":266,"minRead":29},153,"vuevue3中子元件傳資料給父元件的作法","[Vue]Vue3中子元件傳資料給父元件的作法","Vue3中子元件傳資料給父元件的作法 隨著使用Vue越來越深入，一定會遇到父子元件傳資料的需求，我想有稍微研究 […]","\u003Ch1>Vue3中子元件傳資料給父元件的作法\u003C\u002Fh1>\n\u003Cp>隨著使用Vue越來越深入，一定會遇到父子元件傳資料的需求，我想有稍微研究過一定知道這個口訣「Props in, Event out」XD，父傳子用props，子傳父用event。那今天就來記錄我實作時遇到的狀況。\u003C\u002Fp>\n\u003Cp>在運作上，子元件透過觸發「事件event」的作法來更新父元件的資料，Vue3中使用了\u003Cstrong>v-on\u003C\u002Fstrong>來實作，父元件需要有個監聽事件如下：\u003C\u002Fp>\n\u003Ch1>parent-component.js\u003C\u002Fh1>\n\u003Cpre>\u003Ccode>&lt;child-component\n    @listenevent=&quot;updateEvent&quot;\n>&lt;\u002Fchild-component&gt;\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cpre>\u003Ccode>&lt;script&gt;\nexport default defineComponent({\n   setup() {\n\n    },\n  methods: {\n    updateEvent(val) {\n      \u002F\u002F這邊會被觸發\n      \u002F\u002F做你想做的事\n      console.log(val)\n    }\n  }\n});\n&lt;\u002Fscript&gt;\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>先準備一個listenevent監聽事件，取名為updateEvent，當子元件打資料過來時會觸發updateEvent。\u003C\u002Fp>\n\u003Ch3>child-component.js\u003C\u002Fh3>\n\u003Cpre>\u003Ccode>&lt;script&gt;\nexport default defineComponent({\n   setup(_,{ emit }) {\n    const handleChange = (value) =&gt; {\n        emit(&quot;listenevent&quot;,value)\n    }\n    onMounted(() =&gt; {\n      handleChange(&quot;hello&quot;)\n      });\n    },\n});\n&lt;\u002Fscript&gt;\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>接著在子元件增加觸發\u003Cstrong>listenevent\u003C\u002Fstrong>事件的方式，如上\u003Cstrong>handleChange\u003C\u002Fstrong>，範例是onMounted時會觸發\u003Cstrong>handleChange\u003C\u002Fstrong>，這時會打&quot;hello&quot;回到父元件，這時父元件的\u003Cstrong>updateEvent\u003C\u002Fstrong>就會被觸發，應該就能看到&quot;hello&quot;囉。\u003C\u002Fp>\n\u003Cp>如果出現「...vue 3 emit warning &quot; Extraneous non-emits event listeners&quot;...」的警告提示，記得子元件要加上emits的宣告。\u003C\u002Fp>\n\u003Ch3>child-component.js\u003C\u002Fh3>\n\u003Cpre>\u003Ccode>&lt;script&gt;\nexport default defineComponent({\n   emits: [&quot;getGameitle&quot;], \u002F\u002F&lt;---加上宣告\n   setup(_,{ emit }) {\n    const handleChange = (value) =&gt; {\n        emit(&quot;listenevent&quot;,value)\n    }\n    onMounted(() =&gt; {\n      handleChange(&quot;hello&quot;)\n      });\n    },\n});\n&lt;\u002Fscript&gt;\u003C\u002Fcode>\u003C\u002Fpre>\n","2022-11-14T20:00:08",{"name":88,"link":89},[264,265],{"name":22,"slug":22},{"name":61,"slug":61},"\u002Fblog\u002Fvuevue3中子元件傳資料給父元件的作法",{"id":268,"slug":269,"title":270,"description":271,"content":272,"date":273,"image":187,"imageAlt":270,"author":274,"categories":275,"path":278,"minRead":29},150,"vueionic中將資料與style綁定的方式","[Vue]ionic中將資料與style綁定的方式","ionic中將資料與style綁定的方式 在Vue中如果需要資料丟到style中，例如動態讀取背景圖片，一種寫 […]","\u003Ch1>ionic中將資料與style綁定的方式\u003C\u002Fh1>\n\u003Cp>在Vue中如果需要資料丟到style中，例如動態讀取背景圖片，一種寫法是這樣。\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-html\">&lt;template&gt;\n    &lt;div v-for=&quot;(item) in data&quot;&gt;\n        &lt;div class=&quot;image-container&quot; v-bind:style=&quot;{ backgroundImage: &#039;url(&#039; + item.image + &#039;)&#039; }&quot;&gt;\n    &lt;\u002Fdiv&gt;\n    &lt;\u002Fdiv&gt;\n&lt;\u002Ftemplate&gt;\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>或是這樣\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-html\">&lt;template&gt;\n      &lt;div class=&quot;image-container&quot;&gt;\n        Image Container\n      &lt;\u002Fdiv&gt;\n&lt;\u002Ftemplate&gt;\n\n&lt;script &gt;\nconst props = {\n    image: &quot;xxxxxxx&quot;,\n  }\n&lt;\u002Fscript &gt;\n&lt;style&gt;\n.image-container{\n    background: v-bind(&quot;props.image&quot;) no-repeat left center fixed;\n}\n&lt;\u002Fstyle&gt;\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>第二種寫法在ionic中可以使用，但第一種則會判讀不到，需要改成這樣寫：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-html\">&lt;template&gt;\n    &lt;div v-for=&quot;(item) in data&quot;&gt;\n        &lt;div class=&quot;image-container&quot;  :style=&quot;`background-image: url(${image});`&quot;&gt;&lt;\u002Fdiv&gt;\n    &lt;\u002Fdiv&gt;\n&lt;\u002Ftemplate&gt;\u003C\u002Fcode>\u003C\u002Fpre>\n","2022-11-08T13:00:29",{"name":88,"link":89},[276,277],{"name":22,"slug":22},{"name":61,"slug":61},"\u002Fblog\u002Fvueionic中將資料與style綁定的方式",{"id":280,"slug":281,"title":282,"description":283,"content":284,"date":285,"image":286,"imageAlt":282,"author":287,"categories":288,"path":292,"minRead":29},147,"nodeaxios自動加上網址","[Node]axios自動加上網址","axios自動加上網址 我使用了axios來實作vue中請求資料的部分，但網站中有許多地方要請求不同地方的資料 […]","\u003Ch1>axios自動加上網址\u003C\u002Fh1>\n\u003Cp>我使用了axios來實作vue中請求資料的部分，但網站中有許多地方要請求不同地方的資料時，就需要有個地方統一管理這個位置，這時就可以用到axios.baseURL\u003C\u002Fp>\n\u003Cp>只要在main.js中宣告axios後加上這行\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-javascript\">import axios from &#039;axios&#039;;\n\naxios.defaults.baseURL=&#039;http:\u002F\u002Flocalhost:3031&#039;;  \u002F\u002F宣告baseURL\n\napp.use(VueAxios, axios)\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>之後只要使用了axios，他會自動加上baseURL，例如\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-javascript\">let url = &#039;\u002Fapi&#039;\n axios.get(url)\n .then((res)=&gt;{\n\n })\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>他就會送出這個位置http:\u002F\u002Flocalhost:3031\u002Fapi\u003C\u002Fp>\n\u003Cp>通常還會搭配.env文件來使用，將環境變數先存在.env中，例如建立一個.env.dev的文件，裡面寫下\u003C\u002Fp>\n\u003Cpre>\u003Ccode>NODE_ENV=development\nAPI_URL=http:\u002F\u002Flocalhost:3031\u002F\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>然後在main.js中串上env的環境變數就可以了\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-javascript\">import axios from &#039;axios&#039;;\n\naxios.defaults.baseURL=process.env.API_URL\n\u003C\u002Fcode>\u003C\u002Fpre>\n","2022-11-02T10:00:27","",{"name":88,"link":89},[289,290,291],{"name":22,"slug":22},{"name":43,"slug":43},{"name":61,"slug":61},"\u002Fblog\u002Fnodeaxios自動加上網址",{"id":294,"slug":295,"title":296,"description":297,"content":298,"date":299,"image":187,"imageAlt":296,"author":300,"categories":301,"path":304,"minRead":29},136,"vueionic中typescript改寫為javascript的一些做法","[Vue]ionic中TypeScript改寫為JavaScript的一些做法","ionic中TypeScript改寫為JavaScript的一些做法 好吧，這問題完全是個人還沒學TypeSc […]","\u003Ch1>ionic中TypeScript改寫為JavaScript的一些做法\u003C\u002Fh1>\n\u003Cp>好吧，這問題完全是個人還沒學TypeScript的關係所衍生出來的(遮臉)，使用ionic的vue版本時，預設會有大量TypeScript的寫法，官方有說他們偏好TypeScript，但實際使用時可以關閉TypeScript(\u003Ca href=\"https:\u002F\u002Fionicframework.com\u002Fdocs\u002Fvue\u002Fquickstart\" title=\"參考如下\">參考\u003C\u002Fa>)，由於個人對TypeScript沒那麼熟，所以這次開發就關光光了，但也出現這次得狀況。\u003C\u002Fp>\n\u003Cp>這次在\u003Ca href=\"https:\u002F\u002Fionicframework.com\u002Fdocs\u002Fapi\u002Finfinite-scroll\" title=\"ion-infinite-scroll\">ion-infinite-scroll\u003C\u002Fa>中遇到一些問題，像是(可以參考連結中的範例)：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-javascript\">const items = ref&lt;number[]&gt;([]);\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>對我來說這是一個陌生的寫法，一開始看到錯誤還在想到底哪邊出了問題，回想了一下發現這應該是個ts的寫法，從前後文推測這只是一個陣列宣告，於是改寫成這樣就通了。\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-javascript\">const items = [];\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>另外像是\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-javascript\">const loadData = (ev: InfiniteScrollCustomEvent) =&gt; {\n      setTimeout(() =&gt; {\n        pushData();\n        console.log(&#039;Loaded data&#039;);\n        ev.target.complete();\n        \u002F\u002F App logic to determine if all data is loaded\n        \u002F\u002F and disable the infinite scroll\n        if (items.value.length === 1000) {\n          ev.target.disabled = true;\n        }\n      }, 500);\n    }\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>其實也是一般的箭頭函式，ev宣告是ts的寫法，所以這樣改寫也就可以囉。\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-javascript\">const loadData = (event) =&gt; {\n      setTimeout(() =&gt; {\n        pushData();\n        console.log(&#039;Loaded data&#039;);\n        event.target.complete();\n\n        \u002F\u002F App logic to determine if all data is loaded\n        \u002F\u002F and disable the infinite scroll\n        if (skipitems === 2000) {\n          event.target.disabled = true;\n        }\n      }, 500);\n    }\u003C\u002Fcode>\u003C\u002Fpre>\n","2022-10-02T16:10:02",{"name":88,"link":89},[302,303],{"name":22,"slug":22},{"name":61,"slug":61},"\u002Fblog\u002Fvueionic中typescript改寫為javascript的一些做法",{"id":306,"slug":307,"title":308,"description":309,"content":310,"date":311,"image":286,"imageAlt":308,"author":312,"categories":313,"path":316,"minRead":29},133,"vueionic中取得route資訊的方式","[Vue]ionic中取得route資訊的方式","ionic中取得route資訊 一般在Vue中想取得url的參數或是網域位置，如果有使用vue-route的話 […]","\u003Ch1>ionic中取得route資訊\u003C\u002Fh1>\n\u003Cp>一般在Vue中想取得url的參數或是網域位置，如果有使用vue-route的話，可以直接取用，方法如下\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-vue\">const params = $route.params\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>或是在3.0要這要宣告\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-vue\">const params = this.$route.params\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>例如localhost\u002Ftest?username=橫隔膜，就可以以下面形式取出。\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-vue\">const params = this.$route.params\nconst username = params.username\n\u002F\u002Fparams : test\n\u002F\u002Fusername : 橫隔膜\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>但最近使用ionic做這這件時，這樣宣告會一直跳出錯誤，發現不能直接取用this.$route，甚至不能用this。找了一段時間終於在ionic的\u003Ca href=\"https:\u002F\u002Fionicframework.com\u002Fdocs\u002Fvue\u002Fnavigation\" title=\"說明文件\">說明文件\u003C\u002Fa>中找到正確寫法，其實只要拿掉&quot;$&quot;就可以囉。\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-vue\">const params= route.params;\u003C\u002Fcode>\u003C\u002Fpre>\n","2022-09-27T16:10:00",{"name":88,"link":89},[314,315],{"name":22,"slug":22},{"name":61,"slug":61},"\u002Fblog\u002Fvueionic中取得route資訊的方式",{"id":318,"slug":319,"title":320,"description":321,"content":322,"date":323,"image":286,"imageAlt":320,"author":324,"categories":325,"path":328,"minRead":29},72,"vue在docker中hot-reloading失效的問題","[Vue]在docker中Hot-Reloading失效的問題","Vue在docker中hot reload失效的問題 這陣子在嘗試在開發流程中導入docker，有次寫Vue時 […]","\u003Ch1>Vue在docker中hot reload失效的問題\u003C\u002Fh1>\n\u003Cp>這陣子在嘗試在開發流程中導入docker，有次寫Vue時就發生了標題所述的狀況。\u003Cbr \u002F>\n通常我們在啟動node之後，修改文檔不用重啟服務，就能直接在網頁看到調整的結果，在Vue中是透過Hot-Reloading這個機制，偵測文檔是否變動過而重新載入到服務中。\u003Cbr \u002F>\n但掛載到docker後我發現這個機制失效了，一開始以為是設定問題，找了幾種方式來重啟Hot-Reloading結果都失敗，最後發現是docker掛在volumes時似乎無法偵測到host端的檔案修改裝態。\u003C\u002Fp>\n\u003Ch2>解決方式\u003C\u002Fh2>\n\u003Cp>解決方式很簡單，設定CHOKIDAR_USEPOLLING這個環境參數即可。\u003Cbr \u002F>\n因為我是用docker-compose，所以設定方式如下：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-yml\">version: &quot;3.3&quot;\n\nservices: \n  my_app:\n    ....\n    ports:\n      - &quot;3030:3000&quot;\n    volumes:\n      - \u002Fpath\u002Fon\u002Flaptop:\u002Ftarget\u002Fin\u002Fcontainer\n    stdin_open: true\n    tty: true\n    environment:\n      - ...\n      - CHOKIDAR_USEPOLLING=true #加上這個\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>如果用Dockerfile則也可以用指令方式呼CHOKIDAR_USEPOLLING\u003Cbr \u002F>\n這樣就能在容器中偵測volumes的檔案變化囉！\u003C\u002Fp>\n\u003Cp>可以參考以下資料\u003Cbr \u002F>\n\u003Ca href=\"https:\u002F\u002Fdaten-und-bass.io\u002Fblog\u002Fenabling-hot-reloading-with-vuejs-and-vue-cli-in-docker\u002F\">https:\u002F\u002Fdaten-und-bass.io\u002Fblog\u002Fenabling-hot-reloading-with-vuejs-and-vue-cli-in-docker\u002F\u003C\u002Fa>\u003C\u002Fp>\n\u003Cp>\u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fpaulmillr\u002Fchokidar\">https:\u002F\u002Fgithub.com\u002Fpaulmillr\u002Fchokidar\u003C\u002Fa>\u003C\u002Fp>\n","2022-07-15T14:00:10",{"name":88,"link":89},[326,327],{"name":22,"slug":22},{"name":61,"slug":61},"\u002Fblog\u002Fvue在docker中hot-reloading失效的問題"]