|
|
@@ -1,15 +1,63 @@
|
|
|
<template>
|
|
|
<div class="app-container">
|
|
|
+ <!-- ===== 统计卡片 ===== -->
|
|
|
+ <el-row :gutter="16" class="mb16">
|
|
|
+ <el-col :span="6">
|
|
|
+ <el-card shadow="hover" class="stat-card">
|
|
|
+ <div class="stat-label">服务器总数</div>
|
|
|
+ <div class="stat-value">{{ stats.serverCount }}</div>
|
|
|
+ </el-card>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="6">
|
|
|
+ <el-card shadow="hover" class="stat-card">
|
|
|
+ <div class="stat-label">总容量</div>
|
|
|
+ <div class="stat-value">{{ stats.totalCapacity }}</div>
|
|
|
+ </el-card>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="6">
|
|
|
+ <el-card shadow="hover" class="stat-card stat-used">
|
|
|
+ <div class="stat-label">已使用</div>
|
|
|
+ <div class="stat-value">{{ stats.used }}</div>
|
|
|
+ </el-card>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="6">
|
|
|
+ <el-card shadow="hover" class="stat-card stat-remain">
|
|
|
+ <div class="stat-label">剩余可用</div>
|
|
|
+ <div class="stat-value">{{ stats.remaining }}</div>
|
|
|
+ </el-card>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+
|
|
|
+ <!-- ===== 租户使用概览 ===== -->
|
|
|
+ <el-card shadow="never" class="mb16" v-if="stats.tenantStats && stats.tenantStats.length > 0">
|
|
|
+ <div slot="header" class="clearfix">
|
|
|
+ <span>租户 iPad 使用概览</span>
|
|
|
+ </div>
|
|
|
+ <el-table :data="stats.tenantStats" size="mini" border style="width:100%">
|
|
|
+ <el-table-column label="租户名称" prop="companyName" min-width="160" />
|
|
|
+ <el-table-column label="已绑定iPad数" prop="usedCount" width="120" align="center" />
|
|
|
+ <el-table-column label="限制数" prop="maxPadNum" width="100" align="center">
|
|
|
+ <template slot-scope="s">
|
|
|
+ {{ s.row.maxPadNum === -1 ? '不限' : s.row.maxPadNum }}
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="状态" prop="status" width="100" align="center">
|
|
|
+ <template slot-scope="s">
|
|
|
+ <el-tag :type="s.row.status === '超限' ? 'danger' : 'success'" size="mini">{{ s.row.status }}</el-tag>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+ </el-card>
|
|
|
+
|
|
|
<!-- ===== 搜索区 ===== -->
|
|
|
<el-card shadow="never" class="mb16 filter-card">
|
|
|
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true">
|
|
|
- <el-form-item label="服务器地址" prop="serverIp">
|
|
|
- <el-input v-model="queryParams.serverIp" placeholder="请输入服务器IP" clearable style="width:200px" @keyup.enter.native="handleQuery" />
|
|
|
+ <el-form-item label="服务器IP" prop="ip">
|
|
|
+ <el-input v-model="queryParams.ip" placeholder="请输入服务器IP" clearable style="width:200px" @keyup.enter.native="handleQuery" />
|
|
|
</el-form-item>
|
|
|
- <el-form-item label="状态" prop="status">
|
|
|
- <el-select v-model="queryParams.status" placeholder="全部状态" clearable style="width:120px">
|
|
|
- <el-option label="在线" :value="1" />
|
|
|
- <el-option label="离线" :value="0" />
|
|
|
+ <el-form-item label="租户" prop="companyId">
|
|
|
+ <el-select v-model="queryParams.companyId" placeholder="全部租户" clearable style="width:180px">
|
|
|
+ <el-option v-for="t in tenantOptions" :key="t.companyId" :label="t.companyName" :value="t.companyId" />
|
|
|
</el-select>
|
|
|
</el-form-item>
|
|
|
<el-form-item>
|
|
|
@@ -32,17 +80,29 @@
|
|
|
|
|
|
<!-- ===== 列表 ===== -->
|
|
|
<el-table border v-loading="loading" :data="dataList" size="small" style="width:100%">
|
|
|
- <el-table-column label="服务器ID" prop="id" width="80" align="center" />
|
|
|
- <el-table-column label="服务器地址/IP" prop="serverIp" min-width="180" />
|
|
|
- <el-table-column label="端口" prop="port" width="100" align="center" />
|
|
|
- <el-table-column label="状态" prop="status" width="100" align="center">
|
|
|
+ <el-table-column label="ID" prop="id" width="60" align="center" />
|
|
|
+ <el-table-column label="标题" prop="title" min-width="120" />
|
|
|
+ <el-table-column label="服务器IP" prop="ip" min-width="140" />
|
|
|
+ <el-table-column label="端口" prop="port" width="80" align="center" />
|
|
|
+ <el-table-column label="总容量" prop="totalCount" width="80" align="center" />
|
|
|
+ <el-table-column label="剩余" prop="count" width="70" align="center">
|
|
|
+ <template slot-scope="s">
|
|
|
+ <span :class="{'text-danger': s.row.count !== null && s.row.count <= 0}">{{ s.row.count }}</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="已分配租户数" prop="tenantCount" width="110" align="center">
|
|
|
<template slot-scope="s">
|
|
|
- <el-tag v-if="s.row.status === 1" type="success" size="mini">在线</el-tag>
|
|
|
- <el-tag v-else-if="s.row.status === 0" type="danger" size="mini">离线</el-tag>
|
|
|
- <el-tag v-else size="mini">{{ s.row.status || '-' }}</el-tag>
|
|
|
+ <el-tag v-if="s.row.tenantCount > 0" size="mini" type="warning">{{ s.row.tenantCount }}</el-tag>
|
|
|
+ <span v-else>-</span>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
- <el-table-column label="在线设备数" prop="onlineDeviceCount" width="100" align="center" />
|
|
|
+ <el-table-column label="已绑定用户数" prop="usedCount" width="110" align="center">
|
|
|
+ <template slot-scope="s">
|
|
|
+ <span v-if="s.row.usedCount > 0">{{ s.row.usedCount }} / {{ s.row.totalCount }}</span>
|
|
|
+ <span v-else>-</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="使用租户" prop="tenantNames" min-width="160" show-overflow-tooltip />
|
|
|
<el-table-column label="创建时间" prop="createTime" width="160" align="center" />
|
|
|
<el-table-column label="操作" align="center" width="160" fixed="right" class-name="small-padding fixed-width">
|
|
|
<template slot-scope="s">
|
|
|
@@ -57,11 +117,23 @@
|
|
|
<!-- ===== 编辑弹窗 ===== -->
|
|
|
<el-dialog :title="formTitle" :visible.sync="dialogVisible" width="500px" append-to-body>
|
|
|
<el-form ref="form" :model="form" :rules="rules" label-width="100px">
|
|
|
- <el-form-item label="服务器地址" prop="serverIp">
|
|
|
- <el-input v-model="form.serverIp" placeholder="请输入服务器地址/IP" />
|
|
|
+ <el-form-item label="标题" prop="title">
|
|
|
+ <el-input v-model="form.title" placeholder="请输入服务器标题" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="服务器地址" prop="ip">
|
|
|
+ <el-input v-model="form.ip" placeholder="请输入服务器IP" />
|
|
|
</el-form-item>
|
|
|
<el-form-item label="端口" prop="port">
|
|
|
- <el-input-number v-model="form.port" :min="1" :max="65535" :precision="0" style="width:100%" placeholder="请输入端口号" />
|
|
|
+ <el-input v-model="form.port" placeholder="请输入端口号" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="URL" prop="url">
|
|
|
+ <el-input v-model="form.url" placeholder="请输入URL(可选)" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="总容量" prop="totalCount">
|
|
|
+ <el-input-number v-model="form.totalCount" :min="0" :precision="0" style="width:100%" placeholder="总容量" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="剩余数量" prop="count">
|
|
|
+ <el-input-number v-model="form.count" :min="0" :precision="0" style="width:100%" placeholder="剩余数量" />
|
|
|
</el-form-item>
|
|
|
<el-form-item label="备注" prop="remark">
|
|
|
<el-input v-model="form.remark" type="textarea" :rows="2" placeholder="请输入备注" />
|
|
|
@@ -76,6 +148,7 @@
|
|
|
</template>
|
|
|
|
|
|
<script>
|
|
|
+import { listIpadServers, getIpadStats } from '@/api/admin/ipadServer'
|
|
|
import request from '@/utils/request'
|
|
|
|
|
|
export default {
|
|
|
@@ -86,28 +159,53 @@ export default {
|
|
|
loading: false,
|
|
|
dataList: [],
|
|
|
total: 0,
|
|
|
- queryParams: { pageNum: 1, pageSize: 10, serverIp: '', status: '' },
|
|
|
+ queryParams: { pageNum: 1, pageSize: 10, ip: '', companyId: '' },
|
|
|
+ stats: { serverCount: 0, totalCapacity: 0, used: 0, remaining: 0, tenantStats: [] },
|
|
|
+ tenantOptions: [],
|
|
|
dialogVisible: false,
|
|
|
formTitle: '',
|
|
|
submitting: false,
|
|
|
- form: { id: null, serverIp: '', port: 8080, remark: '' },
|
|
|
+ form: { id: null, title: '', ip: '', port: '8080', url: '', totalCount: 0, count: 0, remark: '' },
|
|
|
rules: {
|
|
|
- serverIp: [{ required: true, message: '请输入服务器地址', trigger: 'blur' }],
|
|
|
+ ip: [{ required: true, message: '请输入服务器地址', trigger: 'blur' }],
|
|
|
port: [{ required: true, message: '请输入端口', trigger: 'blur' }]
|
|
|
}
|
|
|
}
|
|
|
},
|
|
|
created() {
|
|
|
+ this.loadStats()
|
|
|
this.loadList()
|
|
|
},
|
|
|
methods: {
|
|
|
+ /** 加载统计数据 */
|
|
|
+ loadStats() {
|
|
|
+ getIpadStats().then(res => {
|
|
|
+ const data = res.data
|
|
|
+ this.stats = {
|
|
|
+ serverCount: data.serverCount || 0,
|
|
|
+ totalCapacity: data.totalCapacity || 0,
|
|
|
+ used: data.used || 0,
|
|
|
+ remaining: data.remaining || 0,
|
|
|
+ tenantStats: data.tenantStats || []
|
|
|
+ }
|
|
|
+ // 租户筛选下拉框复用统计数据
|
|
|
+ this.tenantOptions = (data.tenantStats || []).map(t => ({
|
|
|
+ companyId: t.companyId,
|
|
|
+ companyName: t.companyName
|
|
|
+ }))
|
|
|
+ })
|
|
|
+ },
|
|
|
+ /** 加载服务器列表 */
|
|
|
loadList() {
|
|
|
this.loading = true
|
|
|
- request({ url: '/qw/qwIpadServer/list', method: 'get', params: this.queryParams }).then(r => {
|
|
|
+ const params = { pageNum: this.queryParams.pageNum, pageSize: this.queryParams.pageSize }
|
|
|
+ if (this.queryParams.ip) params.ip = this.queryParams.ip
|
|
|
+ if (this.queryParams.companyId) params.companyId = this.queryParams.companyId
|
|
|
+ listIpadServers(params).then(r => {
|
|
|
this.dataList = r.rows
|
|
|
this.total = r.total
|
|
|
this.loading = false
|
|
|
- })
|
|
|
+ }).catch(() => { this.loading = false })
|
|
|
},
|
|
|
openDialog(row) {
|
|
|
this.reset()
|
|
|
@@ -133,14 +231,16 @@ export default {
|
|
|
this.$message.success(this.form.id ? '修改成功' : '新增成功')
|
|
|
this.dialogVisible = false
|
|
|
this.loadList()
|
|
|
+ this.loadStats()
|
|
|
}).finally(() => { this.submitting = false })
|
|
|
})
|
|
|
},
|
|
|
handleDelete(row) {
|
|
|
- this.$confirm(`确认删除服务器 "${row.serverIp}"?`, '提示', { type: 'warning' }).then(() => {
|
|
|
+ this.$confirm(`确认删除服务器 "${row.ip || row.title}"?`, '提示', { type: 'warning' }).then(() => {
|
|
|
request({ url: `/qw/qwIpadServer/${row.id}`, method: 'delete' }).then(() => {
|
|
|
this.$message.success('删除成功')
|
|
|
this.loadList()
|
|
|
+ this.loadStats()
|
|
|
})
|
|
|
})
|
|
|
},
|
|
|
@@ -160,11 +260,11 @@ export default {
|
|
|
},
|
|
|
resetQuery() {
|
|
|
this.$refs['queryForm'].resetFields()
|
|
|
- this.queryParams = { pageNum: 1, pageSize: 10, serverIp: '', status: '' }
|
|
|
+ this.queryParams = { pageNum: 1, pageSize: 10, ip: '', companyId: '' }
|
|
|
this.loadList()
|
|
|
},
|
|
|
reset() {
|
|
|
- this.form = { id: null, serverIp: '', port: 8080, remark: '' }
|
|
|
+ this.form = { id: null, title: '', ip: '', port: '8080', url: '', totalCount: 0, count: 0, remark: '' }
|
|
|
if (this.$refs['form']) this.$refs['form'].resetFields()
|
|
|
}
|
|
|
}
|
|
|
@@ -175,5 +275,10 @@ export default {
|
|
|
.mb8 { margin-bottom: 8px; }
|
|
|
.mb16 { margin-bottom: 16px; }
|
|
|
.filter-card { padding-bottom: 0; }
|
|
|
-
|
|
|
+.stat-card { text-align: center; }
|
|
|
+.stat-card .stat-label { font-size: 13px; color: #909399; margin-bottom: 8px; }
|
|
|
+.stat-card .stat-value { font-size: 28px; font-weight: bold; color: #303133; }
|
|
|
+.stat-card.stat-used .stat-value { color: #e6a23c; }
|
|
|
+.stat-card.stat-remain .stat-value { color: #67c23a; }
|
|
|
+.text-danger { color: #f5222d; font-weight: bold; }
|
|
|
</style>
|