Это не подробный учебник с информацией о каждой платформе. В этой статье я описал только необходимые пункты конфигурации а так же причины почему это делается.
Весь проект можно найти на странице GitHub
Итак начнем с создания проекта на Django. Для начало создадим соотвествующе виртуальное окружение с помощью pipenv.
mkdir myproject cd myproject pipenv shell --three --python 3.6
Установим django
(myproject) bash-3.2$ pipenv install django
Далее создадим проект
(myproject) bash-3.2$ django-admin startproject myproject .
Инициализируем базу данных
(myproject) bash-3.2$ python manage.py migrate
Сразу создадим администратора
(myproject) bash-3.2$ python manage.py createsuperuser
Проверим работу
(myproject) bash-3.2$ python manage.py runserver
Перейдем по ссылке http://127.0.0.1:8000/
Проверим имя администратора и его пароль. Перейдем по ссылке http://127.0.0.1:8000/admin/ и залогинимся
В папке проекта создадим папку myproject/settings, а так же следующие файлы:
__init__.py dev.py prod.py
Переместим файл settings.py в папку myproject/settings.
Изменим BASE_DIR и добавим STATIC_ROOT в файл settings.py.
BASE_DIR = os.path.dirname(os.path.dirname( os.path.dirname(os.path.abspath(__file__)))) STATIC_ROOT = os.path.join(BASE_DIR, "staticfiles")
В __init__.py добавим импорт настроек по умолчанию
from .dev import *
В dev.py импортируем базовые настройки из settings.py и добавим настройки для hosts и debug.
from .settings import * DEBUG = True ALLOWED_HOSTS = ['127.0.0.1', 'localhost']
В prod.py добавим информацию о DEBUG и hosts
from .settings import * DEBUG = False ALLOWED_HOSTS = ['*']
Создадим скрип запуска dev и prod. Для этого создадим файл run.sh в корне проекта.
Эти 3 команды для синхронизации всех изменений на сервере перед его запуском.
collectsatic
— перемещает все статику в папку STATIC_ROOT
makemigrations
— создаем новую миграцию основанную на изменения в моделяхmigrate
— применяет миграцию#!/bin/bash # pipenv shell case $1 in dev) python manage.py runserver --settings=myproject.settings.dev ;; prod) python manage.py collectstatic --noinput python manage.py makemigrations python manage.py migrate python manage.py runserver --settings=myproject.settings.prod ;; esac
Проверим запуск проекта
(myproject) bash-3.2$ sh run.sh dev
(myproject) bash-3.2$ sh run.sh prod
Создадим в корне проекта папку templates и в ней файл index.html со следующим содержимым:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> </head> <body> Home page </body> </html>
Добавим папку templates в параметр конфигурации TEMPLATES. Для этого внесем следующую строку в mypproject\settings\settings.py:
TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [ os.path.join(BASE_DIR, 'templates'), ], ...
Добавим index.html в список urls. Для этого внесем следующие изменения в myproject\url.py:
from django.contrib import admin from django.urls import path from django.views.generic import TemplateView urlpatterns = [ path('', TemplateView.as_view(template_name='index.html')), path('admin/', admin.site.urls), ]
Проверим работу в dev и prod режиме:
(myproject) bash-3.2$ sh run.sh dev
(myproject) bash-3.2$ sh run.sh prod
Установим Django Debug Toolbar:
(myproject) bash-3.2$ pipenv install django-debug-toolbar
Добавим debug toolbar в настройки dev. Для этого добавим следующие изменения в myproject\settings\dev.py:
MIDDLEWARE.append('debug_toolbar.middleware.DebugToolbarMiddleware') INSTALLED_APPS.append('debug_toolbar') INTERNAL_IPS = ('127.0.0.1', 'localhost')
Добавим debug toolbar в список url. Внесем следующие изменения в myproject\urls.py:
from django.conf import settings from django.conf.urls import include, url ... if settings.DEBUG: import debug_toolbar urlpatterns = [ url(r'^__debug__/', include(debug_toolbar.urls)), ] + urlpatterns
Проверим работу toolbar
(myproject) bash-3.2$ sh run.sh dev
Установите глобально vue/cli если у вас еще до сих пор не установлена эта библиотека:
npm i -g @vue/cli
Создадим новый проект на Vue. Для этого в новом окне терминала в корне проекта запустите команду:
vue create frontend
Ответьте на все вопросы скрипта по вашему усмотрению. Но если будет предложен выбор то выберите вариант с vue-router, если нет то придется до установить нужные компоненты см. ниже.
Далее перейдем в папку проекта
cd frontend
Запустим новый проект командой:
yarn serve
Так как на этом шаге возможны различные варианты генерации базового приложения то нужно убедиться что у нас в приложение есть следующие страницы views/About.vue и views/Home.vue. Если они не создались создайте их вручную (полную версию см в репозитории). Так же возможно нужно будет до установить следующие компоненты.
npm install --save vue-router npm install --save vuex
Нужно проверить наличие router.js:
import Vue from 'vue' import Router from 'vue-router' import Home from './views/Home.vue' Vue.use(Router) export default new Router({ mode: 'history', base: process.env.BASE_URL, routes: [ { path: '/', name: 'home', component: Home }, { path: '/about', name: 'about', // route level code-splitting // this generates a separate chunk (about.[hash].js) for this route // which is lazy-loaded when the route is visited. component: () => import(/* webpackChunkName: "about" */ './views/About.vue') } ] })
И наличия store.js:
import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) export default new Vuex.Store({ state: { }, mutations: { }, actions: { } })
Содержимое main.js должно быть примерно таким:
import Vue from 'vue' import App from './App.vue' import router from './router' import store from './store' Vue.config.productionTip = false new Vue({ router, store, render: h => h(App) }).$mount('#app')
А содержимое App.js таким:
<template> <div id="app"> <div id="nav"> <router-link to="/">Home</router-link> | <router-link to="/about">About</router-link> </div> <router-view/> </div> </template> <script> import HelloWorld from './components/HelloWorld.vue' export default { name: 'app', components: { HelloWorld } } </script> <style> #app { font-family: 'Avenir', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px; } </style>
Установите webpack-boundle-tracker в папку фронтенд проекта. Для этого запустите следующие команды в папке frontend:
npm install webpack-bundle-tracker --save-dev npm install write-file-webpack-plugin --save-dev
Запустите следующую команду в папке проекта Django:
(myproject) bash-3.2$ pipenv install django-webpack-loader
В папке фронтенд проекта frontend создайте файл vue.config.js и внесите в него следующие содержимое:
var BundleTracker = require('webpack-bundle-tracker') var WriteFilePlugin = require('write-file-webpack-plugin') module.exports = { outputDir: (process.env.NODE_ENV === "production" ? 'dist' : 'static'), baseUrl: '/', devServer: { publicPath: "http://localhost:8080/", headers: { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, PATCH, OPTIONS", "Access-Control-Allow-Headers": "Origin, X-Requested-With, Content-Type, Accept, Accept-Encoding, Accept-Language, Access-Control-Request-Headers, Access-Control-Request-Method", "Access-Control-Allow-Credentials": "true" } }, chainWebpack: config => { config.optimization.splitChunks({ cacheGroups: { vendors: { name: 'chunk-vendors', test: /[\\\/]node_modules[\\\\/]/, priority: -10, chunks: 'initial' }, common: { name: 'chunk-common', minChunks: 2, priority: -20, chunks: 'initial', reuseExistingChunk: true } } }) }, configureWebpack: { output: { filename: 'js/[name].js', chunkFilename: 'js/[name].js' }, plugins: [ new WriteFilePlugin(), (process.env.NODE_ENV === "production" ? new BundleTracker({ filename: 'webpack-stats-prod.json', publicPath: '/' }) : new BundleTracker({ filename: 'webpack-stats.json', publicPath: 'http://localhost:8080/' }) ) ] } }
baseUrl: “./”
: Это адрес на которых webpack будет генерировать все ссылки. ./
означает, что слеш не должно быть в начале адреса и все ссылки идут из текущей папки. Эта конфигурация не будет работать для приложений с router, где браузер будет загружать ссылку со страницы. (для примера img/logo2.png
по адресу http://127.0.0.1:8000/about/
будет загружать файл с адреса http://127.0.0.1:8000/about/img/logo2.png
который не будет верным)
если baseUrl: "/"
тогда все ссылки будут загружены с URL хоста. Если ваша веб-страница находится в папке (не на главном хосте), вам нужно изменить эту конфигурацию на базовую папку, в которой вы размещаете файлы vue.js. (например, /username/mypage)
BundleTracker
будет генерировать webpack-stats.json
который будет использоваться в проекте. (для prod и dev)
outputDir
— это адрес расположения все файлов для Djange (static
или dist
)
output
, chainWebpack
— поскольку мы не используем файлы *.html по умолчанию у нас *.vue, нам нужно иметь один и тот же список блоков для загрузки в среде prod и dev (webpack-stats.json
и webpack-stats-prod.json
)
WriteFilePlugin
— этот плагин будет копировать файлы в папку назначения, даже когда мы запускаем webpack-dev-server. Это важно, потому что Django использует статические файлы, такие как изображения из этой папки.
Протестируем как это работает. Запустите следующие команды в папке фронтенд проекта
vue inspect > output-dev.js vue inspect --mode production > output-prod.js
Должно создаться два файла output-dev.js и output-prod.js
Добавим WEBPACK_LOADER в проект Django. Для этого внесите следующие изменения в myproject\settings\settings.py:
INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'webpack_loader' ]
В myproject\settings\dev.py следующие изменения:
... WEBPACK_LOADER = { 'DEFAULT': { 'BUNDLE_DIR_NAME': '', 'STATS_FILE': os.path.join(BASE_DIR, 'frontend/webpack-stats.json'), } } STATICFILES_DIRS = [ os.path.join(BASE_DIR, "static"), ] MEDIA_URL = '/dmedia/' MEDIA_ROOT = os.path.join(BASE_DIR, "mediafiles") STATIC_URL = '/static/' STATIC_ROOT = os.path.join(BASE_DIR, "staticfiles") VUE_ROOT = os.path.join(os.path.join(BASE_DIR, "frontend"), "static")
В myproject\settings\prod.py следующие:
WEBPACK_LOADER = { 'DEFAULT': { 'BUNDLE_DIR_NAME': '', 'STATS_FILE': os.path.join(BASE_DIR, 'frontend/webpack-stats-prod.json'), } } STATICFILES_DIRS = [ os.path.join(BASE_DIR, "static"), ] MEDIA_URL = '/dmedia/' MEDIA_ROOT = os.path.join(BASE_DIR, "mediafiles") STATIC_URL = '/static/' STATIC_ROOT = os.path.join(BASE_DIR, "staticfiles") VUE_ROOT = os.path.join(os.path.join(BASE_DIR, "frontend"), "dist")
BUNDLE_DIR_NAME
устанавливает свой префикс . Это префикс для файлов .js и .css. Параметр должен быть пустым для нашей конфигурации и совпадать с baseUrl из vue.config.js.STATS_FILE
— файл для загрузки статистики webpack.MEDIA_URL
— Медиа файлы из Django (dmedia
потому что vue.js
так же имеет папку media
дляmp4|webm|ogg|...
файлов)MEDIA_ROOT
— Путь куда будут загружаться пользовательские медиа файлыSTATIC_URL
— Url для статики в Django.STATIC_ROOT
— Куда все файлы будут копироваться при запуски команды python manage.py collectstatic
VUE_ROOT
— полезный путь для всего вывода vue.js. Он должен соответствовать папке назначения проекта vue.js. Создадим папку static в проекте Django:
(myproject) bash-3.2$ mkdir static
Настроим все наши ulrs для vue.js, static и media. Для этого внесем следующие изменения в myproject\urls.py:
from django.contrib import admin from django.urls import path from django.views.generic import TemplateView from django.conf import settings from django.conf.urls import include, url from django.views.static import serve import os urlpatterns = [ path('admin/', admin.site.urls), path('', TemplateView.as_view(template_name='index.html')), url(r'^static/(?P<path>.*)$', serve, {'document_root': settings.STATIC_ROOT}), url(r'^dmedia/(?P<path>.*)$', serve, {'document_root': settings.MEDIA_ROOT}), url(r'^media/(?P<path>.*)$', serve, {'document_root': os.path.join(settings.VUE_ROOT, 'media')}), url(r'^img/(?P<path>.*)$', serve, {'document_root': os.path.join(settings.VUE_ROOT, 'img')}), url(r'^js/(?P<path>.*)$', serve, {'document_root': os.path.join(settings.VUE_ROOT, 'js')}), url(r'^css/(?P<path>.*)$', serve, {'document_root': os.path.join(settings.VUE_ROOT, 'css')}), url(r'^fonts/(?P<path>.*)$', serve, {'document_root': os.path.join(settings.VUE_ROOT, 'fonts')}), ] if settings.DEBUG: import debug_toolbar urlpatterns = [ url(r'^__debug__/', include(debug_toolbar.urls)), ] + urlpatterns
все эти URL-адреса аналогичны тем, которые можно найти в проекте vue.js. Мы используем dmedia для django media, потому что vue.js использует ту же папку для медиа файлов.
Перемести ваш favicon.ico в главную папку проекта myproject и внесите соотвествующие изменения в /myproject/urls.py:
import os from django.views.generic.base import RedirectView favicon_view = RedirectView.as_view(url=os.path.join(settings.STATIC_URL,'favicon.ico'), permanent=True) urlpatterns = [ path('favicon.ico', favicon_view), ...
Внесите следующие изменения в templates\index.html:
{% load render_bundle from webpack_loader %} {% load static from staticfiles %} <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>frontend</title> <meta http-equiv=X-UA-Compatible content="IE=edge"> <meta name=viewport content="width=device-width,initial-scale=1"> <link rel=icon href="{% static 'favicon.ico' %}"> {% render_bundle 'chunk-vendors' %} </head> <body> <noscript> <strong> We're sorry but frontend doesn't work properly without JavaScript enabled. Please enable it to continue. </strong> </noscript> {% for i in 'abc' %} <strong>{{ i }} DJANGO PART</strong> {% endfor %} <div id=app> </div> {% render_bundle 'app' %} </body> </html>
{% render_bundle 'chunk-vendors' %}
— это загрузчик для splitChunks
в dev
иprod
окружениях. Если мы не настроим splitChunks
для dev
Django покажет следующую ошибку.
Главное приложение vue.js:
<div id=app> </div> {% render_bundle 'app' %} ...
Протестируем dev проект. Для этого в папке фронтенд проекта запустите следующую команду:
npm run serve
А в главной папке Django проекта:
(myproject) bash-3.2$ sh run.sh dev
Протестируем запуск в прод окружение. Для этого запустите в папке фронтенд проект
npm run build
А в папке Django команду:
(myproject) bash-3.2$ sh run.sh prod
Добавим новый url в приложение на Django. Это может быть тот же файл, что и для приложения (index.html), потому что vue.js знает, какой URL-путь вы хотите использовать.
... urlpatterns = [ path('', TemplateView.as_view(template_name='index.html')), path('about/', TemplateView.as_view(template_name='index.html')), ....
Установим graphene_django. Для этого в папке проекта Django наберите следующие команды:
(myproject) bash-3.2$ pipenv install graphene_django (myproject) bash-3.2$ pipenv install django-filter==1.1.0
Далее добавим новое приложение Django
(myproject) bash-3.2$ django-admin startapp tasks
И добавим новые модели в myproject/tasks/models.py:
from django.db import models from django.contrib import admin # Create your models here. class Task(models.Model): isDone = models.BooleanField() name = models.CharField(max_length=100) description = models.TextField() def __str__(self): return self.name @admin.register(Task) class TaskAdmin(admin.ModelAdmin): pass
Добавим новое приложение в настройки myproject/settings/settings.py:
INSTALLED_APPS = [ ... # Install the ingredients app 'tasks', ]
Запустим миграцию:
(myproject) bash-3.2$ python manage.py makemigrations (myproject) bash-3.2$ python manage.py migrate
Создадим JSON файл с данными фикстур tasks\fixtures\tasks.json:
[{ "model": "tasks.Task", "pk": 1, "fields": { "isDone": "True", "name": "Do shoppping", "description": "milk, butter" } }, { "model": "tasks.Task", "pk": 2, "fields": { "isDone": "False", "name": "Do laundry", "description": "all clothes" } }, { "model": "tasks.Task", "pk": 3, "fields": { "isDone": "False", "name": "Fix computer", "description": "Fix computer" } }]
Загрузим этот файл в базу:
(myproject) bash-3.2$ python manage.py loaddata tasks
Создадим файл tasks\schema.py со следующим содержимым и добавим схему для наших моделей:
import graphene from graphene_django.types import DjangoObjectType from graphql_relay.node.node import from_global_id from tasks.models import Task class TaskType(DjangoObjectType): class Meta: model = Task class CreateTask(graphene.Mutation): ok = graphene.Boolean() task = graphene.Field(lambda: TaskType) class Arguments: name = graphene.String() description = graphene.String() def mutate(self, info, name, description): task = Task(name=name, description=description, isDone=False) task.save() ok = True return CreateTask(task=task, ok=ok) class UpdateTask(graphene.Mutation): task = graphene.Field(lambda: TaskType) ok = graphene.Boolean() class Arguments: id = graphene.String() IsDone = graphene.Boolean() def mutate(self, info, id, IsDone): task = Task.objects.get(pk=id) task.isDone = IsDone task.save() ok = True return UpdateTask(task=task, ok=ok) class Query(graphene.ObjectType): tasks = graphene.List(TaskType) def resolve_tasks(self, info): return Task.objects.all() class Mutations(graphene.ObjectType): create_task = CreateTask.Field() update_task = UpdateTask.Field()
Добавим глобальную схему в наш проект то есть создадим файл myproject\schema.py со следующим содержимым:
import graphene import tasks.schema class Query(tasks.schema.Query, graphene.ObjectType): # This class will inherit from multiple Queries # as we begin to add more apps to our project pass class Mutation(tasks.schema.Mutations, graphene.ObjectType): pass schema = graphene.Schema(query=Query, mutation=Mutation)
Добавим graphene_django в настройки Django myproject\settings\settings.py:
INSTALLED_APPS = [ ... 'graphene_django', ]
Добавить новую схему в список GRAPHENE в файле нстроек myproject\settings\settings.py:
GRAPHENE = { 'SCHEMA': 'myproject.schema.schema' }
Добавим url для graphql в файле myproject\urls.py:
... from graphene_django.views import GraphQLView from django.views.decorators.csrf import csrf_exempt urlpatterns = [ ... url(r'^graphql', csrf_exempt(GraphQLView.as_view(graphiql=True))), ]
Запустите сервер и зайдите на страницу http://127.0.0.1:8000/graphql/ для тестового запроса:
query { tasks { id, isDone, name, description } }
Протестируйте запрос на создание записи:
mutation createTask($name:String, $description: String) { createTask(name: $name, description: $description) { task { name description } ok } }
переменные:
{ "name": "something to do", "description": "description for task" }
Протестируем запрос на редактирование записи:
mutation updateTask($id: String, $IsDone: Boolean) { updateTask(id: $id, IsDone: $IsDone) { task { id isDone name description } ok } }
переменные:
{"id": "2", "IsDone": true}
Устанвим apollo в фронтенд проект
npm install --save vue-apollo graphql apollo-client apollo-link apollo-link-http apollo-cache-inmemory graphql-tag
Создадим конфигурационный файл для apollo frontend/src/vue-apollo.js:
import Vue from 'vue' import { ApolloClient } from 'apollo-client' import { HttpLink } from 'apollo-link-http' import { InMemoryCache } from 'apollo-cache-inmemory' import VueApollo from 'vue-apollo' const httpLink = new HttpLink({ // You should use an absolute URL here uri: '/graphql', }) // Create the apollo client const apolloClient = new ApolloClient({ link: httpLink, cache: new InMemoryCache(), connectToDevTools: true, }) const apolloProvider = new VueApollo({ defaultClient: apolloClient, }) // Install the vue plugin Vue.use(VueApollo) export default apolloProvider
uri: /graphql
— Это url
для взаимодействия graphql с Django.
Добавим apolloProvider в main.js:
import Vue from 'vue' import App from './App.vue' import router from './router' import store from './store' import apolloProvider from './vue-apollo' Vue.config.productionTip = false new Vue({ router, store, apolloProvider: apolloProvider, render: h => h(App) }).$mount('#app')
Добавим a query в vue.js компонент (имя tasks должно совпадать с именем в query). Внесите следующие изменения в frontend/src/views/Home.vue:
<template> <div class="home"> <div v-for="i in tasks" :key="i.id"> <ul> <li> <strong>{{i.name}}</strong>:<span>{{i.description}}</span> </li> </ul> </div> ....
<script> import gql from 'graphql-tag' const TaskQuery = gql` query { tasks { id isDone name description } } `; export default { data() { return { // Initialize your apollo data tasks: '', } }, apollo: { // Simple query that will update the 'hello' vue property tasks: TaskQuery, }, ...
Запустите dev и prod конфигурации что бы проверить как это работает. В локальном запуске фронтенд не будет работать ( http://127.0.0.1:8080/ ) потому что мы определили uri как /graphql а адрес http://127.0.0.1:8080/graphql вернет ошибку.
npm run serve (myproject) bash-3.2$ sh run.sh dev
Добавьте поля ввода в шаблон для добавления новых значений. Файл frontend\src\views\Home.vue:
<template> <div class="home"> <div> <span>Name:</span><input type="text" v-model="name"> <span>Description:</span><input type="text" v-model="description"> <button @click="create_task">Add</button> </div> ...
data() { return { name: '', description: '', ...
Добавим запрос createTask. Файл frontend\src\views\Home.vue:
<script> ... const TaskCreate = gql`mutation createTask($name:String, $description: String) { createTask(name: $name, description: $description) { task { id isDone name description } ok } }`
Добавим метод create_task. Файл frontend\src\views\Home.vue:
... methods: { async create_task() { const name = this.name const description = this.description // Call to the graphql mutation let data = await this.$apollo.mutate({ // Query mutation: TaskCreate, // Parameters variables: { name: name, description: description }, update: (store, { data: { createTask } }) => { // Add to All tasks list const data = store.readQuery({ query: TaskQuery }) data.tasks.push(createTask.task) store.writeQuery({ query: TaskQuery, data }) }, // optimisticResponse: { // __typename: 'Mutation', // createTask: { // __typename: 'CreateTask', // task: { // __typename: "TaskType", // id: -1, // isDone: false, // name: name, // description: description // }, // ok: false // } // }, }) var t = data.data.createTask.task console.log('Added: ' , t) this.name = '' this.description = '' } ...
mutation
: запрос на мутациюvariables
: перменные используемы в зпросеupdate
: что будет делаться после update. Считайте локального cash для запроса readQuery с запросом TaskQuery, подстановка новых данных в data.tasks и запуск writeQuery, чтобы обновить локальный список задач.optimisticResponse
: Только если необходимо вернуть ответ до завершения запроса, чтобы не ждать результатов.Протестируем запрос на создание записи
Добавьте checkbox для редактируемой модели. Файл frontend\src\views\Home.vue:
... <div v-for="i in tasks" :key="i.id"> <ul> <li> <input type="checkbox" @input="update_task(i)" :checked="i.isDone">
Добавим запрос updateTask. Файл frontend\src\views\Home.vue:
<script> ... const TaskUpdate = gql`mutation updateTask($id: String, $IsDone: Boolean) { updateTask(id: $id, IsDone: $IsDone) { task { id isDone name description } ok } }` ...
Добавим метод update_task. Файл frontend\src\views\Home.vue:
methods: { async update_task(i) { await this.$apollo.mutate({ mutation: TaskUpdate, variables: { id: i.id, IsDone: !i.isDone }, }) }, ...
Протестируйте метод редактирования
В статье было продемонстрировано создания приложения на Django с API на GraphQL и реализацией фронтенд части на Vue.js.
Оригинальная статья: Django Vue.js and GraphQL — Step by step
Краткий перевод: https://vuejs.org/guide/components/v-model.html Основное использование v-model используется для реализации двусторонней привязки в компоненте. Начиная с Vue…
Сегодня мы рады объявить о выпуске Vue 3.4 «🏀 Slam Dunk»! Этот выпуск включает в…
Vue.js — это универсальный и адаптируемый фреймворк. Благодаря своей отличительной архитектуре и системе реактивности Vue…
Недавно, у меня истек сертификат и пришлось заказывать новый и затем устанавливать на хостинг с…
Каким бы ни было ваше мнение о JavaScript, но всем известно, что работа с датами…
Все, кто следит за последними событиями в мире адаптивного дизайна, согласятся, что введение контейнерных запросов…
View Comments
Incredible! Thanks so much.