nuxt jq在ie不加载_全栈“食”代:Django + Nuxt 实现美食分享网站(下)

3975c7c86d954d40f0965a57257574fb.png

在上篇[1]中,我们分别用 Django 和 Nuxt 实现了后端和前端的雏形。在这一部分,我们将实现前后端之间的通信,使得前端可以从后端获取数据,并且将进一步丰富网站的功能。

本文所涉及的源代码都放在了 Github[2] 上,如果您觉得我们写得还不错,希望您能给❤️这篇文章点个在看+Github仓库加星❤️哦~ 本文代码改编自 Scotch[3]

从服务器获取数据

在这一部分,我们将真正实现一个全栈应用——让前端能够向后端发起请求,从而获取想要的数据。

配置 Django 的静态文件服务

首先我们要配置一下 Django 服务器,使前端能够访问其静态文件。调整 api/api/urls.py 文件如下:

# ...from django.conf import settingsfrom django.conf.urls.static import staticurlpatterns = [    path('admin/', admin.site.urls),    path('api/', include('core.urls')),] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

注意

这样配置静态文件路由的方式仅应当在开发环境下使用。在生产环境下(settings.py 中的 DEBUG 设为 False 时),静态文件路由将自动失效(因为 Django 并不适合作为静态文件服务器,应该选用类似 Nginx 之类的服务器,在后续教程中我们将更深入地讨论)。

实现前端的数据请求功能

在客户端,我们先要对 Nuxt 进行全局配置。Nuxt 包括 axios[4] 包,这是一个非常出色的基于 Promise 的 HTTP 请求库。在 nuxt.config.js 中的 axios 一项中添加 Django 服务器的 URL:

export default {  // ...  /*  ** Axios module configuration  ** See https://axios.nuxtjs.org/options  */  axios: {    baseURL: 'http://localhost:8000/api',  },  // ...}

将食谱列表页面中暂时填充的假数据删去,通过 asyncData 方法获取数据。由于我们之前配置好了 axios,所以 asyncData 函数可以获取到 $axios 对象用于发起 HTTP 请求。我们实现页面加载的数据获取以及 deleteRecipe 事件,代码如下:

<template>  <main class="container mt-5">    <div class="row">      <div class="col-12 text-right mb-4">        <div class="d-flex justify-content-between">          <h3>吃货天堂h3>          <nuxt-link to="/recipes/add" class="btn btn-info">添加食谱nuxt-link>        div>      div>      <template v-for="recipe in recipes">        <div :key="recipe.id" class="col-lg-3 col-md-4 col-sm-6 mb-4">          <recipe-card :onDelete="deleteRecipe" :recipe="recipe">recipe-card>        div>      template>    div>  main>template><script>import RecipeCard from "~/components/RecipeCard.vue";export default {  head() {    return {      title: "食谱列表"    };  },  components: {    RecipeCard  },  async asyncData({ $axios, params }) {    try {      let recipes = await $axios.$get(`/recipes/`);      return { recipes };    } catch (e) {      return { recipes: [] };    }  },  data() {    return {      recipes: []    };  },  methods: {    async deleteRecipe(recipe_id) {      try {        if (confirm('确认要删除吗?')) {          await this.$axios.$delete(`/recipes/${recipe_id}/`);          let newRecipes = await this.$axios.$get("/recipes/");          this.recipes = newRecipes;        }      } catch (e) {        console.log(e);      }    }  }};script><style scoped>style>

实现食谱详情页面

我们进一步实现食谱详情页面。在 pages/recipes 目录中创建 _id 目录,在其中添加 index.vue 文件,代码如下:

<template>  <main class="container my-5">    <div class="row">      <div class="col-12 text-center my-3">        <h2 class="mb-3 display-4 text-uppercase">{{ recipe.name }}h2>      div>      <div class="col-md-6 mb-4">        <img          class="img-fluid"          style="width: 400px; border-radius: 10px; box-shadow: 0 1rem 1rem rgba(0,0,0,.7);"          :src="recipe.picture"          alt        >      div>      <div class="col-md-6">        <div class="recipe-details">          <h4>食材h4>          <p>{{ recipe.ingredients }}p>          <h4>准备时间 ⏱h4>          <p>{{ recipe.prep_time }} minsp>          <h4>制作难度h4>          <p>{{ recipe.difficulty }}p>          <h4>制作指南h4>          <textarea class="form-control" rows="10" v-html="recipe.prep_guide" disabled/>        div>      div>    div>  main>template><script>export default {  head() {    return {      title: "食谱详情"    };  },  async asyncData({ $axios, params }) {    try {      let recipe = await $axios.$get(`/recipes/${params.id}`);      return { recipe };    } catch (e) {      return { recipe: [] };    }  },  data() {    return {      recipe: {        name: "",        picture: "",        ingredients: "",        difficulty: "",        prep_time: null,        prep_guide: ""      }    };  }};script><style scoped>style>

为了测试前端页面能否真正从后端获取数据,我们先要在后端数据库中添加一些数据,而这对 Django 来说就非常方便了。进入 api 目录,运行 python manage.py runserver 打开服务器,然后进入后台管理页面(http://localhost:8000/admin[5]),添加一些数据:

b877caabbf09dbbc0ddcfa7b07a398e8.png

再运行前端页面,可以看到我们刚刚在 Django 后台管理中添加的项目:

f149e210678e552e91b5ffd958e07683.png

实现食谱的编辑和创建页面

有了前面的铺垫,实现食谱的添加和删除也基本上是按部就班了。我们在 pages/recipes/_id 中实现 edit.vue(食谱编辑页面),代码如下:

<template>  <main class="container my-5">    <div class="row">      <div class="col-12 text-center my-3">        <h2 class="mb-3 display-4 text-uppercase">{{ recipe.name }}h2>      div>      <div class="col-md-6 mb-4">        <img v-if="!preview" class="img-fluid" style="width: 400px; border-radius: 10px; box-shadow: 0 1rem 1rem rgba(0,0,0,.7);"  :src="recipe.picture">        <img v-else class="img-fluid" style="width: 400px; border-radius: 10px; box-shadow: 0 1rem 1rem rgba(0,0,0,.7);"  :src="preview">      div>      <div class="col-md-4">        <form @submit.prevent="submitRecipe">          <div class="form-group">            <label for>Recipe Namelabel>            <input type="text" class="form-control" v-model="recipe.name" >          div>          <div class="form-group">            <label for>Ingredientslabel>            <input type="text" v-model="recipe.ingredients" class="form-control" name="Ingredients" >          div>          <div class="form-group">            <label for>Food picturelabel>            <input type="file" @change="onFileChange">          div>          <div class="row">            <div class="col-md-6">              <div class="form-group">                <label for>Difficultylabel>                <select v-model="recipe.difficulty" class="form-control" >                  <option value="Easy">Easyoption>                  <option value="Medium">Mediumoption>                  <option value="Hard">Hardoption>                select>              div>            div>            <div class="col-md-6">              <div class="form-group">                <label for>                  Prep time                  <small>(minutes)small>                label>                <input type="text" v-model="recipe.prep_time" class="form-control" name="Ingredients" >              div>            div>          div>          <div class="form-group mb-3">            <label for>Preparation guidelabel>            <textarea v-model="recipe.prep_guide" class="form-control" rows="8">textarea>          div>          <button type="submit" class="btn btn-success">Savebutton>        form>      div>    div>  main>template><script>export default {  head(){      return {        title: "编辑食谱"      }    },  async asyncData({ $axios, params }) {    try {      let recipe = await $axios.$get(`/recipes/${params.id}`);      return { recipe };    } catch (e) {      return { recipe: [] };    }  },  data() {    return {      recipe: {        name: "",        picture: "",        ingredients: "",        difficulty: "",        prep_time: null,        prep_guide: ""      },      preview: ""    };  },  methods: {    onFileChange(e) {      let files = e.target.files || e.dataTransfer.files;      if (!files.length) {        return;      }      this.recipe.picture = files[0]      this.createImage(files[0]);    },    createImage(file) {      let reader = new FileReader();      let vm = this;      reader.onload = e => {        vm.preview = e.target.result;      };      reader.readAsDataURL(file);    },    async submitRecipe() {      let editedRecipe = this.recipe      if (editedRecipe.picture.indexOf("http://") != -1){        delete editedRecipe["picture"]      }      const config = {        headers: { "content-type": "multipart/form-data" }      };      let formData = new FormData();      for (let data in editedRecipe) {        formData.append(data, editedRecipe[data]);      }      try {        let response = await this.$axios.$patch(`/recipes/${editedRecipe.id}/`, formData, config);        this.$router.push("/recipes/");      } catch (e) {        console.log(e);      }    }  }};script><style>style>

实现之后的页面如下:

d4a1560d1d8ab71c8b97d416e71b70a2.png

继续在 pages/recipes/_id 中实现 add.vue (创建食谱页面)如下:

<template>  <main class="container my-5">    <div class="row">      <div class="col-12 text-center my-3">        <h2 class="mb-3 display-4 text-uppercase">{{ recipe.name }}h2>      div>      <div class="col-md-6 mb-4">        <img          v-if="preview"          class="img-fluid"          style="width: 400px; border-radius: 10px; box-shadow: 0 1rem 1rem rgba(0,0,0,.7);"          :src="preview"          alt        >        <img          v-else          class="img-fluid"          style="width: 400px; border-radius: 10px; box-shadow: 0 1rem 1rem rgba(0,0,0,.7);"          src="@/static/images/placeholder.png"        >      div>      <div class="col-md-4">        <form @submit.prevent="submitRecipe">          <div class="form-group">            <label for>食谱名称label>            <input type="text" class="form-control" v-model="recipe.name">          div>          <div class="form-group">            <label for>食材label>            <input v-model="recipe.ingredients" type="text" class="form-control">          div>          <div class="form-group">            <label for>图片label>            <input type="file" name="file" @change="onFileChange">          div>          <div class="row">            <div class="col-md-6">              <div class="form-group">                <label for>难度label>                <select v-model="recipe.difficulty" class="form-control">                  <option value="Easy">容易option>                  <option value="Medium">中等option>                  <option value="Hard">困难option>                select>              div>            div>            <div class="col-md-6">              <div class="form-group">                <label for>                  制作时间                  <small>(分钟)small>                label>                <input v-model="recipe.prep_time" type="number" class="form-control">              div>            div>          div>          <div class="form-group mb-3">            <label for>制作指南label>            <textarea v-model="recipe.prep_guide" class="form-control" rows="8">textarea>          div>          <button type="submit" class="btn btn-primary">提交button>        form>      div>    div>  main>template><script>export default {  head() {    return {      title: "Add Recipe"    };  },  data() {    return {      recipe: {        name: "",        picture: "",        ingredients: "",        difficulty: "",        prep_time: null,        prep_guide: ""      },      preview: ""    };  },  methods: {    onFileChange(e) {      let files = e.target.files || e.dataTransfer.files;      if (!files.length) {        return;      }      this.recipe.picture = files[0];      this.createImage(files[0]);    },    createImage(file) {      let reader = new FileReader();      let vm = this;      reader.onload = e => {        vm.preview = e.target.result;      };      reader.readAsDataURL(file);    },    async submitRecipe() {      const config = {        headers: { "content-type": "multipart/form-data" }      };      let formData = new FormData();      for (let data in this.recipe) {        formData.append(data, this.recipe[data]);      }      try {        let response = await this.$axios.$post("/recipes/", formData, config);        this.$router.push("/recipes/");      } catch (e) {        console.log(e);      }    }  }};script><style scoped>style>

实现的页面如下:

51a932252181f666447bd6bf9a0cd00b.png

一点强迫症:全局页面跳转效果

在这一节中,我们将演示如何在 Nuxt 中添加全局样式文件,来实现前端页面之间的跳转效果。

首先在 assets 目录中创建 css 目录,并在其中添加 transition.css 文件,代码如下:

.page-enter-active,.page-leave-active {  transition: opacity .3s ease;}.page-enter,.page-leave-to {  opacity: 0;}

在 Nuxt 配置文件中将刚才写的 transition.css 中添加到全局 CSS 中:

export default {  // ...  /*  ** Global CSS  */  css: [    '~/assets/css/transition.css',  ],    // ...}

欧耶,一个具有完整增删改查功能、实现了前后端分离的美食分享网站就完成了!

想要学习更多精彩的实战技术教程?来图雀社区[6]逛逛吧。

本文所涉及的源代码都放在了 Github[7] 上,如果您觉得我们写得还不错,希望您能给❤️这篇文章点个在看+Github仓库加星❤️哦~ 本文代码改编自 Scotch[8]

参考资料

[1]

上篇: https://juejin.im/post/5e435dfc6fb9a07cc3213686

[2]

Github: https://github.com/tuture-dev/recipes_app

[3]

Scotch: https://scotch.io/tutorials/building-a-universal-application-with-nuxtjs-and-django

[4]

axios: https://github.com/axios/axios

[5]

http://localhost:8000/admin: http://localhost:8000/admin

[6]

图雀社区: https://tuture.co/

[7]

Github: https://github.com/tuture-dev/recipes_app

[8]

Scotch: https://scotch.io/tutorials/building-a-universal-application-with-nuxtjs-and-django

● 一杯茶的时间,上手Django框架开发

● 全栈“食”代:用Django+Nuxt实现美食分享网站(一)

● 用Vue+ElementUI搭建后台管理极简模板

·END·

图雀社区

汇聚精彩的免费实战教程

4db9f9cfc94f2bab48ad0e1eeb101163.png

关注公众号回复 z 拉学习交流群

喜欢本文,点个“在看”告诉我

c93e500246684ac05a1fab0a8227a766.png
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Nuxt.js 中,`asyncData` 方法是用于在页面组件渲染前获取数据的一种特殊方法。它可以在服务端渲染和客户端渲染时都被调用,以保证页面在渲染前已经获取到了必要的数据。 如果你在使用 Nuxt.js 时发现 `asyncData` 方法在刷新页面时没有被执行,可能是因为你的页面组件并没有被重新渲染。这种情况下,你可以尝试以下方法: 1. 确认页面组件是否被重新渲染 在 Nuxt.js 中,页面组件的数据获取是在服务端渲染时进行的,而在客户端渲染时则会直接使用已经获取到的数据。因此,如果你在刷新页面时发现 `asyncData` 方法没有被执行,可能是因为页面组件并没有被重新渲染。你可以通过查看控制台中的输出信息,确认页面组件是否被重新渲染。 2. 使用 `fetch` 方法替 `asyncData` 在 Nuxt.js 2.12 版本之后,`fetch` 方法被引入作为新的数据获取方法。与 `asyncData` 方法不同的是,`fetch` 方法可以在客户端渲染时被重新执行,以确保页面组件获取到最新的数据。因此,你可以尝试使用 `fetch` 方法替 `asyncData` 方法,以解决刷新页面时数据获取不到的问题。 3. 使用 `beforeRouteUpdate` 方法 如果你在使用 Nuxt.js 2.12 之前的版本,或者不希望使用 `fetch` 方法,你可以尝试使用 `beforeRouteUpdate` 方法。这个方法会在路由参数发生变化时被调用,可以用来重新获取数据并更新页面组件。 总之,如果你在使用 Nuxt.js 时遇到了 `asyncData` 刷新不执行的问题,可以通过以上方法尝试解决。同时,也可以在 Nuxt.js 的官方文档中查找更多的解决方案和使用经验。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值