This commit is contained in:
Theluyuan 2022-11-14 11:56:21 +08:00
commit 0a63adba99
337 changed files with 25661 additions and 0 deletions

23
.gitignore vendored Normal file
View File

@ -0,0 +1,23 @@
.DS_Store
node_modules/
dist/
example/dist/
lib/
es/
umd/
esm/
/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

78
README.md Normal file
View File

@ -0,0 +1,78 @@
## Introduction
<p>
<a href="https://www.npmjs.com/package/@layui/layui-vue"><img src="https://img.shields.io/npm/v/@layui/layui-vue.svg?sanitize=true" alt="Version"></a>
<a href="https://www.npmjs.com/package/@layui/layui-vue"><img src="https://img.shields.io/npm/l/@layui/layui-vue.svg?sanitize=true" alt="License"></a>
<a href="https://travis-ci.org/sentsin/layui"><img alt="Build Status" src="https://img.shields.io/travis/sentsin/layui/master.svg"></a>
<a href="https://coveralls.io/r/sentsin/layui?branch=master"><img alt="Test Coverage" src="https://img.shields.io/coveralls/sentsin/layui/master.svg"></a>
</p>
**[🔶 Explore the docs »](http://www.layui-vue.com)** **[Join us](https://jq.qq.com/?_wv=1027&k=ffiUQgnE)**
An enterprise-class UI components based on Layui and Vue.
**Run with code Sandbox.**
[![Edit layui-vue](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/11mvy)
## Features
A few of the things you can do with layui vue:
* Writing components using setup script
* Up to 60 high quality components
* Provide Axure design resources
* Support theme customization
* Support internationalization
## Get Started
Use npm to install.
```bash
npm i @layui/layui-vue
```
Before using, you need to mount layui Vue to Vue and introduce index.css style file
```
import App from './App.vue'
import { createApp } from 'vue'
import layui from '@layui/layui-vue'
import '@layui/layui-vue/lib/index.css'
createApp(App).use(layui).mount('#app')
```
We have several examples on the [website](http://layui-vue.pearadmin.com). Here is the first one to get you started:
```
<template>
<lay-button-container>
<lay-button>Hello Word</lay-button>
</lay-button-container>
</template>
```
## Feedback
Feel free to send us feedback on [file an issue](https://github.com/layui-vue/layui-vue/issues/new). Feature requests are always welcome. If you wish to contribute, please take a quick look at the [guidelines](./CONTRIBUTING.md)!
If there's anything you'd like to chat about, please feel free to join our [Gitter chat](https://gitter.im/layui-vue/community)!
## Build Process
- `build` Packaged component library
Please take a look at the [contributing guidelines](./CONTRIBUTING.md) for a detailed process on how to build your application as well as troubleshooting information.
## Contributors
This project follows the [all-contributors](https://github.com/layui-vue/layui-vue/graphs/contributors) specification and is brought to you by these [awesome contributors](https://github.com/layui-vue/layui-vue/graphs/contributors).
<a href="https://github.com/layui-vue/layui-vue/graphs/contributors">
<img src="https://contrib.rocks/image?repo=layui-vue/layui-vue" />
</a>
## Licence
Layui vue is licensed under the [MIT license](https://opensource.org/licenses/MIT).

58
package.json Normal file
View File

@ -0,0 +1,58 @@
{
"name": "@layui/layui-vue",
"version": "1.7.7",
"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",
"type": "module",
"keywords": [
"vue",
"vue-component",
"component-library",
"layui-vue",
"layui"
],
"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": {
"@layui/icons-vue": "^1.0.9",
"@layui/layer-vue": "^1.4.7",
"@vueuse/core": "^9.2.0",
"@umijs/ssr-darkreader": "^4.9.45",
"@ctrl/tinycolor": "^3.4.1",
"async-validator": "^4.1.1",
"cropperjs": "^1.5.12",
"dayjs": "^1.11.0",
"evtd": "^0.2.3",
"vue-i18n": "^9.1.10"
},
"files": [
"lib",
"es",
"umd",
"types"
],
"browserslist": [
"current node",
"last 2 versions and > 2%",
"ie > 10"
]
}

49
script/build.all.ts Normal file
View File

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

123
script/build.es.ts Normal file
View File

@ -0,0 +1,123 @@
import { UserConfigExport } from "vite";
import vue from "@vitejs/plugin-vue";
import vueJsx from "@vitejs/plugin-vue-jsx";
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");
const matchModule: string[] = [
"input",
"dropdown",
"carousel",
"transition",
"datePicker",
"header",
"selectOption",
"skeletonItem",
"tabItem",
"upload",
"checkbox",
"badge",
"button",
"tooltip",
"page",
"scroll",
"radio",
"empty",
"dropdownMenu",
"dropdownMenuItem",
"tag",
"tagInput",
"footer",
];
export default (): UserConfigExport => {
return {
publicDir: false,
resolve: {
alias: [
{
find: "@",
replacement: resolve(process.cwd(), "./"),
},
],
},
css: {
preprocessorOptions: {
less: {
javascriptEnabled: true,
},
},
postcss: {},
},
plugins: [vue(), vueJsx()],
build: {
cssCodeSplit: true,
emptyOutDir: true,
outDir: "es",
lib: {
entry: resolve(process.cwd(), "./src/index.ts"),
formats: ["es"],
},
terserOptions: {
compress: {
drop_console: true,
drop_debugger: true,
pure_funcs: ["console.log"],
},
output: {
comments: true,
},
},
rollupOptions: {
input: inputs,
output: {
globals: {
vue: "Vue",
},
manualChunks(id) {
let arr = id.toString().split("/");
if (id.includes("node_modules")) {
//id => layui-vue/node_modules/.pnpm/@vue+devtools-api@6.1.4/node_modules/@vue/devtools-api/lib/esm/api/app.js
const chunksName = "_chunks/";
return (
chunksName +
id.toString().split("node_modules/")[2].split("/")[0].toString()
);
} else if (arr.length >= 2) {
//if (arr.length >= 2 && arr[arr.length - 1].split('.')[1] != 'ts'){
let entryPoint = arr[arr.length - 2].toString();
if (matchModule.includes(entryPoint)) {
return entryPoint;
}
}
},
chunkFileNames: ({ name }) => {
return name === "index" ? "index.js" : "[name]/index.js";
},
entryFileNames: ({ name }) => {
return name === "index" ? "index.js" : "[name]/index.js";
},
assetFileNames: "[name]/index.css",
},
external: ["vue", "vue-router"],
},
},
};
};

49
script/build.umd.ts Normal file
View File

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

25
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,23 @@
import { defineComponent, VNodeTypes } from "vue";
import type { PropType } from "vue";
export type RenderFunc = (props: Record<string, unknown>) => VNodeTypes;
export default defineComponent({
name: "RenderFunction",
props: {
renderFunc: {
type: Function as PropType<RenderFunc>,
default: null,
},
},
setup(props, ctx) {
return () => {
if (typeof props.renderFunc !== "function") {
return null;
}
return props.renderFunc(ctx.attrs);
};
},
});

View File

@ -0,0 +1,40 @@
<script lang="ts">
export default {
name: "TeleportWrapper",
};
</script>
<script lang="ts" setup>
import { onMounted, ref } from "vue";
export interface TeleportWrapperProps {
to?: string;
disabled?: boolean;
}
const props = withDefaults(defineProps<TeleportWrapperProps>(), {
to: "",
disabled: false,
});
const target = ref<Element | null>(null);
onMounted(() => {
const observer = new MutationObserver((mutationList, observer) => {
for (const mutation of mutationList) {
if (mutation.type !== "childList") continue;
const el = document.querySelector(props.to);
if (!el) continue;
target.value = el;
observer.disconnect();
break;
}
});
observer.observe(document, { childList: true, subtree: true });
return () => observer.disconnect();
});
</script>
<template>
<Teleport :to="target" :disabled="!target || disabled">
<slot></slot>
</Teleport>
</template>

View File

@ -0,0 +1,5 @@
.layui-affix{
display: block;
z-index: 999;
transition: all 0.3s ease-in-out;
}

View File

@ -0,0 +1,5 @@
import { withInstall, WithInstallType } from "../../utils";
import Component from "./index.vue";
const component: WithInstallType<typeof Component> = withInstall(Component);
export default component;

View File

@ -0,0 +1,137 @@
<template>
<div class="layui-affix" :style="getStyle" ref="dom">
<slot></slot>
</div>
</template>
<script lang="ts">
export default {
name: "LayAffix",
};
</script>
<script setup lang="ts">
import "./index.less";
import {
ref,
onMounted,
onUnmounted,
nextTick,
computed,
StyleValue,
} from "vue";
export interface AiffxProps {
offset?: number;
target?: HTMLElement;
position?: string;
}
const props = withDefaults(defineProps<AiffxProps>(), {
offset: 0,
position: "top",
target: () => {
return document.body;
},
});
const emit = defineEmits(["scroll"]);
const outWindow = ref(false);
const dom = ref();
let changeScrollTop = 0;
let orginOffsetLeft = 0;
let orginOffsetTop = 0;
let marginLeft = 0;
let marginTop = 0;
let marginBottom = 0;
let fixedOffset = 0;
const getStyle = computed(() => {
if (outWindow.value && dom.value) {
let style = {
position: "fixed !important",
top: "unset",
bottom: "unset",
left: orginOffsetLeft - marginLeft + "px",
};
if (props.position === "top") {
style.top = fixedOffset - marginTop + "px";
} else {
style.bottom = fixedOffset - marginBottom + "px";
}
return style as StyleValue;
}
});
//
const checkInWindow = () => {
if (dom.value) {
let offsetTop = dom.value.offsetTop;
let scrollTop = props.target?.scrollTop;
if (props.position === "top") {
//top -+offsetTop
let result = offsetTop - scrollTop + props.target.offsetTop;
if (result < fixedOffset) {
if (outWindow.value) {
if (scrollTop <= changeScrollTop) {
outWindow.value = false;
}
} else {
changeScrollTop = scrollTop;
outWindow.value = true;
}
}
} else {
//bottom + - -
let viewHeight =
props.target.offsetHeight > window.innerHeight
? window.innerHeight
: props.target.offsetHeight;
let result = viewHeight + scrollTop - offsetTop - dom.value.offsetHeight;
if (outWindow.value) {
if (scrollTop >= changeScrollTop) {
outWindow.value = false;
}
} else {
if (result < fixedOffset) {
changeScrollTop = scrollTop - result + props.offset;
outWindow.value = true;
}
}
}
emit("scroll", {
targetScroll: scrollTop,
affixed: outWindow.value,
offset: !outWindow.value ? 0 : Math.abs(scrollTop - changeScrollTop),
});
}
};
const getDomStyle = (dom: any, attr: string) => {
if (dom.currentStyle) {
return dom.currentStyle[attr];
} else {
// @ts-ignore
return document.defaultView.getComputedStyle(dom, null)[attr];
}
};
onMounted(() => {
nextTick(() => {
orginOffsetTop = dom.value.offsetTop - props.target.offsetTop;
orginOffsetLeft = dom.value.getBoundingClientRect().left;
marginLeft = parseFloat(getDomStyle(dom.value, "marginLeft"));
marginTop = parseFloat(getDomStyle(dom.value, "marginTop"));
marginBottom = parseFloat(getDomStyle(dom.value, "marginBottom"));
fixedOffset = props.offset + props.target.offsetTop;
if (props.position === "bottom") {
fixedOffset = props.offset;
}
props.target.addEventListener("scroll", checkInWindow, true);
checkInWindow();
});
});
onUnmounted(() => {
props.target.removeEventListener("scroll", checkInWindow);
});
</script>

View File

@ -0,0 +1,51 @@
.layui-avatar {
font-size: 14px;
font-variant: tabular-nums;
border-radius: var(--global-border-radius);
box-sizing: border-box;
color: #ffffff;
list-style: none;
position: relative;
display: inline-block;
background: #eeeeee;
overflow: hidden;
white-space: nowrap;
text-align: center;
width: 32px;
height: 32px;
line-height: 32px;
vertical-align: middle;
}
.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;
}
.layui-avatar {
& > img {
width: 100%;
height: 100%;
display: block;
object-fit: cover;
}
}

View File

@ -0,0 +1,5 @@
import { withInstall, WithInstallType } from "../../utils";
import Component from "./index.vue";
const component: WithInstallType<typeof Component> = withInstall(Component);
export default component;

View File

@ -0,0 +1,45 @@
<script lang="ts">
import { computed, useSlots } from "vue";
import { LayIcon } from "@layui/icons-vue";
export default {
name: "LayAvatar",
};
</script>
<script setup lang="ts">
import "./index.less";
export interface AvatarProps {
src?: string;
size?: "xs" | "sm" | "md" | "lg";
radius?: boolean;
icon?: string;
alt?: string;
}
const props = withDefaults(defineProps<AvatarProps>(), {
size: "md",
radius: false,
icon: "layui-icon-username",
});
const slot = useSlots();
const classes = computed(() => {
return [
"layui-avatar",
props.radius ? "layui-avatar-radius" : "",
props.size ? `layui-avatar-${props.size}` : "",
];
});
</script>
<template>
<span :class="classes" v-if="slot.default">
<slot></slot>
</span>
<span v-else :class="classes">
<img v-if="src" :src="src" :alt="alt" />
<lay-icon v-else :type="icon" />
</span>
</template>

View File

@ -0,0 +1,5 @@
import { withInstall, WithInstallType } from "../../utils";
import Component from "./index.vue";
const component: WithInstallType<typeof Component> = withInstall(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,35 @@
@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: var(--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,5 @@
import { withInstall, WithInstallType } from "../../utils";
import Component from "./index.vue";
const component: WithInstallType<typeof Component> = withInstall(Component);
export default component;

View File

@ -0,0 +1,223 @@
<script lang="ts">
export default {
name: "LayBacktop",
};
</script>
<script lang="ts" setup>
import {
ref,
shallowRef,
withDefaults,
computed,
onMounted,
onBeforeUnmount,
} from "vue";
import { LayIcon } from "@layui/icons-vue";
import "./index.less";
export interface BackTopProps {
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<BackTopProps>(), {
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") {
// @ts-ignore
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;
}
}
// @ts-ignore
return 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);
}
};
};
const callback = throttle(handleScroll, 300);
onMounted(() => {
if (!props.target) return;
scrollTarget.value = getScrollTarget();
scrollTarget.value.addEventListener("scroll", callback);
});
onBeforeUnmount(() => {
scrollTarget.value?.removeEventListener("scroll", callback);
});
</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

View File

@ -0,0 +1,82 @@
.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: var(--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;
border-color: var(--global-neutral-color-3);
color: #666;
}
.layui-badge-dot-ripple > span {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: block;
border-radius: 50%;
box-sizing: border-box;
animation: layui-badge-dot-anim-ripple 1.2s ease-in-out infinite;
}
@keyframes layui-badge-dot-anim-ripple {
from {
transform: scale(0.8);
opacity: 0.6;
}
to {
transform: scale(2.4);
opacity: 0;
}
}
/** Badge 在与其他组件配合使用时的辅助样式 */
.layui-btn .layui-badge,
.layui-btn .layui-badge-dot {
margin-left: 5px;
}
.layui-nav .layui-badge,
.layui-nav .layui-badge-dot {
position: absolute;
top: 50%;
margin: -5px 6px 0;
}
.layui-nav .layui-badge {
margin-top: -10px;
}
.layui-tab-title .layui-badge,
.layui-tab-title .layui-badge-dot {
left: 5px;
top: -2px;
}

View File

@ -0,0 +1,5 @@
import { withInstall, WithInstallType } from "../../utils";
import Component from "./index.vue";
const component: WithInstallType<typeof Component> = withInstall(Component);
export default component;

View File

@ -0,0 +1,48 @@
<script lang="ts">
export default {
name: "LayBadge",
};
</script>
<script setup lang="ts">
import { computed, StyleValue } from "vue";
import "./index.less";
export interface BadgeProps {
type?: "dot" | "rim";
theme?: string;
color?: string;
ripple?: boolean;
}
const props = defineProps<BadgeProps>();
const classes = computed(() => {
return [
{
"layui-badge": !props.type,
"layui-badge-dot": props.type == "dot",
"layui-badge-rim": props.type == "rim",
"layui-badge-dot-ripple": props.ripple,
},
`layui-bg-${props.theme}`,
];
});
const styles = computed<StyleValue>(() => {
return [props.color ? `background-color: ${props.color}` : ""];
});
</script>
<template>
<span :class="classes" :style="styles">
<span
v-if="type === 'dot'"
:class="props.theme ? `layui-bg-${props.theme}` : ``"
:style="styles ?? `background-color: #ff5722;`"
>
</span>
<slot v-if="type != 'dot'"></slot>
</span>
</template>

View File

View File

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

View File

@ -0,0 +1,5 @@
import { withInstall, WithInstallType } from "../../utils";
import Component from "./index.vue";
const component: WithInstallType<typeof Component> = withInstall(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,29 @@
.layui-breadcrumb {
font-size: 0;
}
.layui-breadcrumb > * {
font-size: 14px;
}
.layui-breadcrumb a {
color: #999;
}
.layui-breadcrumb a:hover {
color: var(--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: var(--global-neutral-color-7);
}

View File

@ -0,0 +1,5 @@
import { withInstall, WithInstallType } from "../../utils";
import Component from "./index.vue";
const component: WithInstallType<typeof Component> = withInstall(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 BreadcrumbProps {
separator?: string;
}
const props = withDefaults(defineProps<BreadcrumbProps>(), {
separator: "/",
});
provide("separator", props.separator);
</script>
<template>
<span class="layui-breadcrumb">
<slot></slot>
</span>
</template>

View File

View File

@ -0,0 +1,5 @@
import { withInstall, WithInstallType } from "../../utils";
import Component from "./index.vue";
const component: WithInstallType<typeof Component> = withInstall(Component);
export default component;

View File

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

View File

@ -0,0 +1,120 @@
:root {
--button-primary-text-color: #fff;
--button-primary-background-color: var(--global-primary-color);
--button-primary-border-color: var(--global-primary-color);
--button-normal-text-color: #fff;
--button-normal-background-color: var(--global-normal-color);
--button-normal-border-color: var(--global-normal-color);
--button-warm-text-color: #fff;
--button-warm-background-color: var(--global-warm-color);
--button-warm-border-color: var(--global-warm-color);
--button-danger-text-color: #fff;
--button-danger-background-color: var(--global-danger-color);
--button-danger-border-color: var(--global-danger-color);
--button-border-radius: var(--global-border-radius);
--button-border-color: var(--global-neutral-color-6);
--button-background-color: 0 0;
--button-text-color: #666;
}
.layui-btn {
height: 38px;
line-height: 36px;
padding: 0 18px;
font-size: 14px;
text-align: center;
white-space: nowrap;
color: var(--button-text-color);
background: var(--button-background-color);
border-radius: var(--button-border-radius);
border-color: var(--button-border-color);
border-width: 1px;
border-style: solid;
cursor: pointer;
}
.layui-btn-primary {
color: var(--button-primary-text-color);
background-color: var(--button-primary-background-color);
border-color: var(--button-primary-border-color);
}
.layui-btn-normal {
color: var(--button-normal-text-color);
background-color: var(--button-normal-background-color);
border-color: var(--button-normal-border-color);
}
.layui-btn-warm {
color: var(--button-warm-text-color);
background-color: var(--button-warm-background-color);
border-color: var(--button-warm-border-color);
}
.layui-btn-danger {
color: var(--button-danger-text-color);
background-color: var(--button-danger-background-color);
border-color: var(--button-danger-border-color);
}
.layui-btn:hover {
opacity: 0.8;
filter: alpha(opacity=80);
}
.layui-btn:active {
opacity: 1;
filter: alpha(opacity=100);
}
.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-radius {
border-radius: 100px;
}
.layui-btn-disabled,
.layui-btn-disabled:active,
.layui-btn-disabled:hover {
border-color: #eee !important;
background-color: #fbfbfb !important;
color: #d2d2d2 !important;
cursor: not-allowed !important;
opacity: 1;
}
.layui-btn + .layui-btn {
margin-left: 10px;
}
.layui-btn .layui-icon {
padding: 0 2px;
vertical-align: middle\9;
vertical-align: bottom;
}

View File

@ -0,0 +1,5 @@
import { withInstall, WithInstallType } from "../../utils";
import Component from "./index.vue";
const component: WithInstallType<typeof Component> = withInstall(Component);
export default component;

View File

@ -0,0 +1,88 @@
<!-- done -->
<script lang="ts">
export default {
name: "LayButton",
};
</script>
<script setup lang="ts">
import "./index.less";
import { computed } from "vue";
import {
ButtonBorder,
ButtonEmits,
ButtonNativeType,
ButtonSize,
ButtonType,
} from "./interface";
export interface ButtonProps {
type?: ButtonType;
size?: ButtonSize;
prefixIcon?: string;
suffixIcon?: string;
loadingIcon?: string;
borderStyle?: string;
border?: ButtonBorder;
fluid?: boolean;
radius?: boolean;
loading?: boolean;
disabled?: boolean;
nativeType?: ButtonNativeType;
}
const props = withDefaults(defineProps<ButtonProps>(), {
loadingIcon: "layui-icon-loading-one",
borderStyle: "soild",
nativeType: "button",
loading: false,
radius: false,
fluid: false,
});
const emits = defineEmits(ButtonEmits);
const onClick = (event: MouseEvent) => {
if (!props.disabled) {
emits("click", event);
}
};
const styles = computed(() => {
return {
border: `1px ${props.borderStyle}`,
};
});
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"
:style="styles"
:type="nativeType"
@click="onClick"
>
<i v-if="prefixIcon" :class="`layui-icon ${prefixIcon}`"></i>
<i
v-if="loading"
:class="loadingIcon"
class="layui-icon 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,8 @@
export type ButtonType = "primary" | "normal" | "warm" | "danger";
export type ButtonSize = "lg" | "md" | "sm" | "xs";
export type ButtonBorder = "green" | "blue" | "orange" | "red" | "black";
export type ButtonNativeType = "button" | "submit" | "reset";
export const ButtonEmits = {
click: (evt: MouseEvent) => evt instanceof MouseEvent,
};

View File

@ -0,0 +1,12 @@
.layui-btn-container {
font-size: 0;
}
.layui-btn-container .layui-btn {
margin-right: 10px;
margin-bottom: 10px;
}
.layui-btn-container .layui-btn + .layui-btn {
margin-left: 0;
}

View File

@ -0,0 +1,5 @@
import { withInstall, WithInstallType } from "../../utils";
import Component from "./index.vue";
const component: WithInstallType<typeof Component> = withInstall(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,42 @@
:root {
--button-primary-color: var(--global-primary-color);
--button-border-radius: var(--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-radius: 0;
}
.layui-btn-group .layui-btn:not(:last-child) {
border-right: none !important;
}
.layui-btn-group .layui-btn.layui-btn-primary:not(:first-child),
.layui-btn-group .layui-btn.layui-btn-normal:not(:first-child),
.layui-btn-group .layui-btn.layui-btn-warm:not(:first-child),
.layui-btn-group .layui-btn.layui-btn-danger:not(:first-child) {
border-left: 1px solid rgba(255, 255, 255, .5)
}
.layui-btn-group .layui-btn:first-child {
border-radius: var(--button-border-radius) 0 0 var(--button-border-radius);
}
.layui-btn-group .layui-btn:last-child {
border-radius: 0 var(--button-border-radius) var(--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,5 @@
import { withInstall, WithInstallType } from "../../utils";
import Component from "./index.vue";
const component: WithInstallType<typeof Component> = withInstall(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,46 @@
:root {
--card-border-radius: var(--global-border-radius);
}
.layui-card {
margin-bottom: 15px;
background-color: #ffffff;
border-radius: var(--card-border-radius);
}
.layui-card .layui-card-header {
height: 42px;
line-height: 42px;
padding: 0 15px;
border-bottom: 1px solid #f6f6f6;
font-size: 14px;
}
.layui-card .layui-card-footer {
height: 42px;
line-height: 42px;
padding: 0 15px;
border-top: 1px solid #f6f6f6;
font-size: 14px;
}
.layui-card .layui-card-header .layui-card-header-extra {
float: right;
}
.layui-card .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,5 @@
import { withInstall, WithInstallType } from "../../utils";
import Component from "./index.vue";
const component: WithInstallType<typeof Component> = withInstall(Component);
export default component;

View File

@ -0,0 +1,49 @@
<script lang="ts">
export default {
name: "LayCard",
};
</script>
<script setup lang="ts">
import "./index.less";
import { computed, useSlots } from "vue";
import { CardShadow } from "./interface";
export interface CardProps {
title?: string;
shadow?: CardShadow;
}
const props = withDefaults(defineProps<CardProps>(), {
shadow: "always",
});
const slots = useSlots();
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="slots.title || title || slots.extra">
<span class="layui-card-header-title">
<slot name="title">{{ title }}</slot>
</span>
<span class="layui-card-header-extra" v-if="slots.extra">
<slot name="extra"></slot>
</span>
</div>
<div class="layui-card-body">
<slot name="body" v-if="slots.body"></slot>
<slot v-else></slot>
</div>
<div class="layui-card-footer" v-if="slots.footer">
<slot name="footer"></slot>
</div>
</div>
</template>

View File

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

View File

@ -0,0 +1,276 @@
.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: var(--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;
overflow: hidden;
visibility: hidden;
}
.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: var(--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;
transform: translateY(-50%);
}
.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,5 @@
import { withInstall, WithInstallType } from "../../utils";
import Component from "./index.vue";
const component: WithInstallType<typeof Component> = withInstall(Component);
export default component;

View File

@ -0,0 +1,197 @@
<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,
} from "vue";
import CarouselItem from "../carouselItem/index.vue";
export interface CarouselProps {
width?: string;
height?: string;
modelValue: string;
autoplay?: boolean;
arrow?: "always" | "hover" | "none";
anim?: "default" | "updown" | "fade";
indicator?: "inside" | "outside" | "none";
pauseOnHover?: boolean;
interval?: number;
}
const props = withDefaults(defineProps<CarouselProps>(), {
width: "100%",
height: "280px",
anim: "default",
autoplay: true,
arrow: "hover",
interval: 3000,
indicator: "inside",
pauseOnHover: true,
});
const slot = useSlots() as any;
const slots = slot.default && (slot.default() as any[]);
const active = computed({
get() {
return props.modelValue;
},
set(val) {
emit("update:modelValue", val);
},
});
const anim = computed(() => props.anim);
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 (nodes: VNode[]) {
const showNodes = nodes?.filter((item: VNode) => {
return item.children != "v-if";
});
showNodes?.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,
() => {
childrens.value = [];
setItemInstanceBySlot((slot.default && slot.default()) as VNode[]);
},
{ immediate: true, deep: true }
);
const sub = () => {
for (var i = 0; i < childrens.value.length; i++) {
if (childrens.value[i].props?.id === active.value) {
if (i === 0) {
active.value = childrens.value[slots.length - 1].props?.id;
} else {
active.value = childrens.value[i - 1].props?.id;
}
break;
}
}
};
const add = () => {
for (var i = 0; i < childrens.value.length; i++) {
if (childrens.value[i].props?.id === active.value) {
if (i === childrens.value.length - 1) {
active.value = childrens.value[0].props?.id;
} else {
active.value = childrens.value[i + 1].props?.id;
}
break;
}
}
};
const autoplay = () => {
for (var i = 0; i < childrens.value.length; i++) {
if (childrens.value[i].props?.id === active.value) {
if (i === childrens.value.length - 1) {
active.value = childrens.value[0].props?.id;
} else {
active.value = childrens.value[i + 1].props?.id;
}
break;
}
}
};
let intervalTimer = 0;
const cleanIntervalTimer = () => {
if (intervalTimer) {
window.clearInterval(intervalTimer);
intervalTimer = 0;
}
};
const handleMouseEnter = () => {
if (props.autoplay && props.pauseOnHover) {
cleanIntervalTimer();
}
};
const handleMouseLeave = () => {
if (props.autoplay && props.pauseOnHover) {
intervalTimer = window.setInterval(autoplay, props.interval);
}
};
watch(
() => props.autoplay,
() => {
if (props.autoplay) {
intervalTimer = window.setInterval(autoplay, props.interval);
}
},
{ immediate: true }
);
provide("active", active);
provide("slotsChange", slotsChange);
provide("anim", anim);
</script>
<template>
<div
class="layui-carousel"
:lay-anim="anim"
:lay-indicator="indicator"
:lay-arrow="arrow"
:style="{ width: width, height: height }"
@mouseenter="handleMouseEnter"
@mouseleave="handleMouseLeave"
>
<div carousel-item>
<slot></slot>
</div>
<div class="layui-carousel-ind">
<ul>
<li
v-for="(ss, index) in childrens"
:key="index"
: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,5 @@
import { withInstall, WithInstallType } from "../../utils";
import Component from "./index.vue";
const component: WithInstallType<typeof Component> = withInstall(Component);
export default component;

View File

@ -0,0 +1,110 @@
<script lang="ts">
export default {
name: "LayCarouselItem",
};
</script>
<script setup lang="ts">
import {
inject,
Ref,
computed,
ref,
ComputedRef,
WritableComputedRef,
} from "vue";
export interface CarouselItemProps {
id: string;
}
const props = defineProps<CarouselItemProps>();
const active = inject("active") as WritableComputedRef<string>;
const slotsChange: Ref<boolean> = inject("slotsChange") as Ref<boolean>;
slotsChange.value = !slotsChange.value;
const anim = inject("anim") as ComputedRef<string>;
const item = ref();
const getStyle = computed<any>(() => {
if (item.value) {
let allChild = item.value.parentNode.children;
let allChildNum = allChild.length;
let activeIndex = 0;
let currentIndex = 0;
for (let index = 0; index < allChild.length; index++) {
const element = allChild[index];
if (element.getAttribute("data-id") === active.value) {
activeIndex = index;
}
if (element.getAttribute("data-id") === props.id) {
currentIndex = index;
}
}
let prevIndex = activeIndex > 0 ? activeIndex - 1 : allChildNum - 1;
let nextIndex = activeIndex + 1 < allChildNum ? activeIndex + 1 : 0;
let animation = anim.value;
if (activeIndex === currentIndex) {
if (animation === "updown") {
return {
transform: "translateY(0)",
visibility: "inherit",
};
} else if (animation.includes("fade")) {
return {
opacity: 1,
visibility: "inherit",
};
} else {
return {
transform: "translateX(0)",
visibility: "inherit",
};
}
}
if (prevIndex === currentIndex) {
if (animation === "updown") {
return {
transform: "translateY(-100%)",
};
} else if (animation.includes("fade")) {
return {
opacity: 0,
};
} else {
return {
transform: "translateX(-100%)",
};
}
}
if (nextIndex === currentIndex) {
if (animation === "updown") {
return {
transform: "translateY(100%)",
};
} else if (animation.includes("fade")) {
return {
opacity: 0,
};
} else {
return {
transform: "translateX(100%)",
};
}
}
return {
display: "none",
};
}
});
</script>
<template>
<li ref="item" :style="getStyle" :data-id="id">
<slot></slot>
</li>
</template>

View File

@ -0,0 +1,101 @@
@import "../input/index.less";
@lg: 44px;
@md: 38px;
@sm: 32px;
@xs: 26px;
@lg-width: 260px;
@md-width: 220px;
@sm-width: 180px;
@xs-width: 140px;
.set-size(@size, @width) {
& {
height: @size;
width: @width;
.layui-input {
height: @size;
line-height: @size;
}
}
}
.layui-cascader {
display: inline-block;
&[size="lg"] {
.set-size(@lg,@lg-width);
}
&[size="md"] {
.set-size(@md,@md-width);
}
&[size="sm"] {
.set-size(@sm,@sm-width);
}
&[size="xs"] {
.set-size(@xs,@xs-width);
}
}
.layui-cascader .layui-input-suffix{
padding-right: 10px;
}
.layui-cascader .layui-icon-triangle-d {
transition: all 0.3s ease-in-out;
transform: rotate(0);
color:var(--global-neutral-color-8);
}
.layui-cascader-opend .layui-icon-triangle-d {
transform: rotate(180deg);
}
.layui-cascader .layui-cascader-panel {
box-sizing: border-box;
border-radius: 2px;
line-height: 26px;
color: #000c;
font-size: 14px;
white-space: nowrap;
display: inline-flex;
}
.layui-cascader-menu {
display: inline-block;
border-right: 1px solid var(--global-neutral-color-3);
}
.layui-cascader-menu:last-child {
border-right: none;
}
.layui-cascader-menu-item {
min-width: 130px;
padding: 5px 15px;
box-sizing: border-box;
transition: all 0.1s ease-in-out;
display: flex;
justify-content: space-between;
align-items: center;
padding-right: 9px;
min-height: 35px;
}
.layui-cascader-menu-item:hover {
background-color: var(--global-checked-color);
color: white;
}
.layui-cascader-selected {
background-color: var(--global-checked-color);
color: white;
}
.layui-cascader-menu-item .layui-icon-right{
margin-left: 10px;
}
.layui-cascader-disabled,
.layui-cascader-disabled * {
cursor: not-allowed !important;
}

View File

@ -0,0 +1,5 @@
import { withInstall, WithInstallType } from "../../utils";
import Component from "./index.vue";
const component: WithInstallType<typeof Component> = withInstall(Component);
export default component;

View File

@ -0,0 +1,311 @@
<template>
<div
:size="size"
:class="[
'layui-cascader',
{
'layui-cascader-opend': openState,
'layui-cascader-disabled': disabled,
},
]"
>
<lay-dropdown
ref="dropdownRef"
:trigger="trigger"
:autoFitMinWidth="false"
:updateAtScroll="true"
:disabled="disabled"
:contentClass="contentClass"
:contentStyle="contentStyle"
@show="openState = true"
@hide="openState = false"
>
<lay-input
v-if="!slots.default"
v-model="displayValue"
suffix-icon="layui-icon-triangle-d"
:placeholder="placeholder"
:allow-clear="allowClear"
:disabled="disabled"
:readonly="true"
:size="size"
@clear="onClear"
></lay-input>
<slot v-else></slot>
<template #content>
<div class="layui-cascader-panel">
<template v-for="(itemCol, index) in treeData">
<lay-scroll
height="180px"
class="layui-cascader-menu"
:key="'cascader-menu' + index"
v-if="itemCol.data.length"
>
<div
class="layui-cascader-menu-item"
v-for="(item, i) in itemCol.data"
:key="index + i"
@click="selectBar(item, i, index)"
:class="[
{
'layui-cascader-selected': itemCol.selectIndex === i,
},
]"
>
<slot
:name="item.slot"
v-if="item.slot && slots[item.slot]"
></slot>
<template v-else>{{ item.label }}</template>
<i
class="layui-icon layui-icon-right"
v-if="item.children && item.children.length"
></i>
</div>
</lay-scroll>
</template>
</div>
</template>
</lay-dropdown>
</div>
</template>
<script lang="ts">
export default {
name: "LayCascader",
};
</script>
<script setup lang="ts">
import "./index.less";
import LayInput from "../input/index.vue";
import LayScroll from "../scroll/index.vue";
import LayDropdown from "../dropdown/index.vue";
import { ref, onMounted, watch, useSlots, StyleValue } from "vue";
import { CascaderSize } from "./interface";
export type DropdownTrigger = "click" | "hover" | "focus" | "contextMenu";
export interface CascaderProps {
options?: Array<any> | null;
modelValue?: string;
decollator?: string;
placeholder?: string;
onlyLastLevel?: boolean;
disabled?: boolean;
replaceFields?: { label: string; value: string; children: string };
allowClear?: boolean;
size?: CascaderSize;
trigger?: DropdownTrigger | DropdownTrigger[];
contentClass?: string | Array<string | object> | object;
contentStyle?: StyleValue;
}
const props = withDefaults(defineProps<CascaderProps>(), {
options: null,
modelValue: "",
decollator: "/",
placeholder: "",
onlyLastLevel: false,
allowClear: false,
disabled: false,
size: "md",
trigger: "click",
replaceFields: () => {
return {
label: "label",
value: "value",
children: "children",
};
},
});
const emit = defineEmits(["update:modelValue", "change", "clear"]);
onMounted(() => {
initTreeData();
});
watch(
() => props.options,
() => {
initTreeData();
}
);
watch(
() => props.modelValue,
() => {
if (props.modelValue === null || props.modelValue === "") {
onClear();
}
}
);
const treeData = ref<any>([]);
const initTreeData = () => {
let treeLvNum = getMaxFloor(props.options);
for (let index = 0; index < treeLvNum; index++) {
if (index == 0) {
treeData.value[0] = {
selectIndex: null,
data: findData(props.options, 1),
};
} else {
treeData.value[index] = {
selectIndex: null,
data: [],
};
}
}
if (props.modelValue) {
try {
let valueData = props.modelValue.split(props.decollator);
let data: any[] = [];
for (let index = 0; index < treeData.value.length; index++) {
const element = treeData.value[index];
const nowValue = valueData[index];
for (let i = 0; i < element.length; i++) {
const ele = element[i];
if (nowValue === ele.value) {
data.push(ele);
element.selectIndex = i;
}
}
}
displayValue.value = data
.map((e) => {
return e.label;
})
.join(` ${props.decollator} `);
} catch (error) {
console.error(error);
}
}
};
function getMaxFloor(treeData: any) {
//let floor = 0;
let max = 0;
function each(data: any, floor: any) {
data.forEach((e: any) => {
//e.layFloor = floor;
if (floor > max) {
max = floor;
}
if (
e[props.replaceFields.children] &&
e[props.replaceFields.children].length > 0
) {
each(e[props.replaceFields.children], floor + 1);
}
});
}
each(treeData, 1);
return max;
}
function findData(orginData: any, level: number) {
let data: any[] = [];
for (let i = 0; i < orginData.length; i++) {
const element = orginData[i];
if (level === 1) {
data.push({
value: element[props.replaceFields.value],
label: element[props.replaceFields.label],
slot: element.slot || false,
children: element[props.replaceFields.children] ?? false,
orginData: element,
});
}
if (
level !== 1 &&
element[props.replaceFields.children] &&
element[props.replaceFields.children].length > 0
) {
findData(element[props.replaceFields.children], level - 1);
}
}
return data;
}
const dataContainer = ref<any>([]);
const selectBar = (item: any, selectIndex: number, parentIndex: number) => {
treeData.value[parentIndex].selectIndex = selectIndex;
if (item.children && item.children.length > 0) {
treeData.value[parentIndex + 1].selectIndex = null;
treeData.value[parentIndex + 1].data = findData(item.children, 1);
}
//
let nextIndex = parentIndex + 2;
for (let index = nextIndex; index < treeData.value.length; index++) {
treeData.value[index].selectIndex = null;
treeData.value[index].data = [];
}
if (!item.children || item.children.length === 0) {
//
let data: any[] = [];
function extractData(orginData: any, dataContainer: any, index: number) {
const element = orginData[index].data;
const selectIndex = orginData[index].selectIndex;
const selectData = element[selectIndex];
dataContainer.push(selectData);
if (selectData.children && selectData.children.length > 0) {
extractData(orginData, dataContainer, index + 1);
}
}
extractData(treeData.value, data, 0);
let fullLable = data
.map((e: any) => {
return e.label;
})
.join(` ${props.decollator} `);
if (!props.onlyLastLevel) {
displayValue.value = fullLable;
} else {
let _data = data.map((e: any) => {
return e.label;
});
displayValue.value = _data[_data.length - 1];
}
let value = data
.map((e: any) => {
return e.value;
})
.join(props.decollator);
emit("update:modelValue", value);
let evt = {
display: displayValue.value,
value: value,
label: fullLable,
currentClick: JSON.parse(JSON.stringify(item.orginData)),
};
emit("change", evt);
if (dropdownRef.value)
// @ts-ignore
dropdownRef.value.hide();
}
};
const displayValue = ref<string | number>("");
const slots = useSlots();
const dropdownRef = ref();
//
const onClear = () => {
displayValue.value = "";
let arr = JSON.parse(JSON.stringify(treeData.value));
for (let index = 0; index < arr.length; index++) {
arr[index].selectIndex = null;
if (index === 0) {
continue;
}
arr[index].data = [];
}
treeData.value = arr;
emit("update:modelValue", null);
};
const openState = ref(false);
</script>

View File

@ -0,0 +1 @@
export type CascaderSize = "lg" | "md" | "sm" | "xs";

View File

@ -0,0 +1,187 @@
@checkbox-lg: 18px;
@checkbox-md: 16px;
@checkbox-sm: 14px;
@checkbox-xs: 12px;
@checkbox-lg-font-size: 16px;
@checkbox-md-font-size: 14px;
@checkbox-sm-font-size: 12px;
@checkbox-xs-font-size: 10px;
.set-size(@size, @font-size) {
& {
height: @size;
line-height: @size;
.layui-form-checkbox[lay-skin="primary"] {
.layui-icon {
width: @size;
height: @size;
font-size: @font-size;
}
.layui-checkbox-label {
height: @size;
line-height: @size;
font-size: @font-size;
}
}
}
}
.layui-checkbox{
&[size="lg"] {
.set-size(@checkbox-lg, @checkbox-lg-font-size);
}
&[size="md"] {
.set-size(@checkbox-md, @checkbox-md-font-size);
}
&[size="sm"] {
.set-size(@checkbox-sm, @checkbox-sm-font-size);
}
&[size="xs"] {
.set-size(@checkbox-xs, @checkbox-xs-font-size);
}
}
.layui-checkbox input[type="checkbox"] {
display: none;
}
.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: var(--global-neutral-color-6);
color: #fff;
overflow: hidden;
}
.layui-form-checkbox:hover span {
background-color: var(--global-neutral-color-8);
}
.layui-form-checkbox i {
top: 0;
right: 0;
width: 29px;
height: 28px;
position: absolute;
border: 1px solid var(--global-neutral-color-6);
border-radius: 0 2px 2px 0;
color: #fff;
font-size: 20px;
text-align: center;
}
.layui-form-checkbox:hover i {
border-color: var(--global-neutral-color-8);
color: var(--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 var(--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: var(--global-checked-color);
color: #fff;
}
.layui-form-checked,
.layui-form-checked:hover {
border-color: var(--global-checked-color);
}
.layui-form-checked i,
.layui-form-checked:hover i {
color: var(--global-checked-color);
}
.layui-form-checked span,
.layui-form-checked:hover span {
background-color: var(--global-checked-color);
}
.layui-form-checked[lay-skin="primary"] i {
border-color: var(--global-checked-color);
background-color: var(--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: var(--global-neutral-color-8) !important;
}
.layui-checkbox-disabled[lay-skin="primary"]:hover i {
border-color: var(--global-neutral-color-6);
}
.layui-checkbox-disabled,
.layui-checkbox-disabled i {
border-color: var(--global-neutral-color-3) !important;
}
.layui-checkbox-disabled span {
background-color: var(--global-neutral-color-3) !important;
}
.layui-checkbox-disabled em {
color: var(--global-neutral-color-6) !important;
}
.layui-checkbox-disabled:hover i {
color: #fff !important;
}
.layui-checkbox-disabled .layui-icon-ok,.layui-checkbox-disabled .layui-icon-subtraction{
background-color: var(--global-neutral-color-3) !important;
border-color: var(--global-neutral-color-3) !important;
}

View File

@ -0,0 +1,5 @@
import { withInstall, WithInstallType } from "../../utils";
import Component from "./index.vue";
const component: WithInstallType<typeof Component> = withInstall(Component);
export default component;

View File

@ -0,0 +1,145 @@
<script lang="ts">
export default {
name: "LayCheckbox",
};
</script>
<script setup lang="ts">
import { LayIcon } from "@layui/icons-vue";
import { computed, inject, useSlots } from "vue";
import "./index.less";
import { CheckboxSize } from "./interface";
export interface CheckboxProps {
name?: string;
skin?: string;
label?: string;
value: string | number | object;
modelValue?: boolean | Array<string | number | object>;
isIndeterminate?: boolean;
size?: CheckboxSize;
disabled?: boolean;
}
const props = withDefaults(defineProps<CheckboxProps>(), {
isIndeterminate: false,
modelValue: false,
disabled: false,
label: "",
size: "md",
});
const checkboxGroup: any = inject("checkboxGroup", {});
const isGroup = computed(() => {
return (
checkboxGroup != undefined && checkboxGroup?.name === "LayCheckboxGroup"
);
});
const emit = defineEmits(["update:modelValue", "change"]);
const slots = useSlots();
const isChecked = computed({
get() {
if (isGroup.value) {
return checkboxGroup.modelValue.value.includes(props.value);
} else {
if (Array.isArray(props.modelValue)) {
return props.modelValue.includes(props.value);
} 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.value), 1);
} else {
groupModelValue.push(props.value);
}
checkboxGroup.modelValue.value = groupModelValue;
};
const setArrayModelValue = function (checked: any) {
let arr = [...arrayModelValue.value];
if (!checked) {
arr.splice(arr.indexOf(props.value), 1);
} else {
arr.push(props.value);
}
emit("change", arr);
emit("update:modelValue", arr);
};
const handleClick = function () {
if (!isDisabled.value) {
isChecked.value = !isChecked.value;
}
};
const isDisabled = computed(() => {
if (props.disabled) {
return true;
}
if (
checkboxGroup.hasOwnProperty("disabled") &&
checkboxGroup.disabled.value
) {
return true;
}
return false;
});
defineExpose({ toggle: handleClick });
</script>
<template>
<span @click.stop="handleClick" class="layui-checkbox" :size="size">
<input type="checkbox" :name="name" :value="value" />
<div
class="layui-form-checkbox"
:class="{
'layui-form-checked': isChecked,
'layui-checkbox-disabled layui-disabled': isDisabled,
}"
:lay-skin="skin"
>
<span class="layui-checkbox-label" v-if="slots.default || label">
<slot>{{ label }}</slot>
</span>
<lay-icon
:type="
props.isIndeterminate && isChecked
? 'layui-icon-subtraction'
: isChecked
? 'layui-icon-ok'
: ''
"
></lay-icon>
</div>
</span>
</template>

View File

@ -0,0 +1 @@
export type CheckboxSize = "lg" | "md" | "sm" | "xs";

View File

@ -0,0 +1,5 @@
import { withInstall, WithInstallType } from "../../utils";
import Component from "./index.vue";
const component: WithInstallType<typeof Component> = withInstall(Component);
export default component;

View File

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

View File

View File

@ -0,0 +1,5 @@
import { withInstall, WithInstallType } from "../../utils";
import Component from "./index.vue";
const component: WithInstallType<typeof Component> = withInstall(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 ColProps {
md?: string | number;
xs?: string | number;
sm?: string | number;
lg?: string | number;
mdOffset?: string | number;
xsOffset?: string | number;
smOffset?: string | number;
lgOffset?: string | number;
}
const props = defineProps<ColProps>();
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,40 @@
.layui-collapse {
border-width: 1px;
border-style: solid;
border-radius: 2px;
}
.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: var(--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 {
left: 15px;
top: 0;
font-size: 14px;
position: absolute;
}

View File

@ -0,0 +1,5 @@
import { withInstall, WithInstallType } from "../../utils";
import Component from "./index.vue";
const component: WithInstallType<typeof Component> = withInstall(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 CollapseProps {
accordion?: boolean;
modelValue?: number | string | number[] | string[];
collapseTransition?: boolean;
}
const props = withDefaults(defineProps<CollapseProps>(), {
modelValue: () => [],
accordion: false,
collapseTransition: true,
});
watch(
() => props.modelValue,
(val) => {
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

View File

@ -0,0 +1,5 @@
import { withInstall, WithInstallType } from "../../utils";
import Component from "./index.vue";
const component: WithInstallType<typeof Component> = withInstall(Component);
export default component;

View File

@ -0,0 +1,77 @@
<script lang="ts">
export default {
name: "LayCollapseItem",
};
</script>
<script setup lang="ts">
import LayTransition from "../transition/index.vue";
import { withDefaults, inject, computed, ref } from "vue";
export interface CollapseItemProps {
id: number | string;
title: string;
disabled?: boolean;
}
const props = withDefaults(defineProps<CollapseItemProps>(), {
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 layui-icon-right"
:style="{
transform: isShow ? 'rotate(90deg)' : 'none',
transition: collapseTransition ? 'all 0.2s ease 0s' : '',
}"
></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

View File

@ -0,0 +1,43 @@
<template>
<svg
t="1651169382813"
class="icon"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="2529"
width="27"
height="27"
>
<path
d="M769.3 409.1c-4.3-16.2-14.7-29.7-29.2-38l-51.3-29.6-10 17.3c-3.7 6.4-10.4 10-17.3 10-3.4 0-6.8-0.9-10-2.7l68.6 39.6c5.2 3 9 7.9 10.5 13.7 1.6 5.8 0.8 11.9-2.3 17.2l-6.6 11.5c-6.2 10.8-20.1 14.5-30.9 8.3L441 312.2c-5.2-3-9-7.9-10.5-13.7-1.6-5.8-0.8-11.9 2.3-17.2l6.6-11.5c3-5.2 7.9-9 13.7-10.5 1.9-0.5 3.9-0.8 5.9-0.8 3.9 0 7.8 1 11.3 3l69.3 40c-9.6-5.5-12.8-17.8-7.3-27.3l10-17.3-52-30c-14.5-8.4-31.4-10.6-47.5-6.3-16.2 4.3-29.7 14.7-38 29.2l-6.6 11.5c-8.4 14.5-10.6 31.4-6.3 47.5 4.3 16.2 14.7 29.7 29.2 38l52 30 10-17.3c5.5-9.6 17.8-12.8 27.3-7.3 9.6 5.5 12.8 17.8 7.3 27.3l-10 17.3 77.2 44.6 10-17.3c5.5-9.6 17.8-12.8 27.3-7.3 9.6 5.5 12.8 17.8 7.3 27.3l-10 17.3 51.3 29.6c9.8 5.7 20.6 8.4 31.2 8.4 21.7 0 42.7-11.2 54.3-31.3l6.6-11.5c8.5-14.5 10.7-31.4 6.4-47.5z"
fill="#515151"
p-id="2530"
></path>
<path
d="M644.2 338.8l10-17.3-77.2-44.6-10 17.3c-5.5 9.6-17.8 12.8-27.3 7.3l111.8 64.6c-9.6-5.5-12.9-17.8-7.3-27.3zM539.6 301.5zM651.5 366.1z"
fill="#515151"
p-id="2531"
></path>
<path
d="M624.1 195.2c12.3-21.3 39.6-28.6 60.9-16.3 10.3 6 17.7 15.6 20.8 27.1s1.5 23.5-4.5 33.8l-47.2 81.7 34.6 20 47.2-81.7c11.3-19.6 14.3-42.4 8.5-64.2-5.8-21.8-19.8-40.1-39.4-51.4-40.4-23.3-92.2-9.4-115.5 31l-47.2 81.7 34.6 20 47.2-81.7z"
fill="#515151"
p-id="2532"
></path>
<path
d="M644.2 338.8c-5.5 9.6-2.2 21.8 7.3 27.3 3.1 1.8 6.6 2.7 10 2.7 6.9 0 13.6-3.6 17.3-10l10-17.3-34.6-20-10 17.3zM539.6 301.5c9.6 5.5 21.8 2.2 27.3-7.3l10-17.3-34.6-20-10 17.3c-5.5 9.6-2.2 21.8 7.3 27.3z"
fill="#515151"
p-id="2533"
></path>
<path
d="M395.4 769.6c-2.6 4.4-6.5 7.6-11 9-2.3 0.7-6.9 1.5-11.3-1-9.6-5.5-21.8-2.2-27.3 7.3l-23.1 40c-1.7 2.9-4.2 5-7.1 5.8-1.7 0.5-4.1 0.8-6.5-0.5-2.4-1.4-3.3-3.7-3.7-5.4-0.7-2.9-0.1-6.2 1.5-9l22.9-39.7c5.6-9.7 2.3-22.2-7.4-27.8-7.3-4.2-9.2-14.7-4.2-23.3l189.5-328.2-34.6-20L283.6 705c-12.4 21.4-10.1 47.2 3.7 64.8l-15 26c-6.7 11.6-8.8 25.1-5.9 37.9 3 13.4 11.1 24.4 22.7 31.1 7.3 4.2 15.4 6.4 23.7 6.4 4.9 0 9.8-0.7 14.6-2.2 12.6-3.9 23.2-12.4 29.9-24.1l15-26c7.8 1.1 15.9 0.4 23.8-2 14.2-4.4 26.2-14.1 33.8-27.3l189.5-328.2-34.6-20-189.4 328.2z"
fill="#515151"
p-id="2534"
></path>
<path
d="M622.2 416.8c-9.6-5.5-21.8-2.2-27.3 7.3l-10 17.3 34.6 20 10-17.3c5.6-9.6 2.3-21.8-7.3-27.3zM517.7 379.5c5.5-9.6 2.2-21.8-7.3-27.3-9.6-5.5-21.8-2.2-27.3 7.3l-10 17.3 34.6 20 10-17.3z"
fill="#515151"
p-id="2535"
></path>
</svg>
</template>

View File

@ -0,0 +1,389 @@
@import "../dropdown/index.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: var(--global-border-radius);
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAeCAYAAAA7MK6iAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAWElEQVRIiWM8fubkfwYygKWJOSM5+mCAhRLNoxaPWjxq8ajFoxbTyeL/DAfJ0Xjs3Cl7Siwmu4Yht1aDgZEYx6MWj1o8avGoxaMWD3qLya5X//4nqx6HAQC7RBGFzolqTAAAAABJRU5ErkJggg==");
background-size: 10px 10px;
}
.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 var(--global-neutral-color-3);
padding: 5px;
border-radius: var(--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: var(--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: var(--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 var(--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;
}
.layui-colorpicker-disabled {
opacity: 0.6;
}
.layui-colorpicker-disabled,
.layui-colorpicker-disabled * {
cursor: not-allowed !important;
}

View File

@ -0,0 +1,5 @@
import { withInstall, WithInstallType } from "../../utils";
import Component from "./index.vue";
const component: WithInstallType<typeof Component> = withInstall(Component);
export default component;

View File

@ -0,0 +1,512 @@
<script lang="ts">
export default {
name: "LayColorPicker",
};
</script>
<script lang="ts" setup>
import "./index.less";
import LayDropdown from "../dropdown/index.vue";
import EyeDropper from "./EyeDropper.vue";
import { ref, computed, watch, onMounted, StyleValue } from "vue";
import { useEyeDropper } from "@vueuse/core";
export interface ColorPicker {
modelValue?: any;
preset?: any;
disabled?: boolean;
eyeDropper?: boolean;
contentClass?: string | Array<string | object> | object;
contentStyle?: StyleValue;
}
const emit = defineEmits(["update:modelValue"]);
const props = withDefaults(defineProps<ColorPicker>(), {
modelValue: { r: 255, g: 255, b: 255, a: 1 },
preset: ["#009688", "#1e9fff", "#ffb800", "#ff5722", "#5fb878"],
disabled: false,
});
const saturationValue = ref<null | HTMLElement>(null);
const hueSlider = ref<null | HTMLElement>(null);
const alphaSlider = ref<null | HTMLElement>(null);
const { isSupported, open, sRGBHex } = useEyeDropper();
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);
const openEyeDropper = function () {
if (isSupported) {
open();
} else {
console.warn("LayColorPicker: Eye dropper not supported by your browser!");
}
};
onMounted(() => {
let { r, g, b, a } = parseColor(props.modelValue);
red.value = r;
green.value = g;
blue.value = b;
alpha.value = a;
});
watch(sRGBHex, (sRGBHex) => {
let { r, g, b, a } = hex2rgba(sRGBHex);
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;
// hsvrgb
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);
// hsvrgb
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);
}
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
:disabled="disabled"
:contentClass="contentClass"
:contentStyle="contentStyle"
updateAtScroll
>
<div
class="layui-unselect layui-colorpicker"
:class="[{ 'layui-colorpicker-disabled': disabled }]"
>
<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
v-if="eyeDropper"
@click="openEyeDropper"
style="margin-left: 5px"
>
<EyeDropper />
</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,12 @@
.layui-container {
position: relative;
margin: 0 auto;
padding: 0 15px;
box-sizing: border-box;
}
.layui-fluid {
position: relative;
margin: 0 auto;
padding: 0 15px;
}

View File

@ -0,0 +1,5 @@
import { withInstall, WithInstallType } from "../../utils";
import Component from "./index.vue";
const component: WithInstallType<typeof Component> = withInstall(Component);
export default component;

View File

@ -0,0 +1,29 @@
<script lang="ts">
export default {
name: "LayContainer",
};
</script>
<script setup lang="ts">
import "./index.less";
import { computed } from "vue";
import { BooleanOrString } from "../../types";
export interface ContainerProps {
fluid?: BooleanOrString;
}
const props = withDefaults(defineProps<ContainerProps>(), {
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,5 @@
import { withInstall, WithInstallType } from "../../utils";
import Component from "./index.vue";
const component: WithInstallType<typeof Component> = withInstall(Component);
export default component;

View File

@ -0,0 +1,104 @@
<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 CountUpProps {
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<CountUpProps>(), {
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
* */
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.useGrouping && 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"
? // @ts-ignore
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>{{ displayValue }}</span>
<slot name="suffix"></slot>
</template>

View File

@ -0,0 +1,148 @@
<template>
<div class="layui-laydate">
<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="datePicker.showPanel.value = 'year'"
>{{ datePicker.currentYear.value }} {{ t("datePicker.year") }}</span
>
<span @click="datePicker.showPanel.value = 'month'">
{{ MONTH_NAME[datePicker.currentMonth.value] }}
</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>
<DateContent
:date-list="dateList"
v-model="Day"
@simple="footOnOk"
@update:model-value="ChildUpdateModelValue"
></DateContent>
<PanelFoot @ok="footOnOk" @now="footOnNow" @clear="footOnClear">
<span
v-if="datePicker.type === 'datetime'"
@click="datePicker.showPanel.value = 'time'"
class="laydate-btns-time"
>{{ t("datePicker.selectTime") }}</span
>
</PanelFoot>
</div>
</div>
</template>
<script lang="ts">
export default {
name: "DatePanel",
};
</script>
<script lang="ts" setup>
import { computed, inject, ref, watch } from "vue";
import { provideType } from "../interface";
import { setDateList } from "../day";
import PanelFoot from "./PanelFoot.vue";
import DateContent from "./components/DateContent.vue";
import dayjs from "dayjs";
import { useI18n } from "../../../language";
export interface TimePanelProps {
modelValue: number;
}
const { t } = useI18n();
const props = withDefaults(defineProps<TimePanelProps>(), {});
const emits = defineEmits(["update:modelValue", "ok"]);
const Day = ref(props.modelValue);
const datePicker: provideType = inject("datePicker") as provideType;
const dateList = ref<any>([]);
const MONTH_NAME = computed(() => [
t("datePicker.january"),
t("datePicker.february"),
t("datePicker.march"),
t("datePicker.april"),
t("datePicker.may"),
t("datePicker.june"),
t("datePicker.july"),
t("datePicker.august"),
t("datePicker.september"),
t("datePicker.october"),
t("datePicker.november"),
t("datePicker.december"),
]);
// ,
watch(
[datePicker.currentYear, datePicker.currentMonth],
() => {
dateList.value = setDateList(
datePicker.currentYear.value,
datePicker.currentMonth.value
);
},
{ immediate: true }
);
watch(
() => props.modelValue,
() => {
Day.value = props.modelValue;
}
);
//
const changeYearOrMonth = (type: "year" | "month", num: number) => {
if (type === "year") {
datePicker.currentYear.value += num;
} else {
let month = datePicker.currentMonth.value + num;
if (month > 11) {
month = 0;
datePicker.currentYear.value++;
} else if (month < 0) {
month = 11;
datePicker.currentYear.value--;
}
datePicker.currentMonth.value = month;
}
};
//
const footOnOk = () => {
emits("update:modelValue", Day.value);
datePicker.ok();
};
//
const footOnNow = () => {
datePicker.currentYear.value = dayjs().year();
datePicker.currentMonth.value = dayjs().month();
Day.value = new Date(new Date().toDateString()).getTime();
};
//
const footOnClear = () => {
Day.value = -1;
};
const ChildUpdateModelValue = () => {
emits("update:modelValue", Day.value);
};
</script>

View File

@ -0,0 +1,350 @@
<template>
<div
class="layui-laydate layui-laydate-range"
:class="'layui-laydate-range-' + datePicker.showPanel.value"
>
<div style="display: flex">
<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">
<lay-dropdown ref="dropdownYearPanelRefLeft">
<span class="laydate-range-time"
>{{ startTime.year || "--" }} {{ t("datePicker.year") }}</span
>
<template #content>
<YearPanel
class="time-panel"
v-model="startTime.year"
@ok="closeTimePanel"
></YearPanel>
</template>
</lay-dropdown>
<lay-dropdown ref="dropdownMonthPanelRefLeft">
<span class="laydate-range-time">
{{ MONTH_NAME[startTime.month] }}</span
>
<template #content>
<MonthPanel
class="time-panel"
v-model="startTime.month"
@ok="closeTimePanel"
></MonthPanel>
</template>
</lay-dropdown>
<lay-dropdown
ref="dropdownTimePanelRefLeft"
v-if="datePicker.type === 'datetime'"
>
<span class="laydate-range-time">
{{
dayjs()
.hour(startTime.hms.hh)
.minute(startTime.hms.mm)
.second(startTime.hms.ss)
.format("HH:mm:ss")
}}
</span>
<template #content>
<TimePanel
v-model="startTime.hms"
class="time-panel"
@ok="closeTimePanel"
></TimePanel>
</template>
</lay-dropdown>
</div>
</div>
<DateContent
:date-list="prevDateList"
v-model:hoverDate="hoverDate"
v-model:startDate="startTime.day"
v-model:endDate="endTime.day"
></DateContent>
</div>
<div class="layui-laydate-main laydate-main-list-0">
<div class="layui-laydate-header">
<div class="laydate-set-ym">
<lay-dropdown ref="dropdownYearPanelRefRight">
<span class="laydate-range-time"
>{{
startTime.month + 1 > 11 ? startTime.year + 1 : startTime.year
}}
{{ t("datePicker.year") }}</span
>
<template #content>
<YearPanel
class="time-panel"
v-model="endTime.year"
@ok="closeRightYearPanel"
></YearPanel>
</template>
</lay-dropdown>
<lay-dropdown ref="dropdownMonthPanelRefRight">
<span class="laydate-range-time">
{{
MONTH_NAME[
startTime.month + 1 > 11
? startTime.month + 1 - 12
: startTime.month + 1
]
}}
</span>
<template #content>
<MonthPanel
class="time-panel"
v-model="endTime.month"
@ok="closeRightMonthPanel"
></MonthPanel>
</template>
</lay-dropdown>
<lay-dropdown
ref="dropdownTimePanelRefRight"
v-if="datePicker.type === 'datetime'"
>
<span class="laydate-range-time">
{{
dayjs()
.hour(endTime.hms.hh)
.minute(endTime.hms.mm)
.second(endTime.hms.ss)
.format("HH:mm:ss")
}}
</span>
<template #content>
<TimePanel
v-model="endTime.hms"
class="time-panel"
@ok="closeTimePanel"
></TimePanel>
</template>
</lay-dropdown>
</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>
<DateContent
:date-list="nextDateList"
v-model:hoverDate="hoverDate"
v-model:startDate="startTime.day"
v-model:endDate="endTime.day"
></DateContent>
</div>
</div>
<PanelFoot @ok="footOnOk" @now="footOnNow" @clear="footOnClear">
<span v-if="startTime.day !== -1" class="layui-laydate-preview">
{{ dayjs(startTime.day).format("YYYY-MM-DD") }}
<template v-if="datePicker.type === 'datetime'">
{{
dayjs()
.hour(startTime.hms.hh)
.minute(startTime.hms.mm)
.second(startTime.hms.ss)
.format("HH:mm:ss")
}}
</template>
{{ datePicker.rangeSeparator }}
<template v-if="endTime.day !== -1">
{{ dayjs(endTime.day).format("YYYY-MM-DD") }}
<template v-if="datePicker.type === 'datetime'">
{{
dayjs()
.hour(endTime.hms.hh)
.minute(endTime.hms.mm)
.second(endTime.hms.ss)
.format("HH:mm:ss")
}}
</template>
</template>
<template v-else> -- </template>
</span>
</PanelFoot>
</div>
</template>
<script lang="ts">
export default {
name: "DateRange",
};
</script>
<script lang="ts" setup>
import { computed, inject, reactive, ref, watch } from "vue";
import { provideType } from "../interface";
import { setDateList } from "../day";
import PanelFoot from "./PanelFoot.vue";
import DateContent from "./components/DateContent.vue";
import TimePanel from "./TimePanel.vue";
import YearPanel from "./YearPanel.vue";
import MonthPanel from "./MonthPanel.vue";
import LayDropdown from "../../dropdown/index.vue";
import dayjs from "dayjs";
import { useI18n } from "../../../language";
export interface DateRangeProps {
startTime: string;
endTime: string;
}
const props = withDefaults(defineProps<DateRangeProps>(), {});
const emits = defineEmits([
"update:modelValue",
"update:startTime",
"update:endTime",
]);
const datePicker: provideType = inject("datePicker") as provideType;
const { t } = useI18n();
const MONTH_NAME = computed(() => [
t("datePicker.january"),
t("datePicker.february"),
t("datePicker.march"),
t("datePicker.april"),
t("datePicker.may"),
t("datePicker.june"),
t("datePicker.july"),
t("datePicker.august"),
t("datePicker.september"),
t("datePicker.october"),
t("datePicker.november"),
t("datePicker.december"),
]);
const prevDateList = ref<any>([]);
const nextDateList = ref<any>([]);
const startTime = reactive({
year: props.startTime ? dayjs(props.startTime).year() : dayjs().year(),
month: props.startTime ? dayjs(props.startTime).month() : dayjs().month(),
day: props.startTime ? dayjs(props.startTime).startOf("day").valueOf() : -1,
hms: {
hh: props.startTime ? dayjs(props.startTime).hour() : 0,
mm: props.startTime ? dayjs(props.startTime).minute() : 0,
ss: props.startTime ? dayjs(props.startTime).second() : 0,
},
});
const endTime = reactive({
year: props.endTime ? dayjs(props.endTime).year() : dayjs().year(),
month: props.endTime ? dayjs(props.endTime).month() : dayjs().month(),
day: props.endTime ? dayjs(props.endTime).startOf("day").valueOf() : -1,
hms: {
hh: props.endTime ? dayjs(props.endTime).hour() : 0,
mm: props.endTime ? dayjs(props.endTime).minute() : 0,
ss: props.endTime ? dayjs(props.endTime).second() : 0,
},
});
const hoverDate = ref(-1);
//
const changeYearOrMonth = (type: "year" | "month", num: number) => {
if (type === "year") {
startTime.year += num;
} else {
let month = startTime.month + num;
if (month > 11) {
month = 0;
startTime.year++;
} else if (month < 0) {
month = 11;
startTime.year--;
}
startTime.month = month;
}
};
// ,
watch(
() => [startTime.year, startTime.month],
() => {
prevDateList.value = setDateList(startTime.year, startTime.month);
nextDateList.value = setDateList(startTime.year, startTime.month + 1);
},
{ immediate: true }
);
//
const dropdownTimePanelRefLeft = ref();
const dropdownTimePanelRefRight = ref();
const dropdownYearPanelRefLeft = ref();
const dropdownYearPanelRefRight = ref();
const dropdownMonthPanelRefLeft = ref();
const dropdownMonthPanelRefRight = ref();
const closeTimePanel = () => {
if (dropdownTimePanelRefLeft.value) dropdownTimePanelRefLeft.value.hide();
if (dropdownTimePanelRefRight.value) dropdownTimePanelRefRight.value.hide();
if (dropdownYearPanelRefLeft.value) dropdownYearPanelRefLeft.value.hide();
if (dropdownMonthPanelRefLeft.value) dropdownMonthPanelRefLeft.value.hide();
};
const closeRightYearPanel = () => {
if (dropdownYearPanelRefRight.value) dropdownYearPanelRefRight.value.hide();
startTime.year = endTime.year;
};
const closeRightMonthPanel = () => {
dropdownMonthPanelRefRight.value.hide();
let month = endTime.month - 1;
if (month > 11) {
month = 0;
startTime.year++;
} else if (month < 0) {
month = 11;
startTime.year--;
}
startTime.month = month;
};
//
const footOnOk = () => {
let format =
datePicker.type === "datetime" ? "YYYY-MM-DD HH:mm:ss" : "YYYY-MM-DD";
let startTimeVal =
startTime.day !== -1 && endTime.day !== -1
? dayjs(startTime.day)
.hour(startTime.hms.hh)
.minute(startTime.hms.mm)
.second(startTime.hms.ss)
.format(format)
: "";
let endTimeVal =
startTime.day !== -1 && endTime.day !== -1
? dayjs(endTime.day)
.hour(endTime.hms.hh)
.minute(endTime.hms.mm)
.second(endTime.hms.ss)
.format(format)
: "";
emits("update:startTime", startTimeVal);
emits("update:endTime", endTimeVal);
datePicker.ok();
};
//
const footOnNow = () => {
startTime.year = dayjs().year();
startTime.month = dayjs().month();
startTime.day = new Date(new Date().toDateString()).getTime();
startTime.hms.hh = dayjs().hour();
startTime.hms.mm = dayjs().minute();
startTime.hms.ss = dayjs().second();
endTime.day = -1;
};
//
const footOnClear = () => {
startTime.day = -1;
endTime.day = -1;
};
</script>

View File

@ -0,0 +1,126 @@
<template>
<div class="layui-laydate">
<div class="layui-laydate-main laydate-main-list-0 laydate-ym-show">
<div class="layui-laydate-header">
<div class="laydate-set-ym">
<span @click="datePicker.showPanel.value = 'month'">{{
typeof Month !== "string"
? MONTH_NAME[Month]
: t("datePicker.selectMonth")
}}</span>
</div>
</div>
</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) === Month }"
@click="handleMonthClick(item)"
>
{{ item.slice(0, 3) }}
</li>
</ul>
</div>
<PanelFoot @ok="footOnOk" @now="footOnNow" @clear="footOnClear">
<span
v-if="datePicker.type === 'yearmonth'"
@click="datePicker.showPanel.value = 'year'"
class="laydate-btns-time"
>{{ t("datePicker.selectYear") }}</span
>
</PanelFoot>
</div>
</template>
<script lang="ts">
export default {
name: "TimePanel",
};
</script>
<script lang="ts" setup>
import dayjs from "dayjs";
import { useI18n } from "../../../language";
import { computed, inject, ref, watch } from "vue";
import { provideType } from "../interface";
import PanelFoot from "./PanelFoot.vue";
export interface TimePanelProps {
modelValue: number | string;
max?: number;
}
const props = withDefaults(defineProps<TimePanelProps>(), {
max: dayjs().year() + 100,
});
const emits = defineEmits(["update:modelValue", "ok"]);
const datePicker: provideType = inject("datePicker") as provideType;
const Month = ref(props.modelValue);
const { t } = useI18n();
const MONTH_NAME = computed(() => [
t("datePicker.january"),
t("datePicker.february"),
t("datePicker.march"),
t("datePicker.april"),
t("datePicker.may"),
t("datePicker.june"),
t("datePicker.july"),
t("datePicker.august"),
t("datePicker.september"),
t("datePicker.october"),
t("datePicker.november"),
t("datePicker.december"),
]);
//
const handleMonthClick = (item: any) => {
Month.value = MONTH_NAME.value.indexOf(item);
if (!datePicker.range) {
if (datePicker.type === "yearmonth") {
datePicker.currentDay.value = dayjs(datePicker.currentDay.value)
.month(MONTH_NAME.value.indexOf(item))
.valueOf();
}
if (datePicker.type === "date" || datePicker.type === "datetime") {
emits("update:modelValue", MONTH_NAME.value.indexOf(item));
datePicker.showPanel.value = datePicker.type;
}
}
if (datePicker.simple) {
footOnOk();
}
};
watch(
() => props.modelValue,
() => {
Month.value = props.modelValue;
}
);
//
const footOnOk = () => {
emits("update:modelValue", Month.value ? Month.value : -1);
if (datePicker.range) {
//
emits("ok");
return;
} else {
if (datePicker.type === "datetime" || datePicker.type === "date") {
datePicker.showPanel.value = datePicker.type;
} else {
datePicker.ok();
}
}
};
//
const footOnNow = () => {
Month.value = dayjs().month();
};
//
const footOnClear = () => {
Month.value = "";
};
</script>

View File

@ -0,0 +1,295 @@
<template>
<div class="layui-laydate layui-laydate-range">
<div style="display: flex">
<div class="layui-laydate-main laydate-main-list-0">
<div class="layui-laydate-header">
<i
class="layui-icon laydate-icon laydate-prev-y"
@click="changeYear(-1)"
></i
>
<div class="laydate-set-ym">
<lay-dropdown ref="dropdownYearPanelRefLeft">
<span class="laydate-range-time"
>{{ startTime.year || "--" }} {{ t("datePicker.year") }}</span
>
<template #content>
<YearPanel
class="time-panel"
v-model="startTime.year"
@ok="closeLeftYearPanel"
></YearPanel>
</template>
</lay-dropdown>
</div>
</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"
:data-unix="getUnix(item, 'left')"
:class="{
'layui-this':
getUnix(item, 'left') === startTime.unix ||
getUnix(item, 'left') === endTime.unix,
'laydate-range-hover': ifHasRangeHoverClass(
getUnix(item, 'left')
),
}"
@click="handleMonthClick(getUnix(item, 'left'))"
@mouseenter="monthItemMouseEnter($event, item)"
>
{{ item.slice(0, 3) }}
</li>
</ul>
</div>
</div>
<div class="layui-laydate-main laydate-main-list-0">
<div class="layui-laydate-header">
<div class="laydate-set-ym">
<lay-dropdown ref="dropdownYearPanelRefRight">
<span class="laydate-range-time"
>{{ startTime.year + 1 }} {{ t("datePicker.year") }}</span
>
<template #content>
<YearPanel
class="time-panel"
v-model="endTime.year"
@ok="closeRightYearPanel"
></YearPanel>
</template>
</lay-dropdown>
</div>
<i
class="layui-icon laydate-icon laydate-next-y"
@click="changeYear(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"
:data-unix="getUnix(item, 'right')"
:class="{
'layui-this':
getUnix(item, 'right') === startTime.unix ||
getUnix(item, 'right') === endTime.unix,
'laydate-range-hover': ifHasRangeHoverClass(
getUnix(item, 'right')
),
}"
@click="handleMonthClick(getUnix(item, 'right'))"
@mouseenter="monthItemMouseEnter($event, item)"
>
{{ item.slice(0, 3) }}
</li>
</ul>
</div>
</div>
</div>
<PanelFoot @ok="footOnOk" @now="footOnNow" @clear="footOnClear">
<span v-if="startTime.unix !== -1" class="layui-laydate-preview">
{{ dayjs(startTime.unix).format("YYYY-MM-DD") }}
{{ datePicker.rangeSeparator }}
<template v-if="endTime.unix !== -1">
{{ dayjs(endTime.unix).format("YYYY-MM-DD") }}
</template>
<template v-else> -- </template>
</span>
</PanelFoot>
</div>
</template>
<script lang="ts">
export default {
name: "MonthRange",
};
</script>
<script lang="ts" setup>
import { inject, reactive, ref, watch, computed } from "vue";
import { provideType } from "../interface";
import PanelFoot from "./PanelFoot.vue";
import YearPanel from "./YearPanel.vue";
import dayjs from "dayjs";
import LayDropdown from "../../dropdown/index.vue";
import { useI18n } from "../../../language";
export interface DateRangeProps {
startTime: string;
endTime: string;
}
const props = withDefaults(defineProps<DateRangeProps>(), {});
const emits = defineEmits([
"update:modelValue",
"update:startTime",
"update:endTime",
]);
const { t } = useI18n();
const datePicker: provideType = inject("datePicker") as provideType;
const startTime = reactive({
year: props.startTime ? dayjs(props.startTime).year() : dayjs().year(),
unix: props.startTime
? dayjs(props.startTime).hour(0).minute(0).second(0).valueOf()
: -1,
});
const endTime = reactive({
year: props.endTime ? dayjs(props.endTime).year() : dayjs().year() + 1,
unix: props.startTime
? dayjs(props.endTime).hour(0).minute(0).second(0).valueOf()
: -1,
});
let hoverMonth = ref(-1);
const MONTH_NAME = computed(() => [
t("datePicker.january"),
t("datePicker.february"),
t("datePicker.march"),
t("datePicker.april"),
t("datePicker.may"),
t("datePicker.june"),
t("datePicker.july"),
t("datePicker.august"),
t("datePicker.september"),
t("datePicker.october"),
t("datePicker.november"),
t("datePicker.december"),
]);
//
const changeYear = (num: number) => {
startTime.year += num;
};
//
const handleMonthClick = (item: number) => {
if (startTime.unix === -1 && endTime.unix === -1) {
startTime.unix = item;
} else if (startTime.unix !== -1 && endTime.unix !== -1) {
hoverMonth.value = -1;
startTime.unix = item;
endTime.unix = -1;
} else if (startTime.unix !== -1 && endTime.unix === -1) {
endTime.unix = item;
if (item < startTime.unix) {
//swap
const first = startTime.unix;
const last = item;
startTime.unix = last;
endTime.unix = first;
}
}
};
// ,
watch(
() => [props.startTime, props.endTime],
() => {
startTime.year = props.startTime
? dayjs(props.startTime).year()
: dayjs().year();
startTime.unix = props.startTime
? dayjs(props.startTime).hour(0).minute(0).second(0).valueOf()
: -1;
endTime.year = props.endTime ? dayjs(props.endTime).year() : dayjs().year();
endTime.unix = props.startTime
? dayjs(props.endTime).hour(0).minute(0).second(0).valueOf()
: -1;
}
);
//
const dropdownYearPanelRefLeft = ref();
const dropdownYearPanelRefRight = ref();
const closeLeftYearPanel = () => {
if (dropdownYearPanelRefLeft.value) dropdownYearPanelRefLeft.value.hide();
};
const closeRightYearPanel = () => {
if (dropdownYearPanelRefRight.value) dropdownYearPanelRefRight.value.hide();
startTime.year = endTime.year;
};
//
const footOnOk = () => {
let format = "YYYY-MM";
let startTimeVal =
startTime.unix !== -1 && endTime.unix !== -1
? dayjs(startTime.unix).format(format)
: "";
let endTimeVal =
endTime.unix !== -1 && endTime.unix !== -1
? dayjs(endTime.unix).format(format)
: "";
emits("update:startTime", startTimeVal);
emits("update:endTime", endTimeVal);
datePicker.ok();
};
//
const footOnNow = () => {
startTime.year = dayjs().year();
startTime.unix = dayjs(
startTime.year + "-" + (dayjs().month() + 1)
).valueOf();
endTime.unix = -1;
hoverMonth.value = -1;
};
//
const footOnClear = () => {
startTime.unix = -1;
endTime.unix = -1;
hoverMonth.value = -1;
};
const monthItemMouseEnter = (event: MouseEvent, item: any) => {
if (!datePicker.range) {
return;
}
if (startTime.unix === -1) {
return;
}
if (hoverMonth.value !== -1 && endTime.unix !== -1) {
hoverMonth.value = -1;
return;
}
hoverMonth.value = parseInt(
(event.target as HTMLElement).dataset.unix as string
);
};
const ifHasRangeHoverClass = computed(() => {
return function (item: any) {
if (!datePicker.range) {
return false;
}
if (startTime.unix === -1) {
return false;
}
if (hoverMonth.value === -1 && endTime.unix === -1) {
return false;
}
let hover = endTime.unix !== -1 ? endTime.unix : hoverMonth.value;
let max = startTime.unix > hover ? startTime.unix : hover;
let min = startTime.unix < hover ? startTime.unix : hover;
if (item >= min && item <= max) {
return true;
}
return false;
};
});
const getUnix = computed(() => {
return function (item: any, position: "left" | "right") {
let month = MONTH_NAME.value.indexOf(item);
let year = position === "left" ? startTime.year : startTime.year + 1;
return dayjs(year + "-" + (month + 1)).valueOf();
};
});
</script>

View File

@ -0,0 +1,40 @@
<template>
<div class="layui-laydate-footer">
<slot></slot>
<div class="laydate-footer-btns">
<span lay-type="clear" class="laydate-btns-clear" @click="handelClear">{{
t("datePicker.clear")
}}</span>
<span lay-type="now" class="laydate-btns-now" @click="handelNow">{{
t("datePicker.now")
}}</span>
<span lay-type="confirm" class="laydate-btns-confirm" @click="handelOk">{{
t("datePicker.confirm")
}}</span>
</div>
</div>
</template>
<script lang="ts">
export default {
name: "PanelFoot",
};
</script>
<script lang="ts" setup>
import { useI18n } from "../../../language";
const { t } = useI18n();
const emits = defineEmits(["ok", "clear", "now"]);
const handelOk = () => {
emits("ok");
};
const handelNow = () => {
emits("now");
};
const handelClear = () => {
emits("clear");
};
</script>

View File

@ -0,0 +1,164 @@
<template>
<div class="layui-laydate">
<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">{{
t("datePicker.selectTime")
}}</span>
</div>
</div>
<div class="layui-laydate-content" style="height: 210px">
<ul class="layui-laydate-list laydate-time-list" ref="timePanelRef">
<li
class="num-list"
v-for="item in els"
:key="item.type"
:data-type="item.type"
>
<ol class="scroll" @click="chooseTime">
<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 == hms[item.type] ? 'layui-this' : '']"
>
{{ index.toString().padStart(2, "0") }}
</li>
</ol>
</li>
</ul>
</div>
</div>
<PanelFoot @ok="footOnOk" @now="footOnNow" @clear="footOnClear">
<span
v-if="datePicker.type === 'datetime' && !datePicker.range"
@click="datePicker.showPanel.value = 'datetime'"
class="laydate-btns-time"
>{{ t("datePicker.selectDate") }}</span
>
<template v-else-if="!isNaN(hms.hh) && !isNaN(hms.mm) && !isNaN(hms.ss)">
{{
dayjs().hour(hms.hh).minute(hms.mm).second(hms.ss).format("HH:mm:ss")
}}
</template>
</PanelFoot>
</div>
</template>
<script lang="ts">
export default {
name: "TimePanel",
};
</script>
<script lang="ts" setup>
import dayjs from "dayjs";
import { useI18n } from "../../../language";
import { inject, onMounted, ref, nextTick, watch } from "vue";
import { provideType } from "../interface";
import PanelFoot from "./PanelFoot.vue";
export interface hmsType {
hh: number;
mm: number;
ss: number;
[key: string]: any;
}
export interface TimePanelProps {
modelValue: hmsType;
}
const props = withDefaults(defineProps<TimePanelProps>(), {});
const emits = defineEmits(["update:modelValue", "ok"]);
const datePicker: provideType = inject("datePicker") as provideType;
const { t } = useI18n();
const els = [
{ count: 24, type: "hh" },
{ count: 60, type: "mm" },
{ count: 60, type: "ss" },
];
const hms = ref<hmsType>({
hh: props.modelValue.hh,
mm: props.modelValue.mm,
ss: props.modelValue.ss,
});
// - hms
const chooseTime = (e: any) => {
if (e.target.nodeName == "LI") {
let { value, type } = e.target.dataset;
hms.value[type as keyof typeof hms.value] = parseInt(value);
}
};
const timePanelRef = ref();
onMounted(() => {
scrollTo();
});
watch(
() => props.modelValue,
() => {
hms.value = {
hh: props.modelValue.hh,
mm: props.modelValue.mm,
ss: props.modelValue.ss,
};
},
{ deep: true }
);
const scrollTo = () => {
nextTick(() => {
timePanelRef.value.childNodes.forEach((element: HTMLElement) => {
if (element.nodeName === "LI") {
let scrollTop = 0;
let parentDom = element.firstElementChild as HTMLElement;
let childList = parentDom.childNodes;
for (let index = 0; index < childList.length; index++) {
const child = childList[index] as HTMLElement;
if (child.nodeName !== "LI") {
continue;
}
if (child.classList && child.classList.contains("layui-this")) {
scrollTop =
child.offsetTop -
(parentDom.offsetHeight - child.offsetHeight) / 2;
parentDom.scrollTo(0, scrollTop);
break;
}
}
}
});
});
};
//
const footOnOk = () => {
emits("update:modelValue", hms.value);
if (datePicker.range) {
//
emits("ok");
return;
} else {
datePicker.ok();
if (datePicker.type === "datetime") {
datePicker.showPanel.value = "date";
}
}
};
//
const footOnNow = () => {
hms.value.hh = dayjs().hour();
hms.value.mm = dayjs().minute();
hms.value.ss = dayjs().second();
scrollTo();
};
//
const footOnClear = () => {
hms.value.hh = 0;
hms.value.mm = 0;
hms.value.ss = 0;
scrollTo();
};
</script>

View File

@ -0,0 +1,146 @@
<template>
<div class="layui-laydate">
<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">{{
t("datePicker.selectYear")
}}</span>
</div>
</div>
</div>
<div
class="layui-laydate-content"
style="height: 220px; overflow-y: auto"
ref="ScrollRef"
>
<ul class="layui-laydate-list laydate-year-list">
<li
v-for="item of yearList"
:key="item"
:class="{ 'layui-this': Year === item }"
@click="handleYearClick(item)"
>
{{ item }}
</li>
</ul>
</div>
<PanelFoot @ok="footOnOk" @now="footOnNow" @clear="footOnClear">
<span
v-if="datePicker.type === 'yearmonth'"
@click="datePicker.showPanel.value = 'month'"
class="laydate-btns-time"
>{{ t("datePicker.selectMonth") }}</span
>
<template v-else-if="Year > 0">{{ Year }}</template>
</PanelFoot>
</div>
</template>
<script lang="ts">
export default {
name: "YearPanel",
};
</script>
<script lang="ts" setup>
import dayjs from "dayjs";
import { useI18n } from "../../../language";
import { inject, nextTick, onMounted, ref, watch } from "vue";
import { getYears } from "../day";
import { provideType } from "../interface";
import PanelFoot from "./PanelFoot.vue";
export interface TimePanelProps {
modelValue: number | string;
max?: number;
}
const props = withDefaults(defineProps<TimePanelProps>(), {
max: dayjs().year() + 100,
});
const emits = defineEmits(["update:modelValue", "ok"]);
const datePicker: provideType = inject("datePicker") as provideType;
const yearList = ref<number[]>(getYears());
const unWatch = ref(false);
const Year = ref(props.modelValue);
const { t } = useI18n();
//
const handleYearClick = (item: any) => {
unWatch.value = true;
Year.value = item;
if (!datePicker.range) {
if (datePicker.type === "year") {
datePicker.currentDay.value = dayjs().year(item).valueOf();
} else if (datePicker.type === "yearmonth") {
datePicker.currentDay.value = dayjs().year(item).valueOf();
datePicker.showPanel.value = "month";
emits("update:modelValue", Year.value);
} else {
emits("update:modelValue", Year.value);
datePicker.showPanel.value = datePicker.type;
}
}
setTimeout(() => {
unWatch.value = false;
}, 0);
if (datePicker.simple) {
footOnOk();
}
};
const ScrollRef = ref();
onMounted(() => {
scrollTo();
});
watch(
() => Year,
() => {
Year.value = props.modelValue;
}
);
const scrollTo = () => {
nextTick(() => {
let scrollTop = 0;
for (const child of ScrollRef.value.firstElementChild.childNodes) {
if (child.classList && child.classList.contains("layui-this")) {
scrollTop =
child.offsetTop -
(ScrollRef.value.offsetHeight - child.offsetHeight) / 2;
break;
}
}
ScrollRef.value.scrollTo(0, scrollTop);
});
};
//
const footOnOk = () => {
emits("update:modelValue", Year.value ? Year.value : -1);
if (datePicker.range) {
//
emits("ok");
return;
} else {
if (datePicker.type === "datetime" || datePicker.type === "date") {
datePicker.showPanel.value = datePicker.type;
} else {
datePicker.ok();
}
}
};
//
const footOnNow = () => {
Year.value = dayjs().year();
if (datePicker.type === "yearmonth") {
datePicker.currentMonth.value = dayjs().month();
}
scrollTo();
};
//
const footOnClear = () => {
Year.value = "";
};
</script>

View File

@ -0,0 +1,169 @@
<template>
<div class="layui-laydate-content">
<table style="width: 100%">
<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 === modelValue ||
(datePicker.range &&
item.type === 'current' &&
(item.value == startDate || item.value == endDate)),
'laydate-range-hover': ifHasRangeHoverClass(item),
'layui-disabled': item.type !== 'current' && datePicker.range,
}"
@click="handleDayClick(item)"
@mouseenter="dayItemMouseEnter($event, item)"
>
{{ item.day }}
</td>
</tr>
</template>
</tbody>
</table>
</div>
</template>
<script lang="ts">
export default {
name: "DateContent",
};
</script>
<script lang="ts" setup>
import { useI18n } from "../../../../language";
import { computed, inject } from "vue";
import { provideType } from "../../interface";
export interface DateContentProps {
dateList: any;
modelValue?: number;
startDate?: number;
endDate?: number;
hoverDate?: number;
}
const props = withDefaults(defineProps<DateContentProps>(), {
dateList: [],
modelValue: -1,
hoverDate: -1,
startDate: -1,
endDate: -1,
});
const { t } = useI18n();
const WEEK_NAME = computed(() => [
t("datePicker.sunday"),
t("datePicker.monday"),
t("datePicker.tuesday"),
t("datePicker.wednesday"),
t("datePicker.thursday"),
t("datePicker.friday"),
t("datePicker.saturday"),
]);
const datePicker: provideType = inject("datePicker") as provideType;
const emits = defineEmits([
"update:modelValue",
"update:startDate",
"update:endDate",
"update:hoverDate",
"simple",
]);
//
const handleDayClick = (item: any) => {
if (datePicker.range) {
if (item.type !== "current") {
return;
}
if (props.startDate === -1 && props.endDate === -1) {
emits("update:startDate", item.value);
} else if (props.startDate !== -1 && props.endDate !== -1) {
emits("update:hoverDate", item.value);
emits("update:startDate", item.value);
emits("update:endDate", -1);
} else if (props.startDate !== -1 && props.endDate === -1) {
emits("update:endDate", item.value);
if (item.value < props.startDate) {
//swap
const first = props.startDate;
const last = item.value;
emits("update:startDate", last);
emits("update:endDate", first);
}
}
} else {
emits("update:modelValue", item.value);
if (item.type !== "current") {
datePicker.currentMonth.value =
item.type === "prev"
? datePicker.currentMonth.value - 1
: datePicker.currentMonth.value + 1;
}
if (datePicker.simple) {
emits("simple");
}
}
};
const dayItemMouseEnter = (event: MouseEvent, item: any) => {
if (!datePicker.range) {
return;
}
if (props.startDate === -1) {
return;
}
if (item.type !== "current") {
return;
}
if (props.startDate !== -1 && props.endDate !== -1) {
emits("update:hoverDate", -1);
return;
}
emits(
"update:hoverDate",
parseInt((event.target as HTMLElement).dataset.unix as string)
);
};
const ifHasRangeHoverClass = computed(() => {
return function (item: any) {
if (!datePicker.range) {
return false;
}
if (props.startDate === -1) {
return false;
}
if (item.type !== "current") {
return false;
}
if (props.hoverDate === -1 && props.endDate === -1) {
return false;
}
let hover = props.endDate !== -1 ? props.endDate : props.hoverDate;
let max = props.startDate > hover ? props.startDate : hover;
let min = props.startDate < hover ? props.startDate : hover;
if (item.value >= min && item.value <= max) {
return true;
}
return false;
};
});
</script>

View File

@ -0,0 +1,105 @@
/**
*
*/
const getYears = () => {
let years = [];
for (let i = 1970; i < getYear() + 100; i++) {
years.push(i);
}
return years;
};
/**
*
*/
const getDate = (val = "") => {
if (val) {
return new Date(val);
} else {
return new Date();
}
};
/**
*
*/
const getYear = (val = "") => {
return getDate(val).getFullYear();
};
/**
*
*/
const getMonth = (val = "") => {
return getDate(val).getMonth();
};
const getDay = (val = "") => {
if (val) {
return new Date(getDate(val).toDateString()).getTime();
} else {
return -1;
}
};
/**
*
*
* @param year
* @param month
*/
const getDayLength = (year: number, month: number): number => {
return new Date(year, month + 1, 0).getDate();
};
// 设置日期列表
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",
});
}
}
return list;
};
export {
getDayLength,
getYears,
getDate,
getMonth,
getYear,
getDay,
setDateList,
};

Binary file not shown.

View File

@ -0,0 +1,45 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
<!--
2013-9-30: Created.
-->
<svg>
<metadata>
Created by iconfont
</metadata>
<defs>
<font id="laydate-icon" horiz-adv-x="1024" >
<font-face
font-family="laydate-icon"
font-weight="500"
font-stretch="normal"
units-per-em="1024"
ascent="896"
descent="-128"
/>
<missing-glyph />
<glyph glyph-name="x" unicode="x" horiz-adv-x="1001"
d="M281 543q-27 -1 -53 -1h-83q-18 0 -36.5 -6t-32.5 -18.5t-23 -32t-9 -45.5v-76h912v41q0 16 -0.5 30t-0.5 18q0 13 -5 29t-17 29.5t-31.5 22.5t-49.5 9h-133v-97h-438v97zM955 310v-52q0 -23 0.5 -52t0.5 -58t-10.5 -47.5t-26 -30t-33 -16t-31.5 -4.5q-14 -1 -29.5 -0.5
t-29.5 0.5h-32l-45 128h-439l-44 -128h-29h-34q-20 0 -45 1q-25 0 -41 9.5t-25.5 23t-13.5 29.5t-4 30v167h911zM163 247q-12 0 -21 -8.5t-9 -21.5t9 -21.5t21 -8.5q13 0 22 8.5t9 21.5t-9 21.5t-22 8.5zM316 123q-8 -26 -14 -48q-5 -19 -10.5 -37t-7.5 -25t-3 -15t1 -14.5
t9.5 -10.5t21.5 -4h37h67h81h80h64h36q23 0 34 12t2 38q-5 13 -9.5 30.5t-9.5 34.5q-5 19 -11 39h-368zM336 498v228q0 11 2.5 23t10 21.5t20.5 15.5t34 6h188q31 0 51.5 -14.5t20.5 -52.5v-227h-327z" />
<glyph glyph-name="youyou" unicode="&#58882;" d="M283.648 721.918976 340.873216 780.926976 740.352 383.997952 340.876288-12.925952 283.648 46.077952 619.52 383.997952Z" horiz-adv-x="1024" />
<glyph glyph-name="zuozuo" unicode="&#58883;" d="M740.352 721.918976 683.126784 780.926976 283.648 383.997952 683.123712-12.925952 740.352 46.077952 404.48 383.997952Z" horiz-adv-x="1024" />
<glyph glyph-name="xiayiye" unicode="&#58970;" d="M62.573 384.103l423.401 423.662c18.985 18.985 49.757 18.985 68.727 0 18.982-18.972 18.985-49.746 0-68.729l-355.058-355.067 356.796-356.796c18.977-18.971 18.976-49.746 0-68.727-18.982-18.976-49.751-18.976-68.727 0l-39.753 39.753 0.269 0.246-385.655 385.661zM451.365 384.103l423.407 423.662c18.985 18.985 49.757 18.985 68.727 0 18.982-18.972 18.985-49.746 0-68.729l-355.058-355.067 356.796-356.796c18.977-18.971 18.976-49.746 0-68.727-18.982-18.976-49.757-18.977-68.727 0l-39.762 39.754 0.273 0.249-385.662 385.661zM451.365 384.103z" horiz-adv-x="1024" />
<glyph glyph-name="xiayiye1" unicode="&#58971;" d="M948.066926 382.958838l-411.990051-412.24426c-18.47333-18.47333-48.417689-18.47333-66.875207 0-18.47333 18.461167-18.47333 48.405526 0 66.875207L814.691135 383.088983 467.512212 730.269123c-18.466032 18.458735-18.466032 48.405526 0 66.873991 18.468465 18.464816 48.410391 18.464816 66.872774 0l38.682336-38.682336-0.261507-0.239614 375.259894-375.265975v0.003649m-378.312834 0L157.756743-29.285422c-18.47333-18.47333-48.415256-18.47333-66.872775 0-18.47333 18.461167-18.47333 48.405526 0 66.875207L436.369787 383.088983 89.19208 730.269123c-18.4636 18.458735-18.4636 48.405526 0 66.873991 18.470898 18.464816 48.415256 18.464816 66.872774 0l38.692067-38.682336-0.266372-0.239614 375.267191-375.265975-0.004865 0.003649m0 0z" horiz-adv-x="1024" />
</font>
</defs></svg>

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,579 @@
@import "../dropdown/index.less";
@import "../input/index.less";
@lg: 44px;
@md: 38px;
@sm: 32px;
@xs: 26px;
@lg-width: 260px;
@md-width: 220px;
@sm-width: 180px;
@xs-width: 140px;
@lg-range-width: 520px;
@md-range-width: 440px;
@sm-range-width: 360px;
@xs-range-width: 280px;
.set-size(@size,@width) {
& {
width: @width;
height: @size;
.layui-input {
height: @size;
line-height: @size;
}
}
}
.layui-date-picker {
&[size="lg"] {
.set-size(@lg,@lg-width);
}
&[size="md"] {
.set-size(@md,@md-width);
}
&[size="sm"] {
.set-size(@sm,@sm-width);
}
&[size="xs"] {
.set-size(@xs,@xs-width);
}
}
.layui-date-range-picker {
&[size="lg"] {
.set-size(@lg,@lg-range-width);
}
&[size="md"] {
.set-size(@md,@md-range-width);
}
&[size="sm"] {
.set-size(@sm,@sm-range-width);
}
&[size="xs"] {
.set-size(@xs,@xs-range-width);
}
}
@font-face {
font-family: "laydate-icon";
src: url("./font/iconfont.eot");
src: url("./font/iconfont.eot#iefix") format("embedded-opentype"),
url("./font/iconfont.svg#iconfont") format("svg"),
url("./font/iconfont.woff") format("woff"),
url("./font/iconfont.ttf") format("truetype");
}
.laydate-icon {
font-family: "laydate-icon" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
html #layuicss-laydate {
display: none;
position: absolute;
width: 1989px;
}
/* 初始化 */
.layui-laydate * {
margin: 0;
padding: 0;
}
/* 主体结构 */
.layui-laydate,
.layui-laydate * {
box-sizing: border-box;
}
.layui-laydate {
z-index: 66666666;
border-radius: 2px;
font-size: 14px;
-webkit-animation-duration: 0.2s;
animation-duration: 0.2s;
-webkit-animation-fill-mode: both;
animation-fill-mode: both;
}
.layui-laydate-main {
width: 272px;
}
.layui-laydate-header *,
.layui-laydate-content td,
.layui-laydate-list li {
transition-duration: 0.3s;
-webkit-transition-duration: 0.3s;
}
/* 微微往下滑入 */
@keyframes laydate-downbit {
0% {
opacity: 0.3;
transform: translate3d(0, -5px, 0);
}
100% {
opacity: 1;
transform: translate3d(0, 0, 0);
}
}
.layui-laydate {
animation-name: laydate-downbit;
}
.layui-laydate-static {
position: relative;
z-index: 0;
display: inline-block;
margin: 0;
-webkit-animation: none;
animation: none;
}
/* 展开年月列表时 */
.laydate-ym-show .laydate-prev-m,
.laydate-ym-show .laydate-next-m {
display: none !important;
}
.laydate-ym-show .laydate-prev-y,
.laydate-ym-show .laydate-next-y {
display: inline-block !important;
}
.laydate-ym-show .laydate-set-ym span[lay-type="month"] {
display: none !important;
}
/* 展开时间列表时 */
.laydate-time-show .layui-laydate-header .layui-icon,
.laydate-time-show .laydate-set-ym span[lay-type="year"],
.laydate-time-show .laydate-set-ym span[lay-type="month"] {
display: none !important;
}
/* 头部结构 */
.layui-laydate-header {
position: relative;
line-height: 30px;
padding: 10px 70px 5px;
}
.layui-laydate-header * {
vertical-align: bottom;
}
.layui-laydate-header i {
position: absolute;
top: 10px;
padding: 0 5px;
color: #999;
font-size: 18px;
cursor: pointer;
}
.layui-laydate-header i.laydate-prev-y {
left: 15px;
}
.layui-laydate-header i.laydate-prev-m {
left: 45px;
}
.layui-laydate-header i.laydate-next-y {
right: 15px;
}
.layui-laydate-header i.laydate-next-m {
right: 45px;
}
.laydate-set-ym {
width: 100%;
text-align: center;
box-sizing: border-box;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
.laydate-set-ym span {
padding: 0 10px;
cursor: pointer;
}
.laydate-time-text {
cursor: default !important;
}
/* 主体结构 */
.layui-laydate-content {
position: relative;
padding: 10px;
-moz-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
}
.layui-laydate-content table {
border-collapse: collapse;
border-spacing: 0;
}
.layui-laydate-content th,
.layui-laydate-content td {
width: 36px;
height: 30px;
padding: 5px;
text-align: center !important;
}
.layui-laydate-content th {
font-weight: 400;
}
.layui-laydate-content td {
position: relative;
cursor: pointer;
}
.laydate-day-mark {
position: absolute;
left: 0;
top: 0;
width: 100%;
line-height: 30px;
font-size: 12px;
overflow: hidden;
}
.laydate-day-mark::after {
position: absolute;
content: "";
right: 2px;
top: 2px;
width: 5px;
height: 5px;
border-radius: 50%;
}
/* 底部结构 */
.layui-laydate-footer {
position: relative;
height: 46px;
line-height: 26px;
padding: 10px;
}
.layui-laydate-footer span {
display: inline-block;
vertical-align: top;
height: 26px;
line-height: 24px;
padding: 0 10px;
border: 1px solid #c9c9c9;
border-radius: 2px;
background-color: #fff;
font-size: 12px;
cursor: pointer;
white-space: nowrap;
transition: all 0.3s;
}
.layui-laydate-footer span:hover {
color: #5fb878;
}
.layui-laydate-footer span.layui-laydate-preview {
cursor: default;
border-color: transparent !important;
}
.layui-laydate-footer span.layui-laydate-preview:hover {
color: #666;
}
.layui-laydate-footer span:first-child.layui-laydate-preview {
padding-left: 0;
}
.laydate-footer-btns {
position: absolute;
right: 10px;
top: 10px;
}
.laydate-footer-btns span {
margin: 0 0 0 -1px;
}
/* 年月列表 */
.layui-laydate-list {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
padding: 10px;
box-sizing: border-box;
background-color: #fff;
}
.layui-laydate-list > li {
position: relative;
display: inline-block;
width: 33.3%;
height: 36px;
line-height: 36px;
margin: 3px 0;
vertical-align: middle;
text-align: center;
cursor: pointer;
}
.laydate-month-list > li {
width: 25%;
margin: 17px 0;
}
.laydate-time-list > li {
height: 100%;
margin: 0;
line-height: normal;
cursor: default;
}
.laydate-time-list p {
position: relative;
top: -4px;
line-height: 29px;
}
.laydate-time-list ol {
height: 181px;
overflow: hidden;
}
.laydate-time-list > li:hover ol {
overflow-y: auto;
}
.laydate-time-list ol li {
width: 130%;
padding-left: 4px;
height: 30px;
line-height: 30px;
text-align: left;
cursor: pointer;
}
/* 提示 */
.layui-laydate-hint {
top: 115px;
left: 50%;
width: 250px;
margin-left: -125px;
line-height: 20px;
padding: 15px;
text-align: center;
font-size: 12px;
color: #ff5722;
}
/* 双日历 */
.layui-laydate-range {
min-width: 546px;
}
.layui-laydate-range .layui-laydate-main {
display: inline-block;
vertical-align: middle;
}
.layui-laydate-range .laydate-main-list-1 .layui-laydate-header,
.layui-laydate-range .laydate-main-list-1 .layui-laydate-content {
border-left: 1px solid #e2e2e2;
}
/* 默认简约主题 */
.layui-laydate,
.layui-laydate-hint {
background-color: #fff;
color: #666;
}
.layui-laydate-header {
border-bottom: 1px solid #e2e2e2;
}
.layui-laydate-header i:hover,
.layui-laydate-header span:hover {
color: #5fb878;
}
.layui-laydate-content {
border-top: none 0;
border-bottom: none 0;
}
.layui-laydate-content th {
color: #333;
}
.layui-laydate-content td {
color: #666;
}
.layui-laydate-content td.laydate-selected {
background-color: #b5fff8;
}
.laydate-selected:hover {
background-color: #00f7de !important;
}
.layui-laydate-content td:hover,
.layui-laydate-list li:hover {
background-color: #eee;
color: #333;
}
.laydate-time-list li ol {
margin: 0;
padding: 0;
border: 1px solid #e2e2e2;
}
.laydate-time-list li:first-child ol {
border-left-width: 1px;
}
.laydate-time-list > li:hover {
background: none;
}
.layui-laydate-content .laydate-day-prev,
.layui-laydate-content .laydate-day-next {
color: #d2d2d2;
}
.laydate-selected.laydate-day-prev,
.laydate-selected.laydate-day-next {
background-color: #f8f8f8 !important;
}
.layui-laydate-footer {
border-top: 1px solid #e2e2e2;
}
.layui-laydate-hint {
color: #ff5722;
}
.laydate-day-mark::after {
background-color: #5fb878;
}
.layui-laydate-content td.layui-this .laydate-day-mark::after {
display: none;
}
.layui-laydate-footer span[lay-type="date"] {
color: #5fb878;
}
.layui-laydate .layui-this {
background-color: #009688 !important;
color: #fff !important;
}
.layui-laydate .laydate-disabled,
.layui-laydate .laydate-disabled:hover {
background: none !important;
color: #d2d2d2 !important;
cursor: not-allowed !important;
-moz-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
}
/* 墨绿/自定义背景色主题 */
.laydate-theme-molv {
border: none;
}
.laydate-theme-molv.layui-laydate-range {
width: 548px;
}
.laydate-theme-molv .layui-laydate-main {
width: 274px;
}
.laydate-theme-molv .layui-laydate-header {
border: none;
background-color: #009688;
}
.laydate-theme-molv .layui-laydate-header i,
.laydate-theme-molv .layui-laydate-header span {
color: #f6f6f6;
}
.laydate-theme-molv .layui-laydate-header i:hover,
.laydate-theme-molv .layui-laydate-header span:hover {
color: #fff;
}
.laydate-theme-molv .layui-laydate-content {
border: 1px solid #e2e2e2;
border-top: none;
border-bottom: none;
}
.laydate-theme-molv .laydate-main-list-1 .layui-laydate-content {
border-left: none;
}
.laydate-theme-molv .layui-laydate-footer {
border: 1px solid #e2e2e2;
}
/* 格子主题 */
.laydate-theme-grid .layui-laydate-content td,
.laydate-theme-grid .layui-laydate-content thead,
.laydate-theme-grid .laydate-year-list > li,
.laydate-theme-grid .laydate-month-list > li {
border: 1px solid #e2e2e2;
}
.laydate-theme-grid .laydate-selected,
.laydate-theme-grid .laydate-selected:hover {
background-color: #f2f2f2 !important;
color: #009688 !important;
}
.laydate-theme-grid .laydate-selected.laydate-day-prev,
.laydate-theme-grid .laydate-selected.laydate-day-next {
color: #d2d2d2 !important;
}
.laydate-theme-grid .laydate-year-list,
.laydate-theme-grid .laydate-month-list {
margin: 1px 0 0 1px;
}
.laydate-theme-grid .laydate-year-list > li,
.laydate-theme-grid .laydate-month-list > li {
margin: 0 -1px -1px 0;
}
.laydate-theme-grid .laydate-year-list > li {
height: 43px;
line-height: 43px;
}
.laydate-theme-grid .laydate-month-list > li {
height: 71px;
line-height: 71px;
}
.laydate-range-hover{
background-color: var(--global-neutral-color-2) !important;
}
.layui-laydate-content .layui-disabled:hover{
background-color: transparent !important;
}
.laydate-range-inputs{
display: flex;
align-items: center;
border-width: 1px;
border-style: solid;
display: inline-flex;
border-color: var(--input-border-color);
border-radius: var(--input-border-radius);
.range-separator{
margin: 0 5px;
color: var(--global-neutral-color-8);
background-color: transparent;
}
.layui-input-wrapper{
border: none;
box-sizing: border-box;
input{
text-align: center;
padding: 0;
}
}
.layui-input {
border: none;
}
}
.layui-laydate-range{
.laydate-set-ym{
overflow: visible;
white-space: nowrap;
}
.laydate-set-ym .layui-dropdown{
width: auto !important;
}
.time-panel{
.layui-laydate-main{
width: 272px;
display: unset !important;
}
.layui-laydate-preview{
display: none;
}
}
.layui-laydate-content{
.laydate-year-list{
display: flex;
flex-wrap: wrap;
}
}
.layui-laydate-list{
display: flex;
flex-wrap: wrap;
}
}
.layui-laydate-range-datetime{
.layui-laydate-main{
width: 340px;
}
}

View File

@ -0,0 +1,5 @@
import { withInstall, WithInstallType } from "../../utils";
import Component from "./index.vue";
const component: WithInstallType<typeof Component> = withInstall(Component);
export default component;

View File

@ -0,0 +1,417 @@
<template>
<div
:class="['layui-date-picker', { 'layui-date-range-picker': range }]"
:size="size"
>
<lay-dropdown
ref="dropdownRef"
:disabled="disabled"
:autoFitMinWidth="false"
:contentClass="contentClass"
:contentStyle="contentStyle"
updateAtScroll
>
<lay-input
:name="name"
:readonly="readonly"
:placeholder="startPlaceholder"
:prefix-icon="prefixIcon"
:suffix-icon="suffixIcon"
:disabled="disabled"
v-model="dateValue"
v-if="!range"
@change="onChange"
@blur="$emit('blur')"
@focus="$emit('focus')"
:allow-clear="!disabled && allowClear"
:size="size"
@clear="
dateValue = '';
onChange();
"
>
</lay-input>
<div class="laydate-range-inputs" v-else>
<lay-input
:readonly="readonly"
:name="name"
v-model="dateValue[0]"
:placeholder="startPlaceholder"
:disabled="disabled"
@change="onChange"
@blur="$emit('blur')"
@focus="$emit('focus')"
class="start-input"
:size="size"
>
</lay-input>
<span class="range-separator">{{ rangeSeparator }}</span>
<lay-input
:readonly="readonly"
:name="name"
:allow-clear="disabled && allowClear"
:placeholder="endPlaceholder"
v-model="dateValue[1]"
:disabled="disabled"
@change="onChange"
@blur="$emit('blur')"
@focus="$emit('focus')"
class="end-input"
:size="size"
@clear="
dateValue = [];
onChange();
"
>
</lay-input>
</div>
<template #content>
<!-- 日期选择 -->
<DatePanel
v-if="!range && (showPanel === 'date' || showPanel === 'datetime')"
v-model="currentDay"
></DatePanel>
<!-- 时间选择 -->
<TimePanel
v-if="!range && showPanel === 'time'"
v-model="hms"
></TimePanel>
<!-- 年份选择器 -->
<YearPanel
v-if="!range && (showPanel === 'year' || showPanel === 'yearmonth')"
v-model="currentYear"
>
</YearPanel>
<!-- 月份选择器 -->
<MonthPanel
v-if="!range && showPanel === 'month'"
v-model="currentMonth"
></MonthPanel>
<!-- 范围选择 -->
<DateRange
v-if="range && (showPanel === 'date' || showPanel === 'datetime')"
v-model:startTime="rangeValue.first"
v-model:endTime="rangeValue.last"
></DateRange>
<MonthRange
v-if="range && showPanel === 'yearmonth'"
v-model:startTime="rangeValue.first"
v-model:endTime="rangeValue.last"
>
</MonthRange>
</template>
</lay-dropdown>
</div>
</template>
<script lang="ts">
export default {
name: "LayDatePicker",
};
</script>
<script lang="ts" setup>
import "./index.less";
import dayjs from "dayjs";
import { LayIcon } from "@layui/icons-vue";
import LayInput from "../input/index.vue";
import LayDropdown from "../dropdown/index.vue";
import { getMonth, getYear, getDay } from "./day";
import {
ref,
watch,
defineProps,
defineEmits,
reactive,
provide,
StyleValue,
} from "vue";
import DatePanel from "./components/DatePanel.vue";
import TimePanel from "./components/TimePanel.vue";
import YearPanel from "./components/YearPanel.vue";
import MonthPanel from "./components/MonthPanel.vue";
import DateRange from "./components/DateRange.vue";
import MonthRange from "./components/MonthRange.vue";
import { computed } from "@vue/reactivity";
export interface DatePickerProps {
type?: "date" | "datetime" | "year" | "time" | "month" | "yearmonth";
placeholder?: string | string[];
modelValue?: string | number | string[];
disabled?: boolean;
simple?: boolean;
name?: string;
max?: string;
min?: string;
range?: boolean;
rangeSeparator?: string;
readonly?: boolean;
allowClear?: boolean;
size?: "lg" | "md" | "sm" | "xs";
prefixIcon?: string;
suffixIcon?: string;
timestamp?: boolean;
contentClass?: string | Array<string | object> | object;
contentStyle?: StyleValue;
}
const props = withDefaults(defineProps<DatePickerProps>(), {
modelValue: "",
type: "date",
disabled: false,
simple: false,
range: false,
rangeSeparator: "至",
readonly: false,
allowClear: false,
size: "md",
prefixIcon: "layui-icon-date",
suffixIcon: "",
timestamp: false,
});
const startPlaceholder = computed(() => {
if (Array.isArray(props.placeholder)) {
return props.placeholder[0];
}
return props.placeholder;
});
const endPlaceholder = computed(() => {
if (Array.isArray(props.placeholder)) {
return props.placeholder[1];
}
return props.placeholder;
});
const dropdownRef = ref(null);
const $emits = defineEmits(["update:modelValue", "change", "blur", "focus"]);
const hms = ref({
hh: 0,
mm: 0,
ss: 0,
});
const currentYear = ref<number>(0);
const currentMonth = ref<number>(0);
const currentDay = ref<number>(0);
const showPanel = ref<string>("date");
const rangeValue = reactive({ first: "", last: "" });
let unWatch = false;
//
const dateValue = props.range ? ref(["", ""]) : ref("");
const getDateValue = () => {
unWatch = true;
let dayjsVal;
switch (props.type) {
case "date":
dayjsVal =
currentDay.value !== -1
? dayjs(currentDay.value).format("YYYY-MM-DD")
: "";
break;
case "datetime":
dayjsVal =
currentDay.value !== -1
? dayjs(currentDay.value)
.hour(hms.value.hh)
.minute(hms.value.mm)
.second(hms.value.ss)
.format("YYYY-MM-DD HH:mm:ss")
: "";
break;
case "year":
dayjsVal =
currentYear.value !== -1
? dayjs().year(currentYear.value).format("YYYY")
: "";
break;
case "month":
dayjsVal =
currentMonth.value !== -1 ? (currentMonth.value + 1).toString() : "";
break;
case "time":
dayjsVal = dayjs()
.hour(hms.value.hh)
.minute(hms.value.mm)
.second(hms.value.ss)
.format("HH:mm:ss");
break;
case "yearmonth":
dayjsVal =
currentYear.value !== -1 && currentMonth.value !== -1
? dayjs()
.year(currentYear.value)
.month(currentMonth.value)
.format("YYYY-MM")
: "";
break;
default:
dayjsVal =
currentDay.value !== -1
? dayjs(currentDay.value)
.hour(hms.value.hh)
.minute(hms.value.mm)
.second(hms.value.ss)
.format()
: "";
break;
}
dateValue.value = dayjsVal !== "Invalid Date" ? dayjsVal : "";
if (dayjsVal === "Invalid Date") {
unWatch = false;
$emits("update:modelValue", "");
return;
}
if (props.timestamp) {
$emits("update:modelValue", dayjs(dayjsVal).unix() * 1000);
$emits("change", dayjs(dayjsVal).unix() * 1000);
} else {
$emits("update:modelValue", dayjsVal);
$emits("change", dayjsVal);
}
setTimeout(() => {
unWatch = false;
}, 0);
};
const getDateValueByRange = () => {
unWatch = true;
if (rangeValue.first === "" || rangeValue.last === "") {
dateValue.value = ["", ""];
$emits("update:modelValue", dateValue.value);
$emits("change", dateValue.value);
return;
}
let format = "YYYY-MM-DD";
switch (props.type) {
case "date":
format = "YYYY-MM-DD";
break;
case "datetime":
format = "YYYY-MM-DD HH:mm:ss";
break;
case "yearmonth":
format = "YYYY-MM";
break;
}
dateValue.value = [
dayjs(rangeValue.first).format(format),
dayjs(rangeValue.last).format(format),
];
$emits("update:modelValue", dateValue.value);
$emits("change", dateValue.value);
setTimeout(() => {
unWatch = false;
}, 0);
};
//
const ok = () => {
if (!props.range) {
getDateValue();
} else {
getDateValueByRange();
}
if (dropdownRef.value)
// @ts-ignore
dropdownRef.value.hide();
showPanel.value = props.type;
};
//
watch(
() => props.type,
() => {
showPanel.value = props.type;
if (props.type === "yearmonth" && !props.range) {
showPanel.value = "year";
}
},
{ immediate: true }
);
//modelValue
watch(
() => props.modelValue,
() => {
if (unWatch) {
return;
}
let initModelValue: string =
props.range && props.modelValue
? (props.modelValue as string[])[0] || ""
: (props.modelValue as string);
if (props.type === "month" || props.type === "year") {
initModelValue += "";
}
hms.value.hh = isNaN(dayjs(initModelValue).hour())
? 0
: dayjs(initModelValue).hour();
hms.value.mm = isNaN(dayjs(initModelValue).minute())
? 0
: dayjs(initModelValue).minute();
hms.value.ss = isNaN(dayjs(initModelValue).second())
? 0
: dayjs(initModelValue).second();
if (initModelValue.length === 8 && props.type === "time") {
let modelValue = initModelValue;
modelValue = "1970-01-01 " + modelValue;
hms.value.hh = dayjs(modelValue).hour();
hms.value.mm = dayjs(modelValue).minute();
hms.value.ss = dayjs(modelValue).second();
}
currentYear.value = initModelValue ? getYear(initModelValue) : -1;
currentMonth.value = initModelValue ? getMonth(initModelValue) : -1;
currentDay.value = initModelValue ? getDay(initModelValue) : -1;
if (props.type === "date" || props.type === "datetime") {
if (currentYear.value === -1) currentYear.value = dayjs().year();
if (currentMonth.value === -1) currentMonth.value = dayjs().month();
if (props.timestamp) {
currentDay.value = initModelValue
? dayjs(parseInt(initModelValue)).startOf("date").unix() * 1000
: -1;
}
}
rangeValue.first = initModelValue;
rangeValue.last =
props.range && props.modelValue
? (props.modelValue as string[])[1] || ""
: "";
if (!props.range) {
getDateValue();
} else {
getDateValueByRange();
}
},
{ immediate: true }
);
const onChange = () => {
if (dropdownRef.value)
// @ts-ignore
dropdownRef.value.hide();
$emits("update:modelValue", dateValue.value);
};
provide("datePicker", {
currentYear: currentYear,
currentMonth: currentMonth,
currentDay: currentDay,
dateValue: dateValue,
type: props.type,
showPanel: showPanel,
hms: hms,
ok: () => ok(),
getDateValue: () => getDateValue,
range: props.range,
rangeValue: rangeValue,
rangeSeparator: props.rangeSeparator,
simple: props.simple,
timestamp: props.timestamp,
});
</script>

View File

@ -0,0 +1,27 @@
import { Ref } from "vue";
export type DatePickerType = "date" | "datetime" | "year" | "time" | "month";
export type provideType = {
currentYear: Ref;
currentMonth: Ref;
currentDay: Ref;
dateValue: Ref;
hms: Ref;
type: string;
showPanel: Ref;
clear: Function;
now: Function;
ok: Function;
range: boolean;
rangeValue: {
first: string;
last: string;
hover: string;
firstTime: { hh: number; mm: number; ss: number };
lastTime: { hh: number; mm: number; ss: number };
};
rangeSeparator: string;
simple: boolean;
timestamp: boolean;
};

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