This commit is contained in:
Theluyuan 2022-12-09 16:43:03 +08:00
parent 093de34571
commit c696834501
24 changed files with 422 additions and 206 deletions

View File

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

View File

@ -31,7 +31,9 @@
:size="size" :size="size"
@clear="onClear" @clear="onClear"
></lay-input> ></lay-input>
<slot v-else></slot> <div class="slot-area" v-else>
<slot></slot>
</div>
<template #content> <template #content>
<div class="layui-cascader-panel"> <div class="layui-cascader-panel">
@ -137,12 +139,19 @@ watch(
watch( watch(
() => props.modelValue, () => props.modelValue,
() => { () => {
if (props.modelValue === null || props.modelValue === "") { if (watchModelValue.value) {
onClear(); if (props.modelValue === null || props.modelValue === "") {
onClear();
} else {
updateDisplayByModelValue();
}
setTimeout(() => {
watchModelValue.value = true;
}, 0);
} }
} }
); );
const watchModelValue = ref(true);
const treeData = ref<any>([]); const treeData = ref<any>([]);
const initTreeData = () => { const initTreeData = () => {
let treeLvNum = getMaxFloor(props.options); let treeLvNum = getMaxFloor(props.options);
@ -159,31 +168,28 @@ const initTreeData = () => {
}; };
} }
} }
updateDisplayByModelValue();
};
function updateDisplayByModelValue() {
if (props.modelValue) { if (props.modelValue) {
try { try {
let valueData = props.modelValue.split(props.decollator); let valueData = props.modelValue.split(props.decollator);
let data: any[] = []; for (let index = 0; index < valueData.length; index++) {
for (let index = 0; index < treeData.value.length; index++) { const element = valueData[index];
const element = treeData.value[index]; let selectIndex = treeData.value[index].data.findIndex(
const nowValue = valueData[index]; (e: { value: string }) => e.value === element
for (let i = 0; i < element.length; i++) { );
const ele = element[i]; if (selectIndex == -1) {
if (nowValue === ele.value) { break;
data.push(ele);
element.selectIndex = i;
}
} }
selectBar(treeData.value[index].data[selectIndex], selectIndex, index);
} }
displayValue.value = data
.map((e) => {
return e.label;
})
.join(` ${props.decollator} `);
} catch (error) { } catch (error) {
console.error(error); console.error(error);
} }
} }
}; }
function getMaxFloor(treeData: any) { function getMaxFloor(treeData: any) {
//let floor = 0; //let floor = 0;
@ -274,6 +280,7 @@ const selectBar = (item: any, selectIndex: number, parentIndex: number) => {
return e.value; return e.value;
}) })
.join(props.decollator); .join(props.decollator);
watchModelValue.value = false;
emit("update:modelValue", value); emit("update:modelValue", value);
let evt = { let evt = {
display: displayValue.value, display: displayValue.value,

View File

@ -79,7 +79,9 @@
width: 29px; width: 29px;
height: 28px; height: 28px;
position: absolute; position: absolute;
border: 1px solid var(--global-neutral-color-6); border-top: 1px solid var(--global-neutral-color-6);
border-bottom: 1px solid var(--global-neutral-color-6);
border-right: 1px solid var(--global-neutral-color-6);
border-radius: 0 2px 2px 0; border-radius: 0 2px 2px 0;
color: #fff; color: #fff;
font-size: 20px; font-size: 20px;

View File

@ -20,7 +20,6 @@ import {
cloneVNode, cloneVNode,
useAttrs, useAttrs,
StyleValue, StyleValue,
PropType,
} from "vue"; } from "vue";
import { import {
computed, computed,
@ -45,9 +44,8 @@ import {
DropdownContext, DropdownContext,
} from "./interface"; } from "./interface";
import TeleportWrapper from "../_components/teleportWrapper.vue"; import TeleportWrapper from "../_components/teleportWrapper.vue";
import { useFirstElement, isScrollElement, getScrollElements } from "./util"; import { useFirstElement, getScrollElements, transformPlacement } from "./util";
import RenderFunction, { RenderFunc } from "../_components/renderFunction"; import RenderFunction from "../_components/renderFunction";
import { transformPlacement } from "./util";
export type DropdownTrigger = "click" | "hover" | "focus" | "contextMenu"; export type DropdownTrigger = "click" | "hover" | "focus" | "contextMenu";
@ -105,7 +103,6 @@ const dropdownCtx = inject<DropdownContext | undefined>(
undefined undefined
); );
const { children, firstElement: dropdownRef } = useFirstElement(); const { children, firstElement: dropdownRef } = useFirstElement();
//const dropdownRef = shallowRef<HTMLElement | undefined>();
const contentRef = shallowRef<HTMLElement | undefined>(); const contentRef = shallowRef<HTMLElement | undefined>();
const contentStyle = ref<CSSProperties>({}); const contentStyle = ref<CSSProperties>({});
const { width: windowWidth, height: windowHeight } = useWindowSize(); const { width: windowWidth, height: windowHeight } = useWindowSize();

View File

@ -1,27 +1,12 @@
import { DropdownPlacement } from "./interface"; import { DropdownPlacement } from "./interface";
import { Component, onMounted, onUpdated, ref, VNode, VNodeTypes } from "vue"; import { Component, onMounted, onUpdated, ref, VNode, VNodeTypes } from "vue";
import { isArrayChildren, isComponent, isElement } from "../../utils";
export interface SlotChildren { export interface SlotChildren {
value?: VNode[]; value?: VNode[];
} }
// Quoted from arco-vue
// https://github.com/arco-design/arco-design-vue/blob/main/packages/web-vue/components/_utils/vue-utils.ts
export enum ShapeFlags {
ELEMENT = 1,
FUNCTIONAL_COMPONENT = 1 << 1,
STATEFUL_COMPONENT = 1 << 2,
COMPONENT = ShapeFlags.STATEFUL_COMPONENT | ShapeFlags.FUNCTIONAL_COMPONENT,
TEXT_CHILDREN = 1 << 3,
ARRAY_CHILDREN = 1 << 4,
SLOTS_CHILDREN = 1 << 5,
TELEPORT = 1 << 6,
SUSPENSE = 1 << 7,
COMPONENT_SHOULD_KEEP_ALIVE = 1 << 8,
COMPONENT_KEPT_ALIVE = 1 << 9,
}
export const isScrollElement = (element: HTMLElement) => { export const isScrollElement = (element: HTMLElement) => {
return ( return (
element.scrollHeight > element.offsetHeight || element.scrollHeight > element.offsetHeight ||
@ -41,24 +26,6 @@ export const getScrollElements = (container: HTMLElement | undefined) => {
return scrollElements; return scrollElements;
}; };
export const isElement = (vn: VNode) => {
return Boolean(vn && vn.shapeFlag & ShapeFlags.ELEMENT);
};
export const isComponent = (
vn: VNode,
type?: VNodeTypes
): type is Component => {
return Boolean(vn && vn.shapeFlag & ShapeFlags.COMPONENT);
};
export const isArrayChildren = (
vn: VNode,
children: VNode["children"]
): children is VNode[] => {
return Boolean(vn && vn.shapeFlag & ShapeFlags.ARRAY_CHILDREN);
};
export const getChildrenArray = (vn: VNode): VNode[] | undefined => { export const getChildrenArray = (vn: VNode): VNode[] | undefined => {
if (isArrayChildren(vn, vn.children)) { if (isArrayChildren(vn, vn.children)) {
return vn.children; return vn.children;
@ -140,6 +107,5 @@ export const transformPlacement = (
placementMap[separated[1]] || separated[1] placementMap[separated[1]] || separated[1]
}` as DropdownPlacement; }` as DropdownPlacement;
} }
return placement; return placement;
}; };

View File

@ -53,6 +53,7 @@
font-size: 14px; font-size: 14px;
color: rgba(0, 0, 0, 0.5); color: rgba(0, 0, 0, 0.5);
transition: all 0.3s; transition: all 0.3s;
display: inline-block;
} }
.layui-iconpicker-down .layui-iconpicker-suffix .layui-icon-down { .layui-iconpicker-down .layui-iconpicker-suffix .layui-icon-down {
@ -151,6 +152,15 @@
display: none; display: none;
} }
.layui-icon-picker-clear {
color: rgba(0, 0, 0, 0.45);
padding: 0px 0px 0px 10px;
}
.layui-icon-picker-clear:hover {
opacity: 0.6;
}
.layui-colorpicker-disabled { .layui-colorpicker-disabled {
opacity: 0.6; opacity: 0.6;
} }
@ -159,3 +169,7 @@
.layui-colorpicker-disabled * { .layui-colorpicker-disabled * {
cursor: not-allowed !important; cursor: not-allowed !important;
} }
.transform{
transform: rotate(180deg);
}

View File

@ -17,6 +17,7 @@ export interface IconPickerProps {
modelValue?: string; modelValue?: string;
disabled?: boolean; disabled?: boolean;
showSearch?: boolean; showSearch?: boolean;
allowClear?: boolean;
contentClass?: string | Array<string | object> | object; contentClass?: string | Array<string | object> | object;
contentStyle?: StyleValue; contentStyle?: StyleValue;
} }
@ -30,6 +31,7 @@ const props = withDefaults(defineProps<IconPickerProps>(), {
const emit = defineEmits(["update:modelValue", "change"]); const emit = defineEmits(["update:modelValue", "change"]);
const selectedIcon = computed(() => props.modelValue); const selectedIcon = computed(() => props.modelValue);
const dropdownRef = ref<any>(null); const dropdownRef = ref<any>(null);
const openState = ref<boolean>(false);
const selectIcon = function (icon: string): void { const selectIcon = function (icon: string): void {
emit("update:modelValue", icon); emit("update:modelValue", icon);
@ -37,6 +39,14 @@ const selectIcon = function (icon: string): void {
dropdownRef.value?.hide(); dropdownRef.value?.hide();
}; };
const onClear = function (): void {
emit("update:modelValue", "");
};
const hasContent = computed(() => {
return props.modelValue != null && props.modelValue != "";
});
const icones: Ref = ref([]); const icones: Ref = ref([]);
const total: Ref<number> = ref(icons.length); const total: Ref<number> = ref(icons.length);
const totalPage: Ref<number> = ref(total.value / 12); const totalPage: Ref<number> = ref(total.value / 12);
@ -143,6 +153,8 @@ const searchList = (str: string, container: any) => {
:disabled="disabled" :disabled="disabled"
:contentClass="contentClass" :contentClass="contentClass"
:contentStyle="contentStyle" :contentStyle="contentStyle"
@hide="openState = false"
@show="openState = true"
updateAtScroll updateAtScroll
> >
<div <div
@ -152,8 +164,17 @@ const searchList = (str: string, container: any) => {
<div class="layui-inline layui-iconpicker-main"> <div class="layui-inline layui-iconpicker-main">
<i class="layui-inline layui-icon" :class="[selectedIcon]"></i> <i class="layui-inline layui-icon" :class="[selectedIcon]"></i>
</div> </div>
<span
class="layui-icon-picker-clear"
v-if="allowClear && hasContent && !disabled"
>
<lay-icon type="layui-icon-close-fill" @click.stop="onClear"></lay-icon>
</span>
<span class="layui-inline layui-iconpicker-suffix" <span class="layui-inline layui-iconpicker-suffix"
><i class="layui-icon layui-icon-down layui-anim"></i ><i
class="layui-icon layui-icon-down"
:class="[openState ? 'transform' : '']"
></i
></span> ></span>
</div> </div>
<template #content> <template #content>

View File

@ -1,7 +1,7 @@
<template> <template>
<div class="lay-page-header"> <div class="lay-page-header">
<div class="lay-page-header__left" @click="emits('back')"> <div class="lay-page-header__left" @click="emits('back')">
<slot name="backIcon" <slot :name="backIconSlotName"
><i class="layui-icon" :class="[backIcon]"></i ><i class="layui-icon" :class="[backIcon]"></i
></slot> ></slot>
<div class="lay-page-header__title">{{ backText }}</div> <div class="lay-page-header__title">{{ backText }}</div>
@ -19,7 +19,8 @@ export default {
}; };
</script> </script>
<script lang="ts" setup> <script lang="ts" setup>
import { useSlots } from "vue"; import { convertSlotName } from "../../utils";
import { getCurrentInstance, useSlots } from "vue";
import "./index.less"; import "./index.less";
export interface PageHeaderProps { export interface PageHeaderProps {
@ -34,6 +35,7 @@ const props = withDefaults(defineProps<PageHeaderProps>(), {
}); });
const emits = defineEmits(["back"]); const emits = defineEmits(["back"]);
const slots = useSlots(); const slots = useSlots();
const instance = getCurrentInstance()!;
const backIconSlotName = convertSlotName(instance, "backIcon");
</script> </script>

View File

@ -18,6 +18,7 @@ import {
watch, watch,
onUnmounted, onUnmounted,
StyleValue, StyleValue,
Fragment,
} from "vue"; } from "vue";
import { LayIcon } from "@layui/icons-vue"; import { LayIcon } from "@layui/icons-vue";
import LayInput from "../input/index.vue"; import LayInput from "../input/index.vue";
@ -25,12 +26,14 @@ import LayTagInput from "../tagInput/index.vue";
import LayDropdown from "../dropdown/index.vue"; import LayDropdown from "../dropdown/index.vue";
import LaySelectOption, { SelectOptionProps } from "../selectOption/index.vue"; import LaySelectOption, { SelectOptionProps } from "../selectOption/index.vue";
import { SelectSize } from "./interface"; import { SelectSize } from "./interface";
import { isArrayChildren } from "../../utils";
export interface SelectProps { export interface SelectProps {
name?: string; name?: string;
disabled?: boolean; disabled?: boolean;
placeholder?: string; placeholder?: string;
searchPlaceholder?: string; searchPlaceholder?: string;
searchMethod?: Function;
modelValue?: any; modelValue?: any;
multiple?: boolean; multiple?: boolean;
items?: SelectOptionProps[]; items?: SelectOptionProps[];
@ -73,15 +76,13 @@ var timer: any;
const getOption = (nodes: VNode[], newOptions: any[]) => { const getOption = (nodes: VNode[], newOptions: any[]) => {
nodes?.map((item) => { nodes?.map((item) => {
let component = item.type as Component; if (isArrayChildren(item, item.children)) {
if (item.type.toString() == "Symbol(Fragment)") {
getOption(item.children as VNode[], newOptions); getOption(item.children as VNode[], newOptions);
} else { } else {
if (component.name == LaySelectOption.name) { if ((item.type as Component).name == LaySelectOption.name) {
if (item.children) { if (item.children) {
// @ts-ignore // @ts-ignore
const label = item.children.default()[0].children; const label = item.children.default()[0].children;
if (typeof label == "string") { if (typeof label == "string") {
// @ts-ignore // @ts-ignore
item.props.label = label; item.props.label = label;
@ -122,7 +123,6 @@ const onCompositionend = (event: Event) => {
onMounted(() => { onMounted(() => {
intOption(); intOption();
timer = setInterval(intOption, 500); timer = setInterval(intOption, 500);
watch( watch(
[selectedValue, options], [selectedValue, options],
() => { () => {
@ -178,11 +178,17 @@ const handleClear = () => {
} }
}; };
const handleHide = () => {
searchValue.value = "";
openState.value = false;
};
provide("selectRef", selectRef); provide("selectRef", selectRef);
provide("openState", openState); provide("openState", openState);
provide("selectedValue", selectedValue); provide("selectedValue", selectedValue);
provide("searchValue", searchValue); provide("searchValue", searchValue);
provide("multiple", multiple); provide("multiple", multiple);
provide("searchMethod", props.searchMethod);
</script> </script>
<template> <template>
@ -194,22 +200,27 @@ provide("multiple", multiple);
:contentStyle="contentStyle" :contentStyle="contentStyle"
:update-at-scroll="true" :update-at-scroll="true"
:autoFitWidth="true" :autoFitWidth="true"
@hide="openState = false" @hide="handleHide"
@show="openState = true" @show="openState = true"
> >
<lay-tag-input <lay-tag-input
v-if="multiple" v-if="multiple"
v-model="multipleValue" v-model="multipleValue"
v-model:input-value="searchValue"
:allow-clear="allowClear" :allow-clear="allowClear"
:placeholder="placeholder" :placeholder="placeholder"
:collapseTagsTooltip="collapseTagsTooltip" :collapseTagsTooltip="collapseTagsTooltip"
:minCollapsedNum="minCollapsedNum" :minCollapsedNum="minCollapsedNum"
:disabledInput="true"
:disabled="disabled" :disabled="disabled"
:disabledInput="!showSearch"
:size="size" :size="size"
:class="{ 'layui-unselect': true }" :class="{ 'layui-unselect': true }"
@remove="handleRemove" @remove="handleRemove"
@clear="handleClear" @clear="handleClear"
@input-value-change="handleSearch"
@keyup.delete.capture.prevent.stop
@keyup.backspace.capture.prevent.stop
@keydown.enter.capture.prevent.stop
> >
<template #suffix> <template #suffix>
<lay-icon <lay-icon
@ -220,13 +231,13 @@ provide("multiple", multiple);
</lay-tag-input> </lay-tag-input>
<lay-input <lay-input
v-else v-else
:modelValue="singleValue"
:placeholder="placeholder"
:allow-clear="allowClear"
:readonly="!showSearch"
:disabled="disabled"
:class="{ 'layui-unselect': !showSearch }"
:size="size" :size="size"
:disabled="disabled"
:readonly="!showSearch"
:modelValue="singleValue"
:allow-clear="allowClear"
:placeholder="placeholder"
:class="{ 'layui-unselect': !showSearch }"
@compositionstart="onCompositionstart" @compositionstart="onCompositionstart"
@compositionend="onCompositionend" @compositionend="onCompositionend"
@Input="handleSearch" @Input="handleSearch"
@ -241,17 +252,6 @@ provide("multiple", multiple);
</lay-input> </lay-input>
<template #content> <template #content>
<dl class="layui-select-content"> <dl class="layui-select-content">
<div class="layui-select-search" v-if="multiple && showSearch">
<lay-input
:modelValue="searchValue"
:placeholder="searchPlaceholder"
@Input="handleSearch"
@compositionstart="onCompositionstart"
@compositionend="onCompositionend"
prefix-icon="layui-icon-search"
size="sm"
></lay-input>
</div>
<template v-if="items"> <template v-if="items">
<lay-select-option <lay-select-option
v-for="(item, index) in items" v-for="(item, index) in items"

View File

@ -30,6 +30,7 @@ const props = withDefaults(defineProps<SelectOptionProps>(), {
const searchValue: Ref<string> = inject("searchValue") as Ref<string>; const searchValue: Ref<string> = inject("searchValue") as Ref<string>;
const selectRef: Ref<HTMLElement> = inject("selectRef") as Ref<HTMLElement>; const selectRef: Ref<HTMLElement> = inject("selectRef") as Ref<HTMLElement>;
const searchMethod: Function = inject("searchMethod") as Function;
const selectedValue: WritableComputedRef<any> = inject( const selectedValue: WritableComputedRef<any> = inject(
"selectedValue" "selectedValue"
) as WritableComputedRef<any>; ) as WritableComputedRef<any>;
@ -59,7 +60,13 @@ const selected = computed(() => {
} }
}); });
const first = ref(true);
const display = computed(() => { const display = computed(() => {
if (searchMethod && !first.value) {
return searchMethod(searchValue.value, props);
}
first.value = false;
return ( return (
props.keyword?.toString().indexOf(searchValue.value) > -1 || props.keyword?.toString().indexOf(searchValue.value) > -1 ||
props.label?.toString().indexOf(searchValue.value) > -1 props.label?.toString().indexOf(searchValue.value) > -1

View File

@ -97,8 +97,6 @@
border-bottom: 1px solid #eeeeee; border-bottom: 1px solid #eeeeee;
} }
.layui-tab-title.is-right { .layui-tab-title.is-right {
border-left: 1px solid var(--global-neutral-color-3); border-left: 1px solid var(--global-neutral-color-3);
} }

View File

@ -29,6 +29,7 @@ import {
} from "vue"; } from "vue";
import { useResizeObserver } from "@vueuse/core"; import { useResizeObserver } from "@vueuse/core";
import { TabData, TabInjectKey, TabPosition } from "./interface"; import { TabData, TabInjectKey, TabPosition } from "./interface";
import { isArrayChildren } from "../../utils";
export interface TabProps { export interface TabProps {
type?: string; type?: string;
@ -41,16 +42,15 @@ export interface TabProps {
} }
const slot = useSlots(); const slot = useSlots();
const childrens: Ref<VNode[]> = ref([]);
const tabMap = reactive(new Map<number, TabData>()); const tabMap = reactive(new Map<number, TabData>());
const childrens: Ref<VNode[]> = ref([]);
const setItemInstanceBySlot = function (nodes: VNode[]) { const setItemInstanceBySlot = function (nodes: VNode[]) {
nodes?.map((item) => { nodes?.map((item) => {
let component = item.type as Component; if (isArrayChildren(item, item.children)) {
if (item.type.toString() == "Symbol(Fragment)") {
setItemInstanceBySlot(item.children as VNode[]); setItemInstanceBySlot(item.children as VNode[]);
} else { } else {
if (component.name == tabItem.name) { if ((item.type as Component).name == tabItem.name) {
childrens.value.push(item); childrens.value.push(item);
} }
} }
@ -265,6 +265,24 @@ const update = () => {
} }
}; };
const horizontalScroll = (e: WheelEvent) => {
e.preventDefault();
const navSize = getNavSize();
const containerSize = navRef.value![`offset${sizeName.value}`];
const currentOffset = navOffset.value;
const scrollNextSize = scrollNextRef.value?.[`offset${sizeName.value}`] ?? 0;
const direction =
Math.abs(e.deltaX) >= Math.abs(e.deltaY) ? e.deltaX : e.deltaY;
const distance = 50 * (direction > 0 ? 1 : -1);
const newOffset = Math.max(currentOffset + distance, 0);
if (
navSize - currentOffset <= containerSize - scrollNextSize &&
direction > 0
)
return;
navOffset.value = newOffset;
};
const renderTabIcon = (attrs: Record<string, unknown>) => { const renderTabIcon = (attrs: Record<string, unknown>) => {
const tab = attrs.tabData as TabData; const tab = attrs.tabData as TabData;
if (typeof tab.icon === "function") { if (typeof tab.icon === "function") {
@ -293,7 +311,7 @@ useResizeObserver(navRef, update);
watch( watch(
tabMap, tabMap,
function () { () => {
childrens.value = []; childrens.value = [];
setItemInstanceBySlot((slot.default && slot.default()) as VNode[]); setItemInstanceBySlot((slot.default && slot.default()) as VNode[]);
}, },
@ -335,6 +353,7 @@ provide("active", active);
> >
<ul <ul
ref="navRef" ref="navRef"
@wheel="horizontalScroll"
:class="[ :class="[
'layui-tab-title', 'layui-tab-title',
props.tabPosition ? `is-${tabPosition}` : '', props.tabPosition ? `is-${tabPosition}` : '',

View File

@ -486,51 +486,58 @@ const radioProps = props.getRadioProps(props.data, props.index);
column.fixed ? `layui-table-fixed-${column.fixed}` : '', column.fixed ? `layui-table-fixed-${column.fixed}` : '',
]" ]"
> >
<span <div
v-if="expandSpace && columnIndex === expandIndex" style="display: flex"
:style="{ 'margin-right': currentIndentSize + 'px' }" :style="[
></span> { textAlign: column.align, justifyContent: column.align },
]"
>
<span
v-if="expandSpace && columnIndex === expandIndex"
:style="{ 'margin-right': currentIndentSize + 'px' }"
></span>
<span <span
v-if=" v-if="
expandSpace && expandSpace &&
!data[childrenColumnName] && !data[childrenColumnName] &&
!slot.expand && !slot.expand &&
columnIndex === expandIndex columnIndex === expandIndex
" "
class="layui-table-cell-expand-icon-spaced" class="layui-table-cell-expand-icon-spaced"
></span> ></span>
<lay-icon <lay-icon
v-if=" v-if="
(slot.expand || data[childrenColumnName]) && (slot.expand || data[childrenColumnName]) &&
columnIndex === expandIndex columnIndex === expandIndex
" "
class="layui-table-cell-expand-icon" class="layui-table-cell-expand-icon"
:type="expandIconType" :type="expandIconType"
@click="handleExpand" @click="handleExpand"
></lay-icon> ></lay-icon>
<lay-tooltip v-if="column.ellipsisTooltip" :isAutoShow="true"> <lay-tooltip v-if="column.ellipsisTooltip" :isAutoShow="true">
<slot
:name="column.customSlot"
:data="data"
:column="column"
></slot>
<template #content>
<slot <slot
:name="column.customSlot" :name="column.customSlot"
:data="data" :data="data"
:column="column" :column="column"
></slot> ></slot>
</template> <template #content>
</lay-tooltip> <slot
<slot :name="column.customSlot"
v-else :data="data"
:name="column.customSlot" :column="column"
:data="data" ></slot>
:column="column" </template>
></slot> </lay-tooltip>
<slot
v-else
:name="column.customSlot"
:data="data"
:column="column"
></slot>
</div>
</td> </td>
</template> </template>
@ -558,39 +565,47 @@ const radioProps = props.getRadioProps(props.data, props.index);
column.fixed ? `layui-table-fixed-${column.fixed}` : '', column.fixed ? `layui-table-fixed-${column.fixed}` : '',
]" ]"
> >
<span <div
v-if="expandSpace && columnIndex === expandIndex" style="display: flex"
:style="{ 'margin-right': currentIndentSize + 'px' }" :style="[
></span> { textAlign: column.align, justifyContent: column.align },
]"
<span
v-if="
expandSpace &&
!data[childrenColumnName] &&
!slot.expand &&
columnIndex === expandIndex
"
class="layui-table-cell-expand-icon-spaced"
></span>
<lay-icon
v-if="
(slot.expand || data[childrenColumnName]) &&
columnIndex === expandIndex
"
class="layui-table-cell-expand-icon"
:type="expandIconType"
@click="handleExpand"
></lay-icon>
<lay-tooltip
v-if="column.ellipsisTooltip"
:content="data[column.key]"
:isAutoShow="true"
> >
{{ data[column.key] }} <span
</lay-tooltip> v-if="expandSpace && columnIndex === expandIndex"
<span v-else> {{ data[column.key] }} </span> :style="{ 'margin-right': currentIndentSize + 'px' }"
></span>
<span
v-if="
expandSpace &&
!data[childrenColumnName] &&
!slot.expand &&
columnIndex === expandIndex
"
class="layui-table-cell-expand-icon-spaced"
></span>
<lay-icon
v-if="
(slot.expand || data[childrenColumnName]) &&
columnIndex === expandIndex
"
class="layui-table-cell-expand-icon"
:type="expandIconType"
@click="handleExpand"
></lay-icon>
<lay-tooltip
v-if="column.ellipsisTooltip"
:content="data[column.key]"
:isAutoShow="true"
>
{{ data[column.key] }}
</lay-tooltip>
<span v-else> {{ data[column.key] }} </span>
</div>
</td> </td>
</template> </template>
</template> </template>

View File

@ -44,12 +44,11 @@
.layui-table-mend, .layui-table-mend,
.layui-table-tool, .layui-table-tool,
.layui-table-total,
.layui-table-patch, .layui-table-patch,
.layui-table-click, .layui-table-click,
.layui-table-hover, .layui-table-hover,
.layui-table-header, .layui-table-header,
.layui-table-total tr, .layui-table-total td,
.layui-table thead tr, .layui-table thead tr,
.layui-table tbody tr:hover td, .layui-table tbody tr:hover td,
.layui-table.layui-table-even tr:nth-child(even) td { .layui-table.layui-table-even tr:nth-child(even) td {
@ -305,6 +304,11 @@
right: -1px; right: -1px;
} }
.layui-table-tool-self {
display: flex;
align-items: center;
}
.layui-table-col-set { .layui-table-col-set {
position: absolute; position: absolute;
right: 0; right: 0;

View File

@ -32,7 +32,7 @@ export interface TableProps {
page?: Recordable; page?: Recordable;
columns: Recordable[]; columns: Recordable[];
dataSource: Recordable[]; dataSource: Recordable[];
defaultToolbar?: boolean | Recordable[]; defaultToolbar?: boolean | any[];
selectedKey?: string; selectedKey?: string;
selectedKeys?: Recordable[]; selectedKeys?: Recordable[];
indentSize?: number; indentSize?: number;
@ -60,6 +60,7 @@ const props = withDefaults(defineProps<TableProps>(), {
childrenColumnName: "children", childrenColumnName: "children",
dataSource: () => [], dataSource: () => [],
selectedKeys: () => [], selectedKeys: () => [],
defaultToolbar: false,
selectedKey: "", selectedKey: "",
maxHeight: "auto", maxHeight: "auto",
even: false, even: false,
@ -518,9 +519,6 @@ const childrenExpandSpace = computed(() => {
); );
}); });
/**
* @remark 排除 hide
*/
const renderFixedStyle = (column: any, columnIndex: number) => { const renderFixedStyle = (column: any, columnIndex: number) => {
if (column.fixed) { if (column.fixed) {
if (column.fixed == "left") { if (column.fixed == "left") {
@ -563,6 +561,55 @@ const renderFixedStyle = (column: any, columnIndex: number) => {
return {} as StyleValue; return {} as StyleValue;
}; };
/**
* @remark 排除 hide
*/
const renderHeadFixedStyle = (
column: any,
columnIndex: number,
tableHeadColumn: any[]
) => {
if (column.fixed) {
if (column.fixed == "left") {
var left = 0;
for (var i = 0; i < columnIndex; i++) {
if (
props.columns[i].fixed &&
props.columns[i].fixed == "left" &&
tableColumnKeys.value.includes(props.columns[i].key)
) {
left = left + Number(props.columns[i]?.width?.replace("px", ""));
}
}
return { left: `${left}px` } as StyleValue;
} else {
var right = 0;
for (var i = columnIndex + 1; i < props.columns.length; i++) {
if (
props.columns[i].fixed &&
props.columns[i].fixed == "right" &&
tableColumnKeys.value.includes(props.columns[i].key)
) {
right = right + Number(props.columns[i]?.width?.replace("px", ""));
}
}
return { right: `${right}px` } as StyleValue;
}
} else {
var isLast = true;
for (var i = columnIndex + 1; i < tableHeadColumn.length; i++) {
if (
tableHeadColumn[i].fixed == undefined &&
tableColumnKeys.value.includes(tableHeadColumn[i].key)
) {
isLast = false;
}
}
return isLast ? ({ "border-right": "none" } as StyleValue) : {};
}
return {} as StyleValue;
};
/** /**
* @remark 排除 hide * @remark 排除 hide
*/ */
@ -628,6 +675,19 @@ const totalRowMethod = (column: any, dataSource: any[]) => {
return total; return total;
}; };
const showToolbar = (toolbarName: string) => {
if (props.defaultToolbar instanceof Array) {
return props.defaultToolbar.includes(toolbarName);
}
return props.defaultToolbar;
};
const toolbarStyle = (toolbarName: string) => {
if (props.defaultToolbar instanceof Array) {
return { order: props.defaultToolbar.indexOf(toolbarName) } as StyleValue;
}
};
onBeforeUnmount(() => { onBeforeUnmount(() => {
window.onresize = null; window.onresize = null;
}); });
@ -642,7 +702,12 @@ onBeforeUnmount(() => {
<slot name="toolbar"></slot> <slot name="toolbar"></slot>
</div> </div>
<div v-if="defaultToolbar" class="layui-table-tool-self"> <div v-if="defaultToolbar" class="layui-table-tool-self">
<lay-dropdown updateAtScroll> <!-- 筛选 -->
<lay-dropdown
v-if="showToolbar('filter')"
updateAtScroll
:style="toolbarStyle('filter')"
>
<div class="layui-inline" title="筛选" lay-event> <div class="layui-inline" title="筛选" lay-event>
<i class="layui-icon layui-icon-slider"></i> <i class="layui-icon layui-icon-slider"></i>
</div> </div>
@ -660,15 +725,28 @@ onBeforeUnmount(() => {
</div> </div>
</template> </template>
</lay-dropdown> </lay-dropdown>
<!-- 导出 -->
<div <div
v-if="showToolbar('export')"
class="layui-inline" class="layui-inline"
title="导出" title="导出"
lay-event lay-event
:style="toolbarStyle('export')"
@click="exportData()" @click="exportData()"
> >
<i class="layui-icon layui-icon-export"></i> <i class="layui-icon layui-icon-export"></i>
</div> </div>
<div class="layui-inline" title="打印" lay-event @click="print()">
<!-- 打印 -->
<div
v-if="showToolbar('print')"
:style="toolbarStyle('print')"
class="layui-inline"
title="打印"
lay-event
@click="print()"
>
<i class="layui-icon layui-icon-print"></i> <i class="layui-icon layui-icon-print"></i>
</div> </div>
</div> </div>
@ -739,7 +817,11 @@ onBeforeUnmount(() => {
{ {
textAlign: column.align, textAlign: column.align,
}, },
renderFixedStyle(column, columnIndex), renderHeadFixedStyle(
column,
columnIndex,
tableHeadColumn
),
]" ]"
> >
<template v-if="column.type == 'checkbox'"> <template v-if="column.type == 'checkbox'">

View File

@ -73,12 +73,10 @@
} }
} }
&-ellipsis &-text { & &-text {
display: inline-block; overflow: hidden;
white-space: nowrap; text-overflow: ellipsis;
word-wrap: normal; white-space: nowrap
overflow: hidden;
text-overflow: ellipsis
} }
& &-close-icon { & &-close-icon {

View File

@ -6,7 +6,7 @@ export default {
<script lang="ts" setup> <script lang="ts" setup>
import "./index.less"; import "./index.less";
import { LayIcon } from "@layui/icons-vue"; import { LayIcon } from "@layui/icons-vue";
import { computed, ref } from "vue"; import { computed, nextTick, ref } from "vue";
import { TinyColor } from "@ctrl/tinycolor"; import { TinyColor } from "@ctrl/tinycolor";
import { TagShape, TagType, TagVariant } from "./interface"; import { TagShape, TagType, TagVariant } from "./interface";
@ -49,13 +49,12 @@ const classTag = computed(() => [
[`layui-tag-${props.type}`]: props.type, [`layui-tag-${props.type}`]: props.type,
"layui-tag-bordered": props.bordered, "layui-tag-bordered": props.bordered,
"layui-tag-disabled": props.disabled, "layui-tag-disabled": props.disabled,
"layui-tag-ellipsis": props.maxWidth,
}, },
]); ]);
const styleTag = computed(() => [ const styleTag = computed(() => [
{ {
"max-width": props.maxWidth ?? "unset", "max-width": props.maxWidth ?? "100%",
...useTagCustomStyle(props).value, ...useTagCustomStyle(props).value,
}, },
]); ]);
@ -105,10 +104,9 @@ function useTagCustomStyle(props: TagProps) {
<span v-if="$slots.icon" class="layui-tag-icon"> <span v-if="$slots.icon" class="layui-tag-icon">
<slot name="icon" /> <slot name="icon" />
</span> </span>
<span v-if="maxWidth" :style="styleTag" class="layui-tag-text"> <span class="layui-tag-text">
<slot /> <slot />
</span> </span>
<slot v-else />
<span <span
v-if="closable" v-if="closable"
class="layui-tag-close-icon" class="layui-tag-close-icon"

View File

@ -86,7 +86,7 @@
display: flex; display: flex;
align-items: center; align-items: center;
flex-wrap: wrap; flex-wrap: wrap;
width: fit-content; width: 100%;
height: auto; height: auto;
overflow: hidden; overflow: hidden;
@ -108,6 +108,9 @@
.layui-tag-input-inner-input { .layui-tag-input-inner-input {
height: @@height - (@@inner-padding + @tag-input-boeder)* 2; height: @@height - (@@inner-padding + @tag-input-boeder)* 2;
vertical-align: middle; vertical-align: middle;
&:disabled {
background: transparent;
}
} }
.layui-tag-input-inner { .layui-tag-input-inner {

View File

@ -300,24 +300,22 @@ defineExpose({
</template> </template>
</LayToopTip> </LayToopTip>
</template> </template>
<template v-if="!disabledInput"> <input
<input ref="inputRefEl"
ref="inputRefEl" class="layui-tag-input-inner-input"
class="layui-tag-input-inner-input" :style="inputStyle"
:style="inputStyle" :disabled="disabled || disabledInput"
:disabled="disabled" :placeholder="placeholder"
:placeholder="placeholder" :readonly="readonly"
:readonly="readonly" @keydown.enter="handleEnter"
@keydown.enter="handleEnter" @keyup="handleBackspaceKeyUp"
@keyup="handleBackspaceKeyUp" @input="handleInput"
@input="handleInput" @focus="handleFocus"
@focus="handleFocus" @blur="handleBlur"
@blur="handleBlur" @compositionstart="handleComposition"
@compositionstart="handleComposition" @compositionupdate="handleComposition"
@compositionupdate="handleComposition" @compositionend="handleComposition"
@compositionend="handleComposition" />
/>
</template>
</span> </span>
<span <span
v-if="allowClear && tagData?.length && !disabled" v-if="allowClear && tagData?.length && !disabled"

View File

@ -18,6 +18,8 @@
padding: 6px 10px; padding: 6px 10px;
resize: vertical; resize: vertical;
position: relative; position: relative;
transition: none;
-webkit-transition: none;
} }
.layui-textarea-wrapper { .layui-textarea-wrapper {

View File

@ -6,7 +6,8 @@ export default {
<script setup lang="ts"> <script setup lang="ts">
import { LayIcon } from "@layui/icons-vue"; import { LayIcon } from "@layui/icons-vue";
import { computed, ref } from "vue"; import { computed, ref, watch } from "vue";
import { isObject } from "@vueuse/shared";
import "./index.less"; import "./index.less";
export interface TextareaProps { export interface TextareaProps {
@ -17,6 +18,7 @@ export interface TextareaProps {
showCount?: boolean; showCount?: boolean;
allowClear?: boolean; allowClear?: boolean;
maxlength?: number; maxlength?: number;
autosize?: boolean | { minHeight: number; maxHeight: number };
} }
const props = defineProps<TextareaProps>(); const props = defineProps<TextareaProps>();
@ -31,6 +33,7 @@ interface TextareaEmits {
} }
const emit = defineEmits<TextareaEmits>(); const emit = defineEmits<TextareaEmits>();
const textareaRef = ref<HTMLTextAreaElement | null>(null);
const composing = ref(false); const composing = ref(false);
const onInput = function (event: Event) { const onInput = function (event: Event) {
@ -78,11 +81,31 @@ const wordCount = computed(() => {
} }
return count; return count;
}); });
watch(
[() => props.modelValue, textareaRef],
() => {
if (!textareaRef.value || !props.autosize) return;
const height: number = textareaRef.value?.scrollHeight + 2; //
if (isObject(props.autosize)) {
const { minHeight, maxHeight } = props.autosize;
if (height < minHeight || height > maxHeight) return;
}
textareaRef.value!.style.height = "1px";
textareaRef.value!.style.height = `${
textareaRef.value?.scrollHeight + 2
}px`;
},
{
immediate: true,
}
);
</script> </script>
<template> <template>
<div class="layui-textarea-wrapper"> <div class="layui-textarea-wrapper">
<textarea <textarea
ref="textareaRef"
class="layui-textarea" class="layui-textarea"
:value="modelValue" :value="modelValue"
:placeholder="placeholder" :placeholder="placeholder"

View File

@ -30,4 +30,6 @@
--global-neutral-color-8: #c2c2c2; --global-neutral-color-8: #c2c2c2;
--darkreader-border--global-primary-color: #0d796f;
} }

View File

@ -1,3 +1,4 @@
export * from "./domUtil"; export * from "./domUtil";
export * from "./withInstall"; export * from "./withInstall";
export * from "./arrayUtil"; export * from "./arrayUtil";
export * from "./vueUtil";

57
src/utils/vueUtil.ts Normal file
View File

@ -0,0 +1,57 @@
import { Component, ComponentInternalInstance, VNode, VNodeTypes } from "vue";
export enum ShapeFlags {
ELEMENT = 1,
FUNCTIONAL_COMPONENT = 1 << 1,
STATEFUL_COMPONENT = 1 << 2,
COMPONENT = ShapeFlags.STATEFUL_COMPONENT | ShapeFlags.FUNCTIONAL_COMPONENT,
TEXT_CHILDREN = 1 << 3,
ARRAY_CHILDREN = 1 << 4,
SLOTS_CHILDREN = 1 << 5,
TELEPORT = 1 << 6,
SUSPENSE = 1 << 7,
COMPONENT_SHOULD_KEEP_ALIVE = 1 << 8,
COMPONENT_KEPT_ALIVE = 1 << 9,
}
export const isElement = (vn: VNode) => {
return Boolean(vn && vn.shapeFlag & ShapeFlags.ELEMENT);
};
export const isComponent = (
vn: VNode,
type?: VNodeTypes
): type is Component => {
return Boolean(vn && vn.shapeFlag & ShapeFlags.COMPONENT);
};
export const isArrayChildren = (
vn: VNode,
children: VNode["children"]
): children is VNode[] => {
return Boolean(vn && vn.shapeFlag & ShapeFlags.ARRAY_CHILDREN);
};
/**
* back-icon backIcon
* @param vm
* @param name
*/
export function convertSlotName(vm: ComponentInternalInstance, name: string) {
const camelCaseName = camelCase(name);
const kebabCaseName = kebabCase(name);
return vm.slots[camelCaseName]
? camelCaseName
: vm.slots[kebabCaseName]
? kebabCaseName
: name;
}
export function camelCase(str: string) {
return str.replace(/-(\w)/g, (_, c) => (c ? c.toUpperCase() : ""));
}
export function kebabCase(key: string) {
const result = key.replace(/([A-Z])/g, " $1").trim();
return result.split(" ").join("-").toLowerCase();
}