[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"article-public-UgkPAWGn":3,"public-project-articles-UgkPAWGn":17},{"id":4,"uuid":5,"project_id":6,"title":7,"content":8,"type":9,"status":10,"public_enabled":10,"views":11,"sort":12,"created_at":13,"updated_at":14,"project_title":15,"project_slug":16},1182,"UgkPAWGn",58,"03. 开发图书分类信息管理功能","## SQL\n\n```sql\nCREATE TABLE `category` (\n  `id` int NOT NULL AUTO_INCREMENT COMMENT 'ID',\n  `name` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '名称',\n  PRIMARY KEY (`id`)\n) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;\n```\n\n```javascript\nINSERT INTO `book_system`.`category` (`id`, `name`) VALUES (1, '文学');\nINSERT INTO `book_system`.`category` (`id`, `name`) VALUES (2, '科幻\u002F奇幻');\nINSERT INTO `book_system`.`category` (`id`, `name`) VALUES (3, '历史');\nINSERT INTO `book_system`.`category` (`id`, `name`) VALUES (4, '科学技术');\nINSERT INTO `book_system`.`category` (`id`, `name`) VALUES (5, '经济管理');\nINSERT INTO `book_system`.`category` (`id`, `name`) VALUES (6, '哲学\u002F心理学');\nINSERT INTO `book_system`.`category` (`id`, `name`) VALUES (7, '艺术设计');\nINSERT INTO `book_system`.`category` (`id`, `name`) VALUES (8, '生活\u002F健康');\nINSERT INTO `book_system`.`category` (`id`, `name`) VALUES (9, '儿童读物');\nINSERT INTO `book_system`.`category` (`id`, `name`) VALUES (10, '教育\u002F考试');\n\n```\n\n## 后端接口\n\ncategory.py\n\n```python\nfrom typing import List, Optional\n\nfrom fastapi import APIRouter\nfrom pydantic import create_model\nfrom tortoise.contrib.pydantic import pydantic_model_creator\n\nfrom common.result import Result, PageInfo\nfrom models import Category\n\nrouter = APIRouter(prefix=\"\u002Fcategory\")\n# 创建 pydantic 只读模型\nCategoryPydantic = pydantic_model_creator(Category)\n# 自动生成所有字段为 Optional 的更新模型\nCategoryCreatePydantic = create_model(\n    \"CategoryPydantic\",\n    **{\n        name: (Optional[field.annotation], None)\n        for name, field in CategoryPydantic.model_fields.items()\n    }\n)\n\n\n# 新增\n@router.post(\"\u002Fadd\")\nasync def add(category_create_pydantic: CategoryCreatePydantic):\n    create_data = category_create_pydantic.model_dump(exclude_unset=True, exclude={'id'})\n    await Category.create(**create_data)\n    return Result.success()\n\n\n# 修改\n@router.put(\"\u002Fupdate\")\nasync def update(category_create_pydantic: CategoryPydantic):\n    update_data = category_create_pydantic.model_dump(exclude_unset=True, exclude={\"id\"})\n    await Category.filter(id=category_create_pydantic.id).update(**update_data)\n    return Result.success()\n\n\n# 删除\n@router.delete(\"\u002Fdelete\u002F{category_id}\")\nasync def delete(category_id: int):\n    await Category.filter(id=category_id).delete()\n    return Result.success()\n\n\n# 批量删除\n@router.delete(\"\u002FdeleteBatch\")\nasync def delete_batch(ids: List[int]):\n    await Category.filter(id__in=ids).delete()\n    return Result.success()\n\n\n# 单个查询\n@router.get(\"\u002FselectById\u002F{category_id}\")\nasync def select_one(category_id: int):\n    category = await Category.get_or_none(id=category_id)\n    return Result.success(category)\n\n\n# 查询所有\n@router.get(\"\u002FselectAll\")\nasync def select_all(name: str = \"\"):\n    category_list = await Category.filter(name__contains=name)\n    return Result.success(category_list)\n\n\n# 分页查询\n@router.get(\"\u002FselectPage\")\nasync def select_page(name: str = \"\", pageNum: int = 1, pageSize: int = 10):\n    # 同时获取分页数据和总数\n    query = Category.filter(name__contains=name)\n    # 获取分页数据\n    category_list = await query.order_by(\"-id\").offset((pageNum - 1) * pageSize).limit(pageSize)\n    category_list = [\n        CategoryPydantic.model_validate(category).model_dump()\n        for category in category_list\n    ]\n    # 计算总数\n    total = await query.count()\n    # 封装分页数据\n    pageinfo = PageInfo(total=total, list=category_list)\n    return Result.success(pageinfo)\n\n```\n\n\n\n## 前端页面\n\nCategory.vue\n\n```vue\n\u003Ctemplate>\n  \u003Cdiv>\n\n    \u003Cdiv class=\"card\" style=\"margin-bottom: 5px;\">\n      \u003Cel-input v-model=\"data.name\" style=\"width: 300px; margin-right: 10px\" placeholder=\"请输入名称查询\">\u003C\u002Fel-input>\n      \u003Cel-button type=\"primary\" @click=\"load\">查询\u003C\u002Fel-button>\n      \u003Cel-button type=\"info\" style=\"margin: 0 10px\" @click=\"reset\">重置\u003C\u002Fel-button>\n    \u003C\u002Fdiv>\n\n    \u003Cdiv class=\"card\" style=\"margin-bottom: 5px\">\n      \u003Cdiv style=\"margin-bottom: 10px\">\n        \u003Cel-button type=\"primary\" @click=\"handleAdd\">新增\u003C\u002Fel-button>\n      \u003C\u002Fdiv>\n      \u003Cel-table :data=\"data.tableData\" stripe>\n        \u003Cel-table-column label=\"名称\" prop=\"name\">\u003C\u002Fel-table-column>\n        \u003Cel-table-column label=\"操作\" align=\"center\" width=\"160\">\n          \u003Ctemplate #default=\"scope\">\n            \u003Cel-button type=\"primary\" @click=\"handleEdit(scope.row)\">编辑\u003C\u002Fel-button>\n            \u003Cel-button type=\"danger\" @click=\"handleDelete(scope.row.id)\">删除\u003C\u002Fel-button>\n          \u003C\u002Ftemplate>\n        \u003C\u002Fel-table-column>\n      \u003C\u002Fel-table>\n    \u003C\u002Fdiv>\n\n    \u003Cdiv class=\"card\">\n      \u003Cel-pagination @current-change=\"load\" background layout=\"total, prev, pager, next\" v-model:page-size=\"data.pageSize\" v-model:current-page=\"data.pageNum\" :total=\"data.total\"\u002F>\n    \u003C\u002Fdiv>\n\n    \u003Cel-dialog title=\"分类信息\" width=\"40%\" v-model=\"data.formVisible\" :close-on-click-modal=\"false\" destroy-on-close>\n      \u003Cel-form ref=\"formRef\" :model=\"data.form\" :rules=\"data.rules\" label-width=\"100px\" style=\"padding-right: 50px\">\n        \u003Cel-form-item label=\"名称\" prop=\"name\">\n          \u003Cel-input v-model=\"data.form.name\" autocomplete=\"off\" \u002F>\n        \u003C\u002Fel-form-item>\n      \u003C\u002Fel-form>\n      \u003Ctemplate #footer>\n      \u003Cspan class=\"dialog-footer\">\n        \u003Cel-button @click=\"data.formVisible = false\">取 消\u003C\u002Fel-button>\n        \u003Cel-button type=\"primary\" @click=\"save\">保 存\u003C\u002Fel-button>\n      \u003C\u002Fspan>\n      \u003C\u002Ftemplate>\n    \u003C\u002Fel-dialog>\n\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n\n\u003Cscript setup>\nimport request from \"@\u002Futils\u002Frequest\";\nimport {reactive, ref} from \"vue\";\nimport {ElMessageBox, ElMessage} from \"element-plus\";\n\n\nconst formRef = ref()\nconst data = reactive({\n  user: JSON.parse(localStorage.getItem('system-user') || '{}'),\n  pageNum: 1,\n  pageSize: 10,\n  total: 0,\n  formVisible: false,\n  form: {},\n  tableData: [],\n  name: null,\n  rules: {\n    name: [\n      { required: true, message: '请输入名称', trigger: 'blur' }\n    ],\n  }\n})\n\n\u002F\u002F 分页查询\nconst load = () => {\n  request.get('\u002Fcategory\u002FselectPage', {\n    params: {\n      pageNum: data.pageNum,\n      pageSize: data.pageSize,\n      name: data.name\n    }\n  }).then(res => {\n    data.tableData = res.data?.list\n    data.total = res.data?.total\n  })\n}\n\n\u002F\u002F 新增\nconst handleAdd = () => {\n  data.form = {}\n  data.formVisible = true\n}\n\n\u002F\u002F 编辑\nconst handleEdit = (row) => {\n  data.form = JSON.parse(JSON.stringify(row))\n  data.formVisible = true\n}\n\n\u002F\u002F 新增保存\nconst add = () => {\n  request.post('\u002Fcategory\u002Fadd', data.form).then(res => {\n    if (res.code === '200') {\n      load()\n      ElMessage.success('操作成功')\n      data.formVisible = false\n    } else {\n      ElMessage.error(res.msg)\n    }\n  })\n}\n\n\u002F\u002F 编辑保存\nconst update = () => {\n  request.put('\u002Fcategory\u002Fupdate', data.form).then(res => {\n    if (res.code === '200') {\n      load()\n      ElMessage.success('操作成功')\n      data.formVisible = false\n    } else {\n      ElMessage.error(res.msg)\n    }\n  })\n}\n\n\u002F\u002F 弹窗保存\nconst save = () => {\n  formRef.value.validate(valid => {\n    if (valid) {\n      \u002F\u002F data.form有id就是更新，没有就是新增\n      data.form.id ? update() : add()\n    }\n  })\n}\n\n\u002F\u002F 删除\nconst handleDelete = (id) => {\n  ElMessageBox.confirm('删除后数据无法恢复，您确定删除吗?', '删除确认', { type: 'warning' }).then(res => {\n    request.delete('\u002Fcategory\u002Fdelete\u002F' + id).then(res => {\n      if (res.code === '200') {\n        load()\n        ElMessage.success('操作成功')\n      } else {\n        ElMessage.error(res.msg)\n      }\n    })\n  }).catch(err => {})\n}\n\n\u002F\u002F 重置\nconst reset = () => {\n  data.name = null\n  load()\n}\n\nload()\n\u003C\u002Fscript>\n```\n\n","coding",1,128,2307,"2026-01-09 18:33:16","2026-05-03 22:49:02","基于FastAPI+Vue3+推荐算法的图书推荐系统","book-recommendation",{"project":18,"items":19},{"id":6,"title":15,"slug":16},[20,27,34,41,42,49],{"id":21,"uuid":22,"project_id":6,"title":23,"type":9,"status":10,"public_enabled":10,"views":24,"sort":25,"created_at":26,"updated_at":14,"project_title":15,"project_slug":16},1174,"NzsRyEPn","00. 课程相关资料获取",308,2297,"2026-04-19 17:28:24",{"id":28,"uuid":29,"project_id":6,"title":30,"type":9,"status":10,"public_enabled":10,"views":31,"sort":32,"created_at":33,"updated_at":14,"project_title":15,"project_slug":16},1180,"oRHLGeOy","01. 图书推荐系统介绍",182,2304,"2026-01-09 11:50:58",{"id":35,"uuid":36,"project_id":6,"title":37,"type":9,"status":10,"public_enabled":10,"views":38,"sort":39,"created_at":40,"updated_at":14,"project_title":15,"project_slug":16},1181,"NrrW311Q","02. 导入并运行项目脚手架",154,2306,"2026-01-09 18:33:00",{"id":4,"uuid":5,"project_id":6,"title":7,"type":9,"status":10,"public_enabled":10,"views":11,"sort":12,"created_at":13,"updated_at":14,"project_title":15,"project_slug":16},{"id":43,"uuid":44,"project_id":6,"title":45,"type":9,"status":10,"public_enabled":10,"views":46,"sort":47,"created_at":48,"updated_at":14,"project_title":15,"project_slug":16},1183,"8k9XX7Tv","04. 开发图书信息管理功能",126,2308,"2026-01-09 18:34:50",{"id":50,"uuid":51,"project_id":6,"title":52,"type":9,"status":10,"public_enabled":10,"views":53,"sort":54,"created_at":55,"updated_at":14,"project_title":15,"project_slug":16},1189,"h0SZsjkz","05. 使用AI帮助开发图书列表页面和详情展示页面",110,2318,"2026-01-12 17:27:25"]