Blog
首页
文档
收藏
关于
  • 在线转换时间戳 (opens new window)
  • 在线压缩图片 (opens new window)
  • Float-Double转二进制 (opens new window)
  • 文件转Hex字符串 (opens new window)

HiuZing

🍑
首页
文档
收藏
关于
  • 在线转换时间戳 (opens new window)
  • 在线压缩图片 (opens new window)
  • Float-Double转二进制 (opens new window)
  • 文件转Hex字符串 (opens new window)
  • 前端面试题

  • JavaScript

  • Vue2

  • port

  • CSS

  • Node.js

  • JavaScript优化

  • uniapp

  • Mini Program

  • TypeScript

  • 面向对象编程

  • UI组件

  • Plugin

  • Vue3

    • 教程

    • Vue Router

    • API

    • Vuex

    • 实例处理方案

      • 图标处理方案
      • 本地缓存处理方案
      • 响应拦截器处理方案
      • 登录鉴权处理方案
      • 退出登录处理方案
      • 国际化处理方案
      • 动态换肤处理方案
      • Screenfull全屏处理方案
      • HeaderSearch 处理方案
      • TagsView处理方案
      • Guide 处理方案
      • Excel 导入处理方案
      • 打印详情处理方案
      • 权限受控处理方案
      • 动态表格处理方案
        • 辅助业务:文章排名页面渲染
        • 相对时间与时间国际化处理
        • 动态表格原理与实现分析
        • 方案落地:动态列数据构建
        • 方案落地:实现动态表格能力
        • 动态表格实现总结
        • 拖拽排序原理与实现分析
        • 方案落地:实现表格拖拽功能
        • 方案落地:完成拖拽后的排序
        • 拖拽排序方案总结
        • 辅助业务:文章删除
        • 辅助业务:文章详情展示
        • 总结
      • 富文本和markdown处理方案
      • 项目部署处理方案
      • 可视化处理方案
    • 文档

    • 用法

  • 性能优化

  • Axios

  • 状态管理

  • React

  • Mock

  • Icon

  • Template

  • 构建工具

  • 项目规范配置

  • Taro

  • SVG

  • React Native

  • 前端
  • Vue3
  • 实例处理方案
HiuZing
2023-03-20
目录

动态表格处理方案

# 动态表格渲染方案之文章排名业务实现

文章排名 核心的内容是围绕着表格处理来进行的。

核心业务:

  1. 文章排名切换
  2. 动态表格渲染

辅助功能:

  1. 文章排名页面展示
  2. 文章详情页面展示

# 辅助业务:文章排名页面渲染

点击查看

整个 文章排名 的页面渲染分成三个部分:

  1. 顶部的动态展示区域
  2. 中间的 table 列表展示区域
  3. 底部的分页展示区域

那么在这一小节中,我们先去渲染第 2、3 两部分:

  1. 创建 api/article 文件定义数据获取接口

    import request from '@/utils/request'
    
    /**
     * 获取列表数据
     */
    export const getArticleList = data => {
      return request({
        url: '/article/list',
        params: data
      })
    }
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
  2. 在 article-ranking 中获取对应数据

    <script setup>
    import { ref, onActivated } from 'vue'
    import { getArticleList } from '@/api/article'
    import { watchSwitchLang } from '@/utils/i18n'
    
    // 数据相关
    const tableData = ref([])
    const total = ref(0)
    const page = ref(1)
    const size = ref(10)
    
    // 获取数据的方法
    const getListData = async () => {
      const result = await getArticleList({
        page: page.value,
        size: size.value
      })
      tableData.value = result.list
      total.value = result.total
    }
    getListData()
    // 监听语言切换
    watchSwitchLang(getListData)
    // 处理数据不重新加载的问题
    onActivated(getListData)
    </script>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
  3. 根据数据渲染视图

    <template>
      <div class="article-ranking-container">
        <el-card>
          <el-table ref="tableRef" :data="tableData" border>
            <el-table-column
              :label="$t('msg.article.ranking')"
              prop="ranking"
            ></el-table-column>
            <el-table-column
              :label="$t('msg.article.title')"
              prop="title"
            ></el-table-column>
            <el-table-column
              :label="$t('msg.article.author')"
              prop="author"
            ></el-table-column>
            <el-table-column
              :label="$t('msg.article.publicDate')"
              prop="publicDate"
            >
            </el-table-column>
            <el-table-column
              :label="$t('msg.article.desc')"
              prop="desc"
            ></el-table-column>
            <el-table-column :label="$t('msg.article.action')">
              <el-button type="primary" size="mini" @click="onShowClick(row)">{{
                $t('msg.article.show')
              }}</el-button>
              <el-button type="danger" size="mini" @click="onRemoveClick(row)">{{
                $t('msg.article.remove')
              }}</el-button>
            </el-table-column>
          </el-table>
    
          <el-pagination
            class="pagination"
            @size-change="handleSizeChange"
            @current-change="handleCurrentChange"
            :current-page="page"
            :page-sizes="[5, 10, 50, 100, 200]"
            :page-size="size"
            layout="total, sizes, prev, pager, next, jumper"
            :total="total"
          >
          </el-pagination>
        </el-card>
      </div>
    </template>
    
    <script setup>
    ...
    /**
     * size 改变触发
     */
    const handleSizeChange = currentSize => {
      size.value = currentSize
      getListData()
    }
    
    /**
     * 页码改变触发
     */
    const handleCurrentChange = currentPage => {
      page.value = currentPage
      getListData()
    }
    </script>
    
    <style lang="scss" scoped>
    .article-ranking-container {
      .header {
        margin-bottom: 20px;
        .dynamic-box {
          display: flex;
          align-items: center;
          .title {
            margin-right: 20px;
            font-size: 14px;
            font-weight: bold;
          }
        }
      }
    
      ::v-deep .el-table__row {
        cursor: pointer;
      }
    
      .pagination {
        margin-top: 20px;
        text-align: center;	
      }
    }
    </style>
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95

