@liujitcn/kratos-admin-core 0.0.97 → 0.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.
Files changed (36) hide show
  1. package/README.md +6 -0
  2. package/dist/package/declarations/src/components/CodeEditor/index.vue.d.ts +22 -0
  3. package/dist/package/declarations/src/components/ProTable/columns.d.ts +3 -0
  4. package/dist/package/declarations/src/stores/modules/dict.d.ts +2 -2
  5. package/dist/package/declarations/src/views/demo/demo-data.d.ts +13 -0
  6. package/dist/package/src/components/CodeEditor/index.vue +92 -0
  7. package/dist/package/src/components/Dict/DictLabel.vue +7 -24
  8. package/dist/package/src/components/Dict/index.vue +21 -23
  9. package/dist/package/src/components/ProTable/columns.ts +27 -0
  10. package/dist/package/src/components/ProTable/index.vue +53 -20
  11. package/dist/package/src/components/WangEditor/index.vue +15 -4
  12. package/dist/package/src/rpc/base/v1/config.ts +1 -1
  13. package/dist/package/src/rpc/base/v1/file.ts +1 -1
  14. package/dist/package/src/rpc/base/v1/language.ts +1 -1
  15. package/dist/package/src/rpc/base/v1/login.ts +1 -1
  16. package/dist/package/src/rpc/base/v1/mfa.ts +1 -1
  17. package/dist/package/src/rpc/base/v1/oauth.ts +1 -1
  18. package/dist/package/src/rpc/base/v1/oauth_client.ts +1 -1
  19. package/dist/package/src/rpc/base/v1/secret_crypto.ts +1 -1
  20. package/dist/package/src/rpc/common/v1/common.ts +1 -1
  21. package/dist/package/src/rpc/common/v1/enum.ts +1 -1
  22. package/dist/package/src/rpc/common/v1/types.ts +1 -1
  23. package/dist/package/src/rpc/google/protobuf/empty.ts +1 -1
  24. package/dist/package/src/rpc/google/protobuf/struct.ts +1 -1
  25. package/dist/package/src/rpc/google/protobuf/wrappers.ts +1 -1
  26. package/dist/package/src/rpc/system/admin/v1/auth.ts +1 -1
  27. package/dist/package/src/rpc/system/admin/v1/base_dict.ts +1 -1
  28. package/dist/package/src/rpc/system/admin/v1/base_i18n.ts +1 -1
  29. package/dist/package/src/rpc/system/admin/v1/base_tenant.ts +1 -1
  30. package/dist/package/src/rpc/system/admin/v1/common.ts +1 -1
  31. package/dist/package/src/rpc/system/common/v1/common.ts +1 -1
  32. package/dist/package/src/stores/modules/dict.ts +1 -2
  33. package/dist/package/src/utils/request.ts +3 -2
  34. package/dist/package/src/views/demo/demo-data.ts +21 -0
  35. package/dist/package/src/views/demo/table/index.vue +4 -3
  36. package/package.json +7 -2
package/README.md CHANGED
@@ -187,6 +187,12 @@ pnpm --filter @liujitcn/kratos-admin-core pack
187
187
 
188
188
  时间列的对齐方式由页面通过 `align` 显式配置,ProTable 不按时间字段名称判断;修改时间列使用 `align: "center"` 居中显示。
189
189
 
190
+ 管理端切换语言后,ProTable 同步响应式列配置中的表头、搜索项、行操作和列设置文案,保留当前筛选、分页、勾选及用户调整的列显隐、排序设置;异步枚举在公共字典刷新后重新加载,过期请求不会覆盖最新结果。页面文案应在模板或 `computed` 中调用 `t()`,避免将翻译结果保存为固定字符串。
191
+
192
+ Dict 和 DictLabel 直接响应字典 Store 的文案变化。字典刷新成功后整体替换缓存,刷新期间保留已加载选项和表单值,无需重新挂载页面。
193
+
194
+ 纯表格 demo 的“所在城市”通过 `search.enum` 提供浙江、广东、四川的省市级联选项,选择城市后可筛选对应项目,清空或重置后恢复全部项目。
195
+
190
196
  ProTable 的 `cellType: "actions"` 操作列按当前行的 `hidden` 过滤按钮,优先展示使用 `common.action.edit`、`common.action.delete` 文案的编辑、删除操作;可见按钮超过三项时,其余操作收入“更多”菜单。未配置或无权限的编辑、删除不会补充显示,菜单保留图标、禁用状态和点击参数;自定义 render 或插槽不参与折叠。
191
197
 
192
198
  操作列默认固定在右侧。配置式操作列(`cellType: "actions"`)根据当前页实际展示按钮和表头的最大宽度自适应,计入翻译文案、图标和间距,随语言、字体和行权限变化重新测量,最小宽度为 `80px`;折叠菜单内的按钮不参与测量。自定义 render 或插槽操作列仍默认 `160px`。页面可通过 `width` 或 `minWidth` 覆盖自动宽度,通过 `fixed: "left"` 调整固定位置,或通过 `fixed: false` 取消固定。
