代码修改
This commit is contained in:
parent
1b6239b4bc
commit
0f5690fadb
21
src/App.vue
21
src/App.vue
@ -1,8 +1,9 @@
|
||||
<!-- src/App.vue -->
|
||||
<template>
|
||||
<div id="app">
|
||||
<Header />
|
||||
<router-view />
|
||||
<main class="main-content">
|
||||
<router-view />
|
||||
</main>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -19,12 +20,26 @@ import Header from '@/components/Header.vue'
|
||||
|
||||
html, body {
|
||||
height: 100%;
|
||||
overflow-x: hidden; /* 可选:防止水平滚动 */
|
||||
}
|
||||
|
||||
#app {
|
||||
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>
|
||||
@ -76,7 +76,8 @@ const menuItems: MenuItem[] = [
|
||||
name: '账号',
|
||||
icon: TeamOutlined,
|
||||
children: [
|
||||
{ path: '/controlPanel/account/profile', name: '个人资料' },
|
||||
{ path: '/accountSecurity', name: '账号安全' },
|
||||
{ path: '/accountHistory', name: '访问记录' },
|
||||
{ path: '/controlPanel/account/security', name: '安全设置' }
|
||||
]
|
||||
}
|
||||
@ -102,7 +103,8 @@ const handleMenuClick = ({ key }: { key: string }) => {
|
||||
height: 100vh;
|
||||
background: #fff;
|
||||
box-shadow: 2px 0 4px rgba(0, 0, 0, 0.1);
|
||||
overflow-y: auto;
|
||||
padding-top:30px;
|
||||
/* overflow-y: auto; */
|
||||
}
|
||||
|
||||
.menu-item-content {
|
||||
|
||||
@ -4,6 +4,10 @@ import Layout from '@/components/Layout.vue'
|
||||
|
||||
const HomeView = () => import('@/views/home/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[] = [
|
||||
{
|
||||
@ -20,6 +24,16 @@ const routes: RouteRecordRaw[] = [
|
||||
path: '', // 空路径,访问 /fileStore 时显示FileStore组件
|
||||
name: 'FileStoreContent',
|
||||
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>
|
||||
<div class="container">
|
||||
<header>
|
||||
<!-- 标题 -->
|
||||
<div class="header">
|
||||
<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 class="region-section">
|
||||
<h2 class="section-title">选择存储区域</h2>
|
||||
<div class="region-tabs">
|
||||
<button
|
||||
v-for="region in regions"
|
||||
:key="region.id"
|
||||
:class="['region-tab', { active: selectedRegion === region.id }]"
|
||||
@click="selectRegion(region.id)"
|
||||
>
|
||||
{{ region.name }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="divider"></div>
|
||||
|
||||
<section class="init-section">
|
||||
<h2>初始化文件存储</h2>
|
||||
<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
|
||||
class="btn btn-primary"
|
||||
@click="initializeStorage"
|
||||
:disabled="!canInitialize"
|
||||
>
|
||||
{{ isInitializing ? '初始化中...' : '开始初始化' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="init-success" v-else>
|
||||
<div class="success-icon">
|
||||
<i class="fas fa-check-circle"></i>
|
||||
</div>
|
||||
<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 class="init-help">
|
||||
<h3>文件存储使用介绍</h3>
|
||||
<p>选择适合您需求的存储区域,配置存储参数并开始使用</p>
|
||||
<div v-if="selectedStorage" class="selected-info">
|
||||
当前选择: <strong>{{ getSelectedStorageName() }}</strong>
|
||||
</div>
|
||||
<div class="help-actions">
|
||||
<button class="btn btn-outline">查看文档</button>
|
||||
<button class="btn btn-primary" @click="viewPricingRules">查看计费规则</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 中心图标 -->
|
||||
<div class="center-section">
|
||||
<div class="center-icon">
|
||||
<i class="icon-book"></i>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<!-- 主要操作按钮 -->
|
||||
<div class="action-section">
|
||||
<button class="btn-primary" @click="initializeStorage">
|
||||
初始化文件存储
|
||||
</button>
|
||||
<p class="action-hint">将在 {{ getSelectedRegionName() }} 创建存储空间</p>
|
||||
</div>
|
||||
|
||||
<!-- 底部帮助链接 -->
|
||||
<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>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed } from 'vue'
|
||||
import { ref } from 'vue'
|
||||
|
||||
// 定义存储选项接口
|
||||
interface StorageOption {
|
||||
id: number
|
||||
// 定义区域选项
|
||||
interface Region {
|
||||
id: string
|
||||
name: string
|
||||
class: string
|
||||
icon: string
|
||||
description: string
|
||||
capacity: string
|
||||
speed: string
|
||||
price: string
|
||||
}
|
||||
|
||||
// 定义初始化表单接口
|
||||
interface InitForm {
|
||||
name: string
|
||||
size: number | null
|
||||
sizeUnit: string
|
||||
storageType: string
|
||||
enableEncryption: boolean
|
||||
const regions = [
|
||||
{ id: 'a100', name: 'A100专区' },
|
||||
{ id: 'v100', name: 'V100专区' },
|
||||
{ id: 'foshan', name: '佛山区' },
|
||||
{ id: 'beijing', name: '北京B区' }
|
||||
]
|
||||
|
||||
const selectedRegion = ref('a100') // 默认选中 A100
|
||||
|
||||
// 方法:获取选中区域名称
|
||||
const getSelectedRegionName = () => {
|
||||
const region = regions.find(r => r.id === selectedRegion.value)
|
||||
return region ? region.name : ''
|
||||
}
|
||||
|
||||
interface FormErrors {
|
||||
name?: string
|
||||
size?: string
|
||||
}
|
||||
|
||||
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 selectRegion = (id: string) => {
|
||||
selectedRegion.value = id
|
||||
}
|
||||
|
||||
// 方法:初始化存储
|
||||
const initializeStorage = async (): Promise<void> => {
|
||||
if (!validateForm()) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!selectedStorage.value) {
|
||||
alert('请先选择存储区域')
|
||||
return
|
||||
}
|
||||
|
||||
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 initializeStorage = () => {
|
||||
alert(`正在初始化 ${selectedRegion.value} 的文件存储...`)
|
||||
// 这里可以跳转到下一步流程
|
||||
}
|
||||
|
||||
// 方法:重置表单
|
||||
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 viewHelp = () => {
|
||||
alert('文件存储使用介绍')
|
||||
}
|
||||
|
||||
// 方法:查看计费规则
|
||||
const viewPricingRules = (): void => {
|
||||
alert('计费规则页面即将打开')
|
||||
const viewPricing = () => {
|
||||
alert('计费规则详情')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
/* max-width: 800px;
|
||||
margin: 0 auto; */
|
||||
padding: 40px 30px;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
header {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
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;
|
||||
/* 标题区域 */
|
||||
.header {
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.storage-card {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
||||
transition: all 0.3s ease;
|
||||
cursor: pointer;
|
||||
border: 2px solid transparent;
|
||||
h1 {
|
||||
font-size: 2rem;
|
||||
font-weight: 600;
|
||||
color: #1a1a1a;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.storage-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.12);
|
||||
/* 区域选择 */
|
||||
.region-section {
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.storage-card.selected {
|
||||
border-color: #3498db;
|
||||
background-color: #f8fafd;
|
||||
.section-title {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 500;
|
||||
color: #555;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
.region-tabs {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 15px;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.card-icon {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 10px;
|
||||
.region-tab {
|
||||
padding: 12px 24px;
|
||||
border: 1px solid #ddd;
|
||||
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;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 15px;
|
||||
font-size: 1.5rem;
|
||||
background: #f5f5f5;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.a100 .card-icon {
|
||||
background-color: #e8f4fd;
|
||||
.icon-book {
|
||||
font-size: 36px;
|
||||
color: #3498db;
|
||||
}
|
||||
|
||||
.v100 .card-icon {
|
||||
background-color: #e8f6f3;
|
||||
color: #1abc9c;
|
||||
.icon-book::before {
|
||||
content: "📁";
|
||||
}
|
||||
|
||||
.foshan .card-icon {
|
||||
background-color: #fef9e7;
|
||||
color: #f39c12;
|
||||
}
|
||||
|
||||
.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 {
|
||||
/* 操作区域 */
|
||||
.action-section {
|
||||
margin: 40px 0;
|
||||
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 {
|
||||
background-color: #3498db;
|
||||
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;
|
||||
}
|
||||
|
||||
.btn-outline {
|
||||
background-color: transparent;
|
||||
border: 1px solid #3498db;
|
||||
color: #3498db;
|
||||
.action-hint {
|
||||
margin-top: 12px;
|
||||
font-size: 0.9rem;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.btn-outline:hover {
|
||||
background-color: #e8f4fd;
|
||||
/* 帮助区域 */
|
||||
.help-section {
|
||||
margin-top: 50px;
|
||||
}
|
||||
|
||||
.init-success {
|
||||
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 {
|
||||
.help-links {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
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;
|
||||
gap: 30px;
|
||||
justify-content: center;
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.selected-info {
|
||||
margin-top: 20px;
|
||||
padding: 15px;
|
||||
background-color: #e8f4fd;
|
||||
border-radius: 8px;
|
||||
border-left: 4px solid #3498db;
|
||||
.link {
|
||||
text-decoration: none;
|
||||
color: #3498db;
|
||||
font-size: 14px;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
|
||||
.help-actions {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.init-help h3 {
|
||||
font-size: 1.4rem;
|
||||
margin-bottom: 8px;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.init-help p {
|
||||
color: #7f8c8d;
|
||||
margin-bottom: 15px;
|
||||
.link:hover {
|
||||
color: #2980b9;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 768px) {
|
||||
.storage-grid {
|
||||
grid-template-columns: 1fr;
|
||||
.container {
|
||||
padding: 30px 20px;
|
||||
}
|
||||
|
||||
.init-content {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.init-form,
|
||||
.init-help {
|
||||
grid-column: 1;
|
||||
}
|
||||
|
||||
.success-actions {
|
||||
.region-tabs {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
.region-tab {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.help-links {
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.center-section {
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Loading…
x
Reference in New Issue
Block a user