소스 검색

添加管理端和移动端的多个新功能模块,包括文件上传、在线地图、用户认证、系统设置等,优化代码结构,提升可维护性和用户体验。

zyh 8 달 전
부모
커밋
b1a6b608c6

+ 0 - 0
asset/admin/api.ts → client/admin/api.ts


+ 0 - 0
asset/admin/components_amap.tsx → client/admin/components_amap.tsx


+ 0 - 0
asset/admin/components_uploader.tsx → client/admin/components_uploader.tsx


+ 0 - 0
asset/admin/deno.json → client/admin/deno.json


+ 0 - 0
asset/admin/deno.lock → client/admin/deno.lock


+ 0 - 0
asset/admin/hooks_sys.tsx → client/admin/hooks_sys.tsx


+ 0 - 0
asset/admin/pages_chart.tsx → client/admin/pages_chart.tsx


+ 0 - 0
asset/admin/pages_login_reg.tsx → client/admin/pages_login_reg.tsx


+ 0 - 0
asset/admin/pages_map.tsx → client/admin/pages_map.tsx


+ 0 - 0
asset/admin/pages_settings.tsx → client/admin/pages_settings.tsx


+ 0 - 0
asset/admin/pages_sys.tsx → client/admin/pages_sys.tsx


+ 0 - 0
asset/admin/style_amap.css → client/admin/style_amap.css


+ 0 - 0
asset/admin/utils.ts → client/admin/utils.ts


+ 0 - 0
asset/admin/web_app.tsx → client/admin/web_app.tsx


+ 18 - 0
client/mobile/deno.json

@@ -0,0 +1,18 @@
+{
+  "imports": {
+    "react": "https://esm.d8d.fun/react@19.0.0?deps=react@19.0.0,react-dom@19.0.0",
+    "react-dom": "https://esm.d8d.fun/react-dom@19.0.0?deps=react@19.0.0,react-dom@19.0.0",
+    "react-dom/client": "https://esm.d8d.fun/react-dom@19.0.0/client?deps=react@19.0.0,react-dom@19.0.0",
+    "react-router": "https://esm.d8d.fun/react-router@7.3.0?deps=react@19.0.0,react-dom@19.0.0",
+    "@heroicons/react/24/outline": "https://esm.d8d.fun/@heroicons/react@2.1.1/24/outline?deps=react@19.0.0",
+    "@heroicons/react/24/solid": "https://esm.d8d.fun/@heroicons/react@2.1.1/24/solid?deps=react@19.0.0",
+    "axios": "https://esm.d8d.fun/axios@1.6.7",
+    "dayjs": "https://esm.d8d.fun/dayjs@1.11.13",
+    "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",
+    "dayjs/locale/zh-cn": "https://esm.d8d.fun/dayjs@1.11.13/locale/zh-cn",
+    "@tanstack/react-query": "https://esm.d8d.fun/@tanstack/react-query@5.67.1?deps=react@19.0.0,react-dom@19.0.0",
+    "@d8d-appcontainer/api": "https://esm.d8d.fun/@d8d-appcontainer/api@3.0.47",
+    "@d8d-appcontainer/types": "https://esm.d8d.fun/@d8d-appcontainer/types@3.0.47"
+  }
+} 

+ 271 - 0
client/mobile/hooks.tsx

@@ -0,0 +1,271 @@
+import React, { createContext, useContext, useState, useEffect } from 'react';
+import axios from 'axios';
+import { getLocalStorageWithExpiry, setLocalStorageWithExpiry } from './utils.ts';
+import type { User, AuthContextType, ThemeContextType, ThemeSettings } from '../share/types.ts';
+import { ThemeMode, FontSize, CompactMode } from '../share/types.ts';
+
+// 创建axios实例
+const api = axios.create({
+  baseURL: window.CONFIG?.API_BASE_URL || '/api',
+  timeout: 10000,
+  headers: {
+    'Content-Type': 'application/json',
+  }
+});
+
+// 请求拦截器添加token
+api.interceptors.request.use(
+  (config) => {
+    const token = getLocalStorageWithExpiry('token');
+    if (token) {
+      config.headers['Authorization'] = `Bearer ${token}`;
+    }
+    return config;
+  },
+  (error) => {
+    return Promise.reject(error);
+  }
+);
+
+// 响应拦截器处理错误
+api.interceptors.response.use(
+  (response) => {
+    return response;
+  },
+  (error) => {
+    if (error.response && error.response.status === 401) {
+      // 清除本地存储并刷新页面
+      localStorage.removeItem('token');
+      localStorage.removeItem('user');
+      window.location.href = '/mobile/login';
+    }
+    return Promise.reject(error);
+  }
+);
+
+// 默认主题设置
+const defaultThemeSettings: ThemeSettings = {
+  user_id: 0,
+  theme_mode: ThemeMode.LIGHT,
+  primary_color: '#3B82F6', // 蓝色
+  background_color: '#F9FAFB',
+  text_color: '#111827',
+  border_radius: 8,
+  font_size: FontSize.MEDIUM,
+  is_compact: CompactMode.NORMAL
+};
+
+// 创建认证上下文
+const AuthContext = createContext<AuthContextType>({
+  user: null,
+  token: null,
+  login: async () => {},
+  logout: async () => {},
+  isAuthenticated: false,
+  isLoading: true
+});
+
+// 创建主题上下文
+const ThemeContext = createContext<ThemeContextType>({
+  isDark: false,
+  currentTheme: defaultThemeSettings,
+  updateTheme: () => {},
+  saveTheme: async () => defaultThemeSettings,
+  resetTheme: async () => defaultThemeSettings,
+  toggleTheme: () => {}
+});
+
+// 认证提供者组件
+export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
+  const [user, setUser] = useState<User | null>(null);
+  const [token, setToken] = useState<string | null>(null);
+  const [isLoading, setIsLoading] = useState(true);
+
+  // 初始化时从本地存储获取用户信息和令牌
+  useEffect(() => {
+    const storedToken = getLocalStorageWithExpiry('token');
+    const storedUser = getLocalStorageWithExpiry('user');
+    
+    if (storedToken && storedUser) {
+      setToken(storedToken);
+      setUser(storedUser);
+    }
+    
+    setIsLoading(false);
+  }, []);
+
+  // 登录函数
+  const login = async (username: string, password: string) => {
+    try {
+      const response = await api.post('/auth/login', { username, password });
+      const { token, user } = response.data;
+      
+      // 保存到状态和本地存储
+      setToken(token);
+      setUser(user);
+      setLocalStorageWithExpiry('token', token, 24); // 24小时过期
+      setLocalStorageWithExpiry('user', user, 24);
+      
+      return user;
+    } catch (error) {
+      console.error('登录失败:', error);
+      throw error;
+    }
+  };
+
+  // 登出函数
+  const logout = async () => {
+    try {
+      // 调用登出API
+      await api.post('/auth/logout');
+    } catch (error) {
+      console.error('登出API调用失败:', error);
+    } finally {
+      // 无论API调用成功与否,都清除本地状态
+      setToken(null);
+      setUser(null);
+      localStorage.removeItem('token');
+      localStorage.removeItem('user');
+    }
+  };
+
+  return (
+    <AuthContext.Provider
+      value={{
+        user,
+        token,
+        login,
+        logout,
+        isAuthenticated: !!token,
+        isLoading
+      }}
+    >
+      {children}
+    </AuthContext.Provider>
+  );
+};
+
+// 主题提供者组件
+export const ThemeProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
+  const [currentTheme, setCurrentTheme] = useState<ThemeSettings>(() => {
+    const storedTheme = localStorage.getItem('theme');
+    return storedTheme ? JSON.parse(storedTheme) : defaultThemeSettings;
+  });
+  
+  const isDark = currentTheme.theme_mode === ThemeMode.DARK;
+
+  // 更新主题(实时预览)
+  const updateTheme = (theme: Partial<ThemeSettings>) => {
+    setCurrentTheme(prev => {
+      const updatedTheme = { ...prev, ...theme };
+      localStorage.setItem('theme', JSON.stringify(updatedTheme));
+      return updatedTheme;
+    });
+  };
+
+  // 保存主题到后端
+  const saveTheme = async (theme: Partial<ThemeSettings>): Promise<ThemeSettings> => {
+    try {
+      const updatedTheme = { ...currentTheme, ...theme };
+      const { data } = await api.post('/theme/save', updatedTheme);
+      
+      setCurrentTheme(data);
+      localStorage.setItem('theme', JSON.stringify(data));
+      
+      return data;
+    } catch (error) {
+      console.error('保存主题失败:', error);
+      throw error;
+    }
+  };
+
+  // 重置主题
+  const resetTheme = async (): Promise<ThemeSettings> => {
+    try {
+      const { data } = await api.post('/theme/reset');
+      
+      setCurrentTheme(data);
+      localStorage.setItem('theme', JSON.stringify(data));
+      
+      return data;
+    } catch (error) {
+      console.error('重置主题失败:', error);
+      
+      // 如果API失败,至少重置到默认主题
+      setCurrentTheme(defaultThemeSettings);
+      localStorage.setItem('theme', JSON.stringify(defaultThemeSettings));
+      
+      return defaultThemeSettings;
+    }
+  };
+
+  // 切换主题模式(亮色/暗色)
+  const toggleTheme = () => {
+    const newMode = isDark ? ThemeMode.LIGHT : ThemeMode.DARK;
+    const updatedTheme = {
+      ...currentTheme,
+      theme_mode: newMode,
+      // 暗色和亮色模式下自动调整背景色和文字颜色
+      background_color: newMode === ThemeMode.DARK ? '#121212' : '#F9FAFB',
+      text_color: newMode === ThemeMode.DARK ? '#E5E7EB' : '#111827'
+    };
+    
+    setCurrentTheme(updatedTheme);
+    localStorage.setItem('theme', JSON.stringify(updatedTheme));
+  };
+
+  // 主题变化时应用CSS变量
+  useEffect(() => {
+    document.documentElement.style.setProperty('--primary-color', currentTheme.primary_color);
+    document.documentElement.style.setProperty('--background-color', currentTheme.background_color || '#F9FAFB');
+    document.documentElement.style.setProperty('--text-color', currentTheme.text_color || '#111827');
+    document.documentElement.style.setProperty('--border-radius', `${currentTheme.border_radius || 8}px`);
+    
+    // 设置字体大小
+    let rootFontSize = '16px'; // 默认中等字体
+    if (currentTheme.font_size === FontSize.SMALL) {
+      rootFontSize = '14px';
+    } else if (currentTheme.font_size === FontSize.LARGE) {
+      rootFontSize = '18px';
+    }
+    document.documentElement.style.setProperty('--font-size', rootFontSize);
+    
+    // 设置暗色模式类
+    if (isDark) {
+      document.documentElement.classList.add('dark');
+    } else {
+      document.documentElement.classList.remove('dark');
+    }
+  }, [currentTheme, isDark]);
+
+  return (
+    <ThemeContext.Provider
+      value={{
+        isDark,
+        currentTheme,
+        updateTheme,
+        saveTheme,
+        resetTheme,
+        toggleTheme
+      }}
+    >
+      {children}
+    </ThemeContext.Provider>
+  );
+};
+
+// 主题hook
+export const useTheme = () => useContext(ThemeContext);
+
+// 认证hook
+export const useAuth = () => useContext(AuthContext);
+
+// API hook
+export const useApi = () => {
+  const { token } = useAuth();
+  
+  return {
+    api,
+    isAuthenticated: !!token
+  };
+}; 

+ 313 - 0
client/mobile/mobile_app.tsx

