<add>替换了一个组件

- 重新封装了一个通知消息组件,用于替换原来的全局通知组件 SkMessage
This commit is contained in:
skmcj committed 2025-05-28 15:57:55 +08:00
1 parent 918d19050b
commit 5159103ffe
11 files changed
+679 -34

No files matched your search

Vendored
-31
View File
@@ -1,32 +1 @@
/// <reference types="vite/client" />
/** 消息配置 */
interface MessageOptions {
/** 消息类型 */
type?: 'info' | 'success' | 'warning' | 'error' | 'help' | 'none';
/** 消息内容 */
content: string;
/** 关闭延时,0为不自动关闭 */
duration?: number;
/** 是否可关闭 */
closeable?: boolean;
pos?: 'left' | 'center' | 'right';
/** 自定义图标 */
icon?: string;
/** 自定义类 */
class?: string;
/** 关闭回调 */
onClose?: () => void;
}
/** 项目使用的全局消息组件 */
interface SkMessageObject {
success: (options: MessageOptions | string) => void;
error: (options: MessageOptions | string) => void;
warning: (options: MessageOptions | string) => void;
info: (options: MessageOptions | string) => void;
default: (options: MessageOptions | string) => void;
help: (options: MessageOptions | string) => void;
destroy: () => void;
}
declare const SkMessage: SkMessageObject;
-1
View File
@@ -8,7 +8,6 @@
<meta name="author" content="SKMCJ">
<title>抖音弹幕姬</title>
<script src="./mssdk.js"></script>
<script src="./skmessage.min.js"></script>
</head>
<body>
-1
View File
@@ -1 +0,0 @@
(()=>{"use strict";var e={591:(e,t,n)=>{n.d(t,{A:()=>i});var s=n(236),o=n.n(s),r=n(517),a=n.n(r)()(o());a.push([e.id,".sk-messages{box-sizing:border-box;margin:0;padding:0 24px;font-size:14px;line-height:1.5;position:fixed;top:8px;left:0;width:100%;pointer-events:none;z-index:1007}@media(max-width: 768px){.sk-messages{padding:0}}.sk-messages-destroy{animation:fade-out .3s ease-in-out}.sk-message-destroy{animation:slide-out .3s ease-in-out}.sk-message{position:relative;width:100%;box-sizing:border-box;padding:8px;display:flex;align-items:center}.sk-message.pos-center{justify-content:center}.sk-message.pos-left{justify-content:flex-start}.sk-message.pos-right{justify-content:flex-end}.sk-message-inner{pointer-events:painted;display:flex;align-items:center;box-sizing:border-box;padding:8px 12px;border-radius:5px;border:1px solid #bdcbd2;background-color:#f2f5f6;border-color:#bdcbd2;color:#bdcbd2;fill:#bdcbd2;animation:slide-in .3s ease-in-out}.sk-message-inner .sk-message-content{-webkit-user-select:none;-moz-user-select:none;user-select:none}.sk-message-inner.sk-message-info{background-color:#ebedef;border-color:#9aa7b1;color:#9aa7b1;fill:#9aa7b1}.sk-message-inner.sk-message-success{background-color:#d7f0e8;border-color:#38b48b;color:#38b48b;fill:#38b48b}.sk-message-inner.sk-message-warning{background-color:#fdefdb;border-color:#f6ad49;color:#f6ad49;fill:#f6ad49}.sk-message-inner.sk-message-error{background-color:#fad7d4;border-color:#e83929;color:#e83929;fill:#e83929}.sk-message-inner.sk-message-help{background-color:#ede5f1;border-color:#a67eb7;color:#a67eb7;fill:#a67eb7}.sk-message-close{width:14px;height:14px;display:flex;align-items:center;justify-content:center;margin-left:18px;cursor:pointer}.sk-message-close svg{transition:fill .3s ease-in-out}.sk-message-close:hover svg{fill:#e83929}.sk-message-icon{width:18px;height:18px;display:flex;align-items:center;justify-content:center;margin-right:8px}.sk-message-icon svg{width:100%;height:100%}.sk-message-icon i{font-size:18px}.sk-icon-info{fill:#9aa7b1}.sk-icon-success{fill:#38b48b}.sk-icon-warning{fill:#f6ad49}.sk-icon-error{fill:#e83929}.sk-icon-help{fill:#a67eb7}@keyframes fade-out{0%{opacity:1}100%{opacity:0}}@keyframes fade-in{0%{opacity:0}100%{opacity:1}}@keyframes slide-in{0%{opacity:0;transform:translateY(-60%)}100%{opacity:1;transform:translateY(0%)}}@keyframes slide-out{0%{opacity:1;transform:translateY(0%)}100%{opacity:0;transform:translateY(-60%)}}",""]);const i=a},517:e=>{e.exports=function(e){var t=[];return t.toString=function(){return this.map((function(t){var n="",s=void 0!==t[5];return t[4]&&(n+="@supports (".concat(t[4],") {")),t[2]&&(n+="@media ".concat(t[2]," {")),s&&(n+="@layer".concat(t[5].length>0?" ".concat(t[5]):""," {")),n+=e(t),s&&(n+="}"),t[2]&&(n+="}"),t[4]&&(n+="}"),n})).join("")},t.i=function(e,n,s,o,r){"string"==typeof e&&(e=[[null,e,void 0]]);var a={};if(s)for(var i=0;i<this.length;i++){var c=this[i][0];null!=c&&(a[c]=!0)}for(var d=0;d<e.length;d++){var l=[].concat(e[d]);s&&a[l[0]]||(void 0!==r&&(void 0===l[5]||(l[1]="@layer".concat(l[5].length>0?" ".concat(l[5]):""," {").concat(l[1],"}")),l[5]=r),n&&(l[2]?(l[1]="@media ".concat(l[2]," {").concat(l[1],"}"),l[2]=n):l[2]=n),o&&(l[4]?(l[1]="@supports (".concat(l[4],") {").concat(l[1],"}"),l[4]=o):l[4]="".concat(o)),t.push(l))}},t}},236:e=>{e.exports=function(e){return e[1]}},316:e=>{var t=[];function n(e){for(var n=-1,s=0;s<t.length;s++)if(t[s].identifier===e){n=s;break}return n}function s(e,s){for(var r={},a=[],i=0;i<e.length;i++){var c=e[i],d=s.base?c[0]+s.base:c[0],l=r[d]||0,p="".concat(d," ").concat(l);r[d]=l+1;var u=n(p),f={css:c[1],media:c[2],sourceMap:c[3],supports:c[4],layer:c[5]};if(-1!==u)t[u].references++,t[u].updater(f);else{var m=o(f,s);s.byIndex=i,t.splice(i,0,{identifier:p,updater:m,references:1})}a.push(p)}return a}function o(e,t){var n=t.domAPI(t);return n.update(e),function(t){if(t){if(t.css===e.css&&t.media===e.media&&t.sourceMap===e.sourceMap&&t.supports===e.supports&&t.layer===e.layer)return;n.update(e=t)}else n.remove()}}e.exports=function(e,o){var r=s(e=e||[],o=o||{});return function(e){e=e||[];for(var a=0;a<r.length;a++){var i=n(r[a]);t[i].references--}for(var c=s(e,o),d=0;d<r.length;d++){var l=n(r[d]);0===t[l].references&&(t[l].updater(),t.splice(l,1))}r=c}}},231:e=>{var t={};e.exports=function(e,n){var s=function(e){if(void 0===t[e]){var n=document.querySelector(e);if(window.HTMLIFrameElement&&n instanceof window.HTMLIFrameElement)try{n=n.contentDocument.head}catch(e){n=null}t[e]=n}return t[e]}(e);if(!s)throw new Error("Couldn't find a style target. This probably means that the value for the 'insert' parameter is invalid.");s.appendChild(n)}},584:e=>{e.exports=function(e){var t=document.createElement("style");return e.setAttributes(t,e.attributes),e.insert(t,e.options),t}},260:(e,t,n)=>{e.exports=function(e){var t=n.nc;t&&e.setAttribute("nonce",t)}},525:e=>{e.exports=function(e){if("undefined"==typeof document)return{update:function(){},remove:functiLine truncated
-1
View File
@@ -3,7 +3,6 @@
</template>
<script setup lang="ts">
import { ref } from 'vue';
import IndexView from './views/IndexView.vue';
import { printInfo, printSKMCJ } from './utils/logUtil';
+160
View File
@@ -0,0 +1,160 @@
import { createVNode, isVNode, render } from 'vue';
import { instances, skMessageTypes } from './instance';
import MessageConstructor from './message.vue';
import type { AppContext } from 'vue';
import type { SkMessageContext, SkMessageHandler, SkMessageOptions, SkMessageProps, SKMessageTypes } from './instance';
import { isFunction, isString } from '@/utils/typeUtil';
export type SkMessageParams = SkMessageOptions | SkMessageOptions['message'];
export type SkMessageFn = {
(options?: SkMessageParams, appContext?: null | AppContext): SkMessageHandler;
closeAll(type?: SKMessageTypes): void;
};
export type SkMessageParamWithType = Omit<SkMessageOptions, 'type'> | SkMessageOptions['message'];
export type MessageTypedFn = (options?: SkMessageParamWithType, appContext?: null | AppContext) => SkMessageHandler;
export type SkMessage = SkMessageFn & {
info: MessageTypedFn;
success: MessageTypedFn;
warning: MessageTypedFn;
error: MessageTypedFn;
help: MessageTypedFn;
default: MessageTypedFn;
};
export type SkMessageNormalOptions = Omit<SkMessageProps, 'id'> & {
appendTo: HTMLElement;
};
// 计数
let seed = 1;
// 创建消息
const createMessage = function ({ appendTo, ...options }: SkMessageNormalOptions, context?: AppContext | null) {
const id = `message_${seed++}`;
// 用户的关闭回调
const handleUserClose = options.onClose;
// 渲染容器
const container = document.createElement('div');
// 组件属性
const props = {
...options,
id,
onClose: () => {
handleUserClose?.();
closeMessage(instance);
},
onDestroy: () => {
render(null, container);
}
};
// vue 节点 VNode
const vnode = createVNode(
MessageConstructor,
props,
isFunction(props.message) || isVNode(props.message)
? {
default: isFunction(props.message) ? props.message : () => props.message
}
: null
);
// 节点上下文
vnode.appContext = context || message._context;
// 开始渲染
render(vnode, container);
// 插入页面
appendTo.appendChild(container.firstElementChild!);
// 虚拟节点
const vm = vnode.component!;
// 处理函数
const handler: SkMessageHandler = {
// 关闭
close: () => {
vm.exposed!.close();
}
};
// 实例对象
const instance: SkMessageContext = {
id,
vnode,
vm,
handler,
props: (vnode.component as any).props
};
return instance;
};
// 关闭消息
const closeMessage = function (instance: SkMessageContext) {
// 获取当前实例索引
const idx = instances.indexOf(instance);
if (idx === -1) return;
// 从集合中移除
instances.splice(idx, 1);
// 获取实例处理对象
const { handler } = instance;
// 调用关闭函数
handler.close();
};
// 关闭索引同类组件
const closeAll = function (type?: SKMessageTypes) {
const instancesToClose = [...instances];
for (const instance of instancesToClose) {
if (!type || type === instance.props.type) {
instance.handler.close();
}
}
};
// 规范配置
const normalizeOptions = function (params: SkMessageParams) {
const options: SkMessageOptions =
!params || isString(params) || isVNode(params) || isFunction(params) ? { message: params } : params;
const normalized = { ...options };
if (!normalized.appendTo) {
normalized.appendTo = document.body;
} else if (isString(normalized.appendTo)) {
const appendTo = document.querySelector<HTMLElement>(normalized.appendTo);
if (!appendTo) {
normalized.appendTo = document.body;
} else {
normalized.appendTo = appendTo;
}
}
return normalized as SkMessageNormalOptions;
};
// 主接口
const message: SkMessageFn & Partial<SkMessage> & { _context: AppContext | null } = function (
options = {},
context = null
) {
// 规范话配置
const normalized = normalizeOptions(options);
// 创建实例
const instance = createMessage(normalized, context);
// 加入集合
instances.push(instance);
// 返回处理函数
return instance.handler;
};
// 绑定类型函数
skMessageTypes.forEach(type => {
message[type] = (options = {}, appContext) => {
const normalized = normalizeOptions(options);
return message({ ...normalized, type }, appContext);
};
});
message.closeAll = closeAll;
message._context = null;
/**
* 消息组件
*/
export default message as SkMessage;
+77
View File
@@ -0,0 +1,77 @@
import { shallowReactive } from 'vue';
import type { ComponentInternalInstance, VNode } from 'vue';
// 组件返回处理器
export interface SkMessageHandler {
/**
* 关闭通知
*/
close: () => void;
}
export const skMessagePos = ['center', 'left', 'right'] as const;
export type SkMessagePos = (typeof skMessagePos)[number];
// 消息类型集
export const skMessageTypes = ['success', 'warning', 'info', 'error', 'help', 'default'] as const;
// 消息类型
export type SKMessageTypes = (typeof skMessageTypes)[number];
// 组件属性
export interface SkMessageProps {
id: string;
// 类型
type?: SKMessageTypes;
// 信息体
message?: string | VNode | (() => VNode);
// 关闭延时
duration?: number;
// Message 距离窗口顶部的偏移量
offset?: number;
// 自定义图标
icon?: string;
zIndex?: number;
// 自定义类
customClass?: string;
// 是否可手动关闭
closeable?: boolean;
// 位置
pos?: SkMessagePos;
// 监听关闭
onClose?: () => void;
}
export type SkMessageOptions = Partial<
Omit<SkMessageProps, 'id'> & {
appendTo: HTMLElement;
}
>;
export interface SkMessageContext {
id: string;
vnode: VNode;
handler: SkMessageHandler;
vm: ComponentInternalInstance;
props: SkMessageProps;
}
export const instances: SkMessageContext[] = shallowReactive([]);
// 根据ID获取组件
export const getInstance = (id: string) => {
const idx = instances.findIndex(instance => instance.id === id);
const current = instances[idx];
let prev: SkMessageContext | undefined;
if (idx > 0) {
prev = instances[idx - 1];
}
// 返回当前组件与前一个组件
return { current, prev };
};
// 获取ID的上一个组件底部纵坐标
export const getLastOffset = (id: string): number => {
const { prev } = getInstance(id);
if (!prev) return 0;
return prev.vm.exposed!.bottom.value;
};
// 获取组件偏移量
export const getOffsetOrSpace = (id: string, offset: number) => {
const idx = instances.findIndex(instance => instance.id === id);
return idx > 0 ? 16 : offset;
};
+327
View File
@@ -0,0 +1,327 @@
<template>
<Transition
name="sk-message-fade"
@before-enter="isStartTransition = true"
@before-leave="handleClose"
@after-leave="handleDestory">
<div
:class="{
'sk-message': true,
'sk-message-closeable': closeable,
[`sk-message-${type}`]: type,
[`sk-message-${pos}`]: pos,
[customClass]: customClass
}"
v-show="visible"
ref="elRef"
:style="customStyle"
role="alert"
@mouseenter="clearTimer"
@mouseleave="startTimer">
<i v-if="type !== 'default'" :class="`sk-message-icon ${typeIcon}`"></i>
<slot>
<p class="sk-message-content">
{{ message }}
</p>
</slot>
<div v-if="closeable || !duration" class="sk-message-close" @click.stop="close">
<i class="ice-close"></i>
</div>
</div>
</Transition>
</template>
<script setup lang="ts">
import { useResizeObserver } from '@/hooks/useResizeObserver';
import { computed, nextTick, onMounted, ref } from 'vue';
import { getLastOffset, getOffsetOrSpace } from './instance';
import type { CSSProperties } from 'vue';
import type { SkMessageProps } from './instance';
// 组件属性
const props = withDefaults(defineProps<SkMessageProps>(), {
id: '',
type: 'default',
duration: 3000,
offset: 16,
customClass: '',
closeable: false,
pos: 'center'
});
// 抛出事件
const emit = defineEmits<{
(e: 'destroy'): void;
}>();
// 是否开始过渡
const isStartTransition = ref(false);
// 主体元素DOM
const elRef = ref<HTMLDivElement>();
// 是否可见
const visible = ref(false);
const height = ref(0);
// 监听组件变化
useResizeObserver(elRef, () => {
height.value = elRef.value!.getBoundingClientRect().height;
});
const TypeIcons = {
info: 'ice-info',
success: 'ice-success',
warning: 'ice-warn',
error: 'ice-error',
help: 'ice-help',
default: ''
};
// 类型图标
const typeIcon = computed(() => {
if (props.icon) return props.icon;
else return TypeIcons[props.type];
});
// 上一个消息组件的底部在可视窗口中的纵坐标
const lastOffset = computed(() => getLastOffset(props.id));
// 当前组件的顶部纵坐标
const offset = computed(() => getOffsetOrSpace(props.id, props.offset) + lastOffset.value);
// 当前组件的底部纵坐标
const bottom = computed(() => height.value + offset.value);
// 样式
const customStyle = computed<CSSProperties>(() => ({
top: `${offset.value}px`,
zIndex: props.zIndex
}));
// 组件加载完毕
onMounted(() => {
// 开始计时
startTimer();
// 开始过渡显示
visible.value = true;
});
// 计时器
let timer: ReturnType<typeof setTimeout> | null = null;
// 开始计时
const startTimer = () => {
if (props.duration === 0) return;
stopTimer();
timer = setTimeout(() => {
timer = null;
// 关闭
close();
}, props.duration);
};
// 清除计时
const clearTimer = () => {
stopTimer();
};
// 停止计时
const stopTimer = () => {
if (timer) {
clearTimeout(timer);
timer = null;
}
};
// 关闭组件
const close = function () {
visible.value = false;
nextTick(() => {
// 如果组件还未开始过渡,直接销毁
if (!isStartTransition.value) {
props.onClose?.();
emit('destroy');
}
});
};
// 处理关闭
const handleClose = function () {
props.onClose?.();
};
// 处理销毁
const handleDestory = function () {
// 通知销毁
emit('destroy');
};
// 导出,使外部可访问
defineExpose({
visible,
bottom,
close
});
</script>
<style lang="scss" scoped>
@use 'sass:color';
$info: #9aa7b1;
$warning: #f6ad49;
$error: #e83929;
$success: #38b48b;
$help: #a67eb7;
$cancel: #108b96;
$white: #fff;
$black: #000;
$none: #bdcbd2;
$whiteWeight: 20%;
$blackWeight: 90%;
$borderRadius: 5px;
$borderColor: $none;
$bg: color.mix($none, $white, $whiteWeight);
$mainText: color.mix($none, $black, $blackWeight);
$lrMargin: 12px;
.sk-message {
width: fit-content;
max-width: calc(100% - 32px);
box-sizing: border-box;
border-radius: $borderRadius;
border: 1px solid $borderColor;
position: fixed;
left: 50%;
top: 20px;
transform: translate(-50%);
background-color: $bg;
transition: opacity 0.3s, transform 0.4s, top 0.4s;
padding: 8px 12px;
display: flex;
align-items: center;
gap: 8px;
font-size: 16px;
// 固定层级
z-index: 9999;
&.sk-message-info {
border-color: $info;
background-color: color.mix($info, $white, $whiteWeight);
.sk-message-content {
color: color.mix($info, $black, $blackWeight);
}
.sk-message-icon {
color: color.mix($info, $black, $blackWeight);
svg {
fill: color.mix($info, $black, $blackWeight);
}
}
}
&.sk-message-success {
border-color: $success;
background-color: color.mix($success, $white, $whiteWeight);
.sk-message-content {
color: color.mix($success, $black, $blackWeight);
}
.sk-message-icon {
color: color.mix($success, $black, $blackWeight);
svg {
fill: color.mix($success, $black, $blackWeight);
}
}
}
&.sk-message-warning {
border-color: $warning;
background-color: color.mix($warning, $white, $whiteWeight);
.sk-message-content {
color: color.mix($warning, $black, $blackWeight);
}
.sk-message-icon {
color: color.mix($warning, $black, $blackWeight);
svg {
fill: color.mix($warning, $black, $blackWeight);
}
}
}
&.sk-message-error {
border-color: $error;
background-color: color.mix($error, $white, $whiteWeight);
.sk-message-content {
color: color.mix($error, $black, $blackWeight);
}
.sk-message-icon {
color: color.mix($error, $black, $blackWeight);
svg {
fill: color.mix($error, $black, $blackWeight);
}
}
}
&.sk-message-help {
border-color: $help;
background-color: color.mix($help, $white, $whiteWeight);
.sk-message-content {
color: color.mix($help, $black, $blackWeight);
}
.sk-message-icon {
color: color.mix($help, $black, $blackWeight);
svg {
fill: color.mix($help, $black, $blackWeight);
}
}
}
&.sk-message-left {
left: $lrMargin;
transform: translate(0);
}
&.sk-message-right {
left: auto;
right: $lrMargin;
transform: translate(0);
}
}
.sk-message-content {
font-size: 14px;
margin: 0;
padding: 0;
overflow-wrap: break-word;
color: $mainText;
line-height: 1;
}
.sk-message-icon {
width: 1em;
height: 1em;
color: $mainText;
svg {
width: 1em;
height: 1em;
fill: $mainText;
}
}
.sk-message-close {
width: 1em;
height: 1em;
color: $none;
display: inline-flex;
align-items: center;
justify-content: center;
transition: color 0.3s ease-in-out;
cursor: pointer;
svg {
width: 1em;
height: 1em;
fill: $none;
transition: fill 0.3s ease-in-out;
}
&:hover {
color: $error;
svg {
fill: $error;
}
}
}
.sk-message-fade-enter-from,
.sk-message-fade-leave-to {
opacity: 0;
transform: translate(-50%, -100%);
}
@media (max-width: 768px) {
.sk-message-content {
text-align: center;
width: min-content;
}
}
</style>
+75
View File
@@ -0,0 +1,75 @@
import { unrefElement } from '@/utils/vueUtil';
import { computed, getCurrentScope, onScopeDispose, toValue, watch } from 'vue';
import type { MaybeRefOrGetter } from 'vue';
import type { MaybeComputedElementRef, MaybeElement } from '@/utils/vueUtil';
/** @vue/use useResizeObserver */
/**
* 如果有活跃的 effect 作用域,则调用 onScopeDispose
* - 即尝试取消当前作用域内的监听(副作用函数)
* @param fn
*/
export function tryOnScopeDispose(fn: () => void) {
if (getCurrentScope()) {
onScopeDispose(fn);
return true;
}
return false;
}
export interface UseResizeObserverOptions {
/*
* 指定一个自定义的 window 实例,例如使用 iframe 或在测试环境中
*/
window?: Window;
/**
* 观察者观察的框模型
* @default 'content-box'
*/
box?: ResizeObserverBoxOptions;
}
const defaultWindow = window;
export const useResizeObserver = function (
target: MaybeComputedElementRef | MaybeComputedElementRef[] | MaybeRefOrGetter<MaybeElement[]>,
callback: ResizeObserverCallback,
options: UseResizeObserverOptions = {}
) {
const { window = defaultWindow, ...observerOptions } = options;
let observer: ResizeObserver | undefined;
const cleanup = () => {
if (observer) {
observer.disconnect();
observer = undefined;
}
};
const targets = computed(() => {
const _targets = toValue(target);
return Array.isArray(_targets) ? _targets.map(el => unrefElement(el)) : [unrefElement(_targets)];
});
const stopWatch = watch(
targets,
els => {
cleanup();
observer = new ResizeObserver(callback);
for (const _el of els) {
if (_el) observer!.observe(_el, observerOptions);
}
},
{ immediate: true, flush: 'post' }
);
const stop = () => {
cleanup();
stopWatch();
};
tryOnScopeDispose(stop);
return { stop };
};
+18
View File
@@ -0,0 +1,18 @@
export const objectToString = Object.prototype.toString;
export const toTypeString = (value: unknown): string => objectToString.call(value);
export const isNumber = (val: any): val is number => typeof val === 'number';
export const isArray = Array.isArray;
export const isMap = (val: unknown): val is Map<any, any> => toTypeString(val) === '[object Map]';
export const isSet = (val: unknown): val is Set<any> => toTypeString(val) === '[object Set]';
export const isDate = (val: unknown): val is Date => toTypeString(val) === '[object Date]';
export const isRegExp = (val: unknown): val is RegExp => toTypeString(val) === '[object RegExp]';
export const isFunction = (val: unknown): val is Function => typeof val === 'function';
export const isString = (val: unknown): val is string => typeof val === 'string';
export const isSymbol = (val: unknown): val is symbol => typeof val === 'symbol';
export const isObject = (val: unknown): val is Record<any, any> => val !== null && typeof val === 'object';
export const isPromise = <T = any>(val: unknown): val is Promise<T> => {
return isObject(val) && isFunction(val.then) && isFunction(val.catch);
};
+20
View File
@@ -0,0 +1,20 @@
import type { ComponentPublicInstance, MaybeRef, MaybeRefOrGetter } from 'vue';
import { toValue } from 'vue';
export type VueInstance = ComponentPublicInstance;
export type MaybeElementRef<T extends MaybeElement = MaybeElement> = MaybeRef<T>;
export type MaybeComputedElementRef<T extends MaybeElement = MaybeElement> = MaybeRefOrGetter<T>;
export type MaybeElement = HTMLElement | SVGElement | VueInstance | undefined | null;
export type UnRefElementReturn<T extends MaybeElement = MaybeElement> = T extends VueInstance
? Exclude<MaybeElement, VueInstance>
: T | undefined;
/**
* 获取元素或 Vue 组件实例 ref 的 dom 元素
* @param elRef
*/
export function unrefElement<T extends MaybeElement>(elRef: MaybeComputedElementRef<T>): UnRefElementReturn<T> {
const plain = toValue(elRef);
return (plain as VueInstance)?.$el ?? plain;
}
+2
View File
@@ -68,6 +68,8 @@ import { ref, useTemplateRef } from 'vue';
import { CLog } from '@/utils/logUtil';
import { getId } from '@/utils/idUtil';
import { RelayCast } from '@/core/relay';
import SkMessage from '@/components/Message';
// 连接状态
const connectStatus = ref<ConnectStatus>(0);
// 转发状态