🐛[修复bug]解决tooltip内容动态更新和深色主题边框白边的问题,新增禁用属性(disabled)

This commit is contained in:
xumi 2021-12-15 00:33:44 +08:00
parent 6dd0bd791f
commit 25ee7195f0
6 changed files with 118 additions and 55 deletions

View File

@ -52,11 +52,60 @@
::: demo ::: demo
<template> <template>
<lay-tooltip content="假装这里有文字提示" :is-dark="false"> <lay-tooltip content="不明白是是非非,只知我不会不在。" :is-dark="false">
<lay-button>tooltip</lay-button> <lay-button >tooltip</lay-button>
</lay-tooltip> </lay-tooltip>
</template> </template>
<script>
</script>
:::
::: title 是否禁用/动态属性
:::
::: demo
<template>
<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>
</template>
<script>
import { ref } from 'vue';
export default {
setup() {
const contentArr = [
"不明白是是非非,只知我不会不在。",
"千山万水,去程是你,归程也是你。",
"一约既定,万山无阻。",
"时光都淡了,我还伴着你。",
"只问深情,不问西东。",
"感谢曾经在我身边的,一直在我身边。",
"经年再相逢,魂梦与子同。"
];
const rendonCotent = function(){
return contentArr[Math.floor(Math.random() * contentArr.length)];
};
const content = ref(rendonCotent())
const isDark = ref(false)
const disabled = ref(true)
setInterval(()=> content.value = rendonCotent(), 1000)
return {
content,
isDark,
disabled
}
}
}
</script>
<style> <style>
</style> </style>
::: :::
@ -71,5 +120,6 @@
| content | 显示内容 | -- | | content | 显示内容 | -- |
| position | 显示位置 | `top`(默认值)、`bottom`、`left`、`right` | | position | 显示位置 | `top`(默认值)、`bottom`、`left`、`right` |
| isDark | 是否为黑色主题 | `true`(默认值)、`false`(浅色) | | isDark | 是否为黑色主题 | `true`(默认值)、`false`(浅色) |
| disabled | 是否禁用 | `false`(默认值)、`true`(禁用) ||
::: :::

View File

@ -31,7 +31,7 @@ const postionFns: any = {
left = right; left = right;
} }
return { return {
top: `${top - Math.abs(popper.offsetHeight - el.offsetHeight) / 2}px`, top: `${top - (popper.offsetHeight - el.offsetHeight) / 2}px`,
left: `${left}px` left: `${left}px`
} }
}, },
@ -42,7 +42,7 @@ const postionFns: any = {
right = left - popper.offsetWidth - 6; right = left - popper.offsetWidth - 6;
} }
return { return {
top: `${top - Math.abs(popper.offsetHeight - el.offsetHeight) / 2}px`, top: `${top - (popper.offsetHeight - el.offsetHeight) / 2}px`,
left: `${right}px` left: `${right}px`
} }
} }

View File

@ -10,14 +10,16 @@
@border-clor: #cecece; @border-clor: #cecece;
// 单一设置主题 // 单一设置主题
.single-theme(@position, @contrary_position, @margin_postion, @color) { .single-theme(@position, @contrary_position, @margin_postion, @color, @m-border-color) {
@attr : ~'[position=@{position}]'; @attr : ~'[position=@{position}]';
&.layui-popper@{attr}{ &{
border: 1px solid @m-border-color;
&.layui-popper@{attr}{
margin-@{contrary_position}: 6px; margin-@{contrary_position}: 6px;
.layui-popper-arrow { .layui-popper-arrow {
@{contrary_position}: -6px; @{contrary_position}: -6px;
border-@{contrary_position}-width: 0; border-@{contrary_position}-width: 0;
border-@{position}-color: @border-clor; border-@{position}-color: @m-border-color;
&::after{ &::after{
@{contrary_position}: 1px; @{contrary_position}: 1px;
border-@{contrary_position}-width: 0; border-@{contrary_position}-width: 0;
@ -26,15 +28,16 @@
} }
} }
} }
}
} }
// 统一设置四个方向的主题 // 统一设置四个方向的主题
.theme(@background-color, @color) { .theme(@background-color, @color, @border-color) {
background-color: @background-color; background-color: @background-color;
color: @color; color: @color;
.single-theme(top, bottom, left, @background-color); .single-theme(top, bottom, left, @background-color, @border-color);
.single-theme(bottom, top, left, @background-color); .single-theme(bottom, top, left, @background-color, @border-color);
.single-theme(right, left, top, @background-color); .single-theme(right, left, top, @background-color, @border-color);
.single-theme(left, right, top, @background-color); .single-theme(left, right, top, @background-color, @border-color);
} }
// 箭头默认居中 // 箭头默认居中
@ -65,9 +68,8 @@
min-height: 12px; min-height: 12px;
font-size: 14px; font-size: 14px;
box-sizing: border-box; box-sizing: border-box;
border: 1px solid @border-clor;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.15); box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.15);
.theme(@ligh-background, @ligh-color); .theme(@ligh-background, @ligh-color, @border-clor);
max-width: 300px; max-width: 300px;
z-index: 9999; z-index: 9999;
@ -93,8 +95,7 @@
/* 深色主题 */ /* 深色主题 */
&.layui-dark { &.layui-dark {
.theme(@dark-background, @dark-color); .theme(@dark-background, @dark-color, @dark-background);
border: 0;
} }
} }

