<template>
  <div class="setting-manage-container">
    <!-- 顶部操作栏 -->
    <div class="top-action-bar">
      <div class="action-left">
        <el-input
          v-model="queryParams.key"
          placeholder="搜索配置名称..."
          clearable
          prefix-icon="Search"
          class="search-input"
          @keyup.enter="handleQuery"
          @clear="handleQuery"
        />
        <el-button type="primary" @click="handleQuery">
          <el-icon><Search /></el-icon>查询
        </el-button>
      </div>
      <div class="action-right">
        <el-button type="primary" @click="handleAddGroup">
          <el-icon><FolderAdd /></el-icon>新增配置组
        </el-button>
        <el-button type="primary" @click="handleAddSetting">
          <el-icon><Plus /></el-icon>新增配置
        </el-button>
        <el-button type="success" @click="handleBatchImport">
          <el-icon><UploadFilled /></el-icon>导入配置
        </el-button>
      </div>
    </div>

    <!-- 配置组 Tab 栏 -->
    <div class="tabs-wrapper">
      <el-tabs
        v-model="activeGroupId"
        type="card"
        class="group-tabs"
        @tab-change="handleTabChange"
      >
        <el-tab-pane
          v-for="group in allGroupList"
          :key="group.id"
          :name="String(group.id)"
        >
          <template #label>
            <div class="tab-label">
              <el-icon class="tab-icon"><Folder /></el-icon>
              <span class="tab-name">{{ group.name }}</span>
              <el-badge v-if="group._settingCount" :value="group._settingCount" :max="99" class="tab-badge" />
            </div>
          </template>
        </el-tab-pane>
      </el-tabs>

      <!-- 当前配置组信息与操作 -->
      <div class="group-info-bar" v-if="activeGroupId && currentGroup">
        <div class="group-meta">
          <div class="group-meta-item">
            <el-icon class="meta-icon"><InfoFilled /></el-icon>
            <span class="meta-label">备注：</span>
            <span class="meta-value">{{ currentGroup.note || '暂无备注' }}</span>
          </div>
          <div class="group-meta-item">
            <el-icon class="meta-icon"><Clock /></el-icon>
            <span class="meta-label">创建时间：</span>
            <span class="meta-value">{{ formatDate(currentGroup.CreatedAt) }}</span>
          </div>
          <div class="group-meta-item" v-if="currentGroup._settingCount !== undefined">
            <el-icon class="meta-icon"><Document /></el-icon>
            <span class="meta-label">配置数：</span>
            <span class="meta-value meta-count">{{ currentGroup._settingCount }}</span>
          </div>
        </div>
        <div class="group-actions">
          <el-button class="action-btn action-btn--edit" @click="handleEditGroup(currentGroup)">
            <el-icon><Edit /></el-icon><span>编辑</span>
          </el-button>
          <el-button class="action-btn action-btn--add" @click="handleAddSettingToGroup(currentGroup)">
            <el-icon><Plus /></el-icon><span>新增配置</span>
          </el-button>
          <el-button class="action-btn action-btn--copy" @click="handleCopyGroupSettings(currentGroup)">
            <el-icon><CopyDocument /></el-icon><span>复制</span>
          </el-button>
          <el-button class="action-btn action-btn--import" @click="handleImportToGroup(currentGroup)">
            <el-icon><UploadFilled /></el-icon><span>导入</span>
          </el-button>
          <el-button class="action-btn action-btn--delete" @click="handleDeleteGroup(currentGroup)">
            <el-icon><Delete /></el-icon><span>删除</span>
          </el-button>
        </div>
      </div>
    </div>

    <!-- 配置项卡片区域 -->
    <div class="cards-section" v-loading="settingLoading">
      <transition-group name="card-list" tag="div" class="cards-grid" v-if="settingList.length > 0">
        <div
          v-for="setting in settingList"
          :key="setting.id"
          class="setting-card"
          :class="{ 'is-private': !setting.open }"
          @click="handleEditSetting(setting)"
        >
          <!-- 卡片头部 -->
          <div class="card-header">
            <div class="card-title-row">
              <div class="card-name-wrap">
                <span class="card-name" :title="setting.name">{{ setting.name }}</span>
                <el-icon class="copy-name-btn" @click.stop="copySettingName(setting.name)"><CopyDocument /></el-icon>
              </div>
              <el-tag :type="getTypeColor(setting.type)" size="small" effect="light" class="card-type-tag">
                {{ setting.type }}
              </el-tag>
            </div>
            <div class="card-status">
              <el-tag :type="setting.open ? 'success' : 'info'" size="small" effect="plain" round>
                {{ setting.open ? '公开' : '私有' }}
              </el-tag>
            </div>
          </div>

          <!-- 卡片内容 -->
          <div class="card-body">
            <div class="card-value">
              <span v-if="setting.type === 'bool'" class="value-bool">
                <el-icon :class="setting.value ? 'bool-true' : 'bool-false'">
                  <component :is="setting.value ? 'CircleCheck' : 'CircleClose'" />
                </el-icon>
                {{ setting.value ? '是' : '否' }}
              </span>
              <span v-else-if="setting.type === 'file'" class="value-file">
                <el-icon><Picture /></el-icon>
                文件ID: {{ truncateText(setting.value, 12) }}
              </span>
              <span v-else-if="setting.type === 'file_list'" class="value-file-list">
                <el-icon><Folder /></el-icon>
                共 {{ getFileList(setting.value).length }} 个文件
              </span>
              <span v-else-if="setting.type === 'string_list'" class="value-string-list">
                <el-icon><Document /></el-icon>
                共 {{ getStringList(setting.value).length }} 条
              </span>
              <span v-else-if="setting.type === 'struct'" class="value-struct">
                <el-icon><Connection /></el-icon>
                复合结构 ({{ getStructFieldCount(setting) }} 个字段)
              </span>
              <span v-else-if="setting.type === 'struct_list'" class="value-struct">
                <el-icon><Connection /></el-icon>
                结构列表 ({{ getStructListCardCount(setting) }} 项)
              </span>
              <span v-else class="value-text" :title="setting.value">
                {{ truncateText(setting.value, 40) || '-' }}
              </span>
            </div>
          </div>

          <!-- 卡片底部 -->
          <div class="card-footer">
            <span class="card-note" :title="setting.note">{{ setting.note || '暂无备注' }}</span>
            <div class="card-actions" @click.stop>
              <el-button type="primary" link size="small" @click.stop="handleEditSetting(setting)">
                <el-icon><Edit /></el-icon>
              </el-button>
              <el-button type="danger" link size="small" @click.stop="handleDeleteSetting(setting)">
                <el-icon><Delete /></el-icon>
              </el-button>
            </div>
          </div>
        </div>
      </transition-group>

      <!-- 空状态 -->
      <div v-else-if="!settingLoading" class="empty-state">
        <el-empty :description="activeGroupId ? '该配置组暂无配置项' : '请选择一个配置组'">
          <el-button v-if="activeGroupId" type="primary" @click="handleAddSettingToGroup(currentGroup)">
            <el-icon><Plus /></el-icon>新增配置
          </el-button>
        </el-empty>
      </div>

      <!-- 分页 -->
      <div class="pagination-wrapper" v-if="settingTotal > 0">
        <el-pagination
          v-model:current-page="settingPagination.page"
          v-model:page-size="settingPagination.pageSize"
          :page-sizes="[12, 24, 36, 48]"
          :total="settingTotal"
          layout="total, sizes, prev, pager, next, jumper"
          background
          @size-change="handlePageSizeChange"
          @current-change="handlePageChange"
        />
      </div>
    </div>

    <!-- 配置组表单对话框 -->
    <el-dialog
      v-model="groupDialogVisible"
      :title="groupDialogTitle"
      width="500px"
      destroy-on-close
      class="dialog-scrollable"
    >
      <el-form
        ref="groupFormRef"
        :model="groupForm"
        :rules="groupRules"
        label-width="100px"
      >
        <el-form-item label="名称" prop="name">
          <el-input v-model="groupForm.name" placeholder="请输入配置组名称" />
        </el-form-item>
        <el-form-item label="备注" prop="note">
          <el-input
            v-model="groupForm.note"
            type="textarea"
            :rows="3"
            placeholder="请输入备注信息"
          />
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="groupDialogVisible = false">取消</el-button>
        <el-button type="primary" @click="submitGroupForm">确定</el-button>
      </template>
    </el-dialog>

    <!-- 配置表单对话框 -->
    <el-dialog
      v-model="settingDialogVisible"
      :title="settingDialogTitle"
      width="1000px"
      destroy-on-close
      class="dialog-scrollable"
    >
      <el-form
        ref="settingFormRef"
        :model="settingForm"
        :rules="settingRules"
        label-width="120px"
      >
        <el-form-item label="配置组" prop="settingGroupID">
          <el-select v-model="settingForm.settingGroupID" placeholder="请选择配置组" style="width: 100%">
            <el-option
              v-for="group in allGroupList"
              :key="group.id"
              :label="group.name"
              :value="group.id"
            />
          </el-select>
        </el-form-item>
        <el-form-item label="名称" prop="name">
          <el-input v-model="settingForm.name" placeholder="请输入配置名称" />
        </el-form-item>
        <el-form-item label="类型" prop="type">
          <el-select v-model="settingForm.type" placeholder="请选择类型" style="width: 100%" @change="handleTypeChange">
            <el-option label="字符串 (string)" value="string" />
            <el-option label="长文本 (text)" value="text" />
            <el-option label="整数 (int)" value="int" />
            <el-option label="浮点数 (float)" value="float" />
            <el-option label="布尔值 (bool)" value="bool" />
            <el-option label="文件 (file)" value="file" />
            <el-option label="多文件 (file_list)" value="file_list" />
            <el-option label="字符串列表 (string_list)" value="string_list" />
            <el-option label="复合结构 (struct)" value="struct" />
            <el-option label="结构列表 (struct_list)" value="struct_list" />
          </el-select>
        </el-form-item>
        <el-form-item label="值" prop="value">
          <div v-if="settingForm.type === 'string' || settingForm.type === 'text'" style="width: 100%">
            <div v-if="isJsonValue" class="json-toolbar">
              <el-tag size="small" type="success">JSON</el-tag>
              <el-button size="small" @click="formatJson">格式化</el-button>
              <el-button size="small" @click="compressJson">压缩</el-button>
            </div>
            <el-input
              v-model="settingForm.value"
              type="textarea"
              :rows="settingForm.type === 'text' ? 8 : (isJsonValue ? 12 : 3)"
              placeholder="请输入配置值"
              :input-style="isJsonValue ? { fontFamily: 'Consolas, Monaco, monospace', fontSize: '13px', lineHeight: '1.5' } : {}"
            />
          </div>
          <el-input-number
            v-else-if="settingForm.type === 'int'"
            v-model="settingForm.value"
            :controls="false"
            placeholder="请输入整数"
            style="width: 100%"
          />
          <el-input-number
            v-else-if="settingForm.type === 'float'"
            v-model="settingForm.value"
            :controls="false"
            :precision="2"
            placeholder="请输入浮点数"
            style="width: 100%"
          />
          <el-switch
            v-else-if="settingForm.type === 'bool'"
            v-model="settingForm.value"
          />
          <div v-else-if="settingForm.type === 'file'" class="file-upload-section">
            <div class="file-info-display" v-if="settingForm.value && fileInfo">
              <div class="file-item" :class="{ 'uploading': fileUploading && fileInfo.isLocal }">
                <div class="file-preview" v-if="fileInfo.url || fileInfo.localUrl" @click="openImageSelector" style="cursor: pointer;">
                  <img :src="fileInfo.localUrl || processImageUrl(fileInfo.url)" :alt="fileInfo.realName" class="preview-image" @click.stop="previewImage(fileInfo.localUrl || fileInfo.url)" @error="handleImageError" />
                  <div v-if="fileUploading && fileInfo.isLocal" class="upload-overlay">
                    <el-icon class="is-loading"><Loading /></el-icon>
                    <span>上传中...</span>
                  </div>
                </div>
                <div class="file-preview" v-else @click="openImageSelector" style="cursor: pointer;">
                  <div class="file-placeholder">
                    <el-icon><Document /></el-icon>
                  </div>
                </div>
                <div class="file-details">
                  <span class="file-name" :title="fileInfo.realName || fileInfo.saveName">{{ truncateFileName(fileInfo.realName || fileInfo.saveName) }}</span>
                  <span class="file-id">文件ID: {{ settingForm.value }}</span>
                  <span class="file-size">{{ formatFileSize(fileInfo.size) }}</span>
                </div>
                <div class="file-actions">
                  <el-button type="primary" size="small" @click="openImageSelector" :icon="Picture">
                    修改图片
                  </el-button>
                  <el-button type="danger" size="small" @click="clearFile">删除</el-button>
                </div>
              </div>
            </div>
            <div v-else class="file-upload-options">
              <div class="upload-methods">
                <el-button 
                  type="primary" 
                  @click="openImageSelector"
                  :icon="Picture"
                  class="image-selector-btn"
                >
                  从文件库选择图片
                </el-button>
              </div>
            </div>
          </div>
          <div v-else-if="settingForm.type === 'file_list'" class="file-list-section">
            <div class="editable-file-list">
              <div class="file-list-header">
                <span class="list-title">文件列表 ({{ getEditableFormFileList().length }} 项)</span>
                <div class="header-actions">
                  <el-button size="small" @click="openImageSelectorForList" :icon="Picture">
                    从文件库选择
                  </el-button>
                </div>
              </div>
              <div class="file-list-items">
                <div
                  v-for="(fileItem, index) in getEditableFormFileList()"
                  :key="`form-file-${index}`"
                  class="file-list-item"
                  draggable="true"
                  @dragstart="handleFormFileDragStart($event, index)"
                  @dragover.prevent
                  @drop="handleFormFileDrop($event, index)"
                >
                  <div class="drag-handle">
                    <el-icon><Rank /></el-icon>
                  </div>
                  <div class="file-preview">
                    <img 
                      v-if="fileItem.url || fileItem.localUrl" 
                      :src="fileItem.localUrl || processImageUrl(fileItem.url)" 
                      :alt="fileItem.realName" 
                      class="preview-image" 
                      @click="previewImage(fileItem.localUrl || fileItem.url)" 
                      @error="handleImageError" 
                    />
                    <div v-else class="file-placeholder">
                      <el-icon><Document /></el-icon>
                    </div>
                    <div v-if="fileItem.uploading" class="upload-overlay">
                      <el-icon class="is-loading"><Loading /></el-icon>
                      <span>上传中...</span>
                    </div>
                  </div>
                  <div class="file-info">
                    <div class="file-name" :title="fileItem.realName || fileItem.saveName">
                      {{ truncateFileName(fileItem.realName || fileItem.saveName, 30) }}
                    </div>
                    <div class="file-id">ID: {{ fileItem.id || '上传中' }}</div>
                    <div class="file-size">{{ formatFileSize(fileItem.size) }}</div>
                  </div>
                  <div class="file-actions">
                    <el-button type="text" size="small" @click="openImageSelectorForListItem(index)" :icon="Edit" title="替换文件" />
                    <el-button type="text" size="small" @click="removeFormEditableFileItem(index)" :icon="Delete" class="danger-btn" title="删除文件" />
                  </div>
                </div>
              </div>
            </div>
          </div>
          <div v-else-if="settingForm.type === 'struct'" class="struct-section">
            <!-- Schema 定义 -->
            <div class="struct-schema-editor">
              <div class="schema-header">
                <span class="schema-title">
                  <el-icon><Setting /></el-icon>
                  结构定义 (Schema)
                </span>
                <el-button type="primary" size="small" @click="addStructSchemaField()">
                  <el-icon><Plus /></el-icon>添加字段
                </el-button>
              </div>
              <div class="schema-fields" v-if="structSchema.length > 0">
                <div v-for="(field, index) in structSchema" :key="index" class="schema-field-row">
                  <el-input v-model="field.key" placeholder="字段名" style="width: 160px" @change="syncStructValueFromSchema" />
                  <el-select v-model="field.type" placeholder="类型" style="width: 150px" @change="(val) => handleSchemaFieldTypeChange(field, val)">
                    <el-option label="字符串 (string)" value="string" />
                    <el-option label="文本 (text)" value="text" />
                    <el-option label="整数 (int)" value="int" />
                    <el-option label="浮点数 (float)" value="float" />
                    <el-option label="布尔值 (bool)" value="bool" />
                    <el-option label="文件 (file)" value="file" />
                    <el-option label="多文件 (file_list)" value="file_list" />
                    <el-option label="字符串列表 (string_list)" value="string_list" />
                    <el-option label="嵌套结构 (struct)" value="struct" />
                    <el-option label="结构列表 (struct_list)" value="struct_list" />
                  </el-select>
                  <el-button type="danger" :icon="Delete" circle size="small" @click="removeStructSchemaField(index)" />
                  <!-- 嵌套 struct 展开 -->
                  <div v-if="field.type === 'struct'" class="nested-schema">
                    <div class="nested-schema-header">
                      <span class="nested-label">{{ field.key }} 的子字段：</span>
                      <el-button size="small" @click="addNestedSchemaField(field)">
                        <el-icon><Plus /></el-icon>添加
                      </el-button>
                    </div>
                    <div v-for="(sub, si) in (field.schema || [])" :key="si" class="schema-field-row nested">
                      <el-input v-model="sub.key" placeholder="子字段名" style="width: 140px" @change="syncStructValueFromSchema" />
                      <el-select v-model="sub.type" placeholder="类型" style="width: 150px">
                        <el-option label="字符串 (string)" value="string" />
                        <el-option label="文本 (text)" value="text" />
                        <el-option label="整数 (int)" value="int" />
                        <el-option label="浮点数 (float)" value="float" />
                        <el-option label="布尔值 (bool)" value="bool" />
                        <el-option label="文件 (file)" value="file" />
                        <el-option label="多文件 (file_list)" value="file_list" />
                        <el-option label="字符串列表 (string_list)" value="string_list" />
                        <el-option label="嵌套结构 (struct)" value="struct" />
                        <el-option label="结构列表 (struct_list)" value="struct_list" />
                      </el-select>
                      <el-button type="danger" :icon="Delete" circle size="small" @click="removeNestedSchemaField(field, si)" />
                    </div>
                  </div>
                  <!-- 嵌套 struct_list 展开 -->
                  <div v-if="field.type === 'struct_list'" class="nested-schema">
                    <div class="nested-schema-header">
                      <span class="nested-label">{{ field.key }} 的列表项子字段：</span>
                      <el-button size="small" @click="addNestedSchemaField(field)">
                        <el-icon><Plus /></el-icon>添加
                      </el-button>
                    </div>
                    <div v-for="(sub, si) in (field.schema || [])" :key="si" class="schema-field-row nested">
                      <el-input v-model="sub.key" placeholder="子字段名" style="width: 140px" @change="syncStructValueFromSchema" />
                      <el-select v-model="sub.type" placeholder="类型" style="width: 150px">
                        <el-option label="字符串 (string)" value="string" />
                        <el-option label="文本 (text)" value="text" />
                        <el-option label="整数 (int)" value="int" />
                        <el-option label="浮点数 (float)" value="float" />
                        <el-option label="布尔值 (bool)" value="bool" />
                        <el-option label="文件 (file)" value="file" />
                        <el-option label="多文件 (file_list)" value="file_list" />
                        <el-option label="字符串列表 (string_list)" value="string_list" />
                        <el-option label="嵌套结构 (struct)" value="struct" />
                        <el-option label="结构列表 (struct_list)" value="struct_list" />
                      </el-select>
                      <el-button type="danger" :icon="Delete" circle size="small" @click="removeNestedSchemaField(field, si)" />
                    </div>
                  </div>
                </div>
              </div>
              <div v-else class="schema-empty">
                <span>暂无字段定义，请点击上方「添加字段」</span>
              </div>
            </div>

            <!-- 值编辑 -->
            <div class="struct-value-editor" v-if="structSchema.length > 0">
              <div class="value-header">
                <span class="value-title">
                  <el-icon><EditPen /></el-icon>
                  配置值
                </span>
                <el-button size="small" @click="toggleStructRawJson">
                  {{ showStructRawJson ? '表单模式' : 'JSON 模式' }}
                </el-button>
              </div>
              <div v-if="showStructRawJson" class="struct-raw-json">
                <el-input v-model="structRawJson" type="textarea" :rows="8" placeholder="JSON 格式的配置值" :input-style="{ fontFamily: 'Consolas, Monaco, monospace', fontSize: '13px' }" @blur="syncStructValueFromJson" />
              </div>
              <div v-else class="struct-value-fields">
                <div v-for="(field, index) in structSchema" :key="index" class="struct-value-row">
                  <span class="field-label">{{ field.key }}</span>
                  <el-tag size="small" :type="getStructFieldTypeColor(field.type)" style="margin-right: 8px">{{ field.type }}</el-tag>
                  <div class="field-input" v-if="field.type !== 'struct' && field.type !== 'struct_list'">
                    <el-input v-if="field.type === 'string'" v-model="structValue[field.key]" placeholder="请输入字符串" />
                    <el-input v-else-if="field.type === 'text'" v-model="structValue[field.key]" type="textarea" :rows="3" placeholder="请输入文本" />
                    <el-input-number v-else-if="field.type === 'int'" v-model="structValue[field.key]" :controls="false" style="width: 100%" />
                    <el-input-number v-else-if="field.type === 'float'" v-model="structValue[field.key]" :controls="false" :precision="4" style="width: 100%" />
                    <el-switch v-else-if="field.type === 'bool'" v-model="structValue[field.key]" />
                    <div v-else-if="field.type === 'file'" class="struct-file-picker">
                      <div class="file-thumb" @click="openStructFileSelector('struct', field.key)">
                        <img v-if="structFileUrls[field.key]" :src="structFileUrls[field.key]" @error="(e) => e.target.style.display='none'" />
                        <div v-else class="file-thumb-empty">
                          <el-icon><Picture /></el-icon>
                        </div>
                      </div>
                      <el-button v-if="structValue[field.key]" type="danger" link size="small" class="file-clear-btn" @click="structValue[field.key] = ''; delete structFileUrls[field.key]"><el-icon><Delete /></el-icon></el-button>
                    </div>
                    <div v-else-if="field.type === 'file_list'" class="struct-file-list-picker">
                      <div class="file-thumb-list">
                        <div v-for="(fid, fi) in getFileList(structValue[field.key])" :key="fi" class="file-thumb-item"
                          draggable="true"
                          @dragstart="startInlineListDrag($event, 'file_list', 'struct', field.key, null, -1, fi)"
                          @dragover.prevent="inlineListDragOver($event)"
                          @dragleave="inlineListDragLeave($event)"
                          @drop="dropInlineListItem($event, 'file_list', 'struct', field.key, null, -1, fi)"
                        >
                          <div class="file-thumb" @click="previewFile(fid)">
                            <img v-if="structFileUrls[field.key + '_' + fi]" :src="structFileUrls[field.key + '_' + fi]" @error="(e) => e.target.style.display='none'" />
                            <div v-else class="file-thumb-empty"><el-icon><Picture /></el-icon></div>
                          </div>
                          <el-button class="thumb-del-btn" type="danger" link size="small" @click="removeInlineListItem('file_list', 'struct', field.key, null, -1, fi)"><el-icon><Delete /></el-icon></el-button>
                        </div>
                        <div class="file-thumb file-thumb-add" @click="openStructFileListSelector('struct', field.key)">
                          <el-icon><Plus /></el-icon>
                        </div>
                      </div>
                    </div>
                    <div v-else-if="field.type === 'string_list'" class="struct-string-list">
                      <div class="string-list-items">
                        <div v-for="(sval, si) in ensureStringListArr('struct', field.key, null, -1)" :key="si" class="string-list-item"
                          draggable="true"
                          @dragstart="startInlineListDrag($event, 'string_list', 'struct', field.key, null, -1, si)"
                          @dragover.prevent="inlineListDragOver($event)"
                          @dragleave="inlineListDragLeave($event)"
                          @drop="dropInlineListItem($event, 'string_list', 'struct', field.key, null, -1, si)"
                        >
                          <el-icon class="string-drag-handle"><Rank /></el-icon>
                          <el-input v-model="structStringLists[getSlKey('struct', field.key, null, -1)][si]" size="small" @change="syncStringListBack('struct', field.key, null, -1)" />
                          <el-button type="danger" link size="small" @click="removeInlineListItem('string_list', 'struct', field.key, null, -1, si)"><el-icon><Delete /></el-icon></el-button>
                        </div>
                      </div>
                      <el-button size="small" type="primary" link @click="addInlineStringItem('struct', field.key, null, -1)"><el-icon><Plus /></el-icon> 添加</el-button>
                    </div>
                  </div>
                  <div v-else-if="field.type === 'struct_list'" class="struct-list-value">
                    <div class="struct-list-header">
                      <span class="struct-list-hint">列表项结构</span>
                      <el-tag size="small" type="info">{{ getNestedStructListItems(field.key).length }} 项</el-tag>
                      <el-button size="small" type="primary" link @click="addNestedStructListItem(field.key, field.schema)"><el-icon><Plus /></el-icon> 添加</el-button>
                    </div>
                    <div v-if="field.schema && field.schema.length > 0" class="nested-struct-list-cards">
                      <div v-for="(nItem, nIdx) in getNestedStructListItems(field.key)" :key="nIdx" class="nested-struct-list-card">
                        <div class="nested-card-header">
                          <span class="nested-card-index">#{{ nIdx + 1 }}</span>
                          <el-button type="danger" link size="small" @click="removeNestedStructListItem(field.key, nIdx)"><el-icon><Delete /></el-icon></el-button>
                        </div>
                        <div class="nested-card-body">
                          <div v-for="sub in field.schema" :key="sub.key" class="nested-card-field">
                            <span class="field-label sub">{{ sub.key }}</span>
                            <el-input v-if="sub.type === 'string'" v-model="nItem[sub.key]" size="small" placeholder="请输入" @change="syncNestedStructList(field.key)" />
                            <el-input v-else-if="sub.type === 'text'" v-model="nItem[sub.key]" type="textarea" :rows="2" size="small" placeholder="请输入" @change="syncNestedStructList(field.key)" />
                            <el-input-number v-else-if="sub.type === 'int'" v-model="nItem[sub.key]" :controls="false" size="small" style="width: 100%" @change="syncNestedStructList(field.key)" />
                            <el-input-number v-else-if="sub.type === 'float'" v-model="nItem[sub.key]" :controls="false" :precision="4" size="small" style="width: 100%" @change="syncNestedStructList(field.key)" />
                            <el-switch v-else-if="sub.type === 'bool'" v-model="nItem[sub.key]" @change="syncNestedStructList(field.key)" />
                            <el-input v-else v-model="nItem[sub.key]" size="small" placeholder="请输入" @change="syncNestedStructList(field.key)" />
                          </div>
                        </div>
                      </div>
                    </div>
                    <el-input v-else v-model="structValue[field.key]" type="textarea" :rows="4" placeholder='[{"key": "value"}, ...]' :input-style="{ fontFamily: 'Consolas, Monaco, monospace', fontSize: '13px' }" />
                  </div>
                  <div v-else class="nested-value-fields">
                    <div v-for="(sub, si) in (field.schema || [])" :key="si" class="struct-value-row nested">
                      <span class="field-label sub">{{ field.key }}.{{ sub.key }}</span>
                      <el-tag size="small" :type="getStructFieldTypeColor(sub.type)" style="margin-right: 8px">{{ sub.type }}</el-tag>
                      <div class="field-input">
                        <el-input v-if="sub.type === 'string'" v-model="getNestedValue(field.key)[sub.key]" placeholder="请输入字符串" />
                        <el-input v-else-if="sub.type === 'text'" v-model="getNestedValue(field.key)[sub.key]" type="textarea" :rows="3" placeholder="请输入文本" />
                        <el-input-number v-else-if="sub.type === 'int'" v-model="getNestedValue(field.key)[sub.key]" :controls="false" style="width: 100%" />
                        <el-input-number v-else-if="sub.type === 'float'" v-model="getNestedValue(field.key)[sub.key]" :controls="false" :precision="4" style="width: 100%" />
                        <el-switch v-else-if="sub.type === 'bool'" v-model="getNestedValue(field.key)[sub.key]" />
                        <div v-else-if="sub.type === 'file'" class="struct-file-picker">
                          <div class="file-thumb" @click="openStructFileSelector('struct-nested', field.key, sub.key)">
                            <img v-if="structFileUrls[field.key + '.' + sub.key]" :src="structFileUrls[field.key + '.' + sub.key]" @error="(e) => e.target.style.display='none'" />
                            <div v-else class="file-thumb-empty"><el-icon><Picture /></el-icon></div>
                          </div>
                          <el-button v-if="getNestedValue(field.key)[sub.key]" type="danger" link size="small" class="file-clear-btn" @click="getNestedValue(field.key)[sub.key] = ''; delete structFileUrls[field.key + '.' + sub.key]"><el-icon><Delete /></el-icon></el-button>
                        </div>
                        <div v-else-if="sub.type === 'file_list'" class="struct-file-list-picker">
                          <div class="file-thumb-list">
                            <div v-for="(fid, fi) in getFileList(getNestedValue(field.key)[sub.key])" :key="fi" class="file-thumb-item"
                              draggable="true"
                              @dragstart="startInlineListDrag($event, 'file_list', 'struct-nested', field.key, sub.key, -1, fi)"
                              @dragover.prevent="inlineListDragOver($event)"
                              @dragleave="inlineListDragLeave($event)"
                              @drop="dropInlineListItem($event, 'file_list', 'struct-nested', field.key, sub.key, -1, fi)"
                            >
                              <div class="file-thumb" @click="previewFile(fid)">
                                <img v-if="structFileUrls[field.key + '.' + sub.key + '_' + fi]" :src="structFileUrls[field.key + '.' + sub.key + '_' + fi]" @error="(e) => e.target.style.display='none'" />
                                <div v-else class="file-thumb-empty"><el-icon><Picture /></el-icon></div>
                              </div>
                              <el-button class="thumb-del-btn" type="danger" link size="small" @click="removeInlineListItem('file_list', 'struct-nested', field.key, sub.key, -1, fi)"><el-icon><Delete /></el-icon></el-button>
                            </div>
                            <div class="file-thumb file-thumb-add" @click="openStructFileListSelector('struct-nested', field.key, sub.key)">
                              <el-icon><Plus /></el-icon>
                            </div>
                          </div>
                        </div>
                        <div v-else-if="sub.type === 'string_list'" class="struct-string-list">
                          <div class="string-list-items">
                            <div v-for="(sval, si) in ensureStringListArr('struct-nested', field.key, sub.key, -1)" :key="si" class="string-list-item"
                              draggable="true"
                              @dragstart="startInlineListDrag($event, 'string_list', 'struct-nested', field.key, sub.key, -1, si)"
                              @dragover.prevent="inlineListDragOver($event)"
                              @dragleave="inlineListDragLeave($event)"
                              @drop="dropInlineListItem($event, 'string_list', 'struct-nested', field.key, sub.key, -1, si)"
                            >
                              <el-icon class="string-drag-handle"><Rank /></el-icon>
                              <el-input v-model="structStringLists[getSlKey('struct-nested', field.key, sub.key, -1)][si]" size="small" @change="syncStringListBack('struct-nested', field.key, sub.key, -1)" />
                              <el-button type="danger" link size="small" @click="removeInlineListItem('string_list', 'struct-nested', field.key, sub.key, -1, si)"><el-icon><Delete /></el-icon></el-button>
                            </div>
                          </div>
                          <el-button size="small" type="primary" link @click="addInlineStringItem('struct-nested', field.key, sub.key, -1)"><el-icon><Plus /></el-icon> 添加</el-button>
                        </div>
                        <el-input v-else-if="sub.type === 'struct_list'" v-model="getNestedValue(field.key)[sub.key]" type="textarea" :rows="3" placeholder='[{"key": "value"}, ...]' :input-style="{ fontFamily: 'Consolas, Monaco, monospace', fontSize: '12px' }" />
                        <div v-else-if="sub.type === 'struct' && sub.schema && sub.schema.length > 0" class="nested-sub-struct">
                          <div v-for="ss in sub.schema" :key="ss.key" class="nested-card-field">
                            <span class="field-label sub">{{ ss.key }}</span>
                            <el-input v-if="ss.type === 'string' || ss.type === 'text'" v-model="ensureDeepNested(field.key, sub.key)[ss.key]" size="small" :placeholder="ss.key" />
                            <el-input-number v-else-if="ss.type === 'int'" v-model="ensureDeepNested(field.key, sub.key)[ss.key]" :controls="false" size="small" style="width: 100%" />
                            <el-input-number v-else-if="ss.type === 'float'" v-model="ensureDeepNested(field.key, sub.key)[ss.key]" :controls="false" :precision="4" size="small" style="width: 100%" />
                            <el-switch v-else-if="ss.type === 'bool'" v-model="ensureDeepNested(field.key, sub.key)[ss.key]" />
                            <el-input v-else v-model="ensureDeepNested(field.key, sub.key)[ss.key]" size="small" :placeholder="ss.key" />
                          </div>
                        </div>
                      </div>
                    </div>
                  </div>
                </div>
              </div>
            </div>
          </div>
          <div v-else-if="settingForm.type === 'struct_list'" class="struct-section">
            <!-- Schema 定义（与 struct 共用） -->
            <div class="struct-schema-editor">
              <div class="schema-header">
                <span class="schema-title">
                  <el-icon><Setting /></el-icon>
                  列表项结构定义 (Schema)
                </span>
                <el-button type="primary" size="small" @click="addStructSchemaField()">
                  <el-icon><Plus /></el-icon>添加字段
                </el-button>
              </div>
              <div class="schema-fields" v-if="structSchema.length > 0">
                <div v-for="(field, index) in structSchema" :key="index" class="schema-field-row">
                  <el-input v-model="field.key" placeholder="字段名" style="width: 160px" @change="syncStructValueFromSchema" />
                  <el-select v-model="field.type" placeholder="类型" style="width: 150px" @change="(val) => handleSchemaFieldTypeChange(field, val)">
                    <el-option label="字符串 (string)" value="string" />
                    <el-option label="文本 (text)" value="text" />
                    <el-option label="整数 (int)" value="int" />
                    <el-option label="浮点数 (float)" value="float" />
                    <el-option label="布尔值 (bool)" value="bool" />
                    <el-option label="文件 (file)" value="file" />
                    <el-option label="多文件 (file_list)" value="file_list" />
                    <el-option label="字符串列表 (string_list)" value="string_list" />
                    <el-option label="嵌套结构 (struct)" value="struct" />
                    <el-option label="结构列表 (struct_list)" value="struct_list" />
                  </el-select>
                  <el-button type="danger" :icon="Delete" circle size="small" @click="removeStructSchemaField(index)" />
                  <!-- struct_list schema editor: nested struct -->
                  <div v-if="field.type === 'struct'" class="nested-schema">
                    <div class="nested-schema-header">
                      <span class="nested-label">{{ field.key }} 的子字段：</span>
                      <el-button size="small" @click="addNestedSchemaField(field)">
                        <el-icon><Plus /></el-icon>添加
                      </el-button>
                    </div>
                    <div v-for="(sub, si) in (field.schema || [])" :key="si" class="schema-field-row nested">
                      <el-input v-model="sub.key" placeholder="子字段名" style="width: 140px" @change="syncStructValueFromSchema" />
                      <el-select v-model="sub.type" placeholder="类型" style="width: 150px">
                        <el-option label="字符串 (string)" value="string" />
                        <el-option label="文本 (text)" value="text" />
                        <el-option label="整数 (int)" value="int" />
                        <el-option label="浮点数 (float)" value="float" />
                        <el-option label="布尔值 (bool)" value="bool" />
                        <el-option label="文件 (file)" value="file" />
                        <el-option label="多文件 (file_list)" value="file_list" />
                        <el-option label="字符串列表 (string_list)" value="string_list" />
                      </el-select>
                      <el-button type="danger" :icon="Delete" circle size="small" @click="removeNestedSchemaField(field, si)" />
                    </div>
                  </div>
                  <!-- struct_list schema editor: nested struct_list -->
                  <div v-if="field.type === 'struct_list'" class="nested-schema">
                    <div class="nested-schema-header">
                      <span class="nested-label">{{ field.key }} 的列表项子字段：</span>
                      <el-button size="small" @click="addNestedSchemaField(field)">
                        <el-icon><Plus /></el-icon>添加
                      </el-button>
                    </div>
                    <div v-for="(sub, si) in (field.schema || [])" :key="si" class="schema-field-row nested">
                      <el-input v-model="sub.key" placeholder="子字段名" style="width: 140px" @change="syncStructValueFromSchema" />
                      <el-select v-model="sub.type" placeholder="类型" style="width: 150px">
                        <el-option label="字符串 (string)" value="string" />
                        <el-option label="文本 (text)" value="text" />
                        <el-option label="整数 (int)" value="int" />
                        <el-option label="浮点数 (float)" value="float" />
                        <el-option label="布尔值 (bool)" value="bool" />
                        <el-option label="文件 (file)" value="file" />
                        <el-option label="多文件 (file_list)" value="file_list" />
                        <el-option label="字符串列表 (string_list)" value="string_list" />
                      </el-select>
                      <el-button type="danger" :icon="Delete" circle size="small" @click="removeNestedSchemaField(field, si)" />
                    </div>
                  </div>
                </div>
              </div>
              <div v-else class="schema-empty">
                <span>暂无字段定义，请点击上方「添加字段」定义每个列表项的结构</span>
              </div>
            </div>

            <!-- 值编辑 -->
            <div class="struct-value-editor" v-if="structSchema.length > 0">
              <div class="value-header">
                <span class="value-title">
                  <el-icon><EditPen /></el-icon>
                  配置值
                </span>
                <div class="value-header-actions">
                  <el-tag size="small" type="info">{{ structListItems.length }} 项</el-tag>
                  <el-button size="small" @click="toggleStructListRawMode">
                    {{ structListRawMode ? '表单模式' : 'JSON 模式' }}
                  </el-button>
                  <el-button type="primary" size="small" @click="addStructListItem">
                    <el-icon><Plus /></el-icon>添加
                  </el-button>
                </div>
              </div>

              <!-- JSON 模式 -->
              <div v-if="structListRawMode" class="struct-raw-json">
                <el-input v-model="structListRawJson" type="textarea" :rows="10" placeholder='[{"field": "value"}, ...]' :input-style="{ fontFamily: 'Consolas, Monaco, monospace', fontSize: '13px', lineHeight: '1.5' }" @blur="syncStructListFromJson" />
                <div class="struct-list-actions">
                  <el-button size="small" @click="formatStructListJson">格式化</el-button>
                </div>
              </div>

              <!-- 表单模式 -->
              <div v-else class="struct-list-cards">
                <div v-if="structListItems.length === 0" class="schema-empty">
                  <span>暂无数据项，点击上方「添加」按钮创建</span>
                </div>
                <div
                  v-for="(item, itemIndex) in structListItems"
                  :key="item._id"
                  class="struct-list-card"
                  :class="{ 'is-collapsed': item._collapsed }"
                  draggable="true"
                  @dragstart="handleStructListDragStart($event, itemIndex)"
                  @dragover.prevent="handleStructListDragOver($event, itemIndex)"
                  @dragleave="handleStructListDragLeave($event)"
                  @drop="handleStructListDrop($event, itemIndex)"
                >
                  <div class="struct-list-card-header" @click="toggleStructListItemCollapse(itemIndex)">
                    <div class="card-header-left">
                      <div class="drag-handle" @click.stop @mousedown.stop>
                        <el-icon><Rank /></el-icon>
                      </div>
                      <el-icon class="collapse-arrow" :class="{ rotated: !item._collapsed }"><ArrowRight /></el-icon>
                      <span class="card-index">#{{ itemIndex + 1 }}</span>
                      <span class="card-summary">{{ getStructListItemSummary(item) }}</span>
                    </div>
                    <div class="card-header-right" @click.stop>
                      <el-button type="danger" link size="small" @click="removeStructListItem(itemIndex)">
                        <el-icon><Delete /></el-icon>
                      </el-button>
                    </div>
                  </div>
                  <div class="struct-list-card-body" v-show="!item._collapsed">
                    <div v-for="field in structSchema" :key="field.key" class="struct-list-field-row">
                      <span class="field-label">{{ field.key }}</span>
                      <el-tag size="small" :type="getStructFieldTypeColor(field.type)">{{ field.type }}</el-tag>
                      <div class="field-input">
                        <el-input v-if="field.type === 'string'" v-model="item[field.key]" placeholder="请输入" size="small" />
                        <el-input v-else-if="field.type === 'text'" v-model="item[field.key]" type="textarea" :rows="2" placeholder="请输入" size="small" />
                        <el-input-number v-else-if="field.type === 'int'" v-model="item[field.key]" :controls="false" size="small" style="width: 100%" />
                        <el-input-number v-else-if="field.type === 'float'" v-model="item[field.key]" :controls="false" :precision="4" size="small" style="width: 100%" />
                        <el-switch v-else-if="field.type === 'bool'" v-model="item[field.key]" />
                        <div v-else-if="field.type === 'file'" class="struct-file-picker">
                          <div class="file-thumb small" @click="openStructFileSelector('struct-list', field.key, null, itemIndex)">
                            <img v-if="structFileUrls['list_' + itemIndex + '_' + field.key]" :src="structFileUrls['list_' + itemIndex + '_' + field.key]" @error="(e) => e.target.style.display='none'" />
                            <div v-else class="file-thumb-empty"><el-icon><Picture /></el-icon></div>
                          </div>
                          <el-button v-if="item[field.key]" type="danger" link size="small" class="file-clear-btn" @click="item[field.key] = ''; delete structFileUrls['list_' + itemIndex + '_' + field.key]"><el-icon><Delete /></el-icon></el-button>
                        </div>
                        <div v-else-if="field.type === 'file_list'" class="struct-file-list-picker">
                          <div class="file-thumb-list">
                            <div v-for="(fid, fi) in getFileList(item[field.key])" :key="fi" class="file-thumb-item"
                              draggable="true"
                              @dragstart="startInlineListDrag($event, 'file_list', 'struct-list', field.key, null, itemIndex, fi)"
                              @dragover.prevent="inlineListDragOver($event)"
                              @dragleave="inlineListDragLeave($event)"
                              @drop="dropInlineListItem($event, 'file_list', 'struct-list', field.key, null, itemIndex, fi)"
                            >
                              <div class="file-thumb small" @click="previewFile(fid)">
                                <img v-if="structFileUrls['list_' + itemIndex + '_' + field.key + '_' + fi]" :src="structFileUrls['list_' + itemIndex + '_' + field.key + '_' + fi]" @error="(e) => e.target.style.display='none'" />
                                <div v-else class="file-thumb-empty"><el-icon><Picture /></el-icon></div>
                              </div>
                              <el-button class="thumb-del-btn" type="danger" link size="small" @click="removeInlineListItem('file_list', 'struct-list', field.key, null, itemIndex, fi)"><el-icon><Delete /></el-icon></el-button>
                            </div>
                            <div class="file-thumb small file-thumb-add" @click="openStructFileListSelector('struct-list', field.key, null, itemIndex)">
                              <el-icon><Plus /></el-icon>
                            </div>
                          </div>
                        </div>
                        <div v-else-if="field.type === 'string_list'" class="struct-string-list">
                          <div class="string-list-items">
                            <div v-for="(sval, si) in ensureStringListArr('struct-list', field.key, null, itemIndex)" :key="si" class="string-list-item"
                              draggable="true"
                              @dragstart="startInlineListDrag($event, 'string_list', 'struct-list', field.key, null, itemIndex, si)"
                              @dragover.prevent="inlineListDragOver($event)"
                              @dragleave="inlineListDragLeave($event)"
                              @drop="dropInlineListItem($event, 'string_list', 'struct-list', field.key, null, itemIndex, si)"
                            >
                              <el-icon class="string-drag-handle"><Rank /></el-icon>
                              <el-input v-model="structStringLists[getSlKey('struct-list', field.key, null, itemIndex)][si]" size="small" @change="syncStringListBack('struct-list', field.key, null, itemIndex)" />
                              <el-button type="danger" link size="small" @click="removeInlineListItem('string_list', 'struct-list', field.key, null, itemIndex, si)"><el-icon><Delete /></el-icon></el-button>
                            </div>
                          </div>
                          <el-button size="small" type="primary" link @click="addInlineStringItem('struct-list', field.key, null, itemIndex)"><el-icon><Plus /></el-icon> 添加</el-button>
                        </div>
                        <el-input v-else v-model="item[field.key]" placeholder="请输入" size="small" />
                      </div>
                    </div>
                  </div>
                </div>
              </div>
            </div>
          </div>
          <div v-else-if="settingForm.type === 'string_list'" class="string-list-section">
            <div class="editable-string-list">
              <div class="string-list-header">
                <span class="list-title">字符串列表 ({{ getEditableFormStringList().length }} 项)</span>
                <el-button type="primary" size="small" @click="addFormEditableStringItem">添加项目</el-button>
              </div>
              <div class="string-list-items">
                <div
                  v-for="(item, index) in getEditableFormStringList()"
                  :key="`form-editable-${index}`"
                  class="string-list-item"
                  draggable="true"
                  @dragstart="handleFormDragStart($event, index)"
                  @dragover.prevent
                  @drop="handleFormDrop($event, index)"
                >
                  <div class="drag-handle">
                    <el-icon><Rank /></el-icon>
                  </div>
                  <div class="item-content">
                    <el-input
                      v-if="item.editing"
                      v-model="item.value"
                      size="small"
                      @blur="finishFormEditItem(index)"
                      @keyup.enter="finishFormEditItem(index)"
                      ref="formEditInput"
                      @mounted="focusFormEditInput"
                    />
                    <span v-else class="item-text" @dblclick="startFormEditItem(index)">
                      {{ truncateFileName(item.value, 40) }}
                    </span>
                  </div>
                  <div class="item-actions">
                    <el-button type="text" size="small" @click="startFormEditItem(index)" :icon="Edit" />
                    <el-button type="text" size="small" @click="removeFormEditableStringItem(index)" :icon="Delete" class="danger-btn" />
                  </div>
                </div>
              </div>
            </div>
          </div>
          <el-input
            v-else
            v-model="settingForm.value"
            placeholder="请输入配置值"
          />
        </el-form-item>
        <el-form-item label="是否开放访问">
          <el-switch v-model="settingForm.open" />
          <span style="margin-left: 10px; color: #909399; font-size: 12px;">
            开启后允许公开访问
          </span>
        </el-form-item>
        <el-form-item label="备注" prop="note">
          <el-input
            v-model="settingForm.note"
            type="textarea"
            :rows="3"
            placeholder="请输入备注信息"
          />
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="settingDialogVisible = false">取消</el-button>
        <el-button type="primary" @click="submitSettingForm">确定</el-button>
      </template>
    </el-dialog>

    <!-- 一键导入配置对话框 -->
    <el-dialog
      v-model="batchImportDialogVisible"
      title="一键导入配置"
      width="900px"
      destroy-on-close
      class="dialog-scrollable"
      :close-on-click-modal="!batchImportLoading"
      :close-on-press-escape="!batchImportLoading"
      :show-close="!batchImportLoading"
    >
      <el-alert
        title="使用说明"
        type="info"
        :closable="false"
        show-icon
        style="margin-bottom: 16px"
      >
        <template #default>
          <div style="line-height: 1.8">
            粘贴通过「一键复制」导出的内容即可自动导入。系统会自动识别配置组名称，不存在则自动创建。<br />
            格式示例：<br />
            <code>[配置组] 移动端-全局配置</code><br />
            <code>| 配置名 | 类型 | 默认值 | 说明 |</code><br />
            <code>|--------|------|--------|------|</code><br />
            <code>| `移动端_主题主色` | `string` | `#2B7EFB` | 按钮、链接、选中态主色 |</code>
          </div>
        </template>
      </el-alert>

      <div v-if="batchImportLoading" class="import-progress-panel">
        <div class="progress-header">
          <el-icon class="is-loading" :size="20" color="#409eff"><Loading /></el-icon>
          <span style="font-weight: 600; font-size: 15px; color: #303133">正在导入...</span>
        </div>
        <div class="progress-info">
          <div v-if="batchImportStatusText" style="margin-bottom: 12px; color: #606266; font-size: 13px;">
            {{ batchImportStatusText }}
          </div>
          <el-progress
            :percentage="batchImportTotal > 0 ? Math.round(batchImportProgress / batchImportTotal * 100) : 0"
            :stroke-width="18"
            :text-inside="true"
            striped
            striped-flow
          />
          <div style="margin-top: 8px; color: #909399; font-size: 12px; text-align: center;">
            {{ batchImportProgress }} / {{ batchImportTotal }}
          </div>
        </div>
      </div>

      <template v-if="!batchImportLoading">
        <el-form label-width="100px">
          <el-form-item label="配置组">
            <div v-if="batchImportGroupName" style="display: flex; align-items: center; gap: 8px; width: 100%;">
              <el-tag :type="batchImportGroupExists ? 'success' : 'warning'" size="large" style="font-size: 14px;">
                <el-icon style="margin-right: 4px;"><Folder /></el-icon>
                {{ batchImportGroupName }}
              </el-tag>
              <span v-if="batchImportGroupExists" style="color: #67c23a; font-size: 12px;">已存在，将导入到此配置组</span>
              <span v-else style="color: #e6a23c; font-size: 12px;">不存在，将自动创建后导入</span>
            </div>
            <span v-else style="color: #c0c4cc; font-size: 13px;">粘贴内容后自动识别（首行 <code>[配置组] 名称</code>）</span>
          </el-form-item>
          <el-form-item label="默认公开">
            <el-switch v-model="batchImportOpen" />
            <span style="margin-left: 8px; color: #909399; font-size: 13px">导入的配置项是否默认对外公开</span>
          </el-form-item>
          <el-form-item label="导入内容">
            <el-input
              v-model="batchImportText"
              type="textarea"
              :rows="10"
              placeholder="粘贴通过「一键复制」导出的内容..."
              style="font-family: monospace"
            />
          </el-form-item>
        </el-form>

        <div v-if="batchImportParsed.length > 0" style="margin-top: 8px">
          <div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px">
            <span style="font-weight: 600; font-size: 14px; color: #303133">
              解析预览（共 {{ batchImportParsed.length }} 条）
            </span>
            <el-button type="primary" link @click="parseBatchImportText">
              <el-icon><Search /></el-icon>重新解析
            </el-button>
          </div>
          <el-table :data="batchImportParsed" border size="small" max-height="300">
            <el-table-column type="index" label="#" width="50" />
            <el-table-column prop="name" label="配置名" min-width="180">
              <template #default="{ row }">
                <span :style="{ color: row._duplicate ? '#F56C6C' : '' }">
                  {{ row.name }}
                  <el-tag v-if="row._duplicate" type="danger" size="small" style="margin-left: 4px">重复</el-tag>
                </span>
              </template>
            </el-table-column>
            <el-table-column prop="type" label="类型" width="120">
              <template #default="{ row }">
                <el-tag size="small">{{ row.type }}</el-tag>
              </template>
            </el-table-column>
            <el-table-column prop="value" label="默认值" min-width="160">
              <template #default="{ row }">
                <span class="text-value">{{ row.value }}</span>
              </template>
            </el-table-column>
            <el-table-column prop="note" label="说明" min-width="200" />
            <el-table-column label="操作" width="70" fixed="right">
              <template #default="{ $index }">
                <el-button link type="danger" size="small" @click="batchImportParsed.splice($index, 1)">移除</el-button>
              </template>
            </el-table-column>
          </el-table>
        </div>
      </template>

      <template #footer>
        <el-button @click="batchImportDialogVisible = false" :disabled="batchImportLoading">取消</el-button>
        <el-button type="primary" @click="parseBatchImportText" :disabled="!batchImportText.trim() || batchImportLoading">
          解析预览
        </el-button>
        <el-button
          type="success"
          :loading="batchImportLoading"
          :disabled="batchImportParsed.length === 0 || !batchImportGroupName"
          @click="submitBatchImport"
        >
          确认导入 ({{ batchImportParsed.length }})
        </el-button>
      </template>
    </el-dialog>

    <!-- 图片查看器 -->
    <el-dialog v-model="imageViewerVisible" width="auto" destroy-on-close>
      <img :src="currentViewImage" style="max-width: 100%; max-height: 80vh;" />
    </el-dialog>

    <!-- 图片选择器 -->
    <ImageSelector 
      v-model="imageSelectorVisible" 
      :current-file-id="currentImageSelectorFileId"
      :multiple="imageSelectorMode === 'list' || imageSelectorMode === 'struct-file-list'"
      @confirm="handleImageSelectorConfirm"
    />
  </div>
