chore: 升级 monorepo 架构

This commit is contained in:
就眠儀式
2022-04-05 10:31:31 +08:00
parent 436fd407ca
commit c7f8998376
659 changed files with 13598 additions and 1039 deletions

22
package/component/.gitignore vendored Normal file
View File

@@ -0,0 +1,22 @@
.DS_Store
node_modules/
dist/
example/dist/
lib/
es/
umd/
/types/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
yarn.lock
package-lock.json
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
pnpm-lock.yaml

View File

@@ -0,0 +1,75 @@
{
"name": "@layui/layui-vue",
"version": "1.0.0",
"author": "就眠儀式",
"license": "MIT",
"description": "a component library for Vue 3 base on layui-vue",
"homepage": "http://www.layui-vue.com",
"main": "es/index.js",
"unpkg": "umd/index.js",
"jsdelivr": "umd/index.js",
"types": "types/index.d.ts",
"style": "lib/index.css",
"keywords": [
"layui-vue",
"layui",
"vue"
],
"exports": {
".": {
"import": "./es/index.js"
},
"./lib/": "./lib/",
"./es/": "./es/"
},
"scripts": {
"build": "npm run build:all && npm run build:es && npm run build:umd && npm run build:types",
"build:es": "vite build --emptyOutDir --config ./script/build.es.ts",
"build:all": "vite build --emptyOutDir --config ./script/build.all.ts",
"build:umd": "vite build --emptyOutDir --config ./script/build.umd.ts",
"build:types": "rimraf types && tsc -d"
},
"dependencies": {
"@vueuse/core": "^7.6.2",
"async-validator": "^4.0.7",
"cropperjs": "^1.5.12",
"darkreader": "^4.9.46",
"evtd": "^0.2.3",
"moment": "^2.29.1",
"uuid": "^8.3.2",
"vue-i18n": "^9.2.0-beta.33",
"xlsx": "^0.18.4"
},
"devDependencies": {
"@babel/core": "^7.15.8",
"@babel/preset-env": "^7.15.8",
"@babel/preset-typescript": "^7.15.0",
"@rollup/plugin-babel": "^5.3.0",
"@types/markdown-it": "^12.2.3",
"@types/markdown-it-container": "^2.0.4",
"@types/node": "^16.11.9",
"@vitejs/plugin-vue": "^2.3.1",
"@vue/compiler-sfc": "^3.2.31",
"@vue/server-renderer": "^3.2.31",
"escape-html": "^1.0.3",
"less": "^4.1.2",
"markdown-it-container": "^3.0.0",
"prismjs": "^1.25.0",
"rimraf": "^3.0.2",
"rollup": "^2.70.1",
"typescript": "^4.5.5",
"vite": "2.9.1",
"vite-plugin-md": "^0.11.6"
},
"files": [
"lib",
"es",
"umd",
"types"
],
"browserslist": [
"current node",
"last 2 versions and > 2%",
"ie > 10"
]
}

View File

@@ -0,0 +1,38 @@
import { UserConfigExport } from "vite";
import vue from "@vitejs/plugin-vue";
import { resolve } from "path";
export default (): UserConfigExport => {
return {
publicDir: false,
resolve: {
alias: [
{
find: "@",
replacement: resolve(process.cwd(), "./"),
},
],
},
plugins: [vue()],
build: {
cssCodeSplit: false,
outDir: "lib",
emptyOutDir: true,
lib: {
entry: resolve(process.cwd(), "./src/index.ts"),
name: "layui-vue",
formats: ["es"],
fileName: (name) => `index.js`,
},
rollupOptions: {
output: {
globals: {
vue: "Vue",
},
assetFileNames: "index.css",
},
external: ["vue"],
},
},
};
};

View File

@@ -0,0 +1,67 @@
import { UserConfigExport } from "vite";
import vue from "@vitejs/plugin-vue";
import { resolve } from "path";
import * as fs from "fs";
const inputDir = resolve(process.cwd(), "./src/component");
const inputsArray = fs.readdirSync(inputDir).filter((name) => {
const componentDir = resolve(inputDir, name);
const isDir = fs.lstatSync(componentDir).isDirectory();
return isDir && fs.readdirSync(componentDir).includes("index.ts");
});
const inputs = inputsArray.reduce((backObj, pkgName) => {
backObj[pkgName] = resolve(
process.cwd(),
`./src/component/${pkgName}/index.ts`
);
return backObj;
}, {});
inputs["index"] = resolve(process.cwd(), "./src/index.ts");
export default (): UserConfigExport => {
return {
publicDir: false,
resolve: {
alias: [
{
find: "@",
replacement: resolve(process.cwd(), "./"),
},
],
},
css: {
preprocessorOptions: {
less: {
javascriptEnabled: true,
},
},
postcss: {},
},
plugins: [vue()],
build: {
cssCodeSplit: true,
emptyOutDir: true,
outDir: "es",
lib: {
entry: "./index.ts",
formats: ["es"],
},
rollupOptions: {
input: inputs,
output: {
globals: {
vue: "Vue",
},
entryFileNames: ({ name }) => {
return name === "index" ? "index.js" : "[name]/index.js";
},
assetFileNames: "[name]/index.css",
},
external: ["vue", "vue-router"],
},
},
};
};

View File

@@ -0,0 +1,38 @@
import { UserConfigExport } from "vite";
import vue from "@vitejs/plugin-vue";
import { resolve } from "path";
export default (): UserConfigExport => {
return {
publicDir: false,
resolve: {
alias: [
{
find: "@",
replacement: resolve(process.cwd(), "./"),
},
],
},
plugins: [vue()],
build: {
cssCodeSplit: false,
outDir: "umd",
emptyOutDir: true,
lib: {
entry: resolve(process.cwd(), "./src/index.ts"),
name: "LayuiVue",
formats: ["umd"],
fileName: (name) => `index.js`,
},
rollupOptions: {
output: {
globals: {
vue: "Vue",
},
assetFileNames: "index.css",
},
external: ["vue"],
},
},
};
};

25
package/component/shims-vue.d.ts vendored Normal file
View File

@@ -0,0 +1,25 @@
declare module "*.vue" {
import { DefineComponent } from "vue";
const comp: DefineComponent;
export default comp;
}
declare module "*.md" {
import { DefineComponent } from "vue";
const comp: DefineComponent;
export default comp;
}
declare module "prismjs";
declare module "prismjs/components/index";
declare module "escape-html";
interface ImportMeta {
env: {
MODE: string;
BASE_URL: string;
PROD: boolean;
DEV: boolean;
SSR: boolean;
};
}

View File

@@ -0,0 +1,49 @@
@import (reference) "../../theme/variable.less";
.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: @global-border-radius;
}
.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,8 @@
import type { App } from "vue";
import Component from "./index.vue";
Component.install = (app: App) => {
app.component(Component.name, Component);
};
export default Component;

View File

@@ -0,0 +1,31 @@
<script lang="ts">
export default {
name: "LayAvatar",
};
</script>
<script setup lang="ts">
import "./index.less";
export interface LayAvatarProps {
src?: string;
size?: "xs" | "sm" | "md" | "lg";
radius?: boolean;
}
const props = withDefaults(defineProps<LayAvatarProps>(), {
size: "md",
radius: false,
});
</script>
<template>
<img
:src="src"
class="layui-avatar"
:class="[
radius ? 'layui-avatar-radius' : '',
size ? 'layui-avatar-' + size : '',
]"
/>
</template>

View File

@@ -0,0 +1 @@
@import (reference) "../../theme/variable.less";

View File

@@ -0,0 +1,8 @@
import type { App } from "vue";
import Component from "./index.vue";
Component.install = (app: App) => {
app.component(Component.name, Component);
};
export default Component;

View File

@@ -0,0 +1,13 @@
<script lang="ts">
export default {
name: "LayAvatarList",
};
</script>
<script setup lang="ts"></script>
<template>
<div class="layui-avatar-list">
<slot></slot>
</div>
</template>

View File

