利用vue3+ts實現管理後臺(增刪改查)

簡單的管理後臺基本上就是數據的增刪改查。主要就是 列表 + form 表單。每一個頁面的邏輯基本上都相同。不一樣的地方就是每一個頁面須要調用的具體 API 及參數。vue

之前 vue2 的時候最簡單的作法是寫出來一個頁面的邏輯,而後直接 copy 到各個頁面中,修改 API 及參數便可。高級一點的是利用 mixin 函數,將可複用邏輯抽離,每一個頁面引入 mixinreact

vue3 以後新增了composition API。本文就是利用composition API,將可複用的邏輯抽離到composition API中,並引入ts,實現一個簡單的管理後臺功能。webpack

利用@vue/cli建立項目

首先須要將 @vue/cli 升級到最新版。本文用的是4.5.6版本。ios

vue create admin
cd admin
npm run serve

create選擇手動選擇Manually select features,會有一些交互性的選擇,是否要安裝router、vuex等選項,空格能夠切換是否選中。咱們選中TypeScript、Router、Vuex、CSS Pre-processorsgit

咱們利用axios + axios-mock-adapter + mockjs來進行接口請求、接口模擬及假數據生成,接下來再安裝這幾個包。github

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

項目總體框架

假設咱們的項目包含一個 Header,Header 的做用是切換頁面。兩個頁面,分別爲 List 和 About,這兩個頁面都是簡單的列表+增刪改查的操做。web

路由

須要在 router 中增長一個 list 的路由信息。vuex

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 的結構大致類似。npm

<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 APIaxios

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 頁面更改一下接口請求的地址。

最終實現效果

image.png

composition API vs Mixin

在vue3以前,代碼複用的話通常都是用mixin,可是mixin相比於composition API的劣勢,在官網中的解釋以下:

  • mixin很容易發生衝突:由於每一個特性的屬性都被合併到同一個組件中,因此爲了不 property名衝突和調試,你仍然須要瞭解其餘每一個特性。
  • 可重用性是有限的:咱們不能向mixin傳遞任何參數來改變它的邏輯,這下降了它們在抽象邏輯方面的靈活性

源代碼

項目中用到的一些 TS 接口的定義、模擬數據及接口請求本文中沒有具體介紹,若是想了解的話能夠去看看源碼。

戳這裏:vue3_ts_admin

相關文章
相關標籤/搜索