mirror of
https://github.com/whyour/qinglong.git
synced 2025-05-22 22:36:06 +08:00
定时任务增加关联订阅
This commit is contained in:
parent
e7d023a7e0
commit
2e27d4057e
|
@ -129,6 +129,7 @@
|
|||
"@types/uuid": "^8.3.4",
|
||||
"@umijs/max": "^4.0.72",
|
||||
"@umijs/ssr-darkreader": "^4.9.45",
|
||||
"ahooks": "^3.7.8",
|
||||
"ansi-to-react": "^6.1.6",
|
||||
"antd": "^4.24.8",
|
||||
"antd-img-crop": "^4.2.3",
|
||||
|
|
|
@ -219,6 +219,9 @@ devDependencies:
|
|||
'@umijs/ssr-darkreader':
|
||||
specifier: ^4.9.45
|
||||
version: 4.9.45
|
||||
ahooks:
|
||||
specifier: ^3.7.8
|
||||
version: 3.7.8(react@18.2.0)
|
||||
ansi-to-react:
|
||||
specifier: ^6.1.6
|
||||
version: 6.1.6(react-dom@18.2.0)(react@18.2.0)
|
||||
|
@ -4421,6 +4424,10 @@ packages:
|
|||
'@types/istanbul-lib-report': 3.0.0
|
||||
dev: true
|
||||
|
||||
/@types/js-cookie@2.2.7:
|
||||
resolution: {integrity: sha512-aLkWa0C0vO5b4Sr798E26QgOkss68Un0bLjs7u9qxzPT5CG+8DuNTffWES58YzJs3hrVAOs1wonycqEBqNJubA==}
|
||||
dev: true
|
||||
|
||||
/@types/js-yaml@4.0.5:
|
||||
resolution: {integrity: sha512-FhpRzf927MNQdRZP0J5DLIdTXhjLYzeUTmLAu69mnVksLH9CJY3IuSeEgbKUki7GQZm0WqDkGzyxju2EZGD2wA==}
|
||||
dev: true
|
||||
|
@ -5574,6 +5581,32 @@ packages:
|
|||
clean-stack: 2.2.0
|
||||
indent-string: 4.0.0
|
||||
|
||||
/ahooks-v3-count@1.0.0:
|
||||
resolution: {integrity: sha512-V7uUvAwnimu6eh/PED4mCDjE7tokeZQLKlxg9lCTMPhN+NjsSbtdacByVlR1oluXQzD3MOw55wylDmQo4+S9ZQ==}
|
||||
dev: true
|
||||
|
||||
/ahooks@3.7.8(react@18.2.0):
|
||||
resolution: {integrity: sha512-e/NMlQWoCjaUtncNFIZk3FG1ImSkV/JhScQSkTqnftakRwdfZWSw6zzoWSG9OMYqPNs2MguDYBUFFC6THelWXA==}
|
||||
engines: {node: '>=8.0.0'}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || 18
|
||||
peerDependenciesMeta:
|
||||
react:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@babel/runtime': 7.22.3
|
||||
'@types/js-cookie': 2.2.7
|
||||
ahooks-v3-count: 1.0.0
|
||||
dayjs: 1.11.8
|
||||
intersection-observer: 0.12.2
|
||||
js-cookie: 2.2.1
|
||||
lodash: 4.17.21
|
||||
react: 18.2.0
|
||||
resize-observer-polyfill: 1.5.1
|
||||
screenfull: 5.2.0
|
||||
tslib: 2.5.3
|
||||
dev: true
|
||||
|
||||
/ajv-formats@2.1.1(ajv@8.12.0):
|
||||
resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==}
|
||||
peerDependencies:
|
||||
|
@ -9101,6 +9134,10 @@ packages:
|
|||
side-channel: 1.0.4
|
||||
dev: true
|
||||
|
||||
/intersection-observer@0.12.2:
|
||||
resolution: {integrity: sha512-7m1vEcPCxXYI8HqnL8CKI6siDyD+eIWSwgB3DZA+ZTogxk9I4CDnj4wilt9x/+/QbHI4YG5YZNmC6458/e9Ktg==}
|
||||
dev: true
|
||||
|
||||
/intl-format-cache@4.3.1:
|
||||
resolution: {integrity: sha512-OEUYNA7D06agqPOYhbTkl0T8HA3QKSuwWh1HiClEnpd9vw7N+3XsQt5iZ0GUEchp5CW1fQk/tary+NsbF3yQ1Q==}
|
||||
dev: true
|
||||
|
@ -9578,6 +9615,10 @@ packages:
|
|||
'@sideway/pinpoint': 2.0.0
|
||||
dev: false
|
||||
|
||||
/js-cookie@2.2.1:
|
||||
resolution: {integrity: sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ==}
|
||||
dev: true
|
||||
|
||||
/js-sdsl@4.4.1:
|
||||
resolution: {integrity: sha512-6Gsx8R0RucyePbWqPssR8DyfuXmLBooYN5cZFZKjHGnQuaf7pEzhtpceagJxVu4LqhYY5EYA7nko3FmeHZ1KbA==}
|
||||
dev: true
|
||||
|
@ -13626,6 +13667,11 @@ packages:
|
|||
ajv-keywords: 5.1.0(ajv@8.12.0)
|
||||
dev: true
|
||||
|
||||
/screenfull@5.2.0:
|
||||
resolution: {integrity: sha512-9BakfsO2aUQN2K9Fdbj87RJIEZ82Q9IGim7FqM5OsebfoFC6ZHXgDq/KvniuLTPdeM8wY2o6Dj3WQ7KeQCj3cA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: true
|
||||
|
||||
/scroll-into-view-if-needed@2.2.31:
|
||||
resolution: {integrity: sha512-dGCXy99wZQivjmjIqihaBQNjryrz5rueJY7eHfTdyWEiR4ttYpsajb14rn9s5d4DY4EcY6+4+U/maARBXJedkA==}
|
||||
dependencies:
|
||||
|
|
22
src/components/name.tsx
Normal file
22
src/components/name.tsx
Normal file
|
@ -0,0 +1,22 @@
|
|||
import { useRequest } from 'ahooks';
|
||||
import { Service, Options } from 'ahooks/lib/useRequest/src/types';
|
||||
import { Spin, Typography } from 'antd';
|
||||
|
||||
export default function Name<
|
||||
TData extends { data?: { name: string } },
|
||||
TParams,
|
||||
>({
|
||||
service,
|
||||
options,
|
||||
}: {
|
||||
service: Service<TData, [TParams]>;
|
||||
options: Options<TData, [TParams]>;
|
||||
}) {
|
||||
const { loading, data } = useRequest(service, options);
|
||||
console.log(loading, data);
|
||||
return (
|
||||
<Spin spinning={loading}>
|
||||
<Typography.Text ellipsis={true}>{data?.data?.name}</Typography.Text>
|
||||
</Spin>
|
||||
);
|
||||
}
|
|
@ -1,404 +0,0 @@
|
|||
import React, {
|
||||
useRef,
|
||||
useEffect,
|
||||
useContext,
|
||||
createContext,
|
||||
useReducer,
|
||||
useState,
|
||||
useMemo,
|
||||
} from 'react';
|
||||
import { throttle, isNumber, debounce } from 'lodash';
|
||||
|
||||
const initialState = {
|
||||
// 行高度
|
||||
rowHeight: 0,
|
||||
// 当前的scrollTop
|
||||
curScrollTop: 0,
|
||||
// 总行数
|
||||
totalLen: 0,
|
||||
};
|
||||
|
||||
function reducer(state, action) {
|
||||
const { curScrollTop, totalLen, ifScrollTopClear, scrollTop } = action;
|
||||
|
||||
let stateScrollTop = state.curScrollTop;
|
||||
switch (action.type) {
|
||||
// 改变trs 即 改变渲染的列表trs
|
||||
case 'changeTrs':
|
||||
return {
|
||||
...state,
|
||||
curScrollTop,
|
||||
};
|
||||
// 更改totalLen
|
||||
case 'changeTotalLen':
|
||||
if (totalLen === 0) {
|
||||
stateScrollTop = 0;
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
totalLen,
|
||||
curScrollTop: stateScrollTop,
|
||||
};
|
||||
|
||||
case 'reset':
|
||||
return {
|
||||
...state,
|
||||
curScrollTop: ifScrollTopClear ? 0 : scrollTop ?? state.curScrollTop,
|
||||
};
|
||||
default:
|
||||
throw new Error();
|
||||
}
|
||||
}
|
||||
|
||||
// ==============全局常量 ================== //
|
||||
const DEFAULT_VID = 'vtable';
|
||||
const vidMap = new Map();
|
||||
let preData = 0;
|
||||
|
||||
// ===============context ============== //
|
||||
const ScrollContext = createContext({
|
||||
dispatch: undefined,
|
||||
renderLen: 1,
|
||||
start: 0,
|
||||
offsetStart: 0,
|
||||
// =============
|
||||
rowHeight: initialState.rowHeight,
|
||||
totalLen: 0,
|
||||
vid: DEFAULT_VID,
|
||||
});
|
||||
|
||||
// =============组件 =================== //
|
||||
|
||||
function VCell(props: any): JSX.Element {
|
||||
const { children, ...restProps } = props;
|
||||
|
||||
return (
|
||||
<td {...restProps}>
|
||||
<div>{children[1]}</div>
|
||||
</td>
|
||||
);
|
||||
}
|
||||
|
||||
function VRow(props: any, ref: any): JSX.Element {
|
||||
const { rowHeight } = useContext(ScrollContext);
|
||||
|
||||
const { children, style, ...restProps } = props;
|
||||
|
||||
const trRef = useRef<HTMLTableRowElement>(null);
|
||||
|
||||
return (
|
||||
<tr
|
||||
{...restProps}
|
||||
ref={Object.prototype.hasOwnProperty.call(ref, 'current') ? ref : trRef}
|
||||
style={{
|
||||
...style,
|
||||
height: rowHeight || 'auto',
|
||||
boxSizing: 'border-box',
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
function VWrapper(props: any): JSX.Element {
|
||||
const { children, ...restProps } = props;
|
||||
|
||||
const { renderLen, start, dispatch, vid, totalLen } =
|
||||
useContext(ScrollContext);
|
||||
|
||||
const contents = useMemo(() => {
|
||||
return children[1];
|
||||
}, [children]);
|
||||
|
||||
const contentsLen = useMemo(() => {
|
||||
return contents?.length ?? 0;
|
||||
}, [contents]);
|
||||
|
||||
useEffect(() => {
|
||||
if (totalLen !== contentsLen) {
|
||||
dispatch({
|
||||
type: 'changeTotalLen',
|
||||
totalLen: contentsLen ?? 0,
|
||||
});
|
||||
}
|
||||
}, [contentsLen, dispatch, vid, totalLen]);
|
||||
|
||||
let tempNode = null;
|
||||
if (Array.isArray(contents) && contents.length) {
|
||||
tempNode = [
|
||||
children[0],
|
||||
contents.slice(start, start + (renderLen ?? 1)).map((item) => {
|
||||
if (Array.isArray(item)) {
|
||||
// 兼容antd v4.3.5 --- rc-table 7.8.1及以下
|
||||
return item[0];
|
||||
}
|
||||
// 处理antd ^v4.4.0 --- rc-table ^7.8.2
|
||||
return item;
|
||||
}),
|
||||
];
|
||||
} else {
|
||||
tempNode = children;
|
||||
}
|
||||
|
||||
return <tbody {...restProps}>{tempNode}</tbody>;
|
||||
}
|
||||
|
||||
function VTable(props: any, otherParams): JSX.Element {
|
||||
const { style, children, ...rest } = props;
|
||||
const { width, ...rest_style } = style;
|
||||
|
||||
const { vid, scrollY, resetScrollTopWhenDataChange, rowHeight, scrollTop } =
|
||||
otherParams ?? {};
|
||||
|
||||
const [state, dispatch] = useReducer(reducer, {
|
||||
...initialState,
|
||||
curScrollTop: scrollTop,
|
||||
rowHeight,
|
||||
});
|
||||
|
||||
const wrap_tableRef = useRef<HTMLDivElement>(null);
|
||||
const tableRef = useRef<HTMLTableElement>(null);
|
||||
|
||||
const ifChangeRef = useRef(false);
|
||||
|
||||
// 数据的总条数
|
||||
const [totalLen, setTotalLen] = useState<number>(
|
||||
children[1]?.props?.data?.length ?? 0,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setTotalLen(state.totalLen);
|
||||
}, [state.totalLen]);
|
||||
|
||||
// 组件卸载的清除操作
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
vidMap.delete(vid);
|
||||
};
|
||||
}, [vid]);
|
||||
|
||||
// 数据变更
|
||||
useEffect(() => {
|
||||
ifChangeRef.current = true;
|
||||
// console.log('数据变更')
|
||||
if (isNumber(children[1]?.props?.data?.length)) {
|
||||
dispatch({
|
||||
type: 'changeTotalLen',
|
||||
totalLen: children[1]?.props?.data?.length ?? 0,
|
||||
});
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [children[1].props.data]);
|
||||
|
||||
// table总高度
|
||||
const tableHeight = useMemo<string | number>(() => {
|
||||
let temp: string | number = 'auto';
|
||||
|
||||
if (rowHeight && totalLen) {
|
||||
temp = rowHeight * totalLen;
|
||||
}
|
||||
return temp;
|
||||
}, [totalLen]);
|
||||
|
||||
// table的scrollY值
|
||||
const [tableScrollY, setTableScrollY] = useState(0);
|
||||
|
||||
// tableScrollY 随scrollY / tableHeight 进行变更
|
||||
useEffect(() => {
|
||||
let temp = 0;
|
||||
|
||||
if (typeof scrollY === 'string') {
|
||||
temp =
|
||||
(wrap_tableRef.current?.parentNode as HTMLElement)?.offsetHeight ?? 0;
|
||||
} else {
|
||||
temp = scrollY;
|
||||
}
|
||||
|
||||
// if (isNumber(tableHeight) && tableHeight < temp) {
|
||||
// temp = tableHeight;
|
||||
// }
|
||||
|
||||
// 处理tableScrollY <= 0的情况
|
||||
if (temp <= 0) {
|
||||
temp = 0;
|
||||
}
|
||||
|
||||
setTableScrollY(temp);
|
||||
}, [scrollY, tableHeight]);
|
||||
|
||||
// 渲染的条数
|
||||
const renderLen = useMemo<number>(() => {
|
||||
let temp = 1;
|
||||
if (rowHeight && totalLen && tableScrollY) {
|
||||
if (tableScrollY <= 0) {
|
||||
temp = 0;
|
||||
} else {
|
||||
const tempRenderLen = ((tableScrollY / rowHeight) | 0) + 10;
|
||||
// console.log('tempRenderLen', tempRenderLen)
|
||||
// temp = tempRenderLen > totalLen ? totalLen : tempRenderLen;
|
||||
temp = tempRenderLen;
|
||||
}
|
||||
}
|
||||
|
||||
return temp;
|
||||
}, [totalLen, tableScrollY]);
|
||||
|
||||
// 渲染中的第一条
|
||||
let start = rowHeight ? (state.curScrollTop / rowHeight) | 0 : 0;
|
||||
|
||||
start = start < 5 ? 0 : start - 5 + 1;
|
||||
|
||||
// 偏移量
|
||||
let offsetStart = state.curScrollTop % (rowHeight * 5);
|
||||
|
||||
if (start > 0) {
|
||||
offsetStart = offsetStart % rowHeight;
|
||||
}
|
||||
// console.log(offsetStart)
|
||||
// offsetStart= offsetStart%rowHeight
|
||||
// 用来优化向上滚动出现的空白
|
||||
if (state.curScrollTop && state.curScrollTop >= rowHeight * 5) {
|
||||
// start -= 1
|
||||
// if (offsetStart >= rowHeight) {
|
||||
// offsetStart +=
|
||||
// } else {
|
||||
offsetStart += rowHeight * 4;
|
||||
// }
|
||||
} else {
|
||||
start = 0;
|
||||
}
|
||||
|
||||
// console.log(state.curScrollTop, start, offsetStart)
|
||||
|
||||
// 数据变更 操作scrollTop
|
||||
useEffect(() => {
|
||||
const scrollNode = wrap_tableRef.current?.parentNode as HTMLElement;
|
||||
if (ifChangeRef?.current) {
|
||||
// console.log(scrollNode)
|
||||
ifChangeRef.current = false;
|
||||
|
||||
if (resetScrollTopWhenDataChange) {
|
||||
// 重置scrollTop
|
||||
if (scrollNode) {
|
||||
scrollNode.scrollTop = 0;
|
||||
}
|
||||
|
||||
dispatch({ type: 'reset', ifScrollTopClear: true });
|
||||
} else {
|
||||
// console.log(preData)
|
||||
// scrollNode.scrollTop = preData+53
|
||||
// 不重置scrollTop 不清空curScrollTop
|
||||
dispatch({ type: 'reset', ifScrollTopClear: false });
|
||||
}
|
||||
}
|
||||
|
||||
if (vidMap.has(vid)) {
|
||||
vidMap.set(vid, {
|
||||
...vidMap.get(vid),
|
||||
scrollNode,
|
||||
});
|
||||
}
|
||||
}, [totalLen, resetScrollTopWhenDataChange, vid, children]);
|
||||
|
||||
useEffect(() => {
|
||||
const throttleScroll = throttle((e) => {
|
||||
const scrollTop: number = e?.target?.scrollTop ?? 0;
|
||||
// const scrollHeight: number = e?.target?.scrollHeight ?? 0
|
||||
// const clientHeight: number = e?.target?.clientHeight ?? 0
|
||||
if (scrollTop) {
|
||||
preData = scrollTop;
|
||||
}
|
||||
// 到底了 没有滚动条就不会触发reachEnd. 建议设置scrolly高度少点或者数据量多点.
|
||||
// 若renderLen大于totalLen, 置空curScrollTop. => table paddingTop会置空.
|
||||
dispatch({
|
||||
type: 'changeTrs',
|
||||
curScrollTop: renderLen <= totalLen ? scrollTop : 0,
|
||||
});
|
||||
}, 60);
|
||||
|
||||
const ref = wrap_tableRef?.current?.parentNode as HTMLElement;
|
||||
|
||||
if (ref) {
|
||||
ref.addEventListener('scroll', throttleScroll, { passive: true });
|
||||
}
|
||||
|
||||
return () => {
|
||||
ref.removeEventListener('scroll', throttleScroll);
|
||||
};
|
||||
}, [renderLen, totalLen]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className="virtuallist"
|
||||
ref={wrap_tableRef}
|
||||
style={{
|
||||
width: '100%',
|
||||
position: 'relative',
|
||||
height: tableHeight,
|
||||
boxSizing: 'border-box',
|
||||
paddingTop: state.curScrollTop,
|
||||
}}
|
||||
>
|
||||
<ScrollContext.Provider
|
||||
value={{
|
||||
dispatch,
|
||||
start,
|
||||
offsetStart,
|
||||
renderLen,
|
||||
totalLen,
|
||||
vid,
|
||||
rowHeight,
|
||||
}}
|
||||
>
|
||||
<table
|
||||
{...rest}
|
||||
ref={tableRef}
|
||||
style={{
|
||||
...rest_style,
|
||||
width,
|
||||
position: 'relative',
|
||||
transform: `translateY(-${offsetStart}px)`,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</table>
|
||||
</ScrollContext.Provider>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// ================导出===================
|
||||
export function VList(props: {
|
||||
height: number;
|
||||
// 唯一标识
|
||||
vid?: string;
|
||||
rowHeight: number | string;
|
||||
reset?: boolean;
|
||||
scrollTop?: number | string;
|
||||
}): any {
|
||||
const { vid = DEFAULT_VID, rowHeight, height, reset, scrollTop } = props;
|
||||
|
||||
const resetScrollTopWhenDataChange = reset ?? true;
|
||||
|
||||
if (!vidMap.has(vid)) {
|
||||
vidMap.set(vid, { _id: vid });
|
||||
}
|
||||
|
||||
return {
|
||||
table: (p) =>
|
||||
VTable(p, {
|
||||
vid,
|
||||
scrollY: height,
|
||||
rowHeight,
|
||||
resetScrollTopWhenDataChange,
|
||||
scrollTop,
|
||||
}),
|
||||
body: {
|
||||
wrapper: VWrapper,
|
||||
row: VRow,
|
||||
cell: VCell,
|
||||
},
|
||||
};
|
||||
}
|
|
@ -420,3 +420,12 @@ body[data-mode='phone'] header {
|
|||
box-shadow: none;
|
||||
border-right: 1px solid #eee;
|
||||
}
|
||||
|
||||
.ant-table.ant-table-middle .ant-table-title,
|
||||
.ant-table.ant-table-middle .ant-table-footer,
|
||||
.ant-table.ant-table-middle .ant-table-thead > tr > th,
|
||||
.ant-table.ant-table-middle .ant-table-tbody > tr > td,
|
||||
.ant-table.ant-table-middle tfoot > tr > th,
|
||||
.ant-table.ant-table-middle tfoot > tr > td {
|
||||
padding: 12px 16px;
|
||||
}
|
||||
|
|
|
@ -392,5 +392,7 @@
|
|||
"个人": "Personal",
|
||||
"重新安装": "Reinstall",
|
||||
"强制删除": "Force Delete",
|
||||
"全部任务": "All Tasks"
|
||||
"全部任务": "All Tasks",
|
||||
"关联订阅": "Associate Subscription",
|
||||
"订阅": "Subscription"
|
||||
}
|
||||
|
|
|
@ -392,5 +392,7 @@
|
|||
"个人": "个人",
|
||||
"重新安装": "重新安装",
|
||||
"强制删除": "强制删除",
|
||||
"全部任务": "全部任务"
|
||||
"全部任务": "全部任务",
|
||||
"关联订阅": "关联订阅",
|
||||
"订阅": "订阅"
|
||||
}
|
||||
|
|
|
@ -56,6 +56,7 @@ import { getCommandScript, parseCrontab } from '@/utils';
|
|||
import { ColumnProps } from 'antd/lib/table';
|
||||
import { useVT } from 'virtualizedtableforantd4';
|
||||
import { ICrontab, OperationName, OperationPath, CrontabStatus } from './type';
|
||||
import Name from '@/components/name';
|
||||
|
||||
const { Text, Paragraph } = Typography;
|
||||
const { Search } = Input;
|
||||
|
@ -67,6 +68,7 @@ const Crontab = () => {
|
|||
title: intl.get('名称'),
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
fixed: isPhone ? undefined : 'left',
|
||||
width: 120,
|
||||
render: (text: string, record: any) => (
|
||||
<>
|
||||
|
@ -122,7 +124,7 @@ const Crontab = () => {
|
|||
title: intl.get('命令/脚本'),
|
||||
dataIndex: 'command',
|
||||
key: 'command',
|
||||
width: 240,
|
||||
width: 200,
|
||||
render: (text, record) => {
|
||||
return (
|
||||
<Paragraph
|
||||
|
@ -146,82 +148,11 @@ const Crontab = () => {
|
|||
compare: (a: any, b: any) => a.command.localeCompare(b.command),
|
||||
},
|
||||
},
|
||||
{
|
||||
title: intl.get('定时规则'),
|
||||
dataIndex: 'schedule',
|
||||
key: 'schedule',
|
||||
width: 140,
|
||||
sorter: {
|
||||
compare: (a, b) => a.schedule.localeCompare(b.schedule),
|
||||
},
|
||||
},
|
||||
{
|
||||
title: intl.get('最后运行时长'),
|
||||
width: 150,
|
||||
dataIndex: 'last_running_time',
|
||||
key: 'last_running_time',
|
||||
sorter: {
|
||||
compare: (a: any, b: any) => {
|
||||
return a.last_running_time - b.last_running_time;
|
||||
},
|
||||
},
|
||||
render: (text, record) => {
|
||||
return record.last_running_time
|
||||
? diffTime(record.last_running_time)
|
||||
: '-';
|
||||
},
|
||||
},
|
||||
{
|
||||
title: intl.get('最后运行时间'),
|
||||
dataIndex: 'last_execution_time',
|
||||
key: 'last_execution_time',
|
||||
width: 120,
|
||||
sorter: {
|
||||
compare: (a, b) => {
|
||||
return (a.last_execution_time || 0) - (b.last_execution_time || 0);
|
||||
},
|
||||
},
|
||||
render: (text, record) => {
|
||||
const language = navigator.language || navigator.languages[0];
|
||||
return (
|
||||
<span
|
||||
style={{
|
||||
display: 'block',
|
||||
}}
|
||||
>
|
||||
{record.last_execution_time
|
||||
? new Date(record.last_execution_time * 1000)
|
||||
.toLocaleString(language, {
|
||||
hour12: false,
|
||||
})
|
||||
.replace(' 24:', ' 00:')
|
||||
: '-'}
|
||||
</span>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: intl.get('下次运行时间'),
|
||||
width: 120,
|
||||
sorter: {
|
||||
compare: (a: any, b: any) => {
|
||||
return a.nextRunTime - b.nextRunTime;
|
||||
},
|
||||
},
|
||||
render: (text, record) => {
|
||||
const language = navigator.language || navigator.languages[0];
|
||||
return record.nextRunTime
|
||||
.toLocaleString(language, {
|
||||
hour12: false,
|
||||
})
|
||||
.replace(' 24:', ' 00:');
|
||||
},
|
||||
},
|
||||
{
|
||||
title: intl.get('状态'),
|
||||
key: 'status',
|
||||
dataIndex: 'status',
|
||||
width: 88,
|
||||
width: 100,
|
||||
filters: [
|
||||
{
|
||||
text: intl.get('运行中'),
|
||||
|
@ -272,10 +203,97 @@ const Crontab = () => {
|
|||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: intl.get('定时规则'),
|
||||
dataIndex: 'schedule',
|
||||
key: 'schedule',
|
||||
width: 150,
|
||||
sorter: {
|
||||
compare: (a, b) => a.schedule.localeCompare(b.schedule),
|
||||
},
|
||||
},
|
||||
{
|
||||
title: intl.get('最后运行时长'),
|
||||
width: 180,
|
||||
dataIndex: 'last_running_time',
|
||||
key: 'last_running_time',
|
||||
sorter: {
|
||||
compare: (a: any, b: any) => {
|
||||
return a.last_running_time - b.last_running_time;
|
||||
},
|
||||
},
|
||||
render: (text, record) => {
|
||||
return record.last_running_time
|
||||
? diffTime(record.last_running_time)
|
||||
: '-';
|
||||
},
|
||||
},
|
||||
{
|
||||
title: intl.get('最后运行时间'),
|
||||
dataIndex: 'last_execution_time',
|
||||
key: 'last_execution_time',
|
||||
width: 150,
|
||||
sorter: {
|
||||
compare: (a, b) => {
|
||||
return (a.last_execution_time || 0) - (b.last_execution_time || 0);
|
||||
},
|
||||
},
|
||||
render: (text, record) => {
|
||||
const language = navigator.language || navigator.languages[0];
|
||||
return (
|
||||
<span
|
||||
style={{
|
||||
display: 'block',
|
||||
}}
|
||||
>
|
||||
{record.last_execution_time
|
||||
? new Date(record.last_execution_time * 1000)
|
||||
.toLocaleString(language, {
|
||||
hour12: false,
|
||||
})
|
||||
.replace(' 24:', ' 00:')
|
||||
: '-'}
|
||||
</span>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: intl.get('下次运行时间'),
|
||||
width: 150,
|
||||
sorter: {
|
||||
compare: (a: any, b: any) => {
|
||||
return a.nextRunTime - b.nextRunTime;
|
||||
},
|
||||
},
|
||||
render: (text, record) => {
|
||||
const language = navigator.language || navigator.languages[0];
|
||||
return record.nextRunTime
|
||||
.toLocaleString(language, {
|
||||
hour12: false,
|
||||
})
|
||||
.replace(' 24:', ' 00:');
|
||||
},
|
||||
},
|
||||
{
|
||||
title: intl.get('关联订阅'),
|
||||
width: 190,
|
||||
render: (text, record: any) =>
|
||||
record.sub_id ? (
|
||||
<Name
|
||||
service={() =>
|
||||
request.get(`${config.apiPrefix}subscriptions/${record.sub_id}`)
|
||||
}
|
||||
options={{ ready: record?.sub_id, cacheKey: record.sub_id }}
|
||||
/>
|
||||
) : (
|
||||
'-'
|
||||
),
|
||||
},
|
||||
{
|
||||
title: intl.get('操作'),
|
||||
key: 'action',
|
||||
width: 130,
|
||||
fixed: isPhone ? undefined : 'right',
|
||||
render: (text, record, index) => {
|
||||
const isPc = !isPhone;
|
||||
return (
|
||||
|
@ -350,6 +368,7 @@ const Crontab = () => {
|
|||
const [moreMenuActive, setMoreMenuActive] = useState(false);
|
||||
const tableRef = useRef<HTMLDivElement>(null);
|
||||
const tableScrollHeight = useTableScrollHeight(tableRef);
|
||||
const [activeKey, setActiveKey] = useState('');
|
||||
|
||||
const goToScriptManager = (record: any) => {
|
||||
const result = getCommandScript(record.command);
|
||||
|
@ -784,6 +803,9 @@ const Crontab = () => {
|
|||
if (pageConf.page && pageConf.size) {
|
||||
getCrons();
|
||||
}
|
||||
if (viewConf && viewConf.id) {
|
||||
setActiveKey(viewConf.id);
|
||||
}
|
||||
}, [pageConf, viewConf]);
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -883,11 +905,6 @@ const Crontab = () => {
|
|||
setViewConf(view ? view : null);
|
||||
};
|
||||
|
||||
const [vt] = useVT(
|
||||
() => ({ scroll: { y: tableScrollHeight } }),
|
||||
[tableScrollHeight],
|
||||
);
|
||||
|
||||
return (
|
||||
<PageContainer
|
||||
className="ql-container-wrapper crontab-wrapper ql-container-wrapper-has-tab"
|
||||
|
@ -914,6 +931,7 @@ const Crontab = () => {
|
|||
<Tabs
|
||||
defaultActiveKey="all"
|
||||
size="small"
|
||||
activeKey={activeKey}
|
||||
tabPosition="top"
|
||||
className={`crontab-view ${moreMenuActive ? 'more-active' : ''}`}
|
||||
tabBarExtraContent={
|
||||
|
@ -1023,7 +1041,6 @@ const Crontab = () => {
|
|||
rowSelection={rowSelection}
|
||||
rowClassName={getRowClassName}
|
||||
onChange={onPageChange}
|
||||
// components={isPhone ? undefined : vt}
|
||||
/>
|
||||
</div>
|
||||
<CronLogModal
|
||||
|
@ -1063,9 +1080,6 @@ const Crontab = () => {
|
|||
handleCancel={(data) => {
|
||||
setIsCreateViewModalVisible(false);
|
||||
getCronViews();
|
||||
if (data && data.id === viewConf.id) {
|
||||
setViewConf({ ...viewConf, ...data });
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<ViewManageModal
|
||||
|
@ -1076,9 +1090,6 @@ const Crontab = () => {
|
|||
}}
|
||||
cronViewChange={(data) => {
|
||||
getCronViews();
|
||||
if (data && data.id === viewConf.id) {
|
||||
setViewConf({ ...viewConf, ...data });
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</PageContainer>
|
||||
|
|
|
@ -14,15 +14,16 @@ import { request } from '@/utils/http';
|
|||
import config from '@/utils/config';
|
||||
import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
|
||||
import IconFont from '@/components/iconfont';
|
||||
import get from 'lodash/get';
|
||||
import { CrontabStatus } from './type';
|
||||
import { useRequest } from 'ahooks';
|
||||
|
||||
const PROPERTIES = [
|
||||
{ name: intl.get('命令'), value: 'command' },
|
||||
{ name: intl.get('名称'), value: 'name' },
|
||||
{ name: intl.get('定时规则'), value: 'schedule' },
|
||||
{ name: intl.get('状态'), value: 'status' },
|
||||
{ name: intl.get('状态'), value: 'status', onlySelect: true },
|
||||
{ name: intl.get('标签'), value: 'labels' },
|
||||
{ name: intl.get('订阅'), value: 'sub_id', onlySelect: true },
|
||||
];
|
||||
|
||||
const EOperation: any = {
|
||||
|
@ -47,14 +48,6 @@ const SORTTYPES = [
|
|||
{ name: intl.get('倒序'), value: 'DESC' },
|
||||
];
|
||||
|
||||
const STATUS_MAP = {
|
||||
status: [
|
||||
{ name: intl.get('运行中'), value: CrontabStatus.running },
|
||||
{ name: intl.get('空闲中'), value: CrontabStatus.idle },
|
||||
{ name: intl.get('已禁用'), value: CrontabStatus.disabled },
|
||||
],
|
||||
};
|
||||
|
||||
enum ViewFilterRelation {
|
||||
'and' = '且',
|
||||
'or' = '或',
|
||||
|
@ -73,6 +66,21 @@ const ViewCreateModal = ({
|
|||
const [loading, setLoading] = useState(false);
|
||||
const [filterRelation, setFilterRelation] = useState<'and' | 'or'>('and');
|
||||
const filtersValue = Form.useWatch('filters', form);
|
||||
const { data } = useRequest(
|
||||
() => request.get(`${config.apiPrefix}subscriptions`),
|
||||
{
|
||||
cacheKey: 'subscriptions',
|
||||
},
|
||||
);
|
||||
|
||||
const STATUS_MAP = {
|
||||
status: [
|
||||
{ name: intl.get('运行中'), value: CrontabStatus.running },
|
||||
{ name: intl.get('空闲中'), value: CrontabStatus.idle },
|
||||
{ name: intl.get('已禁用'), value: CrontabStatus.disabled },
|
||||
],
|
||||
sub_id: data?.data.map((x) => ({ name: x.name, value: x.id })),
|
||||
};
|
||||
|
||||
const handleOk = async (values: any) => {
|
||||
setLoading(true);
|
||||
|
@ -105,7 +113,7 @@ const ViewCreateModal = ({
|
|||
}, [view, visible]);
|
||||
|
||||
const operationElement = (
|
||||
<Select style={{ width: 80 }}>
|
||||
<Select style={{ width: 120 }}>
|
||||
{OPERATIONS.map((x) => (
|
||||
<Select.Option key={x.name} value={x.value}>
|
||||
{x.name}
|
||||
|
@ -243,7 +251,7 @@ const ViewCreateModal = ({
|
|||
name={[name, 'property']}
|
||||
rules={[{ required: true }]}
|
||||
>
|
||||
{propertyElement(PROPERTIES, { width: 90 })}
|
||||
{propertyElement(PROPERTIES, { width: 120 })}
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
{...restField}
|
||||
|
|
|
@ -85,6 +85,7 @@ const ViewManageModal = ({
|
|||
title: intl.get('名称'),
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
render: (v) => (v === '全部任务' ? intl.get('全部任务') : v)
|
||||
},
|
||||
{
|
||||
title: intl.get('类型'),
|
||||
|
|
Loading…
Reference in New Issue
Block a user