@@ -0,0 +1,38 @@
@import (reference) "../../theme/variable.less";
/** 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: @global-border-radius;
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,8 @@
import type { App } from "vue";
import Component from "./index.vue";
Component.install = (app: App) => {
app.component(Component.name, Component);
};
export default Component;

View File

@@ -0,0 +1,225 @@
<script lang="ts">
export default {
name: "LayBacktop",
};
</script>
<script lang="ts" setup>
import {
ref,
shallowRef,
withDefaults,
computed,
onMounted,
onBeforeUnmount,
} from "vue";
import { useThrottle } from "@vueuse/core";
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));
});
onBeforeUnmount(() => {
scrollTarget.value?.removeEventListener(
"scroll",
throttle(handleScroll, 300)
);
});
</script>
<template>
<div
v-show="visible"
ref="backtopRef"
class="layui-backtop"
:class="classBacktop"
:style="{ ...styleBacktop }"
@click.stop="handleClick"
@mousedown="handlerMousedown"
@mouseup="handlerMouseup"
>
<slot>
<lay-icon
:type="props.icon"
:size="`${props.iconSize}px`"
:color="props.iconColor"
/>
</slot>
</div>
</template>

View File

@@ -0,0 +1,35 @@
@import (reference) "../../theme/variable.less";
.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: @global-border-radius;
}
.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,8 @@
import type { App } from "vue";
import Component from "./index.vue";
Component.install = (app: App) => {
app.component(Component.name, Component);
};
export default Component;

View File

@@ -0,0 +1,39 @@
<script lang="ts">
export default {
name: "LayBadge",
};
</script>
<script setup lang="ts">
import { computed } 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,10 @@
@import (reference) "../../theme/variable.less";
.layui-body {
display: block;
flex: 1;
overflow: auto;
height: 100%;
box-sizing: border-box;
min-height: 300px;
}

View File

@@ -0,0 +1,8 @@
import type { App } from "vue";
import Component from "./index.vue";
Component.install = (app: App) => {
app.component(Component.name, Component);
};
export default Component;

View File

@@ -0,0 +1,15 @@
<script lang="ts">
export default {
name: "LayBody",
};
</script>
<script setup lang="ts">
import "./index.less";
</script>
<template>
<div class="layui-body">
<slot></slot>
</div>
</template>

View File

@@ -0,0 +1,31 @@
@import (reference) "../../theme/variable.less";
.layui-breadcrumb {
font-size: 0;
}
.layui-breadcrumb > * {
font-size: 14px;
}
.layui-breadcrumb a {
color: #999;
}
.layui-breadcrumb a:hover {
color: @global-checked-color !important;
}
.layui-breadcrumb a:nth-last-child(2) {
color: #666;
font-style: normal;
}
.layui-breadcrumb span:last-child {
display: none;
}
.layui-breadcrumb span[lay-separator] {
margin: 0 10px;
color: @global-neutral-color-7;
}

View File

@@ -0,0 +1,8 @@
import type { App } from "vue";
import Component from "./index.vue";
Component.install = (app: App) => {
app.component(Component.name, Component);
};
export default Component;

View File

@@ -0,0 +1,26 @@
<script lang="ts">
export default {
name: "LayBreadcrumb",
};
</script>
<script setup lang="ts">
import "./index.less";
import { provide, withDefaults } from "vue";
export interface LayBreadcrumbProps {
separator?: string;
}
const props = withDefaults(defineProps<LayBreadcrumbProps>(), {
separator: "/",
});
provide("separator", props.separator);
</script>
<template>
<span class="layui-breadcrumb" style="visibility: visible">
<slot></slot>
</span>
</template>

View File

@@ -0,0 +1 @@
@import (reference) "../../theme/variable.less";

View File

@@ -0,0 +1,8 @@
import type { App } from "vue";
import Component from "./index.vue";
Component.install = (app: App) => {
app.component(Component.name, Component);
};
export default Component;

View File

@@ -0,0 +1,31 @@
<template>
<a href="javascript:void(0);">
<template v-if="slot.default">
<slot></slot>
</template>
<template v-else>
{{ title }}
</template>
</a>
<span lay-separator>{{ separator }}</span>
</template>
<script lang="ts">
export default {
name: "LayBreadcrumbItem",
};
</script>
<script setup lang="ts">
import { inject, useSlots } from "vue";
export interface LayBreadcrumbItemProps {
title?: string;
}
const slot = useSlots();
const props = defineProps<LayBreadcrumbItemProps>();
const separator = inject("separator");
</script>

View File

@@ -0,0 +1,131 @@
@import (reference) "../../theme/variable.less";
@button-primary-color: var(--button-primary-color);
@button-primary-border-color: var(--button-primary-border-color);
@button-normal-color: var(--button-normal-color);
@button-normal-border-color: var(--button-normal-border-color);
@button-warm-color: var(--button-warm-color);
@button-warm-border-color: var(--button-warm-border-color);
@button-danger-color: var(--button-danger-color);
@button-danger-border-color: var(--button-danger-border-color);
@button-border-radius: var(--button-border-radius);
@button-border-color: var(--button-border-color);
:root {
--button-primary-color: @global-primary-color;
--button-primary-border-color: @global-primary-color;
--button-normal-color: @global-normal-color;
--button-normal-border-color: @global-normal-color;
--button-warm-color: @global-warm-color;
--button-warm-border-color: @global-warm-color;
--button-danger-color: @global-danger-color;
--button-danger-border-color: @global-danger-color;
--button-border-radius: @global-border-radius;
--button-border-color: @global-neutral-color-6;
}
.layui-btn {
height: 38px;
line-height: 36px;
padding: 0 18px;
font-size: 14px;
text-align: center;
white-space: nowrap;
border-radius: @button-border-radius;
border-color: @button-border-color;
border-width: 1px;
border-style: solid;
background: 0 0;
color: #666;
cursor: pointer;
}
.layui-btn:hover,
.layui-btn:active {
color: #333;
}
.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 {
color: #fff;
background-color: @button-primary-color;
border-color: @button-primary-border-color;
}
.layui-btn-normal {
color: #fff;
background-color: @button-normal-color;
border-color: @button-normal-border-color;
}
.layui-btn-warm {
color: #fff;
background-color: @button-warm-color;
border-color: @button-warm-border-color;
}
.layui-btn-danger {
color: #fff;
background-color: @button-danger-color;
border-color: @button-danger-border-color;
}
.layui-btn-primary:hover,
.layui-btn-normal:hover,
.layui-btn-warm:hover,
.layui-btn-danger:hover {
color: #fff;
opacity: 0.8;
filter: alpha(opacity=80);
}
.layui-btn-lg {
height: 44px;
line-height: 44px;
padding: 0 25px;
font-size: 16px;
}
.layui-btn-sm {
height: 30px;
line-height: 30px;
padding: 0 10px;
font-size: 12px;
}
.layui-btn-xs {
height: 22px;
line-height: 22px;
padding: 0 5px;
font-size: 12px;
i {
font-size: 12px !important;
}
}
.layui-btn-fluid {
width: 100%;
}
.layui-btn-disabled,
.layui-btn-disabled:active,
.layui-btn-disabled:hover {
border-color: #eee !important;
background-color: #fbfbfb !important;
color: #d2d2d2 !important;
cursor: not-allowed !important;
opacity: 1;
}

View File

@@ -0,0 +1,8 @@
import type { App } from "vue";
import Component from "./index.vue";
Component.install = (app: App) => {
app.component(Component.name, Component);
};
export default Component;

View File

@@ -0,0 +1,76 @@
<script lang="ts">
export default {
name: "LayButton",
};
</script>
<script setup lang="ts">
import "./index.less";
import { computed } from "vue";
import {
ButtonBorder,
ButtonNativeType,
ButtonSize,
ButtonType,
} from "./interface";
import { Boolean, BooleanOrString, String } from "../../types";
export interface LayButtonProps {
type?: ButtonType;
size?: ButtonSize;
prefixIcon?: String;
suffixIcon?: String;
border?: ButtonBorder;
fluid?: BooleanOrString;
radius?: BooleanOrString;
loading?: BooleanOrString;
disabled?: BooleanOrString;
nativeType?: ButtonNativeType;
}
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 [
{
"layui-btn-fluid": props.fluid,
"layui-btn-radius": props.radius,
"layui-btn-disabled": props.disabled,
},
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="classes"
:type="nativeType"
@click="onClick"
>
<i v-if="prefixIcon" :class="`layui-icon ${prefixIcon}`"></i>
<i
v-if="loading"
class="layui-icon layui-icon-loading-one layui-anim layui-anim-rotate layui-anim-loop"
></i>
<slot v-else></slot>
<i v-if="suffixIcon" :class="`layui-icon ${suffixIcon}`"></i>
</button>
</template>

View File

@@ -0,0 +1,4 @@
export type ButtonType = "primary" | "normal" | "warm" | "danger";
export type ButtonSize = "lg" | "sm" | "xs";
export type ButtonBorder = "green" | "blue" | "orange" | "red" | "black";
export type ButtonNativeType = "button" | "submit" | "reset";

View File

@@ -0,0 +1,12 @@
@import (reference) "../../theme/variable.less";
.layui-btn-container {
font-size: 0;
.layui-btn {
margin-right: 10px;
margin-bottom: 10px;
}
.layui-btn + .layui-btn {
margin-left: 0;
}
}

View File

@@ -0,0 +1,8 @@
import type { App } from "vue";
import Component from "./index.vue";
Component.install = (app: App) => {
app.component(Component.name, Component);
};
export default Component;

View File

@@ -0,0 +1,15 @@
<script lang="ts">
export default {
name: "LayButtonContainer",
};
</script>
<script setup lang="ts">
import "./index.less";
</script>
<template>
<div class="layui-btn-container">
<slot></slot>
</div>
</template>

View File

@@ -0,0 +1,41 @@
@import (reference) "../../theme/variable.less";
@button-primary-color: @global-primary-color;
@button-border-radius: @global-border-radius;
.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 {
border-left: none;
}
.layui-btn-group .layui-btn:first-child {
border-left: none;
border-radius: @button-border-radius 0 0 @button-border-radius;
}
.layui-btn-group .layui-btn:first-child {
border-left: 1px solid #d2d2d2;
}
.layui-btn-group .layui-btn:last-child {
border-radius: 0 @button-border-radius @button-border-radius 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,8 @@
import type { App } from "vue";
import Component from "./index.vue";
Component.install = (app: App) => {
app.component(Component.name, Component);
};
export default Component;

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,42 @@
@import (reference) "../../theme/variable.less";
@card-border-radius: var(--card-border-radius);
@card-fore-color: var(--card-fore-color);
:root {
--card-border-radius: @global-border-radius;
--card-fore-color: @global-fore-color;
}
.layui-card {
margin-bottom: 15px;
border-radius: @card-border-radius;
background-color: #ffffff;
.layui-card-header {
height: 42px;
line-height: 42px;
padding: 0 15px;
border-bottom: 1px solid #f6f6f6;
color: @card-fore-color;
font-size: 14px;
.layui-card-header-extra {
float: right;
}
}
.layui-card-body {
padding: 10px 15px;
line-height: 24px;
}
}
.layui-card:last-child {
margin-bottom: 0;
}
.layui-card.is-hover-shadow:hover {
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
}
.layui-card.shadow {
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
}

View File

@@ -0,0 +1,8 @@
import type { App } from "vue";
import Component from "./index.vue";
Component.install = (app: App) => {
app.component(Component.name, Component);
};
export default Component;

View File

@@ -0,0 +1,48 @@
<script lang="ts">
export default {
name: "LayCard",
};
</script>
<script setup lang="ts">
import { computed, useSlots } from "vue";
import "./index.less";
import { String } from "../../types";
import { CardShadow } from "./interface";
const slot = useSlots();
export interface LayCardProps {
title?: String;
shadow?: CardShadow;
}
const props = withDefaults(defineProps<LayCardProps>(), {
shadow: "always",
});
const classes = computed(() => {
return {
shadow: props.shadow === "always",
"is-hover-shadow": props.shadow === "hover",
};
});
</script>
<template>
<div class="layui-card" :class="classes">
<div class="layui-card-header" v-if="slot.title || title || slot.extra">
<span class="layui-card-header-title">
<slot name="title" v-if="slot.title"></slot>
<template v-else>{{ title }}</template>
</span>
<span class="layui-card-header-extra">
<slot name="extra" v-if="slot.extra"></slot>
</span>
</div>
<div class="layui-card-body">
<slot name="body" v-if="slot.body"></slot>
<slot v-else></slot>
</div>
</div>
</template>

View File

@@ -0,0 +1 @@
export type CardShadow = "always" | "hover" | "never";

View File

@@ -0,0 +1,275 @@
@import (reference) "../../theme/variable.less";
.layui-carousel {
position: relative;
left: 0;
top: 0;
background-color: #f8f8f8;
}
.layui-carousel > [carousel-item] {
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
}
.layui-carousel > [carousel-item]:before {
position: absolute;
content: "\e63d";
left: 50%;
top: 50%;
width: 100px;
line-height: 20px;
margin: -10px 0 0 -50px;
text-align: center;
color: @global-neutral-color-8;
font-family: layui-icon !important;
font-size: 30px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.layui-carousel > [carousel-item] > * {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: #f8f8f8;
transition-duration: 0.3s;
-webkit-transition-duration: 0.3s;
}
.layui-carousel-updown > * {
-webkit-transition: 0.3s ease-in-out up;
transition: 0.3s ease-in-out up;
}
.layui-carousel-arrow {
display: none\9;
opacity: 0;
position: absolute;
left: 10px;
top: 50%;
margin-top: -18px;
width: 36px;
height: 36px;
line-height: 36px;
text-align: center;
font-size: 20px;
border: 0;
border-radius: 50%;
background-color: rgba(0, 0, 0, 0.2);
color: #fff;
-webkit-transition-duration: 0.3s;
transition-duration: 0.3s;
cursor: pointer;
}
.layui-carousel-arrow[lay-type="add"] {
left: auto !important;
right: 10px;
}
.layui-carousel:hover .layui-carousel-arrow[lay-type="add"],
.layui-carousel[lay-arrow="always"] .layui-carousel-arrow[lay-type="add"] {
right: 20px;
}
.layui-carousel[lay-arrow="always"] .layui-carousel-arrow {
opacity: 1;
left: 20px;
}
.layui-carousel[lay-arrow="none"] .layui-carousel-arrow {
display: none;
}
.layui-carousel-arrow:hover,
.layui-carousel-ind ul:hover {
background-color: rgba(0, 0, 0, 0.35);
}
.layui-carousel:hover .layui-carousel-arrow {
display: block\9;
opacity: 1;
left: 20px;
}
.layui-carousel-ind {
position: relative;
top: -35px;
width: 100%;
line-height: 0 !important;
text-align: center;
font-size: 0;
}
.layui-carousel[lay-indicator="outside"] {
margin-bottom: 30px;
}
.layui-carousel[lay-indicator="outside"] .layui-carousel-ind {
top: 10px;
}
.layui-carousel[lay-indicator="outside"] .layui-carousel-ind ul {
background-color: rgba(0, 0, 0, 0.5);
}
.layui-carousel[lay-indicator="none"] .layui-carousel-ind {
display: none;
}
.layui-carousel-ind ul {
display: inline-block;
padding: 5px;
background-color: rgba(0, 0, 0, 0.2);
border-radius: 10px;
-webkit-transition-duration: 0.3s;
transition-duration: 0.3s;
}
.layui-carousel-ind li {
display: inline-block;
width: 10px;
height: 10px;
margin: 0 3px;
font-size: 14px;
background-color: @global-neutral-color-3;
background-color: rgba(255, 255, 255, 0.5);
border-radius: 50%;
cursor: pointer;
-webkit-transition-duration: 0.3s;
transition-duration: 0.3s;
}
.layui-carousel-ind li:hover {
background-color: rgba(255, 255, 255, 0.7);
}
.layui-carousel-ind li.layui-this {
background-color: #fff;
}
.layui-carousel > [carousel-item] > .layui-carousel-next,
.layui-carousel > [carousel-item] > .layui-carousel-prev,
.layui-carousel > [carousel-item] > .layui-this {
display: block;
}
.layui-carousel > [carousel-item] > .layui-this {
left: 0;
}
.layui-carousel > [carousel-item] > .layui-carousel-prev {
left: -100%;
}
.layui-carousel > [carousel-item] > .layui-carousel-next {
left: 100%;
}
.layui-carousel > [carousel-item] > .layui-carousel-next.layui-carousel-left,
.layui-carousel > [carousel-item] > .layui-carousel-prev.layui-carousel-right {
left: 0;
}
.layui-carousel > [carousel-item] > .layui-this.layui-carousel-left {
left: -100%;
}
.layui-carousel > [carousel-item] > .layui-this.layui-carousel-right {
left: 100%;
}
.layui-carousel[lay-anim="updown"] .layui-carousel-arrow {
left: 50% !important;
top: 20px;
margin: 0 0 0 -18px;
}
.layui-carousel[lay-anim="updown"] > [carousel-item] > *,
.layui-carousel[lay-anim="fade"] > [carousel-item] > * {
left: 0 !important;
}
.layui-carousel[lay-anim="updown"] .layui-carousel-arrow[lay-type="add"] {
top: auto !important;
bottom: 20px;
}
.layui-carousel[lay-anim="updown"] .layui-carousel-ind {
position: absolute;
top: 50%;
right: 20px;
width: auto;
height: auto;
}
.layui-carousel[lay-anim="updown"] .layui-carousel-ind ul {
padding: 3px 5px;
}
.layui-carousel[lay-anim="updown"] .layui-carousel-ind li {
display: block;
margin: 6px 0;
}
.layui-carousel[lay-anim="updown"] > [carousel-item] > .layui-this {
top: 0;
}
.layui-carousel[lay-anim="updown"] > [carousel-item] > .layui-carousel-prev {
top: -100%;
}
.layui-carousel[lay-anim="updown"] > [carousel-item] > .layui-carousel-next {
top: 100%;
}
.layui-carousel[lay-anim="updown"]
> [carousel-item]
> .layui-carousel-next.layui-carousel-left,
.layui-carousel[lay-anim="updown"]
> [carousel-item]
> .layui-carousel-prev.layui-carousel-right {
top: 0;
}
.layui-carousel[lay-anim="updown"]
> [carousel-item]
> .layui-this.layui-carousel-left {
top: -100%;
}
.layui-carousel[lay-anim="updown"]
> [carousel-item]
> .layui-this.layui-carousel-right {
top: 100%;
}
.layui-carousel[lay-anim="fade"] > [carousel-item] > .layui-carousel-next,
.layui-carousel[lay-anim="fade"] > [carousel-item] > .layui-carousel-prev {
opacity: 0;
}
.layui-carousel[lay-anim="fade"]
> [carousel-item]
> .layui-carousel-next.layui-carousel-left,
.layui-carousel[lay-anim="fade"]
> [carousel-item]
> .layui-carousel-prev.layui-carousel-right {
opacity: 1;
}
.layui-carousel[lay-anim="fade"]
> [carousel-item]
> .layui-this.layui-carousel-left,
.layui-carousel[lay-anim="fade"]
> [carousel-item]
> .layui-this.layui-carousel-right {
opacity: 0;
}

View File

@@ -0,0 +1,8 @@
import type { App } from "vue";
import Component from "./index.vue";
Component.install = (app: App) => {
app.component(Component.name, Component);
};
export default Component;

View File

@@ -0,0 +1,151 @@
<script lang="ts">
export default {
name: "LayCarousel",
};
</script>
<script setup lang="ts">
import "./index.less";
import {
withDefaults,
provide,
useSlots,
ref,
computed,
VNode,
Ref,
Component,
watch,
onMounted,
} from "vue";
import CarouselItem from "../carouselItem";
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;
autoplay?: boolean;
arrow?: string;
interval?: number;
indicator?: string;
}>(),
{
width: "100%",
height: "280px",
anim: "default",
autoplay: true,
arrow: "hover",
interval: 3000,
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;
};
const childrens: Ref<VNode[]> = ref([]);
const slotsChange = ref(true);
const setItemInstanceBySlot = function (nodeList: VNode[]) {
nodeList?.map((item) => {
let component = item.type as Component;
if (component.name != CarouselItem.name) {
setItemInstanceBySlot(item.children as VNode[]);
} else {
childrens.value.push(item);
}
});
};
watch(slotsChange, function () {
childrens.value = [];
setItemInstanceBySlot((slot.default && slot.default()) as VNode[]);
});
provide("active", active);
provide("slotsChange", slotsChange);
// 上一页
const sub = function () {
for (var i = 0; i < childrens.value.length; i++) {
// @ts-ignore
if (childrens.value[i].props.id === active.value) {
if (i === 0) {
// @ts-ignore
active.value = childrens.value[slots.length - 1].props.id;
}
// @ts-ignore
active.value = childrens.value[i - 1].props.id;
break;
}
}
};
// 下一页
const add = function () {
for (var i = 0; i < childrens.value.length; i++) {
// @ts-ignore
if (childrens.value[i].props.id === active.value) {
if (i === childrens.value.length - 1) {
// @ts-ignore
active.value = childrens.value[0].props.id;
}
// @ts-ignore
active.value = childrens.value[i + 1].props.id;
break;
}
}
};
</script>
<template>
<div
class="layui-carousel"
:lay-anim="anim"
:lay-indicator="indicator"
:lay-arrow="arrow"
:style="{ width: width, height: height }"
>
<!-- 内容 -->
<div carousel-item>
<slot></slot>
</div>
<!-- 轮播 -->
<div class="layui-carousel-ind">
<ul>
<li
v-for="ss in childrens"
: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="sub">
{{ anim === "updown" ? "" : "" }}
</button>
<button class="layui-icon layui-carousel-arrow" lay-type="add" @click="add">
{{ anim === "updown" ? "" : "" }}
</button>
</div>
</template>

View File

@@ -0,0 +1 @@
@import (reference) "../../theme/variable.less";

View File

@@ -0,0 +1,8 @@
import type { App } from "vue";
import Component from "./index.vue";
Component.install = (app: App) => {
app.component(Component.name, Component);
};
export default Component;

View File

@@ -0,0 +1,23 @@
<script lang="ts">
export default {
name: "LayCarouselItem",
};
</script>
<script setup lang="ts">
import { inject, Ref } from "vue";
const props = defineProps<{
id: string;
}>();
const active = inject("active");
const slotsChange: Ref<boolean> = inject("slotsChange") as Ref<boolean>;
slotsChange.value = !slotsChange.value;
</script>
<template>
<li v-if="active === id">
<slot></slot>
</li>
</template>

View File

@@ -0,0 +1,137 @@
@import (reference) "../../theme/variable.less";
.layui-form-checkbox {
position: relative;
height: 30px;
line-height: 30px;
margin-right: 10px;
padding-right: 30px;
cursor: pointer;
font-size: 0;
-webkit-transition: 0.1s linear;
transition: 0.1s linear;
box-sizing: border-box;
}
.layui-form-checkbox span {
padding: 0 10px;
height: 100%;
font-size: 14px;
border-radius: 2px 0 0 2px;
background-color: @global-neutral-color-6;
color: #fff;
overflow: hidden;
}
.layui-form-checkbox:hover span {
background-color: @global-neutral-color-8;
}
.layui-form-checkbox i {
position: absolute;
right: 0;
top: 0;
width: 30px;
height: 28px;
border: 1px solid @global-neutral-color-6;
border-left: none;
border-radius: 0 2px 2px 0;
color: #fff;
font-size: 20px;
text-align: center;
}
.layui-form-checkbox:hover i {
border-color: @global-neutral-color-8;
color: @global-neutral-color-8;
}
.layui-form-checkbox[lay-skin="primary"] {
height: auto !important;
line-height: normal !important;
min-width: 18px;
min-height: 18px;
border: none !important;
margin-right: 0;
padding-left: 28px;
padding-right: 0;
background: 0 0;
}
.layui-form-checkbox[lay-skin="primary"] span {
padding-left: 0;
padding-right: 15px;
line-height: 18px;
background: 0 0;
color: #666;
}
.layui-form-checkbox[lay-skin="primary"] i {
right: auto;
left: 0;
width: 16px;
height: 16px;
line-height: 16px;
border: 1px solid @global-neutral-color-6;
font-size: 12px;
border-radius: 2px;
background-color: #fff;
-webkit-transition: 0.1s linear;
transition: 0.1s linear;
}
.layui-form-checkbox[lay-skin="primary"]:hover i {
border-color: @global-checked-color;
color: #fff;
}
.layui-form-checked,
.layui-form-checked:hover {
border-color: @global-checked-color;
}
.layui-form-checked i,
.layui-form-checked:hover i {
color: @global-checked-color;
}
.layui-form-checked span,
.layui-form-checked:hover span {
background-color: @global-checked-color;
}
.layui-form-checked[lay-skin="primary"] i {
border-color: @global-checked-color !important;
background-color: @global-checked-color;
color: #fff;
}
.layui-form-checked[lay-skin="primary"] span {
background: 0 0 !important;
}
.layui-checkbox-disabled[lay-skin="primary"] span {
background: 0 0 !important;
color: @global-neutral-color-8 !important;
}
.layui-checkbox-disabled[lay-skin="primary"]:hover i {
border-color: @global-neutral-color-6;
}
.layui-checkbox-disabled,
.layui-checkbox-disabled i {
border-color: @global-neutral-color-3 !important;
}
.layui-checkbox-disabled span {
background-color: @global-neutral-color-3 !important;
}
.layui-checkbox-disabled em {
color: @global-neutral-color-6 !important;
}
.layui-checkbox-disabled:hover i {
color: #fff !important;
}

View File

@@ -0,0 +1,8 @@
import type { App } from "vue";
import Component from "./index.vue";
Component.install = (app: App) => {
app.component(Component.name, Component);
};
export default Component;

View File

@@ -0,0 +1,111 @@
<script lang="ts">
export default {
name: "LayCheckbox",
};
</script>
<script setup lang="ts">
import { computed, 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-disabled 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': isChecked }"></i>
</div>
</span>
</template>

View File

@@ -0,0 +1 @@
@import (reference) "../../theme/variable.less";

View File

@@ -0,0 +1,8 @@
import type { App } from "vue";
import Component from "./index.vue";
Component.install = (app: App) => {
app.component(Component.name, Component);
};
export default Component;

View File

@@ -0,0 +1,44 @@
<script lang="ts">
export default {
name: "LayCheckboxGroup",
};
</script>
<script setup lang="ts">
import { provide, ref, watch } from "vue";
import { Recordable } from "../../types";
export interface LayCheckboxGroupProps {
modelValue?: Recordable[];
}
const props = withDefaults(defineProps<LayCheckboxGroupProps>(), {
modelValue: () => [],
});
const emit = defineEmits(["update:modelValue", "change"]);
const modelValue = ref(props.modelValue);
provide("checkboxGroup", { name: "LayCheckboxGroup", modelValue: modelValue });
watch(
() => modelValue,
(val) => {
emit("change", modelValue.value);
emit("update:modelValue", modelValue.value);
},
{ deep: true }
);
watch(
() => props.modelValue,
(val) => (modelValue.value = val)
);
</script>
<template>
<div class="layui-checkbox-group">
<slot></slot>
</div>
</template>

View File

@@ -0,0 +1 @@
@import (reference) "../../theme/variable.less";

View File

@@ -0,0 +1,8 @@
import type { App } from "vue";
import Component from "./index.vue";
Component.install = (app: App) => {
app.component(Component.name, Component);
};
export default Component;

View File

@@ -0,0 +1,41 @@
<script lang="ts">
export default {
name: "LayCol",
};
</script>
<script setup lang="ts">
import { computed } from "vue";
export interface LayColProps {
md?: string;
xs?: string;
sm?: string;
lg?: string;
mdOffset?: string;
xsOffset?: string;
smOffset?: string;
lgOffset?: string;
}
const props = defineProps<LayColProps>();
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,7 @@
@import (reference) "../../theme/variable.less";
.layui-collapse {
border-width: 1px;
border-style: solid;
border-radius: 2px;
}

View File

@@ -0,0 +1,8 @@
import type { App } from "vue";
import Component from "./index.vue";
Component.install = (app: App) => {
app.component(Component.name, Component);
};
export default Component;

View File

@@ -0,0 +1,46 @@
<script lang="ts">
export default {
name: "LayCollapse",
};
</script>
<script setup lang="ts">
import "./index.less";
import { withDefaults, provide, ref, watch } from "vue";
export interface LayCollapseProps {
modelValue?: number | string | [];
accordion?: boolean;
collapseTransition?: boolean;
}
const props = withDefaults(defineProps<LayCollapseProps>(), {
modelValue: () => [],
accordion: false,
collapseTransition: true,
});
// 监听传入的值
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,
collapseTransition: props.collapseTransition,
activeValues,
emit,
});
</script>
<template>
<div class="layui-collapse">
<slot></slot>
</div>
</template>

View File

@@ -0,0 +1,36 @@
@import (reference) "../../theme/variable.less";
.layui-colla-content,
.layui-colla-item {
border-top-width: 1px;
border-top-style: solid;
}
.layui-colla-item:first-child {
border-top: none;
}
.layui-colla-title {
position: relative;
height: 42px;
line-height: 42px;
padding: 0 15px 0 35px;
color: #333;
background-color: @global-neutral-color-1;
cursor: pointer;
font-size: 14px;
overflow: hidden;
}
.layui-colla-content {
padding: 10px 15px;
line-height: 1.6;
color: #666;
}
.layui-colla-icon {
position: absolute;
left: 15px;
top: 0;
font-size: 14px;
}

View File

@@ -0,0 +1,8 @@
import type { App } from "vue";
import Component from "./index.vue";
Component.install = (app: App) => {
app.component(Component.name, Component);
};
export default Component;

View File

@@ -0,0 +1,72 @@
<script lang="ts">
export default {
name: "LayCollapseItem",
};
</script>
<script setup lang="ts">
import "./index.less";
import LayTransition from "../transition/index.vue";
import { withDefaults, inject, computed, ref } from "vue";
export interface LayCollapseItemProps {
id: number | string;
title: string;
disabled?: boolean;
}
const props = withDefaults(defineProps<LayCollapseItemProps>(), {
disabled: false,
});
const { accordion, activeValues, emit, collapseTransition } = inject(
"layCollapse"
) as any;
let isShow = computed(() => {
return activeValues.value.includes(props.id);
});
const showHandle = function () {
if (props.disabled) {
return;
}
const _isShow = isShow.value;
// 手风琴效果
if (accordion) {
activeValues.value = !_isShow ? [props.id] : [];
} else if (_isShow) {
// 普通折叠面板 --> 折叠
activeValues.value.splice(activeValues.value.indexOf(props.id), 1);
} else {
// 普通折叠面板 --> 展开
activeValues.value.push(props.id);
}
emit(
"update:modelValue",
accordion ? activeValues.value[0] || null : activeValues.value
);
emit("change", props.id, !_isShow, activeValues.value);
};
</script>
<template>
<div class="layui-colla-item">
<h2
:class="['layui-colla-title', { 'layui-disabled': disabled }]"
@click="showHandle"
>
<slot name="title" :props="props">{{ title }}</slot>
<i class="layui-icon layui-colla-icon">{{ isShow ? "" : "" }}</i>
</h2>
<lay-transition :enable="collapseTransition">
<div v-if="isShow">
<div class="layui-colla-content">
<p>
<slot :props="props"></slot>
</p>
</div>
</div>
</lay-transition>
</div>
</template>

View File

@@ -0,0 +1,389 @@
@import (reference) "../../theme/variable.less";
.layui-color-picker {
position: relative;
user-select: none;
width: 320px;
background: #fff;
padding: 8px;
}
/* 饱和度和亮度 */
.saturation-value {
cursor: pointer;
width: 100%;
height: 200px;
position: relative;
margin-bottom: 10px;
box-shadow: 1px 1px 1px rgba(0, 0, 0, 0.1);
}
.saturation-value > div {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
/* 圆圈 */
.point {
box-sizing: border-box;
width: 6px;
height: 6px;
background-color: transparent;
border: 2px solid #ccc;
border-radius: 50%;
transform: translate(-50%, -50%);
position: absolute;
z-index: 9;
}
.saturation-value-2 {
background: linear-gradient(to right, white, #ffffff00);
}
.saturation-value-3 {
background: linear-gradient(to top, black, #ffffff00);
}
/* 色调 透明度 */
.layui-color-picker-middle {
width: 100%;
display: flex;
margin-bottom: 10px;
}
/* 色调滑块条 */
.hue-slider {
position: relative;
margin-bottom: 6px;
height: 10px;
background: linear-gradient(
90deg,
red 0,
#ff0 17%,
#0f0 33%,
#0ff 50%,
#00f 67%,
#f0f 83%,
red
);
box-shadow: 1px 1px 1px rgba(0, 0, 0, 0.1);
}
/* 透明度滑块条 */
.alpha-slider {
position: relative;
height: 10px;
box-shadow: 1px 1px 1px rgba(0, 0, 0, 0.1);
background: #fff
url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAeCAYAAAA7MK6iAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAWElEQVRIiWM8fubkfwYygKWJOSM5+mCAhRLNoxaPWjxq8ajFoxbTyeL/DAfJ0Xjs3Cl7Siwmu4Yht1aDgZEYx6MWj1o8avGoxaMWD3qLya5X//4nqx6HAQC7RBGFzolqTAAAAABJRU5ErkJggg==");
background-size: 10px 10px;
}
/* 滑块 */
.slider {
position: absolute;
box-shadow: 0 0 2px rgba(0, 0, 0, 0.6);
box-sizing: border-box;
width: 6px;
height: 100%;
background-color: #fff;
}
/* 颜色方块 */
.color-diamond {
position: relative;
margin-left: 5px;
width: 26px;
height: 26px;
border-radius: 2px;
overflow: hidden;
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAeCAYAAAA7MK6iAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAWElEQVRIiWM8fubkfwYygKWJOSM5+mCAhRLNoxaPWjxq8ajFoxbTyeL/DAfJ0Xjs3Cl7Siwmu4Yht1aDgZEYx6MWj1o8avGoxaMWD3qLya5X//4nqx6HAQC7RBGFzolqTAAAAABJRU5ErkJggg==");
background-size: 10px 10px;
}
.layui-colorpicker > span {
width: 26px;
height: 26px;
display: block;
border-radius: @global-border-radius;
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAeCAYAAAA7MK6iAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAWElEQVRIiWM8fubkfwYygKWJOSM5+mCAhRLNoxaPWjxq8ajFoxbTyeL/DAfJ0Xjs3Cl7Siwmu4Yht1aDgZEYx6MWj1o8avGoxaMWD3qLya5X//4nqx6HAQC7RBGFzolqTAAAAABJRU5ErkJggg==");
background-size: 10px 10px;
}
/* 颜色的值 hex rgba */
.color-value {
width: 100%;
display: flex;
justify-content: space-between;
}
.color-value div {
padding: 0 3px;
text-align: center;
}
.color-value input {
font-size: 12px;
box-sizing: border-box;
width: 34px;
height: 24px;
padding: 0;
margin: 0;
outline: none;
text-align: center;
border-radius: 2px;
border: 1px solid #eee;
}
.color-value p {
font-size: 12px;
margin: 3px 0 0;
}
.color-value .rgba-a {
padding-right: 0;
}
.color-value .hex {
flex: 1;
padding-left: 0;
}
.color-value .hex input {
width: 100%;
height: 24px;
}
/* 预设颜色 */
.preset {
width: 100%;
padding: 0;
margin: 10px 0 0;
list-style: none;
display: flex;
flex-wrap: wrap;
justify-content: flex-start;
}
.preset li {
width: 20px;
height: 20px;
padding: 0px;
margin-right: 6px;
margin-bottom: 6px;
border: 1px solid #eee;
border-radius: 2px;
}
.layui-colorpicker {
border: 1px solid @global-neutral-color-3;
padding: 5px;
border-radius: @global-border-radius;
line-height: 24px;
display: inline-block;
cursor: pointer;
transition: all 0.3s;
-webkit-transition: all 0.3s;
}
.layui-colorpicker:hover {
border-color: @global-neutral-color-6;
}
.layui-colorpicker.layui-colorpicker-lg {
width: 34px;
height: 34px;
line-height: 32px;
}
.layui-colorpicker.layui-colorpicker-sm {
width: 24px;
height: 24px;
line-height: 22px;
}
.layui-colorpicker.layui-colorpicker-xs {
width: 22px;
height: 22px;
line-height: 20px;
}
.layui-colorpicker-trigger-bgcolor {
display: block;
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAIAAADZF8uwAAAAGUlEQVQYV2M4gwH+YwCGIasIUwhT25BVBADtzYNYrHvv4gAAAABJRU5ErkJggg==);
border-radius: 2px;
}
.layui-colorpicker-trigger-span {
display: block;
height: 100%;
box-sizing: border-box;
border: 1px solid rgba(0, 0, 0, 0.15);
border-radius: @global-border-radius;
text-align: center;
}
.layui-colorpicker-trigger-i {
display: inline-block;
color: #fff;
font-size: 12px;
}
.layui-colorpicker-trigger-i.layui-icon-close {
color: #999;
}
.layui-colorpicker-main {
position: absolute;
left: -999999px;
top: -999999px;
z-index: 66666666;
width: 280px;
margin: 5px 0;
padding: 7px;
background: #fff;
border: 1px solid @global-neutral-color-6;
border-radius: 2px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.12);
}
.layui-colorpicker-main-wrapper {
height: 180px;
position: relative;
}
.layui-colorpicker-basis {
width: 260px;
height: 100%;
position: relative;
}
.layui-colorpicker-basis-white {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
background: linear-gradient(90deg, #fff, hsla(0, 0%, 100%, 0));
}
.layui-colorpicker-basis-black {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
background: linear-gradient(0deg, #000, transparent);
}
.layui-colorpicker-basis-cursor {
width: 10px;
height: 10px;
border: 1px solid #fff;
border-radius: 50%;
position: absolute;
top: -3px;
right: -3px;
cursor: pointer;
}
.layui-colorpicker-side {
position: absolute;
top: 0;
right: 0;
width: 12px;
height: 100%;
background: linear-gradient(red, #ff0, #0f0, #0ff, #00f, #f0f, red);
}
.layui-colorpicker-side-slider {
width: 100%;
height: 5px;
box-shadow: 0 0 1px #888;
background: #fff;
border-radius: 1px;
border: 1px solid #f0f0f0;
cursor: pointer;
position: absolute;
left: 0;
}
.layui-colorpicker-main-alpha {
display: none;
height: 12px;
margin-top: 7px;
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAIAAADZF8uwAAAAGUlEQVQYV2M4gwH+YwCGIasIUwhT25BVBADtzYNYrHvv4gAAAABJRU5ErkJggg==);
}
.layui-colorpicker-alpha-bgcolor {
height: 100%;
position: relative;
}
.layui-colorpicker-alpha-slider {
width: 5px;
height: 100%;
box-shadow: 0 0 1px #888;
background: #fff;
border-radius: 1px;
border: 1px solid #f0f0f0;
cursor: pointer;
position: absolute;
top: 0;
}
.layui-colorpicker-main-pre {
padding-top: 7px;
font-size: 0;
}
.layui-colorpicker-pre {
width: 20px;
height: 20px;
border-radius: 2px;
display: inline-block;
margin-left: 6px;
margin-bottom: 7px;
cursor: pointer;
}
.layui-colorpicker-pre:nth-child(11n + 1) {
margin-left: 0;
}
.layui-colorpicker-pre-isalpha {
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAIAAADZF8uwAAAAGUlEQVQYV2M4gwH+YwCGIasIUwhT25BVBADtzYNYrHvv4gAAAABJRU5ErkJggg==);
}
.layui-colorpicker-pre.layui-this {
box-shadow: 0 0 3px 2px rgba(0, 0, 0, 0.15);
}
.layui-colorpicker-pre > div {
height: 100%;
border-radius: 2px;
}
.layui-colorpicker-main-input {
text-align: right;
padding-top: 7px;
}
.layui-colorpicker-main-input .layui-btn-container .layui-btn {
margin: 0 0 0 10px;
}
.layui-colorpicker-main-input div.layui-inline {
float: left;
margin-right: 10px;
font-size: 14px;
}
.layui-colorpicker-main-input input.layui-input {
width: 150px;
height: 30px;
color: #666;
}

View File

@@ -0,0 +1,8 @@
import type { App } from "vue";
import Component from "./index.vue";
Component.install = (app: App) => {
app.component("LayColorPicker", Component);
};
export default Component;

View File

@@ -0,0 +1,481 @@
<script lang="ts">
export default {
name: "LayColorPicker",
};
</script>
<script lang="ts" setup>
import "./index.less";
import LayDropdown from "../dropdown/index.vue";
import { ref, computed, watch, onMounted } from "vue";
const emit = defineEmits(["update:modelValue"]);
export interface LayColorPicker {
modelValue?: any;
preset?: any;
}
const props = withDefaults(defineProps<LayColorPicker>(), {
modelValue: { r: 255, g: 255, b: 255, a: 1 },
preset: ["#009688", "#1e9fff", "#ffb800", "#ff5722", "#5fb878"],
});
const saturationValue = ref<null | HTMLElement>(null);
const hueSlider = ref<null | HTMLElement>(null);
const alphaSlider = ref<null | HTMLElement>(null);
let pointStyle = ref("top: 25%;left: 80%;");
let hueSliderStyle = ref("left: 0;");
let alphaSliderStyle = ref("left: calc(100% - 6px);");
let hue = ref(0);
let saturation = ref(1);
let value = ref(1);
let red = ref(255);
let green = ref(0);
let blue = ref(0);
let alpha = ref(1);
onMounted(() => {
let { r, g, b, a } = parseColor(props.modelValue);
red.value = r;
green.value = g;
blue.value = b;
alpha.value = a;
});
watch([red, green, blue], (newValue) => {
emit(
"update:modelValue",
rgba2hex(red.value, green.value, blue.value, alpha.value)
);
let { h, s, v } = rgb2hsv(red.value, green.value, blue.value);
hue.value = h;
saturation.value = s;
value.value = v;
pointStyle.value = `top: ${100 - v * 100}%;left: ${s * 100}%;`;
hueSliderStyle.value = `left: ${(hue.value / 360) * 100}%;`;
});
watch(alpha, () => {
emit(
"update:modelValue",
rgba2hex(red.value, green.value, blue.value, alpha.value)
);
// 移动透明度滑块
alphaSliderStyle.value = `left: ${
alpha.value >= 1 ? "calc(100% - 6px)" : alpha.value * 100 + "%"
};`;
});
let colorObj = computed(() => {
let r = red.value;
let g = green.value;
let b = blue.value;
let a = alpha.value;
let h = hue.value;
let s = saturation.value;
let v = value.value;
return {
rgb: `rgba(${r},${g},${b})`,
rgba: `rgba(${r},${g},${b},${a})`,
hex6: rgba2hex(r, g, b),
hex8: rgba2hex(r, g, b, a),
hsv: `hsv(${h},${s},${v})`,
hsl: ``,
};
});
// 输入框值变化,限制输入的值
function hexChange(e: any) {
let v = e.target.value;
if (/^#?([0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/.test(v)) {
let { r, g, b, a } = hex2rgba(v);
red.value = r;
green.value = g;
blue.value = b;
alpha.value = a;
}
}
function redChange(e: any) {
let v = e.target.value;
if (v !== "") {
v > 255 && (red.value = 255);
v < 0 && (red.value = 0);
v >= 0 && v <= 255 && (red.value = parseInt(v));
}
}
function greenChange(e: any) {
let v = e.target.value;
if (v !== "") {
v > 255 && (green.value = 255);
v < 0 && (green.value = 0);
v >= 0 && v <= 255 && (green.value = parseInt(v));
}
}
function blueChange(e: any) {
let v = e.target.value;
if (v !== "") {
v > 255 && (blue.value = 255);
v < 0 && (blue.value = 0);
v >= 0 && v <= 255 && (blue.value = parseInt(v));
}
}
function alphaChange(e: any) {
let v = e.target.value;
if (v !== "") {
v = parseFloat(v);
alpha.value = v;
v > 1 && (alpha.value = 1);
v < 0 && (alpha.value = 0);
v >= 0 && v <= 1 && (alpha.value = v);
}
}
// 点击预设方块事件
function presetChange(item: any) {
if (/^#?([0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/.test(item)) {
let { r, g, b, a } = hex2rgba(item);
red.value = r;
green.value = g;
blue.value = b;
alpha.value = a;
}
}
// 饱和度和亮度
function handleChangeSV(e: any) {
// @ts-ignore
let w = saturationValue.value.clientWidth;
// @ts-ignore
let h = saturationValue.value.clientHeight;
// @ts-ignore
let x = e.pageX - saturationValue.value.getBoundingClientRect().left;
// @ts-ignore
let y = e.pageY - saturationValue.value.getBoundingClientRect().top;
x = x < w && x > 0 ? x : x > w ? w : 0;
y = y < h && y > 0 ? y : y > h ? h : 0;
// 计算饱和度和亮度
saturation.value = Math.floor((x / w) * 100 + 0.5) / 100;
value.value = Math.floor((1 - y / h) * 100 + 0.5) / 100;
// hsv转化为rgb
let { r, g, b } = hsv2rgb(hue.value, saturation.value, value.value);
red.value = r;
green.value = g;
blue.value = b;
// 移动背景板圆圈
pointStyle.value = `top: ${y}px;left: ${x}px;`;
}
function mousedownSV(e: any) {
// 鼠标按下计算饱和度和亮度并添加事件
handleChangeSV(e);
// 添加整个页面的鼠标事件
window.addEventListener("mousemove", handleChangeSV);
window.addEventListener("mouseup", mouseupSV);
}
function mouseupSV(e: any) {
// 鼠标松开后移除事件
window.removeEventListener("mousemove", handleChangeSV);
window.removeEventListener("mouseup", mouseupSV);
}
// 色调
function handleChangeHue(e: any) {
// @ts-ignore
let w = hueSlider.value.clientWidth;
// @ts-ignore
let x = e.pageX - saturationValue.value.getBoundingClientRect().left;
x = x < w && x > 0 ? x : x > w ? w : 0;
// 计算色调
hue.value = Math.floor((x / w) * 360 + 0.5);
// hsv转化为rgb
let { r, g, b } = hsv2rgb(hue.value, saturation.value, value.value);
red.value = r;
green.value = g;
blue.value = b;
// 移动滑块
hueSliderStyle.value = `left: ${x >= w - 6 ? w - 6 : x}px;`;
}
function mousedownHue(e: any) {
handleChangeHue(e);
window.addEventListener("mousemove", handleChangeHue);
window.addEventListener("mouseup", mouseupHue);
}
function mouseupHue(e: any) {
window.removeEventListener("mousemove", handleChangeHue);
window.removeEventListener("mouseup", mouseupHue);
}
// 透明度
function handleChangeAlpha(e: any) {
// @ts-ignore
let w = alphaSlider.value.clientWidth;
// @ts-ignore
let x = e.pageX - saturationValue.value.getBoundingClientRect().left;
x = x < w && x > 0 ? x : x > w ? w : 0;
// 计算透明度
alpha.value = Math.floor((x / w) * 100 + 0.5) / 100;
// 移动滑块
alphaSliderStyle.value = `left: ${x >= w - 6 ? w - 6 : x}px;`;
}
function mousedownAlpha(e: any) {
handleChangeAlpha(e);
window.addEventListener("mousemove", handleChangeAlpha);
window.addEventListener("mouseup", mouseupAlpha);
}
function mouseupAlpha(e: any) {
window.removeEventListener("mousemove", handleChangeAlpha);
window.removeEventListener("mouseup", mouseupAlpha);
}
/**
* 解析输入的数据,只能解析hex颜色和rgb对象形式的数据
* @param color
*/
function parseColor(color: any) {
if (color) {
let r, g, b, a;
if (typeof color === "string") {
if (
/^#?([0-9a-fA-F]{6}|[0-9a-fA-F]{8}|[0-9a-fA-F]{3}|[0-9a-fA-F]{4})$/.test(
color
)
) {
return hex2rgba(color);
}
} else {
r = color.r > 255 ? 255 : color.r < 0 ? 0 : color.r;
g = color.g > 255 ? 255 : color.g < 0 ? 0 : color.g;
b = color.b > 255 ? 255 : color.b < 0 ? 0 : color.b;
a = color.a > 1 ? 1 : color.a < 0 ? 0 : color.a;
return { r, g, b, a };
}
} else {
return null;
}
}
function hsv2rgb(h: any, s: any, v: any) {
h === 360 && (h = 0);
let i = Math.floor(h / 60) % 6;
let f = h / 60 - i;
let p = v * (1 - s);
let q = v * (1 - s * f);
let t = v * (1 - s * (1 - f));
let r, g, b;
if (i === 0) {
r = v;
g = t;
b = p;
} else if (i === 1) {
r = q;
g = v;
b = p;
} else if (i === 2) {
r = p;
g = v;
b = t;
} else if (i === 3) {
r = p;
g = q;
b = v;
} else if (i === 4) {
r = t;
g = p;
b = v;
} else if (i === 5) {
r = v;
g = p;
b = q;
}
r = Math.floor(r * 255 + 0.5);
g = Math.floor(g * 255 + 0.5);
b = Math.floor(b * 255 + 0.5);
return { r, g, b };
}
function rgb2hsv(r: any, g: any, b: any) {
let r1 = r / 255;
let g1 = g / 255;
let b1 = b / 255;
let cmax = Math.max(r1, g1, b1);
let cmin = Math.min(r1, g1, b1);
let d = cmax - cmin;
let h, s, v;
if (d === 0) {
h = 0;
} else if (cmax === r1) {
h = ((60 * (g1 - b1)) / d + 360) % 360;
} else if (cmax === g1) {
h = 60 * ((b1 - r1) / d + 2);
} else if (cmax === b1) {
h = 60 * ((r1 - g1) / d + 4);
}
if (cmax === 0) {
s = 0;
} else {
s = d / cmax;
}
v = cmax;
// @ts-ignore
h = Math.floor(h + 0.5);
s = Math.floor(s * 100 + 0.5) / 100;
v = Math.floor(v * 100 + 0.5) / 100;
return { h, s, v };
}
function rgba2hex(r: any, g: any, b: any, a = 1) {
r = parseInt(r);
let r1 = r.toString(16).length !== 2 ? "0" + r.toString(16) : r.toString(16);
g = parseInt(g);
let g1 = g.toString(16).length !== 2 ? "0" + g.toString(16) : g.toString(16);
b = parseInt(b);
let b1 = b.toString(16).length !== 2 ? "0" + b.toString(16) : b.toString(16);
// @ts-ignore
a = parseFloat(a);
let a1 = "";
if (a !== 1) {
let temp = Math.floor(256 * a);
a1 =
temp.toString(16).length !== 2
? "0" + temp.toString(16)
: temp.toString(16);
}
return `#${r1}${g1}${b1}${a1}`.toUpperCase();
}
// @ts-ignore
function hex2rgba(s: any) {
if (/^#?[0-9a-fA-F]{3}$/.test(s)) {
let b = s.substring(s.length - 1, s.length);
let g = s.substring(s.length - 2, s.length - 1);
let r = s.substring(s.length - 3, s.length - 2);
return hex2rgba(`${r + r}${g + g}${b + b}`);
}
if (/^#?[0-9a-fA-F]{4}$/.test(s)) {
let a = s.substring(s.length - 1, s.length);
let b = s.substring(s.length - 2, s.length - 1);
let g = s.substring(s.length - 3, s.length - 2);
let r = s.substring(s.length - 4, s.length - 3);
return hex2rgba(`${r + r}${g + g}${b + b}${a + a}`);
}
if (/^#?[0-9a-fA-F]{6}$/.test(s)) {
let b = parseInt("0x" + s.substring(s.length - 2, s.length));
let g = parseInt("0x" + s.substring(s.length - 4, s.length - 2));
let r = parseInt("0x" + s.substring(s.length - 6, s.length - 4));
return { r, g, b, a: 1 };
}
if (/^#?[0-9a-fA-F]{8}$/.test(s)) {
let a = parseInt("0x" + s.substring(s.length - 2, s.length));
a = a / 255;
let b = parseInt("0x" + s.substring(s.length - 4, s.length - 2));
let g = parseInt("0x" + s.substring(s.length - 6, s.length - 4));
let r = parseInt("0x" + s.substring(s.length - 8, s.length - 6));
return { r, g, b, a };
}
}
</script>
<template>
<lay-dropdown>
<div class="layui-unselect layui-colorpicker">
<span>
<span
class="layui-colorpicker-trigger-span"
lay-type=""
:style="`background-color: ${colorObj.rgba}`"
>
<i class="layui-icon layui-colorpicker-trigger-i layui-icon-down"></i>
</span>
</span>
</div>
<template #content>
<div class="layui-color-picker">
<div
class="saturation-value"
ref="saturationValue"
@mousedown="mousedownSV"
>
<div :style="`background-color: hsl(${hue}, 100%, 50%);`">
<div class="point" :style="pointStyle"></div>
</div>
<div class="saturation-value-2"></div>
<div class="saturation-value-3"></div>
</div>
<div class="layui-color-picker-middle">
<div style="flex: auto">
<div class="hue-slider" ref="hueSlider" @mousedown="mousedownHue">
<div class="slider" :style="hueSliderStyle"></div>
</div>
<div
class="alpha-slider"
ref="alphaSlider"
@mousedown="mousedownAlpha"
>
<div class="slider" :style="alphaSliderStyle"></div>
<div
:style="`background: linear-gradient(to right, rgba(0,0,0,0), ${colorObj.rgb});width: 100%;height: 100%`"
></div>
</div>
</div>
<div class="color-diamond">
<div
:style="`background-color: ${colorObj.rgba};width: 100%;height: 100%;box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .15), inset 0 0 4px rgba(0, 0, 0, .25);`"
></div>
</div>
</div>
<div class="color-value">
<div class="hex">
<label>
<input
:value="colorObj.hex8"
@input="hexChange"
spellcheck="false"
/>
</label>
</div>
<div class="rgba-r">
<label>
<input :value="red" @input="redChange" />
</label>
</div>
<div class="rgba-g">
<label>
<input :value="green" @input="greenChange" />
</label>
</div>
<div class="rgba-b">
<label>
<input :value="blue" @input="blueChange" />
</label>
</div>
<div class="rgba-a">
<label>
<input :value="alpha" @input="alphaChange" />
</label>
</div>
</div>
<ul class="preset">
<li
v-for="item in preset"
:key="item"
:style="`background-color: ${item}`"
@click="presetChange(item)"
></li>
</ul>
</div>
</template>
</lay-dropdown>
</template>

View File

@@ -0,0 +1,14 @@
@import (reference) "../../theme/variable.less";
.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,8 @@
import type { App } from "vue";
import Component from "./index.vue";
Component.install = (app: App) => {
app.component(Component.name, Component);
};
export default Component;

View File

@@ -0,0 +1,28 @@
<script lang="ts">
export default {
name: "LayContainer",
};
</script>
<script setup lang="ts">
import { computed } 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 @@
@import (reference) "../../theme/variable.less";

View File

@@ -0,0 +1,8 @@
import type { App } from "vue";
import Component from "./index.vue";
Component.install = (app: App) => {
app.component(Component.name, Component);
};
export default Component;

View File

@@ -0,0 +1,107 @@
<script lang="ts">
export default {
name: "LayCountUp",
};
</script>
<script setup lang="ts">
import { computed, onMounted, Ref, ref, watch } from "vue";
import { TransitionPresets, useTransition } from "@vueuse/core";
export interface LayCountupProps {
startVal?: number; // 起始值
endVal?: number; //显示的值
decimal?: string; // 小数点
decimalPlaces?: number; // 小数位数
useGrouping?: boolean; // 是否使用千位分隔符
separator?: string; // 千位分隔符
autoplay?: boolean; //是否自动播放
useEasing?: boolean; // 使用动画
easingFn?: any; //动画类型
duration?: number; // 动画持续时间
prefix?: string; // 前缀
suffix?: string; // 后缀
}
const props = withDefaults(defineProps<LayCountupProps>(), {
startVal: 0,
endVal: 0,
decimal: ".",
decimalPlaces: 0,
useGrouping: true,
separator: ",",
autoplay: true,
useEasing: true,
easingFn: TransitionPresets.easeInOutCubic,
duration: 2000,
prefix: "",
suffix: "",
});
let localStartVal: Ref<number> = ref(props.startVal);
const isNumber = (val: string) => !isNaN(parseFloat(val));
/**
* from: https://github.com/PanJiaChen/vue-countTo/blob/master/src/vue-countTo.vue
* @description 格式化数字
* @param num 要格式化的数字
* @returns 格式化后的数字
*/
const formatNumber = (num: number | string): string => {
if (typeof num != "number") return "0";
num = num.toFixed(props.decimalPlaces);
num += "";
const x = num.split(".");
let x1 = x[0];
const x2 = x.length > 1 ? props.decimal + x[1] : "";
const rgx = /(\d+)(\d{3})/;
if (props.separator && !isNumber(props.separator)) {
while (rgx.test(x1)) {
x1 = x1.replace(rgx, "$1" + props.separator + "$2");
}
}
return props.prefix + x1 + x2 + props.suffix;
};
const printVal = useTransition(localStartVal, {
delay: 0,
duration: props.duration,
disabled: !props.useEasing,
transition:
typeof props.easingFn === "string"
? TransitionPresets[props.easingFn]
: props.easingFn,
});
const displayValue = computed(() => formatNumber(printVal.value));
const start = function () {
localStartVal.value = props.endVal;
};
watch(
() => props.endVal,
() => {
if (props.autoplay) {
localStartVal.value = props.endVal;
}
}
);
onMounted(() => {
if (props.autoplay) {
start();
}
});
defineExpose({
start,
});
</script>
<template>
<slot name="prefix"></slot>
<!-- <span style="font-family: sans-serif" /> -->
<span>{{ displayValue }}</span>
<slot name="suffix"></slot>
</template>

View File

@@ -0,0 +1,43 @@
/**
* 获取年份列表
*/
const getYears = () => {
let years = [];
for (let i = 1970; i < getYear() + 100; i++) {
years.push(i);
}
return years;
};
/**
* 获取当前日期
*/
const getDate = () => {
return new Date();
};
/**
* 获取当前年份
*/
const getYear = () => {
return getDate().getFullYear();
};
/**
* 获取当前月份
*/
const getMonth = () => {
return getDate().getMonth();
};
/**
* 获取月份天数
*
* @param year
* @param month
*/
const getDayLength = (year: number, month: number): number => {
return new Date(year, month + 1, 0).getDate();
};
export { getDayLength, getYears, getDate, getMonth, getYear };

View File

@@ -0,0 +1,8 @@
import type { App } from "vue";
import Component from "./index.vue";
Component.install = (app: App) => {
app.component(Component.name, Component);
};
export default Component;

View File

@@ -0,0 +1,525 @@
<template>
<div>
<lay-dropdown ref="dropdownRef" :disabled="props.disabled">
<lay-input :name="name" :value="dateValue || modelValue" readonly>
<template #prefix>
<lay-icon type="layui-icon-date"></lay-icon>
</template>
</lay-input>
<template #content>
<!-- 日期选择 -->
<div
class="layui-laydate"
v-show="showPane === 'date' || showPane === 'datetime'"
>
<div class="layui-laydate-main laydate-main-list-0">
<div class="layui-laydate-header">
<i
class="layui-icon laydate-icon laydate-prev-y"
@click="changeYearOrMonth('year', -1)"
></i
>
<i
class="layui-icon laydate-icon laydate-prev-m"
@click="changeYearOrMonth('month', -1)"
></i
>
<div class="laydate-set-ym">
<span @click="showYearPanel">{{ currentYear }} </span>
<span @click="showPane = 'month'"
>{{ currentMonth + 1 }} </span
>
</div>
<i
class="layui-icon laydate-icon laydate-next-m"
@click="changeYearOrMonth('month', 1)"
></i
>
<i
class="layui-icon laydate-icon laydate-next-y"
@click="changeYearOrMonth('year', 1)"
></i
>
</div>
<div class="layui-laydate-content">
<table>
<thead>
<tr>
<th v-for="item of WEEK_NAME" :key="item">{{ item }}</th>
</tr>
</thead>
<tbody>
<template
v-for="(o, i) of dateList.length % 7 == 0
? dateList.length / 7
: Math.floor(dateList.length / 7) + 1"
:key="i"
>
<tr>
<td
v-for="(item, index) of dateList.slice(
i * 7,
i * 7 + 7
)"
:key="index"
:data-unix="item.value"
:class="{
'laydate-day-prev': item.type !== 'current',
'layui-this': item.value === currentDay,
}"
@click="handleDayClick(item)"
>
{{ item.day }}
</td>
</tr>
</template>
</tbody>
</table>
</div>
</div>
<div class="layui-laydate-footer">
<span
v-if="type === 'datetime'"
@click="showPane = 'time'"
class="laydate-btns-time"
>选择时间</span
>
<div class="laydate-footer-btns">
<span lay-type="clear" class="laydate-btns-clear" @click="clear"
>清空</span
>
<span lay-type="now" class="laydate-btns-now" @click="now"
>现在</span
>
<span lay-type="confirm" class="laydate-btns-confirm" @click="ok"
>确定</span
>
</div>
</div>
</div>
<!-- 年份选择器 -->
<div
class="layui-laydate"
v-show="showPane === 'year' || showPane === 'yearmonth'"
>
<div class="layui-laydate-main laydate-main-list-0 laydate-ym-show">
<div class="layui-laydate-header">
<div class="laydate-set-ym">
<span class="laydate-time-text">选择年份</span>
</div>
</div>
<div
class="layui-laydate-content"
style="height: 220px; overflow-y: auto"
>
<ul class="layui-laydate-list laydate-year-list">
<li
v-for="item of yearList"
:key="item"
:class="[{ 'layui-this': currentYear === item }]"
@click="handleYearClick(item)"
>
{{ item }}
</li>
</ul>
</div>
</div>
<div class="layui-laydate-footer">
<span
class="layui-laydate-preview"
title="当前选中的结果"
style="color: rgb(102, 102, 102)"
>{{ dateValue }}</span
>
<div class="laydate-footer-btns">
<span lay-type="clear" class="laydate-btns-clear" @click="clear"
>清空</span
>
<span lay-type="now" class="laydate-btns-now" @click="now"
>现在</span
>
<span lay-type="confirm" class="laydate-btns-confirm" @click="ok"
>确定</span
>
</div>
</div>
</div>
<!-- 月份选择器 -->
<div class="layui-laydate" v-show="showPane === 'month'">
<div class="layui-laydate-main laydate-main-list-0 laydate-ym-show">
<div class="layui-laydate-header">
<i
class="layui-icon laydate-icon laydate-prev-y"
@click="changeYearOrMonth('year', -1)"
></i
>
<div class="laydate-set-ym">
<span
@click="showYearPanel"
v-if="showPane === 'date' || showPane === 'datetime'"
>{{ currentYear }} 年</span
>
<span @click="showPane = 'month'"
>{{ currentMonth + 1 }} 月</span
>
</div>
<i
class="layui-icon laydate-icon laydate-next-y"
@click="changeYearOrMonth('year', 1)"
></i
>
</div>
<div class="layui-laydate-content" style="height: 220px">
<ul class="layui-laydate-list laydate-month-list">
<li
v-for="item of MONTH_NAME"
:key="item"
:class="[
{ 'layui-this': MONTH_NAME.indexOf(item) === currentMonth },
]"
@click="handleMonthClick(item)"
>
{{ item.slice(0, 3) }}
</li>
</ul>
</div>
</div>
<div class="layui-laydate-footer">
<span
class="layui-laydate-preview"
title="当前选中的结果"
style="color: rgb(102, 102, 102)"
>{{ dateValue }}</span
>
<div class="laydate-footer-btns">
<span lay-type="clear" class="laydate-btns-clear" @click="clear"
>清空</span
>
<span lay-type="now" class="laydate-btns-now" @click="now"
>现在</span
>
<span lay-type="confirm" class="laydate-btns-confirm" @click="ok"
>确定</span
>
</div>
</div>
</div>
<!-- 时间选择器 -->
<div class="layui-laydate" v-if="showPane == 'time'">
<div class="layui-laydate-main laydate-main-list-0 laydate-time-show">
<div class="layui-laydate-header">
<div class="laydate-set-ym">
<span class="laydate-time-text">选择时间</span>
</div>
</div>
<div class="layui-laydate-content" style="height: 210px">
<ul class="layui-laydate-list laydate-time-list">
<li class="num-list" v-for="item in els" :key="item.type">
<ol class="scroll" @click="choseTime">
<li
v-for="(it, index) in item.count"
:id="item.type + index.toString()"
:data-value="index.toString().padStart(2, '0')"
:data-type="item.type"
:key="it"
:class="[
'num',
index.toString().padStart(2, '0') == hms[item.type]
? 'layui-this'
: '',
]"
>
{{ index.toString().padStart(2, "0") }}
</li>
</ol>
</li>
</ul>
</div>
</div>
<div class="layui-laydate-footer">
<span
@click="showPane = 'date'"
v-if="type != 'time'"
class="laydate-btns-time"
>返回日期</span
>
<div class="laydate-footer-btns">
<span lay-type="clear" class="laydate-btns-clear" @click="clear"
>清空</span
>
<span lay-type="now" class="laydate-btns-now" @click="now"
>现在</span
>
<span lay-type="confirm" class="laydate-btns-confirm" @click="ok"
>确定</span
>
</div>
</div>
</div>
</template>
</lay-dropdown>
</div>
</template>
<script lang="ts" setup>
import { ref, watch, computed, defineProps, defineEmits, onMounted } from "vue";
import moment from "moment";
import LayIcon from "../icon/index";
import LayInput from "../input/index.vue";
import LayDropdown from "../dropdown/index.vue";
import { getDayLength, getYears, getMonth, getYear } from "./day";
export interface LayDatePickerProps {
modelValue?: string;
type?: "date" | "datetime" | "year" | "time" | "month" | "yearmonth";
name?: string;
max?: string;
min?: string;
disabled?: boolean;
simple?: boolean;
}
const props = withDefaults(defineProps<LayDatePickerProps>(), {
modelValue: "",
type: "date",
disabled: false,
simple: false,
});
const dropdownRef = ref(null);
const $emits = defineEmits(["update:modelValue"]);
const WEEK_NAME = ["日", "一", "二", "三", "四", "五", "六"];
const MONTH_NAME = [
"1月",
"2月",
"3月",
"4月",
"5月",
"6月",
"7月",
"8月",
"9月",
"10月",
"11月",
"12月",
];
const hms = ref({
hh: moment(props.modelValue).hour(),
mm: moment(props.modelValue).minute(),
ss: moment(props.modelValue).second(),
});
const els = [
{ count: 24, type: "hh" },
{ count: 60, type: "mm" },
{ count: 60, type: "ss" },
];
const currentYear = ref(getYear());
const currentMonth = ref(getMonth());
const currentDay = ref<number>();
const yearList = ref<number[]>(getYears());
const dateList = ref<any[]>([]);
const showPane = ref("date");
watch(
() => props.type,
() => {
showPane.value = props.type;
},
{ immediate: true }
);
onMounted(() => {
hms.value.hh = moment(props.modelValue).hour();
hms.value.mm = moment(props.modelValue).minute();
hms.value.ss = moment(props.modelValue).second();
});
// 计算结果日期
const dateValue = computed<string>(() => {
if (currentDay.value === -1) {
$emits("update:modelValue", "");
return "";
}
let momentVal;
let momentObj = moment(currentDay.value)
.hour(hms.value.hh)
.minute(hms.value.mm)
.second(hms.value.ss);
switch (props.type) {
case "date":
momentVal = momentObj.format("YYYY-MM-DD");
break;
case "datetime":
momentVal = momentObj.format("YYYY-MM-DD HH:mm:ss");
break;
case "year":
momentVal = momentObj.format("YYYY");
break;
case "month":
momentVal = momentObj.format("MM");
break;
case "time":
momentVal = momentObj.format("HH:mm:ss");
break;
case "yearmonth":
momentVal = momentObj.format("YYYY-MM");
break;
default:
momentVal = momentObj.format();
break;
}
$emits("update:modelValue", momentVal);
if (props.simple) {
ok();
}
return momentVal;
});
// 设置日期列表
const setDateList = (year: number, month: number) => {
const curDays = getDayLength(year, month); // 当月天数
const prevDays = getDayLength(year, month - 1); // 上月天数
const curFirstDayWeek = new Date(year, month, 1).getDay(); // 当月第一天星期几
const list: any[] = [];
// 填充上月天数
for (let i = prevDays - curFirstDayWeek + 1; i <= prevDays; i++) {
list.push({
day: i,
value: +new Date(year, month - 1, i),
isRange: false,
isSelected: false,
type: "prev",
});
}
// 填充当月天数
for (let i = 1; i <= curDays; i++) {
list.push({
day: i,
value: +new Date(year, month, i),
isRange: false,
isSelected: false,
type: "current",
});
}
// 填充下月天数
const nextDays = 7 - (list.length % 7);
if (nextDays !== 7) {
for (let i = 1; i <= nextDays; i++) {
list.push({
day: i,
value: +new Date(year, month + 1, i),
isRange: false,
isSelected: false,
type: "next",
});
}
}
dateList.value = list;
};
// 监听年月, 刷新日期
watch(
[currentYear, currentMonth],
() => {
setDateList(currentYear.value, currentMonth.value);
},
{ immediate: true }
);
// 确认事件
const ok = () => {
if (dropdownRef.value)
// @ts-ignore
dropdownRef.value.hide();
};
// 现在时间
const now = () => {
currentDay.value = moment().valueOf();
hms.value.hh = moment().hour();
hms.value.mm = moment().minute();
hms.value.ss = moment().second();
};
// 清空日期
const clear = () => {
currentDay.value = -1;
};
// 切换年月
const changeYearOrMonth = (type: "year" | "month", num: number) => {
if (type === "year") {
currentYear.value += num;
} else {
let month = currentMonth.value + num;
if (month > 11) {
month = 0;
currentYear.value++;
} else if (month < 0) {
month = 11;
currentYear.value--;
}
currentMonth.value = month;
}
};
// 显示年列表面板
const showYearPanel = () => {
showPane.value = "year";
};
// 点击年份
const handleYearClick = (item: any) => {
currentYear.value = item;
if (props.type === "year") {
currentDay.value = moment().year(item).valueOf();
} else if (props.type === "yearmonth") {
currentDay.value = moment().year(item).valueOf();
showPane.value = "month";
} else {
showPane.value = "date";
}
};
// 点击月份
const handleMonthClick = (item: any) => {
currentMonth.value = MONTH_NAME.indexOf(item);
if (props.type === "month") {
currentDay.value = moment(currentDay.value)
.month(MONTH_NAME.indexOf(item))
.valueOf();
} else if (props.type === "yearmonth") {
currentDay.value = moment(currentDay.value)
.month(MONTH_NAME.indexOf(item))
.valueOf();
} else {
showPane.value = "date";
}
};
// 点击日期
const handleDayClick = (item: any) => {
currentDay.value = item.value;
if (item.type !== "current") {
currentMonth.value =
item.type === "prev" ? currentMonth.value - 1 : currentMonth.value + 1;
}
};
// 点击时间 - hms
const choseTime = (e: any) => {
if (e.target.nodeName == "LI") {
let { value, type } = e.target.dataset;
hms.value[type as keyof typeof hms.value] = value;
}
};
</script>

