瀏覽代碼

新增版本更新,个性化配置页面优化

yawuga 8 月之前
父節點
當前提交
cd6d4caebd
共有 3 個文件被更改,包括 1488 次插入241 次删除
  1. 1 0
      package.json
  2. 1483 237
      src/views/devops/individuation/index.vue
  3. 4 4
      src/views/devops/version/index.vue

+ 1 - 0
package.json

@@ -48,6 +48,7 @@
     "js-beautify": "1.13.0",
     "js-cookie": "3.0.1",
     "jsencrypt": "3.0.0-rc.1",
+    "mqtt": "^4.2.1",
     "nprogress": "0.2.0",
     "ol": "^6.15.1",
     "quill": "2.0.2",

+ 1483 - 237
src/views/devops/individuation/index.vue

@@ -1,237 +1,1483 @@
-<template>
-  <div class="app-container">
-    <div class="hearder">
-      <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" label-width="68px">
-        <el-form-item label="配置名称" prop="mapName">
-          <el-input v-model="queryParams.paramType" placeholder="请输入配置名称" clearable />
-        </el-form-item>
-        <el-form-item>
-          <el-button type="primary" icon="el-icon-search" size="mini" @click="">搜索</el-button>
-          <el-button icon="el-icon-refresh" size="mini" @click="">重置</el-button>
-        </el-form-item>
-      </el-form>
-    </div>
-    <el-row :gutter="10" class="mb8">
-      <el-col :span="1.5">
-        <el-button type="primary" plain icon="el-icon-plus" size="mini" @click="addData">新建配置</el-button>
-      </el-col>
-      <el-col :span="1.5">
-        <el-button type="primary" plain icon="el-icon-edit" size="mini" @click="editData"
-          :disabled="single">修改配置</el-button>
-      </el-col>
-    </el-row>
-    <div class="individ-tables">
-    <el-table :data="data" border style="width: 100%" @selection-change="handleSelectionChange">
-      <el-table-column type="selection" width="45" align="center" />
-      <el-table-column prop="id" label="编号" width="60" align="center"></el-table-column>
-      <el-table-column prop="theme" label="个性名称" align="center"></el-table-column>
-      <el-table-column prop="logo" label="顶部logo" align="center">
-        <template slot-scope="scope">
-          <el-image style="width: 50px; height: 30px" :src="scope.row.logo" fit="fill">
-          </el-image>
-        </template>
-      </el-table-column>
-      <el-table-column prop="loginLogo" label="登录页logo" align="center">
-        <template slot-scope="scope">
-          <el-image style="width: 50px; height: 30px" :src="scope.row.loginLogo" fit="fill">
-          </el-image>
-        </template>
-      </el-table-column>
-      <el-table-column prop="backgroundImg" label="登录页背景图片" align="center">
-        <template slot-scope="scope">
-          <el-image style="width: 50px; height: 30px" :src="scope.row.backgroundImg" fit="fill">
-          </el-image>
-        </template>
-      </el-table-column>
-      <el-table-column prop="company" label="公司名称" align="center"></el-table-column>
-      <el-table-column prop="status" label="当前状态" align="center"></el-table-column>
-      <el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="120">
-        <template slot-scope="scope">
-          <el-button size="mini" type="text" icon="el-icon-edit-outline" @click="editData(scope.row)">编辑</el-button>
-          <el-button size="mini" type="text" icon="el-icon-delete" @click="removeParams(scope.row)">删除</el-button>
-        </template>
-      </el-table-column>
-    </el-table>
-  </div>
-    <div class="indiv-dia">
-      <el-dialog :title="title" :visible.sync="dialogVisible" width="600px" @close="clearForm">
-        <el-form ref="ruleForm" :model="form" :rules="rules" label-width="120px">
-          <el-form-item label="个性名称" prop="theme">
-            <el-input v-model="form.theme" placeholder="请输入内容" max="20"></el-input>
-          </el-form-item>
-          <el-form-item label="顶部logo" prop="logo">
-            <ImageUpload :isShowTip="false" :limit="1" @input="(fileList) => handleInput(fileList, 'logo')"
-              :value="form.logo"></ImageUpload>
-          </el-form-item>
-          <el-form-item label="登录页logo" prop="loginLogo">
-            <ImageUpload :isShowTip="false" :limit="1" @input="(fileList) => handleInput(fileList, 'logoLogin')"
-              :value="form.loginLogo"></ImageUpload>
-          </el-form-item>
-          <el-form-item label="登录页背景图片" prop="backgroundImg">
-            <ImageUpload :isShowTip="false" :limit="1" @input="(fileList) => handleInput(fileList, 'loginGround')"
-              :value="form.backgroundImg"></ImageUpload>
-          </el-form-item>
-          <el-form-item label="公司名称" prop="company">
-            <el-input v-model="form.company" placeholder="请输入内容" max="20"></el-input>
-          </el-form-item>
-          <el-form-item label="当前状态">
-            <el-radio-group v-model="form.status">
-              <el-radio v-for="dict in dict.type.sys_notice_status" :key="dict.value" :label="dict.value">{{ dict.label
-                }}</el-radio>
-            </el-radio-group>
-          </el-form-item>
-        </el-form>
-        <span slot="footer" class="dialog-footer">
-          <el-button @click="dialogVisible = false">取 消</el-button>
-          <el-button type="primary" @click="submitData('ruleForm')">确 定</el-button>
-        </span>
-      </el-dialog>
-    </div>
-  </div>
-</template>
-
-<script>
-export default {
-  dicts: ['sys_notice_status'],
-  data() {
-    return {
-      title: '',
-      dialogVisible: false,
-      ids: [],
-      // 非单个禁用
-      single: true,
-      // 非多个禁用
-      multiple: true,
-      queryParams: {
-        pageNum: 1,
-        pageSize: 10,
-        theme: '',
-        logo: '',
-        loginLogo: '',
-        backgroundImg: '',
-        company: '',
-        status: ''
-      },
-      form: {
-        id: '',
-        theme: '',
-        logo: '',
-        loginLogo: '',
-        backgroundImg: '',
-        company: '',
-        status: '1'
-      },
-      data: [
-
-      ],
-      rules: {
-        theme: [
-          { required: true, message: "请输入个性名称", trigger: "blur" }
-        ],
-        logo: [
-          { required: true, message: "请上传顶部logo", trigger: "blur" }
-        ],
-        loginLogo: [
-          { required: true, message: "请上传登录页logo", trigger: "blur" }
-        ],
-        backgroundImg: [
-          { required: true, message: "请上传背景页图片", trigger: "blur" }
-        ],
-        company: [
-          { required: true, message: "请输入公司名称", trigger: "blur" }
-        ],
-        status: [
-          { required: true, message: "启用状态", trigger: "blur" }
-        ]
-      }
-    }
-  },
-  methods: {
-    handleSelectionChange(selection) {
-      this.ids = selection.map(item => item.id)
-      this.names = selection.map(item => item.mapName)
-      this.single = selection.length !== 1
-      this.multiple = !selection.length
-    },
-    addData() {
-      this.title = "新增配置"
-      this.dialogVisible = true;
-    },
-    editData(row) {
-      this.title = "修改配置"
-      let id = this.ids || row.id;
-      this.form = this.data.find(item => item.id == id);
-      this.dialogVisible = true;
-    },
-    clearForm() {
-      this.form = {
-        id: '',
-        theme: '',
-        logo: '',
-        loginLogo: '',
-        backgroundImg: '',
-        company: '',
-        status: '1'
-      }
-    },
-    // 图片上传的回调
-    handleInput(fileList, extraParam) {
-      if (extraParam === 'logo') {
-        if (this.form.logo) {
-          this.form.logo = null;
-        }
-        this.form.logo = fileList;
-      } else if (extraParam === 'logoLogin') {
-        if (this.form.loginLogo) {
-          this.form.loginLogo = null;
-        }
-        this.form.loginLogo = fileList;
-      } else if (extraParam === 'loginGround') {
-        if (this.form.backgroundImg) {
-          this.form.backgroundImg = null;
-        }
-        this.form.backgroundImg = fileList;
-      }
-    },
-    submitData(ruleForm) {
-      this.$refs[ruleForm].validate((valid) => {
-        if (valid) {
-          this.dialogVisible = false;
-          // todo 模拟id(开始)
-          if(this.data.length > 0){
-            let id = this.data[this.data.length -1].id; 
-            this.form.id = id + 1;
-          } else {
-            this.form.id = 1;
-          }
-          // todo 模拟id(结束)
-          this.data.push(this.form)
-          console.log(this.data);
-        } else {
-          return false;
-        }
-      })
-    },
-    removeParams(row){
-      // 删除
-    }
-  }
-}
-</script>
-
-<style scoped>
-::v-deep .indiv-dia .el-dialog {
-  margin-top: 3vh !important;
-  border-radius: 10px;
-}
-
-::v-deep .indiv-dia .el-dialog:not(.is-fullscreen) {
-  margin-top: 3vh !important;
-  border-radius: 10px;
-}
-
-::v-deep .el-table .el-table__header-wrapper th,
-.el-table .el-table__fixed-header-wrapper th {
-	background-color: #f6f6f6 !important;
-}
-</style>
+<template>
+  <div class="individuation-page">
+    <!-- 页面头部 -->
+    <div class="page-header">
+      <div class="header-content">
+        <div class="header-main">
+          <h1 class="page-title">个性配置</h1>
+          <p class="page-desc">配置主题颜色与登录资源,修改后即时生效</p>
+        </div>
+      </div>
+    </div>
+
+    <!-- 未保存更改提示 -->
+    <div v-if="hasUnsavedChanges" class="unsaved-warning">
+      <div class="warning-content">
+        <i class="el-icon-warning"></i>
+        <span>有未保存更改,请及时保存以免丢失</span>
+        <el-button type="text" size="mini" @click="saveConfig">立即保存</el-button>
+      </div>
+    </div>
+
+    <!-- 主内容区域 -->
+    <div class="page-content">
+      <div class="config-card">
+        <div class="card-header">
+          <h2 class="card-title">主题配置</h2>
+          <p class="card-desc">自定义主题颜色与登录资源,修改后即时生效</p>
+    </div>
+        <div class="card-content">
+          <el-form ref="configForm" :model="config" label-width="120px" class="config-form">
+            <!-- 主题主色 -->
+            <el-form-item label="主题主色" class="color-form-item">
+              <div class="color-input-group">
+                <el-input 
+                  v-model="primaryColorInput" 
+                  @input="onPrimaryColorInputChange"
+                  placeholder="1677ff"
+                  class="color-input"
+                  :class="{ 'input-error': !isPrimaryColorValid }"
+                  maxlength="6"
+                >
+                  <template slot="prepend">#</template>
+                  <template slot="append">
+                    <el-color-picker 
+                      v-model="config.primaryColor" 
+                      @change="onPrimaryColorChange"
+                      color-format="hex"
+                      class="color-picker-append"
+                      size="mini"
+                    />
+        </template>
+                </el-input>
+              </div>
+              <div class="form-hint">请输入 HEX 色值或通过取色器选择</div>
+            </el-form-item>
+
+            <!-- 主题辅色 -->
+            <el-form-item label="主题辅色" class="color-form-item">
+              <div class="color-input-group">
+                <el-input 
+                  v-model="secondaryColorInput" 
+                  @input="onSecondaryColorInputChange"
+                  placeholder="52c41a"
+                  class="color-input"
+                  :class="{ 'input-error': !isSecondaryColorValid }"
+                  maxlength="6"
+                >
+                  <template slot="prepend">#</template>
+                  <template slot="append">
+                    <el-color-picker 
+                      v-model="config.secondaryColor" 
+                      @change="onSecondaryColorChange"
+                      color-format="hex"
+                      class="color-picker-append"
+                      size="mini"
+                    />
+        </template>
+                </el-input>
+  </div>
+              <div class="form-hint">请输入 HEX 色值或通过取色器选择</div>
+          </el-form-item>
+
+            <!-- 顶部 Logo -->
+            <el-form-item label="顶部 Logo">
+              <div class="upload-group">
+                <div class="unified-upload-container">
+                  <!-- 缩略图区域 -->
+                  <div v-if="config.topLogo" class="thumbnail-area">
+                    <div class="thumbnail-wrapper checkered-bg">
+                      <img :src="config.topLogo" alt="顶部Logo" class="thumbnail-img" />
+                    </div>
+                  </div>
+                  
+                  <!-- 上传按钮区域 -->
+                  <div class="uploader-box" @click="triggerTopLogoUpload">
+                    <i class="el-icon-plus"></i>
+                    <div class="upload-text">上传图片</div>
+                  </div>
+                  
+                  <!-- 隐藏的上传组件 -->
+                  <input
+                    ref="topLogoInput"
+                    type="file"
+                    accept="image/png"
+                    @change="handleTopLogoUpload"
+                    style="display: none"
+                  />
+                </div>
+                <div class="upload-actions">
+                  <div class="upload-hint">推荐 100×50 / PNG,大小 < 1MB</div>
+                  <el-button 
+                    v-if="config.topLogo" 
+                    type="text" 
+                    size="mini" 
+                    @click="confirmClearTopLogo"
+                    class="clear-btn"
+                  >
+                    清空图片
+                  </el-button>
+                </div>
+              </div>
+            </el-form-item>
+
+            <!-- 登录页 Logo -->
+            <el-form-item label="登录页 Logo">
+              <div class="upload-group">
+                <div class="unified-upload-container">
+                  <!-- 缩略图区域 -->
+                  <div v-if="config.loginLogo" class="thumbnail-area">
+                    <div class="thumbnail-wrapper checkered-bg">
+                      <img :src="config.loginLogo" alt="登录页Logo" class="thumbnail-img" />
+                    </div>
+                  </div>
+                  
+                  <!-- 上传按钮区域 -->
+                  <div class="uploader-box" @click="triggerLoginLogoUpload">
+                    <i class="el-icon-plus"></i>
+                    <div class="upload-text">上传图片</div>
+                  </div>
+                  
+                  <!-- 隐藏的上传组件 -->
+                  <input
+                    ref="loginLogoInput"
+                    type="file"
+                    accept="image/png"
+                    @change="handleLoginLogoUpload"
+                    style="display: none"
+                  />
+                </div>
+                <div class="upload-actions">
+                  <div class="upload-hint">推荐 200×100 / PNG,大小 < 1MB</div>
+                  <el-button 
+                    v-if="config.loginLogo" 
+                    type="text" 
+                    size="mini" 
+                    @click="confirmClearLoginLogo"
+                    class="clear-btn"
+                  >
+                    清空图片
+                  </el-button>
+                </div>
+              </div>
+            </el-form-item>
+
+            <!-- 登录页背景图 -->
+            <el-form-item label="登录页背景图">
+              <div class="upload-group">
+                <div class="unified-upload-container">
+                  <!-- 缩略图区域 -->
+                  <div v-if="config.loginBg" class="thumbnail-area">
+                    <div class="thumbnail-wrapper">
+                      <img :src="config.loginBg" alt="登录页背景图" class="thumbnail-img" />
+                    </div>
+                  </div>
+                  
+                  <!-- 上传按钮区域 -->
+                  <div class="uploader-box" @click="triggerLoginBgUpload">
+                    <i class="el-icon-plus"></i>
+                    <div class="upload-text">上传图片</div>
+                  </div>
+                  
+                  <!-- 隐藏的上传组件 -->
+                  <input
+                    ref="loginBgInput"
+                    type="file"
+                    accept="image/jpeg,image/jpg"
+                    @change="handleLoginBgUpload"
+                    style="display: none"
+                  />
+                </div>
+                <div class="upload-actions">
+                  <div class="upload-hint">推荐 1290×1080 / JPG,大小 < 3MB</div>
+                  <el-button 
+                    v-if="config.loginBg" 
+                    type="text" 
+                    size="mini" 
+                    @click="confirmClearLoginBg"
+                    class="clear-btn"
+                  >
+                    清空图片
+                  </el-button>
+                </div>
+              </div>
+            </el-form-item>
+        </el-form>
+
+          <!-- 吸底操作条 -->
+          <div class="sticky-actions">
+            <div class="actions-content">
+              <el-button 
+                @click="resetDefault" 
+                icon="el-icon-refresh"
+                :loading="resetting"
+              >
+                重置默认
+              </el-button>
+              <el-button 
+                type="primary" 
+                @click="saveConfig" 
+                icon="el-icon-check"
+                :disabled="!isFormValid"
+                :loading="saving"
+              >
+                保存
+              </el-button>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'IndividuationPage',
+  
+  data() {
+    return {
+      // 配置状态
+      config: {
+        primaryColor: '#1677ff',
+        secondaryColor: '#52c41a',
+        topLogo: null,
+        loginLogo: null,
+        loginBg: null
+      },
+      
+      // 颜色输入框的值(用于校验和格式化)
+      primaryColorInput: '1677ff',
+      secondaryColorInput: '52c41a',
+      
+      // 默认配置
+      defaultConfig: {
+        primaryColor: '#1677ff',
+        secondaryColor: '#52c41a',
+        topLogo: null,
+        loginLogo: null,
+        loginBg: null
+      },
+      
+      // 原始配置(用于检测是否有未保存更改)
+      originalConfig: null,
+      
+      // 离开页面拦截
+      isLeaving: false,
+      
+      // 按钮loading状态
+      saving: false,
+      resetting: false
+    }
+  },
+  
+  computed: {
+    // 检查主色是否为有效的HEX格式
+    isPrimaryColorValid() {
+      return this.isValidHexColor(this.primaryColorInput)
+    },
+    
+    // 检查辅色是否为有效的HEX格式
+    isSecondaryColorValid() {
+      return this.isValidHexColor(this.secondaryColorInput)
+    },
+    
+    // 表单是否有效(颜色格式正确)
+    isFormValid() {
+      return this.isPrimaryColorValid && this.isSecondaryColorValid
+    },
+    
+    // 是否有未保存的更改
+    hasUnsavedChanges() {
+      if (!this.originalConfig) return false
+      return JSON.stringify(this.config) !== JSON.stringify(this.originalConfig)
+    }
+  },
+  
+  created() {
+    this.loadFromServer()
+    this.applyTheme()
+    this.setupBeforeUnload()
+  },
+  
+  beforeDestroy() {
+    this.removeBeforeUnload()
+  },
+  
+  beforeRouteLeave(to, from, next) {
+    if (this.hasUnsavedChanges && !this.isLeaving) {
+      this.$confirm('当前修改未保存,是否确认离开?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        this.isLeaving = true
+        next()
+      }).catch(() => {
+        next(false)
+      })
+    } else {
+      next()
+    }
+  },
+  
+  methods: {
+    // 应用主题到全局 CSS 变量
+    applyTheme() {
+      const root = document.documentElement
+      root.style.setProperty('--brand-primary', this.config.primaryColor)
+      root.style.setProperty('--brand-accent', this.config.secondaryColor)
+    },
+    
+    // 检查是否为有效的HEX颜色
+    isValidHexColor(hex) {
+      if (!hex) return false
+      // 移除可能的#前缀进行验证
+      const cleanHex = hex.replace('#', '')
+      // 验证3位或6位HEX格式
+      const hexRegex = /^([A-Fa-f0-9]{3}|[A-Fa-f0-9]{6})$/
+      return hexRegex.test(cleanHex)
+    },
+    
+    // 将3位HEX转换为6位
+    expandHex(hex) {
+      const cleanHex = hex.replace('#', '')
+      if (cleanHex.length === 3) {
+        return cleanHex.split('').map(char => char + char).join('')
+      }
+      return cleanHex
+    },
+    
+    
+    // 主色输入变化处理
+    onPrimaryColorInputChange(value) {
+      // 只允许输入合法的HEX字符
+      const cleanValue = value.replace(/[^A-Fa-f0-9]/g, '').toLowerCase()
+      this.primaryColorInput = cleanValue
+      
+      if (this.isValidHexColor(cleanValue)) {
+        const expandedHex = this.expandHex(cleanValue)
+        this.config.primaryColor = '#' + expandedHex
+        this.applyTheme()
+      }
+    },
+    
+    // 辅色输入变化处理
+    onSecondaryColorInputChange(value) {
+      // 只允许输入合法的HEX字符
+      const cleanValue = value.replace(/[^A-Fa-f0-9]/g, '').toLowerCase()
+      this.secondaryColorInput = cleanValue
+      
+      if (this.isValidHexColor(cleanValue)) {
+        const expandedHex = this.expandHex(cleanValue)
+        this.config.secondaryColor = '#' + expandedHex
+        this.applyTheme()
+      }
+    },
+    
+    // 主色变化处理(取色器)
+    onPrimaryColorChange(value) {
+      if (value) {
+        this.config.primaryColor = value
+        this.primaryColorInput = value.replace('#', '')
+        this.applyTheme()
+      }
+    },
+    
+    // 辅色变化处理(取色器)
+    onSecondaryColorChange(value) {
+      if (value) {
+        this.config.secondaryColor = value
+        this.secondaryColorInput = value.replace('#', '')
+        this.applyTheme()
+      }
+    },
+    
+    // 触发顶部 Logo 上传
+    triggerTopLogoUpload() {
+      this.$refs.topLogoInput.click()
+    },
+    
+    // 处理顶部 Logo 上传
+    handleTopLogoUpload(event) {
+      const file = event.target.files[0]
+      if (!file) return
+      
+      // 校验文件类型
+      if (file.type !== 'image/png') {
+        this.$message.error('顶部 Logo 只支持 PNG 格式!')
+        return
+      }
+      
+      // 校验文件大小
+      if (file.size / 1024 / 1024 > 1) {
+        this.$message.error('顶部 Logo 大小不能超过 1MB!')
+        return
+      }
+      
+      // 读取文件
+      const reader = new FileReader()
+      reader.onload = (e) => {
+        this.config.topLogo = e.target.result
+        this.applyTheme()
+      }
+      reader.readAsDataURL(file)
+      
+      // 清空input值,允许重复选择同一文件
+      event.target.value = ''
+    },
+    
+    // 顶部 Logo 上传前校验(保留兼容性)
+    beforeUploadTopLogo(file) {
+      const isValidType = file.type === 'image/png'
+      const isValidSize = file.size / 1024 / 1024 < 1 // 1MB
+      
+      if (!isValidType) {
+        this.$message.error('顶部 Logo 只支持 PNG 格式!')
+        return false
+      }
+      if (!isValidSize) {
+        this.$message.error('顶部 Logo 大小不能超过 1MB!')
+        return false
+      }
+      return false // 阻止自动上传
+    },
+    
+    // 顶部 Logo 变化处理
+    onTopLogoChange(file, fileList) {
+      if (file.status !== 'ready') return
+      
+      const reader = new FileReader()
+      reader.onload = (e) => {
+        this.config.topLogo = e.target.result
+        this.topLogoFileList = [{
+          name: file.name,
+          url: e.target.result,
+          uid: file.uid
+        }]
+        this.applyTheme() // 图片变化也触发主题应用
+      }
+      reader.readAsDataURL(file.raw)
+    },
+    
+    // 顶部 Logo 移除处理
+    onTopLogoRemove() {
+      this.config.topLogo = null
+      this.topLogoFileList = []
+      this.applyTheme()
+    },
+    
+    // 确认清空顶部 Logo
+    async confirmClearTopLogo() {
+      try {
+        await this.$confirm('确认清空当前图片?', '提示', {
+          confirmButtonText: '确定',
+          cancelButtonText: '取消',
+          type: 'warning'
+        })
+        this.clearTopLogo()
+      } catch (error) {
+        // 用户取消操作
+      }
+    },
+    
+    // 清空顶部 Logo
+    clearTopLogo() {
+      this.config.topLogo = null
+      this.topLogoFileList = []
+      this.applyTheme()
+    },
+    
+    // 触发登录页 Logo 上传
+    triggerLoginLogoUpload() {
+      this.$refs.loginLogoInput.click()
+    },
+    
+    // 处理登录页 Logo 上传
+    handleLoginLogoUpload(event) {
+      const file = event.target.files[0]
+      if (!file) return
+      
+      // 校验文件类型
+      if (file.type !== 'image/png') {
+        this.$message.error('登录页 Logo 只支持 PNG 格式!')
+        return
+      }
+      
+      // 校验文件大小
+      if (file.size / 1024 / 1024 > 1) {
+        this.$message.error('登录页 Logo 大小不能超过 1MB!')
+        return
+      }
+      
+      // 读取文件
+      const reader = new FileReader()
+      reader.onload = (e) => {
+        this.config.loginLogo = e.target.result
+        this.applyTheme()
+      }
+      reader.readAsDataURL(file)
+      
+      // 清空input值,允许重复选择同一文件
+      event.target.value = ''
+    },
+    
+    // 登录页 Logo 上传前校验(保留兼容性)
+    beforeUploadLoginLogo(file) {
+      const isValidType = file.type === 'image/png'
+      const isValidSize = file.size / 1024 / 1024 < 1 // 1MB
+      
+      if (!isValidType) {
+        this.$message.error('登录页 Logo 只支持 PNG 格式!')
+        return false
+      }
+      if (!isValidSize) {
+        this.$message.error('登录页 Logo 大小不能超过 1MB!')
+        return false
+      }
+      return false // 阻止自动上传
+    },
+    
+    // 登录页 Logo 变化处理
+    onLoginLogoChange(file, fileList) {
+      if (file.status !== 'ready') return
+      
+      const reader = new FileReader()
+      reader.onload = (e) => {
+        this.config.loginLogo = e.target.result
+        this.loginLogoFileList = [{
+          name: file.name,
+          url: e.target.result,
+          uid: file.uid
+        }]
+        this.applyTheme()
+      }
+      reader.readAsDataURL(file.raw)
+    },
+    
+    // 登录页 Logo 移除处理
+    onLoginLogoRemove() {
+      this.config.loginLogo = null
+      this.loginLogoFileList = []
+      this.applyTheme()
+    },
+    
+    // 确认清空登录页 Logo
+    async confirmClearLoginLogo() {
+      try {
+        await this.$confirm('确认清空当前图片?', '提示', {
+          confirmButtonText: '确定',
+          cancelButtonText: '取消',
+          type: 'warning'
+        })
+        this.clearLoginLogo()
+      } catch (error) {
+        // 用户取消操作
+      }
+    },
+    
+    // 清空登录页 Logo
+    clearLoginLogo() {
+      this.config.loginLogo = null
+      this.loginLogoFileList = []
+      this.applyTheme()
+    },
+    
+    // 触发登录背景图上传
+    triggerLoginBgUpload() {
+      this.$refs.loginBgInput.click()
+    },
+    
+    // 处理登录背景图上传
+    handleLoginBgUpload(event) {
+      const file = event.target.files[0]
+      if (!file) return
+      
+      // 校验文件类型
+      if (file.type !== 'image/jpeg' && file.type !== 'image/jpg') {
+        this.$message.error('登录背景图只支持 JPG/JPEG 格式!')
+        return
+      }
+      
+      // 校验文件大小
+      if (file.size / 1024 / 1024 > 3) {
+        this.$message.error('登录背景图大小不能超过 3MB!')
+        return
+      }
+      
+      // 读取文件
+      const reader = new FileReader()
+      reader.onload = (e) => {
+        this.config.loginBg = e.target.result
+        this.applyTheme()
+      }
+      reader.readAsDataURL(file)
+      
+      // 清空input值,允许重复选择同一文件
+      event.target.value = ''
+    },
+    
+    // 登录背景图上传前校验(保留兼容性)
+    beforeUploadLoginBg(file) {
+      const isValidType = file.type === 'image/jpeg' || file.type === 'image/jpg'
+      const isValidSize = file.size / 1024 / 1024 < 3 // 3MB
+      
+      if (!isValidType) {
+        this.$message.error('登录背景图只支持 JPG/JPEG 格式!')
+        return false
+      }
+      if (!isValidSize) {
+        this.$message.error('登录背景图大小不能超过 3MB!')
+        return false
+      }
+      return false // 阻止自动上传
+    },
+    
+    // 登录背景图变化处理
+    onLoginBgChange(file, fileList) {
+      if (file.status !== 'ready') return
+      
+      const reader = new FileReader()
+      reader.onload = (e) => {
+        this.config.loginBg = e.target.result
+        this.loginBgFileList = [{
+          name: file.name,
+          url: e.target.result,
+          uid: file.uid
+        }]
+        this.applyTheme()
+      }
+      reader.readAsDataURL(file.raw)
+    },
+    
+    // 登录背景图移除处理
+    onLoginBgRemove() {
+      this.config.loginBg = null
+      this.loginBgFileList = []
+      this.applyTheme()
+    },
+    
+    // 确认清空登录背景图
+    async confirmClearLoginBg() {
+      try {
+        await this.$confirm('确认清空当前图片?', '提示', {
+          confirmButtonText: '确定',
+          cancelButtonText: '取消',
+          type: 'warning'
+        })
+        this.clearLoginBg()
+      } catch (error) {
+        // 用户取消操作
+      }
+    },
+    
+    // 清空登录背景图
+    clearLoginBg() {
+      this.config.loginBg = null
+      this.loginBgFileList = []
+      this.applyTheme()
+    },
+    
+    // 从后端加载配置
+    async loadFromServer() {
+      try {
+        // 调用后端API获取配置
+        // const response = await this.$axios.get('/api/individuation/config')
+        // const serverConfig = response.data
+        // 
+        // 返回格式:
+        // {
+        //   code: 200,
+        //   message: "获取成功",
+        //   data: {
+        //     primaryColor: "#1677ff",
+        //     secondaryColor: "#52c41a", 
+        //     topLogo: "/uploads/top-logo.png",      // 文件路径或null
+        //     loginLogo: "/uploads/login-logo.png",  // 文件路径或null
+        //     loginBg: "/uploads/login-bg.jpg"       // 文件路径或null
+        //   }
+        // }
+        
+        // 模拟API调用
+        await new Promise(resolve => setTimeout(resolve, 500))
+        
+        // 先尝试从本地存储加载(作为备用)
+        const saved = localStorage.getItem('individuationConfig')
+        let serverConfig = null
+        
+        if (saved) {
+          serverConfig = JSON.parse(saved)
+        }
+        
+        // 如果有服务器配置就使用,否则使用默认配置
+        if (serverConfig) {
+          this.config = { ...this.defaultConfig, ...serverConfig }
+        } else {
+          this.config = { ...this.defaultConfig }
+        }
+        
+        // 同步颜色输入框的值
+        this.primaryColorInput = this.config.primaryColor.replace('#', '')
+        this.secondaryColorInput = this.config.secondaryColor.replace('#', '')
+        
+        // 记录原始配置用于检测更改
+        this.originalConfig = JSON.parse(JSON.stringify(this.config))
+      } catch (error) {
+        console.error('加载服务器配置失败:', error)
+        this.config = { ...this.defaultConfig }
+        this.originalConfig = JSON.parse(JSON.stringify(this.config))
+        this.$message.warning('加载配置失败,使用默认配置')
+      }
+    },
+    
+    // 保存配置到后端
+    async saveConfig() {
+      if (!this.isFormValid) {
+        this.$message.error('请检查颜色格式是否正确')
+        return
+      }
+      
+      this.saving = true
+      
+      try {
+        // 构造保存数据
+        const saveData = {
+          primaryColor: this.config.primaryColor,
+          secondaryColor: this.config.secondaryColor,
+          topLogo: this.config.topLogo,
+          loginLogo: this.config.loginLogo,
+          loginBg: this.config.loginBg
+        }
+        
+        // 调用后端API保存配置
+        // const response = await this.$axios.post('/api/individuation/save', saveData)
+        // 
+        // 接口参数说明:
+        // {
+        //   primaryColor: string,      // 主题主色,如 "#1677ff"
+        //   secondaryColor: string,    // 主题辅色,如 "#52c41a" 
+        //   topLogo: string | null,    // 顶部Logo的Base64数据或上传后的文件路径
+        //   loginLogo: string | null,  // 登录页Logo的Base64数据或上传后的文件路径
+        //   loginBg: string | null     // 登录背景图的Base64数据或上传后的文件路径
+        // }
+        // 
+        // 返回格式:
+        // {
+        //   code: 200,
+        //   message: "保存成功",
+        //   data: {
+        //     topLogoUrl: "/uploads/top-logo.png",      // 如果上传了顶部Logo
+        //     loginLogoUrl: "/uploads/login-logo.png",  // 如果上传了登录Logo  
+        //     loginBgUrl: "/uploads/login-bg.jpg"       // 如果上传了背景图
+        //   }
+        // }
+        
+        // 模拟API调用
+        await new Promise(resolve => setTimeout(resolve, 1000))
+        
+        // 保存成功,同时保存到本地
+        localStorage.setItem('individuationConfig', JSON.stringify(this.config))
+        
+        // 更新原始配置,清除未保存状态
+        this.originalConfig = JSON.parse(JSON.stringify(this.config))
+        
+        this.$message.success('配置保存成功')
+      } catch (error) {
+        console.error('保存配置失败:', error)
+        this.$message.error('保存失败:' + (error.message || '请稍后重试'))
+      } finally {
+        this.saving = false
+      }
+    },
+    
+    // 重置为默认配置
+    async resetDefault() {
+      this.resetting = true
+      
+      try {
+        // 调用后端API重置为默认配置
+        // const response = await this.$axios.post('/api/individuation/reset')
+        // const defaultConfig = response.data
+        // 
+        // 返回格式:
+        // {
+        //   code: 200,
+        //   message: "重置成功", 
+        //   data: {
+        //     primaryColor: "#1677ff",
+        //     secondaryColor: "#52c41a",
+        //     topLogo: null,
+        //     loginLogo: null,
+        //     loginBg: null
+        //   }
+        // }
+        
+        // 模拟API调用
+        await new Promise(resolve => setTimeout(resolve, 800))
+        
+        // 模拟后端返回的默认配置
+        const defaultConfig = {
+          primaryColor: '#1677ff',
+          secondaryColor: '#52c41a',
+          topLogo: null,
+          loginLogo: null,
+          loginBg: null
+        }
+        
+        // 应用默认配置
+        this.config = { ...defaultConfig }
+        this.primaryColorInput = defaultConfig.primaryColor.replace('#', '')
+        this.secondaryColorInput = defaultConfig.secondaryColor.replace('#', '')
+        
+        // 更新原始配置
+        this.originalConfig = JSON.parse(JSON.stringify(this.config))
+        
+        // 应用主题
+        this.applyTheme()
+        
+        // 同时更新本地存储
+        localStorage.setItem('individuationConfig', JSON.stringify(this.config))
+        
+        this.$message.success('已重置为默认配置')
+      } catch (error) {
+        console.error('重置配置失败:', error)
+        this.$message.error('重置失败:' + (error.message || '请稍后重试'))
+      } finally {
+        this.resetting = false
+      }
+    },
+    
+    // 设置页面离开拦截
+    setupBeforeUnload() {
+      this.beforeUnloadHandler = (event) => {
+        if (this.hasUnsavedChanges && !this.isLeaving) {
+          event.preventDefault()
+          event.returnValue = '当前修改未保存,是否确认离开?'
+          return '当前修改未保存,是否确认离开?'
+        }
+      }
+      window.addEventListener('beforeunload', this.beforeUnloadHandler)
+    },
+    
+    // 移除页面离开拦截
+    removeBeforeUnload() {
+      if (this.beforeUnloadHandler) {
+        window.removeEventListener('beforeunload', this.beforeUnloadHandler)
+      }
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.individuation-page {
+  min-height: 100vh;
+  background: var(--color-bg-secondary);
+  /* 移除多余的底部间距,卡片内置底栏不需要额外空间 */
+  padding-bottom: var(--spacing-4);
+
+  .page-header {
+    background: var(--color-bg-card);
+    border-bottom: 1px solid var(--color-border-primary);
+    padding: var(--spacing-6);
+    margin-bottom: var(--spacing-6);
+    box-shadow: 0 6px 18px rgba(2, 6, 23, 0.05);
+
+    .header-content {
+      max-width: 1200px;
+      margin: 0 auto;
+
+      .header-main {
+        .page-title {
+          color: var(--color-text-primary);
+          font-size: var(--font-size-3xl);
+          font-weight: var(--font-weight-bold);
+          margin: 0 0 var(--spacing-2) 0;
+          line-height: var(--line-height-tight);
+        }
+
+        .page-desc {
+          color: var(--color-text-secondary);
+          font-size: var(--font-size-base);
+          margin: 0;
+          line-height: var(--line-height-relaxed);
+        }
+      }
+    }
+  }
+
+  .page-content {
+    max-width: 800px;
+    margin: 0 auto;
+    padding: 0 var(--spacing-6);
+    /* 确保不会裁剪卡片阴影 */
+    overflow: visible;
+  }
+}
+
+/* 未保存更改警告条 */
+.unsaved-warning {
+  background: #fff7e6;
+  border: 1px solid #ffd591;
+  border-radius: var(--radius-lg);
+  margin: 0 auto var(--spacing-4);
+  max-width: 1200px;
+  
+  .warning-content {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding: var(--spacing-3) var(--spacing-6);
+    
+    i {
+      color: #fa8c16;
+      font-size: 16px;
+      margin-right: 8px;
+    }
+    
+    span {
+      color: #d46b08;
+      font-size: 14px;
+      flex: 1;
+    }
+    
+    .el-button {
+      margin-left: 12px;
+      color: #d46b08;
+      
+      &:hover {
+        color: #ad4e00;
+      }
+    }
+  }
+}
+
+/* 配置卡片 */
+.config-card {
+  background: var(--color-bg-card);
+  border-radius: var(--radius-xl);
+  box-shadow: 0 6px 18px rgba(2, 6, 23, 0.05);
+  border: 1px solid var(--color-border-primary);
+  transition: all var(--duration-200) var(--ease-out);
+  animation: slideInUp 0.3s ease-out;
+  position: relative;
+  
+  /* 确保卡片内容能够继承圆角,底栏与卡片融为一体 */
+  overflow: hidden;
+  
+  &:hover {
+    box-shadow: 0 8px 24px rgba(2, 6, 23, 0.08);
+    border-color: var(--color-border-secondary);
+  }
+  
+  .card-header {
+    padding: var(--spacing-5);
+    border-bottom: 1px solid var(--color-border-secondary);
+    
+    .card-title {
+      font-size: var(--font-size-lg);
+      font-weight: var(--font-weight-semibold);
+      color: var(--color-text-primary);
+      margin: 0 0 var(--spacing-1) 0;
+      line-height: var(--line-height-tight);
+    }
+    
+    .card-desc {
+      font-size: var(--font-size-sm);
+      color: var(--color-text-tertiary);
+      margin: 0;
+      line-height: var(--line-height-relaxed);
+      opacity: 0.8;
+    }
+  }
+  
+  .card-content {
+    padding: var(--spacing-5);
+    /* 移除底部额外间距,让内容与底栏紧密连接 */
+    padding-bottom: 0;
+  }
+}
+
+/* 表单样式 */
+.config-form {
+  /* 为表单内容添加底部边距,与卡片底栏形成自然间隔 */
+  padding-bottom: var(--spacing-4);
+  .color-form-item {
+    .color-input-group {
+      .color-input {
+        width: 300px;
+        
+        &.input-error {
+          ::v-deep .el-input__inner {
+            border-color: var(--color-danger) !important;
+            box-shadow: 0 0 0 2px rgba(245, 34, 45, 0.2) !important;
+          }
+        }
+      }
+    }
+  }
+  
+  .upload-group {
+    display: flex;
+    align-items: flex-start;
+    gap: 16px;
+    
+    .upload-actions {
+      flex: 1;
+      
+      .upload-hint {
+        color: #999;
+        font-size: 12px;
+        line-height: 1.5;
+        margin-bottom: 8px;
+      }
+      
+      .clear-btn {
+        color: var(--color-danger);
+        font-size: 12px;
+        padding: 0;
+        
+        &:hover {
+          color: var(--color-danger-light);
+        }
+      }
+    }
+  }
+  
+  /* 统一上传容器 */
+  .unified-upload-container {
+    display: flex;
+    align-items: center;
+    gap: 12px;
+    flex-shrink: 0;
+  }
+  
+  /* 缩略图区域 */
+  .thumbnail-area {
+    .thumbnail-wrapper {
+      width: 120px;
+      height: 120px;
+      border-radius: 8px;
+      overflow: hidden;
+      border: 1px solid var(--color-border-secondary);
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      
+      &.checkered-bg {
+        background-image: 
+          linear-gradient(45deg, #f5f5f5 25%, transparent 25%), 
+          linear-gradient(-45deg, #f5f5f5 25%, transparent 25%), 
+          linear-gradient(45deg, transparent 75%, #f5f5f5 75%), 
+          linear-gradient(-45deg, transparent 75%, #f5f5f5 75%);
+        background-size: 12px 12px;
+        background-position: 0 0, 0 6px, 6px -6px, -6px 0px;
+      }
+      
+      .thumbnail-img {
+        width: 100%;
+        height: 100%;
+        object-fit: contain;
+      }
+    }
+  }
+  
+  /* 统一上传按钮样式 */
+  .uploader-box {
+    width: 120px;
+    height: 120px;
+    border: 2px dashed var(--color-border-secondary);
+    border-radius: 8px;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    cursor: pointer;
+    transition: all 0.3s ease;
+    background-color: var(--color-bg-card);
+    
+    &:hover {
+      border-color: var(--color-primary);
+      background-color: var(--color-bg-secondary);
+    }
+    
+    .el-icon-plus {
+      font-size: 24px;
+      color: var(--color-text-tertiary);
+      margin-bottom: 8px;
+      transition: color 0.3s ease;
+    }
+    
+    .upload-text {
+      font-size: 12px;
+      color: var(--color-text-tertiary);
+      transition: color 0.3s ease;
+    }
+    
+    &:hover {
+      .el-icon-plus,
+      .upload-text {
+        color: var(--color-primary);
+      }
+    }
+  }
+  
+  /* 隐藏的上传组件 */
+  .hidden-upload {
+    display: none !important;
+  }
+  
+  .form-hint {
+    color: #999;
+    font-size: 12px;
+    margin-top: 4px;
+    line-height: 1.4;
+  }
+}
+
+/* 表单项间距 */
+::v-deep .el-form-item {
+  margin-bottom: 16px;
+  
+  .el-form-item__label {
+    color: var(--color-text-primary);
+    font-weight: var(--font-weight-medium);
+    line-height: 1.5;
+  }
+}
+
+/* 卡片内置底栏 */
+.sticky-actions {
+  /* 移除独立吸底定位,改为卡片内部底栏 */
+  position: static;
+  
+  /* 与卡片融为一体的样式 */
+  background: var(--color-bg-card);
+  border-top: 1px solid var(--color-border-secondary);
+  
+  /* 移除独立阴影和独立圆角,与卡片共享 */
+  box-shadow: none;
+  border-radius: 0;
+  
+  /* 与卡片内容保持一致的内边距 */
+  padding: var(--spacing-4) var(--spacing-5);
+  margin: 0 calc(-1 * var(--spacing-5));
+  margin-top: var(--spacing-4);
+  
+  .actions-content {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    
+    .el-button {
+      min-width: 100px;
+    }
+  }
+}
+
+/* 上传组件样式优化 */
+::v-deep .el-upload--picture-card {
+  width: 100px;
+  height: 100px;
+  border-radius: 6px;
+  display: flex !important;
+  justify-content: center !important;
+  align-items: center !important;
+  
+  .el-icon-plus {
+    font-size: 24px;
+    color: var(--color-text-tertiary);
+    margin: 0 !important;
+    line-height: 1 !important;
+  }
+}
+
+/* 上传列表样式 */
+::v-deep .el-upload-list--picture-card {
+  .el-upload-list__item {
+    width: 100px;
+    height: 100px;
+    border-radius: 6px;
+    transition: all 0.3s ease;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    
+    &:hover {
+      box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+      transform: translateY(-2px);
+    }
+  }
+  
+  .el-upload-list__item-thumbnail {
+    object-fit: contain;
+    width: 100%;
+    height: 100%;
+  }
+}
+
+/* 确保上传按钮完全居中 */
+::v-deep .el-upload {
+  position: relative;
+  
+  &.el-upload--picture-card {
+    /* 重置所有可能影响居中的样式 */
+    text-align: center !important;
+    vertical-align: middle !important;
+    
+    /* 确保内容居中 */
+    > * {
+      position: absolute;
+      top: 50%;
+      left: 50%;
+      transform: translate(-50%, -50%);
+    }
+    
+    .el-icon-plus {
+      position: absolute;
+      top: 50%;
+      left: 50%;
+      transform: translate(-50%, -50%);
+      margin: 0 !important;
+      padding: 0 !important;
+    }
+  }
+}
+
+/* 棋盘格背景 */
+.checkered-bg {
+  ::v-deep .el-upload--picture-card,
+  ::v-deep .el-upload-list__item {
+    background-image: 
+      linear-gradient(45deg, #f5f5f5 25%, transparent 25%), 
+      linear-gradient(-45deg, #f5f5f5 25%, transparent 25%), 
+      linear-gradient(45deg, transparent 75%, #f5f5f5 75%), 
+      linear-gradient(-45deg, transparent 75%, #f5f5f5 75%);
+    background-size: 12px 12px;
+    background-position: 0 0, 0 6px, 6px -6px, -6px 0px;
+    
+    /* 确保棋盘格背景不影响居中 */
+    display: flex !important;
+    justify-content: center !important;
+    align-items: center !important;
+  }
+  
+  ::v-deep .el-upload-list__item-thumbnail {
+    background: transparent;
+  }
+  
+  /* 棋盘格背景下的上传按钮居中 */
+  ::v-deep .el-upload--picture-card {
+    .el-icon-plus {
+      background: rgba(255, 255, 255, 0.8);
+      border-radius: 50%;
+      width: 32px;
+      height: 32px;
+      display: flex;
+      justify-content: center;
+      align-items: center;
+      position: absolute;
+      top: 50%;
+      left: 50%;
+      transform: translate(-50%, -50%);
+    }
+  }
+}
+
+/* 颜色选择器样式 */
+::v-deep .el-color-picker {
+  .el-color-picker__trigger {
+    border-radius: 4px;
+  }
+}
+
+/* 取色器在输入框右侧的样式 */
+::v-deep .el-input-group__append {
+  .color-picker-append {
+    .el-color-picker__trigger {
+      width: 24px;
+      height: 24px;
+      border: none;
+      border-radius: 4px;
+      box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.15);
+    }
+  }
+}
+
+/* 主题变量验证 */
+.brand-primary {
+  color: var(--brand-primary);
+}
+
+.brand-accent {
+  color: var(--brand-accent);
+}
+
+// 滑入动画
+@keyframes slideInUp {
+  from {
+    opacity: 0;
+    transform: translateY(20px);
+  }
+  to {
+    opacity: 1;
+    transform: translateY(0);
+  }
+}
+
+/* 响应式适配 */
+@media (max-width: 768px) {
+  .individuation-page {
+    .page-header {
+      padding: var(--spacing-4);
+      margin-bottom: var(--spacing-4);
+    }
+    
+    .page-content {
+      padding: 0 var(--spacing-4);
+    }
+  }
+  
+  .config-card {
+    .card-header {
+      padding: var(--spacing-4);
+    }
+    
+    .card-content {
+      padding: var(--spacing-4);
+      padding-bottom: 80px;
+    }
+  }
+  
+  .config-form {
+    .color-input-group {
+      flex-direction: column;
+      align-items: flex-start;
+      gap: 8px;
+      
+      .color-input {
+        width: 100%;
+      }
+    }
+    
+    .upload-group {
+      flex-direction: column;
+      gap: 8px;
+    }
+  }
+  
+  .sticky-actions {
+    /* 移动端也是卡片内部底栏,无需特殊定位 */
+    padding: var(--spacing-3) var(--spacing-4);
+    margin: 0 calc(-1 * var(--spacing-4));
+    margin-top: var(--spacing-3);
+    
+    .actions-content {
+      flex-direction: column;
+      gap: 8px;
+      
+      .el-button {
+        width: 100%;
+      }
+    }
+  }
+}
+
+/* 暗色主题适配 */
+html.dark {
+  .individuation-page {
+    .page-header {
+      background: var(--color-bg-tertiary);
+      box-shadow: 0 6px 18px rgba(0, 0, 0, 0.15);
+    }
+  }
+  
+  .unsaved-warning {
+    background: rgba(255, 247, 230, 0.1);
+    border-color: rgba(255, 213, 145, 0.3);
+    
+    .warning-content {
+      i {
+        color: #ffa940;
+      }
+      
+      span {
+        color: #ffa940;
+      }
+      
+      .el-button {
+        color: #ffa940;
+        
+        &:hover {
+          color: #ffbb96;
+        }
+      }
+    }
+  }
+  
+  .config-card {
+    background: var(--color-bg-tertiary);
+    box-shadow: 0 6px 18px rgba(0, 0, 0, 0.15);
+    border-color: var(--color-border-tertiary);
+    
+    &:hover {
+      box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);
+      border-color: var(--color-border-secondary);
+    }
+    
+    .card-header {
+      border-bottom-color: var(--color-border-tertiary);
+      
+      .card-title {
+        color: var(--color-text-primary);
+      }
+      
+      .card-desc {
+        color: var(--color-text-quaternary);
+      }
+    }
+  }
+  
+  .sticky-actions {
+    /* 暗色主题下的卡片内置底栏 */
+    background: var(--color-bg-tertiary);
+    border-top-color: var(--color-border-tertiary);
+    /* 移除独立阴影,与卡片共享阴影 */
+    box-shadow: none;
+  }
+  
+  .upload-actions {
+    .upload-hint {
+      color: var(--color-text-quaternary);
+    }
+  }
+  
+  .form-hint {
+    color: var(--color-text-quaternary);
+  }
+  
+  /* 暗色主题下的缩略图区域 */
+  .thumbnail-area {
+    .thumbnail-wrapper {
+      &.checkered-bg {
+        background-image: 
+          linear-gradient(45deg, #3a3a3a 25%, transparent 25%), 
+          linear-gradient(-45deg, #3a3a3a 25%, transparent 25%), 
+          linear-gradient(45deg, transparent 75%, #3a3a3a 75%), 
+          linear-gradient(-45deg, transparent 75%, #3a3a3a 75%);
+      }
+    }
+  }
+  
+  /* 暗色主题下的上传按钮 */
+  .uploader-box {
+    background-color: var(--color-bg-tertiary);
+    border-color: var(--color-border-tertiary);
+    
+    &:hover {
+      background-color: var(--color-bg-secondary);
+      border-color: var(--color-primary);
+    }
+  }
+}
+</style>

+ 4 - 4
src/views/devops/version/index.vue

@@ -153,7 +153,7 @@
                     {{ row.status }}
                   </el-tag>
                 </template>
-              </el-table-column>
+        </el-table-column>
               <el-table-column label="操作" width="180" align="center" :show-overflow-tooltip="false">
                 <template slot-scope="{ row, $index }">
                   <div class="action-buttons">
@@ -1124,9 +1124,9 @@ export default {
     width: 100%;
     height: 100%;
     margin: 0;
-    padding: 0;
-  }
-  
+  padding: 0;
+}
+
   // text类型按钮样式
   .el-table__body .el-button--text {
     color: var(--color-primary);