Commit 0c43d37b by yzh

feat:解析表达式

parent 2fcb89ec
......@@ -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">
......
......@@ -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
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])
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('ZIP文件保存成功');
close()
} catch (e) {
console.error('导出失败', e);
loading.close();
ElMessage.error('ZIP文件导出失败,请重试');
}
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('校验不通过');
});
} 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()
......
......@@ -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',
......
......@@ -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'
}
......
......@@ -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
}
......
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