Перейти к основному содержимому

Form Guardian для Vue

Полное руководство по использованию Form Guardian с Vue 3 и Vue 2 приложениями.

🚀 Быстрый старт

Установка

npm install @form-guardian/dom

Vue 3 Composition API

<script setup>
import { ref, onMounted, onBeforeUnmount } from 'vue';
import { attachFormAutosave } from '@form-guardian/dom';

const formRef = ref(null);
let autosave = null;

onMounted(() => {
autosave = attachFormAutosave({
formId: 'contact-form',
root: formRef.value,
autoRestore: true,
debounceMs: 500,
});
});

onBeforeUnmount(() => {
if (autosave) {
autosave.destroy();
}
});

const handleSubmit = async () => {
await autosave.clear();
// Логика отправки...
};
</script>

<template>
<form ref="formRef" @submit.prevent="handleSubmit">
<input v-model="name" name="name" placeholder="Имя" />
<input v-model="email" name="email" type="email" placeholder="Email" />
<textarea v-model="message" name="message" placeholder="Сообщение" />
<button type="submit">Отправить</button>
</form>
</template>

Vue 2 Options API

<template>
<form ref="contactForm" @submit.prevent="handleSubmit">
<input v-model="form.name" name="name" placeholder="Имя" />
<input v-model="form.email" name="email" type="email" />
<textarea v-model="form.message" name="message" />
<button type="submit">Отправить</button>
</form>
</template>

<script>
import { attachFormAutosave } from '@form-guardian/dom';

export default {
name: 'ContactForm',
data() {
return {
form: {
name: '',
email: '',
message: '',
},
autosave: null,
};
},
mounted() {
this.autosave = attachFormAutosave({
formId: 'contact-form',
root: this.$refs.contactForm,
autoRestore: true,
});
},
beforeUnmount() { // Vue 3: beforeUnmount, Vue 2: beforeDestroy
if (this.autosave) {
this.autosave.destroy();
}
},
methods: {
async handleSubmit() {
await this.autosave.clear();
// Логика отправки...
},
},
};
</script>

📋 Полные примеры

Со статусом черновика

<script setup>
import { ref, onMounted, onBeforeUnmount } from 'vue';
import { attachFormAutosave } from '@form-guardian/dom';

const formRef = ref(null);
const hasDraft = ref(false);
const draftTime = ref(null);
let autosave = null;

onMounted(async () => {
autosave = attachFormAutosave({
formId: 'contact-form',
root: formRef.value,
autoRestore: true,
onAfterSave: async () => {
hasDraft.value = true;
draftTime.value = new Date();
},
});

// Проверка наличия существующего черновика
hasDraft.value = await autosave.hasDraft();
if (hasDraft.value) {
const meta = await autosave.getDraftMeta();
draftTime.value = new Date(meta.updatedAt);
}
});

onBeforeUnmount(() => {
autosave?.destroy();
});

const handleSubmit = async () => {
await autosave.clear();
hasDraft.value = false;
draftTime.value = null;
// Отправка...
};
</script>

<template>
<div>
<div v-if="hasDraft" class="draft-alert">
📝 Черновик сохранен в {{ draftTime?.toLocaleTimeString() }}
</div>

<form ref="formRef" @submit.prevent="handleSubmit">
<input name="name" placeholder="Имя" />
<input name="email" type="email" placeholder="Email" />
<textarea name="message" placeholder="Сообщение" />
<button type="submit">Отправить</button>
</form>
</div>
</template>

С диалогом ручного восстановления

<script setup>
import { ref, onMounted, onBeforeUnmount } from 'vue';
import { attachFormAutosave } from '@form-guardian/dom';

const formRef = ref(null);
const showRestoreDialog = ref(false);
const draftTimestamp = ref(null);
let autosave = null;

onMounted(async () => {
autosave = attachFormAutosave({
formId: 'my-form',
root: formRef.value,
autoRestore: false, // Ручное восстановление
});

const hasDraft = await autosave.hasDraft();
if (hasDraft) {
const meta = await autosave.getDraftMeta();
draftTimestamp.value = meta.updatedAt;
showRestoreDialog.value = true;
}
});

onBeforeUnmount(() => autosave?.destroy());

const restoreDraft = async () => {
await autosave.restore();
showRestoreDialog.value = false;
};

