[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"article-public-oEDhokSf":3,"public-project-articles-oEDhokSf":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},1218,"oEDhokSf",59,"02. 脚手架基本的开发流程","## 开发流程\n\n### 数据库\n\n```sql\nCREATE TABLE `book` (\n  `id` int NOT NULL AUTO_INCREMENT COMMENT 'ID',\n  `name` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '名称',\n  `img` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '图片',\n  `author` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '作者',\n  `price` double DEFAULT NULL COMMENT '价格',\n  `publisher` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '出版社',\n  `publishtime` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '出版时间',\n  PRIMARY KEY (`id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='图书信息';\n```\n\n## 后端接口\n\n### 开发 model\n\n```python\nclass Book(models.Model):\n    \"\"\"图书信息\"\"\"\n    id = models.AutoField(primary_key=True)\n    name = models.CharField(max_length=255, null=True, blank=True)\n    img = models.CharField(max_length=255, null=True, blank=True)\n    author = models.CharField(max_length=255, null=True, blank=True)\n    price = models.FloatField(max_length=10, null=True, blank=True)\n    publisher = models.FloatField(max_length=255, null=True, blank=True)\n    publishtime = models.FloatField(max_length=255, null=True, blank=True)\n\n    class Meta:\n        db_table = 'book'\n```\n\n### 开发 api，配置 api\n\n```python\nfrom typing import List\n\nfrom django.core.paginator import Paginator, EmptyPage\nfrom ninja import Router, ModelSchema\n\nfrom core.api.base import Result\nfrom core.models import Book\n\n# 创建的是路由对象\nrouter = Router()\n\n\n# 定义前端发送过来的数据结构\nclass DataIn(ModelSchema):\n    class Meta:\n        model = Book\n        fields = '__all__'  # 或者指定字段列表  或者['username', 'name' ]\n        fields_optional = '__all__'  # 把所有选中的字段都变成可选  或者['username', 'name']\n\n\n# --- API 接口 ---\n\n@router.post(\"\u002Fadd\", response=Result)\ndef add(request, data: DataIn):\n    \"\"\"新增数据\"\"\"\n    Book.objects.create(**data.dict(exclude={\"id\"}))\n    return Result.success()\n\n\n@router.put(\"\u002Fupdate\", response=Result)\ndef add(request, data: DataIn):\n    \"\"\"更新数据\"\"\"\n    update_dict = data.dict(exclude_unset=True)\n    Book.objects.filter(id=data.id).update(**update_dict)\n    return Result.success()\n\n\n@router.delete(\"\u002Fdelete\u002F{book_id}\", response=Result)\ndef delete(request, book_id: int):\n    \"\"\"删除管理员\"\"\"\n    Book.objects.filter(id=book_id).delete()\n    return Result.success()\n\n\n@router.delete(\"\u002FdeleteBatch\", response=Result)\ndef delete_batch(request, ids: List[int]):\n    \"\"\"批量删除管理员\"\"\"\n    Book.objects.filter(id__in=ids).delete()\n    return Result.success()\n\n\n@router.get(\"\u002FselectAll\", response=Result)\ndef select_all(request, name: str = \"\"):\n    \"\"\"查询所有数据\"\"\"\n    data = Book.objects.filter(name__contains=name)\n    return Result.success(data)\n\n\n@router.get(\"\u002FselectById\u002F{book_id}\", response=Result)\ndef select_by_id(request, book_id: int):\n    \"\"\"查询所有数据\"\"\"\n    data = Book.objects.filter(id=book_id).first()\n    return Result.success(data)\n\n\n@router.get(\"\u002FselectPage\", response=Result)\ndef select_page(request, pageNum: int = 1, pageSize: int = 10, name: str = \"\"):\n    \"\"\"查询分页数据\"\"\"\n    # 参数验证和修正\n    pageNum = max(1, pageNum)  # 确保页码至少为1\n    pageSize = max(1, min(pageSize, 100))  # 限制每页大小 1-100\n    # 构建查询集 (QuerySet)\n    query = Book.objects.all().order_by('-id')  # 按 ID 倒序\n    query = query.filter(name__icontains=name)\n    # 使用 Django 内置分页器\n    paginator = Paginator(query, pageSize)\n    # 获取当前页数据  防止页码越界，可以使用 get_page(pageNum)\n    data_list = []\n    try:\n        data_list = paginator.page(pageNum).object_list\n    except EmptyPage as e:\n        print('未查询到数据')\n    # 构造返回结构\n    res_data = {\n        \"list\": data_list,  # 列表数据\n        \"total\": paginator.count  # 总条数\n    }\n    return Result.success(res_data)\n\n```\n\n### 配置 api    __init__.py\n\n```python\nfrom .book_api import router as book_router\n\napi.add_router(\"\u002Fbook\", book_router, tags=[\"图书信息模块\"])\n```\n\n## 前端\n\n### 开发 vue3 页面  配置路由和菜单\n\n```python\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        \u003C!-- 图书名称列 -->\n        \u003Cel-table-column label=\"图书名称\" prop=\"name\" min-width=\"180\">\u003C\u002Fel-table-column>\n        \u003C!-- 图书图片列：简单展示图片，限制宽高 -->\n        \u003Cel-table-column label=\"图书图片\" align=\"center\" width=\"100\">\n          \u003Ctemplate #default=\"scope\">\n            \u003Cel-image\n                v-if=\"scope.row.img\"\n                :src=\"scope.row.img\"\n                fit=\"cover\"\n                style=\"width: 60px; height: 80px; border-radius: 4px;\"\n                preview-src-list=\"[scope.row.img]\"\n            >\u003C\u002Fel-image>\n            \u003Cspan v-else>无图片\u003C\u002Fspan>\n          \u003C\u002Ftemplate>\n        \u003C\u002Fel-table-column>\n        \u003C!-- 作者列 -->\n        \u003Cel-table-column label=\"作者\" prop=\"author\" align=\"center\" min-width=\"120\">\u003C\u002Fel-table-column>\n        \u003C!-- 价格列：格式化保留两位小数 -->\n        \u003Cel-table-column label=\"价格(元)\" prop=\"price\" align=\"center\" width=\"100\">\n          \u003Ctemplate #default=\"scope\">\n            {{ scope.row.price ? scope.row.price.toFixed(2) : '0.00' }}\n          \u003C\u002Ftemplate>\n        \u003C\u002Fel-table-column>\n        \u003C!-- 出版社列 -->\n        \u003Cel-table-column label=\"出版社\" prop=\"publisher\" align=\"center\" min-width=\"150\">\u003C\u002Fel-table-column>\n        \u003C!-- 出版时间列 -->\n        \u003Cel-table-column label=\"出版时间\" prop=\"publishtime\" align=\"center\" width=\"120\">\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\" placeholder=\"请输入名称\" \u002F>\n        \u003C\u002Fel-form-item>\n\n        \u003Cel-form-item label=\"图片\" prop=\"img\">\n          \u003Cel-upload :action=\"uploadUrl\" list-type=\"picture\" :on-success=\"handleImgSuccess\">\n            \u003Cel-button type=\"primary\">上传图片\u003C\u002Fel-button>\n          \u003C\u002Fel-upload>\n        \u003C\u002Fel-form-item>\n\n        \u003Cel-form-item label=\"作者\" prop=\"author\">\n          \u003Cel-input v-model=\"data.form.author\" autocomplete=\"off\" placeholder=\"请输入作者\" \u002F>\n        \u003C\u002Fel-form-item>\n\n        \u003Cel-form-item label=\"价格\" prop=\"price\">\n          \u003Cel-input-number style=\"width: 200px\" v-model=\"data.form.price\" autocomplete=\"off\" placeholder=\"请输入价格\" \u002F>\n        \u003C\u002Fel-form-item>\n\n        \u003Cel-form-item label=\"出版社\" prop=\"publisher\">\n          \u003Cel-input v-model=\"data.form.publisher\" autocomplete=\"off\" placeholder=\"请输入出版社\" \u002F>\n        \u003C\u002Fel-form-item>\n\n        \u003Cel-form-item label=\"出版时间\" prop=\"publishtime\">\n          \u003Cel-date-picker value-format=\"YYYY-MM-DD\" format=\"YYYY-MM-DD\" v-model=\"data.form.publishtime\" type=\"date\" placeholder=\"选择出版时间\" 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\u002F\u002F 文件上传的接口地址\nconst uploadUrl = import.meta.env.VITE_BASE_URL + '\u002Ffiles\u002Fupload'\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    username: [\n      { required: true, message: '请输入账号', trigger: 'blur' }\n    ],\n    name: [\n      { required: true, message: '请输入名称', trigger: 'blur' }\n    ],\n    avatar: [\n      { required: true, message: '请上传头像', trigger: 'blur' }\n    ],\n  }\n})\n\n\u002F\u002F 分页查询\nconst load = () => {\n  request.get('\u002Fbook\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('\u002Fbook\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('\u002Fbook\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('\u002Fbook\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\n\u002F\u002F 处理文件上传的钩子\nconst handleImgSuccess = (res) => {\n  data.form.img = res.data  \u002F\u002F res.data就是文件上传返回的文件路径，获取到路径后赋值表单的属性\n}\n\nload()\n\u003C\u002Fscript>\n```\n\n","coding",1,100,2375,"2026-02-03 12:18:47","2026-05-03 22:49:02","基于Django+Vue3的免费项目脚手架","free-project-scaffolding",{"project":18,"items":19},{"id":6,"title":15,"slug":16},[20,27],{"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},1217,"OFLZqyxP","01. Django+Vue3项目脚手架介绍",424,2372,"2026-02-03 12:19:19",{"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}]