Yayaw
Documentation
Bien démarrer

Démarrage Vue et Nuxt

Une table Vue native complète, un client Query partagé et l’intégration Nuxt.

Construisez la même table produits avec une configuration Vue native et un client Vue Query partagé. Installez le registre Vue avant de copier ces fichiers. Les concepts expliquent le fonctionnement commun.

Structure du projet

products/
  shared/products.ts
  vue/product-config.ts
  vue/product-actions.ts
  vue/products.vue
  vue/main.ts

1. Définir les enregistrements

products.ts
export interface Product extends Record<string, unknown> {
  active: boolean;
  id: string;
  image: string;
  name: string;
  price: number;
  /** Civil dates, inclusive at both ends: the window a batch is being restocked. */
  restockFrom: string;
  restockTo: string;
  status: "draft" | "active" | "archived";
  stock: number;
  tags: string[];
  updatedAt: string;
}

export const products: Product[] = [
  {
    id: "desk",
    name: "Oak desk",
    price: 420,
    stock: 12,
    status: "active",
    active: true,
    tags: ["office", "wood"],
    image: "https://placehold.co/640x480/png?text=Oak+desk",
    restockFrom: "2026-09-14",
    restockTo: "2026-09-18",
    updatedAt: "2026-09-01T09:00:00Z",
  },
  {
    id: "lamp",
    name: "Desk lamp",
    price: 65,
    stock: 0,
    status: "draft",
    active: false,
    tags: ["office"],
    image: "https://placehold.co/640x480/png?text=Desk+lamp",
    restockFrom: "2026-09-17",
    restockTo: "2026-09-24",
    updatedAt: "2026-09-02T09:00:00Z",
  },
  {
    id: "chair",
    name: "Studio chair",
    price: 180,
    stock: 6,
    status: "active",
    active: true,
    tags: ["office", "seating"],
    image: "https://placehold.co/640x480/png?text=Studio+chair",
    restockFrom: "2026-09-23",
    restockTo: "2026-10-02",
    updatedAt: "2026-09-03T09:00:00Z",
  },
];

2. Configurer la table

product-config.ts
import { defineTableConfig } from "@/components/ui/yayaw-table-vue";

export const productConfig = defineTableConfig({
  id: "products",
  translations: { namespace: "products", keys: {} },
  columns: {
    definitions: [
      { id: "name", header: "Name", type: "text" },
      { id: "price", header: "Price", type: "number" },
      { id: "stock", header: "Stock", type: "number" },
      {
        id: "status",
        header: "Status",
        type: "select",
        options: [
          { label: "Draft", value: "draft" },
          { label: "Active", value: "active" },
          { label: "Archived", value: "archived" },
        ],
      },
    ],
    order: ["name", "price", "stock", "status"],
    visible: ["name", "price", "stock", "status"],
    mandatory: ["name"],
    sort: [{ id: "name", desc: false }],
  },
  table: {
    allowCreate: false,
    allowEdit: false,
    allowDelete: false,
    allowDuplicate: false,
    allowBulkEdit: false,
    allowBulkDelete: false,
    enableRowSelection: false,
    enableViews: false,
    syncUrl: false,
    defaultPageSize: 10,
  },
});

export const getTableConfig = (tableType: string) =>
  tableType === "products" ? productConfig : undefined;

3. Connecter les actions de liste

Cet adaptateur implémente le même contrat de requête que React. La recherche porte sur les noms, références, prix, stocks, statuts, booléens et tags. Les filtres et le tri s’appliquent avant la pagination. Le résultat contient les totaux. Les enregistrements sont fictifs et restent en mémoire.

product-actions.ts
import type { TableActions } from "@/components/ui/yayaw-table-vue/types";
import { products } from "../shared/products";
import { listRecords } from "./list-records";

export const productActions: TableActions = {
  list: (input) => listRecords(products, input),
};
export const getTableActions = (tableType: string) =>
  tableType === "products" ? productActions : undefined;

Adaptateur de liste complet

list-records.ts
import {
  compatibleListParams,
  matchesContractFilter,
  normalizeFilterEnvelope,
  recordValue,
} from "@/components/ui/yayaw-table-vue/table-contracts";