const discardDraft = async () => {
await autosave.clear();
showRestoreDialog.value = false;
};
</script>

<template>
<div>
<!-- Диалог восстановления -->
<div v-if="showRestoreDialog" class="modal">
<div class="modal-content">
<h3>Найден несохраненный черновик</h3>
<p>Последнее сохранение: {{ new Date(draftTimestamp).toLocaleString() }}</p>
<button @click="restoreDraft">Восстановить</button>
<button @click="discardDraft">Отказаться</button>
</div>
</div>

<form ref="formRef">
<!-- поля формы -->
</form>
</div>
</template>

Интеграция с Vuelidate

<script setup>
import { ref, reactive, onMounted, onBeforeUnmount } from 'vue';
import { useVuelidate } from '@vuelidate/core';
import { required, email } from '@vuelidate/validators';
import { attachFormAutosave } from '@form-guardian/dom';

const formRef = ref(null);
const state = reactive({
name: '',
email: '',
message: '',
});

const rules = {
name: { required },
email: { required, email },
message: { required },
};

const v$ = useVuelidate(rules, state);
let autosave = null;

onMounted(() => {
autosave = attachFormAutosave({
formId: 'contact-form',
root: formRef.value,
autoRestore: true,
onAfterRestore: () => {
// Запуск валидации после восстановления
v$.value.$touch();
},
});
});

onBeforeUnmount(() => autosave?.destroy());

const handleSubmit = async () => {
v$.value.$touch();
if (v$.value.$invalid) return;

await autosave.clear();
// Отправка...
};
</script>

<template>
<form ref="formRef" @submit.prevent="handleSubmit">
<div>
<input v-model="state.name" name="name" />
<span v-if="v$.name.$error">{{ v$.name.$errors[0].$message }}</span>
</div>

<div>
<input v-model="state.email" name="email" type="email" />
<span v-if="v$.email.$error">{{ v$.email.$errors[0].$message }}</span>
</div>

<div>
<textarea v-model="state.message" name="message" />
<span v-if="v$.message.$error">{{ v$.message.$errors[0].$message }}</span>
</div>

<button type="submit" :disabled="v$.$invalid">Отправить</button>
</form>
</template>

🔧 Продвинутые паттерны

Composable для переиспользования

// composables/useFormDraft.ts
import { ref, onMounted, onBeforeUnmount } from 'vue';
import { attachFormAutosave } from '@form-guardian/dom';

export function useFormDraft(formId: string, formRef, options = {}) {
const hasDraft = ref(false);
const draftTimestamp = ref(null);
let autosave = null;

onMounted(async () => {
autosave = attachFormAutosave({
formId,
root: formRef.value,
...options,
onAfterSave: async (values) => {
hasDraft.value = true;
draftTimestamp.value = Date.now();
options.onAfterSave?.(values);
},
});

hasDraft.value = await autosave.hasDraft();
if (hasDraft.value) {
const meta = await autosave.getDraftMeta();
draftTimestamp.value = meta.updatedAt;
}
});

onBeforeUnmount(() => {
autosave?.destroy();
});

const clearDraft = async () => {
await autosave?.clear();
hasDraft.value = false;
draftTimestamp.value = null;
};

return {
hasDraft,
draftTimestamp,
clearDraft,
};
}

Использование:

<script setup>
import { ref } from 'vue';
import { useFormDraft } from '@/composables/useFormDraft';

const formRef = ref(null);
const { hasDraft, draftTimestamp, clearDraft } = useFormDraft(
'my-form',
formRef,
{ autoRestore: true }
);
</script>

<template>
<div>
<div v-if="hasDraft" class="alert">
Черновик от {{ new Date(draftTimestamp).toLocaleString() }}
</div>
<form ref="formRef">
<!-- поля -->
</form>
</div>
</template>

💡 Лучшие практики

✅ ДЕЛАЙТЕ

  • Всегда вызывайте destroy() в onBeforeUnmount / beforeDestroy
  • Используйте autoRestore: true для лучшего UX
  • Очищайте черновик после успешной отправки
  • Показывайте статус черновика пользователям

❌ НЕ ДЕЛАЙТЕ

  • Не забывайте делать очистку (вызывать destroy())
  • Не сохраняйте поля паролей (уже исключены по умолчанию)
  • Не сохраняйте при каждом нажатии клавиши без debounce

🔗 Связанные ресурсы