Files
mokeegateway/mokee-gateway-web/src/views/admin/system/SystemList.vue
2026-07-15 16:13:27 +08:00

273 lines
11 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!--
系统管理列表页
功能
- 按系统编码系统名称状态筛选
- 图标列支持 URL 图片和 Element Plus 图标组件通过 getIconComponent 动态解析
- 操作列编辑删除文档打开本系统的接入文档页 /docs/:docToken
- 新增按钮使用 v-permission 指令控制可见性 system:system:add 权限
路由
- 新增/admin/system/form
- 编辑/admin/system/form/:id
- 文档新标签页打开 /docs/:docToken公开文档无需登录
数据来源
- 分页列表POST /zgapi/v1/admin/system/page
- 删除DELETE /zgapi/v1/admin/system/:id
-->
<template>
<div class="page-wrapper">
<!-- 搜索区域 -->
<el-card shadow="never" class="search-card">
<template #header>
<div class="card-header-title">
<el-icon><Search /></el-icon>
<span>搜索条件</span>
</div>
</template>
<el-form :model="query" inline>
<el-form-item label="系统编码">
<el-input v-model="query.systemCode" placeholder="请输入系统编码" clearable />
</el-form-item>
<el-form-item label="系统名称">
<el-input v-model="query.systemName" placeholder="请输入系统名称" clearable />
</el-form-item>
<el-form-item label="状态">
<el-select v-model="query.status" placeholder="全部" clearable style="width: 120px">
<el-option label="全部" value="" />
<el-option label="启用" :value="1" />
<el-option label="禁用" :value="0" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSearch">
<el-icon><Search /></el-icon>
搜索
</el-button>
<el-button @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</el-card>
<!-- 操作栏 -->
<div class="toolbar">
<el-button type="primary" v-permission="'system:system:add'" @click="handleAdd">
<el-icon><Plus /></el-icon>
新增系统
</el-button>
</div>
<!-- 表格 -->
<el-card shadow="never">
<!-- 桌面端表格 -->
<el-table v-if="!isMobile" :data="tableData" border stripe v-loading="loading" style="width: 100%">
<el-table-column prop="systemCode" label="系统编码" width="140" />
<el-table-column prop="systemName" label="系统名称" width="160" />
<el-table-column prop="icon" label="图标" width="70" align="center">
<template #default="{ row }">
<img v-if="row.icon && (row.icon.startsWith('http') || row.icon.startsWith('/zgapi'))" :src="row.icon" style="width:20px;height:20px;object-fit:contain" />
<el-icon v-else-if="row.icon" :size="20"><component :is="getIconComponent(row.icon)" /></el-icon>
<span v-else style="color:var(--text-muted)">-</span>
</template>
</el-table-column>
<el-table-column prop="frontUrl" label="前端地址" min-width="180" show-overflow-tooltip />
<el-table-column prop="backendUrl" label="后端真实地址" min-width="180" show-overflow-tooltip />
<el-table-column prop="status" label="状态" width="90" align="center">
<template #default="{ row }"><el-tag :type="row.status === 1 ? 'success' : 'danger'" effect="dark" size="small">{{ row.status === 1 ? '启用' : '禁用' }}</el-tag></template>
</el-table-column>
<el-table-column label="操作" width="360" align="center">
<template #default="{ row }">
<el-button size="small" plain @click="showDetail(row)"><el-icon><View /></el-icon>查看</el-button>
<el-button type="primary" size="small" plain v-permission="'system:system:edit'" @click="handleEdit(row)"><el-icon><Edit /></el-icon>编辑</el-button>
<el-button type="danger" size="small" plain v-permission="'system:system:delete'" @click="handleDelete(row)"><el-icon><Delete /></el-icon>删除</el-button>
<el-button type="success" size="small" plain @click="openDoc(row)"><el-icon><Document /></el-icon>文档</el-button>
</template>
</el-table-column>
<template #empty><el-empty description="暂无系统数据" :image-size="100" /></template>
</el-table>
<!-- 手机端卡片 -->
<div v-else class="mobile-cards" v-loading="loading">
<div v-for="row in tableData" :key="row.id" class="mobile-card">
<div class="card-field"><span class="fld-label">系统编码</span><span class="fld-val">{{ row.systemCode }}</span></div>
<div class="card-field"><span class="fld-label">系统名称</span><span class="fld-val">{{ row.systemName }}</span></div>
<div class="card-field"><span class="fld-label">前端地址</span><span class="fld-val" style="font-size:12px">{{ row.frontUrl || '-' }}</span></div>
<div class="card-field"><span class="fld-label">后端地址</span><span class="fld-val" style="font-size:12px">{{ row.backendUrl || '-' }}</span></div>
<div class="card-field"><span class="fld-label">状态</span><span class="fld-val"><el-tag :type="row.status === 1 ? 'success' : 'danger'" effect="dark" size="small">{{ row.status === 1 ? '启用' : '禁用' }}</el-tag></span></div>
<div class="card-actions">
<el-button size="small" plain @click="showDetail(row)"><el-icon><View /></el-icon>查看</el-button>
<el-button size="small" plain type="primary" v-permission="'system:system:edit'" @click="handleEdit(row)"><el-icon><Edit /></el-icon>编辑</el-button>
<el-button size="small" plain type="danger" v-permission="'system:system:delete'" @click="handleDelete(row)"><el-icon><Delete /></el-icon>删除</el-button>
<el-button size="small" plain type="success" @click="openDoc(row)"><el-icon><Document /></el-icon>文档</el-button>
</div>
</div>
<el-empty v-if="tableData.length === 0" description="暂无系统数据" :image-size="100" />
</div>
<div class="pagination-wrapper">
<el-pagination
v-model:current-page="query.page"
v-model:page-size="query.pageSize"
:total="total"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
background
@current-change="fetchData"
@size-change="fetchData"
/>
</div>
</el-card>
<!-- 查看详情弹窗 -->
<el-dialog v-model="detailVisible" title="系统详情" width="600px" destroy-on-close>
<el-descriptions v-if="detailRow" :column="2" border size="small">
<el-descriptions-item label="系统编码">{{ detailRow.systemCode }}</el-descriptions-item>
<el-descriptions-item label="系统名称">{{ detailRow.systemName }}</el-descriptions-item>
<el-descriptions-item label="前端地址" :span="2">{{ detailRow.frontUrl || '-' }}</el-descriptions-item>
<el-descriptions-item label="后端地址" :span="2">{{ detailRow.backendUrl || '-' }}</el-descriptions-item>
<el-descriptions-item label="网关地址" :span="2">{{ detailRow.gatewayUrl || '-' }}</el-descriptions-item>
<el-descriptions-item label="文档Token">{{ detailRow.docToken || '-' }}</el-descriptions-item>
<el-descriptions-item label="状态">
<el-tag :type="detailRow.status === 1 ? 'success' : 'danger'" effect="dark" size="small">{{ detailRow.status === 1 ? '启用' : '禁用' }}</el-tag>
</el-descriptions-item>
<el-descriptions-item label="描述" :span="2">{{ detailRow.description || '-' }}</el-descriptions-item>
</el-descriptions>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { reactive, ref } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Search, Plus, Edit, Delete, Document, MoreFilled, View } from '@element-plus/icons-vue'
import { getIconComponent } from '@/utils/icons'
import request from '@/utils/request'
import { useIsMobile } from '@/composables/useIsMobile'
const isMobile = useIsMobile()
/** 系统类型 */
interface SystemItem {
id?: string
systemCode: string
systemName: string
frontUrl: string
backendUrl: string
icon: string
description: string
docToken: string
status: number
}
// --- 查询 ---
const query = reactive({
page: 1,
pageSize: 10,
systemCode: '',
systemName: '',
status: '' as string | number,
})
const tableData = ref<SystemItem[]>([])
const total = ref(0)
const loading = ref(false)
const detailVisible = ref(false)
const detailRow = ref<any>(null)
function showDetail(row: any) {
detailRow.value = row
detailVisible.value = true
}
async function fetchData() {
loading.value = true
try {
const params: Record<string, unknown> = {
page: query.page,
pageSize: query.pageSize,
}
if (query.systemCode) params.systemCode = query.systemCode
if (query.systemName) params.systemName = query.systemName
if (query.status !== '') params.status = query.status
const res = await request.post('/zgapi/v1/admin/system/page', params)
tableData.value = res.data?.records ?? []
total.value = res.data?.total ?? 0
} catch {
// 错误由拦截器处理
} finally {
loading.value = false
}
}
function handleSearch() {
query.page = 1
fetchData()
}
function handleReset() {
query.systemCode = ''
query.systemName = ''
query.status = ''
query.page = 1
fetchData()
}
// ===== 新增/编辑(路由跳转到独立表单页)=====
const router = useRouter()
/** 跳转到系统新增页 */
function handleAdd() {
router.push('/admin/system/form')
}
/**
* 打开系统接入文档(新标签页)
* 文档路由为 /docs/:docToken公开访问无需登录
*/
function openDoc(row: SystemItem) {
window.open('/docs/' + row.docToken, '_blank')
}
/** 跳转到系统编辑页,携带系统 ID */
function handleEdit(row: SystemItem) {
router.push('/admin/system/form/' + row.id)
}
// ===== 删除:确认弹窗后调用 DELETE 接口 =====
function handleDelete(row: SystemItem) {
ElMessageBox.confirm(`确认删除系统"${row.systemName}"吗?`, '删除确认', {
type: 'warning',
confirmButtonText: '确认',
cancelButtonText: '取消',
}).then(async () => {
try {
await request.delete(`/zgapi/v1/admin/system/${row.id}`)
ElMessage.success('删除成功')
fetchData() // 删除成功后刷新列表
} catch {
// 错误由 axios 响应拦截器统一处理
}
}).catch(() => {})
}
// 初始化
fetchData()
</script>
<style scoped>
.actions-desk {
display: flex;
gap: 8px;
flex-wrap: wrap;
justify-content: center;
}
.actions-mob { display: none; }
@media (max-width: 768px) {
.toolbar { margin-bottom: 4px; }
.toolbar .el-button { width: 100%; }
}
</style>