@@ -0,0 +1,313 @@
+import React, { useEffect } from 'react';
+import { createRoot } from 'react-dom/client';
+import { 
+  createBrowserRouter,
+  RouterProvider,
+  Outlet,
+  Navigate,
+  useLocation
+} from 'react-router';
+import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
+import { AuthProvider, ThemeProvider, useAuth } from './hooks.tsx';
+import HomePage from './pages/index.tsx';
+import LoginPage from './pages/login.tsx';
+import dayjs from 'dayjs';
+import 'dayjs/locale/zh-cn';
+
+// 设置中文语言
+dayjs.locale('zh-cn');
+
+// 创建QueryClient实例
+const queryClient = new QueryClient();
+
+// 添加全局CSS(使用TailwindCSS的类)
+const injectGlobalStyles = () => {
+  const style = document.createElement('style');
+  style.innerHTML = `
+    :root {
+      --primary-color: #3B82F6;
+      --background-color: #F9FAFB;
+      --text-color: #111827;
+      --border-radius: 8px;
+      --font-size: 16px;
+    }
+    
+    * {
+      margin: 0;
+      padding: 0;
+      box-sizing: border-box;
+    }
+    
+    body {
+      font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen,
+        Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
+      background-color: var(--background-color);
+      color: var(--text-color);
+      font-size: var(--font-size);
+      line-height: 1.5;
+      -webkit-font-smoothing: antialiased;
+      -moz-osx-font-smoothing: grayscale;
+    }
+    
+    .line-clamp-1 {
+      display: -webkit-box;
+      -webkit-line-clamp: 1;
+      -webkit-box-orient: vertical;
+      overflow: hidden;
+    }
+    
+    .line-clamp-2 {
+      display: -webkit-box;
+      -webkit-line-clamp: 2;
+      -webkit-box-orient: vertical;
+      overflow: hidden;
+    }
+    
+    .line-clamp-3 {
+      display: -webkit-box;
+      -webkit-line-clamp: 3;
+      -webkit-box-orient: vertical;
+      overflow: hidden;
+    }
+    
+    /* 暗色模式支持 */
+    .dark {
+      color-scheme: dark;
+    }
+    
+    .dark body {
+      background-color: #121212;
+      color: #E5E7EB;
+    }
+    
+    /* 滚动条美化 */
+    ::-webkit-scrollbar {
+      width: 6px;
+      height: 6px;
+    }
+    
+    ::-webkit-scrollbar-track {
+      background: transparent;
+    }
+    
+    ::-webkit-scrollbar-thumb {
+      background: #BFDBFE;
+      border-radius: 3px;
+    }
+    
+    ::-webkit-scrollbar-thumb:hover {
+      background: #93C5FD;
+    }
+    
+    /* 移动端点击高亮颜色 */
+    * {
+      -webkit-tap-highlight-color: transparent;
+    }
+  `;
+  document.head.appendChild(style);
+};
+
+// 授权路由守卫
+const ProtectedRoute = ({ children }: { children: React.ReactNode }) => {
+  const { isAuthenticated, isLoading } = useAuth();
+  const location = useLocation();
+  
+  if (isLoading) {
+    return (
+      <div className="flex items-center justify-center min-h-screen">
+        <div className="w-12 h-12 border-4 border-blue-200 border-t-blue-600 rounded-full animate-spin"></div>
+      </div>
+    );
+  }
+  
+  if (!isAuthenticated) {
+    return <Navigate to="/mobile/login" state={{ from: location }} replace />;
+  }
+  
+  return <>{children}</>;
+};
+
+// 页面组件
+const PageNotFound = () => (
+  <div className="flex flex-col items-center justify-center min-h-screen p-6 text-center">
+    <div className="text-6xl font-bold text-blue-600 mb-4">404</div>
+    <h1 className="text-2xl font-medium mb-2">页面不存在</h1>
+    <p className="text-gray-500 mb-6">您访问的页面不存在或已被移除</p>
+    <a 
+      href="/mobile"
+      className="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
+    >
+      返回首页
+    </a>
+  </div>
+);
+
+// 添加个人页面组件
+const ProfilePage = () => (
+  <div className="p-4">
+    <h1 className="text-2xl font-bold mb-4">我的</h1>
+    <div className="bg-white rounded-lg shadow p-4 mb-4">
+      <div className="flex items-center mb-4">
+        <div className="w-16 h-16 bg-blue-100 rounded-full flex items-center justify-center mr-4">
+          <span className="text-2xl text-blue-600">用户</span>
+        </div>
+        <div>
+          <h2 className="text-xl font-semibold">用户名</h2>
+          <p className="text-gray-500">个人信息</p>
+        </div>
+      </div>
+    </div>
+    <div className="bg-white rounded-lg shadow">
+      <div className="p-4 border-b">
+        <span className="font-medium">设置</span>
+      </div>
+      <div className="divide-y">
+        <div className="p-4 flex justify-between items-center">
+          <span>账号安全</span>
+          <span className="text-gray-400">›</span>
+        </div>
+        <div className="p-4 flex justify-between items-center">
+          <span>通知设置</span>
+          <span className="text-gray-400">›</span>
+        </div>
+        <div className="p-4 flex justify-between items-center">
+          <span>隐私</span>
+          <span className="text-gray-400">›</span>
+        </div>
+        <div className="p-4 flex justify-between items-center">
+          <span>关于</span>
+          <span className="text-gray-400">›</span>
+        </div>
+      </div>
+    </div>
+  </div>
+);
+
+// 添加通知页面组件
+const NotificationsPage = () => (
+  <div className="p-4">
+    <h1 className="text-2xl font-bold mb-4">通知</h1>
+    <div className="bg-white rounded-lg shadow divide-y">
+      <div className="p-4">
+        <h3 className="font-medium">系统通知</h3>
+        <p className="text-gray-500 text-sm mt-1">欢迎使用移动应用!</p>
+        <p className="text-xs text-gray-400 mt-2">今天 10:00</p>
+      </div>
+      <div className="p-4">
+        <h3 className="font-medium">活动提醒</h3>
+        <p className="text-gray-500 text-sm mt-1">您有一个新的活动邀请</p>
+        <p className="text-xs text-gray-400 mt-2">昨天 14:30</p>
+      </div>
+    </div>
+  </div>
+);
+
+// 移动端布局组件 - 包含底部导航
+const MobileLayout = () => {
+  const location = useLocation();
+  
+  return (
+    <div className="flex flex-col min-h-screen">
+      <div className="flex-1 pb-16">
+        <Outlet />
+      </div>
+      
+      {/* 底部导航栏 */}
+      <nav className="fixed bottom-0 left-0 right-0 bg-white border-t border-gray-200 shadow-lg">
+        <div className="flex justify-around">
+          <a 
+            href="/mobile" 
+            className={`flex flex-col items-center py-2 px-4 ${
+              location.pathname === '/mobile' ? 'text-blue-600' : 'text-gray-500'
+            }`}
+          >
+            <div className="text-xl mb-1">🏠</div>
+            <span className="text-xs">首页</span>
+          </a>
+          <a 
+            href="/mobile/notifications" 
+            className={`flex flex-col items-center py-2 px-4 ${
+              location.pathname === '/mobile/notifications' ? 'text-blue-600' : 'text-gray-500'
+            }`}
+          >
+            <div className="text-xl mb-1">🔔</div>
+            <span className="text-xs">通知</span>
+          </a>
+          <a 
+            href="/mobile/profile" 
+            className={`flex flex-col items-center py-2 px-4 ${
+              location.pathname === '/mobile/profile' ? 'text-blue-600' : 'text-gray-500'
+            }`}
+          >
+            <div className="text-xl mb-1">👤</div>
+            <span className="text-xs">我的</span>
+          </a>
+        </div>
+      </nav>
+    </div>
+  );
+};
+
+// 主应用组件
+const App = () => {
+  // 创建路由器配置
+  const router = createBrowserRouter([
+    {
+      path: '/',
+      element: <Navigate to="/mobile" replace />
+    },
+    {
+      path: '/mobile/login',
+      element: <LoginPage />
+    },
+    {
+      path: '/mobile',
+      element: (
+        <ProtectedRoute>
+          <MobileLayout />
+        </ProtectedRoute>
+      ),
+      children: [
+        {
+          index: true,
+          element: <HomePage />
+        },
+        {
+          path: 'profile',
+          element: <ProfilePage />
+        },
+        {
+          path: 'notifications',
+          element: <NotificationsPage />
+        }
+      ]
+    },
+    {
+      path: '*',
+      element: <PageNotFound />
+    }
+  ]);
+
+  return <RouterProvider router={router} />;
+};
+
+// 渲染应用到DOM
+const initApp = () => {
+  // 注入全局样式
+  injectGlobalStyles();
+  
+  // 渲染应用
+  const root = createRoot(document.getElementById('root') as HTMLElement);
+  root.render(
+    <QueryClientProvider client={queryClient}>
+      <ThemeProvider>
+        <AuthProvider>
+          <App />
+        </AuthProvider>
+      </ThemeProvider>
+    </QueryClientProvider>
+  );
+};
+
+// 初始化应用
+initApp(); 

