feat(ooxml): 支持艺术字部分样式;修复外部数据变更不会重新渲染问题 (#6702)

This commit is contained in:
吴多益 2023-04-24 23:53:33 +08:00 committed by GitHub
parent 7428ea8620
commit 1692273925
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 114 additions and 3699 deletions

View File

@ -87,12 +87,12 @@ Word 渲染支持以下功能:
分页渲染的其它设置项 分页渲染的其它设置项
| 属性名 | 类型 | 默认值 | 说明 | | 属性名 | 类型 | 默认值 | 说明 |
| ------------------ | --------- | --------- | ------------------------------------------ | --- | | ------------------ | --------- | --------- | ------------------------------------------ |
| page | `boolean` | false | 是否开启分页渲染 | | page | `boolean` | false | 是否开启分页渲染 |
| pageMarginBottom | `number` | 20 | 页面上下间距 | | pageMarginBottom | `number` | 20 | 页面上下间距 |
| pageBackground | `string` | '#FFF' | 页面内背景色 | | pageBackground | `string` | '#FFF' | 页面内背景色 |
| pageShadow | `boolean` | true | 是否显示阴影 | | pageShadow | `boolean` | true | 是否显示阴影 |
| pageWrap | `boolean` | true | 是否显示页面包裹 | | | pageWrap | `boolean` | true | 是否显示页面包裹 |
| pageWrapBackground | `string` | '#ECECEC' | 页面包裹的背景色 | | pageWrapBackground | `string` | '#ECECEC' | 页面包裹的背景色 |
| zoom | `number` | | 缩放比例,取值 0-1 之间 | | zoom | `number` | | 缩放比例,取值 0-1 之间 |
| zoomFitWidth | `boolean` | false | 自适应宽度缩放,如果设置了 zoom 将不会生效 | | zoomFitWidth | `boolean` | false | 自适应宽度缩放,如果设置了 zoom 将不会生效 |
@ -194,54 +194,51 @@ Word 渲染支持以下功能:
```schema ```schema
{ {
"type": "page", "type": "page",
"data": {
"users": [
{
name: 'u1',
age: 10
},
{
name: 'u2',
age: 11
}
]
},
"body": [ "body": [
{ {
"type": "office-viewer", "type": "office-viewer",
"src": "/examples/static/table-list.docx", "id": "office-viewer-table-list",
"wordOptions": { "src": "/examples/static/table-list.docx",
"padding": "8px" "wordOptions": {
} "padding": "8px"
},
{
"type": "action",
"label": "下载文档",
"onEvent": {
"click": {
"actions": [
{
"actionType": "saveAs",
"componentId": "office-viewer-table-list"
}
]
}
} }
}, },
{ {
"type": "office-viewer", "type": "service",
"id": "office-viewer-table-list", "api": "/api/mock2/sample/mirror?json=%7B%22users%22%3A%5B%7B%22name%22%3A%22u1%22%2C%22age%22%3A10%7D%2C%7B%22name%22%3A%22u2%22%2C%22age%22%3A11%7D%5D%7D",
"src": "/examples/static/table-list.docx", "body": [{
"wordOptions": { "type": "office-viewer",
"padding": "8px", "src": "/examples/static/table-list.docx",
"enableVar": true, "wordOptions": {
"ignoreWidth": true "padding": "8px",
} "enableVar": true,
}] "ignoreWidth": true
},
"trackExpression": "${users}"
}]
},
{
"type": "action",
"label": "下载文档",
"onEvent": {
"click": {
"actions": [
{
"actionType": "saveAs",
"componentId": "office-viewer-table-list"
}
]
}
}
},
]
} }
``` ```
循环的语法是以 `{{#name}}` 开始,`{{/}}` 结束,在这期间的变量会取循环内的值 循环的语法是以 `{{#name}}` 开始,`{{/}}` 结束,在这期间的变量会取循环内的值。
注意上面的例子用到了 `trackExpression`,默认情况下如果设置了 `enableVar`,每次上层数据变化都会重新渲染文档,如果文档较大可能会有性能问题,这时可以通过配置 `trackExpression` 来限制只有某个数据变化时才重新渲染。
## 不渲染模式 ## 不渲染模式

View File

@ -263,12 +263,20 @@ function bulkUpdate2(req, res) {
function mirror(req, res) { function mirror(req, res) {
const json = JSON.parse(req.query.json); const json = JSON.parse(req.query.json);
console.log('mirror', json); console.log('mirror', json);
if ('status' in json) {
return res.json(json); const response = () => {
} else { if ('status' in json) {
return res.json({ return res.json(json);
status: 0, } else {
data: json return res.json({
}); status: 0,
data: json
});
}
};
if (req.query.waitSeconds) {
return setTimeout(response, parseInt(req.query.waitSeconds, 10) * 1000);
} }
return response();
} }

View File

@ -85,7 +85,7 @@
"rollup-pluginutils": "^2.8.2", "rollup-pluginutils": "^2.8.2",
"setprototypeof": "^1.2.0", "setprototypeof": "^1.2.0",
"ts-jest": "^29.0.2", "ts-jest": "^29.0.2",
"vite": "^4.2.1", "vite": "^4.3.1",
"vite-plugin-monaco-editor": "^1.1.0", "vite-plugin-monaco-editor": "^1.1.0",
"vite-plugin-svgr": "^2.2.2", "vite-plugin-svgr": "^2.2.2",
"zrender": "^5.3.2" "zrender": "^5.3.2"

View File

@ -7,6 +7,7 @@ import {BaseSchema} from '../Schema';
import { import {
ActionObject, ActionObject,
createObject, createObject,
filter,
isApiOutdated, isApiOutdated,
IScopedContext, IScopedContext,
Renderer, Renderer,
@ -28,7 +29,7 @@ export interface OfficeViewerSchema extends BaseSchema {
/** /**
* word * word
*/ */
wordOptions?: {}; wordOptions?: any;
/** /**
* *
@ -86,8 +87,18 @@ export default class OfficeViewer extends React.Component<
this.renderWord(); this.renderWord();
} }
// 这个变量替换只会更新变化的部分,所以性能还能接受 if (props.wordOptions?.enableVar) {
this.word?.updateVariable(); if (
props.trackExpression &&
filter(props.trackExpression, props.data) !==
filter(prevProps.trackExpression, prevProps.data)
) {
this.renderWord();
} else {
// 目前 word 渲染比较快,所以全量渲染性能可以接受
this.renderWord();
}
}
} }
/** /**

File diff suppressed because it is too large Load Diff

View File

@ -61,7 +61,6 @@
"ts-loader": "^9.2.3", "ts-loader": "^9.2.3",
"ts-node": "^10.4.0", "ts-node": "^10.4.0",
"typescript": "^4.3.5", "typescript": "^4.3.5",
"vite": "^3.2.2",
"xml-formatter": "^3.3.2" "xml-formatter": "^3.3.2"
}, },
"jest": { "jest": {

View File

@ -66,10 +66,12 @@ export function parseChildColor(word: Word, element: Element): string {
return srgbClr; return srgbClr;
case 'a:schemeClr': case 'a:schemeClr':
const schemeClr = colorChild.getAttribute('val') || ''; case 'w14:schemeClr':
const schemeClr = getVal(colorChild);
if (schemeClr) { if (schemeClr) {
return changeLum(colorChild, word.getThemeColor(schemeClr)); return changeLum(colorChild, word.getThemeColor(schemeClr));
} }
break;
default: default:
console.warn( console.warn(

View File

@ -342,7 +342,17 @@ export function parsePr(word: Word, element: Element, type: 'r' | 'p' = 'p') {
break; break;
case 'w:rPr': case 'w:rPr':
// TODO: 这个有时候会不正确,需要再看看 // TODO: 这个有时候和 r 里的 rPr 不一致,不知道如何处理
const reflection = child.getElementsByTagName('w14:reflection').item(0);
if (reflection) {
// css 只支持在块级节点设置
// 只支持一小部分设置项,另外因为只支持块级别的情况,所以看起来差异较大
const reflectionDistance =
parseSize(reflection, 'w4:dist', LengthUsage.Emu) || '0px';
style[
'-webkit-box-reflect'
] = `below ${reflectionDistance} linear-gradient(transparent, white)`;
}
break; break;
case 'w:rStyle': case 'w:rStyle':
@ -429,26 +439,45 @@ export function parsePr(word: Word, element: Element, type: 'r' | 'p' = 'p') {
case 'w:outline': case 'w:outline':
style['text-shadow'] = style['text-shadow'] =
'-1pt -1pt 0 #AAA, 1pt -1pt 0 #AAA, -1pt 1pt 0 #AAA, 1pt 1pt 0 #AAA'; '-1px -1px 0 #AAA, 1px -1px 0 #AAA, -1px 1px 0 #AAA, 1px 1px 0 #AAA';
break; break;
case 'w:shadown': case 'w:shadown':
case 'w:imprint': case 'w:imprint':
if (getValBoolean(child, true)) { if (getValBoolean(child, true)) {
style['text-shadow'] = '1pt 1pt 2pt rgba(0, 0, 0, 0.6)'; style['text-shadow'] = '1px 1px 2px rgba(0, 0, 0, 0.6)';
} }
break; break;
case 'w14:shadow': case 'w14:shadow':
const blurRad = const blurRad =
parseSize(child, 'w14:blurRad', LengthUsage.Emu) || '2pt'; parseSize(child, 'w14:blurRad', LengthUsage.Emu) || '4px';
// 其它结果算出来不像就先忽略了 // 其它结果算出来不像就先忽略了
let color = 'rgba(0, 0, 0, 0.6)'; let color = 'rgba(0, 0, 0, 0.6)';
const childColor = parseChildColor(word, child); const childColor = parseChildColor(word, child);
if (childColor) { if (childColor) {
color = childColor; color = childColor;
} }
style['text-shadow'] = `1pt 1pt ${blurRad} ${color}`; style['text-shadow'] = `1px 1px ${blurRad} ${color}`;
break;
case 'w14:textOutline':
const outlineWidth =
parseSize(child, 'w14:w', LengthUsage.Emu) || '1px';
style['-webkit-text-stroke-width'] = outlineWidth;
let outlineColor = 'white';
const fillColor = child.getElementsByTagName('w14:solidFill');
if (fillColor.length > 0) {
outlineColor = parseChildColor(word, fillColor.item(0)!) || 'white';
}
style['-webkit-text-stroke-color'] = outlineColor;
break;
case 'w14:reflection':
// 在 rPr 里处理了
break; break;
default: default:

View File

@ -30,6 +30,11 @@ function generateDefaultStyle(word: Word) {
/** docDefaults **/ /** docDefaults **/
.${classPrefix} {
--docx-theme-font-minorHAnsi: Calibri, Helvetica, Arial, 'Helvetica Neue';
--docx-theme-font-minorEastAsia: 'PingFang SC', 'Microsoft YaHei', 'Hiragino Sans GB', 'STHeiti',
'Microsoft YaHei';
}
.${classPrefix} p { .${classPrefix} p {
margin: 0; margin: 0;