</template>

<script setup>
import { ref, reactive, onMounted, watch, nextTick, computed } from 'vue'
import { useRoute } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Search, Plus, Delete, UploadFilled, Folder, Document, Loading, Picture, Edit, Rank, FolderAdd, CopyDocument, CircleCheck, CircleClose, InfoFilled, Clock, Setting, EditPen, Connection, ArrowRight, View } from '@element-plus/icons-vue'
import {
  getSettingGroupList,
  getSettingGroupInfo,
  createSettingGroup,
  updateSettingGroup,
  deleteSettingGroup,
  getSettingList,
  getSettingInfo,
  createSetting,
  updateSetting,
  setSettingOpen,
  deleteSetting
} from '@/api/admin/setting'
import { uploadFile, getFileDetail, downloadFile } from '@/api/admin/file'
import ImageSelector from '@/components/admin/ImageSelector.vue'

const route = useRoute()

// ==================== 配置组 Tab 相关 ====================
const activeGroupId = ref('')
const allGroupList = ref([])

const currentGroup = computed(() => {
  return allGroupList.value.find(g => String(g.id) === activeGroupId.value) || null
})

// ==================== 配置项列表 ====================
const settingLoading = ref(false)
const settingList = ref([])
const settingTotal = ref(0)
const settingPagination = reactive({
  page: 1,
  pageSize: 12
})

