@scorpioz/suna-components 0.0.2

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 (38) hide show
  1. package/README.md +52 -0
  2. package/button/index.ts +3 -0
  3. package/button/src/Button.vue +42 -0
  4. package/button/src/props.ts +24 -0
  5. package/button/src/types.ts +26 -0
  6. package/confirm-button/index.ts +2 -0
  7. package/confirm-button/src/ConfirmButton.vue +52 -0
  8. package/confirm-button/src/props.ts +50 -0
  9. package/debounce-button/index.ts +2 -0
  10. package/debounce-button/src/DebounceButton.vue +65 -0
  11. package/debounce-button/src/props.ts +15 -0
  12. package/drawer/index.ts +3 -0
  13. package/drawer/src/Drawer.vue +120 -0
  14. package/drawer/src/props.ts +76 -0
  15. package/drawer/src/types.ts +8 -0
  16. package/drawer-form/index.ts +2 -0
  17. package/drawer-form/src/DrawerForm.vue +164 -0
  18. package/drawer-form/src/props.ts +74 -0
  19. package/form/index.ts +3 -0
  20. package/form/src/Form.vue +183 -0
  21. package/form/src/form-items/FCascader.vue +25 -0
  22. package/form/src/form-items/FCheckboxGroup.vue +33 -0
  23. package/form/src/form-items/FDatePicker.vue +24 -0
  24. package/form/src/form-items/FInput.vue +26 -0
  25. package/form/src/form-items/FInputNumber.vue +26 -0
  26. package/form/src/form-items/FRadioGroup.vue +33 -0
  27. package/form/src/form-items/FRate.vue +24 -0
  28. package/form/src/form-items/FSelect.vue +34 -0
  29. package/form/src/form-items/FSwitch.vue +24 -0
  30. package/form/src/form-items/FText.vue +16 -0
  31. package/form/src/form-items/FTextarea.vue +27 -0
  32. package/form/src/path.ts +39 -0
  33. package/form/src/props.ts +73 -0
  34. package/form/src/types.ts +168 -0
  35. package/form/src/useFormField.ts +137 -0
  36. package/form/src/useFormItems.ts +85 -0
  37. package/index.ts +82 -0
  38. package/package.json +50 -0
