|
@@ -1,153 +1,1514 @@
|
|
|
-<template>
|
|
|
|
|
- <div class="app-container">
|
|
|
|
|
- <div class="version-card">
|
|
|
|
|
- <el-card class="box-card" shadow="always">
|
|
|
|
|
- <div slot="header" class="clearfix">
|
|
|
|
|
- <span>服务器版本</span>
|
|
|
|
|
- <el-button style="float: right; padding: 3px 5px" type="success">版本导出</el-button>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="text-item" v-for="item in versionData">
|
|
|
|
|
- <el-row style="height: 100%; display: flex;align-items: center; ">
|
|
|
|
|
- <el-col :span="6">
|
|
|
|
|
- <div class="grid-content">{{ item.name }}</div>
|
|
|
|
|
- </el-col>
|
|
|
|
|
- <el-col :span="16">
|
|
|
|
|
- <div class="grid-content">{{ item.value }}</div>
|
|
|
|
|
- </el-col>
|
|
|
|
|
- <el-col :span="2">
|
|
|
|
|
- <div class="grid-content"><el-button style="float: right; padding: 3px 0" type="text">卸载</el-button></div>
|
|
|
|
|
- </el-col>
|
|
|
|
|
- </el-row>
|
|
|
|
|
- </div>
|
|
|
|
|
- </el-card>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div style="margin-top: 30px;position: relative;">
|
|
|
|
|
- <h4>更新版本</h4>
|
|
|
|
|
- <div style="position: absolute;right: 0;top: 0;">
|
|
|
|
|
- <el-row>
|
|
|
|
|
- <el-button type="info" size="mini" @click="install">全部安装</el-button>
|
|
|
|
|
- </el-row>
|
|
|
|
|
- </div>
|
|
|
|
|
- <el-table :data="softData" border style="width: 100%">
|
|
|
|
|
- <el-table-column type="index" width="50" label="序号" align="center"></el-table-column>
|
|
|
|
|
- <el-table-column prop="name" label="文件名" width="280" align="center">
|
|
|
|
|
- </el-table-column>
|
|
|
|
|
- <el-table-column prop="address" label="编辑">
|
|
|
|
|
- </el-table-column>
|
|
|
|
|
- </el-table>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div style="margin-top: 20px;">
|
|
|
|
|
- <IndexHand :fileType="['deb', 'tar', 'zip']" :fileSize="100" @input="fileListBack"></IndexHand>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
-</template>
|
|
|
|
|
-
|
|
|
|
|
-<script>
|
|
|
|
|
-import IndexHand from "@/components/FileUpload/index-hand";
|
|
|
|
|
-
|
|
|
|
|
-export default {
|
|
|
|
|
- components: { IndexHand },
|
|
|
|
|
- data() {
|
|
|
|
|
- return {
|
|
|
|
|
- versionData: [
|
|
|
|
|
- {
|
|
|
|
|
- name: 'leador-data-analyser',
|
|
|
|
|
- value: '192.168.10.10'
|
|
|
|
|
- },
|
|
|
|
|
- {
|
|
|
|
|
- name: '激光网卡',
|
|
|
|
|
- value: '0.3.1'
|
|
|
|
|
- },
|
|
|
|
|
- {
|
|
|
|
|
- name: 'leador-robot-config-web',
|
|
|
|
|
- value: '0.7.133'
|
|
|
|
|
- },
|
|
|
|
|
- {
|
|
|
|
|
- name: 'ros-kinetic-leador-auto-pursuit-local-planner',
|
|
|
|
|
- value: '0.0.1'
|
|
|
|
|
- },
|
|
|
|
|
- {
|
|
|
|
|
- name: 'ros-kinetic-leador-en-control-msgs',
|
|
|
|
|
- value: '1.0.0'
|
|
|
|
|
- },
|
|
|
|
|
- {
|
|
|
|
|
- name: 'ros-kinetic-leador-ld-sensor-msgs',
|
|
|
|
|
- value: '2.0.1'
|
|
|
|
|
- }
|
|
|
|
|
- ],
|
|
|
|
|
- softData: []
|
|
|
|
|
- }
|
|
|
|
|
- },
|
|
|
|
|
- methods: {
|
|
|
|
|
- fileListBack(data) {
|
|
|
|
|
- const fileName = data.split('/').pop(); // 获取文件名
|
|
|
|
|
- let file = { name: fileName };
|
|
|
|
|
- // 检查 softData 中是否已经存在该文件名
|
|
|
|
|
- const index = this.softData.findIndex(item => item.name === fileName);
|
|
|
|
|
- if (index === -1) {
|
|
|
|
|
- // 如果不存在,则追加
|
|
|
|
|
- this.softData.push(file);
|
|
|
|
|
- } else {
|
|
|
|
|
- // 如果存在,则删除该文件
|
|
|
|
|
- this.softData.splice(index, 1);
|
|
|
|
|
- }
|
|
|
|
|
- },
|
|
|
|
|
- // 全部安装
|
|
|
|
|
- install() {
|
|
|
|
|
- if (this.softData.length < 1) {
|
|
|
|
|
- this.$message({
|
|
|
|
|
- message: '请先上传对应文件!',
|
|
|
|
|
- type: 'warning'
|
|
|
|
|
- });
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
- this.$confirm('安装已上传的所有文件包?', '提示', {
|
|
|
|
|
- confirmButtonText: '确定',
|
|
|
|
|
- cancelButtonText: '取消',
|
|
|
|
|
- type: 'warning'
|
|
|
|
|
- }).then(() => {
|
|
|
|
|
- this.$message({
|
|
|
|
|
- type: 'success',
|
|
|
|
|
- message: '已开始安装!'
|
|
|
|
|
- });
|
|
|
|
|
- }).catch(() => {});
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-</script>
|
|
|
|
|
-
|
|
|
|
|
-<style scoped>
|
|
|
|
|
-::v-deep .version-card .el-card {
|
|
|
|
|
- border-radius: 8px;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-::v-deep .version-card .el-card.is-always-shadow,
|
|
|
|
|
-.el-card.is-hover-shadow:focus,
|
|
|
|
|
-.el-card.is-hover-shadow:hover {
|
|
|
|
|
- box-shadow: 0 2px 12px 0 rgba(161, 161, 161, 0.1);
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-::v-deep .version-card .el-card__header {
|
|
|
|
|
- padding: 12px 20px
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-::v-deep .version-card .el-card__body,
|
|
|
|
|
-.el-main {
|
|
|
|
|
- padding: 0;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.text-item {
|
|
|
|
|
- width: 100%;
|
|
|
|
|
- height: 50px;
|
|
|
|
|
- border-bottom: 1px solid #EBEEF5;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.grid-content {
|
|
|
|
|
- padding: 0 20px;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-::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="version-management">
|
|
|
|
|
+ <!-- 页面头部 -->
|
|
|
|
|
+ <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 class="search-section">
|
|
|
|
|
+ <el-input
|
|
|
|
|
+ v-model="searchQuery"
|
|
|
|
|
+ placeholder="搜索已安装版本..."
|
|
|
|
|
+ clearable
|
|
|
|
|
+ prefix-icon="el-icon-search"
|
|
|
|
|
+ class="search-input"
|
|
|
|
|
+ @input="handleSearch"
|
|
|
|
|
+ />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 主内容区域 -->
|
|
|
|
|
+ <div class="page-content">
|
|
|
|
|
+ <!-- 车端已安装版本卡片 -->
|
|
|
|
|
+ <div class="version-card">
|
|
|
|
|
+ <div class="card-header">
|
|
|
|
|
+ <h2 class="card-title">车端已安装版本</h2>
|
|
|
|
|
+ <p class="card-desc">当前车载系统已安装的软件组件与版本</p>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="card-content">
|
|
|
|
|
+ <div class="table-wrapper">
|
|
|
|
|
+ <el-table
|
|
|
|
|
+ :data="installedVersions"
|
|
|
|
|
+ v-loading="loadingInstalled"
|
|
|
|
|
+ border
|
|
|
|
|
+ stripe
|
|
|
|
|
+ style="width: 100%"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-table-column type="index" width="100" label="序号" align="center" />
|
|
|
|
|
+ <el-table-column prop="name" label="组件/软件" min-width="220" show-overflow-tooltip />
|
|
|
|
|
+ <el-table-column prop="version" label="当前版本" width="140" align="center" />
|
|
|
|
|
+ <el-table-column prop="updateTime" label="更新时间" width="200" align="center" show-overflow-tooltip />
|
|
|
|
|
+ <el-table-column label="操作" width="100" align="center">
|
|
|
|
|
+ <template slot-scope="{ row }">
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ type="text"
|
|
|
|
|
+ size="mini"
|
|
|
|
|
+ @click="handleUninstall(row)"
|
|
|
|
|
+ :loading="row.uninstalling"
|
|
|
|
|
+ >
|
|
|
|
|
+ 卸载
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ </el-table>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 分页 -->
|
|
|
|
|
+ <div class="pagination-wrapper">
|
|
|
|
|
+ <el-pagination
|
|
|
|
|
+ @size-change="handleSizeChange"
|
|
|
|
|
+ @current-change="handleCurrentChange"
|
|
|
|
|
+ :current-page="pagination.pageNum"
|
|
|
|
|
+ :page-sizes="[10, 20, 50, 100]"
|
|
|
|
|
+ :page-size="pagination.pageSize"
|
|
|
|
|
+ layout="total, sizes, prev, pager, next, jumper"
|
|
|
|
|
+ :total="pagination.total"
|
|
|
|
|
+ />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 待安装列表卡片 -->
|
|
|
|
|
+ <div class="version-card">
|
|
|
|
|
+ <div class="card-header">
|
|
|
|
|
+ <h2 class="card-title">待安装列表</h2>
|
|
|
|
|
+ <p class="card-desc">管理上传的安装包文件,支持 .deb / .tar / .zip 格式</p>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="card-content">
|
|
|
|
|
+ <!-- 工具栏 -->
|
|
|
|
|
+ <div class="install-toolbar">
|
|
|
|
|
+ <div class="toolbar-actions">
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ type="success"
|
|
|
|
|
+ icon="el-icon-upload2"
|
|
|
|
|
+ size="small"
|
|
|
|
|
+ @click="showUploadDialog"
|
|
|
|
|
+ :disabled="installing"
|
|
|
|
|
+ aria-label="上传安装包"
|
|
|
|
|
+ >
|
|
|
|
|
+ 上传安装包
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ type="primary"
|
|
|
|
|
+ icon="el-icon-download"
|
|
|
|
|
+ size="small"
|
|
|
|
|
+ @click="installAll"
|
|
|
|
|
+ :disabled="!installTasks.length || installing"
|
|
|
|
|
+ :loading="installing"
|
|
|
|
|
+ aria-label="批量安装所有文件"
|
|
|
|
|
+ >
|
|
|
|
|
+ 全部安装
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ size="small"
|
|
|
|
|
+ type="danger"
|
|
|
|
|
+ icon="el-icon-delete"
|
|
|
|
|
+ plain
|
|
|
|
|
+ @click="clearList"
|
|
|
|
|
+ :disabled="installing"
|
|
|
|
|
+ aria-label="清空待安装列表"
|
|
|
|
|
+ >
|
|
|
|
|
+ 清空列表
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 表格区域 -->
|
|
|
|
|
+ <div class="table-container">
|
|
|
|
|
+ <div v-if="installTasks.length > 0" class="table-wrapper">
|
|
|
|
|
+ <el-table
|
|
|
|
|
+ :data="installTasks"
|
|
|
|
|
+ height="420"
|
|
|
|
|
+ border
|
|
|
|
|
+ stripe
|
|
|
|
|
+ style="width: 100%"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-table-column type="index" width="100" label="序号" align="center" />
|
|
|
|
|
+ <el-table-column prop="fileName" label="文件名" min-width="300" show-overflow-tooltip>
|
|
|
|
|
+ <template slot-scope="{ row }">
|
|
|
|
|
+ <div class="file-name-cell">{{ row.fileName }}</div>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="大小" width="130" align="center">
|
|
|
|
|
+ <template slot-scope="{ row }">
|
|
|
|
|
+ {{ formatFileSize(row.size) }}
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="类型" width="90" align="center">
|
|
|
|
|
+ <template slot-scope="{ row }">
|
|
|
|
|
+ {{ getFileType(row.fileName) }}
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="状态" width="110" align="center">
|
|
|
|
|
+ <template slot-scope="{ row }">
|
|
|
|
|
+ <el-tag
|
|
|
|
|
+ :type="getStatusType(row.status)"
|
|
|
|
|
+ size="mini"
|
|
|
|
|
+ >
|
|
|
|
|
+ {{ row.status }}
|
|
|
|
|
+ </el-tag>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="操作" width="180" align="center" :show-overflow-tooltip="false">
|
|
|
|
|
+ <template slot-scope="{ row, $index }">
|
|
|
|
|
+ <div class="action-buttons">
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ type="text"
|
|
|
|
|
+ size="mini"
|
|
|
|
|
+ @click="handleInstall(row, $index)"
|
|
|
|
|
+ :disabled="installing"
|
|
|
|
|
+ :aria-label="`${row.status === '失败' ? '重试安装' : '安装'} ${row.fileName}`"
|
|
|
|
|
+ >
|
|
|
|
|
+ {{ row.status === '失败' ? '重试' : '安装' }}
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ type="text"
|
|
|
|
|
+ size="mini"
|
|
|
|
|
+ @click="handleDelete(row, $index)"
|
|
|
|
|
+ :disabled="installing"
|
|
|
|
|
+ :aria-label="`删除 ${row.fileName}`"
|
|
|
|
|
+ >
|
|
|
|
|
+ 删除
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ </el-table>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 空状态 -->
|
|
|
|
|
+ <div v-else class="empty-container">
|
|
|
|
|
+ <el-empty
|
|
|
|
|
+ description="暂无安装文件"
|
|
|
|
|
+ :image-size="120"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ type="primary"
|
|
|
|
|
+ @click="showUploadDialog"
|
|
|
|
|
+ :disabled="installing"
|
|
|
|
|
+ >
|
|
|
|
|
+ 上传安装包
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ </el-empty>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 上传弹框 -->
|
|
|
|
|
+ <el-dialog
|
|
|
|
|
+ title="上传安装包"
|
|
|
|
|
+ :visible.sync="uploadDialogVisible"
|
|
|
|
|
+ width="600px"
|
|
|
|
|
+ :close-on-click-modal="false"
|
|
|
|
|
+ :close-on-press-escape="false"
|
|
|
|
|
+ custom-class="upload-dialog"
|
|
|
|
|
+ >
|
|
|
|
|
+ <div class="upload-dialog-content">
|
|
|
|
|
+ <div class="upload-area">
|
|
|
|
|
+ <div class="upload-container">
|
|
|
|
|
+ <el-upload
|
|
|
|
|
+ ref="dialogUpload"
|
|
|
|
|
+ :action="uploadAction"
|
|
|
|
|
+ :multiple="true"
|
|
|
|
|
+ :drag="true"
|
|
|
|
|
+ :accept="'.deb,.tar,.zip'"
|
|
|
|
|
+ :before-upload="beforeUpload"
|
|
|
|
|
+ :on-progress="onUploadProgress"
|
|
|
|
|
+ :on-success="onUploadSuccess"
|
|
|
|
|
+ :on-error="onUploadError"
|
|
|
|
|
+ :show-file-list="false"
|
|
|
|
|
+ :disabled="installing"
|
|
|
|
|
+ class="dialog-upload-dragger"
|
|
|
|
|
+ >
|
|
|
|
|
+ <i class="el-icon-upload"></i>
|
|
|
|
|
+ <div class="el-upload__text">
|
|
|
|
|
+ 将文件拖拽到此处,或<em>点击选择文件</em>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </el-upload>
|
|
|
|
|
+ <div class="upload-format-hint">
|
|
|
|
|
+ 支持 .deb、.tar、.zip 格式文件,单个文件不超过 100MB,可同时选择多个文件
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 上传进度显示 -->
|
|
|
|
|
+ <div v-if="uploadingFiles.length > 0" class="upload-progress">
|
|
|
|
|
+ <h4>上传进度</h4>
|
|
|
|
|
+ <div
|
|
|
|
|
+ v-for="file in uploadingFiles"
|
|
|
|
|
+ :key="file.uid"
|
|
|
|
|
+ class="upload-item"
|
|
|
|
|
+ >
|
|
|
|
|
+ <div class="upload-item-info">
|
|
|
|
|
+ <span class="file-name">{{ file.name }}</span>
|
|
|
|
|
+ <span class="file-size">{{ formatFileSize(file.size) }}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <el-progress
|
|
|
|
|
+ :percentage="file.percentage"
|
|
|
|
|
+ :status="file.status"
|
|
|
|
|
+ :stroke-width="6"
|
|
|
|
|
+ />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div slot="footer" class="dialog-footer">
|
|
|
|
|
+ <el-button plain @click="closeUploadDialog">取消</el-button>
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ type="primary"
|
|
|
|
|
+ @click="closeUploadDialog"
|
|
|
|
|
+ :disabled="uploadingFiles.some(f => f.status === 'uploading')"
|
|
|
|
|
+ >
|
|
|
|
|
+ 完成
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </el-dialog>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+</template>
|
|
|
|
|
+
|
|
|
|
|
+<script>
|
|
|
|
|
+import { apiWithFallback } from '@/api/devops/version'
|
|
|
|
|
+
|
|
|
|
|
+export default {
|
|
|
|
|
+ name: 'VersionManagement',
|
|
|
|
|
+
|
|
|
|
|
+ data() {
|
|
|
|
|
+ return {
|
|
|
|
|
+ // API 待接入 - 当前使用Mock API作为兜底
|
|
|
|
|
+ uploadAction: '/api/devops/versions/upload',
|
|
|
|
|
+
|
|
|
|
|
+ // 搜索相关
|
|
|
|
|
+ searchQuery: '',
|
|
|
|
|
+ searchTimer: null,
|
|
|
|
|
+
|
|
|
|
|
+ // 已安装版本列表
|
|
|
|
|
+ installedVersions: [],
|
|
|
|
|
+ loadingInstalled: false,
|
|
|
|
|
+ pagination: {
|
|
|
|
|
+ pageNum: 1,
|
|
|
|
|
+ pageSize: 10,
|
|
|
|
|
+ total: 0
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ // 待安装列表
|
|
|
|
|
+ installTasks: [],
|
|
|
|
|
+ installing: false,
|
|
|
|
|
+
|
|
|
|
|
+ // 上传弹框
|
|
|
|
|
+ uploadDialogVisible: false,
|
|
|
|
|
+ uploadingFiles: [],
|
|
|
|
|
+
|
|
|
|
|
+ // 允许的文件类型和大小限制
|
|
|
|
|
+ allowedTypes: ['.deb', '.tar', '.zip'],
|
|
|
|
|
+ maxFileSize: 100 * 1024 * 1024 // 100MB
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ async created() {
|
|
|
|
|
+ await this.loadInstalledVersions()
|
|
|
|
|
+ await this.loadUploadList()
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ methods: {
|
|
|
|
|
+ // 加载已安装版本列表
|
|
|
|
|
+ async loadInstalledVersions() {
|
|
|
|
|
+ this.loadingInstalled = true
|
|
|
|
|
+ try {
|
|
|
|
|
+ const params = {
|
|
|
|
|
+ pageNum: this.pagination.pageNum,
|
|
|
|
|
+ pageSize: this.pagination.pageSize,
|
|
|
|
|
+ search: this.searchQuery
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const response = await apiWithFallback.getInstalledVersions(params)
|
|
|
|
|
+ if (response.code === 200) {
|
|
|
|
|
+ this.installedVersions = response.data.list || []
|
|
|
|
|
+ this.pagination.total = response.data.total || 0
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ this.$message.error('加载已安装版本失败: ' + error.message)
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ this.loadingInstalled = false
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ // 加载上传文件列表
|
|
|
|
|
+ async loadUploadList() {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const response = await apiWithFallback.getUploadList()
|
|
|
|
|
+ if (response.code === 200) {
|
|
|
|
|
+ this.installTasks = response.data.list || []
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ this.$message.error('加载上传列表失败: ' + error.message)
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ // 搜索处理(防抖)
|
|
|
|
|
+ handleSearch() {
|
|
|
|
|
+ if (this.searchTimer) {
|
|
|
|
|
+ clearTimeout(this.searchTimer)
|
|
|
|
|
+ }
|
|
|
|
|
+ this.searchTimer = setTimeout(() => {
|
|
|
|
|
+ this.pagination.pageNum = 1
|
|
|
|
|
+ this.loadInstalledVersions()
|
|
|
|
|
+ }, 300)
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ // 分页处理
|
|
|
|
|
+ handleSizeChange(val) {
|
|
|
|
|
+ this.pagination.pageSize = val
|
|
|
|
|
+ this.pagination.pageNum = 1
|
|
|
|
|
+ this.loadInstalledVersions()
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ handleCurrentChange(val) {
|
|
|
|
|
+ this.pagination.pageNum = val
|
|
|
|
|
+ this.loadInstalledVersions()
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ // 卸载软件包
|
|
|
|
|
+ async handleUninstall(row) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ await this.$confirm('确定要卸载 "' + row.name + '" 吗?', '提示', {
|
|
|
|
|
+ confirmButtonText: '确定',
|
|
|
|
|
+ cancelButtonText: '取消',
|
|
|
|
|
+ type: 'warning'
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ this.$set(row, 'uninstalling', true)
|
|
|
|
|
+
|
|
|
|
|
+ const response = await apiWithFallback.uninstallPackage({
|
|
|
|
|
+ id: row.id,
|
|
|
|
|
+ name: row.name
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ if (response.code === 200) {
|
|
|
|
|
+ this.$message.success('卸载成功')
|
|
|
|
|
+ await this.loadInstalledVersions()
|
|
|
|
|
+ } else {
|
|
|
|
|
+ throw new Error(response.message || '卸载失败')
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ if (error !== 'cancel') {
|
|
|
|
|
+ this.$message.error('卸载失败: ' + error.message)
|
|
|
|
|
+ }
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ this.$set(row, 'uninstalling', false)
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ // 上传前校验
|
|
|
|
|
+ beforeUpload(file) {
|
|
|
|
|
+ console.log('准备上传文件:', file.name)
|
|
|
|
|
+
|
|
|
|
|
+ // 检查文件类型
|
|
|
|
|
+ const fileExt = '.' + file.name.split('.').pop().toLowerCase()
|
|
|
|
|
+ if (!this.allowedTypes.includes(fileExt)) {
|
|
|
|
|
+ this.$message.error(`不支持的文件类型:${fileExt},只支持 ${this.allowedTypes.join('、')}`)
|
|
|
|
|
+ return false
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 检查文件大小
|
|
|
|
|
+ if (file.size > this.maxFileSize) {
|
|
|
|
|
+ this.$message.error(`文件大小不能超过 ${this.formatFileSize(this.maxFileSize)}`)
|
|
|
|
|
+ return false
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 添加到上传进度列表
|
|
|
|
|
+ const uploadingFile = {
|
|
|
|
|
+ uid: file.uid,
|
|
|
|
|
+ name: file.name,
|
|
|
|
|
+ size: file.size,
|
|
|
|
|
+ percentage: 0,
|
|
|
|
|
+ status: 'uploading'
|
|
|
|
|
+ }
|
|
|
|
|
+ this.uploadingFiles.push(uploadingFile)
|
|
|
|
|
+
|
|
|
|
|
+ return true
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ // 上传成功回调
|
|
|
|
|
+ onUploadSuccess(response, file) {
|
|
|
|
|
+ console.log('上传成功:', file.name)
|
|
|
|
|
+
|
|
|
|
|
+ // 更新上传进度中的文件状态
|
|
|
|
|
+ const uploadingFile = this.uploadingFiles.find(f => f.uid === file.uid)
|
|
|
|
|
+ if (uploadingFile) {
|
|
|
|
|
+ uploadingFile.status = 'success'
|
|
|
|
|
+ uploadingFile.percentage = 100
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const task = {
|
|
|
|
|
+ id: this.generateTaskId(),
|
|
|
|
|
+ fileName: file.name,
|
|
|
|
|
+ size: file.size,
|
|
|
|
|
+ status: '待安装',
|
|
|
|
|
+ fileId: response.data?.fileId || '',
|
|
|
|
|
+ url: response.data?.url || ''
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 如果弹框打开,添加到本地数据;否则重新加载列表
|
|
|
|
|
+ if (this.uploadDialogVisible) {
|
|
|
|
|
+ this.installTasks.push(task)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ this.$message.success(`${file.name} 上传成功`)
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ // 上传失败回调
|
|
|
|
|
+ onUploadError(error, file) {
|
|
|
|
|
+ console.error('上传失败:', file.name, error)
|
|
|
|
|
+
|
|
|
|
|
+ // 更新上传进度中的文件状态
|
|
|
|
|
+ const uploadingFile = this.uploadingFiles.find(f => f.uid === file.uid)
|
|
|
|
|
+ if (uploadingFile) {
|
|
|
|
|
+ uploadingFile.status = 'exception'
|
|
|
|
|
+ uploadingFile.percentage = 0
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ this.$message.error(`上传失败: ${file.name}`)
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ // 上传进度回调
|
|
|
|
|
+ onUploadProgress(event, file) {
|
|
|
|
|
+ const uploadingFile = this.uploadingFiles.find(f => f.uid === file.uid)
|
|
|
|
|
+ if (uploadingFile) {
|
|
|
|
|
+ uploadingFile.percentage = Math.round((event.loaded / event.total) * 100)
|
|
|
|
|
+ uploadingFile.status = 'uploading'
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ // 全部安装
|
|
|
|
|
+ async installAll() {
|
|
|
|
|
+ console.log('批量安装所有文件')
|
|
|
|
|
+ console.log('当前待安装列表:', this.installTasks)
|
|
|
|
|
+
|
|
|
|
|
+ if (!this.installTasks.length) {
|
|
|
|
|
+ this.$message.warning('没有待安装的文件')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const pendingTasks = this.installTasks.filter(task => task.status === '待安装' || task.status === '失败')
|
|
|
|
|
+ console.log('可安装的文件:', pendingTasks)
|
|
|
|
|
+
|
|
|
|
|
+ if (!pendingTasks.length) {
|
|
|
|
|
+ this.$message.warning('没有可安装的文件')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ await this.$confirm(`确定要安装 ${pendingTasks.length} 个文件吗?`, '批量安装', {
|
|
|
|
|
+ confirmButtonText: '确定',
|
|
|
|
|
+ cancelButtonText: '取消',
|
|
|
|
|
+ type: 'info'
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ this.installing = true
|
|
|
|
|
+ console.log('开始批量安装...')
|
|
|
|
|
+
|
|
|
|
|
+ // 批量安装,由于安装成功会移除行,需要从后往前处理
|
|
|
|
|
+ for (let i = this.installTasks.length - 1; i >= 0; i--) {
|
|
|
|
|
+ const task = this.installTasks[i]
|
|
|
|
|
+ if (task.status === '待安装' || task.status === '失败') {
|
|
|
|
|
+ console.log(`安装第 ${i+1} 个文件:`, task.fileName)
|
|
|
|
|
+ await this.handleInstall(task, i)
|
|
|
|
|
+ // 安装间隔,避免并发
|
|
|
|
|
+ await new Promise(resolve => setTimeout(resolve, 500))
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ this.$message.success('批量安装已启动')
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ if (error !== 'cancel') {
|
|
|
|
|
+ this.$message.error('批量安装出现错误')
|
|
|
|
|
+ console.error('批量安装错误:', error)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ console.log('用户取消批量安装')
|
|
|
|
|
+ }
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ this.installing = false
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ // 清空列表
|
|
|
|
|
+ clearList() {
|
|
|
|
|
+ console.log('清空待安装列表')
|
|
|
|
|
+ console.log('清空前文件数量:', this.installTasks.length)
|
|
|
|
|
+
|
|
|
|
|
+ if (this.installing) {
|
|
|
|
|
+ this.$message.warning('安装进行中,无法清空列表')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 清空所有文件
|
|
|
|
|
+ this.installTasks = []
|
|
|
|
|
+
|
|
|
|
|
+ console.log('清空后文件数量:', this.installTasks.length)
|
|
|
|
|
+ this.$message.success('列表已清空')
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ // 处理安装操作
|
|
|
|
|
+ async handleInstall(row, index) {
|
|
|
|
|
+ console.log('安装文件:', row)
|
|
|
|
|
+ console.log('文件ID:', row.id)
|
|
|
|
|
+ console.log('文件名:', row.fileName)
|
|
|
|
|
+ console.log('当前状态:', row.status)
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 开始安装提示
|
|
|
|
|
+ this.$message.info(`开始安装 ${row.fileName}`)
|
|
|
|
|
+ this.installing = true
|
|
|
|
|
+
|
|
|
|
|
+ // 模拟安装过程(2秒)
|
|
|
|
|
+ await new Promise((resolve) => {
|
|
|
|
|
+ setTimeout(() => {
|
|
|
|
|
+ const isSuccess = Math.random() > 0.2 // 80%成功率
|
|
|
|
|
+ resolve(isSuccess)
|
|
|
|
|
+ }, 2000)
|
|
|
|
|
+ }).then((isSuccess) => {
|
|
|
|
|
+ if (isSuccess) {
|
|
|
|
|
+ // 安装成功:从列表中移除该行
|
|
|
|
|
+ this.installTasks.splice(index, 1)
|
|
|
|
|
+ this.$message.success(`${row.fileName} 安装成功`)
|
|
|
|
|
+ // 刷新已安装版本列表
|
|
|
|
|
+ this.loadInstalledVersions()
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 安装失败:更新状态为失败
|
|
|
|
|
+ this.$set(this.installTasks, index, {
|
|
|
|
|
+ ...row,
|
|
|
|
|
+ status: '失败'
|
|
|
|
|
+ })
|
|
|
|
|
+ this.$message.error(`${row.fileName} 安装失败`)
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ // 安装异常:更新状态为失败
|
|
|
|
|
+ this.$set(this.installTasks, index, {
|
|
|
|
|
+ ...row,
|
|
|
|
|
+ status: '失败'
|
|
|
|
|
+ })
|
|
|
|
|
+ this.$message.error(`${row.fileName} 安装失败: ${error.message}`)
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ this.installing = false
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ // 处理删除操作
|
|
|
|
|
+ async handleDelete(row, index) {
|
|
|
|
|
+ console.log('删除文件:', row)
|
|
|
|
|
+ console.log('文件ID:', row.id)
|
|
|
|
|
+ console.log('文件名:', row.fileName)
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ await this.$confirm(`确定要删除 "${row.fileName}" 吗?`, '删除确认', {
|
|
|
|
|
+ confirmButtonText: '确定',
|
|
|
|
|
+ cancelButtonText: '取消',
|
|
|
|
|
+ type: 'warning'
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ // 删除操作
|
|
|
|
|
+ this.installTasks.splice(index, 1)
|
|
|
|
|
+ this.$message.success('删除成功')
|
|
|
|
|
+
|
|
|
|
|
+ console.log('文件已删除,剩余文件数量:', this.installTasks.length)
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.log('用户取消删除')
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ // 显示上传弹框
|
|
|
|
|
+ showUploadDialog() {
|
|
|
|
|
+ console.log('显示上传弹框')
|
|
|
|
|
+ this.uploadDialogVisible = true
|
|
|
|
|
+ this.uploadingFiles = []
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ // 关闭上传弹框
|
|
|
|
|
+ closeUploadDialog() {
|
|
|
|
|
+ console.log('关闭上传弹框')
|
|
|
|
|
+ this.uploadDialogVisible = false
|
|
|
|
|
+ this.uploadingFiles = []
|
|
|
|
|
+
|
|
|
|
|
+ // 重新加载待安装列表
|
|
|
|
|
+ this.loadUploadList()
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ // 工具方法
|
|
|
|
|
+ generateTaskId() {
|
|
|
|
|
+ return 'task_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9)
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ formatFileSize(bytes) {
|
|
|
|
|
+ if (bytes === 0) 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]
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ getStatusType(status) {
|
|
|
|
|
+ switch (status) {
|
|
|
|
|
+ case '待安装': return 'info'
|
|
|
|
|
+ case '失败': return 'danger'
|
|
|
|
|
+ default: return 'info'
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ getFileType(filename) {
|
|
|
|
|
+ const ext = filename.split('.').pop().toLowerCase()
|
|
|
|
|
+ switch (ext) {
|
|
|
|
|
+ case 'deb': return 'DEB'
|
|
|
|
|
+ case 'tar': return 'TAR'
|
|
|
|
|
+ case 'zip': return 'ZIP'
|
|
|
|
|
+ default: return ext.toUpperCase()
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+</script>
|
|
|
|
|
+
|
|
|
|
|
+<style lang="scss" scoped>
|
|
|
|
|
+.version-management {
|
|
|
|
|
+ min-height: 100vh;
|
|
|
|
|
+ background: var(--color-bg-secondary);
|
|
|
|
|
+ padding-bottom: var(--spacing-6);
|
|
|
|
|
+
|
|
|
|
|
+ .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 0 var(--spacing-4) 0;
|
|
|
|
|
+ line-height: var(--line-height-relaxed);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .search-section {
|
|
|
|
|
+ max-width: 1200px;
|
|
|
|
|
+ margin: 0 auto;
|
|
|
|
|
+
|
|
|
|
|
+ .search-input {
|
|
|
|
|
+ max-width: 400px;
|
|
|
|
|
+
|
|
|
|
|
+ ::v-deep .el-input__inner {
|
|
|
|
|
+ border-radius: var(--radius-lg);
|
|
|
|
|
+ border: 1px solid var(--color-border-primary);
|
|
|
|
|
+ background: var(--color-bg-card);
|
|
|
|
|
+ transition: all var(--duration-200) var(--ease-out);
|
|
|
|
|
+
|
|
|
|
|
+ &:focus {
|
|
|
|
|
+ border-color: var(--color-primary);
|
|
|
|
|
+ box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.15);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ ::v-deep .el-input__prefix {
|
|
|
|
|
+ color: var(--color-text-tertiary);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .page-content {
|
|
|
|
|
+ max-width: 1200px;
|
|
|
|
|
+ margin: 0 auto;
|
|
|
|
|
+ padding: 0 var(--spacing-6);
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ gap: var(--spacing-6);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 版本卡片 */
|
|
|
|
|
+.version-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;
|
|
|
|
|
+
|
|
|
|
|
+ &: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-4) var(--spacing-5) var(--spacing-5);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 工具栏样式 */
|
|
|
|
|
+.install-toolbar {
|
|
|
|
|
+ margin-bottom: var(--spacing-4);
|
|
|
|
|
+ padding-bottom: var(--spacing-3);
|
|
|
|
|
+ border-bottom: 1px solid var(--color-border-secondary);
|
|
|
|
|
+
|
|
|
|
|
+ .toolbar-actions {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ gap: var(--spacing-2);
|
|
|
|
|
+
|
|
|
|
|
+ .el-button {
|
|
|
|
|
+ &.el-button--success {
|
|
|
|
|
+ background-color: var(--color-success);
|
|
|
|
|
+ border-color: var(--color-success);
|
|
|
|
|
+
|
|
|
|
|
+ &:hover {
|
|
|
|
|
+ background-color: var(--color-success-light);
|
|
|
|
|
+ border-color: var(--color-success-light);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 分页样式 */
|
|
|
|
|
+.pagination-wrapper {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ margin-top: var(--spacing-4);
|
|
|
|
|
+ padding-top: var(--spacing-4);
|
|
|
|
|
+ border-top: 1px solid var(--color-border-secondary);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 上传弹框样式 */
|
|
|
|
|
+.upload-dialog {
|
|
|
|
|
+ ::v-deep .el-dialog__header {
|
|
|
|
|
+ padding: 20px 24px 16px;
|
|
|
|
|
+ border-bottom: 1px solid var(--color-border-secondary);
|
|
|
|
|
+
|
|
|
|
|
+ .el-dialog__title {
|
|
|
|
|
+ font-size: 18px;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ color: var(--color-text-primary);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ ::v-deep .el-dialog__body {
|
|
|
|
|
+ padding: 24px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ ::v-deep .el-dialog__footer {
|
|
|
|
|
+ padding: 16px 24px 20px;
|
|
|
|
|
+ border-top: 1px solid var(--color-border-secondary);
|
|
|
|
|
+ background: var(--color-bg-tertiary);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.upload-dialog-content {
|
|
|
|
|
+
|
|
|
|
|
+ .upload-area {
|
|
|
|
|
+ margin-top: 0;
|
|
|
|
|
+
|
|
|
|
|
+ .upload-container {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 16px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .dialog-upload-dragger {
|
|
|
|
|
+ ::v-deep .el-upload-dragger {
|
|
|
|
|
+ width: 400px;
|
|
|
|
|
+ height: 140px;
|
|
|
|
|
+ border: 2px dashed #B3D9FF;
|
|
|
|
|
+ border-radius: 8px;
|
|
|
|
|
+ background: #FFFFFF;
|
|
|
|
|
+ transition: all 0.3s ease;
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+
|
|
|
|
|
+ &:hover {
|
|
|
|
|
+ border-color: #409EFF;
|
|
|
|
|
+ box-shadow: 0 4px 12px rgba(64, 158, 255, 0.15);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .el-icon-upload {
|
|
|
|
|
+ font-size: 40px;
|
|
|
|
|
+ color: #409EFF;
|
|
|
|
|
+ margin-bottom: 12px;
|
|
|
|
|
+ transition: color 0.3s ease;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .el-upload__text {
|
|
|
|
|
+ color: #666;
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+ transition: color 0.3s ease;
|
|
|
|
|
+
|
|
|
|
|
+ em {
|
|
|
|
|
+ color: #409EFF;
|
|
|
|
|
+ font-style: normal;
|
|
|
|
|
+ font-weight: 500;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ &:hover {
|
|
|
|
|
+ .el-icon-upload {
|
|
|
|
|
+ color: #1890FF;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .el-upload__text {
|
|
|
|
|
+ color: #333;
|
|
|
|
|
+
|
|
|
|
|
+ em {
|
|
|
|
|
+ color: #1890FF;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .upload-format-hint {
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ color: #999;
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+ line-height: 1.5;
|
|
|
|
|
+ max-width: 400px;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .upload-progress {
|
|
|
|
|
+ margin-top: 20px;
|
|
|
|
|
+ padding-top: 16px;
|
|
|
|
|
+ border-top: 1px solid var(--color-border-secondary);
|
|
|
|
|
+
|
|
|
|
|
+ h4 {
|
|
|
|
|
+ margin: 0 0 12px 0;
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ color: var(--color-text-primary);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .upload-item {
|
|
|
|
|
+ margin-bottom: 12px;
|
|
|
|
|
+
|
|
|
|
|
+ &:last-child {
|
|
|
|
|
+ margin-bottom: 0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .upload-item-info {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ margin-bottom: 6px;
|
|
|
|
|
+
|
|
|
|
|
+ .file-name {
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+ color: var(--color-text-primary);
|
|
|
|
|
+ font-weight: 500;
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ text-overflow: ellipsis;
|
|
|
|
|
+ white-space: nowrap;
|
|
|
|
|
+ margin-right: 12px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .file-size {
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ color: #999;
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.dialog-footer {
|
|
|
|
|
+ text-align: right;
|
|
|
|
|
+
|
|
|
|
|
+ .el-button {
|
|
|
|
|
+ min-width: 72px;
|
|
|
|
|
+
|
|
|
|
|
+ &.el-button--default.is-plain {
|
|
|
|
|
+ border-color: #D9D9D9;
|
|
|
|
|
+ color: #666;
|
|
|
|
|
+
|
|
|
|
|
+ &:hover {
|
|
|
|
|
+ border-color: #409EFF;
|
|
|
|
|
+ color: #409EFF;
|
|
|
|
|
+ background: #F0F8FF;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ &.el-button--primary {
|
|
|
|
|
+ background: #409EFF;
|
|
|
|
|
+ border-color: #409EFF;
|
|
|
|
|
+
|
|
|
|
|
+ &:hover {
|
|
|
|
|
+ background: #66B1FF;
|
|
|
|
|
+ border-color: #66B1FF;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ &:disabled {
|
|
|
|
|
+ background: #C6E2FF;
|
|
|
|
|
+ border-color: #C6E2FF;
|
|
|
|
|
+ color: #FFFFFF;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .el-button + .el-button {
|
|
|
|
|
+ margin-left: 12px;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 表格容器和空状态 */
|
|
|
|
|
+.table-container {
|
|
|
|
|
+ position: relative;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 表格包装器 - 解决圆角显示问题 */
|
|
|
|
|
+.table-wrapper {
|
|
|
|
|
+ border-radius: var(--radius-lg);
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ border: 1px solid var(--color-border-secondary);
|
|
|
|
|
+ background: var(--color-bg-tertiary);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.table-wrapper .el-table {
|
|
|
|
|
+ border: none !important;
|
|
|
|
|
+ border-radius: 0;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.empty-container {
|
|
|
|
|
+ height: 420px;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ border: 1px solid var(--color-border-secondary);
|
|
|
|
|
+ border-radius: var(--radius-lg);
|
|
|
|
|
+ background: var(--color-bg-secondary);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.xt-table {
|
|
|
|
|
+ ::v-deep(.el-table__header-wrapper) {
|
|
|
|
|
+ position: sticky;
|
|
|
|
|
+ top: 0;
|
|
|
|
|
+ z-index: 1;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ ::v-deep(.el-table__body-wrapper) {
|
|
|
|
|
+ tr:hover > td {
|
|
|
|
|
+ background-color: #f8fafc !important;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.file-name-cell {
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ text-overflow: ellipsis;
|
|
|
|
|
+ white-space: nowrap;
|
|
|
|
|
+ font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+ color: var(--color-text-primary);
|
|
|
|
|
+ padding: 2px 0;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 表格样式优化 */
|
|
|
|
|
+::v-deep .el-table {
|
|
|
|
|
+ .el-table__header-wrapper th {
|
|
|
|
|
+ .cell {
|
|
|
|
|
+ color: var(--color-text-primary);
|
|
|
|
|
+ font-weight: var(--font-weight-semibold);
|
|
|
|
|
+ padding: var(--spacing-3) var(--spacing-4);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .el-table__body-wrapper tr:hover > td {
|
|
|
|
|
+ background-color: var(--color-bg-hover) !important;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .cell {
|
|
|
|
|
+ padding: var(--spacing-3) var(--spacing-4);
|
|
|
|
|
+ white-space: nowrap; // 防止内容换行
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ text-overflow: ellipsis;
|
|
|
|
|
+ line-height: 1.5;
|
|
|
|
|
+ min-height: 20px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 操作列不应用文本省略
|
|
|
|
|
+ .el-table__body td:last-child .cell {
|
|
|
|
|
+ overflow: visible;
|
|
|
|
|
+ text-overflow: unset;
|
|
|
|
|
+ white-space: nowrap;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ padding: var(--spacing-2) var(--spacing-2);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 特别处理文字内容列,确保显示完整
|
|
|
|
|
+ .el-table__body td {
|
|
|
|
|
+ .cell {
|
|
|
|
|
+ white-space: nowrap;
|
|
|
|
|
+ font-variant-numeric: tabular-nums; // 数字等宽,提升对齐效果
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 确保按钮列有足够的空间
|
|
|
|
|
+ .el-table__body .el-button {
|
|
|
|
|
+ margin: 0;
|
|
|
|
|
+ min-width: 42px;
|
|
|
|
|
+ padding: 4px 8px;
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .el-table__body .el-button + .el-button {
|
|
|
|
|
+ margin-left: 0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 操作按钮容器
|
|
|
|
|
+ .action-buttons {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 8px;
|
|
|
|
|
+ white-space: nowrap;
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ height: 100%;
|
|
|
|
|
+ margin: 0;
|
|
|
|
|
+ padding: 0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // text类型按钮样式
|
|
|
|
|
+ .el-table__body .el-button--text {
|
|
|
|
|
+ color: var(--color-primary);
|
|
|
|
|
+
|
|
|
|
|
+ &:hover {
|
|
|
|
|
+ color: var(--color-primary-light);
|
|
|
|
|
+ background-color: rgba(64, 158, 255, 0.1);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ &:disabled {
|
|
|
|
|
+ color: var(--color-text-quaternary);
|
|
|
|
|
+ background-color: transparent;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 表格包装器内的表格特殊样式 */
|
|
|
|
|
+.table-wrapper {
|
|
|
|
|
+ ::v-deep .el-table {
|
|
|
|
|
+ // 设置表格背景色与容器一致,避免白角露出
|
|
|
|
|
+ background-color: var(--color-bg-tertiary);
|
|
|
|
|
+
|
|
|
|
|
+ .el-table__header-wrapper th {
|
|
|
|
|
+ background-color: var(--color-bg-tertiary) !important;
|
|
|
|
|
+
|
|
|
|
|
+ // 移除顶部边框,使用容器边框
|
|
|
|
|
+ &:first-child {
|
|
|
|
|
+ border-top: none;
|
|
|
|
|
+ border-left: none;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ &:last-child {
|
|
|
|
|
+ border-top: none;
|
|
|
|
|
+ border-right: none;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 其他表头单元格也移除顶部边框
|
|
|
|
|
+ border-top: none;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 确保表格主体背景
|
|
|
|
|
+ .el-table__body-wrapper {
|
|
|
|
|
+ background: var(--color-bg-card);
|
|
|
|
|
+
|
|
|
|
|
+ tr:first-child td {
|
|
|
|
|
+ border-top: none;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ td:first-child {
|
|
|
|
|
+ border-left: none;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ td:last-child {
|
|
|
|
|
+ border-right: none;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 保留边框效果,但移除外边框
|
|
|
|
|
+ &.el-table--border {
|
|
|
|
|
+ // 移除表格外边框
|
|
|
|
|
+ border: none;
|
|
|
|
|
+
|
|
|
|
|
+ // 移除伪元素边框
|
|
|
|
|
+ &::after {
|
|
|
|
|
+ display: none;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ &::before {
|
|
|
|
|
+ display: none;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 保留内部竖线和横线
|
|
|
|
|
+ .el-table__header-wrapper th,
|
|
|
|
|
+ .el-table__body-wrapper td {
|
|
|
|
|
+ border-right: 1px solid var(--color-border-secondary);
|
|
|
|
|
+ border-bottom: 1px solid var(--color-border-secondary);
|
|
|
|
|
+
|
|
|
|
|
+ &:last-child {
|
|
|
|
|
+ border-right: none;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 最后一行移除底边框
|
|
|
|
|
+ .el-table__body-wrapper tr:last-child td {
|
|
|
|
|
+ border-bottom: none;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 状态标签样式 */
|
|
|
|
|
+::v-deep .el-tag {
|
|
|
|
|
+ border-radius: var(--radius-base);
|
|
|
|
|
+ font-weight: var(--font-weight-medium);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 进度条样式 */
|
|
|
|
|
+::v-deep .el-progress-bar {
|
|
|
|
|
+ .el-progress-bar__outer {
|
|
|
|
|
+ border-radius: var(--radius-base);
|
|
|
|
|
+ background-color: var(--color-bg-tertiary);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .el-progress-bar__inner {
|
|
|
|
|
+ border-radius: var(--radius-base);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 滑入动画
|
|
|
|
|
+@keyframes slideInUp {
|
|
|
|
|
+ from {
|
|
|
|
|
+ opacity: 0;
|
|
|
|
|
+ transform: translateY(20px);
|
|
|
|
|
+ }
|
|
|
|
|
+ to {
|
|
|
|
|
+ opacity: 1;
|
|
|
|
|
+ transform: translateY(0);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 响应式断点 */
|
|
|
|
|
+@media (max-width: 768px) {
|
|
|
|
|
+ .version-management {
|
|
|
|
|
+ .page-header {
|
|
|
|
|
+ padding: var(--spacing-4);
|
|
|
|
|
+ margin-bottom: var(--spacing-4);
|
|
|
|
|
+
|
|
|
|
|
+ .header-content {
|
|
|
|
|
+ .header-main {
|
|
|
|
|
+ .page-title {
|
|
|
|
|
+ font-size: var(--font-size-2xl);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .page-desc {
|
|
|
|
|
+ font-size: var(--font-size-sm);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .search-section {
|
|
|
|
|
+ .search-input {
|
|
|
|
|
+ max-width: 100%;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .page-content {
|
|
|
|
|
+ padding: 0 var(--spacing-4);
|
|
|
|
|
+ gap: var(--spacing-4);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .version-card,
|
|
|
|
|
+ .upload-card {
|
|
|
|
|
+ .card-header {
|
|
|
|
|
+ padding: var(--spacing-4);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .card-content {
|
|
|
|
|
+ padding: var(--spacing-3) var(--spacing-4) var(--spacing-4);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .install-toolbar {
|
|
|
|
|
+ .toolbar-actions {
|
|
|
|
|
+ flex-wrap: wrap;
|
|
|
|
|
+ gap: var(--spacing-2);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 移动端表格优化
|
|
|
|
|
+ ::v-deep .el-table {
|
|
|
|
|
+ .cell {
|
|
|
|
|
+ padding: var(--spacing-2) var(--spacing-2);
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .el-table__header-wrapper th .cell {
|
|
|
|
|
+ padding: var(--spacing-2) var(--spacing-2);
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .el-button {
|
|
|
|
|
+ min-width: 40px;
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ padding: 4px 8px;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 暗色主题适配
|
|
|
|
|
+html.dark {
|
|
|
|
|
+ .version-management {
|
|
|
|
|
+ .page-header {
|
|
|
|
|
+ background: var(--color-bg-tertiary);
|
|
|
|
|
+ box-shadow: 0 6px 18px rgba(0, 0, 0, 0.15);
|
|
|
|
|
+
|
|
|
|
|
+ .search-section {
|
|
|
|
|
+ .search-input {
|
|
|
|
|
+ ::v-deep .el-input__inner {
|
|
|
|
|
+ background: var(--color-bg-quaternary);
|
|
|
|
|
+ border-color: var(--color-border-tertiary);
|
|
|
|
|
+ color: var(--color-text-primary);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .version-card,
|
|
|
|
|
+ .upload-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);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .install-toolbar {
|
|
|
|
|
+ border-bottom-color: var(--color-border-tertiary);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .upload-dialog {
|
|
|
|
|
+ ::v-deep .el-dialog__header {
|
|
|
|
|
+ border-bottom-color: var(--color-border-tertiary);
|
|
|
|
|
+
|
|
|
|
|
+ .el-dialog__title {
|
|
|
|
|
+ color: var(--color-text-primary);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ ::v-deep .el-dialog__footer {
|
|
|
|
|
+ border-top-color: var(--color-border-tertiary);
|
|
|
|
|
+ background: var(--color-bg-quaternary);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .upload-dialog-content {
|
|
|
|
|
+ .upload-area {
|
|
|
|
|
+ .dialog-upload-dragger {
|
|
|
|
|
+ ::v-deep .el-upload-dragger {
|
|
|
|
|
+ border-color: var(--color-primary-dark);
|
|
|
|
|
+ background: var(--color-bg-card);
|
|
|
|
|
+
|
|
|
|
|
+ &:hover {
|
|
|
|
|
+ border-color: var(--color-primary);
|
|
|
|
|
+ box-shadow: 0 4px 12px rgba(64, 158, 255, 0.2);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .el-icon-upload {
|
|
|
|
|
+ color: var(--color-primary);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .el-upload__text {
|
|
|
|
|
+ color: var(--color-text-secondary);
|
|
|
|
|
+
|
|
|
|
|
+ em {
|
|
|
|
|
+ color: var(--color-primary);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ &:hover {
|
|
|
|
|
+ .el-icon-upload {
|
|
|
|
|
+ color: var(--color-primary-light);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .el-upload__text {
|
|
|
|
|
+ color: var(--color-text-primary);
|
|
|
|
|
+
|
|
|
|
|
+ em {
|
|
|
|
|
+ color: var(--color-primary-light);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .upload-format-hint {
|
|
|
|
|
+ color: var(--color-text-quaternary);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .upload-progress {
|
|
|
|
|
+ border-top-color: var(--color-border-tertiary);
|
|
|
|
|
+
|
|
|
|
|
+ h4 {
|
|
|
|
|
+ color: var(--color-text-primary);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .upload-item {
|
|
|
|
|
+ .upload-item-info {
|
|
|
|
|
+ .file-name {
|
|
|
|
|
+ color: var(--color-text-primary);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .file-size {
|
|
|
|
|
+ color: var(--color-text-quaternary);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .dialog-footer {
|
|
|
|
|
+ .el-button {
|
|
|
|
|
+ &.el-button--default.is-plain {
|
|
|
|
|
+ border-color: var(--color-border-tertiary);
|
|
|
|
|
+ color: var(--color-text-secondary);
|
|
|
|
|
+
|
|
|
|
|
+ &:hover {
|
|
|
|
|
+ border-color: var(--color-primary);
|
|
|
|
|
+ color: var(--color-primary);
|
|
|
|
|
+ background: var(--color-primary-dark);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .empty-container {
|
|
|
|
|
+ background: var(--color-bg-quaternary);
|
|
|
|
|
+ border-color: var(--color-border-tertiary);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .table-wrapper {
|
|
|
|
|
+ border-color: var(--color-border-tertiary);
|
|
|
|
|
+ background: var(--color-bg-quaternary);
|
|
|
|
|
+
|
|
|
|
|
+ ::v-deep .el-table {
|
|
|
|
|
+ background-color: var(--color-bg-quaternary);
|
|
|
|
|
+
|
|
|
|
|
+ .el-table__header-wrapper th {
|
|
|
|
|
+ background-color: var(--color-bg-quaternary) !important;
|
|
|
|
|
+ border-color: var(--color-border-tertiary);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .el-table__body-wrapper {
|
|
|
|
|
+ background: var(--color-bg-tertiary);
|
|
|
|
|
+
|
|
|
|
|
+ td {
|
|
|
|
|
+ border-color: var(--color-border-tertiary);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ &.el-table--border {
|
|
|
|
|
+ .el-table__header-wrapper th,
|
|
|
|
|
+ .el-table__body-wrapper td {
|
|
|
|
|
+ border-right-color: var(--color-border-tertiary);
|
|
|
|
|
+ border-bottom-color: var(--color-border-tertiary);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 暗色主题下的text按钮样式
|
|
|
|
|
+ .el-button--text {
|
|
|
|
|
+ color: var(--color-primary);
|
|
|
|
|
+
|
|
|
|
|
+ &:hover {
|
|
|
|
|
+ color: var(--color-primary-light);
|
|
|
|
|
+ background-color: rgba(64, 158, 255, 0.15);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ &:disabled {
|
|
|
|
|
+ color: var(--color-text-quaternary);
|
|
|
|
|
+ background-color: transparent;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+</style>
|
|
|
|
|
+
|