diff --git a/packages/devui-vue/devui/popover/src/popover.tsx b/packages/devui-vue/devui/popover/src/popover.tsx index f467f8b8808b3ff36751ea7b45d8df554e10d338..d0b367c3efb2e16373162adb9ded6e6084ff8e13 100644 --- a/packages/devui-vue/devui/popover/src/popover.tsx +++ b/packages/devui-vue/devui/popover/src/popover.tsx @@ -16,7 +16,7 @@ const popTypeClass = { export default defineComponent({ name: 'DPopover', - + directives: { clickoutside: clickoutsideDirective }, @@ -81,7 +81,11 @@ export default defineComponent({ setup(props, ctx) { const { slots } = ctx; const visible = ref(props.visible); - const { position, content, zIndex, trigger, popType, popoverStyle, mouseEnterDelay, mouseLeaveDelay, showAnimation, popMaxWidth } = toRefs(props); + const { + position, content, zIndex, trigger, popType, + popoverStyle, mouseEnterDelay, mouseLeaveDelay, + showAnimation, popMaxWidth + } = toRefs(props); const style: CSSProperties = { zIndex: zIndex.value, ...popoverStyle.value } const isClick = trigger.value === 'click' const iconType = reactive(popTypeClass[popType.value]) @@ -110,7 +114,12 @@ export default defineComponent({ 'devui-popover-isVisible': visible.value } ]} > -
+
{slots.reference?.()}
diff --git a/packages/devui-vue/devui/read-tip/__tests__/read-tip.spec.ts b/packages/devui-vue/devui/read-tip/__tests__/read-tip.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..fae43c0e63a45a3f7dca07b4e5b4a02c22c6f84c --- /dev/null +++ b/packages/devui-vue/devui/read-tip/__tests__/read-tip.spec.ts @@ -0,0 +1,8 @@ +import { mount } from '@vue/test-utils'; +import { ReadTip, ReadTipDirective } from '../index'; + +describe('read-tip test', () => { + it('read-tip init render', async () => { + // todo + }) +}) diff --git a/packages/devui-vue/devui/read-tip/index.ts b/packages/devui-vue/devui/read-tip/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..db1b532c6fda5e7fcce0288f9baa6f3d312885f1 --- /dev/null +++ b/packages/devui-vue/devui/read-tip/index.ts @@ -0,0 +1,17 @@ +import type { App } from 'vue' +import ReadTip from './src/read-tip' + +ReadTip.install = function(app: App): void { + app.component(ReadTip.name, ReadTip) +} + +export { ReadTip, } + +export default { + title: 'ReadTip 阅读提示', + category: '反馈', + status: '30%', // TODO: 组件若开发完成则填入"已完成",并删除该注释 + install(app: App): void { + app.use(ReadTip as any) + } +} diff --git a/packages/devui-vue/devui/read-tip/src/read-tip-directive.ts b/packages/devui-vue/devui/read-tip/src/read-tip-directive.ts new file mode 100644 index 0000000000000000000000000000000000000000..788dde30eedb905e475ad731757230cb936f4d3a --- /dev/null +++ b/packages/devui-vue/devui/read-tip/src/read-tip-directive.ts @@ -0,0 +1,10 @@ +// can export function. +export default { + // created() { }, + // beforeMount() { }, + // mounted() { }, + // beforeUpdate() { }, + // updated() { }, + // beforeUnmount() { }, + // unmounted() { } +} 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 new file mode 100644 index 0000000000000000000000000000000000000000..9ef7afb9187703e179d1c1f64622da87df0a0db7 --- /dev/null +++ b/packages/devui-vue/devui/read-tip/src/read-tip-template.tsx @@ -0,0 +1,52 @@ +import { defineComponent, ref, onMounted, Teleport } from 'vue' +import { readTipProps, ReadTipProps, } from './read-tip-types' +import './read-tip.scss' + +export default defineComponent({ + name: 'DReadTipTemplate', + props: readTipProps, + emits: [], + setup(props: ReadTipProps, ctx) { + const query = props.defaultTemplateProps?.id ? `#${props.defaultTemplateProps.id}` : props.defaultTemplateProps.selector + + const temp = ref(null) + onMounted(() => { + // 当定位为top展示时 元素定位高度需要计算弹窗的高度 + if(props.defaultTemplateProps.position == 'top') { + temp.value.style.top = (- temp.value.offsetHeight - 10) + 'px' + } + }) + return () => { + return ( + +
+ + { + props.defaultTemplateProps.contentTemplate ? ctx.slots?.default() : + ( + <> +
+ {props.defaultTemplateProps.title} +
+
+ {props.defaultTemplateProps.content} +
+ + ) + } + + {/* { + 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 new file mode 100644 index 0000000000000000000000000000000000000000..422d29909a14880fc5d8dd19b8061a773d07ca69 --- /dev/null +++ b/packages/devui-vue/devui/read-tip/src/read-tip-types.ts @@ -0,0 +1,59 @@ +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 type DefaultTemplateProps = { + title?: string + content?: string + top?: number + selector?: string + position?: string + id? : string + temp: string + contentTemplate: boolean +} + +export interface ReadTipOptions { + trigger?: Trigger + showAnimate?: boolean + mouseenterTime?: number + mouseleaveTime?: number + position?: Position + overlayClassName?: string + appendToBody?: boolean + rules: ReadTipRules +} + +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, + // }) => Observable<{ title?: string; content?: string; template?: TemplateRef; customData?: any }> +} + + +export type ReadTipProps = ExtractPropTypes diff --git a/packages/devui-vue/devui/read-tip/src/read-tip.scss b/packages/devui-vue/devui/read-tip/src/read-tip.scss new file mode 100644 index 0000000000000000000000000000000000000000..e113de163cc787ef65f0dcbe8cee85c856fa6dc5 --- /dev/null +++ b/packages/devui-vue/devui/read-tip/src/read-tip.scss @@ -0,0 +1,79 @@ +@import '../../style/theme/color'; +@import '../../style/theme/variables'; +@import '../../style/mixins/index'; +@import '../../style/theme/shadow'; +@import '../../style/theme/corner'; +@import '../../style/core/_font'; +// @import '../../style/core/z-index'; +.devui-read-tip { + position: relative; +} + +.source { + overflow: initial; +} + +.read-tip-container { + font-size: $devui-font-size; + position: absolute; + width: max-content; + height: max-content; + // top: 0; + // left: 0; + line-height: 1.5; + border: none; + border-radius: $devui-border-radius-feedback; + // z-index: $devui-z-index-pop-up; + background-color: $devui-feedback-overlay-bg; + color: $devui-feedback-overlay-text; + overflow-wrap: break-word; + padding: 10px; + z-index: 50; + + .after { + content: ''; + width: 12px; + height: 12px; + transform: rotate(45deg); + position: absolute; + background-color: $devui-feedback-overlay-bg; + } + // 定位 top + &.top { + top: -60px; + left: 0; + + .after { + bottom: -4px; + } + } + + // 定位 left + &.left { + right: calc(100% + 10px); + top: -2px; + + .after { + right: -4px; + } + } + + // 定位 right + &.right { + left: calc(100% + 10px); + top: -2px; + + .after { + left: -4px; + } + } + + // 定位 bottom + &.bottom { + top: calc(100% + 10px); + + .after { + top: -4px; + } + } +} diff --git a/packages/devui-vue/devui/read-tip/src/read-tip.tsx b/packages/devui-vue/devui/read-tip/src/read-tip.tsx new file mode 100644 index 0000000000000000000000000000000000000000..74333c4dbd8d756fb44aaf932a2a8c4ff4adfc1b --- /dev/null +++ b/packages/devui-vue/devui/read-tip/src/read-tip.tsx @@ -0,0 +1,163 @@ +import { defineComponent, ref, onMounted, reactive, Teleport, onUnmounted } from 'vue' +import { readTipProps, ReadTipProps, ReadTipOptions } from './read-tip-types' +import './read-tip.scss' +import TipsTemplate from './read-tip-template'; + +export default defineComponent({ + name: 'DReadTip', + props: readTipProps, + emits: [], + setup(props: ReadTipProps, ctx) { + // 默认配置 + const defaultOptions: ReadTipOptions = { + trigger: 'hover', + showAnimate: false, + mouseenterTime: 100, + mouseleaveTime: 100, + position: 'top', + overlayClassName: '', + appendToBody: true, + rules: { selector: null }, + }; + const tempTop = ref(0) + // 合并基础配置 + const options = { ...defaultOptions, ...props.readTipOptions } + const defaultSlot = ref(null) + const onMouseenter = (rule) => () => { + setTimeout(() => { + if (rule.id) { + const a = refRules.find(u => u.id === rule.id) + a.status = true + } + rule.status = true + }, rule.mouseenterTime || options.mouseenterTime); + } + const onMouseleave = (rule) => () => { + setTimeout(() => { + if (rule.id) { + const a = refRules.find(u => u.id === rule.id) + a.status = false + } + rule.status = false + + }, rule.mouseleaveTime || options.mouseleaveTime); + } + + const init = (rules, trigger = 'hover') => { + rules.map(rule => { + rule.status = false + trigger = rule.trigger || trigger + rule.contentTemplate = !!(ctx.slots.contentTemplate) + const doms = defaultSlot.value.querySelectorAll(rule.selector); + [...doms].map((dom, index) => { + dom.style.position = 'relative' + + let newRule = reactive({ + id: null + }) + if (index > 0) { + newRule = { ...rule } + dom.id = rule.selector.slice(1) + index + newRule.id = rule.selector.slice(1) + index + rules.push(newRule) + } + + if (trigger === 'hover') { + dom.addEventListener('mouseenter', onMouseenter(newRule.id ? newRule : rule,)) + dom.addEventListener('mouseleave', onMouseleave(newRule.id ? newRule : rule)) + } + }) + + }) + return rules + } + function show(dom, rule) { + const top = dom.offsetTop + 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 + }) + } + onMounted(() => { + init(refRules, options.trigger) + // 点击其他位置 关闭弹框 + document.addEventListener('click', clickFn, true) + + }) + + onUnmounted(() => { + // 取消事件 + document.removeEventListener('click', clickFn) + }) + + // 添加点击事件 当前元素是click事件目标则弹框展示 + const onClick = (e: Event) => { + for (const rule of refRules) { + 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) { + show(dom, rule) + return + } else { + rule.status = false + } + } + + } + + } + return () => { + return (
+
+ { + ctx.slots?.default() + } +
+ + {(refRules).map(rule => ( +
+ {rule.status && ( + { + rule.contentTemplate && ctx.slots?.contentTemplate() + } + ) + + } +
+ ) + )} +
) + } + } +}) diff --git a/packages/devui-vue/docs/components/read-tip/index.md b/packages/devui-vue/docs/components/read-tip/index.md new file mode 100644 index 0000000000000000000000000000000000000000..d3c4c955d939359bd24083daa106f6cb5c54c3ce --- /dev/null +++ b/packages/devui-vue/docs/components/read-tip/index.md @@ -0,0 +1,194 @@ +# ReadTip 阅读提示 + +阅读提示组件。 + +### 何时使用 + +当html文档中需要对特定内容进行提示时使用。 + + +### 基本用法 +通过设置selector选择需要显示readtip的元素,传入title和content设置显示的内容。 +:::demo + +```vue + + + + + +``` + +::: + +### 包括多个提示的readtip +传入多个rule,设置不同元素的readtip显示模式。 +:::demo + +```vue + + + + + +``` + +::: + +### d-read-tip + +d-read-tip 参数 + +| 参数 | 类型 | 默认 | 说明 | 跳转 Demo | 全局配置项 | +| -------------------- | ------------------ | ---- | ------------------------------- | ---------------------- | ---------- | +| readTipOptions | ReadTipOptions | -- | 必选,配置提示选项 | 基本用法 | -- | +| readTipOptions.rules | ReadTipRules | -- | 必选,配置 readtip 内容 | 包括多个提示的 readtip | -- | +| contentTemplate | `TemplateRef` | -- | 可选,传入模板显示 readtip 内容 | 传入模板显示内容 | -- | + + + +``` +export interface ReadTipOptions { + trigger?: 'hover' | 'click'; // 默认值是 hover + showAnimate?: boolean; // 默认值是 false + mouseenterTime?: number; // 默认值是 100 + mouseleaveTime?: number; // 默认值是 100 + position?: PositionType | PositionType[]; // 默认值是 'top' + overlayClassName?: string; // 默认值为空字符串 + appendToBody?: boolean; // 默认值为true + rules: ReadTipRules; +} +export type ReadTipRules = ReadTipRule | ReadTipRule[]; + +export interface ReadTipRule { + key?: string; + selector: string; + trigger?: 'hover' | 'click'; // 可以继承自 ReadTipOptions + title?: string; + content?: string; + showAnimate?: boolean; // 可以继承自 ReadTipOptions + mouseenterTime?: number; // 可以继承自 ReadTipOptions + mouseleaveTime?: number; // 可以继承自 ReadTipOptions + position?: PositionType | PositionType[]; // 可以继承自 ReadTipOptions + overlayClassName?: string; // 可以继承自 ReadTipOptions + appendToBody?: boolean; //可以继承自 ReadTipOtions + //customData与template搭配使用,customData为传入模板的上下文,可以自定义模板内容 + dataFn?: ({ + element, + rule: ReadTipRule, + }) => Observable<{ title?: string; content?: string; template?: TemplateRef; customData?: any }>; +} +``` +