mirror of
https://github.com/ruilisi/fortune-sheet.git
synced 2025-01-09 04:07:33 +08:00
feat: add background picture
This commit is contained in:
parent
ace1b33138
commit
688ad4457d
@ -652,7 +652,7 @@ export class Canvas {
|
||||
const colEndX = this.sheetCtx.visibledatacolumn[colEnd];
|
||||
|
||||
// 表格canvas 初始化处理
|
||||
renderCtx.fillStyle = "#ffffff";
|
||||
renderCtx.fillStyle = "rgba(255,255,255,0)";
|
||||
renderCtx.fillRect(
|
||||
offsetLeft - 1,
|
||||
offsetTop - 1,
|
||||
@ -1641,7 +1641,7 @@ export class Canvas {
|
||||
// }
|
||||
|
||||
if (!fillStyle) {
|
||||
renderCtx.fillStyle = "#FFFFFF";
|
||||
renderCtx.fillStyle = "rgba(255,255,255,0)";
|
||||
} else {
|
||||
renderCtx.fillStyle = fillStyle;
|
||||
}
|
||||
@ -1849,7 +1849,7 @@ export class Canvas {
|
||||
fillStyle = checksCF.cellColor;
|
||||
}
|
||||
if (!fillStyle) {
|
||||
renderCtx.fillStyle = "#FFFFFF";
|
||||
renderCtx.fillStyle = "rgba(255,255,255,0)";
|
||||
} else {
|
||||
renderCtx.fillStyle = fillStyle;
|
||||
}
|
||||
|
@ -10971,6 +10971,12 @@ export default {
|
||||
screenshot: "Screenshot",
|
||||
splitColumn: "Split text",
|
||||
insertImage: "Insert image",
|
||||
setBackgroundPic: "Set background picture",
|
||||
unsetBackgroundPic: "Clear background picture",
|
||||
backgroundSettings: "Background settings",
|
||||
backgroundRepeat: "Background repeat",
|
||||
backgroundNoRepeat: "Background no-repeat",
|
||||
backgroundStretch: "Background stretch",
|
||||
insertLink: "Insert link",
|
||||
dataVerification: "Data verification",
|
||||
protection: "Protect the sheet",
|
||||
|
@ -10954,6 +10954,12 @@ export default {
|
||||
screenshot: "截图",
|
||||
splitColumn: "分列",
|
||||
insertImage: "插入图片",
|
||||
setBackgroundPic: "设置背景",
|
||||
unsetBackgroundPic: "清除背景",
|
||||
backgroundSettings: "背景设置",
|
||||
backgroundRepeat: "背景平铺",
|
||||
backgroundNoRepeat: "背景不重复",
|
||||
backgroundStretch: "背景拉伸填充",
|
||||
insertLink: "插入链接",
|
||||
dataVerification: "数据验证",
|
||||
protection: "保护工作表内容",
|
||||
|
@ -57,6 +57,13 @@ export function showImgChooser() {
|
||||
if (chooser) chooser.click();
|
||||
}
|
||||
|
||||
export function showBgChooser() {
|
||||
const chooser = document.getElementById(
|
||||
"fortune-bg-upload"
|
||||
) as HTMLInputElement;
|
||||
if (chooser) chooser.click();
|
||||
}
|
||||
|
||||
export function saveImage(ctx: Context) {
|
||||
const index = getSheetIndex(ctx, ctx.currentSheetId);
|
||||
if (index == null) return;
|
||||
@ -284,3 +291,46 @@ export function onImageResizeEnd(ctx: Context, globalCache: GlobalCache) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function setBackgroundPic(
|
||||
ctx: Context,
|
||||
image: HTMLImageElement,
|
||||
repeat: "repeat" | "no-repeat" | "stretch" = "no-repeat"
|
||||
) {
|
||||
const index = getSheetIndex(ctx, ctx.currentSheetId);
|
||||
if (index == null || !image.src) return;
|
||||
const file = ctx.luckysheetfile[index];
|
||||
if (!file.backgroundPic) file.backgroundPic = {};
|
||||
file.backgroundPic.src = image.src;
|
||||
if (repeat === "repeat" || repeat === "no-repeat") {
|
||||
file.backgroundPic.repeat = repeat;
|
||||
} else {
|
||||
file.backgroundPic.repeat = "no-repeat";
|
||||
file.backgroundPic.backgroundSize = "100%";
|
||||
}
|
||||
}
|
||||
|
||||
export function clearBackground(ctx: Context) {
|
||||
const index = getSheetIndex(ctx, ctx.currentSheetId);
|
||||
if (index == null) return;
|
||||
const file = ctx.luckysheetfile[index];
|
||||
if (file.backgroundPic) {
|
||||
file.backgroundPic = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export function setBackgroundPicRepeat(
|
||||
ctx: Context,
|
||||
repeat: string = "no-repeat"
|
||||
) {
|
||||
const index = getSheetIndex(ctx, ctx.currentSheetId);
|
||||
if (index == null) return;
|
||||
const file = ctx.luckysheetfile[index];
|
||||
if (!file.backgroundPic) file.backgroundPic = {};
|
||||
if (repeat === "repeat" || repeat === "no-repeat") {
|
||||
file.backgroundPic.repeat = repeat;
|
||||
} else if (repeat === "stretch") {
|
||||
file.backgroundPic.repeat = "no-repeat";
|
||||
file.backgroundPic.backgroundSize = "100%";
|
||||
}
|
||||
}
|
||||
|
@ -156,6 +156,11 @@ export type Sheet = {
|
||||
type: "row" | "column" | "both" | "rangeRow" | "rangeColumn" | "rangeBoth";
|
||||
range?: { row_focus: number; column_focus: number };
|
||||
};
|
||||
backgroundPic?: {
|
||||
src?: string;
|
||||
repeat?: "repeat" | "no-repeat";
|
||||
backgroundSize?: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type CommentBox = {
|
||||
|
@ -3,6 +3,7 @@
|
||||
flex-direction: row;
|
||||
height: 28px;
|
||||
border-bottom: 1px solid #d4d4d4;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.fortune-fx-icon {
|
||||
|
@ -304,7 +304,7 @@ const InputBox: React.FC = () => {
|
||||
? {
|
||||
left: firstSelection.left,
|
||||
top: firstSelection.top,
|
||||
zIndex: _.isEmpty(context.luckysheetCellUpdate) ? -1 : 19,
|
||||
zIndex: _.isEmpty(context.luckysheetCellUpdate) ? -2 : 19,
|
||||
display: "block",
|
||||
}
|
||||
: { left: -10000, top: -10000, display: "block" }
|
||||
|
@ -107,6 +107,8 @@
|
||||
|
||||
.fortune-toolbar-select-option {
|
||||
font-size: 12px;
|
||||
height: 24px;
|
||||
line-height: 24px;
|
||||
min-width: 60px;
|
||||
padding: 8px 12px;
|
||||
cursor: pointer;
|
||||
@ -183,4 +185,29 @@
|
||||
user-select: none;
|
||||
font-family: Arial;
|
||||
line-height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.set-background-sub-menu {
|
||||
position: absolute;
|
||||
top: -8px;
|
||||
box-shadow: 0 2px 4px rgb(0 0 0 / 20%);
|
||||
background: #fff;
|
||||
border: 1px solid rgba(0, 0, 0, .2);
|
||||
cursor: default;
|
||||
font-size: 12px;
|
||||
z-index: 1004;
|
||||
box-sizing: border-box;
|
||||
user-select: none;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.set-background-item {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 6px 18px;
|
||||
z-index: 1005;
|
||||
}
|
||||
|
||||
.set-background-item:hover {
|
||||
background: #efefef;
|
||||
}
|
||||
|
@ -34,6 +34,10 @@ import {
|
||||
createFilter,
|
||||
clearFilter,
|
||||
applyLocation,
|
||||
setBackgroundPic,
|
||||
showBgChooser,
|
||||
clearBackground,
|
||||
setBackgroundPicRepeat,
|
||||
} from "@fortune-sheet/core";
|
||||
import _ from "lodash";
|
||||
import WorkbookContext from "../../context";
|
||||
@ -90,6 +94,62 @@ const Toolbar: React.FC<{
|
||||
} = locale(context);
|
||||
const sheetWidth = context.luckysheetTableContentHW[0];
|
||||
|
||||
const showSubMenu = useCallback(
|
||||
(e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
|
||||
const target = e.target as HTMLDivElement;
|
||||
const menuItem =
|
||||
target.className === "fortune-toolbar-menu-line"
|
||||
? target.parentElement!
|
||||
: target;
|
||||
const menuItemRect = menuItem.getBoundingClientRect();
|
||||
const workbookContainerRect =
|
||||
refs.workbookContainer.current!.getBoundingClientRect();
|
||||
const subMenu = menuItem.querySelector(
|
||||
".set-background-sub-menu"
|
||||
) as HTMLDivElement;
|
||||
if (_.isNil(subMenu)) return;
|
||||
const menuItemStyle = window.getComputedStyle(menuItem);
|
||||
const menuItemPaddingRight = parseFloat(
|
||||
menuItemStyle.getPropertyValue("padding-right").replace("px", "")
|
||||
);
|
||||
|
||||
if (
|
||||
workbookContainerRect.right - menuItemRect.right <
|
||||
parseFloat(subMenu.style.width.replace("px", ""))
|
||||
) {
|
||||
subMenu.style.display = "block";
|
||||
subMenu.style.right = `${menuItemRect.width - menuItemPaddingRight}px`;
|
||||
} else {
|
||||
subMenu.style.display = "block";
|
||||
subMenu.style.right = `${-(
|
||||
parseFloat(subMenu.style.width.replace("px", "")) +
|
||||
menuItemPaddingRight
|
||||
)}px`;
|
||||
}
|
||||
},
|
||||
[refs.workbookContainer]
|
||||
);
|
||||
|
||||
const hideSubMenu = useCallback(
|
||||
(e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
|
||||
const target = e.target as HTMLDivElement;
|
||||
|
||||
if (target.className === "set-background-sub-menu") {
|
||||
target.style.display = "none";
|
||||
return;
|
||||
}
|
||||
|
||||
const subMenu = (
|
||||
target.className === "condition-format-item"
|
||||
? target.parentElement
|
||||
: target.querySelector(".set-background-sub-menu")
|
||||
) as HTMLDivElement;
|
||||
if (_.isNil(subMenu)) return;
|
||||
subMenu.style.display = "none";
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
// rerenders the entire toolbar and trigger recalculation of item locations
|
||||
useEffect(() => {
|
||||
setToolbarWrapIndex(-1);
|
||||
@ -725,41 +785,140 @@ const Toolbar: React.FC<{
|
||||
}
|
||||
if (name === "image") {
|
||||
return (
|
||||
<Button
|
||||
iconId={name}
|
||||
tooltip={toolbar.insertImage}
|
||||
key={name}
|
||||
onClick={() => {
|
||||
if (context.allowEdit === false) return;
|
||||
showImgChooser();
|
||||
}}
|
||||
>
|
||||
<input
|
||||
id="fortune-img-upload"
|
||||
type="file"
|
||||
accept="image/*"
|
||||
style={{ display: "none" }}
|
||||
onChange={(e) => {
|
||||
const file = e.currentTarget.files?.[0];
|
||||
if (!file) return;
|
||||
<Combo iconId={name} tooltip={toolbar.insertImage} key={name}>
|
||||
{(setOpen) => (
|
||||
<Select style={{ overflow: "visible" }}>
|
||||
<Option
|
||||
key="upload-picture"
|
||||
onClick={() => {
|
||||
if (context.allowEdit === false) return;
|
||||
showImgChooser();
|
||||
}}
|
||||
>
|
||||
<div className="fortune-toolbar-menu-line">
|
||||
{toolbar.insertImage}
|
||||
<input
|
||||
id="fortune-img-upload"
|
||||
type="file"
|
||||
accept="image/*"
|
||||
style={{ display: "none" }}
|
||||
onChange={(e) => {
|
||||
const file = e.currentTarget.files?.[0];
|
||||
if (!file) return;
|
||||
|
||||
const render = new FileReader();
|
||||
render.readAsDataURL(file);
|
||||
render.onload = (event) => {
|
||||
if (event.target == null) return;
|
||||
const src = event.target?.result;
|
||||
const image = new Image();
|
||||
image.onload = () => {
|
||||
const render = new FileReader();
|
||||
render.readAsDataURL(file);
|
||||
render.onload = (event) => {
|
||||
if (event.target == null) return;
|
||||
const src = event.target?.result;
|
||||
const image = new Image();
|
||||
image.onload = () => {
|
||||
setContext((draftCtx) => {
|
||||
insertImage(draftCtx, image);
|
||||
});
|
||||
};
|
||||
image.src = src as string;
|
||||
};
|
||||
e.currentTarget.value = "";
|
||||
setOpen(false);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</Option>
|
||||
<MenuDivider key="divider" />
|
||||
<Option
|
||||
key="set-background-picture"
|
||||
onClick={() => {
|
||||
if (context.allowEdit === false) return;
|
||||
showBgChooser();
|
||||
}}
|
||||
>
|
||||
<div className="fortune-toolbar-menu-line">
|
||||
{toolbar.setBackgroundPic}
|
||||
<input
|
||||
id="fortune-bg-upload"
|
||||
type="file"
|
||||
accept="image/*"
|
||||
style={{ display: "none" }}
|
||||
onChange={(e) => {
|
||||
const file = e.currentTarget.files?.[0];
|
||||
if (!file) return;
|
||||
|
||||
const render = new FileReader();
|
||||
render.readAsDataURL(file);
|
||||
render.onload = (event) => {
|
||||
if (event.target == null) return;
|
||||
const src = event.target?.result;
|
||||
const image = new Image();
|
||||
image.onload = () => {
|
||||
setContext((draftCtx) => {
|
||||
setBackgroundPic(draftCtx, image);
|
||||
});
|
||||
};
|
||||
image.src = src as string;
|
||||
};
|
||||
e.currentTarget.value = "";
|
||||
setOpen(false);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</Option>
|
||||
<Option
|
||||
key="clear-background-picture"
|
||||
onClick={() => {
|
||||
if (context.allowEdit === false) return;
|
||||
setContext((draftCtx) => {
|
||||
insertImage(draftCtx, image);
|
||||
clearBackground(draftCtx);
|
||||
});
|
||||
};
|
||||
image.src = src as string;
|
||||
};
|
||||
e.currentTarget.value = "";
|
||||
}}
|
||||
/>
|
||||
</Button>
|
||||
setOpen(false);
|
||||
}}
|
||||
>
|
||||
<div className="fortune-toolbar-menu-line">
|
||||
{toolbar.unsetBackgroundPic}
|
||||
</div>
|
||||
</Option>
|
||||
<Option
|
||||
key="backgroundSetting"
|
||||
onMouseEnter={showSubMenu}
|
||||
onMouseLeave={hideSubMenu}
|
||||
>
|
||||
<div className="fortune-toolbar-menu-line">
|
||||
{toolbar.backgroundSettings}
|
||||
<SVGIcon name="rightArrow" width={18} />
|
||||
<div
|
||||
className="set-background-sub-menu"
|
||||
style={{
|
||||
display: "none",
|
||||
width: 150,
|
||||
}}
|
||||
>
|
||||
{[
|
||||
{ text: toolbar.backgroundRepeat, value: "repeat" },
|
||||
{
|
||||
text: toolbar.backgroundNoRepeat,
|
||||
value: "no-repeat",
|
||||
},
|
||||
{ text: toolbar.backgroundStretch, value: "stretch" },
|
||||
].map((v) => (
|
||||
<div
|
||||
className="set-background-item"
|
||||
key={v.text}
|
||||
onClick={() => {
|
||||
setContext((draftCtx) => {
|
||||
setBackgroundPicRepeat(draftCtx, v.value);
|
||||
});
|
||||
setOpen(false);
|
||||
}}
|
||||
>
|
||||
{v.text}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</Option>
|
||||
</Select>
|
||||
)}
|
||||
</Combo>
|
||||
);
|
||||
}
|
||||
if (name === "comment") {
|
||||
@ -1318,6 +1477,9 @@ const Toolbar: React.FC<{
|
||||
context.allowEdit,
|
||||
comment,
|
||||
fontarray,
|
||||
hideSubMenu,
|
||||
showSubMenu,
|
||||
refs.canvas,
|
||||
]
|
||||
);
|
||||
|
||||
|
@ -6,7 +6,6 @@
|
||||
padding: 0;
|
||||
flex-direction: column;
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, "PingFang SC", "Hiragino Sans GB", "Heiti SC", "Microsoft YaHei", "WenQuanYi Micro Hei", sans-serif;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.fortune-workarea {
|
||||
@ -28,3 +27,11 @@
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.sheet-background {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
background-color: white;
|
||||
}
|
||||
|
@ -678,6 +678,18 @@ const Workbook = React.forwardRef<WorkbookInstance, Settings & AdditionalProps>(
|
||||
)}
|
||||
{mergedSettings.showFormulaBar && <FxEditor />}
|
||||
</div>
|
||||
<div
|
||||
className="sheet-background"
|
||||
style={{
|
||||
backgroundImage: `url(${sheet.backgroundPic?.src})`,
|
||||
backgroundSize: sheet.backgroundPic?.backgroundSize,
|
||||
backgroundRepeat: sheet.backgroundPic?.repeat,
|
||||
top: cellArea.current?.getBoundingClientRect().y,
|
||||
left: cellArea.current?.getBoundingClientRect().x,
|
||||
height: cellArea.current?.getBoundingClientRect().height,
|
||||
width: cellArea.current?.getBoundingClientRect().width,
|
||||
}}
|
||||
/>
|
||||
<Sheet sheet={sheet} />
|
||||
{mergedSettings.showSheetTabs && <SheetTab />}
|
||||
<ContextMenu />
|
||||
|
Loading…
Reference in New Issue
Block a user