修复 src 结构
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;
|
||||
}
|
||||
9
src/component/avatar/index.ts
Normal file
9
src/component/avatar/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import type { App } from "vue";
|
||||
import Component from "./index.vue";
|
||||
import type { IDefineComponent } from "../type/index";
|
||||
|
||||
Component.install = (app: App) => {
|
||||
app.component(Component.name || "LayAvatar", Component);
|
||||
};
|
||||
|
||||
export default Component as IDefineComponent;
|
||||
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;
|
||||
radius?: boolean;
|
||||
size?: string;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<img
|
||||
:src="src"
|
||||
class="layui-avatar"
|
||||
:class="[
|
||||
radius ? 'layui-avatar-radius' : '',
|
||||
size ? 'layui-avatar-' + size : '',
|
||||
]"
|
||||
/>
|
||||
</template>
|
||||
9
src/component/avatarList/index.ts
Normal file
9
src/component/avatarList/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import type { App } from "vue";
|
||||
import Component from "./index.vue";
|
||||
import type { IDefineComponent } from "../type/index";
|
||||
|
||||
Component.install = (app: App) => {
|
||||
app.component(Component.name || "LayAvatarList", Component);
|
||||
};
|
||||
|
||||
export default Component as IDefineComponent;
|
||||
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;
|
||||
}
|
||||
9
src/component/backTop/index.ts
Normal file
9
src/component/backTop/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import type { App } from "vue";
|
||||
import Component from "./index.vue";
|
||||
import type { IDefineComponent } from "../type/index";
|
||||
|
||||
Component.install = (app: App) => {
|
||||
app.component(Component.name || "LayBacktop", Component);
|
||||
};
|
||||
|
||||
export default Component as IDefineComponent;
|
||||
217
src/component/backTop/index.vue
Normal file
217
src/component/backTop/index.vue
Normal file
@@ -0,0 +1,217 @@
|
||||
<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>
|
||||
|
||||
<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>
|
||||
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;
|
||||
}
|
||||
9
src/component/badge/index.ts
Normal file
9
src/component/badge/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import type { App } from "vue";
|
||||
import Component from "./index.vue";
|
||||
import type { IDefineComponent } from "../type/index";
|
||||
|
||||
Component.install = (app: App) => {
|
||||
app.component(Component.name || "LayBadge", Component);
|
||||
};
|
||||
|
||||
export default Component as IDefineComponent;
|
||||
38
src/component/badge/index.vue
Normal file
38
src/component/badge/index.vue
Normal file
@@ -0,0 +1,38 @@
|
||||
<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;
|
||||
}
|
||||
9
src/component/block/index.ts
Normal file
9
src/component/block/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import type { App } from "vue";
|
||||
import Component from "./index.vue";
|
||||
import type { IDefineComponent } from "../type/index";
|
||||
|
||||
Component.install = (app: App) => {
|
||||
app.component(Component.name || "LayBlock", Component);
|
||||
};
|
||||
|
||||
export default Component as IDefineComponent;
|
||||
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;
|
||||
}
|
||||
9
src/component/body/index.ts
Normal file
9
src/component/body/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import type { App } from "vue";
|
||||
import Component from "./index.vue";
|
||||
import type { IDefineComponent } from "../type/index";
|
||||
|
||||
Component.install = (app: App) => {
|
||||
app.component(Component.name || "LayBody", Component);
|
||||
};
|
||||
|
||||
export default Component as IDefineComponent;
|
||||
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 name="LayBody" lang="ts">
|
||||
import "./index.less";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="layui-body">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
9
src/component/breadcrumb/index.ts
Normal file
9
src/component/breadcrumb/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import type { App } from "vue";
|
||||
import Component from "./index.vue";
|
||||
import type { IDefineComponent } from "../type/index";
|
||||
|
||||
Component.install = (app: App) => {
|
||||
app.component(Component.name || "LayBreadcrumb", Component);
|
||||
};
|
||||
|
||||
export default Component as IDefineComponent;
|
||||
20
src/component/breadcrumb/index.vue
Normal file
20
src/component/breadcrumb/index.vue
Normal file
@@ -0,0 +1,20 @@
|
||||
<template>
|
||||
<span class="layui-breadcrumb" style="visibility: visible">
|
||||
<slot></slot>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script setup name="LayBreadcrumb" lang="ts">
|
||||
import { defineProps, provide, withDefaults } from "vue";
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
separator?: string;
|
||||
}>(),
|
||||
{
|
||||
separator: "/",
|
||||
}
|
||||
);
|
||||
|
||||
provide("separator", props.separator);
|
||||
</script>
|
||||
9
src/component/breadcrumbItem/index.ts
Normal file
9
src/component/breadcrumbItem/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import type { App } from "vue";
|
||||
import Component from "./index.vue";
|
||||
import type { IDefineComponent } from "../type/index";
|
||||
|
||||
Component.install = (app: App) => {
|
||||
app.component(Component.name || "LayBreadcrumbItem", Component);
|
||||
};
|
||||
|
||||
export default Component as IDefineComponent;
|
||||
23
src/component/breadcrumbItem/index.vue
Normal file
23
src/component/breadcrumbItem/index.vue
Normal file
@@ -0,0 +1,23 @@
|
||||
<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 setup name="LayBreadcrumbItem" lang="ts">
|
||||
import { defineProps, inject, useSlots } from "vue";
|
||||
|
||||
const slot = useSlots();
|
||||
|
||||
const props = defineProps<{
|
||||
title?: string;
|
||||
}>();
|
||||
|
||||
const separator = inject("separator");
|
||||
</script>
|
||||
104
src/component/button/index.less
Normal file
104
src/component/button/index.less
Normal file
@@ -0,0 +1,104 @@
|
||||
.layui-btn {
|
||||
height: 38px;
|
||||
line-height: 36px;
|
||||
border: 1px solid transparent;
|
||||
padding: 0 18px;
|
||||
background-color: #009688;
|
||||
color: #fff;
|
||||
white-space: nowrap;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
border-radius: 2px;
|
||||
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 {
|
||||
border-color: #009688;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.layui-btn-normal {
|
||||
background-color: #1e9fff;
|
||||
}
|
||||
|
||||
.layui-btn-warm {
|
||||
background-color: #ffb800;
|
||||
}
|
||||
|
||||
.layui-btn-danger {
|
||||
background-color: #ff5722;
|
||||
}
|
||||
|
||||
.layui-btn-checked {
|
||||
background-color: #5fb878;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.layui-btn-xs i {
|
||||
font-size: 12px !important;
|
||||
}
|
||||
|
||||
.layui-btn-fluid {
|
||||
width: 100%;
|
||||
}
|
||||
9
src/component/button/index.ts
Normal file
9
src/component/button/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import type { App } from "vue";
|
||||
import Component from "./index.vue";
|
||||
import type { IDefineComponent } from "../type/index";
|
||||
|
||||
Component.install = (app: App) => {
|
||||
app.component(Component.name || "LayButton", Component);
|
||||
};
|
||||
|
||||
export default Component as IDefineComponent;
|
||||
67
src/component/button/index.vue
Normal file
67
src/component/button/index.vue
Normal file
@@ -0,0 +1,67 @@
|
||||
<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";
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<LayButtonProps>(), {
|
||||
fluid: false,
|
||||
radius: false,
|
||||
loading: false,
|
||||
disabled: false,
|
||||
nativeType: "button",
|
||||
});
|
||||
|
||||
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="loading"
|
||||
class="layui-icon layui-icon-loading-one layui-anim layui-anim-rotate layui-anim-loop"
|
||||
></i>
|
||||
<slot v-else></slot>
|
||||
</button>
|
||||
</template>
|
||||
16
src/component/buttonContainer/index.less
Normal file
16
src/component/buttonContainer/index.less
Normal file
@@ -0,0 +1,16 @@
|
||||
.layui-btn-container {
|
||||
font-size: 0;
|
||||
}
|
||||
|
||||
.layui-btn-container .layui-btn {
|
||||
margin-right: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.layui-btn-container .layui-btn + .layui-btn {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.layui-table .layui-btn-container .layui-btn {
|
||||
margin-bottom: 9px;
|
||||
}
|
||||
9
src/component/buttonContainer/index.ts
Normal file
9
src/component/buttonContainer/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import type { App } from "vue";
|
||||
import Component from "./index.vue";
|
||||
import type { IDefineComponent } from "../type/index";
|
||||
|
||||
Component.install = (app: App) => {
|
||||
app.component(Component.name || "LayButtonContainer", Component);
|
||||
};
|
||||
|
||||
export default Component as IDefineComponent;
|
||||
15
src/component/buttonContainer/index.vue
Normal file
15
src/component/buttonContainer/index.vue
Normal file
@@ -0,0 +1,15 @@
|
||||
<template>
|
||||
<div class="layui-btn-container">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: "LayButtonContainer",
|
||||
};
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import "./index.less";
|
||||
</script>
|
||||
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;
|
||||
}
|
||||
9
src/component/buttonGroup/index.ts
Normal file
9
src/component/buttonGroup/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import type { App } from "vue";
|
||||
import Component from "./index.vue";
|
||||
import type { IDefineComponent } from "../type/index";
|
||||
|
||||
Component.install = (app: App) => {
|
||||
app.component(Component.name || "LayButtonGroup", Component);
|
||||
};
|
||||
|
||||
export default Component as IDefineComponent;
|
||||
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>
|
||||
41
src/component/card/index.less
Normal file
41
src/component/card/index.less
Normal file
@@ -0,0 +1,41 @@
|
||||
.layui-card {
|
||||
margin-bottom: 15px;
|
||||
border-radius: 2px;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.layui-card:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.layui-card-header {
|
||||
height: 42px;
|
||||
line-height: 42px;
|
||||
padding: 0 15px;
|
||||
border-bottom: 1px solid #f6f6f6;
|
||||
color: #333;
|
||||
border-radius: 2px 2px 0 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.layui-card-body {
|
||||
padding: 10px 15px;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.layui-card-body[pad15] {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.layui-card-body[pad20] {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.layui-card-body .layui-table {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.layui-card .layui-tab {
|
||||
margin: 0;
|
||||
}
|
||||
9
src/component/card/index.ts
Normal file
9
src/component/card/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import type { App } from "vue";
|
||||
import Component from "./index.vue";
|
||||
import type { IDefineComponent } from "../type/index";
|
||||
|
||||
Component.install = (app: App) => {
|
||||
app.component(Component.name || "LayCard ", Component);
|
||||
};
|
||||
|
||||
export default Component as IDefineComponent;
|
||||
25
src/component/card/index.vue
Normal file
25
src/component/card/index.vue
Normal file
@@ -0,0 +1,25 @@
|
||||
<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>
|
||||
|
||||
<script setup name="LayCard" lang="ts">
|
||||
import { useSlots } from "vue";
|
||||
import "./index.less";
|
||||
|
||||
const slot = useSlots();
|
||||
|
||||
export interface LayCardProps {
|
||||
title?: string;
|
||||
}
|
||||
|
||||
const props = defineProps<LayCardProps>();
|
||||
</script>
|
||||
9
src/component/carousel/index.ts
Normal file
9
src/component/carousel/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import type { App } from "vue";
|
||||
import Component from "./index.vue";
|
||||
import type { IDefineComponent } from "../type/index";
|
||||
|
||||
Component.install = (app: App) => {
|
||||
app.component(Component.name || "LayCarousel", Component);
|
||||
};
|
||||
|
||||
export default Component as IDefineComponent;
|
||||
109
src/component/carousel/index.vue
Normal file
109
src/component/carousel/index.vue
Normal file
@@ -0,0 +1,109 @@
|
||||
<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>
|
||||
<script setup name="LayCarousel" 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>
|
||||
9
src/component/carouselItem/index.ts
Normal file
9
src/component/carouselItem/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import type { App } from "vue";
|
||||
import Component from "./index.vue";
|
||||
import type { IDefineComponent } from "../type/index";
|
||||
|
||||
Component.install = (app: App) => {
|
||||
app.component(Component.name || "LayCarouselItem", Component);
|
||||
};
|
||||
|
||||
export default Component as IDefineComponent;
|
||||
14
src/component/carouselItem/index.vue
Normal file
14
src/component/carouselItem/index.vue
Normal file
@@ -0,0 +1,14 @@
|
||||
<template>
|
||||
<li :class="[active === id ? 'layui-this' : '']">
|
||||
<slot></slot>
|
||||
</li>
|
||||
</template>
|
||||
<script setup name="LayCarouselItem" lang="ts">
|
||||
import { defineProps, inject } from "vue";
|
||||
|
||||
const props = defineProps<{
|
||||
id: string;
|
||||
}>();
|
||||
|
||||
const active = inject("active");
|
||||
</script>
|
||||
0
src/component/checkbox/index.less
Normal file
0
src/component/checkbox/index.less
Normal file
9
src/component/checkbox/index.ts
Normal file
9
src/component/checkbox/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import type { App } from "vue";
|
||||
import Component from "./index.vue";
|
||||
import type { IDefineComponent } from "../type/index";
|
||||
|
||||
Component.install = (app: App) => {
|
||||
app.component(Component.name || "LayCheckbox", Component);
|
||||
};
|
||||
|
||||
export default Component as IDefineComponent;
|
||||
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>
|
||||
9
src/component/checkboxGroup/index.ts
Normal file
9
src/component/checkboxGroup/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import type { App } from "vue";
|
||||
import Component from "./index.vue";
|
||||
import type { IDefineComponent } from "../type/index";
|
||||
|
||||
Component.install = (app: App) => {
|
||||
app.component(Component.name || "LayCheckboxGroup", Component);
|
||||
};
|
||||
|
||||
export default Component as IDefineComponent;
|
||||
44
src/component/checkboxGroup/index.vue
Normal file
44
src/component/checkboxGroup/index.vue
Normal file
@@ -0,0 +1,44 @@
|
||||
<template>
|
||||
<div class="layui-checkbox-group">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: "LayCheckboxGroup",
|
||||
};
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, defineProps, provide, ref, watch } from "vue";
|
||||
import { Recordable } from "../type";
|
||||
|
||||
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>
|
||||
9
src/component/col/index.ts
Normal file
9
src/component/col/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import type { App } from "vue";
|
||||
import Component from "./index.vue";
|
||||
import type { IDefineComponent } from "../type/index";
|
||||
|
||||
Component.install = (app: App) => {
|
||||
app.component(Component.name || "LayCol", Component);
|
||||
};
|
||||
|
||||
export default Component as IDefineComponent;
|
||||
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>
|
||||
9
src/component/collapse/index.ts
Normal file
9
src/component/collapse/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import type { App } from "vue";
|
||||
import Component from "./index.vue";
|
||||
import type { IDefineComponent } from "../type/index";
|
||||
|
||||
Component.install = (app: App) => {
|
||||
app.component(Component.name || "LayCollapse", Component);
|
||||
};
|
||||
|
||||
export default Component as IDefineComponent;
|
||||
45
src/component/collapse/index.vue
Normal file
45
src/component/collapse/index.vue
Normal file
@@ -0,0 +1,45 @@
|
||||
<template>
|
||||
<div class="layui-collapse">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup 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>
|
||||
9
src/component/collapseItem/index.ts
Normal file
9
src/component/collapseItem/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import type { App } from "vue";
|
||||
import Component from "./index.vue";
|
||||
import type { IDefineComponent } from "../type/index";
|
||||
|
||||
Component.install = (app: App) => {
|
||||
app.component(Component.name || "LayCollapseItem", Component);
|
||||
};
|
||||
|
||||
export default Component as IDefineComponent;
|
||||
60
src/component/collapseItem/index.vue
Normal file
60
src/component/collapseItem/index.vue
Normal file
@@ -0,0 +1,60 @@
|
||||
<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>
|
||||
|
||||
<script setup name="LayCollapseItem" 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>
|
||||
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 "../type/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;
|
||||
}
|
||||
9
src/component/container/index.ts
Normal file
9
src/component/container/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import type { App } from "vue";
|
||||
import Component from "./index.vue";
|
||||
import type { IDefineComponent } from "../type/index";
|
||||
|
||||
Component.install = (app: App) => {
|
||||
app.component(Component.name || "LayContainer", Component);
|
||||
};
|
||||
|
||||
export default Component as IDefineComponent;
|
||||
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>
|
||||
9
src/component/countUp/index.ts
Normal file
9
src/component/countUp/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import type { App } from "vue";
|
||||
import Component from "./index.vue";
|
||||
import type { IDefineComponent } from "../type/index";
|
||||
|
||||
Component.install = (app: App) => {
|
||||
app.component(Component.name || "LayCountUp", Component);
|
||||
};
|
||||
|
||||
export default Component as IDefineComponent;
|
||||
74
src/component/countUp/index.vue
Normal file
74
src/component/countUp/index.vue
Normal file
@@ -0,0 +1,74 @@
|
||||
<template>
|
||||
<slot name="prefix"></slot>
|
||||
<span ref="counterRef" style="font-family:sans-serif;" />
|
||||
<slot name="suffix"></slot>
|
||||
</template>
|
||||
|
||||
<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>
|
||||
9
src/component/dropdown/index.ts
Normal file
9
src/component/dropdown/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import type { App } from "vue";
|
||||
import Component from "./index.vue";
|
||||
import type { IDefineComponent } from "../type/index";
|
||||
|
||||
Component.install = (app: App) => {
|
||||
app.component(Component.name || "LayDropdown", Component);
|
||||
};
|
||||
|
||||
export default Component as IDefineComponent;
|
||||
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>
|
||||
9
src/component/dropdownItem/index.ts
Normal file
9
src/component/dropdownItem/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import type { App } from "vue";
|
||||
import Component from "./index.vue";
|
||||
import type { IDefineComponent } from "../type/index";
|
||||
|
||||
Component.install = (app: App) => {
|
||||
app.component(Component.name || "LayDropdownItem", Component);
|
||||
};
|
||||
|
||||
export default Component as IDefineComponent;
|
||||
17
src/component/dropdownItem/index.vue
Normal file
17
src/component/dropdownItem/index.vue
Normal file
@@ -0,0 +1,17 @@
|
||||
<template>
|
||||
<li>
|
||||
<div class="layui-menu-body-title" @click="click">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</li>
|
||||
</template>
|
||||
|
||||
<script setup name="LayDropdownItem" lang="ts">
|
||||
import { inject, Ref } from "vue";
|
||||
|
||||
const openState: Ref<boolean> = inject("openState") as Ref<boolean>;
|
||||
|
||||
const click = function () {
|
||||
openState.value = false;
|
||||
};
|
||||
</script>
|
||||
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;
|
||||
}
|
||||
1
src/component/empty/index.svg
Normal file
1
src/component/empty/index.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg width="184" height="152" viewBox="0 0 184 152" xmlns="http://www.w3.org/2000/svg"><g fill="none" fillRule="evenodd"><g transform="translate(24 31.67)"><ellipse fillOpacity=".8" fill="#F5F5F7" cx="67.797" cy="106.89" rx="67.797" ry="12.668"></ellipse><path d="M122.034 69.674L98.109 40.229c-1.148-1.386-2.826-2.225-4.593-2.225h-51.44c-1.766 0-3.444.839-4.592 2.225L13.56 69.674v15.383h108.475V69.674z" fill="#AEB8C2"></path><path d="M101.537 86.214L80.63 61.102c-1.001-1.207-2.507-1.867-4.048-1.867H31.724c-1.54 0-3.047.66-4.048 1.867L6.769 86.214v13.792h94.768V86.214z" fill="url(#linearGradient-1)" transform="translate(13.56)"></path><path d="M33.83 0h67.933a4 4 0 0 1 4 4v93.344a4 4 0 0 1-4 4H33.83a4 4 0 0 1-4-4V4a4 4 0 0 1 4-4z" fill="#F5F5F7"></path><path d="M42.678 9.953h50.237a2 2 0 0 1 2 2V36.91a2 2 0 0 1-2 2H42.678a2 2 0 0 1-2-2V11.953a2 2 0 0 1 2-2zM42.94 49.767h49.713a2.262 2.262 0 1 1 0 4.524H42.94a2.262 2.262 0 0 1 0-4.524zM42.94 61.53h49.713a2.262 2.262 0 1 1 0 4.525H42.94a2.262 2.262 0 0 1 0-4.525zM121.813 105.032c-.775 3.071-3.497 5.36-6.735 5.36H20.515c-3.238 0-5.96-2.29-6.734-5.36a7.309 7.309 0 0 1-.222-1.79V69.675h26.318c2.907 0 5.25 2.448 5.25 5.42v.04c0 2.971 2.37 5.37 5.277 5.37h34.785c2.907 0 5.277-2.421 5.277-5.393V75.1c0-2.972 2.343-5.426 5.25-5.426h26.318v33.569c0 .617-.077 1.216-.221 1.789z" fill="#DCE0E6"></path></g><path d="M149.121 33.292l-6.83 2.65a1 1 0 0 1-1.317-1.23l1.937-6.207c-2.589-2.944-4.109-6.534-4.109-10.408C138.802 8.102 148.92 0 161.402 0 173.881 0 184 8.102 184 18.097c0 9.995-10.118 18.097-22.599 18.097-4.528 0-8.744-1.066-12.28-2.902z" fill="#DCE0E6"></path><g transform="translate(149.65 15.383)" fill="#FFF"><ellipse cx="20.654" cy="3.167" rx="2.849" ry="2.815"></ellipse><path d="M5.698 5.63H0L2.898.704zM9.259.704h4.985V5.63H9.259z"></path></g></g></svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
9
src/component/empty/index.ts
Normal file
9
src/component/empty/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import type { App } from "vue";
|
||||
import Component from "./index.vue";
|
||||
import type { IDefineComponent } from "../type/index";
|
||||
|
||||
Component.install = (app: App) => {
|
||||
app.component(Component.name || "LayEmpty", Component);
|
||||
};
|
||||
|
||||
export default Component as IDefineComponent;
|
||||
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>
|
||||
9
src/component/field/index.ts
Normal file
9
src/component/field/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import type { App } from "vue";
|
||||
import Component from "./index.vue";
|
||||
import type { IDefineComponent } from "../type/index";
|
||||
|
||||
Component.install = (app: App) => {
|
||||
app.component(Component.name || "LayField", Component);
|
||||
};
|
||||
|
||||
export default Component as IDefineComponent;
|
||||
24
src/component/field/index.vue
Normal file
24
src/component/field/index.vue
Normal file
@@ -0,0 +1,24 @@
|
||||
<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>
|
||||
|
||||
<script setup name="LayField" lang="ts">
|
||||
import { defineProps, useSlots } from "vue";
|
||||
|
||||
const slot = useSlots();
|
||||
|
||||
const props = defineProps<{
|
||||
title?: string;
|
||||
}>();
|
||||
</script>
|
||||
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;
|
||||
}
|
||||
9
src/component/footer/index.ts
Normal file
9
src/component/footer/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import type { App } from "vue";
|
||||
import Component from "./index.vue";
|
||||
import type { IDefineComponent } from "../type/index";
|
||||
|
||||
Component.install = (app: App) => {
|
||||
app.component(Component.name || "LayFooter", Component);
|
||||
};
|
||||
|
||||
export default Component as IDefineComponent;
|
||||
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>
|
||||
9
src/component/form/index.ts
Normal file
9
src/component/form/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import type { App } from "vue";
|
||||
import Component from "./index.vue";
|
||||
import type { IDefineComponent } from "../type/index";
|
||||
|
||||
Component.install = (app: App) => {
|
||||
app.component(Component.name || "LayForm", Component);
|
||||
};
|
||||
|
||||
export default Component as IDefineComponent;
|
||||
162
src/component/form/index.vue
Normal file
162
src/component/form/index.vue
Normal file
@@ -0,0 +1,162 @@
|
||||
<template>
|
||||
<form class="layui-form" :onsubmit="submit">
|
||||
<slot></slot>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<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 {
|
||||
layFormKey,
|
||||
LayFormItemContext,
|
||||
FormCallback,
|
||||
modelType,
|
||||
} from "../type/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(
|
||||
layFormKey,
|
||||
reactive({
|
||||
formItems,
|
||||
addField,
|
||||
clearValidate,
|
||||
validate,
|
||||
...toRefs(props),
|
||||
})
|
||||
);
|
||||
</script>
|
||||
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);
|
||||
}
|
||||
}
|
||||
9
src/component/formItem/index.ts
Normal file
9
src/component/formItem/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import type { App } from "vue";
|
||||
import Component from "./index.vue";
|
||||
import type { IDefineComponent } from "../type/index";
|
||||
|
||||
Component.install = (app: App) => {
|
||||
app.component(Component.name || "LayFormItem", Component);
|
||||
};
|
||||
|
||||
export default Component as IDefineComponent;
|
||||
194
src/component/formItem/index.vue
Normal file
194
src/component/formItem/index.vue
Normal file
@@ -0,0 +1,194 @@
|
||||
<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>
|
||||
<script setup name="LayFormItem" lang="ts">
|
||||
import "./index.less";
|
||||
import {
|
||||
defineProps,
|
||||
inject,
|
||||
withDefaults,
|
||||
ref,
|
||||
reactive,
|
||||
toRefs,
|
||||
onMounted,
|
||||
computed,
|
||||
watch,
|
||||
} from "vue";
|
||||
import {
|
||||
layFormKey,
|
||||
LayFormContext,
|
||||
LayFormItemContext,
|
||||
FieldValidateError,
|
||||
} from "../type/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(layFormKey, {} 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>
|
||||
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;
|
||||
}
|
||||
9
src/component/header/index.ts
Normal file
9
src/component/header/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import type { App } from "vue";
|
||||
import Component from "./index.vue";
|
||||
import type { IDefineComponent } from "../type/index";
|
||||
|
||||
Component.install = (app: App) => {
|
||||
app.component(Component.name || "LayHeader", Component);
|
||||
};
|
||||
|
||||
export default Component as IDefineComponent;
|
||||
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>
|
||||
9
src/component/icon/index.ts
Normal file
9
src/component/icon/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import type { App } from "vue";
|
||||
import { LayIcon as Component } from "@layui/icons-vue";
|
||||
import type { IDefineComponent } from "../type/index";
|
||||
|
||||
Component.install = (app: App) => {
|
||||
app.component(Component.name || "LayIcon", Component);
|
||||
};
|
||||
|
||||
export default Component as IDefineComponent;
|
||||
9
src/component/iconPicker/index.ts
Normal file
9
src/component/iconPicker/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import type { App } from "vue";
|
||||
import Component from "./index.vue";
|
||||
import type { IDefineComponent } from "../type/index";
|
||||
|
||||
Component.install = (app: App) => {
|
||||
app.component(Component.name || "LayIconPicker", Component);
|
||||
};
|
||||
|
||||
export default Component as IDefineComponent;
|
||||
196
src/component/iconPicker/index.vue
Normal file
196
src/component/iconPicker/index.vue
Normal file
@@ -0,0 +1,196 @@
|
||||
<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 setup name="LayIconPicker" 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
9
src/component/input/index.ts
Normal file
9
src/component/input/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import type { App } from "vue";
|
||||
import Component from "./index.vue";
|
||||
import type { IDefineComponent } from "../type/index";
|
||||
|
||||
Component.install = (app: App) => {
|
||||
app.component(Component.name || "LayInput", Component);
|
||||
};
|
||||
|
||||
export default Component as IDefineComponent;
|
||||
44
src/component/input/index.vue
Normal file
44
src/component/input/index.vue
Normal file
@@ -0,0 +1,44 @@
|
||||
<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>
|
||||
|
||||
<script setup name="LayInput" 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>
|
||||
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);
|
||||
}
|
||||
}
|
||||
9
src/component/inputNumber/index.ts
Normal file
9
src/component/inputNumber/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import type { App } from "vue";
|
||||
import Component from "./index.vue";
|
||||
import type { IDefineComponent } from "../type/index";
|
||||
|
||||
Component.install = (app: App) => {
|
||||
app.component(Component.name || "LayInputNumber", Component);
|
||||
};
|
||||
|
||||
export default Component as IDefineComponent;
|
||||
153
src/component/inputNumber/index.vue
Normal file
153
src/component/inputNumber/index.vue
Normal file
@@ -0,0 +1,153 @@
|
||||
<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>
|
||||
|
||||
<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>
|
||||
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 "../type/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;
|
||||
}
|
||||
9
src/component/layout/index.ts
Normal file
9
src/component/layout/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import type { App } from "vue";
|
||||
import Component from "./index.vue";
|
||||
import type { IDefineComponent } from "../type/index";
|
||||
|
||||
Component.install = (app: App) => {
|
||||
app.component(Component.name || "LayLayout", Component);
|
||||
};
|
||||
|
||||
export default Component as IDefineComponent;
|
||||
41
src/component/layout/index.vue
Normal file
41
src/component/layout/index.vue
Normal file
@@ -0,0 +1,41 @@
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: "LayLayout",
|
||||
};
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Component, computed, useSlots } from "vue";
|
||||
import Header from "../header/index.vue";
|
||||
import "./index.less";
|
||||
|
||||
export interface LayLayoutProps {
|
||||
isVertical?: boolean;
|
||||
}
|
||||
|
||||
const slots = useSlots();
|
||||
|
||||
const props = withDefaults(defineProps<LayLayoutProps>(), {
|
||||
isVertical: false,
|
||||
});
|
||||
|
||||
const isVertical = computed(() => {
|
||||
if (!slots.default) return false;
|
||||
const vNodes = slots.default();
|
||||
return vNodes.some((vNode) => {
|
||||
const componentName = (vNode.type as Component).name;
|
||||
if (!componentName) return false;
|
||||
return [Header.name].includes(componentName);
|
||||
});
|
||||
});
|
||||
|
||||
const classes = computed(() => {
|
||||
return ["layui-layout", { "layui-layout-vertical": isVertical.value }];
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section :class="classes">
|
||||
<slot></slot>
|
||||
</section>
|
||||
</template>
|
||||
9
src/component/line/index.ts
Normal file
9
src/component/line/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import type { App } from "vue";
|
||||
import Component from "./index.vue";
|
||||
import type { IDefineComponent } from "../type/index";
|
||||
|
||||
Component.install = (app: App) => {
|
||||
app.component(Component.name || "LayLine", Component);
|
||||
};
|
||||
|
||||
export default Component as IDefineComponent;
|
||||
11
src/component/line/index.vue
Normal file
11
src/component/line/index.vue
Normal file
@@ -0,0 +1,11 @@
|
||||
<template>
|
||||
<hr :class="['layui-border-' + theme]" />
|
||||
</template>
|
||||
|
||||
<script setup name="LayLine" lang="ts">
|
||||
import { defineProps } from "vue";
|
||||
|
||||
const props = defineProps<{
|
||||
theme?: string;
|
||||
}>();
|
||||
</script>
|
||||
9
src/component/logo/index.ts
Normal file
9
src/component/logo/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import type { App } from "vue";
|
||||
import Component from "./index.vue";
|
||||
import type { IDefineComponent } from "../type/index";
|
||||
|
||||
Component.install = (app: App) => {
|
||||
app.component(Component.name || "LayLogo", Component);
|
||||
};
|
||||
|
||||
export default Component as IDefineComponent;
|
||||
7
src/component/logo/index.vue
Normal file
7
src/component/logo/index.vue
Normal file
@@ -0,0 +1,7 @@
|
||||
<template>
|
||||
<div class="layui-logo">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="LayLogo" lang="ts"></script>
|
||||
9
src/component/menu/index.ts
Normal file
9
src/component/menu/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import type { App } from "vue";
|
||||
import Component from "./index.vue";
|
||||
import type { IDefineComponent } from "../type/index";
|
||||
|
||||
Component.install = (app: App) => {
|
||||
app.component(Component.name || "LayMenu", Component);
|
||||
};
|
||||
|
||||
export default Component as IDefineComponent;
|
||||
47
src/component/menu/index.vue
Normal file
47
src/component/menu/index.vue
Normal file
@@ -0,0 +1,47 @@
|
||||
<template>
|
||||
<ul class="layui-nav" :class="[tree ? 'layui-nav-tree' : '']">
|
||||
<slot></slot>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<script setup name="LayMenu" lang="ts">
|
||||
import { computed, defineProps, provide } from "vue";
|
||||
|
||||
export interface LayMenuProps {
|
||||
selectedKey?: string;
|
||||
openKeys?: string[];
|
||||
tree?: boolean;
|
||||
}
|
||||
|
||||
const emit = defineEmits(["update:selectedKey", "update:openKeys"]);
|
||||
|
||||
const props = withDefaults(defineProps<LayMenuProps>(), {
|
||||
selectedKey: "",
|
||||
openKeys: () => [],
|
||||
tree: false,
|
||||
});
|
||||
|
||||
const isTree = computed(() => props.tree);
|
||||
|
||||
const openKeys = computed({
|
||||
get() {
|
||||
return props.openKeys;
|
||||
},
|
||||
set(val) {
|
||||
emit("update:selectedKey", val);
|
||||
},
|
||||
});
|
||||
|
||||
const selectedKey = computed({
|
||||
get() {
|
||||
return props.selectedKey;
|
||||
},
|
||||
set(val) {
|
||||
emit("update:selectedKey", val);
|
||||
},
|
||||
});
|
||||
|
||||
provide("isTree", isTree);
|
||||
provide("selectedKey", selectedKey);
|
||||
provide("openKeys", openKeys);
|
||||
</script>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user