Merge branch 'next' into feat-popup-menu

This commit is contained in:
sight
2022-06-30 19:38:03 +08:00
50 changed files with 2541 additions and 1361 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "@layui/layui-vue",
"version": "1.2.3",
"version": "1.2.5",
"author": "就眠儀式",
"license": "MIT",
"description": "a component library for Vue 3 base on layui-vue",

View File

@@ -18,12 +18,12 @@ export interface LayAiffxProps {
}
const props = withDefaults(defineProps<LayAiffxProps>(), {
offset: 0,
position: 'top',
position: "top",
target: () => {
return document.body;
},
});
const emit = defineEmits(['scroll'])
const emit = defineEmits(["scroll"]);
const outWindow = ref(false);
const dom = ref();
@@ -43,7 +43,7 @@ const getStyle = computed(() => {
bottom: "unset",
left: orginOffsetLeft - marginLeft + "px",
};
if (props.position === 'top') {
if (props.position === "top") {
style.top = fixedOffset - marginTop + "px";
} else {
style.bottom = fixedOffset - marginBottom + "px";
@@ -56,7 +56,7 @@ const checkInWindow = () => {
if (dom.value) {
let offsetTop = dom.value.offsetTop;
let scrollTop = props.target?.scrollTop;
if (props.position === 'top') {
if (props.position === "top") {
//top检查 当前元素与容器顶部距离-减去滚动条的高度+容器offsetTop
let result = offsetTop - scrollTop + props.target.offsetTop;
if (result < fixedOffset) {
@@ -82,15 +82,15 @@ const checkInWindow = () => {
}
} else {
if (result < fixedOffset) {
changeScrollTop = scrollTop - result + props.offset
changeScrollTop = scrollTop - result + props.offset;
outWindow.value = true;
}
}
}
emit('scroll', {
emit("scroll", {
targetScroll: scrollTop,
affixed: outWindow.value,
offset: !outWindow.value ? 0 : Math.abs(scrollTop - changeScrollTop)
offset: !outWindow.value ? 0 : Math.abs(scrollTop - changeScrollTop),
});
}
};
@@ -106,14 +106,14 @@ const getDomStyle = (dom: any, attr: string) => {
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
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();

View File

@@ -174,7 +174,7 @@ function findData(orginData: any, level: number) {
const dataContainer = ref<any>([]);
const selectBar = (item: any, selectIndex: number, parentIndex: number) => {
treeData.value[parentIndex].selectIndex = selectIndex;
if (item.children) {
if (item.children && item.children.length > 0) {
treeData.value[parentIndex + 1].selectIndex = null;
treeData.value[parentIndex + 1].data = findData(item.children, 1);
}

View File

@@ -72,11 +72,9 @@ watch([red, green, blue], (newValue) => {
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, () => {

View File

@@ -1,10 +1,12 @@
<template>
<div>
<lay-dropdown ref="dropdownRef" :disabled="props.disabled">
<lay-input :name="name" :value="dateValue || modelValue" readonly>
<template #prefix>
<lay-icon type="layui-icon-date"></lay-icon>
</template>
<lay-input
readonly
:name="name"
:value="dateValue || modelValue"
prefix-icon="layui-icon-date"
>
</lay-input>
<template #content>
<!-- 日期选择 -->

View File

@@ -34,6 +34,7 @@
.layui-dropdown .layui-menu li {
position: relative;
display: flex;
line-height: 26px;
color: rgba(0, 0, 0, 0.8);
font-size: 14px;
@@ -46,7 +47,19 @@
}
.layui-dropdown .layui-menu-body-title {
position: relative;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.layui-dropdown-menu-prefix{
margin-right: 8px;
}
.layui-dropdown-menu-suffix{
margin-left: 15px;
}
.layui-dropdown .layui-line-horizontal{
margin: 0px;
border-color: #EEEEEE;
}

View File

@@ -387,11 +387,15 @@ const handleClick = () => {
}
};
const handleContextMenuClick = () => {
const handleContextMenuClick = (e: Event) => {
if (props.disabled || (openState.value && !props.clickToClose)) {
return;
}
if (triggerMethods.value.includes("contextMenu")) {
e.preventDefault();
if (props.alignPoint) {
hide();
}
toggle();
}
};
@@ -511,7 +515,7 @@ defineExpose({ open, hide, toggle });
@focusout="handleFocusout()"
:class="{ 'layui-dropdown-up': openState }"
>
<div @click="handleClick()" @contextmenu.prevent="handleContextMenuClick()">
<div @click="handleClick()" @contextmenu="handleContextMenuClick">
<slot></slot>
</div>
<dl

View File

@@ -15,9 +15,18 @@ const handleClick = () => {
</script>
<template>
<li>
<div class="layui-menu-body-title" @click="handleClick">
<slot></slot>
</div>
<li
@click="handleClick"
:style="$slots.suffix ? `justify-content: space-between;` : ''"
>
<span class="layui-menu-body-title">
<span v-if="$slots.prefix" class="layui-dropdown-menu-prefix">
<slot name="prefix" />
</span>
<slot />
</span>
<span v-if="$slots.suffix" class="layui-dropdown-menu-suffix">
<slot name="suffix" />
</span>
</li>
</template>

View File

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

View File

@@ -0,0 +1,58 @@
<script lang="ts">
export default {
name: "LayDropdownSubMenu",
};
</script>
<script setup lang="ts">
import { inject, Ref } from "vue";
import LayDropdown from "../dropdown/index.vue";
import LayDropdownMenu from "../dropdownMenu/index.vue";
import LayDropdownMenuItem from "../dropdownMenuItem/index.vue";
import { LayIcon } from "@layui/icons-vue";
import { DropdownPlacement } from "../dropdown/interface";
export type DropdownTrigger = "click" | "hover" | "focus" | "contextMenu";
export interface LayDropdownProps {
trigger?: DropdownTrigger | DropdownTrigger[];
placement?: DropdownPlacement;
disabled?: boolean;
contentOffset?: number;
}
const props = withDefaults(defineProps<LayDropdownProps>(), {
trigger: "hover",
disabled: false,
placement: "right-top",
contentOffset: 2,
});
</script>
<template>
<lay-dropdown
:trigger="trigger"
:placement="placement"
:auto-fit-min-width="false"
:contentOffset="contentOffset"
>
<lay-dropdown-menu-item>
<template v-if="$slots.prefix" #prefix>
<slot name="prefix" />
</template>
<template v-if="$slots.default" #default>
<slot />
</template>
<template #suffix>
<slot name="suffix">
<lay-icon type="layui-icon-right" size="14px"></lay-icon>
</slot>
</template>
</lay-dropdown-menu-item>
<template #content>
<lay-dropdown-menu>
<slot name="content" />
</lay-dropdown-menu>
</template>
</lay-dropdown>
</template>

View File

@@ -25,8 +25,9 @@
border-radius: var(--input-border-radius);
}
.layui-input-wrapper:hover,
.layui-input-wrapper:focus-within {
border-color: var(--global-checked-color);
border-color: #d2d2d2 !important;
}
.layui-input-prefix {
@@ -55,14 +56,6 @@
color: rgba(0, 0, 0, 0.45);
}
.layui-input:hover {
border-color: #eee !important;
}
.layui-input:focus {
border-color: #d2d2d2 !important;
}
.layui-input::-webkit-input-placeholder {
line-height: 1.3;
}

View File

@@ -20,6 +20,7 @@ import {
shallowRef,
} from "vue";
import LayBadge from "../badge/index.vue";
import LayInput from "../input/index.vue";
import LayScroll from "../scroll/index.vue";
import { onClickOutside } from "@vueuse/core";
import { SelectItem } from "../../types";

View File

@@ -0,0 +1,28 @@
<script lang="ts">
import { h, PropType, defineComponent } from "vue";
import { TabData } from "./interface";
export default defineComponent({
name: "RenderTitle",
props: {
tag: {
type: String,
required: false,
default: "span",
},
tabItemData: {
type: Object as PropType<TabData>,
required: false,
},
},
setup(props) {
return props.tabItemData?.slots?.title
? () =>
h(
props.tag,
props.tabItemData?.slots?.title && props.tabItemData?.slots.title()
)
: () => props.tabItemData?.title;
},
});
</script>

View File

@@ -8,6 +8,7 @@ export default {
import "./index.less";
import { LayIcon } from "@layui/icons-vue";
import tabItem from "../tabItem/index.vue";
import RenderTitle from "./renderTitle.vue";
import {
Component,
computed,
@@ -21,8 +22,10 @@ import {
onMounted,
nextTick,
CSSProperties,
reactive,
} from "vue";
import { useResizeObserver } from "@vueuse/core";
import { TabData, TabInjectKey } from "./interface";
export type tabPositionType = "top" | "bottom" | "left" | "right";
@@ -40,6 +43,7 @@ const slot = useSlots();
const slots = slot.default && slot.default();
const childrens: Ref<VNode[]> = ref([]);
const slotsChange = ref(true);
const tabMap = reactive(new Map<number, TabData>());
const setItemInstanceBySlot = function (nodeList: VNode[]) {
nodeList?.map((item) => {
@@ -67,6 +71,32 @@ const active = computed({
},
});
const tabItems = computed(() => {
const tabData: TabData[] = [];
childrens.value.forEach((item) => {
const tab = tabMap.get(item.props?.id);
if (tab) tabData.push(tab);
});
return tabData;
});
const addItem = (id: number, data: any) => {
tabMap.set(id, data);
};
const removeItem = (id: number) => {
tabMap.delete(id);
};
provide(
TabInjectKey,
reactive({
active: active,
addItem,
removeItem,
})
);
const change = function (id: any) {
if (props.beforeLeave && props.beforeLeave(id) === false) {
return;
@@ -294,16 +324,16 @@ provide("slotsChange", slotsChange);
:style="tabBarStyle"
></div>
<li
v-for="(children, index) in childrens"
:key="children.props?.id"
:class="[children.props?.id === active ? 'layui-this' : '']"
@click.stop="change(children.props?.id)"
v-for="(children, index) in tabItems"
:key="children.id"
:class="[children.id === active ? 'layui-this' : '']"
@click.stop="change(children.id)"
>
{{ children.props?.title }}
<RenderTitle :tabItemData="children"></RenderTitle>
<i
v-if="allowClose && children.props?.closable != false"
v-if="allowClose && children.closable != false"
class="layui-icon layui-icon-close layui-unselect layui-tab-close"
@click.stop="close(index, children.props?.id)"
@click.stop="close(index, children.id)"
></i>
</li>
</ul>

View File

@@ -0,0 +1,16 @@
import { Slots } from "vue";
export const TabInjectKey = Symbol("layuiTab");
export interface TabData {
id: string;
title?: string;
closable?: string | boolean;
slots: Slots;
}
export interface TabsContext {
active: string;
addItem: (id: string, data: TabData) => void;
removeItem: (id: string) => void;
}

View File

@@ -5,7 +5,18 @@ export default {
</script>
<script setup lang="ts">
import { withDefaults, inject, Ref } from "vue";
import {
withDefaults,
inject,
Ref,
useSlots,
getCurrentInstance,
computed,
reactive,
onBeforeUnmount,
toRefs,
} from "vue";
import { TabInjectKey, TabsContext } from "../tab/interface";
export interface LayTabItemProps {
id: string;
@@ -17,9 +28,29 @@ const props = withDefaults(defineProps<LayTabItemProps>(), {
closable: true,
});
const instance = getCurrentInstance();
const slots = useSlots();
const active = inject("active");
const slotsChange: Ref<boolean> = inject("slotsChange") as Ref<boolean>;
slotsChange.value = !slotsChange.value;
const tabsCtx = inject<Partial<TabsContext>>(TabInjectKey, {});
const data = reactive({
id: props.id,
title: props.title,
closable: props.closable,
slots: slots,
});
if (instance?.uid) {
tabsCtx.addItem?.(props.id, data);
}
onBeforeUnmount(() => {
if (instance?.uid) {
tabsCtx.removeItem?.(props.id);
}
});
</script>
<template>

View File

@@ -10,8 +10,10 @@ import { computed, ref, useSlots, WritableComputedRef } from "vue";
import LayCheckbox from "../checkbox/index.vue";
import LayDropdown from "../dropdown/index.vue";
import LayTooltip from "../tooltip/index.vue";
import { LayIcon } from "@layui/icons-vue";
export interface LayTableRowProps {
index: number;
indentSize: number;
currentIndentSize: number;
expandSpace: boolean;
@@ -20,6 +22,10 @@ export interface LayTableRowProps {
childrenColumnName?: string;
columns: Recordable[];
checkbox?: boolean;
cellClassName: string | Function;
cellStyle: string | Function;
rowClassName: string | Function;
rowStyle: string | Function;
id: string;
data: any;
}
@@ -35,6 +41,8 @@ const emit = defineEmits([
const props = withDefaults(defineProps<LayTableRowProps>(), {
checkbox: false,
childrenColumnName: "children",
cellStyle: "",
cellClassName: "",
});
const tableSelectedKeys: WritableComputedRef<Recordable[]> = computed({
@@ -75,11 +83,51 @@ const handleExpand = () => {
isExpand.value = !isExpand.value;
};
const renderCellStyle = (
row: any,
column: any,
rowIndex: number,
columnIndex: number
) => {
if (typeof props.cellStyle === "string") {
return props.cellStyle;
}
return props.cellStyle(row, column, rowIndex, columnIndex);
};
const renderCellClassName = (
row: any,
column: any,
rowIndex: number,
columnIndex: number
) => {
if (typeof props.cellClassName === "string") {
return props.cellClassName;
}
return props.cellClassName(row, column, rowIndex, columnIndex);
};
const renderRowStyle = (data: any, index: number) => {
if (typeof props.rowStyle === "string") {
return props.rowStyle;
}
return props.rowStyle(data, index);
};
const renderRowClassName = (data: any, index: number) => {
if (typeof props.rowClassName === "string") {
return props.rowClassName;
}
return props.rowClassName(data, index);
};
const childrenIndentSize = props.currentIndentSize + props.indentSize;
</script>
<template>
<tr
:style="[renderRowStyle(data, index)]"
:class="[renderRowClassName(data, index)]"
@click.stop="rowClick(data, $event)"
@dblclick.stop="rowDoubleClick(data, $event)"
@contextmenu.stop="contextmenu(data, $event)"
@@ -96,21 +144,25 @@ const childrenIndentSize = props.currentIndentSize + props.indentSize;
</td>
<!-- 数据列 -->
<template v-for="(column, index) in columns" :key="column">
<template v-for="(column, columnIndex) in columns" :key="columnIndex">
<!-- 展示否 -->
<template v-if="tableColumnKeys.includes(column.key)">
<!-- 插槽列 -->
<template v-if="column.customSlot">
<td
class="layui-table-cell"
:style="{
textAlign: column.align,
whiteSpace: column.ellipsisTooltip ? 'nowrap' : 'normal',
}"
:style="[
{
textAlign: column.align,
whiteSpace: column.ellipsisTooltip ? 'nowrap' : 'normal',
},
renderCellStyle(data, column, index, columnIndex),
]"
:class="[renderCellClassName(data, column, index, columnIndex)]"
>
<!-- 树表占位与缩进 -->
<span
v-if="expandSpace && index === 0"
v-if="expandSpace && columnIndex === 0"
:style="{ 'margin-right': currentIndentSize + 'px' }"
></span>
@@ -119,13 +171,15 @@ const childrenIndentSize = props.currentIndentSize + props.indentSize;
expandSpace &&
!data[childrenColumnName] &&
!slot.expand &&
index === 0
columnIndex === 0
"
class="layui-table-cell-expand-icon-spaced"
></span>
<lay-icon
v-if="(slot.expand || data[childrenColumnName]) && index === 0"
v-if="
(slot.expand || data[childrenColumnName]) && columnIndex === 0
"
class="layui-table-cell-expand-icon"
:type="expandIconType"
@click="handleExpand"
@@ -147,14 +201,18 @@ const childrenIndentSize = props.currentIndentSize + props.indentSize;
<template v-if="column.key in data">
<td
class="layui-table-cell"
:style="{
textAlign: column.align,
whiteSpace: column.ellipsisTooltip ? 'nowrap' : 'normal',
}"
:style="[
{
textAlign: column.align,
whiteSpace: column.ellipsisTooltip ? 'nowrap' : 'normal',
},
renderCellStyle(data, column, index, columnIndex),
]"
:class="[renderCellClassName(data, column, index, columnIndex)]"
>
<!-- 树表占位与缩进 -->
<span
v-if="expandSpace && index === 0"
v-if="expandSpace && columnIndex === 0"
:style="{ 'margin-right': currentIndentSize + 'px' }"
></span>
@@ -163,13 +221,15 @@ const childrenIndentSize = props.currentIndentSize + props.indentSize;
expandSpace &&
!data[childrenColumnName] &&
!slot.expand &&
index === 0
columnIndex === 0
"
class="layui-table-cell-expand-icon-spaced"
></span>
<lay-icon
v-if="(slot.expand || data[childrenColumnName]) && index === 0"
v-if="
(slot.expand || data[childrenColumnName]) && columnIndex === 0
"
class="layui-table-cell-expand-icon"
:type="expandIconType"
@click="handleExpand"
@@ -198,11 +258,12 @@ const childrenIndentSize = props.currentIndentSize + props.indentSize;
<!-- 树形结构 -->
<template v-if="data[childrenColumnName] && isExpand">
<template
v-for="(children, index) in data[childrenColumnName]"
:key="index"
v-for="(children, childrenIndex) in data[childrenColumnName]"
:key="childrenIndex"
>
<table-row
:id="id"
:index="childrenIndex"
:data="children"
:columns="columns"
:indent-size="indentSize"
@@ -210,6 +271,10 @@ const childrenIndentSize = props.currentIndentSize + props.indentSize;
:checkbox="checkbox"
:tableColumnKeys="tableColumnKeys"
:expandSpace="expandSpace"
:cellStyle="cellStyle"
:cellClassName="cellClassName"
:rowStyle="rowStyle"
:rowClassName="rowClassName"
@row="rowClick"
@row-double="rowDoubleClick"
@contextmenu="contextmenu"

View File

@@ -30,6 +30,10 @@ export interface LayTableProps {
height?: number;
maxHeight?: string;
even?: boolean;
rowClassName?: string | Function;
cellClassName?: string | Function;
rowStyle?: string | Function;
cellStyle?: string | Function;
}
const props = withDefaults(defineProps<LayTableProps>(), {
@@ -41,6 +45,10 @@ const props = withDefaults(defineProps<LayTableProps>(), {
selectedKeys: () => [],
maxHeight: "auto",
even: false,
rowClassName: "",
cellClassName: "",
rowStyle: "",
cellStyle: "",
});
const tableId = uuidv4();
@@ -384,9 +392,10 @@ props.dataSource.map((value: any) => {
</colgroup>
<tbody>
<!-- 渲染 -->
<template v-for="data in tableDataSource" :key="data">
<template v-for="(data, index) in tableDataSource" :key="data">
<table-row
:id="id"
:index="index"
:data="data"
:columns="columns"
:checkbox="checkbox"
@@ -394,6 +403,10 @@ props.dataSource.map((value: any) => {
:currentIndentSize="currentIndentSize"
:tableColumnKeys="tableColumnKeys"
:expandSpace="childrenExpandSpace"
:cellStyle="cellStyle"
:cellClassName="cellClassName"
:rowStyle="rowStyle"
:rowClassName="rowClassName"
@row="rowClick"
@row-double="rowDoubleClick"
@contextmenu="contextmenu"

View File

@@ -12,6 +12,7 @@
import "./index.less";
import LayPopper from "../popper/index.vue";
import { defineComponent, ref } from "vue";
import { useEventListener } from "@vueuse/core";
export default defineComponent({
name: "LayTooltip",
components: {
@@ -45,7 +46,7 @@ export default defineComponent({
},
setup() {
const isMounted = ref(false);
const refTooltip = ref(null);
const refTooltip = ref<any>(null);
return {
isMounted,
refTooltip,
@@ -57,11 +58,20 @@ export default defineComponent({
},
},
mounted() {
if (this.isAutoShow) {
useEventListener("resize", () => {
this.setEllipsis();
});
}
this.$nextTick(() => {
this.setEllipsis();
});
},
methods: {
setEllipsis() {
if (this.refTooltip) {
if (
this.refTooltip.offsetWidth >= this.refTooltip.firstChild.offsetWidth
) {
let tooltipHtml = this.refTooltip;
if (tooltipHtml.offsetWidth >= tooltipHtml.firstChild.offsetWidth) {
this.isMounted = false;
} else {
this.isMounted = true;
@@ -69,7 +79,7 @@ export default defineComponent({
} else {
this.isMounted = true;
}
});
},
},
});
</script>

View File

@@ -57,6 +57,7 @@ import LayRate from "./component/rate/index";
import LayDropdown from "./component/dropdown/index";
import LayDropdownMenu from "./component/dropdownMenu/index";
import LayDropdownMenuItem from "./component/dropdownMenuItem/index";
import LayDropdownSubMenu from "./component/dropdownSubMenu/index";
import LayTab from "./component/tab/index";
import LayTabItem from "./component/tabItem/index";
import LayTree from "./component/tree/index";
@@ -140,6 +141,7 @@ const components: Record<string, Plugin> = {
LayDropdown,
LayDropdownMenu,
LayDropdownMenuItem,
LayDropdownSubMenu,
LayTab,
LayTabItem,
LayIconPicker,
@@ -232,6 +234,7 @@ export {
LayDropdown,
LayDropdownMenu,
LayDropdownMenuItem,
LayDropdownSubMenu,
LayTab,
LayTabItem,
LayIconPicker,

View File

@@ -13,6 +13,7 @@ import {
enable as enableDarkMode,
disable as disableDarkMode,
auto as followSystemColorScheme,
setFetchMethod,
} from "@umijs/ssr-darkreader";
export interface LayConfigProviderProps {
@@ -71,6 +72,9 @@ const changeTheme = (theme: string) => {
};
Object.assign(defaultPartial, props.darkPartial);
if (theme === "dark") {
if (window) {
setFetchMethod(window.fetch);
}
enableDarkMode(defaultPartial, defaultFixes);
} else if (theme === "light") {
disableDarkMode();