+ 336 - 0
client/mobile/pages/index.tsx

@@ -0,0 +1,336 @@
+import React, { useState, useEffect } from 'react';
+import { useNavigate, useLocation } from 'react-router';
+import { 
+  HomeIcon, 
+  UserIcon, 
+  NewspaperIcon, 
+  BellIcon 
+} from '@heroicons/react/24/outline';
+import { useAuth } from '../hooks.tsx';
+import { formatRelativeTime } from '../utils.ts';
+
+interface BannerItem {
+  id: number;
+  title: string;
+  image: string;
+  link: string;
+}
+
+interface NewsItem {
+  id: number;
+  title: string;
+  summary: string;
+  publish_date: string;
+  cover?: string;
+  category: string;
+}
+
+interface NoticeItem {
+  id: number;
+  title: string;
+  content: string;
+  created_at: string;
+  is_read: boolean;
+}
+
+// 首页组件
+const HomePage: React.FC = () => {
+  const { user } = useAuth();
+  const navigate = useNavigate();
+  const location = useLocation();
+  const [loading, setLoading] = useState(true);
+  const [banners, setBanners] = useState<BannerItem[]>([]);
+  const [news, setNews] = useState<NewsItem[]>([]);
+  const [notices, setNotices] = useState<NoticeItem[]>([]);
+  const [activeTab, setActiveTab] = useState('news');
+  
+  // 模拟加载数据
+  useEffect(() => {
+    // 模拟API请求
+    setTimeout(() => {
+      // 模拟轮播图数据
+      setBanners([
+        { 
+          id: 1, 
+          title: '欢迎使用移动端应用', 
+          image: 'https://images.unsplash.com/photo-1518655048521-f130df041f66?ixid=MnwxMjA3fDB8MHxzZWFyY2h8Mnx8cG9ydGZvbGlvJTIwYmFja2dyb3VuZHxlbnwwfHwwfHw%3D&ixlib=rb-1.2.1&w=1000&q=80',
+          link: '/welcome'
+        },
+        { 
+          id: 2, 
+          title: '新功能上线了', 
+          image: 'https://images.unsplash.com/photo-1516321318423-f06f85e504b3?ixid=MnwxMjA3fDB8MHxzZWFyY2h8MXx8cG9ydGZvbGlvJTIwYmFja2dyb3VuZHxlbnwwfHwwfHw%3D&ixlib=rb-1.2.1&w=1000&q=80',
+          link: '/new-features'
+        }
+      ]);
+      
+      // 模拟新闻数据
+      setNews([
+        {
+          id: 1,
+          title: '用户体验升级,新版本发布',
+          summary: '我们很高兴地宣布,新版本已经发布,带来了更好的用户体验和更多新功能。',
+          publish_date: '2023-05-01T08:30:00',
+          cover: 'https://images.unsplash.com/photo-1496171367470-9ed9a91ea931?ixid=MnwxMjA3fDB8MHxzZWFyY2h8MTB8fHRlY2h8ZW58MHx8MHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60',
+          category: '产品更新'
+        },
+        {
+          id: 2,
+          title: '新的数据分析功能上线',
+          summary: '新的数据分析功能让您更深入地了解您的业务数据,提供更好的决策支持。',
+          publish_date: '2023-04-25T14:15:00',
+          cover: 'https://images.unsplash.com/photo-1551288049-bebda4e38f71?ixid=MnwxMjA3fDB8MHxzZWFyY2h8MTJ8fGNoYXJ0fGVufDB8fDB8fA%3D%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60',
+          category: '功能介绍'
+        },
+        {
+          id: 3,
+          title: '如何提高工作效率的5个小技巧',
+          summary: '这篇文章分享了5个可以立即实施的小技巧,帮助您提高日常工作效率。',
+          publish_date: '2023-04-20T09:45:00',
+          category: '使用技巧'
+        }
+      ]);
+      
+      // 模拟通知数据
+      setNotices([
+        {
+          id: 1,
+          title: '系统维护通知',
+          content: '我们将于本周六凌晨2点至4点进行系统维护,期间系统可能会出现短暂不可用。',
+          created_at: '2023-05-02T10:00:00',
+          is_read: false
+        },
+        {
+          id: 2,
+          title: '您的账户信息已更新',
+          content: '您的账户信息已成功更新,如非本人操作,请及时联系客服。',
+          created_at: '2023-05-01T16:30:00',
+          is_read: true
+        }
+      ]);
+      
+      setLoading(false);
+    }, 800);
+  }, []);
+  
+  // 处理轮播图点击
+  const handleBannerClick = (link: string) => {
+    navigate(link);
+  };
+  
+  // 处理新闻点击
+  const handleNewsClick = (id: number) => {
+    navigate(`/news/${id}`);
+  };
+  
+  // 处理通知点击
+  const handleNoticeClick = (id: number) => {
+    navigate(`/notices/${id}`);
+  };
+  
+  return (
+    <div className="pb-16">
+      {/* 顶部用户信息 */}
+      <div className="bg-blue-600 text-white p-4">
+        <div className="flex items-center justify-between">
+          <div className="flex items-center space-x-3">
+            <div className="w-12 h-12 rounded-full bg-white/20 flex items-center justify-center">
+              {user?.avatar ? (
+                <img 
+                  src={user.avatar} 
+                  alt={user?.nickname || user?.username || '用户'}
+                  className="w-10 h-10 rounded-full object-cover"
+                />
+              ) : (
+                <UserIcon className="w-6 h-6" />
+              )}
+            </div>
+            <div>
+              <h2 className="text-lg font-medium">
+                {user ? `您好,${user.nickname || user.username}` : '您好,游客'}
+              </h2>
+              <p className="text-sm text-white/80">
+                {user ? '欢迎回来' : '请登录体验更多功能'}
+              </p>
+            </div>
+          </div>
+          
+          <div className="relative">
+            <BellIcon className="w-6 h-6" />
+            {notices.some(notice => !notice.is_read) && (
+              <span className="absolute top-0 right-0 w-2 h-2 bg-red-500 rounded-full"></span>
+            )}
+          </div>
+        </div>
+      </div>
+      
+      {/* 轮播图 */}
+      {!loading && banners.length > 0 && (
+        <div className="relative w-full h-40 overflow-hidden mt-2">
+          <div className="flex transition-transform duration-300" 
+               style={{ transform: `translateX(-${0 * 100}%)` }}>
+            {banners.map((banner) => (
+              <div 
+                key={banner.id}
+                className="w-full h-40 flex-shrink-0 relative"
+                onClick={() => handleBannerClick(banner.link)}
+              >
+                <img 
+                  src={banner.image} 
+                  alt={banner.title}
+                  className="w-full h-full object-cover"
+                />
+                <div className="absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black/70 to-transparent p-3">
+                  <h3 className="text-white font-medium">{banner.title}</h3>
+                </div>
+              </div>
+            ))}
+          </div>
+          
+          {/* 指示器 */}
+          <div className="absolute bottom-2 left-0 right-0 flex justify-center space-x-1">
+            {banners.map((_, index) => (
+              <span 
+                key={index}
+                className={`w-2 h-2 rounded-full ${index === 0 ? 'bg-white' : 'bg-white/50'}`}
+              ></span>
+            ))}
+          </div>
+        </div>
+      )}
+      
+      {/* 快捷入口 */}
+      <div className="grid grid-cols-4 gap-2 p-4 bg-white rounded-lg shadow mt-4 mx-2">
+        {[
+          { icon: <HomeIcon className="w-6 h-6" />, name: '首页', path: '/' },
+          { icon: <NewspaperIcon className="w-6 h-6" />, name: '资讯', path: '/news' },
+          { icon: <BellIcon className="w-6 h-6" />, name: '通知', path: '/notices' },
+          { icon: <UserIcon className="w-6 h-6" />, name: '我的', path: '/profile' }
+        ].map((item, index) => (
+          <div 
+            key={index}
+            className="flex flex-col items-center justify-center p-2"
+            onClick={() => navigate(item.path)}
+          >
+            <div className="w-12 h-12 rounded-full bg-blue-100 flex items-center justify-center text-blue-600 mb-1">
+              {item.icon}
+            </div>
+            <span className="text-sm">{item.name}</span>
+          </div>
+        ))}
+      </div>
+      
+      {/* 内容标签页 */}
+      <div className="mt-4 mx-2">
+        <div className="flex border-b border-gray-200">
+          <button
+            className={`flex-1 py-2 text-center ${activeTab === 'news' ? 'text-blue-600 border-b-2 border-blue-600 font-medium' : 'text-gray-500'}`}
+            onClick={() => setActiveTab('news')}
+          >
+            最新资讯
+          </button>
+          <button
+            className={`flex-1 py-2 text-center ${activeTab === 'notices' ? 'text-blue-600 border-b-2 border-blue-600 font-medium' : 'text-gray-500'}`}
+            onClick={() => setActiveTab('notices')}
+          >
+            通知公告
+          </button>
+        </div>
+        
+        <div className="mt-2">
+          {activeTab === 'news' ? (
+            loading ? (
+              <div className="flex justify-center p-4">
+                <div className="w-6 h-6 border-2 border-gray-200 border-t-blue-600 rounded-full animate-spin"></div>
+              </div>
+            ) : (
+              <div className="space-y-4">
+                {news.map((item) => (
+                  <div 
+                    key={item.id}
+                    className="bg-white p-3 rounded-lg shadow flex items-start space-x-3"
+                    onClick={() => handleNewsClick(item.id)}
+                  >
+                    {item.cover && (
+                      <img 
+                        src={item.cover} 
+                        alt={item.title}
+                        className="w-20 h-20 object-cover rounded-md flex-shrink-0"
+                      />
+                    )}
+                    <div className={item.cover ? '' : 'w-full'}>
+                      <h3 className="font-medium text-gray-900 line-clamp-2">{item.title}</h3>
+                      <p className="text-sm text-gray-500 mt-1 line-clamp-2">{item.summary}</p>
+                      <div className="flex justify-between items-center mt-2">
+                        <span className="text-xs text-blue-600 bg-blue-50 px-2 py-1 rounded-full">
+                          {item.category}
+                        </span>
+                        <span className="text-xs text-gray-400">
+                          {formatRelativeTime(item.publish_date)}
+                        </span>
+                      </div>
+                    </div>
+                  </div>
+                ))}
+              </div>
+            )
+          ) : (
+            loading ? (
+              <div className="flex justify-center p-4">
+                <div className="w-6 h-6 border-2 border-gray-200 border-t-blue-600 rounded-full animate-spin"></div>
+              </div>
+            ) : (
+              <div className="space-y-3">
+                {notices.map((item) => (
+                  <div 
+                    key={item.id}
+                    className="bg-white p-3 rounded-lg shadow"
+                    onClick={() => handleNoticeClick(item.id)}
+                  >
+                    <div className="flex justify-between items-start">
+                      <h3 className={`font-medium ${item.is_read ? 'text-gray-700' : 'text-blue-600'}`}>
+                        {!item.is_read && (
+                          <span className="inline-block w-2 h-2 bg-blue-600 rounded-full mr-2"></span>
+                        )}
+                        {item.title}
+                      </h3>
+                      <span className="text-xs text-gray-400 mt-1">
+                        {formatRelativeTime(item.created_at)}
+                      </span>
+                    </div>
+                    <p className="text-sm text-gray-500 mt-2 line-clamp-2">{item.content}</p>
+                  </div>
+                ))}
+              </div>
+            )
+          )}
+        </div>
+      </div>
+      
+      {/* 底部导航 */}
+      <div className="fixed bottom-0 left-0 right-0 bg-white border-t border-gray-200 flex justify-around py-2">
+        {[
+          { icon: <HomeIcon className="w-6 h-6" />, name: '首页', path: '/' },
+          { icon: <NewspaperIcon className="w-6 h-6" />, name: '资讯', path: '/news' },
+          { icon: <BellIcon className="w-6 h-6" />, name: '通知', path: '/notices' },
+          { icon: <UserIcon className="w-6 h-6" />, name: '我的', path: '/profile' }
+        ].map((item, index) => (
+          <div 
+            key={index}
+            className="flex flex-col items-center"
+            onClick={() => navigate(item.path)}
+          >
+            <div className={`${location.pathname === item.path ? 'text-blue-600' : 'text-gray-500'}`}>
+              {item.icon}
+            </div>
+            <span className={`text-xs mt-1 ${location.pathname === item.path ? 'text-blue-600' : 'text-gray-500'}`}>
+              {item.name}
+            </span>
+          </div>
+        ))}
+      </div>
+    </div>
+  );
+};
+
+export default HomePage; 