View File

@ -1,7 +1,7 @@
<template> <template>
<transition v-if="visible"> <transition v-show="visible">
<div :class="['layui-popper', {'layui-dark' : isDark}]" :style="style" :position="innnerPosition"> <div ref="popper" :class="['layui-popper', {'layui-dark' : innnerIsDark}]" :style="style" :position="innnerPosition">
<slot>{{content}}</slot> <slot>{{content.value}}</slot>
<div class="layui-popper-arrow"></div> <div class="layui-popper-arrow"></div>
</div> </div>
</transition> </transition>
@ -16,22 +16,21 @@
<script setup lang="ts"> <script setup lang="ts">
import "./index.less"; import "./index.less";
import postionFns from "./calcPosition"; import postionFns from "./calcPosition";
import { getCurrentInstance, CSSProperties, ref, watch, onUpdated, defineEmits, onMounted, des} from "vue"; import { CSSProperties, ref, watch, onUpdated, defineEmits, onMounted, Ref} from "vue";
import {on} from "../../tools/domUtil"; import {on} from "../../tools/domUtil";
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
el : any, el : any,
content ?: string, content ?: Ref<string>,
position ?: string, position ?: Ref<string>,
trigger ?: string, trigger ?: string,
enterable ?: boolean, enterable ?: boolean,
isDark ?: boolean, isDark ?: Ref<boolean>,
disabled ?: Ref<boolean>,
modelValue ?: boolean modelValue ?: boolean
}>(), }>(),
{ {
position : 'top',
enterable : true, enterable : true,
isDark : false,
trigger : 'hover', trigger : 'hover',
modelValue : true modelValue : true
} }
@ -48,43 +47,50 @@
} }
const style = ref<CSSProperties>({top: (-window.innerHeight) + 'px',left:0}); const style = ref<CSSProperties>({top: (-window.innerHeight) + 'px',left:0});
const visible = ref(props.modelValue);
const checkTarget = ref(false); const checkTarget = ref(false);
const popper = ref(); const popper = ref<HTMLDivElement>({} as HTMLDivElement);
const innnerPosition = ref(props.position); const tempPosition = props.position??ref('top');
const innnerPosition = ref(tempPosition.value);
const innnerIsDark = ref(props.isDark??true);
const innnerDisabled = ref(props.disabled??false);
const visible = ref(props.modelValue && !innnerDisabled.value);
const emit = defineEmits(['update:modelValue']) const emit = defineEmits(['update:modelValue'])
watch(visible, (val)=>{ watch(visible, (val)=>{
emit('update:modelValue', val); emit('update:modelValue', val);
val && (popper.value.offsetWidth === 0 ? setTimeout(showPosistion, 0) : showPosistion());
})
watch(innnerDisabled, (val)=>{
visible.value = false;
})
watch(()=>props.content?.value, (val)=>{
visible.value && setTimeout(showPosistion, 5);
}) })
const doShow = function(){ const doShow = function(){
visible.value = true; if (!innnerDisabled.value) {
visible.value = true;
}
} }
const doHidden = function(e : MouseEvent){ const doHidden = function(e : MouseEvent){
if ((checkTarget.value && props.el.contains(e.target)) || (props.enterable && popper.value.contains(e.target))) return; if ((checkTarget.value && props.el.contains(e.target)) || (props.enterable && popper.value.contains(e.target as Node))) return;
style.value = {top: (-window.innerHeight) + 'px',left:0}; style.value = {top: (-window.innerHeight) + 'px',left:0};
popper.value.remove(); // popper.value.remove();
// visible.value = false; visible.value = false;
innnerPosition.value = props.position; innnerPosition.value = tempPosition.value;
} }
// //
// on(props.el, triggerArr[0], doShow); on(props.el, triggerArr[0], doShow);
on(triggerArr[1]??props.el, triggerArr[2], doHidden); on(triggerArr[1]??props.el, triggerArr[2], doHidden);
checkTarget.value = triggerArr[3]; checkTarget.value = triggerArr[3];
// //
const showPosistion = function(){ const showPosistion = function(){
postionFns[props.position] && (style.value = postionFns[props.position](props.el, popper.value, innnerPosition)); postionFns[tempPosition.value] && (style.value = postionFns[tempPosition.value](props.el, popper.value, innnerPosition));
} }
onMounted(()=>{
// , dom,
const nodeChange = function(){
popper.value = getCurrentInstance()?.vnode.el;
visible.value && (popper.value.offsetWidth === 0 ? setTimeout(showPosistion, 0) : showPosistion()); visible.value && (popper.value.offsetWidth === 0 ? setTimeout(showPosistion, 0) : showPosistion());
} })
onMounted(nodeChange)
onUpdated(nodeChange)
</script> </script>