View File

@@ -0,0 +1 @@
export type DatePickerType = "date" | "datetime" | "year" | "time" | "month";

View File

@@ -0,0 +1,28 @@
@import (reference) "../../theme/variable.less";
.layui-dropdown {
position: relative;
display: inline-block;
}
.layui-dropdown dl {
display: none;
position: absolute;
left: 0;
margin-top: 2px;
z-index: 899;
min-width: 100%;
background-color: #fff;
box-sizing: border-box;
border: 1px solid #e4e7ed;
border-radius: 2px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, .1);
}
.layui-dropdown dl > .layui-dropdown-menu {
border-radius: @global-border-radius;
}
.layui-dropdown-up > dl {
display: block;
}

View File

@@ -0,0 +1,8 @@
import type { App } from "vue";
import Component from "./index.vue";
Component.install = (app: App) => {
app.component(Component.name, Component);
};
export default Component;

View File

@@ -0,0 +1,71 @@
<script lang="ts">
export default {
name: "LayDropdown",
};
</script>
<script setup lang="ts">
import "./index.less";
import { provide, ref } from "vue";
import { onClickOutside } from "@vueuse/core";
import { DropdownTrigger } from "./interface";
export interface LayDropdownProps {
trigger?: DropdownTrigger;
disabled?: boolean;
}
const props = withDefaults(defineProps<LayDropdownProps>(), {
trigger: "click",
disabled: false,
});
const emit = defineEmits(["open", "hide"]);
const openState = ref(false);
const dropdownRef = ref<null | HTMLElement>(null);
onClickOutside(dropdownRef, (event) => {
openState.value = false;
});
const open = function () {
if (props.disabled === false) {
openState.value = true;
emit("open");
}
};
const hide = function () {
openState.value = false;
emit("hide");
};
const toggle = function () {
if (props.disabled === false)
if (openState.value) {
hide();
} else {
open();
}
};
provide("openState", openState);
defineExpose({ open, hide, toggle });
</script>
<template>
<div
ref="dropdownRef"
class="layui-dropdown"
@mouseenter="trigger === 'hover' && open()"
@mouseleave="trigger === 'hover' && hide()"
:class="{ 'layui-dropdown-up': openState }"
>
<div @click="trigger === 'click' && toggle()">
<slot></slot>
</div>
<dl class="layui-anim layui-anim-upbit">
<slot name="content"></slot>
</dl>
</div>
</template>