// 查询参数
const queryParams = reactive({
  key: ''
})

// ==================== 配置组表单 ====================
const groupForm = reactive({
  id: undefined,
  name: '',
  note: ''
})

const groupRules = {
  name: [
    { required: true, message: '请输入配置组名称', trigger: 'blur' }
  ]
}

const groupLoading = ref(false)
const groupDialogVisible = ref(false)
const groupDialogTitle = ref('新增配置组')
const groupFormRef = ref(null)

// ==================== 配置表单 ====================
const settingForm = reactive({
  id: undefined,
  name: '',
  value: '',
  type: 'string',
  settingGroupID: undefined,
  open: false,
  note: ''
})

const settingRules = {
  name: [
    { required: true, message: '请输入配置名称', trigger: 'blur' }
  ],
  type: [
    { required: true, message: '请选择配置类型', trigger: 'change' }
  ],
  settingGroupID: [
    { required: true, message: '请选择配置组', trigger: 'change' }
  ]
}

const settingDialogVisible = ref(false)
const settingDialogTitle = ref('新增配置')
const settingFormRef = ref(null)
const fileInfo = ref(null)
const fileUploading = ref(false)
const fileListInfo = ref([])
const newStringItem = ref('')
const imageViewerVisible = ref(false)
const currentViewImage = ref('')
const imageSelectorVisible = ref(false)
const currentImageSelectorFileId = ref('')
const imageSelectorMode = ref('single')

