404 Not Found

404 Not Found


nginx

Vue 3 総合プロジェクト:Eコマース・バックエンド管理システム—8つのモジュール(30レッスンの卒業プロジェクト)

これは、Vue 3 チュートリアルの最終プロジェクトです。レッスン 29 で学んだすべての内容を、実際の SaaS 型 e コマースのバックエンドに応用していきます。GitHubで88k⭐を獲得しているPanJiaChen/vue-element-adminアーキテクチャをモデルとして、5,000社の販売業者と30,000点のSKUをサポートするECバックエンドを構築していきます。

このプロジェクト全体は8つの主要なモジュールで構成されており、所要時間は10~12時間です。このプロジェクトを完了すると、エンタープライズレベルのVueアプリケーションを独自に開発する能力が身につきます。

1. 学習内容


2. SaaS系Eコマース企業におけるプロダクトマネージャーの「フルスタック要件」

(1) 課題:5,000社の販売業者と30,000点のSKUに対応するECバックエンドをどのように構築するか

アリスは、管理ダッシュボード・プロジェクトの責任者に昇進しました:

TEXT
Business Scenarios:
  - 5,000 Small and Medium-Sized Business Owners(Per household 30-300 SKU)
  - 30,000 Items SKU
  - Daily 10,000 Order
  - 5 Role Permissions(admin / manager / editor / viewer / guest)
  - Real-time Data(Dashboard、Order、Notice)
  - Multilingual(5 Language)

プロダクトマネージャーのチャーリーが仕様書を作成しました:

「アリス、当社のSaaSプラットフォームには包括的な管理画面が必要です。必要なモジュールは8つあります:ダッシュボード、製品、注文、ユーザー、マーケティング、設定、ログイン、デプロイメント。どのモジュールも不可欠です。これまでに学んだ29のレッスンをすべて活用して、Vue 3で10~12時間以内に構築してください。」

(2) Vue 3 総合プロジェクト:8モジュール構成

TEXT
src/
├── api/                   # API Packaging
│   ├── auth.ts
│   ├── product.ts
│   ├── order.ts
│   └── user.ts
├── assets/                # Static Resources
├── components/            # Public Components
│   ├── BaseButton.vue
│   ├── BaseTable.vue       # Secondary Packaging Element Plus
│   ├── BasePagination.vue
│   ├── BaseDialog.vue
│   └── charts/             # Charts
│       ├── SalesChart.vue
│       ├── UserChart.vue
│       └── OrderChart.vue
├── composables/           # Composite Functions
│   ├── useAuth.ts
│   ├── usePermission.ts
│   ├── useTable.ts
│   └── usePagination.ts
├── layouts/                # Layout
│   ├── DefaultLayout.vue   # After logging in
│   └── AuthLayout.vue      # Login Page
├── router/                # Routing
│   └── index.ts
├── stores/                 # Pinia
│   ├── auth.ts
│   ├── product.ts
│   ├── order.ts
│   └── app.ts              # Global(Topic / Sidebar)
├── types/                  # TS Type
│   ├── user.ts
│   ├── product.ts
│   └── api.ts
├── utils/                  # Tools
│   ├── request.ts          # axios Packaging
│   ├── auth.ts
│   └── format.ts
├── views/                  # Page
│   ├── dashboard/          # Dashboard
│   ├── login/              # Log In
│   ├── product/            # Products
│   ├── order/              # Order
│   ├── user/               # User
│   ├── marketing/          # Marketing
│   └── error/              # 403 / 404
├── App.vue
└── main.ts

(3) 収益

このプロジェクトを構築した後:


3. モジュール 1:プロジェクトの初期化(1.5 時間)

(1) package.json を完成させる

(1) ▶ サンプル:プロジェクトの初期設定を完了する (難易度: ⭐⭐)

Vue 3 + Vite + Pinia + Element Plus + ECharts + TypeScript を使用して、ゼロから完全なプロジェクトをセットアップする方法:

