Node + Nuxt.js + Ant Design VueでTABLEを作るとき、それぞれの行の値を抽出する方法

NuxtのUIコンポーネントはいくつかあって、Reactからの移植版も多いのでカオスになっていますね。

以前はVuetifyをメインで使っていたのですが、機能がまだ不足気味です。

TABLEで検索機能や固定ヘッダを使いたい場合、今のところReactからの移植版Ant Design Vue一択ではないでしょうか。

例えばIDとNAMEというカラムがあるTABLEを作っていたとします。

NAMEのリンク先をIDを使って動的にしたい場合が多々あると思うのですが、どのUIコンポーネントもどうやってリンクをはればいいのかサッパリわからないです。

Vuetifyのときもかなりググってpropsというobjectの中に入っていることを突き止めました。

Ant Designの場合は結局見つからず、マニュアルを見ながら一つ一つ確かめて、keyというobjectに入ることがわかりました。

マニュアルの下の方に一応書いてあるのですが、まったく意味がわかりませんでしたよw

実際のコードの例です。
Ant Design Vueのcustomized-filter-panelを使ったとします。

<template>
  <a-table :dataSource="data" :columns="columns">
    <div
      slot="filterDropdown"
      slot-scope="{ setSelectedKeys, selectedKeys, confirm, clearFilters, column }"
      style="padding: 8px"
    >
      <a-input
        v-ant-ref="c => searchInput = c"
        :placeholder="`Search ${column.dataIndex}`"
        :value="selectedKeys[0]"
        @change="e => setSelectedKeys(e.target.value ? [e.target.value] : [])"
        @pressEnter="() => handleSearch(selectedKeys, confirm)"
        style="width: 188px; margin-bottom: 8px; display: block;"
      />
      <a-button
        type="primary"
        @click="() => handleSearch(selectedKeys, confirm)"
        icon="search"
        size="small"
        style="width: 90px; margin-right: 8px"
      >Search</a-button>
      <a-button @click="() => handleReset(clearFilters)" size="small" style="width: 90px">Reset</a-button>
    </div>
    <a-icon
      slot="filterIcon"
      slot-scope="filtered"
      type="search"
      :style="{ color: filtered ? '#108ee9' : undefined }"
    />
    <!-- ここに設定を追加 -->
    <template slot="customRender" slot-scope="text,key">
      <span v-if="searchText">
        <!-- ここにリンク設定を追加 -->
        <nuxt-link :to="`/hoge/`+key.key">
          <template
            v-for="(fragment, i) in text.toString().split(new RegExp(`(?<=${searchText})|(?=${searchText})`, 'i'))"
          >
            <mark
              v-if="fragment.toLowerCase() === searchText.toLowerCase()"
              :key="i"
              class="highlight"
            >{{fragment}}</mark>
            <template v-else>{{fragment}}</template>
          </template>
        </nuxt-link>
      </span>
      <template v-else>
        <!-- ここにリンク設定を追加 -->
        <nuxt-link :to="`/hoge/`+key.key">{{text}}</nuxt-link>
      </template>
    </template>
  </a-table>
</template>

<script>
const data = [
  {
    key: "1",
    name: "John Brown",
    age: 32,
    address: "New York No. 1 Lake Park"
  },
  {
    key: "2",
    name: "Joe Black",
    age: 42,
    address: "London No. 1 Lake Park"
  },
  {
    key: "3",
    name: "Jim Green",
    age: 32,
    address: "Sidney No. 1 Lake Park"
  },
  {
    key: "4",
    name: "Jim Red",
    age: 32,
    address: "London No. 2 Lake Park"
  }
];

