This commit is contained in:
qiuyuan 2026-01-13 10:42:31 +08:00
parent 3655207bfa
commit 3ccfcea326
4 changed files with 318 additions and 228 deletions

View File

@ -46,10 +46,10 @@ export const getBankCardInfo = (params:any) => request.get('/v1/bank_card/bank_c
export const delBankCard = (params:any) => request.post('/v1/bank_card/delete_bank_card',params) export const delBankCard = (params:any) => request.post('/v1/bank_card/delete_bank_card',params)
// 发票管理-开票记录 // 发票管理-开票记录
export const invoiceList = (params:any) => request.get('/v1/order/invoice_list',{params}) export const invoiceList = (params:any) => request.get('/v1/invoice/invoice_list',{params})
// 发票管理-开发记录详情 // 发票管理-开发记录详情
export const invoiceDetail = (id:any) => request.delete(`/v1/order/invoice_detail/${id}`) export const invoiceDetail = (id:any) => request.delete(`/v1/invoice/invoice_detail/${id}`)
// 获取订单列表 // 获取订单列表
export const getOrderList = (params:any) => request.get('/v1/order/order_list',{params}) export const getOrderList = (params:any) => request.get('/v1/order/order_list',{params})

View File

@ -492,8 +492,8 @@ onMounted(() => {
} }
.main-content { .main-content {
max-width: 1200px; /* max-width: 1200px;
margin: 0 auto; margin: 0 auto; */
} }
/* 余额区域 */ /* 余额区域 */

View File

@ -37,13 +37,13 @@
<div class="batch-operations" style="margin-bottom: 16px;"> <div class="batch-operations" style="margin-bottom: 16px;">
<a-space> <a-space>
<!-- :disabled="selectedRowKeys.length === 0" --> <!-- :disabled="selectedRowKeys.length === 0" -->
<a-button <!-- <a-button
type="primary" type="primary"
@click="handleBatchInvoice" @click="handleBatchInvoice"
> >
批量去开票 批量去开票
</a-button> </a-button> -->
<span v-if="selectedRowKeys.length > 0"> <span v-if="selectedRowKeys.length > 0">
已选择 {{ selectedRowKeys.length }} 条记录 已选择 {{ selectedRowKeys.length }} 条记录
</span> </span>
@ -94,9 +94,9 @@
{{ getPayStatusText(record.pay_status) }} {{ getPayStatusText(record.pay_status) }}
</a-tag> </a-tag>
</template> </template>
<template v-if="column.key === 'action'"> <!-- <template v-if="column.key === 'action'">
<a-space> <a-space>
<!-- :disabled="!canApplyInvoice(record)" -->
<a-button <a-button
type="link" type="link"
size="small" size="small"
@ -106,7 +106,7 @@
去开票 去开票
</a-button> </a-button>
</a-space> </a-space>
</template> </template> -->
</template> </template>
</a-table> </a-table>
</a-card> </a-card>
@ -483,12 +483,12 @@ const columns = ref([
key: 'amount', key: 'amount',
width: 120 width: 120
}, },
{ // {
title: '操作', // title: '',
key: 'action', // key: 'action',
width: 100, // width: 100,
align: 'center' // align: 'center'
} // }
]) ])
// //

View File

@ -1,255 +1,345 @@
<template> <template>
<div class="container"> <div class="file-storage-page">
<!-- 标题 --> <div class="page-header">
<div class="header"> <h2 class="page-title">文件存储</h2>
<h1>文件存储</h1> <a-button type="primary" @click="showModal" class="create-btn">
<template #icon><PlusOutlined /></template>
新建存储
</a-button>
</div> </div>
<!-- 区域选择 --> <!-- 表格 -->
<div class="region-section"> <div class="table-container">
<h2 class="section-title">选择存储区域</h2> <a-table
<div class="region-tabs"> :columns="columns"
<button :data-source="storageList"
v-for="region in regions" :row-key="record => record.id"
:key="region.id" :pagination="{ pageSize: 8 }"
:class="['region-tab', { active: selectedRegion === region.id }]" class="storage-table"
@click="selectRegion(region.id)" >
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'size'">
<span class="data-cell">{{ record.size }} GB</span>
</template>
<template v-else-if="column.key === 'duration'">
<span class="data-cell">{{ record.duration }} </span>
</template>
<template v-else-if="column.key === 'cost'">
<span class="cost-cell">¥{{ record.cost.toFixed(2) }}</span>
</template>
<template v-else-if="column.key === 'name'">
<div class="name-cell">
<FolderOutlined class="folder-icon" />
<span>{{ record.name }}</span>
</div>
</template>
</template>
</a-table>
</div>
<!-- 创建弹窗 -->
<a-modal
v-model:open="modalVisible"
title="创建存储空间"
@ok="handleCreate"
@cancel="modalVisible = false"
:width="520"
class="create-modal"
>
<a-form
:model="createForm"
:label-col="{ span: 6 }"
:wrapper-col="{ span: 18 }"
ref="createFormRef"
class="create-form"
>
<a-form-item label="存储名称" name="name" :rules="[{ required: true, message: '请输入存储名称' }]">
<a-input
v-model:value="createForm.name"
placeholder="请输入存储空间名称"
size="large"
/>
</a-form-item>
<a-form-item
label="存储大小"
name="size"
:rules="[{ required: true, message: '请输入存储大小' }, { type: 'number', min: 1, message: '至少1GB' }]"
> >
{{ region.name }} <a-input-number
</button> v-model:value="createForm.size"
</div> :min="1"
</div> :max="10000"
style="width: 100%"
size="large"
addon-after="GB"
/>
</a-form-item>
<!-- 中心图标 --> <a-form-item
<div class="center-section"> label="购买时长"
<div class="center-icon"> name="duration"
<i class="icon-book"></i> :rules="[{ required: true, message: '请输入购买时长' }, { type: 'number', min: 1, message: '至少1天' }]"
</div> >
</div> <a-input-number
v-model:value="createForm.duration"
:min="1"
:max="365"
style="width: 100%"
size="large"
addon-after="天"
/>
</a-form-item>
<!-- 主要操作按钮 --> <div class="cost-summary">
<div class="action-section"> <div class="cost-label">费用合计</div>
<button class="btn-primary" @click="initializeStorage"> <div class="cost-amount">
初始化文件存储 <span class="amount">¥{{ totalCost }}</span>
</button> <span class="cost-note">0.1/GB/</span>
<p class="action-hint">将在 {{ getSelectedRegionName() }} 创建存储空间</p> </div>
</div> </div>
</a-form>
<!-- 底部帮助链接 -->
<div class="help-section"> <template #footer>
<div class="help-links"> <a-button @click="modalVisible = false">取消</a-button>
<a href="#" class="link" @click.prevent="viewHelp">文件存储使用介绍</a> <a-button type="primary" @click="handleCreate" :loading="creating">确认创建</a-button>
<a href="#" class="link" @click.prevent="viewPricing">查看计费规则</a> </template>
</div> </a-modal>
</div>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup>
import { ref } from 'vue' import { ref, reactive, computed } from 'vue'
import { PlusOutlined, FolderOutlined } from '@ant-design/icons-vue'
// //
interface Region { const columns = [
id: string {
name: string title: '名称',
} dataIndex: 'name',
key: 'name',
const regions = [ width: '30%'
{ id: 'a100', name: 'A100专区' }, },
{ id: 'v100', name: 'V100专区' }, {
{ id: 'foshan', name: '佛山区' }, title: '存储大小',
{ id: 'beijing', name: '北京B区' } dataIndex: 'size',
key: 'size',
align: 'right'
},
{
title: '购买时长',
dataIndex: 'duration',
key: 'duration',
align: 'right'
},
{
title: '费用',
dataIndex: 'cost',
key: 'cost',
align: 'right'
}
] ]
const selectedRegion = ref('a100') // A100 //
const storageList = ref([
{ id: 1, name: '项目A存储', size: 100, duration: 30, cost: 300 },
{ id: 2, name: '备份存储', size: 500, duration: 7, cost: 350 }
])
// //
const getSelectedRegionName = () => { const modalVisible = ref(false)
const region = regions.find(r => r.id === selectedRegion.value) const creating = ref(false)
return region ? region.name : ''
//
const createForm = reactive({
name: '',
size: 50,
duration: 30
})
// 0.1 / GB /
const totalCost = computed(() => {
return (createForm.size * createForm.duration * 0.1).toFixed(2)
})
//
const createFormRef = ref()
//
const showModal = () => {
createForm.name = ''
createForm.size = 50
createForm.duration = 30
modalVisible.value = true
} }
// //
const selectRegion = (id: string) => { const handleCreate = async () => {
selectedRegion.value = id try {
} await createFormRef.value.validateFields()
creating.value = true
//
const initializeStorage = () => { // API
alert(`正在初始化 ${selectedRegion.value} 的文件存储...`) await new Promise(resolve => setTimeout(resolve, 800))
//
} //
const newStorage = {
// id: Date.now(),
const viewHelp = () => { name: createForm.name,
alert('文件存储使用介绍') size: createForm.size,
} duration: createForm.duration,
cost: parseFloat(totalCost.value)
// }
const viewPricing = () => { storageList.value.unshift(newStorage)
alert('计费规则详情') modalVisible.value = false
// API
console.log('创建成功:', newStorage)
} catch (error) {
console.log('校验失败:', error)
} finally {
creating.value = false
}
} }
</script> </script>
<style scoped> <style scoped>
.container { .file-storage-page {
/* max-width: 800px; padding: 24px;
margin: 0 auto; */ background: #f5f7fa;
padding: 40px 30px; min-height: 100vh;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
color: #333;
} }
/* 标题区域 */ .page-header {
.header {
margin-bottom: 40px;
}
h1 {
font-size: 2rem;
font-weight: 600;
color: #1a1a1a;
margin-bottom: 8px;
}
/* 区域选择 */
.region-section {
margin-bottom: 40px;
}
.section-title {
font-size: 1.1rem;
font-weight: 500;
color: #555;
margin-bottom: 16px;
}
.region-tabs {
display: flex;
flex-wrap: wrap;
gap: 12px;
}
.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; display: flex;
justify-content: space-between;
align-items: center; align-items: center;
justify-content: center; margin-bottom: 24px;
background: #f5f5f5; padding-bottom: 16px;
border-bottom: 1px solid #e8e8e8;
}
.page-title {
margin: 0;
font-size: 20px;
font-weight: 600;
color: #1f2d3d;
}
.create-btn {
height: 40px;
padding: 0 20px;
font-weight: 500;
}
.table-container {
background: white;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.03);
overflow: hidden;
}
.storage-table {
border-radius: 8px; border-radius: 8px;
} }
.icon-book { .storage-table :deep(.ant-table-thead > tr > th) {
font-size: 36px; background: #fafbfc;
color: #3498db; font-weight: 600;
color: #5e6d82;
border-bottom: 2px solid #e8e8e8;
padding: 16px;
} }
.icon-book::before { .storage-table :deep(.ant-table-tbody > tr > td) {
content: "📁"; padding: 16px;
border-bottom: 1px solid #f0f0f0;
transition: background-color 0.2s;
} }
/* 操作区域 */ .storage-table :deep(.ant-table-tbody > tr:hover > td) {
.action-section { background-color: #f9fafc;
margin: 40px 0;
text-align: center;
} }
.btn-primary { .name-cell {
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 {
background-color: #2980b9;
}
.action-hint {
margin-top: 12px;
font-size: 0.9rem;
color: #666;
}
/* 帮助区域 */
.help-section {
margin-top: 50px;
}
.help-links {
display: flex; display: flex;
gap: 30px; align-items: center;
justify-content: center; gap: 10px;
font-weight: 500;
} }
.link { .folder-icon {
text-decoration: none; color: #1890ff;
color: #3498db; font-size: 18px;
}
.data-cell {
color: #5e6d82;
font-weight: 500;
}
.cost-cell {
color: #1890ff;
font-weight: 600;
font-size: 15px;
}
.create-form {
padding: 8px 0;
}
.create-form :deep(.ant-form-item-label) {
font-weight: 500;
}
.create-form :deep(.ant-input-number),
.create-form :deep(.ant-input) {
border-radius: 6px;
}
.cost-summary {
background: #f8f9fa;
border-radius: 6px;
padding: 16px 20px;
margin-top: 8px;
border: 1px solid #e8e8e8;
}
.cost-label {
font-size: 14px; font-size: 14px;
transition: color 0.2s; color: #5e6d82;
margin-bottom: 6px;
} }
.link:hover { .cost-amount {
color: #2980b9; display: flex;
text-decoration: underline; align-items: baseline;
gap: 10px;
} }
/* 响应式设计 */ .amount {
@media (max-width: 768px) { font-size: 24px;
.container { font-weight: 600;
padding: 30px 20px; color: #1890ff;
} }
.region-tabs { .cost-note {
flex-direction: column; font-size: 13px;
} color: #8c98ae;
}
.region-tab {
width: 100%; .create-modal :deep(.ant-modal-header) {
max-width: 100%; border-bottom: 1px solid #e8e8e8;
} padding: 20px 24px;
}
.help-links {
flex-direction: column; .create-modal :deep(.ant-modal-title) {
gap: 15px; font-size: 18px;
} font-weight: 600;
}
.center-section {
justify-content: center; .create-modal :deep(.ant-modal-footer) {
} border-top: 1px solid #e8e8e8;
padding: 16px 24px;
} }
</style> </style>