JSON
{
  "name": "mercury-admin",
  "version": "1.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vue-tsc --noEmit && vite build",
    "preview": "vite preview",
    "test": "vitest",
    "test:e2e": "playwright test",
    "lint": "eslint . --ext .vue,.ts,.tsx"
  },
  "dependencies": {
    "vue": "^3.4.0",
    "vue-router": "^4.3.0",
    "pinia": "^2.1.0",
    "element-plus": "^2.5.0",
    "@element-plus/icons-vue": "^2.3.0",
    "echarts": "^5.4.0",
    "vue-echarts": "^7.0.0",
    "axios": "^1.6.0",
    "dayjs": "^1.11.0",
    "vueuse": "^10.7.0"
  },
  "devDependencies": {
    "@vitejs/plugin-vue": "^5.0.0",
    "vite": "^5.0.0",
    "vue-tsc": "^1.8.0",
    "typescript": "^5.3.0",
    "unplugin-vue-components": "^0.26.0",
    "unplugin-auto-import": "^0.17.0",
    "unplugin-icons": "^0.17.0",
    "@iconify-json/carbon": "^1.1.0",
    "sass": "^1.69.0",
    "vitest": "^1.0.0",
    "@vue/test-utils": "^2.4.0",
    "@playwright/test": "^1.40.0"
  }
}

(2) vite.config.ts

TS
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import AutoImport from 'unplugin-auto-import'
import Components from 'unplugin-vue-components'
import Icons from 'unplugin-icons/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
import path from 'path'

export default defineConfig({
  plugins: [
    vue(),
    AutoImport({
      imports: ['vue', 'vue-router', 'pinia', 'vueuse'],
      resolvers: [ElementPlusResolver()],
      dts: 'src/auto-imports.d.ts'
    }),
    Components({
      resolvers: [ElementPlusResolver()],
      dts: 'src/components.d.ts'
    }),
    Icons({ autoInstall: true })
  ],
  resolve: {
    alias: { '@': path.resolve(__dirname, 'src') }
  },
  server: {
    port: 5173,
    proxy: { '/api': { target: 'http://localhost:3000', changeOrigin: true } }
  },
  build: {
    rollupOptions: {
      output: {
        manualChunks: {
          'vue-vendor': ['vue', 'vue-router', 'pinia'],
          'echarts-vendor': ['echarts', 'vue-echarts']
        }
      }
    }
  }
})

4. モジュール 2:ログイン + JWT 認証 + 権限ベースのルーティング (2時間)

(1) ピニア・オーソライズド・ストア

(1) ▶ サンプル:JWT ログイン + 5 つのロールによる権限管理 (難易度: ⭐⭐)

Pinia Setup Store を使用して、トークン、ユーザー、ロール、権限を管理します:

TS
// stores/auth.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import { loginApi, getUserInfoApi } from '@/api/auth'

export const useAuthStore = defineStore('auth', () => {
  const token = ref<string>(localStorage.getItem('token') || '')
  const user = ref<User | null>(null)
  const roles = ref<string[]>([])
  const permissions = ref<string[]>([])
  
  const isLoggedIn = computed(() => !!token.value)
  const isAdmin = computed(() => roles.value.includes('admin'))
  
  async function login(credentials: { username: string; password: string }) {
    const { data } = await loginApi(credentials)
    token.value = data.token
    user.value = data.user
    roles.value = data.roles
    permissions.value = data.permissions
    localStorage.setItem('token', data.token)
  }
  
  async function fetchUserInfo() {
    const { data } = await getUserInfoApi()
    user.value = data.user
    roles.value = data.roles
    permissions.value = data.permissions
  }
  
  function logout() {
    token.value = ''
    user.value = null
    roles.value = []
    permissions.value = []
    localStorage.removeItem('token')
  }
  
  return { token, user, roles, permissions, isLoggedIn, isAdmin, login, fetchUserInfo, logout }
}, {
  persist: {
    key: 'mercury-auth',
    paths: ['token', 'user', 'roles', 'permissions']
  }
})
▶ 試してみよう

(2) ルートガード

TS
// router/index.ts
import { createRouter, createWebHistory } from 'vue-router'
import { useAuthStore } from '@/stores/auth'

const router = createRouter({
  history: createWebHistory(),
  routes: [
    { path: '/login', component: () => import('@/views/login/LoginPage.vue') },
    {
      path: '/',
      component: () => import('@/layouts/DefaultLayout.vue'),
      meta: { requiresAuth: true },
      children: [
        { path: '', redirect: '/dashboard' },
        { path: 'dashboard', component: () => import('@/views/dashboard/DashboardPage.vue') },
        { path: 'products', component: () => import('@/views/product/ProductListPage.vue') }
      ]
    }
  ]
})

router.beforeEach((to, from, next) => {
  const auth = useAuthStore()
  
  if (to.meta.requiresAuth && !auth.isLoggedIn) {
    return next({ name: 'login', query: { redirect: to.fullPath } })
  }
  
  next()
})

export default router

(3) カスタム権限ディレクティブ

TS
// directives/permission.ts
import { useAuthStore } from '@/stores/auth'

export const permission = {
  mounted(el: HTMLElement, binding: { value: string }) {
    const auth = useAuthStore()
    if (!auth.permissions.includes(binding.value)) {
      el.parentNode?.removeChild(el)
    }
  }
}
VUE
<button v-permission="'product.create'">Create Product</button>

5. モジュール 3:ダッシュボード(2 時間)

(1) EChartsのチャート5つ

(1) ▶ サンプル:つのEChartsチャートを搭載したダッシュボード(難易度:⭐⭐)

ダッシュボードのコアコード:5つのチャート+4つのKPI+Element Plusのグリッドレイアウト:

VUE
<!-- views/dashboard/DashboardPage.vue -->
<template>
  <div class="dashboard">
    <!-- 1. KPI Card -->
    <div class="kpi-grid">
      <KpiCard title="Today's Sales" :value="stats.todaySales" :change="+12%" />
      <KpiCard title="Orders" :value="stats.todayOrders" :change="+5%" />
      <KpiCard title="Active Users" :value="stats.activeUsers" :change="+8%" />
      <KpiCard title="Conversion" :value="stats.conversion" :change="-2%" />
    </div>
    
    <!-- 2. 5 View Chart -->
    <el-row :gutter="20">
      <el-col :span="12">
        <SalesChart :data="salesData" />
      </el-col>
      <el-col :span="12">
        <UserChart :data="userData" />
      </el-col>
      <el-col :span="24">
        <OrderChart :data="orderData" />
      </el-col>
      <el-col :span="12">
        <CategoryChart :data="categoryData" />
      </el-col>
      <el-col :span="12">
        <TopProductsTable :data="topProducts" />
      </el-col>
    </el-row>
  </div>
</template>

<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { getDashboardStatsApi } from '@/api/dashboard'

const stats = ref({})
const salesData = ref([])
const userData = ref([])
const orderData = ref([])
const categoryData = ref([])
const topProducts = ref([])

onMounted(async () => {
  const { data } = await getDashboardStatsApi()
  Object.assign(stats.value, data.stats)
  salesData.value = data.sales
  userData.value = data.users
  orderData.value = data.orders
  categoryData.value = data.categories
  topProducts.value = data.topProducts
})
</script>
▶ 試してみよう

(2) ECharts コンポーネントのカプセル化

VUE
<!-- components/charts/SalesChart.vue -->
<template>
  <el-card title="Sales Trend">
    <v-chart :option="chartOption" autoresize style="height: 300px" />
  </el-card>
</template>

<script setup lang="ts">
import { computed } from 'vue'
import { use } from 'echarts/core'
import { CanvasRenderer } from 'echarts/renderers'
import { LineChart } from 'echarts/charts'
import { GridComponent, TooltipComponent, LegendComponent } from 'echarts/components'

use([CanvasRenderer, LineChart, GridComponent, TooltipComponent, LegendComponent])

const props = defineProps<{
  data: Array<{ date: string; sales: number }>
}>()

const chartOption = computed(() => ({
  tooltip: { trigger: 'axis' },
  xAxis: { type: 'category', data: props.data.map(d => d.date) },
  yAxis: { type: 'value' },
  series: [{
    name: 'Sales',
    type: 'line',
    data: props.data.map(d => d.sales),
    smooth: true
  }]
}))
</script>

6. モジュール4:プロダクトマネジメント(1.5時間)

(1) 製品一覧 + CRUD

(1) ▶ サンプル:製品の CRUD 操作 + アクセス制御 (難易度: ⭐⭐)

プロダクトマネジメントの核心:BaseTableの再カプセル化 + 6列表示 + 権限ディレクティブ + 削除確認:

VUE
<!-- views/product/ProductListPage.vue -->
<template>
  <BaseTable
    :data="products"
    :columns="columns"
    :total="total"
    :loading="loading"
    @search="onSearch"
    @page="onPage"
  >
    <template #toolbar>
      <el-button type="primary" v-permission="'product.create'" @click="onCreate">
        Create Product
      </el-button>
    </template>
    
    <template #column-status="{ row }">
      <el-tag :type="statusType(row.status)">{{ row.status }}</el-tag>
    </template>
    
    <template #column-actions="{ row }">
      <el-button v-permission="'product.edit'" @click="onEdit(row)">Edit</el-button>
      <el-button v-permission="'product.delete'" type="danger" @click="onDelete(row)">Delete</el-button>
    </template>
  </BaseTable>
</template>

<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { getProductsApi, deleteProductApi } from '@/api/product'

const products = ref<Product[]>([])
const total = ref(0)
const loading = ref(false)
const search = ref({ name: '', category: '', status: '' })
const page = ref(1)
const size = ref(20)

const columns = [
  { key: 'id', label: 'ID', width: 80 },
  { key: 'name', label: 'Name' },
  { key: 'category', label: 'Category' },
  { key: 'price', label: 'Price', formatter: (row) => `$${row.price}` },
  { key: 'stock', label: 'Stock' },
  { key: 'status', label: 'Status' }
]

async function fetchData() {
  loading.value = true
  const { data } = await getProductsApi({ ...search.value, page: page.value, size: size.value })
  products.value = data.items
  total.value = data.total
  loading.value = false
}

function onSearch() { page.value = 1; fetchData() }
function onPage(p) { page.value = p; fetchData() }

async function onDelete(row) {
  await ElMessageBox.confirm(`Delete ${row.name}?`, 'Confirm')
  await deleteProductApi(row.id)
  ElMessage.success('Deleted')
  fetchData()
}

onMounted(fetchData)
</script>
▶ 試してみよう

(2) 製品編集ダイアログボックス

VUE
<!-- views/product/ProductEditDialog.vue -->
<template>
  <el-dialog v-model="visible" :title="isEdit ? 'Edit Product' : 'Create Product'" width="700px">
    <el-form :model="form" :rules="rules" ref="formRef" label-width="120px">
      <el-form-item label="Name" prop="name">
        <el-input v-model="form.name" />
      </el-form-item>
      <el-form-item label="Category" prop="category">
        <el-select v-model="form.category">
          <el-option v-for="c in categories" :key="c" :value="c" :label="c" />
        </el-select>
      </el-form-item>
      <el-form-item label="Price" prop="price">
        <el-input-number v-model="form.price" :min="0" />
      </el-form-item>
      <el-form-item label="Stock" prop="stock">
        <el-input-number v-model="form.stock" :min="0" />
      </el-form-item>
      <el-form-item label="Image">
        <el-upload action="/api/upload" :show-file-list="false">
          <img v-if="form.image" :src="form.image" class="preview" />
          <el-button v-else>Upload</el-button>
        </el-upload>
      </el-form-item>
    </el-form>
    
    <template #footer>
      <el-button @click="visible = false">Cancel</el-button>
      <el-button type="primary" @click="onSave">Save</el-button>
    </template>
  </el-dialog>
</template>

7. モジュール 5:注文管理(1 時間)

(1) 注文一覧 + 詳細

(1) ▶ サンプル:注文リストと詳細ダイアログボックス(難易度:⭐)

「Order Core」:BaseTable リスト + 詳細ドロワー + ステータス遷移:

VUE
<template>
  <BaseTable :data="orders" :columns="columns" :total="total">
    <template #column-status="{ row }">
      <el-tag :type="statusType(row.status)">{{ row.status }}</el-tag>
    </template>
    <template #column-actions="{ row }">
      <el-button @click="viewDetail(row)">View</el-button>
      <el-button v-if="row.status === 'pending'" @click="confirmOrder(row)">Confirm</el-button>
    </template>
  </BaseTable>
  
  <el-dialog v-model="detailVisible" title="Order Detail" width="800px">
    <h3>Order #{{ currentOrder?.id }}</h3>
    <p>User: {{ currentOrder?.user }}</p>
    <p>Total: ${{ currentOrder?.total }}</p>
    <h4>Items:</h4>
    <ul>
      <li v-for="item in currentOrder?.items" :key="item.id">
        {{ item.name }} × {{ item.quantity }} = ${{ item.price * item.quantity }}
      </li>
    </ul>
  </el-dialog>