// 表单相关可编辑列表
const editableFormStringList = ref([])
const editableFormFileList = ref([])
const formDraggedIndex = ref(-1)
const formFileDraggedIndex = ref(-1)

// ==================== 批量导入 ====================
const batchImportDialogVisible = ref(false)
const batchImportText = ref('')
const batchImportParsed = ref([])
const batchImportGroupId = ref(undefined)
const batchImportGroupName = ref('')
const batchImportGroupExists = ref(false)
const batchImportOpen = ref(true)
const batchImportLoading = ref(false)
const batchImportProgress = ref(0)
const batchImportTotal = ref(0)
const batchImportStatusText = ref('')

// ==================== Struct 相关 ====================
const structSchema = ref([])
const structValue = reactive({})
const showStructRawJson = ref(false)
const structRawJson = ref('')

const addStructSchemaField = () => {
  structSchema.value.push({ key: '', type: 'string', schema: [] })
}

const removeStructSchemaField = (index) => {
  const field = structSchema.value[index]
  if (field.key && structValue[field.key] !== undefined) {
    delete structValue[field.key]
  }
  structSchema.value.splice(index, 1)
}

const handleSchemaFieldTypeChange = (field, newType) => {
  if (newType === 'struct') {
    if (!field.schema) field.schema = []
    if (field.key) structValue[field.key] = {}
  } else if (newType === 'struct_list') {
    field.schema = []
    if (field.key) structValue[field.key] = '[]'
  } else {
    field.schema = []
    if (field.key) {
      if (newType === 'bool') structValue[field.key] = false
      else if (newType === 'int' || newType === 'float') structValue[field.key] = 0
      else structValue[field.key] = ''
    }
  }
}

const STRUCT_TYPE_LABELS = {
  string: '字符串', text: '文本', int: '整数', float: '浮点数',
  bool: '布尔值', file: '文件', file_list: '多文件', string_list: '字符串列表',
  struct: '嵌套结构', struct_list: '结构列表'
}

const addNestedSchemaField = (parentField) => {
  if (!parentField.schema) parentField.schema = []
  parentField.schema.push({ key: '', type: 'string' })
}

const removeNestedSchemaField = (parentField, index) => {
  const sub = parentField.schema[index]
  if (parentField.key && sub.key && structValue[parentField.key]) {
    delete structValue[parentField.key][sub.key]
  }
  parentField.schema.splice(index, 1)
}

const syncStructValueFromSchema = () => {
  structSchema.value.forEach(field => {
    if (!field.key) return
    if (field.type === 'struct') {
      if (!structValue[field.key] || typeof structValue[field.key] !== 'object') {
        structValue[field.key] = {}
      }
    } else if (structValue[field.key] === undefined) {
      if (field.type === 'bool') structValue[field.key] = false
      else if (field.type === 'int' || field.type === 'float') structValue[field.key] = 0
      else structValue[field.key] = ''
    }
  })
}

const getStructFieldDefaultValue = (type) => {
  if (type === 'bool') return false
  if (type === 'int' || type === 'float') return 0
  if (type === 'struct') return {}
  if (type === 'struct_list') return '[]'
  return ''
}

// ==================== Struct List 相关 ====================
const structListRawJson = ref('[]')
const structListRawMode = ref(false)
const structListItems = ref([])
const structListDragIndex = ref(-1)
let structListIdSeq = 0

const toggleStructListRawMode = () => {
  if (!structListRawMode.value) {
    structListRawJson.value = JSON.stringify(
      structListItems.value.map(item => {
        const clean = { ...item }
        delete clean._id
        delete clean._collapsed
        return clean
      }), null, 2
    )
  }
  structListRawMode.value = !structListRawMode.value
}

const syncStructListFromJson = () => {
  try {
    const arr = JSON.parse(structListRawJson.value)
    if (!Array.isArray(arr)) { ElMessage.warning('值必须是 JSON 数组'); return }
    structListItems.value = arr.map(item => ({ ...item, _id: ++structListIdSeq, _collapsed: false }))
  } catch {
    ElMessage.warning('JSON 格式不合法')
  }
}

const formatStructListJson = () => {
  try {
    const parsed = JSON.parse(structListRawJson.value)
    structListRawJson.value = JSON.stringify(parsed, null, 2)
  } catch {
    ElMessage.warning('JSON 格式不合法')
  }
}

const addStructListItem = () => {
  const newItem = { _id: ++structListIdSeq, _collapsed: false }
  structSchema.value.forEach(field => {
    if (!field.key) return
    newItem[field.key] = getStructFieldDefaultValue(field.type)
  })
  structListItems.value.push(newItem)
}

const removeStructListItem = (index) => {
  structListItems.value.splice(index, 1)
}

const toggleStructListItemCollapse = (index) => {
  structListItems.value[index]._collapsed = !structListItems.value[index]._collapsed
}

const getStructListItemSummary = (item) => {
  const parts = []
  for (const field of structSchema.value) {
    if (!field.key || field.type === 'struct' || field.type === 'struct_list') continue
    const val = item[field.key]
    if (val !== undefined && val !== '' && val !== null) {
      const str = String(val)
      parts.push(str.length > 20 ? str.slice(0, 20) + '...' : str)
    }
    if (parts.length >= 3) break
  }
  return parts.length > 0 ? parts.join(' | ') : '(空)'
}

const handleStructListDragStart = (event, index) => {
  structListDragIndex.value = index
  event.dataTransfer.effectAllowed = 'move'
  event.target.style.opacity = '0.5'
}

const handleStructListDragOver = (event, index) => {
  event.dataTransfer.dropEffect = 'move'
  const el = event.currentTarget
  if (el) el.classList.add('drag-over')
}

const handleStructListDragLeave = (event) => {
  const el = event.currentTarget
  if (el) el.classList.remove('drag-over')
}

const handleStructListDrop = (event, dropIndex) => {
  event.preventDefault()
  const el = event.currentTarget
  if (el) el.classList.remove('drag-over')
  const dragIndex = structListDragIndex.value
  if (dragIndex === dropIndex || dragIndex < 0) return
  const items = [...structListItems.value]
  const [dragged] = items.splice(dragIndex, 1)
  items.splice(dropIndex, 0, dragged)
  structListItems.value = items
  structListDragIndex.value = -1
  event.target.style.opacity = '1'
}

// inline list helpers for file_list / string_list within struct
const inlineListDragIndex = ref(-1)
const structStringLists = reactive({})

const getSlKey = (source, fieldKey, subKey, itemIndex) => {
  if (source === 'struct') return 'sl_' + fieldKey
  if (source === 'struct-nested') return 'sl_' + fieldKey + '.' + subKey
  if (source === 'struct-list') return 'sl_list_' + itemIndex + '_' + fieldKey
  return 'sl_' + fieldKey
}

const ensureStringListArr = (source, fieldKey, subKey, itemIndex) => {
  const key = getSlKey(source, fieldKey, subKey, itemIndex)
  if (!structStringLists[key]) {
    const raw = getInlineListValue(source, fieldKey, subKey, itemIndex)
    structStringLists[key] = raw ? (Array.isArray(raw) ? [...raw] : String(raw).split(',').filter(Boolean)) : []
  }
  return structStringLists[key]
}

const syncStringListBack = (source, fieldKey, subKey, itemIndex) => {
  const key = getSlKey(source, fieldKey, subKey, itemIndex)
  const arr = structStringLists[key] || []
  const val = arr.filter(Boolean).join(',')
  setInlineListValue(source, fieldKey, subKey, itemIndex, val)
}

const getInlineListValue = (source, fieldKey, subKey, itemIndex) => {
  if (source === 'struct') return structValue[fieldKey] || ''
  if (source === 'struct-nested') return getNestedValue(fieldKey)[subKey] || ''
  if (source === 'struct-list') return structListItems.value[itemIndex]?.[fieldKey] || ''
  return ''
}

const setInlineListValue = (source, fieldKey, subKey, itemIndex, val) => {
  if (source === 'struct') structValue[fieldKey] = val
  else if (source === 'struct-nested') getNestedValue(fieldKey)[subKey] = val
  else if (source === 'struct-list' && structListItems.value[itemIndex]) structListItems.value[itemIndex][fieldKey] = val
}

const getFileListPrefix = (source, fieldKey, subKey, itemIndex) => {
  if (source === 'struct') return fieldKey + '_'
  if (source === 'struct-nested') return fieldKey + '.' + subKey + '_'
  if (source === 'struct-list') return 'list_' + itemIndex + '_' + fieldKey + '_'
  return ''
}

const reindexFileUrls = (prefix, urlsByOldIndex, indexMap) => {
  Object.keys(structFileUrls).forEach(k => { if (k.startsWith(prefix)) delete structFileUrls[k] })
  for (const [newIdx, oldIdx] of Object.entries(indexMap)) {
    if (urlsByOldIndex[oldIdx]) structFileUrls[prefix + newIdx] = urlsByOldIndex[oldIdx]
  }
}

const removeInlineListItem = (listType, source, fieldKey, subKey, itemIndex, removeIndex) => {
  if (listType === 'string_list') {
    const key = getSlKey(source, fieldKey, subKey, itemIndex)
    structStringLists[key].splice(removeIndex, 1)
    syncStringListBack(source, fieldKey, subKey, itemIndex)
  } else {
    const prefix = getFileListPrefix(source, fieldKey, subKey, itemIndex)
    const raw = getInlineListValue(source, fieldKey, subKey, itemIndex)
    const arr = getFileList(raw)
    const oldUrls = {}
    arr.forEach((_, i) => { oldUrls[i] = structFileUrls[prefix + i] })
    arr.splice(removeIndex, 1)
    setInlineListValue(source, fieldKey, subKey, itemIndex, arr.join(','))
    const indexMap = {}
    let oi = 0
    for (let ni = 0; ni < arr.length; ni++) {
      if (oi === removeIndex) oi++
      indexMap[ni] = oi
      oi++
    }
    reindexFileUrls(prefix, oldUrls, indexMap)
  }
}

const startInlineListDrag = (event, listType, source, fieldKey, subKey, itemIndex, dragIdx) => {
  inlineListDragIndex.value = dragIdx
  event.dataTransfer.effectAllowed = 'move'
  event.dataTransfer.setData('text/plain', String(dragIdx))
  const item = event.target.closest('.file-thumb-item, .string-list-item')
  if (item) item.style.opacity = '0.5'
}

const inlineListDragOver = (event) => {
  event.dataTransfer.dropEffect = 'move'
  const el = event.currentTarget
  if (el) el.classList.add('drag-over')
}

const inlineListDragLeave = (event) => {
  const el = event.currentTarget
  if (el) el.classList.remove('drag-over')
}

const dropInlineListItem = (event, listType, source, fieldKey, subKey, itemIndex, dropIdx) => {
  event.preventDefault()
  const el = event.currentTarget
  if (el) el.classList.remove('drag-over')
  const dragIdx = inlineListDragIndex.value
  if (dragIdx === dropIdx || dragIdx < 0) { inlineListDragIndex.value = -1; return }
  if (listType === 'string_list') {
    const key = getSlKey(source, fieldKey, subKey, itemIndex)
    const arr = structStringLists[key]
    const [dragged] = arr.splice(dragIdx, 1)
    arr.splice(dropIdx, 0, dragged)
    syncStringListBack(source, fieldKey, subKey, itemIndex)
  } else {
    const prefix = getFileListPrefix(source, fieldKey, subKey, itemIndex)
    const raw = getInlineListValue(source, fieldKey, subKey, itemIndex)
    const arr = getFileList(raw)
    const oldUrls = {}
    arr.forEach((_, i) => { oldUrls[i] = structFileUrls[prefix + i] })
    const [dragged] = arr.splice(dragIdx, 1)
    arr.splice(dropIdx, 0, dragged)
    setInlineListValue(source, fieldKey, subKey, itemIndex, arr.join(','))
    // build index map: new position -> old position
    const order = Array.from({ length: arr.length + 1 }, (_, i) => i)
    const [d] = order.splice(dragIdx, 1)
    order.splice(dropIdx, 0, d)
    // order[newIdx] = oldIdx
    const indexMap = {}
    for (let i = 0; i < arr.length; i++) indexMap[i] = order[i]
    reindexFileUrls(prefix, oldUrls, indexMap)
  }
  inlineListDragIndex.value = -1
}

