乐趣区

关于vue.js:利用vue3ts实现管理后台增删改查

简略的治理后盾基本上就是数据的增删改查。次要就是 列表 + form 表单。每个页面的逻辑基本上都雷同。不同的中央就是每个页面须要调用的具体 API 及参数。

以前 vue2 的时候最简略的做法是写进去一个页面的逻辑,而后间接 copy 到各个页面中,批改 API 及参数即可。高级一点的是利用 mixin 函数,将可复用逻辑抽离,每个页面引入 mixin

vue3 之后新增了 composition API。本文就是利用composition API,将可复用的逻辑抽离到composition API 中,并引入 ts,实现一个简略的治理后盾性能。

利用 @vue/cli 创立我的项目

首先须要将 @vue/cli 降级到最新版。本文用的是 4.5.6 版本。

vue create admin
cd admin
npm run serve

create 抉择手动抉择 Manually select features,会有一些交互性的抉择,是否要装置router、vuex 等选项,空格能够切换是否选中。咱们选中TypeScript、Router、Vuex、CSS Pre-processors

咱们利用 axios + axios-mock-adapter + mockjs 来进行接口申请、接口模仿及假数据生成,接下来再装置这几个包。

npm install axios
npm install -D axios-mock-adapter mockjs

我的项目整体框架

假如咱们的我的项目蕴含一个 Header,Header 的作用是切换页面。两个页面,别离为 List 和 About,这两个页面都是简略的列表 + 增删改查的操作。

路由

须要在 router 中减少一个 list 的路由信息。

const routes: Array<RouteRecordRaw> = [
  {
    path: '/',
    name: 'Home',
    component: Home,
  },
  {
    path: '/about',
    name: 'About',
    component: () => { return import(/* webpackChunkName: "about" */ '../views/About.vue'); },
  },
  {
    path: '/list',
    name: 'List',
    component: () => { return import(/* webpackChunkName: "list" */ '../views/List.vue'); },
  },
];

列表页

首先把列表页的构造写进去,List 和 About 的构造大体类似。

<template>
    <div class='content_page'>
        <div class='content_body'>
            <div class='content_button'>
                <button class='add primary' @click='addItem' title='增加'> 增加 </button>
            </div>
            <div class='content_table'>
                <table>
                    <thead>
                        <tr>
                            <th v-for='item in thead' :key='item'>{{item}}</th>
                        </tr>
                    </thead>
                    <tbody>
                        <tr v-for='(item, index) in list' :key='item.id'>
                            <td>
                                <span :title='item.id'>{{item.id}}</span>
                            </td>
                            <td>
                                <div v-if='index === currentIndex'>
                                    <input
                                        v-model='item.name'
                                        title='name'
                                    />
                                </div>
                                <span :title='item.name' v-else>{{item.name}}</span>
                            </td>
                            <td :title='item.sex'>
                              <div v-if='index === currentIndex'>
                                    <input
                                        v-model='item.sex'
                                        title='sex'
                                    />
                                </div>
                                <span :title='item.sex' v-else>{{item.sex ? '男' : '女'}}</span>
                            </td>
                            <td :title='item.birth'>
                              <div v-if='index === currentIndex'>
                                    <input
                                        v-model='item.birth'
                                        title='birth'
                                    />
                                </div>
                                <span :title='item.birth' v-else>{{item.birth}}</span></td>
                            <td :title='item.address'>
                              <div v-if='index === currentIndex'>
                                  <input
                                      v-model='item.address'
                                      title='birth'
                                  />
                              </div>
                              <span :title='item.address' v-else>{{item.address}}</span>
                            </td>
                            <td>
                                <div v-if='index === currentIndex'>
                                    <button
                                        class='primary confirm'
                                        @click='confirm(item)'
                                    > 确定 </button>
                                    <button
                                        @click='cancel(item)'
                                    > 勾销 </button>
                                </div>
                                <span v-else>
                                    <span @click='editItem(index)'>
                                        edit
                                    </span>
                                    <span @click='deleteItem(index, item)'>delete</span>
                                </span>
                            </td>
                        </tr>
                    </tbody>
                </table>
            </div>
        </div>
    </div>
