iT邦幫忙

2025 iThome 鐵人賽

DAY 26
0
Vue.js

打造銷售系統30天修練 - 全集中・Vue之呼吸系列 第 26

Day 25(下):客戶資料管理— CRUD 的前端實作

  • 分享至 

  • xImage
  •  

承接上篇的介面設計,本文聚焦在「可運行的前端 CRUD 骨架」,以 CustomerManagement.vue 為中心,補齊資料模型、Store、API 契約、表單與刪除流程。

資料模型與驗證重點

  • 欄位:idcodenamephoneemailprovidercreatedAt
  • 驗證:
    • name:必填、長度 1~50
    • phone:必填、僅允許數字與 -
    • email:必填、Email 格式

Pinia Store (搜尋、分頁、錯誤處理)

// src/stores/customer.js
import { defineStore } from 'pinia';

export const useCustomerStore = defineStore('customer', {
  state: () => ({
    items: [],
    loading: false,
    error: null,
    searchParams: { keyword: '' },
    pagination: { currentPage: 1, pageSize: 10, totalItems: 0, totalPages: 0 },
  }),
  actions: {
    async fetchCustomers() {
      this.loading = true;
      this.error = null;
      try {
        // TODO: 串接 API
        // const res = await api.get('/api/customers', { params: { ...this.searchParams, ...this.pagination } });
        // this.items = res.data.items;
        // this.pagination = res.data.pagination;
      } catch (err) {
        this.error = err;
      } finally {
        this.loading = false;
      }
    },
    setSearch(keyword) {
      this.searchParams.keyword = keyword;
      this.pagination.currentPage = 1;
      this.fetchCustomers();
    },
    setPage(page) {
      this.pagination.currentPage = page;
      this.fetchCustomers();
    },
  },
});

API 契約(先定義,後串接)

GET /api/customers?keyword=xxx&page=1&pageSize=10
POST /api/customers
PUT /api/customers/:id
DELETE /api/customers/:id
  • 回應格式:{ items, pagination }
  • 錯誤格式:{ code, message, fields? }

CustomerForm(新增/編輯共用)

<template>
  <form @submit.prevent="onSubmit">
    <label>姓名</label>
    <input v-model.trim="form.name" required maxlength="50" />
    <label>電話</label>
    <input v-model.trim="form.phone" pattern="^[0-9\-]+$" required />
    <label>Email</label>
    <input v-model.trim="form.email" type="email" required />
    <label>註冊方式</label>
    <select v-model="form.provider">
      <option value="email">Email</option>
      <option value="line">LINE</option>
      <option value="facebook">Facebook</option>
      <option value="google">Google</option>
    </select>
    <div class="actions">
      <BaseButton type="submit" variant="primary">儲存</BaseButton>
      <BaseButton type="button" variant="secondary" @click="$emit('cancel')">取消</BaseButton>
    </div>
  </form>
</template>

<script setup>
import { reactive, watch } from 'vue';

const props = defineProps({
  customer: Object, // 編輯時傳入的客戶資料
});

const emit = defineEmits(['submit', 'cancel']);

const form = reactive({
  name: props.customer?.name || '',
  phone: props.customer?.phone || '',
  email: props.customer?.email || '',
  provider: props.customer?.provider || 'email',
});

// 監聽 props 變化,更新表單
watch(
  () => props.customer,
  (newCustomer) => {
    if (newCustomer) {
      Object.assign(form, {
        name: newCustomer.name || '',
        phone: newCustomer.phone || '',
        email: newCustomer.email || '',
        provider: newCustomer.provider || 'email',
      });
    }
  },
  { immediate: true },
);

function onSubmit() {
  emit('submit', { ...form });
}
</script>

新增客戶實作(Create)

  1. 點擊「新增客戶」按鈕觸發 handleAddCustomer()
// CustomerManagement.vue
function handleAddCustomer() {
  showAddForm.value = true; // 控制表單顯示
  editingCustomer.value = null; // 清空編輯資料
}
  1. 表單提交處理:
async function handleFormSubmit(formData) {
  try {
    // 新增客戶
    const newCustomer = {
      id: `C${Date.now()}`, // 臨時 ID
      ...formData,
      createdAt: new Date().toISOString().split('T')[0],
    };

    customers.push(newCustomer);

    // 關閉表單
    showAddForm.value = false;
    alert('客戶新增成功!');
  } catch (error) {
    alert('新增失敗:' + error.message);
  }
}

編輯客戶實作(Update)

  1. 在卡片或表格加入編輯按鈕:
<!-- 在 ResultsList 的 #card slot中 -->
<div class="customer-card">
  <!-- 現有內容 -->
  <div class="card-actions">
    <BaseButton size="small" variant="secondary" @click="editCustomer(item)">
      編輯
    </BaseButton>
  </div>
</div>
  1. 編輯處理函式:
function editCustomer(customer) {
  editingCustomer.value = { ...customer }; // 複製資料
  showAddForm.value = true; // 顯示表單
}

async function handleFormSubmit(formData) {
  try {
    if (editingCustomer.value) {
      // 編輯模式:更新現有客戶
      const index = customers.findIndex((c) => c.id === editingCustomer.value.id);
      if (index !== -1) {
        customers[index] = { ...customers[index], ...formData };
      }
    } else {
      // 新增模式:加入新客戶
      const newCustomer = {
        id: `C${Date.now()}`,
        ...formData,
        createdAt: new Date().toISOString().split('T')[0],
      };
      customers.push(newCustomer);
    }

    showAddForm.value = false;
    editingCustomer.value = null;
    alert('儲存成功!');
  } catch (error) {
    alert('儲存失敗:' + error.message);
  }
}

刪除流程(含確認)

async function handleDeleteCustomer(id) {
  if (!confirm('確定刪除?')) return;
  // await customerStore.deleteCustomer(id)
  // await customerStore.fetchCustomers()
}

總結

本篇把 CustomerManagement 的 CRUD 動線補全至可運行骨架。當 API 就緒時,只需替換資料來源與錯誤處理。

明日,Day 26:[Systemの呼吸・參之型] 訂單流程 - 狀態機與流程設計。心を燃やせ 🔥!


上一篇
Day 25:[Systemの呼吸・貳之型] 客戶資料管理(上):介面結構、搜尋與雙視圖設計
系列文
打造銷售系統30天修練 - 全集中・Vue之呼吸26
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言