<!DOCTYPE html>
<html lang="zh-CN">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>夸克自动转存</title>
  <!-- CSS -->
  <link rel="stylesheet" href="./static/css/bootstrap.min.css">
  <link rel="stylesheet" href="./static/css/bootstrap-icons.min.css">
  <link rel="stylesheet" href="./static/css/main.css">
  <!-- Bootstrap JS -->
  <script src="./static/js/jquery-3.5.1.slim.min.js"></script>
  <script src="./static/js/bootstrap.bundle.min.js"></script>
  <!-- Vue.js -->
  <script src="./static/js/vue@2.js"></script>
  <script src="./static/js/axios.min.js"></script>
  <script src="./static/js/v-jsoneditor.min.js"></script>
  <script>
  // 添加检测文本溢出的自定义指令
  Vue.directive('check-overflow', {
    inserted: function(el, binding, vnode) {
      // 检查元素是否溢出
      const isOverflowing = el.scrollWidth > el.clientWidth;
      
      // 如果绑定了值，则绑定到该值对应的记录属性上
      if (binding.value) {
        const indexAndField = binding.value.split('|');
        const index = parseInt(indexAndField[0]);
        const field = indexAndField[1];
        
        // 设置记录的_isOverflowing属性
        // 获取记录数组
        const records = vnode.context.filteredHistoryRecords;
        if (records && records[index]) {
          // 初始化_isOverflowing属性（如果不存在）
          if (!records[index]._isOverflowing) {
            vnode.context.$set(records[index], '_isOverflowing', {});
          }
          // 设置对应字段的溢出状态
          vnode.context.$set(records[index]._isOverflowing, field, isOverflowing);
        }
      }
    },
    // 添加update钩子，在DOM更新时重新检测溢出状态
    update: function(el, binding, vnode) {
      // 检查元素是否溢出
      const isOverflowing = el.scrollWidth > el.clientWidth;
      
      // 如果绑定了值，则绑定到该值对应的记录属性上
      if (binding.value) {
        const indexAndField = binding.value.split('|');
        const index = parseInt(indexAndField[0]);
        const field = indexAndField[1];
        
        // 设置记录的_isOverflowing属性
        const records = vnode.context.filteredHistoryRecords;
        if (records && records[index]) {
          // 初始化_isOverflowing属性（如果不存在）
          if (!records[index]._isOverflowing) {
            vnode.context.$set(records[index], '_isOverflowing', {});
          }
          // 设置对应字段的溢出状态
          vnode.context.$set(records[index]._isOverflowing, field, isOverflowing);
        }
      }
    }
  });
  
  // 添加检测模态框表格文本溢出的自定义指令
  Vue.directive('check-modal-overflow', {
    inserted: function(el, binding, vnode) {
      // 检查元素是否溢出
      const isOverflowing = el.scrollWidth > el.clientWidth;
      
      // 如果绑定了值，则绑定到该值对应的文件属性上
      if (binding.value) {
        const indexAndField = binding.value.split('|');
        const index = parseInt(indexAndField[0]);
        const field = indexAndField[1];
        
        // 设置文件的_isOverflowing属性
        const files = vnode.context.fileSelect.fileList;
        if (files && files[index]) {
          // 初始化_isOverflowing属性（如果不存在）
          if (!files[index]._isOverflowing) {
            vnode.context.$set(files[index], '_isOverflowing', {});
          }
          // 设置对应字段的溢出状态
          vnode.context.$set(files[index]._isOverflowing, field, isOverflowing);
        }
      }
    },
    // 添加update钩子，在DOM更新时重新检测溢出状态
    update: function(el, binding, vnode) {
      // 检查元素是否溢出
      const isOverflowing = el.scrollWidth > el.clientWidth;
      
      // 如果绑定了值，则绑定到该值对应的文件属性上
      if (binding.value) {
        const indexAndField = binding.value.split('|');
        const index = parseInt(indexAndField[0]);
        const field = indexAndField[1];
        
        // 设置文件的_isOverflowing属性
        const files = vnode.context.fileSelect.fileList;
        if (files && files[index]) {
          // 初始化_isOverflowing属性（如果不存在）
          if (!files[index]._isOverflowing) {
            vnode.context.$set(files[index], '_isOverflowing', {});
          }
          // 设置对应字段的溢出状态
          vnode.context.$set(files[index]._isOverflowing, field, isOverflowing);
        }
      }
    }
  });
  </script>
</head>

<body>
  <div id="app">
    <!-- 添加通知组件 -->
    <div class="toast-container toast-container-center">
      <div class="toast toast-custom" ref="toast" data-delay="2000">
        <div class="toast-body toast-body-custom">
          {{ toastMessage }}
        </div>
      </div>
    </div>

    <nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0">
      <a class="navbar-brand col-md-2 col-lg-2 mr-0 px-3" href="#" @click.prevent="window.innerWidth > 767.98 && toggleSidebar()" :class="{'sidebar-collapsed-navbar-brand': sidebarCollapsed}" :title="sidebarCollapsed ? '展开侧边栏' : '收起侧边栏'">
        <i class="bi bi-clouds"></i> <span class="navbar-title">夸克自动转存</span>
      </a>
      <!-- 顶部操作按钮组 -->
      <div class="navbar-actions">
        <button type="button" class="navbar-action-btn" title="保存（CTRL+S）" @click="saveConfig()">
          <i class="bi bi-check2"></i>
        </button>
        <button type="button" class="navbar-action-btn" title="运行全部任务（CTRL+R）" @click="runScriptNow()">
          <i class="bi bi-caret-right"></i>
        </button>
        <button type="button" class="navbar-action-btn" title="单击回到顶部/双击回到底部" @click="scrollToX(0)" @dblclick="scrollToX()">
          <i class="bi bi-chevron-expand"></i>
        </button>
        <button type="button" class="navbar-action-btn d-none d-md-inline-block" :title="'页面宽度：' + (pageWidthMode === 'narrow' ? '窄' : pageWidthMode === 'medium' ? '中' : '宽')" @click="togglePageWidth()">
          <i class="bi bi-arrows"></i>
        </button>
      </div>
      <button class="navbar-toggler navbar-toggler-square position-absolute d-md-none collapsed" type="button" data-toggle="collapse" data-target="#sidebarMenu" aria-controls="sidebarMenu" aria-expanded="false">
        <i class="bi bi-list"></i>
      </button>
    </nav>

    <div class="container-fluid">
      <nav id="sidebarMenu" class="col-md-2 col-lg-2 d-md-block bg-light sidebar collapse" :class="{'sidebar-collapsed': sidebarCollapsed}">
        <div class="sidebar-sticky">
          <ul class="nav flex-column">
            <li class="nav-item">
              <a class="nav-link" href="#" :class="{active: activeTab === 'tasklist'}" @click="changeTab('tasklist')">
                <i class="bi bi-list-ul"></i> <span class="nav-text">任务列表</span>
              </a>
            </li>
            <li class="nav-item">
              <a class="nav-link" href="#" :class="{active: activeTab === 'history'}" @click="changeTab('history')">
                <i class="bi bi-clock-history"></i> <span class="nav-text">转存记录</span>
              </a>
            </li>
            <li class="nav-item">
              <a class="nav-link" href="#" :class="{active: activeTab === 'config'}" @click="changeTab('config')">
                <i class="bi bi-gear"></i> <span class="nav-text">系统配置</span>
              </a>
            </li>
            <li class="nav-item">
              <a class="nav-link" href="/logout">
                <i class="bi bi-power"></i> <span class="nav-text">退出</span>
              </a>
            </li>
          </ul>
          <div class="bottom-links">
            <ul class="nav flex-column">
              <li class="nav-item">
                <a class="nav-link" href="https://github.com/x1ao4/quark-auto-save-x/wiki" target="_blank">
                  <i class="bi bi-book"></i>
                  <span class="nav-text">Quark Auto Save X</span>
                </a>
              </li>
              <li class="nav-item">
                <a class="nav-link" href="https://github.com/x1ao4/quark-auto-save-x/releases" target="_blank">
                  <i class="bi bi-github"></i>
                  <span class="nav-text"><span v-html="versionTips"></span></span>
                </a>
              </li>
            </ul>
          </div>
        </div>
      </nav>

      <main class="col-md-10 col-lg-10 ml-sm-auto">
        <form @submit.prevent="saveConfig" @keydown.enter.prevent>

          <div v-if="activeTab === 'config'">
            <div style="height: 0.5px;"></div>
            <div class="row title">
              <div class="col">
                <h2 style="display: inline-block; font-size: 1.5rem;">账号设置</h2>
                <span class="badge badge-pill badge-light">
                  <a href="#" title="夸克自动转存WebUI账号设置"><i class="bi bi-question-circle"></i></a>
                </span>
              </div>
            </div>
            <div class="row mb-2">
              <div class="col webui-username-col">
                <div class="input-group">
                  <div class="input-group-prepend">
                    <span class="input-group-text">用户名</span>
                  </div>
                  <input type="text" v-model="formData.webui.username" class="form-control" placeholder="登录用户名">
                </div>
              </div>
              <div class="col webui-password-col">
                <div class="input-group">
                  <div class="input-group-prepend">
                    <span class="input-group-text">密码</span>
                  </div>
                  <input :type="showWebuiPassword ? 'text' : 'password'" v-model="formData.webui.password" class="form-control" placeholder="登录密码">
                  <div class="input-group-append">
                    <button type="button" class="btn btn-outline-secondary" @click="toggleWebuiPassword">
                      <i :class="['bi', showWebuiPassword ? 'bi-eye' : 'bi-eye-slash']"></i>
                    </button>
                  </div>
                </div>
              </div>
            </div>

            <div class="row title">
              <div class="col">
                <h2 style="display: inline-block; font-size: 1.5rem;">Cookie</h2>
                <span class="badge badge-pill badge-light">
                  <a href="https://github.com/x1ao4/quark-auto-save-x/wiki/使用技巧集锦" target="_blank" title="夸克网盘账号的Cookie，用于转存和签到，查阅Wiki了解详情"><i class="bi bi-question-circle"></i></a>
                </span>
              </div>
            </div>
            <p style="margin-top: -4px; margin-bottom: 4px;">所有账号都会进行签到（纯签到只需填写移动端参数），只有第一个账号会进行转存，请自行确认顺序。如需签到，请在 Cookie 后方添加签到参数。</p>
            <div v-for="(value, index) in formData.cookie" :key="index" class="input-group mb-2">
              <div class="input-group-prepend" v-if="userInfoList[index]">
                <span class="input-group-text" :style="userInfoList[index].nickname ? (userInfoList[index].is_active ? 'color: var(--dark-text-color);' : 'color: red;') : 'color: var(--dark-text-color);'">
                  {{ userInfoList[index].nickname || '未登录' }}
                </span>
              </div>
              <div class="input-group-prepend" v-else>
                <span class="input-group-text" style="color: var(--dark-text-color);">未验证</span>
              </div>
              <input type="text" v-model="formData.cookie[index]" class="form-control" placeholder="打开 pan.quark.com 按 F12 抓取" @change="fetchUserInfo">
              <div class="input-group-append">
                <button v-if="index === formData.cookie.length - 1" type="button" class="btn btn-outline-primary" @click="addCookie()"><i class="bi bi-plus-lg"></i></button>
                <button type="button" class="btn btn-outline-danger" @click="removeCookie(index)"><i class="bi bi-dash-lg"></i></button>
              </div>
            </div>

            <div class="row title">
              <div class="col">
                <h2 style="display: inline-block; font-size: 1.5rem;">定时规则</h2>
                <span class="badge badge-pill badge-light">
                  <a target="_blank" href="https://tool.lu/crontab/" title="Crontab执行时间计算器"><i class="bi bi-question-circle"></i></a>
                </span>
              </div>
            </div>
            <div class="input-group mb-2">
              <div class="input-group-prepend">
                <span class="input-group-text">Crontab</span>
              </div>
              <input type="text" v-model="formData.crontab" class="form-control" placeholder="必填">
            </div>

            <div class="row title" title="通知推送，支持多个渠道，查阅Wiki了解详情">
              <div class="col">
                <h2 style="display: inline-block; font-size: 1.5rem;">通知</h2>
                <span class="badge badge-pill badge-light">
                  <a href="https://github.com/x1ao4/quark-auto-save-x/wiki/通知推送服务配置" target="_blank"><i class="bi bi-question-circle"></i></a>
                </span>
              </div>
            </div>
            <div v-for="(value, key, index) in formData.push_config" :key="key" class="input-group mb-2">
              <div class="input-group-prepend">
                <span class="input-group-text" v-html="key"></span>
              </div>
              <div class="input-group-prepend" v-if="(key == 'DEER_KEY' || key == 'PUSH_KEY')">
                <a type="button" class="btn btn-warning" target="_blank" href="https://sct.ftqq.com/r/13249" title="Server酱推荐计划"><i class="bi bi-award"></i></a>
              </div>
              <input type="text" v-model="formData.push_config[key]" class="form-control">
              <div class="input-group-append">
                <button v-if="index === Object.keys(formData.push_config).length - 1" type="button" class="btn btn-outline-primary" @click="addPush()"><i class="bi bi-plus-lg"></i></button>
                <button type="button" class="btn btn-outline-danger" @click="removePush(key)"><i class="bi bi-dash-lg"></i></button>
              </div>
            </div>

            <div class="row title" v-if="Object.keys(getAvailablePlugins(formData.plugins)).length" title="插件配置，具体键值由插件定义，查阅Wiki了解详情">
              <div class="col">
                <h2 style="display: inline-block; font-size: 1.5rem;">插件</h2>
                <span class="badge badge-pill badge-light">
                  <a href="https://github.com/x1ao4/quark-auto-save-x/wiki/插件配置" target="_blank"><i class="bi bi-question-circle"></i></a>
                </span>
              </div>
            </div>
            <div v-for="(plugin, pluginName) in getAvailablePlugins(formData.plugins)" :key="pluginName" style="margin-bottom: -8px;" :style="pluginName === Object.keys(getAvailablePlugins(formData.plugins))[0] ? 'margin-top: -8px;' : ''">
              <div class="form-group row mb-0" style="display:flex; align-items:center;">
                <div data-toggle="collapse" :data-target="'#collapse_'+pluginName" aria-expanded="true" :aria-controls="'collapse_'+pluginName">
                  <div class="btn btn-block text-left">
                    <i class="bi bi-caret-right-fill"></i> <span v-html="`${pluginName}`"></span>
                  </div>
                </div>
              </div>
              <div class="collapse" :id="'collapse_'+pluginName" style="margin-left: 26px;">
                <div v-for="(value, key, keyIndex) in plugin" :key="key" class="input-group mb-2" :style="{ marginBottom: keyIndex === Object.keys(plugin).length - 1 && pluginName === Object.keys(getAvailablePlugins(formData.plugins)).pop() ? '8.5px !important' : '' }">
                  <div class="input-group-prepend">
                    <span class="input-group-text" v-html="key"></span>
                  </div>
                  <input type="text" v-model="formData.plugins[pluginName][key]" class="form-control">
                </div>
              </div>
            </div>

            <div class="row title" title="预定义的正则匹配规则，在任务列表中可直接点击使用，查阅Wiki了解详情">
              <div class="col">
                <h2 style="display: inline-block; font-size: 1.5rem;">魔法匹配</h2>
                <span class="badge badge-pill badge-light">
                  <a href="https://github.com/x1ao4/quark-auto-save-x/wiki/正则处理教程#21-魔法匹配" target="_blank"><i class="bi bi-question-circle"></i></a>
                </span>
              </div>
            </div>
            <div v-for="(value, key, index) in formData.magic_regex" :key="key" class="form-group mb-2">
              <div class="input-group">
                <div class="input-group-prepend">
                  <span class="input-group-text">魔法名</span>
                </div>
                <input type="text" :data-oldkey="key" v-model="key" class="form-control" @change="updateMagicRegexKey($event.target.dataset.oldkey, $event.target.value)" placeholder="自定义名称">
                <div class="input-group-prepend">
                  <span class="input-group-text">正则命名</span>
                </div>
                <input type="text" v-model="value.pattern" class="form-control" placeholder="匹配表达式">
                <input type="text" v-model="value.replace" class="form-control" placeholder="替换表达式">
                <div class="input-group-append">
                  <button v-if="index === Object.keys(formData.magic_regex).length - 1" type="button" class="btn btn-outline-primary" @click="addMagicRegex()"><i class="bi bi-plus-lg"></i></button>
                  <button type="button" class="btn btn-outline-danger" @click="removeMagicRegex(key)"><i class="bi bi-dash-lg"></i></button>
                </div>
              </div>
            </div>

            <!-- 剧集识别模块 -->
            <div class="row title" title="识别文件名中的剧集编号，用于自动重命名，查阅Wiki了解详情">
              <div class="col">
                <h2 style="display: inline-block; font-size: 1.5rem;">剧集识别</h2>
                <span class="badge badge-pill badge-light">
                  <a href="https://github.com/x1ao4/quark-auto-save-x/wiki/正则处理教程#25-剧集命名" target="_blank"><i class="bi bi-question-circle"></i></a>
                </span>
              </div>
            </div>
            <div class="form-group">
              <div class="input-group">
                <div class="input-group-prepend">
                  <span class="input-group-text">集编号识别规则</span>
                </div>
                <input type="text" class="form-control" v-model="episodePatternsText" placeholder="输入用于识别集编号的正则表达式，多个表达式用竖线分隔，特殊符号需要转义">
              </div>
            </div>

            <div class="row title" title="API接口，用于第三方添加任务等操作，查阅Wiki了解详情">
              <div class="col">
                <h2 style="display: inline-block; font-size: 1.5rem;">API</h2>
                <span class="badge badge-pill badge-light">
                  <a href="https://github.com/x1ao4/quark-auto-save-x/wiki/API接口" target="_blank"><i class="bi bi-question-circle"></i></a>
                </span>
              </div>
            </div>
            <div class="input-group">
              <div class="input-group-prepend">
                <span class="input-group-text">Token</span>
              </div>
              <input type="text" v-model="formData.api_token" class="form-control" style="background-color:white;" disabled>
            </div>

            <div class="row title" title="资源搜索服务配置，用于任务名称智能搜索，查阅Wiki了解详情">
              <div class="col">
                <h2 style="display: inline-block; font-size: 1.5rem;">CloudSaver</h2>
                <span class="badge badge-pill badge-light">
                  <a href="https://github.com/x1ao4/quark-auto-save-x/wiki/CloudSaver搜索源" target="_blank"><i class="bi bi-question-circle"></i></a>
                </span>
              </div>
            </div>
            <div class="input-group mb-2">
              <div class="input-group-prepend">
                <span class="input-group-text">服务器</span>
              </div>
              <input type="text" v-model="formData.source.cloudsaver.server" class="form-control" placeholder="资源搜索服务器地址，如 http://172.17.0.1:8008">
            </div>
            <div class="row mb-2">
              <div class="col cloudsaver-username-col">
                <div class="input-group">
                  <div class="input-group-prepend">
                    <span class="input-group-text">用户名</span>
                  </div>
                  <input type="text" v-model="formData.source.cloudsaver.username" class="form-control" placeholder="用户名">
                </div>
              </div>
              <div class="col cloudsaver-password-col">
                <div class="input-group">
                  <div class="input-group-prepend">
                    <span class="input-group-text">密码</span>
                  </div>
                  <input :type="showCloudSaverPassword ? 'text' : 'password'" v-model="formData.source.cloudsaver.password" class="form-control" placeholder="密码">
                  <div class="input-group-append">
                    <button type="button" class="btn btn-outline-secondary" @click="toggleCloudSaverPassword">
                      <i :class="['bi', showCloudSaverPassword ? 'bi-eye' : 'bi-eye-slash']"></i>
                    </button>
                  </div>
                </div>
              </div>
            </div>

          </div>

          <div v-if="activeTab === 'tasklist'">
            <div style="height: 20px;"></div>
            <div class="row" style="margin-bottom: 20px;">
              <div class="col-lg-6 col-md-6 mb-2 mb-md-0">
                <div class="input-group">
                  <div class="input-group-prepend">
                    <span class="input-group-text">名称筛选</span>
                  </div>
                  <input type="text" class="form-control" v-model="taskNameFilter" placeholder="名称关键词">
                  <div class="input-group-append">
                    <button type="button" class="btn btn-outline-secondary filter-btn-square" @click="clearData('taskNameFilter')"><i class="bi bi-x-lg"></i></button>
                  </div>
                </div>
              </div>
              <div class="col-lg-6 col-md-6">
                <div class="input-group">
                  <div class="input-group-prepend">
                    <span class="input-group-text">任务筛选</span>
                  </div>
                  <div class="position-relative" style="flex: 1;">
                    <select class="form-control task-filter-select" v-model="taskDirSelected" style="padding-left: 8px !important; text-indent: 0 !important; display: flex !important; align-items: center !important; line-height: 1.5 !important; padding-right: 24px !important;">
                      <option value="">全部任务</option>
                      <option v-for="task in taskNames" :value="task" v-html="task"></option>
                    </select>
                    <i class="bi bi-chevron-down select-arrow" style="position: absolute; pointer-events: none; color: var(--dark-text-color);"></i>
                  </div>
                  <div class="input-group-append">
                    <button type="button" class="btn btn-outline-secondary filter-btn-square" @click="clearData('taskDirSelected')"><i class="bi bi-x-lg"></i></button>
                  </div>
                </div>
              </div>
            </div>
            <div v-for="(task, index) in formData.tasklist" :key="index" class="task mb-3">
              <template v-if="(taskDirSelected == '' || task.taskname == taskDirSelected) && task.taskname.includes(taskNameFilter)">
                <hr>
                <div class="form-group row" style="align-items:center">
                  <div class="col pl-0" data-toggle="collapse" :data-target="'#collapse_'+index" aria-expanded="true" :aria-controls="'collapse_'+index">
                    <div class="btn btn-block text-left">
                      <i class="bi bi-caret-right-fill"></i> #<span v-html="`${String(index+1).padStart(2, '0')} ${task.taskname}`"></span>
                    </div>
                  </div>
                  <div class="col-auto">
                    <button class="btn btn-warning" v-if="task.shareurl_ban" :title="formatShareUrlBanMessage(task.shareurl_ban)" disabled><i class="bi bi-exclamation-circle"></i></button>
                    <button type="button" class="btn btn-outline-primary" @click="runScriptNow(index)" title="运行此任务" v-else><i class="bi bi-caret-right"></i></button>
                    <button type="button" class="btn btn-outline-danger" @click="removeTask(index)" title="删除此任务"><i class="bi bi-trash3"></i></button>
                  </div>
                </div>
                <div class="collapse ml-3" :id="'collapse_'+index">
                  <div class="alert alert-warning" role="alert" v-if="task.shareurl_ban" v-html="formatShareUrlBanMessage(task.shareurl_ban)"></div>
                  <div class="form-group row">
                    <label class="col-sm-2 col-form-label">任务名称</label>
                    <div class="col-sm-10">
                      <div class="input-group">
                        <input type="text" name="taskname[]" class="form-control" v-model="task.taskname" placeholder="必填" @focus="smart_param.showSuggestions=true;focusTaskname(index, task)" @input="changeTaskname(index, task)">
                        <div class="dropdown-menu show task-suggestions" v-if="smart_param.showSuggestions && smart_param.index === index && (smart_param.taskSuggestions.success || smart_param.isSearching)">
                          <div class="dropdown-item text-muted" v-if="smart_param.isSearching" style="font-size:14px; padding: 0 8px; text-align: left;">
                            <span v-if="smart_param.validating">
                              <i class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></i> 
                              正在验证链接有效性...（{{ smart_param.validateProgress.current }}/{{ smart_param.validateProgress.total }}）<span v-if="smart_param.validateProgress.valid > 0">已找到 {{ smart_param.validateProgress.valid }} 个有效链接</span>
                            </span>
                            <span v-else>正在搜索中...</span>
                          </div>
                          <div class="dropdown-item text-muted" v-else style="font-size:14px; padding-left: 8px; text-align: left;">{{ smart_param.taskSuggestions.message ? smart_param.taskSuggestions.message : smart_param.taskSuggestions.data && smart_param.taskSuggestions.data.length ? `以下资源由 ${smart_param.taskSuggestions.source} 搜索提供（仅显示有效链接），如有侵权请联系资源发布方` : "未搜索到有效资源" }}</div>
                          <div v-for="suggestion in smart_param.taskSuggestions.data || []" :key="suggestion.taskname" class="dropdown-item cursor-pointer" @click.prevent="selectSuggestion(index, suggestion)" style="font-size: 14px;" :title="suggestion.content">
                            <span v-html="suggestion.verify ? '✅': ''"></span> {{ suggestion.taskname }}
                            <small class="text-muted">
                              <a :href="suggestion.shareurl" target="_blank" @click.stop>{{ suggestion.shareurl }}</a>
                            </small>
                          </div>
                        </div>
                        <div class="input-group-append" title="资源搜索">
                          <button class="btn btn-primary" type="button" @click="searchSuggestions(index, task.taskname)">
                            <i v-if="smart_param.isSearching && smart_param.index === index" class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></i>
                            <i v-else class="bi bi-search"></i>
                          </button>
                          <div class="input-group-text" title="谷歌搜索">
                            <a target="_blank" :href="`https://www.google.com/search?q=%22pan.quark.cn/s%22+${task.taskname}`"><i class="bi bi-google"></i></a>
                          </div>
                          <div class="input-group-text" title="TMDB搜索">
                            <a target="_blank" :href="`https://www.themoviedb.org/search?query=${cleanTaskNameForSearch(task.taskname)}`"><img src="./static/TMDB.svg" class="tmdb-icon"></a>
                          </div>
                          <div class="input-group-text" title="豆瓣搜索">
                            <a target="_blank" :href="`https://search.douban.com/movie/subject_search?search_text=${cleanTaskNameForSearch(task.taskname)}`"><img src="./static/Douban.svg" class="douban-icon"></a>
                          </div>
                        </div>
                      </div>
                    </div>
                  </div>
                  <div class="form-group row" title="支持子目录链接，Web端打开分享点入目录，复制浏览器的URL即可；支持带提取码链接，查阅Wiki了解详情">
                    <label class="col-sm-2 col-form-label">分享链接</label>
                    <div class="col-sm-10">
                      <div class="input-group">
                        <input type="text" name="shareurl[]" class="form-control" v-model="task.shareurl" placeholder="必填" @blur="changeShareurl(task)">
                        <div class="input-group-append" v-if="task.shareurl">
                          <button type="button" class="btn btn-outline-secondary" @click="fileSelect.selectDir=true;fileSelect.previewRegex=false;showShareSelect(index)" title="选择文件夹"><i class="bi bi-folder"></i></button>
                          <div class="input-group-text">
                            <a target="_blank" :href="task.shareurl"><i class="bi bi-link-45deg"></i></a>
                          </div>
                        </div>
                      </div>
                    </div>
                  </div>
                  <div class="form-group row">
                    <label class="col-sm-2 col-form-label">保存路径</label>
                    <div class="col-sm-10">
                      <div class="input-group">
                        <input type="text" name="savepath[]" class="form-control" v-model="task.savepath" placeholder="必填" @focus="focusTaskname(index, task)">
                        <div class="input-group-append">
                          <button class="btn btn-outline-secondary" type="button" v-if="smart_param.savepath && smart_param.index == index && task.savepath != smart_param.origin_savepath" @click="task.savepath = smart_param.origin_savepath" title="恢复保存路径"><i class="bi bi-reply"></i></button>
                          <button class="btn btn-outline-secondary" type="button" @click="showSavepathSelect(index)" title="选择文件夹"><i class="bi bi-folder"></i></button>
                        </div>
                      </div>
                    </div>
                  </div>
                  <div class="form-group row" :title="task.use_sequence_naming || task.use_episode_naming ? (task.use_sequence_naming ? '输入带{}占位符的重命名格式，如：剧名 - S01E{}、剧名.S03E{}等，{}将被替换为集序号（按文件名和上传时间智能排序），目录中的文件夹会被自动过滤' : '输入带[]占位符的重命名格式，如：剧名 - S01E[]、剧名.S03E[]等，[]将被替换为从文件名中提取的集编号，目录中的文件夹会被自动过滤') : '可用作筛选，只转存匹配到文件名的文件，留空则转存所有文件'">
                    <label class="col-sm-2 col-form-label">命名规则</label>
                    <div class="col-sm-10">
                      <div class="input-group">
                        <div class="input-group-prepend">
                          <button class="btn btn-outline-secondary" type="button" @click="fileSelect.selectDir=true;fileSelect.previewRegex=true;fileSelect.sortBy='file_name_re';fileSelect.sortOrder='desc';showShareSelect(index)" :title="task.use_sequence_naming ? '预览顺序命名效果' : (task.use_episode_naming ? '预览剧集命名效果' : '预览正则命名效果')">
                            {{ task.use_sequence_naming ? '顺序命名' : (task.use_episode_naming ? '剧集命名' : '正则命名') }}
                          </button>
                        </div>
                        <input type="text" name="pattern[]" class="form-control" v-model="task.pattern" :placeholder="task.use_sequence_naming ? '输入带{}占位符的重命名格式，如：剧名 - S01E{}' : (task.use_episode_naming ? '输入带[]占位符的重命名格式，如：剧名 - S01E[]' : '匹配表达式')"  list="magicRegex" @input="detectNamingMode(task)">
                        <input v-if="!task.use_sequence_naming && !task.use_episode_naming" type="text" name="replace[]" class="form-control" v-model="task.replace" placeholder="替换表达式">
                        <input v-else type="text" class="form-control" disabled value="" style="background-color: #e9ecef;">
                        <div class="input-group-append" title="保存时只比较文件名的部分，01.mp4和01.mkv视同为同一文件，不重复转存">
                          <div class="input-group-text">
                            <input type="checkbox" v-model="task.ignore_extension">&nbsp;忽略后缀
                          </div>
                        </div>
                      </div>
                      <datalist id="magicRegex">
                        <option v-for="(value, key) in formData.magic_regex" :key="key" :value="`${key}`" v-html="`${value.pattern.replace('<', '<\u200B')} → ${value.replace}`"></option>
                      </datalist>
                    </div>
                  </div>
                  
                  <div class="form-group row" title="名称包含过滤词汇的项目不会被转存，多个词用逗号分隔，支持通过文件名和扩展名过滤文件和文件夹">
                    <label class="col-sm-2 col-form-label">过滤规则</label>
                    <div class="col-sm-10">
                      <div class="input-group">
                        <input type="text" name="filterwords[]" class="form-control" v-model="task.filterwords" placeholder="可选，输入过滤词汇，用逗号分隔，如：纯享，txt，超前企划，名称包含过滤词汇的项目不会被转存">
                      </div>
                    </div>
                  </div>
                  
                  <div class="form-group row" title="只转存修改日期大于选中文件的文件，请在符合筛选条件的文件中进行选择，在更换分享链接时非常有用">
                    <label class="col-sm-2 col-form-label">起始文件</label>
                    <div class="col-sm-10">
                      <div class="input-group">
                        <input type="text" class="form-control" placeholder="可选，只转存修改日期大于此文件的文件，请在符合筛选条件的文件中进行选择" name="startfid[]" v-model="task.startfid">
                        <div class="input-group-append" v-if="task.shareurl">
                          <button class="btn btn-outline-secondary" type="button" @click="fileSelect.selectDir=false;fileSelect.previewRegex=false;showShareSelect(index)" title="选择文件"><i class="bi bi-folder"></i></button>
                        </div>
                      </div>
                    </div>
                  </div>
                  <div class="form-group row" title="匹配成功的文件夹的所有嵌套目录都会被更新，并且会应用与根目录相同的重命名和过滤规则。注意：原理是逐级索引，深层嵌套目录的场景下效率非常低，慎用.*">
                    <label class="col-sm-2 col-form-label">更新目录</label>
                    <div class="col-sm-10">
                      <input type="text" name="update_subdir[]" class="form-control" v-model="task.update_subdir" placeholder="可选，输入需要更新的子目录的文件夹名称（或正则表达式），多个项目用竖线分隔，如：4K|1080P">
                    </div>
                  </div>
                  <div class="form-group row">
                    <label class="col-sm-2 col-form-label">截止日期</label>
                    <div class="col-sm-10">
                      <div class="input-group">
                        <input type="date" name="enddate[]" class="form-control date-input-no-icon" v-model="task.enddate" placeholder="可选" :ref="'enddate_' + index">
                        <div class="input-group-append">
                          <button type="button" class="btn btn-outline-secondary" @click="openDatePicker(index)" title="选择日期">
                            <i class="bi bi-calendar3"></i>
                          </button>
                        </div>
                      </div>
                    </div>
                  </div>
                  <div class="form-group row" title="只在勾选的星期时才运行，对周更的内容非常有用">
                    <label class="col-sm-2 col-form-label">执行周期</label>
                    <div class="col-sm-10 col-form-label">
                      <div class="form-check form-check-inline" title="也可用作任务总开关">
                        <input class="form-check-input" type="checkbox" :checked="task.runweek.length === 7" @change="toggleAllWeekdays(task)" :indeterminate.prop="task.runweek.length > 0 && task.runweek.length < 7">
                        <label class="form-check-label">全选</label>
                      </div>
                      <div class="form-check form-check-inline" v-for="(day, index) in weekdays" :key="index">
                        <input class="form-check-input" type="checkbox" v-model="task.runweek" :value="index+1">
                        <label class="form-check-label" v-html="day"></label>
                      </div>
                    </div>
                  </div>
                  <div class="form-group row" v-if="Object.keys(getAvailablePlugins(formData.plugins)).length" title="单个任务的插件配置，具体键值由插件定义，查阅Wiki了解详情">
                    <label class="col-sm-2 col-form-label">插件配置</label>
                    <div class="col-sm-10">
                      <v-jsoneditor v-model="task.addition" :options="{mode:'tree'}" :plus="false" height="162px" style="margin-bottom: -8px;"></v-jsoneditor>
                    </div>
                  </div>
                </div>
              </template>
            </div>
            <hr v-if="formData.tasklist.length > 0" class="task-divider">
            <div class="row">
              <div class="col-sm-12 d-flex justify-content-end">
                <button type="button" class="btn btn-outline-primary" @click="addTask()" title="添加任务"><i class="bi bi-plus-lg"></i></button>
              </div>
            </div>
          </div>

          <div v-if="activeTab === 'history'">
            <div style="height: 20px;"></div>
            <div class="row" style="margin-bottom: 20px;">
              <div class="col-lg-6 col-md-6 mb-2 mb-md-0">
                <div class="input-group">
                  <div class="input-group-prepend">
                    <span class="input-group-text">名称筛选</span>
                  </div>
                  <input type="text" class="form-control" v-model="historyNameFilter" placeholder="名称关键词">
                  <div class="input-group-append">
                    <button type="button" class="btn btn-outline-secondary filter-btn-square" @click="clearData('historyNameFilter')"><i class="bi bi-x-lg"></i></button>
                  </div>
                </div>
              </div>
              <div class="col-lg-6 col-md-6">
                <div class="input-group">
                  <div class="input-group-prepend">
                    <span class="input-group-text">任务筛选</span>
                  </div>
                  <div class="position-relative" style="flex: 1;">
                    <select class="form-control task-filter-select" v-model="historyTaskSelected" style="padding-left: 8px !important; text-indent: 0 !important; display: flex !important; align-items: center !important; line-height: 1.5 !important; padding-right: 24px !important;">
                      <option value="">全部任务</option>
                      <option v-for="task in historyTasks" :value="task" v-html="task"></option>
                    </select>
                    <i class="bi bi-chevron-down select-arrow" style="position: absolute; pointer-events: none; color: var(--dark-text-color);"></i>
                  </div>
                  <div class="input-group-append">
                    <button type="button" class="btn btn-outline-secondary filter-btn-square" @click="clearData('historyTaskSelected')"><i class="bi bi-x-lg"></i></button>
                  </div>
                </div>
              </div>
            </div>
            
            <div class="table-responsive">
              <table class="table table-hover">
                <thead>
                  <tr>
                    <th style="width:10%" class="cursor-pointer" @click="sortHistory('transfer_time')">转存日期 <i v-if="historyParams.sortBy === 'transfer_time'" :class="historyParams.order === 'asc' ? 'bi bi-arrow-up' : 'bi bi-arrow-down'"></i></th>
                    <th style="width:18%" class="cursor-pointer" @click="sortHistory('task_name')">任务名称 <i v-if="historyParams.sortBy === 'task_name'" :class="historyParams.order === 'asc' ? 'bi bi-arrow-up' : 'bi bi-arrow-down'"></i></th>
                    <th style="width:25%" class="cursor-pointer" @click="sortHistory('original_name')">原文件 <i v-if="historyParams.sortBy === 'original_name'" :class="historyParams.order === 'asc' ? 'bi bi-arrow-up' : 'bi bi-arrow-down'"></i></th>
                    <th style="width:25%" class="cursor-pointer" @click="sortHistory('renamed_to')">转存为 <i v-if="historyParams.sortBy === 'renamed_to'" :class="historyParams.order === 'asc' ? 'bi bi-arrow-up' : 'bi bi-arrow-down'"></i></th>
                    <th style="width:7%" class="cursor-pointer" @click="sortHistory('file_size')">大小 <i v-if="historyParams.sortBy === 'file_size'" :class="historyParams.order === 'asc' ? 'bi bi-arrow-up' : 'bi bi-arrow-down'"></i></th>
                    <th style="width:10%" class="cursor-pointer" @click="sortHistory('modify_date')">修改日期 <i v-if="historyParams.sortBy === 'modify_date'" :class="historyParams.order === 'asc' ? 'bi bi-arrow-up' : 'bi bi-arrow-down'"></i></th>
                  </tr>
                </thead>
                <tbody>
                  <tr v-if="filteredHistoryRecords.length === 0">
                    <td colspan="6" class="text-center">暂无记录</td>
                  </tr>
                  <tr v-for="(record, index) in filteredHistoryRecords" :key="record.id">
                    <td>{{ record.transfer_time_readable }}</td>
                    <td class="position-relative">
                      <div v-if="!record._expandedFields || !record._expandedFields.includes('task_name')" 
                           class="text-truncate" 
                           :title="record.task_name" 
                           v-check-overflow="index + '|task_name'">
                        {{ record.task_name }}
                      </div>
                      <div class="expand-button" v-if="isTextTruncated(record.task_name, index, 'task_name')" @click="toggleExpand(index, 'task_name')">
                        <i :class="record._expandedFields && record._expandedFields.length > 0 ? 'bi bi-chevron-up' : 'bi bi-chevron-down'"></i>
                      </div>
                      <div class="expanded-text" v-if="record._expandedFields && record._expandedFields.includes('task_name')">
                        {{ record.task_name }}
                      </div>
                    </td>
                    <td class="position-relative">
                      <div v-if="!record._expandedFields || !record._expandedFields.includes('original_name')" 
                           class="text-truncate" 
                           :title="record.original_name" 
                           v-check-overflow="index + '|original_name'">
                        {{ record.original_name }}
                      </div>
                      <div class="expand-button" v-if="isTextTruncated(record.original_name, index, 'original_name')" @click="toggleExpand(index, 'original_name')">
                        <i :class="record._expandedFields && record._expandedFields.length > 0 ? 'bi bi-chevron-up' : 'bi bi-chevron-down'"></i>
                      </div>
                      <div class="expanded-text" v-if="record._expandedFields && record._expandedFields.includes('original_name')">
                        {{ record.original_name }}
                      </div>
                    </td>
                    <td class="position-relative">
                      <div v-if="!record._expandedFields || !record._expandedFields.includes('renamed_to')" 
                           class="text-truncate" 
                           :title="record.renamed_to" 
                           v-check-overflow="index + '|renamed_to'">
                        {{ record.renamed_to }}
                      </div>
                      <div class="expand-button" v-if="isTextTruncated(record.renamed_to, index, 'renamed_to')" @click="toggleExpand(index, 'renamed_to')">
                        <i :class="record._expandedFields && record._expandedFields.length > 0 ? 'bi bi-chevron-up' : 'bi bi-chevron-down'"></i>
                      </div>
                      <div class="expanded-text" v-if="record._expandedFields && record._expandedFields.includes('renamed_to')">
                        {{ record.renamed_to }}
                      </div>
                    </td>
                    <td>{{ record.file_size_readable }}</td>
                    <td>{{ record.modify_date_readable }}</td>
                  </tr>
                </tbody>
              </table>
            </div>
            
            <!-- 分页控制 -->
            <div class="pagination-container d-flex justify-content-between align-items-center mt-3">
              <div class="page-info text-secondary">
                显示 {{ history.pagination && history.pagination.total_records > 0 ? ((historyParams.page - 1) * historyParams.page_size + 1) + '-' + Math.min(historyParams.page * historyParams.page_size, history.pagination.total_records) : '0' }} 条，共 {{ history.pagination ? history.pagination.total_records : 0 }} 条记录
              </div>
              <div class="pagination-controls d-flex align-items-center">
                <button type="button" class="btn btn-outline-secondary btn-sm mx-1" :class="{ disabled: historyParams.page <= 1 }" @click="changePage(historyParams.page - 1)" :disabled="historyParams.page <= 1">
                  <i class="bi bi-chevron-left"></i>
                </button>
                
                <!-- 分页按钮 -->
                <template v-if="totalPages > 0">
                  <!-- 第一页按钮 -->
                  <button type="button" class="btn btn-sm mx-1" 
                          :class="historyParams.page === 1 ? 'btn-primary' : 'btn-outline-secondary'" 
                          @click="changePage(1)">
                    1
                  </button>
                  
                  <!-- 省略号(前) -->
                  <span class="mx-1" v-if="getVisiblePageNumbers().length > 0 && getVisiblePageNumbers()[0] > 2">...</span>
                  
                  <!-- 中间页码 -->
                  <button v-for="page in getVisiblePageNumbers()" :key="page" type="button" 
                          class="btn btn-sm mx-1" 
                          :class="historyParams.page === page ? 'btn-primary' : 'btn-outline-secondary'" 
                          @click="changePage(page)">
                    {{ page }}
                  </button>
                  
                  <!-- 省略号(后) -->
                  <span class="mx-1" v-if="getVisiblePageNumbers().length > 0 && getVisiblePageNumbers()[getVisiblePageNumbers().length - 1] < totalPages - 1">...</span>
                  
                  <!-- 最后一页按钮 -->
                  <button v-if="totalPages > 1" type="button" class="btn btn-sm mx-1" 
                          :class="historyParams.page === totalPages ? 'btn-primary' : 'btn-outline-secondary'" 
                          @click="changePage(totalPages)">
                    {{ totalPages }}
                  </button>
                </template>
                
                <button type="button" class="btn btn-outline-secondary btn-sm mx-1" :class="{ disabled: historyParams.page >= totalPages }" @click="changePage(historyParams.page + 1)" :disabled="historyParams.page >= totalPages">
                  <i class="bi bi-chevron-right"></i>
                </button>
              </div>
              <div class="pagination-settings d-flex align-items-center">
                <span class="text-secondary mr-1">跳到第</span>
                <div class="input-group mx-1" style="width: 70px">
                  <input type="number" class="form-control form-control-sm" v-model.number="gotoPage" min="1" :max="totalPages">
                </div>
                <span class="text-secondary mr-2">页</span>
                <button type="button" class="btn btn-outline-secondary btn-sm mr-2" @click="changePage(gotoPage)">确定</button>
                <div class="dropdown">
                  <button class="btn btn-outline-secondary btn-sm dropdown-toggle" type="button" data-toggle="dropdown" aria-expanded="false">
                    {{ historyParams.page_size === 99999 ? '全部记录' : historyParams.page_size + ' 条/页' }}
                  </button>
                  <div class="dropdown-menu">
                    <a class="dropdown-item" href="#" v-for="size in [15, 30, 50, 100, 500, 1000, 'all']" :key="size" @click.prevent="changePageSizeTo(size)">{{ size === 'all' ? '全部记录' : size + ' 条/页' }}</a>
                  </div>
                </div>
              </div>
            </div>
          </div>

        </form>
      </main>
    </div>

    <!-- 模态框 运行日志 -->
    <div class="modal" tabindex="-1" id="logModal">
      <div class="modal-dialog">
        <div class="modal-content">
          <div class="modal-header">
            <h5 class="modal-title"><b>运行日志</b>
              <div v-if="modalLoading" class="spinner-border spinner-border-sm m-1" role="status"></div>
            </h5>
            <button type="button" class="close" data-dismiss="modal" aria-label="Close">
              <i class="bi bi-x-lg"></i>
            </button>
          </div>
          <div class="modal-body">
            <pre v-html="run_log"></pre>
          </div>
        </div>
      </div>
    </div>

    <!-- 模态框 文件选择 -->
    <div class="modal" tabindex="-1" id="fileSelectModal">
      <div class="modal-dialog modal-lg">
        <div class="modal-content">
          <div class="modal-header">
            <h5 class="modal-title" style="font-weight: 600; font-family: inherit; letter-spacing: normal;">
              <span v-if="fileSelect.previewRegex" style="font-weight: 600; font-family: inherit; letter-spacing: normal;">{{ formData.tasklist[fileSelect.index].use_sequence_naming ? '顺序命名预览' : (formData.tasklist[fileSelect.index].use_episode_naming ? '剧集命名预览' : '正则命名预览') }}</span>
              <span v-else-if="fileSelect.selectDir" style="font-weight: 600; font-family: inherit; letter-spacing: normal;">选择{{fileSelect.selectShare ? '需转存的' : '保存到的'}}文件夹</span>
              <span v-else style="font-weight: 600; font-family: inherit; letter-spacing: normal;">选择起始文件</span>
              <div v-if="modalLoading" class="spinner-border spinner-border-sm m-1" role="status"></div>
            </h5>
            <button type="button" class="close" data-dismiss="modal" aria-label="Close">
              <i class="bi bi-x-lg"></i>
            </button>
          </div>
          <div class="modal-body small">
            <div class="alert alert-warning" v-if="fileSelect.error" v-html="fileSelect.error"></div>
            <div v-else>
              <!-- 面包屑导航 -->
              <nav aria-label="breadcrumb" v-if="fileSelect.selectDir">
                <ol class="breadcrumb">
                  <li class="breadcrumb-item cursor-pointer" @click="navigateTo('0','/')"><i class="bi bi-house-door"></i></li>
                  <li v-for="(item, index) in fileSelect.paths" class="breadcrumb-item">
                    <a v-if="index != fileSelect.paths.length - 1" href="#" @click="navigateTo(item.fid, item.name)">{{ item.name }}</a>
                    <span v-else class="text-muted">{{ item.name }}</span>
                  </li>
                </ol>
              </nav>
              <!-- 文件列表 -->
              <div class="mb-3" v-if="fileSelect.previewRegex">
                <div v-if="formData.tasklist[fileSelect.index].use_sequence_naming">
                  <div style="margin-bottom: 0; padding-left: 5px; display: flex; align-items: center;">
                    <span style="font-weight: 600; font-family: inherit; letter-spacing: normal;">顺序命名表达式：</span><span class="badge badge-info" v-html="formData.tasklist[fileSelect.index].pattern"></span>
                  </div>
                  <div style="padding-left: 5px; display: flex; align-items: center;">
                    <span class="text-muted" style="font-family: inherit; letter-spacing: normal;">预览结果仅供参考，新文件序号将基于网盘中已有文件的最大序号递增。若分享链接文件不完整，实际结果可能有所不同。</span>
                  </div>
                </div>
                <div v-else-if="formData.tasklist[fileSelect.index].use_episode_naming">
                  <div style="padding-left: 5px; display: flex; align-items: center;">
                    <span style="font-weight: 600; font-family: inherit; letter-spacing: normal;">剧集命名表达式：</span><span class="badge badge-info" v-html="formData.tasklist[fileSelect.index].pattern"></span>
                  </div>
                </div>
                <div v-else>
                  <div style="margin-bottom: 0; padding-left: 5px; display: flex; align-items: center;">
                    <span style="font-weight: 600; font-family: inherit; letter-spacing: normal;">匹配表达式：</span><span class="badge badge-info" v-html="formData.tasklist[fileSelect.index].pattern"></span>
                  </div>
                  <div style="padding-left: 5px; display: flex; align-items: center;">
                    <span style="font-weight: 600; font-family: inherit; letter-spacing: normal;">替换表达式：</span><span class="badge badge-info" v-html="formData.tasklist[fileSelect.index].replace"></span>
                  </div>
                </div>
              </div>
              <table class="table table-hover table-sm">
                <thead>
                  <tr>
                    <th scope="col" class="col-filename cursor-pointer" style="padding-left: 5px; font-family: inherit; letter-spacing: normal;" @click="sortFileList('file_name')">文件名 <i v-if="fileSelect.sortBy === 'file_name'" :class="fileSelect.sortOrder === 'asc' ? 'bi bi-arrow-up' : 'bi bi-arrow-down'"></i></th>
                    <th scope="col" class="col-rename cursor-pointer" v-if="fileSelect.selectShare" style="padding-left: 5px; font-family: inherit; letter-spacing: normal;" @click="sortFileList('file_name_re')">{{ fileSelect.index !== null && formData.tasklist[fileSelect.index] ? 
                      (formData.tasklist[fileSelect.index].use_sequence_naming ? '顺序命名' : 
                      (formData.tasklist[fileSelect.index].use_episode_naming ? '剧集命名' : '正则命名')) : '重命名' }} <i v-if="fileSelect.sortBy === 'file_name_re'" :class="fileSelect.sortOrder === 'asc' ? 'bi bi-arrow-up' : 'bi bi-arrow-down'"></i></th>
                    <template v-if="!fileSelect.previewRegex">
                      <th scope="col" class="col-size cursor-pointer" style="font-family: inherit; letter-spacing: normal;" @click="sortFileList('size')">大小 <i v-if="fileSelect.sortBy === 'size'" :class="fileSelect.sortOrder === 'asc' ? 'bi bi-arrow-up' : 'bi bi-arrow-down'"></i></th>
                      <th scope="col" class="col-date cursor-pointer" style="font-family: inherit; letter-spacing: normal;" @click="sortFileList('updated_at')">修改日期 <i v-if="fileSelect.sortBy === 'updated_at'" :class="fileSelect.sortOrder === 'asc' ? 'bi bi-arrow-up' : 'bi bi-arrow-down'"></i></th>
                      <th scope="col" class="col-action" v-if="!fileSelect.selectShare" style="font-family: inherit; letter-spacing: normal;">操作</th>
                    </template>
                  </tr>
                </thead>
                <tbody>
                  <tr v-for="(file, key) in fileSelect.fileList" :key="key" @click="fileSelect.selectDir ? (file.dir ? navigateTo(file.fid, file.file_name) : null) : selectStartFid(file.fid)" :class="{'cursor-pointer': fileSelect.selectDir ? file.dir : true}" style="vertical-align: top;">
                    <td class="col-filename position-relative" style="padding-left: 5px; vertical-align: top;">
                      <div v-if="!file._expandedFields || !file._expandedFields.includes('file_name')" 
                           style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis; padding-right: 25px;" 
                           :title="file.file_name" 
                           v-check-modal-overflow="key + '|file_name'">
                        <i class="bi" :class="file.dir ? 'bi-folder-fill text-warning' : 'bi-file-earmark'"></i> {{file.file_name}}
                      </div>
                      <div class="expand-button" v-if="isModalTextTruncated(file.file_name, key, 'file_name')" @click.stop="toggleModalExpand(key, 'file_name')">
                        <i :class="file._expandedFields && file._expandedFields.includes('file_name') ? 'bi bi-chevron-up' : 'bi bi-chevron-down'"></i>
                      </div>
                      <div v-if="file._expandedFields && file._expandedFields.includes('file_name')" style="white-space: normal; word-break: break-word; padding-right: 25px; display: block; width: 100%; position: relative;">
                        <i class="bi" :class="file.dir ? 'bi-folder-fill text-warning' : 'bi-file-earmark'"></i> {{file.file_name}}
                      </div>
                    </td>
                    <td class="col-rename position-relative" v-if="fileSelect.selectShare" :class="file.file_name_re ? 'text-success' : 'text-danger'" style="padding-left: 5px; vertical-align: top;">
                      <div v-if="!file._expandedFields || !file._expandedFields.includes('file_name_re')" 
                           style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis; padding-right: 25px;" 
                           :title="file.file_name_re || '×'" 
                           v-check-modal-overflow="key + '|file_name_re'">
                        {{file.file_name_re || '&times;'}}
                      </div>
                      <div class="expand-button" v-if="isModalTextTruncated(file.file_name_re || '×', key, 'file_name_re')" @click.stop="toggleModalExpand(key, 'file_name_re')">
                        <i :class="file._expandedFields && file._expandedFields.includes('file_name_re') ? 'bi bi-chevron-up' : 'bi bi-chevron-down'"></i>
                      </div>
                      <div v-if="file._expandedFields && file._expandedFields.includes('file_name_re')" :class="file.file_name_re ? 'text-success' : 'text-danger'" style="white-space: normal; word-break: break-word; padding-right: 25px; display: block; width: 100%; position: relative;">
                        {{file.file_name_re || '&times;'}}
                      </div>
                    </td>
                    <template v-if="!fileSelect.previewRegex">
                      <td class="col-size" v-if="file.dir" style="vertical-align: top;">{{ file.include_items }} 项</td>
                      <td class="col-size" v-else style="vertical-align: top;">{{file.size | size}}</td>
                      <td class="col-date" style="vertical-align: top;">{{file.updated_at | ts2date}}</td>
                      <td class="col-action" v-if="!fileSelect.selectShare" style="vertical-align: top;"><a @click.stop.prevent="deleteFile(file.fid, file.file_name, file.dir)" style="cursor: pointer;">删除</a></td>
                    </template>
                  </tr>
                </tbody>
              </table>
            </div>
          </div>
          <div class="modal-footer" v-if="fileSelect.selectDir && !fileSelect.previewRegex">
            <button type="button" class="btn btn-primary btn-sm" @click="selectCurrentFolder()">{{fileSelect.selectShare ? '转存当前文件夹' : '保存到当前文件夹'}}</button>
            <button type="button" class="btn btn-primary btn-sm" v-if="!fileSelect.selectShare" @click="selectCurrentFolder(true)">保存到当前位置的「<span class="badge badge-light" v-html="formData.tasklist[fileSelect.index].taskname"></span>」文件夹</button>
          </div>
        </div>
      </div>
    </div>
  </div>


  <script>
    var app = new Vue({
      el: '#app',
      data: {
        version: "[[ version ]]",
        versionTips: "",
        plugin_flags: "[[ plugin_flags ]]",
        weekdays: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'],
        sidebarCollapsed: false,
        showCloudSaverPassword: false,
        showWebuiPassword: false,
        pageWidthMode: 'medium', // 页面宽度模式：narrow, medium, wide
        formData: {
          cookie: [],
          push_config: {},
          media_servers: {},
          tasklist: [],
          magic_regex: {},
          episode_patterns: [],
          source: {
            cloudsaver: {
              server: "",
              username: "",
              password: "",
              token: ""
            }
          },
          webui: {
            username: "",
            password: ""
          }
        },
        userInfoList: [], // 用户信息列表
        newTask: {
          taskname: "",
          shareurl: "",
          savepath: "",
          pattern: "",
          replace: "",
          enddate: "",
          addition: {},
          ignore_extension: false,
          filterwords: "",
          runweek: [1, 2, 3, 4, 5, 6, 7],
          sequence_naming: "",
          use_sequence_naming: false,
          episode_naming: "",
          use_episode_naming: false
        },
        run_log: "",
        taskDirs: [""],
        taskDirSelected: "",
        taskNameFilter: "",
        modalLoading: false,
        smart_param: {
          index: null,
          savepath: "",
          origin_savepath: "",
          taskSuggestions: {
            success: false,
            data: []
          },
          showSuggestions: false,
          isSearching: false,
          validating: false,
          validateProgress: {
            total: 0,
            current: 0,
            valid: 0
          },
          searchTimer: null,
        },
        activeTab: 'config',
        configModified: false,
        fileSelect: {
          index: null,
          shareurl: "",
          stoken: "",
          fileList: [],
          paths: [],
          selectDir: true,
          selectShare: true,
          previewRegex: false,
          sortBy: "updated_at", // 默认排序字段
          sortOrder: "desc" // 默认排序顺序
        },
        historyParams: {
          sortBy: "transfer_time",
          order: "desc",
          page_size: 15,
          page: 1
        },
        history: {
          records: [],
          pagination: {}
        },
        historyNameFilter: "",
        historyTaskSelected: "",
        gotoPage: 1,
        totalPages: 1,
        displayedPages: [],
        allTaskNames: [],
        toastMessage: "",
      },
      computed: {
        episodePatternsText: {
          get() {
            if (!this.formData.episode_patterns) return '';
            return this.formData.episode_patterns.map(p => p.regex || '').join('|');
          },
          set(value) {
            // 允许直接输入正则表达式，当用户按下Enter键或失焦时再处理
            // 这里我们创建一个单一的正则表达式对象，而不是拆分
            this.formData.episode_patterns = [{
              regex: value.trim()
            }];
          }
        },
        filteredHistoryRecords() {
          // 直接返回服务器端已筛选的数据
          if (!this.history.records || this.history.records.length === 0) {
            return [];
          }
          return this.history.records;
        },
        historyTasks() {
          // 如果已经通过API加载了所有任务名称，则使用它
          if (this.allTaskNames && this.allTaskNames.length > 0) {
            return this.allTaskNames;
          }
          
          // 否则，从当前页的历史记录中提取唯一的任务名称（后备方案）
          if (!this.history.records || this.history.records.length === 0) {
            return [];
          }
          
          const taskNames = new Set();
          this.history.records.forEach(record => {
            if (record.task_name) {
              taskNames.add(record.task_name);
            }
          });
          
          return [...taskNames].sort();
        },
        taskNames() {
          // 从任务列表中提取唯一的任务名称
          if (!this.formData.tasklist || this.formData.tasklist.length === 0) {
            return [];
          }
          
          const taskNames = new Set();
          this.formData.tasklist.forEach(task => {
            if (task.taskname) {
              taskNames.add(task.taskname);
            }
          });
          
          return [...taskNames].sort();
        },
        totalPages() {
          // 直接使用后端返回的total_pages
          if (this.history.pagination && this.history.pagination.total_pages) {
            return parseInt(this.history.pagination.total_pages);
          }
          
          // 后备方案
          return 1;
        },
        displayedPages() {
          // 显示有限数量的页码，最多显示5个页码
          const pageCount = this.totalPages;
          const currentPage = this.historyParams.page;
          
          if (pageCount <= 5) {
            // 页数少于等于5，显示所有页码
            return Array.from({ length: pageCount }, (_, i) => i + 1);
          } else {
            // 页数多于5，显示当前页附近的页码
            const pages = [];
            let startPage = Math.max(currentPage - 2, 1);
            let endPage = Math.min(startPage + 4, pageCount);
            
            if (endPage - startPage < 4) {
              startPage = Math.max(endPage - 4, 1);
            }
            
            for (let i = startPage; i <= endPage; i++) {
              pages.push(i);
            }
            
            return pages;
          }
        }
      },
      filters: {
        ts2date: function (value) {
          const date = new Date(value);
          return `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()} ${date.getHours()}:${date.getMinutes().toString().padStart(2, '0')}`;
        },
        size: function (value) {
          if (!value) return "";
          const unitArr = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
          const srcsize = parseFloat(value);
          const index = srcsize ? Math.floor(Math.log(srcsize) / Math.log(1024)) : 0;
          const size = (srcsize / Math.pow(1024, index)).toFixed(1).replace(/\.?0+$/, "");
          return size + " " + unitArr[index];
        }
      },
      watch: {
        formData: {
          handler(newVal, oldVal) {
            this.configModified = true;
          },
          deep: true
        },
        historyNameFilter: {
          handler(newVal, oldVal) {
            // 延迟加载，避免频繁请求
            if (this._filterTimeout) {
              clearTimeout(this._filterTimeout);
            }
            this._filterTimeout = setTimeout(() => {
              this.loadHistoryRecords();
            }, 500);
          }
        },
        historyTaskSelected: {
          handler(newVal, oldVal) {
            // 选择任务筛选条件后立即加载数据
            this.loadHistoryRecords();
          }
        }
      },
      mounted() {
        this.fetchData();
        this.checkNewVersion();
        this.fetchUserInfo(); // 获取用户信息
        
        // 从本地存储中恢复之前的标签页状态
        const savedTab = localStorage.getItem('quarkAutoSave_activeTab');
        if (savedTab) {
          this.activeTab = savedTab;
        } else {
          // 默认显示任务列表页面
          this.activeTab = 'tasklist';
        }
        
        // 从本地存储中恢复侧边栏折叠状态
        const savedSidebarState = localStorage.getItem('quarkAutoSave_sidebarCollapsed');
        if (savedSidebarState) {
          this.sidebarCollapsed = savedSidebarState === 'true';
        }
        
        // 从本地存储中恢复用户设置的每页记录数
        const savedPageSize = localStorage.getItem('quarkAutoSave_pageSize');
        if (savedPageSize) {
          this.historyParams.page_size = savedPageSize === 'all' ? 99999 : parseInt(savedPageSize);
        }
        
        // 从本地存储中恢复页面宽度设置
        const savedPageWidthMode = localStorage.getItem('quarkAutoSave_pageWidthMode');
        if (savedPageWidthMode) {
          this.pageWidthMode = savedPageWidthMode;
          document.body.classList.add('page-width-' + this.pageWidthMode);
        } else {
          // 默认使用中等宽度
          document.body.classList.add('page-width-medium');
        }
        
        $('[data-toggle="tooltip"]').tooltip();
        document.addEventListener('keydown', this.handleKeyDown);
        document.addEventListener('click', (e) => {
          // 如果点击的是输入框、搜索按钮或下拉菜单本身，不关闭下拉菜单
          if (e.target.closest('.input-group input') || 
              e.target.closest('.btn-primary[type="button"]') || 
              e.target.closest('.dropdown-menu.task-suggestions') ||
              e.target.closest('.bi-search')) {
            return;
          }
          // 只隐藏下拉菜单，不清空搜索结果，这样点击同一任务的输入框时还能看到之前的搜索结果
          this.smart_param.showSuggestions = false;
        });
        window.addEventListener('beforeunload', this.handleBeforeUnload);
        
        // 监听模态框显示事件，检查滚动条状态
        $('#fileSelectModal').on('shown.bs.modal', this.checkPreviewScrollbar);
        // 监听窗口大小改变，重新检查滚动条状态
        window.addEventListener('resize', this.checkPreviewScrollbar);
        
        // 初始化时检查所有任务的命名模式
        setTimeout(() => {
          if (this.formData.tasklist && this.formData.tasklist.length > 0) {
            this.formData.tasklist.forEach(task => {
              // 检查现有的顺序命名设置
              if (task.use_sequence_naming && task.sequence_naming) {
                // 已经设置过顺序命名的，将顺序命名模式转换为匹配表达式
                if (!task.pattern || task._pattern_backup) {
                  task.pattern = task.sequence_naming;
                }
              } else if (task.use_episode_naming && task.episode_naming) {
                // 已经设置过剧集命名的，将剧集命名模式转换为匹配表达式
                if (!task.pattern || task._pattern_backup) {
                  task.pattern = task.episode_naming;
                }
              } else {
                // 检测是否包含顺序命名或剧集命名模式
                this.detectNamingMode(task);
              }
            });
          }
          
          // 如果没有剧集识别模式，添加默认模式
          if (!this.formData.episode_patterns || this.formData.episode_patterns.length === 0) {
            this.formData.episode_patterns = [
              { regex: '第(\\d+)集|第(\\d+)期|第(\\d+)话|(\\d+)集|(\\d+)期|(\\d+)话|[Ee][Pp]?(\\d+)|(\\d+)[-_\\s]*4[Kk]|\\[(\\d+)\\]|【(\\d+)】|_?(\\d+)_?' }
            ];
          }
          
          // 如果当前标签是历史记录，则加载历史记录
          if (this.activeTab === 'history') {
            this.loadHistoryRecords();
            // 加载所有任务名称用于筛选
            this.loadAllTaskNames();
          }
          
          // 添加对history.pagination的监听
          this.$watch('history.pagination', function(newVal) {
            if (newVal && newVal.total_pages) {
              this.$nextTick(() => {
                // 强制Vue更新视图
                this.$forceUpdate();
              });
            }
          }, { deep: true });
          
          // 检查分享链接状态
          this.checkShareUrlStatus();
        }, 500);
      },
      beforeDestroy() {
        window.removeEventListener('beforeunload', this.handleBeforeUnload);
      },
      methods: {
        // 添加格式化分享链接警告信息的方法
        formatShareUrlBanMessage(message) {
          if (!message) return message;
          
          if (message.includes("分享者用户封禁链接查看受限")) {
            return "该分享已失效，不可访问";
          } else if (message.includes("好友已取消了分享")) {
            return "该分享已被取消，无法访问";
          } else if (message.includes("文件已被分享者删除") || message === "文件已被分享者删除或文件夹为空") {
            return "该分享已被删除，无法访问";
          }
          
          return message;
        },
        
        fetchUserInfo() {
          // 获取所有cookie对应的用户信息
          axios.get('/get_user_info')
            .then(response => {
              if (response.data.success) {
                this.userInfoList = response.data.data;
              }
            })
            .catch(error => {
              console.error('获取用户信息失败:', error);
            });
        },
        // 添加一个检查分享链接状态的方法
        checkShareUrlStatus() {
          // 只在任务列表页面检查
          if (this.activeTab !== 'tasklist') return;
          
          // 遍历所有任务
          this.formData.tasklist.forEach((task, index) => {
            // 如果任务有分享链接且没有设置shareurl_ban
            if (task.shareurl && !task.shareurl_ban) {
              // 检查分享链接
              axios.get('/get_share_detail', { params: { shareurl: task.shareurl } })
                .then(response => {
                  const share_detail = response.data.data;
                  if (!response.data.success) {
                    // 使用格式化函数处理错误信息
                    this.$set(task, "shareurl_ban", this.formatShareUrlBanMessage(share_detail.error));
                  } else if (share_detail.list !== undefined && share_detail.list.length === 0) {
                    // 检查文件列表是否为空，确保列表存在且为空
                    this.$set(task, "shareurl_ban", "该分享已被删除，无法访问");
                  }
                })
                .catch(error => {
                  // 错误处理
                  console.error('检查分享链接状态失败:', error);
                });
            }
          });
        },
        toggleSidebar() {
          this.sidebarCollapsed = !this.sidebarCollapsed;
          // 保存侧边栏状态到本地存储
          localStorage.setItem('quarkAutoSave_sidebarCollapsed', this.sidebarCollapsed);
        },
        cleanTaskNameForSearch(taskName) {
          if (!taskName) return '';
          // 移除 Sxx 格式（包括 S01、S01E01 等）
          let cleanedName = taskName.replace(/\s*[-–—_]\s*S\d+(\w\d+)?/i, '');
          // 移除孤立的特殊符号
          cleanedName = cleanedName.replace(/\s*[-–—_]\s*$/, '');
          // 移除开头和结尾的特殊符号
          cleanedName = cleanedName.trim().replace(/^[-–—_\s]+|[-–—_\s]+$/g, '');
          return encodeURIComponent(cleanedName);
        },
        changeTab(tab) {
          this.activeTab = tab;
          // 在本地存储中保存当前标签页状态
          localStorage.setItem('quarkAutoSave_activeTab', tab);
          if (window.innerWidth <= 768) {
            $('#sidebarMenu').collapse('toggle')
          }
          
          // 当切换到历史记录标签时加载数据
          if (tab === 'history') {
            this.loadHistoryRecords();
            // 加载所有任务名称用于筛选
            this.loadAllTaskNames();
          }
        },
        checkNewVersion() {
          // 移除本地版本中的v前缀
          const localVersionClean = this.version.replace(/^v/i, '');
          
          // 显示时也去掉v前缀
          this.versionTips = localVersionClean; 
          
          axios.get('https://api.github.com/repos/x1ao4/quark-auto-save-x/tags')
            .then(response => {
              const latestVersion = response.data[0].name; // GitHub的版本不带v，不需要replace
              
              // 使用处理后的版本号进行比较
              if (latestVersion !== localVersionClean) {
                this.versionTips += ` <sup><span class="badge badge-pill badge-danger">${latestVersion}</span></sup>`;
              }
            })
            .catch(error => {
              console.error('Error:', error);
            });
        },
        fetchData() {
          axios.get('/data')
            .then(response => {
              config_data = response.data.data
              // cookie兼容
              if (typeof config_data.cookie === 'string')
                config_data.cookie = [config_data.cookie];
              // 添加星期预设
              config_data.tasklist = config_data.tasklist.map(task => {
                if (!task.hasOwnProperty('runweek')) {
                  task.runweek = [1, 2, 3, 4, 5, 6, 7];
                }
                
                // 格式化已有的警告信息
                if (task.shareurl_ban) {
                  task.shareurl_ban = this.formatShareUrlBanMessage(task.shareurl_ban);
                }
                
                return task;
              });
              // 获取所有任务父目录
              config_data.tasklist.forEach(item => {
                parentDir = this.getParentDirectory(item.savepath)
                if (!this.taskDirs.includes(parentDir))
                  this.taskDirs.push(parentDir);
              });
              this.newTask.addition = config_data.task_plugins_config_default;
              // 确保source配置存在
              if (!config_data.source) {
                config_data.source = {};
              }
              if (!config_data.source.cloudsaver) {
                config_data.source.cloudsaver = {
                  server: "",
                  username: "",
                  password: "",
                  token: ""
                };
              }
              // 确保剧集识别模式存在
              if (!config_data.episode_patterns) {
                config_data.episode_patterns = [];
              }
              this.formData = config_data;
              setTimeout(() => {
                this.configModified = false;
              }, 100);
              
              // 数据加载完成后检查分享链接状态
              if (this.activeTab === 'tasklist') {
                setTimeout(() => {
                  this.checkShareUrlStatus();
                }, 300);
              }
            })
            .catch(error => {
              // 错误处理
            });
        },
        handleKeyDown(event) {
          if (event.ctrlKey || event.metaKey) {
            if (event.keyCode === 83 || event.key === 's') {
              event.preventDefault();
              this.saveConfig();
            } else if (event.keyCode === 82 || event.key === 'r') {
              event.preventDefault();
              this.runScriptNow();
            }
          }
        },
        handleBeforeUnload(e) {
          if (this.configModified) {
            e.preventDefault();
            e.returnValue = '配置已修改但未保存，确定要离开吗？';
            return e.returnValue;
          }
        },
        saveConfig() {
          // 保存前处理每个任务的命名模式
          if (this.formData.tasklist && this.formData.tasklist.length > 0) {
            this.formData.tasklist.forEach(task => {
              // 如果是顺序命名模式，确保sequence_naming字段已正确设置
              if (task.use_sequence_naming && task.pattern && task.pattern.includes('{}')) {
                task.sequence_naming = task.pattern;
              }
              // 如果是剧集命名模式，确保episode_naming字段已正确设置
              if (task.use_episode_naming && task.pattern) {
                if (task.pattern === "[]" || task.pattern.includes('[]')) {
                  task.episode_naming = task.pattern;
                }
              }
            });
          }
          
          axios.post('/update', this.formData)
            .then(response => {
              if (response.data.success) {
                this.configModified = false;
                // 使用Toast通知替代alert
                this.showToast(response.data.message);
                // 保存成功后更新用户信息
                this.fetchUserInfo();
              } else {
                // 错误信息仍然使用alert，确保用户看到
                alert(response.data.message);
              }
            })
            .catch(error => {
              // 错误处理
              alert("保存失败: " + (error.response?.data?.message || error.message || "未知错误"));
            });
        },
        addCookie() {
          this.formData.cookie.push("");
          // 添加cookie后，添加一个空的用户信息占位
          this.userInfoList.push({
            index: this.userInfoList.length,
            nickname: "",
            is_active: false
          });
        },
        removeCookie(index) {
          if (this.formData.cookie[index] == "" || confirm("确认删除吗？")) {
            this.formData.cookie.splice(index, 1);
            // 删除对应的用户信息
            if (this.userInfoList.length > index) {
              this.userInfoList.splice(index, 1);
              // 更新剩余用户信息的索引
              this.userInfoList.forEach((user, idx) => {
                user.index = idx;
              });
            }
          }
        },
        addPush() {
          key = prompt("增加的键名", "");
          if (key != "" && key != null)
            this.$set(this.formData.push_config, key, "");
        },
        removePush(key) {
          if (confirm("确认删除吗？"))
            this.$delete(this.formData.push_config, key);
        },
        addTask() {
          if (!this.formData.tasklist) 
            this.formData.tasklist = [];
          newTask = { ...this.newTask }
          this.formData.tasklist.push(newTask)
          const index = this.formData.tasklist.length - 1;
          
          // 清除之前任务的搜索记录，避免影响新任务
          this.smart_param.taskSuggestions = {
            success: false,
            data: []
          };
          
          // 等Vue更新DOM后，自动展开新添加的任务
          this.$nextTick(() => {
            $(`#collapse_${index}`).collapse('show');
            // 滚动到底部
            this.scrollToX();
          });
        },
        focusTaskname(index, task) {
          // 如果当前聚焦的任务不同于上一个任务，清除搜索记录
          if (this.smart_param.index !== index) {
            this.smart_param.taskSuggestions = {
              success: false,
              data: []
            };
          }
          
          this.smart_param.index = index
          this.smart_param.origin_savepath = task.savepath
          regex = new RegExp(`/${task.taskname.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}(/|$)`)
          if (task.savepath.includes('TASKNAME')) {
            this.smart_param.savepath = task.savepath;
          } else if (task.savepath.match(regex)) {
            this.smart_param.savepath = task.savepath.replace(task.taskname, 'TASKNAME');
          } else {
            this.smart_param.savepath = undefined;
          }
        },
        changeTaskname(index, task) {
          if (this.smart_param.searchTimer) {
            clearTimeout(this.smart_param.searchTimer);
          }
          this.smart_param.searchTimer = setTimeout(() => {
            this.searchSuggestions(index, task.taskname, 0);
          }, 1000);
          if (this.smart_param.savepath)
            task.savepath = this.smart_param.savepath.replace('TASKNAME', task.taskname);
        },
        removeTask(index) {
          if (confirm("确认删除任务 [#" + (index + 1) + ": " + this.formData.tasklist[index].taskname + "] 吗？"))
            this.formData.tasklist.splice(index, 1);
        },
        changeShareurl(task) {
          if (!task.shareurl)
            return;
          this.$set(task, "shareurl_ban", undefined);
          // 从URL中提取任务名
          try {
            const matches = decodeURIComponent(task.shareurl).match(/\/(\w{32})-([^\/]+)$/);
            if (matches) {
              task.taskname = task.taskname == "" ? matches[2] : task.taskname;
              task.savepath = task.savepath.replace(/TASKNAME/g, matches[2]);
            }
          } catch (e) {
            // 错误处理
          }
          // 从分享中提取任务名
          axios.get('/get_share_detail', { params: { shareurl: task.shareurl } })
            .then(response => {
              share_detail = response.data.data
              if (!response.data.success) {
                if (share_detail.error.includes("提取码")) {
                  const passcode = prompt("检查失败[" + share_detail.error + "]，请输入提取码：");
                  if (passcode != null) {
                    task.shareurl = task.shareurl.replace(/pan.quark.cn\/s\/(\w+)(\?pwd=\w*)*/, `pan.quark.cn/s/$1?pwd=${passcode}`);
                    this.changeShareurl(task);
                    return;
                  }
                }
                
                // 使用格式化函数处理错误信息
                this.$set(task, "shareurl_ban", this.formatShareUrlBanMessage(share_detail.error));
              } else {
                // 检查文件列表是否为空
                if (share_detail.list !== undefined && share_detail.list.length === 0) {
                  this.$set(task, "shareurl_ban", "该分享已被删除，无法访问");
                } else {
                  task.taskname = task.taskname == "" ? share_detail.share.title : task.taskname;
                  task.savepath = task.savepath.replace(/TASKNAME/g, share_detail.share.title);
                  this.$set(task, "shareurl_ban", undefined);
                }
              }
            })
            .catch(error => {
              // 错误处理
            });
        },
        clearData(target) {
          this[target] = "";
        },
        async runScriptNow(task_index = null) {
          body = {};
          if (task_index != null) {
            task = { ...this.formData.tasklist[task_index] };
            delete task.runweek;
            delete task.enddate;
            body = {
              "tasklist": [task],
              "original_index": task_index + 1  // 添加原始索引，从1开始计数
            };
          } else if (this.configModified) {
            if (!confirm('配置已修改但未保存，是否继续运行？')) {
              return;
            }
          }
          $('#logModal').modal('toggle');
          this.modalLoading = true;
          this.run_log = '';
          try {
            // 1. 发送 POST 请求
            const response = await fetch(`/run_script_now`, {
              method: 'POST',
              headers: {
                'Content-Type': 'application/json'
              },
              body: JSON.stringify(body)
            });
            if (!response.ok) {
              throw new Error(`HTTP error! Status: ${response.status}`);
            }
            // 2. 处理 SSE 流
            const reader = response.body.getReader();
            const decoder = new TextDecoder();
            let partialData = '';
            while (true) {
              const { done, value } = await reader.read();
              if (done) {
                this.modalLoading = false;
                // 运行后刷新数据
                this.fetchData();
                break;
              }
              partialData += decoder.decode(value);
              const lines = partialData.split('\n').filter(line => line.trim() !== '');
              for (const line of lines) {
                if (line.startsWith('data:')) {
                  const eventData = line.substring(5).trim();
                  if (eventData === '[DONE]') {
                    this.modalLoading = false;
                    this.fetchData();
                    return;
                  }
                  this.run_log += eventData + '\n';
                  // 在更新 run_log 后将滚动条滚动到底部
                  this.$nextTick(() => {
                    const modalBody = document.querySelector('.modal-body');
                    modalBody.scrollTop = modalBody.scrollHeight;
                  });
                }
              }
              partialData = '';
            }
          } catch (error) {
            this.modalLoading = false;
          }
        },
        getParentDirectory(path) {
          parentDir = path.substring(0, path.lastIndexOf('/'))
          if (parentDir == "")
            parentDir = "/"
          return parentDir;
        },
        scrollToX(top = undefined) {
          if (top == undefined)
            top = document.documentElement.scrollHeight
          window.scrollTo({
            top: top,
            behavior: "smooth"
          });
        },
        getAvailablePlugins(plugins) {
          availablePlugins = {};
          const pluginsFlagsArray = this.plugin_flags.split(',');
          for (const pluginName in plugins) {
            if (!pluginsFlagsArray.includes(`-${pluginName}`)) {
              availablePlugins[pluginName] = plugins[pluginName];
            }
          }
          return availablePlugins;
        },
        toggleAllWeekdays(task) {
          if (task.runweek.length === 7) {
            task.runweek = [];
          } else {
            task.runweek = [1, 2, 3, 4, 5, 6, 7];
          }
        },
        searchSuggestions(index, taskname, deep = 1) {
          if (taskname.length < 2) {
            return;
          }
          // 开始搜索前清除之前的搜索结果
          this.smart_param.taskSuggestions = {
            success: false,
            data: []
          };
          this.smart_param.isSearching = true;
          this.smart_param.validating = false; // 重置验证状态
          this.smart_param.index = index;
          // 确保显示下拉菜单
          this.smart_param.showSuggestions = true;
          try {
            axios.get('/task_suggestions', {
              params: {
                q: taskname,
                d: deep
              }
            }).then(response => {
              // 接收到数据后，过滤无效链接
              if (response.data.success && response.data.data && response.data.data.length > 0) {
                // 使用新增的方法验证链接有效性
                this.validateSearchResults(response.data);
              } else {
                this.smart_param.taskSuggestions = response.data;
                // 重新确认设置为true
                this.smart_param.showSuggestions = true;
                this.smart_param.isSearching = false;
                this.smart_param.validating = false; // 重置验证状态
              }
            }).catch(error => {
              this.smart_param.isSearching = false;
              this.smart_param.validating = false; // 重置验证状态
            });
          } catch (e) {
            this.smart_param.taskSuggestions = {
              error: "网络异常"
            };
            this.smart_param.isSearching = false;
            this.smart_param.validating = false; // 重置验证状态
          }
        },
        // 添加新方法来验证搜索结果
        validateSearchResults(searchData) {
          const invalidTerms = [
            "分享者用户封禁链接查看受限",
            "好友已取消了分享",
            "文件已被分享者删除",
            "文件夹为空"
          ];

          // 如果没有搜索结果，直接返回
          if (!searchData.data || searchData.data.length === 0) {
            this.smart_param.taskSuggestions = searchData;
            this.smart_param.isSearching = false;
            this.smart_param.validating = false;
            return;
          }
          
          // 设置验证状态
          this.smart_param.validating = true;
          
          // 初始化验证进度
          this.smart_param.validateProgress = {
            total: searchData.data.length,
            current: 0,
            valid: 0
          };

          // 初始化一个结果数组和待处理数组
          const validResults = [];
          const toProcess = [...searchData.data]; // 复制数组，保留原始顺序
          
          // 批量处理的大小，每批次最多处理5个链接
          const batchSize = 5;
          
          // 处理单个链接的函数
          const processLink = (link) => {
            return new Promise((resolve) => {
              if (!link.shareurl) {
                // 没有分享链接，直接跳过
                resolve(null);
                return;
              }
              
              // 使用现有的get_share_detail接口验证链接
              axios.get('/get_share_detail', { 
                params: { shareurl: link.shareurl }
              })
              .then(response => {
                // 更新进度
                this.smart_param.validateProgress.current++;
                
                if (response.data.success) {
                  // 检查文件列表是否为空
                  const shareDetail = response.data.data;
                  if (shareDetail.list && shareDetail.list.length > 0) {
                    // 链接有效，添加到有效结果列表
                    this.smart_param.validateProgress.valid++;
                    resolve(link);
                    return;
                  }
                } else {
                  // 检查是否包含已知的失效原因
                  const error = response.data.data.error || "";
                  let isInvalid = false;
                  
                  for (const term of invalidTerms) {
                    if (error.includes(term)) {
                      isInvalid = true;
                      break;
                    }
                  }
                  
                  // 如果不是已知的失效原因，保留该结果
                  if (!isInvalid) {
                    this.smart_param.validateProgress.valid++;
                    resolve(link);
                    return;
                  }
                }
                
                // 链接无效
                resolve(null);
              })
              .catch(error => {
                // 验证出错，保守处理为有效
                this.smart_param.validateProgress.current++;
                this.smart_param.validateProgress.valid++;
                resolve(link);
              });
            });
          };
          
          // 修改processBatch函数，增加快速显示功能
          const processBatch = async () => {
            // 取下一批处理
            const batch = toProcess.splice(0, batchSize);
            if (batch.length === 0) {
              // 所有批次处理完毕
              this.finishValidation(searchData, validResults);
              return;
            }
            
            // 并行处理当前批次
            const results = await Promise.all(batch.map(processLink));
            
            // 收集有效结果
            results.forEach(result => {
              if (result) {
                validResults.push(result);
              }
            });
            
            // 快速显示：如果已经找到至少3个有效链接，并且还没有显示过结果
            // 同时已验证的链接数量超过总数的30%或者已经找到5个有效链接
            const hasEnoughValidLinks = validResults.length >= 3;
            const hasProcessedEnough = this.smart_param.validateProgress.current >= this.smart_param.validateProgress.total * 0.3 || validResults.length >= 5;
            
            if (hasEnoughValidLinks && hasProcessedEnough && !this.smart_param._hasShownInterimResults) {
              // 标记已显示过快速结果
              this.smart_param._hasShownInterimResults = true;
              
              // 创建一个中间结果显示，同时保持验证状态
              const interimResult = {
                success: searchData.success,
                source: searchData.source,
                data: [...validResults], // 复制当前有效结果
                message: `已找到${validResults.length}个有效链接，验证继续进行中...`
              };
              
              // 更新显示但保持验证状态
              this.smart_param.taskSuggestions = interimResult;
            }
            
            // 继续处理下一批
            processBatch();
          };
          
          // 开始批量处理前重置快速显示标记
          this.smart_param._hasShownInterimResults = false;
          processBatch();
          
          // 设置超时，避免永久等待
          setTimeout(() => {
            // 如果验证还在进行中，强制完成
            if (this.smart_param.validating) {
              // 将剩余未验证的链接添加到结果中
              const remaining = toProcess.filter(item => item.shareurl);
              validResults.push(...remaining);
              
              // 更新进度统计
              this.smart_param.validateProgress.current = this.smart_param.validateProgress.total;
              this.smart_param.validateProgress.valid = validResults.length;
              
              // 完成验证
              this.finishValidation(searchData, validResults);
            }
          }, 30000); // 30秒超时
        },
        // 完成验证并更新搜索结果
        finishValidation(searchData, validResults) {
          // 清除快速显示标记
          this.smart_param._hasShownInterimResults = false;
          
          // 更新搜索结果
          const result = {
            success: searchData.success,
            source: searchData.source,
            data: validResults,
            message: validResults.length === 0 ? "未找到有效的分享链接" : searchData.message
          };
          
          this.smart_param.taskSuggestions = result;
          this.smart_param.isSearching = false;
          this.smart_param.validating = false;
          this.smart_param.showSuggestions = true;
        },
        selectSuggestion(index, suggestion) {
          this.smart_param.showSuggestions = false;
          this.showShareSelect(index, suggestion.shareurl);
        },
        addMagicRegex() {
          const newKey = `$MAGIC_${Object.keys(this.formData.magic_regex).length + 1}`;
          this.$set(this.formData.magic_regex, newKey, { pattern: '', replace: '' });
        },
        updateMagicRegexKey(oldKey, newKey) {
          if (oldKey !== newKey) {
            if (this.formData.magic_regex[newKey]) {
              alert(`魔法名 [${newKey}] 已存在，请使用其他名称`);
              return;
            }
            this.$set(this.formData.magic_regex, newKey, this.formData.magic_regex[oldKey]);
            this.$delete(this.formData.magic_regex, oldKey);
          }
        },
        removeMagicRegex(key) {
          if (confirm(`确认删除魔法匹配规则 [${key}] 吗？`)) {
            this.$delete(this.formData.magic_regex, key);
          }
        },
        deleteFile(fid, fname, isDir) {
          if (fid != "" && confirm(`确认删除${isDir ? '目录' : '文件'} [${fname}] 吗？`))
            axios.post('/delete_file', {
              fid: fid
            }).then(response => {
              if (response.data.code == 0) {
                this.fileSelect.fileList = this.fileSelect.fileList.filter(item => item.fid != fid);
              } else {
                alert('删除失败：' + response.data.message);
              }
            }).catch(error => {
              // 错误处理
            });
        },
        getSavepathDetail(params = 0) {
          if (params === "" || params === null || params === undefined) {
            // 为空字符串时直接使用根目录fid
            params = 0;
          } else if (params.includes && params.includes('/')) {
            params = { path: params }
          } else {
            params = { fid: params }
          }
          this.modalLoading = true;
          axios.get('/get_savepath_detail', {
            params: params
          }).then(response => {
            this.fileSelect.fileList = response.data.data.list
            // 应用默认排序，但不改变当前的排序顺序
            this.manualSortFileList(this.fileSelect.sortBy, this.fileSelect.sortOrder);
            if (response.data.data.paths.length > 0) {
              this.fileSelect.paths = response.data.data.paths
            }
            this.modalLoading = false;
          }).catch(error => {
            this.fileSelect.error = "获取文件夹列表失败";
            this.modalLoading = false;
          });
        },
        showSavepathSelect(index) {
          this.fileSelect.selectShare = false;
          this.fileSelect.selectDir = true;
          this.fileSelect.previewRegex = false;
          this.fileSelect.error = undefined;
          this.fileSelect.fileList = [];
          this.fileSelect.paths = [];
          this.fileSelect.index = index;
          // 重置排序状态为默认值
          this.fileSelect.sortBy = "updated_at";
          this.fileSelect.sortOrder = "desc";
          
          // 设置模态框类型为target（保存目标文件夹）
          document.getElementById('fileSelectModal').setAttribute('data-modal-type', 'target');
          
          $('#fileSelectModal').modal('toggle');
          
          // 当savepath为空时，直接加载根目录
          const savepath = this.formData.tasklist[index].savepath;
          if (!savepath || savepath === "") {
            this.getSavepathDetail(0); // 加载根目录
          } else {
            this.getSavepathDetail(savepath);
          }
        },
        getShareDetail() {
          this.modalLoading = true;
          axios.post('/get_share_detail', {
            shareurl: this.fileSelect.shareurl,
            stoken: this.fileSelect.stoken,
            regex: {
              pattern: this.formData.tasklist[this.fileSelect.index].pattern,
              replace: this.formData.tasklist[this.fileSelect.index].replace,
              taskname: this.formData.tasklist[this.fileSelect.index].taskname,
              filterwords: this.formData.tasklist[this.fileSelect.index].filterwords,
              magic_regex: this.formData.magic_regex,
              use_sequence_naming: this.formData.tasklist[this.fileSelect.index].use_sequence_naming,
              sequence_naming: this.formData.tasklist[this.fileSelect.index].sequence_naming,
              use_episode_naming: this.formData.tasklist[this.fileSelect.index].use_episode_naming,
              episode_naming: this.formData.tasklist[this.fileSelect.index].episode_naming,
              episode_patterns: this.formData.episode_patterns
            }
          }).then(response => {
            if (response.data.success) {
              this.fileSelect.fileList = response.data.data.list;
              // 应用默认排序，但不改变当前的排序顺序
              this.manualSortFileList(this.fileSelect.sortBy, this.fileSelect.sortOrder);
              this.fileSelect.paths = response.data.data.paths;
              this.fileSelect.stoken = response.data.data.stoken;
              
              // 检查文件列表是否为空，仅在预览界面显示错误，不影响任务本身
              if (this.fileSelect.selectShare && (!this.fileSelect.fileList || this.fileSelect.fileList.length === 0)) {
                this.fileSelect.error = "该分享已被删除，无法访问";
                // 移除下面这段代码，避免在预览时直接设置任务的shareurl_ban
                // if (this.fileSelect.index !== null && this.formData.tasklist[this.fileSelect.index]) {
                //   this.$set(this.formData.tasklist[this.fileSelect.index], "shareurl_ban", "该分享已被删除，无法访问");
                // }
              }
              
              // 命名预览模式下，在数据加载完成后检查滚动条状态
              if (this.fileSelect.previewRegex) {
                this.$nextTick(() => {
                  this.checkPreviewScrollbar();
                });
              }
            } else {
              // 使用格式化函数处理错误信息
              this.fileSelect.error = this.formatShareUrlBanMessage(response.data.data.error);
            }
            this.modalLoading = false;
          }).catch(error => {
            this.fileSelect.error = "获取文件夹列表失败";
            this.modalLoading = false;
          });
        },
        showShareSelect(index, shareurl = null) {
          this.fileSelect.selectShare = true;
          this.fileSelect.fileList = [];
          this.fileSelect.paths = [];
          this.fileSelect.error = undefined;
          // 根据模式设置不同的默认排序
          if (this.fileSelect.previewRegex) {
            // 命名预览模式下使用重命名列倒序排序
            this.fileSelect.sortBy = "file_name_re";
            this.fileSelect.sortOrder = "desc";
          } else {
            // 其他情况使用修改日期倒序排序
            this.fileSelect.sortBy = "updated_at";
            this.fileSelect.sortOrder = "desc";
          }
          if (this.getShareurl(this.fileSelect.shareurl) != this.getShareurl(this.formData.tasklist[index].shareurl)) {
            this.fileSelect.stoken = "";
          }
          this.fileSelect.shareurl = shareurl || this.formData.tasklist[index].shareurl;
          this.fileSelect.index = index;
          
          // 根据不同条件设置模态框类型
          if (this.fileSelect.previewRegex) {
            document.getElementById('fileSelectModal').setAttribute('data-modal-type', 'preview');
          } else if (this.fileSelect.selectDir) {
            document.getElementById('fileSelectModal').setAttribute('data-modal-type', 'source');
          } else {
            document.getElementById('fileSelectModal').setAttribute('data-modal-type', 'start-file');
          }
          
          $('#fileSelectModal').modal('toggle');
          this.getShareDetail();
          
          // 命名预览模式下，确保在模态框显示后检查滚动条状态
          if (this.fileSelect.previewRegex) {
            this.$nextTick(() => {
              this.checkPreviewScrollbar();
            });
          }
        },
        navigateTo(fid, name) {
          path = { fid: fid, name: name }
          if (this.fileSelect.selectShare) {
            this.fileSelect.shareurl = this.getShareurl(this.fileSelect.shareurl, path);
            this.getShareDetail();
          } else {
            if (fid == "0") {
              this.fileSelect.paths = []
            } else {
              index = this.fileSelect.paths.findIndex(item => item.fid === fid);
              if (index !== -1) {
                this.fileSelect.paths = this.fileSelect.paths.slice(0, index + 1)
              } else {
                this.fileSelect.paths.push({ fid: fid, name: name })
              }
            }
            this.getSavepathDetail(fid);
          }
        },
        selectCurrentFolder(addTaskname = false) {
          if (this.fileSelect.selectShare) {
            this.formData.tasklist[this.fileSelect.index].shareurl_ban = undefined;
            // 如果是分享文件夹且文件列表为空，则设置shareurl_ban
            if (!this.fileSelect.fileList || this.fileSelect.fileList.length === 0) {
              this.$set(this.formData.tasklist[this.fileSelect.index], "shareurl_ban", "该分享已被删除，无法访问");
            }
            this.formData.tasklist[this.fileSelect.index].shareurl = this.fileSelect.shareurl;
          } else {
            // 去掉前导斜杠，避免双斜杠问题
            this.formData.tasklist[this.fileSelect.index].savepath = this.fileSelect.paths.map(item => item.name).join("/");
            if (addTaskname) {
              this.formData.tasklist[this.fileSelect.index].savepath += "/" + this.formData.tasklist[this.fileSelect.index].taskname
            }
          }
          $('#fileSelectModal').modal('hide')
        },
        selectStartFid(fid) {
          Vue.set(this.formData.tasklist[this.fileSelect.index], 'startfid', fid);
          $('#fileSelectModal').modal('hide')
        },
        getShareurl(shareurl, path = {}) {
          if (path == {} || path.fid == 0) {
            shareurl = shareurl.match(`.*s/[a-z0-9]+`)[0]
          } else if (shareurl.includes(path.fid)) {
            shareurl = shareurl.match(`.*/${path.fid}[^\/]*`)[0]
          } else if (shareurl.includes('#/list/share')) {
            shareurl = `${shareurl}/${path.fid}-${path.name}`
          } else {
            shareurl = `${shareurl}#/list/share/${path.fid}-${path.name}`
          }
          return shareurl;
        },
        detectNamingMode(task) {
          // 检测是否为顺序命名模式或剧集命名模式
          const sequencePatterns = ['第{}集', '第{}期', '第{}话', '{}集', '{}期', '{}话', 'E{}', 'EP{}', 'S\\d+E{}', '[{}]', '【{}】', '_{}_'];
          const episodePatterns = ['第{% raw %}[]{% endraw %}集', '第{% raw %}[]{% endraw %}期', '第{% raw %}[]{% endraw %}话', '{% raw %}[]{% endraw %}集', '{% raw %}[]{% endraw %}期', '{% raw %}[]{% endraw %}话', 'E{% raw %}[]{% endraw %}', 'EP{% raw %}[]{% endraw %}', 'S\\d+E{% raw %}[]{% endraw %}', '[{% raw %}[]{% endraw %}', '【{% raw %}[]{% endraw %}】', '_{% raw %}[]{% endraw %}_'];
          
          let isSequenceNaming = false;
          let isEpisodeNaming = false;
          
          // 保存当前值以支持撤销操作
          const currentValue = task.pattern;
          
          if (task.pattern !== undefined) {
            // 检查是否包含完整的{}占位符（确保不是分开的{和}）
            isSequenceNaming = task.pattern.includes('{}') && (!task.replace || task.replace === '');
            
            // 如果不是顺序命名模式，检查是否包含完整的[]占位符或就是单独的[]
            if (!isSequenceNaming) {
              isEpisodeNaming = (task.pattern === '[]' || task.pattern.includes('[]')) && (!task.replace || task.replace === '');
            }
          }
          
          // 处理模式切换
          if (isSequenceNaming) {
            // 如果当前不是顺序命名模式，则保存现有的正则表达式
            if (!task.use_sequence_naming) {
              task._pattern_backup = task.pattern;
              task._replace_backup = task.replace;
              task.use_sequence_naming = true;
              task.use_episode_naming = false;
            }
            // 设置序列命名模式
            task.sequence_naming = task.pattern;
          } else if (isEpisodeNaming) {
            // 如果当前不是剧集命名模式，则保存现有的正则表达式
            if (!task.use_episode_naming) {
              task._pattern_backup = task.pattern;
              task._replace_backup = task.replace;
              task.use_episode_naming = true;
              task.use_sequence_naming = false;
            }
            // 设置剧集命名模式
            task.episode_naming = task.pattern;
          } else {
            // 如果当前是顺序命名模式或剧集命名模式，但现在检测不到相应的命名模式
            if (task.use_sequence_naming || task.use_episode_naming) {
              // 如果用户正在删除内容（当前值为空或比上一次更短）
              if (!currentValue || (task._lastPatternValue && currentValue.length < task._lastPatternValue.length)) {
                // 保持当前编辑状态，不切换模式
                if (task.use_sequence_naming) {
                  task.sequence_naming = currentValue;
                } else if (task.use_episode_naming) {
                  task.episode_naming = currentValue;
                }
                
                // 只有当完全删除后才切换回正则模式
                if (!currentValue) {
                  task.use_sequence_naming = false;
                  task.use_episode_naming = false;
                  if (task._pattern_backup) {
                    task.pattern = "";
                    task.replace = task._replace_backup || "";
                  }
                  task.sequence_naming = null;
                  task.episode_naming = null;
                }
              } else if (task._pattern_backup && (!task.pattern.includes('{}') && !task.pattern.includes('[]'))) {
                // 正常切换回正则命名模式（非删除操作）
                task.use_sequence_naming = false;
                task.use_episode_naming = false;
                task.pattern = task._pattern_backup;
                task.replace = task._replace_backup;
                if (task.use_sequence_naming) {
                  task._sequence_backup = task.sequence_naming;
                  task.sequence_naming = null;
                } else if (task.use_episode_naming) {
                  task._episode_backup = task.episode_naming;
                  task.episode_naming = null;
                }
              } else if (!task._pattern_backup && (!task.pattern.includes('{}') && !task.pattern.includes('[]'))) {
                // 没有备份，但需要切换回正则模式
                task.use_sequence_naming = false;
                task.use_episode_naming = false;
                task.sequence_naming = null;
                task.episode_naming = null;
              }
            }
          }
          
          // 保存当前值，用于下次比较
          task._lastPatternValue = currentValue;
          
          // 强制Vue更新视图
          this.$forceUpdate();
        },
        // 增加剧集识别模式
        addEpisodePattern() {
          // 此方法保留但不再使用
        },
        
        // 移除剧集识别模式
        removeEpisodePattern(index) {
          // 此方法保留但不再使用
        },
        loadHistoryRecords() {
          // 构建请求参数
          const params = {
            sort_by: this.historyParams.sortBy || "transfer_time",
            order: this.historyParams.order || "desc",
            page_size: parseInt(this.historyParams.page_size || 15),
            page: this.historyParams.page || 1
          };
          
          // 添加筛选条件
          if (this.historyTaskSelected) {
            params.task_name = this.historyTaskSelected;
          }
          
          if (this.historyNameFilter) {
            params.keyword = this.historyNameFilter;
          }
          
          // 判断筛选条件是否变化，只有变化时才重置页码
          const isFilterChanged = 
            (this._lastTaskFilter !== this.historyTaskSelected) || 
            (this._lastNameFilter !== this.historyNameFilter);
          
          if (isFilterChanged) {
            // 筛选条件变化时重置为第一页
            params.page = 1;
            this.gotoPage = 1;
          }
          
          // 记录当前筛选条件
          this._lastTaskFilter = this.historyTaskSelected;
          this._lastNameFilter = this.historyNameFilter;
          
          // 更新当前参数
          this.historyParams = { 
            sortBy: params.sort_by,
            order: params.order,
            page_size: params.page_size,
            page: params.page
          };
          
          axios.get('/history_records', {
            params: params
          }).then(response => {
            if (response.data.success) {
              this.history = response.data.data;
              
              // 确保页码信息正确反映在UI上
              if (this.history.pagination) {
                this.totalPages = parseInt(this.history.pagination.total_pages);
              }
            } else {
              console.error('Error:', response.data.message);
            }
          }).catch(error => {
            console.error('Error loading history records:', error);
          });
        },
        
        sortHistory(field) {
          // 更新排序参数
          if (this.historyParams.sortBy === field) {
            this.historyParams.order = this.historyParams.order === 'asc' ? 'desc' : 'asc';
          } else {
            this.historyParams.sortBy = field;
            // 保持默认排序顺序为'desc'，与后端一致
            this.historyParams.order = 'desc';
          }
          
          // 构建请求参数
          const params = {
            sort_by: this.historyParams.sortBy,
            order: this.historyParams.order,
            page_size: parseInt(this.historyParams.page_size || 15),
            page: this.historyParams.page || 1
          };
          
          // 添加筛选条件
          if (this.historyTaskSelected) {
            params.task_name = this.historyTaskSelected;
          }
          
          if (this.historyNameFilter) {
            params.keyword = this.historyNameFilter;
          }
          
          // 重新加载数据，使用当前页和当前设置的排序方式
          axios.get('/history_records', { params })
            .then(response => {
              if (response.data.success) {
                this.history = response.data.data;
                // 确保总页数更新
                if (this.history.pagination) {
                  this.totalPages = parseInt(this.history.pagination.total_pages);
                }
              } else {
                console.error('排序失败:', response.data.message);
              }
            })
            .catch(error => {
              console.error('排序请求错误:', error);
            });
        },
        
        changePage(page) {
          if (page < 1) page = 1;
          if (page > this.totalPages) page = this.totalPages;
          
          // 更新当前页码
          this.historyParams.page = page;
          this.gotoPage = page;
          
          // 直接请求新页的数据
          const params = {
            sort_by: this.historyParams.sortBy,
            order: this.historyParams.order,
            page_size: parseInt(this.historyParams.page_size),
            page: page
          };
          
          if (this.historyTaskSelected) {
            params.task_name = this.historyTaskSelected;
          }
          
          if (this.historyNameFilter) {
            params.keyword = this.historyNameFilter;
          }
          
          axios.get('/history_records', { params })
            .then(response => {
              if (response.data.success) {
                this.history = response.data.data;
                
                // 确保页码信息正确反映在UI上
                if (this.history.pagination) {
                  this.totalPages = parseInt(this.history.pagination.total_pages);
                }
              } else {
                console.error('Error:', response.data.message);
              }
            })
            .catch(error => {
              console.error('Error changing page:', error);
            });
        },
        
        changePageSize() {
          // 重置为第一页
          this.historyParams.page = 1;
          this.gotoPage = 1;
          
          // 构建参数
          const params = {
            sort_by: this.historyParams.sortBy,
            order: this.historyParams.order,
            page_size: parseInt(this.historyParams.page_size),
            page: 1
          };
          
          if (this.historyTaskSelected) {
            params.task_name = this.historyTaskSelected;
          }
          
          if (this.historyNameFilter) {
            params.keyword = this.historyNameFilter;
          }
          
          // 重新加载数据
          axios.get('/history_records', { params })
            .then(response => {
              if (response.data.success) {
                this.history = response.data.data;
                
                // 确保页码信息正确反映在UI上
                if (this.history.pagination) {
                  this.totalPages = parseInt(this.history.pagination.total_pages);
                }
              } else {
                console.error('Error:', response.data.message);
              }
            })
            .catch(error => {
              console.error('Error changing page size:', error);
            });
        },
        
        changePageSizeTo(size) {
          this.historyParams.page_size = size === 'all' ? 99999 : size;
          // 保存用户设置的每页记录数到本地存储
          localStorage.setItem('quarkAutoSave_pageSize', size.toString());
          this.changePageSize();
        },
        
        getVisiblePageNumbers() {
          const current = parseInt(this.historyParams.page) || 1;
          const total = parseInt(this.totalPages) || 1;
          const delta = 2; // 当前页左右显示的页码数
          
          // 处理特殊情况
          if (total <= 1) return [];
          
          let range = [];
          
          // 确定显示范围
          let rangeStart = Math.max(2, current - delta);
          let rangeEnd = Math.min(total - 1, current + delta);
          
          // 调整范围，确保显示足够的页码
          if (rangeEnd - rangeStart < delta * 2) {
            if (current - rangeStart < delta) {
              // 当前页靠近开始，扩展结束范围
              rangeEnd = Math.min(total - 1, rangeStart + delta * 2);
            } else {
              // 当前页靠近结束，扩展开始范围
              rangeStart = Math.max(2, rangeEnd - delta * 2);
            }
          }
          
          // 生成页码数组
          for (let i = rangeStart; i <= rangeEnd; i++) {
            range.push(i);
          }
          
          return range;
        },
        isTextTruncated(text, recordIndex, field) {
          if (!this.filteredHistoryRecords[recordIndex]) return false;
          
          // 确保文本是字符串类型
          const str = String(text || '');
          
          // 先检查文本是否有足够长度，太短的文本肯定不需要展开
          if (str.length <= 20) return false;
          
          // 从记录的_isOverflowing属性中获取溢出状态
          const record = this.filteredHistoryRecords[recordIndex];
          
          // 如果已经展开了，那么我们认为它可能是需要截断的
          if (record._expandedFields && record._expandedFields.includes(field)) {
            return true;
          }
          
          return record._isOverflowing && record._isOverflowing[field];
        },
        toggleExpand(index, field) {
          // 获取当前记录
          const record = this.filteredHistoryRecords[index];
          // 初始化_expandedFields属性（如果不存在）
          if (!record._expandedFields) {
            this.$set(record, '_expandedFields', []);
          }
          
          // 检查字段是否已经展开
          const fieldIndex = record._expandedFields.indexOf(field);
          const isExpanding = fieldIndex === -1;
          
          // 所有可能需要展开的字段
          const allFields = ['task_name', 'original_name', 'renamed_to'];
          
          if (isExpanding) {
            // 如果是展开操作，展开该行所有超长字段
            allFields.forEach(fieldName => {
              // 只有超长的字段才需要展开
              if (this.isTextTruncated(record[fieldName], index, fieldName) && 
                  !record._expandedFields.includes(fieldName)) {
                record._expandedFields.push(fieldName);
              }
            });
          } else {
            // 如果是收起操作，收起该行所有字段
            record._expandedFields = [];
          }
          
          // 在下一个DOM更新周期中强制重新检测溢出状态
          this.$nextTick(() => {
            // 确保_isOverflowing属性存在
            if (!record._isOverflowing) {
              this.$set(record, '_isOverflowing', {});
            }
            
            // 在收起状态下，通过设置为true确保展开按钮依然可见
            allFields.forEach(fieldName => {
              if (!isExpanding) {
                this.$set(record._isOverflowing, fieldName, true);
              }
            });
          });
        },
        loadAllTaskNames() {
          // 向后端请求获取所有可用的任务名称
          axios.get('/history_records', {
            params: {
              get_all_task_names: true,
              page_size: 1,  // 只需要任务名称，不需要大量记录
              page: 1
            }
          }).then(response => {
            if (response.data.success && response.data.data.all_task_names) {
              this.allTaskNames = response.data.data.all_task_names.sort();
            } else {
              // 如果API失败，回退到从当前页记录中提取任务名称
              if (this.history.records && this.history.records.length > 0) {
                const taskNames = new Set();
                this.history.records.forEach(record => {
                  if (record.task_name) {
                    taskNames.add(record.task_name);
                  }
                });
                this.allTaskNames = [...taskNames].sort();
              }
            }
          }).catch(error => {
            // 如果API请求出错，回退到从当前页记录中提取任务名称
            if (this.history.records && this.history.records.length > 0) {
              const taskNames = new Set();
              this.history.records.forEach(record => {
                if (record.task_name) {
                  taskNames.add(record.task_name);
                }
              });
              this.allTaskNames = [...taskNames].sort();
            }
          });
        },
        openDatePicker(index) {
          // 使用$refs访问对应的日期选择器并打开它
          const dateRef = this.$refs[`enddate_${index}`];
          if (dateRef && dateRef[0]) {
            // 由于Vue的ref在v-for中会返回一个数组，所以我们需要访问第一个元素
            dateRef[0].showPicker();
            
            // 点击按钮后立即使按钮失去焦点
            document.activeElement.blur();
          }
        },
        toggleCloudSaverPassword() {
          this.showCloudSaverPassword = !this.showCloudSaverPassword;
        },
        toggleWebuiPassword() {
          this.showWebuiPassword = !this.showWebuiPassword;
        },
        togglePageWidth() {
          // 循环切换页面宽度模式：窄 -> 中 -> 宽 -> 窄
          if (this.pageWidthMode === 'narrow') {
            this.pageWidthMode = 'medium';
          } else if (this.pageWidthMode === 'medium') {
            this.pageWidthMode = 'wide';
          } else {
            this.pageWidthMode = 'narrow';
          }
          // 保存用户选择到本地存储
          localStorage.setItem('quarkAutoSave_pageWidthMode', this.pageWidthMode);
          // 应用页面宽度类
          document.body.classList.remove('page-width-narrow', 'page-width-medium', 'page-width-wide');
          document.body.classList.add('page-width-' + this.pageWidthMode);
        },
        showToast(message) {
          this.toastMessage = message;
          $(this.$refs.toast).toast('show');
        },
        checkPreviewScrollbar() {
          // 检查命名预览界面的滚动条状态
          const fileSelectModal = document.getElementById('fileSelectModal');
          if (fileSelectModal && fileSelectModal.getAttribute('data-modal-type') === 'preview') {
            // 查找命名预览模式的内容容器
            const contentContainers = fileSelectModal.querySelectorAll('.modal-body > div:not(.alert-warning)');
            
            contentContainers.forEach(container => {
              // 检查是否有水平滚动条（滚动宽度大于客户端宽度）
              if (container.scrollWidth > container.clientWidth) {
                container.classList.add('has-scrollbar');
              } else {
                container.classList.remove('has-scrollbar');
              }
            });
          }
        },
        isModalTextTruncated(text, fileIndex, field) {
          if (!this.fileSelect.fileList[fileIndex]) return false;
          
          // 确保文本是字符串类型
          const str = String(text || '');
          
          // 先检查文本是否有足够长度，太短的文本肯定不需要展开
          if (str.length <= 20) return false;
          
          // 从文件的_isOverflowing属性中获取溢出状态
          const file = this.fileSelect.fileList[fileIndex];
          
          // 如果已经展开了，那么我们认为它可能是需要截断的
          if (file._expandedFields && file._expandedFields.includes(field)) {
            return true;
          }
          
          return file._isOverflowing && file._isOverflowing[field];
        },
        toggleModalExpand(index, field) {
          // 获取当前文件
          const file = this.fileSelect.fileList[index];
          // 初始化_expandedFields属性（如果不存在）
          if (!file._expandedFields) {
            this.$set(file, '_expandedFields', []);
          }
          
          // 检查字段是否已经展开
          const fieldIndex = file._expandedFields.indexOf(field);
          const isExpanding = fieldIndex === -1;
          
          if (isExpanding) {
            // 如果是展开操作，添加字段到展开列表
            file._expandedFields.push(field);
          } else {
            // 如果是收起操作，从展开列表中移除字段
            file._expandedFields.splice(fieldIndex, 1);
          }
          
          // 在下一个DOM更新周期中强制重新检测溢出状态
          this.$nextTick(() => {
            // 确保_isOverflowing属性存在
            if (!file._isOverflowing) {
              this.$set(file, '_isOverflowing', {});
            }
            
            // 在收起状态下，通过设置为true确保展开按钮依然可见
            if (!isExpanding) {
              this.$set(file._isOverflowing, field, true);
            }
            
            // 强制更新视图
            this.$forceUpdate();
          });
          
          // 阻止事件冒泡，避免触发行点击事件
          event.stopPropagation();
        },
        // 文件选择模态框的排序方法
        sortFileList(field) {
          // 如果点击了当前排序字段，则切换排序顺序
          if (this.fileSelect.sortBy === field) {
            this.fileSelect.sortOrder = this.fileSelect.sortOrder === 'asc' ? 'desc' : 'asc';
          } else {
            // 如果点击了不同的字段，则设置为该字段并使用默认排序顺序
            this.fileSelect.sortBy = field;
            // 对于日期和重命名列使用降序，其他字段使用升序
            this.fileSelect.sortOrder = field === 'updated_at' || field === 'file_name_re' ? 'desc' : 'asc';
          }
          
          // 按选定字段和顺序对文件列表进行排序
          this.fileSelect.fileList.sort((a, b) => {
            // 始终将文件夹排在文件前面
            if (a.dir && !b.dir) return -1;
            if (!a.dir && b.dir) return 1;
            
            let aValue, bValue;
            
            // 根据不同的字段选择不同的排序值
            if (field === 'file_name') {
              aValue = a.file_name.toLowerCase();
              bValue = b.file_name.toLowerCase();
            } else if (field === 'file_name_re') {
              // 对于重命名列，使用重命名后的文件名进行排序
              aValue = (a.file_name_re || '').toLowerCase();
              bValue = (b.file_name_re || '').toLowerCase();
            } else if (field === 'size') {
              // 对于文件大小，使用数字进行排序
              if (a.dir && b.dir) {
                aValue = a.include_items || 0;
                bValue = b.include_items || 0;
              } else {
                aValue = a.dir ? 0 : (a.size || 0);
                bValue = b.dir ? 0 : (b.size || 0);
              }
            } else if (field === 'updated_at') {
              // 对于日期，使用时间戳进行排序
              aValue = a.updated_at || 0;
              bValue = b.updated_at || 0;
            } else {
              // 默认字段
              aValue = a[field] || '';
              bValue = b[field] || '';
            }
            
            // 根据排序顺序返回比较结果
            if (this.fileSelect.sortOrder === 'asc') {
              return aValue > bValue ? 1 : -1;
            } else {
              return aValue < bValue ? 1 : -1;
            }
          });
        },
        manualSortFileList(field, order) {
          // 按选定字段和顺序对文件列表进行排序，但不改变当前的排序顺序
          this.fileSelect.fileList.sort((a, b) => {
            // 始终将文件夹排在文件前面
            if (a.dir && !b.dir) return -1;
            if (!a.dir && b.dir) return 1;
            
            let aValue, bValue;
            
            // 根据不同的字段选择不同的排序值
            if (field === 'file_name') {
              aValue = a.file_name.toLowerCase();
              bValue = b.file_name.toLowerCase();
            } else if (field === 'file_name_re') {
              // 对于重命名列，使用重命名后的文件名进行排序
              aValue = (a.file_name_re || '').toLowerCase();
              bValue = (b.file_name_re || '').toLowerCase();
            } else if (field === 'size') {
              // 对于文件大小，使用数字进行排序
              if (a.dir && b.dir) {
                aValue = a.include_items || 0;
                bValue = b.include_items || 0;
              } else {
                aValue = a.dir ? 0 : (a.size || 0);
                bValue = b.dir ? 0 : (b.size || 0);
              }
            } else if (field === 'updated_at') {
              // 对于日期，使用时间戳进行排序
              aValue = a.updated_at || 0;
              bValue = b.updated_at || 0;
            } else {
              // 默认字段
              aValue = a[field] || '';
              bValue = b[field] || '';
            }
            
            // 根据排序顺序返回比较结果
            if (order === 'asc') {
              return aValue > bValue ? 1 : -1;
            } else {
              return aValue < bValue ? 1 : -1;
            }
          });
        },
      }
    });
  </script>
</body>

</html>

