rj-grid 0.1.0 → 0.2.1

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 CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  自研**零第三方依赖**的企业级数据网格(Vue 3 + TypeScript),功能规格对标 AG Grid:虚拟滚动、冻结列、行/列拖拽、分组透视、树形、编辑与撤销、公式引擎、SSRM 服务端行模型、主题与 i18n、打印/CSV/Excel 导出、离线自然语言查询(NLQ)。gzip 约 93 KB(不含 vue / echarts)。
4
4
 
5
- 本仓库是该组件的**独立维护仓**:源码、单测、构建链、演示 playground、使用手册都在这里;宿主项目(如 join-cloud-vue3)经 npm 的 git URL 安装产物,不参与组件开发。
5
+ 本仓库是该组件的**独立维护仓**:源码、单测、构建链、演示 playground、使用手册都在这里;产物已发布到**公共 npm(包名 `rj-grid`)**,宿主项目(如 join-cloud-vue3)按版本号安装,不参与组件开发。
6
6
 
7
7
  ## 仓库布局
8
8
 
@@ -33,18 +33,24 @@ pnpm size # 体积报告(可选)
33
33
  pnpm dev # playground 演示页(默认 :5173,alias 走本地源码)
34
34
  ```
35
35
 
36
- 改完源码的发布纪律:`pnpm build` → 让源码与 `dist/` 处在**同一个 commit** → 打 tag → push。
36
+ 发布新版本:改完源码 → `npm version patch|minor|major`(自动 bump + commit + tag)→ `git push --follow-tags` → `npm publish`(`prepublishOnly` 会自动 `pnpm build && pnpm test`,杜绝发出旧 dist)。
37
37
 
38
- ## 宿主接入(git URL)
38
+ ## 宿主接入(npm 版本号)
39
39
 
40
40
  ```jsonc
41
- // 宿主 package.json
41
+ // 宿主 package.json —— 从公共 npm 按语义化版本安装
42
42
  "dependencies": {
43
- "rj-grid": "git+https://gitee.com/rjding/rj-grid.git#v0.1.0"
43
+ "rj-grid": "^0.1.0"
44
44
  }
45
45
  ```
46
46
 
47
- 样式**不要**在 JS 里裸引 `rj-grid/style.css`:pnpm 会把 git 依赖装进带 `#<commit>` 的目录名,vite 输出的资源 URL 含裸 `#`,浏览器把 `#` 之后当片段丢掉 → dev 下 404。改走一层 CSS 垫片:
47
+ ```powershell
48
+ pnpm add rj-grid # 或 npm i rj-grid
49
+ ```
50
+
51
+ > 仍保留 git URL 方式(离线内网 / 需锁 commit 时):`"rj-grid": "git+https://gitee.com/rjding/rj-grid.git#v0.1.0"`。
52
+
53
+ 从 npm 安装时目录名规范(`node_modules/rj-grid`),样式可**直接**引:`import 'rj-grid/style.css'`。仅当走 git URL 时才需下面的 CSS 垫片——pnpm 会把 git 依赖装进带 `#<commit>` 的目录名,vite 输出的资源 URL 含裸 `#`,浏览器把 `#` 之后当片段丢掉 → dev 下 404:
48
54
 
49
55
  ```css
50
56
  /* 宿主 src/styles/rjgrid/index.css */
@@ -67,21 +73,21 @@ peer 依赖:`vue ^3.4.0` 必装;`echarts >=5.0.0` 可选(仅「集成图
67
73
  ## 升级与回滚
68
74
 
69
75
  ```powershell