View File

@ -1,4 +1,4 @@
import { h, render} from "vue"; import { h, ref, render, watchEffect} from "vue";
import popper from "./index.vue"; import popper from "./index.vue";
import { once } from "../../tools/domUtil"; import { once } from "../../tools/domUtil";
const EVENT_MAP : any = { const EVENT_MAP : any = {
@ -9,16 +9,24 @@ const usePopper = {
createPopper(el: HTMLElement, props: any, trigger : string) { createPopper(el: HTMLElement, props: any, trigger : string) {
const _this = this; const _this = this;
once(el, EVENT_MAP[trigger], () => { once(el, EVENT_MAP[trigger], () => {
const _props = {...props}; // TODO 临时解决方案
_props.el = el; const _props:any = {el};
for (const key in props) {
_props[key] = ref(props[key]);
}
_this.renderPopper(_props); _this.renderPopper(_props);
watchEffect(() => {
for (const key in _props) {
_props[key].value = props[key];
}
})
}) })
}, },
renderPopper(props: any) { renderPopper(props: any) {
const container: HTMLDivElement = document.createElement("div"); const container: HTMLDivElement = document.createElement("div");
// container.setAttribute("class", "lay-div"); // container.setAttribute("class", "lay-div");
const node = h(popper, props); const node = h(popper, props);
render(h(popper, props), container); render(node, container);
container.firstElementChild && document.body.appendChild(container.firstElementChild); container.firstElementChild && document.body.appendChild(container.firstElementChild);
return node; return node;
} }

View File

@ -1,8 +1,6 @@
<script lang="ts"> <script lang="ts">
import usePopper from "../popper/usePopper"; import usePopper from "../popper/usePopper";
import { on } from "../../tools/domUtil"; import { defineComponent} from "vue";
import { defineComponent, h, render} from "vue";
import popper from "../popper/index.vue";
export default defineComponent({ export default defineComponent({
name: "LayTooltip", name: "LayTooltip",
props: { props: {
@ -17,6 +15,10 @@ export default defineComponent({
isDark: { isDark: {
type: Boolean, type: Boolean,
default: true, default: true,
},
disabled: {
type: Boolean,
default: false,
} }
}, },
render() { render() {
@ -25,12 +27,8 @@ export default defineComponent({
mounted() { mounted() {
const _this = this; const _this = this;
this.$nextTick(() => { this.$nextTick(() => {
on(_this.$el, 'mouseenter', () => { usePopper.createPopper(_this.$el, _this.$props, 'hover')
const container: HTMLDivElement = document.createElement("div");
render(h(popper, {...this.$props, el: this.$el}), container);
container.firstElementChild && document.body.appendChild(container.firstElementChild);
})
}); });
}, }
}); });
</script> </script>