修复 src 结构

This commit is contained in:
就眠儀式
2022-01-22 21:30:17 +08:00
parent 96bb84020b
commit d0debbb1b2
186 changed files with 251 additions and 251 deletions

View 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;
}

View 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;

View 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>

View 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;

View 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>

View 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;
}

View 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;

View 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>

View 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;
}

View 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;

View 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>

View 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;
}

View 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;

View 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>

View File

@@ -0,0 +1,8 @@
.layui-body {
display: block;
flex: 1;
overflow: auto;
height: 100%;
box-sizing: border-box;
min-height: 300px;
}

View 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;

View 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>

View 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;

View 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>

View 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;

View 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>

View 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%;
}

View 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;

View 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>

View 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;
}

View 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;

View 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>

View 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;
}

View 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;

View 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>

View 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;
}

View 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;

View 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>

View 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;

View 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>

View 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;

View 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>

View File

View 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;

View 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>

View 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;

View 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>

View 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;

View 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>

View 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;

View 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>

View 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;

View 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>

View 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>

View 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>

View 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";
}

View 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] };
}

View 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;

View 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>

View 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;
}

View 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;

View 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>

View 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;

View 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>

View 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;

View 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>

View 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;

View 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>

View 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;
}

View 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

View 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;

View 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>

View 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;

View 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>

View File

@@ -0,0 +1,3 @@
.layui-footer {
box-sizing: border-box;
}

View 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;

View 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>

View 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;

View 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>

View 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;

View 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);
}
}

View 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;

View 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>

View File

@@ -0,0 +1,4 @@
.layui-header {
box-sizing: border-box;
height: 60px;
}

View 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;

View 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>

View 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;

View 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;

View 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>

View File

View 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;

View 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>

View 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);
}
}

View 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;

View 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>

View 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;

View File

@@ -0,0 +1,10 @@
.layui-layout {
flex: 1;
display: flex;
flex-basis: auto;
box-sizing: border-box;
}
.layui-layout-vertical {
flex-direction: column;
}

View 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;

View 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>

View 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;

View 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>

View 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;

View File

@@ -0,0 +1,7 @@
<template>
<div class="layui-logo">
<slot></slot>
</div>
</template>
<script setup name="LayLogo" lang="ts"></script>

View 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;

View 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