@@ -0,0 +1,22 @@
1
+ /** 代码编辑器属性。 */
2
+ interface CodeEditorProps {
3
+ /** 编辑器源码。 */
4
+ modelValue: string;
5
+ /** Monaco 语言标识。 */
6
+ language?: string;
7
+ /** 编辑器高度。 */
8
+ height?: string;
9
+ /** 是否只读。 */
10
+ readonly?: boolean;
11
+ }
12
+ declare const __VLS_export: import("vue").DefineComponent<CodeEditorProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
13
+ "update:modelValue": (value: string) => any;
14
+ }, string, import("vue").PublicProps, Readonly<CodeEditorProps> & Readonly<{
15
+ "onUpdate:modelValue"?: ((value: string) => any) | undefined;
16
+ }>, {
17
+ readonly: boolean;
18
+ height: string;
19
+ language: string;
20
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
21
+ declare const _default: typeof __VLS_export;
22
+ export default _default;
@@ -0,0 +1,3 @@
1
+ import type { ColumnProps } from "./interface";
2
+ /** 同步最新列配置,保留未被页面配置覆盖的用户显隐和排序设置。 */
3
+ export declare function syncTableColumns(columns: ColumnProps[], current?: ColumnProps[], previous?: ColumnProps[]): ColumnProps[];
@@ -10,7 +10,7 @@ export declare const useDictStore: import("pinia").StoreDefinition<"admin-dict",
10
10
  ensureDictionary(dictCode: string): Promise<OptionBaseDictResponse_BaseDictItem[]>;
11
11
  /** 清空字典缓存 */
12
12
  clearDictionaryCache(): void;
13
- /** 强制刷新字典缓存 */
13
+ /** 加载成功后整体替换字典缓存,刷新期间保留当前文案和表单选中值。 */
14
14
  updateDictionaryCache(): Promise<void>;
15
15
  }>;
16
16
  /**
@@ -27,6 +27,6 @@ export declare function useDictStoreHook(): import("pinia").Store<"admin-dict",
27
27
  ensureDictionary(dictCode: string): Promise<OptionBaseDictResponse_BaseDictItem[]>;
28
28
  /** 清空字典缓存 */
29
29
  clearDictionaryCache(): void;
30
- /** 强制刷新字典缓存 */
30
+ /** 加载成功后整体替换字典缓存,刷新期间保留当前文案和表单选中值。 */
31
31
  updateDictionaryCache(): Promise<void>;
32
32
  }>;
@@ -139,6 +139,19 @@ export declare const demoRegionTree: {
139
139
  label: string;
140
140
  }[];
141
141
  }[];
142
+ /** 团队树静态数据:与城市级联数据分离,供纯表格团队筛选使用。 */
143
+ export declare const demoOwnerGroupTree: ({
144
+ value: string;
145
+ label: string;
146
+ children: {
147
+ value: string;
148
+ label: string;
149
+ }[];
150
+ } | {
151
+ value: string;
152
+ label: string;
153
+ children?: undefined;
154
+ })[];
142
155
  /** 项目静态数据:25 条覆盖分页、多级表头、进度条、标签列等展示场景。 */
143
156
  export declare const demoProjects: DemoProject[];
144
157
  /** 目录树静态数据:三层嵌套演示树形表格的展开、折叠与子级操作。 */
@@ -0,0 +1,92 @@
1
+ <template>
2
+ <div ref="editorHost" class="code-editor" :style="{ height }" />
3
+ </template>
4
+
5
+ <script setup lang="ts">
6
+ import { onBeforeUnmount, onMounted, ref, watch } from "vue";
7
+ import * as monaco from "monaco-editor";
8
+ import editorWorker from "monaco-editor/esm/vs/editor/editor.worker?worker";
9
+
10
+ /** 代码编辑器属性。 */
11
+ interface CodeEditorProps {
12
+ /** 编辑器源码。 */
13
+ modelValue: string;
14
+ /** Monaco 语言标识。 */
15
+ language?: string;
16
+ /** 编辑器高度。 */
17
+ height?: string;
18
+ /** 是否只读。 */
19
+ readonly?: boolean;
20
+ }
21
+
22
+ const props = withDefaults(defineProps<CodeEditorProps>(), {
23
+ language: "javascript",
24
+ height: "360px",
25
+ readonly: false
26
+ });
27
+
28
+ const emit = defineEmits<{ "update:modelValue": [value: string] }>();
29
+ const editorHost = ref<HTMLElement>();
30
+ let editor: monaco.editor.IStandaloneCodeEditor | undefined;
31
+ let model: monaco.editor.ITextModel | undefined;
32
+
33
+ function createEditor() {
34
+ if (!editorHost.value) return;
35
+ self.MonacoEnvironment = {
36
+ getWorker() {
37
+ return new editorWorker();
38
+ }
39
+ };
40
+ model = monaco.editor.createModel(props.modelValue, props.language);
41
+ editor = monaco.editor.create(editorHost.value, {
42
+ model,
43
+ automaticLayout: true,
44
+ minimap: { enabled: false },
45
+ fontSize: 13,
46
+ tabSize: 2,
47
+ scrollBeyondLastLine: false,
48
+ wordWrap: "on",
49
+ readOnly: props.readonly,
50
+ padding: { top: 12, bottom: 12 }
51
+ });
52
+ editor.onDidChangeModelContent(() => {
53
+ emit("update:modelValue", editor?.getValue() ?? "");
54
+ });
55
+ }
56
+
57
+ function disposeEditor() {
58
+ editor?.dispose();
59
+ model?.dispose();
60
+ editor = undefined;
61
+ model = undefined;
62
+ }
63
+
64
+ onMounted(createEditor);
65
+ onBeforeUnmount(disposeEditor);
66
+
67
+ watch(
68
+ () => props.modelValue,
69
+ value => {
70
+ if (!editor || value === editor.getValue()) return;
71
+ editor.setValue(value);
72
+ }
73
+ );
74
+
75
+ watch(
76
+ () => props.language,
77
+ value => {
78
+ if (!model) return;
79
+ monaco.editor.setModelLanguage(model, value);
80
+ }
81
+ );
82
+ </script>
83
+
84
+ <style scoped>
85
+ .code-editor {
86
+ width: 100%;
87
+ min-height: 240px;
88
+ overflow: hidden;
89
+ border: 1px solid var(--el-border-color);
90
+ border-radius: var(--admin-page-radius);
91
+ }
92
+ </style>
@@ -4,7 +4,7 @@
4
4
  </template>
5
5
 
6
6
  <script setup lang="ts">
7
- import { ref, watch } from "vue";
7
+ import { computed, watch } from "vue";
8
8
  import { useDictStore } from "../../stores/modules/dict";
9
9
 
10
10
  /** Element Plus 标签支持的展示类型。 */
@@ -22,8 +22,9 @@ const props = withDefaults(defineProps<DictLabelProps>(), {
22
22
  });
23
23
 
24
24
  const dictStore = useDictStore();
25
- const label = ref("");
26
- const tagType = ref<TagType | undefined>();
25
+ const matchedItem = computed(() => dictStore.getDictionary(props.code).find(item => item.value == props.modelValue));
26
+ const label = computed(() => matchedItem.value?.label ?? "");
27
+ const tagType = computed(() => normalizeTagType(matchedItem.value?.tag_type));
27
28
 
28
29
  /**
29
30
  * 过滤后端返回的 tag 类型,确保只传递 Element Plus 支持的枚举值。
@@ -34,28 +35,10 @@ function normalizeTagType(rawTagType?: string): TagType | undefined {
34
35
  return supportedTagTypes.find(tag => tag === rawTagType);
35
36
  }
36
37
 
37
- /**
38
- * 根据字典值刷新标签文本和标签类型。
39
- */
40
- async function refreshLabelAndTag() {
41
- if (!props.code) {
42
- label.value = "";
43
- tagType.value = undefined;
44
- return;
45
- }
46
-
47
- // 字典缓存可能来自持久化旧数据,按当前编码兜底刷新一次。
48
- const dictList = await dictStore.ensureDictionary(props.code);
49
-
50
- const matchedItem = dictList.find(dictItem => dictItem.value == props.modelValue);
51
- label.value = matchedItem?.label ?? "";
52
- tagType.value = normalizeTagType(matchedItem?.tag_type);
53
- }
54
-
55
38
  watch(
56
- () => [props.code, props.modelValue],
57
- async () => {
58
- await refreshLabelAndTag();
39
+ () => props.code,
40
+ async code => {
41
+ if (code) await dictStore.ensureDictionary(code);
59
42
  },
60
43
  { immediate: true }
61
44
  );
@@ -38,7 +38,7 @@
38
38
 
39
39
  <script setup lang="ts">
40
40
  import type { CSSProperties } from "vue";
41
- import { ref, watch } from "vue";
41
+ import { computed, ref, watch } from "vue";
42
42
  import { useDictStore } from "../../stores/modules/dict";
43
43
  import type { OptionBaseDictResponse_BaseDictItem } from "../../rpc/system/admin/v1/base_dict";
44
44
  import { useLocaleStore } from "../../locales";
@@ -80,7 +80,12 @@ const emit = defineEmits<{
80
80
  }>();
81
81
 
82
82
  const dictStore = useDictStore();
83
- const options = ref<Array<{ label: string; value: DictValue }>>([]);
83
+ const options = computed(() =>
84
+ dictStore.getDictionary(props.code).map(dictItem => ({
85
+ label: dictItem.label,
86
+ value: convertDictValue(dictItem)
87
+ }))
88
+ );
84
89
  const selectedSingleValue = ref<DictValue | undefined>();
85
90
  const selectedMultiValue = ref<DictValue[]>([]);
86
91
  const optionsLoaded = ref(false);
@@ -104,21 +109,6 @@ function convertDictValue(dictItem: OptionBaseDictResponse_BaseDictItem): DictVa
104
109
  return dictItem.value;
105
110
  }
106
111
 
107
- /**
108
- * 加载当前字典选项,若本地无缓存则自动拉取。
109
- */
110
- async function loadOptions() {
111
- // 按当前字典编码兜底刷新,避免持久化旧缓存里缺少该字典导致下拉无数据。
112
- optionsLoaded.value = false;
113
- const dictList = await dictStore.ensureDictionary(props.code);
114
-
115
- options.value = dictList.map(dictItem => ({
116
- label: dictItem.label,
117
- value: convertDictValue(dictItem)
118
- }));
119
- optionsLoaded.value = true;
120
- }
121
-
122
112
  /**
123
113
  * 校验当前值是否仍在可选项内,避免字典切换后残留无效值。
124
114
  */
@@ -131,10 +121,7 @@ function ensureSelectedValueValid() {
131
121
  emit("update:modelValue", normalizedValues);
132
122
  return;
133
123
  }
134
- const normalizedValue = normalizeDictValue(
135
- selectedSingleValue.value,
136
- availableValues
137
- );
124
+ const normalizedValue = normalizeDictValue(selectedSingleValue.value, availableValues);
138
125
  if (normalizedValue === selectedSingleValue.value) return;
