@fui-org/fui-cli 1.3.2 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -3
- package/dist/fui-cx180e19.js +379 -0
- package/dist/fui.js +1 -1
- package/package.json +3 -6
- package/skills/fui/SKILL.md +9 -41
- package/skills/fui-skill/SKILL.md +95 -225
- package/skills/fui-skill/assets/projectdefaultstyle-3.0.css +555 -0
- package/skills/fui-skill/assets/projectdefaultstyle.css +207 -235
- package/skills/fui-skill/references/INDEX.md +105 -137
- package/skills/fui-skill/references/advanced-techniques.md +76 -68
- package/skills/fui-skill/references/coding-standards.md +56 -56
- package/skills/fui-skill/references/component-design.md +166 -173
- package/skills/fui-skill/references/component-quickref.md +61 -60
- package/skills/fui-skill/references/component-table.md +128 -117
- package/skills/fui-skill/references/components-dialog.md +55 -56
- package/skills/fui-skill/references/components-display.md +24 -30
- package/skills/fui-skill/references/components-echart.md +186 -261
- package/skills/fui-skill/references/components-input.md +72 -96
- package/skills/fui-skill/references/controls-patterns.md +196 -342
- package/skills/fui-skill/references/controls-styling-vocabulary.md +130 -97
- package/skills/fui-skill/references/db-table-design.md +24 -28
- package/skills/fui-skill/references/db-workflow.md +191 -390
- package/skills/fui-skill/references/default-function.md +169 -128
- package/skills/fui-skill/references/design-modes.md +35 -63
- package/skills/fui-skill/references/echart-templates.md +204 -196
- package/skills/fui-skill/references/fastproject.md +62 -60
- package/skills/fui-skill/references/fsheet.md +109 -124
- package/skills/fui-skill/references/fullstack-workflow.md +90 -128
- package/skills/fui-skill/references/module-data-patterns.md +31 -40
- package/skills/fui-skill/references/module-json-anatomy.md +47 -52
- package/skills/fui-skill/references/module-structure.md +80 -196
- package/skills/fui-skill/references/new-session.md +49 -51
- package/skills/fui-skill/references/pdfmake.md +17 -17
- package/skills/fui-skill/references/permission-system.md +89 -108
- package/skills/fui-skill/references/platform-architecture.md +128 -153
- package/skills/fui-skill/references/project-config.md +102 -134
- package/skills/fui-skill/references/project-provisioning.md +139 -244
- package/skills/fui-skill/references/script-map.md +208 -242
- package/skills/fui-skill/references/sql-clr-functions.md +98 -97
- package/skills/fui-skill/references/system-design.md +63 -88
- package/skills/fui-skill/references/tapi-file-api.md +46 -52
- package/skills/fui-skill/references/tapi-permission-patterns.md +51 -53
- package/skills/fui-skill/references/tapi-reference.md +132 -207
- package/skills/fui-skill/references/tools-registry.md +84 -460
- package/skills/fui-skill/references/ui-crosswindow-patterns.md +79 -75
- package/skills/fui-skill/references/ui-dialog-patterns.md +98 -72
- package/skills/fui-skill/references/ui-layout-patterns.md +26 -26
- package/skills/fui-skill/references/ui-patterns.md +71 -83
- package/skills/fui-skill/references/ui-screenshot-review.md +63 -62
- package/skills/fui-skill/references/ui-table-cell-patterns.md +61 -59
- package/skills/fui-skill/references/ui-templates.md +16 -23
- package/skills/fui-skill/references/verification.md +236 -246
- package/skills/fui-skill/references/watcher-patterns.md +30 -63
- package/skills/fui-skill/references/websocket-realtime.md +83 -66
- package/skills/fui-skill/scripts/component-3.0.js +298 -131
- package/skills/fui-skill/scripts/component.js +277 -271
- package/skills/fui-skill/scripts/componentTable-3.0.js +182 -53
- package/skills/fui-skill/scripts/componentTable.js +171 -49
- package/skills/fui-skill/scripts/defaultfunction-3.0.js +88 -3
- package/skills/fui-skill/scripts/defaultfunction.js +88 -3
- package/skills/fui-skill/scripts/fsheet.js +38 -0
- package/skills/fui-target/.claude-plugin/plugin.json +6 -0
- package/skills/fui-target/hooks/hooks.json +1 -0
- package/skills/fui-target/hooks/register.tsx +71 -0
- package/skills/fui-target/types/index.d.ts +7 -0
- package/dist/fui-y8an39cn.js +0 -420
- package/skills/fui-skill/README.md +0 -112
- package/skills/fui-skill/metadata.json +0 -75
- package/skills/fui-skill-cli/SKILL.md +0 -139
- package/skills/fui-skill-cli/references/INDEX.md +0 -110
- package/skills/fui-skill-cli/references/advanced-techniques.md +0 -168
- package/skills/fui-skill-cli/references/coding-standards.md +0 -112
- package/skills/fui-skill-cli/references/component-design.md +0 -448
- package/skills/fui-skill-cli/references/component-quickref.md +0 -78
- package/skills/fui-skill-cli/references/component-table.md +0 -248
- package/skills/fui-skill-cli/references/components-dialog.md +0 -191
- package/skills/fui-skill-cli/references/components-display.md +0 -141
- package/skills/fui-skill-cli/references/components-echart.md +0 -316
- package/skills/fui-skill-cli/references/components-input.md +0 -335
- package/skills/fui-skill-cli/references/controls-patterns.md +0 -701
- package/skills/fui-skill-cli/references/controls-styling-vocabulary.md +0 -137
- package/skills/fui-skill-cli/references/db-table-design.md +0 -73
- package/skills/fui-skill-cli/references/db-workflow.md +0 -288
- package/skills/fui-skill-cli/references/default-function.md +0 -425
- package/skills/fui-skill-cli/references/design-modes.md +0 -57
- package/skills/fui-skill-cli/references/echart-templates.md +0 -489
- package/skills/fui-skill-cli/references/fastproject.md +0 -99
- package/skills/fui-skill-cli/references/fsheet.md +0 -203
- package/skills/fui-skill-cli/references/fullstack-workflow.md +0 -313
- package/skills/fui-skill-cli/references/module-data-patterns.md +0 -117
- package/skills/fui-skill-cli/references/module-json-anatomy.md +0 -132
- package/skills/fui-skill-cli/references/module-structure.md +0 -141
- package/skills/fui-skill-cli/references/new-session.md +0 -85
- package/skills/fui-skill-cli/references/pdfmake.md +0 -60
- package/skills/fui-skill-cli/references/permission-system.md +0 -150
- package/skills/fui-skill-cli/references/platform-architecture.md +0 -269
- package/skills/fui-skill-cli/references/project-config.md +0 -303
- package/skills/fui-skill-cli/references/project-provisioning.md +0 -278
- package/skills/fui-skill-cli/references/script-map.md +0 -262
- package/skills/fui-skill-cli/references/sql-clr-functions.md +0 -225
- package/skills/fui-skill-cli/references/system-design.md +0 -89
- package/skills/fui-skill-cli/references/tapi-file-api.md +0 -185
- package/skills/fui-skill-cli/references/tapi-permission-patterns.md +0 -156
- package/skills/fui-skill-cli/references/tapi-reference.md +0 -474
- package/skills/fui-skill-cli/references/tools-registry.md +0 -84
- package/skills/fui-skill-cli/references/ui-crosswindow-patterns.md +0 -321
- package/skills/fui-skill-cli/references/ui-dialog-patterns.md +0 -255
- package/skills/fui-skill-cli/references/ui-layout-patterns.md +0 -176
- package/skills/fui-skill-cli/references/ui-patterns.md +0 -303
- package/skills/fui-skill-cli/references/ui-screenshot-review.md +0 -95
- package/skills/fui-skill-cli/references/ui-table-cell-patterns.md +0 -318
- package/skills/fui-skill-cli/references/ui-templates.md +0 -22
- package/skills/fui-skill-cli/references/verification.md +0 -236
- package/skills/fui-skill-cli/references/watcher-patterns.md +0 -163
- package/skills/fui-skill-cli/references/websocket-realtime.md +0 -271
|
@@ -475,6 +475,10 @@ function jsonToExcel(Obj) {
|
|
|
475
475
|
filename: "dataFP.xlsx", // filename to export
|
|
476
476
|
worksheet: "SheetFP", // sheet name for single sheet
|
|
477
477
|
sheets: null, // New property for multiple sheets
|
|
478
|
+
// optional: [{header, key, type?: 'number'|'date'|'html', numFmt?, width?, sum?: true|numFmt}]
|
|
479
|
+
// Có columns thì cột/tiêu đề/định dạng theo đúng danh sách này (dùng cho f-table excel="UI");
|
|
480
|
+
// không có thì giữ hành vi cũ: cột = key của dòng đầu, giá trị thô. Mỗi sheet có thể có columns riêng.
|
|
481
|
+
columns: null,
|
|
478
482
|
onProgress: null, // optional: function({percent, processedRows, totalRows, phase, sheetName}) - phase: 'building'|'finalizing'|'done'|'error'
|
|
479
483
|
showProgress: true // tự hiện thanh % cố định giữa màn hình khi xuất; truyền false để tắt UI này
|
|
480
484
|
}
|
|
@@ -487,7 +491,7 @@ function jsonToExcel(Obj) {
|
|
|
487
491
|
if (!sheetList.length) { showMessage({ title: "Sheets data is empty" }); return; }
|
|
488
492
|
} else {
|
|
489
493
|
if (!props.data || !props.data.length) { showMessage({ title: "Data Null" }); return; }
|
|
490
|
-
sheetList = [{ name: props.worksheet, data: props.data }];
|
|
494
|
+
sheetList = [{ name: props.worksheet, data: props.data, columns: props.columns }];
|
|
491
495
|
}
|
|
492
496
|
|
|
493
497
|
var totalRows = sheetList.reduce(function (sum, s) { return sum + s.data.length; }, 0);
|
|
@@ -520,15 +524,18 @@ function jsonToExcel(Obj) {
|
|
|
520
524
|
if (sheetIndex >= sheetList.length) { finalize(); return; }
|
|
521
525
|
var sheetCfg = sheetList[sheetIndex];
|
|
522
526
|
var worksheet = workbook.addWorksheet(sheetCfg.name || 'Sheet');
|
|
527
|
+
var cols = Array.isArray(sheetCfg.columns) && sheetCfg.columns.length ? sheetCfg.columns : null;
|
|
523
528
|
// Create header (chỉ cần đọc key của dòng đầu, tương đương logic cũ nhưng không lặp check index===0 mỗi vòng)
|
|
524
|
-
|
|
529
|
+
if (cols) setupColumns(worksheet, cols, sheetCfg.data);
|
|
530
|
+
else worksheet.columns = Object.keys(sheetCfg.data[0]).map(function (k) { return { header: k, key: k }; });
|
|
525
531
|
|
|
526
532
|
var rows = sheetCfg.data;
|
|
527
533
|
var i = 0;
|
|
528
534
|
function nextChunk() {
|
|
529
535
|
try {
|
|
530
536
|
var end = Math.min(i + CHUNK_SIZE, rows.length);
|
|
531
|
-
for (var r = i; r < end; r++) { worksheet.addRow(rows[r]); }
|
|
537
|
+
for (var r = i; r < end; r++) { worksheet.addRow(cols ? columnRowValues(cols, rows[r]) : rows[r]); }
|
|
538
|
+
if (cols && end === rows.length) appendSumRow(worksheet, cols, rows);
|
|
532
539
|
processedRows += (end - i);
|
|
533
540
|
i = end;
|
|
534
541
|
report('building', sheetCfg.name);
|
|
@@ -574,6 +581,84 @@ function jsonToExcel(Obj) {
|
|
|
574
581
|
buildSheet(0);
|
|
575
582
|
}
|
|
576
583
|
|
|
584
|
+
//---------------------------------------------------- chế độ `columns`
|
|
585
|
+
function readColumnValue(row, key) {
|
|
586
|
+
if (row === null || row === undefined) return null;
|
|
587
|
+
if (row[key] !== undefined) return row[key];
|
|
588
|
+
return _.get(row, key, null);
|
|
589
|
+
}
|
|
590
|
+
// ExcelJS ghi Date theo UTC → dựng lại mốc UTC từ giờ ĐỊA PHƯƠNG, nếu không ngày 00:00 ở UTC+7
|
|
591
|
+
// sẽ lùi thành 17:00 hôm trước trong Excel. "yyyy-mm-dd" trần thì new Date() hiểu là UTC nên tách riêng.
|
|
592
|
+
function toExcelDate(v) {
|
|
593
|
+
if (v === null || v === undefined || v === '') return null;
|
|
594
|
+
var m = typeof v === 'string' && /^(\d{4})-(\d{2})-(\d{2})$/.exec(v);
|
|
595
|
+
if (m) return new Date(Date.UTC(+m[1], +m[2] - 1, +m[3]));
|
|
596
|
+
var d = v instanceof Date ? v : new Date(v);
|
|
597
|
+
if (isNaN(d.getTime())) return v;
|
|
598
|
+
return new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate(), d.getHours(), d.getMinutes(), d.getSeconds()));
|
|
599
|
+
}
|
|
600
|
+
function htmlToText(v) {
|
|
601
|
+
var s = String(v);
|
|
602
|
+
if (s.indexOf('<') < 0 && s.indexOf('&') < 0) return s;
|
|
603
|
+
// DOMParser không chạy script/handler — innerHTML trên element sống thì <img onerror> VẪN chạy.
|
|
604
|
+
return new DOMParser().parseFromString(s, 'text/html').body.textContent;
|
|
605
|
+
}
|
|
606
|
+
function convertCellValue(col, v) {
|
|
607
|
+
if (v === undefined) return null;
|
|
608
|
+
if (col.type === 'date') return toExcelDate(v);
|
|
609
|
+
if (v === null) return null;
|
|
610
|
+
if (col.type === 'number') return (typeof v === 'string' && v.trim() !== '' && !isNaN(v)) ? Number(v) : v;
|
|
611
|
+
if (col.type === 'html') return htmlToText(v);
|
|
612
|
+
// object/array đưa thẳng vào ExcelJS sẽ bị hiểu nhầm là rich text/hyperlink/formula
|
|
613
|
+
if (typeof v === 'object' && !(v instanceof Date)) return JSON.stringify(v);
|
|
614
|
+
return v;
|
|
615
|
+
}
|
|
616
|
+
function columnRowValues(cols, row) {
|
|
617
|
+
var out = new Array(cols.length);
|
|
618
|
+
for (var c = 0; c < cols.length; c++) out[c] = convertCellValue(cols[c], readColumnValue(row, cols[c].key));
|
|
619
|
+
return out;
|
|
620
|
+
}
|
|
621
|
+
function estimateWidth(col, rows) {
|
|
622
|
+
var w = String(col.header != null ? col.header : col.key).length;
|
|
623
|
+
if (col.type === 'date') w = Math.max(w, String(col.numFmt || 'dd/mm/yyyy').replace(/"/g, '').length);
|
|
624
|
+
else {
|
|
625
|
+
for (var i = 0, n = Math.min(rows.length, 100); i < n; i++) {
|
|
626
|
+
var v = readColumnValue(rows[i], col.key);
|
|
627
|
+
if (v !== null && v !== undefined) w = Math.max(w, String(v).length + (typeof v === 'number' ? 3 : 0));
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
return Math.min(60, Math.max(8, w + 2));
|
|
631
|
+
}
|
|
632
|
+
function setupColumns(worksheet, cols, rows) {
|
|
633
|
+
// key nội bộ theo vị trí: hai cột trùng tiêu đề/trùng field vẫn không đè nhau (dòng ghi bằng mảng)
|
|
634
|
+
worksheet.columns = cols.map(function (c, idx) {
|
|
635
|
+
var def = { header: c.header != null ? String(c.header) : String(c.key), key: '__c' + idx, width: c.width || estimateWidth(c, rows) };
|
|
636
|
+
if (c.numFmt) def.style = { numFmt: c.numFmt };
|
|
637
|
+
return def;
|
|
638
|
+
});
|
|
639
|
+
worksheet.getRow(1).font = { bold: true };
|
|
640
|
+
worksheet.views = [{ state: 'frozen', ySplit: 1 }];
|
|
641
|
+
}
|
|
642
|
+
// Hàng tổng = công thức SUM trên chính các dòng đã xuất (kèm kết quả tính sẵn để trình xem không
|
|
643
|
+
// tính công thức vẫn thấy số) — sửa số trong Excel thì tổng tự cập nhật.
|
|
644
|
+
function appendSumRow(worksheet, cols, rows) {
|
|
645
|
+
if (!rows.length || !cols.some(function (c) { return c.sum; })) return;
|
|
646
|
+
var row = worksheet.addRow(cols.map(function (c, idx) {
|
|
647
|
+
if (!c.sum) return null;
|
|
648
|
+
var total = 0;
|
|
649
|
+
for (var i = 0; i < rows.length; i++) {
|
|
650
|
+
var n = Number(readColumnValue(rows[i], c.key));
|
|
651
|
+
if (!isNaN(n)) total += n;
|
|
652
|
+
}
|
|
653
|
+
var letter = worksheet.getColumn(idx + 1).letter;
|
|
654
|
+
return { formula: 'SUM(' + letter + '2:' + letter + (rows.length + 1) + ')', result: total };
|
|
655
|
+
}));
|
|
656
|
+
row.font = { bold: true };
|
|
657
|
+
cols.forEach(function (c, idx) {
|
|
658
|
+
if (typeof c.sum === 'string') row.getCell(idx + 1).numFmt = c.sum;
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
|
|
577
662
|
// Thanh % mặc định, tự tạo/tự dọn bằng DOM thuần (CSS thủ công, không dùng v-dialog/framework nào)
|
|
578
663
|
// — fix cố định giữa màn hình, không phụ thuộc Vue nên dùng chung được cho cả V2 và V3.
|
|
579
664
|
function createProgressUI() {
|
|
@@ -660,6 +660,10 @@ function jsonToExcel(Obj) {
|
|
|
660
660
|
filename: "dataFP.xlsx", // filename to export
|
|
661
661
|
worksheet: "SheetFP", // sheet name for single sheet
|
|
662
662
|
sheets: null, // New property for multiple sheets
|
|
663
|
+
// optional: [{header, key, type?: 'number'|'date'|'html', numFmt?, width?, sum?: true|numFmt}]
|
|
664
|
+
// Có columns thì cột/tiêu đề/định dạng theo đúng danh sách này (dùng cho f-table excel="UI");
|
|
665
|
+
// không có thì giữ hành vi cũ: cột = key của dòng đầu, giá trị thô. Mỗi sheet có thể có columns riêng.
|
|
666
|
+
columns: null,
|
|
663
667
|
onProgress: null, // optional: function({percent, processedRows, totalRows, phase, sheetName}) - phase: 'building'|'finalizing'|'done'|'error'
|
|
664
668
|
showProgress: true // tự hiện thanh % cố định giữa màn hình khi xuất; truyền false để tắt UI này
|
|
665
669
|
}
|
|
@@ -672,7 +676,7 @@ function jsonToExcel(Obj) {
|
|
|
672
676
|
if (!sheetList.length) { showMessage({ title: "Sheets data is empty" }); return; }
|
|
673
677
|
} else {
|
|
674
678
|
if (!props.data || !props.data.length) { showMessage({ title: "Data Null" }); return; }
|
|
675
|
-
sheetList = [{ name: props.worksheet, data: props.data }];
|
|
679
|
+
sheetList = [{ name: props.worksheet, data: props.data, columns: props.columns }];
|
|
676
680
|
}
|
|
677
681
|
|
|
678
682
|
var totalRows = sheetList.reduce(function (sum, s) { return sum + s.data.length; }, 0);
|
|
@@ -705,15 +709,18 @@ function jsonToExcel(Obj) {
|
|
|
705
709
|
if (sheetIndex >= sheetList.length) { finalize(); return; }
|
|
706
710
|
var sheetCfg = sheetList[sheetIndex];
|
|
707
711
|
var worksheet = workbook.addWorksheet(sheetCfg.name || 'Sheet');
|
|
712
|
+
var cols = Array.isArray(sheetCfg.columns) && sheetCfg.columns.length ? sheetCfg.columns : null;
|
|
708
713
|
// Create header (chỉ cần đọc key của dòng đầu, tương đương logic cũ nhưng không lặp check index===0 mỗi vòng)
|
|
709
|
-
|
|
714
|
+
if (cols) setupColumns(worksheet, cols, sheetCfg.data);
|
|
715
|
+
else worksheet.columns = Object.keys(sheetCfg.data[0]).map(function (k) { return { header: k, key: k }; });
|
|
710
716
|
|
|
711
717
|
var rows = sheetCfg.data;
|
|
712
718
|
var i = 0;
|
|
713
719
|
function nextChunk() {
|
|
714
720
|
try {
|
|
715
721
|
var end = Math.min(i + CHUNK_SIZE, rows.length);
|
|
716
|
-
for (var r = i; r < end; r++) { worksheet.addRow(rows[r]); }
|
|
722
|
+
for (var r = i; r < end; r++) { worksheet.addRow(cols ? columnRowValues(cols, rows[r]) : rows[r]); }
|
|
723
|
+
if (cols && end === rows.length) appendSumRow(worksheet, cols, rows);
|
|
717
724
|
processedRows += (end - i);
|
|
718
725
|
i = end;
|
|
719
726
|
report('building', sheetCfg.name);
|
|
@@ -759,6 +766,84 @@ function jsonToExcel(Obj) {
|
|
|
759
766
|
buildSheet(0);
|
|
760
767
|
}
|
|
761
768
|
|
|
769
|
+
//---------------------------------------------------- chế độ `columns`
|
|
770
|
+
function readColumnValue(row, key) {
|
|
771
|
+
if (row === null || row === undefined) return null;
|
|
772
|
+
if (row[key] !== undefined) return row[key];
|
|
773
|
+
return _.get(row, key, null);
|
|
774
|
+
}
|
|
775
|
+
// ExcelJS ghi Date theo UTC → dựng lại mốc UTC từ giờ ĐỊA PHƯƠNG, nếu không ngày 00:00 ở UTC+7
|
|
776
|
+
// sẽ lùi thành 17:00 hôm trước trong Excel. "yyyy-mm-dd" trần thì new Date() hiểu là UTC nên tách riêng.
|
|
777
|
+
function toExcelDate(v) {
|
|
778
|
+
if (v === null || v === undefined || v === '') return null;
|
|
779
|
+
var m = typeof v === 'string' && /^(\d{4})-(\d{2})-(\d{2})$/.exec(v);
|
|
780
|
+
if (m) return new Date(Date.UTC(+m[1], +m[2] - 1, +m[3]));
|
|
781
|
+
var d = v instanceof Date ? v : new Date(v);
|
|
782
|
+
if (isNaN(d.getTime())) return v;
|
|
783
|
+
return new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate(), d.getHours(), d.getMinutes(), d.getSeconds()));
|
|
784
|
+
}
|
|
785
|
+
function htmlToText(v) {
|
|
786
|
+
var s = String(v);
|
|
787
|
+
if (s.indexOf('<') < 0 && s.indexOf('&') < 0) return s;
|
|
788
|
+
// DOMParser không chạy script/handler — innerHTML trên element sống thì <img onerror> VẪN chạy.
|
|
789
|
+
return new DOMParser().parseFromString(s, 'text/html').body.textContent;
|
|
790
|
+
}
|
|
791
|
+
function convertCellValue(col, v) {
|
|
792
|
+
if (v === undefined) return null;
|
|
793
|
+
if (col.type === 'date') return toExcelDate(v);
|
|
794
|
+
if (v === null) return null;
|
|
795
|
+
if (col.type === 'number') return (typeof v === 'string' && v.trim() !== '' && !isNaN(v)) ? Number(v) : v;
|
|
796
|
+
if (col.type === 'html') return htmlToText(v);
|
|
797
|
+
// object/array đưa thẳng vào ExcelJS sẽ bị hiểu nhầm là rich text/hyperlink/formula
|
|
798
|
+
if (typeof v === 'object' && !(v instanceof Date)) return JSON.stringify(v);
|
|
799
|
+
return v;
|
|
800
|
+
}
|
|
801
|
+
function columnRowValues(cols, row) {
|
|
802
|
+
var out = new Array(cols.length);
|
|
803
|
+
for (var c = 0; c < cols.length; c++) out[c] = convertCellValue(cols[c], readColumnValue(row, cols[c].key));
|
|
804
|
+
return out;
|
|
805
|
+
}
|
|
806
|
+
function estimateWidth(col, rows) {
|
|
807
|
+
var w = String(col.header != null ? col.header : col.key).length;
|
|
808
|
+
if (col.type === 'date') w = Math.max(w, String(col.numFmt || 'dd/mm/yyyy').replace(/"/g, '').length);
|
|
809
|
+
else {
|
|
810
|
+
for (var i = 0, n = Math.min(rows.length, 100); i < n; i++) {
|
|
811
|
+
var v = readColumnValue(rows[i], col.key);
|
|
812
|
+
if (v !== null && v !== undefined) w = Math.max(w, String(v).length + (typeof v === 'number' ? 3 : 0));
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
return Math.min(60, Math.max(8, w + 2));
|
|
816
|
+
}
|
|
817
|
+
function setupColumns(worksheet, cols, rows) {
|
|
818
|
+
// key nội bộ theo vị trí: hai cột trùng tiêu đề/trùng field vẫn không đè nhau (dòng ghi bằng mảng)
|
|
819
|
+
worksheet.columns = cols.map(function (c, idx) {
|
|
820
|
+
var def = { header: c.header != null ? String(c.header) : String(c.key), key: '__c' + idx, width: c.width || estimateWidth(c, rows) };
|
|
821
|
+
if (c.numFmt) def.style = { numFmt: c.numFmt };
|
|
822
|
+
return def;
|
|
823
|
+
});
|
|
824
|
+
worksheet.getRow(1).font = { bold: true };
|
|
825
|
+
worksheet.views = [{ state: 'frozen', ySplit: 1 }];
|
|
826
|
+
}
|
|
827
|
+
// Hàng tổng = công thức SUM trên chính các dòng đã xuất (kèm kết quả tính sẵn để trình xem không
|
|
828
|
+
// tính công thức vẫn thấy số) — sửa số trong Excel thì tổng tự cập nhật.
|
|
829
|
+
function appendSumRow(worksheet, cols, rows) {
|
|
830
|
+
if (!rows.length || !cols.some(function (c) { return c.sum; })) return;
|
|
831
|
+
var row = worksheet.addRow(cols.map(function (c, idx) {
|
|
832
|
+
if (!c.sum) return null;
|
|
833
|
+
var total = 0;
|
|
834
|
+
for (var i = 0; i < rows.length; i++) {
|
|
835
|
+
var n = Number(readColumnValue(rows[i], c.key));
|
|
836
|
+
if (!isNaN(n)) total += n;
|
|
837
|
+
}
|
|
838
|
+
var letter = worksheet.getColumn(idx + 1).letter;
|
|
839
|
+
return { formula: 'SUM(' + letter + '2:' + letter + (rows.length + 1) + ')', result: total };
|
|
840
|
+
}));
|
|
841
|
+
row.font = { bold: true };
|
|
842
|
+
cols.forEach(function (c, idx) {
|
|
843
|
+
if (typeof c.sum === 'string') row.getCell(idx + 1).numFmt = c.sum;
|
|
844
|
+
});
|
|
845
|
+
}
|
|
846
|
+
|
|
762
847
|
// Thanh % mặc định, tự tạo/tự dọn bằng DOM thuần (CSS thủ công, không dùng v-dialog/framework nào)
|
|
763
848
|
// — fix cố định giữa màn hình, không phụ thuộc Vue nên dùng chung được cho cả V2 và V3.
|
|
764
849
|
function createProgressUI() {
|
|
@@ -696,8 +696,35 @@ Vue.component('FSheet', {
|
|
|
696
696
|
hit.forEach(function (cell) { cell.classList.remove('ag-cell-copy-flash') })
|
|
697
697
|
}, 800)
|
|
698
698
|
},
|
|
699
|
+
// true khi một ô đang ở chế độ sửa (AG Grid đã render <input> thật bên trong và focus
|
|
700
|
+
// vào đó) — dùng ĐÚNG API của AG Grid thay vì đoán qua e.target.tagName, vì cellEditor
|
|
701
|
+
// tuỳ biến có thể không dùng thẻ <input>/<textarea>. Lưới an toàn cho ca cellEditor lạ;
|
|
702
|
+
// ca THƯỜNG GẶP (input chuẩn) đã được isNativeTextTarget() bắt trực tiếp, xem dưới.
|
|
703
|
+
isEditingCell: function () {
|
|
704
|
+
return !!(this.gridApi && this.gridApi.getEditingCells().length > 0)
|
|
705
|
+
},
|
|
706
|
+
// true khi TARGET THẬT của sự kiện copy/paste là một control nhập liệu chuẩn của trình
|
|
707
|
+
// duyệt (input/textarea/contentEditable) — không phải ô lưới. Bắt buộc kiểm theo TARGET,
|
|
708
|
+
// không chỉ theo "đang sửa ô": ô lọc (filter) trong header AG Grid — cả floating filter
|
|
709
|
+
// lẫn popup filter đầy đủ — cũng render một <input> thật NẰM TRONG chính gridEl (fsheet
|
|
710
|
+
// không set popupParent nên popup filter không thoát ra ngoài gridEl), và sự kiện
|
|
711
|
+
// copy/paste của nó nổi bọt lên tới đúng listener này giống hệt input của ô đang sửa.
|
|
712
|
+
// Trước đây chỉ hỏi isEditingCell() nên bắt đúng ca sửa ô nhưng bỏ sót ca filter: đặt con
|
|
713
|
+
// trỏ vào ô filter rồi Ctrl+V lại bị "cướp" xuống dán vào ô đang chọn/focus ở lưới, thay vì
|
|
714
|
+
// dán vào chính ô filter đang gõ.
|
|
715
|
+
isNativeTextTarget: function (e) {
|
|
716
|
+
var t = e && e.target
|
|
717
|
+
if (!t) return false
|
|
718
|
+
var tag = t.tagName
|
|
719
|
+
return tag === 'INPUT' || tag === 'TEXTAREA' || !!t.isContentEditable
|
|
720
|
+
},
|
|
699
721
|
doCopy: function (e) {
|
|
700
722
|
var self = this
|
|
723
|
+
// Con trỏ đang ở TRONG một input thật (ô đang sửa, hoặc ô lọc/filter): người dùng có
|
|
724
|
+
// thể chỉ đang copy một PHẦN chữ đã bôi đen trong input đó (không phải cả ô/cả lưới) —
|
|
725
|
+
// để trình duyệt tự xử lý theo cách bình thường, không ghi đè bằng nội dung của cả
|
|
726
|
+
// vùng/ô đang chọn ở tầng lưới.
|
|
727
|
+
if (self.isEditingCell() || self.isNativeTextTarget(e)) return
|
|
701
728
|
var r = self.currentRangeOrFocus()
|
|
702
729
|
if (!r) return
|
|
703
730
|
var text = self.rangeText(r, false)
|
|
@@ -772,6 +799,17 @@ Vue.component('FSheet', {
|
|
|
772
799
|
doPaste: function (e) {
|
|
773
800
|
var self = this
|
|
774
801
|
if (self.readonly) return
|
|
802
|
+
// Con trỏ đang ở TRONG một input thật (ô đang sửa, HOẶC ô lọc/filter trong header):
|
|
803
|
+
// sự kiện 'paste' nổi lên từ chính input đó lên tới gridEl (nơi handler này được gắn),
|
|
804
|
+
// và trước đây bị chặn vô điều kiện bởi e.preventDefault() + pasteText() bên dưới —
|
|
805
|
+
// pasteText() ghi thẳng vào data của Ô ĐANG FOCUS/VÙNG ĐANG CHỌN Ở LƯỚI qua
|
|
806
|
+
// node.setDataValue(), KHÔNG đụng tới input đang hiển thị. Hai triệu chứng khác nhau
|
|
807
|
+
// của cùng một lỗi: (1) đang sửa ô → paste trông như "không có tác dụng gì" (giá trị
|
|
808
|
+
// dán chỉ hiện ra sau khi rời ô sửa, rồi bị input ghi đè lại lúc blur); (2) đang gõ vào
|
|
809
|
+
// ô filter → paste "chạy nhầm chỗ" (dán đè lên ô đang chọn trong lưới thay vì vào ô
|
|
810
|
+
// filter). Phải để trình duyệt tự dán vào đúng vị trí con trỏ trong input đó như một
|
|
811
|
+
// thao tác nhập liệu bình thường.
|
|
812
|
+
if (self.isEditingCell() || self.isNativeTextTarget(e)) return
|
|
775
813
|
var cb = e.clipboardData || window.clipboardData
|
|
776
814
|
var text = cb ? cb.getData('text') : ''
|
|
777
815
|
if (!text) return
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{ "modules": ["./register.tsx"] }
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { atom, read, update } from "claude-code";
|
|
2
|
+
import type { Engine, Register } from "claude-code";
|
|
3
|
+
|
|
4
|
+
import type { FuiTarget } from "../types";
|
|
5
|
+
|
|
6
|
+
const target = atom({ plugin: "fui-target", key: "target" } as const, null);
|
|
7
|
+
|
|
8
|
+
const FUI_CALL = /(?:^|[\s;&|(])fui\s+([a-z-]+)(?:\s+([a-z-]+))?/;
|
|
9
|
+
|
|
10
|
+
// Mirrors sessionFile() in src/core/session/state.ts.
|
|
11
|
+
async function sessionFile($: Engine, command: string) {
|
|
12
|
+
const key = command.match(/FUI_SESSION=([A-Za-z0-9_-]+)/)?.[1] ?? (await $.env.get("FUI_SESSION")) ?? "default";
|
|
13
|
+
const dir =
|
|
14
|
+
(await $.env.get("FUI_SESSION_DIR")) ??
|
|
15
|
+
(await $.env.get("FUI_MCP_SESSION_DIR")) ??
|
|
16
|
+
`${await $.env.get("HOME")}/.fui-mcp/session`;
|
|
17
|
+
return `${dir}/cli-${key}.json`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function sync($: Engine, command: string, lastCommand: string) {
|
|
21
|
+
const text = await $.fs.read(await sessionFile($, command)).catch(() => "");
|
|
22
|
+
let state: { target?: { projectId?: string; moduleId?: string } } = {};
|
|
23
|
+
try {
|
|
24
|
+
state = JSON.parse(text || "{}");
|
|
25
|
+
} catch {}
|
|
26
|
+
|
|
27
|
+
await update($, target, () => ({
|
|
28
|
+
project: state.target?.projectId,
|
|
29
|
+
module: state.target?.moduleId,
|
|
30
|
+
lastCommand,
|
|
31
|
+
}));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const register: Register = (on) => {
|
|
35
|
+
on("tool.call", { tool: "Bash" }, async ($, e, next) => {
|
|
36
|
+
const result = await next(e);
|
|
37
|
+
const call = e.command.match(FUI_CALL);
|
|
38
|
+
if (!call) return result;
|
|
39
|
+
|
|
40
|
+
await sync($, e.command, [call[1], call[2]].filter(Boolean).join(" "));
|
|
41
|
+
return result;
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
on("ui.render", { component: "AbovePrompt" }, async ($, e, next) => {
|
|
45
|
+
const current: FuiTarget | null = await read($, target);
|
|
46
|
+
if (e.props.hasSurvey || current === null) return next(e);
|
|
47
|
+
|
|
48
|
+
const { Box, Text } = $.ui.resolve(e);
|
|
49
|
+
return (
|
|
50
|
+
<Box flexDirection="row" columnGap={1}>
|
|
51
|
+
<Text>🧩</Text>
|
|
52
|
+
{current.project ? (
|
|
53
|
+
<Text>
|
|
54
|
+
<Text bold color="cyan">
|
|
55
|
+
{current.project}
|
|
56
|
+
</Text>
|
|
57
|
+
{current.module && (
|
|
58
|
+
<Text>
|
|
59
|
+
<Text dimColor> › </Text>
|
|
60
|
+
<Text bold>{current.module}</Text>
|
|
61
|
+
</Text>
|
|
62
|
+
)}
|
|
63
|
+
</Text>
|
|
64
|
+
) : (
|
|
65
|
+
<Text dimColor>no active module</Text>
|
|
66
|
+
)}
|
|
67
|
+
{current.lastCommand && <Text dimColor> · fui {current.lastCommand}</Text>}
|
|
68
|
+
</Box>
|
|
69
|
+
);
|
|
70
|
+
});
|
|
71
|
+
};
|