数据存储

This commit is contained in:
cwchen 2025-12-24 14:10:17 +08:00
parent 166c3e5d8c
commit 416a9102b0
6 changed files with 987 additions and 32 deletions

47
src/api/devops/network.js Normal file
View File

@ -0,0 +1,47 @@
import request from '@/utils/request'
// 系统运维->网络配置->查询TCP/IP配置
export function getTCPIPConfigAPI(params) {
return request({
url: '/smartCar/data/device/getTCPIPConfig',
method: 'GET',
params
})
}
// 系统运维->网络配置->更新TCP/IP配置
export function updateTCPIPConfigAPI(data) {
return request({
url: '/smartCar/data/device/updateTCPIPConfig',
method: 'POST',
data
})
}
// 系统运维->网络配置->查询网络状态
export function getNetworkStatusAPI(params) {
return request({
url: '/smartCar/data/device/getNetworkStatus',
method: 'GET',
params
})
}
// 系统运维->网络配置->查询网络配置
export function getNetworkConfigAPI(params) {
return request({
url: '/smartCar/data/device/getNetworkConfig',
method: 'GET',
params
})
}
// 系统运维->网络配置->更新网络配置
export function updateNetworkConfigAPI(data) {
return request({
url: '/smartCar/data/device/updateNetworkConfig',
method: 'POST',
data
})
}

20
src/api/devops/storage.js Normal file
View File

@ -0,0 +1,20 @@
import request from '@/utils/request'
// 系统运维->TF存储->查询存储信息
export function getStorageInfoAPI(params) {
return request({
url: '/smartCar/data/device/getStorageInfo',
method: 'GET',
params
})
}
// 系统运维->TF存储->格式化存储
export function formatStorageAPI(data) {
return request({
url: '/smartCar/data/device/formatStorage',
method: 'POST',
data
})
}

View File

@ -254,20 +254,22 @@ export default {
overflow-y: auto; overflow-y: auto;
background: linear-gradient(180deg, #f1f6ff 20%, #e5efff 100%); background: linear-gradient(180deg, #f1f6ff 20%, #e5efff 100%);
padding: 16px; padding: 16px;
display: flex;
flex-direction: column;
gap: 20px;
::v-deep .el-card__body { ::v-deep .el-card__body {
padding: 0; padding: 0;
} }
.info-card { .info-card {
margin-bottom: 16px;
background: #fff; background: #fff;
border-radius: 8px; border-radius: 8px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
height: calc((100vh - 84px - 16px * 2 - 20px) / 2);
&:last-child { display: flex;
margin-bottom: 0; flex-direction: column;
} flex-shrink: 0;
.card-header { .card-header {
display: flex; display: flex;
@ -303,6 +305,9 @@ export default {
::v-deep .el-card__body { ::v-deep .el-card__body {
padding: 16px 20px; padding: 16px 20px;
flex: 1;
overflow-y: auto;
min-height: 0;
} }
::v-deep .el-descriptions { ::v-deep .el-descriptions {
@ -343,6 +348,8 @@ export default {
} }
.operation-info-card { .operation-info-card {
margin-top: 15px;
::v-deep .el-descriptions { ::v-deep .el-descriptions {
.el-descriptions__table { .el-descriptions__table {
.el-descriptions__label { .el-descriptions__label {

View File

@ -0,0 +1,532 @@
<template>
<!-- 设备运维-网络配置 -->
<el-card class="network-container" v-loading="loading">
<el-card class="network-card">
<div slot="header" class="card-header">
<div class="tab-header">
<div class="tab-item" :class="{ active: activeTab === 'tcpip' }" @click="activeTab = 'tcpip'">TCP/IP</div>
<div class="tab-item" :class="{ active: activeTab === '4g5g' }" @click="activeTab = '4g5g'">4G/5G</div>
</div>
</div>
<!-- TCP/IP 标签页 -->
<div v-if="activeTab === 'tcpip'" class="tab-content">
<el-form :model="tcpipForm" :rules="tcpipRules" ref="tcpipForm" label-width="120px">
<el-form-item label="以太网接口">
<el-select v-model="tcpipForm.ethernetInterface" style="width: 400px">
<el-option label="eth0" value="eth0"></el-option>
</el-select>
</el-form-item>
<el-form-item label="启用DHCP">
<el-checkbox v-model="tcpipForm.enableDHCP" @change="handleDHCPChange"></el-checkbox>
</el-form-item>
<el-form-item label="IP地址" prop="ipAddress" required>
<el-input v-model="tcpipForm.ipAddress" placeholder="请输入IP地址" style="width: 400px" :disabled="tcpipForm.enableDHCP"></el-input>
</el-form-item>
<el-form-item label="子网掩码" prop="subnetMask" required>
<el-input v-model="tcpipForm.subnetMask" placeholder="请输入子网掩码" style="width: 400px" :disabled="tcpipForm.enableDHCP"></el-input>
</el-form-item>
<el-form-item label="默认网关">
<el-input v-model="tcpipForm.defaultGateway" placeholder="请输入默认网关" style="width: 400px" :disabled="tcpipForm.enableDHCP"></el-input>
</el-form-item>
<el-form-item label="DNS">
<div class="dns-list">
<div v-for="(dns, index) in tcpipForm.dnsList" :key="index" class="dns-item">
<el-input v-model="dns.value" placeholder="请输入DNS" style="width: 350px" :disabled="tcpipForm.enableDHCP"></el-input>
<el-button type="text" icon="el-icon-plus" class="dns-btn add-btn" @click="addDNS" :disabled="tcpipForm.enableDHCP"></el-button>
<el-button type="text" icon="el-icon-minus" class="dns-btn remove-btn" @click="removeDNS(index)" :disabled="tcpipForm.enableDHCP || tcpipForm.dnsList.length === 1"></el-button>
</div>
</div>
</el-form-item>
<el-form-item label="MAC地址">
<el-input v-model="tcpipForm.macAddress" style="width: 400px" disabled></el-input>
</el-form-item>
<el-form-item>
<el-button class="save-btn" @click="handleSaveTCPIP">保存</el-button>
</el-form-item>
</el-form>
</div>
<!-- 4G/5G 标签页 -->
<div v-if="activeTab === '4g5g'" class="tab-content">
<div class="network-layout">
<!-- 左侧状态参数 -->
<div class="status-section">
<div class="section-header">
<i class="el-icon-connection section-icon"></i>
<span class="section-title">状态参数</span>
</div>
<div class="status-list">
<div class="status-item">
<span class="status-label">模块状态</span>
<span class="status-value">{{ statusInfo.moduleStatus }}</span>
</div>
<div class="status-item">
<span class="status-label">信号强度</span>
<span class="status-value">{{ statusInfo.signalStrength }}</span>
</div>
<div class="status-item">
<span class="status-label">拨号获取的IP地址</span>
<span class="status-value">{{ statusInfo.dialupIp }}</span>
</div>
<div class="status-item">
<span class="status-label">当前网络模式</span>
<span class="status-value">{{ statusInfo.networkMode }}</span>
</div>
<div class="status-item">
<span class="status-label">当前网络运营商</span>
<span class="status-value">{{ statusInfo.operator }}</span>
</div>
<div class="status-item">
<span class="status-label">模块IMEI</span>
<span class="status-value">{{ statusInfo.imei }}</span>
</div>
</div>
</div>
<!-- 右侧配置参数 -->
<div class="config-section">
<div class="section-header">
<i class="el-icon-setting section-icon"></i>
<span class="section-title">配置参数</span>
</div>
<el-form :model="networkForm" :rules="networkRules" ref="networkForm" label-width="120px">
<el-form-item label="4G/5G">
<el-input v-model="networkForm.networkType" style="width: 400px" disabled></el-input>
</el-form-item>
<el-form-item label="启用拨号">
<el-checkbox v-model="networkForm.enableDialup"></el-checkbox>
</el-form-item>
<el-form-item label="APN" prop="apn">
<el-input v-model="networkForm.apn" placeholder="请输入APN" style="width: 400px"></el-input>
</el-form-item>
<el-form-item label="接入号" prop="accessNumber">
<el-input v-model="networkForm.accessNumber" placeholder="请输入接入号" style="width: 400px"></el-input>
</el-form-item>
<el-form-item label="拨号用户名" prop="dialupUsername">
<el-input v-model="networkForm.dialupUsername" placeholder="请输入拨号用户名" style="width: 400px"></el-input>
</el-form-item>
<el-form-item label="拨号密码" prop="dialupPassword">
<el-input v-model="networkForm.dialupPassword" type="password" placeholder="请输入拨号密码" style="width: 400px" show-password></el-input>
</el-form-item>
<el-form-item>
<el-button class="save-btn" @click="handleSaveNetwork">保存</el-button>
</el-form-item>
</el-form>
</div>
</div>
</div>
</el-card>
</el-card>
</template>
<script>
import { getTCPIPConfigAPI, updateTCPIPConfigAPI, getNetworkStatusAPI, getNetworkConfigAPI, updateNetworkConfigAPI } from '@/api/devops/network'
export default {
name: 'Network',
data() {
return {
loading: false,
activeTab: 'tcpip',
tcpipForm: {
ethernetInterface: 'eth0',
enableDHCP: false,
ipAddress: '',
subnetMask: '',
defaultGateway: '',
dnsList: [{ value: '' }],
macAddress: '18-F2-2C-32-DF-B6'
},
tcpipRules: {
ipAddress: [
{ required: true, message: 'IP地址不能为空', trigger: 'blur' }
],
subnetMask: [
{ required: true, message: '子网掩码不能为空', trigger: 'blur' }
]
},
statusInfo: {
moduleStatus: '拨号成功',
signalStrength: '96',
dialupIp: '10.52.34.103',
networkMode: 'LTE TDD',
operator: '中国移动',
imei: '862819047641246'
},
networkForm: {
networkType: '4G',
enableDialup: false,
apn: '',
accessNumber: '',
dialupUsername: '',
dialupPassword: ''
},
networkRules: {}
}
},
created() {
this.getTCPIPConfig()
this.getNetworkStatus()
this.getNetworkConfig()
},
methods: {
/** 获取TCP/IP配置 */
async getTCPIPConfig() {
try {
this.loading = true
const res = await getTCPIPConfigAPI()
if (res.code === 200 && res.data) {
const data = res.data
this.tcpipForm = {
ethernetInterface: data.ethernetInterface || 'eth0',
enableDHCP: data.enableDHCP || false,
ipAddress: data.ipAddress || '',
subnetMask: data.subnetMask || '',
defaultGateway: data.defaultGateway || '',
dnsList: data.dnsList && data.dnsList.length > 0 ? data.dnsList.map(dns => ({ value: dns })) : [{ value: '' }],
macAddress: data.macAddress || '18-F2-2C-32-DF-B6'
}
}
} catch (error) {
console.error('获取TCP/IP配置失败:', error)
} finally {
this.loading = false
}
},
/** 获取网络状态 */
async getNetworkStatus() {
try {
const res = await getNetworkStatusAPI()
if (res.code === 200 && res.data) {
this.statusInfo = {
moduleStatus: res.data.moduleStatus || '拨号成功',
signalStrength: res.data.signalStrength || '96',
dialupIp: res.data.dialupIp || '10.52.34.103',
networkMode: res.data.networkMode || 'LTE TDD',
operator: res.data.operator || '中国移动',
imei: res.data.imei || '862819047641246'
}
}
} catch (error) {
console.error('获取网络状态失败:', error)
}
},
/** 获取网络配置 */
async getNetworkConfig() {
try {
const res = await getNetworkConfigAPI()
if (res.code === 200 && res.data) {
const data = res.data
this.networkForm = {
networkType: data.networkType || '4G',
enableDialup: data.enableDialup || false,
apn: data.apn || '',
accessNumber: data.accessNumber || '',
dialupUsername: data.dialupUsername || '',
dialupPassword: data.dialupPassword || ''
}
}
} catch (error) {
console.error('获取网络配置失败:', error)
}
},
/** DHCP切换 */
handleDHCPChange() {
// DHCPIP
if (this.tcpipForm.enableDHCP) {
this.tcpipForm.ipAddress = ''
this.tcpipForm.subnetMask = ''
this.tcpipForm.defaultGateway = ''
}
},
/** 添加DNS */
addDNS() {
this.tcpipForm.dnsList.push({ value: '' })
},
/** 删除DNS */
removeDNS(index) {
if (this.tcpipForm.dnsList.length > 1) {
this.tcpipForm.dnsList.splice(index, 1)
}
},
/** 保存TCP/IP配置 */
async handleSaveTCPIP() {
try {
await this.$refs.tcpipForm.validate()
if (!this.tcpipForm.enableDHCP) {
if (!this.tcpipForm.ipAddress) {
this.$message.error('IP地址不能为空')
return
}
if (!this.tcpipForm.subnetMask) {
this.$message.error('子网掩码不能为空')
return
}
}
this.loading = true
const params = {
ethernetInterface: this.tcpipForm.ethernetInterface,
enableDHCP: this.tcpipForm.enableDHCP,
ipAddress: this.tcpipForm.ipAddress,
subnetMask: this.tcpipForm.subnetMask,
defaultGateway: this.tcpipForm.defaultGateway,
dnsList: this.tcpipForm.dnsList.map(item => item.value).filter(item => item),
macAddress: this.tcpipForm.macAddress
}
const res = await updateTCPIPConfigAPI(params)
if (res.code === 200) {
this.$message.success('保存成功')
} else {
this.$message.error(res.msg || '保存失败')
}
} catch (error) {
if (error.message !== '数据未填写完整') {
console.error('保存TCP/IP配置失败:', error)
this.$message.error('保存失败,请稍后重试')
}
} finally {
this.loading = false
}
},
/** 保存网络配置 */
async handleSaveNetwork() {
try {
this.loading = true
const params = {
networkType: this.networkForm.networkType,
enableDialup: this.networkForm.enableDialup,
apn: this.networkForm.apn,
accessNumber: this.networkForm.accessNumber,
dialupUsername: this.networkForm.dialupUsername,
dialupPassword: this.networkForm.dialupPassword
}
const res = await updateNetworkConfigAPI(params)
if (res.code === 200) {
this.$message.success('保存成功')
//
this.getNetworkStatus()
} else {
this.$message.error(res.msg || '保存失败')
}
} catch (error) {
console.error('保存网络配置失败:', error)
this.$message.error('保存失败,请稍后重试')
} finally {
this.loading = false
}
}
}
}
</script>
<style scoped lang="scss">
.network-container {
height: calc(100vh - 84px);
overflow-y: auto;
background: linear-gradient(180deg, #f1f6ff 20%, #e5efff 100%);
padding: 0;
.network-card {
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
height: calc(100vh - 130px);
display: flex;
flex-direction: column;
.card-header {
background: #fff;
padding: 0;
border-radius: 8px 8px 0 0;
.tab-header {
display: flex;
padding: 0;
.tab-item {
display: inline-block;
padding: 10px 20px;
font-size: 14px;
color: #606266;
cursor: pointer;
position: relative;
transition: all 0.3s;
&:hover {
color: #1f72ea;
}
&.active {
color: #1f72ea;
font-weight: 600;
&::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 2px;
background: #1f72ea;
}
}
}
}
}
::v-deep .el-card__body {
padding: 20px;
flex: 1;
overflow-y: auto;
min-height: 0;
}
.tab-content {
height: 100%;
display: flex;
flex-direction: column;
}
::v-deep .el-form-item {
margin-bottom: 18px;
&:last-child {
margin-bottom: 0;
}
.el-form-item__label {
font-weight: 500;
color: #606266;
}
}
.dns-list {
.dns-item {
display: flex;
align-items: center;
margin-bottom: 10px;
&:last-child {
margin-bottom: 0;
}
.dns-btn {
margin-left: 8px;
padding: 0;
width: 24px;
height: 24px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
&.add-btn {
background: #1f72ea;
color: #fff;
&:hover {
background: #4a8bff;
}
}
&.remove-btn {
background: #f56c6c;
color: #fff;
&:hover {
background: #f78989;
}
}
}
}
}
.network-layout {
display: flex;
gap: 20px;
margin-bottom: 16px;
flex: 1;
min-height: 0;
.status-section,
.config-section {
flex: 1;
display: flex;
flex-direction: column;
}
.section-header {
display: flex;
align-items: center;
margin-bottom: 16px;
padding-bottom: 12px;
border-bottom: 1px solid #ebeef5;
.section-icon {
font-size: 18px;
color: #1f72ea;
margin-right: 8px;
}
.section-title {
font-size: 16px;
font-weight: 600;
color: #333;
}
}
.status-list {
flex: 1;
overflow-y: auto;
.status-item {
display: flex;
align-items: center;
padding: 12px 0;
border-bottom: 1px solid #f5f7fa;
&:last-child {
border-bottom: none;
}
.status-label {
font-size: 14px;
color: #606266;
min-width: 140px;
}
.status-value {
font-size: 14px;
color: #303133;
background: #f5f7fa;
padding: 6px 12px;
border-radius: 4px;
flex: 1;
}
}
}
}
.save-btn {
width: 98px;
height: 36px;
background: #1f72ea;
box-shadow: 0px 4px 8px 0px rgba(51, 135, 255, 0.5);
border-radius: 4px;
color: #fff;
border: none;
font-size: 14px;
transition: all 0.3s;
&:hover {
background: #4a8bff;
box-shadow: 0px 6px 12px 0px rgba(51, 135, 255, 0.6);
}
}
}
}
</style>

View File

@ -1,16 +1,16 @@
<template> <template>
<!-- 设备运维-时间设置 --> <!-- 设备运维-时间设置 -->
<el-card class="time-setting-container" v-loading="loading"> <el-card class="time-setting-container" v-loading="loading">
<div class="current-time">
<span class="time-label">设备当前时间</span>
<span class="time-value">{{ currentTime }}</span>
</div>
<el-card class="setting-card"> <el-card class="setting-card">
<div slot="header" class="card-header"> <div slot="header" class="card-header">
<span class="card-title">时间设置</span> <span class="card-title">时间设置</span>
</div> </div>
<div class="current-time">
<span class="time-label">设备当前时间</span>
<span class="time-value">{{ currentTime }}</span>
</div>
<el-form :model="form" :rules="rules" ref="timeSettingForm" label-width="120px"> <el-form :model="form" :rules="rules" ref="timeSettingForm" label-width="120px">
<el-form-item label="校时模式" prop="calibrationMode"> <el-form-item label="校时模式" prop="calibrationMode">
<el-radio-group v-model="form.calibrationMode" @change="handleModeChange"> <el-radio-group v-model="form.calibrationMode" @change="handleModeChange">
@ -179,34 +179,14 @@ export default {
height: calc(100vh - 84px); height: calc(100vh - 84px);
overflow-y: auto; overflow-y: auto;
background: linear-gradient(180deg, #f1f6ff 20%, #e5efff 100%); background: linear-gradient(180deg, #f1f6ff 20%, #e5efff 100%);
padding: 0px;
.current-time {
margin-bottom: 10px;
padding: 8px 14px;
background: #fff;
border-radius: 4px;
box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.1);
.time-label {
font-size: 14px;
color: #606266;
font-weight: 500;
}
.time-value {
font-size: 14px;
color: #303133;
font-weight: 600;
margin-left: 8px;
}
}
.setting-card { .setting-card {
margin-bottom: 0; margin-bottom: 0;
background: #fff; background: #fff;
border-radius: 8px; border-radius: 8px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
height: calc(100vh - 180px); height: calc(100vh - 130px);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -225,9 +205,32 @@ export default {
} }
} }
.current-time {
margin-bottom: 16px;
padding: 8px 14px;
// background: #f5f7fa;
border-radius: 4px;
flex-shrink: 0;
.time-label {
font-size: 14px;
color: #606266;
font-weight: 500;
}
.time-value {
font-size: 14px;
color: #303133;
font-weight: 600;
margin-left: 8px;
}
}
::v-deep .el-card__body { ::v-deep .el-card__body {
padding: 14px 18px; padding: 14px 18px;
flex: 1; flex: 1;
display: flex;
flex-direction: column;
overflow-y: auto; overflow-y: auto;
} }

View File

@ -0,0 +1,346 @@
<template>
<!-- 设备运维-TF存储 -->
<el-card class="storage-container" v-loading="loading">
<!-- 存储信息卡片 -->
<el-card class="storage-card">
<div slot="header" class="card-header">
<div class="tab-header">
<div class="tab-item active">TF存储</div>
</div>
</div>
<div class="storage-info">
<div class="storage-text">
<span class="available">{{ availableSpace }}GB可用, {{ totalSpace }}GB</span>
</div>
<el-button class="format-btn" @click="handleFormat">格式化</el-button>
</div>
<!-- 进度条 -->
<div class="progress-container">
<div class="progress-bar">
<el-tooltip
v-if="usedPercentage > 0"
:content="`已用 容量:${usedSpace.toFixed(2)}GB(${usedPercentage.toFixed(2)}%)`"
placement="top"
effect="dark">
<div class="progress-used" :style="{ width: usedPercentage + '%' }"></div>
</el-tooltip>
<el-tooltip
v-if="freePercentage > 0"
:content="`空闲 容量:${availableSpace.toFixed(2)}GB(${freePercentage.toFixed(2)}%)`"
placement="top"
effect="dark">
<div class="progress-free" :style="{ width: freePercentage + '%' }"></div>
</el-tooltip>
</div>
</div>
<!-- 图例 -->
<div class="legend">
<div class="legend-item">
<span class="legend-color used-color"></span>
<span class="legend-label">已用</span>
</div>
<div class="legend-item">
<span class="legend-color free-color"></span>
<span class="legend-label">空闲</span>
</div>
</div>
</el-card>
<!-- 格式化确认对话框 -->
<el-dialog
title="格式化"
:visible.sync="formatDialogVisible"
width="400px"
:close-on-click-modal="false">
<div class="dialog-content">
<i class="el-icon-warning warning-icon"></i>
<span class="dialog-message">确定格式化当前固态硬盘吗?</span>
</div>
<div slot="footer" class="dialog-footer">
<el-button @click="formatDialogVisible = false">取消</el-button>
<el-button type="primary" @click="confirmFormat">确定</el-button>
</div>
</el-dialog>
</el-card>
</template>
<script>
import { getStorageInfoAPI, formatStorageAPI } from '@/api/devops/storage'
export default {
name: 'Storage',
data() {
return {
loading: false,
totalSpace: 128,
usedSpace: 100,
availableSpace: 28,
formatDialogVisible: false
}
},
computed: {
usedPercentage() {
if (this.totalSpace === 0) return 0
return Math.round((this.usedSpace / this.totalSpace) * 100)
},
freePercentage() {
return 100 - this.usedPercentage
}
},
created() {
this.getStorageInfo()
},
methods: {
/** 获取存储信息 */
async getStorageInfo() {
try {
this.loading = true
const res = await getStorageInfoAPI()
if (res.code === 200 && res.data) {
const data = res.data
this.totalSpace = data.totalSpace || 128
this.usedSpace = data.usedSpace || 100
this.availableSpace = data.availableSpace || 28
}
} catch (error) {
console.error('获取存储信息失败:', error)
} finally {
this.loading = false
}
},
/** 格式化操作 */
handleFormat() {
this.formatDialogVisible = true
},
/** 确认格式化 */
async confirmFormat() {
try {
this.loading = true
const res = await formatStorageAPI()
if (res.code === 200) {
this.$message.success('格式化成功')
this.formatDialogVisible = false
//
this.getStorageInfo()
} else {
this.$message.error(res.msg || '格式化失败')
}
} catch (error) {
console.error('格式化失败:', error)
this.$message.error('格式化失败,请稍后重试')
} finally {
this.loading = false
}
}
}
}
</script>
<style scoped lang="scss">
.storage-container {
height: calc(100vh - 84px);
overflow-y: auto;
background: linear-gradient(180deg, #f1f6ff 20%, #e5efff 100%);
padding: 0;
.storage-card {
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
height: calc(100vh - 130px);
display: flex;
flex-direction: column;
.card-header {
background: #fff;
padding: 0;
border-radius: 8px 8px 0 0;
.tab-header {
padding: 0;
margin: 0;
.tab-item {
display: inline-block;
padding: 10px 20px;
font-size: 14px;
color: #606266;
cursor: pointer;
position: relative;
&.active {
color: #1f72ea;
font-weight: 600;
&::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 2px;
background: #1f72ea;
}
}
}
}
}
::v-deep .el-card__body {
padding: 20px;
flex: 1;
display: flex;
flex-direction: column;
overflow-y: auto;
}
.storage-info {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
flex-shrink: 0;
.storage-text {
.available {
font-size: 14px;
color: #303133;
font-weight: 500;
}
}
.format-btn {
background: #f5f7fa;
border: 1px solid #dcdfe6;
color: #606266;
font-size: 14px;
padding: 8px 16px;
border-radius: 4px;
transition: all 0.3s;
&:hover {
background: #e4e7ed;
border-color: #c0c4cc;
color: #303133;
}
}
}
.progress-container {
margin-bottom: 16px;
flex-shrink: 0;
.progress-bar {
width: 100%;
height: 24px;
background: #f5f7fa;
border-radius: 12px;
overflow: hidden;
display: flex;
.progress-used {
background: #ff6b9d;
height: 100%;
transition: width 0.3s;
cursor: pointer;
&:hover {
opacity: 0.9;
}
}
.progress-free {
background: #1f72ea;
height: 100%;
transition: width 0.3s;
cursor: pointer;
&:hover {
opacity: 0.9;
}
}
}
}
.legend {
display: flex;
gap: 24px;
flex-shrink: 0;
.legend-item {
display: flex;
align-items: center;
gap: 8px;
.legend-color {
width: 16px;
height: 16px;
border-radius: 2px;
display: inline-block;
&.used-color {
background: #ff6b9d;
}
&.free-color {
background: #1f72ea;
}
}
.legend-label {
font-size: 14px;
color: #606266;
}
}
}
}
}
::v-deep .el-dialog {
.el-dialog__header {
padding: 16px 20px;
border-bottom: 1px solid #ebeef5;
.el-dialog__title {
font-size: 16px;
font-weight: 600;
color: #303133;
}
}
.el-dialog__body {
padding: 20px;
.dialog-content {
display: flex;
align-items: flex-start;
gap: 12px;
.warning-icon {
font-size: 24px;
color: #f56c6c;
margin-top: 2px;
}
.dialog-message {
font-size: 14px;
color: #606266;
line-height: 1.6;
}
}
}
.dialog-footer {
padding: 12px 20px;
border-top: 1px solid #ebeef5;
text-align: right;
.el-button {
margin-left: 10px;
}
}
}
</style>