const addInlineStringItem = (source, fieldKey, subKey, itemIndex) => {
  const key = getSlKey(source, fieldKey, subKey, itemIndex)
  if (!structStringLists[key]) structStringLists[key] = []
  structStringLists[key].push('')
}

const resetStructStringLists = () => {
  Object.keys(structStringLists).forEach(k => delete structStringLists[k])
  Object.keys(nestedStructListCache).forEach(k => delete nestedStructListCache[k])
}

const buildStructListValue = () => {
  return JSON.stringify(
    structListItems.value.map(item => {
      const clean = { ...item }
      delete clean._id
      delete clean._collapsed
      return clean
    })
  )
}

const initStructListFromValue = (value, parsedValue = null) => {
  Object.keys(structFileUrls).forEach(k => { if (k.startsWith('list_')) delete structFileUrls[k] })
  Object.keys(structStringLists).forEach(k => { if (k.startsWith('sl_list_')) delete structStringLists[k] })
  try {
    const arr = typeof value === 'string' ? JSON.parse(value) : (Array.isArray(value) ? value : [])
    structListItems.value = arr.map(item => ({ ...item, _id: ++structListIdSeq, _collapsed: true }))
  } catch {
    structListItems.value = []
  }
  if (parsedValue && Array.isArray(parsedValue)) {
    const schema = structSchema.value || []
    parsedValue.forEach((parsedItem, itemIndex) => {
      if (!parsedItem || typeof parsedItem !== 'object') return
      for (const field of schema) {
        if (field.type === 'file' && parsedItem[field.key]) {
          structFileUrls['list_' + itemIndex + '_' + field.key] = processImageUrl(parsedItem[field.key])
        } else if (field.type === 'file_list' && parsedItem[field.key]) {
          const urls = Array.isArray(parsedItem[field.key]) ? parsedItem[field.key] : []
          urls.forEach((url, fi) => {
            structFileUrls['list_' + itemIndex + '_' + field.key + '_' + fi] = processImageUrl(url)
          })
        }
      }
    })
  }
  structListRawMode.value = false
}

const getNestedValue = (parentKey) => {
  if (!structValue[parentKey] || typeof structValue[parentKey] !== 'object') {
    structValue[parentKey] = {}
  }
  return structValue[parentKey]
}

const ensureDeepNested = (parentKey, subKey) => {
  const parent = getNestedValue(parentKey)
  if (!parent[subKey] || typeof parent[subKey] !== 'object') {
    parent[subKey] = {}
  }
  return parent[subKey]
}

const nestedStructListCache = reactive({})

const getNestedStructListItems = (fieldKey) => {
  if (!nestedStructListCache[fieldKey]) {
    try {
      const raw = structValue[fieldKey]
      const arr = typeof raw === 'string' ? JSON.parse(raw) : (Array.isArray(raw) ? raw : [])
      nestedStructListCache[fieldKey] = Array.isArray(arr) ? arr : []
    } catch {
      nestedStructListCache[fieldKey] = []
    }
  }
  return nestedStructListCache[fieldKey]
}

const syncNestedStructList = (fieldKey) => {
  structValue[fieldKey] = JSON.stringify(nestedStructListCache[fieldKey] || [])
}

const addNestedStructListItem = (fieldKey, schema) => {
  if (!nestedStructListCache[fieldKey]) nestedStructListCache[fieldKey] = []
  const newItem = {}
  if (schema) {
    for (const s of schema) {
      if (s.type === 'int' || s.type === 'float') newItem[s.key] = 0
      else if (s.type === 'bool') newItem[s.key] = false
      else newItem[s.key] = ''
    }
  }
  nestedStructListCache[fieldKey].push(newItem)
  syncNestedStructList(fieldKey)
}

const removeNestedStructListItem = (fieldKey, index) => {
  if (nestedStructListCache[fieldKey]) {
    nestedStructListCache[fieldKey].splice(index, 1)
    syncNestedStructList(fieldKey)
  }
}

const getFileList = (val) => {
  if (!val) return []
  return String(val).split(',').filter(Boolean)
}


const toggleStructRawJson = () => {
  if (!showStructRawJson.value) {
    structRawJson.value = JSON.stringify(structValue, null, 2)
  }
  showStructRawJson.value = !showStructRawJson.value
}

const syncStructValueFromJson = () => {
  try {
    const parsed = JSON.parse(structRawJson.value)
    Object.keys(structValue).forEach(k => delete structValue[k])
    Object.assign(structValue, parsed)
  } catch (e) {
    ElMessage.warning('JSON 格式不合法')
  }
}

const buildSchemaJson = () => {
  const schema = {}
  structSchema.value.forEach(field => {
    if (!field.key) return
    if ((field.type === 'struct' || field.type === 'struct_list') && field.schema && field.schema.length > 0) {
      const innerSchema = {}
      field.schema.forEach(sub => {
        if (sub.key) innerSchema[sub.key] = sub.type
      })
      schema[field.key] = { type: field.type, schema: innerSchema }
    } else {
      schema[field.key] = field.type
    }
  })
  return JSON.stringify(schema)
}

const parseSchemaJson = (schemaStr) => {
  try {
    const schema = typeof schemaStr === 'string' ? JSON.parse(schemaStr) : schemaStr
    const fields = []
    Object.entries(schema).forEach(([key, val]) => {
      if (typeof val === 'object' && (val.type === 'struct' || val.type === 'struct_list')) {
        const subFields = []
        if (val.schema) {
          Object.entries(val.schema).forEach(([subKey, subType]) => {
            subFields.push({ key: subKey, type: subType })
          })
        }
        fields.push({ key, type: val.type, schema: subFields })
      } else {
        fields.push({ key, type: typeof val === 'string' ? val : 'string', schema: [] })
      }
    })
    return fields
  } catch {
    return []
  }
}

const getStructFieldTypeColor = (type) => {
  const map = { string: 'primary', text: 'primary', int: 'success', float: 'warning', bool: 'info', file: 'danger', file_list: 'danger', string_list: '', struct: '', struct_list: 'warning' }
  return map[type] || ''
}

const getStructListCount = (key) => {
  const val = structValue[key]
  if (!val) return 0
  try {
    const arr = typeof val === 'string' ? JSON.parse(val) : val
    return Array.isArray(arr) ? arr.length : 0
  } catch { return 0 }
}

const getStructFieldCount = (setting) => {
  if (!setting.schema) return 0
  try {
    const schema = typeof setting.schema === 'string' ? JSON.parse(setting.schema) : setting.schema
    return Object.keys(schema).length
  } catch {
    return 0
  }
}

const getStructListCardCount = (setting) => {
  if (!setting.value) return 0
  try {
    const arr = typeof setting.value === 'string' ? JSON.parse(setting.value) : setting.value
    return Array.isArray(arr) ? arr.length : 0
  } catch {
    return 0
  }
}

const initStructFromSetting = (data) => {
  structSchema.value = parseSchemaJson(data.schema)
  Object.keys(structValue).forEach(k => delete structValue[k])
  Object.keys(structFileUrls).forEach(k => delete structFileUrls[k])
  resetStructStringLists()
  try {
    const rawVal = typeof data.value === 'string' ? JSON.parse(data.value) : {}
    Object.assign(structValue, rawVal)
  } catch {
    // value parse failed, leave empty
  }
  if (data.parsedValue && typeof data.parsedValue === 'object') {
    const schema = structSchema.value || []
    for (const field of schema) {
      if (field.type === 'file' && data.parsedValue[field.key]) {
        structFileUrls[field.key] = processImageUrl(data.parsedValue[field.key])
      } else if (field.type === 'file_list' && data.parsedValue[field.key]) {
        const urls = Array.isArray(data.parsedValue[field.key]) ? data.parsedValue[field.key] : []
        urls.forEach((url, fi) => {
          structFileUrls[field.key + '_' + fi] = processImageUrl(url)
        })
      } else if (field.type === 'struct' && field.schema && data.parsedValue[field.key]) {
        const nested = data.parsedValue[field.key]
        for (const sub of field.schema) {
          if (sub.type === 'file' && nested[sub.key]) {
            structFileUrls[field.key + '.' + sub.key] = processImageUrl(nested[sub.key])
          } else if (sub.type === 'file_list' && nested[sub.key]) {
            const subUrls = Array.isArray(nested[sub.key]) ? nested[sub.key] : []
            subUrls.forEach((url, fi) => {
              structFileUrls[field.key + '.' + sub.key + '_' + fi] = processImageUrl(url)
            })
          }
        }
      }
    }
  }
  showStructRawJson.value = false
  structRawJson.value = ''
}

// ==================== 数据加载 ====================

const loadGroups = async () => {
  try {
    const res = await getSettingGroupList({ page: 1, count: 100 })
    if (res.data.code === 200) {
      const groups = res.data.data.data || []
      allGroupList.value = groups
      if (groups.length > 0 && !activeGroupId.value) {
        activeGroupId.value = String(groups[0].id)
        loadSettings()
      }
    }
  } catch (error) {
    console.error('加载配置组失败:', error)
    ElMessage.error('加载配置组失败')
  }
}

const loadSettings = async () => {
  if (!activeGroupId.value) {
    settingList.value = []
    settingTotal.value = 0
    return
  }

  settingLoading.value = true
  try {
    const params = {
      group_id: activeGroupId.value,
      page: settingPagination.page,
      count: settingPagination.pageSize
    }
    if (queryParams.key) {
      params.key = queryParams.key
    }
    const res = await getSettingList(params)
    if (res.data.code === 200) {
      settingList.value = sortBySimilarity(res.data.data.data || [])
      settingTotal.value = res.data.data.all_count || 0
      // 更新当前组的配置数量
      const groupIndex = allGroupList.value.findIndex(g => String(g.id) === activeGroupId.value)
      if (groupIndex !== -1) {
        allGroupList.value[groupIndex]._settingCount = settingTotal.value
      }
    }
  } catch (error) {
    console.error('加载配置列表失败:', error)
    ElMessage.error('加载配置列表失败')
  } finally {
    settingLoading.value = false
  }
}

const sortBySimilarity = (settings) => {
  if (!settings || settings.length <= 1) return settings
  return settings.sort((a, b) => {
    const nameA = (a.name || '').toLowerCase()
    const nameB = (b.name || '').toLowerCase()
    const prefixA = nameA.substring(0, 2)
    const prefixB = nameB.substring(0, 2)
    if (prefixA !== prefixB) {
      return prefixA.localeCompare(prefixB, 'zh-CN')
    }
    return nameA.localeCompare(nameB, 'zh-CN')
  })
}

// ==================== Tab / 分页 事件 ====================

const handleTabChange = (tabId) => {
  settingPagination.page = 1
  loadSettings()
}

const handlePageChange = (page) => {
  loadSettings()
}

const handlePageSizeChange = (size) => {
  settingPagination.page = 1
  loadSettings()
}

const handleQuery = () => {
  settingPagination.page = 1
  loadSettings()
}

// ==================== 配置组方法 ====================

const handleAddGroup = () => {
  groupDialogTitle.value = '新增配置组'
  Object.assign(groupForm, {
    id: undefined,
    name: '',
    note: ''
  })
  groupDialogVisible.value = true
}

const handleEditGroup = async (group) => {
  if (!group) return
  groupDialogTitle.value = '编辑配置组'
  try {
    const res = await getSettingGroupInfo({ setting_group_id: group.id })
    if (res.data.code === 200) {
      Object.assign(groupForm, {
        id: res.data.data.id,
        name: res.data.data.name || '',
        note: res.data.data.note || ''
      })
      groupDialogVisible.value = true
    }
  } catch (error) {
    console.error('获取配置组详情失败:', error)
    ElMessage.error('获取配置组详情失败')
  }
}

const handleDeleteGroup = (group) => {
  if (!group) return
  ElMessageBox.confirm(`确认删除配置组 "${group.name}" 吗？删除后其下所有配置项也将被删除。`, '警告', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning'
  }).then(async () => {
    try {
      const res = await deleteSettingGroup({ setting_group_id: group.id })
      if (res.data.code === 200) {
        ElMessage.success('删除成功')
        activeGroupId.value = ''
        await loadGroups()
      }
    } catch (error) {
      console.error('删除失败:', error)
      ElMessage.error(error.response?.data?.message || '删除失败')
    }
  }).catch(() => {})
}

const submitGroupForm = () => {
  groupFormRef.value?.validate(async (valid) => {
    if (valid) {
      try {
        const submitData = {
          name: groupForm.name,
          note: groupForm.note
        }
        if (groupForm.id) {
          submitData.id = groupForm.id
        }
        const res = groupForm.id 
          ? await updateSettingGroup(submitData)
          : await createSettingGroup(submitData)
        if (res.data.code === 200) {
          ElMessage.success(groupForm.id ? '修改成功' : '创建成功')
          groupDialogVisible.value = false
          await loadGroups()
          if (!groupForm.id && res.data.data?.id) {
            activeGroupId.value = String(res.data.data.id)
            loadSettings()
          }
        }
      } catch (error) {
        console.error('提交失败:', error)
        ElMessage.error(error.response?.data?.message || '提交失败')
      }
    }
  })
}

// ==================== 配置方法 ====================

const handleAddSetting = () => {
  settingDialogTitle.value = '新增配置'
  Object.assign(settingForm, {
    id: undefined,
    name: '',
    value: '',
    type: 'string',
    settingGroupID: activeGroupId.value ? Number(activeGroupId.value) : undefined,
    open: true,
    note: ''
  })
  fileInfo.value = null
  fileListInfo.value = []
  newStringItem.value = ''
  editableFormStringList.value = []
  editableFormFileList.value = []
  structSchema.value = []
  Object.keys(structValue).forEach(k => delete structValue[k])
  Object.keys(structFileUrls).forEach(k => delete structFileUrls[k])
  resetStructStringLists()
  showStructRawJson.value = false
  structListItems.value = []
  structListRawJson.value = '[]'
  structListRawMode.value = false
  settingDialogVisible.value = true
}

const handleAddSettingToGroup = (group) => {
  if (!group) return
  settingDialogTitle.value = '新增配置'
  Object.assign(settingForm, {
    id: undefined,
    name: '',
    value: '',
    type: 'string',
    settingGroupID: group.id,
    open: true,
    note: ''
  })
  fileInfo.value = null
  fileListInfo.value = []
  newStringItem.value = ''
  editableFormStringList.value = []
  editableFormFileList.value = []
  structSchema.value = []
  Object.keys(structValue).forEach(k => delete structValue[k])
  Object.keys(structFileUrls).forEach(k => delete structFileUrls[k])
  resetStructStringLists()
  showStructRawJson.value = false
  structListItems.value = []
  structListRawJson.value = '[]'
  structListRawMode.value = false
  settingDialogVisible.value = true
}

const handleEditSetting = async (row) => {
  settingDialogTitle.value = '编辑配置'
  try {
    const res = await getSettingInfo({ id: row.id })
    if (res.data.code === 200) {
      const data = res.data.data
      Object.assign(settingForm, {
        id: data.id,
        name: data.name || '',
        value: data.value,
        type: data.type || 'string',
        settingGroupID: data.settingGroupID,
        open: data.open || false,
        note: data.note || ''
      })
      if (data.type === 'bool') {
        settingForm.value = data.value === true || data.value === 'true' || data.value === 1
      } else if (data.type === 'int') {
        settingForm.value = parseInt(data.value) || 0
      } else if (data.type === 'float') {
        settingForm.value = parseFloat(data.value) || 0
      } else if (data.type === 'struct') {
        initStructFromSetting(data)
      } else if (data.type === 'struct_list') {
        structSchema.value = parseSchemaJson(data.schema)
        initStructListFromValue(data.value, data.parsedValue)
      } else if (data.type === 'file') {
        if (data.parsedValue && typeof data.parsedValue === 'string') {
          fileInfo.value = {
            id: data.value,
            url: processImageUrl(data.parsedValue),
            realName: '文件',
            saveName: 'file',
            size: 0
          }
        } else {
          fileInfo.value = null
        }
      } else if (data.type === 'string_list') {
        if (data.parsedValue && Array.isArray(data.parsedValue)) {
          const truncatedValues = data.parsedValue.map(item => truncateFileName(item, 25))
          settingForm.value = truncatedValues.join(',')
          initEditableFormStringList()
        } else {
          settingForm.value = ''
          editableFormStringList.value = []
        }
        newStringItem.value = ''
      } else if (data.type === 'file_list') {
        if (data.parsedValue && Array.isArray(data.parsedValue)) {
          fileListInfo.value = data.parsedValue.map((url, index) => {
            const fileIds = getFileList(data.value)
            return {
              id: fileIds[index] || `file_${index}`,
              url: processImageUrl(url),
              realName: `文件${index + 1}`,
              saveName: `file_${index}`,
              size: 0
            }
          })
          nextTick(() => {
            initEditableFormFileList()
          })
        } else {
          fileListInfo.value = []
          editableFormFileList.value = []
        }
      }
      settingDialogVisible.value = true
    }
  } catch (error) {
    console.error('获取配置详情失败:', error)
    ElMessage.error('获取配置详情失败')
  }
}

