浅谈Vue3的逻辑复用

Vue3的逻辑复用

使用了 Vue3 Composables 之后,逻辑复用比之前要顺手很多,这里说说前端开发最常用的场景,管理页面的列表查询和增删改查的逻辑复用,欢迎大家共同探讨。

用免费的 render 服务搭建了个在线的预览地址源码在这里,用了免费的 node 环境和免费的 pg 数据库,对这部分有兴趣的可以看看我以前的分享,我写了个部署 spring-boot 的分享,使用 node 就更简单了。

可能每个人的具体工作内容不一致,但是应该都完成过这样的工作内容:

  1. 列表查询,带分页和过滤条件
  2. 新增,修改,查看,删除
  3. 进行一些快捷操作,比如:激活、通过

这些最基础的工作可能占用了我们很大的时间和精力,下面来讨论下如何逻辑复用,提高工作效率

需求分析

一个后台管理中心,绝大部分都是这种管理页面,那么需要:

  • 首先是封装一些通用的组件,这样代码量最低也容易保持操作逻辑和 UI 的一致性
  • 其次要封装一些逻辑复用,比如进入页面就要进行一次列表查询,翻页的时候需要重新查询
  • 最后需要有一些定制化的能力,最基本的列需要自定义,页面的过滤条件和操作也不一样

统一复用

  1. 发起 http 请求
  2. 展示后端接口返回的信息,有成功或者参数校验失败等信息

列表的查询过程

  1. 页面加载后的首次列表查询
  2. 页面 loading 状态的维护
  3. 翻页的逻辑和翻页后的列表重新查询
  4. 过滤条件和模糊搜索的逻辑,还有对应的列表重新查询

新增 Item、查询 Item、修改 Item

  1. form 在提交过程的禁用状态
  2. 发起网络请求
  3. 后端接口返回的信息提示
  4. 列表重新查询

删除 Item

  1. 删除按钮状态的维护(需要至少一个选中删除按钮才可用)
  2. 发起网络请求
  3. 后端接口返回的信息提示
  4. 列表重新查询

定制化的内容

  1. table 的列数据
  2. item 的属性,也就是具体的表单
  3. 快捷操作:改变 user 激活状态
  4. 列表的过滤条件

成果展示

  1. 打开页面会进行一次列表查询
  2. 翻页或者调整页面数量,会进行一次列表查询
  3. 右上角的是否激活筛选状态变更会进行一次列表查询
  4. 右上角模糊搜索,输入内容点击搜索按钮会进行一次列表查询
  5. 点击左上角的新增,弹出表单对话框,进行 item 的新增
  6. 点击操作的"编辑"按钮,弹出表单对话框,对点击的 item 进行编辑
  7. 点击"改变状态"按钮,弹出确认框,改变 user 的激活状态
  8. 选中列表的 checkbox,可以进行删除

代码直接贴在下面了,使用逻辑复用完成以上的内容一共 200 多行,大部分是各种缩进,可以轻松阅读,还写了一个 Work 的管理,也很简单,证明这套东西复用起来没有任何难度。

html 复制代码
<template>
  <div class="user-mgmt">
    <biz-table
      :operations="operations"
      :filters="filters"
      :loading="loading"
      :columns="columns"
      :data="tableData"
      :pagination="pagination"
      :row-key="rowKey"
      :checked-row-keys="checkedRowKeys"
      @operate="onOperate"
      @filter-change="onFilterChange"
      @update:checked-row-keys="onCheckedRow"
      @update:page="onUpdatePage"
      @update:page-size="onUpdatePageSize"
    />
    <user-item :show="showModel" :item-id="itemId" @model-show-change="onModelShowChange" @refresh-list="queryList" />
  </div>
</template>

<script setup name="user-mgmt">
import { h, ref, computed } from 'vue';
import urls from '@/common/urls';
import dayjs from 'dayjs';
import { NButton } from 'naive-ui';
import BizTable from '@/components/table/BizTable.vue';
import UserItem from './UserItem.vue';
import useQueryList from '@/composables/useQueryList';
import useDeleteList from '@/composables/useDeleteList';
import useChangeUserActiveState from './useChangeUserActiveState';

// 自定义列数据
const columns = [
  {
    type: 'selection'
  },
  {
    title: '姓',
    key: 'firstName'
  },
  {
    title: '名',
    key: 'lastName'
  },
  {
    title: '是否激活',
    key: 'isActive',
    render(row) {
      return row.isActive ? '已激活' : '未激活';
    }
  },
  {
    title: '创建时间',
    key: 'createdAt'
  },
  {
    title: '更新时间',
    key: 'updatedAt'
  },
  {
    title: '操作',
    key: 'actions',
    render(row) {
      return [
        h(
          NButton,
          {
            size: 'small',
            onClick: () => onEdit(row),
            style: { marginRight: '5px' }
          },
          { default: () => '编辑' }
        ),
        h(
          NButton,
          {
            size: 'small',
            onClick: () => onChangeUserActiveState(row),
            style: { marginRight: '5px' }
          },
          { default: () => '改变状态' }
        )
      ];
    }
  }
];

