@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.
- package/README.md +112 -2
- package/dist/MarkdownUI.vue.d.ts +15 -0
- package/dist/favicon.ico +0 -0
- package/dist/index.css +1 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.js +10843 -0
- package/dist/index.umd.cjs +53 -0
- package/dist/widgets/Button.vue.d.ts +19 -0
- package/dist/widgets/ButtonGroup.vue.d.ts +13 -0
- package/dist/widgets/Cascader.vue.d.ts +48 -0
- package/dist/widgets/Chart.vue.d.ts +24 -0
- package/dist/widgets/DatePicker.vue.d.ts +42 -0
- package/dist/widgets/Form.vue.d.ts +59 -0
- package/dist/widgets/Incomplete.vue.d.ts +2 -0
- package/dist/widgets/MultipleChoiceQuestion.vue.d.ts +20 -0
- package/dist/widgets/NumberInput.vue.d.ts +37 -0
- package/dist/widgets/Quiz.vue.d.ts +39 -0
- package/dist/widgets/RadioGroup.vue.d.ts +43 -0
- package/dist/widgets/Select.vue.d.ts +27 -0
- package/dist/widgets/SelectMulti.vue.d.ts +38 -0
- package/dist/widgets/ShortAnswerQuestion.vue.d.ts +22 -0
- package/dist/widgets/Slider.vue.d.ts +21 -0
- package/dist/widgets/Switch.vue.d.ts +28 -0
- package/dist/widgets/Text.vue.d.ts +22 -0
- package/dist/widgets/TextInput.vue.d.ts +32 -0
- package/dist/widgets/TreeSelect.vue.d.ts +109 -0
- package/dist/widgets/Widget.vue.d.ts +8 -0
- package/dist/widgets/chartRemoteData.d.ts +17 -0
- package/dist/widgets/index.d.ts +20 -0
- package/dist/widgets/remoteChoices.d.ts +22 -0
- package/dist/widgets/treeSelectTypes.d.ts +43 -0
- package/package.json +46 -4
- package/src/App.vue +11 -0
- package/src/lib/MarkdownUI.vue +105 -0
- package/src/lib/index.ts +28 -0
- package/src/lib/widgets/Button.vue +47 -0
- package/src/lib/widgets/ButtonGroup.vue +43 -0
- package/src/lib/widgets/Cascader.vue +475 -0
- package/src/lib/widgets/Chart.vue +287 -0
- package/src/lib/widgets/DatePicker.vue +659 -0
- package/src/lib/widgets/Form.vue +479 -0
- package/src/lib/widgets/Incomplete.vue +56 -0
- package/src/lib/widgets/MultipleChoiceQuestion.vue +73 -0
- package/src/lib/widgets/NumberInput.vue +179 -0
- package/src/lib/widgets/Quiz.vue +205 -0
- package/src/lib/widgets/RadioGroup.vue +237 -0
- package/src/lib/widgets/Select.vue +279 -0
- package/src/lib/widgets/SelectMulti.vue +145 -0
- package/src/lib/widgets/ShortAnswerQuestion.vue +79 -0
- package/src/lib/widgets/Slider.vue +61 -0
- package/src/lib/widgets/Switch.vue +68 -0
- package/src/lib/widgets/Text.vue +59 -0
- package/src/lib/widgets/TextInput.vue +83 -0
- package/src/lib/widgets/TreeSelect.vue +872 -0
- package/src/lib/widgets/Widget.vue +185 -0
- package/src/lib/widgets/chartRemoteData.ts +84 -0
- package/src/lib/widgets/index.ts +20 -0
- package/src/lib/widgets/remoteChoices.ts +64 -0
- package/src/lib/widgets/treeSelectTypes.ts +49 -0
- package/src/lib/widgets.css +1754 -0
- package/src/main.ts +4 -0
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="widget">
|
|
3
|
+
<component
|
|
4
|
+
v-if="WidgetComponent"
|
|
5
|
+
:is="WidgetComponent"
|
|
6
|
+
v-bind="parsed"
|
|
7
|
+
:initial-data="initialData"
|
|
8
|
+
@change="(value) => dispatch({ id: props.id, value })"
|
|
9
|
+
@submit="(value) => dispatchSubmit(props.id, value)"
|
|
10
|
+
@fieldchange="(stats) => dispatchFieldChange(props.id, stats)"
|
|
11
|
+
/>
|
|
12
|
+
<span v-else style="color: red">
|
|
13
|
+
Unknown widget "{{ parsed.type }}"
|
|
14
|
+
</span>
|
|
15
|
+
</div>
|
|
16
|
+
</template>
|
|
17
|
+
|
|
18
|
+
<script setup lang="ts">
|
|
19
|
+
import { computed, defineProps } from 'vue'
|
|
20
|
+
import Button from './Button.vue'
|
|
21
|
+
import ButtonGroup from './ButtonGroup.vue'
|
|
22
|
+
import Cascader from './Cascader.vue'
|
|
23
|
+
import TreeSelect from './TreeSelect.vue'
|
|
24
|
+
import Chart from './Chart.vue'
|
|
25
|
+
import Form from './Form.vue'
|
|
26
|
+
import Incomplete from './Incomplete.vue'
|
|
27
|
+
import Select from './Select.vue'
|
|
28
|
+
import SelectMulti from './SelectMulti.vue'
|
|
29
|
+
import Slider from './Slider.vue'
|
|
30
|
+
import Switch from './Switch.vue'
|
|
31
|
+
import TextInput from './TextInput.vue'
|
|
32
|
+
import MultipleChoiceQuestion from './MultipleChoiceQuestion.vue'
|
|
33
|
+
import ShortAnswerQuestion from './ShortAnswerQuestion.vue'
|
|
34
|
+
import Quiz from './Quiz.vue'
|
|
35
|
+
import RadioGroup from './RadioGroup.vue'
|
|
36
|
+
import Text from './Text.vue'
|
|
37
|
+
import NumberInput from './NumberInput.vue'
|
|
38
|
+
import DatePicker from './DatePicker.vue'
|
|
39
|
+
|
|
40
|
+
interface WidgetProps {
|
|
41
|
+
id: string
|
|
42
|
+
content: string
|
|
43
|
+
/** 外部传入的表单初始值(encodeURIComponent 后的 JSON 字符串),由 MarkdownUI 注入 */
|
|
44
|
+
data?: string
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const props = defineProps<WidgetProps>()
|
|
48
|
+
|
|
49
|
+
const widgets = {
|
|
50
|
+
'Button': Button,
|
|
51
|
+
'ButtonGroup': ButtonGroup,
|
|
52
|
+
'Cascader': Cascader,
|
|
53
|
+
'TreeSelect': TreeSelect,
|
|
54
|
+
'Chart': Chart,
|
|
55
|
+
'Form': Form,
|
|
56
|
+
'Incomplete': Incomplete,
|
|
57
|
+
// Support both PascalCase and DSL type strings for robustness
|
|
58
|
+
'MultipleChoiceQuestion': MultipleChoiceQuestion,
|
|
59
|
+
'multiple-choice-question': MultipleChoiceQuestion,
|
|
60
|
+
'Select': Select,
|
|
61
|
+
'SelectMulti': SelectMulti,
|
|
62
|
+
'ShortAnswerQuestion': ShortAnswerQuestion,
|
|
63
|
+
'short-answer-question': ShortAnswerQuestion,
|
|
64
|
+
'Slider': Slider,
|
|
65
|
+
'Switch': Switch,
|
|
66
|
+
'TextInput': TextInput,
|
|
67
|
+
'Quiz': Quiz,
|
|
68
|
+
'quiz': Quiz,
|
|
69
|
+
'RadioGroup': RadioGroup,
|
|
70
|
+
'radio-group': RadioGroup,
|
|
71
|
+
'Text': Text,
|
|
72
|
+
'text': Text,
|
|
73
|
+
'NumberInput': NumberInput,
|
|
74
|
+
'number-input': NumberInput,
|
|
75
|
+
'DatePicker': DatePicker,
|
|
76
|
+
'date-picker': DatePicker
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const typeMapping: Record<string, keyof typeof widgets> = {
|
|
80
|
+
'button': 'Button',
|
|
81
|
+
'button-group': 'ButtonGroup',
|
|
82
|
+
'cascade': 'Cascader',
|
|
83
|
+
'tree-select': 'TreeSelect',
|
|
84
|
+
'chart-line': 'Chart',
|
|
85
|
+
'chart-bar': 'Chart',
|
|
86
|
+
'chart-pie': 'Chart',
|
|
87
|
+
'chart-scatter': 'Chart',
|
|
88
|
+
'form': 'Form',
|
|
89
|
+
'incomplete': 'Incomplete',
|
|
90
|
+
'multiple-choice-question': 'MultipleChoiceQuestion',
|
|
91
|
+
'quiz': 'Quiz',
|
|
92
|
+
'radio-group': 'RadioGroup',
|
|
93
|
+
'select': 'Select',
|
|
94
|
+
'select-multi': 'SelectMulti',
|
|
95
|
+
'short-answer-question': 'ShortAnswerQuestion',
|
|
96
|
+
'slider': 'Slider',
|
|
97
|
+
'switch': 'Switch',
|
|
98
|
+
'text': 'Text',
|
|
99
|
+
'text-input': 'TextInput',
|
|
100
|
+
'number-input': 'NumberInput',
|
|
101
|
+
'date-picker': 'DatePicker'
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
let parsed = { type: 'incomplete' }
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
parsed = JSON.parse(decodeURIComponent(props.content))
|
|
108
|
+
} catch (e) {
|
|
109
|
+
// suppress intermediate states
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// 解析外部注入的表单初始值(容错:非法 JSON 时忽略)
|
|
113
|
+
const initialData = computed(() => {
|
|
114
|
+
if (!props.data) return undefined
|
|
115
|
+
try {
|
|
116
|
+
return JSON.parse(decodeURIComponent(props.data))
|
|
117
|
+
} catch {
|
|
118
|
+
return undefined
|
|
119
|
+
}
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
const WidgetComponent = computed(() => {
|
|
123
|
+
const rawType = parsed.type as string
|
|
124
|
+
const mapped = typeMapping[rawType] || rawType
|
|
125
|
+
let component = widgets[mapped as keyof typeof widgets]
|
|
126
|
+
|
|
127
|
+
// Fallback: try PascalCase from hyphen/underscore types
|
|
128
|
+
if (!component && typeof mapped === 'string') {
|
|
129
|
+
const pascal = mapped
|
|
130
|
+
.split(/[-_]/)
|
|
131
|
+
.map(s => s.charAt(0).toUpperCase() + s.slice(1))
|
|
132
|
+
.join('')
|
|
133
|
+
component = widgets[pascal as keyof typeof widgets]
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return component
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
const dispatch = (detail: any) => {
|
|
140
|
+
const event = new CustomEvent('widget-event', {
|
|
141
|
+
detail: detail,
|
|
142
|
+
bubbles: true,
|
|
143
|
+
composed: true
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
// Find the custom element and bubble up to find the closest widget container
|
|
147
|
+
const customElement = document.querySelector(`markdown-ui-widget[id="${props.id}"]`)
|
|
148
|
+
if (customElement) {
|
|
149
|
+
const container = customElement.closest('.widget-container')
|
|
150
|
+
if (container) {
|
|
151
|
+
container.dispatchEvent(event)
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** form 提交事件:独立于 widget-event,通过 widget-submit 派发 */
|
|
157
|
+
const dispatchSubmit = (id: string, value: unknown) => {
|
|
158
|
+
const customElement = document.querySelector(`markdown-ui-widget[id="${id}"]`)
|
|
159
|
+
if (customElement) {
|
|
160
|
+
const container = customElement.closest('.widget-container')
|
|
161
|
+
if (container) {
|
|
162
|
+
container.dispatchEvent(new CustomEvent('widget-submit', {
|
|
163
|
+
detail: { id, value },
|
|
164
|
+
bubbles: true,
|
|
165
|
+
composed: true
|
|
166
|
+
}))
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** form 字段级统计事件:任一表单项被填写时通过 widget-form-change 派发 */
|
|
172
|
+
const dispatchFieldChange = (id: string, stats: unknown) => {
|
|
173
|
+
const customElement = document.querySelector(`markdown-ui-widget[id="${id}"]`)
|
|
174
|
+
if (customElement) {
|
|
175
|
+
const container = customElement.closest('.widget-container')
|
|
176
|
+
if (container) {
|
|
177
|
+
container.dispatchEvent(new CustomEvent('widget-form-change', {
|
|
178
|
+
detail: { id, stats },
|
|
179
|
+
bubbles: true,
|
|
180
|
+
composed: true
|
|
181
|
+
}))
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
</script>
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 图表远端数据源解析:
|
|
3
|
+
* - CSV 文本(与 DSL 内嵌 CSV 同格式:首行 header「标签列,系列1,系列2...」,其后数据行)
|
|
4
|
+
* - JSON:支持 { labels, datasets:[{label,data}] } 及常见包装({data|result} 内嵌)
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export interface ChartDataset {
|
|
8
|
+
label: string;
|
|
9
|
+
data: number[];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface RemoteChartData {
|
|
13
|
+
labels: string[];
|
|
14
|
+
datasets: ChartDataset[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** 解析 CSV 文本为图表数据(header 至少 2 列,数据列须为数字) */
|
|
18
|
+
export function parseChartCsv(text: string): RemoteChartData | null {
|
|
19
|
+
const lines = text
|
|
20
|
+
.split('\n')
|
|
21
|
+
.map(l => l.trim())
|
|
22
|
+
.filter(l => l.length > 0);
|
|
23
|
+
if (lines.length === 0) return null;
|
|
24
|
+
|
|
25
|
+
const headers = lines[0].split(',').map(h => h.trim());
|
|
26
|
+
if (headers.length < 2) return null;
|
|
27
|
+
|
|
28
|
+
const labels: string[] = [];
|
|
29
|
+
const datasets: ChartDataset[] = headers.slice(1).map(label => ({ label, data: [] }));
|
|
30
|
+
|
|
31
|
+
for (let i = 1; i < lines.length; i++) {
|
|
32
|
+
const values = lines[i].split(',').map(v => v.trim());
|
|
33
|
+
if (values.length !== headers.length) return null;
|
|
34
|
+
labels.push(values[0]);
|
|
35
|
+
for (let j = 1; j < values.length; j++) {
|
|
36
|
+
const num = parseFloat(values[j]);
|
|
37
|
+
if (isNaN(num)) return null;
|
|
38
|
+
datasets[j - 1].data.push(num);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return { labels, datasets };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** 归一化 JSON 响应为图表数据(labels + datasets) */
|
|
45
|
+
export function normalizeRemoteChartData(json: any): RemoteChartData | null {
|
|
46
|
+
// 解开常见包装:{data|result|chart} 内嵌
|
|
47
|
+
let payload = json;
|
|
48
|
+
for (const key of ['data', 'result', 'chart']) {
|
|
49
|
+
if (payload && typeof payload === 'object' && !Array.isArray(payload)
|
|
50
|
+
&& payload[key] && typeof payload[key] === 'object') {
|
|
51
|
+
payload = payload[key];
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (!payload || typeof payload !== 'object') return null;
|
|
56
|
+
|
|
57
|
+
// 直接是 { labels, datasets } 结构
|
|
58
|
+
let labels: any = payload.labels;
|
|
59
|
+
let datasets: any = payload.datasets;
|
|
60
|
+
|
|
61
|
+
// 数组形式:[{ label, value }] → 单系列
|
|
62
|
+
if (Array.isArray(payload) && payload.length > 0 && payload[0]?.label !== undefined && payload[0]?.value !== undefined) {
|
|
63
|
+
labels = payload.map((it: any) => it.label);
|
|
64
|
+
datasets = [{ label: 'Series', data: payload.map((it: any) => Number(it.value)) }];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (!Array.isArray(labels) || !Array.isArray(datasets) || labels.length === 0 || datasets.length === 0) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const normLabels = labels.map((l: any) => String(l));
|
|
72
|
+
const normDatasets: ChartDataset[] = [];
|
|
73
|
+
for (const ds of datasets) {
|
|
74
|
+
if (!ds || !Array.isArray(ds.data)) continue;
|
|
75
|
+
const data = ds.data.map((v: any) => {
|
|
76
|
+
const num = Number(v);
|
|
77
|
+
return isNaN(num) ? 0 : num;
|
|
78
|
+
});
|
|
79
|
+
normDatasets.push({ label: String(ds.label ?? 'Series'), data });
|
|
80
|
+
}
|
|
81
|
+
if (normDatasets.length === 0) return null;
|
|
82
|
+
|
|
83
|
+
return { labels: normLabels, datasets: normDatasets };
|
|
84
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export { default as Button } from './Button.vue'
|
|
2
|
+
export { default as ButtonGroup } from './ButtonGroup.vue'
|
|
3
|
+
export { default as Cascader } from './Cascader.vue'
|
|
4
|
+
export { default as Chart } from './Chart.vue'
|
|
5
|
+
export { default as Form } from './Form.vue'
|
|
6
|
+
export { default as Incomplete } from './Incomplete.vue'
|
|
7
|
+
export { default as MultipleChoiceQuestion } from './MultipleChoiceQuestion.vue'
|
|
8
|
+
export { default as Select } from './Select.vue'
|
|
9
|
+
export { default as SelectMulti } from './SelectMulti.vue'
|
|
10
|
+
export { default as TreeSelect } from './TreeSelect.vue'
|
|
11
|
+
export { default as ShortAnswerQuestion } from './ShortAnswerQuestion.vue'
|
|
12
|
+
export { default as Slider } from './Slider.vue'
|
|
13
|
+
export { default as Switch } from './Switch.vue'
|
|
14
|
+
export { default as TextInput } from './TextInput.vue'
|
|
15
|
+
export { default as NumberInput } from './NumberInput.vue'
|
|
16
|
+
export { default as DatePicker } from './DatePicker.vue'
|
|
17
|
+
export { default as Text } from './Text.vue'
|
|
18
|
+
export { default as Quiz } from './Quiz.vue'
|
|
19
|
+
export { default as RadioGroup } from './RadioGroup.vue'
|
|
20
|
+
export { default as Widget } from './Widget.vue'
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/** 远端选项:value 是表单实际取值,label 是展示文案 */
|
|
2
|
+
export interface RemoteChoice {
|
|
3
|
+
value: string;
|
|
4
|
+
label: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 兼容常见远端响应格式并归一化为 {value,label}:
|
|
9
|
+
* - ["测试"] → value=label="测试"
|
|
10
|
+
* - [{key:"test",label:"测试"}] → value="test"(表单取值),label="测试"(展示)
|
|
11
|
+
* - [{value:"test",label:"测试"}] → value="test",label="测试"
|
|
12
|
+
* - {data:[...]} / {options:[...]} / {choices:[...]} / {items:[...]} → 取内嵌数组
|
|
13
|
+
*
|
|
14
|
+
* 对象条目取值优先级:value > key > id > name;展示优先级:label > name > text > value。
|
|
15
|
+
*/
|
|
16
|
+
export function normalizeRemoteChoices(json: any): RemoteChoice[] {
|
|
17
|
+
let list = json;
|
|
18
|
+
if (!Array.isArray(json)) {
|
|
19
|
+
list = Array.isArray(json?.data) ? json.data
|
|
20
|
+
: Array.isArray(json?.options) ? json.options
|
|
21
|
+
: Array.isArray(json?.choices) ? json.choices
|
|
22
|
+
: Array.isArray(json?.items) ? json.items
|
|
23
|
+
: [];
|
|
24
|
+
}
|
|
25
|
+
return list
|
|
26
|
+
.map((item: any): RemoteChoice | null => {
|
|
27
|
+
if (typeof item === 'string' || typeof item === 'number') {
|
|
28
|
+
const s = String(item);
|
|
29
|
+
return s ? { value: s, label: s } : null;
|
|
30
|
+
}
|
|
31
|
+
if (item && typeof item === 'object') {
|
|
32
|
+
const value = item.value ?? item.key ?? item.id ?? item.name;
|
|
33
|
+
if (value === undefined || value === null || value === '') return null;
|
|
34
|
+
const label = item.label ?? item.name ?? item.text ?? String(value);
|
|
35
|
+
return { value: String(value), label: String(label) };
|
|
36
|
+
}
|
|
37
|
+
return null;
|
|
38
|
+
})
|
|
39
|
+
.filter((c: RemoteChoice | null): c is RemoteChoice => c !== null);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** 解析本地 choices 条目的 key:label 格式:"bj:北京" → {value:"bj",label:"北京"};不符规则时 value=label=原文 */
|
|
43
|
+
export function parseChoice(c: string): RemoteChoice {
|
|
44
|
+
const s = String(c ?? '');
|
|
45
|
+
const idx = s.indexOf(':');
|
|
46
|
+
if (idx > 0) {
|
|
47
|
+
const key = s.slice(0, idx);
|
|
48
|
+
const label = s.slice(idx + 1);
|
|
49
|
+
if (key && label && !/\s/.test(key) && !/^\d+$/.test(key) && !label.startsWith('//')) {
|
|
50
|
+
return { value: key, label };
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return { value: s, label: s };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* 把本地 choices / default 值(可能写的是展示文案或 key:label 原文)映射到远端 value 体系:
|
|
58
|
+
* 条目本身已是 value 优先,字符串则用 label 匹配远端条目回查 value。
|
|
59
|
+
*/
|
|
60
|
+
export function resolveChoiceValue(c: string, remote: RemoteChoice[]): string {
|
|
61
|
+
const parsed = parseChoice(c).value;
|
|
62
|
+
const hit = remote.find(r => r.value === c || r.label === c || r.value === parsed);
|
|
63
|
+
return hit ? hit.value : c;
|
|
64
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// 树形选择组件共享类型:
|
|
2
|
+
// 单独成文件是因为 TreeSelect.vue 的 <script setup>(主组件)与普通 <script>(TreeNode 子组件)
|
|
3
|
+
// 是两个独立作用域,无法共享局部 interface;放此处两边统一 import,避免与 DOM 全局 Node 类型冲突
|
|
4
|
+
|
|
5
|
+
/** 远端/静态 data 原始节点(容错:value/key、label/name 等字段映射) */
|
|
6
|
+
export interface TreeNodeRaw {
|
|
7
|
+
value?: string | number
|
|
8
|
+
key?: string | number
|
|
9
|
+
label?: string | number
|
|
10
|
+
children?: TreeNodeRaw[]
|
|
11
|
+
loaded?: boolean
|
|
12
|
+
[k: string]: any
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** 内部归一化树节点 */
|
|
16
|
+
export interface TreeSelectNode {
|
|
17
|
+
value: string
|
|
18
|
+
label: string
|
|
19
|
+
children: TreeSelectNode[]
|
|
20
|
+
loaded: boolean
|
|
21
|
+
depth: number
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** TreeSelect 组件 props */
|
|
25
|
+
export interface TreeSelectProps {
|
|
26
|
+
choices: string[]
|
|
27
|
+
data?: TreeNodeRaw[]
|
|
28
|
+
url?: string
|
|
29
|
+
lazy?: boolean
|
|
30
|
+
/** 多选模式(antd 默认单选,multiple 开启多选) */
|
|
31
|
+
multiple?: boolean
|
|
32
|
+
/** 父子节点选中不关联(antd checkStrictly) */
|
|
33
|
+
checkStrictly?: boolean
|
|
34
|
+
/** 选中值回显策略:all / parent / child(antd SHOW_ALL/SHOW_PARENT/SHOW_CHILD) */
|
|
35
|
+
showCheckedStrategy?: string
|
|
36
|
+
/** 触发器最多显示的标签数,超出 +N(antd maxTagCount) */
|
|
37
|
+
maxTagCount?: number
|
|
38
|
+
/** 树连接线(antd treeLine) */
|
|
39
|
+
treeLine?: boolean
|
|
40
|
+
label?: string
|
|
41
|
+
default?: string | string[]
|
|
42
|
+
placeholder?: string
|
|
43
|
+
hint?: string
|
|
44
|
+
required?: boolean
|
|
45
|
+
message?: string
|
|
46
|
+
error?: string
|
|
47
|
+
disabled?: boolean
|
|
48
|
+
searchable?: boolean
|
|
49
|
+
}
|