Merge branch 'next' into feat-popup-menu

This commit is contained in:
sight 2022-06-30 19:38:03 +08:00
commit e16c2707f6
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();

View File

@ -318,7 +318,7 @@ const valueLv=ref(null)
::: title 触发方式
:::
::: demo 本组弹出效果依托于DropDown组件触发方式与其保持一致
::: demo 本组弹出效果依托于DropDown组件触发方式与其保持一致
<template>
<lay-cascader :options="options" placeholder="click触发(默认)" style="width:250px;margin-right:20px"></lay-cascader>
<lay-cascader :options="options" placeholder="hover触发" style="width:250px;margin-right:20px" trigger="hover"></lay-cascader>

View File

@ -362,6 +362,104 @@ export default {
:::
::: title 菜单插槽
:::
::: demo
<template>
<lay-dropdown>
<lay-button type="primary">图标菜单</lay-button>
<template #content>
<lay-dropdown-menu>
<lay-dropdown-menu-item>
<template #prefix><lay-icon type="layui-icon-return"></lay-icon></template>
<template #default>返回</template>
<template #suffix><span style="margin-left: 50px; font-size:10px">Alt + 向左键</span></template>
</lay-dropdown-menu-item>
<lay-dropdown-menu-item>
<template #prefix><lay-icon type="layui-icon-refresh"></lay-icon></template>
<template #default>刷新</template>
<template #suffix><span style="font-size:10px">Ctrl + R</span></template>
</lay-dropdown-menu-item>
<lay-line></lay-line>
<lay-dropdown-menu-item>
<template #prefix><lay-icon type="layui-icon-about"></lay-icon></template>
<template #default>更多</template>
<template #suffix><lay-icon type="layui-icon-right"></lay-icon></template>
</lay-dropdown-menu-item>
</lay-dropdown-menu>
</template>
</lay-dropdown>
</template>
<script>
import { ref } from 'vue'
export default {
setup() {
return {
}
}
}
</script>
:::
::: title 多级菜单
:::
::: demo
<template>
<lay-dropdown>
<lay-button type="primary">下拉菜单</lay-button>
<template #content>
<lay-dropdown-menu>
<lay-dropdown-menu-item>
<template #prefix><lay-icon type="layui-icon-list"></lay-icon></template>
<template #default>选项一</template>
</lay-dropdown-menu-item>
<lay-dropdown-sub-menu>
<template #prefix><lay-icon type="layui-icon-set-sm"></lay-icon></template>
<template #default>选项二</template>
<template #content>
<lay-dropdown-menu-item>子菜单一</lay-dropdown-menu-item>
<lay-dropdown-menu-item>子菜单二</lay-dropdown-menu-item>
<lay-dropdown-sub-menu>
<template #default>子菜单三</template>
<template #content>
<lay-dropdown-menu-item>菜单1</lay-dropdown-menu-item>
<lay-dropdown-menu-item>菜单2</lay-dropdown-menu-item>
<lay-dropdown-menu-item>菜单3</lay-dropdown-menu-item>
</template>
</lay-dropdown-sub-menu>
</template>
</lay-dropdown-sub-menu>
<lay-dropdown-menu-item>
<template #prefix><lay-icon type="layui-icon-share"></lay-icon></template>
<template #default>选项三</template>
</lay-dropdown-menu-item>
</lay-dropdown-menu>
</template>
</lay-dropdown>
</template>
<script>
import { ref } from 'vue'
export default {
setup() {
return {
}
}
}
</script>
:::
::: title 其它属性
:::
@ -498,6 +596,47 @@ export default {
:::
::: title Dropdown Menu Item 插槽
:::
::: table
| 插槽 | 描述 | 参数 |
| ------- | -------- | ------ |
| prefix | 前缀 | -- |
| default | 默认 | -- |
| suffix | 后缀|--|
:::
::: title Dropdown Sub Menu 属性
:::
::: table
| 插槽 | 描述 | 参数 |
| ------- | -------- | ------ |
| trigger | 触发方式,类型 `string` 或 trigger 数组,默认 hover | `click` `hover` `focus` `contextMenu` |
| disabled | 是否禁用触发 | `true` `false` |
| placement | 下拉面板位置,默认 right-top |`top` `bottom` `right` `left` `*-left` `*-right` `*-top` `*-bottom`|
| contentOffset | 下拉面板距离触发器的偏移距离,默认 2| -|
:::
::: title Dropdown Sub Menu 插槽
:::
::: table
| 插槽 | 描述 | 参数 |
| ------- | -------- | ------ |
| prefix | 前缀 | -- |
| default | 默认 | -- |
| suffix | 后缀|--|
| content | 下拉面板内容|--|
:::
::: contributor dropdown
:::

View File

@ -15,7 +15,7 @@
<template>
<div>
<div style="margin-bottom: 10px">
<lay-switch v-model="loading" active-text="加载" inactive-text="关闭"></lay-switch>
<lay-switch v-model="loading" onswitch-text="加载" unswitch-text="关闭"></lay-switch>
</div>
<lay-skeleton :rows="4" :loading="loading" animated>
<p style="margin-bottom: 18px">1 layui-vue , 基 于 vue 3.0 的 桌 面 端 组 件 库 , layui 的 另 一 种 呈 现 方 式</p>
@ -50,7 +50,7 @@ export default {
<template>
<div>
<div style="margin-bottom: 10px">
<lay-switch v-model="loading" active-text="加载" inactive-text="关闭"></lay-switch>
<lay-switch v-model="loading" onswitch-text="加载" unswitch-text="关闭"></lay-switch>
</div>
<lay-skeleton :loading="loading" animated>
<template #skeleton>

View File

@ -97,7 +97,7 @@ export default {
::: demo
<template>
<lay-switch v-model="active4" active-text="白天" inactive-text="夜间"></lay-switch>
<lay-switch v-model="active4" onswitch-text="白天" unswitch-text="夜间"></lay-switch>
</template>
<script>

View File

@ -36,6 +36,54 @@ export default {
:::
::: title 标题插槽
:::
::: demo
<template>
<lay-tab v-model="current11" :allow-close="true">
<lay-tab-item title="选项一" id="1">
<template #title>
<lay-icon type="layui-icon-console"></lay-icon>
<span style="margin-left:10px">选项一</span>
</template>
<div style="padding:20px">选项一</div>
</lay-tab-item>
<lay-tab-item title="选项一" id="2">
<template #title>
<lay-icon type="layui-icon-user"></lay-icon>
<span style="margin-left:10px">选项一</span>
</template>
<div style="padding:20px">选项二</div>
</lay-tab-item>
<lay-tab-item title="选项一" id="3">
<template #title>
<lay-icon type="layui-icon-set"></lay-icon>
<span style="margin-left:10px">选项一</span>
</template>
<div style="padding:20px">选项三</div>
</lay-tab-item>
</lay-tab>
</template>
<script>
import { ref } from 'vue'
export default {
setup() {
const current11 = ref("1")
return {
current11
}
}
}
</script>
:::
::: title 简约模式
:::
@ -359,6 +407,17 @@ export default {
:::
::: title Tab Item 插槽
:::
::: table
| 属性 | 描述 | 参数 |
| -------- | -------- | ------ |
| title | 标题 | -- |
:::
::: contributor tab
:::

View File

@ -530,6 +530,74 @@ export default {
:::
::: title 定义样式
:::
::: demo 通过 `cellStyle` `rowStyle` `cellClassName` `rowClassName` 属性, 自定义单元格样式。
<template>
<lay-table :columns="columns1" :dataSource="dataSource1" :cellStyle="cellStyle" :rowStyle="rowStyle"></lay-table>
</template>
<script>
import { ref } from 'vue'
export default {
setup() {
const columns1 = [
{
title:"账户",
width:"200px",
key:"username"
},{
title:"密码",
width: "180px",
key:"password"
},{
title:"年龄",
width: "180px",
key:"age"
},{
title:"备注",
width: "180px",
key:"remark",
ellipsisTooltip: true,
}
]
const dataSource1 = [
{username:"root", password:"root", age:"18", remark: 'layui - vue谐音类 UI) '},
{username:"root", password:"root", age:"18", remark: 'layui - vue谐音类 UI) '},
{username:"woow", password:"woow", age:"20", remark: 'layui - vue谐音类 UI) '},
{username:"woow", password:"woow", age:"20", remark: 'layui - vue谐音类 UI) '},
{username:"woow", password:"woow", age:"20", remark: 'layui - vue谐音类 UI) '}
]
const cellStyle = function(row, column, rowIndex, columnIndex) {
if(columnIndex % 2 == 0) {
return 'color:red';
}
}
const rowStyle = function(row, rowIndex) {
if(rowIndex % 2 == 0) {
return 'color:blue';
}
}
return {
columns1,
dataSource1,
cellStyle,
rowStyle
}
}
}
</script>
:::
::: title Table 属性
:::
@ -549,6 +617,11 @@ export default {
| height | 表格高度 | `number` | -- | -- |
| maxHeight | 表格最大高度 | `number` | -- | -- |
| even | 斑马条纹 | `boolean` | `false` | `true` `false` |
| cellStyle | 列样式 function(row, column, rowIndex, columnIndex) | `string` `function` | -- | -- |
| rowStyle | 行样式 function(row, rowIndex) | `string` `function` | -- | -- |
| cellClassName | 列类名称 function(row, column, rowIndex, columnIndex) | `string` `function` | -- | -- |
| rowClassName | 行类名称 function(row, rowIndex) | `string` `function` | -- | -- |
:::
::: title Table 事件

View File

@ -7,7 +7,12 @@
::: demo
<template>
<div style="width: 125px; ">
<div style="max-width: 100%; ">
<lay-tooltip content="缩小浏览器时...假装这里有文字提示假装这里有文字提示假装这里有文字提示假装这里有文字提示" ref="tooltip" :isAutoShow="true">
缩小浏览器时...假装这里有文字提示假装这里有文字提示假装这里有文字提示假装这里有文字提示
</lay-tooltip>
</div>
<div style="max-width: 124px; margin-top: 20px">
<lay-tooltip content="假装这里有文字提示" ref="tooltip" :isAutoShow="true">
假装这里有文字提示
</lay-tooltip>
@ -105,8 +110,8 @@ setup() {
<lay-tooltip :content="content" :is-dark="isDark" :disabled="!disabled">
<lay-button>tooltip</lay-button>
</lay-tooltip>
<lay-switch v-model="disabled" active-text="启用tooltip" inactive-text="禁用tooltip" style="margin-left: 5px;"></lay-switch>
<lay-switch v-model="isDark" active-text="深色" inactive-text="浅色" style="margin-left: 5px;"></lay-switch>
<lay-switch v-model="disabled" onswitch-text="启用tooltip" unswitch-text="禁用tooltip" style="margin-left: 5px;"></lay-switch>
<lay-switch v-model="isDark" onswitch-text="深色" unswitch-text="浅色" style="margin-left: 5px;"></lay-switch>
</template>
<script>

View File

@ -11,17 +11,55 @@
<template>
<lay-timeline>
<lay-timeline-item title="1.2.x">
<ul>
<a name="1-2-3"></a>
<ul>
<a name="1-2-6"></a>
<li>
<h3>1.2.3 <span class="layui-badge-rim">2022-06-26</span></h3>
<h3>1.2.6 <span class="layui-badge-rim">2022-07-01</span></h3>
<ul>
<li>[修复] tooltip 组件设置isAutoShow 属性时宽度设置max-width 时拖动浏览器时出现...时tooltip不显示问题。 by @dingyongya</li>
<li>[修复] table 组件设置 ellipsisTooltip 属性时 出现...时tooltip不显示问题。by @dingyongya</li>
</ul>
</li>
</ul>
<ul>
<a name="1-2-5"></a>
<li>
<h3>1.2.5 <span class="layui-badge-rim">2022-06-29</span></h3>
<ul>
<li>[新增] dropdown-sub-menu 组件, 用于呈现二级下拉菜单。</li>
<li>[新增] dropdown-menu-item 组件 default 插槽, 自定义内容。</li>
<li>[新增] dropdown-menu-item 组件 prefix 插槽, 自定义内容前缀。</li>
<li>[新增] dropdown-menu-item 组件 suffix 插槽, 自定义内容后缀。</li>
<li>[新增] table 组件 rowStyle 属性, 自定义行样式, 值为 function(row, rowIndex) 函数或 string 类型。</li>
<li>[新增] table 组件 rowClassName 属性, 自定义行样式, 值为 function(row, rowIndex) 函数或 string 类型。</li>
<li>[新增] table 组件 cellStyle 属性, 自定义单元格样式, 值为 function(row, column, rowIndex, columnIndex) 函数或 string 类型。</li>
<li>[新增] table 组件 cellClassName 属性, 自定义单元格样式, 值为 function(row, column, rowIndex, columnIndex) 函数或 string 类型。</li>
<li>[修复] icon-picker 组件在颜色面板中拉动选取颜色,触碰颜色面板边角时,导致色相变为 0。 </li>
<li>[修复] provider 组件切换主题 Embedded Dark Reader cannot access a cross-origin resource。</li>
<li>[修复] switch 组件 onswitch-text 与 unswitch-text 使用文档。</li>
</ul>
</li>
</ul>
<ul>
<a name="1-2-4"></a>
<li>
<h3>1.2.4 <span class="layui-badge-rim">2022-06-28</span></h3>
<ul>
<li>[新增] select 组件 多选模式下 的搜索功能。 </li>
<li>[新增] table 组件 even 属性, 用于开启斑马条纹背景样式。</li>
<li>[新增] dropdown 组件 placement 属性 right left right-bottom right-top left-bottom left-top 值。</li>
<li>[新增] affix 组件, 使用锚点,可以将内容固定在容器内,并且不随容器的滚动而滚动,常用于侧边菜单导航等。 </li>
<li>[新增] affix 组件 position 属性, 用于设置固定的位置, 可选值为 top 与 bottom。</li>
<li>[新增] affix 组件 offset 属性, 定位偏移量, 默认为 0。</li>
<li>[新增] affix 组件 target 属性, 定位时的参考容器, 默认为 document.body。</li>
<li>[修复] date-picker 组件 prefix-icon 前置图标无边距的问题。</li>
<li>[修复] input 组件 foucs 状态时 border 颜色为 #d2d2d2</li>
<li>[修复] cascader 组件 children 不能为空的问题。</li>
<li>[修复] scroll 组件 按需加载时 index.css 不存在的问题。</li>
</ul>
</li>
</ul>
<ul>
<ul>
<a name="1-2-2"></a>
<li>
<h3>1.2.2 <span class="layui-badge-rim">2022-06-26</span></h3>

View File

@ -140,6 +140,25 @@
</lay-row>
</lay-card>
<lay-card>
<lay-row>
<lay-col md="2">
<lay-avatar src="https://portrait.gitee.com/uploads/avatars/user/495/1486240_SmallWai_1656341928.png"></lay-avatar>
</lay-col>
<lay-col md="3">
0o张不歪o0
</lay-col>
<lay-col md="8">
焦点layui-vue layer-vue
</lay-col>
<lay-col md="4">
地点:中国 成都
</lay-col>
<lay-col md="6">
其他Gitee Github
</lay-col>
</lay-row>
</lay-card>
<br>
::: title 社区伙伴

View File

@ -21,6 +21,6 @@
<div id="app">
<!--app-html-->
</div>
<script type="module" src="./entry-client.ts"></script>
<script type="module" src="./src/entry-client.ts"></script>
</body>
</html>

View File

@ -6,7 +6,7 @@ button + button {
button {
height: 38px;
line-height: 36px;
padding: 0 18px;
padding: 0 18px !important;
color: #fff;
font-size: 14px;
text-align: center;

View File

@ -5,7 +5,7 @@
</form>
</template>
<script setup>
const emit = defineEmits("add", "sub");
const emit = defineEmits(["add", "sub"]);
const add = () => {
emit("add");

View File

@ -1,55 +1,27 @@
<template>
<div class="lay-code">
<div id="source" class="source">
<slot />
<slot></slot>
<div v-if="$slots.description" class="description">
<slot name="description" />
<slot name="description"></slot>
</div>
</div>
<div ref="meta" class="meta">
<div class="language-html">
<slot name="code" />
<slot name="code"></slot>
</div>
</div>
<div :class="{ 'is-fixed': isFixContorl }" class="control">
<i class="layui-icon layui-icon-file btn" @click="copy"
><svg
t="1646244236057"
class="icon"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="5505"
width="18"
height="18"
>
<path
d="M768 682.666667V170.666667a85.333333 85.333333 0 0 0-85.333333-85.333334H170.666667a85.333333 85.333333 0 0 0-85.333334 85.333334v512a85.333333 85.333333 0 0 0 85.333334 85.333333h512a85.333333 85.333333 0 0 0 85.333333-85.333333zM170.666667 170.666667h512v512H170.666667z m682.666666 85.333333v512a85.333333 85.333333 0 0 1-85.333333 85.333333H256a85.333333 85.333333 0 0 0 85.333333 85.333334h426.666667a170.666667 170.666667 0 0 0 170.666667-170.666667V341.333333a85.333333 85.333333 0 0 0-85.333334-85.333333z"
p-id="5506"
></path></svg
></i>
<i class="layui-icon layui-icon-fonts-code btn" @click="toggle"
><svg
t="1646244296040"
class="icon"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="7127"
width="18"
height="18"
>
<path
d="M217.301333 414.933333a21.333333 21.333333 0 0 1-1.066666-30.165333l29.098666-31.232a21.333333 21.333333 0 0 1 30.165334-1.024l236.117333 220.16 236.16-220.16a21.333333 21.333333 0 0 1 30.122667 1.024l29.098666 31.232a21.333333 21.333333 0 0 1-1.024 30.165333L541.738667 661.333333l-13.44 14.421334a21.290667 21.290667 0 0 1-16.725334 6.741333 21.290667 21.290667 0 0 1-16.64-6.741333l-13.44-14.506667z"
p-id="7128"
></path></svg
></i>
<i class="layui-icon layui-icon-play btn" @click="onPlayground" />
<i class="layui-icon layui-icon-file btn" @click="copy"></i>
<i class="layui-icon layui-icon-fonts-code btn" @click="toggle"></i>
</div>
</div>
</template>
<script setup lang="ts">
import { onMounted, onUnmounted, ref, watch } from "vue";
import { usePlayGround } from "../composable/usePlayground";
const meta = ref<HTMLElement>({} as HTMLElement);
const isFixContorl = ref(false);
@ -61,6 +33,13 @@ const toggle = function () {
show.value = !show.value;
};
const onPlayground = async function () {
const foundCode = meta.value.querySelector(".language-html");
const sourceCode = foundCode?.textContent ?? "";
const { link } = await usePlayGround(sourceCode, true);
window.open(link);
};
const copy = function () {
const foundCodes = meta.value.getElementsByClassName("language-html");
const foundCode = foundCodes[0];
@ -94,7 +73,9 @@ const copy = function () {
}
if (successful) {
window.alert("复制成功");
} else {
window.alert("复制失败");
}
};

View File

@ -0,0 +1,147 @@
import type { BuiltInParserName } from "prettier";
import { zlibSync, unzlibSync, strToU8, strFromU8 } from "fflate";
import extraCss from "../assets/css/index.css?raw";
const scriptRe = /<script[^>]*>([\s\S]*)<\/script>/;
const exportDefaultRe = /export\s*default\s*\{([\s\S]*)\}\;?\s*<\/script>/;
const setupRe = /setup\s*\(\)\s*\{([\s\S]*)return\s*\{([\s\S]*)\}\;?\s*\}\;?/;
const layerRe = /import\s*\{\s*layer\s*\}\s*from\s*[\"|\'][^\;\"\']*[\"|\']/;
const MAIN_FILE_NAME = "App.vue";
/**
* URI hash, playground
* @param source
* @param convertSetupSugar setup
* @returns URI hsah playground
}
*/
export const usePlayGround = async (
source: string,
convertSetupSugar: boolean
) => {
const decodeCode = source;
const scriptResult = decodeCode.match(scriptRe);
// 替换 script 标签
// $1 正则第一个括号匹配的内容
let code: string | undefined = decodeCode;
if (convertSetupSugar) {
if (scriptResult) {
code = decodeCode.replace(
scriptRe,
`<script lang="ts" setup>$1
</script>`
);
} else {
code = `${decodeCode}
<script lang="ts" setup>
</script>`;
}
// 去除 export default,保留其中的内容
const exportDefaultResult = code.match(exportDefaultRe);
if (exportDefaultResult) {
code = code.replace(
exportDefaultRe,
trimBr(exportDefaultResult[1] + `</script>`).trim()
);
// console.log("export",code);
}
// 去除 setup 函数,保留其中的内容
const setupResult = code.match(setupRe);
if (setupResult) {
code = code.replace(setupRe, trimBr(setupResult[1]));
// console.log("setup",code);
}
}
// 替换 layer 引入语句
// playground 中使用最新版 layer 请从 @layui/layer-vue 引入
if (code.match(layerRe)) {
code = code.replace(layerRe, `import { layer } from "@layui/layer-vue"`);
// console.log("layer",code);
}
code = await formatCode(MAIN_FILE_NAME, code);
const originCode = {
[MAIN_FILE_NAME]: code,
"index.css": extraCss,
};
const encoded = utoa(JSON.stringify(originCode));
const link = `https://layui-vue.gitee.io/sandbox-vue/#${encoded}`;
return {
code,
encoded,
link,
};
};
/**
*
* @returns
*/
async function formatCode(filename: string, data: string) {
const [format, parserHtml, parserTypeScript, parserBabel, parserPostcss] =
await Promise.all([
import("prettier/standalone").then((r) => r.format),
import("prettier/parser-html").then((m) => m.default),
import("prettier/parser-typescript").then((m) => m.default),
import("prettier/parser-babel").then((m) => m.default),
import("prettier/parser-postcss").then((m) => m.default),
]);
let code = data;
let parser: BuiltInParserName;
if (filename.endsWith(".vue")) {
parser = "vue";
} else if (filename.endsWith(".js")) {
parser = "babel";
} else if (filename.endsWith(".ts")) {
parser = "typescript";
} else if (filename.endsWith(".json")) {
parser = "json";
} else {
return;
}
code = format(code, {
parser,
plugins: [parserHtml, parserTypeScript, parserBabel, parserPostcss],
semi: false, // 语句末尾打印分号
singleQuote: true, // 使用单引号
vueIndentScriptAndStyle: false, // 是否缩进 Vue 文件中的 script 和 style 标签
});
return code;
}
/**
*
* @param str
* @returns
*/
function trimBr(str: string): string {
return str.replace(/(^[\r\n]*)|([\r\n]*$)/, "");
}
export function utoa(data: string): string {
const buffer = strToU8(data);
const zipped = zlibSync(buffer, { level: 9 });
const binary = strFromU8(zipped, true);
return btoa(binary);
}
export function atou(base64: string): string {
const binary = atob(base64);
// zlib header (x78), level 9 (xDA)
if (binary.startsWith("\x78\xDA")) {
const buffer = strToU8(binary, true);
const unzipped = unzlibSync(buffer);
return strFromU8(unzipped);
}
// old unicode hacks for backward compatibility
// https://base64.guru/developers/javascript/examples/unicode-strings
return decodeURIComponent(escape(binary));
}

View File

@ -0,0 +1,523 @@
<fieldset class="layui-elem-field layui-field-title">
<legend>消息</legend>
</fieldset>
::: demo 通过 layer.msg(content, options) 创建消息窗, 第一个参数`msg`为消息内容, 第二参数`options`为可选配置, 常用于配置`time`超时时间等。
<template>
<button @click="msg">普通消息</button>
<button @click="success">成功消息</button>
<button @click="failure">失败消息</button>
<button @click="warm">警告消息</button>
<button @click="info">锁定消息</button>
</template>
<script>
import { layer } from "../../../../layer/src/index"
const msg = function() {
layer.msg("普通消息", { time: 1000 })
}
const success = function() {
layer.msg("成功消息", { time: 1000, icon: 1})
}
const failure = function() {
layer.msg("失败消息", { time: 1000, icon: 2})
}
const warm = function() {
layer.msg("警告消息", { time: 1000, icon: 3})
}
const info = function() {
layer.msg("疑问消息", { time: 1000, icon: 4})
}
</script>
:::
<fieldset class="layui-elem-field layui-field-title">
<legend>确认</legend>
</fieldset>
::: demo 通过 layer.confirm(msg, options) 创建确认框, 第一个参数`msg`为文本消息, 第二个参数`options`为选项配置, 你可以使用`options`的`btn`属性定义操作。
<template>
<button @click="openConfirm1">确认框</button>
<button @click="openConfirm2">询问框</button>
</template>
<script setup>
import { layer } from "../../../../layer/src/index"
const openConfirm1 = function() {
layer.confirm("layui-vue 1.0.0 已经发布")
}
const openConfirm2 = function() {
layer.confirm("你如何看待 layui-vue 的发布",
{btn:
[
{text:'站着看', callback: function(id){layer.close(id);}},
{text:'坐着看', callback: function(id){layer.close(id);}}
]
})
}
</script>
:::
<fieldset class="layui-elem-field layui-field-title">
<legend>加载</legend>
</fieldset>
::: demo 通过 layer.load(type, options) 创建加载层, 第一个参数`type`为加载动画样式, 第二个参数`options`为可选配置, 常用于设置`time`加载时长`shade`遮盖层等。
<template>
<button @click="load1">加载1</button>
<button @click="load2">加载2</button>
<button @click="load3">加载3</button>
<button @click="load4">加载4</button>
</template>
<script setup>
import { layer } from "../../../../layer/src/index"
const load1 = function() {
layer.load(0, {time: 2000})
}
const load2 = function() {
layer.load(1, {time: 2000})
}
const load3 = function() {
layer.load(2, {time: 2000})
}
const load4 = function() {
layer.msg("加载中...",{icon: 16, time: 2000})
}
</script>
:::
<fieldset class="layui-elem-field layui-field-title">
<legend>模态</legend>
</fieldset>
::: demo 通过 layer.open(option) 创建模态窗, 目前支持`iframe` `page`等类型, 你可以使用`options`选项开启`Resize` `Offset`等更多特性。
<template>
<lay-row :space="30" >
<lay-col :span="24">
<button @click="open">小试牛刀</button>
<button @click="openSize">指定尺寸</button>
<button @click="openOffset">指定位置</button>
<button @click="openIframe">远程窗体</button>
<button @click="openHtml">代码片段</button>
</lay-col>
<lay-col :span="24">
<button @click="openVNode">虚拟节点</button>
<button @click="openMaxmin">缩小放大</button>
<button @click="openResize">尺寸拉伸</button>
<button @click="openIndex">设置层级</button>
<button @click="openAreaAuto">内容适应</button>
<button @click="openAreaAuto2">内容适应2</button>
</lay-col>
</lay-row>
</template>
<script setup>
import { layer } from "../../../../layer/src/index"
const open = function() {
layer.open({
type: 1,
title: "标题",
content: "内容"
})
}
const openSize = function() {
layer.open({
type: 1,
title: "标题",
area: ['400px','400px'],
content: "内容"
})
}
const openOffset = function() {
layer.open({
type: 1,
title: "标题",
offset: ['100px','100px'],
content: "内容"
})
}
const openIframe = function() {
layer.open({
type: 2,
title: "标题",
resize: true,
area: ['500px','500px'],
content: "http://layui-vue.pearadmin.com"
})
}
const openHtml = function() {
layer.open({
type: 1,
title: "标题",
isHtmlFragment: true,
content: "<p style='color:red;'>内容</p>"
})
}
const openVNode = function() {
layer.open({
type: 1,
title: "标题",
content: h('button', { style: 'margin: 10px;' },'按钮')
})
}
const openMaxmin = function() {
layer.open({
type: 1,
title: "标题",
maxmin: true,
content: "内容",
full:(e)=>{console.log('full',e)},
min:(e)=>{console.log('min',e)},
restore:(e)=>{console.log('restore',e)}
})
}
const openResize = function() {
layer.open({
type: 1,
title: "标题",
resize: true,
content: "内容"
})
}
const openIndex = function() {
layer.open({
type: 1,
title: "标题",
zIndex: 999,
content: "设置层级"
})
}
const openAreaAuto = function(){
layer.open({
type:1,
title:"area:auto",
isHtmlFragment:true,
content:"<img src='https://chixian.oss-cn-hangzhou.aliyuncs.com/20210819230007_346ce.jpeg'/>"
})
}
const openAreaAuto2 = function(){
layer.open({
type:1,
title:"area:auto",
offset:['10px','50%'],
isHtmlFragment:true,
content:"<img src='https://chixian.oss-cn-hangzhou.aliyuncs.com/20210819230007_346ce.jpeg'/>",
})
}
</script>
:::
<fieldset class="layui-elem-field layui-field-title">
<legend>抽屉</legend>
</fieldset>
::: demo 通过 layer.drawer(options) 创建抽屉层, `options`选项配置, 抽屉本质上是一个特殊的模态窗, 你可以使用`offset`选项来设置方向。
<template>
<button @click="openTop"></button>
<button @click="openBottom"></button>
<button @click="openLeft"></button>
<button @click="openRight"></button>
</template>
<script setup>
import { layer } from "../../../../layer/src/index"
const openTop = function() {
layer.drawer({
title: "标题",
content: "内容",
offset: "t"
})
}
const openBottom = function() {
layer.drawer({
title: "标题",
content: "内容",
offset: "b"
})
}
const openLeft = function() {
layer.drawer({
title: "标题",
content: "内容",
offset: "l"
})
}
const openRight = function() {
layer.drawer({
title: "标题",
content: "内容",
offset: "r"
})
}
</script>
:::
<fieldset class="layui-elem-field layui-field-title">
<legend>图片</legend>
</fieldset>
::: demo 通过 layer.photos(options) 创建图片预览弹层, 参数`options`主要传入预览的图片链接。
<template>
<button @click="signleImg">图片查看</button>
<button @click="signleImg2">图片标题</button>
<button @click="groupImg">图片分组</button>
</template>
<script>
import { layer } from "../../../../layer/src/index"
const signleImg = function() {
layer.photos("/src/assets/logo.jpg")
}
const signleImg2 = function() {
layer.photos({
imgList:[{src:'/src/assets/logo.jpg',alt:'layer for vue'}]
})
}
const groupImg = function() {
layer.photos({
imgList:[
{ src:'http://www.pearadmin.com/assets/images/un8.svg', alt:'图片1'},
{ src:'http://www.pearadmin.com/assets/images/un32.svg', alt:'图片2'}
]
})
}
</script>
:::
<fieldset class="layui-elem-field layui-field-title">
<legend>通知</legend>
</fieldset>
::: demo 通过 layer.notifiy(options) 创建通知。
<template>
<lay-row :space="30" >
<lay-col :span="24">
<button @click="baseNotifiy">基本使用</button>
<button @click="baseNotifiyRB">右下</button>
<button @click="baseNotifiyLT">左上</button>
<button @click="baseNotifiyLB">左下</button>
<button @click="baseNotifiyTime0">不主动关闭</button>
<button @click="baseNotifiyHtml">HTML解析</button>
</lay-col>
<lay-col :span="24">
<button @click="NotifiySuccess">成功通知</button>
<button @click="NotifiyFailure">失败通知</button>
<button @click="NotifiyWarm">警告通知</button>
<button @click="NotifiyInfo">锁定通知</button>
</lay-col>
</lay-row>
</template>
<script>
import { layer } from "../../../../layer/src/index"
const baseNotifiy = function() {
layer.notifiy({
title:"这是标题",
content:"默认就是右上,也是用得最多的"
})
}
const baseNotifiyRB = function() {
layer.notifiy({
title:"这是标题",
content:"我出现在右下",
offset:'rb',
})
}
const baseNotifiyLT = function() {
layer.notifiy({
title:"这是标题",
content:"我出现在左上",
offset:'lt',
})
}
const baseNotifiyLB = function() {
layer.notifiy({
title:"这是标题",
content:"我出现在左下",
offset:'lb',
})
}
const baseNotifiyTime0 = function() {
layer.notifiy({
title:"这是标题",
content:"不会主动关闭,请点击右上角关闭图标",
time:0
})
}
const baseNotifiyHtml = function() {
layer.notifiy({
title:"这是标题,有图片时请设置area参数",
isHtmlFragment:true,
content:"<img src='https://chixian.oss-cn-hangzhou.aliyuncs.com/20210819230007_346ce.jpeg'/>",
area:['330px','220px'],
time:3000
})
}
const NotifiySuccess=function(){
layer.notifiy({
title:"Success",
content:"默认就是右上,也是用得最多的",
icon:1
})
}
const NotifiyFailure=function(){
layer.notifiy({
title:"Error",
content:"默认就是右上,也是用得最多的",
icon:2
})
}
const NotifiyWarm=function(){
layer.notifiy({
title:"Warming",
content:"默认就是右上,也是用得最多的",
icon:3
})
}
const NotifiyInfo=function(){
layer.notifiy({
title:"Question",
content:"默认就是右上,也是用得最多的",
icon:4
})
}
</script>
:::
<fieldset class="layui-elem-field layui-field-title">
<legend>销毁</legend>
</fieldset>
::: demo 我们提供 layer.close(id) 与 layer.closeAll() 函数实现弹出层的主动销毁。
<template>
<button @click="open">打开</button>
<button @click="close">销毁</button>
<button @click="closeAll">销毁全部</button>
</template>
<script setup>
import { layer } from "../../../../layer/src/index";
let id = null;
const open = function() {
id = layer.open({
title: "标题",
content: "内容",
shade: false
})
}
const close = function() {
layer.close(id);
}
const closeAll = function() {
layer.closeAll();
}
</script>
:::
<fieldset class="layui-elem-field layui-field-title">
<legend>通讯</legend>
</fieldset>
::: demo 👉 查看 [Children1.vue](https://layui-vue.gitee.io/sandbox-vue/#eNqdUkGO00AQ/ErLFyeSY5OrSaJFK87LA3yZtTub2R3PjGba0UaWD5w4cUTiCDwA+AGfQZH4BT127HXEjZu7XNVTXd1t9Mba9NhglEcbwtoqQbgrNMDmviEyGm5KJcunbREZi/rW1NZo1LQuot3508/zx++bbCAOIqltQ0Ani6wgfKYiguOqNhUqBipBInVYC/fEeMaSTTY9CoXm2pdOWgIl9EPo4JnnkRrLXMlvO4IWHIqS5BETOCRceKOOODmDDvbO1BDzUPFMpMQJ3fTzhstGZj24umLeHqSqHOo7jRdumo3YOiTFVOi9lkZ7gjATbCdPizb8HmbMIf796+v5/Y84KXS3fNFcR8nqxRK2O+ilvaU0MIZWACRJIfc6f/nw59vn0Cug3IlYncNhMbOc8KS9o2554fmDqFi9F8pjDwUjXUh+iJqDjZJoGH5VC5s+eqP5GtriAvIK8rbrmHQVw78Hszeu7r/+4w5Ykl30s5N4uYfxBsYEsZbEuVW4lxrfcuEXcSmUwiqe5WydsX6ivQvVEGowkMPd/SOWNK5mFkh4l04KwZe8iIqR4G3YjxVVJfVDDutX9vn1GGVg76LuL+G7LGU=), 通过 h() 函数的第二个参数向 Children1.vue 传递响应式变量。
<template>
<button @click="openComponent1">数据</button>
<input type="text" v-model="data.remark" />
</template>
<script setup>
import { reactive, h, resolveComponent } from 'vue'
import { layer } from "../../../../layer/src/index"
const data = reactive({
remark: "信息"
})
const ChildrenOne = resolveComponent('Children1')
const openComponent1 = () => {
layer.open({
title: '标题',
content: h(ChildrenOne, { data }),
shade: false,
})
}
</script>
:::
::: demo 👉 查看 [Children2.vue](https://layui-vue.gitee.io/sandbox-vue/#eNqNksFum0AQhl9lxAWsYmPnSG2UKOoTtLfSA4Yh3hZ2VzC4sRDnSO2h1x6jPkQP7esklfoWnQXWAaWHcGJmv5n9d+ZvnSutV8cGndDZEpa6SAijWAJs9w2RknCZFiL9tIsdpVFeq1IriZIuYid6+PX14ffPbTCAQ5GQuiGgk0auILyl2IHjslQZFpyQTbnnRMDsNpjcxmGdVkITFIm8MZU1YzVSo/lU8J0VQQsVJimJI/pw8DnIoYO8UiW4rN+dcEVywup8eMlhI4I+uZyR1wdRZBXKd5/VyK4Cm7swQ2E0lqmSNYGulIbdWYLXdgt7ZF7VH+XeZr1ec96ezEfGjLeAXQStGVWvZ2UIr48BSFCBIbh/7u/+/vju+kOWOxFXh3DwJnr9oYn5jLKRBVDyKsu8CuvFEwC9wtUxKRpkDZPgFWws1E1avG32L26xfN6iW/Q/ZkCdWfSwWl6k4zvD5JdlolcfayXZdW08JnnlYdt1DM128NyYuarK/u/JpKPhhoitczZtkmXs1Mcv9zObvqCwbtip0ePdt7m/g/HymX2tea1h7fqxFMTTyjAXEt9wUHvvXVbk+i63dz9MnMLZuT2gr/Z6fBikRbn0/6jpadHJ1I0+OhUIdcpuyzhj3jCUam4v5E0Im7W+fW33ZejI6f4B7opkgw==), 通过 h() 函数的第二个参数声明 onXxx() 形式的函数完成 Children2.vue 的事件监听。
<template>
<button @click="openComponent2">事件</button>
<input type="text" v-model="numb" />
</template>
<script setup>
import { reactive, h, resolveComponent, ref } from 'vue'
import { layer } from "../../../../layer/src/index"
const prop = reactive({})
const numb = ref(1000)
const ChildrenTwo = resolveComponent('Children2')
const openComponent2 = () => {
layer.open({
title: '标题',
content: h(ChildrenTwo, {
prop,
onAdd(res){
numb.value = numb.value + 1;
}, onSub(res) {
numb.value = numb.value - 1;
}
}),
})
}
</script>
:::

View File

@ -69,445 +69,6 @@ const changeVisible = () => {
}
</script>
```
<fieldset class="layui-elem-field layui-field-title">
<legend>消息</legend>
</fieldset>
::: demo 通过 layer.msg(content, options) 创建消息窗, 第一个参数`msg`为消息内容, 第二参数`options`为可选配置, 常用于配置`time`超时时间等。
<template>
<button @click="msg">普通消息</button>
<button @click="success">成功消息</button>
<button @click="failure">失败消息</button>
<button @click="warm">警告消息</button>
<button @click="info">锁定消息</button>
</template>
<script>
import { layer } from "../../../../layer/src/index"
const msg = function() {
layer.msg("普通消息", { time: 1000 })
}
const success = function() {
layer.msg("成功消息", { time: 1000, icon: 1})
}
const failure = function() {
layer.msg("失败消息", { time: 1000, icon: 2})
}
const warm = function() {
layer.msg("警告消息", { time: 1000, icon: 3})
}
const info = function() {
layer.msg("疑问消息", { time: 1000, icon: 4})
}
</script>
:::
<fieldset class="layui-elem-field layui-field-title">
<legend>加载</legend>
</fieldset>
::: demo 通过 layer.load(type, options) 创建加载层, 第一个参数`type`为加载动画样式, 第二个参数`options`为可选配置, 常用于设置`time`加载时长`shade`遮盖层等。
<template>
<button @click="load1">加载1</button>
<button @click="load2">加载2</button>
<button @click="load3">加载3</button>
<button @click="load4">加载4</button>
</template>
<script setup>
import { layer } from "../../../../layer/src/index"
const load1 = function() {
layer.load(0, {time: 2000})
}
const load2 = function() {
layer.load(1, {time: 2000})
}
const load3 = function() {
layer.load(2, {time: 2000})
}
const load4 = function() {
layer.msg("加载中...",{icon: 16, time: 2000})
}
</script>
:::
<fieldset class="layui-elem-field layui-field-title">
<legend>模态</legend>
</fieldset>
::: demo 通过 layer.open(option) 创建模态窗, 目前支持`iframe` `page`等类型, 你可以使用`options`选项开启`Resize` `Offset`等更多特性。
<template>
<button @click="open">小试牛刀</button>
<button @click="openSize">指定尺寸</button>
<button @click="openOffset">指定位置</button>
<button @click="openIframe">远程窗体</button>
<button @click="openHtml">HTML</button>
<button @click="openVNode">vNode</button>
<button @click="openMaxmin">缩小放大</button>
<button @click="openResize">尺寸拉伸</button>
<button @click="openIndex">设置层级</button>
<button @click="openAreaAuto">内容自撑开</button>
<button @click="openBtns">自定义按钮组</button>
</template>
<script setup>
import { layer } from "../../../../layer/src/index"
const open = function() {
layer.open({
type: 1,
title: "标题",
content: "内容"
})
}
const openSize = function() {
layer.open({
type: 1,
title: "标题",
area: ['400px','400px'],
content: "内容"
})
}
const openOffset = function() {
layer.open({
type: 1,
title: "标题",
offset: ['100px','100px'],
content: "内容"
})
}
const openIframe = function() {
layer.open({
type: 2,
title: "标题",
resize: true,
area: ['500px','500px'],
content: "http://layui-vue.pearadmin.com"
})
}
const openHtml = function() {
layer.open({
type: 1,
title: "标题",
isHtmlFragment: true,
content: "<p style='color:red;'>内容</p>"
})
}
const openVNode = function() {
layer.open({
type: 1,
title: "标题",
content: h('button', { style: 'margin: 10px;' },'按钮')
})
}
const openMaxmin = function() {
layer.open({
type: 1,
title: "标题",
maxmin: true,
content: "内容"
})
}
const openResize = function() {
layer.open({
type: 1,
title: "标题",
resize: true,
content: "内容"
})
}
const openIndex = function() {
layer.open({
type: 1,
title: "标题",
zIndex: 666,
content: "设置层级"
})
}
const openAreaAuto = function(){
layer.open({
type:1,
title:"area:auto",
isHtmlFragment:true,
content:"<img src='https://chixian.oss-cn-hangzhou.aliyuncs.com/20210819230007_346ce.jpeg'/>"
})
}
const openBtns = function(){
layer.open({
type:1,
title:"自定义按钮",
btn:[{text:"第一个按钮",callback:function(){
console.log("第一个按钮被点击");
}},{text:"第二个按钮",callback:function(){
console.log("第二个按钮被点击");
}}]
})
}
</script>
:::
<fieldset class="layui-elem-field layui-field-title">
<legend>询问</legend>
</fieldset>
::: demo 通过 layer.confirm(msg, options) 创建确认框, 第一个参数`msg`为文本消息, 第二个参数`options`为选项配置, 你可以使用`options`的`btn`属性定义操作。
<template>
<button @click="openConfirm1">确认框</button>
<button @click="openConfirm2">询问框</button>
</template>
<script setup>
import { layer } from "../../../../layer/src/index"
const openConfirm1 = function() {
layer.confirm("layui-vue 1.0.0 已经发布")
}
const openConfirm2 = function() {
layer.confirm("你如何看待 layui-vue 的发布", {btn: [{text:'站着看', callback: function(){
layer.msg("有点意思");
}},{text:'坐着看', callback: function(){
layer.msg("无聊");
}}]})
}
</script>
:::
<fieldset class="layui-elem-field layui-field-title">
<legend>抽屉</legend>
</fieldset>
::: demo 通过 layer.drawer(options) 创建抽屉层, `options`选项配置, 抽屉本质上是一个特殊的模态窗, 你可以使用`offset`选项来设置方向。
<template>
<button @click="openTop"></button>
<button @click="openBottom"></button>
<button @click="openLeft"></button>
<button @click="openRight"></button>
</template>
<script setup>
import { layer } from "../../../../layer/src/index"
const openTop = function() {
layer.drawer({
title: "标题",
content: "内容",
offset: "t"
})
}
const openBottom = function() {
layer.drawer({
title: "标题",
content: "内容",
offset: "b"
})
}
const openLeft = function() {
layer.drawer({
title: "标题",
content: "内容",
offset: "l"
})
}
const openRight = function() {
layer.drawer({
title: "标题",
content: "内容",
offset: "r"
})
}
</script>
:::
<fieldset class="layui-elem-field layui-field-title">
<legend>图片层</legend>
</fieldset>
::: demo 通过 layer.photos(options) 创建图片预览弹层, 参数`options`主要传入预览的图片链接。
<template>
<button @click="signleImg">快速预览一张图片</button>
<button @click="signleImg2">单张图片带文字描述</button>
<button @click="groupImg">图片组</button>
</template>
<script>
import { layer } from "../../../../layer/src/index"
const signleImg = function() {
layer.photos("/src/assets/logo.jpg")
}
const signleImg2 = function() {
layer.photos({
imgList:[ {src:'/src/assets/logo.jpg',alt:'layer for vue'}]
})
}
const groupImg = function() {
layer.photos({
imgList:[
{ src:'http://www.pearadmin.com/assets/images/un8.svg', alt:'图片1'},
{ src:'http://www.pearadmin.com/assets/images/un32.svg', alt:'图片2'}
]
})
}
</script>
:::
<fieldset class="layui-elem-field layui-field-title">
<legend>通讯</legend>
</fieldset>
::: demo 👉 查看 [Children1.vue](https://gitee.com/layui-vue/layer-vue/blob/master/example/src/components/Children1.vue), 通过 h() 函数的第二个参数向 Children1.vue 传递响应式变量。
<template>
<button @click="openComponent1">数据</button>
<input type="text" v-model="data.remark" />
</template>
<script setup>
import { reactive, h, resolveComponent } from 'vue'
import { layer } from "../../../../layer/src/index"
const data = reactive({
remark: "信息"
})
const ChildrenOne = resolveComponent('Children1')
const openComponent1 = () => {
layer.open({
title: '标题',
content: h(ChildrenOne, { data }),
shade: false,
})
}
</script>
:::
::: demo 👉 查看 [Children2.vue](https://gitee.com/layui-vue/layer-vue/blob/master/example/src/components/Children2.vue), 通过 h() 函数的第二个参数声明 onXxx() 形式的函数完成 Children2.vue 的事件监听。
<template>
<button @click="openComponent2">事件</button>
<input type="text" v-model="numb" />
</template>
<script setup>
import { reactive, h, resolveComponent } from 'vue'
import { layer } from "../../../../layer/src/index"
const prop = reactive({})
const numb = ref(1000)
const ChildrenTwo = resolveComponent('Children2')
const openComponent2 = () => {
layer.open({
title: '标题',
content: h(ChildrenTwo, {
prop,
onAdd(res){
numb.value = numb.value + 1;
}, onSub(res) {
numb.value = numb.value - 1;
}
}),
})
}
</script>
:::
<fieldset class="layui-elem-field layui-field-title">
<legend>销毁</legend>
</fieldset>
::: demo 我们提供 layer.close(id) 与 layer.closeAll() 函数实现弹出层的主动销毁。
<template>
<button @click="open">打开</button>
<button @click="close">销毁</button>
<button @click="closeAll">销毁全部</button>
</template>
<script setup>
import { layer } from "../../../../layer/src/index";
let id = null;
const open = function() {
id = layer.open({
title: "标题",
content: "内容",
shade: false
})
}
const close = function() {
layer.close(id);
}
const closeAll = function() {
layer.closeAll();
}
</script>
:::
<fieldset class="layui-elem-field layui-field-title">
<legend>组件</legend>
</fieldset>
::: demo 我们提供了一些核心函数来创建弹出层, 但在一些复杂的应用场景中, `lay-layer`是一个更好的选择。
<template>
<button @click="changeVisible">{{ visible ? '隐藏':'显示' }}</button>
<lay-layer title="标题" v-model="visible" :area="['300px','300px']">
content
</lay-layer>
</template>
<script setup>
import { ref, reactive, h } from 'vue'
import { layer } from "../../../../layer/src/index"
const visible = ref(false)
const changeVisible = () => {
visible.value = !visible.value;
}
</script>
:::
<fieldset class="layui-elem-field layui-field-title">
<legend>选项</legend>
</fieldset>
@ -535,6 +96,7 @@ const changeVisible = () => {
| shadeOpacity | 遮盖层透明度 | string | `0.1` | `0.1` - `1` |
| isHtmlFragment | 解析 html 字符 | boolean | `false` | `true` `false` |
| imgList | 图片数据数组 | array[{src:图片链接,alt:图片名字可选'}] | - | - |
| startIndex | 图片初始浏览索引 | number | 0 | - |
<fieldset class="layui-elem-field layui-field-title">
<legend>动画</legend>
</fieldset>

View File

@ -1,47 +1,62 @@
<template>
<div class="markdown-body light-scheme">
<div class="alone-header">
<img class="alone-logo" src="../assets/logo.png" />
<a
style="
position: absolute;
right: 16%;
line-height: 60px;
color: white;
font-size: 15px;
"
>{{ version }}</a
>
<a
href="https://gitee.com/layui-vue/layer-vue"
style="position: absolute; right: 10%; line-height: 75px"
>
<svg width="1.7em" height="1.7em" viewBox="0 0 24 24">
<path
fill="#fff"
d="M10.9,2.1c-4.6,0.5-8.3,4.2-8.8,8.7c-0.5,4.7,2.2,8.9,6.3,10.5C8.7,21.4,9,21.2,9,20.8v-1.6c0,0-0.4,0.1-0.9,0.1 c-1.4,0-2-1.2-2.1-1.9c-0.1-0.4-0.3-0.7-0.6-1C5.1,16.3,5,16.3,5,16.2C5,16,5.3,16,5.4,16c0.6,0,1.1,0.7,1.3,1c0.5,0.8,1.1,1,1.4,1 c0.4,0,0.7-0.1,0.9-0.2c0.1-0.7,0.4-1.4,1-1.8c-2.3-0.5-4-1.8-4-4c0-1.1,0.5-2.2,1.2-3C7.1,8.8,7,8.3,7,7.6C7,7.2,7,6.6,7.3,6 c0,0,1.4,0,2.8,1.3C10.6,7.1,11.3,7,12,7s1.4,0.1,2,0.3C15.3,6,16.8,6,16.8,6C17,6.6,17,7.2,17,7.6c0,0.8-0.1,1.2-0.2,1.4 c0.7,0.8,1.2,1.8,1.2,3c0,2.2-1.7,3.5-4,4c0.6,0.5,1,1.4,1,2.3v2.6c0,0.3,0.3,0.6,0.7,0.5c3.7-1.5,6.3-5.1,6.3-9.3 C22,6.1,16.9,1.4,10.9,2.1z"
></path>
</svg>
</a>
</div>
<div class="alone-banner layui-bg-black">
<div class="layui-main">
<img src="../assets/logo.jpg" />
<h1>layer vue</h1>
<p> </p>
<lay-scroll height="100%">
<div class="markdown-body light-scheme">
<div class="alone-header">
<img class="alone-logo" src="../assets/logo.png" />
<a
style="
position: absolute;
right: 16%;
line-height: 60px;
color: white;
font-size: 15px;
"
>{{ version }}</a
>
<a
href="https://gitee.com/layui-vue/layer-vue"
style="position: absolute; right: 10%; line-height: 75px"
>
<svg width="1.7em" height="1.7em" viewBox="0 0 24 24">
<path
fill="#fff"
d="M10.9,2.1c-4.6,0.5-8.3,4.2-8.8,8.7c-0.5,4.7,2.2,8.9,6.3,10.5C8.7,21.4,9,21.2,9,20.8v-1.6c0,0-0.4,0.1-0.9,0.1 c-1.4,0-2-1.2-2.1-1.9c-0.1-0.4-0.3-0.7-0.6-1C5.1,16.3,5,16.3,5,16.2C5,16,5.3,16,5.4,16c0.6,0,1.1,0.7,1.3,1c0.5,0.8,1.1,1,1.4,1 c0.4,0,0.7-0.1,0.9-0.2c0.1-0.7,0.4-1.4,1-1.8c-2.3-0.5-4-1.8-4-4c0-1.1,0.5-2.2,1.2-3C7.1,8.8,7,8.3,7,7.6C7,7.2,7,6.6,7.3,6 c0,0,1.4,0,2.8,1.3C10.6,7.1,11.3,7,12,7s1.4,0.1,2,0.3C15.3,6,16.8,6,16.8,6C17,6.6,17,7.2,17,7.6c0,0.8-0.1,1.2-0.2,1.4 c0.7,0.8,1.2,1.8,1.2,3c0,2.2-1.7,3.5-4,4c0.6,0.5,1,1.4,1,2.3v2.6c0,0.3,0.3,0.6,0.7,0.5c3.7-1.5,6.3-5.1,6.3-9.3 C22,6.1,16.9,1.4,10.9,2.1z"
></path>
</svg>
</a>
</div>
<div class="alone-banner layui-bg-black">
<div class="layui-main">
<img src="../assets/logo.jpg" />
<h1>layer vue</h1>
<p> </p>
</div>
</div>
<div class="layui-container" style="width: 80%; margin-left: 10%">
<lay-tab type="brief" v-model="active">
<lay-tab-item title="入门" id="/zh-CN/index"></lay-tab-item>
<lay-tab-item title="示例" id="/zh-CN/demo"></lay-tab-item>
<lay-tab-item title="帮助" id="/zh-CN/help"></lay-tab-item>
</lay-tab>
<router-view></router-view>
</div>
</div>
<br />
<div class="layui-container" style="width: 80%; margin-left: 10%">
<router-view></router-view>
</div>
</div>
</lay-scroll>
</template>
<script setup>
import config from "../../../layer/package.json";
import { useRouter } from "vue-router";
import { ref, watch } from "vue";
const version = config.version;
const active = ref("/zh-CN/index");
const router = useRouter();
watch(active, (val) => {
router.push(val);
});
</script>
<style>
@ -116,4 +131,8 @@ body {
border-top: 1px solid #eee;
border-style: solid;
}
.layui-tab-content {
padding: 0 !important;
}
</style>

View File

@ -1,12 +1,13 @@
import Layout from "./App.vue";
import { App, createApp as _createApp } from "vue";
import { createRouter } from "./src/router/index";
import { createRouter } from "./router/index";
import { Router } from "vue-router";
import LayCode from "./src/components/LayCode.vue";
import Children1 from "./src/components/Children1.vue";
import Children2 from "./src/components/Children2.vue";
import layer from "../layer/src/index";
import "./src/assets/css/index.css";
import LayCode from "./components/LayCode.vue";
import Children1 from "./components/Children1.vue";
import Children2 from "./components/Children2.vue";
import layui from "../../component/src/index";
import layer from "../../layer/src/index";
import "./assets/css/index.css";
export function createApp(): {
app: App<Element>;
@ -16,6 +17,7 @@ export function createApp(): {
const router = createRouter();
app
.use(layui)
.use(layer)
.use(router)
.component("LayCode", LayCode)

View File

@ -10,8 +10,17 @@ const zhCN = [
{
path: "/zh-CN/index",
component: () => import("../document/zh-CN/index.md"),
meta: { title: "指南" },
meta: { title: "入门" },
},
{
path: "/zh-CN/demo",
component: () => import("../document/zh-CN/demo.md"),
meta: { title: "示例" },
},
{
path: "/zh-CN/help",
component: () => import("../document/zh-CN/help.md"),
meta: { title: "帮助" },
},
],
},

View File

@ -0,0 +1,19 @@
{
"compilerOptions": {
"baseUrl": ".",
"module": "esnext",
"target": "es2015",
"moduleResolution": "Node",
"strict": true,
"allowJs": true,
"resolveJsonModule": true,
"declaration": true,
"emitDeclarationOnly": true,
"esModuleInterop": true,
"declarationDir": "types",
"jsx": "preserve",
"lib": ["ESNext","DOM"]
},
"include": ["src/**/*","shims-vue.d.ts"],
"exclude": ["node_modules"]
}

View File

@ -0,0 +1,62 @@
<template>
<div class="layer-notifiy" ref="notifyRef">
<h2 class="title">
<i v-if="icon" :class="iconClass"></i>
{{ title }}
</h2>
<div class="content" v-if="!isHtmlFragment">
<p>{{ content }}</p>
</div>
<div class="content" v-html="content" v-else></div>
<CloseBtnVue @click="close"></CloseBtnVue>
</div>
</template>
<script lang="ts">
export default {
name: "Notifiy",
};
</script>
<script lang="ts" setup>
import { nextTick, onMounted, ref } from "vue";
import CloseBtnVue from "./CloseBtn.vue";
export interface LayNotifyProps {
title: any;
content: any;
isHtmlFragment?: boolean;
icon?: string | number | undefined;
iconClass: string[];
}
const props = withDefaults(defineProps<LayNotifyProps>(), {
isHtmlFragment: false,
});
const emit = defineEmits(["close"]);
const close = () => {
emit("close");
};
function addClass(obj: { className: any }, cls: string) {
// class .
let obj_class = obj.className,
// class , ''.
blank = obj_class != "" ? " " : "";
let added = obj_class + blank + cls; // class class.
obj.className = added; // class.
}
const notifyRef = ref<HTMLElement | null>(null);
onMounted(() => {
nextTick(() => {
if (notifyRef.value) {
setTimeout(() => {
//class
// @ts-ignore
addClass(
notifyRef.value.parentElement?.parentElement,
"layui-layer-notify"
);
}, 300);
}
});
});
</script>

View File

@ -11,6 +11,7 @@ import Title from "./Title.vue";
import CloseBtn from "./CloseBtn.vue";
import Resize from "./Resize.vue";
import Photos from "./Photos.vue";
import Notifiy from "./Notifiy.vue";
import {
Ref,
ref,
@ -36,6 +37,8 @@ import {
getDrawerAnimationClass,
calculateDrawerArea,
calculatePhotosArea,
calculateNotifOffset,
removeNotifiyFromQueen,
} from "../utils";
import useMove from "../composable/useMove";
import useResize from "../composable/useResize";
@ -62,12 +65,14 @@ export interface LayModalProps {
| 3
| 4
| 5
| 6
| "dialog"
| "page"
| "iframe"
| "loading"
| "drawer"
| "photos";
| "photos"
| "notifiy";
content?: string | Function | object | VNodeTypes;
isHtmlFragment?: boolean;
shade?: boolean | string;
@ -89,6 +94,9 @@ export interface LayModalProps {
appContext?: any;
startIndex?: number;
imgList?: { src: string; alt: string }[];
min?: Function;
full?: Function;
restore?: Function;
}
const props = withDefaults(defineProps<LayModalProps>(), {
@ -114,6 +122,9 @@ const props = withDefaults(defineProps<LayModalProps>(), {
destroy: () => {},
success: () => {},
end: () => {},
full: () => {},
min: () => {},
restore: () => {},
yesText: "确定",
isFunction: false,
isMessage: false,
@ -171,13 +182,19 @@ const firstOpenDelayCalculation = function () {
props
);
}
if (props.isHtmlFragment && props.area === "auto") {
area.value = ["auto", "auto"];
}
offset.value = calculateOffset(props.offset, area.value, props.type);
if (type == 6) {
offset.value = calculateNotifOffset(props.offset, area.value, id.value);
}
w.value = area.value[0];
h.value = area.value[1];
t.value = offset.value[0];
l.value = offset.value[1];
_w.value = area.value[0];
_l.value = area.value[1];
t.value = offset.value[0];
l.value = offset.value[1];
_t.value = offset.value[0];
_l.value = offset.value[1];
supportMove();
@ -220,6 +237,7 @@ const maxHandle = () => {
h.value = _h.value;
t.value = _t.value;
l.value = _l.value;
props.restore(props.id);
} else {
_t.value = t.value;
_l.value = l.value;
@ -229,6 +247,7 @@ const maxHandle = () => {
h.value = maxArea().h;
t.value = maxOffset().t;
l.value = maxOffset().l;
props.full(props.id);
}
max.value = !max.value;
};
@ -247,6 +266,7 @@ const minHandle = () => {
h.value = _h.value;
t.value = _t.value;
l.value = _l.value;
props.restore(props.id);
} else {
_w.value = w.value;
_h.value = h.value;
@ -256,6 +276,7 @@ const minHandle = () => {
w.value = minArea().w;
t.value = minOffset(left).t;
l.value = minOffset(left).l;
props.min(props.id);
}
min.value = !min.value;
};
@ -361,6 +382,7 @@ const boxClasses = computed(() => {
type === 3 ? "layui-layer-loading" : "",
type === 4 ? "layui-layer-drawer" : "",
type === 5 ? "layui-layer-photos" : "",
type === 6 ? "layui-layer-notifiy-border" : "",
props.isMessage ? "layui-layer-msg" : "",
props.isMessage && !props.icon ? "layui-layer-hui" : "",
props.skin,
@ -393,13 +415,39 @@ const supportMove = function () {
* <p>
*/
const styles = computed<any>(() => {
return {
let style = {
top: t.value,
left: l.value,
width: w.value,
height: h.value,
zIndex: index.value,
};
if (props.isHtmlFragment && props.area === "auto") {
// @ts-ignore
style.maxWidth = "calc(100% - 2px)";
// @ts-ignore
style.maxHeight = "calc(100% - 51px)";
style.top = "50%";
style.left = "50%";
if (Array.isArray(offset.value)) {
if (offset.value[0].indexOf("px") > -1) {
style.top = offset.value[0];
}
if (offset.value[1].indexOf("px") > -1) {
style.left = offset.value[1];
}
if (
offset.value[0].indexOf("%") > -1 ||
offset.value[1].indexOf("%") > -1
) {
// @ts-ignore
style.transform = `translate(-${
style.left.indexOf("%") > -1 ? style.left : 0
},-${style.top.indexOf("%") > -1 ? style.top : 0})`;
}
}
}
return style;
});
/**
@ -425,6 +473,11 @@ const closeHandle = () => {
emit("close");
emit("update:modelValue", false);
props.destroy();
//Notify
if (type === 6) {
removeNotifiyFromQueen(props.id);
}
};
/**
@ -538,7 +591,7 @@ const showResize = computed(() => {
* @param type 类型
*/
const showTitle = computed(() => {
return props.title && props.type != 3 && props.type != "photos";
return props.title && props.type != 3 && props.type != 5 && props.type != 6;
});
/*
@ -610,9 +663,21 @@ defineExpose({ reset, open, close });
:startIndex="props.startIndex"
@resetCalculationPohtosArea="resetCalculationPohtosArea"
></Photos>
<Notifiy
v-if="type === 6"
@close="closeHandle"
:title="props.title"
:content="props.content"
:isHtmlFragment="props.isHtmlFragment"
:icon="props.icon"
:iconClass="iconClass"
></Notifiy>
</div>
<!-- 工具栏 -->
<span class="layui-layer-setwin" v-if="type != 3 && type != 5">
<span
class="layui-layer-setwin"
v-if="type != 3 && type != 5 && type != 6"
>
<a
v-if="maxmin && !max"
class="layui-layer-min"

View File

@ -2,7 +2,7 @@ import { render, h, isVNode, getCurrentInstance, AppContext, App } from "vue";
import LayLayer from "./component/index.vue";
import { InstallOptions } from "./types";
import { zIndexKey } from "./tokens";
import { nextId } from "./utils";
import { nextId, removeNotifiyFromQueen } from "./utils";
// 实例队列
const layerInstance: any = [];
@ -129,7 +129,7 @@ const layer = {
};
}
let defaultOption = {
type: "photos",
type: 5,
anim: 2,
startIndex: 0,
isOutAnim: true,
@ -138,6 +138,18 @@ const layer = {
};
return layer.create(option, defaultOption, callback);
},
//通知
notifiy: (option: any = {}, callback?: Function) => {
option.anim = 5;
option.shade = false;
option.type = 6;
let defaultOption = {
offset: "rt",
time: 2000,
area: "auto",
};
return layer.create(option, defaultOption, callback);
},
// 创建弹出层
create: (option: any, defaultOption: any, callback?: Function) => {
// 销毁定时
@ -174,7 +186,11 @@ const layer = {
// 调用 open 函数
modalInstance.component?.exposed?.open();
// 延时 time 销毁
if (defaultOption && defaultOption.time != undefined) {
if (
defaultOption &&
defaultOption.time != undefined &&
defaultOption.time != 0
) {
timer = setTimeout(() => {
modalInstance.component?.exposed?.close();
if (callback) callback(modalContainer.id);
@ -186,6 +202,10 @@ const layer = {
}, 2000);
// 销毁实例
delInstance(modalContainer.id);
//Notifiy特殊处理
if (options.type === 6) {
removeNotifiyFromQueen(options.id);
}
}, defaultOption.time);
}
// 维护实例

File diff suppressed because it is too large Load Diff

View File

@ -120,8 +120,10 @@ export function calculateType(modalType: number | string) {
return 3;
} else if (modalType === "drawer" || modalType == 4) {
return 4;
} else if (modalType === "photos") {
} else if (modalType === "photos" || modalType == 5) {
return 5;
} else if (modalType === "notifiy" || modalType == 6) {
return 6;
}
return 0;
}
@ -235,6 +237,7 @@ export function getDrawerAnimationClass(offset: any, isClose: boolean = false) {
return isClose ? `${prefix}-${suffix}-close` : `${prefix}-${suffix}`;
}
//计算图片大小 并缩放
export async function calculatePhotosArea(url: string, options: object) {
let img = new Image();
img.src = url;
@ -273,3 +276,107 @@ export async function calculatePhotosArea(url: string, options: object) {
return [imgarea[0] + "px", imgarea[1] + "px"];
}
}
// 计算Notify位置 队列 此处先暂时定义Notify的间距为15px
export function calculateNotifOffset(offset: any, area: any, layerId: string) {
let arr = ["lt", "lb", "rt", "rb"];
let t = "0",
l = "0";
// 间隙
let transOffsetLeft = 15;
let transOffsetTop = 15;
// @ts-ignore
window.NotifiyQueen = window.NotifiyQueen || [];
// @ts-ignore
let notifiyQueen = window.NotifiyQueen;
if (typeof offset != "string" || arr.indexOf(offset) === -1) {
offset = "rt";
}
// 当前区域元素集合
let nodeList = notifiyQueen.filter((e: { offset: any }) => {
if (e.offset === offset) {
return e;
}
});
//前一个元素
let prevNode = nodeList.length > 0 ? nodeList[nodeList.length - 1] : null;
if (prevNode) {
prevNode = document.getElementById(prevNode["id"])?.firstElementChild
?.firstElementChild;
if (offset === "rt" || offset === "lt") {
transOffsetTop +=
prevNode.offsetHeight + parseFloat(prevNode.style["top"]);
} else {
let bottom = parseFloat(prevNode.style["top"].split(" - ")[1]);
transOffsetTop += prevNode.offsetHeight + bottom;
}
} else {
if (offset === "rb" || offset === "lb") {
transOffsetTop += parseFloat(area[1]);
}
}
// 关键字处理
if (offset === "rt") {
t = transOffsetTop + "px";
l = "calc(100% - " + (parseFloat(area[0]) + transOffsetLeft) + "px)";
} else if (offset === "rb") {
t = "calc(100vh - " + transOffsetTop + "px)";
l = "calc(100% - " + (parseFloat(area[0]) + transOffsetLeft) + "px)";
} else if (offset === "lt") {
t = transOffsetTop + "px";
l = transOffsetLeft + "px";
} else if (offset === "lb") {
t = "calc(100vh - " + transOffsetTop + "px)";
l = transOffsetLeft + "px";
}
notifiyQueen.push({
id: layerId,
offset: offset,
});
// 返回位置
return [t, l];
}
//移除Notify队列中某项并且重新计算其他Notify位置
export function removeNotifiyFromQueen(layerId: string | undefined) {
// 间隙
let transOffsetTop = 15;
// @ts-ignore 删除项的高度
let offsetHeight =
document.getElementById(layerId)?.firstElementChild?.firstElementChild
?.offsetHeight;
// @ts-ignore
window.NotifiyQueen = window.NotifiyQueen || [];
// @ts-ignore
let notifiyQueen = window.NotifiyQueen;
let index = notifiyQueen.findIndex((e: { id: string }) => e.id === layerId);
let offsetType = notifiyQueen[index].offset;
let list = notifiyQueen.filter((e: { offset: any }) => {
if (e.offset === offsetType) {
return e;
}
});
let findIndex = list.findIndex((e: { id: string }) => e.id === layerId);
// //得到需要修改的定位的Notifiy集合
let needCalculatelist = list.slice(findIndex + 1);
needCalculatelist.forEach((e: { id: string }) => {
let dom = document.getElementById(e.id)?.firstElementChild
?.firstElementChild;
if (offsetType === "rt" || offsetType === "lt") {
// @ts-ignore
dom.style["top"] =
parseFloat(dom.style["top"]) - transOffsetTop - offsetHeight + "px";
} else {
// @ts-ignore
let bottom =
parseFloat(dom.style["top"].split(" - ")[1]) -
transOffsetTop -
offsetHeight;
// @ts-ignore
dom.style["top"] = "calc(100vh - " + bottom + "px)";
}
});
notifiyQueen.splice(index, 1); //删除
}