+ 144 - 0
client/mobile/pages/login.tsx

@@ -0,0 +1,144 @@
+import React, { useState } from 'react';
+import { useNavigate } from 'react-router';
+import { ArrowRightIcon, LockClosedIcon, UserIcon } from '@heroicons/react/24/outline';
+import { useAuth } from '../hooks.tsx';
+import { handleApiError } from '../utils.ts';
+
+// 登录页面组件
+const LoginPage: React.FC = () => {
+  const { login } = useAuth();
+  const navigate = useNavigate();
+  const [username, setUsername] = useState('');
+  const [password, setPassword] = useState('');
+  const [loading, setLoading] = useState(false);
+  const [error, setError] = useState<string | null>(null);
+
+  const handleLogin = async (e: React.FormEvent) => {
+    e.preventDefault();
+    
+    if (!username.trim() || !password.trim()) {
+      setError('用户名和密码不能为空');
+      return;
+    }
+    
+    setLoading(true);
+    setError(null);
+    
+    try {
+      await login(username, password);
+      navigate('/');
+    } catch (err) {
+      setError(handleApiError(err));
+    } finally {
+      setLoading(false);
+    }
+  };
+
+  return (
+    <div className="min-h-screen flex flex-col bg-gradient-to-b from-blue-500 to-blue-700 p-6">
+      {/* 顶部Logo和标题 */}
+      <div className="flex flex-col items-center justify-center mt-10 mb-8">
+        <div className="w-20 h-20 bg-white rounded-2xl flex items-center justify-center shadow-lg mb-4">
+          <svg className="w-12 h-12 text-blue-600" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+            <path d="M12 2L2 7L12 12L22 7L12 2Z" fill="currentColor" />
+            <path d="M2 17L12 22L22 17" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
+            <path d="M2 12L12 17L22 12" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
+          </svg>
+        </div>
+        <h1 className="text-3xl font-bold text-white">
+          {window.CONFIG?.APP_NAME || '移动应用'}
+        </h1>
+        <p className="text-blue-100 mt-2">登录您的账户</p>
+      </div>
+
+      {/* 登录表单 */}
+      <div className="bg-white rounded-xl shadow-xl p-6 w-full">
+        {error && (
+          <div className="bg-red-50 text-red-700 p-3 rounded-lg mb-4 text-sm">
+            {error}
+          </div>
+        )}
+        
+        <form onSubmit={handleLogin}>
+          <div className="mb-4">
+            <label className="block text-gray-700 text-sm font-medium mb-2" htmlFor="username">
+              用户名
+            </label>
+            <div className="relative">
+              <div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
+                <UserIcon className="h-5 w-5 text-gray-400" />
+              </div>
+              <input
+                id="username"
+                type="text"
+                value={username}
+                onChange={(e) => setUsername(e.target.value)}
+                className="w-full pl-10 pr-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
+                placeholder="请输入用户名"
+              />
+            </div>
+          </div>
+          
+          <div className="mb-6">
+            <label className="block text-gray-700 text-sm font-medium mb-2" htmlFor="password">
+              密码
+            </label>
+            <div className="relative">
+              <div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
+                <LockClosedIcon className="h-5 w-5 text-gray-400" />
+              </div>
+              <input
+                id="password"
+                type="password"
+                value={password}
+                onChange={(e) => setPassword(e.target.value)}
+                className="w-full pl-10 pr-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
+                placeholder="请输入密码"
+              />
+            </div>
+          </div>
+          
+          <button
+            type="submit"
+            disabled={loading}
+            className="w-full bg-blue-600 hover:bg-blue-700 text-white font-medium py-2 px-4 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 flex items-center justify-center"
+          >
+            {loading ? (
+              <svg className="animate-spin -ml-1 mr-2 h-5 w-5 text-white" fill="none" viewBox="0 0 24 24">
+                <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
+                <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
+              </svg>
+            ) : (
+              <ArrowRightIcon className="h-5 w-5 mr-2" />
+            )}
+            {loading ? '登录中...' : '登录'}
+          </button>
+        </form>
+        
+        <div className="mt-6 flex items-center justify-between">
+          <button
+            type="button"
+            className="text-sm text-blue-600 hover:text-blue-700"
+            onClick={() => navigate('/register')}
+          >
+            注册账号
+          </button>
+          <button
+            type="button"
+            className="text-sm text-blue-600 hover:text-blue-700"
+          >
+            忘记密码?
+          </button>
+        </div>
+      </div>
+      
+      {/* 底部文本 */}
+      <div className="mt-auto pt-8 text-center text-blue-100 text-sm">
+        &copy; {new Date().getFullYear()} {window.CONFIG?.APP_NAME || '移动应用'} 
+        <p className="mt-1">保留所有权利</p>
+      </div>
+    </div>
+  );
+};
+
+export default LoginPage; 