@@ -0,0 +1,164 @@
1
+ <script setup lang="ts">
2
+ import { ref, watch } from "vue";
3
+ import SDrawer from "../../drawer/src/Drawer.vue";
4
+ import SForm from "../../form/src/Form.vue";
5
+ import SunaButton from "../../button/src/Button.vue";
6
+ import SunaDebounceButton from "../../debounce-button/src/DebounceButton.vue";
7
+ import { drawerFormProps } from "./props";
8
+
9
+ defineOptions({ name: "SunaDrawerForm" });
10
+
11
+ // ---- v-model:visible(抽屉显隐) + 数据(表单默认值 / 确认后同步) ----
12
+ const visible = defineModel<boolean>("visible", { default: false });
13
+ const formData = defineModel<Record<string, any>>({ default: () => ({}) });
14
+
15
+ // ---- 其余 props ----
16
+ const props = defineProps(drawerFormProps);
17
+ const emit = defineEmits<{
18
+ /** 提交且校验通过时触发 */
19
+ confirm: [data: Record<string, any>];
20
+ cancel: [];
21
+ open: [];
22
+ opened: [];
23
+ close: [];
24
+ closed: [];
25
+ }>();
26
+
27
+ // ---- 内部表单数据(支持「取消即撤销」) ----
28
+ const internalData = ref<Record<string, any>>({});
29
+
30
+ watch(visible, (opening) => {
31
+ if (opening) {
32
+ if (props.cloneOnOpen) {
33
+ // 深层克隆:cancel 不会污染外部数据
34
+ internalData.value = JSON.parse(JSON.stringify(formData.value));
35
+ } else {
36
+ internalData.value = formData.value;
37
+ }
38
+ emit("open");
39
+ } else {
40
+ emit("close");
41
+ }
42
+ });
43
+
44
+ function onOpened() {
45
+ emit("opened");
46
+ }
47
+ function onClosed() {
48
+ emit("closed");
49
+ }
50
+
51
+ // ---- 表单 ref ----
52
+ const formRef = ref<InstanceType<typeof SForm> | null>(null);
53
+
54
+ function onReset() {
55
+ formRef.value?.resetFields();
56
+ }
57
+
58
+ function onCancel() {
59
+ visible.value = false;
60
+ emit("cancel");
61
+ }
62
+
63
+ async function onSubmit() {
64
+ try {
65
+ await formRef.value?.validate();
66
+ // 确认时同步数据到父组件
67
+ if (props.cloneOnOpen) {
68
+ formData.value = JSON.parse(JSON.stringify(internalData.value));
69
+ }
70
+ visible.value = false;
71
+ emit("confirm", formData.value);
72
+ emit("closed");
73
+ } catch {
74
+ /* 校验未通过,el-form 会显示错误,无需额外处理 */
75
+ }
76
+ }
77
+
78
+ defineExpose({
79
+ formRef,
80
+ validate: () => formRef.value?.validate(),
81
+ resetFields: () => formRef.value?.resetFields(),
82
+ open: () => { visible.value = true; },
83
+ close: () => { visible.value = false; },
84
+ });
85
+ </script>
86
+
87
+ <template>
88
+ <SDrawer
89
+ :model-value="visible"
90
+ :title="title"
91
+ :direction="direction"
92
+ :size="size"
93
+ :with-footer="false"
94
+ v-bind="drawerConfig"
95
+ @update:model-value="visible = $event"
96
+ @opened="onOpened"
97
+ @closed="onClosed"
98
+ >
99
+ <SForm
100
+ ref="formRef"
101
+ v-model="internalData"
102
+ :form-items="formItems"
103
+ :label-width="labelWidth"
104
+ :label-position="labelPosition"
105
+ :gutter="gutter"
106
+ :default-span="defaultSpan"
107
+ :show-type="showType"
108
+ :with-actions="false"
109
+ v-bind="formConfig"
110
+ >
111
+ <template v-for="(_, name) in $slots" #[name]="scope">
112
+ <slot :name="name" v-bind="scope" />
113
+ </template>
114
+ </SForm>
115
+
116
+ <!-- 内置底部操作栏(位于 Drawer footer 区域) -->
117
+ <template #footer>
118
+ <slot name="footer">
119
+ <div
120
+ v-if="withActions"
121
+ class="suna-drawer-form__actions"
122
+ :class="`is-${actionAlign}`"
123
+ >
124
+ <SunaButton
125
+ v-if="showReset"
126
+ :type="resetButtonType"
127
+ @click="onReset"
128
+ >{{ resetText }}</SunaButton>
129
+ <SunaButton
130
+ v-if="showCancel"
131
+ :type="cancelButtonType"
132
+ @click="onCancel"
133
+ >{{ cancelText }}</SunaButton>
134
+ <SunaDebounceButton
135
+ v-if="showSubmit"
136
+ :type="submitButtonType"
137
+ :loading="submitLoading"
138
+ :wait="submitWait"
139
+ :leading="submitLeading"
140
+ @click="onSubmit"
141
+ >{{ submitText }}</SunaDebounceButton>
142
+ </div>
143
+ </slot>
144
+ </template>
145
+ </SDrawer>
146
+ </template>
147
+
148
+ <style scoped>
149
+ .suna-drawer-form__actions {
150
+ display: flex;
151
+ align-items: center;
152
+ gap: 12px;
153
+ width: 100%;
154
+ }
155
+ .suna-drawer-form__actions.is-left {
156
+ justify-content: flex-start;
157
+ }
158
+ .suna-drawer-form__actions.is-center {
159
+ justify-content: center;
160
+ }
161
+ .suna-drawer-form__actions.is-right {
162
+ justify-content: flex-end;
163
+ }
164
+ </style>
@@ -0,0 +1,74 @@
1
+ import type { PropType } from "vue";
2
+ import type { ButtonType } from "../../button/src/types";
3
+ import type { DrawerDirection, DrawerSize } from "../../drawer/src/types";
4
+ import type { FormItem } from "../../form/src/types";
5
+
6
+ /**
7
+ * DrawerForm 的运行时 props:汇集 SunaDrawer 和 SunaForm 最常用的属性,
8
+ * 其余高级配置通过 drawerConfig / formConfig 传入。
9
+ */
10
+ export const drawerFormProps = {
11
+ // ---- 抽屉控制 ----
12
+ /** 抽屉标题 */
13
+ title: { type: String, default: "" },
14
+ /** 滑出方向 */
15
+ direction: { type: String as PropType<DrawerDirection>, default: "rtl" },
16
+ /** 抽屉尺寸 */
17
+ size: { type: [String, Number] as PropType<DrawerSize>, default: "30%" },
18
+ /** 其余 ElDrawer 属性透传(before-close / z-index / append-to 等) */
19
+ drawerConfig: { type: Object, default: () => ({}) },
20
+
21
+ // ---- 表单控制 ----
22
+ /** 字段配置数组 */
23
+ formItems: {
24
+ type: Array as PropType<FormItem[]>,
25
+ required: true as const,
26
+ },
27
+ /** 标签宽度 */
28
+ labelWidth: { type: [String, Number], default: "100px" },
29
+ /** 标签对齐方式 */
30
+ labelPosition: {
31
+ type: String as PropType<"left" | "right" | "top">,
32
+ default: "right",
33
+ },
34
+ /** 栅格间隔(px) */
35
+ gutter: { type: Number, default: 12 },
36
+ /** 未指定 span 时的默认栅格占比 */
37
+ defaultSpan: { type: Number, default: 12 },
38
+ /** 渲染模式:normal(可编辑)/ text(只读) */
39
+ showType: {
40
+ type: String as PropType<"normal" | "text">,
41
+ default: "normal",
42
+ },
43
+ /** 其余 ElForm 属性透传(inline / disabled 等) */
44
+ formConfig: { type: Object, default: () => ({}) },
45
+
46
+ // ---- 底部操作栏 ----
47
+ /** 是否显示操作栏 */
48
+ withActions: { type: Boolean, default: true },
49
+ /** 按钮对齐方式 */
50
+ actionAlign: {
51
+ type: String as PropType<"left" | "center" | "right">,
52
+ default: "right",
53
+ },
54
+ showSubmit: { type: Boolean, default: true },
55
+ showReset: { type: Boolean, default: true },
56
+ showCancel: { type: Boolean, default: true },
57
+ submitText: { type: String, default: "提交" },
58
+ resetText: { type: String, default: "重置" },
59
+ cancelText: { type: String, default: "取消" },
60
+ submitButtonType: { type: String as PropType<ButtonType>, default: "primary" },
61
+ resetButtonType: { type: String as PropType<ButtonType>, default: "default" },
62
+ cancelButtonType: { type: String as PropType<ButtonType>, default: "default" },
63
+ submitLoading: { type: Boolean, default: false },
64
+ submitWait: { type: Number, default: 300 },
65
+ submitLeading: { type: Boolean, default: true },
66
+
67
+ // ---- DrawerForm 特有 ----
68
+ /**
69
+ * 每次打开抽屉时是否深层克隆 v-model 数据。
70
+ * true(默认)→ 编辑在内部副本上进行,「取消」不会污染外部数据;
71
+ * false → 直接绑定外部数据,编辑实时同步。
72
+ */
73
+ cloneOnOpen: { type: Boolean, default: true },
74
+ };
package/form/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ export { default as Form } from "./src/Form.vue";
2
+ export { default } from "./src/Form.vue";
3
+ export type * from "./src/types";
@@ -0,0 +1,183 @@
1
+ <script setup lang="ts">
2
+ import { ref, computed, provide } from "vue";
3
+ import type { FormInstance } from "element-plus";
4
+ import { ElForm, ElRow, ElCol, ElFormItem } from "element-plus";
5
+ import SunaButton from "../../button/src/Button.vue";
6
+ import SunaDebounceButton from "../../debounce-button/src/DebounceButton.vue";
7
+ import { formProps } from "./props";
8
+ import { useFormItems } from "./useFormItems";
9
+ import { getPath, setPath } from "./path";
10
+
11
+ defineOptions({ name: "SunaForm" });
12
+
13
+ // ---- v-model:整个表单数据对象(omit from formProps 以避免冲突) ----
14
+ const model = defineModel<Record<string, any>>({ default: () => ({}) });
15
+
16
+ // ---- 其余 props ----
17
+ const props = defineProps(formProps);
18
+ const emit = defineEmits(["update:modelValue", "submit", "reset", "cancel"]);
19
+
20
+ // ---- 注入详情模式到后代字段组件 ----
21
+ provide("suna:showType", computed(() => props.showType));
22
+
23
+ // ---- 字段映射 / 校验 / 版式 ----
24
+ const { getType, getRules, getSpan } = useFormItems(props.defaultSpan);
25
+
26
+ // ---- el-form 暴露 ----
27
+ const elFormRef = ref<FormInstance | null>(null);
28
+ defineExpose({
29
+ validate: () => elFormRef.value?.validate(),
30
+ resetFields: () => elFormRef.value?.resetFields(),
31
+ clearValidate: () => elFormRef.value?.clearValidate(),
32
+ scrollToField: (prop: string) => elFormRef.value?.scrollToField(prop),
33
+ elForm: elFormRef,
34
+ });
35
+
36
+ // ---- 值读写辅助(支持嵌套 prop 路径) ----
37
+ function getVal(prop: string | undefined): any {
38
+ return prop ? getPath(model.value, prop) : undefined;
39
+ }
40
+ function onFieldUpdate(item: any, val: any) {
41
+ if (item.prop) setPath(model.value, item.prop, val);
42
+ }
43
+
44
+ // ---- 操作栏事件 ----
45
+ async function onSubmit() {
46
+ try {
47
+ await elFormRef.value?.validate();
48
+ emit("submit");
49
+ } catch {
50
+ /* 校验失败由 EP 显示错误 */
51
+ }
52
+ }
53
+ function onReset() {
54
+ elFormRef.value?.resetFields();
55
+ emit("reset");
56
+ }
57
+ function onCancel() {
58
+ emit("cancel");
59
+ }
60
+ </script>
61
+
62
+ <template>
63
+ <el-form
64
+ ref="elFormRef"
65
+ class="suna-form"
66
+ :model="model"
67
+ :label-width="labelWidth"
68
+ :label-position="labelPosition"
69
+ :size="size"
70
+ v-bind="$attrs"
71
+ >
72
+ <el-row :gutter="gutter">
73
+ <template v-for="(item, i) in formItems" :key="(item as any).prop || (item as any).slot || i">
74
+ <el-col
75
+ v-if="(item as any).showItem !== false"
76
+ :span="getSpan(item)"
77
+ :offset="(item as any).offset || 0"
78
+ >
79
+ <!-- 纯插槽项:由调用方通过具名 slot 完全掌控渲染 -->
80
+ <slot
81
+ v-if="(item as any).slot"
82
+ :name="(item as any).slot"
83
+ :form="model"
84
+ :item="item"
85
+ />
86
+ <!-- 字段项 -->
87
+ <el-form-item
88
+ v-else
89
+ :prop="(item as any).prop"
90
+ :label="(item as any).label"
91
+ :label-width="(item as any).labelWidth"
92
+ :rules="getRules(item)"
93
+ >
94
+ <template #label>
95
+ <span>{{ (item as any).label }}</span>
96
+ <span
97
+ v-if="(item as any).tooltip"
98
+ class="suna-form__tooltip-icon"
99
+ :title="(item as any).tooltip"
100
+ >ⓘ</span>
101
+ </template>
102
+ <component
103
+ :is="getType((item as any).type)"
104
+ :model-value="getVal((item as any).prop)"
105
+ :item="item"
106
+ :form="model"
107
+ @update:model-value="(v: any) => onFieldUpdate(item, v)"
108
+ />
109
+ </el-form-item>
110
+ </el-col>
111
+ </template>
112
+ </el-row>
113
+
114
+ <!-- 内置操作栏(按钮对齐、显隐、文字、防抖等语义同 Drawer footer) -->
115
+ <div
116
+ v-if="withActions"
117
+ class="suna-form__actions"
118
+ :class="`is-${actionAlign}`"
119
+ >
120
+ <SunaButton
121
+ v-if="showReset"
122
+ :type="resetButtonType"
123
+ @click="onReset"
124
+ >{{ resetText }}</SunaButton>
125
+ <SunaButton
126
+ v-if="showCancel"
127
+ :type="cancelButtonType"
128
+ @click="onCancel"
129
+ >{{ cancelText }}</SunaButton>
130
+ <SunaDebounceButton
131
+ v-if="showSubmit"
132
+ :type="submitButtonType"
133
+ :loading="submitLoading"
134
+ :wait="submitWait"
135
+ :leading="submitLeading"
136
+ @click="onSubmit"
137
+ >{{ submitText }}</SunaDebounceButton>
138
+ </div>
139
+ </el-form>
140
+ </template>
141
+
142
+ <style scoped>
143
+ /* 操作栏:flex 布局,对齐方式由 .is-* 控制 */
144
+ .suna-form__actions {
145
+ display: flex;
146
+ align-items: center;
147
+ gap: 12px;
148
+ width: 100%;
149
+ margin-top: 16px;
150
+ padding-top: 16px;
151
+ border-top: 1px solid var(--el-border-color-light, #e5e7eb);
152
+ }
153
+
154
+ .suna-form__actions.is-left {
155
+ justify-content: flex-start;
156
+ }
157
+
158
+ .suna-form__actions.is-center {
159
+ justify-content: center;
160
+ }
161
+
162
+ .suna-form__actions.is-right {
163
+ justify-content: flex-end;
164
+ }
165
+ </style>
166
+
167
+ <!-- 子组件(F*.vue)共用的文本 / 工具提示类 -->
168
+ <style>
169
+ .suna-form__text {
170
+ font-size: 14px;
171
+ color: var(--el-text-color-regular, #303133);
172
+ word-break: break-all;
173
+ line-height: 1.6;
174
+ }
175
+
176
+ .suna-form__tooltip-icon {
177
+ cursor: help;
178
+ font-size: 14px;
179
+ color: var(--el-text-color-secondary, #909399);
180
+ margin-left: 4px;
181
+ vertical-align: middle;
182
+ }
183
+ </style>
@@ -0,0 +1,25 @@
1
+ <script setup lang="ts">
2
+ import { ElCascader } from "element-plus";
3
+ import { useFormField } from "../useFormField";
4
+
5
+ const props = defineProps({
6
+ modelValue: { default: undefined },
7
+ item: { type: Object, default: () => ({}) },
8
+ form: { type: Object, default: () => ({}) },
9
+ });
10
+ const emit = defineEmits(["update:modelValue", "change"]);
11
+
12
+ const { valueTmp, showText, formatTextVal, handleChange } =
13
+ useFormField(props, emit);
14
+ </script>
15
+
16
+ <template>
17
+ <ElCascader
18
+ v-if="!showText"
19
+ :model-value="valueTmp"
20
+ :options="item.options"
21
+ v-bind="item.attrs"
22
+ @update:model-value="handleChange"
23
+ />
24
+ <span v-else class="suna-form__text">{{ formatTextVal }}</span>
25
+ </template>
@@ -0,0 +1,33 @@
1
+ <script setup lang="ts">
2
+ import { ElCheckboxGroup, ElCheckbox } from "element-plus";
3
+ import { useFormField } from "../useFormField";
4
+
5
+ const props = defineProps({
6
+ modelValue: { default: undefined },
7
+ item: { type: Object, default: () => ({}) },
8
+ form: { type: Object, default: () => ({}) },
9
+ });
10
+ const emit = defineEmits(["update:modelValue", "change"]);
11
+
12
+ const { valueTmp, showText, formatTextVal, handleChange } =
13
+ useFormField(props, emit);
14
+ </script>
15
+
16
+ <template>
17
+ <ElCheckboxGroup
18
+ v-if="!showText"
19
+ :model-value="valueTmp"
20
+ v-bind="item.attrs"
21
+ @update:model-value="handleChange"
22
+ >
23
+ <ElCheckbox
24
+ v-for="opt in (item.options || [])"
25
+ :key="opt[item.optionProps?.value || 'value']"
26
+ :value="opt[item.optionProps?.value || 'value']"
27
+ :disabled="opt.disabled"
28
+ >
29
+ {{ opt[item.optionProps?.label || 'label'] }}
30
+ </ElCheckbox>
31
+ </ElCheckboxGroup>
32
+ <span v-else class="suna-form__text">{{ formatTextVal }}</span>
33
+ </template>
@@ -0,0 +1,24 @@
1
+ <script setup lang="ts">
2
+ import { ElDatePicker } from "element-plus";
3
+ import { useFormField } from "../useFormField";
4
+
5
+ const props = defineProps({
6
+ modelValue: { default: undefined },
7
+ item: { type: Object, default: () => ({}) },
8
+ form: { type: Object, default: () => ({}) },
9
+ });
10
+ const emit = defineEmits(["update:modelValue", "change"]);
11
+
12
+ const { valueTmp, showText, formatTextVal, handleChange } =
13
+ useFormField(props, emit);
14
+ </script>
15
+
16
+ <template>
17
+ <ElDatePicker
18
+ v-if="!showText"
19
+ :model-value="valueTmp"
20
+ v-bind="item.attrs"
21
+ @update:model-value="handleChange"
22
+ />
23
+ <span v-else class="suna-form__text">{{ formatTextVal }}</span>
24
+ </template>
@@ -0,0 +1,26 @@
1
+ <script setup lang="ts">
2
+ import { ElInput } from "element-plus";
3
+ import { useFormField } from "../useFormField";
4
+
5
+ const props = defineProps({
6
+ modelValue: { default: undefined },
7
+ item: { type: Object, default: () => ({}) },
8
+ form: { type: Object, default: () => ({}) },
9
+ });
10
+ const emit = defineEmits(["update:modelValue", "change"]);
11
+
12
+ const { valueTmp, showText, formatTextVal, handleChange, blur, focus } =
13
+ useFormField(props, emit);
14
+ </script>
15
+
16
+ <template>
17
+ <ElInput
18
+ v-if="!showText"
19
+ :model-value="valueTmp"
20
+ v-bind="item.attrs"
21
+ @update:model-value="handleChange"
22
+ @blur="blur"
23
+ @focus="focus"
24
+ />
25
+ <span v-else class="suna-form__text">{{ formatTextVal }}</span>
26
+ </template>
@@ -0,0 +1,26 @@
1
+ <script setup lang="ts">
2
+ import { ElInputNumber } from "element-plus";
3
+ import { useFormField } from "../useFormField";
4
+
5
+ const props = defineProps({
6
+ modelValue: { default: undefined },
7
+ item: { type: Object, default: () => ({}) },
8
+ form: { type: Object, default: () => ({}) },
9
+ });
10
+ const emit = defineEmits(["update:modelValue", "change"]);
11
+
12
+ const { valueTmp, showText, formatTextVal, handleChange, blur, focus } =
13
+ useFormField(props, emit);
14
+ </script>
15
+
16
+ <template>
17
+ <ElInputNumber
18
+ v-if="!showText"
19
+ :model-value="valueTmp"
20
+ v-bind="item.attrs"
21
+ @update:model-value="handleChange"
22
+ @blur="blur"
23
+ @focus="focus"
24
+ />
25
+ <span v-else class="suna-form__text">{{ formatTextVal }}</span>
26
+ </template>
@@ -0,0 +1,33 @@
1
+ <script setup lang="ts">
2
+ import { ElRadioGroup, ElRadio } from "element-plus";
3
+ import { useFormField } from "../useFormField";
4
+
5
+ const props = defineProps({
6
+ modelValue: { default: undefined },
7
+ item: { type: Object, default: () => ({}) },
8
+ form: { type: Object, default: () => ({}) },
9
+ });
10
+ const emit = defineEmits(["update:modelValue", "change"]);
11
+
12
+ const { valueTmp, showText, formatTextVal, handleChange } =
13
+ useFormField(props, emit);
14
+ </script>
15
+
16
+ <template>
17
+ <ElRadioGroup
18
+ v-if="!showText"
19
+ :model-value="valueTmp"
20
+ v-bind="item.attrs"
21
+ @update:model-value="handleChange"
22
+ >
23
+ <ElRadio
24
+ v-for="opt in (item.options || [])"
25
+ :key="opt[item.optionProps?.value || 'value']"
26
+ :label="opt[item.optionProps?.value || 'value']"
27
+ :disabled="opt.disabled"
28
+ >
29
+ {{ opt[item.optionProps?.label || 'label'] }}
30
+ </ElRadio>
31
+ </ElRadioGroup>
32
+ <span v-else class="suna-form__text">{{ formatTextVal }}</span>
33
+ </template>
@@ -0,0 +1,24 @@
1
+ <script setup lang="ts">
2
+ import { ElRate } from "element-plus";
3
+ import { useFormField } from "../useFormField";
4
+
5
+ const props = defineProps({
6
+ modelValue: { default: undefined },
7
+ item: { type: Object, default: () => ({}) },
8
+ form: { type: Object, default: () => ({}) },
9
+ });
10
+ const emit = defineEmits(["update:modelValue", "change"]);
11
+
12
+ const { valueTmp, showText, formatTextVal, handleChange } =
13
+ useFormField(props, emit);
14
+ </script>
15
+
16
+ <template>
17
+ <ElRate
18
+ v-if="!showText"
19
+ :model-value="valueTmp"
20
+ v-bind="item.attrs"
21
+ @update:model-value="handleChange"
22
+ />
23
+ <span v-else class="suna-form__text">{{ formatTextVal }}</span>
24
+ </template>
@@ -0,0 +1,34 @@
1
+ <script setup lang="ts">
2
+ import { ElSelect, ElOption } from "element-plus";
3
+ import { useFormField } from "../useFormField";
4
+
5
+ const props = defineProps({
6
+ modelValue: { default: undefined },
7
+ item: { type: Object, default: () => ({}) },
8
+ form: { type: Object, default: () => ({}) },
9
+ });
10
+ const emit = defineEmits(["update:modelValue", "change"]);
11
+
12
+ const { valueTmp, showText, formatTextVal, handleChange, blur, focus } =
13
+ useFormField(props, emit);
14
+ </script>
15
+
16
+ <template>
17
+ <ElSelect
18
+ v-if="!showText"
19
+ :model-value="valueTmp"
20
+ v-bind="item.attrs"
21
+ @update:model-value="handleChange"
22
+ @blur="blur"
23
+ @focus="focus"
24
+ >
25
+ <ElOption
26
+ v-for="(opt, i) in (item.options || [])"
27
+ :key="opt[item.optionProps?.value || 'value'] ?? i"
28
+ :label="opt[item.optionProps?.label || 'label']"
29
+ :value="opt[item.optionProps?.value || 'value']"
30
+ :disabled="opt.disabled"
31
+ />
32
+ </ElSelect>
33
+ <span v-else class="suna-form__text">{{ formatTextVal }}</span>
34
+ </template>
@@ -0,0 +1,24 @@
1
+ <script setup lang="ts">
2
+ import { ElSwitch } from "element-plus";
3
+ import { useFormField } from "../useFormField";
4
+
5
+ const props = defineProps({
6
+ modelValue: { default: undefined },
7
+ item: { type: Object, default: () => ({}) },
8
+ form: { type: Object, default: () => ({}) },
9
+ });
10
+ const emit = defineEmits(["update:modelValue", "change"]);
11
+
12
+ const { valueTmp, showText, formatTextVal, handleChange } =
13
+ useFormField(props, emit);
14
+ </script>
15
+
16
+ <template>
17
+ <ElSwitch
18
+ v-if="!showText"
19
+ :model-value="valueTmp"
20
+ v-bind="item.attrs"
21
+ @update:model-value="handleChange"
22
+ />
23
+ <span v-else class="suna-form__text">{{ formatTextVal }}</span>
24
+ </template>