Vue+Ant Design實現CRUD

进击的学酥發表於2024-05-19

vite.config.ts

import {defineConfig} from 'vite'
import vue from '@vitejs/plugin-vue'

//配置Vite開發環境
export default defineConfig({
    //使用vue外掛
    plugins: [vue()],
    //伺服器配置
    server: {
        //指定伺服器埠
        port: 9090,
        //配置代理,用於將本地伺服器的請求轉發到其他伺服器
        proxy: {
            //觸發代理的路徑字首
            '/api': {
                //指定請求轉發的目標伺服器地址
                target: 'http://localhost:8080',
                //改變請求的源,允許跨域請求
                changeOrigin: true
            }
        }
    }
})

Model8080.ts

//學生的基本資訊資料結構
export interface Student {
    id: number,
    name: string,
    sex: string,
    birthday: string
}

//新增和修改學生時需要傳入的資料結構
export interface StudentSave {
    name: string,
    sex: string,
    birthday: string
}

//查詢學生時需要傳入的查詢條件
export interface StudentQuery {
    name?: string,
    sex?: string | null,
    pageNum: number,
    pageSize: number
}

//Spring框架返回的分頁結果資料結構
export interface SpringPage<T> {
    code: number,
    data: { records: T[], total: number },
    message?: string
}

//Spring框架返回的字串結果資料結構
export interface SpringString {
    data: string,
    message?: string,
    code: number
}

//匯入Axios庫中的AxiosResponse型別
import {AxiosResponse} from 'axios'

//Axios庫返回的分頁結果資料結構,繼承AxiosResponse,將SpringPage作為其響應體型別
export interface AxiosRespPage<T> extends AxiosResponse<SpringPage<T>> {
}

//Axios庫返回的字串結果資料結構,繼承AxiosResponse,將SpringString作為其響應體型別
export interface AxiosRespString extends AxiosResponse<SpringString> {
}

Student.vue

<template>
  <a-row class="button-container">
    <!-- 新增按鈕:點選觸發onAdd()函式,開啟新增學生資訊的模態框 -->
    <a-button type="primary" @click="onAdd">新增</a-button>
    <!-- 刪除選中按鈕:點選觸發onBatchRemove()函式,批次刪除選中的學生資訊 -->
    <a-popconfirm title="確認要刪除選中學生嗎?" ok-text="確定" cancel-text="取消"
                  @confirm="onBatchRemove" @visibleChange="onVisibleChange" :visible="visible">
      <a-button type="primary">刪除選中</a-button>
    </a-popconfirm>
    <!-- 姓名搜尋框 -->
    <div class="input-container">
      <a-input v-model:value="student.name" placeholder="姓名" :allowClear="true"></a-input>
    </div>
    <!-- 性別選擇框 -->
    <div class="select-container">
      <a-select v-model:value="student.sex" placeholder="性別" :allowClear="true">
        <a-select-option value="男">男</a-select-option>
        <a-select-option value="女">女</a-select-option>
      </a-select>
    </div>
    <!-- 搜尋按鈕:點選觸發query()函式,在分頁、排序、篩選等操作後重新搜尋學生資訊 -->
    <a-button class="search-button" @click="query" type="primary">搜尋</a-button>
  </a-row>
  <hr>
  <!-- 學生資訊表格:columns屬性設定列配置,data-source屬性設定資料來源,row-key屬性設定行唯一標識,
       pagination屬性設定分頁配置,@change事件繫結查詢函式query,:row-selection屬性設定行選擇配置 -->
  <a-table :columns="columns" :data-source="students" row-key="id" :pagination="pagination"
           @change="query" :row-selection="{selectedRowKeys:ids,onChange:onSelectChange}">
    <!-- 在bodyCell列模板中,根據列的dataIndex判斷是否為"operation"列,如果是,則渲染修改和刪除按鈕 -->
    <template #bodyCell="{column, record}">
      <template v-if="column.dataIndex==='operation'">
        <!-- 修改按鈕:點選觸發onEdit()函式,開啟修改學生資訊的模態框,並將選中的學生資訊傳遞給模態框 -->
        <a @click="onEdit(record)">修改</a>
        <a-divider type="vertical"></a-divider>
        <!-- 刪除按鈕:點選觸發onRemove()函式,並傳遞要刪除的學生ID -->
        <a-popconfirm title="確認要刪除該學生嗎?" ok-text="確定" cancel-text="取消" @confirm="onRemove(record.id)">
          <a>刪除</a>
        </a-popconfirm>
      </template>
    </template>
  </a-table>
  <!-- Save元件,用於顯示和編輯學生資訊的模態框,透過屬性和資料模型與父元件進行互動,並透過@saved事件觸發onSaved()方法 -->
  <Save :id="id" :save="save" v-model:visible="saveVisible" @saved="onSaved"></Save>
