@liujitcn/kratos-admin-bi 0.0.0-stage → 0.1.6

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 (166) hide show
  1. package/README.md +43 -2
  2. package/dist/package/declarations/index.d.ts +1 -0
  3. package/dist/package/declarations/src/api/bi/admin/v1/bi_card.d.ts +18 -0
  4. package/dist/package/declarations/src/api/bi/admin/v1/bi_card.js +46 -0
  5. package/dist/package/declarations/src/api/bi/admin/v1/bi_dashboard.d.ts +17 -0
  6. package/dist/package/declarations/src/api/bi/admin/v1/bi_dashboard.js +30 -0
  7. package/dist/package/declarations/src/api/bi/admin/v1/bi_query.d.ts +11 -0
  8. package/dist/package/declarations/src/api/bi/admin/v1/bi_query.js +41 -0
  9. package/dist/package/declarations/src/api/bi/admin/v1/bi_query_record.d.ts +10 -0
  10. package/dist/package/declarations/src/api/bi/admin/v1/bi_query_record.js +14 -0
  11. package/dist/package/declarations/src/api/bi/admin/v1/bi_source.d.ts +26 -0
  12. package/dist/package/declarations/src/api/bi/admin/v1/bi_source.js +46 -0
  13. package/dist/package/declarations/src/bi-core.d.ts +17 -0
  14. package/dist/package/declarations/src/bi-core.js +22 -0
  15. package/dist/package/declarations/src/bi-dashboard-schema.d.ts +31 -0
  16. package/dist/package/declarations/src/bi-dashboard-schema.js +39 -0
  17. package/dist/package/declarations/src/bi-messages.d.ts +247 -0
  18. package/dist/package/declarations/src/bi-messages.js +247 -0
  19. package/dist/package/declarations/src/bi-response.d.ts +10 -0
  20. package/dist/package/declarations/src/bi-response.js +33 -0
  21. package/dist/package/declarations/src/bi-vue.d.ts +10 -0
  22. package/dist/package/declarations/src/bi-vue.js +10 -0
  23. package/dist/package/declarations/src/card-schema.d.ts +30 -0
  24. package/dist/package/declarations/src/card-schema.js +87 -0
  25. package/dist/package/declarations/src/components/BiCardChartEditor.vue.d.ts +17 -0
  26. package/dist/package/declarations/src/components/BiCardChartEditor.vue.js +527 -0
  27. package/dist/package/declarations/src/components/BiCardFormDialog.vue.d.ts +20 -0
  28. package/dist/package/declarations/src/components/BiCardFormDialog.vue.js +495 -0
  29. package/dist/package/declarations/src/components/BiCardLibrary.vue.d.ts +3 -0
  30. package/dist/package/declarations/src/components/BiCardLibrary.vue.js +807 -0
  31. package/dist/package/declarations/src/components/BiCardVisualization.vue.d.ts +10 -0
  32. package/dist/package/declarations/src/components/BiCardVisualization.vue.js +97 -0
  33. package/dist/package/declarations/src/components/BiChart.vue.d.ts +20 -0
  34. package/dist/package/declarations/src/components/BiChart.vue.js +112 -0
  35. package/dist/package/declarations/src/components/BiDashboard.vue.d.ts +9 -0
  36. package/dist/package/declarations/src/components/BiDashboard.vue.js +81 -0
  37. package/dist/package/declarations/src/components/BiDashboardMenuDialog.vue.d.ts +12 -0
  38. package/dist/package/declarations/src/components/BiDashboardMenuDialog.vue.js +252 -0
  39. package/dist/package/declarations/src/components/BiDataCard.vue.d.ts +36 -0
  40. package/dist/package/declarations/src/components/BiDataCard.vue.js +372 -0
  41. package/dist/package/declarations/src/components/BiDesigner.vue.d.ts +17 -0
  42. package/dist/package/declarations/src/components/BiDesigner.vue.js +666 -0
  43. package/dist/package/declarations/src/components/BiSourceModelDialog.vue.d.ts +17 -0
  44. package/dist/package/declarations/src/components/BiSourceModelDialog.vue.js +2118 -0
  45. package/dist/package/declarations/src/components/BiWorkbenchPreview.vue.d.ts +7 -0
  46. package/dist/package/declarations/src/components/BiWorkbenchPreview.vue.js +176 -0
  47. package/dist/package/declarations/src/components/source-model-layout.d.ts +3 -0
  48. package/dist/package/declarations/src/components/source-model-layout.js +66 -0
  49. package/dist/package/declarations/src/dataset/index.d.ts +7 -0
  50. package/dist/package/declarations/src/dataset/index.js +25 -0
  51. package/dist/package/declarations/src/dataset/labels.d.ts +2 -0
  52. package/dist/package/declarations/src/dataset/labels.js +21 -0
  53. package/dist/package/declarations/src/dataset/transform.d.ts +20 -0
  54. package/dist/package/declarations/src/dataset/transform.js +173 -0
  55. package/dist/package/declarations/src/engine/chart-types.d.ts +14 -0
  56. package/dist/package/declarations/src/engine/chart-types.js +37 -0
  57. package/dist/package/declarations/src/engine/echarts.d.ts +16 -0
  58. package/dist/package/declarations/src/engine/echarts.js +491 -0
  59. package/dist/package/declarations/src/engine/types.d.ts +36 -0
  60. package/dist/package/declarations/src/engine/types.js +20 -0
  61. package/dist/package/declarations/src/env.d.ts +17 -0
  62. package/dist/package/declarations/src/env.js +67 -0
  63. package/dist/package/declarations/src/index.d.ts +1 -0
  64. package/dist/package/declarations/src/index.js +1 -0
  65. package/dist/package/declarations/src/locales/en-US.json +254 -0
  66. package/dist/package/declarations/src/locales/generated.d.ts +1021 -0
  67. package/dist/package/declarations/src/locales/generated.js +13 -0
  68. package/dist/package/declarations/src/locales/ja-JP.json +254 -0
  69. package/dist/package/declarations/src/locales/zh-CN.json +254 -0
  70. package/dist/package/declarations/src/locales/zh-TW.json +254 -0
  71. package/dist/package/declarations/src/module.d.ts +2 -0
  72. package/dist/package/declarations/src/module.js +9 -0
  73. package/dist/package/declarations/src/rpc/bi/admin/v1/bi_card.d.ts +145 -0
  74. package/dist/package/declarations/src/rpc/bi/admin/v1/bi_card.js +6 -0
  75. package/dist/package/declarations/src/rpc/bi/admin/v1/bi_dashboard.d.ts +107 -0
  76. package/dist/package/declarations/src/rpc/bi/admin/v1/bi_dashboard.js +6 -0
  77. package/dist/package/declarations/src/rpc/bi/admin/v1/bi_query.d.ts +157 -0
  78. package/dist/package/declarations/src/rpc/bi/admin/v1/bi_query.js +6 -0
  79. package/dist/package/declarations/src/rpc/bi/admin/v1/bi_query_record.d.ts +83 -0
  80. package/dist/package/declarations/src/rpc/bi/admin/v1/bi_query_record.js +15 -0
  81. package/dist/package/declarations/src/rpc/bi/admin/v1/bi_source.d.ts +301 -0
  82. package/dist/package/declarations/src/rpc/bi/admin/v1/bi_source.js +6 -0
  83. package/dist/package/declarations/src/rpc/common/v1/types.d.ts +60 -0
  84. package/dist/package/declarations/src/rpc/common/v1/types.js +6 -0
  85. package/dist/package/declarations/src/schema/layout.d.ts +7 -0
  86. package/dist/package/declarations/src/schema/layout.js +52 -0
  87. package/dist/package/declarations/src/schema/types.d.ts +83 -0
  88. package/dist/package/declarations/src/schema/types.js +1 -0
  89. package/dist/package/declarations/src/theme/index.d.ts +29 -0
  90. package/dist/package/declarations/src/theme/index.js +83 -0
  91. package/dist/package/declarations/src/theme/palettes.d.ts +7 -0
  92. package/dist/package/declarations/src/theme/palettes.js +28 -0
  93. package/dist/package/declarations/src/theme/presets.d.ts +73 -0
  94. package/dist/package/declarations/src/theme/presets.js +235 -0
  95. package/dist/package/declarations/src/views/card/index.vue.d.ts +3 -0
  96. package/dist/package/declarations/src/views/card/index.vue.js +18 -0
  97. package/dist/package/declarations/src/views/dashboard/index.vue.d.ts +3 -0
  98. package/dist/package/declarations/src/views/dashboard/index.vue.js +107 -0
  99. package/dist/package/declarations/src/views/designer/index.vue.d.ts +3 -0
  100. package/dist/package/declarations/src/views/designer/index.vue.js +332 -0
  101. package/dist/package/declarations/src/views/manage/index.vue.d.ts +3 -0
  102. package/dist/package/declarations/src/views/manage/index.vue.js +325 -0
  103. package/dist/package/declarations/src/views/query/index.vue.d.ts +3 -0
  104. package/dist/package/declarations/src/views/query/index.vue.js +876 -0
  105. package/dist/package/declarations/src/views/source/index.vue.d.ts +3 -0
  106. package/dist/package/declarations/src/views/source/index.vue.js +343 -0
  107. package/dist/package/declarations/src/views/workbench/index.vue.d.ts +3 -0
  108. package/dist/package/declarations/src/views/workbench/index.vue.js +362 -0
  109. package/dist/package/src/api/bi/admin/v1/bi_card.ts +67 -0
  110. package/dist/package/src/api/bi/admin/v1/bi_dashboard.ts +51 -0
  111. package/dist/package/src/api/bi/admin/v1/bi_query.ts +55 -0
  112. package/dist/package/src/api/bi/admin/v1/bi_query_record.ts +25 -0
  113. package/dist/package/src/api/bi/admin/v1/bi_source.ts +80 -0
  114. package/dist/package/src/bi-core.ts +43 -0
  115. package/dist/package/src/bi-dashboard-schema.ts +69 -0
  116. package/dist/package/src/bi-messages.ts +249 -0
  117. package/dist/package/src/bi-response.ts +39 -0
  118. package/dist/package/src/bi-vue.ts +13 -0
  119. package/dist/package/src/card-schema.ts +111 -0
  120. package/dist/package/src/components/BiCardChartEditor.vue +139 -0
  121. package/dist/package/src/components/BiCardFormDialog.vue +289 -0
  122. package/dist/package/src/components/BiCardLibrary.vue +310 -0
  123. package/dist/package/src/components/BiCardVisualization.vue +95 -0
  124. package/dist/package/src/components/BiChart.vue +134 -0
  125. package/dist/package/src/components/BiDashboard.vue +53 -0
  126. package/dist/package/src/components/BiDashboardMenuDialog.vue +177 -0
  127. package/dist/package/src/components/BiDataCard.vue +231 -0
  128. package/dist/package/src/components/BiDesigner.vue +287 -0
  129. package/dist/package/src/components/BiSourceModelDialog.vue +846 -0
  130. package/dist/package/src/components/BiWorkbenchPreview.vue +131 -0
  131. package/dist/package/src/components/source-model-layout.ts +62 -0
  132. package/dist/package/src/dataset/index.ts +28 -0
  133. package/dist/package/src/dataset/labels.ts +19 -0
  134. package/dist/package/src/dataset/transform.ts +189 -0
  135. package/dist/package/src/engine/chart-types.ts +44 -0
  136. package/dist/package/src/engine/echarts.ts +568 -0
  137. package/dist/package/src/engine/types.ts +60 -0
  138. package/dist/package/src/env.ts +82 -0
  139. package/dist/package/src/index.ts +1 -0
  140. package/dist/package/src/locales/en-US.json +254 -0
  141. package/dist/package/src/locales/generated.ts +16 -0
  142. package/dist/package/src/locales/ja-JP.json +254 -0
  143. package/dist/package/src/locales/zh-CN.json +254 -0
  144. package/dist/package/src/locales/zh-TW.json +254 -0
  145. package/dist/package/src/module.ts +12 -0
  146. package/dist/package/src/rpc/bi/admin/v1/bi_card.ts +169 -0
  147. package/dist/package/src/rpc/bi/admin/v1/bi_dashboard.ts +126 -0
  148. package/dist/package/src/rpc/bi/admin/v1/bi_query.ts +187 -0
  149. package/dist/package/src/rpc/bi/admin/v1/bi_query_record.ts +98 -0
  150. package/dist/package/src/rpc/bi/admin/v1/bi_source.ts +338 -0
  151. package/dist/package/src/rpc/common/v1/types.ts +77 -0
  152. package/dist/package/src/schema/layout.ts +54 -0
  153. package/dist/package/src/schema/types.ts +103 -0
  154. package/dist/package/src/styles.scss +1 -0
  155. package/dist/package/src/theme/index.ts +108 -0
  156. package/dist/package/src/theme/palettes.ts +34 -0
  157. package/dist/package/src/theme/presets.ts +278 -0
  158. package/dist/package/src/views/card/index.vue +5 -0
  159. package/dist/package/src/views/dashboard/index.vue +58 -0
  160. package/dist/package/src/views/designer/index.vue +164 -0
  161. package/dist/package/src/views/manage/index.vue +276 -0
  162. package/dist/package/src/views/query/index.vue +593 -0
  163. package/dist/package/src/views/source/index.vue +214 -0
  164. package/dist/package/src/views/workbench/index.vue +208 -0
  165. package/package.json +67 -3
  166. package/tsconfig.json +12 -0
@@ -0,0 +1,289 @@
1
+ <template>
2
+ <FormDialog
3
+ ref="dialogRef"
4
+ :model-value="modelValue"
5
+ :title="card ? t('bi.card.edit') : t('bi.card.create')"
6
+ width="min(1040px, 94vw)"
7
+ label-width="7em"
8
+ :model="form"
9
+ :fields="fields"
10
+ :rules="rules"
11
+ :confirm-loading="saving"
12
+ :close-on-click-modal="!saving"
13
+ :form-props="{ disabled: saving }"
14
+ @update:model-value="emit('update:modelValue', $event)"
15
+ @confirm="submit"
16
+ :destroy-on-close="true"
17
+ @closed="reset"
18
+ >
19
+ <template #measures>
20
+ <div class="bi-card-measures">
21
+ <div v-for="(measure, index) in form.measures" :key="index" class="bi-card-measures__row">
22
+ <el-select disabled v-model="measure.code" :aria-label="t('bi.card.measure')">
23
+ <el-option v-for="item in source?.measures ?? []" :key="item.code" :label="item.name" :value="item.code" />
24
+ </el-select>
25
+ <el-select disabled v-model="measure.aggregation" :aria-label="t('bi.card.aggregation')">
26
+ <el-option
27
+ v-for="aggregation in source?.measures.find(item => item.code === measure.code)?.aggregations ?? []"
28
+ :key="aggregation"
29
+ :label="t(`bi.card.aggregation.${aggregation}`)"
30
+ :value="aggregation"
31
+ />
32
+ </el-select>
33
+ </div>
34
+ </div>
35
+ </template>
36
+ <template #chart>
37
+ <BiCardChartEditor v-model="chartConfig" v-model:chart-type="form.chart_type" :query="query" :result="previewResult" />
38
+ </template>
39
+ <template #preview>
40
+ <BiDataCard v-if="previewResult" :card="previewCard" :preview-result="previewResult" :source-name="source?.name" />
41
+ <el-empty v-else :image-size="36" :description="t('bi.card.preview_empty')" />
42
+ </template>
43
+ <template #footer>
44
+ <el-button :disabled="saving" @click="emit('update:modelValue', false)">{{ t("common.action.cancel") }}</el-button>
45
+ <el-button :loading="previewing" :disabled="saving" @click="preview">{{ t("bi.card.preview") }}</el-button>
46
+ <el-button type="primary" :loading="saving" @click="submit">{{ t("bi.card.save") }}</el-button>
47
+ </template>
48
+ </FormDialog>
49
+ </template>
50
+ <script setup lang="ts">
51
+ import { computed, reactive, ref, watch } from "vue";
52
+ import { t } from "@liujitcn/kratos-admin-core";
53
+ import FormDialog from "@liujitcn/kratos-admin-core/components/Dialog/FormDialog.vue";
54
+ import type { ProFormField } from "@liujitcn/kratos-admin-core/components/ProForm/interface";
55
+ import type { BiCard } from "../rpc/bi/admin/v1/bi_card";
56
+ import type { BiMeasureSpec, BiQueryResult, BiQuerySpec, BiSourceDescriptor } from "../rpc/bi/admin/v1/bi_query";
57
+ import { defBiCardService } from "../api/bi/admin/v1/bi_card";
58
+ import { defBiQueryService } from "../api/bi/admin/v1/bi_query";
59
+ import BiDataCard from "./BiDataCard.vue";
60
+ import BiCardChartEditor from "./BiCardChartEditor.vue";
61
+ import { parseCardChart } from "../card-schema";
62
+ import type { CardChartConfig } from "../card-schema";
63
+
64
+ /** 卡片编辑状态,查询条件沿用原语义计划。 */
65
+ interface BiCardEditState {
66
+ name: string;
67
+ source_code: string;
68
+ dimensions: string[];
69
+ measures: BiMeasureSpec[];
70
+ chart_type: string;
71
+ refresh_seconds: number;
72
+ is_shared: boolean;
73
+ }
74
+
75
+ const props = defineProps<{
76
+ modelValue: boolean;
77
+ sources: BiSourceDescriptor[];
78
+ card?: BiCard;
79
+ initialQuery?: BiQuerySpec;
80
+ initialResult?: BiQueryResult;
81
+ initialTitle?: string;
82
+ initialChartType?: string;
83
+ }>();
84
+ const emit = defineEmits<{ "update:modelValue": [value: boolean]; saved: [card: BiCard] }>();
85
+ const dialogRef = ref<InstanceType<typeof FormDialog>>();
86
+ const form = reactive<BiCardEditState>(defaultForm());
87
+ const baseQuery = ref<BiQuerySpec>();
88
+ const previewResult = ref<BiQueryResult>();
89
+ const sourceRecordID = ref(0);
90
+ const chartConfig = ref<CardChartConfig>({ encoding: {}, style: { showLegend: true, showLabel: false } });
91
+ const saving = ref(false);
92
+ const previewing = ref(false);
93
+ let previewRequest = 0;
94
+ const source = computed(() => props.sources.find(item => item.code === form.source_code));
95
+ const query = computed<BiQuerySpec>(() => ({
96
+ source_code: form.source_code,
97
+ dimensions: [...form.dimensions],
98
+ measures: form.measures.map(item => ({ ...item })),
99
+ filters: baseQuery.value?.filters ?? [],
100
+ limit: baseQuery.value?.limit || 100,
101
+ time_range: baseQuery.value?.time_range,
102
+ orders: baseQuery.value?.orders ?? []
103
+ }));
104
+ const previewCard = computed<BiCard>(() => ({
105
+ id: 0,
106
+ tenant_id: 0,
107
+ project_id: 0,
108
+ owner_id: 0,
109
+ name: form.name,
110
+ source_code: form.source_code,
111
+ query: query.value,
112
+ chart_type: form.chart_type,
113
+ chart_json: JSON.stringify(chartConfig.value),
114
+ query_record_id: sourceRecordID.value,
115
+ refresh_seconds: form.refresh_seconds,
116
+ is_shared: form.is_shared,
117
+ updated_at: "",
118
+ category: "mine",
119
+ can_edit: true
120
+ }));
121
+ const fields = computed<ProFormField[]>(() => [
122
+ { prop: "name", label: t("bi.card.name"), component: "input", colSpan: 12, props: { maxlength: 128 } },
123
+ {
124
+ prop: "source_code",
125
+ label: t("bi.card.source"),
126
+ component: "select",
127
+ colSpan: 12,
128
+ options: props.sources.map(item => ({ label: item.name, value: item.code })),
129
+ props: { disabled: true }
130
+ },
131
+ {
132
+ prop: "dimensions",
133
+ label: t("bi.card.dimensions"),
134
+ component: "select",
135
+ colSpan: 12,
136
+ options: (source.value?.dimensions ?? []).map(item => ({ label: item.name, value: item.code })),
137
+ props: {
138
+ multiple: true,
139
+ multipleLimit: 2,
140
+ disabled: true
141
+ }
142
+ },
143
+ { prop: "measures", label: t("bi.card.measure"), component: "slot", slotName: "measures", colSpan: 12 },
144
+ { prop: "chart", label: t("bi.card.chart_type"), component: "slot", slotName: "chart", colSpan: 12 },
145
+ { prop: "preview", label: t("bi.card.preview"), component: "slot", slotName: "preview", colSpan: 12 },
146
+ {
147
+ prop: "refresh_seconds",
148
+ label: t("bi.card.refresh"),
149
+ component: "select",
150
+ colSpan: 12,
151
+ options: [0, 300, 86400].map(value => ({ label: t(`bi.card.refresh.${value}`), value }))
152
+ },
153
+ { prop: "is_shared", label: t("bi.card.share"), component: "switch", colSpan: 12 }
154
+ ]);
155
+ const rules = computed(() => ({
156
+ name: [{ required: true, whitespace: true, message: t("bi.card.validation.name"), trigger: "blur" }],
157
+ source_code: [{ required: true, message: t("bi.card.validation.source"), trigger: "change" }],
158
+ measures: [{ type: "array" as const, required: true, min: 1, message: t("bi.card.validation.measures"), trigger: "change" }]
159
+ }));
160
+
161
+ /** 创建独立表单状态,关闭后不保留上次卡片。 */
162
+ function defaultForm(): BiCardEditState {
163
+ return {
164
+ name: "",
165
+ source_code: "",
166
+ dimensions: [],
167
+ measures: [],
168
+ chart_type: "bar",
169
+ refresh_seconds: 0,
170
+ is_shared: false
171
+ };
172
+ }
173
+
174
+ /** 打开弹窗时复制原查询,保留问数生成的过滤、排序和时间范围。 */
175
+ watch(
176
+ () => props.modelValue,
177
+ visible => {
178
+ if (!visible) return;
179
+ const current = props.card?.query ?? props.initialQuery;
180
+ baseQuery.value = current ? (JSON.parse(JSON.stringify(current)) as BiQuerySpec) : undefined;
181
+ Object.assign(form, defaultForm(), {
182
+ name: props.card?.name ?? props.initialTitle ?? "",
183
+ source_code: current?.source_code || props.sources[0]?.code || "",
184
+ dimensions: [...(current?.dimensions ?? [])],
185
+ measures: (current?.measures ?? []).map(item => ({ ...item })),
186
+ chart_type: props.card?.chart_type || (current && !current.dimensions.length ? "metric" : props.initialChartType || "bar"),
187
+ refresh_seconds: props.card?.refresh_seconds ?? 0,
188
+ is_shared: props.card?.is_shared ?? false
189
+ });
190
+ previewResult.value = props.initialResult;
191
+ sourceRecordID.value = props.card?.query_record_id || props.initialResult?.query_record_id || 0;
192
+ chartConfig.value = parseCardChart(props.card ?? { chart_json: "" });
193
+ }
194
+ );
195
+ watch(
196
+ query,
197
+ () => {
198
+ previewRequest++;
199
+ previewResult.value = undefined;
200
+ sourceRecordID.value = 0;
201
+ chartConfig.value = { encoding: {}, style: { ...chartConfig.value.style } };
202
+ },
203
+ { deep: true, flush: "sync" }
204
+ );
205
+
206
+ /** 预览当前语义配置,忽略配置变化前的迟到响应。 */
207
+ async function preview() {
208
+ if (!(await dialogRef.value?.validate()) || previewing.value) return;
209
+ if (!sourceRecordID.value) {
210
+ ElMessage.warning(t("bi.card.ask_required"));
211
+ return;
212
+ }
213
+ const request = ++previewRequest;
214
+ previewing.value = true;
215
+ try {
216
+ const result = await defBiQueryService.PreviewBiQuery({ query: query.value });
217
+ if (request === previewRequest) {
218
+ previewResult.value = result;
219
+ }
220
+ } catch (cause) {
221
+ ElMessage.error(cause instanceof Error ? cause.message : t("bi.query.error"));
222
+ } finally {
223
+ previewing.value = false;
224
+ }
225
+ }
226
+
227
+ /** 仅保存与原始智能问数关联的卡片,图表编辑不改变查询定义。 */
228
+ async function submit() {
229
+ if (!(await dialogRef.value?.validate()) || saving.value || previewing.value) return;
230
+ if (!sourceRecordID.value) {
231
+ ElMessage.warning(t("bi.card.ask_required"));
232
+ return;
233
+ }
234
+ if (!previewResult.value) await preview();
235
+ if (!previewResult.value || !sourceRecordID.value) return;
236
+ saving.value = true;
237
+ try {
238
+ const saved = await defBiCardService.SaveBiCard({
239
+ id: props.card?.id ?? 0,
240
+ card: {
241
+ name: form.name,
242
+ query: query.value,
243
+ chart_type: form.chart_type,
244
+ chart_json: JSON.stringify(chartConfig.value),
245
+ query_record_id: sourceRecordID.value,
246
+ refresh_seconds: form.refresh_seconds,
247
+ is_shared: form.is_shared
248
+ }
249
+ });
250
+ emit("saved", saved);
251
+ emit("update:modelValue", false);
252
+ ElMessage.success(t("bi.card.saved"));
253
+ } catch (cause) {
254
+ ElMessage.error(cause instanceof Error ? cause.message : t("bi.card.save_failed"));
255
+ } finally {
256
+ saving.value = false;
257
+ }
258
+ }
259
+
260
+ /** 关闭弹窗后清空表单、预览与校验状态。 */
261
+ function reset() {
262
+ previewRequest++;
263
+ baseQuery.value = undefined;
264
+ Object.assign(form, defaultForm());
265
+ previewResult.value = undefined;
266
+ sourceRecordID.value = 0;
267
+ chartConfig.value = { encoding: {}, style: { showLegend: true, showLabel: false } };
268
+ dialogRef.value?.clearValidate();
269
+ }
270
+ </script>
271
+
272
+ <style scoped>
273
+ .bi-card-measures {
274
+ display: flex;
275
+ width: 100%;
276
+ flex-direction: column;
277
+ gap: 8px;
278
+ }
279
+ .bi-card-measures__row {
280
+ display: grid;
281
+ grid-template-columns: minmax(0, 1fr) minmax(90px, 0.65fr) auto;
282
+ gap: 8px;
283
+ }
284
+ @media (max-width: 480px) {
285
+ .bi-card-measures__row {
286
+ grid-template-columns: minmax(0, 1fr) 90px auto;
287
+ }
288
+ }
289
+ </style>
@@ -0,0 +1,310 @@
1
+ <template>
2
+ <main class="bi-card-library">
3
+ <el-card class="admin-page-card" shadow="never">
4
+ <el-tabs v-model="category" @tab-change="search">
5
+ <el-tab-pane v-for="item in categories" :key="item" :name="item">
6
+ <template #label>
7
+ <span>{{ t(`bi.card.category.${item || "all"}`) }}</span>
8
+ <small class="bi-card-library__count">{{ counts[item || "all"] }}</small>
9
+ </template>
10
+ </el-tab-pane>
11
+ </el-tabs>
12
+ <div class="bi-card-library__filters">
13
+ <el-input v-model="name" :placeholder="t('bi.card.search')" clearable @keyup.enter="search" @clear="search" />
14
+ <el-select v-model="sourceCode" :placeholder="t('bi.card.all_sources')" clearable @change="search">
15
+ <el-option v-for="item in sources" :key="item.code" :label="item.name" :value="item.code" />
16
+ </el-select>
17
+ <el-button type="primary" :icon="icons.Search" :loading="loading" @click="search">{{
18
+ t("common.action.search")
19
+ }}</el-button>
20
+ <span>{{ t("bi.card.total", { count: total }) }}</span>
21
+ </div>
22
+ </el-card>
23
+ <el-skeleton v-if="loading" :rows="6" animated /><el-empty v-else-if="!cards.length" :description="t('bi.card.empty')" />
24
+ <div v-else class="bi-card-library__grid">
25
+ <BiDataCard
26
+ v-for="card in cards"
27
+ :key="card.id"
28
+ :card="card"
29
+ :source-name="sources.find(item => item.code === card.source_code)?.name || card.source_code"
30
+ ><template #actions>
31
+ <el-tooltip :content="t(workbench.favorite_ids.includes(card.id) ? 'bi.card.unfavorite' : 'bi.card.favorite', { name: card.name })">
32
+ <el-button
33
+ text
34
+ circle
35
+ :type="workbench.favorite_ids.includes(card.id) ? 'warning' : 'default'"
36
+ :icon="workbench.favorite_ids.includes(card.id) ? icons.StarFilled : icons.Star"
37
+ :aria-label="t(workbench.favorite_ids.includes(card.id) ? 'bi.card.unfavorite' : 'bi.card.favorite', { name: card.name })"
38
+ :aria-pressed="workbench.favorite_ids.includes(card.id)"
39
+ @click="toggleFavorite(card)"
40
+ />
41
+ </el-tooltip>
42
+ </template
43
+ ><template #footer
44
+ ><div>
45
+ <el-button link type="primary" @click="previewCard = card">{{ t("bi.card.preview") }}</el-button
46
+ ><el-button v-if="card.can_edit" link @click="openEditor(card)">{{ t("common.action.edit") }}</el-button
47
+ ><el-button v-if="card.can_edit" link type="danger" @click="remove(card)">{{ t("common.action.delete") }}</el-button>
48
+ </div>
49
+ <el-button size="small" type="primary" @click="pickDashboard(card)">{{ t("bi.card.add_to_dashboard") }}</el-button
50
+ ><el-button
51
+ v-if="card.query_record_id"
52
+ link
53
+ :disabled="!card.can_edit"
54
+ @click="router.push({ path: '/bi/query', query: { record_id: String(card.query_record_id) } })"
55
+ >{{ t("bi.card.source_record", { id: card.query_record_id }) }}</el-button
56
+ ></template
57
+ ></BiDataCard
58
+ >
59
+ </div>
60
+ <el-pagination
61
+ v-if="total > 100"
62
+ v-model:current-page="page"
63
+ :page-size="100"
64
+ :total="total"
65
+ layout="prev,pager,next"
66
+ @current-change="loadCards"
67
+ /><BiCardFormDialog v-model="editorOpen" :sources="sources" :card="editingCard" @saved="reload" /><el-dialog
68
+ :model-value="Boolean(previewCard)"
69
+ :title="t('bi.card.preview')"
70
+ width="min(760px,94vw)"
71
+ destroy-on-close
72
+ @close="previewCard = undefined"
73
+ ><BiDataCard
74
+ v-if="previewCard"
75
+ :card="previewCard"
76
+ :source-name="sources.find(item => item.code === previewCard?.source_code)?.name || previewCard.source_code" /></el-dialog
77
+ ><FormDialog
78
+ v-model="dashboardOpen"
79
+ :model="target"
80
+ :title="t('bi.card.add_to_dashboard')"
81
+ :fields="dashboardFields"
82
+ :rules="{ id: [{ required: true, message: t('bi.card.select_dashboard'), trigger: 'change' }] }"
83
+ :confirm-loading="saving"
84
+ @confirm="addToDashboard"
85
+ @closed="target.id = undefined"
86
+ />
87
+ </main>
88
+ </template>
89
+ <script setup lang="ts">
90
+ import { computed, onMounted, onActivated, reactive, ref } from "vue";
91
+ import { useRouter } from "vue-router";
92
+ import { t } from "@liujitcn/kratos-admin-core";
93
+ import FormDialog from "@liujitcn/kratos-admin-core/components/Dialog/FormDialog.vue";
94
+ import type { ProFormField } from "@liujitcn/kratos-admin-core/components/ProForm/interface";
95
+ import type { BiCard, BiCardCounts, BiWorkbench } from "../rpc/bi/admin/v1/bi_card";
96
+ import type { BiDashboard } from "../rpc/bi/admin/v1/bi_dashboard";
97
+ import type { BiSourceDescriptor } from "../rpc/bi/admin/v1/bi_query";
98
+ import { defBiCardService } from "../api/bi/admin/v1/bi_card";
99
+ import { defBiQueryService } from "../api/bi/admin/v1/bi_query";
100
+ import { defBiDashboardService } from "../api/bi/admin/v1/bi_dashboard";
101
+ import { parseDashboardLayout, reflowDashboardCards } from "../bi-dashboard-schema";
102
+ import BiDataCard from "./BiDataCard.vue";
103
+ import BiCardFormDialog from "./BiCardFormDialog.vue";
104
+ const icons = { Star, StarFilled, Search };
105
+ const router = useRouter(),
106
+ cards = ref<BiCard[]>([]),
107
+ sources = ref<BiSourceDescriptor[]>([]),
108
+ name = ref(""),
109
+ sourceCode = ref(""),
110
+ category = ref(""),
111
+ page = ref(1),
112
+ total = ref(0),
113
+ loading = ref(false),
114
+ saving = ref(false),
115
+ editorOpen = ref(false),
116
+ dashboardOpen = ref(false),
117
+ editingCard = ref<BiCard>(),
118
+ previewCard = ref<BiCard>(),
119
+ addingCard = ref<BiCard>(),
120
+ dashboards = ref<BiDashboard[]>([]);
121
+ const categories = ["", "mine", "favorite", "shared", "system"];
122
+ const counts = ref<BiCardCounts>({ all: 0, system: 0, mine: 0, favorite: 0, shared: 0 });
123
+ const workbench = ref<BiWorkbench>({ dashboard_ids: [], favorite_ids: [], dashboards: [] });
124
+ const target = reactive({ id: undefined as number | undefined });
125
+ let request = 0,
126
+ loaded = false;
127
+ const dashboardFields = computed<ProFormField[]>(() => [
128
+ {
129
+ prop: "id",
130
+ label: t("bi.dashboard.title"),
131
+ component: "select",
132
+ options: dashboards.value.map(item => ({ label: item.name, value: item.id })),
133
+ props: { filterable: true }
134
+ }
135
+ ]);
136
+ /** 按同一过滤条件查询当前可见卡片,丢弃迟到请求。 */
137
+ async function loadCards() {
138
+ const current = ++request;
139
+ loading.value = true;
140
+ try {
141
+ const data = await defBiCardService.PageBiCard({
142
+ name: name.value,
143
+ source_code: sourceCode.value,
144
+ category: category.value,
145
+ search_source_codes: sources.value.filter(item => item.name.includes(name.value)).map(item => item.code),
146
+ page_num: page.value,
147
+ page_size: 100
148
+ });
149
+ if (current !== request) return;
150
+ cards.value = data.cards;
151
+ total.value = data.total;
152
+ counts.value = data.counts!;
153
+ loaded = true;
154
+ } catch (cause) {
155
+ ElMessage.error(cause instanceof Error ? cause.message : t("bi.query.error"));
156
+ } finally {
157
+ if (current === request) loading.value = false;
158
+ }
159
+ }
160
+ /** 搜索或切换分类时从第一页开始。 */
161
+ function search() {
162
+ page.value = 1;
163
+ void loadCards();
164
+ }
165
+ /** 预加载查询目录和收藏后刷新列表。 */
166
+ async function reload() {
167
+ try {
168
+ const [dictionary, config] = await Promise.all([defBiQueryService.ListBiSources({}), defBiCardService.GetBiWorkbench({})]);
169
+ sources.value = dictionary.sources;
170
+ workbench.value = config;
171
+ await loadCards();
172
+ } catch (cause) {
173
+ ElMessage.error(cause instanceof Error ? cause.message : t("bi.query.error"));
174
+ }
175
+ }
176
+ /** 打开已有卡片的编辑表单。 */
177
+ function openEditor(card: BiCard) {
178
+ editingCard.value = card;
179
+ editorOpen.value = true;
180
+ }
181
+ /** 保存收藏后只更新星标选中状态,保持当前列表和图表实例。 */
182
+ async function toggleFavorite(card: BiCard) {
183
+ try {
184
+ const latest = await defBiCardService.GetBiWorkbench({});
185
+ const ids = latest.favorite_ids.includes(card.id)
186
+ ? latest.favorite_ids.filter(id => id !== card.id)
187
+ : [...latest.favorite_ids, card.id];
188
+ const saved = await defBiCardService.SaveBiWorkbench({ dashboard_ids: latest.dashboard_ids, favorite_ids: ids });
189
+ workbench.value.favorite_ids = saved.favorite_ids;
190
+ } catch (cause) {
191
+ ElMessage.error(cause instanceof Error ? cause.message : t("bi.card.save_failed"));
192
+ }
193
+ }
194
+ /** 删除当前用户自己的卡片,确认时展示卡片名称。 */
195
+ async function remove(card: BiCard) {
196
+ try {
197
+ await ElMessageBox.confirm(t("bi.card.delete_confirm", { name: card.name }), t("common.action.delete"), { type: "warning" });
198
+ } catch {
199
+ return;
200
+ }
201
+ try {
202
+ await defBiCardService.DeleteBiCard({ id: card.id });
203
+ await reload();
204
+ } catch (cause) {
205
+ ElMessage.error(cause instanceof Error ? cause.message : t("bi.card.delete_failed"));
206
+ }
207
+ }
208
+ /** 选择当前用户可编辑的仪表盘草稿。 */
209
+ async function pickDashboard(card: BiCard) {
210
+ try {
211
+ const result: BiDashboard[] = [];
212
+ let current = 1;
213
+ while (true) {
214
+ const data = await defBiDashboardService.PageBiDashboard({ name: "", page_num: current, page_size: 100 });
215
+ result.push(...data.dashboards.filter(item => item.can_edit));
216
+ if (current * 100 >= data.total || !data.dashboards.length) break;
217
+ current++;
218
+ }
219
+ if (!result.length) {
220
+ ElMessage.info(t("bi.card.create_dashboard_first"));
221
+ return;
222
+ }
223
+ dashboards.value = result;
224
+ addingCard.value = card;
225
+ target.id = undefined;
226
+ dashboardOpen.value = true;
227
+ } catch (cause) {
228
+ ElMessage.error(cause instanceof Error ? cause.message : t("bi.query.error"));
229
+ }
230
+ }
231
+ /** 只追加卡片引用,发布之前不改变正式仪表盘。 */
232
+ async function addToDashboard() {
233
+ if (!target.id || !addingCard.value || saving.value) return;
234
+ saving.value = true;
235
+ try {
236
+ const item = await defBiDashboardService.GetBiDashboard({ id: target.id });
237
+ const layout = parseDashboardLayout(item.schema_json);
238
+ if (layout.cards.some(item => item.card_id === addingCard.value!.id)) {
239
+ ElMessage.info(t("bi.card.already_in_dashboard"));
240
+ return;
241
+ }
242
+ layout.cards = reflowDashboardCards([
243
+ ...layout.cards,
244
+ { id: `card_${addingCard.value.id}`, card_id: addingCard.value.id, w: 6, h: 4 }
245
+ ]);
246
+ await defBiDashboardService.SaveBiDashboard({ dashboard: { ...item, schema_json: JSON.stringify(layout) } });
247
+ dashboardOpen.value = false;
248
+ ElMessage.success(t("bi.card.add_dashboard_success"));
249
+ } catch (cause) {
250
+ ElMessage.error(cause instanceof Error ? cause.message : t("bi.card.save_failed"));
251
+ } finally {
252
+ saving.value = false;
253
+ }
254
+ }
255
+ onMounted(() => void reload());
256
+ onActivated(() => {
257
+ if (loaded) void reload();
258
+ });
259
+ </script>
260
+ <style scoped>
261
+ .bi-card-library {
262
+ display: flex;
263
+ flex-direction: column;
264
+ gap: 16px;
265
+ min-width: 0;
266
+ }
267
+ .bi-card-library__count {
268
+ margin-left: 8px;
269
+ color: var(--el-text-color-secondary);
270
+ font-size: 12px;
271
+ }
272
+ .bi-card-library__filters {
273
+ display: flex;
274
+ align-items: center;
275
+ flex-wrap: wrap;
276
+ gap: 12px;
277
+ }
278
+ .bi-card-library__filters > .el-input {
279
+ width: 260px;
280
+ }
281
+ .bi-card-library__filters > .el-select {
282
+ width: 200px;
283
+ }
284
+ .bi-card-library__filters > span {
285
+ font-size: 12px;
286
+ color: var(--el-text-color-secondary);
287
+ }
288
+ .bi-card-library__grid {
289
+ display: grid;
290
+ grid-template-columns: repeat(3, minmax(0, 1fr));
291
+ gap: 16px;
292
+ }
293
+ .bi-card-library :deep(.el-pagination) {
294
+ justify-content: flex-end;
295
+ }
296
+ @media (max-width: 1100px) {
297
+ .bi-card-library__grid {
298
+ grid-template-columns: repeat(2, minmax(0, 1fr));
299
+ }
300
+ }
301
+ @media (max-width: 650px) {
302
+ .bi-card-library__grid {
303
+ grid-template-columns: 1fr;
304
+ }
305
+ .bi-card-library__filters > .el-input,
306
+ .bi-card-library__filters > .el-select {
307
+ width: 100%;
308
+ }
309
+ }
310
+ </style>
@@ -0,0 +1,95 @@
1
+ <template>
2
+ <div v-if="data.metricOnly" class="bi-card-metrics" :class="{ 'is-multiple': data.metrics.length > 1, 'is-compact': compact }">
3
+ <div v-for="metric in data.metrics" :key="metric.code" class="bi-card-metrics__item">
4
+ <span v-if="data.metrics.length > 1">{{ metric.name }}</span
5
+ ><strong>{{ formatCardMetric(metric.value) }}</strong>
6
+ </div>
7
+ </div>
8
+ <div v-else class="bi-card-plot">
9
+ <BiChart :chart="data.chart" :rows="data.rows" :theme="theme" :empty-text="t('common.message.no_data')" />
10
+ </div>
11
+ </template>
12
+
13
+ <script setup lang="ts">
14
+ import { computed } from "vue";
15
+ import { t } from "@liujitcn/kratos-admin-core";
16
+ import type { BiCard } from "../rpc/bi/admin/v1/bi_card";
17
+ import type { BiQueryResult } from "../rpc/bi/admin/v1/bi_query";
18
+ import { buildCardChart, formatCardMetric } from "../card-schema";
19
+ import { useBiTheme } from "../env";
20
+ import BiChart from "./BiChart.vue";
21
+
22
+ const props = defineProps<{ card: BiCard; result: BiQueryResult; compact?: boolean }>();
23
+ const theme = useBiTheme();
24
+ const data = computed(() => {
25
+ const value = buildCardChart(props.card, props.result);
26
+ if (props.compact) {
27
+ value.chart.style = { ...value.chart.style, showLegend: false, showLabel: false, dataZoom: false };
28
+ value.chart.options = { tooltip: { show: false }, visualMap: { show: false } };
29
+ if (["bar", "line", "area", "scatter", "heatmap"].includes(props.card.chart_type)) {
30
+ value.chart.options.grid = { left: 4, right: 4, top: 6, bottom: 4 };
31
+ value.chart.options.xAxis = { type: props.card.chart_type === "scatter" ? "value" : "category", show: false };
32
+ value.chart.options.yAxis = { type: props.card.chart_type === "heatmap" ? "category" : "value", show: false };
33
+ }
34
+ }
35
+ return value;
36
+ });
37
+ </script>
38
+
39
+ <style scoped>
40
+ .bi-card-metrics {
41
+ display: grid;
42
+ height: 100%;
43
+ align-content: center;
44
+ gap: 16px;
45
+ overflow: auto;
46
+ color: var(--el-text-color-primary);
47
+ }
48
+ .bi-card-metrics__item {
49
+ display: flex;
50
+ min-width: 0;
51
+ flex-direction: column;
52
+ gap: 4px;
53
+ text-align: center;
54
+ overflow-wrap: anywhere;
55
+ }
56
+ .bi-card-metrics__item strong {
57
+ font-size: 32px;
58
+ font-weight: 500;
59
+ font-variant-numeric: tabular-nums;
60
+ }
61
+ .bi-card-metrics__item span,
62
+ .bi-card-gauge__details span {
63
+ color: var(--el-text-color-secondary);
64
+ font-size: 12px;
65
+ }
66
+ .bi-card-metrics:not(.is-multiple) {
67
+ align-content: start;
68
+ padding-top: 4px;
69
+ }
70
+ .bi-card-metrics:not(.is-multiple) .bi-card-metrics__item {
71
+ text-align: left;
72
+ }
73
+ .bi-card-metrics.is-multiple {
74
+ gap: 12px;
75
+ grid-template-columns: repeat(auto-fit, minmax(60px, 1fr));
76
+ }
77
+ .bi-card-metrics.is-multiple strong {
78
+ font-size: 22px;
79
+ }
80
+ .bi-card-metrics.is-compact .bi-card-metrics__item strong {
81
+ font-size: 20px;
82
+ }
83
+ .bi-card-plot {
84
+ display: flex;
85
+ height: 100%;
86
+ min-height: 0;
87
+ flex-direction: column;
88
+ gap: 4px;
89
+ }
90
+ .bi-card-plot :deep(.bi-chart) {
91
+ flex: 1;
92
+ min-height: 0;
93
+ height: auto;
94
+ }
95
+ </style>