pureList
This commit is contained in:
parent
dfe01079ea
commit
0a5c5de102
|
|
@ -9,6 +9,12 @@
|
|||
"lib/**/*"
|
||||
],
|
||||
"dependencies": {
|
||||
"@ant-design/cssinjs": "^1.6.1",
|
||||
"@ant-design/icons": "^5.0.1",
|
||||
"@icon-park/react": "^1.4.2",
|
||||
"antd": "^5.3.0",
|
||||
"antd-mobile": "^5.28.1",
|
||||
"antd-mobile-icons": "^0.3.0",
|
||||
"debounce": "^1.2.1",
|
||||
"format-message-parse": "^6.2.4",
|
||||
"history": "^5.3.0",
|
||||
|
|
@ -16,6 +22,7 @@
|
|||
"oak-common-aspect": "file:../oak-common-aspect",
|
||||
"oak-domain": "file:../oak-domain",
|
||||
"oak-memory-tree-store": "file:../oak-memory-tree-store",
|
||||
"react-scripts": "^5.0.1",
|
||||
"rmc-pull-to-refresh": "^1.0.13",
|
||||
"url": "^0.11.0",
|
||||
"uuid": "^8.3.2"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"component": true,
|
||||
"usingComponents": {
|
||||
"l-dialog": "../../miniprogram_npm/lin-ui/dialog/index",
|
||||
"l-button": "../../miniprogram_npm/lin-ui/button/index",
|
||||
"popover": "../../miniprogram_npm/popover/popover",
|
||||
"popover-item": "../../miniprogram_npm/popover/popover-item"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
|
||||
.panel-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 20rpx 10rpx;
|
||||
}
|
||||
|
||||
.more {
|
||||
white-space: nowrap;
|
||||
font-size: 28rpx;
|
||||
color: #000;
|
||||
padding: 10rpx;
|
||||
}
|
||||
|
||||
.btn-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
flex-wrap: wrap;
|
||||
width: 100%;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
export default OakComponent({
|
||||
properties: {
|
||||
entity: String,
|
||||
actions: {
|
||||
type: Array,
|
||||
value: [],
|
||||
},
|
||||
items: {
|
||||
type: Array,
|
||||
value: [],
|
||||
},
|
||||
mode: {
|
||||
type: String,
|
||||
value: 'cell',
|
||||
},
|
||||
column: {
|
||||
type: Number,
|
||||
value: 3,
|
||||
},
|
||||
},
|
||||
data: {
|
||||
},
|
||||
lifetimes: {
|
||||
},
|
||||
methods: {
|
||||
},
|
||||
});
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
<block wx:if="{{ newItems && newItems.length > 0 }}">
|
||||
<view class="panel-container">
|
||||
<block wx:if="{{ moreItems && moreItems.length > 0 }}">
|
||||
<view id='more' class="more" catchtap="handleMoreClick">
|
||||
更多
|
||||
</view>
|
||||
<popover id='popover'>
|
||||
<block wx:for="{{ moreItems }}" wx:key="index">
|
||||
<popover-item hasline catchtap='handleClick' data-type="popover" data-item="{{ item }}">{{ item.text }}</popover-item>
|
||||
</block>
|
||||
</popover>
|
||||
</block>
|
||||
|
||||
|
||||
<view class="btn-container">
|
||||
|
||||
<block wx:for="{{ newItems }}" wx:key="index">
|
||||
<l-button plain="{{true}}" shape="{{ item.buttonProps.shape || 'square' }}" catchtap="handleClick" data-item="{{ item }}">
|
||||
{{item.text}}
|
||||
</l-button>
|
||||
</block>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
|
||||
|
||||
|
||||
<l-dialog id="my-action-btn-dialog" bind:linconfirm="linconfirm" bind:lincancel="lincancel" />
|
||||
|
||||
</block>
|
||||
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
|
||||
|
||||
.panelContainer {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px 5px;
|
||||
}
|
||||
|
||||
.more {
|
||||
white-space: nowrap;
|
||||
font-size: 14px;
|
||||
color: #000;
|
||||
padding: 5px;
|
||||
}
|
||||
|
|
@ -0,0 +1,253 @@
|
|||
import React from 'react';
|
||||
import {
|
||||
Space,
|
||||
Button,
|
||||
Modal,
|
||||
ButtonProps,
|
||||
SpaceProps,
|
||||
Dropdown,
|
||||
Typography,
|
||||
} from 'antd';
|
||||
import { WebComponentProps } from '../../types/Page';
|
||||
import { EntityDict } from 'oak-domain/lib/types/Entity';
|
||||
import Style from './web.module.less';
|
||||
const { confirm } = Modal;
|
||||
|
||||
type Item = {
|
||||
icon?: string | React.ReactNode;
|
||||
label?: string;
|
||||
action?: string;
|
||||
auth: boolean;
|
||||
type?: 'a' | 'button';
|
||||
index?: number;
|
||||
alerted?: boolean;
|
||||
alertTitle?: string;
|
||||
alertContent?: string;
|
||||
confirmText?: string;
|
||||
cancelText?: string;
|
||||
render?: React.ReactNode;
|
||||
beforeAction?: (item: Item) => boolean | Promise<boolean>;
|
||||
afterAction?: (item: Item) => void;
|
||||
onClick?: (item: Item) => void | Promise<void>;
|
||||
buttonProps?: Omit<ButtonProps, 'onClick'>;
|
||||
filter?: () => boolean;
|
||||
};
|
||||
|
||||
function ItemComponent(
|
||||
props: Item & {
|
||||
onClick: () => void | Promise<void>;
|
||||
text: string;
|
||||
}
|
||||
) {
|
||||
const { type, buttonProps, render, onClick, text } = props;
|
||||
|
||||
if (type === 'button') {
|
||||
return (
|
||||
<Button {...buttonProps} onClick={onClick}>
|
||||
{text}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
if (render) {
|
||||
return <div onClick={onClick}>{render}</div>;
|
||||
}
|
||||
return <a onClick={onClick}>{text}</a>;
|
||||
}
|
||||
|
||||
export default function Render(
|
||||
props: WebComponentProps<
|
||||
EntityDict,
|
||||
keyof EntityDict,
|
||||
false,
|
||||
{
|
||||
entity: string;
|
||||
actions: string[];
|
||||
items: Item[];
|
||||
spaceProps: SpaceProps;
|
||||
mode: 'cell' | 'table-cell';
|
||||
column: 3;
|
||||
},
|
||||
{
|
||||
getActionName: (action?: string) => string;
|
||||
}
|
||||
>
|
||||
) {
|
||||
const { methods, data } = props;
|
||||
const { t, getActionName } = methods;
|
||||
const {
|
||||
items,
|
||||
oakLegalActions,
|
||||
spaceProps,
|
||||
entity,
|
||||
mode = 'cell',
|
||||
column,
|
||||
} = data;
|
||||
|
||||
const getItems = () => {
|
||||
const items2 = items
|
||||
.filter((ele) => {
|
||||
const { action, filter } = ele;
|
||||
const authResult =
|
||||
!action ||
|
||||
(action &&
|
||||
oakLegalActions?.includes(
|
||||
action as EntityDict[keyof EntityDict]['Action']
|
||||
));
|
||||
const filterResult =
|
||||
ele.hasOwnProperty('filter') && filter ? filter() : true;
|
||||
return authResult && filterResult;
|
||||
})
|
||||
.map((ele, index: number) => {
|
||||
const { label, action } = ele;
|
||||
let text: string = '';
|
||||
if (label) {
|
||||
text = label;
|
||||
} else {
|
||||
text = getActionName(action);
|
||||
}
|
||||
let onClick = async () => {
|
||||
if (ele.onClick) {
|
||||
ele.onClick(ele);
|
||||
return;
|
||||
}
|
||||
if (ele.beforeAction) {
|
||||
const r = await ele.beforeAction(ele);
|
||||
if (!r) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
await methods.execute(
|
||||
action as EntityDict[keyof EntityDict]['Action']
|
||||
);
|
||||
|
||||
if (ele.afterAction) {
|
||||
ele.afterAction(ele);
|
||||
}
|
||||
};
|
||||
if (ele.alerted) {
|
||||
onClick = async () => {
|
||||
let content = '';
|
||||
if (action) {
|
||||
const text = getActionName(action);
|
||||
content = `确认${text}该数据`;
|
||||
}
|
||||
confirm({
|
||||
title: ele.alertTitle,
|
||||
content: ele.alertContent || content,
|
||||
okText: ele.confirmText || '确定',
|
||||
cancelText: ele.cancelText || '取消',
|
||||
onOk: async () => {
|
||||
if (ele.onClick) {
|
||||
ele.onClick(ele);
|
||||
return;
|
||||
}
|
||||
if (ele.beforeAction) {
|
||||
const r = await ele.beforeAction(ele);
|
||||
if (!r) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
await methods.execute(
|
||||
ele.action as EntityDict[keyof EntityDict]['Action']
|
||||
);
|
||||
if (ele.afterAction) {
|
||||
ele.afterAction(ele);
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
||||
return Object.assign(ele, {
|
||||
text: text,
|
||||
onClick2: onClick,
|
||||
});
|
||||
});
|
||||
|
||||
let newItems = items2;
|
||||
let moreItems: Item[] = [];
|
||||
if (column && items2.length > column) {
|
||||
newItems = [...items2].splice(0, column);
|
||||
moreItems = [...items2].splice(column, items2.length);
|
||||
}
|
||||
|
||||
return {
|
||||
newItems,
|
||||
moreItems,
|
||||
};
|
||||
};
|
||||
|
||||
const { newItems, moreItems } = getItems();
|
||||
|
||||
if (!newItems || newItems.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (mode === 'table-cell') {
|
||||
return (
|
||||
<Space {...spaceProps}>
|
||||
{newItems?.map((ele, index: number) => {
|
||||
return (
|
||||
<ItemComponent
|
||||
{...ele}
|
||||
onClick={ele.onClick2}
|
||||
text={ele.text}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
{moreItems && moreItems.length > 0 && (
|
||||
<Dropdown
|
||||
menu={{
|
||||
items: moreItems.map((ele: any, index) => ({
|
||||
label: ele.text as string,
|
||||
key: index,
|
||||
})),
|
||||
onClick: (e: any) => {
|
||||
const item = moreItems[e.key] as any;
|
||||
item.onClick2();
|
||||
},
|
||||
}}
|
||||
placement="top"
|
||||
arrow
|
||||
>
|
||||
<a onClick={(e) => e.preventDefault()}>更多</a>
|
||||
</Dropdown>
|
||||
)}
|
||||
</Space>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={Style.panelContainer}>
|
||||
{moreItems && moreItems.length > 0 && (
|
||||
<Dropdown
|
||||
menu={{
|
||||
items: moreItems.map((ele: any, index) => ({
|
||||
label: ele.text,
|
||||
key: index,
|
||||
})),
|
||||
onClick: (e: any) => {
|
||||
const item = moreItems[e.key] as any;
|
||||
item.onClick2();
|
||||
},
|
||||
}}
|
||||
arrow
|
||||
>
|
||||
<Typography className={Style.more}>更多</Typography>
|
||||
</Dropdown>
|
||||
)}
|
||||
<Space {...spaceProps}>
|
||||
{newItems?.map((ele, index: number) => {
|
||||
return (
|
||||
<ItemComponent
|
||||
type="button"
|
||||
{...ele}
|
||||
onClick={ele.onClick2}
|
||||
text={ele.text}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Space>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,151 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Table, Tag, TableProps } from 'antd';
|
||||
import type { ColumnsType, ColumnType, ColumnGroupType } from 'antd/es/table';
|
||||
import assert from 'assert';
|
||||
import {useFeatures} from '../../platforms/web/features/index';
|
||||
import { getAttributes } from '../../utils/usefulFn';
|
||||
import { get } from 'oak-domain/lib/utils/lodash';
|
||||
import dayjs from 'dayjs';
|
||||
import { ActionBtnPanelProps } from '@oak-general-business/types/actionBtnPanel';
|
||||
import ActionBtnPanel from '@oak-general-business/components/func/actionBtnPanel';
|
||||
|
||||
type SelfColumn = {
|
||||
path?: string;
|
||||
}
|
||||
|
||||
type Column = SelfColumn & ColumnType<any>;
|
||||
|
||||
|
||||
type Props = {
|
||||
entity: string;
|
||||
data: any[];
|
||||
columns: (Column | string)[];
|
||||
actionBtnProps?: (row: any) => ActionBtnPanelProps;
|
||||
tableProps?: TableProps<any>;
|
||||
}
|
||||
|
||||
type RenderCellProps = {
|
||||
content: any;
|
||||
entity: string;
|
||||
path: string;
|
||||
attr: string;
|
||||
attrType: string;
|
||||
}
|
||||
|
||||
function decodeTitle(entity: string, attr: string) {
|
||||
const { t } = useTranslation();
|
||||
if (attr === ('$$createAt$$' || '$$updateAt$$')) {
|
||||
return t(`common:${attr}`)
|
||||
}
|
||||
return t(`${entity}:attr.${attr}`)
|
||||
}
|
||||
|
||||
// 解析路径, 获取属性类型、属性值、以及实体名称
|
||||
function Fn(entity: string, path: string) {
|
||||
let _entity = entity;
|
||||
let attr: string;
|
||||
assert(!path.includes('['), '数组索引不需要携带[],请使用arr.0.value')
|
||||
const features = useFeatures();
|
||||
const dataSchema = features.cache.getSchema();
|
||||
if (!path.includes('.')) {
|
||||
attr = path;
|
||||
}
|
||||
else {
|
||||
const strs = path.split('.');
|
||||
// 最后一个肯定是属性
|
||||
attr = strs.pop()!;
|
||||
// 倒数第二个可能是类名可能是索引
|
||||
_entity = strs.pop()!;
|
||||
// 判断是否是数组索引
|
||||
if (!Number.isNaN(Number(_entity))) {
|
||||
_entity = strs.pop()!.split('$')[0];
|
||||
}
|
||||
}
|
||||
const attributes = getAttributes(dataSchema[_entity as keyof typeof dataSchema].attributes);
|
||||
const attribute = attributes[attr];
|
||||
return {
|
||||
entity: _entity,
|
||||
attr,
|
||||
attribute,
|
||||
}
|
||||
}
|
||||
|
||||
function RenderCell(props: RenderCellProps) {
|
||||
const { content, entity, path, attr, attrType } = props;
|
||||
const value = get(content, path);
|
||||
const { t } = useTranslation();
|
||||
const feature = useFeatures();
|
||||
const colorDict = feature.style.getColorDict();
|
||||
if (!value) {
|
||||
return (<div>--</div>);
|
||||
}
|
||||
// 属性类型是enum要使用标签
|
||||
else if (attrType === 'enum') {
|
||||
return (
|
||||
<Tag color={colorDict[entity][attr][String(value)]} >
|
||||
{t(`${entity}:v.${attr}.${value}`)}
|
||||
</Tag>
|
||||
)
|
||||
}
|
||||
else if (attrType === 'datetime') {
|
||||
return <div>{dayjs(value).format('YYYY-MM-DD HH:mm')}</div>
|
||||
}
|
||||
return (
|
||||
<div>{value}</div>
|
||||
)
|
||||
}
|
||||
|
||||
function List(props: Props) {
|
||||
const { data, columns, entity, actionBtnProps, tableProps } = props;
|
||||
const { t } = useTranslation();
|
||||
const tableColumns: ColumnsType<any> = columns.map((ele) => {
|
||||
let title: string = '';
|
||||
let render: (value: any, row: any) => React.ReactNode = () => <></>;
|
||||
let path: string | undefined;
|
||||
if (typeof ele === 'string') {
|
||||
path = ele;
|
||||
}
|
||||
else {
|
||||
path = ele.path;
|
||||
}
|
||||
const { entity: useEntity, attr, attribute } = Fn(entity, path!) || {};
|
||||
title = decodeTitle(useEntity, attr);
|
||||
render = (value, row) => (
|
||||
<RenderCell entity={entity} content={row} path={path!} attr={attr} attrType={attribute.type} />
|
||||
);
|
||||
const column = {
|
||||
align: 'center',
|
||||
title,
|
||||
dataIndex: typeof ele === 'string' ? ele : ele.dataIndex,
|
||||
render,
|
||||
};
|
||||
// 类型如果是枚举类型,那么它的宽度一般不超过160
|
||||
// if (attribute?.type === 'enum') {
|
||||
// Object.assign(column, {width: 160})
|
||||
// }
|
||||
return Object.assign(column, typeof ele !== 'string' && ele);
|
||||
}) as ColumnsType<any>;
|
||||
if (tableColumns && tableColumns) {
|
||||
tableColumns.unshift({title: '序号', width: 100, render(value, record, index) {
|
||||
return index + 1;
|
||||
},})
|
||||
}
|
||||
if (actionBtnProps) {
|
||||
tableColumns.push({
|
||||
fixed: 'right',
|
||||
align: 'right',
|
||||
title: '操作',
|
||||
key: 'operation',
|
||||
width: 100,
|
||||
render: (value, row) => (
|
||||
<ActionBtnPanel {...actionBtnProps(row)} />
|
||||
)
|
||||
})
|
||||
}
|
||||
return (
|
||||
<Table dataSource={data} scroll={{ x: 2200 }} columns={tableColumns} ></Table>
|
||||
);
|
||||
}
|
||||
|
||||
export default List;
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
|
||||
export function getAttributes(attributes: Record<string, any>) {
|
||||
return Object.assign({}, attributes, {
|
||||
id: {
|
||||
type: 'char',
|
||||
},
|
||||
$$createAt$$: {
|
||||
type: 'datetime',
|
||||
},
|
||||
$$updateAt$$: {
|
||||
type: 'datetime',
|
||||
},
|
||||
$$deleteAt$$: {
|
||||
type: 'datetime',
|
||||
},
|
||||
$$seq$$: {
|
||||
type: 'datetime',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
import {
|
||||
MakeOakComponent,
|
||||
} from '../src/types/Page';
|
||||
import { EntityDict as BaseEntityDict } from 'oak-domain/lib/base-app-domain';
|
||||
import { EntityDict, Aspect } from 'oak-domain/lib/types';
|
||||
import { Feature } from '../src/types/Feature';
|
||||
import { AsyncContext } from "oak-domain/lib/store/AsyncRowStore";
|
||||
import { SyncContext } from "oak-domain/lib/store/SyncRowStore";
|
||||
type ED = EntityDict & BaseEntityDict;
|
||||
type Cxt = AsyncContext<ED>;
|
||||
type FrontCxt = SyncContext<ED>;
|
||||
type AD = Record<string, Aspect<ED, Cxt>>;
|
||||
type FD = Record<string, Feature>
|
||||
declare global {
|
||||
const OakComponent: MakeOakComponent<
|
||||
ED,
|
||||
Cxt,
|
||||
FrontCxt,
|
||||
AD,
|
||||
FD
|
||||
>;
|
||||
}
|
||||
export {};
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
/// <reference types="react-scripts" />
|
||||
|
||||
declare module '*.module.less' {
|
||||
const classes: {
|
||||
readonly [key: string]: string;
|
||||
};
|
||||
export default classes;
|
||||
}
|
||||
Loading…
Reference in New Issue