ソースを参照

feat(app): 添加全局消息模块并更新样式

- 新增 globalMessage.ts 模块,实现全局消息显示功能
- 添加 globals.css 文件,定义全局样式变量
- 更新 globals.css,引入 tw-animate-css动画库
nahida 9 ヶ月 前
コミット
6eddf73be0
3 ファイル変更396 行追加0 行削除
  1. 127 0
      app/(other)/test8/globals.css
  2. 268 0
      app/_modules/globalMessage.ts
  3. 1 0
      app/globals.css

+ 127 - 0
app/(other)/test8/globals.css

@@ -0,0 +1,127 @@
+@import "tailwindcss";
+
+
+html {
+    --font-sans: ${GeistSans.variable};
+    --font-mono: ${GeistMono.variable};
+}
+
+:root {
+    --background: oklch(1 0 0);
+    --foreground: oklch(0.145 0 0);
+    --card: oklch(1 0 0);
+    --card-foreground: oklch(0.145 0 0);
+    --popover: oklch(1 0 0);
+    --popover-foreground: oklch(0.145 0 0);
+    --primary: oklch(0.205 0 0);
+    --primary-foreground: oklch(0.985 0 0);
+    --secondary: oklch(0.97 0 0);
+    --secondary-foreground: oklch(0.205 0 0);
+    --muted: oklch(0.97 0 0);
+    --muted-foreground: oklch(0.556 0 0);
+    --accent: oklch(0.97 0 0);
+    --accent-foreground: oklch(0.205 0 0);
+    --destructive: oklch(0.577 0.245 27.325);
+    --destructive-foreground: oklch(0.577 0.245 27.325);
+    --border: oklch(0.922 0 0);
+    --input: oklch(0.922 0 0);
+    --ring: oklch(0.708 0 0);
+    --chart-1: oklch(0.646 0.222 41.116);
+    --chart-2: oklch(0.6 0.118 184.704);
+    --chart-3: oklch(0.398 0.07 227.392);
+    --chart-4: oklch(0.828 0.189 84.429);
+    --chart-5: oklch(0.769 0.188 70.08);
+    --radius: 0.625rem;
+    --sidebar: oklch(0.985 0 0);
+    --sidebar-foreground: oklch(0.145 0 0);
+    --sidebar-primary: oklch(0.205 0 0);
+    --sidebar-primary-foreground: oklch(0.985 0 0);
+    --sidebar-accent: oklch(0.97 0 0);
+    --sidebar-accent-foreground: oklch(0.205 0 0);
+    --sidebar-border: oklch(0.922 0 0);
+    --sidebar-ring: oklch(0.708 0 0);
+}
+
+.dark {
+    --background: oklch(0.145 0 0);
+    --foreground: oklch(0.985 0 0);
+    --card: oklch(0.145 0 0);
+    --card-foreground: oklch(0.985 0 0);
+    --popover: oklch(0.145 0 0);
+    --popover-foreground: oklch(0.985 0 0);
+    --primary: oklch(0.985 0 0);
+    --primary-foreground: oklch(0.205 0 0);
+    --secondary: oklch(0.269 0 0);
+    --secondary-foreground: oklch(0.985 0 0);
+    --muted: oklch(0.269 0 0);
+    --muted-foreground: oklch(0.708 0 0);
+    --accent: oklch(0.269 0 0);
+    --accent-foreground: oklch(0.985 0 0);
+    --destructive: oklch(0.396 0.141 25.723);
+    --destructive-foreground: oklch(0.637 0.237 25.331);
+    --border: oklch(0.269 0 0);
+    --input: oklch(0.269 0 0);
+    --ring: oklch(0.439 0 0);
+    --chart-1: oklch(0.488 0.243 264.376);
+    --chart-2: oklch(0.696 0.17 162.48);
+    --chart-3: oklch(0.769 0.188 70.08);
+    --chart-4: oklch(0.627 0.265 303.9);
+    --chart-5: oklch(0.645 0.246 16.439);
+    --sidebar: oklch(0.205 0 0);
+    --sidebar-foreground: oklch(0.985 0 0);
+    --sidebar-primary: oklch(0.488 0.243 264.376);
+    --sidebar-primary-foreground: oklch(0.985 0 0);
+    --sidebar-accent: oklch(0.269 0 0);
+    --sidebar-accent-foreground: oklch(0.985 0 0);
+    --sidebar-border: oklch(0.269 0 0);
+    --sidebar-ring: oklch(0.439 0 0);
+}
+
+@theme inline {
+    /* optional: --font-sans, --font-serif, --font-mono if they are applied in the layout.tsx */
+    --color-background: var(--background);
+    --color-foreground: var(--foreground);
+    --color-card: var(--card);
+    --color-card-foreground: var(--card-foreground);
+    --color-popover: var(--popover);
+    --color-popover-foreground: var(--popover-foreground);
+    --color-primary: var(--primary);
+    --color-primary-foreground: var(--primary-foreground);
+    --color-secondary: var(--secondary);
+    --color-secondary-foreground: var(--secondary-foreground);
+    --color-muted: var(--muted);
+    --color-muted-foreground: var(--muted-foreground);
+    --color-accent: var(--accent);
+    --color-accent-foreground: var(--accent-foreground);
+    --color-destructive: var(--destructive);
+    --color-destructive-foreground: var(--destructive-foreground);
+    --color-border: var(--border);
+    --color-input: var(--input);
+    --color-ring: var(--ring);
+    --color-chart-1: var(--chart-1);
+    --color-chart-2: var(--chart-2);
+    --color-chart-3: var(--chart-3);
+    --color-chart-4: var(--chart-4);
+    --color-chart-5: var(--chart-5);
+    --radius-sm: calc(var(--radius) - 4px);
+    --radius-md: calc(var(--radius) - 2px);
+    --radius-lg: var(--radius);
+    --radius-xl: calc(var(--radius) + 4px);
+    --color-sidebar: var(--sidebar);
+    --color-sidebar-foreground: var(--sidebar-foreground);
+    --color-sidebar-primary: var(--sidebar-primary);
+    --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
+    --color-sidebar-accent: var(--sidebar-accent);
+    --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
+    --color-sidebar-border: var(--sidebar-border);
+    --color-sidebar-ring: var(--sidebar-ring);
+}
+
+@layer base {
+    * {
+        @apply border-border outline-ring/50;
+    }
+    body {
+        @apply bg-background text-foreground;
+    }
+}

