Commit 1d23619c by 周田

Merge branch 'liucan' into 'main'

feat:监控状态页面新增错误日志统计图表

See merge request !14
parents 865f028d 2f48a31a
......@@ -16,6 +16,7 @@
"animate.css": "^4.1.1",
"axios": "^1.9.0",
"cron-parser": "^5.4.0",
"dayjs": "^1.11.19",
"echarts": "^5.6.0",
"element-plus": "^2.9.10",
"js-cookie": "^3.0.5",
......@@ -24,6 +25,7 @@
"path-to-regexp": "^3.3.0",
"pinia": "^3.0.2",
"qs": "~6.11.2",
"socket.io-client": "^4.8.1",
"uuid": "^11.1.0",
"vue": "^3.5.13",
"vue-router": "^4.5.1"
......@@ -1415,6 +1417,12 @@
"win32"
]
},
"node_modules/@socket.io/component-emitter": {
"version": "3.1.2",
"resolved": "https://registry.npmmirror.com/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz",
"integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==",
"license": "MIT"
},
"node_modules/@types/estree": {
"version": "1.0.8",
"resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.8.tgz",
......@@ -2666,6 +2674,45 @@
"vue": "^3.2.0"
}
},
"node_modules/engine.io-client": {
"version": "6.6.3",
"resolved": "https://registry.npmmirror.com/engine.io-client/-/engine.io-client-6.6.3.tgz",
"integrity": "sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w==",
"license": "MIT",
"dependencies": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.3.1",
"engine.io-parser": "~5.2.1",
"ws": "~8.17.1",
"xmlhttprequest-ssl": "~2.1.1"
}
},
"node_modules/engine.io-client/node_modules/debug": {
"version": "4.3.7",
"resolved": "https://registry.npmmirror.com/debug/-/debug-4.3.7.tgz",
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/engine.io-parser": {
"version": "5.2.3",
"resolved": "https://registry.npmmirror.com/engine.io-parser/-/engine.io-parser-5.2.3.tgz",
"integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/entities": {
"version": "4.5.0",
"resolved": "https://registry.npmmirror.com/entities/-/entities-4.5.0.tgz",
......@@ -3303,7 +3350,6 @@
"version": "2.1.3",
"resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"dev": true,
"license": "MIT"
},
"node_modules/muggle-string": {
......@@ -4080,6 +4126,68 @@
"node": ">=18"
}
},
"node_modules/socket.io-client": {
"version": "4.8.1",
"resolved": "https://registry.npmmirror.com/socket.io-client/-/socket.io-client-4.8.1.tgz",
"integrity": "sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==",
"license": "MIT",
"dependencies": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.3.2",
"engine.io-client": "~6.6.1",
"socket.io-parser": "~4.2.4"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/socket.io-client/node_modules/debug": {
"version": "4.3.7",
"resolved": "https://registry.npmmirror.com/debug/-/debug-4.3.7.tgz",
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/socket.io-parser": {
"version": "4.2.4",
"resolved": "https://registry.npmmirror.com/socket.io-parser/-/socket.io-parser-4.2.4.tgz",
"integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==",
"license": "MIT",
"dependencies": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.3.1"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/socket.io-parser/node_modules/debug": {
"version": "4.3.7",
"resolved": "https://registry.npmmirror.com/debug/-/debug-4.3.7.tgz",
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz",
......@@ -4569,6 +4677,35 @@
"typescript": ">=5.0.0"
}
},
"node_modules/ws": {
"version": "8.17.1",
"resolved": "https://registry.npmmirror.com/ws/-/ws-8.17.1.tgz",
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/xmlhttprequest-ssl": {
"version": "2.1.2",
"resolved": "https://registry.npmmirror.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz",
"integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/zrender": {
"version": "5.6.1",
"resolved": "https://registry.npmmirror.com/zrender/-/zrender-5.6.1.tgz",
......
......@@ -17,6 +17,7 @@
"animate.css": "^4.1.1",
"axios": "^1.9.0",
"cron-parser": "^5.4.0",
"dayjs": "^1.11.19",
"echarts": "^5.6.0",
"element-plus": "^2.9.10",
"js-cookie": "^3.0.5",
......@@ -25,6 +26,7 @@
"path-to-regexp": "^3.3.0",
"pinia": "^3.0.2",
"qs": "~6.11.2",
"socket.io-client": "^4.8.1",
"uuid": "^11.1.0",
"vue": "^3.5.13",
"vue-router": "^4.5.1"
......
......@@ -27,6 +27,7 @@ export const scrapydApi = {
projectLogs: "/scrapyd/projectLogs",
spiderLogs: "/scrapyd/spiderLogs",
jobLog: "/scrapyd/jobLog",
errorLogs: "/errorLogs/list",
} as const;
export const scheduleApi = {
......
import { request, POST } from '@/utils/request'
import type { ApiResponse, QueryParams } from '@/utils/request'
import { scrapydApi } from './apiPaths'
import { request, POST } from "@/utils/request";
import type { ApiResponse, QueryParams } from "@/utils/request";
import { scrapydApi } from "./apiPaths";
// 获取日志列表
export function getLogs(data: QueryParams): Promise<ApiResponse> {
return request({
url: scrapydApi.logs,
method: POST,
data
}) as unknown as Promise<ApiResponse>
data,
}) as unknown as Promise<ApiResponse>;
}
// 获取项目日志
......@@ -16,8 +16,8 @@ export function getProjectLogs(data: QueryParams): Promise<ApiResponse> {
return request({
url: scrapydApi.projectLogs,
method: POST,
data
}) as unknown as Promise<ApiResponse>
data,
}) as unknown as Promise<ApiResponse>;
}
// 获取爬虫日志
......@@ -25,8 +25,8 @@ export function getSpiderLogs(data: QueryParams): Promise<ApiResponse> {
return request({
url: scrapydApi.spiderLogs,
method: POST,
data
}) as unknown as Promise<ApiResponse>
data,
}) as unknown as Promise<ApiResponse>;
}
// 获取任务日志
......@@ -34,6 +34,15 @@ export function getJobLog(data: QueryParams): Promise<ApiResponse> {
return request({
url: scrapydApi.jobLog,
method: POST,
data
}) as unknown as Promise<ApiResponse>
data,
}) as unknown as Promise<ApiResponse>;
}
//获取错误日志列表
export function getErrorLogsList(): Promise<ApiResponse> {
return request({
url: scrapydApi.errorLogs,
method: POST,
data: null,
}) as unknown as Promise<ApiResponse>;
}
\ No newline at end of file
import { request, POST } from '@/utils/request'
import type { ApiResponse, QueryParams } from '@/utils/request'
import { dataApi } from './apiPaths'
import { request, POST } from "@/utils/request";
import type { ApiResponse, QueryParams } from "@/utils/request";
import { dataApi } from "./apiPaths";
// 获取爬虫静态数据列表
export function getStatsDataList(params: QueryParams): Promise<ApiResponse> {
return request({
url: dataApi.dataStatistics,
method: POST,
params
}) as unknown as Promise<ApiResponse>
params,
}) as unknown as Promise<ApiResponse>;
}
// 获取爬虫任务列表
......@@ -16,8 +16,8 @@ export function getSpiderTaskList(params: QueryParams): Promise<ApiResponse> {
return request({
url: dataApi.taskStatistics,
method: POST,
params
}) as unknown as Promise<ApiResponse>
params,
}) as unknown as Promise<ApiResponse>;
}
// 获取性能统计列表
......@@ -25,8 +25,8 @@ export function getPerformanceList(params: QueryParams): Promise<ApiResponse> {
return request({
url: dataApi.performanceStatistics,
method: POST,
params
}) as unknown as Promise<ApiResponse>
params,
}) as unknown as Promise<ApiResponse>;
}
// 获取爬虫任务统计列表
......@@ -34,6 +34,6 @@ export function getAllSpiderTaskStatistics(params: QueryParams): Promise<ApiResp
return request({
url: dataApi.allSpiderTaskStatistics,
method: POST,
params
}) as unknown as Promise<ApiResponse>
params,
}) as unknown as Promise<ApiResponse>;
}
\ No newline at end of file
......@@ -35,7 +35,6 @@ defineProps({
const handleClick = (item: InfoItem) => {
// 处理点击事件
console.log('Clicked item:', item)
}
</script>
......
<!-- 解析路径并跳转路由 -->
<script setup lang="ts">
import { computed, onMounted } from 'vue'
import { RouterLink } from 'vue-router'
import { isExternal } from '@/utils/validate'
import { computed, onMounted } from "vue";
import { RouterLink } from "vue-router";
import { isExternal } from "@/utils/validate";
const props = defineProps({
to: {
type: String,
required: true
}
})
required: true,
},
});
const isExtLink = computed(() => isExternal(props.to))
const isExtLink = computed(() => isExternal(props.to));
onMounted(()=>{
// console.log(props.to,'111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111')
})
onMounted(() => {});
</script>
<template>
......
<template>
<div style="margin-bottom: 0" class="menu-title">
<div class="title">{{ props.title }}</div>
<div class="low-titme">{{ props.subtitle }}</div>
<div class="sub-title">{{ props.subtitle }}</div>
</div>
</template>
......@@ -21,7 +21,7 @@ const props = defineProps<{
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
}
.low-titme {
.sub-title {
color: rgba(255, 255, 255, 0.8);
text-align: left;
margin-left: 20px;
......
......@@ -6,7 +6,7 @@
v-model:current-page="currentPage"
v-model:page-size="pageSize"
:background="true"
:page-sizes="[10]"
:page-sizes="[5, 10]"
:pager-count="pagerCount"
:total="total"
class="mt-4"
......@@ -19,71 +19,71 @@
</template>
<script lang="ts" setup>
import { computed } from 'vue'
import { computed } from "vue";
// ElConfigProvider 组件
import { ElConfigProvider } from 'element-plus';
import { ElConfigProvider } from "element-plus";
// 引入中文包
import zhCn from 'element-plus/es/locale/lang/zh-cn';
defineOptions({ name: 'Pagination' })
import zhCn from "element-plus/es/locale/lang/zh-cn";
defineOptions({ name: "Pagination" });
// 更改分页文字
zhCn.el.pagination.total = '共 `{total} 条`';
zhCn.el.pagination.goto = '跳至';
zhCn.el.pagination.pagesize = '条/页';
zhCn.el.pagination.pageClassifier = '页';
zhCn.el.pagination.total = "共 {total} 条";
zhCn.el.pagination.goto = "跳至";
zhCn.el.pagination.pagesize = "条/页";
zhCn.el.pagination.pageClassifier = "页";
const props = defineProps({
// 总条目数
total: {
required: true,
type: Number
type: Number,
},
// 当前页数:pageNo
page: {
type: Number,
default: 1
default: 1,
},
// 每页显示条目个数:pageSize
limit: {
type: Number,
default: 20
default: 10,
},
// 设置最大页码按钮数。 页码按钮的数量,当总页数超过该值时会折叠
// 移动端页码按钮的数量端默认值 5
pagerCount: {
type: Number,
default: document.body.clientWidth < 992 ? 5 : 7
}
})
default: document.body.clientWidth < 992 ? 5 : 7,
},
});
const emit = defineEmits(['update:page', 'update:limit', 'pagination'])
const emit = defineEmits(["update:page", "update:limit", "pagination"]);
const currentPage = computed({
get() {
return props.page
return props.page;
},
set(val) {
// 触发 update:page 事件,更新 limit 属性,从而更新 pageNo
emit('update:page', val)
}
})
emit("update:page", val);
},
});
const pageSize = computed({
get() {
return props.limit
return props.limit;
},
set(val) {
// 触发 update:limit 事件,更新 limit 属性,从而更新 pageSize
emit('update:limit', val)
}
})
const handleSizeChange = (val:number) => {
emit("update:limit", val);
},
});
const handleSizeChange = (val: number) => {
// 如果修改后超过最大页面,强制跳转到第 1 页
if (currentPage.value * val > props.total) {
currentPage.value = 1
currentPage.value = 1;
}
// 触发 pagination 事件,重新加载列表
emit('pagination', { page: currentPage.value, limit: val })
}
const handleCurrentChange = (val:number) => {
emit("pagination", { page: currentPage.value, limit: val });
};
const handleCurrentChange = (val: number) => {
// 触发 pagination 事件,重新加载列表
emit('pagination', { page: val, limit: pageSize.value })
}
emit("pagination", { page: val, limit: pageSize.value });
};
</script>
import { markRaw, onMounted, onUnmounted, ref, type Ref } from "vue";
import * as echarts from "echarts";
//封装图表渲染逻辑
export function useChart(chartRef: Ref<HTMLElement | null>, setChartData: any, handleClickChart?: any) {
const chartInstance = ref<echarts.ECharts | null>(null);
const initChart = async () => {
if (chartRef.value) {
//这里echarts内部已经做了响应式处理 会和vue的响应式冲突 需要取消vue的响应式
chartInstance.value = markRaw(echarts.init(chartRef.value));
const options = await setChartData();
chartInstance.value.setOption(options);
//绑定图表点击事件
if (handleClickChart) {
chartInstance.value?.on("click", handleClickChart);
}
}
};
const resizeChart = () => {
chartInstance.value?.resize();
};
onMounted(() => {
initChart();
window.addEventListener("resize", resizeChart);
});
onUnmounted(() => {
window.removeEventListener("resize", resizeChart);
if (chartInstance.value) {
chartInstance.value.dispose();
}
});
}
......@@ -124,3 +124,25 @@
.el-popper__arrow::before{
background-color: #1D5484 !important;
}
//分页器总数据量文字颜色
.el-pagination__total{
color: white !important;
}
.el-select__placeholder,.el-pagination__goto,.el-pagination__classifier,.el-range-separator{
color: white !important;
}
.el-input__inner{
color: white;
}
.el-select__placeholder{
color: #ccc !important;
}
/* 修改el输入框的样式 */
.el-input__wrapper {
background-color: #1d5484 !important;
box-shadow: none !important;
}
\ No newline at end of file
......@@ -19,7 +19,6 @@ instance.interceptors.request.use(
// 保持与原项目相同的Token头设置
config.headers["Token"] = token;
}
return config;
},
(err) => {
......
......@@ -8,7 +8,7 @@
<div class="table-content">
<el-table
v-loading="!tableData"
element-loading-background="rgba(48, 65, 86, 0.7)"
element-loading-background="rgba(48, 65, 86, 0.3)"
:data="tableData"
style="width: 100%"
border
......@@ -60,7 +60,7 @@
<div class="detailForm">
<el-dialog v-model="detailVisibleValue" center width="800px" align-center @close="handleClose" draggable>
<template #header>
<div class="text-center font-size-6">详情-{{ antennaName }}</div>
<div class="text-center font-size-6">详情{{ antennaName }}</div>
</template>
<el-row>
<el-col :span="12">
......
......@@ -8,7 +8,7 @@
<el-input placeholder="请输入目标名称:" v-model="searchTargetName" style="width: 180px" />
</div>
<div>
<span style="color: white">时间范围</span>
<span style="color: white">采集时间</span>
<el-config-provider :locale="zhCn">
<el-date-picker
type="datetimerange"
......@@ -31,7 +31,7 @@
<div class="btns">
<el-button plain type="primary" @click="handleSearch">查询</el-button>
<el-button plain @click="getData">重置表格</el-button>
<el-button plain @click="resetForm">重置</el-button>
</div>
</div>
</table-search>
......@@ -46,8 +46,8 @@
:row-style="{ height: '45.5px' }"
:header-cell-style="{ textAlign: 'center' }"
:cell-style="{ textAlign: 'center' }"
v-loading="tableData.length == 0"
element-loading-background="rgba(48, 65, 86, 0.7)"
v-loading="tableLoading"
element-loading-background="rgba(48, 65, 86, 0.3)"
>
<el-table-column property="number" label="序号" width="55" type="index" />
<el-table-column property="data.mission" label="目标名称" width="86" show-overflow-tooltip />
......@@ -113,7 +113,7 @@
<div class="detailForm">
<el-dialog v-model="detailVisibleValue" center width="765px" align-center @close="handleClose" draggable>
<template #header>
<div class="text-center font-size-6">目标详情-{{ targetName }}</div>
<div class="text-center font-size-6">目标详情{{ targetName }}</div>
</template>
<el-row>
<el-col>
......@@ -210,7 +210,7 @@
<div class="detailForm">
<el-dialog v-model="stationVisibleValue" center width="880px" align-center @close="handleClose" draggable>
<template #header>
<div class="text-center font-size-6">站点详情-{{ stationName }}</div>
<div class="text-center font-size-6">站点详情{{ stationName }}</div>
</template>
<el-row>
<el-col>
......@@ -618,15 +618,22 @@ const handleMissionDetails = async (id: any) => {
}
detailVisibleValue.value = true;
};
// 获取esa数据列表的方法
const getData = async () => {
//表格loading标记
const tableLoading = ref(false);
//重置搜索条件
const resetForm = () => {
searchStation.value = "";
searchTargetName.value = "";
searchTimeValue.value = "";
getData();
};
// 获取esa数据列表的方法
const getData = async () => {
tableLoading.value = true;
const res = await getESAList({ page: pageObj.value.pageNo, size: pageObj.value.pageSize });
pageObj.value.total = res.data.total;
tableData.value = res.data.list;
tableLoading.value = false;
};
const handleClose = () => {
detailVisibleValue.value = false;
......
......@@ -8,7 +8,7 @@
<el-input placeholder="请输入目标名称" v-model="searchTargetName" style="width: 180px" />
</div>
<div>
<span style="color: white">时间范围</span>
<span style="color: white">采集时间</span>
<el-config-provider :locale="zhCn">
<el-date-picker
type="datetimerange"
......@@ -30,7 +30,7 @@
</div>
<div class="btns">
<el-button plain type="primary" @click="handleSearch">查询</el-button>
<el-button plain="" @click="getData">重置</el-button>
<el-button plain="" @click="resetForm">重置</el-button>
</div>
</div>
</TableSearch>
......@@ -45,8 +45,8 @@
:row-style="{ height: '45px' }"
:header-cell-style="{ textAlign: 'center' }"
:cell-style="{ textAlign: 'center' }"
v-loading="tableData.length == 0"
element-loading-background="rgba(48, 65, 86, 0.7)"
v-loading="tableLoading"
element-loading-background="rgba(48, 65, 86, 0.3)"
>
<!-- <el-table-column type="selection" width="40" /> -->
<el-table-column property="number" label="序号" width="55" type="index" />
......@@ -107,7 +107,7 @@
<div class="detailForm">
<el-dialog v-model="detailVisibleValue" center width="765px" align-center @close="handleClose" draggable>
<template #header>
<div class="text-center font-size-6">详情-{{ targetName }}</div>
<div class="text-center font-size-6">详情{{ targetName }}</div>
</template>
<el-row>
<el-col :span="12">
......@@ -335,14 +335,23 @@ const handleDetails = async (id: any) => {
BFIFICdate.value = res.data.regulatory_status.d_wic === null ? "-" : res.data.regulatory_status.d_wic;
detailVisibleValue.value = true;
};
//表单loading标记
const tableLoading = ref(false);
// 获取itu数据列表的方法
const getData = async () => {
const res = await getItuList({ page: pageObj.value.pageNo, size: pageObj.value.pageSize });
//重置搜索条件
const resetForm = () => {
searchTargetName.value = "";
searchTargetId.value = "";
searchTimeValue.value = "";
getData();
};
const getData = async () => {
tableLoading.value = true;
const res = await getItuList({ page: pageObj.value.pageNo, size: pageObj.value.pageSize });
pageObj.value.total = res.data.total;
tableData.value = res.data.list;
tableLoading.value = false;
};
const handleClose = () => {
detailVisibleValue.value = false;
......@@ -352,6 +361,7 @@ const handleSearch = async () => {
ElMessage.warning("请输入搜索内容");
return;
}
tableLoading.value = true;
const res = await getItuList({
sat_name: searchTargetName.value,
ntc_id: searchTargetId.value,
......@@ -361,6 +371,7 @@ const handleSearch = async () => {
});
pageObj.value.total = res.data.total;
tableData.value = res.data.list;
tableLoading.value = false;
};
onMounted(() => {
......@@ -430,7 +441,7 @@ onMounted(() => {
}
.item-title {
color: #fff;
color: #b0dfff;
}
.item {
......
......@@ -8,7 +8,7 @@
<el-input placeholder="请输入目标名称" style="width: 180px" v-model="searchTargetName" />
</div>
<div>
<span style="color: white">时间范围</span>
<span style="color: white">采集时间</span>
<el-config-provider :locale="zhCn">
<el-date-picker
v-model="timeValue"
......@@ -30,7 +30,7 @@
</div>
<div class="btns">
<el-button plain type="primary" @click="handleSearch">查询</el-button>
<el-button plain @click="getData">重置表格</el-button>
<el-button plain @click="resetForm">重置</el-button>
</div>
</div>
</table-search>
......@@ -40,6 +40,8 @@
<div class="table-content">
<el-table
:data="tableData"
v-loading="tableLoading"
element-loading-background="rgba(48, 65, 86, 0.3)"
style="width: 100%"
border
:row-style="{ height: '45.5px' }"
......@@ -80,7 +82,7 @@
<div class="detailForm">
<el-dialog v-model="detailVisibleValue" center width="765px" align-center @close="handleClose" draggable>
<template #header>
<div class="text-center font-size-6">详情-{{ targetName }}</div>
<div class="text-center font-size-6">详情{{ targetName }}</div>
</template>
<el-row>
<el-col>
......@@ -295,14 +297,22 @@ ${res.data.data[0].TLE_LINE1}
${res.data.data[0].TLE_LINE2}`;
detailVisibleValue.value = true;
};
//表单loading标记
const tableLoading = ref(false);
//重置搜索条件
const resetForm = () => {
noradCatId.value = "";
searchTargetName.value = "";
timeValue.value = "";
getData();
};
// 获取st数据列表的方法
const getData = async () => {
tableLoading.value = true;
const res = await getStList({ page: pageObj.value.pageNo, size: pageObj.value.pageSize });
pageObj.value.total = res.data.total;
tableData.value = res.data.list;
noradCatId.value = "";
searchTargetName.value = "";
timeValue.value = "";
tableLoading.value = false;
};
const handleClose = () => {
detailVisibleValue.value = false;
......
......@@ -32,7 +32,6 @@ import stDataTab from "./components/stDataTab.vue";
import dsnDataTab from "./components/dsnData/dsnTab.vue";
import esDataTab from "./components/esDataTab.vue";
import exportDialog from "@/components/Export/index.vue";
import type { UploadInstance } from "element-plus";
import MenuTitle from "@/components/MenuTitle.vue";
const mode = ref(sessionStorage.getItem("dataDisplayMode") || "DSN数据");
......
......@@ -10,17 +10,17 @@
:cell-style="{ textAlign: 'center' }"
:row-style="{ height: '60px' }"
v-loading="tableData.length == 0"
element-loading-background="rgba(48, 65, 86, 0.7)"
element-loading-background="rgba(48, 65, 86, 0.3)"
>
<el-table-column property="number" label="序号" type="index" width="80" />
<el-table-column property="spider" label="爬虫代号" show-overflow-tooltip />
<el-table-column property="spider_name" label="爬虫名称" show-overflow-tooltip />
<el-table-column property="spider_info" label="任务描述" show-overflow-tooltip />
<el-table-column min-width="200" property="spider_info" label="任务描述" show-overflow-tooltip />
<el-table-column label="操作">
<template #default="scope">
<div class="btn-group">
<el-button type="primary" plain @click="handleDetails(scope.row)">查看任务</el-button>
<el-button type="primary" plain @click="handleEditSateId" v-if="scope.row.editable">编辑</el-button>
<el-button link type="primary" plain @click="handleDetails(scope.row)">查看任务</el-button>
<el-button link type="primary" plain @click="handleEditSateId" v-if="scope.row.editable">编辑</el-button>
</div>
</template>
</el-table-column>
......@@ -35,7 +35,7 @@
</div>
</div>
<el-dialog style="z-index: 999999" draggable v-model="editDialogVisible" title="指定下载以下卫星" width="400">
<el-dialog style="z-index: 999999" draggable v-model="editDialogVisible" title="指定下载以下卫星" width="550">
<div class="No-Content" ref="noContentRef">
<TransitionGroup
enter-active-class="animate__animated animate__slideInRight"
......@@ -65,7 +65,7 @@
style="width: 100px"
></el-input>
</div>
<div class="btn">
<div class="btns">
<el-button :disabled="curSateNo == ''" plain type="primary" @click="addNoToList">添加</el-button>
<el-popconfirm title="确定删除选中编号吗?" width="200" style="background-color: pink">
<template #reference>
......@@ -87,6 +87,16 @@
</el-button>
</template>
</el-popconfirm>
<el-upload
:on-change="handleFileChange"
accept=".txt"
action="#"
:auto-upload="false"
:show-file-list="false"
:before-upload="beforeUpload"
>
<el-button type="primary">导入编号文件</el-button>
</el-upload>
</div>
</div>
</el-dialog>
......@@ -94,12 +104,12 @@
</template>
<script setup lang="ts">
import { ref, onMounted, nextTick } from "vue";
import { ref, onMounted, nextTick, watch } from "vue";
import Pagination from "@/components/pagination/index.vue";
import { useRouter } from "vue-router";
import { getSpiderList, addSateNo, getSateIdList } from "@/api/system.ts";
import NoItem from "./components/NoItem.vue";
import { ElMessage } from "element-plus";
import { ElMessage, type UploadProps, type UploadRawFile } from "element-plus";
import MenuTitle from "@/components/MenuTitle.vue";
const router = useRouter();
......@@ -123,7 +133,6 @@ const handleDetails = (row: any) => {
const getData = async () => {
const res = await getSpiderList({ scrapydServerId: "1", project: "spiders" });
tableData.value = res.data;
console.log(res.data);
};
const editDialogVisible = ref(false);
......@@ -148,7 +157,7 @@ const handleSateDel = (no: string | number) => {
//添加编号到列表并整体发到后端
const addNoToList = async () => {
try {
if (sateNoList.value.includes(padZeroTo5Digits(curSateNo.value))) {
if (curSateNo.value && sateNoList.value.includes(padZeroTo5Digits(curSateNo.value))) {
ElMessage.error("当前编号已存在,请重新输入");
curSateNo.value = "";
return;
......@@ -168,6 +177,22 @@ const addNoToList = async () => {
}
};
//编号文件导入后发送到后端
const fileImptAddNoList = async () => {
try {
const res = await addSateNo({ id: sateNoList.value });
if (res.code === 0) {
ElMessage.success("编号导入成功");
} else {
sateNoList.value.pop();
ElMessage.error(res.message);
}
scrollToBottom();
} catch (error: any) {
ElMessage.error(error.message);
}
};
//删除选中编号
const delNoList = async () => {
try {
......@@ -217,18 +242,112 @@ const handleEditSateId = async () => {
if (res.code === 0) {
sateNoList.value = res.data || [];
}
console.log(res);
} catch (error: any) {
ElMessage.error(error.message);
}
};
//读取文本文件后存储读取的编号
const textFileNumbers = ref<number[]>([]);
//监听导入文件的数组变化
watch(textFileNumbers, (newVal) => {
if (newVal.length > 0) {
newVal.map((num) => {
if (!sateNoList.value.includes(padZeroTo5Digits(num))) {
sateNoList.value.push(padZeroTo5Digits(num));
}
});
fileImptAddNoList();
}
});
//判断上传文件类型
const beforeUpload: UploadProps["beforeUpload"] = (rawFile: UploadRawFile) => {
// 检查文件是否存在
if (!rawFile) {
ElMessage.error("请选择文件");
return Promise.reject();
}
// 检查文件类型
const isTxtType = rawFile.type === "text/plain";
// 检查文件后缀名
const fileName = rawFile.name;
const isTxtExt = fileName.lastIndexOf(".") > 0 && fileName.slice(fileName.lastIndexOf(".")).toLowerCase() === ".txt";
// 如果两种检查都不通过
if (!isTxtType && !isTxtExt) {
ElMessage.error("只能上传 .txt 格式的文件!");
//阻止上传
return Promise.reject();
}
// 校验通过
return true;
};
//处理文件上传
const handleFileChange: UploadProps["onChange"] = (uploadFile) => {
//清空之前的值
textFileNumbers.value = [];
//获取原生file对象
const file = uploadFile.raw;
if (!file || !(file instanceof File)) {
ElMessage.error("未获取到有效文件");
return;
}
//验证文件类型
if (file.type !== "text/plain" && !file.name.endsWith(".txt")) {
ElMessage.error("请上传.txt文件");
}
//使用FileReader读取文件内容
const reader = new FileReader();
//成功的回调
reader.onload = (event: ProgressEvent<FileReader>) => {
const textContent = event.target?.result as string;
if (!textContent) {
throw new Error("文件内容为空");
}
textFileNumbers.value = parseTextToNumbers(textContent);
};
//失败的回调
reader.onerror = () => {
ElMessage.error("文件导入失败,请检查文件是否损坏或重试");
};
//读取文件
reader.readAsText(file, "utf-8");
};
//解析文件内容
const parseTextToNumbers = (text: string): number[] => {
if (!text) return [];
//换行符分割
const lines = text.split(/\r?\n/);
//遍历处理
const numbers = lines
.map((line) => {
//去除首尾空格
const trimmedLine = line.trim();
if (trimmedLine === "") return null;
const number = Number(trimmedLine);
if (isNaN(number)) {
return null;
}
return number;
})
.filter((num): num is number => num !== null);
return numbers;
};
onMounted(() => {
getData();
});
</script>
<style scoped>
:deep(.el-input__inner) {
color: black;
}
/* 去除按钮边框 */
.el-button:focus {
outline: none;
......@@ -252,6 +371,11 @@ onMounted(() => {
color: white;
}
.btns {
display: flex;
gap: 10px;
}
.low-titme {
color: #ccc;
text-align: left;
......
......@@ -142,7 +142,7 @@ onMounted(() => {
background-repeat: no-repeat;
// background: #c6ebfc;
// border: 1.5px solid rgb(193, 188, 188);
width: 500px;
width: 350px;
border-radius: 5px;
}
......
<template>
<div class="dataCard">
<div class="titleStyle">错误日志统计</div>
<div class="chart-container" ref="chartRef"></div>
<el-dialog draggable v-model="DialogVisible" :title="`日志详情丨${curDate}`" @close="tableData = []">
<div class="table-content">
<el-table :data="curTableData" border style="width: 100%">
<el-table-column width="55" label="序号" type="index" />
<el-table-column width="150" property="spider" label="爬虫名称" />
<el-table-column property="error" label="错误信息" />
<el-table-column width="160" property="timestamp" label="发生时间" />
</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"
v-model:pagerCount="pageObj.pagerCount"
/>
</div>
</div>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { computed, reactive, ref, useTemplateRef } from "vue";
import { getErrorLogsList } from "@/api/log";
import { useChart } from "@/hooks/useChart";
import { ElMessage } from "element-plus";
import Pagination from "@/components/pagination/index.vue";
const chartRef = useTemplateRef<HTMLElement | null>("chartRef");
//详情弹窗状态
const DialogVisible = ref(false);
const pageObj = ref({
total: 0,
pageNo: 1,
pageSize: 5,
pagerCount: 5,
});
//弹窗表格数据
const tableData = ref<any[]>([]);
const curTableData = ref<any[]>([]);
//数据项定义
interface LogItem {
date: string;
count: number;
}
//原始数据
const rawLogList = ref<any[]>([]);
//日志列表
const logList = ref<LogItem[]>([]);
//日志数
const logCountList = computed(() => {
return logList.value.map((item) => item.count);
});
//x轴日期列表
const dateList = ref<string[]>([]);
//Y轴最大值
const maxVal = computed(() => {
let val = logList.value[0]?.count * 1.5;
logList.value.forEach((item) => {
if (item.count > val) {
val = item.count * 1.5;
}
});
return val;
});
//当前打开的详情日期
const curDate = ref("");
const getData = () => {
curTableData.value = tableData.value.slice(
(pageObj.value.pageNo - 1) * pageObj.value.pageSize,
pageObj.value.pageNo * pageObj.value.pageSize
);
};
//设置图表数据
const setChartData = async () => {
try {
const res = await getErrorLogsList();
if (res.code === 0) {
rawLogList.value = res.data.list;
//提取日期列表
res.data.list.forEach((item: any) => {
const date = item.timestamp.split(" ")[0];
if (dateList.value.includes(date)) {
//如果该日期日志项存在,则进行+1操作
logList.value.forEach((item) => {
if (item.date === date) {
item.count++;
}
});
} else {
//如果该日期日志项不存在,则初始化到数组中
logList.value.push({ date, count: 1 });
dateList.value.push(date);
}
});
} else {
ElMessage.error(res.message);
}
} catch (error: any) {
ElMessage(error);
}
//初始化图表配置项
const chartOptions = reactive({
tooltip: {
trigger: "axis",
axisPointer: {
type: "shadow",
},
},
grid: {
left: "3%",
right: "4%",
bottom: "3%",
containLabel: true,
},
xAxis: [
{
type: "category",
data: dateList.value,
name: "日期",
nameLocation: "middle",
nameTextStyle: {
color: "#fff",
fontSize: 12,
},
axisTick: {
alignWithLabel: true,
},
axisLabel: {
color: "#fff",
},
},
],
yAxis: [
{
type: "value",
name: "数量",
nameLocation: "middle",
max: maxVal.value,
axisLabel: {
color: "#fff",
},
nameTextStyle: {
color: "#fff",
fontSize: 12,
},
},
],
series: [
{
name: "日志数量",
type: "bar",
barWidth: "60%",
data: logCountList.value,
itemStyle: {
color: "#409EE4",
},
},
],
});
return chartOptions;
};
//处理用户点击柱状图事件
const handleClickChart = (params: any) => {
DialogVisible.value = true;
curDate.value = params.name;
rawLogList.value.forEach((item) => {
if (item.timestamp.split(" ")[0] === params.name) {
tableData.value.push(item);
}
});
pageObj.value.total = tableData.value.length;
curTableData.value = tableData.value.slice(0, 5);
};
//生成图表
useChart(chartRef, setChartData, handleClickChart);
</script>
<style scoped lang="scss">
.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);
border-radius: 5px;
height: 100%;
padding: 35px;
box-sizing: border-box;
}
/* 标题区 */
.titleStyle {
color: #dff6ff;
font-size: 18px;
font-weight: 600;
text-align: center;
text-shadow: 0 0 8px rgba(100, 200, 255, 0.8);
letter-spacing: 2px;
}
.chart-container {
margin-top: 20px;
width: 100%;
height: 600px;
}
.cotnent {
padding: 10px;
display: flex;
}
.table-content {
padding: 10px;
margin-top: 30px;
background: rgba(255, 255, 255, 0.03);
backdrop-filter: blur(8px);
border: 1px solid rgba(78, 237, 255, 0.2);
box-shadow: 0 0 15px rgba(78, 237, 255, 0.1);
border-radius: 8px;
padding: 15px;
}
</style>
......@@ -138,7 +138,7 @@ const goToESADataPage = () => {
background-repeat: no-repeat;
// background: #c6ebfc;
// border: 1.5px solid rgb(193, 188, 188);
width: 375px;
width: 263px;
height: 100%;
border-radius: 5px;
display: flex;
......
<template>
<div>
<div class="monitor-container">
<div class="card-container">
<div class="monitoringCard">
<div class="data-card-wrapper">
......@@ -17,6 +17,9 @@
</div>
</div>
</div>
<div class="monitor-chart">
<errorMessageChart />
</div>
</div>
</template>
......@@ -24,6 +27,7 @@
import dataCard from "./dataCard.vue";
import taskCard from "@/views/os-status/components/taskCard.vue";
import qbCard from "@/views/os-status/components/qbDataMonitor.vue";
import errorMessageChart from "./errorMessageChart.vue";
import { onMounted } from "vue";
defineProps({
......@@ -41,6 +45,15 @@ onMounted(() => {});
</script>
<style lang="scss" scoped>
.monitor-container {
display: flex;
}
.monitor-chart {
color: white;
width: 30%;
}
.card-container {
height: 100%;
display: flex;
......@@ -49,6 +62,7 @@ onMounted(() => {});
align-items: center;
padding: 10px;
gap: 30px;
width: 70%;
}
.monitoringCard {
......
......@@ -8,17 +8,19 @@
<div class="items">
<div class="wordStyle">
<span>DSN爬虫任务数:</span>
<span class="total-num" v-if="dsnTotalTaskNumber != null">{{ dsnTotalTaskNumber }}</span>
<span class="total-num" v-if="dsnTotalTaskNumber != null">{{ formatExactLargeNum(dsnTotalTaskNumber) }}</span>
<span class="loading" v-else></span>
</div>
<div class="wordStyle">
<span>任务执行成功统计:</span>
<span class="total-num" v-if="dsnTaskSuccessNumber != null">{{ dsnTaskSuccessNumber }}</span>
<span class="total-num" v-if="dsnTaskSuccessNumber != null">{{
formatExactLargeNum(dsnTaskSuccessNumber)
}}</span>
<span class="loading" v-else></span>
</div>
<div class="wordStyle">
<span>任务执行失败统计:</span>
<span class="total-num" v-if="dsnTaskFailNumber != null">{{ dsnTaskFailNumber }}</span>
<span class="total-num" v-if="dsnTaskFailNumber != null">{{ formatExactLargeNum(dsnTaskFailNumber) }}</span>
<span class="loading" v-else></span>
</div>
<div class="wordStyle">
......@@ -35,22 +37,24 @@
<div class="items">
<div class="wordStyle">
<span>ITU爬虫任务数:</span>
<span class="total-num" v-if="ituTotalTaskNumber != null">{{ ituTotalTaskNumber }}</span>
<span class="total-num" v-if="ituTotalTaskNumber != null">{{ formatExactLargeNum(ituTotalTaskNumber) }}</span>
<span class="loading" v-else></span>
</div>
<div class="wordStyle">
<span>任务执行成功统计:</span>
<span class="total-num" v-if="ituTaskSuccessNumber != null">{{ ituTaskSuccessNumber }}</span>
<span class="total-num" v-if="ituTaskSuccessNumber != null">{{
formatExactLargeNum(ituTaskSuccessNumber)
}}</span>
<span class="loading" v-else></span>
</div>
<div class="wordStyle">
<span>任务执行失败统计:</span>
<span class="total-num" v-if="ituTaskFailNumber != null">{{ ituTaskFailNumber }}</span>
<span class="total-num" v-if="ituTaskFailNumber != null">{{ formatExactLargeNum(ituTaskFailNumber) }}</span>
<span class="loading" v-else></span>
</div>
<div class="wordStyle">
<span>任务执行失败统计:</span>
<span class="total-num" v-if="ituTaskFailNumber != null">{{ ituTaskFailNumber }}</span>
<span class="total-num" v-if="ituTaskFailNumber != null">{{ formatExactLargeNum(ituTaskFailNumber) }}</span>
<span class="loading" v-else></span>
</div>
<div class="wordStyle">
......@@ -67,17 +71,19 @@
<div class="items">
<div class="wordStyle">
<span>ST爬虫任务数:</span>
<span class="total-num" v-if="stTotalTaskNumber != null">{{ stTotalTaskNumber }}</span>
<span class="total-num" v-if="stTotalTaskNumber != null">{{ formatExactLargeNum(stTotalTaskNumber) }}</span>
<span class="loading" v-else></span>
</div>
<div class="wordStyle">
<span>任务执行成功统计:</span>
<span class="total-num" v-if="stTaskSuccessNumber != null">{{ stTaskSuccessNumber }}</span>
<span class="total-num" v-if="stTaskSuccessNumber != null">{{
formatExactLargeNum(stTaskSuccessNumber)
}}</span>
<span class="loading" v-else></span>
</div>
<div class="wordStyle">
<span>采集速度:</span>
<span class="total-num" v-if="stTaskFailNumber != null">{{ stTaskFailNumber }}</span>
<span class="total-num" v-if="stTaskFailNumber != null">{{ formatExactLargeNum(stTaskFailNumber) }}</span>
<span class="loading" v-else></span>
</div>
<div class="wordStyle">
......@@ -97,6 +103,7 @@ import { onMounted, ref } from "vue";
import { getSpiderTaskList } from "@/api/spiderTask";
import { useSlideStateStore } from "@/store/slideState";
import { storeToRefs } from "pinia";
import formatExactLargeNum from "@/utils/formatExactLargeNum";
const slideStateStore = useSlideStateStore();
const { slideState } = storeToRefs(slideStateStore);
......@@ -178,7 +185,7 @@ onMounted(() => {
background-repeat: no-repeat;
// background: #c6ebfc;
// border: 1.5px solid rgb(193, 188, 188);
width: 500px;
width: 350px;
height: 100%;
border-radius: 5px;
cursor: pointer;
......
......@@ -6,7 +6,7 @@
<el-button test-element="userSystem-AddUser" type="primary" plain @click="openAddUserDialog"
>创建用户</el-button
>
<el-button type="danger" plain @click="handleBatchDelete">批量删除</el-button>
<el-button type="danger" plain @click="handleBatchDelete" class="">批量删除</el-button>
</div>
</el-card>
<div class="table-content">
......@@ -18,7 +18,7 @@
:row-style="{ height: '58px' }"
@selection-change="handleSelectionChange"
v-loading="tableData?.length == 0"
element-loading-background="rgba(48, 65, 86, 0.7)"
element-loading-background="rgba(48, 65, 86, 0.3)"
>
<el-table-column type="selection" width="40" />
<el-table-column property="number" label="序号" width="55" type="index" />
......@@ -37,8 +37,8 @@
<el-table-column property="create_time" label="创建时间" width="280" show-overflow-tooltip />
<el-table-column label="操作" width="220">
<template #default="scope">
<el-button type="primary" plain @click="handleEdit(scope.row)" id="editUser"> 编辑 </el-button>
<el-button type="danger" plain @click="handleDelete(scope.row)"> 删除 </el-button>
<el-button link type="primary" plain @click="handleEdit(scope.row)" id="editUser"> 编辑 </el-button>
<el-button link type="danger" plain @click="handleDelete(scope.row)"> 删除 </el-button>
</template>
</el-table-column>
</el-table>
......
......@@ -12,8 +12,8 @@
<el-form-item label="任务名称:" v-if="currentMode === AddMode.ADD_TASK" prop="taskName">
<el-input v-model="ruleForm.taskName" placeholder="请输入任务名称" style="width: 90%" />
</el-form-item>
<el-form-item label="所属爬虫:" v-if="currentMode === AddMode.ADD_TASK" prop="spiderTypeValue">
<el-select v-model="ruleForm.spiderTypeValue" placeholder="请选择所属爬虫" style="width: 90%">
<el-form-item label="选择爬虫:" v-if="currentMode === AddMode.ADD_TASK" prop="spiderTypeValue">
<el-select v-model="ruleForm.spiderTypeValue" placeholder="请选择爬虫" style="width: 90%">
<el-option
v-for="item in spiderTypeOptions"
:key="item.spider"
......
......@@ -4,7 +4,7 @@
<TableSearch>
<div class="form-content">
<div class="ipt">
<span style="color: white">所属爬虫:</span>
<span style="color: white">选择爬虫:</span>
<el-select
v-model="searchCondition.spiders"
placeholder="请选择"
......@@ -26,15 +26,9 @@
</div>
</div>
</TableSearch>
<div class="cardStyle">
<div class="cardStyle" v-loading="isLoading" element-loading-background="rgba(48, 65, 86, 0.3)">
<div v-if="taskList.length === 0 && !isLoading" class="empty-tip">暂无任务数据</div>
<div
v-loading="taskList.length == 0"
element-loading-background="rgba(48, 65, 86, 0.7)"
class="taskCard p-2"
v-for="task in taskList || []"
:key="task?.id || task?.taskId"
>
<div class="taskCard p-2" v-for="task in taskList || []" :key="task?.id || task?.taskId">
<div class="header">
<span class="task-name"
>任务名称: {{ (task?.kwargs?.options && JSON.parse(task.kwargs.options).jobName) || "无名称" }}</span
......@@ -49,6 +43,7 @@
>删除</el-button
>
</div>
<div class="info">
<div class="item">
<span class="wordStyle">启用/停止: </span>
......@@ -61,8 +56,16 @@
/>
</div>
<div class="item">
<span class="wordStyle">爬虫名称: </span>
<span class="wordStyle">{{ task.kwargs.spider ?? "-" }} </span>
</div>
<div class="item">
<span class="wordStyle">执行频率: </span>
<span class="wordStyle">{{ parseCronExpression(task?.kwargs?.cron) || "未设置" }} </span>
<span class="wordStyle"
>{{
`${task.kwargs.interval[3]} 天 ${task.kwargs.interval[2]} 小时 ${task.kwargs.interval[1]} 分钟 ${task.kwargs.interval[0]} 秒`
}}
</span>
</div>
<div class="item">
<span class="wordStyle">执行次数: </span>
......@@ -87,17 +90,18 @@
</template>
<script lang="ts" setup>
import { ref, onMounted } from "vue";
import { ref, onMounted, onUnmounted } from "vue";
import { useRouter } from "vue-router";
import addTaskDialog from "./addTaskDialog.vue";
import { getSpiderTaskList, resumeSpiderTask, pauseSpiderTask, getTaskCount } from "@/api/spiderTask";
import { DeleteMode } from "@/components/Delete/enum.ts";
import { AddMode } from "./enum";
import deleteDialog from "./deleteDialog.vue";
import { ElMessage } from "element-plus";
import { ElMessage, ElNotification } from "element-plus";
import { getSpiderList } from "@/api/system.ts";
import formatExactLargeNum from "@/utils/formatExactLargeNum";
import TableSearch from "@/components/TableSearch.vue";
import { Manager, Socket } from "socket.io-client";
const props = defineProps({
spiderType: {
......@@ -114,6 +118,26 @@ const props = defineProps({
},
});
const socket = ref<Socket | null>(null);
//错误消息通知
const handleErrorMsgNotify = () => {
//websocket连接
const manager = new Manager("/api:5001");
socket.value = manager.socket("/");
//收到错误通知
socket.value.on("error_message", onErrorMsg);
};
//错误消息处理
const onErrorMsg = ({ message }: { message: any }) => {
ElNotification({
title: "任务错误提示",
message,
type: "error",
});
};
// 所有响应式变量初始化时避免 undefined
const taskSelectOptions = ref<any[]>([]); // 初始化为空数组
const searchCondition = ref({
......@@ -269,44 +293,6 @@ const search = async () => {
}
};
// 解析cron表达式的方法(修复逻辑错误,避免数组越界)
const parseCronExpression = (cronExpression?: string) => {
// 兜底:cron表达式不存在时返回默认值
if (!cronExpression) return "未设置";
const parts = cronExpression.trim().split(" ");
// 只处理标准 5/6 位 cron 表达式
if (parts.length < 5 || parts.length > 6) return "表达式格式错误";
// 遍历每个字段,找到非 * 的字段(修复数组越界问题)
for (let i = 0; i < parts.length; i++) {
const part = parts[i].trim();
if (part !== "*" && part !== "") {
// 转换为数字,避免非数字字符导致的错误
const num = parseInt(part, 10);
const validNum = isNaN(num) ? 1 : num;
switch (i) {
case 0:
return `每${validNum}秒执行一次`;
case 1:
return `每${validNum}分钟执行一次`;
case 2:
return `每${validNum}小时执行一次`;
case 3:
return `每${validNum}天执行一次`;
case 4:
return `每${validNum}月执行一次`;
case 5:
return `每${validNum}周执行一次`;
default:
return "未知频率";
}
}
}
// 全是 * 时返回默认
return "每秒执行一次";
};
// 获取爬虫类型列表
const getSpiderTypeList = async () => {
try {
......@@ -325,10 +311,22 @@ onMounted(() => {
}
getData();
getSpiderTypeList();
//开启socket连接
handleErrorMsgNotify();
});
onUnmounted(() => {
//关闭socket连接
if (socket.value) {
socket.value.disconnect();
}
});
</script>
<style lang="scss" scoped>
:deep(.el-select__selected-item) {
color: white !important;
}
:deep(.el-form-item__label) {
color: white;
}
......@@ -391,7 +389,7 @@ onMounted(() => {
.taskCard .wordStyle {
font-size: 16px;
color: #ffffff;
color: #ffffff !important;
}
/* 按钮样式微调 */
......
......@@ -6,31 +6,32 @@
<table-search>
<div class="form-content">
<div class="timer">
<span style="color: white">时间</span>
<span style="color: white">采集日期</span>
<el-config-provider :locale="zhCn">
<el-date-picker
v-model="timeValue"
type="datetimerange"
start-placeholder="开始时间"
end-placeholder="结束时间"
format="YYYY-MM-DD HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
format="YYYY-MM-DD"
date-format="YYYY/MM/DD ddd"
time-format="A hh:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss"
value-format="YYYY-MM-DD"
style="width: 250px"
/>
</el-config-provider>
</div>
<div class="btns">
<el-button plain type="primary" @click="searchData">查询</el-button>
<el-button plain @click="getData">重置</el-button>
<el-button plain @click="resetQuery">重置</el-button>
</div>
</div>
</table-search>
<div class="table-content">
<div>
<el-table
v-loading="tableData.length == 0"
element-loading-background="rgba(48, 65, 86, 0.7)"
v-loading="tableLoading"
element-loading-background="rgba(48, 65, 86, 0.3)"
:data="tableData"
style="width: 100%"
border
......@@ -56,7 +57,12 @@
{{ scope.row.run_status === "unknown" ? "已结束" : scope.row.run_status }}
</template>
</el-table-column>
<el-table-column property="create_time" label="调度时间" width="200" show-overflow-tooltip />
<el-table-column label="运行耗时/ms">
<template #default="scope">
{{ scope.row.duration == 0 ? "-" : scope.row.duration }}
</template>
</el-table-column>
<el-table-column property="create_time" label="采集时间" width="200" show-overflow-tooltip />
</el-table>
</div>
<div class="pagination w-full flex flex-row-reverse pr-4 m-t-4">
......@@ -81,6 +87,8 @@ import { getSpiderTaskRecord } from "@/api/spiderTask.ts";
import { ElMessage } from "element-plus";
import MenuTitle from "@/components/MenuTitle.vue";
import TableSearch from "@/components/TableSearch.vue";
//时间处理库
import dayjs from "dayjs";
// ElConfigProvider 组件
import { ElConfigProvider } from "element-plus";
// 引入中文包
......@@ -93,21 +101,8 @@ zhCn.el.pagination.pagesize = "条/页";
zhCn.el.pagination.pageClassifier = "页";
const route = useRoute();
const router = useRouter();
const timeValue = ref("");
const taskOptions = [
{
value: "task1",
label: "sk网",
},
{
value: "task2",
label: "nasa网",
},
{
value: "task3",
label: "网",
},
];
//初始数据为近七天
const timeValue = ref([dayjs().subtract(7, "day").format("YYYY-MM-DD"), dayjs().format("YYYY-MM-DD")]);
const tableData = ref([]);
const pageObj = ref({
total: 10,
......@@ -115,16 +110,7 @@ const pageObj = ref({
pageNo: 1,
pagerCount: 5,
});
// 前往运行日志,暂时不要
// const handleDetails = (row: any) => {
// console.log(row);
// router.push({
// path: '/os-log/list',
// query: {
// id: row.id
// }
// })
// }
const goToStatus = () => {
if (route.query.mode === "状态监控") {
router.push({
......@@ -136,23 +122,34 @@ const goToStatus = () => {
});
}
};
//表单loading标记
const tableLoading = ref(false);
//重置查询
const resetQuery = () => {
timeValue.value = [dayjs().subtract(7, "day").format("YYYY-MM-DD"), dayjs().format("YYYY-MM-DD")];
getData();
};
// 获取任务执行记录列表
const getData = async () => {
const res = await getSpiderTaskRecord({ page: pageObj.value.pageNo, size: pageObj.value.pageSize, status: "total" });
pageObj.value.total = res.data.total;
tableData.value = res.data.list;
searchData();
};
// 数据的方法
//查询数据的方法
const searchData = async () => {
if (!timeValue.value) {
ElMessage.warning("请先选择时间段");
return;
}
let resTime = [];
let resTime: any[] = [];
const startTime = timeValue.value[0];
const endTime = timeValue.value[1];
resTime.push(startTime);
resTime.push(endTime);
resTime.push(startTime + " 00:00:00");
resTime.push(endTime + " 00:00:00");
tableLoading.value = true;
const res = await getSpiderTaskRecord({
page: pageObj.value.pageNo,
size: pageObj.value.pageSize,
......@@ -161,6 +158,9 @@ const searchData = async () => {
});
pageObj.value.total = res.data.total;
tableData.value = res.data.list;
console.log(tableData.value);
tableLoading.value = false;
};
onMounted(() => {
......
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