merge
This commit is contained in:
parent
093de34571
commit
c696834501
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@layui/layui-vue",
|
||||
"version": "1.7.7",
|
||||
"version": "1.7.11",
|
||||
"author": "就眠儀式",
|
||||
"license": "MIT",
|
||||
"description": "a component library for Vue 3 base on layui-vue",
|
||||
|
@ -31,7 +31,9 @@
|
||||
:size="size"
|
||||
@clear="onClear"
|
||||
></lay-input>
|
||||
<slot v-else></slot>
|
||||
<div class="slot-area" v-else>
|
||||
<slot></slot>
|
||||
</div>
|
||||
|
||||
<template #content>
|
||||
<div class="layui-cascader-panel">
|
||||
@ -137,12 +139,19 @@ watch(
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
() => {
|
||||
if (props.modelValue === null || props.modelValue === "") {
|
||||
onClear();
|
||||
if (watchModelValue.value) {
|
||||
if (props.modelValue === null || props.modelValue === "") {
|
||||
onClear();
|
||||
} else {
|
||||
updateDisplayByModelValue();
|
||||
}
|
||||
setTimeout(() => {
|
||||
watchModelValue.value = true;
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const watchModelValue = ref(true);
|
||||
const treeData = ref<any>([]);
|
||||
const initTreeData = () => {
|
||||
let treeLvNum = getMaxFloor(props.options);
|
||||
@ -159,31 +168,28 @@ const initTreeData = () => {
|
||||
};
|
||||
}
|
||||
}
|
||||
updateDisplayByModelValue();
|
||||
};
|
||||
|
||||
function updateDisplayByModelValue() {
|
||||
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;
|
||||
}
|
||||
for (let index = 0; index < valueData.length; index++) {
|
||||
const element = valueData[index];
|
||||
let selectIndex = treeData.value[index].data.findIndex(
|
||||
(e: { value: string }) => e.value === element
|
||||
);
|
||||
if (selectIndex == -1) {
|
||||
break;
|
||||
}
|
||||
selectBar(treeData.value[index].data[selectIndex], selectIndex, index);
|
||||
}
|
||||
displayValue.value = data
|
||||
.map((e) => {
|
||||
return e.label;
|
||||
})
|
||||
.join(` ${props.decollator} `);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function getMaxFloor(treeData: any) {
|
||||
//let floor = 0;
|
||||
@ -274,6 +280,7 @@ const selectBar = (item: any, selectIndex: number, parentIndex: number) => {
|
||||
return e.value;
|
||||
})
|
||||
.join(props.decollator);
|
||||
watchModelValue.value = false;
|
||||
emit("update:modelValue", value);
|
||||
let evt = {
|
||||
display: displayValue.value,
|
||||
|
@ -79,7 +79,9 @@
|
||||
width: 29px;
|
||||
height: 28px;
|
||||
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;
|
||||
color: #fff;
|
||||
font-size: 20px;
|
||||
|
@ -20,7 +20,6 @@ import {
|
||||
cloneVNode,
|
||||
useAttrs,
|
||||
StyleValue,
|
||||
PropType,
|
||||
} from "vue";
|
||||
import {
|
||||
computed,
|
||||
@ -45,9 +44,8 @@ import {
|
||||
DropdownContext,
|
||||
} from "./interface";
|
||||
import TeleportWrapper from "../_components/teleportWrapper.vue";
|
||||
import { useFirstElement, isScrollElement, getScrollElements } from "./util";
|
||||
import RenderFunction, { RenderFunc } from "../_components/renderFunction";
|
||||
import { transformPlacement } from "./util";
|
||||
import { useFirstElement, getScrollElements, transformPlacement } from "./util";
|
||||
import RenderFunction from "../_components/renderFunction";
|
||||
|
||||
export type DropdownTrigger = "click" | "hover" | "focus" | "contextMenu";
|
||||
|
||||
@ -105,7 +103,6 @@ const dropdownCtx = inject<DropdownContext | undefined>(
|
||||
undefined
|
||||
);
|
||||
const { children, firstElement: dropdownRef } = useFirstElement();
|
||||
//const dropdownRef = shallowRef<HTMLElement | undefined>();
|
||||
const contentRef = shallowRef<HTMLElement | undefined>();
|
||||
const contentStyle = ref<CSSProperties>({});
|
||||
const { width: windowWidth, height: windowHeight } = useWindowSize();
|
||||
|
@ -1,27 +1,12 @@
|
||||
import { DropdownPlacement } from "./interface";
|
||||
|
||||
import { Component, onMounted, onUpdated, ref, VNode, VNodeTypes } from "vue";
|
||||
import { isArrayChildren, isComponent, isElement } from "../../utils";
|
||||
|
||||
export interface SlotChildren {
|
||||
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) => {
|
||||
return (
|
||||
element.scrollHeight > element.offsetHeight ||
|
||||
@ -41,24 +26,6 @@ export const getScrollElements = (container: HTMLElement | undefined) => {
|
||||
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 => {
|
||||
if (isArrayChildren(vn, vn.children)) {
|
||||
return vn.children;
|
||||
@ -140,6 +107,5 @@ export const transformPlacement = (
|
||||
placementMap[separated[1]] || separated[1]
|
||||
}` as DropdownPlacement;
|
||||
}
|
||||
|
||||
return placement;
|
||||
};
|
||||
|
@ -53,6 +53,7 @@
|
||||
font-size: 14px;
|
||||
color: rgba(0, 0, 0, 0.5);
|
||||
transition: all 0.3s;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.layui-iconpicker-down .layui-iconpicker-suffix .layui-icon-down {
|
||||
@ -151,6 +152,15 @@
|
||||
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 {
|
||||
opacity: 0.6;
|
||||
}
|
||||
@ -158,4 +168,8 @@
|
||||
.layui-colorpicker-disabled,
|
||||
.layui-colorpicker-disabled * {
|
||||
cursor: not-allowed !important;
|
||||
}
|
||||
|
||||
.transform{
|
||||
transform: rotate(180deg);
|
||||
}
|
@ -17,6 +17,7 @@ export interface IconPickerProps {
|
||||
modelValue?: string;
|
||||
disabled?: boolean;
|
||||
showSearch?: boolean;
|
||||
allowClear?: boolean;
|
||||
contentClass?: string | Array<string | object> | object;
|
||||
contentStyle?: StyleValue;
|
||||
}
|
||||
@ -30,6 +31,7 @@ const props = withDefaults(defineProps<IconPickerProps>(), {
|
||||
const emit = defineEmits(["update:modelValue", "change"]);
|
||||
const selectedIcon = computed(() => props.modelValue);
|
||||
const dropdownRef = ref<any>(null);
|
||||
const openState = ref<boolean>(false);
|
||||
|
||||
const selectIcon = function (icon: string): void {
|
||||
emit("update:modelValue", icon);
|
||||
@ -37,6 +39,14 @@ const selectIcon = function (icon: string): void {
|
||||
dropdownRef.value?.hide();
|
||||
};
|
||||
|
||||
const onClear = function (): void {
|
||||
emit("update:modelValue", "");
|
||||
};
|
||||
|
||||
const hasContent = computed(() => {
|
||||
return props.modelValue != null && props.modelValue != "";
|
||||
});
|
||||
|
||||
const icones: Ref = ref([]);
|
||||
const total: Ref<number> = ref(icons.length);
|
||||
const totalPage: Ref<number> = ref(total.value / 12);
|
||||
@ -143,6 +153,8 @@ const searchList = (str: string, container: any) => {
|
||||
:disabled="disabled"
|
||||
:contentClass="contentClass"
|
||||
:contentStyle="contentStyle"
|
||||
@hide="openState = false"
|
||||
@show="openState = true"
|
||||
updateAtScroll
|
||||
>
|
||||
<div
|
||||
@ -152,8 +164,17 @@ const searchList = (str: string, container: any) => {
|
||||
<div class="layui-inline layui-iconpicker-main">
|
||||
<i class="layui-inline layui-icon" :class="[selectedIcon]"></i>
|
||||
</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"
|
||||
><i class="layui-icon layui-icon-down layui-anim"></i
|
||||
><i
|
||||
class="layui-icon layui-icon-down"
|
||||
:class="[openState ? 'transform' : '']"
|
||||
></i
|
||||
></span>
|
||||
</div>
|
||||
<template #content>
|
||||
|
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="lay-page-header">
|
||||
<div class="lay-page-header__left" @click="emits('back')">
|
||||
<slot name="backIcon"
|
||||
<slot :name="backIconSlotName"
|
||||
><i class="layui-icon" :class="[backIcon]"></i
|
||||
></slot>
|
||||
<div class="lay-page-header__title">{{ backText }}</div>
|
||||
@ -19,7 +19,8 @@ export default {
|
||||
};
|
||||
</script>
|
||||
<script lang="ts" setup>
|
||||
import { useSlots } from "vue";
|
||||
import { convertSlotName } from "../../utils";
|
||||
import { getCurrentInstance, useSlots } from "vue";
|
||||
import "./index.less";
|
||||
|
||||
export interface PageHeaderProps {
|
||||
@ -34,6 +35,7 @@ const props = withDefaults(defineProps<PageHeaderProps>(), {
|
||||
});
|
||||
|
||||
const emits = defineEmits(["back"]);
|
||||
|
||||
const slots = useSlots();
|
||||
const instance = getCurrentInstance()!;
|
||||
const backIconSlotName = convertSlotName(instance, "backIcon");
|
||||
</script>
|
||||
|
@ -18,6 +18,7 @@ import {
|
||||
watch,
|
||||
onUnmounted,
|
||||
StyleValue,
|
||||
Fragment,
|
||||
} from "vue";
|
||||
import { LayIcon } from "@layui/icons-vue";
|
||||
import LayInput from "../input/index.vue";
|
||||
@ -25,12 +26,14 @@ import LayTagInput from "../tagInput/index.vue";
|
||||
import LayDropdown from "../dropdown/index.vue";
|
||||
import LaySelectOption, { SelectOptionProps } from "../selectOption/index.vue";
|
||||
import { SelectSize } from "./interface";
|
||||
import { isArrayChildren } from "../../utils";
|
||||
|
||||
export interface SelectProps {
|
||||
name?: string;
|
||||
disabled?: boolean;
|
||||
placeholder?: string;
|
||||
searchPlaceholder?: string;
|
||||
searchMethod?: Function;
|
||||
modelValue?: any;
|
||||
multiple?: boolean;
|
||||
items?: SelectOptionProps[];
|
||||
@ -73,15 +76,13 @@ var timer: any;
|
||||
|
||||
const getOption = (nodes: VNode[], newOptions: any[]) => {
|
||||
nodes?.map((item) => {
|
||||
let component = item.type as Component;
|
||||
if (item.type.toString() == "Symbol(Fragment)") {
|
||||
if (isArrayChildren(item, item.children)) {
|
||||
getOption(item.children as VNode[], newOptions);
|
||||
} else {
|
||||
if (component.name == LaySelectOption.name) {
|
||||
if ((item.type as Component).name == LaySelectOption.name) {
|
||||
if (item.children) {
|
||||
// @ts-ignore
|
||||
const label = item.children.default()[0].children;
|
||||
|
||||
if (typeof label == "string") {
|
||||
// @ts-ignore
|
||||
item.props.label = label;
|
||||
@ -122,7 +123,6 @@ const onCompositionend = (event: Event) => {
|
||||
onMounted(() => {
|
||||
intOption();
|
||||
timer = setInterval(intOption, 500);
|
||||
|
||||
watch(
|
||||
[selectedValue, options],
|
||||
() => {
|
||||
@ -178,11 +178,17 @@ const handleClear = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleHide = () => {
|
||||
searchValue.value = "";
|
||||
openState.value = false;
|
||||
};
|
||||
|
||||
provide("selectRef", selectRef);
|
||||
provide("openState", openState);
|
||||
provide("selectedValue", selectedValue);
|
||||
provide("searchValue", searchValue);
|
||||
provide("multiple", multiple);
|
||||
provide("searchMethod", props.searchMethod);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -194,22 +200,27 @@ provide("multiple", multiple);
|
||||
:contentStyle="contentStyle"
|
||||
:update-at-scroll="true"
|
||||
:autoFitWidth="true"
|
||||
@hide="openState = false"
|
||||
@hide="handleHide"
|
||||
@show="openState = true"
|
||||
>
|
||||
<lay-tag-input
|
||||
v-if="multiple"
|
||||
v-model="multipleValue"
|
||||
v-model:input-value="searchValue"
|
||||
:allow-clear="allowClear"
|
||||
:placeholder="placeholder"
|
||||
:collapseTagsTooltip="collapseTagsTooltip"
|
||||
:minCollapsedNum="minCollapsedNum"
|
||||
:disabledInput="true"
|
||||
:disabled="disabled"
|
||||
:disabledInput="!showSearch"
|
||||
:size="size"
|
||||
:class="{ 'layui-unselect': true }"
|
||||
@remove="handleRemove"
|
||||
@clear="handleClear"
|
||||
@input-value-change="handleSearch"
|
||||
@keyup.delete.capture.prevent.stop
|
||||
@keyup.backspace.capture.prevent.stop
|
||||
@keydown.enter.capture.prevent.stop
|
||||
>
|
||||
<template #suffix>
|
||||
<lay-icon
|
||||
@ -220,13 +231,13 @@ provide("multiple", multiple);
|
||||
</lay-tag-input>
|
||||
<lay-input
|
||||
v-else
|
||||
:modelValue="singleValue"
|
||||
:placeholder="placeholder"
|
||||
:allow-clear="allowClear"
|
||||
:readonly="!showSearch"
|
||||
:disabled="disabled"
|
||||
:class="{ 'layui-unselect': !showSearch }"
|
||||
:size="size"
|
||||
:disabled="disabled"
|
||||
:readonly="!showSearch"
|
||||
:modelValue="singleValue"
|
||||
:allow-clear="allowClear"
|
||||
:placeholder="placeholder"
|
||||
:class="{ 'layui-unselect': !showSearch }"
|
||||
@compositionstart="onCompositionstart"
|
||||
@compositionend="onCompositionend"
|
||||
@Input="handleSearch"
|
||||
@ -241,17 +252,6 @@ provide("multiple", multiple);
|
||||
</lay-input>
|
||||
<template #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">
|
||||
<lay-select-option
|
||||
v-for="(item, index) in items"
|
||||
|
@ -30,6 +30,7 @@ const props = withDefaults(defineProps<SelectOptionProps>(), {
|
||||
|
||||
const searchValue: Ref<string> = inject("searchValue") as Ref<string>;
|
||||
const selectRef: Ref<HTMLElement> = inject("selectRef") as Ref<HTMLElement>;
|
||||
const searchMethod: Function = inject("searchMethod") as Function;
|
||||
const selectedValue: WritableComputedRef<any> = inject(
|
||||
"selectedValue"
|
||||
) as WritableComputedRef<any>;
|
||||
@ -59,7 +60,13 @@ const selected = computed(() => {
|
||||
}
|
||||
});
|
||||
|
||||
const first = ref(true);
|
||||
|
||||
const display = computed(() => {
|
||||
if (searchMethod && !first.value) {
|
||||
return searchMethod(searchValue.value, props);
|
||||
}
|
||||
first.value = false;
|
||||
return (
|
||||
props.keyword?.toString().indexOf(searchValue.value) > -1 ||
|
||||
props.label?.toString().indexOf(searchValue.value) > -1
|
||||
|
@ -97,8 +97,6 @@
|
||||
border-bottom: 1px solid #eeeeee;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.layui-tab-title.is-right {
|
||||
border-left: 1px solid var(--global-neutral-color-3);
|
||||
}
|
||||
|
@ -29,6 +29,7 @@ import {
|
||||
} from "vue";
|
||||
import { useResizeObserver } from "@vueuse/core";
|
||||
import { TabData, TabInjectKey, TabPosition } from "./interface";
|
||||
import { isArrayChildren } from "../../utils";
|
||||
|
||||
export interface TabProps {
|
||||
type?: string;
|
||||
@ -41,16 +42,15 @@ export interface TabProps {
|
||||
}
|
||||
|
||||
const slot = useSlots();
|
||||
const childrens: Ref<VNode[]> = ref([]);
|
||||
const tabMap = reactive(new Map<number, TabData>());
|
||||
const childrens: Ref<VNode[]> = ref([]);
|
||||
|
||||
const setItemInstanceBySlot = function (nodes: VNode[]) {
|
||||
nodes?.map((item) => {
|
||||
let component = item.type as Component;
|
||||
if (item.type.toString() == "Symbol(Fragment)") {
|
||||
if (isArrayChildren(item, item.children)) {
|
||||
setItemInstanceBySlot(item.children as VNode[]);
|
||||
} else {
|
||||
if (component.name == tabItem.name) {
|
||||
if ((item.type as Component).name == tabItem.name) {
|
||||
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 tab = attrs.tabData as TabData;
|
||||
if (typeof tab.icon === "function") {
|
||||
@ -293,7 +311,7 @@ useResizeObserver(navRef, update);
|
||||
|
||||
watch(
|
||||
tabMap,
|
||||
function () {
|
||||
() => {
|
||||
childrens.value = [];
|
||||
setItemInstanceBySlot((slot.default && slot.default()) as VNode[]);
|
||||
},
|
||||
@ -335,6 +353,7 @@ provide("active", active);
|
||||
>
|
||||
<ul
|
||||
ref="navRef"
|
||||
@wheel="horizontalScroll"
|
||||
:class="[
|
||||
'layui-tab-title',
|
||||
props.tabPosition ? `is-${tabPosition}` : '',
|
||||
|
@ -486,51 +486,58 @@ const radioProps = props.getRadioProps(props.data, props.index);
|
||||
column.fixed ? `layui-table-fixed-${column.fixed}` : '',
|
||||
]"
|
||||
>
|
||||
<span
|
||||
v-if="expandSpace && columnIndex === expandIndex"
|
||||
:style="{ 'margin-right': currentIndentSize + 'px' }"
|
||||
></span>
|
||||
<div
|
||||
style="display: flex"
|
||||
:style="[
|
||||
{ textAlign: column.align, justifyContent: column.align },
|
||||
]"
|
||||
>
|
||||
<span
|
||||
v-if="expandSpace && columnIndex === expandIndex"
|
||||
:style="{ 'margin-right': currentIndentSize + 'px' }"
|
||||
></span>
|
||||
|
||||
<span
|
||||
v-if="
|
||||
expandSpace &&
|
||||
!data[childrenColumnName] &&
|
||||
!slot.expand &&
|
||||
columnIndex === expandIndex
|
||||
"
|
||||
class="layui-table-cell-expand-icon-spaced"
|
||||
></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-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" :isAutoShow="true">
|
||||
<slot
|
||||
:name="column.customSlot"
|
||||
:data="data"
|
||||
:column="column"
|
||||
></slot>
|
||||
<template #content>
|
||||
<lay-tooltip v-if="column.ellipsisTooltip" :isAutoShow="true">
|
||||
<slot
|
||||
:name="column.customSlot"
|
||||
:data="data"
|
||||
:column="column"
|
||||
></slot>
|
||||
</template>
|
||||
</lay-tooltip>
|
||||
<slot
|
||||
v-else
|
||||
:name="column.customSlot"
|
||||
:data="data"
|
||||
:column="column"
|
||||
></slot>
|
||||
<template #content>
|
||||
<slot
|
||||
:name="column.customSlot"
|
||||
:data="data"
|
||||
:column="column"
|
||||
></slot>
|
||||
</template>
|
||||
</lay-tooltip>
|
||||
<slot
|
||||
v-else
|
||||
:name="column.customSlot"
|
||||
:data="data"
|
||||
:column="column"
|
||||
></slot>
|
||||
</div>
|
||||
</td>
|
||||
</template>
|
||||
|
||||
@ -558,39 +565,47 @@ const radioProps = props.getRadioProps(props.data, props.index);
|
||||
column.fixed ? `layui-table-fixed-${column.fixed}` : '',
|
||||
]"
|
||||
>
|
||||
<span
|
||||
v-if="expandSpace && columnIndex === expandIndex"
|
||||
: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"
|
||||
<div
|
||||
style="display: flex"
|
||||
:style="[
|
||||
{ textAlign: column.align, justifyContent: column.align },
|
||||
]"
|
||||
>
|
||||
{{ data[column.key] }}
|
||||
</lay-tooltip>
|
||||
<span v-else> {{ data[column.key] }} </span>
|
||||
<span
|
||||
v-if="expandSpace && columnIndex === expandIndex"
|
||||
: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>
|
||||
</template>
|
||||
</template>
|
||||
|
@ -44,12 +44,11 @@
|
||||
|
||||
.layui-table-mend,
|
||||
.layui-table-tool,
|
||||
.layui-table-total,
|
||||
.layui-table-patch,
|
||||
.layui-table-click,
|
||||
.layui-table-hover,
|
||||
.layui-table-header,
|
||||
.layui-table-total tr,
|
||||
.layui-table-total td,
|
||||
.layui-table thead tr,
|
||||
.layui-table tbody tr:hover td,
|
||||
.layui-table.layui-table-even tr:nth-child(even) td {
|
||||
@ -305,6 +304,11 @@
|
||||
right: -1px;
|
||||
}
|
||||
|
||||
.layui-table-tool-self {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.layui-table-col-set {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
|
@ -32,7 +32,7 @@ export interface TableProps {
|
||||
page?: Recordable;
|
||||
columns: Recordable[];
|
||||
dataSource: Recordable[];
|
||||
defaultToolbar?: boolean | Recordable[];
|
||||
defaultToolbar?: boolean | any[];
|
||||
selectedKey?: string;
|
||||
selectedKeys?: Recordable[];
|
||||
indentSize?: number;
|
||||
@ -60,6 +60,7 @@ const props = withDefaults(defineProps<TableProps>(), {
|
||||
childrenColumnName: "children",
|
||||
dataSource: () => [],
|
||||
selectedKeys: () => [],
|
||||
defaultToolbar: false,
|
||||
selectedKey: "",
|
||||
maxHeight: "auto",
|
||||
even: false,
|
||||
@ -518,9 +519,6 @@ const childrenExpandSpace = computed(() => {
|
||||
);
|
||||
});
|
||||
|
||||
/**
|
||||
* @remark 排除 hide 列
|
||||
*/
|
||||
const renderFixedStyle = (column: any, columnIndex: number) => {
|
||||
if (column.fixed) {
|
||||
if (column.fixed == "left") {
|
||||
@ -563,6 +561,55 @@ const renderFixedStyle = (column: any, columnIndex: number) => {
|
||||
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 列
|
||||
*/
|
||||
@ -628,6 +675,19 @@ const totalRowMethod = (column: any, dataSource: any[]) => {
|
||||
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(() => {
|
||||
window.onresize = null;
|
||||
});
|
||||
@ -642,7 +702,12 @@ onBeforeUnmount(() => {
|
||||
<slot name="toolbar"></slot>
|
||||
</div>
|
||||
<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>
|
||||
<i class="layui-icon layui-icon-slider"></i>
|
||||
</div>
|
||||
@ -660,15 +725,28 @@ onBeforeUnmount(() => {
|
||||
</div>
|
||||
</template>
|
||||
</lay-dropdown>
|
||||
|
||||
<!-- 导出 -->
|
||||
<div
|
||||
v-if="showToolbar('export')"
|
||||
class="layui-inline"
|
||||
title="导出"
|
||||
lay-event
|
||||
:style="toolbarStyle('export')"
|
||||
@click="exportData()"
|
||||
>
|
||||
<i class="layui-icon layui-icon-export"></i>
|
||||
</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>
|
||||
</div>
|
||||
</div>
|
||||
@ -739,7 +817,11 @@ onBeforeUnmount(() => {
|
||||
{
|
||||
textAlign: column.align,
|
||||
},
|
||||
renderFixedStyle(column, columnIndex),
|
||||
renderHeadFixedStyle(
|
||||
column,
|
||||
columnIndex,
|
||||
tableHeadColumn
|
||||
),
|
||||
]"
|
||||
>
|
||||
<template v-if="column.type == 'checkbox'">
|
||||
|
@ -73,12 +73,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
&-ellipsis &-text {
|
||||
display: inline-block;
|
||||
white-space: nowrap;
|
||||
word-wrap: normal;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis
|
||||
& &-text {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap
|
||||
}
|
||||
|
||||
& &-close-icon {
|
||||
|
@ -6,7 +6,7 @@ export default {
|
||||
<script lang="ts" setup>
|
||||
import "./index.less";
|
||||
import { LayIcon } from "@layui/icons-vue";
|
||||
import { computed, ref } from "vue";
|
||||
import { computed, nextTick, ref } from "vue";
|
||||
import { TinyColor } from "@ctrl/tinycolor";
|
||||
import { TagShape, TagType, TagVariant } from "./interface";
|
||||
|
||||
@ -49,13 +49,12 @@ const classTag = computed(() => [
|
||||
[`layui-tag-${props.type}`]: props.type,
|
||||
"layui-tag-bordered": props.bordered,
|
||||
"layui-tag-disabled": props.disabled,
|
||||
"layui-tag-ellipsis": props.maxWidth,
|
||||
},
|
||||
]);
|
||||
|
||||
const styleTag = computed(() => [
|
||||
{
|
||||
"max-width": props.maxWidth ?? "unset",
|
||||
"max-width": props.maxWidth ?? "100%",
|
||||
...useTagCustomStyle(props).value,
|
||||
},
|
||||
]);
|
||||
@ -105,10 +104,9 @@ function useTagCustomStyle(props: TagProps) {
|
||||
<span v-if="$slots.icon" class="layui-tag-icon">
|
||||
<slot name="icon" />
|
||||
</span>
|
||||
<span v-if="maxWidth" :style="styleTag" class="layui-tag-text">
|
||||
<span class="layui-tag-text">
|
||||
<slot />
|
||||
</span>
|
||||
<slot v-else />
|
||||
<span
|
||||
v-if="closable"
|
||||
class="layui-tag-close-icon"
|
||||
|
@ -86,7 +86,7 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
width: fit-content;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
overflow: hidden;
|
||||
|
||||
@ -108,6 +108,9 @@
|
||||
.layui-tag-input-inner-input {
|
||||
height: @@height - (@@inner-padding + @tag-input-boeder)* 2;
|
||||
vertical-align: middle;
|
||||
&:disabled {
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.layui-tag-input-inner {
|
||||
|
@ -300,24 +300,22 @@ defineExpose({
|
||||
</template>
|
||||
</LayToopTip>
|
||||
</template>
|
||||
<template v-if="!disabledInput">
|
||||
<input
|
||||
ref="inputRefEl"
|
||||
class="layui-tag-input-inner-input"
|
||||
:style="inputStyle"
|
||||
:disabled="disabled"
|
||||
:placeholder="placeholder"
|
||||
:readonly="readonly"
|
||||
@keydown.enter="handleEnter"
|
||||
@keyup="handleBackspaceKeyUp"
|
||||
@input="handleInput"
|
||||
@focus="handleFocus"
|
||||
@blur="handleBlur"
|
||||
@compositionstart="handleComposition"
|
||||
@compositionupdate="handleComposition"
|
||||
@compositionend="handleComposition"
|
||||
/>
|
||||
</template>
|
||||
<input
|
||||
ref="inputRefEl"
|
||||
class="layui-tag-input-inner-input"
|
||||
:style="inputStyle"
|
||||
:disabled="disabled || disabledInput"
|
||||
:placeholder="placeholder"
|
||||
:readonly="readonly"
|
||||
@keydown.enter="handleEnter"
|
||||
@keyup="handleBackspaceKeyUp"
|
||||
@input="handleInput"
|
||||
@focus="handleFocus"
|
||||
@blur="handleBlur"
|
||||
@compositionstart="handleComposition"
|
||||
@compositionupdate="handleComposition"
|
||||
@compositionend="handleComposition"
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
v-if="allowClear && tagData?.length && !disabled"
|
||||
|
@ -18,6 +18,8 @@
|
||||
padding: 6px 10px;
|
||||
resize: vertical;
|
||||
position: relative;
|
||||
transition: none;
|
||||
-webkit-transition: none;
|
||||
}
|
||||
|
||||
.layui-textarea-wrapper {
|
||||
|
@ -6,7 +6,8 @@ export default {
|
||||
|
||||
<script setup lang="ts">
|
||||
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";
|
||||
|
||||
export interface TextareaProps {
|
||||
@ -17,6 +18,7 @@ export interface TextareaProps {
|
||||
showCount?: boolean;
|
||||
allowClear?: boolean;
|
||||
maxlength?: number;
|
||||
autosize?: boolean | { minHeight: number; maxHeight: number };
|
||||
}
|
||||
|
||||
const props = defineProps<TextareaProps>();
|
||||
@ -31,6 +33,7 @@ interface TextareaEmits {
|
||||
}
|
||||
|
||||
const emit = defineEmits<TextareaEmits>();
|
||||
const textareaRef = ref<HTMLTextAreaElement | null>(null);
|
||||
const composing = ref(false);
|
||||
|
||||
const onInput = function (event: Event) {
|
||||
@ -78,11 +81,31 @@ const wordCount = computed(() => {
|
||||
}
|
||||
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>
|
||||
|
||||
<template>
|
||||
<div class="layui-textarea-wrapper">
|
||||
<textarea
|
||||
ref="textareaRef"
|
||||
class="layui-textarea"
|
||||
:value="modelValue"
|
||||
:placeholder="placeholder"
|
||||
|
@ -29,5 +29,7 @@
|
||||
--global-neutral-color-7: #cccccc;
|
||||
|
||||
--global-neutral-color-8: #c2c2c2;
|
||||
|
||||
--darkreader-border--global-primary-color: #0d796f;
|
||||
|
||||
}
|
@ -1,3 +1,4 @@
|
||||
export * from "./domUtil";
|
||||
export * from "./withInstall";
|
||||
export * from "./arrayUtil";
|
||||
export * from "./vueUtil";
|
||||
|
57
src/utils/vueUtil.ts
Normal file
57
src/utils/vueUtil.ts
Normal 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();
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user