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
+
+
+ Let's see how to use ReadTip
+ Set selector to display readtip
+ The following is the target you want to show readtip
+ @Jack
+
+ 我是devui 基础用法
+
+
+
+
+
+
+
+```
+
+:::
+
+### 包括多个提示的readtip
+传入多个rule,设置不同元素的readtip显示模式。
+:::demo
+
+```vue
+
+
+ Multiple Readtips
+ You can pass in multiple rules to display different readtips
+ Click here to display first content
+ Click here to display second content
+ Hover here to display third content
+ Another third content with same class name
+
+
+
+
+
+
+
+
+```
+
+:::
+
+### d-read-tip
+
+d-read-tip 参数
+
+| 参数 | 类型 | 默认 | 说明 | 跳转 Demo | 全局配置项 |
+| -------------------- | ------------------ | ---- | ------------------------------- | ---------------------- | ---------- |
+| readTipOptions | ReadTipOptions | -- | 必选,配置提示选项 | 基本用法 | -- |
+| readTipOptions.rules | ReadTipRules | -- | 必选,配置 readtip 内容 | 包括多个提示的 readtip | -- |
+| contentTemplate | `TemplateRef