与django一起使用Vue

use*_*827 18 html javascript django vue.js

我最近开始使用Django在一些社交媒体网站上.我使用默认的django模板引擎来填充我的页面.但此时此刻,我想添加javascript以使网站更具活力.这意味着:

  • 页眉和页脚在每页上都相同.标题应该有一个下拉菜单,一个在您输入时搜索的搜索表单.
  • 我当前的django应用程序有一个基本模板,它具有页眉和页脚HTML,因为每个页面都应该有这个.
  • 该网站由多个页面组成,想到索引页面,个人资料页面,注册页面.这些页面中的每一个都有一些共同但也有很多不同的动态组件.例如,注册页面应该有动态的表单验证,但是配置文件页面不需要这样.配置文件页面应具有无限滚动的状态订阅源.

我想用Vue来处理动态组件,但我不知道应该如何开始.申请不应该是SPA.

  • 我应该如何构建 Vue代码?
  • 我应该如何捆绑这个.用Gulp?或者也许django-webpack-loader
  • 我仍然可以使用Django模板标签,例如我希望能够{% url 'index' %}在下拉菜单中使用.

Man*_*ani 20

这看起来像一个基于意见的问题,没有明确的答案.

您提到您希望该应用程序成为单页应用程序(SPA).如果是这样,使用Vue的动机是什么?处理页面内的用户交互?

Vue可以在非SPA环境中完美地工作.它将帮助您处理页面中的丰富交互,例如将数据绑定到下拉菜单,表单等.但是当您在SPA上下文中使用Vue时,Vue的真正力量就会出现.

对于您的情况,我建议在独立模式下使用Vue.js ,您可以template在Vue组件中快速定义并在一个javascript文件中轻松编写所有代码.

您需要的是:https://vuejs.org/guide/installation.html#Standalone

在"Vue.js独立模式"中,不需要任何webpack构建系统或vue-cli.您可以直接在django的现有开发环境中构建应用程序.gulp可以选择正常缩小和捆绑您的javascript文件,就像使用基于jQuery的应用程序一样.

Vue.js使用双花括号{{..}}作为模板,因此它不会干扰你的django模板字符串.

Vue.js的所有jsFiddle示例都以独立模式运行.这正是您目前所需要的.您可以使用vue.js标签查看一些最近的问题,找到一个示例jsFiddle,看看它是如何完成的.

对于复杂的SPA应用程序,您需要从服务器端单独构建Vue代码,使用虚拟AJAX调用对其进行彻底测试,将其构建用于生产,然后将最终生产版本放入服务器以进行端到端测试.这是你将来可以做的事情.

  • 忘了提 - 你的页眉和页脚将是Vue组件,模板定义了布局和标记.请记住:所有渲染都在客户端,因此Google搜索机器人对该页面基本上是不可见的. (2认同)
  • 双花括号如何不与 Django 混淆?Django 使用相同的语法在模板中使用变量? (2认同)

JL *_*ret 18

我一会儿看着这个问题和其他人,同时他们正在寻找OP所要求的内容.不幸的是,关于Vue的大多数信息都是在SPA环境中给出的.但是,正如Evan You经常重复的那样,Vue并不是固执己见的,也不需要在SPA中使用.

我想分享一些我的发现,并勾勒出让Django和Vue一起工作的可能方法.虽然不需要SPA,但我认为Vue的真正功能来自于它的组件,这有点推动您使用Webpack或类似的方法,而不是html中的简单独立模式.

另外,要非常清楚:我的内容的90%以上Django视图代码和模板与以前完全相同.Django并不特别在意它正在使用webpack_loader和render_bundle.甚至更少的render_bundle与Vue有关.在Django template blocks,verbatim标签和Vue之间slots,您可以获得很多灵活性,使您可以单独留下大部分现有内容.

最后,一旦你的应用程序稳定,你可以在端口3000上禁用hotreload服务器,运行npm run build-production并使用collectstatic通过nginx/apache提供你的JS,就像任何正常的静态内容一样.因此node,偶尔运行而不是作为服务运行.需要稍微摆弄Django的配置,但在合理范围内.

道歉,这是非常粗略的,不要指望工作代码,因为我剥离了这么多.但希望它会给你一个想法.

mysite的/ __ full12_vue.html:

这是我的基础Vue-ify Django模板,它扩展了我现有的Django基础模板__full12.html.

  • 假设__full12.html定义了所有常规Django块,例如{%block content%}

    (实际上,有一个非常重要的div,ID bme-vue所以我也在最后添加了这个模板.)

  • 我添加了一个Vue组件来显示用户消息.

  • 并重新定义了菜单模板以使用Vue + Bootstrap下拉菜单.

{% extends "mysite/__full12.html" %}
<!-- KEY: use this to hook up to https://github.com/ezhome/django-webpack-loader -->
{% load render_bundle from webpack_loader %}


{% block nav %}
    <!-- uses Vue to setup Bootstrap dropdown menus -->
    {% include "mysite/menu_vue.html" %}
{% endblock nav %}


{% block user_msg %}
<div class="row">
    <div class="col-6">
        <!-- uses Vue to display user messages -->
        <bme-user-messages>
            <div>THIS SHOULDNT APPEAR ON SCREEN IF VUE WORKED</div>
        </bme-user-messages>
    </div>
</div>
{% endblock user_msg %}



{%block extra_js_body_end%}
    <!-- KEY  this points Django Webpack loader to appropriate Webpack entry point -->
    {% render_bundle bundle_name %}
{%endblock extra_js_body_end%}
Run Code Online (Sandbox Code Playgroud)

webpack.config.development.js:

这是您告诉Webpack哪个JS为您在视图中指定的bundle_name提供服务的地方.

如何配置Webpack超出了我的职位范围,但我可以向你保证这是一个真正的PIA.我开始使用pip django-webpack-loader,然后https://github.com/NdagiStanley/vue-django然后启动Bootstrap 4.但是,在我看来,最终结果比独立更强大.

/*
webpack config dev for retest
*/

config.entry = {
  "main" : [
    'webpack-dev-server/client?http://localhost:3000','../../static_src/js/index'],

  // ....stuff..
  //KEY: ONE entrypoint for EACH bundlename that you use.  
  "mydjangoappname/some_django_view" : ["../../static_src/js/mydjangoappname/some_django_view"],
  "mydjangoappname/another_django_view" : ["../../static_src/js/mydjangoappname/another_django_view"],
  // ....stuff..

}

  // ....stuff left out...
Run Code Online (Sandbox Code Playgroud)

mydjangoappname/some_django_template.html

最后,我们准备显示一些实际内容:

bme-nav-item并且bme-tab-pane是我用于Boostrap 4选项卡导航和内容的2个自定义Vue组件.

Django用于var settings= some-json-object将特定于实例的数据而非页面通用数据传递给Vue和JS

{% extends "mysite/__full12_vue.html" %}

<script>
// KEY: settings is provided by json.dumps(some_settings_dictionary) 
// which your views puts into your RequestContext.
// this is how Django tells Vue what changes with different data, on the same view
var settings = {{settings | safe}};
</script>

{% block content %}

    <!-- a button to run a Celery batch via a post command, url should probably come 
    from Django url reverse and be put into a Vue property...
     -->
    <button v-bind:data-url="url_batch" type="button" class="btn btn-block btn-outline-primary" @click.prevent="run_batch">

    <!-- lotsa stuff left out.... -->

    <ul id="tab-contents-nav" class="nav nav-tabs nav-pills">

    <!--  *label* is using a Vue Prop and because there is no {% verbatim %} guard around it, Django will
        inject the contents.  {% urlrev xxx %} could be used to write to an 'url' prop.  Ditto the conditional
        inclusion, by Django, of a template if it's in the RequestContext.
    -->
        {% if templatename_details %}
        <bme-nav-item link="details" 
            label="{{details_label}}" >         
        </bme-nav-item>
        {% endif %}

<!-- lotsa stuff left out.... -->

<bme-tab-pane link="details">
    <div slot="content">

        <!-- KEY: Vue slots are incredibly powerful with Django.  Basically this is saying
                  to Django : inject what you want in the slot, using your include, I'll tidy up afterwards.
                  In my case, this is a Bootstrap NavItem + NavTab 
        -->
        {% if templatename_details %}

            {% include templatename_details %}
        {% else %}
            <span class="text-warning">SHOULDNT APPEAR IF VUE WORKED </span>
        {% endif %}

    </div>
</bme-tab-pane>

{% endblock content %}
Run Code Online (Sandbox Code Playgroud)

mydjangoappname/some_django_view.js:

  import Vue from 'vue';
  import Vuex from 'vuex';
  //now Vue is using Vuex, which injects $store centralized state variables as needed
  Vue.use(Vuex);



  //KEY: re-using components defined once.
  import {base_messages, base_components} from '../mysite/commonbase.js';

  var local_components = {
    //nothing, but I could have imported some other components to mix-n-match
    //in any case, bme-nav-item, bme-tab-pane and bme-user-messages need to 
    //coming from somewhere for this page! 
  };

  const components = Object.assign({}, base_components, local_components);

  //we're going to put together a Vue on the fly...

  export function dovue(config) {

      //KEY:  The store is a Vuex object - don't be fooled, it's not SPA-only
      // it's the easiest way to coherently share data across Vue Components, so use it.
      store.commit('initialize', config);

      //here I am telling the store which html IDs need hiding
      var li_tohide = settings.li_tohide || [];
      li_tohide.forEach(function(hidden) {
          store.commit('add_hidden', hidden);
      });

      /* eslint-disable no-new */
      var vm = new Vue({

        //KEY:  This tells the Vue instance what parts of your html are in its scope.
        el: '#bme-vue'

        //KEY: each bme-xxx and bme-yyy special tag needs to be found in components below
        //otherwise you'll see my SHOULDNT APPEAR IF VUE WORKED text in your page
        ,components: components

        ,data: {
          li_rowcount: window.settings.li_rowcount || []
          ,csrf_token: window.csrf_token
          ,url_batch: "some url"
        }
        ,mounted: function () {
           // a Vue lifecycle hook.  You could use to set up Vue Event listening for example
           console.log("data.js.lifecycle.mounted");
        }
        ,methods : {
          ,run_batch: function(e) {
              //hook this up to a button 
              console.assert(this.$data, COMPONENTNAME + ".run_batch.this.$data missing. check object types");
              var url = e.target.dataset.url

              //this is defined elsewhere 
              post_url_message(this, url, this.csrf_token);
          }
        }
        //the Vuex instance to use for this Vue.
        ,store: store
      });

      //did Django provide any user messages that need displaying?
      var li_user_message = config.li_user_message || [];

      li_user_message.forEach(function(user_message, i) {
        //the bme-user-messages Component?  It's listening for this event 
        //and will display Bootstrap Alerts for them.
        vm.$emit(base_messages.EV_USER_MESSAGE, user_message);
      });
      return vm;
  }

  //various app and page specific settings...
  import {app_config, LOCALNAV_LINK, TOPNAV_LINK_OTHERS} from "./constants";
  var page_config = {
    //used to show which navigation items are .active
    localnav_link : LOCALNAV_LINK.data
    , topnav_link: TOPNAV_LINK_OTHERS.system_edit_currentdb
  };

  //this is coming from Django's RequestContext.
  var generated_config = window.settings;

  //ok, now we are merging together a Django app level config, the page config and finally
  //what the Django view has put into settings.  This will be passed to the Vuex store
  //individual Vue Components will then grab what they need from their $store attribute
  //which will point to that Vuex instance.
  var local_config = Object.assign({}, app_config, page_config, generated_config);
  var vm = dovue(local_config);
Run Code Online (Sandbox Code Playgroud)

vuex/generic.js:

还有一个天真的,大多数只读的商店实现:

//you can add your page's extra state, but this is a shared baseline
//for the site
const state = {
  active_tab: ""
  ,topnav_link: ""
  ,localnav_link: ""
  ,li_user_message: []
  ,s_visible_tabid: new Set()
  ,urls: {} 
};
const mutations = {
    //this is what your page-specific JS is putting into the state.
    initialize(state, config){
      //initialize the store to a given configuration
      //KEY: attributes that did not exist on the state in the first place wont be reactive.
        // console.log("store.initialize");
        Object.assign(state, config);
    },
    //a sample mutation
    set_active_tab(state, tabid){
        //which bme-tab-nav is active?
        if (! state.s_visible_tab.has(tabid)){
          return;
        }
        state.active_tab = tabid;
    },
};

export {state as generic_state, mutations};
Run Code Online (Sandbox Code Playgroud)

并让您了解一般文件层次结构:

.
./manage.py
./package.json  //keep this under version control
./

??? mydjangoappname
?   ??? migrations
?   ??? static
?       ??? mydjangoappname
??? node_modules
?        //lots of JavaScript packages here, deposited/managed via npm && package.json
??? static
?   ??? js
??? static_src
?   ??? assets
?   ??? bundles
?   ?   // this is where django-webpack-loader's default config deposits generated bundles...
?   ?   // probably belonged somewhere else than under static_src ...
?   ?   ??? mydjangoappname
?   ??? components
?   ?   ??? mydjangoappname
?   ??? css
?   ??? js
?   ?   ??? mydjangoappname
?   ?   ??? mysite
?   ??? vuex
?       ??? mydjangoappname
??? staticfiles
?   //  for Production, collectstatic should grab django-webpack-loader's bundles, I think...
??? templates
?   ??? admin
?   ?   ??? pssystem
?   ??? mydjangoappname
?   ??? mysite
??? mysite
    ??? config
    ?       // where you configure webpack and the entry points.
    ?       webpack.config.development.js 
    ??? sql
    ?   ??? sysdata
    ??? static
    ?   ??? mysite
    ??? templatetags
Run Code Online (Sandbox Code Playgroud)

好的,我必须修改网站的基本模板,以确保div#bme-vue始终可用.

可能需要在这个和mysite/__ full12_vue.html之间进行一些重构.

mysite/__ full12.html:

<!-- lots of stuff left out -->
<body>

    <!--     KEY: the #bme-vue wrapper/css selector tells Vue what's in scope.  
    it needs to span as much of the <body> as possible, but 
    also end right BEFORE the render_bundle tag.  I set that css
    selector in mydjangoappname/some_django_view.js and I'd want to
    use the same selector everywhere throughout my app.
    -->

    <div id="bme-vue">
        <!-- anything that ends up here
, including through multiple nested/overridden Django content blocks
, can be processed by Vue
, but only when have Vue-relevant markup 
such as custom component tags or v-for directives.
-->

    ...more blocks...
    {% block search %}
    {% endblock search %}

    <div id="main" role="main">
        <div> <!-- class="container"> -->
            {% block content %}
            {% endblock %}
        </div>
    </div>
    ...more blocks...


    </div>    <!-- bme-vue -->
    {%block extra_js_body_end%}
    {%endblock extra_js_body_end%}
</body>
</html>
Run Code Online (Sandbox Code Playgroud)