@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.
- package/README.md +52 -0
- package/button/index.ts +3 -0
- package/button/src/Button.vue +42 -0
- package/button/src/props.ts +24 -0
- package/button/src/types.ts +26 -0
- package/confirm-button/index.ts +2 -0
- package/confirm-button/src/ConfirmButton.vue +52 -0
- package/confirm-button/src/props.ts +50 -0
- package/debounce-button/index.ts +2 -0
- package/debounce-button/src/DebounceButton.vue +65 -0
- package/debounce-button/src/props.ts +15 -0
- package/drawer/index.ts +3 -0
- package/drawer/src/Drawer.vue +120 -0
- package/drawer/src/props.ts +76 -0
- package/drawer/src/types.ts +8 -0
- package/drawer-form/index.ts +2 -0
- package/drawer-form/src/DrawerForm.vue +164 -0
- package/drawer-form/src/props.ts +74 -0
- package/form/index.ts +3 -0
- package/form/src/Form.vue +183 -0
- package/form/src/form-items/FCascader.vue +25 -0
- package/form/src/form-items/FCheckboxGroup.vue +33 -0
- package/form/src/form-items/FDatePicker.vue +24 -0
- package/form/src/form-items/FInput.vue +26 -0
- package/form/src/form-items/FInputNumber.vue +26 -0
- package/form/src/form-items/FRadioGroup.vue +33 -0
- package/form/src/form-items/FRate.vue +24 -0
- package/form/src/form-items/FSelect.vue +34 -0
- package/form/src/form-items/FSwitch.vue +24 -0
- package/form/src/form-items/FText.vue +16 -0
- package/form/src/form-items/FTextarea.vue +27 -0
- package/form/src/path.ts +39 -0
- package/form/src/props.ts +73 -0
- package/form/src/types.ts +168 -0
- package/form/src/useFormField.ts +137 -0
- package/form/src/useFormItems.ts +85 -0
- package/index.ts +82 -0
- package/package.json +50 -0
package/README.md
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Suna Components
|
|
2
|
+
|
|
3
|
+
Vue 3 component library based on [Element Plus](https://element-plus.org/).
|
|
4
|
+
|
|
5
|
+
- **Button** — action buttons with type/size/loading/icon support
|
|
6
|
+
- **DebounceButton** — click with debounce (trailing or leading)
|
|
7
|
+
- **ConfirmButton** — confirmation popover before executing actions
|
|
8
|
+
- **Drawer** — slide-in panel with configurable direction/size/footer
|
|
9
|
+
- **DrawerForm** — Drawer + Form composition for create/edit flows
|
|
10
|
+
- **Form** — config-driven form (define fields via `formItems` array), with validation, layout grid, detail mode (read-only), and built-in action bar
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install @scorpioz/suna-components
|
|
16
|
+
# or
|
|
17
|
+
pnpm add @scorpioz/suna-components
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Peer dependencies: `vue ^3.5.0`, `element-plus ^2.14.0`.
|
|
21
|
+
|
|
22
|
+
## Usage
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
import { createApp } from "vue";
|
|
26
|
+
import ElementPlus from "element-plus";
|
|
27
|
+
import { createComponents } from "@scorpioz/suna-components";
|
|
28
|
+
|
|
29
|
+
const app = createApp(App);
|
|
30
|
+
app.use(ElementPlus);
|
|
31
|
+
app.use(createComponents());
|
|
32
|
+
// Now use <SunaButton>, <SunaForm>, <SunaDrawer>, etc.
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Custom prefix:
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
app.use(createComponents({ prefix: "S" }));
|
|
39
|
+
// <SButton>, <SForm>, <SDrawer>, …
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Import Style
|
|
43
|
+
|
|
44
|
+
Element Plus styles are imported once when you install the components:
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
import "element-plus/dist/index.css"; // already included via createComponents()
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Docs
|
|
51
|
+
|
|
52
|
+
See the [full documentation](https://github.com/scorpioz/web-component).
|
package/button/index.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from "vue";
|
|
3
|
+
import { ElButton } from "element-plus";
|
|
4
|
+
import { buttonProps } from "./props";
|
|
5
|
+
import type { ElButtonType } from "./types";
|
|
6
|
+
|
|
7
|
+
defineOptions({ name: "SunaButton" });
|
|
8
|
+
|
|
9
|
+
// props 定义抽取到 ./props(运行时声明 + PropType,原因见该文件)
|
|
10
|
+
const props = defineProps(buttonProps);
|
|
11
|
+
|
|
12
|
+
const emit = defineEmits<{ click: [evt: MouseEvent] }>();
|
|
13
|
+
|
|
14
|
+
// Element Plus 的默认 type 为空字符串,将我们的 "default" 映射为 ""
|
|
15
|
+
const elType = computed<ElButtonType>(() =>
|
|
16
|
+
props.type === "default" ? "" : props.type
|
|
17
|
+
);
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<template>
|
|
21
|
+
<!--
|
|
22
|
+
基于 Element Plus 的 ElButton 封装:
|
|
23
|
+
- 声明的 props 显式绑定并设置了默认值;
|
|
24
|
+
- 其余属性(如 icon / tag / autofocus / class / id 等)通过 attrs 自动透传;
|
|
25
|
+
- 透传 `#icon`、`#loading`、默认插槽。
|
|
26
|
+
-->
|
|
27
|
+
<ElButton
|
|
28
|
+
:type="elType"
|
|
29
|
+
:size="size"
|
|
30
|
+
:plain="plain"
|
|
31
|
+
:round="round"
|
|
32
|
+
:circle="circle"
|
|
33
|
+
:disabled="disabled"
|
|
34
|
+
:loading="loading"
|
|
35
|
+
:native-type="nativeType"
|
|
36
|
+
@click="emit('click', $event)"
|
|
37
|
+
>
|
|
38
|
+
<template v-if="$slots.icon" #icon><slot name="icon" /></template>
|
|
39
|
+
<template v-if="$slots.loading" #loading><slot name="loading" /></template>
|
|
40
|
+
<slot />
|
|
41
|
+
</ElButton>
|
|
42
|
+
</template>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { PropType } from "vue";
|
|
2
|
+
import type { ButtonType, ButtonSize, NativeButtonType } from "./types";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Button 的运行时 props 声明(从 .vue 中抽出,保持模板文件简洁)。
|
|
6
|
+
*
|
|
7
|
+
* 采用运行时声明(而非 defineProps<T>() 类型声明):@vue/compiler-sfc 在 dev 下解析
|
|
8
|
+
* defineProps<T>() 时会静态分析类型,无法可靠解析跨文件导入的类型,会抛出
|
|
9
|
+
* "Unresolvable type reference"。运行时声明配合 PropType 断言既保证类型安全,
|
|
10
|
+
* 又不触发该解析;抽到独立文件后依然是「值」而非「类型」,同样不受影响。
|
|
11
|
+
*/
|
|
12
|
+
export const buttonProps = {
|
|
13
|
+
type: { type: String as PropType<ButtonType>, default: "default" },
|
|
14
|
+
size: { type: String as PropType<ButtonSize>, default: "default" },
|
|
15
|
+
plain: { type: Boolean, default: false },
|
|
16
|
+
round: { type: Boolean, default: false },
|
|
17
|
+
circle: { type: Boolean, default: false },
|
|
18
|
+
disabled: { type: Boolean, default: false },
|
|
19
|
+
loading: { type: Boolean, default: false },
|
|
20
|
+
nativeType: {
|
|
21
|
+
type: String as PropType<NativeButtonType>,
|
|
22
|
+
default: "button",
|
|
23
|
+
},
|
|
24
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** 按钮类型(对外暴露的 API 类型,default 表示默认样式) */
|
|
2
|
+
export type ButtonType =
|
|
3
|
+
| "default"
|
|
4
|
+
| "primary"
|
|
5
|
+
| "success"
|
|
6
|
+
| "warning"
|
|
7
|
+
| "danger"
|
|
8
|
+
| "info";
|
|
9
|
+
|
|
10
|
+
/** 按钮尺寸 */
|
|
11
|
+
export type ButtonSize = "large" | "default" | "small";
|
|
12
|
+
|
|
13
|
+
/** 原生 button 的 type 属性 */
|
|
14
|
+
export type NativeButtonType = "button" | "submit" | "reset";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Element Plus 接受的按钮类型。
|
|
18
|
+
* EP 的默认类型以空字符串表示,因此需要把我们的 "default" 映射为 ""。
|
|
19
|
+
*/
|
|
20
|
+
export type ElButtonType =
|
|
21
|
+
| ""
|
|
22
|
+
| "primary"
|
|
23
|
+
| "success"
|
|
24
|
+
| "warning"
|
|
25
|
+
| "danger"
|
|
26
|
+
| "info";
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ElPopconfirm } from "element-plus";
|
|
3
|
+
import SunaButton from "../../button/src/Button.vue";
|
|
4
|
+
import { confirmButtonProps } from "./props";
|
|
5
|
+
|
|
6
|
+
defineOptions({ name: "SunaConfirmButton", inheritAttrs: false });
|
|
7
|
+
|
|
8
|
+
// props 定义抽取到 ./props(运行时声明 + PropType,原因见该文件)
|
|
9
|
+
defineProps(confirmButtonProps);
|
|
10
|
+
|
|
11
|
+
const emit = defineEmits<{ confirm: []; cancel: [] }>();
|
|
12
|
+
|
|
13
|
+
const onConfirm = () => emit("confirm");
|
|
14
|
+
const onCancel = () => emit("cancel");
|
|
15
|
+
|
|
16
|
+
// 显式声明的 props 之外,其余属性(teleported / popper-class / icon / class 等)透传给 ElPopconfirm
|
|
17
|
+
</script>
|
|
18
|
+
|
|
19
|
+
<template>
|
|
20
|
+
<ElPopconfirm
|
|
21
|
+
:title="title"
|
|
22
|
+
:confirm-button-text="confirmButtonText"
|
|
23
|
+
:cancel-button-text="cancelButtonText"
|
|
24
|
+
:confirm-button-type="confirmButtonType"
|
|
25
|
+
:cancel-button-type="cancelButtonType"
|
|
26
|
+
:icon-color="iconColor"
|
|
27
|
+
:hide-icon="hideIcon"
|
|
28
|
+
:width="width"
|
|
29
|
+
:placement="placement"
|
|
30
|
+
:hide-after="hideAfter"
|
|
31
|
+
:disabled="disabled"
|
|
32
|
+
v-bind="$attrs"
|
|
33
|
+
@confirm="onConfirm"
|
|
34
|
+
@cancel="onCancel"
|
|
35
|
+
>
|
|
36
|
+
<template #reference>
|
|
37
|
+
<SunaButton
|
|
38
|
+
:type="type"
|
|
39
|
+
:size="size"
|
|
40
|
+
:plain="plain"
|
|
41
|
+
:round="round"
|
|
42
|
+
:circle="circle"
|
|
43
|
+
:disabled="disabled"
|
|
44
|
+
:loading="loading"
|
|
45
|
+
:native-type="nativeType"
|
|
46
|
+
>
|
|
47
|
+
<template v-if="$slots.icon" #icon><slot name="icon" /></template>
|
|
48
|
+
<slot />
|
|
49
|
+
</SunaButton>
|
|
50
|
+
</template>
|
|
51
|
+
</ElPopconfirm>
|
|
52
|
+
</template>
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { PropType } from "vue";
|
|
2
|
+
import type {
|
|
3
|
+
ButtonType,
|
|
4
|
+
ButtonSize,
|
|
5
|
+
NativeButtonType,
|
|
6
|
+
ElButtonType,
|
|
7
|
+
} from "../../button/src/types";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* ConfirmButton 的 props:触发按钮属性 + 弹层确认属性(运行时声明,见同类文件说明)。
|
|
11
|
+
*/
|
|
12
|
+
export const confirmButtonProps = {
|
|
13
|
+
// ---- 触发按钮属性 ----
|
|
14
|
+
type: { type: String as PropType<ButtonType>, default: "primary" },
|
|
15
|
+
size: { type: String as PropType<ButtonSize>, default: "default" },
|
|
16
|
+
plain: { type: Boolean, default: false },
|
|
17
|
+
round: { type: Boolean, default: false },
|
|
18
|
+
circle: { type: Boolean, default: false },
|
|
19
|
+
disabled: { type: Boolean, default: false },
|
|
20
|
+
loading: { type: Boolean, default: false },
|
|
21
|
+
nativeType: {
|
|
22
|
+
type: String as PropType<NativeButtonType>,
|
|
23
|
+
default: "button",
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
// ---- 弹层确认属性 ----
|
|
27
|
+
/** 确认提示文案 */
|
|
28
|
+
title: { type: String, default: "确定执行此操作吗?" },
|
|
29
|
+
/** 确认按钮文字 */
|
|
30
|
+
confirmButtonText: { type: String, default: "确定" },
|
|
31
|
+
/** 取消按钮文字 */
|
|
32
|
+
cancelButtonText: { type: String, default: "取消" },
|
|
33
|
+
/** 确认按钮类型 */
|
|
34
|
+
confirmButtonType: {
|
|
35
|
+
type: String as PropType<ElButtonType>,
|
|
36
|
+
default: "primary",
|
|
37
|
+
},
|
|
38
|
+
/** 取消按钮类型 */
|
|
39
|
+
cancelButtonType: { type: String as PropType<ElButtonType>, default: "" },
|
|
40
|
+
/** 图标颜色 */
|
|
41
|
+
iconColor: { type: String, default: "#e6a23c" },
|
|
42
|
+
/** 是否隐藏图标 */
|
|
43
|
+
hideIcon: { type: Boolean, default: false },
|
|
44
|
+
/** 弹层宽度(px) */
|
|
45
|
+
width: { type: Number, default: 200 },
|
|
46
|
+
/** 弹层出现位置 */
|
|
47
|
+
placement: { type: String, default: "top" },
|
|
48
|
+
/** 确认/取消后延迟隐藏弹层的时间(ms) */
|
|
49
|
+
hideAfter: { type: Number, default: 200 },
|
|
50
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { onBeforeUnmount } from "vue";
|
|
3
|
+
import SunaButton from "../../button/src/Button.vue";
|
|
4
|
+
import { debounceButtonProps } from "./props";
|
|
5
|
+
|
|
6
|
+
defineOptions({ name: "SunaDebounceButton" });
|
|
7
|
+
|
|
8
|
+
// props 定义抽取到 ./props(复用 buttonProps + 防抖参数)
|
|
9
|
+
const props = defineProps(debounceButtonProps);
|
|
10
|
+
|
|
11
|
+
const emit = defineEmits<{ click: [evt: MouseEvent] }>();
|
|
12
|
+
|
|
13
|
+
let timer: ReturnType<typeof setTimeout> | null = null;
|
|
14
|
+
let pendingEvt: MouseEvent | null = null;
|
|
15
|
+
|
|
16
|
+
const clearTimer = () => {
|
|
17
|
+
if (timer !== null) {
|
|
18
|
+
clearTimeout(timer);
|
|
19
|
+
timer = null;
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const handleClick = (evt: MouseEvent) => {
|
|
24
|
+
if (props.leading) {
|
|
25
|
+
// 前缘防抖:未锁定时立即触发,随后锁定 wait 毫秒(锁定期内忽略点击)
|
|
26
|
+
if (timer === null) {
|
|
27
|
+
emit("click", evt);
|
|
28
|
+
timer = setTimeout(() => {
|
|
29
|
+
timer = null;
|
|
30
|
+
}, props.wait);
|
|
31
|
+
}
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// 后缘防抖:记录最近一次点击,重置计时,停止点击 wait 毫秒后触发一次
|
|
36
|
+
pendingEvt = evt;
|
|
37
|
+
clearTimer();
|
|
38
|
+
timer = setTimeout(() => {
|
|
39
|
+
const e = pendingEvt;
|
|
40
|
+
timer = null;
|
|
41
|
+
pendingEvt = null;
|
|
42
|
+
if (e) emit("click", e);
|
|
43
|
+
}, props.wait);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
onBeforeUnmount(clearTimer);
|
|
47
|
+
</script>
|
|
48
|
+
|
|
49
|
+
<template>
|
|
50
|
+
<SunaButton
|
|
51
|
+
:type="type"
|
|
52
|
+
:size="size"
|
|
53
|
+
:plain="plain"
|
|
54
|
+
:round="round"
|
|
55
|
+
:circle="circle"
|
|
56
|
+
:disabled="disabled"
|
|
57
|
+
:loading="loading"
|
|
58
|
+
:native-type="nativeType"
|
|
59
|
+
@click="handleClick"
|
|
60
|
+
>
|
|
61
|
+
<template v-if="$slots.icon" #icon><slot name="icon" /></template>
|
|
62
|
+
<template v-if="$slots.loading" #loading><slot name="loading" /></template>
|
|
63
|
+
<slot />
|
|
64
|
+
</SunaButton>
|
|
65
|
+
</template>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { buttonProps } from "../../button/src/props";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* DebounceButton 的 props:复用 Button 的全部 props,并追加防抖参数。
|
|
5
|
+
*/
|
|
6
|
+
export const debounceButtonProps = {
|
|
7
|
+
...buttonProps,
|
|
8
|
+
/** 防抖等待时间(ms) */
|
|
9
|
+
wait: { type: Number, default: 300 },
|
|
10
|
+
/**
|
|
11
|
+
* 是否前缘触发:首次点击立即触发,并在 wait 毫秒内锁定后续点击。
|
|
12
|
+
* 默认 false(后缘防抖:停止点击 wait 毫秒后才触发一次)。
|
|
13
|
+
*/
|
|
14
|
+
leading: { type: Boolean, default: false },
|
|
15
|
+
};
|
package/drawer/index.ts
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ElDrawer } from "element-plus";
|
|
3
|
+
import SunaButton from "../../button/src/Button.vue";
|
|
4
|
+
import SunaDebounceButton from "../../debounce-button/src/DebounceButton.vue";
|
|
5
|
+
import { drawerProps } from "./props";
|
|
6
|
+
|
|
7
|
+
defineOptions({ name: "SunaDrawer" });
|
|
8
|
+
|
|
9
|
+
// props 定义抽取到 ./props(运行时声明 + PropType,原因见该文件)
|
|
10
|
+
defineProps(drawerProps);
|
|
11
|
+
|
|
12
|
+
const emit = defineEmits<{
|
|
13
|
+
"update:modelValue": [value: boolean];
|
|
14
|
+
open: [];
|
|
15
|
+
opened: [];
|
|
16
|
+
close: [];
|
|
17
|
+
closed: [];
|
|
18
|
+
/** 点击「提交」按钮时触发 */
|
|
19
|
+
submit: [];
|
|
20
|
+
/** 点击「重置」按钮时触发 */
|
|
21
|
+
reset: [];
|
|
22
|
+
/** 点击「取消」按钮时触发 */
|
|
23
|
+
cancel: [];
|
|
24
|
+
}>();
|
|
25
|
+
|
|
26
|
+
// 显式声明的 props 之外,其余属性(before-close / append-to / z-index /
|
|
27
|
+
// modal-class / class 等)与未声明的事件通过 attrs 自动透传给 ElDrawer。
|
|
28
|
+
</script>
|
|
29
|
+
|
|
30
|
+
<template>
|
|
31
|
+
<!--
|
|
32
|
+
基于 Element Plus 的 ElDrawer 封装:
|
|
33
|
+
- 声明的 props 显式绑定并设置了默认值;
|
|
34
|
+
- v-model 由 modelValue + update:modelValue 转发;
|
|
35
|
+
- open / opened / close / closed 四个生命周期事件透传;
|
|
36
|
+
- 透传 #header、#footer 与默认插槽(#header 携带 close 等作用域参数)。
|
|
37
|
+
-->
|
|
38
|
+
<ElDrawer
|
|
39
|
+
:model-value="modelValue"
|
|
40
|
+
:title="title"
|
|
41
|
+
:direction="direction"
|
|
42
|
+
:size="size"
|
|
43
|
+
:with-header="withHeader"
|
|
44
|
+
:show-close="showClose"
|
|
45
|
+
:modal="modal"
|
|
46
|
+
:close-on-click-modal="closeOnClickModal"
|
|
47
|
+
:close-on-press-escape="closeOnPressEscape"
|
|
48
|
+
:destroy-on-close="destroyOnClose"
|
|
49
|
+
:lock-scroll="lockScroll"
|
|
50
|
+
@update:model-value="emit('update:modelValue', $event)"
|
|
51
|
+
@open="emit('open')"
|
|
52
|
+
@opened="emit('opened')"
|
|
53
|
+
@close="emit('close')"
|
|
54
|
+
@closed="emit('closed')"
|
|
55
|
+
>
|
|
56
|
+
<template v-if="$slots.header" #header="headerProps">
|
|
57
|
+
<slot name="header" v-bind="headerProps" />
|
|
58
|
+
</template>
|
|
59
|
+
<template v-if="withFooter" #footer>
|
|
60
|
+
<!-- 提供 #footer 插槽时完全交由调用方自定义,不做任何布局约束 -->
|
|
61
|
+
<slot v-if="$slots.footer" name="footer" />
|
|
62
|
+
<!-- 否则渲染默认底部按钮「重置 / 取消 / 提交」,对齐方式由 footerAlign 控制 -->
|
|
63
|
+
<div v-else class="suna-drawer__footer" :class="`is-${footerAlign}`">
|
|
64
|
+
<SunaButton
|
|
65
|
+
v-if="showReset"
|
|
66
|
+
:type="resetButtonType"
|
|
67
|
+
@click="emit('reset')"
|
|
68
|
+
>
|
|
69
|
+
{{ resetText }}
|
|
70
|
+
</SunaButton>
|
|
71
|
+
<SunaButton
|
|
72
|
+
v-if="showCancel"
|
|
73
|
+
:type="cancelButtonType"
|
|
74
|
+
@click="emit('cancel')"
|
|
75
|
+
>
|
|
76
|
+
{{ cancelText }}
|
|
77
|
+
</SunaButton>
|
|
78
|
+
<!-- 提交按钮使用防抖按钮,避免重复提交;默认前缘触发(首次立即提交并锁定 wait 毫秒) -->
|
|
79
|
+
<SunaDebounceButton
|
|
80
|
+
v-if="showSubmit"
|
|
81
|
+
:type="submitButtonType"
|
|
82
|
+
:loading="submitLoading"
|
|
83
|
+
:wait="submitWait"
|
|
84
|
+
:leading="submitLeading"
|
|
85
|
+
@click="emit('submit')"
|
|
86
|
+
>
|
|
87
|
+
{{ submitText }}
|
|
88
|
+
</SunaDebounceButton>
|
|
89
|
+
</div>
|
|
90
|
+
</template>
|
|
91
|
+
<slot />
|
|
92
|
+
</ElDrawer>
|
|
93
|
+
</template>
|
|
94
|
+
|
|
95
|
+
<style scoped>
|
|
96
|
+
/*
|
|
97
|
+
默认底部按钮容器。
|
|
98
|
+
el-drawer__footer 默认为 text-align:right(非 flex),这里用 flex + justify-content
|
|
99
|
+
精确控制左 / 居中 / 右对齐;width:100% 保证占满 footer 可用宽度。
|
|
100
|
+
该 div 由本组件渲染(携带 scoped 标记),即便被 ElDrawer teleport 到 body 仍可命中。
|
|
101
|
+
*/
|
|
102
|
+
.suna-drawer__footer {
|
|
103
|
+
display: flex;
|
|
104
|
+
align-items: center;
|
|
105
|
+
gap: 12px;
|
|
106
|
+
width: 100%;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.suna-drawer__footer.is-left {
|
|
110
|
+
justify-content: flex-start;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.suna-drawer__footer.is-center {
|
|
114
|
+
justify-content: center;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.suna-drawer__footer.is-right {
|
|
118
|
+
justify-content: flex-end;
|
|
119
|
+
}
|
|
120
|
+
</style>
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { PropType } from "vue";
|
|
2
|
+
import type { ButtonType } from "../../button/src/types";
|
|
3
|
+
import type {
|
|
4
|
+
DrawerDirection,
|
|
5
|
+
DrawerSize,
|
|
6
|
+
DrawerFooterAlign,
|
|
7
|
+
} from "./types";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Drawer 的 props:抽屉显隐 / 方向 / 尺寸 + 默认底部操作栏(运行时声明,见同类文件说明)。
|
|
11
|
+
*/
|
|
12
|
+
export const drawerProps = {
|
|
13
|
+
/** 是否显示抽屉(支持 v-model) */
|
|
14
|
+
modelValue: { type: Boolean, default: false },
|
|
15
|
+
/** 抽屉标题,也可通过 #header 插槽自定义 */
|
|
16
|
+
title: { type: String, default: "" },
|
|
17
|
+
/** 抽屉滑出方向 */
|
|
18
|
+
direction: { type: String as PropType<DrawerDirection>, default: "rtl" },
|
|
19
|
+
/** 抽屉尺寸:数字为 px,字符串可为百分比 */
|
|
20
|
+
size: { type: [String, Number] as PropType<DrawerSize>, default: "30%" },
|
|
21
|
+
/** 是否显示头部 */
|
|
22
|
+
withHeader: { type: Boolean, default: true },
|
|
23
|
+
/** 是否显示关闭按钮 */
|
|
24
|
+
showClose: { type: Boolean, default: true },
|
|
25
|
+
/** 是否显示遮罩层 */
|
|
26
|
+
modal: { type: Boolean, default: true },
|
|
27
|
+
/** 点击遮罩层是否关闭抽屉 */
|
|
28
|
+
closeOnClickModal: { type: Boolean, default: true },
|
|
29
|
+
/** 按 ESC 是否关闭抽屉 */
|
|
30
|
+
closeOnPressEscape: { type: Boolean, default: true },
|
|
31
|
+
/** 关闭时是否销毁抽屉内的元素 */
|
|
32
|
+
destroyOnClose: { type: Boolean, default: false },
|
|
33
|
+
/** 是否在抽屉打开时锁定 body 滚动 */
|
|
34
|
+
lockScroll: { type: Boolean, default: true },
|
|
35
|
+
|
|
36
|
+
// ---- 默认底部操作栏 ----
|
|
37
|
+
// 未提供 #footer 插槽时,底部默认渲染「重置 / 取消 / 提交」按钮;
|
|
38
|
+
// 提供 #footer 插槽则完全交由调用方自定义(见 Drawer.vue 模板)。
|
|
39
|
+
/** 是否显示底部区域 */
|
|
40
|
+
withFooter: { type: Boolean, default: true },
|
|
41
|
+
/** 默认底部按钮的对齐方式 */
|
|
42
|
+
footerAlign: {
|
|
43
|
+
type: String as PropType<DrawerFooterAlign>,
|
|
44
|
+
default: "right",
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
// 重置按钮
|
|
48
|
+
/** 是否显示重置按钮 */
|
|
49
|
+
showReset: { type: Boolean, default: true },
|
|
50
|
+
/** 重置按钮文字 */
|
|
51
|
+
resetText: { type: String, default: "重置" },
|
|
52
|
+
/** 重置按钮类型 */
|
|
53
|
+
resetButtonType: { type: String as PropType<ButtonType>, default: "default" },
|
|
54
|
+
|
|
55
|
+
// 取消按钮
|
|
56
|
+
/** 是否显示取消按钮 */
|
|
57
|
+
showCancel: { type: Boolean, default: true },
|
|
58
|
+
/** 取消按钮文字 */
|
|
59
|
+
cancelText: { type: String, default: "取消" },
|
|
60
|
+
/** 取消按钮类型 */
|
|
61
|
+
cancelButtonType: { type: String as PropType<ButtonType>, default: "default" },
|
|
62
|
+
|
|
63
|
+
// 提交按钮(使用防抖按钮,避免重复提交)
|
|
64
|
+
/** 是否显示提交按钮 */
|
|
65
|
+
showSubmit: { type: Boolean, default: true },
|
|
66
|
+
/** 提交按钮文字 */
|
|
67
|
+
submitText: { type: String, default: "提交" },
|
|
68
|
+
/** 提交按钮类型 */
|
|
69
|
+
submitButtonType: { type: String as PropType<ButtonType>, default: "primary" },
|
|
70
|
+
/** 提交按钮是否加载中(异步提交时置 true,按钮自动禁用) */
|
|
71
|
+
submitLoading: { type: Boolean, default: false },
|
|
72
|
+
/** 提交按钮防抖等待时间(ms) */
|
|
73
|
+
submitWait: { type: Number, default: 300 },
|
|
74
|
+
/** 提交按钮是否前缘防抖:首次点击立即触发并锁定 wait 毫秒,避免重复提交 */
|
|
75
|
+
submitLeading: { type: Boolean, default: true },
|
|
76
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** 抽屉滑出方向:rtl 右侧、ltr 左侧、ttb 顶部、btt 底部 */
|
|
2
|
+
export type DrawerDirection = "rtl" | "ltr" | "ttb" | "btt";
|
|
3
|
+
|
|
4
|
+
/** 抽屉尺寸:数字按 px 计算,字符串可用百分比(如 "30%") */
|
|
5
|
+
export type DrawerSize = string | number;
|
|
6
|
+
|
|
7
|
+
/** 默认底部按钮的对齐方式 */
|
|
8
|
+
export type DrawerFooterAlign = "left" | "center" | "right";
|