refactor: excel 部分重构,优化弹窗体验 (#598)
Reviewed-on: daoyoucloud/tachycode#598 Co-authored-by: bai.jingfeng <bai.jingfeng@foxmail.com> Co-committed-by: bai.jingfeng <bai.jingfeng@foxmail.com>
This commit is contained in:
parent
bcec8a2414
commit
a87d95210c
@ -1,53 +0,0 @@
|
|||||||
import React, { forwardRef, useEffect, useImperativeHandle, useRef } from 'react';
|
|
||||||
import Spreadsheet from './x-sheet';
|
|
||||||
import { css } from '@nocobase/client';
|
|
||||||
import { demoData } from './x-sheet/demo';
|
|
||||||
|
|
||||||
export type SheetRef = {
|
|
||||||
getData: () => any;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type SheetProps = {
|
|
||||||
data?: any;
|
|
||||||
style?: React.CSSProperties;
|
|
||||||
};
|
|
||||||
|
|
||||||
const Sheet = forwardRef<SheetRef, SheetProps>(({ data }, ref) => {
|
|
||||||
const workbookRef = useRef<Spreadsheet>(null);
|
|
||||||
const containerRef = useRef(null);
|
|
||||||
|
|
||||||
useImperativeHandle(ref, () => ({
|
|
||||||
getData,
|
|
||||||
}));
|
|
||||||
|
|
||||||
const getData = () => {
|
|
||||||
if (!workbookRef.current) {
|
|
||||||
throw new Error('Workbook is not initialized');
|
|
||||||
}
|
|
||||||
return workbookRef.current.getData();
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const workbook = new Spreadsheet('#sheet', {
|
|
||||||
view: {
|
|
||||||
height: () => containerRef.current.offsetHeight,
|
|
||||||
width: () => containerRef.current.offsetWidth,
|
|
||||||
},
|
|
||||||
}).loadData(demoData);
|
|
||||||
workbookRef.current = workbook;
|
|
||||||
}, [data]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
ref={containerRef}
|
|
||||||
id="sheet"
|
|
||||||
className={css`
|
|
||||||
height: 800px;
|
|
||||||
`}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
Sheet.displayName = 'Sheet';
|
|
||||||
|
|
||||||
export default Sheet;
|
|
@ -0,0 +1,40 @@
|
|||||||
|
import { connect, mapProps } from '@nocobase/schema';
|
||||||
|
import { Button } from 'antd';
|
||||||
|
import React, { useRef, useState } from 'react';
|
||||||
|
import ModalFullScreen from '../modal-full-screen/ModalFullScreen';
|
||||||
|
import Sheet, { SheetRef } from './Sheet';
|
||||||
|
|
||||||
|
const ExcelFileComponent = (props) => {
|
||||||
|
const { title, disabled, value, onChange } = props;
|
||||||
|
const ref = useRef<SheetRef>();
|
||||||
|
const [isOpenModal, setIsOpenModal] = useState(false);
|
||||||
|
|
||||||
|
const showModal = () => {
|
||||||
|
setIsOpenModal(true);
|
||||||
|
};
|
||||||
|
const hideModal = () => {
|
||||||
|
setIsOpenModal(false);
|
||||||
|
};
|
||||||
|
const handleOk = () => {
|
||||||
|
const data = ref.current.getData();
|
||||||
|
onChange(data);
|
||||||
|
setIsOpenModal(false);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Button type="primary" onClick={showModal}>
|
||||||
|
{disabled ? '查看' : '编辑'}
|
||||||
|
</Button>
|
||||||
|
<ModalFullScreen title={title ?? 'Excel'} open={isOpenModal} onOk={handleOk} onCancel={hideModal}>
|
||||||
|
<Sheet ref={ref} data={value || []} />
|
||||||
|
</ModalFullScreen>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ExcelFile = connect(
|
||||||
|
ExcelFileComponent,
|
||||||
|
mapProps((props) => ({
|
||||||
|
...props,
|
||||||
|
})),
|
||||||
|
);
|
@ -0,0 +1,63 @@
|
|||||||
|
import React, { forwardRef, useEffect, useImperativeHandle, useRef } from 'react';
|
||||||
|
import Spreadsheet from '../x-sheet';
|
||||||
|
import { ClassNamesArg, css, cx } from '@nocobase/client';
|
||||||
|
|
||||||
|
export type SheetRef = {
|
||||||
|
getData: () => any;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type SheetProps = {
|
||||||
|
data?: any;
|
||||||
|
className?: ClassNamesArg;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ExcelSheet = forwardRef<SheetRef, SheetProps>(({ data, className }, ref) => {
|
||||||
|
const workbookRef = useRef<Spreadsheet>(null);
|
||||||
|
const containerRef = useRef(null);
|
||||||
|
|
||||||
|
const getData = () => {
|
||||||
|
if (!workbookRef.current) {
|
||||||
|
throw new Error('Workbook is not initialized');
|
||||||
|
}
|
||||||
|
return workbookRef.current.getData();
|
||||||
|
};
|
||||||
|
|
||||||
|
useImperativeHandle(ref, () => ({
|
||||||
|
getData,
|
||||||
|
}));
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (containerRef.current) {
|
||||||
|
const workbook = new Spreadsheet(containerRef.current, {
|
||||||
|
view: {
|
||||||
|
height: () => containerRef.current?.offsetHeight,
|
||||||
|
width: () => containerRef.current?.offsetWidth,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
workbook.loadData(data);
|
||||||
|
workbookRef.current = workbook;
|
||||||
|
return () => {
|
||||||
|
workbook.dispose();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}, [data]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
ref={containerRef}
|
||||||
|
id="sheet988"
|
||||||
|
className={cx(
|
||||||
|
css`
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
`,
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
ExcelSheet.displayName = 'ExcelSheet';
|
||||||
|
|
||||||
|
export default ExcelSheet;
|
@ -1,5 +1,5 @@
|
|||||||
export * from './PDFViewer';
|
export * from './PDFViewer';
|
||||||
export * from './Sheet';
|
export * from './excel-table/Sheet';
|
||||||
export * from './SignatureInput';
|
export * from './SignatureInput';
|
||||||
export * from './SignaturePad';
|
export * from './SignaturePad';
|
||||||
export * from './custom-components/CustomAssociatedField';
|
export * from './custom-components/CustomAssociatedField';
|
||||||
@ -11,3 +11,4 @@ export * from './fields/Expression';
|
|||||||
export * from './system/MobileLink';
|
export * from './system/MobileLink';
|
||||||
export * from './system/Notifications';
|
export * from './system/Notifications';
|
||||||
export * from './system/OnlineUserProvider';
|
export * from './system/OnlineUserProvider';
|
||||||
|
export * from './excel-table/ExcelFile';
|
||||||
|
@ -0,0 +1,46 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
|
import { cx, css, ClassNamesArg } from '@nocobase/client';
|
||||||
|
import { ModalHeader } from './ModalFullScreenChild';
|
||||||
|
|
||||||
|
interface ModalFullScreenProps {
|
||||||
|
className?: ClassNamesArg;
|
||||||
|
title?: string;
|
||||||
|
open?: boolean;
|
||||||
|
destroyOnClose?: boolean;
|
||||||
|
children?: any;
|
||||||
|
onOk?: () => void;
|
||||||
|
onCancel?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ModalFullScreen = (props: ModalFullScreenProps) => {
|
||||||
|
const { className, title, open, children, onOk, onCancel } = props;
|
||||||
|
if (!open) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return ReactDOM.createPortal(
|
||||||
|
<div
|
||||||
|
className={cx(
|
||||||
|
css`
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 1000;
|
||||||
|
display: ${open ? 'flex' : 'none'};
|
||||||
|
flex-direction: column;
|
||||||
|
`,
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<ModalHeader title={title} onOk={onOk} onCancel={onCancel} />
|
||||||
|
{children}
|
||||||
|
</div>,
|
||||||
|
document.body,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
ModalFullScreen.displayName = 'ModalFullScreen';
|
||||||
|
|
||||||
|
export default ModalFullScreen;
|
@ -0,0 +1,51 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { css } from '@nocobase/client';
|
||||||
|
import { Button } from 'antd';
|
||||||
|
|
||||||
|
export const ModalHeader = (props) => {
|
||||||
|
const { title, onOk, onCancel } = props;
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={css`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0 50px;
|
||||||
|
height: 50px;
|
||||||
|
background-color: #e9e9e9;
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={css`
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333333;
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
{title}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={css`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-around;
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
className={css`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-around;
|
||||||
|
margin-right: 20px;
|
||||||
|
`}
|
||||||
|
type="primary"
|
||||||
|
onClick={onOk}
|
||||||
|
>
|
||||||
|
确认
|
||||||
|
</Button>
|
||||||
|
<Button onClick={onCancel}>取消</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -1,11 +0,0 @@
|
|||||||
/* eslint no-bitwise: "off" */
|
|
||||||
/*
|
|
||||||
v: int value
|
|
||||||
digit: bit len of v
|
|
||||||
flag: true or false
|
|
||||||
*/
|
|
||||||
const bitmap = (v, digit, flag) => {
|
|
||||||
const b = 1 << digit;
|
|
||||||
return flag ? (v | b) : (v ^ b);
|
|
||||||
};
|
|
||||||
export default bitmap;
|
|
@ -0,0 +1,12 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param v int value
|
||||||
|
* @param digit bit len of v
|
||||||
|
* @param flag true or false
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
const bitmap = (v: number, digit: number, flag: boolean) => {
|
||||||
|
const b = 1 << digit;
|
||||||
|
return flag ? v | b : v ^ b;
|
||||||
|
};
|
||||||
|
export default bitmap;
|
@ -1,39 +0,0 @@
|
|||||||
// src: include chars: [0-9], +, -, *, /
|
|
||||||
// // 9+(3-1)*3+10/2 => 9 3 1-3*+ 10 2/+
|
|
||||||
const infix2suffix = (src) => {
|
|
||||||
const operatorStack = [];
|
|
||||||
const stack = [];
|
|
||||||
for (let i = 0; i < src.length; i += 1) {
|
|
||||||
const c = src.charAt(i);
|
|
||||||
if (c !== ' ') {
|
|
||||||
if (c >= '0' && c <= '9') {
|
|
||||||
stack.push(c);
|
|
||||||
} else if (c === ')') {
|
|
||||||
let c1 = operatorStack.pop();
|
|
||||||
while (c1 !== '(') {
|
|
||||||
stack.push(c1);
|
|
||||||
c1 = operatorStack.pop();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// priority: */ > +-
|
|
||||||
if (operatorStack.length > 0 && (c === '+' || c === '-')) {
|
|
||||||
const last = operatorStack[operatorStack.length - 1];
|
|
||||||
if (last === '*' || last === '/') {
|
|
||||||
while (operatorStack.length > 0) {
|
|
||||||
stack.push(operatorStack.pop());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
operatorStack.push(c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
while (operatorStack.length > 0) {
|
|
||||||
stack.push(operatorStack.pop());
|
|
||||||
}
|
|
||||||
return stack;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default {
|
|
||||||
infix2suffix,
|
|
||||||
};
|
|
@ -1,145 +1,37 @@
|
|||||||
/* global window */
|
import { DrawBox } from './DrawBox';
|
||||||
function dpr() {
|
import { npxLine } from './utils';
|
||||||
return window.devicePixelRatio || 1;
|
import { drawFontLine } from './utils';
|
||||||
|
import { npx } from './utils';
|
||||||
|
import { thinLineWidth } from './utils';
|
||||||
|
import { dpr } from './utils';
|
||||||
|
|
||||||
|
interface Attr {
|
||||||
|
align?: 'left' | 'center' | 'right';
|
||||||
|
valign?: 'top' | 'middle' | 'bottom';
|
||||||
|
color?: string;
|
||||||
|
strike?: boolean;
|
||||||
|
font?: {
|
||||||
|
name: string;
|
||||||
|
size: number;
|
||||||
|
bold: boolean;
|
||||||
|
italic: boolean;
|
||||||
|
};
|
||||||
|
underline?: unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
function thinLineWidth() {
|
export class Draw {
|
||||||
return dpr() - 0.5;
|
private ctx: CanvasRenderingContext2D;
|
||||||
}
|
constructor(
|
||||||
|
private el: HTMLCanvasElement,
|
||||||
function npx(px) {
|
width: number,
|
||||||
return parseInt(px * dpr(), 10);
|
height: number,
|
||||||
}
|
) {
|
||||||
|
|
||||||
function npxLine(px) {
|
|
||||||
const n = npx(px);
|
|
||||||
return n > 0 ? n - 0.5 : 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
class DrawBox {
|
|
||||||
constructor(x, y, w, h, padding = 0) {
|
|
||||||
this.x = x;
|
|
||||||
this.y = y;
|
|
||||||
this.width = w;
|
|
||||||
this.height = h;
|
|
||||||
this.padding = padding;
|
|
||||||
this.bgcolor = '#ffffff';
|
|
||||||
// border: [width, style, color]
|
|
||||||
this.borderTop = null;
|
|
||||||
this.borderRight = null;
|
|
||||||
this.borderBottom = null;
|
|
||||||
this.borderLeft = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
setBorders({
|
|
||||||
top, bottom, left, right,
|
|
||||||
}) {
|
|
||||||
if (top) this.borderTop = top;
|
|
||||||
if (right) this.borderRight = right;
|
|
||||||
if (bottom) this.borderBottom = bottom;
|
|
||||||
if (left) this.borderLeft = left;
|
|
||||||
}
|
|
||||||
|
|
||||||
innerWidth() {
|
|
||||||
return this.width - (this.padding * 2) - 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
innerHeight() {
|
|
||||||
return this.height - (this.padding * 2) - 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
textx(align) {
|
|
||||||
const { width, padding } = this;
|
|
||||||
let { x } = this;
|
|
||||||
if (align === 'left') {
|
|
||||||
x += padding;
|
|
||||||
} else if (align === 'center') {
|
|
||||||
x += width / 2;
|
|
||||||
} else if (align === 'right') {
|
|
||||||
x += width - padding;
|
|
||||||
}
|
|
||||||
return x;
|
|
||||||
}
|
|
||||||
|
|
||||||
texty(align, h) {
|
|
||||||
const { height, padding } = this;
|
|
||||||
let { y } = this;
|
|
||||||
if (align === 'top') {
|
|
||||||
y += padding;
|
|
||||||
} else if (align === 'middle') {
|
|
||||||
y += height / 2 - h / 2;
|
|
||||||
} else if (align === 'bottom') {
|
|
||||||
y += height - padding - h;
|
|
||||||
}
|
|
||||||
return y;
|
|
||||||
}
|
|
||||||
|
|
||||||
topxys() {
|
|
||||||
const { x, y, width } = this;
|
|
||||||
return [[x, y], [x + width, y]];
|
|
||||||
}
|
|
||||||
|
|
||||||
rightxys() {
|
|
||||||
const {
|
|
||||||
x, y, width, height,
|
|
||||||
} = this;
|
|
||||||
return [[x + width, y], [x + width, y + height]];
|
|
||||||
}
|
|
||||||
|
|
||||||
bottomxys() {
|
|
||||||
const {
|
|
||||||
x, y, width, height,
|
|
||||||
} = this;
|
|
||||||
return [[x, y + height], [x + width, y + height]];
|
|
||||||
}
|
|
||||||
|
|
||||||
leftxys() {
|
|
||||||
const {
|
|
||||||
x, y, height,
|
|
||||||
} = this;
|
|
||||||
return [[x, y], [x, y + height]];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function drawFontLine(type, tx, ty, align, valign, blheight, blwidth) {
|
|
||||||
const floffset = { x: 0, y: 0 };
|
|
||||||
if (type === 'underline') {
|
|
||||||
if (valign === 'bottom') {
|
|
||||||
floffset.y = 0;
|
|
||||||
} else if (valign === 'top') {
|
|
||||||
floffset.y = -(blheight + 2);
|
|
||||||
} else {
|
|
||||||
floffset.y = -blheight / 2;
|
|
||||||
}
|
|
||||||
} else if (type === 'strike') {
|
|
||||||
if (valign === 'bottom') {
|
|
||||||
floffset.y = blheight / 2;
|
|
||||||
} else if (valign === 'top') {
|
|
||||||
floffset.y = -((blheight / 2) + 2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (align === 'center') {
|
|
||||||
floffset.x = blwidth / 2;
|
|
||||||
} else if (align === 'right') {
|
|
||||||
floffset.x = blwidth;
|
|
||||||
}
|
|
||||||
this.line(
|
|
||||||
[tx - floffset.x, ty - floffset.y],
|
|
||||||
[tx - floffset.x + blwidth, ty - floffset.y],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
class Draw {
|
|
||||||
constructor(el, width, height) {
|
|
||||||
this.el = el;
|
|
||||||
this.ctx = el.getContext('2d');
|
this.ctx = el.getContext('2d');
|
||||||
this.resize(width, height);
|
this.resize(width, height);
|
||||||
this.ctx.scale(dpr(), dpr());
|
this.ctx.scale(dpr(), dpr());
|
||||||
}
|
}
|
||||||
|
|
||||||
resize(width, height) {
|
resize(width: number, height: number) {
|
||||||
// console.log('dpr:', dpr);
|
|
||||||
this.el.style.width = `${width}px`;
|
this.el.style.width = `${width}px`;
|
||||||
this.el.style.height = `${height}px`;
|
this.el.style.height = `${height}px`;
|
||||||
this.el.width = npx(width);
|
this.el.width = npx(width);
|
||||||
@ -152,7 +44,7 @@ class Draw {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
attr(options) {
|
attr(options: unknown) {
|
||||||
Object.assign(this.ctx, options);
|
Object.assign(this.ctx, options);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
@ -173,27 +65,27 @@ class Draw {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
translate(x, y) {
|
translate(x: number, y: number) {
|
||||||
this.ctx.translate(npx(x), npx(y));
|
this.ctx.translate(npx(x), npx(y));
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
scale(x, y) {
|
scale(x: number, y: number) {
|
||||||
this.ctx.scale(x, y);
|
this.ctx.scale(x, y);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
clearRect(x, y, w, h) {
|
clearRect(x: number, y: number, w: number, h: number) {
|
||||||
this.ctx.clearRect(x, y, w, h);
|
this.ctx.clearRect(x, y, w, h);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
fillRect(x, y, w, h) {
|
fillRect(x: number, y: number, w: number, h: number) {
|
||||||
this.ctx.fillRect(npx(x) - 0.5, npx(y) - 0.5, npx(w), npx(h));
|
this.ctx.fillRect(npx(x) - 0.5, npx(y) - 0.5, npx(w), npx(h));
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
fillText(text, x, y) {
|
fillText(text: string, x: number, y: number) {
|
||||||
this.ctx.fillText(text, npx(x), npx(y));
|
this.ctx.fillText(text, npx(x), npx(y));
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
@ -215,11 +107,9 @@ class Draw {
|
|||||||
}
|
}
|
||||||
textWrap: text wrapping
|
textWrap: text wrapping
|
||||||
*/
|
*/
|
||||||
text(mtxt, box, attr = {}, textWrap = true) {
|
text(mtxt: string, box: DrawBox, attr: Attr = {}, textWrap = true) {
|
||||||
const { ctx } = this;
|
const { ctx } = this;
|
||||||
const {
|
const { align, valign, font, color, strike, underline } = attr;
|
||||||
align, valign, font, color, strike, underline,
|
|
||||||
} = attr;
|
|
||||||
const tx = box.textx(align);
|
const tx = box.textx(align);
|
||||||
ctx.save();
|
ctx.save();
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
@ -269,9 +159,9 @@ class Draw {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
border(style, color) {
|
border(style: string, color: string) {
|
||||||
const { ctx } = this;
|
const { ctx } = this;
|
||||||
ctx.lineWidth = thinLineWidth;
|
ctx.lineWidth = thinLineWidth();
|
||||||
ctx.strokeStyle = color;
|
ctx.strokeStyle = color;
|
||||||
// console.log('style:', style);
|
// console.log('style:', style);
|
||||||
if (style === 'medium') {
|
if (style === 'medium') {
|
||||||
@ -303,16 +193,13 @@ class Draw {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
strokeBorders(box) {
|
strokeBorders(box: DrawBox) {
|
||||||
const { ctx } = this;
|
const { ctx } = this;
|
||||||
ctx.save();
|
ctx.save();
|
||||||
// border
|
// border
|
||||||
const {
|
const { borderTop, borderRight, borderBottom, borderLeft } = box;
|
||||||
borderTop, borderRight, borderBottom, borderLeft,
|
|
||||||
} = box;
|
|
||||||
if (borderTop) {
|
if (borderTop) {
|
||||||
this.border(...borderTop);
|
this.border(...borderTop);
|
||||||
// console.log('box.topxys:', box.topxys());
|
|
||||||
this.line(...box.topxys());
|
this.line(...box.topxys());
|
||||||
}
|
}
|
||||||
if (borderRight) {
|
if (borderRight) {
|
||||||
@ -332,9 +219,7 @@ class Draw {
|
|||||||
|
|
||||||
dropdown(box) {
|
dropdown(box) {
|
||||||
const { ctx } = this;
|
const { ctx } = this;
|
||||||
const {
|
const { x, y, width, height } = box;
|
||||||
x, y, width, height,
|
|
||||||
} = box;
|
|
||||||
const sx = x + width - 15;
|
const sx = x + width - 15;
|
||||||
const sy = y + height - 15;
|
const sy = y + height - 15;
|
||||||
ctx.save();
|
ctx.save();
|
||||||
@ -380,9 +265,7 @@ class Draw {
|
|||||||
|
|
||||||
rect(box, dtextcb) {
|
rect(box, dtextcb) {
|
||||||
const { ctx } = this;
|
const { ctx } = this;
|
||||||
const {
|
const { x, y, width, height, bgcolor } = box;
|
||||||
x, y, width, height, bgcolor,
|
|
||||||
} = box;
|
|
||||||
ctx.save();
|
ctx.save();
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.fillStyle = bgcolor || '#fff';
|
ctx.fillStyle = bgcolor || '#fff';
|
||||||
@ -393,11 +276,3 @@ class Draw {
|
|||||||
ctx.restore();
|
ctx.restore();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {};
|
|
||||||
export {
|
|
||||||
Draw,
|
|
||||||
DrawBox,
|
|
||||||
thinLineWidth,
|
|
||||||
npx,
|
|
||||||
};
|
|
@ -0,0 +1,100 @@
|
|||||||
|
type Border = [string, string];
|
||||||
|
|
||||||
|
export class DrawBox {
|
||||||
|
private width: number;
|
||||||
|
private height: number;
|
||||||
|
private bgcolor: string;
|
||||||
|
borderTop?: Border;
|
||||||
|
borderRight?: Border;
|
||||||
|
borderBottom?: Border;
|
||||||
|
borderLeft?: Border;
|
||||||
|
constructor(
|
||||||
|
private x: number,
|
||||||
|
private y: number,
|
||||||
|
w: number,
|
||||||
|
h: number,
|
||||||
|
private padding = 0,
|
||||||
|
) {
|
||||||
|
this.width = w;
|
||||||
|
this.height = h;
|
||||||
|
this.bgcolor = '#ffffff';
|
||||||
|
// border: [width, style, color]
|
||||||
|
this.borderTop = null;
|
||||||
|
this.borderRight = null;
|
||||||
|
this.borderBottom = null;
|
||||||
|
this.borderLeft = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
setBorders({ top, bottom, left, right }) {
|
||||||
|
if (top) this.borderTop = top;
|
||||||
|
if (right) this.borderRight = right;
|
||||||
|
if (bottom) this.borderBottom = bottom;
|
||||||
|
if (left) this.borderLeft = left;
|
||||||
|
}
|
||||||
|
|
||||||
|
innerWidth() {
|
||||||
|
return this.width - this.padding * 2 - 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
innerHeight() {
|
||||||
|
return this.height - this.padding * 2 - 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
textx(align) {
|
||||||
|
const { width, padding } = this;
|
||||||
|
let { x } = this;
|
||||||
|
if (align === 'left') {
|
||||||
|
x += padding;
|
||||||
|
} else if (align === 'center') {
|
||||||
|
x += width / 2;
|
||||||
|
} else if (align === 'right') {
|
||||||
|
x += width - padding;
|
||||||
|
}
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
texty(align, h) {
|
||||||
|
const { height, padding } = this;
|
||||||
|
let { y } = this;
|
||||||
|
if (align === 'top') {
|
||||||
|
y += padding;
|
||||||
|
} else if (align === 'middle') {
|
||||||
|
y += height / 2 - h / 2;
|
||||||
|
} else if (align === 'bottom') {
|
||||||
|
y += height - padding - h;
|
||||||
|
}
|
||||||
|
return y;
|
||||||
|
}
|
||||||
|
|
||||||
|
topxys() {
|
||||||
|
const { x, y, width } = this;
|
||||||
|
return [
|
||||||
|
[x, y],
|
||||||
|
[x + width, y],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
rightxys() {
|
||||||
|
const { x, y, width, height } = this;
|
||||||
|
return [
|
||||||
|
[x + width, y],
|
||||||
|
[x + width, y + height],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
bottomxys() {
|
||||||
|
const { x, y, width, height } = this;
|
||||||
|
return [
|
||||||
|
[x, y + height],
|
||||||
|
[x + width, y + height],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
leftxys() {
|
||||||
|
const { x, y, height } = this;
|
||||||
|
return [
|
||||||
|
[x, y],
|
||||||
|
[x, y + height],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
@ -1,92 +0,0 @@
|
|||||||
class Draw {
|
|
||||||
constructor(el) {
|
|
||||||
this.el = el;
|
|
||||||
this.ctx = el.getContext('2d');
|
|
||||||
}
|
|
||||||
|
|
||||||
clear() {
|
|
||||||
const { width, height } = this.el;
|
|
||||||
this.ctx.clearRect(0, 0, width, height);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
attr(m) {
|
|
||||||
Object.assign(this.ctx, m);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
save() {
|
|
||||||
this.ctx.save();
|
|
||||||
this.ctx.beginPath();
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
restore() {
|
|
||||||
this.ctx.restore();
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
beginPath() {
|
|
||||||
this.ctx.beginPath();
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
closePath() {
|
|
||||||
this.ctx.closePath();
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
measureText(text) {
|
|
||||||
return this.ctx.measureText(text);
|
|
||||||
}
|
|
||||||
|
|
||||||
rect(x, y, width, height) {
|
|
||||||
this.ctx.rect(x, y, width, height);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
scale(x, y) {
|
|
||||||
this.ctx.scale(x, y);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
rotate(angle) {
|
|
||||||
this.ctx.rotate(angle);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
translate(x, y) {
|
|
||||||
this.ctx.translate(x, y);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
transform(a, b, c, d, e) {
|
|
||||||
this.ctx.transform(a, b, c, d, e);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
fillRect(x, y, w, h) {
|
|
||||||
this.ctx.fillRect(x, y, w, h);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
strokeRect(x, y, w, h) {
|
|
||||||
this.ctx.strokeRect(x, y, w, h);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
fillText(text, x, y, maxWidth) {
|
|
||||||
this.ctx.fillText(text, x, y, maxWidth);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
strokeText(text, x, y, maxWidth) {
|
|
||||||
this.ctx.strokeText(text, x, y, maxWidth);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {};
|
|
||||||
export {
|
|
||||||
Draw,
|
|
||||||
};
|
|
@ -0,0 +1,3 @@
|
|||||||
|
export { DrawBox } from './DrawBox';
|
||||||
|
export { thinLineWidth, dpr, npx } from './utils';
|
||||||
|
export { Draw } from './Draw';
|
@ -0,0 +1,49 @@
|
|||||||
|
export function dpr() {
|
||||||
|
return window.devicePixelRatio ?? 1;
|
||||||
|
}
|
||||||
|
export function thinLineWidth() {
|
||||||
|
return dpr() - 0.5;
|
||||||
|
}
|
||||||
|
export function npx(px: number | string) {
|
||||||
|
if (typeof px === 'string') {
|
||||||
|
return parseInt(px, 10) * dpr();
|
||||||
|
}
|
||||||
|
return px * dpr();
|
||||||
|
}
|
||||||
|
export function npxLine(px) {
|
||||||
|
const n = npx(px);
|
||||||
|
return n > 0 ? n - 0.5 : 0.5;
|
||||||
|
}
|
||||||
|
export function drawFontLine(
|
||||||
|
type: 'underline' | 'strike',
|
||||||
|
tx: number,
|
||||||
|
ty: number,
|
||||||
|
align: 'center' | 'right',
|
||||||
|
valign: 'bottom' | 'top',
|
||||||
|
blheight: number,
|
||||||
|
blwidth: number,
|
||||||
|
) {
|
||||||
|
const floffset = { x: 0, y: 0 };
|
||||||
|
if (type === 'underline') {
|
||||||
|
if (valign === 'bottom') {
|
||||||
|
floffset.y = 0;
|
||||||
|
} else if (valign === 'top') {
|
||||||
|
floffset.y = -(blheight + 2);
|
||||||
|
} else {
|
||||||
|
floffset.y = -blheight / 2;
|
||||||
|
}
|
||||||
|
} else if (type === 'strike') {
|
||||||
|
if (valign === 'bottom') {
|
||||||
|
floffset.y = blheight / 2;
|
||||||
|
} else if (valign === 'top') {
|
||||||
|
floffset.y = -(blheight / 2 + 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (align === 'center') {
|
||||||
|
floffset.x = blwidth / 2;
|
||||||
|
} else if (align === 'right') {
|
||||||
|
floffset.x = blwidth;
|
||||||
|
}
|
||||||
|
this.line([tx - floffset.x, ty - floffset.y], [tx - floffset.x + blwidth, ty - floffset.y]);
|
||||||
|
}
|
@ -16,23 +16,23 @@ class DropdownMore extends Dropdown {
|
|||||||
}
|
}
|
||||||
|
|
||||||
reset(items) {
|
reset(items) {
|
||||||
const eles = items.map((it, i) => h('div', `${cssPrefix}-item`)
|
const eles = items.map((it, i) =>
|
||||||
|
h('div', `${cssPrefix}-item`)
|
||||||
.css('width', '150px')
|
.css('width', '150px')
|
||||||
.css('font-weight', 'normal')
|
.css('font-weight', 'normal')
|
||||||
.on('click', () => {
|
.on('click', () => {
|
||||||
this.contentClick(i);
|
this.contentClick(i);
|
||||||
this.hide();
|
this.hide();
|
||||||
})
|
})
|
||||||
.child(it));
|
.child(it),
|
||||||
|
);
|
||||||
this.setContentChildren(...eles);
|
this.setContentChildren(...eles);
|
||||||
}
|
}
|
||||||
|
|
||||||
setTitle() {}
|
setTitle() {}
|
||||||
}
|
}
|
||||||
|
|
||||||
const menuItems = [
|
const menuItems = [{ key: 'delete', title: tf('contextmenu.deleteSheet') }];
|
||||||
{ key: 'delete', title: tf('contextmenu.deleteSheet') },
|
|
||||||
];
|
|
||||||
|
|
||||||
function buildMenuItem(item) {
|
function buildMenuItem(item) {
|
||||||
return h('div', `${cssPrefix}-item`)
|
return h('div', `${cssPrefix}-item`)
|
||||||
@ -44,7 +44,7 @@ function buildMenuItem(item) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function buildMenu() {
|
function buildMenu() {
|
||||||
return menuItems.map(it => buildMenuItem.call(this, it));
|
return menuItems.map((it) => buildMenuItem.call(this, it));
|
||||||
}
|
}
|
||||||
|
|
||||||
class ContextMenu {
|
class ContextMenu {
|
||||||
@ -71,10 +71,7 @@ class ContextMenu {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default class Bottombar {
|
export default class Bottombar {
|
||||||
constructor(addFunc = () => {},
|
constructor(addFunc = () => {}, swapFunc = (index) => {}, deleteFunc = () => {}, updateFunc = (index, value) => {}) {
|
||||||
swapFunc = () => {},
|
|
||||||
deleteFunc = () => {},
|
|
||||||
updateFunc = () => {}) {
|
|
||||||
this.swapFunc = swapFunc;
|
this.swapFunc = swapFunc;
|
||||||
this.updateFunc = updateFunc;
|
this.updateFunc = updateFunc;
|
||||||
this.dataNames = [];
|
this.dataNames = [];
|
||||||
@ -88,35 +85,38 @@ export default class Bottombar {
|
|||||||
this.contextMenu.itemClick = deleteFunc;
|
this.contextMenu.itemClick = deleteFunc;
|
||||||
this.el = h('div', `${cssPrefix}-bottombar`).children(
|
this.el = h('div', `${cssPrefix}-bottombar`).children(
|
||||||
this.contextMenu.el,
|
this.contextMenu.el,
|
||||||
this.menuEl = h('ul', `${cssPrefix}-menu`).child(
|
(this.menuEl = h('ul', `${cssPrefix}-menu`).child(
|
||||||
h('li', '').children(
|
h('li', '').children(
|
||||||
new Icon('add').on('click', () => {
|
new Icon('add').on('click', () => {
|
||||||
addFunc();
|
addFunc();
|
||||||
}),
|
}),
|
||||||
h('span', '').child(this.moreEl),
|
h('span', '').child(this.moreEl),
|
||||||
),
|
),
|
||||||
),
|
)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
addItem(name, active, options) {
|
addItem(name, active, options) {
|
||||||
this.dataNames.push(name);
|
this.dataNames.push(name);
|
||||||
const item = h('li', active ? 'active' : '').child(name);
|
const item = h('li', active ? 'active' : '').child(name);
|
||||||
item.on('click', () => {
|
item
|
||||||
|
.on('click', () => {
|
||||||
this.clickSwap2(item);
|
this.clickSwap2(item);
|
||||||
}).on('contextmenu', (evt) => {
|
})
|
||||||
|
.on('contextmenu', (evt) => {
|
||||||
if (options.mode === 'read') return;
|
if (options.mode === 'read') return;
|
||||||
const { offsetLeft, offsetHeight } = evt.target;
|
const { offsetLeft, offsetHeight } = evt.target;
|
||||||
this.contextMenu.setOffset({ left: offsetLeft, bottom: offsetHeight + 1 });
|
this.contextMenu.setOffset({ left: offsetLeft, bottom: offsetHeight + 1 });
|
||||||
this.deleteEl = item;
|
this.deleteEl = item;
|
||||||
}).on('dblclick', () => {
|
})
|
||||||
|
.on('dblclick', () => {
|
||||||
if (options.mode === 'read') return;
|
if (options.mode === 'read') return;
|
||||||
const v = item.html();
|
const v = item.html();
|
||||||
const input = new FormInput('auto', '');
|
const input = new FormInput('auto', '');
|
||||||
input.val(v);
|
input.val(v);
|
||||||
input.input.on('blur', ({ target }) => {
|
input.input.on('blur', ({ target }) => {
|
||||||
const { value } = target;
|
const { value } = target;
|
||||||
const nindex = this.dataNames.findIndex(it => it === v);
|
const nindex = this.dataNames.findIndex((it) => it === v);
|
||||||
this.renameItem(nindex, value);
|
this.renameItem(nindex, value);
|
||||||
/*
|
/*
|
||||||
this.dataNames.splice(nindex, 1, value);
|
this.dataNames.splice(nindex, 1, value);
|
||||||
@ -155,7 +155,7 @@ export default class Bottombar {
|
|||||||
deleteItem() {
|
deleteItem() {
|
||||||
const { activeEl, deleteEl } = this;
|
const { activeEl, deleteEl } = this;
|
||||||
if (this.items.length > 1) {
|
if (this.items.length > 1) {
|
||||||
const index = this.items.findIndex(it => it === deleteEl);
|
const index = this.items.findIndex((it) => it === deleteEl);
|
||||||
this.items.splice(index, 1);
|
this.items.splice(index, 1);
|
||||||
this.dataNames.splice(index, 1);
|
this.dataNames.splice(index, 1);
|
||||||
this.menuEl.removeChild(deleteEl.el);
|
this.menuEl.removeChild(deleteEl.el);
|
||||||
@ -172,7 +172,7 @@ export default class Bottombar {
|
|||||||
}
|
}
|
||||||
|
|
||||||
clickSwap2(item) {
|
clickSwap2(item) {
|
||||||
const index = this.items.findIndex(it => it === item);
|
const index = this.items.findIndex((it) => it === item);
|
||||||
this.clickSwap(item);
|
this.clickSwap(item);
|
||||||
this.activeEl.toggle();
|
this.activeEl.toggle();
|
||||||
this.swapFunc(index);
|
this.swapFunc(index);
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { Element } from './element';
|
import { HComponent } from './element';
|
||||||
import { cssPrefix } from '../config';
|
import { cssPrefix } from '../config';
|
||||||
import { t } from '../locale/locale';
|
import { t } from '../locale/locale';
|
||||||
|
|
||||||
export default class Button extends Element {
|
export default class Button extends HComponent {
|
||||||
// type: primary
|
// type: primary
|
||||||
constructor(title, type = '') {
|
constructor(title, type = '') {
|
||||||
super('div', `${cssPrefix}-button ${type}`);
|
super('div', `${cssPrefix}-button ${type}`);
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { Element, h } from './element';
|
import { HComponent, h } from './element';
|
||||||
import { bindClickoutside, unbindClickoutside } from './event';
|
import { bindClickoutside, unbindClickoutside } from './event';
|
||||||
import { cssPrefix } from '../config';
|
import { cssPrefix } from '../config';
|
||||||
|
|
||||||
export default class Dropdown extends Element {
|
export default class Dropdown extends HComponent {
|
||||||
constructor(title, width, showArrow, placement, ...children) {
|
constructor(title, width, showArrow, placement, ...children) {
|
||||||
super('div', `${cssPrefix}-dropdown ${placement}`);
|
super('div', `${cssPrefix}-dropdown ${placement}`);
|
||||||
this.title = title;
|
this.title = title;
|
||||||
@ -13,24 +13,22 @@ export default class Dropdown extends Element {
|
|||||||
} else if (showArrow) {
|
} else if (showArrow) {
|
||||||
this.title.addClass('arrow-left');
|
this.title.addClass('arrow-left');
|
||||||
}
|
}
|
||||||
this.contentEl = h('div', `${cssPrefix}-dropdown-content`)
|
this.contentEl = h('div', `${cssPrefix}-dropdown-content`).css('width', width).hide();
|
||||||
.css('width', width)
|
|
||||||
.hide();
|
|
||||||
|
|
||||||
this.setContentChildren(...children);
|
this.setContentChildren(...children);
|
||||||
|
|
||||||
this.headerEl = h('div', `${cssPrefix}-dropdown-header`);
|
this.headerEl = h('div', `${cssPrefix}-dropdown-header`);
|
||||||
this.headerEl.on('click', () => {
|
this.headerEl
|
||||||
|
.on('click', () => {
|
||||||
if (this.contentEl.css('display') !== 'block') {
|
if (this.contentEl.css('display') !== 'block') {
|
||||||
this.show();
|
this.show();
|
||||||
} else {
|
} else {
|
||||||
this.hide();
|
this.hide();
|
||||||
}
|
}
|
||||||
}).children(
|
})
|
||||||
|
.children(
|
||||||
this.title,
|
this.title,
|
||||||
showArrow ? h('div', `${cssPrefix}-icon arrow-right`).child(
|
showArrow ? h('div', `${cssPrefix}-icon arrow-right`).child(h('div', `${cssPrefix}-icon-img arrow-down`)) : '',
|
||||||
h('div', `${cssPrefix}-icon-img arrow-down`),
|
|
||||||
) : '',
|
|
||||||
);
|
);
|
||||||
this.children(this.headerEl, this.contentEl);
|
this.children(this.headerEl, this.contentEl);
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
|
import _ from 'lodash';
|
||||||
class Element {
|
class Element {
|
||||||
private el: HTMLElement;
|
el: HTMLElement;
|
||||||
private _data: Record<string, any>;
|
private _data: Record<string, any>;
|
||||||
constructor(tag: HTMLElement | string, className = '') {
|
constructor(tag: HTMLElement | string, className = '') {
|
||||||
if (typeof tag === 'string') {
|
if (typeof tag === 'string') {
|
||||||
@ -23,7 +24,7 @@ class Element {
|
|||||||
const [fen, ...oen] = eventNames.split('.');
|
const [fen, ...oen] = eventNames.split('.');
|
||||||
const eventName = fen;
|
const eventName = fen;
|
||||||
this.el.addEventListener(eventName, (evt) => {
|
this.el.addEventListener(eventName, (evt) => {
|
||||||
console.log('excel debug', eventName, eventNames, oen, evt);
|
// console.log('excel debug', eventName, eventNames, oen, evt);
|
||||||
handler(evt);
|
handler(evt);
|
||||||
for (let i = 0; i < oen.length; i += 1) {
|
for (let i = 0; i < oen.length; i += 1) {
|
||||||
const k = oen[i];
|
const k = oen[i];
|
||||||
@ -58,7 +59,7 @@ class Element {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
scroll(v?) {
|
scroll = _.throttle((v?) => {
|
||||||
const { el } = this;
|
const { el } = this;
|
||||||
if (v !== undefined) {
|
if (v !== undefined) {
|
||||||
if (v.left !== undefined) {
|
if (v.left !== undefined) {
|
||||||
@ -69,7 +70,7 @@ class Element {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return { left: el.scrollLeft, top: el.scrollTop };
|
return { left: el.scrollLeft, top: el.scrollTop };
|
||||||
}
|
}, 10);
|
||||||
|
|
||||||
box() {
|
box() {
|
||||||
return this.el.getBoundingClientRect();
|
return this.el.getBoundingClientRect();
|
||||||
@ -240,4 +241,4 @@ class Element {
|
|||||||
|
|
||||||
const h = (tag, className = '') => new Element(tag, className);
|
const h = (tag, className = '') => new Element(tag, className);
|
||||||
|
|
||||||
export { Element, h };
|
export { Element as HComponent, h };
|
||||||
|
@ -91,16 +91,12 @@ export function createEventEmitter() {
|
|||||||
function on(eventName, callback) {
|
function on(eventName, callback) {
|
||||||
const push = () => {
|
const push = () => {
|
||||||
const currentListener = listeners.get(eventName);
|
const currentListener = listeners.get(eventName);
|
||||||
return (Array.isArray(currentListener)
|
return (Array.isArray(currentListener) && currentListener.push(callback)) || false;
|
||||||
&& currentListener.push(callback))
|
|
||||||
|| false;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const create = () => listeners.set(eventName, [].concat(callback));
|
const create = () => listeners.set(eventName, [].concat(callback));
|
||||||
|
|
||||||
return (listeners.has(eventName)
|
return (listeners.has(eventName) && push()) || create();
|
||||||
&& push())
|
|
||||||
|| create();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function fire(eventName, args) {
|
function fire(eventName, args) {
|
||||||
@ -109,22 +105,22 @@ export function createEventEmitter() {
|
|||||||
for (const callback of currentListener) callback.call(null, ...args);
|
for (const callback of currentListener) callback.call(null, ...args);
|
||||||
};
|
};
|
||||||
|
|
||||||
return listeners.has(eventName)
|
return listeners.has(eventName) && exec();
|
||||||
&& exec();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeListener(eventName, callback) {
|
function removeListener(eventName, callback) {
|
||||||
const remove = () => {
|
const remove = () => {
|
||||||
const currentListener = listeners.get(eventName);
|
const currentListener = listeners.get(eventName);
|
||||||
const idx = currentListener.indexOf(callback);
|
const idx = currentListener.indexOf(callback);
|
||||||
return (idx >= 0)
|
return (
|
||||||
&& currentListener.splice(idx, 1)
|
idx >= 0 &&
|
||||||
&& listeners.get(eventName).length === 0
|
currentListener.splice(idx, 1) &&
|
||||||
&& listeners.delete(eventName);
|
listeners.get(eventName).length === 0 &&
|
||||||
|
listeners.delete(eventName)
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
return listeners.has(eventName)
|
return listeners.has(eventName) && remove();
|
||||||
&& remove();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function once(eventName, callback) {
|
function once(eventName, callback) {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Element, h } from './element';
|
import { HComponent, h } from './element';
|
||||||
import { cssPrefix } from '../config';
|
import { cssPrefix } from '../config';
|
||||||
|
|
||||||
export default class Icon extends Element {
|
export default class Icon extends HComponent {
|
||||||
constructor(name) {
|
constructor(name) {
|
||||||
super('div', `${cssPrefix}-icon`);
|
super('div', `${cssPrefix}-icon`);
|
||||||
this.iconNameEl = h('div', `${cssPrefix}-icon-img ${name}`);
|
this.iconNameEl = h('div', `${cssPrefix}-icon-img ${name}`);
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
import { h } from './element';
|
import { h } from './element';
|
||||||
import { cssPrefix } from '../config';
|
import { cssPrefix } from '../config';
|
||||||
import Button from './button';
|
import Button from './button';
|
||||||
import { Draw } from '../canvas/draw';
|
import { Draw } from '../canvas';
|
||||||
import { renderCell } from './table';
|
import { renderCell } from './table';
|
||||||
import { t } from '../locale/locale';
|
import { t } from '../locale/locale';
|
||||||
|
|
||||||
@ -16,7 +16,7 @@ const PAGER_SIZES = [
|
|||||||
['A3', 11.69, 16.54],
|
['A3', 11.69, 16.54],
|
||||||
['A4', 8.27, 11.69],
|
['A4', 8.27, 11.69],
|
||||||
['A5', 5.83, 8.27],
|
['A5', 5.83, 8.27],
|
||||||
['B4', 9.84, 13.90],
|
['B4', 9.84, 13.9],
|
||||||
['B5', 6.93, 9.84],
|
['B5', 6.93, 9.84],
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -68,8 +68,7 @@ export default class Print {
|
|||||||
this.data = data;
|
this.data = data;
|
||||||
this.el = h('div', `${cssPrefix}-print`)
|
this.el = h('div', `${cssPrefix}-print`)
|
||||||
.children(
|
.children(
|
||||||
h('div', `${cssPrefix}-print-bar`)
|
h('div', `${cssPrefix}-print-bar`).children(
|
||||||
.children(
|
|
||||||
h('div', '-title').child('Print settings'),
|
h('div', '-title').child('Print settings'),
|
||||||
h('div', '-right').children(
|
h('div', '-right').children(
|
||||||
h('div', `${cssPrefix}-buttons`).children(
|
h('div', `${cssPrefix}-buttons`).children(
|
||||||
@ -78,27 +77,37 @@ export default class Print {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
h('div', `${cssPrefix}-print-content`)
|
h('div', `${cssPrefix}-print-content`).children(
|
||||||
.children(
|
(this.contentEl = h('div', '-content')),
|
||||||
this.contentEl = h('div', '-content'),
|
|
||||||
h('div', '-sider').child(
|
h('div', '-sider').child(
|
||||||
h('form', '').children(
|
h('form', '').children(
|
||||||
h('fieldset', '').children(
|
h('fieldset', '').children(
|
||||||
h('label', '').child(`${t('print.size')}`),
|
h('label', '').child(`${t('print.size')}`),
|
||||||
h('select', '').children(
|
h('select', '')
|
||||||
...PAGER_SIZES.map((it, index) => h('option', '').attr('value', index).child(`${it[0]} ( ${it[1]}''x${it[2]}'' )`)),
|
.children(
|
||||||
).on('change', pagerSizeChange.bind(this)),
|
...PAGER_SIZES.map((it, index) =>
|
||||||
|
h('option', '').attr('value', index).child(`${it[0]} ( ${it[1]}''x${it[2]}'' )`),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.on('change', pagerSizeChange.bind(this)),
|
||||||
),
|
),
|
||||||
h('fieldset', '').children(
|
h('fieldset', '').children(
|
||||||
h('label', '').child(`${t('print.orientation')}`),
|
h('label', '').child(`${t('print.orientation')}`),
|
||||||
h('select', '').children(
|
h('select', '')
|
||||||
...PAGER_ORIENTATIONS.map((it, index) => h('option', '').attr('value', index).child(`${t('print.orientations')[index]}`)),
|
.children(
|
||||||
).on('change', pagerOrientationChange.bind(this)),
|
...PAGER_ORIENTATIONS.map((it, index) =>
|
||||||
|
h('option', '')
|
||||||
|
.attr('value', index)
|
||||||
|
.child(`${t('print.orientations')[index]}`),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.on('change', pagerOrientationChange.bind(this)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
).hide();
|
)
|
||||||
|
.hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
resetData(data) {
|
resetData(data) {
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { h, Element } from './element';
|
import { h, HComponent } from './element';
|
||||||
import { cssPrefix } from '../config';
|
import { cssPrefix } from '../config';
|
||||||
|
|
||||||
export default class Scrollbar {
|
export default class Scrollbar {
|
||||||
private el: Element;
|
private el: HComponent;
|
||||||
private contentEl: Element;
|
private contentEl: HComponent;
|
||||||
private _moveFn: (type: string, handler: (e: Event) => void) => void;
|
private _moveFn: (type: string, handler: (e: Event) => void) => void;
|
||||||
constructor(private vertical: 'vertical' | 'horizontal') {
|
constructor(private vertical: 'vertical' | 'horizontal') {
|
||||||
this._moveFn = null;
|
this._moveFn = null;
|
||||||
|
@ -4,15 +4,13 @@ import _cell from '../core/cell';
|
|||||||
import { formulam } from '../core/formula';
|
import { formulam } from '../core/formula';
|
||||||
import { formatm } from '../core/format';
|
import { formatm } from '../core/format';
|
||||||
|
|
||||||
import {
|
import { Draw, DrawBox, thinLineWidth, npx } from '../canvas';
|
||||||
Draw, DrawBox, thinLineWidth, npx,
|
|
||||||
} from '../canvas/draw';
|
|
||||||
// gobal var
|
// gobal var
|
||||||
const cellPaddingWidth = 5;
|
const cellPaddingWidth = 5;
|
||||||
const tableFixedHeaderCleanStyle = { fillStyle: '#f4f5f8' };
|
const tableFixedHeaderCleanStyle = { fillStyle: '#f4f5f8' };
|
||||||
const tableGridStyle = {
|
const tableGridStyle = {
|
||||||
fillStyle: '#fff',
|
fillStyle: '#fff',
|
||||||
lineWidth: thinLineWidth,
|
lineWidth: thinLineWidth(),
|
||||||
strokeStyle: '#e6e6e6',
|
strokeStyle: '#e6e6e6',
|
||||||
};
|
};
|
||||||
function tableFixedHeaderStyle() {
|
function tableFixedHeaderStyle() {
|
||||||
@ -27,9 +25,7 @@ function tableFixedHeaderStyle() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getDrawBox(data, rindex, cindex, yoffset = 0) {
|
function getDrawBox(data, rindex, cindex, yoffset = 0) {
|
||||||
const {
|
const { left, top, width, height } = data.cellRect(rindex, cindex);
|
||||||
left, top, width, height,
|
|
||||||
} = data.cellRect(rindex, cindex);
|
|
||||||
return new DrawBox(left, top + yoffset, width, height, cellPaddingWidth);
|
return new DrawBox(left, top + yoffset, width, height, cellPaddingWidth);
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
@ -76,7 +72,7 @@ export function renderCell(draw, data, rindex, cindex, yoffset = 0) {
|
|||||||
// render text
|
// render text
|
||||||
let cellText = '';
|
let cellText = '';
|
||||||
if (!data.settings.evalPaused) {
|
if (!data.settings.evalPaused) {
|
||||||
cellText = _cell.render(cell.text || '', formulam, (y, x) => (data.getCellTextOrDefault(x, y)));
|
cellText = _cell.render(cell.text || '', formulam, (y, x) => data.getCellTextOrDefault(x, y));
|
||||||
} else {
|
} else {
|
||||||
cellText = cell.text || '';
|
cellText = cell.text || '';
|
||||||
}
|
}
|
||||||
@ -87,14 +83,19 @@ export function renderCell(draw, data, rindex, cindex, yoffset = 0) {
|
|||||||
const font = Object.assign({}, style.font);
|
const font = Object.assign({}, style.font);
|
||||||
font.size = getFontSizePxByPt(font.size);
|
font.size = getFontSizePxByPt(font.size);
|
||||||
// console.log('style:', style);
|
// console.log('style:', style);
|
||||||
draw.text(cellText, dbox, {
|
draw.text(
|
||||||
|
cellText,
|
||||||
|
dbox,
|
||||||
|
{
|
||||||
align: style.align,
|
align: style.align,
|
||||||
valign: style.valign,
|
valign: style.valign,
|
||||||
font,
|
font,
|
||||||
color: style.color,
|
color: style.color,
|
||||||
strike: style.strike,
|
strike: style.strike,
|
||||||
underline: style.underline,
|
underline: style.underline,
|
||||||
}, style.textwrap);
|
},
|
||||||
|
style.textwrap,
|
||||||
|
);
|
||||||
// error
|
// error
|
||||||
const error = data.validations.getError(rindex, cindex);
|
const error = data.validations.getError(rindex, cindex);
|
||||||
if (error) {
|
if (error) {
|
||||||
@ -125,8 +126,7 @@ function renderAutofilter(viewRange) {
|
|||||||
function renderContent(viewRange, fw, fh, tx, ty) {
|
function renderContent(viewRange, fw, fh, tx, ty) {
|
||||||
const { draw, data } = this;
|
const { draw, data } = this;
|
||||||
draw.save();
|
draw.save();
|
||||||
draw.translate(fw, fh)
|
draw.translate(fw, fh).translate(tx, ty);
|
||||||
.translate(tx, ty);
|
|
||||||
|
|
||||||
const { exceptRowSet } = data;
|
const { exceptRowSet } = data;
|
||||||
// const exceptRows = Array.from(exceptRowSet);
|
// const exceptRows = Array.from(exceptRowSet);
|
||||||
@ -143,12 +143,14 @@ function renderContent(viewRange, fw, fh, tx, ty) {
|
|||||||
// 1 render cell
|
// 1 render cell
|
||||||
draw.save();
|
draw.save();
|
||||||
draw.translate(0, -exceptRowTotalHeight);
|
draw.translate(0, -exceptRowTotalHeight);
|
||||||
viewRange.each((ri, ci) => {
|
viewRange.each(
|
||||||
|
(ri, ci) => {
|
||||||
renderCell(draw, data, ri, ci);
|
renderCell(draw, data, ri, ci);
|
||||||
}, ri => filteredTranslateFunc(ri));
|
},
|
||||||
|
(ri) => filteredTranslateFunc(ri),
|
||||||
|
);
|
||||||
draw.restore();
|
draw.restore();
|
||||||
|
|
||||||
|
|
||||||
// 2 render mergeCell
|
// 2 render mergeCell
|
||||||
const rset = new Set();
|
const rset = new Set();
|
||||||
draw.save();
|
draw.save();
|
||||||
@ -173,8 +175,7 @@ function renderContent(viewRange, fw, fh, tx, ty) {
|
|||||||
function renderSelectedHeaderCell(x, y, w, h) {
|
function renderSelectedHeaderCell(x, y, w, h) {
|
||||||
const { draw } = this;
|
const { draw } = this;
|
||||||
draw.save();
|
draw.save();
|
||||||
draw.attr({ fillStyle: 'rgba(75, 137, 255, 0.08)' })
|
draw.attr({ fillStyle: 'rgba(75, 137, 255, 0.08)' }).fillRect(x, y, w, h);
|
||||||
.fillRect(x, y, w, h);
|
|
||||||
draw.restore();
|
draw.restore();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -197,9 +198,7 @@ function renderFixedHeaders(type, viewRange, w, h, tx, ty) {
|
|||||||
if (type === 'all' || type === 'left') draw.fillRect(0, nty, w, sumHeight);
|
if (type === 'all' || type === 'left') draw.fillRect(0, nty, w, sumHeight);
|
||||||
if (type === 'all' || type === 'top') draw.fillRect(ntx, 0, sumWidth, h);
|
if (type === 'all' || type === 'top') draw.fillRect(ntx, 0, sumWidth, h);
|
||||||
|
|
||||||
const {
|
const { sri, sci, eri, eci } = data.selector.range;
|
||||||
sri, sci, eri, eci,
|
|
||||||
} = data.selector.range;
|
|
||||||
// console.log(data.selectIndexes);
|
// console.log(data.selectIndexes);
|
||||||
// draw text
|
// draw text
|
||||||
// text font, align...
|
// text font, align...
|
||||||
@ -213,7 +212,7 @@ function renderFixedHeaders(type, viewRange, w, h, tx, ty) {
|
|||||||
if (sri <= ii && ii < eri + 1) {
|
if (sri <= ii && ii < eri + 1) {
|
||||||
renderSelectedHeaderCell.call(this, 0, y, w, rowHeight);
|
renderSelectedHeaderCell.call(this, 0, y, w, rowHeight);
|
||||||
}
|
}
|
||||||
draw.fillText(ii + 1, w / 2, y + (rowHeight / 2));
|
draw.fillText(ii + 1, w / 2, y + rowHeight / 2);
|
||||||
if (i > 0 && data.rows.isHide(i - 1)) {
|
if (i > 0 && data.rows.isHide(i - 1)) {
|
||||||
draw.save();
|
draw.save();
|
||||||
draw.attr({ strokeStyle: '#c6c6c6' });
|
draw.attr({ strokeStyle: '#c6c6c6' });
|
||||||
@ -233,7 +232,7 @@ function renderFixedHeaders(type, viewRange, w, h, tx, ty) {
|
|||||||
if (sci <= ii && ii < eci + 1) {
|
if (sci <= ii && ii < eci + 1) {
|
||||||
renderSelectedHeaderCell.call(this, x, 0, colWidth, h);
|
renderSelectedHeaderCell.call(this, x, 0, colWidth, h);
|
||||||
}
|
}
|
||||||
draw.fillText(stringAt(ii), x + (colWidth / 2), h / 2);
|
draw.fillText(stringAt(ii), x + colWidth / 2, h / 2);
|
||||||
if (i > 0 && data.cols.isHide(i - 1)) {
|
if (i > 0 && data.cols.isHide(i - 1)) {
|
||||||
draw.save();
|
draw.save();
|
||||||
draw.attr({ strokeStyle: '#c6c6c6' });
|
draw.attr({ strokeStyle: '#c6c6c6' });
|
||||||
@ -251,20 +250,16 @@ function renderFixedLeftTopCell(fw, fh) {
|
|||||||
const { draw } = this;
|
const { draw } = this;
|
||||||
draw.save();
|
draw.save();
|
||||||
// left-top-cell
|
// left-top-cell
|
||||||
draw.attr({ fillStyle: '#f4f5f8' })
|
draw.attr({ fillStyle: '#f4f5f8' }).fillRect(0, 0, fw, fh);
|
||||||
.fillRect(0, 0, fw, fh);
|
|
||||||
draw.restore();
|
draw.restore();
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderContentGrid({
|
function renderContentGrid({ sri, sci, eri, eci, w, h }, fw, fh, tx, ty) {
|
||||||
sri, sci, eri, eci, w, h,
|
|
||||||
}, fw, fh, tx, ty) {
|
|
||||||
const { draw, data } = this;
|
const { draw, data } = this;
|
||||||
const { settings } = data;
|
const { settings } = data;
|
||||||
|
|
||||||
draw.save();
|
draw.save();
|
||||||
draw.attr(tableGridStyle)
|
draw.attr(tableGridStyle).translate(fw + tx, fh + ty);
|
||||||
.translate(fw + tx, fh + ty);
|
|
||||||
// const sumWidth = cols.sumWidth(sci, eci + 1);
|
// const sumWidth = cols.sumWidth(sci, eci + 1);
|
||||||
// const sumHeight = rows.sumHeight(sri, eri + 1);
|
// const sumHeight = rows.sumHeight(sri, eri + 1);
|
||||||
// console.log('sumWidth:', sumWidth);
|
// console.log('sumWidth:', sumWidth);
|
||||||
@ -290,9 +285,7 @@ function renderFreezeHighlightLine(fw, fh, ftw, fth) {
|
|||||||
const { draw, data } = this;
|
const { draw, data } = this;
|
||||||
const twidth = data.viewWidth() - fw;
|
const twidth = data.viewWidth() - fw;
|
||||||
const theight = data.viewHeight() - fh;
|
const theight = data.viewHeight() - fh;
|
||||||
draw.save()
|
draw.save().translate(fw, fh).attr({ strokeStyle: 'rgba(75, 137, 255, .6)' });
|
||||||
.translate(fw, fh)
|
|
||||||
.attr({ strokeStyle: 'rgba(75, 137, 255, .6)' });
|
|
||||||
draw.line([0, fth], [twidth, fth]);
|
draw.line([0, fth], [twidth, fth]);
|
||||||
draw.line([ftw, 0], [ftw, theight]);
|
draw.line([ftw, 0], [ftw, theight]);
|
||||||
draw.restore();
|
draw.restore();
|
||||||
|
@ -1,201 +0,0 @@
|
|||||||
export interface ExtendToolbarOption {
|
|
||||||
tip?: string;
|
|
||||||
el?: HTMLElement;
|
|
||||||
icon?: string;
|
|
||||||
onClick?: (data: object, sheet: object) => void
|
|
||||||
}
|
|
||||||
export interface Options {
|
|
||||||
mode?: 'edit' | 'read';
|
|
||||||
showToolbar?: boolean;
|
|
||||||
showGrid?: boolean;
|
|
||||||
showContextmenu?: boolean;
|
|
||||||
showBottomBar?: boolean;
|
|
||||||
extendToolbar?: {
|
|
||||||
left?: ExtendToolbarOption[],
|
|
||||||
right?: ExtendToolbarOption[],
|
|
||||||
};
|
|
||||||
autoFocus?: boolean;
|
|
||||||
view?: {
|
|
||||||
height: () => number;
|
|
||||||
width: () => number;
|
|
||||||
};
|
|
||||||
row?: {
|
|
||||||
len: number;
|
|
||||||
height: number;
|
|
||||||
};
|
|
||||||
col?: {
|
|
||||||
len: number;
|
|
||||||
width: number;
|
|
||||||
indexWidth: number;
|
|
||||||
minWidth: number;
|
|
||||||
};
|
|
||||||
style?: {
|
|
||||||
bgcolor: string;
|
|
||||||
align: 'left' | 'center' | 'right';
|
|
||||||
valign: 'top' | 'middle' | 'bottom';
|
|
||||||
textwrap: boolean;
|
|
||||||
strike: boolean;
|
|
||||||
underline: boolean;
|
|
||||||
color: string;
|
|
||||||
font: {
|
|
||||||
name: 'Helvetica';
|
|
||||||
size: number;
|
|
||||||
bold: boolean;
|
|
||||||
italic: false;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export type CELL_SELECTED = 'cell-selected';
|
|
||||||
export type CELLS_SELECTED = 'cells-selected';
|
|
||||||
export type CELL_EDITED = 'cell-edited';
|
|
||||||
|
|
||||||
export type CellMerge = [number, number];
|
|
||||||
|
|
||||||
export interface SpreadsheetEventHandler {
|
|
||||||
(
|
|
||||||
envt: CELL_SELECTED,
|
|
||||||
callback: (cell: Cell, rowIndex: number, colIndex: number) => void
|
|
||||||
): void;
|
|
||||||
(
|
|
||||||
envt: CELLS_SELECTED,
|
|
||||||
callback: (
|
|
||||||
cell: Cell,
|
|
||||||
parameters: { sri: number; sci: number; eri: number; eci: number }
|
|
||||||
) => void
|
|
||||||
): void;
|
|
||||||
(
|
|
||||||
evnt: CELL_EDITED,
|
|
||||||
callback: (text: string, rowIndex: number, colIndex: number) => void
|
|
||||||
): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ColProperties {
|
|
||||||
width?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Data for representing a cell
|
|
||||||
*/
|
|
||||||
export interface CellData {
|
|
||||||
text: string;
|
|
||||||
style?: number;
|
|
||||||
merge?: CellMerge;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Data for representing a row
|
|
||||||
*/
|
|
||||||
export interface RowData {
|
|
||||||
cells: {
|
|
||||||
[key: number]: CellData;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Data for representing a sheet
|
|
||||||
*/
|
|
||||||
export interface SheetData {
|
|
||||||
name?: string;
|
|
||||||
freeze?: string;
|
|
||||||
styles?: CellStyle[];
|
|
||||||
merges?: string[];
|
|
||||||
cols?: {
|
|
||||||
len?: number;
|
|
||||||
[key: number]: ColProperties;
|
|
||||||
};
|
|
||||||
rows?: {
|
|
||||||
[key: number]: RowData
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Data for representing a spreadsheet
|
|
||||||
*/
|
|
||||||
export interface SpreadsheetData {
|
|
||||||
[index: number]: SheetData;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CellStyle {
|
|
||||||
align?: 'left' | 'center' | 'right';
|
|
||||||
valign?: 'top' | 'middle' | 'bottom';
|
|
||||||
font?: {
|
|
||||||
bold?: boolean;
|
|
||||||
}
|
|
||||||
bgcolor?: string;
|
|
||||||
textwrap?: boolean;
|
|
||||||
color?: string;
|
|
||||||
border?: {
|
|
||||||
top?: string[];
|
|
||||||
right?: string[];
|
|
||||||
bottom?: string[];
|
|
||||||
left?: string[];
|
|
||||||
};
|
|
||||||
}
|
|
||||||
export interface Editor {}
|
|
||||||
export interface Element {}
|
|
||||||
|
|
||||||
export interface Row {}
|
|
||||||
export interface Table {}
|
|
||||||
export interface Cell {}
|
|
||||||
export interface Sheet {}
|
|
||||||
|
|
||||||
export default class Spreadsheet {
|
|
||||||
constructor(container: string | HTMLElement, opts?: Options);
|
|
||||||
on: SpreadsheetEventHandler;
|
|
||||||
/**
|
|
||||||
* retrieve cell
|
|
||||||
* @param rowIndex {number} row index
|
|
||||||
* @param colIndex {number} column index
|
|
||||||
* @param sheetIndex {number} sheet iindex
|
|
||||||
*/
|
|
||||||
cell(rowIndex: number, colIndex: number, sheetIndex: number): Cell;
|
|
||||||
/**
|
|
||||||
* retrieve cell style
|
|
||||||
* @param rowIndex
|
|
||||||
* @param colIndex
|
|
||||||
* @param sheetIndex
|
|
||||||
*/
|
|
||||||
cellStyle(
|
|
||||||
rowIndex: number,
|
|
||||||
colIndex: number,
|
|
||||||
sheetIndex: number
|
|
||||||
): CellStyle;
|
|
||||||
/**
|
|
||||||
* get/set cell text
|
|
||||||
* @param rowIndex
|
|
||||||
* @param colIndex
|
|
||||||
* @param text
|
|
||||||
* @param sheetIndex
|
|
||||||
*/
|
|
||||||
cellText(
|
|
||||||
rowIndex: number,
|
|
||||||
colIndex: number,
|
|
||||||
text: string,
|
|
||||||
sheetIndex?: number
|
|
||||||
): this;
|
|
||||||
/**
|
|
||||||
* remove current sheet
|
|
||||||
*/
|
|
||||||
deleteSheet(): void;
|
|
||||||
|
|
||||||
/**s
|
|
||||||
* load data
|
|
||||||
* @param json
|
|
||||||
*/
|
|
||||||
loadData(json: Record<string, any>): this;
|
|
||||||
/**
|
|
||||||
* get data
|
|
||||||
*/
|
|
||||||
getData(): Record<string, any>;
|
|
||||||
/**
|
|
||||||
* bind handler to change event, including data change and user actions
|
|
||||||
* @param callback
|
|
||||||
*/
|
|
||||||
change(callback: (json: Record<string, any>) => void): this;
|
|
||||||
/**
|
|
||||||
* set locale
|
|
||||||
* @param lang
|
|
||||||
* @param message
|
|
||||||
*/
|
|
||||||
static locale(lang: string, message: object): void;
|
|
||||||
}
|
|
@ -1,131 +0,0 @@
|
|||||||
/* global window, document */
|
|
||||||
import { h } from './component/element';
|
|
||||||
import DataProxy from './core/data_proxy';
|
|
||||||
import Sheet from './component/sheet';
|
|
||||||
import Bottombar from './component/bottombar';
|
|
||||||
import { cssPrefix } from './config';
|
|
||||||
import { locale } from './locale/locale';
|
|
||||||
import './index.less';
|
|
||||||
|
|
||||||
|
|
||||||
class Spreadsheet {
|
|
||||||
constructor(selectors, options = {}) {
|
|
||||||
let targetEl = selectors;
|
|
||||||
this.options = { showBottomBar: true, ...options };
|
|
||||||
this.sheetIndex = 1;
|
|
||||||
this.datas = [];
|
|
||||||
if (typeof selectors === 'string') {
|
|
||||||
targetEl = document.querySelector(selectors);
|
|
||||||
}
|
|
||||||
this.bottombar = this.options.showBottomBar ? new Bottombar(() => {
|
|
||||||
if (this.options.mode === 'read') return;
|
|
||||||
const d = this.addSheet();
|
|
||||||
this.sheet.resetData(d);
|
|
||||||
}, (index) => {
|
|
||||||
const d = this.datas[index];
|
|
||||||
this.sheet.resetData(d);
|
|
||||||
}, () => {
|
|
||||||
this.deleteSheet();
|
|
||||||
}, (index, value) => {
|
|
||||||
this.datas[index].name = value;
|
|
||||||
this.sheet.trigger('change');
|
|
||||||
}) : null;
|
|
||||||
this.data = this.addSheet();
|
|
||||||
const rootEl = h('div', `${cssPrefix}`)
|
|
||||||
.on('contextmenu', evt => evt.preventDefault());
|
|
||||||
// create canvas element
|
|
||||||
targetEl.appendChild(rootEl.el);
|
|
||||||
this.sheet = new Sheet(rootEl, this.data);
|
|
||||||
if (this.bottombar !== null) {
|
|
||||||
rootEl.child(this.bottombar.el);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
addSheet(name, active = true) {
|
|
||||||
const n = name || `sheet${this.sheetIndex}`;
|
|
||||||
const d = new DataProxy(n, this.options);
|
|
||||||
d.change = (...args) => {
|
|
||||||
this.sheet.trigger('change', ...args);
|
|
||||||
};
|
|
||||||
this.datas.push(d);
|
|
||||||
// console.log('d:', n, d, this.datas);
|
|
||||||
if (this.bottombar !== null) {
|
|
||||||
this.bottombar.addItem(n, active, this.options);
|
|
||||||
}
|
|
||||||
this.sheetIndex += 1;
|
|
||||||
return d;
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteSheet() {
|
|
||||||
if (this.bottombar === null) return;
|
|
||||||
|
|
||||||
const [oldIndex, nindex] = this.bottombar.deleteItem();
|
|
||||||
if (oldIndex >= 0) {
|
|
||||||
this.datas.splice(oldIndex, 1);
|
|
||||||
if (nindex >= 0) this.sheet.resetData(this.datas[nindex]);
|
|
||||||
this.sheet.trigger('change');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
loadData(data) {
|
|
||||||
const ds = Array.isArray(data) ? data : [data];
|
|
||||||
if (this.bottombar !== null) {
|
|
||||||
this.bottombar.clear();
|
|
||||||
}
|
|
||||||
this.datas = [];
|
|
||||||
if (ds.length > 0) {
|
|
||||||
for (let i = 0; i < ds.length; i += 1) {
|
|
||||||
const it = ds[i];
|
|
||||||
const nd = this.addSheet(it.name, i === 0);
|
|
||||||
nd.setData(it);
|
|
||||||
if (i === 0) {
|
|
||||||
this.sheet.resetData(nd);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
getData() {
|
|
||||||
return this.datas.map(it => it.getData());
|
|
||||||
}
|
|
||||||
|
|
||||||
cellText(ri, ci, text, sheetIndex = 0) {
|
|
||||||
this.datas[sheetIndex].setCellText(ri, ci, text, 'finished');
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
cell(ri, ci, sheetIndex = 0) {
|
|
||||||
return this.datas[sheetIndex].getCell(ri, ci);
|
|
||||||
}
|
|
||||||
|
|
||||||
cellStyle(ri, ci, sheetIndex = 0) {
|
|
||||||
return this.datas[sheetIndex].getCellStyle(ri, ci);
|
|
||||||
}
|
|
||||||
|
|
||||||
reRender() {
|
|
||||||
this.sheet.table.render();
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
on(eventName, func) {
|
|
||||||
this.sheet.on(eventName, func);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
validate() {
|
|
||||||
const { validations } = this.data;
|
|
||||||
return validations.errors.size <= 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
change(cb) {
|
|
||||||
this.sheet.on('change', cb);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
static locale(lang, message) {
|
|
||||||
locale(lang, message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Spreadsheet;
|
|
@ -0,0 +1,292 @@
|
|||||||
|
import { h, HComponent } from './component/element';
|
||||||
|
import DataProxy from './core/data_proxy';
|
||||||
|
import Sheet from './component/sheet';
|
||||||
|
import Bottombar from './component/bottombar';
|
||||||
|
import { cssPrefix } from './config';
|
||||||
|
import { locale } from './locale/locale';
|
||||||
|
import './index.less';
|
||||||
|
|
||||||
|
export type CELL_SELECTED = 'cell-selected';
|
||||||
|
export type CELLS_SELECTED = 'cells-selected';
|
||||||
|
export type CELL_EDITED = 'cell-edited';
|
||||||
|
|
||||||
|
export type CellMerge = [number, number];
|
||||||
|
|
||||||
|
export interface SpreadsheetEventHandler {
|
||||||
|
(envt: CELL_SELECTED, callback: (cell: Cell, rowIndex: number, colIndex: number) => void): void;
|
||||||
|
(
|
||||||
|
envt: CELLS_SELECTED,
|
||||||
|
callback: (cell: Cell, parameters: { sri: number; sci: number; eri: number; eci: number }) => void,
|
||||||
|
): void;
|
||||||
|
(evnt: CELL_EDITED, callback: (text: string, rowIndex: number, colIndex: number) => void): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ColProperties {
|
||||||
|
width?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data for representing a cell
|
||||||
|
*/
|
||||||
|
export interface CellData {
|
||||||
|
text: string;
|
||||||
|
style?: number;
|
||||||
|
merge?: CellMerge;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Data for representing a row
|
||||||
|
*/
|
||||||
|
export interface RowData {
|
||||||
|
cells: {
|
||||||
|
[key: number]: CellData;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data for representing a sheet
|
||||||
|
*/
|
||||||
|
export interface SheetData {
|
||||||
|
name?: string;
|
||||||
|
freeze?: string;
|
||||||
|
styles?: CellStyle[];
|
||||||
|
merges?: string[];
|
||||||
|
cols?: {
|
||||||
|
len?: number;
|
||||||
|
[key: number]: ColProperties;
|
||||||
|
};
|
||||||
|
rows?: {
|
||||||
|
[key: number]: RowData;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data for representing a spreadsheet
|
||||||
|
*/
|
||||||
|
export interface SpreadsheetData {
|
||||||
|
[index: number]: SheetData;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CellStyle {
|
||||||
|
align?: 'left' | 'center' | 'right';
|
||||||
|
valign?: 'top' | 'middle' | 'bottom';
|
||||||
|
font?: {
|
||||||
|
bold?: boolean;
|
||||||
|
};
|
||||||
|
bgcolor?: string;
|
||||||
|
textwrap?: boolean;
|
||||||
|
color?: string;
|
||||||
|
border?: {
|
||||||
|
top?: string[];
|
||||||
|
right?: string[];
|
||||||
|
bottom?: string[];
|
||||||
|
left?: string[];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
export interface Editor {}
|
||||||
|
export interface Element {}
|
||||||
|
|
||||||
|
export interface Row {}
|
||||||
|
export interface Table {}
|
||||||
|
export interface Cell {}
|
||||||
|
|
||||||
|
export interface ExtendToolbarOption {
|
||||||
|
tip?: string;
|
||||||
|
el?: HTMLElement;
|
||||||
|
icon?: string;
|
||||||
|
onClick?: (data: object, sheet: object) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Options {
|
||||||
|
mode?: 'edit' | 'read';
|
||||||
|
showToolbar?: boolean;
|
||||||
|
showGrid?: boolean;
|
||||||
|
showContextmenu?: boolean;
|
||||||
|
showBottomBar?: boolean;
|
||||||
|
extendToolbar?: {
|
||||||
|
left?: ExtendToolbarOption[];
|
||||||
|
right?: ExtendToolbarOption[];
|
||||||
|
};
|
||||||
|
autoFocus?: boolean;
|
||||||
|
view?: {
|
||||||
|
height: () => number;
|
||||||
|
width: () => number;
|
||||||
|
};
|
||||||
|
row?: {
|
||||||
|
len: number;
|
||||||
|
height: number;
|
||||||
|
};
|
||||||
|
col?: {
|
||||||
|
len: number;
|
||||||
|
width: number;
|
||||||
|
indexWidth: number;
|
||||||
|
minWidth: number;
|
||||||
|
};
|
||||||
|
style?: {
|
||||||
|
bgcolor: string;
|
||||||
|
align: 'left' | 'center' | 'right';
|
||||||
|
valign: 'top' | 'middle' | 'bottom';
|
||||||
|
textwrap: boolean;
|
||||||
|
strike: boolean;
|
||||||
|
underline: boolean;
|
||||||
|
color: string;
|
||||||
|
font: {
|
||||||
|
name: 'Helvetica';
|
||||||
|
size: number;
|
||||||
|
bold: boolean;
|
||||||
|
italic: false;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
class Spreadsheet {
|
||||||
|
private targetEl: HTMLElement;
|
||||||
|
private options: Options;
|
||||||
|
sheetIndex: number;
|
||||||
|
datas: any[];
|
||||||
|
bottombar: Bottombar;
|
||||||
|
sheet: any;
|
||||||
|
data: DataProxy;
|
||||||
|
rootEl: HComponent;
|
||||||
|
constructor(selectors: string | HTMLElement, options: Options = {}) {
|
||||||
|
this.options = { showBottomBar: true, ...options };
|
||||||
|
this.sheetIndex = 1;
|
||||||
|
this.datas = [];
|
||||||
|
// debugger;
|
||||||
|
if (typeof selectors === 'string') {
|
||||||
|
this.targetEl = document.querySelector(selectors) as HTMLElement;
|
||||||
|
} else {
|
||||||
|
this.targetEl = selectors;
|
||||||
|
}
|
||||||
|
this.bottombar = this.options.showBottomBar
|
||||||
|
? new Bottombar(
|
||||||
|
() => {
|
||||||
|
if (this.options.mode === 'read') return;
|
||||||
|
const d = this.addSheet();
|
||||||
|
this.sheet.resetData(d);
|
||||||
|
},
|
||||||
|
(index) => {
|
||||||
|
const d = this.datas[index];
|
||||||
|
this.sheet.resetData(d);
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
this.deleteSheet();
|
||||||
|
},
|
||||||
|
(index, value) => {
|
||||||
|
this.datas[index].name = value;
|
||||||
|
this.sheet.trigger('change');
|
||||||
|
},
|
||||||
|
)
|
||||||
|
: null;
|
||||||
|
this.data = this.addSheet();
|
||||||
|
this.rootEl = h('div', `${cssPrefix}`).on('contextmenu', (evt) => evt.preventDefault());
|
||||||
|
// create canvas element
|
||||||
|
this.targetEl.appendChild(this.rootEl.el);
|
||||||
|
this.sheet = new Sheet(this.rootEl, this.data);
|
||||||
|
if (this.bottombar !== null) {
|
||||||
|
this.rootEl.child(this.bottombar.el);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dispose() {
|
||||||
|
this.targetEl.removeChild(this.rootEl.el);
|
||||||
|
console.debug('x-sheet is diposed');
|
||||||
|
}
|
||||||
|
|
||||||
|
addSheet(name?: string, active = true) {
|
||||||
|
const n = name || `sheet${this.sheetIndex}`;
|
||||||
|
const d = new DataProxy(n, this.options);
|
||||||
|
d.change = (...args) => {
|
||||||
|
this.sheet.trigger('change', ...args);
|
||||||
|
};
|
||||||
|
this.datas.push(d);
|
||||||
|
// console.log('d:', n, d, this.datas);
|
||||||
|
if (this.bottombar !== null) {
|
||||||
|
this.bottombar.addItem(n, active, this.options);
|
||||||
|
}
|
||||||
|
this.sheetIndex += 1;
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteSheet() {
|
||||||
|
if (this.bottombar === null) return;
|
||||||
|
|
||||||
|
const [oldIndex, nindex] = this.bottombar.deleteItem();
|
||||||
|
if (oldIndex >= 0) {
|
||||||
|
this.datas.splice(oldIndex, 1);
|
||||||
|
if (nindex >= 0) this.sheet.resetData(this.datas[nindex]);
|
||||||
|
this.sheet.trigger('change');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadData(data: Record<string, any>): this {
|
||||||
|
const ds = Array.isArray(data) ? data : [data];
|
||||||
|
if (this.bottombar !== null) {
|
||||||
|
this.bottombar.clear();
|
||||||
|
}
|
||||||
|
this.datas = [];
|
||||||
|
if (ds.length > 0) {
|
||||||
|
for (let i = 0; i < ds.length; i += 1) {
|
||||||
|
const it = ds[i];
|
||||||
|
const nd = this.addSheet(it.name, i === 0);
|
||||||
|
nd.setData(it);
|
||||||
|
if (i === 0) {
|
||||||
|
this.sheet.resetData(nd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
getData(): Record<string, any> {
|
||||||
|
return this.datas.map((it) => it.getData());
|
||||||
|
}
|
||||||
|
|
||||||
|
cellText(ri: number, ci: number, text: string, sheetIndex = 0): this {
|
||||||
|
this.datas[sheetIndex].setCellText(ri, ci, text, 'finished');
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
cell(ri: number, ci: number, sheetIndex = 0) {
|
||||||
|
return this.datas[sheetIndex].getCell(ri, ci);
|
||||||
|
}
|
||||||
|
|
||||||
|
cellStyle(ri: number, ci: number, sheetIndex = 0): CellStyle {
|
||||||
|
return this.datas[sheetIndex].getCellStyle(ri, ci);
|
||||||
|
}
|
||||||
|
|
||||||
|
reRender() {
|
||||||
|
this.sheet.table.render();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
on(eventName, func) {
|
||||||
|
this.sheet.on(eventName, func);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
validate() {
|
||||||
|
const { validations } = this.data;
|
||||||
|
return validations.errors.size <= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* bind handler to change event, including data change and user actions
|
||||||
|
* @param cb
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
change(cb: (json: Record<string, any>) => void): this {
|
||||||
|
this.sheet.on('change', cb);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* set locale
|
||||||
|
* @param lang
|
||||||
|
* @param message
|
||||||
|
*/
|
||||||
|
static locale(lang: string, message: unknown): void {
|
||||||
|
locale(lang, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Spreadsheet;
|
@ -30,6 +30,7 @@ import {
|
|||||||
CustomFieldInterface,
|
CustomFieldInterface,
|
||||||
CustomAssociatedFieldInterface,
|
CustomAssociatedFieldInterface,
|
||||||
SignaturePadFieldInterface,
|
SignaturePadFieldInterface,
|
||||||
|
ExcelFieldInterface,
|
||||||
} from './interfaces';
|
} from './interfaces';
|
||||||
import {
|
import {
|
||||||
AssociatedField,
|
AssociatedField,
|
||||||
@ -41,6 +42,7 @@ import {
|
|||||||
customComponentDispatcherSettings,
|
customComponentDispatcherSettings,
|
||||||
Expression,
|
Expression,
|
||||||
SignatureInput,
|
SignatureInput,
|
||||||
|
ExcelFile,
|
||||||
} from './components';
|
} from './components';
|
||||||
import { AutoComplete, InternalPDFViewer } from './schema-components';
|
import { AutoComplete, InternalPDFViewer } from './schema-components';
|
||||||
import {
|
import {
|
||||||
@ -192,6 +194,7 @@ export class PluginCoreClient extends Plugin {
|
|||||||
SheetBlockProvider,
|
SheetBlockProvider,
|
||||||
SheetBlockToolbar,
|
SheetBlockToolbar,
|
||||||
SignatureInput,
|
SignatureInput,
|
||||||
|
ExcelFile,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -274,6 +277,7 @@ export class PluginCoreClient extends Plugin {
|
|||||||
CustomFieldInterface,
|
CustomFieldInterface,
|
||||||
CustomAssociatedFieldInterface,
|
CustomAssociatedFieldInterface,
|
||||||
SignaturePadFieldInterface,
|
SignaturePadFieldInterface,
|
||||||
|
ExcelFieldInterface,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,25 @@
|
|||||||
|
import { defaultProps, CollectionFieldInterface } from '@nocobase/client';
|
||||||
|
import { tval } from '../locale';
|
||||||
|
|
||||||
|
export class ExcelFieldInterface extends CollectionFieldInterface {
|
||||||
|
name = 'excelField';
|
||||||
|
type = 'json';
|
||||||
|
group = 'advanced';
|
||||||
|
order = 3;
|
||||||
|
title = tval('Excel table');
|
||||||
|
sortable = true;
|
||||||
|
default = {
|
||||||
|
interface: 'json',
|
||||||
|
type: 'json',
|
||||||
|
uiSchema: {
|
||||||
|
type: 'object',
|
||||||
|
'x-component': 'ExcelFile',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
availableTypes = ['json'];
|
||||||
|
hasDefaultValue = false;
|
||||||
|
|
||||||
|
properties = {
|
||||||
|
...defaultProps,
|
||||||
|
};
|
||||||
|
}
|
@ -1,3 +1,4 @@
|
|||||||
|
export * from './ExcelFieldInterface';
|
||||||
export * from './AssociatedFieldInterface';
|
export * from './AssociatedFieldInterface';
|
||||||
export * from './CalcFieldInterface';
|
export * from './CalcFieldInterface';
|
||||||
export * from './CustomFieldInterface';
|
export * from './CustomFieldInterface';
|
||||||
|
@ -13,7 +13,7 @@ import {
|
|||||||
import React, { createContext, useRef } from 'react';
|
import React, { createContext, useRef } from 'react';
|
||||||
import { uid } from '@nocobase/utils/client';
|
import { uid } from '@nocobase/utils/client';
|
||||||
import { Button, Spin } from 'antd';
|
import { Button, Spin } from 'antd';
|
||||||
import Sheet, { SheetRef } from '../../components/Sheet';
|
import Sheet, { SheetRef } from '../../components/excel-table/Sheet';
|
||||||
|
|
||||||
export const SheetBlockContext = createContext<any>({});
|
export const SheetBlockContext = createContext<any>({});
|
||||||
|
|
||||||
|
@ -76,9 +76,16 @@ export class ContractRuleService {
|
|||||||
appends: ['lease_items', 'lease_items.products'],
|
appends: ['lease_items', 'lease_items.products'],
|
||||||
});
|
});
|
||||||
const add = options.values.products;
|
const add = options.values.products;
|
||||||
const productData = plan.lease_items.filter(item => item.id !== options.values.id && item.products.length === add.length).map((item) => item.products).flat()
|
const productData = plan.lease_items
|
||||||
|
.filter((item) => item.id !== options.values.id && item.products.length === add.length)
|
||||||
|
.map((item) => item.products)
|
||||||
|
.flat();
|
||||||
productData.forEach((item) => {
|
productData.forEach((item) => {
|
||||||
const isHas = add.find((p) => (p.id < 99999 && (p.id === item.id || (item.id > 99999 && p.raw_category_id === item.raw_category_id))) || (p.id > 99999 && p.raw_category_id === item.raw_category_id));
|
const isHas = add.find(
|
||||||
|
(p) =>
|
||||||
|
(p.id < 99999 && (p.id === item.id || (item.id > 99999 && p.raw_category_id === item.raw_category_id))) ||
|
||||||
|
(p.id > 99999 && p.raw_category_id === item.raw_category_id),
|
||||||
|
);
|
||||||
if (isHas) {
|
if (isHas) {
|
||||||
throw new Error('方案中存在此产品');
|
throw new Error('方案中存在此产品');
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user