feature/核算表格双击编辑
This commit is contained in:
parent
ed8f9a056b
commit
9e3e4e6546
|
|
@ -54,6 +54,12 @@
|
|||
<div class="content unicode" style="display: block;">
|
||||
<ul class="icon_lists dib-box">
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">双击</div>
|
||||
<div class="code-name">&#xe6f0;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">export</div>
|
||||
|
|
@ -204,9 +210,9 @@
|
|||
<pre><code class="language-css"
|
||||
>@font-face {
|
||||
font-family: 'iconfont';
|
||||
src: url('iconfont.woff2?t=1741245710478') format('woff2'),
|
||||
url('iconfont.woff?t=1741245710478') format('woff'),
|
||||
url('iconfont.ttf?t=1741245710478') format('truetype');
|
||||
src: url('iconfont.woff2?t=1742897212506') format('woff2'),
|
||||
url('iconfont.woff?t=1742897212506') format('woff'),
|
||||
url('iconfont.ttf?t=1742897212506') format('truetype');
|
||||
}
|
||||
</code></pre>
|
||||
<h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3>
|
||||
|
|
@ -232,6 +238,15 @@
|
|||
<div class="content font-class">
|
||||
<ul class="icon_lists dib-box">
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont icon-shuangji"></span>
|
||||
<div class="name">
|
||||
双击
|
||||
</div>
|
||||
<div class="code-name">.icon-shuangji
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont icon-export"></span>
|
||||
<div class="name">
|
||||
|
|
@ -457,6 +472,14 @@
|
|||
<div class="content symbol">
|
||||
<ul class="icon_lists dib-box">
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#icon-shuangji"></use>
|
||||
</svg>
|
||||
<div class="name">双击</div>
|
||||
<div class="code-name">#icon-shuangji</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#icon-export"></use>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
@font-face {
|
||||
font-family: "iconfont"; /* Project id 4257468 */
|
||||
src: url('iconfont.woff2?t=1741245710478') format('woff2'),
|
||||
url('iconfont.woff?t=1741245710478') format('woff'),
|
||||
url('iconfont.ttf?t=1741245710478') format('truetype');
|
||||
src: url('iconfont.woff2?t=1742897212506') format('woff2'),
|
||||
url('iconfont.woff?t=1742897212506') format('woff'),
|
||||
url('iconfont.ttf?t=1742897212506') format('truetype');
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
|
|
@ -13,6 +13,10 @@
|
|||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.icon-shuangji:before {
|
||||
content: "\e6f0";
|
||||
}
|
||||
|
||||
.icon-export:before {
|
||||
content: "\e61e";
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -5,6 +5,13 @@
|
|||
"css_prefix_text": "icon-",
|
||||
"description": "",
|
||||
"glyphs": [
|
||||
{
|
||||
"icon_id": "39084114",
|
||||
"name": "双击",
|
||||
"font_class": "shuangji",
|
||||
"unicode": "e6f0",
|
||||
"unicode_decimal": 59120
|
||||
},
|
||||
{
|
||||
"icon_id": "10124054",
|
||||
"name": "export",
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -268,16 +268,16 @@
|
|||
background-color: #f7fbfe;
|
||||
}
|
||||
|
||||
.ant-form-item {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.ant-input-number {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
th, td {
|
||||
font-size: var(--data-size);
|
||||
|
||||
.ant-form-item {
|
||||
margin-bottom: 0;
|
||||
|
||||
.ant-input-number {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ant-typography {
|
||||
|
|
|
|||
|
|
@ -11,8 +11,7 @@ type Props = OwnProps;
|
|||
|
||||
const CalcExplainFooter: FunctionComponent<Props> = (props) => {
|
||||
const { i18n } = props;
|
||||
return (
|
||||
<div className={styles["explain-icon-area"]}>
|
||||
return (<div className={styles["explain-icon-area"]}>
|
||||
<div className={styles["icon-item"]}>
|
||||
<Icon type="icon-piliangsuoding" />
|
||||
<span>{i18n["批量锁定"]}</span>
|
||||
|
|
@ -29,18 +28,19 @@ const CalcExplainFooter: FunctionComponent<Props> = (props) => {
|
|||
<UnlockOutlined />
|
||||
<span>{i18n["当前状态未锁定,右击锁定"]}</span>
|
||||
</div>
|
||||
{(i18n["反馈信息,右击删除反馈信息"] || i18n["反馈信息,右击设置反馈信息"]) && (
|
||||
<div className={styles["icon-item"]}>
|
||||
{(i18n["反馈信息,右击删除反馈信息"] || i18n["反馈信息,右击设置反馈信息"]) && (<div className={styles["icon-item"]}>
|
||||
<QuestionCircleOutlined />
|
||||
<span>{i18n["反馈信息,右击删除反馈信息"] || i18n["反馈信息,右击设置反馈信息"]}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>)}
|
||||
<div className={styles["icon-item"]}>
|
||||
<Icon type="icon-pilianggengxin" />
|
||||
<span>{i18n["批量更新"]}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
<div className={styles["icon-item"]}>
|
||||
<Icon type="icon-shuangji" />
|
||||
<span>{i18n["双击单元格更新"]}</span>
|
||||
</div>
|
||||
</div>);
|
||||
};
|
||||
|
||||
export default CalcExplainFooter;
|
||||
|
|
|
|||
|
|
@ -4,26 +4,39 @@
|
|||
* Description:
|
||||
* Date: 2023/9/14
|
||||
*/
|
||||
import React, { FunctionComponent, useEffect, useState } from "react";
|
||||
import type { MenuProps } from "antd";
|
||||
import { Button, Dropdown, Space, Table, Tooltip, Typography } from "antd";
|
||||
import React, { FunctionComponent, useContext, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { Button, Dropdown, Form, Input, InputNumber, MenuProps, Space, Table, Tooltip, Typography } from "antd";
|
||||
import { DeleteOutlined, LockOutlined, QuestionCircleOutlined, SettingOutlined, UnlockOutlined } from "@ant-design/icons";
|
||||
import type { FormInstance } from "antd/es/form";
|
||||
import CustomTableTitle from "@/pages/calcTable/customTableTitle";
|
||||
import CalcExplainFooter from "@/pages/calcTable/calcExplainFooter";
|
||||
import CaclFixedTotal from "./calcFixedTotal";
|
||||
import type { ColumnType } from "antd/lib/table";
|
||||
import type { PaginationData } from "rc-pagination";
|
||||
import { exceptStr, paginationFun } from "@/utils/common";
|
||||
import { exceptStr, paginationFun, toDecimal_n } from "@/utils/common";
|
||||
import { IPage } from "@/common/types";
|
||||
import styles from "@/pages/atdTable/components/index.less";
|
||||
|
||||
interface OwnProps {}
|
||||
|
||||
interface EditableRowProps {}
|
||||
|
||||
interface EditableCellProps {
|
||||
editCell?: any;
|
||||
children?: React.ReactNode;
|
||||
record?: any;
|
||||
pattern?: number;
|
||||
rowIndex?: number;
|
||||
dataIndex?: any;
|
||||
handleSave?: (type: string, record: any) => void;
|
||||
}
|
||||
|
||||
type Props = OwnProps;
|
||||
type fixedProps = boolean | "top" | "bottom";
|
||||
const { Text } = Typography;
|
||||
|
||||
const index: FunctionComponent<Props> = (props) => {
|
||||
const EditableContext = React.createContext<FormInstance<any> | null>(null);
|
||||
const Index: FunctionComponent<Props> = (props) => {
|
||||
const [columns, setColumns] = useState<ColumnType<any>[]>([]);
|
||||
const [dataSource, setDataSource] = useState<any[]>([]);
|
||||
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
|
||||
|
|
@ -36,7 +49,10 @@ const index: FunctionComponent<Props> = (props) => {
|
|||
const [tableScrollHeight, setTableScrollHeight] = useState<Number>(0);
|
||||
const [payload, setPayload] = useState<string>("");
|
||||
const [fixed, setFixed] = useState<fixedProps>(true);
|
||||
const [optWidth, setOptWidth] = useState<number>(120); //操作列宽度
|
||||
const [sumRow, setSumRow] = useState<Partial<{}>>({}); //总计行数据
|
||||
const [editCell, setEditCell] = useState<any>({}); //编辑行key
|
||||
const [form] = Form.useForm();
|
||||
|
||||
useEffect(() => {
|
||||
window.parent.postMessage({ type: "init" }, "*");
|
||||
|
|
@ -74,51 +90,11 @@ const index: FunctionComponent<Props> = (props) => {
|
|||
setSumRow(sumRow);
|
||||
setPageInfo(pageInfo);
|
||||
setDataSource(dataSource);
|
||||
setOptWidth(optWidth);
|
||||
setSelectedRowKeys([...selectedRowKeys, ...rowKeys]);
|
||||
setTableScrollHeight(tableScrollHeight);
|
||||
setColumns([
|
||||
...convertColumns(_.map(columns, (o) => ({ ...o, i18n: i18nRes }))),
|
||||
{
|
||||
title: i18nRes["操作"],
|
||||
dataIndex: "operate",
|
||||
fixed: "right",
|
||||
width: optWidth || 120,
|
||||
render: (__, record) => (
|
||||
<Space>
|
||||
<Button type="link" onClick={() => handleEdit(record?.id, showSee)}>
|
||||
{showSee ? i18nRes["查看"] : i18nRes["编辑"]}
|
||||
</Button>
|
||||
{optWidth && (
|
||||
<>
|
||||
<Button
|
||||
type="link"
|
||||
onClick={() =>
|
||||
handleLockEmp({
|
||||
lockStatus: "LOCK",
|
||||
acctEmpIds: [record?.id]
|
||||
})
|
||||
}
|
||||
>
|
||||
{i18nRes["锁定"]}
|
||||
</Button>
|
||||
<Button
|
||||
type="link"
|
||||
onClick={() =>
|
||||
handleLockEmp({
|
||||
lockStatus: "UNLOCK",
|
||||
acctEmpIds: [record?.id]
|
||||
})
|
||||
}
|
||||
>
|
||||
{i18nRes["解锁"]}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
{record?.lockTime && <Text>{record?.lockTime}</Text>}
|
||||
</Space>
|
||||
)
|
||||
}
|
||||
]);
|
||||
setColumns(_.map(columns, (o) => ({ ...o, i18n: i18nRes })));
|
||||
setEditCell({});
|
||||
}
|
||||
};
|
||||
const convertColumns: any = (cols: any[]) => {
|
||||
|
|
@ -132,11 +108,21 @@ const index: FunctionComponent<Props> = (props) => {
|
|||
children: convertColumns(_.map(item.children, (o) => ({ ...o, i18n: item.i18n }))),
|
||||
className: styles["td_odd"],
|
||||
i18n: item.i18n,
|
||||
onCell: (record: any) => ({
|
||||
onCell: (record: any, rowIndex: number) => ({
|
||||
className: !_.isEmpty(record[`${item.dataIndex}_feedback`]) && styles["feedbackBg"],
|
||||
editCell,
|
||||
record,
|
||||
rowIndex,
|
||||
pattern: item.pattern,
|
||||
dataIndex: item.dataIndex,
|
||||
onContextMenu: (e: any) => {
|
||||
(!item.calcDetail || item.rightClickType) && e.preventDefault();
|
||||
}
|
||||
},
|
||||
onDoubleClick: (e: any) => {
|
||||
e.preventDefault();
|
||||
!item.calcDetail || item.rightClickType ? setEditCell({ dataIndex: item?.dataIndex, rowIndex }) : null;
|
||||
},
|
||||
handleSave
|
||||
}),
|
||||
render: (text: string, record: any) => {
|
||||
let items: MenuProps["items"] = !item.calcDetail
|
||||
|
|
@ -215,6 +201,10 @@ const index: FunctionComponent<Props> = (props) => {
|
|||
}
|
||||
});
|
||||
};
|
||||
const handleSave = (salaryItemId: string, row: any) => {
|
||||
const params = { salaryItemId, idList: [row?.id], value: row[salaryItemId] };
|
||||
window.parent.postMessage({ type: "turn", payload: { id: "EDITCELL", params } }, "*");
|
||||
};
|
||||
const handleFormulaTd = (dataIndex: string) => {
|
||||
window.parent.postMessage({ type: "turn", payload: { id: "FORMULA", params: { dataIndex } } }, "*");
|
||||
};
|
||||
|
|
@ -240,7 +230,7 @@ const index: FunctionComponent<Props> = (props) => {
|
|||
};
|
||||
const rowSelection = {
|
||||
columnWidth: 50,
|
||||
columnTitle: isDetailTable ? "序号" : "",
|
||||
columnTitle: isDetailTable ? "序号" : "",
|
||||
renderCell: (value: boolean, record: any, index: number, originNode: React.ReactNode) => (isDetailTable ? <span>{index + 1}</span> : originNode),
|
||||
selectedRowKeys,
|
||||
preserveSelectedRowKeys: true,
|
||||
|
|
@ -255,44 +245,149 @@ const index: FunctionComponent<Props> = (props) => {
|
|||
);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<Table
|
||||
rowKey="id"
|
||||
size="small"
|
||||
bordered
|
||||
className={styles.tableWrapper}
|
||||
dataSource={dataSource}
|
||||
rowSelection={rowSelection}
|
||||
scroll={{
|
||||
x: 1200,
|
||||
y: `calc(100vh - ${tableScrollHeight || (!showTotalCell ? 165 : 200)}px)`
|
||||
}}
|
||||
columns={!isDetailTable || showSee ? columns : _.filter(columns, (o) => o.dataIndex !== "operate")}
|
||||
footer={() => (!isDetailTable ? <CalcExplainFooter i18n={i18n} /> : null)}
|
||||
pagination={
|
||||
!_.isNil(pageInfo)
|
||||
? {
|
||||
...paginationFun(pageInfo, sizeChange, onChange, i18n),
|
||||
size: "default"
|
||||
}
|
||||
: false
|
||||
}
|
||||
summary={() =>
|
||||
!showTotalCell ? (
|
||||
<></>
|
||||
) : (
|
||||
<Table.Summary fixed={fixed}>
|
||||
<Table.Summary.Row>
|
||||
<Table.Summary.Cell index={0} align="center">
|
||||
<Text type="danger">{i18n["总计"]}</Text>
|
||||
</Table.Summary.Cell>
|
||||
<CaclFixedTotal columns={columns} dataSourceUrl={sumRowlistUrl} payload={payload} sumRow={sumRow} />
|
||||
</Table.Summary.Row>
|
||||
</Table.Summary>
|
||||
const cols = useMemo(() => {
|
||||
return [
|
||||
...convertColumns(columns),
|
||||
{
|
||||
title: i18n["操作"],
|
||||
dataIndex: "operate",
|
||||
fixed: "right",
|
||||
width: optWidth || 120,
|
||||
render: (__: any, record: any) => (
|
||||
<Space>
|
||||
<Button type="link" onClick={() => handleEdit(record?.id, showSee)}>
|
||||
{showSee ? i18n["查看"] : i18n["编辑"]}
|
||||
</Button>
|
||||
{optWidth && (
|
||||
<>
|
||||
<Button
|
||||
type="link"
|
||||
onClick={() =>
|
||||
handleLockEmp({
|
||||
lockStatus: "LOCK",
|
||||
acctEmpIds: [record?.id]
|
||||
})
|
||||
}
|
||||
>
|
||||
{i18n["锁定"]}
|
||||
</Button>
|
||||
<Button
|
||||
type="link"
|
||||
onClick={() =>
|
||||
handleLockEmp({
|
||||
lockStatus: "UNLOCK",
|
||||
acctEmpIds: [record?.id]
|
||||
})
|
||||
}
|
||||
>
|
||||
{i18n["解锁"]}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
{record?.lockTime && <Text>{record?.lockTime}</Text>}
|
||||
</Space>
|
||||
)
|
||||
}
|
||||
/>
|
||||
];
|
||||
}, [columns, editCell, i18n]);
|
||||
const components = {
|
||||
body: {
|
||||
row: EditableRow,
|
||||
cell: EditableCell
|
||||
}
|
||||
};
|
||||
return (
|
||||
<Form form={form} component={false}>
|
||||
<Table
|
||||
rowKey="id"
|
||||
size="small"
|
||||
bordered
|
||||
className={styles.tableWrapper}
|
||||
dataSource={dataSource}
|
||||
rowSelection={rowSelection}
|
||||
components={components}
|
||||
scroll={{
|
||||
x: 1200,
|
||||
y: `calc(100vh - ${tableScrollHeight || (!showTotalCell ? 165 : 200)}px)`
|
||||
}}
|
||||
columns={!isDetailTable || showSee ? cols : _.filter(cols, (o) => o.dataIndex !== "operate")}
|
||||
footer={() => (!isDetailTable ? <CalcExplainFooter i18n={i18n} /> : null)}
|
||||
pagination={
|
||||
!_.isNil(pageInfo)
|
||||
? {
|
||||
...paginationFun(pageInfo, sizeChange, onChange, i18n),
|
||||
size: "default"
|
||||
}
|
||||
: false
|
||||
}
|
||||
summary={() =>
|
||||
!showTotalCell ? (
|
||||
<></>
|
||||
) : (
|
||||
<Table.Summary fixed={fixed}>
|
||||
<Table.Summary.Row>
|
||||
<Table.Summary.Cell index={0} align="center">
|
||||
<Text type="danger">{i18n["总计"]}</Text>
|
||||
</Table.Summary.Cell>
|
||||
<CaclFixedTotal columns={cols} dataSourceUrl={sumRowlistUrl} payload={payload} sumRow={sumRow} />
|
||||
</Table.Summary.Row>
|
||||
</Table.Summary>
|
||||
)
|
||||
}
|
||||
/>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
export default index;
|
||||
export default Index;
|
||||
const EditableRow: React.FC<EditableRowProps> = (props) => {
|
||||
const [form] = Form.useForm();
|
||||
return (
|
||||
<Form form={form} component={false}>
|
||||
<EditableContext.Provider value={form}>
|
||||
<tr {...props} />
|
||||
</EditableContext.Provider>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
const EditableCell: React.FC<EditableCellProps> = (props) => {
|
||||
const { children, editCell, record, dataIndex, rowIndex, handleSave, ...restProps } = props;
|
||||
const form = useContext(EditableContext)!;
|
||||
const inputRef = useRef<any>(null);
|
||||
useEffect(() => {
|
||||
if (dataIndex && editCell && editCell.dataIndex === dataIndex && editCell.rowIndex === rowIndex) {
|
||||
toggleEdit();
|
||||
inputRef.current!.focus();
|
||||
}
|
||||
}, [editCell, dataIndex, rowIndex]);
|
||||
const toggleEdit = () => {
|
||||
form.setFieldsValue({ [dataIndex]: record[dataIndex] });
|
||||
};
|
||||
const onSave = _.debounce(async (e) => {
|
||||
try {
|
||||
let values = await form.validateFields();
|
||||
if (record[`${_.keys(values)[0]}_type`] === "number") {
|
||||
values = {
|
||||
...values,
|
||||
[_.keys(values)[0]]: !!_.get(values, _.keys(values)[0]) ? toDecimal_n(_.get(values, _.keys(values)[0]), restProps?.pattern) : ""
|
||||
};
|
||||
}
|
||||
handleSave?.(_.keys(values)[0], { ...record, ...values });
|
||||
} catch (errInfo) {
|
||||
console.log("Save failed:", errInfo);
|
||||
}
|
||||
}, 200);
|
||||
let childNode =
|
||||
dataIndex && editCell && editCell.dataIndex === dataIndex && editCell.rowIndex === rowIndex ? (
|
||||
<Form.Item style={{ margin: 0 }} name={dataIndex}>
|
||||
{record[`${dataIndex}_type`] === "number" ? (
|
||||
<InputNumber ref={inputRef} onPressEnter={onSave} onBlur={onSave} precision={restProps?.pattern} />
|
||||
) : (
|
||||
<Input value={record[dataIndex]} onPressEnter={onSave} onBlur={onSave} ref={inputRef} />
|
||||
)}
|
||||
</Form.Item>
|
||||
) : (
|
||||
<>{children}</>
|
||||
);
|
||||
return <td {...restProps}>{childNode}</td>;
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue