Commit 865f028d by 周田

Merge branch 'liucan' into 'main'

feat:新增状态监控页loading效果,重写表单搜索栏组件,调整文字排版

See merge request !12
parents cff37174 d1356d4d
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
"@types/node": "^22.15.17", "@types/node": "^22.15.17",
"@types/nprogress": "^0.2.3", "@types/nprogress": "^0.2.3",
"@types/uuid": "^10.0.0", "@types/uuid": "^10.0.0",
"animate.css": "^4.1.1",
"axios": "^1.9.0", "axios": "^1.9.0",
"cron-parser": "^5.4.0", "cron-parser": "^5.4.0",
"echarts": "^5.6.0", "echarts": "^5.6.0",
......
...@@ -7,7 +7,8 @@ ...@@ -7,7 +7,8 @@
</template> </template>
<style> <style>
html, body { html,
body {
margin: 0; margin: 0;
padding: 0; padding: 0;
height: 100%; height: 100%;
......
// API路径配置文件 // API路径配置文件
// 这个文件确保所有API路径在一处统一管理,方便修改 // 这个文件确保所有API路径在一处统一管理,方便修改
import { pauseJob } from "./schedule" import { pauseJob } from "./schedule";
export const authApi = { export const authApi = {
login: '/auth/login', login: "/auth/login",
logout: '/auth/logout' logout: "/auth/logout",
} as const } as const;
export const scrapydApi = { export const scrapydApi = {
// scrapyd相关接口 // scrapyd相关接口
listProjects: '/scrapyd/listProjects', listProjects: "/scrapyd/listProjects",
listVersions: '/scrapyd/listVersions', listVersions: "/scrapyd/listVersions",
listSpiders: '/scrapyd/listSpiders', listSpiders: "/scrapyd/listSpiders",
listJobsMerge: '/scrapyd/listJobsMerge', listJobsMerge: "/scrapyd/listJobsMerge",
cancel: '/scrapyd/cancel', cancel: "/scrapyd/cancel",
cancelAllJob: '/scrapyd/cancelAllJob', cancelAllJob: "/scrapyd/cancelAllJob",
deleteVersion: '/scrapyd/deleteVersion', deleteVersion: "/scrapyd/deleteVersion",
deleteProject: '/scrapyd/deleteProject', deleteProject: "/scrapyd/deleteProject",
schedule: '/scrapyd/schedule', schedule: "/scrapyd/schedule",
daemonStatus: '/scrapyd/daemonStatus', daemonStatus: "/scrapyd/daemonStatus",
addVersion: '/scrapyd/addVersion', addVersion: "/scrapyd/addVersion",
// 日志相关 // 日志相关
logs: '/scrapyd/logs', logs: "/scrapyd/logs",
projectLogs: '/scrapyd/projectLogs', projectLogs: "/scrapyd/projectLogs",
spiderLogs: '/scrapyd/spiderLogs', spiderLogs: "/scrapyd/spiderLogs",
jobLog: '/scrapyd/jobLog' jobLog: "/scrapyd/jobLog",
} as const } as const;
export const scheduleApi = { export const scheduleApi = {
// 调度任务相关 // 调度任务相关
addJob: '/schedule/addJob', addJob: "/schedule/addJob",
getJobs: '/schedule/getJobs', getJobs: "/schedule/getJobs",
pauseJob: '/schedule/pauseJob', pauseJob: "/schedule/pauseJob",
resumeJob: '/schedule/resumeJob', resumeJob: "/schedule/resumeJob",
removeJob: '/schedule/removeJob', removeJob: "/schedule/removeJob",
jobDetail: '/schedule/jobDetail', jobDetail: "/schedule/jobDetail",
state: '/schedule/state', state: "/schedule/state",
start: '/schedule/start', start: "/schedule/start",
shutdown: '/schedule/shutdown', shutdown: "/schedule/shutdown",
pause: '/schedule/pause', pause: "/schedule/pause",
resume: '/schedule/resume', resume: "/schedule/resume",
removeAllJobs: '/schedule/removeAllJobs', removeAllJobs: "/schedule/removeAllJobs",
scheduleLogs: '/schedule/scheduleLogs', scheduleLogs: "/schedule/scheduleLogs",
removeScheduleLogs: '/schedule/removeScheduleLogs', removeScheduleLogs: "/schedule/removeScheduleLogs",
} as const } as const;
export const scrapydServerApi = { export const scrapydServerApi = {
getScrapydServerPage: '/scrapydServer/getScrapydServerPage', getScrapydServerPage: "/scrapydServer/getScrapydServerPage",
addScrapydServer: '/scrapydServer/addScrapydServer', addScrapydServer: "/scrapydServer/addScrapydServer",
updateScrapydServer: '/scrapydServer/updateScrapydServer', updateScrapydServer: "/scrapydServer/updateScrapydServer",
updateScrapydServerStatus: '/scrapydServer/updateScrapydServerStatus', updateScrapydServerStatus: "/scrapydServer/updateScrapydServerStatus",
deleteScrapydServer: '/scrapydServer/deleteScrapydServer' deleteScrapydServer: "/scrapydServer/deleteScrapydServer",
} as const } as const;
// 系统相关 // 系统相关
export const systemApi = { export const systemApi = {
systemInfo: '/system/systemInfo', systemInfo: "/system/systemInfo",
systemData: '/system/systemData', systemData: "/system/systemData",
systemConfig: '/system/systemConfig', systemConfig: "/system/systemConfig",
loginHistoryList: '/actionHistory/loginHistoryList', loginHistoryList: "/actionHistory/loginHistoryList",
scrapydServerList: '/system/scrapydServerList' // 没用 scrapydServerList: "/system/scrapydServerList", // 没用
} as const } as const;
// 统计相关 // 统计相关
export const statsApi = { export const statsApi = {
statsList: '/statsCollection/listItem', statsList: "/statsCollection/listItem",
statsDetail: '/statsCollection/detail', statsDetail: "/statsCollection/detail",
removeStats: '/statsCollection/delete', removeStats: "/statsCollection/delete",
clearAllStats: '/statsCollection/clearAll' // TODO 未实现 clearAllStats: "/statsCollection/clearAll", // TODO 未实现
} as const } as const;
// 系统用户相关接口 // 系统用户相关接口
export const userApi = { export const userApi = {
userList: '/user/list', userList: "/user/list",
addUser: '/user/insert', addUser: "/user/insert",
updateUser: '/user/update', updateUser: "/user/update",
deleteUser: '/user/delete', deleteUser: "/user/delete",
batchDelete: '/user/batchDelete', batchDelete: "/user/batchDelete",
} as const } as const;
// 系统用户相关接口 // 系统用户相关接口
export const spiderApi = { export const spiderApi = {
spiderList: '/scrapyd/listSpiders', spiderList: "/scrapyd/listSpiders",
} as const } as const;
// 爬虫任务相关接口 // 爬虫任务相关接口
export const spiderTaskApi = { export const spiderTaskApi = {
taskList: '/schedule/getJobs', taskList: "/schedule/getJobs",
taskRecord: '/schedule/scheduleLogs', taskRecord: "/schedule/scheduleLogs",
addTask: '/schedule/addJob', addTask: "/schedule/addJob",
deleteTask: '/schedule/removeJob', deleteTask: "/schedule/removeJob",
pauseJob: '/schedule/pauseJob', pauseJob: "/schedule/pauseJob",
resumeJob: '/schedule/resumeJob', resumeJob: "/schedule/resumeJob",
jobDetail: '/schedule/jobDetail', jobDetail: "/schedule/jobDetail",
taskCount:'/schedule/getAllJobCount' taskCount: "/schedule/getAllJobCount",
} as const addSateNo: "/schedule/resetNoradID",
getSateIdList: "/schedule/getNoradIDList",
// 爬虫数据相关接口 } as const;
// 爬虫数据相关接口
export const spiderDataApi = { export const spiderDataApi = {
dsnList: '/dsn/list', dsnList: "/dsn/list",
dsnDetail: '/dsn/detail', dsnDetail: "/dsn/detail",
dsnDataDelete: '/dsn/delete', dsnDataDelete: "/dsn/delete",
ituList: '/itu/list', ituList: "/itu/list",
ituDetail: '/itu/detail', ituDetail: "/itu/detail",
ituDataDelete: '/itu/delete', ituDataDelete: "/itu/delete",
stList: '/spaceTrack/list', stList: "/spaceTrack/list",
stDetail: '/spaceTrack/detail', stDetail: "/spaceTrack/detail",
stDataDelete: '/sspaceTrackt/delete', stDataDelete: "/sspaceTrackt/delete",
exportSpiderData: '/export/downloadFile', exportSpiderData: "/export/downloadFile",
esaList: '/esa/list', esaList: "/esa/list",
esaMissionDetail: '/esa/missionDetail', esaMissionDetail: "/esa/missionDetail",
esaStationDetail: '/esa/stationDetail', esaStationDetail: "/esa/stationDetail",
} as const;
} as const
// 爬虫数据相关接口
// 爬虫数据相关接口
export const dataApi = { export const dataApi = {
dataStatistics: '/statistic/dataStatistics', dataStatistics: "/statistic/dataStatistics",
taskStatistics: '/statistic/taskStatistics', taskStatistics: "/statistic/taskStatistics",
performanceStatistics: '/statistic/performanceStatistics', performanceStatistics: "/statistic/performanceStatistics",
allSpiderTaskStatistics: '/statistic/getAllTaskStatistics', allSpiderTaskStatistics: "/statistic/getAllTaskStatistics",
} as const } as const;
import { request, POST } from '@/utils/request' import { request, POST } from "@/utils/request";
import type { ApiResponse, QueryParams ,UserQueryParams } from '@/utils/request' import type { ApiResponse, QueryParams, UserQueryParams } from "@/utils/request";
import { systemApi, scrapydServerApi, spiderApi } from './apiPaths' import { systemApi, scrapydServerApi, spiderApi, spiderTaskApi } from "./apiPaths";
// 获取系统信息 // 获取系统信息
export function getSystemInfo(): Promise<ApiResponse> { export function getSystemInfo(): Promise<ApiResponse> {
return request({ return request({
url: systemApi.systemInfo, url: systemApi.systemInfo,
method: POST method: POST,
}) as unknown as Promise<ApiResponse> }) as unknown as Promise<ApiResponse>;
} }
// 获取系统数据 // 获取系统数据
...@@ -15,16 +15,16 @@ export function getSystemData(data: QueryParams): Promise<ApiResponse> { ...@@ -15,16 +15,16 @@ export function getSystemData(data: QueryParams): Promise<ApiResponse> {
return request({ return request({
url: systemApi.systemData, url: systemApi.systemData,
method: POST, method: POST,
data data,
}) as unknown as Promise<ApiResponse> }) as unknown as Promise<ApiResponse>;
} }
// 获取系统配置 // 获取系统配置
export function getSystemConfig(): Promise<ApiResponse> { export function getSystemConfig(): Promise<ApiResponse> {
return request({ return request({
url: systemApi.systemConfig, url: systemApi.systemConfig,
method: POST method: POST,
}) as unknown as Promise<ApiResponse> }) as unknown as Promise<ApiResponse>;
} }
// 更新系统配置 // 更新系统配置
...@@ -32,8 +32,8 @@ export function updateSystemConfig(data: Record<string, any>): Promise<ApiRespon ...@@ -32,8 +32,8 @@ export function updateSystemConfig(data: Record<string, any>): Promise<ApiRespon
return request({ return request({
url: systemApi.systemConfig, url: systemApi.systemConfig,
method: POST, method: POST,
data data,
}) as unknown as Promise<ApiResponse> }) as unknown as Promise<ApiResponse>;
} }
// 获取登录历史 // 获取登录历史
...@@ -41,22 +41,39 @@ export function getLoginHistory(params: QueryParams): Promise<ApiResponse> { ...@@ -41,22 +41,39 @@ export function getLoginHistory(params: QueryParams): Promise<ApiResponse> {
return request({ return request({
url: systemApi.loginHistoryList, url: systemApi.loginHistoryList,
method: POST, method: POST,
params params,
}) as unknown as Promise<ApiResponse> }) as unknown as Promise<ApiResponse>;
} }
export const getScrapydServerList = (): Promise<ApiResponse> => { export const getScrapydServerList = (): Promise<ApiResponse> => {
return request({ return request({
url: scrapydServerApi.getScrapydServerPage, url: scrapydServerApi.getScrapydServerPage,
method: POST, method: POST,
}) as unknown as Promise<ApiResponse> }) as unknown as Promise<ApiResponse>;
} };
// 爬虫列表 // 爬虫列表
export function getSpiderList(data: UserQueryParams) { export function getSpiderList(data: UserQueryParams) {
return request({ return request({
url: spiderApi.spiderList, url: spiderApi.spiderList,
method: POST, method: POST,
data data,
}) as unknown as Promise<ApiResponse> }) as unknown as Promise<ApiResponse>;
}
//添加卫星编号
export function addSateNo(data: { id: string[] }) {
return request({
url: spiderTaskApi.addSateNo,
method: POST,
data,
}) as unknown as Promise<ApiResponse>;
}
//获取编号列表
export function getSateIdList() {
return request({
url: spiderTaskApi.getSateIdList,
method: POST,
}) as unknown as Promise<ApiResponse>;
} }
<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 20 20"><g fill="none"><path fill="url(#SVG4JAgqc9r)" d="M16 17V5.5a.5.5 0 0 0-.812-.39l-4.735 3.787l-3.205-1.831a.5.5 0 0 0-.451-.023L3 8.731V17z"/><path fill="url(#SVGazSFLmzC)" fill-opacity="0.3" d="M16 17V5.5a.5.5 0 0 0-.812-.39l-4.735 3.787l-3.205-1.831a.5.5 0 0 0-.451-.023L3 8.731V17z"/><path fill="url(#SVGctjeee0c)" fill-opacity="0.3" d="M16 17V5.5a.5.5 0 0 0-.812-.39l-4.735 3.787l-3.205-1.831a.5.5 0 0 0-.451-.023L3 8.731V17z"/><path fill="url(#SVGZhcbtstm)" fill-opacity="0.3" d="M16 17V5.5a.5.5 0 0 0-.812-.39l-4.735 3.787l-3.205-1.831a.5.5 0 0 0-.451-.023L3 8.731V17z"/><path fill="url(#SVGaypZDdDD)" d="M2.75 2a.75.75 0 0 1 .75.75v12.5c0 .69.56 1.25 1.25 1.25h12.5a.75.75 0 0 1 0 1.5H4.75A2.75 2.75 0 0 1 2 15.25V2.75A.75.75 0 0 1 2.75 2"/><defs><linearGradient id="SVG4JAgqc9r" x1="-1.5" x2="20" y1="6.5" y2="19" gradientUnits="userSpaceOnUse"><stop offset=".164" stop-color="#3bd5ff"/><stop offset=".537" stop-color="#9c6cfe"/><stop offset=".908" stop-color="#e656eb"/></linearGradient><linearGradient id="SVGctjeee0c" x1="9.5" x2="9.5" y1="15" y2="17" gradientUnits="userSpaceOnUse"><stop stop-color="#163697" stop-opacity="0"/><stop offset="1" stop-color="#163697"/></linearGradient><linearGradient id="SVGZhcbtstm" x1="5" x2="3" y1="13.5" y2="13.5" gradientUnits="userSpaceOnUse"><stop stop-color="#163697" stop-opacity="0"/><stop offset="1" stop-color="#163697"/></linearGradient><linearGradient id="SVGaypZDdDD" x1="16.5" x2="2.498" y1="3" y2="23.849" gradientUnits="userSpaceOnUse"><stop stop-color="#70777d"/><stop offset="1" stop-color="#b9c0c7"/></linearGradient><radialGradient id="SVGazSFLmzC" cx="0" cy="0" r="1" gradientTransform="matrix(-10.00002 8.99995 -9.1043 -10.11597 14 7)" gradientUnits="userSpaceOnUse"><stop offset=".636" stop-color="#0fafff" stop-opacity="0"/><stop offset=".962" stop-color="#0067bf"/></radialGradient></defs></g></svg>
\ No newline at end of file
<template> <template>
<el-dialog v-model="deleteDialogVisible" title="删除" width="250" center align-center @close="close" draggable> <el-dialog v-model="deleteDialogVisible" title="删除" width="250" center align-center @close="close" draggable>
<div class="text-center"> <div class="text-center">
<span style="color: #fff;font-size: 15px;">确定删除吗?</span> <span style="color: #fff; font-size: 15px">确定删除吗?</span>
</div> </div>
<template #footer> <template #footer>
<div class="dialog-footer"> <div class="dialog-footer">
<el-button @click="close">取消</el-button> <el-button @click="close">取消</el-button>
<el-button type="primary" @click="handleDelelte"> <el-button type="primary" @click="handleDelelte"> 确定 </el-button>
确定
</el-button>
</div> </div>
</template> </template>
</el-dialog> </el-dialog>
</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 { getUserList, addUser, deleteUser, updateUser, batchDeleteUser } from '@/api/user.ts' import { deleteUser, batchDeleteUser } from "@/api/user.ts";
import { DeleteMode } from '@/components/Delete/enum.ts' import { DeleteMode } from "@/components/Delete/enum.ts";
interface deleteDialogPropType { interface deleteDialogPropType {
dialogVisible: boolean, dialogVisible: boolean;
deleteMode: DeleteMode, deleteMode: DeleteMode;
id: number, id: number;
ids: number[], ids: number[];
} }
const props = defineProps<deleteDialogPropType>() const props = defineProps<deleteDialogPropType>();
const emit = defineEmits(['update:dialogVisible', 'confirm', 'getUserList', 'update:deleteMode']) const emit = defineEmits(["update:dialogVisible", "confirm", "getUserList", "update:deleteMode"]);
const deleteDialogVisible = ref(props.dialogVisible) const deleteDialogVisible = ref(props.dialogVisible);
const deleteMode = ref(props.deleteMode) const deleteMode = ref(props.deleteMode);
// 删除方法 // 删除方法
const handleDelelte = async () => { const handleDelelte = async () => {
if (props.deleteMode == DeleteMode.SINGLE_DELETE) { if (props.deleteMode == DeleteMode.SINGLE_DELETE) {
await deleteUser({ id: props.id }) await deleteUser({ id: props.id });
emit('getUserList') emit("getUserList");
deleteDialogVisible.value = false deleteDialogVisible.value = false;
} } else if (props.deleteMode == DeleteMode.BATCH_DELETE) {
else if (props.deleteMode == DeleteMode.BATCH_DELETE) {
console.log(props.ids); console.log(props.ids);
await batchDeleteUser({ ids: props.ids }) await batchDeleteUser({ ids: props.ids });
emit('getUserList') emit("getUserList");
deleteDialogVisible.value = false deleteDialogVisible.value = false;
} }
} };
// 关闭弹窗的方法 // 关闭弹窗的方法
const close = () => { const close = () => {
deleteDialogVisible.value = false deleteDialogVisible.value = false;
} };
// 监听父组件传过来的值 // 监听父组件传过来的值
watch(() => props.dialogVisible, watch(
() => props.dialogVisible,
(newVal) => { (newVal) => {
deleteDialogVisible.value = newVal deleteDialogVisible.value = newVal;
} }
) );
// 监听组件内的值并向父组件更新 // 监听组件内的值并向父组件更新
watch(() => deleteDialogVisible.value, watch(
() => deleteDialogVisible.value,
(newVal) => { (newVal) => {
emit('update:dialogVisible', newVal) emit("update:dialogVisible", newVal);
} }
) );
// 监听父组件传过来的值 // 监听父组件传过来的值
watch(() => props.deleteMode, watch(
() => props.deleteMode,
(newVal) => { (newVal) => {
deleteMode.value = newVal deleteMode.value = newVal;
} }
) );
// 监听组件内的值并向父组件更新 // 监听组件内的值并向父组件更新
watch(() => deleteMode.value, watch(
() => deleteMode.value,
(newVal) => { (newVal) => {
emit('update:deleteMode', newVal) emit("update:deleteMode", newVal);
} }
) );
</script> </script>
<template> <template>
<div> <div>
<el-dialog v-model="exportDialogVisible" v-loading="fullscreenLoading" title="导出" width="500" center align-center <el-dialog v-model="exportDialogVisible" title="导出" width="500" center align-center @close="close" draggable>
@close="close" draggable> <div class="dialog-content" v-loading="fullscreenLoading">
<el-form class="formStyle" :rules="rules" ref="formRef" :model="exportObject"> <el-form class="formStyle" :rules="rules" ref="formRef" :model="exportObject">
<el-form-item prop="timeValue"> <el-form-item prop="timeValue">
<el-date-picker v-model="exportObject.timeValue" type="datetimerange" format="YYYY-MM-DD HH:mm:ss" <el-date-picker
start-placeholder="开始时间" end-placeholder="结束时间" date-format="YYYY/MM/DD ddd" time-format="A hh:mm:ss" v-model="exportObject.timeValue"
value-format="YYYY-MM-DD HH:mm:ss" id="export-time-picker"/> 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"
/>
</el-form-item> </el-form-item>
<el-form-item prop="spiderType"> <el-form-item prop="spiderType">
<el-checkbox-group v-model="exportObject.spiderType" class="checkboxStyle"> <el-checkbox-group v-model="exportObject.spiderType" class="checkboxStyle">
<el-checkbox label="DSN数据" value="dsn" id="dsn-checkbox"/> <el-checkbox label="DSN数据" value="dsn" id="dsn-checkbox" />
<el-checkbox label="ITU数据" value="itu" id="itu-checkbox"/> <el-checkbox label="ITU数据" value="itu" id="itu-checkbox" />
<el-checkbox label="ST数据" value="spaceTrack" id="st-checkbox"/> <el-checkbox label="ST数据" value="spaceTrack" id="st-checkbox" />
<el-checkbox label="ESA数据" value="esa" id="st-checkbox"/> <el-checkbox label="ESA数据" value="esa" id="esa-checkbox" />
</el-checkbox-group> </el-checkbox-group>
</el-form-item> </el-form-item>
</el-form> </el-form>
</div>
<template #footer> <template #footer>
<div class="dialog-footer"> <div class="dialog-footer">
<el-button @click="close">取消</el-button> <el-button @click="close">取消</el-button>
<el-button type="primary" @click="handleExport"> <el-button type="primary" @click="handleExport"> 确定 </el-button>
确定
</el-button>
</div> </div>
</template> </template>
</el-dialog> </el-dialog>
...@@ -31,64 +36,64 @@ ...@@ -31,64 +36,64 @@
</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 axios from "axios";
import { ElLoading, ElMessage } from 'element-plus' import { ElLoading, ElMessage } from "element-plus";
import type { FormInstance, FormRules } from 'element-plus' import type { FormInstance, FormRules } from "element-plus";
interface RuleForm { interface RuleForm {
//表单字段的类型 //表单字段的类型
timeValue: string timeValue: string;
spiderType: Array<string> spiderType: Array<string>;
} }
const formRef = ref<FormInstance>() const formRef = ref<FormInstance>();
const rules = ref<FormRules<RuleForm>>({ const rules = ref<FormRules<RuleForm>>({
timeValue: [ timeValue: [
{ {
// type: 'date', // type: 'date',
required: true, required: true,
message: '请选择时间段', message: "请选择时间段",
trigger: 'change', trigger: "change",
validator: (rule, value, callback) => { validator: (rule, value, callback) => {
// 检查是否为数组且长度为2 // 检查是否为数组且长度为2
if (!Array.isArray(value) || value.length !== 2) { if (!Array.isArray(value) || value.length !== 2) {
callback(new Error('请选择完整的时间段')); callback(new Error("请选择完整的时间段"));
} else { } else {
// 检查日期格式是否有效(可选) // 检查日期格式是否有效(可选)
const isValid = value.every(date => /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/.test(date)); const isValid = value.every((date) => /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/.test(date));
if (!isValid) { if (!isValid) {
callback(new Error('时间段格式不正确')); callback(new Error("时间段格式不正确"));
} else { } else {
callback(); callback();
} }
} }
} },
}, },
], ],
spiderType: [ spiderType: [
{ {
type: 'array', type: "array",
required: true, required: true,
message: '请至少选择一种数据类型', message: "请至少选择一种数据类型",
trigger: 'change', trigger: "change",
}, },
] ],
}) });
//表单绑定的字段 //表单绑定的字段
const exportObject = ref({ const exportObject = ref({
timeValue: [], timeValue: [],
spiderType: [] spiderType: [],
}) });
const fullscreenLoading = ref(false) const fullscreenLoading = ref(false);
const props = defineProps({ const props = defineProps({
dialogVisible: { dialogVisible: {
type: Boolean, type: Boolean,
default: false default: false,
} },
}) });
const emit = defineEmits(['update:dialogVisible']) const emit = defineEmits(["update:dialogVisible"]);
const exportDialogVisible = ref(props.dialogVisible) const exportDialogVisible = ref(props.dialogVisible);
// 导出方法 // 导出方法
// const handleExport = async () => { // const handleExport = async () => {
...@@ -145,7 +150,6 @@ const exportDialogVisible = ref(props.dialogVisible) ...@@ -145,7 +150,6 @@ const exportDialogVisible = ref(props.dialogVisible)
// } // }
// }) // })
// } // }
// const handleExport = async () => { // const handleExport = async () => {
...@@ -209,7 +213,7 @@ const handleExport = async () => { ...@@ -209,7 +213,7 @@ const handleExport = async () => {
// 1. 先在用户点击时获取保存文件句柄(关键:在用户手势中执行) // 1. 先在用户点击时获取保存文件句柄(关键:在用户手势中执行)
const fileHandle = await getSaveFileHandle(); const fileHandle = await getSaveFileHandle();
if (!fileHandle) { if (!fileHandle) {
ElMessage.info('已取消保存'); ElMessage.info("已取消保存");
return; return;
} }
...@@ -218,19 +222,19 @@ const handleExport = async () => { ...@@ -218,19 +222,19 @@ const handleExport = async () => {
if (valid) { if (valid) {
const loading = ElLoading.service({ const loading = ElLoading.service({
lock: true, lock: true,
text: '正在导出文件...', text: "正在导出文件...",
background: 'rgba(0, 0, 0, 0.7)', background: "rgba(0, 0, 0, 0.7)",
}); });
try { try {
// 3. 请求文件数据 // 3. 请求文件数据
const res = await axios.post( const res = await axios.post(
'/api/export/downloadFile', "/api/export/downloadFile",
{ times: exportObject.value.timeValue, filters: exportObject.value.spiderType }, { times: exportObject.value.timeValue, filters: exportObject.value.spiderType },
{ {
responseType: 'blob', responseType: "blob",
headers: { headers: {
'Token': localStorage.getItem('Admin-Token') || '', Token: localStorage.getItem("Admin-Token") || "",
}, },
} }
); );
...@@ -238,12 +242,12 @@ const handleExport = async () => { ...@@ -238,12 +242,12 @@ const handleExport = async () => {
// 4. 使用之前获取的句柄保存文件 // 4. 使用之前获取的句柄保存文件
await saveToSelectedLocation(res.data, fileHandle); await saveToSelectedLocation(res.data, fileHandle);
loading.close(); loading.close();
ElMessage.success('数据导出成功'); ElMessage.success("数据导出成功");
close() close();
} catch (e) { } catch (e) {
console.error('导出失败', e); console.error("导出失败", e);
loading.close(); loading.close();
ElMessage.error('ZIP文件导出失败,请重试'); ElMessage.error("ZIP文件导出失败,请重试");
} }
} }
}); });
...@@ -257,24 +261,26 @@ const handleExport = async () => { ...@@ -257,24 +261,26 @@ const handleExport = async () => {
const getSaveFileHandle = async () => { const getSaveFileHandle = async () => {
// 检查浏览器是否支持 // 检查浏览器是否支持
if (!(window as any).showSaveFilePicker) { if (!(window as any).showSaveFilePicker) {
ElMessage.warning('您的浏览器不支持选择保存位置,请使用最新版Chrome或Edge浏览器'); ElMessage.warning("您的浏览器不支持选择保存位置,请使用最新版Chrome或Edge浏览器");
return null; return null;
} }
try { try {
// 弹出保存对话框(必须在用户直接交互中调用) // 弹出保存对话框(必须在用户直接交互中调用)
return await (window as any).showSaveFilePicker({ return await (window as any).showSaveFilePicker({
suggestedName: 'export.zip', suggestedName: "export.zip",
types: [{ types: [
description: 'ZIP压缩文件', {
accept: { 'application/zip': ['.zip'] }, description: "ZIP压缩文件",
}], accept: { "application/zip": [".zip"] },
excludeAcceptAllOption: true },
],
excludeAcceptAllOption: true,
}); });
} catch (error: any) { } catch (error: any) {
// 用户取消选择时也会触发错误,这里视为正常取消 // 用户取消选择时也会触发错误,这里视为正常取消
if (error.name !== 'AbortError') { if (error.name !== "AbortError") {
console.error('获取保存位置失败', error); console.error("获取保存位置失败", error);
} }
return null; return null;
} }
...@@ -283,7 +289,7 @@ const getSaveFileHandle = async () => { ...@@ -283,7 +289,7 @@ const getSaveFileHandle = async () => {
// 保存数据到用户选择的位置 // 保存数据到用户选择的位置
const saveToSelectedLocation = async (blobData: any, fileHandle: any) => { const saveToSelectedLocation = async (blobData: any, fileHandle: any) => {
if (!(blobData instanceof Blob) || blobData.size === 0) { if (!(blobData instanceof Blob) || blobData.size === 0) {
throw new Error('无效的ZIP文件数据'); throw new Error("无效的ZIP文件数据");
} }
// 将blob转换为可写数据 // 将blob转换为可写数据
...@@ -299,26 +305,25 @@ const saveToSelectedLocation = async (blobData: any, fileHandle: any) => { ...@@ -299,26 +305,25 @@ const saveToSelectedLocation = async (blobData: any, fileHandle: any) => {
const close = () => { const close = () => {
exportObject.value = { exportObject.value = {
timeValue: [], timeValue: [],
spiderType: [] spiderType: [],
} };
formRef.value?.clearValidate() formRef.value?.clearValidate();
exportDialogVisible.value = false exportDialogVisible.value = false;
} };
// 监听父组件传过来的值 // 监听父组件传过来的值
watch(() => props.dialogVisible, watch(
() => props.dialogVisible,
(newVal) => { (newVal) => {
exportDialogVisible.value = newVal exportDialogVisible.value = newVal;
} }
) );
// 监听组件内的值并向父组件更新 // 监听组件内的值并向父组件更新
watch(() => exportDialogVisible.value, watch(
() => exportDialogVisible.value,
(newVal) => { (newVal) => {
emit('update:dialogVisible', newVal) emit("update:dialogVisible", newVal);
} }
) );
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
......
<template>
<div style="margin-bottom: 0" class="menu-title">
<div class="title">{{ props.title }}</div>
<div class="low-titme">{{ props.subtitle }}</div>
</div>
</template>
<script setup lang="ts">
const props = defineProps<{
title?: string;
subtitle?: string;
}>();
</script>
<style scoped>
.title {
text-align: left;
font-size: 18px;
font-weight: 500;
color: white;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
}
.low-titme {
color: rgba(255, 255, 255, 0.8);
text-align: left;
margin-left: 20px;
text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
}
.menu-title {
padding: 20px 0 10px 20px;
user-select: none;
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.15);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1), inset 0 0 10px rgba(255, 255, 255, 0.05);
}
</style>
<template>
<div class="box">
<div class="left"></div>
<div class="content">
<slot></slot>
</div>
<div class="right"></div>
</div>
</template>
<script setup lang="ts"></script>
<style scoped>
.box {
position: relative;
}
.content {
width: 95%;
height: 80px;
margin: auto;
border: 1.5px solid #0270b6;
border-top-left-radius: 20px;
border-bottom-left-radius: 20px;
border-bottom-right-radius: 20px;
border-top-right-radius: 20px;
border-left: none;
border-right: none;
background-image: linear-gradient(
120deg,
rgba(7, 47, 70, 0.6) 0%,
/* 起始色:80% 透明度 */ rgba(6, 20, 37, 0.4) 100% /* 结束色:60% 透明度 */
);
}
.left {
width: 50px;
height: 40px;
background-color: white;
position: absolute;
top: 50%;
transform: translateY(-50%);
background-image: linear-gradient(to right, #00a8ff, #003366);
}
.right {
width: 50px;
height: 40px;
background-color: white;
position: absolute;
top: 50%;
right: 0;
transform: translateY(-50%);
background-image: linear-gradient(to left, #00a8ff, #003366);
}
</style>
<script setup lang="ts"> <script setup lang="ts">
import { computed } from 'vue' import { computed } from "vue";
import { useRoute } from 'vue-router' import { useRoute } from "vue-router";
const route = useRoute() const route = useRoute();
const key = computed(() => route.path) const key = computed(() => route.path);
</script> </script>
<template> <template>
...@@ -28,7 +28,7 @@ const key = computed(() => route.path) ...@@ -28,7 +28,7 @@ const key = computed(() => route.path)
overflow-y: auto; /* 修改为auto允许垂直滚动 */ overflow-y: auto; /* 修改为auto允许垂直滚动 */
overflow-x: hidden; /* 防止水平滚动 */ overflow-x: hidden; /* 防止水平滚动 */
} }
.fixed-header+.app-main { .fixed-header + .app-main {
padding-top: 50px; padding-top: 50px;
} }
</style> </style>
<script setup lang="ts"> <script setup lang="ts">
import { computed } from 'vue' import { computed } from "vue";
import { useRouter } from 'vue-router' import { useRouter } from "vue-router";
import { useUserStore } from '@/store/user' import { useUserStore } from "@/store/user";
import { useAppStore } from '@/store/app' import { useAppStore } from "@/store/app";
import { storeToRefs } from 'pinia' import { storeToRefs } from "pinia";
import Breadcrumb from '@/components/Breadcrumb/index.vue' import Breadcrumb from "@/components/Breadcrumb/index.vue";
import Hamburger from '@/components/Hamburger/index.vue' import Hamburger from "@/components/Hamburger/index.vue";
const router = useRouter() const router = useRouter();
const userStore = useUserStore() const userStore = useUserStore();
const appStore = useAppStore() const appStore = useAppStore();
const { sidebar } = storeToRefs(appStore) const { sidebar } = storeToRefs(appStore);
const avatar = computed(() => userStore.avatar || 'user-avatar.gif') const avatar = computed(() => userStore.avatar || "user-avatar.gif");
const toggleSideBar = () => { const toggleSideBar = () => {
appStore.toggleSidebar() appStore.toggleSidebar();
} };
const logout = async () => { const logout = async () => {
await userStore.logout() await userStore.logout();
router.push(`/login?redirect=${router.currentRoute.value.fullPath}`) router.push(`/login?redirect=${router.currentRoute.value.fullPath}`);
} };
</script> </script>
<template> <template>
...@@ -30,18 +30,16 @@ const logout = async () => { ...@@ -30,18 +30,16 @@ const logout = async () => {
<div class="right-menu"> <div class="right-menu">
<el-dropdown class="avatar-container" trigger="click"> <el-dropdown class="avatar-container" trigger="click">
<div class="avatar-wrapper"> <div class="avatar-wrapper">
<img :src="avatar" class="user-avatar"> <img :src="avatar" class="user-avatar" />
<el-icon><CaretBottom /></el-icon> <el-icon><CaretBottom /></el-icon>
</div> </div>
<template #dropdown> <template #dropdown>
<el-dropdown-menu class="user-dropdown"> <el-dropdown-menu class="user-dropdown">
<router-link to="/"> <router-link to="/">
<el-dropdown-item> <el-dropdown-item> 首页 </el-dropdown-item>
首页
</el-dropdown-item>
</router-link> </router-link>
<el-dropdown-item divided @click="logout"> <el-dropdown-item divided @click="logout">
<span style="display:block;">退出</span> <span style="display: block">退出</span>
</el-dropdown-item> </el-dropdown-item>
</el-dropdown-menu> </el-dropdown-menu>
</template> </template>
...@@ -56,18 +54,18 @@ const logout = async () => { ...@@ -56,18 +54,18 @@ const logout = async () => {
overflow: hidden; overflow: hidden;
position: relative; position: relative;
background: #fff; background: #fff;
box-shadow: 0 1px 4px rgba(0,21,41,.08); box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
.hamburger-container { .hamburger-container {
line-height: 46px; line-height: 46px;
height: 100%; height: 100%;
float: left; float: left;
cursor: pointer; cursor: pointer;
transition: background .3s; transition: background 0.3s;
-webkit-tap-highlight-color:transparent; -webkit-tap-highlight-color: transparent;
&:hover { &:hover {
background: rgba(0, 0, 0, .025) background: rgba(0, 0, 0, 0.025);
} }
} }
...@@ -94,10 +92,10 @@ const logout = async () => { ...@@ -94,10 +92,10 @@ const logout = async () => {
&.hover-effect { &.hover-effect {
cursor: pointer; cursor: pointer;
transition: background .3s; transition: background 0.3s;
&:hover { &:hover {
background: rgba(0, 0, 0, .025) background: rgba(0, 0, 0, 0.025);
} }
} }
} }
......
<script setup lang="ts"> <script setup lang="ts">
import settings from '@/settings' import settings from "@/settings";
const props = defineProps({ const props = defineProps({
collapse: { collapse: {
type: Boolean, type: Boolean,
required: true required: true,
} },
}) });
const title = settings.title || 'fk spider web' const title = settings.title || "fk spider web";
// Logo 图片可在后续使用实际的 logo 文件 // Logo 图片可在后续使用实际的 logo 文件
// const logo = require('@/assets/logo.png') // const logo = require('@/assets/logo.png')
const logo = '' // 暂时为空 const logo = ""; // 暂时为空
</script> </script>
<template> <template>
<div class="sidebar-logo-container" :class="{ collapse: collapse }">
<div class="sidebar-logo-container" :class="{'collapse': collapse}">
<transition name="sidebarLogoFade"> <transition name="sidebarLogoFade">
<router-link v-if="collapse" key="collapse" class="sidebar-logo-link" to="/"> <router-link v-if="collapse" key="collapse" class="sidebar-logo-link" to="/">
<img v-if="logo" :src="logo" class="sidebar-logo" alt="logo"> <img v-if="logo" :src="logo" class="sidebar-logo" alt="logo" />
<h1 v-else class="sidebar-title">{{ title }}</h1> <h1 v-else class="sidebar-title">{{ title }}</h1>
</router-link> </router-link>
<router-link v-else key="expand" class="sidebar-logo-link" to="/"> <router-link v-else key="expand" class="sidebar-logo-link" to="/">
<img v-if="logo" :src="logo" class="sidebar-logo" alt="logo"> <img v-if="logo" :src="logo" class="sidebar-logo" alt="logo" />
<h1 class="sidebar-title">{{ title }}</h1> <h1 class="sidebar-title">{{ title }}</h1>
</router-link> </router-link>
</transition> </transition>
...@@ -55,6 +53,7 @@ const logo = '' // 暂时为空 ...@@ -55,6 +53,7 @@ const logo = '' // 暂时为空
& .sidebar-title { & .sidebar-title {
display: inline-block; display: inline-block;
letter-spacing: 2px;
margin: 0; margin: 0;
color: #fff; color: #fff;
font-weight: 600; font-weight: 600;
......
import { createApp } from 'vue' import { createApp } from "vue";
import 'virtual:uno.css' import "virtual:uno.css";
import App from './App.vue' import App from "./App.vue";
import router from './router' import router from "./router";
import pinia from './store' import pinia from "./store";
import ElementPlus from 'element-plus' import ElementPlus from "element-plus";
import 'element-plus/dist/index.css' import "element-plus/dist/index.css";
import { import {
UserFilled, UserFilled,
Lock, Lock,
...@@ -31,46 +31,47 @@ import { ...@@ -31,46 +31,47 @@ import {
Message, Message,
DataAnalysis, DataAnalysis,
Notebook, Notebook,
Setting Setting,
} from '@element-plus/icons-vue' } from "@element-plus/icons-vue";
import 'normalize.css/normalize.css' import "normalize.css/normalize.css";
import './styles/index.scss' import "./styles/index.scss";
import './style.css' import "./style.css";
import './permission' // 权限控制 import "./permission"; // 权限控制
import "animate.css";
const app = createApp(App) const app = createApp(App);
// 注册 Element Plus 图标 // 注册 Element Plus 图标
app.component('Monitor', Monitor) app.component("Monitor", Monitor);
app.component('Document', Document) app.component("Document", Document);
app.component('List', List) app.component("List", List);
app.component('Key', Key) app.component("Key", Key);
app.component('House', House) app.component("House", House);
app.component('Clock', Clock) app.component("Clock", Clock);
app.component('Message', Message) app.component("Message", Message);
app.component('DataAnalysis', DataAnalysis) app.component("DataAnalysis", DataAnalysis);
app.component('Notebook', Notebook) app.component("Notebook", Notebook);
app.component('Setting', Setting) app.component("Setting", Setting);
app.component('UserFilled', UserFilled) app.component("UserFilled", UserFilled);
app.component('Lock', Lock) app.component("Lock", Lock);
app.component('View', View) app.component("View", View);
app.component('Hide', Hide) app.component("Hide", Hide);
app.component('CaretBottom', CaretBottom) app.component("CaretBottom", CaretBottom);
app.component('HomeFilled', HomeFilled) app.component("HomeFilled", HomeFilled);
app.component('Menu', Menu) app.component("Menu", Menu);
app.component('Tickets', Tickets) app.component("Tickets", Tickets);
app.component('Folder', Folder) app.component("Folder", Folder);
app.component('Files', Files) app.component("Files", Files);
app.component('Loading', Loading) app.component("Loading", Loading);
app.component('VideoPlay', VideoPlay) app.component("VideoPlay", VideoPlay);
app.component('Refresh', Refresh) app.component("Refresh", Refresh);
app.component('Back', Back) app.component("Back", Back);
app.component('Upload', Upload) app.component("Upload", Upload);
app.component('Plus', Plus) app.component("Plus", Plus);
app.use(router) app.use(router);
app.use(pinia) app.use(pinia);
app.use(ElementPlus, { size: 'default' }) app.use(ElementPlus, { size: "default" });
app.mount('#app') app.mount("#app");
interface ISettings { interface ISettings {
title: string title: string;
fixedHeader: boolean fixedHeader: boolean;
sidebarLogo: boolean sidebarLogo: boolean;
} }
const settings: ISettings = { const settings: ISettings = {
title: 'Spider Admin Pro', title: "互联网信息采集",
fixedHeader: true, fixedHeader: true,
sidebarLogo: true sidebarLogo: true,
} };
export default settings export default settings;
import { defineStore } from 'pinia' import { defineStore } from "pinia";
import { useSlideStateStore } from "./slideState";
interface AppState { interface AppState {
sidebar: { sidebar: {
opened: boolean opened: boolean;
withoutAnimation: boolean withoutAnimation: boolean;
}, };
device: 'desktop' | 'mobile' device: "desktop" | "mobile";
} }
export const useAppStore = defineStore('app', { export const useAppStore = defineStore("app", {
state: (): AppState => ({ state: (): AppState => ({
sidebar: { sidebar: {
opened: localStorage.getItem('sidebarStatus') opened: localStorage.getItem("sidebarStatus")
? !!+localStorage.getItem('sidebarStatus')! // 将获取到的值转换为布尔值 其中 + 为先转为数字 !! 为转成 bool 值 ? !!+localStorage.getItem("sidebarStatus")! // 将获取到的值转换为布尔值 其中 + 为先转为数字 !! 为转成 bool 值
: true, : true,
withoutAnimation: false withoutAnimation: false,
}, },
device: 'desktop', device: "desktop",
}), }),
actions: { actions: {
toggleSidebar() { toggleSidebar() {
this.sidebar.opened = !this.sidebar.opened const slideStateStore = useSlideStateStore();
this.sidebar.withoutAnimation = false this.sidebar.opened = !this.sidebar.opened;
this.sidebar.withoutAnimation = false;
if (this.sidebar.opened) { if (this.sidebar.opened) {
localStorage.setItem('sidebarStatus', '1') localStorage.setItem("sidebarStatus", "1");
} else { } else {
localStorage.setItem('sidebarStatus', '0') localStorage.setItem("sidebarStatus", "0");
} }
slideStateStore.setSlideState(this.sidebar.opened ? "1" : "0");
}, },
closeSideBar(options: { withoutAnimation: boolean }) { closeSideBar(options: { withoutAnimation: boolean }) {
this.sidebar.opened = false this.sidebar.opened = false;
this.sidebar.withoutAnimation = options.withoutAnimation this.sidebar.withoutAnimation = options.withoutAnimation;
localStorage.setItem('sidebarStatus', '0') localStorage.setItem("sidebarStatus", "0");
const slideStateStore = useSlideStateStore();
slideStateStore.setSlideState("0");
}, },
toggleDevice(device: 'desktop' | 'mobile') { toggleDevice(device: "desktop" | "mobile") {
this.device = device this.device = device;
} },
} },
}) });
import { defineStore } from "pinia";
import { ref } from "vue";
export const useSlideStateStore = defineStore("slideState", () => {
const slideState = ref<boolean>(localStorage.getItem("sidebarStatus") == "1");
function getSlideState(): boolean {
return slideState.value;
}
function setSlideState(state: any) {
slideState.value = state == "1" ? true : false;
}
return {
slideState,
getSlideState,
setSlideState,
};
});
...@@ -38,7 +38,6 @@ ...@@ -38,7 +38,6 @@
--el-table-text-color: #FFFFFF; --el-table-text-color: #FFFFFF;
} }
// 分段选择器的样式 // 分段选择器的样式
.custom-style .el-segmented { .custom-style .el-segmented {
// 选中选项的背景颜色 // 选中选项的背景颜色
...@@ -97,3 +96,31 @@ ...@@ -97,3 +96,31 @@
.el-date-editor .el-range-input{ .el-date-editor .el-range-input{
color: #ffffff; color: #ffffff;
} }
.el-loading-spinner .path {
stroke: white !important;
/* 替换为你需要的颜色(如 #304156 或 rgb(48, 65, 86)) */
}
/* 卡片外层容器透明 */
.el-card {
background-color: transparent !important;
border: none;
/* 可选:去除边框 */
}
/* 卡片内容区域透明 */
.el-card__body {
background-color: transparent !important;
padding: 16px;
/* 保持默认内边距,可按需调整 */
}
.el-popover {
background-color: #1D5484 !important;
color: white !important;
}
.el-popper__arrow::before{
background-color: #1D5484 !important;
}
\ No newline at end of file
...@@ -58,3 +58,7 @@ body { ...@@ -58,3 +58,7 @@ body {
display: table; display: table;
clear: both; clear: both;
} }
:root {
--animate-duration: 0.15s !important;
}
\ No newline at end of file
const getCurTime = () => {
const now = new Date();
const year = now.getFullYear();
const month = now.getMonth() + 1;
const date = now.getDate();
const weekdays = ["周日", "周一", "周二", "周三", "周四", "周五", "周六"];
const weekday = weekdays[now.getDay()];
const hours = String(now.getHours()).padStart(2, "0");
const minutes = String(now.getMinutes()).padStart(2, "0");
const seconds = String(now.getSeconds()).padStart(2, "0");
return `${year}${month}${date}${weekday} ${hours}${minutes}${seconds}`;
};
export default getCurTime;
\ No newline at end of file
const formatExactLargeNum = (num: number | string | null) => {
if (num === null || num === undefined) return "";
if (typeof num !== "number" && typeof num !== "string") return num;
const parsedNum = typeof num === "string" ? Number(num.replace(/,/g, "")) : num;
if (isNaN(parsedNum)) return num;
if (Math.abs(parsedNum) > 100000000) {
return parsedNum.toExponential(2);
} else {
return parsedNum.toLocaleString("en-US", {
useGrouping: true,
maximumFractionDigits: 0,
minimumFractionDigits: 0,
});
}
};
export default formatExactLargeNum;
import axios from 'axios' import axios from "axios";
import { ElMessage } from 'element-plus' import { ElMessage } from "element-plus";
import { getToken, removeToken } from '@/utils/auth' import { getToken, removeToken } from "@/utils/auth";
// 创建axios实例 // 创建axios实例
const instance = axios.create({ 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/zip; charset=utf-8' "Content-Type": "application/zip; charset=utf-8",
} },
}) });
// 请求拦截器 // 请求拦截器
instance.interceptors.request.use( instance.interceptors.request.use(
(config) => { (config) => {
const token = getToken() const token = getToken();
if (token) { if (token) {
// 保持与原项目相同的Token头设置 // 保持与原项目相同的Token头设置
config.headers['Token'] = token config.headers["Token"] = token;
} }
return config return config;
}, },
(err) => { (err) => {
console.log('请求出错') console.log("请求出错");
return Promise.reject(err) return Promise.reject(err);
} }
) );
// 响应拦截器 // 响应拦截器
instance.interceptors.response.use( instance.interceptors.response.use(
(res) => { (res) => {
let result = res.data let result = res.data;
if (typeof res.data === 'string') { if (typeof res.data === "string") {
try { try {
result = JSON.parse(res.data) result = JSON.parse(res.data);
} catch (e) { } catch (e) {
// 不是JSON格式,保持原样 // 不是JSON格式,保持原样
} }
} else { } else {
// 4000 token无效或者过期 // 4000 token无效或者过期
if (result.code === 4000) { if (result.code === 4000) {
removeToken() removeToken();
window.location.reload() window.location.reload();
} }
if (result.code === 0) { if (result.code === 0) {
result.ok = true result.ok = true;
} else { } else {
result.ok = false result.ok = false;
ElMessage.error(result.msg || '请求失败') ElMessage.error(result.msg || "请求失败");
} }
} }
return result return result;
}, },
(err) => { (err) => {
ElMessage.error('网络请求出错, 请检查网络') ElMessage.error("网络请求出错, 请检查网络");
return Promise.reject(err) return Promise.reject(err);
} }
) );
// HTTP 请求方法常量 // HTTP 请求方法常量
export const GET = 'get' export const GET = "get";
export const POST = 'post' export const POST = "post";
export const PUT = 'put' export const PUT = "put";
export const DELETE = 'delete' export const DELETE = "delete";
// 请求函数类型 // 请求函数类型
export interface RequestConfig { export interface RequestConfig {
url: string; url: string;
method: 'get' | 'post' | 'put' | 'delete'; method: "get" | "post" | "put" | "delete";
params?: any; params?: any;
data?: any; data?: any;
headers?: Record<string, string>; headers?: Record<string, string>;
} }
export interface ApiResponse<T = any> { export interface ApiResponse<T = any> {
code: number code: number;
data: T data: T;
message: string message: string;
} }
export interface QueryParams { export interface QueryParams {
page?: number page?: number;
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;
status?: any status?: any;
job?:any job?: any;
} }
// 请求函数 // 请求函数
export const request = (config: RequestConfig) => { export const request = (config: RequestConfig) => {
return instance(config); return instance(config);
} };
export interface UserQueryParams { export interface UserQueryParams {
page?: number page?: number;
size?: number size?: number;
username?: string username?: string;
nickname?: string nickname?: string;
password?: string password?: string;
role?: number role?: number;
status?: any status?: any;
id?: number id?: number;
ids?: any ids?: any;
scrapydServerId?: string scrapydServerId?: string;
project?: string project?: string;
spiders?: any spiders?: any;
times?: any times?: any;
job_id?: string job_id?: string;
scrapyd_server_id?: string scrapyd_server_id?: string;
schedule_type?: string schedule_type?: string;
spider?: string spider?: string;
cron?: string cron?: string;
options?: string options?: string;
sat_name?: string sat_name?: string;
ntc_id?: string ntc_id?: string;
filters?: any filters?: any;
norad_cat_id?: string norad_cat_id?: string;
object_name?: string object_name?: string;
name?:string name?: string;
spacecraft?:string spacecraft?: string;
station?:string station?: string;
interval?: number[];
} }
...@@ -14,8 +14,15 @@ ...@@ -14,8 +14,15 @@
<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="开始时间" end-placeholder="结束时间" <el-date-picker
format="YYYY-MM-DD HH:mm:ss" date-format="YYYY/MM/DD ddd" time-format="A hh:mm:ss" /> v-model="timeValue"
type="datetimerange"
start-placeholder="开始时间"
end-placeholder="结束时间"
format="YYYY-MM-DD HH:mm:ss"
date-format="YYYY/MM/DD ddd"
time-format="A 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>
...@@ -23,7 +30,6 @@ ...@@ -23,7 +30,6 @@
<el-form-item> <el-form-item>
<el-input placeholder="请输入" style="width: 220px" /> <el-input placeholder="请输入" style="width: 220px" />
</el-form-item> </el-form-item>
<br></br>
<el-form-item> <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>
...@@ -56,8 +62,14 @@ ...@@ -56,8 +62,14 @@
</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: '45px' }" <el-table
:header-cell-style="{ textAlign: 'center' }" :cell-style="{ textAlign: 'center' }"> :data="tableData"
style="width: 100%"
border
:row-style="{ height: '45px' }"
: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 />
...@@ -71,23 +83,23 @@ ...@@ -71,23 +83,23 @@
<el-table-column property="isSync" label="是否为同步" show-overflow-tooltip /> <el-table-column property="isSync" 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)"> 详情 </el-button>
详情
</el-button>
</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
@pagination="getData" /> :total="pageObj.total"
v-model:page="pageObj.pageNo"
v-model:limit="pageObj.pageSize"
@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="765px" align-center @close="handleClose" draggable>
<template #header> <template #header>
<div class="text-center font-size-8"> <div class="text-center font-size-8">详情</div>
详情
</div>
</template> </template>
<el-form label-width="155px" size="small" class="px-4"> <el-form label-width="155px" size="small" class="px-4">
<div> <div>
...@@ -356,160 +368,158 @@ ...@@ -356,160 +368,158 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue' import { 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";
const timeValue = ref('') const timeValue = ref("");
const detailVisibleValue = ref(false) const detailVisibleValue = ref(false);
const showDeleteDialog = ref(false) const showDeleteDialog = ref(false);
// SNS Notice ID // SNS Notice ID
const snsId = ref('') const snsId = ref("");
// 主管部门 // 主管部门
const department = ref('') const department = ref("");
// 目标名称 // 目标名称
const targetName = ref('') const targetName = ref("");
// 计划类型 // 计划类型
const planType = ref('') const planType = ref("");
// 是否为同步 // 是否为同步
const syncType = ref('') const syncType = ref("");
// 同步位置 // 同步位置
const syncPosition = ref('') const syncPosition = ref("");
// 近地点 // 近地点
const perigee = ref('') const perigee = ref("");
// 远地点 // 远地点
const apogee = ref('') const apogee = ref("");
// 最低海拔 // 最低海拔
const lowestAltitude = ref('') const lowestAltitude = ref("");
// 参考主体 // 参考主体
const referenceSubject = ref('') const referenceSubject = ref("");
// 最低频率 // 最低频率
const lowestFrequency = ref('') const lowestFrequency = ref("");
// 最高频率 // 最高频率
const highestFrequency = ref('') const highestFrequency = ref("");
// 状态 // 状态
const status = ref('') const status = ref("");
// 有效期 // 有效期
const validityPeriod = ref('') const validityPeriod = ref("");
// 最早使用日期 // 最早使用日期
const earliestUsageDate = ref('') const earliestUsageDate = ref("");
// 是否确认使用 // 是否确认使用
const isUsed = ref('') const isUsed = ref("");
// 是否暂停使用 // 是否暂停使用
const isPauseUsed = ref('') const isPauseUsed = ref("");
// 记录时间 // 记录时间
const recordTime = ref('') const recordTime = ref("");
// 是否在国际频率列表中 // 是否在国际频率列表中
const IsInTheInternationalFrequencyList = ref('') const IsInTheInternationalFrequencyList = ref("");
// 最早监管日期 // 最早监管日期
const earliestRegulatoryDate = ref('') const earliestRegulatoryDate = ref("");
// 是否恢复使用 // 是否恢复使用
const isRestoreUsed = ref('') const isRestoreUsed = ref("");
// 最新相关BF IFIC发布日期 // 最新相关BF IFIC发布日期
const BFIFICdate = ref('') const BFIFICdate = ref("");
// 往返光时 // 往返光时
const roundTripTime = ref('') const roundTripTime = ref("");
// 距离 // 距离
const distance = ref('') const distance = ref("");
// 目标方位 // 目标方位
const targetAzimuth = ref('') const targetAzimuth = ref("");
// 目标俯仰 // 目标俯仰
const targetElevation = ref('') const targetElevation = ref("");
// 上行信号来源 // 上行信号来源
const upstreamSignalSource = ref('') const upstreamSignalSource = ref("");
// 上行发射功率 // 上行发射功率
const upstreamLaunchPower = ref('') const upstreamLaunchPower = ref("");
// 上行信号频段 // 上行信号频段
const upstreamFrequencyBand = ref('') const upstreamFrequencyBand = ref("");
// 风速 // 风速
const windSpeed = ref('') const windSpeed = ref("");
// 下行信号来源 // 下行信号来源
const downstreamSignalSource = ref('') const downstreamSignalSource = ref("");
// 下行信号频段 // 下行信号频段
const downstreamFrequencyBand = ref('') const downstreamFrequencyBand = ref("");
// 下行接收功率 // 下行接收功率
const downstreamReceptionPower = ref('') const downstreamReceptionPower = ref("");
// 类型 // 类型
const type = ref('') const type = ref("");
// INTLDES // INTLDES
const intldes = ref('') const intldes = ref("");
// 失效时间 // 失效时间
const expirationTime = ref('') const expirationTime = ref("");
// 周期 // 周期
const period = ref('') const period = ref("");
// INCL // INCL
const incl = ref('') const incl = ref("");
// RCS // RCS
const rcs = ref('') const rcs = ref("");
// SITE // SITE
const site = ref('') const site = ref("");
// TLE // TLE
const tle = ref('') const tle = ref("");
const tableData = ref([ const tableData = ref([
{ {
number: '1', number: "1",
targetName: 'VOYAGER-2', targetName: "VOYAGER-2",
stationPosition: '[148.98,-35.22]', stationPosition: "[148.98,-35.22]",
stationName: 'CANBERRA', stationName: "CANBERRA",
NoradCatId: '2134', NoradCatId: "2134",
SNSNoticeId: '90504649', SNSNoticeId: "90504649",
dataTime: '2025-06-24 14:35:23', dataTime: "2025-06-24 14:35:23",
recordTime: '2025-06-25 14:35:23', recordTime: "2025-06-25 14:35:23",
tle: '-', tle: "-",
isSync: '是', isSync: "是",
}, },
{ {
name: '1', name: "1",
}, },
{ {
name: '1', name: "1",
}, },
{ {
name: '1', name: "1",
}, },
{ {
name: '1', name: "1",
}, },
{ {
name: '1', 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) => { const handleDetails = (row: any) => {
console.log(row); console.log(row);
detailVisibleValue.value = true detailVisibleValue.value = true;
} };
const getData = () => { const getData = () => {
console.log('getData'); console.log("getData");
} };
const handleClose = () => { const handleClose = () => {
detailVisibleValue.value = false detailVisibleValue.value = false;
} };
const handleExportConfirm = () => {
} const handleExportConfirm = () => {};
const handleExport = () => { const handleExport = () => {
showDeleteDialog.value = true showDeleteDialog.value = true;
} };
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
...@@ -537,7 +547,7 @@ const handleExport = () => { ...@@ -537,7 +547,7 @@ const handleExport = () => {
/* 文字样式 */ /* 文字样式 */
.el-text { .el-text {
color: #FFFFFF; color: #ffffff;
} }
</style> </style>
...@@ -572,7 +582,7 @@ const handleExport = () => { ...@@ -572,7 +582,7 @@ const handleExport = () => {
/* 修改el下拉框选项的字体颜色 */ /* 修改el下拉框选项的字体颜色 */
.el-select-dropdown__item { .el-select-dropdown__item {
color: #FFFFFF; color: #ffffff;
} }
/* 修改el下拉框选项选中时的文字颜色 */ /* 修改el下拉框选项选中时的文字颜色 */
...@@ -582,7 +592,7 @@ const handleExport = () => { ...@@ -582,7 +592,7 @@ const handleExport = () => {
/* 修改el日期选择器的边框为none */ /* 修改el日期选择器的边框为none */
.el-date-editor { .el-date-editor {
--el-input-border-color: none --el-input-border-color: none;
} }
/* 去除按钮点击后的黑边框 */ /* 去除按钮点击后的黑边框 */
......
<template> <template>
<div> <div>
<MenuTitle title="数据展示" subtitle="Data Display" />
<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="segment-content">
<div class="custom-style flex gap-4"> <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> <el-button type="primary" @click="handleExport">导出</el-button>
...@@ -11,71 +12,62 @@ ...@@ -11,71 +12,62 @@
<!-- <allDataTab v-if="mode === '综合数据'"> <!-- <allDataTab v-if="mode === '综合数据'">
</allDataTab> --> </allDataTab> -->
<!-- DSN数据页面组件 --> <!-- DSN数据页面组件 -->
<dsnDataTab v-if="mode === 'DSN数据'"> <dsnDataTab v-if="mode === 'DSN数据'"></dsnDataTab>
</dsnDataTab>
<!-- ITU数据页面组件 --> <!-- ITU数据页面组件 -->
<ituDataTab v-if="mode === 'ITU数据'"> <ituDataTab v-if="mode === 'ITU数据'"></ituDataTab>
</ituDataTab>
<!-- ST数据页面组件 --> <!-- ST数据页面组件 -->
<stDataTab v-if="mode === 'ST数据'"> <stDataTab v-if="mode === 'ST数据'"></stDataTab>
</stDataTab>
<!-- ESA数据页面组件 --> <!-- ESA数据页面组件 -->
<esDataTab v-if="mode === 'ESA数据'"> <esDataTab v-if="mode === 'ESA数据'"></esDataTab>
</esDataTab>
</div>
<exportDialog v-model:dialogVisible="showDeleteDialog" @confirm="handleExportConfirm" /> <exportDialog v-model:dialogVisible="showDeleteDialog" @confirm="handleExportConfirm" />
</div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted } from 'vue' import { ref, onMounted, watch } from "vue";
import { useRoute } from 'vue-router' import { useRoute } from "vue-router";
import { useRouter } from 'vue-router' import { useRouter } from "vue-router";
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 esDataTab from './components/esDataTab.vue' import exportDialog from "@/components/Export/index.vue";
import exportDialog from '@/components/Export/index.vue' import type { UploadInstance } from "element-plus";
import { genFileId } from 'element-plus' import MenuTitle from "@/components/MenuTitle.vue";
import type { UploadInstance, UploadProps, UploadRawFile } from 'element-plus'
const mode = ref('DSN数据') const mode = ref(sessionStorage.getItem("dataDisplayMode") || "DSN数据");
const showDeleteDialog = ref(false) const showDeleteDialog = ref(false);
const sizeOptions = ['DSN数据', 'ITU数据', 'ST数据', 'ESA数据'] 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 = () => { const handleExport = () => {
showDeleteDialog.value = true showDeleteDialog.value = true;
} };
const handleExportConfirm = () => { const handleExportConfirm = () => {};
}
onMounted(() => { onMounted(() => {
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) });
})
watch(
mode,
(newVal) => {
sessionStorage.setItem("dataDisplayMode", newVal);
modeValue.value = sessionStorage.getItem("dataDisplayMode") || "DSN数据";
},
{
immediate: true,
}
);
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
...@@ -83,9 +75,25 @@ onMounted(() => { ...@@ -83,9 +75,25 @@ onMounted(() => {
outline: none; outline: none;
} }
.title {
text-align: left;
font-size: 18px;
font-weight: 500;
color: white;
}
.low-titme {
color: #ccc;
text-align: left;
margin-left: 20px;
}
.divider {
margin-top: 15px;
}
/* 文字样式 */ /* 文字样式 */
.el-text { .el-text {
color: #FFFFFF; color: #ffffff;
} }
.backStyle { .backStyle {
...@@ -97,4 +105,8 @@ onMounted(() => { ...@@ -97,4 +105,8 @@ onMounted(() => {
display: flex; display: flex;
margin-left: 0.8%; margin-left: 0.8%;
} }
.segment-content {
padding: 10px 10px 0 10px;
}
</style> </style>
<template>
<div class="No-Item" :class="{ selected: isSelected }" @click="selectNo">
<!-- 新增选中标识,增强视觉反馈 -->
<span class="selected-icon" v-if="isSelected"></span>
{{ props.No }}
</div>
</template>
<script setup lang="ts">
import { ref, watch } from "vue";
const props = defineProps<{
No: string | number;
resetSelected?: boolean;
}>();
const isSelected = ref(false);
const emit = defineEmits<{
(e: "addNo", sateNo: string | number): void;
(e: "delNo", sateNo: string | number): void;
}>();
const selectNo = () => {
isSelected.value = !isSelected.value;
isSelected.value ? emit("addNo", props.No) : emit("delNo", props.No);
};
watch(
() => props.resetSelected,
(newVal) => {
if (newVal) isSelected.value = false;
},
{ immediate: true }
);
</script>
<style scoped>
.No-Item {
width: 100% !important;
letter-spacing: 6px;
background-color: #e8f4f8;
font-size: 16px;
font-weight: 600;
color: #192a3e;
height: 32px;
border-radius: 8px;
display: flex;
align-items: center;
padding: 0 16px;
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
cursor: pointer;
box-sizing: border-box;
user-select: none;
border: 1px solid transparent;
}
.No-Item:not(.selected):hover {
background-color: #f0f8fb;
border-color: #4299e1;
}
.selected {
background-color: #4299e1;
color: #ffffff;
box-shadow: 0 4px 12px rgba(66, 153, 225, 0.3);
border-color: #2563eb;
}
.selected-icon {
margin-right: 8px;
font-size: 14px;
font-weight: bold;
color: #ffffff;
}
</style>
<template> <template>
<div class="flex gap-10"> <div class="flex">
<span class="textStyle">数据统计</span> <span class="textStyle" :class="{ toRight0: slideState }">数据统计</span>
<div class="dataCard" @click="goToAllDataPage"> <div class="dataCard" @click="goToAllDataPage" style="cursor: pointer">
<div class="titleStyle"> <div class="titleStyle">
<span>数据统计</span> <span>数据统计</span>
</div> </div>
<div class="items">
<div class="wordStyle"> <div class="wordStyle">
<span>总数据量: {{ totalDataNumber }}</span> <span>总数据量:</span>
<span class="total-num" v-if="totalDataNumber != null">{{ formatExactLargeNum(totalDataNumber) }}</span>
<span class="loading" v-else></span>
</div> </div>
<div class="wordStyle"> <div class="wordStyle">
<span>采集的页面数量: {{ totalPageNumber }}</span> <span>采集的页面数量:</span>
<span class="total-num" v-if="totalPageNumber != null"> {{ formatExactLargeNum(totalPageNumber) }}</span>
<span class="loading" v-else></span>
</div> </div>
<div class="wordStyle"> <div class="wordStyle">
<span>采集的目标数量: {{ totalTargetNumber }}</span> <span>采集的目标数量:</span>
<span class="total-num" v-if="totalTargetNumber != null"> {{ formatExactLargeNum(totalTargetNumber) }}</span>
<span class="loading" v-else></span>
</div> </div>
</div> </div>
<div class="dataCard" @click="goToTaskRecordPage"> </div>
<div class="dataCard" @click="goToTaskRecordPage" style="cursor: pointer">
<div class="titleStyle"> <div class="titleStyle">
<span>任务执行统计</span> <span>任务执行统计</span>
</div> </div>
<div class="items">
<div class="wordStyle"> <div class="wordStyle">
<span>任务执行成功统计: {{ successTask }}</span> <span>任务执行成功统计:</span>
<span class="total-num" v-if="successTask != null">{{ formatExactLargeNum(successTask) }}</span>
<span class="loading" v-else></span>
</div> </div>
<div class="wordStyle"> <div class="wordStyle">
<span>任务执行失败统计: {{ failTask }}</span> <span>任务执行失败统计:</span>
<span class="total-num" v-if="failTask != null">{{ formatExactLargeNum(failTask) }}</span>
<span class="loading" v-else></span>
</div> </div>
<div class="wordStyle"> <div class="wordStyle">
<span>任务异常数统计: {{ unusualTask }}</span> <span>任务异常数统计:</span>
<span class="total-num" v-if="unusualTask != null">{{ formatExactLargeNum(unusualTask) }}</span>
<span class="loading" v-else></span>
</div>
</div> </div>
</div> </div>
<div class="dataCard"> <div class="dataCard">
<div class="titleStyle"> <div class="titleStyle">
<span>性能统计</span> <span>性能统计</span>
</div> </div>
<div class="items">
<div class="wordStyle"> <div class="wordStyle">
<span>平均成功率: {{ speed }}</span> <span>平均成功率:</span>
<span class="total-num" v-if="speed != null"> {{ speed }}</span>
<span class="loading" v-else></span>
</div> </div>
<div class="wordStyle"> <div class="wordStyle">
<span>平均错误率: {{ errorRate }}</span> <span>平均错误率: </span>
<span class="total-num" v-if="errorRate != null">{{ errorRate }}</span>
<span class="loading" v-else></span>
</div> </div>
<div class="wordStyle"> <div class="wordStyle">
<span>平均异常率: {{ unusualRate }}</span> <span>平均异常率:</span>
<span class="total-num" v-if="unusualRate != null">{{ unusualRate }}</span>
<span class="loading" v-else></span>
</div>
</div> </div>
</div> </div>
</div> </div>
</template> </template>
<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 { getStatsDataList, getSpiderTaskList, getPerformanceList } from "@/api/staticData";
import { onMounted, ref } from 'vue'; import { onMounted, ref } from "vue";
import formatExactLargeNum from "@/utils/formatExactLargeNum";
const router = useRouter() import { useSlideStateStore } from "@/store/slideState";
const totalDataNumber = ref('') import { storeToRefs } from "pinia";
const totalPageNumber = ref('')
const totalTargetNumber = ref('') const slideStateStore = useSlideStateStore();
const successTask = ref('') const { slideState } = storeToRefs(slideStateStore);
const failTask = ref('')
const unusualTask = ref('') const router = useRouter();
const speed = ref('') const totalDataNumber = ref(null);
const errorRate = ref('') const totalPageNumber = ref(null);
const unusualRate = ref('') const totalTargetNumber = ref(null);
const successTask = ref(null);
const failTask = ref(null);
const unusualTask = ref(null);
const speed = ref(null);
const errorRate = ref(null);
const unusualRate = ref(null);
const goToAllDataPage = () => { const goToAllDataPage = () => {
router.push({ router.push({
path: '/osDataDisplay/list', path: "/osDataDisplay/list",
query: { query: {
jump: 'yes', jump: "yes",
mode: 'DSN数据' mode: "DSN数据",
} },
}) });
} };
const goToTaskRecordPage = () => { const goToTaskRecordPage = () => {
router.push({ router.push({
path: '/osTaskRecord/list', path: "/osTaskRecord/list",
query: { query: {
jump: 'yes', jump: "yes",
mode: '状态监控' mode: "状态监控",
} },
}) });
} };
// 获取数据统计数据的方法 // 获取数据统计数据的方法
const getData = async () => { const getData = async () => {
const staticData = await getStatsDataList({}) const staticData = await getStatsDataList({});
const spiderTask = await getSpiderTaskList({}) const spiderTask = await getSpiderTaskList({});
const performance = await getPerformanceList({}) const performance = await getPerformanceList({});
totalDataNumber.value = staticData.data.totalData totalDataNumber.value = staticData.data.totalData;
totalPageNumber.value = staticData.data.ituPage totalPageNumber.value = staticData.data.ituPage;
totalTargetNumber.value = staticData.data.spaceTrackItemCount totalTargetNumber.value = staticData.data.spaceTrackItemCount;
successTask.value = spiderTask.data.successCount successTask.value = spiderTask.data.successCount;
failTask.value = spiderTask.data.failCount failTask.value = spiderTask.data.failCount;
unusualTask.value = spiderTask.data.exceptionCount unusualTask.value = spiderTask.data.exceptionCount;
speed.value = performance.data.success speed.value = performance.data.success;
errorRate.value = performance.data.error errorRate.value = performance.data.error;
unusualRate.value = performance.data.exception unusualRate.value = performance.data.exception;
} };
//sidebarStatus
onMounted(() => { onMounted(() => {
getData(); getData();
}) });
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
...@@ -111,29 +142,95 @@ onMounted(() => { ...@@ -111,29 +142,95 @@ onMounted(() => {
background-repeat: no-repeat; background-repeat: no-repeat;
// background: #c6ebfc; // background: #c6ebfc;
// border: 1.5px solid rgb(193, 188, 188); // border: 1.5px solid rgb(193, 188, 188);
width: 360px; width: 500px;
height: 100%;
border-radius: 5px; border-radius: 5px;
} }
/* 标题区 */
.titleStyle { .titleStyle {
font-size: 30px; color: #dff6ff;
margin-top: 5%; font-size: 18px;
color: #FFFFFF; font-weight: 600;
text-align: center;
text-shadow: 0 0 8px rgba(100, 200, 255, 0.8);
letter-spacing: 2px;
margin-top: 15px;
} }
.wordStyle { .wordStyle {
font-size: 20px; font-size: 18px;
color: #FFFFFF; color: #ffffff;
margin-top: 2%; margin-top: 1%;
display: flex;
margin-left: 15px;
display: flex;
justify-content: space-between;
} }
.textStyle { .textStyle {
writing-mode: vertical-lr; writing-mode: vertical-lr;
font-size: 26px; font-size: 22px;
letter-spacing: 3px; letter-spacing: 3px;
margin-left: 0.5%; color: #ffffff;
color: #FFFFFF; font-weight: 500;
// display: flex; transform: translateX(-30px);
transition: 0.2s;
}
.total-num {
color: #4edaff;
font-size: 16px;
font-weight: 600;
text-shadow: 0 0 10px rgba(0, 200, 255, 0.5);
letter-spacing: 1px;
margin-right: 30px;
animation: visible 0.6s;
}
@keyframes visible {
0% {
opacity: 0;
}
50% {
opacity: 0.5;
}
100% {
opacity: 1;
}
}
.toRight0 {
transform: translateX(-10px);
}
.items {
display: flex;
flex-direction: column;
padding-left: 15px;
padding-top: 20px;
}
.loading {
width: 40px;
aspect-ratio: 2;
margin-right: 30px;
--_g: no-repeat radial-gradient(circle closest-side, #4edaff 90%, #0000);
background: var(--_g) 0% 50%, var(--_g) 50% 50%, var(--_g) 100% 50%;
background-size: calc(100% / 3) 50%;
animation: l3 1s infinite linear;
}
@keyframes l3 {
20% {
background-position: 0% 0%, 50% 50%, 100% 50%;
}
40% {
background-position: 0% 100%, 50% 0%, 100% 50%;
}
60% {
background-position: 0% 50%, 50% 100%, 100% 0%;
}
80% {
background-position: 0% 50%, 50% 50%, 100% 100%;
}
} }
</style> </style>
<template> <template>
<div class="flex gap-10"> <div class="flex">
<span class="textStyle">QB数据管理</span> <span class="textStyle" :class="{ toRight0: !slideState }">数据管理</span>
<!-- <div class="dataCard" @click="goToAllDataPage">
<div class="titleStyle">
<span>综合数据</span>
</div>
<div class="iconStyle" />
</div> -->
<div class="dataCard" @click="goToDSNDataPage"> <div class="dataCard" @click="goToDSNDataPage">
<div class="titleStyle"> <div class="titleStyle">
<span>DSN数据</span> <span>DSN数据</span>
...@@ -25,92 +19,112 @@ ...@@ -25,92 +19,112 @@
</div> </div>
<div class="iconStyle" /> <div class="iconStyle" />
</div> </div>
<div class="dataCard" @click="goToESADataPage">
<div class="titleStyle">
<span>ESA数据</span>
</div>
<div class="iconStyle" />
</div>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { useRouter } from 'vue-router' import { useRouter } from "vue-router";
const router = useRouter() import { storeToRefs } from "pinia";
import { useSlideStateStore } from "@/store/slideState";
const slideStateStore = useSlideStateStore();
const { slideState } = storeToRefs(slideStateStore);
const router = useRouter();
defineProps({ defineProps({
totalDataNumber: { totalDataNumber: {
type: String, type: String,
default: '' default: "",
}, },
totalPageNumber: { totalPageNumber: {
type: String, type: String,
default: '' default: "",
}, },
totalTargetNumber: { totalTargetNumber: {
type: String, type: String,
default: '' default: "",
}, },
successTask: { successTask: {
type: String, type: String,
default: '' default: "",
}, },
failTask: { failTask: {
type: String, type: String,
default: '' default: "",
}, },
unusualTask: { unusualTask: {
type: String, type: String,
default: '' default: "",
}, },
speed: { speed: {
type: String, type: String,
default: '' default: "",
}, },
errorRate: { errorRate: {
type: String, type: String,
default: '' default: "",
} },
}) });
const goToAllDataPage = () => { const goToAllDataPage = () => {
router.push({ router.push({
path: '/osDataDisplay/list', path: "/osDataDisplay/list",
query: { query: {
mode: '综合数据', mode: "综合数据",
jump: 'yes' jump: "yes",
} },
}) });
} };
const goToDSNDataPage = () => { const goToDSNDataPage = () => {
router.push({ router.push({
path: '/osDataDisplay/list', path: "/osDataDisplay/list",
query: { query: {
mode: 'DSN数据', mode: "DSN数据",
jump: 'yes' jump: "yes",
} },
}) });
} };
const goToITUDataPage = () => { const goToITUDataPage = () => {
router.push({ router.push({
path: '/osDataDisplay/list', path: "/osDataDisplay/list",
query: { query: {
mode: 'ITU数据', mode: "ITU数据",
jump: 'yes' jump: "yes",
} },
}) });
} };
const goToSTDataPage = () => { const goToSTDataPage = () => {
router.push({ router.push({
path: '/osDataDisplay/list', path: "/osDataDisplay/list",
query: { query: {
mode: 'ST数据', mode: "ST数据",
jump: 'yes' jump: "yes",
} },
}) });
} };
const goToESADataPage = () => {
router.push({
path: "/osDataDisplay/list",
query: {
mode: "ESA数据",
jump: "yes",
},
});
};
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.iconStyle { .iconStyle {
background-image: url("@/assets/picture/wenjianjia.png"); background-image: url("@/assets/picture/data-icon.svg");
background-size: 100% 120%; background-size: 100% 100%;
background-repeat: no-repeat; background-repeat: no-repeat;
margin-top: -5%; margin-top: 5%;
display: flex; display: flex;
// border: 1.5px solid rgb(193, 188, 188); // border: 1.5px solid rgb(193, 188, 188);
height: 65%; height: 65%;
...@@ -118,12 +132,13 @@ const goToSTDataPage = () => { ...@@ -118,12 +132,13 @@ const goToSTDataPage = () => {
} }
.dataCard { .dataCard {
cursor: pointer;
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;
// background: #c6ebfc; // background: #c6ebfc;
// border: 1.5px solid rgb(193, 188, 188); // border: 1.5px solid rgb(193, 188, 188);
width: 360px; width: 375px;
height: 100%; height: 100%;
border-radius: 5px; border-radius: 5px;
display: flex; display: flex;
...@@ -133,23 +148,32 @@ const goToSTDataPage = () => { ...@@ -133,23 +148,32 @@ const goToSTDataPage = () => {
} }
.titleStyle { .titleStyle {
font-size: 30px; color: #dff6ff;
margin-top: 5%; font-size: 18px;
color: #FFFFFF; font-weight: 600;
text-align: center;
text-shadow: 0 0 8px rgba(100, 200, 255, 0.8);
letter-spacing: 2px;
margin-top: 15px;
} }
.wordStyle { .wordStyle {
font-size: 20px; font-size: 20px;
color: #FFFFFF; color: #ffffff;
margin-top: 2%; margin-top: 2%;
} }
.textStyle { .textStyle {
writing-mode: vertical-lr; writing-mode: vertical-lr;
font-size: 26px; font-size: 22px;
letter-spacing: 3px; letter-spacing: 3px;
margin-left: 0.5%; color: #ffffff;
color: #FFFFFF; font-weight: 500;
// display: flex; transform: translateX(-10px);
transition: 0.2s;
}
.toRight0 {
transform: translateX(-25px);
} }
</style> </style>
...@@ -18,28 +18,26 @@ ...@@ -18,28 +18,26 @@
</div> </div>
</div> </div>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import dataCard from './dataCard.vue' import dataCard from "./dataCard.vue";
import taskCard from '@/views/os-status/components/taskCard.vue'; import taskCard from "@/views/os-status/components/taskCard.vue";
import qbCard from '@/views/os-status/components/qbDataMonitor.vue'; import qbCard from "@/views/os-status/components/qbDataMonitor.vue";
import { onMounted } from 'vue'; import { onMounted } from "vue";
defineProps({ defineProps({
title: { title: {
type: String, type: String,
default: '' default: "",
}, },
desc: { desc: {
type: String, type: String,
default: '' default: "",
} },
}) });
onMounted(() => { onMounted(() => {});
})
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
...@@ -51,26 +49,24 @@ onMounted(() => { ...@@ -51,26 +49,24 @@ onMounted(() => {
align-items: center; align-items: center;
padding: 10px; padding: 10px;
gap: 30px; gap: 30px;
} }
.monitoringCard { .monitoringCard {
background-image: url("@/assets/picture/box1.png"); background-image: url("@/assets/picture/box1.png");
background-size: 100% 100%; background-size: 100% 100%;
background-repeat: no-repeat; background-repeat: no-repeat;
height: 22.5vh; height: 20vh;
width: 96%; width: 96%;
display: flex; display: flex;
gap: 21.5px; gap: 21.5px;
padding: 10px; padding: 10px;
} }
.taskCard { .taskCard {
background-image: url("@/assets/picture/box1.png"); background-image: url("@/assets/picture/box1.png");
background-size: 100% 100%; background-size: 100% 100%;
background-repeat: no-repeat; background-repeat: no-repeat;
height: 26.5vh; height: 25vh;
width: 96%; width: 96%;
display: flex; display: flex;
gap: 21.5px; gap: 21.5px;
...@@ -92,7 +88,7 @@ onMounted(() => { ...@@ -92,7 +88,7 @@ onMounted(() => {
font-size: 26px; font-size: 26px;
letter-spacing: 3px; letter-spacing: 3px;
margin-left: 2%; margin-left: 2%;
color: #FFFFFF; color: #ffffff;
// display: flex; // display: flex;
} }
...@@ -102,6 +98,5 @@ onMounted(() => { ...@@ -102,6 +98,5 @@ onMounted(() => {
justify-content: center; justify-content: center;
/* 水平居中 */ /* 水平居中 */
// gap: 20px; // gap: 20px;
} }
</style> </style>
<template> <template>
<div> <div>
<MenuTitle title="状态监控" subtitle="Status Monitor" />
<div> <div>
<div class="text-left timeStyle" /> <div class="text-left timeStyle" />
<div class="border-100"> <div class="border-100">
...@@ -7,11 +8,11 @@ ...@@ -7,11 +8,11 @@
</div> </div>
</div> </div>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import statusMonitor from './components/statusMonitor.vue' import statusMonitor from "./components/statusMonitor.vue";
import MenuTitle from "@/components/MenuTitle.vue";
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
...@@ -26,5 +27,18 @@ import statusMonitor from './components/statusMonitor.vue' ...@@ -26,5 +27,18 @@ import statusMonitor from './components/statusMonitor.vue'
border: 1.5px solid rgb(193, 188, 188); border: 1.5px solid rgb(193, 188, 188);
} }
.title {
text-align: left;
font-size: 18px;
font-weight: 500;
color: white;
}
.low-titme {
color: #ccc;
text-align: left;
margin-left: 20px;
}
.divider {
margin-top: 15px;
}
</style> </style>
<template> <template>
<div> <div class="sys-content">
<div class="text-left p-8"> <MenuTitle title="系统管理" subtitle="System Manager" />
<el-form inline> <el-card>
<el-form-item> <div class="btns">
<el-button test-element="userSystem-AddUser" type="primary" plain @click="openAddUserDialog">创建用户</el-button> <el-button test-element="userSystem-AddUser" type="primary" plain @click="openAddUserDialog"
</el-form-item> >创建用户</el-button
<el-form-item> >
<el-button type="danger" plain @click="handleBatchDelete">批量删除</el-button> <el-button type="danger" plain @click="handleBatchDelete">批量删除</el-button>
</el-form-item>
</el-form>
</div> </div>
<el-table :data="tableData" style="width: 100%" border :header-cell-style="{ textAlign: 'center' }" </el-card>
:cell-style="{ textAlign: 'center' }" :row-style="{ height: '58px' }" <div class="table-content">
@selection-change="handleSelectionChange"> <el-table
:data="tableData"
border
:header-cell-style="{ textAlign: 'center' }"
:cell-style="{ textAlign: 'center' }"
:row-style="{ height: '58px' }"
@selection-change="handleSelectionChange"
v-loading="tableData?.length == 0"
element-loading-background="rgba(48, 65, 86, 0.7)"
>
<el-table-column type="selection" width="40" /> <el-table-column type="selection" width="40" />
<el-table-column property="number" label="序号" width="55" type="index" /> <el-table-column property="number" label="序号" width="55" type="index" />
<el-table-column property="username" label="用户账号" show-overflow-tooltip /> <el-table-column property="username" label="用户账号" show-overflow-tooltip />
<el-table-column property="nickname" label="用户名称" show-overflow-tooltip /> <el-table-column property="nickname" label="用户名称" show-overflow-tooltip />
<el-table-column property="role" label="用户角色" show-overflow-tooltip>
<template #default="scope">
{{ scope.row.role == 1 ? "管理员" : "普通用户" }}
</template>
</el-table-column>
<el-table-column property="status" label="用户状态" show-overflow-tooltip>
<template #default="scope">
{{ scope.row.status ? "启用" : "停用" }}
</template>
</el-table-column>
<el-table-column property="create_time" label="创建时间" width="280" show-overflow-tooltip /> <el-table-column property="create_time" label="创建时间" width="280" show-overflow-tooltip />
<el-table-column label="操作" width="220"> <el-table-column label="操作" width="220">
<template #default="scope"> <template #default="scope">
<el-button type="primary" plain @click="handleEdit(scope.row)" id="editUser"> <el-button type="primary" plain @click="handleEdit(scope.row)" id="editUser"> 编辑 </el-button>
编辑 <el-button type="danger" plain @click="handleDelete(scope.row)"> 删除 </el-button>
</el-button>
<el-button type="danger" plain @click="handleDelete(scope.row)">
删除
</el-button>
</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
@pagination="getUserListData" /> :total="pageObj.total"
v-model:page="pageObj.pageNo"
v-model:limit="pageObj.pageSize"
@pagination="getUserListData"
/>
</div> </div>
</div>
<!-- 删除弹窗组件 --> <!-- 删除弹窗组件 -->
<deleteDialog v-model:dialogVisible="showDeleteDialog" @get-user-list="getUserListData" <deleteDialog
:ids="userIds" :deleteMode="deleteMode" :id="userId" /> v-model:dialogVisible="showDeleteDialog"
@get-user-list="getUserListData"
:ids="userIds"
:deleteMode="deleteMode"
:id="userId"
/>
<!-- 创建用户弹窗组件 --> <!-- 创建用户弹窗组件 -->
<addUserDialog v-model:dialogVisible="dialogVisible" :mode="mode" :nickName="nickName" :userName="userName" <addUserDialog
:id="userId" :userRole="userRole" :userStatus="userStatus" :userPassword="userPassword" v-model:dialogVisible="dialogVisible"
@get-user-list="getUserListData" /> :mode="mode"
:nickName="nickName"
:userName="userName"
:id="userId"
:userRole="userRole"
:userStatus="userStatus"
:userPassword="userPassword"
@get-user-list="getUserListData"
/>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted } from 'vue' 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 } 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' import { ElMessage } from "element-plus";
const userId = ref<any>([]) import MenuTitle from "@/components/MenuTitle.vue";
const userIds = ref<any>() const userId = ref<number>(-1);
const nickName = ref<string>('') const userIds = ref<number[]>([]);
const userName = ref<string>('') const nickName = ref<string>("");
const userRole = ref<number>(0) const userName = ref<string>("");
const userStatus = ref<boolean>(true) const userRole = ref<number>(0);
const userPassword = ref<string>('') const userStatus = ref<boolean>(true);
const showDeleteDialog = ref(false) const userPassword = ref<string>("");
const deleteMode = ref<DeleteMode>(DeleteMode.SINGLE_DELETE) const showDeleteDialog = ref(false);
const dialogVisible = ref<boolean>(false) const deleteMode = ref<DeleteMode>(DeleteMode.SINGLE_DELETE);
const mode = ref<AddMode>(AddMode.ADD_MODE) const dialogVisible = ref<boolean>(false);
const tableData = ref() const mode = ref<AddMode>(AddMode.ADD_MODE);
const tableData = ref([]);
const pageObj = ref({ const pageObj = ref({
total: 0, 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;
userName.value = row.username userName.value = row.username;
userRole.value = row.role userRole.value = row.role;
userStatus.value = row.status userStatus.value = row.status;
userPassword.value = row.password userPassword.value = row.password;
dialogVisible.value = true dialogVisible.value = true;
userId.value = row.id userId.value = row.id;
} };
// 弹窗关闭的方法 // 弹窗关闭的方法
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); };
}
// 批量删除用户的方法 // 批量删除用户的方法
const handleBatchDelete = async () => { const handleBatchDelete = async () => {
if(userIds.value == undefined){ if (!userIds.value || userIds.value.length === 0) {
ElMessage({ ElMessage({
message: '请先选择要删除的用户', message: "请先选择要删除的用户",
type: 'warning' type: "warning",
}) });
return return;
}else{ } 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;
} };
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: pageObj.value.pageNo, page: pageObj.value.pageNo,
size: pageObj.value.pageSize size: pageObj.value.pageSize,
}) });
tableData.value = userList.data.list tableData.value = userList.data.list;
pageObj.value.total = userList.data.total pageObj.value.total = userList.data.total;
console.log(userList); };
}
onMounted(async () => { onMounted(async () => {
getUserListData() getUserListData();
}) });
</script> </script>
<style scoped> <style scoped>
...@@ -134,6 +164,24 @@ onMounted(async () => { ...@@ -134,6 +164,24 @@ onMounted(async () => {
.el-button:focus { .el-button:focus {
outline: none; outline: none;
} }
.title {
text-align: left;
font-size: 18px;
font-weight: 500;
color: white;
}
.low-titme {
color: #ccc;
text-align: left;
margin-left: 20px;
}
.divider {
margin-top: 15px;
}
.btns {
display: flex;
justify-content: end;
}
</style> </style>
<style> <style>
...@@ -153,4 +201,13 @@ onMounted(async () => { ...@@ -153,4 +201,13 @@ onMounted(async () => {
background-color: #1d5484; background-color: #1d5484;
box-shadow: none; box-shadow: none;
} }
.table-content {
background: rgba(255, 255, 255, 0.03);
backdrop-filter: blur(8px);
border: 1px solid rgba(78, 237, 255, 0.2);
box-shadow: 0 0 15px rgba(78, 237, 255, 0.1);
border-radius: 8px;
padding: 10px !important;
}
</style> </style>
<template> <template>
<div> <div>
<MenuTitle title="任务信息" subtitle="Task Information" />
<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-8" /> <div class="m-t-8" />
<div> <div>
<taskCard :spiderType="spiderType" 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">
<Pagination :total="pageObj.total" v-model:page="pageObj.pageNo" v-model:limit="pageObj.pageSize"
@pagination="getData" />
</div> -->
</div> </div>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted } from 'vue' import { ref } from "vue";
import Pagination from '@/components/pagination/index.vue' import taskCard from "./components/taskCard.vue";
import taskCard from './components/taskCard.vue' import { useRouter } from "vue-router";
import { useRouter } from 'vue-router' import { useRoute } from "vue-router";
import { useRoute } from 'vue-router' import MenuTitle from "@/components/MenuTitle.vue";
import { getSpiderTaskList,getTaskData } from '@/api/spiderTask'
import { AddMode } from './components/enum' const route = useRoute();
const router = useRouter();
const route = useRoute() const spiderType = ref<any>(route.query.spiderType);
const router = useRouter()
const spiderType = ref<any>(route.query.spiderType)
const taskValue = ref('')
const taskList = ref([])
const taskOptions = [
{
value: 'task1',
label: 'sk网',
},
{
value: 'task2',
label: 'nasa网',
},
{
value: 'task3',
label: '网',
},
]
const pageObj = ref({
total: 10,
pageSize: 10,
pageNo: 1
})
const goToTaskInformation = () => { const goToTaskInformation = () => {
if(route.query.page === 'spiderManager'){ if (route.query.page === "spiderManager") {
router.push({ path: '/osSpiderManager/list' }) router.push({ path: "/osSpiderManager/list" });
}else{ } else {
router.push({ path: '/osStatus/list' }) router.push({ path: "/osStatus/list" });
} }
} };
onMounted(async () => {
// if(route.query.spiderType){
// spiderType.value = route.query.spiderType
// }
})
</script> </script>
<style scoped> <style scoped>
.title {
text-align: left;
font-size: 18px;
font-weight: 500;
color: white;
}
.low-titme {
color: #ccc;
text-align: left;
margin-left: 20px;
}
.divider {
margin-top: 15px;
}
/* 返回样式 */ /* 返回样式 */
.backStyle { .backStyle {
background-image: url("@/assets/picture/back.png"); background-image: url("@/assets/picture/back.png");
...@@ -85,7 +68,7 @@ onMounted(async () => { ...@@ -85,7 +68,7 @@ onMounted(async () => {
/* 文本样式 */ /* 文本样式 */
.el-text { .el-text {
color: #FFFFFF; color: #ffffff;
} }
/* 工具栏样式 */ /* 工具栏样式 */
...@@ -129,7 +112,7 @@ onMounted(async () => { ...@@ -129,7 +112,7 @@ onMounted(async () => {
/* 修改el下拉框选项的字体颜色 */ /* 修改el下拉框选项的字体颜色 */
.el-select-dropdown__item { .el-select-dropdown__item {
color: #FFFFFF; color: #ffffff;
} }
/* 修改el下拉框选项选中时的文字颜色 */ /* 修改el下拉框选项选中时的文字颜色 */
......
<template> <template>
<div> <div>
<MenuTitle title="任务执行记录" subtitle="Task Record" />
<div class="backStyle" v-if="route.query.jump === 'yes'" @click="goToStatus" /> <div class="backStyle" v-if="route.query.jump === 'yes'" @click="goToStatus" />
<div class="m-t-7" /> <div class="m-t-8" />
<div class="text-left p-6 toolbarStyle"> <table-search>
<div class="formStyle"> <div class="form-content">
<el-form inline> <div class="timer">
<el-form-item> <span style="color: white">时间:</span>
<el-text class="mx-1">时间:</el-text>
</el-form-item>
<el-form-item>
<el-config-provider :locale="zhCn"> <el-config-provider :locale="zhCn">
<el-date-picker v-model="timeValue" type="datetimerange" start-placeholder="开始时间" <el-date-picker
end-placeholder="结束时间" format="YYYY-MM-DD HH:mm:ss" date-format="YYYY/MM/DD ddd" v-model="timeValue"
time-format="A hh:mm:ss" value-format="YYYY-MM-DD HH:mm:ss" /> type="datetimerange"
start-placeholder="开始时间"
end-placeholder="结束时间"
format="YYYY-MM-DD HH:mm:ss"
date-format="YYYY/MM/DD ddd"
time-format="A hh:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss"
/>
</el-config-provider> </el-config-provider>
</el-form-item>
<el-form-item>
<el-space>
<el-button type="primary" @click="searchData">查询</el-button>
<el-button type="primary" @click="getData">重置表格</el-button>
</el-space>
</el-form-item>
</el-form>
</div> </div>
<div class="btns">
<el-button plain type="primary" @click="searchData">查询</el-button>
<el-button plain @click="getData">重置</el-button>
</div> </div>
<div class="m-t-10" /> </div>
</table-search>
<div class="table-content">
<div> <div>
<el-table :data="tableData" style="width: 100%" border :header-cell-style="{ textAlign: 'center' }" <el-table
:cell-style="{ textAlign: 'center' }"> v-loading="tableData.length == 0"
element-loading-background="rgba(48, 65, 86, 0.7)"
:data="tableData"
style="width: 100%"
border
:header-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="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>
<template #default="scope"> <template #default="scope">
{{ '已完成' }} {{ "已完成" }}
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="错误消息" show-overflow-tooltip> <el-table-column label="错误消息" show-overflow-tooltip>
<!-- 当错误消息为空时默认值为'-' --> <!-- 当错误消息为空时默认值为'-' -->
<template #default="scope"> <template #default="scope">
{{ 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>
<template #default="scope"> <template #default="scope">
{{ scope.row.run_status === "unknown" ? '已结束' : scope.row.run_status }} {{ scope.row.run_status === "unknown" ? "已结束" : scope.row.run_status }}
</template> </template>
</el-table-column> </el-table-column>
<el-table-column property="create_time" label="调度时间" width="200" show-overflow-tooltip /> <el-table-column 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">
<Pagination :total="pageObj.total" v-model:page="pageObj.pageNo" v-model:limit="pageObj.pageSize" <Pagination
@pagination="getData" v-model:pagerCount="pageObj.pagerCount" /> :total="pageObj.total"
v-model:page="pageObj.pageNo"
v-model:limit="pageObj.pageSize"
@pagination="getData"
v-model:pagerCount="pageObj.pagerCount"
/>
</div>
</div> </div>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted } from 'vue' import { ref, onMounted } from "vue";
import Pagination from '@/components/pagination/index.vue' 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' import { ElMessage } from "element-plus";
import MenuTitle from "@/components/MenuTitle.vue";
import TableSearch from "@/components/TableSearch.vue";
// ElConfigProvider 组件 // ElConfigProvider 组件
import { ElConfigProvider } from 'element-plus'; import { ElConfigProvider } from "element-plus";
// 引入中文包 // 引入中文包
import zhCn from 'element-plus/es/locale/lang/zh-cn'; import zhCn from "element-plus/es/locale/lang/zh-cn";
defineOptions({ name: 'Pagination' }) defineOptions({ name: "Pagination" });
// 更改分页文字 // 更改分页文字
zhCn.el.pagination.total = '共 `{total} 条`'; zhCn.el.pagination.total = "共 `{total} 条`";
zhCn.el.pagination.goto = '跳至'; zhCn.el.pagination.goto = "跳至";
zhCn.el.pagination.pagesize = '条/页'; zhCn.el.pagination.pagesize = "条/页";
zhCn.el.pagination.pageClassifier = '页'; zhCn.el.pagination.pageClassifier = "页";
const route = useRoute() const route = useRoute();
const router = useRouter() const router = useRouter();
const taskValue = ref('') const timeValue = ref("");
const timeValue = ref('')
const taskOptions = [ const taskOptions = [
{ {
value: 'task1', value: "task1",
label: 'sk网', label: "sk网",
}, },
{ {
value: 'task2', value: "task2",
label: 'nasa网', label: "nasa网",
}, },
{ {
value: 'task3', value: "task3",
label: '网', label: "网",
}, },
] ];
const tableData = ref([]) const tableData = ref([]);
const pageObj = ref({ const pageObj = ref({
total: 10, total: 10,
pageSize: 10, pageSize: 10,
pageNo: 1, pageNo: 1,
pagerCount: 5, pagerCount: 5,
}) });
// 前往运行日志,暂时不要 // 前往运行日志,暂时不要
// const handleDetails = (row: any) => { // const handleDetails = (row: any) => {
// console.log(row); // console.log(row);
...@@ -111,44 +126,52 @@ const pageObj = ref({ ...@@ -111,44 +126,52 @@ const pageObj = ref({
// }) // })
// } // }
const goToStatus = () => { const goToStatus = () => {
if (route.query.mode === '状态监控') { if (route.query.mode === "状态监控") {
router.push({ router.push({
path: '/osStatus/list', path: "/osStatus/list",
}) });
} else { } else {
router.push({ router.push({
path: '/osTaskInformation/list', path: "/osTaskInformation/list",
}) });
} }
} };
// 获取任务执行记录列表 // 获取任务执行记录列表
const getData = async () => { const getData = async () => {
const res = await getSpiderTaskRecord({ page: pageObj.value.pageNo, size: pageObj.value.pageSize, status: 'total' }) const res = await getSpiderTaskRecord({ page: pageObj.value.pageNo, size: pageObj.value.pageSize, status: "total" });
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) { if (!timeValue.value) {
ElMessage.warning('请先选择时间段') ElMessage.warning("请先选择时间段");
return 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({
pageObj.value.total = res.data.total page: pageObj.value.pageNo,
tableData.value = res.data.list size: pageObj.value.pageSize,
} status: "total",
times: resTime,
});
pageObj.value.total = res.data.total;
tableData.value = res.data.list;
};
onMounted(() => { onMounted(() => {
getData() getData();
}) });
</script> </script>
<style scoped> <style scoped>
:deep(.el-form-item__label) {
color: white;
}
/* 工具栏样式 */ /* 工具栏样式 */
.toolbarStyle { .toolbarStyle {
background-image: url("@/assets/picture/box3.png"); background-image: url("@/assets/picture/box3.png");
...@@ -158,14 +181,22 @@ onMounted(() => { ...@@ -158,14 +181,22 @@ onMounted(() => {
/* 表格样式 */ /* 表格样式 */
.formStyle { .formStyle {
display: flex;
justify-content: space-around;
padding: 3px; padding: 3px;
} }
/* 文字样式 */ /* 文字样式 */
.el-text { .el-text {
color: #FFFFFF; color: #ffffff;
}
.table-content {
background: rgba(255, 255, 255, 0.03);
backdrop-filter: blur(8px);
border: 1px solid rgba(78, 237, 255, 0.2);
box-shadow: 0 0 15px rgba(78, 237, 255, 0.1);
border-radius: 8px;
padding: 10px !important;
margin-top: 18px;
} }
.backStyle { .backStyle {
...@@ -177,6 +208,30 @@ onMounted(() => { ...@@ -177,6 +208,30 @@ onMounted(() => {
display: flex; display: flex;
margin-left: 0.8%; margin-left: 0.8%;
} }
.title {
text-align: left;
font-size: 18px;
font-weight: 500;
color: white;
}
.low-titme {
color: #ccc;
text-align: left;
margin-left: 20px;
}
.divider {
margin-top: 15px;
}
.form-content {
height: 80px;
justify-content: space-between;
display: flex;
align-items: center;
padding: 0 30px;
}
</style> </style>
<style> <style>
/* 修改el选择器的样式 */ /* 修改el选择器的样式 */
...@@ -203,7 +258,7 @@ onMounted(() => { ...@@ -203,7 +258,7 @@ onMounted(() => {
/* 修改el下拉框选项的字体颜色 */ /* 修改el下拉框选项的字体颜色 */
.el-select-dropdown__item { .el-select-dropdown__item {
color: #FFFFFF; color: #ffffff;
} }
/* 修改el下拉框选项选中时的文字颜色 */ /* 修改el下拉框选项选中时的文字颜色 */
...@@ -218,6 +273,6 @@ onMounted(() => { ...@@ -218,6 +273,6 @@ onMounted(() => {
/* 修改el日期选择器的边框为none */ /* 修改el日期选择器的边框为none */
.el-date-editor { .el-date-editor {
--el-input-border-color: none --el-input-border-color: none;
} }
</style> </style>
<template> <template>
<h1>system index</h1> <div>system index</div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts"></script>
import { ref } from 'vue';
</script> <style scoped></style>
...@@ -31,7 +31,7 @@ export default defineConfig({ ...@@ -31,7 +31,7 @@ export default defineConfig({
proxy: { proxy: {
// 代理API请求,使用更精确的路径匹配 // 代理API请求,使用更精确的路径匹配
'/api': { '/api': {
target: 'http://192.168.0.176:5001/', target: 'http://192.168.3.10: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