mirror of
https://github.com/whyour/qinglong.git
synced 2025-07-07 11:56:08 +08:00
定时任务增加关联订阅
This commit is contained in:
parent
e7d023a7e0
commit
2e27d4057e
|
@ -129,6 +129,7 @@
|
||||||
"@types/uuid": "^8.3.4",
|
"@types/uuid": "^8.3.4",
|
||||||
"@umijs/max": "^4.0.72",
|
"@umijs/max": "^4.0.72",
|
||||||
"@umijs/ssr-darkreader": "^4.9.45",
|
"@umijs/ssr-darkreader": "^4.9.45",
|
||||||
|
"ahooks": "^3.7.8",
|
||||||
"ansi-to-react": "^6.1.6",
|
"ansi-to-react": "^6.1.6",
|
||||||
"antd": "^4.24.8",
|
"antd": "^4.24.8",
|
||||||
"antd-img-crop": "^4.2.3",
|
"antd-img-crop": "^4.2.3",
|
||||||
|
|
|
@ -219,6 +219,9 @@ devDependencies:
|
||||||
'@umijs/ssr-darkreader':
|
'@umijs/ssr-darkreader':
|
||||||
specifier: ^4.9.45
|
specifier: ^4.9.45
|
||||||
version: 4.9.45
|
version: 4.9.45
|
||||||
|
ahooks:
|
||||||
|
specifier: ^3.7.8
|
||||||
|
version: 3.7.8(react@18.2.0)
|
||||||
ansi-to-react:
|
ansi-to-react:
|
||||||
specifier: ^6.1.6
|
specifier: ^6.1.6
|
||||||
version: 6.1.6(react-dom@18.2.0)(react@18.2.0)
|
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
|
'@types/istanbul-lib-report': 3.0.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/@types/js-cookie@2.2.7:
|
||||||
|
resolution: {integrity: sha512-aLkWa0C0vO5b4Sr798E26QgOkss68Un0bLjs7u9qxzPT5CG+8DuNTffWES58YzJs3hrVAOs1wonycqEBqNJubA==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/@types/js-yaml@4.0.5:
|
/@types/js-yaml@4.0.5:
|
||||||
resolution: {integrity: sha512-FhpRzf927MNQdRZP0J5DLIdTXhjLYzeUTmLAu69mnVksLH9CJY3IuSeEgbKUki7GQZm0WqDkGzyxju2EZGD2wA==}
|
resolution: {integrity: sha512-FhpRzf927MNQdRZP0J5DLIdTXhjLYzeUTmLAu69mnVksLH9CJY3IuSeEgbKUki7GQZm0WqDkGzyxju2EZGD2wA==}
|
||||||
dev: true
|
dev: true
|
||||||
|
@ -5574,6 +5581,32 @@ packages:
|
||||||
clean-stack: 2.2.0
|
clean-stack: 2.2.0
|
||||||
indent-string: 4.0.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):
|
/ajv-formats@2.1.1(ajv@8.12.0):
|
||||||
resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==}
|
resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
@ -9101,6 +9134,10 @@ packages:
|
||||||
side-channel: 1.0.4
|
side-channel: 1.0.4
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/intersection-observer@0.12.2:
|
||||||
|
resolution: {integrity: sha512-7m1vEcPCxXYI8HqnL8CKI6siDyD+eIWSwgB3DZA+ZTogxk9I4CDnj4wilt9x/+/QbHI4YG5YZNmC6458/e9Ktg==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/intl-format-cache@4.3.1:
|
/intl-format-cache@4.3.1:
|
||||||
resolution: {integrity: sha512-OEUYNA7D06agqPOYhbTkl0T8HA3QKSuwWh1HiClEnpd9vw7N+3XsQt5iZ0GUEchp5CW1fQk/tary+NsbF3yQ1Q==}
|
resolution: {integrity: sha512-OEUYNA7D06agqPOYhbTkl0T8HA3QKSuwWh1HiClEnpd9vw7N+3XsQt5iZ0GUEchp5CW1fQk/tary+NsbF3yQ1Q==}
|
||||||
dev: true
|
dev: true
|
||||||
|
@ -9578,6 +9615,10 @@ packages:
|
||||||
'@sideway/pinpoint': 2.0.0
|
'@sideway/pinpoint': 2.0.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/js-cookie@2.2.1:
|
||||||
|
resolution: {integrity: sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/js-sdsl@4.4.1:
|
/js-sdsl@4.4.1:
|
||||||
resolution: {integrity: sha512-6Gsx8R0RucyePbWqPssR8DyfuXmLBooYN5cZFZKjHGnQuaf7pEzhtpceagJxVu4LqhYY5EYA7nko3FmeHZ1KbA==}
|
resolution: {integrity: sha512-6Gsx8R0RucyePbWqPssR8DyfuXmLBooYN5cZFZKjHGnQuaf7pEzhtpceagJxVu4LqhYY5EYA7nko3FmeHZ1KbA==}
|
||||||
dev: true
|
dev: true
|
||||||
|
@ -13626,6 +13667,11 @@ packages:
|
||||||
ajv-keywords: 5.1.0(ajv@8.12.0)
|
ajv-keywords: 5.1.0(ajv@8.12.0)
|
||||||
dev: true
|
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:
|
/scroll-into-view-if-needed@2.2.31:
|
||||||
resolution: {integrity: sha512-dGCXy99wZQivjmjIqihaBQNjryrz5rueJY7eHfTdyWEiR4ttYpsajb14rn9s5d4DY4EcY6+4+U/maARBXJedkA==}
|
resolution: {integrity: sha512-dGCXy99wZQivjmjIqihaBQNjryrz5rueJY7eHfTdyWEiR4ttYpsajb14rn9s5d4DY4EcY6+4+U/maARBXJedkA==}
|
||||||
dependencies:
|
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;
|
box-shadow: none;
|
||||||
border-right: 1px solid #eee;
|
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",
|
"个人": "Personal",
|
||||||
"重新安装": "Reinstall",
|
"重新安装": "Reinstall",
|
||||||
"强制删除": "Force Delete",
|
"强制删除": "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 { ColumnProps } from 'antd/lib/table';
|
||||||
import { useVT } from 'virtualizedtableforantd4';
|
import { useVT } from 'virtualizedtableforantd4';
|
||||||
import { ICrontab, OperationName, OperationPath, CrontabStatus } from './type';
|
import { ICrontab, OperationName, OperationPath, CrontabStatus } from './type';
|
||||||
|
import Name from '@/components/name';
|
||||||
|
|
||||||
const { Text, Paragraph } = Typography;
|
const { Text, Paragraph } = Typography;
|
||||||
const { Search } = Input;
|
const { Search } = Input;
|
||||||
|
@ -67,6 +68,7 @@ const Crontab = () => {
|
||||||
title: intl.get('名称'),
|
title: intl.get('名称'),
|
||||||
dataIndex: 'name',
|
dataIndex: 'name',
|
||||||
key: 'name',
|
key: 'name',
|
||||||
|
fixed: isPhone ? undefined : 'left',
|
||||||
width: 120,
|
width: 120,
|
||||||
render: (text: string, record: any) => (
|
render: (text: string, record: any) => (
|
||||||
<>
|
<>
|
||||||
|
@ -122,7 +124,7 @@ const Crontab = () => {
|
||||||
title: intl.get('命令/脚本'),
|
title: intl.get('命令/脚本'),
|
||||||
dataIndex: 'command',
|
dataIndex: 'command',
|
||||||
key: 'command',
|
key: 'command',
|
||||||
width: 240,
|
width: 200,
|
||||||
render: (text, record) => {
|
render: (text, record) => {
|
||||||
return (
|
return (
|
||||||
<Paragraph
|
<Paragraph
|
||||||
|
@ -146,82 +148,11 @@ const Crontab = () => {
|
||||||
compare: (a: any, b: any) => a.command.localeCompare(b.command),
|
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('状态'),
|
title: intl.get('状态'),
|
||||||
key: 'status',
|
key: 'status',
|
||||||
dataIndex: 'status',
|
dataIndex: 'status',
|
||||||
width: 88,
|
width: 100,
|
||||||
filters: [
|
filters: [
|
||||||
{
|
{
|
||||||
text: intl.get('运行中'),
|
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('操作'),
|
title: intl.get('操作'),
|
||||||
key: 'action',
|
key: 'action',
|
||||||
width: 130,
|
width: 130,
|
||||||
|
fixed: isPhone ? undefined : 'right',
|
||||||
render: (text, record, index) => {
|
render: (text, record, index) => {
|
||||||
const isPc = !isPhone;
|
const isPc = !isPhone;
|
||||||
return (
|
return (
|
||||||
|
@ -350,6 +368,7 @@ const Crontab = () => {
|
||||||
const [moreMenuActive, setMoreMenuActive] = useState(false);
|
const [moreMenuActive, setMoreMenuActive] = useState(false);
|
||||||
const tableRef = useRef<HTMLDivElement>(null);
|
const tableRef = useRef<HTMLDivElement>(null);
|
||||||
const tableScrollHeight = useTableScrollHeight(tableRef);
|
const tableScrollHeight = useTableScrollHeight(tableRef);
|
||||||
|
const [activeKey, setActiveKey] = useState('');
|
||||||
|
|
||||||
const goToScriptManager = (record: any) => {
|
const goToScriptManager = (record: any) => {
|
||||||
const result = getCommandScript(record.command);
|
const result = getCommandScript(record.command);
|
||||||
|
@ -784,6 +803,9 @@ const Crontab = () => {
|
||||||
if (pageConf.page && pageConf.size) {
|
if (pageConf.page && pageConf.size) {
|
||||||
getCrons();
|
getCrons();
|
||||||
}
|
}
|
||||||
|
if (viewConf && viewConf.id) {
|
||||||
|
setActiveKey(viewConf.id);
|
||||||
|
}
|
||||||
}, [pageConf, viewConf]);
|
}, [pageConf, viewConf]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -883,11 +905,6 @@ const Crontab = () => {
|
||||||
setViewConf(view ? view : null);
|
setViewConf(view ? view : null);
|
||||||
};
|
};
|
||||||
|
|
||||||
const [vt] = useVT(
|
|
||||||
() => ({ scroll: { y: tableScrollHeight } }),
|
|
||||||
[tableScrollHeight],
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageContainer
|
<PageContainer
|
||||||
className="ql-container-wrapper crontab-wrapper ql-container-wrapper-has-tab"
|
className="ql-container-wrapper crontab-wrapper ql-container-wrapper-has-tab"
|
||||||
|
@ -914,6 +931,7 @@ const Crontab = () => {
|
||||||
<Tabs
|
<Tabs
|
||||||
defaultActiveKey="all"
|
defaultActiveKey="all"
|
||||||
size="small"
|
size="small"
|
||||||
|
activeKey={activeKey}
|
||||||
tabPosition="top"
|
tabPosition="top"
|
||||||
className={`crontab-view ${moreMenuActive ? 'more-active' : ''}`}
|
className={`crontab-view ${moreMenuActive ? 'more-active' : ''}`}
|
||||||
tabBarExtraContent={
|
tabBarExtraContent={
|
||||||
|
@ -1023,7 +1041,6 @@ const Crontab = () => {
|
||||||
rowSelection={rowSelection}
|
rowSelection={rowSelection}
|
||||||
rowClassName={getRowClassName}
|
rowClassName={getRowClassName}
|
||||||
onChange={onPageChange}
|
onChange={onPageChange}
|
||||||
// components={isPhone ? undefined : vt}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<CronLogModal
|
<CronLogModal
|
||||||
|
@ -1063,9 +1080,6 @@ const Crontab = () => {
|
||||||
handleCancel={(data) => {
|
handleCancel={(data) => {
|
||||||
setIsCreateViewModalVisible(false);
|
setIsCreateViewModalVisible(false);
|
||||||
getCronViews();
|
getCronViews();
|
||||||
if (data && data.id === viewConf.id) {
|
|
||||||
setViewConf({ ...viewConf, ...data });
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<ViewManageModal
|
<ViewManageModal
|
||||||
|
@ -1076,9 +1090,6 @@ const Crontab = () => {
|
||||||
}}
|
}}
|
||||||
cronViewChange={(data) => {
|
cronViewChange={(data) => {
|
||||||
getCronViews();
|
getCronViews();
|
||||||
if (data && data.id === viewConf.id) {
|
|
||||||
setViewConf({ ...viewConf, ...data });
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</PageContainer>
|
</PageContainer>
|
||||||
|
|
|
@ -14,15 +14,16 @@ import { request } from '@/utils/http';
|
||||||
import config from '@/utils/config';
|
import config from '@/utils/config';
|
||||||
import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
|
import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
|
||||||
import IconFont from '@/components/iconfont';
|
import IconFont from '@/components/iconfont';
|
||||||
import get from 'lodash/get';
|
|
||||||
import { CrontabStatus } from './type';
|
import { CrontabStatus } from './type';
|
||||||
|
import { useRequest } from 'ahooks';
|
||||||
|
|
||||||
const PROPERTIES = [
|
const PROPERTIES = [
|
||||||
{ name: intl.get('命令'), value: 'command' },
|
{ name: intl.get('命令'), value: 'command' },
|
||||||
{ name: intl.get('名称'), value: 'name' },
|
{ name: intl.get('名称'), value: 'name' },
|
||||||
{ name: intl.get('定时规则'), value: 'schedule' },
|
{ 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: 'labels' },
|
||||||
|
{ name: intl.get('订阅'), value: 'sub_id', onlySelect: true },
|
||||||
];
|
];
|
||||||
|
|
||||||
const EOperation: any = {
|
const EOperation: any = {
|
||||||
|
@ -47,14 +48,6 @@ const SORTTYPES = [
|
||||||
{ name: intl.get('倒序'), value: 'DESC' },
|
{ 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 {
|
enum ViewFilterRelation {
|
||||||
'and' = '且',
|
'and' = '且',
|
||||||
'or' = '或',
|
'or' = '或',
|
||||||
|
@ -73,6 +66,21 @@ const ViewCreateModal = ({
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [filterRelation, setFilterRelation] = useState<'and' | 'or'>('and');
|
const [filterRelation, setFilterRelation] = useState<'and' | 'or'>('and');
|
||||||
const filtersValue = Form.useWatch('filters', form);
|
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) => {
|
const handleOk = async (values: any) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
@ -105,7 +113,7 @@ const ViewCreateModal = ({
|
||||||
}, [view, visible]);
|
}, [view, visible]);
|
||||||
|
|
||||||
const operationElement = (
|
const operationElement = (
|
||||||
<Select style={{ width: 80 }}>
|
<Select style={{ width: 120 }}>
|
||||||
{OPERATIONS.map((x) => (
|
{OPERATIONS.map((x) => (
|
||||||
<Select.Option key={x.name} value={x.value}>
|
<Select.Option key={x.name} value={x.value}>
|
||||||
{x.name}
|
{x.name}
|
||||||
|
@ -243,7 +251,7 @@ const ViewCreateModal = ({
|
||||||
name={[name, 'property']}
|
name={[name, 'property']}
|
||||||
rules={[{ required: true }]}
|
rules={[{ required: true }]}
|
||||||
>
|
>
|
||||||
{propertyElement(PROPERTIES, { width: 90 })}
|
{propertyElement(PROPERTIES, { width: 120 })}
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
{...restField}
|
{...restField}
|
||||||
|
|
|
@ -85,6 +85,7 @@ const ViewManageModal = ({
|
||||||
title: intl.get('名称'),
|
title: intl.get('名称'),
|
||||||
dataIndex: 'name',
|
dataIndex: 'name',
|
||||||
key: 'name',
|
key: 'name',
|
||||||
|
render: (v) => (v === '全部任务' ? intl.get('全部任务') : v)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: intl.get('类型'),
|
title: intl.get('类型'),
|
||||||
|
|
Loading…
Reference in New Issue
Block a user