Let’s look at the design of the management class.
Should we separate out the JS code and make it into a separate management API?
This will make the code cleaner, mainly the setup code will not mess up.
Management class
import webSQLHelp from '.. /store/websql-help'
import { blog, blogForm, blogList, articleList, discuss, discussList } from './blogModel'
import blogStateManage from '.. /model/blogState'
// Connect to the database
const help = new webSQLHelp('vite2-blog'.1.0.'Blog Database for Testing')
/ / = = = = = = = = = = = = = = = = = = = = = database = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
/** * create vite2-blog database, blog table, discuss table *@returns Create databases and tables */
export const databaseInit = () = > {
help.createTable('blog', blog())
help.createTable('discuss', discuss())
}
/** * delete: blog table, discuss table *@returns Delete * / table
export const deleteBlogTable = () = > {
help.deleteTable('blog')
help.deleteTable('discuss')}/** * Blog management *@returns Add, modify, get lists, etc. */
export const blogManage = () = > {
/ / = = = = = = = = = = = = = = = = = = = = = post = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
/** * Add a new blog *@param { object } The model of blogging@return {*} Promise, the ID of the new post */
const addNewBlog = (blog) = > {
return new Promise((resolve, reject) = > {
const newBlog = {}
Object.assign(newBlog, blog, {
addTime: new Date(), // Add time
viewCount: 0./ / views
agreeCount: 0.// The number of likes
discussCount: 0 // Discuss quantity
})
help.insert('blog', newBlog).then((id) = > {
resolve(id)
})
})
}
/** * modify the blog *@param { object } The model of blogging@return {*} Promise, change the state */
const updateBlog = (blog) = > {
return new Promise((resolve, reject) = > {
help.update('blog', blog, blog.ID).then((state) = > {
resolve(state)
})
})
}
/** ** ** ** ** ** ** ** ** ** ** ** ** **@param { number } Id Blog id *@returns * /
const getArtcileById = (id) = > {
return new Promise((resolve, reject) = > {
help.getDataById('blog', id).then((data) = > {
if (data.length > 0) {
resolve(data[0])}else {
console.log('No record found', data)
resolve({})
}
})
})
}
/** * Get the list of posts according to the group ID. *@param {number} GroupId indicates the groupId *@returns * /
const getBlogListByGroupId = (groupId) = > {
return new Promise((resolve, reject) = > {
help.select('blog', articleList(), {groupId: [401, groupId]})
.then((data) = > {
resolve(data)
})
})
}
// State management
const { getBlogState } = blogStateManage()
const blogState = getBlogState()
/** ** ** ** * /** ** *@returns List of posts */
const getBlogList = () = > {
// Set query conditions and paging conditions according to the status
const _query = blogState.findQuery || {}
_query.state = [401.2] // Display published blog posts, set fixed query criteria
return new Promise((resolve, reject) = > {
help.select('blog', blogList(), _query, blogState.page).then((data) = > {
resolve(data)
})
})
}
const getBlogCount = () = > {
// Set query conditions and paging conditions according to the status
const _query = blogState.findQuery || {}
_query.state = [401.2] // Display published blog posts, set fixed query criteria
return new Promise((resolve, reject) = > {
help.getCountByWhere('blog', _query).then((count) = > {
resolve(count)
})
})
}
/ / = = = = = = = = = = = = = = = = = = = = = discuss = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
/** * Add a new discussion *@param {object}} Model * for discuss@returns * /
const addDiuss = (discuss) = > {
return new Promise((resolve, reject) = > {
const newDiscuss = {}
Object.assign(newDiscuss, discuss, {
addTime: new Date(), // Add time
agreeCount: 0 // The number of likes
})
help.insert('discuss', newDiscuss).then((id) = > {
resolve(id)
})
})
}
/** * Get the discussion list based on the blog ID. *@param {number} BlogId Group ID *@returns * /
const getDiscussListByBlogId = (blogId) = > {
return new Promise((resolve, reject) = > {
help.select('discuss', discussList(), {blogId: [401, blogId]})
.then((data) = > {
resolve(data)
})
})
}
return {
addDiuss, // Add a new discussion
getDiscussListByBlogId, // Get the discussion list based on the blog ID.
addNewBlog, // Add a new blog post
updateBlog, // Modify the blog post
getArtcileById, // Get the blog by the blog ID
getBlogListByGroupId, // Get a list of blog posts
getBlogList, // Get a list of blog posts
getBlogCount // Count the quantity}}Copy the code
In fact, it should be divided into two classes, one for blog posts, one for discussion, and in the future there could be groups of management classes. Now, since there are only two functions that are related, there is no separation.
The required functions are centralized, easy to manage and reuse, reduce the code inside the component, but also easy to upgrade the code replacement. For example, now the data is stored in the front-end webSQL, so how to submit to the back-end? You just need to change the code here, you don’t need to change the code in xxx.vue. Keep changes to a minimum.
coding
After the design is ready to start coding, first look at the file structure:
File structure
My personal feeling is still quite clear.
The config Settings
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
base: '/vue3-blog/'.// Modify the directory of the published website
build: {
outDir: 'blog' // Modify the packaged default folder}})Copy the code
- Base, which sets the directory to publish the website.
The default project is deployed at the site root when published, and if it is not, you can use base to change it.
- build.outDir
Modify the build output path for the default (dist).
Other Settings can be seen here: cn.vitejs.dev/config/, the content is not…
Routing setting
src/router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import Home from '.. /views/home.vue'
const routes = [
{
path: '/'.name: 'home'.component: Home
},
{
path: '/write'.name: 'write'.component: () = > import('.. /views/write.vue')}, {path: '/blogs/:id'.name: 'blogs'.props: true.component: () = > import('.. /views/blog.vue')}, {path: '/groups/:groupId'.name: 'groups'.props: true.component: Home
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
export default router
Copy the code
Nothing has changed except that the createWebHistory parameter is removed. Routing Settings are also very simple, only home page, write blog, blog details, group display blog these four.
Web portal
/index.html
<! DOCTYPEhtml>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="Width = device - width, initial - scale = 1.0" />
<title>Vite2 + vue3 do simple personal blog</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>
Copy the code
Very succinctly, we can set a title and load the entry JS file with type=”module”. Others can be set up as needed.
Code entry
/src/main.js
import { createApp, provide, reactive } from 'vue'
import App from './App.vue'
import router from './router' / / routing
/ / UI library
import ElementPlus from 'element-plus'
import 'element-plus/lib/theme-chalk/index.css'
import 'dayjs/locale/zh-cn'
import locale from 'element-plus/lib/locale/lang/zh-cn'
// Markdown edit plugin
import VueMarkdownEditor from '@kangc/v-md-editor'
import '@kangc/v-md-editor/lib/style/base-editor.css'
import vuepressTheme from '@kangc/v-md-editor/lib/theme/vuepress.js'
import '@kangc/v-md-editor/lib/theme/style/vuepress.css'
VueMarkdownEditor.use(vuepressTheme)
// Markdown display plugin
import VMdPreview from '@kangc/v-md-editor/lib/preview'
import '@kangc/v-md-editor/lib/style/preview.css'
The github theme is used as an example
// import githubTheme from '@kangc/v-md-editor/lib/theme/github'
VMdPreview.use(vuepressTheme)
// Create a database
import { databaseInit, deleteBlogTable } from './model/blogManage'
// deleteBlogTable()
databaseInit()
// Injection state
import { blogState } from './model/blogState'
const state = reactive(blogState)
createApp(App)
.provide('blogState', state) // Injection state
.use(router) / / routing
.use(ElementPlus, { locale, size: 'small' }) / / UI library
.use(VueMarkdownEditor) // markDown editor
.use(VMdPreview) / / markDown shows
.mount('#app')
Copy the code
The code here is a bit long, and in addition to the general operations, MarkdownEditor is used to edit the blog post, which is a bit too much code.
Then I added code to design a webSQL database and my own easy state management with Provide.
Home page, blog list
Template section:
<template>
<! -- List of posts -->
<el-row :gutter="12">
<el-col :span="5">
<! Grouping -- - >
<blogGroup :isDetail="true"/>
</el-col>
<el-col :span="18">
<el-card shadow="hover"
v-for="(item, index) in blogList"
:key="'bloglist_' + index"
>
<template #header>
<div class="card-header">
<router-link :to="{name:'blogs', params:{id:item.ID}}">
{{item.title}}
</router-link>
<span class="button">({{dateFormat(item.addTime).format('YYYY-MM-DD')}})</span>
</div>
</template>
<! - introduction -- - >
<div class="text item" v-html="item.introduction"></div>
<hr>
<i class="el-icon-view"></i> {{item.viewCount}}
<i class="el-icon-circle-check"></i> {{item.agreeCount}}
<i class="el-icon-chat-dot-square"></i> {{item.discussCount}}
</el-card>
<! -- no data found -->
<el-empty description="No bowen." v-if="blogList.length === 0"></el-empty>
<el-pagination
background
layout="prev, pager, next"
v-model:currentPage="blogState.page.pageIndex"
:page-size="blogState.page.pageSize"
:total="blogState.page.pageTotal">
</el-pagination>
</el-col>
</el-row>
</template>
Copy the code
The template remains the same, with a simple layout using el-Row:
- On the left, the blogGroup shows the grouped components.
- On the right, a list of posts is made with el-Card.
- Next, use el-Pagination to implement paging.
Code part:
<script setup>
import { watch, reactive } from 'vue'
import { useRoute } from 'vue-router'
import blogGroup from '.. /components/blog-group.vue'
import blogStateManage from '.. /model/blogState'
import { blogManage } from '.. /model/blogManage'
// Format the date
const dateFormat = dayjs
// Blog management
const { getBlogList, getBlogCount } = blogManage()
// State management
const { getBlogState } = blogStateManage()
// The status of the blog post
const blogState = getBlogState()
// List of posts
constBlogList = reactive([])/** * Display blog list by home page, group, query. * Displays the first page and counts the total number of records */
const showBlog = () = > {
/ / group ID
let groupId = blogState.currentGroupId
if (groupId === 0) {
// The first page is displayed
blogState.findQuery = {}
blogState.page.pageIndex = 1
} else {
// List of grouped blog posts, set grouping conditions, display the first page
blogState.findQuery = {
groupId: [401, groupId]
}
blogState.page.pageIndex = 1
}
// Count the total number of records that match the conditions
getBlogCount().then((count) = > {
blogState.page.pageTotal = count
})
// Get the data from the first page
getBlogList().then((data) = > {
blogList.length = 0blogList.push(... data) }) }const route = useRoute()
// If it is the home page, set the current group ID to 0, so that all group posts can be displayed.
watch(() = > route.fullPath, () = > {
if (route.fullPath === '/' || route.fullPath === '/blog') {
blogState.currentGroupId = 0}})// Monitor the ID of the selected group
watch(() = > blogState.currentGroupId, () = > {
showBlog()
})
// Listen for changes in the page number to display the list of blog posts by page number
watch(() = > blogState.page.pageIndex, () = > {
getBlogList().then((data) = > {
blogList.length = 0blogList.push(... data) }) })// Execute it once by default
showBlog()
Copy the code
The code is a little long, so what does that tell you? There is room for improvement.
- script setup
Export default {setup (props, CTX) {}} is supported on vite2. Of course vue-CLI setup projects also support script Setup. So which one to use is a matter of personal preference.
Script Setup is simpler and saves a lot of “hassle”, such as component import, import, and no need to register again. Const; / / const; / / const; / / const;
- All kinds of js class
With this “scattered” approach, you have to write various separate JS files to implement the basic functionality and then integrate it into Setup, otherwise setup becomes unreadable.
- Watch etc.
The use of watch, ref, and reactive has not changed.
Take a look at the results:
Back-end origin, not CSS, there is no art cells so ugly, but also hope understanding
Form post
Here is a reference to the “Jane book” editing, personal feeling or very convenient, the left side is the grouping directory, the middle of the selection of the grouping of the list of blog posts, the right side is the area of editing blog posts.
<template>
<el-row :gutter="12">
<el-col :span="4">
<! Grouping -- - >
<blogGroup/>
</el-col>
<el-col :span="5">
<! -- Title list -->
<blogArticle/>
</el-col>
<el-col :span="14">
<! -- Write a blog post -->
<el-input
style="width:90%"
:show-word-limit="true"
maxlength="100"
placeholder="Please enter the blog title, 100 words Max."
v-model="blogModel.title"
/>
<el-button type="primary" plain @click="submit">Published articles</el-button>
{{dateFormat(blogModel.addTime).format('YYYY-MM-DD HH:mm:ss')}}
<v-md-editor
:include-level="[1, 2, 3, 4]"
v-model="blogModel.concent" :height="editHeight+'px'"></v-md-editor>1
</el-col>
</el-row>
</template>
Copy the code
- blogGroup
The component of blog groups displays a list of groups for us to select.
- blogArticle
List of blog posts, select group, display the list of blog posts in the group. Here you can add the blog, click the title of the blog, you can load the form on the right side of the blog, blog editing.
After using the editing mode of Jane book, I feel this is still very convenient.
Code part:
[Omitted code introduced]/ / component
import blogGroup from '.. /components/blog-group.vue'
import blogArticle from '.. /components/blog-article.vue'
// Visible height
const editHeight = document.documentElement.clientHeight - 200
/ / management
const { updateBlog, getArtcileById } = blogManage()
// The form's model
const blogModel = reactive(blogForm())
// Monitor the ID of the edited article
watch(() = > blogState.editArticleId, (v1, v2) = > {
getArtcileById(v1).then((data) = > {
Object.assign(blogModel, data)
})
})
// Publish articles
const submit = () = > {
blogModel.ID = blogState.editArticleId
blogModel.state = 2 // Change the state to published
updateBlog(blogModel).then((id) = > {
// Notification list})}Copy the code
- watch(() => blogState.editArticleId
Listen for the blog ID to be edited, and then load the blog data binding form. After editing, use Submit to publish the blog.
There is also a need for an auto-save draft feature, which will be improved later.
- submit
Posting a blog post is actually modifying the blog post, because the added work is done inside the blogArticle component.
- updateBlog
Call management class in the way to achieve the function of publishing blog posts.
I have also experienced the Posting methods of various platforms, and I still like this way, so personal blogs also use this way to achieve the function of editing blog posts.
Take a look at the results:
Directory navigation:
V-md-editor provides directory navigation function, or very powerful, looking at the outline writing, thinking much clearer.
Blog content + discussion
<template>
<el-row :gutter="12">
<el-col :span="5">
<! Grouping -- - >
<blogGroup :isDetail="true"/>
</el-col>
<el-col :span="18">
<! -- Display blog post -->
<h1>{{blogInfo.title}}</h1>
({{dateFormat(blogInfo.addTime).format('YYYY-MM-DD')}})
<v-md-preview :text="blogInfo.concent"></v-md-preview>
<hr>
<! Discussion list -->
<discussList :id="id"/>
<! Discuss the form -->
<discussForm :id="id"/>
</el-col>
</el-row>
</template>
Copy the code
[Omitted code introduced]// Component properties, blog ID
const props = defineProps({
id: String
})
/ / management
const { getArtcileById } = blogManage()
// The form's model
const blogInfo = reactive({})
getArtcileById(props.id).then((data) = > {
Object.assign(blogInfo, data)
})
Copy the code
This code is very simple, because only the implementation of the basic post discussion and display discussion function, other temporarily omitted.
Look at the results:
Well, this discussion is quite perfunctory. There are actually a lot of ideas, but the space is limited. I will introduce them later.
Component-level code
Although in VUE, in addition to JS files, is vUE files, but I think it should be subdivided. For example, this is page-level code, and this is “component” level code.
Post group
Groups of blog posts mentioned many times.
<template>
<! -- Group into display state and edit state -->
<el-card shadow="hover"
v-for="(item, index) in blogGroupList"
:key="'grouplist_' + index"
>
<template #header>
<div class="card-header">
<span>{{item.label}}</span>
<span class="button"></span>
</div>
</template>
<div
class="text item"
style="cursor:pointer"
v-for="(item, index) in item.children"
:key="'group_' + index"
@click="changeGroup(item.value)"
>
{{item.label}}
</div>
</el-card>
</template>
Copy the code
Temporarily use el-Card to achieve, later will be changed to NavMenu to achieve.
[Omitted code introduced]// Component properties
const props = defineProps({
isDetail: Boolean
})
/** * Subgroup list of posts */
const blogGroupList = reactive([
{
value: '1000'.label: 'front end'.children: [{value: '1001'.label: 'VUE Basics'}, {value: '1002'.label: 'the vue components'}, {value: '1003'.label: 'the vue routing',}]}, {value: '2000'.label: 'back-end'.children: [{value: '2001'.label: 'MySQL'}, {value: '2002'.label: 'web services',}]}])// Select a group
const { setCurrentGroupId } = blogStateManage()
const router = useRouter()
const changeGroup = (id) = > {
setCurrentGroupId(id)
// Determine whether to jump
// Home page, edit page do not jump, blog details page adjustment
if (props.isDetail) {
// Jump to the list page
router.push({ name: 'groups'.params: { groupId: id }})
}
}
Copy the code
Group data is temporarily written dead, not done in a way that can be maintained, to improve later.
List of blog posts for editing
<template>
<! -- Add title -->
<el-card shadow="hover">
<template #header>
<div class="card-header">
<el-button @click="addNewArticle" >Adding new articles</el-button>
<span class="button"></span>
</div>
</template>
<div
class="text item"
style="cursor:pointer"
v-for="(item, index) in blogList"
:key="'article_' + index"
@click="changeArticle(item.ID)"
>{{item. ID}}, {{item. The title}}, {{dateFormat (item. AddTime). The format (' YYYY - MM - DD)}})</div>
<el-empty description="There are no articles in this category yet." v-if="blogList.length === 0"></el-empty>
</el-card>
</template>
Copy the code
Use el-card to make a list, above is the button to add blog posts, below is the list of blog posts, click to modify.
[Omitted code introduced]// List of posts
const blogList = reactive([])
// Blog management
const { addNewBlog, getBlogListByGroupId } = blogManage()
// State management
const { getBlogState, setEditArticleId } = blogStateManage()
// The status of the blog post
const blogState = getBlogState()
// Update the list
const load = () = > {
getBlogListByGroupId(blogState.currentGroupId).then((data) = > {
blogList.length = 0blogList.push(... data) }) } load()// Monitor the ID of the selected group
watch(() = > blogState.currentGroupId, () = > {
load()
})
// Add new article, title only, time only
const addNewArticle = () = > {
const newArticle = blogForm()
// Select the group ID
newArticle.groupId = blogState.currentGroupId
// Use the date as the default title
newArticle.title = dayjs(new Date()).format('YYYY-MM-DD')
addNewBlog(newArticle).then((id) = > {
// Set the ID of the article to edit
setEditArticleId(id)
// Notification list
newArticle.ID = id
blogList.unshift(newArticle)
})
}
// Select the article to edit
const changeArticle = (id) = > {
setEditArticleId(id)
}
Copy the code
Discussion list
<el-card shadow="hover"
v-for="(item, index) in discussList"
:key="'bloglist_' + index"
>
<template #header>
<div class="card-header">
{{item.discusser}}
<span class="button">({{dateFormat(item.addTime).format('YYYY-MM-DD')}})</span>
</div>
</template>
<! - introduction -- - >
<div class="text item" v-html="item.concent"></div>
<hr>
<i class="el-icon-circle-check"></i> {{item.agreeCount}}
</el-card>
<! -- no data found -->
<el-empty description="No discussion. Grab a couch." v-if="discussList.length === 0"></el-empty>
Copy the code
Again, use el-card as a list, and el-Empty as a hint without discussion.
[Omitted code introduced]// Component properties
const props = defineProps({
id: String
})
/ / management
const { getDiscussListByBlogId } = blogManage()
// Get the status
const { getBlogState } = blogStateManage()
const blogState = getBlogState()
// The form's model
const discussList = reactive([])
getDiscussListByBlogId(props.id).then((data) = >{ discussList.push(... data) }) watch(() = > blogState.isReloadDiussList, () = > {
getDiscussListByBlogId(props.id).then((data) = > {
discussList.length = 0discussList.push(... data) }) })Copy the code
Because the function is relatively simple, so the code is very simple, to obtain the discussion data binding display, there is no pagination function.
Discuss the form
<el-form
style="width:400px;"
label-position="top"
:model="dicussModel"
label-width="80px"
>
<el-form-item label="Nickname">
<el-input v-model="dicussModel.discusser"></el-input>
</el-form-item>
<el-form-item label="Content">
<el-input type="textarea" v-model="dicussModel.concent"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submit">Published to discuss</el-button>
<el-button>cancel</el-button>
</el-form-item>
</el-form>
Copy the code
Let’s make a form with el-Form.
[Omitted code introduced]// Component properties
const props = defineProps({
id: String
})
/ / management
const { addDiuss } = blogManage()
// Get the status
const { getBlogState, setReloadDiussList } = blogStateManage()
const blogState = getBlogState()
// The form's model
const dicussModel = reactive(discuss())
// Publish the discussion
const submit = () = > {
dicussModel.blogId = props.id // This is the blog ID
addDiuss(dicussModel).then((id) = > { // Think of it as an Axios commit
// Notification list
setReloadDiussList()
})
}
Copy the code
Divided into components, each component has very little code, which is easy to maintain. The blogManage function is used to submit data first, and the state management function is used in the callback function to remind the discussion list to refresh data.
The source code
Gitee.com/naturefw/vu…
The online demo
naturefw.gitee.io/vue3-blog