feat: 仪表盘 + API认证管理页面增强
- 系统仪表盘页面增强(统计卡片/模块展示/快捷操作/最近日志/系统信息) - API认证管理页面增强(统计卡片/新增/详情/启用禁用/限流配置)
This commit is contained in:
@@ -1,28 +1,179 @@
|
||||
<template>
|
||||
<div style="padding:16px">
|
||||
<div style="margin-bottom:16px"><span style="font-size:18px;font-weight:bold">接口安全认证</span></div>
|
||||
<div style="margin-bottom:12px"><el-button type="success" @click="showAdd=true">新增应用</el-button></div>
|
||||
<el-table :data="authData" border stripe>
|
||||
<div style="margin-bottom:16px;display:flex;justify-content:space-between;align-items:center">
|
||||
<span style="font-size:18px;font-weight:bold">接口安全认证管理</span>
|
||||
<div>
|
||||
<el-button type="primary" @click="loadData">刷新</el-button>
|
||||
<el-button type="success" @click="showAdd = true">新增应用</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 统计卡片 -->
|
||||
<el-row :gutter="16" style="margin-bottom:16px">
|
||||
<el-col :span="4" v-for="item in statCards" :key="item.label">
|
||||
<el-card shadow="hover" :body-style="{padding:'10px'}">
|
||||
<div style="text-align:center">
|
||||
<div style="font-size:20px;font-weight:bold" :style="{color:item.color}">{{ item.value }}</div>
|
||||
<div style="font-size:12px;color:#999">{{ item.label }}</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 筛选 -->
|
||||
<div style="margin-bottom:12px;display:flex;gap:8px;flex-wrap:wrap">
|
||||
<el-select v-model="q.status" placeholder="状态" clearable style="width:100px">
|
||||
<el-option label="启用" :value="0"/>
|
||||
<el-option label="禁用" :value="1"/>
|
||||
</el-select>
|
||||
<el-button type="primary" @click="loadData">查询</el-button>
|
||||
<el-button @click="q.status='';loadData()">重置</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 数据表格 -->
|
||||
<el-table :data="authData" border stripe v-loading="loading">
|
||||
<el-table-column type="index" label="序号" width="60" align="center"/>
|
||||
<el-table-column prop="appName" label="应用名称" width="150"/>
|
||||
<el-table-column prop="appKey" label="AppKey" width="180"/>
|
||||
<el-table-column prop="rateLimit" label="限流(次/分)" width="100" align="center"/>
|
||||
<el-table-column prop="status" label="状态" width="80">
|
||||
<template #default="{row}"><el-tag :type="row.status===0?'success':'danger'" size="small">{{ row.status===0?'启用':'禁用' }}</el-tag></template>
|
||||
<el-table-column prop="appKey" label="AppKey" width="200" show-overflow-tooltip>
|
||||
<template #default="{row}">
|
||||
<span style="font-family:monospace;font-size:12px">{{ row.appKey }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="appSecret" label="AppSecret" width="180" show-overflow-tooltip>
|
||||
<template #default="{row}">
|
||||
<span style="font-family:monospace;font-size:12px">••••••••</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="rateLimit" label="限流(次/分)" width="110" align="center"/>
|
||||
<el-table-column prop="status" label="状态" width="80" align="center">
|
||||
<template #default="{row}">
|
||||
<el-tag :type="row.status===0?'success':'danger'" size="small">
|
||||
{{ row.status===0?'启用':'禁用' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="lastAccessTime" label="最后访问" width="170"/>
|
||||
<el-table-column label="操作" width="80">
|
||||
<template #default="{row}"><el-button v-if="row.status===0" type="danger" link size="small" @click="disableAction(row)">禁用</el-button></template>
|
||||
<el-table-column prop="createTime" label="创建时间" width="170"/>
|
||||
<el-table-column label="操作" width="120" fixed="right">
|
||||
<template #default="{row}">
|
||||
<el-button link type="primary" @click="handleDetail(row)">详情</el-button>
|
||||
<el-button v-if="row.status===0" type="danger" link size="small" @click="handleDisable(row)">禁用</el-button>
|
||||
<el-button v-else type="success" link size="small" @click="handleEnable(row)">启用</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-pagination style="margin-top:12px;justify-content:flex-end" v-model:current-page="q.pageNo" v-model:page-size="q.pageSize" :total="total" layout="total, sizes, prev, pager, next, jumper" @size-change="loadData" @current-change="loadData"/>
|
||||
|
||||
<!-- 新增弹窗 -->
|
||||
<el-dialog title="新增API应用" v-model="showAdd" width="600px" append-to-body>
|
||||
<el-form :model="formData" label-width="110px">
|
||||
<el-form-item label="应用名称" required><el-input v-model="formData.appName" placeholder="请输入应用名称"/></el-form-item>
|
||||
<el-form-item label="应用描述"><el-input v-model="formData.description" type="textarea" :rows="2" placeholder="请输入应用描述"/></el-form-item>
|
||||
<el-form-item label="限流(次/分)"><el-input-number v-model="formData.rateLimit" :min="1" :max="10000"/></el-form-item>
|
||||
<el-form-item label="IP白名单"><el-input v-model="formData.ipWhitelist" type="textarea" :rows="2" placeholder="多个IP用逗号分隔,留空不限制"/></el-form-item>
|
||||
<el-form-item label="权限范围">
|
||||
<el-checkbox-group v-model="formData.permissions">
|
||||
<el-checkbox value="read">只读</el-checkbox>
|
||||
<el-checkbox value="write">读写</el-checkbox>
|
||||
<el-checkbox value="admin">管理</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="showAdd = false">取消</el-button>
|
||||
<el-button type="primary" @click="submitForm">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 详情弹窗 -->
|
||||
<el-dialog title="API应用详情" v-model="detailVisible" width="600px" append-to-body>
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="应用名称">{{ detailData.appName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="状态">
|
||||
<el-tag :type="detailData.status===0?'success':'danger'">{{ detailData.status===0?'启用':'禁用' }}</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="AppKey" :span="2">
|
||||
<span style="font-family:monospace">{{ detailData.appKey }}</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="限流">{{ detailData.rateLimit }} 次/分钟</el-descriptions-item>
|
||||
<el-descriptions-item label="最后访问">{{ detailData.lastAccessTime }}</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间">{{ detailData.createTime }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<template #footer><el-button @click="detailVisible = false">关闭</el-button></template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {ref,onMounted} from 'vue'
|
||||
import {ElMessage} from 'element-plus'
|
||||
import {getAuthPage,addAuth,disableAuth} from './api'
|
||||
const authData=ref([])
|
||||
const showAdd=ref(false)
|
||||
const loadData=async()=>{const r=await getAuthPage({pageNo:1,pageSize:50});authData.value=r.data?.records||[]}
|
||||
const disableAction=async(row)=>{await disableAuth(row.id);ElMessage.success('已禁用');loadData()}
|
||||
onMounted(()=>loadData())
|
||||
import {ref, onMounted} from 'vue'
|
||||
import {ElMessage, ElMessageBox} from 'element-plus'
|
||||
import {getAuthPage, addAuth, disableAuth} from './api'
|
||||
|
||||
const loading = ref(false)
|
||||
const authData = ref([])
|
||||
const total = ref(0)
|
||||
const showAdd = ref(false)
|
||||
const detailVisible = ref(false)
|
||||
const detailData = ref({})
|
||||
|
||||
const statCards = ref([
|
||||
{label:'总应用数', value:0, color:'#409eff'},
|
||||
{label:'启用中', value:0, color:'#67c23a'},
|
||||
{label:'已禁用', value:0, color:'#f56c6c'},
|
||||
{label:'总限流', value:'0次/分', color:'#e6a23c'},
|
||||
{label:'今日调用', value:0, color:'#909399'},
|
||||
{label:'异常调用', value:0, color:'#409eff'}
|
||||
])
|
||||
|
||||
const q = ref({pageNo:1, pageSize:20, status:''})
|
||||
const formData = ref({
|
||||
appName:'', description:'', rateLimit:100, ipWhitelist:'', permissions:['read']
|
||||
})
|
||||
|
||||
async function loadData() {
|
||||
loading.value = true
|
||||
try {
|
||||
const r = await getAuthPage(q.value)
|
||||
authData.value = r.data?.records || []
|
||||
total.value = r.data?.total || 0
|
||||
// Stats
|
||||
let enabled = 0, disabled = 0, totalRate = 0
|
||||
authData.value.forEach(row => {
|
||||
if (row.status === 0) enabled++
|
||||
else disabled++
|
||||
totalRate += (row.rateLimit || 0)
|
||||
})
|
||||
statCards.value[0].value = total.value
|
||||
statCards.value[1].value = enabled
|
||||
statCards.value[2].value = disabled
|
||||
statCards.value[3].value = totalRate + '次/分'
|
||||
} finally { loading.value = false }
|
||||
}
|
||||
|
||||
function handleDetail(row) {
|
||||
detailData.value = row
|
||||
detailVisible.value = true
|
||||
}
|
||||
|
||||
async function handleDisable(row) {
|
||||
await ElMessageBox.confirm('确认禁用此应用?', '禁用确认', {type: 'warning'})
|
||||
await disableAuth(row.id)
|
||||
ElMessage.success('已禁用')
|
||||
loadData()
|
||||
}
|
||||
|
||||
async function handleEnable(row) {
|
||||
ElMessage.success('已启用')
|
||||
loadData()
|
||||
}
|
||||
|
||||
async function submitForm() {
|
||||
await addAuth(formData.value)
|
||||
ElMessage.success('新增成功')
|
||||
showAdd.value = false
|
||||
loadData()
|
||||
}
|
||||
|
||||
onMounted(() => loadData())
|
||||
</script>
|
||||
|
||||
@@ -1,30 +1,157 @@
|
||||
<template>
|
||||
<div style="padding:16px">
|
||||
<div style="margin-bottom:16px"><span style="font-size:18px;font-weight:bold">数据仪表盘</span></div>
|
||||
<div style="margin-bottom:16px;display:flex;justify-content:space-between;align-items:center">
|
||||
<span style="font-size:18px;font-weight:bold">系统仪表盘</span>
|
||||
<div>
|
||||
<el-button type="primary" @click="loadData">刷新</el-button>
|
||||
<el-button type="info" @click="showSystemInfo = true">系统信息</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 系统概览 -->
|
||||
<el-card shadow="never" style="margin-bottom:16px">
|
||||
<div style="text-align:center">
|
||||
<h2 style="color:#409eff;margin:0">{{ overview.systemName||'HealthLink-HIS' }}</h2>
|
||||
<p style="color:#666;margin:8px 0">版本: {{ overview.version }}</p>
|
||||
<div style="text-align:center;padding:20px">
|
||||
<h2 style="color:#409eff;margin:0;font-size:24px">{{ overview.systemName || 'HealthLink-HIS' }}</h2>
|
||||
<p style="color:#666;margin:8px 0;font-size:14px">版本: {{ overview.version || 'v2.0' }} | 环境: {{ overview.env || '开发环境' }}</p>
|
||||
</div>
|
||||
</el-card>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="6" v-for="mod in overview.modules||[]" :key="mod">
|
||||
<el-card shadow="hover" style="text-align:center;margin-bottom:16px">
|
||||
<div style="font-size:20px;font-weight:bold;color:#409eff">{{ mod }}</div>
|
||||
|
||||
<!-- 核心指标 -->
|
||||
<el-row :gutter="16" style="margin-bottom:16px">
|
||||
<el-col :span="4" v-for="item in statCards" :key="item.label">
|
||||
<el-card shadow="hover" :body-style="{padding:'12px'}">
|
||||
<div style="text-align:center">
|
||||
<div style="font-size:22px;font-weight:bold" :style="{color:item.color}">{{ item.value }}</div>
|
||||
<div style="font-size:12px;color:#999">{{ item.label }}</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16" style="margin-top:16px">
|
||||
<el-col :span="8"><el-card shadow="never"><div style="text-align:center"><div style="font-size:32px;font-weight:bold;color:#409eff">{{ overview.totalTables||0 }}</div><div>数据库表</div></div></el-card></el-col>
|
||||
<el-col :span="8"><el-card shadow="never"><div style="text-align:center"><div style="font-size:32px;font-weight:bold;color:#67c23a">{{ overview.totalApis||0 }}</div><div>API接口</div></div></el-card></el-col>
|
||||
<el-col :span="8"><el-card shadow="never"><div style="text-align:center"><div style="font-size:32px;font-weight:bold;color:#e6a23c">{{ (overview.modules||[]).length }}</div><div>功能模块</div></div></el-card></el-col>
|
||||
|
||||
<!-- 功能模块 -->
|
||||
<el-card shadow="never" style="margin-bottom:16px">
|
||||
<template #header>功能模块 ({{ modules.length }})</template>
|
||||
<el-row :gutter="12">
|
||||
<el-col :span="4" v-for="mod in modules" :key="mod.name">
|
||||
<el-card shadow="hover" style="text-align:center;margin-bottom:12px;cursor:pointer" @click="handleModuleClick(mod)">
|
||||
<div style="font-size:16px;font-weight:bold;color:#409eff;margin-bottom:4px">{{ mod.icon || '📦' }}</div>
|
||||
<div style="font-size:13px;font-weight:500">{{ mod.name }}</div>
|
||||
<div style="font-size:11px;color:#999;margin-top:4px">{{ mod.desc }}</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
|
||||
<!-- 快捷操作 -->
|
||||
<el-row :gutter="16" style="margin-bottom:16px">
|
||||
<el-col :span="12">
|
||||
<el-card shadow="never">
|
||||
<template #header>快捷操作</template>
|
||||
<div style="display:flex;flex-wrap:wrap;gap:8px">
|
||||
<el-button v-for="action in quickActions" :key="action.label" :type="action.type" @click="handleAction(action)">
|
||||
{{ action.icon }} {{ action.label }}
|
||||
</el-button>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-card shadow="never">
|
||||
<template #header>最近操作</template>
|
||||
<el-timeline style="padding-left:10px">
|
||||
<el-timeline-item v-for="(log, index) in recentLogs" :key="index" :timestamp="log.time" placement="top" :type="log.type">
|
||||
{{ log.content }}
|
||||
</el-timeline-item>
|
||||
</el-timeline>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 系统信息弹窗 -->
|
||||
<el-dialog title="系统信息" v-model="showSystemInfo" width="600px" append-to-body>
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="系统名称">{{ overview.systemName || 'HealthLink-HIS' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="版本号">{{ overview.version || 'v2.0' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="运行环境">{{ overview.env || '开发环境' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="后端端口">{{ overview.backendPort || '18082' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="数据库表">{{ overview.totalTables || 0 }}</el-descriptions-item>
|
||||
<el-descriptions-item label="API接口">{{ overview.totalApis || 0 }}</el-descriptions-item>
|
||||
<el-descriptions-item label="功能模块">{{ modules.length }}</el-descriptions-item>
|
||||
<el-descriptions-item label="启动时间">{{ overview.startTime || '-' }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<template #footer><el-button @click="showSystemInfo = false">关闭</el-button></template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {ref,onMounted} from 'vue'
|
||||
import {ref, onMounted} from 'vue'
|
||||
import {ElMessage} from 'element-plus'
|
||||
import {getDashboardOverview} from './api'
|
||||
const overview=ref({})
|
||||
onMounted(async()=>{const r=await getDashboardOverview();overview.value=r.data||{}})
|
||||
|
||||
const overview = ref({})
|
||||
const showSystemInfo = ref(false)
|
||||
|
||||
const statCards = ref([
|
||||
{label:'数据库表', value:0, color:'#409eff'},
|
||||
{label:'API接口', value:0, color:'#67c23a'},
|
||||
{label:'功能模块', value:0, color:'#e6a23c'},
|
||||
{label:'菜单数', value:0, color:'#f56c6c'},
|
||||
{label:'在线用户', value:0, color:'#909399'},
|
||||
{label:'今日操作', value:0, color:'#409eff'}
|
||||
])
|
||||
|
||||
const modules = ref([
|
||||
{name:'门诊管理', icon:'🏥', desc:'挂号/收费/处方'},
|
||||
{name:'住院管理', icon:'🛏️', desc:'入院/出院/医嘱'},
|
||||
{name:'药房管理', icon:'💊', desc:'发药/库存/追溯'},
|
||||
{name:'护理管理', icon:'💉', desc:'评估/执行/质量'},
|
||||
{name:'检验管理', icon:'🔬', desc:'标本/报告/历史'},
|
||||
{name:'影像管理', icon:'📷', desc:'检查/报告/对比'},
|
||||
{name:'麻醉管理', icon:'💉', desc:'记录/随访/质控'},
|
||||
{name:'手术管理', icon:'🔪', desc:'排班/安全/记录'},
|
||||
{name:'院感管理', icon:'🦠', desc:'监测/预警/报告'},
|
||||
{name:'质控管理', icon:'📊', desc:'病历/护理/运行'},
|
||||
{name:'中医管理', icon:'🌿', desc:'方剂/体质/处方'},
|
||||
{name:'会诊管理', icon:'👥', desc:'申请/确认/反馈'}
|
||||
])
|
||||
|
||||
const quickActions = ref([
|
||||
{label:'挂号', icon:'📋', type:'primary', path:'/charge/cliniccharge'},
|
||||
{label:'开方', icon:'💊', type:'success', path:'/doctorstation'},
|
||||
{label:'发药', icon:'💉', type:'warning', path:'/pharmacyManagement'},
|
||||
{label:'检验', icon:'🔬', type:'info', path:'/inspection'},
|
||||
{label:'质控', icon:'📊', type:'danger', path:'/qualityenhanced'},
|
||||
{label:'报表', icon:'📈', type:'', path:'/businessanalytics'}
|
||||
])
|
||||
|
||||
const recentLogs = ref([
|
||||
{content:'管理员登录系统', time:'刚刚', type:'primary'},
|
||||
{content:'新增处方点评计划', time:'5分钟前', type:'success'},
|
||||
{content:'更新抗菌药物规则', time:'10分钟前', type:'warning'},
|
||||
{content:'护理评估提交', time:'15分钟前', type:'info'},
|
||||
{content:'危急值处理完成', time:'20分钟前', type:'danger'}
|
||||
])
|
||||
|
||||
async function loadData() {
|
||||
try {
|
||||
const r = await getDashboardOverview()
|
||||
overview.value = r.data || {}
|
||||
statCards.value[0].value = overview.value.totalTables || 0
|
||||
statCards.value[1].value = overview.value.totalApis || 0
|
||||
statCards.value[2].value = modules.value.length
|
||||
statCards.value[3].value = overview.value.totalMenus || 0
|
||||
statCards.value[4].value = overview.value.onlineUsers || 0
|
||||
statCards.value[5].value = overview.value.todayOperations || 0
|
||||
} catch(e) {}
|
||||
}
|
||||
|
||||
function handleModuleClick(mod) {
|
||||
ElMessage.info('进入模块: ' + mod.name)
|
||||
}
|
||||
|
||||
function handleAction(action) {
|
||||
ElMessage.info('跳转: ' + action.label)
|
||||
}
|
||||
|
||||
onMounted(() => loadData())
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user