• 做个开源博客学习Vite2 + Vue3 (四)实现博客功能


    我们再来看一下管理类的设计。

    Composition API,就是组合API的意思,那么是不是应该把js代码分离出来,做成独立的管理类的形式呢?

    这样代码可以更整洁一些,主要是setup里面的代码就不会乱掉了。

    管理类

    import webSQLHelp from '../store/websql-help'
    import { blog, blogForm, blogList, articleList, discuss, discussList } from './blogModel'
    import blogStateManage from '../model/blogState'
    
    // 连接数据库 
    const help = new webSQLHelp('vite2-blog', 1.0, '测试用的博客数据库')
    
    // =====================数据库==============================
    /**
     * 建立 vite2-blog 数据库,blog表、discuss表
     * @returns 建立数据库和表
     */
    export const databaseInit = () => {
      help.createTable('blog', blog())
      help.createTable('discuss', discuss())
    }
    
    /**
     * 删除:blog表、discuss表
     * @returns 删除表
     */
    export const deleteBlogTable = () => {
      help.deleteTable('blog')
      help.deleteTable('discuss')
    }
     
    /**
     * 博客的管理类
     * @returns 添加、修改、获得列表等
     */
    export const blogManage = () => {
      // =====================博文==============================
      /**
       * 添加新的博文
       * @param { object } blog 博文 的 model
       * @return {*} promise,新博文的ID
       */
      const addNewBlog = (blog) => {
        return new Promise((resolve, reject) => {
          const newBlog = {}
          Object.assign(newBlog, blog, {
            addTime: new Date(), // 添加时间
            viewCount: 0, // 浏览量
            agreeCount: 0, // 点赞数量
            discussCount: 0 // 讨论数量
          })
    
          help.insert('blog', newBlog).then((id) => {
            resolve(id)
          })
        })
      }
    
      /**
       * 修改博文
       * @param { object } blog 博文 的 model
       * @return {*} promise,修改状态
       */
      const updateBlog = (blog) => {
        return new Promise((resolve, reject) => {
          help.update('blog', blog, blog.ID).then((state) => {
             resolve(state)
          })
        })
      }
    
      /**
       * 根据博文ID获取博文,编辑博文、显示博文用
       * @param { number } id 博文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('没有找到记录', data)
              resolve({})
            }
          })
        })
      }
    
      /**
       * 依据分组ID获取博文列表,编辑博文列表用。
       * @param {number} groupId 分组ID
       * @returns 
       */
      const getBlogListByGroupId = (groupId) => {
        return new Promise((resolve, reject) => {
          help.select('blog', articleList(), {groupId: [401, groupId]})
            .then((data) => {
              resolve(data)
            })
        })
      }
    
      // 状态管理
      const { getBlogState } = blogStateManage()
      const blogState = getBlogState()
    
      /**
       * 依据状态,分页查询博文
       * @returns 博文列表
       */
      const getBlogList = () => {
        // 根据状态设置查询条件和分页条件
        const _query = blogState.findQuery || {}
        _query.state = [401, 2] // 显示发布的博文,设置固定查询条件
     
        return new Promise((resolve, reject) => {
          help.select('blog', blogList(), _query, blogState.page).then((data) => {
            resolve(data)
          })
        })
      }
    
      const getBlogCount = () => {
        // 根据状态设置查询条件和分页条件
        const _query = blogState.findQuery || {}
        _query.state = [401, 2] // 显示发布的博文,设置固定查询条件
      
        return new Promise((resolve, reject) => {
          help.getCountByWhere('blog', _query).then((count) => {
            resolve(count)
          })
        })
      }
    
      // =====================讨论==============================
      /**
       * 添加一个新讨论
       * @param {object}} discuss 讨论的model
       * @returns 
       */
      const addDiuss = (discuss) => {
        return new Promise((resolve, reject) => {
          const newDiscuss = {}
          Object.assign(newDiscuss, discuss, {
            addTime: new Date(), // 添加时间
            agreeCount: 0 // 点赞数量
          })
    
          help.insert('discuss', newDiscuss).then((id) => {
            resolve(id)
          })
        })
      }
    
      /**
       * 依据博文ID获取讨论列表。
       * @param {number} blogId 分组ID
       * @returns 
       */
       const getDiscussListByBlogId = (blogId) => {
        return new Promise((resolve, reject) => {
          help.select('discuss', discussList(), {blogId: [401, blogId]})
            .then((data) => {
              resolve(data)
            })
        })
      }
      
      return {
        addDiuss, // 添加新讨论
        getDiscussListByBlogId, // 依据博文ID获取讨论列表。
        addNewBlog, // 添加 新博文
        updateBlog, // 修改博文
        getArtcileById, // 根据博文ID获取博文
        getBlogListByGroupId, // 获取博文列表
        getBlogList, // 获取博文列表
        getBlogCount // 统计数量
      }
    }
    

    其实应该分成两个类,一个是博文的管理类,一个是讨论的管理类,以后还可以有分组的管理类。
    现在因为讨论相关的只有两个函数,所以就没有分开。

    把需要的功能集中起来,便于管理和复用,减少组件里面的代码,也便于代码的升级更换。
    比如现在是把数据保存在前端的webSQL里面,那么以后要提交到后端怎么办?
    只需要在这里改代码即可,不需要修改xxx.vue里面的代码。
    把变化限制在最小的范围内

    编码

    设计好了之后可以动手编码了,先看一下文件结构:

    文件结构

    文件结构
    个人感觉还是比较清晰的。

    config设置

    import { defineConfig } from 'vite'
    import vue from '@vitejs/plugin-vue'
    
    // https://vitejs.dev/config/
    export default defineConfig({
      plugins: [vue()],
      base: '/vue3-blog/', // 修改发布网站的目录
      build: {
        outDir: 'blog' // 修改打包的默认文件夹
      }
    })
    
    • base,设置发布网站的目录。
      发布的时候默认项目会部署在网站根目录,如果不是根目录的话,可以使用 base 来更改。

    • build.outDir
      修改默认(dist)的构建输出路径。

    其他设置方式可以看这里:https://cn.vitejs.dev/config/,内容非常多。

    路由设置

    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
    

    除了 createWebHistory 的参数要去掉之外,没啥变化。
    路由设置也很简单,只有首页、编写博文、博文详细、分组显示博文这四项。

    网页入口

    /index.html

    <!DOCTYPE html>
    <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 做的简单的个人博客</title>
      </head>
      <body>
        <div id="app"></div>
        <script type="module" src="/src/main.js"></script>
      </body>
    </html>
    

    非常简洁,我们可以设置一个标题,用 type="module" 的方式加载入口js文件。其他的可以按照需要自行设置。

    代码入口

    /src/main.js

    import { createApp, provide, reactive } from 'vue'
    import App from './App.vue'
    import router from './router' // 路由
    
    // UI库
    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 编辑插件
    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 显示插件
    import VMdPreview from '@kangc/v-md-editor/lib/preview'
    import '@kangc/v-md-editor/lib/style/preview.css'
    // 引入你所使用的主题 此处以 github 主题为例
    // import githubTheme from '@kangc/v-md-editor/lib/theme/github'
    VMdPreview.use(vuepressTheme)
    
    // 建立数据库
    import { databaseInit, deleteBlogTable } from './model/blogManage'
    // deleteBlogTable()
    databaseInit()
    
    // 注入状态
    import { blogState } from './model/blogState'
    const state = reactive(blogState)
    
    createApp(App)
      .provide('blogState', state) // 注入状态
      .use(router) // 路由
      .use(ElementPlus, { locale, size: 'small' }) // UI库
      .use(VueMarkdownEditor) // markDown编辑器
      .use(VMdPreview) // markDown 显示
      .mount('#app')
    

    这里的代码稍微有点长,除了常规操作外,还使用了 MarkdownEditor 用于编辑博文,这个部分代码有点多。

    然后又加入了设计webSQL数据库的代码,以及自己用 provide 实现的简易的状态管理。

    首页、博文列表

    模板部分:

    <template>
      <!--博文列表-->
      <el-row :gutter="12">
        <el-col :span="5">
          <!--分组-->
          <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>
            <!--简介-->
            <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>
          <!--没有找到数据-->
          <el-empty description="没有找到博文呢。" 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>
    

    模板部分没啥变化,还是老样子,使用 el-row 做了一个简单的布局:

    • 左面,blogGroup 显示分组的组件。
    • 右面,用 el-card 做了一个列表,用于显示博文。
    • 下面,用 el-pagination 实现分页功能。

    代码部分:

    <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'
    
    // 日期格式化
    const dateFormat = dayjs
    
    // 博文管理
    const { getBlogList, getBlogCount } = blogManage()
    // 状态管理
    const { getBlogState } = blogStateManage()
    // 博文的状态
    const blogState = getBlogState()
    // 博文列表
    const blogList = reactive([])
    
    【后面就不写这些引入的代码了】
    
    /**
     * 按照首页、分组、查询显示博文列表。
     * 显示第一页,并且统计总记录数
     */
    const showBlog = () => {
      // 分组ID
      let groupId = blogState.currentGroupId
      if (groupId === 0) {
        // 首页,清空查询条件,显示第一页
        blogState.findQuery = {} 
        blogState.page.pageIndex = 1
      } else {
        // 分组的博文列表,设置分组条件,显示第一页
        blogState.findQuery = {
          groupId: [401, groupId]
        }
        blogState.page.pageIndex = 1
      }
      // 统计符合条件的总记录数
      getBlogCount().then((count) => {
        blogState.page.pageTotal = count
      })
      // 获取第一页的数据
      getBlogList().then((data) => {
        blogList.length = 0
        blogList.push(...data)
      })
    }
    
    const route = useRoute()
    // 如果是首页,把 当前分组ID设置为 0 ,以便于显示所有分组的博文。
    watch(() => route.fullPath, () => {
      if (route.fullPath === '/' || route.fullPath === '/blog') {
        blogState.currentGroupId = 0
      }
    })
    
    // 监控选择的分组的ID
    watch(() => blogState.currentGroupId, () => {
      showBlog()
    })
    
    // 监听页号的变化,按照页号显示博文列表
    watch(() => blogState.page.pageIndex, () => {
      getBlogList().then((data) => {
        blogList.length = 0
        blogList.push(...data)
      })
    })
    
    // 默认执行一遍
    showBlog()
    

    代码有点长,这说明了啥呢?还有优化的空间。

    • script setup
      vite2 建立的项目,默认推荐的是这种方式,其实 vite2 也是支持 export default { setup (props, ctx) { }} 这种写法的。
      当然 vue-cli 建立的项目也是支持 script setup 这种方式。所以用哪一种可以看个人喜好。

    script setup 更简洁,省去了好多“麻烦”,比如组件引入部分,import 就好,不需要再次注册了。
    const 后也不用 return 了,模板可以直接读取到。

    • 各种js类
      基于这种“散养”方式,所以必须写各种单独的js文件来实现基础功能,然后在 setup 里面整合,否则 setup 就没法看了。

    • watch等
      watch、ref、reactive这些的用法没有改变。

    看一下效果:
    博客首页

    后端出身,不会css,也没有艺术细胞所以比较难看,还望谅解

    表单 发布博文

    这里借鉴一下“简书”的编辑方式,个人感觉还是很方便的,左面是分组目录,中间的选择的分组的博文列表,右面是编辑博文的区域。

    <template>
       <el-row :gutter="12">
        <el-col :span="4">
          <!--分组-->
          <blogGroup/>
        </el-col>
        <el-col :span="5">
          <!--标题列表-->
          <blogArticle/>
        </el-col>
        <el-col :span="14">
          <!--写博文-->
          <el-input
            style="90%"
            :show-word-limit="true"
            maxlength="100"
            placeholder="请输入博文标题,最多100字"
            v-model="blogModel.title"
          />
          <el-button type="primary" plain @click="submit"> 发布文章 </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>
    
    • blogGroup
      博文分组的组件,显示分组列表,便于我们选择分组。

    • blogArticle
      博文列表,选择分组后,显示分组里面的博文列表。在这里可以添加博文,点击博文标题,可以在右面加载博文的表单,进行博文编辑。

    用过简书的编辑方式之后,感觉这个还是非常方便的。

    代码部分:

    【引入的代码略】
    // 组件
    import blogGroup from '../components/blog-group.vue'
    import blogArticle from '../components/blog-article.vue'
    
    // 可见的高度
    const editHeight = document.documentElement.clientHeight - 200
    // 管理
    const { updateBlog, getArtcileById } = blogManage()
    
    // 表单的model
    const blogModel = reactive(blogForm())
    
    // 监控编辑文章的ID
    watch(() => blogState.editArticleId, (v1, v2) => {
      getArtcileById(v1).then((data) => {
        Object.assign(blogModel, data)
      })
    })
    
    // 发布文章
    const submit = () => {
      blogModel.ID = blogState.editArticleId
      blogModel.state = 2 // 改为发布状态
      updateBlog(blogModel).then((id) => {
        // 通知列表
      })
    }
    
    • watch(() => blogState.editArticleId
      监听要编辑的博文ID,然后加载博文数据绑定表单,编辑之后用 submit 发布博文。

    这里还需要一个自动保存草稿的功能,以后再完善。

    • submit
      发布博文,其实这里是修改博文,因为添加的工作是在 blogArticle 组件里面实现的。

    • updateBlog
      调用管理类里面的方式实现发布博文的功能。

    各个平台的发文方式也体验了一下,还是喜欢这种方式,所以个人博客也采用这种方式来实现编辑博文的功能。

    看一下效果:

    编辑博文

    目录导航:

    博文的目录导航

    v-md-editor 提供的目录导航功能,还是非常给力的,看着大纲编写,思路清晰多了。

    博文内容 + 讨论

    <template>
      <el-row :gutter="12">
        <el-col :span="5">
          <!--分组-->
          <blogGroup :isDetail="true"/>
        </el-col>
        <el-col :span="18">
          <!--显示博文-->
          <h1>{{blogInfo.title}}</h1>
          ({{dateFormat(blogInfo.addTime).format('YYYY-MM-DD')}})
          <v-md-preview :text="blogInfo.concent"></v-md-preview>
          <hr>
          <!--讨论列表-->
          <discussList :id="id"/>
          <!--讨论表单-->
          <discussForm :id="id"/>
        </el-col>
      </el-row>
    </template>
    
    【引入的代码略】
    // 组件的属性,博文ID
    const props = defineProps({
      id: String
    })
    
    // 管理
    const { getArtcileById } = blogManage()
    
    // 表单的model
    const blogInfo = reactive({})
    
    getArtcileById(props.id).then((data) => {
      Object.assign(blogInfo, data)
    })
    

    这个代码就很简单了,因为只实现了基本的发讨论和显示讨论的功能,其他暂略。

    看看效果:
    博文的讨论

    好吧,这个讨论做的蛮敷衍的,其实有好多想法,只是篇幅有限,以后再介绍。

    组件级别的代码

    虽然在vue里面,除了js文件,就是vue文件了,但是我觉得还是应该细分一下。
    比如上面都是是页面级的代码,下面这些是“组件”级别的代码了。

    博文分组

    多次提到的博文分组。

    <template>
      <!--分组,分为显示状态和编辑状态-->
      <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>
    

    暂时先用 el-card 来实现,后期会改成 NavMenu 来实现。

    【引入的代码略】
    // 组件的属性
    const props = defineProps({
      isDetail: Boolean
    })
    
    /** 
    * 博文的分组列表
    */
    const blogGroupList = reactive([
      {
        value: '1000',
        label: '前端',
        children: [
          {  value: '1001', label: 'vue基础知识',  },
          {  value: '1002', label: 'vue组件',     },
          {  value: '1003', label: 'vue路由',     }
        ]
      },
      {  value: '2000',  label: '后端',
         children: [
          { value: '2001', label: 'MySQL',     },
          { value: '2002', label: 'web服务',    }
        ]
      }
    ])
    
    // 选择分组
    const { setCurrentGroupId } = blogStateManage()
     
    const router = useRouter()
    const changeGroup = (id) => {
      setCurrentGroupId(id)
      // 判断是不是要跳转
      // 首页、编辑页不跳,博文详细页面调整 
      if (props.isDetail) {
        // 跳转到列表页
        router.push({ name: 'groups', params: { groupId: id }})
      }
    }
    

    分组数据暂时写死了,没有做成可以维护的方式,以后再完善。

    博文列表,编辑用

    <template>
      <!--添加标题-->
      <el-card shadow="hover">
        <template #header>
          <div class="card-header">
            <el-button @click="addNewArticle" >添加新文章</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.title}} ({{dateFormat(item.addTime).format('YYYY-MM-DD')}})
        </div>
        <el-empty description="该分类里面还没有文章呢。" v-if="blogList.length === 0"></el-empty>
      </el-card>
    </template>
    

    用 el-card 做个列表,上面是 添加博文的按钮,下面是博文列表,单击可以进行修改。

    【引入的代码略】
    // 博文列表
    const blogList = reactive([])
    
    // 博文管理
    const { addNewBlog, getBlogListByGroupId } = blogManage()
    // 状态管理
    const { getBlogState, setEditArticleId } = blogStateManage()
    // 博文的状态
    const blogState = getBlogState()
    
    // 更新列表
    const load = () => {
      getBlogListByGroupId(blogState.currentGroupId).then((data) => {
        blogList.length = 0
        blogList.push(...data)
      })
    }
    load()
    
    // 监控选择的分组的ID
    watch(() => blogState.currentGroupId, () => {
      load()
    })
     
    // 添加新文章,仅标题、时间
    const addNewArticle = () => {
      const newArticle = blogForm()
      // 选择的分组ID
      newArticle.groupId = blogState.currentGroupId
      // 用日期作为默认标题
      newArticle.title = dayjs(new Date()).format('YYYY-MM-DD')
    
      addNewBlog(newArticle).then((id) => {
        // 设置要编辑的文章ID
        setEditArticleId(id)
        // 通知列表
        newArticle.ID = id
        blogList.unshift(newArticle)
      })
    }
    
    // 选择要编辑的文章
    const changeArticle = (id) => {
      setEditArticleId(id)
    }
    

    讨论列表

     <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>
        <!--简介-->
        <div class="text item" v-html="item.concent"></div>
        <hr>
        <i class="el-icon-circle-check"></i>&nbsp;{{item.agreeCount}}&nbsp;&nbsp;
      </el-card>
      <!--没有找到数据-->
      <el-empty description="没有讨论呢,抢个沙发呗。" v-if="discussList.length === 0"></el-empty>
      
    

    还是用 el-card 做个列表,el-empty 做一个没有讨论的提示。

    【引入的代码略】
    // 组件的属性
    const props = defineProps({
      id: String
    })
    
    // 管理
    const { getDiscussListByBlogId } = blogManage()
    // 获取状态
    const { getBlogState } = blogStateManage()
    const blogState = getBlogState()
    
    // 表单的model
    const discussList = reactive([])
    getDiscussListByBlogId(props.id).then((data) => {
      discussList.push(...data)
    })
    
    watch(() => blogState.isReloadDiussList, () => {
      getDiscussListByBlogId(props.id).then((data) => {
        discussList.length = 0
        discussList.push(...data)
      })
    })
    

    因为功能比较简单,所以代码也很简单,获取讨论数据绑定显示即可,暂时没有实现分页功能。

    讨论表单

      <el-form
        style="400px;"
        label-position="top"
        :model="dicussModel"
        label-width="80px"
      >
      <el-form-item label="昵称">
        <el-input v-model="dicussModel.discusser"></el-input>
      </el-form-item>
      
      <el-form-item label="内容">
        <el-input type="textarea" v-model="dicussModel.concent"></el-input>
      </el-form-item>
      <el-form-item>
        <el-button type="primary" @click="submit">发表讨论</el-button>
        <el-button>取消</el-button>
      </el-form-item>
    </el-form>
    

    用 el-form 做个表单。

    【引入的代码略】
    // 组件的属性
    const props = defineProps({
      id: String
    })
    
    // 管理
    const { addDiuss } = blogManage()
    // 获取状态
    const { getBlogState, setReloadDiussList } = blogStateManage()
    const blogState = getBlogState()
    
    // 表单的model
    const dicussModel = reactive(discuss())
    
    // 发布讨论
    const submit = () => {
      dicussModel.blogId = props.id // 这是博文ID
      addDiuss(dicussModel).then((id) => { // 可以想象成 axios 的提交
          // 通知列表
        setReloadDiussList()
      })
    }
    

    分成多个组件,每个组件的代码就可以非常少了,这样便于维护。
    发布讨论的函数,先使用blogManage的功能提交数据,回调函数里面,使用的状态管理的功能提醒讨论列表刷新数据。

    源码

    https://gitee.com/naturefw/vue3-blog

    在线演示

    https://naturefw.gitee.io/vue3-blog

  • 相关阅读:
    站点目录中的文件夹被删除后,应用程序池被重启
    silverlight中UserControl的属性在xaml文件中敲不出来的问题
    提取自Discuz NT 的验证码生成
    Asp.net首页生成静态页的一个比较好的方法
    asp.net 字符串格式化
    阻止用户关闭网页,提示保存的解决方案IE/FF/OP通用(未经测试)
    .NET程序如何防止被注入(整站)
    好久没有进步了
    C#数组排序
    我的静态页面
  • 原文地址:https://www.cnblogs.com/jyk/p/14696474.html
Copyright © 2020-2023  润新知