Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
F
fk-spider-web
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
周田
fk-spider-web
Commits
0c43d37b
Commit
0c43d37b
authored
Oct 09, 2025
by
yzh
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat:解析表达式
parent
2fcb89ec
Show whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
310 additions
and
48 deletions
+310
-48
Crontab.vue
src/components/Crontab/src/Crontab.vue
+9
-7
index.vue
src/components/Export/index.vue
+199
-34
index.vue
src/views/os-dataDisplay/index.vue
+29
-1
taskCard.vue
src/views/os-status/components/taskCard.vue
+3
-3
taskCard.vue
src/views/os-taskInformation/components/taskCard.vue
+70
-3
No files found.
src/components/Crontab/src/Crontab.vue
View file @
0c43d37b
...
...
@@ -505,7 +505,7 @@ const submit = () => {
dialogVisible
.
value
=
false
}
else
{
ElMessage
.
warning
(
'cron表达式错误,只可设置一个间隔'
)
defaultValue
.
value
=
'* * * * * *'
//
defaultValue.value = '* * * * * *'
}
}
...
...
@@ -697,7 +697,8 @@ const inputChange = () => {
</el-form-item>
</el-form>
</el-tab-pane>
<el-tab-pane>
<!-- <el-tab-pane>
<template #label>
<div class="sc-cron-num">
<h2>周</h2>
...
...
@@ -709,11 +710,11 @@ const inputChange = () => {
<el-form-item label="类型">
<el-radio-group v-model="cronValue.week.type">
<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="3">指定</el-radio-button> --
>
<
!-- <
el-radio-button value="4">本月最后一周</el-radio-button>
<el-radio-button value="5">不指定</el-radio-button>
-->
<
el-radio-button value="3">指定</el-radio-button
>
<el-radio-button value="4">本月最后一周</el-radio-button>
<el-radio-button value="5">不指定</el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item v-if="cronValue.week.type == '1'" label="范围" class="m-t-4">
...
...
@@ -746,7 +747,8 @@ const inputChange = () => {
</el-form-item>
</el-form>
</el-form>
</el-tab-pane>
</el-tab-pane> -->
<!-- <el-tab-pane>
<template #label>
<div class="sc-cron-num">
...
...
src/components/Export/index.vue
View file @
0c43d37b
...
...
@@ -44,10 +44,24 @@ const formRef = ref<FormInstance>()
const
rules
=
ref
<
FormRules
<
RuleForm
>>
({
timeValue
:
[
{
type
:
'date'
,
//
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
:
[
...
...
@@ -61,7 +75,7 @@ const rules = ref<FormRules<RuleForm>>({
})
//表单绑定的字段
const
exportObject
=
ref
({
timeValue
:
""
,
timeValue
:
[]
,
spiderType
:
[]
})
const
fullscreenLoading
=
ref
(
false
)
...
...
@@ -75,59 +89,210 @@ const emit = defineEmits(['update:dialogVisible'])
const
exportDialogVisible
=
ref
(
props
.
dialogVisible
)
// 导出方法
// const handleExport = async () => {
// // saveToFile('666')
// 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
if
(
!
formRef
.
value
)
return
;
try
{
// 1. 先在用户点击时获取保存文件句柄(关键:在用户手势中执行)
const
fileHandle
=
await
getSaveFileHandle
();
if
(
!
fileHandle
)
{
ElMessage
.
info
(
'已取消保存'
);
return
;
}
// 2. 执行表单验证
await
formRef
.
value
.
validate
(
async
(
valid
,
fields
)
=>
{
console
.
log
(
'开始校验'
);
if
(
valid
)
{
console
.
log
(
'校验通过'
);
const
loading
=
ElLoading
.
service
({
lock
:
true
,
text
:
'Loading
'
,
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'
)
||
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiZXhwIjoxNzU5MjA5NzM4fQ.6hVko0EQTuz7OYjXEafZYLpmkVEyiLhZ8aWHi0Pni_s'
,
// 根据你的 token 存储方式调整
'Token'
:
localStorage
.
getItem
(
'Admin-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
()
);
// 4. 使用之前获取的句柄保存文件
await
saveToSelectedLocation
(
res
.
data
,
fileHandle
);
loading
.
close
();
ElMessage
.
success
(
'ZIP文件保存成功'
);
close
()
ElMessage
.
success
(
'导出成功'
);
}
catch
(
e
)
{
console
.
error
(
'导出失败'
,
e
)
close
()
ElMessage
.
error
(
'导出失败
'
);
console
.
error
(
'导出失败'
,
e
);
loading
.
close
();
ElMessage
.
error
(
'ZIP文件导出失败,请重试
'
);
}
}
else
{
console
.
log
(
'校验不通过'
);
}
})
});
}
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
=
()
=>
{
formRef
.
value
?.
clearValidate
()
...
...
src/views/os-dataDisplay/index.vue
View file @
0c43d37b
...
...
@@ -5,6 +5,22 @@
<div
class=
"custom-style flex gap-4"
>
<el-segmented
v-model=
"mode"
:options=
"sizeOptions"
style=
"margin-bottom: 1rem"
size=
"default"
/>
<el-button
type=
"primary"
@
click=
"handleExport"
>
导出
</el-button>
<el-button
type=
"primary"
@
click=
"handleExport"
>
导入
</el-button>
<!--
<el-upload
ref=
"upload"
class=
"upload-demo"
action=
"https://run.mocky.io/v3/9d059bf9-4660-45f2-925d-ce80ad6c4d15"
:limit=
"1"
:on-exceed=
"handleExceed"
:auto-upload=
"false"
>
<template
#
trigger
>
<el-button
type=
"primary"
>
select file
</el-button>
</
template
>
<el-button
class=
"ml-3"
type=
"success"
@
click=
"submitUpload"
>
upload to server
</el-button>
<
template
#
tip
>
<div
class=
"el-upload__tip text-red"
>
limit 1 file, new file will cover the old file
</div>
</
template
>
</el-upload>
-->
</div>
</div>
<!-- 综合数据页面组件 -->
...
...
@@ -33,7 +49,8 @@ import ituDataTab from './components/ituDataTab.vue'
import
stDataTab
from
'./components/stDataTab.vue'
import
dsnDataTab
from
'./components/dsnData/dsnTab.vue'
import
exportDialog
from
'@/components/Export/index.vue'
import
{
genFileId
}
from
'element-plus'
import
type
{
UploadInstance
,
UploadProps
,
UploadRawFile
}
from
'element-plus'
// const
const
mode
=
ref
(
'DSN数据'
)
const
showDeleteDialog
=
ref
(
false
)
...
...
@@ -41,7 +58,18 @@ const sizeOptions = ['DSN数据', 'ITU数据', 'ST数据']
const
route
=
useRoute
()
const
router
=
useRouter
()
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
=
()
=>
{
router
.
push
({
path
:
'/osStatus/list'
,
...
...
src/views/os-status/components/taskCard.vue
View file @
0c43d37b
...
...
@@ -80,7 +80,7 @@ const goToDSNTaskRecordPage = () => {
router
.
push
({
path
:
'/osTaskInformation/list'
,
query
:
{
mode
:
'dsn
'
,
spiderType
:
'dsn_now
'
,
jump
:
'yes'
,
page
:
'statusMonitor'
}
...
...
@@ -90,7 +90,7 @@ const goToITUTaskRecordPage = () => {
router
.
push
({
path
:
'/osTaskInformation/list'
,
query
:
{
mode
:
'itu
'
,
spiderType
:
'itu_space_explorer
'
,
jump
:
'yes'
,
page
:
'statusMonitor'
...
...
@@ -101,7 +101,7 @@ const goToSTTaskRecordPage = () => {
router
.
push
({
path
:
'/osTaskInformation/list'
,
query
:
{
mode
:
'st
'
,
spiderType
:
'api_spider
'
,
jump
:
'yes'
,
page
:
'statusMonitor'
}
...
...
src/views/os-taskInformation/components/taskCard.vue
View file @
0c43d37b
...
...
@@ -2,7 +2,6 @@
<
template
>
<div>
<div
class=
"m-t-2"
/>
<div
class=
"text-left p-4 toolbarStyle "
>
<div
class=
"formStyle"
>
<el-form
inline
>
...
...
@@ -61,7 +60,13 @@
<div
class=
"wordStyle"
>
<el-form-item
class=
"form-item"
>
<el-space>
<span
class=
"wordStyle"
>
执行频率:
{{
task
.
frequency
}}
</span>
<span
class=
"wordStyle"
>
执行频率:
{{
parseCronExpression
(
task
.
kwargs
.
cron
)
}}
</span>
</el-space>
</el-form-item>
</div>
<div
class=
"wordStyle"
>
<el-form-item
class=
"form-item"
>
<el-space>
<span
class=
"wordStyle"
>
执行次数:
{{
task
.
count
}}
</span>
</el-space>
</el-form-item>
...
...
@@ -225,7 +230,69 @@ const search = async () => {
}
taskList
.
value
=
res
.
data
}
onMounted
(()
=>
{
// 解析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
]}
月执行一次`
}
}
else
if
(
i
==
5
){
console
.
log
(
aaa
[
i
]);
// if(aaa[i][0] === '1'){
// return `第${aaa[i][2]}周的星期天执行一次`
// }else if(aaa[i][0] === '2'){
// return `第${aaa[i][2]}周的星期一执行一次`
// }else if(aaa[i][0] === '3'){
// return `第${aaa[i][2]}周的星期二执行一次`
// }else if(aaa[i][0] === '4'){
// return `第${aaa[i][2]}周的星期三执行一次`
// }else if(aaa[i][0] === '5'){
// return `第${aaa[i][2]}周的星期四执行一次`
// }else if(aaa[i][0] === '6'){
// return `第${aaa[i][2]}周的星期五执行一次`
// }else if(aaa[i][0] === '7'){
// return `第${aaa[i][2]}周的星期六执行一次`
// }
}
}
console
.
log
(
aaa
);
}
}
}
onMounted
(()
=>
{
if
(
props
.
spiderType
!==
''
)
{
searchCondition
.
value
.
spiders
=
props
.
spiderType
}
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment