Commit 5ee60f95 by 周田

Merge branch 'yzh' into 'main'

feat:完成了状态监控静态页面

See merge request !2
parents 0b1c698e 4da0d493
<!-- 解析路径并跳转路由 -->
<script setup lang="ts">
import { computed } from 'vue'
import { computed, onMounted } from 'vue'
import { RouterLink } from 'vue-router'
import { isExternal } from '@/utils/validate'
......@@ -11,6 +12,11 @@ const props = defineProps({
})
const isExtLink = computed(() => isExternal(props.to))
onMounted(()=>{
// console.log(props.to,'111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111')
})
</script>
<template>
......
......@@ -101,4 +101,8 @@ onBeforeUnmount(() => {
color: #666666;
font-size: 12px;
}
.el-button:focus {
outline: none;
}
</style>
<!-- 基于 ruoyi-vue3 的 Pagination 重构,核心是简化无用的属性,并使用 ts 重写 -->
<template>
<el-pagination
v-show="total > 0"
v-model:current-page="currentPage"
v-model:page-size="pageSize"
:background="true"
:page-sizes="[5,10, 20, 30, 50, 100]"
:pager-count="pagerCount"
:total="total"
class="mt-4"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</template>
<script lang="ts" setup>
import { computed } from 'vue'
defineOptions({ name: 'Pagination' })
const props = defineProps({
// 总条目数
total: {
required: true,
type: Number
},
// 当前页数:pageNo
page: {
type: Number,
default: 1
},
// 每页显示条目个数:pageSize
limit: {
type: Number,
default: 20
},
// 设置最大页码按钮数。 页码按钮的数量,当总页数超过该值时会折叠
// 移动端页码按钮的数量端默认值 5
pagerCount: {
type: Number,
default: document.body.clientWidth < 992 ? 5 : 7
}
})
const emit = defineEmits(['update:page', 'update:limit', 'pagination'])
const currentPage = computed({
get() {
return props.page
},
set(val) {
// 触发 update:page 事件,更新 limit 属性,从而更新 pageNo
emit('update:page', val)
}
})
const pageSize = computed({
get() {
return props.limit
},
set(val) {
// 触发 update:limit 事件,更新 limit 属性,从而更新 pageSize
emit('update:limit', val)
}
})
const handleSizeChange = (val:number) => {
// 如果修改后超过最大页面,强制跳转到第 1 页
if (currentPage.value * val > props.total) {
currentPage.value = 1
}
// 触发 pagination 事件,重新加载列表
emit('pagination', { page: currentPage.value, limit: val })
}
const handleCurrentChange = (val:number) => {
// 触发 pagination 事件,重新加载列表
emit('pagination', { page: val, limit: pageSize.value })
}
</script>
<template>
<div>
<span>关键词搜索:</span>
<el-input v-model="input2" style="width: 240px" placeholder="Search" :prefix-icon="Search" />
</div>
</template>
<script setup lang="ts">
</script>
\ No newline at end of file
......@@ -17,6 +17,9 @@ const key = computed(() => route.path)
<style scoped>
.app-main {
/*50 = navbar */
background-image: url("@/assets/picture/background1.png");
background-size: 100% 100%;
background-repeat: no-repeat;
min-height: calc(100vh - 50px);
width: 100%;
position: relative;
......
......@@ -26,9 +26,7 @@ const logout = async () => {
<template>
<div class="navbar">
<hamburger :is-active="sidebar.opened" class="hamburger-container" @toggleClick="toggleSideBar" />
<breadcrumb class="breadcrumb-container" />
<div class="right-menu">
<el-dropdown class="avatar-container" trigger="click">
<div class="avatar-wrapper">
......
......@@ -16,7 +16,9 @@ const logo = '' // 暂时为空
</script>
<template>
<div class="sidebar-logo-container" :class="{'collapse': collapse}">
<transition name="sidebarLogoFade">
<router-link v-if="collapse" key="collapse" class="sidebar-logo-link" to="/">
<img v-if="logo" :src="logo" class="sidebar-logo" alt="logo">
......@@ -60,6 +62,7 @@ const logo = '' // 暂时为空
font-size: 14px;
font-family: Avenir, Helvetica Neue, Arial, Helvetica, sans-serif;
vertical-align: middle;
margin-left: 6px;
}
}
......
<script setup lang="ts">
import { ref, computed } from 'vue'
import { ref, computed, onMounted } from 'vue'
import type { PropType } from 'vue'
import { isExternal } from '@/utils/validate'
import AppLink from '@/components/AppLink/index.vue'
......@@ -19,6 +19,9 @@ const props = defineProps({
basePath: {
type: String,
default: ''
},
sidebarStatus: {
type: Boolean,
}
})
......@@ -32,6 +35,7 @@ const alwaysShowRootMenu = computed(() => {
// 解析路径
const resolvePath = (routePath: string) => {
if (isExternal(routePath)) {
return routePath
}
......@@ -46,7 +50,7 @@ const hasOneShowingChild = (children: MenuItem[] = [], parent: MenuItem): boolea
if (!children) {
children = []
}
const showingChildren = children.filter(item => {
if (item.meta?.hidden) {
return false
......@@ -64,7 +68,7 @@ const hasOneShowingChild = (children: MenuItem[] = [], parent: MenuItem): boolea
// 当没有要显示的子路由时,则显示父路由
if (showingChildren.length === 0) {
onlyOneChild.value = {
onlyOneChild.value = {
...parent,
path: '',
meta: parent.meta
......@@ -74,21 +78,22 @@ const hasOneShowingChild = (children: MenuItem[] = [], parent: MenuItem): boolea
return false
}
onMounted(() => {
})
</script>
<template>
<div v-if="!item.meta?.hidden" class="sidebar-item-container">
<template v-if="hasOneShowingChild(item.children as MenuItem[], item) && (!onlyOneChild?.children?.length || onlyOneChild?.meta?.noShowingChildren) && !alwaysShowRootMenu">
<app-link
v-if="onlyOneChild?.meta"
:to="resolvePath(onlyOneChild.path)"
class="sidebar-link"
>
<el-menu-item :index="resolvePath(onlyOneChild.path)" >
<template
v-if="hasOneShowingChild(item.children as MenuItem[], item) && (!onlyOneChild?.children?.length || onlyOneChild?.meta?.noShowingChildren) && !alwaysShowRootMenu">
<app-link v-if="onlyOneChild?.meta" :to="resolvePath(onlyOneChild.path)" class="sidebar-link">
<el-menu-item :index="resolvePath(onlyOneChild.path)">
<el-icon v-if="onlyOneChild.meta?.icon">
<component :is="onlyOneChild.meta.icon" />
</el-icon>
<span v-if="onlyOneChild.meta?.title">{{ onlyOneChild.meta.title }}</span>
<span v-if="onlyOneChild.meta?.title && sidebarStatus">{{ onlyOneChild.meta.title }}</span>
</el-menu-item>
</app-link>
</template>
......@@ -100,14 +105,8 @@ const hasOneShowingChild = (children: MenuItem[] = [], parent: MenuItem): boolea
</el-icon>
<span v-if="item.meta?.title">{{ item.meta.title }}</span>
</template>
<sidebar-item
v-for="child in item.children"
:key="child.path"
:is-nest="true"
:item="child"
:base-path="resolvePath(child.path)"
class="nest-menu"
/>
<sidebar-item v-for="child in item.children" :key="child.path" :is-nest="true" :item="child"
:base-path="resolvePath(child.path)" class="nest-menu" />
</el-sub-menu>
</div>
</template>
......@@ -117,7 +116,8 @@ const hasOneShowingChild = (children: MenuItem[] = [], parent: MenuItem): boolea
text-decoration: none;
}
.el-menu-item, .el-sub-menu {
.el-menu-item,
.el-sub-menu {
background-color: transparent !important;
}
</style>
<script setup lang="ts">
import { computed } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { computed, onMounted } from 'vue'
import { useRoute, useRouter, type RouteRecordRaw } from 'vue-router'
import { useSettingsStore } from '@/store/settings'
import { useAppStore } from '@/store/app'
import { storeToRefs } from 'pinia'
......@@ -14,16 +14,29 @@ const settingsStore = useSettingsStore()
const appStore = useAppStore()
const { sidebar } = storeToRefs(appStore)
const { sidebarLogo } = storeToRefs(settingsStore)
// 获取所有路由配置,而不仅仅是匹配的路由
const routes = computed(() => {
// 获取路由器中的所有路由
return router.options.routes.filter(item => {
// 过滤掉设置了hidden的路由
// @ts-ignore - hidden 属性在实际路由对象中存在,但TypeScript类型定义中没有
return !(item.hidden || (item.meta && item.meta.hidden))
})
})
// 递归过滤路由,包括子路由
const filterHiddenRoutes = (routes: RouteRecordRaw[]): RouteRecordRaw[] => {
return routes.filter((route: any) => {
console.log(route,'每一项');
// 如果当前路由标记为 hidden,则过滤掉
if (route.meta?.hidden || route.hidden) {
return false;
}
// 如果有子路由,递归过滤子路由
if (route.children && route.children.length > 0) {
route.children = filterHiddenRoutes(route.children);
// 如果过滤后子路由为空,且父路由没有 component,则过滤掉整个路由
if (route.children.length === 0 && !route.component) {
return false;
}
}
return true;
});
};
return filterHiddenRoutes([...router.options.routes]);
});
const activeMenu = computed(() => {
const { meta, path } = route
......@@ -35,6 +48,9 @@ const activeMenu = computed(() => {
})
const isCollapse = computed(() => !sidebar.value.opened)
onMounted(()=>{
})
</script>
<template>
......@@ -50,12 +66,14 @@ const isCollapse = computed(() => !sidebar.value.opened)
active-text-color="#409eff"
:collapse-transition="false"
mode="vertical"
class="el-menu-vertical-demo"
>
<sidebar-item
v-for="route in routes"
:key="route.path"
:item="route"
:base-path="route.path"
:sidebarStatus="sidebar.opened"
/>
</el-menu>
</el-scrollbar>
......@@ -64,17 +82,21 @@ const isCollapse = computed(() => !sidebar.value.opened)
<style lang="scss" scoped>
@use "@/styles/variables" as vars;
.el-menu-vertical-demo:not(.el-menu--collapse) {
width: 200px;
min-height: 400px;
}
.sidebar-container-wrapper {
height: 100%;
display: flex;
flex-direction: column;
margin-left: -6px;
// 主题定制
:deep(.el-menu) {
border: none;
height: 100%;
width: 100% !important;
}
:deep(.submenu-title-noDropdown:hover), :deep(.el-submenu__title:hover) {
......
......@@ -84,7 +84,7 @@ export const constantRoutes: Array<RouteRecordRaw> = [
}
]
} as unknown as RouteRecordRaw,
{
path: '/spider',
component: Layout,
......@@ -97,7 +97,7 @@ export const constantRoutes: Array<RouteRecordRaw> = [
}
]
} as unknown as RouteRecordRaw,
{
path: '/schedule',
component: Layout,
......@@ -136,7 +136,7 @@ export const constantRoutes: Array<RouteRecordRaw> = [
}
]
} as unknown as RouteRecordRaw,
{
path: '/stats',
component: Layout,
......@@ -205,6 +205,107 @@ export const constantRoutes: Array<RouteRecordRaw> = [
]
} as unknown as RouteRecordRaw,
{
path: "/os-status",
redirect: "/os-status/list",
component: Layout,
children: [
{
path: "list",
name: "statusMonitor",
component: () => import("@/views/os-status/index.vue"),
meta: { title: "状态监控", icon: "Key" }
},
]
} as unknown as RouteRecordRaw,
{
path: "/os-taskInformation",
redirect: "/os-taskInformation/list",
component: Layout,
children: [
{
path: "list",
name: "taskInformation",
component: () => import("@/views/os-taskInformation/index.vue"),
meta: { title: "任务信息", icon: "Key" }
},
]
} as unknown as RouteRecordRaw,
{
path: "/os-taskManager",
redirect: "/os-status/list",
component: Layout,
children: [
{
path: "list",
name: "taskManager",
component: () => import("@/views/os-taskManager/index.vue"),
meta: { title: "任务执行记录", icon: "Key" }
},
]
} as unknown as RouteRecordRaw,
{
path: "/os-taskRecord",
redirect: "/os-taskRecord/list",
component: Layout,
children: [
{
path: "list",
name: "taskRecord",
component: () => import("@/views/os-taskRecord/index.vue"),
meta: { title: "任务执行记录", icon: "Key" }
},
]
} as unknown as RouteRecordRaw,
{
path: "/os-dataDisplay",
redirect: "/os-dataDisplay/list",
component: Layout,
children: [
{
path: "list",
name: "dataDisplay",
component: () => import("@/views/os-dataDisplay/index.vue"),
meta: { title: "数据展示", icon: "Key" }
},
]
} as unknown as RouteRecordRaw,
{
path: "/os-preprocessingRecord",
redirect: "/os-preprocessingRecord/list",
component: Layout,
children: [
{
path: "list",
name: "preprocessingRecord",
component: () => import("@/views/os-preprocessingRecord/index.vue"),
meta: { title: "预处理记录", icon: "Key" }
},
]
} as unknown as RouteRecordRaw,
{
path: "/os-system",
redirect: "/os-system/list",
component: Layout,
children: [
{
path: "list",
name: "osSystem",
component: () => import("@/views/os-system/index.vue"),
meta: { title: "开源系统管理", icon: "Key" }
},
]
} as unknown as RouteRecordRaw,
// 404 页面必须放在最后 !!!
// { path: '/:pathMatch(.*)*', redirect: '/404', hidden: true } as unknown as RouteRecordRaw
]
......
......@@ -76,7 +76,7 @@
.hideSidebar {
.sidebar-container {
width: 54px !important;
width: 60px !important;
}
.main-container {
......
......@@ -26,15 +26,12 @@ const resetData = (id: string) => {
@on-init="handleSelectScrapydServerInit"
@change="resetData"
/>
<div class="mt-md">
<home-data-info
:key="scrapydServerId"
:scrapyd-server-id="scrapydServerId"
/>
<home-system-info />
<!-- <home-config /> -->
</div>
</div>
......
......@@ -8,7 +8,6 @@
@success="fetchJobList"
@on-init="handleToolInit"
/>
<JobTable
:data="jobList"
:loading="loading"
......
<template>
<div class="text-left p-10">
<el-form inline>
<el-form-item label="目标选择:">
<el-select v-model="taskValue" placeholder=" " style="width: 140px">
<el-option v-for="item in sortOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="分类选择:">
<el-select v-model="errorValue" placeholder=" " style="width: 140px">
<el-option v-for="item in sortOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="关键词搜索:">
<el-input v-model="searchInput" style="width: 220px" placeholder="Search" :prefix-icon="Search" />
</el-form-item>
<el-form-item label="时间:">
<el-date-picker style="width: 240px" v-model="timeValue" type="datetimerange" range-separator="-" start-placeholder="开始时间"
end-placeholder="结束时间" />
</el-form-item>
<el-form-item>
<el-button type="primary">查询</el-button>
</el-form-item>
<el-form-item>
<el-button type="primary">批量导出</el-button>
<el-button type="danger">批量删除</el-button>
</el-form-item>
</el-form>
</div>
<el-table :data="tableData" style="width: 100%" border>
<el-table-column type="selection" width="40" />
<el-table-column property="name" label="序号" width="55" />
<el-table-column property="name" label="目标名称" show-overflow-tooltip />
<el-table-column property="name" label="标题" show-overflow-tooltip />
<el-table-column property="name" label="关键字" show-overflow-tooltip />
<el-table-column property="name" label="数据分类" show-overflow-tooltip />
<el-table-column property="name" label="数据时间" width="200" show-overflow-tooltip />
<el-table-column property="name" label="记录时间" width="200" show-overflow-tooltip />
<el-table-column label="操作" width="240">
<template #default="scope">
<el-button type="primary" plain @click="handleDetails(scope.row)">
编辑
</el-button>
<el-button type="success" plain @click="handleDetails(scope.row)">
详情
</el-button>
<el-button type="danger" plain @click="handleConfirm(scope.row)">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<div class="pagination w-full flex flex-row-reverse pr-4 m-t-4">
<Pagination :total="pageObj.total" v-model:page="pageObj.pageNo" v-model:limit="pageObj.pageSize"
@pagination="getData" />
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import Pagination from '@/components/pagination/index.vue'
import { Search } from '@element-plus/icons-vue'
const errorValue = ref('')
const taskValue = ref('')
const timeValue = ref('')
const searchInput = ref('')
const sortOptions = [
{
value: 'task1',
label: '基础信息',
},
{
value: 'task2',
label: '信号信息',
},
{
value: 'task3',
label: '其他信息',
},
]
const tableData = ref([
{
name: '1',
address: 'sk网',
address1: '成功',
address2: '100',
address3: '0',
address4: '无',
address5: '10s',
address6: '2023-05-13 10:00:00',
}
])
const pageObj = ref({
total: 10,
pageSize: 10,
pageNo: 1
})
const handleDetails = (row: any) => {
console.log(row);
}
const handleConfirm = (row: any) => {
console.log(row);
}
const getData = () => {
console.log('getData');
}
</script>
<style scoped>
.el-button:focus {
outline: none;
}
</style>
<template>
<div class="text-left p-10">
<el-form inline>
<el-form-item label="目标选择:">
<el-select v-model="taskValue" placeholder=" " style="width: 200px">
<el-option v-for="item in targetOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="预处理结果选择:">
<el-select v-model="errorValue" placeholder=" " style="width: 200px">
<el-option v-for="item in resOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="时间:">
<el-date-picker style="width: 240px" v-model="timeValue" type="datetimerange" range-separator="-" start-placeholder="开始时间"
end-placeholder="结束时间" />
</el-form-item>
<el-form-item>
<el-button type="primary">查询</el-button>
</el-form-item>
<el-form-item>
<el-button type="danger">批量处理</el-button>
</el-form-item>
</el-form>
</div>
<el-table :data="tableData" style="width: 100%" border>
<el-table-column type="selection" width="40" />
<el-table-column property="name" label="序号" width="55" />
<el-table-column property="name" label="目标名称" show-overflow-tooltip />
<el-table-column property="name" label="标题" show-overflow-tooltip />
<el-table-column property="name" label="关键字" show-overflow-tooltip />
<el-table-column property="name" label="数据分类" show-overflow-tooltip />
<el-table-column property="name" label="数据时间" width="200" show-overflow-tooltip />
<el-table-column property="name" label="记录时间" width="200" show-overflow-tooltip />
<el-table-column label="操作" width="240">
<template #default="scope">
<el-button type="primary" plain @click="handleDetails(scope.row)">
编辑
</el-button>
<el-button type="success" plain @click="handleDetails(scope.row)">
详情
</el-button>
<el-button type="danger" plain @click="handleConfirm(scope.row)">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<div class="pagination w-full flex flex-row-reverse pr-4 m-t-4">
<Pagination :total="pageObj.total" v-model:page="pageObj.pageNo" v-model:limit="pageObj.pageSize"
@pagination="getData" />
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import Pagination from '@/components/pagination/index.vue'
const errorValue = ref('')
const taskValue = ref('')
const timeValue = ref('')
const searchInput = ref('')
const targetOptions = [
{
value: 'task1',
label: 'sk网',
},
{
value: 'task2',
label: 'nasa网',
},
{
value: 'task3',
label: '网',
},
]
const resOptions = [
{
value: 'task1',
label: '网络中断',
},
{
value: 'task2',
label: '地址不存在',
},
{
value: 'task3',
label: '反爬虫',
},
]
const tableData = ref([
{
name: '1',
address: 'sk网',
address1: '成功',
address2: '100',
address3: '0',
address4: '无',
address5: '10s',
address6: '2023-05-13 10:00:00',
}
])
const pageObj = ref({
total: 10,
pageSize: 10,
pageNo: 1
})
const handleDetails = (row: any) => {
console.log(row);
}
const handleConfirm = (row: any) => {
console.log(row);
}
const getData = () => {
console.log('getData');
}
</script>
<style scoped></style>
<template>
<div class="flex gap-10">
<div class="dataCard">
<div class="titleStyle">
<span>数据统计</span>
</div>
<div class="wordStyle">
<span>总数据量: {{ totalDataNumber }}</span>
</div>
<div class="wordStyle">
<span>采集的页面数量: {{ totalPageNumber }}</span>
</div>
<div class="wordStyle">
<span>采集的目标数量: {{ totalTargetNumber }}</span>
</div>
</div>
<div class="dataCard">
<div class="titleStyle">
<span>任务执行统计</span>
</div>
<div class="wordStyle">
<span>任务执行成功统计: {{ successTask }}</span>
</div>
<div class="wordStyle">
<span>任务执行失败统计: {{ failTask }}</span>
</div>
<div class="wordStyle">
<span>任务异常数统计: {{ unusualTask }}</span>
</div>
</div>
<div class="dataCard">
<div class="titleStyle">
<span>性能统计</span>
</div>
<div class="wordStyle">
<span>平均成功率: {{ speed }}%</span>
</div>
<div class="wordStyle">
<span>平均错误率: {{ errorRate }}% </span>
</div>
<div class="wordStyle">
<span>平均异常率: {{ errorRate }}% </span>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
defineProps({
totalDataNumber: {
type: String,
default: ''
},
totalPageNumber: {
type: String,
default: ''
},
totalTargetNumber: {
type: String,
default: ''
},
successTask: {
type: String,
default: ''
},
failTask: {
type: String,
default: ''
},
unusualTask: {
type: String,
default: ''
},
speed: {
type: String,
default: ''
},
errorRate: {
type: String,
default: ''
}
})
</script>
<style lang="scss" scoped>
.dataCard {
background-image: url("@/assets/picture/box2.png");
background-size: 100% 100%;
background-repeat: no-repeat;
// background: #c6ebfc;
// border: 1.5px solid rgb(193, 188, 188);
width: 360px;
height: 100%;
border-radius: 5px;
}
.titleStyle {
font-size: 34px;
margin-top: 3.5%;
color: #FFFFFF;
}
.wordStyle {
font-size: 24px;
color: #FFFFFF;
}
</style>
<template>
<div class="flex gap-10">
<div class="dataCard">
<div class="titleStyle">
<span>综合数据</span>
</div>
<div class="wordStyle">
<span>总数据量: {{ totalDataNumber }}</span>
</div>
<div class="wordStyle">
<span>采集的页面数量: {{ totalPageNumber }}</span>
</div>
<div class="wordStyle">
<span>采集的目标数量: {{ totalTargetNumber }}</span>
</div>
</div>
<div class="dataCard">
<div class="titleStyle">
<span>DSN数据</span>
</div>
<div class="wordStyle">
<span>任务执行成功统计: {{ successTask }}</span>
</div>
<div class="wordStyle">
<span>任务执行失败统计: {{ failTask }}</span>
</div>
<div class="wordStyle">
<span>任务异常数统计: {{ unusualTask }}</span>
</div>
</div>
<div class="dataCard">
<div class="titleStyle">
<span>ITU数据</span>
</div>
<div class="wordStyle">
<span>平均成功率: {{ speed }}%</span>
</div>
<div class="wordStyle">
<span>平均错误率: {{ errorRate }}% </span>
</div>
<div class="wordStyle">
<span>平均异常率: {{ errorRate }}% </span>
</div>
</div>
<div class="dataCard">
<div class="titleStyle">
<span>ST数据</span>
</div>
<div class="wordStyle">
<span>总数据量: {{ totalDataNumber }}</span>
</div>
<div class="wordStyle">
<span>采集的页面数量: {{ totalPageNumber }}</span>
</div>
<div class="wordStyle">
<span>采集的目标数量: {{ totalTargetNumber }}</span>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
defineProps({
totalDataNumber: {
type: String,
default: ''
},
totalPageNumber: {
type: String,
default: ''
},
totalTargetNumber: {
type: String,
default: ''
},
successTask: {
type: String,
default: ''
},
failTask: {
type: String,
default: ''
},
unusualTask: {
type: String,
default: ''
},
speed: {
type: String,
default: ''
},
errorRate: {
type: String,
default: ''
}
})
</script>
<style lang="scss" scoped>
.dataCard {
background-image: url("@/assets/picture/box2.png");
background-size: 100% 100%;
background-repeat: no-repeat;
// background: #c6ebfc;
// border: 1.5px solid rgb(193, 188, 188);
width: 280px;
height: 100%;
border-radius: 5px;
}
.titleStyle {
font-size: 34px;
margin-top: 3.5%;
color: #FFFFFF;
}
.wordStyle {
font-size: 24px;
color: #FFFFFF;
}
</style>
<template>
<div class="card-container">
<div class="monitoringCard">
<!-- <img src="@/assets/picture/box1.png" alt="Description of the image" style="max-width: 100%; height: auto;"> -->
<span class="textStyle">数据统计</span>
<div class="data-card-wrapper">
<dataCard></dataCard>
</div>
</div>
<div class="taskCard">
<span class="textStyle">任务统计</span>
<div class="data-card-wrapper">
<taskCard></taskCard>
</div>
</div>
<div class="qbCard">
<span class="textStyle">QB数据管理</span>
<div class="data-card-wrapper">
<qbCard></qbCard>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import dataCard from './dataCard.vue'
import taskCard from '@/views/os-status/components/taskCard.vue';
import qbCard from '@/views/os-status/components/qbDataMonitor.vue';
defineProps({
title: {
type: String,
default: ''
},
desc: {
type: String,
default: ''
}
})
</script>
<style lang="scss" scoped>
.card-container {
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 10px;
gap: 30px;
}
.monitoringCard {
background-image: url("@/assets/picture/box1.png");
background-size: 100% 100%;
background-repeat: no-repeat;
// background: #ffffff;
// border: 1.7px solid rgb(193, 188, 188);
height: 22.5vh;
width: 96%;
display: flex;
gap: 21.5px;
padding: 10px;
}
.taskCard {
background-image: url("@/assets/picture/box1.png");
background-size: 100% 100%;
background-repeat: no-repeat;
// background: #ffffff;
// border: 1.7px solid rgb(193, 188, 188);
height: 26.5vh;
width: 96%;
display: flex;
gap: 21.5px;
padding: 10px;
}
.qbCard {
background-image: url("@/assets/picture/box1.png");
background-size: 100% 100%;
background-repeat: no-repeat;
// background: #ffffff;
// border: 1.7px solid rgb(193, 188, 188);
height: 22vh;
width: 96%;
display: flex;
padding: 10px;
}
.textStyle {
writing-mode: vertical-lr;
font-size: 26px;
letter-spacing: 3px;
margin-left: 2%;
color: #FFFFFF;
}
.data-card-wrapper {
flex: 1;
display: flex;
justify-content: center; /* 水平居中 */
// gap: 20px;
}
</style>
\ No newline at end of file
<template>
<div class="flex gap-10">
<div class="dataCard">
<!-- <img src="@/assets/picture/box1.png" style="max-width: 100%; height: auto;"> -->
<div class="titleStyle">
<span>DSN爬取任务</span>
</div>
<div class="wordStyle">
<span>任务采集目标数: {{ totalDataNumber }}</span>
</div>
<div class="wordStyle">
<span>任务执行成功统计: {{ totalPageNumber }}</span>
</div>
<div class="wordStyle">
<span>采集速度: {{ totalTargetNumber }} </span>
</div>
<div class="wordStyle">
<span>错误率: {{ totalTargetNumber }} </span>
</div>
</div>
<div class="dataCard">
<div class="titleStyle">
<span>ITU爬取任务</span>
</div>
<div class="wordStyle">
<span>任务采集目标数: {{ totalDataNumber }}</span>
</div>
<div class="wordStyle">
<span>任务执行成功统计: {{ totalPageNumber }}</span>
</div>
<div class="wordStyle">
<span>采集速度: {{ totalTargetNumber }} </span>
</div>
<div class="wordStyle">
<span>错误率: {{ totalTargetNumber }} </span>
</div>
</div>
<div class="dataCard">
<div class="titleStyle">
<span>ST爬取任务</span>
</div>
<div class="wordStyle">
<span>任务采集目标数: {{ totalDataNumber }}</span>
</div>
<div class="wordStyle">
<span>任务执行成功统计: {{ totalPageNumber }}</span>
</div>
<div class="wordStyle">
<span>采集速度: {{ totalTargetNumber }} </span>
</div>
<div class="wordStyle">
<span>错误率: {{ totalTargetNumber }} </span>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
defineProps({
totalDataNumber: {
type: String,
default: ''
},
totalPageNumber: {
type: String,
default: ''
},
totalTargetNumber: {
type: String,
default: ''
},
successTask: {
type: String,
default: ''
},
failTask: {
type: String,
default: ''
},
unusualTask: {
type: String,
default: ''
},
speed: {
type: String,
default: ''
},
errorRate: {
type: String,
default: ''
}
})
</script>
<style lang="scss" scoped>
.dataCard {
background-image: url("@/assets/picture/box2.png");
background-size: 100% 100%;
background-repeat: no-repeat;
// background: #c6ebfc;
// border: 1.5px solid rgb(193, 188, 188);
width: 360px;
height: 100%;
border-radius: 5px;
}
.titleStyle {
font-size: 34px;
margin-top: 3.5%;
color: #FFFFFF;
}
.wordStyle {
font-size: 24px;
color: #FFFFFF;
}
</style>
<!-- 任务监控盒子组件 -->
<template>
<div class="card">
<div class="text-left textStyle ">
<span>任务监控</span>
</div>
<div class="flex flex-wrap ">
<taskMonitoringCard></taskMonitoringCard>
</div>
</div>
</template>
<script lang="ts" setup>
import taskMonitoringCard from './taskCard.vue'
defineProps({
title: {
type: String,
default: ''
},
desc: {
type: String,
default: ''
}
})
</script>
<style lang="scss" scoped>
.card {
background: #ffffff;
margin-top: 12px;
border: 1.5px solid rgb(193, 188, 188);
height: 53vh;
width: 95.5%;
display: flex;
gap: 20px;
// flex-wrap: wrap;
.textStyle {
writing-mode: vertical-lr;
font-size: 24px;
letter-spacing: 3px;
// margin-top: -1.5%;
text-align: center;
}
}
</style>
<template>
<div>
<div class="text-left timeStyle">
</div>
<div class="border-100">
<statusMonitor></statusMonitor>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import statusMonitor from './components/statusMonitor.vue'
const loading = ref(false);
const statusList = ref([]);
const title = ref(' ');
const timeValue = ref();
onMounted(() => {
});
const changeTime = () => {
console.log(timeValue.value);
}
</script>
<style lang="scss" scoped>
.timeStyle {
margin-top: 5px;
padding: 10px;
}
.integratedMonitoringBox {
background: #ffffff;
margin-bottom: 20px;
border: 1.5px solid rgb(193, 188, 188);
}
</style>
<template>
<div class="text-left p-10">
<el-form inline>
<el-form-item label="角色选择:">
<el-select v-model="taskValue" placeholder=" " style="width: 200px">
<el-option v-for="item in rolesOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary">创建用户</el-button>
</el-form-item>
<el-form-item>
<el-button type="danger">批量删除</el-button>
</el-form-item>
</el-form>
</div>
<el-table :data="tableData" style="width: 100%" border>
<el-table-column type="selection" width="40" />
<el-table-column property="name" label="序号" width="55" />
<el-table-column property="name" label="用户名" show-overflow-tooltip />
<el-table-column property="name" label="用户名称" show-overflow-tooltip />
<el-table-column property="name" label="角色" show-overflow-tooltip />
<el-table-column property="name" label="创建时间" show-overflow-tooltip />
<el-table-column label="操作" width="240">
<template #default="scope">
<el-button type="primary" plain @click="handleDetails(scope.row)">
编辑
</el-button>
<el-button type="success" plain @click="handleDetails(scope.row)">
详情
</el-button>
<el-button type="danger" plain @click="handleConfirm(scope.row)">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<div class="pagination w-full flex flex-row-reverse pr-4 m-t-4">
<Pagination :total="pageObj.total" v-model:page="pageObj.pageNo" v-model:limit="pageObj.pageSize"
@pagination="getData" />
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import Pagination from '@/components/pagination/index.vue'
const errorValue = ref('')
const taskValue = ref('')
const timeValue = ref('')
const searchInput = ref('')
const rolesOptions = [
{
value: 'task1',
label: '管理员',
},
{
value: 'task2',
label: '用户',
},
]
const tableData = ref([
{
name: '1',
address: 'sk网',
address1: '成功',
address2: '100',
address3: '0',
address4: '无',
address5: '10s',
address6: '2023-05-13 10:00:00',
}
])
const pageObj = ref({
total: 10,
pageSize: 10,
pageNo: 1
})
const handleDetails = (row: any) => {
console.log(row);
}
const handleConfirm = (row: any) => {
console.log(row);
}
const getData = () => {
console.log('getData');
}
</script>
<style scoped></style>
<!-- 任务执行统计卡片组件 -->
<template>
<div class="taskCard p-7" v-for="i in options" :key="i.value">
<div class="flex justify-center">
<el-form-item>
<span class="titleStyle">任务名称</span>
</el-form-item>
</div>
<div class="flex justify-center">
<el-form-item>
<el-button type="primary">编辑</el-button>
<el-button type="success">执行记录</el-button>
<el-button type="danger">删除</el-button>
</el-form-item>
</div>
<div class="wordStyle flex justify-center">
<el-form-item>
<el-space>
<span class="wordStyle">启用/停止: </span>
<el-switch v-model="taskSwitch" />
<span class="wordStyle">执行频率: {{ successTask }} </span>
</el-space>
</el-form-item>
</div>
<div class="wordStyle flex justify-center">
<el-form-item>
<el-space>
<span class="wordStyle">执行次数: {{ failTask }} 个; </span>
<span class="wordStyle">成功次数: {{ failTask }}</span>
</el-space>
</el-form-item>
</div>
<div class="wordStyle flex justify-center">
<el-form-item>
<el-space>
<span class="wordStyle">失败次数: {{ failTask }} 个; </span>
<span class="wordStyle">异常记录: {{ unusualTask }}</span>
</el-space>
</el-form-item>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
defineProps({
successTask: {
type: String,
default: ''
},
failTask: {
type: String,
default: ''
},
unusualTask: {
type: String,
default: ''
}
})
const taskSwitch = ref(false)
const options = [
{
value: 'year',
label: '近一年',
},
{
value: 'month',
label: '近一月',
},
{
value: 'week',
label: '近一周',
},
{
value: 'day',
label: '近一日',
},
{
value: 'year',
label: '近一年',
},
{
value: 'month',
label: '近一月',
},
]
</script>
<style lang="scss" scoped>
.taskCard {
background-image: url("@/assets/picture/box2.png");
background-size: 100% 100%;
background-repeat: no-repeat;
margin-bottom: 20px;
width: 380px;
font-size: 20px;
height: 100%;
border-radius: 7px;
.titleStyle {
font-size: 22px;
margin-top: -5px;
color: #FFFFFF;
}
.wordStyle {
font-size: 16px;
color: #FFFFFF;
}
}
.el-button:focus {
outline: none;
}
</style>
<template>
<div class="m-t-7">
</div>
<div class="text-left p-6 toolbarStyle">
<div class="formStyle">
<el-form inline>
<el-form-item>
<el-text class="mx-1">所属爬虫:</el-text>
</el-form-item>
<el-form-item>
<div>
<el-select v-model="taskValue" placeholder="请选择" style="width: 220px">
<el-option v-for="item in taskOptions" :key="item.value" :label="item.label"
:value="item.value" />
</el-select>
</div>
</el-form-item>
<el-form-item>
<el-text class="mx-1">任务名称:</el-text>
</el-form-item>
<el-form-item>
<el-input v-model="taskValue" placeholder="请输入" style="width: 220px" />
</el-form-item>
<el-form-item>
<el-space>
<el-button type="primary">查询</el-button>
</el-space>
</el-form-item>
<el-form-item>
<el-space>
<el-button type="primary">新建任务</el-button>
</el-space>
</el-form-item>
</el-form>
</div>
</div>
<div class="cardStyle">
<taskCard successTask="100" failTask="10" unusualTask="1" />
<div class="pagination w-full flex flex-row-reverse pr-18 m-t-0">
<Pagination :total="pageObj.total" v-model:page="pageObj.pageNo" v-model:limit="pageObj.pageSize"
@pagination="getData" />
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { Search } from '@element-plus/icons-vue'
import Pagination from '@/components/pagination/index.vue'
import taskCard from './components/taskCard.vue'
const errorValue = ref('')
const taskValue = ref('')
const timeValue = ref('')
const searchInput = ref('')
const taskOptions = [
{
value: 'task1',
label: 'sk网',
},
{
value: 'task2',
label: 'nasa网',
},
{
value: 'task3',
label: '网',
},
]
const tableData = ref([
{
name: '1',
address: 'sk网',
address1: '成功',
address2: '100',
address3: '0',
address4: '无',
address5: '10s',
address6: '2023-05-13 10:00:00',
}
])
const pageObj = ref({
total: 10,
pageSize: 10,
pageNo: 1
})
const getData = () => {
console.log('getData');
}
</script>
<style scoped>
.cardStyle {
display: flex;
flex-wrap: wrap;
justify-content: space-around;
margin-top: 1.5%;
padding: 8px;
height: 26vh;
}
.el-text {
color: #FFFFFF;
}
.toolbarStyle {
background-image: url("@/assets/picture/box3.png");
background-size: 100% 100%;
background-repeat: no-repeat;
}
.formStyle {
display: flex;
justify-content: space-around;
padding: 3px;
}
</style>
<style>
/* 修改el选择器的样式 */
.el-select__wrapper {
background-color: #1d5484;
box-shadow: none;
}
/* 删除el选择器选中时的边框 */
.is-hovering {
box-shadow: none !important;
}
/* 修改el输入框的样式 */
.el-input__wrapper {
background-color: #1d5484;
box-shadow: none;
}
/* 修改el下拉框的背景颜色 */
.el-select-dropdown {
background-color: #1d5484;
}
/* 修改el下拉框选项的字体颜色 */
.el-select-dropdown__item {
color: #FFFFFF;
}
/* 修改el下拉框选项选中时的文字颜色 */
.el-select-dropdown__item:hover{
color:#409eff;
}
</style>
\ No newline at end of file
<template>
<div class="m-t-7">
</div>
<div class="text-left p-6 toolbarStyle">
<div class="formStyle">
<el-form inline>
<el-form-item>
<el-text class="mx-1">任务名称:</el-text>
</el-form-item>
<el-form-item>
<div>
<el-select v-model="taskValue" placeholder="请选择" style="width: 220px">
<el-option v-for="item in taskOptions" :key="item.value" :label="item.label"
:value="item.value" />
</el-select>
</div>
</el-form-item>
<el-form-item>
<el-text class="mx-1">时间:</el-text>
</el-form-item>
<el-form-item>
<el-date-picker v-model="timeValue" type="datetimerange" start-placeholder="Start date"
end-placeholder="End date" format="YYYY-MM-DD HH:mm:ss" date-format="YYYY/MM/DD ddd"
time-format="A hh:mm:ss" />
</el-form-item>
<el-form-item>
<el-text class="mx-1">调度状态:</el-text>
</el-form-item>
<el-form-item>
<div>
<el-select v-model="taskValue" placeholder="请选择" style="width: 220px">
<el-option v-for="item in taskOptions" :key="item.value" :label="item.label"
:value="item.value" />
</el-select>
</div>
</el-form-item>
<el-form-item>
<el-space>
<el-button type="primary">查询</el-button>
</el-space>
</el-form-item>
</el-form>
</div>
</div>
<div class="m-t-8">
</div>
<el-table :data="tableData" style="width: 100%" border>
<el-table-column type="selection" width="40" />
<el-table-column property="name" label="序号" width="55" />
<el-table-column property="address" label="任务名称" show-overflow-tooltip />
<el-table-column property="address" label="目标网址" show-overflow-tooltip />
<el-table-column property="status" label="是否启用">
<template #default="scope">
<el-switch v-model="scope.row.status" @change="changeSwitch(scope.row)" />
</template>
</el-table-column>
<el-table-column property="address" label="执行频率" show-overflow-tooltip />
<el-table-column property="address" label="执行次数" show-overflow-tooltip />
<el-table-column property="address" label="成功次数" show-overflow-tooltip />
<el-table-column property="address" label="失败次数" width="200" show-overflow-tooltip />
<el-table-column property="address" label="异常记录" width="200" show-overflow-tooltip />
<el-table-column label="操作" width="220">
<template #default="scope">
<el-button size="small" plain type="primary" @click="handleDetails(scope.row)">
编辑
</el-button>
<el-button size="small" plain type="success" @click="handleDetails(scope.row)">
详情
</el-button>
<el-button size="small" plain type="danger" @click="handleConfirm(scope.row)">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<div class="pagination w-full flex flex-row-reverse pr-4 m-t-4">
<Pagination :total="pageObj.total" v-model:page="pageObj.pageNo" v-model:limit="pageObj.pageSize"
@pagination="getData" />
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import Pagination from '@/components/pagination/index.vue'
import { Search } from '@element-plus/icons-vue'
const searchInput = ref('')
const errorValue = ref('')
const taskValue = ref('')
const timeValue = ref('')
const taskOptions = [
{
value: 'task1',
label: 'sk网',
},
{
value: 'task2',
label: 'nasa网',
},
{
value: 'task3',
label: '网',
},
]
const errorOptions = [
{
value: 'task1',
label: '网络中断',
},
{
value: 'task2',
label: '地址不存在',
},
{
value: 'task3',
label: '反爬虫',
},
]
const tableData = ref([
{
name: '1',
address: 'sk网',
address1: '成功',
address2: '100',
address3: '0',
address4: '无',
address5: '10s',
address6: '2023-05-13 10:00:00',
status: 0
}
])
const pageObj = ref({
total: 10,
pageSize: 10,
pageNo: 1
})
const paginationSize = ref('default')
const disabled = ref(false)
const background = ref(false)
const currentPage4 = ref(4)
const pageSize4 = ref(10)
const changeSwitch = (row: any) => {
console.log(row);
}
const handleSizeChange = (val: number) => {
}
const handleCurrentChange = (val: number) => {
}
const handleDetails = (row: any) => {
console.log(row);
}
const handleConfirm = (row: any) => {
console.log(row);
}
const getData = () => {
console.log('getData');
}
</script>
<style scoped>
.toolbarStyle {
background-image: url("@/assets/picture/box3.png");
background-size: 100% 100%;
background-repeat: no-repeat;
}
.formStyle {
display: flex;
justify-content: space-around;
padding: 3px;
}
.el-text {
color: #FFFFFF;
}
</style>
<style>
/* 修改el选择器的样式 */
.el-select__wrapper {
background-color: #1d5484;
box-shadow: none;
}
/* 删除el选择器选中时的边框 */
.is-hovering {
box-shadow: none !important;
}
/* 修改el输入框的样式 */
.el-input__wrapper {
background-color: #1d5484;
box-shadow: none;
}
/* 修改el下拉框的背景颜色 */
.el-select-dropdown {
background-color: #1d5484;
}
/* 修改el下拉框选项的字体颜色 */
.el-select-dropdown__item {
color: #FFFFFF;
}
/* 修改el下拉框选项选中时的文字颜色 */
.el-select-dropdown__item:hover {
color: #409eff;
}
.el-date-picker__wrapper {
border: none;
}
</style>
\ No newline at end of file
<template>
<div class="text-left p-10">
<el-form inline>
<el-form-item label="任务选择:">
<el-select v-model="taskValue" placeholder=" " style="width: 220px">
<el-option v-for="item in taskOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="异常选择:">
<el-select v-model="errorValue" placeholder=" " style="width: 220px">
<el-option v-for="item in errorOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="时间:">
<el-date-picker style="width: 240px" v-model="timeValue" type="datetimerange" range-separator="-" start-placeholder="开始时间"
end-placeholder="结束时间" />
</el-form-item>
<el-form-item>
<el-space>
<el-button type="primary">查询</el-button>
<el-button type="danger">批量确认</el-button>
</el-space>
</el-form-item>
</el-form>
</div>
<el-table :data="tableData" style="width: 100%" border>
<el-table-column type="selection" width="40" />
<el-table-column property="name" label="序号" width="55" />
<el-table-column property="address" label="任务名称" show-overflow-tooltip />
<el-table-column property="address" label="执行结果" show-overflow-tooltip />
<el-table-column property="address" label="收集数" show-overflow-tooltip />
<el-table-column property="address" label="丢弃数" show-overflow-tooltip />
<el-table-column property="address" label="异常信息" show-overflow-tooltip />
<el-table-column property="address" label="持续时间" show-overflow-tooltip />
<el-table-column property="address" label="开始时间" width="200" show-overflow-tooltip />
<el-table-column property="address" label="结束时间" width="200" show-overflow-tooltip />
<el-table-column label="操作" width="220">
<template #default="scope">
<el-button size="small" plain type="success" @click="handleDetails(scope.row)">
详情
</el-button>
<el-button size="small" plain type="danger" @click="handleConfirm(scope.row)">
异常确认
</el-button>
</template>
</el-table-column>
</el-table>
<div class="pagination w-full flex flex-row-reverse pr-4 m-t-4">
<Pagination :total="pageObj.total" v-model:page="pageObj.pageNo" v-model:limit="pageObj.pageSize"
@pagination="getData" />
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import Pagination from '@/components/pagination/index.vue'
const errorValue = ref('')
const taskValue = ref('')
const timeValue = ref('')
const taskOptions = [
{
value: 'task1',
label: 'sk网',
},
{
value: 'task2',
label: 'nasa网',
},
{
value: 'task3',
label: '网',
},
]
const errorOptions = [
{
value: 'task1',
label: '网络中断',
},
{
value: 'task2',
label: '地址不存在',
},
{
value: 'task3',
label: '反爬虫',
},
]
const tableData = ref([
{
name: '1',
address: 'sk网',
address1: '成功',
address2: '100',
address3: '0',
address4: '无',
address5: '10s',
address6: '2023-05-13 10:00:00',
}
])
const pageObj = ref({
total: 10,
pageSize: 10,
pageNo: 1
})
const handleDetails = (row: any) => {
console.log(row);
}
const handleConfirm = (row: any) => {
console.log(row);
}
const getData = () => {
console.log('getData');
}
</script>
<style scoped></style>
<template>
<div class="app-container">
<!-- 添加服务器选择器 -->
<SelectScrapydServer
v-model:value="scrapydServerId"
@change="handleServerChange"
@on-init="handleServerInit"
/>
<SelectScrapydServer v-model:value="scrapydServerId" @change="handleServerChange" @on-init="handleServerInit" />
<el-card class="box-card">
<template #header>
<div class="card-header">
<span>爬虫项目管理</span>
<div class="right-panel">
<el-button type="primary" @click="handleAddProject">
<el-icon><Plus /></el-icon>添加项目
<el-icon>
<Plus />
</el-icon>添加项目
</el-button>
<el-button type="primary" @click="refreshProjectList">
<el-icon><Refresh /></el-icon>刷新
<el-icon>
<Refresh />
</el-icon>刷新
</el-button>
</div>
</div>
</template>
<el-table v-loading="loading" :data="projectList" stripe border style="width: 100%">
<el-table-column label="序号" width="80" align="center">
<template #default="scope">
{{ scope.$index + 1 }}
</template>
</el-table-column>
<el-table-column prop="name" label="项目名称" align="center" width="800"/>
<el-table-column prop="name" label="项目名称" align="center" width="800" />
<el-table-column label="操作" align="center">
<template #default="scope">
<el-button text type="primary" size="small" @click="viewSpiders(scope.row)">
<el-icon><Document /></el-icon>Spider
<el-icon>
<Document />
</el-icon>Spider
</el-button>
</template>
</el-table-column>
<el-table-column label="查看版本" align="center">
<template #default="scope">
<el-button text type="success" size="small" @click="viewVersions(scope.row)">
<el-icon><Files /></el-icon>版本
<el-icon>
<Files />
</el-icon>版本
</el-button>
</template>
</el-table-column>
<el-table-column label="查看任务" align="center">
<template #default="scope">
<el-button text type="info" size="small" @click="viewJobs(scope.row)">
<el-icon><List /></el-icon>任务
<el-icon>
<List />
</el-icon>任务
</el-button>
</template>
</el-table-column>
<el-table-column label="查看日志" align="center">
<template #default="scope">
<el-button text type="warning" size="small" @click="viewLogs(scope.row)">
<el-icon><Notebook /></el-icon>日志
<el-icon>
<Notebook />
</el-icon>日志
</el-button>
</template>
</el-table-column>
<el-table-column label="更新版本" align="center">
<template #default="scope">
<el-button text type="primary" size="small" @click="handleUpdateVersion(scope.row)">
<el-icon><EditPen /></el-icon>编辑
<el-icon>
<EditPen />
</el-icon>编辑
</el-button>
</template>
</el-table-column>
<el-table-column label="删除项目" align="center">
<template #default="scope">
<el-button text type="danger" size="small" @click="handleDeleteProject(scope.row)">
<el-icon><Delete /></el-icon>删除
<el-icon>
<Delete />
</el-icon>删除
</el-button>
</template>
</el-table-column>
......@@ -78,15 +90,10 @@
<el-dialog v-model="addDialogVisible" :title="dialogTitle" width="500px">
<el-form :model="addForm" label-width="100px" :rules="rules" ref="addFormRef">
<el-form-item label="项目文件" prop="file">
<el-upload
class="upload-demo"
drag
action=""
:auto-upload="false"
:limit="1"
:on-change="handleFileChange"
>
<el-icon class="el-icon--upload"><Upload /></el-icon>
<el-upload class="upload-demo" drag action="" :auto-upload="false" :limit="1" :on-change="handleFileChange">
<el-icon class="el-icon--upload">
<Upload />
</el-icon>
<div class="el-upload__text">拖拽文件到此处或 <em>点击上传</em></div>
<template #tip>
<div class="el-upload__tip">请上传爬虫项目文件(.zip 或 .egg)</div>
......@@ -102,8 +109,9 @@
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="addDialogVisible = false">取消</el-button>
<el-button type="primary" @click="submitAddProject" :loading="submitLoading">确认</el-button>
<el-button @click="addDialogVisible = false" class="outline-none">取消</el-button>
<el-button type="primary" @click="submitAddProject" :loading="submitLoading"
class="outline-none">确认</el-button>
</span>
</template>
</el-dialog>
......@@ -175,7 +183,7 @@ const refreshProjectList = () => {
const viewSpiders = (row: any) => {
router.push({
path: '/spider',
query: {
query: {
project: row.name,
scrapydServerId: scrapydServerId.value
}
......@@ -186,7 +194,7 @@ const viewSpiders = (row: any) => {
const viewVersions = (row: any) => {
router.push({
path: '/project/version-list',
query: {
query: {
project: row.name,
scrapydServerId: scrapydServerId.value
}
......@@ -197,7 +205,7 @@ const viewVersions = (row: any) => {
const viewJobs = (row: any) => {
router.push({
path: '/job/list',
query: {
query: {
project: row.name,
scrapydServerId: scrapydServerId.value
}
......@@ -208,7 +216,7 @@ const viewJobs = (row: any) => {
const viewLogs = (row: any) => {
router.push({
path: '/logs/project',
query: {
query: {
project: row.name,
scrapydServerId: scrapydServerId.value
}
......@@ -248,21 +256,21 @@ const handleFileChange = (file: UploadFile) => {
ElMessage.warning('只能上传 .egg 或 .zip 格式的文件!');
return false;
}
// 设置文件和自动填写项目名称
addForm.value.file = file.raw
// 自动提取项目名称(仅在添加模式下)
if (!isEditMode.value && !addForm.value.project) {
addForm.value.project = fileName.replace(/\.(egg|zip)$/, '');
// 自动生成版本号
if (!addForm.value.version) {
const now = new Date();
addForm.value.version = `${now.getFullYear()}${String(now.getMonth() + 1).padStart(2, '0')}${String(now.getDate()).padStart(2, '0')}`;
}
}
return true;
}
return false;
......@@ -271,17 +279,17 @@ const handleFileChange = (file: UploadFile) => {
// 提交添加项目
const submitAddProject = async () => {
if (!addFormRef.value) return
await addFormRef.value.validate(async (valid: any) => {
if (valid && addForm.value.file) {
submitLoading.value = true
const formData = new FormData()
formData.append('project', addForm.value.project)
formData.append('version', addForm.value.version)
formData.append('file', addForm.value.file)
formData.append('scrapydServerId', scrapydServerId.value)
try {
await addProjectVersion(formData)
ElMessage.success(isEditMode.value ? '更新版本成功' : '添加项目成功')
......@@ -305,13 +313,14 @@ const handleDeleteProject = (row: any) => {
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
type: 'warning',
customClass: 'custom-message-box',
}
).then(async () => {
try {
await deleteProject({
await deleteProject({
project: row.name,
scrapydServerId: scrapydServerId.value
scrapydServerId: scrapydServerId.value
})
ElMessage.success('删除项目成功')
fetchProjectList()
......@@ -375,4 +384,26 @@ onMounted(() => {
.box-card {
margin-top: 20px;
}
.el-button:focus {
outline: none;
}
</style>
<style>
.el-dialog__headerbtn:focus {
outline: none;
}
.custom-message-box .el-button--primary {
outline: none;
}
.custom-message-box .el-button--default {
outline: none;
}
.custom-message-box .el-message-box__headerbtn {
outline: none;
}
</style>
\ No newline at end of file
<template>
<div class="app-container">
<schedule-tool @success="getData" />
<div style="height: 20px"></div>
<schedule-table
:data="list"
@success="getData"
......
......@@ -19,21 +19,16 @@
<el-button type="primary" @click="getList">查询</el-button>
<el-button type="danger" @click="handleClearLogs">清空日志</el-button>
<el-button type="default" @click="setupAutoRefresh">
<el-icon v-if="isAutoRefreshing"><Loading /></el-icon>
<el-icon v-if="isAutoRefreshing">
<Loading />
</el-icon>
{{ isAutoRefreshing ? '停止自动刷新' : '自动刷新' }}
</el-button>
</el-form-item>
</el-form>
</div>
<el-table
v-loading="listLoading"
:data="list"
element-loading-text="Loading"
border
fit
highlight-current-row
>
<el-table v-loading="listLoading" :data="list" element-loading-text="Loading" border fit highlight-current-row>
<el-table-column align="center" label="ID" width="80">
<template #default="scope">
{{ scope.$index + 1 }}
......@@ -83,15 +78,9 @@
<div class="pagination-container">
<el-config-provider :locale="zhCn">
<el-pagination
:current-page="listQuery.page"
:page-sizes="[10, 20, 30, 50]"
:page-size="listQuery.limit"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
<el-pagination :current-page="listQuery.page" :page-sizes="[10, 20, 30, 50]" :page-size="listQuery.limit"
layout="total, sizes, prev, pager, next, jumper" :total="total" @size-change="handleSizeChange"
@current-change="handleCurrentChange" />
</el-config-provider>
</div>
</div>
......@@ -154,7 +143,7 @@ const getList = async () => {
})
list.value = res.data.list || []
total.value = res.data.total || 0
// 更新状态计数
if (res.data) {
statusInfo.total = res.data.total || 0
......@@ -203,7 +192,7 @@ const setupAutoRefresh = () => {
isAutoRefreshing.value = true
ElMessage({
type: 'success',
message: `已设置每${refreshFrequency/1000}秒自动刷新`
message: `已设置每${refreshFrequency / 1000}秒自动刷新`
})
}
}
......@@ -212,13 +201,13 @@ const setupAutoRefresh = () => {
const handleClearLogs = () => {
// 构建确认提示信息
let confirmMsg = '确认清空'
if (status.value !== 'total') {
confirmMsg += `${status.value === 'success' ? '成功' : '失败'}`
}
confirmMsg += '调度日志'
if (listQuery.project) {
confirmMsg += `(项目:${listQuery.project}`
if (listQuery.spider) {
......@@ -228,13 +217,14 @@ const handleClearLogs = () => {
} else if (listQuery.spider) {
confirmMsg += `(爬虫:${listQuery.spider})`
}
confirmMsg += '?'
ElMessageBox.confirm(confirmMsg, '提示', {
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning'
type: 'warning',
customClass: 'custom-message-box',
}).then(async () => {
try {
// 构造清空参数,可以按项目、爬虫过滤
......@@ -245,7 +235,7 @@ const handleClearLogs = () => {
scrapydServerId: listQuery.scrapydServerId,
job_id: listQuery.job_id
}
await scheduleApi.removeScheduleLogs(clearParams)
ElMessage({
type: 'success',
......@@ -271,12 +261,12 @@ onMounted(() => {
const querySpider = route.query.spider as string
const queryServerId = route.query.scrapydServerId as string
const queryJob = route.query.job as string
if (queryProject) listQuery.project = queryProject
if (querySpider) listQuery.spider = querySpider
if (queryServerId) listQuery.scrapydServerId = queryServerId
if (queryJob) listQuery.job_id = queryJob
getList()
})
......@@ -297,4 +287,24 @@ onBeforeUnmount(() => {
.pagination-container {
padding: 10px 0;
}
.el-button:focus {
outline: none;
}
</style>
<style>
.custom-message-box .el-button--primary {
outline: none;
}
.custom-message-box .el-button--default {
outline: none;
}
.custom-message-box .el-message-box__headerbtn {
outline: none;
}
</style>
\ No newline at end of file
......@@ -198,3 +198,12 @@ const submit = () => {
};
</script>
<style>
.el-button:focus {
outline: none;
}
.el-dialog__headerbtn:focus {
outline: none;
}
</style>
......@@ -31,7 +31,8 @@ const handleDelete = () => {
ElMessageBox.confirm(`确定要删除服务"${props.row.server_name}"吗?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
type: 'warning',
customClass: 'custom-message-box',
}).then(async () => {
try {
const res = await deleteScrapydServer({
......@@ -55,3 +56,9 @@ const handleDelete = () => {
</script>
<style lang="scss" scoped></style>
<style>
.custom-message-box .el-message-box__headerbtn {
outline: none;
}
</style>
<template>
<div class="app-container">
<ScrapydServerTool @success="getData" />
<div style="height:20px"></div>
<ScrapydServerTable
:data="list"
:loading="listLoading"
@success="getData"
/>
</div>
</template>
......
......@@ -42,7 +42,8 @@ const confirmRemove = () => {
ElMessageBox.confirm('确认删除所选的统计数据?', '提示', {
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning'
type: 'warning',
customClass: 'custom-message-box',
})
.then(async () => {
try {
......@@ -88,6 +89,23 @@ const confirmRemove = () => {
<style lang="scss" scoped>
.stats-remove {
margin-left: 10px;
margin-top: -2px;
display: inline-block;
}
.el-button:focus {
outline: none;
}
.custom-message-box .el-button--primary {
outline: none;
}
.custom-message-box .el-button--default {
outline: none;
}
.custom-message-box .el-message-box__headerbtn {
outline: none;
}
</style>
......@@ -2,58 +2,28 @@
<div class="stats-tool">
<el-form :inline="true" class="demo-form-inline">
<el-form-item label="服务器" size="default">
<select-scrapyd-server
v-model:value="serverValue"
@change="handleServerChange"
/>
<select-scrapyd-server v-model:value="serverValue" @change="handleServerChange" />
</el-form-item>
<el-form-item size="default" label="项目">
<el-select
v-model="projectValue"
placeholder="请选择项目"
clearable
@change="handleProjectChange"
style="width: 180px"
size="default"
>
<el-option
v-for="item in projectOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
<el-select v-model="projectValue" placeholder="请选择项目" clearable @change="handleProjectChange"
style="width: 180px" size="default">
<el-option v-for="item in projectOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="爬虫" size="default">
<el-select
v-model="spiderValue"
placeholder="请选择爬虫"
clearable
size="default"
@change="handleSpiderChange"
style="width: 180px"
>
<el-option
v-for="item in spiderOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
<el-select v-model="spiderValue" placeholder="请选择爬虫" clearable size="default" @change="handleSpiderChange"
style="width: 180px">
<el-option v-for="item in spiderOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSearch">查询</el-button>
<el-button type="danger" @click="confirmClear">清空数据</el-button>
<stats-remove
:scrapyd-server-id="serverValue"
:project="projectValue"
:spider="spiderValue"
button-text="批量删除"
@success="handleRemoveSuccess"
/>
<stats-remove :scrapyd-server-id="serverValue" :project="projectValue" :spider="spiderValue" button-text="批量删除"
@success="handleRemoveSuccess" />
</el-form-item>
</el-form>
</div>
......@@ -106,12 +76,12 @@ const spiderOptions = ref<OptionItem[]>([])
// 获取项目列表
const getProjects = async () => {
if (!serverValue.value) return
try {
const res = await projectApi.listProjects({
scrapydServerId: serverValue.value
})
if (res.code === 0) {
projectOptions.value = (res.data || []).map((item: string) => ({
label: item,
......@@ -126,13 +96,13 @@ const getProjects = async () => {
// 获取爬虫列表
const getSpiders = async () => {
if (!serverValue.value || !projectValue.value) return
try {
const res = await projectApi.listSpiders({
scrapydServerId: serverValue.value,
project: projectValue.value
})
if (res.code === 0) {
spiderOptions.value = (res.data || []).map((item: string) => ({
label: item,
......@@ -148,13 +118,13 @@ const getSpiders = async () => {
const handleServerChange = async (val: string) => {
serverValue.value = val
emit('update:scrapyd-server-id', val)
// 清空项目和爬虫选择
projectValue.value = ''
emit('update:project', '')
spiderValue.value = ''
emit('update:spider', '')
// 重新加载项目列表
await getProjects()
}
......@@ -163,14 +133,14 @@ const handleServerChange = async (val: string) => {
const handleProjectChange = async (val: string) => {
projectValue.value = val
emit('update:project', val)
// 清空爬虫选择
spiderValue.value = ''
emit('update:spider', '')
// 重新加载爬虫列表
await getSpiders()
// 触发项目变化事件
emit('project-change')
}
......@@ -179,7 +149,7 @@ const handleProjectChange = async (val: string) => {
const handleSpiderChange = async (val: string) => {
spiderValue.value = val
emit('update:spider', val)
// 触发爬虫变化事件
emit('spider-change')
}
......@@ -199,7 +169,9 @@ const confirmClear = () => {
ElMessageBox.confirm('确认清空所有统计数据?', '提示', {
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning'
type: 'warning',
customClass: 'custom-message-box',
})
.then(async () => {
try {
......@@ -208,7 +180,7 @@ const confirmClear = () => {
project: projectValue.value,
spider: spiderValue.value
})
if (res.code === 0) {
ElMessage({
type: 'success',
......@@ -241,7 +213,7 @@ watch(() => props.spider, (val) => {
onMounted(async () => {
if (serverValue.value) {
await getProjects()
if (projectValue.value) {
await getSpiders()
}
......@@ -250,8 +222,37 @@ onMounted(async () => {
</script>
<style lang="scss" scoped>
// .custom-message-box .el-button--primary {
// outline: none;
// // background-color: aqua;
// }
.stats-tool {
margin-bottom: 20px;
text-align: left;
}
:deep(.Border) {
border-color: white;
}
::v-deep .el-button:focus {
outline: none;
}
</style>
<style>
.custom-message-box .el-button--primary {
outline: none;
}
.custom-message-box .el-button--default {
outline: none;
}
.custom-message-box .el-message-box__headerbtn {
outline: none;
}
</style>
......@@ -7,7 +7,6 @@
<span>爬虫统计数据</span>
</div>
</template>
<!-- 工具栏组件 -->
<stats-tool
v-model:scrapyd-server-id="scrapydServerId"
......@@ -17,14 +16,12 @@
@spider-change="handleSpiderChange"
@success="handleSuccess"
/>
<!-- 统计数据表格 -->
<stats-table
:data="list"
v-loading="listLoading"
@sort-change="handleSortChange"
/>
<!-- 分页控件 -->
<div class="pagination-container">
<el-config-provider :locale="zhCn">
......
......@@ -30,7 +30,7 @@ export default defineConfig({
proxy: {
// 代理API请求,使用更精确的路径匹配
'/api': {
target: 'http://127.0.0.1:5001/',
target: 'http://127.0.0.1:5000/',
changeOrigin: true,
},
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment