const { createApp } = Vue;
const DEFAULT_LOCAL_STORAGE_QUOTA_BYTES = 1024 * 1024 * 1024; // 1GB
const DEFAULT_OSS_STORAGE_QUOTA_BYTES = 1024 * 1024 * 1024; // 1GB

createApp({
  data() {
    // 预先确定管理员标签页，避免刷新时状态丢失导致闪烁
    const initialAdminTab = (() => {
      const saved = localStorage.getItem('adminTab');
      return (saved && ['overview', 'settings', 'monitor', 'users'].includes(saved)) ? saved : 'overview';
    })();

    return {
      // API配置
      // API配置 - 通过nginx代理访问
      apiBase: window.location.protocol + '//' + window.location.host,

      // 应用状态
      appReady: false,  // 应用是否初始化完成（防止UI闪烁）

      // 用户状态
      isLoggedIn: false,
      user: null,
      token: null,  // 仅用于内部状态跟踪，实际认证通过 HttpOnly Cookie
      tokenRefreshTimer: null,

      // 视图状态
      currentView: 'files',
      isLogin: true,
      fileViewMode: 'grid', // 文件显示模式: grid 大图标, list 列表
      shareViewMode: 'list', // 分享显示模式: grid 大图标, list 列表
      debugMode: false, // 调试模式（管理员可切换）
      adminTab: initialAdminTab, // 管理员页面当前标签：overview, settings, monitor, users

      // 表单数据
      loginForm: {
        username: '',
        password: '',
        captcha: ''
      },
      registerForm: {
        username: '',
        email: '',
        password: '',
        captcha: ''
      },
      registerCaptchaUrl: '',

      // 验证码相关
      showCaptcha: false,
      captchaUrl: '',

      // OSS配置表单
      ossConfigForm: {
        oss_provider: 'aliyun',
        oss_region: '',
        oss_access_key_id: '',
        oss_access_key_secret: '',
        oss_bucket: '',
        oss_endpoint: ''
      },
      showOssConfigModal: false,
      ossConfigSaving: false, // OSS 配置保存中状态
      ossConfigTesting: false, // OSS 配置测试中状态

      // 修改密码表单
      changePasswordForm: {
        current_password: '',
        new_password: ''
      },
      // 用户名修改表单
      usernameForm: {
        newUsername: ''
      },
      // 用户资料表单
      profileForm: {
        email: ''
      },
      // 管理员资料表单
      adminProfileForm: {
        username: ''
      },
      // 分享表单（通用）
      shareForm: {
        path: '',
        password: '',
        expiryDays: null
      },
      currentPath: '/',
      files: [],
      loading: false,

      // 分享管理
      shares: [],
      directLinks: [],
      directLinksLoading: false,
      showDeleteConfirmModal: false,
      deleteConfirm: {
        type: '', // share | direct_link
        id: null,
        title: '',
        message: '',
        loading: false
      },
      showShareFileModal: false,
      creatingShare: false, // 创建分享中状态
      shareFileForm: {
        fileName: "",
        filePath: "",
        isDirectory: false,  // 新增：标记是否为文件夹
        enablePassword: false,
        password: "",
        expiryType: "never",
        customDays: 7,
        enableAdvancedSecurity: false,
        maxDownloadsEnabled: false,
        maxDownloads: 10,
        ipWhitelist: '',
        deviceLimit: 'all',
        accessTimeEnabled: false,
        accessTimeStart: '09:00',
        accessTimeEnd: '23:00'
      },
      shareResult: null,
      showDirectLinkModal: false,
      creatingDirectLink: false,
      directLinkForm: {
        fileName: '',
        filePath: '',
        expiryType: 'never',
        customDays: 7
      },
      directLinkResult: null,
      shareFilters: {
        keyword: '',
        type: 'all', // all/file/directory/all_files
        status: 'all', // all/active/expiring/expired/protected/public
        sort: 'created_desc' // created_desc/created_asc/views_desc/downloads_desc/expire_asc
      },

      // 文件重命名
      showRenameModal: false,
      renameForm: {
        oldName: "",
        newName: "",
        path: ""
      },

      // 创建文件夹
      showCreateFolderModal: false,
      creatingFolder: false, // 创建文件夹中状态
      createFolderForm: {
        folderName: ""
      },

      // 文件夹详情
      showFolderInfoModal: false,
      folderInfo: null,

      // 上传
      showUploadModal: false,
      uploadProgress: 0,
      uploadedBytes: 0,
      totalBytes: 0,
      uploadingFileName: '',
      isDragging: false,
      modalMouseDownTarget: null, // 模态框鼠标按下的目标

      // 全局搜索（文件页）
      globalSearchKeyword: '',
      globalSearchType: 'all', // all/file/directory
      globalSearchLoading: false,
      globalSearchError: '',
      globalSearchResults: [],
      globalSearchMeta: null,
      globalSearchVisible: false,

      // 管理员
      adminUsers: [],
      adminUsersLoading: false,
      adminUsersPage: 1,
      adminUsersPageSize: 20,
      adminUsersTotalCount: 0,
      adminUsersTotalPages: 1,
      adminUsersGlobalCount: 0,
      adminUserStats: {
        active: 0,
        banned: 0,
        unverified: 0,
        download_blocked: 0
      },
      adminUserFilters: {
        keyword: '',
        role: 'all', // all/admin/user
        status: 'all', // all/active/banned/unverified/download_blocked
        storage: 'all', // all/local/oss/local_only/oss_only/user_choice
        sort: 'created_desc' // created_desc/created_asc/username_asc/username_desc/storage_usage_desc/download_usage_desc
      },
      showResetPwdModal: false,
      resetPwdUser: {},
      newPassword: '',

      // 文件审查
      showFileInspectionModal: false,
      inspectionUser: null,
      inspectionFiles: [],
      inspectionPath: '/',
      inspectionLoading: false,
      inspectionViewMode: 'grid', // 文件审查显示模式: grid 大图标, list 列表

      // 忘记密码
      showForgotPasswordModal: false,
      forgotPasswordForm: {
        email: '',
        captcha: ''
      },
      forgotPasswordCaptchaUrl: '',
      showResetPasswordModal: false,
      resetPasswordForm: {
        token: '',
        new_password: ''
      },
      showResendVerify: false,
      resendVerifyEmail: '',
      resendVerifyCaptcha: '',
      resendVerifyCaptchaUrl: '',

      // 加载状态
      loginLoading: false,       // 登录中
      registerLoading: false,    // 注册中
      passwordChanging: false,   // 修改密码中
      usernameChanging: false,   // 修改用户名中
      passwordResetting: false,  // 重置密码中
      resendingVerify: false,    // 重发验证邮件中

      // 系统设置
      systemSettings: {
        maxUploadSizeMB: 100,
        downloadSecurity: {
          enabled: true,
          same_ip_same_file: {
            enabled: true,
            limit_5m: 3,
            limit_1h: 10,
            limit_1d: 20
          },
          same_ip_same_user: {
            enabled: false,
            limit_1h: 80,
            limit_1d: 300
          },
          same_ip_same_file_min_interval: {
            enabled: false,
            seconds: 2
          }
        },
        smtp: {
          host: '',
          port: 465,
          secure: true,
          user: '',
          from: '',
          password: '',
          has_password: false
        }
      },
      onlineDevices: {
        loading: false,
        kickingSessionId: '',
        items: [],
        error: '',
        lastLoadedAt: ''
      },
      desktopClientDownloading: false,
      desktopClientDownloadUrl: '',

      // 健康检测
      healthCheck: {
        loading: initialAdminTab === 'monitor',
        lastCheck: null,
        overallStatus: null, // healthy, warning, critical
        summary: { total: 0, pass: 0, warning: 0, fail: 0, info: 0 },
        checks: []
      },

      // 系统日志
      systemLogs: {
        loading: initialAdminTab === 'monitor',
        logs: [],
        total: 0,
        page: 1,
        pageSize: 30,
        totalPages: 0,
        filters: {
          level: '',
          category: '',
          keyword: ''
        }
      },

      // 下载预扣运维面板（管理员-监控）
      reservationMonitor: {
        loading: false,
        cleaning: false,
        rows: [],
        summary: null,
        page: 1,
        pageSize: 20,
        total: 0,
        totalPages: 1,
        filters: {
          status: 'pending',
          keyword: '',
          userId: ''
        }
      },

      // 监控页整体加载遮罩（避免刷新时闪一下空态）
      monitorTabLoading: initialAdminTab === 'monitor',

      // Toast通知
      toasts: [],
      toastIdCounter: 0,

      // 上传限制（字节），默认10GB
      maxUploadSize: 10737418240,

      // 提示信息
      errorMessage: '',
      successMessage: '',
      verifyMessage: '',

      // 存储相关
      storageType: 'oss', // 当前使用的存储类型
      storagePermission: 'oss_only', // 存储权限
      localQuota: 0, // 本地存储配额（字节）
      localUsed: 0, // 本地存储已使用（字节）


      // 右键菜单
      showContextMenu: false,
      contextMenuX: 0,
      contextMenuY: 0,
      contextMenuFile: null,
      showMobileFileActionSheet: false,
      mobileActionFile: null,

      // 长按检测
      longPressTimer: null,
      longPressStartX: 0,
      longPressStartY: 0,
      longPressFile: null,
      longPressTriggered: false,

      // 媒体预览
      showImageViewer: false,
      showVideoPlayer: false,
      showAudioPlayer: false,
      currentMediaUrl: '',
      currentMediaName: '',
      currentMediaType: '', // 'image', 'video', 'audio'
      mediaPreviewLoading: false,
      mediaPreviewCache: {}, // { [filePath]: { url, expiresAt } }
      thumbnailLoadErrors: {}, // 缩略图加载失败标记（按文件路径）
      longPressDuration: 420, // 长按时间（毫秒）
      // 管理员编辑用户存储权限
      showEditStorageModal: false,
      editStorageForm: {
        userId: null,
        username: '',
        storage_permission: 'oss_only',
        local_storage_quota_value: 1, // 本地配额数值
        quota_unit: 'GB', // 本地配额单位：MB 或 GB
        oss_storage_quota_value: 1, // OSS配额数值（默认1GB）
        oss_quota_unit: 'GB', // OSS配额单位：MB / GB / TB
        oss_quota_unlimited: false, // 兼容旧数据字段（当前固定为有限配额）
        download_traffic_quota_value: 1, // 下载流量配额数值
        download_quota_unit: 'GB', // 下载流量单位：MB / GB / TB
        download_quota_unlimited: false, // 下载流量：true=不限（后端值为 -1）
        download_traffic_used: 0, // 下载流量已使用（字节）
        download_quota_operation: 'set', // set/increase/decrease
        download_quota_adjust_value: 1, // 增减额度数值
        download_quota_adjust_unit: 'GB', // 增减额度单位
        download_quota_expires_at: '', // 到期时间（datetime-local）
        download_quota_reset_cycle: 'none', // none/daily/weekly/monthly
        reset_download_used_now: false // 保存时立即重置已用流量
      },

      // 服务器存储统计
      serverStorageStats: {
        totalDisk: 0,
        usedDisk: 0,
        availableDisk: 0,
        totalUserQuotas: 0,
        totalUserUsed: 0,
        totalUsers: 0
      },

      // 定期检查用户配置更新的定时器
      profileCheckInterval: null,

      // 存储切换状态
      storageSwitching: false,
      storageSwitchTarget: null,
      suppressStorageToast: false,
      profileInitialized: false,

      // OSS配置引导弹窗
      showOssGuideModal: false,

      // OSS空间使用统计
      ossUsage: null, // { totalSize, totalSizeFormatted, fileCount, dirCount }
      ossUsageLoading: false,
      ossUsageError: null,

      // 下载流量报表
      downloadTrafficReport: {
        days: 30,
        loading: false,
        error: null,
        quota: null,
        daily: [],
        summary: null,
        lastUpdatedAt: null
      },

      // 主题设置
      currentTheme: 'dark', // 当前生效的主题: 'dark' 或 'light'
      globalTheme: 'dark', // 全局默认主题（管理员设置）
      userThemePreference: null // 用户主题偏好: 'dark', 'light', 或 null(跟随全局)
    };
  },

  computed: {
    pathParts() {
      return this.currentPath.split('/').filter(p => p !== '');
    },

    // 格式化配额显示
    localQuotaFormatted() {
      return this.formatBytes(this.localQuota);
    },

    localUsedFormatted() {
      return this.formatBytes(this.localUsed);
    },

    // 配额使用百分比
    quotaPercentage() {
      if (this.localQuota === 0) return 0;
      return Math.round((this.localUsed / this.localQuota) * 100);
    },

    // OSS 配额相关（文件页展示）
    ossQuotaBytes() {
      const quota = Number(this.user?.oss_storage_quota || DEFAULT_OSS_STORAGE_QUOTA_BYTES);
      if (!Number.isFinite(quota) || quota <= 0) {
        return DEFAULT_OSS_STORAGE_QUOTA_BYTES;
      }
      return quota;
    },

    ossUsedBytes() {
      const usageFromStats = Number(this.ossUsage?.totalSize);
      if (Number.isFinite(usageFromStats) && usageFromStats >= 0) {
        return usageFromStats;
      }

      const usageFromUser = Number(this.user?.storage_used || 0);
      if (Number.isFinite(usageFromUser) && usageFromUser >= 0) {
        return usageFromUser;
      }

      return 0;
    },

    ossQuotaFormatted() {
      return this.formatBytes(this.ossQuotaBytes);
    },

    ossUsedFormatted() {
      return this.formatBytes(this.ossUsedBytes);
    },

    ossQuotaPercentage() {
      if (this.ossQuotaBytes <= 0) return 0;
      return Math.min(100, Math.round((this.ossUsedBytes / this.ossQuotaBytes) * 100));
    },

    downloadTrafficQuotaBytes() {
      const reportQuota = Number(this.downloadTrafficReport?.quota?.quota);
      if (Number.isFinite(reportQuota) && reportQuota >= 0) {
        return reportQuota;
      }
      const userQuota = Number(this.user?.download_traffic_quota || 0);
      return Number.isFinite(userQuota) && userQuota > 0 ? Math.floor(userQuota) : 0;
    },

    downloadTrafficUsedBytes() {
      const reportUsed = Number(this.downloadTrafficReport?.quota?.used);
      if (Number.isFinite(reportUsed) && reportUsed >= 0) {
        return Math.floor(reportUsed);
      }
      const userUsed = Number(this.user?.download_traffic_used || 0);
      return Number.isFinite(userUsed) && userUsed > 0 ? Math.floor(userUsed) : 0;
    },

    downloadTrafficIsUnlimited() {
      if (this.downloadTrafficReport?.quota?.is_unlimited === true) {
        return true;
      }
      const userQuota = Number(this.user?.download_traffic_quota);
      return Number.isFinite(userQuota) && userQuota < 0;
    },

    downloadTrafficRemainingBytes() {
      if (this.downloadTrafficIsUnlimited) {
        return null;
      }

      const reportRemaining = Number(this.downloadTrafficReport?.quota?.remaining);
      if (Number.isFinite(reportRemaining) && reportRemaining >= 0) {
        return Math.floor(reportRemaining);
      }

      return Math.max(0, this.downloadTrafficQuotaBytes - this.downloadTrafficUsedBytes);
    },

    downloadTrafficUsagePercentage() {
      if (this.downloadTrafficIsUnlimited) {
        return 0;
      }

      const reportPercentage = Number(this.downloadTrafficReport?.quota?.usage_percentage);
      if (Number.isFinite(reportPercentage) && reportPercentage >= 0) {
        return Math.min(100, Math.round(reportPercentage));
      }

      if (this.downloadTrafficQuotaBytes <= 0) {
        return 100;
      }

      return Math.min(100, Math.round((this.downloadTrafficUsedBytes / this.downloadTrafficQuotaBytes) * 100));
    },

    downloadTrafficResetCycle() {
      return this.downloadTrafficReport?.quota?.reset_cycle
        || this.user?.download_traffic_reset_cycle
        || 'none';
    },

    downloadTrafficExpiresAt() {
      return this.downloadTrafficReport?.quota?.expires_at
        || this.user?.download_traffic_quota_expires_at
        || null;
    },

    downloadTrafficDailyRowsDesc() {
      const rows = Array.isArray(this.downloadTrafficReport?.daily)
        ? this.downloadTrafficReport.daily
        : [];
      return [...rows].reverse();
    },

    // 存储类型显示文本
    storageTypeText() {
      return this.storageType === 'local' ? '本地存储' : 'OSS存储';
    },

    // 文件统计信息（用于文件页工具栏）
    fileStats() {
      const list = Array.isArray(this.files) ? this.files : [];
      let directoryCount = 0;
      let fileCount = 0;
      let totalFileBytes = 0;

      for (const item of list) {
        if (item?.isDirectory) {
          directoryCount += 1;
          continue;
        }

        fileCount += 1;
        const currentSize = Number(item?.size || 0);
        if (Number.isFinite(currentSize) && currentSize > 0) {
          totalFileBytes += currentSize;
        }
      }

      return {
        totalCount: list.length,
        directoryCount,
        fileCount,
        totalFileBytes
      };
    },

    currentPathLabel() {
      if (!this.currentPath || this.currentPath === '/') {
        return '根目录';
      }
      return this.currentPath;
    },

    // 分享筛选+排序后的列表
    filteredShares() {
      let list = [...this.shares];
      const keyword = this.shareFilters.keyword.trim().toLowerCase();

      if (keyword) {
        list = list.filter(s =>
          (s.share_path || '').toLowerCase().includes(keyword) ||
          (s.share_code || '').toLowerCase().includes(keyword) ||
          (s.share_url || '').toLowerCase().includes(keyword)
        );
      }

      if (this.shareFilters.type !== 'all') {
        const selectedType = this.shareFilters.type;
        list = list.filter(s => {
          if (selectedType === 'all_files') {
            return this.isShareAllFiles(s);
          }
          if (selectedType === 'directory') {
            return (s.share_type || 'file') === 'directory' && !this.isShareAllFiles(s);
          }
          return (s.share_type || 'file') === selectedType;
        });
      }

      if (this.shareFilters.status !== 'all') {
        list = list.filter(s => {
          if (this.shareFilters.status === 'expired') return this.isExpired(s.expires_at);
          if (this.shareFilters.status === 'expiring') return this.isExpiringSoon(s.expires_at) && !this.isExpired(s.expires_at);
          if (this.shareFilters.status === 'active') return !this.isExpired(s.expires_at);
          if (this.shareFilters.status === 'protected') return this.hasSharePassword(s);
          if (this.shareFilters.status === 'public') return !this.hasSharePassword(s);
          return true;
        });
      }

      list.sort((a, b) => {
        const getTime = (s) => {
          const date = this.parseDateValue(s?.created_at);
          return date ? date.getTime() : 0;
        };
        const getExpire = (s) => {
          const date = this.parseDateValue(s?.expires_at);
          return date ? date.getTime() : Number.MAX_SAFE_INTEGER;
        };

        switch (this.shareFilters.sort) {
          case 'created_asc':
            return getTime(a) - getTime(b);
          case 'views_desc':
            return (b.view_count || 0) - (a.view_count || 0);
          case 'downloads_desc':
            return (b.download_count || 0) - (a.download_count || 0);
          case 'expire_asc':
            return getExpire(a) - getExpire(b);
          default:
            return getTime(b) - getTime(a); // created_desc
        }
      });

      return list;
    },

    filteredDirectLinks() {
      let list = [...this.directLinks];
      const keyword = this.shareFilters.keyword.trim().toLowerCase();

      if (keyword) {
        list = list.filter((link) =>
          (link.file_path || '').toLowerCase().includes(keyword)
          || (link.file_name || '').toLowerCase().includes(keyword)
          || (link.link_code || '').toLowerCase().includes(keyword)
          || (link.direct_url || '').toLowerCase().includes(keyword)
        );
      }

      list.sort((a, b) => {
        const da = this.parseDateValue(a?.created_at);
        const db = this.parseDateValue(b?.created_at);
        const ta = da ? da.getTime() : 0;
        const tb = db ? db.getTime() : 0;
        return tb - ta;
      });

      return list;
    },

    adminUsersFilteredCount() {
      return Math.max(0, Number(this.adminUsersTotalCount) || 0);
    },

    adminUsersCurrentPage() {
      const page = Math.max(1, Number(this.adminUsersPage) || 1);
      return Math.min(page, this.adminUsersTotalPages);
    },

    adminUsersPageStart() {
      if (this.adminUsersFilteredCount <= 0) return 0;
      const size = Math.max(1, Number(this.adminUsersPageSize) || 20);
      return (this.adminUsersCurrentPage - 1) * size + 1;
    },

    adminUsersPageEnd() {
      if (this.adminUsersFilteredCount <= 0) return 0;
      const size = Math.max(1, Number(this.adminUsersPageSize) || 20);
      return Math.min(this.adminUsersCurrentPage * size, this.adminUsersFilteredCount);
    }
  },

  methods: {
    // ========== 工具函数 ==========
    // 防抖函数 - 避免频繁调用
    debounce(fn, delay) {
      let timer = null;
      return function(...args) {
        if (timer) clearTimeout(timer);
        timer = setTimeout(() => fn.apply(this, args), delay);
      };
    },

    // 创建防抖版本的 loadUserProfile（延迟2秒，避免频繁请求）
    debouncedLoadUserProfile() {
      if (!this._debouncedLoadUserProfile) {
        this._debouncedLoadUserProfile = this.debounce(() => {
          this.loadUserProfile();
        }, 2000);
      }
      this._debouncedLoadUserProfile();
    },

    resolveDesktopClientUrl(rawUrl) {
      const trimmed = String(rawUrl || '').trim();
      if (!trimmed) return '';
      try {
        return new URL(trimmed, `${this.apiBase}/`).toString();
      } catch (error) {
        console.warn('[客户端下载] 下载地址格式异常:', trimmed, error);
        return '';
      }
    },

    triggerDesktopClientDownload(url) {
      const resolved = this.resolveDesktopClientUrl(url);
      if (!resolved) return false;
      const link = document.createElement('a');
      link.href = resolved;
      link.target = '_blank';
      link.rel = 'noopener noreferrer';
      link.download = '';
      document.body.appendChild(link);
      link.click();
      link.remove();
      return true;
    },

    async fetchDesktopClientDownloadUrl() {
      try {
        const response = await axios.get(`${this.apiBase}/api/client/desktop-update`, {
          params: {
            currentVersion: '0.0.0',
            platform: 'windows-x64',
            channel: 'stable'
          }
        });
        const resolved = this.resolveDesktopClientUrl(response.data?.downloadUrl);
        if (resolved) {
          this.desktopClientDownloadUrl = resolved;
          return resolved;
        }
      } catch (error) {
        console.warn('[客户端下载] 获取最新版下载地址失败:', error?.message || error);
      }
      return this.resolveDesktopClientUrl(this.desktopClientDownloadUrl);
    },

    async downloadDesktopClient() {
      if (this.desktopClientDownloading) return;
      this.desktopClientDownloading = true;
      try {
        const downloadUrl = await this.fetchDesktopClientDownloadUrl();
        if (!downloadUrl) {
          this.showToast('warning', '暂无安装包', '服务器尚未配置客户端安装包下载地址');
          return;
        }
        if (!this.triggerDesktopClientDownload(downloadUrl)) {
          this.showToast('error', '下载失败', '下载地址无效，请稍后重试');
          return;
        }
        this.showToast('success', '开始下载', '客户端安装包已开始下载');
      } finally {
        this.desktopClientDownloading = false;
      }
    },

    // ========== 主题管理 ==========
    // 初始化主题
    async initTheme() {
      // 先从localStorage读取，避免页面闪烁
      const savedTheme = localStorage.getItem('theme');
      if (savedTheme) {
        this.applyTheme(savedTheme);
      }
      // 如果没有登录，从公开API获取全局主题
      if (!this.token) {
        try {
          const res = await axios.get(`${this.apiBase}/api/public/theme`);
          if (res.data.success) {
            this.globalTheme = res.data.theme;
            this.applyTheme(res.data.theme);
          }
        } catch (e) {
          console.log('无法加载全局主题');
        }
      }
    },

    // 加载用户主题设置（登录后调用）
    async loadUserTheme() {
      try {
        const res = await axios.get(`${this.apiBase}/api/user/theme`);
        if (res.data.success) {
          this.globalTheme = res.data.theme.global;
          this.userThemePreference = res.data.theme.user;
          this.currentTheme = res.data.theme.effective;
          this.applyTheme(this.currentTheme);
          localStorage.setItem('theme', this.currentTheme);
        }
      } catch (error) {
        console.error('加载主题设置失败:', error);
      }
    },

    // 应用主题到DOM
    applyTheme(theme) {
      this.currentTheme = theme;
      if (theme === 'light') {
        document.body.classList.add('light-theme');
      } else {
        document.body.classList.remove('light-theme');
      }
    },

    // 切换用户主题偏好
    async setUserTheme(theme) {
      try {
        const res = await axios.post(`${this.apiBase}/api/user/theme`,
          { theme },
        );
        if (res.data.success) {
          this.userThemePreference = res.data.theme.user;
          this.currentTheme = res.data.theme.effective;
          this.applyTheme(this.currentTheme);
          localStorage.setItem('theme', this.currentTheme);
          this.showToast('success', '主题已更新', theme === null ? '已设为跟随全局' : (theme === 'dark' ? '已切换到暗色主题' : '已切换到亮色主题'));
        }
      } catch (error) {
        this.showToast('error', '主题更新失败', error.response?.data?.message || '请稍后重试');
      }
    },

    // 获取主题显示文本
    getThemeText(theme) {
      if (theme === null) return '跟随全局';
      return theme === 'dark' ? '暗色主题' : '亮色主题';
    },

    // 设置全局主题（管理员）
    async setGlobalTheme(theme) {
      try {
        console.log('[主题] 设置全局主题:', theme);
        const res = await axios.post(`${this.apiBase}/api/admin/settings`,
          { global_theme: theme },
        );
        console.log('[主题] API响应:', res.data);
        if (res.data.success) {
          this.globalTheme = theme;
          console.log('[主题] globalTheme已更新为:', this.globalTheme);
          // 如果用户没有设置个人偏好，则跟随全局
          if (this.userThemePreference === null) {
            this.currentTheme = theme;
            this.applyTheme(theme);
            localStorage.setItem('theme', theme);
          } else {
            console.log('[主题] 用户有个人偏好，不更改当前显示主题:', this.userThemePreference);
          }
          // 提示信息
          let toastMsg = theme === 'dark' ? '默认暗色主题' : '默认亮色主题';
          if (this.userThemePreference !== null) {
            toastMsg += '（你设置了个人偏好，不受全局影响）';
          }
          this.showToast('success', '全局主题已更新', toastMsg);
        } else {
          console.error('[主题] API返回失败:', res.data);
          this.showToast('error', '设置失败', res.data.message || '未知错误');
        }
      } catch (error) {
        console.error('[主题] 设置全局主题失败:', error);
        this.showToast('error', '设置失败', error.response?.data?.message || '请稍后重试');
      }
    },

    // 提取URL中的token（兼容缺少 ? 的场景）
    getTokenFromUrl(key) {
      const currentHref = window.location.href;
      const url = new URL(currentHref);
      let token = url.searchParams.get(key);

      if (!token) {
        const match = currentHref.match(new RegExp(`${key}=([\\w-]+)`));
        if (match && match[1]) {
          token = match[1];
        }
      }
      return token;
    },

    // 清理URL中的token（同时处理路径和查询参数）
    sanitizeUrlToken(key) {
      const url = new URL(window.location.href);
      url.searchParams.delete(key);
      if (url.pathname.includes(`${key}=`)) {
        url.pathname = url.pathname.split(`${key}=`)[0];
      }
      window.history.replaceState({}, document.title, url.toString());
    },

    // 模态框点击外部关闭优化 - 防止拖动选择文本时误关闭
    handleModalMouseDown(e) {
      // 记录鼠标按下时的目标
      this.modalMouseDownTarget = e.target;
    },
    handleModalMouseUp(modalName, e) {
      // 只有在同一个overlay元素上按下和释放鼠标时才关闭
      if (e && e.target === this.modalMouseDownTarget) {
        this[modalName] = false;
        this.shareResult = null; // 重置分享结果
      }
      this.modalMouseDownTarget = null;
    },

    // 格式化文件大小
    formatFileSize(bytes) {
      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 (bytes / Math.pow(k, i)).toFixed(2) + ' ' + sizes[i];
    },

    // 拖拽上传处理
    handleDragEnter(e) {
      e.preventDefault();
      e.stopPropagation();
      this.isDragging = true;
    },
    handleDragOver(e) {
      e.preventDefault();
      e.stopPropagation();
      this.isDragging = true;
    },
handleDragLeave(e) {
      e.preventDefault();
      e.stopPropagation();
      
      // 使用更可靠的检测：检查鼠标实际位置
      const container = e.currentTarget;
      const rect = container.getBoundingClientRect();
      const x = e.clientX;
      const y = e.clientY;
      
      // 如果鼠标位置在容器边界外，隐藏覆盖层
      // 添加5px的容差，避免边界问题
      const margin = 5;
      const isOutside = 
        x < rect.left - margin || 
        x > rect.right + margin || 
        y < rect.top - margin || 
        y > rect.bottom + margin;
      
      if (isOutside) {
        this.isDragging = false;
        return;
      }
      
      // 备用检测：检查 relatedTarget
      const related = e.relatedTarget;
      if (!related || !container.contains(related)) {
        this.isDragging = false;
      }
    },

    async handleDrop(e) {
      e.preventDefault();
      e.stopPropagation();
      this.isDragging = false;
      
      const files = e.dataTransfer.files;
      if (files.length > 0) {
        const file = files[0];
        await this.uploadFile(file);
      }
    },

    // ===== 认证相关 =====

    toggleAuthMode() {
      this.isLogin = !this.isLogin;
      this.errorMessage = '';
      this.successMessage = '';
      // 切换到注册模式时加载验证码
      if (!this.isLogin) {
        this.refreshRegisterCaptcha();
      }
    },

    getOrCreateWebDeviceId() {
      const storageKey = 'wanwan_web_device_id_v1';
      const existing = localStorage.getItem(storageKey);
      if (existing && existing.trim()) {
        return existing.trim();
      }
      const generated = (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function')
        ? crypto.randomUUID()
        : `web-${Date.now()}-${Math.random().toString(16).slice(2)}`;
      localStorage.setItem(storageKey, generated);
      return generated;
    },

    buildLoginClientMeta() {
      const platform = navigator.platform || '未知平台';
      const name = `${navigator.userAgent?.includes('Mobile') ? '移动端网页' : '网页端'} · ${platform || '未知平台'}`;
      return {
        client_type: navigator.userAgent?.includes('Mobile') ? 'mobile' : 'web',
        device_id: this.getOrCreateWebDeviceId(),
        device_name: name.slice(0, 120),
        platform: String(platform || '').slice(0, 80)
      };
    },

    formatOnlineDeviceType(clientType) {
      if (clientType === 'desktop') return '桌面端';
      if (clientType === 'mobile') return '移动端';
      if (clientType === 'api') return 'API';
      return '网页端';
    },

    async loadOnlineDevices(silent = false) {
      if (!silent) {
        this.onlineDevices.loading = true;
      }
      this.onlineDevices.error = '';
      try {
        const response = await axios.get(`${this.apiBase}/api/user/online-devices`);
        if (response.data?.success) {
          this.onlineDevices.items = Array.isArray(response.data.devices) ? response.data.devices : [];
          this.onlineDevices.lastLoadedAt = new Date().toISOString();
          return;
        }
        this.onlineDevices.error = response.data?.message || '加载在线设备失败';
      } catch (error) {
        this.onlineDevices.error = error.response?.data?.message || '加载在线设备失败';
      } finally {
        this.onlineDevices.loading = false;
      }
    },

    async kickOnlineDevice(device) {
      const sessionId = String(device?.session_id || '').trim();
      if (!sessionId || this.onlineDevices.kickingSessionId) return;

      const isCurrent = !!device?.is_current;
      const tip = isCurrent
        ? '确定要下线当前设备吗？下线后需要重新登录。'
        : '确定要强制该设备下线吗？';
      if (!confirm(tip)) return;

      this.onlineDevices.kickingSessionId = sessionId;
      try {
        const response = await axios.post(`${this.apiBase}/api/user/online-devices/${encodeURIComponent(sessionId)}/kick`);
        if (response.data?.success) {
          this.showToast('success', '成功', response.data?.message || '设备已下线');
          if (response.data?.kicked_current) {
            this.handleTokenExpired();
            return;
          }
          await this.loadOnlineDevices(true);
          return;
        }
        this.showToast('error', '失败', response.data?.message || '踢下线失败');
      } catch (error) {
        this.showToast('error', '失败', error.response?.data?.message || '踢下线失败');
      } finally {
        this.onlineDevices.kickingSessionId = '';
      }
    },

    async handleLogin() {
      this.errorMessage = '';
      this.loginLoading = true;
      try {
        const response = await axios.post(`${this.apiBase}/api/login`, {
          ...this.loginForm,
          ...this.buildLoginClientMeta()
        });

        if (response.data.success) {
          // token 和 refreshToken 都通过 HttpOnly Cookie 自动管理
          this.user = response.data.user;
          this.isLoggedIn = true;
          this.showResendVerify = false;
          this.resendVerifyEmail = '';

          // 登录成功后隐藏验证码并清空验证码输入
          this.showCaptcha = false;
          this.loginForm.captcha = '';

          // 保存用户信息到localStorage（非敏感信息，用于页面刷新后恢复）
          // 注意：token 通过 HttpOnly Cookie 传递，不再存储在 localStorage
          localStorage.setItem('user', JSON.stringify(this.user));

          // 启动token自动刷新（在过期前5分钟刷新）
          const expiresIn = response.data.expiresIn || (2 * 60 * 60 * 1000);
          this.startTokenRefresh(expiresIn);

          // 直接从登录响应中获取存储信息
          this.storagePermission = this.user.storage_permission || 'oss_only';
          this.storageType = this.user.current_storage_type || 'oss';
          this.localQuota = this.user.local_storage_quota || 0;
          this.localUsed = this.user.local_storage_used || 0;

          console.log('[登录] 存储权限:', this.storagePermission, '存储类型:', this.storageType, 'OSS配置:', this.user.oss_config_source);

          // 智能存储类型修正：如果当前是OSS但未配置（包括个人配置和系统级配置），且用户有本地存储权限，自动切换到本地
          if (this.storageType === 'oss' && (!this.user || this.user.oss_config_source === 'none')) {
            if (this.storagePermission === 'local_only' || this.storagePermission === 'user_choice') {
              console.log('[登录] OSS未配置但用户有本地存储权限，自动切换到本地存储');
              this.storageType = 'local';
              // 异步更新到后端（不等待，避免阻塞登录流程）
              axios.post(`${this.apiBase}/api/user/switch-storage`, { storage_type: 'local' })
                .catch(err => console.error('[登录] 自动切换存储类型失败:', err));
            }
          }

          // 启动定期检查用户配置
          this.startProfileSync();
          // 加载用户主题设置
          this.loadUserTheme();
          // 管理员直接跳转到管理后台
          if (this.user.is_admin) {
            this.currentView = 'admin';
          }
          // 普通用户：检查存储权限
          else {
            // 如果用户可以使用本地存储，直接进入文件页面
            if (this.storagePermission === 'local_only' || this.storagePermission === 'user_choice') {
              this.currentView = 'files';
              this.loadFiles('/');
            }
            // 如果仅OSS模式，需要检查是否配置了OSS（包括系统级统一配置）
            else if (this.storagePermission === 'oss_only') {
              if (this.user?.oss_config_source !== 'none') {
                this.currentView = 'files';
                this.loadFiles('/');
              } else {
                this.currentView = 'settings';
                this.showToast('info', '欢迎', '请先配置您的OSS服务');
                this.openOssConfigModal();
              }
            } else {
              // 默认行为：跳转到文件页面
              this.currentView = 'files';
              this.loadFiles('/');
            }
          }
        }
      } catch (error) {
        this.errorMessage = error.response?.data?.message || '登录失败';

        // 检查是否需要显示验证码
        if (error.response?.data?.needCaptcha) {
          this.showCaptcha = true;
          this.refreshCaptcha();
        }

        // 邮箱未验证提示
        if (error.response?.data?.needVerify) {
          this.showResendVerify = true;
          this.resendVerifyEmail = error.response?.data?.email || this.loginForm.username || '';
        } else {
          this.showResendVerify = false;
          this.resendVerifyEmail = '';
        }
      } finally {
        this.loginLoading = false;
      }
    },

    // 通用验证码加载函数（带防抖）
    async loadCaptcha(targetField) {
      // 防抖：2秒内不重复请求
      const now = Date.now();
      if (this._lastCaptchaTime && (now - this._lastCaptchaTime) < 2000) {
        console.log('[验证码] 请求过于频繁，跳过');
        return;
      }
      this._lastCaptchaTime = now;

      try {
        const response = await axios.get(`${this.apiBase}/api/captcha?t=${now}`, {
          responseType: 'blob'
        });
        this[targetField] = URL.createObjectURL(response.data);
      } catch (error) {
        console.error('获取验证码失败:', error);
        // 如果是429错误，不清除已有验证码
        if (error.response?.status !== 429) {
          this[targetField] = '';
        }
      }
    },

    // 刷新验证码（登录）
    refreshCaptcha() {
      this.loadCaptcha('captchaUrl');
    },

    // 刷新注册验证码
    refreshRegisterCaptcha() {
      this.loadCaptcha('registerCaptchaUrl');
    },

    // 刷新忘记密码验证码
    refreshForgotPasswordCaptcha() {
      this.loadCaptcha('forgotPasswordCaptchaUrl');
    },

    // 刷新重发验证邮件验证码
    refreshResendVerifyCaptcha() {
      this.loadCaptcha('resendVerifyCaptchaUrl');
    },

    async resendVerification() {
      if (!this.resendVerifyEmail) {
        this.showToast('error', '错误', '请输入邮箱或用户名后再重试');
        return;
      }
      if (!this.resendVerifyCaptcha) {
        this.showToast('error', '错误', '请输入验证码');
        return;
      }
      this.resendingVerify = true;
      try {
        const payload = { captcha: this.resendVerifyCaptcha };
        if (this.resendVerifyEmail.includes('@')) {
          payload.email = this.resendVerifyEmail;
        } else {
          payload.username = this.resendVerifyEmail;
        }
        const response = await axios.post(`${this.apiBase}/api/resend-verification`, payload);
        if (response.data.success) {
          this.showToast('success', '成功', '验证邮件已发送，请查收');
          this.showResendVerify = false;
          this.resendVerifyEmail = '';
          this.resendVerifyCaptcha = '';
          this.resendVerifyCaptchaUrl = '';
        }
      } catch (error) {
        console.error('重发验证邮件失败:', error);
        this.showToast('error', '错误', error.response?.data?.message || '发送失败');
        // 刷新验证码
        this.resendVerifyCaptcha = '';
        this.refreshResendVerifyCaptcha();
      } finally {
        this.resendingVerify = false;
      }
    },

    async handleVerifyToken(token) {
      try {
        const response = await axios.get(`${this.apiBase}/api/verify-email`, { params: { token } });
        if (response.data.success) {
          this.verifyMessage = '邮箱验证成功，请登录';
          this.isLogin = true;
          // 清理URL
          this.sanitizeUrlToken('verifyToken');
        }
      } catch (error) {
        console.error('邮箱验证失败:', error);
        this.verifyMessage = error.response?.data?.message || '验证失败';
      }
    },

    async handleRegister() {
      this.errorMessage = '';
      this.successMessage = '';
      this.registerLoading = true;

      try {
        const response = await axios.post(`${this.apiBase}/api/register`, this.registerForm);

        if (response.data.success) {
          this.successMessage = '注册成功！请查收邮箱完成验证后再登录';
          this.isLogin = true;

          // 清空表单
          this.registerForm = {
            username: '',
            email: '',
            password: '',
            captcha: ''
          };
          this.registerCaptchaUrl = '';
        }
      } catch (error) {
        const errorData = error.response?.data;
        if (errorData?.errors) {
          this.errorMessage = errorData.errors.map(e => e.msg).join(', ');
        } else {
          this.errorMessage = errorData?.message || '注册失败';
        }
        // 刷新验证码
        this.registerForm.captcha = '';
        this.refreshRegisterCaptcha();
      } finally {
        this.registerLoading = false;
      }
    },

    async updateOssConfig() {
      // 防止重复提交
      if (this.ossConfigSaving) {
        return;
      }

      // 前端验证
      if (!this.ossConfigForm.oss_provider || !['aliyun', 'tencent', 'aws'].includes(this.ossConfigForm.oss_provider)) {
        this.showToast('error', '配置错误', '请选择有效的 OSS 服务商');
        return;
      }
      if (!this.ossConfigForm.oss_region || this.ossConfigForm.oss_region.trim() === '') {
        this.showToast('error', '配置错误', '地域/Region 不能为空');
        return;
      }
      if (!this.ossConfigForm.oss_access_key_id || this.ossConfigForm.oss_access_key_id.trim() === '') {
        this.showToast('error', '配置错误', 'Access Key ID 不能为空');
        return;
      }
      if (!this.ossConfigForm.oss_access_key_secret || this.ossConfigForm.oss_access_key_secret.trim() === '') {
        this.showToast('error', '配置错误', 'Access Key Secret 不能为空');
        return;
      }
      if (!this.ossConfigForm.oss_bucket || this.ossConfigForm.oss_bucket.trim() === '') {
        this.showToast('error', '配置错误', 'Bucket 名称不能为空');
        return;
      }

      this.ossConfigSaving = true;

      try {
        const response = await axios.post(
          `${this.apiBase}/api/user/update-oss`,
          this.ossConfigForm,
        );

        if (response.data.success) {
          // 更新用户信息
          this.user.has_oss_config = 1;

          // 如果用户有 user_choice 权限，自动切换到 OSS 存储
          if (this.storagePermission === 'user_choice' || this.storagePermission === 'oss_only') {
            try {
              const switchResponse = await axios.post(
                `${this.apiBase}/api/user/switch-storage`,
                { storage_type: 'oss' },
              );

              if (switchResponse.data.success) {
                this.storageType = 'oss';
                console.log('[OSS配置] 已自动切换到OSS存储模式');
              }
            } catch (err) {
              console.error('[OSS配置] 自动切换存储模式失败:', err);
            }
          }

          // 关闭配置弹窗
          this.showOssConfigModal = false;

          // 刷新到文件页面
          this.currentView = 'files';
          this.loadFiles('/');

          // 显示成功提示
          this.showToast('success', '配置成功', 'OSS存储配置已保存！');
        }
      } catch (error) {
        console.error('OSS配置保存失败:', error);
        this.showToast('error', '配置失败', error.response?.data?.message || error.message || '请检查配置信息后重试');
      } finally {
        this.ossConfigSaving = false;
      }
    },

    // 测试 OSS 连接（不保存配置）
    async testOssConnection() {
      // 防止重复提交
      if (this.ossConfigTesting) {
        return;
      }

      // 前端验证
      if (!this.ossConfigForm.oss_provider || !['aliyun', 'tencent', 'aws'].includes(this.ossConfigForm.oss_provider)) {
        this.showToast('error', '配置错误', '请选择有效的 OSS 服务商');
        return;
      }
      if (!this.ossConfigForm.oss_region || this.ossConfigForm.oss_region.trim() === '') {
        this.showToast('error', '配置错误', '地域/Region 不能为空');
        return;
      }
      if (!this.ossConfigForm.oss_access_key_id || this.ossConfigForm.oss_access_key_id.trim() === '') {
        this.showToast('error', '配置错误', 'Access Key ID 不能为空');
        return;
      }
      // 如果用户已有配置，Secret 可以为空（使用现有密钥）
      if (this.user?.oss_config_source === 'none' && (!this.ossConfigForm.oss_access_key_secret || this.ossConfigForm.oss_access_key_secret.trim() === '')) {
        this.showToast('error', '配置错误', 'Access Key Secret 不能为空');
        return;
      }
      if (!this.ossConfigForm.oss_bucket || this.ossConfigForm.oss_bucket.trim() === '') {
        this.showToast('error', '配置错误', 'Bucket 名称不能为空');
        return;
      }

      this.ossConfigTesting = true;

      try {
        const response = await axios.post(
          `${this.apiBase}/api/user/test-oss`,
          this.ossConfigForm,
        );

        if (response.data.success) {
          this.showToast('success', '连接成功', 'OSS 配置验证通过，可以保存');
        }
      } catch (error) {
        console.error('OSS连接测试失败:', error);
        this.showToast('error', '连接失败', error.response?.data?.message || error.message || '请检查配置信息');
      } finally {
        this.ossConfigTesting = false;
      }
    },

    async updateAdminProfile() {
      try {
        const response = await axios.post(
          `${this.apiBase}/api/admin/update-profile`,
          {
            username: this.adminProfileForm.username
          },
        );

        if (response.data.success) {
          this.showToast('success', '成功', '用户名已更新！即将重新登录');

          // 更新用户信息（后端已通过 Cookie 更新 token）
          if (response.data.user) {
            this.user = response.data.user;
            localStorage.setItem('user', JSON.stringify(response.data.user));
          }

          // 延迟后重新登录
          setTimeout(() => this.logout(), 1500);
        }
      } catch (error) {
        this.showToast('error', '错误', '修改失败: ' + (error.response?.data?.message || error.message));
      }
    },

    async changePassword() {
      if (!this.changePasswordForm.current_password) {
        this.showToast('warning', '提示', '请输入当前密码');
        return;
      }

      if (this.changePasswordForm.new_password.length < 6) {
        this.showToast('warning', '提示', '新密码至少6个字符');
        return;
      }

      this.passwordChanging = true;
      try {
        const response = await axios.post(
          `${this.apiBase}/api/user/change-password`,
          {
            current_password: this.changePasswordForm.current_password,
            new_password: this.changePasswordForm.new_password
          },
        );

        if (response.data.success) {
          this.showToast('success', '成功', '密码修改成功！');
          this.changePasswordForm.new_password = '';
          this.changePasswordForm.current_password = '';
        }
      } catch (error) {
        this.showToast('error', '错误', '密码修改失败: ' + (error.response?.data?.message || error.message));
      } finally {
        this.passwordChanging = false;
      }
    },

    async loadOssConfig() {
      try {
        const response = await axios.get(
          `${this.apiBase}/api/user/profile`,
        );

        if (response.data.success && response.data.user) {
          const user = response.data.user;
          // 填充OSS配置表单（密钥不回显）
          this.ossConfigForm.oss_provider = user.oss_provider || 'aliyun';
          this.ossConfigForm.oss_region = user.oss_region || '';
          this.ossConfigForm.oss_access_key_id = user.oss_access_key_id || '';
          this.ossConfigForm.oss_access_key_secret = ''; // 密钥不回显
          this.ossConfigForm.oss_bucket = user.oss_bucket || '';
          this.ossConfigForm.oss_endpoint = user.oss_endpoint || '';
        }
      } catch (error) {
        console.error('加载OSS配置失败:', error);
      }
    },

    // 上传工具配置引导已移除（OSS 不需要配置文件导入）

    async updateUsername() {
      if (!this.usernameForm.newUsername || this.usernameForm.newUsername.length < 3) {
        this.showToast('warning', '提示', '用户名至少3个字符');
        return;
      }

      this.usernameChanging = true;
      try {
        const response = await axios.post(
          `${this.apiBase}/api/user/update-username`,
          { username: this.usernameForm.newUsername },
        );

        if (response.data.success) {
          this.showToast('success', '成功', '用户名修改成功！');
          // 更新本地用户信息
          this.user.username = this.usernameForm.newUsername;
          localStorage.setItem('user', JSON.stringify(this.user));
          this.usernameForm.newUsername = '';
        }
      } catch (error) {
        this.showToast('error', '错误', '用户名修改失败: ' + (error.response?.data?.message || error.message));
      } finally {
        this.usernameChanging = false;
      }
    },

    async updateProfile() {
      try {
        const response = await axios.post(
          `${this.apiBase}/api/user/update-profile`,
          { email: this.profileForm.email },
        );

        if (response.data.success) {
          this.showToast('success', '成功', '邮箱已更新！');
          // 更新本地用户信息
          if (response.data.user) {
            this.user = response.data.user;
            localStorage.setItem('user', JSON.stringify(this.user));
          }
        }
      } catch (error) {
        this.showToast('error', '错误', '更新失败: ' + (error.response?.data?.message || error.message));
      }
    },

    async logout() {
      // 调用后端清除 HttpOnly Cookie
      try {
        await axios.post(`${this.apiBase}/api/logout`);
      } catch (err) {
        console.error('[登出] 清除Cookie失败:', err);
      }

      this.isLoggedIn = false;
      this.user = null;
      this.token = null;
      this.stopTokenRefresh();
      localStorage.removeItem('user');
      localStorage.removeItem('lastView');
      localStorage.removeItem('adminTab');
      this.showResendVerify = false;
      this.resendVerifyEmail = '';
      this.onlineDevices.items = [];
      this.onlineDevices.kickingSessionId = '';
      this.onlineDevices.loading = false;
      this.onlineDevices.error = '';
      this.onlineDevices.lastLoadedAt = '';

      // 停止定期检查
      this.stopProfileSync();
      this.closeMobileFileActionSheet();
    },

    // 获取公开的系统配置（上传限制等）
    async loadPublicConfig() {
      try {
        const response = await axios.get(`${this.apiBase}/api/config`);
        if (response.data.success) {
          this.maxUploadSize = response.data.config.max_upload_size || 10737418240;
        }
      } catch (error) {
        console.error('获取系统配置失败:', error);
        // 使用默认值
      }
    },

    // 检查登录状态（通过 HttpOnly Cookie 验证）
    async checkLoginStatus() {
      // 直接调用API验证，Cookie会自动携带
      try {
        const response = await axios.get(`${this.apiBase}/api/user/profile`);

        if (response.data.success && response.data.user) {
          // Cookie有效，用户已登录
          this.user = response.data.user;
          this.isLoggedIn = true;

          // 更新localStorage中的用户信息（非敏感信息）
          localStorage.setItem('user', JSON.stringify(this.user));

          // 从最新的用户信息初始化存储相关字段
          this.storagePermission = this.user.storage_permission || 'oss_only';
          this.storageType = this.user.current_storage_type || 'oss';
          this.localQuota = this.user.local_storage_quota || 0;
          this.localUsed = this.user.local_storage_used || 0;

          console.log('[页面加载] Cookie验证成功，存储权限:', this.storagePermission, '存储类型:', this.storageType);

          // 启动token自动刷新（假设剩余1.5小时，实际由服务端控制）
          this.startTokenRefresh(1.5 * 60 * 60 * 1000);

          // 启动定期检查用户配置
          this.startProfileSync();
          // 加载用户主题设置
          this.loadUserTheme();

          // 读取上次停留的视图（需合法才生效）
          const savedView = localStorage.getItem('lastView');
          let targetView = null;
          if (savedView && this.isViewAllowed(savedView)) {
            targetView = savedView;
          } else if (this.user.is_admin) {
            targetView = 'admin';
          } else if (this.storagePermission === 'oss_only' && this.user?.oss_config_source === 'none') {
            targetView = 'settings';
          } else {
            targetView = 'files';
          }

          // 强制切换到目标视图并加载数据
          this.switchView(targetView, true);
        }
      } catch (error) {
        // 401表示未登录或Cookie过期，静默处理（用户需要重新登录）
        if (error.response?.status === 401) {
          console.log('[页面加载] 未登录或Cookie已过期');
        } else {
          console.warn('[页面加载] 验证登录状态失败:', error.message);
        }
        // 清理可能残留的用户信息
        localStorage.removeItem('user');
      } finally {
        // 无论登录验证成功还是失败，都标记应用已准备就绪
        this.appReady = true;
      }
    },

    // 尝试刷新token，失败则登出
    async tryRefreshOrLogout() {
      // refreshToken 通过 Cookie 自动管理，直接尝试刷新
      const refreshed = await this.doRefreshToken();
      if (refreshed) {
        await this.checkLoginStatus();
        return;
      }
      this.handleTokenExpired();
    },

    // 处理token过期/失效
    handleTokenExpired() {
      console.log('[认证] Cookie已失效，清除登录状态');
      this.isLoggedIn = false;
      this.user = null;
      this.token = null;
      this.stopTokenRefresh();
      localStorage.removeItem('user');
      localStorage.removeItem('lastView');
      this.stopProfileSync();
      this.onlineDevices.items = [];
      this.onlineDevices.kickingSessionId = '';
      this.onlineDevices.loading = false;
      this.onlineDevices.error = '';
      this.onlineDevices.lastLoadedAt = '';
    },

    // 启动token自动刷新定时器
    startTokenRefresh(expiresIn) {
      this.stopTokenRefresh(); // 先清除旧的定时器

      // 在token过期前5分钟刷新
      const refreshTime = Math.max(expiresIn - 5 * 60 * 1000, 60 * 1000);
      console.log(`[认证] Token将在 ${Math.round(refreshTime / 60000)} 分钟后刷新`);

      this.tokenRefreshTimer = setTimeout(async () => {
        await this.doRefreshToken();
      }, refreshTime);
    },

    // 停止token刷新定时器
    stopTokenRefresh() {
      if (this.tokenRefreshTimer) {
        clearTimeout(this.tokenRefreshTimer);
        this.tokenRefreshTimer = null;
      }
    },

    // 执行token刷新（refreshToken 通过 HttpOnly Cookie 自动发送）
    async doRefreshToken() {
      try {
        console.log('[认证] 正在刷新access token...');
        // refreshToken 通过 Cookie 自动携带，无需手动传递
        const response = await axios.post(`${this.apiBase}/api/refresh-token`);

        if (response.data.success) {
          // 后端已自动更新 HttpOnly Cookie 中的 token
          console.log('[认证] Token刷新成功（Cookie已更新）');

          // 继续下一次刷新
          const expiresIn = response.data.expiresIn || (2 * 60 * 60 * 1000);
          this.startTokenRefresh(expiresIn);
          return true;
        }
      } catch (error) {
        console.error('[认证] Token刷新失败:', error.response?.data?.message || error.message);
        // 刷新失败，需要重新登录
        this.handleTokenExpired();
      }
      return false;
    },

    // 检查URL参数
    checkUrlParams() {
      const urlParams = new URLSearchParams(window.location.search);
      const action = urlParams.get('action');

      if (action === 'login') {
        this.isLogin = true;
      } else if (action === 'register') {
        this.isLogin = false;
      }
    },

    // ===== 文件管理 =====

    async loadFiles(path) {
      this.loading = true;
      // 确保路径不为undefined
      this.currentPath = path || '/';
      this.globalSearchVisible = false;

      try {
        const response = await axios.get(`${this.apiBase}/api/files`, {
          params: { path }
        });

        if (response.data.success) {
          this.files = response.data.items;
          this.thumbnailLoadErrors = {};

          // 更新存储类型信息
          if (response.data.storageType) {
            this.storageType = response.data.storageType;
          }
          if (response.data.storagePermission) {
            this.storagePermission = response.data.storagePermission;
          }

          // 更新用户本地存储信息（使用防抖避免频繁请求）
          this.debouncedLoadUserProfile();

          // OSS 模式下刷新一次空间统计，保证文件页配额显示实时
          if (this.storageType === 'oss' && this.user?.oss_config_source !== 'none') {
            this.loadOssUsage();
          }
        }
      } catch (error) {
        console.error('加载文件失败:', error);
        this.showToast('error', '加载失败', error.response?.data?.message || error.message);

        if (error.response?.status === 401) {
          this.logout();
        }
      } finally {
        this.loading = false;
      }
    },

    triggerGlobalSearch() {
      const keyword = String(this.globalSearchKeyword || '').trim();
      if (!keyword) {
        this.clearGlobalSearch(false);
        return;
      }

      if (!this._debouncedGlobalSearch) {
        this._debouncedGlobalSearch = this.debounce(() => {
          this.runGlobalSearch();
        }, 260);
      }
      this._debouncedGlobalSearch();
    },

    async runGlobalSearch() {
      const keyword = String(this.globalSearchKeyword || '').trim();
      if (!keyword) {
        this.clearGlobalSearch(false);
        return;
      }

      this.globalSearchLoading = true;
      this.globalSearchError = '';
      this.globalSearchVisible = true;

      try {
        const response = await axios.get(`${this.apiBase}/api/files/search`, {
          params: {
            keyword,
            path: '/',
            type: this.globalSearchType || 'all',
            limit: 80
          }
        });

        if (response.data?.success) {
          this.globalSearchResults = Array.isArray(response.data.items) ? response.data.items : [];
          this.globalSearchMeta = response.data.meta || null;
        } else {
          this.globalSearchResults = [];
          this.globalSearchMeta = null;
          this.globalSearchError = response.data?.message || '搜索失败';
        }
      } catch (error) {
        this.globalSearchResults = [];
        this.globalSearchMeta = null;
        this.globalSearchError = error.response?.data?.message || '搜索失败';
      } finally {
        this.globalSearchLoading = false;
      }
    },

    clearGlobalSearch(clearKeyword = true) {
      if (clearKeyword) {
        this.globalSearchKeyword = '';
      }
      this.globalSearchLoading = false;
      this.globalSearchError = '';
      this.globalSearchResults = [];
      this.globalSearchMeta = null;
      this.globalSearchVisible = false;
    },

    async jumpToSearchResult(item) {
      if (!item || !item.parent_path) return;
      this.clearGlobalSearch(false);
      await this.loadFiles(item.parent_path);
      this.showToast('info', '已定位', `已定位到 ${item.name}`);
    },

    async handleFileClick(file) {
      // 修复：长按后会触发一次 click，需要忽略避免误打开文件/目录
      if (this.longPressTriggered) {
        this.longPressTriggered = false;
        return;
      }

      if (file.isDirectory) {
        const newPath = this.currentPath === '/'
          ? `/${file.name}`
          : `${this.currentPath}/${file.name}`;
        this.loadFiles(newPath);
      } else {
        // 检查文件类型，打开相应的预览（异步）
        if (file.name.match(/\.(jpg|jpeg|png|gif|bmp|svg|webp)$/i)) {
          await this.openImageViewer(file);
        } else if (file.name.match(/\.(mp4|avi|mov|wmv|flv|mkv|webm)$/i)) {
          await this.openVideoPlayer(file);
        } else if (file.name.match(/\.(mp3|wav|flac|aac|ogg|m4a)$/i)) {
          await this.openAudioPlayer(file);
        }
        // 其他文件类型不做任何操作，用户可以通过右键菜单下载
      }
    },
    navigateToPath(path) {
      this.loadFiles(path);
    },

    navigateToIndex(index) {
      const parts = this.pathParts.slice(0, index + 1);
      const path = '/' + parts.join('/');
      this.loadFiles(path);
    },

    // 返回上一级目录
    navigateUp() {
      if (this.currentPath === '/') return;
      const parts = this.currentPath.split('/').filter(p => p !== '');
      parts.pop();
      const newPath = parts.length === 0 ? '/' : '/' + parts.join('/');
      this.loadFiles(newPath);
    },

    async downloadFile(file) {
      // 构建文件路径
      const filePath = this.currentPath === '/' ? `/${file.name}` : `${this.currentPath}/${file.name}`;

      const canDirectOssDownload = this.storageType === 'oss'
        && this.user?.oss_config_source !== 'none';

      // OSS 下载优先使用直连（避免服务器中转导致带宽与费用双重损耗）
      if (canDirectOssDownload) {
        await this.downloadFromOSS(filePath);
        return;
      }

      // 其他场景走后端下载接口（支持下载流量计量/权限控制）
      await this.downloadFromLocal(filePath);
    },

    async downloadFromOSS(filePath) {
      try {
        const response = await axios.get(`${this.apiBase}/api/files/download-url`, {
          params: { path: filePath }
        });

        if (!response.data?.success || !response.data?.downloadUrl) {
          return false;
        }

        const link = document.createElement('a');
        link.href = response.data.downloadUrl;
        link.setAttribute('download', '');
        link.rel = 'noopener noreferrer';
        link.target = '_blank';
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
        return true;
      } catch (error) {
        console.error('OSS直连下载失败:', error);
        const message = error.response?.data?.message || '下载失败，请稍后重试';
        this.showToast('error', '下载失败', message);
        if (error.response?.status === 401) {
          this.logout();
        }
        return false;
      }
    },

    // 本地存储下载（先预检，避免浏览器下载 JSON 错误文件）
    async downloadFromLocal(filePath) {
      try {
        const checkResp = await axios.get(`${this.apiBase}/api/files/download-check`, {
          params: { path: filePath }
        });

        if (!checkResp.data?.success) {
          this.showToast('error', '下载失败', checkResp.data?.message || '下载失败，请稍后重试');
          return false;
        }

        const url = `${this.apiBase}/api/files/download?path=${encodeURIComponent(filePath)}`;
        const link = document.createElement('a');
        link.href = url;
        link.setAttribute('download', '');
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
        return true;
      } catch (error) {
        console.error('本地下载预检失败:', error);
        const message = error.response?.data?.message || '下载失败，请稍后重试';
        this.showToast('error', '下载失败', message);
        if (error.response?.status === 401) {
          this.logout();
        }
        return false;
      }
    },

    // ===== 文件操作 =====

    openRenameModal(file) {
      this.renameForm.oldName = file.name;
      this.renameForm.newName = file.name;
      this.renameForm.path = this.currentPath;
      this.showRenameModal = true;
    },

    async renameFile() {
      if (!this.renameForm.newName || this.renameForm.newName === this.renameForm.oldName) {
        this.showToast('warning', '提示', '请输入新的文件名');
        return;
      }

      try {
        const response = await axios.post(
          `${this.apiBase}/api/files/rename`,
          this.renameForm,
        );

        if (response.data.success) {
          this.showToast('success', '成功', '文件已重命名');
          this.showRenameModal = false;
          this.loadFiles(this.currentPath);
        }
      } catch (error) {
        console.error('重命名失败:', error);
        this.showToast('error', '错误', error.response?.data?.message || '重命名失败');
      }
    },

    // 创建文件夹
    async createFolder() {
      if (this.creatingFolder) return; // 防止重复提交

      const folderName = this.createFolderForm.folderName.trim();

      if (!folderName) {
        this.showToast('error', '错误', '请输入文件夹名称');
        return;
      }

      // 前端验证文件夹名称
      if (folderName.includes('/') || folderName.includes('\\') || folderName.includes('..') || folderName.includes(':')) {
        this.showToast('error', '错误', '文件夹名称不能包含特殊字符 (/ \\ .. :)');
        return;
      }

      this.creatingFolder = true;
      try {
        const response = await axios.post(`${this.apiBase}/api/files/mkdir`, {
          path: this.currentPath,
          folderName: folderName
        });

        if (response.data.success) {
          this.showToast('success', '成功', '文件夹创建成功');
          this.showCreateFolderModal = false;
          this.createFolderForm.folderName = '';
          await this.loadFiles(this.currentPath); // 刷新文件列表
          await this.refreshStorageUsage(); // 刷新空间统计（OSS会增加空对象）
        }
      } catch (error) {
        console.error('[创建文件夹失败]', error);
        this.showToast('error', '错误', error.response?.data?.message || '创建文件夹失败');
      } finally {
        this.creatingFolder = false;
      }
    },

    // 显示文件夹详情
    async showFolderInfo(file) {
      if (!file.isDirectory) {
        this.showToast('error', '错误', '只能查看文件夹详情');
        return;
      }

      this.showFolderInfoModal = true;
      this.folderInfo = null; // 先清空,显示加载中

      try {
        const response = await axios.post(`${this.apiBase}/api/files/folder-info`, {
          path: this.currentPath,
          folderName: file.name
        });

        if (response.data.success) {
          this.folderInfo = response.data.data;
        }
      } catch (error) {
        console.error('[获取文件夹详情失败]', error);
        this.showToast('error', '错误', error.response?.data?.message || '获取文件夹详情失败');
        this.showFolderInfoModal = false;
      }
    },

    confirmDeleteFile(file) {
      const fileType = file.isDirectory ? '文件夹' : '文件';
      const warning = file.isDirectory ? "\n⚠️ 警告：文件夹内所有文件将被永久删除！" : "";
      if (confirm(`确定要删除${fileType} "${file.name}" 吗？此操作无法撤销！${warning}`)) {
        this.deleteFile(file);
      }
    },

    // ===== 右键菜单和长按功能 =====

    // 显示右键菜单（PC端）/ 长按菜单（移动端）
    showFileContextMenu(file, event) {
      event?.preventDefault?.();
      event?.stopPropagation?.();

      const isTouchContextMenu = this.isMobileViewport()
        || event?.pointerType === 'touch'
        || Boolean(event?.sourceCapabilities?.firesTouchEvents);

      if (isTouchContextMenu) {
        this.openMobileFileActionSheet(file, event);
        return;
      }

      this.contextMenuFile = file;
      this.contextMenuX = event?.clientX || 0;
      this.contextMenuY = event?.clientY || 0;
      this.showContextMenu = true;

      // 点击其他地方关闭菜单
      this.$nextTick(() => {
        document.addEventListener('click', this.hideContextMenu, { once: true });
      });
    },
    
    // 隐藏右键菜单
    hideContextMenu() {
      this.showContextMenu = false;
      this.contextMenuFile = null;
    },

    isMobileViewport() {
      return window.matchMedia('(max-width: 768px)').matches;
    },

    setMobileSheetScrollLock(locked) {
      if (!document.body) return;
      document.body.style.overflow = locked ? 'hidden' : '';
    },

    openMobileFileActionSheet(file, event) {
      if (event) {
        event.preventDefault();
        event.stopPropagation();
      }

      this.hideContextMenu();
      this.mobileActionFile = file;
      this.showMobileFileActionSheet = true;
      this.setMobileSheetScrollLock(true);
    },

    closeMobileFileActionSheet() {
      this.showMobileFileActionSheet = false;
      this.mobileActionFile = null;
      this.setMobileSheetScrollLock(false);
    },

    async mobileFileAction(action) {
      const targetFile = this.mobileActionFile;
      this.closeMobileFileActionSheet();

      if (!targetFile) {
        return;
      }

      this.contextMenuFile = targetFile;
      await this.contextMenuAction(action);
    },
    
    // 长按开始（移动端）
    handleLongPressStart(file, event) {
      if (!event?.touches?.length) return;
      if (this.showMobileFileActionSheet) return;

      this.longPressTriggered = false;

      // 记录初始触摸位置，用于检测是否在滑动
      const touch = event.touches[0];
      this.longPressStartX = touch.clientX;
      this.longPressStartY = touch.clientY;
      this.longPressFile = file;

      this.longPressTimer = setTimeout(() => {
        this.longPressTriggered = true;

        if (event?.cancelable) {
          event.preventDefault();
        }

        if (this.isMobileViewport()) {
          this.openMobileFileActionSheet(file, event);
        } else {
          // 触发长按菜单
          this.contextMenuFile = file;

          // 使用记录的触摸位置
          this.contextMenuX = this.longPressStartX;
          this.contextMenuY = this.longPressStartY;
          this.showContextMenu = true;

          // 点击其他地方关闭菜单
          this.$nextTick(() => {
            document.addEventListener('click', this.hideContextMenu, { once: true });
          });
        }

        // 触摸震动反馈（如果支持）
        if (navigator.vibrate) {
          navigator.vibrate(50);
        }
      }, this.longPressDuration);
    },

    // 长按移动检测（移动端）- 滑动时取消长按
    handleLongPressMove(event) {
      if (!this.longPressTimer || !event?.touches?.length) return;

      const touch = event.touches[0];
      const moveX = Math.abs(touch.clientX - this.longPressStartX);
      const moveY = Math.abs(touch.clientY - this.longPressStartY);

      // 如果移动超过10px，认为是滑动，取消长按
      if (moveX > 10 || moveY > 10) {
        clearTimeout(this.longPressTimer);
        this.longPressTimer = null;
      }
    },

    // 长按取消（移动端）
    handleLongPressEnd() {
      if (this.longPressTimer) {
        clearTimeout(this.longPressTimer);
        this.longPressTimer = null;
      }
      this.longPressFile = null;
    },
    
    // 从菜单执行操作
    async contextMenuAction(action) {
      if (!this.contextMenuFile) return;

      switch (action) {
        case 'preview':
          // 根据文件类型打开对应的预览（异步）
          if (this.contextMenuFile.name.match(/\.(jpg|jpeg|png|gif|bmp|svg|webp)$/i)) {
            await this.openImageViewer(this.contextMenuFile);
          } else if (this.contextMenuFile.name.match(/\.(mp4|avi|mov|wmv|flv|mkv|webm)$/i)) {
            await this.openVideoPlayer(this.contextMenuFile);
          } else if (this.contextMenuFile.name.match(/\.(mp3|wav|flac|aac|ogg|m4a)$/i)) {
            await this.openAudioPlayer(this.contextMenuFile);
          }
          break;
        case 'download':
          this.downloadFile(this.contextMenuFile);
          break;
        case 'rename':
          this.openRenameModal(this.contextMenuFile);
          break;
        case 'info':
          this.showFolderInfo(this.contextMenuFile);
          break;
        case 'share':
          this.openShareFileModal(this.contextMenuFile);
          break;
        case 'direct_link':
          this.openDirectLinkModal(this.contextMenuFile);
          break;
        case 'delete':
          this.confirmDeleteFile(this.contextMenuFile);
          break;
      }

      this.hideContextMenu();
    },

    // ===== 媒体预览功能 =====

    getCachedMediaUrl(filePath) {
      if (!filePath) return null;
      const cached = this.mediaPreviewCache[filePath];
      if (!cached || !cached.url || !cached.expiresAt) return null;
      if (Date.now() >= cached.expiresAt) return null;
      return cached.url;
    },

    setCachedMediaUrl(filePath, url, expiresInSeconds = 3600) {
      if (!filePath || !url) return;
      const ttlMs = Math.max(60 * 1000, (Number(expiresInSeconds) || 3600) * 1000);
      const expiresAt = Date.now() + ttlMs - 20 * 1000;
      this.mediaPreviewCache = {
        ...this.mediaPreviewCache,
        [filePath]: { url, expiresAt }
      };
    },

    getCurrentFilePath(file) {
      if (!file || !file.name) return '';
      return this.currentPath === '/' ? `/${file.name}` : `${this.currentPath}/${file.name}`;
    },

    isThumbnailLoadFailed(file) {
      const filePath = this.getCurrentFilePath(file);
      return !!(filePath && this.thumbnailLoadErrors[filePath]);
    },

    markThumbnailLoadFailed(file) {
      const filePath = this.getCurrentFilePath(file);
      if (!filePath || this.thumbnailLoadErrors[filePath]) return;
      this.thumbnailLoadErrors = {
        ...this.thumbnailLoadErrors,
        [filePath]: true
      };
    },

    // 获取媒体文件URL（OSS直连或后端代理）
    async getMediaUrl(file) {
      const filePath = this.getCurrentFilePath(file);

      // OSS 模式优先直连，避免限流场景回退为后端中转
      if (this.storageType === 'oss' && this.user?.oss_config_source !== 'none') {
        const cachedUrl = this.getCachedMediaUrl(filePath);
        if (cachedUrl) {
          return cachedUrl;
        }

        try {
          const { data } = await axios.get(`${this.apiBase}/api/files/download-url`, {
            params: {
              path: filePath,
              mode: 'preview'
            }
          });
          if (data.success) {
            this.setCachedMediaUrl(filePath, data.downloadUrl, data.expiresIn || 3600);
            return data.downloadUrl;
          }
        } catch (error) {
          console.error('获取媒体URL失败:', error);
        }
        return null;
      }

      // 本地存储模式：通过后端 API
      return `${this.apiBase}/api/files/download?path=${encodeURIComponent(filePath)}`;
    },

    // 获取文件缩略图URL（同步方法，用于本地存储模式）
    // 注意：OSS 模式下缩略图需要单独处理，此处返回本地存储的直接URL
    getThumbnailUrl(file) {
      if (!file || file.isDirectory) return null;

      // 检查是否是图片或视频
      const isImage = file.name.match(/\.(jpg|jpeg|png|gif|bmp|svg|webp)$/i);
      const isVideo = file.name.match(/\.(mp4|avi|mov|wmv|flv|mkv|webm)$/i);

      if (!isImage && !isVideo) return null;

      const downloadQuota = Number(this.user?.download_traffic_quota);
      const downloadUsed = Number(this.user?.download_traffic_used || 0);
      if (Number.isFinite(downloadQuota) && downloadQuota >= 0) {
        if (downloadQuota === 0 || downloadUsed >= downloadQuota) {
          return null;
        }
      }

      // 本地存储模式：返回同步的下载 URL
      // OSS 模式下缩略图功能暂不支持（需要预签名 URL，建议点击文件预览）
      if (this.storageType !== 'oss') {
        const filePath = this.getCurrentFilePath(file);
        return `${this.apiBase}/api/files/download?path=${encodeURIComponent(filePath)}`;
      }

      // OSS 模式暂不支持同步缩略图，返回 null
      return null;
    },

    // 打开图片预览
    async openImageViewer(file) {
      this.mediaPreviewLoading = true;
      const url = await this.getMediaUrl(file);
      if (url) {
        this.currentMediaUrl = url;
        this.currentMediaName = file.name;
        this.currentMediaType = 'image';
        this.showImageViewer = true;
      } else {
        this.mediaPreviewLoading = false;
        this.showToast('error', '错误', '无法获取文件预览链接');
      }
    },

    handleMediaPreviewLoaded() {
      this.mediaPreviewLoading = false;
    },

    handleMediaPreviewWaiting() {
      if (this.currentMediaType === 'video' || this.currentMediaType === 'audio') {
        this.mediaPreviewLoading = true;
      }
    },

    handleMediaPreviewPlaying() {
      this.mediaPreviewLoading = false;
    },

    handleMediaPreviewError(type = 'file') {
      const typeTextMap = {
        image: '图片',
        video: '视频',
        audio: '音频'
      };
      const typeText = typeTextMap[type] || '文件';
      this.mediaPreviewLoading = false;
      this.showToast('error', '预览失败', `${typeText}预览失败，请尝试下载后查看`);
      this.closeMediaViewer();
    },

    // 打开视频播放器
    async openVideoPlayer(file) {
      this.mediaPreviewLoading = true;
      const url = await this.getMediaUrl(file);
      if (url) {
        this.currentMediaUrl = url;
        this.currentMediaName = file.name;
        this.currentMediaType = 'video';
        this.showVideoPlayer = true;
      } else {
        this.mediaPreviewLoading = false;
        this.showToast('error', '错误', '无法获取文件预览链接');
      }
    },

    // 打开音频播放器
    async openAudioPlayer(file) {
      this.mediaPreviewLoading = true;
      const url = await this.getMediaUrl(file);
      if (url) {
        this.currentMediaUrl = url;
        this.currentMediaName = file.name;
        this.currentMediaType = 'audio';
        this.showAudioPlayer = true;
      } else {
        this.mediaPreviewLoading = false;
        this.showToast('error', '错误', '无法获取文件预览链接');
      }
    },

    // 关闭媒体预览
    closeMediaViewer() {
      this.showImageViewer = false;
      this.showVideoPlayer = false;
      this.showAudioPlayer = false;
      this.currentMediaUrl = '';
      this.currentMediaName = '';
      this.currentMediaType = '';
      this.mediaPreviewLoading = false;
    },

    // 下载当前预览的媒体文件
    downloadCurrentMedia() {
      if (!this.currentMediaUrl) return;

      // 创建临时a标签触发下载
      const link = document.createElement('a');
      link.href = this.currentMediaUrl;
      link.setAttribute('download', this.currentMediaName);
      document.body.appendChild(link);
      link.click();
      document.body.removeChild(link);
    },


    // 判断文件是否支持预览
    isPreviewable(file) {
      if (!file || file.isDirectory) return false;
      return file.name.match(/\.(jpg|jpeg|png|gif|bmp|svg|webp|mp4|avi|mov|wmv|flv|mkv|webm|mp3|wav|flac|aac|ogg|m4a)$/i);
    },
    async deleteFile(file) {
      try {
        const response = await axios.post(
          `${this.apiBase}/api/files/delete`,
          {
            fileName: file.name,
            path: this.currentPath,
            isDirectory: file.isDirectory
          },
        );

        if (response.data.success) {
          this.showToast('success', '成功', '文件已删除');
          // 刷新文件列表和空间统计
          await this.loadFiles(this.currentPath);
          await this.refreshStorageUsage();
        }
      } catch (error) {
        console.error('删除失败:', error);
        this.showToast('error', '错误', error.response?.data?.message || '删除失败');
      }
    },

    // ===== 分享功能 =====

    openShareFileModal(file) {
      this.shareFileForm.fileName = file.name;
      this.shareFileForm.filePath = this.currentPath === '/'
        ? file.name
        : `${this.currentPath}/${file.name}`;
      this.shareFileForm.isDirectory = file.isDirectory;  // 设置是否为文件夹
      this.shareFileForm.enablePassword = false;
      this.shareFileForm.password = '';
      this.shareFileForm.expiryType = 'never';
      this.shareFileForm.customDays = 7;
      this.shareFileForm.enableAdvancedSecurity = false;
      this.shareFileForm.maxDownloadsEnabled = false;
      this.shareFileForm.maxDownloads = 10;
      this.shareFileForm.ipWhitelist = '';
      this.shareFileForm.deviceLimit = 'all';
      this.shareFileForm.accessTimeEnabled = false;
      this.shareFileForm.accessTimeStart = '09:00';
      this.shareFileForm.accessTimeEnd = '23:00';
      this.shareResult = null; // 清空上次的分享结果
      this.showShareFileModal = true;
    },

    toggleSharePassword(formType) {
      if (formType === 'file' && !this.shareFileForm.enablePassword) {
        this.shareFileForm.password = '';
      }
    },

    resolveShareExpiry(expiryType, customDays) {
      if (expiryType === 'never') {
        return { valid: true, value: null };
      }

      const days = expiryType === 'custom'
        ? Number(customDays)
        : Number(expiryType);

      if (!Number.isInteger(days) || days < 1 || days > 365) {
        return {
          valid: false,
          message: '有效期必须是 1-365 天的整数'
        };
      }

      return { valid: true, value: days };
    },

    normalizeSharePassword(enablePassword, rawPassword) {
      if (!enablePassword) {
        return { valid: true, value: null };
      }

      const password = (rawPassword || '').trim();
      if (!password) {
        return { valid: false, message: '已启用密码保护，请输入访问密码' };
      }

      if (password.length > 32) {
        return { valid: false, message: '访问密码不能超过32个字符' };
      }

      return { valid: true, value: password };
    },

    buildShareSecurityPayload() {
      if (!this.shareFileForm.enableAdvancedSecurity) {
        return { valid: true, payload: {} };
      }

      const payload = {};

      if (this.shareFileForm.maxDownloadsEnabled) {
        const maxDownloads = Number(this.shareFileForm.maxDownloads);
        if (!Number.isInteger(maxDownloads) || maxDownloads < 1 || maxDownloads > 1000000) {
          return { valid: false, message: '下载次数上限需为 1 到 1000000 的整数' };
        }
        payload.max_downloads = maxDownloads;
      }

      const whitelistText = String(this.shareFileForm.ipWhitelist || '').trim();
      if (whitelistText) {
        payload.ip_whitelist = whitelistText;
      }

      if (!['all', 'mobile', 'desktop'].includes(this.shareFileForm.deviceLimit)) {
        return { valid: false, message: '设备限制参数无效' };
      }
      payload.device_limit = this.shareFileForm.deviceLimit;

      if (this.shareFileForm.accessTimeEnabled) {
        const start = String(this.shareFileForm.accessTimeStart || '').trim();
        const end = String(this.shareFileForm.accessTimeEnd || '').trim();
        const timeReg = /^([01]\d|2[0-3]):([0-5]\d)$/;
        if (!timeReg.test(start) || !timeReg.test(end)) {
          return { valid: false, message: '访问时段格式必须为 HH:mm' };
        }
        payload.access_time_start = start;
        payload.access_time_end = end;
      }

      return { valid: true, payload };
    },

    buildShareResult(data, options = {}) {
      return {
        ...data,
        has_password: typeof options.hasPassword === 'boolean' ? options.hasPassword : !!data.has_password,
        target_name: options.targetName || '文件',
        target_type: options.targetType || 'file',
        share_password_plain: options.password || ''
      };
    },
    async createShareFile() {
      if (this.creatingShare) return; // 防止重复提交
      this.creatingShare = true;

      try {
        const expiryCheck = this.resolveShareExpiry(this.shareFileForm.expiryType, this.shareFileForm.customDays);
        if (!expiryCheck.valid) {
          this.showToast('warning', '提示', expiryCheck.message);
          return;
        }

        const passwordCheck = this.normalizeSharePassword(this.shareFileForm.enablePassword, this.shareFileForm.password);
        if (!passwordCheck.valid) {
          this.showToast('warning', '提示', passwordCheck.message);
          return;
        }

        const expiryDays = expiryCheck.value;
        const password = passwordCheck.value;
        const securityCheck = this.buildShareSecurityPayload();
        if (!securityCheck.valid) {
          this.showToast('warning', '提示', securityCheck.message);
          return;
        }

        // 根据是否为文件夹决定share_type
        const shareType = this.shareFileForm.isDirectory ? 'directory' : 'file';

        const response = await axios.post(
          `${this.apiBase}/api/share/create`,
          Object.assign({
            share_type: shareType,  // 修复：文件夹使用directory类型
            file_path: this.shareFileForm.filePath,
            file_name: this.shareFileForm.fileName,
            password,
            expiry_days: expiryDays
          }, securityCheck.payload),
        );

        if (response.data.success) {
          const itemType = this.shareFileForm.isDirectory ? '文件夹' : '文件';
          this.shareResult = this.buildShareResult(response.data, {
            hasPassword: !!response.data.has_password,
            targetName: this.shareFileForm.fileName,
            targetType: shareType,
            password: response.data.reused ? '' : password
          });
          this.showToast(
            'success',
            '成功',
            response.data.reused
              ? `${itemType}已复用现有分享链接`
              : (this.shareFileForm.enablePassword ? `${itemType}加密分享已创建` : `${itemType}分享链接已创建`)
          );
          this.loadShares();
        }
      } catch (error) {
        console.error('创建分享失败:', error);
        this.showToast('error', '错误', error.response?.data?.message || '创建分享失败');
      } finally {
        this.creatingShare = false;
      }
    },

    openDirectLinkModal(file) {
      if (!file || file.isDirectory) {
        this.showToast('warning', '提示', '目录不支持生成直链，请选择文件');
        return;
      }

      this.directLinkForm.fileName = file.name;
      this.directLinkForm.filePath = this.currentPath === '/'
        ? `/${file.name}`
        : `${this.currentPath}/${file.name}`;
      this.directLinkForm.expiryType = 'never';
      this.directLinkForm.customDays = 7;
      this.directLinkResult = null;
      this.showDirectLinkModal = true;
    },

    async createDirectLink() {
      if (this.creatingDirectLink) return;
      this.creatingDirectLink = true;

      try {
        const expiryCheck = this.resolveShareExpiry(this.directLinkForm.expiryType, this.directLinkForm.customDays);
        if (!expiryCheck.valid) {
          this.showToast('warning', '提示', expiryCheck.message);
          return;
        }

        const response = await axios.post(`${this.apiBase}/api/direct-link/create`, {
          file_path: this.directLinkForm.filePath,
          file_name: this.directLinkForm.fileName,
          expiry_days: expiryCheck.value
        });

        if (response.data?.success) {
          this.directLinkResult = {
            ...response.data,
            target_name: this.directLinkForm.fileName
          };
          this.showToast('success', '成功', response.data.reused ? '已复用现有直链' : '直链已创建');
          this.loadDirectLinks();
        }
      } catch (error) {
        console.error('创建直链失败:', error);
        this.showToast('error', '错误', error.response?.data?.message || '创建直链失败');
      } finally {
        this.creatingDirectLink = false;
      }
    },


    // ===== 文件上传 =====

    handleFileSelect(event) {
      const files = event.target.files;
      if (files && files.length > 0) {
        // 支持多文件上传
        Array.from(files).forEach(file => {
          this.uploadFile(file);
        });
        // 清空input，允许重复上传相同文件
        event.target.value = '';
      }
    },

    handleFileDrop(event) {
      this.isDragging = false;
      const file = event.dataTransfer.files[0];
      if (file) {
        this.uploadFile(file);
      }
    },

    async uploadFile(file) {
      // 文件大小限制预检查
      if (file.size > this.maxUploadSize) {
        const fileSizeMB = Math.round(file.size / (1024 * 1024));
        const maxSizeMB = Math.round(this.maxUploadSize / (1024 * 1024));
        this.showToast(
          'error',
          '文件超过上传限制',
          `文件大小 ${fileSizeMB}MB 超过系统限制 ${maxSizeMB}MB，请选择更小的文件`
        );
        return;
      }

      // 设置上传状态
      this.uploadingFileName = file.name;
      this.uploadProgress = 0;
      this.uploadedBytes = 0;
      this.totalBytes = file.size;

      try {
        const fileHash = await this.computeQuickFileHash(file);
        const instantUploaded = await this.checkInstantUpload(file, fileHash);
        if (instantUploaded) {
          this.uploadProgress = 0;
          this.uploadedBytes = 0;
          this.totalBytes = 0;
          this.uploadingFileName = '';
          await this.loadFiles(this.currentPath);
          await this.refreshStorageUsage();
          return;
        }

        if (this.storageType === 'oss' && this.user?.oss_config_source !== 'none') {
          // ===== OSS 直连上传（不经过后端） =====
          await this.uploadToOSSDirect(file, fileHash);
        } else {
          // ===== 本地存储优先分片上传（断点续传） =====
          const resumableOk = await this.uploadToLocalResumable(file, fileHash);
          if (!resumableOk) {
            await this.uploadToLocal(file, fileHash);
          }
        }
      } catch (error) {
        console.error('上传失败:', error);

        // 重置上传进度
        this.uploadProgress = 0;
        this.uploadedBytes = 0;
        this.totalBytes = 0;
        this.uploadingFileName = '';

        this.showToast('error', '上传失败', error.message || '上传失败，请重试');
      }
    },

    async computeQuickFileHash(file) {
      try {
        if (!file || !window.crypto?.subtle) {
          return null;
        }

        const sampleSize = 2 * 1024 * 1024; // 2MB
        const fullHashLimit = 8 * 1024 * 1024; // 8MB
        const chunks = [];

        if (file.size <= fullHashLimit) {
          chunks.push(new Uint8Array(await file.arrayBuffer()));
        } else {
          const first = await file.slice(0, sampleSize).arrayBuffer();
          const middleStart = Math.max(0, Math.floor(file.size / 2) - Math.floor(sampleSize / 2));
          const middle = await file.slice(middleStart, middleStart + sampleSize).arrayBuffer();
          const lastStart = Math.max(0, file.size - sampleSize);
          const last = await file.slice(lastStart).arrayBuffer();
          const meta = new TextEncoder().encode(`${file.name}|${file.size}|${file.lastModified}`);

          chunks.push(
            new Uint8Array(first),
            new Uint8Array(middle),
            new Uint8Array(last),
            meta
          );
        }

        const totalLength = chunks.reduce((sum, arr) => sum + arr.length, 0);
        const merged = new Uint8Array(totalLength);
        let offset = 0;
        for (const arr of chunks) {
          merged.set(arr, offset);
          offset += arr.length;
        }

        const digest = await window.crypto.subtle.digest('SHA-256', merged.buffer);
        const hashHex = Array.from(new Uint8Array(digest))
          .map(byte => byte.toString(16).padStart(2, '0'))
          .join('');
        return hashHex || null;
      } catch (error) {
        console.warn('计算文件哈希失败，跳过秒传:', error);
        return null;
      }
    },

    async checkInstantUpload(file, fileHash) {
      if (!file || !fileHash) {
        return false;
      }

      try {
        const response = await axios.post(`${this.apiBase}/api/files/instant-upload/check`, {
          filename: file.name,
          path: this.currentPath,
          size: file.size,
          file_hash: fileHash
        });

        if (response.data?.success && response.data.instant) {
          this.showToast('success', '秒传成功', response.data.message || `文件 ${file.name} 已秒传`);
          return true;
        }
        return false;
      } catch (error) {
        const status = Number(error.response?.status || 0);
        const message = error.response?.data?.message;
        // 4xx 视为明确业务失败，直接抛给外层；5xx/网络错误降级为普通上传
        if (status >= 400 && status < 500 && message) {
          throw new Error(message);
        }
        console.warn('秒传检查失败，降级普通上传:', error);
        return false;
      }
    },

    // OSS 直连上传
    async uploadToOSSDirect(file, fileHash = null) {
      try {
        // 预检查 OSS 配额（后端也会做强校验，未配置默认 1GB）
        const ossQuota = Number(this.user?.oss_storage_quota || DEFAULT_OSS_STORAGE_QUOTA_BYTES);
        const currentUsage = Number(this.user?.storage_used || this.ossUsage?.totalSize || 0);
        if (currentUsage + file.size > ossQuota) {
          const remaining = Math.max(ossQuota - currentUsage, 0);
          throw new Error(`OSS 配额不足：文件 ${this.formatBytes(file.size)}，剩余 ${this.formatBytes(remaining)}（总配额 ${this.formatBytes(ossQuota)}）`);
        }

        // 1. 获取签名 URL（传递当前路径）
        const { data: signData } = await axios.get(`${this.apiBase}/api/files/upload-signature`, {
          params: {
            filename: file.name,
            path: this.currentPath,
            contentType: file.type || 'application/octet-stream',
            size: file.size,
            fileHash: fileHash || undefined
          }
        });

        if (!signData.success) {
          throw new Error(signData.message || '获取上传签名失败');
        }

        if (!signData.completionToken) {
          throw new Error('上传签名缺少完成凭证，请刷新页面后重试');
        }

        // 2. 直连 OSS 上传（不经过后端！）
        await axios.put(signData.uploadUrl, file, {
          headers: {
            'Content-Type': file.type || 'application/octet-stream'
          },
          onUploadProgress: (progressEvent) => {
            this.uploadProgress = Math.round((progressEvent.loaded * 100) / progressEvent.total);
            this.uploadedBytes = progressEvent.loaded;
            this.totalBytes = progressEvent.total;
          },
          timeout: 30 * 60 * 1000 // 30分钟超时
        });

        // 3. 通知后端上传完成
        await axios.post(`${this.apiBase}/api/files/upload-complete`, {
          objectKey: signData.objectKey,
          size: file.size,
          completionToken: signData.completionToken,
          path: this.currentPath
        });

        // 4. 显示成功提示
        this.showToast('success', '上传成功', `文件 ${file.name} 已上传到 OSS`);

        // 5. 重置上传进度
        this.uploadProgress = 0;
        this.uploadedBytes = 0;
        this.totalBytes = 0;
        this.uploadingFileName = '';

        // 6. 刷新文件列表和空间统计
        await this.loadFiles(this.currentPath);
        await this.refreshStorageUsage();

      } catch (error) {
        // 处理 CORS 错误
        if (error.message?.includes('CORS') || error.message?.includes('Cross-Origin')) {
          throw new Error('OSS 跨域配置错误，请联系管理员检查 Bucket CORS 设置');
        }
        throw error;
      }
    },

    // 本地分片上传（断点续传）
    async uploadToLocalResumable(file, fileHash = null) {
      // 本地存储配额预检查
      const estimatedUsage = this.localUsed + file.size;
      if (estimatedUsage > this.localQuota) {
        this.showToast(
          'error',
          '配额不足',
          `文件大小 ${this.formatBytes(file.size)}，剩余配额 ${this.formatBytes(this.localQuota - this.localUsed)}`
        );
        return true;
      }

      try {
        const initResponse = await axios.post(`${this.apiBase}/api/upload/resumable/init`, {
          filename: file.name,
          path: this.currentPath,
          size: file.size,
          chunk_size: 4 * 1024 * 1024,
          file_hash: fileHash || undefined
        });

        if (!initResponse.data?.success) {
          throw new Error(initResponse.data?.message || '初始化分片上传失败');
        }

        const sessionId = initResponse.data.session_id;
        const chunkSize = Number(initResponse.data.chunk_size || 4 * 1024 * 1024);
        const totalChunks = Number(initResponse.data.total_chunks || Math.ceil(file.size / chunkSize));
        const uploadedSet = new Set(Array.isArray(initResponse.data.uploaded_chunks) ? initResponse.data.uploaded_chunks : []);
        let uploadedBytes = Number(initResponse.data.uploaded_bytes || 0);

        if (uploadedBytes > 0 && file.size > 0) {
          this.uploadedBytes = uploadedBytes;
          this.uploadProgress = Math.min(100, Math.round((uploadedBytes / file.size) * 100));
        }

        for (let chunkIndex = 0; chunkIndex < totalChunks; chunkIndex += 1) {
          if (uploadedSet.has(chunkIndex)) {
            continue;
          }

          const start = chunkIndex * chunkSize;
          const end = Math.min(file.size, start + chunkSize);
          const chunkBlob = file.slice(start, end);

          const formData = new FormData();
          formData.append('session_id', sessionId);
          formData.append('chunk_index', String(chunkIndex));
          formData.append('chunk', chunkBlob, `${file.name}.part${chunkIndex}`);

          const chunkResp = await axios.post(`${this.apiBase}/api/upload/resumable/chunk`, formData, {
            headers: { 'Content-Type': 'multipart/form-data' },
            timeout: 30 * 60 * 1000
          });

          if (!chunkResp.data?.success) {
            throw new Error(chunkResp.data?.message || '上传分片失败');
          }

          uploadedBytes = Number(chunkResp.data.uploaded_bytes || uploadedBytes + chunkBlob.size);
          this.uploadedBytes = uploadedBytes;
          this.totalBytes = file.size;
          this.uploadProgress = file.size > 0
            ? Math.min(100, Math.round((uploadedBytes / file.size) * 100))
            : 0;
        }

        const completeResp = await axios.post(`${this.apiBase}/api/upload/resumable/complete`, {
          session_id: sessionId
        });

        if (!completeResp.data?.success) {
          throw new Error(completeResp.data?.message || '完成分片上传失败');
        }

        this.showToast('success', '上传成功', `文件 ${file.name} 已上传`);
        this.uploadProgress = 0;
        this.uploadedBytes = 0;
        this.totalBytes = 0;
        this.uploadingFileName = '';
        await this.loadFiles(this.currentPath);
        await this.refreshStorageUsage();
        return true;
      } catch (error) {
        if (error.response?.status === 404) {
          // 后端未启用分片接口时自动降级
          return false;
        }
        throw error;
      }
    },

    // 本地存储上传（经过后端）
    async uploadToLocal(file, fileHash = null) {
      // 本地存储配额预检查
      const estimatedUsage = this.localUsed + file.size;
      if (estimatedUsage > this.localQuota) {
        this.showToast(
          'error',
          '配额不足',
          `文件大小 ${this.formatBytes(file.size)}，剩余配额 ${this.formatBytes(this.localQuota - this.localUsed)}`
        );
        this.uploadProgress = 0;
        this.uploadedBytes = 0;
        this.totalBytes = 0;
        this.uploadingFileName = '';
        return true;
      }

      const formData = new FormData();
      formData.append('file', file);
      formData.append('path', this.currentPath);
      if (fileHash) {
        formData.append('file_hash', fileHash);
      }

      const response = await axios.post(`${this.apiBase}/api/upload`, formData, {
        headers: { 'Content-Type': 'multipart/form-data' },
        timeout: 30 * 60 * 1000,
        onUploadProgress: (progressEvent) => {
          this.uploadProgress = Math.round((progressEvent.loaded * 100) / progressEvent.total);
          this.uploadedBytes = progressEvent.loaded;
          this.totalBytes = progressEvent.total;
        }
      });

      if (response.data.success) {
        this.showToast('success', '上传成功', `文件 ${file.name} 已上传`);
        this.uploadProgress = 0;
        this.uploadedBytes = 0;
        this.totalBytes = 0;
        this.uploadingFileName = '';
        await this.loadFiles(this.currentPath);
        await this.refreshStorageUsage();
      }
      return true;
    },

    // ===== 分享管理 =====

    async loadShares() {
      try {
        const response = await axios.get(`${this.apiBase}/api/share/my`);

        if (response.data.success) {
          this.shares = response.data.shares;
        }
      } catch (error) {
        console.error('加载分享列表失败:', error);
        this.showToast('error', '加载失败', error.response?.data?.message || error.message);
      }
    },

    async loadDirectLinks() {
      this.directLinksLoading = true;
      try {
        const response = await axios.get(`${this.apiBase}/api/direct-link/my`);
        if (response.data?.success) {
          this.directLinks = response.data.links || [];
        }
      } catch (error) {
        console.error('加载直链列表失败:', error);
        this.showToast('error', '加载失败', error.response?.data?.message || '加载直链列表失败');
      } finally {
        this.directLinksLoading = false;
      }
    },

    requestDeleteDirectLink(id, event = null) {
      if (event) {
        event.preventDefault();
        event.stopPropagation();
      }

      const linkId = parseInt(id, 10);
      if (!Number.isFinite(linkId) || linkId <= 0) {
        this.showToast('warning', '提示', '无效的直链ID');
        return;
      }

      this.deleteConfirm = {
        type: 'direct_link',
        id: linkId,
        title: '删除直链',
        message: '确定要删除这个直链吗？删除后将无法继续访问。',
        loading: false
      };
      this.showDeleteConfirmModal = true;
    },

    async deleteDirectLink(id) {
      try {
        const response = await axios.delete(`${this.apiBase}/api/direct-link/${id}`);
        if (response.data?.success) {
          this.showToast('success', '成功', '直链已删除');
          this.loadDirectLinks();
        }
      } catch (error) {
        console.error('删除直链失败:', error);
        this.showToast('error', '删除失败', error.response?.data?.message || '删除直链失败');
      }
    },

    async refreshShareResources() {
      await Promise.all([this.loadShares(), this.loadDirectLinks()]);
    },

    async createShare() {
      this.shareForm.path = this.currentPath;

      try {
        const response = await axios.post(`${this.apiBase}/api/share/create`, this.shareForm);

        if (response.data.success) {
          this.shareResult = response.data;
          this.loadShares();
        }
      } catch (error) {
        console.error('创建分享失败:', error);
        this.showToast('error', '创建失败', error.response?.data?.message || error.message);
      }
    },

    requestDeleteShare(id, event = null) {
      if (event) {
        event.preventDefault();
        event.stopPropagation();
      }

      const shareId = parseInt(id, 10);
      if (!Number.isFinite(shareId) || shareId <= 0) {
        this.showToast('warning', '提示', '无效的分享ID');
        return;
      }

      this.deleteConfirm = {
        type: 'share',
        id: shareId,
        title: '删除分享',
        message: '确定要删除这个分享吗？删除后外链将立即失效。',
        loading: false
      };
      this.showDeleteConfirmModal = true;
    },

    async deleteShare(id) {
      try {
        const response = await axios.delete(`${this.apiBase}/api/share/${id}`);

        if (response.data.success) {
          this.showToast('success', '成功', '分享已删除');
          this.loadShares();
        }
      } catch (error) {
        console.error('删除分享失败:', error);
        this.showToast('error', '删除失败', error.response?.data?.message || error.message);
      }
    },

    closeDeleteConfirmModal(force = false) {
      if (this.deleteConfirm.loading && !force) return;
      this.showDeleteConfirmModal = false;
      this.deleteConfirm = {
        type: '',
        id: null,
        title: '',
        message: '',
        loading: false
      };
    },

    async confirmDeleteAction() {
      if (this.deleteConfirm.loading) return;
      const { type, id } = this.deleteConfirm;
      if (!type || !id) {
        this.closeDeleteConfirmModal(true);
        return;
      }

      this.deleteConfirm.loading = true;
      try {
        if (type === 'share') {
          await this.deleteShare(id);
        } else if (type === 'direct_link') {
          await this.deleteDirectLink(id);
        }
        this.closeDeleteConfirmModal(true);
      } catch (error) {
        console.error('执行删除操作失败:', error);
        this.deleteConfirm.loading = false;
      }
    },

    // 格式化到期时间显示
    formatExpireTime(expiresAt) {
      if (!expiresAt) return '永久有效';

      const expireDate = this.parseDateValue(expiresAt);
      if (!expireDate) return String(expiresAt);
      const now = new Date();
      const diffMs = expireDate - now;
      const diffMinutes = Math.floor(diffMs / (1000 * 60));
      const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
      const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));

      // 格式化日期
      const dateStr = expireDate.toLocaleString('zh-CN', {
        year: 'numeric',
        month: '2-digit',
        day: '2-digit',
        hour: '2-digit',
        minute: '2-digit'
      });

      if (diffMs < 0) {
        return `已过期 (${dateStr})`;
      } else if (diffMinutes < 60) {
        return `${diffMinutes}分钟后过期 (${dateStr})`;
      } else if (diffHours < 24) {
        return `${diffHours}小时后过期 (${dateStr})`;
      } else if (diffDays === 1) {
        return `明天过期 (${dateStr})`;
      } else if (diffDays <= 7) {
        return `${diffDays}天后过期 (${dateStr})`;
      } else {
        return dateStr;
      }
    },

    // 判断是否即将过期（3天内）
    isExpiringSoon(expiresAt) {
      if (!expiresAt) return false;
      const expireDate = this.parseDateValue(expiresAt);
      if (!expireDate) return false;
      const now = new Date();
      const diffMs = expireDate - now;
      const diffDays = diffMs / (1000 * 60 * 60 * 24);
      return diffDays > 0 && diffDays <= 3;
    },

    // 判断是否已过期
    isExpired(expiresAt) {
      if (!expiresAt) return false;
      const expireDate = this.parseDateValue(expiresAt);
      if (!expireDate) return false;
      const now = new Date();
      return expireDate <= now;
    },

    normalizeSharePath(sharePath) {
      if (!sharePath) return '/';
      const normalized = String(sharePath)
        .replace(/\\/g, '/')
        .replace(/\/+/g, '/');

      if (!normalized || normalized === '.') {
        return '/';
      }

      return normalized.startsWith('/') ? normalized : `/${normalized}`;
    },

    getPathBaseName(pathValue, preferredName = '') {
      if (typeof preferredName === 'string' && preferredName.trim()) {
        return preferredName.trim();
      }

      const normalized = this.normalizeSharePath(pathValue || '/');
      if (normalized === '/') {
        return '/';
      }

      const segments = normalized.split('/').filter(Boolean);
      return segments.length > 0 ? segments[segments.length - 1] : '/';
    },

    isShareAllFiles(share) {
      if (!share) return false;
      const shareType = share.share_type || 'file';
      const sharePath = this.normalizeSharePath(share.share_path || '/');
      return shareType === 'all' || (shareType === 'directory' && sharePath === '/');
    },

    hasSharePassword(share) {
      if (!share) return false;
      if (typeof share.has_password === 'boolean') {
        return share.has_password;
      }
      return !!share.share_password;
    },

    getShareTypeIcon(share) {
      if (this.isShareAllFiles(share)) return 'fa-layer-group';
      if ((share?.share_type || 'file') === 'directory') return 'fa-folder';
      return 'fa-file-alt';
    },

    // 分享类型标签
    getShareTypeLabel(shareOrType, sharePath) {
      const share = typeof shareOrType === 'object' && shareOrType !== null
        ? shareOrType
        : { share_type: shareOrType, share_path: sharePath };

      if (this.isShareAllFiles(share)) {
        return '全部文件';
      }

      switch (share.share_type) {
        case 'directory': return '文件夹';
        case 'file':
        default: return '文件';
      }
    },

    // 分享状态标签
    getShareStatus(share) {
      if (this.isExpired(share.expires_at)) {
        return { text: '已过期', class: 'danger', icon: 'fa-clock' };
      }
      if (this.isExpiringSoon(share.expires_at)) {
        return { text: '即将到期', class: 'warn', icon: 'fa-hourglass-half' };
      }
      return { text: '有效', class: 'success', icon: 'fa-check-circle' };
    },

    // 分享保护标签
    getShareProtection(share) {
      if (this.hasSharePassword(share)) {
        return { text: '已加密', class: 'info', icon: 'fa-lock' };
      }
      return { text: '公开', class: 'info', icon: 'fa-unlock' };
    },

    // 存储来源
    getStorageLabel(storageType) {
      if (!storageType) return '默认';
      return storageType === 'local' ? '本地存储' : storageType.toUpperCase();
    },

    // 格式化时间
    formatDateTime(value) {
      if (!value) return '--';
      const d = this.parseDateValue(value);
      if (!d) return value;
      return d.toLocaleString();
    },

    // HTML实体解码（前端兜底，防止已实体化的文件名显示乱码）
    decodeHtmlEntities(str) {
      if (typeof str !== 'string') return '';
      const entityMap = {
        amp: '&',
        lt: '<',
        gt: '>',
        quot: '"',
        apos: "'",
        '#x27': "'",
        '#x2F': '/',
        '#x60': '`'
      };
      const decodeOnce = (input) =>
        input.replace(/&(#x[0-9a-fA-F]+|#\d+|[a-zA-Z]+);/g, (match, code) => {
          if (code[0] === '#') {
            const isHex = code[1]?.toLowerCase() === 'x';
            const num = isHex ? parseInt(code.slice(2), 16) : parseInt(code.slice(1), 10);
            if (!Number.isNaN(num)) {
              return String.fromCharCode(num);
            }
            return match;
          }
          const mapped = entityMap[code];
          return mapped !== undefined ? mapped : match;
        });
      let output = str;
      let decoded = decodeOnce(output);
      while (decoded !== output) {
        output = decoded;
        decoded = decodeOnce(output);
      }
      return output;
    },

    getFileDisplayName(file) {
      if (!file) return '';
      const base = (typeof file.displayName === 'string' && file.displayName !== '')
        ? file.displayName
        : (typeof file.name === 'string' ? file.name : '');
      const decoded = this.decodeHtmlEntities(base);
      return decoded || base || '';
    },

    openShare(url) {
      if (!url) return;
      // 仅在真正被拦截（返回 null/undefined）时提示，避免已打开仍误报
      let newWindow = null;
      try {
        newWindow = window.open(url, '_blank');
      } catch (error) {
        newWindow = null;
      }

      if (newWindow) {
        try {
          // 防止新窗口通过 opener 反向控制当前页面
          newWindow.opener = null;
        } catch (e) {
          // 忽略跨域写 opener 失败
        }
        return;
      }

      this.showToast('info', '提示', '浏览器阻止了新标签页，请允许弹窗或手动打开链接');
    },

    copyTextToClipboard(text, successMessage = '已复制到剪贴板') {
      if (!text) {
        this.showToast('warning', '提示', '没有可复制的内容');
        return;
      }

      if (navigator.clipboard && navigator.clipboard.writeText) {
        navigator.clipboard.writeText(text).then(() => {
          this.showToast('success', '成功', successMessage);
        }).catch(() => {
          this.fallbackCopyToClipboard(text, successMessage);
        });
      } else {
        this.fallbackCopyToClipboard(text, successMessage);
      }
    },

    copyShareLink(url) {
      this.copyTextToClipboard(url, '分享链接已复制到剪贴板');
    },

    copyDirectLink(url) {
      this.copyTextToClipboard(url, '直链已复制到剪贴板');
    },

    copySharePassword(password) {
      this.copyTextToClipboard(password, '访问密码已复制到剪贴板');
    },

    fallbackCopyToClipboard(text, successMessage = '已复制到剪贴板') {
      // 备用复制方法
      const textArea = document.createElement('textarea');
      textArea.value = text;
      textArea.style.position = 'fixed';
      textArea.style.left = '-999999px';
      document.body.appendChild(textArea);
      textArea.select();
      try {
        document.execCommand('copy');
        this.showToast('success', '成功', successMessage);
      } catch (err) {
        this.showToast('error', '错误', '复制失败，请手动复制');
      }
      document.body.removeChild(textArea);
    },

    // ===== 管理员功能 =====

    calculateAdminUserStats(users = []) {
      const stats = {
        active: 0,
        banned: 0,
        unverified: 0,
        download_blocked: 0
      };
      for (const user of users) {
        const statusTag = this.getAdminUserStatusTag(user);
        if (Object.prototype.hasOwnProperty.call(stats, statusTag)) {
          stats[statusTag] += 1;
        }
      }
      return stats;
    },

    buildAdminUsersQueryParams() {
      const params = {
        paged: 1,
        page: Math.max(1, Number(this.adminUsersPage) || 1),
        pageSize: Math.max(1, Number(this.adminUsersPageSize) || 20),
        sort: this.adminUserFilters.sort || 'created_desc'
      };

      const keyword = String(this.adminUserFilters.keyword || '').trim();
      if (keyword) params.keyword = keyword;
      if (this.adminUserFilters.role && this.adminUserFilters.role !== 'all') {
        params.role = this.adminUserFilters.role;
      }
      if (this.adminUserFilters.status && this.adminUserFilters.status !== 'all') {
        params.status = this.adminUserFilters.status;
      }
      if (this.adminUserFilters.storage && this.adminUserFilters.storage !== 'all') {
        params.storage = this.adminUserFilters.storage;
      }

      return params;
    },

    async loadUsers(options = {}) {
      const resetPage = options && options.resetPage === true;
      if (resetPage) {
        this.adminUsersPage = 1;
      }

      this.adminUsersLoading = true;
      try {
        const response = await axios.get(`${this.apiBase}/api/admin/users`, {
          params: this.buildAdminUsersQueryParams()
        });

        if (response.data.success) {
          const rows = Array.isArray(response.data.users) ? response.data.users : [];
          this.adminUsers = rows;

          const rawTotal = Number(response.data?.pagination?.total);
          const totalCount = Number.isFinite(rawTotal) && rawTotal >= 0
            ? Math.floor(rawTotal)
            : rows.length;
          this.adminUsersTotalCount = totalCount;

          const rawTotalPages = Number(response.data?.pagination?.totalPages);
          this.adminUsersTotalPages = Number.isFinite(rawTotalPages) && rawTotalPages > 0
            ? Math.floor(rawTotalPages)
            : Math.max(1, Math.ceil(totalCount / Math.max(1, Number(this.adminUsersPageSize) || 20)));

          const rawPage = Number(response.data?.pagination?.page);
          const nextPage = Number.isFinite(rawPage) && rawPage > 0
            ? Math.floor(rawPage)
            : this.adminUsersPage;
          this.adminUsersPage = Math.min(Math.max(1, nextPage), this.adminUsersTotalPages);

          const summary = response.data?.summary || {};
          const rawGlobalTotal = Number(summary.global_total);
          this.adminUsersGlobalCount = Number.isFinite(rawGlobalTotal) && rawGlobalTotal >= 0
            ? Math.floor(rawGlobalTotal)
            : this.adminUsersTotalCount;

          const fallbackStats = this.calculateAdminUserStats(rows);
          this.adminUserStats = {
            active: Number.isFinite(Number(summary.active)) ? Math.max(0, Math.floor(Number(summary.active))) : fallbackStats.active,
            banned: Number.isFinite(Number(summary.banned)) ? Math.max(0, Math.floor(Number(summary.banned))) : fallbackStats.banned,
            unverified: Number.isFinite(Number(summary.unverified)) ? Math.max(0, Math.floor(Number(summary.unverified))) : fallbackStats.unverified,
            download_blocked: Number.isFinite(Number(summary.download_blocked)) ? Math.max(0, Math.floor(Number(summary.download_blocked))) : fallbackStats.download_blocked
          };
        }
      } catch (error) {
        console.error('加载用户列表失败:', error);
        this.showToast('error', '加载失败', error.response?.data?.message || error.message);
      } finally {
        this.adminUsersLoading = false;
      }
    },


    async banUser(userId, banned) {
      const action = banned ? '封禁' : '解封';
      if (!confirm(`确定要${action}这个用户吗？`)) return;

      try {
        const response = await axios.post(
          `${this.apiBase}/api/admin/users/${userId}/ban`,
          { banned },
        );

        if (response.data.success) {
          this.showToast('success', '成功', response.data.message);
          this.loadUsers();
        }
      } catch (error) {
        console.error('操作失败:', error);
        this.showToast('error', '操作失败', error.response?.data?.message || error.message);
      }
    },

    async deleteUser(userId) {
      if (!confirm('确定要删除这个用户吗？此操作不可恢复！')) return;

      try {
        const response = await axios.delete(`${this.apiBase}/api/admin/users/${userId}`);

        if (response.data.success) {
          this.showToast('success', '成功', '用户已删除');
          this.loadUsers();
        }
      } catch (error) {
        console.error('删除用户失败:', error);
        this.showToast('error', '删除失败', error.response?.data?.message || error.message);
      }
    },

    // ===== 忘记密码功能 =====

    async requestPasswordReset() {
      if (!this.forgotPasswordForm.email) {
        this.showToast('error', '错误', '请输入注册邮箱');
        return;
      }
      if (!this.forgotPasswordForm.captcha) {
        this.showToast('error', '错误', '请输入验证码');
        return;
      }

      this.passwordResetting = true;
      try {
        const response = await axios.post(
          `${this.apiBase}/api/password/forgot`,
          this.forgotPasswordForm
        );

        if (response.data.success) {
          this.showToast('success', '成功', '如果邮箱存在，将收到重置邮件');
          this.showForgotPasswordModal = false;
          this.forgotPasswordForm = { email: '', captcha: '' };
          this.forgotPasswordCaptchaUrl = '';
        }
      } catch (error) {
        console.error('提交密码重置请求失败:', error);
        this.showToast('error', '错误', error.response?.data?.message || '提交失败');
        // 刷新验证码
        this.forgotPasswordForm.captcha = '';
        this.refreshForgotPasswordCaptcha();
      } finally {
        this.passwordResetting = false;
      }
    },

    async submitResetPassword() {
      if (!this.resetPasswordForm.token || !this.resetPasswordForm.new_password || this.resetPasswordForm.new_password.length < 6) {
        this.showToast('error', '错误', '请输入有效的重置链接和新密码（至少6位）');
        return;
      }
      this.passwordResetting = true;
      try {
        const response = await axios.post(`${this.apiBase}/api/password/reset`, this.resetPasswordForm);
        if (response.data.success) {
          this.verifyMessage = '密码已重置，请登录';
          this.isLogin = true;
          this.showResetPasswordModal = false;
          this.resetPasswordForm = { token: '', new_password: '' };
          // 清理URL中的token
          this.sanitizeUrlToken('resetToken');
        }
      } catch (error) {
        console.error('密码重置失败:', error);
        this.showToast('error', '错误', error.response?.data?.message || '重置失败');
      } finally {
        this.passwordResetting = false;
      }
    },

    // ===== 管理员：文件审查功能 =====

    async openFileInspection(user) {
      this.inspectionUser = user;
      this.inspectionPath = '/';
      this.showFileInspectionModal = true;
      await this.loadUserFiles('/');
    },

    async loadUserFiles(path) {
      this.inspectionLoading = true;
      this.inspectionPath = path;

      try {
        const response = await axios.get(
          `${this.apiBase}/api/admin/users/${this.inspectionUser.id}/files`,
          {
            params: { path }
          }
        );

        if (response.data.success) {
          this.inspectionFiles = response.data.items;
        }
      } catch (error) {
        console.error('加载用户文件失败:', error);
        this.showToast('error', '错误', error.response?.data?.message || '加载文件失败');
      } finally {
        this.inspectionLoading = false;
      }
    },

    handleInspectionFileClick(file) {
      if (file.isDirectory) {
        const newPath = this.inspectionPath === '/'
          ? `/${file.name}`
          : `${this.inspectionPath}/${file.name}`;
        this.loadUserFiles(newPath);
      }
    },

    navigateInspectionToRoot() {
      this.loadUserFiles('/');
    },

    navigateInspectionUp() {
      if (this.inspectionPath === '/') return;
      const lastSlash = this.inspectionPath.lastIndexOf('/');
      const parentPath = lastSlash > 0 ? this.inspectionPath.substring(0, lastSlash) : '/';
      this.loadUserFiles(parentPath);
    },

    // ===== 存储管理 =====

    // 加载用户个人资料（包含存储信息）
    async loadUserProfile() {
      try {
        const response = await axios.get(
          `${this.apiBase}/api/user/profile`,
        );

        if (response.data.success && response.data.user) {
          const user = response.data.user;
          // 同步用户信息（含 has_oss_config）
          this.user = { ...(this.user || {}), ...user };

          // 检测存储配置是否被管理员更改
          const oldStorageType = this.storageType;
          const oldStoragePermission = this.storagePermission;
          const newStorageType = user.current_storage_type || 'oss';
          const newStoragePermission = user.storage_permission || 'oss_only';

          // 更新本地数据
          this.localQuota = user.local_storage_quota || 0;
          this.localUsed = user.local_storage_used || 0;
          this.storagePermission = newStoragePermission;
          this.storageType = newStorageType;

          // 首次加载仅同步，不提示
          if (!this.profileInitialized) {
            this.profileInitialized = true;
            return;
          }

          // 如果存储类型被管理员更改，通知用户并重新加载文件
          if (oldStorageType !== newStorageType || oldStoragePermission !== newStoragePermission) {
            console.log('[存储配置更新] 旧类型:', oldStorageType, '新类型:', newStorageType);
            console.log('[存储配置更新] 旧权限:', oldStoragePermission, '新权限:', newStoragePermission);

            if (!this.suppressStorageToast) {
              this.showToast('info', '存储配置已更新', `管理员已将您的存储方式更改为${newStorageType === 'local' ? '本地存储' : 'OSS存储'}`);
            } else {
              this.suppressStorageToast = false;
            }

            // 如果当前在文件页面，重新加载文件列表
            if (this.currentView === 'files') {
              await this.loadFiles(this.currentPath);
            }
          }
        }
      } catch (error) {
        console.error('加载用户资料失败:', error);
      }
    },

    // 加载OSS空间使用统计
    async loadOssUsage() {
      // 检查是否有可用的OSS配置（个人配置或系统级统一配置）
      if (!this.user || this.user?.oss_config_source === 'none') {
        this.ossUsage = null;
        return;
      }

      this.ossUsageLoading = true;
      this.ossUsageError = null;

      try {
        const response = await axios.get(
          `${this.apiBase}/api/user/oss-usage`,
        );

        if (response.data.success) {
          this.ossUsage = response.data.usage;
          if (this.user && this.ossUsage && Number.isFinite(Number(this.ossUsage.totalSize))) {
            this.user.storage_used = Number(this.ossUsage.totalSize);
          }
        }
      } catch (error) {
        console.error('获取OSS空间使用情况失败:', error);
        this.ossUsageError = error.response?.data?.message || '获取失败';
      } finally {
        this.ossUsageLoading = false;
      }
    },

    async loadDownloadTrafficReport(days = this.downloadTrafficReport.days) {
      if (!this.isLoggedIn || !this.user || this.user.is_admin) {
        return;
      }

      const allowedDays = [7, 30, 90, 180];
      const normalizedDays = allowedDays.includes(Number(days)) ? Number(days) : 30;

      this.downloadTrafficReport.days = normalizedDays;
      this.downloadTrafficReport.loading = true;
      this.downloadTrafficReport.error = null;

      try {
        const response = await axios.get(
          `${this.apiBase}/api/user/download-traffic-report?days=${normalizedDays}`,
        );

        if (response.data.success) {
          const quota = response.data.quota || null;
          const report = response.data.report || {};
          this.downloadTrafficReport.quota = quota;
          this.downloadTrafficReport.daily = Array.isArray(report.daily) ? report.daily : [];
          this.downloadTrafficReport.summary = report.summary || null;
          this.downloadTrafficReport.lastUpdatedAt = new Date().toISOString();

          // 同步到 user 对象，保证文件页/设置页显示一致
          if (this.user && quota) {
            this.user.download_traffic_quota = Number(quota.quota || 0);
            this.user.download_traffic_used = Number(quota.used || 0);
            this.user.download_traffic_reset_cycle = quota.reset_cycle || 'none';
            this.user.download_traffic_quota_expires_at = quota.expires_at || null;
            this.user.download_traffic_last_reset_at = quota.last_reset_at || null;
          }
        } else {
          this.downloadTrafficReport.error = response.data.message || '获取报表失败';
        }
      } catch (error) {
        console.error('获取下载流量报表失败:', error);
        this.downloadTrafficReport.error = error.response?.data?.message || '获取报表失败';
      } finally {
        this.downloadTrafficReport.loading = false;
      }
    },

    setDownloadTrafficReportDays(days) {
      const nextDays = Number(days);
      if (this.downloadTrafficReport.loading || nextDays === this.downloadTrafficReport.days) {
        return;
      }
      this.loadDownloadTrafficReport(nextDays);
    },

    formatReportDateLabel(dateKey) {
      if (!dateKey) return '-';
      const match = String(dateKey).match(/^(\d{4})-(\d{2})-(\d{2})$/);
      if (match) {
        return `${match[2]}-${match[3]}`;
      }
      return dateKey;
    },

    // 刷新存储空间使用统计（根据当前存储类型）
    async refreshStorageUsage() {
      if (this.storageType === 'oss' && this.user?.oss_config_source !== 'none') {
        // 刷新 OSS 空间统计
        await this.loadOssUsage();
      } else if (this.storageType === 'local') {
        // 刷新本地存储统计（通过重新获取用户信息）
        await this.loadUserProfile();
      }
    },

    // 启动定期检查用户配置
    startProfileSync() {
      // 清除已有的定时器
      if (this.profileCheckInterval) {
        clearInterval(this.profileCheckInterval);
      }

      // 每30秒检查一次用户配置是否有更新
      this.profileCheckInterval = setInterval(() => {
        // 注意：token 通过 HttpOnly Cookie 管理，仅检查 isLoggedIn
        if (this.isLoggedIn) {
          this.loadUserProfile();
        }
      }, 30000); // 30秒

      console.log('[配置同步] 已启动定期检查（30秒间隔）');
    },

    // 停止定期检查
    stopProfileSync() {
      if (this.profileCheckInterval) {
        clearInterval(this.profileCheckInterval);
        this.profileCheckInterval = null;
        console.log('[配置同步] 已停止定期检查');
      }
    },

    // 用户切换存储方式
    async switchStorage(type) {
      if (this.storageSwitching || type === this.storageType) {
        return;
      }

      const confirmMessage = type === 'local'
        ? '切换到本地存储不会自动迁移 OSS 文件。切换后只会显示本地文件，确认继续？'
        : '切换到 OSS 存储不会自动迁移本地文件。切换后只会显示 OSS 文件，确认继续？';
      if (!window.confirm(confirmMessage)) {
        return;
      }

      // 不再弹出配置引导弹窗，直接尝试切换
      // 如果后端检测到没有OSS配置，会返回错误提示

      this.storageSwitching = true;
      this.storageSwitchTarget = type;

      try {
        const response = await axios.post(
          `${this.apiBase}/api/user/switch-storage`,
          { storage_type: type },
        );

        if (response.data.success) {
          this.storageType = type;
          // 用户主动切换后，下一次配置同步不提示管理员修改
          this.suppressStorageToast = true;
          this.showToast('success', '成功', `已切换到${type === 'local' ? '本地存储' : 'OSS存储'}`);

          // 重新加载文件列表
          if (this.currentView === 'files') {
            this.loadFiles(this.currentPath);
          }
        }
      } catch (error) {
        console.error('切换存储失败:', error);
        this.showToast('error', '错误', error.response?.data?.message || '切换存储失败');
      } finally {
        this.storageSwitching = false;
        this.storageSwitchTarget = null;
      }
    },

    ensureOssConfigSection() {
      this.openOssConfigModal();
    },

    openOssGuideModal() {
      this.showOssGuideModal = true;
    },

    closeOssGuideModal() {
      this.showOssGuideModal = false;
    },

    proceedOssGuide() {
      this.showOssGuideModal = false;
      this.ensureOssConfigSection();
    },

    openOssConfigModal() {
      // 只有管理员才能配置OSS
      if (!this.user?.is_admin) {
        this.showToast('error', '权限不足', '只有管理员才能配置OSS服务');
        return;
      }
      this.showOssGuideModal = false;
      this.showOssConfigModal = true;
      if (this.user && !this.user.is_admin) {
        this.loadOssConfig();
      }
    },

    closeOssConfigModal() {
      this.showOssConfigModal = false;
    },

    // 检查视图权限
    isViewAllowed(view) {
      if (!this.isLoggedIn) return false;
      const commonViews = ['files', 'shares', 'settings'];
      if (view === 'admin') {
        return !!(this.user && this.user.is_admin);
      }
      return commonViews.includes(view);
    },

    // 切换视图并自动刷新数据
    switchView(view, force = false) {
      if (this.isLoggedIn && !this.isViewAllowed(view)) {
        return;
      }
      // 如果已经在当前视图，不重复刷新
      if (!force && this.currentView === view) {
        return;
      }

      this.currentView = view;

      // 根据视图类型自动加载对应数据
      switch (view) {
        case 'files':
          // 切换到文件视图时，重新加载文件列表
          this.loadFiles(this.currentPath);
          break;
        case 'shares':
          // 切换到分享视图时，重新加载分享列表
          this.refreshShareResources();
          break;
        case 'admin':
          // 切换到管理后台时，重新加载用户列表、健康检测和系统日志
          if (this.user && this.user.is_admin) {
            this.loadUsers();
            this.loadServerStorageStats();
            if (this.adminTab === 'monitor') {
              this.initMonitorTab();
            } else {
              this.loadHealthCheck();
              this.loadSystemLogs(1);
            }
          }
          break;
        case 'settings':
          this.loadOnlineDevices();
          if (this.user && !this.user.is_admin) {
            this.loadDownloadTrafficReport();
          }
          break;
      }
    },

    // 管理员：打开编辑用户存储权限模态框
    openEditStorageModal(user) {
      this.editStorageForm.userId = user.id;
      this.editStorageForm.username = user.username;
      this.editStorageForm.storage_permission = user.storage_permission || 'oss_only';
      
      // 智能识别本地配额单位
      const localQuotaBytes = Number(user.local_storage_quota || DEFAULT_LOCAL_STORAGE_QUOTA_BYTES);
      const localQuotaMB = localQuotaBytes / 1024 / 1024;
      const localQuotaGB = localQuotaMB / 1024;

      if (localQuotaMB >= 1024 && localQuotaMB % 1024 === 0) {
        this.editStorageForm.local_storage_quota_value = localQuotaGB;
        this.editStorageForm.quota_unit = 'GB';
      } else {
        this.editStorageForm.local_storage_quota_value = Math.max(1, Math.round(localQuotaMB));
        this.editStorageForm.quota_unit = 'MB';
      }

      // 智能识别 OSS 配额单位（未配置时默认 1GB）
      const ossQuotaBytes = Number(user.oss_storage_quota || DEFAULT_OSS_STORAGE_QUOTA_BYTES);
      const effectiveOssQuotaBytes = Number.isFinite(ossQuotaBytes) && ossQuotaBytes > 0
        ? ossQuotaBytes
        : DEFAULT_OSS_STORAGE_QUOTA_BYTES;
      const mb = 1024 * 1024;
      const gb = 1024 * 1024 * 1024;
      const tb = 1024 * 1024 * 1024 * 1024;

      this.editStorageForm.oss_quota_unlimited = false;
      if (effectiveOssQuotaBytes >= tb && effectiveOssQuotaBytes % tb === 0) {
        this.editStorageForm.oss_storage_quota_value = effectiveOssQuotaBytes / tb;
        this.editStorageForm.oss_quota_unit = 'TB';
      } else if (effectiveOssQuotaBytes >= gb && effectiveOssQuotaBytes % gb === 0) {
        this.editStorageForm.oss_storage_quota_value = effectiveOssQuotaBytes / gb;
        this.editStorageForm.oss_quota_unit = 'GB';
      } else {
        this.editStorageForm.oss_storage_quota_value = Math.max(1, Math.round(effectiveOssQuotaBytes / mb));
        this.editStorageForm.oss_quota_unit = 'MB';
      }

      // 下载流量配额（-1 表示不限，0 表示禁止下载）
      const downloadQuotaBytes = Number(user.download_traffic_quota || 0);
      const isDownloadUnlimited = Number.isFinite(downloadQuotaBytes) && downloadQuotaBytes < 0;
      const effectiveDownloadQuotaBytes = Number.isFinite(downloadQuotaBytes) && downloadQuotaBytes > 0
        ? downloadQuotaBytes
        : 0;
      const downloadUsedBytes = Number(user.download_traffic_used || 0);

      this.editStorageForm.download_traffic_used = Number.isFinite(downloadUsedBytes) && downloadUsedBytes > 0
        ? Math.floor(downloadUsedBytes)
        : 0;
      this.editStorageForm.download_quota_operation = 'set';
      this.editStorageForm.download_quota_adjust_value = 1;
      this.editStorageForm.download_quota_adjust_unit = 'GB';
      this.editStorageForm.download_quota_reset_cycle = user.download_traffic_reset_cycle || 'none';
      this.editStorageForm.download_quota_expires_at = this.toDateTimeLocalInput(user.download_traffic_quota_expires_at);
      this.editStorageForm.reset_download_used_now = false;

      this.editStorageForm.download_quota_unlimited = isDownloadUnlimited;
      if (isDownloadUnlimited) {
        this.editStorageForm.download_traffic_quota_value = 1;
        this.editStorageForm.download_quota_unit = 'GB';
      } else if (effectiveDownloadQuotaBytes <= 0) {
        this.editStorageForm.download_traffic_quota_value = 0;
        this.editStorageForm.download_quota_unit = 'MB';
      } else if (effectiveDownloadQuotaBytes >= tb && effectiveDownloadQuotaBytes % tb === 0) {
        this.editStorageForm.download_traffic_quota_value = effectiveDownloadQuotaBytes / tb;
        this.editStorageForm.download_quota_unit = 'TB';
      } else if (effectiveDownloadQuotaBytes >= gb && effectiveDownloadQuotaBytes % gb === 0) {
        this.editStorageForm.download_traffic_quota_value = effectiveDownloadQuotaBytes / gb;
        this.editStorageForm.download_quota_unit = 'GB';
      } else {
        this.editStorageForm.download_traffic_quota_value = Math.max(1, Math.round(effectiveDownloadQuotaBytes / mb));
        this.editStorageForm.download_quota_unit = 'MB';
      }

      this.showEditStorageModal = true;
    },

    // 管理员：更新用户存储权限
    async updateUserStorage() {
      try {
        // 计算本地配额（字节）
        if (!this.editStorageForm.local_storage_quota_value || this.editStorageForm.local_storage_quota_value < 1) {
          this.showToast('error', '参数错误', '本地配额必须大于 0');
          return;
        }

        let localQuotaBytes;
        if (this.editStorageForm.quota_unit === 'GB') {
          localQuotaBytes = this.editStorageForm.local_storage_quota_value * 1024 * 1024 * 1024;
        } else {
          localQuotaBytes = this.editStorageForm.local_storage_quota_value * 1024 * 1024;
        }

        // 计算 OSS 配额（字节）
        if (!this.editStorageForm.oss_storage_quota_value || this.editStorageForm.oss_storage_quota_value < 1) {
          this.showToast('error', '参数错误', 'OSS 配额必须大于 0');
          return;
        }

        let ossQuotaBytes;
        if (this.editStorageForm.oss_quota_unit === 'TB') {
          ossQuotaBytes = this.editStorageForm.oss_storage_quota_value * 1024 * 1024 * 1024 * 1024;
        } else if (this.editStorageForm.oss_quota_unit === 'GB') {
          ossQuotaBytes = this.editStorageForm.oss_storage_quota_value * 1024 * 1024 * 1024;
        } else {
          ossQuotaBytes = this.editStorageForm.oss_storage_quota_value * 1024 * 1024;
        }

        const toBytes = (value, unit) => {
          if (unit === 'TB') return value * 1024 * 1024 * 1024 * 1024;
          if (unit === 'GB') return value * 1024 * 1024 * 1024;
          return value * 1024 * 1024;
        };

        // 下载流量：支持直接设置 / 增加 / 删减
        let downloadQuotaBytes = null;
        let downloadTrafficDelta = null;
        if (this.editStorageForm.download_quota_operation === 'set') {
          if (this.editStorageForm.download_quota_unlimited) {
            downloadQuotaBytes = -1;
          } else {
            if (this.editStorageForm.download_traffic_quota_value === null || this.editStorageForm.download_traffic_quota_value === undefined || this.editStorageForm.download_traffic_quota_value < 0) {
              this.showToast('error', '参数错误', '下载流量配额必须大于等于 0，或选择不限流量');
              return;
            }
            downloadQuotaBytes = toBytes(
              this.editStorageForm.download_traffic_quota_value,
              this.editStorageForm.download_quota_unit
            );
          }
        } else {
          if (!this.editStorageForm.download_quota_adjust_value || this.editStorageForm.download_quota_adjust_value < 1) {
            this.showToast('error', '参数错误', '下载流量增减值必须大于 0');
            return;
          }
          const adjustBytes = toBytes(
            this.editStorageForm.download_quota_adjust_value,
            this.editStorageForm.download_quota_adjust_unit
          );
          downloadTrafficDelta = this.editStorageForm.download_quota_operation === 'increase'
            ? adjustBytes
            : -adjustBytes;
        }

        const downloadQuotaExpiresAt = this.normalizeDateTimeLocalToApi(this.editStorageForm.download_quota_expires_at);
        if (this.editStorageForm.download_quota_expires_at && !downloadQuotaExpiresAt) {
          this.showToast('error', '参数错误', '下载流量到期时间格式无效');
          return;
        }

        const payload = {
          storage_permission: this.editStorageForm.storage_permission,
          local_storage_quota: localQuotaBytes,
          oss_storage_quota: ossQuotaBytes,
          download_traffic_quota_expires_at: downloadQuotaExpiresAt,
          download_traffic_reset_cycle: this.editStorageForm.download_quota_reset_cycle || 'none',
          reset_download_traffic_used: !!this.editStorageForm.reset_download_used_now
        };
        if (downloadQuotaBytes !== null) {
          payload.download_traffic_quota = downloadQuotaBytes;
        }
        if (downloadTrafficDelta !== null) {
          payload.download_traffic_delta = downloadTrafficDelta;
        }

        const response = await axios.post(
          `${this.apiBase}/api/admin/users/${this.editStorageForm.userId}/storage-permission`,
          payload,
        );

        if (response.data.success) {
          this.showToast('success', '成功', '存储权限已更新');
          this.showEditStorageModal = false;
          this.loadUsers();
        }
      } catch (error) {
        console.error('更新存储权限失败:', error);
        this.showToast('error', '错误', error.response?.data?.message || '更新失败');
      }
    },

    // ===== 工具函数 =====

    setAdminUsersPage(page) {
      const nextPage = Number(page) || 1;
      if (nextPage < 1) {
        if (this.adminUsersPage !== 1) {
          this.adminUsersPage = 1;
          this.loadUsers();
        }
        return;
      }
      const maxPage = this.adminUsersTotalPages;
      const targetPage = Math.min(nextPage, maxPage);
      if (targetPage === this.adminUsersPage) return;
      this.adminUsersPage = targetPage;
      this.loadUsers();
    },

    triggerAdminUsersKeywordSearch() {
      this.adminUsersPage = 1;
      if (!this._debouncedAdminUsersQuery) {
        this._debouncedAdminUsersQuery = this.debounce(() => {
          this.loadUsers();
        }, 260);
      }
      this._debouncedAdminUsersQuery();
    },

    handleAdminUsersFilterChange() {
      this.adminUsersPage = 1;
      this.loadUsers();
    },

    handleAdminUsersPageSizeChange() {
      this.adminUsersPage = 1;
      this.loadUsers();
    },

    resetAdminUserFilters() {
      this.adminUserFilters = {
        keyword: '',
        role: 'all',
        status: 'all',
        storage: 'all',
        sort: 'created_desc'
      };
      this.adminUsersPageSize = 20;
      this.adminUsersPage = 1;
      this.loadUsers();
    },

    getAdminUserStatusTag(user) {
      if (user?.is_banned) return 'banned';
      if (!user?.is_verified) return 'unverified';
      const quota = Number(user?.download_traffic_quota);
      const used = Number(user?.download_traffic_used || 0);
      if (Number.isFinite(quota) && quota >= 0 && (quota === 0 || used >= quota)) {
        return 'download_blocked';
      }
      return 'active';
    },

    getAdminUserStatusLabel(user) {
      const tag = this.getAdminUserStatusTag(user);
      if (tag === 'banned') return '已封禁';
      if (tag === 'unverified') return '未激活';
      if (tag === 'download_blocked') return '下载受限';
      return '正常';
    },

    escapeHtml(value) {
      return String(value ?? '')
        .replace(/&/g, '&amp;')
        .replace(/</g, '&lt;')
        .replace(/>/g, '&gt;')
        .replace(/"/g, '&quot;')
        .replace(/'/g, '&#39;');
    },

    escapeRegExp(value) {
      return String(value ?? '').replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
    },

    getHighlightedText(value, keyword) {
      const text = this.escapeHtml(value || '-');
      const search = String(keyword || '').trim();
      if (!search) return text;
      const reg = new RegExp(this.escapeRegExp(search), 'ig');
      return text.replace(reg, (match) => `<mark class="admin-search-hit">${match}</mark>`);
    },

    formatBytes(bytes) {
      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 Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i];
    },

    getAdminUserQuotaPercentage(user) {
      const quota = Number(user?.local_storage_quota || 0);
      const used = Number(user?.local_storage_used || 0);
      if (!Number.isFinite(quota) || quota <= 0) return 0;
      if (!Number.isFinite(used) || used <= 0) return 0;
      return Math.round((used / quota) * 100);
    },

    getAdminUserOssQuotaPercentage(user) {
      const quota = Number(user?.oss_storage_quota || DEFAULT_OSS_STORAGE_QUOTA_BYTES);
      const used = Number(user?.storage_used || 0);
      if (!Number.isFinite(quota) || quota <= 0) return 0;
      if (!Number.isFinite(used) || used <= 0) return 0;
      return Math.min(100, Math.round((used / quota) * 100));
    },

    getAdminUserDownloadQuotaPercentage(user) {
      const quota = Number(user?.download_traffic_quota || 0);
      const used = Number(user?.download_traffic_used || 0);
      if (!Number.isFinite(quota)) return 0;
      if (quota < 0) return 0;
      if (quota === 0) return 100;
      if (!Number.isFinite(used) || used <= 0) return 0;
      return Math.min(100, Math.round((used / quota) * 100));
    },

    getDownloadResetCycleText(cycle) {
      if (cycle === 'daily') return '每日重置';
      if (cycle === 'weekly') return '每周重置';
      if (cycle === 'monthly') return '每月重置';
      return '不自动重置';
    },

    parseDateValue(value) {
      if (!value) return null;
      if (value instanceof Date) {
        return Number.isNaN(value.getTime()) ? null : value;
      }

      const raw = String(value).trim();
      if (!raw) return null;

      const localMatch = raw.match(/^(\d{4})-(\d{2})-(\d{2})(?:[ T](\d{2}):(\d{2})(?::(\d{2}))?)?$/);
      if (localMatch) {
        const year = Number(localMatch[1]);
        const month = Number(localMatch[2]);
        const day = Number(localMatch[3]);
        const hour = Number(localMatch[4] || 0);
        const minute = Number(localMatch[5] || 0);
        const second = Number(localMatch[6] || 0);

        const localDate = new Date(year, month - 1, day, hour, minute, second);
        return Number.isNaN(localDate.getTime()) ? null : localDate;
      }

      const normalized = raw.includes('T') ? raw : raw.replace(' ', 'T');
      const parsed = new Date(normalized);
      return Number.isNaN(parsed.getTime()) ? null : parsed;
    },

    toDateTimeLocalInput(dateString) {
      if (!dateString) return '';
      const normalized = String(dateString).trim().replace(' ', 'T');
      const match = normalized.match(/^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2})/);
      if (match) return match[1];

      const parsed = this.parseDateValue(dateString);
      if (!parsed) return '';
      const year = parsed.getFullYear();
      const month = String(parsed.getMonth() + 1).padStart(2, '0');
      const day = String(parsed.getDate()).padStart(2, '0');
      const hours = String(parsed.getHours()).padStart(2, '0');
      const minutes = String(parsed.getMinutes()).padStart(2, '0');
      return `${year}-${month}-${day}T${hours}:${minutes}`;
    },

    normalizeDateTimeLocalToApi(localValue) {
      if (!localValue) return null;
      const normalized = String(localValue).trim();
      if (!normalized) return null;
      const fullMatch = normalized.match(/^(\d{4}-\d{2}-\d{2})T(\d{2}:\d{2})(?::(\d{2}))?$/);
      if (!fullMatch) return null;
      const seconds = fullMatch[3] || '00';
      return `${fullMatch[1]} ${fullMatch[2]}:${seconds}`;
    },

    formatDate(dateString) {
      if (!dateString) return '-';

      const date = this.parseDateValue(dateString);
      if (!date) return String(dateString);
      const year = date.getFullYear();
      const month = String(date.getMonth() + 1).padStart(2, '0');
      const day = String(date.getDate()).padStart(2, '0');
      const hours = String(date.getHours()).padStart(2, '0');
      const minutes = String(date.getMinutes()).padStart(2, '0');
      return `${year}-${month}-${day} ${hours}:${minutes}`;
    },

    // ===== Toast通知 =====

    showToast(type, title, message) {
      const toast = {
        id: ++this.toastIdCounter,
        type,
        title,
        message,
        icon: type === 'error' ? 'fas fa-circle-exclamation' : type === 'success' ? 'fas fa-circle-check' : 'fas fa-circle-info',
        hiding: false
      };

      // 清除之前的所有通知，只保留最新的一个
      this.toasts = [toast];

      // 4.5秒后开始淡出动画
      setTimeout(() => {
        const index = this.toasts.findIndex(t => t.id === toast.id);
        if (index !== -1) {
          this.toasts[index].hiding = true;

          // 0.5秒后移除（动画时长）
          setTimeout(() => {
            const removeIndex = this.toasts.findIndex(t => t.id === toast.id);
            if (removeIndex !== -1) {
              this.toasts.splice(removeIndex, 1);
            }
          }, 500);
        }
      }, 4500);
    },

    // ===== 系统设置管理 =====

    async loadSystemSettings() {
      try {
        const response = await axios.get(`${this.apiBase}/api/admin/settings`);

        if (response.data.success) {
          const settings = response.data.settings;
          this.systemSettings.maxUploadSizeMB = Math.round(settings.max_upload_size / (1024 * 1024));
          // 加载全局主题设置
          console.log('[主题] 从服务器加载全局主题:', settings.global_theme);
          if (settings.global_theme) {
            this.globalTheme = settings.global_theme;
            console.log('[主题] globalTheme已设置为:', this.globalTheme);
          }
          if (settings.download_security && typeof settings.download_security === 'object') {
            const ds = settings.download_security;
            this.systemSettings.downloadSecurity.enabled = !!ds.enabled;
            this.systemSettings.downloadSecurity.same_ip_same_file.enabled = !!ds.same_ip_same_file?.enabled;
            this.systemSettings.downloadSecurity.same_ip_same_file.limit_5m = Math.max(1, Number(ds.same_ip_same_file?.limit_5m || 3));
            this.systemSettings.downloadSecurity.same_ip_same_file.limit_1h = Math.max(1, Number(ds.same_ip_same_file?.limit_1h || 10));
            this.systemSettings.downloadSecurity.same_ip_same_file.limit_1d = Math.max(1, Number(ds.same_ip_same_file?.limit_1d || 20));
            this.systemSettings.downloadSecurity.same_ip_same_user.enabled = !!ds.same_ip_same_user?.enabled;
            this.systemSettings.downloadSecurity.same_ip_same_user.limit_1h = Math.max(1, Number(ds.same_ip_same_user?.limit_1h || 80));
            this.systemSettings.downloadSecurity.same_ip_same_user.limit_1d = Math.max(1, Number(ds.same_ip_same_user?.limit_1d || 300));
            this.systemSettings.downloadSecurity.same_ip_same_file_min_interval.enabled = !!ds.same_ip_same_file_min_interval?.enabled;
            this.systemSettings.downloadSecurity.same_ip_same_file_min_interval.seconds = Math.max(1, Number(ds.same_ip_same_file_min_interval?.seconds || 2));
          }
          if (settings.smtp) {
            this.systemSettings.smtp.host = settings.smtp.host || '';
            this.systemSettings.smtp.port = settings.smtp.port || 465;
            this.systemSettings.smtp.secure = !!settings.smtp.secure;
            this.systemSettings.smtp.user = settings.smtp.user || '';
            this.systemSettings.smtp.from = settings.smtp.from || settings.smtp.user || '';
            this.systemSettings.smtp.has_password = !!settings.smtp.has_password;
            this.systemSettings.smtp.password = '';
          }
        }
      } catch (error) {
        console.error('加载系统设置失败:', error);
        this.showToast('error', '错误', '加载系统设置失败');
      }
    },

    async loadServerStorageStats() {
      try {
        const response = await axios.get(`${this.apiBase}/api/admin/storage-stats`);

        if (response.data.success) {
          this.serverStorageStats = response.data.stats;
        }
      } catch (error) {
        console.error('加载服务器存储统计失败:', error);
        this.showToast('error', '错误', '加载服务器存储统计失败');
      }
    },

  async updateSystemSettings() {
    try {
      const maxUploadSize = parseInt(this.systemSettings.maxUploadSizeMB) * 1024 * 1024;

      const payload = {
        max_upload_size: maxUploadSize,
        download_security: {
          enabled: !!this.systemSettings.downloadSecurity.enabled,
          same_ip_same_file: {
            enabled: !!this.systemSettings.downloadSecurity.same_ip_same_file.enabled,
            limit_5m: Math.max(1, Math.floor(Number(this.systemSettings.downloadSecurity.same_ip_same_file.limit_5m) || 3)),
            limit_1h: Math.max(1, Math.floor(Number(this.systemSettings.downloadSecurity.same_ip_same_file.limit_1h) || 10)),
            limit_1d: Math.max(1, Math.floor(Number(this.systemSettings.downloadSecurity.same_ip_same_file.limit_1d) || 20))
          },
          same_ip_same_user: {
            enabled: !!this.systemSettings.downloadSecurity.same_ip_same_user.enabled,
            limit_1h: Math.max(1, Math.floor(Number(this.systemSettings.downloadSecurity.same_ip_same_user.limit_1h) || 80)),
            limit_1d: Math.max(1, Math.floor(Number(this.systemSettings.downloadSecurity.same_ip_same_user.limit_1d) || 300))
          },
          same_ip_same_file_min_interval: {
            enabled: !!this.systemSettings.downloadSecurity.same_ip_same_file_min_interval.enabled,
            seconds: Math.max(1, Math.floor(Number(this.systemSettings.downloadSecurity.same_ip_same_file_min_interval.seconds) || 2))
          }
        },
        smtp: {
          host: this.systemSettings.smtp.host,
          port: this.systemSettings.smtp.port,
          secure: this.systemSettings.smtp.secure,
          user: this.systemSettings.smtp.user,
          from: this.systemSettings.smtp.from || this.systemSettings.smtp.user
        }
      };
      if (this.systemSettings.smtp.password) {
        payload.smtp.password = this.systemSettings.smtp.password;
      }

      const response = await axios.post(
        `${this.apiBase}/api/admin/settings`,
        payload
      );

      if (response.data.success) {
        this.showToast('success', '成功', '系统设置已更新');
        this.systemSettings.smtp.password = '';
      }
    } catch (error) {
      console.error('更新系统设置失败:', error);
      this.showToast('error', '错误', '更新系统设置失败');
    }
  },

    async testSmtp() {
      try {
        const response = await axios.post(
          `${this.apiBase}/api/admin/settings/test-smtp`,
          { to: this.systemSettings.smtp.user },
        );
        this.showToast('success', '成功', response.data.message || '测试邮件已发送');
      } catch (error) {
        console.error('测试SMTP失败:', error);
        this.showToast('error', '错误', error.response?.data?.message || '测试失败');
      }
    },

    // 打开监控标签页（带整体loading遮罩）
    openMonitorTab() {
      this.adminTab = 'monitor';
      this.initMonitorTab();
    },

    // 统一加载监控数据，避免初次渲染空态闪烁
    async initMonitorTab() {
      this.monitorTabLoading = true;
      this.healthCheck.loading = true;
      this.systemLogs.loading = true;
      this.reservationMonitor.loading = true;

      try {
        await Promise.all([
          this.loadHealthCheck(),
          this.loadSystemLogs(1),
          this.loadDownloadReservationMonitor(1)
        ]);
      } catch (e) {
        // 子方法内部已处理错误
      } finally {
        this.monitorTabLoading = false;
      }
    },

    // ===== 健康检测 =====

    async loadHealthCheck() {
      this.healthCheck.loading = true;
      try {
        const response = await axios.get(`${this.apiBase}/api/admin/health-check`);

        if (response.data.success) {
          this.healthCheck.overallStatus = response.data.overallStatus;
          this.healthCheck.summary = response.data.summary;
          this.healthCheck.checks = response.data.checks;
          this.healthCheck.lastCheck = response.data.timestamp;
        }
      } catch (error) {
        console.error('健康检测失败:', error);
        this.showToast('error', '错误', '健康检测失败');
      } finally {
        this.healthCheck.loading = false;
      }
    },

    getHealthStatusColor(status) {
      const colors = {
        pass: 'bg-green-100 text-green-800',
        warning: 'bg-yellow-100 text-yellow-800',
        fail: 'bg-red-100 text-red-800',
        info: 'bg-blue-100 text-blue-800'
      };
      return colors[status] || 'bg-gray-100 text-gray-800';
    },

    getHealthStatusIcon(status) {
      const icons = {
        pass: '✓',
        warning: '⚠',
        fail: '✗',
        info: 'ℹ'
      };
      return icons[status] || '?';
    },

    getOverallStatusColor(status) {
      const colors = {
        healthy: 'text-green-600',
        warning: 'text-yellow-600',
        critical: 'text-red-600'
      };
      return colors[status] || 'text-gray-600';
    },

    getOverallStatusText(status) {
      const texts = {
        healthy: '系统健康',
        warning: '存在警告',
        critical: '存在问题'
      };
      return texts[status] || '未知';
    },

    // ===== 系统日志 =====

    async loadSystemLogs(page = 1) {
      this.systemLogs.loading = true;
      try {
        const params = new URLSearchParams({
          page: page,
          pageSize: this.systemLogs.pageSize
        });

        if (this.systemLogs.filters.level) {
          params.append('level', this.systemLogs.filters.level);
        }
        if (this.systemLogs.filters.category) {
          params.append('category', this.systemLogs.filters.category);
        }
        if (this.systemLogs.filters.keyword) {
          params.append('keyword', this.systemLogs.filters.keyword);
        }

        const response = await axios.get(`${this.apiBase}/api/admin/logs?${params}`);

        if (response.data.success) {
          this.systemLogs.logs = response.data.logs;
          this.systemLogs.total = response.data.total;
          this.systemLogs.page = response.data.page;
          this.systemLogs.totalPages = response.data.totalPages;
        }
      } catch (error) {
        console.error('加载系统日志失败:', error);
        this.showToast('error', '错误', '加载系统日志失败');
      } finally {
        this.systemLogs.loading = false;
      }
    },

    filterLogs() {
      this.loadSystemLogs(1);
    },

    clearLogFilters() {
      this.systemLogs.filters = { level: '', category: '', keyword: '' };
      this.loadSystemLogs(1);
    },

    getLogLevelColor(level) {
      const colors = {
        debug: 'background: #6c757d; color: white;',
        info: 'background: #17a2b8; color: white;',
        warn: 'background: #ffc107; color: black;',
        error: 'background: #dc3545; color: white;'
      };
      return colors[level] || 'background: #6c757d; color: white;';
    },

    getLogLevelText(level) {
      const texts = { debug: '调试', info: '信息', warn: '警告', error: '错误' };
      return texts[level] || level;
    },

    getLogCategoryText(category) {
      const texts = {
        auth: '认证',
        user: '用户',
        file: '文件',
        share: '分享',
        system: '系统',
        security: '安全'
      };
      return texts[category] || category;
    },

    getLogCategoryIcon(category) {
      const icons = {
        auth: 'fa-key',
        user: 'fa-user',
        file: 'fa-file',
        share: 'fa-share-alt',
        system: 'fa-cog',
        security: 'fa-shield-alt'
      };
      return icons[category] || 'fa-info';
    },

    formatLogTime(timestamp) {
      if (!timestamp) return '';
      const date = this.parseDateValue(timestamp);
      if (!date) return String(timestamp);
      return date.toLocaleString('zh-CN', {
        year: 'numeric',
        month: '2-digit',
        day: '2-digit',
        hour: '2-digit',
        minute: '2-digit',
        second: '2-digit'
      });
    },

    async cleanupLogs() {
      if (!confirm('确定要清理90天前的日志吗？此操作不可恢复。')) return;

      try {
        const response = await axios.post(
          `${this.apiBase}/api/admin/logs/cleanup`,
          { keepDays: 90 },
        );

        if (response.data.success) {
          this.showToast('success', '成功', response.data.message);
          this.loadSystemLogs(1);
        }
      } catch (error) {
        console.error('清理日志失败:', error);
        this.showToast('error', '错误', '清理日志失败');
      }
    },

    async loadDownloadReservationMonitor(page = this.reservationMonitor.page) {
      this.reservationMonitor.loading = true;
      try {
        const response = await axios.get(`${this.apiBase}/api/admin/download-reservations`, {
          params: {
            page: Math.max(1, Number(page) || 1),
            pageSize: this.reservationMonitor.pageSize,
            status: this.reservationMonitor.filters.status || undefined,
            keyword: (this.reservationMonitor.filters.keyword || '').trim() || undefined,
            user_id: (this.reservationMonitor.filters.userId || '').trim() || undefined
          }
        });

        if (response.data?.success) {
          const rows = Array.isArray(response.data.reservations) ? response.data.reservations : [];
          const pagination = response.data.pagination || {};

          this.reservationMonitor.rows = rows;
          this.reservationMonitor.summary = response.data.summary || null;
          this.reservationMonitor.total = Number(pagination.total || rows.length);
          this.reservationMonitor.totalPages = Number(pagination.totalPages || 1);
          this.reservationMonitor.page = Number(pagination.page || page || 1);
        }
      } catch (error) {
        console.error('加载预扣监控失败:', error);
        this.showToast('error', '错误', error.response?.data?.message || '加载预扣监控失败');
      } finally {
        this.reservationMonitor.loading = false;
      }
    },

    triggerReservationKeywordSearch() {
      this.reservationMonitor.page = 1;
      if (!this._debouncedReservationQuery) {
        this._debouncedReservationQuery = this.debounce(() => {
          this.loadDownloadReservationMonitor(1);
        }, 260);
      }
      this._debouncedReservationQuery();
    },

    async changeReservationPage(nextPage) {
      const page = Math.max(1, Number(nextPage) || 1);
      if (page === this.reservationMonitor.page) return;
      await this.loadDownloadReservationMonitor(page);
    },

    getReservationStatusText(status) {
      if (status === 'pending') return '待确认';
      if (status === 'confirmed') return '已确认';
      if (status === 'expired') return '已过期';
      if (status === 'cancelled') return '已取消';
      return status || '-';
    },

    getReservationStatusColor(status) {
      if (status === 'pending') return '#f59e0b';
      if (status === 'confirmed') return '#22c55e';
      if (status === 'expired') return '#ef4444';
      if (status === 'cancelled') return '#94a3b8';
      return 'var(--text-secondary)';
    },

    async cancelReservation(row) {
      if (!row || !row.id) return;
      if (!confirm(`确认释放预扣 #${row.id} 吗？`)) return;

      try {
        const response = await axios.post(`${this.apiBase}/api/admin/download-reservations/${row.id}/cancel`);
        if (response.data?.success) {
          this.showToast('success', '成功', response.data.message || '预扣额度已释放');
          await this.loadDownloadReservationMonitor(this.reservationMonitor.page);
        }
      } catch (error) {
        console.error('释放预扣失败:', error);
        this.showToast('error', '错误', error.response?.data?.message || '释放预扣失败');
      }
    },

    async cleanupReservations() {
      if (this.reservationMonitor.cleaning) return;
      if (!confirm('确认清理预扣历史吗？将立即删除已完成/已过期/已取消记录。')) return;

      this.reservationMonitor.cleaning = true;
      try {
        const response = await axios.post(`${this.apiBase}/api/admin/download-reservations/cleanup`, {
          keep_days: 0
        });
        if (response.data?.success) {
          const result = response.data?.result || {};
          const msg = `已清理历史 ${Number(result.deleted_finalized || 0)} 条，过期待确认 ${Number(result.expired_pending || 0)} 条`;
          this.showToast('success', '成功', msg);
          await this.loadDownloadReservationMonitor(1);
        }
      } catch (error) {
        console.error('清理预扣失败:', error);
        this.showToast('error', '错误', error.response?.data?.message || '清理预扣失败');
      } finally {
        this.reservationMonitor.cleaning = false;
      }
    },

    // ===== 调试模式管理 =====

    // 切换调试模式
    toggleDebugMode() {
      this.debugMode = !this.debugMode;
      
      // 保存到 localStorage
      if (this.debugMode) {
        localStorage.setItem('debugMode', 'true');
        this.showToast('success', '调试模式已启用', 'F12和开发者工具快捷键已启用');
        // 刷新页面以应用更改
        setTimeout(() => {
          window.location.reload();
        }, 1000);
      } else {
        localStorage.removeItem('debugMode');
        this.showToast('info', '调试模式已禁用', '页面将重新加载以应用更改');
        // 刷新页面以应用更改
        setTimeout(() => {
          window.location.reload();
        }, 1000);
      }
    }
  },

  mounted() {
    // 配置axios全局设置 - 确保验证码session cookie正确传递
    axios.defaults.withCredentials = true;

    // 设置 axios 请求拦截器，自动添加 CSRF Token
    axios.interceptors.request.use(config => {
      // 从 Cookie 中读取 CSRF token
      const csrfToken = document.cookie
        .split('; ')
        .find(row => row.startsWith('csrf_token='))
        ?.split('=')[1];

      if (csrfToken && ['POST', 'PUT', 'DELETE', 'PATCH'].includes(config.method?.toUpperCase())) {
        config.headers['X-CSRF-Token'] = csrfToken;
      }
      return config;
    });

    // 初始化调试模式状态
    this.debugMode = localStorage.getItem('debugMode') === 'true';

    // 初始化主题（从localStorage加载，避免闪烁）
    this.initTheme();

    // 处理URL中的验证/重置token（兼容缺少?的旧链接）
    const verifyToken = this.getTokenFromUrl('verifyToken');
    const resetToken = this.getTokenFromUrl('resetToken');
    if (verifyToken) {
      this.handleVerifyToken(verifyToken);
      this.sanitizeUrlToken('verifyToken');
    }
    if (resetToken) {
      this.resetPasswordForm.token = resetToken;
      this.showResetPasswordModal = true;
      this.sanitizeUrlToken('resetToken');
    }

    // 阻止全局拖拽默认行为（防止拖到区域外打开新页面）
    window.addEventListener("dragover", (e) => {
      e.preventDefault();
    });
    window.addEventListener("drop", (e) => {
      e.preventDefault();
    });


    // 添加全局 dragend 监听（拖拽结束时总是隐藏覆盖层）
    window.addEventListener("dragend", () => {
      this.isDragging = false;
    });
    
    // 添加 ESC 键监听（按 ESC 关闭拖拽覆盖层）
    window.addEventListener("keydown", (e) => {
      if (e.key === "Escape" && this.isDragging) {
        this.isDragging = false;
      }
    });
    
    // 设置axios响应拦截器，处理401错误（token过期/失效）
    axios.interceptors.response.use(
      response => response,
      error => {
        if (error.response && error.response.status === 401) {
          // 排除登录接口本身的401（密码错误等）
          const isLoginApi = error.config?.url?.includes('/api/login');
          if (!isLoginApi && this.isLoggedIn) {
            console.warn('[认证] 收到401响应，Token已失效');
            this.handleTokenExpired();
            this.showToast('warning', '登录已过期', '请重新登录');
          }
        }
        return Promise.reject(error);
      }
    );

    // 检查URL参数
    this.checkUrlParams();
    // 获取系统配置（上传限制等）
    this.loadPublicConfig();

    // 如果用户在监控页面刷新，提前设置loading状态（防止显示"无数据"闪烁）
    if (this.adminTab === 'monitor') {
      this.healthCheck.loading = true;
      this.systemLogs.loading = true;
      this.monitorTabLoading = true;
    }

    // 检查登录状态
    this.checkLoginStatus();
  },

  watch: {
    currentView(newView) {
      if (newView === 'shares') {
        this.refreshShareResources();
      } else if (newView === 'admin' && this.user?.is_admin) {
        this.loadUsers();
        this.loadSystemSettings();
        this.loadServerStorageStats();
      } else if (newView === 'settings' && this.user && !this.user.is_admin) {
        // 普通用户进入设置页面时加载OSS配置
        this.loadOssConfig();
        this.loadDownloadTrafficReport();
        this.loadOnlineDevices();
      } else if (newView === 'settings') {
        this.loadOnlineDevices();
      }

      // 记住最后停留的视图（需合法且已登录）
      if (this.isLoggedIn && this.isViewAllowed(newView)) {
        localStorage.setItem('lastView', newView);
      }
    },
    // 记住管理员当前标签页
    adminTab(newTab) {
      if (this.isLoggedIn && this.user?.is_admin) {
        localStorage.setItem('adminTab', newTab);
        if (newTab === 'users') {
          this.loadUsers();
        }
      }
    }
  }
}).mount('#app');
