Commit 1d23619c by 周田

Merge branch 'liucan' into 'main'

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

See merge request !14
parents 865f028d 2f48a31a
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
"animate.css": "^4.1.1", "animate.css": "^4.1.1",
"axios": "^1.9.0", "axios": "^1.9.0",
"cron-parser": "^5.4.0", "cron-parser": "^5.4.0",
"dayjs": "^1.11.19",
"echarts": "^5.6.0", "echarts": "^5.6.0",
"element-plus": "^2.9.10", "element-plus": "^2.9.10",
"js-cookie": "^3.0.5", "js-cookie": "^3.0.5",
...@@ -24,6 +25,7 @@ ...@@ -24,6 +25,7 @@
"path-to-regexp": "^3.3.0", "path-to-regexp": "^3.3.0",
"pinia": "^3.0.2", "pinia": "^3.0.2",
"qs": "~6.11.2", "qs": "~6.11.2",
"socket.io-client": "^4.8.1",
"uuid": "^11.1.0", "uuid": "^11.1.0",
"vue": "^3.5.13", "vue": "^3.5.13",
"vue-router": "^4.5.1" "vue-router": "^4.5.1"
...@@ -1415,6 +1417,12 @@ ...@@ -1415,6 +1417,12 @@
"win32" "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": { "node_modules/@types/estree": {
"version": "1.0.8", "version": "1.0.8",
"resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.8.tgz", "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.8.tgz",
...@@ -2666,6 +2674,45 @@ ...@@ -2666,6 +2674,45 @@
"vue": "^3.2.0" "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": { "node_modules/entities": {
"version": "4.5.0", "version": "4.5.0",
"resolved": "https://registry.npmmirror.com/entities/-/entities-4.5.0.tgz", "resolved": "https://registry.npmmirror.com/entities/-/entities-4.5.0.tgz",
...@@ -3303,7 +3350,6 @@ ...@@ -3303,7 +3350,6 @@
"version": "2.1.3", "version": "2.1.3",
"resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/muggle-string": { "node_modules/muggle-string": {
...@@ -4080,6 +4126,68 @@ ...@@ -4080,6 +4126,68 @@
"node": ">=18" "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": { "node_modules/source-map-js": {
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz", "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz",
...@@ -4569,6 +4677,35 @@ ...@@ -4569,6 +4677,35 @@
"typescript": ">=5.0.0" "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": { "node_modules/zrender": {
"version": "5.6.1", "version": "5.6.1",
"resolved": "https://registry.npmmirror.com/zrender/-/zrender-5.6.1.tgz", "resolved": "https://registry.npmmirror.com/zrender/-/zrender-5.6.1.tgz",
......
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
"animate.css": "^4.1.1", "animate.css": "^4.1.1",
"axios": "^1.9.0", "axios": "^1.9.0",
"cron-parser": "^5.4.0", "cron-parser": "^5.4.0",
"dayjs": "^1.11.19",
"echarts": "^5.6.0", "echarts": "^5.6.0",
"element-plus": "^2.9.10", "element-plus": "^2.9.10",
"js-cookie": "^3.0.5", "js-cookie": "^3.0.5",
...@@ -25,6 +26,7 @@ ...@@ -25,6 +26,7 @@
"path-to-regexp": "^3.3.0", "path-to-regexp": "^3.3.0",
"pinia": "^3.0.2", "pinia": "^3.0.2",
"qs": "~6.11.2", "qs": "~6.11.2",
"socket.io-client": "^4.8.1",
"uuid": "^11.1.0", "uuid": "^11.1.0",
"vue": "^3.5.13", "vue": "^3.5.13",
"vue-router": "^4.5.1" "vue-router": "^4.5.1"
......
...@@ -27,6 +27,7 @@ export const scrapydApi = { ...@@ -27,6 +27,7 @@ export const scrapydApi = {
projectLogs: "/scrapyd/projectLogs", projectLogs: "/scrapyd/projectLogs",
spiderLogs: "/scrapyd/spiderLogs", spiderLogs: "/scrapyd/spiderLogs",
jobLog: "/scrapyd/jobLog", jobLog: "/scrapyd/jobLog",
errorLogs: "/errorLogs/list",
} as const; } as const;
export const scheduleApi = { export const scheduleApi = {
......
import { request, POST } from '@/utils/request' import { request, POST } from "@/utils/request";
import type { ApiResponse, QueryParams } from '@/utils/request' import type { ApiResponse, QueryParams } from "@/utils/request";
import { scrapydApi } from './apiPaths' import { scrapydApi } from "./apiPaths";
// 获取日志列表 // 获取日志列表
export function getLogs(data: QueryParams): Promise<ApiResponse> { export function getLogs(data: QueryParams): Promise<ApiResponse> {
return request({ return request({
url: scrapydApi.logs, url: scrapydApi.logs,
method: POST, method: POST,
data data,
}) as unknown as Promise<ApiResponse> }) as unknown as Promise<ApiResponse>;
} }
// 获取项目日志 // 获取项目日志
...@@ -16,8 +16,8 @@ export function getProjectLogs(data: QueryParams): Promise<ApiResponse> { ...@@ -16,8 +16,8 @@ export function getProjectLogs(data: QueryParams): Promise<ApiResponse> {
return request({ return request({
url: scrapydApi.projectLogs, url: scrapydApi.projectLogs,
method: POST, method: POST,
data data,
}) as unknown as Promise<ApiResponse> }) as unknown as Promise<ApiResponse>;
} }
// 获取爬虫日志 // 获取爬虫日志
...@@ -25,8 +25,8 @@ export function getSpiderLogs(data: QueryParams): Promise<ApiResponse> { ...@@ -25,8 +25,8 @@ export function getSpiderLogs(data: QueryParams): Promise<ApiResponse> {
return request({ return request({
url: scrapydApi.spiderLogs, url: scrapydApi.spiderLogs,
method: POST, method: POST,
data data,
}) as unknown as Promise<ApiResponse> }) as unknown as Promise<ApiResponse>;
} }
// 获取任务日志 // 获取任务日志
...@@ -34,6 +34,15 @@ export function getJobLog(data: QueryParams): Promise<ApiResponse> { ...@@ -34,6 +34,15 @@ export function getJobLog(data: QueryParams): Promise<ApiResponse> {
return request({ return request({
url: scrapydApi.jobLog, url: scrapydApi.jobLog,
method: POST, method: POST,
data data,
}) as unknown as Promise<ApiResponse> }) 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 { request, POST } from "@/utils/request";
import type { ApiResponse, QueryParams } from '@/utils/request' import type { ApiResponse, QueryParams } from "@/utils/request";
import { dataApi } from './apiPaths' import { dataApi } from "./apiPaths";
// 获取爬虫静态数据列表 // 获取爬虫静态数据列表
export function getStatsDataList(params: QueryParams): Promise<ApiResponse> { export function getStatsDataList(params: QueryParams): Promise<ApiResponse> {
return request({ return request({
url: dataApi.dataStatistics, url: dataApi.dataStatistics,
method: POST, method: POST,
params params,
}) as unknown as Promise<ApiResponse> }) as unknown as Promise<ApiResponse>;
} }
// 获取爬虫任务列表 // 获取爬虫任务列表
...@@ -16,8 +16,8 @@ export function getSpiderTaskList(params: QueryParams): Promise<ApiResponse> { ...@@ -16,8 +16,8 @@ export function getSpiderTaskList(params: QueryParams): Promise<ApiResponse> {
return request({ return request({
url: dataApi.taskStatistics, url: dataApi.taskStatistics,
method: POST, method: POST,
params params,
}) as unknown as Promise<ApiResponse> }) as unknown as Promise<ApiResponse>;
} }
// 获取性能统计列表 // 获取性能统计列表
...@@ -25,8 +25,8 @@ export function getPerformanceList(params: QueryParams): Promise<ApiResponse> { ...@@ -25,8 +25,8 @@ export function getPerformanceList(params: QueryParams): Promise<ApiResponse> {
return request({ return request({
url: dataApi.performanceStatistics, url: dataApi.performanceStatistics,
method: POST, method: POST,
params params,
}) as unknown as Promise<ApiResponse> }) as unknown as Promise<ApiResponse>;
} }
// 获取爬虫任务统计列表 // 获取爬虫任务统计列表
...@@ -34,6 +34,6 @@ export function getAllSpiderTaskStatistics(params: QueryParams): Promise<ApiResp ...@@ -34,6 +34,6 @@ export function getAllSpiderTaskStatistics(params: QueryParams): Promise<ApiResp
return request({ return request({
url: dataApi.allSpiderTaskStatistics, url: dataApi.allSpiderTaskStatistics,
method: POST, method: POST,
params params,
}) as unknown as Promise<ApiResponse> }) as unknown as Promise<ApiResponse>;
} }
\ No newline at end of file
...@@ -35,7 +35,6 @@ defineProps({ ...@@ -35,7 +35,6 @@ defineProps({
const handleClick = (item: InfoItem) => { const handleClick = (item: InfoItem) => {
// 处理点击事件 // 处理点击事件
console.log('Clicked item:', item)
} }
</script> </script>
......
<!-- 解析路径并跳转路由 --> <!-- 解析路径并跳转路由 -->
<script setup lang="ts"> <script setup lang="ts">
import { computed, onMounted } from 'vue' import { computed, onMounted } from "vue";
import { RouterLink } from 'vue-router' import { RouterLink } from "vue-router";
import { isExternal } from '@/utils/validate' import { isExternal } from "@/utils/validate";
const props = defineProps({ const props = defineProps({
to: { to: {
type: String, type: String,
required: true required: true,
} },
}) });
const isExtLink = computed(() => isExternal(props.to)) const isExtLink = computed(() => isExternal(props.to));
onMounted(()=>{ onMounted(() => {});
// console.log(props.to,'111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111')
})
</script> </script>
<template> <template>
......
<template> <template>
<div style="margin-bottom: 0" class="menu-title"> <div style="margin-bottom: 0" class="menu-title">
<div class="title">{{ props.title }}</div> <div class="title">{{ props.title }}</div>
<div class="low-titme">{{ props.subtitle }}</div> <div class="sub-title">{{ props.subtitle }}</div>
</div> </div>
</template> </template>
...@@ -21,7 +21,7 @@ const props = defineProps<{ ...@@ -21,7 +21,7 @@ const props = defineProps<{
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3); text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
} }
.low-titme { .sub-title {
color: rgba(255, 255, 255, 0.8); color: rgba(255, 255, 255, 0.8);
text-align: left; text-align: left;
margin-left: 20px; margin-left: 20px;
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
v-model:current-page="currentPage" v-model:current-page="currentPage"
v-model:page-size="pageSize" v-model:page-size="pageSize"
:background="true" :background="true"
:page-sizes="[10]" :page-sizes="[5, 10]"
:pager-count="pagerCount" :pager-count="pagerCount"
:total="total" :total="total"
class="mt-4" class="mt-4"
...@@ -19,71 +19,71 @@ ...@@ -19,71 +19,71 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed } from 'vue' import { computed } from "vue";
// ElConfigProvider 组件 // ElConfigProvider 组件
import { ElConfigProvider } from 'element-plus'; import { ElConfigProvider } from "element-plus";
// 引入中文包 // 引入中文包
import zhCn from 'element-plus/es/locale/lang/zh-cn'; import zhCn from "element-plus/es/locale/lang/zh-cn";
defineOptions({ name: 'Pagination' }) defineOptions({ name: "Pagination" });
// 更改分页文字 // 更改分页文字
zhCn.el.pagination.total = '共 `{total} 条`'; zhCn.el.pagination.total = "共 {total} 条";
zhCn.el.pagination.goto = '跳至'; zhCn.el.pagination.goto = "跳至";
zhCn.el.pagination.pagesize = '条/页'; zhCn.el.pagination.pagesize = "条/页";
zhCn.el.pagination.pageClassifier = '页'; zhCn.el.pagination.pageClassifier = "页";
const props = defineProps({ const props = defineProps({
// 总条目数 // 总条目数
total: { total: {
required: true, required: true,
type: Number type: Number,
}, },
// 当前页数:pageNo // 当前页数:pageNo
page: { page: {
type: Number, type: Number,
default: 1 default: 1,
}, },
// 每页显示条目个数:pageSize // 每页显示条目个数:pageSize
limit: { limit: {
type: Number, type: Number,
default: 20 default: 10,
}, },
// 设置最大页码按钮数。 页码按钮的数量,当总页数超过该值时会折叠 // 设置最大页码按钮数。 页码按钮的数量,当总页数超过该值时会折叠
// 移动端页码按钮的数量端默认值 5 // 移动端页码按钮的数量端默认值 5
pagerCount: { pagerCount: {
type: Number, 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({ const currentPage = computed({
get() { get() {
return props.page return props.page;
}, },
set(val) { set(val) {
// 触发 update:page 事件,更新 limit 属性,从而更新 pageNo // 触发 update:page 事件,更新 limit 属性,从而更新 pageNo
emit('update:page', val) emit("update:page", val);
} },
}) });
const pageSize = computed({ const pageSize = computed({
get() { get() {
return props.limit return props.limit;
}, },
set(val) { set(val) {
// 触发 update:limit 事件,更新 limit 属性,从而更新 pageSize // 触发 update:limit 事件,更新 limit 属性,从而更新 pageSize
emit('update:limit', val) emit("update:limit", val);
} },
}) });
const handleSizeChange = (val:number) => { const handleSizeChange = (val: number) => {
// 如果修改后超过最大页面,强制跳转到第 1 页 // 如果修改后超过最大页面,强制跳转到第 1 页
if (currentPage.value * val > props.total) { if (currentPage.value * val > props.total) {
currentPage.value = 1 currentPage.value = 1;
} }
// 触发 pagination 事件,重新加载列表 // 触发 pagination 事件,重新加载列表
emit('pagination', { page: currentPage.value, limit: val }) emit("pagination", { page: currentPage.value, limit: val });
} };
const handleCurrentChange = (val:number) => { const handleCurrentChange = (val: number) => {
// 触发 pagination 事件,重新加载列表 // 触发 pagination 事件,重新加载列表
emit('pagination', { page: val, limit: pageSize.value }) emit("pagination", { page: val, limit: pageSize.value });
} };
</script> </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 @@ ...@@ -124,3 +124,25 @@
.el-popper__arrow::before{ .el-popper__arrow::before{
background-color: #1D5484 !important; 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( ...@@ -19,7 +19,6 @@ instance.interceptors.request.use(
// 保持与原项目相同的Token头设置 // 保持与原项目相同的Token头设置
config.headers["Token"] = token; config.headers["Token"] = token;
} }
return config; return config;
}, },
(err) => { (err) => {
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
<div class="table-content"> <div class="table-content">
<el-table <el-table
v-loading="!tableData" v-loading="!tableData"
element-loading-background="rgba(48, 65, 86, 0.7)" element-loading-background="rgba(48, 65, 86, 0.3)"
:data="tableData" :data="tableData"
style="width: 100%" style="width: 100%"
border border
...@@ -60,7 +60,7 @@ ...@@ -60,7 +60,7 @@
<div class="detailForm"> <div class="detailForm">
<el-dialog v-model="detailVisibleValue" center width="800px" align-center @close="handleClose" draggable> <el-dialog v-model="detailVisibleValue" center width="800px" align-center @close="handleClose" draggable>
<template #header> <template #header>
<div class="text-center font-size-6">详情-{{ antennaName }}</div> <div class="text-center font-size-6">详情{{ antennaName }}</div>
</template> </template>
<el-row> <el-row>
<el-col :span="12"> <el-col :span="12">
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
<el-input placeholder="请输入目标名称:" v-model="searchTargetName" style="width: 180px" /> <el-input placeholder="请输入目标名称:" v-model="searchTargetName" style="width: 180px" />
</div> </div>
<div> <div>
<span style="color: white">时间范围</span> <span style="color: white">采集时间</span>
<el-config-provider :locale="zhCn"> <el-config-provider :locale="zhCn">
<el-date-picker <el-date-picker
type="datetimerange" type="datetimerange"
...@@ -31,7 +31,7 @@ ...@@ -31,7 +31,7 @@
<div class="btns"> <div class="btns">
<el-button plain type="primary" @click="handleSearch">查询</el-button> <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>
</div> </div>
</table-search> </table-search>
...@@ -46,8 +46,8 @@ ...@@ -46,8 +46,8 @@
:row-style="{ height: '45.5px' }" :row-style="{ height: '45.5px' }"
:header-cell-style="{ textAlign: 'center' }" :header-cell-style="{ textAlign: 'center' }"
:cell-style="{ textAlign: 'center' }" :cell-style="{ textAlign: 'center' }"
v-loading="tableData.length == 0" v-loading="tableLoading"
element-loading-background="rgba(48, 65, 86, 0.7)" element-loading-background="rgba(48, 65, 86, 0.3)"
> >
<el-table-column property="number" label="序号" width="55" type="index" /> <el-table-column property="number" label="序号" width="55" type="index" />
<el-table-column property="data.mission" label="目标名称" width="86" show-overflow-tooltip /> <el-table-column property="data.mission" label="目标名称" width="86" show-overflow-tooltip />
...@@ -113,7 +113,7 @@ ...@@ -113,7 +113,7 @@
<div class="detailForm"> <div class="detailForm">
<el-dialog v-model="detailVisibleValue" center width="765px" align-center @close="handleClose" draggable> <el-dialog v-model="detailVisibleValue" center width="765px" align-center @close="handleClose" draggable>
<template #header> <template #header>
<div class="text-center font-size-6">目标详情-{{ targetName }}</div> <div class="text-center font-size-6">目标详情{{ targetName }}</div>
</template> </template>
<el-row> <el-row>
<el-col> <el-col>
...@@ -210,7 +210,7 @@ ...@@ -210,7 +210,7 @@
<div class="detailForm"> <div class="detailForm">
<el-dialog v-model="stationVisibleValue" center width="880px" align-center @close="handleClose" draggable> <el-dialog v-model="stationVisibleValue" center width="880px" align-center @close="handleClose" draggable>
<template #header> <template #header>
<div class="text-center font-size-6">站点详情-{{ stationName }}</div> <div class="text-center font-size-6">站点详情{{ stationName }}</div>
</template> </template>
<el-row> <el-row>
<el-col> <el-col>
...@@ -618,15 +618,22 @@ const handleMissionDetails = async (id: any) => { ...@@ -618,15 +618,22 @@ const handleMissionDetails = async (id: any) => {
} }
detailVisibleValue.value = true; detailVisibleValue.value = true;
}; };
//表格loading标记
// 获取esa数据列表的方法 const tableLoading = ref(false);
const getData = async () => { //重置搜索条件
const resetForm = () => {
searchStation.value = ""; searchStation.value = "";
searchTargetName.value = ""; searchTargetName.value = "";
searchTimeValue.value = ""; searchTimeValue.value = "";
getData();
};
// 获取esa数据列表的方法
const getData = async () => {
tableLoading.value = true;
const res = await getESAList({ page: pageObj.value.pageNo, size: pageObj.value.pageSize }); const res = await getESAList({ page: pageObj.value.pageNo, size: pageObj.value.pageSize });
pageObj.value.total = res.data.total; pageObj.value.total = res.data.total;
tableData.value = res.data.list; tableData.value = res.data.list;
tableLoading.value = false;
}; };
const handleClose = () => { const handleClose = () => {
detailVisibleValue.value = false; detailVisibleValue.value = false;
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
<el-input placeholder="请输入目标名称" v-model="searchTargetName" style="width: 180px" /> <el-input placeholder="请输入目标名称" v-model="searchTargetName" style="width: 180px" />
</div> </div>
<div> <div>
<span style="color: white">时间范围</span> <span style="color: white">采集时间</span>
<el-config-provider :locale="zhCn"> <el-config-provider :locale="zhCn">
<el-date-picker <el-date-picker
type="datetimerange" type="datetimerange"
...@@ -30,7 +30,7 @@ ...@@ -30,7 +30,7 @@
</div> </div>
<div class="btns"> <div class="btns">
<el-button plain type="primary" @click="handleSearch">查询</el-button> <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>
</div> </div>
</TableSearch> </TableSearch>
...@@ -45,8 +45,8 @@ ...@@ -45,8 +45,8 @@
:row-style="{ height: '45px' }" :row-style="{ height: '45px' }"
:header-cell-style="{ textAlign: 'center' }" :header-cell-style="{ textAlign: 'center' }"
:cell-style="{ textAlign: 'center' }" :cell-style="{ textAlign: 'center' }"
v-loading="tableData.length == 0" v-loading="tableLoading"
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 type="selection" width="40" /> -->
<el-table-column property="number" label="序号" width="55" type="index" /> <el-table-column property="number" label="序号" width="55" type="index" />
...@@ -107,7 +107,7 @@ ...@@ -107,7 +107,7 @@
<div class="detailForm"> <div class="detailForm">
<el-dialog v-model="detailVisibleValue" center width="765px" align-center @close="handleClose" draggable> <el-dialog v-model="detailVisibleValue" center width="765px" align-center @close="handleClose" draggable>
<template #header> <template #header>
<div class="text-center font-size-6">详情-{{ targetName }}</div> <div class="text-center font-size-6">详情{{ targetName }}</div>
</template> </template>
<el-row> <el-row>
<el-col :span="12"> <el-col :span="12">
...@@ -335,14 +335,23 @@ const handleDetails = async (id: any) => { ...@@ -335,14 +335,23 @@ const handleDetails = async (id: any) => {
BFIFICdate.value = res.data.regulatory_status.d_wic === null ? "-" : res.data.regulatory_status.d_wic; BFIFICdate.value = res.data.regulatory_status.d_wic === null ? "-" : res.data.regulatory_status.d_wic;
detailVisibleValue.value = true; detailVisibleValue.value = true;
}; };
//表单loading标记
const tableLoading = ref(false);
// 获取itu数据列表的方法 // 获取itu数据列表的方法
const getData = async () => { //重置搜索条件
const res = await getItuList({ page: pageObj.value.pageNo, size: pageObj.value.pageSize }); const resetForm = () => {
searchTargetName.value = ""; searchTargetName.value = "";
searchTargetId.value = ""; searchTargetId.value = "";
searchTimeValue.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; pageObj.value.total = res.data.total;
tableData.value = res.data.list; tableData.value = res.data.list;
tableLoading.value = false;
}; };
const handleClose = () => { const handleClose = () => {
detailVisibleValue.value = false; detailVisibleValue.value = false;
...@@ -352,6 +361,7 @@ const handleSearch = async () => { ...@@ -352,6 +361,7 @@ const handleSearch = async () => {
ElMessage.warning("请输入搜索内容"); ElMessage.warning("请输入搜索内容");
return; return;
} }
tableLoading.value = true;
const res = await getItuList({ const res = await getItuList({
sat_name: searchTargetName.value, sat_name: searchTargetName.value,
ntc_id: searchTargetId.value, ntc_id: searchTargetId.value,
...@@ -361,6 +371,7 @@ const handleSearch = async () => { ...@@ -361,6 +371,7 @@ const handleSearch = async () => {
}); });
pageObj.value.total = res.data.total; pageObj.value.total = res.data.total;
tableData.value = res.data.list; tableData.value = res.data.list;
tableLoading.value = false;
}; };
onMounted(() => { onMounted(() => {
...@@ -430,7 +441,7 @@ onMounted(() => { ...@@ -430,7 +441,7 @@ onMounted(() => {
} }
.item-title { .item-title {
color: #fff; color: #b0dfff;
} }
.item { .item {
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
<el-input placeholder="请输入目标名称" style="width: 180px" v-model="searchTargetName" /> <el-input placeholder="请输入目标名称" style="width: 180px" v-model="searchTargetName" />
</div> </div>
<div> <div>
<span style="color: white">时间范围</span> <span style="color: white">采集时间</span>
<el-config-provider :locale="zhCn"> <el-config-provider :locale="zhCn">
<el-date-picker <el-date-picker
v-model="timeValue" v-model="timeValue"
...@@ -30,7 +30,7 @@ ...@@ -30,7 +30,7 @@
</div> </div>
<div class="btns"> <div class="btns">
<el-button plain type="primary" @click="handleSearch">查询</el-button> <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>
</div> </div>
</table-search> </table-search>
...@@ -40,6 +40,8 @@ ...@@ -40,6 +40,8 @@
<div class="table-content"> <div class="table-content">
<el-table <el-table
:data="tableData" :data="tableData"
v-loading="tableLoading"
element-loading-background="rgba(48, 65, 86, 0.3)"
style="width: 100%" style="width: 100%"
border border
:row-style="{ height: '45.5px' }" :row-style="{ height: '45.5px' }"
...@@ -80,7 +82,7 @@ ...@@ -80,7 +82,7 @@
<div class="detailForm"> <div class="detailForm">
<el-dialog v-model="detailVisibleValue" center width="765px" align-center @close="handleClose" draggable> <el-dialog v-model="detailVisibleValue" center width="765px" align-center @close="handleClose" draggable>
<template #header> <template #header>
<div class="text-center font-size-6">详情-{{ targetName }}</div> <div class="text-center font-size-6">详情{{ targetName }}</div>
</template> </template>
<el-row> <el-row>
<el-col> <el-col>
...@@ -295,14 +297,22 @@ ${res.data.data[0].TLE_LINE1} ...@@ -295,14 +297,22 @@ ${res.data.data[0].TLE_LINE1}
${res.data.data[0].TLE_LINE2}`; ${res.data.data[0].TLE_LINE2}`;
detailVisibleValue.value = true; detailVisibleValue.value = true;
}; };
//表单loading标记
const tableLoading = ref(false);
//重置搜索条件
const resetForm = () => {
noradCatId.value = "";
searchTargetName.value = "";
timeValue.value = "";
getData();
};
// 获取st数据列表的方法 // 获取st数据列表的方法
const getData = async () => { const getData = async () => {
tableLoading.value = true;
const res = await getStList({ page: pageObj.value.pageNo, size: pageObj.value.pageSize }); const res = await getStList({ page: pageObj.value.pageNo, size: pageObj.value.pageSize });
pageObj.value.total = res.data.total; pageObj.value.total = res.data.total;
tableData.value = res.data.list; tableData.value = res.data.list;
noradCatId.value = ""; tableLoading.value = false;
searchTargetName.value = "";
timeValue.value = "";
}; };
const handleClose = () => { const handleClose = () => {
detailVisibleValue.value = false; detailVisibleValue.value = false;
......
...@@ -32,7 +32,6 @@ import stDataTab from "./components/stDataTab.vue"; ...@@ -32,7 +32,6 @@ import stDataTab from "./components/stDataTab.vue";
import dsnDataTab from "./components/dsnData/dsnTab.vue"; import dsnDataTab from "./components/dsnData/dsnTab.vue";
import esDataTab from "./components/esDataTab.vue"; import esDataTab from "./components/esDataTab.vue";
import exportDialog from "@/components/Export/index.vue"; import exportDialog from "@/components/Export/index.vue";
import type { UploadInstance } from "element-plus";
import MenuTitle from "@/components/MenuTitle.vue"; import MenuTitle from "@/components/MenuTitle.vue";
const mode = ref(sessionStorage.getItem("dataDisplayMode") || "DSN数据"); const mode = ref(sessionStorage.getItem("dataDisplayMode") || "DSN数据");
......
...@@ -10,17 +10,17 @@ ...@@ -10,17 +10,17 @@
:cell-style="{ textAlign: 'center' }" :cell-style="{ textAlign: 'center' }"
:row-style="{ height: '60px' }" :row-style="{ height: '60px' }"
v-loading="tableData.length == 0" 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="number" label="序号" type="index" width="80" />
<el-table-column property="spider" label="爬虫代号" show-overflow-tooltip /> <el-table-column property="spider" label="爬虫代号" show-overflow-tooltip />
<el-table-column property="spider_name" 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="操作"> <el-table-column label="操作">
<template #default="scope"> <template #default="scope">
<div class="btn-group"> <div class="btn-group">
<el-button type="primary" plain @click="handleDetails(scope.row)">查看任务</el-button> <el-button link 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="handleEditSateId" v-if="scope.row.editable">编辑</el-button>
</div> </div>
</template> </template>
</el-table-column> </el-table-column>
...@@ -35,7 +35,7 @@ ...@@ -35,7 +35,7 @@
</div> </div>
</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"> <div class="No-Content" ref="noContentRef">
<TransitionGroup <TransitionGroup
enter-active-class="animate__animated animate__slideInRight" enter-active-class="animate__animated animate__slideInRight"
...@@ -65,7 +65,7 @@ ...@@ -65,7 +65,7 @@
style="width: 100px" style="width: 100px"
></el-input> ></el-input>
</div> </div>
<div class="btn"> <div class="btns">
<el-button :disabled="curSateNo == ''" plain type="primary" @click="addNoToList">添加</el-button> <el-button :disabled="curSateNo == ''" plain type="primary" @click="addNoToList">添加</el-button>
<el-popconfirm title="确定删除选中编号吗?" width="200" style="background-color: pink"> <el-popconfirm title="确定删除选中编号吗?" width="200" style="background-color: pink">
<template #reference> <template #reference>
...@@ -87,6 +87,16 @@ ...@@ -87,6 +87,16 @@
</el-button> </el-button>
</template> </template>
</el-popconfirm> </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>
</div> </div>
</el-dialog> </el-dialog>
...@@ -94,12 +104,12 @@ ...@@ -94,12 +104,12 @@
</template> </template>
<script setup lang="ts"> <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 Pagination from "@/components/pagination/index.vue";
import { useRouter } from "vue-router"; import { useRouter } from "vue-router";
import { getSpiderList, addSateNo, getSateIdList } from "@/api/system.ts"; import { getSpiderList, addSateNo, getSateIdList } from "@/api/system.ts";
import NoItem from "./components/NoItem.vue"; 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"; import MenuTitle from "@/components/MenuTitle.vue";
const router = useRouter(); const router = useRouter();
...@@ -123,7 +133,6 @@ const handleDetails = (row: any) => { ...@@ -123,7 +133,6 @@ const handleDetails = (row: any) => {
const getData = async () => { const getData = async () => {
const res = await getSpiderList({ scrapydServerId: "1", project: "spiders" }); const res = await getSpiderList({ scrapydServerId: "1", project: "spiders" });
tableData.value = res.data; tableData.value = res.data;
console.log(res.data);
}; };
const editDialogVisible = ref(false); const editDialogVisible = ref(false);
...@@ -148,7 +157,7 @@ const handleSateDel = (no: string | number) => { ...@@ -148,7 +157,7 @@ const handleSateDel = (no: string | number) => {
//添加编号到列表并整体发到后端 //添加编号到列表并整体发到后端
const addNoToList = async () => { const addNoToList = async () => {
try { try {
if (sateNoList.value.includes(padZeroTo5Digits(curSateNo.value))) { if (curSateNo.value && sateNoList.value.includes(padZeroTo5Digits(curSateNo.value))) {
ElMessage.error("当前编号已存在,请重新输入"); ElMessage.error("当前编号已存在,请重新输入");
curSateNo.value = ""; curSateNo.value = "";
return; return;
...@@ -168,6 +177,22 @@ const addNoToList = async () => { ...@@ -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 () => { const delNoList = async () => {
try { try {
...@@ -217,18 +242,112 @@ const handleEditSateId = async () => { ...@@ -217,18 +242,112 @@ const handleEditSateId = async () => {
if (res.code === 0) { if (res.code === 0) {
sateNoList.value = res.data || []; sateNoList.value = res.data || [];
} }
console.log(res);
} catch (error: any) { } catch (error: any) {
ElMessage.error(error.message); 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(() => { onMounted(() => {
getData(); getData();
}); });
</script> </script>
<style scoped> <style scoped>
:deep(.el-input__inner) {
color: black;
}
/* 去除按钮边框 */ /* 去除按钮边框 */
.el-button:focus { .el-button:focus {
outline: none; outline: none;
...@@ -252,6 +371,11 @@ onMounted(() => { ...@@ -252,6 +371,11 @@ onMounted(() => {
color: white; color: white;
} }
.btns {
display: flex;
gap: 10px;
}
.low-titme { .low-titme {
color: #ccc; color: #ccc;
text-align: left; text-align: left;
......
...@@ -142,7 +142,7 @@ onMounted(() => { ...@@ -142,7 +142,7 @@ onMounted(() => {
background-repeat: no-repeat; background-repeat: no-repeat;
// background: #c6ebfc; // background: #c6ebfc;
// border: 1.5px solid rgb(193, 188, 188); // border: 1.5px solid rgb(193, 188, 188);
width: 500px; width: 350px;
border-radius: 5px; 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 = () => { ...@@ -138,7 +138,7 @@ const goToESADataPage = () => {
background-repeat: no-repeat; background-repeat: no-repeat;
// background: #c6ebfc; // background: #c6ebfc;
// border: 1.5px solid rgb(193, 188, 188); // border: 1.5px solid rgb(193, 188, 188);
width: 375px; width: 263px;
height: 100%; height: 100%;
border-radius: 5px; border-radius: 5px;
display: flex; display: flex;
......
<template> <template>
<div> <div class="monitor-container">
<div class="card-container"> <div class="card-container">
<div class="monitoringCard"> <div class="monitoringCard">
<div class="data-card-wrapper"> <div class="data-card-wrapper">
...@@ -17,6 +17,9 @@ ...@@ -17,6 +17,9 @@
</div> </div>
</div> </div>
</div> </div>
<div class="monitor-chart">
<errorMessageChart />
</div>
</div> </div>
</template> </template>
...@@ -24,6 +27,7 @@ ...@@ -24,6 +27,7 @@
import dataCard from "./dataCard.vue"; import dataCard from "./dataCard.vue";
import taskCard from "@/views/os-status/components/taskCard.vue"; import taskCard from "@/views/os-status/components/taskCard.vue";
import qbCard from "@/views/os-status/components/qbDataMonitor.vue"; import qbCard from "@/views/os-status/components/qbDataMonitor.vue";
import errorMessageChart from "./errorMessageChart.vue";
import { onMounted } from "vue"; import { onMounted } from "vue";
defineProps({ defineProps({
...@@ -41,6 +45,15 @@ onMounted(() => {}); ...@@ -41,6 +45,15 @@ onMounted(() => {});
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.monitor-container {
display: flex;
}
.monitor-chart {
color: white;
width: 30%;
}
.card-container { .card-container {
height: 100%; height: 100%;
display: flex; display: flex;
...@@ -49,6 +62,7 @@ onMounted(() => {}); ...@@ -49,6 +62,7 @@ onMounted(() => {});
align-items: center; align-items: center;
padding: 10px; padding: 10px;
gap: 30px; gap: 30px;
width: 70%;
} }
.monitoringCard { .monitoringCard {
......
...@@ -8,17 +8,19 @@ ...@@ -8,17 +8,19 @@
<div class="items"> <div class="items">
<div class="wordStyle"> <div class="wordStyle">
<span>DSN爬虫任务数:</span> <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> <span class="loading" v-else></span>
</div> </div>
<div class="wordStyle"> <div class="wordStyle">
<span>任务执行成功统计:</span> <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> <span class="loading" v-else></span>
</div> </div>
<div class="wordStyle"> <div class="wordStyle">
<span>任务执行失败统计:</span> <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> <span class="loading" v-else></span>
</div> </div>
<div class="wordStyle"> <div class="wordStyle">
...@@ -35,22 +37,24 @@ ...@@ -35,22 +37,24 @@
<div class="items"> <div class="items">
<div class="wordStyle"> <div class="wordStyle">
<span>ITU爬虫任务数:</span> <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> <span class="loading" v-else></span>
</div> </div>
<div class="wordStyle"> <div class="wordStyle">
<span>任务执行成功统计:</span> <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> <span class="loading" v-else></span>
</div> </div>
<div class="wordStyle"> <div class="wordStyle">
<span>任务执行失败统计:</span> <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> <span class="loading" v-else></span>
</div> </div>
<div class="wordStyle"> <div class="wordStyle">
<span>任务执行失败统计:</span> <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> <span class="loading" v-else></span>
</div> </div>
<div class="wordStyle"> <div class="wordStyle">
...@@ -67,17 +71,19 @@ ...@@ -67,17 +71,19 @@
<div class="items"> <div class="items">
<div class="wordStyle"> <div class="wordStyle">
<span>ST爬虫任务数:</span> <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> <span class="loading" v-else></span>
</div> </div>
<div class="wordStyle"> <div class="wordStyle">
<span>任务执行成功统计:</span> <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> <span class="loading" v-else></span>
</div> </div>
<div class="wordStyle"> <div class="wordStyle">
<span>采集速度:</span> <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> <span class="loading" v-else></span>
</div> </div>
<div class="wordStyle"> <div class="wordStyle">
...@@ -97,6 +103,7 @@ import { onMounted, ref } from "vue"; ...@@ -97,6 +103,7 @@ import { onMounted, ref } from "vue";
import { getSpiderTaskList } from "@/api/spiderTask"; import { getSpiderTaskList } from "@/api/spiderTask";
import { useSlideStateStore } from "@/store/slideState"; import { useSlideStateStore } from "@/store/slideState";
import { storeToRefs } from "pinia"; import { storeToRefs } from "pinia";
import formatExactLargeNum from "@/utils/formatExactLargeNum";
const slideStateStore = useSlideStateStore(); const slideStateStore = useSlideStateStore();
const { slideState } = storeToRefs(slideStateStore); const { slideState } = storeToRefs(slideStateStore);
...@@ -178,7 +185,7 @@ onMounted(() => { ...@@ -178,7 +185,7 @@ onMounted(() => {
background-repeat: no-repeat; background-repeat: no-repeat;
// background: #c6ebfc; // background: #c6ebfc;
// border: 1.5px solid rgb(193, 188, 188); // border: 1.5px solid rgb(193, 188, 188);
width: 500px; width: 350px;
height: 100%; height: 100%;
border-radius: 5px; border-radius: 5px;
cursor: pointer; cursor: pointer;
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
<el-button test-element="userSystem-AddUser" type="primary" plain @click="openAddUserDialog" <el-button test-element="userSystem-AddUser" type="primary" plain @click="openAddUserDialog"
>创建用户</el-button >创建用户</el-button
> >
<el-button type="danger" plain @click="handleBatchDelete">批量删除</el-button> <el-button type="danger" plain @click="handleBatchDelete" class="">批量删除</el-button>
</div> </div>
</el-card> </el-card>
<div class="table-content"> <div class="table-content">
...@@ -18,7 +18,7 @@ ...@@ -18,7 +18,7 @@
:row-style="{ height: '58px' }" :row-style="{ height: '58px' }"
@selection-change="handleSelectionChange" @selection-change="handleSelectionChange"
v-loading="tableData?.length == 0" 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 type="selection" width="40" />
<el-table-column property="number" label="序号" width="55" type="index" /> <el-table-column property="number" label="序号" width="55" type="index" />
...@@ -37,8 +37,8 @@ ...@@ -37,8 +37,8 @@
<el-table-column property="create_time" label="创建时间" width="280" show-overflow-tooltip /> <el-table-column property="create_time" label="创建时间" width="280" show-overflow-tooltip />
<el-table-column label="操作" width="220"> <el-table-column label="操作" width="220">
<template #default="scope"> <template #default="scope">
<el-button type="primary" plain @click="handleEdit(scope.row)" id="editUser"> 编辑 </el-button> <el-button link 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="danger" plain @click="handleDelete(scope.row)"> 删除 </el-button>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
......
...@@ -12,8 +12,8 @@ ...@@ -12,8 +12,8 @@
<el-form-item label="任务名称:" v-if="currentMode === AddMode.ADD_TASK" prop="taskName"> <el-form-item label="任务名称:" v-if="currentMode === AddMode.ADD_TASK" prop="taskName">
<el-input v-model="ruleForm.taskName" placeholder="请输入任务名称" style="width: 90%" /> <el-input v-model="ruleForm.taskName" placeholder="请输入任务名称" style="width: 90%" />
</el-form-item> </el-form-item>
<el-form-item label="所属爬虫:" v-if="currentMode === AddMode.ADD_TASK" prop="spiderTypeValue"> <el-form-item label="选择爬虫:" v-if="currentMode === AddMode.ADD_TASK" prop="spiderTypeValue">
<el-select v-model="ruleForm.spiderTypeValue" placeholder="请选择所属爬虫" style="width: 90%"> <el-select v-model="ruleForm.spiderTypeValue" placeholder="请选择爬虫" style="width: 90%">
<el-option <el-option
v-for="item in spiderTypeOptions" v-for="item in spiderTypeOptions"
:key="item.spider" :key="item.spider"
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
<TableSearch> <TableSearch>
<div class="form-content"> <div class="form-content">
<div class="ipt"> <div class="ipt">
<span style="color: white">所属爬虫:</span> <span style="color: white">选择爬虫:</span>
<el-select <el-select
v-model="searchCondition.spiders" v-model="searchCondition.spiders"
placeholder="请选择" placeholder="请选择"
...@@ -26,15 +26,9 @@ ...@@ -26,15 +26,9 @@
</div> </div>
</div> </div>
</TableSearch> </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-if="taskList.length === 0 && !isLoading" class="empty-tip">暂无任务数据</div>
<div <div class="taskCard p-2" v-for="task in taskList || []" :key="task?.id || task?.taskId">
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="header"> <div class="header">
<span class="task-name" <span class="task-name"
>任务名称: {{ (task?.kwargs?.options && JSON.parse(task.kwargs.options).jobName) || "无名称" }}</span >任务名称: {{ (task?.kwargs?.options && JSON.parse(task.kwargs.options).jobName) || "无名称" }}</span
...@@ -49,6 +43,7 @@ ...@@ -49,6 +43,7 @@
>删除</el-button >删除</el-button
> >
</div> </div>
<div class="info"> <div class="info">
<div class="item"> <div class="item">
<span class="wordStyle">启用/停止: </span> <span class="wordStyle">启用/停止: </span>
...@@ -61,8 +56,16 @@ ...@@ -61,8 +56,16 @@
/> />
</div> </div>
<div class="item"> <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">执行频率: </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>
<div class="item"> <div class="item">
<span class="wordStyle">执行次数: </span> <span class="wordStyle">执行次数: </span>
...@@ -87,17 +90,18 @@ ...@@ -87,17 +90,18 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, onMounted } from "vue"; import { ref, onMounted, onUnmounted } from "vue";
import { useRouter } from "vue-router"; import { useRouter } from "vue-router";
import addTaskDialog from "./addTaskDialog.vue"; import addTaskDialog from "./addTaskDialog.vue";
import { getSpiderTaskList, resumeSpiderTask, pauseSpiderTask, getTaskCount } from "@/api/spiderTask"; import { getSpiderTaskList, resumeSpiderTask, pauseSpiderTask, getTaskCount } from "@/api/spiderTask";
import { DeleteMode } from "@/components/Delete/enum.ts"; import { DeleteMode } from "@/components/Delete/enum.ts";
import { AddMode } from "./enum"; import { AddMode } from "./enum";
import deleteDialog from "./deleteDialog.vue"; import deleteDialog from "./deleteDialog.vue";
import { ElMessage } from "element-plus"; import { ElMessage, ElNotification } from "element-plus";
import { getSpiderList } from "@/api/system.ts"; import { getSpiderList } from "@/api/system.ts";
import formatExactLargeNum from "@/utils/formatExactLargeNum"; import formatExactLargeNum from "@/utils/formatExactLargeNum";
import TableSearch from "@/components/TableSearch.vue"; import TableSearch from "@/components/TableSearch.vue";
import { Manager, Socket } from "socket.io-client";
const props = defineProps({ const props = defineProps({
spiderType: { spiderType: {
...@@ -114,6 +118,26 @@ const props = defineProps({ ...@@ -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 // 所有响应式变量初始化时避免 undefined
const taskSelectOptions = ref<any[]>([]); // 初始化为空数组 const taskSelectOptions = ref<any[]>([]); // 初始化为空数组
const searchCondition = ref({ const searchCondition = ref({
...@@ -269,44 +293,6 @@ const search = async () => { ...@@ -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 () => { const getSpiderTypeList = async () => {
try { try {
...@@ -325,10 +311,22 @@ onMounted(() => { ...@@ -325,10 +311,22 @@ onMounted(() => {
} }
getData(); getData();
getSpiderTypeList(); getSpiderTypeList();
//开启socket连接
handleErrorMsgNotify();
});
onUnmounted(() => {
//关闭socket连接
if (socket.value) {
socket.value.disconnect();
}
}); });
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
:deep(.el-select__selected-item) {
color: white !important;
}
:deep(.el-form-item__label) { :deep(.el-form-item__label) {
color: white; color: white;
} }
...@@ -391,7 +389,7 @@ onMounted(() => { ...@@ -391,7 +389,7 @@ onMounted(() => {
.taskCard .wordStyle { .taskCard .wordStyle {
font-size: 16px; font-size: 16px;
color: #ffffff; color: #ffffff !important;
} }
/* 按钮样式微调 */ /* 按钮样式微调 */
......
...@@ -6,31 +6,32 @@ ...@@ -6,31 +6,32 @@
<table-search> <table-search>
<div class="form-content"> <div class="form-content">
<div class="timer"> <div class="timer">
<span style="color: white">时间</span> <span style="color: white">采集日期</span>
<el-config-provider :locale="zhCn"> <el-config-provider :locale="zhCn">
<el-date-picker <el-date-picker
v-model="timeValue" v-model="timeValue"
type="datetimerange" type="daterange"
start-placeholder="开始时间" start-placeholder="开始日期"
end-placeholder="结束时间" end-placeholder="结束日期"
format="YYYY-MM-DD HH:mm:ss" format="YYYY-MM-DD"
date-format="YYYY/MM/DD ddd" date-format="YYYY/MM/DD ddd"
time-format="A hh:mm:ss" 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> </el-config-provider>
</div> </div>
<div class="btns"> <div class="btns">
<el-button plain type="primary" @click="searchData">查询</el-button> <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>
</div> </div>
</table-search> </table-search>
<div class="table-content"> <div class="table-content">
<div> <div>
<el-table <el-table
v-loading="tableData.length == 0" v-loading="tableLoading"
element-loading-background="rgba(48, 65, 86, 0.7)" element-loading-background="rgba(48, 65, 86, 0.3)"
:data="tableData" :data="tableData"
style="width: 100%" style="width: 100%"
border border
...@@ -56,7 +57,12 @@ ...@@ -56,7 +57,12 @@
{{ scope.row.run_status === "unknown" ? "已结束" : scope.row.run_status }} {{ scope.row.run_status === "unknown" ? "已结束" : scope.row.run_status }}
</template> </template>
</el-table-column> </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> </el-table>
</div> </div>
<div class="pagination w-full flex flex-row-reverse pr-4 m-t-4"> <div class="pagination w-full flex flex-row-reverse pr-4 m-t-4">
...@@ -81,6 +87,8 @@ import { getSpiderTaskRecord } from "@/api/spiderTask.ts"; ...@@ -81,6 +87,8 @@ import { getSpiderTaskRecord } from "@/api/spiderTask.ts";
import { ElMessage } from "element-plus"; import { ElMessage } from "element-plus";
import MenuTitle from "@/components/MenuTitle.vue"; import MenuTitle from "@/components/MenuTitle.vue";
import TableSearch from "@/components/TableSearch.vue"; import TableSearch from "@/components/TableSearch.vue";
//时间处理库
import dayjs from "dayjs";
// ElConfigProvider 组件 // ElConfigProvider 组件
import { ElConfigProvider } from "element-plus"; import { ElConfigProvider } from "element-plus";
// 引入中文包 // 引入中文包
...@@ -93,21 +101,8 @@ zhCn.el.pagination.pagesize = "条/页"; ...@@ -93,21 +101,8 @@ zhCn.el.pagination.pagesize = "条/页";
zhCn.el.pagination.pageClassifier = "页"; zhCn.el.pagination.pageClassifier = "页";
const route = useRoute(); const route = useRoute();
const router = useRouter(); const router = useRouter();
const timeValue = ref(""); //初始数据为近七天
const taskOptions = [ const timeValue = ref([dayjs().subtract(7, "day").format("YYYY-MM-DD"), dayjs().format("YYYY-MM-DD")]);
{
value: "task1",
label: "sk网",
},
{
value: "task2",
label: "nasa网",
},
{
value: "task3",
label: "网",
},
];
const tableData = ref([]); const tableData = ref([]);
const pageObj = ref({ const pageObj = ref({
total: 10, total: 10,
...@@ -115,16 +110,7 @@ const pageObj = ref({ ...@@ -115,16 +110,7 @@ const pageObj = ref({
pageNo: 1, pageNo: 1,
pagerCount: 5, pagerCount: 5,
}); });
// 前往运行日志,暂时不要
// const handleDetails = (row: any) => {
// console.log(row);
// router.push({
// path: '/os-log/list',
// query: {
// id: row.id
// }
// })
// }
const goToStatus = () => { const goToStatus = () => {
if (route.query.mode === "状态监控") { if (route.query.mode === "状态监控") {
router.push({ router.push({
...@@ -136,23 +122,34 @@ const goToStatus = () => { ...@@ -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 getData = async () => {
const res = await getSpiderTaskRecord({ page: pageObj.value.pageNo, size: pageObj.value.pageSize, status: "total" }); searchData();
pageObj.value.total = res.data.total;
tableData.value = res.data.list;
}; };
// 数据的方法
//查询数据的方法
const searchData = async () => { const searchData = async () => {
if (!timeValue.value) { if (!timeValue.value) {
ElMessage.warning("请先选择时间段"); ElMessage.warning("请先选择时间段");
return; return;
} }
let resTime = []; let resTime: any[] = [];
const startTime = timeValue.value[0]; const startTime = timeValue.value[0];
const endTime = timeValue.value[1]; const endTime = timeValue.value[1];
resTime.push(startTime); resTime.push(startTime + " 00:00:00");
resTime.push(endTime); resTime.push(endTime + " 00:00:00");
tableLoading.value = true;
const res = await getSpiderTaskRecord({ const res = await getSpiderTaskRecord({
page: pageObj.value.pageNo, page: pageObj.value.pageNo,
size: pageObj.value.pageSize, size: pageObj.value.pageSize,
...@@ -161,6 +158,9 @@ const searchData = async () => { ...@@ -161,6 +158,9 @@ const searchData = async () => {
}); });
pageObj.value.total = res.data.total; pageObj.value.total = res.data.total;
tableData.value = res.data.list; tableData.value = res.data.list;
console.log(tableData.value);
tableLoading.value = false;
}; };
onMounted(() => { 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