139
126
  selectedSingleValue.value = normalizedValue;
140
127
  emit("update:modelValue", normalizedValue);
@@ -165,10 +152,21 @@ watch(
165
152
 
166
153
  watch(
167
154
  () => [props.code, props.codeType],
168
- async () => {
169
- await loadOptions();
155
+ async (_, __, onCleanup) => {
156
+ let active = true;
157
+ onCleanup(() => {
158
+ active = false;
159
+ });
160
+ optionsLoaded.value = false;
161
+ await dictStore.ensureDictionary(props.code);
162
+ if (!active) return;
163
+ optionsLoaded.value = true;
170
164
  ensureSelectedValueValid();
171
165
  },
172
166
  { immediate: true }
173
167
  );
168
+
169
+ watch(options, () => {
170
+ if (optionsLoaded.value) ensureSelectedValueValid();
171
+ });
174
172
  </script>
@@ -0,0 +1,27 @@
1
+ import { unref } from "vue";
2
+ import type { ColumnProps } from "./interface";
3
+
4
+ /** 同步最新列配置,保留未被页面配置覆盖的用户显隐和排序设置。 */
5
+ export function syncTableColumns(
6
+ columns: ColumnProps[],
7
+ current: ColumnProps[] = [],
8
+ previous: ColumnProps[] = []
9
+ ): ColumnProps[] {
10
+ const currentMap = new Map(current.map((column, index) => [column.prop ?? column.type ?? index, column]));
11
+ const previousMap = new Map(previous.map((column, index) => [column.prop ?? column.type ?? index, column]));
12
+ return columns.map((column, index) => {
13
+ const key = column.prop ?? column.type ?? index;
14
+ const existing = currentMap.get(key);
15
+ const source = previousMap.get(key);
16
+ const isShow = unref(column.isShow) ?? true;
17
+ return {
18
+ ...column,
19
+ isShow: existing && source && isShow === (unref(source.isShow) ?? true) ? existing.isShow : isShow,
20
+ sortable: existing && source && column.sortable === source.sortable ? existing.sortable : column.sortable,
21
+ isSetting: column.isSetting ?? true,
22
+ isFilterEnum: column.isFilterEnum ?? true,
23
+ ...(column.search ? { search: { ...column.search } } : {}),
24
+ ...(column._children ? { _children: syncTableColumns(column._children, existing?._children, source?._children) } : {})
25
+ };
26
+ });
27
+ }
@@ -128,11 +128,25 @@
128
128
  </slot>
129
129
  </div>
130
130
  <!-- 列设置 -->
131
- <ColSetting v-if="toolButton" ref="colRef" v-model:col-setting="colSetting" />
131
+ <ColSetting v-if="toolButton" ref="colRef" :col-setting="colSetting" />
132
132
  </template>
133
133
 
134
134
  <script setup lang="ts" name="ProTable">
135
- import { ref, watch, provide, onMounted, unref, computed, nextTick, useAttrs, useSlots, isProxy, markRaw, toRaw } from "vue";
135
+ import {
136
+ ref,
137
+ watch,
138
+ provide,
139
+ onMounted,
140
+ onScopeDispose,
141
+ unref,
142
+ computed,
143
+ nextTick,
144
+ useAttrs,
145
+ useSlots,
146
+ isProxy,
147
+ markRaw,
148
+ toRaw
149
+ } from "vue";
136
150
  import { ElTable, type TableInstance } from "element-plus";
137
151
  import { useTable } from "../../hooks/useTable";
138
152
  import { useSelection } from "../../hooks/useSelection";
@@ -151,8 +165,9 @@ import SearchForm from "../../components/SearchForm/index.vue";
151
165
  import Pagination from "./components/Pagination.vue";
152
166
  import ColSetting from "./components/ColSetting.vue";
153
167
  import TableColumn from "./components/TableColumn.vue";
168
+ import { syncTableColumns } from "./columns";
154
169
  import Sortable from "sortablejs";
155
- import { useLocaleStore } from "../../locales";
170
+ import { registerLocaleChangeHandler, useLocaleStore } from "../../locales";
156
171
 
157
172
  const { t } = useLocaleStore();
158
173
 
@@ -313,14 +328,24 @@ watch(
313
328
  // 监听页面 initParam 改化,重新获取表格数据
314
329
  watch(() => props.initParam, getTableList, { deep: true });
315
330
 
316
- // 接收 columns 并设置为响应式
317
- const tableColumns = ref<ColumnProps[]>(props.columns as ColumnProps[]);
331
+ // 保留独立的列设置和源配置快照,语言重算时同步新文案并保留用户设置。
332
+ const tableColumns = ref<ColumnProps[]>([]);
333
+ let previousColumns: ColumnProps[] = [];
334
+ watch(
335
+ () => props.columns,
336
+ columns => {
337
+ tableColumns.value = syncTableColumns(columns, tableColumns.value, previousColumns);
338
+ previousColumns = syncTableColumns(columns);
339
+ },
340
+ { deep: true, immediate: true }
341
+ );
318
342
 
319
343
  // 扁平化 columns
320
344
  const flatColumns = computed(() => flatColumnsFunc(tableColumns.value as any) as ColumnProps[]);
321
345
 
322
346
  // 定义 enumMap 存储 enum 值(避免异步请求无法格式化单元格内容 || 无法填充搜索下拉选择)
323
347
  const enumMap = ref(new Map<string, { [key: string]: any }[]>());
348
+ const enumRequests = new Map<string, symbol>();
324
349
 
325
350
  /** 更新枚举缓存时替换 Map 引用,确保搜索下拉能响应异步枚举结果。 */
326
351
  const setEnumMapValue = (prop: string | undefined, data: { [key: string]: any }[]) => {
@@ -346,24 +371,29 @@ const buildStatusEnum = (column: ColumnProps): EnumProps[] => {
346
371
  const setEnumMap = async ({ prop, enum: enumValue }: ColumnProps) => {
347
372
  if (!prop || !enumValue) return;
348
373
 
349
- // 如果当前 enumMap 存在相同的值 return
350
- if (enumMap.value.has(prop) && (typeof enumValue === "function" || enumMap.value.get(prop) === enumValue)) return;
351
-
352
374
  // 当前 enum 为静态数据,则直接存储到 enumMap
353
375
  if (typeof enumValue !== "function") {
354
- setEnumMapValue(prop, unref(enumValue!));
376
+ enumRequests.delete(prop);
377
+ const data = unref(enumValue);
378
+ if (enumMap.value.get(prop) !== data) setEnumMapValue(prop, data);
355
379
  return;
356
380
  }
357
381
 
382
+ if (enumMap.value.has(prop)) return;
383
+
384
+ const request = Symbol(prop);
385
+ enumRequests.set(prop, request);
358
386
  // 为了防止接口执行慢,而存储慢,导致重复请求,所以预先存储为[],接口返回后再二次存储
359
387
  setEnumMapValue(prop, []);
360
388
 
361
389
  // 当前 enum 为后台数据需要请求数据,则调用该请求接口,并存储到 enumMap
362
390
  try {
363
391
  const response = await enumValue();
392
+ if (enumRequests.get(prop) !== request) return;
364
393
  const data = Array.isArray(response) ? response : (response?.data ?? response?.list ?? []);
365
394
  setEnumMapValue(prop, data);
366
395
  } catch {
396
+ if (enumRequests.get(prop) !== request) return;
367
397
  // 请求层负责展示接口错误;删除占位缓存,允许列配置再次同步时重试。
368
398
  const nextEnumMap = new Map(enumMap.value);
369
399
  nextEnumMap.delete(prop);
@@ -377,6 +407,7 @@ const setEnumMap = async ({ prop, enum: enumValue }: ColumnProps) => {
377
407
  const syncColumnEnumMap = async (column: ColumnProps) => {
378
408
  const statusEnum = buildStatusEnum(column);
379
409
  if (statusEnum.length && column.prop) {
410
+ enumRequests.delete(column.prop);
380
411
  setEnumMapValue(column.prop, statusEnum);
381
412
  return;
382
413
  }
@@ -387,25 +418,25 @@ const syncColumnEnumMap = async (column: ColumnProps) => {
387
418
  const refreshEnums = async () => {
388
419
  const nextEnumMap = new Map(enumMap.value);
389
420
  flatColumns.value.forEach(column => {
390
- if (column.prop) nextEnumMap.delete(column.prop);
421
+ if (!column.prop) return;
422
+ nextEnumMap.delete(column.prop);
423
+ enumRequests.delete(column.prop);
391
424
  });
392
425
  enumMap.value = nextEnumMap;
393
426
  await Promise.all(flatColumns.value.map(column => syncColumnEnumMap(column)));
394
427
  };
395
428
 
429
+ // 公共运行时先刷新字典缓存,随后按当前语言重新加载当前表格的枚举。
430
+ onScopeDispose(registerLocaleChangeHandler(refreshEnums));
431
+
396
432
  // 注入 enumMap
397
433
  provide("enumMap", enumMap);
398
434
 
399
- // 扁平化 columns 的方法
435
+ /** 扁平化多级列配置,供搜索项和枚举缓存使用。 */
400
436
  const flatColumnsFunc = (columns: ColumnProps[], flatArr: ColumnProps[] = []) => {
401
437
  columns.forEach(col => {
402
438
  if (col._children?.length) flatArr.push(...flatColumnsFunc(col._children));
403
439
  flatArr.push(col);
404
-
405
- // column 添加默认 isShow && isSetting && isFilterEnum 属性值
406
- col.isShow = col.isShow ?? true;
407
- col.isSetting = col.isSetting ?? true;
408
- col.isFilterEnum = col.isFilterEnum ?? true;
409
440
  });
410
441
  return flatArr.filter(item => !item._children?.length);
411
442
  };
@@ -441,10 +472,12 @@ searchColumns.value?.forEach((column, index) => {
441
472
 
442
473
  // 列设置 ==> 需要过滤掉不需要设置的列
443
474
  const colRef = ref();
444
- const colSetting = (tableColumns.value as ColumnProps[]).filter(item => {
445
- const { type, prop, isSetting } = item;
446
- return !columnTypes.includes(type!) && prop !== "operation" && isSetting;
447
- });
475
+ const colSetting = computed<ColumnProps[]>(() =>
476
+ (tableColumns.value as ColumnProps[]).filter(item => {
477
+ const { type, prop, isSetting } = item;
478
+ return !columnTypes.includes(type!) && prop !== "operation" && isSetting;
479
+ })
480
+ );
448
481
  /** 打开列设置弹窗。 */
449
482
  const openColSetting = () => colRef.value.openColSetting();
450
483
 
@@ -86,14 +86,24 @@ const emit = defineEmits<{
86
86
  "update:value": [value: string];
87
87
  "check-validate": [];
88
88
  }>();
89
+
90
+ /** 判断富文本 HTML 是否只包含空白节点。 */
91
+ const normalizeEditorValue = (value: string) => {
92
+ if (!value) return "";
93
+ if (/<(?:img|video|iframe)\b/i.test(value)) return value;
94
+ const text = value
95
+ .replace(/<[^>]*>/g, "")
96
+ .replace(/&nbsp;|&#160;|&#8203;/gi, "")
97
+ .trim();
98
+ return text ? value : "";
99
+ };
89
100
  const valueHtml = computed({
90
101
  get() {
91
102
  return normalizeRichTextMediaPaths(props.value);
92
103
  },
93
104
  set(val: string) {
94
- // 防止富文本内容为空时,校验失败
95
- if (editorRef.value.isEmpty()) val = "";
96
- emit("update:value", val);
105
+ // 以编辑器回写的 HTML 为准,避免 isEmpty() 在回写时序中误清空有效内容。
106
+ emit("update:value", normalizeEditorValue(val));
97
107
  }
98
108
  });
99
109
 
@@ -180,7 +190,8 @@ editorMenuConfig["uploadVideo"] = {
180
190
  };
181
191
 
182
192
  // 编辑框失去焦点时触发
183
- const handleBlur = () => {
193
+ const handleBlur = async () => {
194
+ await nextTick();
184
195
  formItemContext?.prop && formContext?.validateField([formItemContext.prop as string]);
185
196
  };
186
197
 
@@ -1,6 +1,6 @@
1
1
  // Code generated by protoc-gen-ts_proto. DO NOT EDIT.
2
2
  // versions:
3
- // protoc-gen-ts_proto v2.11.8
3
+ // protoc-gen-ts_proto v2.12.1
4
4
  // protoc unknown
5
5
  // source: base/v1/config.proto
6
6
 
@@ -1,6 +1,6 @@
1
1
  // Code generated by protoc-gen-ts_proto. DO NOT EDIT.
2
2
  // versions:
3
- // protoc-gen-ts_proto v2.11.8
3
+ // protoc-gen-ts_proto v2.12.1
4
4
  // protoc unknown
5
5
  // source: base/v1/file.proto
6
6
 
@@ -1,6 +1,6 @@
1
1
  // Code generated by protoc-gen-ts_proto. DO NOT EDIT.
2
2
  // versions:
3
- // protoc-gen-ts_proto v2.11.8
3
+ // protoc-gen-ts_proto v2.12.1
4
4
  // protoc unknown
5
5
  // source: base/v1/language.proto
6
6
 
@@ -1,6 +1,6 @@
1
1
  // Code generated by protoc-gen-ts_proto. DO NOT EDIT.
2
2
  // versions:
3
- // protoc-gen-ts_proto v2.11.8
3
+ // protoc-gen-ts_proto v2.12.1
4
4
  // protoc unknown
5
5
  // source: base/v1/login.proto
6
6
 
@@ -1,6 +1,6 @@
1
1
  // Code generated by protoc-gen-ts_proto. DO NOT EDIT.
2
2
  // versions:
3
- // protoc-gen-ts_proto v2.11.8
3
+ // protoc-gen-ts_proto v2.12.1
4
4
  // protoc unknown
5
5
  // source: base/v1/mfa.proto
6
6
 
@@ -1,6 +1,6 @@
1
1
  // Code generated by protoc-gen-ts_proto. DO NOT EDIT.
2
2
  // versions:
3
- // protoc-gen-ts_proto v2.11.8
3
+ // protoc-gen-ts_proto v2.12.1
4
4
  // protoc unknown
5
5
  // source: base/v1/oauth.proto
6
6
 
@@ -1,6 +1,6 @@
1
1
  // Code generated by protoc-gen-ts_proto. DO NOT EDIT.
2
2
  // versions:
3
- // protoc-gen-ts_proto v2.11.8
3
+ // protoc-gen-ts_proto v2.12.1
4
4
  // protoc unknown
5
5
  // source: base/v1/oauth_client.proto
6
6
 
@@ -1,6 +1,6 @@
1
1
  // Code generated by protoc-gen-ts_proto. DO NOT EDIT.
2
2
  // versions:
3
- // protoc-gen-ts_proto v2.11.8
3
+ // protoc-gen-ts_proto v2.12.1
4
4
  // protoc unknown
5
5
  // source: base/v1/secret_crypto.proto
6
6
 
@@ -1,6 +1,6 @@
1
1
  // Code generated by protoc-gen-ts_proto. DO NOT EDIT.
2
2
  // versions:
3
- // protoc-gen-ts_proto v2.11.8
3
+ // protoc-gen-ts_proto v2.12.1
4
4
  // protoc unknown
5
5
  // source: common/v1/common.proto
6
6
 
@@ -1,6 +1,6 @@
1
1
  // Code generated by protoc-gen-ts_proto. DO NOT EDIT.
2
2
  // versions:
3
- // protoc-gen-ts_proto v2.11.8
3
+ // protoc-gen-ts_proto v2.12.1
4
4
  // protoc unknown
5
5
  // source: common/v1/enum.proto
6
6
 
@@ -1,6 +1,6 @@
1
1
  // Code generated by protoc-gen-ts_proto. DO NOT EDIT.
2
2
  // versions:
3
- // protoc-gen-ts_proto v2.11.8
3
+ // protoc-gen-ts_proto v2.12.1
4
4
  // protoc unknown
5
5
  // source: common/v1/types.proto
6
6
 
@@ -1,6 +1,6 @@
1
1
  // Code generated by protoc-gen-ts_proto. DO NOT EDIT.
2
2
  // versions:
3
- // protoc-gen-ts_proto v2.11.8
3
+ // protoc-gen-ts_proto v2.12.1
4
4
  // protoc unknown
5
5
  // source: google/protobuf/empty.proto
6
6
 
@@ -1,6 +1,6 @@
1
1
  // Code generated by protoc-gen-ts_proto. DO NOT EDIT.
2
2
  // versions:
3
- // protoc-gen-ts_proto v2.11.8
3
+ // protoc-gen-ts_proto v2.12.1
4
4
  // protoc unknown
5
5
  // source: google/protobuf/struct.proto
6
6
 
@@ -1,6 +1,6 @@
1
1
  // Code generated by protoc-gen-ts_proto. DO NOT EDIT.
2
2
  // versions:
3
- // protoc-gen-ts_proto v2.11.8
3
+ // protoc-gen-ts_proto v2.12.1
4
4
  // protoc unknown
5
5
  // source: google/protobuf/wrappers.proto
6
6
 
@@ -1,6 +1,6 @@
1
1
  // Code generated by protoc-gen-ts_proto. DO NOT EDIT.
2
2
  // versions:
3
- // protoc-gen-ts_proto v2.11.8
3
+ // protoc-gen-ts_proto v2.12.1
4
4
  // protoc unknown
5
5
  // source: system/admin/v1/auth.proto
6
6
 
@@ -1,6 +1,6 @@
1
1
  // Code generated by protoc-gen-ts_proto. DO NOT EDIT.
2
2
  // versions:
3
- // protoc-gen-ts_proto v2.11.8
3
+ // protoc-gen-ts_proto v2.12.1
4
4
  // protoc unknown
5
5
  // source: system/admin/v1/base_dict.proto
6
6
 
@@ -1,6 +1,6 @@
1
1
  // Code generated by protoc-gen-ts_proto. DO NOT EDIT.
2
2
  // versions:
3
- // protoc-gen-ts_proto v2.11.8
3
+ // protoc-gen-ts_proto v2.12.1
4
4
  // protoc unknown
5
5
  // source: system/admin/v1/base_i18n.proto
6
6
 
@@ -1,6 +1,6 @@
1
1
  // Code generated by protoc-gen-ts_proto. DO NOT EDIT.
2
2
  // versions:
3
- // protoc-gen-ts_proto v2.11.8
3
+ // protoc-gen-ts_proto v2.12.1
4
4
  // protoc unknown
5
5
  // source: system/admin/v1/base_tenant.proto
6
6
 
@@ -1,6 +1,6 @@
1
1
  // Code generated by protoc-gen-ts_proto. DO NOT EDIT.
2
2
  // versions:
3
- // protoc-gen-ts_proto v2.11.8
3
+ // protoc-gen-ts_proto v2.12.1
4
4
  // protoc unknown
5
5
  // source: system/admin/v1/common.proto
6
6
 
@@ -1,6 +1,6 @@
1
1
  // Code generated by protoc-gen-ts_proto. DO NOT EDIT.
2
2
  // versions:
3
- // protoc-gen-ts_proto v2.11.8
3
+ // protoc-gen-ts_proto v2.12.1
4
4
  // protoc unknown
5
5
  // source: system/common/v1/common.proto
6
6
 
@@ -48,9 +48,8 @@ export const useDictStore = defineStore("admin-dict", {
48
48
  clearDictionaryCache() {
49
49
  this.dictionary = {};
50
50
  },
51
- /** 强制刷新字典缓存 */
51
+ /** 加载成功后整体替换字典缓存,刷新期间保留当前文案和表单选中值。 */
52
52
  async updateDictionaryCache() {
53
- this.clearDictionaryCache();
54
53
  await this.loadDictionaries(true);
55
54
  }
56
55
  },
@@ -121,14 +121,15 @@ const service = axios.create({
121
121
  baseURL: requestBaseURL,
122
122
  timeout: 50000,
123
123
  headers: { "Content-Type": "application/json;charset=utf-8" },
124
- paramsSerializer: params => qs.stringify(compactTenantProjectQueryParams(params)),
124
+ paramsSerializer: params => qs.stringify(compactTenantProjectQueryParams(params), { arrayFormat: "repeat" }),
125
125
  withCredentials: true
126
126
  });
127
127
 
128
128
  // 刷新令牌请求使用独立实例,避免与主请求拦截器互相递归。
129
129
  const refreshService = axios.create({
130
130
  baseURL: requestBaseURL,
131
- timeout: 50000,
131
+ // 路由守卫会同步等待刷新结果:超时过长时后端重启窗口内整站黑屏挂起,收敛到 8 秒。
132
+ timeout: 8000,
132
133
  headers: { "Content-Type": "application/json;charset=utf-8" },
133
134
  withCredentials: true
134
135
  });
@@ -220,6 +220,27 @@ export const demoRegionTree = [
220
220
  }
221
221
  ];
222
222
 
223
+ /** 团队树静态数据:与城市级联数据分离,供纯表格团队筛选使用。 */
224
+ export const demoOwnerGroupTree = [
225
+ {
226
+ value: "engineering",
227
+ label: "研发团队",
228
+ children: [
229
+ { value: "frontend", label: "前端组" },
230
+ { value: "backend", label: "后端组" }
231
+ ]
232
+ },
233
+ {
234
+ value: "product",
235
+ label: "产品团队",
236
+ children: [
237
+ { value: "design", label: "设计组" },
238
+ { value: "research", label: "研究组" }
239
+ ]
240
+ },
241
+ { value: "operations", label: "运营团队" }
242
+ ];
243
+
223
244
  /** 项目静态数据:25 条覆盖分页、多级表头、进度条、标签列等展示场景。 */
224
245
  export const demoProjects: DemoProject[] = [
225
246
  { id: 1, name: "星尘数据平台", level: "S", owner: "陈默", status: STATUS_ENABLE, budget: 128000000, task_total: 46, progress: 82, region: ["zhejiang", "hangzhou"], created_at: "2025-01-06 09:30:00" },
@@ -46,7 +46,7 @@ import { t } from "../../../locales";
46
46
  import DemoFormDialog from "../components/DemoFormDialog.vue";
47
47
  import DemoDetailDrawer from "../components/DemoDetailDrawer.vue";
48
48
  import type { DemoDetailModel, DemoProject } from "../demo-data";
49
- import { STATUS_DISABLE, STATUS_ENABLE, demoLevelOptions, demoProjects, demoRegionTree } from "../demo-data";
49
+ import { STATUS_DISABLE, STATUS_ENABLE, demoLevelOptions, demoOwnerGroupTree, demoProjects, demoRegionTree } from "../demo-data";
50
50
 
51
51
  defineOptions({
52
52
  name: "DemoTable",
@@ -71,7 +71,7 @@ const statusTagOptions = [
71
71
  const tagSearchOptions = Array.from({ length: 50 }, (_, index) => ({ label: t("core.demo.table.tag_item", { index: index + 1 }), value: `tag-${index + 1}` }));
72
72
 
73
73
  /** tree-select 搜索项的静态团队树(isShow: false,仅出现在搜索区)。 */
74
- const ownerGroupOptions = demoRegionTree;
74
+ const ownerGroupOptions = demoOwnerGroupTree;
75
75
 
76
76
  /** 把级联编码翻译成“省 / 市”文本,用于 region 列单元格展示。 */
77
77
  function formatRegion(region: string[]): string {
@@ -165,7 +165,8 @@ const columns = computed<ColumnProps[]>(() => [
165
165
  minWidth: 160,
166
166
  search: {
167
167
  el: "cascader",
168
- props: { options: demoRegionTree, clearable: true, placeholder: t("common.placeholder.select") },
168
+ enum: demoRegionTree,
169
+ props: { clearable: true, placeholder: t("common.placeholder.select") },
169
170
  order: 3
170
171
  },
171
172
  render: scope => formatRegion((scope.row as DemoProject).region)
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@liujitcn/kratos-admin-core",
3
3
  "private": false,
4
- "version": "0.0.97",
4
+ "version": "0.1.0",
5
5
  "type": "module",
6
6
  "description": "kratos-admin frontend runtime and base modules",
7
7
  "author": {
@@ -45,6 +45,7 @@
45
45
  "md-editor-v3": "^6.5.5",
46
46
  "md5": "^2.3.0",
47
47
  "mitt": "^3.0.1",
48
+ "monaco-editor": "^0.52.2",
48
49
  "nprogress": "^0.2.0",
49
50
  "pinia": "^4.0.3",
50
51
  "pinia-plugin-persistedstate": "^4.7.1",
@@ -150,6 +151,10 @@
150
151
  "types": "./dist/package/declarations/src/components/YamlEditor/index.vue.d.ts",
151
152
  "default": "./dist/package/src/components/YamlEditor/index.vue"
152
153
  },
154
+ "./components/CodeEditor/index.vue": {
155
+ "types": "./dist/package/declarations/src/components/CodeEditor/index.vue.d.ts",
156
+ "default": "./dist/package/src/components/CodeEditor/index.vue"
157
+ },
153
158
  "./components/ProForm/index.vue": {
154
159
  "types": "./dist/package/declarations/src/components/ProForm/index.vue.d.ts",
155
160
  "default": "./dist/package/src/components/ProForm/index.vue"
@@ -258,7 +263,7 @@
258
263
  "scripts": {
259
264
  "build": "pnpm build:package",
260
265
  "build:package": "node ../../scripts/build-package.mjs .",
261
- "test": "tsc -p tsconfig.test.json && node --test dist/test/tests/modules.test.js dist/test/tests/dict.test.js dist/test/tests/menu.test.js dist/test/tests/source-patterns.test.js dist/test/tests/codegen-hmr.test.js dist/test/tests/dialog-request.test.js dist/test/tests/request-error.test.js",
266
+ "test": "tsc -p tsconfig.test.json && node --test dist/test/tests/modules.test.js dist/test/tests/dict.test.js dist/test/tests/menu.test.js dist/test/tests/source-patterns.test.js dist/test/tests/codegen-hmr.test.js dist/test/tests/dialog-request.test.js dist/test/tests/request-error.test.js dist/test/tests/locale-reactivity.test.js",
262
267
  "type:check": "vue-tsc --noEmit --skipLibCheck"
263
268
  }
264
269
  }