// 自定义右上角筛选
const filters = ref([
  {
    label: '是否激活',
    type: 'select',
    value: '0',
    class: 'filter-select',
    options: [
      {
        label: '全部',
        value: '0'
      },
      {
        label: '已激活',
        value: '1'
      },
      {
        label: '未激活',
        value: '2'
      }
    ]
  },
  {
    label: '',
    type: 'input',
    placeholder: '请输入姓氏',
    value: ''
  }
]);

// 筛选变化,需要重新查询列表
const onFilterChange = ({ index, type, value }) => {
  filters.value[index].value = value;
  queryList();
};

// 自定义查询列表参数
const parmas = computed(() => {
  return {
    isActive: filters.value[0].value,
    like: filters.value[1].value
  };
});

// 封装好的查询列表方法和返回的数据
const { data, loading, pagination, onUpdatePage, onUpdatePageSize, queryList } = useQueryList(urls.user.user, parmas);

// 经过处理的列表数据,用于在 table 中展示
const tableData = computed(() =>
  data.value.list.map(item => {
    return {
      ...item,
      createdAt: dayjs(item.createdAt).format('YYYY-MM-DD HH:mm:ss'),
      updatedAt: dayjs(item.updatedAt).format('YYYY-MM-DD HH:mm:ss')
    };
  })
);

// 删除列表相关逻辑封装
const { checkedRowKeys, onCheckedRow, deleteList } = useDeleteList({
  content: '确定删除选中的用户?',
  url: urls.user.userDelete,
  callback: () => {
    queryList();
  }
});

// 列表中的快捷操作
const operations = computed(() => {
  return [
    {
      name: 'create',
      label: '新增',
      type: 'primary'
    },
    {
      name: 'delete',
      label: '删除',
      disabled: checkedRowKeys.value.length === 0
    }
  ];
});

// 触发操作函数
const onOperate = function (name) {
  operationFucs.get(name)();
};

// 新创建 item
const create = () => {
  showModel.value = true;
  itemId.value = 0;
};

const onModelShowChange = () => {
  showModel.value = !showModel.value;
};

const itemId = ref(0);

// 控制模态对话框
const showModel = ref(false);

// 编辑 item
const onEdit = row => {
  itemId.value = row.id;
  showModel.value = true;
};

const { changeUserActiveState } = useChangeUserActiveState();

// 改变激活状态
const onChangeUserActiveState = row => {
  changeUserActiveState({
    id: row.id,
    isActive: !row.isActive,
    loading,
    callback: () => {
      queryList();
    }
  });
};

// 指定 table 的 rowKey
const rowKey = row => row['id'];

// operation 函数集合
const operationFucs = new Map();
operationFucs.set('create', create);
operationFucs.set('delete', deleteList);
</script>

<style lang="scss">
.user-mgmt {
  height: 100%;
  .filter-select {
    .biz-select {
      width: 100px;
    }
  }
}
</style>
相关推荐
lichenyang4535 小时前
Docker 学习笔记(一):为什么需要镜像、容器和仓库?
前端
kyriewen5 小时前
别再对着 TypeScript 报错发呆了:我把 10 个最常见的红色波浪线翻译成了人话
前端·javascript·typescript
IT_陈寒5 小时前
SpringBoot自动配置的坑,我的API突然就404了
前端·人工智能·后端
free356 小时前
从 0 实现一个 Tiny JavaScript VM:项目架构拆解
javascript
暴走的小呆6 小时前
Vue 2 中 Object 的变化侦测:从 getter/setter 到 Dep、Watcher、Observer
vue.js
奇奇怪怪的6 小时前
Embedding 模型 10+ 横向评测
前端
陈广亮6 小时前
Monorepo 从 0 到 1 实操指南 2026 版:pnpm catalogs + Turborepo 2.x + changesets 全链路
前端
子兮曰6 小时前
OpenMontage 深度解剖:你的 AI 编程助手,其实是个视频工作室
前端·后端·ai编程
敲代码的鱼6 小时前
PDF 预览与签名批注写回 支持安卓 iOS 鸿蒙 UTS插件
android·前端·ios
英勇无比的消炎药6 小时前
TinyVue v-auto-tip: 文本超长自动提示的优雅方案
vue.js