代码修改
This commit is contained in:
parent
1b6239b4bc
commit
0f5690fadb
19
src/App.vue
19
src/App.vue
@ -1,8 +1,9 @@
|
|||||||
<!-- src/App.vue -->
|
|
||||||
<template>
|
<template>
|
||||||
<div id="app">
|
<div id="app">
|
||||||
<Header />
|
<Header />
|
||||||
|
<main class="main-content">
|
||||||
<router-view />
|
<router-view />
|
||||||
|
</main>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -19,12 +20,26 @@ import Header from '@/components/Header.vue'
|
|||||||
|
|
||||||
html, body {
|
html, body {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
overflow-x: hidden; /* 可选:防止水平滚动 */
|
||||||
}
|
}
|
||||||
|
|
||||||
#app {
|
#app {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
margin-top: 66px;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 假设 Header 高度是 66px */
|
||||||
|
header {
|
||||||
|
height: 66px;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-content {
|
||||||
|
padding-top: 30px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
@ -76,7 +76,8 @@ const menuItems: MenuItem[] = [
|
|||||||
name: '账号',
|
name: '账号',
|
||||||
icon: TeamOutlined,
|
icon: TeamOutlined,
|
||||||
children: [
|
children: [
|
||||||
{ path: '/controlPanel/account/profile', name: '个人资料' },
|
{ path: '/accountSecurity', name: '账号安全' },
|
||||||
|
{ path: '/accountHistory', name: '访问记录' },
|
||||||
{ path: '/controlPanel/account/security', name: '安全设置' }
|
{ path: '/controlPanel/account/security', name: '安全设置' }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -102,7 +103,8 @@ const handleMenuClick = ({ key }: { key: string }) => {
|
|||||||
height: 100vh;
|
height: 100vh;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
box-shadow: 2px 0 4px rgba(0, 0, 0, 0.1);
|
box-shadow: 2px 0 4px rgba(0, 0, 0, 0.1);
|
||||||
overflow-y: auto;
|
padding-top:30px;
|
||||||
|
/* overflow-y: auto; */
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu-item-content {
|
.menu-item-content {
|
||||||
|
|||||||
@ -4,6 +4,10 @@ import Layout from '@/components/Layout.vue'
|
|||||||
|
|
||||||
const HomeView = () => import('@/views/home/index.vue')
|
const HomeView = () => import('@/views/home/index.vue')
|
||||||
const FileStore = () => import('@/views/controlPanel/fileStore/index.vue')
|
const FileStore = () => import('@/views/controlPanel/fileStore/index.vue')
|
||||||
|
// 账号安全
|
||||||
|
const AccountSecurity = () => import('@/views/controlPanel/account/security/index.vue')
|
||||||
|
// 访问记录
|
||||||
|
const AccountHistory = () => import('@/views/controlPanel/account/history/index.vue')
|
||||||
|
|
||||||
const routes: RouteRecordRaw[] = [
|
const routes: RouteRecordRaw[] = [
|
||||||
{
|
{
|
||||||
@ -20,6 +24,16 @@ const routes: RouteRecordRaw[] = [
|
|||||||
path: '', // 空路径,访问 /fileStore 时显示FileStore组件
|
path: '', // 空路径,访问 /fileStore 时显示FileStore组件
|
||||||
name: 'FileStoreContent',
|
name: 'FileStoreContent',
|
||||||
component: FileStore
|
component: FileStore
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/accountSecurity',
|
||||||
|
name: 'AccountSecurity',
|
||||||
|
component: AccountSecurity
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/accountHistory',
|
||||||
|
name: 'AccountHistory',
|
||||||
|
component: AccountHistory
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
116
src/views/controlPanel/account/history/index.vue
Normal file
116
src/views/controlPanel/account/history/index.vue
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
<!-- src/components/AccessLog.vue -->
|
||||||
|
<template>
|
||||||
|
<div class="access-log">
|
||||||
|
<div class="title-wrapper">
|
||||||
|
<h3>访问记录</h3>
|
||||||
|
<span class="desc">以下显示内容为账号及其子账号近3个月的登录记录</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a-table
|
||||||
|
:columns="columns"
|
||||||
|
:data-source="loginRecords"
|
||||||
|
:pagination="false"
|
||||||
|
size="middle"
|
||||||
|
:bordered="true"
|
||||||
|
class="login-table"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
// 定义类型
|
||||||
|
interface LoginRecord {
|
||||||
|
id: number;
|
||||||
|
time: string;
|
||||||
|
ip: string;
|
||||||
|
city: string;
|
||||||
|
type: string;
|
||||||
|
operator: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 模拟数据
|
||||||
|
const loginRecords = ref<LoginRecord[]>([
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
time: '2025-11-21 09:58:38',
|
||||||
|
ip: '183.209.69.36',
|
||||||
|
city: '中国 江苏省',
|
||||||
|
type: 'Web微信登录',
|
||||||
|
operator: '炼丹师5075',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
time: '2025-11-19 17:07:13',
|
||||||
|
ip: '183.209.69.36',
|
||||||
|
city: '中国 江苏省',
|
||||||
|
type: 'Web微信登录',
|
||||||
|
operator: '炼丹师5075',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 表格列配置
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: '访问时间',
|
||||||
|
dataIndex: 'time',
|
||||||
|
key: 'time',
|
||||||
|
width: 200,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '访问IP地址',
|
||||||
|
dataIndex: 'ip',
|
||||||
|
key: 'ip',
|
||||||
|
width: 180,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '访问城市',
|
||||||
|
dataIndex: 'city',
|
||||||
|
key: 'city',
|
||||||
|
width: 180,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '登录类型',
|
||||||
|
dataIndex: 'type',
|
||||||
|
key: 'type',
|
||||||
|
width: 200,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作人',
|
||||||
|
dataIndex: 'operator',
|
||||||
|
key: 'operator',
|
||||||
|
width: 180,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.access-log {
|
||||||
|
padding: 20px;
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-wrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-wrapper h3 {
|
||||||
|
margin: 0;
|
||||||
|
color: #333;
|
||||||
|
font-size: 1.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.desc {
|
||||||
|
color: #faad14;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-table {
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
203
src/views/controlPanel/account/security/index.vue
Normal file
203
src/views/controlPanel/account/security/index.vue
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
<!-- src/components/AccountSecurity.vue -->
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { CheckCircleOutlined, ExclamationCircleOutlined } from '@ant-design/icons-vue';
|
||||||
|
|
||||||
|
// 定义安全项接口
|
||||||
|
interface SecurityItem {
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
status: 'unset' | 'bound' | 'unverified' | 'verified';
|
||||||
|
actionText: string;
|
||||||
|
actionHandler: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const securityItems = ref<SecurityItem[]>([
|
||||||
|
{
|
||||||
|
title: '登录密码',
|
||||||
|
description: '安全性高的密码可以使账号更安全。建议您定期更换密码,设置一个包含字母和数字且长度超过8位的密码',
|
||||||
|
status: 'unset',
|
||||||
|
actionText: '设置',
|
||||||
|
actionHandler: () => alert('跳转到设置密码页面'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '手机绑定',
|
||||||
|
description: '您已绑定了手机178****5075您的手机号可以直接用于登录、找回密码等',
|
||||||
|
status: 'bound',
|
||||||
|
actionText: '修改',
|
||||||
|
actionHandler: () => alert('跳转到修改手机号页面'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '实名认证',
|
||||||
|
description: '实名认证后可以使用AutoDL更完整的功能,如打开实例的自定义服务等',
|
||||||
|
status: 'unverified',
|
||||||
|
actionText: '立即认证',
|
||||||
|
actionHandler: () => alert('跳转到实名认证页面'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '微信绑定',
|
||||||
|
description: '您已绑定微信,可快速扫码登录',
|
||||||
|
status: 'bound',
|
||||||
|
actionText: '解绑',
|
||||||
|
actionHandler: () => alert('确认解绑微信?'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '邮箱绑定',
|
||||||
|
description: '绑定邮箱后可接收系统消息,如余额不足、实例即将到期、实例即将释放等消息',
|
||||||
|
status: 'unset',
|
||||||
|
actionText: '绑定',
|
||||||
|
actionHandler: () => alert('跳转到绑定邮箱页面'),
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 根据状态返回对应图标和颜色
|
||||||
|
const getStatusIcon = (status: SecurityItem['status']) => {
|
||||||
|
switch (status) {
|
||||||
|
case 'bound':
|
||||||
|
return CheckCircleOutlined;
|
||||||
|
case 'unverified':
|
||||||
|
case 'unset':
|
||||||
|
return ExclamationCircleOutlined;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getStatusColor = (status: SecurityItem['status']) => {
|
||||||
|
switch (status) {
|
||||||
|
case 'bound':
|
||||||
|
return 'green';
|
||||||
|
case 'unverified':
|
||||||
|
case 'unset':
|
||||||
|
return 'red';
|
||||||
|
default:
|
||||||
|
return 'gray';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getActionColor = (status: SecurityItem['status']) => {
|
||||||
|
if (status === 'bound') {
|
||||||
|
return 'blue';
|
||||||
|
}
|
||||||
|
return 'blue'; // 默认蓝色
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="account-security">
|
||||||
|
<h2>账号安全</h2>
|
||||||
|
<div v-for="(item, index) in securityItems" :key="index" class="security-item">
|
||||||
|
<div class="item-header">
|
||||||
|
<span class="title">{{ item.title }}</span>
|
||||||
|
<div class="status">
|
||||||
|
<component
|
||||||
|
:is="getStatusIcon(item.status)"
|
||||||
|
v-if="getStatusIcon(item.status)"
|
||||||
|
:class="['icon', `color-${getStatusColor(item.status)}`]"
|
||||||
|
/>
|
||||||
|
<span :class="['text', `color-${getStatusColor(item.status)}`]">
|
||||||
|
{{ item.status === 'bound' ? '已绑定' : item.status === 'unverified' ? '未认证' : '未设置' }}
|
||||||
|
</span>
|
||||||
|
<span class="divider">|</span>
|
||||||
|
<button
|
||||||
|
class="action-btn"
|
||||||
|
:class="`color-${getActionColor(item.status)}`"
|
||||||
|
@click="item.actionHandler"
|
||||||
|
>
|
||||||
|
{{ item.actionText }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p class="description">{{ item.description }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.account-security {
|
||||||
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
padding: 20px;
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.account-security h2 {
|
||||||
|
margin-top: 0;
|
||||||
|
color: #333;
|
||||||
|
font-size: 1.4em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.security-item {
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 6px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
padding: 16px;
|
||||||
|
background: white;
|
||||||
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.divider {
|
||||||
|
color: #ccc;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: #0066cc;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
padding: 0;
|
||||||
|
margin-left: 8px;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn:hover {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.color-green {
|
||||||
|
color: #52c41a !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.color-red {
|
||||||
|
color: #ff4d4f !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.color-blue {
|
||||||
|
color: #0066cc !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
color: #666;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -1,761 +1,255 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<header>
|
<!-- 标题 -->
|
||||||
|
<div class="header">
|
||||||
<h1>文件存储</h1>
|
<h1>文件存储</h1>
|
||||||
</header>
|
|
||||||
|
|
||||||
<div class="storage-grid">
|
|
||||||
<div
|
|
||||||
v-for="storage in storageOptions"
|
|
||||||
:key="storage.id"
|
|
||||||
:class="['storage-card', storage.class, { selected: selectedStorage === storage.id }]"
|
|
||||||
@click="selectStorage(storage.id)"
|
|
||||||
>
|
|
||||||
<div class="card-header">
|
|
||||||
<div class="card-icon">
|
|
||||||
<i :class="storage.icon"></i>
|
|
||||||
</div>
|
|
||||||
<div class="card-title">{{ storage.name }}</div>
|
|
||||||
</div>
|
|
||||||
<div class="card-description">
|
|
||||||
{{ storage.description }}
|
|
||||||
</div>
|
|
||||||
<div class="card-stats">
|
|
||||||
<div class="stat">
|
|
||||||
<div class="stat-value">{{ storage.capacity }}</div>
|
|
||||||
<div class="stat-label">存储容量</div>
|
|
||||||
</div>
|
|
||||||
<div class="stat">
|
|
||||||
<div class="stat-value">{{ storage.speed }}</div>
|
|
||||||
<div class="stat-label">传输速度</div>
|
|
||||||
</div>
|
|
||||||
<div class="stat">
|
|
||||||
<div class="stat-value">{{ storage.price }}</div>
|
|
||||||
<div class="stat-label">价格/月</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="divider"></div>
|
<!-- 区域选择 -->
|
||||||
|
<div class="region-section">
|
||||||
<section class="init-section">
|
<h2 class="section-title">选择存储区域</h2>
|
||||||
<h2>初始化文件存储</h2>
|
<div class="region-tabs">
|
||||||
<div class="init-content">
|
|
||||||
<div class="init-form" v-if="!isInitialized">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="storageName">存储名称</label>
|
|
||||||
<input
|
|
||||||
id="storageName"
|
|
||||||
v-model="initForm.name"
|
|
||||||
type="text"
|
|
||||||
placeholder="请输入存储名称"
|
|
||||||
:class="{ error: formErrors.name }"
|
|
||||||
>
|
|
||||||
<span class="error-message" v-if="formErrors.name">{{ formErrors.name }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="storageSize">存储容量</label>
|
|
||||||
<div class="size-input">
|
|
||||||
<input
|
|
||||||
id="storageSize"
|
|
||||||
v-model.number="initForm.size"
|
|
||||||
type="number"
|
|
||||||
placeholder="请输入存储容量"
|
|
||||||
:class="{ error: formErrors.size }"
|
|
||||||
>
|
|
||||||
<select v-model="initForm.sizeUnit">
|
|
||||||
<option value="GB">GB</option>
|
|
||||||
<option value="TB">TB</option>
|
|
||||||
<option value="PB">PB</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<span class="error-message" v-if="formErrors.size">{{ formErrors.size }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label>存储类型</label>
|
|
||||||
<div class="radio-group">
|
|
||||||
<label class="radio-option">
|
|
||||||
<input
|
|
||||||
type="radio"
|
|
||||||
v-model="initForm.storageType"
|
|
||||||
value="standard"
|
|
||||||
>
|
|
||||||
<span class="radio-label">标准存储</span>
|
|
||||||
</label>
|
|
||||||
<label class="radio-option">
|
|
||||||
<input
|
|
||||||
type="radio"
|
|
||||||
v-model="initForm.storageType"
|
|
||||||
value="archive"
|
|
||||||
>
|
|
||||||
<span class="radio-label">归档存储</span>
|
|
||||||
</label>
|
|
||||||
<label class="radio-option">
|
|
||||||
<input
|
|
||||||
type="radio"
|
|
||||||
v-model="initForm.storageType"
|
|
||||||
value="performance"
|
|
||||||
>
|
|
||||||
<span class="radio-label">性能存储</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="checkbox-option">
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
v-model="initForm.enableEncryption"
|
|
||||||
>
|
|
||||||
<span class="checkmark"></span>
|
|
||||||
启用数据加密
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-actions">
|
|
||||||
<button class="btn btn-outline" @click="resetForm">重置</button>
|
|
||||||
<button
|
<button
|
||||||
class="btn btn-primary"
|
v-for="region in regions"
|
||||||
@click="initializeStorage"
|
:key="region.id"
|
||||||
:disabled="!canInitialize"
|
:class="['region-tab', { active: selectedRegion === region.id }]"
|
||||||
|
@click="selectRegion(region.id)"
|
||||||
>
|
>
|
||||||
{{ isInitializing ? '初始化中...' : '开始初始化' }}
|
{{ region.name }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="init-success" v-else>
|
<!-- 中心图标 -->
|
||||||
<div class="success-icon">
|
<div class="center-section">
|
||||||
<i class="fas fa-check-circle"></i>
|
<div class="center-icon">
|
||||||
</div>
|
<i class="icon-book"></i>
|
||||||
<h3>初始化成功!</h3>
|
|
||||||
<p>文件存储已成功创建,您现在可以开始使用了。</p>
|
|
||||||
<div class="storage-info">
|
|
||||||
<div class="info-item">
|
|
||||||
<span class="info-label">存储名称:</span>
|
|
||||||
<span class="info-value">{{ initializedStorage.name }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="info-item">
|
|
||||||
<span class="info-label">存储区域:</span>
|
|
||||||
<span class="info-value">{{ getSelectedStorageName() }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="info-item">
|
|
||||||
<span class="info-label">存储容量:</span>
|
|
||||||
<span class="info-value">{{ initializedStorage.size }} {{ initializedStorage.sizeUnit }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="info-item">
|
|
||||||
<span class="info-label">存储类型:</span>
|
|
||||||
<span class="info-value">{{ getStorageTypeText(initializedStorage.storageType) }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="success-actions">
|
|
||||||
<button class="btn btn-outline" @click="createNewStorage">创建新的存储</button>
|
|
||||||
<button class="btn btn-primary" @click="goToStorage">进入文件存储</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="init-help">
|
<!-- 主要操作按钮 -->
|
||||||
<h3>文件存储使用介绍</h3>
|
<div class="action-section">
|
||||||
<p>选择适合您需求的存储区域,配置存储参数并开始使用</p>
|
<button class="btn-primary" @click="initializeStorage">
|
||||||
<div v-if="selectedStorage" class="selected-info">
|
初始化文件存储
|
||||||
当前选择: <strong>{{ getSelectedStorageName() }}</strong>
|
</button>
|
||||||
|
<p class="action-hint">将在 {{ getSelectedRegionName() }} 创建存储空间</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="help-actions">
|
|
||||||
<button class="btn btn-outline">查看文档</button>
|
<!-- 底部帮助链接 -->
|
||||||
<button class="btn btn-primary" @click="viewPricingRules">查看计费规则</button>
|
<div class="help-section">
|
||||||
|
<div class="help-links">
|
||||||
|
<a href="#" class="link" @click.prevent="viewHelp">文件存储使用介绍</a>
|
||||||
|
<a href="#" class="link" @click.prevent="viewPricing">查看计费规则</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, reactive, computed } from 'vue'
|
import { ref } from 'vue'
|
||||||
|
|
||||||
// 定义存储选项接口
|
// 定义区域选项
|
||||||
interface StorageOption {
|
interface Region {
|
||||||
id: number
|
id: string
|
||||||
name: string
|
name: string
|
||||||
class: string
|
|
||||||
icon: string
|
|
||||||
description: string
|
|
||||||
capacity: string
|
|
||||||
speed: string
|
|
||||||
price: string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 定义初始化表单接口
|
const regions = [
|
||||||
interface InitForm {
|
{ id: 'a100', name: 'A100专区' },
|
||||||
name: string
|
{ id: 'v100', name: 'V100专区' },
|
||||||
size: number | null
|
{ id: 'foshan', name: '佛山区' },
|
||||||
sizeUnit: string
|
{ id: 'beijing', name: '北京B区' }
|
||||||
storageType: string
|
]
|
||||||
enableEncryption: boolean
|
|
||||||
|
const selectedRegion = ref('a100') // 默认选中 A100
|
||||||
|
|
||||||
|
// 方法:获取选中区域名称
|
||||||
|
const getSelectedRegionName = () => {
|
||||||
|
const region = regions.find(r => r.id === selectedRegion.value)
|
||||||
|
return region ? region.name : ''
|
||||||
}
|
}
|
||||||
|
|
||||||
interface FormErrors {
|
// 方法:选择区域
|
||||||
name?: string
|
const selectRegion = (id: string) => {
|
||||||
size?: string
|
selectedRegion.value = id
|
||||||
}
|
|
||||||
|
|
||||||
interface InitializedStorage {
|
|
||||||
name: string
|
|
||||||
size: number
|
|
||||||
sizeUnit: string
|
|
||||||
storageType: string
|
|
||||||
enableEncryption: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
// 响应式数据
|
|
||||||
const selectedStorage = ref<number | null>(null)
|
|
||||||
const isInitializing = ref(false)
|
|
||||||
const isInitialized = ref(false)
|
|
||||||
|
|
||||||
// 初始化表单数据
|
|
||||||
const initForm = reactive<InitForm>({
|
|
||||||
name: '',
|
|
||||||
size: null,
|
|
||||||
sizeUnit: 'GB',
|
|
||||||
storageType: 'standard',
|
|
||||||
enableEncryption: true
|
|
||||||
})
|
|
||||||
|
|
||||||
const formErrors = reactive<FormErrors>({})
|
|
||||||
const initializedStorage = ref<InitializedStorage | null>(null)
|
|
||||||
|
|
||||||
// 存储选项数据
|
|
||||||
const storageOptions = ref<StorageOption[]>([
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
name: 'A100专区',
|
|
||||||
class: 'a100',
|
|
||||||
icon: 'fas fa-microchip',
|
|
||||||
description: '高性能计算存储,适用于AI训练和大数据分析',
|
|
||||||
capacity: '10TB',
|
|
||||||
speed: '10Gbps',
|
|
||||||
price: '¥299'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
name: 'V100专区',
|
|
||||||
class: 'v100',
|
|
||||||
icon: 'fas fa-server',
|
|
||||||
description: '企业级存储解决方案,提供高可靠性和稳定性',
|
|
||||||
capacity: '20TB',
|
|
||||||
speed: '8Gbps',
|
|
||||||
price: '¥499'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 3,
|
|
||||||
name: '佛山区',
|
|
||||||
class: 'foshan',
|
|
||||||
icon: 'fas fa-map-marker-alt',
|
|
||||||
description: '华南地区存储节点,低延迟访问',
|
|
||||||
capacity: '15TB',
|
|
||||||
speed: '6Gbps',
|
|
||||||
price: '¥199'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 4,
|
|
||||||
name: '北京B区',
|
|
||||||
class: 'beijing',
|
|
||||||
icon: 'fas fa-cloud',
|
|
||||||
description: '华北地区存储节点,高可用性架构',
|
|
||||||
capacity: '25TB',
|
|
||||||
speed: '12Gbps',
|
|
||||||
price: '¥399'
|
|
||||||
}
|
|
||||||
])
|
|
||||||
|
|
||||||
// 计算属性
|
|
||||||
const canInitialize = computed(() => {
|
|
||||||
return selectedStorage.value !== null &&
|
|
||||||
initForm.name.trim() !== '' &&
|
|
||||||
initForm.size !== null &&
|
|
||||||
initForm.size > 0
|
|
||||||
})
|
|
||||||
|
|
||||||
// 方法:选择存储区域
|
|
||||||
const selectStorage = (id: number): void => {
|
|
||||||
selectedStorage.value = id
|
|
||||||
}
|
|
||||||
|
|
||||||
// 方法:获取选中的存储名称
|
|
||||||
const getSelectedStorageName = (): string => {
|
|
||||||
const selected = storageOptions.value.find(storage => storage.id === selectedStorage.value)
|
|
||||||
return selected ? selected.name : ''
|
|
||||||
}
|
|
||||||
|
|
||||||
// 方法:验证表单
|
|
||||||
const validateForm = (): boolean => {
|
|
||||||
// 清空错误信息
|
|
||||||
formErrors.name = ''
|
|
||||||
formErrors.size = ''
|
|
||||||
|
|
||||||
let isValid = true
|
|
||||||
|
|
||||||
if (!initForm.name.trim()) {
|
|
||||||
formErrors.name = '请输入存储名称'
|
|
||||||
isValid = false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (initForm.size === null || initForm.size <= 0) {
|
|
||||||
formErrors.size = '请输入有效的存储容量'
|
|
||||||
isValid = false
|
|
||||||
}
|
|
||||||
|
|
||||||
return isValid
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 方法:初始化存储
|
// 方法:初始化存储
|
||||||
const initializeStorage = async (): Promise<void> => {
|
const initializeStorage = () => {
|
||||||
if (!validateForm()) {
|
alert(`正在初始化 ${selectedRegion.value} 的文件存储...`)
|
||||||
return
|
// 这里可以跳转到下一步流程
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!selectedStorage.value) {
|
// 方法:查看帮助
|
||||||
alert('请先选择存储区域')
|
const viewHelp = () => {
|
||||||
return
|
alert('文件存储使用介绍')
|
||||||
}
|
|
||||||
|
|
||||||
isInitializing.value = true
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 模拟API调用
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 2000))
|
|
||||||
|
|
||||||
// 保存初始化后的存储信息
|
|
||||||
initializedStorage.value = {
|
|
||||||
name: initForm.name,
|
|
||||||
size: initForm.size!,
|
|
||||||
sizeUnit: initForm.sizeUnit,
|
|
||||||
storageType: initForm.storageType,
|
|
||||||
enableEncryption: initForm.enableEncryption
|
|
||||||
}
|
|
||||||
|
|
||||||
isInitialized.value = true
|
|
||||||
} catch (error) {
|
|
||||||
console.error('初始化失败:', error)
|
|
||||||
alert('初始化失败,请重试')
|
|
||||||
} finally {
|
|
||||||
isInitializing.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 方法:重置表单
|
|
||||||
const resetForm = (): void => {
|
|
||||||
initForm.name = ''
|
|
||||||
initForm.size = null
|
|
||||||
initForm.sizeUnit = 'GB'
|
|
||||||
initForm.storageType = 'standard'
|
|
||||||
initForm.enableEncryption = true
|
|
||||||
|
|
||||||
// 清空错误信息
|
|
||||||
formErrors.name = ''
|
|
||||||
formErrors.size = ''
|
|
||||||
}
|
|
||||||
|
|
||||||
// 方法:创建新的存储
|
|
||||||
const createNewStorage = (): void => {
|
|
||||||
isInitialized.value = false
|
|
||||||
resetForm()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 方法:进入文件存储
|
|
||||||
const goToStorage = (): void => {
|
|
||||||
alert('正在跳转到文件存储管理页面...')
|
|
||||||
// 实际项目中这里会进行页面跳转
|
|
||||||
}
|
|
||||||
|
|
||||||
// 方法:获取存储类型文本
|
|
||||||
const getStorageTypeText = (type: string): string => {
|
|
||||||
const typeMap: { [key: string]: string } = {
|
|
||||||
standard: '标准存储',
|
|
||||||
archive: '归档存储',
|
|
||||||
performance: '性能存储'
|
|
||||||
}
|
|
||||||
return typeMap[type] || type
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 方法:查看计费规则
|
// 方法:查看计费规则
|
||||||
const viewPricingRules = (): void => {
|
const viewPricing = () => {
|
||||||
alert('计费规则页面即将打开')
|
alert('计费规则详情')
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.container {
|
.container {
|
||||||
max-width: 1200px;
|
/* max-width: 800px;
|
||||||
margin: 0 auto;
|
margin: 0 auto; */
|
||||||
padding: 20px;
|
padding: 40px 30px;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||||
|
color: #333;
|
||||||
}
|
}
|
||||||
|
|
||||||
header {
|
/* 标题区域 */
|
||||||
margin-bottom: 30px;
|
.header {
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
font-size: 2.2rem;
|
|
||||||
color: #2c3e50;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.divider {
|
|
||||||
height: 1px;
|
|
||||||
background: linear-gradient(to right, transparent, #ddd, transparent);
|
|
||||||
margin: 25px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
h2 {
|
|
||||||
font-size: 1.6rem;
|
|
||||||
color: #34495e;
|
|
||||||
margin-bottom: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.storage-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
|
||||||
gap: 20px;
|
|
||||||
margin-bottom: 40px;
|
margin-bottom: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.storage-card {
|
h1 {
|
||||||
background: white;
|
font-size: 2rem;
|
||||||
border-radius: 12px;
|
font-weight: 600;
|
||||||
padding: 20px;
|
color: #1a1a1a;
|
||||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
margin-bottom: 8px;
|
||||||
transition: all 0.3s ease;
|
|
||||||
cursor: pointer;
|
|
||||||
border: 2px solid transparent;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.storage-card:hover {
|
/* 区域选择 */
|
||||||
transform: translateY(-5px);
|
.region-section {
|
||||||
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.12);
|
margin-bottom: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.storage-card.selected {
|
.section-title {
|
||||||
border-color: #3498db;
|
font-size: 1.1rem;
|
||||||
background-color: #f8fafd;
|
font-weight: 500;
|
||||||
|
color: #555;
|
||||||
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-header {
|
.region-tabs {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
flex-wrap: wrap;
|
||||||
margin-bottom: 15px;
|
gap: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-icon {
|
.region-tab {
|
||||||
width: 50px;
|
padding: 12px 24px;
|
||||||
height: 50px;
|
border: 1px solid #ddd;
|
||||||
border-radius: 10px;
|
background: white;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
font-size: 14px;
|
||||||
|
min-width: 140px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.region-tab:hover {
|
||||||
|
border-color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.region-tab.active {
|
||||||
|
background-color: #3498db;
|
||||||
|
color: white;
|
||||||
|
border-color: #3498db;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 中心图标区域 */
|
||||||
|
.center-section {
|
||||||
|
margin: 40px 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.center-icon {
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
margin-right: 15px;
|
background: #f5f5f5;
|
||||||
font-size: 1.5rem;
|
border-radius: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.a100 .card-icon {
|
.icon-book {
|
||||||
background-color: #e8f4fd;
|
font-size: 36px;
|
||||||
color: #3498db;
|
color: #3498db;
|
||||||
}
|
}
|
||||||
|
|
||||||
.v100 .card-icon {
|
.icon-book::before {
|
||||||
background-color: #e8f6f3;
|
content: "📁";
|
||||||
color: #1abc9c;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.foshan .card-icon {
|
/* 操作区域 */
|
||||||
background-color: #fef9e7;
|
.action-section {
|
||||||
color: #f39c12;
|
margin: 40px 0;
|
||||||
}
|
|
||||||
|
|
||||||
.beijing .card-icon {
|
|
||||||
background-color: #fdedec;
|
|
||||||
color: #e74c3c;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-title {
|
|
||||||
font-size: 1.2rem;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-description {
|
|
||||||
color: #7f8c8d;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
margin-bottom: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-stats {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat {
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.stat-value {
|
|
||||||
font-weight: 700;
|
|
||||||
font-size: 1.1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-label {
|
|
||||||
font-size: 0.8rem;
|
|
||||||
color: #7f8c8d;
|
|
||||||
}
|
|
||||||
|
|
||||||
.init-section {
|
|
||||||
background: white;
|
|
||||||
border-radius: 12px;
|
|
||||||
padding: 25px;
|
|
||||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
|
||||||
}
|
|
||||||
|
|
||||||
.init-content {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr;
|
|
||||||
gap: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.init-form {
|
|
||||||
grid-column: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.init-help {
|
|
||||||
grid-column: 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group label {
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #2c3e50;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group input[type="text"],
|
|
||||||
.form-group input[type="number"] {
|
|
||||||
width: 100%;
|
|
||||||
padding: 10px 12px;
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
border-radius: 6px;
|
|
||||||
font-size: 14px;
|
|
||||||
transition: border-color 0.3s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group input:focus {
|
|
||||||
outline: none;
|
|
||||||
border-color: #3498db;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group input.error {
|
|
||||||
border-color: #e74c3c;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-message {
|
|
||||||
color: #e74c3c;
|
|
||||||
font-size: 12px;
|
|
||||||
margin-top: 5px;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.size-input {
|
|
||||||
display: flex;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.size-input input {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.size-input select {
|
|
||||||
padding: 10px 12px;
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
border-radius: 6px;
|
|
||||||
background: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.radio-group {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.radio-option {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.radio-option input {
|
|
||||||
margin-right: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.checkbox-option {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.checkbox-option input {
|
|
||||||
margin-right: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-actions {
|
|
||||||
display: flex;
|
|
||||||
gap: 10px;
|
|
||||||
margin-top: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn {
|
|
||||||
padding: 10px 20px;
|
|
||||||
border-radius: 6px;
|
|
||||||
border: none;
|
|
||||||
font-weight: 600;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn:disabled {
|
|
||||||
opacity: 0.6;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-primary {
|
.btn-primary {
|
||||||
background-color: #3498db;
|
background-color: #3498db;
|
||||||
color: white;
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 14px 32px;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-primary:hover:not(:disabled) {
|
.btn-primary:hover {
|
||||||
background-color: #2980b9;
|
background-color: #2980b9;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-outline {
|
.action-hint {
|
||||||
background-color: transparent;
|
margin-top: 12px;
|
||||||
border: 1px solid #3498db;
|
font-size: 0.9rem;
|
||||||
color: #3498db;
|
color: #666;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-outline:hover {
|
/* 帮助区域 */
|
||||||
background-color: #e8f4fd;
|
.help-section {
|
||||||
|
margin-top: 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.init-success {
|
.help-links {
|
||||||
grid-column: 1 / -1;
|
|
||||||
text-align: center;
|
|
||||||
padding: 40px 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.success-icon {
|
|
||||||
font-size: 4rem;
|
|
||||||
color: #27ae60;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.success-icon i {
|
|
||||||
animation: scaleIn 0.5s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes scaleIn {
|
|
||||||
0% { transform: scale(0); }
|
|
||||||
70% { transform: scale(1.2); }
|
|
||||||
100% { transform: scale(1); }
|
|
||||||
}
|
|
||||||
|
|
||||||
.storage-info {
|
|
||||||
background: #f8f9fa;
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 20px;
|
|
||||||
margin: 30px 0;
|
|
||||||
text-align: left;
|
|
||||||
max-width: 400px;
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-item {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
gap: 30px;
|
||||||
margin-bottom: 10px;
|
|
||||||
padding-bottom: 10px;
|
|
||||||
border-bottom: 1px solid #e9ecef;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-item:last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-label {
|
|
||||||
font-weight: 600;
|
|
||||||
color: #495057;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-value {
|
|
||||||
color: #6c757d;
|
|
||||||
}
|
|
||||||
|
|
||||||
.success-actions {
|
|
||||||
display: flex;
|
|
||||||
gap: 10px;
|
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
margin-top: 30px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.selected-info {
|
.link {
|
||||||
margin-top: 20px;
|
text-decoration: none;
|
||||||
padding: 15px;
|
color: #3498db;
|
||||||
background-color: #e8f4fd;
|
font-size: 14px;
|
||||||
border-radius: 8px;
|
transition: color 0.2s;
|
||||||
border-left: 4px solid #3498db;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.help-actions {
|
.link:hover {
|
||||||
margin-top: 20px;
|
color: #2980b9;
|
||||||
}
|
text-decoration: underline;
|
||||||
|
|
||||||
.init-help h3 {
|
|
||||||
font-size: 1.4rem;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
color: #2c3e50;
|
|
||||||
}
|
|
||||||
|
|
||||||
.init-help p {
|
|
||||||
color: #7f8c8d;
|
|
||||||
margin-bottom: 15px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 响应式设计 */
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.storage-grid {
|
.container {
|
||||||
grid-template-columns: 1fr;
|
padding: 30px 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.init-content {
|
.region-tabs {
|
||||||
grid-template-columns: 1fr;
|
|
||||||
gap: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.init-form,
|
|
||||||
.init-help {
|
|
||||||
grid-column: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.success-actions {
|
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-actions {
|
.region-tab {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-links {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.center-section {
|
||||||
|
justify-content: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
Loading…
x
Reference in New Issue
Block a user