@c2-markdown-ui/vue 0.0.0-stage → 0.4.4

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 (61) hide show
  1. package/README.md +112 -2
  2. package/dist/MarkdownUI.vue.d.ts +15 -0
  3. package/dist/favicon.ico +0 -0
  4. package/dist/index.css +1 -0
  5. package/dist/index.d.ts +23 -0
  6. package/dist/index.js +10843 -0
  7. package/dist/index.umd.cjs +53 -0
  8. package/dist/widgets/Button.vue.d.ts +19 -0
  9. package/dist/widgets/ButtonGroup.vue.d.ts +13 -0
  10. package/dist/widgets/Cascader.vue.d.ts +48 -0
  11. package/dist/widgets/Chart.vue.d.ts +24 -0
  12. package/dist/widgets/DatePicker.vue.d.ts +42 -0
  13. package/dist/widgets/Form.vue.d.ts +59 -0
  14. package/dist/widgets/Incomplete.vue.d.ts +2 -0
  15. package/dist/widgets/MultipleChoiceQuestion.vue.d.ts +20 -0
  16. package/dist/widgets/NumberInput.vue.d.ts +37 -0
  17. package/dist/widgets/Quiz.vue.d.ts +39 -0
  18. package/dist/widgets/RadioGroup.vue.d.ts +43 -0
  19. package/dist/widgets/Select.vue.d.ts +27 -0
  20. package/dist/widgets/SelectMulti.vue.d.ts +38 -0
  21. package/dist/widgets/ShortAnswerQuestion.vue.d.ts +22 -0
  22. package/dist/widgets/Slider.vue.d.ts +21 -0
  23. package/dist/widgets/Switch.vue.d.ts +28 -0
  24. package/dist/widgets/Text.vue.d.ts +22 -0
  25. package/dist/widgets/TextInput.vue.d.ts +32 -0
  26. package/dist/widgets/TreeSelect.vue.d.ts +109 -0
  27. package/dist/widgets/Widget.vue.d.ts +8 -0
  28. package/dist/widgets/chartRemoteData.d.ts +17 -0
  29. package/dist/widgets/index.d.ts +20 -0
  30. package/dist/widgets/remoteChoices.d.ts +22 -0
  31. package/dist/widgets/treeSelectTypes.d.ts +43 -0
  32. package/package.json +46 -4
  33. package/src/App.vue +11 -0
  34. package/src/lib/MarkdownUI.vue +105 -0
  35. package/src/lib/index.ts +28 -0
  36. package/src/lib/widgets/Button.vue +47 -0
  37. package/src/lib/widgets/ButtonGroup.vue +43 -0
  38. package/src/lib/widgets/Cascader.vue +475 -0
  39. package/src/lib/widgets/Chart.vue +287 -0
  40. package/src/lib/widgets/DatePicker.vue +659 -0
  41. package/src/lib/widgets/Form.vue +479 -0
  42. package/src/lib/widgets/Incomplete.vue +56 -0
  43. package/src/lib/widgets/MultipleChoiceQuestion.vue +73 -0
  44. package/src/lib/widgets/NumberInput.vue +179 -0
  45. package/src/lib/widgets/Quiz.vue +205 -0
  46. package/src/lib/widgets/RadioGroup.vue +237 -0
  47. package/src/lib/widgets/Select.vue +279 -0
  48. package/src/lib/widgets/SelectMulti.vue +145 -0
  49. package/src/lib/widgets/ShortAnswerQuestion.vue +79 -0
  50. package/src/lib/widgets/Slider.vue +61 -0
  51. package/src/lib/widgets/Switch.vue +68 -0
  52. package/src/lib/widgets/Text.vue +59 -0
  53. package/src/lib/widgets/TextInput.vue +83 -0
  54. package/src/lib/widgets/TreeSelect.vue +872 -0
  55. package/src/lib/widgets/Widget.vue +185 -0
  56. package/src/lib/widgets/chartRemoteData.ts +84 -0
  57. package/src/lib/widgets/index.ts +20 -0
  58. package/src/lib/widgets/remoteChoices.ts +64 -0
  59. package/src/lib/widgets/treeSelectTypes.ts +49 -0
  60. package/src/lib/widgets.css +1754 -0
  61. package/src/main.ts +4 -0
@@ -0,0 +1,287 @@
1
+ <script setup lang="ts">
2
+ import { computed, onMounted, onUnmounted, ref, watch } from 'vue';
3
+ import {
4
+ Chart,
5
+ CategoryScale,
6
+ LinearScale,
7
+ PointElement,
8
+ LineElement,
9
+ BarElement,
10
+ Title,
11
+ Tooltip,
12
+ Legend,
13
+ ArcElement,
14
+ LineController,
15
+ BarController,
16
+ PieController,
17
+ ScatterController
18
+ } from 'chart.js';
19
+ import { parseChartCsv, normalizeRemoteChartData, type RemoteChartData } from './chartRemoteData';
20
+
21
+ // Register Chart.js components
22
+ Chart.register(
23
+ CategoryScale,
24
+ LinearScale,
25
+ PointElement,
26
+ LineElement,
27
+ BarElement,
28
+ Title,
29
+ Tooltip,
30
+ Legend,
31
+ ArcElement,
32
+ LineController,
33
+ BarController,
34
+ PieController,
35
+ ScatterController
36
+ );
37
+
38
+ interface Dataset {
39
+ label: string;
40
+ data: number[];
41
+ }
42
+
43
+ interface Props {
44
+ type: "chart-line" | "chart-bar" | "chart-pie" | "chart-scatter";
45
+ title?: string;
46
+ height?: number;
47
+ /** 远端数据源 URL:设置后运行时 fetch 拉取(CSV 文本或 JSON),本地 CSV 作为初始/后备数据 */
48
+ url?: string;
49
+ labels: string[];
50
+ datasets: Dataset[];
51
+ options?: Record<string, any>;
52
+ }
53
+
54
+ const props = withDefaults(defineProps<Props>(), {
55
+ height: 400,
56
+ url: '',
57
+ options: () => ({})
58
+ });
59
+
60
+ const emit = defineEmits<{
61
+ change: [data: any]
62
+ }>();
63
+
64
+ // 远端数据:拉取成功后覆盖本地 CSV 数据
65
+ // 加载失败静默处理:回落本地数据,均无数据时图表区域显示灰色"暂无数据"
66
+ const remoteData = ref<RemoteChartData | null>(null);
67
+ const loading = ref(false);
68
+
69
+ // 实际渲染数据:远端优先,本地 CSV 作为初始/后备
70
+ const effectiveLabels = computed(() => remoteData.value ? remoteData.value.labels : props.labels);
71
+ const effectiveDatasets = computed(() => remoteData.value ? remoteData.value.datasets : props.datasets);
72
+ /** 无任何可渲染数据(本地与远端均为空) */
73
+ const isEmpty = computed(() =>
74
+ !loading.value && effectiveLabels.value.length === 0 && effectiveDatasets.value.length === 0
75
+ );
76
+
77
+ // Clamp height to safe bounds: 200px min, 800px max, 400px default
78
+ const safeHeight = Math.max(200, Math.min(800, props.height || 400));
79
+
80
+ const canvasRef = ref<HTMLCanvasElement>();
81
+ let chart: Chart | null = null;
82
+
83
+ const getChartType = (widgetType: string) => {
84
+ switch (widgetType) {
85
+ case 'chart-line': return 'line';
86
+ case 'chart-bar': return 'bar';
87
+ case 'chart-pie': return 'pie';
88
+ case 'chart-scatter': return 'scatter';
89
+ default: return 'line';
90
+ }
91
+ };
92
+
93
+ const getDefaultColors = () => [
94
+ '#1677ff',
95
+ '#52c41a',
96
+ '#faad14',
97
+ '#ff4d4f',
98
+ '#722ed1',
99
+ '#13c2c2'
100
+ ];
101
+
102
+ const prepareDatasets = (datasets: Dataset[], chartType: string, chartLabels: string[]) => {
103
+ const colors = getDefaultColors();
104
+
105
+ return datasets.map((dataset, index) => {
106
+ const color = colors[index % colors.length];
107
+ let processedData: any = dataset.data;
108
+
109
+ // For scatter plots, convert data to {x, y} format
110
+ if (chartType === 'scatter') {
111
+ processedData = dataset.data.map((value, dataIndex) => ({
112
+ x: dataIndex,
113
+ y: value
114
+ }));
115
+ }
116
+
117
+ if (chartType === 'pie') {
118
+ return {
119
+ ...dataset,
120
+ data: processedData,
121
+ backgroundColor: colors.slice(0, dataset.data.length),
122
+ borderColor: colors.slice(0, dataset.data.length),
123
+ borderWidth: 1
124
+ };
125
+ } else {
126
+ return {
127
+ ...dataset,
128
+ data: processedData,
129
+ borderColor: color,
130
+ backgroundColor: chartType === 'line' ? color + '20' : color,
131
+ borderWidth: 2,
132
+ fill: chartType === 'line' ? false : true
133
+ };
134
+ }
135
+ });
136
+ };
137
+
138
+ // 构建图表(销毁旧实例后重建)
139
+ const renderChart = () => {
140
+ if (!canvasRef.value) return;
141
+ if (chart) {
142
+ chart.destroy();
143
+ chart = null;
144
+ }
145
+
146
+ const chartType = getChartType(props.type);
147
+ const preparedDatasets = prepareDatasets(effectiveDatasets.value, chartType, effectiveLabels.value);
148
+
149
+ const defaultOptions = {
150
+ responsive: true,
151
+ maintainAspectRatio: false,
152
+ plugins: {
153
+ title: {
154
+ display: !!props.title,
155
+ text: props.title || ''
156
+ },
157
+ legend: {
158
+ display: effectiveDatasets.value.length > 1 || chartType === 'pie'
159
+ }
160
+ },
161
+ onClick: (_event: any, elements: any[]) => {
162
+ if (elements.length > 0) {
163
+ const element = elements[0];
164
+ const dataIndex = element.index;
165
+ const datasetIndex = element.datasetIndex;
166
+
167
+ emit('change', {
168
+ label: effectiveLabels.value[dataIndex],
169
+ value: effectiveDatasets.value[datasetIndex]?.data[dataIndex],
170
+ datasetLabel: effectiveDatasets.value[datasetIndex]?.label,
171
+ dataIndex,
172
+ datasetIndex
173
+ });
174
+ }
175
+ }
176
+ };
177
+
178
+ const mergedOptions = { ...defaultOptions, ...props.options };
179
+
180
+ const chartData = chartType === 'scatter'
181
+ ? { datasets: preparedDatasets }
182
+ : { labels: effectiveLabels.value, datasets: preparedDatasets };
183
+
184
+ chart = new Chart(canvasRef.value, {
185
+ type: chartType as any,
186
+ data: chartData,
187
+ options: mergedOptions
188
+ });
189
+ };
190
+
191
+ // 远端数据源:fetch 拉取 CSV 文本或 JSON,成功后覆盖本地数据并触发重建
192
+ watch(() => props.url, (url) => {
193
+ if (!url) {
194
+ remoteData.value = null;
195
+ return;
196
+ }
197
+ let cancelled = false;
198
+ loading.value = true;
199
+ (async () => {
200
+ try {
201
+ const res = await fetch(url);
202
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
203
+ const text = await res.text();
204
+ if (cancelled) return;
205
+ let parsed: RemoteChartData | null = null;
206
+ const trimmed = text.trim();
207
+ if (trimmed.startsWith('{') || trimmed.startsWith('[')) {
208
+ try {
209
+ parsed = normalizeRemoteChartData(JSON.parse(trimmed));
210
+ } catch {
211
+ parsed = null;
212
+ }
213
+ }
214
+ if (!parsed) parsed = parseChartCsv(text);
215
+ if (cancelled) return;
216
+ if (!parsed) throw new Error('响应无法解析为图表数据(CSV 或 JSON)');
217
+ remoteData.value = parsed;
218
+ } catch {
219
+ // 加载失败:静默回落本地数据
220
+ } finally {
221
+ if (!cancelled) loading.value = false;
222
+ }
223
+ })();
224
+ }, { immediate: true });
225
+
226
+ // 数据(远端覆盖本地)变化时重建图表
227
+ watch([effectiveLabels, effectiveDatasets], () => {
228
+ renderChart();
229
+ });
230
+
231
+ onMounted(() => {
232
+ renderChart();
233
+ });
234
+
235
+ onUnmounted(() => {
236
+ if (chart) {
237
+ chart.destroy();
238
+ chart = null;
239
+ }
240
+ });
241
+ </script>
242
+
243
+ <template>
244
+ <div class="widget-chart" :style="{ height: `${safeHeight}px` }">
245
+ <div v-if="isEmpty" class="chart-empty">暂无数据</div>
246
+ <div v-if="loading" class="chart-loading">数据加载中...</div>
247
+ <canvas ref="canvasRef"></canvas>
248
+ </div>
249
+ </template>
250
+
251
+ <style scoped>
252
+ .widget-chart {
253
+ position: relative;
254
+ width: 100%;
255
+ }
256
+
257
+ canvas {
258
+ max-width: 100%;
259
+ height: 100%;
260
+ }
261
+
262
+ .chart-empty {
263
+ position: absolute;
264
+ top: 0;
265
+ left: 0;
266
+ right: 0;
267
+ bottom: 0;
268
+ z-index: 1;
269
+ display: flex;
270
+ align-items: center;
271
+ justify-content: center;
272
+ color: rgba(0, 0, 0, 0.45);
273
+ font-size: 13px;
274
+ background: #ffffff;
275
+ }
276
+
277
+ .chart-loading {
278
+ position: absolute;
279
+ top: 8px;
280
+ right: 8px;
281
+ z-index: 1;
282
+ padding: 2px 8px;
283
+ border-radius: 4px;
284
+ background: rgba(0, 0, 0, 0.05);
285
+ font-size: 12px;
286
+ }
287
+ </style>