first commit

This commit is contained in:
brusnitsyn
2026-06-24 17:20:43 +09:00
commit 43499acf1c
165 changed files with 25929 additions and 0 deletions

View File

@@ -0,0 +1,98 @@
import { nextTick, ref } from 'vue';
/**
* Очередь подтверждающих диалогов с поддержкой async-onConfirm и анимаций.
* Перенесено из проекта onboard (Composables/useAppDialog.js).
*
* Использование:
* const ok = await useAppDialog({ title: 'Удалить?', content: '...' })
*/
export interface AppDialogButtonProps {
type?: 'default' | 'primary' | 'info' | 'success' | 'warning' | 'error';
secondary?: boolean;
[key: string]: unknown;
}
export interface AppDialogItem {
id: number;
show: boolean;
title?: string;
content?: string;
loading: boolean;
onConfirm?: () => unknown | Promise<unknown>;
positiveText?: string;
negativeText?: string;
positiveProps?: AppDialogButtonProps;
negativeProps?: AppDialogButtonProps;
maskClosable?: boolean;
resolve: (confirmed: boolean) => void;
}
export interface AppDialogOptions {
title?: string;
content?: string;
positiveText?: string;
negativeText?: string;
positiveProps?: AppDialogButtonProps;
negativeProps?: AppDialogButtonProps;
maskClosable?: boolean;
onConfirm?: () => unknown | Promise<unknown>;
}
export const dialogQueue = ref<AppDialogItem[]>([]);
let idCounter = 0;
/** Закрытие диалога (кнопка / Esc / клик по маске). */
export function closeDialog(id: number, confirmed = false): void {
const dialog = dialogQueue.value.find((d) => d.id === id);
if (dialog && dialog.show) {
dialog.show = false;
dialog.resolve(confirmed);
}
}
/** Удаление из очереди после завершения leave-анимации. */
export function cleanupDialog(id: number): void {
dialogQueue.value = dialogQueue.value.filter((d) => d.id !== id);
}
export function useAppDialog(options: AppDialogOptions = {}): Promise<boolean> {
const {
title,
content,
positiveProps,
negativeProps,
positiveText = 'Подтвердить',
negativeText = 'Отмена',
maskClosable = false,
onConfirm,
} = options;
return new Promise<boolean>((resolve) => {
const id = idCounter++;
dialogQueue.value.push({
id,
show: false,
title,
content,
loading: false,
onConfirm,
positiveText,
negativeText,
positiveProps,
negativeProps,
maskClosable,
resolve,
});
nextTick(() => {
const dialog = dialogQueue.value.find((d) => d.id === id);
if (dialog) {
dialog.show = true;
}
});
});
}

View File

@@ -0,0 +1,57 @@
import { computed, ref } from 'vue';
/**
* Глобальный индикатор загрузки. Баннер показывается только если операция длится
* дольше SHOW_DELAY_MS — чтобы не мигать на быстрых переходах.
*
* Перенесено из проекта onboard (Composables/useGlobalLoading.js).
*/
const pending = ref(0);
const visible = ref(false);
let showTimer: ReturnType<typeof setTimeout> | null = null;
const SHOW_DELAY_MS = 3000;
const scheduleShow = (): void => {
if (visible.value || showTimer) {
return;
}
showTimer = setTimeout(() => {
showTimer = null;
if (pending.value > 0) {
visible.value = true;
}
}, SHOW_DELAY_MS);
};
const cancelScheduledShow = (): void => {
if (!showTimer) {
return;
}
clearTimeout(showTimer);
showTimer = null;
};
export const startGlobalLoading = (): void => {
pending.value += 1;
scheduleShow();
};
export const stopGlobalLoading = (): void => {
pending.value = Math.max(0, pending.value - 1);
if (pending.value === 0) {
cancelScheduledShow();
visible.value = false;
}
};
export const useGlobalLoading = () => {
return {
isGlobalLoading: computed(() => visible.value),
pendingCount: computed(() => pending.value),
};
};

View File

@@ -0,0 +1,41 @@
import type { NotificationOptions } from 'naive-ui';
/**
* Обёртка над discrete notification API NaiveUI с удобными хелперами.
* Перенесено и упрощено из проекта onboard (Composables/useNotification.js).
*/
export function useNotification() {
const showNotification = (options: NotificationOptions) => {
return window.$notification?.create(options) ?? null;
};
const success = (
title: string,
description = '',
options: Partial<NotificationOptions> = {},
) => {
return showNotification({
title,
description,
meta: options.meta ?? new Date().toLocaleDateString(),
...options,
type: 'success',
});
};
const error = (
title = 'Произошла ошибка',
description = '',
options: Partial<NotificationOptions> = {},
) => {
return showNotification({
title,
description,
meta: options.meta ?? new Date().toLocaleDateString(),
...options,
type: 'error',
});
};
return { success, error };
}