+ 169 - 0
client/mobile/utils.ts

@@ -0,0 +1,169 @@
+import dayjs from 'dayjs';
+import { EnableStatus, DeleteStatus, AuditStatus } from '../share/types.ts';
+
+// 日期格式化
+export const formatDate = (date: string | Date, format = 'YYYY-MM-DD HH:mm:ss'): string => {
+  if (!date) return '-';
+  return dayjs(date).format(format);
+};
+
+// 格式化时间为相对时间(如:3小时前)
+export const formatRelativeTime = (date: string | Date): string => {
+  if (!date) return '-';
+  const now = dayjs();
+  const dateObj = dayjs(date);
+  const diffInSeconds = now.diff(dateObj, 'second');
+  
+  if (diffInSeconds < 60) {
+    return `${diffInSeconds}秒前`;
+  } else if (diffInSeconds < 3600) {
+    return `${Math.floor(diffInSeconds / 60)}分钟前`;
+  } else if (diffInSeconds < 86400) {
+    return `${Math.floor(diffInSeconds / 3600)}小时前`;
+  } else if (diffInSeconds < 2592000) {
+    return `${Math.floor(diffInSeconds / 86400)}天前`;
+  } else {
+    return formatDate(date, 'YYYY-MM-DD');
+  }
+};
+
+// 获取枚举的选项(用于下拉菜单等)
+export const getEnumOptions = (enumObj: Record<string | number, string | number>) => {
+  return Object.entries(enumObj)
+    .filter(([key]) => !isNaN(Number(key))) // 过滤掉映射对象中的字符串键
+    .map(([value, label]) => ({
+      value: Number(value),
+      label: String(label)
+    }));
+};
+
+// 获取启用状态选项
+export const getEnableStatusOptions = () => {
+  return [
+    { value: EnableStatus.ENABLED, label: '启用' },
+    { value: EnableStatus.DISABLED, label: '禁用' }
+  ];
+};
+
+// 获取删除状态选项
+export const getDeleteStatusOptions = () => {
+  return [
+    { value: DeleteStatus.NOT_DELETED, label: '未删除' },
+    { value: DeleteStatus.DELETED, label: '已删除' }
+  ];
+};
+
+// 获取审核状态选项
+export const getAuditStatusOptions = () => {
+  return [
+    { value: AuditStatus.PENDING, label: '待审核' },
+    { value: AuditStatus.APPROVED, label: '已通过' },
+    { value: AuditStatus.REJECTED, label: '已拒绝' }
+  ];
+};
+
+// 格式化文件大小
+export const formatFileSize = (bytes: number): string => {
+  if (bytes === 0) return '0 B';
+  
+  const k = 1024;
+  const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
+  const i = Math.floor(Math.log(bytes) / Math.log(k));
+  
+  return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
+};
+
+// 处理API错误
+export const handleApiError = (error: any): string => {
+  if (error.response) {
+    // 服务器响应错误
+    const status = error.response.status;
+    const data = error.response.data;
+    
+    if (status === 401) {
+      return '您的登录已过期,请重新登录';
+    } else if (status === 403) {
+      return '您没有权限执行此操作';
+    } else if (status === 404) {
+      return '请求的资源不存在';
+    } else if (status === 422) {
+      // 表单验证错误
+      return data.message || '输入数据无效';
+    } else {
+      return data.message || `服务器错误 (${status})`;
+    }
+  } else if (error.request) {
+    // 请求发送成功但没有收到响应
+    return '网络连接错误,请检查您的网络连接';
+  } else {
+    // 请求设置错误
+    return '应用程序错误,请稍后再试';
+  }
+};
+
+// 复制文本到剪贴板
+export const copyToClipboard = async (text: string): Promise<boolean> => {
+  try {
+    await navigator.clipboard.writeText(text);
+    return true;
+  } catch (error) {
+    console.error('复制到剪贴板失败:', error);
+    return false;
+  }
+};
+
+// 防抖函数
+export const debounce = <T extends (...args: any[]) => any>(
+  func: T,
+  wait: number
+): ((...args: Parameters<T>) => void) => {
+  let timeout: number | null = null;
+  
+  return (...args: Parameters<T>) => {
+    if (timeout) {
+      clearTimeout(timeout);
+    }
+    
+    timeout = setTimeout(() => {
+      func(...args);
+    }, wait) as unknown as number;
+  };
+};
+
+// 生成随机颜色
+export const getRandomColor = (): string => {
+  const letters = '0123456789ABCDEF';
+  let color = '#';
+  for (let i = 0; i < 6; i++) {
+    color += letters[Math.floor(Math.random() * 16)];
+  }
+  return color;
+};
+
+// 获取本地存储值,带过期检查
+export const getLocalStorageWithExpiry = (key: string) => {
+  const itemStr = localStorage.getItem(key);
+  
+  if (!itemStr) return null;
+  
+  const item = JSON.parse(itemStr);
+  const now = new Date();
+  
+  if (item.expiry && now.getTime() > item.expiry) {
+    localStorage.removeItem(key);
+    return null;
+  }
+  
+  return item.value;
+};
+
+// 设置本地存储值,带过期时间
+export const setLocalStorageWithExpiry = (key: string, value: any, expiryHours = 24) => {
+  const now = new Date();
+  const item = {
+    value: value,
+    expiry: now.getTime() + expiryHours * 60 * 60 * 1000
+  };
+  
+  localStorage.setItem(key, JSON.stringify(item));
+};