const handleDeleteSetting = (setting) => {
  ElMessageBox.confirm(`确认删除配置 "${setting.name}" 吗？`, '警告', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning'
  }).then(async () => {
    try {
      const res = await deleteSetting({ id: setting.id })
      if (res.data.code === 200) {
        ElMessage.success('删除成功')
        loadSettings()
      }
    } catch (error) {
      console.error('删除失败:', error)
      ElMessage.error(error.response?.data?.message || '删除失败')
    }
  }).catch(() => {})
}

const copySettingName = async (name) => {
  try {
    await navigator.clipboard.writeText(name)
    ElMessage.success('已复制配置名称')
  } catch (e) {
    ElMessage.error('复制失败')
  }
}

const getTypeColor = (type) => {
  const colorMap = {
    'string': 'primary',
    'text': 'primary',
    'int': 'success',
    'float': 'warning',
    'bool': 'info',
    'file': 'danger',
    'file_list': 'danger',
    'string_list': 'primary',
    'struct': '',
    'struct_list': 'warning'
  }
  return colorMap[type] || ''
}

const isJsonValue = computed(() => {
  if ((settingForm.type !== 'string' && settingForm.type !== 'text') || !settingForm.value) return false
  const v = String(settingForm.value).trim()
  return (v.startsWith('{') && v.endsWith('}')) || (v.startsWith('[') && v.endsWith(']'))
})

const formatJson = () => {
  try {
    const parsed = JSON.parse(settingForm.value)
    settingForm.value = JSON.stringify(parsed, null, 2)
  } catch (e) {
    ElMessage.warning('JSON 格式不合法，无法格式化')
  }
}

const compressJson = () => {
  try {
    const parsed = JSON.parse(settingForm.value)
    settingForm.value = JSON.stringify(parsed)
  } catch (e) {
    ElMessage.warning('JSON 格式不合法，无法压缩')
  }
}

const handleTypeChange = (newType) => {
  if (newType === 'bool') {
    settingForm.value = false
  } else if (newType === 'int') {
    settingForm.value = 0
  } else if (newType === 'float') {
    settingForm.value = 0.0
  } else if (newType === 'struct') {
    settingForm.value = '{}'
    structSchema.value = []
    Object.keys(structValue).forEach(k => delete structValue[k])
    showStructRawJson.value = false
  } else if (newType === 'struct_list') {
    settingForm.value = '[]'
    structSchema.value = []
    structListItems.value = []
    structListRawJson.value = '[]'
    structListRawMode.value = false
  } else {
    settingForm.value = ''
  }
  fileInfo.value = null
  fileListInfo.value = []
  editableFormStringList.value = []
  editableFormFileList.value = []
  newStringItem.value = ''
}

const submitSettingForm = async () => {
  if (!settingFormRef.value) return
  
  try {
    await settingFormRef.value.validate()
    
    let submitValue = settingForm.value
    if (settingForm.type === 'struct') {
      submitValue = JSON.stringify(structValue)
    } else if (settingForm.type === 'struct_list') {
      submitValue = structListRawMode.value ? structListRawJson.value : buildStructListValue()
    }
    
    const submitData = {
      id: settingForm.id,
      name: settingForm.name,
      value: submitValue,
      type: settingForm.type,
      setting_group_id: settingForm.settingGroupID,
      open: settingForm.open,
      note: settingForm.note
    }
    
    if (settingForm.type === 'struct' || settingForm.type === 'struct_list') {
      submitData.schema = buildSchemaJson()
    }
    
    const originalOpen = settingList.value.find(s => s.id === settingForm.id)?.open
    const newOpen = settingForm.open
    
    const res = settingForm.id 
      ? await updateSetting(submitData)
      : await createSetting(submitData)
      
    if (res.data.code === 200) {
      if (settingForm.id && originalOpen !== newOpen) {
        try {
          await setSettingOpen({ id: settingForm.id, open: newOpen })
        } catch (openError) {
          console.error('更新开放状态失败:', openError)
          ElMessage.warning('配置已更新，但开放状态更新失败')
        }
      }
      
      ElMessage.success(settingForm.id ? '修改成功' : '创建成功')
      settingDialogVisible.value = false
      loadSettings()
    } else {
      ElMessage.error(res.data.message || '操作失败')
    }
  } catch (error) {
    console.error('提交配置失败:', error)
    ElMessage.error('操作失败')
  }
}

// ==================== 文件相关 ====================

const clearFile = () => {
  if (fileInfo.value?.localUrl) {
    URL.revokeObjectURL(fileInfo.value.localUrl)
  }
  if (fileInfo.value?.isLocal && fileInfo.value?.url) {
    URL.revokeObjectURL(fileInfo.value.url)
  }
  settingForm.value = ''
  fileInfo.value = null
}

const formatFileSize = (bytes) => {
  if (!bytes) return '0 B'
  const k = 1024
  const sizes = ['B', 'KB', 'MB', 'GB']
  const i = Math.floor(Math.log(bytes) / Math.log(k))
  return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
}

const truncateFileName = (fileName, maxLength = 25) => {
  if (!fileName) return ''
  if (fileName.length <= maxLength) return fileName
  const extension = fileName.includes('.') ? '.' + fileName.split('.').pop() : ''
  const nameWithoutExt = fileName.substring(0, fileName.lastIndexOf('.'))
  if (nameWithoutExt.length <= maxLength) {
    return fileName
  }
  const truncatedName = nameWithoutExt.substring(0, maxLength - 3)
  return truncatedName + '...' + extension
}

const getStringList = (value) => {
  if (!value || typeof value !== 'string') return []
  return value.split(',').filter(str => str && str.trim())
}

const truncateText = (text, maxLength = 30) => {
  if (!text) return ''
  if (typeof text !== 'string') text = String(text)
  if (text.length <= maxLength) return text
  return text.substring(0, maxLength) + '...'
}

const formatDate = (dateString) => {
  if (!dateString) return '-'
  const date = new Date(dateString)
  const year = date.getFullYear()
  const month = String(date.getMonth() + 1).padStart(2, '0')
  const day = String(date.getDate()).padStart(2, '0')
  const hours = String(date.getHours()).padStart(2, '0')
  const minutes = String(date.getMinutes()).padStart(2, '0')
  const seconds = String(date.getSeconds()).padStart(2, '0')
  return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
}

// ==================== 表单可编辑字符串列表 ====================

const getEditableFormStringList = () => {
  return editableFormStringList.value
}

const initEditableFormStringList = () => {
  const stringItems = getStringList(settingForm.value)
  editableFormStringList.value = stringItems.map(item => ({
    value: item,
    editing: false
  }))
}

const handleFormDragStart = (event, index) => {
  formDraggedIndex.value = index
  event.dataTransfer.effectAllowed = 'move'
  event.dataTransfer.setData('text/html', event.target.outerHTML)
  event.target.style.opacity = '0.5'
}

const handleFormDrop = (event, dropIndex) => {
  event.preventDefault()
  const dragIndex = formDraggedIndex.value
  if (dragIndex === dropIndex) return
  const newList = [...editableFormStringList.value]
  const [draggedItem] = newList.splice(dragIndex, 1)
  newList.splice(dropIndex, 0, draggedItem)
  editableFormStringList.value = newList
  formDraggedIndex.value = -1
  const updatedValues = newList.map(item => item.value)
  settingForm.value = updatedValues.join(',')
  event.target.style.opacity = '1'
}

const addFormEditableStringItem = () => {
  const newItem = { value: '', editing: true }
  editableFormStringList.value.push(newItem)
  const updatedValues = editableFormStringList.value.map(item => item.value)
  settingForm.value = updatedValues.join(',')
  nextTick(() => {
    const inputs = document.querySelectorAll('.string-list-item input')
    if (inputs.length > 0) {
      inputs[inputs.length - 1].focus()
    }
  })
}

const startFormEditItem = (index) => {
  editableFormStringList.value[index].editing = true
  nextTick(() => {
    const inputs = document.querySelectorAll('.string-list-item input')
    if (inputs[index]) {
      inputs[index].focus()
      inputs[index].select()
    }
  })
}

const finishFormEditItem = (index) => {
  editableFormStringList.value[index].editing = false
  const updatedValues = editableFormStringList.value.map(item => item.value)
  settingForm.value = updatedValues.join(',')
}

const removeFormEditableStringItem = (index) => {
  ElMessageBox.confirm('确定要删除这个项目吗？', '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning'
  }).then(() => {
    editableFormStringList.value.splice(index, 1)
    const updatedValues = editableFormStringList.value.map(item => item.value)
    settingForm.value = updatedValues.join(',')
    ElMessage.success('删除成功')
  })
}

const focusFormEditInput = () => {}

// ==================== 表单可编辑文件列表 ====================

const getEditableFormFileList = () => {
  return editableFormFileList.value
}

const initEditableFormFileList = () => {
  if (settingForm.type === 'file_list' && fileListInfo.value && fileListInfo.value.length > 0) {
    editableFormFileList.value = fileListInfo.value.map(file => ({
      id: file.id || '',
      url: file.url || '',
      localUrl: file.localUrl || '',
      realName: file.realName || '文件',
      saveName: file.saveName || 'file',
      size: file.size || 0,
      uploading: false
    }))
  } else {
    editableFormFileList.value = []
  }
}

const handleFormFileDragStart = (event, index) => {
  formFileDraggedIndex.value = index
  event.dataTransfer.effectAllowed = 'move'
  event.dataTransfer.setData('text/html', event.target.outerHTML)
  event.target.style.opacity = '0.5'
}

const handleFormFileDrop = (event, dropIndex) => {
  event.preventDefault()
  const dragIndex = formFileDraggedIndex.value
  if (dragIndex === dropIndex) return
  const newList = [...editableFormFileList.value]
  const [draggedItem] = newList.splice(dragIndex, 1)
  newList.splice(dropIndex, 0, draggedItem)
  editableFormFileList.value = newList
  formFileDraggedIndex.value = -1
  fileListInfo.value = newList.map(item => ({ ...item }))
  updateFormFileListValue()
  event.target.style.opacity = '1'
}

const removeFormEditableFileItem = (index) => {
  ElMessageBox.confirm('确定要删除这个文件吗？', '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning'
  }).then(() => {
    if (editableFormFileList.value[index].localUrl) {
      URL.revokeObjectURL(editableFormFileList.value[index].localUrl)
    }
    if (editableFormFileList.value[index].isLocal && editableFormFileList.value[index].url) {
      URL.revokeObjectURL(editableFormFileList.value[index].url)
    }
    editableFormFileList.value.splice(index, 1)
    fileListInfo.value = editableFormFileList.value
    updateFormFileListValue()
    ElMessage.success('删除成功')
  })
}

const updateFormFileListValue = () => {
  if (!editableFormFileList.value || !Array.isArray(editableFormFileList.value)) return
  const fileIds = editableFormFileList.value.map(file => file.id).filter(id => id)
  settingForm.value = fileIds.join(',')
}

// ==================== 文件预览 ====================

const previewFile = async (fileId) => {
  if (!fileId) return
  try {
    const res = await getFileDetail({ file_id: fileId })
    if (res.data.code === 200 && res.data.data?.url) {
      const url = processImageUrl(res.data.data.url)
      const fileName = res.data.data.data?.realName || ''
      const imageExts = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp', '.svg']
      const ext = fileName.toLowerCase().substring(fileName.lastIndexOf('.'))
      if (imageExts.includes(ext) || url.match(/\.(jpg|jpeg|png|gif|bmp|webp|svg)/i)) {
        currentViewImage.value = url
        imageViewerVisible.value = true
      } else {
        window.open(url, '_blank')
      }
    } else {
      const downRes = await downloadFile({ file_id: fileId })
      if (downRes.data.code === 200 && downRes.data.data?.url) {
        window.open(processImageUrl(downRes.data.data.url), '_blank')
      } else {
        ElMessage.error('获取文件预览失败')
      }
    }
  } catch (error) {
    console.error('文件预览失败:', error)
    ElMessage.error('文件预览失败')
  }
}

const processImageUrl = (url) => {
  if (!url) return ''
  let processedUrl = url.replace(/\\u0026/g, '&')
  return decodeURIComponent(processedUrl)
}

const previewImage = (url) => {
  currentViewImage.value = processImageUrl(url)
  imageViewerVisible.value = true
}

const handleImageError = (event) => {
  console.error('图片加载失败:', event.target.src)
}

// ==================== 图像选择器 ====================

const openImageSelector = () => {
  imageSelectorMode.value = 'single'
  currentImageSelectorFileId.value = settingForm.value || ''
  imageSelectorVisible.value = true
}

const openImageSelectorForList = () => {
  imageSelectorMode.value = 'list'
  currentImageSelectorFileId.value = ''
  imageSelectorVisible.value = true
}

const openImageSelectorForListItem = (index) => {
  imageSelectorMode.value = 'list-item'
  currentImageSelectorFileId.value = index
  imageSelectorVisible.value = true
}

const handleImageSelectorConfirm = (selectedFileOrFiles) => {
  if (imageSelectorMode.value === 'struct-file') {
    const selectedFile = selectedFileOrFiles
    if (!selectedFile || !selectedFile.id) { ElMessage.warning('选择的文件无效'); return }
    setStructFileValue(String(selectedFile.id), selectedFile.url ? processImageUrl(selectedFile.url) : '')
    imageSelectorVisible.value = false
    return
  }
  
  if (imageSelectorMode.value === 'struct-file-list') {
    const files = Array.isArray(selectedFileOrFiles) ? selectedFileOrFiles : [selectedFileOrFiles]
    if (files.length === 0 || !files[0]?.id) { ElMessage.warning('未选择任何文件'); return }
    const currentVal = getStructFileCurrentValue(structFileTarget.source, structFileTarget.fieldKey, structFileTarget.subKey, structFileTarget.itemIndex)
    const existingIds = currentVal ? currentVal.split(',').filter(Boolean) : []
    const newIds = files.map(f => String(f.id))
    const newUrls = files.map(f => f.url ? processImageUrl(f.url) : '')
    const merged = [...existingIds, ...newIds].join(',')
    setStructFileValue(merged, null, newUrls, existingIds.length)
    ElMessage.success(`已添加 ${newIds.length} 个文件`)
    imageSelectorVisible.value = false
    return
  }

  if (imageSelectorMode.value === 'list' && Array.isArray(selectedFileOrFiles)) {
    if (selectedFileOrFiles.length === 0) {
      ElMessage.warning('未选择任何文件')
      return
    }
    if (!fileListInfo.value) {
      fileListInfo.value = []
    }
    for (const selectedFile of selectedFileOrFiles) {
      if (!selectedFile || !selectedFile.id) continue
      const newFile = {
        id: selectedFile.id,
        url: processImageUrl(selectedFile.url || ''),
        realName: selectedFile.realName || '文件',
        saveName: selectedFile.realName || 'file',
        size: selectedFile.size || 0
      }
      fileListInfo.value.push(newFile)
      editableFormFileList.value.push({
        id: selectedFile.id,
        url: processImageUrl(selectedFile.url || ''),
        localUrl: '',
        realName: selectedFile.realName || '文件',
        saveName: selectedFile.realName || 'file',
        size: selectedFile.size || 0,
        uploading: false
      })
    }
    updateFormFileListValue()
    ElMessage.success(`已添加 ${selectedFileOrFiles.length} 个文件`)
    imageSelectorVisible.value = false
    return
  }
  
  const selectedFile = selectedFileOrFiles
  if (!selectedFile || !selectedFile.id) {
    ElMessage.warning('选择的文件无效')
    return
  }
  
  if (imageSelectorMode.value === 'single') {
    settingForm.value = selectedFile.id
    fileInfo.value = {
      id: selectedFile.id,
      url: processImageUrl(selectedFile.url || ''),
      realName: selectedFile.realName || '文件',
      saveName: selectedFile.realName || 'file',
      size: selectedFile.size || 0
    }
  } else if (imageSelectorMode.value === 'list') {
    if (!fileListInfo.value) {
      fileListInfo.value = []
    }
    const newFile = {
      id: selectedFile.id,
      url: processImageUrl(selectedFile.url || ''),
      realName: selectedFile.realName || '文件',
      saveName: selectedFile.realName || 'file',
      size: selectedFile.size || 0
    }
    fileListInfo.value.push(newFile)
    editableFormFileList.value.push({
      id: selectedFile.id,
      url: processImageUrl(selectedFile.url || ''),
      localUrl: '',
      realName: selectedFile.realName || '文件',
      saveName: selectedFile.realName || 'file',
      size: selectedFile.size || 0,
      uploading: false
    })
    updateFormFileListValue()
  } else if (imageSelectorMode.value === 'list-item') {
    const index = currentImageSelectorFileId.value
    const updatedFile = {
      id: selectedFile.id,
      url: processImageUrl(selectedFile.url || ''),
      realName: selectedFile.realName || '文件',
      saveName: selectedFile.realName || 'file',
      size: selectedFile.size || 0
    }
    if (fileListInfo.value && fileListInfo.value[index] !== undefined) {
      fileListInfo.value[index] = updatedFile
    }
    if (editableFormFileList.value && editableFormFileList.value[index] !== undefined) {
      editableFormFileList.value[index] = {
        ...updatedFile,
        localUrl: '',
        uploading: false
      }
    }
    updateFormFileListValue()
    ElMessage.success('文件替换成功')
  }
  
  imageSelectorVisible.value = false
}

// ==================== Struct 文件选择器 ====================
const structFileUrls = reactive({})

const structFileTarget = reactive({
  source: '',
  fieldKey: '',
  subKey: '',
  itemIndex: -1
})

const openStructFileSelector = (source, fieldKey, subKey = null, itemIndex = -1) => {
  structFileTarget.source = source
  structFileTarget.fieldKey = fieldKey
  structFileTarget.subKey = subKey || ''
  structFileTarget.itemIndex = itemIndex
  imageSelectorMode.value = 'struct-file'
  currentImageSelectorFileId.value = getStructFileCurrentValue(source, fieldKey, subKey, itemIndex) || ''
  imageSelectorVisible.value = true
}

const openStructFileListSelector = (source, fieldKey, subKey = null, itemIndex = -1) => {
  structFileTarget.source = source
  structFileTarget.fieldKey = fieldKey
  structFileTarget.subKey = subKey || ''
  structFileTarget.itemIndex = itemIndex
  imageSelectorMode.value = 'struct-file-list'
  currentImageSelectorFileId.value = ''
  imageSelectorVisible.value = true
}

const getStructFileCurrentValue = (source, fieldKey, subKey, itemIndex) => {
  if (source === 'struct') {
    return structValue[fieldKey] || ''
  } else if (source === 'struct-nested') {
    return getNestedValue(fieldKey)[subKey] || ''
  } else if (source === 'struct-list') {
    return structListItems.value[itemIndex]?.[fieldKey] || ''
  }
  return ''
}

const setStructFileValue = (value, url = null, urls = null, startIndex = 0) => {
  const { source, fieldKey, subKey, itemIndex } = structFileTarget
  if (source === 'struct') {
    structValue[fieldKey] = value
    if (url) structFileUrls[fieldKey] = url
    if (urls) urls.forEach((u, i) => { if (u) structFileUrls[fieldKey + '_' + (startIndex + i)] = u })
  } else if (source === 'struct-nested') {
    getNestedValue(fieldKey)[subKey] = value
    if (url) structFileUrls[fieldKey + '.' + subKey] = url
    if (urls) urls.forEach((u, i) => { if (u) structFileUrls[fieldKey + '.' + subKey + '_' + (startIndex + i)] = u })
  } else if (source === 'struct-list') {
    if (structListItems.value[itemIndex]) {
      structListItems.value[itemIndex][fieldKey] = value
      if (url) structFileUrls['list_' + itemIndex + '_' + fieldKey] = url
      if (urls) urls.forEach((u, i) => { if (u) structFileUrls['list_' + itemIndex + '_' + fieldKey + '_' + (startIndex + i)] = u })
    }
  }
}

// ==================== 批量导入 ====================

const handleBatchImport = async () => {
  batchImportText.value = ''
  batchImportParsed.value = []
  batchImportGroupId.value = undefined
  batchImportGroupName.value = ''
  batchImportGroupExists.value = false
  batchImportOpen.value = true
  batchImportProgress.value = 0
  batchImportTotal.value = 0
  batchImportStatusText.value = ''
  batchImportDialogVisible.value = true

  try {
    const clipText = await navigator.clipboard.readText()
    if (clipText && clipText.trim()) {
      batchImportText.value = clipText.trim()
    }
  } catch (e) {
    console.warn('读取剪贴板失败:', e)
  }
}

const handleImportToGroup = (group) => {
  if (!group) return
  batchImportText.value = ''
  batchImportParsed.value = []
  batchImportGroupId.value = group.id
  batchImportGroupName.value = group.name
  batchImportGroupExists.value = true
  batchImportOpen.value = true
  batchImportProgress.value = 0
  batchImportTotal.value = 0
  batchImportStatusText.value = ''
  batchImportDialogVisible.value = true
}

const parseBatchImportText = () => {
  const text = batchImportText.value.trim()
  if (!text) {
    ElMessage.warning('请先粘贴导入内容')
    return
  }

  const lines = text.split('\n').map(l => l.trim()).filter(l => l.length > 0)
  const validTypes = ['string', 'text', 'int', 'float', 'bool', 'file', 'file_list', 'string_list', 'struct', 'struct_list']
  const results = []
  const stripBackticks = (str) => str.replace(/`/g, '').trim()

  let detectedGroupName = ''
  for (const line of lines) {
    const groupMatch = line.match(/^\[配置组\]\s*(.+)$/)
    if (groupMatch) {
      detectedGroupName = groupMatch[1].trim()
      continue
    }
    if (/^[\s|:\-]+$/.test(line)) continue
    if (/配置名|类型.*默认值|名称.*类型/.test(line)) continue
    const parts = line.split('|').map(s => s.trim()).filter(s => s.length > 0)
    if (parts.length < 3) continue
    const name = stripBackticks(parts[0])
    const type = stripBackticks(parts[1]).toLowerCase()
    const value = stripBackticks(parts[2])
    const note = parts.length >= 4 ? stripBackticks(parts[3]) : ''
    if (!name) continue
    if (!validTypes.includes(type)) continue
    results.push({ name, type, value, note, _duplicate: false })
  }

  if (detectedGroupName) {
    batchImportGroupName.value = detectedGroupName
    const existingGroup = allGroupList.value.find(g => g.name === detectedGroupName)
    if (existingGroup) {
      batchImportGroupId.value = existingGroup.id
      batchImportGroupExists.value = true
    } else {
      batchImportGroupId.value = undefined
      batchImportGroupExists.value = false
    }
  }

  const nameSet = new Set()
  results.forEach(item => {
    if (nameSet.has(item.name)) {
      item._duplicate = true
    } else {
      nameSet.add(item.name)
    }
  })

  batchImportParsed.value = results
  if (results.length === 0) {
    ElMessage.warning('未能解析到有效的配置项，请检查格式')
  } else {
    const groupInfo = detectedGroupName ? `，配置组：${detectedGroupName}` : ''
    ElMessage.success(`成功解析 ${results.length} 条配置项${groupInfo}`)
  }
}

watch(batchImportText, (val) => {
  if (val && val.trim().split('\n').length >= 2) {
    parseBatchImportText()
  }
})

const submitBatchImport = async () => {
  if (!batchImportGroupName.value) {
    ElMessage.warning('未识别到配置组名称，请检查内容格式')
    return
  }
  const items = batchImportParsed.value.filter(i => !i._duplicate)
  if (items.length === 0) {
    ElMessage.warning('没有可导入的配置项')
    return
  }

  batchImportLoading.value = true
  batchImportProgress.value = 0
  batchImportTotal.value = items.length
  let successCount = 0
  let failCount = 0
  const errors = []

  let targetGroupId = batchImportGroupId.value
  if (!targetGroupId) {
    batchImportStatusText.value = `正在创建配置组「${batchImportGroupName.value}」...`
    try {
      const res = await createSettingGroup({ name: batchImportGroupName.value, note: '' })
      if (res.data.code === 200) {
        targetGroupId = res.data.data?.id || res.data.data?.ID
        batchImportGroupId.value = targetGroupId
        batchImportGroupExists.value = true
      } else {
        batchImportLoading.value = false
        batchImportStatusText.value = ''
        ElMessage.error(`创建配置组失败：${res.data.message || '未知错误'}`)
        return
      }
    } catch (error) {
      batchImportLoading.value = false
      batchImportStatusText.value = ''
      ElMessage.error(`创建配置组失败：${error.response?.data?.message || error.message}`)
      return
    }
  }

  for (let i = 0; i < items.length; i++) {
    const item = items[i]
    batchImportStatusText.value = `正在导入：${item.name}`
    batchImportProgress.value = i

    try {
      const res = await createSetting({
        name: item.name,
        value: item.value,
        type: item.type,
        setting_group_id: targetGroupId,
        open: batchImportOpen.value,
        note: item.note
      })
      if (res.data.code === 200) {
        if (batchImportOpen.value && res.data.data?.id) {
          try {
            await setSettingOpen({ id: res.data.data.id, open: true })
          } catch (e) {
            console.warn('设置公开状态失败:', item.name, e)
          }
        }
        successCount++
      } else {
        failCount++
        errors.push(`${item.name}: ${res.data.message || '未知错误'}`)
      }
    } catch (error) {
      failCount++
      errors.push(`${item.name}: ${error.response?.data?.message || error.message || '请求失败'}`)
    }
  }

  batchImportProgress.value = items.length
  batchImportStatusText.value = '导入完成'
  batchImportLoading.value = false

  if (failCount === 0) {
    ElMessage.success(`全部导入成功！共 ${successCount} 条，配置组：${batchImportGroupName.value}`)
    batchImportDialogVisible.value = false
  } else {
    ElMessage.warning(`导入完成：成功 ${successCount} 条，失败 ${failCount} 条`)
    if (errors.length > 0) {
      console.error('批量导入失败详情:', errors)
    }
  }

  await loadGroups()
  if (targetGroupId) {
    activeGroupId.value = String(targetGroupId)
    await loadSettings()
  }
}

// 一键复制
const handleCopyGroupSettings = async (group) => {
  if (!group) return
  const groupId = group.id
  const groupName = group.name
  try {
    const res = await getSettingList({ group_id: groupId, page: 1, count: 100 })
    let settings = []
    if (res.data.code === 200) {
      settings = res.data.data.data || []
    }

    if (settings.length === 0) {
      ElMessage.warning(`配置组「${groupName}」下没有配置项`)
      return
    }

    const lines = [
      `[配置组] ${groupName}`,
      '',
      '| 配置名 | 类型 | 默认值 | 说明 |',
      '|--------|------|--------|------|'
    ]
    settings.forEach(s => {
      const name = s.name || ''
      const type = s.type || 'string'
      const value = (s.value != null ? String(s.value) : '').replace(/\|/g, '\\|')
      const note = (s.note || '-').replace(/\|/g, '\\|')
      lines.push(`| \`${name}\` | \`${type}\` | \`${value}\` | ${note} |`)
    })

    const text = lines.join('\n')
    await navigator.clipboard.writeText(text)
    ElMessage.success(`已复制「${groupName}」的 ${settings.length} 条配置项到剪贴板`)
  } catch (error) {
    console.error('一键复制失败:', error)
    ElMessage.error('复制失败，请重试')
  }
}

// ==================== 初始化 ====================
onMounted(() => {
  loadGroups()
})
</script>

<style scoped>
.setting-manage-container {
  padding: 20px;
  display: flex;
  flex-direction: column;
  gap: 16px;
  min-height: 100%;
}

/* 顶部操作栏 */
.top-action-bar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 16px 20px;
  background: #ffffff;
  border-radius: 12px;
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.04);
  border: 1px solid #f0f2f5;
}

.action-left {
  display: flex;
  align-items: center;
  gap: 12px;
}

.search-input {
  width: 280px;
}

.search-input :deep(.el-input__wrapper) {
  border-radius: 8px;
  box-shadow: 0 0 0 1px #e4e7ed inset;
  transition: all 0.3s;
}

.search-input :deep(.el-input__wrapper:hover) {
  box-shadow: 0 0 0 1px #c0c4cc inset;
}

.search-input :deep(.el-input__wrapper.is-focus) {
  box-shadow: 0 0 0 1px #409eff inset;
}

.action-right {
  display: flex;
  align-items: center;
  gap: 10px;
}

/* Tab 栏 */
.tabs-wrapper {
  background: #ffffff;
  border-radius: 12px;
  padding: 16px 20px 0;
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.04);
  border: 1px solid #f0f2f5;
  position: relative;
}

.group-tabs :deep(.el-tabs__header) {
  margin: 0;
  border-bottom: none;
}

.group-tabs :deep(.el-tabs__nav-wrap) {
  margin-bottom: 0;
}

.group-tabs :deep(.el-tabs__nav-wrap::after) {
  display: none;
}

.group-tabs :deep(.el-tabs__item) {
  border: 1px solid transparent;
  border-radius: 8px 8px 0 0;
  padding: 0 20px;
  height: 42px;
  line-height: 42px;
  margin-right: 4px;
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  background: #f5f7fa;
}

.group-tabs :deep(.el-tabs__item:hover) {
  background: #ecf5ff;
  border-color: #d9ecff;
  color: #409eff;
}

.group-tabs :deep(.el-tabs__item.is-active) {
  background: linear-gradient(135deg, #409eff 0%, #66b1ff 100%);
  color: #ffffff;
  border-color: transparent;
  font-weight: 600;
  box-shadow: 0 4px 12px rgba(64, 158, 255, 0.3);
}

.group-tabs :deep(.el-tabs__item.is-active .tab-icon) {
  color: #ffffff;
}

.group-tabs :deep(.el-tabs__item.is-active .tab-badge .el-badge__content) {
  background-color: #ffffff;
  color: #409eff;
}

.tab-label {
  display: flex;
  align-items: center;
  gap: 6px;
}

.tab-icon {
  font-size: 15px;
  color: #909399;
  transition: color 0.3s;
}

.tab-name {
  max-width: 120px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.tab-badge {
  margin-left: 4px;
}

.tab-badge :deep(.el-badge__content) {
  font-size: 10px;
  height: 16px;
  line-height: 16px;
  padding: 0 5px;
}

.group-info-bar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 14px 0 16px;
  border-top: 1px solid #f0f2f5;
  margin-top: 12px;
  gap: 20px;
  flex-wrap: wrap;
}

.group-meta {
  display: flex;
  align-items: center;
  gap: 20px;
  flex-wrap: wrap;
}

.group-meta-item {
  display: flex;
  align-items: center;
  gap: 4px;
  font-size: 13px;
  color: #606266;
}

.meta-icon {
  font-size: 14px;
  color: #909399;
}

.meta-label {
  color: #909399;
  white-space: nowrap;
}

.meta-value {
  color: #303133;
  max-width: 200px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.meta-count {
  font-weight: 600;
  color: #409eff;
}

.group-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-shrink: 0;
}

.action-btn {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 6px 14px;
  border-radius: 20px;
  font-size: 12px;
  font-weight: 500;
  border: none;
  cursor: pointer;
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  height: 32px;
}

.action-btn span {
  line-height: 1;
}

.action-btn--edit {
  background: #ecf5ff;
  color: #409eff;
}

.action-btn--edit:hover {
  background: #409eff;
  color: #ffffff;
  box-shadow: 0 4px 12px rgba(64, 158, 255, 0.35);
  transform: translateY(-1px);
}

.action-btn--add {
  background: #f0f9eb;
  color: #67c23a;
}

.action-btn--add:hover {
  background: #67c23a;
  color: #ffffff;
  box-shadow: 0 4px 12px rgba(103, 194, 58, 0.35);
  transform: translateY(-1px);
}

.action-btn--copy {
  background: #fdf6ec;
  color: #e6a23c;
}

.action-btn--copy:hover {
  background: #e6a23c;
  color: #ffffff;
  box-shadow: 0 4px 12px rgba(230, 162, 60, 0.35);
  transform: translateY(-1px);
}

.action-btn--import {
  background: #f4f4f5;
  color: #909399;
}

.action-btn--import:hover {
  background: #909399;
  color: #ffffff;
  box-shadow: 0 4px 12px rgba(144, 147, 153, 0.35);
  transform: translateY(-1px);
}

.action-btn--delete {
  background: #fef0f0;
  color: #f56c6c;
}

.action-btn--delete:hover {
  background: #f56c6c;
  color: #ffffff;
  box-shadow: 0 4px 12px rgba(245, 108, 108, 0.35);
  transform: translateY(-1px);
}

/* 卡片区域 */
.cards-section {
  background: #ffffff;
  border-radius: 12px;
  padding: 24px;
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.04);
  border: 1px solid #f0f2f5;
  min-height: 400px;
  flex: 1;
}

.cards-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
  gap: 16px;
}

/* 卡片样式 */
.setting-card {
  background: #ffffff;
  border: 1px solid #ebeef5;
  border-radius: 12px;
  padding: 20px;
  cursor: pointer;
  transition: all 0.35s cubic-bezier(0.4, 0, 0.2, 1);
  position: relative;
  overflow: hidden;
}