+ 268 - 0
app/_modules/globalMessage.ts

@@ -0,0 +1,268 @@
+// 定义消息类型
+type MessageType = 'success' | 'error' | 'default' | 'warning' | 'info';
+
+// 定义消息配置接口
+interface MessageConfig {
+  content: string;
+  type?: MessageType;
+  duration?: number;
+}
+
+// 定义消息返回接口
+interface MessageInstance {
+  close: () => void;
+}
+
+// 消息容器样式
+const containerStyle = `
+  position: fixed;
+  top: 20px;
+  right: 20px;
+  z-index: 9999;
+  display: flex;
+  flex-direction: column;
+  gap: 8px;
+`;
+
+// 消息基础样式
+const baseMessageStyle = `
+  padding: 12px 16px;
+  border-radius: 4px;
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  min-width: 300px;
+  max-width: 500px;
+  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+  transition: all 0.3s ease;
+  opacity: 0;
+  transform: translateX(100%);
+  word-break: break-word;
+`;
+
+// 成功消息样式
+const successStyle = `
+  ${baseMessageStyle}
+  background-color: #f0f9eb;
+  border-left: 4px solid #52c41a;
+  color: #1f6d31;
+`;
+
+// 警告消息样式
+const warningStyle = `  ${baseMessageStyle}  background-color: #fffbe6;
+  border-left: 4px solid #faad14;
+  color: #d48806;`;
+
+// 错误消息样式
+const errorStyle = `
+  ${baseMessageStyle}
+  background-color: #fff2f0;
+  border-left: 4px solid #ff4d4f;
+  color: #b3261e;
+`;
+
+// 信息消息样式
+const infoStyle = `
+  ${baseMessageStyle}
+  background-color: #e6f7ff;
+  border-left: 4px solid #1890ff;
+  color: #0c5460;
+`;
+
+// 默认消息样式
+const defaultStyle = `
+  ${baseMessageStyle}
+  background-color: #f0f2f5;
+  border-left: 4px solid #1890ff;
+  color: #1890ff;
+`;
+
+// 图标样式
+const iconStyle = `
+  font-size: 16px;
+`;
+
+// 消息容器
+let container: HTMLDivElement | null = null;
+let messageCount = 0;
+
+// 创建消息容器
+function createContainer(): void {
+  if (container) return;
+
+  container = document.createElement('div');
+  container.style.cssText = containerStyle;
+  document.body.appendChild(container);
+}
+
+// 创建图标元素
+function createIcon(type: MessageType): HTMLSpanElement {
+  const icon = document.createElement('span');
+  icon.style.cssText = iconStyle;
+
+  switch(type) {
+    case 'success':
+      icon.innerHTML = '✓'; // 成功图标
+      break;
+    case 'error':
+      icon.innerHTML = '✕'; // 错误图标
+      break;
+    case 'warning':
+      icon.innerHTML = '⚠'; // 警告图标
+      break;
+    case 'info':
+      icon.innerHTML = 'ℹ'; // 信息图标
+      break;
+    default:
+      icon.innerHTML = 'i'; // 信息图标
+  }
+
+  return icon;
+}
+
+// 显示消息
+function showMessage(
+  content: string,
+  type: MessageType = 'default',
+  duration: number = 3000
+): MessageInstance {
+  createContainer();
+
+  // 创建消息元素
+  const messageId = `message-${Date.now()}-${messageCount++}`;
+  const messageEl = document.createElement('div');
+  messageEl.id = messageId;
+
+  // 设置样式
+  switch(type) {
+    case 'success':
+      messageEl.style.cssText = successStyle;
+      break;
+    case 'error':
+      messageEl.style.cssText = errorStyle;
+      break;
+    case 'warning':
+      messageEl.style.cssText = warningStyle;
+      break;
+    case 'info':
+      messageEl.style.cssText = infoStyle;
+      break;
+    default:
+      messageEl.style.cssText = defaultStyle;
+  }
+
+  // 创建图标和内容
+  const icon = createIcon(type);
+  const contentEl = document.createElement('span');
+  contentEl.textContent = content;
+
+  // 组装消息元素
+  messageEl.appendChild(icon);
+  messageEl.appendChild(contentEl);
+  container!.appendChild(messageEl);
+
+  // 触发动画
+  setTimeout(() => {
+    messageEl.style.opacity = '1';
+    messageEl.style.transform = 'translateX(0)';
+  }, 10);
+
+  // 自动关闭
+  const timer = setTimeout(() => {
+    closeMessage(messageId);
+  }, duration);
+
+  // 点击关闭
+  messageEl.addEventListener('click', () => {
+    clearTimeout(timer);
+    closeMessage(messageId);
+  });
+
+  return {
+    close: () => {
+      clearTimeout(timer);
+      closeMessage(messageId);
+    }
+  };
+}
+
+// 关闭消息
+function closeMessage(id: string): void {
+  const messageEl = document.getElementById(id);
+  if (!messageEl) return;
+
+  // 触发退出动画
+  messageEl.style.opacity = '0';
+  messageEl.style.transform = 'translateX(100%)';
+
+  // 移除元素
+  setTimeout(() => {
+    if (container && messageEl.parentNode === container) {
+      container.removeChild(messageEl);
+    }
+  }, 300);
+}
+
+// 导出公共方法
+export const message = {
+  /**
+   * 显示成功消息
+   * @param content 消息内容
+   * @param duration 显示时长(毫秒),默认3000
+   * @returns 包含close方法的消息实例
+   */
+  success: (content: string, duration: number = 3000): MessageInstance => {
+    return showMessage(content, 'success', duration);
+  },
+
+  /**
+   * 显示错误消息
+   * @param content 消息内容
+   * @param duration 显示时长(毫秒),默认3000
+   * @returns 包含close方法的消息实例
+   */
+  error: (content: string, duration: number = 3000): MessageInstance => {
+    return showMessage(content, 'error', duration);
+  },
+
+  /**
+   * 显示警告消息
+   * @param content 消息内容
+   * @param duration 显示时长(毫秒),默认3000
+   * @returns 包含close方法的消息实例
+   */
+  warning: (content: string, duration: number = 3000): MessageInstance => {
+    return showMessage(content, 'warning', duration);
+  },
+
+  /**
+   * 显示信息消息
+   * @param content 消息内容
+   * @param duration 显示时长(毫秒),默认3000
+   * @returns 包含close方法的消息实例
+   */
+  info: (content: string, duration: number = 3000): MessageInstance => {
+    return showMessage(content, 'info', duration);
+  },
+
+  /**
+   * 显示自定义消息
+   * @param config 消息配置
+   * @returns 包含close方法的消息实例
+   */
+  open: (config: MessageConfig): MessageInstance => {
+    const { content, type = 'default', duration = 3000 } = config;
+    return showMessage(content, type, duration);
+  },
+
+  /**
+   * 关闭所有消息
+   */
+  destroy: (): void => {
+    if (container) {
+      container.innerHTML = '';
+    }
+  }
+};
+
+export default message;

+ 1 - 0
app/globals.css

@@ -1 +1,2 @@
 @import "tailwindcss";
+@import "tw-animate-css";