70
- pnpm update rj-grid # 跟到 URL 允许的最新 commit
71
- pnpm install --force # 或改 #<tag> 后强制重装
76
+ pnpm update rj-grid # 在 ^0.1.0 范围内升到最新
77
+ pnpm add rj-grid@latest # 跨范围拉最新
72
78
  ```
73
79
 
74
- lockfile 里记录的是具体 commit(`specifier` 存 tag,`resolution` 存 commit),因此 CI / 他人 `pnpm install --frozen-lockfile` 可精确复现。本地联调可把 URL 临时换成 `"link:../rjGrid"`,调完记得换回 git URL 并重装。
80
+ npm 安装的 lockfile 记录精确版本与 `integrity`,CI / 他人 `pnpm install --frozen-lockfile` 可复现。回滚只需把版本钉回旧号(如 `"rj-grid": "0.1.0"`)。本地联调可临时用 `"link:../rjGrid"`,调完换回版本号并重装。
75
81
 
76
82
  ## 为什么 dist 入库
77
83
 
78
- npm/pnpm 安装 git 依赖时按 `files` 字段(`["dist","README.md"]`)对仓库内容做 pack 式裁剪,**不会**在宿主机跑 `prepare` 重建。所以产物必须提交进仓库:只推源码不推 `dist/`,安装方会拿到空包。代价是 commit 里要带上产物,收益是安装期零工具链、跨机器结果一致。
84
+ 发布走 `prepublishOnly`(`pnpm build && pnpm test`):`npm publish` 打的是**构建后磁盘上的 `dist/`**(按 `files` 字段 `["dist","README.md"]` 裁剪),所以公共 npm 消费方无需依赖仓库里的 dist。dist 仍入库只是为了兼容 **git URL 安装**那条路径(git 依赖不会在宿主机跑 `prepare` 重建);保留 dist 入库能同时喂饱两条路。
79
85
 
80
86
  `build/rjDistCheck.mjs` 的五道守卫同时在把关:外部 import 只允许 `vue`/`echarts`;CSS 不引宿主资源;d.ts 不断链;dist 不得混入 `.vue/.ts/.scss` 源码;**不得带 sourcemap**——vite 的 `.map` 内嵌 `sourcesContent`(原始源码),一发包等于源码同步外流,因此 `vite.lib.config.ts` 里 `sourcemap: false` 是源码保护而非可选项。
81
87
 
82
88
  ## 私有仓注意
83
89
 
84
- 本仓库为 gitee 私有仓时,安装方机器必须具备克隆凭据(HTTPS 走 credential manager 里的 gitee 账号,或改用有权限的部署密钥);CI 环境需预先注入凭据,否则 `pnpm install` 会卡在 git 认证。
90
+ 已发布到**公共 npm**,按版本号安装无需任何 git 凭据。仅当某宿主仍走 git URL 且仓库为 gitee 私有仓时,安装机才需具备克隆凭据(HTTPS 走 credential manager 里的 gitee 账号或部署密钥),CI 需预置,否则会卡在 git 认证。
85
91
 
86
92
  ## 文档
87
93
 
package/dist/rj-grid.js CHANGED
@@ -773,6 +773,22 @@ function deriveQueryFields(columns, override) {
773
773
  }
774
774
  return out;
775
775
  }
776
+ function withCarrierOptions(fields, carrier) {
777
+ if (!fields.length || !carrier || !carrier.size)
778
+ return fields;
779
+ return fields.map((f) => {
780
+ if (f.options && f.options.length)
781
+ return f;
782
+ const hit = carrier.get(f.field);
783
+ if (!hit || !hit.list || !hit.list.length)
784
+ return f;
785
+ return {
786
+ ...f,
787
+ kind: "select",
788
+ options: hit.list.map((o) => ({ label: o.label, value: o.value }))
789
+ };
790
+ });
791
+ }
776
792
  function queryOpsOfKind(kind) {
777
793
  if (kind === "number")
778
794
  return ["eq", "gt", "gte", "lt", "lte", "between"];
@@ -4506,6 +4522,125 @@ function translate(messages, key, params) {
4506
4522
  }
4507
4523
  const RJ_LOCALE_KEY = Symbol("rj-locale");
4508
4524
  const defaultTranslate = (key, params) => interpolate(zhCN[key] != null ? zhCN[key] : key, params);
4525
+ const RJ_OPTIONS_KEY = Symbol("rj-options");
4526
+ const defaultOptionsAccessor = {
4527
+ list: () => void 0,
4528
+ label: () => void 0
4529
+ };
4530
+ function pickValue(item, valueKey) {
4531
+ if (valueKey)
4532
+ return item[valueKey];
4533
+ if (item.value !== void 0)
4534
+ return item.value;
4535
+ if (item.id !== void 0)
4536
+ return item.id;
4537
+ if (item.code !== void 0)
4538
+ return item.code;
4539
+ if (item.key !== void 0)
4540
+ return item.key;
4541
+ return void 0;
4542
+ }
4543
+ function pickLabel(item, labelKey, value) {
4544
+ if (labelKey)
4545
+ return String(item[labelKey] ?? value);
4546
+ if (item.label !== void 0)
4547
+ return String(item.label);
4548
+ if (item.name !== void 0)
4549
+ return String(item.name);
4550
+ if (item.text !== void 0)
4551
+ return String(item.text);
4552
+ return String(value);
4553
+ }
4554
+ function normalizeOptions(raw, shape = {}) {
4555
+ const out = [];
4556
+ if (!Array.isArray(raw))
4557
+ return out;
4558
+ const ck = shape.childrenKey ?? "children";
4559
+ const walk = (arr) => {
4560
+ for (const it of arr) {
4561
+ if (it == null || typeof it !== "object") {
4562
+ if (it !== null && it !== void 0)
4563
+ out.push({ label: String(it), value: it });
4564
+ continue;
4565
+ }
4566
+ const item = it;
4567
+ if (shape.map) {
4568
+ out.push(shape.map(item));
4569
+ } else {
4570
+ const value = pickValue(item, shape.valueKey);
4571
+ const label = pickLabel(item, shape.labelKey, value);
4572
+ out.push({ label, value });
4573
+ }
4574
+ if (Array.isArray(item[ck]))
4575
+ walk(item[ck]);
4576
+ }
4577
+ };
4578
+ walk(raw);
4579
+ return out;
4580
+ }
4581
+ async function resolveOptions(source, dict, loaders) {
4582
+ var _a, _b, _c;
4583
+ try {
4584
+ if (dict)
4585
+ return normalizeOptions(await ((_a = loaders.dictLoader) == null ? void 0 : _a.call(loaders, dict)));
4586
+ if (!source)
4587
+ return [];
4588
+ if (Array.isArray(source))
4589
+ return normalizeOptions(source);
4590
+ if (typeof source === "function")
4591
+ return normalizeOptions(await source());
4592
+ const shape = source;
4593
+ if (source.items)
4594
+ return normalizeOptions(source.items, shape);
4595
+ if (source.dict)
4596
+ return normalizeOptions(await ((_b = loaders.dictLoader) == null ? void 0 : _b.call(loaders, source.dict)), shape);
4597
+ if (source.ref)
4598
+ return normalizeOptions(await ((_c = loaders.optionsLoader) == null ? void 0 : _c.call(loaders, source.ref)), shape);
4599
+ if (source.load)
4600
+ return normalizeOptions(await source.load(), shape);
4601
+ } catch {
4602
+ }
4603
+ return [];
4604
+ }
4605
+ let _fnSeq = 0;
4606
+ const fnKeys = /* @__PURE__ */ new WeakMap();
4607
+ function fnKey(fn, prefix) {
4608
+ let k = fnKeys.get(fn);
4609
+ if (!k) {
4610
+ k = `${prefix}:${++_fnSeq}`;
4611
+ fnKeys.set(fn, k);
4612
+ }
4613
+ return k;
4614
+ }
4615
+ function sourceCacheKey(col) {
4616
+ if (col.dict)
4617
+ return "dict:" + col.dict;
4618
+ const s = col.options;
4619
+ if (!s)
4620
+ return null;
4621
+ if (Array.isArray(s))
4622
+ return "static:" + colIdOf(col);
4623
+ if (typeof s === "function")
4624
+ return fnKey(s, "fn");
4625
+ if (s.dict)
4626
+ return "dict:" + s.dict;
4627
+ if (s.ref)
4628
+ return "ref:" + s.ref;
4629
+ if (s.load)
4630
+ return fnKey(s.load, "load");
4631
+ if (s.items)
4632
+ return "static:" + colIdOf(col);
4633
+ return null;
4634
+ }
4635
+ function collectOptionCols(cols, out = []) {
4636
+ for (const c of cols || []) {
4637
+ if (c.dict || c.options)
4638
+ out.push(c);
4639
+ if (c.children && c.children.length)
4640
+ collectOptionCols(c.children, out);
4641
+ }
4642
+ return out;
4643
+ }
4509
4644
  const NUM_OPS = [
4510
4645
  { k: "greater than or equal to", op: "gte" },
4511
4646
  { k: "more than or equal to", op: "gte" },
@@ -6184,6 +6319,7 @@ const _sfc_main$g = /* @__PURE__ */ defineComponent({
6184
6319
  emits: ["commit", "cancel"],
6185
6320
  setup(__props, { emit: __emit }) {
6186
6321
  const t = inject(RJ_LOCALE_KEY, defaultTranslate);
6322
+ const optionsAccessor = inject(RJ_OPTIONS_KEY, defaultOptionsAccessor);
6187
6323
  const props = __props;
6188
6324
  const emit = __emit;
6189
6325
  const cfg = computed(() => {
@@ -6225,9 +6361,16 @@ const _sfc_main$g = /* @__PURE__ */ defineComponent({
6225
6361
  );
6226
6362
  const options = computed(() => {
6227
6363
  const o = cfg.value.options;
6228
- if (!o)
6229
- return [];
6230
- return typeof o === "function" ? o(props.row) : o;
6364
+ if (typeof o === "function") {
6365
+ const r = o(props.row);
6366
+ if (Array.isArray(r) && r.length)
6367
+ return r;
6368
+ } else if (Array.isArray(o) && o.length) {
6369
+ return o;
6370
+ }
6371
+ if (isSelectLike.value)
6372
+ return optionsAccessor.list(props.column) || [];
6373
+ return Array.isArray(o) ? o : [];
6231
6374
  });
6232
6375
  const filteredOptions = computed(() => {
6233
6376
  if (!isSelectLike.value)
@@ -6962,6 +7105,7 @@ const _sfc_main$c = /* @__PURE__ */ defineComponent({
6962
7105
  setup(__props, { emit: __emit }) {
6963
7106
  var _a, _b, _c, _d, _e, _f, _g, _h, _i;
6964
7107
  const t = inject(RJ_LOCALE_KEY, defaultTranslate);
7108
+ const optionsAccessor = inject(RJ_OPTIONS_KEY, defaultOptionsAccessor);
6965
7109
  const props = __props;
6966
7110
  const emit = __emit;
6967
7111
  const ops = computed(() => FILTER_OPS[props.filterType] || []);
@@ -6993,7 +7137,7 @@ const _sfc_main$c = /* @__PURE__ */ defineComponent({
6993
7137
  });
6994
7138
  const displayOf = (v) => {
6995
7139
  var _a2;
6996
- return ((_a2 = props.column.filterValueMap) == null ? void 0 : _a2[v]) ?? v;
7140
+ return optionsAccessor.label(props.column, v) ?? ((_a2 = props.column.filterValueMap) == null ? void 0 : _a2[v]) ?? v;
6997
7141
  };
6998
7142
  const allChecked = computed(
6999
7143
  () => options.value.length > 0 && values.value.length >= options.value.length
@@ -9701,12 +9845,67 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
9701
9845
  queryFields: { default: () => [] },
9702
9846
  queryActions: { default: () => [] },
9703
9847
  rowForm: { type: [Object, Boolean], default: true },
9848
+ dictLoader: {},
9849
+ optionsLoader: {},
9704
9850
  builtinViews: { default: () => [] }
9705
9851
  },
9706
9852
  emits: ["selection-change", "cell-click", "cell-dblclick", "cell-value-changed", "cells-changed", "sort-change", "filter-change", "row-group-change", "pivot-change", "page-change", "view-change", "detail-open", "row-drag-end", "row-form-submit", "row-form-add", "ready"],
9707
9853
  setup(__props, { expose: __expose, emit: __emit }) {
9708
9854
  var _a;
9709
9855
  const props = __props;
9856
+ const optCache = reactive(/* @__PURE__ */ new Map());
9857
+ async function resolveAllOptions() {
9858
+ const pending = /* @__PURE__ */ new Map();
9859
+ for (const col of collectOptionCols(props.columns)) {
9860
+ const key = sourceCacheKey(col);
9861
+ if (!key || optCache.has(key) || pending.has(key))
9862
+ continue;
9863
+ pending.set(key, col);
9864
+ }
9865
+ await Promise.all(
9866
+ [...pending.entries()].map(async ([key, col]) => {
9867
+ const list = await resolveOptions(col.options, col.dict, {
9868
+ dictLoader: props.dictLoader,
9869
+ optionsLoader: props.optionsLoader
9870
+ });
9871
+ optCache.set(key, list);
9872
+ })
9873
+ );
9874
+ }
9875
+ const colOptionsIndex = computed(() => {
9876
+ const m = /* @__PURE__ */ new Map();
9877
+ for (const col of collectOptionCols(props.columns)) {
9878
+ const key = sourceCacheKey(col);
9879
+ const list = key ? optCache.get(key) : void 0;
9880
+ if (!list || !list.length)
9881
+ continue;
9882
+ const labelMap = /* @__PURE__ */ new Map();
9883
+ for (const o of list)
9884
+ labelMap.set(String(o.value), String(o.label));
9885
+ m.set(colIdOf(col), { list, labelMap });
9886
+ }
9887
+ return m;
9888
+ });
9889
+ function colOptionList(col) {
9890
+ var _a2;
9891
+ return (_a2 = colOptionsIndex.value.get(colIdOf(col))) == null ? void 0 : _a2.list;
9892
+ }
9893
+ function colOptionLabel(col, value) {
9894
+ var _a2;
9895
+ if (value == null || value === "")
9896
+ return void 0;
9897
+ return (_a2 = colOptionsIndex.value.get(colIdOf(col))) == null ? void 0 : _a2.labelMap.get(String(value));
9898
+ }
9899
+ provide(RJ_OPTIONS_KEY, { list: colOptionList, label: colOptionLabel });
9900
+ onMounted(() => {
9901
+ void resolveAllOptions();
9902
+ });
9903
+ watch(
9904
+ () => props.columns,
9905
+ () => {
9906
+ void resolveAllOptions();
9907
+ }
9908
+ );
9710
9909
  const emit = __emit;
9711
9910
  const busListeners = /* @__PURE__ */ new Map();
9712
9911
  function addEventListener(type, cb) {
@@ -9915,7 +10114,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
9915
10114
  watch(pipelineCols, (v) => rowModel.setPipelineColumns(v), { immediate: true });
9916
10115
  const queryConditions = rowModel.queryConditions;
9917
10116
  const queryFieldDefs = computed(
9918
- () => deriveQueryFields(userColumns.value, props.queryFields)
10117
+ () => withCarrierOptions(deriveQueryFields(userColumns.value, props.queryFields), colOptionsIndex.value)
9919
10118
  );
9920
10119
  const savedViews = useSavedViews(
9921
10120
  () => props.stateKey,
@@ -10588,11 +10787,16 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
10588
10787
  const type = rowModel.filterTypeOf(col);
10589
10788
  let unique = [];
10590
10789
  if (type === "select") {
10591
- unique = Array.from(
10592
- new Set(
10593
- rowModel.sourceRows().slice(0, 5e3).map((r) => cellRawValue(col, r)).filter((v) => v != null)
10594
- )
10595
- ).map(String);
10790
+ const optList = colOptionList(col);
10791
+ if (optList && optList.length) {
10792
+ unique = optList.map((o) => String(o.value));
10793
+ } else {
10794
+ unique = Array.from(
10795
+ new Set(
10796
+ rowModel.sourceRows().slice(0, 5e3).map((r) => cellRawValue(col, r)).filter((v) => v != null)
10797
+ )
10798
+ ).map(String);
10799
+ }
10596
10800
  }
10597
10801
  const targetRect = el.target.getBoundingClientRect();
10598
10802
  const rootRect = (_a2 = rootRef.value) == null ? void 0 : _a2.getBoundingClientRect();
@@ -11090,6 +11294,9 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
11090
11294
  return formatByType(col, value, BOOL_LABELS.value);
11091
11295
  return s;
11092
11296
  }
11297
+ const lab = colOptionLabel(col, value);
11298
+ if (lab != null)
11299
+ return lab;
11093
11300
  return formatByType(col, value, BOOL_LABELS.value);
11094
11301
  }
11095
11302
  function coveredByRowSpan(rows, i, leaf) {
@@ -12154,11 +12361,16 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
12154
12361
  const type = rowModel.filterTypeOf(col);
12155
12362
  let unique = [];
12156
12363
  if (type === "select") {
12157
- unique = Array.from(
12158
- new Set(
12159
- rowModel.sourceRows().slice(0, 5e3).map((r) => cellRawValue(col, r)).filter((v) => v != null)
12160
- )
12161
- ).map(String);
12364
+ const optList = colOptionList(col);
12365
+ if (optList && optList.length) {
12366
+ unique = optList.map((o) => String(o.value));
12367
+ } else {
12368
+ unique = Array.from(
12369
+ new Set(
12370
+ rowModel.sourceRows().slice(0, 5e3).map((r) => cellRawValue(col, r)).filter((v) => v != null)
12371
+ )
12372
+ ).map(String);
12373
+ }
12162
12374
  }
12163
12375
  const ss = rowModel.sortStates.value.find((x) => x.field === colId || x.field === col.field);
12164
12376
  colMenu.value = {
@@ -12774,10 +12986,15 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
12774
12986
  filterType = "select";
12775
12987
  let options;
12776
12988
  let strictOptions = false;
12989
+ const resolvedOpts = colOptionList(col);
12777
12990
  if (Array.isArray(eo) && eo.length) {
12778
12991
  filterType = "select";
12779
12992
  options = eo.map((o) => String((o == null ? void 0 : o.label) ?? (o == null ? void 0 : o.value) ?? o));
12780
12993
  strictOptions = true;
12994
+ } else if (resolvedOpts && resolvedOpts.length) {
12995
+ filterType = "select";
12996
+ options = resolvedOpts.map((o) => String(o.label ?? o.value));
12997
+ strictOptions = true;
12781
12998
  } else if (filterType === "select") {
12782
12999
  const set = /* @__PURE__ */ new Set();
12783
13000
  for (const r of src) {
@@ -1,6 +1,6 @@
1
1
  import RjGrid from './src/RjGrid.vue';
2
2
  export { RjGrid };
3
- export type { RjColumn, RjRowData, RjCellParams, RjGridApi, RjGridState, RjDataMode, RjLoadServerParams, RjLoadServerResult, RjFilterModel, RjFilterCondition, RjFilterType, RjSortState, RjSortDir, RjFixed, RjMenuItem, RjMenuContext, RjCellEventParams, RjSelectionEventParams, RjPinnedRow, RjFindMatch, RjEditorType, RjEditorConfig, RjEditorOption, RjImageConfig, RjSparklineConfig, RjColStateItem, RjColumnFilterState, RjExportScope, RjExportScopeResolved, RjServerExportParams, RjTransaction, RjExportParams, RjPrintOptions, RjPrintParams, RjAgg, RjAggFunc, RjAggCustom, RjQueryCondition, RjQueryFieldDef, RjQueryFieldKind, RjQueryOperator, RjQueryAction, RjQueryActionCtx, RjRowFormConfig, RjSavedView } from './src/types';
3
+ export type { RjColumn, RjRowData, RjCellParams, RjGridApi, RjGridState, RjDataMode, RjLoadServerParams, RjLoadServerResult, RjFilterModel, RjFilterCondition, RjFilterType, RjSortState, RjSortDir, RjFixed, RjMenuItem, RjMenuContext, RjCellEventParams, RjSelectionEventParams, RjPinnedRow, RjFindMatch, RjEditorType, RjEditorConfig, RjEditorOption, RjOptionSource, RjImageConfig, RjSparklineConfig, RjColStateItem, RjColumnFilterState, RjExportScope, RjExportScopeResolved, RjServerExportParams, RjTransaction, RjExportParams, RjPrintOptions, RjPrintParams, RjAgg, RjAggFunc, RjAggCustom, RjQueryCondition, RjQueryFieldDef, RjQueryFieldKind, RjQueryOperator, RjQueryAction, RjQueryActionCtx, RjRowFormConfig, RjSavedView } from './src/types';
4
4
  export type { RjThemeParams } from './src/theme';
5
5
  export type { RjIconName, RjIcons, RjIconsOverride } from './src/icons';
6
6
  export { deriveQueryFields, queryOpsOfKind, defaultQueryOperator, matchQueryValue } from './src/query';
@@ -1,5 +1,5 @@
1
1
  import './styles/rj-grid.scss';
2
- import type { RjCellParams, RjColumn, RjDataMode, RjExportScopeResolved, RjFilterModel, RjGridState, RjLoadServerParams, RjMenuItem, RjPrintParams, RjRowData, RjServerExportParams, RjSortState, RjTransaction, RjQueryAction, RjQueryFieldDef, RjRowFormConfig, RjSavedView } from './types';
2
+ import type { RjCellParams, RjColumn, RjDataMode, RjEditorOption, RjExportScopeResolved, RjFilterModel, RjGridState, RjLoadServerParams, RjMenuItem, RjPrintParams, RjRowData, RjServerExportParams, RjSortState, RjTransaction, RjQueryAction, RjQueryFieldDef, RjRowFormConfig, RjSavedView } from './types';
3
3
  import { type RjDisplayRow } from './useRowModel';
4
4
  import { type RjThemeParams } from './theme';
5
5
  import { type RjMessages } from './locale';
@@ -129,6 +129,10 @@ declare const _default: __VLS_WithTemplateSlots<import("vue").DefineComponent<im
129
129
  queryActions?: RjQueryAction[] | undefined;
130
130
  /** 内置行编辑/新增弹窗:false 关闭 openRowForm/openRowFormAdd 能力;对象可配 title/width/columns 与 addTitle/addColumns */
131
131
  rowForm?: boolean | RjRowFormConfig | undefined;
132
+ /** 网格级字典加载器:列上 dict:'x'(或 options:{dict:'x'})时调用,返回 {label,value}[] 或原始数组(异步可) */
133
+ dictLoader?: ((key: string) => RjEditorOption[] | Promise<RjEditorOption[]>) | undefined;
134
+ /** 网格级命名源加载器:列上 options:{ref:'name'} 时调用,返回接口原始数组(异步可),字段归一交给列上的 labelKey/valueKey/map */
135
+ optionsLoader?: ((name: string) => unknown[] | Promise<unknown[]>) | undefined;
132
136
  /** 内置视图(只读,恒排在自建视图前) */
133
137
  builtinViews?: RjSavedView[] | undefined;
134
138
  }>, {
@@ -332,6 +336,10 @@ declare const _default: __VLS_WithTemplateSlots<import("vue").DefineComponent<im
332
336
  queryActions?: RjQueryAction[] | undefined;
333
337
  /** 内置行编辑/新增弹窗:false 关闭 openRowForm/openRowFormAdd 能力;对象可配 title/width/columns 与 addTitle/addColumns */
334
338
  rowForm?: boolean | RjRowFormConfig | undefined;
339
+ /** 网格级字典加载器:列上 dict:'x'(或 options:{dict:'x'})时调用,返回 {label,value}[] 或原始数组(异步可) */
340
+ dictLoader?: ((key: string) => RjEditorOption[] | Promise<RjEditorOption[]>) | undefined;
341
+ /** 网格级命名源加载器:列上 options:{ref:'name'} 时调用,返回接口原始数组(异步可),字段归一交给列上的 labelKey/valueKey/map */
342
+ optionsLoader?: ((name: string) => unknown[] | Promise<unknown[]>) | undefined;
335
343
  /** 内置视图(只读,恒排在自建视图前) */
336
344
  builtinViews?: RjSavedView[] | undefined;
337
345
  }>, {
@@ -0,0 +1,42 @@
1
+ import type { InjectionKey } from 'vue';
2
+ import type { RjColumn, RjEditorOption, RjOptionSource } from './types';
3
+ /** 列字段归一选项(对象式源携带 map/labelKey/valueKey/childrenKey 时生效) */
4
+ interface RjOptionShape {
5
+ map?: (item: any) => RjEditorOption;
6
+ labelKey?: string;
7
+ valueKey?: string;
8
+ childrenKey?: string;
9
+ }
10
+ /** 网格注入给子组件(RjEditor / RjFilterMenu)的只读访问器 */
11
+ export interface RjOptionsAccessor {
12
+ /** 该列解析出的候选;未就绪或未声明时 undefined */
13
+ list: (col: RjColumn) => RjEditorOption[] | undefined;
14
+ /** 该列 value→label;未命中时 undefined */
15
+ label: (col: RjColumn, value: any) => string | undefined;
16
+ }
17
+ /** 注入键:网格根 provide 访问器,子组件 inject 消费(仿 RJ_LOCALE_KEY / RJ_ICONS_KEY) */
18
+ export declare const RJ_OPTIONS_KEY: InjectionKey<RjOptionsAccessor>;
19
+ /** 子组件默认访问器(单独使用、未 inject 到时回退,全部返回 undefined 不影响原行为) */
20
+ export declare const defaultOptionsAccessor: RjOptionsAccessor;
21
+ /**
22
+ * 把任意接口返回归一成 RjEditorOption[]:
23
+ * - map 命中优先(完全自定义);
24
+ * - 否则按 labelKey/valueKey(或默认字段)取;
25
+ * - childrenKey(默认 'children')命中数组即递归拍平(depth 缩进不写进 label,保持干净 value→label 表)。
26
+ */
27
+ export declare function normalizeOptions(raw: unknown, shape?: RjOptionShape): RjEditorOption[];
28
+ /** 解析选项源所需的网格级加载器 */
29
+ export interface RjOptionLoaders {
30
+ dictLoader?: (key: string) => unknown | Promise<unknown>;
31
+ optionsLoader?: (name: string) => unknown | Promise<unknown>;
32
+ }
33
+ /** 解析一个列的选项源为候选数组(异步;失败静默回落空数组 → 显示原样回落原始值) */
34
+ export declare function resolveOptions(source: RjOptionSource | undefined, dict: string | undefined, loaders: RjOptionLoaders): Promise<RjEditorOption[]>;
35
+ /**
36
+ * 计算列选项源的缓存键:dict/ref 按 key 归并(两列同字典只加载一次),
37
+ * 静态数组 / 函数按列或函数身份。返回 null 表示该列无选项载体。
38
+ */
39
+ export declare function sourceCacheKey(col: RjColumn): string | null;
40
+ /** 递归收集声明了 options/dict 的列(含多级表头子列) */
41
+ export declare function collectOptionCols(cols: RjColumn[], out?: RjColumn[]): RjColumn[];
42
+ export {};
@@ -9,6 +9,14 @@ export declare function optionsOfColumn(col: RjColumn): RjEditorOption[];
9
9
  * - 用 override 覆盖标题、类别或候选(宿主可为枚举列补 options)。
10
10
  */
11
11
  export declare function deriveQueryFields(columns: RjColumn[], override?: RjQueryFieldDef[]): RjQueryFieldDef[];
12
+ /**
13
+ * 把统一选项载体(异步解析出的候选)并入查询字段池:仅在该字段自身无候选
14
+ * (editor.options / filterValueMap / override 都缺位)时,用载体候选填充并升级为 select 类别。
15
+ * 向后兼容:已有 options 的字段原样保留;carrier 未命中/为空不动。carrier 以 colIdOf 为键。
16
+ */
17
+ export declare function withCarrierOptions(fields: RjQueryFieldDef[], carrier: Map<string, {
18
+ list: RjEditorOption[];
19
+ }>): RjQueryFieldDef[];
12
20
  /** 各类别对应的合理运算符集合 */
13
21
  export declare function queryOpsOfKind(kind: RjQueryFieldKind): RjQueryOperator[];
14
22
  /** 新增字段时的默认运算符(取该类别最常用的一项) */
@@ -109,6 +109,28 @@ export interface RjEditorOption {
109
109
  label: string;
110
110
  value: any;
111
111
  }
112
+ /**
113
+ * 统一选项载体:一份数据源同时驱动 显示(value→label) / select 筛选候选 / select 编辑器 / NLQ 枚举。
114
+ * 数组=静态候选;函数=工厂(可同步/异步,网格级按列只解析一次);对象=按 load/ref/dict 取数,
115
+ * 再用 map 或 labelKey/valueKey 归一,childrenKey(默认 'children')命中即递归拍平树(label 不加缩进)。
116
+ */
117
+ export type RjOptionSource = RjEditorOption[] | ((row?: RjRowData) => RjEditorOption[] | Promise<RjEditorOption[]>) | {
118
+ /** 静态候选(对象式) */
119
+ items?: RjEditorOption[];
120
+ /** 宿主直接给的接口函数(返回原始数组,交给 labelKey/valueKey/map 归一) */
121
+ load?: () => unknown[] | Promise<unknown[]>;
122
+ /** 命名源:交网格级 optionsLoader(name) 解析 */
123
+ ref?: string;
124
+ /** 字典 key:交网格级 dictLoader(key) 解析 */
125
+ dict?: string;
126
+ /** 自定义映射:接口单项 → {label,value} */
127
+ map?: (item: any) => RjEditorOption;
128
+ /** 字段映射(默认 label/name → label,value/id/code → value) */
129
+ labelKey?: string;
130
+ valueKey?: string;
131
+ /** 传入即递归拍平树(默认 'children') */
132
+ childrenKey?: string;
133
+ };
112
134
  /** 编辑器配置 */
113
135
  export interface RjEditorConfig {
114
136
  type?: RjEditorType;
@@ -223,6 +245,13 @@ export interface RjColumn<T = RjRowData> {
223
245
  headerComponent?: any;
224
246
  editable?: boolean | ((row: T) => boolean);
225
247
  editor?: RjEditorConfig | RjEditorType;
248
+ /**
249
+ * 统一选项载体:一处声明同时喂给 显示 / select 筛选 / select 编辑器 / NLQ(异步自动加载 + 按源缓存)。
250
+ * 向后兼容:显式 col.formatter / editor.options / filterValueMap 一律优先,本载体仅在其缺位时填充。
251
+ */
252
+ options?: RjOptionSource;
253
+ /** options 的糖:等价 options:{dict},走网格级 dictLoader 按字典 key 取候选 */
254
+ dict?: string;
226
255
  /** 编辑值新旧合并钩子 */
227
256
  onCellValueChanged?: (params: {
228
257
  newValue: any;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rj-grid",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "description": "零第三方依赖的自研 Vue3 虚拟表格:虚拟滚动 / 冻结列 / 行拖拽 / 分组透视 / 公式 / SSRM / 主题 i18n / 自然语言查询",
5
5
  "type": "module",
6
6
  "license": "MIT",