| <template> |
| <div class="services-view"> |
| <div class="page-header"> |
| <h2>服务控制台</h2> |
| <p>按功能模块启动 / 停止 — 期货仿真、Qlib 因子研究、前端界面</p> |
| </div> |
| |
| <el-row :gutter="16" class="summary-row"> |
| <el-col :span="6" v-for="item in summaryCards" :key="item.label"> |
| <el-card shadow="hover" class="summary-card"> |
| <div class="summary-label">{{ item.label }}</div> |
| <div class="summary-value" :class="item.class">{{ item.value }}</div> |
| </el-card> |
| </el-col> |
| </el-row> |
| |
| <el-card shadow="never" class="table-card"> |
| <template #header> |
| <div class="card-header"> |
| <span>功能模块</span> |
| <el-button size="small" :loading="loading" @click="loadServices">刷新</el-button> |
| </div> |
| </template> |
| |
| <el-table :data="services" v-loading="loading" stripe> |
| <el-table-column prop="name" label="模块" width="180" /> |
| <el-table-column prop="description" label="说明" min-width="280" /> |
| <el-table-column label="状态" width="120"> |
| <template #default="{ row }"> |
| <el-tag :type="statusType(row.status)" size="small">{{ statusText(row.status) }}</el-tag> |
| </template> |
| </el-table-column> |
| <el-table-column prop="port" label="端口" width="90"> |
| <template #default="{ row }">{{ row.port || '—' }}</template> |
| </el-table-column> |
| <el-table-column label="操作" width="220" fixed="right"> |
| <template #default="{ row }"> |
| <el-button |
| size="small" |
| type="primary" |
| :disabled="row.status === 'running'" |
| :loading="actionId === row.id && actionType === 'start'" |
| @click="startService(row.id)" |
| >启动</el-button> |
| <el-button |
| size="small" |
| type="danger" |
| plain |
| :disabled="row.status !== 'running'" |
| :loading="actionId === row.id && actionType === 'stop'" |
| @click="stopService(row.id)" |
| >停止</el-button> |
| </template> |
| </el-table-column> |
| </el-table> |
| </el-card> |
| |
| <el-card shadow="never" class="hint-card"> |
| <template #header>使用说明</template> |
| <ul class="hint-list"> |
| <li><strong>期货行情</strong>:启动后 Dashboard / 行情页可获取模拟合约数据</li> |
| <li><strong>期货策略引擎</strong>:启动后可在「策略管理」运行 MA / 布林带 / DualThrust</li> |
| <li><strong>Qlib 因子研究</strong>:启动后可在「因子研究」查看注册因子、算子库、策略列表</li> |
| <li><strong>前端界面</strong>:本页面即 Vue 开发服务器;生产环境请 <code>npm run build</code></li> |
| </ul> |
| </el-card> |
| </div> |
| </template> |
| |
| <script setup> |
| import { ref, computed, onMounted } from 'vue' |
| import { ElMessage } from 'element-plus' |
| import { platformApi } from '../api' |
| |
| const services = ref([]) |
| const loading = ref(false) |
| const actionId = ref('') |
| const actionType = ref('') |
| |
| const summaryCards = computed(() => { |
| const total = services.value.length |
| const running = services.value.filter(s => s.status === 'running').length |
| const stopped = services.value.filter(s => s.status === 'stopped').length |
| const error = services.value.filter(s => s.status === 'error').length |
| return [ |
| { label: '模块总数', value: total, class: '' }, |
| { label: '运行中', value: running, class: 'text-success' }, |
| { label: '已停止', value: stopped, class: 'text-muted' }, |
| { label: '异常', value: error, class: 'text-danger' }, |
| ] |
| }) |
| |
| function statusType(status) { |
| if (status === 'running') return 'success' |
| if (status === 'error') return 'danger' |
| return 'info' |
| } |
| |
| function statusText(status) { |
| const map = { running: '运行中', stopped: '已停止', error: '异常', starting: '启动中' } |
| return map[status] || status |
| } |
| |
| async function loadServices() { |
| loading.value = true |
| try { |
| const res = await platformApi.listServices() |
| services.value = res.data.services || [] |
| } catch (e) { |
| ElMessage.error('加载服务列表失败') |
| } finally { |
| loading.value = false |
| } |
| } |
| |
| async function startService(id) { |
| actionId.value = id |
| actionType.value = 'start' |
| try { |
| const res = await platformApi.startService(id) |
| services.value = res.data.services || services.value |
| ElMessage.success(res.data.message || '已启动') |
| } catch (e) { |
| ElMessage.error(e.response?.data?.detail || '启动失败') |
| } finally { |
| actionId.value = '' |
| actionType.value = '' |
| } |
| } |
| |
| async function stopService(id) { |
| actionId.value = id |
| actionType.value = 'stop' |
| try { |
| const res = await platformApi.stopService(id) |
| services.value = res.data.services || services.value |
| ElMessage.success(res.data.message || '已停止') |
| } catch (e) { |
| ElMessage.error(e.response?.data?.detail || '停止失败') |
| } finally { |
| actionId.value = '' |
| actionType.value = '' |
| } |
| } |
| |
| onMounted(loadServices) |
| </script> |
| |
| <style scoped> |
| .services-view { padding: 0 4px; } |
| .page-header { margin-bottom: 20px; } |
| .page-header h2 { margin: 0 0 8px; font-size: 22px; } |
| .page-header p { margin: 0; color: #909399; font-size: 14px; } |
| .summary-row { margin-bottom: 16px; } |
| .summary-card { text-align: center; } |
| .summary-label { font-size: 13px; color: #909399; margin-bottom: 8px; } |
| .summary-value { font-size: 28px; font-weight: 600; } |
| .text-success { color: #67c23a; } |
| .text-danger { color: #f56c6c; } |
| .text-muted { color: #909399; } |
| .card-header { display: flex; justify-content: space-between; align-items: center; } |
| .table-card { margin-bottom: 16px; } |
| .hint-list { margin: 0; padding-left: 20px; line-height: 1.8; color: #606266; } |
| .hint-list code { background: #f5f7fa; padding: 2px 6px; border-radius: 4px; } |
| </style> |
| |