View File

@@ -0,0 +1 @@
export type DropdownTrigger = "click" | "hover";

View File

@@ -0,0 +1,8 @@
import type { App } from "vue";
import Component from "./index.vue";
Component.install = (app: App) => {
app.component(Component.name, Component);
};
export default Component;

View File

@@ -0,0 +1,11 @@
<template>
<ul class="layui-menu layui-dropdown-menu">
<slot></slot>
</ul>
</template>
<script lang="ts">
export default {
name: "LayDropdownMenu",
};
</script>

View File

@@ -0,0 +1 @@
@import (reference) "../../theme/variable.less";

View File

@@ -0,0 +1,8 @@
import type { App } from "vue";
import Component from "./index.vue";
Component.install = (app: App) => {
app.component(Component.name, Component);
};
export default Component;

View File

@@ -0,0 +1,23 @@
<script lang="ts">
export default {
name: "LayDropdownMenuItem",
};
</script>
<script setup lang="ts">
import { inject, Ref } from "vue";
const openState: Ref<boolean> = inject("openState") as Ref<boolean>;
const click = function () {
openState.value = false;
};
</script>
<template>
<li>
<div class="layui-menu-body-title" @click="click">
<slot></slot>
</div>
</li>
</template>

View File

@@ -0,0 +1,26 @@
@import (reference) "../../theme/variable.less";
.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;
}
.layui-empty-extra {
margin-top: 30px;
}

View File

@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="620px" height="200px" viewBox="0 0 620 200" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>Group 35</title>
<defs>
<linearGradient x1="100%" y1="28.3855365%" x2="50%" y2="60.1282768%" id="linearGradient-1">
<stop stop-color="#A3B1BF" stop-opacity="0" offset="0%"></stop>
<stop stop-color="#A3B1BF" offset="100%"></stop>
</linearGradient>
<linearGradient x1="-1.11022302e-14%" y1="50%" x2="100%" y2="50%" id="linearGradient-2">
<stop stop-color="#A3B1BF" stop-opacity="0" offset="0%"></stop>
<stop stop-color="#A3B1BF" offset="100%"></stop>
</linearGradient>
<linearGradient x1="100%" y1="50%" x2="2.77050217%" y2="50%" id="linearGradient-3">
<stop stop-color="#A3B1BF" stop-opacity="0" offset="0%"></stop>
<stop stop-color="#A3B1BF" offset="100%"></stop>
</linearGradient>
</defs>
<g id="框架设计-过程版" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="缺省图" transform="translate(-1543.000000, -154.000000)">
<g id="Group-35" transform="translate(1543.000000, 154.000000)">
<g id="暂无数据" transform="translate(57.000000, 3.000000)">
<path d="M127.424021,59.5388128 C126.606599,58.4063927 125.438263,57.4280114 123.958785,57.4280114 C123.344291,57.4280114 122.540774,57.5633407 121.976378,57.8001748 L121.286649,58.1531256 L121.120501,57.4280114 C120.642337,55.5707287 118.554686,53.8578054 116.339591,53.8578054 C114.440304,53.8578054 112.647282,54.7806864 111.893274,56.4567899 L111.685907,56.9188903 C110.879484,56.8390213 110.408842,56.7990868 110.273979,56.7990868 C108.807184,56.7990868 107.918237,58.1707763 107.760333,59.5388128 L127.424021,59.5388128 Z M116.256317,51.6240487 C118.838356,51.6240487 121.212177,53.0633181 122.428006,55.3281583 C122.702588,55.2940639 122.980213,55.2770167 123.257839,55.2770167 C126.194825,55.2770167 128.762253,57.1607306 129.646271,59.9643836 L129.674277,60.0538813 L129.674277,61.6371587 L91.9308053,61.6371587 L91.9308053,59.5388128 L105.610959,59.5388128 C105.763166,56.9263318 107.861796,54.8283105 110.474277,54.6767123 C111.779604,52.7589041 113.917199,51.6240487 116.256317,51.6240487 Z M98.0152207,55.2770167 L98.0152207,57.338946 L85.8386606,57.338946 L85.8386606,55.2770167 L98.0152207,55.2770167 Z" id="cloud4-copy" fill="#A3B1BF"></path>
<path d="M41.5853608,128.914764 C40.7679381,127.782344 39.5996027,126.803963 38.1201246,126.803963 C37.5056307,126.803963 36.7021134,126.939292 36.1377176,127.176126 L35.4479887,127.529077 L35.2818407,126.803963 C34.8036768,124.94668 32.7160256,123.233757 30.5009305,123.233757 C28.6016434,123.233757 26.8086214,124.156638 26.0546139,125.832741 L25.847246,126.294842 C25.0408238,126.214973 24.5701811,126.175038 24.435318,126.175038 C22.968523,126.175038 22.0795768,127.546728 21.9216729,128.914764 L41.5853608,128.914764 Z M30.417656,121 C32.9996956,121 35.373516,122.439269 36.5893455,124.70411 C36.8639269,124.670015 37.1415525,124.652968 37.4191781,124.652968 C40.3561644,124.652968 42.9235921,126.536682 43.8076104,129.340335 L43.8356164,129.429833 L43.8356164,131.01311 L6.09214469,131.01311 L6.09214469,128.914764 L19.7722983,128.914764 C19.9245053,126.302283 22.0231355,124.204262 24.6356164,124.052664 C25.9409437,122.134855 28.0785388,121 30.417656,121 Z M12.1765601,124.652968 L12.1765601,126.714897 L1.19726451e-12,126.714897 L1.19726451e-12,124.652968 L12.1765601,124.652968 Z" id="cloud3" fill="#A3B1BF"></path>
<path d="M503.585361,128.914764 C502.767938,127.782344 501.599603,126.803963 500.120125,126.803963 C499.505631,126.803963 498.702113,126.939292 498.137718,127.176126 L497.447989,127.529077 L497.281841,126.803963 C496.803677,124.94668 494.716026,123.233757 492.50093,123.233757 C490.601643,123.233757 488.808621,124.156638 488.054614,125.832741 L487.847246,126.294842 C487.040824,126.214973 486.570181,126.175038 486.435318,126.175038 C484.968523,126.175038 484.079577,127.546728 483.921673,128.914764 L503.585361,128.914764 Z M492.417656,121 C494.999696,121 497.373516,122.439269 498.589346,124.70411 C498.863927,124.670015 499.141553,124.652968 499.419178,124.652968 C502.356164,124.652968 504.923592,126.536682 505.80761,129.340335 L505.835616,129.429833 L505.835616,131.01311 L468.092145,131.01311 L468.092145,128.914764 L481.772298,128.914764 C481.924505,126.302283 484.023135,124.204262 486.635616,124.052664 C487.940944,122.134855 490.078539,121 492.417656,121 Z M474.17656,124.652968 L474.17656,126.714897 L462,126.714897 L462,124.652968 L474.17656,124.652968 Z" id="cloud3" fill="#A3B1BF"></path>
<path d="M404.542857,12.7724191 C393.849559,45.4839352 372.154979,58.2098935 339.459117,50.950294 C277.116397,35.7637149 362.44716,-23.5985442 347.6627,53.8807932 C342.542621,80.7130415 317.479147,99.6801616 272.40272,99.6528527" id="Path-4" stroke="url(#linearGradient-1)" stroke-width="3" stroke-linecap="round" fill-rule="nonzero" transform="translate(338.472789, 56.212636) rotate(11.000000) translate(-338.472789, -56.212636) "></path>
<path d="M362.000112,73.9497723 C362.670671,73.9497723 363.214076,73.4063457 363.214076,72.7358084 L363.214076,71.2139639 C363.214076,70.5434265 362.670649,70 362.000134,70 C361.329619,70 360.786125,70.5434489 360.786125,71.2139639 L360.786125,72.7358084 C360.786125,73.4063234 361.329574,73.9497723 362.000134,73.9497723 L362.000112,73.9497723 Z M362.000112,86.0502054 C361.329574,86.0502054 360.786148,86.5936543 360.786148,87.2641693 L360.786148,88.7859915 C360.786148,89.4565511 361.329597,89.9999777 362.000134,89.9999777 C362.670671,89.9999777 363.214076,89.4565288 363.214076,88.7859915 L363.214076,87.2641916 C363.214076,86.5936543 362.670649,86.0502054 362.000134,86.0502054 L362.000112,86.0502054 Z M370.786058,78.7861255 L369.264236,78.7861255 C368.593677,78.7861255 368.050429,79.3293733 368.050429,80.0000894 C368.050429,80.670448 368.593677,81.2138969 369.264236,81.2138969 L370.786058,81.2138969 C371.456596,81.2138969 372,80.670448 372,80.0000894 C372,79.3293733 371.456573,78.7861255 370.786058,78.7861255 L370.786058,78.7861255 Z M355.949795,80.0000894 C355.949795,79.3293733 355.406346,78.7861255 354.735808,78.7861255 L353.213986,78.7861255 C352.54403,78.7861255 352,79.3293733 352,80.0000894 C352,80.670448 352.54403,81.2138969 353.213986,81.2138969 L354.735808,81.2138969 C355.406368,81.2138969 355.949795,80.670448 355.949795,80.0000894 Z M367.995429,84.2784508 C367.521165,83.8043656 366.753139,83.8043656 366.278473,84.2784508 C365.804388,84.7527148 365.804388,85.5210984 366.278473,85.9951836 L367.354715,87.0710234 C367.591467,87.3081554 367.902029,87.4269336 368.213171,87.4269336 C368.523732,87.4269336 368.834115,87.3081778 369.071046,87.0710234 C369.545533,86.5967595 369.545533,85.8285769 369.071046,85.3544693 L367.995407,84.2784508 L367.995429,84.2784508 Z M356.004236,75.721929 C356.241569,75.959262 356.552733,76.0774371 356.863272,76.0774371 C357.174012,76.0774371 357.484395,75.959262 357.721728,75.7224874 C358.195813,75.2484022 358.195813,74.4798399 357.722108,74.0051961 L356.646089,72.9289542 C356.172384,72.4554721 355.40362,72.4554721 354.928954,72.9279936 C354.45469,73.4026597 354.45469,74.1712444 354.928574,74.6453073 L356.004236,75.721929 Z M356.004839,84.2784508 L354.928999,85.3544917 C354.454735,85.8285769 354.454735,86.5967818 354.928999,87.0710234 C355.165572,87.3081554 355.476715,87.4269336 355.787656,87.4269336 C356.097837,87.4269336 356.408801,87.3081778 356.645531,87.0710234 L357.72175,85.9951836 C358.195836,85.5210984 358.195836,84.7527595 357.72175,84.2784508 C357.247665,83.8043879 356.478343,83.8043879 356.004839,84.2784508 L356.004839,84.2784508 Z M367.13713,76.0774371 C367.447513,76.0774371 367.759057,75.959262 367.995787,75.721929 L369.071828,74.6453073 C369.545511,74.1712444 369.545511,73.4026597 369.071046,72.9279936 C368.596782,72.4554721 367.828421,72.4545115 367.353754,72.9289542 L366.278116,74.0051961 C365.80441,74.4798622 365.80441,75.2484245 366.278495,75.7224874 C366.515806,75.959262 366.826368,76.0774371 367.13713,76.0774371 Z" id="Shape" stroke="#A3B1BF" fill="#A3B1BF" fill-rule="nonzero" opacity="0.40327381"></path>
<path d="M401.801829,22.0236541 C402.387615,22.6094406 402.387615,23.559188 401.801829,24.1449745 L400.033314,25.9123143 L401.801829,27.6805084 C402.387615,28.2662948 402.387615,29.2160423 401.801829,29.8018287 C401.216042,30.3876151 400.266295,30.3876151 399.680508,29.8018287 L397.912314,28.0333143 L396.144974,29.8018287 C395.559188,30.3876151 394.609441,30.3876151 394.023654,29.8018287 C393.437868,29.2160423 393.437868,28.2662948 394.023654,27.6805084 L395.791314,25.9123143 L394.023654,24.1449745 C393.437868,23.559188 393.437868,22.6094406 394.023654,22.0236541 C394.609441,21.4378677 395.559188,21.4378677 396.144974,22.0236541 L397.912314,23.7913143 L399.680508,22.0236541 C400.266295,21.4378677 401.216042,21.4378677 401.801829,22.0236541 Z" id="Combined-Shape-Copy-8" fill="#A3B1BF" fill-rule="nonzero" opacity="0.40327381" transform="translate(397.912741, 25.912741) rotate(30.000000) translate(-397.912741, -25.912741) "></path>
<path d="M182.481192,112.703017 C183.066978,113.288804 183.066978,114.238551 182.481192,114.824337 L180.712677,116.591677 L182.481192,118.359871 C183.066978,118.945658 183.066978,119.895405 182.481192,120.481192 C181.895405,121.066978 180.945658,121.066978 180.359871,120.481192 L178.591677,118.712677 L176.824337,120.481192 C176.238551,121.066978 175.288804,121.066978 174.703017,120.481192 C174.117231,119.895405 174.117231,118.945658 174.703017,118.359871 L176.470677,116.591677 L174.703017,114.824337 C174.117231,114.238551 174.117231,113.288804 174.703017,112.703017 C175.288804,112.117231 176.238551,112.117231 176.824337,112.703017 L178.591677,114.470677 L180.359871,112.703017 C180.945658,112.117231 181.895405,112.117231 182.481192,112.703017 Z" id="Combined-Shape-Copy-9" fill="#A3B1BF" fill-rule="nonzero" opacity="0.40327381" transform="translate(178.592104, 116.592104) rotate(21.000000) translate(-178.592104, -116.592104) "></path>
<path d="M255.756523,87.6774526 C252.646357,87.2191043 252.409392,86.777944 252.409392,86.777944 C253.049197,83.0595931 251.064615,78.3500639 247.824118,75.8005013 C243.511353,72.4087236 236.953345,74.8723459 231.846748,69.7159271 C230.655999,68.5127627 231.349122,83.2085563 237.741254,88.7774886 C242.41539,92.8453301 247.918904,91.5963309 249.429556,90.9317258 C250.798029,90.3244143 251.757738,89.2186489 251.757738,89.2186489 C254.251795,89.963465 255.484013,89.9176301 255.484013,89.9176301 C256.360784,90.0207585 256.805093,87.8321452 255.756523,87.6774526 Z M250.23976,88.4387369 C241.572443,86.6548975 235.860216,76.8764451 235.860216,76.8764451 C235.860216,76.8764451 241.393479,83.559077 250.840571,86.3965362 C250.852475,86.8794631 250.555229,88.1644822 250.23976,88.4387369 Z" id="Shape" fill="#A3B1BF" fill-rule="nonzero" transform="translate(243.866495, 80.646219) rotate(-50.000000) translate(-243.866495, -80.646219) "></path>
<path d="M284.036575,108.186104 C283.941177,108.060119 283.790165,107.990999 283.632082,108.000944 C283.475216,108.010889 283.333562,108.099961 283.25515,108.23651 C281.890084,110.62706 280.122319,111.45085 278.562891,112.177417 C277.542023,112.653192 276.577904,113.102556 275.8597,113.917577 C275.058899,114.82753 274.68547,116.056199 274.68547,117.781131 C274.68547,118.442066 274.740466,119.174493 274.850479,119.994775 C272.790007,120.490481 271,120.205086 271,120.205086 L271,122.082971 C273.298142,122.082971 275.205273,121.541583 276.595466,120.956805 C277.50628,120.611142 278.309967,120.149996 279.018256,119.620329 C279.099658,119.559441 279.143523,119.524819 279.145275,119.523086 C282.559723,116.887066 283.66259,112.694189 283.66259,112.694189 C283.66259,117.175267 280.397443,121.061703 276.229665,122.45329 C277.51164,122.861698 278.702256,123.042133 279.779315,122.991747 C280.98867,122.934347 282.061049,122.588642 282.966607,121.961113 C284.894206,120.628123 286,118.044222 286,114.873809 C286,112.310396 285.265367,109.810244 284.036121,108.185485 L284.036595,108.186062 L284.036575,108.186104 Z" id="Path" fill="#A3B1BF"></path>
<g id="Group-3" transform="translate(258.984540, 88.713689) rotate(-20.000000) translate(-258.984540, -88.713689) translate(190.484540, 20.213689)" fill="#A3B1BF">
<path d="M108.768881,108.807257 L108.638364,133.235841 L100.902418,130.420187 L108.768881,108.807257 Z M32.9768459,14 L5.35958214,86.9540043 L5,15.0324311 L32.9768459,14 Z M109.292315,14.1079371 L109.493973,29.0798232 L68.8719874,14.2946298 L109.292315,14.1079371 Z" id="Combined-Shape" fill-rule="nonzero" opacity="0.149972098"></path>
<path d="M35.8002691,0.501535499 L66.6412144,11.7265355 L111.032838,11.7269142 L111.010214,27.8755355 L136.347379,37.0976908 L110.908214,106.989535 L110.871416,136.371331 L101.313214,132.891535 L98.642589,131.919338 L132.502241,38.8907083 L37.5932865,4.34667379 L3.73363431,97.3753036 L0.914214432,96.3501828 L3.75221443,88.5525355 L3.87910868,11.7269142 L31.7142144,11.7265355 L35.8002691,0.501535499 Z M108.412214,113.847535 L102.829214,129.187535 L108.487865,131.247078 L108.412214,113.847535 Z M30.5682144,14.8755355 L7.06327341,14.8759949 L7.09021443,79.3815355 L30.5682144,14.8755355 Z M108.039214,26.7945355 L107.989648,14.8759949 L75.2922144,14.8755355 L108.039214,26.7945355 Z" id="Combined-Shape"></path>
<path d="M13.847793,64.3592085 L13.847793,66.7945205 L11.3059361,66.7945205 L11.3059361,64.3592085 L13.847793,64.3592085 Z M18.847793,52.1826484 L18.847793,54.6179604 L11.3059361,54.6179604 L11.3059361,52.1826484 L18.847793,52.1826484 Z M21.847793,40.0060883 L21.847793,42.4414003 L11.3059361,42.4414003 L11.3059361,40.0060883 L21.847793,40.0060883 Z M18.0852588,25.3366488 C20.1027373,25.3366488 21.7382268,26.9721383 21.7382268,28.9896168 C21.7382268,31.0070954 20.1027373,32.6425849 18.0852588,32.6425849 C16.0677803,32.6425849 14.4322908,31.0070954 14.4322908,28.9896168 C14.4322908,26.9721383 16.0677803,25.3366488 18.0852588,25.3366488 Z" id="Combined-Shape" opacity="0.4765625"></path>
</g>
<g id="Group-3-Copy" transform="translate(208.000000, 39.000000)">
<path d="M3,99 L1.81887838e-12,99 L1.81887838e-12,-2.77555756e-17 L101,-2.77555756e-17 L101,99 L98,99 L98,3 L3,3 L3,99 Z" id="Combined-Shape" fill="#A3B1BF" fill-rule="nonzero" opacity="0.248070126"></path>
<path d="M25,89 L91,89 L91,92 L11,92 L11,89 L22,89 L22,68 L11,68 L11,65 L22,65 L22,56 L11,56 L11,53 L22,53 L22,44 L11,44 L11,41 L22,41 L22,32 L11,32 L11,29 L22,29 L22,10 L25,10 L25,17 L91,17 L91,20 L25,20 L25,29 L91,29 L91,32 L25,32 L25,41 L30,41 L30,44 L25,44 L25,53 L91,53 L91,56 L25,56 L25,65 L91,65 L91,68 L25,68 L25,77 L68,77 L68,80 L25,80 L25,89 Z M91,77 L91,80 L77,80 L77,77 L91,77 Z M91,41 L91,44 L47,44 L47,41 L91,41 Z" id="Combined-Shape" fill="#A3B1BF" fill-rule="nonzero" opacity="0.248070126"></path>
<circle id="Oval-6" stroke="#A3B1BF" stroke-width="3" opacity="0.3" cx="15" cy="18" r="2.5"></circle>
<circle id="Oval-6-Copy" stroke="#A3B1BF" stroke-width="3" opacity="0.3" cx="15" cy="79" r="2.5"></circle>
</g>
<rect id="Rectangle-34" fill="url(#linearGradient-2)" x="154" y="134" width="51" height="4"></rect>
<rect id="Rectangle-34-Copy" fill="url(#linearGradient-3)" x="319" y="134" width="45" height="4"></rect>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -0,0 +1,8 @@
import type { App } from "vue";
import Component from "./index.vue";
Component.install = (app: App) => {
app.component(Component.name, Component);
};
export default Component;

View File

@@ -0,0 +1,31 @@
<script lang="ts">
export default {
name: "LayEmpty",
};
</script>
<script setup lang="ts">
import "./index.less";
import { withDefaults } from "vue";
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 class="layui-empty-extra">
<slot name="extra"></slot>
</div>
</div>
</template>

Some files were not shown because too many files have changed in this diff Show More