Skip to content

CForm

Form chuẩn của framework: bọc a-form, đặt sẵn layout dọc (nhãn trên, ô nhập dưới) và chuyển tiếp nguyên vẹn mọi prop, slot, sự kiện của antd Form.

Dùng khi: mọi màn hình nhập liệu — tạo mới, chỉnh sửa, bộ lọc trong drawer, hộp thoại nhập nhanh.

Không dùng khi: chỉ có một ô nhập không cần nhãn và không cần kiểm tra (ô tìm kiếm rời) — dùng CInput trực tiếp.

Gửi form

Hai cách, chọn theo việc trang cần làm:

CáchDùng khiĐánh đổi
@finish + nút html-type="submit"Trang thêm/sửa thường: một nút Lưu, validate cả form rồi gọi APIÍt code nhất, và Enter trong ô nhập cũng gửi được form
refformRef.value.validate()Trang tự quyết thời điểm: validate một phần (bước wizard), resetFields() sau khi lưu, clearValidate() khi đổi giữa chế độ thêm và sửaTrang phải tự bắt lỗi khi validate() reject

Dùng chung được: nút Lưu vẫn đi qua @finish, còn ref lo phần reset và validate từng bước.

Bằng @finish

Sự kiệnKhi nào phát
@finishNgười dùng gửi form và mọi rule đã qua — đây là chỗ gọi API
@finish-failedCó rule chưa qua; antd đã tự hiện lỗi dưới từng ô
vue
<CForm :model="model" :rules="rules" @finish="onFinish" @finish-failed="onFinishFailed">
  <!-- … -->
  <CFormItem>
    <CButton variant="primary" :loading="submitting" html-type="submit">Lưu</CButton>
  </CFormItem>
</CForm>

Bằng ref từ 2.1

Bản 2.0 của @tasco/ui chưa expose API của form (formRef.value.validateundefined) — app còn ở 2.0 gửi form bằng @finish.

CForm expose API của form; khai kiểu bằng CFormInstance của @tasco/ui. Đừng import FormInstance từ ant-design-vue — ESLint chặn mọi import từ đó, kể cả import type.

vue
<script setup lang="ts">
import type { CFormInstance } from '@tasco/ui'

const formRef = ref<CFormInstance>()

async function luu() {
  try {
    await formRef.value?.validate()
  } catch {
    return   // rule chưa qua — antd đã hiện lỗi dưới từng ô
  }
  await createOrder({ ...model })
  formRef.value?.resetFields()
}
</script>

<template>
  <CForm ref="formRef" :model="model" :rules="rules">
    <!-- … -->
  </CForm>
</template>
HàmViệc
validate(nameList?)Validate cả form, hoặc chỉ các field trong nameList
validateFields(nameList?)Tên gọi khác của validate, hành vi giống hệt
resetFields(name?)Trả về giá trị khởi tạo xoá lỗi
clearValidate(name?)Chỉ xoá lỗi, giữ nguyên giá trị
scrollToField(name)Cuộn tới một ô — dùng khi tự xử lý lỗi 422

validate() reject khi có rule chưa qua, nên luôn bọc try/catch: để promise reject trần là tạo unhandled rejection. Gọi các hàm này lúc form chưa mount (hoặc đã bị gỡ) sẽ ném lỗi [@tasco/ui] … nói rõ nguyên nhân.

Ví dụ đầy đủ — wizard hai bước, mỗi bước chỉ validate phần của nó:

Rules

ts
const rules = {
  customer: [{ required: true, message: 'Nhập tên khách hàng' }],
  email: [
    { required: true, message: 'Nhập email' },
    { type: 'email', message: 'Email không hợp lệ' },
  ],
  amount: [{ required: true, message: 'Nhập giá trị' }],
}

name của CFormItem phải trùng khoá trong model và trong rules, nếu không rule không chạy và lỗi không hiện ở đúng ô.

Thông báo viết tiếng Việt, nói việc cần làm ("Nhập tên khách hàng") thay vì mô tả lỗi ("Trường bắt buộc").

Lỗi nghiệp vụ từ backend

Rules chỉ kiểm được thứ trình duyệt biết. Lỗi nghiệp vụ (mã trùng, vượt hạn mức) do backend trả về dạng 422 và gắn vào từng ô bằng validate-status + help:

vue
<CFormItem
  label="Khách hàng"
  name="customer"
  :validate-status="serverErrors.customer ? 'error' : undefined"
  :help="serverErrors.customer?.[0]"
>
  <CInput v-model:value="model.customer" />
</CFormItem>

Chi tiết: Lỗi 422 và form.

Bố cục

Mặc định layout="vertical" — dễ đọc, hợp màn hình hẹp, nhãn dài không bị cắt. Đổi sang horizontal khi form ngắn và nhãn đều nhau; nhớ khai label-col / wrapper-col.

Form nhiều cột dùng Row + Col từ @tasco/ui, luôn có điểm ngắt (:xs="24") để không vỡ trên màn hình hẹp.

API

CForm không thêm prop nào ngoài mặc định layout="vertical"; toàn bộ prop, slot và sự kiện của a-form (model, rules, label-col, wrapper-col, disabled, @finish, @finish-failed, @values-change…) dùng như bình thường.

Bảng trên không liệt kê hàm lấy qua ref — xem Bằng ref.

Ô nhập đi kèm

Dữ liệuComponent
Chữ ngắn / dàiCInput, CTextarea
SốCInputNumber
TiềnCInputCurrency
Phần trămCInputPercent
Chọn một / nhiềuCSelect + CSelectOption
Có/khôngCSwitch, CCheckbox
Ngày, khoảng ngàyCDatePicker, CRangePicker
TệpCUpload

Tất cả là primitive antdv re-export: import { CInput, CSelect } from '@tasco/ui'.

Khả năng tiếp cận

  • CFormItem gắn <label> với ô nhập, nên bấm nhãn là focus vào ô — đừng thay nhãn bằng chữ thường đặt cạnh.
  • Lỗi hiện qua help được antd liên kết bằng aria-describedby; trình đọc màn hình đọc được khi người dùng vào ô.
  • Ô bắt buộc có dấu * do required trong rules sinh ra — đừng tự thêm dấu sao vào nhãn.
  • Sau khi gửi thất bại, antd cuộn tới ô lỗi đầu tiên; giữ hành vi đó thay vì tự cuộn.

Lưu ý

  • model nên là reactive object phẳng chứa đúng các field của form; đừng gán nguyên bản ghi lấy từ backend vào đó.
  • Bật :loading trên nút lưu trong lúc gửi; luôn tắt trong finally.
  • Form dài nên có nút Huỷ rõ ràng và cảnh báo khi rời trang — xem Trang form.

Liên quan