+ 100 - 1
asset/share/types.ts → client/share/types.ts

@@ -308,4 +308,103 @@ export enum AllowedFileType {
 }
 
 // 允许的文件类型列表(用于系统设置)
-export const ALLOWED_FILE_TYPES = Object.values(AllowedFileType).join(',');
+export const ALLOWED_FILE_TYPES = Object.values(AllowedFileType).join(',');
+
+// 文件库接口
+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 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;
+}

+ 0 - 0
README.md → server/README.md


+ 9 - 9
app.tsx → server/app.tsx

@@ -11,8 +11,8 @@ 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 type { SystemSettingRecord, GlobalConfig } from '../client/share/types.ts';
+import { SystemSettingKey, OssType, MapMode } from '../client/share/types.ts';
 
 import {
   createKnowInfoRoutes,
@@ -388,7 +388,7 @@ export default function({ apiClient, app, moduleDir }: ModuleParams) {
             <title>{title}</title>
             
             {isProd ? (
-              <script type="module" src={scriptConfig.prodSrc || `/asset_dist/${scriptConfig.prodPath}`}></script>
+              <script type="module" src={scriptConfig.prodSrc || `/client_dist/${scriptConfig.prodPath}`}></script>
             ) : (
               <script src={scriptConfig.src} href={scriptConfig.href} deno-json={scriptConfig.denoJson} refresh={scriptConfig.refresh}></script>
             )}
@@ -425,16 +425,16 @@ export default function({ apiClient, app, moduleDir }: ModuleParams) {
   // 后台管理路由
   honoApp.get('/admin', createHtmlWithConfig({
     src: "https://esm.d8d.fun/xb", 
-    href: "/asset/admin/web_app.tsx", 
-    denoJson: "/asset/admin/deno.json", 
+    href: "/client/admin/web_app.tsx", 
+    denoJson: "/client/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", 
+    href: "/client/admin/web_app.tsx", 
+    denoJson: "/client/admin/deno.json", 
     refresh: true, 
     prodPath: "admin/web_app.js"
   }, GLOBAL_CONFIG.APP_NAME))
@@ -462,10 +462,10 @@ export default function({ apiClient, app, moduleDir }: ModuleParams) {
   })
   
   // 静态资源路由
-  honoApp.get('/asset/*', staticRoutes)
+  honoApp.get('/client/*', staticRoutes)
   honoApp.get('/amap/*', staticRoutes)
   honoApp.get('/tailwindcss@3.4.16/*', staticRoutes)
-  honoApp.get('/asset_dist/*', staticRoutes)
+  honoApp.get('/client_dist/*', staticRoutes)
   
   return honoApp
 }

+ 0 - 0
deno.json → server/deno.json


+ 0 - 0
deno.lock → server/deno.lock


+ 3 - 8
migrations.ts → server/migrations.ts

@@ -1,17 +1,12 @@
 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,
+  EnableStatus, DeleteStatus,
+  AuditStatus, ThemeMode, FontSize, CompactMode,
   SystemSettingKey,
   SystemSettingGroup,
   ALLOWED_FILE_TYPES,
-} from './asset/share/types.ts';
+} from '../client/share/types.ts';
 
 // 定义用户表迁移
 const createUsersTable: MigrationLiveDefinition = {

+ 0 - 0
routes_auth.ts → server/routes_auth.ts


+ 1 - 12
routes_charts.ts → server/routes_charts.ts

@@ -1,19 +1,8 @@
 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";
+} from "../client/share/types.ts";
 
 import type { Variables, WithAuth } from "./app.tsx";
 

+ 0 - 14
routes_maps.ts → server/routes_maps.ts

@@ -1,19 +1,5 @@
 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";
 

+ 2 - 2
routes_sys.ts → server/routes_sys.ts

@@ -7,7 +7,7 @@ import type {
   ThemeSettings,
   SystemSetting,
   SystemSettingGroupData,
-} from "./asset/share/types.ts";
+} from "../client/share/types.ts";
 
 import {
   EnableStatus,
@@ -15,7 +15,7 @@ import {
   ThemeMode,
   FontSize,
   CompactMode,
-} from "./asset/share/types.ts";
+} from "../client/share/types.ts";
 
 import type { Variables, WithAuth } from "./app.tsx";
 

+ 0 - 0
routes_users.ts → server/routes_users.ts