export default {
  data() {
    return {
      data,
      searchText: "",
      searchInput: null,
      columns: [
        {
          title: "Name",
          dataIndex: "name",
          key: "name",
          scopedSlots: {
            filterDropdown: "filterDropdown",
            filterIcon: "filterIcon",
            customRender: "customRender"
          },
          onFilter: (value, record) =>
            record.name
              .toString()
              .toLowerCase()
              .includes(value.toLowerCase()),
          onFilterDropdownVisibleChange: visible => {
            if (visible) {
              setTimeout(() => {
                this.searchInput.focus();
              }, 0);
            }
          }
        },
        {
          title: "Age",
          dataIndex: "age",
          key: "age",
          scopedSlots: {
            filterDropdown: "filterDropdown",
            filterIcon: "filterIcon",
            customRender: "customRender"
          },
          onFilter: (value, record) =>
            record.age
              .toString()
              .toLowerCase()
              .includes(value.toLowerCase()),
          onFilterDropdownVisibleChange: visible => {
            if (visible) {
              setTimeout(() => {
                this.searchInput.focus();
              });
            }
          }
        },
        {
          title: "Address",
          dataIndex: "address",
          key: "address",
          scopedSlots: {
            filterDropdown: "filterDropdown",
            filterIcon: "filterIcon",
            customRender: "customRender"
          },
          onFilter: (value, record) =>
            record.address
              .toString()
              .toLowerCase()
              .includes(value.toLowerCase()),
          onFilterDropdownVisibleChange: visible => {
            if (visible) {
              setTimeout(() => {
                this.searchInput.focus();
              });
            }
          }
        }
      ]
    };
  },
  methods: {
    handleSearch(selectedKeys, confirm) {
      confirm();
      this.searchText = selectedKeys[0];
    },

    handleReset(clearFilters) {
      clearFilters();
      this.searchText = "";
    }
  }
};
</script>
<style scoped>
.highlight {
  background-color: rgb(255, 192, 105);
  padding: 0px;
}
</style>

<!– ここにリンク設定を追加 –>と書いてあるところにリンクを、slot-scopeにkeyを追加しています。

slot=”customRender”とセットになっているslot-scopeにtextとkeyを追加すると、該当のtemplateで値を取り出せるようになるみたいです。

ここらへんはAnt Designの仕様みたいで、イマイチまだよくわかっていません。

Ant Design VueでデフォルトCSSを変更する方法

すぐ忘れるのでメモ。
vuetifyにちょっと問題があってAnt Design Vueを使うことになったのだけど、Nuxt.js最新版に対応しておらず、またしてもデフォルトCSSを変更できないという問題に遭遇。

その解決策はこちら。
https://github.com/vueComponent/ant-design-vue/issues/234#issuecomment-466308850

デフォルト
https://github.com/vueComponent/ant-design-vue/blob/master/components/style/themes/default.less

color
https://ant.design/docs/spec/colors

Nuxt.js: ExpressとaxiosでPOSTした時にreq.bodyがundefinedになる件

別件でpostした時普通にreq.bodyで取得できたと思ったのですが、今いじっていたらreqの中にbodyが見つからず、あれやこれやと調べていたら、body-parserというモジュールを別途インストールしなければいけないとのことでした。

工エエェェ(´д`)ェェエエ工! すっごいハマりポイントなので記事にしておきます。

Nuxt.jsの中にAPIも合体させて作るパターンの続きになります。
Nuxt.js使用開始にあたっての設定もろもろ−APIを追加

まずnpm install body-parserするのですが、コロコロ変わるので必ずオフィシャルのマニュアルを一読することを強くおすすめします。

ファイルは /server/api.js で以下を追加するだけです。

const bodyParser = require('body-parser');
const jsonParser = bodyParser.json();

router.post('/post', jsonParser, (req, res, next) => {
    console.log(req.body);
});

たったこれだけでreq.bodyに値が入ってきました。

あと、よくやるのが、index.vueなどでリクエストを投げるときに、投げるデータをJSONにしていないこと。
デフォルトがJSONなので、JSONで送る場合はJSON.parseしておく必要があります。

Nuxt.js + Vuetify デフォルトフォント変更の仕方

以下の記事はVuetify バージョン1の話なのでご注意を。
バージョン2ではstylusではなくてSASSを使っているのでまったく方法が異なります。続きは一番下で。

 
オフィシャルの説明ではさっぱりわからなかったのでメモです。

純粋なNuxtではassetsの中にcssファイルを作って、nuxt.config.jsで読み込めばスタイルシートが使えるはずなのですが、困ったことにVuetifyはデフォルトでstylusを使うのだそうで反応しません。

stylusって何だよと調べてみるとSassと同じようなスタイルシートの記法だそうで、$ほにゃららで上書き設定を入れなければいけないのだそうです。

面倒ですね〜。
ちなみにVuetify2.0でSASSに移行するそうです。

デフォルト値はこちら。
https://github.com/vuetifyjs/vuetify/blob/master/packages/vuetify/src/stylus/settings/_variables.styl
※Vuetifyのバージョンが2に上がったのでリンク切れしてます。

でもどこにどのファイルを置いて、何を書けばいいのかオフィシャルの解説ではさっぱりでした。

まずassetsディレクトリに「stylus」というフォルダを作ります。
その中に「main.styl」というファイルを作り以下のようにコードを書きます。

/assets/stylus/main.styl

// main.styl
$body-font-family = 'ヒラギノ丸ゴ Pro W4', 'ヒラギノ丸ゴ Pro', 'Hiragino Maru Gothic Pro', 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', 'HG丸ゴシックM-PRO', 'HGMaruGothicMPRO' !important;

@require '~vuetify/src/stylus/main.styl'

nuxt.config.jsのcssのところに「~/assets/stylus/main.styl」を追加します。

  css: [
    '~/assets/style/app.styl', '~/assets/stylus/main.styl'
  ],

こんな感じ。
とりあえずこれでデフォルトフォントは変更できました。

 
【追記 2019/08/30】
Vuetifyの最新のアップデートが9時間前になってました。
これを書いている間にもいじってるみたいです。

バージョンが2に上がってNuxtのほうも対応したようです。
一番上にも書きましたが、stylusからSASSに変わってデフォルトフォントの変更の仕方も変わりました。

ここにあるとおりにすれば簡単に変更できます。
ただしデフォルトではrun buildしてNODE_ENVがproductionの時しか効かないです。

run devのときにも反映させたかったのですが、現時点ではちょっと無理と判断し、デフォルトフォントを変えるのではなくてdefault.vueのdivタグでstyleを指定することにしました。

こんな感じです。
/layouts/default.vue

<template>
  <v-app>
    <div id="fontSetting">
      <nuxt />
      <div>テスト中</div>
      <v-footer>
        <v-icon size="16">mdi-copyright</v-icon>
        {{ new Date().getFullYear() }} hogehage Inc.
      </v-footer>
    </div>
  </v-app>
</template>

<script>
export default {};
</script>

<style>
#fontSetting {
  font-family: "ヒラギノ丸ゴ Pro W4", "ヒラギノ丸ゴ Pro",
    "Hiragino Maru Gothic Pro", "ヒラギノ角ゴ Pro W3",
    "Hiragino Kaku Gothic Pro", "HG丸ゴシックM-PRO", "HGMaruGothicMPRO";
  font-size: 50px;
}
</style>

これで一応フォントは変えられます。

Node.js: Nuxt.jsの設定でローカル、本番環境に分ける方法

前回のコードを使いまわします。
前回は NuxtとVuetifyとaxiosで簡単な動的ページを作りました。

開発をしていると少なくともローカルとサーバー、2つの環境は必要になると思うのですが、前回の方法だとサーバーでうまく動かなくなります。

原因はapiのアクセス先をlocalhost:3000で固定してしまったせいなのですが、ここらはNuxt.jsの設定を変えればビルドする時に対応してくれます。

/nuxt.config.js

  axios: {
    baseURL: 'http://localhost:3000' // ここを追加
  },

デフォルトはlocalhost:3000にしておきます。
もちろん別でも構いません。

/package.json

  "scripts": {
    "dev": "cross-env NUXT_PORT=3000 NODE_ENV=development nodemon server/index.js --watch server", // (1)
    "build": "NUXT_HOST=0.0.0.0 NUXT_PORT=3000 API_URL=http://hogehage.com:3000 nuxt build", // (2)
    "start": "cross-env NUXT_HOST=0.0.0.0 NUXT_PORT=3000 API_URL=http://hogehage.com:3000 NODE_ENV=production node server/index.js", // (3)
    "generate": "nuxt generate",
    "test": "ava"
  },

デフォルトから(1)(2)(3)を変更しています。

NUXT_PORTの設定は必要なら入れます。
ここで入れなくてもnuxt.config.jsに一発で入れる方法もあるので、そこら辺は公式マニュアルをご覧ください。

NUXT_HOSTを0.0.0.0にすると、ホスト名をいちいち指定しなくてもNuxtがうまくやってくれます。

今重要なのはAPI_URLの設定です。
ここでaxiosで使うapiの接続先を上書きできるのです。

nuxt.config.jsでデフォルトをlocalhostにしているので、サーバー側(production)をhogehage.comに上書きしています。

では、実際にvueファイルでapiにアクセスする場合です。
例)/pages/index.vue

async asyncData({ $axios }) {
    const items = await $axios.$get($axios.defaults.baseURL + "/api/users");
    return { items };
  }

URLを直書きしていた部分を$axios.defaults.baseURLに変更しています。

これでnpm run devしてNODE_ENVがdevelopmentの場合はlocalhost:3000を、npm run buildしてstart、つまりNODE_ENVがproductionの場合は、apiのアクセス先がhogehage.com:3000になります。

ちょっとメンドっちぃな〜と思ったのは自分だけでしょうか。。。

しかしこれを応用してステージングサーバーの設定も変えられます。
詳しくは公式マニュアルをあさってください。

 
MySQLの設定などをローカルとサーバーで分けたい場合の方法はこちらです。