.setting-card::before {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 3px;
  background: linear-gradient(90deg, #409eff, #66b1ff);
  opacity: 0;
  transition: opacity 0.3s;
}

.setting-card:hover {
  transform: translateY(-4px);
  box-shadow: 0 12px 28px rgba(64, 158, 255, 0.12);
  border-color: #d9ecff;
}

.setting-card:hover::before {
  opacity: 1;
}

.setting-card.is-private {
  border-left: 3px solid #909399;
}

.setting-card.is-private::before {
  background: linear-gradient(90deg, #909399, #b1b3b8);
}

/* 卡片头部 */
.card-header {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  margin-bottom: 14px;
}

.card-title-row {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: 1;
  min-width: 0;
}

.card-name-wrap {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  min-width: 0;
  flex: 1;
}

.card-name {
  font-size: 15px;
  font-weight: 600;
  color: #303133;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.copy-name-btn {
  font-size: 16px;
  color: #c0c4cc;
  cursor: pointer;
  flex-shrink: 0;
  padding: 4px;
  border-radius: 4px;
  transition: all 0.25s;
}

.copy-name-btn:hover {
  color: #409eff;
  background: #ecf5ff;
}

.card-type-tag {
  flex-shrink: 0;
}

/* 卡片内容 */
.card-body {
  margin-bottom: 14px;
  padding: 12px;
  background: #f8f9fc;
  border-radius: 8px;
  min-height: 44px;
  display: flex;
  align-items: center;
}

.card-value {
  font-size: 13px;
  color: #606266;
  width: 100%;
  overflow: hidden;
}

.value-bool {
  display: flex;
  align-items: center;
  gap: 6px;
  font-weight: 500;
}

.bool-true {
  color: #67c23a;
  font-size: 18px;
}

.bool-false {
  color: #f56c6c;
  font-size: 18px;
}

.value-file,
.value-file-list,
.value-string-list,
.value-struct {
  display: flex;
  align-items: center;
  gap: 6px;
  color: #409eff;
}

.value-text {
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-family: 'SF Mono', Consolas, monospace;
  font-size: 12.5px;
  color: #5a6b7b;
}

/* 卡片底部 */
.card-footer {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding-top: 12px;
  border-top: 1px solid #f2f3f5;
}

.card-note {
  font-size: 12px;
  color: #909399;
  max-width: 200px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.card-actions {
  display: flex;
  gap: 4px;
  opacity: 0;
  transition: opacity 0.3s;
}

.setting-card:hover .card-actions {
  opacity: 1;
}

/* 空状态 */
.empty-state {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 300px;
}

/* 分页 */
.pagination-wrapper {
  display: flex;
  justify-content: center;
  padding-top: 24px;
  margin-top: 16px;
  border-top: 1px solid #f2f3f5;
}

/* 卡片列表动画 */
.card-list-enter-active,
.card-list-leave-active {
  transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
}

.card-list-enter-from {
  opacity: 0;
  transform: translateY(20px) scale(0.95);
}

.card-list-leave-to {
  opacity: 0;
  transform: translateY(-10px) scale(0.95);
}

.card-list-move {
  transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1);
}

/* 弹窗样式 */
:deep(.dialog-scrollable .el-dialog) {
  max-height: 90vh;
  display: flex;
  flex-direction: column;
  border-radius: 12px;
}

:deep(.dialog-scrollable .el-dialog__body) {
  max-height: calc(90vh - 120px);
  overflow-y: auto;
  padding: 20px;
  scrollbar-width: none;
  -ms-overflow-style: none;
}

:deep(.dialog-scrollable .el-dialog__body)::-webkit-scrollbar {
  display: none;
}

:deep(.dialog-scrollable .el-dialog__header) {
  flex-shrink: 0;
}

:deep(.dialog-scrollable .el-dialog__footer) {
  flex-shrink: 0;
  padding: 20px;
  border-top: 1px solid #e4e7ed;
}

/* JSON 工具栏 */
.json-toolbar {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 8px;
  padding: 6px 10px;
  background: #f5f7fa;
  border-radius: 6px;
}

/* 文件相关样式 */
.file-upload-section {
  width: 100%;
}

.file-info-display {
  width: 100%;
}

.file-item {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px;
  border: 1px solid #e4e7ed;
  border-radius: 8px;
  background: #f8f9fc;
}

.file-item.uploading {
  border-color: #409eff;
  background: #f0f9ff;
}

.file-preview {
  width: 80px;
  height: 80px;
  border-radius: 6px;
  overflow: hidden;
  border: 1px solid #e4e7ed;
  flex-shrink: 0;
  position: relative;
}

.preview-image {
  width: 100%;
  height: 100%;
  object-fit: cover;
  cursor: pointer;
  transition: transform 0.2s;
}

.preview-image:hover {
  transform: scale(1.05);
}

.file-placeholder {
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: #f5f7fa;
  color: #909399;
  font-size: 24px;
}

.file-details {
  display: flex;
  flex-direction: column;
  gap: 4px;
  flex: 1;
}

.file-name {
  font-weight: 500;
  color: #303133;
  max-width: 200px;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.file-id {
  font-size: 12px;
  color: #909399;
}

.file-size {
  font-size: 12px;
  color: #909399;
}

.file-actions {
  display: flex;
  flex-direction: column;
  gap: 8px;
  align-items: flex-end;
  flex-shrink: 0;
}

.upload-overlay {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: rgba(0, 0, 0, 0.6);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  color: white;
  font-size: 12px;
  border-radius: 6px;
}

.upload-overlay .el-icon {
  font-size: 20px;
  margin-bottom: 4px;
}

.upload-overlay span {
  font-size: 11px;
}

.image-selector-btn {
  width: 200px;
  height: 40px;
}

/* 文件列表样式 */
.file-list-section {
  width: 100%;
}

.editable-file-list {
  border: 1px solid #e4e7ed;
  border-radius: 8px;
  padding: 16px;
  background: #fafbfc;
}

.file-list-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 12px;
  padding-bottom: 8px;
  border-bottom: 1px solid #ebeef5;
}

.header-actions {
  display: flex;
  gap: 8px;
}

.list-title {
  font-weight: 500;
  color: #303133;
  font-size: 14px;
}

.file-list-items {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.file-list-item {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px;
  background: #ffffff;
  border: 1px solid #e4e7ed;
  border-radius: 8px;
  transition: all 0.3s;
  cursor: move;
}

.file-list-item:hover {
  border-color: #c0c4cc;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
}

.file-list-item .file-preview {
  width: 50px;
  height: 50px;
  border-radius: 6px;
  overflow: hidden;
  border: 1px solid #e4e7ed;
  flex-shrink: 0;
  position: relative;
}

.file-list-item .preview-image {
  width: 100%;
  height: 100%;
  object-fit: cover;
  cursor: pointer;
}

.file-list-item .file-placeholder {
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: #f5f7fa;
  color: #909399;
}

.file-list-item .file-info {
  flex: 1;
  min-width: 0;
}

.file-list-item .file-name {
  font-weight: 500;
  color: #303133;
  margin-bottom: 2px;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.file-list-item .file-id {
  font-size: 12px;
  color: #909399;
}

.file-list-item .file-size {
  font-size: 12px;
  color: #909399;
}

.file-list-item .file-actions {
  display: flex;
  gap: 4px;
  flex-shrink: 0;
  flex-direction: row;
}

/* 字符串列表样式 */
.string-list-section {
  width: 100%;
}

.editable-string-list {
  border: 1px solid #e4e7ed;
  border-radius: 8px;
  padding: 16px;
  background: #fafbfc;
}

.string-list-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 12px;
  padding-bottom: 8px;
  border-bottom: 1px solid #ebeef5;
}

.string-list-items {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.string-list-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  background: #ffffff;
  border: 1px solid #e4e7ed;
  border-radius: 6px;
  transition: all 0.3s;
  cursor: move;
}

.string-list-item:hover {
  border-color: #c0c4cc;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.06);
}

.drag-handle {
  color: #909399;
  cursor: grab;
  flex-shrink: 0;
}

.drag-handle:hover {
  color: #606266;
}

.item-content {
  flex: 1;
  min-width: 0;
}

.item-text {
  padding: 4px 8px;
  border-radius: 4px;
  transition: background-color 0.3s;
  cursor: pointer;
  word-break: break-word;
  white-space: normal;
  line-height: 1.4;
}

.item-text:hover {
  background: #f5f7fa;
}

.item-actions {
  display: flex;
  gap: 4px;
  flex-shrink: 0;
}

.danger-btn {
  color: #f56c6c;
}

.danger-btn:hover {
  color: #f78989;
}

/* 导入进度面板 */
.import-progress-panel {
  padding: 24px;
  text-align: center;
}

.progress-header {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  margin-bottom: 20px;
}

.progress-info {
  max-width: 400px;
  margin: 0 auto;
}

/* 文本值 */
.text-value {
  display: inline-block;
  max-width: 200px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  vertical-align: middle;
}

/* Struct 编辑器样式 */
.struct-section {
  width: 100%;
}

.struct-schema-editor,
.struct-value-editor {
  border: 1px solid #e4e7ed;
  border-radius: 8px;
  padding: 16px;
  background: #fafbfc;
  margin-bottom: 16px;
}

.schema-header,
.value-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 14px;
  padding-bottom: 10px;
  border-bottom: 1px solid #ebeef5;
}

.schema-title,
.value-title {
  font-weight: 600;
  font-size: 14px;
  color: #303133;
  display: flex;
  align-items: center;
  gap: 6px;
}

.schema-fields {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.schema-field-row {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}

.schema-field-row .nested-schema {
  width: 100%;
  margin-top: 8px;
  margin-left: 32px;
  padding: 12px;
  border: 1px dashed #d9ecff;
  border-radius: 6px;
  background: #f0f9ff;
}

.nested-schema-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 10px;
}

.nested-label {
  font-size: 12px;
  color: #606266;
  font-weight: 500;
}

.schema-field-row.nested {
  margin-left: 0;
}

.schema-empty {
  padding: 24px;
  text-align: center;
  color: #909399;
  font-size: 13px;
}

.struct-value-fields {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.struct-value-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  background: #ffffff;
  border: 1px solid #e4e7ed;
  border-radius: 6px;
  flex-wrap: wrap;
}

.struct-value-row.nested {
  margin-left: 24px;
  border-style: dashed;
}

.field-label {
  font-size: 13px;
  font-weight: 500;
  color: #303133;
  min-width: 100px;
  flex-shrink: 0;
}

.field-label.sub {
  color: #606266;
  font-size: 12px;
}

.field-input {
  flex: 1;
  min-width: 200px;
}

.nested-value-fields {
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: 8px;
}

.struct-raw-json {
  width: 100%;
}

.struct-list-value {
  width: 100%;
  margin-top: 8px;
}

.struct-list-header {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 8px;
}

.nested-struct-list-cards {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.nested-struct-list-card {
  border: 1px solid #ebeef5;
  border-radius: 6px;
  padding: 10px;
  background: #fafbfc;
}

.nested-card-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 8px;
  padding-bottom: 6px;
  border-bottom: 1px solid #ebeef5;
}

.nested-card-index {
  font-size: 12px;
  color: #909399;
  font-weight: 600;
}

.nested-card-body {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.nested-card-field {
  display: flex;
  align-items: center;
  gap: 8px;
}

.nested-card-field .field-label {
  min-width: 80px;
  font-size: 12px;
}

.nested-card-field .el-input,
.nested-card-field .el-input-number {
  flex: 1;
}

.nested-sub-struct {
  width: 100%;
  padding: 8px;
  border: 1px dashed #dcdfe6;
  border-radius: 4px;
  background: #f9f9f9;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.struct-list-hint {
  font-size: 12px;
  color: #909399;
}

.struct-list-actions {
  display: flex;
  gap: 8px;
  margin-top: 10px;
}

.value-header-actions {
  display: flex;
  align-items: center;
  gap: 8px;
}

/* Struct List Cards */
.struct-list-cards {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.struct-list-card {
  border: 1px solid #e4e7ed;
  border-radius: 8px;
  background: #ffffff;
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  overflow: hidden;
}

.struct-list-card:hover {
  border-color: #c0c4cc;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
}

.struct-list-card.drag-over {
  border-color: #409eff;
  box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.15);
}

.struct-list-card-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 10px 14px;
  background: #f8f9fc;
  cursor: pointer;
  user-select: none;
  transition: background 0.2s;
}

.struct-list-card-header:hover {
  background: #f0f2f5;
}

.card-header-left {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: 1;
  min-width: 0;
}

.card-header-left .drag-handle {
  cursor: grab;
  color: #c0c4cc;
  transition: color 0.2s;
  flex-shrink: 0;
}

.card-header-left .drag-handle:hover {
  color: #606266;
}

.collapse-arrow {
  font-size: 12px;
  color: #909399;
  transition: transform 0.3s;
  flex-shrink: 0;
}

.collapse-arrow.rotated {
  transform: rotate(90deg);
}

.card-index {
  font-size: 12px;
  font-weight: 600;
  color: #409eff;
  background: #ecf5ff;
  padding: 1px 8px;
  border-radius: 10px;
  flex-shrink: 0;
}

.card-summary {
  font-size: 12px;
  color: #606266;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.card-header-right {
  flex-shrink: 0;
}

.struct-list-card-body {
  padding: 14px 16px;
  border-top: 1px solid #f0f2f5;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.struct-list-field-row {
  display: flex;
  align-items: center;
  gap: 10px;
}

.struct-list-field-row .field-label {
  min-width: 90px;
  font-size: 13px;
  font-weight: 500;
  color: #303133;
  flex-shrink: 0;
}

.struct-list-field-row .field-input {
  flex: 1;
  min-width: 0;
}

.struct-file-picker {
  display: flex;
  align-items: center;
  gap: 8px;
}

.struct-file-list-picker {
  width: 100%;
}

.file-thumb-list {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.file-thumb {
  width: 80px;
  height: 80px;
  border: 1px dashed #dcdfe6;
  border-radius: 6px;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  overflow: hidden;
  transition: border-color 0.2s;
  background: #fafafa;
}

.file-thumb:hover {
  border-color: #409eff;
}

.file-thumb.small {
  width: 60px;
  height: 60px;
}

.file-thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.file-thumb-empty {
  color: #c0c4cc;
  font-size: 24px;
  display: flex;
  align-items: center;
  justify-content: center;
}

.file-thumb.small .file-thumb-empty {
  font-size: 18px;
}

.file-thumb-add {
  border-style: dashed;
  color: #c0c4cc;
  font-size: 20px;
}

.file-thumb-add:hover {
  color: #409eff;
  border-color: #409eff;
}

.file-clear-btn {
  flex-shrink: 0;
}

.file-thumb-item {
  position: relative;
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  cursor: grab;
  transition: opacity 0.2s, transform 0.2s;
}

.file-thumb-item:active {
  cursor: grabbing;
}

.file-thumb-item.drag-over {
  transform: scale(1.05);
  outline: 2px dashed #409eff;
  border-radius: 6px;
}

.file-thumb-item .thumb-del-btn {
  position: absolute;
  top: -6px;
  right: -6px;
  background: #fff;
  border-radius: 50%;
  box-shadow: 0 1px 3px rgba(0,0,0,0.2);
  width: 18px;
  height: 18px;
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  transition: opacity 0.2s;
}

.file-thumb-item:hover .thumb-del-btn {
  opacity: 1;
}

.struct-string-list {
  width: 100%;
}

.string-list-items {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-bottom: 6px;
}

.string-list-item {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 4px 8px;
  border: 1px solid #ebeef5;
  border-radius: 4px;
  background: #fafafa;
  cursor: grab;
  transition: border-color 0.2s, box-shadow 0.2s;
}

.string-list-item:active {
  cursor: grabbing;
}

.string-list-item.drag-over {
  border-color: #409eff;
  box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2);
}

.string-list-item .string-drag-handle {
  color: #c0c4cc;
  cursor: grab;
  flex-shrink: 0;
}

.string-list-item .el-input {
  flex: 1;
}

/* 响应式 */
@media (max-width: 1200px) {
  .cards-grid {
    grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  }
}

@media (max-width: 768px) {
  .setting-manage-container {
    padding: 12px;
    gap: 12px;
  }

  .top-action-bar {
    flex-direction: column;
    gap: 12px;
    padding: 12px;
  }

  .action-left {
    width: 100%;
  }

  .search-input {
    width: 100%;
    flex: 1;
  }

  .action-right {
    width: 100%;
    flex-wrap: wrap;
  }

  .cards-grid {
    grid-template-columns: 1fr;
  }

  .group-info-bar {
    flex-direction: column;
    align-items: flex-start;
    gap: 12px;
  }

  .group-actions {
    flex-wrap: wrap;
  }

  .cards-section {
    padding: 16px;
  }

  :deep(.dialog-scrollable .el-dialog) {
    max-height: 95vh;
    width: 95vw !important;
    margin: 0 auto;
  }

  :deep(.dialog-scrollable .el-dialog__body) {
    max-height: calc(95vh - 140px);
    padding: 15px;
  }
}
</style>
