VueとuseFuseをつかって東京都府中市の粗大ゴミシール料金検索をつくりました

## 概要
府中市の粗大ゴミを出すときに必要なシール料金の検索サイトをVue3で作りました。

https://gomi-sticker-fee-search.netlify.app/


![CleanShot 2023-06-04 at 22.16.01.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/124445/7b09136c-07fc-739f-a192-13e44cb8fc77.gif)

## 環境
* Vue3 (Composition API)

## 背景
以前、下記を作っていました。

https://qiita.com/junara/items/8ee0774ccbfdb0197829


しかし、herokuの有料化にともない、無料で運用するのが難しくなりました。


改めて、検索対象を見たところ、Algolia使うほどではなかったので、サーバー不要でクライアントだけで検索できるようにVue3で書き直しました。

結果、netlifyで公開し、無料で運用できました。


## ソース

https://github.com/junara/vue-fuchu-fee-search

## 解説
データは、東京都府中市が公開しているデータを使っています。
CSVで公開されていましたので、importしやすいように加工して使っています。
加工したCSVは[こちら](https://github.com/junara/vue-fuchu-fee-search/blob/main/src/data/fuchu-gomi.csv)です。

```data/fuchu-gomi.csv
key,品目,料金,ふりがな
1,アイエイチ（IH）クッキングヒーター（1口）,200円,あいえいち（IH）くっきんぐひーたー（1くち）
2,アイスボックス（高さと幅の合計90cm未満）,200円,あいすぼっくす（たかさとはばのごうけい90cmみまん）
```

### csvを読み取る
csvを読み取るためには、csvをassetとして扱い、csvのurlを参照してcsvを[PapaParse](https://www.papaparse.com/)でimportしました。

Viteでassetsに含めるための設定は以下の通りです。

https://github.com/junara/vue-fuchu-fee-search/blob/2efc20cc3db91f64a4055bf029869967f6bf70e2/vite.config.ts#L14

```vite.config.ts
  assetsInclude: ['**/*.csv']
```

[共通オプション | Vite](https://ja.vitejs.dev/config/shared-options.html#assetsinclude)

> JS からインポートすると、解決された URL 文字列が返されます


ということなので、importするとurlが取得できます。


```components/SearchPageBody.vue
import csvUrl from '@/data/fuchu-gomi.csv';
```

csvが保存されているurlを元にして、PapaParseでimportするのは以下の通り。

https://www.papaparse.com/docs#remote-files

```
Papa.parse(url, {
	download: true,
	// rest of config ...
})
```

実際は以下の通り。

```src/composables/useGomiFeeCsv.ts
const importFromUrl = (csvUrl: string, callback: (gomiFees: GomiFee[]) => void) => {
    Papa.parse(csvUrl, {
      download: true,
      header: true,
      complete: (results: ParseResult<GomiFeeRow>) => {
        gomiFees.value = results.data.map((row) => {
          return gomiFeeRow2GomiFee(row);
        });
        callback(gomiFees.value);
      },
    });
  };
```


```src/components/SearchPageBody.vue
import csvUrl from '@/data/fuchu-gomi.csv';
importFromUrl(csvUrl, (_gomiFees: GomiFee[]) => {
  gomiFees.value = _gomiFees;
});
```

### データを検索する
検索はFuseを使いました。

[What is Fuse.js? | Fuse.js](https://fusejs.io/)

実際は、VueUseでcomposable [useFuse | VueUse](https://vueuse.org/integrations/useFuse/)経由です。


```src/components/SearchResultSearch.vue
<script setup lang="ts">
import { useFuse } from '@vueuse/integrations/useFuse';

const { results } = useFuse(searchText, props.gomiFees, {
  fuseOptions: {
    keys: ['name', 'furigana'],
  },
  resultLimit: 10,
  matchAllWhenSearchEmpty: false,
});

const onInputSearchText = (e: any) => {
  searchText.value = e.target.value;
};
</script>

<template>
  <div>
    <div>
      <input type="text" placeholder="検索" @input="onInputSearchText" />
    </div>
    <div class="card">
      <div v-for="result in results" :key="result.item.key" class="card-item">
        <label>{{ result.item.name }}</label>
        <div>
          {{ result.item.furigana }}
        </div>
        <div>
          {{ result.item.fee }}
        </div>
      </div>
    </div>
  </div>
</template>
```

## その他
csvを置き換えれば、他の市町村にも対応できます。

## 感想
Ruby on Railsだとサーバー代が高くて、書いてもなかなか公開できなかった。しかし、JavaScript onlyで作ると、サーバーを無料運用が現実的に可能になる。

無料で運用できる結果、色々作り放題ですので、個人開発が楽しくなりました。