diff --git a/packages/devui-vue/devui/read-tip/__tests__/read-tip-template.spec.ts b/packages/devui-vue/devui/read-tip/__tests__/read-tip-template.spec.ts index 97ad474077..d473804fae 100644 --- a/packages/devui-vue/devui/read-tip/__tests__/read-tip-template.spec.ts +++ b/packages/devui-vue/devui/read-tip/__tests__/read-tip-template.spec.ts @@ -1,6 +1,4 @@ import { mount } from '@vue/test-utils'; -import { ReadTip, } from '../index'; -import { nextTick } from 'vue'; import TipsTemplate from '../src/read-tip-template'; @@ -27,15 +25,15 @@ describe('read-tip test', () => { title: 'Name: Jack', content: 'This is Jack\'s profile', }; - const wrapper = mount(TipsTemplate, { + mount(TipsTemplate, { props: { defaultTemplateProps } }); - expect(document.querySelector(defaultTemplateProps.selector).innerHTML).toContain(defaultTemplateProps.title); + expect(document.querySelector(defaultTemplateProps.selector)?.innerHTML).toContain(defaultTemplateProps.title); - expect(document.querySelector(defaultTemplateProps.selector).innerHTML).toContain(defaultTemplateProps.content); + expect(document.querySelector(defaultTemplateProps.selector)?.innerHTML).toContain(defaultTemplateProps.content); }); @@ -53,13 +51,14 @@ describe('read-tip test', () => { title: 'Name: Jack', content: 'This is Jack\'s profile', }; - const wrapper = mount(TipsTemplate, { + mount(TipsTemplate, { props: { defaultTemplateProps } }); - expect(document.querySelector(defaultTemplateProps.selector).innerHTML).toContain('read-tip-container ' + defaultTemplateProps.position); + expect(document.querySelector(defaultTemplateProps.selector)?.innerHTML) + .toContain('read-tip-container ' + defaultTemplateProps.position); }); }); @@ -74,17 +73,17 @@ describe('read-tip test', () => { title: 'Name: Jack', content: 'This is Jack\'s profile', }; - const wrapper = mount(TipsTemplate, { + mount(TipsTemplate, { props: { defaultTemplateProps } }); - expect(document.querySelector(defaultTemplateProps.selector).innerHTML).toBe(''); + expect(document.querySelector(defaultTemplateProps.selector)?.innerHTML).toBe(''); - expect(document.querySelector(defaultTemplateProps.selector).innerHTML).not.toContain(defaultTemplateProps.title); + expect(document.querySelector(defaultTemplateProps.selector)?.innerHTML).not.toContain(defaultTemplateProps.title); - expect(document.querySelector(defaultTemplateProps.selector).innerHTML).not.toContain(defaultTemplateProps.content); + expect(document.querySelector(defaultTemplateProps.selector)?.innerHTML).not.toContain(defaultTemplateProps.content); }); @@ -98,7 +97,7 @@ describe('read-tip test', () => { selector: '#readtip-target', contentTemplate: true }; - const wrapper = mount(TipsTemplate, { + mount(TipsTemplate, { props: { defaultTemplateProps }, @@ -107,24 +106,23 @@ describe('read-tip test', () => { } }); - expect(document.querySelector(defaultTemplateProps.selector).innerHTML).toContain('
I am test
'); + expect(document.querySelector(defaultTemplateProps.selector)?.innerHTML).toContain('
I am test
'); }); it('read-tip dataFn', async () => { // 基础用法 - + function getDataFromDB({ element, rule }) { + return { content: element.innerHTML, title: rule.key }; + } const defaultTemplateProps = { appendToBody: false, selector: '#readtip-target', dataFn: getDataFromDB, key: 'GetData' }; - function getDataFromDB({ element, rule }) { - return { content: element.innerHTML, title: rule.key }; - } - const wrapper = mount(TipsTemplate, { + mount(TipsTemplate, { props: { defaultTemplateProps }, @@ -135,6 +133,9 @@ describe('read-tip test', () => { }); it('read-tip overlayClassName', async () => { + function getDataFromDB({ element, rule }) { + return { content: element.innerHTML, title: rule.key }; + } // 基础用法 const defaultTemplateProps = { appendToBody: false, @@ -143,10 +144,7 @@ describe('read-tip test', () => { key: 'GetData', overlayClassName: 'red' }; - function getDataFromDB({ element, rule }) { - return { content: element.innerHTML, title: rule.key }; - } - const wrapper = mount(TipsTemplate, { + mount(TipsTemplate, { props: { defaultTemplateProps }, diff --git a/packages/devui-vue/devui/read-tip/src/read-tip-template.tsx b/packages/devui-vue/devui/read-tip/src/read-tip-template.tsx index 185dbc0d59..3060bc8b4c 100644 --- a/packages/devui-vue/devui/read-tip/src/read-tip-template.tsx +++ b/packages/devui-vue/devui/read-tip/src/read-tip-template.tsx @@ -1,3 +1,4 @@ +import type { CSSProperties } from 'vue'; import { defineComponent, reactive, ref, onMounted, Teleport, toRefs } from 'vue'; import { readTipProps, ReadTipProps, DefaultTemplateProps } from './read-tip-types'; import './read-tip.scss'; @@ -11,7 +12,7 @@ export default defineComponent({ let rule: DefaultTemplateProps = defaultTemplateProps.value; const query = rule?.id ? `#${rule.id}` : rule.selector; - const styles: any = reactive({}); + const styles: CSSProperties = reactive({}); if (typeof rule.dataFn === 'function') { const dataFn = rule.dataFn({ element: document.querySelector(query), rule }); rule = { ...rule, ...dataFn }; @@ -27,7 +28,7 @@ export default defineComponent({ return deviationConstant; }; onMounted(() => { - const domBounding = document.querySelector(query).getBoundingClientRect(); + const domBounding = document.querySelector(query)?.getBoundingClientRect(); const { width, height } = domBounding; const distance = 10; let positionTop = 0; @@ -38,7 +39,7 @@ export default defineComponent({ let positionLeft = 0; - const targetDom = document.querySelector('.read-tip-container').getBoundingClientRect(); + const targetDom = document.querySelector('.read-tip-container')?.getBoundingClientRect(); if (rule.appendToBody) { positionTop = domBounding.y + document.documentElement.scrollTop; positionLeft = domBounding.x; @@ -72,7 +73,7 @@ export default defineComponent({ > { - rule.contentTemplate ? ctx.slots?.default() : + rule.contentTemplate ? ctx.slots?.default?.() : ( <>
diff --git a/packages/devui-vue/devui/read-tip/src/read-tip-types.ts b/packages/devui-vue/devui/read-tip/src/read-tip-types.ts index c9171aab4a..c1f8e6e2e4 100644 --- a/packages/devui-vue/devui/read-tip/src/read-tip-types.ts +++ b/packages/devui-vue/devui/read-tip/src/read-tip-types.ts @@ -1,17 +1,31 @@ import type { PropType, ExtractPropTypes } from 'vue'; -export const readTipProps = { - readTipOptions: { - type: Object as PropType - }, - defaultTemplateProps: { - type: Object as PropType - } -} as const; - export type Position = 'top' | 'left' | 'right' | 'bottom'; export type Trigger = 'hover' | 'click'; +export interface ReadTipRule { + id?: string; + key?: string; + selector: string | null; + trigger?: Trigger; + title?: string; + content?: string; + showAnimate?: boolean; + mouseenterTime?: number; + mouseleaveTime?: number; + position?: Position; + overlayClassName?: string; + appendToBody?: boolean; + status?: boolean; + // customData与template搭配使用,customData为传入模板的上下文,可以自定义模板内容 + dataFn?: ({ + element, + rule: ReadTipRule, + }) => { title?: string; content?: string }; +} + +export type ReadTipRules = ReadTipRule | ReadTipRule[]; + export type DefaultTemplateProps = { title?: string; content?: string; @@ -41,26 +55,13 @@ export interface ReadTipOptions { } -export type ReadTipRules = ReadTipRule | ReadTipRule[]; - -export interface ReadTipRule { - key?: string; - selector: string; - trigger?: Trigger; - title?: string; - content?: string; - showAnimate?: boolean; - mouseenterTime?: number; - mouseleaveTime?: number; - position?: Position; - overlayClassName?: string; - appendToBody?: boolean; - // customData与template搭配使用,customData为传入模板的上下文,可以自定义模板内容 - dataFn?: ({ - element, - rule: ReadTipRule, - }) => { title?: string; content?: string }; -} - +export const readTipProps = { + readTipOptions: { + type: Object as PropType + }, + defaultTemplateProps: { + type: Object as PropType + } +} as const; export type ReadTipProps = ExtractPropTypes; diff --git a/packages/devui-vue/devui/read-tip/src/read-tip.tsx b/packages/devui-vue/devui/read-tip/src/read-tip.tsx index 08305403e4..bbdee8f2a2 100644 --- a/packages/devui-vue/devui/read-tip/src/read-tip.tsx +++ b/packages/devui-vue/devui/read-tip/src/read-tip.tsx @@ -1,8 +1,22 @@ import { defineComponent, ref, onMounted, reactive, onUnmounted } from 'vue'; -import { readTipProps, ReadTipProps, ReadTipOptions } from './read-tip-types'; +import { readTipProps } from './read-tip-types'; +import type { ReadTipProps, ReadTipOptions, ReadTipRules, ReadTipRule } from './read-tip-types'; import './read-tip.scss'; import TipsTemplate from './read-tip-template'; +// 把传入的props.rules统一转为数组对象格式 +const rules = (ruleList: ReadTipRules) => { + if (ruleList === null) { return []; } + if (typeof ruleList === 'object' && !Array.isArray(ruleList)) { + ruleList = [ruleList]; + } + ruleList = [...ruleList]; + Array.isArray(ruleList) && ruleList.map(rule => { + rule.status = false; + }); + return ruleList; +}; + export default defineComponent({ name: 'DReadTip', props: readTipProps, @@ -21,8 +35,9 @@ export default defineComponent({ }; // 合并基础配置 const options = { ...defaultOptions, ...props.readTipOptions }; - const defaultSlot = ref(null); - const onMouseenter = (rule) => () => { + const defaultSlot = ref(null); + const refRules = reactive(rules(options.rules)); + const onMouseenter = (rule: ReadTipRule) => () => { setTimeout(() => { if (rule.id) { const a = refRules.find(u => u.id === rule.id); @@ -31,7 +46,7 @@ export default defineComponent({ rule.status = true; }, rule.mouseenterTime || options.mouseenterTime); }; - const onMouseleave = (rule) => () => { + const onMouseleave = (rule: ReadTipRule) => () => { setTimeout(() => { if (rule.id) { const a = refRules.find(u => u.id === rule.id); @@ -42,17 +57,17 @@ export default defineComponent({ }, rule.mouseleaveTime || options.mouseleaveTime); }; - const init = (rules, trigger = 'hover') => { - rules.map(rule => { + const init = (ruleList, trigger = 'hover') => { + ruleList.map(rule => { rule.status = false; trigger = rule.trigger || trigger; rule.overlayClassName = rule.overlayClassName || options.overlayClassName; rule.position = rule.position || options.position; rule.contentTemplate = !!(ctx.slots.contentTemplate); - if (!('appendToBody' in rule)) {rule.appendToBody = options.appendToBody;} - const doms = defaultSlot.value.querySelectorAll(rule.selector); + if (!('appendToBody' in rule)) { rule.appendToBody = options.appendToBody; } + const doms = defaultSlot.value?.querySelectorAll(rule.selector); [...doms].map((dom, index) => { - if (rule.appendToBody === false) {dom.style.position = 'relative';} + if (rule.appendToBody === false) { dom.style.position = 'relative'; } let newRule = reactive({ id: null }); @@ -61,34 +76,20 @@ export default defineComponent({ newRule = { ...rule }; dom.id = id; newRule.id = id; - rules.push(newRule); + ruleList.push(newRule); } - if (trigger === 'hover') { dom.addEventListener('mouseenter', onMouseenter(newRule.id ? newRule : rule,)); dom.addEventListener('mouseleave', onMouseleave(newRule.id ? newRule : rule)); } }); - }); - return rules; + return ruleList; }; - function show(dom, rule) { + function show(_: unknown, rule) { rule.status = true; } - // 把传入的props.rules统一转为数组对象格式 - const rules = (rules) => { - if (rules === null) {return;} - if (typeof rules === 'object' && !Array.isArray(rules)) { - rules = [rules]; - } - rules = [...rules]; - Array.isArray(rules) && rules.map(rule => { - rule.status = false; - }); - return rules; - }; - const refRules = reactive(rules(options.rules)); + const clickFn = () => { refRules.forEach(element => { element.status = false; @@ -109,56 +110,42 @@ export default defineComponent({ // 添加点击事件 当前元素是click事件目标则弹框展示 const onClick = (e: Event) => { for (const rule of refRules) { - const doms = defaultSlot.value.querySelectorAll(rule.selector); + const doms = defaultSlot.value?.querySelectorAll(rule.selector); for (const dom of doms) { if (doms.length > 1) { if (dom === e.target && rule.id) { show(dom, rule); return; - } else if (dom === e.target && !rule.id && !dom.id) { show(dom, rule); return; } - - } else - - if (dom === e.target) { + } else if (dom === e.target) { show(dom, rule); return; } else { rule.status = false; } } - } - }; return () => { return (
-
- { - ctx.slots?.default() - } +
+ {ctx.slots?.default?.()}
- {(refRules).map(rule => ( -
- {rule.status && ( - { - rule.contentTemplate && ctx.slots?.contentTemplate() - } - ) - - } +
+ {rule.status && ( + + { + rule.contentTemplate && ctx.slots?.contentTemplate?.() + } + + )}
- ) - )} + ))}
); };