AI书签管理工具开发全记录(七):页面编写与接口对接
文章目录
- AI书签管理工具开发全记录(七):页面编写与接口对接
- 前言 📝
- 1. 页面功能规划 📌
- 2. 接口api编写 📡
- 2.1 创建`.env`,设置环境变量
- 2.2 增加`axios`拦截器
- 2.3 创建接口
- 2. 页面编写 📄
- 2.1 示例代码
- 3. 跨域问题 🌐
- 3.1 配置cors中间件
- 4.页面效果 ✨
- 4.1 分类管理页面
- 4.2 书签管理页面
- 总结 📚
AI书签管理工具开发全记录(七):页面编写与接口对接
前言 📝
在上一篇博客中,我们完成了前端基础框架的搭建。现在,我们将实现书签和分类管理的具体页面,并与后端API进行对接,完成整个前后端交互流程。
1. 页面功能规划 📌
我们需要实现以下核心功能页面:
- 书签管理页面
- 书签列表展示
- 书签增删改查
- 分类管理页面
- 分类列表展示
- 分类增删改查
2. 接口api编写 📡
2.1 创建.env
,设置环境变量
VITE_API_BASE_URL=http://localhost:8080
2.2 增加axios
拦截器
import axios from 'axios'
import { ElMessage } from 'element-plus'// 创建 axios 实例
const service = axios.create({baseURL: import.meta.env.VITE_API_BASE_URL,timeout: 10000
})// 请求拦截器
service.interceptors.request.use((config) => {// 在这里可以添加请求前的处理逻辑return config},(error) => {return Promise.reject(error)}
)// 响应拦截器
service.interceptors.response.use((response) => {// 在这里可以添加响应后的处理逻辑const res = response.data// 如果返回的状态码不是200,说明接口请求有误if (response.status !== 200) {ElMessage.error(res.message || '请求失败')return Promise.reject(new Error(res.message || '请求失败'))}return res},(error) => {ElMessage.error(error.message || '请求失败')return Promise.reject(error)}
)export default service
2.3 创建接口
以分类为例,书签相似。
// src/api/category/index.js
import request from '/@/utils/request'// 创建分类
export function createCategory(data) {return request({url: '/api/categories',method: 'post',data})
}// 获取分类列表
export function listCategories(params) {return request({url: '/api/categories',method: 'get',params})
}// 获取单个分类
export function getCategory(id) {return request({url: `/api/categories/${id}`,method: 'get'})
}// 更新分类
export function updateCategory(id, data) {return request({url: `/api/categories/${id}`,method: 'put',data})
}// 删除分类
export function deleteCategory(id) {return request({url: `/api/categories/${id}`,method: 'delete'})
}
2. 页面编写 📄
2.1 示例代码
以分类为例
<!--/src/views/category/index.vue -->
<template><div class="category-container"><div class="header"><h2>分类管理</h2></div><div class="toolbar"><div class="search-row"><el-inputv-model="searchName"placeholder="请输入分类名称"class="search-input"clearable@clear="handleSearch"@keyup.enter="handleSearch"><template #append><el-button @click="handleSearch"><el-icon><Search /></el-icon></el-button></template></el-input></div><div class="button-row"><el-button type="primary" @click="handleAdd">新增分类</el-button></div></div><el-table :data="categoryList" style="width: 100%" v-loading="loading"><el-table-column prop="id" label="ID" width="80" /><el-table-column prop="name" label="分类名称" /><el-table-column prop="description" label="描述" /><el-table-column label="操作" width="200"><template #default="{ row }"><el-button type="primary" link @click="handleEdit(row)">编辑</el-button><el-button type="danger" link @click="handleDelete(row)">删除</el-button></template></el-table-column></el-table><div class="pagination"><el-paginationv-model:current-page="currentPage"v-model:page-size="pageSize":page-sizes="[10, 20, 50, 100]"layout="total, sizes, prev, pager, next":total="total"@size-change="handleSizeChange"@current-change="handleCurrentChange"/></div><!-- 新增/编辑对话框 --><el-dialogv-model="dialogVisible":title="dialogType === 'add' ? '新增分类' : '编辑分类'"width="500px"><el-formref="formRef":model="form":rules="rules"label-width="80px"><el-form-item label="分类名称" prop="name"><el-input v-model="form.name" placeholder="请输入分类名称" /></el-form-item><el-form-item label="描述" prop="description"><el-inputv-model="form.description"type="textarea"placeholder="请输入分类描述"/></el-form-item></el-form><template #footer><span class="dialog-footer"><el-button @click="dialogVisible = false">取消</el-button><el-button type="primary" @click="handleSubmit">确定</el-button></span></template></el-dialog></div>
</template><script setup>
import { ref, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Search } from '@element-plus/icons-vue'
import { listCategories, createCategory, updateCategory, deleteCategory } from '/@/api/category'// 数据列表
const categoryList = ref([])
const loading = ref(false)
const currentPage = ref(1)
const pageSize = ref(10)
const total = ref(0)
const searchName = ref('')// 对话框相关
const dialogVisible = ref(false)
const dialogType = ref('add')
const formRef = ref(null)
const form = ref({name: '',description: ''
})// 表单验证规则
const rules = {name: [{ required: true, message: '请输入分类名称', trigger: ['blur', 'change'] },{ min: 2, max: 20, message: '长度在 2 到 20 个字符', trigger: ['blur', 'change'] }],description: [{ max: 200, message: '长度不能超过 200 个字符', trigger: ['blur', 'change'] }]
}// 获取分类列表
const getList = async () => {loading.value = truetry {const res = await listCategories({page: currentPage.value,size: pageSize.value,name: searchName.value})categoryList.value = res.datatotal.value = res.total} catch (error) {ElMessage.error('获取分类列表失败')} finally {loading.value = false}
}// 处理搜索
const handleSearch = () => {currentPage.value = 1getList()
}// 处理新增
const handleAdd = () => {dialogType.value = 'add'form.value = {name: '',description: ''}dialogVisible.value = true
}// 处理编辑
const handleEdit = (row) => {dialogType.value = 'edit'form.value = {id: row.id,name: row.name,description: row.description}dialogVisible.value = true
}// 处理删除
const handleDelete = (row) => {ElMessageBox.confirm('确认删除该分类吗?', '提示', {confirmButtonText: '确定',cancelButtonText: '取消',type: 'warning'}).then(async () => {try {await deleteCategory(row.id)ElMessage.success('删除成功')getList()} catch (error) {ElMessage.error('删除失败')}})
}// 处理提交
const handleSubmit = async () => {if (!formRef.value) returnawait formRef.value.validate(async (valid) => {if (valid) {try {if (dialogType.value === 'add') {await createCategory(form.value)ElMessage.success('新增成功')} else {await updateCategory(form.value.id, form.value)ElMessage.success('更新成功')}dialogVisible.value = falsegetList()} catch (error) {ElMessage.error(dialogType.value === 'add' ? '新增失败' : '更新失败')}}})
}// 处理分页
const handleSizeChange = (val) => {pageSize.value = valgetList()
}const handleCurrentChange = (val) => {currentPage.value = valgetList()
}// 初始化
onMounted(() => {getList()
})
</script><style scoped>
.category-container {padding: 20px;
}.header {margin-bottom: 16px;
}.header h2 {margin: 0;font-size: 20px;font-weight: 500;
}.toolbar {margin-bottom: 16px;
}.search-row {margin-bottom: 12px;
}.search-input {width: 240px;
}.pagination {margin-top: 20px;display: flex;justify-content: flex-end;
}
</style>
3. 跨域问题 🌐
3.1 配置cors中间件
// internal/api/api.go:NewServer// 配置CORS
router.Use(func(c *gin.Context) {c.Writer.Header().Set("Access-Control-Allow-Origin", "*")c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With")c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT, DELETE")if c.Request.Method == "OPTIONS" {c.AbortWithStatus(204)return}c.Next()
})
4.页面效果 ✨
4.1 分类管理页面
4.2 书签管理页面
总结 📚
本文详细介绍了AI书签管理工具的前端页面实现和接口对接过程,主要完成了:
- 书签管理页面:实现列表展示、搜索、分页、增删改查
- 分类管理页面:实现分类的CRUD操作
- 接口对接:封装API请求,处理跨域问题
- 表单验证:实现表单验证逻辑
在下一篇文章中,我们将实现Ai创建书签功能。