Commit bdd897ee by zhaochengxiang

Merge branch 'virtual' into 'master'

Virtual

See merge request !9
parents 9759856d 247c09f0
const CracoLessPlugin = require('craco-less'); const CracoLessPlugin = require('craco-less');
const { loaderByName } = require("@craco/craco");
const { name } = require('./package'); const { name } = require('./package');
module.exports = { module.exports = {
...@@ -6,6 +7,24 @@ module.exports = { ...@@ -6,6 +7,24 @@ module.exports = {
{ {
plugin: CracoLessPlugin, plugin: CracoLessPlugin,
options: { options: {
modifyLessRule(lessRule, context) {
// You have to exclude these file suffixes first,
// if you want to modify the less module's suffix
lessRule.exclude = /\.m\.less$/;
return lessRule;
},
modifyLessModuleRule(lessModuleRule, context) {
// Configure the file suffix
lessModuleRule.test = /\.m\.less$/;
// Configure the generated local ident name.
const cssLoader = lessModuleRule.use.find(loaderByName("css-loader"));
cssLoader.options.modules = {
localIdentName: "[local]_[hash:base64:5]",
};
return lessModuleRule;
},
lessLoaderOptions: { lessLoaderOptions: {
lessOptions: { lessOptions: {
modifyVars: { modifyVars: {
...@@ -30,8 +49,18 @@ module.exports = { ...@@ -30,8 +49,18 @@ module.exports = {
libraryTarget: 'umd', libraryTarget: 'umd',
chunkLoadingGlobal: `webpackJsonp_${name}`, chunkLoadingGlobal: `webpackJsonp_${name}`,
globalObject: 'window', globalObject: 'window',
} },
} module: {
rules: [
{
test: /\.m?js/,
resolve: {
fullySpecified: false,
},
},
],
},
},
}, },
devServer: { devServer: {
headers: { headers: {
......
...@@ -11,10 +11,10 @@ ...@@ -11,10 +11,10 @@
"@testing-library/jest-dom": "^5.11.4", "@testing-library/jest-dom": "^5.11.4",
"@testing-library/react": "^11.1.0", "@testing-library/react": "^11.1.0",
"@testing-library/user-event": "^12.1.10", "@testing-library/user-event": "^12.1.10",
"@types/jest": "^27.4.1", "@types/jest": "^27.5.1",
"@types/node": "^17.0.21", "@types/node": "16.11.12",
"@types/react": "^17.0.39", "@types/react": "17.0.33",
"@types/react-dom": "^17.0.12", "@types/react-dom": "17.0.10",
"ahooks": "^3.1.7", "ahooks": "^3.1.7",
"antd": "4.18.2", "antd": "4.18.2",
"axios": "^0.19.0", "axios": "^0.19.0",
...@@ -31,6 +31,8 @@ ...@@ -31,6 +31,8 @@
"local-storage": "^2.0.0", "local-storage": "^2.0.0",
"react": "^17.0.1", "react": "^17.0.1",
"react-contexify": "^5.0.0", "react-contexify": "^5.0.0",
"react-contextmenu": "2.14.0",
"react-data-grid": "7.0.0-beta.13",
"react-dnd": "^14.0.2", "react-dnd": "^14.0.2",
"react-dnd-html5-backend": "^14.0.0", "react-dnd-html5-backend": "^14.0.0",
"react-dom": "^17.0.1", "react-dom": "^17.0.1",
...@@ -44,7 +46,7 @@ ...@@ -44,7 +46,7 @@
"redux-saga": "^1.0.5", "redux-saga": "^1.0.5",
"showdown": "^1.9.1", "showdown": "^1.9.1",
"smooth-scroll": "^16.1.3", "smooth-scroll": "^16.1.3",
"typescript": "^4.6.2", "typescript": "4.4.4",
"web-vitals": "^1.0.1" "web-vitals": "^1.0.1"
}, },
"scripts": { "scripts": {
......
declare const rctable: any;
declare module 'rc-table' {
export default rctable;
}
declare const nprogress: any;
declare module 'nprogress' {
export default nprogress;
}
declare module 'react-resizable' {
export const Resizable:any
}
declare module 'd3v3'
declare module '*' // for import jsx
\ No newline at end of file
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
@import '~antd/dist/antd.less'; @import '~antd/dist/antd.less';
@import './mixins.less'; @import './mixins.less';
@import './variables.less'; @import './variables.less';
@import './view/Manage/VirtualTable/mixins.less';
//与center-home中的样式保持统一 //与center-home中的样式保持统一
body { body {
...@@ -214,6 +215,10 @@ tr.drop-over-upward td { ...@@ -214,6 +215,10 @@ tr.drop-over-upward td {
color: #5B5B5B; color: #5B5B5B;
} }
.anchor {
background-color: #e7f7ff;
}
.m-common { .m-common {
margin: 20px 15px; margin: 20px 15px;
} }
......
/// <reference types="react-scripts" /> declare const rctable: any;
declare module 'rc-table' {
export default rctable;
}
declare const nprogress: any;
declare module 'nprogress' {
export default nprogress;
}
declare module 'react-resizable' {
export const Resizable:any
}
declare module 'd3v3'
declare module "*.module.less" {
const classes: { readonly [key: string]: string };
export default classes;
}
declare module '*' // for import jsx
\ No newline at end of file
...@@ -182,6 +182,7 @@ const EditModel = (props) => { ...@@ -182,6 +182,7 @@ const EditModel = (props) => {
setActionData({ ...actionData, ...{ action: 'detail', modelerId: data.id||'', editable: data?.editable||false, stateId: data?.state?.id||'' } }); setActionData({ ...actionData, ...{ action: 'detail', modelerId: data.id||'', editable: data?.editable||false, stateId: data?.state?.id||'' } });
actionRef.current = 'detail'; actionRef.current = 'detail';
LocalStorage.set('modelId', data.id||'');
LocalStorage.set('modelChange', !(LocalStorage.get('modelChange')||false)); LocalStorage.set('modelChange', !(LocalStorage.get('modelChange')||false));
}, },
error: () => { error: () => {
...@@ -199,6 +200,7 @@ const EditModel = (props) => { ...@@ -199,6 +200,7 @@ const EditModel = (props) => {
setActionData({ ...actionData, ...{ action: (_action==='flow')?'flow':'detail', modelerId: data.id||'', stateId: data?.state?.id||'', permitCheckOut: data?.permitCheckOut||false, editable: data?.editable||false } }); setActionData({ ...actionData, ...{ action: (_action==='flow')?'flow':'detail', modelerId: data.id||'', stateId: data?.state?.id||'', permitCheckOut: data?.permitCheckOut||false, editable: data?.editable||false } });
actionRef.current = (_action==='flow')?'flow':'detail'; actionRef.current = (_action==='flow')?'flow':'detail';
LocalStorage.set('modelId', data.id||'');
LocalStorage.set('modelChange', !(LocalStorage.get('modelChange')||false)); LocalStorage.set('modelChange', !(LocalStorage.get('modelChange')||false));
} }
}, },
......
...@@ -20,7 +20,7 @@ ...@@ -20,7 +20,7 @@
display: none; display: none;
} }
.yy-table-tbody > tr:not(.yy-table-measure-row)> td { .yy-table-tbody tr:not(.yy-table-measure-row) td {
padding: 8px 8px !important; padding: 8px 8px !important;
} }
} }
......
import React, { ReactElement, useCallback, useEffect, useMemo, useState, forwardRef } from 'react';
import { createPortal } from 'react-dom';
import { ContextMenu, ContextMenuTrigger } from 'react-contextmenu';
import DataGrid, { Column, DataGridProps, Row as GridRow, RowsChangeData, SortColumn, SortIconProps, SelectColumn } from 'react-data-grid';
import type { DataGridHandle, CheckboxFormatterProps } from 'react-data-grid';
import { Checkbox, Empty } from 'antd';
import type { CheckboxChangeEvent } from 'antd/es/checkbox';
import { DownOutlined, UpOutlined } from '@ant-design/icons';
import { nanoid } from 'nanoid';
import { downNode, upNode } from './virtual-table-helper';
export enum RowAction {
None, Expand, Select
}
export enum RowType {
None, Detail
}
export interface RowData {
id: string,
index?: string;
__pid__?: string
__row__?: any
__type__?: RowType
__expanded__?: boolean
__selected__?: boolean
__action__?: RowAction
}
interface Props<Row> {
gridRef?: React.RefObject<DataGridHandle>
contextMenu?: {
id: string
menu: (row: RowData | undefined) => ReactElement
}
checkable: Boolean
expandable?: {
expandedRowHeight?: number
rowExpandable?: (row: Row) => Boolean
expandRowRender?: (row: Row) => React.ReactElement
}
getComparator?: (sortColumn: string) => (a: Row, b: Row) => number
loadMoreRows?: (length: number) => Promise<Row[]> | undefined
selectedRows?: Array<any>
onSelectedRowsChange?: (selectedRows: Array<any>) => void
rowHeight?: number
rowClassName?: (row: RowData) => string
}
const CheckboxFormatter = forwardRef<HTMLInputElement, CheckboxFormatterProps>(
function CheckboxFormatter({ onChange, ...props }: CheckboxFormatterProps, ref) {
function handleChange(e: CheckboxChangeEvent) {
onChange(e.target.checked, (e.nativeEvent as MouseEvent).shiftKey);
}
return <Checkbox ref={ref} {...props} onChange={handleChange} />;
}
);
export function getExpandingCol<Row extends RowData, SR>({ colSpan, expandRender, expandable }: {
colSpan: () => number
expandRender?: (row: Row) => React.ReactElement
expandable?: (row: Row) => Boolean
}) {
const col: Column<Row, SR> = {
key: 'expanded',
name: '',
minWidth: 30,
width: 30,
colSpan(args) {
return args.type === 'ROW' && args.row.__type__ === RowType.Detail ? colSpan() : undefined;
},
cellClass(row) {
return row.__type__ === RowType.Detail ? 'detail' : undefined;
},
formatter({ row, isCellSelected, onRowChange }) {
if (row.__type__ === RowType.Detail) {
if (expandRender) {
return expandRender(row.__row__)
}
return (
<div>{row.__pid__}</div>
);
}
if (!expandable || !expandable(row)) return null;
// 展开收起
return (
<div style={{ cursor: 'pointer', color: isCellSelected ? '#777' : '#ccc' }}
onClick={() => {
onRowChange({
...row,
__expanded__: !row.__expanded__,
__action__: RowAction.Expand
});
}}
>
{row.__expanded__ ? <UpOutlined /> : <DownOutlined />}
</div>
);
}
}
return col
}
function FC<Row extends RowData, SR, K extends React.Key = React.Key>(props: DataGridProps<Row, SR, K> & Props<Row>) {
const {
gridRef,
expandable,
getComparator,
loadMoreRows,
onSelectedRowsChange,
contextMenu, columns, rows, checkable, selectedRows, rowHeight = 45, rowClassName, ...rest } = props
const rowKeyGetter = (row: Row): K => {
return row.id as K;
}
const [contextItem, setContextItem] = useState<RowData|undefined>(undefined)
// 初始化onRowsChange
const onRowsChange = useCallback((rows: RowData[], { indexes }: RowsChangeData<Row, SR>) => {
const row = rows[indexes[0]];
if (row.__action__ === RowAction.Expand) {
if (!row.__expanded__) {
rows.splice(indexes[0] + 1, 1); // 删除下一行
} else {
rows.splice(indexes[0] + 1, 0, { // 插入下一行
id: `${row.id}-detail`, __type__: RowType.Detail, __pid__: row.id, __row__: row,
});
}
} else if (row.__action__ === RowAction.Select) {
// nothing to do, just update
}
_setRows(rows as Row[]);
}, [])
// 已排序行
const [sortColumns, setSortColumns] = useState<readonly SortColumn[]>([]); // 排序列图标状态
const [_rows, _setRows] = useState<readonly Row[]>([])
useEffect(() => {
const newRows = [...rows];
if (sortColumns.length > 0) {
newRows
// .filter(item => item.__type__ !== RowType.Detail)
.sort((a, b) => {
for (const sort of sortColumns) {
const comparator = getComparator ? getComparator(sort.columnKey) : GetComparator(sort.columnKey);
const compResult = comparator(a, b);
if (compResult !== 0) {
return sort.direction === 'ASC' ? compResult : -compResult;
}
}
return 0;
})
}
(newRows||[]).forEach((item, index) => {
item.index = `${index+1}`;
})
_setRows(newRows);
}, [rows, sortColumns, getComparator]);
// 组装功能s列
const cols = useMemo(() => {
if (expandable && checkable) {
const cols: readonly Column<Row, SR>[] = [
getExpandingCol<Row, SR>({
colSpan: () => columns.length+2, expandRender: expandable.expandRowRender, expandable: expandable.rowExpandable
}),
{ ...SelectColumn, key: 'select', frozen: false },
...columns,
]
return cols
} else if (expandable) {
const cols: readonly Column<Row, SR>[] = [
getExpandingCol<Row, SR>({
colSpan: () => columns.length+1, expandRender: expandable.expandRowRender, expandable: expandable.rowExpandable
}),
...columns,
]
return cols
} else if (checkable) {
const cols: readonly Column<Row, SR>[] = [
{ ...SelectColumn, key: 'select', frozen: false },
...columns,
]
return cols
}
return columns
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [columns, expandable, checkable])
// 处理滚动
const handleScroll = useCallback((event: React.UIEvent<HTMLDivElement>) => {
if (!isAtBottom(event)) return;
loadMoreRows?.(rows.length)
}, [loadMoreRows, rows])
const contextMenuId = contextMenu?.id ?? nanoid()
return (
<React.Fragment>
<DataGrid
ref={gridRef}
{...rest}
columns={cols}
rows={_rows}
rowKeyGetter={rowKeyGetter}
components={{
sortIcon: SortIcon,
checkboxFormatter: CheckboxFormatter,
rowRenderer: (props) => {
return (
contextMenu ? <ContextMenuTrigger
id={contextMenuId}
collect={() => ({ rowIdx: props?.rowIdx })}
disable={props.row.__type__===RowType.Detail}
>
<GridRow
className={rowClassName(props.row)}
id={`row-${props.row.id}`}
onContextMenu={(e: React.MouseEvent) => {
setContextItem(props.row);
}}
{...props}
/>
</ContextMenuTrigger> : <React.Fragment></React.Fragment>
)
},
noRowsFallback: <div style={{ textAlign: 'center', gridColumn: '1/-1' }}>
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
</div>,
}}
onRowsChange={onRowsChange}
// headerRowHeight={45}
rowHeight={(args) => (args.type === 'ROW' && args.row.__type__ === RowType.Detail ? (expandable.expandedRowHeight||100) : rowHeight)}
// defaultColumnOptions={{
// sortable: true,
// resizable: true
// }}
// 排序
sortColumns={sortColumns}
onSortColumnsChange={setSortColumns}
selectedRows={new Set(selectedRows||[])}
onSelectedRowsChange={(values: Set<any>) => {
onSelectedRowsChange && onSelectedRowsChange(Array.from(values));
}}
// 滚动
onScroll={loadMoreRows ? handleScroll : undefined}
/>
{contextMenu && createPortal(
<ContextMenu id={contextMenuId} rtl={false}>
{contextMenu.menu(contextItem)}
</ContextMenu>
,
document.body
)}
</React.Fragment>
);
}
export default FC
function GetComparator(sortColumn: string): (a: any, b: any) => number {
return (a, b) => {
const aVal = a[sortColumn], bVal = b[sortColumn]
if (aVal !== undefined && bVal !== undefined)
return aVal.localeCompare(bVal);
return 0
};
}
function isAtBottom({ currentTarget }: React.UIEvent<HTMLDivElement>): boolean {
return currentTarget.scrollTop + 10 >= currentTarget.scrollHeight - currentTarget.clientHeight;
}
function SortIcon({ sortDirection }: SortIconProps) {
const asc = sortDirection === 'ASC'
const desc = sortDirection === 'DESC'
return <div className="yy-table-column-sorter yy-table-column-sorter-full">
<span className="yy-table-column-sorter-inner">
{upNode(asc)}
{downNode(desc)}
</span>
</div>
}
\ No newline at end of file
.rdg {
border: none !important;
}
.rdg-header-row {
.rdg-cell {
box-shadow: none !important;
background-color: #f2f5fc!important;
border-block-end: none !important;
border-inline-end: none !important;
font-weight: normal !important;
outline: none !important;
&:before {
position: absolute;
top: 50%;
right: 0;
width: 1px;
height: 1.6em;
background-color: rgba(0,0,0,.06);
-webkit-transform: translateY(-50%);
transform: translateY(-50%);
transition: background-color .3s;
content: "";
}
}
}
.rdg-row {
&:hover {
background-color: #fafafa;
}
.rdg-cell {
box-shadow: none !important;
color: #363636 !important;
border-block-end: 1px solid #f0f0f0 !important;
border-inline-end: none !important;
outline: none !important;
}
}
.react-contextmenu-wrapper {
display: contents;
}
.react-contextmenu {
background-color: #fff;
background-clip: padding-box;
border: 1px solid rgba(0, 0, 0, 0.15);
border-radius: 0.25rem;
color: #373a3c;
font-size: 16px;
margin-block-start: 2px;
margin-block-end: 0;
margin-inline-start: 0;
margin-inline-end: 0;
min-inline-size: 160px;
outline: none;
opacity: 0;
padding-block: 5px;
padding-inline: 0;
pointer-events: none;
text-align: start;
transition: opacity 250ms ease !important;
}
.react-contextmenu.react-contextmenu--visible {
opacity: 1;
pointer-events: auto;
}
.react-contextmenu-item {
background: 0 0;
border: 0;
color: #373a3c;
cursor: pointer;
font-weight: 400;
line-height: 1.5;
padding-block: 3px;
padding-inline: 20px;
text-align: inherit;
white-space: nowrap;
}
.react-contextmenu-item.react-contextmenu-item--active,
.react-contextmenu-item.react-contextmenu-item--selected {
color: #fff;
background-color: #20a0ff;
border-color: #20a0ff;
text-decoration: none;
}
.react-contextmenu-item.react-contextmenu-item--disabled,
.react-contextmenu-item.react-contextmenu-item--disabled:hover {
background-color: transparent;
border-color: rgba(0, 0, 0, 0.15);
color: #878a8c;
}
.react-contextmenu-item--divider {
border-block-end: 1px solid rgba(0, 0, 0, 0.15);
cursor: inherit;
margin-block-end: 3px;
padding-block: 2px;
padding-inline: 0;
}
.react-contextmenu-item--divider:hover {
background-color: transparent;
border-color: rgba(0, 0, 0, 0.15);
}
.react-contextmenu-item.react-contextmenu-submenu {
padding: 0;
}
.react-contextmenu-item.react-contextmenu-submenu > .react-contextmenu-item::after {
content: '▶';
display: inline-block;
position: absolute;
inset-inline-end: 7px;
}
import CaretDownOutlined from '@ant-design/icons/CaretDownOutlined';
import CaretUpOutlined from '@ant-design/icons/CaretUpOutlined';
import classNames from 'classnames'
const prefixCls = 'yy'
export const upNode = (active:boolean) => (
<CaretUpOutlined
className={classNames(`${prefixCls}-table-column-sorter-up anticon-caret-up anticon`, {
active,
})}
/>
);
export const downNode = (active:boolean) => (
<CaretDownOutlined
className={classNames(`${prefixCls}-table-column-sorter-down anticon-caret-down anticon`, {
active,
})}
/>
);
{ {
"compilerOptions": { "compilerOptions": {
"target": "es2016", "target": "es5",
"module": "esnext",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true,
"lib": [ "lib": [
"dom", "dom",
"dom.iterable", "dom.iterable",
"esnext" "esnext"
], ],
"allowJs": true, "allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true, "allowSyntheticDefaultImports": true,
"strict": false,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true, "noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node", "moduleResolution": "node",
"resolveJsonModule": true, "resolveJsonModule": true,
"isolatedModules": true, "isolatedModules": 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