</template>
▶ 試してみよう

8. モジュール 6:ユーザー管理(1 時間)

(1) ユーザー一覧 + 役割管理

(1) ▶ サンプル:ユーザーロールの管理 + RBAC の編集(難易度:⭐)

ユーザー管理コア:リスト+タグのロール+チェックボックスグループの編集ロール:

VUE
<template>
  <BaseTable :data="users" :columns="columns">
    <template #column-roles="{ row }">
      <el-tag v-for="role in row.roles" :key="role" :type="roleType(role)">
        {{ role }}
      </el-tag>
    </template>
    <template #column-actions="{ row }">
      <el-button @click="editRoles(row)">Edit Roles</el-button>
    </template>
  </BaseTable>
  
  <el-dialog v-model="rolesDialog" title="Edit Roles">
    <el-checkbox-group v-model="selectedRoles">
      <el-checkbox v-for="r in allRoles" :key="r" :value="r">{{ r }}</el-checkbox>
    </el-checkbox-group>
    <template #footer>
      <el-button @click="saveRoles">Save</el-button>
    </template>
  </el-dialog>
</template>
▶ 試してみよう

(2) 5つの役割ごとの権限

TS
// utils/roles.ts
export const ROLES = {
  admin: {
    label: 'Admin',
    permissions: ['*']  // Full Access
  },
  manager: {
    label: 'Manager',
    permissions: ['product.*', 'order.*', 'user.read']
  },
  editor: {
    label: 'Editor',
    permissions: ['product.read', 'product.edit', 'order.read']
  },
  viewer: {
    label: 'Viewer',
    permissions: ['*.read']
  },
  guest: {
    label: 'Guest',
    permissions: ['product.read']
  }
}

9. モジュール7:マーケティングキャンペーン(1時間)

(1) クーポン管理

(1) ▶ サンプル:クーポン一覧+期間(難易度 ⭐)

マーケティングモジュールのコア機能:el-table + タグステータス + 時刻形式:

VUE
<template>
  <el-table :data="coupons" stripe>
    <el-table-column prop="code" label="Code" />
    <el-table-column prop="type" label="Type" :formatter="formatType" />
    <el-table-column prop="discount" label="Discount" />
    <el-table-column label="Status">
      <template #default="{ row }">
        <el-tag :type="statusType(row.status)">{{ row.status }}</el-tag>
      </template>
    </el-table-column>
    <el-table-column label="Valid Period">
      <template #default="{ row }">
        {{ formatDate(row.startAt) }} ~ {{ formatDate(row.endAt) }}
      </template>
    </el-table-column>
  </el-table>
</template>
▶ 試してみよう

10. モジュール 8:導入(1 時間)

(1) ビルドの最適化

(1) ▶ サンプル:Vite における手動のチャンク分割の最適化 (難易度: ⭐⭐)

モジュールコアのデプロイ:手動によるチャンク分割(vue / echarts / ui / utils):

TS
// vite.config.ts(Production Environment Optimization)
build: {
  outDir: 'dist',
  sourcemap: true,  // Used for Sentry
  minify: 'esbuild',
  cssMinify: 'lightningcss',
  rollupOptions: {
    output: {
      manualChunks: {
        'vue-vendor': ['vue', 'vue-router', 'pinia'],
        'echarts-vendor': ['echarts', 'vue-echarts'],
        'ui-vendor': ['element-plus'],
        'utils': ['axios', 'dayjs']
      }
    }
  }
}
▶ 試してみよう

(2) Vercelへのデプロイ

BASH
npm install -g vercel
vercel --prod
JSON
// vercel.json
{
  "build": { "command": "npm run build" },
  "outputDirectory": "dist",
  "framework": "vite",
  "rewrites": [
    { "source": "/(.*)", "destination": "/index.html" }
  ]
}

(3) パフォーマンスの監視

TS
// main.ts
import * as Sentry from '@sentry/vue'

Sentry.init({
  app,
  dsn: 'https://your-dsn@sentry.io/123',
  tracesSampleRate: 0.1
})

11. 8つのモジュールすべてのコード量統計

モジュール コード行数 主な機能
1 プロジェクトの初期化 300 Vite + Pinia + Router + Element Plus + ECharts
2 認証 400 JWT + ルートガード + パーミッション・ディレクティブ + 5 つのロール
3つのダッシュボード 500 5つのECharts + 4つのKPI + リアルタイムデータ
4 プロダクトマネジメント 600 CRUD + 一覧表示 + 編集 + 画像アップロード + 権限設定
5 注文管理 400 一覧 + 詳細 + ステータス変更 + エクスポート
6 ユーザー管理 300 一覧 + 役割管理 + 権限編集
7つのマーケティングキャンペーン 300 クーポン + 期間 + ステートマシン
8 デプロイとリリース 200 Viteのビルド最適化 + Vercel + Sentry
合計 3,000 8モジュールからなる完全なSaaSバックエンド

12. クイックリファレンス:よくある5つの間違い

エラー 症状 解決策
初回読み込み時に画面が真っ白になる ルーター/Piniaの読み込み順序エラー main.ts内で、app.use(pinia) の前に app.use(router) を呼び出してください
権限の有効期限が切れています 役割が更新されていません ウォッチ権限
10,000回のスタッター発生 テーブルのパフォーマンス低下 仮想スクロール el-table-v2
EChartsが表示されない コンテナの高さが0 style="height: 300px" を設定
デプロイ後に404エラーが発生 SPAのルーティングに関する問題 Vercelによる設定の書き換え

❓ よくある質問

Q モジュール8の所要時間はどれくらいですか?
A 10~12時間(コースのペースに従った場合)。経験豊富な開発者であれば4~6時間です。
Q バックエンドAPIをどのようにモック化すればよいですか?
A MSW(Mock Service Worker)またはExpress + json-serverを使用します。このチュートリアルでは前者を使用します。
Q Element Plus は TypeScript を完全にサポートしていますか?
A はい。Element Plus 2.x は TypeScript で完全に書き直されており、完全な型推論を提供しています。
Q 10,000個のフィールドがあるフォームを最適化するにはどうすればよいですか?
A el-table-v2(Element Plus 公式バーチャルスクロール版)または vue-virtual-scroller を使用してください。パフォーマンスが100倍向上します。
Q 本番環境へのデプロイ方法は?
A Vercel(推奨、無料)+カスタムドメイン。vercel --prod 1つのコマンドで完了。
Q 30レッスンのコースを修了した後、何を学ぶべきですか?
A (1) Nuxt 3 (SSR);(2) React(比較分析);(3) マイクロフロントエンド(qiankun);(4) モバイル開発(uni-app)。
Q このプロジェクトテンプレートをそのまま使用できますか?
A はい。コードは完全にオープンソースであり、エンタープライズプロジェクトの出発点としてそのまま使用できます。

📖 まとめ


13. 🎉 レッスン30を完了しました!

全30レッスンのVue 3コースを修了されたことをお祝い申し上げます! 基礎からエンタープライズレベルのプロジェクトまで、これまでに以下の内容を習得されました:

今後の対応に関する提言


📝 練習問題

(1) 基礎問題(難易度:⭐)

モジュール 1(プロジェクトの初期化)とモジュール 2(ログインと認証)を完了してください:

  1. Vite + Vue 3 + Pinia + Element Plus
  2. ログインページ + ダッシュボード(プレースホルダー) 3.5 ロール権限の基礎

(2) 上級問題(難易度:⭐⭐)

4つの主要モジュール(初期化/ログイン/ダッシュボード/製品)を完了してください:

  1. Vite および TypeScript の設定を完了する
  2. 5つのEChartsチャート
  3. 製品のCRUD(10フィールド)

(3) チャレンジ問題(難易度:⭐⭐⭐)

8つのモジュールすべてを完了し、デプロイを行う:

  1. 全8モジュール(10~12時間)
  2. TypeScriptの厳格な型付け
  3. パフォーマンスの最適化(仮想スクロール/手動チャンク)
  4. テストカバレッジ(5つのコンポーネント+3つのE2Eテスト)
  5. Vercelへのデプロイ + Sentryとの連携
  6. 30レッスンのVueチュートリアルを修了——卒業! 🎉
Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%