🔖
Vue3+Quasarで始めるTodoアプリ開発
はじめに
Vue3ではViteがデフォルトになり、構築手順が色々と変更されているので、今回はQuasarを使ったMaterial DesignベースのToDoアプリ開発手順を紹介します。Quasarについては、この記事も参考にしてください。
基本的には、Why Quasar?にあるYoutubeも参考しましたが、色々と改良しています。
WSL2での開発環境構築
setup.sh
# ubuntu update
sudo apt update
sudo apt upgrade
# npm
sudo apt-get install curl
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash
source .bashrc
nvm install --lts
プロジェクト作成
$ npm init vue@latest
Need to install the following packages:
create-vue@3.4.0
Ok to proceed? (y) y
Vue.js - The Progressive JavaScript Framework
✔ Project name: … quasar-todo
✔ Add TypeScript? … No / Yes
✔ Add JSX Support? … No / Yes
✔ Add Vue Router for Single Page Application development? … No / Yes
✔ Add Pinia for state management? … No / Yes
✔ Add Vitest for Unit Testing? … No / Yes
✔ Add an End-to-End Testing Solution? › No
✔ Add ESLint for code quality? … No / Yes
✔ Add Prettier for code formatting? … No / Yes
...省略
$ cd quasar-todo
$ npm run lint
環境設定
各種設定ファイルを修正します。前に入れていた命名規則チェックはエラーが出ており、調査中です。
- 拡張機能の推奨を追加する。その後、各自でインストールしてください。
.vscode/extensions.json
{
"recommendations": [
// ESLint
"dbaeumer.vscode-eslint",
// Prettier
"esbenp.prettier-vscode",
// Vue Language Features (Volar)
"vue.volar",
// TypeScript Vue Plugin (Volar)
"Vue.vscode-typescript-vue-plugin",
// EditorConfig
"editorconfig.editorconfig",
// TODO Highlight
"wayou.vscode-todo-highlight"
]
}
- ブラケット(括弧)ペアを可視化する。
- フォーマッタをPrettierにする。jsonやtsは、vscode標準が適用されるため、上書き
- 保存、ペースト、改行時に整形する。
.vscode/settings.json
{
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true,
"editor.formatOnPaste": true,
"editor.formatOnType": true
}
- printWidth ... 単純に行数が減るため。
- htmlWhitespaceSensitivity ... タグ改行時の空白を許可する。
- useTabs ... インデントを空白にする。
.prettierrc.json
{
"printWidth": 150,
"useTabs": false,
"endOfLine": "auto",
"htmlWhitespaceSensitivity": "ignore"
}
- editorconfig ... 全ファイルの文字コード、インデントを統一する。
.editorconfig
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
設定変更後は、VS Codeの再起動(F1キー + Reload Window)を行ってください。
Quasarセットアップ
QuasarのGetting StartedにあるVite Pluginの手順に従い、Quasarを使えるようにする。Using Quasarでセットアップするモジュールを選択できる。今回、DialogやNotifyのプラグインを利用するので、追加している。
$ npm install quasar @quasar/extras
$ npm install -D @quasar/vite-plugin sass
package.json
...
"dependencies": {
"@quasar/extras": "^1.15.5",
"pinia": "^2.0.23",
"quasar": "^2.10.1",
"vue": "^3.2.41",
"vue-router": "^4.1.5"
},
"devDependencies": {
"@quasar/vite-plugin": "^1.2.3",
"@rushstack/eslint-patch": "^1.1.4",
"@types/node": "^16.11.68",
"@vitejs/plugin-vue": "^3.1.2",
"@vue/eslint-config-prettier": "^7.0.0",
"@vue/eslint-config-typescript": "^11.0.0",
"@vue/tsconfig": "^0.1.3",
"eslint": "^8.22.0",
"eslint-plugin-vue": "^9.3.0",
"npm-run-all": "^4.1.5",
"prettier": "^2.7.1",
"typescript": "~4.7.4",
"vite": "^3.1.8",
"vue-tsc": "^1.0.8",
"sass": "1.32.12"
}
src/main.ts
import { createApp } from "vue";
import { createPinia } from "pinia";
import { Quasar, Dialog, Notify } from "quasar";
import quasarLang from "quasar/lang/ja";
import App from "./App.vue";
import router from "./router";
// Import quasar CSS
import "@quasar/extras/material-icons/material-icons.css";
import "quasar/src/css/index.sass";
const app = createApp(App);
app.use(createPinia());
app.use(router);
// Setup quasar
app.use(Quasar, {
plugins: [Dialog, Notify],
lang: quasarLang,
});
app.mount("#app");
src/vite.config.ts
import { fileURLToPath, URL } from "node:url";
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import { quasar, transformAssetUrls } from "@quasar/vite-plugin";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue({
template: { transformAssetUrls },
}),
quasar({
sassVariables: "src/quasar-variables.scss",
}),
],
resolve: {
alias: {
"@": fileURLToPath(new URL("./src", import.meta.url)),
},
},
});
src/quasar-variables.scss
$primary: #1976d2;
$secondary: #26a69a;
$accent: #9c27b0;
$dark: #1d1d1d;
$positive: #21ba45;
$negative: #c10015;
$info: #31ccec;
$warning: #f2c037;
ToDo画面作成
今回、ナビゲーション付きのToDo画面を、以下の手順で作成していきます。
- HomeやToDo画面を空で作成し、404ページも適当に用意します。
src/views/ToDoView.vue
<template>
<q-page class="q-pa-lg">
<h5 class="q-mt-none">ToDo</h5>
</q-page>
</template>
views/AboutView.vue
<template>
<q-page class="q-pa-lg">
<h5 class="q-mt-none">About</h5>
</q-page>
</template>
src/views/Error404View.vue
<template>
<q-page class="q-pa-lg">
<div class="fixed-center text-center">
<h5 class="text-faded">
Sorry, nothing here...
<strong>(404)</strong>
</h5>
<q-btn color="secondary" style="width: 100px" @click="$router.push('/')">Go back</q-btn>
</div>
</q-page>
</template>
- routerを修正します。
src/router/index.ts
import { createRouter, createWebHistory } from "vue-router";
import AboutView from "@/views/AboutView.vue";
import Error404View from "@/views/Error404View.vue";
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: "/",
name: "home",
// 非同期にするとファイルが分割され、初回の読み込みが早くなる。
component: () => import("@/views/ToDoView.vue"),
},
{
path: "/about",
name: "about",
component: AboutView,
},
{
path: "/:catchAll(.*)*",
component: Error404View,
},
],
});
export default router;
- ナビゲーションバーを設定します。実装する流れは、以下の通り。
- Layout and Grid - Layout - Layout Builderからレイアウトを作成する。
- Vue Component - List & List Items - Menuでメニューサンプルをコピーし、v-forでループする。
- q-itemにv-modelを追加すると、メニューが選択され、active-classで色を変更できる。
src/App.vue
<script setup lang="ts">
import { ref } from "vue";
export interface Menu {
icon: string;
label: string;
link: string;
separator: boolean;
}
const menuList: Menu[] = [
{
icon: "list",
label: "ToDo",
link: "/",
separator: false,
},
{
icon: "help",
label: "About",
link: "/about",
separator: false,
},
];
const selectdMenu = ref("");
const leftDrawerOpen = ref(false);
</script>
<template>
<q-layout view="hHh lpR fFf">
<q-header elevated class="bg-primary text-white">
<q-toolbar>
<q-btn dense flat round icon="menu" @click="leftDrawerOpen = !leftDrawerOpen" />
<q-toolbar-title>ToDo App</q-toolbar-title>
</q-toolbar>
</q-header>
<q-drawer show-if-above v-model="leftDrawerOpen" side="left" bordered>
<q-scroll-area class="fit">
<q-list>
<template v-for="(menuItem, index) in menuList" :key="index">
<q-item v-model="selectdMenu" clickable :to="menuItem.link" active-class="bg-indigo-6 text-white" v-ripple>
<q-item-section avatar>
<q-icon :name="menuItem.icon" />
</q-item-section>
<q-item-section>
{{ menuItem.label }}
</q-item-section>
</q-item>
<q-separator :key="'sep' + index" v-if="menuItem.separator" />
</template>
</q-list>
</q-scroll-area>
</q-drawer>
<q-page-container>
<router-view />
</q-page-container>
</q-layout>
</template>
- ToDo画面を作成する。実装する流れは、以下の通り。
- 一覧表示 ... Vue Component - Form Component - Checkbox - With QItemのサンプルソースをコピーし、v-forでループする。
- タスク完了 ... task.doneを見て、背景色を変え、取り消し線にする。
- タスク削除 ... タスク完了時に、deleteアイコンを表示し、タスク削除を行う。
- 削除確認ダイアログ ... Quasar Plugins - Dialog - Basic - CONFIRMのサンプルソースをコピーし、編集する。
- 削除完了メッセージ ... Quasar Plugins - Notify - Basicのサンプルソースをコピーし、編集する。
- タスク追加 ... Vue Component - Form Component - Input Textfield - Standardのサンプルをコピーし、編集する。その後、入力後または追加ボタン押下時に、タスク追加を行う。
- バリデーションチェック ... 確認ダイアログを参考に、0文字だった場合、エラーメッセージを出力する。
src/views/ToDoView.vue
<script setup lang="ts">
import { reactive, ref } from "vue";
import { useQuasar } from "quasar";
export interface Task {
title: string;
done: boolean;
}
const tasks: Task[] = reactive([
{
title: "Get bananas",
done: false,
},
{
title: "Eat bananas",
done: false,
},
]);
const $q = useQuasar();
const newTask = ref("");
const deleteTask = (index: number) => {
$q.dialog({
title: "Confirm",
message: "Really delete?",
cancel: true,
persistent: true,
}).onOk(() => {
tasks.splice(index, 1);
$q.notify({ message: "Task deleted.", color: "info" });
});
};
const addTask = () => {
if (newTask.value.length == 0) {
$q.notify({ message: "Add Task is required.", color: "negative" });
} else {
tasks.push({
title: newTask.value,
done: false,
});
newTask.value = "";
}
};
</script>
<template>
<q-page class="bg-grey-3 column">
<div class="row q-pa-sm bg-primary">
<q-input outlined class="col" @keyup.enter="addTask" square bg-color="white" v-model="newTask" placeholder="Add Task" dense>
<q-btn round flat icon="add" @click="addTask" />
</q-input>
</div>
<q-list class="bg-white" separator bordered>
<q-item v-for="(task, index) in tasks" :key="index" tag="label" :class="{ 'bg-blue-1': task.done }" clickable v-ripple>
<q-item-section avatar>
<q-checkbox v-model="task.done" color="primary" />
</q-item-section>
<q-item-section>
<q-item-label v-if="task.done">
<del>{{ task.title }}</del>
</q-item-label>
<q-item-label v-else>
{{ task.title }}
</q-item-label>
</q-item-section>
<q-item-section v-if="task.done" side>
<q-btn flat round dense color="primary" icon="delete" @click.stop="deleteTask(index)"></q-btn>
</q-item-section>
</q-item>
</q-list>
<div v-if="!tasks.length" class="no-task absolute-center">
<q-icon name="check" size="100px" color="primary" />
<div class="text-h5 text-primary text-center">No Tasks</div>
</div>
</q-page>
</template>
- 最後に、assets、componentsやviewsにある不要なファイルは削除して、完成です。
終わりに
以前紹介したToDoアプリに比べて、コーディング量も減り、より高機能がToDoアプリが構築できるようになりました。
今後、Quasarをベースとしたマルチデバイス対応の記事も追加しました。
Discussion