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>&nbsp;{{item.viewCount}}&nbsp;&nbsp;
        <i class="el-icon-circle-check"></i>&nbsp;{{item.agreeCount}}&nbsp;&nbsp;
        <i class="el-icon-chat-dot-square"></i>&nbsp;{{item.discussCount}}&nbsp;
      </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>&nbsp;{{item.agreeCount}}&nbsp;&nbsp;
  </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