在 Nuxt 3.9 為 Vuetify UI 框架建立表單欄位驗證器

建立專案

建立專案。

1
2
npx nuxi@latest init nuxt-vuetify-validator
cd nuxt-vuetify-validator

安裝 Vuetify 框架。

1
npm i -D vuetify vite-plugin-vuetify sass

建立 plugins/vuetify.js 檔。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { createVuetify } from 'vuetify';
import 'vuetify/styles';
import * as components from 'vuetify/components';
import * as directives from 'vuetify/directives';
import '@mdi/font/css/materialdesignicons.css';

export default defineNuxtPlugin((nuxtApp) => {
const vuetify = createVuetify({
ssr: true,
components,
directives,
});
nuxtApp.vueApp.use(vuetify);
});

修改 nuxt.config.js 檔。

1
2
3
4
5
6
7
8
9
// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
devtools: {
enabled: true,
},
build: {
transpile: ['vuetify'],
},
});

實作

建立 validator 資料夾。

1
mkdir validator

建立 validator/rules/index.js 檔。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
const isEmpty = (v) => v === '' || v === null || v === undefined || (Array.isArray(v) && !v.length);

const required = () => (v) => !isEmpty(v);

const email = () => (v) => isEmpty(v) || /.+@.+\..+/.test(v);

const alphaDash = () => (v) => isEmpty(v) || /^[a-zA-Z0-9-_]+$/.test(v);

const alphaDashDot = () => (v) => isEmpty(v) || /^[a-zA-Z0-9-_.]+$/.test(v);

const lowercaseNumUnderscore = () => (v) => isEmpty(v) || /^[a-z0-9_]+$/.test(v);

const uppercaseNumUnderscore = () => (v) => isEmpty(v) || /^[A-Z0-9_]+$/.test(v);

const min = (minValue) => (v) => isEmpty(v) || parseFloat(v) >= minValue;

const max = (maxValue) => (v) => isEmpty(v) || parseFloat(v) <= maxValue;

const minLength = (length) => (v) => isEmpty(v) || v.length >= length;

const maxLength = (length) => (v) => isEmpty(v) || v.length <= length;

const minFileSize = (minValue) => (v) => isEmpty(v) || v.every((file) => file.size >= minValue * 1024 * 1024);

const maxFileSize = (maxValue) => (v) => isEmpty(v) || v.every((file) => file.size <= maxValue * 1024 * 1024);

const url = () => (v) => isEmpty(v) || /^(http|https):\/\/.+$/.test(v);

const unique = (items, ignored) => (v) => isEmpty(v) || (v === ignored) || !items.includes(v);

const json = () => (v) => {
if (isEmpty(v)) return true;
try {
JSON.parse(v);
return true;
} catch (e) {
return false;
}
};

export default {
required,
email,
alphaDash,
alphaDashDot,
lowercaseNumUnderscore,
uppercaseNumUnderscore,
min,
max,
minLength,
maxLength,
minFileSize,
maxFileSize,
url,
unique,
json,
};

建立 validator/field-validator.js 檔。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
import rules from './rules';

class FieldValidator {
constructor(name, messages) {
this.name = name.toLowerCase();
this.messages = messages;
this.rules = [];
this.isPassed = false;
}

getRule(name, ...args) {
return (v) => rules[name](...args)(v) || this.messages[name](this.name, ...args);
}

getRules() {
return this.isPassed ? [] : this.rules;
}

pushRule(name, ...args) {
const rule = this.getRule(name, ...args);
this.rules.push(rule);
return this;
}

when(condition) {
if (!condition) {
this.isPassed = true;
}
return this;
}

required() {
return this.pushRule('required');
}

email() {
return this.pushRule('email');
}

alphaDash() {
return this.pushRule('alphaDash');
}

alphaDashDot() {
return this.pushRule('alphaDashDot');
}

lowercaseNumUnderscore() {
return this.pushRule('lowercaseNumUnderscore');
}

uppercaseNumUnderscore() {
return this.pushRule('uppercaseNumUnderscore');
}

min(minValue) {
return this.pushRule('min', minValue);
}

max(maxValue) {
return this.pushRule('max', maxValue);
}

minLength(length) {
return this.pushRule('minLength', length);
}

maxLength(length) {
return this.pushRule('maxLength', length);
}

minFileSize(length) {
return this.pushRule('minFileSize', length);
}

maxFileSize(length) {
return this.pushRule('maxFileSize', length);
}

url() {
return this.pushRule('url');
}

unique(items, ignored) {
return this.pushRule('unique', items, ignored);
}

json() {
return this.pushRule('json');
}
}

export default FieldValidator;

建立 validator/locales/en.js 檔。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
export default {
required: (field) => `The ${field} field is required.`,
email: (field) => `The ${field} field must be a valid email address.`,
alphaDash: (field) => `The ${field} field must only contain letters, digits and underscores.`,
alphaDashDot: (field) => `The ${field} field must only contain letters, digits, underscores and dots.`,
lowercaseNumUnderscore: (field) => `The ${field} field must only contain lowercase letters, digits and underscores.`,
uppercaseNumUnderscore: (field) => `The ${field} field must only contain uppercase letters, digits and underscores.`,
min: (field, value) => `The ${field} field must be at least ${value}.`,
max: (field, value) => `The ${field} field must not be greater than ${value}.`,
minLength: (field, length) => `The ${field} field must be at least ${length} characters.`,
maxLength: (field, length) => `The ${field} field must not be greater than ${length} characters.`,
minFileSize: (field, value) => `The ${field} field must be at least ${value} megabytes.`,
maxFileSize: (field, value) => `The ${field} field must not be greater than ${value} megabytes.`,
url: (field) => `The ${field} field must be a valid URL.`,
unique: (field) => `The ${field} has already been taken.`,
json: (field) => `The ${field} field must be a valid JSON string.`,
};

建立 validator/locales/index.js 檔。

1
2
3
4
5
import en from './en';

export default {
en,
};

建立 validator/Validator.js 檔。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import FieldValidator from './field-validator';
import locales from './locales';

class Validator {
constructor() {
this.locale = 'en';
}

get messages() {
return locales[this.locale];
}

setLocale(locale) {
this.locale = locale;
return this;
}

createField(name) {
return new FieldValidator(name, this.messages);
}
}

export default Validator;

建立 validator/validate.js 檔。

1
2
3
4
5
import Validator from './Validator';

const validate = name => (new Validator()).createField(name);

export default validate;

建立 validator/index.js 檔。

1
2
3
4
5
import validate from './validate';

export {
validate,
};

註冊

plugins 資料夾,建立 validator.js 檔。

1
2
3
4
5
import { validate } from '~/validator';

export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.provide('validate', validate);
});

使用

修改 app.vue 檔。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<template>
<div>
<v-form>
<v-text-field
:rules="(
$validate('email')
.required()
.email()
.getRules()
)"
/>
</v-form>
</div>
</template>

啟動服務。

1
npm run dev

程式碼