vuex和vuejs

前言:在最近学习 Vue.js 的时候,看到国外一篇讲述了如何使用 Vue.js 和 Vuex 来构建一个简单笔记的单页应用的文章。感觉收获挺多,自己在它的例子的基础上进行了一些优化和自定义功能,在这里和大家分享下学习心得。

在这篇教程中我们将通过构建一个笔记应用来学习如何在我们的 Vue 项目中使用 Vuex。我们将大概的过一遍什么是 Vuex.js,在项目中什么时候使用它,和如何构建我们的 Vue 应用。

这里放一张我们项目的预览图片:

项目源码:vuex-notes-app;有需要的同学可以直接下载源码查看。

主要知识点

  • Vuex 状态管理机制的使用

  • Vue.js 的基础 api

  • Vue-cli 脚手架的安装及使用

  • vur-router 的使用

  • ES6 的语法,这里推荐看下阮一峰的入门教程

Vuex 概述

在我们迫不及待的开始项目之前,我们最好先花几分钟来了解下 Vuex 的核心概念。

Vuex 是一个专门为 Vue.js 应用所设计的集中式状态管理架构。它借鉴了 Flux 和 Redux 的设计思想,但简化了概念,并且采用了一种为能更好发挥 Vue.js 数据响应机制而专门设计的实现。

state 这样概念初次接触的时候可能会感觉到有点模糊,简单来说就是将 state 看成我们项目中使用的数据的集合。然后,Vuex 使得 组件本地状态(component local state)和 应用层级状态(application state) 有了一定的差异。

  • component local state:该状态表示仅仅在组件内部使用的状态,有点类似通过配置选项传入 Vue 组件内部的意思。

  • application level state:应用层级状态,表示同时被多个组件共享的状态层级。

假设有这样一个场景:我们有一个父组件,同时包含两个子组件。父组件可以很容易的通过使用 props 属性来向子组件传递数据。

但是问题来了,当我们的两个子组件如何和对方互相通信的? 或者子组件如何传递数据给他父组件的?在我们的项目很小的时候,这个两个问题都不会太难,因为我们可以通过事件派发和监听来完成父组件和子组件的通信。

然而,随着我们项目的增长:

  • 保持对所有的事件追踪将变得很困难。到底哪个事件是哪个组件派发的,哪个组件该监听哪个事件?

  • 项目逻辑分散在各个组件当中,很容易导致逻辑的混乱,不利于我们项目的维护。

  • 父组件将变得和子组件耦合越来越严重,因为它需要明确的派发和监听子组件的某些事件。

这就是 Vuex 用来解决的问题。 Vuex 的四个核心概念分别是:

  • The state tree:Vuex 使用单一状态树,用一个对象就包含了全部的应用层级状态。至此它便作为一个『唯一数据源(SSOT)』而存在。这也意味着,每个应用将仅仅包含一个 store 实例。单状态树让我们能够直接地定位任一特定的状态片段,在调试的过程中也能轻易地取得整个当前应用状态的快照。

  • Getters:用来从 store 获取 Vue 组件数据。

  • Mutators:事件处理器用来驱动状态的变化。

  • Actions:可以给组件使用的函数,以此用来驱动事件处理器 mutations

如何你暂时还不太理解这个四个概念,不用着急,我们将在后面的项目实战中详细的解释。

下面这张图详细的解释了 Vuex 应用中数据的流向(Vuex 官方图)

简单解释下:

Vuex 规定,属于应用层级的状态只能通过 Mutation 中的方法来修改,而派发 Mutation 中的事件只能通过 action。

从左到又,从组件出发,组件中调用 action,在 action 这一层级我们可以和后台数据交互,比如获取初始化的数据源,或者中间数据的过滤等。然后在 action 中去派发 Mutation。Mutation 去触发状态的改变,状态的改变,将触发视图的更新。

注意事项

  • 数据流都是单向的

  • 组件能够调用 action

  • action 用来派发 Mutation

  • 只有 mutation 可以改变状态

  • store 是响应式的,无论 state 什么时候更新,组件都将同步更新

环境安装

这个应用将使用 webpack 来做模块打包,处理和热重启。使用 Vue 官方提供的脚手架 vue-cli。

安装 vue-cli

npm install -g vue-cli

注:Node.js >= 4.x, 5.x 最好

初始化应用

vue init webpack vue-notes-app
cd vue-notes-app
npm install // 安装依赖包
npm run dev // 启动服务

初始化一个项目名为vue-notes-app的应用,并选择使用 webpack 打包方式。在命令行中按照提示选择初始化配置项。其中在选择 JSLint 校验的时候,推荐选择 AirBNB 规范。

使用你最喜欢的编辑器打开我们刚刚新建的项目,项目的结构大概如下图:

  • components/ 文件夹用来存放我们的 Vue 组件

  • vuex/ 文件夹存放的是和 Vuex store 相关的东西(state object,actions,mutators)

  • build/ 文件是 webpack 的打包编译配置文件

  • config/ 文件夹存放的是一些配置项,比如我们服务器访问的端口配置等

  • dist/ 该文件夹一开始是不存在,在我们的项目经过 build 之后才会产出

  • App.vue 根组件,所有的子组件都将在这里被引用

  • index.html 整个项目的入口文件,将会引用我们的根组件 App.vue

  • main.js 入口文件的 js 逻辑,在 webpack 打包之后将被注入到 index.html 中

功能模块

  • 新增笔记,新增一篇笔记,编辑区显示空的笔记内容

  • 删除笔记,删除一篇笔记之后,编辑区域显示当前笔记类别的第一项

  • 笔记列表切换,分为全部笔记和收藏笔记两种,在切换之后,编辑区域显示当前列表的第一条笔记

  • 收藏笔记,给当前激活的笔记打上收藏的标签

项目组件划分

在这个项目中,我们将总共使用四个组件:根组件 App.vue,操作栏组件 Toolbar.vue,别表组件 NotesList.vue,笔记编辑组件 Editor.vue。

创建 Vuex Store

按照上面我们列出来的功能模块,我们在 Vuex/ 下面建立一个 store.js 文件。

