Commit cff37174 by 周田

Merge branch 'yzh' into 'main'

Yzh

See merge request !9
parents c5b55285 d8f4cbdf
VITE_APP_BASE_API = '/api' VITE_APP_BASE_API = 'http://127.0.0.1:5001/api'
...@@ -22,3 +22,5 @@ dist-ssr ...@@ -22,3 +22,5 @@ dist-ssr
*.njsproj *.njsproj
*.sln *.sln
*.sw? *.sw?
minimal-repro/*
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
"@types/nprogress": "^0.2.3", "@types/nprogress": "^0.2.3",
"@types/uuid": "^10.0.0", "@types/uuid": "^10.0.0",
"axios": "^1.9.0", "axios": "^1.9.0",
"cron-parser": "^5.4.0",
"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",
......
...@@ -26,6 +26,9 @@ importers: ...@@ -26,6 +26,9 @@ importers:
axios: axios:
specifier: ^1.9.0 specifier: ^1.9.0
version: 1.9.0 version: 1.9.0
cron-parser:
specifier: ^5.4.0
version: 5.4.0
echarts: echarts:
specifier: ^5.6.0 specifier: ^5.6.0
version: 5.6.0 version: 5.6.0
...@@ -706,6 +709,10 @@ packages: ...@@ -706,6 +709,10 @@ packages:
resolution: {integrity: sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==} resolution: {integrity: sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==}
engines: {node: '>=12.13'} engines: {node: '>=12.13'}
cron-parser@5.4.0:
resolution: {integrity: sha512-HxYB8vTvnQFx4dLsZpGRa0uHp6X3qIzS3ZJgJ9v6l/5TJMgeWQbLkR5yiJ5hOxGbc9+jCADDnydIe15ReLZnJA==}
engines: {node: '>=18'}
css-tree@3.1.0: css-tree@3.1.0:
resolution: {integrity: sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==} resolution: {integrity: sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==}
engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0}
...@@ -918,6 +925,10 @@ packages: ...@@ -918,6 +925,10 @@ packages:
lodash@4.17.21: lodash@4.17.21:
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
luxon@3.7.2:
resolution: {integrity: sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==}
engines: {node: '>=12'}
magic-string@0.30.17: magic-string@0.30.17:
resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==}
...@@ -1967,6 +1978,10 @@ snapshots: ...@@ -1967,6 +1978,10 @@ snapshots:
dependencies: dependencies:
is-what: 4.1.16 is-what: 4.1.16
cron-parser@5.4.0:
dependencies:
luxon: 3.7.2
css-tree@3.1.0: css-tree@3.1.0:
dependencies: dependencies:
mdn-data: 2.12.2 mdn-data: 2.12.2
...@@ -2179,6 +2194,8 @@ snapshots: ...@@ -2179,6 +2194,8 @@ snapshots:
lodash@4.17.21: {} lodash@4.17.21: {}
luxon@3.7.2: {}
magic-string@0.30.17: magic-string@0.30.17:
dependencies: dependencies:
'@jridgewell/sourcemap-codec': 1.5.0 '@jridgewell/sourcemap-codec': 1.5.0
......
...@@ -95,6 +95,7 @@ export const spiderTaskApi = { ...@@ -95,6 +95,7 @@ export const spiderTaskApi = {
pauseJob: '/schedule/pauseJob', pauseJob: '/schedule/pauseJob',
resumeJob: '/schedule/resumeJob', resumeJob: '/schedule/resumeJob',
jobDetail: '/schedule/jobDetail', jobDetail: '/schedule/jobDetail',
taskCount:'/schedule/getAllJobCount'
} as const } as const
// 爬虫数据相关接口 // 爬虫数据相关接口
...@@ -105,4 +106,22 @@ export const spiderDataApi = { ...@@ -105,4 +106,22 @@ export const spiderDataApi = {
ituList: '/itu/list', ituList: '/itu/list',
ituDetail: '/itu/detail', ituDetail: '/itu/detail',
ituDataDelete: '/itu/delete', ituDataDelete: '/itu/delete',
stList: '/spaceTrack/list',
stDetail: '/spaceTrack/detail',
stDataDelete: '/sspaceTrackt/delete',
exportSpiderData: '/export/downloadFile',
esaList: '/esa/list',
esaMissionDetail: '/esa/missionDetail',
esaStationDetail: '/esa/stationDetail',
} as const
// 爬虫数据相关接口
export const dataApi = {
dataStatistics: '/statistic/dataStatistics',
taskStatistics: '/statistic/taskStatistics',
performanceStatistics: '/statistic/performanceStatistics',
allSpiderTaskStatistics: '/statistic/getAllTaskStatistics',
} as const } as const
...@@ -3,7 +3,7 @@ import type { ApiResponse, QueryParams, UserQueryParams } from '@/utils/request' ...@@ -3,7 +3,7 @@ import type { ApiResponse, QueryParams, UserQueryParams } from '@/utils/request'
import { spiderDataApi } from './apiPaths' import { spiderDataApi } from './apiPaths'
// 获取dsn数据列表 // 获取dsn数据列表
export function getDsnlist(data: UserQueryParams) { export function getDsnList(data: UserQueryParams) {
return request({ return request({
url: spiderDataApi.dsnList, url: spiderDataApi.dsnList,
method: POST, method: POST,
...@@ -30,7 +30,7 @@ export function deleteDsnData(data: UserQueryParams) { ...@@ -30,7 +30,7 @@ export function deleteDsnData(data: UserQueryParams) {
} }
// 获取itu数据列表 // 获取itu数据列表
export function getItulist(data: UserQueryParams) { export function getItuList(data: UserQueryParams) {
return request({ return request({
url: spiderDataApi.ituList, url: spiderDataApi.ituList,
method: POST, method: POST,
...@@ -55,3 +55,67 @@ export function deleteItuData(data: UserQueryParams) { ...@@ -55,3 +55,67 @@ export function deleteItuData(data: UserQueryParams) {
data data
}) as unknown as Promise<ApiResponse> }) as unknown as Promise<ApiResponse>
} }
// 获取st数据列表
export function getStList(data: UserQueryParams) {
return request({
url: spiderDataApi.stList,
method: POST,
data
}) as unknown as Promise<ApiResponse>
}
// 获取st数据详情
export function getStDetail(data: UserQueryParams) {
return request({
url: spiderDataApi.stDetail,
method: POST,
data
}) as unknown as Promise<ApiResponse>
}
// 删除st数据
export function deleteStData(data: UserQueryParams) {
return request({
url: spiderDataApi.stDataDelete,
method: POST,
data
}) as unknown as Promise<ApiResponse>
}
// 导出爬虫数据
export function exportSpiderData(data: UserQueryParams) {
return request({
url: spiderDataApi.exportSpiderData,
method: POST,
data
}) as unknown as Promise<ApiResponse>
}
// 获取esa数据列表
export function getESAList(data: UserQueryParams) {
return request({
url: spiderDataApi.esaList,
method: POST,
data
}) as unknown as Promise<ApiResponse>
}
// 获取esa任务详情
export function getESAMissionDetail(data: UserQueryParams) {
return request({
url: spiderDataApi.esaMissionDetail,
method: POST,
data
}) as unknown as Promise<ApiResponse>
}
// 获取esa站点详情
export function getESAStationDetail(data: UserQueryParams) {
return request({
url: spiderDataApi.esaStationDetail,
method: POST,
data
}) as unknown as Promise<ApiResponse>
}
...@@ -64,3 +64,12 @@ export function getTaskData(data: UserQueryParams) { ...@@ -64,3 +64,12 @@ export function getTaskData(data: UserQueryParams) {
data data
}) as unknown as Promise<ApiResponse> }) as unknown as Promise<ApiResponse>
} }
// 获取爬虫任务统计
export function getTaskCount(data: UserQueryParams) {
return request({
url: spiderTaskApi.taskCount,
method: POST,
data
}) 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'
// 获取爬虫静态数据列表
export function getStatsDataList(params: QueryParams): Promise<ApiResponse> {
return request({
url: dataApi.dataStatistics,
method: POST,
params
}) as unknown as Promise<ApiResponse>
}
// 获取爬虫任务列表
export function getSpiderTaskList(params: QueryParams): Promise<ApiResponse> {
return request({
url: dataApi.taskStatistics,
method: POST,
params
}) as unknown as Promise<ApiResponse>
}
// 获取性能统计列表
export function getPerformanceList(params: QueryParams): Promise<ApiResponse> {
return request({
url: dataApi.performanceStatistics,
method: POST,
params
}) as unknown as Promise<ApiResponse>
}
// 获取爬虫任务统计列表
export function getAllSpiderTaskStatistics(params: QueryParams): Promise<ApiResponse> {
return request({
url: dataApi.allSpiderTaskStatistics,
method: POST,
params
}) as unknown as Promise<ApiResponse>
}
\ No newline at end of file
<template>
<span>
<el-button
@click="changeAutoRefresh"
:size="size"
>{{ autoRefreshText }}</el-button> <span class="tips">{{ countDownText }}</span>
</span>
</template>
<script setup lang="ts">
/**
* 自动刷新组件
* emit: refresh - 刷新事件
*/
import { ref, computed, onBeforeUnmount } from 'vue';
const props = defineProps({
// 刷新频率,默认:3秒
frequency: { type: Number, default: 3000 },
// 按钮大小
size: { type: String, default: 'default' }
});
const emit = defineEmits(['refresh']);
// 定时器
const timer = ref<NodeJS.Timeout | null>(null);
const countDownTimer = ref<NodeJS.Timeout | null>(null);
// 倒计时
const countDown = ref(0);
const autoRefreshText = computed(() => {
if (timer.value == null) {
return '开启自动刷新';
} else {
return '关闭自动刷新';
}
});
const countDownText = computed(() => {
if (timer.value == null) {
return '';
} else {
return countDown.value + ' 秒后自动刷新';
}
});
function autoRefresh() {
emit('refresh');
resetCountDown();
}
function countDownReduce() {
countDown.value--;
}
function resetCountDown() {
countDown.value = Math.ceil(props.frequency * 0.001);
}
function openAutoRefresh() {
resetCountDown();
// 启动读秒计时器
countDownTimer.value = setInterval(() => {
countDownReduce();
}, 1000);
// 启动自动刷新计时器
timer.value = setInterval(() => {
autoRefresh();
}, props.frequency);
}
function closeAutoRefresh() {
if (timer.value) clearInterval(timer.value);
if (countDownTimer.value) clearInterval(countDownTimer.value);
timer.value = null;
countDownTimer.value = null;
}
function changeAutoRefresh() {
if (timer.value) {
closeAutoRefresh();
} else {
openAutoRefresh();
}
}
// 组件销毁前清除定时器
onBeforeUnmount(() => {
closeAutoRefresh();
});
</script>
<style lang="scss" scoped>
.tips {
margin-left: 10px;
color: #666666;
font-size: 12px;
}
.el-button:focus {
outline: none;
}
</style>
...@@ -13,11 +13,12 @@ interface shortcutsType { ...@@ -13,11 +13,12 @@ interface shortcutsType {
const props = defineProps({ const props = defineProps({
modelValue: { modelValue: {
type: String, type: String,
default: '* * * * * ?' default: '* * * * * *'
}, },
shortcuts: { type: Array as PropType<shortcutsType[]>, default: () => [] } shortcuts: { type: Array as PropType<shortcutsType[]>, default: () => [] }
}) })
const defaultValue = ref('') const defaultValue = ref('')
const cronLabel = ref()
const dialogVisible = ref(false) const dialogVisible = ref(false)
const getYear = () => { const getYear = () => {
let v: number[] = [] let v: number[] = []
...@@ -217,7 +218,7 @@ const value_second = computed(() => { ...@@ -217,7 +218,7 @@ const value_second = computed(() => {
} else if (v.type == '1') { } else if (v.type == '1') {
return v.range.start + '-' + v.range.end return v.range.start + '-' + v.range.end
} else if (v.type == '2') { } else if (v.type == '2') {
return v.loop.start + '/' + v.loop.end return '*' + '/' + v.loop.end
} else if (v.type == '3') { } else if (v.type == '3') {
return v.appoint.length > 0 ? v.appoint.join(',') : '*' return v.appoint.length > 0 ? v.appoint.join(',') : '*'
} else { } else {
...@@ -231,7 +232,7 @@ const value_minute = computed(() => { ...@@ -231,7 +232,7 @@ const value_minute = computed(() => {
} else if (v.type == '1') { } else if (v.type == '1') {
return v.range.start + '-' + v.range.end return v.range.start + '-' + v.range.end
} else if (v.type == '2') { } else if (v.type == '2') {
return v.loop.start + '/' + v.loop.end return '*' + '/' + v.loop.end
} else if (v.type == '3') { } else if (v.type == '3') {
return v.appoint.length > 0 ? v.appoint.join(',') : '*' return v.appoint.length > 0 ? v.appoint.join(',') : '*'
} else { } else {
...@@ -245,7 +246,7 @@ const value_hour = computed(() => { ...@@ -245,7 +246,7 @@ const value_hour = computed(() => {
} else if (v.type == '1') { } else if (v.type == '1') {
return v.range.start + '-' + v.range.end return v.range.start + '-' + v.range.end
} else if (v.type == '2') { } else if (v.type == '2') {
return v.loop.start + '/' + v.loop.end return '*' + '/' + v.loop.end
} else if (v.type == '3') { } else if (v.type == '3') {
return v.appoint.length > 0 ? v.appoint.join(',') : '*' return v.appoint.length > 0 ? v.appoint.join(',') : '*'
} else { } else {
...@@ -259,7 +260,7 @@ const value_day = computed(() => { ...@@ -259,7 +260,7 @@ const value_day = computed(() => {
} else if (v.type == '1') { } else if (v.type == '1') {
return v.range.start + '-' + v.range.end return v.range.start + '-' + v.range.end
} else if (v.type == '2') { } else if (v.type == '2') {
return v.loop.start + '/' + v.loop.end return '*' + '/' + v.loop.end
} else if (v.type == '3') { } else if (v.type == '3') {
return v.appoint.length > 0 ? v.appoint.join(',') : '*' return v.appoint.length > 0 ? v.appoint.join(',') : '*'
} else if (v.type == '4') { } else if (v.type == '4') {
...@@ -275,7 +276,7 @@ const value_month = computed(() => { ...@@ -275,7 +276,7 @@ const value_month = computed(() => {
} else if (v.type == '1') { } else if (v.type == '1') {
return v.range.start + '-' + v.range.end return v.range.start + '-' + v.range.end
} else if (v.type == '2') { } else if (v.type == '2') {
return v.loop.start + '/' + v.loop.end return '*' + '/' + v.loop.end
} else if (v.type == '3') { } else if (v.type == '3') {
return v.appoint.length > 0 ? v.appoint.join(',') : '*' return v.appoint.length > 0 ? v.appoint.join(',') : '*'
} else { } else {
...@@ -334,10 +335,56 @@ watch( ...@@ -334,10 +335,56 @@ watch(
() => props.modelValue, () => props.modelValue,
() => { () => {
defaultValue.value = props.modelValue defaultValue.value = props.modelValue
cronLabel.value = parseCronExpression(props.modelValue)
} }
) )
const parseCronExpression = (cronExpression: string) => {
const res = cronExpression.split('*').length - 1
if (res >= 5) {
const aaa = cronExpression.split(' ')
for (let i = 0; i < aaa.length; i++) {
if (aaa[i] != '*') {
if (i == 0) {
if (aaa[i].length === 3) {
return `每${aaa[i][2]}秒执行一次`
} else {
return `每${aaa[i][2] + aaa[i][3]}秒执行一次`
}
} else if (i == 1) {
if (aaa[i].length === 3) {
return `每${aaa[i][2]}分钟执行一次`
} else {
return `每${aaa[i][2] + aaa[i][3]}分钟执行一次`
}
} else if (i == 2) {
if (aaa[i].length === 3) {
return `每${aaa[i][2]}小时执行一次`
} else {
return `每${aaa[i][2] + aaa[i][3]}小时执行一次`
}
} else if (i == 3) {
if (aaa[i].length === 3) {
return `每${aaa[i][2]}天执行一次`
} else {
return `每${aaa[i][2] + aaa[i][3]}天执行一次`
}
} else if (i == 4) {
if (aaa[i].length === 3) {
return `每${aaa[i][2]}月执行一次`
} else {
return `每${aaa[i][2] + aaa[i][3]}月执行一次`
}
}
}
}
} else if(cronExpression == '* * * * * *'){
return `请生成表达式`
}
}
onMounted(() => { onMounted(() => {
defaultValue.value = props.modelValue if(props.modelValue != '* * * * * *'){
cronLabel.value = parseCronExpression(props.modelValue)
}
}) })
const emit = defineEmits(['update:modelValue']) const emit = defineEmits(['update:modelValue'])
const select = ref() const select = ref()
...@@ -497,8 +544,54 @@ const submit = () => { ...@@ -497,8 +544,54 @@ const submit = () => {
' ' + ' ' +
value_week.value + value_week.value +
year year
const res = defaultValue.value.split('*').length - 1
const cronRes = defaultValue.value.split(' ')
// cronLabel
for (let i = 0; i < cronRes.length; i++) {
if (cronRes[i] != '*') {
if (i == 0) {
if (cronRes[i].length === 3) {
cronLabel.value = `每${cronRes[i][2]}秒执行一次`
} else {
cronLabel.value = `每${cronRes[i][2] + cronRes[i][3]}秒执行一次`
}
} else if (i == 1) {
if (cronRes[i].length === 3) {
cronLabel.value = `每${cronRes[i][2]}分钟执行一次`
} else {
cronLabel.value = `每${cronRes[i][2] + cronRes[i][3]}分钟执行一次`
}
} else if (i == 2) {
if (cronRes[i].length === 3) {
cronLabel.value = `每${cronRes[i][2]}小时执行一次`
} else {
cronLabel.value = `每${cronRes[i][2] + cronRes[i][3]}小时执行一次`
}
} else if (i == 3) {
if (cronRes[i].length === 3) {
cronLabel.value = `每${cronRes[i][2]}天执行一次`
} else {
cronLabel.value = `每${cronRes[i][2] + cronRes[i][3]}天执行一次`
}
} else if (i == 4) {
if (cronRes[i].length === 3) {
cronLabel.value = `每${cronRes[i][2]}月执行一次`
} else {
cronLabel.value = `每${cronRes[i][2] + cronRes[i][3]}月执行一次`
}
}
}
}
if (res >= 5) {
emit('update:modelValue', defaultValue.value) emit('update:modelValue', defaultValue.value)
dialogVisible.value = false dialogVisible.value = false
} else {
ElMessage.warning('cron表达式错误,只可设置一个间隔')
emit('update:modelValue', defaultValue.value)
}
} }
const inputChange = () => { const inputChange = () => {
...@@ -507,7 +600,7 @@ const inputChange = () => { ...@@ -507,7 +600,7 @@ const inputChange = () => {
</script> </script>
<template> <template>
<div> <div>
<el-input v-model="defaultValue" disabled class="input-with-select" v-bind="$attrs" @input="inputChange"> <el-input v-model="cronLabel" disabled class="input-with-select" v-bind="$attrs" @input="inputChange">
</el-input> </el-input>
<div class="sc-cron"> <div class="sc-cron">
<el-tabs> <el-tabs>
...@@ -515,16 +608,16 @@ const inputChange = () => { ...@@ -515,16 +608,16 @@ const inputChange = () => {
<template #label> <template #label>
<div class="sc-cron-num"> <div class="sc-cron-num">
<h2></h2> <h2></h2>
<h4>{{ value_second }}</h4> <h4 id="second_tab">{{ value_second }}</h4>
</div> </div>
</template> </template>
<el-form> <el-form>
<el-form-item label="类型" > <el-form-item label="类型">
<el-radio-group v-model="cronValue.second.type"> <el-radio-group v-model="cronValue.second.type">
<el-radio-button value="0">任意值</el-radio-button> <el-radio-button value="0">重置</el-radio-button>
<el-radio-button value="1">范围</el-radio-button> <!-- <el-radio-button value="1">范围</el-radio-button> -->
<el-radio-button value="2">间隔</el-radio-button> <el-radio-button value="2" id="cron-second-frequency">间隔</el-radio-button>
<el-radio-button value="3">指定</el-radio-button> <!-- <el-radio-button value="3">指定</el-radio-button> -->
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
<el-form-item v-if="cronValue.second.type == '1'" label="范围" class="m-t-4"> <el-form-item v-if="cronValue.second.type == '1'" label="范围" class="m-t-4">
...@@ -533,9 +626,10 @@ const inputChange = () => { ...@@ -533,9 +626,10 @@ const inputChange = () => {
<el-input-number v-model="cronValue.second.range.end" :max="59" :min="0" controls-position="right" /> <el-input-number v-model="cronValue.second.range.end" :max="59" :min="0" controls-position="right" />
</el-form-item> </el-form-item>
<el-form-item v-if="cronValue.second.type == '2'" label="间隔" class="m-t-4"> <el-form-item v-if="cronValue.second.type == '2'" label="间隔" class="m-t-4">
<el-input-number v-model="cronValue.second.loop.start" :max="59" :min="0" controls-position="right" /> <!-- <el-input-number v-model="cronValue.second.loop.start" :max="59" :min="0" controls-position="right" />
秒开始,每 秒开始,每 -->
<el-input-number v-model="cronValue.second.loop.end" :max="59" :min="0" controls-position="right" />
<el-input-number v-model="cronValue.second.loop.end" :max="59" :min="5" controls-position="right" />
秒执行一次 秒执行一次
</el-form-item> </el-form-item>
<el-form-item v-if="cronValue.second.type == '3'" label="指定" class="m-t-4"> <el-form-item v-if="cronValue.second.type == '3'" label="指定" class="m-t-4">
...@@ -549,16 +643,16 @@ const inputChange = () => { ...@@ -549,16 +643,16 @@ const inputChange = () => {
<template #label> <template #label>
<div class="sc-cron-num"> <div class="sc-cron-num">
<h2>分钟</h2> <h2>分钟</h2>
<h4>{{ value_minute }}</h4> <h4 id="minute_tab">{{ value_minute }}</h4>
</div> </div>
</template> </template>
<el-form> <el-form>
<el-form-item label="类型"> <el-form-item label="类型">
<el-radio-group v-model="cronValue.minute.type"> <el-radio-group v-model="cronValue.minute.type">
<el-radio-button value="0">任意值</el-radio-button> <el-radio-button value="0">重置</el-radio-button>
<el-radio-button value="1">范围</el-radio-button> <!-- <el-radio-button value="1">范围</el-radio-button> -->
<el-radio-button value="2">间隔</el-radio-button> <el-radio-button value="2" id="cron-minute-frequency">间隔</el-radio-button>
<el-radio-button value="3">指定</el-radio-button> <!-- <el-radio-button value="3">指定</el-radio-button> -->
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
<el-form-item v-if="cronValue.minute.type == '1'" label="范围" class="m-t-4"> <el-form-item v-if="cronValue.minute.type == '1'" label="范围" class="m-t-4">
...@@ -567,8 +661,9 @@ const inputChange = () => { ...@@ -567,8 +661,9 @@ const inputChange = () => {
<el-input-number v-model="cronValue.minute.range.end" :max="59" :min="0" controls-position="right" /> <el-input-number v-model="cronValue.minute.range.end" :max="59" :min="0" controls-position="right" />
</el-form-item> </el-form-item>
<el-form-item v-if="cronValue.minute.type == '2'" label="间隔" class="m-t-4"> <el-form-item v-if="cronValue.minute.type == '2'" label="间隔" class="m-t-4">
<el-input-number v-model="cronValue.minute.loop.start" :max="59" :min="0" controls-position="right" /> <!-- <el-input-number v-model="cronValue.minute.loop.start" :max="59" :min="0" controls-position="right" />
分钟开始,每 分钟开始, -->
<el-input-number v-model="cronValue.minute.loop.end" :max="59" :min="0" controls-position="right" /> <el-input-number v-model="cronValue.minute.loop.end" :max="59" :min="0" controls-position="right" />
分钟执行一次 分钟执行一次
</el-form-item> </el-form-item>
...@@ -583,16 +678,16 @@ const inputChange = () => { ...@@ -583,16 +678,16 @@ const inputChange = () => {
<template #label> <template #label>
<div class="sc-cron-num"> <div class="sc-cron-num">
<h2>小时</h2> <h2>小时</h2>
<h4>{{ value_hour }}</h4> <h4 id="hour_tab">{{ value_hour }}</h4>
</div> </div>
</template> </template>
<el-form> <el-form>
<el-form-item label="类型"> <el-form-item label="类型">
<el-radio-group v-model="cronValue.hour.type"> <el-radio-group v-model="cronValue.hour.type">
<el-radio-button value="0">任意值</el-radio-button> <el-radio-button value="0">重置</el-radio-button>
<el-radio-button value="1">范围</el-radio-button> <!-- <el-radio-button value="1">范围</el-radio-button> -->
<el-radio-button value="2">间隔</el-radio-button> <el-radio-button value="2" id="cron-hour-frequency">间隔</el-radio-button>
<el-radio-button value="3">指定</el-radio-button> <!-- <el-radio-button value="3">指定</el-radio-button> -->
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
<el-form-item v-if="cronValue.hour.type == '1'" label="范围" class="m-t-4"> <el-form-item v-if="cronValue.hour.type == '1'" label="范围" class="m-t-4">
...@@ -601,8 +696,9 @@ const inputChange = () => { ...@@ -601,8 +696,9 @@ const inputChange = () => {
<el-input-number v-model="cronValue.hour.range.end" :max="23" :min="0" controls-position="right" /> <el-input-number v-model="cronValue.hour.range.end" :max="23" :min="0" controls-position="right" />
</el-form-item> </el-form-item>
<el-form-item v-if="cronValue.hour.type == '2'" label="间隔" class="m-t-4"> <el-form-item v-if="cronValue.hour.type == '2'" label="间隔" class="m-t-4">
<el-input-number v-model="cronValue.hour.loop.start" :max="23" :min="0" controls-position="right" /> <!-- <el-input-number v-model="cronValue.hour.loop.start" :max="23" :min="0" controls-position="right" />
小时开始,每 小时开始, -->
<el-input-number v-model="cronValue.hour.loop.end" :max="23" :min="0" controls-position="right" /> <el-input-number v-model="cronValue.hour.loop.end" :max="23" :min="0" controls-position="right" />
小时执行一次 小时执行一次
</el-form-item> </el-form-item>
...@@ -617,16 +713,16 @@ const inputChange = () => { ...@@ -617,16 +713,16 @@ const inputChange = () => {
<template #label> <template #label>
<div class="sc-cron-num"> <div class="sc-cron-num">
<h2></h2> <h2></h2>
<h4>{{ value_day }}</h4> <h4 id="day_tab">{{ value_day }}</h4>
</div> </div>
</template> </template>
<el-form> <el-form>
<el-form-item label="类型"> <el-form-item label="类型">
<el-radio-group v-model="cronValue.day.type"> <el-radio-group v-model="cronValue.day.type">
<el-radio-button value="0">任意值</el-radio-button> <el-radio-button value="0">重置</el-radio-button>
<el-radio-button value="1">范围</el-radio-button> <!-- <el-radio-button value="1">范围</el-radio-button> -->
<el-radio-button value="2">间隔</el-radio-button> <el-radio-button value="2" id="cron-day-frequency">间隔</el-radio-button>
<el-radio-button value="3">指定</el-radio-button> <!-- <el-radio-button value="3">指定</el-radio-button> -->
<!-- <el-radio-button value="4">本月最后一天</el-radio-button> <!-- <el-radio-button value="4">本月最后一天</el-radio-button>
<el-radio-button value="5">不指定</el-radio-button> --> <el-radio-button value="5">不指定</el-radio-button> -->
</el-radio-group> </el-radio-group>
...@@ -637,8 +733,9 @@ const inputChange = () => { ...@@ -637,8 +733,9 @@ const inputChange = () => {
<el-input-number v-model="cronValue.day.range.end" :max="31" :min="1" controls-position="right" /> <el-input-number v-model="cronValue.day.range.end" :max="31" :min="1" controls-position="right" />
</el-form-item> </el-form-item>
<el-form-item v-if="cronValue.day.type == '2'" label="间隔" class="m-t-4"> <el-form-item v-if="cronValue.day.type == '2'" label="间隔" class="m-t-4">
<el-input-number v-model="cronValue.day.loop.start" :max="31" :min="1" controls-position="right" /> <!-- <el-input-number v-model="cronValue.day.loop.start" :max="31" :min="1" controls-position="right" />
号开始,每 号开始, -->
<el-input-number v-model="cronValue.day.loop.end" :max="31" :min="1" controls-position="right" /> <el-input-number v-model="cronValue.day.loop.end" :max="31" :min="1" controls-position="right" />
天执行一次 天执行一次
</el-form-item> </el-form-item>
...@@ -653,16 +750,16 @@ const inputChange = () => { ...@@ -653,16 +750,16 @@ const inputChange = () => {
<template #label> <template #label>
<div class="sc-cron-num"> <div class="sc-cron-num">
<h2></h2> <h2></h2>
<h4>{{ value_month }}</h4> <h4 id="month_tab">{{ value_month }}</h4>
</div> </div>
</template> </template>
<el-form> <el-form>
<el-form-item label="类型"> <el-form-item label="类型">
<el-radio-group v-model="cronValue.month.type"> <el-radio-group v-model="cronValue.month.type">
<el-radio-button value="0">任意值</el-radio-button> <el-radio-button value="0">重置</el-radio-button>
<el-radio-button value="1">范围</el-radio-button> <!-- <el-radio-button value="1">范围</el-radio-button> -->
<el-radio-button value="2">间隔</el-radio-button> <el-radio-button value="2" id="cron-month-frequency">间隔</el-radio-button>
<el-radio-button value="3">指定</el-radio-button> <!-- <el-radio-button value="3">指定</el-radio-button> -->
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
<el-form-item v-if="cronValue.month.type == '1'" label="范围" class="m-t-4"> <el-form-item v-if="cronValue.month.type == '1'" label="范围" class="m-t-4">
...@@ -671,8 +768,9 @@ const inputChange = () => { ...@@ -671,8 +768,9 @@ const inputChange = () => {
<el-input-number v-model="cronValue.month.range.end" :max="12" :min="1" controls-position="right" /> <el-input-number v-model="cronValue.month.range.end" :max="12" :min="1" controls-position="right" />
</el-form-item> </el-form-item>
<el-form-item v-if="cronValue.month.type == '2'" label="间隔" class="m-t-4"> <el-form-item v-if="cronValue.month.type == '2'" label="间隔" class="m-t-4">
<el-input-number v-model="cronValue.month.loop.start" :max="12" :min="1" controls-position="right" /> <!-- <el-input-number v-model="cronValue.month.loop.start" :max="12" :min="1" controls-position="right" />
月开始,每 月开始, -->
<el-input-number v-model="cronValue.month.loop.end" :max="12" :min="1" controls-position="right" /> <el-input-number v-model="cronValue.month.loop.end" :max="12" :min="1" controls-position="right" />
月执行一次 月执行一次
</el-form-item> </el-form-item>
...@@ -683,7 +781,8 @@ const inputChange = () => { ...@@ -683,7 +781,8 @@ const inputChange = () => {
</el-form-item> </el-form-item>
</el-form> </el-form>
</el-tab-pane> </el-tab-pane>
<el-tab-pane>
<!-- <el-tab-pane>
<template #label> <template #label>
<div class="sc-cron-num"> <div class="sc-cron-num">
<h2>周</h2> <h2>周</h2>
...@@ -694,12 +793,12 @@ const inputChange = () => { ...@@ -694,12 +793,12 @@ const inputChange = () => {
<el-form> <el-form>
<el-form-item label="类型"> <el-form-item label="类型">
<el-radio-group v-model="cronValue.week.type"> <el-radio-group v-model="cronValue.week.type">
<el-radio-button value="0">任意值</el-radio-button> <el-radio-button value="0">重置</el-radio-button>
<el-radio-button value="1">范围</el-radio-button> <el-radio-button value="1">范围</el-radio-button>
<el-radio-button value="2">间隔</el-radio-button> <el-radio-button value="2">间隔</el-radio-button>
<el-radio-button value="3">指定</el-radio-button> <el-radio-button value="3">指定</el-radio-button>
<!-- <el-radio-button value="4">本月最后一周</el-radio-button> <el-radio-button value="4">本月最后一周</el-radio-button>
<el-radio-button value="5">不指定</el-radio-button> --> <el-radio-button value="5">不指定</el-radio-button>
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
<el-form-item v-if="cronValue.week.type == '1'" label="范围" class="m-t-4"> <el-form-item v-if="cronValue.week.type == '1'" label="范围" class="m-t-4">
...@@ -732,7 +831,8 @@ const inputChange = () => { ...@@ -732,7 +831,8 @@ const inputChange = () => {
</el-form-item> </el-form-item>
</el-form> </el-form>
</el-form> </el-form>
</el-tab-pane> </el-tab-pane> -->
<!-- <el-tab-pane> <!-- <el-tab-pane>
<template #label> <template #label>
<div class="sc-cron-num"> <div class="sc-cron-num">
...@@ -824,19 +924,18 @@ const inputChange = () => { ...@@ -824,19 +924,18 @@ const inputChange = () => {
background-color: var(--el-fill-color-blank); background-color: var(--el-fill-color-blank);
} }
.el-radio-button__inner{ .el-radio-button__inner {
background: #1d5484; background: #1d5484;
} }
</style> </style>
<style> <style>
.el-input.is-disabled .el-input__wrapper{ .el-input.is-disabled .el-input__wrapper {
background: #1d5484; background: #1d5484;
box-shadow: 0,0,0,0px; box-shadow: 0, 0, 0, 0px;
} }
.el-radio-button__inner{
.el-radio-button__inner {
background: #1d5484; background: #1d5484;
color: #fff; color: #fff;
border: none; border: none;
......
<template> <template>
<el-dialog v-model="exportDialogVisible" title="导出" width="250" center align-center @close="close" draggable> <div>
<div class="text-center"> <el-dialog v-model="exportDialogVisible" v-loading="fullscreenLoading" title="导出" width="500" center align-center
<span style="color: #fff;font-size: 15px;">确定导出吗?</span> @close="close" draggable>
</div> <el-form class="formStyle" :rules="rules" ref="formRef" :model="exportObject">
<el-form-item prop="timeValue">
<el-date-picker v-model="exportObject.timeValue" type="datetimerange" format="YYYY-MM-DD HH:mm:ss"
start-placeholder="开始时间" end-placeholder="结束时间" date-format="YYYY/MM/DD ddd" time-format="A hh:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss" id="export-time-picker"/>
</el-form-item>
<el-form-item prop="spiderType">
<el-checkbox-group v-model="exportObject.spiderType" class="checkboxStyle">
<el-checkbox label="DSN数据" value="dsn" id="dsn-checkbox"/>
<el-checkbox label="ITU数据" value="itu" id="itu-checkbox"/>
<el-checkbox label="ST数据" value="spaceTrack" id="st-checkbox"/>
<el-checkbox label="ESA数据" value="esa" id="st-checkbox"/>
</el-checkbox-group>
</el-form-item>
</el-form>
<template #footer> <template #footer>
<div class="dialog-footer"> <div class="dialog-footer">
<el-button @click="close">取消</el-button> <el-button @click="close">取消</el-button>
...@@ -12,32 +27,281 @@ ...@@ -12,32 +27,281 @@
</div> </div>
</template> </template>
</el-dialog> </el-dialog>
</div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, watch } from 'vue' import { ref, watch } from 'vue'
import { defineProps } from 'vue'; import { defineProps } from 'vue';
import axios from 'axios'
import { ElLoading, ElMessage } from 'element-plus'
import type { FormInstance, FormRules } from 'element-plus'
interface RuleForm {
//表单字段的类型
timeValue: string
spiderType: Array<string>
}
const formRef = ref<FormInstance>()
const rules = ref<FormRules<RuleForm>>({
timeValue: [
{
// type: 'date',
required: true,
message: '请选择时间段',
trigger: 'change',
validator: (rule, value, callback) => {
// 检查是否为数组且长度为2
if (!Array.isArray(value) || value.length !== 2) {
callback(new Error('请选择完整的时间段'));
} else {
// 检查日期格式是否有效(可选)
const isValid = value.every(date => /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/.test(date));
if (!isValid) {
callback(new Error('时间段格式不正确'));
} else {
callback();
}
}
}
},
],
spiderType: [
{
type: 'array',
required: true,
message: '请至少选择一种数据类型',
trigger: 'change',
},
]
})
//表单绑定的字段
const exportObject = ref({
timeValue: [],
spiderType: []
})
const fullscreenLoading = ref(false)
const props = defineProps({ const props = defineProps({
dialogVisible: { dialogVisible: {
type: Boolean, type: Boolean,
default: false default: false
},
mode: {
type: String,
default: '1'
} }
}) })
const emit = defineEmits(['update:dialogVisible', 'confirm']) const emit = defineEmits(['update:dialogVisible'])
const exportDialogVisible = ref(props.dialogVisible) const exportDialogVisible = ref(props.dialogVisible)
// 导出方法 // 导出方法
const handleExport = () => { // const handleExport = async () => {
emit('confirm') // // saveToFile('666')
exportDialogVisible.value = false // if (!formRef.value) return
} // await formRef.value.validate(async (valid, fields) => {
// console.log('开始校验');
// if (valid) {
// console.log('校验通过');
// const loading = ElLoading.service({
// lock: true,
// text: 'Loading',
// background: 'rgba(0, 0, 0, 0.7)',
// })
// try {
// const res = await axios.post(
// '/api/export/downloadFile',
// { times: exportObject.value.timeValue, filters: exportObject.value.spiderType },
// {
// responseType: 'blob',
// headers: {
// 'Token': localStorage.getItem('Admin-Token') || 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiZXhwIjoxNzU5MjA5NzM4fQ.6hVko0EQTuz7OYjXEafZYLpmkVEyiLhZ8aWHi0Pni_s', // 根据你的 token 存储方式调整
// },
// }
// )
// // 获取文件名
// // const disposition = res.headers['content-disposition']
// // let fileName = 'export.zip'
// // if (disposition) {
// // const match = disposition.match(/filename="?([^"]+)"?/)
// // if (match) fileName = decodeURIComponent(match[1])
// // }
// // const blob = new Blob([res.data], { type: 'application/zip' })
// // const url = window.URL.createObjectURL(blob)
// // const a = document.createElement('a')
// // a.href = url
// // a.download = fileName
// // document.body.appendChild(a)
// // a.click()
// // document.body.removeChild(a)
// // window.URL.revokeObjectURL(url)
// loading.close()
// // close()
// // ElMessage.success('导出成功');
// } catch (e) {
// console.error('导出失败', e)
// close()
// ElMessage.error('导出失败');
// }
// } else {
// console.log('校验不通过');
// }
// })
// }
// const handleExport = async () => {
// if (!formRef.value) return
// await formRef.value.validate(async (valid, fields) => {
// if (valid) {
// const loading = ElLoading.service({
// lock: true,
// text: '正在导出ZIP文件...',
// background: 'rgba(0, 0, 0, 0.7)',
// })
// try {
// const res = await axios.post(
// '/api/export/downloadFile',
// { times: exportObject.value.timeValue, filters: exportObject.value.spiderType },
// {
// responseType: 'blob', // 关键:指定接收二进制数据
// headers: {
// 'Token': localStorage.getItem('Admin-Token') || '',
// },
// }
// )
// // 调用保存方法,传入二进制数据和响应头
// saveZipFile(res.data, res.headers)
// loading.close()
// ElMessage.success('ZIP文件导出成功');
// } catch (e) {
// console.error('导出失败', e)
// loading.close()
// ElMessage.error('ZIP文件导出失败,请重试');
// }
// }
// })
// }
// const saveToFile = async (data:any)=> {
// const options = {
// suggestedName: "example.txt",
// types: [
// {
// description: 'Text Files',
// accept: { 'text/plain': ['.txt'] }
// }
// ]
// };
// try {
// const fileHandle = await window.showSaveFilePicker(options); // 打开保存对话框
// const writable = await fileHandle.createWritable(); // 创建可写流
// await writable.write(data); // 写入数据
// await writable.close(); // 关闭流
// console.log("文件保存成功!");
// } catch (error) {
// console.error("保存失败:", error);
// }
// }
const handleExport = async () => {
if (!formRef.value) return;
try {
// 1. 先在用户点击时获取保存文件句柄(关键:在用户手势中执行)
const fileHandle = await getSaveFileHandle();
if (!fileHandle) {
ElMessage.info('已取消保存');
return;
}
// 2. 执行表单验证
await formRef.value.validate(async (valid, fields) => {
if (valid) {
const loading = ElLoading.service({
lock: true,
text: '正在导出文件...',
background: 'rgba(0, 0, 0, 0.7)',
});
try {
// 3. 请求文件数据
const res = await axios.post(
'/api/export/downloadFile',
{ times: exportObject.value.timeValue, filters: exportObject.value.spiderType },
{
responseType: 'blob',
headers: {
'Token': localStorage.getItem('Admin-Token') || '',
},
}
);
// 4. 使用之前获取的句柄保存文件
await saveToSelectedLocation(res.data, fileHandle);
loading.close();
ElMessage.success('数据导出成功');
close()
} catch (e) {
console.error('导出失败', e);
loading.close();
ElMessage.error('ZIP文件导出失败,请重试');
}
}
});
} catch (error) {
console.error(error);
// ElMessage.error('无法打开保存对话框,请使用浏览器默认下载');
}
};
// 获取用户选择的保存位置
const getSaveFileHandle = async () => {
// 检查浏览器是否支持
if (!(window as any).showSaveFilePicker) {
ElMessage.warning('您的浏览器不支持选择保存位置,请使用最新版Chrome或Edge浏览器');
return null;
}
try {
// 弹出保存对话框(必须在用户直接交互中调用)
return await (window as any).showSaveFilePicker({
suggestedName: 'export.zip',
types: [{
description: 'ZIP压缩文件',
accept: { 'application/zip': ['.zip'] },
}],
excludeAcceptAllOption: true
});
} catch (error: any) {
// 用户取消选择时也会触发错误,这里视为正常取消
if (error.name !== 'AbortError') {
console.error('获取保存位置失败', error);
}
return null;
}
};
// 保存数据到用户选择的位置
const saveToSelectedLocation = async (blobData: any, fileHandle: any) => {
if (!(blobData instanceof Blob) || blobData.size === 0) {
throw new Error('无效的ZIP文件数据');
}
// 将blob转换为可写数据
const arrayBuffer = await blobData.arrayBuffer();
// 写入到用户选择的文件
const writable = await fileHandle.createWritable();
await writable.write(arrayBuffer);
await writable.close();
};
// 关闭弹窗的方法 // 关闭弹窗的方法
const close = () => { const close = () => {
exportObject.value = {
timeValue: [],
spiderType: []
}
formRef.value?.clearValidate()
exportDialogVisible.value = false exportDialogVisible.value = false
} }
// 监听父组件传过来的值 // 监听父组件传过来的值
...@@ -52,4 +316,22 @@ watch(() => exportDialogVisible.value, ...@@ -52,4 +316,22 @@ watch(() => exportDialogVisible.value,
emit('update:dialogVisible', newVal) emit('update:dialogVisible', newVal)
} }
) )
</script> </script>
<style lang="scss" scoped>
.formStyle {
text-align: center;
display: flex;
flex-direction: column;
align-items: center;
}
.checkboxStyle {
display: flex;
gap: 30px;
/* 复选框之间的间距 */
}
</style>
\ No newline at end of file
<!-- 基于 ruoyi-vue3 的 Pagination 重构,核心是简化无用的属性,并使用 ts 重写 --> <!-- 基于 ruoyi-vue3 的 Pagination 重构,核心是简化无用的属性,并使用 ts 重写 -->
<template> <template>
<el-config-provider :locale="zhCn">
<el-pagination <el-pagination
v-show="total > 0" v-show="total > 0"
v-model:current-page="currentPage" v-model:current-page="currentPage"
...@@ -9,15 +10,27 @@ ...@@ -9,15 +10,27 @@
:pager-count="pagerCount" :pager-count="pagerCount"
:total="total" :total="total"
class="mt-4" class="mt-4"
layout="total, sizes, prev, pager, next, jumper" layout="total,sizes, prev, pager, next, jumper"
@size-change="handleSizeChange" @size-change="handleSizeChange"
@current-change="handleCurrentChange" @current-change="handleCurrentChange"
jumper-text="跳转"
/> />
</el-config-provider>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed } from 'vue' import { computed } from 'vue'
// ElConfigProvider 组件
import { ElConfigProvider } from 'element-plus';
// 引入中文包
import zhCn from 'element-plus/es/locale/lang/zh-cn';
defineOptions({ name: 'Pagination' }) defineOptions({ name: 'Pagination' })
// 更改分页文字
zhCn.el.pagination.total = '共 `{total} 条`';
zhCn.el.pagination.goto = '跳至';
zhCn.el.pagination.pagesize = '条/页';
zhCn.el.pagination.pageClassifier = '页';
const props = defineProps({ const props = defineProps({
// 总条目数 // 总条目数
total: { total: {
......
<template>
<div>
<span>关键词搜索:</span>
<el-input v-model="input2" style="width: 240px" placeholder="Search" :prefix-icon="Search" />
</div>
</template>
<script setup lang="ts">
</script>
\ No newline at end of file
import { createRouter, createWebHistory, type RouteRecordRaw } from 'vue-router' import { createRouter, createWebHashHistory, createWebHistory, type RouteRecordRaw } from 'vue-router'
import Layout from '@/layout/index.vue' import Layout from '@/layout/index.vue'
...@@ -40,12 +40,12 @@ export const constantRoutes: Array<RouteRecordRaw> = [ ...@@ -40,12 +40,12 @@ export const constantRoutes: Array<RouteRecordRaw> = [
{ {
path: '', path: '',
component: Layout, component: Layout,
redirect: '/dashboard', redirect: '/osStatus/list',
hidden: true, hidden: true,
children: [{ children: [{
path: '/dashboard', path: '/osStatus/list',
name: 'Dashboard', name: 'statusMonitor',
component: () => import('@/views/home/home.vue'), component: () => import('@/views/os-status/index.vue'),
meta: { title: '控制台', icon: 'Monitor' } meta: { title: '控制台', icon: 'Monitor' }
}] }]
} as unknown as RouteRecordRaw, } as unknown as RouteRecordRaw,
...@@ -88,19 +88,7 @@ export const constantRoutes: Array<RouteRecordRaw> = [ ...@@ -88,19 +88,7 @@ export const constantRoutes: Array<RouteRecordRaw> = [
] ]
} as unknown as RouteRecordRaw, } as unknown as RouteRecordRaw,
{
path: '/spider',
component: Layout,
hidden: true,
children: [
{
path: 'list',
name: 'spider-list',
component: () => import('@/views/spider/index.vue'),
meta: { title: 'Spider', icon: 'List' }
}
]
} as unknown as RouteRecordRaw,
{ {
path: '/schedule', path: '/schedule',
...@@ -186,13 +174,7 @@ export const constantRoutes: Array<RouteRecordRaw> = [ ...@@ -186,13 +174,7 @@ export const constantRoutes: Array<RouteRecordRaw> = [
meta: { title: "Spider日志", icon: "Notebook" }, meta: { title: "Spider日志", icon: "Notebook" },
hidden: true hidden: true
}, },
{
path: "job",
name: "logs-project-spider-job",
component: () => import("@/views/log/LogJob.vue"),
meta: { title: "任务日志", icon: "Notebook" },
hidden: true
}
] ]
} as unknown as RouteRecordRaw, } as unknown as RouteRecordRaw,
...@@ -216,14 +198,13 @@ export const constantRoutes: Array<RouteRecordRaw> = [ ...@@ -216,14 +198,13 @@ export const constantRoutes: Array<RouteRecordRaw> = [
{ {
path: "/osStatus", path: "/osStatus",
// redirect: "list",
component: Layout, component: Layout,
children: [ children: [
{ {
path: "list", path: "list",
name: "statusMonitor", name: "statusMonitor",
component: () => import("@/views/os-status/index.vue"), component: () => import("@/views/os-status/index.vue"),
meta: { title: "状态监控", icon: "Key" } meta: { title: "状态监控", icon: "Monitor" }
}, },
] ]
} as unknown as RouteRecordRaw, } as unknown as RouteRecordRaw,
...@@ -237,7 +218,7 @@ export const constantRoutes: Array<RouteRecordRaw> = [ ...@@ -237,7 +218,7 @@ export const constantRoutes: Array<RouteRecordRaw> = [
path: "list", path: "list",
name: "taskInformation", name: "taskInformation",
component: () => import("@/views/os-taskInformation/index.vue"), component: () => import("@/views/os-taskInformation/index.vue"),
meta: { title: "任务信息", icon: "Key" } meta: { title: "任务信息", icon: "List" }
}, },
] ]
} as unknown as RouteRecordRaw, } as unknown as RouteRecordRaw,
...@@ -250,7 +231,7 @@ export const constantRoutes: Array<RouteRecordRaw> = [ ...@@ -250,7 +231,7 @@ export const constantRoutes: Array<RouteRecordRaw> = [
path: "list", path: "list",
name: "taskRecord", name: "taskRecord",
component: () => import("@/views/os-taskRecord/index.vue"), component: () => import("@/views/os-taskRecord/index.vue"),
meta: { title: "任务执行记录", icon: "Key" } meta: { title: "任务执行记录", icon: "Notebook" }
}, },
] ]
} as unknown as RouteRecordRaw, } as unknown as RouteRecordRaw,
...@@ -263,7 +244,7 @@ export const constantRoutes: Array<RouteRecordRaw> = [ ...@@ -263,7 +244,7 @@ export const constantRoutes: Array<RouteRecordRaw> = [
path: "list", path: "list",
name: "dataDisplay", name: "dataDisplay",
component: () => import("@/views/os-dataDisplay/index.vue"), component: () => import("@/views/os-dataDisplay/index.vue"),
meta: { title: "数据展示", icon: "Key" } meta: { title: "数据展示", icon: "DataAnalysis" }
}, },
] ]
} as unknown as RouteRecordRaw, } as unknown as RouteRecordRaw,
...@@ -289,7 +270,7 @@ export const constantRoutes: Array<RouteRecordRaw> = [ ...@@ -289,7 +270,7 @@ export const constantRoutes: Array<RouteRecordRaw> = [
path: "list", path: "list",
name: "osSystem", name: "osSystem",
component: () => import("@/views/os-system/index.vue"), component: () => import("@/views/os-system/index.vue"),
meta: { title: "系统管理", icon: "Key" } meta: { title: "系统管理", icon: "Setting" }
}, },
] ]
} as unknown as RouteRecordRaw, } as unknown as RouteRecordRaw,
...@@ -315,14 +296,14 @@ export const constantRoutes: Array<RouteRecordRaw> = [ ...@@ -315,14 +296,14 @@ export const constantRoutes: Array<RouteRecordRaw> = [
] ]
const router = createRouter({ const router = createRouter({
history: createWebHistory(), history: createWebHashHistory(),
routes: constantRoutes, routes: constantRoutes,
scrollBehavior: () => ({ top: 0 }) scrollBehavior: () => ({ top: 0 })
}) })
export function resetRouter() { export function resetRouter() {
const newRouter = createRouter({ const newRouter = createRouter({
history: createWebHistory(), history: createWebHashHistory(),
routes: constantRoutes, routes: constantRoutes,
scrollBehavior: () => ({ top: 0 }) scrollBehavior: () => ({ top: 0 })
}) })
......
...@@ -63,3 +63,37 @@ ...@@ -63,3 +63,37 @@
--el-segmented-item-hover-color: #FFFFFF; --el-segmented-item-hover-color: #FFFFFF;
--el-segmented-item-active-bg-color: #1977d5d7; --el-segmented-item-active-bg-color: #1977d5d7;
} }
.el-dialog {
background-image: url("@/assets/picture/dialog1.png");
background-size: 100% 100%;
background-repeat: no-repeat;
.el-form-item__label {
color: #ffffff;
}
.el-dialog__header {
color: #ffffff;
}
.el-dialog__title {
color: #ffffff;
}
.el-textarea__inner {
background-color: #1d5484;
color: #ffffff;
box-shadow: none;
}
}
// .el-date-editor {
// .el-date-editor {
// color: #ffffff;
// }
// }
.el-date-editor .el-range-input{
color: #ffffff;
}
\ No newline at end of file
import Cookies from 'js-cookie' // import Cookies from 'js-cookie'
const TokenKey = 'Admin-Token' const TokenKey = 'Admin-Token'
export function getToken(): string { export function getToken(): string {
return Cookies.get(TokenKey) || '' return localStorage.getItem(TokenKey) || ''
} }
export function setToken(token: string) { export function setToken(token: string) {
return Cookies.set(TokenKey, token) return localStorage.setItem(TokenKey, token)
} }
export function removeToken() { export function removeToken() {
return Cookies.remove(TokenKey) return localStorage.removeItem(TokenKey)
} }
...@@ -7,7 +7,7 @@ const instance = axios.create({ ...@@ -7,7 +7,7 @@ const instance = axios.create({
baseURL: import.meta.env.VITE_APP_BASE_API || '', baseURL: import.meta.env.VITE_APP_BASE_API || '',
timeout: 10000, // 毫秒 timeout: 10000, // 毫秒
headers: { headers: {
'Content-Type': 'application/json; charset=utf-8' 'Content-Type': 'application/zip; charset=utf-8'
} }
}) })
...@@ -88,10 +88,11 @@ export interface QueryParams { ...@@ -88,10 +88,11 @@ export interface QueryParams {
size?: number size?: number
project?: string project?: string
spider?: string spider?: string
scrapydServerId: string scrapydServerId?: string
order_prop?: string order_prop?: string
order_type?: string order_type?: string
[key: string]: any status?: any
job?:any
} }
// 请求函数 // 请求函数
...@@ -119,4 +120,12 @@ export interface UserQueryParams { ...@@ -119,4 +120,12 @@ export interface UserQueryParams {
spider?: string spider?: string
cron?: string cron?: string
options?: string options?: string
sat_name?: string
ntc_id?: string
filters?: any
norad_cat_id?: string
object_name?: string
name?:string
spacecraft?:string
station?:string
} }
\ No newline at end of file
...@@ -52,7 +52,6 @@ import { ref, computed, onMounted, watch } from 'vue'; ...@@ -52,7 +52,6 @@ import { ref, computed, onMounted, watch } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import SelectScrapydServer from '@/components/SelectScrapydServer.vue'; import SelectScrapydServer from '@/components/SelectScrapydServer.vue';
import SelectProject from '@/components/SelectProject.vue'; import SelectProject from '@/components/SelectProject.vue';
import AutoRefresh from '@/components/AutoRefresh.vue';
import JobCancelAll from '@/views/job/components/JobCancelAll.vue'; import JobCancelAll from '@/views/job/components/JobCancelAll.vue';
const props = defineProps({ const props = defineProps({
......
<template>
<div class="app-container">
<AutoRefresh
@refresh="getData"
/>
<el-divider />
<div
class="log-content"
v-loading="listLoading"
>
<pre v-if="content">{{ content }}</pre>
<div v-else>暂无数据</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { useRoute } from 'vue-router';
import AutoRefresh from '@/components/AutoRefresh.vue';
import { getJobLog } from '@/api/log';
const route = useRoute();
const scrapydServerId = ref('');
const project = ref('');
const spider = ref('');
const job = ref('');
const listLoading = ref(true);
const content = ref('');
const getData = async () => {
listLoading.value = true;
try {
const res = await getJobLog({
scrapydServerId: scrapydServerId.value,
project: project.value,
spider: spider.value,
job: job.value,
});
content.value = res as unknown as string || '';
} catch (error) {
console.error('获取任务日志失败:', error);
} finally {
listLoading.value = false;
}
}
onMounted(() => {
project.value = route.query.project as string;
spider.value = route.query.spider as string;
job.value = route.query.job as string;
scrapydServerId.value = route.query.scrapydServerId as string;
getData();
});
</script>
<style lang="scss" scoped>
.log-content {
overflow: auto;
text-align: left;
pre {
margin: 0;
}
}
</style>
...@@ -42,6 +42,7 @@ const redirect = ref<string | undefined>(undefined) ...@@ -42,6 +42,7 @@ const redirect = ref<string | undefined>(undefined)
// 监听路由变化 // 监听路由变化
if (route.query?.redirect) { if (route.query?.redirect) {
console.log(route.query.redirect);
redirect.value = route.query.redirect as string redirect.value = route.query.redirect as string
} }
...@@ -60,7 +61,7 @@ const handleLogin = () => { ...@@ -60,7 +61,7 @@ const handleLogin = () => {
loading.value = true loading.value = true
try { try {
await userStore.login(loginForm) await userStore.login(loginForm)
router.push({ path: redirect.value || '/' }) router.push({ path: '/' })
} catch (error: any) { } catch (error: any) {
console.error('登录失败:', error) console.error('登录失败:', error)
} finally { } finally {
......
...@@ -58,7 +58,7 @@ ...@@ -58,7 +58,7 @@
<div> <div>
<el-table :data="tableData" style="width: 100%" border :row-style="{ height: '45px' }" <el-table :data="tableData" style="width: 100%" border :row-style="{ height: '45px' }"
:header-cell-style="{ textAlign: 'center' }" :cell-style="{ textAlign: 'center' }"> :header-cell-style="{ textAlign: 'center' }" :cell-style="{ textAlign: 'center' }">
<el-table-column type="selection" width="40" /> <!-- <el-table-column type="selection" width="40" /> -->
<el-table-column property="number" label="序号" width="55" /> <el-table-column property="number" label="序号" width="55" />
<el-table-column property="targetName" label="目标名称" show-overflow-tooltip /> <el-table-column property="targetName" label="目标名称" show-overflow-tooltip />
<el-table-column property="stationPosition" label="站点位置" show-overflow-tooltip /> <el-table-column property="stationPosition" label="站点位置" show-overflow-tooltip />
......
<template> <template>
<div class="text-left p-1 toolbarStyle"> <!-- <div class="text-left p-1 toolbarStyle">
<div class="formStyle"> <div class="formStyle">
<el-form inline> <el-form inline>
<el-form-item> <el-form-item>
...@@ -7,32 +7,22 @@ ...@@ -7,32 +7,22 @@
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<div> <div>
<el-input placeholder="请输入" style="width: 220px" /> <el-input placeholder="请输入" style="width: 190px" />
</div> </div>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-text class="mx-1">获取时间:</el-text> <el-text class="mx-1">获取时间:</el-text>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-date-picker v-model="timeValue" type="datetimerange" format="YYYY-MM-DD HH:mm:ss" start-placeholder="开始时间" <el-date-picker v-model="timeValue" type="datetimerange" format="YYYY-MM-DD HH:mm:ss"
end-placeholder="结束时间" date-format="YYYY/MM/DD ddd" time-format="A hh:mm:ss" /> start-placeholder="开始时间" end-placeholder="结束时间" date-format="YYYY/MM/DD ddd"
time-format="A hh:mm:ss" style="width: 330px" value-format="YYYY-MM-DD HH:mm:ss" />
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-text class="mx-1">站点名称:</el-text> <el-text class="mx-1">站点名称:</el-text>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-input placeholder="请输入" style="width: 220px" /> <el-input placeholder="请输入" style="width: 180.5px" />
</el-form-item>
<br></br>
<el-form-item>
<el-space>
<el-button type="primary" @click="handleExport">导出最近</el-button>
</el-space>
</el-form-item>
<el-form-item>
<el-space>
<el-button type="primary" @click="handleExport">导出选中</el-button>
</el-space>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-space> <el-space>
...@@ -41,29 +31,51 @@ ...@@ -41,29 +31,51 @@
</el-form-item> </el-form-item>
</el-form> </el-form>
</div> </div>
</div> -->
<div class="flex justify-left m-l-1%">
<el-button :type="statusMode === false ? 'primary' : 'danger'" @click="handleRefresh" size="small">{{ statusMode === false ?
'开始刷新' : '停止刷新' }}</el-button>
</div> </div>
<div class="m-t-5" /> <div class="m-t-5" />
<div> <div>
<el-table :data="tableData" style="width: 100%" border :row-style="{ height: '41px' }" <el-table :data="tableData" style="width: 100%" border :row-style="{ height: '46px' }"
:header-cell-style="{ textAlign: 'center' }" :cell-style="{ textAlign: 'center' }"> :header-cell-style="{ textAlign: 'center' }" :cell-style="{ textAlign: 'center' }">
<el-table-column type="selection" width="40" /> <!-- <el-table-column type="selection" width="40" /> -->
<el-table-column property="number" label="序号" width="55" /> <el-table-column property="number" label="序号" width="55" type="index" />
<el-table-column property="targetName" label="目标名称" show-overflow-tooltip /> <el-table-column property="name" label="天线名称" show-overflow-tooltip />
<el-table-column property="distance" label="距离(km)" show-overflow-tooltip /> <el-table-column property="targets[0].name" label="目标名称" show-overflow-tooltip />
<!-- <el-table-column property="distance" label="距离(km)" show-overflow-tooltip />
<el-table-column property="roundTripTime" label="往返光时(s)" show-overflow-tooltip /> <el-table-column property="roundTripTime" label="往返光时(s)" show-overflow-tooltip />
<el-table-column property="stationName" label="站点名称" show-overflow-tooltip /> <el-table-column property="stationName" label="站点名称" show-overflow-tooltip /> -->
<el-table-column property="targetAzimuth" label="目标方位" show-overflow-tooltip /> <el-table-column property="azimuthAngle" label="目标方位" show-overflow-tooltip />
<el-table-column property="targetElevation" label="目标俯仰" show-overflow-tooltip /> <el-table-column property="elevationAngle" label="目标俯仰" show-overflow-tooltip />
<el-table-column property="targetDistance" label="目标距离" show-overflow-tooltip /> <!-- <el-table-column property="targetDistance" label="目标距离" show-overflow-tooltip /> -->
<el-table-column property="windSpeed" label="风速(km/hr)" show-overflow-tooltip /> <el-table-column property="windSpeed" label="风速(km/hr)" show-overflow-tooltip>
<el-table-column property="upSignalSource" label="上行信号来源" show-overflow-tooltip /> <template #default="scope">
<el-table-column property="upSignalFrequencyBand" label="上行信号频段" show-overflow-tooltip /> {{ scope.row.windSpeed === '' ? '无' : scope.row.windSpeed }}
<el-table-column property="upSignalLaunchPower" label="上行发射功率" show-overflow-tooltip /> </template>
<el-table-column property="downSignalSignalSource" label="下行信号来源" show-overflow-tooltip /> </el-table-column>
<el-table-column property="downSignalFrequencyBand" label="下行信号频段" show-overflow-tooltip /> <el-table-column property="targets[0].name" label="上行信号来源" show-overflow-tooltip />
<el-table-column property="downSignalReceptionPower" label="下行接收功率" show-overflow-tooltip /> <el-table-column property="signals[0].band" label="上行信号频段" show-overflow-tooltip>
<el-table-column property="dataTime" label="数据时间" show-overflow-tooltip /> <template #default="scope">
{{ scope.row.signals.length === 0 ? '无' : scope.row.signals[0].band }}
</template>
</el-table-column>
<el-table-column property="signals[0].power" label="上行发射功率" show-overflow-tooltip>
<template #default="scope">
{{ scope.row.signals.length === 0 ? '无' : scope.row.signals[0].power }}
</template>
</el-table-column>
<el-table-column property="targets[0].name" label="下行信号来源" show-overflow-tooltip />
<el-table-column property="signals[1].band" label="下行信号频段" show-overflow-tooltip>
<template #default="scope">
{{ scope.row.signals.length < 2 ? '无' : scope.row.signals[1].band }} </template>
</el-table-column>
<el-table-column property="signals[1].power" label="下行接收功率" show-overflow-tooltip>
<template #default="scope">
{{ scope.row.signals.length < 2 ? '无' : scope.row.signals[1].power }} </template>
</el-table-column>
<!-- <el-table-column property="dataTime" label="数据时间" show-overflow-tooltip /> -->
<el-table-column label="操作" width="60"> <el-table-column label="操作" width="60">
<template #default="scope"> <template #default="scope">
<el-button type="primary" plain link @click="handleDetails(scope.row)"> <el-button type="primary" plain link @click="handleDetails(scope.row)">
...@@ -72,14 +84,14 @@ ...@@ -72,14 +84,14 @@
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
<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">
<Pagination :total="pageObj.total" v-model:page="pageObj.pageNo" v-model:limit="pageObj.pageSize" <Pagination :total="pageObj.total" v-model:page="pageObj.pageNo" v-model:limit="pageObj.pageSize"
@pagination="getData" /> @pagination="getData" />
</div> </div> -->
</div> </div>
<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="800px" align-center @close="handleClose" draggable>
<template #header> <template #header>
<div class="text-center font-size-8"> <div class="text-center font-size-8">
详情 详情
...@@ -89,18 +101,35 @@ ...@@ -89,18 +101,35 @@
<div> <div>
<el-row :gutter="20"> <el-row :gutter="20">
<el-col :span="12"> <el-col :span="12">
<el-form-item label="目标名称:" label-position="left"> <el-form-item label="天线名称:" label-position="left">
<el-input v-model="targetName" /> <el-input v-model="antennaName" />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
<el-form-item label="风速(km/hr):" label-position="left">
<el-input v-model="windSpeed" />
</el-form-item>
</el-col>
<!-- <el-col :span="12">
<el-form-item label="距离(km):" label-position="left"> <el-form-item label="距离(km):" label-position="left">
<el-input v-model="distance" /> <el-input v-model="distance" />
</el-form-item> </el-form-item>
</el-col> </el-col> -->
</el-row> </el-row>
<el-row :gutter="20"> <el-row :gutter="20">
<el-col :span="12"> <el-col :span="12">
<el-form-item label="目标名称:" label-position="left">
<el-input v-model="targetName" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="天线状态:" label-position="left">
<el-input v-model="antennaStatus" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<!-- <el-col :span="12">
<el-form-item label="往返光时:" label-position="left"> <el-form-item label="往返光时:" label-position="left">
<el-input v-model="roundTripTime" /> <el-input v-model="roundTripTime" />
</el-form-item> </el-form-item>
...@@ -109,7 +138,7 @@ ...@@ -109,7 +138,7 @@
<el-form-item label="站点名称:" label-position="left"> <el-form-item label="站点名称:" label-position="left">
<el-input v-model="stationName" /> <el-input v-model="stationName" />
</el-form-item> </el-form-item>
</el-col> </el-col> -->
</el-row> </el-row>
<el-row :gutter="20"> <el-row :gutter="20">
<el-col :span="12"> <el-col :span="12">
...@@ -123,40 +152,57 @@ ...@@ -123,40 +152,57 @@
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row> </el-row>
<el-row :gutter="20"> <!-- <el-row :gutter="20"> -->
<el-col :span="12"> <!-- <el-col :span="12">
<el-form-item label="目标距离:" label-position="left"> <el-form-item label="目标距离:" label-position="left">
<el-input v-model="targetDistance" /> <el-input v-model="targetDistance" />
</el-form-item> </el-form-item>
</el-col> </el-col> -->
<el-col :span="12"> <!-- <el-col :span="12">
<el-form-item label="风速(km/hr):" label-position="left"> <el-form-item label="风速(km/hr):" label-position="left">
<el-input v-model="windSpeed" /> <el-input v-model="windSpeed" />
</el-form-item> </el-form-item>
</el-col> </el-col> -->
</el-row> <!-- </el-row> -->
<el-row :gutter="20"> <el-row :gutter="20">
<el-col :span="12"> <el-col :span="12">
<el-form-item label="上行信号状态:" label-position="left">
<el-input v-model="upSignalStatus" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="上行信号来源:" label-position="left"> <el-form-item label="上行信号来源:" label-position="left">
<el-input v-model="upSignalSource" /> <el-input v-model="upSignalSource" />
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12"> <el-col :span="12">
<el-form-item label="上行信号频段:" label-position="left"> <el-form-item label="上行信号频段:" label-position="left">
<el-input v-model="upSignalFrequencyBand" /> <el-input v-model="upSignalFrequencyBand" />
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12"> <el-col :span="12">
<el-form-item label="上行发射功率:" label-position="left"> <el-form-item label="上行发射功率:" label-position="left">
<el-input v-model="upSignalLaunchPower" /> <el-input v-model="upSignalLaunchPower" />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12"> <!-- <el-col :span="12">
<el-form-item label="下行信号来源:" label-position="left"> <el-form-item label="下行信号来源:" label-position="left">
<el-input v-model="downSignalSignalSource" /> <el-input v-model="downSignalSignalSource" />
</el-form-item> </el-form-item>
</el-col> -->
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="下行信号状态:" label-position="left">
<el-input v-model="downSignalStatus" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="下行信号来源:" label-position="left">
<el-input v-model="downSignalSource" />
</el-form-item>
</el-col> </el-col>
</el-row> </el-row>
<el-row :gutter="20"> <el-row :gutter="20">
...@@ -173,6 +219,13 @@ ...@@ -173,6 +219,13 @@
</el-row> </el-row>
<el-row :gutter="20"> <el-row :gutter="20">
<el-col :span="12"> <el-col :span="12">
<el-form-item label="往返光时(s):" label-position="left">
<el-input v-model="roundTripTime" />
</el-form-item>
</el-col>
</el-row>
<!-- <el-row :gutter="20">
<el-col :span="12">
<el-form-item label="数据时间:" label-position="left"> <el-form-item label="数据时间:" label-position="left">
<el-input v-model="dataTime" /> <el-input v-model="dataTime" />
</el-form-item> </el-form-item>
...@@ -182,31 +235,28 @@ ...@@ -182,31 +235,28 @@
<el-input v-model="recordTime" /> <el-input v-model="recordTime" />
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row> </el-row> -->
<el-row :gutter="20"> <!-- <el-row :gutter="20">
<el-col :span="12"> <el-col :span="12">
<el-form-item label="是否暂停使用:" label-position="left"> <el-form-item label="是否暂停使用:" label-position="left">
<el-input v-model="isSuspended" /> <el-input v-model="isSuspended" />
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row> </el-row> -->
</div> </div>
</el-form> </el-form>
</el-dialog> </el-dialog>
</div> </div>
<exportDialog v-model:dialogVisible="showDeleteDialog" @confirm="handleExportConfirm" />
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { onMounted, ref } from 'vue' import { onMounted, ref } from 'vue'
import Pagination from '@/components/pagination/index.vue' import Pagination from '@/components/pagination/index.vue'
import exportDialog from '@/components/Export/index.vue' import { getDsnList } from '@/api/spiderData'
import { getDsnlist, getDsnDetail, deleteDsnData } from '@/api/spiderData' import { ElMessage } from 'element-plus'
const showDeleteDialog = ref(false)
const timeValue = ref('') const timeValue = ref('')
const statusMode = ref(false)
const detailVisibleValue = ref(false) const detailVisibleValue = ref(false)
// 目标名称 // 目标名称
const targetName = ref('') const targetName = ref('')
...@@ -216,6 +266,10 @@ const distance = ref('') ...@@ -216,6 +266,10 @@ const distance = ref('')
const roundTripTime = ref('') const roundTripTime = ref('')
// 站点名称 // 站点名称
const stationName = ref('') const stationName = ref('')
// 天线名称
const antennaName = ref('')
// 天线状态
const antennaStatus = ref('')
// 目标方位 // 目标方位
const targetAzimuth = ref('') const targetAzimuth = ref('')
// 目标俯仰 // 目标俯仰
...@@ -224,14 +278,18 @@ const targetElevation = ref('') ...@@ -224,14 +278,18 @@ const targetElevation = ref('')
const targetDistance = ref('') const targetDistance = ref('')
// 风速(km/hr) // 风速(km/hr)
const windSpeed = ref('') const windSpeed = ref('')
// 上行信号状态
const upSignalStatus = ref('')
// 上行信号来源 // 上行信号来源
const upSignalSource = ref('') const upSignalSource = ref('')
// 上行信号频段 // 上行信号频段
const upSignalFrequencyBand = ref('') const upSignalFrequencyBand = ref('')
// 上行发射功率 // 上行发射功率
const upSignalLaunchPower = ref('') const upSignalLaunchPower = ref('')
// 下行信号状态
const downSignalStatus = ref('')
// 下行信号来源 // 下行信号来源
const downSignalSignalSource = ref('') const downSignalSource = ref('')
// 下行信号频段 // 下行信号频段
const downSignalFrequencyBand = ref('') const downSignalFrequencyBand = ref('')
// 下行接收功率 // 下行接收功率
...@@ -243,36 +301,84 @@ const recordTime = ref('') ...@@ -243,36 +301,84 @@ const recordTime = ref('')
// 是否暂停使用 // 是否暂停使用
const isSuspended = ref('') const isSuspended = ref('')
const tableData = ref([
]) interface dsnInfo {
targetName:string
targetNam:string
}
const dsnInfoDialog = ref<dsnInfo>()
// 存放定时器的id
const timerId = ref<number | null>()
const tableData = ref()
const pageObj = ref({ const pageObj = ref({
total: 10, total: 10,
pageSize: 10, pageSize: 1,
pageNo: 1 pageNo: 1
}) })
// 查看数据详情的方法
const handleDetails = (row: any) => { const handleDetails = (row: any) => {
console.log(row); console.log(row);
targetName.value = row.targets[0].name
windSpeed.value = row.windSpeed === '' ? '无' : row.windSpeed
antennaName.value = row.name
antennaStatus.value = row.activity
targetAzimuth.value = row.azimuthAngle
targetElevation.value = row.elevationAngle
upSignalStatus.value = row.signals.length < 1 ? '无' : row.signals[0].active
upSignalSource.value = row.signals.length < 1 ? '无' : row.targets[0].name
upSignalFrequencyBand.value = row.signals.length < 1 ? '无' : row.signals[0].band
upSignalLaunchPower.value = row.signals.length < 1 ? '无' : row.signals[0].power
downSignalStatus.value = row.signals.length < 2 ? '无' : row.signals[1].active
downSignalSource.value = row.signals.length < 2 ? '无' : row.targets[0].name
downSignalFrequencyBand.value = row.signals.length < 2 ? '无' : row.signals[1].band
downSignalReceptionPower.value = row.signals.length < 2 ? '无' : row.signals[1].power
roundTripTime.value = row.targets[0].rtlt
detailVisibleValue.value = true detailVisibleValue.value = true
} }
// 获取dsn数据列表的方法
const getData = async () => { const getData = async () => {
console.log('getData'); const res = await getDsnList({ page: pageObj.value.pageNo, size: pageObj.value.pageSize })
const res = await getDsnlist({page:pageObj.value.pageNo,size:pageObj.value.pageSize}) let dishesData = []
tableData.value = res.data.list for(let i = 0; i < res.data.list[0].stations.length; i++){
pageObj.value.total = res.data.total for(let j = 0; j < res.data.list[0].stations[i].dishes.length; j++){
dishesData.push(res.data.list[0].stations[i].dishes[j])
}
}
pageObj.value.total = dishesData.length
tableData.value = dishesData
} }
const handleClose = () => { const handleClose = () => {
detailVisibleValue.value = false detailVisibleValue.value = false
} }
const handleExportConfirm = () => { // 刷新数据的方法
const handleRefresh = () => {
} // 切换是否开始刷新的状态
const handleExport = () => { statusMode.value = !statusMode.value
showDeleteDialog.value = true // 删除定时器
if (timerId.value !== null) {
clearInterval(timerId.value);
timerId.value = null;
}
// 开始刷新时重新设置一个定时器
if (statusMode.value) {
ElMessage.success('开始刷新')
// 保存id删除的时候用
timerId.value = setInterval(async () => {
const res = await getDsnList({ page: pageObj.value.pageNo, size: pageObj.value.pageSize });
tableData.value = res.data.list[0].dishes
}, 5000);
} else {
ElMessage.success('停止刷新')
getData()
}
} }
onMounted(() => { onMounted(async () => {
// 进入页面时先删除定时器
if (timerId.value !== null) {
clearInterval(timerId.value);
}
statusMode.value = false
// 先获取一次
getData() getData()
}) })
</script> </script>
......
<template> <template>
<div class="text-left p-s"> <!-- <div class="text-left p-s">
<div class="segmentedStyle"> <div class="segmentedStyle">
<el-segmented v-model="mode" :options="sizeOptions" style="margin-bottom: 1rem" size="small" /> <el-segmented v-model="mode" :options="sizeOptions" style="margin-bottom: 1rem" size="small" />
</div> </div>
</div> </div> -->
<dsnDataTab v-if="mode === 'DSN数据'"> <dsnDataTab>
</dsnDataTab> </dsnDataTab>
<newsDataTab v-if="mode === '新闻'"> <!-- <newsDataTab v-if="mode === '新闻'">
</newsDataTab> </newsDataTab> -->
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
......
<template>
<div class="text-left p-4 toolbarStyle">
<div class="formStyle">
<el-form inline>
<el-form-item>
<el-text class="mx-1">目标名称:</el-text>
</el-form-item>
<el-form-item>
<div>
<el-input placeholder="请输入" v-model="searchTargetName" style="width: 180px" />
</div>
</el-form-item>
<el-form-item>
<el-text class="mx-1">获取时间:</el-text>
</el-form-item>
<el-form-item>
<el-config-provider :locale="zhCn">
<el-date-picker type="datetimerange" format="YYYY-MM-DD HH:mm:ss" v-model="searchTimeValue"
start-placeholder="开始时间" style="width: 330px" end-placeholder="结束时间"
date-format="YYYY/MM/DD ddd" time-format="A hh:mm:ss" value-format="YYYY-MM-DD HH:mm:ss" />
</el-config-provider>
</el-form-item>
<el-form-item>
<el-text class="mx-1">站点名称:</el-text>
</el-form-item>
<el-form-item>
<el-input placeholder="请输入" v-model="searchStation" style="width: 170.5px" />
</el-form-item>
<el-form-item>
<el-space>
<el-button type="primary" @click="handleSearch">查询</el-button>
<el-button type="primary" @click="getData">重置表格</el-button>
</el-space>
</el-form-item>
</el-form>
</div>
</div>
<div class="m-t-5" />
<div>
<el-table :data="tableData" style="width: 100%" border :row-style="{ height: '45.5px' }"
:header-cell-style="{ textAlign: 'center' }" :cell-style="{ textAlign: 'center' }">
<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.station" label="站点名称" width="88" show-overflow-tooltip />
<el-table-column property="data.distance" label="距离" show-overflow-tooltip>
<template #default="scope">
{{ scope.row.data.distance === null ? '-' : scope.row.data.distance }}
</template>
</el-table-column>
<el-table-column property="data.roundTripLightTime" label="往返光时" show-overflow-tooltip>
<template #default="scope">
{{ scope.row.data.roundTripLightTime === null ? '-' : scope.row.data.roundTripLightTime }}
</template>
</el-table-column>
<el-table-column property="data.boa" label="行动开始时间" show-overflow-tooltip>
<template #default="scope">
{{ formatTimestamp(scope.row.data.boa) }}
</template>
</el-table-column>
<el-table-column property="data.plan_txt" label="行动结束时间" show-overflow-tooltip>
<template #default="scope">
{{ formatTimestamp(scope.row.data.bot) }}
</template>
</el-table-column>
<el-table-column property="data.plan_txt" label="跟踪结束时间" show-overflow-tooltip>
<template #default="scope">
{{ formatTimestamp(scope.row.data.eoa) }}
</template>
</el-table-column>
<el-table-column property="data.plan_txt" label="跟踪开始时间" show-overflow-tooltip>
<template #default="scope">
{{ formatTimestamp(scope.row.data.eot) }}
</template>
</el-table-column>
<el-table-column label="目标在线状态" width="110" show-overflow-tooltip>
<template #default="scope">
{{ scope.row.data.target?.[scope.row.data.mission] === 'true' ? '是' : '否' }}
</template>
</el-table-column>
<el-table-column fixed="right" label="操作" min-width="85">
<template #default="scope">
<el-button link type="primary" size="small" @click="handleMissionDetails(scope.row.data.mission)">
目标信息
</el-button>
<el-button link type="primary" size="small"
@click="handleStationDetails(scope.row.data.station)">站点信息</el-button>
</template>
</el-table-column>
</el-table>
<div class="pagination w-full flex flex-row-reverse pr-4 m-t-4">
<Pagination :total="pageObj.total" v-model:page="pageObj.pageNo" v-model:limit="pageObj.pageSize"
@pagination="getData" />
</div>
</div>
<div class="detailForm">
<el-dialog v-model="detailVisibleValue" center width="765px" align-center @close="handleClose" draggable>
<template #header>
<div class="text-center font-size-8">
详情
</div>
</template>
<el-form label-width="155px" size="small" class="px-4">
<div>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="目标名称:" label-position="left">
<el-input v-model="targetName" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="任务类型:" label-position="left">
<el-input v-model="targetMissionType" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="目标运行轨道:" label-position="left">
<el-input v-model="targetOrbit" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="目标尺寸:" label-position="left">
<el-input v-model="targetDimension" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="目标发射质量:" label-position="left">
<el-input v-model="targetLaunchMass" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="目标干质量:" label-position="left">
<el-input v-model="targetDryMass" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="近地点:" label-position="left">
<el-input v-model="targetPerigee" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="远地点:" label-position="left">
<el-input v-model="targetApogee" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="目标功率:" label-position="left">
<el-input v-model="targetPower" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="轨道倾角:" label-position="left">
<el-input v-model="targetInclination" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="发射日期:" label-position="left">
<el-input v-model="targetLaunchDate" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="发射站点:" label-position="left">
<el-input v-model="targetLaunchLocation" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="轨道周期:" label-position="left">
<el-input v-model="targetPeriod" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="相关仪器:" label-position="left">
<el-input v-model="targetInstruments" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="服役日期:" label-position="left">
<el-input v-model="targetServiceDate" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="是否为核心任务:" label-position="left">
<el-input v-model="targetIsCoreMission" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="太阳风速:" label-position="left">
<el-input v-model="targetSolarWindSpeed" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="太阳通量密度:" label-position="left">
<el-input v-model="targetSolarFluxDensity" />
</el-form-item>
</el-col>
</el-row>
<!-- <el-row :gutter="20">
<el-col :span="12">
<el-form-item label="是否在仪表盘中显示:" label-position="left">
<el-input v-model="targetIsShowInDashboard" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="类型:" label-position="left">
<el-input v-model="targetType" />
</el-form-item>
</el-col>
</el-row> -->
<el-row :gutter="20">
<el-col :span="24">
<el-form-item label="描述:" label-position="left">
<el-input v-model="targetDescription" type="textarea" :rows="3" />
</el-form-item>
</el-col>
</el-row>
</div>
</el-form>
</el-dialog>
</div>
<div class="detailForm">
<el-dialog v-model="stationVisibleValue" center width="880px" align-center @close="handleClose" draggable>
<template #header>
<div class="text-center font-size-8">
详情
</div>
</template>
<el-form label-width="196px" size="small" class="px-4">
<div>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="站点名称:" label-position="left">
<el-input v-model="stationName" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="所在国家:" label-position="left">
<el-input v-model="stationCountry" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="站点经度:" label-position="left">
<el-input v-model="stationLongitude" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="站点纬度:" label-position="left">
<el-input v-model="stationLatitude" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="站点海拔:" label-position="left">
<el-input v-model="stationAltitude" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="站点直径:" label-position="left">
<el-input v-model="stationDiameter" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="天线方位角速度:" label-position="left">
<el-input v-model="stationAzimuthSpeed" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="天线俯仰角速度:" label-position="left">
<el-input v-model="stationElevationSpeed" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="信号频段:" label-position="left">
<el-input v-model="stationBand" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="TMTC标准:" label-position="left">
<el-input v-model="stationTMTC" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="信号频率与定时:" label-position="left">
<el-input v-model="stationFrequencyAndTiming" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="站点天气情况:" label-position="left">
<el-input v-model="stationWeather" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="上行信号S频段极化:" label-position="left">
<el-input v-model="stationUplinkSPolarization" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="上行信号S频段等效全向辐射功率:" label-position="left">
<el-input v-model="stationUplinkSEirp" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="上行信号X频段极化:" label-position="left">
<el-input v-model="stationUplinkXPolarization" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="上行信号X频段等效全向辐射功率:" label-position="left">
<el-input v-model="stationUplinkXEirp" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="上行信号Ka频段极化:" label-position="left">
<el-input v-model="stationUplinkKaPolarization" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="上行信号Ka频段等效全向辐射功率:" label-position="left">
<el-input v-model="stationUplinkKaEirp" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="下行信号S频段极化:" label-position="left">
<el-input v-model="stationDownlinkSPolarization" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="下行信号S频段G/T比:" label-position="left">
<el-input v-model="stationDownlinkSGt" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="下行信号X频段极化:" label-position="left">
<el-input v-model="stationDownlinkXPolarization" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="下行信号X频段G/T比:" label-position="left">
<el-input v-model="stationDownlinkXGt" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="下行信号Ka频段极化:" label-position="left">
<el-input v-model="stationDownlinkKaPolarization" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="下行信号Ka频段G/T比:" label-position="left">
<el-input v-model="stationDownlinkKaGt" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="站点温度:" label-position="left">
<el-input v-model="stationTemperature" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="站点气压:" label-position="left">
<el-input v-model="stationPressure" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="站点湿度:" label-position="left">
<el-input v-model="stationHumidity" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="站点类型:" label-position="left">
<el-input v-model="stationType" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="24">
<el-form-item label="描述:" label-position="left">
<el-input v-model="stationDescription" type="textarea" :rows="3" />
</el-form-item>
</el-col>
</el-row>
</div>
</el-form>
</el-dialog>
</div>
<exportDialog v-model:dialogVisible="showDeleteDialog" />
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import Pagination from '@/components/pagination/index.vue'
import exportDialog from '@/components/Export/index.vue'
import { getESAList, getESAMissionDetail, getESAStationDetail } from '@/api/spiderData'
import { ElMessage } from 'element-plus'
// ElConfigProvider 组件
import { ElConfigProvider } from 'element-plus';
// 引入中文包
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 = '页';
// 查找字段
const searchTargetName = ref('')
const searchTimeValue = ref('')
const searchStation = ref('')
const showDeleteDialog = ref(false)
const detailVisibleValue = ref(false)
const stationVisibleValue = ref(false)
// 目标详情信息
const targetName = ref('')
// 任务类型
const targetMissionType = ref('')
// 目标运行轨道
const targetOrbit = ref('')
// 目标尺寸
const targetDimension = ref('')
// 目标发射质量
const targetLaunchMass = ref('')
// 目标干质量
const targetDryMass = ref('')
// 近地点
const targetPerigee = ref('')
// 远地点
const targetApogee = ref('')
// 目标功率
const targetPower = ref('')
// 轨道倾角
const targetInclination = ref('')
// 轨道周期
const targetPeriod = ref('')
// 发射日期
const targetLaunchDate = ref('')
// 发射站点
const targetLaunchLocation = ref('')
// 相关仪器
const targetInstruments = ref('')
// 服役日期
const targetServiceDate = ref('')
// 是否为核心任务
const targetIsCoreMission = ref('')
// 太阳风速
const targetSolarWindSpeed = ref('')
// 太阳通量密度
const targetSolarFluxDensity = ref('')
// 是否在仪表盘中显示
// const targetIsShowInDashboard = ref('')
// 目标类型
const targetType = ref('')
// 描述
const targetDescription = ref('')
// 站点详情信息
// 站点名称
const stationName = ref('')
// 站点所在国家
const stationCountry = ref('')
// 站点经度
const stationLongitude = ref('')
// 站点纬度
const stationLatitude = ref('')
// 站点海拔高度
const stationAltitude = ref('')
// 站点直径
const stationDiameter = ref('')
// 天线方位角速度
const stationAzimuthSpeed = ref('')
// 天线俯仰角速度
const stationElevationSpeed = ref('')
// 天线信号频段
const stationBand = ref('')
// TMTC标准
const stationTMTC = ref('')
// 信号频率与定时
const stationFrequencyAndTiming = ref('')
// 天气
const stationWeather = ref('')
// 上行信号S频段极化
const stationUplinkSPolarization = ref('')
// 上行信号S频段等效全向辐射功率
const stationUplinkSEirp = ref('')
// 上行信号X频段极化
const stationUplinkXPolarization = ref('')
// 上行信号X频段等效全向辐射功率
const stationUplinkXEirp = ref('')
// 上行信号Ka频段极化
const stationUplinkKaPolarization = ref('')
// 上行信号Ka频段等效全向辐射功率
const stationUplinkKaEirp = ref('')
// 下行信号S频段极化
const stationDownlinkSPolarization = ref('')
// 下行信号S频段G/T比
const stationDownlinkSGt = ref('')
// 下行信号X频段极化
const stationDownlinkXPolarization = ref('')
// 下行信号X频段G/T比
const stationDownlinkXGt = ref('')
// 下行信号Ka频段极化
const stationDownlinkKaPolarization = ref('')
// 下行信号Ka频段G/T比
const stationDownlinkKaGt = ref('')
// 温度
const stationTemperature = ref('')
// 气压
const stationPressure = ref('')
// 湿度
const stationHumidity = ref('')
// 站点类型
const stationType = ref('')
// 描述
const stationDescription = ref('')
// 查看esa站点详情的方法
const handleStationDetails = async (id: any) => {
const res = await getESAStationDetail({ name: id })
// 防止残留数据
stationDescription.value = ''
// 字段赋值
stationName.value = res.data.data[0].name
stationCountry.value = res.data.data[0].country
stationLongitude.value = res.data.data[0].longitude
stationLatitude.value = res.data.data[0].latitude
stationAltitude.value = res.data.data[0].altitude
stationDiameter.value = res.data.data[0].diameter
stationAzimuthSpeed.value = res.data.data[0].antennaSpeedAzimuth
stationElevationSpeed.value = res.data.data[0].antennaSpeedElevation
stationBand.value = res.data.data[0].band
stationTMTC.value = res.data.data[0].tmtcStandards
stationFrequencyAndTiming.value = res.data.data[0].frequencyAndTiming
stationWeather.value = res.data.data[0].weather === null ? '无' : res.data.data[0].weather
stationUplinkSPolarization.value = res.data.data[0].uplinkSBandPolarization === null ? '无' : res.data.data[0].uplinkSBandPolarization
stationUplinkSEirp.value = res.data.data[0].uplinkSBandEirp === null ? '无' : res.data.data[0].uplinkSBandEirp
stationUplinkXPolarization.value = res.data.data[0].uplinkXBandPolarization === null ? '无' : res.data.data[0].uplinkXBandPolarization
stationUplinkXEirp.value = res.data.data[0].uplinkXBandEirp === null ? '无' : res.data.data[0].uplinkXBandEirp
stationUplinkKaPolarization.value = res.data.data[0].uplinkKaBandPolarization === null ? '无' : res.data.data[0].uplinkKaBandPolarization
stationUplinkKaEirp.value = res.data.data[0].uplinkKaBandEirp === null ? '无' : res.data.data[0].uplinkKaBandEirp
stationDownlinkSPolarization.value = res.data.data[0].downlinkSBandPolarization === null ? '无' : res.data.data[0].downlinkSBandPolarization
stationDownlinkSGt.value = res.data.data[0].downlinkSBandGT === null ? '无' : res.data.data[0].downlinkSBandGT
stationDownlinkXPolarization.value = res.data.data[0].downlinkXBandPolarization === null ? '无' : res.data.data[0].downlinkXBandPolarization
stationDownlinkXGt.value = res.data.data[0].downlinkXBandGT === null ? '无' : res.data.data[0].downlinkXBandGT
stationDownlinkKaPolarization.value = res.data.data[0].downlinkKaBandPolarization === null ? '无' : res.data.data[0].downlinkKaBandPolarization
stationDownlinkKaGt.value = res.data.data[0].downlinkKaBandGT === null ? '无' : res.data.data[0].downlinkKaBandGT
stationTemperature.value = res.data.data[0].temperature
stationPressure.value = res.data.data[0].airPressure
stationHumidity.value = res.data.data[0].humidity
stationType.value = res.data.data[0].type
for (let i = 0; i < res.data.data[0].description.length; i++) {
stationDescription.value += res.data.data[0].description[i]
}
stationVisibleValue.value = true
}
const tableData = ref([])
const pageObj = ref({
total: 0,
pageSize: 10,
pageNo: 1
})
const formatTimestamp = (timestamp: any) => {
if (!timestamp || typeof timestamp !== 'number' || timestamp <= 0) {
return '-';
}
const msTimestamp = String(timestamp).length === 10 ? timestamp * 1000 : timestamp;
const date = new Date(msTimestamp);
if (isNaN(date.getTime())) {
return '-';
}
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hour = String(date.getHours()).padStart(2, '0');
const minute = String(date.getMinutes()).padStart(2, '0');
const second = String(date.getSeconds()).padStart(2, '0');
return `${year}-${month}-${day} ${hour}:${minute}:${second}`;
};
// 查看esa任务详情的方法
const handleMissionDetails = async (id: any) => {
const res = await getESAMissionDetail({ name: id })
// 防止残留数据
targetDescription.value = ''
// 字段赋值
targetName.value = res.data.data[0].name
targetMissionType.value = res.data.data[0].type
targetOrbit.value = res.data.data[0].orbitsAround
targetDimension.value = res.data.data[0].dimensions
targetLaunchMass.value = res.data.data[0].launchMass
targetDryMass.value = res.data.data[0].dryMass
targetPerigee.value = res.data.data[0].perigee
targetApogee.value = res.data.data[0].apogee
targetPower.value = res.data.data[0].power
targetInclination.value = res.data.data[0].inclination
targetLaunchDate.value = formatTimestamp(res.data.data[0].launchDate)
targetLaunchLocation.value = res.data.data[0].launchSite
targetPeriod.value = res.data.data[0].period
targetInstruments.value = res.data.data[0].instruments
targetServiceDate.value = formatTimestamp(res.data.data[0].enteredService)
targetIsCoreMission.value = res.data.data[0].coreMission
targetSolarWindSpeed.value = res.data.data[0].solarWindSpeed
targetSolarFluxDensity.value = res.data.data[0].solarFluxDensity
for (let i = 0; i < res.data.data[0].description.length; i++) {
targetDescription.value += res.data.data[0].description[i]
}
detailVisibleValue.value = true
}
// 获取esa数据列表的方法
const getData = async () => {
searchStation.value = ''
searchTargetName.value = ''
searchTimeValue.value = ''
const res = await getESAList({ page: pageObj.value.pageNo, size: pageObj.value.pageSize })
pageObj.value.total = res.data.total
tableData.value = res.data.list
}
const handleClose = () => {
detailVisibleValue.value = false
}
const handleSearch = async () => {
if (searchTargetName.value === '' && searchStation.value === '' && searchTimeValue.value === '') {
ElMessage.warning('请输入搜索内容')
return
}
const res = await getESAList({ spacecraft: searchTargetName.value, station: searchStation.value, times: searchTimeValue.value, page: pageObj.value.pageNo, size: pageObj.value.pageSize })
pageObj.value.total = res.data.total
tableData.value = res.data.list
}
onMounted(() => {
getData()
})
</script>
<style scoped lang="scss">
// 调整表单项间距
.detailForm {
.el-dialog-title {
font-size: 180px;
}
.el-form-item {
margin-bottom: 20px !important;
}
}
/* 工具栏样式 */
.toolbarStyle {
background-image: url("@/assets/picture/box3.png");
background-size: 100% 100%;
background-repeat: no-repeat;
}
/* 表格样式 */
.formStyle {
display: flex;
justify-content: space-around;
padding: 3px;
margin-top: 1%;
}
/* 文字样式 */
.el-text {
color: #FFFFFF;
}
</style>
<style>
.el-input {
--el-input-text-color: #FFFFFF;
}
/* 修改el选择器的样式 */
.el-select__wrapper {
background-color: #1d5484;
box-shadow: none;
}
/* 删除el选择器选中时的边框 */
.is-hovering {
box-shadow: none !important;
}
/* 修改el输入框的样式 */
.el-input__wrapper {
background-color: #1d5484;
box-shadow: none;
}
/* 修改el下拉框的背景颜色 */
.el-select-dropdown {
background-color: #1d5484;
}
/* 修改el下拉框选项的字体颜色 */
.el-select-dropdown__item {
color: #FFFFFF;
}
/* 修改el下拉框选项选中时的文字颜色 */
.el-select-dropdown__item:hover {
color: #409eff;
}
/* 修改el日期选择器的边框为none */
.el-date-editor {
--el-input-border-color: none
}
.el-button:focus {
outline: none;
}
</style>
\ No newline at end of file
...@@ -7,42 +7,29 @@ ...@@ -7,42 +7,29 @@
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<div> <div>
<el-input placeholder="请输入" style="width: 220px" /> <el-input placeholder="请输入" v-model="searchTargetName" style="width: 180px" />
</div> </div>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-text class="mx-1">获取时间:</el-text> <el-text class="mx-1">获取时间:</el-text>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-date-picker v-model="timeValue" type="datetimerange" format="YYYY-MM-DD HH:mm:ss" start-placeholder="开始时间" <el-config-provider :locale="zhCn">
end-placeholder="结束时间" date-format="YYYY/MM/DD ddd" time-format="A hh:mm:ss" /> <el-date-picker type="datetimerange" format="YYYY-MM-DD HH:mm:ss" v-model="searchTimeValue"
start-placeholder="开始时间" style="width: 330px" end-placeholder="结束时间"
date-format="YYYY/MM/DD ddd" time-format="A hh:mm:ss" value-format="YYYY-MM-DD HH:mm:ss" />
</el-config-provider>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-text class="mx-1">站点名称:</el-text>
</el-form-item>
<el-form-item>
<el-input placeholder="请输入" style="width: 220px" />
</el-form-item>
<br></br>
<el-form-item>
<el-text class="mx-1">SNS Notice ID:</el-text> <el-text class="mx-1">SNS Notice ID:</el-text>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-input placeholder="请输入" style="width: 188.5px" /> <el-input placeholder="请输入" v-model="searchTargetId" style="width: 170.5px" />
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-space> <el-space>
<el-button type="primary" @click="handleExport">导出最近</el-button> <el-button type="primary" @click="handleSearch">查询</el-button>
</el-space> <el-button type="primary" @click="getData">重置表格</el-button>
</el-form-item>
<el-form-item>
<el-space>
<el-button type="primary" @click="handleExport">导出选中</el-button>
</el-space>
</el-form-item>
<el-form-item>
<el-space>
<el-button type="primary">查询</el-button>
</el-space> </el-space>
</el-form-item> </el-form-item>
</el-form> </el-form>
...@@ -51,27 +38,64 @@ ...@@ -51,27 +38,64 @@
<div class="m-t-5" /> <div class="m-t-5" />
<div> <div>
<el-table :data="tableData" style="width: 100%" border :row-style="{ height: '42.5px' }" <el-table :data="tableData" style="width: 100%" border :row-style="{ height: '45px' }"
:header-cell-style="{ textAlign: 'center' }" :cell-style="{ textAlign: 'center' }"> :header-cell-style="{ textAlign: 'center' }" :cell-style="{ textAlign: 'center' }">
<el-table-column type="selection" width="40" /> <!-- <el-table-column type="selection" width="40" /> -->
<el-table-column property="number" label="序号" width="55" /> <el-table-column property="number" label="序号" width="55" type="index" />
<el-table-column property="SNSNoticeID" label="SNS Notice ID" show-overflow-tooltip /> <el-table-column property="item.ntc_id" label="SNS Notice ID" show-overflow-tooltip />
<el-table-column property="department" label="主管部门" show-overflow-tooltip /> <el-table-column property="identity.adm_name_e" label="主管部门" show-overflow-tooltip />
<el-table-column property="targetName" label="目标名称" show-overflow-tooltip /> <el-table-column property="identity.sat_name" label="目标名称" show-overflow-tooltip />
<el-table-column property="planType" label="计划/非计划类型" show-overflow-tooltip /> <el-table-column property="item.plan_txt" label="计划/非计划类型" show-overflow-tooltip />
<el-table-column property="syncType" label="是否为同步" show-overflow-tooltip /> <el-table-column property="identity.ntc_type" label="是否为同步" show-overflow-tooltip>
<el-table-column property="syncPosition" label="同步位置" show-overflow-tooltip /> <template #default="scope">
<el-table-column property="perigee" label="近地点" show-overflow-tooltip /> {{ scope.row.identity.ntc_type === 'G' ? '是' : '否' }}
<el-table-column property="apogee" label="远地点" show-overflow-tooltip /> </template>
<el-table-column property="lowestAltitude" label="最低海拔" show-overflow-tooltip /> </el-table-column>
<el-table-column property="referenceSubject" label="参考主体" show-overflow-tooltip /> <el-table-column property="identity.long_nom" label="同步位置" show-overflow-tooltip>
<el-table-column property="highestFrequency" label="最高频率" show-overflow-tooltip /> <template #default="scope">
<el-table-column property="lowestFrequency" label="最低频率" show-overflow-tooltip /> {{ scope.row.identity.long_nom === null ? '-' : scope.row.identity.long_nom }}
<el-table-column property="status" label="状态" show-overflow-tooltip /> </template>
<el-table-column property="validityPeriod" label="有效期" show-overflow-tooltip /> </el-table-column>
<el-table-column property="earliestUsageDate" label="最早使用日期" show-overflow-tooltip /> <!-- <el-table-column property="item.minPerigee" label="近地点" show-overflow-tooltip>
<el-table-column property="isUsed" label="是否确认使用" show-overflow-tooltip /> <template #default="scope">
<el-table-column property="isPauseUsed" label="是否暂停使用" show-overflow-tooltip /> {{ scope.row.item.minPerigee === null ? '-' : scope.row.item.minPerigee }}
</template>
</el-table-column>
<el-table-column property="item.maxApog" label="远地点" show-overflow-tooltip>
<template #default="scope">
{{ scope.row.item.maxApog === null ? '-' : scope.row.identity.maxApog }}
</template>
</el-table-column>
<el-table-column property="item.minAltitude" label="最低海拔" show-overflow-tooltip>
<template #default="scope">
{{ scope.row.item.minAltitude === null ? '-' : scope.row.item.minAltitude }}
</template>
</el-table-column>
<el-table-column property="item.ref_body" label="参考主体" show-overflow-tooltip>
<template #default="scope">
{{ scope.row.item.ref_body === null ? '-' : scope.row.item.ref_body }}
</template>
</el-table-column> -->
<el-table-column property="item.freq_min" label="最低频率" show-overflow-tooltip>
<template #default="scope">
{{ scope.row.item.freq_min === null ? '-' : scope.row.item.freq_min }}
</template>
</el-table-column>
<el-table-column property="item.freq_max" label="最高频率" show-overflow-tooltip>
<template #default="scope">
{{ scope.row.item.freq_max === null ? '-' : scope.row.item.freq_max }}
</template>
</el-table-column>
<el-table-column property="regulatory_status.active_status" label="状态" show-overflow-tooltip />
<el-table-column property="regulatory_status.prd_valid" label="有效期" show-overflow-tooltip />
<el-table-column property="regulatory_status.d_reg_limit" label="最早使用日期" show-overflow-tooltip>
<template #default="scope">
{{ scope.row.regulatory_status.d_reg_limit === null ? '-' : scope.row.regulatory_status.d_reg_limit
}}
</template>
</el-table-column>
<el-table-column property="regulatory_status.f_biu_grps" label="是否确认使用" show-overflow-tooltip />
<el-table-column property="regulatory_status.resumption_list" label="是否暂停使用" show-overflow-tooltip />
<el-table-column label="操作" width="60"> <el-table-column label="操作" width="60">
<template #default="scope"> <template #default="scope">
<el-button type="primary" plain link @click="handleDetails(scope.row)"> <el-button type="primary" plain link @click="handleDetails(scope.row)">
...@@ -93,7 +117,7 @@ ...@@ -93,7 +117,7 @@
详情 详情
</div> </div>
</template> </template>
<el-form label-width="135px" size="small" class="px-4"> <el-form label-width="155px" size="small" class="px-4">
<div> <div>
<el-row :gutter="20"> <el-row :gutter="20">
<el-col :span="12"> <el-col :span="12">
...@@ -222,33 +246,45 @@ ...@@ -222,33 +246,45 @@
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
<el-form-item label="有效期卫星网络旧名称:" label-position="left"> <el-form-item label="最新相关 BR IFIC 发布日期:" label-position="left">
<el-input v-model="validityPeriodSatelliteNetworkOldName" /> <el-input v-model="BFIFICdate" />
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row> </el-row>
<el-row :gutter="20"> <!-- <el-row :gutter="20">
<el-col :span="24"> <el-col :span="24">
<el-form-item label="最新相关 BR IFIC 发布日期:" label-position="top"> <el-form-item label="最新相关 BR IFIC 发布日期:" label-position="top">
<el-input v-model="BFIFICdate" /> <el-input v-model="BRIFICdate" />
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row> </el-row> -->
</div> </div>
</el-form> </el-form>
</el-dialog> </el-dialog>
</div> </div>
<exportDialog v-model:dialogVisible="showDeleteDialog" @confirm="handleExportConfirm" /> <exportDialog v-model:dialogVisible="showDeleteDialog" />
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { onMounted, ref } from 'vue' import { onMounted, ref } from 'vue'
import Pagination from '@/components/pagination/index.vue' import Pagination from '@/components/pagination/index.vue'
import exportDialog from '@/components/Export/index.vue' import exportDialog from '@/components/Export/index.vue'
import { getItulist, getItuDetail, deleteItuData } from '@/api/spiderData' import { getItuList, getItuDetail, deleteItuData } from '@/api/spiderData'
import { ElMessage } from 'element-plus'
// ElConfigProvider 组件
import { ElConfigProvider } from 'element-plus';
// 引入中文包
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 = '页';
const searchTargetName = ref('')
const searchTimeValue = ref('')
const searchTargetId = ref('')
const showDeleteDialog = ref(false) const showDeleteDialog = ref(false)
const timeValue = ref('')
const detailVisibleValue = ref(false) const detailVisibleValue = ref(false)
// SNS Notice ID // SNS Notice ID
const snsId = ref('') const snsId = ref('')
...@@ -298,30 +334,61 @@ const validityPeriodSatelliteNetworkOldName = ref('') ...@@ -298,30 +334,61 @@ const validityPeriodSatelliteNetworkOldName = ref('')
const BFIFICdate = ref('') const BFIFICdate = ref('')
const tableData = ref([]) const tableData = ref([])
const pageObj = ref({ const pageObj = ref({
total: 10, total: 0,
pageSize: 10, pageSize: 10,
pageNo: 1 pageNo: 1
}) })
const handleDetails = (row: any) => { // 查看数据详情的方法
console.log(row); const handleDetails = async (id: any) => {
const res = await getItuDetail({ id: id._id })
console.log(res);
snsId.value = res.data.item.ntc_id
department.value = res.data.item.adm
targetName.value = res.data.identity.sat_name
planType.value = res.data.identity.plan_type
syncType.value = res.data.identity.ntc_type
syncPosition.value = res.data.identity.long_nom === null ? '无' : res.data.identity.long_nom
perigee.value = res.data.item.minPerigee === null ? '无' : res.data.item.minPerigee
apogee.value = res.data.item.maxApog === null ? '无' : res.data.item.maxApog
lowestAltitude.value = res.data.item.minAltitude === null ? '无' : res.data.item.minAltitude
referenceSubject.value = res.data.item.ref_body === null ? '无' : res.data.item.ref_body
lowestFrequency.value = res.data.item.freq_min === null ? '无' : res.data.item.freq_min
highestFrequency.value = res.data.item.freq_max === null ? '无' : res.data.item.freq_max
status.value = res.data.regulatory_status.active_status
validityPeriod.value = res.data.regulatory_status.prd_valid
earliestUsageDate.value = res.data.regulatory_status.d_reg_limit === null ? '无' : res.data.regulatory_status.d_reg_limit
isUsed.value = res.data.regulatory_status.f_biu_grps
isPauseUsed.value = res.data.regulatory_status.resumption_list
recordTime.value = res.data.crawl_time
IsInTheInternationalFrequencyList.value = res.data.regulatory_status.mifr
earliestRegulatoryDate.value = res.data.regulatory_status.d_inuse_list
isRestoreUsed.value = res.data.regulatory_status.resumption_list
validityPeriodSatelliteNetworkOldName.value = res.data.regulatory_status.old_satellite_network_name
BFIFICdate.value = res.data.regulatory_status.d_wic === null ? '无' : res.data.regulatory_status.d_wic
detailVisibleValue.value = true detailVisibleValue.value = true
} }
// 获取itu数据列表的方法
const getData = async () => { const getData = async () => {
const res = await getItulist({page:pageObj.value.pageNo,size:pageObj.value.pageSize}) const res = await getItuList({ page: pageObj.value.pageNo, size: pageObj.value.pageSize })
pageObj.value.total = res.data.length searchTargetName.value = ''
tableData.value = res.data searchTargetId.value = ''
searchTimeValue.value = ''
pageObj.value.total = res.data.total
tableData.value = res.data.list
} }
const handleClose = () => { const handleClose = () => {
detailVisibleValue.value = false detailVisibleValue.value = false
} }
const handleExportConfirm = () => { const handleSearch = async () => {
if (searchTargetName.value === '' && searchTargetId.value === '' && searchTimeValue.value === '') {
} ElMessage.warning('请输入搜索内容')
const handleExport = () => { return
showDeleteDialog.value = true }
const res = await getItuList({ sat_name: searchTargetName.value, ntc_id: searchTargetId.value, times: searchTimeValue.value, page: pageObj.value.pageNo, size: pageObj.value.pageSize })
pageObj.value.total = res.data.total
tableData.value = res.data.list
} }
onMounted(() => { onMounted(() => {
getData() getData()
}) })
...@@ -330,6 +397,10 @@ onMounted(() => { ...@@ -330,6 +397,10 @@ onMounted(() => {
<style scoped lang="scss"> <style scoped lang="scss">
// 调整表单项间距 // 调整表单项间距
.detailForm { .detailForm {
// background-image: url("@/assets/picture/dialog1.png");
// background-size: 100% 100%;
// background-repeat: no-repeat;
.el-dialog-title { .el-dialog-title {
font-size: 180px; font-size: 180px;
} }
......
...@@ -7,42 +7,34 @@ ...@@ -7,42 +7,34 @@
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<div> <div>
<el-input placeholder="请输入" style="width: 220px" /> <el-input placeholder="请输入" style="width: 180px" v-model="searchTargetName" />
</div> </div>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-text class="mx-1">获取时间:</el-text> <el-text class="mx-1">获取时间:</el-text>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-date-picker v-model="timeValue" type="datetimerange" format="YYYY-MM-DD HH:mm:ss" start-placeholder="开始时间" <el-config-provider :locale="zhCn">
end-placeholder="结束时间" date-format="YYYY/MM/DD ddd" time-format="A hh:mm:ss" /> <el-date-picker v-model="timeValue" type="datetimerange" format="YYYY-MM-DD HH:mm:ss"
start-placeholder="开始时间" end-placeholder="结束时间" date-format="YYYY/MM/DD ddd"
time-format="A hh:mm:ss" style="width: 330px" value-format="YYYY-MM-DD HH:mm:ss" />
</el-config-provider>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-text class="mx-1">站点名称:</el-text>
</el-form-item>
<el-form-item>
<el-input placeholder="请输入" style="width: 220px" />
</el-form-item>
<br></br>
<el-form-item>
<el-text class="mx-1">NORAD CAT ID:</el-text> <el-text class="mx-1">NORAD CAT ID:</el-text>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-input placeholder="请输入" style="width: 188.5px" /> <el-input placeholder="请输入" style="width: 170.5px" v-model="noradCatId" />
</el-form-item> </el-form-item>
<el-form-item> <!-- <el-form-item>
<el-space> <el-space>
<el-button type="primary" @click="handleExport">导出最近</el-button> <el-button type="primary" @click="handleExport">导出最近</el-button>
</el-space> </el-space>
</el-form-item> </el-form-item> -->
<el-form-item>
<el-space>
<el-button type="primary" @click="handleExport">导出选中</el-button>
</el-space>
</el-form-item>
<el-form-item> <el-form-item>
<el-space> <el-space>
<el-button type="primary">查询</el-button> <el-button type="primary" @click="handleSearch">查询</el-button>
<el-button type="primary" @click="getData">重置表格</el-button>
</el-space> </el-space>
</el-form-item> </el-form-item>
</el-form> </el-form>
...@@ -51,24 +43,26 @@ ...@@ -51,24 +43,26 @@
<div class="m-t-5" /> <div class="m-t-5" />
<div> <div>
<el-table :data="tableData" style="width: 100%" border :row-style="{ height: '42.5px' }" <el-table :data="tableData" style="width: 100%" border :row-style="{ height: '45.5px' }"
:header-cell-style="{ textAlign: 'center' }" :cell-style="{ textAlign: 'center' }"> :header-cell-style="{ textAlign: 'center' }" :cell-style="{ textAlign: 'center' }">
<el-table-column type="selection" width="40" /> <!-- <el-table-column type="selection" width="40" /> -->
<el-table-column property="number" label="序号" width="55" /> <el-table-column property="number" label="序号" width="55" type="index" />
<el-table-column property="NoradCatID" label="Norad Cat ID" show-overflow-tooltip /> <el-table-column property="data[0].NORAD_CAT_ID" label="Norad Cat ID" show-overflow-tooltip />
<el-table-column property="targetName" label="目标名称" show-overflow-tooltip /> <el-table-column property="data[0].OBJECT_NAME" label="目标名称" show-overflow-tooltip />
<el-table-column property="intleds" label="INTLDES" show-overflow-tooltip /> <!-- <el-table-column property="intleds" label="INTLDES" show-overflow-tooltip /> -->
<el-table-column property="type" label="类型" show-overflow-tooltip /> <el-table-column property="data[0].OBJECT_TYPE" label="类型" show-overflow-tooltip />
<el-table-column property="belongCountry" label="所属国家" width="82" show-overflow-tooltip /> <el-table-column property="data[0].COUNTRY_CODE" label="所属国家" width="82" show-overflow-tooltip />
<el-table-column property="runningTime" label="运行时间" show-overflow-tooltip /> <!-- <el-table-column property="runningTime" label="运行时间" show-overflow-tooltip /> -->
<el-table-column property="site" label="SITE" show-overflow-tooltip /> <el-table-column property="data[0].SITE" label="SITE" show-overflow-tooltip />
<el-table-column property="failTime" label="失效时间" show-overflow-tooltip /> <!-- <el-table-column property="failTime" label="失效时间" show-overflow-tooltip /> -->
<el-table-column property="period" label="周期" show-overflow-tooltip /> <el-table-column property="data[0].PERIOD" label="周期" show-overflow-tooltip />
<el-table-column property="perigee" label="近地点" show-overflow-tooltip /> <el-table-column property="data[0].PERIAPSIS" label="近地点" show-overflow-tooltip />
<el-table-column property="apogee" label="远地点" show-overflow-tooltip /> <el-table-column property="data[0].APOAPSIS" label="远地点" show-overflow-tooltip />
<el-table-column property="incl" label="INCL" show-overflow-tooltip /> <!-- <el-table-column property="incl" label="INCL" show-overflow-tooltip /> -->
<el-table-column property="rcs" label="RCS" show-overflow-tooltip /> <el-table-column property="data[0].RCS_SIZE" label="RCS" show-overflow-tooltip />
<el-table-column property="tle" label="TLE" show-overflow-tooltip /> <el-table-column property="data[0].TLE_LINE0" label="TLE_LINE0" show-overflow-tooltip />
<el-table-column property="data[0].TLE_LINE1" label="TLE_LINE1" show-overflow-tooltip />
<el-table-column property="data[0].TLE_LINE2" label="TLE_LINE2" show-overflow-tooltip />
<el-table-column label="操作" width="60"> <el-table-column label="操作" width="60">
<template #default="scope"> <template #default="scope">
<el-button type="primary" plain link @click="handleDetails(scope.row)"> <el-button type="primary" plain link @click="handleDetails(scope.row)">
...@@ -106,8 +100,8 @@ ...@@ -106,8 +100,8 @@
</el-row> </el-row>
<el-row :gutter="20"> <el-row :gutter="20">
<el-col :span="12"> <el-col :span="12">
<el-form-item label="INTLDES:" label-position="left"> <el-form-item label="周期(min):" label-position="left">
<el-input v-model="intleds" /> <el-input v-model="period" />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
...@@ -116,7 +110,7 @@ ...@@ -116,7 +110,7 @@
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row> </el-row>
<el-row :gutter="20"> <!-- <el-row :gutter="20">
<el-col :span="12"> <el-col :span="12">
<el-form-item label="运行时间:" label-position="left"> <el-form-item label="运行时间:" label-position="left">
<el-input v-model="workTime" /> <el-input v-model="workTime" />
...@@ -127,7 +121,7 @@ ...@@ -127,7 +121,7 @@
<el-input v-model="failTime" /> <el-input v-model="failTime" />
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row> </el-row> -->
<el-row :gutter="20"> <el-row :gutter="20">
<el-col :span="12"> <el-col :span="12">
<el-form-item label="记录时间:" label-position="left"> <el-form-item label="记录时间:" label-position="left">
...@@ -154,18 +148,6 @@ ...@@ -154,18 +148,6 @@
</el-row> </el-row>
<el-row :gutter="20"> <el-row :gutter="20">
<el-col :span="12"> <el-col :span="12">
<el-form-item label="周期(min):" label-position="left">
<el-input v-model="period" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="INCL:" label-position="left">
<el-input v-model="incl" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="RCS:" label-position="left"> <el-form-item label="RCS:" label-position="left">
<el-input v-model="rcs" /> <el-input v-model="rcs" />
</el-form-item> </el-form-item>
...@@ -187,14 +169,27 @@ ...@@ -187,14 +169,27 @@
</el-form> </el-form>
</el-dialog> </el-dialog>
</div> </div>
<exportDialog v-model:dialogVisible="showDeleteDialog" @confirm="handleExportConfirm" /> <exportDialog v-model:dialogVisible="showDeleteDialog" />
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue' import { onMounted, ref } from 'vue'
import Pagination from '@/components/pagination/index.vue' import Pagination from '@/components/pagination/index.vue'
import exportDialog from '@/components/Export/index.vue' import exportDialog from '@/components/Export/index.vue'
import { getStList, getStDetail, deleteStData } from '@/api/spiderData'
import { ElMessage } from 'element-plus'
// ElConfigProvider 组件
import { ElConfigProvider } from 'element-plus';
// 引入中文包
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 = '页';
const searchTargetName = ref('')
const noradCatId = ref('')
const showDeleteDialog = ref(false) const showDeleteDialog = ref(false)
const timeValue = ref('') const timeValue = ref('')
const detailVisibleValue = ref(false) const detailVisibleValue = ref(false)
...@@ -206,10 +201,6 @@ const targetName = ref('') ...@@ -206,10 +201,6 @@ const targetName = ref('')
const intleds = ref('') const intleds = ref('')
// 类型 // 类型
const type = ref('') const type = ref('')
// 运行时间
const workTime = ref('')
// 失效时间
const failTime = ref('')
// 记录时间 // 记录时间
const recordTime = ref('') const recordTime = ref('')
// 所属国家 // 所属国家
...@@ -229,73 +220,55 @@ const site = ref('') ...@@ -229,73 +220,55 @@ const site = ref('')
// TLE // TLE
const tle = ref('') const tle = ref('')
const tableData = ref([ const tableData = ref([])
{
number: '1',
NoradCatID: '1358',
targetName: 'SL-1 R/B',
intleds: '1957-001A',
type: 'ROCKET BODY',
belongCountry: 'RUS',
runningTime: '1957-10-14',
site: 'TTMTR',
failTime: '1958-12-01',
period: '96.19',
perigee: '214',
apogee: '938',
incl: '65.14',
rcs: 'LARGE',
tle: '-',
},
{
name: '1',
},
{
name: '1',
},
{
name: '1',
},
{
name: '1',
},
{
name: '1',
},
{
name: '1',
},
{
name: '1',
},
{
name: '1',
},
{
name: '1',
},
])
const pageObj = ref({ const pageObj = ref({
total: 10, total: 10,
pageSize: 10, pageSize: 10,
pageNo: 1 pageNo: 1
}) })
const handleDetails = (row: any) => { // 查看数据详情的方法
console.log(row); const handleDetails = async (row: any) => {
const res = await getStDetail({ id: row._id })
NoradCatID.value = res.data.data[0].NORAD_CAT_ID
targetName.value = res.data.data[0].OBJECT_NAME
type.value = res.data.data[0].OBJECT_TYPE
period.value = res.data.data[0].PERIOD
intleds.value = res.data.data[0].INTLDES
recordTime.value = res.data.data[0].CREATION_DATE
belongCountry.value = res.data.data[0].COUNTRY_CODE
perigee.value = res.data.data[0].PERIAPSIS
apogee.value = res.data.data[0].APOAPSIS
rcs.value = res.data.data[0].RCS_SIZE
site.value = res.data.data[0].SITE
tle.value = `${res.data.data[0].TLE_LINE0}
${res.data.data[0].TLE_LINE1}
${res.data.data[0].TLE_LINE2}`
detailVisibleValue.value = true detailVisibleValue.value = true
} }
const getData = () => { // 获取st数据列表的方法
console.log('getData'); const getData = async () => {
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 = ''
} }
const handleClose = () => { const handleClose = () => {
detailVisibleValue.value = false detailVisibleValue.value = false
} }
const handleExportConfirm = () => { const handleSearch = async () => {
if (searchTargetName.value === '' && noradCatId.value === '' && timeValue.value === '') {
} ElMessage.warning('请输入搜索内容')
const handleExport = () => { return
showDeleteDialog.value = true }
const res = await getStList({ norad_cat_id: noradCatId.value, object_name: searchTargetName.value, times: timeValue.value, page: pageObj.value.pageNo, size: pageObj.value.pageSize })
tableData.value = res.data.list
pageObj.value.total = res.data.total
} }
onMounted(() => {
getData()
})
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
...@@ -332,6 +305,10 @@ const handleExport = () => { ...@@ -332,6 +305,10 @@ const handleExport = () => {
</style> </style>
<style> <style>
.el-textarea__inner {
color: #FFFFFF;
}
.el-input { .el-input {
--el-input-text-color: #FFFFFF; --el-input-text-color: #FFFFFF;
} }
......
...@@ -2,13 +2,14 @@ ...@@ -2,13 +2,14 @@
<div> <div>
<div class="backStyle" v-if="route.query.jump === 'yes'" @click="goToStatus" /> <div class="backStyle" v-if="route.query.jump === 'yes'" @click="goToStatus" />
<div class="text-left p-4 "> <div class="text-left p-4 ">
<div class="custom-style"> <div class="custom-style flex gap-4">
<el-segmented v-model="mode" :options="sizeOptions" style="margin-bottom: 1rem" size="default" /> <el-segmented v-model="mode" :options="sizeOptions" style="margin-bottom: 1rem" size="default" />
<el-button type="primary" @click="handleExport">导出</el-button>
</div> </div>
</div> </div>
<!-- 综合数据页面组件 --> <!-- 综合数据页面组件 -->
<allDataTab v-if="mode === '综合数据'"> <!-- <allDataTab v-if="mode === '综合数据'">
</allDataTab> </allDataTab> -->
<!-- DSN数据页面组件 --> <!-- DSN数据页面组件 -->
<dsnDataTab v-if="mode === 'DSN数据'"> <dsnDataTab v-if="mode === 'DSN数据'">
</dsnDataTab> </dsnDataTab>
...@@ -18,7 +19,12 @@ ...@@ -18,7 +19,12 @@
<!-- ST数据页面组件 --> <!-- ST数据页面组件 -->
<stDataTab v-if="mode === 'ST数据'"> <stDataTab v-if="mode === 'ST数据'">
</stDataTab> </stDataTab>
<!-- ESA数据页面组件 -->
<esDataTab v-if="mode === 'ESA数据'">
</esDataTab>
</div> </div>
<exportDialog v-model:dialogVisible="showDeleteDialog" @confirm="handleExportConfirm" />
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
...@@ -29,24 +35,46 @@ import allDataTab from './components/allDataTab.vue' ...@@ -29,24 +35,46 @@ import allDataTab from './components/allDataTab.vue'
import ituDataTab from './components/ituDataTab.vue' import ituDataTab from './components/ituDataTab.vue'
import stDataTab from './components/stDataTab.vue' 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 exportDialog from '@/components/Export/index.vue'
import { genFileId } from 'element-plus'
import type { UploadInstance, UploadProps, UploadRawFile } from 'element-plus'
const mode = ref('综合数据') const mode = ref('DSN数据')
const sizeOptions = ['综合数据', 'DSN数据', 'ITU数据', 'ST数据'] const showDeleteDialog = ref(false)
const sizeOptions = ['DSN数据', 'ITU数据', 'ST数据', 'ESA数据']
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()
const modeValue = ref<any>('数据展示') const modeValue = ref<any>('数据展示')
const upload = ref<UploadInstance>()
const handleExceed: UploadProps['onExceed'] = (files) => {
upload.value!.clearFiles()
const file = files[0] as UploadRawFile
file.uid = genFileId()
upload.value!.handleStart(file)
}
const submitUpload = () => {
upload.value!.submit()
}
const goToStatus = () => { const goToStatus = () => {
router.push({ router.push({
path: '/osStatus/list', path: '/osStatus/list',
}) })
} }
const handleExport = () => {
showDeleteDialog.value = true
}
const handleExportConfirm = () => {
}
onMounted(() => { onMounted(() => {
console.log('数据展示');
console.log(route.query);
if (route.query.mode) { if (route.query.mode) {
modeValue.value = route.query.mode modeValue.value = route.query.mode
mode.value = modeValue.value mode.value = modeValue.value
} }
console.log(mode.value)
}) })
</script> </script>
......
...@@ -36,14 +36,17 @@ const pageObj = ref({ ...@@ -36,14 +36,17 @@ const pageObj = ref({
pageNo: 1 pageNo: 1
}) })
const handleDetails = (row: any) => { const handleDetails = (row: any) => {
console.log(row); // console.log(row);
router.push({ router.push({
path: '/osTaskInformation/list', path: '/osTaskInformation/list',
query: { query: {
jump: 'yes' jump: 'yes',
spiderType: row.spider,
page:'spiderManager'
} }
}) })
} }
// 获取爬虫数据的方法
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
......
...@@ -6,13 +6,13 @@ ...@@ -6,13 +6,13 @@
<span>数据统计</span> <span>数据统计</span>
</div> </div>
<div class="wordStyle"> <div class="wordStyle">
<span>总数据量: {{ totalDataNumber }} 1230</span> <span>总数据量: {{ totalDataNumber }}</span>
</div> </div>
<div class="wordStyle"> <div class="wordStyle">
<span>采集的页面数量: {{ totalPageNumber }} 85</span> <span>采集的页面数量: {{ totalPageNumber }}</span>
</div> </div>
<div class="wordStyle"> <div class="wordStyle">
<span>采集的目标数量: {{ totalTargetNumber }} 569</span> <span>采集的目标数量: {{ totalTargetNumber }}</span>
</div> </div>
</div> </div>
<div class="dataCard" @click="goToTaskRecordPage"> <div class="dataCard" @click="goToTaskRecordPage">
...@@ -20,13 +20,13 @@ ...@@ -20,13 +20,13 @@
<span>任务执行统计</span> <span>任务执行统计</span>
</div> </div>
<div class="wordStyle"> <div class="wordStyle">
<span>任务执行成功统计: {{ successTask }} 36</span> <span>任务执行成功统计: {{ successTask }}</span>
</div> </div>
<div class="wordStyle"> <div class="wordStyle">
<span>任务执行失败统计: {{ failTask }} 0</span> <span>任务执行失败统计: {{ failTask }}</span>
</div> </div>
<div class="wordStyle"> <div class="wordStyle">
<span>任务异常数统计: {{ unusualTask }} 1</span> <span>任务异常数统计: {{ unusualTask }}</span>
</div> </div>
</div> </div>
<div class="dataCard"> <div class="dataCard">
...@@ -34,13 +34,13 @@ ...@@ -34,13 +34,13 @@
<span>性能统计</span> <span>性能统计</span>
</div> </div>
<div class="wordStyle"> <div class="wordStyle">
<span>平均成功率: {{ speed }} 98%</span> <span>平均成功率: {{ speed }}</span>
</div> </div>
<div class="wordStyle"> <div class="wordStyle">
<span>平均错误率: {{ errorRate }} 0%</span> <span>平均错误率: {{ errorRate }}</span>
</div> </div>
<div class="wordStyle"> <div class="wordStyle">
<span>平均异常率: {{ errorRate }} 2%</span> <span>平均异常率: {{ unusualRate }}</span>
</div> </div>
</div> </div>
</div> </div>
...@@ -49,48 +49,26 @@ ...@@ -49,48 +49,26 @@
<script lang="ts" setup> <script lang="ts" setup>
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { getStatsDataList, getSpiderTaskList, getPerformanceList } from '@/api/staticData';
import { onMounted, ref } from 'vue';
const router = useRouter()
const totalDataNumber = ref('')
const totalPageNumber = ref('')
const totalTargetNumber = ref('')
const successTask = ref('')
const failTask = ref('')
const unusualTask = ref('')
const speed = ref('')
const errorRate = ref('')
const unusualRate = ref('')
const router = useRouter();
defineProps({
totalDataNumber: {
type: String,
default: ''
},
totalPageNumber: {
type: String,
default: ''
},
totalTargetNumber: {
type: String,
default: ''
},
successTask: {
type: String,
default: ''
},
failTask: {
type: String,
default: ''
},
unusualTask: {
type: String,
default: ''
},
speed: {
type: String,
default: ''
},
errorRate: {
type: String,
default: ''
}
})
const goToAllDataPage = () => { const goToAllDataPage = () => {
router.push({ router.push({
path: '/osDataDisplay/list', path: '/osDataDisplay/list',
query: { query: {
jump: 'yes', jump: 'yes',
mode: '综合数据' mode: 'DSN数据'
} }
}) })
} }
...@@ -105,7 +83,25 @@ const goToTaskRecordPage = () => { ...@@ -105,7 +83,25 @@ const goToTaskRecordPage = () => {
}) })
} }
// 获取数据统计数据的方法
const getData = async () => {
const staticData = await getStatsDataList({})
const spiderTask = await getSpiderTaskList({})
const performance = await getPerformanceList({})
totalDataNumber.value = staticData.data.totalData
totalPageNumber.value = staticData.data.ituPage
totalTargetNumber.value = staticData.data.spaceTrackItemCount
successTask.value = spiderTask.data.successCount
failTask.value = spiderTask.data.failCount
unusualTask.value = spiderTask.data.exceptionCount
speed.value = performance.data.success
errorRate.value = performance.data.error
unusualRate.value = performance.data.exception
}
onMounted(() => {
getData();
})
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
......
<template> <template>
<div class="flex gap-10"> <div class="flex gap-10">
<span class="textStyle">QB数据管理</span> <span class="textStyle">QB数据管理</span>
<div class="dataCard" @click="goToAllDataPage"> <!-- <div class="dataCard" @click="goToAllDataPage">
<div class="titleStyle"> <div class="titleStyle">
<span>综合数据</span> <span>综合数据</span>
</div> </div>
<div class="iconStyle" /> <div class="iconStyle" />
</div> </div> -->
<div class="dataCard" @click="goToDSNDataPage"> <div class="dataCard" @click="goToDSNDataPage">
<div class="titleStyle"> <div class="titleStyle">
<span>DSN数据</span> <span>DSN数据</span>
...@@ -110,10 +110,10 @@ const goToSTDataPage = () => { ...@@ -110,10 +110,10 @@ const goToSTDataPage = () => {
background-image: url("@/assets/picture/wenjianjia.png"); background-image: url("@/assets/picture/wenjianjia.png");
background-size: 100% 120%; background-size: 100% 120%;
background-repeat: no-repeat; background-repeat: no-repeat;
margin-top: 3%; margin-top: -5%;
display: flex; display: flex;
// border: 1.5px solid rgb(193, 188, 188); // border: 1.5px solid rgb(193, 188, 188);
height: 50%; height: 65%;
width: 50%; width: 50%;
} }
...@@ -123,7 +123,7 @@ const goToSTDataPage = () => { ...@@ -123,7 +123,7 @@ const goToSTDataPage = () => {
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: 260px; width: 360px;
height: 100%; height: 100%;
border-radius: 5px; border-radius: 5px;
display: flex; display: flex;
......
...@@ -6,16 +6,16 @@ ...@@ -6,16 +6,16 @@
<span>DSN爬取任务</span> <span>DSN爬取任务</span>
</div> </div>
<div class="wordStyle"> <div class="wordStyle">
<span>任务采集目标数: {{ totalDataNumber }} 638</span> <span>DSN爬虫任务数: {{ dsnTotalTaskNumber }} </span>
</div> </div>
<div class="wordStyle"> <div class="wordStyle">
<span>任务执行成功统计: {{ totalPageNumber }} 79</span> <span>任务执行成功统计: {{ dsnTaskSuccessNumber }} </span>
</div> </div>
<div class="wordStyle"> <div class="wordStyle">
<span>采集速度: {{ totalTargetNumber }} 324</span> <span>任务执行失败统计: {{ dsnTaskFailNumber }} </span>
</div> </div>
<div class="wordStyle"> <div class="wordStyle">
<span>错误率: {{ totalTargetNumber }} 1%</span> <span>错误率: {{ dsnErrorRate }} </span>
</div> </div>
</div> </div>
<div class="dataCard" @click="goToITUTaskRecordPage"> <div class="dataCard" @click="goToITUTaskRecordPage">
...@@ -23,16 +23,16 @@ ...@@ -23,16 +23,16 @@
<span>ITU爬取任务</span> <span>ITU爬取任务</span>
</div> </div>
<div class="wordStyle"> <div class="wordStyle">
<span>任务采集目标数: {{ totalDataNumber }} 148</span> <span>ITU爬虫任务数: {{ ituTotalTaskNumber }} </span>
</div> </div>
<div class="wordStyle"> <div class="wordStyle">
<span>任务执行成功统计: {{ totalPageNumber }} 98</span> <span>任务执行成功统计: {{ ituTaskSuccessNumber }} </span>
</div> </div>
<div class="wordStyle"> <div class="wordStyle">
<span>采集速度: {{ totalTargetNumber }} 322</span> <span>任务执行失败统计: {{ ituTaskFailNumber }} </span>
</div> </div>
<div class="wordStyle"> <div class="wordStyle">
<span>错误率: {{ totalTargetNumber }} 1%</span> <span>错误率: {{ ituErrorRate }} </span>
</div> </div>
</div> </div>
<div class="dataCard" @click="goToSTTaskRecordPage"> <div class="dataCard" @click="goToSTTaskRecordPage">
...@@ -40,16 +40,16 @@ ...@@ -40,16 +40,16 @@
<span>ST爬取任务</span> <span>ST爬取任务</span>
</div> </div>
<div class="wordStyle"> <div class="wordStyle">
<span>任务采集目标数: {{ totalDataNumber }} 136</span> <span>ST爬虫任务数: {{ stTotalTaskNumber }} </span>
</div> </div>
<div class="wordStyle"> <div class="wordStyle">
<span>任务执行成功统计: {{ totalPageNumber }} 86</span> <span>任务执行成功统计: {{ stTaskSuccessNumber }} </span>
</div> </div>
<div class="wordStyle"> <div class="wordStyle">
<span>采集速度: {{ totalTargetNumber }} 342</span> <span>采集速度: {{ stTaskFailNumber }} </span>
</div> </div>
<div class="wordStyle"> <div class="wordStyle">
<span>错误率: {{ totalTargetNumber }} 0%</span> <span>错误率: {{ stErrorRate }} </span>
</div> </div>
</div> </div>
</div> </div>
...@@ -57,71 +57,80 @@ ...@@ -57,71 +57,80 @@
<script lang="ts" setup> <script lang="ts" setup>
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { getAllSpiderTaskStatistics } from '@/api/staticData';
import { onMounted,ref } from 'vue';
import { getSpiderTaskList } from '@/api/spiderTask'
const router = useRouter() const router = useRouter()
defineProps({
totalDataNumber: { const dsnTotalTaskNumber = ref('')
type: String, const dsnTaskSuccessNumber = ref('')
default: '' const dsnTaskFailNumber = ref('')
}, const dsnErrorRate = ref('')
totalPageNumber: { const ituTotalTaskNumber = ref('')
type: String, const ituTaskSuccessNumber = ref('')
default: '' const ituTaskFailNumber = ref('')
}, const ituErrorRate = ref('')
totalTargetNumber: { const stTotalTaskNumber = ref('')
type: String, const stTaskSuccessNumber = ref('')
default: '' const stTaskFailNumber = ref('')
}, const stErrorRate = ref('')
successTask: {
type: String,
default: ''
},
failTask: {
type: String,
default: ''
},
unusualTask: {
type: String,
default: ''
},
speed: {
type: String,
default: ''
},
errorRate: {
type: String,
default: ''
}
})
const goToDSNTaskRecordPage = () => { const goToDSNTaskRecordPage = () => {
router.push({ router.push({
path: '/osTaskRecord/list', path: '/osTaskInformation/list',
query: { query: {
mode: 'dsn', spiderType: 'dsn_now',
jump: 'yes' jump: 'yes',
page: 'statusMonitor'
} }
}) })
} }
const goToITUTaskRecordPage = () => { const goToITUTaskRecordPage = () => {
router.push({ router.push({
path: '/osTaskRecord/list', path: '/osTaskInformation/list',
query: { query: {
mode: 'itu', spiderType: 'itu_space_explorer',
jump: 'yes' jump: 'yes',
page: 'statusMonitor'
} }
}) })
} }
const goToSTTaskRecordPage = () => { const goToSTTaskRecordPage = () => {
router.push({ router.push({
path: '/osTaskRecord/list', path: '/osTaskInformation/list',
query: { query: {
mode: 'st', spiderType: 'api_spider',
jump: 'yes' jump: 'yes',
page: 'statusMonitor'
} }
}) })
} }
// 获取任务统计数据的方法
const getData = async () => {
const res = await getAllSpiderTaskStatistics({})
const dsnTask = await getSpiderTaskList({ spiders: 'dsn_now' })
const ituTask = await getSpiderTaskList({ spiders: 'itu_space_explorer' })
const stTask = await getSpiderTaskList({ spiders: 'api_spider' })
dsnTotalTaskNumber.value = dsnTask.data.length
dsnTaskSuccessNumber.value = res.data.dsn_now.successCount
dsnTaskFailNumber.value = res.data.dsn_now.failCount
dsnErrorRate.value = res.data.dsn_now.errorRate
ituTotalTaskNumber.value = ituTask.data.length
ituTaskSuccessNumber.value = res.data.itu_space_explorer.successCount
ituTaskFailNumber.value = res.data.itu_space_explorer.failCount
ituErrorRate.value = res.data.itu_space_explorer.errorRate
stTotalTaskNumber.value = stTask.data.length
stTaskSuccessNumber.value = res.data.space_track.successCount
stTaskFailNumber.value = res.data.space_track.failCount
stErrorRate.value = res.data.space_track.errorRate
}
onMounted(() => {
getData()
})
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
......
<template> <template>
<el-dialog v-model="currentVisible" :title="currentMode === AddMode.ADD_MODE ? '新增用户' : '编辑用户'" width="610" center align-center <el-dialog v-model="currentVisible" :title="currentMode === AddMode.ADD_MODE ? '新增用户' : '编辑用户'" width="480" center align-center
@close="handleClose" draggable> @close="handleClose" draggable>
<div v-if="currentMode === AddMode.ADD_MODE"> <div v-if="currentMode === AddMode.ADD_MODE">
<el-form :rules="firstFormRules" ref="firstRuleFormRef" :model="firstForm"> <el-form :rules="firstFormRules" ref="firstRuleFormRef" :model="firstForm">
<el-form-item label="用户账号:" prop="userAccount"> <el-form-item label="用户账号:" prop="userAccount" label-width="100px">
<el-input v-model="firstForm.userAccount" /> <el-input v-model="firstForm.userAccount" style="width: 300px;"/>
</el-form-item> </el-form-item>
<el-form-item label="用户名称:" prop="userName"> <el-form-item label="用户名称:" prop="userName" label-width="100px">
<el-input v-model="firstForm.userName" /> <el-input v-model="firstForm.userName" style="width: 300px"/>
</el-form-item> </el-form-item>
<el-form-item label="用户状态:"> <el-form-item label="用户状态:" label-width="100px">
<el-select v-model="userStatusValue" placeholder="请选择状态" style="width: 100%;"> <el-select v-model="userStatusValue" placeholder="请选择状态" style="width: 300px;">
<el-option v-for="item in userStatusOptions" :key="item.value" :label="item.label" :value="item.value" /> <el-option v-for="item in userStatusOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="用户密码:" prop="userPassword"> <el-form-item label="用户密码:" prop="userPassword" label-width="100px">
<el-input v-model="firstForm.userPassword" /> <el-input v-model="firstForm.userPassword" style="width: 300px;"/>
</el-form-item> </el-form-item>
<el-form-item label="用户角色:"> <el-form-item label="用户角色:" label-width="100px">
<el-select v-model="userRoleValue" placeholder="请选择角色" style="width: 100%;"> <el-select v-model="userRoleValue" placeholder="请选择角色" style="width: 300px;">
<el-option v-for="item in userRoleOptions" :key="item.value" :label="item.label" :value="item.value" /> <el-option v-for="item in userRoleOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select> </el-select>
</el-form-item> </el-form-item>
...@@ -26,24 +26,24 @@ ...@@ -26,24 +26,24 @@
</div> </div>
<div v-if="currentMode === AddMode.UPDATE_MODE"> <div v-if="currentMode === AddMode.UPDATE_MODE">
<el-form :inline="true" :rules="secondFormRules" ref="secondRuleFormRef" :model="secondForm"> <el-form :inline="true" :rules="secondFormRules" ref="secondRuleFormRef" :model="secondForm">
<el-form-item label="用户名称:" prop="userName"> <el-form-item label="用户名称:" prop="userName" label-width="100px">
<el-input v-model="secondForm.userName" /> <el-input v-model="secondForm.userName" style="width: 300px;"/>
</el-form-item> </el-form-item>
<el-form-item label="用户角色:"> <el-form-item label="用户角色:" label-width="100px">
<el-select v-model="userRoleValue" placeholder="请选择角色" style="width: 180px;"> <el-select v-model="userRoleValue" placeholder="请选择角色" style="width: 300px;">
<el-option v-for="item in userRoleOptions" :key="item.value" :label="item.label" :value="item.value" /> <el-option v-for="item in userRoleOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-form> </el-form>
<div class="m-t-4" /> <div class="m-t-4" />
<el-form :inline="true" :rules="editPasswordRules" ref="editPasswordFormRef" :model="editPasswordForm"> <el-form :inline="true" :rules="editPasswordRules" ref="editPasswordFormRef" :model="editPasswordForm">
<el-form-item label="用户状态:"> <el-form-item label="用户状态:" label-width="100px">
<el-select v-model="userStatusValue" placeholder="请选择状态" style="width: 180px;"> <el-select v-model="userStatusValue" placeholder="请选择状态" style="width: 300px;">
<el-option v-for="item in userStatusOptions" :key="item.value" :label="item.label" :value="item.value" /> <el-option v-for="item in userStatusOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="用户密码:" prop="userPassword"> <el-form-item label="用户密码:" prop="userPassword" label-width="100px">
<el-input v-model="editPasswordForm.userPassword" placeholder="不修改请留空" /> <el-input v-model="editPasswordForm.userPassword" placeholder=" " style="width: 300px;"/>
</el-form-item> </el-form-item>
</el-form> </el-form>
</div> </div>
...@@ -100,12 +100,12 @@ const editPasswordFormRef = ref<FormInstance>() ...@@ -100,12 +100,12 @@ const editPasswordFormRef = ref<FormInstance>()
const firstFormRules = ref<FormRules>({ const firstFormRules = ref<FormRules>({
userAccount: [ userAccount: [
{ required: true, message: '请输入用户账号', trigger: 'blur' }, { required: true, message: '请输入用户账号', trigger: 'blur' },
{ type: 'string', min: 1, max: 20, message: '用户账号长度1-20字符', trigger: 'blur' }, { type: 'string', min: 3, max: 20, message: '用户账号长度3-20字符', trigger: 'blur' },
{ pattern: /^[A-Za-z0-9]+$/, message: '只能包含英文字母或数字', trigger: 'blur' } { pattern: /^[A-Za-z0-9]+$/, message: '用户账号只能包含英文字母或数字', trigger: 'blur' }
], ],
userName: [ userName: [
{ required: true, message: '请输入用户名称', trigger: 'blur' }, { required: true, message: '请输入用户名称', trigger: 'blur' },
{ type: 'string', min: 1, max: 20, message: '用户名称长度1-20字符', trigger: 'blur' }, { type: 'string', min: 2, max: 20, message: '用户名称长度2-20字符', trigger: 'blur' },
], ],
userPassword: [ userPassword: [
{ required: true, message: '请输入用户密码', trigger: 'blur' }, { required: true, message: '请输入用户密码', trigger: 'blur' },
...@@ -126,38 +126,38 @@ const editPasswordRules = ref<FormRules>({ ...@@ -126,38 +126,38 @@ const editPasswordRules = ref<FormRules>({
], ],
}) })
// 状态选项(统一值类型,避免类型错误)
const userStatusValue = ref(true) const userStatusValue = ref(true)
const userRoleValue = ref(1) const userRoleValue = ref(1)
const userStatusOptions = [ const userStatusOptions = [
{ value: true, label: '启用' }, { value: true, label: '启用' },
{ value: false, label: '停用' } // 与userStatusValue类型一致(boolean) { value: false, label: '停用' }
] ]
const userRoleOptions = [ const userRoleOptions = [
{ value: false, label: '管理员' }, { value: 1, label: '管理员' },
{ value: true, label: '普通用户' } // 统一值类型 { value: 0, label: '普通用户' }
] ]
const emit = defineEmits(['update:dialogVisible', 'getUserList']) const emit = defineEmits(['update:dialogVisible', 'getUserList'])
// 组件状态 // 组件状态
const currentVisible = ref(props.dialogVisible) const currentVisible = ref(props.dialogVisible)
const currentMode = ref(props.mode) const currentMode = ref(props.mode)
// 关闭对话框 // 关闭对话框的方法
const handleClose = () => { const handleClose = () => {
firstRuleFormRef.value?.resetFields()
secondRuleFormRef.value?.resetFields()
editPasswordFormRef.value?.resetFields()
currentVisible.value = false currentVisible.value = false
} }
// 确认操作 // 创建用户与编辑用户的方法
const handleConfirm = async () => { const handleConfirm = async () => {
try { try {
if (currentMode.value === AddMode.ADD_MODE) { if (currentMode.value === AddMode.ADD_MODE) {
if (!firstRuleFormRef.value) { if (!firstRuleFormRef.value) {
return; return;
} }
// 使用Promise形式的validate(不传入回调),确保await生效
const valid = await firstRuleFormRef.value.validate(); const valid = await firstRuleFormRef.value.validate();
if (valid) { if (valid) {
await addUser({ await addUser({
...@@ -172,7 +172,6 @@ const handleConfirm = async () => { ...@@ -172,7 +172,6 @@ const handleConfirm = async () => {
currentVisible.value = false; currentVisible.value = false;
} }
} else if (currentMode.value === AddMode.UPDATE_MODE) { } else if (currentMode.value === AddMode.UPDATE_MODE) {
// 编辑模式逻辑(略,可参考新增模式调整)
if (!secondRuleFormRef.value) return; if (!secondRuleFormRef.value) return;
const valid = await secondRuleFormRef.value.validate(); const valid = await secondRuleFormRef.value.validate();
if (valid) { if (valid) {
...@@ -211,6 +210,7 @@ watch(() => props.mode, (newVal) => { ...@@ -211,6 +210,7 @@ watch(() => props.mode, (newVal) => {
currentMode.value = newVal currentMode.value = newVal
}) })
// 监听参数变化,将参数展示到对话框中
watch( watch(
[() => props.dialogVisible, () => props.mode, () => props.nickName, () => props.userPassword, () => props.userStatus, () => props.userRole], [() => props.dialogVisible, () => props.mode, () => props.nickName, () => props.userPassword, () => props.userStatus, () => props.userRole],
([newVisible, newMode, newNickName, newPwd, newStatus, newRole]) => { ([newVisible, newMode, newNickName, newPwd, newStatus, newRole]) => {
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
<div class="text-left p-8"> <div class="text-left p-8">
<el-form inline> <el-form inline>
<el-form-item> <el-form-item>
<el-button type="primary" plain @click="openAddUserDialog">创建用户</el-button> <el-button test-element="userSystem-AddUser" type="primary" plain @click="openAddUserDialog">创建用户</el-button>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-button type="danger" plain @click="handleBatchDelete">批量删除</el-button> <el-button type="danger" plain @click="handleBatchDelete">批量删除</el-button>
...@@ -20,7 +20,7 @@ ...@@ -20,7 +20,7 @@
<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)"> <el-button type="primary" plain @click="handleEdit(scope.row)" id="editUser">
编辑 编辑
</el-button> </el-button>
<el-button type="danger" plain @click="handleDelete(scope.row)"> <el-button type="danger" plain @click="handleDelete(scope.row)">
...@@ -48,10 +48,10 @@ import { ref, onMounted } from 'vue' ...@@ -48,10 +48,10 @@ import { ref, onMounted } from 'vue'
import Pagination from '@/components/pagination/index.vue' import Pagination from '@/components/pagination/index.vue'
import deleteDialog from '@/components/Delete/index.vue' import deleteDialog from '@/components/Delete/index.vue'
import addUserDialog from './components/addUserDialog.vue' import addUserDialog from './components/addUserDialog.vue'
import { getUserList, addUser, deleteUser, updateUser, batchDeleteUser } from '@/api/user.ts' import { getUserList } from '@/api/user.ts'
import { DeleteMode } from '@/components/Delete/enum.ts' import { DeleteMode } from '@/components/Delete/enum.ts'
import { AddMode } from './components/enum.ts'; import { AddMode } from './components/enum.ts';
import { ElMessage } from 'element-plus'
const userId = ref<any>([]) const userId = ref<any>([])
const userIds = ref<any>() const userIds = ref<any>()
const nickName = ref<string>('') const nickName = ref<string>('')
...@@ -65,10 +65,11 @@ const dialogVisible = ref<boolean>(false) ...@@ -65,10 +65,11 @@ const dialogVisible = ref<boolean>(false)
const mode = ref<AddMode>(AddMode.ADD_MODE) const mode = ref<AddMode>(AddMode.ADD_MODE)
const tableData = ref() const tableData = ref()
const pageObj = ref({ const pageObj = ref({
total: 12, total: 0,
pageSize: 10, pageSize: 10,
pageNo: 1 pageNo: 1
}) })
// 编辑用户信息的方法
const handleEdit = async (row: any) => { const handleEdit = async (row: any) => {
mode.value = AddMode.UPDATE_MODE mode.value = AddMode.UPDATE_MODE
nickName.value = row.nickname nickName.value = row.nickname
...@@ -78,41 +79,49 @@ const handleEdit = async (row: any) => { ...@@ -78,41 +79,49 @@ const handleEdit = async (row: any) => {
userPassword.value = row.password userPassword.value = row.password
dialogVisible.value = true dialogVisible.value = true
userId.value = row.id userId.value = row.id
// console.log(row);
} }
// 弹窗关闭的方法
const handleDelete = async (row: any) => { const handleDelete = async (row: any) => {
showDeleteDialog.value = true showDeleteDialog.value = true
deleteMode.value = DeleteMode.SINGLE_DELETE deleteMode.value = DeleteMode.SINGLE_DELETE
userId.value = row.id userId.value = row.id
console.log(userId.value); console.log(userId.value);
} }
// 批量删除用户的方法
const handleBatchDelete = async () => { const handleBatchDelete = async () => {
if(userIds.value == undefined){
ElMessage({
message: '请先选择要删除的用户',
type: 'warning'
})
return
}else{
deleteMode.value = DeleteMode.BATCH_DELETE deleteMode.value = DeleteMode.BATCH_DELETE
showDeleteDialog.value = true showDeleteDialog.value = true
}
} }
// 多选框改变后的方法
const handleSelectionChange = (data: any) => { const handleSelectionChange = (data: any) => {
let array = [] let array = []
for (let i = 0; i < data.length; i++) { for (let i = 0; i < data.length; i++) {
array.push(data[i].id) array.push(data[i].id)
} }
userIds.value = array userIds.value = array
console.log(userIds.value);
} }
const openAddUserDialog = () => { const openAddUserDialog = () => {
mode.value = AddMode.ADD_MODE mode.value = AddMode.ADD_MODE
dialogVisible.value = true dialogVisible.value = true
} }
// 获取用户列表数据的方法
const getUserListData = async () => { const getUserListData = async () => {
const userList = await getUserList({ const userList = await getUserList({
// page: 1, page: pageObj.value.pageNo,
size: 10 size: pageObj.value.pageSize
}) })
tableData.value = userList.data tableData.value = userList.data.list
pageObj.value.total = tableData.value.length pageObj.value.total = userList.data.total
console.log(tableData.value.length); console.log(userList);
} }
onMounted(async () => { onMounted(async () => {
......
...@@ -3,11 +3,12 @@ ...@@ -3,11 +3,12 @@
align-center @close="handleClose" draggable> align-center @close="handleClose" draggable>
<el-form :model="ruleForm" :rules="rules" ref="ruleFormRef" label-width="100px"> <el-form :model="ruleForm" :rules="rules" ref="ruleFormRef" label-width="100px">
<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 v-for="item in spiderTypeOptions" :key="item.value" :label="item.label" :value="item.value" /> <el-option v-for="item in spiderTypeOptions" :key="item.spider" :label="item.spider"
:value="item.spider" :id="item.spider" />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="执行频率:" prop="cronExpression"> <el-form-item label="执行频率:" prop="cronExpression">
...@@ -33,6 +34,8 @@ import { addSpiderTask } from '@/api/spiderTask' ...@@ -33,6 +34,8 @@ import { addSpiderTask } from '@/api/spiderTask'
import { AddMode } from './enum' import { AddMode } from './enum'
import { Crontab } from '@/components/Crontab/index' import { Crontab } from '@/components/Crontab/index'
import type { FormInstance, FormRules } from 'element-plus' import type { FormInstance, FormRules } from 'element-plus'
import { ElMessage } from 'element-plus'
import { getSpiderList } from '@/api/system.ts'
interface RuleForm { interface RuleForm {
taskName: string taskName: string
...@@ -57,7 +60,7 @@ const props = defineProps<addDialogPropType>() ...@@ -57,7 +60,7 @@ const props = defineProps<addDialogPropType>()
const rules = ref<FormRules<RuleForm>>({ const rules = ref<FormRules<RuleForm>>({
taskName: [ taskName: [
{ required: true, message: '请输入任务名称', trigger: 'blur' }, { required: true, message: '请输入任务名称', trigger: 'blur' },
{ min: 1, max: 50, message: '任务名称应在1-50个字符之间', trigger: 'blur' }, { min: 2, max: 50, message: '任务名称应在2-20个字符之间', trigger: 'blur' },
], ],
spiderTypeValue: [ spiderTypeValue: [
{ {
...@@ -72,7 +75,20 @@ const rules = ref<FormRules<RuleForm>>({ ...@@ -72,7 +75,20 @@ const rules = ref<FormRules<RuleForm>>({
message: '请输入执行频率', message: '请输入执行频率',
trigger: 'change', trigger: 'change',
}, },
{ min: 1, message: '执行频率应为cron表达式', trigger: 'blur' }, {
validator: (rule, value, callback) => {
// 计算字符串中'*'的数量
const starCount = (value.match(/\//g) || []).length;
if (starCount > 1) {
callback(new Error('cron表达式最多设置一个间隔'));
}else if(starCount == 0){
callback(new Error('请设置cron表达式'));
}else {
callback();
}
},
trigger: 'blur'
}
] ]
}) })
// 向父组件传递dialog值 // 向父组件传递dialog值
...@@ -80,22 +96,7 @@ const emit = defineEmits(['update:dialogVisible', 'confirm', 'update:mode', 'get ...@@ -80,22 +96,7 @@ const emit = defineEmits(['update:dialogVisible', 'confirm', 'update:mode', 'get
// 组件的状态 // 组件的状态
const currentVisible = ref(props.dialogVisible) const currentVisible = ref(props.dialogVisible)
const currentMode = ref(props.mode) const currentMode = ref(props.mode)
const taskName = ref('') const spiderTypeOptions = ref()
const spiderTypeValue = ref('')
const spiderTypeOptions = ref([
{
value: 'api_spider',
label: 'api_spider'
},
{
value: 'dsn_now',
label: 'dsn_now'
},
{
value: 'itu_space_explorer',
label: 'itu_space_explorer'
},
])
const taskParams = ref({ const taskParams = ref({
scrapyd_server_id: '1', scrapyd_server_id: '1',
schedule_type: '0', schedule_type: '0',
...@@ -108,10 +109,13 @@ const handleClose = () => { ...@@ -108,10 +109,13 @@ const handleClose = () => {
ruleForm.value.taskName = '' ruleForm.value.taskName = ''
ruleForm.value.spiderTypeValue ruleForm.value.spiderTypeValue
ruleForm.value.cronExpression = '' ruleForm.value.cronExpression = ''
ruleFormRef.value?.resetFields()
currentVisible.value = false currentVisible.value = false
} }
// 确定的方法 // 确定的方法
const handleConfirm = async () => { const handleConfirm = async () => {
console.log(ruleForm.value.cronExpression);
if (!ruleFormRef.value) return if (!ruleFormRef.value) return
await ruleFormRef.value.validate(async (valid, fields) => { await ruleFormRef.value.validate(async (valid, fields) => {
console.log('开始校验'); console.log('开始校验');
...@@ -128,6 +132,7 @@ const handleConfirm = async () => { ...@@ -128,6 +132,7 @@ const handleConfirm = async () => {
}) })
currentVisible.value = false currentVisible.value = false
emit('getTaskList') emit('getTaskList')
ElMessage.success('添加成功')
} else if (currentMode.value === AddMode.UPDATE_TASK) { } else if (currentMode.value === AddMode.UPDATE_TASK) {
await addSpiderTask({ await addSpiderTask({
scrapyd_server_id: taskParams.value.scrapyd_server_id, scrapyd_server_id: taskParams.value.scrapyd_server_id,
...@@ -140,13 +145,19 @@ const handleConfirm = async () => { ...@@ -140,13 +145,19 @@ const handleConfirm = async () => {
}) })
currentVisible.value = false currentVisible.value = false
emit('getTaskList') emit('getTaskList')
ElMessage.success('修改成功')
} }
} else { } else {
console.log('校验不通过'); console.log('校验不通过');
} }
}) })
} }
const getData = async () => {
const res = await getSpiderList({scrapydServerId:'1',project:'spiders'})
spiderTypeOptions.value = res.data
console.log(res);
}
// 监听props变化,同步给组件内部 // 监听props变化,同步给组件内部
watch(() => props.dialogVisible, watch(() => props.dialogVisible,
(newVal) => { (newVal) => {
...@@ -180,13 +191,13 @@ watch( ...@@ -180,13 +191,13 @@ watch(
} }
if (!newVisible) { if (!newVisible) {
// 清空表单 // 清空表单
cronExpression.value = '' // cronExpression.value = ''
} }
}, },
{ immediate: true } { immediate: true }
); );
onMounted(() => { onMounted(() => {
getData()
}) })
</script> </script>
......
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
import { ref, watch } from 'vue' import { ref, watch } from 'vue'
import { defineProps } from 'vue'; import { defineProps } from 'vue';
import { deleteSpiderTask } from '@/api/spiderTask.ts' import { deleteSpiderTask } from '@/api/spiderTask.ts'
import { ElMessage } from 'element-plus';
interface deleteDialogPropType { interface deleteDialogPropType {
dialogVisible: boolean, dialogVisible: boolean,
jobId: string jobId: string
...@@ -30,6 +31,7 @@ const handleDelelte = async () => { ...@@ -30,6 +31,7 @@ const handleDelelte = async () => {
await deleteSpiderTask({job_id: props.jobId}) await deleteSpiderTask({job_id: props.jobId})
emit('getUserList') emit('getUserList')
deleteDialogVisible.value = false deleteDialogVisible.value = false
ElMessage.success('删除成功')
} }
// 关闭弹窗的方法 // 关闭弹窗的方法
......
<!-- 任务执行统计卡片组件 --> <!-- 任务执行统计卡片组件 -->
<template> <template>
<div>
<div class="m-t-2" />
<div class="text-left p-4 toolbarStyle "> <div class="text-left p-4 toolbarStyle ">
<div class="formStyle"> <div class="formStyle">
<el-form inline> <el-form inline>
...@@ -9,23 +11,21 @@ ...@@ -9,23 +11,21 @@
<el-form-item> <el-form-item>
<div> <div>
<el-select v-model="searchCondition.spiders" placeholder="请选择" style="width: 220px"> <el-select v-model="searchCondition.spiders" placeholder="请选择" style="width: 220px">
<el-option v-for="item in taskSelectOptions" :key="item.value" :label="item.label" :value="item.value" /> <el-option v-for="item in taskSelectOptions" :key="item.spider" :label="item.spider"
:value="item.spider" />
</el-select> </el-select>
</div> </div>
</el-form-item> </el-form-item>
<el-form-item> <!-- <el-form-item>
<el-text class="mx-1" style="color: #fff;">任务名称:</el-text> <el-text class="mx-1" style="color: #fff;">任务名称:</el-text>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-input v-model="taskName" placeholder="请输入" style="width: 220px" /> <el-input v-model="taskName" placeholder="请输入" style="width: 220px" />
</el-form-item> </el-form-item> -->
<el-form-item>
<el-space>
<el-button type="primary" @Click="getData">查询</el-button>
</el-space>
</el-form-item>
<el-form-item> <el-form-item>
<el-space> <el-space>
<el-button type="primary" @Click="search">查询</el-button>
<el-button type="primary" @Click="resetData">重置列表</el-button>
<el-button type="primary" @click="openTaskDialog">新建任务</el-button> <el-button type="primary" @click="openTaskDialog">新建任务</el-button>
</el-space> </el-space>
</el-form-item> </el-form-item>
...@@ -33,68 +33,90 @@ ...@@ -33,68 +33,90 @@
</div> </div>
</div> </div>
<div class="cardStyle"> <div class="cardStyle">
<div class="taskCard p-6 " v-for="task in taskList" :key="task.taskId"> <div class="taskCard p-6" v-for="task in taskList" :key="task.taskId">
<div class="taskContent"> <div class="taskContent">
<div> <div>
<el-form-item> <el-form-item class="form-item">
<span class="titleStyle">{{ task.kwargs.options === '' ? '无名称' : JSON.parse(task.kwargs.options).jobName <span class="titleStyle">{{ task.kwargs.options === '' ? '无名称' :
JSON.parse(task.kwargs.options).jobName
}}</span> }}</span>
</el-form-item> </el-form-item>
</div> </div>
<div> <div>
<el-form-item> <el-form-item class="form-item">
<el-button type="primary" @click="editTask(task)" class="editButton">编辑</el-button> <el-button type="primary" @click="editTask(task)" class="editButton">编辑</el-button>
<el-button type="success" @click="goToTaskRecord" class="recordButton">执行记录 </el-button> <!-- <el-button type="success" @click="goToTaskRecord" class="recordButton">执行记录 </el-button> -->
<el-button type="danger" @click="deleteTask(task)" class="deleteButton">删除</el-button> <el-button type="danger" @click="deleteTask(task)" class="deleteButton">删除</el-button>
</el-form-item> </el-form-item>
</div> </div>
<div class="wordStyle"> <div class="wordStyle">
<el-form-item> <el-form-item class="form-item">
<el-space> <el-space>
<span class="wordStyle">启用/停止: </span> <span class="wordStyle">启用/停止: </span>
<el-switch v-model="task.status" :active-value="'running'" :inactive-value="'paused'" <el-switch v-model="task.status" :active-value="'running'" :inactive-value="'paused'"
@change="(newStatus:any) => changeStatus(task, newStatus)" /> @change="(newStatus: any) => changeStatus(task, newStatus)" />
<span class="wordStyle">执行频率: {{ task.frequency }} </span>
</el-space> </el-space>
</el-form-item> </el-form-item>
</div> </div>
<div class="wordStyle"> <div class="wordStyle">
<el-form-item> <el-form-item class="form-item">
<el-space> <el-space>
<span class="wordStyle">执行次数: 个; </span> <span class="wordStyle">执行频率: {{ parseCronExpression(task.kwargs.cron) }} </span>
<span class="wordStyle">成功次数: 个</span>
</el-space> </el-space>
</el-form-item> </el-form-item>
</div> </div>
<div class="wordStyle "> <div class="wordStyle">
<el-form-item> <el-form-item class="form-item">
<el-space>
<span class="wordStyle">执行次数: {{ task.count }} </span>
</el-space>
</el-form-item>
</div>
<div class="wordStyle">
<el-form-item class="form-item">
<el-space> <el-space>
<span class="wordStyle">失败次数: 个; </span> <span class="wordStyle">所属爬虫: {{ task.kwargs.spider }}</span>
<span class="wordStyle">异常记录: 个</span>
</el-space> </el-space>
</el-form-item> </el-form-item>
</div> </div>
<!-- <div class="wordStyle">
<el-form-item class="form-item">
<el-space>
<span class="wordStyle">失败次数: </span>
<span class="wordStyle">异常记录: </span>
</el-space>
</el-form-item>
</div> -->
</div> </div>
</div> </div>
</div> </div>
<addTaskDialog v-model:dialogVisible="showTaskDialog" @confirm="handleEdit" :mode="editMode" @getTaskList="getData" <addTaskDialog v-model:dialogVisible="showTaskDialog" @confirm="handleEdit" :mode="editMode"
:jobId="jobId" :cron="frequency" :spiderType="spiderType" :options="taskOptions" /> @getTaskList="getData" :jobId="jobId" :cron="frequency" :spiderType="belongSpiderType"
:options="taskOptions" />
<deleteDialog v-model:dialogVisible="showDeleteDialog" @get-user-list="getData" :jobId="jobId" /> <deleteDialog v-model:dialogVisible="showDeleteDialog" @get-user-list="getData" :jobId="jobId" />
</div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, onMounted } from 'vue' import { ref, onMounted, watch } 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 } 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'
defineProps({ import { CronExpressionParser } from 'cron-parser';
successTask: { import { ElMessage } from 'element-plus'
import { getSpiderList } from '@/api/system.ts'
const props = defineProps({
spiderType: {
type: String, type: String,
default: '' default: ''
}, },
...@@ -107,29 +129,18 @@ defineProps({ ...@@ -107,29 +129,18 @@ defineProps({
default: '' default: ''
} }
}) })
const taskSelectOptions = [
{ const taskSelectOptions = ref()
value: 'api_spider',
label: 'api_spider',
},
{
value: 'dsn_now',
label: 'dsn_now',
},
{
value: 'itu_space_explorer',
label: 'itu_space_explorer',
},
]
const searchCondition = ref({ const searchCondition = ref({
spiders: '', spiders: '',
options: '' options: ''
}) })
const belongSpider = ref('')
const taskName = ref('') const taskName = ref('')
const taskValue = ref('') const taskValue = ref('')
const spiderType = ref('') const belongSpiderType = ref('')
const router = useRouter() const router = useRouter()
const frequency = ref('') const frequency = ref()
const editMode = ref(AddMode.UPDATE_TASK) const editMode = ref(AddMode.UPDATE_TASK)
const deleteMode = ref(DeleteMode.SINGLE_DELETE) const deleteMode = ref(DeleteMode.SINGLE_DELETE)
const showDeleteDialog = ref(false) const showDeleteDialog = ref(false)
...@@ -139,16 +150,17 @@ const taskOptions = ref('') ...@@ -139,16 +150,17 @@ const taskOptions = ref('')
const jobId = ref('') const jobId = ref('')
const taskList = ref<any[]>([]) const taskList = ref<any[]>([])
// 删除任务的方法
const deleteTask = (task: any) => { const deleteTask = (task: any) => {
console.log(task);
jobId.value = task.id jobId.value = task.id
deleteMode.value = DeleteMode.SINGLE_DELETE deleteMode.value = DeleteMode.SINGLE_DELETE
showDeleteDialog.value = true showDeleteDialog.value = true
} }
// 编辑任务的方法
const editTask = (task: any) => { const editTask = (task: any) => {
jobId.value = task.id jobId.value = task.id
taskOptions.value = JSON.stringify(JSON.parse(task.kwargs.options)) taskOptions.value = JSON.stringify(JSON.parse(task.kwargs.options))
spiderType.value = task.kwargs.spider belongSpiderType.value = task.kwargs.spider
frequency.value = task.kwargs.cron frequency.value = task.kwargs.cron
editMode.value = AddMode.UPDATE_TASK editMode.value = AddMode.UPDATE_TASK
showTaskDialog.value = true showTaskDialog.value = true
...@@ -164,96 +176,196 @@ const goToTaskRecord = () => { ...@@ -164,96 +176,196 @@ const goToTaskRecord = () => {
} }
}) })
} }
// 启动与停止任务的方法
const changeStatus = async (task: any, newStatus: string) => { const changeStatus = async (task: any, newStatus: string) => {
if (newStatus === "running") { if (newStatus === "running") {
await resumeSpiderTask({ job_id: task.id }); await resumeSpiderTask({ job_id: task.id });
} else if (newStatus === "paused") { } else if (newStatus === "paused") {
await pauseSpiderTask({ job_id: task.id }); await pauseSpiderTask({ job_id: task.id });
} }
await getData(); // await getData();
}; };
const handleEdit = () => { const handleEdit = () => {
} }
// 打开新建任务弹窗
const openTaskDialog = () => { const openTaskDialog = () => {
editMode.value = AddMode.ADD_TASK editMode.value = AddMode.ADD_TASK
frequency.value = '* * * * * *'
showTaskDialog.value = true showTaskDialog.value = true
} }
// 获取任务列表的方法
const getData = async () => { const getData = async () => {
const res = await getSpiderTaskList({ spiders: searchCondition.value.spiders ? [searchCondition.value.spiders] : [] })
const resId = await getTaskCount({})
for (let i = 0; i < res.data.length; i++) {
res.data[i].count = resId.data[res.data[i].id] === undefined ? 0 : resId.data[res.data[i].id]
}
taskList.value = res.data
}
// 重置任务列表的方法
const resetData = async () => {
searchCondition.value.spiders = ''
const res = await getSpiderTaskList({ spiders: [], options: JSON.stringify({ jobName: taskName.value }) })
const resId = await getTaskCount({})
for (let i = 0; i < res.data.length; i++) {
res.data[i].count = resId.data[res.data[i].id] === undefined ? 0 : resId.data[res.data[i].id]
}
taskList.value = res.data
}
// 搜索任务的方法
const search = async () => {
if (searchCondition.value.spiders === '') {
ElMessage.warning('请先选择爬虫')
return
}
const res = await getSpiderTaskList({ spiders: searchCondition.value.spiders ? [searchCondition.value.spiders] : [], options: JSON.stringify({ jobName: taskName.value }) }) const res = await getSpiderTaskList({ spiders: searchCondition.value.spiders ? [searchCondition.value.spiders] : [], options: JSON.stringify({ jobName: taskName.value }) })
const resId = await getTaskCount({})
for (let i = 0; i < res.data.length; i++) {
res.data[i].count = resId.data[res.data[i].id] === undefined ? 0 : resId.data[res.data[i].id]
}
taskList.value = res.data taskList.value = res.data
} }
// 解析cron表达式的方法
const parseCronExpression = (cronExpression: string) => {
const res = cronExpression.split('*').length - 1
if (res >= 5) {
const aaa = cronExpression.split(' ')
for (let i = 0; i < aaa.length; i++) {
if (aaa[i] != '*') {
if (i == 0) {
if (aaa[i].length === 3) {
return `每${aaa[i][2]}秒执行一次`
} else {
return `每${aaa[i][2] + aaa[i][3]}秒执行一次`
}
} else if (i == 1) {
if (aaa[i].length === 3) {
return `每${aaa[i][2]}分钟执行一次`
} else {
return `每${aaa[i][2] + aaa[i][3]}分钟执行一次`
}
} else if (i == 2) {
if (aaa[i].length === 3) {
return `每${aaa[i][2]}小时执行一次`
} else {
return `每${aaa[i][2] + aaa[i][3]}小时执行一次`
}
} else if (i == 3) {
if (aaa[i].length === 3) {
return `每${aaa[i][2]}天执行一次`
} else {
return `每${aaa[i][2] + aaa[i][3]}天执行一次`
}
} else if (i == 4) {
if (aaa[i].length === 3) {
return `每${aaa[i][2]}月执行一次`
} else {
return `每${aaa[i][2] + aaa[i][3]}月执行一次`
}
}
}
}
}
}
const getSpiderTypeList = async () => {
const res = await getSpiderList({ scrapydServerId: '1', project: 'spiders' })
console.log(res,8888888888888);
taskSelectOptions.value = res.data
// taskSelectOptions.value = res.data
}
onMounted(() => { onMounted(() => {
if (props.spiderType !== '') {
searchCondition.value.spiders = props.spiderType
}
getData() getData()
getSpiderTypeList()
}) })
</script>
<style lang="scss" scoped>
.taskContent {
display: flex;
flex-direction: column; // 垂直排列
align-items: center; // 水平居中
justify-content: center; // 垂直居中
height: 100%;
gap: -15px;
padding: 10px 0;
}
// .editButton{ // const parseCronExpression=(cronExpression: string)=> {
// background: none; // try {
// background-image: url("@/assets/picture/button1.png"); // const interval = cronParser.parseExpression(cronExpression);
// background-size: 100% 100%; // const nextDate = interval.next().toDate();
// background-repeat: no-repeat; // const hours = nextDate.getHours();
// } // const minutes = nextDate.getMinutes();
// .recordButton{ // const seconds = nextDate.getSeconds();
// background: none; // return {
// background-image: url("@/assets/picture/button2.png"); // hours,
// background-size: 100% 100%; // minutes,
// background-repeat: no-repeat; // seconds
// };
// } catch (err) {
// console.error('Error parsing cron expression:', err);
// return null;
// } // }
// .deleteButton{
// background: none;
// background-image: url("@/assets/picture/button3.png");
// background-size: 100% 100%;
// background-repeat: no-repeat;
// } // }
</script>
<style lang="scss" scoped>
.cardStyle { .cardStyle {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
justify-content: space-around;
margin-top: 1.5%; margin-top: 1.5%;
padding: 8px; padding: 18px;
height: 26vh; gap: 15px;
width: auto !important;
justify-content: space-between;
} }
// 任务卡片样式
.taskContent {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
gap: 6px;
/* 进一步减小项目之间的间距 */
padding: 5px 0;
}
/* 任务卡片样式 */
.taskCard { .taskCard {
margin-left: 1%;
background-image: url("@/assets/picture/box2.png"); background-image: url("@/assets/picture/box2.png");
background-size: 100% 100%; background-size: 100% 100%;
background-repeat: no-repeat; background-repeat: no-repeat;
margin-bottom: 20px; margin-bottom: 10px;
width: 380px; width: 380px;
font-size: 20px; font-size: 20px;
height: 100%; min-height: 240px;
border-radius: 7px; border-radius: 7px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
.titleStyle { }
/* 减小表单的间距 */
.form-item {
margin-bottom: 3px !important;
}
.taskCard .titleStyle {
font-size: 22px; font-size: 22px;
margin-top: -5px;
color: #FFFFFF; color: #FFFFFF;
} }
.wordStyle { .taskCard .wordStyle {
font-size: 16px; font-size: 16px;
color: #FFFFFF; color: #FFFFFF;
}
} /* 按钮样式微调 */
.editButton,
.recordButton,
.deleteButton {
margin: 0 3px !important;
} }
/* 工具栏样式 */ /* 工具栏样式 */
.toolbarStyle { .toolbarStyle {
background-image: url("@/assets/picture/box3.png"); background-image: url("@/assets/picture/box3.png");
...@@ -286,4 +398,8 @@ onMounted(() => { ...@@ -286,4 +398,8 @@ onMounted(() => {
.el-input { .el-input {
color: #FFFFFF; color: #FFFFFF;
} }
.el-select__placeholder {
color: #FFFFFF;
}
</style> </style>
\ No newline at end of file
<template> <template>
<div> <div>
<div class="backStyle" v-if="route.query.jump === 'yes'" @click="goToTaskInformation" /> <div class="backStyle" v-if="route.query.jump === 'yes'" @click="goToTaskInformation" />
<div class="m-t-10" /> <div class="m-t-8" />
<div > <div>
<taskCard successTask="100" failTask="10" unusualTask="1" /> <taskCard :spiderType="spiderType" failTask="10" unusualTask="1" />
<!-- <div class="pagination w-full flex flex-row-reverse pr-18 m-t-0"> <!-- <div class="pagination w-full flex flex-row-reverse pr-18 m-t-0">
<Pagination :total="pageObj.total" v-model:page="pageObj.pageNo" v-model:limit="pageObj.pageSize" <Pagination :total="pageObj.total" v-model:page="pageObj.pageNo" v-model:limit="pageObj.pageSize"
@pagination="getData" /> @pagination="getData" />
...@@ -23,6 +23,7 @@ import { AddMode } from './components/enum' ...@@ -23,6 +23,7 @@ import { AddMode } from './components/enum'
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()
const spiderType = ref<any>(route.query.spiderType)
const taskValue = ref('') const taskValue = ref('')
const taskList = ref([]) const taskList = ref([])
const taskOptions = [ const taskOptions = [
...@@ -46,8 +47,18 @@ const pageObj = ref({ ...@@ -46,8 +47,18 @@ const pageObj = ref({
}) })
const goToTaskInformation = () => { const goToTaskInformation = () => {
if(route.query.page === 'spiderManager'){
router.push({ path: '/osSpiderManager/list' }) router.push({ path: '/osSpiderManager/list' })
}else{
router.push({ path: '/osStatus/list' })
}
} }
onMounted(async () => {
// if(route.query.spiderType){
// spiderType.value = route.query.spiderType
// }
})
</script> </script>
<style scoped> <style scoped>
......
...@@ -9,21 +9,13 @@ ...@@ -9,21 +9,13 @@
<el-text class="mx-1">时间:</el-text> <el-text class="mx-1">时间:</el-text>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-date-picker v-model="timeValue" type="datetimerange" start-placeholder="Start date" <el-config-provider :locale="zhCn">
end-placeholder="End date" format="YYYY-MM-DD HH:mm:ss" date-format="YYYY/MM/DD ddd" <el-date-picker v-model="timeValue" type="datetimerange" start-placeholder="开始时间"
time-format="A hh:mm:ss" value-format="YYYY-MM-DD HH:mm:ss"/> end-placeholder="结束时间" format="YYYY-MM-DD HH:mm:ss" date-format="YYYY/MM/DD ddd"
</el-form-item> time-format="A hh:mm:ss" value-format="YYYY-MM-DD HH:mm:ss" />
<!-- <el-form-item> </el-config-provider>
<el-text class="mx-1">调度状态:</el-text>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<div>
<el-select v-model="taskValue" placeholder="请选择" style="width: 220px">
<el-option v-for="item in taskOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</div>
</el-form-item> -->
<el-form-item>
<el-space> <el-space>
<el-button type="primary" @click="searchData">查询</el-button> <el-button type="primary" @click="searchData">查询</el-button>
<el-button type="primary" @click="getData">重置表格</el-button> <el-button type="primary" @click="getData">重置表格</el-button>
...@@ -38,7 +30,6 @@ ...@@ -38,7 +30,6 @@
<el-table :data="tableData" style="width: 100%" border :header-cell-style="{ textAlign: 'center' }" <el-table :data="tableData" style="width: 100%" border :header-cell-style="{ textAlign: 'center' }"
:cell-style="{ textAlign: 'center' }"> :cell-style="{ textAlign: 'center' }">
<el-table-column property="number" label="序号" width="55" type="index" /> <el-table-column property="number" label="序号" width="55" type="index" />
<el-table-column property="project" label="项目名称" show-overflow-tooltip />
<el-table-column property="spider" label="所属爬虫" show-overflow-tooltip /> <el-table-column property="spider" label="所属爬虫" show-overflow-tooltip />
<el-table-column property="schedule_mode" label="调度模式" show-overflow-tooltip /> <el-table-column property="schedule_mode" label="调度模式" show-overflow-tooltip />
<el-table-column property="run_status" label="调度状态" show-overflow-tooltip> <el-table-column property="run_status" label="调度状态" show-overflow-tooltip>
...@@ -52,16 +43,12 @@ ...@@ -52,16 +43,12 @@
{{ scope.row.message || '-' }} {{ scope.row.message || '-' }}
</template> </template>
</el-table-column> </el-table-column>
<el-table-column property="run_status" label="运行状态" show-overflow-tooltip /> <el-table-column property="run_status" label="运行状态" show-overflow-tooltip>
<!-- <el-table-column property="duration" label="持续时间" show-overflow-tooltip /> -->
<el-table-column property="create_time" label="调度时间" width="200" show-overflow-tooltip />
<!-- <el-table-column label="运行日志" width="120">
<template #default="scope"> <template #default="scope">
<el-button size="small" plain type="primary" @click="handleDetails(scope.row)"> {{ scope.row.run_status === "unknown" ? '已结束' : scope.row.run_status }}
日志
</el-button>
</template> </template>
</el-table-column> --> </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">
...@@ -77,7 +64,17 @@ import Pagination from '@/components/pagination/index.vue' ...@@ -77,7 +64,17 @@ import Pagination from '@/components/pagination/index.vue'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { getSpiderTaskRecord } from '@/api/spiderTask.ts' import { getSpiderTaskRecord } from '@/api/spiderTask.ts'
import { ElMessage } from 'element-plus'
// ElConfigProvider 组件
import { ElConfigProvider } from 'element-plus';
// 引入中文包
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 = '页';
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()
const taskValue = ref('') const taskValue = ref('')
...@@ -103,17 +100,17 @@ const pageObj = ref({ ...@@ -103,17 +100,17 @@ const pageObj = ref({
pageNo: 1, pageNo: 1,
pagerCount: 5, pagerCount: 5,
}) })
const handleDetails = (row: any) => { // 前往运行日志,暂时不要
console.log(row); // const handleDetails = (row: any) => {
router.push({ // console.log(row);
path: '/os-log/list', // router.push({
query: { // path: '/os-log/list',
id: row.id // query: {
} // id: row.id
}) // }
} // })
// }
const goToStatus = () => { const goToStatus = () => {
if (route.query.mode === '状态监控') { if (route.query.mode === '状态监控') {
router.push({ router.push({
path: '/osStatus/list', path: '/osStatus/list',
...@@ -123,8 +120,6 @@ const goToStatus = () => { ...@@ -123,8 +120,6 @@ const goToStatus = () => {
path: '/osTaskInformation/list', path: '/osTaskInformation/list',
}) })
} }
} }
// 获取任务执行记录列表 // 获取任务执行记录列表
const getData = async () => { const getData = async () => {
...@@ -132,14 +127,18 @@ const getData = async () => { ...@@ -132,14 +127,18 @@ const getData = async () => {
pageObj.value.total = res.data.total pageObj.value.total = res.data.total
tableData.value = res.data.list tableData.value = res.data.list
} }
// 数据的方法
const searchData = async () => { const searchData = async () => {
if (!timeValue.value) {
ElMessage.warning('请先选择时间段')
return
}
let resTime = [] let resTime = []
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)
resTime.push(endTime) resTime.push(endTime)
const res = await getSpiderTaskRecord({ page: pageObj.value.pageNo, size: pageObj.value.pageSize, status: 'total' ,times: resTime}) const res = await getSpiderTaskRecord({ page: pageObj.value.pageNo, size: pageObj.value.pageSize, status: 'total', times: resTime })
pageObj.value.total = res.data.total pageObj.value.total = res.data.total
tableData.value = res.data.list tableData.value = res.data.list
} }
......
<template>
<el-form
ref="formRef"
:model="form"
:rules="rules"
label-width="120px"
v-loading="loading"
>
<el-form-item label="项目名称" prop="project">
<el-select
v-model="form.project"
placeholder="请选择项目"
clearable
filterable
style="width: 100%"
@change="handleProjectChange"
>
<el-option
v-for="item in projectOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="爬虫名称" prop="spider">
<el-select
v-model="form.spider"
placeholder="请选择爬虫"
clearable
filterable
style="width: 100%"
>
<el-option
v-for="item in spiderOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="Cron表达式" prop="cron">
<el-input
v-model="form.cron"
placeholder="例如: */5 * * * *"
>
<template #append>
<el-popover
placement="bottom"
title="Cron表达式帮助"
:width="300"
trigger="click"
>
<template #reference>
<el-button>帮助</el-button>
</template>
<div>
<p>Cron表达式格式:分 时 日 月 周</p>
<p>示例:</p>
<ul>
<li>每5分钟:*/5 * * * *</li>
<li>每小时:0 * * * *</li>
<li>每天凌晨1点:0 1 * * *</li>
<li>每周一3点:0 3 * * 1</li>
<li>每月1号零点:0 0 1 * *</li>
</ul>
</div>
</el-popover>
</template>
</el-input>
</el-form-item>
<el-form-item label="调度服务器" prop="schedule_type">
<el-radio-group v-model="form.schedule_type">
<el-radio
v-for="item in scheduleTypeOptions"
:key="item.value"
:label="item.value"
>{{ item.label }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="选择服务器" prop="scrapyd_server_id" v-if="form.schedule_type === ScheduleTypeEnum.ONLY_ONE_SERVER">
<el-select
v-model="form.scrapyd_server_id"
placeholder="请选择服务器"
clearable
filterable
style="width: 100%"
>
<el-option
v-for="item in serverOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="爬虫参数" prop="options">
<el-input
v-model="form.options"
type="textarea"
:rows="4"
placeholder='{"param1": "value1", "param2": "value2"}'
/>
</el-form-item>
<el-form-item>
<el-button
type="primary"
@click="submitForm"
:loading="submitting"
>
确认
</el-button>
<el-button @click="cancelForm">取消</el-button>
</el-form-item>
</el-form>
</template>
<script lang="ts" setup>
import { ref, reactive, onMounted, watch } from 'vue'
import { ElMessage } from 'element-plus'
import type { FormInstance } from 'element-plus'
import * as scheduleApi from '@/api/schedule'
import * as projectApi from '@/api/project'
import { ScheduleTypeOptions, ScheduleTypeEnum } from '@/enums/schedule-type-enum'
import type { QueryParams } from '@/utils/request'
const props = defineProps({
job_id: {
type: String,
default: ''
},
scrapydServerId: {
type: String,
default: ''
},
project: {
type: String,
default: ''
},
spider: {
type: String,
default: ''
}
})
const emit = defineEmits(['on-success', 'on-cancel'])
const formRef = ref<FormInstance>()
const loading = ref(false)
const submitting = ref(false)
interface OptionType {
label: string
value: string
}
const projectOptions = ref<OptionType[]>([])
const spiderOptions = ref<OptionType[]>([])
const serverOptions = ref<OptionType[]>([])
const scheduleTypeOptions = ref(ScheduleTypeOptions)
const form = reactive({
project: props.project || '',
spider: props.spider || '',
cron: '*/5 * * * *', // 默认每5分钟执行一次
schedule_type: ScheduleTypeEnum.ONLY_ONE_SERVER,
scrapyd_server_id: props.scrapydServerId || '',
options: '{}'
})
const rules = reactive({
project: [
{ required: true, message: '请选择项目', trigger: 'change' }
],
spider: [
{ required: true, message: '请选择爬虫', trigger: 'change' }
],
cron: [
{ required: true, message: '请输入Cron表达式', trigger: 'blur' }
],
schedule_type: [
{ required: true, message: '请选择调度方式', trigger: 'change' }
],
scrapyd_server_id: [
{
required: true,
message: '请选择服务器',
trigger: 'change',
validator: (_rule: any, value: any, callback: any) => {
if (form.schedule_type === ScheduleTypeEnum.ONLY_ONE_SERVER && !value) {
callback(new Error('请选择服务器'))
} else {
callback()
}
}
}
]
})
// 初始化数据
const initData = async () => {
loading.value = true
try {
// 获取项目列表
await getProjects()
// 获取服务器列表
await getScrapydServers()
// 如果有job_id,则获取任务详情
if (props.job_id) {
await getJobDetail()
}
// 如果有项目名,则获取爬虫列表
if (form.project) {
await getSpiders()
}
} catch (error) {
console.error('初始化数据失败:', error)
} finally {
loading.value = false
}
}
// 获取项目列表
const getProjects = async () => {
try {
const scrapyd_server_id = localStorage.getItem('scrapyd_server_id')
const res = await projectApi.getProjectList({ scrapydServerId: scrapyd_server_id! })
if (res.code === 0) {
projectOptions.value = res.data.map((item: any) => ({
label: item.name,
value: item.name
}))
}
} catch (error) {
console.error('获取项目列表失败:', error)
}
}
// 获取爬虫列表
const getSpiders = async () => {
if (!form.project) return
try {
const res = await projectApi.getSpiders({ project: form.project, scrapydServerId: localStorage.getItem('scrapyd_server_id')! })
if (res.code === 0 && Array.isArray(res.data)) {
spiderOptions.value = res.data.map((item: string) => ({
label: item,
value: item
}))
}
} catch (error) {
console.error('获取爬虫列表失败:', error)
}
}
// 获取服务器列表
const getScrapydServers = async () => {
try {
const res = await scheduleApi.getScrapydServerPage()
if (res.code === 0) {
serverOptions.value = res.data.list.map((item: any) => ({
label: item.server_name,
value: item.id
}))
}
} catch (error) {
console.error('获取服务器列表失败:', error)
}
}
// 获取任务详情
const getJobDetail = async () => {
try {
const res = await scheduleApi.getJobDetail({ job_id: props.job_id, scrapydServerId: localStorage.getItem('scrapyd_server_id')! })
if (res.code === 0) {
const jobData = res.data
form.project = jobData.kwargs.project || ''
form.spider = jobData.kwargs.spider || ''
form.cron = jobData.kwargs.cron || ''
form.schedule_type = jobData.kwargs.schedule_type || ScheduleTypeEnum.ONLY_ONE_SERVER
form.scrapyd_server_id = jobData.kwargs.scrapyd_server_id || ''
form.options = jobData.kwargs.options ? JSON.stringify(jobData.kwargs.options) : '{}'
}
} catch (error) {
console.error('获取任务详情失败:', error)
}
}
// 项目变更时获取对应的爬虫
const handleProjectChange = async () => {
form.spider = ''
await getSpiders()
}
// 提交表单
const submitForm = async () => {
if (!formRef.value) return
await formRef.value.validate(async (valid) => {
if (valid) {
submitting.value = true
try {
let options = {}
try {
options = JSON.parse(form.options)
} catch (e) {
ElMessage.error('爬虫参数格式错误,请检查JSON格式')
submitting.value = false
return
}
const params: QueryParams = {
project: form.project,
spider: form.spider,
cron: form.cron,
schedule_type: form.schedule_type,
scrapydServerId: form.scrapyd_server_id,
options
}
if (form.schedule_type === ScheduleTypeEnum.ONLY_ONE_SERVER) {
params.scrapyd_server_id = form.scrapyd_server_id
}
if (props.job_id) {
params.job_id = props.job_id
}
const res = await scheduleApi.addJob(params)
if (res.code === 0) {
ElMessage({
type: 'success',
message: props.job_id ? '修改成功!' : '添加成功!'
})
emit('on-success')
}
} catch (error) {
console.error(props.job_id ? '修改任务失败:' : '添加任务失败:', error)
} finally {
submitting.value = false
}
}
})
}
// 取消表单
const cancelForm = () => {
emit('on-cancel')
}
// 监听项目变化
watch(() => form.project, async (newVal) => {
if (newVal) {
await getSpiders()
}
})
onMounted(() => {
initData()
})
</script>
<style lang="scss" scoped>
</style>
<template>
<el-form
:model="form"
ref="formRef"
label-width="100px"
size="small"
:rules="rules"
>
<el-form-item
label="服务名称"
prop="scrapyd_server_id"
>
<el-select
v-model="form.scrapyd_server_id"
placeholder="请选择服务器"
style="width: 100%"
:disabled="scrapydServerId !== ''"
@change="handleServerChange"
>
<el-option
v-for="server in serverOptions"
:key="server.value"
:label="server.label"
:value="server.value"
/>
</el-select>
</el-form-item>
<el-form-item
label="项目名称"
prop="project"
>
<el-select
v-model="form.project"
placeholder="请选择项目"
style="width: 100%"
:disabled="project !== ''"
@change="handleProjectChange"
>
<el-option
v-for="proj in projectOptions"
:key="proj.value"
:label="proj.label"
:value="proj.value"
/>
</el-select>
</el-form-item>
<el-form-item
label="Spider名称"
prop="spider"
>
<el-select
v-model="form.spider"
placeholder="请选择爬虫"
style="width: 100%"
:disabled="spider !== ''"
filterable
>
<el-option
v-for="item in spiderOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item
label="其他参数"
prop="options"
>
<el-input
type="textarea"
rows="3"
v-model="form.options"
placeholder='任何参数都将作为spider参数传递,例如:{"setting": "DOWNLOAD_DELAY=2"}'
/>
</el-form-item>
<el-form-item>
<el-button
size="small"
@click="handleCancel"
>取 消</el-button>
<el-button
type="primary"
size="small"
@click="submit"
:loading="submitting"
>立即执行</el-button>
</el-form-item>
</el-form>
</template>
<script lang="ts" setup>
import { ref, reactive, onMounted } from 'vue'
import { ElMessage } from 'element-plus'
import type { FormInstance } from 'element-plus'
import * as projectApi from '@/api/project'
import * as systemApi from '@/api/system'
const props = defineProps({
scrapydServerId: {
type: String,
default: ''
},
project: {
type: String,
default: ''
},
spider: {
type: String,
default: ''
},
options: {
type: String,
default: ''
}
})
const emit = defineEmits(['on-success', 'on-cancel'])
const formRef = ref<FormInstance>()
const submitting = ref(false)
interface OptionType {
label: string
value: string
}
const serverOptions = ref<OptionType[]>([])
const projectOptions = ref<OptionType[]>([])
const spiderOptions = ref<OptionType[]>([])
const form = reactive({
scrapyd_server_id: props.scrapydServerId || '',
project: props.project || '',
spider: props.spider || '',
options: props.options || '{}'
})
const rules = reactive({
scrapyd_server_id: [
{ required: true, message: '请选择服务器', trigger: 'change' }
],
project: [
{ required: true, message: '请选择项目', trigger: 'change' }
],
spider: [
{ required: true, message: '请选择爬虫', trigger: 'change' }
]
})
// 初始化数据
const initData = async () => {
await getServerOptions()
if (form.scrapyd_server_id) {
await getProjectOptions()
if (form.project) {
await getSpiderOptions()
}
}
}
// 获取服务器选项
const getServerOptions = async () => {
try {
const res = await systemApi.getScrapydServerList()
if (res.code === 0) {
serverOptions.value = res.data.map((server: any) => ({
label: server.server_name,
value: server.id
}))
}
} catch (error) {
console.error('获取服务器列表失败:', error)
}
}
// 获取项目选项
const getProjectOptions = async () => {
if (!form.scrapyd_server_id) return
try {
const res = await projectApi.listProjects({ scrapydServerId: form.scrapyd_server_id })
if (res.code === 0) {
projectOptions.value = res.data.map((project: string) => ({
label: project,
value: project
}))
}
} catch (error) {
console.error('获取项目列表失败:', error)
}
}
// 获取爬虫选项
const getSpiderOptions = async () => {
if (!form.scrapyd_server_id || !form.project) return
try {
const res = await projectApi.listSpiders({
scrapydServerId: form.scrapyd_server_id,
project: form.project
})
if (res.code === 0) {
spiderOptions.value = res.data.map((spider: string) => ({
label: spider,
value: spider
}))
}
} catch (error) {
console.error('获取爬虫列表失败:', error)
}
}
// 服务器变更处理
const handleServerChange = async () => {
form.project = ''
form.spider = ''
projectOptions.value = []
spiderOptions.value = []
await getProjectOptions()
}
// 项目变更处理
const handleProjectChange = async () => {
form.spider = ''
spiderOptions.value = []
await getSpiderOptions()
}
// 取消按钮
const handleCancel = () => {
emit('on-cancel')
}
// 提交表单
const submit = async () => {
if (!formRef.value) return
await formRef.value.validate(async (valid) => {
if (valid) {
submitting.value = true
try {
// 解析可选参数
let parsedOptions = {}
if (form.options) {
try {
parsedOptions = JSON.parse(form.options)
} catch (e) {
ElMessage.error('其他参数格式错误,请检查JSON格式')
submitting.value = false
return
}
}
// 执行爬虫
const res = await projectApi.schedule({
scrapydServerId: form.scrapyd_server_id,
project: form.project,
spider: form.spider,
option: parsedOptions
})
if (res.code === 0) {
ElMessage({
type: 'success',
message: '任务提交成功: ' + res.data.jobid
})
emit('on-success')
}
} catch (error) {
console.error('提交执行任务失败:', error)
} finally {
submitting.value = false
}
}
})
}
onMounted(() => {
initData()
})
</script>
<template>
<el-button
size="small"
type="text"
@click="handleSchedule"
>
<el-icon><VideoPlay /></el-icon>
</el-button>
</template>
<script setup lang="ts">
import { ElMessage } from 'element-plus'
import { VideoPlay } from '@element-plus/icons-vue'
import { schedule } from '@/api/project'
const props = defineProps({
spider: { type: String, default: '' },
project: { type: String, default: '' },
scrapydServerId: { type: String, default: '' },
options: { type: String, default: '' }
})
const emit = defineEmits(['success'])
// 处理运行爬虫
const handleSchedule = async () => {
try {
// 解析可选参数
let parsedOptions = {}
if (props.options) {
try {
parsedOptions = JSON.parse(props.options)
} catch (e) {
ElMessage.error('参数格式错误,请检查JSON格式')
return
}
}
const res = await schedule({
scrapydServerId: props.scrapydServerId,
project: props.project,
spider: props.spider,
option: parsedOptions
})
if (res.code === 0) {
ElMessage({
type: 'success',
message: '爬虫已启动: ' + res.data.jobid
})
emit('success')
} else {
ElMessage.error(res.msg || '运行失败')
}
} catch (error) {
console.error('运行爬虫失败:', error)
ElMessage.error('运行爬虫失败')
}
}
</script>
<style lang="scss" scoped>
</style>
<template>
<div class="app-container">
<div class="page-header">
<el-button @click="goBack">
<el-icon><Back /></el-icon>返回
</el-button>
<h2>爬虫管理: {{ project }}</h2>
</div>
<el-card class="box-card">
<template #header>
<div class="card-header">
<span>爬虫列表</span>
<div class="right-panel">
<el-input
v-model="searchQuery"
placeholder="搜索爬虫"
clearable
class="search-input"
@input="handleSearch"
>
<template #prefix>
<el-icon><Search /></el-icon>
</template>
</el-input>
<el-button type="primary" @click="refreshSpiderList">
<el-icon><Refresh /></el-icon>刷新
</el-button>
</div>
</div>
</template>
<el-table v-loading="loading" :data="filteredSpiderList" stripe border style="width: 100%">
<el-table-column label="序号" width="60" align="center">
<template #default="scope">
{{ scope.$index + 1 }}
</template>
</el-table-column>
<el-table-column prop="spider" label="爬虫名称" align="center"/>
<el-table-column label="调度日志" width="100" align="center">
<template #default="scope">
<router-link
:to="{
path: '/schedule-log/list',
query: {
project: project,
spider: scope.row.spider,
scrapydServerId: scrapydServerId
}
}"
target="_blank"
>
<el-button type="info" size="small">
<el-icon><Document /></el-icon>日志
</el-button>
</router-link>
</template>
</el-table-column>
<el-table-column label="运行统计" width="100" align="center">
<template #default="scope">
<router-link
:to="{
path: '/stats/list',
query: {
project: project,
spider: scope.row.spider,
scrapydServerId: scrapydServerId
}
}"
target="_blank"
>
<el-button type="warning" size="small">
<el-icon><Histogram /></el-icon>统计
</el-button>
</router-link>
</template>
</el-table-column>
<el-table-column label="操作" width="220">
<template #default="scope">
<el-button type="primary" size="small" @click="handleRunSpider(scope.row)">
<el-icon><VideoPlay /></el-icon>运行
</el-button>
<el-button type="success" size="small" @click="viewLogs(scope.row)">
<el-icon><Document /></el-icon>查看日志
</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
<!-- 运行爬虫对话框 -->
<el-dialog v-model="runDialogVisible" title="运行爬虫" width="500px">
<el-form :model="runForm" label-width="120px" :rules="rules" ref="runFormRef">
<el-form-item label="爬虫名称">
<el-input v-model="runForm.spider" disabled />
</el-form-item>
<el-form-item label="任务ID" prop="jobid">
<el-input v-model="runForm.jobid" placeholder="请输入任务ID" />
</el-form-item>
<el-form-item label="爬虫参数">
<el-input
v-model="runForm.spider_args"
type="textarea"
:rows="4"
placeholder="请输入爬虫参数,格式为: key=value key2=value2"
/>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="runDialogVisible = false">取消</el-button>
<el-button type="primary" @click="submitRunSpider" :loading="submitLoading">确认</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, computed } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
import { getSpiderList, scheduleJob } from '@/api/project'
import { Back, Refresh, VideoPlay, Document, Search, Histogram } from '@element-plus/icons-vue'
import { v4 as uuidv4 } from 'uuid'
const route = useRoute()
const router = useRouter()
const project = ref('')
const scrapydServerId = ref('')
const loading = ref(false)
const spiderList = ref<any[]>([])
const runDialogVisible = ref(false)
const submitLoading = ref(false)
const searchQuery = ref('')
const runForm = ref({
spider: '',
jobid: '',
spider_args: ''
})
const runFormRef = ref()
// 根据搜索条件过滤爬虫列表
const filteredSpiderList = computed(() => {
if (!searchQuery.value) return spiderList.value
return spiderList.value.filter(item =>
item.spider.toLowerCase().includes(searchQuery.value.toLowerCase())
)
})
// 表单验证规则
const rules = {
jobid: [
{ required: true, message: '请输入任务ID', trigger: 'blur' }
]
}
// 处理搜索
const handleSearch = () => {
// 搜索已由计算属性处理
}
// 返回项目列表
const goBack = () => {
router.push('/project')
}
// 获取爬虫列表
const fetchSpiderList = async () => {
if (!project.value || !scrapydServerId.value) return
loading.value = true
try {
const response = await getSpiderList({
project: project.value,
scrapydServerId: scrapydServerId.value
})
spiderList.value = response.data
} catch (error) {
console.error('获取爬虫列表失败:', error)
ElMessage.error('获取爬虫列表失败')
} finally {
loading.value = false
}
}
// 刷新爬虫列表
const refreshSpiderList = () => {
fetchSpiderList()
}
// 运行爬虫
const handleRunSpider = (row: any) => {
runDialogVisible.value = true
runForm.value = {
spider: row.spider,
jobid: uuidv4(),
spider_args: ''
}
}
// 提交运行爬虫
const submitRunSpider = async () => {
if (!runFormRef.value) return
await runFormRef.value.validate(async (valid: any) => {
if (valid) {
submitLoading.value = true
try {
await scheduleJob({
project: project.value,
spider: runForm.value.spider,
scrapydServerId: scrapydServerId.value,
jobid: runForm.value.jobid,
spider_args: runForm.value.spider_args,
})
ElMessage.success('爬虫已启动')
runDialogVisible.value = false
} catch (error) {
console.error('启动爬虫失败:', error)
ElMessage.error('启动爬虫失败')
} finally {
submitLoading.value = false
}
}
})
}
// 查看日志
const viewLogs = (row: any) => {
router.push({
path: '/logs/spider',
query: {
project: project.value,
spider: row.spider,
scrapydServerId: scrapydServerId.value
}
})
}
onMounted(() => {
project.value = route.query.project as string
scrapydServerId.value = window.localStorage.getItem('scrapyd_server_id') || ''
if (!project.value) {
ElMessage.error('缺少项目参数')
router.push('/project')
return
}
if (!scrapydServerId.value) {
ElMessage.error('缺少服务器ID参数')
router.push('/project')
return
}
fetchSpiderList()
})
</script>
<style scoped lang="scss">
.app-container {
padding: 20px;
}
.page-header {
display: flex;
align-items: center;
margin-bottom: 20px;
gap: 10px;
h2 {
margin: 0;
}
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.right-panel {
display: flex;
gap: 10px;
}
.search-input {
width: 200px;
}
.el-icon {
margin-right: 5px;
}
</style>
<template>
<div class="app-container">
<div class="page-header">
<el-button @click="goBack">
<el-icon><Back /></el-icon>返回
</el-button>
<h2>爬虫日志: {{ project }} / {{ spider }}</h2>
</div>
<el-card class="box-card">
<template #header>
<div class="card-header">
<span>日志列表</span>
<div class="right-panel">
<el-button type="primary" @click="refreshLogList">
<el-icon><Refresh /></el-icon>刷新
</el-button>
</div>
</div>
</template>
<el-table v-loading="loading" :data="logList" stripe style="width: 100%">
<el-table-column prop="logfile" label="日志文件" />
<el-table-column label="操作" width="150">
<template #default="scope">
<el-button type="primary" size="small" @click="viewLog(scope.row)">
查看日志内容
</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
<!-- 日志内容对话框 -->
<el-dialog v-model="logDialogVisible" :title="currentLog" width="80%" top="5vh">
<div v-loading="logLoading" class="log-content">
<pre>{{ logContent }}</pre>
</div>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
import { getSpiderLogs, getJobLog } from '@/api/log'
import { Back, Refresh } from '@element-plus/icons-vue'
const route = useRoute()
const router = useRouter()
const project = ref('')
const spider = ref('')
const loading = ref(false)
const logList = ref([])
const logDialogVisible = ref(false)
const logLoading = ref(false)
const logContent = ref('')
const currentLog = ref('')
// 返回爬虫列表
const goBack = () => {
router.push({
path: '/spider',
query: { project: project.value }
})
}
// 获取日志列表
const fetchLogList = async () => {
if (!project.value || !spider.value) return
loading.value = true
try {
const response = await getSpiderLogs({
project: project.value,
spider: spider.value,
scrapydServerId: localStorage.getItem('scrapydServerId')!
})
logList.value = response.data.logs.map((logfile: any) => ({ logfile }))
} catch (error) {
console.error('获取日志列表失败:', error)
ElMessage.error('获取日志列表失败')
} finally {
loading.value = false
}
}
// 刷新日志列表
const refreshLogList = () => {
fetchLogList()
}
// 查看日志内容
const viewLog = async (row: any) => {
currentLog.value = row.logfile
logDialogVisible.value = true
logContent.value = ''
logLoading.value = true
try {
const response = await getJobLog({
project: project.value,
spider: spider.value,
job: row.logfile.replace('.log', ''),
scrapydServerId: localStorage.getItem('scrapydServerId')!
})
logContent.value = response.data.log || '无日志内容'
} catch (error) {
console.error('获取日志内容失败:', error)
ElMessage.error('获取日志内容失败')
logContent.value = '获取日志内容失败'
} finally {
logLoading.value = false
}
}
onMounted(() => {
project.value = route.query.project as string
spider.value = route.query.spider as string
if (!project.value || !spider.value) {
ElMessage.error('缺少必要参数')
router.push('/project')
return
}
fetchLogList()
})
</script>
<style scoped lang="scss">
.app-container {
padding: 20px;
}
.page-header {
display: flex;
align-items: center;
margin-bottom: 20px;
gap: 10px;
h2 {
margin: 0;
}
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.right-panel {
display: flex;
gap: 10px;
}
.log-content {
max-height: 70vh;
overflow-y: auto;
background: #f5f7fa;
padding: 16px;
border-radius: 4px;
pre {
white-space: pre-wrap;
font-family: monospace;
margin: 0;
}
}
.el-icon {
margin-right: 5px;
}
</style>
<template>
<div class="stats-table">
<el-table
v-bind="$attrs"
:data="data"
border
style="width: 100%"
@sort-change="handleSortChange"
>
<el-table-column
align="center"
label="序号"
width="60"
>
<template #default="scope">
{{ scope.$index + 1 }}
</template>
</el-table-column>
<el-table-column
label="项目名"
align="center"
width="150px"
prop="project"
>
<template #default="scope">
{{ scope.row.project || '-' }}
</template>
</el-table-column>
<el-table-column
label="Spider"
align="center"
prop="spider"
>
<template #default="scope">
{{ scope.row.spider }}
</template>
</el-table-column>
<el-table-column
label="收集数量"
align="center"
width="80px"
prop="item_scraped_count"
>
<template #default="scope">
{{ scope.row.item_scraped_count }}
</template>
</el-table-column>
<el-table-column
label="丢弃数量"
align="center"
width="80px"
prop="item_dropped_count"
>
<template #default="scope">
{{ scope.row.item_dropped_count }}
</template>
</el-table-column>
<el-table-column
label="错误日志"
align="center"
width="110px"
prop="log_error_count"
sortable="custom"
>
<template #default="scope">
{{ scope.row.log_error_count }}
</template>
</el-table-column>
<el-table-column
label="持续时间"
align="right"
width="110px"
prop="duration"
sortable="custom"
>
<template #default="scope">
{{ scope.row.duration_str }}
</template>
</el-table-column>
<el-table-column
label="开始时间"
align="center"
width="170px"
prop="start_time"
>
<template #default="scope">
{{ scope.row.start_time }}
</template>
</el-table-column>
<el-table-column
label="结束时间"
align="center"
width="170px"
prop="finish_time"
>
<template #default="scope">
{{ scope.row.finish_time }}
</template>
</el-table-column>
<el-table-column
label="操作"
align="center"
width="180px"
>
<template #default="scope">
<el-button
type="primary"
size="small"
@click="viewDetail(scope.row)"
>
详情
</el-button>
<el-button
type="danger"
size="small"
@click="confirmRemove(scope.row)"
>
删除
</el-button>
</template>
</el-table-column>
<el-table-column
label="运行日志"
align="center"
width="80px"
>
<template #default="scope">
<router-link
:to="{
name: 'SpiderLogs',
query: {
scrapydServerId: scope.row.scrapyd_server_id,
project: scope.row.project,
spider: scope.row.spider,
job: scope.row.spider_job_id
}
}"
target="_blank"
>
<el-icon><Document /></el-icon> 日志
</router-link>
</template>
</el-table-column>
</el-table>
<!-- 详情对话框 -->
<el-dialog
v-model="dialogVisible"
title="统计详情"
width="70%"
>
<div v-if="currentDetail" class="stats-detail">
<el-descriptions :column="2" border>
<el-descriptions-item label="项目名">{{ currentDetail.project }}</el-descriptions-item>
<el-descriptions-item label="爬虫名">{{ currentDetail.spider }}</el-descriptions-item>
<el-descriptions-item label="开始时间">{{ currentDetail.start_time }}</el-descriptions-item>
<el-descriptions-item label="结束时间">{{ currentDetail.finish_time }}</el-descriptions-item>
<el-descriptions-item label="收集数量">{{ currentDetail.item_scraped_count }}</el-descriptions-item>
<el-descriptions-item label="丢弃数量">{{ currentDetail.item_dropped_count }}</el-descriptions-item>
<el-descriptions-item label="持续时间">{{ currentDetail.duration_str }}</el-descriptions-item>
<el-descriptions-item label="结束原因">{{ currentDetail.finish_reason || '-' }}</el-descriptions-item>
</el-descriptions>
<div class="detail-section" v-if="currentDetail.stats">
<h3>统计详情</h3>
<el-table :data="statsTableData" border style="width: 100%">
<el-table-column prop="key" label="指标名称" min-width="250px" />
<el-table-column prop="value" label="值" min-width="100px" />
</el-table>
</div>
</div>
</el-dialog>
</div>
</template>
<script lang="ts" setup>
import { ref, defineProps, defineEmits, computed } from 'vue'
import { Document } from '@element-plus/icons-vue'
import { ElMessageBox, ElMessage } from 'element-plus'
import * as statsApi from '@/api/stats'
const props = defineProps({
data: {
type: Array,
default: () => []
}
})
const emit = defineEmits(['sort-change'])
const dialogVisible = ref(false)
const currentDetail = ref<Record<string, any> | null>(null)
// 统计数据表格展示
const statsTableData = computed(() => {
if (!currentDetail.value || !currentDetail.value.stats) return []
return Object.entries(currentDetail.value.stats)
.filter(([key]) => key !== 'spider' && key !== 'project') // 过滤掉已显示在上方的字段
.map(([key, value]) => ({
key,
value: String(value)
}))
.sort((a, b) => a.key.localeCompare(b.key))
})
// 查看详情
const viewDetail = async (row: Record<string, any>) => {
try {
const res = await statsApi.getStatsDetail({
scrapydServerId: row.scrapyd_server_id,
spider_job_id: row.spider_job_id
})
if (res.code === 0) {
currentDetail.value = res.data
dialogVisible.value = true
}
} catch (error) {
console.error('获取统计详情失败:', error)
}
}
// 确认删除
const confirmRemove = (row: Record<string, any>) => {
ElMessageBox.confirm('确认删除该统计记录?', '提示', {
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning'
})
.then(async () => {
try {
const res = await statsApi.removeStats({
scrapydServerId: row.scrapyd_server_id,
spider_job_id: row.spider_job_id
})
if (res.code === 0) {
ElMessage({
type: 'success',
message: '删除成功!'
})
emit('sort-change', { prop: '', order: '' }) // 触发刷新
}
} catch (error) {
console.error('删除统计记录失败:', error)
}
})
.catch(() => {
// 取消操作
})
}
// 排序变化
const handleSortChange = (val: { prop: string, order: string }) => {
emit('sort-change', val)
}
</script>
<style lang="scss" scoped>
.stats-table {
margin-bottom: 20px;
}
.stats-detail {
.detail-section {
margin-top: 20px;
h3 {
margin-bottom: 15px;
font-weight: 500;
font-size: 16px;
}
}
}
</style>
...@@ -43,7 +43,6 @@ ...@@ -43,7 +43,6 @@
<script lang="ts" setup> <script lang="ts" setup>
import { ref, onMounted } from 'vue' import { ref, onMounted } from 'vue'
import StatsTable from '@/views/stats/components/StatsTable.vue'
import StatsTool from '@/views/stats/components/StatsTool.vue' import StatsTool from '@/views/stats/components/StatsTool.vue'
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';
......
...@@ -24,13 +24,14 @@ export default defineConfig({ ...@@ -24,13 +24,14 @@ export default defineConfig({
} }
} }
}, },
base: './',
server: { server: {
host: '0.0.0.0', host: '0.0.0.0',
port: 9529, port: 9529,
proxy: { proxy: {
// 代理API请求,使用更精确的路径匹配 // 代理API请求,使用更精确的路径匹配
'/api': { '/api': {
target: 'http://192.168.0.200:5001/', target: 'http://192.168.0.176:5001/',
changeOrigin: true, changeOrigin: true,
}, },
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment