Недавно я начал работу на каком-то веб-сайте в социальных сетях, используя Django. Я использую механизм шаблона django по умолчанию для заполнения моих страниц. Но в этот момент я хочу добавить javascript, чтобы сделать сайт более динамичным. Это означает:
Я хочу использовать Vue для работы с динамическими компонентами, но я не знаю, как мне начать работу. Приложение не должно быть SPA.
Ответ 1
Это выглядит как вопрос мнение, для которого нет четкого ответа.
Вы упомянули о том, что не хотите, чтобы приложение было единственным приложением страницы (SPA). Если да, то в чем мотивация использования Vue? Чтобы обрабатывать пользовательские взаимодействия внутри страницы?
Vue может работать отлично в контексте non-SPA. Это поможет вам справиться с богатыми взаимодействиями на странице, например, привязать ваши данные к раскрывающимся спискам, формам и т.д. Но реальная сила Vue появляется, когда вы используете ее в контексте SPA.
В вашем случае я бы рекомендовал использовать Vue.js в автономном режиме , где вы можете быстро определить template
в компонентах Vue и легко записать весь свой код в одном файле javascript.
Вот что вам нужно: https://vuejs.org/guide/installation.html#Standalone
В "автономном режиме Vue.js" нет необходимости в какой-либо системе сборки webpack или vue-cli
. Вы можете создать приложение непосредственно в существующей среде dev для django. gulp
может произвольно минимизировать и связывать ваши файлы javascript, как и с вашими приложениями на основе jQuery.
Vue.js использует двойные фигурные скобки {{..}}
для шаблонов, поэтому он не будет мешать вашим строкам шаблона django.
Все примеры jsFiddle для Vue.js выполняются в автономном режиме . Это именно то, что вам нужно в этот момент. Вы можете посмотреть некоторые из последних вопросов с тегом vue.js
, найти образец jsFiddle и посмотреть, как это делается.
Для сложных SPA-приложений вам необходимо создать свой код Vue отдельно от серверной части, тщательно протестировать его с помощью фиктивных вызовов AJAX, построить его для производства и затем отбросить окончательную производственную сборку на свой сервер для сквозного тестирования. Это то, что вы можете сделать в будущем.
Ответ 2
Я смотрел на этот вопрос, а другие на некоторое время назад, глядя на то, чтобы делать то, что спрашивал OP. К сожалению, большая часть информации о Vue представлена в контексте SPA. Однако, как часто повторял Эван, Vue не упрям и не требует использования в SPA.
Я хотел бы поделиться некоторыми своими выводами и наметить возможный подход к совместной работе Django и Vue. В то время как SPA не требуется
Я думаю, что реальная сила Vue исходит от своих Компонентов, и это подталкивает вас к Webpack или подобному подходу, а не простому автономному
mode в html.
Также, чтобы быть предельно ясным: 90% + моего содержимого код и шаблоны Django остались точно такими же, как раньше. Django не особо заботится о том, чтобы он использовал webpack_loader и render_bundle. И тем более, что render_bundle имеет какое-то отношение к Vue. Между тегами Django template blocks
, verbatim
и Vue slots
вы получаете большую гибкость, которая позволяет оставить только часть вашего существующего контента.
Наконец, мое понимание django-webpack заключается в том, что, когда вы будете готовы, вы можете collectstatic
, и все ваши пакеты, созданные с помощью webpack, могут затем обслуживаться django/nginx/apache, как и другие статические ресурсы, без каких-либо node или webpack. Таким образом, вы не добавляете сложные движущиеся части js в свои производственные системы. (примечание: это последнее является предположением, пожалуйста, подтвердите или опроверьте комментарии, если вы знаете)
Извинения, это довольно отрывочно, не ожидайте рабочего кода, поскольку я так много снимаю. Но, надеюсь, это даст вам представление.
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%}
webpack.config.development.js
Здесь вы указываете Webpack, какой JS должен служить для bundle_name, который вы указываете в представлении.
Как настроить Webpack выходит за рамки моего сообщения, но я могу заверить вас, что это настоящий PIA. Я начал с pip django-webpack-loader, затем https://github.com/NdagiStanley/vue-django, затем загрузив 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...
mydjangoappname/some_django_template.html
Наконец, мы готовы отобразить некоторое фактическое содержимое:
bme-nav-item
и bme-tab-pane
- 2 пользовательских компонента Vue, которые я использую для вкладок и содержимого вкладки Boostrap 4.
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 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 %}
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 not SPA-only
// it 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 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 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);
vuex/generic.js:
И наивная, главным образом реализация магазина только для чтения:
//you can add your page 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};
и дать вам представление об общей иерархии файлов:
.
./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 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 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
ОК, мне пришлось изменить шаблон базы сайта, чтобы убедиться, что 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 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>