init
This commit is contained in:
commit
0a63adba99
23
.gitignore
vendored
Normal file
23
.gitignore
vendored
Normal 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
78
README.md
Normal 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
58
package.json
Normal 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
49
script/build.all.ts
Normal 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
123
script/build.es.ts
Normal 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
49
script/build.umd.ts
Normal 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
25
shims-vue.d.ts
vendored
Normal 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;
|
||||||
|
};
|
||||||
|
}
|
23
src/component/_components/renderFunction.ts
Normal file
23
src/component/_components/renderFunction.ts
Normal 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);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
40
src/component/_components/teleportWrapper.vue
Normal file
40
src/component/_components/teleportWrapper.vue
Normal 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>
|
5
src/component/affix/index.less
Normal file
5
src/component/affix/index.less
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
.layui-affix{
|
||||||
|
display: block;
|
||||||
|
z-index: 999;
|
||||||
|
transition: all 0.3s ease-in-out;
|
||||||
|
}
|
5
src/component/affix/index.ts
Normal file
5
src/component/affix/index.ts
Normal 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;
|
137
src/component/affix/index.vue
Normal file
137
src/component/affix/index.vue
Normal 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>
|
51
src/component/avatar/index.less
Normal file
51
src/component/avatar/index.less
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
5
src/component/avatar/index.ts
Normal file
5
src/component/avatar/index.ts
Normal 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;
|
45
src/component/avatar/index.vue
Normal file
45
src/component/avatar/index.vue
Normal 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>
|
5
src/component/avatarList/index.ts
Normal file
5
src/component/avatarList/index.ts
Normal 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;
|
13
src/component/avatarList/index.vue
Normal file
13
src/component/avatarList/index.vue
Normal 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>
|
35
src/component/backTop/index.less
Normal file
35
src/component/backTop/index.less
Normal 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;
|
||||||
|
}
|
5
src/component/backTop/index.ts
Normal file
5
src/component/backTop/index.ts
Normal 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;
|
223
src/component/backTop/index.vue
Normal file
223
src/component/backTop/index.vue
Normal 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>
|
0
src/component/backTop/interface.ts
Normal file
0
src/component/backTop/interface.ts
Normal file
82
src/component/badge/index.less
Normal file
82
src/component/badge/index.less
Normal 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;
|
||||||
|
}
|
5
src/component/badge/index.ts
Normal file
5
src/component/badge/index.ts
Normal 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;
|
48
src/component/badge/index.vue
Normal file
48
src/component/badge/index.vue
Normal 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>
|
0
src/component/badge/interface.ts
Normal file
0
src/component/badge/interface.ts
Normal file
8
src/component/body/index.less
Normal file
8
src/component/body/index.less
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
.layui-body {
|
||||||
|
display: block;
|
||||||
|
flex: 1;
|
||||||
|
overflow: auto;
|
||||||
|
height: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
min-height: 300px;
|
||||||
|
}
|
5
src/component/body/index.ts
Normal file
5
src/component/body/index.ts
Normal 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;
|
15
src/component/body/index.vue
Normal file
15
src/component/body/index.vue
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
name: "LayBody",
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import "./index.less";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="layui-body">
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
</template>
|
29
src/component/breadcrumb/index.less
Normal file
29
src/component/breadcrumb/index.less
Normal 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);
|
||||||
|
}
|
5
src/component/breadcrumb/index.ts
Normal file
5
src/component/breadcrumb/index.ts
Normal 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;
|
26
src/component/breadcrumb/index.vue
Normal file
26
src/component/breadcrumb/index.vue
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
name: "LayBreadcrumb",
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import "./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>
|
0
src/component/breadcrumb/interface.ts
Normal file
0
src/component/breadcrumb/interface.ts
Normal file
5
src/component/breadcrumbItem/index.ts
Normal file
5
src/component/breadcrumbItem/index.ts
Normal 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;
|
24
src/component/breadcrumbItem/index.vue
Normal file
24
src/component/breadcrumbItem/index.vue
Normal 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>
|
0
src/component/breadcrumbItem/interface.ts
Normal file
0
src/component/breadcrumbItem/interface.ts
Normal file
120
src/component/button/index.less
Normal file
120
src/component/button/index.less
Normal 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;
|
||||||
|
}
|
5
src/component/button/index.ts
Normal file
5
src/component/button/index.ts
Normal 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;
|
88
src/component/button/index.vue
Normal file
88
src/component/button/index.vue
Normal 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>
|
8
src/component/button/interface.ts
Normal file
8
src/component/button/interface.ts
Normal 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,
|
||||||
|
};
|
12
src/component/buttonContainer/index.less
Normal file
12
src/component/buttonContainer/index.less
Normal 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;
|
||||||
|
}
|
5
src/component/buttonContainer/index.ts
Normal file
5
src/component/buttonContainer/index.ts
Normal 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;
|
15
src/component/buttonContainer/index.vue
Normal file
15
src/component/buttonContainer/index.vue
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
name: "LayButtonContainer",
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import "./index.less";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="layui-btn-container">
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
</template>
|
42
src/component/buttonGroup/index.less
Normal file
42
src/component/buttonGroup/index.less
Normal 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;
|
||||||
|
}
|
5
src/component/buttonGroup/index.ts
Normal file
5
src/component/buttonGroup/index.ts
Normal 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;
|
15
src/component/buttonGroup/index.vue
Normal file
15
src/component/buttonGroup/index.vue
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
name: "LayButtonGroup",
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import "./index.less";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="layui-btn-group">
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
</template>
|
46
src/component/card/index.less
Normal file
46
src/component/card/index.less
Normal 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);
|
||||||
|
}
|
5
src/component/card/index.ts
Normal file
5
src/component/card/index.ts
Normal 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;
|
49
src/component/card/index.vue
Normal file
49
src/component/card/index.vue
Normal 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>
|
1
src/component/card/interface.ts
Normal file
1
src/component/card/interface.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export type CardShadow = "always" | "hover" | "never";
|
276
src/component/carousel/index.less
Normal file
276
src/component/carousel/index.less
Normal 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;
|
||||||
|
}
|
5
src/component/carousel/index.ts
Normal file
5
src/component/carousel/index.ts
Normal 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;
|
197
src/component/carousel/index.vue
Normal file
197
src/component/carousel/index.vue
Normal 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>
|
5
src/component/carouselItem/index.ts
Normal file
5
src/component/carouselItem/index.ts
Normal 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;
|
110
src/component/carouselItem/index.vue
Normal file
110
src/component/carouselItem/index.vue
Normal 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>
|
101
src/component/cascader/index.less
Normal file
101
src/component/cascader/index.less
Normal 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;
|
||||||
|
}
|
5
src/component/cascader/index.ts
Normal file
5
src/component/cascader/index.ts
Normal 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;
|
311
src/component/cascader/index.vue
Normal file
311
src/component/cascader/index.vue
Normal 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>
|
1
src/component/cascader/interface.ts
Normal file
1
src/component/cascader/interface.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export type CascaderSize = "lg" | "md" | "sm" | "xs";
|
187
src/component/checkbox/index.less
Normal file
187
src/component/checkbox/index.less
Normal 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;
|
||||||
|
}
|
5
src/component/checkbox/index.ts
Normal file
5
src/component/checkbox/index.ts
Normal 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;
|
145
src/component/checkbox/index.vue
Normal file
145
src/component/checkbox/index.vue
Normal 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>
|
1
src/component/checkbox/interface.ts
Normal file
1
src/component/checkbox/interface.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export type CheckboxSize = "lg" | "md" | "sm" | "xs";
|
5
src/component/checkboxGroup/index.ts
Normal file
5
src/component/checkboxGroup/index.ts
Normal 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;
|
59
src/component/checkboxGroup/index.vue
Normal file
59
src/component/checkboxGroup/index.vue
Normal 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>
|
0
src/component/checkboxGroup/interface.ts
Normal file
0
src/component/checkboxGroup/interface.ts
Normal file
5
src/component/col/index.ts
Normal file
5
src/component/col/index.ts
Normal 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;
|
41
src/component/col/index.vue
Normal file
41
src/component/col/index.vue
Normal 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>
|
40
src/component/collapse/index.less
Normal file
40
src/component/collapse/index.less
Normal 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;
|
||||||
|
}
|
5
src/component/collapse/index.ts
Normal file
5
src/component/collapse/index.ts
Normal 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;
|
46
src/component/collapse/index.vue
Normal file
46
src/component/collapse/index.vue
Normal 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>
|
0
src/component/collapse/interface.ts
Normal file
0
src/component/collapse/interface.ts
Normal file
5
src/component/collapseItem/index.ts
Normal file
5
src/component/collapseItem/index.ts
Normal 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;
|
77
src/component/collapseItem/index.vue
Normal file
77
src/component/collapseItem/index.vue
Normal 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>
|
0
src/component/collapseItem/interface.ts
Normal file
0
src/component/collapseItem/interface.ts
Normal file
43
src/component/colorPicker/EyeDropper.vue
Normal file
43
src/component/colorPicker/EyeDropper.vue
Normal 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>
|
389
src/component/colorPicker/index.less
Normal file
389
src/component/colorPicker/index.less
Normal 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;
|
||||||
|
}
|
5
src/component/colorPicker/index.ts
Normal file
5
src/component/colorPicker/index.ts
Normal 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;
|
512
src/component/colorPicker/index.vue
Normal file
512
src/component/colorPicker/index.vue
Normal 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;
|
||||||
|
// hsv转化为rgb
|
||||||
|
let { r, g, b } = hsv2rgb(hue.value, saturation.value, value.value);
|
||||||
|
red.value = r;
|
||||||
|
green.value = g;
|
||||||
|
blue.value = b;
|
||||||
|
// 移动背景板圆圈
|
||||||
|
pointStyle.value = `top: ${y}px;left: ${x}px;`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function mousedownSV(e: any) {
|
||||||
|
// 鼠标按下计算饱和度和亮度并添加事件
|
||||||
|
handleChangeSV(e);
|
||||||
|
// 添加整个页面的鼠标事件
|
||||||
|
window.addEventListener("mousemove", handleChangeSV);
|
||||||
|
window.addEventListener("mouseup", mouseupSV);
|
||||||
|
}
|
||||||
|
|
||||||
|
function mouseupSV(e: any) {
|
||||||
|
// 鼠标松开后移除事件
|
||||||
|
window.removeEventListener("mousemove", handleChangeSV);
|
||||||
|
window.removeEventListener("mouseup", mouseupSV);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 色调
|
||||||
|
function handleChangeHue(e: any) {
|
||||||
|
// @ts-ignore
|
||||||
|
let w = hueSlider.value.clientWidth;
|
||||||
|
// @ts-ignore
|
||||||
|
let x = e.pageX - saturationValue.value.getBoundingClientRect().left;
|
||||||
|
x = x < w && x > 0 ? x : x > w ? w : 0;
|
||||||
|
// 计算色调
|
||||||
|
hue.value = Math.floor((x / w) * 360 + 0.5);
|
||||||
|
// hsv转化为rgb
|
||||||
|
let { r, g, b } = hsv2rgb(hue.value, saturation.value, value.value);
|
||||||
|
red.value = r;
|
||||||
|
green.value = g;
|
||||||
|
blue.value = b;
|
||||||
|
// 移动滑块
|
||||||
|
hueSliderStyle.value = `left: ${x >= w - 6 ? w - 6 : x}px;`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function mousedownHue(e: any) {
|
||||||
|
handleChangeHue(e);
|
||||||
|
window.addEventListener("mousemove", handleChangeHue);
|
||||||
|
window.addEventListener("mouseup", mouseupHue);
|
||||||
|
}
|
||||||
|
|
||||||
|
function mouseupHue(e: any) {
|
||||||
|
window.removeEventListener("mousemove", handleChangeHue);
|
||||||
|
window.removeEventListener("mouseup", mouseupHue);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 透明度
|
||||||
|
function handleChangeAlpha(e: any) {
|
||||||
|
// @ts-ignore
|
||||||
|
let w = alphaSlider.value.clientWidth;
|
||||||
|
// @ts-ignore
|
||||||
|
let x = e.pageX - saturationValue.value.getBoundingClientRect().left;
|
||||||
|
x = x < w && x > 0 ? x : x > w ? w : 0;
|
||||||
|
// 计算透明度
|
||||||
|
alpha.value = Math.floor((x / w) * 100 + 0.5) / 100;
|
||||||
|
// 移动滑块
|
||||||
|
alphaSliderStyle.value = `left: ${x >= w - 6 ? w - 6 : x}px;`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function mousedownAlpha(e: any) {
|
||||||
|
handleChangeAlpha(e);
|
||||||
|
window.addEventListener("mousemove", handleChangeAlpha);
|
||||||
|
window.addEventListener("mouseup", mouseupAlpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
function mouseupAlpha(e: any) {
|
||||||
|
window.removeEventListener("mousemove", handleChangeAlpha);
|
||||||
|
window.removeEventListener("mouseup", mouseupAlpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
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>
|
12
src/component/container/index.less
Normal file
12
src/component/container/index.less
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
.layui-container {
|
||||||
|
position: relative;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0 15px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layui-fluid {
|
||||||
|
position: relative;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0 15px;
|
||||||
|
}
|
5
src/component/container/index.ts
Normal file
5
src/component/container/index.ts
Normal 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;
|
29
src/component/container/index.vue
Normal file
29
src/component/container/index.vue
Normal 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>
|
5
src/component/countUp/index.ts
Normal file
5
src/component/countUp/index.ts
Normal 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;
|
104
src/component/countUp/index.vue
Normal file
104
src/component/countUp/index.vue
Normal 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>
|
148
src/component/datePicker/components/DatePanel.vue
Normal file
148
src/component/datePicker/components/DatePanel.vue
Normal 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>
|
350
src/component/datePicker/components/DateRange.vue
Normal file
350
src/component/datePicker/components/DateRange.vue
Normal 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>
|
126
src/component/datePicker/components/MonthPanel.vue
Normal file
126
src/component/datePicker/components/MonthPanel.vue
Normal 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>
|
295
src/component/datePicker/components/MonthRange.vue
Normal file
295
src/component/datePicker/components/MonthRange.vue
Normal 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>
|
40
src/component/datePicker/components/PanelFoot.vue
Normal file
40
src/component/datePicker/components/PanelFoot.vue
Normal 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>
|
164
src/component/datePicker/components/TimePanel.vue
Normal file
164
src/component/datePicker/components/TimePanel.vue
Normal 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>
|
146
src/component/datePicker/components/YearPanel.vue
Normal file
146
src/component/datePicker/components/YearPanel.vue
Normal 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>
|
169
src/component/datePicker/components/components/DateContent.vue
Normal file
169
src/component/datePicker/components/components/DateContent.vue
Normal 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>
|
105
src/component/datePicker/day.ts
Normal file
105
src/component/datePicker/day.ts
Normal 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,
|
||||||
|
};
|
BIN
src/component/datePicker/font/iconfont.eot
Normal file
BIN
src/component/datePicker/font/iconfont.eot
Normal file
Binary file not shown.
45
src/component/datePicker/font/iconfont.svg
Normal file
45
src/component/datePicker/font/iconfont.svg
Normal 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="" 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="" 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="" 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="" 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 |
BIN
src/component/datePicker/font/iconfont.ttf
Normal file
BIN
src/component/datePicker/font/iconfont.ttf
Normal file
Binary file not shown.
BIN
src/component/datePicker/font/iconfont.woff
Normal file
BIN
src/component/datePicker/font/iconfont.woff
Normal file
Binary file not shown.
579
src/component/datePicker/index.less
Normal file
579
src/component/datePicker/index.less
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
5
src/component/datePicker/index.ts
Normal file
5
src/component/datePicker/index.ts
Normal 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;
|
417
src/component/datePicker/index.vue
Normal file
417
src/component/datePicker/index.vue
Normal 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>
|
27
src/component/datePicker/interface.ts
Normal file
27
src/component/datePicker/interface.ts
Normal 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
Loading…
Reference in New Issue
Block a user