import Vue from 'vue';
import Vuex from 'vuex';Vue.use(Vuex);// 需要维护的状态
const state = {notes: [],activeNote: {},show: '' }; const mutations = { // 初始化 state INIT_STORE(state, data) { state.notes = data.notes, state.show = data.show; state.activeNote = data.activeNote; }, // 新增笔记 NEW_NOTE(state) { var newNote = { id: +new Date(), title: '', content: '', favorite: false }; state.notes.push(newNote); state.activeNote = newNote; }, // 修改笔记 EDIT_NOTE(state, note) { state.activeNote = note; // 修改原始数据 for (var i = 0; i < state.notes.length; i++) { if(state.notes[i].id === note.id){ state.notes[i] = note; break; } }; }, // 删除笔记 DELETE_NOTE(state) { state.notes.$remove(state.activeNote); state.activeNote = state.notes[0] || {}; }, // 切换笔记的收藏与取消收藏 TOGGLE_FAVORITE(state) { state.activeNote.favorite = !state.activeNote.favorite; }, // 切换显示数据列表类型:全部 or 收藏 SET_SHOW_ALL(state, show){ state.show = show; // 切换数据展示,需要同步更新 activeNote if(show === 'favorite'){ state.activeNote = state.notes.filter(note => note.favorite)[0] || {}; }else{ state.activeNote = state.notes[0] || {}; } }, // 设置当前激活的笔记 SET_ACTIVE_NOTE(state, note) { state.activeNote = note; } }; export default new Vuex.Store({ state, mutations });

创建 Vuex Actions

在 Vuex/ 下面建立一个 action.js,用来给组件使用的函数。

function makeAction(type) {return ({ dispatch }, ...args) => dispatch(type, ...args); }; const initNote = { id: +new Date(), title: '我的笔记', content: '第一篇笔记内容', favorite: false }; // 模拟初始化数据 const initData = { show: 'all', notes: [initNote], activeNote: initNote }; export const initStore = ({ dispatch }) => { dispatch('INIT_STORE', initData); }; // 更新当前activeNote对象 export const updateActiveNote = makeAction('SET_ACTIVE_NOTE'); // 添加一个note对象 export const newNote = makeAction('NEW_NOTE'); // 删除一个note对象 export const deleteNote = makeAction('DELETE_NOTE'); export const toggleFavorite = makeAction('TOGGLE_FAVORITE'); export const editNote = makeAction('EDIT_NOTE'); // 更新列表展示 export const updateShow = makeAction('SET_SHOW_ALL');

创建 Vuex Getters

在 vuex/ 下面建立一个 getter.js 文件,用来从 store 获取数据。

// 获取 noteList,这里将会根据 state.show 的状态做数据过滤
export const filteredNotes = (state) => {if(state.show === 'all'){return state.notes || {}; }else if(state.show === 'favorite'){ return state.notes.filter(note => note.favorite) || {}; } }; // 获取列表展示状态 : all or favorite export const show = (state) => { return state.show; }; // 获取当前激活 note export const activeNote = (state) => { return state.activeNote; };

以上就是我们 Vuex 的所有逻辑了,在定下了我们需要完成的功能之后,接下来就是只需要在组件中去调用 action 来实现对应的功能了。

路由配置

在这里我们将使用 vue-router 来做路由,引用 bootstrap 样式。

index.html

<!DOCTYPE html>
<html><head> <meta charset="utf-8"> <title>vuex-notes-app</title> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css"> </head> <body> <div id="app"></div> <!-- built files will be auto injected --> </body> </html>

所有的入口逻辑我们都将在 main.js 中编写

main.js

import Vue from 'vue';
import App from './App';import VueRouter from 'vue-router';
import VueResource from 'vue-resource';// 路由模块和HTTP模块 Vue.use(VueResource); Vue.use(VueRouter); const router = new VueRouter(); router.map({ '/index': { component: App } }); router.redirect({ '*': '/index' }); router.start(App, '#app');

根组件 App.vue

<template><div id="app" class="app"> <toolbar></toolbar> <notes-list></notes-list> <editor></editor> </div> </template> <style> html, #app { height: 100%; } body { margin: 0; padding: 0; border: 0; height: 100%; max-height: 100%; position: relative; } </style> <script> import Toolbar from './components/Toolbar'; import NotesList from './components/NotesList'; import Editor from './components/Editor'; import store from './vuex/store'; import { initStore } from './vuex/actions'; export default { components: { Toolbar, NotesList, Editor }, store, vuex: { actions: { initStore } }, ready() { this.initStore() } } </script> 

在根组件中引用了三个子组件:Toolbar.vue, NotesList.vue, Editor.vue。

注意:我们在配置里面加入了 vuex 这么一个选项,这里用来将我们 action 里面定义的方法给暴露出来,我们在根组件中只做了一件事情,那就是初始化模拟数据,因此我们在组件生命周期的 ready 阶段调用了 actions 里面的 initStore 来初始化我们的 store 里面的 state

Toolbar.vue

<template><div id="toolbar"> <i class="glyphicon logo"><img src="../assets/logo.png" width="30" height="30"></i> <i @click="newNote" class="glyphicon glyphicon-plus"></i> <i @click="toggleFavorite" class="glyphicon glyphicon-star" :class="{starred: activeNote.favorite}"></i> <i @click="deleteNote" class="glyphicon glyphicon-remove"></i> </div> </template> <script> import { newNote, deleteNote, toggleFavorite } from '../vuex/actions'; import { activeNote } from '../vuex/getters'; export default { vuex: { getters: { activeNote }, actions: { newNote, deleteNote, toggleFavorite } } } </script> <style lang="scss" scoped> #toolbar{ float: left; width: 80px; height: 100%; background-color: #30414D; color: #767676; padding: 35px 25px 25px 25px; .starred { color: #F7AE4F; } i{ font-size: 30px; margin-bottom: 35px; cursor: pointer; opacity: 0.8; transition: opacity 0.5s ease; &:hover{ opacity: 1; } } } </style>

在这里,我们用到了 Vuex 的一个案例就是我们需要知道当前的激活的笔记是否是收藏类别的,如果是,我们需要高亮收藏按钮,那么如何知道呢?那就是通过 vuex 里面的 getters 获取当前激活的笔记对象,判断它的 favorite 是否为 true。

始终牢记一个概念,vuex 中数据是单向的,只能从 store 获取,而我们这个例子中的 activeNote 也是始终都在 store.js 中维护的,这样子就可以给其他组件公用了

// 需要维护的状态
const state = {notes: [],activeNote: {},show: ''
};

NotesList.vue

<template><div id="notes-list"> <div id="list-header"> <h2>Notes | heavenru.com</h2> <div class="btn-group btn-group-justified" role="group"> <!-- all --> <div class="btn-group" role="group"> <button type="button" class="btn btn-default" @click="toggleShow('all')" :class="{active: show === 'all'}">All Notes</button> </div> <!-- favorites --> <div class="btn-group" role="group"> <button type="button" class="btn btn-default" @click="toggleShow('favorite')" :class="{active: show === 'favorite'}">Favorites</button> </div> </div> </div> <!-- 渲染笔记列表 --> <div class="container"> <div class="list-group"> <a v-for="note in filteredNotes" class="list-group-item" href="#" :class="{active: activeNote === note}" @click="updateActiveNote(note)"> <h4 class="list-group-item-heading"> {{note.title.trim().substring(0,30)}} </h4> </a> </div> </div> </div> </template> <script> import { updateActiveNote, updateShow } from '../vuex/actions'; import { show, filteredNotes, activeNote } from '../vuex/getters'; export default { vuex: { getters: { show, filteredNotes, activeNote }, actions: { updateActiveNote, updateShow } }, methods: { toggleShow(show) { this.updateShow(show); } } } </script>

笔记列表组件,主要有三个操作

  • 渲染笔记

  • 切换渲染笔记

  • 点击列表 title,切换 activeNote

我们通过 getters 中的 filteredNotes 方法获取笔记列表

// 获取 noteList,这里将会根据 state.show 的状态做数据过滤
export const filteredNotes = (state) => {if(state.show === 'all'){return state.notes || {}; }else if(state.show === 'favorite'){ return state.notes.filter(note => note.favorite) || {}; } };

可以看到,我们获取的列表是依赖于 state.show 这个状态的。而我们的切换列表操作恰好就是调用 actions 里面的方法来更新 state.show,这样一来,实现了数据列表的动态刷新,而且我们对树的操作都是通过调用 actions 的方法来实现的。

我们再看,在切换列表的时候,我们还需要动态的更新 activeNote。看看我们在 store.js 中是如何做的:

// 切换显示数据列表类型:全部 or 收藏
SET_SHOW_ALL(state, show){state.show = show;// 切换数据展示,需要同步更新 activeNoteif(show === 'favorite'){state.activeNote = state.notes.filter(note => note.favorite)[0] || {}; }else{ state.activeNote = state.notes[0] || {}; } }

触发这些操作的是我们给两个按钮分别绑定了我们自定义的函数,通过给函数传入不同的参数,然后调用 actions 里面的方法,来实现对数据的过滤,更新。

Editor.vue

<template><div id="note-editor"> <div class="form-group"> <input type="text" name="title" class="title form-control" placeholder="请输入标题" @input="updateNote" v-model="currentNote.title"> <textarea v-model="currentNote.content" name="content" class="form-control" row="3" placeholder="请输入正文" @input="updateNote"></textarea> </div> </div> </template> <script> import { editNote } from '../vuex/actions'; import { activeNote } from '../vuex/getters'; export default { vuex: { getters: { activeNote }, actions: { editNote } }, computed: { // 通过计算属性得到的一个对象,这样子我们就能愉快的使用 v-model 了 currentNote: activeNote }, methods: { // 为什么这么做? 因为在严格模式中不允许直接在模板层面去修改 state 中的值 updateNote() { this.editNote(this.currentNote); } } } </script>

在 Editor.vue 组件中,我们需要能够实时的更新当前的 activeNote 组件和列表中对应的我们正在修改的笔记对象的内容。

由于我们前面提到过,在组件中是不允许直接修改 store.js在里面的状态值的,所以在这里的时候,我们通过一个计算属性,将 store 里面的状态值赋值给一个对象,然后在自定义的 updateNotes() 方法中,去调用 action,同时传入 currentNote 对象。

在 store.js 中,我们是这么做的,找到对应的 id 的对象,重新赋值,因为前面提到过,我们的数据是响应式的,在这里进行了改变,对应的视图也将刷新改变,这样一来就实现了实时编辑,实时渲染的功能了。

// 修改笔记
EDIT_NOTE(state, note) {state.activeNote = note;// 修改原始数据for (var i = 0; i < state.notes.length; i++) { if(state.notes[i].id === note.id){ state.notes[i] = note; break; } }; },

Q&A

在这个项目中,我们并没有引入 vue-resource 插件,只是自己模拟了部分的数据,有兴趣的同学可以自己去试试。

由于我们的例子相对简单,没有涉及到很深入的东西,更深层次的研究需要大家花更多的时间去实践了。

最后,再说一句,在 action 里面,我们其实可以做的还有更多,比如根据 id 动态的异步获取笔记内容等等,这些有兴趣的同学可以自己去尝试,一点点的丰富这个例子。

转载于:https://www.cnblogs.com/benmumu/p/9025047.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/398000.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

laravel mysql 配置,laravel5数据库配置及其注意事项

今天分享一个Laravel5数据库配置上的坑。Laravel5作为一套简洁、优雅的PHP Web开发框架(笑)&#xff0c;唯一不足的一点就是中文手册或者说是资料比较少&#xff0c;虽然现在很多大神也开始普及这些东西&#xff0c;但是大神一遍也会忽略一下小坑。今天配置了一下数据库&#x…

React开发中常用的工具集锦

本文从属于笔者的React入门与最佳实践系列。本文记录了笔者在React开发中常见的一些工具插件&#xff0c;如果你想寻找合适的项目生成器或者模板&#xff0c;请参考笔者的使用Facebook的create-react-app快速构建React开发环境 React Devtools React Devtools是React官方提供的…

9-[记录操作]--数据的增删改,权限管理

1、数据操作语言&#xff1a; DML&#xff08;data manage language&#xff09; 在MySQL管理软件中&#xff0c;可以通过SQL语句中的DML语言来实现数据的操作&#xff0c;包括 使用INSERT实现数据的插入UPDATE实现数据的更新使用DELETE实现数据的删除使用SELECT查询数据以及。…

hdu 1023 Train Problem II

题目连接 http://acm.hdu.edu.cn/showproblem.php?pid1212 Train Problem II Description As we all know the Train Problem I, the boss of the Ignatius Train Station want to know if all the trains come in strict-increasing order, how many orders that all the tra…

Nginx自建CDN加速节点 实现DNS智能解析网站项目

如今&#xff0c;网站项目越来越多的会使用CDN加速&#xff0c;如果需要便捷一点的可以直接用第三方提供的CDN加速服务&#xff0c;比如百度CDN、七牛、又拍云、腾讯云、阿里云等等服务商都有提供这类服务。但是前提条件是需要一定的成本&#xff0c;以及网站域名是需要BA才可以…

matlab中获取view,ios 怎么获取一个view的位置

ios 怎么获取一个view的位置以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;ios 怎么获取一个view的位置打开appstore进入应用&#xff0c;右上角的分享按钮(从右往左数第二个)&#xff0c;拷…

eureka需要替换吗_Spring Cloud Alibaba迁移指南1:零代码从Eureka迁移到Nacos

作者&#xff1a;得少&#xff0c;校对&#xff1a;周立。在本号首发&#xff0c;欢迎转载。Spring Cloud官方宣布Spring Cloud Netflix进入维护状态&#xff0c;后续不再会有新的功能已成为事实。作为开发者&#xff0c;如何使用极简的方式替换Netflix相关组件成为首要解决的问…

JAVA TCP通信练习

2019独角兽企业重金招聘Python工程师标准>>> 1、Server端 package com.hhdys.serviceimpl;import java.io.*; import java.net.ServerSocket; import java.net.Socket; import java.util.ArrayList; import java.util.List; import java.util.Scanner; import java.…

微服务架构下的身份认证

从单体应用架构到分布式应用架构再到微服务架构&#xff0c;应用的安全访问在不断的经受考验。为了适应架构的变化、需求的变化&#xff0c;身份认证与鉴权方案也在不断的变革。面对数十个甚至上百个微服务之间的调用&#xff0c;如何保证高效安全的身份认证&#xff1f;面对外…

php缓存注入,利用Thinkphp 5缓存漏洞实现前台Getshell

原标题&#xff1a;利用Thinkphp 5缓存漏洞实现前台Getshell*本文原创作者&#xff1a;WindWing&#xff0c;属于FreeBuf原创奖励计划&#xff0c;禁止转载000 背景网站为了实现加速访问&#xff0c;会将用户访问过的页面存入缓存来减小数据库查询的开销。而Thinkphp5框架的缓存…

mac版小达人点读包怎么安装_小达人点读笔扩容实战:16G变128G

随着小达人点读笔可以点读的童书越来越多&#xff0c;笔的容量就是个尴尬的问题&#xff1a;是处理掉手头的16G容量的旧点读笔&#xff0c;重新再买32G容量的新点记笔吗&#xff1f;如果32G容量也不够用怎么办&#xff1f;官方可没有更大容量的点读笔了。删除原先的点读内容&am…

WorldWind源码剖析系列:可渲染对象类RenderableObject

RenderableObject是WorldWind中所有需要渲染的对象的父类&#xff0c;继承了接口IRenderable和Icomparable。其派生类体系如下所示。RenderableObject的成员如下所示。 RenderableObjectList也继承自RenderableObject。 接口Irenderable内部仅仅定义下面三个接口&#xff0c;分…

php table 下拉框,LayerUI的table 里面加 select 下拉框 – Fly社区-Mikel

先上效果图&#xff1a;直接上代码//添加样式&#xff1a;.table-select-icon{position:absolute;right:10px;line-height:34px;color:#d3d3d3}.table-select-selected dl{display:block}.table-select dl{position:absolute;left:0;padding:5px 0;z-index:999;min-width:100%;…

家里wifi网速越来越慢_家里的wifi信号不好?有了它再也不怕网速慢啦

夏天太热&#xff0c;就想宅在家里刷刷剧、打打游戏、看看新闻。可是网速却不好&#xff0c;看一会儿&#xff0c;卡一会儿&#xff0c;本来天气就热&#xff0c;这样更加烦躁&#xff01;这WiFi信号怎么会这么差&#xff1f;有什么好办法能解决这个问题呢&#xff1f;只要有了…

python文件操作实验总结,[干货分享]Python文件操作技巧总结

引言小伙伴们&#xff0c;今天给大家讲解一下python程序下的文件操作完整技巧梳理&#xff0c;都是笔者总结的干货。通过本文阅读&#xff0c;不仅可以带来生产力提升&#xff0c;也可以开发小脚本为生活带来乐趣。首先不知道大家对python有没有了解&#xff0c;不太会用的小伙…

7价 半导体掺杂_天津大学封伟团队:新型半导体二维原子晶体锗硅烷材料的带隙调控...

xxxxxxxxxxxx【研究背景】xxxx新型二维半导体原子晶体兼具原子级厚度、纳米级层状结构、极高的载流子迁移率&#xff0c;是构建未来高性能纳米光电器件的核心材料。带隙是二维半导体电子器件和光电子光器件中最重要的基本参数之一&#xff0c;是影响二维半导体电子器件开关比和…

vs2017怎么安装python包,vs2017安装pygame,vs2017安装python第三方包

vs2017有独立的python环境&#xff1b;所以想在vs2017开发python并使用第三方包&#xff0c;需要在vs2017中操作&#xff0c;完成第三方包的安装。一&#xff0c;查看vs2017有哪些版本的python&#xff0c;当前使用的是哪个版本工具--》Python--》Python环境可以看到vs2017包含…

基于opencv在摄像头ubuntu根据视频获取

&#xfeff;&#xfeff;基于opencv在摄像头ubuntu根据视频获取 1 工具 原料 平台 &#xff1a;UBUNTU12.04 安装库 Opencv-2.3 2 安装编译执行步骤 安装编译opencv-2.3 參考http://blog.csdn.net/xiabodan/article/details/23547847 提前下载OPENCV源代码包 官方&am…

Flask构建微电影(二)

第三章、项目分析、搭建目录及模型设计 3.1.前后台项目目录分析 微电影网站 前台模块后台模块前台&#xff08;home&#xff09; 数据模型&#xff1a;models.py表单处理&#xff1a;home/forms.py模板目录&#xff1a;templates/home后台&#xff08;admin&#xff09; 数据模…