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 './Sheet';
|
||||
export * from './excel-table/Sheet';
|
||||
export * from './SignatureInput';
|
||||
export * from './SignaturePad';
|
||||
export * from './custom-components/CustomAssociatedField';
|
||||
@ -11,3 +11,4 @@ export * from './fields/Expression';
|
||||
export * from './system/MobileLink';
|
||||
export * from './system/Notifications';
|
||||
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 */
|
||||
function dpr() {
|
||||
return window.devicePixelRatio || 1;
|
||||
import { DrawBox } from './DrawBox';
|
||||
import { npxLine } from './utils';
|
||||
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() {
|
||||
return dpr() - 0.5;
|
||||
}
|
||||
|
||||
function npx(px) {
|
||||
return parseInt(px * dpr(), 10);
|
||||
}
|
||||
|
||||
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;
|
||||
export class Draw {
|
||||
private ctx: CanvasRenderingContext2D;
|
||||
constructor(
|
||||
private el: HTMLCanvasElement,
|
||||
width: number,
|
||||
height: number,
|
||||
) {
|
||||
this.ctx = el.getContext('2d');
|
||||
this.resize(width, height);
|
||||
this.ctx.scale(dpr(), dpr());
|
||||
}
|
||||
|
||||
resize(width, height) {
|
||||
// console.log('dpr:', dpr);
|
||||
resize(width: number, height: number) {
|
||||
this.el.style.width = `${width}px`;
|
||||
this.el.style.height = `${height}px`;
|
||||
this.el.width = npx(width);
|
||||
@ -152,7 +44,7 @@ class Draw {
|
||||
return this;
|
||||
}
|
||||
|
||||
attr(options) {
|
||||
attr(options: unknown) {
|
||||
Object.assign(this.ctx, options);
|
||||
return this;
|
||||
}
|
||||
@ -173,27 +65,27 @@ class Draw {
|
||||
return this;
|
||||
}
|
||||
|
||||
translate(x, y) {
|
||||
translate(x: number, y: number) {
|
||||
this.ctx.translate(npx(x), npx(y));
|
||||
return this;
|
||||
}
|
||||
|
||||
scale(x, y) {
|
||||
scale(x: number, y: number) {
|
||||
this.ctx.scale(x, y);
|
||||
return this;
|
||||
}
|
||||
|
||||
clearRect(x, y, w, h) {
|
||||
clearRect(x: number, y: number, w: number, h: number) {
|
||||
this.ctx.clearRect(x, y, w, h);
|
||||
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));
|
||||
return this;
|
||||
}
|
||||
|
||||
fillText(text, x, y) {
|
||||
fillText(text: string, x: number, y: number) {
|
||||
this.ctx.fillText(text, npx(x), npx(y));
|
||||
return this;
|
||||
}
|
||||
@ -215,11 +107,9 @@ class Draw {
|
||||
}
|
||||
textWrap: text wrapping
|
||||
*/
|
||||
text(mtxt, box, attr = {}, textWrap = true) {
|
||||
text(mtxt: string, box: DrawBox, attr: Attr = {}, textWrap = true) {
|
||||
const { ctx } = this;
|
||||
const {
|
||||
align, valign, font, color, strike, underline,
|
||||
} = attr;
|
||||
const { align, valign, font, color, strike, underline } = attr;
|
||||
const tx = box.textx(align);
|
||||
ctx.save();
|
||||
ctx.beginPath();
|
||||
@ -269,9 +159,9 @@ class Draw {
|
||||
return this;
|
||||
}
|
||||
|
||||
border(style, color) {
|
||||
border(style: string, color: string) {
|
||||
const { ctx } = this;
|
||||
ctx.lineWidth = thinLineWidth;
|
||||
ctx.lineWidth = thinLineWidth();
|
||||
ctx.strokeStyle = color;
|
||||
// console.log('style:', style);
|
||||
if (style === 'medium') {
|
||||
@ -303,16 +193,13 @@ class Draw {
|
||||
return this;
|
||||
}
|
||||
|
||||
strokeBorders(box) {
|
||||
strokeBorders(box: DrawBox) {
|
||||
const { ctx } = this;
|
||||
ctx.save();
|
||||
// border
|
||||
const {
|
||||
borderTop, borderRight, borderBottom, borderLeft,
|
||||
} = box;
|
||||
const { borderTop, borderRight, borderBottom, borderLeft } = box;
|
||||
if (borderTop) {
|
||||
this.border(...borderTop);
|
||||
// console.log('box.topxys:', box.topxys());
|
||||
this.line(...box.topxys());
|
||||
}
|
||||
if (borderRight) {
|
||||
@ -332,9 +219,7 @@ class Draw {
|
||||
|
||||
dropdown(box) {
|
||||
const { ctx } = this;
|
||||
const {
|
||||
x, y, width, height,
|
||||
} = box;
|
||||
const { x, y, width, height } = box;
|
||||
const sx = x + width - 15;
|
||||
const sy = y + height - 15;
|
||||
ctx.save();
|
||||
@ -380,9 +265,7 @@ class Draw {
|
||||
|
||||
rect(box, dtextcb) {
|
||||
const { ctx } = this;
|
||||
const {
|
||||
x, y, width, height, bgcolor,
|
||||
} = box;
|
||||
const { x, y, width, height, bgcolor } = box;
|
||||
ctx.save();
|
||||
ctx.beginPath();
|
||||
ctx.fillStyle = bgcolor || '#fff';
|
||||
@ -393,11 +276,3 @@ class Draw {
|
||||
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) {
|
||||
const eles = items.map((it, i) => h('div', `${cssPrefix}-item`)
|
||||
const eles = items.map((it, i) =>
|
||||
h('div', `${cssPrefix}-item`)
|
||||
.css('width', '150px')
|
||||
.css('font-weight', 'normal')
|
||||
.on('click', () => {
|
||||
this.contentClick(i);
|
||||
this.hide();
|
||||
})
|
||||
.child(it));
|
||||
.child(it),
|
||||
);
|
||||
this.setContentChildren(...eles);
|
||||
}
|
||||
|
||||
setTitle() {}
|
||||
}
|
||||
|
||||
const menuItems = [
|
||||
{ key: 'delete', title: tf('contextmenu.deleteSheet') },
|
||||
];
|
||||
const menuItems = [{ key: 'delete', title: tf('contextmenu.deleteSheet') }];
|
||||
|
||||
function buildMenuItem(item) {
|
||||
return h('div', `${cssPrefix}-item`)
|
||||
@ -44,7 +44,7 @@ function buildMenuItem(item) {
|
||||
}
|
||||
|
||||
function buildMenu() {
|
||||
return menuItems.map(it => buildMenuItem.call(this, it));
|
||||
return menuItems.map((it) => buildMenuItem.call(this, it));
|
||||
}
|
||||
|
||||
class ContextMenu {
|
||||
@ -71,10 +71,7 @@ class ContextMenu {
|
||||
}
|
||||
|
||||
export default class Bottombar {
|
||||
constructor(addFunc = () => {},
|
||||
swapFunc = () => {},
|
||||
deleteFunc = () => {},
|
||||
updateFunc = () => {}) {
|
||||
constructor(addFunc = () => {}, swapFunc = (index) => {}, deleteFunc = () => {}, updateFunc = (index, value) => {}) {
|
||||
this.swapFunc = swapFunc;
|
||||
this.updateFunc = updateFunc;
|
||||
this.dataNames = [];
|
||||
@ -88,35 +85,38 @@ export default class Bottombar {
|
||||
this.contextMenu.itemClick = deleteFunc;
|
||||
this.el = h('div', `${cssPrefix}-bottombar`).children(
|
||||
this.contextMenu.el,
|
||||
this.menuEl = h('ul', `${cssPrefix}-menu`).child(
|
||||
(this.menuEl = h('ul', `${cssPrefix}-menu`).child(
|
||||
h('li', '').children(
|
||||
new Icon('add').on('click', () => {
|
||||
addFunc();
|
||||
}),
|
||||
h('span', '').child(this.moreEl),
|
||||
),
|
||||
),
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
addItem(name, active, options) {
|
||||
this.dataNames.push(name);
|
||||
const item = h('li', active ? 'active' : '').child(name);
|
||||
item.on('click', () => {
|
||||
item
|
||||
.on('click', () => {
|
||||
this.clickSwap2(item);
|
||||
}).on('contextmenu', (evt) => {
|
||||
})
|
||||
.on('contextmenu', (evt) => {
|
||||
if (options.mode === 'read') return;
|
||||
const { offsetLeft, offsetHeight } = evt.target;
|
||||
this.contextMenu.setOffset({ left: offsetLeft, bottom: offsetHeight + 1 });
|
||||
this.deleteEl = item;
|
||||
}).on('dblclick', () => {
|
||||
})
|
||||
.on('dblclick', () => {
|
||||
if (options.mode === 'read') return;
|
||||
const v = item.html();
|
||||
const input = new FormInput('auto', '');
|
||||
input.val(v);
|
||||
input.input.on('blur', ({ 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.dataNames.splice(nindex, 1, value);
|
||||
@ -155,7 +155,7 @@ export default class Bottombar {
|
||||
deleteItem() {
|
||||
const { activeEl, deleteEl } = this;
|
||||
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.dataNames.splice(index, 1);
|
||||
this.menuEl.removeChild(deleteEl.el);
|
||||
@ -172,7 +172,7 @@ export default class Bottombar {
|
||||
}
|
||||
|
||||
clickSwap2(item) {
|
||||
const index = this.items.findIndex(it => it === item);
|
||||
const index = this.items.findIndex((it) => it === item);
|
||||
this.clickSwap(item);
|
||||
this.activeEl.toggle();
|
||||
this.swapFunc(index);
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { Element } from './element';
|
||||
import { HComponent } from './element';
|
||||
import { cssPrefix } from '../config';
|
||||
import { t } from '../locale/locale';
|
||||
|
||||
export default class Button extends Element {
|
||||
export default class Button extends HComponent {
|
||||
// type: primary
|
||||
constructor(title, 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 { cssPrefix } from '../config';
|
||||
|
||||
export default class Dropdown extends Element {
|
||||
export default class Dropdown extends HComponent {
|
||||
constructor(title, width, showArrow, placement, ...children) {
|
||||
super('div', `${cssPrefix}-dropdown ${placement}`);
|
||||
this.title = title;
|
||||
@ -13,24 +13,22 @@ export default class Dropdown extends Element {
|
||||
} else if (showArrow) {
|
||||
this.title.addClass('arrow-left');
|
||||
}
|
||||
this.contentEl = h('div', `${cssPrefix}-dropdown-content`)
|
||||
.css('width', width)
|
||||
.hide();
|
||||
this.contentEl = h('div', `${cssPrefix}-dropdown-content`).css('width', width).hide();
|
||||
|
||||
this.setContentChildren(...children);
|
||||
|
||||
this.headerEl = h('div', `${cssPrefix}-dropdown-header`);
|
||||
this.headerEl.on('click', () => {
|
||||
this.headerEl
|
||||
.on('click', () => {
|
||||
if (this.contentEl.css('display') !== 'block') {
|
||||
this.show();
|
||||
} else {
|
||||
this.hide();
|
||||
}
|
||||
}).children(
|
||||
})
|
||||
.children(
|
||||
this.title,
|
||||
showArrow ? h('div', `${cssPrefix}-icon arrow-right`).child(
|
||||
h('div', `${cssPrefix}-icon-img arrow-down`),
|
||||
) : '',
|
||||
showArrow ? h('div', `${cssPrefix}-icon arrow-right`).child(h('div', `${cssPrefix}-icon-img arrow-down`)) : '',
|
||||
);
|
||||
this.children(this.headerEl, this.contentEl);
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import _ from 'lodash';
|
||||
class Element {
|
||||
private el: HTMLElement;
|
||||
el: HTMLElement;
|
||||
private _data: Record<string, any>;
|
||||
constructor(tag: HTMLElement | string, className = '') {
|
||||
if (typeof tag === 'string') {
|
||||
@ -23,7 +24,7 @@ class Element {
|
||||
const [fen, ...oen] = eventNames.split('.');
|
||||
const eventName = fen;
|
||||
this.el.addEventListener(eventName, (evt) => {
|
||||
console.log('excel debug', eventName, eventNames, oen, evt);
|
||||
// console.log('excel debug', eventName, eventNames, oen, evt);
|
||||
handler(evt);
|
||||
for (let i = 0; i < oen.length; i += 1) {
|
||||
const k = oen[i];
|
||||
@ -58,7 +59,7 @@ class Element {
|
||||
};
|
||||
}
|
||||
|
||||
scroll(v?) {
|
||||
scroll = _.throttle((v?) => {
|
||||
const { el } = this;
|
||||
if (v !== undefined) {
|
||||
if (v.left !== undefined) {
|
||||
@ -69,7 +70,7 @@ class Element {
|
||||
}
|
||||
}
|
||||
return { left: el.scrollLeft, top: el.scrollTop };
|
||||
}
|
||||
}, 10);
|
||||
|
||||
box() {
|
||||
return this.el.getBoundingClientRect();
|
||||
@ -240,4 +241,4 @@ class Element {
|
||||
|
||||
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) {
|
||||
const push = () => {
|
||||
const currentListener = listeners.get(eventName);
|
||||
return (Array.isArray(currentListener)
|
||||
&& currentListener.push(callback))
|
||||
|| false;
|
||||
return (Array.isArray(currentListener) && currentListener.push(callback)) || false;
|
||||
};
|
||||
|
||||
const create = () => listeners.set(eventName, [].concat(callback));
|
||||
|
||||
return (listeners.has(eventName)
|
||||
&& push())
|
||||
|| create();
|
||||
return (listeners.has(eventName) && push()) || create();
|
||||
}
|
||||
|
||||
function fire(eventName, args) {
|
||||
@ -109,22 +105,22 @@ export function createEventEmitter() {
|
||||
for (const callback of currentListener) callback.call(null, ...args);
|
||||
};
|
||||
|
||||
return listeners.has(eventName)
|
||||
&& exec();
|
||||
return listeners.has(eventName) && exec();
|
||||
}
|
||||
|
||||
function removeListener(eventName, callback) {
|
||||
const remove = () => {
|
||||
const currentListener = listeners.get(eventName);
|
||||
const idx = currentListener.indexOf(callback);
|
||||
return (idx >= 0)
|
||||
&& currentListener.splice(idx, 1)
|
||||
&& listeners.get(eventName).length === 0
|
||||
&& listeners.delete(eventName);
|
||||
return (
|
||||
idx >= 0 &&
|
||||
currentListener.splice(idx, 1) &&
|
||||
listeners.get(eventName).length === 0 &&
|
||||
listeners.delete(eventName)
|
||||
);
|
||||
};
|
||||
|
||||
return listeners.has(eventName)
|
||||
&& remove();
|
||||
return listeners.has(eventName) && remove();
|
||||
}
|
||||
|
||||
function once(eventName, callback) {
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Element, h } from './element';
|
||||
import { HComponent, h } from './element';
|
||||
import { cssPrefix } from '../config';
|
||||
|
||||
export default class Icon extends Element {
|
||||
export default class Icon extends HComponent {
|
||||
constructor(name) {
|
||||
super('div', `${cssPrefix}-icon`);
|
||||
this.iconNameEl = h('div', `${cssPrefix}-icon-img ${name}`);
|
||||
|
@ -2,7 +2,7 @@
|
||||
import { h } from './element';
|
||||
import { cssPrefix } from '../config';
|
||||
import Button from './button';
|
||||
import { Draw } from '../canvas/draw';
|
||||
import { Draw } from '../canvas';
|
||||
import { renderCell } from './table';
|
||||
import { t } from '../locale/locale';
|
||||
|
||||
@ -16,7 +16,7 @@ const PAGER_SIZES = [
|
||||
['A3', 11.69, 16.54],
|
||||
['A4', 8.27, 11.69],
|
||||
['A5', 5.83, 8.27],
|
||||
['B4', 9.84, 13.90],
|
||||
['B4', 9.84, 13.9],
|
||||
['B5', 6.93, 9.84],
|
||||
];
|
||||
|
||||
@ -68,8 +68,7 @@ export default class Print {
|
||||
this.data = data;
|
||||
this.el = h('div', `${cssPrefix}-print`)
|
||||
.children(
|
||||
h('div', `${cssPrefix}-print-bar`)
|
||||
.children(
|
||||
h('div', `${cssPrefix}-print-bar`).children(
|
||||
h('div', '-title').child('Print settings'),
|
||||
h('div', '-right').children(
|
||||
h('div', `${cssPrefix}-buttons`).children(
|
||||
@ -78,27 +77,37 @@ export default class Print {
|
||||
),
|
||||
),
|
||||
),
|
||||
h('div', `${cssPrefix}-print-content`)
|
||||
.children(
|
||||
this.contentEl = h('div', '-content'),
|
||||
h('div', `${cssPrefix}-print-content`).children(
|
||||
(this.contentEl = h('div', '-content')),
|
||||
h('div', '-sider').child(
|
||||
h('form', '').children(
|
||||
h('fieldset', '').children(
|
||||
h('label', '').child(`${t('print.size')}`),
|
||||
h('select', '').children(
|
||||
...PAGER_SIZES.map((it, index) => h('option', '').attr('value', index).child(`${it[0]} ( ${it[1]}''x${it[2]}'' )`)),
|
||||
).on('change', pagerSizeChange.bind(this)),
|
||||
h('select', '')
|
||||
.children(
|
||||
...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('label', '').child(`${t('print.orientation')}`),
|
||||
h('select', '').children(
|
||||
...PAGER_ORIENTATIONS.map((it, index) => h('option', '').attr('value', index).child(`${t('print.orientations')[index]}`)),
|
||||
).on('change', pagerOrientationChange.bind(this)),
|
||||
h('select', '')
|
||||
.children(
|
||||
...PAGER_ORIENTATIONS.map((it, index) =>
|
||||
h('option', '')
|
||||
.attr('value', index)
|
||||
.child(`${t('print.orientations')[index]}`),
|
||||
),
|
||||
)
|
||||
.on('change', pagerOrientationChange.bind(this)),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
).hide();
|
||||
)
|
||||
.hide();
|
||||
}
|
||||
|
||||
resetData(data) {
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { h, Element } from './element';
|
||||
import { h, HComponent } from './element';
|
||||
import { cssPrefix } from '../config';
|
||||
|
||||
export default class Scrollbar {
|
||||
private el: Element;
|
||||
private contentEl: Element;
|
||||
private el: HComponent;
|
||||
private contentEl: HComponent;
|
||||
private _moveFn: (type: string, handler: (e: Event) => void) => void;
|
||||
constructor(private vertical: 'vertical' | 'horizontal') {
|
||||
this._moveFn = null;
|
||||
|
@ -4,15 +4,13 @@ import _cell from '../core/cell';
|
||||
import { formulam } from '../core/formula';
|
||||
import { formatm } from '../core/format';
|
||||
|
||||
import {
|
||||
Draw, DrawBox, thinLineWidth, npx,
|
||||
} from '../canvas/draw';
|
||||
import { Draw, DrawBox, thinLineWidth, npx } from '../canvas';
|
||||
// gobal var
|
||||
const cellPaddingWidth = 5;
|
||||
const tableFixedHeaderCleanStyle = { fillStyle: '#f4f5f8' };
|
||||
const tableGridStyle = {
|
||||
fillStyle: '#fff',
|
||||
lineWidth: thinLineWidth,
|
||||
lineWidth: thinLineWidth(),
|
||||
strokeStyle: '#e6e6e6',
|
||||
};
|
||||
function tableFixedHeaderStyle() {
|
||||
@ -27,9 +25,7 @@ function tableFixedHeaderStyle() {
|
||||
}
|
||||
|
||||
function getDrawBox(data, rindex, cindex, yoffset = 0) {
|
||||
const {
|
||||
left, top, width, height,
|
||||
} = data.cellRect(rindex, cindex);
|
||||
const { left, top, width, height } = data.cellRect(rindex, cindex);
|
||||
return new DrawBox(left, top + yoffset, width, height, cellPaddingWidth);
|
||||
}
|
||||
/*
|
||||
@ -76,7 +72,7 @@ export function renderCell(draw, data, rindex, cindex, yoffset = 0) {
|
||||
// render text
|
||||
let cellText = '';
|
||||
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 {
|
||||
cellText = cell.text || '';
|
||||
}
|
||||
@ -87,14 +83,19 @@ export function renderCell(draw, data, rindex, cindex, yoffset = 0) {
|
||||
const font = Object.assign({}, style.font);
|
||||
font.size = getFontSizePxByPt(font.size);
|
||||
// console.log('style:', style);
|
||||
draw.text(cellText, dbox, {
|
||||
draw.text(
|
||||
cellText,
|
||||
dbox,
|
||||
{
|
||||
align: style.align,
|
||||
valign: style.valign,
|
||||
font,
|
||||
color: style.color,
|
||||
strike: style.strike,
|
||||
underline: style.underline,
|
||||
}, style.textwrap);
|
||||
},
|
||||
style.textwrap,
|
||||
);
|
||||
// error
|
||||
const error = data.validations.getError(rindex, cindex);
|
||||
if (error) {
|
||||
@ -125,8 +126,7 @@ function renderAutofilter(viewRange) {
|
||||
function renderContent(viewRange, fw, fh, tx, ty) {
|
||||
const { draw, data } = this;
|
||||
draw.save();
|
||||
draw.translate(fw, fh)
|
||||
.translate(tx, ty);
|
||||
draw.translate(fw, fh).translate(tx, ty);
|
||||
|
||||
const { exceptRowSet } = data;
|
||||
// const exceptRows = Array.from(exceptRowSet);
|
||||
@ -143,12 +143,14 @@ function renderContent(viewRange, fw, fh, tx, ty) {
|
||||
// 1 render cell
|
||||
draw.save();
|
||||
draw.translate(0, -exceptRowTotalHeight);
|
||||
viewRange.each((ri, ci) => {
|
||||
viewRange.each(
|
||||
(ri, ci) => {
|
||||
renderCell(draw, data, ri, ci);
|
||||
}, ri => filteredTranslateFunc(ri));
|
||||
},
|
||||
(ri) => filteredTranslateFunc(ri),
|
||||
);
|
||||
draw.restore();
|
||||
|
||||
|
||||
// 2 render mergeCell
|
||||
const rset = new Set();
|
||||
draw.save();
|
||||
@ -173,8 +175,7 @@ function renderContent(viewRange, fw, fh, tx, ty) {
|
||||
function renderSelectedHeaderCell(x, y, w, h) {
|
||||
const { draw } = this;
|
||||
draw.save();
|
||||
draw.attr({ fillStyle: 'rgba(75, 137, 255, 0.08)' })
|
||||
.fillRect(x, y, w, h);
|
||||
draw.attr({ fillStyle: 'rgba(75, 137, 255, 0.08)' }).fillRect(x, y, w, h);
|
||||
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 === 'top') draw.fillRect(ntx, 0, sumWidth, h);
|
||||
|
||||
const {
|
||||
sri, sci, eri, eci,
|
||||
} = data.selector.range;
|
||||
const { sri, sci, eri, eci } = data.selector.range;
|
||||
// console.log(data.selectIndexes);
|
||||
// draw text
|
||||
// text font, align...
|
||||
@ -213,7 +212,7 @@ function renderFixedHeaders(type, viewRange, w, h, tx, ty) {
|
||||
if (sri <= ii && ii < eri + 1) {
|
||||
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)) {
|
||||
draw.save();
|
||||
draw.attr({ strokeStyle: '#c6c6c6' });
|
||||
@ -233,7 +232,7 @@ function renderFixedHeaders(type, viewRange, w, h, tx, ty) {
|
||||
if (sci <= ii && ii < eci + 1) {
|
||||
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)) {
|
||||
draw.save();
|
||||
draw.attr({ strokeStyle: '#c6c6c6' });
|
||||
@ -251,20 +250,16 @@ function renderFixedLeftTopCell(fw, fh) {
|
||||
const { draw } = this;
|
||||
draw.save();
|
||||
// left-top-cell
|
||||
draw.attr({ fillStyle: '#f4f5f8' })
|
||||
.fillRect(0, 0, fw, fh);
|
||||
draw.attr({ fillStyle: '#f4f5f8' }).fillRect(0, 0, fw, fh);
|
||||
draw.restore();
|
||||
}
|
||||
|
||||
function renderContentGrid({
|
||||
sri, sci, eri, eci, w, h,
|
||||
}, fw, fh, tx, ty) {
|
||||
function renderContentGrid({ sri, sci, eri, eci, w, h }, fw, fh, tx, ty) {
|
||||
const { draw, data } = this;
|
||||
const { settings } = data;
|
||||
|
||||
draw.save();
|
||||
draw.attr(tableGridStyle)
|
||||
.translate(fw + tx, fh + ty);
|
||||
draw.attr(tableGridStyle).translate(fw + tx, fh + ty);
|
||||
// const sumWidth = cols.sumWidth(sci, eci + 1);
|
||||
// const sumHeight = rows.sumHeight(sri, eri + 1);
|
||||
// console.log('sumWidth:', sumWidth);
|
||||
@ -290,9 +285,7 @@ function renderFreezeHighlightLine(fw, fh, ftw, fth) {
|
||||
const { draw, data } = this;
|
||||
const twidth = data.viewWidth() - fw;
|
||||
const theight = data.viewHeight() - fh;
|
||||
draw.save()
|
||||
.translate(fw, fh)
|
||||
.attr({ strokeStyle: 'rgba(75, 137, 255, .6)' });
|
||||
draw.save().translate(fw, fh).attr({ strokeStyle: 'rgba(75, 137, 255, .6)' });
|
||||
draw.line([0, fth], [twidth, fth]);
|
||||
draw.line([ftw, 0], [ftw, theight]);
|
||||
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,
|
||||
CustomAssociatedFieldInterface,
|
||||
SignaturePadFieldInterface,
|
||||
ExcelFieldInterface,
|
||||
} from './interfaces';
|
||||
import {
|
||||
AssociatedField,
|
||||
@ -41,6 +42,7 @@ import {
|
||||
customComponentDispatcherSettings,
|
||||
Expression,
|
||||
SignatureInput,
|
||||
ExcelFile,
|
||||
} from './components';
|
||||
import { AutoComplete, InternalPDFViewer } from './schema-components';
|
||||
import {
|
||||
@ -192,6 +194,7 @@ export class PluginCoreClient extends Plugin {
|
||||
SheetBlockProvider,
|
||||
SheetBlockToolbar,
|
||||
SignatureInput,
|
||||
ExcelFile,
|
||||
});
|
||||
}
|
||||
|
||||
@ -274,6 +277,7 @@ export class PluginCoreClient extends Plugin {
|
||||
CustomFieldInterface,
|
||||
CustomAssociatedFieldInterface,
|
||||
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 './CalcFieldInterface';
|
||||
export * from './CustomFieldInterface';
|
||||
|
@ -13,7 +13,7 @@ import {
|
||||
import React, { createContext, useRef } from 'react';
|
||||
import { uid } from '@nocobase/utils/client';
|
||||
import { Button, Spin } from 'antd';
|
||||
import Sheet, { SheetRef } from '../../components/Sheet';
|
||||
import Sheet, { SheetRef } from '../../components/excel-table/Sheet';
|
||||
|
||||
export const SheetBlockContext = createContext<any>({});
|
||||
|
||||
|
@ -76,9 +76,16 @@ export class ContractRuleService {
|
||||
appends: ['lease_items', 'lease_items.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) => {
|
||||
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) {
|
||||
throw new Error('方案中存在此产品');
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user