export function listRecords<T extends Record<string, unknown>>(
  rows: T[],
  input: unknown
) {
  const params = compatibleListParams(recordValue(input));
  const page = Number(params.page);
  const limit = Number(params.limit);
  const search = String(params.search).toLowerCase();
  const filters = Object.entries(recordValue(params.filters));
  const advanced = normalizeFilterEnvelope(params.advancedFilters).filters;
  const matching = rows.filter((product) => {
    const row: Record<string, unknown> = product;
    const matchesSearch =
      !search ||
      ["name", "reference", "price", "stock", "status", "active", "tags"].some(
        (key) => {
          const value = product[key];
          return (
            value !== null &&
            value !== undefined &&
            String(value).toLowerCase().includes(search)
          );
        }
      );
    const matchesColumns = filters.every(([id, value]) =>
      matchesContractFilter(row[id], {
        type: typeof row[id] === "number" ? "number" : "text",
        operator: Array.isArray(value) ? "isAnyOf" : "contains",
        values: Array.isArray(value) ? value : [value],
      })
    );
    const matchesRule = (rule: Record<string, unknown>) =>
      matchesContractFilter(row[String(rule.columnId)], rule);
    const matchesAdvanced =
      advanced.length === 0 ||
      (params.advancedFilterJoin === "or"
        ? advanced.some(matchesRule)
        : advanced.every(matchesRule));
    return matchesSearch && matchesColumns && matchesAdvanced;
  });
  const sorting = Object.entries(recordValue(params.orderBy));
  matching.sort((left, right) => {
    for (const [id, direction] of sorting) {
      const a = (left as Record<string, unknown>)[id];
      const b = (right as Record<string, unknown>)[id];
      const comparison =
        typeof a === "number" && typeof b === "number"
          ? a - b
          : String(a ?? "").localeCompare(String(b ?? ""));
      if (comparison !== 0) {
        return direction === "desc" ? -comparison : comparison;
      }
    }
    return String(left.id).localeCompare(String(right.id));
  });
  return Promise.resolve({
    // Model a server response: later store mutations must not alter cached rows.
    data: structuredClone(matching.slice((page - 1) * limit, page * limit)),
    meta: {
      pageCount: Math.max(1, Math.ceil(matching.length / limit)),
      totalCount: matching.length,
    },
  });
}

4. Afficher le composant

Agrandir ↗

Données de démonstration. Les modifications restent dans cet aperçu.

5. Installer le client Query partagé

main.ts
import { QueryClient, VueQueryPlugin } from "@tanstack/vue-query";
import { createApp } from "vue";
import Products from "./products.vue";
import "@/components/ui/yayaw-table-vue/styles.css";

const app = createApp(Products);
app.use(VueQueryPlugin, { queryClient: new QueryClient() });
app.mount("#app");

Montez Products dans un élément avec id="app", ou réutilisez le point d’entrée et le provider de votre application. Gardez les styles du registre dans l’entrée CSS de l’application. Vue n’utilise pas Nuqs.

Données locales

Pour une table entièrement locale, passez :data="products" et retirez get-table-actions. Vue applique les filtres, le tri et la pagination à ce tableau. Pour une source distante, utilisez les actions : une page serveur isolée passée comme donnée locale ne représente pas tout le jeu de données.

Nuxt et SSR

Installez VueQueryPlugin dans un plugin Nuxt avec un nouveau QueryClient par application/requête. Réutilisez le mécanisme de déshydratation et d’hydratation du projet pour les requêtes préchargées. Importez les styles du registre dans la configuration CSS Nuxt. Isolez les rendus personnalisés qui nécessitent le navigateur et n’utilisez initialData, initialRowCount et initialPageCount que pour la requête initiale correspondante.

Notifications de l’application

Montez un unique Toaster de vue-sonner à la racine de l’application et importez vue-sonner/style.css si vous utilisez directement Sonner. Toutes les instances de table le partagent. Les formulaires conservent leurs erreurs de validation et de reprise près des champs.

Ajouter une fonctionnalité

La suite de la documentation est organisée par fonctionnalité. Utilisez l’onglet Vue dans les guides colonnes, formulaires, actions groupées et vues. La référence des actions explique les alias de requête et de résultat partagés avec React.

Sections du guide précédent

Vue 3 et Nuxt

Consultez le guide actuel : concepts.

Couverture fonctionnelle

Consultez le guide actuel : compatibility.

Prérequis d'exécution

Consultez le guide actuel : compatibility.

Lancer le workspace Vue

Consultez le guide actuel : installation.

Migrer depuis TanStack Table 8

Consultez le guide actuel : concepts.

Enregistrer le package

Consultez le guide actuel : installation.

Configurer une table

Consultez le guide actuel : configuration.

Densité du tableau

Consultez le guide actuel : layout.

Actions de toolbar du catalogue

Consultez le guide actuel : actions.

Connecter les actions côté serveur

Consultez le guide actuel : actions.

Champ de formulaire tablePicker

Consultez le guide actuel : table-picker.

Partager le cache Vue Query

Consultez le guide actuel : query-integration.

Construire l'item de registre shadcn-vue

Consultez le guide actuel : installation.

Correspondance React vers Vue

Consultez le guide actuel : compatibility.

Comportements communs à React et Vue

Consultez le guide actuel : compatibility.

Sélection multiple et sauvegarde inline

Consultez le guide actuel : inline-editing.

Un type de colonne dans toute l’interface

Consultez le guide actuel : columns.

Formulaires générés structurés

Consultez le guide actuel : form-layout.

Lignes regroupées

Consultez le guide actuel : grouping-calculations.

Contrôles des cartes et résultats vides

Consultez le guide actuel : gallery.

Barre de filtres optionnelle

Consultez le guide actuel : filtering.

Vues favorites personnelles

Consultez le guide actuel : views.

Consultation et activité d’une entrée

Consultez le guide actuel : record-details.