Day21 - [丰收款] 以Django Web框架实作永丰API线上支付模拟情境(2) - 购物车与付款方式确认页

我们今天简单带一点Django Template继承的概念,也就是当你有每一页面具备无论在哪一样都有相同的内容片段时,可采用这个方式把相同的部份拆出来,而具体不同的功能页面只要先继承後,再实作有差异的程序段,和程序码把共用的地方设计成父类别,子类别先继承後再进行override是一样的概念。

建立一个base.html Template

先在Django App根目录下,建立一个templates的目录,然後建立一个base.html

记得在settings.py中,TEMPLATES底下的DIRS,改成如下:

    'DIRS': [os.path.join(BASE_DIR, 'templates')],

注:前面记得import os

{% load bootstrap5 %}
<!DOCTYPE html>
<html>

  <head>
    <meta charset="UTF-8">
    <title>{% block title %}{% endblock %}</title>
    <meta charset="utf-8">
    <meta name="viewport"
      content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <meta name="description" content="">
    <meta name="author" content="">
    <link rel="icon" href="favicon.ico">
    {% bootstrap_css %}
  </head>
  <body>
    <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
      <div class="container-fluid">
        <a class="navbar-brand" href="#">库米狗屋 ● KummyShop</a>
        <button class="navbar-toggler" type="button" data-toggle="collapse"
          data-target="#navbarsExample02" aria-controls="navbarsExample02"
          aria-expanded="false" aria-label="Toggle navigation">
          <span class="navbar-toggler-icon"></span>
        </button>
        <div class="collapse navbar-collapse" id="navbarsExample02">
          <ul class="navbar-nav me-auto">
            <li class="nav-item active">
              <a class="nav-link" href="{% url 'order_create_entrance' %}"><span
                  class="sr-only">回狗屋</span></a>
            </li>
            <li class="nav-item">
              <a class="nav-link" href="{% url 'my_orders' %}">我的订单</a>
            </li>
          </ul>
        </div>
      </div>
    </nav>
    <section class="pt-5 pb-5">
      <div class="jumbotron ">
        <div class="container">
          {% block body %}{% endblock %}
        </div>
      </div>
    </section>
  {% bootstrap_javascript %}
  <script src="https://unpkg.com/vue@next"></script>
  {% block script %}{% endblock %}
  </body>
</html>
程序说明

把你到时候子页面想换掉的地方都透{% block blockname %}{% endblock %}来挖洞设置,以上面为例,我先设定以下的block区:

  • title
  • body
  • script

另外我引入了Vue.js的CDN进去,让前端页面可支援Vue的使用,当然你也可以使用其他的前端框架。

开设一个建立订单入口页

有了刚刚的模版Base後,我们就可以很轻松的开设继承的子页面,然後只要把上面三个挖洞的区域重新填入我们要的html区段即可。

[View]在order app下增加一个新的def order_create_entrance

def order_create_entrance(request):
    context = {
        "title": "下好离手,准备付款",
        "desc": "以下是我们帮你随机挑选好的订单,如果有不喜欢的商品请顺心接受,偶尔这样也不是坏事。接着请选择你的付款方式罗!"
               }
    return render(request, 'order/order_create_entrance.html', context)

如同昨天的greetings test示范,我们新增两个context变数titledesc,将他传递至template中。

接下来我们就要看重头戏了

[Template] 看一下继承Template与Override的Block

{% extends "base.html" %}

{% block title %}{{ title }}{% endblock %}

{% block body %}
<div id="app" class="col-xs-12 col-md-10 align-self-center">
<h1 class="display-4 text-center  mb-3 mt-5">[[ title ]]</h1>
<p class="lead  text-center">[[ desc ]]</p>
<div class="justify-content-center d-flex mt-3 mb-1">
</div>
</div>
{% endblock %}

{% block script %}
<script>
  const EventHandling = {
    data() {
      return {
        title: "{{ title }}",
        desc: "{{ desc }}"
      }
    },
    methods: {
    },
    delimiters: ['[[', ']]'],
  }
Vue.createApp(EventHandling).mount('#app')
</script>
{% endblock %}
程序说明

最前面先使用{% extends "base.html" %}告知我们的这一页,是继承自另一个作为共同页面Template而来。再来,就将先前定义的三个block都先长起来,然後把中间的部份填入我们所需要的内容,到时候生成最终版页面的时候,就会连同base.html的内容一起整合运算後,才render给client端。

接着要解释前,先Run起来看一下结果。
https://ithelp.ithome.com.tw/upload/images/20211006/201303545ln1mooM6L.png

咦,看起来好奇怪呀?

上面的程序码如果有先看过昨天的文章的话,会觉得我们是不是写错了,怎麽里面有些Django的变数绑定不是使用{{ var }}却变成[[ var ]]呢!?

Vue,我加了Vue。

原来是我们把这个前端页面也套用了Vue了的关系。

由於Vue和Django Template都使用{{ var }}的方式来bind,所以会有所冲突,因此在下面的Vue宣当时重新定义了Vue的delimiters为[[]]

你可以注意到,我恰好是将Django context的2个变数,和Vue的data绑定的2个变数都取一样的名称。如果你尝试的将上面的页面的[[]]改成{{}},再跑一次页面会发现页面都一样呀!哪有你说的冲突呢!?

如果你看一下原始码就会发现他们的不同之处,为了解释给各位理解,我就只换一个。
把Body区段的[[ title ]]换成{{ title }},而[[ desc ]]保留原样。

<div id="app" class="col-xs-12 col-md-10 align-self-center">
<h1 class="display-4 text-center  mb-3 mt-5">下好离手,准备付款</h1>
<p class="lead  text-center">[[ desc ]]</p>
<div class="justify-content-center d-flex mt-3 mb-1">
</div>

<!-- 省略掉其他部份 -->

<script>
  const EventHandling = {
    data() {
      return {
        title: "下好离手,准备付款",
        desc: "以下是我们帮你随机挑选好的订单,如果有不喜欢的商品请顺心接受,偶尔这样也不是坏事。接着请选择你的付款方式罗!"
      }
    },
    methods: {
    },
    delimiters: ['[[', ']]'],
  }
Vue.createApp(EventHandling).mount('#app')
</script>

上面的意思就是说,只有[[ desc ]]真正被Vue的data binding起了作用,是在前端网页被执行时才运算完成。但{{ title }}的部份,是被Django Template的context变数绑定在後端产生前就被替换了,因此HTML传递到前端Browser时看到就已经是静态结果了。

没听懂也没关系,总之,如果你有混用Django Template以及使用Vue的朋友,要记得作delimiters的重新定义。然後习惯另一个新的重定义语法,一样可以让两套机制顺利运行。

完整网页内容

{% extends "base.html" %}

{% block title %}{{ title }}{% endblock %}

{% block body %}
<div id="app" class="row g-3 align-self-center">
<h1 class="display-4 text-center  mb-3 mt-5">{{ title }}</h1>
<p class="lead  text-center">{{ desc }}</p>
<hr/>
<div class="">
    <form action="{% url 'order_create_next' %}"  method="POST">
        <span>以下是这次订单的商品,最後一次确认喔!</span>
        <div class="form-check " v-for="(item, index) in cart_items">
          <input class="form-check-input" type="checkbox" :value="item.price" :id="item.id" v-model="checked_items">
          <label class="form-check-label" :for="item.id">
            [[ item.item_name ]] / NT$ [[ item.price ]]
          </label>
        </div>

        <div>Total NT$ <span class="fs-3 text-primary">[[ sum_amount() ]]</span></div>
        <input type="hidden" name="amount" v-model="amount"></hidden>
        <hr/>
        <br />
        <div>选择付款方式:</div>
        <div class="form-check">
          <input class="form-check-input" type="radio" name="payment" id="payment_atm" value="atm">
          <label class="form-check-label" for="payment_atm">
            使用永丰ATM虚拟帐号转帐
          </label>
        </div>
        <div class="form-check">
          <input class="form-check-input" type="radio" name="payment" id="payment_card" value="card">
          <label class="form-check-label" for="payment_card">
            使用永丰线上信用卡刷卡支付
          </label>
        </div>
        <br />
        <button type="submit" class="btn btn-primary">付款去!</button>
    </form>
</div>

</div>
{% endblock %}

{% block script %}
<script>

  const EventHandling = {
    data() {
      return {
        cart_items: [
            {"id": 1, "item_name": "抹茶狗骨头", "price": 750},
            {"id": 2, "item_name": "腊肠狗专用晚宴西装", "price": 4900},
            {"id": 3, "item_name": "潮流嘻哈喝水碗", "price": 1200},
            {"id": 4, "item_name": "小型屁孩犬鸡肉饲料(10KG装)", "price": 3500},
            {"id": 5, "item_name": "冬凉夏热毛孩专用万用毯", "price": 1799}
          ],
        checked_items: [],
        amount: 0
      }
    },
    methods: {
        sum_amount: function() {
           this.amount = this.checked_items.reduce(function(a, b){
                return a + b;
           }, 0);
           return this.amount;
        }
    },
    delimiters: ['[[', ']]']
  }
Vue.createApp(EventHandling).mount('#app')
</script>
{% endblock %}
程序说明

我们简化购物车流程,直接使用Vue里面先把5个库米狗屋严选好物直接帮顾客列好好了,最後只需要勾选确认想要的产品就可以进入最终流程了!是不是快速又方便呢。

前面为了示范Vue与Django Template的混用,但实际title与desc两个部份不太需要使用到Vue来绑定,因此把那段改成纯使用Django Template的变数取代,甚至直接在这页输入明确文字其实也不是不行。但如果未来是有需要使用i18n多国语系的话,还是会将UI上的文字部份以变数方式取代,只是其内容不是用context的方式传递,可参考这篇文章进行i18n设定。

在这个页面里,Form里面会使用POST方式,准备了2个主要的变数要往action页面(order_create_next)传递,这两个数值的内容,一个是由checkbox勾选商品时会计算累积金额,放到hidden input amount栏位中。

这里使用了Javascript Array的一个特殊的方法reduce(),可放入累加函数,用这个方式很精简的可快速计算所勾选的商品的总金额。有兴趣了解进阶用法可参考这篇文章。

另一个就是我们的radio buttons选择付款方式,名称为payment

完成付款前确认UI画面如下:
https://ithelp.ithome.com.tw/upload/images/20211006/20130354TRVF45cSk5.png

所以我们还需要准备一个新的View def order_create_next来接收这一个POST後的页面,待接收完後我们就可以继续呼叫後面的API流程了!

请明天继续看下去罗。


<<:  Day23:【技术篇】CSS 的变数运用技巧(2)

>>:  [day24] 产生订单

Day09_插班车~风险评估的概念应用在日常工作上~XD"

今天这个,真的是插班车,因为今天作完弱扫,总共八份的测报。 我自已看得都要吐了。 在思考,这个月,因...

Day01 前言

前言 第二次参加铁人赛,在决定参赛时,就又让人想起连续30天不间断发文的痛苦,但是要用什麽主题来做为...

Day7 AR的一些好处(我认为)&免费展览推荐

本节来说明AR中虚拟讯息的种类和它的好处,前面有稍微提到,AR是将虚拟讯息/物件叠加在真实世界中,而...

【Day 27】JavaScript 回呼函式(callback function)

回呼函式,或简称回调、回呼(Callback 即call then back 被主函式呼叫运算後会返...

许多有趣的变数!如何使用 Postman 尝试 Nutanix 的 API

那些具有使用API经验的人将听说过并熟悉无处不在的API测试和开发工具。但是,Nutanix De...