Giao diện
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ách | Dù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 |
ref → formRef.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ửa | Trang 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ện | Khi nào phát |
|---|---|
@finish | Người dùng gửi form và mọi rule đã qua — đây là chỗ gọi API |
@finish-failed | Có 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.validate là undefined) — 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àm | Việ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 và 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ệu | Component |
|---|---|
| Chữ ngắn / dài | CInput, CTextarea |
| Số | CInputNumber |
| Tiền | CInputCurrency |
| Phần trăm | CInputPercent |
| Chọn một / nhiều | CSelect + CSelectOption |
| Có/không | CSwitch, CCheckbox |
| Ngày, khoảng ngày | CDatePicker, CRangePicker |
| Tệp | CUpload |
Tất cả là primitive antdv re-export: import { CInput, CSelect } from '@tasco/ui'.
Khả năng tiếp cận
CFormItemgắ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ằngaria-describedby; trình đọc màn hình đọc được khi người dùng vào ô. - Ô bắt buộc có dấu
*dorequiredtrong 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 ý
modelnên làreactiveobject 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
:loadingtrên nút lưu trong lúc gửi; luôn tắt trongfinally. - 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
- Trang form — mẫu trang đầy đủ.
- Lỗi 422 và form — lỗi nghiệp vụ.
CFilterBar— form lọc ngắn đặt trên bảng.