</template>

<script setup lang="ts">
import axios from "axios";
import {ref, computed, reactive} from "vue";
import {usePagination, useRequest} from "vue-request";
import {AxiosRespPage, AxiosRespString, Student, StudentQuery} from "./model/Model8080";
import {PaginationProps} from "ant-design-vue";
import Save from "./Save.vue";

//新增、修改學生資訊的相關變數和函式
const saveVisible = ref(false) //定義saveVisible物件,用於儲存是否顯示儲存學生資訊的對話方塊的可見性狀態,初始值為false
const id = ref(0) //定義id物件,用於儲存要進行新增或修改的學生的ID,初始值為0
const save = reactive({name: '', sex: '', birthday: ''}) //定義save物件,用於儲存要進行新增或修改的學生資訊

//開啟新增學生資訊的模態框
function onAdd() {
  saveVisible.value = true //將saveVisible的值設為true,以顯示模態框
  id.value = 0 //將id的值設為0,用於標識新增的學生資訊
  Object.assign(save, {name: '', sex: '', birthday: ''}) //使用Object.assign方法初始化save物件
}

//開啟修改學生資訊的模態框,並將選中的學生資訊傳遞給模態框
function onEdit(record: Student) {
  saveVisible.value = true //將saveVisible的值設為true,以顯示模態框
  id.value = record.id //將id的值賦給record.id,用於標識修改的學生資訊
  Object.assign(save, record) //使用Object.assign方法將record物件的屬性和值複製給save物件,實現對save物件的更新
}

//在儲存學生資訊後呼叫,用於重新搜尋學生資訊
function onSaved() {
  search(student.value)
}

//分頁、搜尋學生資訊的相關變數和函式
const student = ref({pageNum: 1, pageSize: 5, name: '', sex: null, birthday: null})
//呼叫usePagination函式實現分頁
const {data, total, run: search} = usePagination<AxiosRespPage<Student>, StudentQuery[]>(
    (records) => axios.get('/api/student/findByPage', {params: records}),
    {
      defaultParams: [student.value], //指定請求分頁資料時需要傳遞的預設引數
      pagination: {
        currentKey: "pageNum", //當前頁碼
        pageSizeKey: 'pageSize', //每頁顯示條數
        totalKey: 'data.data.totalRow' //總記錄數
      }
    }
)

//在分頁、排序、篩選等操作後重新搜尋學生資訊
function query(pagination: PaginationProps) {
  student.value.pageNum = pagination.current ?? 1 //當前頁碼
  student.value.pageSize = pagination.pageSize ?? 5 //每頁顯示條數
  search(student.value)
}

//計算分頁控制元件的配置
const pagination = computed<PaginationProps>(() => {
  return {
    current: student.value.pageNum, //當前頁碼
    pageSize: student.value.pageSize, //每頁顯示條數
    total: total.value, //總記錄數
    showSizeChanger: true, //每頁顯示條數的下拉選單
    pageSizeOptions: ["5", "10", "15", "20"] //自定義下拉選單內容
  }
})

//計算學生列表
const students = computed(() => {
  return data.value?.data.data.records || [] //獲取資料,如果該值存在則返回,否則返回一個空陣列
})

//刪除學生資訊的相關函式
async function onRemove(id: number) {
  await remove(id) //呼叫remove函式,刪除指定ID的學生資訊,等待其返回結果
  search(student.value)
}
//刪除學生資訊非同步請求
const {runAsync: remove} = useRequest<AxiosRespString, number[]>(
    (id) => axios.delete(`/api/student/remove/${id}`),
    {
      manual: true //請求需要手動觸發
    }
)

//批次刪除選中的學生資訊
const ids = ref<number[]>([]) //定義ids物件,用於儲存選中的學生ID列表
//當選擇的學生髮生變化時,更新ids的值為新的學生ID列表
function onSelectChange(keys: number[]) {
  ids.value = keys
}

//呼叫batchRemove介面刪除選中的學生,然後清空ids的值,並重新搜尋學生
async function onBatchRemove() {
  await batchRemove(ids.value)
  ids.value = []
  search(student.value)
}

const {runAsync: batchRemove} = useRequest<AxiosRespString, number[][]>(
    (ids) => axios.delete(`/api/student/batchRemove/${ids.join(',')}`),
    {
      manual: true //請求需要手動觸發
    }
)
const visible = ref(false) //關閉批次刪除學生資訊的模態框

