Lightweight wrapper for Univer spreadsheet with templates, tags, and data API support
npm install @phuoc2409/univer-toolkitPackage: @phuoc2409/univer-toolkit
Univer Toolkit là một “bộ khung nhúng” (embed toolkit) dành cho Univer Spreadsheet với mục tiêu:
> Biến việc “kết nối API → lọc dữ liệu → đổ vào sheet” thành cấu hình (config) + UI có sẵn, không cần viết UI tay cho từng API.
Bạn sẽ có:
- Spreadsheet (Univer) hiển thị trong #sheet
- Sidebar trong #sidebar để:
- Chọn nguồn dữ liệu bằng Select2
- Tự sinh form input theo fields (text/number/date/select/range…)
- Thay thế biến @param trong URL/body/headers
- Gọi API và chèn dữ liệu vào sheet theo mapping.columns
Ngoài ra còn có:
- Templates API để upload/insert template
- Tags để lọc theo template
👉 Đối với phần Template, khuyến nghị sử dụng cấu trúc API được xây dựng bằng NestJS (theo thiết kế sẵn của dự án) để đảm bảo tính mở rộng và đồng bộ giữa các service. Bạn có thể tải cấu trúc về để xem sử dụng hoặc là viết lại theo backend của bạn, nó chỉ đơn giản là API về templates và tags
https://github.com/phuoc2426/samplate-template-api-nestjs.git
---
- 1. Dùng nhanh theo CDN (unpkg) – chạy ở mọi dự án
- 2. Dùng theo NPM (ESM/CJS) – phù hợp Vite/Webpack/Framework
- 3. Cấu hình quan trọng nhất: window.USER_UNIVER_CONFIG
- 4. Data APIs: Cấu hình nguồn dữ liệu + filter + mapping
- 5. Fields: Tự sinh UI input (text/select/date/range) + validate
- 6. Cơ chế @param: Ghép URL / body để lọc dữ liệu
- 7. FAQ (Hỏi – Đáp) + ví dụ trả lời sẵn
- 8. Triển khai trên mọi loại dự án (Front framework & Fullstack)
- 9. Troubleshooting nhanh
---
1) React + ReactDOM (Univer UMD cần)
2) RxJS
3) jQuery + Select2 (phải trước toolkit)
4) Univer presets UMD + locales
5) CSS của Univer
6) CSS của Toolkit
7) Toolkit UMD
8) window.USER_UNIVER_CONFIG = {...}
9) UniverToolkit.mount()
> Bắt buộc có 2 div: #sidebar và #sheet.
``html
`
---
Cài đặt:
`bash`
npm i @phuoc2409/univer-toolkit
Import CSS + mount:
`ts
import "@phuoc2409/univer-toolkit/styles/univer-embed.css";
import { mount } from "@phuoc2409/univer-toolkit";
(window as any).USER_UNIVER_CONFIG = {
workbookName: "My Workbook",
dataApis: [],
};
mount();
`
> Nếu bạn chạy SSR (Next.js/Nuxt): chỉ gọi mount() ở client-side (useEffect, onMounted, ).
---
Toolkit đọc config từ biến toàn cục:
`js`
window.USER_UNIVER_CONFIG = { ... };
| Tham số | Bắt buộc | Ý nghĩa | Ví dụ |
|---|---:|---|---|
| workbookName | ✅ | Tên workbook hiển thị | "Demo Workbook" |dataApis
| | ✅ | Danh sách nguồn dữ liệu để chọn và chèn vào sheet | DATA_APIS |templateApi
| | ❌ | Cấu hình backend template (tuỳ dự án) | { baseUrl, headers } |defaultTemplateCategory
| | ❌ | Nhãn category default khi browse templates | "default" |globalContext
| | ❌ | Biến toàn cục dùng để resolve @param | { token, orgId } |
globalContext là “context chung” (session context) dùng thay biến @param mà không cần user nhập mỗi lần.
Ví dụ:
`js`
window.USER_UNIVER_CONFIG = {
// ...
globalContext: {
token: "Bearer abc...",
orgId: 123,
},
};4. Data APIs: Cấu hình nguồn dữ liệu + filter + mapping
Mỗi phần tử trong dataApis đại diện cho một nguồn dữ liệu xuất hiện trong dropdown Select2.
| Tham số | Bắt buộc | Ý nghĩa |
|---|---:|---|
| id | ✅ | Định danh duy nhất |name
| | ✅ | Tên hiển thị (có thể kèm emoji) |method
| | ✅ | GET/POST/PUT… |url
| | ✅ | Endpoint (có thể chứa @param trong query) |headers
| | ❌ | Headers riêng cho API |body
| | ❌ | Payload gửi lên (có thể chứa @param) |fields
| | ❌ | Schema để toolkit tự sinh UI nhập liệu |responseSource
| | ❌ | Nếu response bọc array trong object (vd products, rows) |mapping.columns
| | ✅ | Mapping key → header để đổ vào sheet |
`js`
{
id: "all_students",
name: "Tất cả sinh viên",
method: "GET",
url: "http://127.0.0.1:8000/users",
mapping: {
columns: [
{ key: "id", header: "ID" },
{ key: "name", header: "Họ tên" },
],
},
}
`js
{
id: "products",
name: "Sản phẩm",
method: "GET",
url: "https://dummyjson.com/products/search",
responseSource: "products",
body: {
filters: {
q: "@q", // <-- placeholder
},
},
fields: {
q: {
label: "Từ khóa tìm kiếm",
type: "text",
placeholder: "Nhập từ khóa...",
pattern: "^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$",
},
},
mapping: {
columns: [
{ key: "id", header: "ID" },
{ key: "title", header: "Tên sản phẩm" },
{ key: "price", header: "Giá" },
],
},
}
`
Flow chạy:
1) User chọn “Sản phẩm”
2) Toolkit render input qq = phone
3) User nhập { q: "phone" }
4) Toolkit tạo context @q
5) Resolve → "phone" trong body.filters.qresponseSource="products"
6) Gọi API
7) Lấy list ở mapping.columns
8) Insert vào sheet theo
---
fields là phần quan trọng để sidebar biết cần hiển thị input nào.
#### Text
`js`
q: { label: "Từ khóa", type: "text", placeholder: "Nhập..." }
#### Number (tuỳ dự án)
`js`
limit: { label: "Giới hạn", type: "number", min: 1, max: 100 }
#### Date
`js`
fromDate: { label: "Từ ngày", type: "date" }
toDate: { label: "Đến ngày", type: "date" }
#### Select – dữ liệu tĩnh
`js`
status: {
label: "Trạng thái",
type: "select",
options: {
source: "static",
items: [
{ value: "", label: "-- Tất cả --" },
{ value: "active", label: "Đang học" },
{ value: "graduated", label: "Tốt nghiệp" },
],
},
}
#### Select – load options từ API
`js`
termId: {
label: "Học kỳ",
type: "select",
required: true,
options: {
source: "api",
url: "http://127.0.0.1:8000/terms",
valueKey: "id",
labelKey: "name",
},
}
#### Range – 2 input → 2 biến
`js`
amountRange: {
label: "Khoảng số tiền (VNĐ)",
type: "range",
rangeKeys: ["amountFrom", "amountTo"],
placeholder: "Số tiền",
}
→ bắt buộc nhập
- pattern (nếu bạn dùng) → regex validate
- min/max (number) → validate giá trị---
6. Cơ chế
@param: Ghép URL / body để lọc dữ liệuToolkit resolve biến theo nguyên tắc:
- Mọi chuỗi dạng
@xxx sẽ được thay thế bằng:
- giá trị từ fields user nhập (ưu tiên), hoặc
- giá trị trong globalContext, hoặc
- nếu không có → coi là thiếu tham số$3
Ví dụ:
`js
url: "http://127.0.0.1:8000/grades?courseId=@courseId&semester=@semester"
`Khi user nhập
courseId=101, semester=2024-1
→ URL thực tế sẽ thành:
`
http://127.0.0.1:8000/grades?courseId=101&semester=2024-1
`$3
Ví dụ:
`js
body: {
filters: {
keyword: "@keyword",
status: "@status",
fromDate: "@fromDate",
toDate: "@toDate"
}
}
`Khi user nhập các field tương ứng → toolkit thay vào body trước khi gọi API.
---
7. FAQ (Hỏi – Đáp) + ví dụ trả lời sẵn
$3
Không sao. Bạn cấu hình body.filters (hoặc URL query) và đặt @param đúng tên.
Sau đó cấu hình fields để toolkit hiển thị UI nhập tham số.Ví dụ kiểu “báo cáo”:
`js
{
id: "student_report",
name: "Báo cáo sinh viên",
method: "POST",
url: "http://127.0.0.1:8000/reports/students",
body: {
filters: {
keyword: "@keyword",
termId: "@termId",
departmentId: "@departmentId",
status: "@status",
fromDate: "@fromDate",
toDate: "@toDate",
// range example
amountFrom: "@amountFrom",
amountTo: "@amountTo",
},
},
fields: {
keyword: { label: "Từ khóa", type: "text" },
termId: {
label: "Học kỳ",
type: "select",
required: true,
options: { source: "api", url: "http://127.0.0.1:8000/terms", valueKey: "id", labelKey: "name" },
},
status: {
label: "Trạng thái",
type: "select",
options: {
source: "static",
items: [
{ value: "", label: "-- Tất cả --" },
{ value: "active", label: "Đang học" },
{ value: "graduated", label: "Tốt nghiệp" },
],
},
},
amountRange: {
label: "Khoảng số tiền (VNĐ)",
type: "range",
rangeKeys: ["amountFrom", "amountTo"],
placeholder: "Số tiền",
},
fromDate: { label: "Từ ngày", type: "date" },
toDate: { label: "Đến ngày", type: "date" },
},
mapping: {
columns: [
{ key: "id", header: "ID" },
{ key: "name", header: "Tên" },
{ key: "balance", header: "Số dư" },
],
},
}
`Chốt lại (nhớ 3 bước):
1) Liệt kê tham số lọc trong
body.filters hoặc URL query
2) Gắn @param vào đúng chỗ bạn muốn thay
3) Khai fields.param để có UI nhập và validate---
$3
Nhiều trường hợp response trả về là data, rows, items,... thì ta đặt responseSource: "rows" (hoặc key chứa mảng).---
$3
Trong field select:
`js
options: {
source: "api",
url: "http://.../terms",
valueKey: "id",
labelKey: "name",
}
`---
8. Triển khai trên mọi loại dự án (Front framework & Fullstack)
Cách triển khai nhanh nhất là làm theo y như index.html mẫu ở mục 1, chỉ cần import theo unpkg là có thể sử dụng được
⚠️ Lưu ý: Với .NET hoặc các framework có view template riêng (Razor, MVC, Blazor, Thymeleaf…)
Cần kiểm tra kỹ URL import khi nhúng script / asset.
Một số view engine sử dụng ký tự @ cho cú pháp template,
có thể gây lỗi nếu URL CDN chứa @ mà không được xử lý phù hợp.
$3
- Render trang HTML
- Nhúng script CDN
- Set window.USER_UNIVER_CONFIG
- Gọi UniverToolkit.mount()$3
- npm i
- Import CSS
- Set config trong runtime (hoặc fetch config từ backend rồi gán)
- mount() trong lifecycle (useEffect, onMounted, ngAfterViewInit)$3
- Chỉ mount phía client
- Không gọi mount ở server---
9. Troubleshooting nhanh
-
UniverToolkit not loaded
- Sai thứ tự load script CDN
- UMD URL sai
- Toolkit load trước jQuery/Select2- Không hiện dropdown/select2
- Thiếu
select2.min.css
- Thiếu jquery hoặc select2 script- Chọn API nhưng không hiện input
- Chưa khai
fields
- Bạn dùng @param nhưng chưa tạo fields.param (hoặc không có globalContext)- API gọi ok nhưng sheet rỗng
- Sai
responseSource
- mapping.columns.key` không khớp dữ liệu trả về---
MIT