|
|
@@ -1,204 +1,211 @@
|
|
|
<template>
|
|
|
- <el-container>
|
|
|
- <el-header height="220">
|
|
|
- <h3>对象列表</h3>
|
|
|
- <el-row style="margin-top: 10px">
|
|
|
- <el-button type="plain" icon="el-icon-files">存储节点</el-button>
|
|
|
- <el-select
|
|
|
- v-model="selectedValue"
|
|
|
- style="margin-left: 5px"
|
|
|
- @change="onSelectChange"
|
|
|
- >
|
|
|
- <el-option v-for="(item, index) in tableList" :key="index" :label="item.label" :value="item.value" />
|
|
|
+ <el-container class="admin-store-container">
|
|
|
+ <el-header height="auto" style="padding: 20px; background: #fff; border-bottom: 1px solid #e6e6e6;">
|
|
|
+ <div class="header-top">
|
|
|
+ <div class="title-section">
|
|
|
+ <i class="el-icon-folder-opened" style="color: #409EFF; font-size: 24px; margin-right: 10px;" />
|
|
|
+ <h3 style="margin: 0; display: inline-block;">对象列表</h3>
|
|
|
+ </div>
|
|
|
+ <div class="action-section">
|
|
|
+ <el-button type="primary" icon="el-icon-upload" @click="handleOpenUpload">上传文件</el-button>
|
|
|
+ <el-button type="success" icon="el-icon-refresh" plain @click="onRefresh">刷新</el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <el-row type="flex" align="middle" style="margin-top: 20px; background: #f8f9fa; padding: 10px; border-radius: 4px;">
|
|
|
+ <span style="font-size: 14px; color: #606266; margin-right: 10px;">当前节点:</span>
|
|
|
+ <el-select v-model="selectedValue" placeholder="请选择存储节点" size="small" @change="onSelectChange">
|
|
|
+ <el-option v-for="(item, index) in tableList" :key="index" :label="item.label" :value="item.value">
|
|
|
+ <i class="el-icon-cpu" style="margin-right: 8px;" />
|
|
|
+ <span>{{ item.label }}</span>
|
|
|
+ </el-option>
|
|
|
</el-select>
|
|
|
+ <el-divider direction="vertical" />
|
|
|
+ <el-tag size="small" type="info">共 {{ dataList.length }} 个对象</el-tag>
|
|
|
</el-row>
|
|
|
</el-header>
|
|
|
+
|
|
|
<el-main>
|
|
|
<el-table
|
|
|
+ v-loading="loading"
|
|
|
:data="dataList"
|
|
|
border
|
|
|
- height="480"
|
|
|
- style="width: 100%"
|
|
|
+ stripe
|
|
|
+ height="calc(100vh - 280px)"
|
|
|
+ style="width: 100%; border-radius: 8px; overflow: hidden;"
|
|
|
+ :header-cell-style="{background:'#f5f7fa', color:'#606266'}"
|
|
|
>
|
|
|
- <el-table-column
|
|
|
- fixed="left"
|
|
|
- label="No"
|
|
|
- type="index"
|
|
|
- />
|
|
|
- <el-table-column
|
|
|
- prop="nodeAddr"
|
|
|
- label="文件名"
|
|
|
- />
|
|
|
- <el-table-column
|
|
|
- prop="httpPort"
|
|
|
- label="修改时间"
|
|
|
- />
|
|
|
- <el-table-column
|
|
|
- prop="rpcPort"
|
|
|
- label="大小"
|
|
|
- />
|
|
|
- <el-table-column
|
|
|
- prop="status"
|
|
|
- label="文件类型"
|
|
|
- />
|
|
|
- <el-table-column
|
|
|
- fixed="right"
|
|
|
- label="操作"
|
|
|
- width="280"
|
|
|
- >
|
|
|
+ <el-table-column prop="nodeAddr" label="对象名" min-width="200" />
|
|
|
+ <el-table-column prop="nodeAddr" label="文件名" min-width="200">
|
|
|
+ <template slot-scope="scope">
|
|
|
+ <div style="display: flex; align-items: center">
|
|
|
+ <i class="el-icon-document" style="font-size: 18px; margin-right: 10px; color: #909399;" />
|
|
|
+ <span style="font-weight: 500">{{ scope.row.nodeAddr }}</span>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="rpcPort" label="大小" width="120">
|
|
|
+ <template slot-scope="scope">{{ formatSize(scope.row.rpcPort) }}</template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="status" label="类型" width="120" align="center" />
|
|
|
+ <el-table-column prop="httpPort" label="修改时间" width="180" />
|
|
|
+ <el-table-column fixed="right" label="操作" width="260" align="center">
|
|
|
<template slot-scope="scope">
|
|
|
- <el-button
|
|
|
- size="mini"
|
|
|
- @click="handleEdit(scope.$index, scope.row)"
|
|
|
- >磁盘详情</el-button>
|
|
|
- <el-button
|
|
|
- size="mini"
|
|
|
- @click="handleEdit(scope.$index, scope.row)"
|
|
|
- >禁用</el-button>
|
|
|
- <el-button
|
|
|
- size="mini"
|
|
|
- type="danger"
|
|
|
- @click="handleDelete(scope.$index, scope.row)"
|
|
|
- >删除</el-button>
|
|
|
+ <el-button size="mini" type="text" icon="el-icon-monitor" @click="handleMonitor">磁盘详情</el-button>
|
|
|
+ <el-button size="mini" type="text" icon="el-icon-delete" style="color: #F56C6C" @click="handleDelete(scope.$index, scope.row)">删除</el-button>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
</el-table>
|
|
|
</el-main>
|
|
|
|
|
|
<el-dialog
|
|
|
- append-to-body
|
|
|
- :visible.sync="showEditScopeDialog"
|
|
|
+ title="上传对象至存储节点"
|
|
|
+ :visible.sync="showUploadDialog"
|
|
|
+ width="550px"
|
|
|
center
|
|
|
+ :close-on-click-modal="false"
|
|
|
+ custom-class="upload-dialog"
|
|
|
>
|
|
|
- <div>
|
|
|
- <h3>详情</h3>
|
|
|
- <el-table
|
|
|
- :data="tableList"
|
|
|
- border
|
|
|
- height="480"
|
|
|
- style="width: 100%"
|
|
|
- >
|
|
|
- <el-table-column
|
|
|
- fixed="left"
|
|
|
- label="No"
|
|
|
- type="index"
|
|
|
- />
|
|
|
- <el-table-column
|
|
|
- prop="fsType"
|
|
|
- label="文件系统"
|
|
|
- />
|
|
|
- <el-table-column
|
|
|
- prop="volume"
|
|
|
- label="磁盘分区"
|
|
|
- />
|
|
|
- <el-table-column
|
|
|
- prop="storeDir"
|
|
|
- label="存储目录"
|
|
|
- />
|
|
|
- <el-table-column
|
|
|
- prop="total"
|
|
|
- label="分区容量"
|
|
|
- >
|
|
|
- <template slot-scope="scope">
|
|
|
- <el-progress :percentage="scope.row.percent"></el-progress>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- <el-table-column
|
|
|
- prop="totalInode"
|
|
|
- label="inode 容量"
|
|
|
- >
|
|
|
- <template slot-scope="scope">
|
|
|
- <el-progress :percentage="scope.row.percentInode"></el-progress>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- </el-table>
|
|
|
+ <uploader-card
|
|
|
+ v-if="showUploadDialog && uploadConfig"
|
|
|
+ :upload-config="uploadConfig"
|
|
|
+ @success="handleUploadSuccess"
|
|
|
+ />
|
|
|
+ <div v-else style="text-align: center; padding: 40px;">
|
|
|
+ <el-button v-if="!uploadConfig" type="text" loading>正在获取上传凭证...</el-button>
|
|
|
+ </div>
|
|
|
+ <div slot="footer" class="dialog-footer">
|
|
|
+ <el-button @click="showUploadDialog = false">关闭</el-button>
|
|
|
</div>
|
|
|
</el-dialog>
|
|
|
+
|
|
|
+ <el-dialog title="节点磁盘资源监控" :visible.sync="showEditScopeDialog" width="70%">
|
|
|
+ <el-table :data="tableList" border stripe size="small">
|
|
|
+ <el-table-column label="No" type="index" width="50" />
|
|
|
+ <el-table-column prop="fsType" label="文件系统" width="100" />
|
|
|
+ <el-table-column prop="volume" label="磁盘分区" />
|
|
|
+ <el-table-column prop="storeDir" label="存储目录" />
|
|
|
+ <el-table-column label="分区容量使用率" width="200">
|
|
|
+ <template slot-scope="scope">
|
|
|
+ <el-progress :percentage="scope.row.percent || 0" :color="customColors" />
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+ </el-dialog>
|
|
|
</el-container>
|
|
|
</template>
|
|
|
|
|
|
<script>
|
|
|
+import UploaderCard from 'components/card/UploaderCard.vue'
|
|
|
import { getUserNodeKeyValue } from '@/api/oss'
|
|
|
+import { getVideoChannelInfo } from '@/api/file'
|
|
|
|
|
|
export default {
|
|
|
name: 'AdminStoreObject',
|
|
|
+ components: { UploaderCard },
|
|
|
data() {
|
|
|
return {
|
|
|
- queryInfo: {
|
|
|
- path: null
|
|
|
- },
|
|
|
- // 屏幕宽度, 为了控制分页条的大小
|
|
|
- screenWidth: document.body.clientWidth,
|
|
|
- currentPage: 1,
|
|
|
- pageSize: 12,
|
|
|
- totalSize: 0,
|
|
|
+ loading: false,
|
|
|
+ showUploadDialog: false,
|
|
|
+ showEditScopeDialog: false,
|
|
|
+ selectedValue: null,
|
|
|
+ uploadConfig: null, // 上传所需的通道配置
|
|
|
dataList: [],
|
|
|
tableList: [],
|
|
|
- nextId: 0,
|
|
|
- // **********************************************************************
|
|
|
- showEditScopeDialog: false,
|
|
|
- form: {
|
|
|
- videoId: null,
|
|
|
- scope: 1
|
|
|
- },
|
|
|
- selectedValue: null
|
|
|
+ customColors: [
|
|
|
+ { color: '#67C23A', percentage: 40 },
|
|
|
+ { color: '#E6A23C', percentage: 70 },
|
|
|
+ { color: '#F56C6C', percentage: 90 }
|
|
|
+ ]
|
|
|
}
|
|
|
},
|
|
|
created() {
|
|
|
- document.title = '我的节点'
|
|
|
+ document.title = '我的存储节点'
|
|
|
this.getData()
|
|
|
},
|
|
|
methods: {
|
|
|
- getData() {
|
|
|
- this.tableList = []
|
|
|
- getUserNodeKeyValue().then(resp => {
|
|
|
+ // 1. 初始化数据
|
|
|
+ async getData() {
|
|
|
+ this.loading = true
|
|
|
+ try {
|
|
|
+ const resp = await getUserNodeKeyValue()
|
|
|
if (resp.code === 0) {
|
|
|
- this.tableList = resp.data
|
|
|
+ this.tableList = resp.data || []
|
|
|
+ if (this.tableList.length > 0 && !this.selectedValue) {
|
|
|
+ this.selectedValue = this.tableList[0].value
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } finally {
|
|
|
+ this.loading = false
|
|
|
+ }
|
|
|
+ },
|
|
|
|
|
|
- this.selectedValue = this.tableList[0].value
|
|
|
- this.dataList = []
|
|
|
+ // 2. 处理上传逻辑
|
|
|
+ async handleOpenUpload() {
|
|
|
+ this.showUploadDialog = true
|
|
|
+ // 仅在配置为空时请求,或每次打开都更新
|
|
|
+ if (!this.uploadConfig) {
|
|
|
+ const resp = await getVideoChannelInfo()
|
|
|
+ if (resp.code === 0) {
|
|
|
+ this.uploadConfig = resp.data
|
|
|
+ this.uploadConfig.title = '上传文件'
|
|
|
+ this.uploadConfig.fileAttrs = { accept: 'image/*' }
|
|
|
} else {
|
|
|
- this.$message.error(resp.msg)
|
|
|
+ this.$message.error('获取上传通道失败')
|
|
|
}
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ handleUploadSuccess(result) {
|
|
|
+ const { uploadId, file } = result
|
|
|
+ this.$notify({
|
|
|
+ title: '上传成功',
|
|
|
+ message: `文件 [${file.name}] 已上传完成`,
|
|
|
+ type: 'success'
|
|
|
})
|
|
|
+ // 可以在此处调用额外的业务 API 将 uploadId 与当前节点关联
|
|
|
+ // this.bindFileToNode(uploadId, this.selectedValue)
|
|
|
+
|
|
|
+ this.onRefresh() // 刷新列表
|
|
|
+ },
|
|
|
+
|
|
|
+ // 3. 通用方法
|
|
|
+ formatSize(bytes) {
|
|
|
+ if (!bytes) return '0 B'
|
|
|
+ const k = 1024
|
|
|
+ const sizes = ['B', 'KB', 'MB', 'GB', 'TB']
|
|
|
+ const i = Math.floor(Math.log(bytes) / Math.log(k))
|
|
|
+ return (bytes / Math.pow(k, i)).toFixed(2) + ' ' + sizes[i]
|
|
|
},
|
|
|
onRefresh() {
|
|
|
this.getData()
|
|
|
},
|
|
|
- handleCurrentChange(pageNumber) {
|
|
|
- this.currentPage = pageNumber
|
|
|
+ onSelectChange(val) {
|
|
|
this.getData()
|
|
|
- // 回到顶部
|
|
|
- scrollTo(0, 0)
|
|
|
},
|
|
|
- handleEdit(index, row) {
|
|
|
+ handleMonitor() {
|
|
|
this.showEditScopeDialog = true
|
|
|
},
|
|
|
handleDelete(index, row) {
|
|
|
- this.$confirm('确定要删除 ' + row.title + '?', '提示', {
|
|
|
- confirmButtonText: '确定',
|
|
|
- cancelButtonText: '取消',
|
|
|
- type: 'warning'
|
|
|
- }).then(() => {
|
|
|
- this.$message.info('handleDelete')
|
|
|
- }).catch(() => {
|
|
|
- this.$message({
|
|
|
- type: 'info',
|
|
|
- message: '已取消'
|
|
|
- })
|
|
|
- })
|
|
|
- },
|
|
|
- onAddChannel() {
|
|
|
- this.showEditScopeDialog = false
|
|
|
- },
|
|
|
- onSelectChange() {
|
|
|
- this.dataList = []
|
|
|
- },
|
|
|
- handleClose() {
|
|
|
+ this.$confirm(`永久删除文件 [${row.nodeAddr}], 是否继续?`, '提示', { type: 'warning' })
|
|
|
+ .then(() => { this.$message.success('模拟删除成功') })
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
</script>
|
|
|
|
|
|
-<style>
|
|
|
+<style scoped lang="scss">
|
|
|
+.admin-store-container {
|
|
|
+ background-color: #f0f2f5;
|
|
|
+ min-height: 100vh;
|
|
|
+ .header-top { display: flex; justify-content: space-between; align-items: center; }
|
|
|
+ .el-main { padding: 20px; }
|
|
|
+ .shadow-box { border: none; border-radius: 8px; box-shadow: 0 2px 12px 0 rgba(0,0,0,0.05); }
|
|
|
+}
|
|
|
+
|
|
|
+/* 样式穿透:让弹窗内的 Card 看起来像对话框的一部分 */
|
|
|
+::v-deep .upload-dialog {
|
|
|
+ .el-dialog__body { padding: 10px 20px 30px; }
|
|
|
+ .upload-card { box-shadow: none !important; border: 1px solid #ebeef5; }
|
|
|
+}
|
|
|
</style>
|