nuxt学到数据库这里,就涉及到响应数据,父组件向子组件传值,子组件向父组件传值,最终还是需要掌握vue3的组件知识了。学习真的是一个长期的过程,不管学习了什么知识,有多少,都应该及时的记录下来,这个记录的过程,就是不断巩固自身知识的过程。之前学习其他框架的时候,不用,过两天就忘记了,当许多插件解决问题,忘记这个插件名字后,那想不起来的时候就特别痛苦了。好记忆不如烂笔头,及时记录下来吧。
ui.nuxt.com中的form章节,有很多实例代码:Form - Nuxt UI
还有,github上有很多样例代码,比如这个Table的代码: https://github.com/nuxt/ui/blob/dev/docs/components/content/examples/TableExampleAdvanced.vue
这个Table例子看了好多遍,代码还是掌握不了啊,感觉有点难啊。Table - Nuxt UI
还是根据table上面的简单的例子,做一些修改吧,从简单的入手。
首先接着之前的文章,把drizzle orm的增删改查等做成对应的api接口路由。https://hub.nuxt.com/docs/recipes/drizzle#usage
读:select
server/api/users/index.get.ts
TypeScript
export default eventHandler(async () => {
const todos = await useDrizzle().select().from(tables.users).all()
return todos
})
增:insert
server/api/users/index.post.ts
TypeScript
export default eventHandler(async (event) => {
const { name, email, password, avatar, createdAt } = await readBody(event);
const todo = await useDrizzle()
.insert(tables.users)
.values({
name,
email,
password,
avatar,
createdAt: new Date(),
})
.returning()
.get();
return todo;
});
改:update
server/api/users/[id].patch.ts
TypeScript
export default eventHandler(async (event) => {
const { id } = getRouterParams(event);
const { completed } = await readBody(event);
const todo = await useDrizzle()
.update(tables.users)
.set({
completed,
})
.where(eq(tables.users.id, Number(id)))
.returning()
.get();
return todo;
});
删:delete
server/api/users/[id].delete.ts
TypeScript
export default eventHandler(async (event) => {
const { id } = getRouterParams(event);
const deletedTodo = await useDrizzle()
.delete(tables.users)
.where(and(eq(tables.users.id, Number(id))))
.returning()
.get();
if (!deletedTodo) {
throw createError({
statusCode: 404,
message: 'User not found',
});
}
return deletedTodo;
});
读取的时候,都是一次性的把数据全部select出来,这个当然是不对的,不过初学的时候,先这样应付着写,之后看看怎么带参数select,进行一页一页的读取。
修改和删除的时候,是根据id来操作的,这个时候,url中就需要带上id的值这个参数了。
使用UTable控件把表渲染出来。第一部就是添加数据了。先进行简单的:
TypeScript
const datas = {
name: 'John Doe',
email: 'john@example.com',
password: 'password123',
avatar: 'https://example.com/avatar/john.png',
createdAt: new Date(),
}
$fetch('/api/users', {
method: 'POST',
body: datas,
}).then((users: any) => {
useToast().add({
title: `${users.id}号添加成功!`,
description: `
用户名:${users.name}
邮箱:${users.email}
`,
});
});
先定义需要添加的数据,然后使用$fetch进行post添加。成功后弹出消息提示一下。
下一步就是点击按钮,弹窗添加新用户,弹出的窗口做成控件,那就涉及到父组件向子组件传值,和子组件向父组件传值的了。
简单来说,子组件向父组件传值,就是要emit,子组件emit一个方法,在父组件用这个方法名绑定一个新的方法,父组件上的新的方法就可以执行子组件方法里的方法了,也可以执行父组件上的其他方法。
父组件向子组件传值,那就更简单了,就是子组件先定义一个属性值,不用赋初始值,在父组件上使用的时候,给这个属性值绑定一个数值,在子组件里,就可以直接获取这个属性值的值(父组件上绑定的值)。
先看子组件向父组件传值(方法):【Vue/Nuxt.js】使用Composition API实现子组件向父组件传递数据的方法 - TeHub
TypeScript
子组件
<script setup>
const emit = defineEmits(['hogeEvent']); //emit之外也可以
const hoge = () =>{
emit('hogeEvent');
}
</script>
<template>
<button type="button" @click="hoge">emit测试</button>
</template>
父组件
<script setup>
const customEvent = () =>{
console.log('test')
}
</script>
<template>
<ChildComponents @hogeEvent="customEvent"></ChildComponents>
</template>
在我这个user的实例中,有很多属性值,所可以emit可以多个函数值:
TypeScript
/**
* 属性
*/
type Props = { modelValue: boolean; info: number };
// defineProps<Props>();
const props = defineProps<Props>();
const { info } = toRefs(props);
最后一行info被从属性值里解构出来变成响应式数据后,就可以直接使用了。
TypeScript
/**
* 事件
*/
const emit = defineEmits(['parentRefresh', 'update:modelValue', 'hogeEvent']);
const onUpdate = (value: boolean) => {
emit('update:modelValue', value);
};
const parentRefresh = () => {
const data = { message: '这是子组件传递的数据' };
emit('parentRefresh', data);
};
emit可以定义多个方法,并且需要在下面进行方法的实现,如果父组件中在绑定的方法中有其他方法需要执行,比如刷新表refresh()操作,就需要在最后执行一下parentRefresh()
父组件中绑定的方法或属性值:
TypeScript
<DataUserForm
v-model="isOpen"
@parentRefresh="showrefresh"
@hoge-event="customEvent"
:info="info"
/>
const isOpen = ref(false);
const info = computed(() => {
console.log(users.value.length);
return users.value.length;
});
/**
* 添加用户 按钮事件
*/
const addUser = () => {
isOpen.value = true;
};
const showrefresh = (data: any) => {
console.log('父组件接收到的数据:', data);
refresh();
};
const customEvent = () => {
console.log('test');
};
isOpen是父组件上的按钮点击后显示子组件弹窗的UModal控件。info在父组件是计算select出来所有users表的数据长度,定义的时候,涉及到其他数据来源,所以需要使用computed方法,才能让数据保持响应式数据,自动同步更新。这个打印数据的动作console.log(users.value.length);,就是测试数据是否同步:
打印出来第一次的0,就是info的初始值0,第二次59,是获取数据库数据后,computed计算出来的值。或者加上服务端客服端执行判断:
TypeScript
const info = computed(() => {
if (import.meta.server) {
console.log('服务端执行:' + users.value.length);
}
if (import.meta.client) {
console.log('客户端端执行:' + users.value.length);
}
return users.value.length;
});
这样看就比较明显了,先执行客户端,info的值为0,然后等从数据库select的值获取到后,再次同步了info的值59,客户端执行了2次。 这个就是响应式数据自动更新的动作,数据变动,就是computed再次执行一次。
refresh()刷新数据表,就是读取数据方法useLazyFetch的一个互动操作:
TypeScript
const { status, data, refresh } = await useLazyFetch<any>('/api/users');
const users = computed(() => {
filtered.value.sort((a: any, b: any) => {
return b.id - a.id;
});
return filtered.value;
});
<UTable
:columns="columns"
:emptyState="table.emptyState"
:loadingState="table.loadingState"
:rows="rows"
:loading="status === 'pending'"
class="mb-2"
>
:loading="status === 'pending'" 是加载的时候,在获取数据前有个加载动画。获取的对象数组的排序方法非常有意思:
TypeScript
array.sort((a: any, b: any) => {
return b.id - a.id;
});
这个是降序,如果想升序,return a.id-b.id就可以了。
还有一个知识点,就是node时间格式化操作,其他框架有不少操作插件,安装之后就可以进行日期格式化了,nuxt上搜索了一些插件,比如moment,time等,研究一下还是不行,最后点到new Date()上发现,typescript官方就有日期本地格式化的操作啊!!!
幸亏跟着drizzle orm 的教程添加了seed种子(https://hub.nuxt.com/docs/recipes/drizzle#seed-the-database-optional),不然还发现不了,估计要耽搁很多时间啊。
关于数据表UTable的很多操作,还需要从ui/docs/components/content/examples/TableExampleAdvanced.vue at dev · nuxt/ui · GitHub
这个源码里慢慢的学习,加上baidu和google搜索,不然很花时间啊。
还有一个,就是需要安装vscode职能插件,我安装的是 通义灵码,免费的,还是很不错的,可以提醒不同的写法。
关于创建表单数据,并提交,在ui.nuxt.com上的form栏,有不同的数据库实体类的插件,可以挑选https://ui.nuxt.com/components/form#usage
使用方法大同小异。
TypeScript
/**
* zod
*/
import { z } from 'zod';
import type { FormError, FormSubmitEvent } from '#ui/types';
const schema = z.object({
name: z.string(),
email: z.string().email('邮箱格式不正确'),
password: z.string().min(4, '最少4位字符'),
avatar: z.string(),
});
type Schema = z.output<typeof schema>;
const state = computed(() => {
return reactive({
name: z.string().parse('andu' + (info.value + 1)),
email: z.string().parse('andu' + (info.value + 1) + '@qq.com'),
password: z.string().parse('andu' + (info.value + 1) + 'password'),
avatar: z.string().parse('https://picsum.photos/200/200'),
});
});
const validate = (state: any): FormError[] => {
const errors = [];
if (!state.email) errors.push({ path: 'email', message: '必填项' });
if (!state.password) errors.push({ path: 'password', message: '必填项' });
return errors;
};
在添加用户的时候,我想自动的给表单赋值,免得一个个手动输入太麻烦了。我的想法是获取数据长度,传给子组件,然后在名称和邮箱名后面数值+1,这样邮箱不就不重复了吗?info就是从父组件传递进来的数据长度值,怎么让表单的数据都自动更新呢?当然是要把它们放到computed里面去了。这样添加数据的是很不用一个个输入的了。
子组件最上角就是获取的数据长度。注意:这个赋值的过程,根本搜不到啊,非常非常意外的是,捣腾了好久,终于,通义灵码给自动推荐出来了!!!好意外!!!
TypeScript
name: z.string().parse('andu' + (info.value + 1)),
就是这个z.string().parse()方法,原理是啥,我也不懂,就这么特么的,智能推荐出来了。AI写代码,有的时候是真牛啊。
查询、增加做完了,下面就是删除,删除最简单:
TypeScript
const items = (row: any) => [
[
{
label: '编辑',
icon: 'i-heroicons-pencil-square-20-solid',
},
{
label: '复制',
icon: 'i-heroicons-document-duplicate-20-solid',
},
],
[
{
label: '存档',
icon: 'i-heroicons-archive-box-20-solid',
},
{
label: '移动',
icon: 'i-heroicons-arrow-right-circle-20-solid',
},
],
[
{
label: '删除',
icon: 'i-heroicons-trash-20-solid',
click: () => {
$fetch('/api/users/' + row.id, {
method: 'DELETE',
}).then((users: any) => {
useToast().add({
title: `${users.id}号删除成功!`,
description: `
用户名:${users.name}<br/>
邮箱:${users.email}
`,
});
});
refresh();
},
},
],
];
<template #actions-data="{ row }">
<UDropdown :items="items(row)">
<UButton
color="gray"
variant="ghost"
icon="i-heroicons-ellipsis-horizontal-20-solid"
/>
</UDropdown>
</template>
</UTable>
删除是放在表格最后一列里,这个也是官网的例子有的。只要是路由有id:[id].delete,就需要在$fetch方法中的url里加上row.id。
删除后,刷新一下表refresh()。
修改还没做,应该也不难了。这几天卡在好几个点。第一个是日期格式化操作,尝试了几个插件和使用,花了不少时间。还有就是父组件和子组件之间的相互传值,这个应该是vue的基本知识,或者说是vue3的知识,网上多是Vue2的代码,搜索不少时间,慢慢尝试和总结出来传值的规律了。还有就是响应式数据问题,info的获取后,怎么在表单里让它自动更新,变成响应式数据,也是摸索学习了不少时间。最后就是ui.nuxt.com上最后Table的例子的源代码,直接照搬过来,还是没学会怎么用,感觉官方的代码好少,做的时候还多,而且官方的例子源码还不少,这要学起来,那得花多少时间啊......
时间太不够用了......