| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320 |
- <template>
- <view
- class="nana-notify-container"
- :style="{
- ...selectStyleType<ViewStyle, INotifyPosition>(position, 'center', {
- center: {
- justifyContent: 'center',
- },
- top: {
- justifyContent: 'flex-start',
- paddingTop: theme.resolveThemeSize('NotifyMarginWhenTop', 100),
- },
- bottom: {
- justifyContent: 'flex-end',
- paddingBottom: theme.resolveThemeSize('NotifyMarginWhenBottom', 100),
- },
- }),
- pointerEvents: 'none',
- ...themeStyles.notifyContainerStyle.value,
- }"
- >
- <view
- v-for="item in items"
- :key="item.id"
- :class="[
- 'nana-notify',
- item.showState ? 'show' : 'hide',
- ]"
- :style="{
- pointerEvents: 'auto',
- ...themeStyles.notifyStyle.value,
- ...selectStyleType(item.type, 'text', {
- text: {},
- loading: {},
- success: {
- backgroundColor: theme.resolveThemeColor('NotifySuccessBackgroundColor', 'background.success'),
- },
- fail: {
- backgroundColor: theme.resolveThemeColor('NotifyFailBackgroundColor', 'background.danger'),
- },
- info: {
- backgroundColor: theme.resolveThemeColor('NotifyInfoBackgroundColor', 'background.info'),
- },
- offline: {
- backgroundColor: theme.resolveThemeColor('NotifyOfflineBackgroundColor', 'background.warning'),
- },
- }),
- ...item.notifyStyle,
- }"
- @click="itemClick(item as NotifyItemData)"
- >
- <slot name="item"
- :id="item.id"
- :item="item"
- :tag="item.tag"
- >
- <ActivityIndicator
- v-if="item.type === 'loading'"
- :color="item.textStyle?.color as string || themeStyles.notifyTextStyle.value.color"
- :size="themeStyles.notifyIconStyle.value.size"
- />
- <Icon
- v-else-if="item.type !== 'text' || item.icon"
- :icon="item.icon ?? iconType[item.type!]"
- :color="item.textStyle?.color as string || themeStyles.notifyTextStyle.value.color"
- :size="themeStyles.notifyIconStyle.value.size"
- v-bind="item.iconProps"
- />
- <Height v-if="item.type !== 'text'" :size="themeStyles.notifyIconStyle.value.marginBottom" />
- <slot name="content">
- <text :style="{
- ...themeStyles.notifyTextStyle.value,
- ...item.textStyle
- }">
- {{ item.content }}
- </text>
- </slot>
- <Button
- v-if="item.button"
- type="text"
- size="small"
- :text="item.button"
- v-bind="item.buttonProps"
- @click="item.onButtonClick"
- />
- </slot>
- </view>
- </view>
- </template>
- <script setup lang="ts">
- import { reactive, ref } from 'vue';
- import { SimpleDelay } from '../utils/Timer';
- import { ArrayUtils } from '@imengyu/imengyu-utils';
- import { useTheme, type TextStyle, type ViewStyle } from '../theme/ThemeDefine';
- import { DynamicColor, DynamicSize, DynamicSize2, DynamicVar, selectStyleType } from '../theme/ThemeTools';
- import type { IconProps } from '../basic/Icon.vue';
- import ActivityIndicator from '../basic/ActivityIndicator.vue';
- import Icon from '../basic/Icon.vue';
- import Height from '../layout/space/Height.vue';
- import type { ButtonProp } from '../basic/Button.vue';
- import Button from '../basic/Button.vue';
- export type INotifyPosition = 'top'|'bottom'|'center';
- export type INotifyType = 'text'|'loading'|'success'|'fail'|'info'|'offline';
- export interface NotifyProps {
- /**
- * 提示显示位置
- * @default 'top'
- */
- position?: INotifyPosition;
- }
- export interface NotifyShowProps {
- /**
- * 用于自定义渲染
- */
- id?: number,
- /**
- * 用于自定义渲染
- */
- tag?: string,
- /**
- * 自动关闭的延时,单位ms。为0不会自动关闭
- * @default 4000
- */
- duration?: number;
- /**
- * 图标类型
- */
- type?: INotifyType;
- /**
- * 自定义图标
- */
- icon?: string;
- /**
- * 内容
- */
- content?: string;
- /**
- * 按钮文字
- */
- button?: string;
- /**
- * 按钮属性
- */
- buttonProps?: ButtonProp;
- /**
- * 图标自定义属性
- */
- iconProps?: IconProps;
- /**
- * 是否在点击后关闭
- * @default false
- */
- closeOnClick?: boolean;
- /**
- * 提示容器的自定义样式
- */
- notifyStyle?: ViewStyle;
- /**
- * 提示文字的自定义样式
- */
- textStyle?: TextStyle;
- /**
- * 按钮点击回调
- */
- onButtonClick?: (e: any) => void;
- /**
- * 关闭后回调
- */
- onClose?: () => void;
- /**
- * 点击回调
- */
- onClick?: () => void;
- }
- const props = withDefaults(defineProps<NotifyProps>(), {
- position: 'top',
- forbidClick: false,
- closeOnClick: false,
- })
- const theme = useTheme();
- const iconType: {
- [key: string]: string
- } = {
- success: theme.getVar('NotifyIconSuccess', 'success'),
- fail: theme.getVar('NotifyIconError', 'error'),
- offline: theme.getVar('NotifyIconOffline', 'cry'),
- info: theme.getVar('NotifyIconInfo', 'prompt'),
- };
- const themeStyles = theme.useThemeStyles({
- notifyContainerStyle: {
- gap: DynamicSize('NotifyContainerGap', 15),
- },
- notifyStyle: {
- backgroundColor: DynamicColor('NotifyBackgroundColor', 'background.notify'),
- padding: DynamicSize2('NotifyPaddingVertical', 'NotifyPaddingHorizontal', 25, 30),
- borderRadius: DynamicSize('NotifyRadius', 16),
- boxShadow: '0 0 10px 0 rgba(0, 0, 0, 0.05)',
- gap: DynamicSize('NotifyGap', 21),
- },
- notifyTextStyle: {
- color: DynamicColor('NotifyTextColor', 'text.content'),
- },
- notifyIconStyle: {
- size: DynamicVar('NotifyIconSize', 45),
- marginBottom: DynamicSize('NotifyIconMarginBottom', 12),
- },
- })
- interface NotifyItemData extends NotifyShowProps {
- id: number;
- showState: boolean;
- showTimer: SimpleDelay,
- hideTimer: SimpleDelay,
- updateProps(options: NotifyShowProps): void;
- close() : void;
- }
- const items = ref<NotifyItemData[]>([]);
- let ids = 0;
- function show(options: NotifyShowProps) {
- const {
- duration = 4000,
- type = 'text',
- } = options;
- const item = reactive<NotifyItemData>({
- ...options,
- id: options.id ?? ids++,
- duration,
- type,
- updateProps(options: NotifyShowProps) {
- for (const key in options) {
- const v = (options as any)[key];
- if (v !== undefined)
- (this as any)[key] = v;
- }
- },
- close() {
- this.showState = false;
- this.hideTimer.start();
- this.onClose?.();
- },
- showState: false,
- showTimer: new SimpleDelay(undefined, () => {
- item.close()
- }, duration),
- hideTimer: new SimpleDelay(undefined, () => {
- ArrayUtils.remove(items.value, item);
- }, 300),
- });
- if (duration > 0)
- item.showTimer.start();
- items.value.push(item);
- setTimeout(() => item.showState = true, 20);
- return item;
- }
- function itemClick(item: NotifyItemData) {
- item.onClick?.();
- if (props.closeOnClick)
- item.close();
- }
- function closeAll() {
- ArrayUtils.clear(items.value);
- }
- export interface NotifyItemInstance {
- updateProps(options?: Omit<NotifyShowProps, 'duration'|'onClose'>): void;
- close() : void;
- }
- export interface NotifyInstance {
- show(options: NotifyShowProps) : NotifyItemInstance;
- closeAll(): void;
- }
- defineExpose<NotifyInstance>({
- show,
- closeAll,
- })
- </script>
- <style lang="scss">
- .nana-notify-container {
- position: fixed;
- left: 0;
- right: 0;
- top: 0;
- bottom: 0;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- z-index: 100;
-
- .nana-notify {
- display: flex;
- flex-direction: row;
- align-items: center;
- justify-content: center;
- opacity: 0;
- transition: opacity ease-in-out 0.3s;
- &.show {
- opacity: 1;
- }
- }
- }
- </style>
|