<add>替换了一个组件
- 重新封装了一个通知消息组件,用于替换原来的全局通知组件 SkMessage
This commit is contained in:
1 parent
918d19050b
commit
5159103ffe
11 files changed
+679
-34
No files matched your search
@@ -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;
|
||||
@@ -8,7 +8,6 @@
|
||||
<meta name="author" content="SKMCJ">
|
||||
<title>抖音弹幕姬</title>
|
||||
<script src="./mssdk.js"></script>
|
||||
<script src="./skmessage.min.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
Vendored
-1
@@ -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
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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>
|
||||
@@ -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 };
|
||||
};
|
||||
@@ -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);
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
// 转发状态
|
||||
|
||||
Reference in new issue
Block a user