//控制刪除選中學生的模態框的顯示與隱藏
function onVisibleChange(v: boolean) {
  if (!v) { //隱藏
    visible.value = false
  } else {  //顯示
    visible.value = ids.value.length > 0
  }
}

//表格列的定義
const columns = ref([
  {
    title: "編號",
    dataIndex: "id",
  },
  {
    title: "姓名",
    dataIndex: "name",
  },
  {
    title: "性別",
    dataIndex: "sex",
  },
  {
    title: "生日",
    dataIndex: "birthday",
  },
  {
    title: '操作',
    dataIndex: 'operation'
  }
]);
</script>

<style scoped> /* 樣式僅應用於當前元件的元素 */
.button-container {
  display: flex;
  align-items: center;
}
.input-container {
  display: flex;
  align-items: center;
  margin-left: 15px; /* 調整與按鈕的間距 */
}
.select-container {
  display: flex;
  align-items: center;
  margin-left: 15px; /* 適當調整與輸入框的間距 */
}
.search-button {
  margin-left: 15px; /* 調整與選擇框的間距 */
}
button {
  margin: 5px; /* 調整按鈕的間距 */
}
</style>

Save.vue

<template>
  <!-- 模態框元件,用於展示和編輯使用者資訊 -->
  <a-modal :visible="visible" :title="title" @ok="onOk" @cancel="onCancel">
    <a-form>
      <!-- 如果存在使用者ID,則顯示使用者ID輸入框,此處僅用於展示,不可編輯 -->
      <a-form-item label="編號" v-if="id">
        <a-input v-bind:value="id" readonly></a-input>
      </a-form-item>
      <!-- 姓名輸入框,帶有表單驗證資訊 -->
      <a-form-item label="姓名" v-bind="validateInfos.name">
        <a-input v-model:value="save.name"></a-input>
      </a-form-item>
      <!-- 性別選擇器,支援選擇男性或女性 -->
      <a-form-item label="性別" v-bind="validateInfos.sex">
        <a-radio-group v-model:value="save.sex">
          <a-radio-button value="男">男</a-radio-button>
          <a-radio-button value="女">女</a-radio-button>
        </a-radio-group>
      </a-form-item>
      <!-- 生日選擇器,支援選擇日期,繫結表單驗證資訊 -->
      <a-form-item label="生日" v-bind="validateInfos.birthday">
        <a-date-picker v-model:value="save.birthday" value-format="YYYY-MM-DD"></a-date-picker>
      </a-form-item>
    </a-form>
  </a-modal>
</template>

<script setup lang="ts">
import axios from "axios";
import {ref, computed} from "vue";
import {useRequest} from "vue-request";
import {AxiosRespString, StudentSave} from "./model/Model8080";
import {Form} from 'ant-design-vue'

//定義元件接收的props
const props = defineProps<{ id: number, save: StudentSave, visible: boolean }>()
//透過props中的id值決定顯示的標題
const title = computed(() => props.id === 0 ? '新增學生' : '修改學生')
//定義元件向父元件傳送的事件
const emit = defineEmits(['update:visible', 'saved'])

//點選確認按鈕時的處理邏輯
async function onOk() {
  try {
    //提交前校驗
    await validate()
    //判斷是新增還是編輯
    if (props.id === 0) {
      await add(props.save)
    } else {
      await edit(props.save)
    }
    //通知父元件儲存成功,並關閉當前元件
    emit('saved')
    emit('update:visible', false)
  } catch (e) {
    console.error(e)
  }
}

//點選取消按鈕時的邏輯:關閉當前元件
function onCancel() {
  emit('update:visible', false)
}

//新增學生資訊非同步請求
const {runAsync: add} = useRequest<AxiosRespString, StudentSave[]>(
    (student) => axios.post('/api/student/add', student),
    {
      manual: true //手動觸發請求
    }
)

//編輯學生資訊非同步請求
const {runAsync: edit} = useRequest<AxiosRespString, StudentSave[]>(
    (student) => axios.put('/api/student/edit', student),
    {
      manual: true //手動觸發請求
    }
)

//定義表單校驗規則
const rules = ref({
  name: [
    {required: true, message: '請輸入姓名'},
    {min: 2, message: '字元數至少為2'}
  ],
  sex: [
    {required: true, message: '請選擇性別'}
  ],
  birthday: [
    {required: true, message: '請選擇生日'}
  ]
})

//使用Ant Design Vue的Form.useForm方法將表單和校驗規則進行繫結,獲取校驗資訊和校驗函式
const {validateInfos, validate} = Form.useForm(props.save, rules)
</script>

相關文章