在最近的学习中,初步了解了vue的一些使用方法以及之前学过一些简单openai,于是我产生了这个想法,将两者结合来增强我的实战能力并让我增加熟练的使用vue。接下来让我来简单介绍一下我的todo-list吧。
页面展示
目录结构
布局
本次实战我使用的是BEN命名方式,由于不太熟练取名可能不准确请各位见谅。
todo-list__title: 用于存放标题。
todo-list__container: 用于存放todo-list的主体内容。
- todo-list__container_body:存放了input框和添加按钮。
- todo-list__container_content:其中有todo-list__container_ul和todo-list__container_footer,分成存放list列表和一些按钮。
todo-list__res: 用于存放openai返回的结果。
rodo-list主要功能实现
本项目我使用的是reactive来实现list的响应式变化,todos是我用于存放数据的一个容器。
const state = reactive({
todos: []
});
增加列表
为实现这个功能,我主要用到了@click
和v-model
,给add按钮添加绑定事件,用v-model
实现双向绑定来获取输入框内的值。(这里仅涉及功能实现,后面介绍ai时会重新编写这里)
let todoAdd = ref('');
const Add = (text) => {
text = todoAdd.value.trim();
if (text === '') {
alert('输入不能为空');
} else {
state.todos.push({ id: state.todos.length + 1, text: todoAdd.value, done: false, action: '' });
todoAdd.value = '';
}
}
删除列表
从增加列表可以看出我给list添加了一个id属性,用于删除。当点击删除时,点击事件会将要删除的list的id传给删除方法,我使用的是filter
筛选,将不是这个id的list重新传给todos。
<body>
<button type="button" @click="todoDel(item.id)" class="del">del</button>
<script>
const todoDel = (id) => {
state.todos = state.todos.filter(todo => todo.id !== id);
state.todos.forEach((todo, index) => {
todo.id = index + 1;
});
}
</script>
</body>
将完成list用删除线划掉
这个功能我使用了:class
动态监测list的done属性,并用其属性结果命名,当点击时会改变list的done属性。当为true时会触发text-decoration: line-through;
样式
<style>
.done {
text-decoration: line-through;
}
</style>
<body>
<span @click="todoDone(item.id)" :class="{ 'done': item.done }">{{ item.text }}</span>
<script>
const todoDone = (id) => {
state.todos.forEach((item) => {
if (item.id === id) {
item.done = !item.done;
}
});
}
</script>
</body>
查看全部列表、已完成列表、未完成列表
这里我使用的是computed
属性,它是用来计算并缓存基于其他数据(响应式依赖)的值。点击不同按钮传入不同状态,根据不同状态返回筛选出来的list。
<body>
<div class="todo-list_footer_btn">
<div class="All"><span @click="setFilter('all')">全 部</span></div>
<div class="Active"><span @click="setFilter('active')">未完成</span></div>
<div class="Completed"><span @click="setFilter('completed')">完 成</span></div>
</div>
<script>
const currentFilter = ref('all');
const setFilter = (type) => {
currentFilter.value = type;
}
const filteredTodos = computed(() => {
switch (currentFilter.value) {
case 'active':
return state.todos.filter(todo => !todo.done);
case 'completed':
return state.todos.filter(todo => todo.done);
default:
return state.todos; // 显示全部
}
})
</script>
</body>
项目升级(引入AI)
在本项目中AI主要实现的是根据用户添加的list给出相应的规划安排。
效果如下
功能实现
一、导包并引入项目
使用包管理器将本次ai需要使用的资源导入,在项目文件终端使用如npm i @koa/bodyparser
等指令。
import bodyParser from '@koa/bodyparser';
import Koa from 'koa'//后端简单框架
import Router from 'koa-router';//路由对象来监听端口资源请求
// 多模态能力
import OpenAI from "openai";
import cors from '@koa/cors';//跨域
import aiChat from './chat.mjs';
import dotenv from 'dotenv';
二、前端
这里的实现场景是:用户添加list,点击add获取输入框的内容,添加至list中并将内容传给后端,后端获取到内容调用ai接口产生结果后返回给前端响应。 这里主要实现了点击添加后向端口发出post请求,并将输入框内容以json数组的格式在请求体中一起发出。后端响应后,接收到数据,存储在list的action属性中,如何渲染到页面中。
const Add = (text) => {
text = todoAdd.value.trim();
if (text === '') {
alert('输入不能为空');
} else {
state.todos.push({ id: state.todos.length + 1, text: todoAdd.value, done: false, action: '' });
let data = {
value: text
}
fetch('http://localhost:3000/api', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
}).then(res => res.json())//响应 json
.then(data => {
if (data.status == 500) {
console.log("500:", data);
}
if (data.status == 200) {
state.action = data.res;
console.log(data.res);
state.todos[state.todos.length - 1].action = data.res;
}
}
)
todoAdd.value = '';
}
}
三、后端
创建一个openai实例,通过这个让api给我们做一个apenai的请求转发,然后获取到前端传过来的内容交给chat。在chat.mjs中我使用了'gpt-3.5-turbo'
模型,以对话的形式(即prompt
)让ai返回我需要的内容。拿到openAI返回值后返回给前端。
main.mjs
import bodyParser from '@koa/bodyparser';
import Koa from 'koa'//后端简单框架
import Router from 'koa-router';//路由对象来监听端口资源请求
// 多模态能力
import OpenAI from "openai";
import cors from '@koa/cors';//跨域
import aiChat from './chat.mjs';
import dotenv from 'dotenv';
dotenv.config();
const client = new OpenAI({
apiKey: process.env.OPENAI_KEY,
//gptsapi会帮我们做apenai的请求转发
baseURL: 'https://api.302.ai/v1'
});
// web 服务就是一个app
const app = new Koa();
app.use(bodyParser());
// 路由实例
const router = new Router();
app.use(cors());
router.post('/api', async ctx => {
let { value } = ctx.request.body;
try {
const response = await aiChat(client, value);
ctx.response.body = {
status: 200,
res: response
};
} catch (err) {
ctx.response.body = {
status: 500,
error: err.message
}
}
})
router.get('/', ctx => {
ctx.body = '首页'
})
app.use(router.routes());
const PORT = 3000;
// http 协议的提供
app.listen(PORT, () => {
console.log(`server is running at http://localhost:${PORT}`);
});
aiChat.mjs
export default async (client, prompt) => {
const response = await client.chat.completions.create({
model: 'gpt-3.5-turbo', // 适合聊天的模型 很多种
messages: [
{
role: 'user',
content: `
你是一位擅长给人提供建议的专家,请根据用户接下来的${prompt}活动,给出3条的活动规划方案,每条规划要求简短, 总字数不超过100字,并且都以中文返回。
`
}
],
n: 1
})
return response.choices[0].message.content;
};
总结
这次的实战让我对于vue3中的很多指令有了更深层次的理解,非常深刻的意识到实战能力对于程序员的重要性。