zyh 9 mesiacov pred
commit
6c6bcb9857

+ 471 - 0
app.tsx

@@ -0,0 +1,471 @@
+/** @jsxImportSource https://esm.d8d.fun/hono@4.7.4/jsx */
+import { Hono } from 'hono'
+import { Auth } from '@d8d-appcontainer/auth'
+import type { User as AuthUser } from '@d8d-appcontainer/auth'
+import React from 'hono/jsx'
+import type { FC } from 'hono/jsx'
+import { cors } from 'hono/cors'
+import type { Context as HonoContext } from 'hono'
+import { serveStatic } from 'hono/deno'
+import { APIClient } from '@d8d-appcontainer/api'
+import debug from "debug"
+import dayjs from 'dayjs';
+import utc from 'dayjs/plugin/utc';
+import type { SystemSettingRecord, GlobalConfig } from './asset/share/types.ts';
+import { SystemSettingKey, OssType, MapMode } from './asset/share/types.ts';
+
+import {
+  createKnowInfoRoutes,
+  createFileCategoryRoutes,
+  createFileUploadRoutes,
+  createThemeRoutes,
+  createSystemSettingsRoutes,
+} from "./routes_sys.ts";
+
+import {
+  createMapRoutes,
+} from "./routes_maps.ts";
+
+import {
+  createChartRoutes,
+} from "./routes_charts.ts";
+
+import { migrations } from './migrations.ts';
+// 导入基础路由
+import { createAuthRoutes } from "./routes_auth.ts";
+import { createUserRoutes } from "./routes_users.ts";
+
+dayjs.extend(utc)
+// 初始化debug实例
+const log = {
+  app: debug('app:server'),
+  auth: debug('auth:server'),
+  api: debug('api:server'),
+  debug: debug('debug:server')
+}
+
+const GLOBAL_CONFIG: GlobalConfig = {
+  OSS_BASE_URL: Deno.env.get('OSS_BASE_URL') || 'https://d8d-appcontainer-user.oss-cn-beijing.aliyuncs.com',
+  OSS_TYPE: Deno.env.get('OSS_TYPE') === OssType.MINIO ? OssType.MINIO : OssType.ALIYUN,
+  API_BASE_URL: '/api',
+  APP_NAME: Deno.env.get('APP_NAME') || '应用Starter',
+  ENV: Deno.env.get('ENV') || 'development',
+  DEFAULT_THEME: 'light', // 默认主题
+  MAP_CONFIG: {
+    KEY: Deno.env.get('AMAP_KEY') || '您的地图API密钥',
+    VERSION: '2.0',
+    PLUGINS: ['AMap.ToolBar', 'AMap.Scale', 'AMap.HawkEye', 'AMap.MapType', 'AMap.Geolocation'],
+    MAP_MODE: Deno.env.get('MAP_MODE') === MapMode.OFFLINE ? MapMode.OFFLINE : MapMode.ONLINE,
+  },
+  CHART_THEME: 'default', // 图表主题
+  ENABLE_THEME_CONFIG: false, // 主题配置开关
+  THEME: null
+};
+
+log.app.enabled = true
+log.auth.enabled = true
+log.api.enabled = true
+log.debug.enabled = true
+
+// 定义自定义上下文类型
+export interface Variables {
+  auth: Auth
+  user?: AuthUser
+  apiClient: APIClient
+  moduleDir: string
+  systemSettings?: SystemSettingRecord
+}
+
+// 定义登录历史类型
+interface LoginHistory {
+  id: number
+  user_id: number
+  login_time: string
+  ip_address?: string
+  user_agent?: string
+}
+
+// 定义仪表盘数据类型
+interface DashboardData {
+  lastLogin: string
+  loginCount: number
+  fileCount: number
+  userCount: number
+  systemInfo: {
+    version: string
+    lastUpdate: string
+  }
+}
+
+interface EsmScriptConfig {
+  src: string
+  href: string
+  denoJson: string
+  refresh: boolean
+  prodPath?: string
+  prodSrc?: string
+}
+
+// Auth实例
+let authInstance: Auth | null = null
+
+// 初始化Auth实例
+const initAuth = async (apiClient: APIClient) => {
+  try {
+    if (authInstance) {
+      return authInstance
+    }
+
+    log.auth('正在初始化Auth实例')
+    
+    authInstance = new Auth(apiClient as any, {
+      jwtSecret: Deno.env.get("JWT_SECRET") || 'your-jwt-secret-key',
+      initialUsers: [],
+      storagePrefix: '',
+      userTable: 'users',
+      fieldNames: {
+        id: 'id',
+        username: 'username',
+        password: 'password',
+        phone: 'phone',
+        email: 'email',
+        is_disabled: 'is_disabled',
+        is_deleted: 'is_deleted'
+      },
+      tokenExpiry: 24 * 60 * 60,
+      refreshTokenExpiry: 7 * 24 * 60 * 60
+    })
+    
+    log.auth('Auth实例初始化完成')
+    
+    return authInstance
+  } catch (error) {
+    log.auth('Auth初始化失败:', error)
+    throw error
+  }
+}
+
+// 初始化系统设置
+const initSystemSettings = async (apiClient: APIClient) => {
+  try {
+    const systemSettings = await apiClient.database.table('system_settings')
+      .select()
+    
+    // 将系统设置转换为键值对形式
+    const settings = systemSettings.reduce((acc: Record<string, any>, setting: any) => {
+      acc[setting.key] = setting.value
+      return acc
+    }, {}) as SystemSettingRecord
+    
+    // 更新全局配置
+    if (settings[SystemSettingKey.SITE_NAME]) {
+      GLOBAL_CONFIG.APP_NAME = String(settings[SystemSettingKey.SITE_NAME])
+    }
+    
+    // 设置其他全局配置项
+    if (settings[SystemSettingKey.SITE_FAVICON]) {
+      GLOBAL_CONFIG.DEFAULT_THEME = String(settings[SystemSettingKey.SITE_FAVICON])
+    }
+    
+    if (settings[SystemSettingKey.SITE_LOGO]) {
+      GLOBAL_CONFIG.MAP_CONFIG.KEY = String(settings[SystemSettingKey.SITE_LOGO])
+    }
+    
+    if (settings[SystemSettingKey.SITE_DESCRIPTION]) {
+      GLOBAL_CONFIG.CHART_THEME = String(settings[SystemSettingKey.SITE_DESCRIPTION])
+    }
+
+    // 设置主题配置开关
+    if (settings[SystemSettingKey.ENABLE_THEME_CONFIG]) {
+      GLOBAL_CONFIG.ENABLE_THEME_CONFIG = settings[SystemSettingKey.ENABLE_THEME_CONFIG] === 'true'
+    }
+
+    // 查询ID1管理员的主题配置
+    const adminTheme = await apiClient.database.table('theme_settings')
+      .where('user_id', 1)
+      .first()
+    
+    if (adminTheme) {
+      GLOBAL_CONFIG.THEME = adminTheme.settings
+    }
+    
+    return settings
+    
+  } catch (error) {
+    log.app('获取系统设置失败:', error)
+    return {} as SystemSettingRecord
+  }
+}
+
+// 初始化数据库
+const initDatabase = async (apiClient: APIClient) => {
+  try {
+    log.app('正在执行数据库迁移...')
+    
+    const migrationsResult = await apiClient.database.executeLiveMigrations(migrations)
+    // log.app('数据库迁移完成 %O',migrationsResult)
+    log.app('数据库迁移完成')
+    
+  } catch (error) {
+    log.app('数据库迁移失败:', error)
+  }
+}
+
+// 中间件:数据库初始化
+const withDatabase = async (c: HonoContext<{ Variables: Variables }>, next: () => Promise<void>) => {
+  try {
+    const apiClient = c.get('apiClient')
+    await initDatabase(apiClient)
+    await next()
+  } catch (error) {
+    log.api('数据库操作失败:', error)
+    return c.json({ error: '数据库操作失败' }, 500)
+  }
+}
+
+// 中间件:验证认证
+const withAuth = async (c: HonoContext<{ Variables: Variables }>, next: () => Promise<void>) => {
+  try {
+    const auth = c.get('auth')
+    
+    const token = c.req.header('Authorization')?.replace('Bearer ', '')
+    if (token) {
+      const userData = await auth.verifyToken(token)
+      if (userData) {
+        c.set('user', userData)
+        await next()
+        return
+      }
+    }
+    
+    return c.json({ error: '未授权' }, 401)
+  } catch (error) {
+    log.auth('认证失败:', error)
+    return c.json({ error: '无效凭证' }, 401)
+  }
+}
+
+// 导出withAuth类型定义
+export type WithAuth = typeof withAuth;
+
+// 定义模块参数接口
+interface ModuleParams {
+  apiClient: APIClient
+  app: Hono<{ Variables: Variables }>
+  moduleDir: string
+}
+
+export default function({ apiClient, app, moduleDir }: ModuleParams) {
+  const honoApp = app
+  // 添加CORS中间件
+  honoApp.use('/*', cors())
+  
+  // 创建API路由
+  const api = new Hono<{ Variables: Variables }>()
+
+  // 设置环境变量
+  api.use('*', async (c, next) => {
+    c.set('apiClient', apiClient)
+    c.set('moduleDir', moduleDir)
+    c.set('auth', await initAuth(apiClient))
+    c.set('systemSettings', await initSystemSettings(apiClient))
+    await next()
+  })
+
+  // 使用数据库中间件
+  api.use('/', withDatabase)
+
+  // 查询仪表盘数据
+  api.get('/dashboard', withAuth, async (c) => {
+    try {
+      const user = c.get('user')!
+      const apiClient = c.get('apiClient')
+      const lastLogin = await apiClient.database.table('login_history')
+        .where('user_id', user.id)
+        .orderBy('login_time', 'desc')
+        .limit(1)
+        .first()
+      
+      // 获取登录总次数
+      const loginCount = await apiClient.database.table('login_history')
+        .where('user_id', user.id)
+        .count()
+      
+      // 获取系统数据统计
+      const fileCount = await apiClient.database.table('file_library')
+        .where('is_deleted', 0)
+        .count()
+        
+      const userCount = await apiClient.database.table('users')
+        .where('is_deleted', 0)
+        .count()
+        
+      // 返回仪表盘数据
+      const dashboardData: DashboardData = {
+        lastLogin: lastLogin ? lastLogin.login_time : new Date().toISOString(),
+        loginCount: loginCount,
+        fileCount: Number(fileCount),
+        userCount: Number(userCount),
+        systemInfo: {
+          version: '1.0.0',
+          lastUpdate: new Date().toISOString()
+        }
+      }
+      
+      return c.json(dashboardData)
+    } catch (error) {
+      log.api('获取仪表盘数据失败:', error)
+      return c.json({ error: '获取仪表盘数据失败' }, 500)
+    }
+  })
+  // 注册基础路由
+  api.route('/auth', createAuthRoutes(withAuth))
+  api.route('/users', createUserRoutes(withAuth))
+  api.route('/know-info', createKnowInfoRoutes(withAuth))
+  api.route('/upload', createFileUploadRoutes(withAuth)) // 添加文件上传路由
+  api.route('/file-categories', createFileCategoryRoutes(withAuth)) // 添加文件分类管理路由
+  api.route('/theme', createThemeRoutes(withAuth)) // 添加主题设置路由
+  api.route('/charts', createChartRoutes(withAuth)) // 添加图表数据路由
+  api.route('/map', createMapRoutes(withAuth)) // 添加地图数据路由
+  api.route('/settings', createSystemSettingsRoutes(withAuth)) // 添加系统设置路由
+
+  // 注册API路由
+  honoApp.route('/api', api)
+ 
+  // 首页路由 - SSR
+  honoApp.get('/', async (c: HonoContext) => {
+    const systemName = GLOBAL_CONFIG.APP_NAME
+    return c.html(
+      <html>
+        <head>
+          <title>{systemName}</title>
+          <meta charset="UTF-8" />
+          <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+          <script src="https://cdn.tailwindcss.com"></script>
+        </head>
+        <body>
+          <div className="min-h-screen bg-gray-50 flex items-center justify-center py-12 px-4 sm:px-6 lg:px-8">
+            <div className="max-w-md w-full space-y-8">
+              {/* 系统介绍区域 */}
+              <div className="text-center">
+                <h1 className="text-4xl font-bold text-gray-900 mb-4">
+                  {systemName}
+                </h1>
+                <p className="text-lg text-gray-600 mb-8">
+                  全功能应用Starter
+                </p>
+                <p className="text-base text-gray-500 mb-8">
+                  这是一个基于Hono和React的应用Starter,提供了用户认证、文件管理、图表分析、地图集成和主题切换等常用功能。
+                </p>
+              </div>
+
+              {/* 管理入口按钮 */}
+              <div>
+                <a
+                  href="/admin"
+                  className="w-full flex justify-center py-3 px-4 border border-transparent rounded-md shadow-sm text-lg font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
+                >
+                  进入管理后台
+                </a>
+              </div>
+            </div>
+          </div>
+         </body>
+      </html>
+    )
+  })
+  
+  // 创建一个函数,用于生成包含全局配置的HTML页面
+  const createHtmlWithConfig = (scriptConfig: EsmScriptConfig, title = '应用Starter') => {
+    return (c: HonoContext) => {
+      const isProd = GLOBAL_CONFIG.ENV === 'production';
+      
+      return c.html(
+        <html lang="zh-CN">
+          <head>
+            <meta charset="UTF-8" />
+            <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+            <title>{title}</title>
+            
+            {isProd ? (
+              <script type="module" src={scriptConfig.prodSrc || `/asset_dist/${scriptConfig.prodPath}`}></script>
+            ) : (
+              <script src={scriptConfig.src} href={scriptConfig.href} deno-json={scriptConfig.denoJson} refresh={scriptConfig.refresh}></script>
+            )}
+            
+            {isProd ? (<script src="/tailwindcss@3.4.16/index.js"></script>) : (<script src="https://cdn.tailwindcss.com"></script>)}
+
+            <script dangerouslySetInnerHTML={{ __html: `window.CONFIG = ${JSON.stringify(GLOBAL_CONFIG)};` }} />
+            
+            {!isProd && (
+              <>
+                <script src="https://ai-oss.d8d.fun/umd/vconsole.3.15.1.min.js"></script>
+                <script dangerouslySetInnerHTML={{ __html: `
+                  const urlParams = new URLSearchParams(window.location.search);
+                  if (urlParams.has('vconsole')) {
+                    var vConsole = new VConsole({
+                      theme: urlParams.get('vconsole_theme') || 'light',
+                      onReady: function() {
+                        console.log('vConsole is ready');
+                      }
+                    });
+                  }
+                `}} />
+              </>
+            )}
+          </head>
+          <body className="bg-gray-50">
+            <div id="root"></div>
+          </body>
+        </html>
+      )
+    }
+  }
+
+  // 后台管理路由
+  honoApp.get('/admin', createHtmlWithConfig({
+    src: "https://esm.d8d.fun/xb", 
+    href: "/asset/admin/web_app.tsx", 
+    denoJson: "/asset/admin/deno.json", 
+    refresh: true, 
+    prodPath: "admin/web_app.js"
+  }, GLOBAL_CONFIG.APP_NAME))
+  
+  honoApp.get('/admin/*', createHtmlWithConfig({
+    src: "https://esm.d8d.fun/xb", 
+    href: "/asset/admin/web_app.tsx", 
+    denoJson: "/asset/admin/deno.json", 
+    refresh: true, 
+    prodPath: "admin/web_app.js"
+  }, GLOBAL_CONFIG.APP_NAME))
+
+  const staticRoutes = serveStatic({ 
+    root: moduleDir,
+    onFound: async (path: string, c: HonoContext) => {
+      const fileExt = path.split('.').pop()?.toLowerCase()
+      if (fileExt === 'tsx' || fileExt === 'ts') {
+        c.header('Content-Type', 'text/typescript; charset=utf-8')
+      } else if (fileExt === 'js' || fileExt === 'mjs') {
+        c.header('Content-Type', 'application/javascript; charset=utf-8')
+      } else if (fileExt === 'json') {
+        c.header('Content-Type', 'application/json; charset=utf-8')
+      } else if (fileExt === 'html') {
+        c.header('Content-Type', 'text/html; charset=utf-8')
+      } else if (fileExt === 'css') {
+        c.header('Content-Type', 'text/css; charset=utf-8')
+      } else if (['jpg', 'jpeg', 'png', 'gif', 'webp'].includes(fileExt || '')) {
+        c.header('Content-Type', `image/${fileExt}`)
+      }
+      const fileInfo = await Deno.stat(path)
+      c.header('Last-Modified', fileInfo.mtime?.toUTCString() ?? new Date().toUTCString())
+    },
+  })
+  
+  // 静态资源路由
+  honoApp.get('/asset/*', staticRoutes)
+  honoApp.get('/amap/*', staticRoutes)
+  honoApp.get('/tailwindcss@3.4.16/*', staticRoutes)
+  honoApp.get('/asset_dist/*', staticRoutes)
+  
+  return honoApp
+}

+ 1743 - 0
asset/admin/api.ts

@@ -0,0 +1,1743 @@
+import axios from 'axios';
+import type { MinioUploadPolicy, OSSUploadPolicy } from '@d8d-appcontainer/types';
+import 'dayjs/locale/zh-cn';
+import type { 
+  User, ZichanInfo, ZichanTransLog, DeviceInstance, DeviceType,
+  AlertHandleLog, AlertNotifyConfig, DeviceAlert, DeviceAlertRule,
+  DeviceMonitorData, FileLibrary, FileCategory, ZichanCategory, ZichanArea, RackServerType,
+  RackInfo, RackServer, KnowInfo, WorkOrder, WorkOrderTemplate, WorkOrderLog,
+  AuthContextType, ThemeContextType, Attachment, ThemeSettings,
+  CategoryChartData, OnlineRateChartData, StateChartData, StateChartDataWithPercent, 
+  AlarmChartData, CategoryChartDataWithPercent, MapViewDevice, DeviceMapFilter, DeviceMapStats,
+  DeviceMapDataResponse, DeviceMapStatsResponse, DeviceTreeNode, DeviceTreeStats,
+  LoginLocation, LoginLocationDetail, SystemSetting, SystemSettingGroupData
+} from '../share/types.ts';
+
+import {
+  DeviceCategory, DeviceStatus, AssetTransferType, 
+} from '../share/types.ts';
+
+
+// 定义API基础URL
+const API_BASE_URL = '/api';
+
+// 获取OSS完整URL
+export const getOssUrl = (path: string): string => {
+  // 获取全局配置中的OSS_HOST,如果不存在使用默认值
+  const ossHost = (window.CONFIG?.OSS_BASE_URL) || '';
+  // 确保path不以/开头
+  const ossPath = path.startsWith('/') ? path.substring(1) : path;
+  return `${ossHost}/${ossPath}`;
+};
+
+// ===================
+// Auth API 定义部分
+// ===================
+
+// 定义API返回数据类型
+interface AuthLoginResponse {
+  message: string;
+  token: string;
+  refreshToken?: string;
+  user: User;
+}
+
+interface AuthResponse {
+  message: string;
+  [key: string]: any;
+}
+
+// 定义Auth API接口类型
+interface AuthAPIType {
+  login: (username: string, password: string) => Promise<AuthLoginResponse>;
+  register: (username: string, email: string, password: string) => Promise<AuthResponse>;
+  logout: () => Promise<AuthResponse>;
+  getCurrentUser: () => Promise<User>;
+  updateUser: (userId: number, userData: Partial<User>) => Promise<User>;
+  changePassword: (oldPassword: string, newPassword: string) => Promise<AuthResponse>;
+  requestPasswordReset: (email: string) => Promise<AuthResponse>;
+  resetPassword: (token: string, newPassword: string) => Promise<AuthResponse>;
+}
+
+
+// Auth相关API
+export const AuthAPI: AuthAPIType = {
+  // 登录API
+  login: async (username: string, password: string) => {
+    try {
+      const response = await axios.post(`${API_BASE_URL}/auth/login`, { username, password });
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+  
+  // 注册API
+  register: async (username: string, email: string, password: string) => {
+    try {
+      const response = await axios.post(`${API_BASE_URL}/auth/register`, { username, email, password });
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+  
+  // 登出API
+  logout: async () => {
+    try {
+      const response = await axios.post(`${API_BASE_URL}/auth/logout`);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+  
+  // 获取当前用户信息
+  getCurrentUser: async () => {
+    try {
+      const response = await axios.get(`${API_BASE_URL}/auth/me`);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+  
+  // 更新用户信息
+  updateUser: async (userId: number, userData: Partial<User>) => {
+    try {
+      const response = await axios.put(`${API_BASE_URL}/auth/users/${userId}`, userData);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+  
+  // 修改密码
+  changePassword: async (oldPassword: string, newPassword: string) => {
+    try {
+      const response = await axios.post(`${API_BASE_URL}/auth/change-password`, { oldPassword, newPassword });
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+  
+  // 请求重置密码
+  requestPasswordReset: async (email: string) => {
+    try {
+      const response = await axios.post(`${API_BASE_URL}/auth/request-password-reset`, { email });
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+  
+  // 重置密码
+  resetPassword: async (token: string, newPassword: string) => {
+    try {
+      const response = await axios.post(`${API_BASE_URL}/auth/reset-password`, { token, newPassword });
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  }
+};
+
+// 为UserAPI添加的接口响应类型
+interface UsersResponse {
+  data: User[];
+  pagination: {
+    total: number;
+    current: number;
+    pageSize: number;
+    totalPages: number;
+  };
+}
+
+interface UserResponse {
+  data: User;
+  message?: string;
+}
+
+interface UserCreateResponse {
+  message: string;
+  data: User;
+}
+
+interface UserUpdateResponse {
+  message: string;
+  data: User;
+}
+
+interface UserDeleteResponse {
+  message: string;
+  id: number;
+}
+
+// 用户管理API
+export const UserAPI = {
+  // 获取用户列表
+  getUsers: async (params?: { page?: number, limit?: number, search?: string }): Promise<UsersResponse> => {
+    try {
+      const response = await axios.get(`${API_BASE_URL}/users`, { params });
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+  
+  // 获取单个用户详情
+  getUser: async (userId: number): Promise<UserResponse> => {
+    try {
+      const response = await axios.get(`${API_BASE_URL}/users/${userId}`);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+  
+  // 创建用户
+  createUser: async (userData: Partial<User>): Promise<UserCreateResponse> => {
+    try {
+      const response = await axios.post(`${API_BASE_URL}/users`, userData);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+  
+  // 更新用户信息
+  updateUser: async (userId: number, userData: Partial<User>): Promise<UserUpdateResponse> => {
+    try {
+      const response = await axios.put(`${API_BASE_URL}/users/${userId}`, userData);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+  
+  // 删除用户
+  deleteUser: async (userId: number): Promise<UserDeleteResponse> => {
+    try {
+      const response = await axios.delete(`${API_BASE_URL}/users/${userId}`);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  }
+};
+
+// 资产管理API
+export const ZichanAPI = {
+  // 获取资产列表
+  getZichanList: async (params?: { 
+    page?: number, 
+    limit?: number, 
+    asset_name?: string, 
+    device_category?: DeviceCategory, 
+    device_status?: DeviceStatus 
+  }) => {
+    try {
+      const response = await axios.get(`${API_BASE_URL}/zichan`, { params });
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+  
+  // 获取单个资产
+  getZichan: async (id: number) => {
+    try {
+      const response = await axios.get(`${API_BASE_URL}/zichan/${id}`);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+  
+  // 创建资产
+  createZichan: async (data: Partial<ZichanInfo>) => {
+    try {
+      const response = await axios.post(`${API_BASE_URL}/zichan`, data);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+  
+  // 更新资产
+  updateZichan: async (id: number, data: Partial<ZichanInfo>) => {
+    try {
+      const response = await axios.put(`${API_BASE_URL}/zichan/${id}`, data);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+  
+  // 删除资产
+  deleteZichan: async (id: number) => {
+    try {
+      const response = await axios.delete(`${API_BASE_URL}/zichan/${id}`);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  }
+};
+
+
+
+// 资产流转API接口定义
+export const ZichanTransferAPI = {
+  // 获取资产流转记录列表
+  getTransferList: async (params?: { page?: number, limit?: number, asset_id?: number, asset_transfer?: AssetTransferType }) => {
+    try {
+      const response = await axios.get(`${API_BASE_URL}/zichan-transfer`, { params });
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+  
+  // 获取资产流转记录详情
+  getTransfer: async (id: number) => {
+    try {
+      const response = await axios.get(`${API_BASE_URL}/zichan-transfer/${id}`);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+  
+  // 创建资产流转记录
+  createTransfer: async (data: Partial<ZichanTransLog>) => {
+    try {
+      const response = await axios.post(`${API_BASE_URL}/zichan-transfer`, data);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+  
+  // 更新资产流转记录
+  updateTransfer: async (id: number, data: Partial<ZichanTransLog>) => {
+    try {
+      const response = await axios.put(`${API_BASE_URL}/zichan-transfer/${id}`, data);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+  
+  // 删除资产流转记录
+  deleteTransfer: async (id: number) => {
+    try {
+      const response = await axios.delete(`${API_BASE_URL}/zichan-transfer/${id}`);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  }
+};
+
+// 设备实例API接口定义
+interface DeviceInstancesResponse {
+  data: DeviceInstance[];
+  pagination: {
+    total: number;
+    current: number;
+    pageSize: number;
+    totalPages: number;
+  };
+}
+
+interface DeviceInstanceResponse {
+  data: DeviceInstance;
+  asset_info?: ZichanInfo;
+  type_info?: DeviceType;
+  message?: string;
+}
+
+interface DeviceInstanceCreateResponse {
+  message: string;
+  data: DeviceInstance;
+}
+
+interface DeviceInstanceUpdateResponse {
+  message: string;
+  data: DeviceInstance;
+}
+
+interface DeviceInstanceDeleteResponse {
+  message: string;
+  id: number;
+}
+
+// 设备类型API接口定义
+interface DeviceTypeResponse {
+  data: DeviceType[];
+  pagination: {
+    total: number;
+    current: number;
+    pageSize: number;
+    totalPages: number;
+  };
+}
+
+interface DeviceTypeDetailResponse {
+  data: DeviceType;
+  message?: string;
+}
+
+interface DeviceTypeCreateResponse {
+  message: string;
+  data: DeviceType;
+}
+
+interface DeviceTypeUpdateResponse {
+  message: string;
+  data: DeviceType;
+}
+
+interface DeviceTypeDeleteResponse {
+  message: string;
+  id: number;
+}
+
+export const DeviceTypeAPI = {
+  // 获取设备类型列表
+  getDeviceTypes: async (params?: { 
+    page?: number, 
+    pageSize?: number, 
+    code?: string,
+    name?: string,
+    is_enabled?: boolean
+  }): Promise<DeviceTypeResponse> => {
+    try {
+      const response = await axios.get('/api/device/types', { params });
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+  
+  // 获取单个设备类型信息
+  getDeviceType: async (id: number): Promise<DeviceTypeDetailResponse> => {
+    try {
+      const response = await axios.get(`/api/device/types/${id}`);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+  
+  // 创建设备类型
+  createDeviceType: async (data: Partial<DeviceType>): Promise<DeviceTypeCreateResponse> => {
+    try {
+      const response = await axios.post('/api/device/types', data);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+  
+  // 更新设备类型
+  updateDeviceType: async (id: number, data: Partial<DeviceType>): Promise<DeviceTypeUpdateResponse> => {
+    try {
+      const response = await axios.put(`/api/device/types/${id}`, data);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+  
+  // 删除设备类型
+  deleteDeviceType: async (id: number): Promise<DeviceTypeDeleteResponse> => {
+    try {
+      const response = await axios.delete(`/api/device/types/${id}`);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+  
+  // 获取设备类型图标
+  getTypeIcons: async (): Promise<{data: Record<string, string>, success: boolean}> => {
+    try {
+      const response = await axios.get('/api/device/types/icons');
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  }
+};
+
+export const DeviceInstanceAPI = {
+  // 获取设备实例列表
+  getDeviceInstances: async (params?: { 
+    page?: number, 
+    limit?: number, 
+    type_id?: number,
+    protocol?: string,
+    status?: number
+  }): Promise<DeviceInstancesResponse> => {
+    try {
+      const response = await axios.get('/api/device/instances', { params });
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+  
+  // 获取单个设备实例信息
+  getDeviceInstance: async (id: number): Promise<DeviceInstanceResponse> => {
+    try {
+      const response = await axios.get(`/api/device/instances/${id}`);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+  
+  // 创建设备实例
+  createDeviceInstance: async (data: Partial<DeviceInstance>): Promise<DeviceInstanceCreateResponse> => {
+    try {
+      const response = await axios.post('/api/device/instances', data);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+  
+  // 更新设备实例
+  updateDeviceInstance: async (id: number, data: Partial<DeviceInstance>): Promise<DeviceInstanceUpdateResponse> => {
+    try {
+      const response = await axios.put(`/api/device/instances/${id}`, data);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+  
+  // 删除设备实例
+  deleteDeviceInstance: async (id: number): Promise<DeviceInstanceDeleteResponse> => {
+    try {
+      const response = await axios.delete(`/api/device/instances/${id}`);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  }
+};
+
+// 机柜管理API接口定义
+export const RackAPI = {
+  // 获取机柜列表
+  getRackList: async (params?: { 
+    page?: number, 
+    limit?: number, 
+    rack_name?: string, 
+    rack_code?: string, 
+    area?: string
+  }) => {
+    try {
+      const response = await axios.get(`${API_BASE_URL}/racks`, { params });
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+  
+  // 获取单个机柜信息
+  getRack: async (id: number) => {
+    try {
+      const response = await axios.get(`${API_BASE_URL}/racks/${id}`);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+  
+  // 创建机柜
+  createRack: async (data: Partial<RackInfo>) => {
+    try {
+      const response = await axios.post(`${API_BASE_URL}/racks`, data);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+  
+  // 更新机柜
+  updateRack: async (id: number, data: Partial<RackInfo>) => {
+    try {
+      const response = await axios.put(`${API_BASE_URL}/racks/${id}`, data);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+  
+  // 删除机柜
+  deleteRack: async (id: number) => {
+    try {
+      const response = await axios.delete(`${API_BASE_URL}/racks/${id}`);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  }
+};
+
+// 机柜服务器API接口定义
+export const RackServerAPI = {
+  // 获取机柜服务器列表
+  getRackServerList: async (params?: { 
+    page?: number, 
+    limit?: number, 
+    rack_id?: number,
+    asset_id?: number,
+    server_type?: string
+  }) => {
+    try {
+      const response = await axios.get(`${API_BASE_URL}/rack-servers`, { params });
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+  
+  // 获取单个机柜服务器信息
+  getRackServer: async (id: number) => {
+    try {
+      const response = await axios.get(`${API_BASE_URL}/rack-servers/${id}`);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+  
+  // 创建机柜服务器
+  createRackServer: async (data: Partial<RackServer>) => {
+    try {
+      const response = await axios.post(`${API_BASE_URL}/rack-servers`, data);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+  
+  // 更新机柜服务器
+  updateRackServer: async (id: number, data: Partial<RackServer>) => {
+    try {
+      const response = await axios.put(`${API_BASE_URL}/rack-servers/${id}`, data);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+  
+  // 删除机柜服务器
+  deleteRackServer: async (id: number) => {
+    try {
+      const response = await axios.delete(`${API_BASE_URL}/rack-servers/${id}`);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  }
+};
+
+interface DeviceMonitorDataResponse {
+  data: DeviceMonitorData[];
+  total: number;
+  page: number;
+  pageSize: number;
+}
+
+interface DeviceMonitorResponse {
+  data: DeviceMonitorData;
+  message?: string;
+}
+
+interface MonitorCreateResponse {
+  data: DeviceMonitorData;
+  message: string;
+}
+
+interface MonitorUpdateResponse {
+  data: DeviceMonitorData;
+  message: string;
+}
+
+interface MonitorDeleteResponse {
+  message: string;
+}
+
+// 告警相关响应类型
+interface DeviceAlertDataResponse {
+  data: DeviceAlert[];
+  total: number;
+  page: number;
+  pageSize: number;
+}
+
+interface DeviceAlertResponse {
+  data: DeviceAlert;
+  message?: string;
+}
+
+interface AlertCreateResponse {
+  data: DeviceAlert;
+  message: string;
+}
+
+interface AlertUpdateResponse {
+  data: DeviceAlert;
+  message: string;
+}
+
+interface AlertDeleteResponse {
+  message: string;
+}
+
+// 告警处理相关响应类型
+interface AlertHandleDataResponse {
+  data: AlertHandleLog[];
+  total: number;
+  page: number;
+  pageSize: number;
+}
+
+interface AlertHandleResponse {
+  data: AlertHandleLog;
+  message?: string;
+}
+
+// 告警通知配置相关响应类型
+interface AlertNotifyConfigDataResponse {
+  data: AlertNotifyConfig[];
+  total: number;
+  page: number;
+  pageSize: number;
+}
+
+interface AlertNotifyConfigResponse {
+  data: AlertNotifyConfig;
+  message?: string;
+}
+
+// 监控API接口定义
+export const MonitorAPI = {
+  // 获取监控数据
+  getMonitorData: async (params?: { 
+    page?: number, 
+    limit?: number, 
+    device_id?: number, 
+    device_type?: string, 
+    start_time?: string, 
+    end_time?: string
+  }): Promise<DeviceMonitorDataResponse> => {
+    try {
+      const response = await axios.get(`${API_BASE_URL}/monitor/data`, { params });
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+  
+  // 获取设备树数据
+  getDeviceTree: async (params?: {
+    status?: string,
+    keyword?: string
+  }): Promise<{ data: DeviceTreeNode[] }> => {
+    try {
+      const response = await axios.get(`${API_BASE_URL}/monitor/devices/tree`, { params });
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+  
+  // 获取设备树统计数据
+  getDeviceTreeStats: async (): Promise<{ data: DeviceTreeStats }> => {
+    try {
+      const response = await axios.get(`${API_BASE_URL}/monitor/devices/tree/statistics`);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+  
+  // 获取设备地图数据
+  getDeviceMapData: async (params?: { 
+    type_code?: string, 
+    device_status?: DeviceStatus, 
+    keyword?: string,
+    device_id?: number
+  }): Promise<{ data: MapViewDevice[], stats: DeviceMapStats }> => {
+    try {
+      const response = await axios.get(`${API_BASE_URL}/monitor/devices/map`, { params });
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+  
+  // 获取单个监控数据
+  getMonitor: async (id: number): Promise<DeviceMonitorResponse> => {
+    try {
+      const response = await axios.get(`${API_BASE_URL}/monitor/data/${id}`);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+  
+  // 创建监控数据
+  createMonitor: async (data: Partial<DeviceMonitorData>): Promise<MonitorCreateResponse> => {
+    try {
+      const response = await axios.post(`${API_BASE_URL}/monitor/data`, data);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+  
+  // 更新监控数据
+  updateMonitor: async (id: number, data: Partial<DeviceMonitorData>): Promise<MonitorUpdateResponse> => {
+    try {
+      const response = await axios.put(`${API_BASE_URL}/monitor/data/${id}`, data);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+  
+  // 删除监控数据
+  deleteMonitor: async (id: number): Promise<MonitorDeleteResponse> => {
+    try {
+      const response = await axios.delete(`${API_BASE_URL}/monitor/data/${id}`);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  }
+};
+
+// 告警API接口定义
+export const AlertAPI = {
+  // 获取告警数据
+  getAlertData: async (params?: { 
+    page?: number, 
+    limit?: number, 
+    alert_type?: string, 
+    alert_level?: string, 
+    start_time?: string, 
+    end_time?: string
+  }): Promise<DeviceAlertDataResponse> => {
+    try {
+      const response = await axios.get(`${API_BASE_URL}/alerts`, { params });
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+  
+  // 获取单个告警数据
+  getAlert: async (id: number): Promise<DeviceAlert> => {
+    try {
+      const response = await axios.get(`${API_BASE_URL}/alerts/${id}`);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+  
+  // 创建告警数据
+  createAlert: async (data: Partial<DeviceAlert>): Promise<AlertCreateResponse> => {
+    try {
+      const response = await axios.post(`${API_BASE_URL}/alerts`, data);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+  
+  // 更新告警数据
+  updateAlert: async (id: number, data: Partial<DeviceAlert>): Promise<AlertUpdateResponse> => {
+    try {
+      const response = await axios.put(`${API_BASE_URL}/alerts/${id}`, data);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+  
+  // 删除告警数据
+  deleteAlert: async (id: number): Promise<AlertDeleteResponse> => {
+    try {
+      const response = await axios.delete(`${API_BASE_URL}/alerts/${id}`);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  }
+};
+
+// 告警处理API接口定义
+export const AlertHandleAPI = {
+  // 获取告警处理数据
+  getAlertHandleData: async (params?: { 
+    page?: number, 
+    limit?: number, 
+    alert_id?: number, 
+    handle_type?: string, 
+    start_time?: string, 
+    end_time?: string
+  }): Promise<AlertHandleDataResponse> => {
+    try {
+      const response = await axios.get(`${API_BASE_URL}/alert-handles`, { params });
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+  
+  // 获取单个告警处理数据
+  getAlertHandle: async (id: number): Promise<AlertHandleResponse> => {
+    try {
+      const response = await axios.get(`${API_BASE_URL}/alert-handles/${id}`);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+  
+  // 创建告警处理数据
+  createAlertHandle: async (data: Partial<AlertHandleLog>) => {
+    try {
+      const response = await axios.post(`${API_BASE_URL}/alert-handles`, data);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+  
+  // 更新告警处理数据
+  updateAlertHandle: async (id: number, data: Partial<AlertHandleLog>) => {
+    try {
+      const response = await axios.put(`${API_BASE_URL}/alert-handles/${id}`, data);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+  
+  // 删除告警处理数据
+  deleteAlertHandle: async (id: number) => {
+    try {
+      const response = await axios.delete(`${API_BASE_URL}/alert-handles/${id}`);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  }
+};
+
+// 告警通知配置API接口定义
+export const AlertNotifyConfigAPI = {
+  // 获取告警通知配置
+  getAlertNotifyConfig: async (params?: { 
+    page?: number, 
+    limit?: number, 
+    device_id?: number, 
+    alert_level?: string 
+  }): Promise<AlertNotifyConfigDataResponse> => {
+    try {
+      const response = await axios.get(`${API_BASE_URL}/alert-notify-configs`, { params });
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+  
+  // 创建告警通知配置
+  createAlertNotifyConfig: async (data: Partial<AlertNotifyConfig>): Promise<AlertNotifyConfigResponse> => {
+    try {
+      const response = await axios.post(`${API_BASE_URL}/alert-notify-configs`, data);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+  
+  // 更新告警通知配置
+  updateAlertNotifyConfig: async (id: number, data: Partial<AlertNotifyConfig>): Promise<AlertNotifyConfigResponse> => {
+    try {
+      const response = await axios.put(`${API_BASE_URL}/alert-notify-configs/${id}`, data);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+  
+  // 删除告警通知配置
+  deleteAlertNotifyConfig: async (id: number): Promise<AlertNotifyConfigResponse> => {
+    try {
+      const response = await axios.delete(`${API_BASE_URL}/alert-notify-configs/${id}`);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  }
+};
+
+// 设备告警规则API接口定义
+export const DeviceAlertRuleAPI = {
+  // 获取设备告警规则
+  getDeviceAlertRules: async (params?: { 
+    page?: number, 
+    limit?: number, 
+    device_id?: number, 
+    rule_type?: string
+  }): Promise<DeviceAlertDataResponse> => {
+    try {
+      const response = await axios.get(`${API_BASE_URL}/device-alert-rules`, { params });
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+  
+  // 获取单个设备告警规则
+  getDeviceAlertRule: async (id: number): Promise<DeviceAlertResponse> => {
+    try {
+      const response = await axios.get(`${API_BASE_URL}/device-alert-rules/${id}`);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+  
+  // 创建设备告警规则
+  createDeviceAlertRule: async (data: Partial<DeviceAlertRule>): Promise<DeviceAlertResponse> => {
+    try {
+      const response = await axios.post(`${API_BASE_URL}/device-alert-rules`, data);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+  
+  // 更新设备告警规则
+  updateDeviceAlertRule: async (id: number, data: Partial<DeviceAlertRule>): Promise<DeviceAlertResponse> => {
+    try {
+      const response = await axios.put(`${API_BASE_URL}/device-alert-rules/${id}`, data);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+  
+  // 删除设备告警规则
+  deleteDeviceAlertRule: async (id: number): Promise<AlertDeleteResponse> => {
+    try {
+      const response = await axios.delete(`${API_BASE_URL}/device-alert-rules/${id}`);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  }
+};
+
+
+// 资产分类API响应类型
+interface ZichanCategoryResponse {
+  data: ZichanCategory[];
+  pagination: {
+    total: number;
+    current: number;
+    pageSize: number;
+  };
+}
+
+// 资产归属区域API响应类型
+interface ZichanAreaResponse {
+  data: ZichanArea[];
+  pagination: {
+    total: number;
+    current: number;
+    pageSize: number;
+  };
+}
+
+// 机柜服务器类型API响应类型
+interface RackServerTypeResponse {
+  data: RackServerType[];
+  pagination: {
+    total: number;
+    current: number;
+    pageSize: number;
+  };
+}
+
+// 资产分类API接口定义
+export const ZichanCategoryAPI = {
+  // 获取资产分类列表
+  getZichanCategoryList: async (params?: { 
+    page?: number, 
+    limit?: number, 
+    name?: string,
+    code?: string
+  }) => {
+    try {
+      const response = await axios.get(`${API_BASE_URL}/zichan-categories`, { params });
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+  
+  // 获取单个资产分类信息
+  getZichanCategory: async (id: number) => {
+    try {
+      const response = await axios.get(`${API_BASE_URL}/zichan-categories/${id}`);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+  
+  // 创建资产分类
+  createZichanCategory: async (data: Partial<ZichanCategory>) => {
+    try {
+      const response = await axios.post(`${API_BASE_URL}/zichan-categories`, data);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+  
+  // 更新资产分类
+  updateZichanCategory: async (id: number, data: Partial<ZichanCategory>) => {
+    try {
+      const response = await axios.put(`${API_BASE_URL}/zichan-categories/${id}`, data);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+  
+  // 删除资产分类
+  deleteZichanCategory: async (id: number) => {
+    try {
+      const response = await axios.delete(`${API_BASE_URL}/zichan-categories/${id}`);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  }
+};
+
+// 资产归属区域API接口定义
+export const ZichanAreaAPI = {
+  // 获取资产归属区域列表
+  getZichanAreaList: async (params?: { 
+    page?: number, 
+    limit?: number, 
+    name?: string,
+    code?: string
+  }) => {
+    try {
+      const response = await axios.get(`${API_BASE_URL}/zichan-areas`, { params });
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+  
+  // 获取单个资产归属区域信息
+  getZichanArea: async (id: number) => {
+    try {
+      const response = await axios.get(`${API_BASE_URL}/zichan-areas/${id}`);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+  
+  // 创建资产归属区域
+  createZichanArea: async (data: Partial<ZichanArea>) => {
+    try {
+      const response = await axios.post(`${API_BASE_URL}/zichan-areas`, data);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+  
+  // 更新资产归属区域
+  updateZichanArea: async (id: number, data: Partial<ZichanArea>) => {
+    try {
+      const response = await axios.put(`${API_BASE_URL}/zichan-areas/${id}`, data);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+  
+  // 删除资产归属区域
+  deleteZichanArea: async (id: number) => {
+    try {
+      const response = await axios.delete(`${API_BASE_URL}/zichan-areas/${id}`);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  }
+};
+
+// 机柜服务器类型API接口定义
+export const RackServerTypeAPI = {
+  // 获取机柜服务器类型列表
+  getRackServerTypeList: async (params?: { 
+    page?: number, 
+    limit?: number, 
+    name?: string,
+    code?: string
+  }): Promise<RackServerTypeResponse> => {
+    try {
+      const response = await axios.get(`${API_BASE_URL}/rack-server-types`, { params });
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+  
+  // 获取单个机柜服务器类型信息
+  getRackServerType: async (id: number) => {
+    try {
+      const response = await axios.get(`${API_BASE_URL}/rack-server-types/${id}`);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+  
+  // 创建机柜服务器类型
+  createRackServerType: async (data: Partial<RackServerType>) => {
+    try {
+      const response = await axios.post(`${API_BASE_URL}/rack-server-types`, data);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+  
+  // 更新机柜服务器类型
+  updateRackServerType: async (id: number, data: Partial<RackServerType>) => {
+    try {
+      const response = await axios.put(`${API_BASE_URL}/rack-server-types/${id}`, data);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+  
+  // 删除机柜服务器类型
+  deleteRackServerType: async (id: number) => {
+    try {
+      const response = await axios.delete(`${API_BASE_URL}/rack-server-types/${id}`);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  }
+};
+
+// 定义文件相关接口类型
+interface FileUploadPolicyResponse {
+  message: string;
+  data: MinioUploadPolicy | OSSUploadPolicy;
+}
+
+interface FileListResponse {
+  message: string;
+  data: {
+    list: FileLibrary[];
+    pagination: {
+      current: number;
+      pageSize: number;
+      total: number;
+    };
+  };
+}
+
+interface FileSaveResponse {
+  message: string;
+  data: FileLibrary;
+}
+
+interface FileInfoResponse {
+  message: string;
+  data: FileLibrary;
+}
+
+interface FileDeleteResponse {
+  message: string;
+}
+
+
+interface FileCategoryListResponse {
+  data: FileCategory[];
+  total: number;
+  page: number;
+  pageSize: number;
+}
+
+interface FileCategoryCreateResponse {
+  message: string;
+  data: FileCategory;
+}
+
+interface FileCategoryUpdateResponse {
+  message: string;
+  data: FileCategory;
+}
+
+interface FileCategoryDeleteResponse {
+  message: string;
+}
+
+// 文件API接口定义
+export const FileAPI = {
+  // 获取文件上传策略
+  getUploadPolicy: async (filename: string, prefix: string = 'uploads/', maxSize: number = 10 * 1024 * 1024): Promise<FileUploadPolicyResponse> => {
+    try {
+      const response = await axios.get(`${API_BASE_URL}/upload/policy`, { 
+        params: { filename, prefix, maxSize } 
+      });
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+
+  // 保存文件信息
+  saveFileInfo: async (fileData: Partial<FileLibrary>): Promise<FileSaveResponse> => {
+    try {
+      const response = await axios.post(`${API_BASE_URL}/upload/save`, fileData);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+
+  // 获取文件列表
+  getFileList: async (params?: {
+    page?: number,
+    pageSize?: number,
+    category_id?: number,
+    fileType?: string,
+    keyword?: string
+  }): Promise<FileListResponse> => {
+    try {
+      const response = await axios.get(`${API_BASE_URL}/upload/list`, { params });
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+
+  // 获取单个文件信息
+  getFileInfo: async (id: number): Promise<FileInfoResponse> => {
+    try {
+      const response = await axios.get(`${API_BASE_URL}/upload/${id}`);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+
+  // 更新文件下载计数
+  updateDownloadCount: async (id: number): Promise<FileDeleteResponse> => {
+    try {
+      const response = await axios.post(`${API_BASE_URL}/upload/${id}/download`);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+
+  // 删除文件
+  deleteFile: async (id: number): Promise<FileDeleteResponse> => {
+    try {
+      const response = await axios.delete(`${API_BASE_URL}/upload/${id}`);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+
+  // 获取文件分类列表
+  getCategories: async (params?: {
+    page?: number,
+    pageSize?: number,
+    search?: string
+  }): Promise<FileCategoryListResponse> => {
+    try {
+      const response = await axios.get(`${API_BASE_URL}/file-categories`, { params });
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+
+  // 创建文件分类
+  createCategory: async (data: Partial<FileCategory>): Promise<FileCategoryCreateResponse> => {
+    try {
+      const response = await axios.post(`${API_BASE_URL}/file-categories`, data);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+
+  // 更新文件分类
+  updateCategory: async (id: number, data: Partial<FileCategory>): Promise<FileCategoryUpdateResponse> => {
+    try {
+      const response = await axios.put(`${API_BASE_URL}/file-categories/${id}`, data);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+
+  // 删除文件分类
+  deleteCategory: async (id: number): Promise<FileCategoryDeleteResponse> => {
+    try {
+      const response = await axios.delete(`${API_BASE_URL}/file-categories/${id}`);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  }
+};
+
+// // 添加图表类型定义(从大屏移植)
+// interface CategoryChartData {
+//   设备分类: string;
+//   设备数: number;
+// }
+
+// interface CategoryChartDataWithPercent extends CategoryChartData {
+//   百分比: string;
+// }
+
+// interface OnlineRateChartData {
+//   time_interval: string;
+//   online_devices: number;
+//   total_devices: number;
+// }
+
+// interface StateChartData {
+//   资产流转: string;
+//   设备数: number;
+// }
+
+// interface StateChartDataWithPercent extends StateChartData {
+//   百分比: string;
+// }
+
+// interface AlarmChartData {
+//   time_interval: string;
+//   total_devices: number;
+// }
+
+// 统一图表数据查询函数
+export const chartQueryFns = {
+  // 资产分类数据查询
+  fetchCategoryData: async (): Promise<CategoryChartDataWithPercent[]> => {
+    const res = await axios.get<CategoryChartData[]>(`${API_BASE_URL}/big/zichan_category_chart`);
+    
+    // 预先计算百分比
+    const data = res.data;
+    const total = data.reduce((sum: number, item: CategoryChartData) => sum + item['设备数'], 0);
+    
+    // 为每个数据项添加百分比字段
+    return data.map(item => ({
+      ...item,
+      百分比: total > 0 ? (item['设备数'] / total * 100).toFixed(1) : '0'
+    }));
+  },
+
+  // 在线率变化数据查询
+  fetchOnlineRateData: async (params?: {
+    created_at_gte?: string;
+    created_at_lte?: string;
+    dimension?: string;
+  }): Promise<OnlineRateChartData[]> => {
+    // 可选参数
+    // const params = {
+    //   created_at_gte: dayjs().subtract(7, 'day').format('YYYY-MM-DD HH:mm:ss'),
+    //   created_at_lte: dayjs().format('YYYY-MM-DD HH:mm:ss'),
+    //   dimension: 'day'
+    // };
+    
+    const res = await axios.get<OnlineRateChartData[]>(`${API_BASE_URL}/big/zichan_online_rate_chart`, { params });
+    return res.data;
+  },
+
+  // 资产流转状态数据查询
+  fetchStateData: async (): Promise<StateChartDataWithPercent[]> => {
+    const res = await axios.get<StateChartData[]>(`${API_BASE_URL}/big/zichan_state_chart`);
+    
+    // 预先计算百分比
+    const data = res.data;
+    const total = data.reduce((sum: number, item: StateChartData) => sum + item['设备数'], 0);
+    
+    // 为每个数据项添加百分比字段
+    return data.map(item => ({
+      ...item,
+      百分比: total > 0 ? (item['设备数'] / total * 100).toFixed(1) : '0'
+    }));
+  },
+
+  // 告警数据变化查询
+  fetchAlarmData: async (params?: {
+    created_at_gte?: string;
+    created_at_lte?: string;
+    dimension?: string;
+  }): Promise<AlarmChartData[]> => {
+    // 可选参数
+    // const params = {
+    //   created_at_gte: dayjs().startOf('day').format('YYYY-MM-DD HH:mm:ss'),
+    //   created_at_lte: dayjs().endOf('day').format('YYYY-MM-DD HH:mm:ss'),
+    //   dimension: 'hour'
+    // };
+
+    
+    const res = await axios.get<AlarmChartData[]>(`${API_BASE_URL}/big/zichan_alarm_chart`, { params });
+    return res.data;
+  }
+};
+
+// Theme API 响应类型
+export interface ThemeSettingsResponse {
+  message: string;
+  data: ThemeSettings;
+}
+
+// Theme API 定义
+export const ThemeAPI = {
+  // 获取主题设置
+  getThemeSettings: async (): Promise<ThemeSettings> => {
+    try {
+      const response = await axios.get(`${API_BASE_URL}/theme`);
+      return response.data.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+
+  // 更新主题设置
+  updateThemeSettings: async (themeData: Partial<ThemeSettings>): Promise<ThemeSettings> => {
+    try {
+      const response = await axios.put(`${API_BASE_URL}/theme`, themeData);
+      return response.data.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+
+  // 重置主题设置
+  resetThemeSettings: async (): Promise<ThemeSettings> => {
+    try {
+      const response = await axios.post(`${API_BASE_URL}/theme/reset`);
+      return response.data.data;
+    } catch (error) {
+      throw error;
+    }
+  }
+};
+
+// 图表数据API接口类型
+interface ChartDataResponse<T> {
+  message: string;
+  data: T;
+}
+
+interface UserActivityData {
+  date: string;
+  count: number;
+}
+
+interface FileUploadsData {
+  month: string;
+  count: number;
+}
+
+interface FileTypesData {
+  type: string;
+  value: number;
+}
+
+interface DashboardOverviewData {
+  userCount: number;
+  fileCount: number;
+  articleCount: number;
+  todayLoginCount: number;
+}
+
+// 图表数据API
+export const ChartAPI = {
+  // 获取用户活跃度数据
+  getUserActivity: async (): Promise<ChartDataResponse<UserActivityData[]>> => {
+    try {
+      const response = await axios.get(`${API_BASE_URL}/charts/user-activity`);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+
+  // 获取文件上传统计数据
+  getFileUploads: async (): Promise<ChartDataResponse<FileUploadsData[]>> => {
+    try {
+      const response = await axios.get(`${API_BASE_URL}/charts/file-uploads`);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+
+  // 获取文件类型分布数据
+  getFileTypes: async (): Promise<ChartDataResponse<FileTypesData[]>> => {
+    try {
+      const response = await axios.get(`${API_BASE_URL}/charts/file-types`);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+
+  // 获取仪表盘概览数据
+  getDashboardOverview: async (): Promise<ChartDataResponse<DashboardOverviewData>> => {
+    try {
+      const response = await axios.get(`${API_BASE_URL}/charts/dashboard-overview`);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  }
+};
+
+// 地图相关API的接口类型定义
+export interface LoginLocationResponse {
+  message: string;
+  data: LoginLocation[];
+}
+
+export interface LoginLocationDetailResponse {
+  message: string;
+  data: LoginLocationDetail;
+}
+
+export interface LoginLocationUpdateResponse {
+  message: string;
+  data: LoginLocationDetail;
+}
+
+
+// 地图相关API
+export const MapAPI = {
+  // 获取地图标记点数据
+  getMarkers: async (params?: { 
+    startTime?: string; 
+    endTime?: string; 
+    userId?: number 
+  }): Promise<LoginLocationResponse> => {
+    try {
+      const response = await axios.get(`${API_BASE_URL}/map/markers`, { params });
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+
+  // 获取登录位置详情
+  getLocationDetail: async (locationId: number): Promise<LoginLocationDetailResponse> => {
+    try {
+      const response = await axios.get(`${API_BASE_URL}/map/location/${locationId}`);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+
+  // 更新登录位置信息
+  updateLocation: async (locationId: number, data: { 
+    longitude: number; 
+    latitude: number; 
+    location_name?: string; 
+  }): Promise<LoginLocationUpdateResponse> => {
+    try {
+      const response = await axios.put(`${API_BASE_URL}/map/location/${locationId}`, data);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  }
+};
+
+// 系统设置API响应类型
+interface SystemSettingsResponse {
+  message: string;
+  data: SystemSetting[];
+}
+
+// 系统设置API
+export const SystemAPI = {
+  // 获取所有系统设置
+  getSettings: async (): Promise<SystemSettingGroupData[]> => {
+    try {
+      const response = await axios.get(`${API_BASE_URL}/settings`);
+      return response.data.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+
+  // 获取指定分组的系统设置
+  getSettingsByGroup: async (group: string): Promise<SystemSetting[]> => {
+    try {
+      const response = await axios.get(`${API_BASE_URL}/settings/group/${group}`);
+      return response.data.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+
+  // 更新系统设置
+  updateSettings: async (settings: Partial<SystemSetting>[]): Promise<SystemSetting[]> => {
+    try {
+      const response = await axios.put(`${API_BASE_URL}/settings`, settings);
+      return response.data.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+
+  // 重置系统设置
+  resetSettings: async (): Promise<SystemSetting[]> => {
+    try {
+      const response = await axios.post(`${API_BASE_URL}/settings/reset`);
+      return response.data.data;
+    } catch (error) {
+      throw error;
+    }
+  }
+};
+

+ 441 - 0
asset/admin/components_amap.tsx

@@ -0,0 +1,441 @@
+import React, { useEffect, useRef } from 'react';
+import { useQuery, useQueryClient } from '@tanstack/react-query';
+import { Spin } from 'antd';
+import './style_amap.css';
+import { MapMode, MarkerData } from '../share/types.ts';
+
+// 在线地图配置
+export const AMAP_ONLINE_CONFIG = {
+  // 高德地图 Web API 密钥
+  API_KEY: '75bb5fba220426729ed65da3b2982f25',
+  // 主JS文件路径
+  MAIN_JS: 'https://webapi.amap.com/maps?v=2.0&key=75bb5fba220426729ed65da3b2982f25',
+  // 插件列表
+  PLUGINS: ['AMap.MouseTool', 'AMap.RangingTool', 'AMap.Scale', 'AMap.ToolBar', 'AMap.MarkerCluster'],
+};
+
+export const AMAP_OFFLINE_CONFIG = {
+  // 主JS文件路径
+  MAIN_JS: '/amap/amap3.js?v=2.0',
+  // 插件目录
+  PLUGINS_PATH: '/amap/plugins',
+  // 插件列表
+  PLUGINS: ['AMap.MouseTool', 'AMap.RangingTool', 'AMap.Scale', 'AMap.ToolBar', 'AMap.MarkerCluster'],
+};
+
+// 离线瓦片配置
+export const TILE_CONFIG = {
+  // 瓦片地图基础路径
+  BASE_URL: '/amap/tiles',
+  // 缩放级别范围
+  ZOOMS: [3, 20] as [number, number],
+  // 默认中心点
+  DEFAULT_CENTER: [108.25910334, 27.94292459] as [number, number],
+  // 默认缩放级别
+  DEFAULT_ZOOM: 15
+} as const;
+
+// 地图控件配置
+export const MAP_CONTROLS = {
+  scale: true,
+  toolbar: true,
+  mousePosition: true,
+} as const;
+
+export interface AMapProps {
+  style?: React.CSSProperties;
+  width?: string | number;
+  height?: string | number;
+  center?: [number, number];
+  zoom?: number;
+  mode?: MapMode;
+  onMarkerClick?: (markerData: MarkerData) => void;
+  onClick?: (lnglat: [number, number]) => void;
+  markers?: MarkerData[];
+  showCluster?: boolean;
+  queryKey?: string;
+}
+
+export interface MapConfig {
+  zoom: number;
+  center: [number, number];
+  zooms: [number, number];
+  resizeEnable: boolean;
+  rotateEnable: boolean;
+  pitchEnable: boolean;
+  defaultCursor: string;
+  showLabel: boolean;
+  layers?: any[];
+}
+
+export interface AMapInstance {
+  map: any;
+  setZoomAndCenter: (zoom: number, center: [number, number]) => void;
+  setCenter: (center: [number, number]) => void;
+  setZoom: (zoom: number) => void;
+  destroy: () => void;
+  clearMap: () => void;
+  getAllOverlays: (type: string) => any[];
+  on: (event: string, handler: Function) => void;
+} 
+
+declare global {
+  interface Window {
+    AMap: any;
+  }
+}
+
+const loadScript = (url: string,plugins:string[]): Promise<void> => {
+  return new Promise((resolve, reject) => {
+    const script = document.createElement('script');
+    script.type = 'text/javascript';
+    script.src = url + (plugins.length > 0 ? `&plugin=${plugins.join(',')}` : '');
+    script.onerror = (e) => reject(e);
+    script.onload = () => resolve();
+    document.head.appendChild(script);
+  });
+};
+
+export const useAMapLoader = (mode: MapMode = MapMode.ONLINE) => {
+  return useQuery({
+    queryKey: ['amap-loader', mode],
+    queryFn: async () => {
+      if (typeof window === 'undefined') return null;
+      
+      if (!window.AMap) {
+        const config = mode === MapMode.OFFLINE ? AMAP_OFFLINE_CONFIG : AMAP_ONLINE_CONFIG;
+        await loadScript(config.MAIN_JS,config.PLUGINS);
+      }
+      
+      return window.AMap;
+    },
+    staleTime: Infinity, // 地图脚本加载后永不过期
+    gcTime: Infinity,
+    retry: 2,
+  });
+}; 
+
+export const useAMapClick = (
+  map: any,
+  onClick?: (lnglat: [number, number]) => void
+) => {
+  const mouseTool = useRef<any>(null);
+  const clickHandlerRef = useRef<((e: any) => void) | null>(null);
+
+  useEffect(() => {
+    if (!map) return;
+
+    // 清理旧的点击处理器
+    if (clickHandlerRef.current) {
+      map.off('click', clickHandlerRef.current);
+      clickHandlerRef.current = null;
+    }
+
+    // 如果有点击回调,设置新的点击处理器
+    if (onClick) {
+      clickHandlerRef.current = (e: any) => {
+        const lnglat = e.lnglat.getLng ? 
+          [e.lnglat.getLng(), e.lnglat.getLat()] as [number, number] :
+          [e.lnglat.lng, e.lnglat.lat] as [number, number];
+        onClick(lnglat);
+      };
+      map.on('click', clickHandlerRef.current);
+    }
+
+    return () => {
+      if (clickHandlerRef.current) {
+        map.off('click', clickHandlerRef.current);
+        clickHandlerRef.current = null;
+      }
+    };
+  }, [map, onClick]);
+
+  return {
+    mouseTool: mouseTool.current
+  };
+}; 
+
+// 定义图标配置的类型
+interface MarkerIconConfig {
+  size: [number, number];
+  content: string;
+}
+
+// 默认图标配置
+const DEFAULT_MARKER_ICON: MarkerIconConfig = {
+  size: [25, 34],
+  content: `
+    <svg width="25" height="34" viewBox="0 0 25 34" fill="none" xmlns="http://www.w3.org/2000/svg">
+      <path d="M12.5 0C5.59644 0 0 5.59644 0 12.5C0 21.875 12.5 34 12.5 34C12.5 34 25 21.875 25 12.5C25 5.59644 19.4036 0 12.5 0ZM12.5 17C10.0147 17 8 14.9853 8 12.5C8 10.0147 10.0147 8 12.5 8C14.9853 8 17 10.0147 17 12.5C17 14.9853 14.9853 17 12.5 17Z" fill="#1890ff"/>
+    </svg>
+  `
+};
+
+interface UseAMapMarkersProps {
+  map: any;
+  markers: MarkerData[];
+  showCluster?: boolean;
+  onMarkerClick?: (markerData: MarkerData) => void;
+}
+
+export const useAMapMarkers = ({
+  map,
+  markers,
+  showCluster = true,
+  onMarkerClick,
+}: UseAMapMarkersProps) => {
+  const clusterInstance = useRef<any>(null);
+  const markersRef = useRef<any[]>([]);
+
+  // 优化经纬度格式化函数
+  const toFixedDigit = (num: number, n: number): string => {
+    if (typeof num !== "number") return "";
+    return Number(num).toFixed(n);
+  };
+
+  // 创建标记点
+  const createMarker = (markerData: MarkerData) => {
+    const { longitude, latitude, title, iconUrl } = markerData;
+    
+    // 创建标记点
+    const marker = new window.AMap.Marker({
+      position: [longitude, latitude],
+      title: title,
+      icon: iconUrl ? new window.AMap.Icon({
+        size: DEFAULT_MARKER_ICON.size,
+        imageSize: DEFAULT_MARKER_ICON.size,
+        image: iconUrl
+      }) : new window.AMap.Icon({
+        size: DEFAULT_MARKER_ICON.size,
+        imageSize: DEFAULT_MARKER_ICON.size,
+        image: `data:image/svg+xml;charset=utf-8,${encodeURIComponent(DEFAULT_MARKER_ICON.content)}`
+      }),
+      label: title ? {
+        content: title,
+        direction: 'top'
+      } : undefined
+    });
+
+    // 添加点击事件
+    if (onMarkerClick) {
+      marker.on('click', () => onMarkerClick(markerData));
+    }
+
+    return marker;
+  };
+
+  // 处理聚合点
+  const handleCluster = () => {
+    if (!map || !markers.length) return;
+
+    const points = markers.map(item => ({
+      weight: 1,
+      lnglat: [
+        toFixedDigit(item.longitude, 5),
+        toFixedDigit(item.latitude, 5)
+      ],
+      ...item
+    }));
+
+    if (clusterInstance.current) {
+      clusterInstance.current.setData(points);
+      return;
+    }
+
+    if(window.AMap?.MarkerCluster){
+      clusterInstance.current = new window.AMap.MarkerCluster(map, points, {
+        gridSize: 60,
+        renderMarker: (context: { marker: any; data: MarkerData[] }) => {
+          const { marker, data } = context;
+          const firstPoint = data[0];
+          
+          if (firstPoint.iconUrl) {
+            marker.setContent(`<img src="${firstPoint.iconUrl}" style="width:${DEFAULT_MARKER_ICON.size[0]}px;height:${DEFAULT_MARKER_ICON.size[1]}px;">`);
+          } else {
+            marker.setContent(DEFAULT_MARKER_ICON.content);
+          }
+          marker.setAnchor('bottom-center');
+          marker.setOffset(new window.AMap.Pixel(0, 0));
+
+          if (firstPoint.title) {
+            marker.setLabel({
+              direction: 'top',
+              offset: new window.AMap.Pixel(0, -5),
+              content: firstPoint.title
+            });
+          }
+        
+          marker.on('click', () => onMarkerClick?.(firstPoint));
+        }
+      });
+    }
+
+    // 优化聚合点点击逻辑
+    if(clusterInstance.current){
+      clusterInstance.current.on('click', (item: any) => {
+        if (item.clusterData.length <= 1) return;
+
+        const center = item.clusterData.reduce(
+          (acc: number[], curr: any) => [
+            acc[0] + Number(curr.lnglat[0]),
+            acc[1] + Number(curr.lnglat[1])
+          ],
+          [0, 0]
+        ).map((coord: number) => coord / item.clusterData.length);
+
+        map.setZoomAndCenter(map.getZoom() + 2, center);
+      });
+    }
+  };
+
+  // 处理普通标记点
+  const handleMarkers = () => {
+    if (!map || !markers.length) return;
+    
+    // 清除旧的标记点
+    markersRef.current.forEach(marker => marker.setMap(null));
+    markersRef.current = [];
+
+    // 添加新的标记点
+    markersRef.current = markers.map(markerData => {
+      const marker = createMarker(markerData);
+      marker.setMap(map);
+      return marker;
+    });
+  };
+
+  useEffect(() => {
+    if (!map) return;
+
+    // 清理旧的标记点和聚合点
+    if (clusterInstance.current) {
+      clusterInstance.current.setMap(null);
+      clusterInstance.current = null;
+    }
+    markersRef.current.forEach(marker => marker.setMap(null));
+    markersRef.current = [];
+
+    // 根据配置添加新的标记点
+    if (showCluster) {
+      handleCluster();
+    } else {
+      handleMarkers();
+    }
+
+    return () => {
+      if (clusterInstance.current) {
+        clusterInstance.current.setMap(null);
+        clusterInstance.current = null;
+      }
+      markersRef.current.forEach(marker => marker.setMap(null));
+      markersRef.current = [];
+    };
+  }, [map, markers, showCluster]);
+}; 
+
+const AMapComponent: React.FC<AMapProps> = ({
+  width = '100%',
+  height = '400px',
+  center = TILE_CONFIG.DEFAULT_CENTER as [number, number],
+  zoom = TILE_CONFIG.DEFAULT_ZOOM,
+  mode = window.CONFIG?.MAP_CONFIG?.MAP_MODE || MapMode.ONLINE,
+  onMarkerClick,
+  onClick,
+  markers = [],
+  showCluster = true,
+  queryKey = 'amap-instance',
+}) => {
+  const mapContainer = useRef<HTMLDivElement>(null);
+  const mapInstance = useRef<AMapInstance | null>(null);
+  const queryClient = useQueryClient();
+
+  // 加载地图脚本
+  const { data: AMap, isLoading: isLoadingScript } = useAMapLoader(mode);
+
+  // 初始化地图实例
+  const { data: map } = useQuery<AMapInstance>({
+    queryKey: [ queryKey ],
+    queryFn: async () => {
+      if (!AMap || !mapContainer.current) return null;
+
+      const config: MapConfig = {
+        zoom,
+        center,
+        zooms: [3, 20],
+        resizeEnable: true,
+        rotateEnable: false,
+        pitchEnable: false,
+        defaultCursor: 'pointer',
+        showLabel: true,
+      };
+
+      if (mode === 'offline') {
+        config.layers = [
+          new AMap.TileLayer({
+            getTileUrl: (x: number, y: number, z: number) => 
+              `${TILE_CONFIG.BASE_URL}/${z}/${x}/${y}.png`,
+            zIndex: 100,
+          })
+        ];
+      }
+
+      const newMap = new AMap.Map(mapContainer.current, config);
+      mapInstance.current = newMap;
+      return newMap;
+    },
+    enabled: !!AMap && !!mapContainer.current && !isLoadingScript,
+    gcTime: Infinity,
+    staleTime: Infinity,
+  });
+
+  // 处理标记点
+  useAMapMarkers({
+    map,
+    markers,
+    showCluster,
+    onMarkerClick,
+  });
+
+  // 处理点击事件
+  useAMapClick(map, onClick);
+
+  // 更新地图视图
+  useEffect(() => {
+    if (!map) return;
+    
+    if (center && zoom) {
+      map.setZoomAndCenter(zoom, center);
+    } else if (center) {
+      map.setCenter(center);
+    } else if (zoom) {
+      map.setZoom(zoom);
+    }
+  }, [map, center, zoom]);
+
+  // 清理地图实例和查询缓存
+  useEffect(() => {
+    return () => {
+      if (mapInstance.current) {
+        mapInstance.current.destroy();
+        mapInstance.current = null;
+        // 清理 React Query 缓存
+        queryClient.removeQueries({ queryKey: [ queryKey ] });
+      }
+    };
+  }, [queryClient]);
+
+  return (
+    <div
+      ref={mapContainer}
+      style={{
+        width,
+        height,
+        position: 'relative',
+      }}
+    >
+      {isLoadingScript && <div className="w-full h-full flex justify-center items-center"><Spin /></div>}
+    </div>
+  );
+};
+
+export default AMapComponent;

+ 167 - 0
asset/admin/components_uploader.tsx

@@ -0,0 +1,167 @@
+import React, { useState } from 'react';
+import { 
+  Layout, Menu, Button, Table, Space,
+  Form, Input, Select, message, Modal,
+  Card, Spin, Row, Col, Breadcrumb, Avatar,
+  Dropdown, ConfigProvider, theme, Typography,
+  Switch, Badge, Image, Upload, Divider, Descriptions,
+  Popconfirm, Tag, Statistic, DatePicker, Radio, Progress, Tabs, List, Alert, Collapse, Empty, Drawer
+} from 'antd';
+import {
+  UploadOutlined,
+} from '@ant-design/icons';   
+import { uploadMinIOWithPolicy , uploadOSSWithPolicy} from '@d8d-appcontainer/api';
+import type { MinioUploadPolicy, OSSUploadPolicy } from '@d8d-appcontainer/types';
+import 'dayjs/locale/zh-cn';
+import { OssType } from '../share/types.ts';
+
+import { FileAPI } from './api.ts';
+
+// MinIO文件上传组件
+export const Uploader = ({ 
+  onSuccess, 
+  onError,
+  onProgress, 
+  maxSize = 10 * 1024 * 1024,
+  prefix = 'uploads/',
+  allowedTypes = ['image/jpeg', 'image/png', 'application/pdf', 'text/plain']
+}: {
+  onSuccess?: (fileUrl: string, fileInfo: any) => void;
+  onError?: (error: Error) => void;
+  onProgress?: (percent: number) => void;
+  maxSize?: number;
+  prefix?: string;
+  allowedTypes?: string[];
+}) => {
+  const [uploading, setUploading] = useState(false);
+  const [progress, setProgress] = useState(0);
+  
+  // 处理文件上传
+  const handleUpload = async (options: any) => {
+    const { file, onSuccess: uploadSuccess, onError: uploadError, onProgress: uploadProgress } = options;
+    
+    setUploading(true);
+    setProgress(0);
+    
+    // 文件大小检查
+    if (file.size > maxSize) {
+      message.error(`文件大小不能超过${maxSize / 1024 / 1024}MB`);
+      uploadError(new Error('文件过大'));
+      setUploading(false);
+      return;
+    }
+    
+    // 文件类型检查
+    if (allowedTypes && allowedTypes.length > 0 && !allowedTypes.includes(file.type)) {
+      message.error(`不支持的文件类型: ${file.type}`);
+      uploadError(new Error('不支持的文件类型'));
+      setUploading(false);
+      return;
+    }
+    
+    try {
+      // 1. 获取上传策略
+      const policyResponse = await FileAPI.getUploadPolicy(file.name, prefix, maxSize);
+      const policy = policyResponse.data;
+      
+      if (!policy) {
+        throw new Error('获取上传策略失败');
+      }
+      
+      // 生成随机文件名但保留原始扩展名
+      const fileExt = file.name.split('.').pop() || '';
+      const randomName = `${Date.now()}_${Math.random().toString(36).substring(2, 10)}${fileExt ? `.${fileExt}` : ''}`;
+      
+      // 2. 上传文件到MinIO
+      const callbacks = {
+        onProgress: (event: { progress: number }) => {
+          const percent = Math.round(event.progress);
+          setProgress(percent);
+          uploadProgress({ percent });
+          onProgress?.(percent);
+        },
+        onComplete: () => {
+          setUploading(false);
+          setProgress(100);
+        },
+        onError: (err: Error) => {
+          setUploading(false);
+          message.error(`上传失败: ${err.message}`);
+          uploadError(err);
+          onError?.(err);
+        }
+      };
+      
+      // 执行上传
+      const fileUrl = window.CONFIG?.OSS_TYPE === OssType.MINIO ? 
+        await uploadMinIOWithPolicy(
+          policy as MinioUploadPolicy,
+          file,
+          randomName,
+          callbacks
+        ) : await uploadOSSWithPolicy(
+          policy as OSSUploadPolicy,
+          file,
+          randomName,
+          callbacks
+        );
+      
+      // 从URL中提取相对路径
+      const relativePath = `${policy.prefix}${randomName}`;
+      
+      // 3. 保存文件信息到文件库
+      const fileInfo = {
+        file_name: randomName,
+        original_filename: file.name,
+        file_path: relativePath,
+        file_type: file.type,
+        file_size: file.size,
+        tags: '',
+        description: '',
+        category_id: undefined
+      };
+      
+      const saveResponse = await FileAPI.saveFileInfo(fileInfo);
+      
+      // 操作成功
+      uploadSuccess(relativePath);
+      message.success('文件上传成功');
+      onSuccess?.(relativePath, saveResponse.data);
+    } catch (error: any) {
+      // 上传失败
+      setUploading(false);
+      message.error(`上传失败: ${error.message}`);
+      uploadError(error);
+      onError?.(error);
+    }
+  };
+  
+  return (
+    <Upload.Dragger
+      name="file"
+      multiple={false}
+      customRequest={handleUpload}
+      showUploadList={true}
+      progress={{
+        strokeColor: {
+          '0%': '#108ee9',
+          '100%': '#87d068',
+        },
+        format: (percent) => `${Math.round(percent || 0)}%`,
+      }}
+    >
+      <p className="ant-upload-drag-icon">
+        <UploadOutlined />
+      </p>
+      <p className="ant-upload-text">点击或拖动文件到这里上传</p>
+      <p className="ant-upload-hint">
+        支持单个文件上传,最大{maxSize / 1024 / 1024}MB
+      </p>
+      {uploading && (
+        <div style={{ marginTop: 16 }}>
+          <Progress percent={progress} />
+        </div>
+      )}
+    </Upload.Dragger>
+  );
+};

+ 27 - 0
asset/admin/deno.json

@@ -0,0 +1,27 @@
+{
+  "imports": {
+    "react": "https://esm.d8d.fun/react@19.0.0",
+    "react-dom": "https://esm.d8d.fun/react-dom@19.0.0",
+    "react-dom/client": "https://esm.d8d.fun/react-dom@19.0.0/client",
+    "react-router": "https://esm.d8d.fun/react-router@7.3.0?deps=react@19.0.0,react-dom@19.0.0",
+    "antd": "https://esm.d8d.fun/antd@5.24.5?standalone&deps=react@19.0.0,react-dom@19.0.0",
+    "antd/locale/zh_CN": "https://esm.d8d.fun/antd@5.24.5/locale/zh_CN?standalone&deps=react@19.0.0,react-dom@19.0.0",
+    "@ant-design/icons": "https://esm.d8d.fun/@ant-design/icons@5.6.1?standalone&deps=react@19.0.0,react-dom@19.0.0",
+    "@tanstack/react-query": "https://esm.d8d.fun/@tanstack/react-query@5.67.1?deps=react@19.0.0,react-dom@19.0.0",
+    "axios": "https://esm.d8d.fun/axios@1.6.2",
+    "dayjs": "https://esm.d8d.fun/dayjs@1.11.13",
+    "dayjs/locale/zh-cn": "https://esm.d8d.fun/dayjs@1.11.13/locale/zh-cn",
+    "dayjs/plugin/weekday": "https://esm.d8d.fun/dayjs@1.11.13/plugin/weekday",
+    "dayjs/plugin/localeData": "https://esm.d8d.fun/dayjs@1.11.13/plugin/localeData",
+    "@d8d-appcontainer/types": "https://esm.d8d.fun/@d8d-appcontainer/types@3.0.47",
+    "@d8d-appcontainer/api": "https://esm.d8d.fun/@d8d-appcontainer/api@3.0.47",
+    "@ant-design/plots": "https://esm.d8d.fun/@ant-design/plots@2.1.13?deps=react@19.0.0,react-dom@19.0.0",
+    "lodash": "https://esm.d8d.fun/lodash@4.17.21"
+  },
+  "compilerOptions": {
+    "lib": ["dom", "dom.iterable", "esnext", "deno.ns"],
+    "jsx": "react",
+    "jsxFactory": "React.createElement",
+    "jsxFragmentFactory": "React.Fragment"
+  }
+}

+ 821 - 0
asset/admin/deno.lock

@@ -0,0 +1,821 @@
+{
+  "version": "4",
+  "redirects": {
+    "http://esm.d8d.fun/@types/react@~19.0.12/index.d.ts": "https://esm.d8d.fun/@types/react@19.0.12/index.d.ts",
+    "https://esm.d8d.fun/@antv/component@^2.1.2?target=denonext": "https://esm.d8d.fun/@antv/component@2.1.2?target=denonext",
+    "https://esm.d8d.fun/@antv/coord@^0.4.7?target=denonext": "https://esm.d8d.fun/@antv/coord@0.4.7?target=denonext",
+    "https://esm.d8d.fun/@antv/event-emitter@^0.1.3?target=denonext": "https://esm.d8d.fun/@antv/event-emitter@0.1.3?target=denonext",
+    "https://esm.d8d.fun/@antv/g-canvas@^2.0.29?target=denonext": "https://esm.d8d.fun/@antv/g-canvas@2.0.40?target=denonext",
+    "https://esm.d8d.fun/@antv/g-plugin-dragndrop@^2.0.22?target=denonext": "https://esm.d8d.fun/@antv/g-plugin-dragndrop@2.0.32?target=denonext",
+    "https://esm.d8d.fun/@antv/g2-extension-plot@^0.1.1?target=denonext": "https://esm.d8d.fun/@antv/g2-extension-plot@0.1.2?target=denonext",
+    "https://esm.d8d.fun/@antv/g2@^5.1.8?target=denonext": "https://esm.d8d.fun/@antv/g2@5.2.12?target=denonext",
+    "https://esm.d8d.fun/@antv/g2@^5.1.9?target=denonext": "https://esm.d8d.fun/@antv/g2@5.2.12?target=denonext",
+    "https://esm.d8d.fun/@antv/g?target=denonext": "https://esm.d8d.fun/@antv/g@6.1.21?target=denonext",
+    "https://esm.d8d.fun/@antv/g@^5.18.19?target=denonext": "https://esm.d8d.fun/@antv/g@5.18.27?target=denonext",
+    "https://esm.d8d.fun/@antv/g@^6.1.11?target=denonext": "https://esm.d8d.fun/@antv/g@6.1.21?target=denonext",
+    "https://esm.d8d.fun/@antv/scale@^0.4.12?target=denonext": "https://esm.d8d.fun/@antv/scale@0.4.16?target=denonext",
+    "https://esm.d8d.fun/@antv/scale@^0.4.16?target=denonext": "https://esm.d8d.fun/@antv/scale@0.4.16?target=denonext",
+    "https://esm.d8d.fun/@antv/util@^2.0.13?target=denonext": "https://esm.d8d.fun/@antv/util@2.0.17?target=denonext",
+    "https://esm.d8d.fun/@antv/util@^3.3.10?target=denonext": "https://esm.d8d.fun/@antv/util@3.3.10?target=denonext",
+    "https://esm.d8d.fun/@antv/util@^3.3.4?target=denonext": "https://esm.d8d.fun/@antv/util@3.3.10?target=denonext",
+    "https://esm.d8d.fun/@antv/util@^3.3.5?target=denonext": "https://esm.d8d.fun/@antv/util@3.3.10?target=denonext",
+    "https://esm.d8d.fun/@antv/util@^3.3.7?target=denonext": "https://esm.d8d.fun/@antv/util@3.3.10?target=denonext",
+    "https://esm.d8d.fun/@antv/vendor@^1.0.8/d3-array?target=denonext": "https://esm.d8d.fun/@antv/vendor@1.0.10/d3-array?target=denonext",
+    "https://esm.d8d.fun/@antv/vendor@^1.0.8/d3-dsv?target=denonext": "https://esm.d8d.fun/@antv/vendor@1.0.10/d3-dsv?target=denonext",
+    "https://esm.d8d.fun/@antv/vendor@^1.0.8/d3-force?target=denonext": "https://esm.d8d.fun/@antv/vendor@1.0.10/d3-force?target=denonext",
+    "https://esm.d8d.fun/@antv/vendor@^1.0.8/d3-format?target=denonext": "https://esm.d8d.fun/@antv/vendor@1.0.10/d3-format?target=denonext",
+    "https://esm.d8d.fun/@antv/vendor@^1.0.8/d3-geo?target=denonext": "https://esm.d8d.fun/@antv/vendor@1.0.10/d3-geo?target=denonext",
+    "https://esm.d8d.fun/@antv/vendor@^1.0.8/d3-hierarchy?target=denonext": "https://esm.d8d.fun/@antv/vendor@1.0.10/d3-hierarchy?target=denonext",
+    "https://esm.d8d.fun/@antv/vendor@^1.0.8/d3-path?target=denonext": "https://esm.d8d.fun/@antv/vendor@1.0.10/d3-path?target=denonext",
+    "https://esm.d8d.fun/@antv/vendor@^1.0.8/d3-scale-chromatic?target=denonext": "https://esm.d8d.fun/@antv/vendor@1.0.10/d3-scale-chromatic?target=denonext",
+    "https://esm.d8d.fun/@antv/vendor@^1.0.8/d3-shape?target=denonext": "https://esm.d8d.fun/@antv/vendor@1.0.10/d3-shape?target=denonext",
+    "https://esm.d8d.fun/@deno/shim-deno-test@^0.5.0?target=denonext": "https://esm.d8d.fun/@deno/shim-deno-test@0.5.0?target=denonext",
+    "https://esm.d8d.fun/@deno/shim-deno@~0.18.0?target=denonext": "https://esm.d8d.fun/@deno/shim-deno@0.18.2?target=denonext",
+    "https://esm.d8d.fun/@socket.io/component-emitter@~3.1.0?target=denonext": "https://esm.d8d.fun/@socket.io/component-emitter@3.1.2?target=denonext",
+    "https://esm.d8d.fun/@types/color-name@~1.1.5/index.d.ts": "https://esm.d8d.fun/@types/color-name@1.1.5/index.d.ts",
+    "https://esm.d8d.fun/@types/color-string@~1.5.5/index.d.ts": "https://esm.d8d.fun/@types/color-string@1.5.5/index.d.ts",
+    "https://esm.d8d.fun/@types/combined-stream@~1.0.6/index.d.ts": "https://esm.d8d.fun/@types/combined-stream@1.0.6/index.d.ts",
+    "https://esm.d8d.fun/@types/d3-array@~3.2.1/index.d.ts": "https://esm.d8d.fun/@types/d3-array@3.2.1/index.d.ts",
+    "https://esm.d8d.fun/@types/d3-color@~1.4.5/index.d.ts": "https://esm.d8d.fun/@types/d3-color@1.4.5/index.d.ts",
+    "https://esm.d8d.fun/@types/d3-color@~3.1.3/index.d.ts": "https://esm.d8d.fun/@types/d3-color@3.1.3/index.d.ts",
+    "https://esm.d8d.fun/@types/d3-dispatch@~3.0.6/index.d.ts": "https://esm.d8d.fun/@types/d3-dispatch@3.0.6/index.d.ts",
+    "https://esm.d8d.fun/@types/d3-dsv@~3.0.7/index.d.ts": "https://esm.d8d.fun/@types/d3-dsv@3.0.7/index.d.ts",
+    "https://esm.d8d.fun/@types/d3-force@~3.0.10/index.d.ts": "https://esm.d8d.fun/@types/d3-force@3.0.10/index.d.ts",
+    "https://esm.d8d.fun/@types/d3-format@~3.0.4/index.d.ts": "https://esm.d8d.fun/@types/d3-format@3.0.4/index.d.ts",
+    "https://esm.d8d.fun/@types/d3-geo@~3.1.0/index.d.ts": "https://esm.d8d.fun/@types/d3-geo@3.1.0/index.d.ts",
+    "https://esm.d8d.fun/@types/d3-hierarchy@~3.1.7/index.d.ts": "https://esm.d8d.fun/@types/d3-hierarchy@3.1.7/index.d.ts",
+    "https://esm.d8d.fun/@types/d3-interpolate@~3.0.4/index.d.ts": "https://esm.d8d.fun/@types/d3-interpolate@3.0.4/index.d.ts",
+    "https://esm.d8d.fun/@types/d3-path@~3.1.1/index.d.ts": "https://esm.d8d.fun/@types/d3-path@3.1.1/index.d.ts",
+    "https://esm.d8d.fun/@types/d3-quadtree@~3.0.6/index.d.ts": "https://esm.d8d.fun/@types/d3-quadtree@3.0.6/index.d.ts",
+    "https://esm.d8d.fun/@types/d3-scale-chromatic@~3.1.0/index.d.ts": "https://esm.d8d.fun/@types/d3-scale-chromatic@3.1.0/index.d.ts",
+    "https://esm.d8d.fun/@types/d3-shape@~3.1.7/index.d.ts": "https://esm.d8d.fun/@types/d3-shape@3.1.7/index.d.ts",
+    "https://esm.d8d.fun/@types/d3-timer@~3.0.2/index.d.ts": "https://esm.d8d.fun/@types/d3-timer@3.0.2/index.d.ts",
+    "https://esm.d8d.fun/@types/debug@~4.1.12/index.d.ts": "https://esm.d8d.fun/@types/debug@4.1.12/index.d.ts",
+    "https://esm.d8d.fun/@types/follow-redirects@~1.14.4/index.d.ts": "https://esm.d8d.fun/@types/follow-redirects@1.14.4/index.d.ts",
+    "https://esm.d8d.fun/@types/lodash-es@~4.17.12/index.d.ts": "https://esm.d8d.fun/@types/lodash-es@4.17.12/index.d.ts",
+    "https://esm.d8d.fun/@types/mime-types@~2.1.4/index.d.ts": "https://esm.d8d.fun/@types/mime-types@2.1.4/index.d.ts",
+    "https://esm.d8d.fun/@types/ms@~2.1.0/index.d.ts": "https://esm.d8d.fun/@types/ms@2.1.0/index.d.ts",
+    "https://esm.d8d.fun/@types/proxy-from-env@~1.0.4/index.d.ts": "https://esm.d8d.fun/@types/proxy-from-env@1.0.4/index.d.ts",
+    "https://esm.d8d.fun/@types/rbush@~3.0.4/index.d.ts": "https://esm.d8d.fun/@types/rbush@3.0.4/index.d.ts",
+    "https://esm.d8d.fun/@types/react-dom@~19.0.4/client.d.ts": "https://esm.d8d.fun/@types/react-dom@19.0.6/client.d.ts",
+    "https://esm.d8d.fun/@types/react-dom@~19.0.4/index.d.ts": "https://esm.d8d.fun/@types/react-dom@19.0.6/index.d.ts",
+    "https://esm.d8d.fun/@types/react@~19.0.10/index.d.ts": "https://esm.d8d.fun/@types/react@19.0.14/index.d.ts",
+    "https://esm.d8d.fun/@types/react@~19.0.12/index.d.ts": "https://esm.d8d.fun/@types/react@19.0.12/index.d.ts",
+    "https://esm.d8d.fun/@types/react@~19.0.12/jsx-runtime.d.ts": "https://esm.d8d.fun/@types/react@19.0.14/jsx-runtime.d.ts",
+    "https://esm.d8d.fun/@types/react@~19.0.14/index.d.ts": "https://esm.d8d.fun/@types/react@19.0.14/index.d.ts",
+    "https://esm.d8d.fun/@types/scheduler@~0.23.0/index.d.ts": "https://esm.d8d.fun/@types/scheduler@0.23.0/index.d.ts",
+    "https://esm.d8d.fun/@types/set-cookie-parser@~2.4.10/index.d.ts": "https://esm.d8d.fun/@types/set-cookie-parser@2.4.10/index.d.ts",
+    "https://esm.d8d.fun/@types/simple-swizzle@~0.2.2/index.d.ts": "https://esm.d8d.fun/@types/simple-swizzle@0.2.2/index.d.ts",
+    "https://esm.d8d.fun/@types/svg-path-parser@~1.1.6/index.d.ts": "https://esm.d8d.fun/@types/svg-path-parser@1.1.6/index.d.ts",
+    "https://esm.d8d.fun/@types/ws@~8.18.0/index.d.mts": "https://esm.d8d.fun/@types/ws@8.18.1/index.d.mts",
+    "https://esm.d8d.fun/asynckit@^0.4.0?target=denonext": "https://esm.d8d.fun/asynckit@0.4.0?target=denonext",
+    "https://esm.d8d.fun/axios@^1.7.2?target=denonext": "https://esm.d8d.fun/axios@1.8.4?target=denonext",
+    "https://esm.d8d.fun/bufferutil@^4.0.1?target=denonext": "https://esm.d8d.fun/bufferutil@4.0.9?target=denonext",
+    "https://esm.d8d.fun/color-name@^1.0.0?target=denonext": "https://esm.d8d.fun/color-name@1.1.4?target=denonext",
+    "https://esm.d8d.fun/color-string@^1.5.5?target=denonext": "https://esm.d8d.fun/color-string@1.9.1?target=denonext",
+    "https://esm.d8d.fun/combined-stream@^1.0.8?target=denonext": "https://esm.d8d.fun/combined-stream@1.0.8?target=denonext",
+    "https://esm.d8d.fun/cookie@^1.0.1?target=denonext": "https://esm.d8d.fun/cookie@1.0.2?target=denonext",
+    "https://esm.d8d.fun/d3-array@^3.2.4?target=denonext": "https://esm.d8d.fun/d3-array@3.2.4?target=denonext",
+    "https://esm.d8d.fun/d3-color@^1.4.0?target=denonext": "https://esm.d8d.fun/d3-color@1.4.1?target=denonext",
+    "https://esm.d8d.fun/d3-color@^3.1.0?target=denonext": "https://esm.d8d.fun/d3-color@3.1.0?target=denonext",
+    "https://esm.d8d.fun/d3-dispatch@^3.0.1?target=denonext": "https://esm.d8d.fun/d3-dispatch@3.0.1?target=denonext",
+    "https://esm.d8d.fun/d3-dsv@^3.0.1?target=denonext": "https://esm.d8d.fun/d3-dsv@3.0.1?target=denonext",
+    "https://esm.d8d.fun/d3-force@^3.0.0?target=denonext": "https://esm.d8d.fun/d3-force@3.0.0?target=denonext",
+    "https://esm.d8d.fun/d3-format@^3.1.0?target=denonext": "https://esm.d8d.fun/d3-format@3.1.0?target=denonext",
+    "https://esm.d8d.fun/d3-geo@^3.1.1?target=denonext": "https://esm.d8d.fun/d3-geo@3.1.1?target=denonext",
+    "https://esm.d8d.fun/d3-hierarchy@^3.1.2?target=denonext": "https://esm.d8d.fun/d3-hierarchy@3.1.2?target=denonext",
+    "https://esm.d8d.fun/d3-interpolate@^3.0.1?target=denonext": "https://esm.d8d.fun/d3-interpolate@3.0.1?target=denonext",
+    "https://esm.d8d.fun/d3-path@^3.1.0?target=denonext": "https://esm.d8d.fun/d3-path@3.1.0?target=denonext",
+    "https://esm.d8d.fun/d3-quadtree@^3.0.1?target=denonext": "https://esm.d8d.fun/d3-quadtree@3.0.1?target=denonext",
+    "https://esm.d8d.fun/d3-scale-chromatic@^3.1.0?target=denonext": "https://esm.d8d.fun/d3-scale-chromatic@3.1.0?target=denonext",
+    "https://esm.d8d.fun/d3-shape@^3.2.0?target=denonext": "https://esm.d8d.fun/d3-shape@3.2.0?target=denonext",
+    "https://esm.d8d.fun/d3-timer@^3.0.1?target=denonext": "https://esm.d8d.fun/d3-timer@3.0.1?target=denonext",
+    "https://esm.d8d.fun/debug?target=denonext": "https://esm.d8d.fun/debug@4.4.0?target=denonext",
+    "https://esm.d8d.fun/delayed-stream@~1.0.0?target=denonext": "https://esm.d8d.fun/delayed-stream@1.0.0?target=denonext",
+    "https://esm.d8d.fun/engine.io-client@~6.6.1?target=denonext": "https://esm.d8d.fun/engine.io-client@6.6.3?target=denonext",
+    "https://esm.d8d.fun/engine.io-parser@~5.2.1?target=denonext": "https://esm.d8d.fun/engine.io-parser@5.2.3?target=denonext",
+    "https://esm.d8d.fun/eventemitter3@^5.0.1?target=denonext": "https://esm.d8d.fun/eventemitter3@5.0.1?target=denonext",
+    "https://esm.d8d.fun/fecha@^4.2.1?target=denonext": "https://esm.d8d.fun/fecha@4.2.3?target=denonext",
+    "https://esm.d8d.fun/flru@^1.0.2?target=denonext": "https://esm.d8d.fun/flru@1.0.2?target=denonext",
+    "https://esm.d8d.fun/follow-redirects@^1.15.0?target=denonext": "https://esm.d8d.fun/follow-redirects@1.15.9?target=denonext",
+    "https://esm.d8d.fun/follow-redirects@^1.15.6?target=denonext": "https://esm.d8d.fun/follow-redirects@1.15.9?target=denonext",
+    "https://esm.d8d.fun/form-data@^4.0.0?target=denonext": "https://esm.d8d.fun/form-data@4.0.2?target=denonext",
+    "https://esm.d8d.fun/gl-matrix@^3.3.0?target=denonext": "https://esm.d8d.fun/gl-matrix@3.4.3?target=denonext",
+    "https://esm.d8d.fun/gl-matrix@^3.4.3?target=denonext": "https://esm.d8d.fun/gl-matrix@3.4.3?target=denonext",
+    "https://esm.d8d.fun/internmap@^2.0.3?target=denonext": "https://esm.d8d.fun/internmap@2.0.3?target=denonext",
+    "https://esm.d8d.fun/is-arrayish@^0.3.1?target=denonext": "https://esm.d8d.fun/is-arrayish@0.3.2?target=denonext",
+    "https://esm.d8d.fun/isexe@^3.1.1?target=denonext": "https://esm.d8d.fun/isexe@3.1.1?target=denonext",
+    "https://esm.d8d.fun/lodash-es@^4.17.21?target=denonext": "https://esm.d8d.fun/lodash-es@4.17.21?target=denonext",
+    "https://esm.d8d.fun/mime-types@^2.1.12?target=denonext": "https://esm.d8d.fun/mime-types@2.1.35?target=denonext",
+    "https://esm.d8d.fun/ms@^2.1.3?target=denonext": "https://esm.d8d.fun/ms@2.1.3?target=denonext",
+    "https://esm.d8d.fun/nanoid@^5.1.2?target=denonext": "https://esm.d8d.fun/nanoid@5.1.5?target=denonext",
+    "https://esm.d8d.fun/node-gyp-build@^4.3.0?target=denonext": "https://esm.d8d.fun/node-gyp-build@4.8.4?target=denonext",
+    "https://esm.d8d.fun/pdfast@^0.2.0?target=denonext": "https://esm.d8d.fun/pdfast@0.2.0?target=denonext",
+    "https://esm.d8d.fun/proxy-from-env@^1.1.0?target=denonext": "https://esm.d8d.fun/proxy-from-env@1.1.0?target=denonext",
+    "https://esm.d8d.fun/quickselect@^2.0.0?target=denonext": "https://esm.d8d.fun/quickselect@2.0.0?target=denonext",
+    "https://esm.d8d.fun/scheduler@^0.25.0?target=denonext": "https://esm.d8d.fun/scheduler@0.25.0?target=denonext",
+    "https://esm.d8d.fun/set-cookie-parser@^2.6.0?target=denonext": "https://esm.d8d.fun/set-cookie-parser@2.7.1?target=denonext",
+    "https://esm.d8d.fun/simple-swizzle@^0.2.2?target=denonext": "https://esm.d8d.fun/simple-swizzle@0.2.2?target=denonext",
+    "https://esm.d8d.fun/socket.io-client@^4.7.2?target=denonext": "https://esm.d8d.fun/socket.io-client@4.8.1?target=denonext",
+    "https://esm.d8d.fun/socket.io-parser@~4.2.4?target=denonext": "https://esm.d8d.fun/socket.io-parser@4.2.4?target=denonext",
+    "https://esm.d8d.fun/supports-color?target=denonext": "https://esm.d8d.fun/supports-color@10.0.0?target=denonext",
+    "https://esm.d8d.fun/svg-path-parser@^1.1.0?target=denonext": "https://esm.d8d.fun/svg-path-parser@1.1.0?target=denonext",
+    "https://esm.d8d.fun/tslib?target=denonext": "https://esm.d8d.fun/tslib@2.8.1?target=denonext",
+    "https://esm.d8d.fun/tslib@^2.0.3?target=denonext": "https://esm.d8d.fun/tslib@2.8.1?target=denonext",
+    "https://esm.d8d.fun/tslib@^2.3.1?target=denonext": "https://esm.d8d.fun/tslib@2.8.1?target=denonext",
+    "https://esm.d8d.fun/tslib@^2.5.3?target=denonext": "https://esm.d8d.fun/tslib@2.8.1?target=denonext",
+    "https://esm.d8d.fun/utf-8-validate@%3E=5.0.2?target=denonext": "https://esm.d8d.fun/utf-8-validate@6.0.5?target=denonext",
+    "https://esm.d8d.fun/which@^4.0.0?target=denonext": "https://esm.d8d.fun/which@4.0.0?target=denonext",
+    "https://esm.d8d.fun/ws@~8.17.1?target=denonext": "https://esm.d8d.fun/ws@8.17.1?target=denonext",
+    "https://esm.d8d.fun/xmlhttprequest-ssl@~2.1.1?target=denonext": "https://esm.d8d.fun/xmlhttprequest-ssl@2.1.2?target=denonext"
+  },
+  "remote": {
+    "https://deno.land/std@0.150.0/media_types/_util.ts": "ce9b4fc4ba1c447dafab619055e20fd88236ca6bdd7834a21f98bd193c3fbfa1",
+    "https://deno.land/std@0.150.0/media_types/mod.ts": "2d4b6f32a087029272dc59e0a55ae3cc4d1b27b794ccf528e94b1925795b3118",
+    "https://deno.land/std@0.150.0/media_types/vendor/mime-db.v1.52.0.ts": "724cee25fa40f1a52d3937d6b4fbbfdd7791ff55e1b7ac08d9319d5632c7f5af",
+    "https://deno.land/x/xhr@0.3.0/mod.ts": "094aacd627fd9635cd942053bf8032b5223b909858fa9dc8ffa583752ff63b20",
+    "https://esm.d8d.fun/@ant-design/charts-util@0.0.1-alpha.5/X-ZHJlYWN0LWRvbUAxOS4wLjAscmVhY3RAMTkuMC4w/denonext/charts-util.mjs": "2b5590c1c3b095fd2cc95448c174aa747dd187929dd0f0ecf79a19a95e2f48ba",
+    "https://esm.d8d.fun/@ant-design/icons@5.6.1/X-ZHJlYWN0LWRvbUAxOS4wLjAscmVhY3RAMTkuMC4w/denonext/icons.bundle.mjs": "16bcd798fac77be9ae0119583e1aff242302d9ee8feff6b878ef6199478e53eb",
+    "https://esm.d8d.fun/@ant-design/icons@5.6.1?standalone&deps=react@19.0.0,react-dom@19.0.0": "d85e73ce328297fe787ec94c2cdf8b2c768095ec258698b08aa6be9b91d6cd98",
+    "https://esm.d8d.fun/@ant-design/plots@2.1.13/X-ZHJlYWN0LWRvbUAxOS4wLjAscmVhY3RAMTkuMC4w/denonext/es/core/adaptor.mjs": "b90d9b450f147a6d4d3e1b47baa542671f8efd9ff4660c28f385b3cdbf3b352e",
+    "https://esm.d8d.fun/@ant-design/plots@2.1.13/X-ZHJlYWN0LWRvbUAxOS4wLjAscmVhY3RAMTkuMC4w/denonext/plots.mjs": "876cf1f56ba8aab30a4384f6d9bc4917517c4f3d4ad6de364ca8d9b36ca6242d",
+    "https://esm.d8d.fun/@ant-design/plots@2.1.13?deps=react@19.0.0,react-dom@19.0.0": "84906a36985e619bb0bbd45b0042c4089739a59435f9e79b80beb5e528199e2a",
+    "https://esm.d8d.fun/@antv/component@2.1.2/denonext/component.mjs": "28fc61733f241a38f19e531f8be737349b2eaee6cac2f79e65c7597da039c7a9",
+    "https://esm.d8d.fun/@antv/component@2.1.2/denonext/esm/animation.mjs": "4398a0d87d38f8f28cc66ac5edf73522a2d2022e4a09d7148cdc28a34235def8",
+    "https://esm.d8d.fun/@antv/component@2.1.2/denonext/esm/core.mjs": "28652a2f328e6345420a56c4a0439a97b3e0f0e1f68db5c4bdde6f1a34492d42",
+    "https://esm.d8d.fun/@antv/component@2.1.2/denonext/esm/ui/axis/utils.mjs": "893e0b6c932a7b50d2a9ec50302134b302eea7510a2f7d91c75ee1f47e619d60",
+    "https://esm.d8d.fun/@antv/component@2.1.2/denonext/esm/util/layout.mjs": "eb6a0f127e63d47381373572541ef20f9f5dadd4357c61fb98f6d6b699f3ccfb",
+    "https://esm.d8d.fun/@antv/component@2.1.2?target=denonext": "b2d3867120f1de17c7f04b95a98374e820ba1c1c10fd23d04d706df8362762e6",
+    "https://esm.d8d.fun/@antv/coord@0.4.7/denonext/coord.mjs": "d41035ad648ccfa8c7f30161afddcf9be75875dfea360edc2274ea2caa9bf5bb",
+    "https://esm.d8d.fun/@antv/coord@0.4.7?target=denonext": "8d294610da01962b300019f754cc5d8a971c919a1ecd29978495dfdb76597ba0",
+    "https://esm.d8d.fun/@antv/event-emitter@0.1.3/denonext/event-emitter.mjs": "132e4f3edf35540a0136342421658f5d26112cedec14219d347a2b47e1844df6",
+    "https://esm.d8d.fun/@antv/event-emitter@0.1.3?target=denonext": "04ebe3ece292d5bbdb1248daa78e85365313e0ab6ad39e867e1821d0a7ec477c",
+    "https://esm.d8d.fun/@antv/g-camera-api@1.2.25/denonext/g-camera-api.mjs": "a3f5e7a7b595ca1b5a13b5ec3834846af0bf1fd01ecff2f3780c0a70abb31fb6",
+    "https://esm.d8d.fun/@antv/g-camera-api@2.0.35/denonext/g-camera-api.mjs": "85765f20b17a53d27b9dbbd0fc08b240ef49451734c8b3bbc626fea47015a936",
+    "https://esm.d8d.fun/@antv/g-canvas@2.0.40/denonext/g-canvas.mjs": "9826f2797247ab82ed1f26ea2ca1c68a7031ba9d3f582cc62b968f1903527196",
+    "https://esm.d8d.fun/@antv/g-canvas@2.0.40?target=denonext": "5d46676c68d497ed70c7fb58528c458e473fca7f3cc4eb35e87657e5dfe1583c",
+    "https://esm.d8d.fun/@antv/g-dom-mutation-observer-api@1.2.24/denonext/g-dom-mutation-observer-api.mjs": "0ac8c4e89b7c5a6ef4c38d9f72af379c53238dedb577328fdcda6d6ef4e8bf8a",
+    "https://esm.d8d.fun/@antv/g-dom-mutation-observer-api@2.0.32/denonext/g-dom-mutation-observer-api.mjs": "e03b4abedef9079df61b54096ccc5035b6760a52bd3fa842cc763c0085830612",
+    "https://esm.d8d.fun/@antv/g-lite@1.2.24/denonext/g-lite.mjs": "2044f352f7ffb8649f510ec321ef0b9bf0754e8dc3846cac1357e3f68a77742d",
+    "https://esm.d8d.fun/@antv/g-lite@2.2.16/denonext/g-lite.mjs": "6dab528917bd2de8d7aa0e7699fd93d10da69e40092579c7d169422013c0edba",
+    "https://esm.d8d.fun/@antv/g-math@2.0.2/denonext/g-math.mjs": "81430b250e8653d66c81b214c827a2a6bc1592a4f478e141a68d79ec94adb3ca",
+    "https://esm.d8d.fun/@antv/g-math@3.0.0/denonext/g-math.mjs": "017fef097eb0b54f209b994d232dbb1442356a673673ecc7d2e6b57556d68729",
+    "https://esm.d8d.fun/@antv/g-plugin-canvas-path-generator@2.1.16/denonext/g-plugin-canvas-path-generator.mjs": "6470d7da52e9092836d875aab33c9d055c4918ebedef6f03b8bcb0f40f3fd928",
+    "https://esm.d8d.fun/@antv/g-plugin-canvas-picker@2.1.19/denonext/g-plugin-canvas-picker.mjs": "e16e40d3fdce13b787aa779c0e37e8dc6bd0e89465098c38a4cdf23c714555be",
+    "https://esm.d8d.fun/@antv/g-plugin-canvas-renderer@2.2.19/denonext/g-plugin-canvas-renderer.mjs": "475bbe5c473463cedaf8a246d1880d6abe2aed0e5e062a4d274134052528497a",
+    "https://esm.d8d.fun/@antv/g-plugin-dom-interaction@2.1.21/denonext/g-plugin-dom-interaction.mjs": "2565644f67503b8c973b25c729753d390efd8568ca57f4a4bacc4fee0c672aa1",
+    "https://esm.d8d.fun/@antv/g-plugin-dragndrop@2.0.32/denonext/g-plugin-dragndrop.mjs": "ea2fb1bfae82fff1782602f8ad9827d17bc8036fd5b710b46864af81cd72fc95",
+    "https://esm.d8d.fun/@antv/g-plugin-dragndrop@2.0.32?target=denonext": "901bbdf4206881c45b298e0915a7948892fe29dd8ff09ead7207a516871f23b7",
+    "https://esm.d8d.fun/@antv/g-plugin-html-renderer@2.1.21/denonext/g-plugin-html-renderer.mjs": "d13773d6c3bdc2a654576898bfa9fc56821bdd40b620c343e9c01ecb8171b6e0",
+    "https://esm.d8d.fun/@antv/g-plugin-image-loader@2.1.19/denonext/g-plugin-image-loader.mjs": "697144b990620fe37b1e78147c75a2d800715d6f9c139ba5552c1b6f74484890",
+    "https://esm.d8d.fun/@antv/g-web-animations-api@1.2.25/denonext/g-web-animations-api.mjs": "a7108b0700b02006eaa32d038d984327a4fc309d7e7443834f72ec702bf9c23e",
+    "https://esm.d8d.fun/@antv/g-web-animations-api@2.1.21/denonext/g-web-animations-api.mjs": "5f4ae34f9d4bbf4fc504b5168639340127e05602bf544853996fdea4a0f103d5",
+    "https://esm.d8d.fun/@antv/g2-extension-plot@0.1.2/denonext/g2-extension-plot.mjs": "803f73251c8fc339577625d3c06e9a15dfeaaeeafc599c72c4cf1a964f466945",
+    "https://esm.d8d.fun/@antv/g2-extension-plot@0.1.2?target=denonext": "d8756296dca890bd3eaf0a16cb889f61f5cc9966f0c2b67075278b6084f92e27",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/animation.mjs": "eae43461181dfdc0b67c5eb75ff22428742c3a7777d0f9207e7db45f53d78d35",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/animation/fadeIn.mjs": "3db5d97278d67cdefc2b128fee3bf8c7ba2f18cea3717bd872a1c78a51d387a4",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/animation/fadeOut.mjs": "58379313e30b98e3f736f47771485388df776f4e9ed5ae6616eddae7ca94563d",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/animation/growInX.mjs": "1d72812edfd7e17bbb1a4accc7a8a7547ea1f564d9c7db7f93287a5afb452117",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/animation/growInY.mjs": "a10d52c60bb07c64c3a889ca1903ffad1c093435a08511cffe14333975e92c93",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/animation/morphing.mjs": "98943a5f0ea20d1b091c783d4defdcf2439b3686d61903ef2894ef1495a4c9c6",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/animation/pathIn.mjs": "c6c93f63fd36f020bf589f84d8e2876fff880a4ed5d0ddf6831b59bf5bfe2f14",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/animation/scaleInX.mjs": "779ad4a560a866a602d8b554fde8f5470b30d834bb7100ce11e0676c5363a434",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/animation/scaleInY.mjs": "9226cecb703bcf928923896d2b0235fcdeee4426092d3ac2954d77af4adae3a5",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/animation/scaleOutX.mjs": "413dcfb4289d454a651a880aae332a550bfc7ba5ea90d714aa5623518d84e1b4",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/animation/scaleOutY.mjs": "85ecba56794a5846356388a9a5eff10f199ed8f85f1f39ebdab80d655bc44fd6",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/animation/utils.mjs": "b3201c2e181751b63b8c999286f6ce527488efd0e95f0558c2b179456717c47d",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/animation/waveIn.mjs": "cc9a468032b6a7afd6fb3b04cbf40ec1e3f615d5408fb5b87fa4aca2de8d8e31",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/animation/zoomIn.mjs": "d0fb7cbf45f05d699b06c67c51c993f32ed1ea0329e2514cee88c3f9978e4800",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/animation/zoomOut.mjs": "25cdf7b270f243a0e159e33c40f49f72547d6764ab385a983286f0838b57560d",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/api.mjs": "bcc6af99f614dc698eaae8eacb2ac67d818603fcc614c5763b273b9394e6aff7",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/api/chart.mjs": "d0c39732c0d7459da9e0f9ee28d9c3d1b5c97a76ea8da3f08daa4b79d7b0cc5f",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/api/composition.mjs": "ba631c00e17a70380187ccce4dd51bdf89306cb41eb7c9111c8f6399d0da8b40",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/api/define.mjs": "6742ed1e315952503c8bd87fe9115df5e7565c46252de129ab8a74bbfc8a1881",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/api/extend.mjs": "8f86657caf0fe22a0b6a64dd3c6d0708fe8913c6bf21cb26461b281058892d99",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/api/library.mjs": "2147f1a11a3c439786c2f101394595c599f1423faad2257af464b1bd4cfc1d1b",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/api/mark.mjs": "3946d5d1a4e8b7438fa54974cbe30cd2472d72c3d71942293e0e1a463d4c20d7",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/api/node.mjs": "b6a602a1b31a7f84af115339b5913ad84446c5dbe405266811a3847f9e1f3e96",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/api/props.mjs": "bb0350880f2b966cf381afcc028636bfe038ed7930fb0482be0f74536ae25e8c",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/api/runtime.mjs": "54e6ef60f5c0f2e097b2baf4e7e15d67bf7886ccea83db346da907c7677c586c",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/api/utils.mjs": "eee16b9d77d74195009895d31b41dcfc44fd097ac7db7c5d6d1e447a2d7d18ef",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/component.mjs": "ca0c6dbcb13930411b903e35d5240b7303738ad532cb0867715259728b15dfd4",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/component/axis.mjs": "9bfd18620cce039031865f16b9fb5f2c5491ead6605f90b11fe3f25a24166a31",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/component/axisRadar.mjs": "66a33aa03c98e935e2e65ee71f67dd7fe1ff5dd6181a747c30c255c0b87dfe6a",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/component/axisX.mjs": "85bd77ebcc2a1cdd425042234342c4a10dec93dff8d8a7510b57399c46c5361d",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/component/axisY.mjs": "8bef3510bc3c382ab3de396c936ed2377b8aeb0d28928431b94621e197261944",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/component/constant.mjs": "74b2a796da0c5d030cc185fd4db8ba13c1d55be7ddae8083ccdcf88e39a1ff7a",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/component/legendCategory.mjs": "a9dfd7c3274175bb76f819a38a48c857df80b46ed82146217a89da372766c974",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/component/legendContinuous.mjs": "600fd3188aba183e25026c5430d18da6a71f67a1c65009b4f8e69374702a55f3",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/component/legendContinuousBlock.mjs": "9376a074ce8f5d459ce7f59ff6ae94b0a532476d6c2c0a18c66aa1522c8ef3b3",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/component/legendContinuousBlockSize.mjs": "6221ac8267524677f76fbb5dd051532c08c1054930e031a9d9b16f5eda47e1e7",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/component/legendContinuousSize.mjs": "98303c259a3371151ad8626b03a29fc3df37b5bba367bd0e8cad81c603d58cfa",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/component/legends.mjs": "9136ecafedeb81330e756b9012c0a5ae75ae55a943ccda47793a5c1b3a04abf6",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/component/scrollbar.mjs": "647ca4d8a7b16438dddf97d4d46b1308e0a20ac35bc5f2c4d7f2b5a23a9ab9aa",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/component/scrollbarX.mjs": "03b8d1e7f951fdf76eac78c86a8579c23bd243ca08d9c669dac959ddaa6a53f4",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/component/scrollbarY.mjs": "3fa1a1b043bb6e02364d8e59bcf71d835a2e52d16bd1f09d126f240da031ef04",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/component/slider.mjs": "69dab2ec7be6a3207a3aa08a25a87378d3ee5abe253941ef700a77e4586606d9",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/component/sliderX.mjs": "7c812f9d167dfa1ac9199cb30522ec74f882b6efc0bee631d6da85119b611c5c",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/component/sliderY.mjs": "aac8d424f8ca53233fed1911c65e3815cc8e2f7618f13657b1cc4ed218a879ac",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/component/title.mjs": "2d41cd8ac6bef8e3cf62caf508d622650ad8a66ffa9208464c1fe9788d31c06a",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/component/utils.mjs": "c1c73f5a936eb7c80be3d345b128510c93f19faa87d525cab727b1731e6a15c5",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/composition.mjs": "7ef06216c5ff485d44a3f0309fec71240692b0cbcabce50b8be71e149c8c2da5",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/composition/d3Projection.mjs": "e97eeb7d32f6cac57f3818341d7a76815192430b5843d8dc3704ac2381e415f7",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/composition/facetCircle.mjs": "69a56e37a85f25ab45ac26e09b83e6abe3c4d1bca29f1eefa604f083dde9e9af",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/composition/facetRect.mjs": "8404efede73184cd9ac493af25982886079496ba2bf5232fc70d368ec78fea21",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/composition/geoPath.mjs": "35da37f90fb7459e6eacea6b7fab0671b6dfbbb6a8d8c03efca61b704a2f14ab",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/composition/geoView.mjs": "280b99e9672564ac47f141998ec7acc8bd64d7759f3cdf12efc475c02c8abc0a",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/composition/mark.mjs": "8b9c01ffe6bf3502ccba224671e679c19f805073e0ef899537b4bac922545f88",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/composition/repeatMatrix.mjs": "6558ec41fb64f47d45a9332ba910bf263cf3661cf05c16b472f431bd556a3570",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/composition/spaceFlex.mjs": "37283e672f354b02bc466be3978750090ccf44fe982bbb88c581078df7ecf44c",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/composition/spaceLayer.mjs": "1b065bbf0db3d4a656c65fb37f11e3cbc23fcaec27e1a170454e7e99efa3a96c",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/composition/timingKeyframe.mjs": "b0a10a2e5f4741ddedf3bbf13964cb65c224dba51095dd3e021d1ab02ac2fc0f",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/composition/utils.mjs": "a17dc802bb0c4a6cf8c710326c032e8a62c8a6f501ba3e084eccb2ae6c25fb2f",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/composition/view.mjs": "ce466c61549eb5bd8a09b8f82f3cde9fcce771a6a1df882395dd469574f7273a",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/coordinate.mjs": "36fd4eda7abbcf4ae9be9eb154f1f515059f9935ffa8e45368675c0b8956181f",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/coordinate/cartesian.mjs": "d2ccefbbfe3394b03f6031d244ee52fe7df25fa0fe7154d108fa110b64d6866f",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/coordinate/fisheye.mjs": "3b48b171985cd15e072eefda9fc10b05d3c9d9431d6ecafc64325b38a471cbdb",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/coordinate/helix.mjs": "0630ea012be123cd082a09f2067b89e6729d8c5d7e03b6c24091397e00520f4f",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/coordinate/parallel.mjs": "a72b5cc58eac042af4687abac9481e0685976432428eb2a792e58b720498ffd9",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/coordinate/polar.mjs": "2f90c84407433b72951238d0dfeaaf5708cc8801d36f74a2d9efcd745d702140",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/coordinate/radar.mjs": "d717078b25756618d94508e711209bef64e8a02c36ccbc2824ebf6239d5e3e46",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/coordinate/radial.mjs": "0c95192cf43149c4e0a007eba875250b6c82556781ece09b6a921df68e5922a1",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/coordinate/theta.mjs": "7799b77c75e2b03a12a00e1a69672adeb70db16dd6c6eafedce6967f6f0a5dcf",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/coordinate/transpose.mjs": "ba5566df6a2003339b629e110fc52dd3a5ba71c96b92bfccdec4ab3a0c828080",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/data.mjs": "be3541c8e6a8af14481f68143c1817f0f3c6fb623bdbb5ada332bb39225e08c1",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/data/arc.mjs": "616c6dc7281e09499cf234d330dbda46570e970675881851bc27656acdf9bd39",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/data/cluster.mjs": "9b23498531455f5c815c56279d8d3d00360a6eee944442337445ef54f9698434",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/data/custom.mjs": "636ff8f958405dec94e0729da6cc0dd8bbbfee3453d95a50bde434e2d385b8cd",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/data/ema.mjs": "101f46d867ce8ae3902f007971b0e4edbdf8d9ce27609f4a7ed0e6e015d1b82a",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/data/fetch.mjs": "abbc05e41517cb36f3555c8d996770254b652adbc17c5aa3720066388dca8358",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/data/filter.mjs": "cf95beb4cf400458b700ba0143471b6f20329474305b9c198b4dc05380a79f2f",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/data/fold.mjs": "801469ebbd6fb6911eef60dacbbbde21ca9f7017adef14717dbc422898f0ee57",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/data/inline.mjs": "259808d3f42c0cba7dc1e5d055645dd9c652125d5df93b94b3f9316ad90e4515",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/data/join.mjs": "7af3929bcbb786410da97b8a688a686284c7330da8a28feff3865c3f82667e16",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/data/kde.mjs": "7072ac886fad60788763cad0efa7d3bdcfa433ee5dedace421b1a3a9e6066a4d",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/data/log.mjs": "6c9dd38232d2f1819d2865a07055ee7bf7975dbfadf2dc284443663bb6cdb1b5",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/data/map.mjs": "a0d6831da4be3f7793fb3c04343ec483d6f09e1bdd71928f935689987bdd511e",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/data/pick.mjs": "71a5c46234f88e68d50244721c9b670ce7cd61effd217d03c80e61b01951128f",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/data/rename.mjs": "5b9cba2d708176b75629ba5311e700029b47742bd11461bcc3175ac1f7740ab6",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/data/sankey.mjs": "27166a64f042903564078b93f9a4ba344f983d00d5d3ed5ccaa216dacf5b264b",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/data/slice.mjs": "f0b4b5c0c4ef66e5cbe31d5a7c0b63ca51acdb8f19355759645851377e5f2dc4",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/data/sort.mjs": "36dc616a5a0d131a0749d31994edbc9020011e8c0ce46390a11f751a030afa5b",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/data/sortBy.mjs": "32abf9d8e54a38848622ab04f2570fee066c056373efc4213f5215a2aaa3348b",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/data/tree.mjs": "e0c36ff7ca3387be4e8edd777892cf4e1ed31a0a4f72a3b535faa45e84f24193",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/data/utils/arc.mjs": "b50b43ba12dcee2b7365e5f911a80582d512f7cefa72b65e76f5ac66825ce3e6",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/data/utils/arc/arc.mjs": "4c0ff786b1568f481f6f2e6f90d9d3c40ce647e974e4cd0f6fa3200b3e0ee964",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/data/utils/arc/sort.mjs": "27006c5ace85d0b788954435f499359658d90b73a9f2a856b7236026835804a9",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/data/utils/d3-cloud.mjs": "3cedb78316657be13f6010a4f6b6e560c2b629168eab5eeb1d374dd14d86cc2d",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/data/utils/d3-sankey.mjs": "8b4a86bcbb3771f7a0558a6952115b6944a95d2912275966946fd54265bfd34a",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/data/utils/d3-sankey/align.mjs": "e741a98362ef4e39fb609eb12c1fc54f797bdd7a9472fa14cb134f60320a3700",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/data/utils/d3-sankey/constant.mjs": "985d889ec952e39ae12a1b9fd64959e17cb1cdfb83697d074208859fd4224916",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/data/utils/d3-sankey/sankey.mjs": "7ca262f1bf63ffd86df14f7284ddd5961426aaf47e34e758654d8664d9f30e07",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/data/utils/fields.mjs": "fcca8c33906cfed5c9706d4e5b7eeea61e74f1ef19c01ac25e0ac3820ab12a15",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/data/utils/flow.mjs": "dad7f4ce054d60d6fd872ff5d504b6510cb5c316507ff6dbf604a977060de9e8",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/data/utils/venn.mjs": "9d41fc73bca728e25b6a7fc442d4617064e9abfaf7cb1cf547ac1de255f1e194",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/data/utils/venn/circleintersection.mjs": "c3847d55c6a3157237c4e3c372f1a0f484a72273d35b793e5c9356406f316ca2",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/data/utils/venn/diagram.mjs": "f27b4723d343d6848cc6bcd1021300bd4132d9248433434b1e681fb0f38759b7",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/data/utils/venn/layout.mjs": "64d4a79880f0e83c0592e6f0899b8c80d54bdd978f4a659bd220bf3e4d61e3f9",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/data/venn.mjs": "85be3435e4b86c619f08b4581436313a1dc0d61cbc40ec5ba1e29079c2de6f93",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/data/wordCloud.mjs": "0d2a793fbea62dabbac29fdda31004865dc64efabe2e4c876ff7adf259b54f68",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/encode.mjs": "7566cc45c516f23a7d56df7a9e6bc871aca9f304ede1b7854262f618e134aa50",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/encode/column.mjs": "8d9559e24ae3a75650428abbb8e196a706f951e664e9d5172d3529967a4cadaf",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/encode/constant.mjs": "9774e460d3c83f8c83eabce02567b000f8db8027cb925b3b99804ea6c989d8d3",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/encode/field.mjs": "f65673cbba987098f0fc46eacbf90738ceb11b2016108a5bbe84a78aca40f697",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/encode/transform.mjs": "228cb7280898454da1f11c6e063f31b5db3fdada7584320cb19b714404af0b37",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/exports.mjs": "d3f2df49894ffcbf36776cfe9f93bede08c1fd5c240d8cae3443beeefcebb54c",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/interaction.mjs": "d32fa0b7d801f67e44d672e5b1d465f4b40ef3513305d772bf15ebf3b6c8b326",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/interaction/brushAxisHighlight.mjs": "509d6bfd8e79eaba5c2ad609d68e8c642718898498a95b99bd5dfb53f1dc1477",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/interaction/brushFilter.mjs": "169d59ba63a05663fe9eecf153440c781ac7bd15f32c68f67d0794d676c486a5",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/interaction/brushHighlight.mjs": "78441049bb28050deddd80cfd57e73aa09d64eef6b49c4e40b30d976d17c8a39",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/interaction/brushXFilter.mjs": "bc070d753bc714b78970b67ad2b2fff347b5e0717945029de01457a87debdd96",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/interaction/brushXHighlight.mjs": "41c99af57db95c200e3cd64e05f1e6b91df98d9b76ec615d89f92093cf8b3c42",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/interaction/brushYFilter.mjs": "7f4ec54b02bf4fa965635c4f958859945fae45da94c7060be0632444f8608c9d",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/interaction/brushYHighlight.mjs": "14932d3fd5a5790cb626acc7ff518cfc5695c8b11a0f520af207e9fcf219503a",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/interaction/chartIndex.mjs": "38785421416534c28345cb87f2210454769898f2ccd147653c3d455b0c2c8d36",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/interaction/elementHighlight.mjs": "b491f3b1e7a96de8db2a3fd7c73ed0ac39932758147a11de5cba152292bbb1c8",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/interaction/elementHighlightByColor.mjs": "a62a8012000cf774f71aac627871d69c26bcbf1faf88d429456ca6b0ab362cdc",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/interaction/elementHighlightByX.mjs": "165dc7fa7cf72e1a58909de8754e477bbe6080f80c224cd37f286e1cc5eb1c78",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/interaction/elementPointMove.mjs": "bfbee33ac024bd956940e14ae88926d8c24b8ddfd3222ee3852605aa16da53ba",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/interaction/elementSelect.mjs": "022c4219e3806016274129cb100923b8e0dce4fb58a0fa7802970a170a36772f",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/interaction/elementSelectByColor.mjs": "25b01874f578ec00ebd96431007e6a20dae4e7b64fa41c07eea1d776a5e476ca",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/interaction/elementSelectByX.mjs": "4856ec69557e8fcea255e67d4d02945802f64900e6d830c18a8595cb8d95ce36",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/interaction/event.mjs": "9f46c76c7872382fc094add2094ff8286a6543ba044cfc2932459f18b0adfbc5",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/interaction/fisheye.mjs": "7584b7fb24d4e2c002530e0e6eeca9260c2490176b205ff44f9d040d76a0fc55",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/interaction/legendFilter.mjs": "de8eea856da0546a659aefad7fe418e3c9264f5f3dedfd328f7449477b3d2291",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/interaction/legendHighlight.mjs": "5010ae0236246d2ed52b9d692218b89c3ae4dd4a6a9def49b833d369b09353c9",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/interaction/poptip.mjs": "32ed46ee64c2d72af3a8b87358ba0fced3365452154012d19b1099acbcadc696",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/interaction/scrollbarFilter.mjs": "3fc26ef5c89c8b854648cadc9e0e323a1cfb1144b32411c29866a9556f6ac2b6",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/interaction/sliderFilter.mjs": "df9dce77295b89b0887a928784925c25e68ba5049377ab7533e73e5a0e03ba5c",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/interaction/tooltip.mjs": "0fad44cafd0d0c4e411de5037480306975fa24f9417d9ade1938be85635d32de",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/interaction/treemapDrillDown.mjs": "0cc791f0415ca626367b5c5d37e920b0585fc08fd9e78cc5196a2130f9ecdcd5",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/interaction/utils.mjs": "839a38ec5a7734b412c5428f2d91f54dd8f035ce7ba71fc6cb083e25405b9e52",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/label-transform.mjs": "db742d4d2766316fca3b066a83cb6bbd1a0b579d0413837264aa2e0dfa09cda4",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/label-transform/contrastReverse.mjs": "1b1814ef031561ac6ac0d18062a73f26eaba71dec6edad59f628bb272b70762c",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/label-transform/exceedAdjust.mjs": "b74da82141c03c5559bd879d0fe9a03ffffa7d17cb569379458de4de32b52ad1",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/label-transform/overflowHide.mjs": "2bcce051489e59887155d67ed0ae01ba1076bbe0bc43bf888f852b9e0d0154cf",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/label-transform/overlapDodgeY.mjs": "2d0094cf743222c9b094cb361581003081f538cb30fc6bd47371ed591eda1384",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/label-transform/overlapHide.mjs": "c8985f4359e0a2e42ed3e4bbcb1fc59eece3921fb92de331ad0ebb44ec4575d2",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/lib.mjs": "816d88e8fd599e8e0d443a62a53cfadbbe1192c2f8b11171740c68411edfebd4",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/lib/builtinlib.mjs": "d665ab8604c1a05d67099b0c1693c92d674faa5f079c436ffbece93b98d10b0a",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/lib/core.mjs": "921e43412291dba8cecb6e0c7f7c5967909e54a8c75374b35d28a7f3a28a02b8",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/lib/geo.mjs": "06167da800889c7a7bd817ad23e76e88d2ad7b37ef42805c58077cdb05cb8e4c",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/lib/graph.mjs": "4630ddb7eaa178cbe08696aec3fc643383b0f2d12e675e88783f0ad6964731ba",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/lib/lite.mjs": "cc91b17b581f2c73dde44be932a5f03c0c854a409462848cc0bbcfaa8156441d",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/lib/plot.mjs": "3dfadb2eeb59f4dbf56c5ec6d3513c762bde6a7c8763086ea1d62e06e9fc6fa8",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/lib/std.mjs": "61a46faaad84fe7c2f9bd1fad61e8ace9db5b49d8b800c6b871fa683a060fea3",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/mark.mjs": "d050487f9be6aaafc0434209801d1ffc89489e59afa85476aac8295b9d99fd58",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/mark/area.mjs": "3dc433d9f322420e7e792c9e0d5da21f081ce7e07d01b611a371c43fbf9f7631",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/mark/box.mjs": "48a7f97dcf15a235c891b3c7accb1f42af83391d908265911da42481784c80e0",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/mark/boxplot.mjs": "979910cf332856b18daf5a167640741e5b667f791ec5092af0d680495bcee079",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/mark/cell.mjs": "0387b873ef3d67aba90ba4f0ec34ff43fd03c682fa06916daad338da27672d16",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/mark/chord.mjs": "0f6dc7a41679cffd2e17995138de3e35ae697b164648903ffe5ba5cff93de8ef",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/mark/connector.mjs": "d8562d123f36601ed7ac949881966f2520b6075967bfb8d500dbe933b3ad6948",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/mark/density.mjs": "8b15e03bd6f2053fdbc92471016d60b840a4cb53e3c0f7a21663c21d4f2016aa",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/mark/forceGraph.mjs": "1cc6356ddd918e2c606a3c7a7cb4f417d2badf09334dca713c63617839fbea29",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/mark/gauge.mjs": "3700b153e21ab800494550765beb71a64cfd9be5d8348dd01a38874711fb4cfa",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/mark/heatmap.mjs": "20772c0369d66561bd20687084a239348576768a154515c7e453b081cad15d81",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/mark/image.mjs": "b9faaf716084587813504b5c044e1e6f2343dc3a2132e71ca1c2445c9dc28c1a",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/mark/interval.mjs": "4c935d99ccef2cdfaa6d166f60411a6ce22f3cf04000bbaabc3e32d5b80e2658",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/mark/line.mjs": "13b8403710110e7ba5d1938d4085769ced844d774039f4b6607d648042819114",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/mark/lineX.mjs": "6004bb6eeb4580856d06859f07c4abe54c744e5de4cfb2c575814330f54fd320",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/mark/lineY.mjs": "a2d695a635b85bd85d66d77fbf23866857b19b2c0ff636873c9d04d73e88029f",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/mark/link.mjs": "8c7991dbe376b9fe7c6ed60a2a2d5a65b5b6d222e8c796f16b99952712fd65c4",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/mark/liquid.mjs": "3f94790bfa7a4610046e140257a2a6d891a04cf40c942b969063516064a3030b",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/mark/pack.mjs": "28ffb387c1b22d2b4b1b7103c17c0a5b8629488d1b2824296806c9a68e603374",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/mark/path.mjs": "0fc49af6f2dfddcb3240797852c387da39fe957c4ca9a5ac2e4d064c985a245b",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/mark/point.mjs": "7aed58ca796ce67fadb99a04a3318fba628ba41bba125fd528febf9277cabe9c",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/mark/polygon.mjs": "d22a14e3ce42d7dfd2dfed6012e690c653373a810f9d9e6b5e3fe7877aa82f16",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/mark/range.mjs": "3f1e58375f0da146d969c17241c0f02d356a9e2a162d906a0b06c63ca24299a6",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/mark/rangeX.mjs": "0842d83ea9faa32ea880c5aa35ec4c0579fb4ba44eec1b853278bb09bb9accb7",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/mark/rangeY.mjs": "46429b15330bd52175bdeb40b82d923a54976577ab3853190d9e58b810b3405a",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/mark/rect.mjs": "914d23b540b1e99c81ef555d82f702aed9eae3df34e340d83cdf946b84f843ff",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/mark/sankey.mjs": "3ec03da96045276e695338afdbbae56d77ad77b4cd7a82b7757f390e5b7a300d",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/mark/shape.mjs": "d7d74da2505e9077e60c37024d5fd729a1812970ca1888dda60a51028d11f550",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/mark/text.mjs": "35b437d79d2fe814c09bc4ca1cd4c402ae48781131762a27acef7538c307a9e3",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/mark/tree.mjs": "9d7432c4d7b388f25b626b415ada04205594dd6876c067a5ca3fa66e2037e2d9",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/mark/treemap.mjs": "66ac5327fc128f30371bb04278771605d07adbda13808f959531ee9f2a60ebe6",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/mark/utils.mjs": "2fcdea0cd8b10b09a937289d529a12fa20c9e51e791bf9e03fc6d689b9669077",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/mark/vector.mjs": "c1b32bda3989bfa4e8a4cf6c6dc78e7dbfb396ba05860a12660313755c66d691",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/mark/wordCloud.mjs": "e8b31857432aff815e5435c86cd20343d25f7555165d98a9570ae45026e139c2",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/palette.mjs": "3f5c4663e09efe51110b376f4783d259a5583b7684d8c2170a86b424204f3479",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/palette/category10.mjs": "454b23de6736535e330b69d66db5ba9c8ff93e5f1f3cce6ad7038a5213a46078",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/palette/category20.mjs": "f91aa8699e8a4939ec3c35f9daa8d2eaf4b1d7064efbda93cc5a749a4fa29231",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/runtime.mjs": "01a7aaaa9442952309a0750c905c4fcc33dccfc6afbfcec70efbe88f0ec6f58d",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/runtime/component.mjs": "d213ba2e652439890ac9069d8221b42e85ea51116311bb3482dd9a59db8d2ce5",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/runtime/constant.mjs": "e0f16c4c03602909034efd809cda80bfc85902919d83984d3ccceb80b310546c",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/runtime/coordinate.mjs": "3edc8913251a06d50f303b61b1ac24c6d6b577afe05cf8cb794e8ac9d70411e7",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/runtime/layout.mjs": "15b94c99cd0b722ff8e66268fcaf8d4a33606939467c26a10f934732db88e6bb",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/runtime/library.mjs": "66e2afad3b64a6a651b74340249e510da86966bd75c455d230eb0f0f36db12bd",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/runtime/mark.mjs": "03832ddc9cbdd52b0975c8b776a427ec3ef942a8b701773fa6ef8e6c119b4890",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/runtime/option-preprocess.mjs": "efd36f0382a92c7c61f52019017758dd1fd1ea662a146230ca3594ca8ff0f327",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/runtime/option-preprocess/style.mjs": "7eb3adbf29f0ab4405963808b2a519267916a7c38acf417487aefa226a851a25",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/runtime/plot.mjs": "9d407a9ec6f4a3f9a7d417c22c8ec62229789f46314ca2ea37ce2520cde8aca9",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/runtime/render.mjs": "5743e5644f5c6ad3566fba499cbf6f256a7fccd7034a94d028b4a8b15c6ba323",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/runtime/scale.mjs": "f0f41105f015d736c81714cea2312da3b68ba46e032a0e656bc6d10ed5b5a17d",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/runtime/transform.mjs": "18708d08e184baf4aed914fb8a59057478a18c064c6ea096c8f3a53f97424d7c",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/runtime/types/common.mjs": "294b5fccbd6eae832afee08db0f60b49eabdaf98a1e5c926ea3f13734a4bed8b",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/runtime/types/component.mjs": "a79f43322f3b9f8a7bc7f4f08ce2efccce31af5214c9782713cc2810f8ea41a1",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/runtime/types/data.mjs": "1f7c9d467bd20c533e07c04e808632503ca38a5d0a5942fb74a66631cff8c8fc",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/runtime/types/encode.mjs": "62579f62d333a07283b2d50a504d40266b05137177fa16fba11eef4dba5251c1",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/runtime/types/mark.mjs": "1d6f4553722e70dfef8754e80612ffd0c945f8f9c35009fee646d9319960c790",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/runtime/types/options.mjs": "aa1d9766682c2cade5426aebfe4aa59aadf361c2b208ecc7634142f9ca1c2be5",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/runtime/types/scale.mjs": "0c4985976453a2b6bd67866bad6defee2e7cce1a4706e05db74e682a3665ef1b",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/runtime/types/transform.mjs": "73bfd9764ba19088eb9a4a40a79768782b8d5ae39cf5fc9799f9bd52496eb44c",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/scale.mjs": "b0770f984e8ea95e0a556e8bc571b62fddeeabbf3d65bcbcc792002fce589923",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/scale/band.mjs": "df9f422eeb990c3a982a2120c76f5682489d2a114f61d6c9ecd0a7855171fd60",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/scale/constant.mjs": "eb78b4ba4a8d5defc6441edae05594fff4e9084b85f9e39041e9637724b70afa",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/scale/identity.mjs": "3daff6a1309669822e81840aef1aeddff65ba84e192bd65b3c83100a83530452",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/scale/linear.mjs": "968fa70a71c6c1639703778b8a45272a660a793e47de45ad111b3a0abe384abe",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/scale/log.mjs": "4e1eea30e76afb4360abe4c0dd1e00e96ab43fd01ed6edd083a051dfa15aadee",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/scale/ordinal.mjs": "b98beea63668ef14271339cf0f10764facd95acd41755a968f201a7fffc59f43",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/scale/point.mjs": "2b908f475ee1677d2ab74569198285a683bbf0b1be19c10d53b8c7ca786159c6",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/scale/pow.mjs": "dc0e2777867c1d2f6dc2eb845a3bab5f4f5a292d9b923d8bea2b8072f28379dd",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/scale/quantile.mjs": "40c78697af03e612510fb9d4e3563b3b276986d2672135711eaee10f492ff111",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/scale/quantize.mjs": "5dffc0d8cb97c05d85247780b3a5ff827acbf29ce3d9ed082d9eff13273cfe58",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/scale/sequential.mjs": "d8654c1081c83273503b253bca55f5669d33b437775da3e136784afbfbc9d25f",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/scale/sqrt.mjs": "1d90c8855fd2fd04b4047a55428247141aaa1bbf602566c57e326f3355bc1518",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/scale/threshold.mjs": "c1b8b4e2e414932bcf2473b5129ec4a93cd15d533fc15b98e98bed2a22ea1df9",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/scale/time.mjs": "0d74cb9deab359f5967c1328abc183b6a162222ba8b694cfba709394c7368c86",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/shape.mjs": "ef10477a803271acdff9cd2502f6ed084007f2ac3f0446a42ea18da871d28640",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/shape/area/area.mjs": "ae5bdcd65d47ee49c6a7371083127a30b2f4acb9d70921ce8a525e10bd7ef0cc",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/shape/area/curve.mjs": "184f76ad1976c7beb21b1c768ea9ff1d13e99c53d5e54996eded210673faebbe",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/shape/area/hv.mjs": "a19dc1b57b8b113cf9697cdf5e8b9187a33950fd32c7468e00510f27e0ec7703",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/shape/area/hvh.mjs": "399a57b6cef92dbdb4b108a4ca3eea93a6febab2daa902ae19bbd08525adda54",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/shape/area/smooth.mjs": "500e7adaeb97309990c96373751599c126081f06de99086c87548945eb6d2c2e",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/shape/area/vh.mjs": "20729cf64904e8f4872455d651a6308dd00118da361660859834edf499c78638",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/shape/box/box.mjs": "b2914146ad4852520e4671f470f12ce90abe7b8b708d9c90031bef67c93d46b0",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/shape/box/violin.mjs": "b21b13285f7791ef3a98bea8b34cb917c59e58df0d34f17c33c03c28d5c3fee4",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/shape/connector/connector.mjs": "cc74c297577616841933417d91359ba0d0b30fdb02111c495121f8e34de95377",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/shape/density/density.mjs": "729e391b8cebc7c3d075b5c93adf7ca5207fa9406af7310f83471739bd34cc8f",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/shape/gauge/round.mjs": "52c05b86d127a918d776c330e8df011301162bfb83f4408b8d6c35fa7d8ad4a2",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/shape/heatmap/heatmap.mjs": "0bca8684c1dc617789056529291c9b0f1419421a26de03ae0b5c23f6a9d24621",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/shape/heatmap/renderer.mjs": "c5981c40a17f23c8031792165ad8043ec4a2c00fdc91af3fc5424c16cfd7dff2",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/shape/heatmap/renderer/gradient.mjs": "0809ad3afce067d224d7ea08d80fbf145537ba2a34ca6372696bfdc61c0883ce",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/shape/image/image.mjs": "4742594f46152cb04381832cfc5126ddae62e5fbc48d417c48e343037d249e71",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/shape/interval/color.mjs": "faaa27d50dcf7dc0eb0528cb02cece28d4590cb157232e18f3ecc40e961156a6",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/shape/interval/funnel.mjs": "cf2b7de8619be57a606d92ba1267fe5e09d1bc97dd811443af12a1c391159bc5",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/shape/interval/hollow.mjs": "e30f2aaaa229713d84beb33ba5545a95325f8f7d16ed5b2bf5a5569bb4cb60ef",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/shape/interval/pyramid.mjs": "ba1df5e131835b29a65920d7264874a22833c70e526dcbc4660c4a5dccd7cf0a",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/shape/interval/rect.mjs": "7a92b1949830993d679a9430a6e44789755e402e8ae86d70eb96498293b6a7ea",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/shape/label/label.mjs": "f69c5cd66497165531bc0d7662d725a5dc6018a3a16ca33fa5fdea1303f2dcc0",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/shape/label/position.mjs": "8286ef721a258c0801cc7c55986c5908fab419ed14cac92d1d5d46a3385b3bdb",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/shape/label/position/area.mjs": "285890b5a10771405af0aac8c21401b974ac7ef31409bc6c747edda05e31ef10",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/shape/label/position/bottom.mjs": "1976fb286336139d4d7b3ce6ca62b96542077909ebd2420262884418d1b243bb",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/shape/label/position/bottomLeft.mjs": "998cc275c5523443e808497ad318008a76d757ed5ca524bc697464cc3a21037b",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/shape/label/position/bottomRight.mjs": "9698c79d3d934bd9b462232bbd9e69666ede32f89ab7f07ce0c01c85a43550cd",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/shape/label/position/default.mjs": "d9c96289ad98cb5663bee7e98f9c7c0ca312e9a74e9717bb9d6982496c450864",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/shape/label/position/inside.mjs": "9e8846190694d271e3817aa745870e9970cb7f04d400f54839bbf3a4fad1d3c8",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/shape/label/position/left.mjs": "34c8533d7ebcea9c939afc342462c086505909f1e92276ba5192608021cbc314",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/shape/label/position/outside.mjs": "f247663b45efcad05125f770cc75a0ab881f36945bb7e5d06a48be1c06d4c876",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/shape/label/position/right.mjs": "10a2a066f84774133c1b12544a02c2b536e9914b73531f5e0bc2c86c58cc0ed1",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/shape/label/position/spider.mjs": "2e288d8b788ce678bcea8dd17bd0d62f3d0f4b7cb5c14fcc55619f975f04ba67",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/shape/label/position/surround.mjs": "60d6894e3a03ae19211603ba7536ea9eb3ceff7db5f10e89080b1f6d3fb06c5c",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/shape/label/position/top.mjs": "c28d541733dabbb6930e082a797841d4e00ac974fd3386f25c082d17fc8477ae",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/shape/label/position/topLeft.mjs": "dd6409b61e9ef96e2e271efd799aa0b1879194b6fdea94e39f1de23a761b7bd5",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/shape/label/position/topRight.mjs": "bd917707b93277b1681692dd57f13d424d0c1588c8926c3672a1314a77724782",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/shape/label/position/utils.mjs": "e0b98108006ef586c112f2e522c1c18a44873549f283aba4951ee680d4c31b35",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/shape/line/curve.mjs": "99249e9c2697ae8815292c39a446d1fe2f87345e0b289718916cada597bb3cca",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/shape/line/hv.mjs": "3fdbdb06c2b5342191604cdf3257af4a2f40f69528aefe41169b9f68a320608f",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/shape/line/hvh.mjs": "5949762e6616874425f82669a51cde5c67b26506643813d4b4665ed9c9697d2d",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/shape/line/line.mjs": "78ec33e51764d2fb2684a547bead36eed1e6f6ccf98d3368c233f1cf0c39ec56",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/shape/line/smooth.mjs": "8e23c62877463068461f2f591318478749d5f4c10bfe41080efc609d020760a4",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/shape/line/trail.mjs": "f087c640385a71d3fc4acee30ed03a7ecce3a693832d907b2fa62d6f319c9266",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/shape/line/vh.mjs": "ecf19100068014642fd438e4fd0652778cf6cd93c6df9d48346321e05e71ee5d",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/shape/lineXY/line.mjs": "b67348b562fc9b5456e8bd77e06b06f9a7df3f86a9ba33dd43b443a087e54b5a",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/shape/link/arc.mjs": "70c9940d281232a0247789ea43cd329b319def6391a3a4e6fc821372331ea459",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/shape/link/link.mjs": "54928e2c9d332009338ad9a01343e7296f21cc526cec057581793e51cfe4a3b2",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/shape/link/smooth.mjs": "795991a0a025a1b251be5b3cd49c8cfd76393d51a425229fbd2dc22d0a783feb",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/shape/link/vhv.mjs": "c16ea274e65cc1a90fa13882d9f81ae47cc7f77e2d5deeb1088ef3c05731a930",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/shape/liquid/liquid.mjs": "800ee6be598b9859c9c548edcafedd4a507e3775d9a5f2a957f59bfd7b051440",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/shape/liquid/shapes.mjs": "e903cd314e6ce38057867603e82258546fc2248e69cfb9357b78bc42f77b085d",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/shape/liquid/wave.mjs": "3d002e6ece0b8f7edabba9325ef3be4f6740edc9b548a4f3afb4d244532c1140",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/shape/path/color.mjs": "38f0c520315191c7ce1247ee9a54aeef0751d499c92e989317e688bc8dd9ef98",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/shape/path/hollow.mjs": "97c08ef689f534a7ccd02227a3c05c9ca6b2d787f0d257d5b856c676caad651b",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/shape/path/path.mjs": "c5ed1c9bf6201c594411bd1fea051c5bfb1aedfb2d602b9d4f02da8968790a89",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/shape/point/bowtie.mjs": "e619dd0fb26457d7e5884fb6ae534969aa0ad200febd94f08912f01ac4a9727e",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/shape/point/circle.mjs": "29b13b8ae891d4ea5c786b9a713805563e26f84755eb95b5323c1d52f8dacdd1",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/shape/point/color.mjs": "1a4ef265a05f0b905d1f8a91200af4e2a755fe1bf9292576b22f1c8b23c0e624",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/shape/point/cross.mjs": "e3efa8d5be0fc41086a9af509101eb3847bbfe7b6d1b329f65b7034ee4b596bd",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/shape/point/diamond.mjs": "0d2edb21cbae64cfd89daa1e8b70ac72865fd6cb780ac8509dec473008d58de7",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/shape/point/hexagon.mjs": "330f53a5c8c0c121eedfc8f22551f2ca5a905237cc658d843c21f9e9570c0aa4",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/shape/point/hollow.mjs": "09a7eca23713fa45825674e08f7061e7099de2ced374b8a58b83f4248586ede8",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/shape/point/hollowBowtie.mjs": "30a305b37cbd6e1722b0ea85cd1c7b5806d4db76b5fd9a7158f4707f2374eaa7",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/shape/point/hollowCircle.mjs": "0ab857d0c44cc07fec3f6113acd5bbd938551b307397d977d16607da165b50dc",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/shape/point/hollowDiamond.mjs": "e90ba1531c0c9ec9391f4e3d9dcba5314e50ae71d3658499ebb080c59e99ea83",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/shape/point/hollowHexagon.mjs": "a7a5bac63583f5213a4794446b1e8e4d6050ea593c09b509f5013776b340d8a9",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/shape/point/hollowSquare.mjs": "0330e3171d074e6601892753ea3fcf6cd70a8b47462fa4587661f59a408fc926",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/shape/point/hollowTriangle.mjs": "a9bd6fd84e5892f1f461fadec0e901d9d5e88961c3a1ed10379f8b47e992aedd",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/shape/point/hollowTriangleDown.mjs": "f8e4c19b7129a8608458ea2a1224d4d27730d3be495466cc27e17add1a5f57b6",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/shape/point/hyphen.mjs": "6ba3233ae1436688ae6897011fca1f6c8e70977778a3be94bfbbf79b92a68271",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/shape/point/line.mjs": "6888e24511e94bb43baf920bdbaf001024e96869792adeceb480a506419e2592",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/shape/point/plus.mjs": "60a78d3a1c55f46547101ecda5225b8bbc89d1eded991bd79df313d2251658be",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/shape/point/point.mjs": "361339f016ae75bc123732191da36b731cbc185b7c51a695ae9f9899d18967e9",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/shape/point/square.mjs": "cabd892953f735dbfef2ca78de4af682ddee5879de4297cf81cfaacbd516b2f7",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/shape/point/tick.mjs": "f4de4ced652583f76df90556dc24694455f45aa35411156e54c8f04ef28fa555",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/shape/point/triangle.mjs": "c18ed75c012f765d30a38d6faac5582ceca2894d8b592a1093a8c90faafc2365",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/shape/point/triangleDown.mjs": "070fed7307a404ce5b2dd8b1976bc471f9b25f797b1047ab8771c41f5351d72e",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/shape/polygon/polygon.mjs": "aaf3b2c61cd5d48bee802dd6e9767ffb3cc5f5c907d88b951748a079d1548766",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/shape/polygon/ribbon.mjs": "3b74d58807f5ecf1c1ff02578be8731a44b2fd7904beaec6f5b7e7d2fbce0aae",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/shape/shape/shape.mjs": "90db23917f1612b2b959f4c08162fa915ad00e2f33cb7d49f58557ec2d674e9a",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/shape/text/advance.mjs": "4b823f971222c2340f3eca70787de0c6738d32ecd00df546ef088a049ca3bbad",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/shape/text/badge.mjs": "f1bb05ff640da3c9589a0220ef434654c690527e39edcb48082b6eebf84b1729",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/shape/text/tag.mjs": "f3529b1b3660b765304b5a41b94d2a2d53a160acd2ab63c4e8258d69a517835d",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/shape/text/text.mjs": "1c2b99beb87210ed9f68509c2dd39feebaae40d1cf72e52baa4ca0a918738d58",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/shape/utils.mjs": "e3cef4b83f6c14e25681ab8787d29cf656d62cf0892c467535e047682b2a9f58",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/shape/vector/vector.mjs": "36185ebae4acb11235ab6a8a6130165aa3638c3c70b88ab7976c189c68f8f1c6",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/spec.mjs": "b048c7697ca7f836277da7c961ea1f10354ebdf733c21b2317bb833a78cbc33a",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/spec/animate.mjs": "c48472eaa0729a1e145013877e450d385234508509a2ad41d28971ada974d717",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/spec/component.mjs": "fbb58a8e58dfecac0c784ee11c6a9eb48a4c296e33b92e160753a2f7f2d8e1d4",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/spec/composition.mjs": "228892c8bd877b95c6a785f8db3ecbb5884bd0355f2a7062c539fc8cab5e7eee",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/spec/coordinate.mjs": "5a02059887a96490f014636cf8a6a0090f0b14e80b227075f105b9204b9ecd6c",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/spec/coordinateTransform.mjs": "cdfe2526d1d582c06d777ec21fb83b07c570ff32c6a75d50d645f5a46b3ea7c4",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/spec/data.mjs": "b41761fbefd1a73a236dc7ae2bf0957d6288b14f7c0e0b887b3c4181ee675213",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/spec/dataTransform.mjs": "b4f6dd757c6302691079e6b49caa3be15f606b747fed379f724df1cd51b97910",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/spec/encode.mjs": "20de9f465bd0f4704124d61d8ff28a60b1feb6839bce4a65ee782bd1553eb167",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/spec/interaction.mjs": "edee43f177cb14ca9ca2e1e7b0b694618b271ff4bf6f90a83e0de022d21e6132",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/spec/labelTransform.mjs": "b4f6a8e0765117e390fc604e6c06335ab6f95e7d5d48f41443ef57d2dab5d2bd",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/spec/mark.mjs": "cadaa7c3031628a39a8437fc98669fb7678047f4bc3c40f781ed20ea491255ac",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/spec/palette.mjs": "dc7f85e528c36bc3e504b97ed2d6f9a4f99b7d04bf5fbfa18b650eae9c21a90b",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/spec/scale.mjs": "1e8d2f2bf69c21aaba0868c2d643f772eba46f0dc5dc58838d71269ab17fd4c5",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/spec/theme.mjs": "7444278b05eec7456f106424c9fb92f1cf12b776688af7218d641fd8c300a617",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/spec/transform.mjs": "1ddb166e282693a3ee42dd9fa800ce5d676e745d01ce6a7998ee84e702241f65",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/theme.mjs": "ba4ef80129f98e9f6483bcd6295bcff738a90d077211aa2cb19087382e02d4ca",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/theme/academy.mjs": "71cdf71eaa935532a6714dad4dbad6c873a0766d9abc66445aeb5d6a846d1b71",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/theme/classic.mjs": "adf6ba8fafeb04e74dd009c6058ff63d92635dcc6e4bc130921a169593defcba",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/theme/classicDark.mjs": "26bd487e48b528957b57ee2c3d17a81e826063006b812aa1788422df4bae1979",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/theme/create.mjs": "f92c725bf79a91a0636528f32786c89097582ca90de966cd8efddef6a86f122a",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/theme/dark.mjs": "abbfb5a88ae4a2aca66ef6d46fd2f8c383ffae00ac7cc9c7c8e8e4e4f4b1b114",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/theme/light.mjs": "ce7dfa4aac609ac4d6dd64c7377aebf517cc67a05ed4355dc665274b5981fe71",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/transform.mjs": "649ee00bf3d86139dfa6dba178b94ff65983684498977cad55c51493b1a48771",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/transform/bin.mjs": "456845f55df9fa78d392a913cf3edb5d1151ce3f579362a57054092017b714f2",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/transform/binX.mjs": "527874a08950120f103e844d1f8ed240744b854ab3b45a72e251255e984a14ce",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/transform/diffY.mjs": "412f91005949b57debc0a95e428cda04dbe19a75abe0a533a8cd811af75553e2",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/transform/dodgeX.mjs": "06140d7136daf2333bc2ea350433f0ca62d9825bf07020d046d51cd396456a91",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/transform/filter.mjs": "bdb614e6e99c1e0939db8d7c8c58f6422d906ae43d72e6ece11eb498c90ad620",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/transform/flexX.mjs": "e2db989084208e95aed95abe24afe701596d4e7c5608d6c296928df7a712b1ec",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/transform/group.mjs": "cc37dea1985a77edae98558095563daa3c626cd3c7200379826fea795eecae9d",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/transform/groupColor.mjs": "0535bf594db0546b32ac51837e9cd4690dc88f235df85e62e6b9923cfe32477f",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/transform/groupN.mjs": "f2b085404dd9f98d154905269a09c6165a9f180458a242ed983fbc213f884bbc",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/transform/groupX.mjs": "62a8a163e3145040046ddf7f3833d5d815b01cdf27f6d68e7699080b4688bd05",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/transform/groupY.mjs": "5e37b39b9d6ab27d65d72d2ba965ad2ae937439cdfd111bc94440f900191a9ca",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/transform/jitter.mjs": "1d566af6a198ec905b3eea1cd43f6612717cf742533d1ea0625e11ff612790ae",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/transform/jitterX.mjs": "843e60c0d9ec0c78db99925dee1fd6777bf60e373f4bf1d26ff2b7dd1cee72f2",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/transform/jitterY.mjs": "868733c50d66295b6981c640d29e8cb820feabcb5c21af449cb5a614bfcfd7b6",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/transform/maybeDefaultX.mjs": "aefd310a3085adc6f647f50c96a997e9357cd888b1eed14bedf233ec8e5fb576",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/transform/maybeDefaultY.mjs": "3b6c429467dba7a3e57c47cf304aa2f5451ca907e52876af042db13cfec3695f",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/transform/maybeFunctionAttribute.mjs": "8c01f172ef6be9eeacfc15e23c14f47e6cf8dbe12b205d060940c609435f8f51",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/transform/maybeGradient.mjs": "557a423815ac10ff90a2a6da88b712cd55519901310d8bdc375ec45079a204c2",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/transform/maybeIdentityX.mjs": "8df122c849e02aeffe0a43169c3d073f2e7d85c183c45b3b320dc5400e6e8cb1",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/transform/maybeIdentityY.mjs": "5268aa366d474980cd537f8d10c92f19ba9c42e465fe23066c0d2835497627c1",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/transform/maybeKey.mjs": "ad9996a1b3772aaa8aee00a6ae153f84944b7252e6e726c8f6686511fef617a3",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/transform/maybeSeries.mjs": "69c2d359c0385b3b38a01339b169e04210874483c556c517596d36e820de50b4",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/transform/maybeSize.mjs": "eb673d86da14cb86fd4afbae8c34097479ec156f5c8d9fa8530c0c7174ffca9f",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/transform/maybeStackY.mjs": "476075f3c1aed3b7f54b7b7c9c3ea50a72912502399cd1c106046c4d586008f6",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/transform/maybeTitle.mjs": "15bcbc0a4d433d76f0416d90b33bbfdde8e65250ad2bd86a7bf85e28b6b9ced0",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/transform/maybeTooltip.mjs": "11868a772cf58b9da06fd664608b9e9e30c679eb502d9c596308210a5c0b6799",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/transform/maybeTuple.mjs": "ac42977d696980a6ff43020fdf27797cc6680b0334088e85a30923ae8b12625f",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/transform/maybeTupleX.mjs": "90c2c56fcb6905e564858051926020719caf2a2a894971ba2f127e28ee8212e0",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/transform/maybeTupleY.mjs": "49da49e1ce438b75c56b60e182998358a290198d41cd585c1026d53b0eb9bfa0",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/transform/maybeVisualPosition.mjs": "1e9868449f0ec6d75b9506aa1342d6c95f973bb879daa37bb46ff8a59867900f",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/transform/maybeZeroPadding.mjs": "713bf3adb554ca5f6183f8789797d4aff719841bc46c45ec65dd42b4337e0a15",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/transform/maybeZeroX.mjs": "cd1adc0694eb551ff4c1df0f3e6bc724aac6f4f972fd836de4c68224e4ede837",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/transform/maybeZeroY.mjs": "c283e90e1e926de0a33108c8d2445ec58272e3f777b01836caa66c6529e890d4",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/transform/maybeZeroY1.mjs": "313025400f30af75c722ac6ce2445417a69ce80d6df3ea5b11b30c2f58beb3d0",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/transform/maybeZeroZ.mjs": "3afe4714889b1ec612fa7cf2da1ee95e78733404fa0a518f3f2e4c52ff12380b",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/transform/normalizeY.mjs": "db9eb3964d5ebdb900d8c862a670bb3f0b433f5e6cf1926b141531759fe63ed5",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/transform/pack.mjs": "505d8e6eabde20ca7d4b55e0813c75b0986d896179d96ca24ed8e8db6ca29817",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/transform/sample.mjs": "53d0eb8d0264d616b23c1379127b440b3d6de5a278e891146aacbeb2ce27aa26",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/transform/select.mjs": "3c7ffa1c74af06bb76c0c75bef6997301248bc7800f67a6c793470936319a81d",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/transform/selectX.mjs": "1657a2763e09f68fadc1140b6cdcbe6d52add156e1ba8763f51bf0edc933f229",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/transform/selectY.mjs": "7564d8821d2edc98225867ddb56fcbebd31b2b40b2e583f4d4c6ca0a993394d9",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/transform/sort.mjs": "82f2247aec23f98c968369878bcda0cf40ef475afe92aabc68d6a8f0d85e5a45",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/transform/sortColor.mjs": "651089ad391833b09c43ce4d65619473f62aa7face528a94fe562abe563549c6",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/transform/sortX.mjs": "8a8efa87f3a9fae00455df7c64c2240ebb4a31f31893cfb3d536e3ad413d7488",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/transform/sortY.mjs": "2d8574d5aad4555d53026b9d5400233941f760accdca5da67299c851073db195",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/transform/stackEnter.mjs": "bd4faef5aeeb8a129aa43d31f26618c6fd059f64b9740ec940f87d4360d5ee98",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/transform/stackY.mjs": "7a3005c531c05cbc5f18d63b46b84cd258ef8226c52eb2b51e29b7dd7ddee6c0",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/transform/symmetryY.mjs": "8cc7ce6f945f42e824a3730917435600ca48c9d7bbf6cc8e33d51deb05337833",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/transform/utils/helper.mjs": "6e6bc7e1f38911442a45ad8a8dd3cb579d1b9ce242ddcfc03c1cdb2b36fef69b",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/transform/utils/lttb.mjs": "a2d789850dfe214fa1f661e61b7fe341e9dfcdb666fcc77db7da020cd8515a3d",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/transform/utils/order.mjs": "91fbe4d90e1d456f2876577bd5e1de525bebd4d9c64dc420a00bbece7563ce65",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/utils/angle.mjs": "500e9f7372fd43c068ab612a1bdcdf56ab60f12b25593859e485c7eb1a5eabf5",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/utils/array.mjs": "2d1c8cce3c55c35e202a7bff6f4a6599eb193641c23a4f4adadbfb73f86ed44c",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/utils/bounds.mjs": "ca3a11d52168d68c6f919ddb5adbe30b48bd075deb065dbc3ba4036bfa16acad",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/utils/color.mjs": "ca95830848ab0f1aef40a03ae369feb157d1e5031ceabe379330d00cc57a35e4",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/utils/container.mjs": "ba249ce0b87e710d7ac568ade66296a22f8660c08ca0145ed93e6af5cafd1aa4",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/utils/coordinate.mjs": "b56700d2ca25bacf9bfbd96d019d4fbfe56bcb35964c7357193ec5164c2c6453",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/utils/createElement.mjs": "da259c36c7331e400f35cb17949993def1a26a932202bc6ecf37cc3536798a76",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/utils/dateFormat.mjs": "0c14f65d0cdc566515cb159a4f0e6df25163647de406274493b0320772d95776",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/utils/event.mjs": "90cddfe0f077e6f79cc37fc86978f4563a093fa5a47bf37f5114a1f8dbbce929",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/utils/flow.mjs": "432fca474dcc5281f79d6ddbe40862f694292a50e0f3e37bb809cf4251924aca",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/utils/helper.mjs": "cd94344179c6c7b7c97c96883a3612663a202846429976f2fb2b08eee37e60f0",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/utils/lru.mjs": "9e5655f435206f1996989028a079bd41f3f2c518f749eba4eaf793917f11939d",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/utils/mark.mjs": "39a5243aaae33f33c2a186a5cd7672df766fee786d900a52e71a2bdc0b2257a9",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/utils/marker.mjs": "bb7438218b93a14f0d6d9572ff31aa09ecb3580b6fbb8f100bed8e0a9bd0a2a9",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/utils/number.mjs": "2956d807730cc52aecd72697d981f569bdd709b112327d79441f7ed80dcaed58",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/utils/scale.mjs": "9f596bcdc76477676770dd70caf6c90d2ba91f141b6c7d8a46b38e5368bc726f",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/utils/selection.mjs": "54c6c6a7b1c1868ab43db04839be4d991e5696c08f631e80bfb66d0f43defe32",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/utils/size.mjs": "06e4587ba3d2e9a363c746b169db6be4f8d6d0efd6d73c93bc484bf5129ff4d3",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/utils/string.mjs": "4d9985d39d55a9c5f1713f46c278636efaffb2733f4dc2236bbd45ea6433e062",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/utils/style.mjs": "bd8644603b051d4cc47093f107d2743480c014cf43f665abc5b65f8ed25534a3",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/utils/treeDataTransform.mjs": "688e2c4d53fcd4c7ea265591257a5a98b4ece016b32183903f67054736155b68",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/esm/utils/vector.mjs": "c04fb3e86125fffb36ae34abc0caf38ee3a2c86ef67ca9cc261d73268301cf0e",
+    "https://esm.d8d.fun/@antv/g2@5.2.12/denonext/g2.mjs": "2df16c937f4436f2dd1081cd47698dce2e81098cab36f95fcdd99a30a44d0684",
+    "https://esm.d8d.fun/@antv/g2@5.2.12?target=denonext": "3c9ffb5fd4f09a1473a8df52488932d0fab6485dfd3849b8a5e564716c78fdd5",
+    "https://esm.d8d.fun/@antv/g@5.18.27/denonext/g.mjs": "2e3ca3e4fca5b5337920ebb433ce5c90931fc5237ba5e7995954aa108803f6de",
+    "https://esm.d8d.fun/@antv/g@5.18.27?target=denonext": "a81a858ca88b37d58cd5e6c8726fd0107984bf51bd18cf3c569f17370dd35c0e",
+    "https://esm.d8d.fun/@antv/g@6.1.21/denonext/g.mjs": "d2789af4fc90995163282b99ecd92b6c421ae8aad8503790dd0f413bf367b148",
+    "https://esm.d8d.fun/@antv/g@6.1.21?target=denonext": "f947d63983c3c9e624f0ec778ed98882d37e1dddb2e5b92fcf00e6137e2eff24",
+    "https://esm.d8d.fun/@antv/scale@0.4.16/denonext/scale.mjs": "4c1d852e2323eba1e7427a7f262de7795d290e2ed5919c2f53432d4232d8b52a",
+    "https://esm.d8d.fun/@antv/scale@0.4.16?target=denonext": "3970e698602a0bf02945a3c1bb833bb7b6313c9f647624c50eab5aa0e039a4d2",
+    "https://esm.d8d.fun/@antv/util@2.0.17/denonext/util.mjs": "c0ef7de8ba14e02bc2cfb9759df328b17214a193fa3798a60b8e19624e7f4123",
+    "https://esm.d8d.fun/@antv/util@2.0.17?target=denonext": "60560b02ee3c88a304e85d60ad28984f9f96ed569cd89d7ea321e8f41d8eefde",
+    "https://esm.d8d.fun/@antv/util@3.3.10/denonext/util.mjs": "181cc483e50bba79b33d85569e83bebf1a28692d39c01ccfeaa610ee8811d0a8",
+    "https://esm.d8d.fun/@antv/util@3.3.10?target=denonext": "0467779b6a78a574284d9e53a7b2461af181f46ad9fa6a41b916aababc844b4a",
+    "https://esm.d8d.fun/@antv/vendor@1.0.10/d3-array?target=denonext": "ad63d111fb7f59c0520cf3268b584b5cbf85667db0af88d5cd6a39e9a5b0e1a4",
+    "https://esm.d8d.fun/@antv/vendor@1.0.10/d3-dsv?target=denonext": "48b04795b82a5f3429769a1445ae703e98686f83428ac787d77114f1bc8fdcce",
+    "https://esm.d8d.fun/@antv/vendor@1.0.10/d3-force?target=denonext": "f4fcdfcf69854ba7f3a61dbb68e3ad02953c8e1258ca235fdb97320b0125be54",
+    "https://esm.d8d.fun/@antv/vendor@1.0.10/d3-format?target=denonext": "76772ae7fdfd47e87c087ad34ff6594c0b6ea798d53ad214736a7226dead5d65",
+    "https://esm.d8d.fun/@antv/vendor@1.0.10/d3-geo?target=denonext": "50e582c9c85f395f765724ae09e9d875acb0b27540e359a7dfa51373ceafbbb9",
+    "https://esm.d8d.fun/@antv/vendor@1.0.10/d3-hierarchy?target=denonext": "f404c21c5d836cdd32eb68abd381202afa797aaab339ce2550472b5f0a8ce338",
+    "https://esm.d8d.fun/@antv/vendor@1.0.10/d3-path?target=denonext": "8f3ba8550a5bcd3d444bdab71e5e0cf5a35d38b626fd3da922d78a2de46871ec",
+    "https://esm.d8d.fun/@antv/vendor@1.0.10/d3-scale-chromatic?target=denonext": "242c7f12d97d8f4592095d68f9eed2039282eff5963d6a22ae1e5cb73d2cb5f9",
+    "https://esm.d8d.fun/@antv/vendor@1.0.10/d3-shape?target=denonext": "a8343a1d33f7df7653a1c9b9da37dadf97089ac20edf7e0f8744e65d1aac930b",
+    "https://esm.d8d.fun/@antv/vendor@1.0.10/denonext/d3-array.mjs": "2cf7f7c526eeb5d76fc4a624571bb651a162f0e9f3d4b42e114cdd914099f9df",
+    "https://esm.d8d.fun/@antv/vendor@1.0.10/denonext/d3-dsv.mjs": "6a937e0d90224ecb1402491124a6d31ac2dafdf94b0c3642bde86642d494d187",
+    "https://esm.d8d.fun/@antv/vendor@1.0.10/denonext/d3-force.mjs": "a28da529e74e216e1e1c554d99f86a01b564088ad80dab9288796e2c433607a2",
+    "https://esm.d8d.fun/@antv/vendor@1.0.10/denonext/d3-format.mjs": "3e0e2534343fe82565fafeec4148f2160bc5a94a5ddd071aa8a7527cac8b7e1c",
+    "https://esm.d8d.fun/@antv/vendor@1.0.10/denonext/d3-geo.mjs": "466d9622c0005d54a2d60b1889b831abea9afa9f9c9e2155f9823eca85fc33cc",
+    "https://esm.d8d.fun/@antv/vendor@1.0.10/denonext/d3-hierarchy.mjs": "3e75726632383e8820491b6898883d9998db7f1b8c1692436832bea9281b0249",
+    "https://esm.d8d.fun/@antv/vendor@1.0.10/denonext/d3-path.mjs": "e83d5e4590352b0e16dec07fdd95535f9cc4905a4445b171db5c3396c32bd03b",
+    "https://esm.d8d.fun/@antv/vendor@1.0.10/denonext/d3-scale-chromatic.mjs": "ddfa926c47e5d1e860152c474dd4bd8a24dacc50eaced2b1d1d4a4d4fb65f91b",
+    "https://esm.d8d.fun/@antv/vendor@1.0.10/denonext/d3-shape.mjs": "a8a13222833dff09631421b428566142195a3e16b6ebd494eacc5c5ecc6265c1",
+    "https://esm.d8d.fun/@d8d-appcontainer/api@3.0.47": "6f44e26f9101c9c00c374a01defa883ae9db7c851ef2b8b130cb6c2d51a41b59",
+    "https://esm.d8d.fun/@d8d-appcontainer/api@3.0.47/denonext/api.mjs": "778329c130f21a547d6726e8e13fa680a74faecd96e4d953acb481724f8db7be",
+    "https://esm.d8d.fun/@d8d-appcontainer/types@3.0.47": "69a9caa934400649c91ff4d12f89328d53fe98183829e40b5ae34605cf82e962",
+    "https://esm.d8d.fun/@d8d-appcontainer/types@3.0.47/denonext/types.mjs": "44efd25cb0ad7942ad29d520b5f3296a908d7cefbe85382bcc45e3b3c6c9624c",
+    "https://esm.d8d.fun/@deno/shim-deno-test@0.5.0/denonext/shim-deno-test.mjs": "21298ee12e8add3e8efe527aa1dd2a4fd029eb1876f5a59107bbe62b3969e282",
+    "https://esm.d8d.fun/@deno/shim-deno-test@0.5.0?target=denonext": "503b73de1a14bd33782220e11fa2b33e9c87d574ac793e7addf1466c5436e66a",
+    "https://esm.d8d.fun/@deno/shim-deno@0.18.2/denonext/shim-deno.mjs": "819d8ac34fdaf60658cf03d137f14adaff3f13a279ffd79cd8797d84a6ac46ab",
+    "https://esm.d8d.fun/@deno/shim-deno@0.18.2?target=denonext": "ffa3ca347bb6b6530720158f307a2e31b16728fbb52e6432254a07d52fcbc404",
+    "https://esm.d8d.fun/@socket.io/component-emitter@3.1.2/denonext/component-emitter.mjs": "3c6c5f2d64d4933b577a7117df1d8855c51ff01ab3dea8f42af1adcb1a5989e7",
+    "https://esm.d8d.fun/@socket.io/component-emitter@3.1.2?target=denonext": "f6ff0f94ae3c9850a2c3a925cc2b236ec03a80fc2298d0ca48c2a90b10487db3",
+    "https://esm.d8d.fun/@tanstack/query-core@5.67.1/denonext/query-core.mjs": "3001acc66d3efeab4900278cf630cb56ba23ac70cd77f7e0c413abb8a1f223f3",
+    "https://esm.d8d.fun/@tanstack/react-query@5.67.1/X-ZHJlYWN0LWRvbUAxOS4wLjAscmVhY3RAMTkuMC4w/denonext/react-query.mjs": "348ce86a855f9a6b3de66776043ec9253cbb3508a6a29ed0a820d4808e813846",
+    "https://esm.d8d.fun/@tanstack/react-query@5.67.1?deps=react@19.0.0,react-dom@19.0.0": "cb1fa6dc8e8c85d9676444c6b747a67b7bf03ce5ebf2417db33f00c720334477",
+    "https://esm.d8d.fun/antd@5.24.5/X-ZHJlYWN0LWRvbUAxOS4wLjAscmVhY3RAMTkuMC4w/denonext/antd.bundle.mjs": "2ea332984a059e6e031da8ce2ab71063189b6a365bc9c606599c3b1169d563cc",
+    "https://esm.d8d.fun/antd@5.24.5/X-ZHJlYWN0LWRvbUAxOS4wLjAscmVhY3RAMTkuMC4w/denonext/locale/zh_CN.bundle.mjs": "d683f405f3338aa28b6a5553add7828966056ff0f82907a8c39e9668843d3fcc",
+    "https://esm.d8d.fun/antd@5.24.5/locale/zh_CN?standalone&deps=react@19.0.0,react-dom@19.0.0": "6a243dab340465e9050a94907c49305eb31064f9b8b0c2d55aee9c9fe7f80600",
+    "https://esm.d8d.fun/antd@5.24.5?standalone&deps=react@19.0.0,react-dom@19.0.0": "fc4c18d3b72d269dcbc638505bf3d47df4bf86dd7b108913ae28eecc094162fb",
+    "https://esm.d8d.fun/asynckit@0.4.0/denonext/asynckit.mjs": "4ef3be6eb52c104699b90ca5524db55ec15bc76b361432f05c16b6106279ba72",
+    "https://esm.d8d.fun/asynckit@0.4.0?target=denonext": "c6bd8832d6d16b648e22d124a16d33c3a7f7076e92be9444f2e4f6b27545708d",
+    "https://esm.d8d.fun/axios@1.6.2": "6ed5cb6f7f773d035e3a7d6097a25361d77c2d6f63b9df6d9ba9e2af5a4f4a3e",
+    "https://esm.d8d.fun/axios@1.6.2/denonext/axios.mjs": "cecb6586bc9779aeb64fb22e87824508e49e178fd2ddf2adaa138a5dc6b6945e",
+    "https://esm.d8d.fun/axios@1.6.2/denonext/lib/adapters/http.mjs": "69f9d3b5924fe24d68867eee0de817aecc19ff21e6543c74552fc6cf59a39024",
+    "https://esm.d8d.fun/axios@1.6.2/denonext/lib/adapters/xhr.mjs": "04792efb6d22afea17a7e66c7e97b2adc8aea49484c5ea2947072db70c8e8bb8",
+    "https://esm.d8d.fun/axios@1.6.2/denonext/lib/cancel/CanceledError.mjs": "762e5f2015f3201d1dfd24e7c1a1a545ccf3336fc7d9e63bb27dcdaa61d20bf8",
+    "https://esm.d8d.fun/axios@1.6.2/denonext/lib/core/AxiosError.mjs": "d36240dc9f49522abe5d21dfcfa6caa8e65cdf7c3b9290dcd10daeca9df2dc33",
+    "https://esm.d8d.fun/axios@1.6.2/denonext/lib/core/AxiosHeaders.mjs": "c0687178ffa608c8e04e0711391c96f6a11d607412f063e3fa1f307ae73637e5",
+    "https://esm.d8d.fun/axios@1.6.2/denonext/lib/defaults/transitional.mjs": "4428dbf935255c18443a3804f41f87b70b1a4060239a0caf0fdbf6eb8bb00370",
+    "https://esm.d8d.fun/axios@1.6.2/denonext/lib/helpers/AxiosURLSearchParams.mjs": "bea317342f2cb1a5c3eb006f4cd31d7f1195f2fc62cd1fce1a08148dbfa72f07",
+    "https://esm.d8d.fun/axios@1.6.2/denonext/lib/helpers/bind.mjs": "e1ce8050e9a84e0ca27e7f8cd0bb1e57d45f7ef9822b2926dce2bd9799b44f39",
+    "https://esm.d8d.fun/axios@1.6.2/denonext/lib/helpers/toFormData.mjs": "086b098a98b5032d9fba6d30cf04e751aadae9b1149228a92d0ca2096f688100",
+    "https://esm.d8d.fun/axios@1.6.2/denonext/lib/platform/index.mjs": "f55302665777b17a004f2fc6cd032d28cb7dacce776d26a5ceb7da7321cca3e1",
+    "https://esm.d8d.fun/axios@1.6.2/denonext/unsafe/core/buildFullPath.mjs": "657440a4a7773f270cee91477799291e8408e5de021780e9cc42147bc9aa205e",
+    "https://esm.d8d.fun/axios@1.6.2/denonext/unsafe/core/settle.mjs": "11d7c630bff6c52985d937b932c4df91135d8e5a2046212b8ce66d4a6c7121df",
+    "https://esm.d8d.fun/axios@1.6.2/denonext/unsafe/helpers/buildURL.mjs": "015a6cc012e59edf6904f46f7a39a5b631ead8c19f99dc57993b5f3b97d11dc5",
+    "https://esm.d8d.fun/axios@1.6.2/denonext/unsafe/helpers/combineURLs.mjs": "3a4a1b972a35063b5a9ee7889410190c67b8b8f8eec810cb536d0df5a5b610df",
+    "https://esm.d8d.fun/axios@1.6.2/denonext/unsafe/helpers/isAbsoluteURL.mjs": "98b0cdecb8b376ed6add4182d3f76fe1d25ab5e3f7be11703f7598243199901f",
+    "https://esm.d8d.fun/axios@1.6.2/denonext/unsafe/utils.mjs": "e71bb35332a66d86c09e2fc7097453e9390f23b411262e199fc05eb363111dbd",
+    "https://esm.d8d.fun/axios@1.8.4/denonext/axios.mjs": "a0c3d648353c6a1b9864f1067ff9d366b91ccf2c8a413ec30a6b073dea05366b",
+    "https://esm.d8d.fun/axios@1.8.4/denonext/lib/adapters/http.mjs": "1c7e3b34ddafb39f9b36111dc81ab83a4b1dfed316efc7c9971a534f0c161e7f",
+    "https://esm.d8d.fun/axios@1.8.4/denonext/lib/adapters/xhr.mjs": "443f6f99410af813126a7b3b004a7fe4d3ce1eb51fb940b15a3fb0932773ff78",
+    "https://esm.d8d.fun/axios@1.8.4/denonext/lib/cancel/CanceledError.mjs": "6432ce6e9d09faff4439c0c5dfa1d44a79cea9901eb5c1894c7c7497e791e0fd",
+    "https://esm.d8d.fun/axios@1.8.4/denonext/lib/core/AxiosError.mjs": "2366d9c8250a030e6d82cf72db361d0df9a4311e785e2dd5dd34364c4690cbd3",
+    "https://esm.d8d.fun/axios@1.8.4/denonext/lib/core/AxiosHeaders.mjs": "f05eb0c07bf1f6d418dad3a3e310070822e5d7059e07c9cc232f365813d1f58e",
+    "https://esm.d8d.fun/axios@1.8.4/denonext/lib/core/mergeConfig.mjs": "271e93496a6e07b99694817bbcd619c4862b839f5c25538d736168697d09b846",
+    "https://esm.d8d.fun/axios@1.8.4/denonext/lib/defaults/transitional.mjs": "cf97aea57cebc35857e915fa922468b267947fbd8c8ee6cecc3d9878d2429b4e",
+    "https://esm.d8d.fun/axios@1.8.4/denonext/lib/env/data.mjs": "b7d5dde239c8a22e820d1e42b045a19d981681e0d7b3eebd3b05f8caf1523455",
+    "https://esm.d8d.fun/axios@1.8.4/denonext/lib/helpers/bind.mjs": "6f9b25a0abdcbdee99eea549869d4357151d21d8a004174fe5c135f82fc61412",
+    "https://esm.d8d.fun/axios@1.8.4/denonext/lib/helpers/parseProtocol.mjs": "3268a599f6fa29d6ce6b5e8ee2a61999351b5e414b42cc584c7ee80eae248802",
+    "https://esm.d8d.fun/axios@1.8.4/denonext/lib/helpers/progressEventReducer.mjs": "308616bf82ad4621ed466b2355025d108d40daac8e95831b56018fbe96c2ac1a",
+    "https://esm.d8d.fun/axios@1.8.4/denonext/lib/helpers/resolveConfig.mjs": "5e7d2668d1b6d93c6f860aba237e92c2b07b6f0dfedca3b9e304f1dd4578e224",
+    "https://esm.d8d.fun/axios@1.8.4/denonext/lib/helpers/toFormData.mjs": "9f8a95c8edd57d5e0acb598c56bfac9efae1e5ed39d6c73f94cebc603222b74d",
+    "https://esm.d8d.fun/axios@1.8.4/denonext/lib/platform/index.mjs": "8022c68893946f6714646f45cb87e5a8d58ac966e1d801a7abeb89f40adbb2fc",
+    "https://esm.d8d.fun/axios@1.8.4/denonext/lib/platform/node/classes/FormData.mjs": "3af5a3b503dafe0b26a94454685a1a68da3b78540fa1ca65a85814022d725d14",
+    "https://esm.d8d.fun/axios@1.8.4/denonext/unsafe/core/buildFullPath.mjs": "bd725f6f2e86698888721717727752302d0ae5153bf6216ba5eb6a716692fee2",
+    "https://esm.d8d.fun/axios@1.8.4/denonext/unsafe/core/settle.mjs": "131ebbb5c8592f9505988f7cc8d33b3905a5ea06425db72723d0164be0e19f0c",
+    "https://esm.d8d.fun/axios@1.8.4/denonext/unsafe/helpers/buildURL.mjs": "07eee5bbb02b63ca69bda19d9724480724b82fdc8a75a47c46a1f6dfe985f159",
+    "https://esm.d8d.fun/axios@1.8.4/denonext/unsafe/helpers/combineURLs.mjs": "a2c79317fdc707709b83dc7d3166cab69c3f9dca5ad5c4612ff1e6b29912dee8",
+    "https://esm.d8d.fun/axios@1.8.4/denonext/unsafe/helpers/isAbsoluteURL.mjs": "df75312b93206485ee501d2b51a5784b1cbf76527cb29803a7fadf1238898881",
+    "https://esm.d8d.fun/axios@1.8.4/denonext/unsafe/utils.mjs": "ba8669ad91f8b94b0796e13f03605a620d7fe2d05419670cfa2f6793fd5707cf",
+    "https://esm.d8d.fun/axios@1.8.4?target=denonext": "3652480f46bbae591b9617f4f039537675c72ed4d8b7b44019d7bfdf173102ae",
+    "https://esm.d8d.fun/bufferutil@4.0.9/denonext/bufferutil.mjs": "13dca4d5bb2c68cbe119f880fa3bd785b9a81a8e02e0834dae604b4b85295cd8",
+    "https://esm.d8d.fun/bufferutil@4.0.9?target=denonext": "e32574569ab438facfcc3f412c659b0719bbf05477136ca176938c9a3ac45125",
+    "https://esm.d8d.fun/color-name@1.1.4/denonext/color-name.mjs": "3b5b10fc0cd9f96d56c81cc7559a2106ff64a401a8100cd33e1f4b68e677c562",
+    "https://esm.d8d.fun/color-name@1.1.4?target=denonext": "55709266f414e5115ac52d6831ba4951f7c8b9ac663f0f79ddc8ed782b4fb735",
+    "https://esm.d8d.fun/color-string@1.9.1/denonext/color-string.mjs": "46edab8cecd0440cff0a82ae330d9314ab6da3b6c67680018b9f916e3aa43749",
+    "https://esm.d8d.fun/color-string@1.9.1?target=denonext": "f5b65597951151faf1076eac91abf7703687b71236368777b1765e26241837d7",
+    "https://esm.d8d.fun/combined-stream@1.0.8/denonext/combined-stream.mjs": "364b91aa4c33e5f0b4075949d93a3407b21a8695031e7c2be29999d588f9ca2c",
+    "https://esm.d8d.fun/combined-stream@1.0.8?target=denonext": "a0c89b8b29494e966774c7a708e33cc2df16a0bbe2279c841d088e169e7ab3c4",
+    "https://esm.d8d.fun/cookie@1.0.2/denonext/cookie.mjs": "6c4f976d4d933ff2d7afc1f6ee7af134108c9858f5a2aadc6189084bd5972275",
+    "https://esm.d8d.fun/cookie@1.0.2?target=denonext": "04dabbcad7a7a96d841d6b21f00cb20191fb29339781237088d8edabad52df66",
+    "https://esm.d8d.fun/d3-array@3.2.4/denonext/d3-array.mjs": "6519a5e73c89994e9e5b3959b717052ac2fa998e81c4b911b36f6fe3bb506798",
+    "https://esm.d8d.fun/d3-array@3.2.4?target=denonext": "4266074d204ab57fcfbe3b73d6259dd41e56d864ca72d20b13e159414fe8c5f5",
+    "https://esm.d8d.fun/d3-color@1.4.1/denonext/d3-color.mjs": "af99ed3105d196c504d729ffef139cda0a91a9d65e2cdd825a353c558c2f100a",
+    "https://esm.d8d.fun/d3-color@1.4.1?target=denonext": "a7fd97aae72e5a75309bbf4c56bdc885b5f883dfe9dbfad6feaaf9efc7b2fd56",
+    "https://esm.d8d.fun/d3-color@3.1.0/denonext/d3-color.mjs": "2e852a0327b74fbcf7cafd66913833423b736b8bf2e163801e74cee61624073a",
+    "https://esm.d8d.fun/d3-color@3.1.0?target=denonext": "c44248d4b33f57d8791c0994e1eee000e5bbb4a9939d11e2baf5dfa669cab805",
+    "https://esm.d8d.fun/d3-dispatch@3.0.1/denonext/d3-dispatch.mjs": "b36be2eed6f07b517775df12bde28cb0e778b9a7df04974ce9cb99f7b322a2d6",
+    "https://esm.d8d.fun/d3-dispatch@3.0.1?target=denonext": "75f32f2a0e7cdddbe15573f1a802fb6bdc46247d038a4b1a2ebde2ad86848c56",
+    "https://esm.d8d.fun/d3-dsv@3.0.1/denonext/d3-dsv.mjs": "81798ba83d26850ed74f8c336918d283ee2d3a9cf36cd330a35b770376fa04ef",
+    "https://esm.d8d.fun/d3-dsv@3.0.1?target=denonext": "df2fe1373d8adeb64c44bd1cee552acd6a9a9b6314b2288a42679b3e6638c47f",
+    "https://esm.d8d.fun/d3-force@3.0.0/denonext/d3-force.mjs": "daa176ee52b7f8f113d4b0c5e31b95bd99b0cd0cd66ca5c7445ab4bdf7c5cb71",
+    "https://esm.d8d.fun/d3-force@3.0.0?target=denonext": "1a35f3dd1ff127dc27f2b009574d90304693b0a516eac9583df8d0333d61fd8e",
+    "https://esm.d8d.fun/d3-format@3.1.0/denonext/d3-format.mjs": "de06e6fe57436ec84c146e27a2ccaf444fc5dab14b091afe1e95d985bdfd8219",
+    "https://esm.d8d.fun/d3-format@3.1.0/denonext/src/defaultLocale.mjs": "dfb5be55e86889631b0db011102c240ff60900347f80df21fe4d9d88ad9d2f06",
+    "https://esm.d8d.fun/d3-format@3.1.0/denonext/src/exponent.mjs": "3fabc54097b16d594809a42288cbbbb9e3f0b26e88c83633cae2ead4b858e7d4",
+    "https://esm.d8d.fun/d3-format@3.1.0/denonext/src/formatDecimal.mjs": "22184045739a8af17838956f85c7b747c4b8ad4e744e338d721a96a25c1805a4",
+    "https://esm.d8d.fun/d3-format@3.1.0/denonext/src/formatGroup.mjs": "bbbcd628338ae27949817f50b2fef27ec808eacbbbc022cb29d62892cc778195",
+    "https://esm.d8d.fun/d3-format@3.1.0/denonext/src/formatNumerals.mjs": "546ae2fd688a53c7cab1048cc3a4d8e1f9f64546a0d8021e061bc6f30b997d0a",
+    "https://esm.d8d.fun/d3-format@3.1.0/denonext/src/formatPrefixAuto.mjs": "d4058419991cfc1ff535c5d2c3e556788d935ae9d886e174a306168cb9476cb8",
+    "https://esm.d8d.fun/d3-format@3.1.0/denonext/src/formatRounded.mjs": "3fe4e8c81bc53cc82ba22564eca9b51f10e59c2a6028b6bf53a24884d9200925",
+    "https://esm.d8d.fun/d3-format@3.1.0/denonext/src/formatSpecifier.mjs": "d0fce28aa8ff576e938c0817f18ca2e5824fa9a29ec3443fab92851472efac27",
+    "https://esm.d8d.fun/d3-format@3.1.0/denonext/src/formatTrim.mjs": "bc4bad9a5827dec90c64557083eada6653ef531c64699eec4b8fc4d2fef4f69b",
+    "https://esm.d8d.fun/d3-format@3.1.0/denonext/src/formatTypes.mjs": "3556878fb52315588adb3c8eb91e16c98e7d91371ece737121f52143a0e7304b",
+    "https://esm.d8d.fun/d3-format@3.1.0/denonext/src/identity.mjs": "1a5237ada3ebfa0877cfd22b0d61bd5e90177879e0b9bcb31f72f4959ccdce3f",
+    "https://esm.d8d.fun/d3-format@3.1.0/denonext/src/locale.mjs": "18051fb152a9f4f33bbdb54249bfcee67126a47dfe469fd54c44e108b6b13ba9",
+    "https://esm.d8d.fun/d3-format@3.1.0/denonext/src/precisionFixed.mjs": "9db700a7ea53bd880348a5c26c89c9536a724c892cd7cb153ef2133e6d7bcc34",
+    "https://esm.d8d.fun/d3-format@3.1.0/denonext/src/precisionPrefix.mjs": "98157877e57a9ec2470e574c5cceb12da5e3344e0425e30aa9b19da6a8a6ff7b",
+    "https://esm.d8d.fun/d3-format@3.1.0/denonext/src/precisionRound.mjs": "5ed90c6636ea737e6bc1d522eb4ba60fc389ba6535dcb0c64112bf8ef843d21d",
+    "https://esm.d8d.fun/d3-format@3.1.0?target=denonext": "c0d64767d08d2e6bc97243e6ceaa1f833f6b172ab8a0173e27ab11466c546aea",
+    "https://esm.d8d.fun/d3-geo@3.1.1/denonext/d3-geo.mjs": "c98c715c798ead807108505d44119407cc2c574bd9fd94c1d7594d4dfe1972d5",
+    "https://esm.d8d.fun/d3-geo@3.1.1?target=denonext": "67851d96be856de5fc4629f04304599d0632e5f9eaaa04501a70c01c58e59828",
+    "https://esm.d8d.fun/d3-hierarchy@3.1.2/denonext/d3-hierarchy.mjs": "8d5d1f8a102484e7c7c8d1244b782333d8cf89b0453e0ed40f40f82dda8ace31",
+    "https://esm.d8d.fun/d3-hierarchy@3.1.2?target=denonext": "41ed0806cba567d4cf8a5d84e2a8da7c5fc480f9b1b296e9cc8955d0aac46aef",
+    "https://esm.d8d.fun/d3-interpolate@3.0.1/denonext/d3-interpolate.mjs": "32e4fd3b94b318bce3c39726e50cbeff6e475e989cccb24072a6331cc2fbef6f",
+    "https://esm.d8d.fun/d3-interpolate@3.0.1?target=denonext": "d919d1489c7be9cc2f235b39de7b446c77c912c4069d0cf1ca0a90379cce2968",
+    "https://esm.d8d.fun/d3-path@3.1.0/denonext/d3-path.mjs": "d79581e49a2f7dc620bfc74299b070d858b012456752da7f12315202a4ba7184",
+    "https://esm.d8d.fun/d3-path@3.1.0?target=denonext": "0efef59a6e67de9fe8cf8053cada892ab342eda9820e5061677c75391a11ef2e",
+    "https://esm.d8d.fun/d3-quadtree@3.0.1/denonext/d3-quadtree.mjs": "63669e70a8b12b5b242985137283e55688903456bd96757970835edeb6830d81",
+    "https://esm.d8d.fun/d3-quadtree@3.0.1?target=denonext": "d820b5c5854cf78dfd2793468094f5bb2c55c51b1d890f8503f02f980685fcd7",
+    "https://esm.d8d.fun/d3-scale-chromatic@3.1.0/denonext/d3-scale-chromatic.mjs": "08342f70bf86d6e237b6c8fe01e8de4de5ea01f4b75697365766cb9dea3c4459",
+    "https://esm.d8d.fun/d3-scale-chromatic@3.1.0?target=denonext": "8cddeaf82214dbde006b6bdabb038765d5f501bac05f05349e59070b22f806ce",
+    "https://esm.d8d.fun/d3-shape@3.2.0/denonext/d3-shape.mjs": "650a67568928c8bcd1983a48623dae723c2ce2e009b5553d9567043046ff6ad6",
+    "https://esm.d8d.fun/d3-shape@3.2.0?target=denonext": "08207dbafaa679cbc1346c3c290cf3a179a32b029b5e958639ede04881285133",
+    "https://esm.d8d.fun/d3-timer@3.0.1/denonext/d3-timer.mjs": "eb0b5bf822496c4aad63d2ca4832ec29c8a015467f5a685184bec78d2e3e5003",
+    "https://esm.d8d.fun/d3-timer@3.0.1?target=denonext": "5ee90bdf7721811a8f2020e3779e994dfe881d0c8db53e4ea0f535a032516bc1",
+    "https://esm.d8d.fun/dayjs@1.11.13": "89c34b8b3f7b970708114b4d264c9430c30eb0c2eab1419410c77ffefa18fe2c",
+    "https://esm.d8d.fun/dayjs@1.11.13/denonext/dayjs.mjs": "a6d8258bec464149ab2c9ae26e4bd3736897828586b03f8fea45403080bf8a80",
+    "https://esm.d8d.fun/dayjs@1.11.13/denonext/locale/zh-cn.mjs": "6abdbc636540021cc0a7a01ecd3db2abb114aa9d68479e26e739f5e1fa686389",
+    "https://esm.d8d.fun/dayjs@1.11.13/denonext/plugin/localeData.mjs": "50da72eeac69ca12fc277fae9405a58f711a4b24b6de60377112c96d1cb3610a",
+    "https://esm.d8d.fun/dayjs@1.11.13/denonext/plugin/weekday.mjs": "263a7dbfb1b0aa98ae51cc09d2cc5fcddec453fc329c6e1b8decd9143cca5a41",
+    "https://esm.d8d.fun/dayjs@1.11.13/locale/zh-cn": "eef17bda14d105b6c5919f98fb4be665486340b26733e5b5f94c20157c565fc6",
+    "https://esm.d8d.fun/dayjs@1.11.13/plugin/localeData": "174642f963526c115142279d5b1552b694f67adb9433f6dfe0ca009f5422f340",
+    "https://esm.d8d.fun/dayjs@1.11.13/plugin/weekday": "274e276260fc791f3fd48ae38811db328db86aaa403dbb8d802149870fa5b156",
+    "https://esm.d8d.fun/debug@4.4.0/denonext/debug.mjs": "3077d1ff15cfc5b7baee65b0c00b3200aef8ab51ddddfa960972957c347c1cee",
+    "https://esm.d8d.fun/debug@4.4.0?target=denonext": "dc29873ca5518385fcbddb2b2fa0f3b31dc6463ba52bdd790818683b9dbdc6ad",
+    "https://esm.d8d.fun/delayed-stream@1.0.0/denonext/delayed-stream.mjs": "051a3501b7b3d3c593b78a2c7305093a8e363c518cd156f1a77117185e312abe",
+    "https://esm.d8d.fun/delayed-stream@1.0.0?target=denonext": "d363b81e01f4c886114df14aa660c1a938bbb4be851ff12132260bed0db6126e",
+    "https://esm.d8d.fun/engine.io-client@6.6.3/denonext/engine.io-client.mjs": "d127a167771015e459e79fb0eb38ee99601ae96ae98924ee407dd77d0ee8be0a",
+    "https://esm.d8d.fun/engine.io-client@6.6.3?target=denonext": "d97129d74541438ec8167b8232ff764b408b8bf4c065924c60823795fa3e038d",
+    "https://esm.d8d.fun/engine.io-parser@5.2.3/denonext/engine.io-parser.mjs": "dfb40060c00806566e236f3112b950f43fa6b5e3a142f14ba2e83ad651f4a451",
+    "https://esm.d8d.fun/engine.io-parser@5.2.3?target=denonext": "1dd633858ff4fd2affd2343c0f16f4d0727524919f53f0a5cf240baefd3c91fd",
+    "https://esm.d8d.fun/eventemitter3@5.0.1/denonext/eventemitter3.mjs": "8f5abddb39876fcb8a4ea0c464c7de3755b244f99e4149b1524e77053a7f71a7",
+    "https://esm.d8d.fun/eventemitter3@5.0.1?target=denonext": "70d8b94c6397b6df7daeb699ebcf8d78a00c961d5ea9abd48674805bcfba040a",
+    "https://esm.d8d.fun/fecha@4.2.3/denonext/fecha.mjs": "9fe6d46ddd51bb39319ecf0fb99e016cd5377c12d1e9cd19c0e47780b985f562",
+    "https://esm.d8d.fun/fecha@4.2.3?target=denonext": "6ae1b3f57d6a7ca6963722288db7b6ed90f94321c6a6e53fcf0ab69b6bb56047",
+    "https://esm.d8d.fun/flru@1.0.2/denonext/flru.mjs": "39b49dd705cbe2e8054a56e79ae0cf6371f085d05aef3d39e52cf681220d2f94",
+    "https://esm.d8d.fun/flru@1.0.2?target=denonext": "3d16bf52dfc96b31648b7ff08224581977d9e7c5861fab46f85261c4538001c4",
+    "https://esm.d8d.fun/fmin@0.0.2/denonext/fmin.mjs": "2f539e0aca7ee8b7d3e18f24bdc71edab6cbf2e8f8d8b556c0cc984ada428d42",
+    "https://esm.d8d.fun/follow-redirects@1.15.9/denonext/follow-redirects.mjs": "90866d22d80eface74d3161906835600fbb1d5c9ded05dc72fd55b40960cfce7",
+    "https://esm.d8d.fun/follow-redirects@1.15.9?target=denonext": "c8028ec9d980a1974362377614a4978d1556ba85a75bc32526e8917bede535d1",
+    "https://esm.d8d.fun/form-data@4.0.2/denonext/form-data.mjs": "ad3c492eef1c6153bcfa02eb3041894e8bc8e4aa241ad4d9bd53a385f36d2c6f",
+    "https://esm.d8d.fun/form-data@4.0.2?target=denonext": "83cf111a2e6f0f7b6c045508635ceae449c5d43d8d22df23693e5b996cc65899",
+    "https://esm.d8d.fun/gl-matrix@3.4.3/denonext/gl-matrix.mjs": "1e2709238113c1c50aa414457739f096e3c74368247391c39d349ed33c9c6beb",
+    "https://esm.d8d.fun/gl-matrix@3.4.3?target=denonext": "7ccb5904be73e0ca6fc5100658ce357e5c6874b91ad773dd7626db9a736d96f3",
+    "https://esm.d8d.fun/internmap@2.0.3/denonext/internmap.mjs": "09707265c3a24383ca1b71aa6d97ab980e91ad4e804f55dec34de6d24a9900b4",
+    "https://esm.d8d.fun/internmap@2.0.3?target=denonext": "235fe2491d38df4060d092e8b8e882891b7bcb595435dcf9f52d7d38b6d2c534",
+    "https://esm.d8d.fun/is-arrayish@0.3.2/denonext/is-arrayish.mjs": "89e8f85c8f60717edc68bea3a914acea741dc345616d8ed10c2bbed1f0bf6b22",
+    "https://esm.d8d.fun/is-arrayish@0.3.2?target=denonext": "aa940bd629bed4a1bc2f9158cfea1c09b9c9abe9fc7406e90c785a7d694e18e7",
+    "https://esm.d8d.fun/isexe@3.1.1/denonext/isexe.mjs": "59b58a950d33368749f8b2f0df8377ded09f5f30c276f79239a2542029e77a43",
+    "https://esm.d8d.fun/isexe@3.1.1/denonext/posix.mjs": "f99f8d2aacd0b5424625cee480f36b47639cfbad44c64b7b21cbba18ad77a1b2",
+    "https://esm.d8d.fun/isexe@3.1.1/denonext/win32.mjs": "f52981ee6555549c246db8e9e6c0ee1e2947a35367c3bcec0ba31834387991c0",
+    "https://esm.d8d.fun/isexe@3.1.1?target=denonext": "b3c61e7e70b9d56865de461fbcdae702ebf93743143457079a15a60e30dfcf83",
+    "https://esm.d8d.fun/lodash-es@4.17.21/denonext/lodash-es.mjs": "83b25b8f85872b2805e6b0273c90d6c96960c80a710c55e89a7b399107fc6fa8",
+    "https://esm.d8d.fun/lodash-es@4.17.21?target=denonext": "1252ccd86311d14f2dd05282cf3e40e1ff76bfa79c71ca49b903e902129944cb",
+    "https://esm.d8d.fun/mime-db@1.52.0/denonext/mime-db.mjs": "f93feb3d7150014b71bd0d06c5bd819db56a089b31b8b79a3b0466bb37ef005e",
+    "https://esm.d8d.fun/mime-types@2.1.35/denonext/mime-types.mjs": "704bdb318816fe1360c90a196f7cb3ba6e25fe207707cc2df873f890ad2e5f44",
+    "https://esm.d8d.fun/mime-types@2.1.35?target=denonext": "e4cc9a1aabecc1be22d194375ec3b99cc9d51700cc4629ab689975451c0a8ce5",
+    "https://esm.d8d.fun/ms@2.1.3/denonext/ms.mjs": "9039464da1f4ae1c2042742d335c82556c048bbe49449b5d0cd5198193afa147",
+    "https://esm.d8d.fun/ms@2.1.3?target=denonext": "36f5aa7503ff0ff44ce9e3155a60362d8d3ae5db8db048be5764a3a515b6a263",
+    "https://esm.d8d.fun/nanoid@5.1.5/denonext/nanoid.mjs": "dc919f2d7339a244f732a0cf02e3962dd1289535668026f52fb26bd593e9358b",
+    "https://esm.d8d.fun/nanoid@5.1.5?target=denonext": "33ad5b17f1290cb850164cfcf30f642d9dad489ba19909bc3cfd9eb78369f451",
+    "https://esm.d8d.fun/node-gyp-build@4.8.4/denonext/node-gyp-build.mjs": "9a86f2d044fc77bd60aaa3d697c2ba1b818da5fb1b9aaeedec59a40b8e908803",
+    "https://esm.d8d.fun/node-gyp-build@4.8.4?target=denonext": "261a6cedf1fdbf159798141ba1e2311ac1510682c5c8b55dacc8cf5fdee4aa06",
+    "https://esm.d8d.fun/pdfast@0.2.0/denonext/pdfast.mjs": "d0ba2b4755fdfef58ee7a62400302a35fc32477718cd212d177954bbdb62c581",
+    "https://esm.d8d.fun/pdfast@0.2.0?target=denonext": "3ca441f0d42ceef6375da1e5104e4e3008aa80ae119afcf79179c32a00175472",
+    "https://esm.d8d.fun/proxy-from-env@1.1.0/denonext/proxy-from-env.mjs": "f60f9c79fc3baa07c13c800798d645ae70d1b2059b8d593dcd4f8c5710b50333",
+    "https://esm.d8d.fun/proxy-from-env@1.1.0?target=denonext": "bf02a050a1a6aa56ddba25dbea2c355da294630e5c5520fddea4b2f30a9292bc",
+    "https://esm.d8d.fun/quickselect@2.0.0/denonext/quickselect.mjs": "833d43ecf28cd35f20da6747c8035285b2ecd8c8d9257d774531791c7a3dc1d3",
+    "https://esm.d8d.fun/quickselect@2.0.0?target=denonext": "00b8c4651bcf178a10c1dbea847e727cc7d226992c522d39244a6d075726f8eb",
+    "https://esm.d8d.fun/rbush@3.0.1/denonext/rbush.mjs": "c4e0184ed2094e994ca4b5178695f4a9302b0b36a0387ff019422092e3061f84",
+    "https://esm.d8d.fun/rbush@3.0.1/rbush.js": "500cc6bc3e9c0e1d4a77c15f19b2dd6d61c6d26934cd8378ad866c20d9e135f1",
+    "https://esm.d8d.fun/react-dom@19.0.0": "d057f65e74eca8add1702ba9a5ecbcc8e60a73dd358b7852094bde0361725137",
+    "https://esm.d8d.fun/react-dom@19.0.0/X-ZHJlYWN0QDE5LjAuMA/denonext/react-dom.mjs": "a2f7bc344e1d5b7ca47e68665291e206ae4db17ee84f234f3d3e2533b9119f63",
+    "https://esm.d8d.fun/react-dom@19.0.0/client": "c972c16184c695fc5828dfa61d7f341edbc463d20d8108765c93a98027c24227",
+    "https://esm.d8d.fun/react-dom@19.0.0/denonext/client.mjs": "af662fd134eea98f37fdcea6142accd0f8a7d2e13c1c3c9e98dc37a8c7aad46b",
+    "https://esm.d8d.fun/react-dom@19.0.0/denonext/react-dom.mjs": "a2f7bc344e1d5b7ca47e68665291e206ae4db17ee84f234f3d3e2533b9119f63",
+    "https://esm.d8d.fun/react-router@7.3.0/X-ZHJlYWN0LWRvbUAxOS4wLjAscmVhY3RAMTkuMC4w/denonext/dist/development/chunk-K6CSEXPM.mjs": "441898046ad7c4fd9a6b53e13a398c9c74c4412c519e942f82b8a77f7af9f9d6",
+    "https://esm.d8d.fun/react-router@7.3.0/X-ZHJlYWN0LWRvbUAxOS4wLjAscmVhY3RAMTkuMC4w/denonext/react-router.mjs": "b0b05fcfc3a03c5f679cd0bc69ca19aa10abaa977395df00e86b3fb114e5e346",
+    "https://esm.d8d.fun/react-router@7.3.0?deps=react@19.0.0,react-dom@19.0.0": "ad747718e32a45020d67eb4ff98f9734cb06a10ceb393baac0a965043e96cdf0",
+    "https://esm.d8d.fun/react@19.0.0": "ab1f4aa20ac56c237bbb204632bdb55f03a0ab005d21944eeb447e5e37879637",
+    "https://esm.d8d.fun/react@19.0.0/denonext/jsx-runtime.mjs": "643b749fa9666fbf73619a99fd708722edb4acaa34c8cea7be783a3432367780",
+    "https://esm.d8d.fun/react@19.0.0/denonext/react.mjs": "87fdb28d39ca8983bdba3e7ec329305f95463cfc70c015b2620b4900fa15efdd",
+    "https://esm.d8d.fun/scheduler@0.25.0/denonext/scheduler.mjs": "50687edf9e0034b6db97303b1b16893b59c5833c21ea8cf913dc380b537f6aaf",
+    "https://esm.d8d.fun/scheduler@0.25.0?target=denonext": "c12810f51123057a8a8e309cc8befaac0b5cd371cb4d61bf0372ab8046acc8e0",
+    "https://esm.d8d.fun/set-cookie-parser@2.7.1/denonext/set-cookie-parser.mjs": "81f09c909c63221a2460bc7602746543af6fd05b54fd866a04e81bb754bc7f26",
+    "https://esm.d8d.fun/set-cookie-parser@2.7.1?target=denonext": "5340cc320e1c716299df7947a234e4b58951c97eba69b43d9f79d8933a07e6c8",
+    "https://esm.d8d.fun/simple-swizzle@0.2.2/denonext/simple-swizzle.mjs": "14521d40314cff0bd3b5b89f62c16eb51828ee3872cae3f3cc57478573f4002c",
+    "https://esm.d8d.fun/simple-swizzle@0.2.2?target=denonext": "d55422e39d6f894d6ce044df75163cf89f6c75a9ec147694067aadd1646556b5",
+    "https://esm.d8d.fun/socket.io-client@4.8.1/denonext/socket.io-client.mjs": "b902dafad93171849d6d6e9e98bfa5357513089e43b0fbf9268d394f0839f372",
+    "https://esm.d8d.fun/socket.io-client@4.8.1?target=denonext": "f5543108c5018ca5904af75985dc9ff7b7210334782408cf87bdf091ce1fbf2e",
+    "https://esm.d8d.fun/socket.io-parser@4.2.4/denonext/socket.io-parser.mjs": "a989568a92fa45870a4ae74fb731c5e554ef6c901b97f154d8c84267f7d5aaba",
+    "https://esm.d8d.fun/socket.io-parser@4.2.4?target=denonext": "95bc48ccd83940940fb68cf3401280667a8bad2b6abc8a4c7bb5c39ec59aff16",
+    "https://esm.d8d.fun/supports-color@10.0.0/denonext/supports-color.mjs": "239cd39d0828e1a018dee102748da869b1b75c38fe6a9c0c8f0bd4ffbd3e1ea1",
+    "https://esm.d8d.fun/supports-color@10.0.0?target=denonext": "4895255248e4ba0cbcce9437003dccf3658b1ac1d1e8eba5225fb8194c454ee1",
+    "https://esm.d8d.fun/svg-path-parser@1.1.0/denonext/svg-path-parser.mjs": "2473f2145ed8cd157a6377e7a3913f5fee1c628d1da6bed9b4937de15f0b172b",
+    "https://esm.d8d.fun/svg-path-parser@1.1.0?target=denonext": "c12ffcfafa86a55d2c56cb6b623481405e5b9ec2221dfa7db6c03b407105b30c",
+    "https://esm.d8d.fun/tslib@2.8.1/denonext/tslib.mjs": "ebce3cd5facb654623020337f867b426ba95f71596ba87acc9e6c6f4e55905ca",
+    "https://esm.d8d.fun/tslib@2.8.1?target=denonext": "a89cbf082a0ad54e87ea85d75e6d5492b3e2ff0c5de9e3e478206a015b3f3992",
+    "https://esm.d8d.fun/turbo-stream@2.4.0/denonext/turbo-stream.mjs": "635731418708dd33b7db7bf5a0011b28fb89b61154f70ab112da16d5b0cc20b4",
+    "https://esm.d8d.fun/utf-8-validate@6.0.5/denonext/utf-8-validate.mjs": "90c0c88a13bc4749b497361480d618bf4809153f5d5ba694fac79ae9dbf634a9",
+    "https://esm.d8d.fun/utf-8-validate@6.0.5?target=denonext": "071bc33ba1a58297e23a34d69dd589fd06df04b0f373b382ff5da544a623f271",
+    "https://esm.d8d.fun/which@4.0.0/denonext/which.mjs": "9f47207c6dc9684fe3d852f2290c474577babaeabf60616652630c0b90421a53",
+    "https://esm.d8d.fun/which@4.0.0?target=denonext": "50b06c1a68e3ef88dc8e2c68c17b732a6d1917000d5d59637496da3b61549c8e",
+    "https://esm.d8d.fun/ws@8.17.1/denonext/ws.mjs": "7b349f9bcf5af35a422b01ece5189ac693f84f07cc2e9be12023ec818a18ba71",
+    "https://esm.d8d.fun/ws@8.17.1?target=denonext": "3c5e4dca1be73c0e7776cb033809d16c2421b785cd1b93827b76a43c5b59a0bd",
+    "https://esm.d8d.fun/xmlhttprequest-ssl@2.1.2/denonext/xmlhttprequest-ssl.mjs": "5cb537aeb44e2971f9d84c4e22e0d24ea0554eb6c33a5d10a46cf163debf60ec",
+    "https://esm.d8d.fun/xmlhttprequest-ssl@2.1.2?target=denonext": "5a4574293c501f0f0da3ddd653bd5d9ac00ea59647e3b20694cc05ed02e7a22f"
+  }
+}

+ 318 - 0
asset/admin/hooks_sys.tsx

@@ -0,0 +1,318 @@
+import React, { useState, useEffect, createContext, useContext } from 'react';
+import { ConfigProvider, theme, message
+} from 'antd';
+import zhCN from "antd/locale/zh_CN";
+
+import { 
+  useQuery,
+  useQueryClient,
+  useMutation
+} from '@tanstack/react-query';
+import axios from 'axios';
+import dayjs from 'dayjs';
+import weekday from 'dayjs/plugin/weekday';
+import localeData from 'dayjs/plugin/localeData';
+import 'dayjs/locale/zh-cn';
+import type { 
+  User, AuthContextType, ThemeContextType, ThemeSettings
+} from '../share/types.ts';
+import {
+  ThemeMode,
+  FontSize,
+  CompactMode
+} from '../share/types.ts';
+import {
+  AuthAPI,
+  ThemeAPI
+} from './api.ts';
+
+
+// 配置 dayjs 插件
+dayjs.extend(weekday);
+dayjs.extend(localeData);
+
+// 设置 dayjs 语言
+dayjs.locale('zh-cn');
+
+
+// 确保ConfigProvider能够正确使用中文日期
+const locale = {
+  ...zhCN,
+  DatePicker: {
+    ...zhCN.DatePicker,
+    lang: {
+      ...zhCN.DatePicker?.lang,
+      shortWeekDays: ['日', '一', '二', '三', '四', '五', '六'],
+      shortMonths: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月']
+    }
+  }
+};
+
+// 创建认证上下文
+const AuthContext = createContext<AuthContextType | null>(null);
+const ThemeContext = createContext<ThemeContextType | null>(null);
+
+// 认证提供器组件
+export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
+  const [user, setUser] = useState<User | null>(null);
+  const [token, setToken] = useState<string | null>(localStorage.getItem('token'));
+  const [isAuthenticated, setIsAuthenticated] = useState<boolean>(false);
+  const queryClient = useQueryClient();
+  
+  // 声明handleLogout函数
+  const handleLogout = async () => {
+    try {
+      // 如果已登录,调用登出API
+      if (token) {
+        await AuthAPI.logout();
+      }
+    } catch (error) {
+      console.error('登出请求失败:', error);
+    } finally {
+      // 清除本地状态
+      setToken(null);
+      setUser(null);
+      setIsAuthenticated(false);
+      localStorage.removeItem('token');
+      // 清除Authorization头
+      delete axios.defaults.headers.common['Authorization'];
+      console.log('登出时已删除全局Authorization头');
+      // 清除所有查询缓存
+      queryClient.clear();
+    }
+  };
+  
+  // 使用useQuery检查登录状态
+  const { isLoading } = useQuery({
+    queryKey: ['auth', 'status', token],
+    queryFn: async () => {
+      if (!token) {
+        setIsAuthenticated(false);
+        setUser(null);
+        return null;
+      }
+      
+      try {
+        // 设置全局默认请求头
+        axios.defaults.headers.common['Authorization'] = `Bearer ${token}`;
+        // 使用API验证当前用户
+        const currentUser = await AuthAPI.getCurrentUser();
+        setUser(currentUser);
+        setIsAuthenticated(true);
+        return { isValid: true, user: currentUser };
+      } catch (error) {
+        // 如果API调用失败,自动登出
+        handleLogout();
+        return { isValid: false };
+      }
+    },
+    enabled: !!token,
+    refetchOnWindowFocus: false,
+    retry: false
+  });
+  
+  // 设置请求拦截器
+  useEffect(() => {
+    // console.log('token状态变化,当前token:', token);
+    // if (token) {
+    //   // 从localStorage中恢复token时设置全局请求头
+    //   axios.defaults.headers.common['Authorization'] = `Bearer ${token}`;
+    //   console.log('从状态中恢复全局Authorization头:', axios.defaults.headers.common['Authorization']);
+    // } else {
+    //   // 登出时删除请求头
+    //   delete axios.defaults.headers.common['Authorization'];
+    //   console.log('已删除全局Authorization头');
+    // }
+    
+    // 设置响应拦截器处理401错误
+    const responseInterceptor = axios.interceptors.response.use(
+      (response) => response,
+      (error) => {
+        if (error.response?.status === 401) {
+          console.log('检测到401错误,执行登出操作');
+          handleLogout();
+        }
+        return Promise.reject(error);
+      }
+    );
+    
+    // 清理拦截器
+    return () => {
+      axios.interceptors.response.eject(responseInterceptor);
+    };
+  }, [token]);
+  
+  const handleLogin = async (username: string, password: string): Promise<void> => {
+    try {
+      // 使用AuthAPI登录
+      const response = await AuthAPI.login(username, password);
+      
+      // 保存token和用户信息
+      const { token: newToken, user: newUser } = response;
+      
+      // 设置全局默认请求头
+      axios.defaults.headers.common['Authorization'] = `Bearer ${newToken}`;
+      
+      // 保存状态
+      setToken(newToken);
+      setUser(newUser);
+      setIsAuthenticated(true);
+      localStorage.setItem('token', newToken);
+      
+    } catch (error) {
+      console.error('登录失败:', error);
+      throw error;
+    }
+  };
+  
+  return (
+    <AuthContext.Provider 
+      value={{
+        user,
+        token,
+        login: handleLogin,
+        logout: handleLogout,
+        isAuthenticated,
+        isLoading
+      }}
+    >
+      {children}
+    </AuthContext.Provider>
+  );
+};
+
+// 主题提供器组件
+export const ThemeProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
+  const [isDark, setIsDark] = useState(false);
+  const [currentTheme, setCurrentTheme] = useState<ThemeSettings>({
+    user_id: 0,
+    theme_mode: ThemeMode.LIGHT,
+    primary_color: '#1890ff',
+    font_size: FontSize.MEDIUM,
+    is_compact: CompactMode.NORMAL
+  });
+  
+  // 获取主题设置
+  const { isLoading: isThemeLoading } = useQuery({
+    queryKey: ['theme', 'settings'],
+    queryFn: async () => {
+      try {
+        const settings = await ThemeAPI.getThemeSettings();
+        setCurrentTheme(settings);
+        setIsDark(settings.theme_mode === ThemeMode.DARK);
+        
+        return settings;
+      } catch (error) {
+        console.error('获取主题设置失败:', error);
+        return null;
+      }
+    },
+    refetchOnWindowFocus: false,
+    enabled: !!localStorage.getItem('token')
+  });
+  
+  // 预览主题设置(不保存到后端)
+  const previewTheme = (newTheme: Partial<ThemeSettings>) => {
+    const updatedTheme = { ...currentTheme, ...newTheme };
+    setCurrentTheme(updatedTheme);
+    setIsDark(updatedTheme.theme_mode === ThemeMode.DARK);
+  };
+  
+  // 更新主题设置(保存到后端)
+  const updateThemeMutation = useMutation({
+    mutationFn: async (newTheme: Partial<ThemeSettings>) => {
+      return await ThemeAPI.updateThemeSettings(newTheme);
+    },
+    onSuccess: (data) => {
+      setCurrentTheme(data);
+      setIsDark(data.theme_mode === ThemeMode.DARK);
+      message.success('主题设置已更新');
+    },
+    onError: (error) => {
+      console.error('更新主题设置失败:', error);
+      message.error('更新主题设置失败');
+    }
+  });
+  
+  // 重置主题设置
+  const resetThemeMutation = useMutation({
+    mutationFn: async () => {
+      return await ThemeAPI.resetThemeSettings();
+    },
+    onSuccess: (data) => {
+      setCurrentTheme(data);
+      setIsDark(data.theme_mode === ThemeMode.DARK);
+      message.success('主题设置已重置为默认值');
+    },
+    onError: (error) => {
+      console.error('重置主题设置失败:', error);
+      message.error('重置主题设置失败');
+    }
+  });
+  
+  // 添加 toggleTheme 方法
+  const toggleTheme = () => {
+    const newTheme = {
+      ...currentTheme,
+      theme_mode: isDark ? ThemeMode.LIGHT : ThemeMode.DARK
+    };
+    setIsDark(!isDark);
+    setCurrentTheme(newTheme);
+  };
+  
+  return (
+    <ThemeContext.Provider value={{ 
+      isDark, 
+      currentTheme,
+      updateTheme: previewTheme,
+      saveTheme: updateThemeMutation.mutateAsync,
+      resetTheme: resetThemeMutation.mutateAsync,
+      toggleTheme
+    }}>
+      <ConfigProvider
+        theme={{
+          algorithm: currentTheme.is_compact === CompactMode.COMPACT
+            ? [isDark ? theme.darkAlgorithm : theme.defaultAlgorithm, theme.compactAlgorithm]
+            : isDark ? theme.darkAlgorithm : theme.defaultAlgorithm,
+          token: {
+            colorPrimary: currentTheme.primary_color,
+            fontSize: currentTheme.font_size === FontSize.SMALL ? 12 :
+                     currentTheme.font_size === FontSize.MEDIUM ? 14 : 16,
+            // colorBgBase: isDark ? undefined : currentTheme.background_color || '#fff',
+            colorBgBase: currentTheme.background_color,
+            borderRadius: currentTheme.border_radius ?? 6,
+            colorTextBase: currentTheme.text_color || (isDark ? '#fff' : '#000'),
+          },
+          components: {
+            Layout: {
+              // headerBg: isDark ? undefined : currentTheme.background_color || '#fff',
+              // siderBg: isDark ? undefined : currentTheme.background_color || '#fff',
+              headerBg: currentTheme.background_color,
+              siderBg: currentTheme.background_color,
+            }
+          }
+        }}
+        locale={locale as any}
+      >
+        {children}
+      </ConfigProvider>
+    </ThemeContext.Provider>
+  );
+};
+
+// 使用上下文的钩子
+export const useAuth = () => {
+  const context = useContext(AuthContext);
+  if (!context) {
+    throw new Error('useAuth必须在AuthProvider内部使用');
+  }
+  return context;
+};
+
+export const useTheme = () => {
+  const context = useContext(ThemeContext);
+  if (!context) {
+    throw new Error('useTheme必须在ThemeProvider内部使用');
+  }
+  return context;
+};

+ 212 - 0
asset/admin/pages_chart.tsx

@@ -0,0 +1,212 @@
+import React from 'react';
+import { 
+  Layout, Menu, Button, Table, Space,
+  Form, Input, Select, message, Modal,
+  Card, Spin, Row, Col, Breadcrumb, Avatar,
+  Dropdown, ConfigProvider, theme, Typography,
+  Switch, Badge, Image, Upload, Divider, Descriptions,
+  Popconfirm, Tag, Statistic, DatePicker, Radio, Progress, Tabs, List, Alert, Collapse, Empty, Drawer
+} from 'antd';
+
+import { 
+  useQuery,
+} from '@tanstack/react-query';
+import { Line , Pie, Column} from "@ant-design/plots";
+import 'dayjs/locale/zh-cn';
+
+
+import { ChartAPI } from './api.ts';
+import { useTheme } from './hooks_sys.tsx';
+
+interface ChartTooltipInfo {
+  items: Array<Record<string, any>>;
+  title: string;
+}
+
+// 用户活跃度图表组件
+const UserActivityChart: React.FC = () => {
+  const { isDark } = useTheme();
+  const { data: activityData, isLoading } = useQuery({
+    queryKey: ['userActivity'],
+    queryFn: async () => {
+      const response = await ChartAPI.getUserActivity();
+      return response.data;
+    }
+  });
+
+  if (isLoading) return <Spin />;
+
+  const config = {
+    data: activityData || [],
+    xField: 'date',
+    yField: 'count',
+    smooth: true,
+    theme: isDark ? 'dark' : 'light',
+    color: '#1890ff',
+    areaStyle: {
+      fill: 'l(270) 0:#1890ff10 1:#1890ff',
+    },
+  };
+
+  return (
+    <Card title="用户活跃度趋势" bordered={false}>
+      <Line {...config} />
+    </Card>
+  );
+};
+
+// 文件上传统计图表组件
+const FileUploadsChart: React.FC = () => {
+  const { isDark } = useTheme();
+  const { data: uploadsData, isLoading } = useQuery({
+    queryKey: ['fileUploads'],
+    queryFn: async () => {
+      const response = await ChartAPI.getFileUploads();
+      return response.data;
+    }
+  });
+
+  if (isLoading) return <Spin />;
+
+  const config = {
+    data: uploadsData || [],
+    xField: 'month',
+    yField: 'count',
+    theme: isDark ? 'dark' : 'light',
+    color: '#52c41a',
+    label: {
+      position: 'middle',
+      style: {
+        fill: '#FFFFFF',
+        opacity: 0.6,
+      },
+    },
+    meta: {
+      month: {
+        alias: '月份',
+      },
+      count: {
+        alias: '上传数量',
+      },
+    },
+  };
+
+  return (
+    <Card title="文件上传统计" bordered={false}>
+      <Column {...config} />
+    </Card>
+  );
+};
+
+// 文件类型分布图表组件
+const FileTypesChart: React.FC = () => {
+  const { isDark } = useTheme();
+  const { data: typesData, isLoading } = useQuery({
+    queryKey: ['fileTypes'],
+    queryFn: async () => {
+      const response = await ChartAPI.getFileTypes();
+      return response.data;
+    }
+  });
+
+  if (isLoading) return <Spin />;
+
+  const config = {
+    data: typesData || [],
+    angleField: 'value',
+    colorField: 'type',
+    radius: 0.8,
+    theme: isDark ? 'dark' : 'light',
+    label: {
+      type: 'spider',
+      labelHeight: 28,
+      content: '{name}\n{percentage}',
+    },
+    interactions: [
+      {
+        type: 'element-active',
+      },
+    ],
+  };
+
+  return (
+    <Card title="文件类型分布" bordered={false}>
+      <Pie {...config} />
+    </Card>
+  );
+};
+
+// 仪表盘概览组件
+const DashboardOverview: React.FC = () => {
+  const { data: overviewData, isLoading } = useQuery({
+    queryKey: ['dashboardOverview'],
+    queryFn: async () => {
+      const response = await ChartAPI.getDashboardOverview();
+      return response.data;
+    }
+  });
+
+  if (isLoading) return <Spin />;
+
+  return (
+    <Row gutter={[16, 16]}>
+      <Col xs={12} sm={12} md={6}>
+        <Card bordered={false}>
+          <Statistic
+            title="用户总数"
+            value={overviewData?.userCount || 0}
+            valueStyle={{ color: '#1890ff' }}
+          />
+        </Card>
+      </Col>
+      <Col xs={12} sm={12} md={6}>
+        <Card bordered={false}>
+          <Statistic
+            title="文件总数"
+            value={overviewData?.fileCount || 0}
+            valueStyle={{ color: '#52c41a' }}
+          />
+        </Card>
+      </Col>
+      <Col xs={12} sm={12} md={6}>
+        <Card bordered={false}>
+          <Statistic
+            title="文章总数"
+            value={overviewData?.articleCount || 0}
+            valueStyle={{ color: '#faad14' }}
+          />
+        </Card>
+      </Col>
+      <Col xs={12} sm={12} md={6}>
+        <Card bordered={false}>
+          <Statistic
+            title="今日登录"
+            value={overviewData?.todayLoginCount || 0}
+            valueStyle={{ color: '#722ed1' }}
+          />
+        </Card>
+      </Col>
+    </Row>
+  );
+};
+
+// 图表仪表盘页面组件
+export const ChartDashboardPage: React.FC = () => {
+  return (
+    <div className="chart-dashboard">
+      <DashboardOverview />
+      <div style={{ height: 24 }} />
+      <Row gutter={[16, 16]}>
+        <Col xs={24} lg={12}>
+          <UserActivityChart />
+        </Col>
+        <Col xs={24} lg={12}>
+          <FileUploadsChart />
+        </Col>
+        <Col xs={24}>
+          <FileTypesChart />
+        </Col>
+      </Row>
+    </div>
+  );
+};

+ 97 - 0
asset/admin/pages_login_reg.tsx

@@ -0,0 +1,97 @@
+import React, { useState } from 'react';
+import {
+  Form,
+  Input,
+  Button,
+  Card,
+  message,
+} from 'antd';
+import {
+  UserOutlined,
+} from '@ant-design/icons';
+import { useNavigate } from 'react-router';
+import {
+  useAuth,
+} from './hooks_sys.tsx';
+
+
+// 登录页面
+export const LoginPage = () => {
+  const { login } = useAuth();
+  const [form] = Form.useForm();
+  const [loading, setLoading] = useState(false);
+  const navigate = useNavigate();
+  
+  const handleSubmit = async (values: { username: string; password: string }) => {
+    try {
+      setLoading(true);
+      await login(values.username, values.password);
+      // 登录成功后跳转到管理后台首页
+      navigate('/admin/dashboard');
+    } catch (error: any) {
+      message.error(error.response?.data?.error || '登录失败');
+    } finally {
+      setLoading(false);
+    }
+  };
+  
+  return (
+    <div className="min-h-screen flex items-center justify-center bg-gray-50 py-12 px-4 sm:px-6 lg:px-8">
+      <div className="max-w-md w-full space-y-8">
+        <div>
+          <h2 className="mt-6 text-center text-3xl font-extrabold text-gray-900">
+            登录管理后台
+          </h2>
+        </div>
+        
+        <Card>
+          <Form
+            form={form}
+            name="login"
+            onFinish={handleSubmit}
+            autoComplete="off"
+            layout="vertical"
+          >
+            <Form.Item
+              name="username"
+              rules={[{ required: true, message: '请输入用户名' }]}
+            >
+              <Input 
+                prefix={<UserOutlined />} 
+                placeholder="用户名" 
+                size="large"
+              />
+            </Form.Item>
+            
+            <Form.Item
+              name="password"
+              rules={[{ required: true, message: '请输入密码' }]}
+            >
+              <Input.Password 
+                placeholder="密码" 
+                size="large"
+              />
+            </Form.Item>
+            
+            <Form.Item>
+              <Button 
+                type="primary" 
+                htmlType="submit" 
+                size="large" 
+                block
+                loading={loading}
+              >
+                登录
+              </Button>
+            </Form.Item>
+          </Form>
+          
+          <div className="mt-4 text-center text-gray-500">
+            <p>测试账号: admin / admin123</p>
+            {/* <p>普通账号: user1 / 123456</p> */}
+          </div>
+        </Card>
+      </div>
+    </div>
+  );
+};

+ 211 - 0
asset/admin/pages_map.tsx

@@ -0,0 +1,211 @@
+import React, { useState, useEffect } from 'react';
+import { 
+  Layout, Menu, Button, Table, Space,
+  Form, Input, Select, message, Modal,
+  Card, Spin, Row, Col, Breadcrumb, Avatar,
+  Dropdown, ConfigProvider, theme, Typography,
+  Switch, Badge, Image, Upload, Divider, Descriptions,
+  Popconfirm, Tag, Statistic, DatePicker, Radio, Progress, Tabs, List, Alert, Collapse, Empty, Drawer,
+  Tree
+} from 'antd';
+import {
+  MenuFoldOutlined,
+  MenuUnfoldOutlined,
+  AppstoreOutlined,
+  EnvironmentOutlined,
+  SearchOutlined,
+  ClockCircleOutlined,
+  UserOutlined,
+  GlobalOutlined
+} from '@ant-design/icons';   
+import {
+  useQuery,
+} from '@tanstack/react-query';
+import 'dayjs/locale/zh-cn';
+import AMap from './components_amap.tsx'; // 导入地图组件
+// 从share/types.ts导入所有类型,包括MapMode
+import type { 
+   MarkerData, LoginLocation, LoginLocationDetail, User
+} from '../share/types.ts';
+
+import { MapAPI,UserAPI } from './api.ts';
+import dayjs from 'dayjs';
+
+const { RangePicker } = DatePicker;
+
+
+// 地图页面组件
+export const LoginMapPage = () => {
+  const [selectedTimeRange, setSelectedTimeRange] = useState<[dayjs.Dayjs, dayjs.Dayjs] | null>(null);
+  const [selectedUserId, setSelectedUserId] = useState<number | null>(null);
+  const [selectedMarker, setSelectedMarker] = useState<MarkerData | null>(null);
+  const [drawerVisible, setDrawerVisible] = useState(false);
+
+  // 获取登录位置数据
+  const { data: locations = [], isLoading: markersLoading } = useQuery<LoginLocation[]>({
+    queryKey: ['loginLocations', selectedTimeRange, selectedUserId],
+    queryFn: async () => {
+      try {
+        let params: any = {};
+        
+        if (selectedTimeRange) {
+          params.startTime = selectedTimeRange[0].format('YYYY-MM-DD HH:mm:ss');
+          params.endTime = selectedTimeRange[1].format('YYYY-MM-DD HH:mm:ss');
+        }
+        
+        if (selectedUserId) {
+          params.userId = selectedUserId;
+        }
+        
+        const result = await MapAPI.getMarkers(params);
+        return result.data;
+      } catch (error) {
+        console.error("获取登录位置数据失败:", error);
+        message.error("获取登录位置数据失败");
+        return [];
+      }
+    },
+    refetchInterval: 30000 // 30秒刷新一次
+  });
+
+  // 获取用户列表
+  const { data: users = [] } = useQuery<User[]>({
+    queryKey: ['users'],
+    queryFn: async () => {
+      try {
+        const response = await UserAPI.getUsers();
+        return response.data || [];
+      } catch (error) {
+        console.error("获取用户列表失败:", error);
+        message.error("获取用户列表失败");
+        return [];
+      }
+    }
+  });
+
+  // 获取选中标记点的详细信息
+  const { data: markerDetail, isLoading: detailLoading } = useQuery<LoginLocationDetail | undefined>({
+    queryKey: ['loginLocation', selectedMarker?.id],
+    queryFn: async () => {
+      if (!selectedMarker?.id) return undefined;
+      try {
+        const result = await MapAPI.getLocationDetail(Number(selectedMarker.id));
+        return result.data;
+      } catch (error) {
+        console.error("获取登录位置详情失败:", error);
+        message.error("获取登录位置详情失败");
+        return undefined;
+      }
+    },
+    enabled: !!selectedMarker?.id
+  });
+
+  // 处理标记点点击
+  const handleMarkerClick = (marker: MarkerData) => {
+    setSelectedMarker(marker);
+    setDrawerVisible(true);
+  };
+
+  // 渲染地图标记点
+  const renderMarkers = (locations: LoginLocation[]): MarkerData[] => {
+    return locations.map(location => ({
+      id: location.id,
+      longitude: location.longitude,
+      latitude: location.latitude,
+      title: location.user.nickname || location.user.username,
+      description: `登录时间: ${dayjs(location.loginTime).format('YYYY-MM-DD HH:mm:ss')}\nIP地址: ${location.ipAddress}`,
+      status: 'online',
+      type: 'login',
+      extraData: location
+    }));
+  };
+
+  return (
+    <div className="h-full">
+      <Card style={{ marginBottom: 16 }}>
+        <Space direction="horizontal" size={16} wrap>
+          <RangePicker
+            showTime
+            onChange={(dates) => setSelectedTimeRange(dates as [dayjs.Dayjs, dayjs.Dayjs])}
+            placeholder={['开始时间', '结束时间']}
+          />
+          <Select
+            style={{ width: 200 }}
+            placeholder="选择用户"
+            allowClear
+            onChange={(value) => setSelectedUserId(value)}
+            options={users.map((user: User) => ({
+              label: user.nickname || user.username,
+              value: user.id
+            }))}
+          />
+          <Button 
+            type="primary"
+            onClick={() => {
+              setSelectedTimeRange(null);
+              setSelectedUserId(null);
+            }}
+          >
+            重置筛选
+          </Button>
+        </Space>
+      </Card>
+
+      <Card style={{ height: 'calc(100% - 80px)' }}>
+        <Spin spinning={markersLoading}>
+          <div style={{ height: '100%', minHeight: '500px' }}>
+            <AMap
+              markers={renderMarkers(locations)}
+              center={locations[0] ? [locations[0].longitude, locations[0].latitude] : undefined}
+              onMarkerClick={handleMarkerClick}
+              height={'100%'}
+            />
+          </div>
+        </Spin>
+      </Card>
+
+      <Drawer
+        title="登录位置详情"
+        placement="right"
+        onClose={() => {
+          setDrawerVisible(false);
+          setSelectedMarker(null);
+        }}
+        open={drawerVisible}
+        width={400}
+      >
+        {detailLoading ? (
+          <Spin />
+        ) : markerDetail ? (
+          <Descriptions column={1}>
+            <Descriptions.Item label={<><UserOutlined /> 用户</>}>
+              {markerDetail.user.nickname || markerDetail.user.username}
+            </Descriptions.Item>
+            <Descriptions.Item label={<><ClockCircleOutlined /> 登录时间</>}>
+              {dayjs(markerDetail.login_time).format('YYYY-MM-DD HH:mm:ss')}
+            </Descriptions.Item>
+            <Descriptions.Item label={<><GlobalOutlined /> IP地址</>}>
+              {markerDetail.ip_address}
+            </Descriptions.Item>
+            <Descriptions.Item label={<><EnvironmentOutlined /> 位置名称</>}>
+              {markerDetail.location_name || '未知位置'}
+            </Descriptions.Item>
+            <Descriptions.Item label="经度">
+              {markerDetail.longitude}
+            </Descriptions.Item>
+            <Descriptions.Item label="纬度">
+              {markerDetail.latitude}
+            </Descriptions.Item>
+            <Descriptions.Item label="浏览器信息">
+              <Typography.Paragraph ellipsis={{ rows: 2 }}>
+                {markerDetail.user_agent}
+              </Typography.Paragraph>
+            </Descriptions.Item>
+          </Descriptions>
+        ) : (
+          <div>暂无详细信息</div>
+        )}
+      </Drawer>
+    </div>
+  );
+};

+ 629 - 0
asset/admin/pages_settings.tsx

@@ -0,0 +1,629 @@
+import React, { useState, useEffect } from 'react';
+import { 
+  Layout, Menu, Button, Table, Space,
+  Form, Input, Select, message, Modal,
+  Card, Spin, Row, Col, Breadcrumb, Avatar,
+  Dropdown, ConfigProvider, theme, Typography,
+  Switch, Badge, Image, Upload, Divider, Descriptions,
+  Popconfirm, Tag, Statistic, DatePicker, Radio, Progress, Tabs, List, Alert, Collapse, Empty, Drawer, InputNumber,ColorPicker,
+  Popover
+} from 'antd';
+import {
+  UploadOutlined,
+  ReloadOutlined,
+  SaveOutlined,
+  BgColorsOutlined
+} from '@ant-design/icons';
+import { debounce } from 'lodash';
+import { 
+  useQuery,
+  useMutation,
+  useQueryClient,
+} from '@tanstack/react-query';
+import dayjs from 'dayjs';
+import weekday from 'dayjs/plugin/weekday';
+import localeData from 'dayjs/plugin/localeData';
+import 'dayjs/locale/zh-cn';
+import type { 
+  FileLibrary, FileCategory, KnowInfo, SystemSetting, SystemSettingValue
+} from '../share/types.ts';
+
+import {
+  SystemSettingGroup,
+  SystemSettingKey,
+  ThemeMode,
+  FontSize,
+  CompactMode, 
+  AllowedFileType
+} from '../share/types.ts';
+
+
+import { getEnumOptions } from './utils.ts';
+
+import {
+  SystemAPI,
+} from './api.ts';
+
+import { useTheme } from './hooks_sys.tsx';
+
+import { Uploader } from './components_uploader.tsx';
+
+// 配置 dayjs 插件
+dayjs.extend(weekday);
+dayjs.extend(localeData);
+
+// 设置 dayjs 语言
+dayjs.locale('zh-cn');
+
+const { Title } = Typography;
+
+// 分组标题映射
+const GROUP_TITLES: Record<typeof SystemSettingGroup[keyof typeof SystemSettingGroup], string> = {
+  [SystemSettingGroup.BASIC]: '基础设置',
+  [SystemSettingGroup.FEATURE]: '功能设置',
+  [SystemSettingGroup.UPLOAD]: '上传设置',
+  [SystemSettingGroup.NOTIFICATION]: '通知设置'
+};
+
+// 分组描述映射
+const GROUP_DESCRIPTIONS: Record<typeof SystemSettingGroup[keyof typeof SystemSettingGroup], string> = {
+  [SystemSettingGroup.BASIC]: '配置站点的基本信息',
+  [SystemSettingGroup.FEATURE]: '配置系统功能的开启状态',
+  [SystemSettingGroup.UPLOAD]: '配置文件上传相关的参数',
+  [SystemSettingGroup.NOTIFICATION]: '配置系统通知的触发条件'
+};
+
+// 定义预设配色方案
+const COLOR_SCHEMES = {
+  DEFAULT: {
+    name: '默认',
+    primary: '#1890ff',
+    background: '#f0f2f5',
+    text: '#000000'
+  },
+  DARK: {
+    name: '深色',
+    primary: '#177ddc',
+    background: '#141414',
+    text: '#ffffff'
+  },
+  LIGHT: {
+    name: '浅色',
+    primary: '#40a9ff',
+    background: '#ffffff',
+    text: '#000000'
+  },
+  BLUE: {
+    name: '蓝色',
+    primary: '#096dd9',
+    background: '#e6f7ff',
+    text: '#003a8c'
+  },
+  GREEN: {
+    name: '绿色',
+    primary: '#52c41a',
+    background: '#f6ffed',
+    text: '#135200'
+  },
+  WARM: {
+    name: '暖橙',
+    primary: '#fa8c16',
+    background: '#fff7e6',
+    text: '#873800'
+  },
+  SUNSET: {
+    name: '日落',
+    primary: '#f5222d',
+    background: '#fff1f0',
+    text: '#820014'
+  },
+  GOLDEN: {
+    name: '金色',
+    primary: '#faad14',
+    background: '#fffbe6',
+    text: '#614700'
+  },
+  CORAL: {
+    name: '珊瑚',
+    primary: '#ff7a45',
+    background: '#fff2e8',
+    text: '#ad2102'
+  },
+  ROSE: {
+    name: '玫瑰',
+    primary: '#eb2f96',
+    background: '#fff0f6',
+    text: '#9e1068'
+  }
+};
+
+// 颜色选择器组件
+// const ColorPicker: React.FC<{
+//   value?: string;
+//   onChange?: (color: string) => void;
+//   label?: string;
+// }> = ({ value = '#1890ff', onChange, label = '选择颜色' }) => {
+//   const [color, setColor] = useState(value);
+//   const [open, setOpen] = useState(false);
+
+//   // 更新颜色(预览)
+//   const handleColorChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+//     const newColor = e.target.value;
+//     setColor(newColor);
+//   };
+
+//   // 关闭时确认颜色
+//   const handleOpenChange = (visible: boolean) => {
+//     if (!visible && color) {
+//       onChange?.(color);
+//     }
+//     setOpen(visible);
+//   };
+
+//   return (
+//     <Popover
+//       open={open}
+//       onOpenChange={handleOpenChange}
+//       trigger="click"
+//       content={
+//         <div style={{ padding: '8px' }}>
+//           <Input 
+//             type="color" 
+//             value={color} 
+//             onChange={handleColorChange}
+//             style={{ width: 60, cursor: 'pointer' }}
+//           />
+//         </div>
+//       }
+//     >
+//       <Button 
+//         icon={<BgColorsOutlined />} 
+//         style={{ 
+//           backgroundColor: color,
+//           borderColor: color,
+//           color: '#fff'
+//         }}
+//       >
+//         {label}
+//       </Button>
+//     </Popover>
+//   );
+// };
+
+// 基础设置页面
+export const SettingsPage = () => {
+  const [form] = Form.useForm();
+  const queryClient = useQueryClient();
+  const { isDark } = useTheme();
+
+  // 获取系统设置
+  const { data: settingsData, isLoading: isLoadingSettings } = useQuery({
+    queryKey: ['systemSettings'],
+    queryFn: SystemAPI.getSettings,
+  });
+
+  // 更新系统设置
+  const updateSettingsMutation = useMutation({
+    mutationFn: (values: Partial<SystemSetting>[]) => SystemAPI.updateSettings(values),
+    onSuccess: () => {
+      message.success('基础设置已更新');
+      queryClient.invalidateQueries({ queryKey: ['systemSettings'] });
+    },
+    onError: (error) => {
+      message.error('更新基础设置失败');
+      console.error('更新基础设置失败:', error);
+    },
+  });
+
+  // 重置系统设置
+  const resetSettingsMutation = useMutation({
+    mutationFn: SystemAPI.resetSettings,
+    onSuccess: () => {
+      message.success('基础设置已重置');
+      queryClient.invalidateQueries({ queryKey: ['systemSettings'] });
+    },
+    onError: (error) => {
+      message.error('重置基础设置失败');
+      console.error('重置基础设置失败:', error);
+    },
+  });
+
+  // 初始化表单数据
+  useEffect(() => {
+    if (settingsData) {
+      const formValues = settingsData.reduce((acc: Record<string, any>, group) => {
+        group.settings.forEach(setting => {
+          // 根据值的类型进行转换
+          let value = setting.value;
+          if (typeof value === 'string') {
+            if (value === 'true' || value === 'false') {
+              value = value === 'true';
+            } else if (!isNaN(Number(value)) && !value.includes('.')) {
+              value = parseInt(value, 10);
+            } else if (setting.key === SystemSettingKey.ALLOWED_FILE_TYPES) {
+              value = (value ? (value as string).split(',') : []) as unknown as string;
+            }
+          }
+          acc[setting.key] = value;
+        });
+        return acc;
+      }, {});
+      form.setFieldsValue(formValues);
+    }
+  }, [settingsData, form]);
+
+  // 处理表单提交
+  const handleSubmit = async (values: Record<string, SystemSettingValue>) => {
+    const settings = Object.entries(values).map(([key, value]) => ({
+      key: key as typeof SystemSettingKey[keyof typeof SystemSettingKey],
+      value: String(value),
+      group: key.startsWith('SITE_') ? SystemSettingGroup.BASIC :
+             key.startsWith('ENABLE_') || key.includes('LOGIN_') || key.includes('SESSION_') ? SystemSettingGroup.FEATURE :
+             key.includes('UPLOAD_') || key.includes('FILE_') || key.includes('IMAGE_') ? SystemSettingGroup.UPLOAD :
+             SystemSettingGroup.NOTIFICATION
+    }));
+    updateSettingsMutation.mutate(settings);
+  };
+
+  // 处理重置
+  const handleReset = () => {
+    Modal.confirm({
+      title: '确认重置',
+      content: '确定要将所有设置重置为默认值吗?此操作不可恢复。',
+      okText: '确认',
+      cancelText: '取消',
+      onOk: () => {
+        resetSettingsMutation.mutate();
+      },
+    });
+  };
+
+  // 根据设置类型渲染不同的输入控件
+  const renderSettingInput = (setting: SystemSetting) => {
+    const value = setting.value;
+    
+    if (typeof value === 'boolean' || value === 'true' || value === 'false') {
+      return <Switch checkedChildren="开启" unCheckedChildren="关闭" />;
+    }
+    
+    if (setting.key === SystemSettingKey.ALLOWED_FILE_TYPES) {
+      return <Select
+        mode="tags"
+        placeholder="请输入允许的文件类型"
+        tokenSeparators={[',']}
+        options={Object.values(AllowedFileType).map(type => ({
+          label: type.toUpperCase(),
+          value: type
+        }))}
+      />;
+    }
+    
+    if (setting.key.includes('MAX_SIZE') || setting.key.includes('ATTEMPTS') || 
+        setting.key.includes('TIMEOUT') || setting.key.includes('MAX_WIDTH')) {
+      return <InputNumber min={1} style={{ width: '100%' }} />;
+    }
+    
+    if (setting.key === SystemSettingKey.SITE_LOGO || setting.key === SystemSettingKey.SITE_FAVICON) {
+      return (
+        <div>
+          {value && <img src={String(value)} alt="图片" style={{ width: 100, height: 100, objectFit: 'contain', marginBottom: 8 }} />}
+          <div style={{ width: 100 }}>
+            <Uploader
+              maxSize={2 * 1024 * 1024}
+              prefix={setting.key === SystemSettingKey.SITE_LOGO ? 'logo/' : 'favicon/'}
+              allowedTypes={['image/jpeg', 'image/png', 'image/svg+xml', 'image/x-icon']}
+              onSuccess={(fileUrl) => {
+                form.setFieldValue(setting.key, fileUrl);
+                updateSettingsMutation.mutate([{
+                  key: setting.key,
+                  value: fileUrl,
+                  group: SystemSettingGroup.BASIC
+                }]);
+              }}
+              onError={(error) => {
+                message.error(`上传失败:${error.message}`);
+              }}
+            />
+          </div>
+        </div>
+      );
+    }
+    
+    return <Input placeholder={`请输入${setting.description || setting.key}`} />;
+  };
+
+  return (
+    <div>
+      <Card
+        title={
+          <Space>
+            <Title level={2} style={{ margin: 0 }}>系统设置</Title>
+          </Space>
+        }
+        extra={
+          <Space>
+            <Button 
+              icon={<ReloadOutlined />}
+              onClick={handleReset}
+              loading={resetSettingsMutation.isPending}
+            >
+              重置默认
+            </Button>
+          </Space>
+        }
+      >
+        <Spin spinning={isLoadingSettings || updateSettingsMutation.isPending}>
+          <Tabs
+            type="card"
+            items={Object.values(SystemSettingGroup).map(group => ({
+              key: group,
+              label: String(GROUP_TITLES[group]),
+              children: (
+                <div>
+                  <Alert
+                    message={GROUP_DESCRIPTIONS[group]}
+                    type="info"
+                    showIcon
+                    style={{ marginBottom: 24 }}
+                  />
+                  <Form
+                    form={form}
+                    layout="vertical"
+                    onFinish={handleSubmit}
+                  >
+                    {settingsData
+                      ?.find(g => g.name === group)
+                      ?.settings.map(setting => (
+                        <Form.Item
+                          key={setting.key}
+                          label={setting.description || setting.key}
+                          name={setting.key}
+                          rules={[{ required: true, message: `请输入${setting.description || setting.key}` }]}
+                        >
+                          {renderSettingInput(setting)}
+                        </Form.Item>
+                      ))}
+                    <Form.Item>
+                      <Button 
+                        type="primary" 
+                        htmlType="submit"
+                        icon={<SaveOutlined />}
+                        loading={updateSettingsMutation.isPending}
+                      >
+                        保存设置
+                      </Button>
+                    </Form.Item>
+                  </Form>
+                </div>
+              )
+            }))}
+          />
+        </Spin>
+      </Card>
+    </div>
+  );
+};
+
+// 主题设置页面
+export const ThemeSettingsPage = () => {
+  const { isDark, currentTheme, updateTheme, saveTheme, resetTheme } = useTheme();
+  const [form] = Form.useForm();
+  const [loading, setLoading] = useState(false);
+
+  // 处理配色方案选择
+  const handleColorSchemeChange = (schemeName: keyof typeof COLOR_SCHEMES) => {
+    const scheme = COLOR_SCHEMES[schemeName];
+    form.setFieldsValue({
+      primary_color: scheme.primary,
+      background_color: scheme.background,
+      text_color: scheme.text
+    });
+    updateTheme({
+      primary_color: scheme.primary,
+      background_color: scheme.background,
+      text_color: scheme.text
+    });
+  };
+
+  // 初始化表单数据
+  useEffect(() => {
+    form.setFieldsValue({
+      theme_mode: currentTheme.theme_mode,
+      primary_color: currentTheme.primary_color,
+      background_color: currentTheme.background_color || (isDark ? '#141414' : '#f0f2f5'),
+      font_size: currentTheme.font_size,
+      is_compact: currentTheme.is_compact
+    });
+  }, [currentTheme, form, isDark]);
+
+  // 处理表单提交
+  const handleSubmit = async (values: any) => {
+    try {
+      setLoading(true);
+      await saveTheme(values);
+    } catch (error) {
+      message.error('保存主题设置失败');
+    } finally {
+      setLoading(false);
+    }
+  };
+
+  // 处理重置
+  const handleReset = async () => {
+    try {
+      setLoading(true);
+      await resetTheme();
+    } catch (error) {
+      message.error('重置主题设置失败');
+    } finally {
+      setLoading(false);
+    }
+  };
+
+  // 处理表单值变化 - 实时预览
+  const handleValuesChange = (changedValues: any) => {
+    updateTheme(changedValues);
+  };
+
+  return (
+    <div>
+      <Title level={2}>主题设置</Title>
+      <Card>
+        <Spin spinning={loading}>
+          <Form
+            form={form}
+            layout="vertical"
+            onFinish={handleSubmit}
+            onValuesChange={handleValuesChange}
+            initialValues={{
+              theme_mode: currentTheme.theme_mode,
+              primary_color: currentTheme.primary_color,
+              background_color: currentTheme.background_color || (isDark ? '#141414' : '#f0f2f5'),
+              font_size: currentTheme.font_size,
+              is_compact: currentTheme.is_compact
+            }}
+          >
+            {/* 配色方案选择 */}
+            <Form.Item label="预设配色方案">
+              <Space wrap>
+                {Object.entries(COLOR_SCHEMES).map(([key, scheme]) => (
+                  <Button
+                    key={key}
+                    onClick={() => handleColorSchemeChange(key as keyof typeof COLOR_SCHEMES)}
+                    style={{
+                      backgroundColor: scheme.background,
+                      color: scheme.text,
+                      borderColor: scheme.primary
+                    }}
+                  >
+                    {scheme.name}
+                  </Button>
+                ))}
+              </Space>
+            </Form.Item>
+
+            {/* 主题模式 */}
+            <Form.Item
+              label="主题模式"
+              name="theme_mode"
+              rules={[{ required: true, message: '请选择主题模式' }]}
+            >
+              <Radio.Group>
+                <Radio value={ThemeMode.LIGHT}>浅色模式</Radio>
+                <Radio value={ThemeMode.DARK}>深色模式</Radio>
+              </Radio.Group>
+            </Form.Item>
+
+            {/* 主题色 */}
+            <Form.Item
+              label="主题色"
+              name="primary_color"
+              rules={[{ required: true, message: '请选择主题色' }]}
+            >
+              <ColorPicker 
+                value={form.getFieldValue('primary_color')}
+                onChange={(color) => {
+                  form.setFieldValue('primary_color', color.toHexString());
+                  updateTheme({ primary_color: color.toHexString() });
+                }}
+                allowClear
+              />
+            </Form.Item>
+
+            {/* 背景色 */}
+            <Form.Item
+              label="背景色"
+              name="background_color"
+              rules={[{ required: true, message: '请选择背景色' }]}
+            >
+              <ColorPicker 
+                value={form.getFieldValue('background_color')}
+                onChange={(color) => {
+                  form.setFieldValue('background_color', color.toHexString());
+                  updateTheme({ background_color: color.toHexString() });
+                }}
+                allowClear
+              />
+            </Form.Item>
+
+            {/* 文字颜色 */}
+            <Form.Item
+              label="文字颜色"
+              name="text_color"
+              rules={[{ required: true, message: '请选择文字颜色' }]}
+            >
+              <ColorPicker 
+                value={form.getFieldValue('text_color')}
+                onChange={(color) => {
+                  form.setFieldValue('text_color', color.toHexString());
+                  updateTheme({ text_color: color.toHexString() });
+                }}
+                allowClear
+              />
+            </Form.Item>
+
+            {/* 圆角大小 */}
+            <Form.Item
+              label="圆角大小"
+              name="border_radius"
+              rules={[{ required: true, message: '请设置圆角大小' }]}
+              initialValue={6}
+            >
+              <InputNumber<number>
+                min={0} 
+                max={20}
+                addonAfter="px"
+              />
+            </Form.Item>
+
+            {/* 字体大小 */}
+            <Form.Item
+              label="字体大小"
+              name="font_size"
+              rules={[{ required: true, message: '请选择字体大小' }]}
+            >
+              <Radio.Group>
+                <Radio value={FontSize.SMALL}>小</Radio>
+                <Radio value={FontSize.MEDIUM}>中</Radio>
+                <Radio value={FontSize.LARGE}>大</Radio>
+              </Radio.Group>
+            </Form.Item>
+
+            {/* 紧凑模式 */}
+            <Form.Item
+              label="紧凑模式"
+              name="is_compact"
+              valuePropName="checked"
+              getValueFromEvent={(checked: boolean) => checked ? CompactMode.COMPACT : CompactMode.NORMAL}
+              getValueProps={(value: CompactMode) => ({
+                checked: value === CompactMode.COMPACT
+              })}
+            >
+              <Switch 
+                checkedChildren="开启" 
+                unCheckedChildren="关闭"
+              />
+            </Form.Item>
+
+            {/* 操作按钮 */}
+            <Form.Item>
+              <Space>
+                <Button type="primary" htmlType="submit">
+                  保存设置
+                </Button>
+                <Popconfirm
+                  title="确定要重置主题设置吗?"
+                  onConfirm={handleReset}
+                  okText="确定"
+                  cancelText="取消"
+                >
+                  <Button>重置为默认值</Button>
+                </Popconfirm>
+              </Space>
+            </Form.Item>
+          </Form>
+        </Spin>
+      </Card>
+    </div>
+  );
+};

+ 1390 - 0
asset/admin/pages_sys.tsx

@@ -0,0 +1,1390 @@
+import React, { useState, useEffect } from 'react';
+import { 
+  Layout, Menu, Button, Table, Space,
+  Form, Input, Select, message, Modal,
+  Card, Spin, Row, Col, Breadcrumb, Avatar,
+  Dropdown, ConfigProvider, theme, Typography,
+  Switch, Badge, Image, Upload, Divider, Descriptions,
+  Popconfirm, Tag, Statistic, DatePicker, Radio, Progress, Tabs, List, Alert, Collapse, Empty, Drawer
+} from 'antd';
+import {
+  UploadOutlined,
+  FileImageOutlined,
+  FileExcelOutlined,
+  FileWordOutlined,
+  FilePdfOutlined,
+  FileOutlined,
+} from '@ant-design/icons';   
+import { 
+  useQuery,
+} from '@tanstack/react-query';
+import dayjs from 'dayjs';
+import weekday from 'dayjs/plugin/weekday';
+import localeData from 'dayjs/plugin/localeData';
+import { uploadMinIOWithPolicy,uploadOSSWithPolicy } from '@d8d-appcontainer/api';
+import type { MinioUploadPolicy, OSSUploadPolicy } from '@d8d-appcontainer/types';
+import 'dayjs/locale/zh-cn';
+import type { 
+  FileLibrary, FileCategory, KnowInfo
+} from '../share/types.ts';
+
+import {
+   AuditStatus,AuditStatusNameMap,
+   OssType,
+} from '../share/types.ts';
+
+import { getEnumOptions } from './utils.ts';
+
+import {
+  FileAPI,
+  UserAPI,
+} from './api.ts';
+
+
+// 配置 dayjs 插件
+dayjs.extend(weekday);
+dayjs.extend(localeData);
+
+// 设置 dayjs 语言
+dayjs.locale('zh-cn');
+
+const { Title } = Typography;
+
+
+// 仪表盘页面
+export const DashboardPage = () => {
+  return (
+    <div>
+      <Title level={2}>仪表盘</Title>
+      <Row gutter={16}>
+        <Col span={8}>
+          <Card>
+            <Statistic
+              title="活跃用户"
+              value={112893}
+              loading={false}
+            />
+          </Card>
+        </Col>
+        <Col span={8}>
+          <Card>
+            <Statistic
+              title="系统消息"
+              value={93}
+              loading={false}
+            />
+          </Card>
+        </Col>
+        <Col span={8}>
+          <Card>
+            <Statistic
+              title="在线用户"
+              value={1128}
+              loading={false}
+            />
+          </Card>
+        </Col>
+      </Row>
+    </div>
+  );
+};
+
+// 用户管理页面
+export const UsersPage = () => {
+  const [searchParams, setSearchParams] = useState({
+    page: 1,
+    limit: 10,
+    search: ''
+  });
+  const [modalVisible, setModalVisible] = useState(false);
+  const [modalTitle, setModalTitle] = useState('');
+  const [editingUser, setEditingUser] = useState<any>(null);
+  const [form] = Form.useForm();
+
+  const { data: usersData, isLoading, refetch } = useQuery({
+    queryKey: ['users', searchParams],
+    queryFn: async () => {
+      return await UserAPI.getUsers(searchParams);
+    }
+  });
+
+  const users = usersData?.data || [];
+  const pagination = {
+    current: searchParams.page,
+    pageSize: searchParams.limit,
+    total: usersData?.pagination?.total || 0
+  };
+
+  // 处理搜索
+  const handleSearch = (values: any) => {
+    setSearchParams(prev => ({
+      ...prev,
+      search: values.search || '',
+      page: 1
+    }));
+  };
+
+  // 处理分页变化
+  const handleTableChange = (newPagination: any) => {
+    setSearchParams(prev => ({
+      ...prev,
+      page: newPagination.current,
+      limit: newPagination.pageSize
+    }));
+  };
+
+  // 打开创建用户模态框
+  const showCreateModal = () => {
+    setModalTitle('创建用户');
+    setEditingUser(null);
+    form.resetFields();
+    setModalVisible(true);
+  };
+
+  // 打开编辑用户模态框
+  const showEditModal = (user: any) => {
+    setModalTitle('编辑用户');
+    setEditingUser(user);
+    form.setFieldsValue(user);
+    setModalVisible(true);
+  };
+
+  // 处理模态框确认
+  const handleModalOk = async () => {
+    try {
+      const values = await form.validateFields();
+      
+      if (editingUser) {
+        // 编辑用户
+        await UserAPI.updateUser(editingUser.id, values);
+        message.success('用户更新成功');
+      } else {
+        // 创建用户
+        await UserAPI.createUser(values);
+        message.success('用户创建成功');
+      }
+      
+      setModalVisible(false);
+      form.resetFields();
+      refetch(); // 刷新用户列表
+    } catch (error) {
+      console.error('表单提交失败:', error);
+      message.error('操作失败,请重试');
+    }
+  };
+
+  // 处理删除用户
+  const handleDelete = async (id: number) => {
+    try {
+      await UserAPI.deleteUser(id);
+      message.success('用户删除成功');
+      refetch(); // 刷新用户列表
+    } catch (error) {
+      console.error('删除用户失败:', error);
+      message.error('删除失败,请重试');
+    }
+  };
+  
+  const columns = [
+    {
+      title: '用户名',
+      dataIndex: 'username',
+      key: 'username',
+    },
+    {
+      title: '昵称',
+      dataIndex: 'nickname',
+      key: 'nickname',
+    },
+    {
+      title: '邮箱',
+      dataIndex: 'email',
+      key: 'email',
+    },
+    {
+      title: '角色',
+      dataIndex: 'role',
+      key: 'role',
+      render: (role: string) => (
+        <Tag color={role === 'admin' ? 'red' : 'blue'}>
+          {role === 'admin' ? '管理员' : '普通用户'}
+        </Tag>
+      ),
+    },
+    {
+      title: '创建时间',
+      dataIndex: 'created_at',
+      key: 'created_at',
+      render: (date: string) => dayjs(date).format('YYYY-MM-DD HH:mm:ss'),
+    },
+    {
+      title: '操作',
+      key: 'action',
+      render: (_: any, record: any) => (
+        <Space size="middle">
+          <Button type="link" onClick={() => showEditModal(record)}>
+            编辑
+          </Button>
+          <Popconfirm
+            title="确定要删除此用户吗?"
+            onConfirm={() => handleDelete(record.id)}
+            okText="确定"
+            cancelText="取消"
+          >
+            <Button type="link" danger>
+              删除
+            </Button>
+          </Popconfirm>
+        </Space>
+      ),
+    },
+  ];
+  
+  return (
+    <div>
+      <Title level={2}>用户管理</Title>
+      <Card>
+        <Form layout="inline" onFinish={handleSearch} style={{ marginBottom: 16 }}>
+          <Form.Item name="search" label="搜索">
+            <Input placeholder="用户名/昵称/邮箱" allowClear />
+          </Form.Item>
+          <Form.Item>
+            <Space>
+              <Button type="primary" htmlType="submit">
+                搜索
+              </Button>
+              <Button type="primary" onClick={showCreateModal}>
+                创建用户
+              </Button>
+            </Space>
+          </Form.Item>
+        </Form>
+
+        <Table
+          columns={columns}
+          dataSource={users}
+          loading={isLoading}
+          rowKey="id"
+          pagination={{
+            ...pagination,
+            showSizeChanger: true,
+            showQuickJumper: true,
+            showTotal: (total) => `共 ${total} 条记录`
+          }}
+          onChange={handleTableChange}
+        />
+      </Card>
+
+      {/* 创建/编辑用户模态框 */}
+      <Modal
+        title={modalTitle}
+        open={modalVisible}
+        onOk={handleModalOk}
+        onCancel={() => {
+          setModalVisible(false);
+          form.resetFields();
+        }}
+        width={600}
+      >
+        <Form
+          form={form}
+          layout="vertical"
+        >
+          <Form.Item
+            name="username"
+            label="用户名"
+            rules={[
+              { required: true, message: '请输入用户名' },
+              { min: 3, message: '用户名至少3个字符' }
+            ]}
+          >
+            <Input placeholder="请输入用户名" />
+          </Form.Item>
+
+          <Form.Item
+            name="nickname"
+            label="昵称"
+            rules={[{ required: true, message: '请输入昵称' }]}
+          >
+            <Input placeholder="请输入昵称" />
+          </Form.Item>
+
+          <Form.Item
+            name="email"
+            label="邮箱"
+            rules={[
+              { required: true, message: '请输入邮箱' },
+              { type: 'email', message: '请输入有效的邮箱地址' }
+            ]}
+          >
+            <Input placeholder="请输入邮箱" />
+          </Form.Item>
+
+          {!editingUser && (
+            <Form.Item
+              name="password"
+              label="密码"
+              rules={[
+                { required: true, message: '请输入密码' },
+                { min: 6, message: '密码至少6个字符' }
+              ]}
+            >
+              <Input.Password placeholder="请输入密码" />
+            </Form.Item>
+          )}
+
+          <Form.Item
+            name="role"
+            label="角色"
+            rules={[{ required: true, message: '请选择角色' }]}
+          >
+            <Select placeholder="请选择角色">
+              <Select.Option value="user">普通用户</Select.Option>
+              <Select.Option value="admin">管理员</Select.Option>
+            </Select>
+          </Form.Item>
+        </Form>
+      </Modal>
+    </div>
+  );
+};
+
+// 知识库管理页面组件
+export const KnowInfoPage = () => {
+  const [modalVisible, setModalVisible] = useState(false);
+  const [formMode, setFormMode] = useState<'create' | 'edit'>('create');
+  const [editingId, setEditingId] = useState<number | null>(null);
+  const [form] = Form.useForm();
+  const [isLoading, setIsLoading] = useState(false);
+  const [searchParams, setSearchParams] = useState({
+    title: '',
+    category: '',
+    page: 1,
+    limit: 10,
+  });
+  
+  // 使用React Query获取知识库文章列表
+  const { data: articlesData, isLoading: isListLoading, refetch } = useQuery({
+    queryKey: ['articles', searchParams],
+    queryFn: async () => {
+      const { title, category, page, limit } = searchParams;
+      const params = new URLSearchParams();
+      
+      if (title) params.append('title', title);
+      if (category) params.append('category', category);
+      params.append('page', String(page));
+      params.append('limit', String(limit));
+      
+      const response = await fetch(`/api/know-info?${params.toString()}`, {
+        headers: {
+          'Authorization': `Bearer ${localStorage.getItem('token')}`,
+        },
+      });
+      
+      if (!response.ok) {
+        throw new Error('获取知识库文章列表失败');
+      }
+      
+      return await response.json();
+    }
+  });
+  
+  const articles = articlesData?.data || [];
+  const pagination = articlesData?.pagination || { current: 1, pageSize: 10, total: 0 };
+  
+  // 获取单个知识库文章
+  const fetchArticle = async (id: number) => {
+    try {
+      const response = await fetch(`/api/know-info/${id}`, {
+        headers: {
+          'Authorization': `Bearer ${localStorage.getItem('token')}`,
+        },
+      });
+      
+      if (!response.ok) {
+        throw new Error('获取知识库文章详情失败');
+      }
+      
+      return await response.json();
+    } catch (error) {
+      message.error('获取知识库文章详情失败');
+      return null;
+    }
+  };
+  
+  // 处理表单提交
+  const handleSubmit = async (values: Partial<KnowInfo>) => {
+    setIsLoading(true);
+    
+    try {
+      const url = formMode === 'create'
+        ? '/api/know-info'
+        : `/api/know-info/${editingId}`;
+      
+      const method = formMode === 'create' ? 'POST' : 'PUT';
+      
+      const response = await fetch(url, {
+        method,
+        headers: {
+          'Content-Type': 'application/json',
+          'Authorization': `Bearer ${localStorage.getItem('token')}`,
+        },
+        body: JSON.stringify(values),
+      });
+      
+      if (!response.ok) {
+        throw new Error(formMode === 'create' ? '创建知识库文章失败' : '更新知识库文章失败');
+      }
+      
+      message.success(formMode === 'create' ? '创建知识库文章成功' : '更新知识库文章成功');
+      setModalVisible(false);
+      form.resetFields();
+      refetch();
+    } catch (error) {
+      message.error((error as Error).message);
+    } finally {
+      setIsLoading(false);
+    }
+  };
+  
+  // 处理编辑
+  const handleEdit = async (id: number) => {
+    const article = await fetchArticle(id);
+    
+    if (article) {
+      setFormMode('edit');
+      setEditingId(id);
+      form.setFieldsValue(article);
+      setModalVisible(true);
+    }
+  };
+  
+  // 处理删除
+  const handleDelete = async (id: number) => {
+    try {
+      const response = await fetch(`/api/know-info/${id}`, {
+        method: 'DELETE',
+        headers: {
+          'Authorization': `Bearer ${localStorage.getItem('token')}`,
+        },
+      });
+      
+      if (!response.ok) {
+        throw new Error('删除知识库文章失败');
+      }
+      
+      message.success('删除知识库文章成功');
+      refetch();
+    } catch (error) {
+      message.error((error as Error).message);
+    }
+  };
+  
+  // 处理搜索
+  const handleSearch = (values: any) => {
+    setSearchParams(prev => ({
+      ...prev,
+      title: values.title || '',
+      category: values.category || '',
+      page: 1,
+    }));
+  };
+  
+  // 处理分页
+  const handlePageChange = (page: number, pageSize?: number) => {
+    setSearchParams(prev => ({
+      ...prev,
+      page,
+      limit: pageSize || prev.limit,
+    }));
+  };
+  
+  // 处理添加
+  const handleAdd = () => {
+    setFormMode('create');
+    setEditingId(null);
+    form.resetFields();
+    setModalVisible(true);
+  };
+  
+  // 审核状态映射
+  const auditStatusOptions = getEnumOptions(AuditStatus, AuditStatusNameMap);
+  
+  // 表格列定义
+  const columns = [
+    {
+      title: 'ID',
+      dataIndex: 'id',
+      key: 'id',
+      width: 80,
+    },
+    {
+      title: '标题',
+      dataIndex: 'title',
+      key: 'title',
+    },
+    {
+      title: '分类',
+      dataIndex: 'category',
+      key: 'category',
+    },
+    {
+      title: '标签',
+      dataIndex: 'tags',
+      key: 'tags',
+      render: (tags: string) => tags ? tags.split(',').map(tag => (
+        <Tag key={tag}>{tag}</Tag>
+      )) : null,
+    },
+    {
+      title: '作者',
+      dataIndex: 'author',
+      key: 'author',
+    },
+    {
+      title: '审核状态',
+      dataIndex: 'audit_status',
+      key: 'audit_status',
+      render: (status: AuditStatus) => {
+        let color = '';
+        let text = '';
+        
+        switch(status) {
+          case AuditStatus.PENDING:
+            color = 'orange';
+            text = '待审核';
+            break;
+          case AuditStatus.APPROVED:
+            color = 'green';
+            text = '已通过';
+            break;
+          case AuditStatus.REJECTED:
+            color = 'red';
+            text = '已拒绝';
+            break;
+          default:
+            color = 'default';
+            text = '未知';
+        }
+        
+        return <Tag color={color}>{text}</Tag>;
+      },
+    },
+    {
+      title: '创建时间',
+      dataIndex: 'created_at',
+      key: 'created_at',
+      render: (date: string) => new Date(date).toLocaleString(),
+    },
+    {
+      title: '操作',
+      key: 'action',
+      render: (_: any, record: KnowInfo) => (
+        <Space size="middle">
+          <Button type="link" onClick={() => handleEdit(record.id)}>编辑</Button>
+          <Popconfirm
+            title="确定要删除这篇文章吗?"
+            onConfirm={() => handleDelete(record.id)}
+            okText="确定"
+            cancelText="取消"
+          >
+            <Button type="link" danger>删除</Button>
+          </Popconfirm>
+        </Space>
+      ),
+    },
+  ];
+  
+  return (
+    <div>
+      <Card title="知识库管理" className="mb-4">
+        <Form
+          layout="inline"
+          onFinish={handleSearch}
+          style={{ marginBottom: '16px' }}
+        >
+          <Form.Item name="title" label="标题">
+            <Input placeholder="请输入文章标题" />
+          </Form.Item>
+          
+          <Form.Item name="category" label="分类">
+            <Input placeholder="请输入文章分类" />
+          </Form.Item>
+          
+          <Form.Item>
+            <Space>
+              <Button type="primary" htmlType="submit">
+                搜索
+              </Button>
+              <Button onClick={() => {
+                setSearchParams({
+                  title: '',
+                  category: '',
+                  page: 1,
+                  limit: 10,
+                });
+              }}>
+                重置
+              </Button>
+              <Button type="primary" onClick={handleAdd}>
+                添加文章
+              </Button>
+            </Space>
+          </Form.Item>
+        </Form>
+        
+        <Table
+          columns={columns}
+          dataSource={articles}
+          rowKey="id"
+          loading={isListLoading}
+          pagination={{
+            current: pagination.current,
+            pageSize: pagination.pageSize,
+            total: pagination.total,
+            onChange: handlePageChange,
+            showSizeChanger: true,
+          }}
+        />
+      </Card>
+      
+      <Modal
+        title={formMode === 'create' ? '添加知识库文章' : '编辑知识库文章'}
+        open={modalVisible}
+        onOk={() => form.submit()}
+        onCancel={() => setModalVisible(false)}
+        width={800}
+      >
+        <Form
+          form={form}
+          layout="vertical"
+          onFinish={handleSubmit}
+          initialValues={{
+            audit_status: AuditStatus.PENDING,
+          }}
+        >
+          <Row gutter={16}>
+            <Col span={12}>
+              <Form.Item
+                name="title"
+                label="文章标题"
+                rules={[{ required: true, message: '请输入文章标题' }]}
+              >
+                <Input placeholder="请输入文章标题" />
+              </Form.Item>
+            </Col>
+            <Col span={12}>
+              <Form.Item
+                name="category"
+                label="文章分类"
+              >
+                <Input placeholder="请输入文章分类" />
+              </Form.Item>
+            </Col>
+          </Row>
+          
+          <Form.Item
+            name="tags"
+            label="文章标签"
+            help="多个标签请用英文逗号分隔,如: 服务器,网络,故障"
+          >
+            <Input placeholder="请输入文章标签,多个标签请用英文逗号分隔" />
+          </Form.Item>
+          
+          <Form.Item
+            name="content"
+            label="文章内容"
+            rules={[{ required: true, message: '请输入文章内容' }]}
+          >
+            <Input.TextArea rows={15} placeholder="请输入文章内容,支持Markdown格式" />
+          </Form.Item>
+          
+          <Row gutter={16}>
+            <Col span={12}>
+              <Form.Item
+                name="author"
+                label="文章作者"
+              >
+                <Input placeholder="请输入文章作者" />
+              </Form.Item>
+            </Col>
+            <Col span={12}>
+              <Form.Item
+                name="cover_url"
+                label="封面图片URL"
+              >
+                <Input placeholder="请输入封面图片URL" />
+              </Form.Item>
+            </Col>
+          </Row>
+          
+          <Form.Item
+            name="audit_status"
+            label="审核状态"
+          >
+            <Select options={auditStatusOptions} />
+          </Form.Item>
+          
+          <Form.Item>
+            <Space>
+              <Button type="primary" htmlType="submit" loading={isLoading}>
+                {formMode === 'create' ? '创建' : '保存'}
+              </Button>
+              <Button onClick={() => setModalVisible(false)}>取消</Button>
+            </Space>
+          </Form.Item>
+        </Form>
+      </Modal>
+    </div>
+  );
+};
+
+// 文件库管理页面
+export const FileLibraryPage = () => {
+  const [loading, setLoading] = useState(false);
+  const [fileList, setFileList] = useState<FileLibrary[]>([]);
+  const [categories, setCategories] = useState<FileCategory[]>([]);
+  const [pagination, setPagination] = useState({
+    current: 1,
+    pageSize: 10,
+    total: 0
+  });
+  const [searchParams, setSearchParams] = useState({
+    fileType: '',
+    keyword: ''
+  });
+  const [uploadModalVisible, setUploadModalVisible] = useState(false);
+  const [fileDetailModalVisible, setFileDetailModalVisible] = useState(false);
+  const [currentFile, setCurrentFile] = useState<FileLibrary | null>(null);
+  const [uploadLoading, setUploadLoading] = useState(false);
+  const [form] = Form.useForm();
+  const [categoryForm] = Form.useForm();
+  const [categoryModalVisible, setCategoryModalVisible] = useState(false);
+  const [currentCategory, setCurrentCategory] = useState<FileCategory | null>(null);
+
+  // 获取文件图标
+  const getFileIcon = (fileType: string) => {
+    if (fileType.includes('image')) {
+      return <FileImageOutlined style={{ fontSize: '24px', color: '#1890ff' }} />;
+    } else if (fileType.includes('pdf')) {
+      return <FilePdfOutlined style={{ fontSize: '24px', color: '#ff4d4f' }} />;
+    } else if (fileType.includes('excel') || fileType.includes('sheet')) {
+      return <FileExcelOutlined style={{ fontSize: '24px', color: '#52c41a' }} />;
+    } else if (fileType.includes('word') || fileType.includes('document')) {
+      return <FileWordOutlined style={{ fontSize: '24px', color: '#2f54eb' }} />;
+    } else {
+      return <FileOutlined style={{ fontSize: '24px', color: '#faad14' }} />;
+    }
+  };
+
+  // 加载文件列表
+  const fetchFileList = async () => {
+    setLoading(true);
+    try {
+      const response = await FileAPI.getFileList({
+        page: pagination.current,
+        pageSize: pagination.pageSize,
+        ...searchParams
+      });
+      
+      if (response && response.data) {
+        setFileList(response.data.list);
+        setPagination({
+          ...pagination,
+          total: response.data.pagination.total
+        });
+      }
+    } catch (error) {
+      console.error('获取文件列表失败:', error);
+      message.error('获取文件列表失败');
+    } finally {
+      setLoading(false);
+    }
+  };
+
+  // 加载文件分类
+  const fetchCategories = async () => {
+    try {
+      const response = await FileAPI.getCategories();
+      if (response && response.data) {
+        setCategories(response.data);
+      }
+    } catch (error) {
+      console.error('获取文件分类失败:', error);
+      message.error('获取文件分类失败');
+    }
+  };
+
+  // 组件挂载时加载数据
+  useEffect(() => {
+    fetchFileList();
+    fetchCategories();
+  }, [pagination.current, pagination.pageSize, searchParams]);
+
+  // 上传文件
+  const handleUpload = async (file: File) => {
+    try {
+      setUploadLoading(true);
+      
+      // 1. 获取上传策略
+      const policyResponse = await FileAPI.getUploadPolicy(file.name);
+      if (!policyResponse || !policyResponse.data) {
+        throw new Error('获取上传策略失败');
+      }
+      
+      const policy = policyResponse.data;
+      
+      // 2. 上传文件至 MinIO
+      const uploadProgress = {
+        progress: 0,
+        completed: false,
+        error: null as Error | null
+      };
+      
+      const callbacks = {
+        onProgress: (event: { progress: number }) => {
+          uploadProgress.progress = event.progress;
+        },
+        onComplete: () => {
+          uploadProgress.completed = true;
+        },
+        onError: (err: Error) => {
+          uploadProgress.error = err;
+        }
+      };
+      
+      const uploadUrl = window.CONFIG?.OSS_TYPE === OssType.MINIO ? await uploadMinIOWithPolicy(
+        policy as MinioUploadPolicy,
+        file,
+        file.name,
+        callbacks
+      ) : await uploadOSSWithPolicy(
+        policy as OSSUploadPolicy,
+        file,
+        file.name,
+        callbacks
+      );
+      
+      if (!uploadUrl || uploadProgress.error) {
+        throw uploadProgress.error || new Error('上传文件失败');
+      }
+      
+      // 3. 保存文件信息到文件库
+      const fileValues = form.getFieldsValue();
+      const fileData = {
+        file_name: file.name,
+        file_path: uploadUrl,
+        file_type: file.type,
+        file_size: file.size,
+        category_id: fileValues.category_id ? Number(fileValues.category_id) : undefined,
+        tags: fileValues.tags,
+        description: fileValues.description
+      };
+      
+      const saveResponse = await FileAPI.saveFileInfo(fileData);
+      
+      if (saveResponse && saveResponse.data) {
+        message.success('文件上传成功');
+        setUploadModalVisible(false);
+        form.resetFields();
+        fetchFileList();
+      }
+    } catch (error) {
+      console.error('上传文件失败:', error);
+      message.error('上传文件失败: ' + (error instanceof Error ? error.message : '未知错误'));
+    } finally {
+      setUploadLoading(false);
+    }
+  };
+
+  // 处理文件上传
+  const uploadProps = {
+    name: 'file',
+    multiple: false,
+    showUploadList: false,
+    beforeUpload: (file: File) => {
+      const isLt10M = file.size / 1024 / 1024 < 10;
+      if (!isLt10M) {
+        message.error('文件大小不能超过10MB!');
+        return false;
+      }
+      handleUpload(file);
+      return false;
+    }
+  };
+
+  // 查看文件详情
+  const viewFileDetail = async (id: number) => {
+    try {
+      const response = await FileAPI.getFileInfo(id);
+      if (response && response.data) {
+        setCurrentFile(response.data);
+        setFileDetailModalVisible(true);
+      }
+    } catch (error) {
+      console.error('获取文件详情失败:', error);
+      message.error('获取文件详情失败');
+    }
+  };
+
+  // 下载文件
+  const downloadFile = async (file: FileLibrary) => {
+    try {
+      // 更新下载计数
+      await FileAPI.updateDownloadCount(file.id);
+      
+      // 创建一个暂时的a标签用于下载
+      const link = document.createElement('a');
+      link.href = file.file_path;
+      link.target = '_blank';
+      link.download = file.file_name;
+      document.body.appendChild(link);
+      link.click();
+      document.body.removeChild(link);
+      
+      message.success('下载已开始');
+    } catch (error) {
+      console.error('下载文件失败:', error);
+      message.error('下载文件失败');
+    }
+  };
+
+  // 删除文件
+  const handleDeleteFile = async (id: number) => {
+    try {
+      await FileAPI.deleteFile(id);
+      message.success('文件删除成功');
+      fetchFileList();
+    } catch (error) {
+      console.error('删除文件失败:', error);
+      message.error('删除文件失败');
+    }
+  };
+
+  // 处理搜索
+  const handleSearch = (values: any) => {
+    setSearchParams(values);
+    setPagination({
+      ...pagination,
+      current: 1
+    });
+  };
+
+  // 处理表格分页变化
+  const handleTableChange = (newPagination: any) => {
+    setPagination({
+      ...pagination,
+      current: newPagination.current,
+      pageSize: newPagination.pageSize
+    });
+  };
+
+  // 添加或更新分类
+  const handleCategorySave = async () => {
+    try {
+      const values = await categoryForm.validateFields();
+      
+      if (currentCategory) {
+        // 更新分类
+        await FileAPI.updateCategory(currentCategory.id, values);
+        message.success('分类更新成功');
+      } else {
+        // 创建分类
+        await FileAPI.createCategory(values);
+        message.success('分类创建成功');
+      }
+      
+      setCategoryModalVisible(false);
+      categoryForm.resetFields();
+      setCurrentCategory(null);
+      fetchCategories();
+    } catch (error) {
+      console.error('保存分类失败:', error);
+      message.error('保存分类失败');
+    }
+  };
+
+  // 编辑分类
+  const handleEditCategory = (category: FileCategory) => {
+    setCurrentCategory(category);
+    categoryForm.setFieldsValue(category);
+    setCategoryModalVisible(true);
+  };
+
+  // 删除分类
+  const handleDeleteCategory = async (id: number) => {
+    try {
+      await FileAPI.deleteCategory(id);
+      message.success('分类删除成功');
+      fetchCategories();
+    } catch (error) {
+      console.error('删除分类失败:', error);
+      message.error('删除分类失败');
+    }
+  };
+
+  // 文件表格列配置
+  const columns = [
+    {
+      title: '文件名',
+      key: 'file_name',
+      render: (text: string, record: FileLibrary) => (
+        <Space>
+          {getFileIcon(record.file_type)}
+          <a onClick={() => viewFileDetail(record.id)}>
+            {record.original_filename || record.file_name}
+          </a>
+        </Space>
+      )
+    },
+    {
+      title: '文件类型',
+      dataIndex: 'file_type',
+      key: 'file_type',
+      width: 120,
+      render: (text: string) => text.split('/').pop()
+    },
+    {
+      title: '大小',
+      dataIndex: 'file_size',
+      key: 'file_size',
+      width: 100,
+      render: (size: number) => {
+        if (size < 1024) {
+          return `${size} B`;
+        } else if (size < 1024 * 1024) {
+          return `${(size / 1024).toFixed(2)} KB`;
+        } else {
+          return `${(size / 1024 / 1024).toFixed(2)} MB`;
+        }
+      }
+    },
+    {
+      title: '分类',
+      dataIndex: 'category_id',
+      key: 'category_id',
+      width: 120
+    },
+    {
+      title: '上传者',
+      dataIndex: 'uploader_name',
+      key: 'uploader_name',
+      width: 120
+    },
+    {
+      title: '下载次数',
+      dataIndex: 'download_count',
+      key: 'download_count',
+      width: 120
+    },
+    {
+      title: '上传时间',
+      dataIndex: 'created_at',
+      key: 'created_at',
+      width: 180,
+      render: (date: string) => dayjs(date).format('YYYY-MM-DD HH:mm:ss')
+    },
+    {
+      title: '操作',
+      key: 'action',
+      width: 180,
+      render: (_: any, record: FileLibrary) => (
+        <Space size="middle">
+          <Button type="link" onClick={() => downloadFile(record)}>
+            下载
+          </Button>
+          <Popconfirm
+            title="确定要删除这个文件吗?"
+            onConfirm={() => handleDeleteFile(record.id)}
+            okText="确定"
+            cancelText="取消"
+          >
+            <Button type="link" danger>删除</Button>
+          </Popconfirm>
+        </Space>
+      )
+    }
+  ];
+
+  // 分类表格列配置
+  const categoryColumns = [
+    {
+      title: '分类名称',
+      dataIndex: 'name',
+      key: 'name'
+    },
+    {
+      title: '分类编码',
+      dataIndex: 'code',
+      key: 'code'
+    },
+    {
+      title: '描述',
+      dataIndex: 'description',
+      key: 'description'
+    },
+    {
+      title: '操作',
+      key: 'action',
+      render: (_: any, record: FileCategory) => (
+        <Space size="middle">
+          <Button type="link" onClick={() => handleEditCategory(record)}>
+            编辑
+          </Button>
+          <Popconfirm
+            title="确定要删除这个分类吗?"
+            onConfirm={() => handleDeleteCategory(record.id)}
+            okText="确定"
+            cancelText="取消"
+          >
+            <Button type="link" danger>删除</Button>
+          </Popconfirm>
+        </Space>
+      )
+    }
+  ];
+
+  return (
+    <div>
+      <Title level={2}>文件库管理</Title>
+      
+      <Card>
+        <Tabs defaultActiveKey="files">
+          <Tabs.TabPane tab="文件管理" key="files">
+            {/* 搜索表单 */}
+            <Form layout="inline" onFinish={handleSearch} style={{ marginBottom: 16 }}>
+              <Form.Item name="keyword" label="关键词">
+                <Input placeholder="文件名/描述/标签" allowClear />
+              </Form.Item>
+              <Form.Item name="category_id" label="分类">
+                <Select placeholder="选择分类" allowClear style={{ width: 160 }}>
+                  {categories.map(category => (
+                    <Select.Option key={category.id} value={category.id}>
+                      {category.name}
+                    </Select.Option>
+                  ))}
+                </Select>
+              </Form.Item>
+              <Form.Item name="fileType" label="文件类型">
+                <Select placeholder="选择文件类型" allowClear style={{ width: 160 }}>
+                  <Select.Option value="image">图片</Select.Option>
+                  <Select.Option value="document">文档</Select.Option>
+                  <Select.Option value="application">应用</Select.Option>
+                  <Select.Option value="audio">音频</Select.Option>
+                  <Select.Option value="video">视频</Select.Option>
+                </Select>
+              </Form.Item>
+              <Form.Item>
+                <Button type="primary" htmlType="submit">
+                  搜索
+                </Button>
+              </Form.Item>
+              <Button 
+                type="primary" 
+                onClick={() => setUploadModalVisible(true)}
+                icon={<UploadOutlined />}
+                style={{ marginLeft: 16 }}
+              >
+                上传文件
+              </Button>
+            </Form>
+            
+            {/* 文件列表 */}
+            <Table
+              columns={columns}
+              dataSource={fileList}
+              rowKey="id"
+              loading={loading}
+              pagination={{
+                current: pagination.current,
+                pageSize: pagination.pageSize,
+                total: pagination.total,
+                showSizeChanger: true,
+                showQuickJumper: true,
+                showTotal: (total) => `共 ${total} 条记录`
+              }}
+              onChange={handleTableChange}
+            />
+          </Tabs.TabPane>
+          
+          <Tabs.TabPane tab="分类管理" key="categories">
+            <div style={{ marginBottom: 16 }}>
+              <Button 
+                type="primary" 
+                onClick={() => {
+                  setCurrentCategory(null);
+                  categoryForm.resetFields();
+                  setCategoryModalVisible(true);
+                }}
+              >
+                添加分类
+              </Button>
+            </div>
+            
+            <Table
+              columns={categoryColumns}
+              dataSource={categories}
+              rowKey="id"
+              pagination={{ pageSize: 10 }}
+            />
+          </Tabs.TabPane>
+        </Tabs>
+      </Card>
+      
+      {/* 上传文件弹窗 */}
+      <Modal
+        title="上传文件"
+        open={uploadModalVisible}
+        onCancel={() => setUploadModalVisible(false)}
+        footer={null}
+      >
+        <Form form={form} layout="vertical">
+          <Form.Item
+            name="file"
+            label="文件"
+            rules={[{ required: true, message: '请选择要上传的文件' }]}
+          >
+            <Upload {...uploadProps}>
+              <Button icon={<UploadOutlined />} loading={uploadLoading}>
+                选择文件
+              </Button>
+              <div style={{ marginTop: 8 }}>
+                支持任意类型文件,单个文件不超过10MB
+              </div>
+            </Upload>
+          </Form.Item>
+          
+          <Form.Item
+            name="category_id"
+            label="分类"
+          >
+            <Select placeholder="选择分类" allowClear>
+              {categories.map(category => (
+                <Select.Option key={category.id} value={category.id}>
+                  {category.name}
+                </Select.Option>
+              ))}
+            </Select>
+          </Form.Item>
+          
+          <Form.Item
+            name="tags"
+            label="标签"
+          >
+            <Input placeholder="多个标签用逗号分隔" />
+          </Form.Item>
+          
+          <Form.Item
+            name="description"
+            label="描述"
+          >
+            <Input.TextArea rows={4} placeholder="文件描述..." />
+          </Form.Item>
+        </Form>
+      </Modal>
+      
+      {/* 文件详情弹窗 */}
+      <Modal
+        title="文件详情"
+        open={fileDetailModalVisible}
+        onCancel={() => setFileDetailModalVisible(false)}
+        footer={[
+          <Button key="close" onClick={() => setFileDetailModalVisible(false)}>
+            关闭
+          </Button>,
+          <Button 
+            key="download" 
+            type="primary" 
+            onClick={() => currentFile && downloadFile(currentFile)}
+          >
+            下载
+          </Button>
+        ]}
+        width={700}
+      >
+        {currentFile && (
+          <Descriptions bordered column={2}>
+            <Descriptions.Item label="系统文件名" span={2}>
+              {currentFile.file_name}
+            </Descriptions.Item>
+            {currentFile.original_filename && (
+              <Descriptions.Item label="原始文件名" span={2}>
+                {currentFile.original_filename}
+              </Descriptions.Item>
+            )}
+            <Descriptions.Item label="文件类型">
+              {currentFile.file_type}
+            </Descriptions.Item>
+            <Descriptions.Item label="文件大小">
+              {currentFile.file_size < 1024 * 1024 
+                ? `${(currentFile.file_size / 1024).toFixed(2)} KB` 
+                : `${(currentFile.file_size / 1024 / 1024).toFixed(2)} MB`}
+            </Descriptions.Item>
+            <Descriptions.Item label="上传者">
+              {currentFile.uploader_name}
+            </Descriptions.Item>
+            <Descriptions.Item label="上传时间">
+              {dayjs(currentFile.created_at).format('YYYY-MM-DD HH:mm:ss')}
+            </Descriptions.Item>
+            <Descriptions.Item label="分类">
+              {currentFile.category_id}
+            </Descriptions.Item>
+            <Descriptions.Item label="下载次数">
+              {currentFile.download_count}
+            </Descriptions.Item>
+            <Descriptions.Item label="标签" span={2}>
+              {currentFile.tags?.split(',').map(tag => (
+                <Tag key={tag}>{tag}</Tag>
+              ))}
+            </Descriptions.Item>
+            <Descriptions.Item label="描述" span={2}>
+              {currentFile.description}
+            </Descriptions.Item>
+            {currentFile.file_type.startsWith('image/') && (
+              <Descriptions.Item label="预览" span={2}>
+                <Image src={currentFile.file_path} style={{ maxWidth: '100%' }} />
+              </Descriptions.Item>
+            )}
+          </Descriptions>
+        )}
+      </Modal>
+      
+      {/* 分类管理弹窗 */}
+      <Modal
+        title={currentCategory ? "编辑分类" : "添加分类"}
+        open={categoryModalVisible}
+        onOk={handleCategorySave}
+        onCancel={() => {
+          setCategoryModalVisible(false);
+          categoryForm.resetFields();
+          setCurrentCategory(null);
+        }}
+      >
+        <Form form={categoryForm} layout="vertical">
+          <Form.Item
+            name="name"
+            label="分类名称"
+            rules={[{ required: true, message: '请输入分类名称' }]}
+          >
+            <Input placeholder="请输入分类名称" />
+          </Form.Item>
+          
+          <Form.Item
+            name="code"
+            label="分类编码"
+            rules={[{ required: true, message: '请输入分类编码' }]}
+          >
+            <Input placeholder="请输入分类编码" />
+          </Form.Item>
+          
+          <Form.Item
+            name="description"
+            label="分类描述"
+          >
+            <Input.TextArea rows={4} placeholder="分类描述..." />
+          </Form.Item>
+        </Form>
+      </Modal>
+    </div>
+  );
+};

+ 35 - 0
asset/admin/style_amap.css

@@ -0,0 +1,35 @@
+
+.marker-label {
+  color: #333;
+  font-size: 12px;
+  background: #fff;
+  padding: 2px 6px;
+  border: 1px solid #ccc;
+  border-radius: 2px;
+  white-space: nowrap;
+  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
+  display: flex;
+  align-items: center;
+  gap: 4px;
+}
+
+.amap-marker-label {
+    border: none !important;
+    background: none !important;
+}
+
+.status-dot {
+  display: inline-block;
+  width: 8px;
+  height: 8px;
+  border-radius: 50%;
+  margin-left: 4px;
+}
+
+.status-dot.online {
+  background-color: #4CAF50;
+}
+
+.status-dot.offline {
+  background-color: #FF5252;
+}

+ 9 - 0
asset/admin/utils.ts

@@ -0,0 +1,9 @@
+export function getEnumOptions<T extends string | number, M extends Record<T, string>>(enumObj: Record<string, T>, nameMap: M) {
+  return Object.entries(enumObj)
+    .filter(([_key, value]) => !isNaN(Number(value)) || typeof value === 'string')  // 保留数字和字符串类型的值
+    .filter(([key, _value]) => isNaN(Number(key)))  // 过滤掉数字键(枚举的反向映射)
+    .map(([_key, value]) => ({
+      label: nameMap[value as T],
+      value: value
+    }));
+}

+ 575 - 0
asset/admin/web_app.tsx

@@ -0,0 +1,575 @@
+import React, { useState, useEffect, createContext, useContext } from 'react';
+import { createRoot } from 'react-dom/client';
+import { 
+  createBrowserRouter,
+  RouterProvider,
+  Outlet,
+  useNavigate,
+  useLocation,
+  Navigate,
+  useParams
+} from 'react-router';
+import { 
+  Layout, Menu, Button, Table, Space,
+  Form, Input, Select, message, Modal,
+  Card, Spin, Row, Col, Breadcrumb, Avatar,
+  Dropdown, ConfigProvider, theme, Typography,
+  Switch, Badge, Image, Upload, Divider, Descriptions,
+  Popconfirm, Tag, Statistic, DatePicker, Radio, Progress, Tabs, List, Alert, Collapse, Empty, Drawer
+} from 'antd';
+import zhCN from "antd/locale/zh_CN";
+import {
+  MenuFoldOutlined,
+  MenuUnfoldOutlined,
+  UserOutlined,
+  DashboardOutlined,
+  TeamOutlined,
+  SettingOutlined,
+  LogoutOutlined,
+  BellOutlined,
+  BookOutlined,
+  FileOutlined,
+  PieChartOutlined,
+  UploadOutlined,
+  GlobalOutlined,
+  VerticalAlignTopOutlined,
+  CloseOutlined,
+  SearchOutlined
+} from '@ant-design/icons';   
+import { 
+  QueryClient,
+  QueryClientProvider,
+  useQuery,
+  useMutation,
+  useQueryClient
+} from '@tanstack/react-query';
+import axios from 'axios';
+import dayjs from 'dayjs';
+import weekday from 'dayjs/plugin/weekday';
+import localeData from 'dayjs/plugin/localeData';
+import { uploadMinIOWithPolicy } from '@d8d-appcontainer/api';
+import type { MinioUploadPolicy } from '@d8d-appcontainer/types';
+import { Line, Pie, Column } from "@ant-design/plots";
+import 'dayjs/locale/zh-cn';
+import type { 
+  GlobalConfig
+} from '../share/types.ts';
+
+import {
+  EnableStatus, DeleteStatus, ThemeMode, FontSize, CompactMode
+} from '../share/types.ts';
+
+import { getEnumOptions } from './utils.ts';
+
+import {
+  AuthProvider,
+  useAuth,
+  ThemeProvider,
+  useTheme,
+} from './hooks_sys.tsx';
+
+import {
+  DashboardPage,
+  UsersPage,
+  KnowInfoPage,
+  FileLibraryPage
+} from './pages_sys.tsx';
+import { 
+  SettingsPage,
+  ThemeSettingsPage,
+ } from './pages_settings.tsx';
+ import {
+  ChartDashboardPage,
+ } from './pages_chart.tsx';
+ import {
+  LoginMapPage
+ } from './pages_map.tsx';
+
+import {
+  LoginPage,
+} from './pages_login_reg.tsx';
+
+
+// 配置 dayjs 插件
+dayjs.extend(weekday);
+dayjs.extend(localeData);
+
+// 设置 dayjs 语言
+dayjs.locale('zh-cn');
+
+const { Header, Sider, Content } = Layout;
+
+// 创建QueryClient实例
+const queryClient = new QueryClient();
+
+
+// 声明全局配置对象类型
+declare global {
+  interface Window {
+    CONFIG?: GlobalConfig;
+  }
+}
+
+
+// 主布局组件
+const MainLayout = () => {
+  const [collapsed, setCollapsed] = useState(false);
+  const { user, logout } = useAuth();
+  const { isDark, toggleTheme } = useTheme();
+  const navigate = useNavigate();
+  const location = useLocation();
+  const [openKeys, setOpenKeys] = useState<string[]>([]);
+  const [showBackTop, setShowBackTop] = useState(false);
+  const [searchText, setSearchText] = useState('');
+  const [filteredMenuItems, setFilteredMenuItems] = useState<any[]>([]);
+  
+  // 检测滚动位置,控制回到顶部按钮显示
+  useEffect(() => {
+    const handleScroll = () => {
+      setShowBackTop(window.pageYOffset > 300);
+    };
+    
+    window.addEventListener('scroll', handleScroll);
+    return () => window.removeEventListener('scroll', handleScroll);
+  }, []);
+  
+  // 回到顶部
+  const scrollToTop = () => {
+    window.scrollTo({
+      top: 0,
+      behavior: 'smooth'
+    });
+  };
+  
+  // 菜单项配置
+  const menuItems = [
+    {
+      key: '/dashboard',
+      icon: <DashboardOutlined />,
+      label: '仪表盘',
+    },
+    {
+      key: '/analysis',
+      icon: <PieChartOutlined />,
+      label: '数据分析',
+      children: [
+        {
+          key: '/chart-dashboard',
+          label: '图表统计',
+        },
+        {
+          key: '/map-dashboard',
+          label: '地图概览',
+        },
+      ],
+    },
+    {
+      key: '/files',
+      icon: <FileOutlined />,
+      label: '文件管理',
+      children: [
+        {
+          key: '/file-library',
+          label: '文件库',
+        },
+      ],
+    },
+    {
+      key: '/know-info',
+      icon: <BookOutlined />,
+      label: '知识库',
+    },
+    {
+      key: '/users',
+      icon: <TeamOutlined />,
+      label: '用户管理',
+    },
+    {
+      key: '/settings',
+      icon: <SettingOutlined />,
+      label: '系统设置',
+      children: [
+        {
+          key: '/theme-settings',
+          label: '主题设置',
+        },
+        {
+          key: '/settings',
+          label: '基本设置',
+        },
+      ],
+    },
+  ];
+  
+  // 初始化filteredMenuItems
+  useEffect(() => {
+    setFilteredMenuItems(menuItems);
+  }, []);
+
+  // 搜索菜单项
+  const handleSearch = (value: string) => {
+    setSearchText(value);
+    
+    if (!value.trim()) {
+      setFilteredMenuItems(menuItems);
+      return;
+    }
+    
+    // 搜索功能 - 过滤菜单项
+    const filtered = menuItems.reduce((acc: any[], item) => {
+      // 检查主菜单项是否匹配
+      const mainItemMatch = item.label.toString().toLowerCase().includes(value.toLowerCase());
+      
+      // 如果有子菜单,检查子菜单中是否有匹配项
+      if (item.children) {
+        const matchedChildren = item.children.filter(child => 
+          child.label.toString().toLowerCase().includes(value.toLowerCase())
+        );
+        
+        if (matchedChildren.length > 0) {
+          // 如果有匹配的子菜单,创建包含匹配子菜单的副本
+          acc.push({
+            ...item,
+            children: matchedChildren
+          });
+          return acc;
+        }
+      }
+      
+      // 如果主菜单项匹配,添加整个项
+      if (mainItemMatch) {
+        acc.push(item);
+      }
+      
+      return acc;
+    }, []);
+    
+    setFilteredMenuItems(filtered);
+  };
+  
+  // 清除搜索
+  const clearSearch = () => {
+    setSearchText('');
+    setFilteredMenuItems(menuItems);
+  };
+
+  const handleMenuClick = ({ key }: { key: string }) => {
+    navigate(`/admin${key}`);
+    // 如果有搜索文本,清除搜索
+    if (searchText) {
+      clearSearch();
+    }
+  };
+  
+  // 处理登出
+  const handleLogout = async () => {
+    await logout();
+    navigate('/admin/login');
+  };
+  
+  // 处理菜单展开/收起
+  const onOpenChange = (keys: string[]) => {
+    // 当侧边栏折叠时不保存openKeys状态
+    if (!collapsed) {
+      setOpenKeys(keys);
+    }
+  };
+  
+  // 当侧边栏折叠状态改变时,控制菜单打开状态
+  useEffect(() => {
+    if (collapsed) {
+      setOpenKeys([]);
+    } else {
+      // 找到当前路径所属的父菜单
+      const currentPath = location.pathname.replace('/admin', '');
+      const parentKeys = menuItems
+        .filter(item => item.children && item.children.some(child => child.key === currentPath))
+        .map(item => item.key);
+      
+      // 仅展开当前所在的菜单组
+      if (parentKeys.length > 0) {
+        setOpenKeys(parentKeys);
+      } else {
+        // 初始时可以根据需要设置要打开的菜单组
+        setOpenKeys([]);
+      }
+    }
+  }, [collapsed, location.pathname]);
+  
+  // 用户下拉菜单项
+  const userMenuItems = [
+    {
+      key: 'profile',
+      label: '个人信息',
+      icon: <UserOutlined />
+    },
+    {
+      key: 'theme',
+      label: isDark ? '切换到亮色模式' : '切换到暗色模式',
+      icon: <SettingOutlined />,
+      onClick: toggleTheme
+    },
+    {
+      key: 'logout',
+      label: '退出登录',
+      icon: <LogoutOutlined />,
+      onClick: handleLogout
+    }
+  ];
+  
+  // 应用名称 - 从CONFIG中获取或使用默认值
+  const appName = window.CONFIG?.APP_NAME || '应用Starter';
+  
+  return (
+    <Layout style={{ minHeight: '100vh' }}>
+      <Sider 
+        trigger={null} 
+        collapsible 
+        collapsed={collapsed}
+        width={240}
+        className="custom-sider"
+        style={{
+          overflow: 'auto',
+          height: '100vh',
+          position: 'fixed',
+          left: 0,
+          top: 0,
+          bottom: 0,
+          zIndex: 100,
+        }}
+      >
+        <div className="p-4">
+          <Typography.Title level={2} className="text-xl font-bold truncate">
+            {collapsed ? '应用' : appName}
+          </Typography.Title>
+        </div>
+        
+        {/* 搜索框 - 仅在展开状态下显示 */}
+        {!collapsed && (
+          <div style={{ padding: '0 16px 16px' }}>
+            <Input 
+              placeholder="搜索菜单..." 
+              value={searchText}
+              onChange={(e) => handleSearch(e.target.value)}
+              suffix={
+                searchText ? 
+                <Button 
+                  type="text" 
+                  size="small" 
+                  icon={<CloseOutlined />} 
+                  onClick={clearSearch}
+                /> : 
+                <SearchOutlined />
+              }
+            />
+          </div>
+        )}
+        
+        <Menu
+          theme={isDark ? "light" : "light"}
+          mode="inline"
+          selectedKeys={[location.pathname.replace('/admin', '')]}
+          openKeys={openKeys}
+          onOpenChange={onOpenChange}
+          items={filteredMenuItems}
+          onClick={handleMenuClick}
+        />
+      </Sider>
+      
+      <Layout style={{ marginLeft: collapsed ? 80 : 240, transition: 'margin-left 0.2s' }}>
+        <Header className="p-0 flex justify-between items-center" 
+          style={{ 
+            position: 'sticky', 
+            top: 0, 
+            zIndex: 99, 
+            boxShadow: '0 1px 4px rgba(0,21,41,0.08)',
+          }}
+        >
+          <Button
+            type="text"
+            icon={collapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
+            onClick={() => setCollapsed(!collapsed)}
+            className="w-16 h-16"
+          />
+          
+          <Space size="middle" className="mr-4">
+            <Badge count={5} offset={[0, 5]}>
+              <Button 
+                type="text" 
+                icon={<BellOutlined />}
+              />
+            </Badge>
+            
+            <Dropdown menu={{ items: userMenuItems }}>
+              <Space className="cursor-pointer">
+                <Avatar 
+                  src={user?.avatar || 'https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?q=80&w=40&auto=format&fit=crop'}
+                  icon={!user?.avatar && !navigator.onLine && <UserOutlined />}
+                />
+                <span>
+                  {user?.nickname || user?.username}
+                </span>
+              </Space>
+            </Dropdown>
+          </Space>
+        </Header>
+        
+        <Content className="m-6" style={{ overflow: 'initial' }}>
+          <div className="site-layout-content p-6 rounded-lg">
+            <Outlet />
+          </div>
+          
+          {/* 回到顶部按钮 */}
+          {showBackTop && (
+            <Button
+              type="primary"
+              shape="circle"
+              icon={<VerticalAlignTopOutlined />}
+              size="large"
+              onClick={scrollToTop}
+              style={{
+                position: 'fixed',
+                right: 30,
+                bottom: 30,
+                zIndex: 1000,
+                boxShadow: '0 3px 6px rgba(0,0,0,0.16)',
+              }}
+            />
+          )}
+        </Content>
+      </Layout>
+    </Layout>
+  );
+};
+
+// 受保护的路由组件
+const ProtectedRoute = ({ children }: { children: React.ReactNode }) => {
+  const { isAuthenticated, isLoading } = useAuth();
+  const navigate = useNavigate();
+  
+  useEffect(() => {
+    // 只有在加载完成且未认证时才重定向
+    if (!isLoading && !isAuthenticated) {
+      navigate('/admin/login', { replace: true });
+    }
+  }, [isAuthenticated, isLoading, navigate]);
+  
+  // 显示加载状态,直到认证检查完成
+  if (isLoading) {
+    return (
+      <div className="flex justify-center items-center h-screen">
+        <div className="loader ease-linear rounded-full border-4 border-t-4 border-gray-200 h-12 w-12"></div>
+      </div>
+    );
+  }
+  
+  // 如果未认证且不再加载中,不显示任何内容(等待重定向)
+  if (!isAuthenticated) {
+    return null;
+  }
+  
+  return children;
+};
+
+// 错误页面组件
+const ErrorPage = ({ error }: { error?: Error }) => {
+  const { isDark } = useTheme();
+  
+  return (
+    <div className="flex flex-col items-center justify-center h-screen"
+      style={{ color: isDark ? '#fff' : 'inherit' }}
+    >
+      <h1 className="text-2xl font-bold mb-4">发生错误</h1>
+      <p className="mb-4">{error?.message || '抱歉,应用程序遇到了一些问题。'}</p>
+      <Button 
+        type="primary" 
+        onClick={() => window.location.reload()}
+      >
+        重新加载
+      </Button>
+    </div>
+  );
+};
+
+// 应用入口组件
+const App = () => {
+  // 路由配置
+  const router = createBrowserRouter([
+    {
+      path: '/',
+      element: <Navigate to="/admin" replace />
+    },
+    {
+      path: '/admin/login',
+      element: <LoginPage />
+    },
+    {
+      path: '/admin',
+      element: (
+        <ProtectedRoute>
+          <MainLayout />
+        </ProtectedRoute>
+      ),
+      children: [
+        {
+          index: true,
+          element: <Navigate to="/admin/dashboard" />
+        },
+        {
+          path: 'dashboard',
+          element: <DashboardPage />,
+          errorElement: <ErrorPage />
+        },
+        {
+          path: 'users',
+          element: <UsersPage />,
+          errorElement: <ErrorPage />
+        },
+        {
+          path: 'settings',
+          element: <SettingsPage />,
+          errorElement: <ErrorPage />
+        },
+        {
+          path: 'theme-settings',
+          element: <ThemeSettingsPage />,
+          errorElement: <ErrorPage />
+        },
+        {
+          path: 'chart-dashboard',
+          element: <ChartDashboardPage />,
+          errorElement: <ErrorPage />
+        },
+        {
+          path: 'map-dashboard',
+          element: <LoginMapPage />,
+          errorElement: <ErrorPage />
+        },
+        {
+          path: 'know-info',
+          element: <KnowInfoPage />,
+          errorElement: <ErrorPage />
+        },
+        {
+          path: 'file-library',
+          element: <FileLibraryPage />,
+          errorElement: <ErrorPage />
+        },
+      ],
+    },
+  ]);
+  return <RouterProvider router={router} />
+};
+
+// 渲染应用
+const root = createRoot(document.getElementById('root') as HTMLElement);
+root.render(
+  <QueryClientProvider client={queryClient}>
+    <ThemeProvider>
+      <AuthProvider>
+        <App />
+      </AuthProvider>
+    </ThemeProvider>
+  </QueryClientProvider>
+);
+

+ 1961 - 0
asset/share/types.ts

@@ -0,0 +1,1961 @@
+
+export enum OssType {
+  ALIYUN = 'aliyun',
+  MINIO = 'minio',
+}
+
+// 全局配置常量
+export interface GlobalConfig {
+  OSS_BASE_URL: string;
+  OSS_TYPE: OssType;
+  API_BASE_URL: string;
+  APP_NAME: string;
+  ENV: string;
+  DEFAULT_THEME: string;
+  MAP_CONFIG: {
+    KEY: string;
+    VERSION: string;
+    PLUGINS: string[];
+    MAP_MODE: MapMode;
+  };
+  CHART_THEME: string;
+  ENABLE_THEME_CONFIG: boolean;
+  THEME: ThemeSettings | null;
+}
+
+
+// 定义类型
+export interface User {
+  id: number;
+  username: string;
+  nickname?: string;
+  email?: string;
+  phone?: string;
+  role: string;
+  avatar?: string;
+}
+
+export interface MenuItem {
+  id: number;
+  name: string;
+  path: string;
+  icon: string;
+  component: string;
+  parent_id?: number;
+  children?: MenuItem[];
+}
+
+// 认证上下文类型
+export interface AuthContextType {
+  user: User | null;
+  token: string | null;
+  login: (username: string, password: string) => Promise<void>;
+  logout: () => Promise<void>;
+  isAuthenticated: boolean;
+  isLoading: boolean;
+}
+
+// 主题上下文类型
+export interface ThemeContextType {
+  isDark: boolean;
+  currentTheme: ThemeSettings;
+  updateTheme: (theme: Partial<ThemeSettings>) => void;  // 实时预览
+  saveTheme: (theme: Partial<ThemeSettings>) => Promise<ThemeSettings>;  // 保存到后端
+  resetTheme: () => Promise<ThemeSettings>;
+  toggleTheme: () => void;  // 切换主题模式
+}
+
+// 主题模式枚举
+export enum ThemeMode {
+  LIGHT = 'light',
+  DARK = 'dark'
+}
+
+// 字体大小枚举
+export enum FontSize {
+  SMALL = 'small',
+  MEDIUM = 'medium',
+  LARGE = 'large'
+}
+
+// 紧凑模式枚举
+export enum CompactMode {
+  NORMAL = 0,  // 正常模式
+  COMPACT = 1  // 紧凑模式
+}
+
+// 主题设置类型
+export interface ThemeSettings {
+  /** 主键ID */
+  id?: number;
+  
+  /** 用户ID */
+  user_id: number;
+  
+  /** 主题模式(light/dark) */
+  theme_mode: ThemeMode;
+  
+  /** 主题主色 */
+  primary_color: string;
+  
+  /** 背景色 */
+  background_color?: string;
+  
+  /** 文字颜色 */
+  text_color?: string;
+  
+  /** 边框圆角 */
+  border_radius?: number;
+  
+  /** 字体大小(small/medium/large) */
+  font_size: FontSize;
+  
+  /** 是否紧凑模式(0否 1是) */
+  is_compact: CompactMode;
+  
+  /** 创建时间 */
+  created_at?: string;
+  
+  /** 更新时间 */
+  updated_at?: string;
+}
+
+// 启用/禁用状态枚举
+export enum EnableStatus {
+  DISABLED = 0, // 禁用
+  ENABLED = 1   // 启用
+}
+
+// 启用/禁用状态中文映射
+export const EnableStatusNameMap: Record<EnableStatus, string> = {
+  [EnableStatus.DISABLED]: '禁用',
+  [EnableStatus.ENABLED]: '启用'
+};
+
+// 删除状态枚举
+export enum DeleteStatus {
+  NOT_DELETED = 0, // 未删除
+  DELETED = 1      // 已删除
+}
+
+// 删除状态中文映射
+export const DeleteStatusNameMap: Record<DeleteStatus, string> = {
+  [DeleteStatus.NOT_DELETED]: '未删除',
+  [DeleteStatus.DELETED]: '已删除'
+};
+
+// 设备通信协议类型枚举
+export enum DeviceProtocolType {
+  SNMP = 'SNMP',       // 简单网络管理协议(网络设备管理)
+  HTTP = 'HTTP',       // 超文本传输协议(Web服务)
+  MODBUS = 'MODBUS',   // Modbus协议(工业自动化标准通信协议)
+  MQTT = 'MQTT',       // 消息队列遥测传输(物联网消息协议)
+  SOCKET = 'SOCKET',   // Socket通信(基础网络通信)
+  OPC = 'OPC',         // OPC统一架构(工业设备互操作性标准)
+  RS485 = 'RS485',     // RS485串行通信(工业现场总线)
+  TCP = 'TCP'          // TCP网络协议(可靠的网络传输协议)
+}
+
+// 设备通信协议类型中文映射
+export const DeviceProtocolTypeNameMap: Record<DeviceProtocolType, string> = {
+  [DeviceProtocolType.SNMP]: 'SNMP',
+  [DeviceProtocolType.HTTP]: 'HTTP',
+  [DeviceProtocolType.MODBUS]: 'MODBUS',  
+  [DeviceProtocolType.MQTT]: 'MQTT',
+  [DeviceProtocolType.SOCKET]: 'SOCKET',
+  [DeviceProtocolType.OPC]: 'OPC',
+  [DeviceProtocolType.RS485]: 'RS485',
+  [DeviceProtocolType.TCP]: 'TCP'
+};
+
+// 统一的监控指标类型枚举
+export enum MetricType {
+  TEMPERATURE = 'temperature',
+  HUMIDITY = 'humidity',
+  VOLTAGE = 'voltage',
+  CPU_USAGE = 'cpu_usage',
+  MEMORY_USAGE = 'memory_usage',
+  DISK_USAGE = 'disk_usage',
+  NETWORK_TRAFFIC = 'network_traffic',
+  PING_TIME = 'ping_time',
+  PACKET_LOSS = 'packet_loss',
+  SNMP_RESPONSE_TIME = 'snmp_response_time',
+  SNMP_ERRORS = 'snmp_errors',
+  HTTP_RESPONSE_TIME = 'http_response_time',
+  HTTP_STATUS = 'http_status',
+  TCP_CONNECTION_TIME = 'tcp_connection_time',
+  CONNECTION_STATUS = 'connection_status'
+}
+
+// 监控类型中文映射
+export const MetricTypeNameMap: Record<MetricType, string> = {
+  [MetricType.TEMPERATURE]: '温度',
+  [MetricType.HUMIDITY]: '湿度',
+  [MetricType.VOLTAGE]: '电压',
+  [MetricType.CPU_USAGE]: 'CPU使用率',
+  [MetricType.MEMORY_USAGE]: '内存使用率',
+  [MetricType.DISK_USAGE]: '磁盘使用率',
+  [MetricType.NETWORK_TRAFFIC]: '网络流量',
+  [MetricType.PING_TIME]: 'Ping时间',
+  [MetricType.PACKET_LOSS]: '丢包率',
+  [MetricType.SNMP_RESPONSE_TIME]: 'SNMP响应时间',
+  [MetricType.SNMP_ERRORS]: 'SNMP错误数',
+  [MetricType.HTTP_RESPONSE_TIME]: 'HTTP响应时间',
+  [MetricType.HTTP_STATUS]: 'HTTP状态码',
+  [MetricType.TCP_CONNECTION_TIME]: 'TCP连接时间',
+  [MetricType.CONNECTION_STATUS]: '连接状态'
+};
+
+// 处理类型枚举
+export enum HandleType {
+  CONFIRM = 'confirm',
+  RESOLVE = 'resolve',
+  IGNORE = 'ignore'
+}
+
+// 处理类型中文映射
+export const HandleTypeNameMap: Record<HandleType, string> = {
+  [HandleType.CONFIRM]: '确认',
+  [HandleType.RESOLVE]: '解决',
+  [HandleType.IGNORE]: '忽略'
+};
+
+// 问题类型枚举
+export enum ProblemType {
+  DEVICE = 'device',
+  NETWORK = 'network',
+  POWER = 'power',
+  CONSTRUCTION = 'construction',
+  OTHER = 'other'
+}
+
+// 问题类型中文映射
+export const ProblemTypeNameMap: Record<ProblemType, string> = {
+  [ProblemType.DEVICE]: '设备故障',
+  [ProblemType.NETWORK]: '网络故障',
+  [ProblemType.POWER]: '电源故障',
+  [ProblemType.CONSTRUCTION]: '施工影响',
+  [ProblemType.OTHER]: '其他原因'
+};
+
+// 通知类型枚举
+export enum NotifyType {
+  SMS = 'sms',
+  EMAIL = 'email',
+  WECHAT = 'wechat'
+}
+
+// 通知类型中文映射
+export const NotifyTypeNameMap: Record<NotifyType, string> = {
+  [NotifyType.SMS]: '短信',
+  [NotifyType.EMAIL]: '邮件',
+  [NotifyType.WECHAT]: '微信'
+};
+
+// 告警等级枚举
+export enum AlertLevel {
+  MINOR = 0,    // 次要
+  NORMAL = 1,   // 一般
+  IMPORTANT = 2, // 重要
+  URGENT = 3     // 紧急
+}
+
+// 告警等级中文映射
+export const AlertLevelNameMap: Record<AlertLevel, string> = {
+  [AlertLevel.MINOR]: '次要',
+  [AlertLevel.NORMAL]: '一般',
+  [AlertLevel.IMPORTANT]: '重要',
+  [AlertLevel.URGENT]: '紧急'
+};
+
+// 告警等级颜色映射
+export const AlertLevelColorMap: Record<AlertLevel, string> = {
+  [AlertLevel.MINOR]: 'blue',
+  [AlertLevel.NORMAL]: 'orange',
+  [AlertLevel.IMPORTANT]: 'red',
+  [AlertLevel.URGENT]: 'purple'
+};
+
+// 设备状态枚举(资产管理)
+export enum DeviceStatus {
+  NORMAL = 0,   // 正常
+  MAINTAIN = 1, // 维护中
+  FAULT = 2,    // 故障
+  OFFLINE = 3   // 下线
+}
+
+// 设备分类枚举
+export enum DeviceCategory {
+  SERVER = 1,      // 服务器
+  NETWORK = 2,     // 网络设备
+  STORAGE = 3,     // 存储设备
+  SECURITY = 4,    // 安全设备
+  OTHER = 5        // 其他设备
+}
+
+// 区域枚举
+export enum AreaType {
+  AREA_A = 1,      // A区
+  AREA_B = 2,      // B区
+  AREA_C = 3,      // C区
+  AREA_OTHER = 4   // 其他区域
+}
+
+// 资产状态枚举
+export enum AssetStatus {
+  IN_USE = 0,     // 使用中
+  IDLE = 1,       // 闲置
+  REPAIR = 2,     // 维修中
+  SCRAPPED = 3    // 已报废
+}
+
+// 网络状态枚举
+export enum NetworkStatus {
+  CONNECTED = 0,    // 已连接
+  DISCONNECTED = 1, // 已断开
+  UNSTABLE = 2      // 不稳定
+}
+
+// 丢包状态枚举
+export enum PacketLossStatus {
+  NORMAL = 0,      // 正常
+  HIGH = 1         // 丢包
+}
+
+// 审核状态枚举
+export enum AuditStatus {
+  PENDING = 0,   // 待审核
+  APPROVED = 1,  // 已通过
+  REJECTED = 2   // 已拒绝
+}
+
+// 审核状态中文映射
+export const AuditStatusNameMap: Record<AuditStatus, string> = {
+  [AuditStatus.PENDING]: '待审核',
+  [AuditStatus.APPROVED]: '已通过',
+  [AuditStatus.REJECTED]: '已拒绝'
+};
+
+// 设备状态中文映射(资产管理)
+export const DeviceStatusNameMap: Record<DeviceStatus, string> = {
+  [DeviceStatus.NORMAL]: '正常',
+  [DeviceStatus.MAINTAIN]: '维护中',
+  [DeviceStatus.FAULT]: '故障',
+  [DeviceStatus.OFFLINE]: '下线'
+};
+
+// 设备状态颜色映射(资产管理)
+export const DeviceStatusColorMap: Record<DeviceStatus, string> = {
+  [DeviceStatus.NORMAL]: 'green',
+  [DeviceStatus.MAINTAIN]: 'blue',
+  [DeviceStatus.FAULT]: 'red',
+  [DeviceStatus.OFFLINE]: 'gray'
+};
+
+// 设备分类中文映射
+export const DeviceCategoryNameMap: Record<DeviceCategory, string> = {
+  [DeviceCategory.SERVER]: '服务器',
+  [DeviceCategory.NETWORK]: '网络设备',
+  [DeviceCategory.STORAGE]: '存储设备',
+  [DeviceCategory.SECURITY]: '安全设备',
+  [DeviceCategory.OTHER]: '其他设备'
+};
+
+// 告警状态枚举
+export enum AlertStatus {
+  PENDING = 'pending',
+  HANDLING = 'handling',
+  RESOLVED = 'resolved',
+  IGNORED = 'ignored'
+}
+
+// 告警状态中文映射
+export const AlertStatusNameMap: Record<AlertStatus, string> = {
+  [AlertStatus.PENDING]: '待处理',
+  [AlertStatus.HANDLING]: '处理中',
+  [AlertStatus.RESOLVED]: '已解决',
+  [AlertStatus.IGNORED]: '已忽略'
+};
+
+// 告警状态颜色映射
+export const AlertStatusColorMap: Record<AlertStatus, string> = {
+  [AlertStatus.PENDING]: 'red',
+  [AlertStatus.HANDLING]: 'blue',
+  [AlertStatus.RESOLVED]: 'green',
+  [AlertStatus.IGNORED]: 'gray'
+};
+
+// 设备在线状态枚举
+export enum OnlineStatus {
+  ONLINE = 'online',
+  OFFLINE = 'offline'
+}
+
+// 设备在线状态中文映射
+export const OnlineStatusNameMap: Record<OnlineStatus, string> = {
+  [OnlineStatus.ONLINE]: '在线',
+  [OnlineStatus.OFFLINE]: '离线'
+};
+
+// 设备在线状态颜色映射
+export const OnlineStatusColorMap: Record<OnlineStatus, string> = {
+  [OnlineStatus.ONLINE]: 'green',
+  [OnlineStatus.OFFLINE]: 'red'
+};
+
+// 工单状态枚举
+export enum WorkOrderStatus {
+  PENDING = 0,   // 待处理
+  HANDLING = 1,  // 处理中
+  AUDITING = 2,  // 待审核
+  COMPLETED = 3, // 已完成
+  CLOSED = 4     // 已关闭
+}
+
+// 工单状态中文映射
+export const WorkOrderStatusNameMap: Record<WorkOrderStatus, string> = {
+  [WorkOrderStatus.PENDING]: '待处理',
+  [WorkOrderStatus.HANDLING]: '处理中',
+  [WorkOrderStatus.AUDITING]: '待审核',
+  [WorkOrderStatus.COMPLETED]: '已完成',
+  [WorkOrderStatus.CLOSED]: '已关闭'
+};
+
+// 工单状态颜色映射
+export const WorkOrderStatusColorMap: Record<WorkOrderStatus, string> = {
+  [WorkOrderStatus.PENDING]: 'orange',
+  [WorkOrderStatus.HANDLING]: 'blue',
+  [WorkOrderStatus.AUDITING]: 'gold',
+  [WorkOrderStatus.COMPLETED]: 'green',
+  [WorkOrderStatus.CLOSED]: 'gray'
+};
+
+// 工单优先级枚举
+export enum WorkOrderPriority {
+  NORMAL = 0,    // 普通
+  IMPORTANT = 1, // 重要
+  URGENT = 2     // 紧急
+}
+
+// 工单优先级中文映射
+export const WorkOrderPriorityNameMap: Record<WorkOrderPriority, string> = {
+  [WorkOrderPriority.NORMAL]: '普通',
+  [WorkOrderPriority.IMPORTANT]: '重要',
+  [WorkOrderPriority.URGENT]: '紧急'
+};
+
+// 工单优先级颜色映射
+export const WorkOrderPriorityColorMap: Record<WorkOrderPriority, string> = {
+  [WorkOrderPriority.NORMAL]: 'green',
+  [WorkOrderPriority.IMPORTANT]: 'orange',
+  [WorkOrderPriority.URGENT]: 'red'
+};
+
+// 工单操作类型枚举
+export enum WorkOrderAction {
+  CREATE = 'create',
+  ACCEPT = 'accept',
+  HANDLE = 'handle',
+  AUDIT = 'audit',
+  CLOSE = 'close'
+}
+
+// 工单操作类型中文映射
+export const WorkOrderActionNameMap: Record<WorkOrderAction, string> = {
+  [WorkOrderAction.CREATE]: '创建',
+  [WorkOrderAction.ACCEPT]: '接受',
+  [WorkOrderAction.HANDLE]: '处理',
+  [WorkOrderAction.AUDIT]: '审核',
+  [WorkOrderAction.CLOSE]: '关闭'
+};
+
+// 服务器类型枚举
+export enum ServerType {
+  STANDARD = 'standard',
+  NETWORK = 'network',
+  STORAGE = 'storage',
+  SPECIAL = 'special'
+}
+
+// 服务器类型中文映射
+export const ServerTypeNameMap: Record<ServerType, string> = {
+  [ServerType.STANDARD]: '标准服务器',
+  [ServerType.NETWORK]: '网络设备',
+  [ServerType.STORAGE]: '存储设备',
+  [ServerType.SPECIAL]: '特殊设备'
+};
+
+
+
+// 图表类型映射
+export const AlertTypeMap = {
+  temperature: { text: '温度异常', color: 'orange' },
+  humidity: { text: '湿度异常', color: 'blue' },
+  offline: { text: '设备离线', color: 'red' }
+} as const;
+
+// 工单状态映射
+export const StatusMap = {
+  unread: { text: '未读', color: 'red' },
+  read: { text: '已读', color: 'blue' },
+  processed: { text: '已处理', color: 'green' }
+} as const;
+
+// 图标类型映射
+type IconType = 'dashboard' | 'user' | 'setting' | 'team' | 'book' | 'calendar' | 'pie-chart' | 'database';
+
+// 图标类型中文映射
+export const IconTypeNameMap: Record<IconType, string> = {
+  'dashboard': '仪表盘',
+  'user': '用户',
+  'setting': '设置',
+  'team': '团队',
+  'book': '文档',
+  'calendar': '日历',
+  'pie-chart': '图表',
+  'database': '数据库'
+};
+
+// 定义JSON数据结构接口
+
+// 附件类型定义
+export interface Attachment {
+  /** 附件ID */
+  id: string;
+  
+  /** 附件名称 */
+  name: string;
+  
+  /** 附件访问地址 */
+  url: string;
+  
+  /** 附件类型(如image/jpeg, application/pdf等) */
+  type: string;
+  
+  /** 附件大小(字节) */
+  size: number;
+  
+  /** 上传时间 */
+  upload_time: string;
+}
+
+// 通知项配置类型定义
+interface NotifyItem {
+  /** 通知项ID */
+  id: string;
+  
+  /** 通知项类型 */
+  type: string;
+  
+  /** 是否启用 */
+  enabled: boolean;
+  
+  /** 通知配置参数 */
+  config: Record<string, unknown>;
+}
+
+// 监控配置类型定义
+export interface MonitorConfig {
+  /** 监控间隔(秒) */
+  interval: number;
+  
+  /** 监控指标列表 */
+  metrics: Array<{
+    /** 指标名称 */
+    name: string;
+    
+    /** 指标类型 */
+    type: string;
+    
+    /** 是否启用 */
+    enabled: boolean;
+    
+    /** 阈值设置 */
+    threshold?: {
+      /** 最小阈值 */
+      min?: number;
+      
+      /** 最大阈值 */
+      max?: number;
+    };
+  }>;
+  
+  /** 通知设置 */
+  notification: {
+    /** 是否启用通知 */
+    enabled: boolean;
+    
+    /** 通知渠道列表 */
+    channels: string[];
+  };
+}
+
+// 告警规则类型定义
+export interface AlertRuleConfig {
+  /** 规则列表 */
+  rules: Array<{
+    /** 监控指标 */
+    metric: string;
+    
+    /** 触发条件(如>、<、=等) */
+    condition: string;
+    
+    /** 阈值 */
+    threshold: number;
+    
+    /** 持续时间(秒) */
+    duration: number;
+    
+    /** 告警等级 */
+    level: AlertLevel;
+  }>;
+  
+  /** 动作列表 */
+  actions: Array<{
+    /** 动作类型 */
+    type: string;
+    
+    /** 动作目标 */
+    target: string;
+    
+    /** 通知模板 */
+    template?: string;
+  }>;
+}
+
+// 数据格式配置类型定义
+export interface DataSchema {
+  /** 版本号 */
+  version: string;
+  
+  /** 属性定义 */
+  properties: Record<string, {
+    /** 数据类型 */
+    type: string;
+    
+    /** 描述 */
+    description?: string;
+    
+    /** 是否必填 */
+    required?: boolean;
+    
+    /** 格式(如日期格式) */
+    format?: string;
+    
+    /** 枚举值列表 */
+    enum?: string[];
+  }>;
+  
+  /** 必填字段列表 */
+  required: string[];
+}
+
+// 图标配置类型定义
+export interface IconConfig {
+  /** 图标尺寸 */
+  size: {
+    /** 宽度 */
+    width: number;
+    
+    /** 高度 */
+    height: number;
+  };
+  
+  /** 支持的文件格式 */
+  format: string[];
+  
+  /** 最大文件大小(KB) */
+  maxSize: number;
+}
+
+// 告警等级配置类型定义
+export interface AlertLevelConfig {
+  /** 等级定义 */
+  levels: Record<string, {
+    /** 等级名称 */
+    name: string;
+    
+    /** 等级颜色 */
+    color: string;
+    
+    /** 优先级 */
+    priority: number;
+    
+    /** 是否自动升级 */
+    autoEscalation?: boolean;
+    
+    /** 升级延迟时间(分钟) */
+    escalationDelay?: number;
+  }>;
+  
+  /** 默认等级 */
+  default: string;
+}
+
+// 监控项配置类型定义
+export interface MonitorItemConfig {
+  /** 监控项列表 */
+  items: Array<{
+    /** 监控项名称 */
+    name: string;
+    
+    /** 描述 */
+    description: string;
+    
+    /** 监控项类型 */
+    type: string;
+    
+    /** 单位 */
+    unit: string;
+    
+    /** 默认是否启用 */
+    defaultEnabled: boolean;
+    
+    /** 默认阈值 */
+    defaultThresholds?: {
+      /** 最小阈值 */
+      min?: number;
+      
+      /** 最大阈值 */
+      max?: number;
+    };
+  }>;
+}
+
+// 常用语列表类型定义
+export interface CommonPhrase {
+  /** 常用语ID */
+  id: string;
+  
+  /** 分类 */
+  category: string;
+  
+  /** 内容 */
+  content: string;
+  
+  /** 标签列表 */
+  tags: string[];
+}
+
+// SLA配置类型定义
+export interface SLAConfig {
+  /** 响应时间(分钟) */
+  responseTime: number;
+  
+  /** 解决时间(分钟) */
+  resolveTime: number;
+  
+  /** 工作时间 */
+  workingHours: {
+    /** 开始时间(HH:mm) */
+    start: string;
+    
+    /** 结束时间(HH:mm) */
+    end: string;
+    
+    /** 工作日(0-6,0代表周日) */
+    workDays: number[];
+  };
+  
+  /** 升级规则 */
+  escalationRules: Array<{
+    /** 超时时间(分钟) */
+    timeout: number;
+    
+    /** 动作 */
+    action: string;
+    
+    /** 目标(如用户ID、角色等) */
+    target: string[];
+  }>;
+}
+
+// 流程配置类型定义
+export interface WorkflowConfig {
+  /** 流程步骤 */
+  steps: Array<{
+    /** 步骤名称 */
+    name: string;
+    
+    /** 允许操作的角色 */
+    roles: string[];
+    
+    /** 可执行的动作 */
+    actions: string[];
+    
+    /** 下一步可能的步骤 */
+    nextSteps: string[];
+    
+    /** 自动超时时间(分钟) */
+    autoTimeout?: number;
+  }>;
+  
+  /** 初始步骤 */
+  initialStep: string;
+}
+
+// 告警处理记录表
+export interface AlertHandleLog {
+  /** 主键ID */
+  id: number;
+  
+  /** 关联的告警ID */
+  alert_id: number;
+  
+  /** 处理人ID */
+  handler_id: number;
+  
+  /** 处理类型 */
+  handle_type: HandleType;
+  
+  /** 问题类型 */
+  problem_type: ProblemType;
+  
+  /** 处理结果 */
+  handle_result?: string;
+  
+  /** 附件列表 */
+  attachments?: Attachment[];
+  
+  /** 是否禁用通知 (0否 1是) */
+  notify_disabled?: number;
+  
+  /** 禁用的通知项配置 */
+  notify_items?: NotifyItem[];
+  
+  /** 处理时间 */
+  handle_time: Date;
+  
+  /** 是否删除 (0否 1是) */
+  is_deleted?: DeleteStatus;
+  
+  /** 创建时间 */
+  created_at: Date;
+  
+  /** 更新时间 */
+  updated_at: Date;
+}
+
+// 告警通知配置表
+export interface AlertNotifyConfig {
+  /** 主键ID */
+  id: number;
+  
+  /** 关联的设备ID */
+  device_id: number;
+  
+  /** 告警等级 */
+  alert_level: AlertLevel;
+  
+  /** 通知类型 */
+  notify_type: NotifyType;
+  
+  /** 通知模板 */
+  notify_template?: string;
+  
+  /** 通知用户ID列表 */
+  notify_users?: number[];
+  
+  /** 是否启用 (0否 1是) */
+  is_enabled?: EnableStatus;
+  
+  /** 是否删除 (0否 1是) */
+  is_deleted?: DeleteStatus;
+  
+  /** 创建时间 */
+  created_at: Date;
+  
+  /** 更新时间 */
+  updated_at: Date;
+}
+
+// 设备告警规则表
+export interface DeviceAlertRule {
+  /** 主键ID */
+  id: number;
+  
+  /** 关联的设备ID */
+  device_id: number;
+  
+  /** 监控指标类型 */
+  metric_type: string;
+  
+  /** 最小阈值 */
+  min_value?: number;
+  
+  /** 最大阈值 */
+  max_value?: number;
+  
+  /** 持续时间(秒) */
+  duration_seconds?: number;
+  
+  /** 告警等级 */
+  alert_level: AlertLevel;
+  
+  /** 告警消息模板 */
+  alert_message?: string;
+  
+  /** 是否启用 (0否 1是) */
+  is_enabled?: EnableStatus;
+  
+  /** 是否删除 (0否 1是) */
+  is_deleted?: DeleteStatus;
+  
+  /** 创建时间 */
+  created_at: Date;
+  
+  /** 更新时间 */
+  updated_at: Date;
+}
+
+// 设备告警记录表
+export interface DeviceAlert {
+  /** 主键ID */
+  id: number;
+  
+  /** 关联的设备ID */
+  device_id: number;
+  
+  /** 设备名称 */
+  device_name: string;
+  
+  /** 监控指标类型 */
+  metric_type: string;
+  
+  /** 触发值 */
+  metric_value: number;
+  
+  /** 告警等级 */
+  alert_level: AlertLevel;
+  
+  /** 告警消息 */
+  alert_message: string;
+  
+  /** 状态 */
+  status: AlertStatus;
+  
+  /** 是否删除 (0否 1是) */
+  is_deleted?: DeleteStatus;
+  
+  /** 创建时间 */
+  created_at: Date;
+  
+  /** 更新时间 */
+  updated_at: Date;
+}
+
+// 设备分类图标表
+export interface DeviceCategoryIcon {
+  /** 主键ID */
+  id: number;
+  
+  /** 关联的设备分类ID */
+  category_id: number;
+  
+  /** 分类图标 */
+  icon?: string;
+  
+  /** 图标名称 */
+  icon_name?: string;
+  
+  /** 图标类型(svg/url等) */
+  icon_type?: string;
+  
+  /** 排序 */
+  sort?: number;
+  
+  /** 是否为默认图标 (0否 1是) */
+  is_default?: number;
+  
+  /** 是否禁用 (0否 1是) */
+  is_disabled?: number;
+  
+  /** 是否被删除 (0否 1是) */
+  is_deleted?: number;
+  
+  /** 创建时间 */
+  created_at: Date;
+  
+  /** 更新时间 */
+  updated_at: Date;
+}
+
+
+// 设备实例表
+export interface DeviceInstance {
+  /** 关联资产ID */
+  id: number;
+  
+  /** 设备类型ID */
+  type_id: number;
+  
+  /** 通信协议(SNMP/HTTP/RS485/TCP等) */
+  protocol: DeviceProtocolType;
+  
+  /** 通信地址 */
+  address: string;
+  
+  /** 采集间隔(秒) */
+  collect_interval?: number;
+  
+  /** 最后采集时间 */
+  last_collect_time?: Date;
+  
+  /** 备注 */
+  remark?: string;
+  
+  /** 是否启用 (0否 1是) */
+  is_enabled?: number;
+  
+  /** 是否删除 (0否 1是) */
+  is_deleted?: number;
+  
+  /** 创建时间 */
+  created_at: Date;
+  
+  /** 更新时间 */
+  updated_at: Date;
+  
+  /** 资产名称(来自zichan_info表) */
+  asset_name?: string;
+  
+  /** 设备分类(来自zichan_info表) */
+  device_category?: DeviceCategory;
+  
+  /** 归属区域(来自zichan_info表) */
+  area?: AreaType;
+  
+  /** 供应商(来自zichan_info表) */
+  supplier?: string;
+  
+  /** 设备状态(来自zichan_info表) */
+  device_status?: DeviceStatus;
+}
+
+// 设备监控数据表
+export interface DeviceMonitorData {
+  /** 主键ID */
+  id: number;
+  
+  /** 关联的设备ID */
+  device_id: number;
+  
+  /** 监控指标类型(temperature/humidity/smoke/water等) */
+  metric_type: string;
+  
+  /** 监控值 */
+  metric_value: number;
+  
+  /** 单位 */
+  unit?: string;
+  
+  /** 状态 */
+  status?: DeviceStatus;
+  
+  /** 采集时间 */
+  collect_time: Date;
+  
+  /** 是否删除 (0否 1是) */
+  is_deleted?: DeleteStatus;
+  
+  /** 创建时间 */
+  created_at: Date;
+  
+  /** 更新时间 */
+  updated_at: Date;
+}
+
+// 设备类型表
+export interface DeviceType {
+  /** 主键ID */
+  id: number;
+  
+  /** 类型名称 */
+  name: string;
+  
+  /** 类型编码 */
+  code: string;
+  
+  /** 设备类型图片URL */
+  image_url?: string;
+  
+  /** 类型描述 */
+  description?: string;
+  
+  /** 是否启用 (0否 1是) */
+  is_enabled?: number;
+  
+  /** 是否删除 (0否 1是) */
+  is_deleted?: number;
+  
+  /** 创建时间 */
+  created_at: Date;
+  
+  /** 更新时间 */
+  updated_at: Date;
+}
+
+// 知识库表
+export interface KnowInfo {
+  /** 主键ID */
+  id: number;
+  
+  /** 文章的标题 */
+  title?: string;
+  
+  /** 文章的标签 */
+  tags?: string;
+  
+  /** 文章的内容 */
+  content?: string;
+  
+  /** 文章的作者 */
+  author?: string;
+  
+  /** 文章的分类 */
+  category?: string;
+  
+  /** 文章的封面图片URL */
+  cover_url?: string;
+  
+  /** 审核状态 */
+  audit_status?: number;
+  
+  /** 是否被删除 (0否 1是) */
+  is_deleted?: number;
+  
+  /** 创建时间 */
+  created_at: Date;
+  
+  /** 更新时间 */
+  updated_at: Date;
+}
+
+// 操作日志表
+export interface OperationLog {
+  /** 主键ID */
+  id: number;
+  
+  /** 操作人ID */
+  operator_id: number;
+  
+  /** 操作类型 */
+  operation_type: string;
+  
+  /** 操作内容 */
+  operation_content?: string;
+  
+  /** 操作结果 */
+  operation_result?: string;
+  
+  /** 操作IP */
+  ip_address?: string;
+  
+  /** 是否删除 (0否 1是) */
+  is_deleted?: number;
+  
+  /** 创建时间 */
+  created_at: Date;
+  
+  /** 更新时间 */
+  updated_at: Date;
+}
+
+// 机柜信息表
+export interface RackInfo {
+  /** 主键ID */
+  id: number;
+  
+  /** 机柜名称 */
+  rack_name?: string;
+  
+  /** 机柜编号 */
+  rack_code?: string;
+  
+  /** 机柜可容纳设备数量,默认42U */
+  capacity?: number;
+  
+  /** 机柜X轴位置坐标 */
+  position_x?: number;
+  
+  /** 机柜Y轴位置坐标 */
+  position_y?: number;
+  
+  /** 机柜Z轴位置坐标 */
+  position_z?: number;
+  
+  /** 机柜所在区域 */
+  area?: string;
+  
+  /** 机柜所在机房 */
+  room?: string;
+  
+  /** 备注信息 */
+  remark?: string;
+  
+  /** 是否禁用 (0否 1是) */
+  is_disabled?: EnableStatus;
+  
+  /** 是否被删除 (0否 1是) */
+  is_deleted?: DeleteStatus;
+  
+  /** 创建时间 */
+  created_at: Date;
+  
+  /** 更新时间 */
+  updated_at: Date;
+}
+
+// 机柜服务器表
+export interface RackServer {
+  /** 主键ID */
+  id: number;
+  
+  /** 关联的机柜ID */
+  rack_id: number;
+  
+  /** 关联的资产ID */
+  asset_id: number;
+  
+  /** 设备安装的起始U位 */
+  start_position: number;
+  
+  /** 设备占用U数 */
+  size?: number;
+  
+  /** 服务器类型 */
+  server_type?: number;
+  
+  /** 备注信息 */
+  remark?: string;
+  
+  /** 是否禁用 (0否 1是) */
+  is_disabled?: EnableStatus;
+  
+  /** 是否被删除 (0否 1是) */
+  is_deleted?: DeleteStatus;
+  
+  /** 创建时间 */
+  created_at: Date;
+  
+  /** 更新时间 */
+  updated_at: Date;
+}
+
+// 机柜服务器类型表
+export interface RackServerType {
+  /** 主键ID */
+  id: number;
+  
+  /** 类型名称 */
+  name: string;
+  
+  /** 类型编码 */
+  code: string;
+
+  /** 类型图片 */
+  image_url?: string;
+  
+  /** 类型描述 */
+  description?: string;
+  
+  /** 是否启用 (0否 1是) */
+  is_enabled?: EnableStatus;
+  
+  /** 是否被删除 (0否 1是) */
+  is_deleted?: DeleteStatus;
+  
+  /** 创建时间 */
+  created_at: Date;
+  
+  /** 更新时间 */
+  updated_at: Date;
+}
+
+// 工单表
+export interface WorkOrder {
+  /** 主键ID */
+  id: number;
+  
+  /** 工单标题 */
+  title: string;
+  
+  /** 关联设备ID */
+  device_id?: number;
+  
+  /** 关联告警ID */
+  alert_id?: number;
+  
+  /** 工单模板ID */
+  template_id?: number;
+  
+  /** 工单内容 */
+  content?: string;
+  
+  /** 工单状态 */
+  status: WorkOrderStatus;
+  
+  /** 优先级 */
+  priority: WorkOrderPriority;
+  
+  /** 创建人ID */
+  creator_id: number;
+  
+  /** 处理人ID */
+  handler_id?: number;
+  
+  /** 审核人ID */
+  auditor_id?: number;
+  
+  /** 截止时间 */
+  deadline?: Date;
+  
+  /** 处理结果 */
+  handle_result?: string;
+  
+  /** 审核结果 */
+  audit_result?: string;
+  
+  /** 是否删除 (0否 1是) */
+  is_deleted?: number;
+  
+  /** 创建时间 */
+  created_at: Date;
+  
+  /** 更新时间 */
+  updated_at: Date;
+}
+
+// 工单处理记录表
+export interface WorkOrderLog {
+  /** 主键ID */
+  id: number;
+  
+  /** 工单ID */
+  work_order_id: number;
+  
+  /** 操作人ID */
+  operator_id: number;
+  
+  /** 操作类型 */
+  action: WorkOrderAction;
+  
+  /** 处理内容 */
+  content?: string;
+  
+  /** 是否删除 (0否 1是) */
+  is_deleted?: number;
+  
+  /** 创建时间 */
+  created_at: Date;
+  
+  /** 更新时间 */
+  updated_at: Date;
+}
+
+// 工单模板表
+export interface WorkOrderTemplate {
+  /** 主键ID */
+  id: number;
+  
+  /** 模板名称 */
+  name: string;
+  
+  /** 模板内容 */
+  content?: string;
+  
+  /** 是否需要审核 (0否 1是) */
+  need_audit: number;
+  
+  /** 默认处理人 */
+  default_handler_id?: number;
+  
+  /** 默认完成时限(小时) */
+  default_deadline_hours?: number;
+  
+  /** 常用语列表 */
+  common_phrases?: CommonPhrase[];
+  
+  /** SLA配置 */
+  sla_config?: SLAConfig;
+  
+  /** 流程配置 */
+  workflow_config?: WorkflowConfig;
+  
+  /** 是否启用 (0否 1是) */
+  is_enabled?: number;
+  
+  /** 是否删除 (0否 1是) */
+  is_deleted?: number;
+  
+  /** 创建时间 */
+  created_at: Date;
+  
+  /** 更新时间 */
+  updated_at: Date;
+}
+
+// 资产信息表
+export interface ZichanInfo {
+  /** 主键ID */
+  id: number;
+  
+  /** 资产名称 */
+  asset_name?: string;
+  
+  /** 设备分类 */
+  device_category?: DeviceCategory;
+  
+  /** 归属区域 */
+  area?: AreaType;
+  
+  /** 供应商 */
+  supplier?: string;
+  
+  /** 使用地址 */
+  use_address?: string;
+  
+  /** 运行情况 */
+  operation_status?: string;
+  
+  /** 是否审核 (0否 1是) */
+  is_audited?: number;
+  
+  /** 审核状态 */
+  audit_status?: AuditStatus;
+  
+  /** 资产状态 */
+  asset_status?: AssetStatus;
+  
+  /** 入库数量 */
+  stock_quantity?: number;
+  
+  /** 质保时间 */
+  warranty_time?: Date;
+  
+  /** 品牌 */
+  brand?: string;
+  
+  /** IP地址 */
+  ip_address?: string;
+  
+  /** 设备状态 */
+  device_status?: DeviceStatus;
+  
+  /** 网络状态 */
+  network_status?: NetworkStatus;
+  
+  /** 丢包率 */
+  packet_loss?: number;
+  
+  /** 图片 */
+  images?: string;
+  
+  /** 是否备件 (0否 1是) */
+  is_spare?: number;
+  
+  /** 是否被禁用 (0否 1是) */
+  is_disabled?: number;
+  
+  /** 是否被删除 (0否 1是) */
+  is_deleted?: number;
+  
+  /** 资产位置经度 */
+  longitude?: number;
+  
+  /** 资产位置纬度 */
+  latitude?: number;
+  
+  /** CPU信息 */
+  cpu?: string;
+  
+  /** 内存信息 */
+  memory?: string;
+  
+  /** 硬盘信息 */
+  disk?: string;
+  
+  /** 创建时间 */
+  created_at: Date;
+  
+  /** 更新时间 */
+  updated_at: Date;
+}
+
+// 资产分类表
+export interface ZichanCategory {
+  /** 主键ID */
+  id: number;
+
+  /** 分类名称 */
+  name: string;
+
+  /** 分类编码 */
+  code: string;
+
+  /** 分类图片 */
+  image_url?: string;
+
+  /** 分类描述 */
+  description?: string;
+
+  /** 是否启用 (0否 1是) */
+  is_enabled?: EnableStatus;
+
+  /** 是否被删除 (0否 1是) */ 
+  is_deleted?: DeleteStatus;
+
+  /** 创建时间 */
+  created_at: Date;
+
+  /** 更新时间 */
+  updated_at: Date;
+}
+
+// 资产归属区域
+export interface ZichanArea {
+  /** 主键ID */
+  id: number;
+
+  /** 区域名称 */
+  name: string;
+
+  /** 区域编码 */
+  code: string;
+
+  /** 区域图片 */
+  image_url?: string;
+
+  /** 区域描述 */
+  description?: string;
+  
+  /** 是否启用 (0否 1是) */
+  is_enabled?: EnableStatus;
+
+  /** 是否被删除 (0否 1是) */ 
+  is_deleted?: DeleteStatus;
+
+  /** 创建时间 */
+  created_at: Date;
+
+  /** 更新时间 */
+  updated_at: Date;
+}
+
+// 资产流转记录表
+export interface ZichanTransLog {
+  /** 主键ID */
+  id: number;
+  
+  /** 资产流转 */
+  asset_transfer?: AssetTransferType;
+  
+  /** 资产ID */
+  asset_id?: number;
+  
+  /** 人员 */
+  person?: string;
+  
+  /** 部门 */
+  department?: string;
+  
+  /** 电话 */
+  phone?: string;
+  
+  /** 流转事由 */
+  transfer_reason?: string;
+  
+  /** 流转时间 */
+  transfer_time?: Date | string;
+  
+  /** 是否被禁用 (0否 1是) */
+  is_disabled?: number;
+  
+  /** 是否被删除 (0否 1是) */
+  is_deleted?: number;
+  
+  /** 创建时间 */
+  created_at: Date;
+  
+  /** 更新时间 */
+  updated_at: Date;
+  
+  /** 关联的资产信息(查询时后端关联返回) */
+  asset_info?: ZichanInfo;
+}
+
+// 文件库接口
+export interface FileLibrary {
+  /** 主键ID */
+  id: number;
+  
+  /** 文件名称 */
+  file_name: string;
+  
+  /** 原始文件名 */
+  original_filename?: string;
+  
+  /** 文件路径 */
+  file_path: string;
+  
+  /** 文件类型 */
+  file_type: string;
+  
+  /** 文件大小(字节) */
+  file_size: number;
+  
+  /** 上传用户ID */
+  uploader_id?: number;
+  
+  /** 上传者名称 */
+  uploader_name?: string;
+  
+  /** 文件分类 */
+  category_id?: number;
+  
+  /** 文件标签 */
+  tags?: string;
+  
+  /** 文件描述 */
+  description?: string;
+  
+  /** 下载次数 */
+  download_count: number;
+  
+  /** 是否禁用 (0否 1是) */
+  is_disabled?: EnableStatus;
+  
+  /** 是否被删除 (0否 1是) */
+  is_deleted?: DeleteStatus;
+  
+  /** 创建时间 */
+  created_at: string;
+  
+  /** 更新时间 */
+  updated_at: string;
+}
+
+// 文件分类接口
+export interface FileCategory {
+  id: number;
+  name: string;
+  code: string;
+  description?: string;
+  is_deleted?: DeleteStatus;
+  created_at: string;
+  updated_at: string;
+} 
+
+// 资产流转类型枚举
+export enum AssetTransferType {
+  STOCK = 0,       // 在库
+  BORROW = 1,      // 借用
+  RETURN = 2,      // 归还
+  LOST = 3,        // 遗失
+  MAINTAIN = 4     // 维护保养
+}
+
+// 资产流转类型名称映射
+export const AssetTransferTypeNameMap: Record<AssetTransferType, string> = {
+  [AssetTransferType.STOCK]: '在库',
+  [AssetTransferType.BORROW]: '借用',
+  [AssetTransferType.RETURN]: '归还',
+  [AssetTransferType.LOST]: '遗失',
+  [AssetTransferType.MAINTAIN]: '维护保养'
+};
+
+// 资产流转类型颜色映射
+export const AssetTransferTypeColorMap: Record<AssetTransferType, string> = {
+  [AssetTransferType.STOCK]: 'green',
+  [AssetTransferType.BORROW]: 'blue',
+  [AssetTransferType.RETURN]: 'cyan',
+  [AssetTransferType.LOST]: 'red',
+  [AssetTransferType.MAINTAIN]: 'orange'
+};
+
+// 添加图表类型定义(从大屏移植)
+export interface CategoryChartData {
+  设备分类: string;
+  设备数: number;
+}
+
+export interface CategoryChartDataWithPercent extends CategoryChartData {
+  百分比: string;
+}
+
+export interface OnlineRateChartData {
+  time_interval: string;
+  online_devices: number;
+  total_devices: number;
+}
+
+export interface StateChartData {
+  资产流转: string;
+  设备数: number;
+}
+
+export interface StateChartDataWithPercent extends StateChartData {
+  百分比: string;
+}
+
+export interface AlarmChartData {
+  time_interval: string;
+  total_devices: number;
+}
+
+export interface AlarmDeviceData {
+  deviceName: string;
+  alarmCount: number;
+  rank: number;
+}
+
+// 设备与资产信息结合的接口
+export interface DeviceWithAssetInfo {
+  id: number;
+  asset_name?: string;
+  device_category?: number;
+  ip_address?: string;
+  device_status?: DeviceStatus;
+  network_status?: NetworkStatus;
+  packet_loss?: PacketLossStatus;
+  cpu?: string;
+  memory?: string;
+  disk?: string;
+  is_deleted?: number;
+}
+
+// 地图类型
+export enum MapMode {
+  ONLINE = 'online',
+  OFFLINE = 'offline'
+}
+
+// 地图标记数据接口 - 基础定义
+export interface MarkerData {
+  /** 标记点经度 */
+  longitude: number;
+  
+  /** 标记点纬度 */
+  latitude: number;
+  
+  /** 标记点ID */
+  id?: string | number;
+  
+  /** 标记点标题 */
+  title?: string;
+  
+  /** 标记点描述 */
+  description?: string;
+  
+  /** 标记点图标URL */
+  iconUrl?: string;
+  
+  /** 标记点状态 */
+  status?: string;
+  
+  /** 标记点类型 */
+  type?: string;
+  
+  /** 标记点额外数据 */
+  extraData?: Record<string, any>;
+}
+
+// 设备地图监控视图设备接口
+export interface MapViewDevice extends MarkerData {
+  id: number;
+  name?: string;
+  type_code: string;
+  device_category?: DeviceCategory;
+  device_status?: DeviceStatus;
+  description?: string;
+  address?: string;
+  protocol?: DeviceProtocolType;
+  last_update_time?: string;
+  area_code?: string;
+  area_name?: string;
+  image_url?: string;
+}
+
+// 设备地图筛选条件
+export interface DeviceMapFilter {
+  type_code?: string;
+  device_category?: DeviceCategory[];
+  device_status?: DeviceStatus;
+  area_code?: string[];
+  keyword?: string;
+  device_id?: number;
+}
+
+// 设备地图统计数据接口
+export interface DeviceMapStats {
+  total: number;
+  online: number;
+  offline: number;
+  error: number;
+  normal?: number;
+  fault?: number;
+  categoryStats?: {
+    category: DeviceCategory;
+    count: number;
+    name: string;
+  }[];
+}
+
+// 设备树统计数据类型
+export type DeviceTreeStats = Record<string, {
+  total: number;
+  online: number;
+  offline: number;
+  error: number;
+}>;
+
+// 设备地图响应数据接口
+export interface DeviceMapDataResponse {
+  data: MapViewDevice[];
+  stats: DeviceMapStats;
+  total?: number;
+  page?: number;
+  pageSize?: number;
+}
+
+// 设备地图统计响应接口
+export interface DeviceMapStatsResponse {
+  data: DeviceMapStats;
+}
+
+// 设备树节点类型枚举
+export enum DeviceTreeNodeType {
+  CATEGORY = 'category',
+  DEVICE = 'device'
+}
+
+// 设备树节点状态枚举
+export enum DeviceTreeNodeStatus {
+  NORMAL = 'normal',
+  ERROR = 'error',
+  OFFLINE = 'offline',
+  WARNING = 'warning'
+}
+
+// 设备树节点接口
+export interface DeviceTreeNode {
+  key: string;
+  title: string;
+  type: DeviceTreeNodeType;
+  status?: DeviceTreeNodeStatus;
+  icon?: string | null;
+  isLeaf?: boolean;
+  children?: DeviceTreeNode[];
+}
+
+// 登录位置相关类型定义
+export interface LoginLocation {
+  id: number;
+  loginTime: string;
+  ipAddress: string;
+  longitude: number;
+  latitude: number;
+  location_name?: string;
+  user: {
+    id: number;
+    username: string;
+    nickname: string;
+  };
+}
+
+export interface LoginLocationDetail {
+  id: number;
+  user_id: number;
+  login_time: string;
+  ip_address: string;
+  longitude: number;
+  latitude: number;
+  location_name: string;
+  user_agent: string;
+  user: {
+    id: number;
+    username: string;
+    nickname: string;
+  };
+}
+
+// 系统设置分组
+export enum SystemSettingGroup {
+  BASIC = 'basic',           // 基础设置
+  FEATURE = 'feature',       // 功能设置
+  UPLOAD = 'upload',         // 上传设置
+  NOTIFICATION = 'notify'    // 通知设置
+}
+
+// 系统设置键
+export enum SystemSettingKey {
+  // 基础设置
+  SITE_NAME = 'SITE_NAME',              // 站点名称
+  SITE_DESCRIPTION = 'SITE_DESCRIPTION', // 站点描述
+  SITE_KEYWORDS = 'SITE_KEYWORDS',       // 站点关键词
+  SITE_LOGO = 'SITE_LOGO',              // 站点LOGO
+  SITE_FAVICON = 'SITE_FAVICON',         // 站点图标
+  
+  // 功能设置
+  ENABLE_REGISTER = 'ENABLE_REGISTER',   // 是否开启注册
+  ENABLE_CAPTCHA = 'ENABLE_CAPTCHA',     // 是否开启验证码
+  LOGIN_ATTEMPTS = 'LOGIN_ATTEMPTS',     // 登录尝试次数
+  SESSION_TIMEOUT = 'SESSION_TIMEOUT',   // 会话超时时间(分钟)
+  
+  // 上传设置
+  UPLOAD_MAX_SIZE = 'UPLOAD_MAX_SIZE',   // 最大上传大小(MB)
+  ALLOWED_FILE_TYPES = 'ALLOWED_FILE_TYPES', // 允许的文件类型
+  IMAGE_COMPRESS = 'IMAGE_COMPRESS',     // 是否压缩图片
+  IMAGE_MAX_WIDTH = 'IMAGE_MAX_WIDTH',   // 图片最大宽度
+  
+  // 通知设置
+  NOTIFY_ON_LOGIN = 'NOTIFY_ON_LOGIN',   // 登录通知
+  NOTIFY_ON_UPLOAD = 'NOTIFY_ON_UPLOAD', // 上传通知
+  NOTIFY_ON_ERROR = 'NOTIFY_ON_ERROR',   // 错误通知
+
+  // 主题设置
+  ENABLE_THEME_CONFIG = 'ENABLE_THEME_CONFIG' // 是否开启主题配置
+}
+
+export type SystemSettingGroupType = SystemSettingGroup;
+export type SystemSettingKeyType = SystemSettingKey;
+
+// 系统设置值类型
+export type SystemSettingValue = string | number | boolean;
+
+// 系统设置项接口
+export interface SystemSetting {
+  id: number;
+  key: SystemSettingKeyType;         // 设置键
+  value: SystemSettingValue;     // 设置值
+  description?: string;          // 设置描述
+  group: SystemSettingGroupType;     // 设置分组
+  created_at?: string;
+  updated_at?: string;
+}
+
+// 系统设置分组类型
+export interface SystemSettingGroupData {
+  name: string;
+  description: string;
+  settings: SystemSetting[];
+}
+
+// 系统设置记录类型
+export type SystemSettingRecord = Record<SystemSettingKey, SystemSettingValue>;
+
+// 允许的文件类型枚举
+export enum AllowedFileType {
+  JPG = 'jpg',
+  JPEG = 'jpeg',
+  PNG = 'png',
+  GIF = 'gif',
+  DOC = 'doc',
+  DOCX = 'docx',
+  XLS = 'xls',
+  XLSX = 'xlsx',
+  PDF = 'pdf'
+}
+
+// 允许的文件类型列表(用于系统设置)
+export const ALLOWED_FILE_TYPES = Object.values(AllowedFileType).join(',');

+ 33 - 0
deno.json

@@ -0,0 +1,33 @@
+{
+  "imports": {
+    "hono": "https://esm.d8d.fun/hono@4.7.4",
+    "hono/jsx": "https://esm.d8d.fun/hono@4.7.4/jsx",
+    "hono/jsx/jsx-runtime": "https://esm.d8d.fun/hono@4.7.4/jsx/jsx-runtime",
+    "hono/cors": "https://esm.d8d.fun/hono@4.7.4/cors",
+    "hono/serve-static": "https://esm.d8d.fun/hono@4.7.4/serve-static",
+    "hono/deno": "https://esm.d8d.fun/hono@4.7.4/deno",
+    "@d8d-appcontainer/auth": "https://esm.d8d.fun/@d8d-appcontainer/auth@0.0.14",
+    "debug": "https://esm.d8d.fun/debug@4.4.0",
+    "dayjs/plugin/utc": "https://esm.d8d.fun/dayjs@1.11.13/plugin/utc",
+    "react": "https://esm.d8d.fun/react@19.0.0",
+    "react-dom": "https://esm.d8d.fun/react-dom@19.0.0",
+    "react-dom/client": "https://esm.d8d.fun/react-dom@19.0.0/client",
+    "react-router": "https://esm.d8d.fun/react-router@7.3.0?deps=react@19.0.0,react-dom@19.0.0",
+    "antd": "https://esm.d8d.fun/antd@5.24.5?standalone&deps=react@19.0.0,react-dom@19.0.0",
+    "antd/locale/zh_CN": "https://esm.d8d.fun/antd@5.24.5/locale/zh_CN?standalone&deps=react@19.0.0,react-dom@19.0.0",
+    "@ant-design/icons": "https://esm.d8d.fun/@ant-design/icons@5.6.1?standalone&deps=react@19.0.0,react-dom@19.0.0",
+    "@tanstack/react-query": "https://esm.d8d.fun/@tanstack/react-query@5.67.1?deps=react@19.0.0,react-dom@19.0.0",
+    "axios": "https://esm.d8d.fun/axios@1.6.2",
+    "dayjs": "https://esm.d8d.fun/dayjs@1.11.13",
+    "dayjs/locale/zh-cn": "https://esm.d8d.fun/dayjs@1.11.13/locale/zh-cn",
+    "dayjs/plugin/weekday": "https://esm.d8d.fun/dayjs@1.11.13/plugin/weekday",
+    "dayjs/plugin/localeData": "https://esm.d8d.fun/dayjs@1.11.13/plugin/localeData",
+    "@d8d-appcontainer/types": "https://esm.d8d.fun/@d8d-appcontainer/types@3.0.47",
+    "@d8d-appcontainer/api": "https://esm.d8d.fun/@d8d-appcontainer/api@3.0.47",
+    "@ant-design/plots": "https://esm.d8d.fun/@ant-design/plots@2.1.13?deps=react@19.0.0,react-dom@19.0.0",
+    "lodash": "https://esm.d8d.fun/lodash@4.17.21"
+  },
+  "compilerOptions": {
+    "lib": ["dom", "dom.iterable", "esnext", "deno.ns"]
+  }
+}

+ 209 - 0
deno.lock

@@ -0,0 +1,209 @@
+{
+  "version": "4",
+  "redirects": {
+    "https://esm.d8d.fun/@deno/shim-deno-test@^0.5.0?target=denonext": "https://esm.d8d.fun/@deno/shim-deno-test@0.5.0?target=denonext",
+    "https://esm.d8d.fun/@deno/shim-deno@~0.18.0?target=denonext": "https://esm.d8d.fun/@deno/shim-deno@0.18.2?target=denonext",
+    "https://esm.d8d.fun/@socket.io/component-emitter@~3.1.0?target=denonext": "https://esm.d8d.fun/@socket.io/component-emitter@3.1.2?target=denonext",
+    "https://esm.d8d.fun/asynckit@^0.4.0?target=denonext": "https://esm.d8d.fun/asynckit@0.4.0?target=denonext",
+    "https://esm.d8d.fun/axios@^1.7.2?target=denonext": "https://esm.d8d.fun/axios@1.8.4?target=denonext",
+    "https://esm.d8d.fun/bufferutil@^4.0.1?target=denonext": "https://esm.d8d.fun/bufferutil@4.0.9?target=denonext",
+    "https://esm.d8d.fun/combined-stream@^1.0.8?target=denonext": "https://esm.d8d.fun/combined-stream@1.0.8?target=denonext",
+    "https://esm.d8d.fun/debug?target=denonext": "https://esm.d8d.fun/debug@4.4.0?target=denonext",
+    "https://esm.d8d.fun/delayed-stream@~1.0.0?target=denonext": "https://esm.d8d.fun/delayed-stream@1.0.0?target=denonext",
+    "https://esm.d8d.fun/engine.io-client@~6.6.1?target=denonext": "https://esm.d8d.fun/engine.io-client@6.6.3?target=denonext",
+    "https://esm.d8d.fun/engine.io-parser@~5.2.1?target=denonext": "https://esm.d8d.fun/engine.io-parser@5.2.3?target=denonext",
+    "https://esm.d8d.fun/follow-redirects@^1.15.6?target=denonext": "https://esm.d8d.fun/follow-redirects@1.15.9?target=denonext",
+    "https://esm.d8d.fun/form-data@^4.0.0?target=denonext": "https://esm.d8d.fun/form-data@4.0.2?target=denonext",
+    "https://esm.d8d.fun/isexe@^3.1.1?target=denonext": "https://esm.d8d.fun/isexe@3.1.1?target=denonext",
+    "https://esm.d8d.fun/jsonwebtoken@^9.0.2?target=denonext": "https://esm.d8d.fun/jsonwebtoken@9.0.2?target=denonext",
+    "https://esm.d8d.fun/jwa@^1.4.1?target=denonext": "https://esm.d8d.fun/jwa@1.4.1?target=denonext",
+    "https://esm.d8d.fun/jws@^3.2.2?target=denonext": "https://esm.d8d.fun/jws@3.2.2?target=denonext",
+    "https://esm.d8d.fun/lodash.includes@^4.3.0?target=denonext": "https://esm.d8d.fun/lodash.includes@4.3.0?target=denonext",
+    "https://esm.d8d.fun/lodash.isboolean@^3.0.3?target=denonext": "https://esm.d8d.fun/lodash.isboolean@3.0.3?target=denonext",
+    "https://esm.d8d.fun/lodash.isinteger@^4.0.4?target=denonext": "https://esm.d8d.fun/lodash.isinteger@4.0.4?target=denonext",
+    "https://esm.d8d.fun/lodash.isnumber@^3.0.3?target=denonext": "https://esm.d8d.fun/lodash.isnumber@3.0.3?target=denonext",
+    "https://esm.d8d.fun/lodash.isplainobject@^4.0.6?target=denonext": "https://esm.d8d.fun/lodash.isplainobject@4.0.6?target=denonext",
+    "https://esm.d8d.fun/lodash.isstring@^4.0.1?target=denonext": "https://esm.d8d.fun/lodash.isstring@4.0.1?target=denonext",
+    "https://esm.d8d.fun/lodash.once@^4.0.0?target=denonext": "https://esm.d8d.fun/lodash.once@4.1.1?target=denonext",
+    "https://esm.d8d.fun/mime-types@^2.1.12?target=denonext": "https://esm.d8d.fun/mime-types@2.1.35?target=denonext",
+    "https://esm.d8d.fun/ms@^2.1.1?target=denonext": "https://esm.d8d.fun/ms@2.1.3?target=denonext",
+    "https://esm.d8d.fun/ms@^2.1.3?target=denonext": "https://esm.d8d.fun/ms@2.1.3?target=denonext",
+    "https://esm.d8d.fun/nanoid@^5.1.2?target=denonext": "https://esm.d8d.fun/nanoid@5.1.5?target=denonext",
+    "https://esm.d8d.fun/node-gyp-build@^4.3.0?target=denonext": "https://esm.d8d.fun/node-gyp-build@4.8.4?target=denonext",
+    "https://esm.d8d.fun/proxy-from-env@^1.1.0?target=denonext": "https://esm.d8d.fun/proxy-from-env@1.1.0?target=denonext",
+    "https://esm.d8d.fun/safe-buffer@^5.0.1?target=denonext": "https://esm.d8d.fun/safe-buffer@5.2.1?target=denonext",
+    "https://esm.d8d.fun/semver@^7.5.4?target=denonext": "https://esm.d8d.fun/semver@7.7.1?target=denonext",
+    "https://esm.d8d.fun/socket.io-client@^4.7.2?target=denonext": "https://esm.d8d.fun/socket.io-client@4.8.1?target=denonext",
+    "https://esm.d8d.fun/socket.io-parser@~4.2.4?target=denonext": "https://esm.d8d.fun/socket.io-parser@4.2.4?target=denonext",
+    "https://esm.d8d.fun/supports-color?target=denonext": "https://esm.d8d.fun/supports-color@10.0.0?target=denonext",
+    "https://esm.d8d.fun/utf-8-validate@%3E=5.0.2?target=denonext": "https://esm.d8d.fun/utf-8-validate@6.0.5?target=denonext",
+    "https://esm.d8d.fun/which@^4.0.0?target=denonext": "https://esm.d8d.fun/which@4.0.0?target=denonext",
+    "https://esm.d8d.fun/ws@~8.17.1?target=denonext": "https://esm.d8d.fun/ws@8.17.1?target=denonext",
+    "https://esm.d8d.fun/xmlhttprequest-ssl@~2.1.1?target=denonext": "https://esm.d8d.fun/xmlhttprequest-ssl@2.1.2?target=denonext"
+  },
+  "remote": {
+    "https://deno.land/std@0.150.0/media_types/_util.ts": "ce9b4fc4ba1c447dafab619055e20fd88236ca6bdd7834a21f98bd193c3fbfa1",
+    "https://deno.land/std@0.150.0/media_types/mod.ts": "2d4b6f32a087029272dc59e0a55ae3cc4d1b27b794ccf528e94b1925795b3118",
+    "https://deno.land/std@0.150.0/media_types/vendor/mime-db.v1.52.0.ts": "724cee25fa40f1a52d3937d6b4fbbfdd7791ff55e1b7ac08d9319d5632c7f5af",
+    "https://deno.land/x/xhr@0.3.0/mod.ts": "094aacd627fd9635cd942053bf8032b5223b909858fa9dc8ffa583752ff63b20",
+    "https://esm.d8d.fun/@d8d-appcontainer/api@3.0.47": "6f44e26f9101c9c00c374a01defa883ae9db7c851ef2b8b130cb6c2d51a41b59",
+    "https://esm.d8d.fun/@d8d-appcontainer/api@3.0.47/denonext/api.mjs": "778329c130f21a547d6726e8e13fa680a74faecd96e4d953acb481724f8db7be",
+    "https://esm.d8d.fun/@d8d-appcontainer/auth@0.0.14": "4107b05a0631cc0440dce8b653ccf8e37010e67a7cb80263ac127e1a97c77dc3",
+    "https://esm.d8d.fun/@d8d-appcontainer/auth@0.0.14/denonext/auth.mjs": "fafb3b08f1bfe7b0a5ecbd2856935288e8b564157d11e5157c7f217ac3f73b43",
+    "https://esm.d8d.fun/@d8d-appcontainer/types@3.0.47/denonext/types.mjs": "44efd25cb0ad7942ad29d520b5f3296a908d7cefbe85382bcc45e3b3c6c9624c",
+    "https://esm.d8d.fun/@deno/shim-deno-test@0.5.0/denonext/shim-deno-test.mjs": "21298ee12e8add3e8efe527aa1dd2a4fd029eb1876f5a59107bbe62b3969e282",
+    "https://esm.d8d.fun/@deno/shim-deno-test@0.5.0?target=denonext": "503b73de1a14bd33782220e11fa2b33e9c87d574ac793e7addf1466c5436e66a",
+    "https://esm.d8d.fun/@deno/shim-deno@0.18.2/denonext/shim-deno.mjs": "819d8ac34fdaf60658cf03d137f14adaff3f13a279ffd79cd8797d84a6ac46ab",
+    "https://esm.d8d.fun/@deno/shim-deno@0.18.2?target=denonext": "ffa3ca347bb6b6530720158f307a2e31b16728fbb52e6432254a07d52fcbc404",
+    "https://esm.d8d.fun/@socket.io/component-emitter@3.1.2/denonext/component-emitter.mjs": "3c6c5f2d64d4933b577a7117df1d8855c51ff01ab3dea8f42af1adcb1a5989e7",
+    "https://esm.d8d.fun/@socket.io/component-emitter@3.1.2?target=denonext": "f6ff0f94ae3c9850a2c3a925cc2b236ec03a80fc2298d0ca48c2a90b10487db3",
+    "https://esm.d8d.fun/asynckit@0.4.0/denonext/asynckit.mjs": "4ef3be6eb52c104699b90ca5524db55ec15bc76b361432f05c16b6106279ba72",
+    "https://esm.d8d.fun/asynckit@0.4.0?target=denonext": "c6bd8832d6d16b648e22d124a16d33c3a7f7076e92be9444f2e4f6b27545708d",
+    "https://esm.d8d.fun/axios@1.8.4/denonext/axios.mjs": "a0c3d648353c6a1b9864f1067ff9d366b91ccf2c8a413ec30a6b073dea05366b",
+    "https://esm.d8d.fun/axios@1.8.4/denonext/lib/adapters/http.mjs": "1c7e3b34ddafb39f9b36111dc81ab83a4b1dfed316efc7c9971a534f0c161e7f",
+    "https://esm.d8d.fun/axios@1.8.4/denonext/lib/adapters/xhr.mjs": "443f6f99410af813126a7b3b004a7fe4d3ce1eb51fb940b15a3fb0932773ff78",
+    "https://esm.d8d.fun/axios@1.8.4/denonext/lib/cancel/CanceledError.mjs": "6432ce6e9d09faff4439c0c5dfa1d44a79cea9901eb5c1894c7c7497e791e0fd",
+    "https://esm.d8d.fun/axios@1.8.4/denonext/lib/core/AxiosError.mjs": "2366d9c8250a030e6d82cf72db361d0df9a4311e785e2dd5dd34364c4690cbd3",
+    "https://esm.d8d.fun/axios@1.8.4/denonext/lib/core/AxiosHeaders.mjs": "f05eb0c07bf1f6d418dad3a3e310070822e5d7059e07c9cc232f365813d1f58e",
+    "https://esm.d8d.fun/axios@1.8.4/denonext/lib/core/mergeConfig.mjs": "271e93496a6e07b99694817bbcd619c4862b839f5c25538d736168697d09b846",
+    "https://esm.d8d.fun/axios@1.8.4/denonext/lib/defaults/transitional.mjs": "cf97aea57cebc35857e915fa922468b267947fbd8c8ee6cecc3d9878d2429b4e",
+    "https://esm.d8d.fun/axios@1.8.4/denonext/lib/env/data.mjs": "b7d5dde239c8a22e820d1e42b045a19d981681e0d7b3eebd3b05f8caf1523455",
+    "https://esm.d8d.fun/axios@1.8.4/denonext/lib/helpers/bind.mjs": "6f9b25a0abdcbdee99eea549869d4357151d21d8a004174fe5c135f82fc61412",
+    "https://esm.d8d.fun/axios@1.8.4/denonext/lib/helpers/parseProtocol.mjs": "3268a599f6fa29d6ce6b5e8ee2a61999351b5e414b42cc584c7ee80eae248802",
+    "https://esm.d8d.fun/axios@1.8.4/denonext/lib/helpers/progressEventReducer.mjs": "308616bf82ad4621ed466b2355025d108d40daac8e95831b56018fbe96c2ac1a",
+    "https://esm.d8d.fun/axios@1.8.4/denonext/lib/helpers/resolveConfig.mjs": "5e7d2668d1b6d93c6f860aba237e92c2b07b6f0dfedca3b9e304f1dd4578e224",
+    "https://esm.d8d.fun/axios@1.8.4/denonext/lib/helpers/toFormData.mjs": "9f8a95c8edd57d5e0acb598c56bfac9efae1e5ed39d6c73f94cebc603222b74d",
+    "https://esm.d8d.fun/axios@1.8.4/denonext/lib/platform/index.mjs": "8022c68893946f6714646f45cb87e5a8d58ac966e1d801a7abeb89f40adbb2fc",
+    "https://esm.d8d.fun/axios@1.8.4/denonext/lib/platform/node/classes/FormData.mjs": "3af5a3b503dafe0b26a94454685a1a68da3b78540fa1ca65a85814022d725d14",
+    "https://esm.d8d.fun/axios@1.8.4/denonext/unsafe/core/buildFullPath.mjs": "bd725f6f2e86698888721717727752302d0ae5153bf6216ba5eb6a716692fee2",
+    "https://esm.d8d.fun/axios@1.8.4/denonext/unsafe/core/settle.mjs": "131ebbb5c8592f9505988f7cc8d33b3905a5ea06425db72723d0164be0e19f0c",
+    "https://esm.d8d.fun/axios@1.8.4/denonext/unsafe/helpers/buildURL.mjs": "07eee5bbb02b63ca69bda19d9724480724b82fdc8a75a47c46a1f6dfe985f159",
+    "https://esm.d8d.fun/axios@1.8.4/denonext/unsafe/helpers/combineURLs.mjs": "a2c79317fdc707709b83dc7d3166cab69c3f9dca5ad5c4612ff1e6b29912dee8",
+    "https://esm.d8d.fun/axios@1.8.4/denonext/unsafe/helpers/isAbsoluteURL.mjs": "df75312b93206485ee501d2b51a5784b1cbf76527cb29803a7fadf1238898881",
+    "https://esm.d8d.fun/axios@1.8.4/denonext/unsafe/utils.mjs": "ba8669ad91f8b94b0796e13f03605a620d7fe2d05419670cfa2f6793fd5707cf",
+    "https://esm.d8d.fun/axios@1.8.4?target=denonext": "3652480f46bbae591b9617f4f039537675c72ed4d8b7b44019d7bfdf173102ae",
+    "https://esm.d8d.fun/buffer-equal-constant-time@1.0.1/denonext/buffer-equal-constant-time.mjs": "c83a9e435334af6863aef9f50bd7d1a855e95055ba8af6dde63eaf674791f64b",
+    "https://esm.d8d.fun/bufferutil@4.0.9/denonext/bufferutil.mjs": "13dca4d5bb2c68cbe119f880fa3bd785b9a81a8e02e0834dae604b4b85295cd8",
+    "https://esm.d8d.fun/bufferutil@4.0.9?target=denonext": "e32574569ab438facfcc3f412c659b0719bbf05477136ca176938c9a3ac45125",
+    "https://esm.d8d.fun/combined-stream@1.0.8/denonext/combined-stream.mjs": "364b91aa4c33e5f0b4075949d93a3407b21a8695031e7c2be29999d588f9ca2c",
+    "https://esm.d8d.fun/combined-stream@1.0.8?target=denonext": "a0c89b8b29494e966774c7a708e33cc2df16a0bbe2279c841d088e169e7ab3c4",
+    "https://esm.d8d.fun/dayjs@1.11.13": "89c34b8b3f7b970708114b4d264c9430c30eb0c2eab1419410c77ffefa18fe2c",
+    "https://esm.d8d.fun/dayjs@1.11.13/denonext/dayjs.mjs": "a6d8258bec464149ab2c9ae26e4bd3736897828586b03f8fea45403080bf8a80",
+    "https://esm.d8d.fun/dayjs@1.11.13/denonext/plugin/utc.mjs": "01c663b7318d6daa10a2377306a878808a535d6dc4056fa5b60a8d31c5d2254f",
+    "https://esm.d8d.fun/dayjs@1.11.13/plugin/utc": "2e41a0673e6e7c7c962983f1680911ef6feb27ded6007bc7705787ac1b2637b7",
+    "https://esm.d8d.fun/debug@4.4.0": "dc29873ca5518385fcbddb2b2fa0f3b31dc6463ba52bdd790818683b9dbdc6ad",
+    "https://esm.d8d.fun/debug@4.4.0/denonext/debug.mjs": "3077d1ff15cfc5b7baee65b0c00b3200aef8ab51ddddfa960972957c347c1cee",
+    "https://esm.d8d.fun/debug@4.4.0?target=denonext": "dc29873ca5518385fcbddb2b2fa0f3b31dc6463ba52bdd790818683b9dbdc6ad",
+    "https://esm.d8d.fun/delayed-stream@1.0.0/denonext/delayed-stream.mjs": "051a3501b7b3d3c593b78a2c7305093a8e363c518cd156f1a77117185e312abe",
+    "https://esm.d8d.fun/delayed-stream@1.0.0?target=denonext": "d363b81e01f4c886114df14aa660c1a938bbb4be851ff12132260bed0db6126e",
+    "https://esm.d8d.fun/ecdsa-sig-formatter@1.0.11/denonext/ecdsa-sig-formatter.mjs": "08370379943e48e31cda373711fe5528f60b3d3d992fae7c0d520ef55607640e",
+    "https://esm.d8d.fun/engine.io-client@6.6.3/denonext/engine.io-client.mjs": "d127a167771015e459e79fb0eb38ee99601ae96ae98924ee407dd77d0ee8be0a",
+    "https://esm.d8d.fun/engine.io-client@6.6.3?target=denonext": "d97129d74541438ec8167b8232ff764b408b8bf4c065924c60823795fa3e038d",
+    "https://esm.d8d.fun/engine.io-parser@5.2.3/denonext/engine.io-parser.mjs": "dfb40060c00806566e236f3112b950f43fa6b5e3a142f14ba2e83ad651f4a451",
+    "https://esm.d8d.fun/engine.io-parser@5.2.3?target=denonext": "1dd633858ff4fd2affd2343c0f16f4d0727524919f53f0a5cf240baefd3c91fd",
+    "https://esm.d8d.fun/follow-redirects@1.15.9/denonext/follow-redirects.mjs": "90866d22d80eface74d3161906835600fbb1d5c9ded05dc72fd55b40960cfce7",
+    "https://esm.d8d.fun/follow-redirects@1.15.9?target=denonext": "c8028ec9d980a1974362377614a4978d1556ba85a75bc32526e8917bede535d1",
+    "https://esm.d8d.fun/form-data@4.0.2/denonext/form-data.mjs": "ad3c492eef1c6153bcfa02eb3041894e8bc8e4aa241ad4d9bd53a385f36d2c6f",
+    "https://esm.d8d.fun/form-data@4.0.2?target=denonext": "83cf111a2e6f0f7b6c045508635ceae449c5d43d8d22df23693e5b996cc65899",
+    "https://esm.d8d.fun/hono@4.7.4": "174d520e05dd0a3a1405b1435ee8a8cfe08b16ea80a8363879a88b8a6ff14f69",
+    "https://esm.d8d.fun/hono@4.7.4/cors": "7fae6007a9ce0f5e471d3e7a9244648df6060438c74d8da418bf0cea735e17c4",
+    "https://esm.d8d.fun/hono@4.7.4/deno": "8aa4c5e0f121e7cf1f754b905a40b3369315951ca2d83897b076be8752e77c09",
+    "https://esm.d8d.fun/hono@4.7.4/denonext/cors.mjs": "b7d3ae27185d20108a146cb6f6eb3dc2d3f1010a1d5516534609a370d1e14e7e",
+    "https://esm.d8d.fun/hono@4.7.4/denonext/deno.mjs": "55170c10ac727e9b09384d9fc05dc9d1524e89e885cd5b8bb05b2b42906866bb",
+    "https://esm.d8d.fun/hono@4.7.4/denonext/dist/client/utils.mjs": "e334a8ad22f831c6eda17c09ac4bc26bc8cfe16ce2d7bb7727a3519d500ec057",
+    "https://esm.d8d.fun/hono@4.7.4/denonext/dist/compose.mjs": "1196166f399a52036503ee70ed098af79ea34e1268233970859b2ff355bcffd1",
+    "https://esm.d8d.fun/hono@4.7.4/denonext/dist/context.mjs": "6eb4bfb07b2b129ec5df08ccb4ddeae28e39c91cc0a2dae5ce8e4151548d218f",
+    "https://esm.d8d.fun/hono@4.7.4/denonext/dist/hono.mjs": "b2701b3ab66ab91d39b5d521109c4e0cb78d88a71cdbb094ff4a5b8832548e0c",
+    "https://esm.d8d.fun/hono@4.7.4/denonext/dist/jsx/base.mjs": "33d27345ea175980efe2a0d4a7bf12e7c521fe9506ec081f165baf39c55a6311",
+    "https://esm.d8d.fun/hono@4.7.4/denonext/dist/jsx/children.mjs": "8c8ec600c04c1c7d585af870bc417ee1415dec57a83c7cc5b19c7ed779977787",
+    "https://esm.d8d.fun/hono@4.7.4/denonext/dist/jsx/components.mjs": "329a18bfd2823e3925aad171634839a500f88dc45cec74651046009d6ade9a89",
+    "https://esm.d8d.fun/hono@4.7.4/denonext/dist/jsx/constants.mjs": "a5d4f0c5a118dae37998fe58a430354c89ee5eb9206dde0b8bb0f15d3d0a3822",
+    "https://esm.d8d.fun/hono@4.7.4/denonext/dist/jsx/context.mjs": "ea0aeb1b21b339f96151e94418f2bf32fb5e50f320b9d8ab6273927d19e06105",
+    "https://esm.d8d.fun/hono@4.7.4/denonext/dist/jsx/dom/components.mjs": "637aa2d623ae9f5ebc3109e507f8f27ef6c1ae7d4c9bbfb14ecfe8d8ce2151db",
+    "https://esm.d8d.fun/hono@4.7.4/denonext/dist/jsx/dom/context.mjs": "191930e2b2129d28e9a53612d26bf6a99867370396c71d7ecb1caea3d7f7990c",
+    "https://esm.d8d.fun/hono@4.7.4/denonext/dist/jsx/dom/hooks/index.mjs": "fbed9585c8f693edf92a18f2307ce7fafe36955d6899c52720d30b225d8e16a8",
+    "https://esm.d8d.fun/hono@4.7.4/denonext/dist/jsx/dom/render.mjs": "44bd0364afe7b6b46df2a548e06b070777c61fc03252e7d2ae9424a50c9e21b6",
+    "https://esm.d8d.fun/hono@4.7.4/denonext/dist/jsx/hooks/index.mjs": "145266fb802e8fda823514b085e9653c1d0fb07ce6f67a29adbd9d16438fef23",
+    "https://esm.d8d.fun/hono@4.7.4/denonext/dist/jsx/intrinsic-element/common.mjs": "859e3739f0c2637d291a5aaf76f02ab0a5566a1fb693432f95f19235077849b3",
+    "https://esm.d8d.fun/hono@4.7.4/denonext/dist/jsx/utils.mjs": "169e8732e31a70ed1eecf31a7a1cef6c3882ad32581493d2d7812e3e62caef50",
+    "https://esm.d8d.fun/hono@4.7.4/denonext/dist/utils/body.mjs": "9459ea130b33427fd1f67d9dc96900528e6ab747b296836a524c01ad6f2eb806",
+    "https://esm.d8d.fun/hono@4.7.4/denonext/dist/utils/compress.mjs": "c480cdfc4b4236868fda55aba9b012ac91b4bbf237d855da2d40a8b905ee965d",
+    "https://esm.d8d.fun/hono@4.7.4/denonext/dist/utils/constants.mjs": "b68b3ccd4d516d19a31f1b56a4fb65529b033deb9c8ff34d4b2e44e6e259ef72",
+    "https://esm.d8d.fun/hono@4.7.4/denonext/dist/utils/handler.mjs": "bded9286b7e46345840114eb8e2a8e627ee4fffda3044163fd78d1578b82f26c",
+    "https://esm.d8d.fun/hono@4.7.4/denonext/dist/utils/html.mjs": "9949836effcbff234d47fed244e8fa2c3d0a00b89c55767a16c15b7b35e12006",
+    "https://esm.d8d.fun/hono@4.7.4/denonext/dist/utils/mime.mjs": "0f1f71d357f5b2b15dbe026c4d65219a78d3dd922d799673aed798f1ff18e981",
+    "https://esm.d8d.fun/hono@4.7.4/denonext/dist/utils/url.mjs": "aa55b9a667876655d972b7e086ff11c05ae9327ef29f174b87d2ce87791fa0fd",
+    "https://esm.d8d.fun/hono@4.7.4/denonext/hono-base.mjs": "29af9473270ed537bced9d32bc38a858353fe2dba647c1e7ba63dfa98d33838f",
+    "https://esm.d8d.fun/hono@4.7.4/denonext/hono.mjs": "4d667dd609e6792e5447ea24c718ea906617b8d150675895a04ee4ba7e5e1c52",
+    "https://esm.d8d.fun/hono@4.7.4/denonext/html.mjs": "02391a2fc736bf5b27588e34948b63581526effc738be6d05721bb7ac334d51a",
+    "https://esm.d8d.fun/hono@4.7.4/denonext/jsx.mjs": "7e884cd71ecba137dbeda14ba974dd7eecd870240816803883e2a7f6c06f4c2b",
+    "https://esm.d8d.fun/hono@4.7.4/denonext/jsx/dom/jsx-dev-runtime.mjs": "a02348636d71685c20a9186e66e5f455bdb1999d2bc980e4a4f1bc83dcd7484f",
+    "https://esm.d8d.fun/hono@4.7.4/denonext/jsx/dom/jsx-runtime.mjs": "32f78a1287f90a8afdf0c0d316af28e9b892401b19734bcabfd59f4e5fe9218c",
+    "https://esm.d8d.fun/hono@4.7.4/denonext/jsx/jsx-dev-runtime.mjs": "9ce39e3bc5cd0188ed1ef8f4e71ab18560b29a3ea179cd647a404749a6bd0576",
+    "https://esm.d8d.fun/hono@4.7.4/denonext/jsx/jsx-runtime.mjs": "e84ac4b5216e98c72733b1754acd23349be44a28c929f23ee121263fd11317d5",
+    "https://esm.d8d.fun/hono@4.7.4/denonext/jsx/streaming.mjs": "a0745c3b7b37d44be9139b4879e7ccfcaaeb12ec10e7880a5c66829c0f59928e",
+    "https://esm.d8d.fun/hono@4.7.4/denonext/request.mjs": "5e2499f74cad11ede4fd777c22479bfb2161c86e81435f20b205b9d3d02223b4",
+    "https://esm.d8d.fun/hono@4.7.4/denonext/router.mjs": "51c7a9987e81971338d9e342a305a4c2ce231e7ab6d398aa6a0b74f46443f63b",
+    "https://esm.d8d.fun/hono@4.7.4/denonext/router/reg-exp-router.mjs": "56a9c48e0ec3a264170db8de1c0342bec844ace172cab882223fcf809c0ce0f6",
+    "https://esm.d8d.fun/hono@4.7.4/denonext/router/smart-router.mjs": "69dec2229dadfda412d6736719104ed3dceabe667ddff6804a028cb9650b990b",
+    "https://esm.d8d.fun/hono@4.7.4/denonext/router/trie-router.mjs": "13d18e17ea528e9bfb85cec0221a2da76d9c18b9fe44db8caee98cb51b357234",
+    "https://esm.d8d.fun/hono@4.7.4/denonext/serve-static.mjs": "0e233f7b5b78374defa5281a05469d326444a8c7894ac36c1ae99e5b1412619d",
+    "https://esm.d8d.fun/hono@4.7.4/denonext/ssg.mjs": "c9e8cf200027b7c69c8a97d7bf96b64b09ca3b810198958c3f69a3dc18492730",
+    "https://esm.d8d.fun/hono@4.7.4/denonext/ws.mjs": "896fdef949b92bb10c16ebb1b7977babc3c87d3db43e329a66fcd9a786f8c933",
+    "https://esm.d8d.fun/hono@4.7.4/jsx": "8fb76b6158726b27e0e9be1db08b831967bd1bd8175d98f75652440254f2b0cf",
+    "https://esm.d8d.fun/hono@4.7.4/jsx/jsx-runtime": "add6d23136559fd64a92cab007bc2afa9450ad1773e0cd8e8cfdbf0c97d339d3",
+    "https://esm.d8d.fun/isexe@3.1.1/denonext/isexe.mjs": "59b58a950d33368749f8b2f0df8377ded09f5f30c276f79239a2542029e77a43",
+    "https://esm.d8d.fun/isexe@3.1.1/denonext/posix.mjs": "f99f8d2aacd0b5424625cee480f36b47639cfbad44c64b7b21cbba18ad77a1b2",
+    "https://esm.d8d.fun/isexe@3.1.1/denonext/win32.mjs": "f52981ee6555549c246db8e9e6c0ee1e2947a35367c3bcec0ba31834387991c0",
+    "https://esm.d8d.fun/isexe@3.1.1?target=denonext": "b3c61e7e70b9d56865de461fbcdae702ebf93743143457079a15a60e30dfcf83",
+    "https://esm.d8d.fun/jsonwebtoken@9.0.2/denonext/jsonwebtoken.mjs": "4365e66f5fa04a6903d106e17166fd93ddef582caf66f19d281dc066711c4f6e",
+    "https://esm.d8d.fun/jsonwebtoken@9.0.2?target=denonext": "76a17587b04be9dfee91197bb0119b0be3a6a53ec05cc63d44292077bc03c0f7",
+    "https://esm.d8d.fun/jwa@1.4.1/denonext/jwa.mjs": "fb2de82e653ed6ed5432adb25567772ea9044f241d209c569b23eee07ab50176",
+    "https://esm.d8d.fun/jwa@1.4.1?target=denonext": "10b18f9707bf3a08b740be74a3bf349ad0e483cf3c5bfa305a023a8c5cb0056e",
+    "https://esm.d8d.fun/jws@3.2.2/denonext/jws.mjs": "ed1560f6cd91af6f57bdf5132ffc6a126619d003c5d5e7680fb0b310dbf61ad8",
+    "https://esm.d8d.fun/jws@3.2.2?target=denonext": "e5b49eed4c39c26f02dafda1f75776a1ed468df4b36f8eb0f5a36e036a7af0f2",
+    "https://esm.d8d.fun/lodash.includes@4.3.0/denonext/lodash.includes.mjs": "7891d8e1db31f196dad13cfea5d8c9cd7b57e556e852bde44a32b9c95f52f769",
+    "https://esm.d8d.fun/lodash.includes@4.3.0?target=denonext": "52552ec9e8b0ee8dff8bd6743edb4ff57c1c1dc31fe13061c1fd6b023e26c0dd",
+    "https://esm.d8d.fun/lodash.isboolean@3.0.3/denonext/lodash.isboolean.mjs": "62346e63e84456d408ad9cdd38dc122ea4fecef67b7682c9ef311a9d9376ebb4",
+    "https://esm.d8d.fun/lodash.isboolean@3.0.3?target=denonext": "df362bed13151ed14dc4fea8a56582f7d5165ab057225f20f7a19933bc030603",
+    "https://esm.d8d.fun/lodash.isinteger@4.0.4/denonext/lodash.isinteger.mjs": "661e55e1960486dc985a2dd833c942f79874d2c3d82d89b62d92a7c2d4fb1976",
+    "https://esm.d8d.fun/lodash.isinteger@4.0.4?target=denonext": "1355063df9b6e29a874a0f22874b86abcb7520182efcd5e543ded4209ff4485c",
+    "https://esm.d8d.fun/lodash.isnumber@3.0.3/denonext/lodash.isnumber.mjs": "406375117efebf18a311f7b9a9cde5b961f2b96589c6e70e7805ae8f9b233186",
+    "https://esm.d8d.fun/lodash.isnumber@3.0.3?target=denonext": "129cd84408b3f202f339cfbde411d7e3573414128574123b94375b330cfd02b4",
+    "https://esm.d8d.fun/lodash.isplainobject@4.0.6/denonext/lodash.isplainobject.mjs": "52baba5dc85a16aa38f54fcbbbf47ffe8b3c9d84e788a5199a20eab9135dd35a",
+    "https://esm.d8d.fun/lodash.isplainobject@4.0.6?target=denonext": "aaf731d53ff27671d17a5e700371eb7d073496e7c099a537c05dd1df6606a54e",
+    "https://esm.d8d.fun/lodash.isstring@4.0.1/denonext/lodash.isstring.mjs": "27f803808b3f189fa994d1b3784e13b2978976bf17c06050ee311e7f54b0db32",
+    "https://esm.d8d.fun/lodash.isstring@4.0.1?target=denonext": "dc33fab7958cad340a76cc58bd2f30ad22ee63b00eac0bc2d9a4110049cdfddc",
+    "https://esm.d8d.fun/lodash.once@4.1.1/denonext/lodash.once.mjs": "d98a09cbf4f8520f256be230ba50e9cae609b73bc8ffd2f6bf388099ea2f50b1",
+    "https://esm.d8d.fun/lodash.once@4.1.1?target=denonext": "46682cfe5d75f572d14c6a3c4c5da23d597d80d0552fea124b72a2359bb4b37d",
+    "https://esm.d8d.fun/mime-db@1.52.0/denonext/mime-db.mjs": "f93feb3d7150014b71bd0d06c5bd819db56a089b31b8b79a3b0466bb37ef005e",
+    "https://esm.d8d.fun/mime-types@2.1.35/denonext/mime-types.mjs": "704bdb318816fe1360c90a196f7cb3ba6e25fe207707cc2df873f890ad2e5f44",
+    "https://esm.d8d.fun/mime-types@2.1.35?target=denonext": "e4cc9a1aabecc1be22d194375ec3b99cc9d51700cc4629ab689975451c0a8ce5",
+    "https://esm.d8d.fun/ms@2.1.3/denonext/ms.mjs": "9039464da1f4ae1c2042742d335c82556c048bbe49449b5d0cd5198193afa147",
+    "https://esm.d8d.fun/ms@2.1.3?target=denonext": "36f5aa7503ff0ff44ce9e3155a60362d8d3ae5db8db048be5764a3a515b6a263",
+    "https://esm.d8d.fun/nanoid@5.1.5/denonext/nanoid.mjs": "dc919f2d7339a244f732a0cf02e3962dd1289535668026f52fb26bd593e9358b",
+    "https://esm.d8d.fun/nanoid@5.1.5?target=denonext": "33ad5b17f1290cb850164cfcf30f642d9dad489ba19909bc3cfd9eb78369f451",
+    "https://esm.d8d.fun/node-gyp-build@4.8.4/denonext/node-gyp-build.mjs": "9a86f2d044fc77bd60aaa3d697c2ba1b818da5fb1b9aaeedec59a40b8e908803",
+    "https://esm.d8d.fun/node-gyp-build@4.8.4?target=denonext": "261a6cedf1fdbf159798141ba1e2311ac1510682c5c8b55dacc8cf5fdee4aa06",
+    "https://esm.d8d.fun/proxy-from-env@1.1.0/denonext/proxy-from-env.mjs": "f60f9c79fc3baa07c13c800798d645ae70d1b2059b8d593dcd4f8c5710b50333",
+    "https://esm.d8d.fun/proxy-from-env@1.1.0?target=denonext": "bf02a050a1a6aa56ddba25dbea2c355da294630e5c5520fddea4b2f30a9292bc",
+    "https://esm.d8d.fun/safe-buffer@5.2.1/denonext/safe-buffer.mjs": "51b088d69d0bbf6d7ce4179853887e105715df40e432a3bff0e9575cc2285276",
+    "https://esm.d8d.fun/safe-buffer@5.2.1?target=denonext": "34028b9647c849fa96dfd3d9f217a3adca8b43b13409820ac3f43fb15eba3e20",
+    "https://esm.d8d.fun/semver@7.7.1/denonext/semver.mjs": "f1d8c45a097d5f2da9662be5ff2087f1e4af9ebeb9b0c2eeeb0c90d74fa7a14c",
+    "https://esm.d8d.fun/semver@7.7.1?target=denonext": "7d6e1f9de61981f17d0e5153d48b77475e3433225ce9265ad77206afe216c5c8",
+    "https://esm.d8d.fun/socket.io-client@4.8.1/denonext/socket.io-client.mjs": "b902dafad93171849d6d6e9e98bfa5357513089e43b0fbf9268d394f0839f372",
+    "https://esm.d8d.fun/socket.io-client@4.8.1?target=denonext": "f5543108c5018ca5904af75985dc9ff7b7210334782408cf87bdf091ce1fbf2e",
+    "https://esm.d8d.fun/socket.io-parser@4.2.4/denonext/socket.io-parser.mjs": "a989568a92fa45870a4ae74fb731c5e554ef6c901b97f154d8c84267f7d5aaba",
+    "https://esm.d8d.fun/socket.io-parser@4.2.4?target=denonext": "95bc48ccd83940940fb68cf3401280667a8bad2b6abc8a4c7bb5c39ec59aff16",
+    "https://esm.d8d.fun/supports-color@10.0.0/denonext/supports-color.mjs": "239cd39d0828e1a018dee102748da869b1b75c38fe6a9c0c8f0bd4ffbd3e1ea1",
+    "https://esm.d8d.fun/supports-color@10.0.0?target=denonext": "4895255248e4ba0cbcce9437003dccf3658b1ac1d1e8eba5225fb8194c454ee1",
+    "https://esm.d8d.fun/utf-8-validate@6.0.5/denonext/utf-8-validate.mjs": "90c0c88a13bc4749b497361480d618bf4809153f5d5ba694fac79ae9dbf634a9",
+    "https://esm.d8d.fun/utf-8-validate@6.0.5?target=denonext": "071bc33ba1a58297e23a34d69dd589fd06df04b0f373b382ff5da544a623f271",
+    "https://esm.d8d.fun/which@4.0.0/denonext/which.mjs": "9f47207c6dc9684fe3d852f2290c474577babaeabf60616652630c0b90421a53",
+    "https://esm.d8d.fun/which@4.0.0?target=denonext": "50b06c1a68e3ef88dc8e2c68c17b732a6d1917000d5d59637496da3b61549c8e",
+    "https://esm.d8d.fun/ws@8.17.1/denonext/ws.mjs": "7b349f9bcf5af35a422b01ece5189ac693f84f07cc2e9be12023ec818a18ba71",
+    "https://esm.d8d.fun/ws@8.17.1?target=denonext": "3c5e4dca1be73c0e7776cb033809d16c2421b785cd1b93827b76a43c5b59a0bd",
+    "https://esm.d8d.fun/xmlhttprequest-ssl@2.1.2/denonext/xmlhttprequest-ssl.mjs": "5cb537aeb44e2971f9d84c4e22e0d24ea0554eb6c33a5d10a46cf163debf60ec",
+    "https://esm.d8d.fun/xmlhttprequest-ssl@2.1.2?target=denonext": "5a4574293c501f0f0da3ddd653bd5d9ac00ea59647e3b20694cc05ed02e7a22f"
+  }
+}

+ 408 - 0
migrations.ts

@@ -0,0 +1,408 @@
+import type { MigrationLiveDefinition } from '@d8d-appcontainer/types'
+
+import {
+  DeviceCategory, DeviceStatus, AlertLevel, AlertStatus, EnableStatus, DeleteStatus,
+  HandleType, ProblemType, NotifyType, ServerType, AssetTransferType, AuditStatus,
+  DeviceProtocolType, MetricType, ThemeMode, FontSize, CompactMode,
+
+  AssetStatus, NetworkStatus, PacketLossStatus,
+  ZichanArea, ZichanCategory, DeviceType, DeviceInstance, RackInfo, RackServerType, RackServer, ZichanTransLog, DeviceAlertRule,
+  ZichanInfo, AlertNotifyConfig, KnowInfo,
+  SystemSettingKey,
+  SystemSettingGroup,
+  ALLOWED_FILE_TYPES,
+} from './asset/share/types.ts';
+
+// 定义用户表迁移
+const createUsersTable: MigrationLiveDefinition = {
+  name: "create_users_table",
+  up: async (api) => {
+    await api.schema.createTable('users', (table) => {
+      table.increments('id').primary();
+      table.string('username').unique().notNullable();
+      table.string('password').notNullable();
+      table.string('phone').unique();
+      table.string('email').unique();
+      table.string('nickname');
+      table.string('name');
+      table.integer('is_disabled').defaultTo(0);
+      table.integer('is_deleted').defaultTo(0);
+      table.timestamps(true, true);
+      
+      // 添加索引
+      table.index('username');
+      table.index('is_disabled');
+      table.index('is_deleted');
+    });
+  },
+  down: async (api) => {
+    await api.schema.dropTable('users');
+  }
+}
+
+// 定义登录历史表迁移
+const createLoginHistoryTable: MigrationLiveDefinition = {
+  name: "create_login_history_table",
+  up: async (api) => {
+    await api.schema.createTable('login_history', (table) => {
+      table.increments('id').primary()
+      table.integer('user_id').unsigned().references('id').inTable('users').onDelete('CASCADE')
+      table.timestamp('login_time').defaultTo(api.fn.now())
+      table.string('ip_address')
+      table.text('user_agent')
+      table.decimal('longitude', 10, 6).nullable()  // 经度
+      table.decimal('latitude', 10, 6).nullable()   // 纬度
+      table.string('location_name').nullable()      // 地点名称
+      
+      // 添加索引
+      table.index('user_id');
+      table.index('login_time');
+      // table.index(['longitude', 'latitude']);
+    })
+  },
+  down: async (api) => {
+    await api.schema.dropTable('login_history')
+  }
+}
+
+// 定义知识库文章表迁移
+const createKnowInfoTable: MigrationLiveDefinition = {
+  name: "create_know_info_table",
+  up: async (api) => {
+    await api.schema.createTable('know_info', (table) => {
+      table.increments('id').primary();
+      table.string('title').comment('文章标题');
+      table.string('tags').comment('文章标签');
+      table.text('content').comment('文章内容');
+      table.string('author').comment('作者');
+      table.string('category').comment('分类');
+      table.string('cover_url').comment('封面图片URL');
+      table.integer('audit_status').defaultTo(AuditStatus.PENDING).comment('审核状态');
+      table.integer('is_deleted').defaultTo(0).comment('是否被删除 (0否 1是)');
+      table.timestamps(true, true);
+      
+      // 添加索引
+      table.index('title');
+      table.index('tags');
+      table.index('author');
+      table.index('category');
+      table.index('audit_status');
+      table.index('is_deleted');
+    });
+  },
+  down: async (api) => {
+    await api.schema.dropTable('know_info');
+  }
+};
+
+// 定义文件分类表迁移
+const createFileCategoryTable: MigrationLiveDefinition = {
+  name: "create_file_category_table",
+  up: async (api) => {
+    await api.schema.createTable('file_categories', (table) => {
+      table.increments('id').primary();
+      table.string('name').notNullable().comment('分类名称');
+      table.string('code').notNullable().unique().comment('分类编码');
+      table.text('description').comment('分类描述');
+      table.integer('is_deleted').defaultTo(DeleteStatus.NOT_DELETED).comment('是否被删除 (0否 1是)');
+      table.timestamps(true, true);
+      
+      // 添加索引
+      table.index('name');
+      table.index('code');
+      table.index('is_deleted');
+    });
+  },
+  down: async (api) => {
+    await api.schema.dropTable('file_categories');
+  }
+};
+
+// 定义文件库表迁移
+const createFileLibraryTable: MigrationLiveDefinition = {
+  name: "create_file_library_table",
+  up: async (api) => {
+    await api.schema.createTable('file_library', (table) => {
+      table.increments('id').primary();
+      table.string('file_name').notNullable().comment('文件名称');
+      table.string('original_filename').comment('原始文件名');
+      table.string('file_path').notNullable().comment('文件路径');
+      table.string('file_type').comment('文件类型');
+      table.integer('file_size').unsigned().comment('文件大小(字节)');
+      table.integer('uploader_id').unsigned().references('id').inTable('users').onDelete('SET NULL').comment('上传用户ID');
+      table.string('uploader_name').comment('上传者名称');
+      table.integer('category_id').unsigned().references('id').inTable('file_categories').onDelete('SET NULL').comment('文件分类');
+      table.string('tags').comment('文件标签');
+      table.text('description').comment('文件描述');
+      table.integer('download_count').defaultTo(0).comment('下载次数');
+      table.integer('is_disabled').defaultTo(EnableStatus.DISABLED).comment('是否禁用 (0否 1是)');
+      table.integer('is_deleted').defaultTo(DeleteStatus.NOT_DELETED).comment('是否被删除 (0否 1是)');
+      table.timestamps(true, true);
+      
+      // 添加索引
+      table.index('file_name');
+      table.index('file_type');
+      table.index('category_id');
+      table.index('uploader_id');
+      table.index('is_deleted');
+    });
+  },
+  down: async (api) => {
+    await api.schema.dropTable('file_library');
+  }
+};
+
+// 定义主题设置表迁移
+const createThemeSettingsTable: MigrationLiveDefinition = {
+  name: "create_theme_settings_table",
+  up: async (api) => {
+    await api.schema.createTable('theme_settings', (table) => {
+      table.increments('id').primary();
+      table.integer('user_id').unsigned().references('id').inTable('users').onDelete('CASCADE');
+      table.jsonb('settings').comment('主题设置');
+      table.timestamps(true, true);
+      
+      // 添加索引
+      table.index('user_id');
+    });
+  },
+  down: async (api) => {
+    await api.schema.dropTable('theme_settings');
+  }
+};
+
+// 定义系统设置表迁移
+const createSystemSettingsTable: MigrationLiveDefinition = {
+  name: "create_system_settings_table",
+  up: async (api) => {
+    await api.schema.createTable('system_settings', (table) => {
+      table.increments('id').primary();
+      table.string('key').notNullable().unique().comment('设置键');
+      table.text('value').notNullable().comment('设置值');
+      table.string('description').nullable().comment('设置描述');
+      table.string('group').notNullable().comment('设置分组');
+      table.timestamps(true, true);
+      
+      // 添加索引
+      table.index('key');
+      table.index('group');
+    });
+  },
+  down: async (api) => {
+    await api.schema.dropTable('system_settings');
+  }
+};
+
+// 初始测试数据迁移
+const seedInitialData: MigrationLiveDefinition = {
+  name: "seed_initial_data",
+  up: async (api) => {
+    // 1. 添加默认用户
+    const defaultUser = {
+      username: 'admin',
+      password: 'admin123', // 实际应用中应使用加密后的密码
+      email: 'admin@example.com',
+      nickname: '系统管理员',
+      name: '管理员',
+      is_disabled: EnableStatus.ENABLED,
+      is_deleted: DeleteStatus.NOT_DELETED
+    };
+    
+    const [userId] = await api.table('users').insert(defaultUser);
+    
+    // 2. 添加默认主题设置
+    await api.table('theme_settings').insert({
+      user_id: userId,
+      settings: {
+        theme_mode: ThemeMode.LIGHT,
+        primary_color: '#1890ff',
+        font_size: FontSize.MEDIUM,
+        is_compact: CompactMode.NORMAL
+      },
+      created_at: api.fn.now(),
+      updated_at: api.fn.now()
+    });
+    
+    // 3. 添加知识库文章示例
+    await api.table('know_info').insert([
+      {
+        title: '欢迎使用应用Starter',
+        tags: 'starter,指南',
+        content: '# 欢迎使用应用Starter\n\n这是一个基础的应用Starter,提供了用户认证、文件管理、知识库、主题管理等功能。\n\n## 主要功能\n\n- 用户认证与管理\n- 文件上传与管理\n- 知识库文章管理\n- 主题设置(暗黑模式/明亮模式)\n- 图表数据统计\n- 地图集成\n\n更多功能请参考文档...',
+        author: '系统管理员',
+        category: '使用指南',
+        audit_status: AuditStatus.APPROVED,
+        is_deleted: DeleteStatus.NOT_DELETED
+      },
+      {
+        title: '如何使用文件管理',
+        tags: '文件,上传,管理',
+        content: '# 文件管理使用指南\n\n文件管理模块可以帮助您上传、分类和管理各种文件。\n\n## 上传文件\n\n1. 点击"上传文件"按钮\n2. 选择要上传的文件\n3. 填写文件信息(分类、标签等)\n4. 点击"确定"完成上传\n\n## 文件分类\n\n您可以创建自定义的文件分类,方便管理不同类型的文件...',
+        author: '系统管理员',
+        category: '使用指南',
+        audit_status: AuditStatus.APPROVED,
+        is_deleted: DeleteStatus.NOT_DELETED
+      },
+      {
+        title: '主题设置指南',
+        tags: '主题,设置,外观',
+        content: '# 主题设置指南\n\n主题设置允许您自定义应用的外观和感觉,包括颜色模式、字体大小等。\n\n## 颜色模式\n\n您可以选择明亮模式或暗黑模式,适应不同的工作环境和个人偏好。\n\n## 主题颜色\n\n可以选择主题的主色调,系统会根据选择自动生成配色方案。\n\n## 字体大小\n\n提供小、中、大三种字体大小选项,满足不同用户的阅读需求。',
+        author: '系统管理员',
+        category: '使用指南',
+        audit_status: AuditStatus.APPROVED,
+        is_deleted: DeleteStatus.NOT_DELETED
+      },
+      {
+        title: '数据分析功能介绍',
+        tags: '分析,图表,数据',
+        content: '# 数据分析功能介绍\n\n数据分析模块提供了多种图表和可视化工具,帮助您理解和分析数据。\n\n## 图表类型\n\n支持柱状图、折线图、饼图等多种图表类型,适用于不同的数据展示需求。\n\n## 数据筛选\n\n可以根据时间范围、数据类型等条件筛选数据,获得更精确的分析结果。',
+        author: '系统管理员',
+        category: '使用指南',
+        audit_status: AuditStatus.APPROVED,
+        is_deleted: DeleteStatus.NOT_DELETED
+      }
+    ]);
+    
+    // 4. 添加文件分类示例
+    await api.table('file_categories').insert([
+      {
+        name: '文档',
+        code: 'doc',
+        description: '各类文档文件',
+        is_deleted: DeleteStatus.NOT_DELETED
+      },
+      {
+        name: '图片',
+        code: 'image',
+        description: '各类图片文件',
+        is_deleted: DeleteStatus.NOT_DELETED
+      },
+      {
+        name: '视频',
+        code: 'video',
+        description: '各类视频文件',
+        is_deleted: DeleteStatus.NOT_DELETED
+      },
+      {
+        name: '音频',
+        code: 'audio',
+        description: '各类音频文件',
+        is_deleted: DeleteStatus.NOT_DELETED
+      },
+      {
+        name: '其他',
+        code: 'other',
+        description: '其他类型文件',
+        is_deleted: DeleteStatus.NOT_DELETED
+      }
+    ]);
+
+    // 5. 添加系统设置示例
+    await api.table('system_settings').insert([
+      {
+        key: SystemSettingKey.SITE_NAME,
+        value: '应用管理系统',
+        description: '站点名称',
+        group: SystemSettingGroup.BASIC
+      },
+      {
+        key: SystemSettingKey.SITE_DESCRIPTION,
+        value: '一个功能完善的应用管理系统',
+        description: '站点描述',
+        group: SystemSettingGroup.BASIC
+      },
+      {
+        key: SystemSettingKey.SITE_KEYWORDS,
+        value: '应用,管理,系统',
+        description: '站点关键词',
+        group: SystemSettingGroup.BASIC
+      },
+      {
+        key: SystemSettingKey.ENABLE_REGISTER,
+        value: true,
+        description: '是否开启注册功能',
+        group: SystemSettingGroup.FEATURE
+      },
+      {
+        key: SystemSettingKey.ENABLE_CAPTCHA,
+        value: true,
+        description: '是否开启验证码',
+        group: SystemSettingGroup.FEATURE
+      },
+      {
+        key: SystemSettingKey.LOGIN_ATTEMPTS,
+        value: 5,
+        description: '允许的登录尝试次数',
+        group: SystemSettingGroup.FEATURE
+      },
+      {
+        key: SystemSettingKey.SESSION_TIMEOUT,
+        value: 120,
+        description: '会话超时时间(分钟)',
+        group: SystemSettingGroup.FEATURE
+      },
+      {
+        key: SystemSettingKey.UPLOAD_MAX_SIZE,
+        value: 10,
+        description: '最大上传大小(MB)',
+        group: SystemSettingGroup.UPLOAD
+      },
+      {
+        key: SystemSettingKey.ALLOWED_FILE_TYPES,
+        value: ALLOWED_FILE_TYPES,
+        description: '允许上传的文件类型',
+        group: SystemSettingGroup.UPLOAD
+      },
+      {
+        key: SystemSettingKey.IMAGE_COMPRESS,
+        value: true,
+        description: '是否压缩图片',
+        group: SystemSettingGroup.UPLOAD
+      },
+      {
+        key: SystemSettingKey.IMAGE_MAX_WIDTH,
+        value: 1920,
+        description: '图片最大宽度',
+        group: SystemSettingGroup.UPLOAD
+      },
+      {
+        key: SystemSettingKey.NOTIFY_ON_LOGIN,
+        value: true,
+        description: '是否开启登录通知',
+        group: SystemSettingGroup.NOTIFICATION
+      },
+      {
+        key: SystemSettingKey.NOTIFY_ON_UPLOAD,
+        value: true,
+        description: '是否开启上传通知',
+        group: SystemSettingGroup.NOTIFICATION
+      },
+      {
+        key: SystemSettingKey.NOTIFY_ON_ERROR,
+        value: true,
+        description: '是否开启错误通知',
+        group: SystemSettingGroup.NOTIFICATION
+      }
+    ]);
+  },
+  down: async (api) => {
+    // 删除初始数据
+    await api.table('login_history').where('user_id', 1).delete();
+    await api.table('theme_settings').where('user_id', 1).delete();
+    await api.table('know_info').delete();
+    await api.table('file_categories').delete();
+    await api.table('users').where('username', 'admin').delete();
+  }
+};
+
+// 导出所有迁移
+export const migrations = [
+  createUsersTable,
+  createLoginHistoryTable,
+  createKnowInfoTable,
+  createFileCategoryTable,
+  createFileLibraryTable,
+  createThemeSettingsTable,
+  createSystemSettingsTable,
+  seedInitialData
+];

+ 103 - 0
routes_auth.ts

@@ -0,0 +1,103 @@
+import { Hono } from 'hono'
+import type { Variables } from './app.tsx'
+import type { WithAuth } from './app.tsx'
+
+export function createAuthRoutes(withAuth: WithAuth) {
+  const authRoutes = new Hono<{ Variables: Variables }>()
+  
+  // 登录状态检查
+  authRoutes.get('/status', async (c) => {
+    try {
+      const auth = c.get('auth')
+      const token = c.req.header('Authorization')?.replace('Bearer ', '')
+      
+      if (!token) {
+        return c.json({ isValid: false }, 200)
+      }
+      
+      const status = await auth.checkLoginStatus(token)
+      return c.json(status)
+    } catch (error) {
+      console.error('登录状态检查失败:', error)
+      return c.json({ isValid: false, error: '登录状态检查失败' }, 500)
+    }
+  })
+  
+  // 注册
+  authRoutes.post('/register', async (c) => {
+    try {
+      const auth = c.get('auth')
+      const { username, email, password } = await c.req.json()
+      
+      if (!username || !password) {
+        return c.json({ error: '用户名和密码不能为空' }, 400)
+      }
+      
+      try {
+        await auth.createUser({ username, password, email })
+        const result = await auth.authenticate(username, password)
+        
+        return c.json({
+          message: '注册成功',
+          user: result.user
+        }, 201)
+      } catch (authError) {
+        return c.json({ error: '用户已存在或注册失败' }, 400)
+      }
+    } catch (error) {
+      console.error('注册失败:', error)
+      return c.json({ error: '注册失败' }, 500)
+    }
+  })
+  
+  // 登录
+  authRoutes.post('/login', async (c) => {
+    try {
+      const auth = c.get('auth')
+      const { username, password } = await c.req.json()
+      
+      if (!username || !password) {
+        return c.json({ error: '用户名和密码不能为空' }, 400)
+      }
+      
+      try {
+        const result = await auth.authenticate(username, password)
+        
+        if (result.user) {
+          const apiClient = c.get('apiClient')
+          await apiClient.database.insert('login_history', {
+            user_id: result.user.id,
+            login_time: apiClient.database.fn.now(),
+            ip_address: c.req.header('x-forwarded-for') || '未知',
+            user_agent: c.req.header('user-agent') || '未知'
+          })
+        }
+        
+        return c.json({
+          message: '登录成功',
+          token: result.token,
+          refreshToken: result.refreshToken,
+          user: result.user
+        })
+      } catch (authError) {
+        return c.json({ error: '用户名或密码错误' }, 401)
+      }
+    } catch (error) {
+      console.error('登录失败:', error)
+      return c.json({ error: '登录失败' }, 500)
+    }
+  })
+  
+  // 获取当前用户信息
+  authRoutes.get('/me', withAuth, (c) => {
+    const user = c.get('user')
+    return c.json(user)
+  })
+  
+  // 登出
+  authRoutes.post('/logout', async (c) => {
+    return c.json({ message: '登出成功' })
+  })
+
+  return authRoutes
+} 

+ 191 - 0
routes_charts.ts

@@ -0,0 +1,191 @@
+import { Hono } from "hono";
+import debug from "debug";
+import type {
+  FileLibrary,
+  FileCategory,
+  KnowInfo,
+  ThemeSettings,
+} from "./asset/share/types.ts";
+
+import {
+  EnableStatus,
+  DeleteStatus,
+  ThemeMode,
+  FontSize,
+  CompactMode,
+} from "./asset/share/types.ts";
+
+import type { Variables, WithAuth } from "./app.tsx";
+
+const log = {
+  api: debug("api:sys"),
+};
+
+
+// 创建图表数据路由
+export function createChartRoutes(withAuth: WithAuth) {
+  const chartRoutes = new Hono<{ Variables: Variables }>();
+
+  // 获取用户活跃度图表数据
+  chartRoutes.get("/user-activity", withAuth, async (c) => {
+    try {
+      const apiClient = c.get('apiClient');
+      
+      // 获取过去30天的数据
+      const days = 30;
+      const result = [];
+      
+      // 当前日期
+      const currentDate = new Date();
+      
+      // 生成过去30天的日期范围
+      for (let i = days - 1; i >= 0; i--) {
+        const date = new Date();
+        date.setDate(currentDate.getDate() - i);
+        
+        // 格式化日期为 YYYY-MM-DD
+        const formattedDate = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`;
+        
+        // 查询当天的登录次数
+        const loginCount = await apiClient.database
+          .table('login_history')
+          .whereRaw(`DATE(login_time) = ?`, [formattedDate])
+          .count();
+          
+        result.push({
+          date: formattedDate,
+          count: Number(loginCount),
+        });
+      }
+      
+      return c.json({
+        message: "获取用户活跃度数据成功",
+        data: result,
+      });
+    } catch (error) {
+      log.api("获取用户活跃度数据失败:", error);
+      return c.json({ error: "获取用户活跃度数据失败" }, 500);
+    }
+  });
+  
+  // 获取文件上传统计图表数据
+  chartRoutes.get("/file-uploads", withAuth, async (c) => {
+    try {
+      const apiClient = c.get('apiClient');
+      
+      // 获取过去12个月的数据
+      const months = 12;
+      const result = [];
+      
+      // 当前日期
+      const currentDate = new Date();
+      
+      // 生成过去12个月的月份范围
+      for (let i = months - 1; i >= 0; i--) {
+        const date = new Date();
+        date.setMonth(currentDate.getMonth() - i);
+        
+        // 获取年月
+        const year = date.getFullYear();
+        const month = date.getMonth() + 1;
+        
+        // 月份标签
+        const monthLabel = `${year}-${String(month).padStart(2, '0')}`;
+        
+        // 查询当月的文件上传数量
+        const fileCount = await apiClient.database
+          .table('file_library')
+          .whereRaw(`YEAR(created_at) = ? AND MONTH(created_at) = ?`, [year, month])
+          .count();
+          
+        result.push({
+          month: monthLabel,
+          count: Number(fileCount),
+        });
+      }
+      
+      return c.json({
+        message: "获取文件上传统计数据成功",
+        data: result,
+      });
+    } catch (error) {
+      log.api("获取文件上传统计数据失败:", error);
+      return c.json({ error: "获取文件上传统计数据失败" }, 500);
+    }
+  });
+  
+  // 获取文件类型分布图表数据
+  chartRoutes.get("/file-types", withAuth, async (c) => {
+    try {
+      const apiClient = c.get('apiClient');
+      
+      // 查询不同文件类型的数量
+      const fileTypeStats = await apiClient.database
+        .table('file_library')
+        .select('file_type',apiClient.database.raw('count(id) as count'))
+        .where('is_deleted', DeleteStatus.NOT_DELETED)
+        .groupBy('file_type');
+        
+      // 将结果转换为饼图所需格式
+      const result = fileTypeStats.map(item => ({
+        type: item.file_type || '未知',
+        value: Number(item.count),
+      }));
+      
+      return c.json({
+        message: "获取文件类型分布数据成功",
+        data: result,
+      });
+    } catch (error) {
+      log.api("获取文件类型分布数据失败:", error);
+      return c.json({ error: "获取文件类型分布数据失败" }, 500);
+    }
+  });
+  
+  // 获取仪表盘概览数据
+  chartRoutes.get("/dashboard-overview", withAuth, async (c) => {
+    try {
+      const apiClient = c.get('apiClient');
+      
+      // 获取用户总数
+      const userCount = await apiClient.database
+        .table('users')
+        .where('is_deleted', DeleteStatus.NOT_DELETED)
+        .count();
+        
+      // 获取文件总数
+      const fileCount = await apiClient.database
+        .table('file_library')
+        .where('is_deleted', DeleteStatus.NOT_DELETED)
+        .count();
+        
+      // 获取知识库文章总数
+      const articleCount = await apiClient.database
+        .table('know_info')
+        .where('is_deleted', DeleteStatus.NOT_DELETED)
+        .count();
+        
+      // 获取今日登录次数
+      const today = new Date().toISOString().split('T')[0];
+      const todayLoginCount = await apiClient.database
+        .table('login_history')
+        .whereRaw(`DATE(login_time) = ?`, [today])
+        .count();
+        
+      return c.json({
+        message: "获取仪表盘概览数据成功",
+        data: {
+          userCount: Number(userCount),
+          fileCount: Number(fileCount),
+          articleCount: Number(articleCount),
+          todayLoginCount: Number(todayLoginCount),
+        },
+      });
+    } catch (error) {
+      log.api("获取仪表盘概览数据失败:", error);
+      return c.json({ error: "获取仪表盘概览数据失败" }, 500);
+    }
+  });
+
+  return chartRoutes;
+}

+ 175 - 0
routes_maps.ts

@@ -0,0 +1,175 @@
+import { Hono } from "hono";
+import debug from "debug";
+import type {
+  FileLibrary,
+  FileCategory,
+  KnowInfo,
+  ThemeSettings,
+} from "./asset/share/types.ts";
+
+import {
+  EnableStatus,
+  DeleteStatus,
+  ThemeMode,
+  FontSize,
+  CompactMode,
+} from "./asset/share/types.ts";
+
+import type { Variables, WithAuth } from "./app.tsx";
+
+const log = {
+  api: debug("api:sys"),
+};
+
+// 创建地图数据路由
+export function createMapRoutes(withAuth: WithAuth) {
+  const mapRoutes = new Hono<{ Variables: Variables }>();
+
+  // 获取地图标记点数据
+  mapRoutes.get("/markers", withAuth, async (c) => {
+    try {
+      const apiClient = c.get('apiClient');
+      
+      // 从登录历史表中查询有经纬度的登录记录
+      const locations = await apiClient.database
+        .table('login_history')
+        .select(
+          'id',
+          'user_id',
+          'location_name',
+          'longitude',
+          'latitude',
+          'login_time',
+          'ip_address'
+        )
+        .whereNotNull('longitude')
+        .whereNotNull('latitude')
+        .orderBy('login_time', 'desc')
+        .limit(100); // 限制返回最近100条记录
+        
+      // 获取相关用户信息
+      const userIds = [...new Set(locations.map(loc => loc.user_id))];
+      const users = await apiClient.database
+        .table('users')
+        .select('id', 'username', 'nickname')
+        .whereIn('id', userIds);
+        
+      // 构建用户信息映射
+      const userMap = new Map(users.map(user => [user.id, user]));
+        
+      // 转换为地图标记点数据格式
+      const markers = locations.map(location => ({
+        id: location.id,
+        name: location.location_name || '未知地点',
+        longitude: location.longitude,
+        latitude: location.latitude,
+        loginTime: location.login_time,
+        ipAddress: location.ip_address,
+        user: userMap.get(location.user_id)
+      }));
+      
+      return c.json({
+        message: "获取登录位置数据成功",
+        data: markers,
+      });
+    } catch (error) {
+      log.api("获取登录位置数据失败:", error);
+      return c.json({ error: "获取登录位置数据失败" }, 500);
+    }
+  });
+  
+  // 获取登录位置详情数据
+  mapRoutes.get("/location/:id", withAuth, async (c) => {
+    try {
+      const id = Number(c.req.param("id"));
+      
+      if (!id || isNaN(id)) {
+        return c.json({ error: "无效的登录记录ID" }, 400);
+      }
+      
+      const apiClient = c.get('apiClient');
+      
+      // 查询登录记录详情
+      const location = await apiClient.database
+        .table('login_history')
+        .where('id', id)
+        .first();
+        
+      if (!location) {
+        return c.json({ error: "登录记录不存在" }, 404);
+      }
+      
+      // 获取用户信息
+      const [user] = await apiClient.database
+        .table('users')
+        .select('id', 'username', 'nickname')
+        .where('id', location.user_id);
+        
+      return c.json({
+        message: "获取登录位置详情成功",
+        data: {
+          ...location,
+          user
+        },
+      });
+    } catch (error) {
+      log.api("获取登录位置详情失败:", error);
+      return c.json({ error: "获取登录位置详情失败" }, 500);
+    }
+  });
+  
+  // 更新登录位置信息
+  mapRoutes.put("/location/:id", withAuth, async (c) => {
+    try {
+      const id = Number(c.req.param("id"));
+      
+      if (!id || isNaN(id)) {
+        return c.json({ error: "无效的登录记录ID" }, 400);
+      }
+      
+      const apiClient = c.get('apiClient');
+      const data = await c.req.json();
+      
+      // 验证经纬度
+      if (!data.longitude || !data.latitude) {
+        return c.json({ error: "经度和纬度不能为空" }, 400);
+      }
+      
+      // 检查登录记录是否存在
+      const location = await apiClient.database
+        .table('login_history')
+        .where('id', id)
+        .first();
+        
+      if (!location) {
+        return c.json({ error: "登录记录不存在" }, 404);
+      }
+      
+      // 更新位置信息
+      await apiClient.database
+        .table('login_history')
+        .where('id', id)
+        .update({
+          longitude: data.longitude,
+          latitude: data.latitude,
+          location_name: data.location_name
+        });
+        
+      // 获取更新后的登录记录
+      const updatedLocation = await apiClient.database
+        .table('login_history')
+        .where('id', id)
+        .first();
+        
+      return c.json({
+        message: "登录位置信息更新成功",
+        data: updatedLocation,
+      });
+    } catch (error) {
+      log.api("更新登录位置信息失败:", error);
+      return c.json({ error: "更新登录位置信息失败" }, 500);
+    }
+  });
+
+  return mapRoutes;
+}

+ 1063 - 0
routes_sys.ts

@@ -0,0 +1,1063 @@
+import { Hono } from "hono";
+import debug from "debug";
+import type {
+  FileLibrary,
+  FileCategory,
+  KnowInfo,
+  ThemeSettings,
+  SystemSetting,
+  SystemSettingGroupData,
+} from "./asset/share/types.ts";
+
+import {
+  EnableStatus,
+  DeleteStatus,
+  ThemeMode,
+  FontSize,
+  CompactMode,
+} from "./asset/share/types.ts";
+
+import type { Variables, WithAuth } from "./app.tsx";
+
+const log = {
+  api: debug("api:sys"),
+};
+
+// 创建知识库管理路由
+export function createKnowInfoRoutes(withAuth: WithAuth) {
+  const knowInfoRoutes = new Hono<{ Variables: Variables }>();
+
+  // 获取知识库文章列表
+  knowInfoRoutes.get("/", withAuth, async (c) => {
+    try {
+      const apiClient = c.get('apiClient');
+
+      // 获取分页参数
+      const page = Number(c.req.query("page")) || 1;
+      const limit = Number(c.req.query("limit")) || 10;
+      const offset = (page - 1) * limit;
+
+      // 获取筛选参数
+      const title = c.req.query("title");
+      const category = c.req.query("category");
+      const tags = c.req.query("tags");
+
+      // 构建查询
+      let query = apiClient.database
+        .table("know_info")
+        .where("is_deleted", 0)
+        .orderBy("id", "desc");
+
+      // 应用筛选条件
+      if (title) {
+        query = query.where("title", "like", `%${title}%`);
+      }
+
+      if (category) {
+        query = query.where("category", category);
+      }
+
+      if (tags) {
+        query = query.where("tags", "like", `%${tags}%`);
+      }
+
+      // 克隆查询以获取总数
+      const countQuery = query.clone();
+
+      // 执行分页查询
+      const articles = await query.limit(limit).offset(offset);
+
+      // 获取总数
+      const count = await countQuery.count();
+
+      return c.json({
+        data: articles,
+        pagination: {
+          total: Number(count),
+          current: page,
+          pageSize: limit,
+          totalPages: Math.ceil(Number(count) / limit),
+        },
+      });
+    } catch (error) {
+      log.api("获取知识库文章列表失败:", error);
+      return c.json({ error: "获取知识库文章列表失败" }, 500);
+    }
+  });
+
+  // 获取单个知识库文章
+  knowInfoRoutes.get("/:id", withAuth, async (c) => {
+    try {
+      const id = Number(c.req.param("id"));
+
+      if (!id || isNaN(id)) {
+        return c.json({ error: "无效的文章ID" }, 400);
+      }
+
+      const apiClient = c.get('apiClient');
+      const [article] = await apiClient.database
+        .table("know_info")
+        .where({ id, is_deleted: 0 });
+
+      if (!article) {
+        return c.json({ error: "文章不存在" }, 404);
+      }
+
+      return c.json(article);
+    } catch (error) {
+      log.api("获取知识库文章详情失败:", error);
+      return c.json({ error: "获取知识库文章详情失败" }, 500);
+    }
+  });
+
+  // 创建知识库文章
+  knowInfoRoutes.post("/", withAuth, async (c) => {
+    try {
+      const articleData = (await c.req.json()) as Partial<KnowInfo>;
+
+      // 验证必填字段
+      if (!articleData.title) {
+        return c.json({ error: "文章标题不能为空" }, 400);
+      }
+
+      // 如果作者为空,则使用当前用户的用户名
+      if (!articleData.author) {
+        const user = c.get("user");
+        articleData.author = user ? user.username : "unknown";
+      }
+
+      const apiClient = c.get('apiClient');
+      const [id] = await apiClient.database
+        .table("know_info")
+        .insert(articleData);
+
+      // 获取创建的文章
+      const [createdArticle] = await apiClient.database
+        .table("know_info")
+        .where("id", id);
+
+      return c.json({
+        message: "知识库文章创建成功",
+        data: createdArticle,
+      });
+    } catch (error) {
+      log.api("创建知识库文章失败:", error);
+      return c.json({ error: "创建知识库文章失败" }, 500);
+    }
+  });
+
+  // 更新知识库文章
+  knowInfoRoutes.put("/:id", withAuth, async (c) => {
+    try {
+      const id = Number(c.req.param("id"));
+
+      if (!id || isNaN(id)) {
+        return c.json({ error: "无效的文章ID" }, 400);
+      }
+
+      const articleData = (await c.req.json()) as Partial<KnowInfo>;
+
+      // 验证必填字段
+      if (!articleData.title) {
+        return c.json({ error: "文章标题不能为空" }, 400);
+      }
+
+      const apiClient = c.get('apiClient');
+
+      // 检查文章是否存在
+      const [existingArticle] = await apiClient.database
+        .table("know_info")
+        .where({ id, is_deleted: 0 });
+
+      if (!existingArticle) {
+        return c.json({ error: "文章不存在" }, 404);
+      }
+
+      // 更新文章
+      await apiClient.database
+        .table("know_info")
+        .where("id", id)
+        .update({
+          ...articleData,
+          updated_at: apiClient.database.fn.now(),
+        });
+
+      // 获取更新后的文章
+      const [updatedArticle] = await apiClient.database
+        .table("know_info")
+        .where("id", id);
+
+      return c.json({
+        message: "知识库文章更新成功",
+        data: updatedArticle,
+      });
+    } catch (error) {
+      log.api("更新知识库文章失败:", error);
+      return c.json({ error: "更新知识库文章失败" }, 500);
+    }
+  });
+
+  // 删除知识库文章(软删除)
+  knowInfoRoutes.delete("/:id", withAuth, async (c) => {
+    try {
+      const id = Number(c.req.param("id"));
+
+      if (!id || isNaN(id)) {
+        return c.json({ error: "无效的文章ID" }, 400);
+      }
+
+      const apiClient = c.get('apiClient');
+
+      // 检查文章是否存在
+      const [existingArticle] = await apiClient.database
+        .table("know_info")
+        .where({ id, is_deleted: 0 });
+
+      if (!existingArticle) {
+        return c.json({ error: "文章不存在" }, 404);
+      }
+
+      // 软删除文章
+      await apiClient.database.table("know_info").where("id", id).update({
+        is_deleted: 1,
+        updated_at: apiClient.database.fn.now(),
+      });
+
+      return c.json({
+        message: "知识库文章删除成功",
+      });
+    } catch (error) {
+      log.api("删除知识库文章失败:", error);
+      return c.json({ error: "删除知识库文章失败" }, 500);
+    }
+  });
+  
+  return knowInfoRoutes;
+}
+
+// 创建文件分类路由
+export function createFileCategoryRoutes(withAuth: WithAuth) {
+  const fileCategoryRoutes = new Hono<{ Variables: Variables }>();
+
+  // 获取文件分类列表
+  fileCategoryRoutes.get("/", withAuth, async (c) => {
+    try {
+      const apiClient = c.get('apiClient');
+
+      const page = Number(c.req.query("page")) || 1;
+      const pageSize = Number(c.req.query("pageSize")) || 10;
+      const offset = (page - 1) * pageSize;
+
+      const search = c.req.query("search") || "";
+
+      let query = apiClient.database.table("file_category").orderBy("id", "desc");
+
+      if (search) {
+        query = query.where("name", "like", `%${search}%`);
+      }
+
+      const total = await query.clone().count();
+
+      const categories = await query
+        .select("id", "name", "code", "description")
+        .limit(pageSize)
+        .offset(offset);
+
+      return c.json({
+        data: categories,
+        total: Number(total),
+        page,
+        pageSize,
+      });
+    } catch (error) {
+      log.api("获取文件分类列表失败:", error);
+      return c.json({ error: "获取文件分类列表失败" }, 500);
+    }
+  });
+
+  // 创建文件分类
+  fileCategoryRoutes.post("/", withAuth, async (c) => {
+    try {
+      const apiClient = c.get('apiClient');
+      const data = (await c.req.json()) as Partial<FileCategory>;
+
+      // 验证必填字段
+      if (!data.name) {
+        return c.json({ error: "分类名称不能为空" }, 400);
+      }
+
+      // 插入文件分类
+      const [id] = await apiClient.database.table("file_category").insert({
+        ...data,
+        created_at: apiClient.database.fn.now(),
+        updated_at: apiClient.database.fn.now(),
+      });
+
+      return c.json({
+        message: "文件分类创建成功",
+        data: {
+          id,
+          ...data,
+        },
+      });
+    } catch (error) {
+      log.api("创建文件分类失败:", error);
+      return c.json({ error: "创建文件分类失败" }, 500);
+    }
+  });
+
+  // 更新文件分类
+  fileCategoryRoutes.put("/:id", withAuth, async (c) => {
+    try {
+      const apiClient = c.get('apiClient');
+      const id = Number(c.req.param("id"));
+
+      if (!id || isNaN(id)) {
+        return c.json({ error: "无效的分类ID" }, 400);
+      }
+
+      const data = (await c.req.json()) as Partial<FileCategory>;
+
+      // 更新文件分类
+      await apiClient.database
+        .table("file_category")
+        .where("id", id)
+        .update({
+          ...data,
+          updated_at: apiClient.database.fn.now(),
+        });
+
+      return c.json({
+        message: "文件分类更新成功",
+        data: {
+          id,
+          ...data,
+        },
+      });
+    } catch (error) {
+      log.api("更新文件分类失败:", error);
+      return c.json({ error: "更新文件分类失败" }, 500);
+    }
+  });
+
+  // 删除文件分类
+  fileCategoryRoutes.delete("/:id", withAuth, async (c) => {
+    try {
+      const apiClient = c.get('apiClient');
+      const id = Number(c.req.param("id"));
+
+      if (!id || isNaN(id)) {
+        return c.json({ error: "无效的分类ID" }, 400);
+      }
+
+      await apiClient.database.table("file_category").where("id", id).update({
+        is_deleted: DeleteStatus.DELETED,
+        updated_at: apiClient.database.fn.now(),
+      });
+
+      return c.json({
+        message: "文件分类删除成功",
+      });
+    } catch (error) {
+      log.api("删除文件分类失败:", error);
+      return c.json({ error: "删除文件分类失败" }, 500);
+    }
+  });
+  
+  return fileCategoryRoutes;
+}
+
+// 创建文件上传路由
+export function createFileUploadRoutes(withAuth: WithAuth) {
+  const fileUploadRoutes = new Hono<{ Variables: Variables }>();
+
+  // 获取 MinIO 上传策略
+  fileUploadRoutes.get("/policy", withAuth, async (c) => {
+    try {
+      const prefix = c.req.query("prefix") || "uploads/";
+      const filename = c.req.query("filename");
+      const maxSize = Number(c.req.query("maxSize")) || 10 * 1024 * 1024; // 默认10MB
+
+      if (!filename) {
+        return c.json({ error: "文件名不能为空" }, 400);
+      }
+
+      const apiClient = c.get('apiClient');
+      const policy = await apiClient.storage.getUploadPolicy(
+        prefix,
+        filename,
+        maxSize
+      );
+
+      return c.json({
+        message: "获取上传策略成功",
+        data: policy,
+      });
+    } catch (error) {
+      log.api("获取上传策略失败:", error);
+      return c.json({ error: "获取上传策略失败" }, 500);
+    }
+  });
+
+  // 保存文件信息到文件库
+  fileUploadRoutes.post("/save", withAuth, async (c) => {
+    try {
+      const fileData = (await c.req.json()) as Partial<FileLibrary>;
+      const user = c.get("user");
+
+      // 验证必填字段
+      if (!fileData.file_name || !fileData.file_path || !fileData.file_type) {
+        return c.json({ error: "文件名、路径和类型不能为空" }, 400);
+      }
+
+      const apiClient = c.get('apiClient');
+
+      // 设置上传者信息
+      if (user) {
+        fileData.uploader_id = user.id;
+        fileData.uploader_name = user.nickname || user.username;
+      }
+
+      // 插入文件库记录
+      const [id] = await apiClient.database.table("file_library").insert({
+        ...fileData,
+        download_count: 0,
+        is_disabled: EnableStatus.ENABLED,
+        is_deleted: DeleteStatus.NOT_DELETED,
+        created_at: apiClient.database.fn.now(),
+        updated_at: apiClient.database.fn.now(),
+      });
+
+      // 获取插入的数据
+      const [insertedFile] = await apiClient.database
+        .table("file_library")
+        .where("id", id);
+
+      return c.json({
+        message: "文件信息保存成功",
+        data: insertedFile,
+      });
+    } catch (error) {
+      log.api("保存文件信息失败:", error);
+      return c.json({ error: "保存文件信息失败" }, 500);
+    }
+  });
+
+  // 获取文件列表
+  fileUploadRoutes.get("/list", withAuth, async (c) => {
+    try {
+      const page = Number(c.req.query("page")) || 1;
+      const pageSize = Number(c.req.query("pageSize")) || 10;
+      const category_id = c.req.query("category_id");
+      const fileType = c.req.query("fileType");
+      const keyword = c.req.query("keyword");
+
+      const apiClient = c.get('apiClient');
+      let query = apiClient.database
+        .table("file_library")
+        .where("is_deleted", DeleteStatus.NOT_DELETED)
+        .orderBy("created_at", "desc");
+
+      // 应用过滤条件
+      if (category_id) {
+        query = query.where("category_id", category_id);
+      }
+
+      if (fileType) {
+        query = query.where("file_type", fileType);
+      }
+
+      if (keyword) {
+        query = query.where((builder) => {
+          builder
+            .where("file_name", "like", `%${keyword}%`)
+            .orWhere("description", "like", `%${keyword}%`)
+            .orWhere("tags", "like", `%${keyword}%`);
+        });
+      }
+
+      // 获取总数
+      const total = await query.clone().count();
+
+      // 分页查询
+      const files = await query.limit(pageSize).offset((page - 1) * pageSize);
+
+      return c.json({
+        message: "获取文件列表成功",
+        data: {
+          list: files,
+          pagination: {
+            current: page,
+            pageSize,
+            total: Number(total),
+          },
+        },
+      });
+    } catch (error) {
+      log.api("获取文件列表失败:", error);
+      return c.json({ error: "获取文件列表失败" }, 500);
+    }
+  });
+
+  // 获取单个文件信息
+  fileUploadRoutes.get("/:id", withAuth, async (c) => {
+    try {
+      const id = Number(c.req.param("id"));
+
+      if (!id || isNaN(id)) {
+        return c.json({ error: "无效的文件ID" }, 400);
+      }
+
+      const apiClient = c.get('apiClient');
+      const file = await apiClient.database
+        .table("file_library")
+        .where("id", id)
+        .where("is_deleted", DeleteStatus.NOT_DELETED)
+        .first();
+
+      if (!file) {
+        return c.json({ error: "文件不存在" }, 404);
+      }
+
+      return c.json({
+        message: "获取文件信息成功",
+        data: file,
+      });
+    } catch (error) {
+      log.api("获取文件信息失败:", error);
+      return c.json({ error: "获取文件信息失败" }, 500);
+    }
+  });
+
+  // 增加文件下载计数
+  fileUploadRoutes.post("/:id/download", withAuth, async (c) => {
+    try {
+      const id = Number(c.req.param("id"));
+
+      if (!id || isNaN(id)) {
+        return c.json({ error: "无效的文件ID" }, 400);
+      }
+
+      const apiClient = c.get('apiClient');
+
+      // 查询文件是否存在
+      const file = await apiClient.database
+        .table("file_library")
+        .where("id", id)
+        .where("is_deleted", DeleteStatus.NOT_DELETED)
+        .first();
+
+      if (!file) {
+        return c.json({ error: "文件不存在" }, 404);
+      }
+
+      // 增加下载计数
+      await apiClient.database
+        .table("file_library")
+        .where("id", id)
+        .update({
+          download_count: apiClient.database.raw("download_count + 1"),
+          updated_at: apiClient.database.fn.now(),
+        });
+
+      return c.json({
+        message: "更新下载计数成功",
+      });
+    } catch (error) {
+      log.api("更新下载计数失败:", error);
+      return c.json({ error: "更新下载计数失败" }, 500);
+    }
+  });
+
+  // 删除文件
+  fileUploadRoutes.delete("/:id", withAuth, async (c) => {
+    try {
+      const id = Number(c.req.param("id"));
+
+      if (!id || isNaN(id)) {
+        return c.json({ error: "无效的文件ID" }, 400);
+      }
+
+      const apiClient = c.get('apiClient');
+
+      // 查询文件是否存在
+      const file = await apiClient.database
+        .table("file_library")
+        .where("id", id)
+        .where("is_deleted", DeleteStatus.NOT_DELETED)
+        .first();
+
+      if (!file) {
+        return c.json({ error: "文件不存在" }, 404);
+      }
+
+      // 软删除文件
+      await apiClient.database.table("file_library").where("id", id).update({
+        is_deleted: DeleteStatus.DELETED,
+        updated_at: apiClient.database.fn.now(),
+      });
+
+      return c.json({
+        message: "文件删除成功",
+      });
+    } catch (error) {
+      log.api("删除文件失败:", error);
+      return c.json({ error: "删除文件失败" }, 500);
+    }
+  });
+  
+  return fileUploadRoutes;
+}
+
+// 创建主题设置路由
+export function createThemeRoutes(withAuth: WithAuth) {
+  const themeRoutes = new Hono<{ Variables: Variables }>();
+
+  // 获取当前主题设置
+  themeRoutes.get("/", withAuth, async (c) => {
+    try {
+      const apiClient = c.get('apiClient');
+      const user = c.get('user');
+
+      if (!user) {
+        return c.json({ error: "未授权访问" }, 401);
+      }
+
+      // 获取用户的主题设置
+      let themeSettings = await apiClient.database
+        .table("theme_settings")
+        .where("user_id", user.id)
+        .first();
+
+      // 如果用户没有主题设置,则创建默认设置
+      if (!themeSettings) {
+        const defaultSettings = {
+          theme_mode: ThemeMode.LIGHT,
+          primary_color: '#1890ff',
+          font_size: FontSize.MEDIUM,
+          is_compact: CompactMode.NORMAL
+        };
+
+        const [id] = await apiClient.database.table("theme_settings").insert({
+          user_id: user.id,
+          settings: defaultSettings,
+          created_at: apiClient.database.fn.now(),
+          updated_at: apiClient.database.fn.now(),
+        });
+
+        themeSettings = await apiClient.database
+          .table("theme_settings")
+          .where("id", id)
+          .first();
+      }
+
+      return c.json({
+        message: "获取主题设置成功",
+        data: themeSettings?.settings,
+      });
+    } catch (error) {
+      log.api("获取主题设置失败:", error);
+      return c.json({ error: "获取主题设置失败" }, 500);
+    }
+  });
+
+  // 更新主题设置
+  themeRoutes.put("/", withAuth, async (c) => {
+    try {
+      const user = c.get('user');
+      const apiClient = c.get('apiClient');
+
+      if (!user) {
+        return c.json({ error: "未授权访问" }, 401);
+      }
+
+      const themeData = (await c.req.json()) as Partial<ThemeSettings>;
+
+      // 检查用户是否已有主题设置
+      const existingTheme = await apiClient.database
+        .table("theme_settings")
+        .where("user_id", user.id)
+        .first();
+
+      if (existingTheme) {
+        // 更新现有设置
+        const currentSettings = existingTheme.settings || {};
+        const updatedSettings = {
+          ...currentSettings,
+          ...themeData
+        };
+
+        await apiClient.database
+          .table("theme_settings")
+          .where("user_id", user.id)
+          .update({
+            settings: JSON.stringify(updatedSettings),
+            updated_at: apiClient.database.fn.now(),
+          });
+      } else {
+        // 创建新设置
+        const defaultSettings = {
+          theme_mode: ThemeMode.LIGHT,
+          primary_color: '#1890ff',
+          font_size: FontSize.MEDIUM,
+          is_compact: CompactMode.NORMAL
+        };
+
+        const updatedSettings = {
+          ...defaultSettings,
+          ...themeData
+        };
+
+        await apiClient.database.table("theme_settings").insert({
+          user_id: user.id,
+          settings: updatedSettings,
+          created_at: apiClient.database.fn.now(),
+          updated_at: apiClient.database.fn.now(),
+        });
+      }
+
+      // 获取更新后的主题设置
+      const updatedTheme = await apiClient.database
+        .table("theme_settings")
+        .where("user_id", user.id)
+        .first();
+
+      return c.json({
+        message: "主题设置更新成功",
+        data: updatedTheme,
+      });
+    } catch (error) {
+      log.api("更新主题设置失败:", error);
+      return c.json({ error: "更新主题设置失败" }, 500);
+    }
+  });
+
+  // 重置主题设置为默认值
+  themeRoutes.post("/reset", withAuth, async (c) => {
+    try {
+      const user = c.get('user');
+      const apiClient = c.get('apiClient');
+
+      if (!user) {
+        return c.json({ error: "未授权访问" }, 401);
+      }
+
+      // 默认主题设置
+      const defaultSettings = {
+        theme_mode: ThemeMode.LIGHT,
+        primary_color: '#1890ff',
+        font_size: FontSize.MEDIUM,
+        is_compact: CompactMode.NORMAL
+      };
+
+      // 更新用户的主题设置
+      await apiClient.database
+        .table("theme_settings")
+        .where("user_id", user.id)
+        .update({
+          settings: JSON.stringify(defaultSettings),
+          updated_at: apiClient.database.fn.now(),
+        });
+
+      // 获取更新后的主题设置
+      const updatedTheme = await apiClient.database
+        .table("theme_settings")
+        .where("user_id", user.id)
+        .first();
+
+      return c.json({
+        message: "主题设置已重置为默认值",
+        data: updatedTheme,
+      });
+    } catch (error) {
+      log.api("重置主题设置失败:", error);
+      return c.json({ error: "重置主题设置失败" }, 500);
+    }
+  });
+
+  return themeRoutes;
+}
+
+// 创建系统设置路由
+export function createSystemSettingsRoutes(withAuth: WithAuth) {
+  const settingsRoutes = new Hono<{ Variables: Variables }>();
+
+  // 获取所有系统设置(按分组)
+  settingsRoutes.get('/', withAuth, async (c) => {
+    try {
+      const apiClient = c.get('apiClient');
+      const settings = await apiClient.database
+        .table('system_settings')
+        .select('*');
+
+      // 按分组整理数据
+      const groupedSettings = settings.reduce((acc: SystemSettingGroupData[], setting) => {
+        const groupIndex = acc.findIndex((g: SystemSettingGroupData) => g.name === setting.group);
+        if (groupIndex === -1) {
+          acc.push({
+            name: setting.group,
+            description: `${setting.group}组设置`,
+            settings: [{
+              id: setting.id,
+              key: setting.key,
+              value: setting.value,
+              description: setting.description,
+              group: setting.group
+            }]
+          });
+        } else {
+          acc[groupIndex].settings.push({
+            id: setting.id,
+            key: setting.key,
+            value: setting.value,
+            description: setting.description,
+            group: setting.group
+          });
+        }
+        return acc;
+      }, []);
+
+      return c.json({
+        message: '获取系统设置成功',
+        data: groupedSettings
+      });
+    } catch (error) {
+      log.api('获取系统设置失败:', error);
+      return c.json({ error: '获取系统设置失败' }, 500);
+    }
+  });
+
+  // 获取指定分组的系统设置
+  settingsRoutes.get('/group/:group', withAuth, async (c) => {
+    try {
+      const apiClient = c.get('apiClient');
+      const group = c.req.param('group');
+      
+      const settings = await apiClient.database
+        .table('system_settings')
+        .where('group', group)
+        .select('*');
+
+      return c.json({
+        message: '获取分组设置成功',
+        data: settings
+      });
+    } catch (error) {
+      log.api('获取分组设置失败:', error);
+      return c.json({ error: '获取分组设置失败' }, 500);
+    }
+  });
+
+  // 更新系统设置
+  settingsRoutes.put('/:key', withAuth, async (c) => {
+    try {
+      const apiClient = c.get('apiClient');
+      const key = c.req.param('key');
+      const settingData = await c.req.json();
+
+      // 验证设置是否存在
+      const existingSetting = await apiClient.database
+        .table('system_settings')
+        .where('key', key)
+        .first();
+
+      if (!existingSetting) {
+        return c.json({ error: '设置项不存在' }, 404);
+      }
+
+      // 更新设置
+      await apiClient.database
+        .table('system_settings')
+        .where('key', key)
+        .update({
+          value: settingData.value,
+          updated_at: apiClient.database.fn.now()
+        });
+
+      // 获取更新后的设置
+      const updatedSetting = await apiClient.database
+        .table('system_settings')
+        .where('key', key)
+        .first();
+
+      return c.json({
+        message: '系统设置已更新',
+        data: updatedSetting
+      });
+    } catch (error) {
+      log.api('更新系统设置失败:', error);
+      return c.json({ error: '更新系统设置失败' }, 500);
+    }
+  });
+
+  // 批量更新系统设置
+  settingsRoutes.put('/', withAuth, async (c) => {
+    try {
+      const apiClient = c.get('apiClient');
+      const settingsData = await c.req.json();
+
+      // 验证数据格式
+      if (!Array.isArray(settingsData)) {
+        return c.json({ error: '无效的请求数据格式,应为数组' }, 400);
+      }
+
+      const trxProvider = apiClient.database.transactionProvider();
+      const trx = await trxProvider();
+
+      for (const setting of settingsData) {
+        if (!setting.key) continue;
+
+        // 验证设置是否存在
+        const existingSetting = await trx.table('system_settings')
+          .where('key', setting.key)
+          .first();
+
+        if (!existingSetting) {
+          throw new Error(`设置项 ${setting.key} 不存在`);
+        }
+
+        // 更新设置
+        await trx.table('system_settings')
+          .where('key', setting.key)
+          .update({
+            value: setting.value,
+            updated_at: trx.fn.now()
+          });
+      }
+
+      await trx.commit();
+
+      // 获取所有更新后的设置
+      const updatedSettings = await apiClient.database
+        .table('system_settings')
+        .whereIn('key', settingsData.map(s => s.key))
+        .select('*');
+
+      return c.json({
+        message: '系统设置已批量更新',
+        data: updatedSettings
+      });
+    } catch (error) {
+      log.api('批量更新系统设置失败:', error);
+      return c.json({ error: '批量更新系统设置失败' }, 500);
+    }
+  });
+
+  // 重置系统设置
+  settingsRoutes.post('/reset', withAuth, async (c) => {
+    try {
+      const apiClient = c.get('apiClient');
+
+      // 重置为迁移文件中定义的初始值
+      const trxProvider = apiClient.database.transactionProvider();
+      const trx = await trxProvider();
+
+      // 清空现有设置
+      await trx.table('system_settings').delete();
+      
+      // 插入默认设置
+      await trx.table('system_settings').insert([
+        // 基础设置组
+        {
+          key: 'SITE_NAME',
+          value: '应用管理系统',
+          description: '站点名称',
+          group: 'basic',
+          created_at: apiClient.database.fn.now(),
+          updated_at: apiClient.database.fn.now()
+        },
+        {
+          key: 'SITE_DESCRIPTION',
+          value: '一个强大的应用管理系统',
+          description: '站点描述',
+          group: 'basic',
+          created_at: apiClient.database.fn.now(),
+          updated_at: apiClient.database.fn.now()
+        },
+        {
+          key: 'SITE_KEYWORDS',
+          value: '应用管理,系统管理,后台管理',
+          description: '站点关键词',
+          group: 'basic',
+          created_at: apiClient.database.fn.now(),
+          updated_at: apiClient.database.fn.now()
+        },
+        // 功能设置组
+        {
+          key: 'ENABLE_REGISTER',
+          value: 'true',
+          description: '是否开启注册',
+          group: 'feature',
+          created_at: apiClient.database.fn.now(),
+          updated_at: apiClient.database.fn.now()
+        },
+        {
+          key: 'ENABLE_CAPTCHA',
+          value: 'true',
+          description: '是否开启验证码',
+          group: 'feature',
+          created_at: apiClient.database.fn.now(),
+          updated_at: apiClient.database.fn.now()
+        },
+        {
+          key: 'LOGIN_ATTEMPTS',
+          value: '5',
+          description: '登录尝试次数',
+          group: 'feature',
+          created_at: apiClient.database.fn.now(),
+          updated_at: apiClient.database.fn.now()
+        },
+        // 上传设置组
+        {
+          key: 'UPLOAD_MAX_SIZE',
+          value: '10',
+          description: '最大上传大小(MB)',
+          group: 'upload',
+          created_at: apiClient.database.fn.now(),
+          updated_at: apiClient.database.fn.now()
+        },
+        {
+          key: 'ALLOWED_FILE_TYPES',
+          value: 'jpg,jpeg,png,gif,doc,docx,xls,xlsx,pdf',
+          description: '允许的文件类型',
+          group: 'upload',
+          created_at: apiClient.database.fn.now(),
+          updated_at: apiClient.database.fn.now()
+        },
+        // 通知设置组
+        {
+          key: 'NOTIFY_ON_LOGIN',
+          value: 'true',
+          description: '登录通知',
+          group: 'notify',
+          created_at: apiClient.database.fn.now(),
+          updated_at: apiClient.database.fn.now()
+        },
+        {
+          key: 'NOTIFY_ON_ERROR',
+          value: 'true',
+          description: '错误通知',
+          group: 'notify',
+          created_at: apiClient.database.fn.now(),
+          updated_at: apiClient.database.fn.now()
+        }
+      ]);
+
+      await trx.commit();
+
+      const resetSettings = await apiClient.database
+        .table('system_settings')
+        .select('*');
+
+      return c.json({
+        message: '系统设置已重置',
+        data: resetSettings
+      });
+    } catch (error) {
+      log.api('重置系统设置失败:', error);
+      return c.json({ error: '重置系统设置失败' }, 500);
+    }
+  });
+
+  return settingsRoutes;
+}

+ 241 - 0
routes_users.ts

@@ -0,0 +1,241 @@
+import { Hono } from 'hono'
+import type { Variables } from './app.tsx'
+import type { WithAuth } from './app.tsx'
+
+export function createUserRoutes(withAuth: WithAuth) {
+  const usersRoutes = new Hono<{ Variables: Variables }>()
+  
+  // 获取用户列表
+  usersRoutes.get('/', withAuth, async (c) => {
+    try {
+      const apiClient = c.get('apiClient')
+      
+      const page = Number(c.req.query('page')) || 1
+      const pageSize = Number(c.req.query('pageSize')) || 10
+      const offset = (page - 1) * pageSize
+      
+      const search = c.req.query('search') || ''
+      
+      let query = apiClient.database.table('users')
+        .orderBy('id', 'desc')
+      
+      if (search) {
+        query = query.where((builder) => {
+          builder.where('username', 'like', `%${search}%`)
+              .orWhere('nickname', 'like', `%${search}%`)
+              .orWhere('email', 'like', `%${search}%`)
+        })
+      }
+      
+      const total = await query.clone().count()
+      const users = await query.select('id', 'username', 'nickname', 'email', 'phone', 'role', 'created_at')
+        .limit(pageSize).offset(offset)
+      
+      return c.json({
+        data: users,
+        pagination: {
+          total: Number(total),
+          current: page,
+          pageSize
+        }
+      })
+    } catch (error) {
+      console.error('获取用户列表失败:', error)
+      return c.json({ error: '获取用户列表失败' }, 500)
+    }
+  })
+  
+  // 获取单个用户详情
+  usersRoutes.get('/:id', withAuth, async (c) => {
+    try {
+      const id = Number(c.req.param('id'))
+      
+      if (!id || isNaN(id)) {
+        return c.json({ error: '无效的用户ID' }, 400)
+      }
+      
+      const apiClient = c.get('apiClient')
+      const user = await apiClient.database.table('users')
+        .where('id', id)
+        .select('id', 'username', 'nickname', 'email', 'phone', 'role', 'created_at')
+        .first()
+      
+      if (!user) {
+        return c.json({ error: '用户不存在' }, 404)
+      }
+      
+      return c.json({
+        data: user,
+        message: '获取用户详情成功'
+      })
+    } catch (error) {
+      console.error('获取用户详情失败:', error)
+      return c.json({ error: '获取用户详情失败' }, 500)
+    }
+  })
+
+  // 创建用户
+  usersRoutes.post('/', withAuth, async (c) => {
+    try {
+      const apiClient = c.get('apiClient')
+      const body = await c.req.json()
+      
+      // 验证必填字段
+      const { username, nickname, email, password, role } = body
+      if (!username || !nickname || !email || !password || !role) {
+        return c.json({ error: '缺少必要的用户信息' }, 400)
+      }
+
+      // 检查用户名是否已存在
+      const existingUser = await apiClient.database.table('users')
+        .where('username', username)
+        .first()
+      
+      if (existingUser) {
+        return c.json({ error: '用户名已存在' }, 400)
+      }
+
+      // 创建用户
+      const [id] = await apiClient.database.table('users').insert({
+        username,
+        nickname,
+        email,
+        password: password, // 加密密码
+        role,
+        created_at: new Date(),
+        updated_at: new Date()
+      })
+
+      const newUser = await apiClient.database.table('users')
+        .where('id', id)
+        .select('id', 'username', 'nickname', 'email', 'role', 'created_at')
+        .first()
+
+      return c.json({
+        data: newUser,
+        message: '创建用户成功'
+      })
+    } catch (error) {
+      console.error('创建用户失败:', error)
+      return c.json({ error: '创建用户失败' }, 500)
+    }
+  })
+
+  // 更新用户
+  usersRoutes.put('/:id', withAuth, async (c) => {
+    try {
+      const id = Number(c.req.param('id'))
+      if (!id || isNaN(id)) {
+        return c.json({ error: '无效的用户ID' }, 400)
+      }
+
+      const apiClient = c.get('apiClient')
+      const body = await c.req.json()
+      
+      // 验证必填字段
+      const { username, nickname, email, role } = body
+      if (!username || !nickname || !email || !role) {
+        return c.json({ error: '缺少必要的用户信息' }, 400)
+      }
+
+      // 检查用户是否存在
+      const existingUser = await apiClient.database.table('users')
+        .where('id', id)
+        .first()
+      
+      if (!existingUser) {
+        return c.json({ error: '用户不存在' }, 404)
+      }
+
+      // 如果修改了用户名,检查新用户名是否已被使用
+      if (username !== existingUser.username) {
+        const userWithSameName = await apiClient.database.table('users')
+          .where('username', username)
+          .whereNot('id', id.toString())
+          .first()
+        
+        if (userWithSameName) {
+          return c.json({ error: '用户名已存在' }, 400)
+        }
+      }
+
+      // 更新用户信息
+      const updateData: any = {
+        username,
+        nickname,
+        email,
+        role,
+        updated_at: new Date()
+      }
+
+      // 如果提供了新密码,则更新密码
+      if (body.password) {
+        updateData.password = body.password
+      }
+
+      await apiClient.database.table('users')
+        .where('id', id)
+        .update(updateData)
+
+      const updatedUser = await apiClient.database.table('users')
+        .where('id', id)
+        .select('id', 'username', 'nickname', 'email', 'role', 'created_at')
+        .first()
+
+      return c.json({
+        data: updatedUser,
+        message: '更新用户成功'
+      })
+    } catch (error) {
+      console.error('更新用户失败:', error)
+      return c.json({ error: '更新用户失败' }, 500)
+    }
+  })
+
+  // 删除用户
+  usersRoutes.delete('/:id', withAuth, async (c) => {
+    try {
+      const id = Number(c.req.param('id'))
+      if (!id || isNaN(id)) {
+        return c.json({ error: '无效的用户ID' }, 400)
+      }
+
+      const apiClient = c.get('apiClient')
+      
+      // 检查用户是否存在
+      const existingUser = await apiClient.database.table('users')
+        .where('id', id)
+        .first()
+      
+      if (!existingUser) {
+        return c.json({ error: '用户不存在' }, 404)
+      }
+
+      // 检查是否为最后一个管理员
+      if (existingUser.role === 'admin') {
+        const adminCount = await apiClient.database.table('users')
+          .where('role', 'admin')
+          .count()
+        
+        if (Number(adminCount) <= 1) {
+          return c.json({ error: '不能删除最后一个管理员' }, 400)
+        }
+      }
+
+      // 删除用户
+      await apiClient.database.table('users')
+        .where('id', id)
+        .delete()
+
+      return c.json({
+        message: '删除用户成功',
+        id
+      })
+    } catch (error) {
+      console.error('删除用户失败:', error)
+      return c.json({ error: '删除用户失败' }, 500)
+    }
+  })
+
+  return usersRoutes
+}