</template>

其中用到了 addItem、editItem、deleteItem、confirm、cancel 这几个办法,每个列表页的这几个办法性能都是雷同的,惟一的不同就是申请的 API,咱们能够将这几个 API 做为参数,将增删改查的办法提取到 setup 函数中,做到复用。接下来就来到重点的composition API

composition API 具体实现

import {ref, onMounted} from 'vue';
import {ItemType, FetchType, DeleteType, AddType, EditType} from '../../types/index';

export const compositionApi = (
  fetchApi: FetchType,
  deleteApi: DeleteType,
  confirmAddApi: AddType,
  confirmEditApi: EditType,
  itemData: ItemType,
) => {const currentIndex = ref<number | null>(null);
  const list = ref([{}]);
  const getList = () => {fetchApi().then((res: any) => {list.value = res.data.list;});
  };
  const addItem = () => {list.value.unshift(itemData);
    currentIndex.value = 0;
  };
  const editItem = (index: number) => {currentIndex.value = index;};
  const deleteItem = (index: number, item: ItemType) => {deleteApi(item).then(() => {list.value.splice(index, 1);
    //   getList();});
  };
  const cancel = (item: ItemType) => {
    currentIndex.value = null;
    if (!item.id) {list.value.splice(0, 1);
    }
  };
  const confirm = (item: ItemType) => {
    const api = item.id ? confirmEditApi : confirmAddApi;
    api(item).then(() => {getList();
      cancel(item);
    });
  };
  onMounted(() => {getList();
  });
  return {
    list,
    currentIndex,
    getList,
    addItem,
    editItem,
    deleteItem,
    cancel,
    confirm,
  };
};

export default compositionApi;

接下来就是在 List 和 About 页面中的 setup 办法中引入即可。

<script lang='ts'>
import axios from 'axios';
import {defineComponent, reactive} from 'vue';
import {compositionApi} from '../components/composables/index';
import {ItemType} from '../types/index';

const ListComponent = defineComponent({
  name: 'List',
  setup() {
    const state = reactive({
      itemData: {
        id: '',
        name: '',
        sex: 0,
        birth: '',
        address: '',
      },
    });
    const fetchApi = () => {return axios.get('/users');
    };
    const deleteApi = (item: ItemType) => {return axios.post('/users/delete', { id: item.id});
    };
    const confirmAddApi = (item: ItemType) => {
      return axios.post('/users/add', { 
        name: item.name,
        birth: item.birth,
        address: item.address,
      });
    };
    const confirmEditApi = (item: ItemType) => {
      return axios.post('/users/edit', {
        id: item.id,
        name: item.name,
        birth: item.birth,
        address: item.address,
      });
    };
    const localPageData = compositionApi(fetchApi, deleteApi, confirmAddApi, confirmEditApi, state.itemData);
    return {
      state,
      ...localPageData,
    };
  },
  data() {
    return {
      thead: [
        'id',
        'name',
        'sex',
        'birth',
        'address',
        'option',
      ],
    };
  }
});

这样 List 页面的逻辑基本上就实现了。同样,About 页面的逻辑也就实现了,不同的就是在 About 页面更改一下接口申请的地址。

最终实现成果

composition API vs Mixin

在 vue3 之前,代码复用的话个别都是用 mixin,然而mixin 相比于 composition API 的劣势,在官网中的解释如下:

  • mixin很容易发生冲突:因为每个个性的属性都被合并到同一个组件中,所以为了防止 property 名抵触和调试,你依然须要理解其余每个个性。
  • 可重用性是无限的:咱们不能向 mixin 传递任何参数来扭转它的逻辑,这升高了它们在形象逻辑方面的灵活性

源代码

我的项目中用到的一些 TS 接口的定义、模仿数据及接口申请本文中没有具体介绍,如果想理解的话能够去看看源码。

戳这里:vue3_ts_admin

退出移动版