# 相对时间与时间国际化处理

在 发布时间 列中,我们希望展示相对时间,并且希望相对时间具备国际化的能力。那么我们就去需要到 filters 中对 dayjs 进行处理

  1. 定义相对时间的处理方法

    ...
    import rt from 'dayjs/plugin/relativeTime'
    
    ...
    
    // 加载相对时间插件
    dayjs.extend(rt)
    function relativeTime(val) {
      if (!isNaN(val)) {
        val = parseInt(val)
      }
        // 当前时间 相对于 传入时间 
      return dayjs().to(dayjs(val))
    }
    
    export default app => {
      app.config.globalProperties.$filters = {
        ...
        relativeTime
      }
    }
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
  2. 在 article-ranking 中使用相对时间

    <el-table-column :label="$t('msg.article.publicDate')">
              <template #default="{row}">
                {{ $filters.relativeTime(row.publicDate) }}
              </template>
            </el-table-column>
    
    1
    2
    3
    4
    5
  3. 接下来来处理国际化内容

    ...
    // 语言包
    import 'dayjs/locale/zh-cn'
    import store from '@/store'
    
    ...
    function relativeTime(val) {
     ...
      return dayjs()
        .locale(store.getters.language === 'zh' ? 'zh-cn' : 'en')
        .to(dayjs(val))
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

# 动态表格原理与实现分析

根据列的勾选,动态展示表格中的列

  1. 展示可勾选的列
  2. 动态展示表格的列

展示可勾选的列:

可勾选的列通过 el-checkbox 来进行渲染。

动态展示表格的列:

依赖于数据,通过 v-for 渲染 el-table-column

实现步骤:

  1. 构建列数据(核心)
  2. 根据数据,通过 el-checkbox 渲染可勾选的列
  3. 根据数据,通过 v-for 动态渲染 el-table-column

# 方案落地:动态列数据构建

因为我们要在 article-ranking 中处理多个业务,如果我们把所有的业务处理都写到 article-ranking 中,那么对应的组件就过于复杂了,所以说我们把所有的 动态列表 相关的业务放入到 article-ranking/dynamic 文件夹中

  1. 创建 article-ranking/dynamic/DynamicData 文件,用来指定初始的 列数据

    import i18n from '@/i18n'
    
    const t = i18n.global.t
    
    export default () => [
      {
        label: t('msg.article.ranking'),
        prop: 'ranking'
      },
      {
        label: t('msg.article.title'),
        prop: 'title'
      },
      {
        label: t('msg.article.author'),
        prop: 'author'
      },
      {
        label: t('msg.article.publicDate'),
        prop: 'publicDate'
      },
      {
        label: t('msg.article.desc'),
        prop: 'desc'
      },
      {
        label: t('msg.article.action'),
        prop: 'action'
      }
    ]
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
  2. 创建 article-ranking/dynamic/index 文件,对外暴露出

    1. 动态列数据
    2. 被勾选的动态列数据
    3. table 的列数据
    import getDynamicData from './DynamicData'
    import { watchSwitchLang } from '@/utils/i18n'
    import { watch, ref } from 'vue'
    
    // 暴露出动态列数据
    export const dynamicData = ref(getDynamicData())
    
    // 监听 语言变化
    watchSwitchLang(() => {
      // 重新获取国际化的值
      dynamicData.value = getDynamicData()
      // 重新处理被勾选的列数据
      initSelectDynamicLabel()
    })
    
    // 创建被勾选的动态列数据
    export const selectDynamicLabel = ref([])
    // 默认全部勾选
    const initSelectDynamicLabel = () => {
      selectDynamicLabel.value = dynamicData.value.map(item => item.label)
    }
    initSelectDynamicLabel()
    
    // 声明 table 的列数据
    export const tableColumns = ref([])
    // 监听选中项的变化,根据选中项动态改变 table 列数据的值
    watch(
      selectDynamicLabel,
      val => {
        tableColumns.value = []
        // 遍历选中项
        const selectData = dynamicData.value.filter(item => {
          // 被勾选的动态列数据中有无包括动态列数据中的数据
          return val.includes(item.label)
        })
        tableColumns.value.push(...selectData)
      },
      {
        immediate: true
      }
    )
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41

# 方案落地:实现动态表格能力

  1. 在 article-ranking 中渲染 动态表格的 check

  2. 导入动态表格的 check 数据

    import { dynamicData, selectDynamicLabel } from './dynamic'
    
    1
  3. 完成动态表格的 check 渲染

        <el-card class="header">
          <div class="dynamic-box">
            <span class="title">{{ $t('msg.article.dynamicTitle') }}</span>
            <el-checkbox-group v-model="selectDynamicLabel">
              <el-checkbox
                v-for="(item, index) in dynamicData"
                :label="item.label"
                :key="index"
                >{{ item.label }}</el-checkbox
              >
            </el-checkbox-group>
          </div>
        </el-card>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
  4. 导入动态列数据

    import { ... tableColumns } from './dynamic'
    
    1
  5. 完成动态列渲染

          <el-table ref="tableRef" :data="tableData" border>
            <el-table-column
              v-for="(item, index) in tableColumns"
              :key="index"
              :prop="item.prop"
              :label="item.label"
            >
              <template #default="{ row }" v-if="item.prop === 'publicDate'">
                {{ $filters.relativeTime(row.publicDate) }}
              </template>
              <template #default="{ row }" v-else-if="item.prop === 'action'">
                <el-button type="primary" size="mini" @click="onShowClick(row)">{{
                  $t('msg.article.show')
                }}</el-button>
                <el-button type="danger" size="mini" @click="onRemoveClick(row)">{{
                  $t('msg.article.remove')
                }}</el-button>
              </template>
            </el-table-column>
          </el-table>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20

# 动态表格实现总结

把动态表格拆开来去看,主要就是分成了两部分:

  1. 展示可勾选的列
  2. 动态展示表格的列

那么对于这两部分而言,核心的就是 数据。

# 拖拽排序原理与实现分析

具体业务:

  1. 鼠标在某一行中按下
  2. 移动鼠标位置
  3. 产生对应的替换样式
  4. 鼠标抬起,表格行顺序发生变化

功能核心:监听鼠标事件,完成对应的 UI 视图处理

具体来说:

  1. 监听鼠标的按下事件
  2. 监听鼠标的移动事件
  3. 生成对应的 UI 样式
  4. 监听鼠标的抬起事件

实现方案:

  1. 利用 sortablejs (opens new window) 实现表格拖拽功能
  2. 在拖拽完成后,调用接口完成排序

# 方案落地:实现表格拖拽功能

  1. 下载 sortablejs

    npm i [email protected]
    
    1
  2. 创建 article-ranking/sortable/index 文件,完成 sortable 初始化

    import { ref } from 'vue'
    import Sortable from 'sortablejs'
    
    // 排序相关
    export const tableRef = ref(null)
    
    /**
     * 初始化排序
     */
    export const initSortable = (tableData, cb) => {
      // 设置拖拽效果
      // 取出table中要拖拽的元素
      const el = tableRef.value.$el.querySelectorAll(
        '.el-table__body-wrapper > table > tbody'
      )[0]
      // 1. 要拖拽的元素
      // 2. 配置对象
      Sortable.create(el, {
        // 拖拽时类名
        ghostClass: 'sortable-ghost',
        // 拖拽结束的回调方法
        onEnd(event) {}
      })
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
  3. 在 article-ranking 中导入 tableRef, initSortable,并完成初始化

    import { tableRef, initSortable } from './sortable'
    
    // 表格拖拽相关
    onMounted(() => {
      // 传递Ref响应式
      initSortable(tableData, getListData)
    })
    
    1
    2
    3
    4
    5
    6
    7
  4. 指定拖拽时的样式

    ::v-deep .sortable-ghost {
      opacity: 0.6;
      color: #fff !important;
      background: #304156 !important;
    }
    
    1
    2
    3
    4
    5

# 方案落地:完成拖拽后的排序

完成拖拽后的排序主要是在 拖拽结束的回调方法 中进行。

我们需要在 拖拽结束的回调方法中调用对应的服务端接口完成持久化的排序

  1. 在 api/article 中定义排序接口

    /**
     * 修改排序
     */
    export const articleSort = data => {
      return request({
        url: '/article/sort',
        method: 'POST',
        data
      })
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
  2. 在拖拽结束的回调方法中调用接口

        // 拖拽结束的回调方法
        async onEnd(event) {
          const { newIndex, oldIndex } = event
          // 修改数据
          await articleSort({
            initRanking: tableData.value[oldIndex].ranking,
            finalRanking: tableData.value[newIndex].ranking
          })
          ElMessage.success({
            message: i18n.global.t('msg.article.sortSuccess'),
            type: 'success'
          })
          // 直接重新获取数据无法刷新 table!!
          tableData.value = []
          // 重新获取数据
          cb && cb()
        }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17

# 拖拽排序方案总结

整个拖拽排序的功能我们围绕着 sortablejs (opens new window) 来去进行实现。

# 辅助业务:文章删除

  1. 定义删除接口

    /**
     * 删除文章
     */
    export const deleteArticle = articleId => {
      return request({
        url: `/article/delete/${articleId}`
      })
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
  2. 为删除按钮添加点击事件

    <el-button type="danger" size="mini" @click="onRemoveClick(row)">{{
                  $t('msg.article.remove')
                }}</el-button>
    
    1
    2
    3
  3. 处理删除操作

    // 删除用户
    const i18n = useI18n()
    const onRemoveClick = row => {
      ElMessageBox.confirm(
        i18n.t('msg.article.dialogTitle1') +
          row.title +
          i18n.t('msg.article.dialogTitle2'),
        {
          type: 'warning'
        }
      ).then(async () => {
        await deleteArticle(row._id)
        ElMessage.success(i18n.t('msg.article.removeSuccess'))
        // 重新渲染数据
        getListData()
      })
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17

# 辅助业务:文章详情展示

文章详情中包含一个 编辑 按钮,用于对文章的编辑功能。与 创建文章 配合,达到相辅相成的目的。

  1. 在 api/article 中定义获取文章详情接口

    /**
     * 获取文章详情
     */
    export const articleDetail = (articleId) => {
      return request({
        url: `/article/${articleId}`
      })
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
  2. 在 article-detail 中获取文章详情数据

    <script setup>
    import { ref } from 'vue'
    import { useRoute } from 'vue-router'
    import { articleDetail } from '@/api/article'
    
    // 获取数据
    const route = useRoute()
    const articleId = route.params.id
    const detail = ref({})
    const getArticleDetail = async () => {
      detail.value = await articleDetail(articleId)
    }
    getArticleDetail()
    </script>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
  3. 在 article-detail 中,根据数据渲染视图

    <template>
      <div class="article-detail-container">
        <h2 class="title">{{ detail.title }}</h2>
        <div class="header">
          <span class="author"
            >{{ $t('msg.article.author') }}:{{ detail.author }}</span
          >
          <span class="time"
            >{{ $t('msg.article.publicDate') }}:{{
              $filters.relativeTime(detail.publicDate)
            }}</span
          >
          <el-button type="text" class="edit" @click="onEditClick">{{
            $t('msg.article.edit')
          }}</el-button>
        </div>
        <div class="content" v-html="detail.content"></div>
      </div>
    </template>
    
    ...
    
    <style lang="scss" scoped>
    .article-detail-container {
      .title {
        font-size: 22px;
        text-align: center;
        padding: 12px 0;
      }
      .header {
        padding: 26px 0;
        .author {
          font-size: 14px;
          color: #555666;
          margin-right: 20px;
        }
        .time {
          font-size: 14px;
          color: #999aaa;
          margin-right: 20px;
        }
        .edit {
          float: right;
        }
      }
      .content {
        font-size: 14px;
        padding: 20px 0;
        border-top: 1px solid #d4d4d4;
      }
    }
    </style>
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
  4. 在article-ranking/index.vue点击进入详情页面

    /**
     * 查看按钮点击事件
     */
    const router = useRouter()
    const onShowClick = row => {
      router.push(`/article/${row._id}`) //article-detail页面的路径是/article/:id
    }
    
    1
    2
    3
    4
    5
    6
    7

# 总结

  1. 文章排名切换
  2. 动态表格渲染
上次更新: 2025/06/23, 07:26:12
权限受控处理方案
富文本和markdown处理方案

← 权限受控处理方案 富文本和markdown处理方案→

最近更新
01
CodePush
06-22
02
打包发布
03-09
03
常用命令
03-09
更多文章>
Theme by Vdoing | Copyright © 2021-2025 WeiXiaojing | 友情链接
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式