slider - 增加step
This commit is contained in:
47
src/component/avatar/index.less
Normal file
47
src/component/avatar/index.less
Normal file
@@ -0,0 +1,47 @@
|
||||
.layui-avatar {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
color: #000000d9;
|
||||
font-size: 14px;
|
||||
font-variant: tabular-nums;
|
||||
line-height: 1.5715;
|
||||
list-style: none;
|
||||
font-feature-settings: tnum;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
overflow: hidden;
|
||||
color: #fff;
|
||||
white-space: nowrap;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
background: #ccc;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.layui-avatar.layui-avatar-radius {
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.layui-avatar.layui-avatar-sm {
|
||||
height: 30px;
|
||||
width: 30px;
|
||||
}
|
||||
|
||||
.layui-avatar.layui-avatar-lg {
|
||||
height: 36px;
|
||||
width: 36px;
|
||||
}
|
||||
|
||||
.layui-avatar.layui-avatar-xs {
|
||||
height: 28px;
|
||||
width: 28px;
|
||||
}
|
||||
|
||||
.layui-avatar-list .layui-avatar {
|
||||
margin-left: -10px;
|
||||
display: inline-block;
|
||||
}
|
||||
8
src/component/avatar/index.ts
Normal file
8
src/component/avatar/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import type { App } from "vue";
|
||||
import Component from "./index.vue";
|
||||
|
||||
Component.install = (app: App) => {
|
||||
app.component(Component.name, Component);
|
||||
};
|
||||
|
||||
export default Component;
|
||||
27
src/component/avatar/index.vue
Normal file
27
src/component/avatar/index.vue
Normal file
@@ -0,0 +1,27 @@
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: "LayAvatar",
|
||||
};
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineProps } from "vue";
|
||||
import "./index.less";
|
||||
|
||||
const props = defineProps<{
|
||||
src?: String;
|
||||
size?: string;
|
||||
radius?: boolean;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<img
|
||||
:src="src"
|
||||
class="layui-avatar"
|
||||
:class="[
|
||||
radius ? 'layui-avatar-radius' : '',
|
||||
size ? 'layui-avatar-' + size : '',
|
||||
]"
|
||||
/>
|
||||
</template>
|
||||
8
src/component/avatarList/index.ts
Normal file
8
src/component/avatarList/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import type { App } from "vue";
|
||||
import Component from "./index.vue";
|
||||
|
||||
Component.install = (app: App) => {
|
||||
app.component(Component.name, Component);
|
||||
};
|
||||
|
||||
export default Component;
|
||||
15
src/component/avatarList/index.vue
Normal file
15
src/component/avatarList/index.vue
Normal file
@@ -0,0 +1,15 @@
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: "LayAvatarList",
|
||||
};
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineProps } from "vue";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="layui-avatar-list">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
36
src/component/backTop/index.less
Normal file
36
src/component/backTop/index.less
Normal file
@@ -0,0 +1,36 @@
|
||||
/** backtop **/
|
||||
@width: 50px;
|
||||
@height: @width;
|
||||
|
||||
.layui-backtop {
|
||||
position: fixed;
|
||||
right: 30px;
|
||||
bottom: 40px;
|
||||
width: @width;
|
||||
height: @height;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
font-size: 40px;
|
||||
background-color: #9f9f9f;
|
||||
color: #ffffff;
|
||||
border-radius: 2px;
|
||||
opacity: 0.95;
|
||||
z-index: 999999;
|
||||
:hover {
|
||||
opacity: 0.85;
|
||||
}
|
||||
}
|
||||
|
||||
.layui-backtop-medium{
|
||||
width: @width - 10px;
|
||||
height: @height - 10px;
|
||||
font-size: 30px;
|
||||
}
|
||||
|
||||
.layui-backtop-small{
|
||||
width: @width - 20px;
|
||||
height: @height - 20px;
|
||||
font-size: 20px;
|
||||
}
|
||||
8
src/component/backTop/index.ts
Normal file
8
src/component/backTop/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import type { App } from "vue";
|
||||
import Component from "./index.vue";
|
||||
|
||||
Component.install = (app: App) => {
|
||||
app.component(Component.name, Component);
|
||||
};
|
||||
|
||||
export default Component;
|
||||
217
src/component/backTop/index.vue
Normal file
217
src/component/backTop/index.vue
Normal file
@@ -0,0 +1,217 @@
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: "LayBacktop",
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {
|
||||
defineProps,
|
||||
defineEmits,
|
||||
ref,
|
||||
shallowRef,
|
||||
withDefaults,
|
||||
computed,
|
||||
onMounted,
|
||||
} from "vue";
|
||||
import LayIcon from "../icon/index";
|
||||
import "./index.less";
|
||||
|
||||
export interface LayBacktopProps {
|
||||
/**通用*/
|
||||
target?: string;
|
||||
showHeight?: number;
|
||||
disabled?: boolean;
|
||||
/**组件样式*/
|
||||
position?: "fixed" | "absolute";
|
||||
right?: number;
|
||||
bottom?: number;
|
||||
size?: "medium" | "small";
|
||||
bgcolor?: string;
|
||||
opacity?: number;
|
||||
color?: string;
|
||||
borderRadius?: number | string;
|
||||
circle?: boolean;
|
||||
/**图标样式*/
|
||||
icon?: string;
|
||||
iconSize?: number;
|
||||
iconColor?: string;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<LayBacktopProps>(), {
|
||||
target: "window",
|
||||
showHeight: 200,
|
||||
icon: "layui-icon-top",
|
||||
iconSize: 30,
|
||||
disabled: false,
|
||||
circle: false,
|
||||
});
|
||||
|
||||
const emit = defineEmits(["click"]);
|
||||
|
||||
const backtopRef = ref<HTMLElement | null>(null);
|
||||
const scrollTarget = shallowRef<Window | HTMLElement | undefined>(undefined);
|
||||
let visible = ref(props.showHeight === 0);
|
||||
|
||||
const classBacktop = computed(() => {
|
||||
return {
|
||||
'layui-backtop-medium': props.size === "medium",
|
||||
'layui-backtop-small': props.size === "small"
|
||||
}
|
||||
});
|
||||
|
||||
const borderRadius = computed(() => {
|
||||
if (props.circle) {
|
||||
return "50%"
|
||||
};
|
||||
return typeof props.borderRadius === "number"
|
||||
? `${props.borderRadius}px`
|
||||
: props.borderRadius;
|
||||
});
|
||||
|
||||
const styleBacktop = computed(() => {
|
||||
return {
|
||||
position: props.position,
|
||||
right: `${props.right}px`,
|
||||
bottom: `${props.bottom}px`,
|
||||
backgroundColor: props.bgcolor,
|
||||
opacity: props.opacity,
|
||||
color: props.color,
|
||||
borderRadius: borderRadius.value,
|
||||
};
|
||||
});
|
||||
|
||||
// TODO 待改进
|
||||
const easeInOut = (value: number): number => {
|
||||
return value < 0.5 ? 2 * value * value : 1 - 2 * (value - 1) * (value - 1);
|
||||
};
|
||||
|
||||
const scrollToTop = () => {
|
||||
if (!scrollTarget.value) return;
|
||||
if (scrollTarget.value instanceof Window) {
|
||||
window.scrollTo({ top: 0, left: 0, behavior: "smooth" }); // smooth | instant(default)
|
||||
} else {
|
||||
const previous: number = Date.now();
|
||||
const scrollHeight: number = scrollTarget.value.scrollTop;
|
||||
const animationFunc = () => {
|
||||
if (!scrollTarget.value || scrollTarget.value instanceof Window) return;
|
||||
const elapsed = (Date.now() - previous) / 450;
|
||||
if (elapsed < 1) {
|
||||
scrollTarget.value.scrollTop = scrollHeight * (1 - easeInOut(elapsed));
|
||||
window.requestAnimationFrame(animationFunc);
|
||||
} else {
|
||||
scrollTarget.value.scrollTop = 0;
|
||||
}
|
||||
};
|
||||
window.requestAnimationFrame(animationFunc);
|
||||
}
|
||||
};
|
||||
|
||||
const handleScroll = () => {
|
||||
if (!scrollTarget.value) return;
|
||||
const scrollTop =
|
||||
scrollTarget.value instanceof Window
|
||||
? window.pageYOffset
|
||||
: scrollTarget.value.scrollTop;
|
||||
visible.value = scrollTop >= props.showHeight;
|
||||
};
|
||||
|
||||
const handleClick = (event: MouseEvent) => {
|
||||
if (!props.disabled) {
|
||||
scrollToTop();
|
||||
}
|
||||
emit("click", event);
|
||||
};
|
||||
|
||||
const handlerMousedown = () => {
|
||||
backtopRef.value!.style.opacity = "1";
|
||||
};
|
||||
|
||||
const handlerMouseup = () => {
|
||||
backtopRef.value!.style.opacity = "0.95";
|
||||
};
|
||||
|
||||
// 获取滚动目标元素
|
||||
const getScrollTarget = () => {
|
||||
if (props.target === "window") {
|
||||
return getScrollParent(backtopRef.value!, false);
|
||||
} else {
|
||||
const targetElement = document.querySelector<HTMLElement>(props.target);
|
||||
if (!targetElement){
|
||||
throw new Error(`target is not existed: ${props.target}`);
|
||||
}
|
||||
// 特定容器内部显示
|
||||
if (props.position === "absolute") {
|
||||
if (!targetElement.parentElement){
|
||||
throw new Error( `target parent element is not existed: ${props.target}`);
|
||||
}
|
||||
targetElement.parentElement.style.position = "relative";
|
||||
// backtopRef.value!.style.position = props.position;
|
||||
}
|
||||
return targetElement;
|
||||
}
|
||||
};
|
||||
|
||||
// 获取距离元素最近的可滚动祖先元素
|
||||
const getScrollParent = (
|
||||
element: HTMLElement,
|
||||
includeHidden: boolean
|
||||
): HTMLElement => {
|
||||
let style: CSSStyleDeclaration = getComputedStyle(element);
|
||||
let excludeStaticParent: boolean = style.position === "absolute";
|
||||
let overflowRegex: RegExp = includeHidden
|
||||
? /(auto|scroll|hidden)/
|
||||
: /(auto|scroll)/;
|
||||
//if (style.position === "fixed") return document.documentElement || document.body || window;
|
||||
for (let parent: HTMLElement = element; (parent = parent.parentElement!); ) {
|
||||
style = getComputedStyle(parent);
|
||||
if (excludeStaticParent && style.position === "static") {
|
||||
continue;
|
||||
}
|
||||
if (overflowRegex.test(style.overflow + style.overflowY + style.overflowX)){
|
||||
return parent;
|
||||
}
|
||||
}
|
||||
return document.documentElement || document.body || window;
|
||||
};
|
||||
|
||||
// 节流
|
||||
const throttle = (func: Function, wait: number) => {
|
||||
var timer: any = null;
|
||||
return (...args: any) => {
|
||||
if (!timer) {
|
||||
timer = setTimeout(() => {
|
||||
timer = null;
|
||||
func.apply(this, args);
|
||||
}, wait);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
if (!props.target) return;
|
||||
scrollTarget.value = getScrollTarget();
|
||||
scrollTarget.value.addEventListener("scroll", throttle(handleScroll, 300));
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
v-show="visible"
|
||||
ref="backtopRef"
|
||||
class="layui-backtop"
|
||||
:class="classBacktop"
|
||||
:style="{ ...styleBacktop }"
|
||||
@click.stop="handleClick"
|
||||
@mousedown="handlerMousedown"
|
||||
@mouseup="handlerMouseup"
|
||||
>
|
||||
<slot>
|
||||
<lay-icon
|
||||
:type="props.icon"
|
||||
:size="`${props.iconSize}px`"
|
||||
:color="props.iconColor"
|
||||
/>
|
||||
</slot>
|
||||
</div>
|
||||
</template>
|
||||
33
src/component/badge/index.less
Normal file
33
src/component/badge/index.less
Normal file
@@ -0,0 +1,33 @@
|
||||
.layui-badge,
|
||||
.layui-badge-dot,
|
||||
.layui-badge-rim {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
padding: 0 6px;
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
background-color: #ff5722;
|
||||
color: #fff;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.layui-badge {
|
||||
height: 18px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.layui-badge-dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
padding: 0;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.layui-badge-rim {
|
||||
height: 18px;
|
||||
line-height: 18px;
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
background-color: #fff;
|
||||
color: #666;
|
||||
}
|
||||
8
src/component/badge/index.ts
Normal file
8
src/component/badge/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import type { App } from "vue";
|
||||
import Component from "./index.vue";
|
||||
|
||||
Component.install = (app: App) => {
|
||||
app.component(Component.name, Component);
|
||||
};
|
||||
|
||||
export default Component;
|
||||
39
src/component/badge/index.vue
Normal file
39
src/component/badge/index.vue
Normal file
@@ -0,0 +1,39 @@
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: "LayBadge",
|
||||
};
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, defineProps } from "vue";
|
||||
import "./index.less";
|
||||
|
||||
export interface LayBadgeProps {
|
||||
type?: "dot" | "rim";
|
||||
theme?: string;
|
||||
color?: string;
|
||||
}
|
||||
|
||||
const props = defineProps<LayBadgeProps>();
|
||||
|
||||
const classes = computed(() => {
|
||||
return [
|
||||
{
|
||||
"layui-badge": !props.type,
|
||||
"layui-badge-dot": props.type == "dot",
|
||||
"layui-badge-rim": props.type == "rim",
|
||||
},
|
||||
`layui-bg-${props.theme}`,
|
||||
];
|
||||
});
|
||||
|
||||
const styles = computed(() => {
|
||||
props.color ? `background-color: ${props.color}` : "";
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span :class="classes" :style="styles">
|
||||
<slot v-if="type != 'dot'"></slot>
|
||||
</span>
|
||||
</template>
|
||||
14
src/component/block/index.less
Normal file
14
src/component/block/index.less
Normal file
@@ -0,0 +1,14 @@
|
||||
.layui-elem-quote {
|
||||
margin-bottom: 10px;
|
||||
padding: 15px;
|
||||
line-height: 1.6;
|
||||
border-left: 5px solid #5fb878;
|
||||
border-radius: 0 2px 2px 0;
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
.layui-quote-nm {
|
||||
border-style: solid;
|
||||
border-width: 1px 1px 1px 5px;
|
||||
background: 0 0;
|
||||
}
|
||||
8
src/component/block/index.ts
Normal file
8
src/component/block/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import type { App } from "vue";
|
||||
import Component from "./index.vue";
|
||||
|
||||
Component.install = (app: App) => {
|
||||
app.component(Component.name, Component);
|
||||
};
|
||||
|
||||
export default Component;
|
||||
20
src/component/block/index.vue
Normal file
20
src/component/block/index.vue
Normal file
@@ -0,0 +1,20 @@
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: "LayBlock",
|
||||
};
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineProps } from "vue";
|
||||
import "./index.less";
|
||||
|
||||
const props = defineProps<{
|
||||
nm?: boolean;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<blockquote class="layui-elem-quote" :class="{ 'layui-quote-nm': nm }">
|
||||
<slot></slot>
|
||||
</blockquote>
|
||||
</template>
|
||||
8
src/component/body/index.less
Normal file
8
src/component/body/index.less
Normal file
@@ -0,0 +1,8 @@
|
||||
.layui-body {
|
||||
display: block;
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
min-height: 300px;
|
||||
}
|
||||
8
src/component/body/index.ts
Normal file
8
src/component/body/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import type { App } from "vue";
|
||||
import Component from "./index.vue";
|
||||
|
||||
Component.install = (app: App) => {
|
||||
app.component(Component.name, Component);
|
||||
};
|
||||
|
||||
export default Component;
|
||||
15
src/component/body/index.vue
Normal file
15
src/component/body/index.vue
Normal file
@@ -0,0 +1,15 @@
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: "LayBody",
|
||||
};
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import "./index.less";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="layui-body">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
8
src/component/breadcrumb/index.ts
Normal file
8
src/component/breadcrumb/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import type { App } from "vue";
|
||||
import Component from "./index.vue";
|
||||
|
||||
Component.install = (app: App) => {
|
||||
app.component(Component.name, Component);
|
||||
};
|
||||
|
||||
export default Component;
|
||||
26
src/component/breadcrumb/index.vue
Normal file
26
src/component/breadcrumb/index.vue
Normal file
@@ -0,0 +1,26 @@
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: "LayBreadcrumb",
|
||||
};
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineProps, provide, withDefaults } from "vue";
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
separator?: string;
|
||||
}>(),
|
||||
{
|
||||
separator: "/",
|
||||
}
|
||||
);
|
||||
|
||||
provide("separator", props.separator);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span class="layui-breadcrumb" style="visibility: visible">
|
||||
<slot></slot>
|
||||
</span>
|
||||
</template>
|
||||
8
src/component/breadcrumbItem/index.ts
Normal file
8
src/component/breadcrumbItem/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import type { App } from "vue";
|
||||
import Component from "./index.vue";
|
||||
|
||||
Component.install = (app: App) => {
|
||||
app.component(Component.name, Component);
|
||||
};
|
||||
|
||||
export default Component;
|
||||
29
src/component/breadcrumbItem/index.vue
Normal file
29
src/component/breadcrumbItem/index.vue
Normal file
@@ -0,0 +1,29 @@
|
||||
<template>
|
||||
<a href="javascript:void(0);">
|
||||
<template v-if="slot.default">
|
||||
<slot></slot>
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ title }}
|
||||
</template>
|
||||
</a>
|
||||
<span lay-separator>{{ separator }}</span>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: "LayBreadcrumbItem"
|
||||
}
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineProps, inject, useSlots } from "vue";
|
||||
|
||||
const slot = useSlots();
|
||||
|
||||
const props = defineProps<{
|
||||
title?: string;
|
||||
}>();
|
||||
|
||||
const separator = inject("separator");
|
||||
</script>
|
||||
100
src/component/button/index.less
Normal file
100
src/component/button/index.less
Normal file
@@ -0,0 +1,100 @@
|
||||
@import "../../theme/variable.less";
|
||||
|
||||
.layui-btn {
|
||||
height: 38px;
|
||||
line-height: 36px;
|
||||
padding: 0 18px;
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
border: 1px solid transparent;
|
||||
background-color: @button-primary-color;
|
||||
border-radius: @button-border-radius;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.layui-btn:hover {
|
||||
opacity: 0.8;
|
||||
filter: alpha(opacity=80);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.layui-btn:active {
|
||||
opacity: 1;
|
||||
filter: alpha(opacity=100);
|
||||
}
|
||||
|
||||
.layui-btn + .layui-btn {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.layui-btn-radius {
|
||||
border-radius: 100px;
|
||||
}
|
||||
|
||||
.layui-btn .layui-icon {
|
||||
padding: 0 2px;
|
||||
vertical-align: middle\9;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
||||
.layui-btn-primary {
|
||||
border-color: #d2d2d2;
|
||||
background: 0 0;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.layui-btn-primary:hover {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.layui-btn-normal {
|
||||
background-color: @global-normal-color;
|
||||
}
|
||||
|
||||
.layui-btn-warm {
|
||||
background-color: @global-warm-color;
|
||||
}
|
||||
|
||||
.layui-btn-danger {
|
||||
background-color: @global-danger-color;
|
||||
}
|
||||
|
||||
.layui-btn-lg {
|
||||
height: 44px;
|
||||
line-height: 44px;
|
||||
padding: 0 25px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.layui-btn-sm {
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
padding: 0 10px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.layui-btn-xs {
|
||||
height: 22px;
|
||||
line-height: 22px;
|
||||
padding: 0 5px;
|
||||
font-size: 12px;
|
||||
i {
|
||||
font-size: 12px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.layui-btn-fluid {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.layui-btn-disabled,
|
||||
.layui-btn-disabled:active,
|
||||
.layui-btn-disabled:hover {
|
||||
border-color: #eee !important;
|
||||
background-color: #fbfbfb !important;
|
||||
color: #d2d2d2 !important;
|
||||
cursor: not-allowed !important;
|
||||
opacity: 1;
|
||||
}
|
||||
8
src/component/button/index.ts
Normal file
8
src/component/button/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import type { App } from "vue";
|
||||
import Component from "./index.vue";
|
||||
|
||||
Component.install = (app: App) => {
|
||||
app.component(Component.name, Component);
|
||||
};
|
||||
|
||||
export default Component;
|
||||
70
src/component/button/index.vue
Normal file
70
src/component/button/index.vue
Normal file
@@ -0,0 +1,70 @@
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: "LayButton",
|
||||
};
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import "./index.less";
|
||||
import { computed } from "vue";
|
||||
|
||||
export interface LayButtonProps {
|
||||
type?: "primary" | "normal" | "warm" | "danger";
|
||||
size?: "lg" | "sm" | "xs";
|
||||
fluid?: boolean | string;
|
||||
radius?: boolean | string;
|
||||
border?: "green" | "blue" | "orange" | "red" | "black";
|
||||
disabled?: boolean | string;
|
||||
loading?: boolean | string;
|
||||
nativeType?: "button" | "submit" | "reset";
|
||||
icon?: string;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<LayButtonProps>(), {
|
||||
fluid: false,
|
||||
radius: false,
|
||||
loading: false,
|
||||
disabled: false,
|
||||
nativeType: "button",
|
||||
icon: "",
|
||||
});
|
||||
|
||||
const emit = defineEmits(["click"]);
|
||||
|
||||
const onClick = (event : any) => {
|
||||
if(!props.disabled) {
|
||||
emit("click", event);
|
||||
}
|
||||
}
|
||||
|
||||
const classes = computed(() => {
|
||||
return [
|
||||
props.type ? `layui-btn-${props.type}` : "",
|
||||
props.size ? `layui-btn-${props.size}` : "",
|
||||
props.border ? `layui-border-${props.border}` : "",
|
||||
];
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<button
|
||||
class="layui-btn"
|
||||
:class="[
|
||||
{
|
||||
'layui-btn-fluid': fluid,
|
||||
'layui-btn-radius': radius,
|
||||
'layui-btn-disabled': disabled,
|
||||
},
|
||||
classes,
|
||||
]"
|
||||
:type="nativeType"
|
||||
@click="onClick"
|
||||
>
|
||||
<i v-if="icon" :class="'layui-icon ' + icon"></i>
|
||||
<i
|
||||
v-if="loading"
|
||||
class="layui-icon layui-icon-loading-one layui-anim layui-anim-rotate layui-anim-loop"
|
||||
></i>
|
||||
<slot v-else></slot>
|
||||
</button>
|
||||
</template>
|
||||
10
src/component/buttonContainer/index.less
Normal file
10
src/component/buttonContainer/index.less
Normal file
@@ -0,0 +1,10 @@
|
||||
.layui-btn-container {
|
||||
font-size: 0;
|
||||
.layui-btn {
|
||||
margin-right: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.layui-btn + .layui-btn {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
8
src/component/buttonContainer/index.ts
Normal file
8
src/component/buttonContainer/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import type { App } from "vue";
|
||||
import Component from "./index.vue";
|
||||
|
||||
Component.install = (app: App) => {
|
||||
app.component(Component.name, Component);
|
||||
};
|
||||
|
||||
export default Component;
|
||||
15
src/component/buttonContainer/index.vue
Normal file
15
src/component/buttonContainer/index.vue
Normal file
@@ -0,0 +1,15 @@
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: "LayButtonContainer",
|
||||
};
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import "./index.less";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="layui-btn-container">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
41
src/component/buttonGroup/index.less
Normal file
41
src/component/buttonGroup/index.less
Normal file
@@ -0,0 +1,41 @@
|
||||
.layui-btn-group {
|
||||
vertical-align: middle;
|
||||
font-size: 0;
|
||||
}
|
||||
|
||||
.layui-btn-group .layui-btn {
|
||||
margin-left: 0 !important;
|
||||
margin-right: 0 !important;
|
||||
border-left: 1px solid rgba(255, 255, 255, 0.5);
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.layui-btn-group .layui-btn-primary {
|
||||
border-left: none;
|
||||
}
|
||||
|
||||
.layui-btn-group .layui-btn-primary:hover {
|
||||
border-color: #d2d2d2;
|
||||
color: #009688;
|
||||
}
|
||||
|
||||
.layui-btn-group .layui-btn:first-child {
|
||||
border-left: none;
|
||||
border-radius: 2px 0 0 2px;
|
||||
}
|
||||
|
||||
.layui-btn-group .layui-btn-primary:first-child {
|
||||
border-left: 1px solid #d2d2d2;
|
||||
}
|
||||
|
||||
.layui-btn-group .layui-btn:last-child {
|
||||
border-radius: 0 2px 2px 0;
|
||||
}
|
||||
|
||||
.layui-btn-group .layui-btn + .layui-btn {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.layui-btn-group + .layui-btn-group {
|
||||
margin-left: 10px;
|
||||
}
|
||||
8
src/component/buttonGroup/index.ts
Normal file
8
src/component/buttonGroup/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import type { App } from "vue";
|
||||
import Component from "./index.vue";
|
||||
|
||||
Component.install = (app: App) => {
|
||||
app.component(Component.name, Component);
|
||||
};
|
||||
|
||||
export default Component;
|
||||
15
src/component/buttonGroup/index.vue
Normal file
15
src/component/buttonGroup/index.vue
Normal file
@@ -0,0 +1,15 @@
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: "LayButtonGroup",
|
||||
};
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import "./index.less";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="layui-btn-group">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
24
src/component/card/index.less
Normal file
24
src/component/card/index.less
Normal file
@@ -0,0 +1,24 @@
|
||||
@import "../../theme/variable.less";
|
||||
|
||||
.layui-card {
|
||||
margin-bottom: 15px;
|
||||
border-radius: @card-border-radius;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
||||
.layui-card-header {
|
||||
height: 42px;
|
||||
line-height: 42px;
|
||||
padding: 0 15px;
|
||||
border-bottom: 1px solid #f6f6f6;
|
||||
color: #333;
|
||||
font-size: 14px;
|
||||
}
|
||||
.layui-card-body {
|
||||
padding: 10px 15px;
|
||||
line-height: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.layui-card:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
8
src/component/card/index.ts
Normal file
8
src/component/card/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import type { App } from "vue";
|
||||
import Component from "./index.vue";
|
||||
|
||||
Component.install = (app: App) => {
|
||||
app.component(Component.name, Component);
|
||||
};
|
||||
|
||||
export default Component;
|
||||
31
src/component/card/index.vue
Normal file
31
src/component/card/index.vue
Normal file
@@ -0,0 +1,31 @@
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: "LayCard"
|
||||
}
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useSlots } from "vue";
|
||||
import "./index.less";
|
||||
|
||||
const slot = useSlots();
|
||||
|
||||
export interface LayCardProps {
|
||||
title?: string;
|
||||
}
|
||||
|
||||
const props = defineProps<LayCardProps>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="layui-card">
|
||||
<div class="layui-card-header" v-if="slot.header || title">
|
||||
<slot name="header" v-if="slot.header"></slot>
|
||||
<span v-else>{{ title }}</span>
|
||||
</div>
|
||||
<div class="layui-card-body">
|
||||
<slot name="body" v-if="slot.body"></slot>
|
||||
<slot v-else></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
8
src/component/carousel/index.ts
Normal file
8
src/component/carousel/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import type { App } from "vue";
|
||||
import Component from "./index.vue";
|
||||
|
||||
Component.install = (app: App) => {
|
||||
app.component(Component.name, Component);
|
||||
};
|
||||
|
||||
export default Component;
|
||||
116
src/component/carousel/index.vue
Normal file
116
src/component/carousel/index.vue
Normal file
@@ -0,0 +1,116 @@
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: "LayCarousel"
|
||||
}
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
withDefaults,
|
||||
defineProps,
|
||||
provide,
|
||||
useSlots,
|
||||
ref,
|
||||
computed,
|
||||
} from "vue";
|
||||
|
||||
const slot = useSlots() as any;
|
||||
const slots = slot.default && (slot.default() as any[]);
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
width?: string;
|
||||
height?: string;
|
||||
modelValue: string;
|
||||
anim?: string;
|
||||
arrow?: string;
|
||||
indicator?: string;
|
||||
}>(),
|
||||
{
|
||||
width: "100%",
|
||||
height: "280px",
|
||||
anim: "default",
|
||||
arrow: "hover",
|
||||
indicator: "inside",
|
||||
}
|
||||
);
|
||||
|
||||
const active = computed({
|
||||
get() {
|
||||
return props.modelValue;
|
||||
},
|
||||
set(val) {
|
||||
emit("update:modelValue", val);
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(["update:modelValue", "change"]);
|
||||
|
||||
const change = function (id: any) {
|
||||
emit("change", id);
|
||||
active.value = id;
|
||||
};
|
||||
|
||||
provide("active", active);
|
||||
|
||||
const prev = function () {
|
||||
for (var i = 0; i < slots.length; i++) {
|
||||
if (slots[i].props.id === active.value) {
|
||||
if (i === 0) {
|
||||
active.value = slots[slots.length - 1].props.id;
|
||||
}
|
||||
active.value = slots[i - 1].props.id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const next = function () {
|
||||
for (var i = 0; i < slots.length; i++) {
|
||||
if (slots[i].props.id === active.value) {
|
||||
if (i === slots.length - 1) {
|
||||
active.value = slots[0].props.id;
|
||||
}
|
||||
active.value = slots[i + 1].props.id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="layui-carousel"
|
||||
:lay-anim="anim"
|
||||
:lay-indicator="indicator"
|
||||
:lay-arrow="arrow"
|
||||
:style="{ width: width, height: height }"
|
||||
>
|
||||
<div carousel-item>
|
||||
<slot></slot>
|
||||
</div>
|
||||
<div class="layui-carousel-ind">
|
||||
<ul>
|
||||
<li
|
||||
v-for="ss in slots"
|
||||
:key="ss"
|
||||
:class="[ss.props.id === active ? 'layui-this' : '']"
|
||||
@click.stop="change(ss.props.id)"
|
||||
></li>
|
||||
</ul>
|
||||
</div>
|
||||
<button
|
||||
class="layui-icon layui-carousel-arrow"
|
||||
lay-type="sub"
|
||||
@click="prev"
|
||||
>
|
||||
{{ anim === "updown" ? "" : "" }}</button
|
||||
><button
|
||||
class="layui-icon layui-carousel-arrow"
|
||||
lay-type="add"
|
||||
@click="next"
|
||||
>
|
||||
{{ anim === "updown" ? "" : "" }}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
8
src/component/carouselItem/index.ts
Normal file
8
src/component/carouselItem/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import type { App } from "vue";
|
||||
import Component from "./index.vue";
|
||||
|
||||
Component.install = (app: App) => {
|
||||
app.component(Component.name, Component);
|
||||
};
|
||||
|
||||
export default Component;
|
||||
21
src/component/carouselItem/index.vue
Normal file
21
src/component/carouselItem/index.vue
Normal file
@@ -0,0 +1,21 @@
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: "LayCarouselItem"
|
||||
}
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineProps, inject } from "vue";
|
||||
|
||||
const props = defineProps<{
|
||||
id: string;
|
||||
}>();
|
||||
|
||||
const active = inject("active");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<li :class="[active === id ? 'layui-this' : '']">
|
||||
<slot></slot>
|
||||
</li>
|
||||
</template>
|
||||
0
src/component/checkbox/index.less
Normal file
0
src/component/checkbox/index.less
Normal file
8
src/component/checkbox/index.ts
Normal file
8
src/component/checkbox/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import type { App } from "vue";
|
||||
import Component from "./index.vue";
|
||||
|
||||
Component.install = (app: App) => {
|
||||
app.component(Component.name, Component);
|
||||
};
|
||||
|
||||
export default Component;
|
||||
111
src/component/checkbox/index.vue
Normal file
111
src/component/checkbox/index.vue
Normal file
@@ -0,0 +1,111 @@
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: "LayCheckbox",
|
||||
};
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, defineProps, inject } from "vue";
|
||||
import "./index.less";
|
||||
|
||||
export interface LayCheckboxProps {
|
||||
name?: string;
|
||||
skin?: string;
|
||||
label: string | object;
|
||||
modelValue?: boolean | Array<string | object>;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<LayCheckboxProps>(), {
|
||||
modelValue: false,
|
||||
disabled: false,
|
||||
});
|
||||
|
||||
const checkboxGroup: any = inject("checkboxGroup", {});
|
||||
|
||||
const isGroup = computed(() => {
|
||||
return (
|
||||
checkboxGroup != undefined && checkboxGroup?.name === "LayCheckboxGroup"
|
||||
);
|
||||
});
|
||||
|
||||
const emit = defineEmits(["update:modelValue", "change"]);
|
||||
|
||||
const isChecked = computed({
|
||||
get() {
|
||||
if (isGroup.value) {
|
||||
return checkboxGroup.modelValue.value.includes(props.label);
|
||||
} else {
|
||||
if (Array.isArray(props.modelValue)) {
|
||||
return props.modelValue.includes(props.label);
|
||||
} else {
|
||||
return props.modelValue;
|
||||
}
|
||||
}
|
||||
},
|
||||
set(val) {
|
||||
if (isGroup.value) {
|
||||
setGroupModelValue(val);
|
||||
} else {
|
||||
if (Array.isArray(props.modelValue)) {
|
||||
setArrayModelValue(val);
|
||||
} else {
|
||||
emit("change", val);
|
||||
emit("update:modelValue", val);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const arrayModelValue = computed(() => {
|
||||
if (Array.isArray(props.modelValue)) {
|
||||
return [...props.modelValue];
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
});
|
||||
|
||||
const setGroupModelValue = function (checked: any) {
|
||||
let groupModelValue = [...checkboxGroup.modelValue.value];
|
||||
if (!checked) {
|
||||
groupModelValue.splice(groupModelValue.indexOf(props.label), 1);
|
||||
} else {
|
||||
groupModelValue.push(props.label);
|
||||
}
|
||||
checkboxGroup.modelValue.value = groupModelValue;
|
||||
};
|
||||
|
||||
const setArrayModelValue = function (checked: any) {
|
||||
let arr = [...arrayModelValue.value];
|
||||
if (!checked) {
|
||||
arr.splice(arr.indexOf(props.label), 1);
|
||||
} else {
|
||||
arr.push(props.label);
|
||||
}
|
||||
emit("change", arr);
|
||||
emit("update:modelValue", arr);
|
||||
};
|
||||
|
||||
const handleClick = function () {
|
||||
if (!props.disabled) {
|
||||
isChecked.value = !isChecked.value;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span @click.stop="handleClick">
|
||||
<input type="checkbox" :name="name" :value="label" />
|
||||
<div
|
||||
class="layui-unselect layui-form-checkbox"
|
||||
:class="{
|
||||
'layui-checkbox-disbaled layui-disabled': disabled,
|
||||
'layui-form-checked': isChecked,
|
||||
}"
|
||||
:lay-skin="skin"
|
||||
>
|
||||
<span v-if="$slots?.default"><slot></slot></span>
|
||||
<i class="layui-icon layui-icon-ok"></i>
|
||||
</div>
|
||||
</span>
|
||||
</template>
|
||||
8
src/component/checkboxGroup/index.ts
Normal file
8
src/component/checkboxGroup/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import type { App } from "vue";
|
||||
import Component from "./index.vue";
|
||||
|
||||
Component.install = (app: App) => {
|
||||
app.component(Component.name, Component);
|
||||
};
|
||||
|
||||
export default Component;
|
||||
44
src/component/checkboxGroup/index.vue
Normal file
44
src/component/checkboxGroup/index.vue
Normal file
@@ -0,0 +1,44 @@
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: "LayCheckboxGroup",
|
||||
};
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, defineProps, provide, ref, watch } from "vue";
|
||||
import { Recordable } from "../../types";
|
||||
|
||||
export interface LayCheckboxGroupProps {
|
||||
modelValue?: Recordable[];
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<LayCheckboxGroupProps>(), {
|
||||
modelValue: () => [],
|
||||
});
|
||||
|
||||
const emit = defineEmits(["update:modelValue", "change"]);
|
||||
|
||||
const modelValue = ref(props.modelValue);
|
||||
|
||||
provide("checkboxGroup", { name: "LayCheckboxGroup", modelValue: modelValue });
|
||||
|
||||
watch(
|
||||
() => modelValue,
|
||||
(val) => {
|
||||
emit("change", modelValue.value);
|
||||
emit("update:modelValue", modelValue.value);
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(val) => (modelValue.value = val)
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="layui-checkbox-group">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
8
src/component/col/index.ts
Normal file
8
src/component/col/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import type { App } from "vue";
|
||||
import Component from "./index.vue";
|
||||
|
||||
Component.install = (app: App) => {
|
||||
app.component(Component.name, Component);
|
||||
};
|
||||
|
||||
export default Component;
|
||||
39
src/component/col/index.vue
Normal file
39
src/component/col/index.vue
Normal file
@@ -0,0 +1,39 @@
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: "LayCol",
|
||||
};
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, defineProps } from "vue";
|
||||
|
||||
const props = defineProps<{
|
||||
md?: string;
|
||||
xs?: string;
|
||||
sm?: string;
|
||||
lg?: string;
|
||||
mdOffset?: string;
|
||||
xsOffset?: string;
|
||||
smOffset?: string;
|
||||
lgOffset?: string;
|
||||
}>();
|
||||
|
||||
const classes = computed(() => {
|
||||
return [
|
||||
props.md ? `layui-col-md${props.md}` : "",
|
||||
props.xs ? `layui-col-xs${props.xs}` : "",
|
||||
props.sm ? `layui-col-sm${props.sm}` : "",
|
||||
props.lg ? `layui-col-lg${props.lg}` : "",
|
||||
props.mdOffset ? `layui-col-md-offset${props.mdOffset}` : "",
|
||||
props.xsOffset ? `layui-col-xs-offset${props.xsOffset}` : "",
|
||||
props.smOffset ? `layui-col-sm-offset${props.smOffset}` : "",
|
||||
props.lgOffset ? `layui-col-lg-offset${props.lgOffset}` : "",
|
||||
];
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="layui-col" :class="classes">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
8
src/component/collapse/index.ts
Normal file
8
src/component/collapse/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import type { App } from "vue";
|
||||
import Component from "./index.vue";
|
||||
|
||||
Component.install = (app: App) => {
|
||||
app.component(Component.name, Component);
|
||||
};
|
||||
|
||||
export default Component;
|
||||
50
src/component/collapse/index.vue
Normal file
50
src/component/collapse/index.vue
Normal file
@@ -0,0 +1,50 @@
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name:"LayCollapse"
|
||||
}
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
withDefaults,
|
||||
defineProps,
|
||||
provide,
|
||||
ref,
|
||||
defineEmits,
|
||||
watch,
|
||||
} from "vue";
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
modelValue?: number | string | [];
|
||||
accordion?: boolean;
|
||||
}>(),
|
||||
{
|
||||
modelValue: () => [],
|
||||
accordion: false,
|
||||
}
|
||||
);
|
||||
|
||||
// 监听传入的值
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(val, oldVal) => {
|
||||
activeValues.value = ([] as any[]).concat(val);
|
||||
}
|
||||
);
|
||||
const emit = defineEmits(["update:modelValue", "change"]);
|
||||
|
||||
const activeValues = ref<Array<any>>(([] as any[]).concat(props.modelValue));
|
||||
|
||||
provide("layCollapse", {
|
||||
accordion: props.accordion,
|
||||
activeValues,
|
||||
emit,
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="layui-collapse">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
8
src/component/collapseItem/index.ts
Normal file
8
src/component/collapseItem/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import type { App } from "vue";
|
||||
import Component from "./index.vue";
|
||||
|
||||
Component.install = (app: App) => {
|
||||
app.component(Component.name, Component);
|
||||
};
|
||||
|
||||
export default Component;
|
||||
66
src/component/collapseItem/index.vue
Normal file
66
src/component/collapseItem/index.vue
Normal file
@@ -0,0 +1,66 @@
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name:"LayCollapseItem"
|
||||
}
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { withDefaults, defineProps, inject, computed, ref } from "vue";
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
id: number | string;
|
||||
title: string;
|
||||
disabled?: boolean;
|
||||
}>(),
|
||||
{
|
||||
disabled: false,
|
||||
}
|
||||
);
|
||||
|
||||
const { accordion, activeValues, emit } = inject("layCollapse") as any;
|
||||
|
||||
let isShow = computed(() => {
|
||||
return activeValues.value.includes(props.id);
|
||||
});
|
||||
|
||||
const showHandle = function () {
|
||||
if (props.disabled) {
|
||||
return;
|
||||
}
|
||||
const _isShow = isShow.value;
|
||||
// 手风琴效果
|
||||
if (accordion) {
|
||||
activeValues.value = !_isShow ? [props.id] : [];
|
||||
} else if (_isShow) {
|
||||
// 普通折叠面板 --> 折叠
|
||||
activeValues.value.splice(activeValues.value.indexOf(props.id), 1);
|
||||
} else {
|
||||
// 普通折叠面板 --> 展开
|
||||
activeValues.value.push(props.id);
|
||||
}
|
||||
|
||||
emit(
|
||||
"update:modelValue",
|
||||
accordion ? activeValues.value[0] || null : activeValues.value
|
||||
);
|
||||
emit("change", props.id, !_isShow, activeValues.value);
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="layui-colla-item">
|
||||
<h2
|
||||
:class="['layui-colla-title', { 'layui-disabled': disabled }]"
|
||||
@click="showHandle"
|
||||
>
|
||||
<slot name="title" :props="props">{{ title }}</slot>
|
||||
<i class="layui-icon layui-colla-icon">{{ isShow ? "" : "" }}</i>
|
||||
</h2>
|
||||
<div class="layui-colla-content" :class="isShow ? 'layui-show' : ''">
|
||||
<p>
|
||||
<slot :props="props"></slot>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
113
src/component/colorPicker/ColorBox.vue
Normal file
113
src/component/colorPicker/ColorBox.vue
Normal file
@@ -0,0 +1,113 @@
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: "ColorBox",
|
||||
};
|
||||
</script>
|
||||
<script setup lang="ts">
|
||||
import { Nullable } from "../type";
|
||||
import { computed, onMounted, ref } from "vue";
|
||||
import { HSBToHEX, RGBSTo, RGBToHSB } from "./colorUtil";
|
||||
import ColorPicker from "./ColorPicker.vue";
|
||||
import { usePosition } from "@layui/hooks-vue";
|
||||
|
||||
interface BoxProps {
|
||||
color?: string;
|
||||
size?: Nullable<string>;
|
||||
alpha?: boolean;
|
||||
format?: "hex" | "rgb";
|
||||
predefine?: boolean;
|
||||
colors?: string[];
|
||||
}
|
||||
|
||||
const colorBoxProps = withDefaults(defineProps<BoxProps>(), {
|
||||
color: "",
|
||||
size: () => null,
|
||||
alpha: false,
|
||||
format: "hex",
|
||||
predefine: false,
|
||||
colors: () => [
|
||||
//默认预定义颜色列表
|
||||
"#009688",
|
||||
"#5FB878",
|
||||
"#1E9FFF",
|
||||
"#FF5722",
|
||||
"#FFB800",
|
||||
"#01AAED",
|
||||
"#999",
|
||||
"#c00",
|
||||
"#ff8c00",
|
||||
"#ffd700",
|
||||
"#90ee90",
|
||||
"#00ced1",
|
||||
"#1e90ff",
|
||||
"#c71585",
|
||||
"rgb(0, 186, 189)",
|
||||
"rgb(255, 120, 0)",
|
||||
"rgb(250, 212, 0)",
|
||||
"#393D49",
|
||||
"rgba(0,0,0,.5)",
|
||||
"rgba(255, 69, 0, 0.68)",
|
||||
"rgba(144, 240, 144, 0.5)",
|
||||
"rgba(31, 147, 255, 0.73)",
|
||||
],
|
||||
});
|
||||
|
||||
const triggerSpanStyle = computed(() => {
|
||||
let bgstr = "";
|
||||
if (colorBoxProps.color) {
|
||||
bgstr = colorBoxProps.color;
|
||||
|
||||
if ((colorBoxProps.color.match(/[0-9]{1,3}/g) || []).length > 3) {
|
||||
//需要优化
|
||||
if (!(colorBoxProps.alpha && colorBoxProps.format == "rgb")) {
|
||||
bgstr = "#" + HSBToHEX(RGBToHSB(RGBSTo(colorBoxProps.color)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
background: bgstr,
|
||||
};
|
||||
});
|
||||
|
||||
const colorPickerWrapper = computed(() => {
|
||||
return colorBoxProps.size ? `layui-colorpicker-${colorBoxProps.size}` : "";
|
||||
});
|
||||
|
||||
const colorBoxRefEl = ref<HTMLElement | null>(null);
|
||||
const colorPickerRefEl = ref<HTMLElement | null>(null);
|
||||
|
||||
onMounted(() => {
|
||||
console.log("colorPickerRefEl =>>>", colorPickerRefEl.value.teleportRefEl);
|
||||
usePosition(colorBoxRefEl.value, colorPickerRefEl.value.teleportRefEl);
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<div ref="colorBoxRefEl" class="layui-unselect layui-colorpicker">
|
||||
<span
|
||||
:class="[
|
||||
{
|
||||
'layui-colorpicker-trigger-bgcolor': format == 'rgb' && alpha,
|
||||
},
|
||||
size ? colorPickerWrapper : '',
|
||||
]"
|
||||
>
|
||||
<span class="layui-colorpicker-trigger-span" :style="triggerSpanStyle">
|
||||
<!-- ICON_PICKER_DOWN = 'layui-icon-down', ICON_PICKER_CLOSE = 'layui-icon-close' -->
|
||||
<i
|
||||
:class="[
|
||||
'layui-icon layui-colorpicker-trigger-i',
|
||||
color ? 'layui-icon-down' : 'layui-icon-close',
|
||||
]"
|
||||
></i>
|
||||
</span>
|
||||
</span>
|
||||
<ColorPicker
|
||||
ref="colorPickerRefEl"
|
||||
:visible="true"
|
||||
:alpha="alpha"
|
||||
:predefine="predefine"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<style scoped lang="less"></style>
|
||||
91
src/component/colorPicker/ColorPicker.vue
Normal file
91
src/component/colorPicker/ColorPicker.vue
Normal file
@@ -0,0 +1,91 @@
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: "ColorPicker",
|
||||
};
|
||||
</script>
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
|
||||
interface CProps {
|
||||
visible: boolean;
|
||||
alpha: boolean;
|
||||
predefine: boolean;
|
||||
}
|
||||
|
||||
const props = defineProps<CProps>();
|
||||
|
||||
const domRefEl = ref<HTMLElement | null>(null);
|
||||
defineExpose({
|
||||
teleportRefEl: domRefEl,
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<teleport to="body">
|
||||
<div
|
||||
v-if="visible"
|
||||
ref="domRefEl"
|
||||
class="layui-anim layui-anim-downbit layui-colorpicker-main"
|
||||
>
|
||||
<!-- //颜色面板-->
|
||||
<div class="layui-colorpicker-main-wrapper">
|
||||
<div class="layui-colorpicker-basis">
|
||||
<div class="layui-colorpicker-basis-white"></div>
|
||||
<div class="layui-colorpicker-basis-black"></div>
|
||||
<div class="layui-colorpicker-basis-cursor"></div>
|
||||
</div>
|
||||
<div class="layui-colorpicker-side">
|
||||
<div class="layui-colorpicker-side-slider"></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- //透明度条块-->
|
||||
<div
|
||||
:class="[
|
||||
{
|
||||
'layui-colorpicker-main-alpha': true,
|
||||
'layui-show': alpha,
|
||||
},
|
||||
]"
|
||||
>
|
||||
<div class="layui-colorpicker-alpha-bgcolor">
|
||||
<div class="layui-colorpicker-alpha-slider"></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- //预设颜色列表-->
|
||||
<div v-if="predefine" class="layui-colorpicker-main-pre">
|
||||
<div
|
||||
v-for="c in colors"
|
||||
:key="c"
|
||||
:class="{
|
||||
'layui-colorpicker-pre': true,
|
||||
'layui-colorpicker-pre-isalpha':
|
||||
(c.match(/[0-9]{1,3}/g) || []).length > 3,
|
||||
}"
|
||||
>
|
||||
<div :style="{ background: c }"></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- //底部表单元素区域-->
|
||||
<div class="layui-colorpicker-main-input">
|
||||
<div class="layui-inline">
|
||||
<input type="text" class="layui-input" />
|
||||
</div>
|
||||
<div class="layui-btn-container">
|
||||
<button
|
||||
class="layui-btn layui-btn-primary layui-btn-sm"
|
||||
colorpicker-events="clear"
|
||||
>
|
||||
清空
|
||||
</button>
|
||||
<button
|
||||
class="layui-btn layui-btn-sm"
|
||||
colorpicker-events="confirm"
|
||||
type="submit"
|
||||
>
|
||||
确定
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</teleport>
|
||||
</template>
|
||||
<style scoped lang="less"></style>
|
||||
34
src/component/colorPicker/colorPicker.type.ts
Normal file
34
src/component/colorPicker/colorPicker.type.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
export interface ColorPickerProps {
|
||||
/**
|
||||
* 默认颜色,不管你是使用 hex、rgb 还是 rgba 的格式输入,最终会以指定的格式显示。
|
||||
* v-model:color
|
||||
*/
|
||||
color: string;
|
||||
/**
|
||||
* 颜色显示/输入格式,可选值: hex、rgb
|
||||
* 若在 rgb 格式下开启了透明度,格式会自动变成 rgba。在没有输入颜色的前提下,组件会默认为 #000 也就是黑色。
|
||||
* default: hex(即 16 进制色值)
|
||||
*/
|
||||
format: "hex" | "rgb";
|
||||
/**
|
||||
* 是否开启透明度,若不开启,则不会显示透明框。开启了透明度选项时,当你的默认颜色为 hex 或 rgb 格式,
|
||||
* 组件会默认加上值为 1 的透明度。相同的,当你没有开启透明度,却以 rgba 格式设置默认颜色时,组件会默认没有透明度。
|
||||
* 注意:该参数必须配合 rgba 颜色值使用
|
||||
* default: false
|
||||
*/
|
||||
alpha: boolean;
|
||||
/**
|
||||
* 预定义颜色是否开启
|
||||
* default: false
|
||||
*/
|
||||
predefine: boolean;
|
||||
/**
|
||||
* 预定义颜色,此参数需配合 predefine: true 使用。
|
||||
* 此处列举一部分:['#ff4500','#1e90ff','rgba(255, 69, 0, 0.68)','rgb(255, 120, 0)']
|
||||
*/
|
||||
colors: string[];
|
||||
/**
|
||||
* 下拉框大小,可以选择:lg、sm、xs。
|
||||
*/
|
||||
size: "lg" | "sm" | "xs";
|
||||
}
|
||||
113
src/component/colorPicker/colorUtil.ts
Normal file
113
src/component/colorPicker/colorUtil.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
export interface RGB {
|
||||
h: number;
|
||||
s: number;
|
||||
b: number;
|
||||
}
|
||||
|
||||
// RGB转HSB
|
||||
export function RGBToHSB(rgb: any) {
|
||||
const hsb = { h: 0, s: 0, b: 0 };
|
||||
const min = Math.min(rgb.r, rgb.g, rgb.b);
|
||||
const max = Math.max(rgb.r, rgb.g, rgb.b);
|
||||
const delta = max - min;
|
||||
hsb.b = max;
|
||||
hsb.s = max != 0 ? (255 * delta) / max : 0;
|
||||
if (hsb.s != 0) {
|
||||
if (rgb.r == max) {
|
||||
hsb.h = (rgb.g - rgb.b) / delta;
|
||||
} else if (rgb.g == max) {
|
||||
hsb.h = 2 + (rgb.b - rgb.r) / delta;
|
||||
} else {
|
||||
hsb.h = 4 + (rgb.r - rgb.g) / delta;
|
||||
}
|
||||
} else {
|
||||
hsb.h = -1;
|
||||
}
|
||||
if (max == min) {
|
||||
hsb.h = 0;
|
||||
}
|
||||
hsb.h *= 60;
|
||||
if (hsb.h < 0) {
|
||||
hsb.h += 360;
|
||||
}
|
||||
hsb.s *= 100 / 255;
|
||||
hsb.b *= 100 / 255;
|
||||
return hsb;
|
||||
}
|
||||
|
||||
// HEX转HSB
|
||||
export function HEXToHSB(hex: any) {
|
||||
hex = hex.indexOf("#") > -1 ? hex.substring(1) : hex;
|
||||
if (hex.length == 3) {
|
||||
const num = hex.split("");
|
||||
hex = num[0] + num[0] + num[1] + num[1] + num[2] + num[2];
|
||||
}
|
||||
hex = parseInt(hex, 16);
|
||||
const rgb = { r: hex >> 16, g: (hex & 0x00ff00) >> 8, b: hex & 0x0000ff };
|
||||
return RGBToHSB(rgb);
|
||||
}
|
||||
|
||||
// HSB转RGB
|
||||
export function HSBToRGB(hsb: any) {
|
||||
const rgb: any = {};
|
||||
let h = hsb.h;
|
||||
const s = (hsb.s * 255) / 100;
|
||||
const b = (hsb.b * 255) / 100;
|
||||
if (s == 0) {
|
||||
rgb.r = rgb.g = rgb.b = b;
|
||||
} else {
|
||||
const t1 = b;
|
||||
const t2 = ((255 - s) * b) / 255;
|
||||
const t3 = ((t1 - t2) * (h % 60)) / 60;
|
||||
if (h == 360) h = 0;
|
||||
if (h < 60) {
|
||||
rgb.r = t1;
|
||||
rgb.b = t2;
|
||||
rgb.g = t2 + t3;
|
||||
} else if (h < 120) {
|
||||
rgb.g = t1;
|
||||
rgb.b = t2;
|
||||
rgb.r = t1 - t3;
|
||||
} else if (h < 180) {
|
||||
rgb.g = t1;
|
||||
rgb.r = t2;
|
||||
rgb.b = t2 + t3;
|
||||
} else if (h < 240) {
|
||||
rgb.b = t1;
|
||||
rgb.r = t2;
|
||||
rgb.g = t1 - t3;
|
||||
} else if (h < 300) {
|
||||
rgb.b = t1;
|
||||
rgb.g = t2;
|
||||
rgb.r = t2 + t3;
|
||||
} else if (h < 360) {
|
||||
rgb.r = t1;
|
||||
rgb.g = t2;
|
||||
rgb.b = t1 - t3;
|
||||
} else {
|
||||
rgb.r = 0;
|
||||
rgb.g = 0;
|
||||
rgb.b = 0;
|
||||
}
|
||||
}
|
||||
return { r: Math.round(rgb.r), g: Math.round(rgb.g), b: Math.round(rgb.b) };
|
||||
}
|
||||
|
||||
// HSB转HEX
|
||||
export function HSBToHEX(hsb: any) {
|
||||
const rgb = HSBToRGB(hsb);
|
||||
const hex = [rgb.r.toString(16), rgb.g.toString(16), rgb.b.toString(16)];
|
||||
hex.forEach((val, nr) => {
|
||||
if (val.length == 1) {
|
||||
hex[nr] = "0" + val;
|
||||
}
|
||||
});
|
||||
return hex.join("");
|
||||
}
|
||||
|
||||
//转化成所需rgb格式
|
||||
export function RGBSTo(rgbs: any) {
|
||||
const regexp = /[0-9]{1,3}/g;
|
||||
const re = rgbs.match(regexp) || [];
|
||||
return { r: re[0], g: re[1], b: re[2] };
|
||||
}
|
||||
9
src/component/colorPicker/index.ts
Normal file
9
src/component/colorPicker/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import type { App } from "vue";
|
||||
import Component from "./index.vue";
|
||||
import type { IDefineComponent } from "../../types/index";
|
||||
|
||||
Component.install = (app: App) => {
|
||||
app.component(Component.name || "LayColorPicker", Component);
|
||||
};
|
||||
|
||||
export default Component as IDefineComponent;
|
||||
58
src/component/colorPicker/index.vue
Normal file
58
src/component/colorPicker/index.vue
Normal file
@@ -0,0 +1,58 @@
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: "LayColorPicker",
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { Nullable } from "/@src/module/type";
|
||||
import ColorBox from "./ColorBox.vue";
|
||||
|
||||
interface ColorPickerProps {
|
||||
color?: string;
|
||||
size?: Nullable<string>;
|
||||
alpha?: boolean;
|
||||
format?: "hex" | "rgb";
|
||||
predefine?: boolean;
|
||||
colors?: string[];
|
||||
}
|
||||
|
||||
const colorPickerProps = withDefaults(defineProps<ColorPickerProps>(), {
|
||||
color: "",
|
||||
size: () => null,
|
||||
alpha: false,
|
||||
format: "hex",
|
||||
predefine: false,
|
||||
colors: () => [
|
||||
//默认预定义颜色列表
|
||||
"#009688",
|
||||
"#5FB878",
|
||||
"#1E9FFF",
|
||||
"#FF5722",
|
||||
"#FFB800",
|
||||
"#01AAED",
|
||||
"#999",
|
||||
"#c00",
|
||||
"#ff8c00",
|
||||
"#ffd700",
|
||||
"#90ee90",
|
||||
"#00ced1",
|
||||
"#1e90ff",
|
||||
"#c71585",
|
||||
"rgb(0, 186, 189)",
|
||||
"rgb(255, 120, 0)",
|
||||
"rgb(250, 212, 0)",
|
||||
"#393D49",
|
||||
"rgba(0,0,0,.5)",
|
||||
"rgba(255, 69, 0, 0.68)",
|
||||
"rgba(144, 240, 144, 0.5)",
|
||||
"rgba(31, 147, 255, 0.73)",
|
||||
],
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="layui-inline'">
|
||||
<ColorBox />
|
||||
</div>
|
||||
</template>
|
||||
12
src/component/container/index.less
Normal file
12
src/component/container/index.less
Normal file
@@ -0,0 +1,12 @@
|
||||
.layui-container {
|
||||
position: relative;
|
||||
margin: 0 auto;
|
||||
padding: 0 15px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.layui-fluid {
|
||||
position: relative;
|
||||
margin: 0 auto;
|
||||
padding: 0 15px;
|
||||
}
|
||||
8
src/component/container/index.ts
Normal file
8
src/component/container/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import type { App } from "vue";
|
||||
import Component from "./index.vue";
|
||||
|
||||
Component.install = (app: App) => {
|
||||
app.component(Component.name, Component);
|
||||
};
|
||||
|
||||
export default Component;
|
||||
28
src/component/container/index.vue
Normal file
28
src/component/container/index.vue
Normal file
@@ -0,0 +1,28 @@
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: "LayContainer",
|
||||
};
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, defineProps } from "vue";
|
||||
import "./index.less";
|
||||
|
||||
export interface LayContainerProps {
|
||||
fluid?: boolean;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<LayContainerProps>(), {
|
||||
fluid: false,
|
||||
});
|
||||
|
||||
const classes = computed(() =>
|
||||
props.fluid ? "layui-fluid" : "layui-container"
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="classes">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
8
src/component/countUp/index.ts
Normal file
8
src/component/countUp/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import type { App } from "vue";
|
||||
import Component from "./index.vue";
|
||||
|
||||
Component.install = (app: App) => {
|
||||
app.component(Component.name, Component);
|
||||
};
|
||||
|
||||
export default Component;
|
||||
80
src/component/countUp/index.vue
Normal file
80
src/component/countUp/index.vue
Normal file
@@ -0,0 +1,80 @@
|
||||
<template>
|
||||
<slot name="prefix"></slot>
|
||||
<span ref="counterRef" style="font-family:sans-serif;" />
|
||||
<slot name="suffix"></slot>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: "LayCountUp",
|
||||
};
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref, watch } from 'vue'
|
||||
import { CountUp } from 'countup.js'
|
||||
import type { CountUpOptions } from 'countup.js'
|
||||
|
||||
export interface LayCountupProps {
|
||||
endVal?: number; //显示的值
|
||||
decimalPlaces?: number; // 小数位数
|
||||
useGrouping?: boolean; // 是否使用千位分隔符
|
||||
separator?: string; // 千位分隔符
|
||||
useEasing?: boolean; // 使用动画
|
||||
duration?: number; // 动画持续时间
|
||||
prefix?: string; // 前缀
|
||||
suffix?: string; // 后缀
|
||||
option?: CountUpOptions; // 选项
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<LayCountupProps>(), {
|
||||
endVal: 0,
|
||||
option: () => {
|
||||
return {}
|
||||
}
|
||||
});
|
||||
|
||||
const counterRef = ref<HTMLDivElement | null>(null);
|
||||
const instance = ref<CountUp | null>(null);
|
||||
const {decimalPlaces,useGrouping,separator,useEasing,duration,prefix,suffix} = props;
|
||||
const defaultOptions: CountUpOptions = {
|
||||
startVal: 0, // 开始数字
|
||||
decimalPlaces: decimalPlaces ? decimalPlaces : 0, // 小数位数
|
||||
useEasing: useEasing ? useEasing : true, // 使用缓动动画
|
||||
duration: duration ? duration : 2, // 动画持续时间
|
||||
useGrouping: useGrouping ? useGrouping : true, // 是否使用千位分隔符
|
||||
separator:separator ? separator : ",", // 千位分隔符
|
||||
decimal:".", // 小数点分隔符
|
||||
prefix: prefix ? prefix : "", // 前缀
|
||||
suffix: suffix ? suffix : "", // 后缀
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.endVal,
|
||||
() => {
|
||||
update(props.endVal)
|
||||
}
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
createCounter()
|
||||
})
|
||||
|
||||
const createCounter = () => {
|
||||
if (!counterRef.value) return
|
||||
const opts: CountUpOptions = Object.assign(defaultOptions, props.option)
|
||||
instance.value = new CountUp(counterRef?.value, props.endVal, opts);
|
||||
start();
|
||||
}
|
||||
|
||||
const start = () => {
|
||||
if (!instance.value) return
|
||||
instance?.value.start();
|
||||
}
|
||||
|
||||
const update = (newEndVal: number) => {
|
||||
if (!instance.value) return
|
||||
instance?.value.update(newEndVal);
|
||||
}
|
||||
|
||||
</script>
|
||||
8
src/component/dropdown/index.ts
Normal file
8
src/component/dropdown/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import type { App } from "vue";
|
||||
import Component from "./index.vue";
|
||||
|
||||
Component.install = (app: App) => {
|
||||
app.component(Component.name, Component);
|
||||
};
|
||||
|
||||
export default Component;
|
||||
79
src/component/dropdown/index.vue
Normal file
79
src/component/dropdown/index.vue
Normal file
@@ -0,0 +1,79 @@
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: "LayDropdown",
|
||||
};
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineProps, provide, ref, watch } from "vue";
|
||||
import { useClickOutside } from "@layui/hooks-vue";
|
||||
|
||||
const dropdownRef = ref<null | HTMLElement>(null);
|
||||
const isClickOutside = useClickOutside(dropdownRef);
|
||||
|
||||
export interface LayDropdownProps {
|
||||
trigger?: string;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<LayDropdownProps>(), {
|
||||
trigger: "click",
|
||||
});
|
||||
|
||||
const openState = ref(false);
|
||||
|
||||
const open = function () {
|
||||
openState.value = true;
|
||||
};
|
||||
|
||||
const hide = function () {
|
||||
openState.value = false;
|
||||
};
|
||||
|
||||
const toggle = function () {
|
||||
openState.value = !openState.value;
|
||||
};
|
||||
|
||||
watch(isClickOutside, () => {
|
||||
if (isClickOutside.value) {
|
||||
openState.value = false;
|
||||
}
|
||||
});
|
||||
|
||||
provide("openState", openState);
|
||||
|
||||
defineExpose({ open, hide, toggle });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
v-if="trigger === 'click'"
|
||||
ref="dropdownRef"
|
||||
class="layui-dropdown"
|
||||
:class="[openState ? 'layui-dropdown-up' : '']"
|
||||
>
|
||||
<div @click="toggle">
|
||||
<slot></slot>
|
||||
</div>
|
||||
<dl class="layui-anim layui-anim-upbit">
|
||||
<ul class="layui-menu layui-dropdown-menu">
|
||||
<slot name="content"></slot>
|
||||
</ul>
|
||||
</dl>
|
||||
</div>
|
||||
<div
|
||||
v-if="trigger === 'hover'"
|
||||
class="layui-dropdown"
|
||||
:class="[openState ? 'layui-dropdown-up' : '']"
|
||||
@mouseenter="open"
|
||||
@mouseleave="hide"
|
||||
>
|
||||
<div>
|
||||
<slot></slot>
|
||||
</div>
|
||||
<dl class="layui-anim layui-anim-upbit">
|
||||
<ul class="layui-menu layui-dropdown-menu">
|
||||
<slot name="content"></slot>
|
||||
</ul>
|
||||
</dl>
|
||||
</div>
|
||||
</template>
|
||||
8
src/component/dropdownItem/index.ts
Normal file
8
src/component/dropdownItem/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import type { App } from "vue";
|
||||
import Component from "./index.vue";
|
||||
|
||||
Component.install = (app: App) => {
|
||||
app.component(Component.name, Component);
|
||||
};
|
||||
|
||||
export default Component;
|
||||
23
src/component/dropdownItem/index.vue
Normal file
23
src/component/dropdownItem/index.vue
Normal file
@@ -0,0 +1,23 @@
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: "LayDropdownItem"
|
||||
}
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { inject, Ref } from "vue";
|
||||
|
||||
const openState: Ref<boolean> = inject("openState") as Ref<boolean>;
|
||||
|
||||
const click = function () {
|
||||
openState.value = false;
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<li>
|
||||
<div class="layui-menu-body-title" @click="click">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</li>
|
||||
</template>
|
||||
20
src/component/empty/index.less
Normal file
20
src/component/empty/index.less
Normal file
@@ -0,0 +1,20 @@
|
||||
.layui-empty {
|
||||
margin: 0 8px;
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.layui-empty-image {
|
||||
height: 100px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.layui-empty-image img {
|
||||
height: 100%;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.layui-empty-description {
|
||||
margin: 0;
|
||||
}
|
||||
48
src/component/empty/index.svg
Normal file
48
src/component/empty/index.svg
Normal file
@@ -0,0 +1,48 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="620px" height="200px" viewBox="0 0 620 200" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>Group 35</title>
|
||||
<defs>
|
||||
<linearGradient x1="100%" y1="28.3855365%" x2="50%" y2="60.1282768%" id="linearGradient-1">
|
||||
<stop stop-color="#A3B1BF" stop-opacity="0" offset="0%"></stop>
|
||||
<stop stop-color="#A3B1BF" offset="100%"></stop>
|
||||
</linearGradient>
|
||||
<linearGradient x1="-1.11022302e-14%" y1="50%" x2="100%" y2="50%" id="linearGradient-2">
|
||||
<stop stop-color="#A3B1BF" stop-opacity="0" offset="0%"></stop>
|
||||
<stop stop-color="#A3B1BF" offset="100%"></stop>
|
||||
</linearGradient>
|
||||
<linearGradient x1="100%" y1="50%" x2="2.77050217%" y2="50%" id="linearGradient-3">
|
||||
<stop stop-color="#A3B1BF" stop-opacity="0" offset="0%"></stop>
|
||||
<stop stop-color="#A3B1BF" offset="100%"></stop>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<g id="框架设计-过程版" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="缺省图" transform="translate(-1543.000000, -154.000000)">
|
||||
<g id="Group-35" transform="translate(1543.000000, 154.000000)">
|
||||
<g id="暂无数据" transform="translate(57.000000, 3.000000)">
|
||||
<path d="M127.424021,59.5388128 C126.606599,58.4063927 125.438263,57.4280114 123.958785,57.4280114 C123.344291,57.4280114 122.540774,57.5633407 121.976378,57.8001748 L121.286649,58.1531256 L121.120501,57.4280114 C120.642337,55.5707287 118.554686,53.8578054 116.339591,53.8578054 C114.440304,53.8578054 112.647282,54.7806864 111.893274,56.4567899 L111.685907,56.9188903 C110.879484,56.8390213 110.408842,56.7990868 110.273979,56.7990868 C108.807184,56.7990868 107.918237,58.1707763 107.760333,59.5388128 L127.424021,59.5388128 Z M116.256317,51.6240487 C118.838356,51.6240487 121.212177,53.0633181 122.428006,55.3281583 C122.702588,55.2940639 122.980213,55.2770167 123.257839,55.2770167 C126.194825,55.2770167 128.762253,57.1607306 129.646271,59.9643836 L129.674277,60.0538813 L129.674277,61.6371587 L91.9308053,61.6371587 L91.9308053,59.5388128 L105.610959,59.5388128 C105.763166,56.9263318 107.861796,54.8283105 110.474277,54.6767123 C111.779604,52.7589041 113.917199,51.6240487 116.256317,51.6240487 Z M98.0152207,55.2770167 L98.0152207,57.338946 L85.8386606,57.338946 L85.8386606,55.2770167 L98.0152207,55.2770167 Z" id="cloud4-copy" fill="#A3B1BF"></path>
|
||||
<path d="M41.5853608,128.914764 C40.7679381,127.782344 39.5996027,126.803963 38.1201246,126.803963 C37.5056307,126.803963 36.7021134,126.939292 36.1377176,127.176126 L35.4479887,127.529077 L35.2818407,126.803963 C34.8036768,124.94668 32.7160256,123.233757 30.5009305,123.233757 C28.6016434,123.233757 26.8086214,124.156638 26.0546139,125.832741 L25.847246,126.294842 C25.0408238,126.214973 24.5701811,126.175038 24.435318,126.175038 C22.968523,126.175038 22.0795768,127.546728 21.9216729,128.914764 L41.5853608,128.914764 Z M30.417656,121 C32.9996956,121 35.373516,122.439269 36.5893455,124.70411 C36.8639269,124.670015 37.1415525,124.652968 37.4191781,124.652968 C40.3561644,124.652968 42.9235921,126.536682 43.8076104,129.340335 L43.8356164,129.429833 L43.8356164,131.01311 L6.09214469,131.01311 L6.09214469,128.914764 L19.7722983,128.914764 C19.9245053,126.302283 22.0231355,124.204262 24.6356164,124.052664 C25.9409437,122.134855 28.0785388,121 30.417656,121 Z M12.1765601,124.652968 L12.1765601,126.714897 L1.19726451e-12,126.714897 L1.19726451e-12,124.652968 L12.1765601,124.652968 Z" id="cloud3" fill="#A3B1BF"></path>
|
||||
<path d="M503.585361,128.914764 C502.767938,127.782344 501.599603,126.803963 500.120125,126.803963 C499.505631,126.803963 498.702113,126.939292 498.137718,127.176126 L497.447989,127.529077 L497.281841,126.803963 C496.803677,124.94668 494.716026,123.233757 492.50093,123.233757 C490.601643,123.233757 488.808621,124.156638 488.054614,125.832741 L487.847246,126.294842 C487.040824,126.214973 486.570181,126.175038 486.435318,126.175038 C484.968523,126.175038 484.079577,127.546728 483.921673,128.914764 L503.585361,128.914764 Z M492.417656,121 C494.999696,121 497.373516,122.439269 498.589346,124.70411 C498.863927,124.670015 499.141553,124.652968 499.419178,124.652968 C502.356164,124.652968 504.923592,126.536682 505.80761,129.340335 L505.835616,129.429833 L505.835616,131.01311 L468.092145,131.01311 L468.092145,128.914764 L481.772298,128.914764 C481.924505,126.302283 484.023135,124.204262 486.635616,124.052664 C487.940944,122.134855 490.078539,121 492.417656,121 Z M474.17656,124.652968 L474.17656,126.714897 L462,126.714897 L462,124.652968 L474.17656,124.652968 Z" id="cloud3" fill="#A3B1BF"></path>
|
||||
<path d="M404.542857,12.7724191 C393.849559,45.4839352 372.154979,58.2098935 339.459117,50.950294 C277.116397,35.7637149 362.44716,-23.5985442 347.6627,53.8807932 C342.542621,80.7130415 317.479147,99.6801616 272.40272,99.6528527" id="Path-4" stroke="url(#linearGradient-1)" stroke-width="3" stroke-linecap="round" fill-rule="nonzero" transform="translate(338.472789, 56.212636) rotate(11.000000) translate(-338.472789, -56.212636) "></path>
|
||||
<path d="M362.000112,73.9497723 C362.670671,73.9497723 363.214076,73.4063457 363.214076,72.7358084 L363.214076,71.2139639 C363.214076,70.5434265 362.670649,70 362.000134,70 C361.329619,70 360.786125,70.5434489 360.786125,71.2139639 L360.786125,72.7358084 C360.786125,73.4063234 361.329574,73.9497723 362.000134,73.9497723 L362.000112,73.9497723 Z M362.000112,86.0502054 C361.329574,86.0502054 360.786148,86.5936543 360.786148,87.2641693 L360.786148,88.7859915 C360.786148,89.4565511 361.329597,89.9999777 362.000134,89.9999777 C362.670671,89.9999777 363.214076,89.4565288 363.214076,88.7859915 L363.214076,87.2641916 C363.214076,86.5936543 362.670649,86.0502054 362.000134,86.0502054 L362.000112,86.0502054 Z M370.786058,78.7861255 L369.264236,78.7861255 C368.593677,78.7861255 368.050429,79.3293733 368.050429,80.0000894 C368.050429,80.670448 368.593677,81.2138969 369.264236,81.2138969 L370.786058,81.2138969 C371.456596,81.2138969 372,80.670448 372,80.0000894 C372,79.3293733 371.456573,78.7861255 370.786058,78.7861255 L370.786058,78.7861255 Z M355.949795,80.0000894 C355.949795,79.3293733 355.406346,78.7861255 354.735808,78.7861255 L353.213986,78.7861255 C352.54403,78.7861255 352,79.3293733 352,80.0000894 C352,80.670448 352.54403,81.2138969 353.213986,81.2138969 L354.735808,81.2138969 C355.406368,81.2138969 355.949795,80.670448 355.949795,80.0000894 Z M367.995429,84.2784508 C367.521165,83.8043656 366.753139,83.8043656 366.278473,84.2784508 C365.804388,84.7527148 365.804388,85.5210984 366.278473,85.9951836 L367.354715,87.0710234 C367.591467,87.3081554 367.902029,87.4269336 368.213171,87.4269336 C368.523732,87.4269336 368.834115,87.3081778 369.071046,87.0710234 C369.545533,86.5967595 369.545533,85.8285769 369.071046,85.3544693 L367.995407,84.2784508 L367.995429,84.2784508 Z M356.004236,75.721929 C356.241569,75.959262 356.552733,76.0774371 356.863272,76.0774371 C357.174012,76.0774371 357.484395,75.959262 357.721728,75.7224874 C358.195813,75.2484022 358.195813,74.4798399 357.722108,74.0051961 L356.646089,72.9289542 C356.172384,72.4554721 355.40362,72.4554721 354.928954,72.9279936 C354.45469,73.4026597 354.45469,74.1712444 354.928574,74.6453073 L356.004236,75.721929 Z M356.004839,84.2784508 L354.928999,85.3544917 C354.454735,85.8285769 354.454735,86.5967818 354.928999,87.0710234 C355.165572,87.3081554 355.476715,87.4269336 355.787656,87.4269336 C356.097837,87.4269336 356.408801,87.3081778 356.645531,87.0710234 L357.72175,85.9951836 C358.195836,85.5210984 358.195836,84.7527595 357.72175,84.2784508 C357.247665,83.8043879 356.478343,83.8043879 356.004839,84.2784508 L356.004839,84.2784508 Z M367.13713,76.0774371 C367.447513,76.0774371 367.759057,75.959262 367.995787,75.721929 L369.071828,74.6453073 C369.545511,74.1712444 369.545511,73.4026597 369.071046,72.9279936 C368.596782,72.4554721 367.828421,72.4545115 367.353754,72.9289542 L366.278116,74.0051961 C365.80441,74.4798622 365.80441,75.2484245 366.278495,75.7224874 C366.515806,75.959262 366.826368,76.0774371 367.13713,76.0774371 Z" id="Shape" stroke="#A3B1BF" fill="#A3B1BF" fill-rule="nonzero" opacity="0.40327381"></path>
|
||||
<path d="M401.801829,22.0236541 C402.387615,22.6094406 402.387615,23.559188 401.801829,24.1449745 L400.033314,25.9123143 L401.801829,27.6805084 C402.387615,28.2662948 402.387615,29.2160423 401.801829,29.8018287 C401.216042,30.3876151 400.266295,30.3876151 399.680508,29.8018287 L397.912314,28.0333143 L396.144974,29.8018287 C395.559188,30.3876151 394.609441,30.3876151 394.023654,29.8018287 C393.437868,29.2160423 393.437868,28.2662948 394.023654,27.6805084 L395.791314,25.9123143 L394.023654,24.1449745 C393.437868,23.559188 393.437868,22.6094406 394.023654,22.0236541 C394.609441,21.4378677 395.559188,21.4378677 396.144974,22.0236541 L397.912314,23.7913143 L399.680508,22.0236541 C400.266295,21.4378677 401.216042,21.4378677 401.801829,22.0236541 Z" id="Combined-Shape-Copy-8" fill="#A3B1BF" fill-rule="nonzero" opacity="0.40327381" transform="translate(397.912741, 25.912741) rotate(30.000000) translate(-397.912741, -25.912741) "></path>
|
||||
<path d="M182.481192,112.703017 C183.066978,113.288804 183.066978,114.238551 182.481192,114.824337 L180.712677,116.591677 L182.481192,118.359871 C183.066978,118.945658 183.066978,119.895405 182.481192,120.481192 C181.895405,121.066978 180.945658,121.066978 180.359871,120.481192 L178.591677,118.712677 L176.824337,120.481192 C176.238551,121.066978 175.288804,121.066978 174.703017,120.481192 C174.117231,119.895405 174.117231,118.945658 174.703017,118.359871 L176.470677,116.591677 L174.703017,114.824337 C174.117231,114.238551 174.117231,113.288804 174.703017,112.703017 C175.288804,112.117231 176.238551,112.117231 176.824337,112.703017 L178.591677,114.470677 L180.359871,112.703017 C180.945658,112.117231 181.895405,112.117231 182.481192,112.703017 Z" id="Combined-Shape-Copy-9" fill="#A3B1BF" fill-rule="nonzero" opacity="0.40327381" transform="translate(178.592104, 116.592104) rotate(21.000000) translate(-178.592104, -116.592104) "></path>
|
||||
<path d="M255.756523,87.6774526 C252.646357,87.2191043 252.409392,86.777944 252.409392,86.777944 C253.049197,83.0595931 251.064615,78.3500639 247.824118,75.8005013 C243.511353,72.4087236 236.953345,74.8723459 231.846748,69.7159271 C230.655999,68.5127627 231.349122,83.2085563 237.741254,88.7774886 C242.41539,92.8453301 247.918904,91.5963309 249.429556,90.9317258 C250.798029,90.3244143 251.757738,89.2186489 251.757738,89.2186489 C254.251795,89.963465 255.484013,89.9176301 255.484013,89.9176301 C256.360784,90.0207585 256.805093,87.8321452 255.756523,87.6774526 Z M250.23976,88.4387369 C241.572443,86.6548975 235.860216,76.8764451 235.860216,76.8764451 C235.860216,76.8764451 241.393479,83.559077 250.840571,86.3965362 C250.852475,86.8794631 250.555229,88.1644822 250.23976,88.4387369 Z" id="Shape" fill="#A3B1BF" fill-rule="nonzero" transform="translate(243.866495, 80.646219) rotate(-50.000000) translate(-243.866495, -80.646219) "></path>
|
||||
<path d="M284.036575,108.186104 C283.941177,108.060119 283.790165,107.990999 283.632082,108.000944 C283.475216,108.010889 283.333562,108.099961 283.25515,108.23651 C281.890084,110.62706 280.122319,111.45085 278.562891,112.177417 C277.542023,112.653192 276.577904,113.102556 275.8597,113.917577 C275.058899,114.82753 274.68547,116.056199 274.68547,117.781131 C274.68547,118.442066 274.740466,119.174493 274.850479,119.994775 C272.790007,120.490481 271,120.205086 271,120.205086 L271,122.082971 C273.298142,122.082971 275.205273,121.541583 276.595466,120.956805 C277.50628,120.611142 278.309967,120.149996 279.018256,119.620329 C279.099658,119.559441 279.143523,119.524819 279.145275,119.523086 C282.559723,116.887066 283.66259,112.694189 283.66259,112.694189 C283.66259,117.175267 280.397443,121.061703 276.229665,122.45329 C277.51164,122.861698 278.702256,123.042133 279.779315,122.991747 C280.98867,122.934347 282.061049,122.588642 282.966607,121.961113 C284.894206,120.628123 286,118.044222 286,114.873809 C286,112.310396 285.265367,109.810244 284.036121,108.185485 L284.036595,108.186062 L284.036575,108.186104 Z" id="Path" fill="#A3B1BF"></path>
|
||||
<g id="Group-3" transform="translate(258.984540, 88.713689) rotate(-20.000000) translate(-258.984540, -88.713689) translate(190.484540, 20.213689)" fill="#A3B1BF">
|
||||
<path d="M108.768881,108.807257 L108.638364,133.235841 L100.902418,130.420187 L108.768881,108.807257 Z M32.9768459,14 L5.35958214,86.9540043 L5,15.0324311 L32.9768459,14 Z M109.292315,14.1079371 L109.493973,29.0798232 L68.8719874,14.2946298 L109.292315,14.1079371 Z" id="Combined-Shape" fill-rule="nonzero" opacity="0.149972098"></path>
|
||||
<path d="M35.8002691,0.501535499 L66.6412144,11.7265355 L111.032838,11.7269142 L111.010214,27.8755355 L136.347379,37.0976908 L110.908214,106.989535 L110.871416,136.371331 L101.313214,132.891535 L98.642589,131.919338 L132.502241,38.8907083 L37.5932865,4.34667379 L3.73363431,97.3753036 L0.914214432,96.3501828 L3.75221443,88.5525355 L3.87910868,11.7269142 L31.7142144,11.7265355 L35.8002691,0.501535499 Z M108.412214,113.847535 L102.829214,129.187535 L108.487865,131.247078 L108.412214,113.847535 Z M30.5682144,14.8755355 L7.06327341,14.8759949 L7.09021443,79.3815355 L30.5682144,14.8755355 Z M108.039214,26.7945355 L107.989648,14.8759949 L75.2922144,14.8755355 L108.039214,26.7945355 Z" id="Combined-Shape"></path>
|
||||
<path d="M13.847793,64.3592085 L13.847793,66.7945205 L11.3059361,66.7945205 L11.3059361,64.3592085 L13.847793,64.3592085 Z M18.847793,52.1826484 L18.847793,54.6179604 L11.3059361,54.6179604 L11.3059361,52.1826484 L18.847793,52.1826484 Z M21.847793,40.0060883 L21.847793,42.4414003 L11.3059361,42.4414003 L11.3059361,40.0060883 L21.847793,40.0060883 Z M18.0852588,25.3366488 C20.1027373,25.3366488 21.7382268,26.9721383 21.7382268,28.9896168 C21.7382268,31.0070954 20.1027373,32.6425849 18.0852588,32.6425849 C16.0677803,32.6425849 14.4322908,31.0070954 14.4322908,28.9896168 C14.4322908,26.9721383 16.0677803,25.3366488 18.0852588,25.3366488 Z" id="Combined-Shape" opacity="0.4765625"></path>
|
||||
</g>
|
||||
<g id="Group-3-Copy" transform="translate(208.000000, 39.000000)">
|
||||
<path d="M3,99 L1.81887838e-12,99 L1.81887838e-12,-2.77555756e-17 L101,-2.77555756e-17 L101,99 L98,99 L98,3 L3,3 L3,99 Z" id="Combined-Shape" fill="#A3B1BF" fill-rule="nonzero" opacity="0.248070126"></path>
|
||||
<path d="M25,89 L91,89 L91,92 L11,92 L11,89 L22,89 L22,68 L11,68 L11,65 L22,65 L22,56 L11,56 L11,53 L22,53 L22,44 L11,44 L11,41 L22,41 L22,32 L11,32 L11,29 L22,29 L22,10 L25,10 L25,17 L91,17 L91,20 L25,20 L25,29 L91,29 L91,32 L25,32 L25,41 L30,41 L30,44 L25,44 L25,53 L91,53 L91,56 L25,56 L25,65 L91,65 L91,68 L25,68 L25,77 L68,77 L68,80 L25,80 L25,89 Z M91,77 L91,80 L77,80 L77,77 L91,77 Z M91,41 L91,44 L47,44 L47,41 L91,41 Z" id="Combined-Shape" fill="#A3B1BF" fill-rule="nonzero" opacity="0.248070126"></path>
|
||||
<circle id="Oval-6" stroke="#A3B1BF" stroke-width="3" opacity="0.3" cx="15" cy="18" r="2.5"></circle>
|
||||
<circle id="Oval-6-Copy" stroke="#A3B1BF" stroke-width="3" opacity="0.3" cx="15" cy="79" r="2.5"></circle>
|
||||
</g>
|
||||
<rect id="Rectangle-34" fill="url(#linearGradient-2)" x="154" y="134" width="51" height="4"></rect>
|
||||
<rect id="Rectangle-34-Copy" fill="url(#linearGradient-3)" x="319" y="134" width="45" height="4"></rect>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 16 KiB |
8
src/component/empty/index.ts
Normal file
8
src/component/empty/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import type { App } from "vue";
|
||||
import Component from "./index.vue";
|
||||
|
||||
Component.install = (app: App) => {
|
||||
app.component(Component.name, Component);
|
||||
};
|
||||
|
||||
export default Component;
|
||||
30
src/component/empty/index.vue
Normal file
30
src/component/empty/index.vue
Normal file
@@ -0,0 +1,30 @@
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: "LayEmpty",
|
||||
};
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineProps, withDefaults } from "vue";
|
||||
import "./index.less";
|
||||
|
||||
export interface LayEmptyProps {
|
||||
description?: string;
|
||||
image?: string;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<LayEmptyProps>(), {
|
||||
description: "暂无数据",
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="layui-empty">
|
||||
<div class="layui-empty-image">
|
||||
<img class="layui-empty-image-default" src="./index.svg" />
|
||||
</div>
|
||||
<div class="layui-empty-description">
|
||||
{{ description }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
85
src/component/exception/image/401.svg
Normal file
85
src/component/exception/image/401.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 193 KiB |
75
src/component/exception/image/403.svg
Normal file
75
src/component/exception/image/403.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 195 KiB |
94
src/component/exception/image/404.svg
Normal file
94
src/component/exception/image/404.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 195 KiB |
94
src/component/exception/image/500.svg
Normal file
94
src/component/exception/image/500.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 209 KiB |
43
src/component/exception/index.less
Normal file
43
src/component/exception/index.less
Normal file
@@ -0,0 +1,43 @@
|
||||
.layui-exception{
|
||||
.layui-exception-image{
|
||||
width: 50%;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
> div {
|
||||
height: 200px;
|
||||
background-size: 100% 100%;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
.error-401 {
|
||||
background-image: url(./image/401.svg);
|
||||
}
|
||||
.error-403 {
|
||||
background-image: url(./image/403.svg);
|
||||
}
|
||||
.error-404 {
|
||||
background-image: url(./image/404.svg);
|
||||
}
|
||||
.error-500 {
|
||||
background-image: url(./image/500.svg);
|
||||
}
|
||||
}
|
||||
.layui-exception-details{
|
||||
.layui-exception-details-content {
|
||||
margin-left: 100px;
|
||||
.layui-exception-details-title{
|
||||
font-size: 60px;
|
||||
color: #434e59;
|
||||
margin-bottom: 24px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.layui-exception-details-describe{
|
||||
font-size: 18px;
|
||||
color: rgba(0, 0, 0, .45);
|
||||
margin-bottom: 26px;
|
||||
}
|
||||
}
|
||||
width: 50%;
|
||||
vertical-align: top;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
8
src/component/exception/index.ts
Normal file
8
src/component/exception/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import type { App } from "vue";
|
||||
import Component from "./index.vue";
|
||||
|
||||
Component.install = (app: App) => {
|
||||
app.component(Component.name, Component);
|
||||
};
|
||||
|
||||
export default Component;
|
||||
44
src/component/exception/index.vue
Normal file
44
src/component/exception/index.vue
Normal file
@@ -0,0 +1,44 @@
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: "LayException"
|
||||
}
|
||||
</script>
|
||||
<script setup lang="ts">
|
||||
import { defineProps, useSlots } from "vue";
|
||||
import "./index.less";
|
||||
|
||||
export interface LayDropdownProps {
|
||||
title?: string,
|
||||
status?: '401' | '403' | '404' | '500',
|
||||
describe?: string
|
||||
}
|
||||
|
||||
const slots = useSlots();
|
||||
|
||||
const props = withDefaults(defineProps<LayDropdownProps>(), {
|
||||
title: "Exception",
|
||||
describe: "describe"
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<div class="layui-exception">
|
||||
<div class="layui-exception-image">
|
||||
<slot name="image" v-if="slots.default"></slot>
|
||||
<template v-else>
|
||||
<div v-if="status=='401'" class="error-401" />
|
||||
<div v-if="status=='403'" class="error-403" />
|
||||
<div v-if="status=='404'" class="error-404" />
|
||||
<div v-if="status=='500'" class="error-500" />
|
||||
</template>
|
||||
</div>
|
||||
<div class="layui-exception-details">
|
||||
<div class="layui-exception-details-content">
|
||||
<div class="layui-exception-details-title">{{title}}</div>
|
||||
<div class="layui-exception-details-describe">{{describe}}</div>
|
||||
<div class="layui-exception-details-operate">
|
||||
<slot name="action"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
8
src/component/field/index.ts
Normal file
8
src/component/field/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import type { App } from "vue";
|
||||
import Component from "./index.vue";
|
||||
|
||||
Component.install = (app: App) => {
|
||||
app.component(Component.name, Component);
|
||||
};
|
||||
|
||||
export default Component;
|
||||
30
src/component/field/index.vue
Normal file
30
src/component/field/index.vue
Normal file
@@ -0,0 +1,30 @@
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: "LayField"
|
||||
}
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineProps, useSlots } from "vue";
|
||||
|
||||
const slot = useSlots();
|
||||
|
||||
const props = defineProps<{
|
||||
title?: string;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<fieldset v-if="slot.default" class="layui-elem-field">
|
||||
<legend>{{ title }}</legend>
|
||||
<div class="layui-field-box">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset v-else class="layui-elem-field layui-field-title">
|
||||
<legend>
|
||||
<a name="docend">{{ title }}</a>
|
||||
</legend>
|
||||
</fieldset>
|
||||
</template>
|
||||
3
src/component/footer/index.less
Normal file
3
src/component/footer/index.less
Normal file
@@ -0,0 +1,3 @@
|
||||
.layui-footer {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
8
src/component/footer/index.ts
Normal file
8
src/component/footer/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import type { App } from "vue";
|
||||
import Component from "./index.vue";
|
||||
|
||||
Component.install = (app: App) => {
|
||||
app.component(Component.name, Component);
|
||||
};
|
||||
|
||||
export default Component;
|
||||
15
src/component/footer/index.vue
Normal file
15
src/component/footer/index.vue
Normal file
@@ -0,0 +1,15 @@
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: "LayFooter",
|
||||
};
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import "./index.less";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="layui-footer">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
8
src/component/form/index.ts
Normal file
8
src/component/form/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import type { App } from "vue";
|
||||
import Component from "./index.vue";
|
||||
|
||||
Component.install = (app: App) => {
|
||||
app.component(Component.name, Component);
|
||||
};
|
||||
|
||||
export default Component;
|
||||
161
src/component/form/index.vue
Normal file
161
src/component/form/index.vue
Normal file
@@ -0,0 +1,161 @@
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: "LayForm",
|
||||
};
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { toRefs, provide, reactive, onMounted } from "vue";
|
||||
import { Rule, ValidateError, ValidateMessages } from "async-validator";
|
||||
import {
|
||||
LayFormItemContext,
|
||||
FormCallback,
|
||||
modelType,
|
||||
} from "../../types/form";
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
model?: modelType;
|
||||
required?: boolean;
|
||||
rules?: Rule;
|
||||
initValidate?: boolean;
|
||||
requiredIcons?: string;
|
||||
requiredErrorMessage?: string;
|
||||
validateMessage?: ValidateMessages;
|
||||
useCN?: boolean;
|
||||
}>(),
|
||||
{
|
||||
model: function () {
|
||||
return {};
|
||||
},
|
||||
useCN: true,
|
||||
requiredIcons: "",
|
||||
initValidate: false,
|
||||
}
|
||||
);
|
||||
|
||||
const formItems: LayFormItemContext[] = [];
|
||||
const formItemMap: { [key: string]: LayFormItemContext } = {};
|
||||
|
||||
const emit = defineEmits(["submit"]);
|
||||
|
||||
// 初始化表单就进行校验
|
||||
onMounted(() => {
|
||||
props.initValidate && validate()?.catch((err) => {});
|
||||
});
|
||||
|
||||
// 原生提交表单事件
|
||||
const submit = function () {
|
||||
let _isValidate = false;
|
||||
validate((isValidate, model, errors) => {
|
||||
_isValidate = isValidate as boolean;
|
||||
emit("submit", isValidate, model, errors);
|
||||
});
|
||||
|
||||
// 如果表单失败则阻止提交表单,成功则进行提交表单
|
||||
return _isValidate;
|
||||
};
|
||||
|
||||
/**
|
||||
* 校验表单数据
|
||||
* @param fields 需要校验的表单字段(string|string[]); 该字段如果为function, 则默认为回调函数,校验全部字段;
|
||||
* @param callback 校验表单之后的回调函数
|
||||
**/
|
||||
const validate = function (
|
||||
fields?: string | string[] | FormCallback | null,
|
||||
callback?: FormCallback | null
|
||||
) {
|
||||
// 根据参数识别需要校验的表单项
|
||||
let validateItems: LayFormItemContext[] = formItems;
|
||||
if (typeof fields === "function") {
|
||||
callback = fields;
|
||||
} else if (
|
||||
typeof fields === "string" ||
|
||||
(Array.isArray(fields) && fields.length > 0)
|
||||
) {
|
||||
validateItems = [];
|
||||
const validateFields = !fields ? [] : ([] as string[]).concat(fields);
|
||||
validateFields.forEach(
|
||||
(field) => formItemMap[field] && validateItems.push(formItemMap[field])
|
||||
);
|
||||
}
|
||||
// 通过调用每个子项进行校验
|
||||
let errorsArrs: ValidateError[] = [];
|
||||
validateItems.forEach((filed) => {
|
||||
filed.validate((errors, _fields) => {
|
||||
errorsArrs = errorsArrs.concat(errors as ValidateError[]);
|
||||
});
|
||||
});
|
||||
const isValidate = errorsArrs.length === 0;
|
||||
// 有回调则进行回调
|
||||
if (typeof callback === "function") {
|
||||
isValidate
|
||||
? callback(true, props.model, null)
|
||||
: callback(false, props.model, errorsArrs);
|
||||
return null;
|
||||
}
|
||||
|
||||
// 没有回调则创建一个Promise的链式调用
|
||||
return new Promise((resolve, reject) => {
|
||||
const callbackParams = {
|
||||
isValidate,
|
||||
model: props.model,
|
||||
errors: isValidate ? null : errorsArrs,
|
||||
};
|
||||
callbackParams.isValidate
|
||||
? resolve(callbackParams)
|
||||
: reject(callbackParams);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 清除校验
|
||||
* @param fields 需要进行清除校验的表单字段(string|string[]); 该字段如果为null, 则默认为全部字段清除校验;
|
||||
**/
|
||||
const clearValidate = function (fields?: string | string[]) {
|
||||
const clearFields = !fields ? [] : ([] as string[]).concat(fields);
|
||||
if (clearFields.length === 0) {
|
||||
formItems.forEach((filed) => filed.clearValidate());
|
||||
} else {
|
||||
clearFields.forEach(
|
||||
(field) => formItemMap[field] && formItemMap[field].clearValidate()
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 重置表单所有值
|
||||
**/
|
||||
const reset = function () {
|
||||
for (const key in props.model) {
|
||||
props.model[key] = null;
|
||||
}
|
||||
// 重新校验
|
||||
setTimeout(() => validate()?.catch((err) => {}), 0);
|
||||
};
|
||||
|
||||
// 添加子项
|
||||
const addField = function (item: LayFormItemContext) {
|
||||
formItems.push(item);
|
||||
formItemMap[item.prop as string] = item;
|
||||
};
|
||||
|
||||
defineExpose({ validate, clearValidate, reset });
|
||||
|
||||
provide(
|
||||
'LayForm',
|
||||
reactive({
|
||||
formItems,
|
||||
addField,
|
||||
clearValidate,
|
||||
validate,
|
||||
...toRefs(props),
|
||||
})
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<form class="layui-form" :onsubmit="submit">
|
||||
<slot></slot>
|
||||
</form>
|
||||
</template>
|
||||
49
src/component/formItem/cnValidateMessage.ts
Normal file
49
src/component/formItem/cnValidateMessage.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { ValidateMessages } from "async-validator";
|
||||
// 中文翻译 --> 根据 async-validator 中 ValidateMessages 进行翻译
|
||||
export default {
|
||||
default: "%s验证失败",
|
||||
required: "%s不能为空",
|
||||
enum: "%s不在枚举%s里面",
|
||||
whitespace: "%s不能为空",
|
||||
date: {
|
||||
format: "%s日期%s不是一个有效格式的日期%s",
|
||||
parse: "%s无法解析为日期,%s是无效的",
|
||||
invalid: "%s日期%s是无效的",
|
||||
},
|
||||
types: {
|
||||
number: "%s不是一个有效的数字",
|
||||
boolean: "%s不是一个有效的布尔类型",
|
||||
method: "%s不是一个有效的方法",
|
||||
regexp: "%s不是一个有效的正则表达式",
|
||||
integer: "%s不是一个有效的整型数字",
|
||||
float: "%s不是一个有效的浮点小数",
|
||||
array: "%s不是一个有效的数组",
|
||||
object: "%s不是一个有效的对象",
|
||||
enum: "%s不是一个有效的枚举",
|
||||
date: "%s不是一个有效的日期",
|
||||
url: "%s不是一个有效的url",
|
||||
hex: "%s不是一个有效的十六进制",
|
||||
email: "%s不是一个有效的邮箱",
|
||||
},
|
||||
string: {
|
||||
len: "%s必须是长度为%s个字符",
|
||||
min: "%s最小长度为%s个字符",
|
||||
max: "%s最长%s个字符",
|
||||
range: "%s字符长度需要在%s和%s直接",
|
||||
},
|
||||
number: {
|
||||
len: "%s长度必须为%s",
|
||||
min: "%s必须小于%s",
|
||||
max: "%s必须大于%s",
|
||||
range: "%s需要在%s和%s之间",
|
||||
},
|
||||
array: {
|
||||
len: "%s长度必须为%s",
|
||||
min: "%s长度必须小于%s",
|
||||
max: "%s长度必须大于%s",
|
||||
range: "%s长度需要在%s和%s之间",
|
||||
},
|
||||
pattern: {
|
||||
mismatch: "%s值%s不能匹配%s",
|
||||
},
|
||||
} as ValidateMessages;
|
||||
48
src/component/formItem/index.less
Normal file
48
src/component/formItem/index.less
Normal file
@@ -0,0 +1,48 @@
|
||||
@error_color: red;
|
||||
|
||||
.layui-required {
|
||||
color: @error_color;
|
||||
font-size: 12px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.layui-form .layui-form-item {
|
||||
.layui-input-block,
|
||||
.layui-input-inline {
|
||||
.layui-form-danger {
|
||||
&, .layui-input {
|
||||
border-color: #ff5722 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.layui-error-message {
|
||||
color: @error_color;
|
||||
font-size: 12px;
|
||||
line-height: 1;
|
||||
padding-top: 2px;
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.layui-error-message-anim {
|
||||
-ms-transform-origin: 0 0;
|
||||
-webkit-transform-origin: 0 0;
|
||||
transform-origin: 0 0;
|
||||
-webkit-animation: layui-top-show-anim 0.3s ease 1;
|
||||
animation: layui-top-show-anim 0.3s ease 1;
|
||||
}
|
||||
|
||||
@keyframes layui-top-show-anim {
|
||||
0% {
|
||||
opacity: 0.3;
|
||||
transform: rotateX(45deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: rotateX(0);
|
||||
}
|
||||
}
|
||||
8
src/component/formItem/index.ts
Normal file
8
src/component/formItem/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import type { App } from "vue";
|
||||
import Component from "./index.vue";
|
||||
|
||||
Component.install = (app: App) => {
|
||||
app.component(Component.name, Component);
|
||||
};
|
||||
|
||||
export default Component;
|
||||
200
src/component/formItem/index.vue
Normal file
200
src/component/formItem/index.vue
Normal file
@@ -0,0 +1,200 @@
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'LayFormItem'
|
||||
}
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import "./index.less";
|
||||
import {
|
||||
defineProps,
|
||||
inject,
|
||||
withDefaults,
|
||||
ref,
|
||||
reactive,
|
||||
toRefs,
|
||||
onMounted,
|
||||
computed,
|
||||
watch,
|
||||
} from "vue";
|
||||
import {
|
||||
LayFormContext,
|
||||
LayFormItemContext,
|
||||
FieldValidateError,
|
||||
} from "../../types/form";
|
||||
import Schema, {
|
||||
Rule,
|
||||
RuleItem,
|
||||
Rules,
|
||||
ValidateCallback,
|
||||
ValidateError,
|
||||
ValidateMessages,
|
||||
} from "async-validator";
|
||||
import cnValidateMessage from "./cnValidateMessage";
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
prop?: string;
|
||||
mode?: string;
|
||||
label?: string;
|
||||
errorMessage?: string;
|
||||
rules?: Rule;
|
||||
required?: boolean;
|
||||
}>(),
|
||||
{
|
||||
mode: "block",
|
||||
}
|
||||
);
|
||||
|
||||
const layForm = inject('LayForm', {} as LayFormContext);
|
||||
const formItemRef = ref<HTMLDivElement>();
|
||||
const slotParent = ref<HTMLDivElement>();
|
||||
|
||||
// 是否必填
|
||||
const isRequired = computed(() => {
|
||||
return props.required || layForm.required;
|
||||
});
|
||||
|
||||
// 拼接校验规则
|
||||
const ruleItems = computed(() => {
|
||||
const prop = props.prop;
|
||||
if (!prop) {
|
||||
return {};
|
||||
}
|
||||
|
||||
let rulesArrs: RuleItem[] = [];
|
||||
if (isRequired.value) {
|
||||
rulesArrs.push({ required: true });
|
||||
}
|
||||
if (props.rules) {
|
||||
rulesArrs = rulesArrs.concat(props.rules as RuleItem | RuleItem[]);
|
||||
}
|
||||
if (layForm.rules && layForm.rules[prop]) {
|
||||
rulesArrs = rulesArrs.concat(layForm.rules[prop] as RuleItem | RuleItem[]);
|
||||
}
|
||||
return rulesArrs;
|
||||
});
|
||||
|
||||
// 值 计算 和 监听
|
||||
const filedValue = computed(() =>
|
||||
props.prop ? layForm.model[props.prop] : undefined
|
||||
);
|
||||
watch(
|
||||
() => filedValue.value,
|
||||
(val) => validate(),
|
||||
{ deep: true }
|
||||
);
|
||||
|
||||
// 错误状态和信息
|
||||
const errorStatus = ref(false);
|
||||
const errorMsg = ref();
|
||||
// 校验数据有效性
|
||||
const validate = (callback?: ValidateCallback) => {
|
||||
if (props.prop && (ruleItems.value as RuleItem[]).length > 0) {
|
||||
// 校验规则
|
||||
const descriptor: Rules = {};
|
||||
descriptor[layForm.useCN ? props.label || props.prop : props.prop] =
|
||||
ruleItems.value;
|
||||
const validator = new Schema(descriptor);
|
||||
|
||||
let model: { [key: string]: any } = {};
|
||||
let validateMessage = null;
|
||||
// 使用中文错误提示
|
||||
if (layForm.useCN) {
|
||||
validateMessage = Object.assign(
|
||||
{},
|
||||
cnValidateMessage,
|
||||
layForm.validateMessage
|
||||
);
|
||||
model[props.label || props.prop] = filedValue.value;
|
||||
} else {
|
||||
layForm.validateMessage && (validateMessage = layForm.validateMessage);
|
||||
model[props.prop] = filedValue.value;
|
||||
}
|
||||
// 自定义校验消息
|
||||
layForm.requiredErrorMessage &&
|
||||
(validateMessage = Object.assign(validateMessage, {
|
||||
required: layForm.requiredErrorMessage,
|
||||
}));
|
||||
validateMessage && validator.messages(validateMessage);
|
||||
|
||||
// 开始校验
|
||||
validator.validate(model, (errors, fields) => {
|
||||
errorStatus.value = errors !== null && errors.length > 0;
|
||||
const slotParentDiv = slotParent.value as HTMLDivElement;
|
||||
if (errorStatus.value) {
|
||||
const _errors = errors as FieldValidateError[];
|
||||
// 如果是中文,将错误信息转换成FieldValidateError类型
|
||||
layForm.useCN &&
|
||||
_errors.forEach((error) => {
|
||||
error.label = props.label;
|
||||
error.field = props.prop;
|
||||
});
|
||||
errorMsg.value = props.errorMessage ?? _errors[0].message;
|
||||
slotParentDiv.childElementCount > 0 &&
|
||||
slotParentDiv.firstElementChild?.classList.add("layui-form-danger");
|
||||
callback && callback(_errors, fields);
|
||||
} else {
|
||||
clearValidate();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 清除校验
|
||||
const clearValidate = () => {
|
||||
errorStatus.value = false;
|
||||
errorMsg.value = "";
|
||||
const slotParentDiv = slotParent.value as HTMLDivElement;
|
||||
slotParentDiv.childElementCount > 0 &&
|
||||
slotParentDiv.firstElementChild?.classList.remove("layui-form-danger");
|
||||
};
|
||||
|
||||
defineExpose({ validate, clearValidate });
|
||||
|
||||
onMounted(() => {
|
||||
if (props.prop) {
|
||||
layForm.addField(
|
||||
reactive({
|
||||
...toRefs(props),
|
||||
$el: formItemRef,
|
||||
validate,
|
||||
clearValidate,
|
||||
}) as LayFormItemContext
|
||||
);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="layui-form-item" ref="formItemRef">
|
||||
<label class="layui-form-label">
|
||||
<span
|
||||
v-if="props.prop && isRequired"
|
||||
:class="
|
||||
['layui-required', 'layui-icon'].concat(layForm.requiredIcons ?? '')
|
||||
"
|
||||
>
|
||||
<slot name="required" :props="{ ...props, model: layForm.model }">{{
|
||||
layForm.requiredIcons ? "" : "*"
|
||||
}}</slot>
|
||||
</span>
|
||||
<slot name="label" :props="{ ...props, model: layForm.model }">
|
||||
{{ label }}
|
||||
</slot>
|
||||
</label>
|
||||
<div :class="[mode ? 'layui-input-' + mode : '']">
|
||||
<div ref="slotParent">
|
||||
<slot :props="{ ...props, model: layForm.model }"></slot>
|
||||
</div>
|
||||
<span
|
||||
v-if="errorStatus"
|
||||
:class="[
|
||||
'layui-error-message',
|
||||
{ 'layui-error-message-anim': errorStatus },
|
||||
]"
|
||||
>{{ errorMsg }}</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
4
src/component/header/index.less
Normal file
4
src/component/header/index.less
Normal file
@@ -0,0 +1,4 @@
|
||||
.layui-header {
|
||||
box-sizing: border-box;
|
||||
height: 60px;
|
||||
}
|
||||
8
src/component/header/index.ts
Normal file
8
src/component/header/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import type { App } from "vue";
|
||||
import Component from "./index.vue";
|
||||
|
||||
Component.install = (app: App) => {
|
||||
app.component(Component.name, Component);
|
||||
};
|
||||
|
||||
export default Component;
|
||||
15
src/component/header/index.vue
Normal file
15
src/component/header/index.vue
Normal file
@@ -0,0 +1,15 @@
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: "LayHeader",
|
||||
};
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import "./index.less";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="layui-header">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
8
src/component/icon/index.ts
Normal file
8
src/component/icon/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import type { App } from "vue";
|
||||
import { LayIcon as Component } from "@layui/icons-vue";
|
||||
|
||||
Component.install = (app: App) => {
|
||||
app.component(Component.name, Component);
|
||||
};
|
||||
|
||||
export default Component;
|
||||
8
src/component/iconPicker/index.ts
Normal file
8
src/component/iconPicker/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import type { App } from "vue";
|
||||
import Component from "./index.vue";
|
||||
|
||||
Component.install = (app: App) => {
|
||||
app.component(Component.name, Component);
|
||||
};
|
||||
|
||||
export default Component;
|
||||
202
src/component/iconPicker/index.vue
Normal file
202
src/component/iconPicker/index.vue
Normal file
@@ -0,0 +1,202 @@
|
||||
<template>
|
||||
<lay-dropdown ref="dropdownRef">
|
||||
<div
|
||||
class="layui-inline layui-border-box layui-iconpicker layui-iconpicker-split"
|
||||
>
|
||||
<div class="layui-inline layui-iconpicker-main">
|
||||
<i class="layui-inline layui-icon" :class="[selectedIcon]"></i>
|
||||
</div>
|
||||
<span class="layui-inline layui-iconpicker-suffix"
|
||||
><i class="layui-icon layui-icon-down layui-anim"></i
|
||||
></span>
|
||||
</div>
|
||||
<template #content>
|
||||
<div class="layui-iconpicker-view layui-iconpicker-scroll">
|
||||
<div v-if="showSearch" class="layui-iconpicker-search">
|
||||
<div class="layui-form layui-input-wrap layui-input-wrap-prefix">
|
||||
<div class="layui-input-prefix">
|
||||
<i class="layui-icon layui-icon-search"></i>
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
value=""
|
||||
placeholder="search"
|
||||
autocomplete="off"
|
||||
class="layui-input"
|
||||
lay-affix="clear"
|
||||
@input="search"
|
||||
/>
|
||||
<div class="layui-input-suffix layui-input-affix-event layui-hide">
|
||||
<i class="layui-icon layui-icon-clear"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-iconpicker-list">
|
||||
<ul>
|
||||
<li
|
||||
v-for="icon in icones"
|
||||
:key="icon"
|
||||
:class="[selectedIcon === icon.class ? 'layui-this' : '']"
|
||||
@click="selectIcon(icon.class)"
|
||||
>
|
||||
<i class="layui-icon" :class="[icon.class]"></i>
|
||||
<p class="layui-elip">
|
||||
{{ icon.name }}
|
||||
</p>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div v-if="page" class="layui-iconpicker-page">
|
||||
<div
|
||||
id="layui-laypage-1"
|
||||
class="layui-box layui-laypage layui-laypage-default"
|
||||
>
|
||||
<span class="layui-laypage-count">共 {{ total }} 个</span
|
||||
><a
|
||||
href="javascript:;"
|
||||
class="layui-laypage-prev"
|
||||
:class="[currentPage === 1 ? 'layui-disabled' : '']"
|
||||
@click="prev()"
|
||||
><i class="layui-icon layui-icon-left"></i></a
|
||||
><span class="layui-laypage-curr"
|
||||
><em class="layui-laypage-em"></em
|
||||
><em>{{ currentPage }} / {{ totalPage }}</em></span
|
||||
><span class="layui-laypage-spr">…</span
|
||||
><a href="javascript:;" class="layui-laypage-last" title="尾页"
|
||||
>14</a
|
||||
><a
|
||||
href="javascript:;"
|
||||
:class="[currentPage === totalPage ? 'layui-disabled' : '']"
|
||||
class="layui-laypage-next"
|
||||
@click="next()"
|
||||
><i class="layui-icon layui-icon-right"></i
|
||||
></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</lay-dropdown>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: "LayIconPicker"
|
||||
}
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineProps, Ref, ref } from "vue";
|
||||
import { LayIconList as icons } from "@layui/icons-vue";
|
||||
|
||||
export interface LayIconPickerProps {
|
||||
page?: boolean;
|
||||
modelValue?: string;
|
||||
showSearch?: boolean;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<LayIconPickerProps>(), {
|
||||
modelValue: "layui-icon-face-smile",
|
||||
page: false,
|
||||
});
|
||||
|
||||
const dropdownRef = ref<null | HTMLElement>(null);
|
||||
|
||||
const emit = defineEmits(["update:modelValue"]);
|
||||
|
||||
const selectedIcon: Ref<string> = ref(props.modelValue as string);
|
||||
|
||||
const selectIcon = function (icon: string) {
|
||||
emit("update:modelValue", icon);
|
||||
selectedIcon.value = icon;
|
||||
// @ts-ignore
|
||||
dropdownRef.value.hide();
|
||||
};
|
||||
|
||||
const icones: Ref = ref([]);
|
||||
|
||||
const total = ref(icons.length);
|
||||
const totalPage = ref(total.value / 12);
|
||||
const currentPage: Ref = ref(1);
|
||||
|
||||
if (props.page) {
|
||||
icones.value = icons.slice(0, 12);
|
||||
} else {
|
||||
icones.value = icons;
|
||||
}
|
||||
|
||||
const next = function () {
|
||||
if (currentPage.value === totalPage.value) {
|
||||
return;
|
||||
}
|
||||
currentPage.value = currentPage.value + 1;
|
||||
const start = (currentPage.value - 1) * 12;
|
||||
const end = start + 12;
|
||||
icones.value = icons.slice(start, end);
|
||||
};
|
||||
|
||||
const prev = function () {
|
||||
if (currentPage.value === 1) {
|
||||
return;
|
||||
}
|
||||
currentPage.value = currentPage.value - 1;
|
||||
const start = (currentPage.value - 1) * 12;
|
||||
const end = start + 12;
|
||||
icones.value = icons.slice(start, end);
|
||||
};
|
||||
|
||||
const search = function (e: any) {
|
||||
var text = e.target.value;
|
||||
currentPage.value = 1;
|
||||
const start = (currentPage.value - 1) * 12;
|
||||
const end = start + 12;
|
||||
if (text === "") {
|
||||
if (props.page) {
|
||||
icones.value = icons.slice(start, end);
|
||||
total.value = icons.length;
|
||||
totalPage.value = Math.ceil(icons.length / 12);
|
||||
} else {
|
||||
icones.value = icons;
|
||||
}
|
||||
} else {
|
||||
if (props.page) {
|
||||
icones.value = searchList(text, icons).slice(start, end);
|
||||
total.value = searchList(text, icons).length;
|
||||
totalPage.value = Math.ceil(searchList(text, icons).length / 12);
|
||||
} else {
|
||||
icones.value = searchList(text, icons);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const searchList = function (str: string, container: any) {
|
||||
var newList = [];
|
||||
var startChar = str.charAt(0);
|
||||
var strLen = str.length;
|
||||
for (var i = 0; i < container.length; i++) {
|
||||
var obj = container[i];
|
||||
var isMatch = false;
|
||||
for (var p in obj) {
|
||||
if (typeof obj[p] == "function") {
|
||||
obj[p]();
|
||||
} else {
|
||||
var curItem = "";
|
||||
if (obj[p] != null) {
|
||||
curItem = obj[p];
|
||||
}
|
||||
for (var j = 0; j < curItem.length; j++) {
|
||||
if (curItem.charAt(j) == startChar) {
|
||||
if (curItem.substring(j).substring(0, strLen) == str) {
|
||||
isMatch = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isMatch) {
|
||||
newList.push(obj);
|
||||
}
|
||||
}
|
||||
return newList;
|
||||
};
|
||||
</script>
|
||||
0
src/component/input/index.less
Normal file
0
src/component/input/index.less
Normal file
8
src/component/input/index.ts
Normal file
8
src/component/input/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import type { App } from "vue";
|
||||
import Component from "./index.vue";
|
||||
|
||||
Component.install = (app: App) => {
|
||||
app.component(Component.name, Component);
|
||||
};
|
||||
|
||||
export default Component;
|
||||
50
src/component/input/index.vue
Normal file
50
src/component/input/index.vue
Normal file
@@ -0,0 +1,50 @@
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: "LayInput"
|
||||
}
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineProps, defineEmits } from "vue";
|
||||
|
||||
export interface LayInputProps {
|
||||
name?: string;
|
||||
type?: string;
|
||||
disabled?: boolean;
|
||||
modelValue?: string | number;
|
||||
placeholder?: string;
|
||||
}
|
||||
|
||||
const props = defineProps<LayInputProps>();
|
||||
|
||||
const emit = defineEmits(["update:modelValue", "input", "focus", "blur"]);
|
||||
|
||||
const onInput = function (event: InputEvent) {
|
||||
const inputElement = event.target as HTMLInputElement;
|
||||
emit("update:modelValue", inputElement.value);
|
||||
emit("input", event);
|
||||
};
|
||||
|
||||
const onFocus = function (event: FocusEvent) {
|
||||
emit("focus", event);
|
||||
};
|
||||
|
||||
const onBlur = function () {
|
||||
emit("blur");
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<input
|
||||
:type="type"
|
||||
:name="name"
|
||||
:value="modelValue"
|
||||
:disabled="disabled"
|
||||
:placeholder="placeholder"
|
||||
:class="{ 'layui-disabled': disabled }"
|
||||
class="layui-input"
|
||||
@input="onInput"
|
||||
@focus="onFocus"
|
||||
@blur="onBlur"
|
||||
/>
|
||||
</template>
|
||||
116
src/component/inputNumber/index.less
Normal file
116
src/component/inputNumber/index.less
Normal file
@@ -0,0 +1,116 @@
|
||||
@border-color: #eee;
|
||||
@hover-border-color: #5fb878;
|
||||
@lg: 40px;
|
||||
@lg-wdith: 200px;
|
||||
@lg-right: 20px;
|
||||
@md: 32px;
|
||||
@md-wdith: 160px;
|
||||
@md-right: 16px;
|
||||
@sm: 28px;
|
||||
@sm-wdith: 140px;
|
||||
@sm-right: 14px;
|
||||
@xs: 24px;
|
||||
@xs-wdith: 120px;
|
||||
@xs-right: 12px;
|
||||
|
||||
.set-size(@width, @size, @right-size) {
|
||||
& {
|
||||
height: @size;
|
||||
width: @width;
|
||||
.layui-input {
|
||||
height: @size;
|
||||
line-height: @size;
|
||||
padding: 0 @size;
|
||||
}
|
||||
.layui-control-btn {
|
||||
width: @size;
|
||||
height: @size;
|
||||
line-height: @size;
|
||||
}
|
||||
&[position="right"] {
|
||||
.layui-input {
|
||||
padding: 0 @size 0 0;
|
||||
}
|
||||
.layui-control-btn {
|
||||
height: @right-size;
|
||||
line-height: @right-size;
|
||||
}
|
||||
.layui-subtraction-btn {
|
||||
top: @right-size - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.layui-input-number {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid @border-color;
|
||||
border-color: @border-color;
|
||||
border-radius: 2px;
|
||||
overflow: hidden;
|
||||
.set-size(@lg-wdith, @lg, @lg-right);
|
||||
margin-left: 5px;
|
||||
.layui-input {
|
||||
text-align: center;
|
||||
border: 0;
|
||||
}
|
||||
.layui-control-btn {
|
||||
position: absolute;
|
||||
box-sizing: border-box;
|
||||
border: 0;
|
||||
border-color: @border-color;
|
||||
border-style: solid;
|
||||
border-radius: 0;
|
||||
padding: 0;
|
||||
text-align: center;
|
||||
top: 0;
|
||||
&:hover {
|
||||
color: @hover-border-color;
|
||||
}
|
||||
&.layui-subtraction-btn {
|
||||
border-right-width: 1px;
|
||||
}
|
||||
&.layui-addition-btn {
|
||||
border-left-width: 1px;
|
||||
right: 0;
|
||||
}
|
||||
.layui-icon {
|
||||
padding: 0px;
|
||||
}
|
||||
}
|
||||
/* 谷歌--去掉自带的控制按钮 */
|
||||
input.layui-input::-webkit-outer-spin-button,
|
||||
input.layui-input::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
/* 火狐--去掉自带的控制按钮 */
|
||||
input.layui-input[type="number"] {
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
|
||||
&[position="right"] {
|
||||
.layui-subtraction-btn {
|
||||
right: 0;
|
||||
border-right-width: 0px;
|
||||
border-left-width: 1px;
|
||||
}
|
||||
.layui-addition-btn {
|
||||
border-bottom-width: 1px;
|
||||
}
|
||||
}
|
||||
|
||||
&[size="md"] {
|
||||
.set-size(@md-wdith,@md, @md-right);
|
||||
}
|
||||
|
||||
&[size="sm"] {
|
||||
.set-size(@sm-wdith, @sm, @sm-right);
|
||||
}
|
||||
|
||||
&[size="xs"] {
|
||||
.set-size(@xs-wdith, @xs, @xs-right);
|
||||
}
|
||||
}
|
||||
8
src/component/inputNumber/index.ts
Normal file
8
src/component/inputNumber/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import type { App } from "vue";
|
||||
import Component from "./index.vue";
|
||||
|
||||
Component.install = (app: App) => {
|
||||
app.component(Component.name, Component);
|
||||
};
|
||||
|
||||
export default Component;
|
||||
153
src/component/inputNumber/index.vue
Normal file
153
src/component/inputNumber/index.vue
Normal file
@@ -0,0 +1,153 @@
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: "LayInputNumber",
|
||||
};
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import layButton from "../button/index";
|
||||
import layIcon from "../icon/index";
|
||||
import layInput from "../input/index";
|
||||
import "./index.less";
|
||||
import {
|
||||
defineProps,
|
||||
defineEmits,
|
||||
ref,
|
||||
watch,
|
||||
withDefaults,
|
||||
computed,
|
||||
} from "vue";
|
||||
|
||||
export interface LayInputNumberProps {
|
||||
modelValue?: number;
|
||||
name?: string;
|
||||
disabled?: boolean;
|
||||
disabledInput?: boolean;
|
||||
step?: number;
|
||||
position?: "right";
|
||||
min?: number;
|
||||
max?: number;
|
||||
size?: "md" | "sm" | "xs";
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<LayInputNumberProps>(), {
|
||||
disabled: false,
|
||||
disabledInput: false,
|
||||
modelValue: 0,
|
||||
step: 1,
|
||||
min: -Infinity,
|
||||
max: Infinity,
|
||||
});
|
||||
|
||||
const emit = defineEmits(["update:modelValue", "change"]);
|
||||
|
||||
let num = ref(props.modelValue);
|
||||
watch(num, (val) => {
|
||||
if (props.max !== Infinity && val > props.max) {
|
||||
num.value = props.max;
|
||||
return;
|
||||
}
|
||||
if (props.min !== -Infinity && val < props.min) {
|
||||
num.value = props.min;
|
||||
return;
|
||||
}
|
||||
if (isNumber(num.value)) {
|
||||
tempValue.value = Number(num.value);
|
||||
emit("update:modelValue", tempValue.value);
|
||||
emit("change", tempValue.value);
|
||||
}
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(val) => {
|
||||
if (val !== num.value) {
|
||||
num.value = props.modelValue;
|
||||
}
|
||||
}
|
||||
);
|
||||
const tempValue = ref(0);
|
||||
let timer: any = 0;
|
||||
|
||||
const minControl = computed(
|
||||
() => props.min !== -Infinity && Number(props.min) >= num.value
|
||||
);
|
||||
const maxControl = computed(
|
||||
() => props.max !== Infinity && Number(props.max) <= num.value
|
||||
);
|
||||
|
||||
const addition = function () {
|
||||
num.value += Number(props.step);
|
||||
};
|
||||
|
||||
const subtraction = function () {
|
||||
num.value -= Number(props.step);
|
||||
};
|
||||
|
||||
const longDown = function (fn: Function) {
|
||||
cancelLongDown();
|
||||
if (props.disabled) {
|
||||
return;
|
||||
}
|
||||
timer = setInterval(() => fn.call(timer), 150);
|
||||
fn.call(timer);
|
||||
};
|
||||
|
||||
const cancelLongDown = function () {
|
||||
clearInterval(timer);
|
||||
};
|
||||
|
||||
const inputChange = function () {
|
||||
if (isNumber(num.value)) {
|
||||
tempValue.value = Number(num.value);
|
||||
return;
|
||||
}
|
||||
num.value = tempValue.value;
|
||||
};
|
||||
|
||||
const isNumber = function (num: any) {
|
||||
return /^\d+(\.\d+)?$/.test(num);
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="layui-input-number" :position="position" :size="size">
|
||||
<lay-button
|
||||
type="primary"
|
||||
size="gl"
|
||||
@mousedown="longDown(subtraction)"
|
||||
@mouseup="cancelLongDown"
|
||||
@blur="cancelLongDown"
|
||||
:disabled="minControl"
|
||||
class="layui-control-btn layui-subtraction-btn"
|
||||
>
|
||||
<lay-icon
|
||||
:type="
|
||||
position === 'right' ? 'layui-icon-down' : 'layui-icon-subtraction'
|
||||
"
|
||||
/>
|
||||
</lay-button>
|
||||
<div class="layui-input-number-input">
|
||||
<lay-input
|
||||
v-model="num"
|
||||
:readonly="disabledInput || disabled"
|
||||
type="number"
|
||||
:name="name"
|
||||
@change="inputChange"
|
||||
/>
|
||||
</div>
|
||||
<lay-button
|
||||
type="primary"
|
||||
size="gl"
|
||||
@mousedown="longDown(addition)"
|
||||
@mouseup="cancelLongDown"
|
||||
@blur="cancelLongDown"
|
||||
:disabled="maxControl"
|
||||
class="layui-control-btn layui-addition-btn"
|
||||
>
|
||||
<lay-icon
|
||||
:type="position === 'right' ? 'layui-icon-up' : 'layui-icon-addition'"
|
||||
/>
|
||||
</lay-button>
|
||||
</div>
|
||||
</template>
|
||||
9
src/component/layer/index.ts
Normal file
9
src/component/layer/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import type { App } from "vue";
|
||||
import { LayLayer } from "@layui/layer-vue";
|
||||
import type { IDefineComponent } from "../../types/index";
|
||||
|
||||
LayLayer.install = (app: App) => {
|
||||
app.component(LayLayer.name || "LayLayer", LayLayer);
|
||||
};
|
||||
|
||||
export default LayLayer as IDefineComponent;
|
||||
10
src/component/layout/index.less
Normal file
10
src/component/layout/index.less
Normal file
@@ -0,0 +1,10 @@
|
||||
.layui-layout {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-basis: auto;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.layui-layout-vertical {
|
||||
flex-direction: column;
|
||||
}
|
||||
8
src/component/layout/index.ts
Normal file
8
src/component/layout/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import type { App } from "vue";
|
||||
import Component from "./index.vue";
|
||||
|
||||
Component.install = (app: App) => {
|
||||
app.component(Component.name, Component);
|
||||
};
|
||||
|
||||
export default Component;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user