Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
330f4e96ce | ||
|
|
a10969b562 | ||
|
|
f3d60360ac | ||
|
|
5035647c91 | ||
|
|
d4d86eb1da | ||
|
|
8ab3f479a7 | ||
|
|
f3853e31d4 | ||
|
|
0ed04eb3ea | ||
|
|
8f507256ef | ||
|
|
f4adac77c5 | ||
|
|
58ee40f71a | ||
|
|
aaa28f1bd8 | ||
|
|
dd916dcf9e | ||
|
|
0e60f7525b | ||
|
|
a2c7f23107 | ||
|
|
d079a4b4cc | ||
|
|
56daab6b67 | ||
|
|
8802b00bdc | ||
|
|
7735bb75de | ||
|
|
46ecb17956 | ||
|
|
eec8c8c215 | ||
|
|
cff5b12975 | ||
|
|
37839c1e86 | ||
|
|
0d05e9f0a8 | ||
|
|
15575b37d6 | ||
|
|
ec7a44d6fb | ||
|
|
c1a9c2fcda | ||
|
|
17d232dcef | ||
|
|
768ac12740 | ||
|
|
ba8d17ba62 | ||
|
|
355b01b9dc |
@@ -0,0 +1,451 @@
|
|||||||
|
# PptxGenJS v4.0.1 完整 API 参考
|
||||||
|
|
||||||
|
> 从 TypeScript 类型定义生成
|
||||||
|
|
||||||
|
## 1. 初始化
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const pptxgen = require('pptxgenjs');
|
||||||
|
const pres = new pptxgen();
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. 布局设置
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// 自定义布局
|
||||||
|
pres.defineLayout({ name: 'CUSTOM', width: 10, height: 5.625 });
|
||||||
|
pres.layout = 'CUSTOM';
|
||||||
|
|
||||||
|
// 预设布局
|
||||||
|
// 'LAYOUT_16x9' (10x5.63)
|
||||||
|
// 'LAYOUT_4x3' (10x7.5)
|
||||||
|
// 'LAYOUT_16x10' (10x6.25)
|
||||||
|
// 'LAYOUT_WIDE' (13.33x7.5)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. Slide 管理
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface PresSlide {
|
||||||
|
addChart()
|
||||||
|
addImage()
|
||||||
|
addMedia()
|
||||||
|
addNotes()
|
||||||
|
addShape()
|
||||||
|
addTable()
|
||||||
|
addText()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. addText — 核心方法
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
slide.addText(text, options);
|
||||||
|
// text: string | number | Array<{text, options}>
|
||||||
|
// options: TextPropsOptions
|
||||||
|
```
|
||||||
|
|
||||||
|
### 位置 (继承自 PositionProps)
|
||||||
|
|
||||||
|
| 属性 | 类型 | 说明 | 默认值 |
|
||||||
|
|------|------|------|--------|
|
||||||
|
|
||||||
|
### 文本样式 (继承自 TextBaseProps)
|
||||||
|
|
||||||
|
| 属性 | 类型 | 说明 | 默认值 |
|
||||||
|
|------|------|------|--------|
|
||||||
|
|
||||||
|
### TextPropsOptions 特有属性
|
||||||
|
|
||||||
|
| 属性 | 类型 | 说明 | 默认值 |
|
||||||
|
|------|------|------|--------|
|
||||||
|
|
||||||
|
## 5. 图片
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
slide.addImage({
|
||||||
|
path: '/path/to/image.png', // 本地路径或URL
|
||||||
|
data: 'image/png;base64,...', // 或 base64
|
||||||
|
x: 1, y: 1, w: 8, h: 4.5
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## 6. 形状
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
type SHAPE_NAME = | 'accentBorderCallout1'
|
||||||
|
| 'accentBorderCallout2'
|
||||||
|
| 'accentBorderCallout3'
|
||||||
|
| 'accentCallout1'
|
||||||
|
| 'accentCallout2'
|
||||||
|
| 'accentCallout3'
|
||||||
|
| 'actionButtonBackPrevious'
|
||||||
|
| 'actionButtonBeginning'
|
||||||
|
| 'actionButtonBlank'
|
||||||
|
| 'actionButtonDocument'
|
||||||
|
| 'actionButtonEnd'
|
||||||
|
| 'actionButtonForwardNext'
|
||||||
|
| 'actionButtonHelp'
|
||||||
|
| 'actionButtonHome'
|
||||||
|
| 'actionButtonInformation'
|
||||||
|
| 'actionButtonMovie'
|
||||||
|
| 'actionButtonReturn'
|
||||||
|
| 'actionButtonSound'
|
||||||
|
| 'arc'
|
||||||
|
| 'bentArrow'
|
||||||
|
| 'bentUpArrow'
|
||||||
|
| 'bevel'
|
||||||
|
| 'blockArc'
|
||||||
|
| 'borderCallout1'
|
||||||
|
| 'borderCallout2'
|
||||||
|
| 'borderCallout3'
|
||||||
|
| 'bracePair'
|
||||||
|
| 'bracketPair'
|
||||||
|
| 'callout1'
|
||||||
|
| 'callout2'
|
||||||
|
| 'callout3'
|
||||||
|
| 'can'
|
||||||
|
| 'chartPlus'
|
||||||
|
| 'chartStar'
|
||||||
|
| 'chartX'
|
||||||
|
| 'chevron'
|
||||||
|
| 'chord'
|
||||||
|
| 'circularArrow'
|
||||||
|
| 'cloud'
|
||||||
|
| 'cloudCallout'
|
||||||
|
| 'corner'
|
||||||
|
| 'cornerTabs'
|
||||||
|
| 'cube'
|
||||||
|
| 'curvedDownArrow'
|
||||||
|
| 'curvedLeftArrow'
|
||||||
|
| 'curvedRightArrow'
|
||||||
|
| 'curvedUpArrow'
|
||||||
|
| 'decagon'
|
||||||
|
| 'diagStripe'
|
||||||
|
| 'diamond'
|
||||||
|
| 'dodecagon'
|
||||||
|
| 'donut'
|
||||||
|
| 'doubleWave'
|
||||||
|
| 'downArrow'
|
||||||
|
| 'downArrowCallout'
|
||||||
|
| 'ellipse'
|
||||||
|
| 'ellipseRibbon'
|
||||||
|
| 'ellipseRibbon2'
|
||||||
|
| 'flowChartAlternateProcess'
|
||||||
|
| 'flowChartCollate'
|
||||||
|
| 'flowChartConnector'
|
||||||
|
| 'flowChartDecision'
|
||||||
|
| 'flowChartDelay'
|
||||||
|
| 'flowChartDisplay'
|
||||||
|
| 'flowChartDocument'
|
||||||
|
| 'flowChartExtract'
|
||||||
|
| 'flowChartInputOutput'
|
||||||
|
| 'flowChartInternalStorage'
|
||||||
|
| 'flowChartMagneticDisk'
|
||||||
|
| 'flowChartMagneticDrum'
|
||||||
|
| 'flowChartMagneticTape'
|
||||||
|
| 'flowChartManualInput'
|
||||||
|
| 'flowChartManualOperation'
|
||||||
|
| 'flowChartMerge'
|
||||||
|
| 'flowChartMultidocument'
|
||||||
|
| 'flowChartOfflineStorage'
|
||||||
|
| 'flowChartOffpageConnector'
|
||||||
|
| 'flowChartOnlineStorage'
|
||||||
|
| 'flowChartOr'
|
||||||
|
| 'flowChartPredefinedProcess'
|
||||||
|
| 'flowChartPreparation'
|
||||||
|
| 'flowChartProcess'
|
||||||
|
| 'flowChartPunchedCard'
|
||||||
|
| 'flowChartPunchedTape'
|
||||||
|
| 'flowChartSort'
|
||||||
|
| 'flowChartSummingJunction'
|
||||||
|
| 'flowChartTerminator'
|
||||||
|
| 'folderCorner'
|
||||||
|
| 'frame'
|
||||||
|
| 'funnel'
|
||||||
|
| 'gear6'
|
||||||
|
| 'gear9'
|
||||||
|
| 'halfFrame'
|
||||||
|
| 'heart'
|
||||||
|
| 'heptagon'
|
||||||
|
| 'hexagon'
|
||||||
|
| 'homePlate'
|
||||||
|
| 'horizontalScroll'
|
||||||
|
| 'irregularSeal1'
|
||||||
|
| 'irregularSeal2'
|
||||||
|
| 'leftArrow'
|
||||||
|
| 'leftArrowCallout'
|
||||||
|
| 'leftBrace'
|
||||||
|
| 'leftBracket'
|
||||||
|
| 'leftCircularArrow'
|
||||||
|
| 'leftRightArrow'
|
||||||
|
| 'leftRightArrowCallout'
|
||||||
|
| 'leftRightCircularArrow'
|
||||||
|
| 'leftRightRibbon'
|
||||||
|
| 'leftRightUpArrow'
|
||||||
|
| 'leftUpArrow'
|
||||||
|
| 'lightningBolt'
|
||||||
|
| 'line'
|
||||||
|
| 'lineInv'
|
||||||
|
| 'mathDivide'
|
||||||
|
| 'mathEqual'
|
||||||
|
| 'mathMinus'
|
||||||
|
| 'mathMultiply'
|
||||||
|
| 'mathNotEqual'
|
||||||
|
| 'mathPlus'
|
||||||
|
| 'moon'
|
||||||
|
| 'noSmoking'
|
||||||
|
| 'nonIsoscelesTrapezoid'
|
||||||
|
| 'notchedRightArrow'
|
||||||
|
| 'octagon'
|
||||||
|
| 'parallelogram'
|
||||||
|
| 'pentagon'
|
||||||
|
| 'pie'
|
||||||
|
| 'pieWedge'
|
||||||
|
| 'plaque'
|
||||||
|
| 'plaqueTabs'
|
||||||
|
| 'plus'
|
||||||
|
| 'quadArrow'
|
||||||
|
| 'quadArrowCallout'
|
||||||
|
| 'rect'
|
||||||
|
| 'ribbon'
|
||||||
|
| 'ribbon2'
|
||||||
|
| 'rightArrow'
|
||||||
|
| 'rightArrowCallout'
|
||||||
|
| 'rightBrace'
|
||||||
|
| 'rightBracket'
|
||||||
|
| 'round1Rect'
|
||||||
|
| 'round2DiagRect'
|
||||||
|
| 'round2SameRect'
|
||||||
|
| 'roundRect'
|
||||||
|
| 'rtTriangle'
|
||||||
|
| 'smileyFace'
|
||||||
|
| 'snip1Rect'
|
||||||
|
| 'snip2DiagRect'
|
||||||
|
| 'snip2SameRect'
|
||||||
|
| 'snipRoundRect'
|
||||||
|
| 'squareTabs'
|
||||||
|
| 'star10'
|
||||||
|
| 'star12'
|
||||||
|
| 'star16'
|
||||||
|
| 'star24'
|
||||||
|
| 'star32'
|
||||||
|
| 'star4'
|
||||||
|
| 'star5'
|
||||||
|
| 'star6'
|
||||||
|
| 'star7'
|
||||||
|
| 'star8'
|
||||||
|
| 'stripedRightArrow'
|
||||||
|
| 'sun'
|
||||||
|
| 'swooshArrow'
|
||||||
|
| 'teardrop'
|
||||||
|
| 'trapezoid'
|
||||||
|
| 'triangle'
|
||||||
|
| 'upArrow'
|
||||||
|
| 'upArrowCallout'
|
||||||
|
| 'upDownArrow'
|
||||||
|
| 'upDownArrowCallout'
|
||||||
|
| 'uturnArrow'
|
||||||
|
| 'verticalScroll'
|
||||||
|
| 'wave'
|
||||||
|
| 'wedgeEllipseCallout'
|
||||||
|
| 'wedgeRectCallout'
|
||||||
|
| 'wedgeRoundRectCallout'
|
||||||
|
|
||||||
|
export enum SLIDE_OBJECT_TYPES {
|
||||||
|
'chart' = 'chart',
|
||||||
|
'hyperlink' = 'hyperlink',
|
||||||
|
'image' = 'image',
|
||||||
|
'media' = 'media',
|
||||||
|
'online' = 'online',
|
||||||
|
'placeholder' = 'placeholder',
|
||||||
|
'table' = 'table',
|
||||||
|
'tablecell' = 'tablecell',
|
||||||
|
'text' = 'text',
|
||||||
|
'notes' = 'notes',
|
||||||
|
}
|
||||||
|
export enum TEXT_HALIGN {
|
||||||
|
'left' = 'left',
|
||||||
|
'center' = 'center',
|
||||||
|
'right' = 'right',
|
||||||
|
'justify' = 'justify',
|
||||||
|
}
|
||||||
|
export enum TEXT_VALIGN {
|
||||||
|
'b' = 'b',
|
||||||
|
'ctr' = 'ctr',
|
||||||
|
't' = 't',
|
||||||
|
}
|
||||||
|
|
||||||
|
// @source `core-interfaces.d.ts` (direct)
|
||||||
|
// Core Types
|
||||||
|
// ==========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Coordinate number - either:
|
||||||
|
* - Inches (0-n)
|
||||||
|
* - Percentage (0-100)
|
||||||
|
*
|
||||||
|
* @example 10.25 // coordinate in inches
|
||||||
|
* @example '75%' // coordinate as percentage of slide size
|
||||||
|
*/
|
||||||
|
export type Coord = number | `${number}%`
|
||||||
|
export interface PositionProps {
|
||||||
|
/**
|
||||||
|
* Horizontal position
|
||||||
|
* - inches or percentage
|
||||||
|
* @example 10.25 // position in inches
|
||||||
|
* @example '75%' // position as percentage of slide size
|
||||||
|
*/
|
||||||
|
x?: Coord
|
||||||
|
/**
|
||||||
|
* Vertical position
|
||||||
|
* - inches or percentage
|
||||||
|
* @example 10.25 // position in inches
|
||||||
|
* @example '75%' // position as percentage of slide size
|
||||||
|
*/
|
||||||
|
y?: Coord
|
||||||
|
/**
|
||||||
|
* Height
|
||||||
|
* - inches or percentage
|
||||||
|
* @example 10.25 // height in inches
|
||||||
|
* @example '75%' // height as percentage of slide size
|
||||||
|
*/
|
||||||
|
h?: Coord
|
||||||
|
/**
|
||||||
|
* Width
|
||||||
|
* - inches or percentage
|
||||||
|
* @example 10.25 // width in inches
|
||||||
|
* @example '75%' // width as percentage of slide size
|
||||||
|
*/
|
||||||
|
w?: Coord
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Either `data` or `path` is required
|
||||||
|
*/
|
||||||
|
export interface DataOrPathProps {
|
||||||
|
/**
|
||||||
|
* URL or relative path
|
||||||
|
*
|
||||||
|
* @example 'https://onedrives.com/myimg.png` // retrieve image via URL
|
||||||
|
* @example '/home/gitbrent/images/myimg.png` // retrieve image via local path
|
||||||
|
*/
|
||||||
|
path?: string
|
||||||
|
/**
|
||||||
|
* base64-encoded string
|
||||||
|
* - Useful for avoiding potential path/server issues
|
||||||
|
*
|
||||||
|
* @example 'image/png;
|
||||||
|
```
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
slide.addShape(pres.ShapeType.rect, {
|
||||||
|
x: 1, y: 1, w: 8, h: 4,
|
||||||
|
fill: { color: '0088CC' },
|
||||||
|
line: { color: '0055AA', width: 2 }
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## 7. 表格
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
slide.addTable([
|
||||||
|
[
|
||||||
|
{ text: 'Header 1', options: { bold: true, color: 'FFFFFF' } },
|
||||||
|
{ text: 'Header 2', options: { bold: true, color: 'FFFFFF' } }
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ text: 'Row 1 Col 1' },
|
||||||
|
{ text: 'Row 1 Col 2' }
|
||||||
|
]
|
||||||
|
], {
|
||||||
|
x: 1, y: 1, w: 8,
|
||||||
|
border: { type: 'solid', color: 'CCCCCC', pt: 1 },
|
||||||
|
colW: [4, 4]
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## 8. 图表
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
slide.addChart(pres.ChartType.bar, [
|
||||||
|
{
|
||||||
|
name: 'Sales',
|
||||||
|
labels: ['Q1', 'Q2', 'Q3', 'Q4'],
|
||||||
|
values: [100, 200, 150, 300]
|
||||||
|
}
|
||||||
|
], {
|
||||||
|
x: 1, y: 1, w: 8, h: 4
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## 9. 输出
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// 保存到文件
|
||||||
|
pres.writeFile({ fileName: 'output.pptx' });
|
||||||
|
|
||||||
|
// 浏览器下载
|
||||||
|
pres.write({ outputType: 'blob' });
|
||||||
|
pres.write({ outputType: 'arraybuffer' });
|
||||||
|
pres.write({ outputType: 'dataUrl' });
|
||||||
|
|
||||||
|
// Node stream
|
||||||
|
pres.stream();
|
||||||
|
```
|
||||||
|
|
||||||
|
## 10. 坐标换算
|
||||||
|
|
||||||
|
PptxGenJS 使用英寸作为单位。
|
||||||
|
|
||||||
|
| 单位 | 换算 |
|
||||||
|
|------|------|
|
||||||
|
| 1 英寸 | 1.0 |
|
||||||
|
| 1 厘米 | 0.3937 英寸 |
|
||||||
|
| CSS px (96dpi) | px / 96 英寸 |
|
||||||
|
| PPT pt (72dpi) | pt / 72 英寸 |
|
||||||
|
|
||||||
|
常用:`pxToInches = px / 96`
|
||||||
|
|
||||||
|
## 11. 完整示例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const pptxgen = require('pptxgenjs');
|
||||||
|
const pres = new pptxgen();
|
||||||
|
|
||||||
|
// 16:9 布局
|
||||||
|
pres.defineLayout({ name: 'CUSTOM', width: 10, height: 5.625 });
|
||||||
|
pres.layout = 'CUSTOM';
|
||||||
|
pres.author = 'LiJin';
|
||||||
|
pres.title = 'Presentation';
|
||||||
|
|
||||||
|
// 第一页
|
||||||
|
let slide = pres.addSlide();
|
||||||
|
slide.background = { fill: '1A1A2E' };
|
||||||
|
slide.addText('Title Slide', {
|
||||||
|
x: 1, y: 1.5, w: 8, h: 1,
|
||||||
|
fontSize: 44, color: 'FFFFFF', bold: true,
|
||||||
|
align: 'center', fontFace: 'Arial'
|
||||||
|
});
|
||||||
|
slide.addText('Subtitle', {
|
||||||
|
x: 1, y: 3, w: 8, h: 0.5,
|
||||||
|
fontSize: 18, color: 'AAAAAA',
|
||||||
|
align: 'center'
|
||||||
|
});
|
||||||
|
|
||||||
|
// 第二页
|
||||||
|
slide = pres.addSlide();
|
||||||
|
slide.background = { fill: 'FFFFFF' };
|
||||||
|
slide.addText('Content Page', {
|
||||||
|
x: 0.5, y: 0.3, w: 9, h: 0.6,
|
||||||
|
fontSize: 28, bold: true
|
||||||
|
});
|
||||||
|
slide.addText([
|
||||||
|
{ text: 'Bullet 1\n', options: { fontSize: 18, bullet: true } },
|
||||||
|
{ text: 'Bullet 2\n', options: { fontSize: 18, bullet: true } },
|
||||||
|
{ text: 'Bullet 3', options: { fontSize: 18, bullet: true } }
|
||||||
|
], {
|
||||||
|
x: 0.5, y: 1.5, w: 5, h: 2
|
||||||
|
});
|
||||||
|
|
||||||
|
pres.writeFile({ fileName: 'demo.pptx' });
|
||||||
|
```
|
||||||
@@ -1,37 +1,94 @@
|
|||||||
# html2pptx
|
# html2pptx
|
||||||
|
|
||||||
HTML to editable PPTX export engine.
|
HTML / 截图 → 标准 JSON → 可编辑 PPTX
|
||||||
|
|
||||||
将 HTML 演示文稿导出为**文字可编辑、布局可还原**的 PPTX 文件。
|
将任何格式的演示文稿描述转化为**文字可编辑、布局可还原**的 PPTX 文件。
|
||||||
|
|
||||||
## 原理
|
## 架构
|
||||||
|
|
||||||
```
|
```
|
||||||
HTML 文件 → HTTP 服务 → Chrome headless 渲染 → DOM 遍历提取位置/样式 → pptxgenjs 生成 PPTX
|
输入(任意来源) 中间层 输出
|
||||||
|
━━━━━━━━━━━━━━━ ━━━━━━━━━━━ ━━━━━
|
||||||
|
AI 生成的描述 标准 JSON PPTX 文件
|
||||||
|
HTML 提取的结构 → Schema 校验 → pptxgenjs 渲染
|
||||||
|
多模态识别结果 ↓
|
||||||
|
Markdown / 其他 翻译引擎(机械映射)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 三层结构
|
||||||
|
|
||||||
|
| 层 | 目录 | 说明 |
|
||||||
|
|---|------|------|
|
||||||
|
| **知识库** | `reference/` | pptxgenjs 完整参数表,每个参数的类型/取值范围/默认值。给 AI 用的操作手册 |
|
||||||
|
| **Schema** | `schema/` | JSON Schema 定义 + 示例。AI 输出符合此 Schema 的 JSON 即可驱动 PPT 生成 |
|
||||||
|
| **翻译引擎** | `renderer/` | JSON → pptxgenjs API 调用的机械映射。不做逻辑判断,只做参数透传 |
|
||||||
|
|
||||||
|
## 快速开始
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
### 从 JSON 生成 PPTX
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const { render } = require('./renderer/index');
|
||||||
|
const pptx = render(jsonObject);
|
||||||
|
pptx.writeFile({ fileName: 'output.pptx' });
|
||||||
|
```
|
||||||
|
|
||||||
|
### 从 JSON 文件生成
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const { renderFile } = require('./renderer/index');
|
||||||
|
renderFile('./schema/examples/ui-dashboard.json', 'output.pptx');
|
||||||
|
```
|
||||||
|
|
||||||
|
## 目录结构
|
||||||
|
|
||||||
|
```
|
||||||
|
reference/ ← 知识库:每个元素的完整参数表
|
||||||
|
text.md 文字参数
|
||||||
|
shape.md 形状参数
|
||||||
|
table.md 表格参数
|
||||||
|
image.md 图片参数
|
||||||
|
chart.md 图表参数
|
||||||
|
slide.md 页面参数
|
||||||
|
master.md 母版参数
|
||||||
|
output.md 输出参数
|
||||||
|
coordinate.md 坐标换算
|
||||||
|
|
||||||
|
schema/ ← JSON Schema 定义
|
||||||
|
presentation.schema.json
|
||||||
|
examples/
|
||||||
|
basic.json 基础示例(2页)
|
||||||
|
full.json 全功能示例
|
||||||
|
ui-dashboard.json UI 看板示例
|
||||||
|
ui-advanced.json 精细化 UI 示例
|
||||||
|
|
||||||
|
renderer/ ← 翻译引擎
|
||||||
|
index.js 主入口
|
||||||
|
text-renderer.js 文字渲染
|
||||||
|
shape-renderer.js 形状渲染
|
||||||
|
table-renderer.js 表格渲染
|
||||||
|
image-renderer.js 图片渲染
|
||||||
|
chart-renderer.js 图表渲染
|
||||||
|
slide-renderer.js 页面属性
|
||||||
|
|
||||||
|
PPTXGENJS_API.md ← pptxgenjs v4.0.1 完整 API 参考
|
||||||
|
```
|
||||||
|
|
||||||
|
## 设计原则
|
||||||
|
|
||||||
|
- **翻译引擎是纯机械映射** — 不做样式推断,不做逻辑判断。JSON 里有什么就画什么
|
||||||
|
- **Schema 是 AI 友好** — 字段名直观,不暴露 pptxgenjs 内部命名
|
||||||
|
- **知识库是完整参考** — 每个参数都有类型+取值范围+默认值,不需要 AI 去猜
|
||||||
|
|
||||||
## 依赖
|
## 依赖
|
||||||
|
|
||||||
- Node.js 18+
|
|
||||||
- Chrome / Chromium / Edge(用于渲染页面)
|
|
||||||
|
|
||||||
## 使用
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 安装
|
|
||||||
npm install
|
|
||||||
|
|
||||||
# 导出
|
|
||||||
node bin/html2pptx input.html output.pptx
|
|
||||||
```
|
|
||||||
|
|
||||||
## 核心依赖
|
|
||||||
|
|
||||||
| 包 | 用途 |
|
| 包 | 用途 |
|
||||||
|---|------|
|
|---|------|
|
||||||
| playwright | 控制 Chrome headless 渲染页面 |
|
| pptxgenjs | 生成 PPTX 文件(OOXML) |
|
||||||
| pptxgenjs | 生成 PPTX 文件 |
|
|
||||||
| http-server | 本地静态文件服务(可选) |
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|||||||
@@ -1,115 +1,62 @@
|
|||||||
# HTML to PPTX 导出引擎
|
# html2pptx 项目文档
|
||||||
|
|
||||||
## 背景
|
## 项目定位
|
||||||
|
|
||||||
DashiAI PPT Skill 中包含一个 `html-deck-to-pptx` 导出子包,能将 HTML 演示文稿导出为可编辑的 PPTX 文件。该子包目前为专有组件(非开源),但其核心逻辑可复刻。
|
一套通用 PPT 生成基础设施。核心价值在于**翻译引擎**——把标准 JSON 描述翻译成可编辑的 PPTX 文件。
|
||||||
|
|
||||||
我们需要一个独立、可控的导出引擎,集成到现有工作流中。凡是 Hermes/Agogent 生成的 HTML PPT,都能一键导出为**文字可编辑、布局可还原**的 PPTX 文件。
|
## 架构
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 需求
|
|
||||||
|
|
||||||
### 输入
|
|
||||||
|
|
||||||
- 一个 HTML 文件(含完整 CSS 内联或外联),结构为横向翻页的演示文稿
|
|
||||||
- 每页用 `<section>` 或特定容器标识
|
|
||||||
- 样式通过 CSS 定义(font-size, color, font-weight, text-align, background-color 等)
|
|
||||||
|
|
||||||
### 输出
|
|
||||||
|
|
||||||
- 一个标准的 `.pptx` 文件,可用 Microsoft PowerPoint / WPS / Keynote 打开
|
|
||||||
- **文字必须可编辑**(双击修改字体、大小、颜色)
|
|
||||||
- **形状/框线/背景色保持矢量**(可拖动、缩放)
|
|
||||||
- **排版布局按原始位置还原**(x, y, width, height 尽可能一致)
|
|
||||||
|
|
||||||
### 可放弃的特性(第一期不做)
|
|
||||||
|
|
||||||
- JS 图表(ECharts/Chart.js)→ 导出为截图占位或忽略
|
|
||||||
- CSS 渐变/阴影/圆角 → 简化或忽略
|
|
||||||
- 入场/翻页动画 → 忽略
|
|
||||||
- Web 字体 → 映射为 Office 相近字体
|
|
||||||
- 响应式布局 → 固定一宽高比
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 技术方案
|
|
||||||
|
|
||||||
### 架构
|
|
||||||
|
|
||||||
```
|
```
|
||||||
HTML 文件
|
输入(任意来源) 中间层 输出
|
||||||
↓
|
━━━━━━━━━━━━━━━ ━━━━━━━━━━━ ━━━━━
|
||||||
启动本地 HTTP 服务(Node.js http-server 或 express)
|
AI 直接输出 标准 JSON PPTX 文件
|
||||||
↓
|
HTML 提取 → JSON → Schema 校验 → 翻译引擎
|
||||||
Chrome headless(通过 playwright)打开页面
|
截图识别 → JSON (机械映射) pptxgenjs
|
||||||
↓
|
Markdown → JSON
|
||||||
遍历 DOM,提取每页每个元素:
|
|
||||||
- 位置(offsetLeft, offsetTop, offsetWidth, offsetHeight)
|
|
||||||
- 文字内容(innerText)
|
|
||||||
- 样式(font-size, color, font-weight, text-align, background-color 等)
|
|
||||||
↓
|
|
||||||
pptxgenjs 按页创建 slide,按坐标绘制 TextBox / Shape
|
|
||||||
↓
|
|
||||||
输出 .pptx 文件
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 核心依赖
|
### 三层
|
||||||
|
|
||||||
| 依赖 | 用途 | 备注 |
|
| 层 | 内容 | 状态 |
|
||||||
|------|------|------|
|
|---|------|------|
|
||||||
| `playwright` | 控制 Chrome headless 渲染页面、提取 DOM 信息 | 你的环境已有 Chrome |
|
| **知识库** (`reference/`) | pptxgenjs 完整参数表,每个参数的类型/取值范围/默认值 | ✅ 已完成 |
|
||||||
| `pptxgenjs` | 生成 PPTX 文件(OOXML) | 纯 JS,不依赖 Office |
|
| **Schema** (`schema/`) | JSON Schema 定义 + 多种场景的示例文件 | ✅ 已完成 |
|
||||||
| `http-server` 或 express | 本地静态文件服务 | |
|
| **翻译引擎** (`renderer/`) | JSON → pptxgenjs API 调用的机械映射代码 | ✅ 已验收 |
|
||||||
|
|
||||||
### 可选升级
|
### 前端处理器(待开发)
|
||||||
|
|
||||||
- 支持 PNG/JPEG 图片的提取与还原
|
"输入 → 标准 JSON"这层目前没有稳定的通用方案。试验过以下方向:
|
||||||
- 表格结构识别(`<table>` → PPT 表格)
|
|
||||||
- 列表识别(`<ul>/<ol>` → PPT 列表)
|
|
||||||
- CSS 圆角/边框映射
|
|
||||||
- 批量导出(多 HTML 文件)
|
|
||||||
|
|
||||||
---
|
| 方案 | 结论 |
|
||||||
|
|------|------|
|
||||||
|
| 多模态截图识别(Mimo v2.5) | ❌ 坐标和字号不够精确 |
|
||||||
|
| LLM 直接读 HTML+CSS 源码 | ❌ 样式映射不准确 |
|
||||||
|
| playwright 全量 DOM 提取 | ⚠️ 数据精确但分页和元素筛选不稳定 |
|
||||||
|
|
||||||
|
目前推荐的做法:**AI 直接按 Schema 输出 JSON**,不经过中间提取步骤。
|
||||||
|
|
||||||
|
## 翻译引擎使用
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const { render, renderFile } = require('./renderer/index');
|
||||||
|
|
||||||
|
// 从 JSON 对象生成
|
||||||
|
const pptx = render(specObject);
|
||||||
|
pptx.writeFile({ fileName: 'output.pptx' });
|
||||||
|
|
||||||
|
// 从 JSON 文件生成
|
||||||
|
await renderFile('./spec.json', 'output.pptx');
|
||||||
|
```
|
||||||
|
|
||||||
## 验收标准
|
## 验收标准
|
||||||
|
|
||||||
1. **输入一个 10 页的 HTML PPT**,导出 PPTX 后在 PowerPoint 中打开
|
- 输入标准 JSON(符合 Schema)→ 输出可编辑的 PPTX
|
||||||
2. **每一页的标题、正文文字可双击编辑**
|
- 文字可双击编辑(非图片)
|
||||||
3. **文字字体、大小、颜色、粗体与原 HTML 一致**
|
- 支持:文字、形状、表格、图片、图表
|
||||||
4. **布局位置基本还原**(不要求像素级,但段落不应错位到其他区域)
|
- 颜色、字号、位置精确还原
|
||||||
5. **执行时间不超过 30 秒**(10 页)
|
|
||||||
|
|
||||||
---
|
## 已知限制
|
||||||
|
|
||||||
## 工作流集成
|
- 翻译引擎不做样式推断——JSON 里有什么就画什么
|
||||||
|
- 前端处理器(HTML/截图→JSON)尚无稳定方案
|
||||||
```bash
|
- 渐变、阴影等高级 CSS 效果依赖 pptxgenjs 支持程度
|
||||||
# 命令行调用
|
|
||||||
html2pptx input.html output.pptx
|
|
||||||
|
|
||||||
# 或
|
|
||||||
html2pptx input.html -o output.pptx
|
|
||||||
|
|
||||||
# 或指定端口
|
|
||||||
html2pptx input.html -o output.pptx -p 8080
|
|
||||||
```
|
|
||||||
|
|
||||||
输出文件路径直接返回给用户,用户双击或在 PowerPoint 中打开即可。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 第一版范围(MVP)
|
|
||||||
|
|
||||||
- [ ] 基础架构:HTTP 服务 + Chrome headless 渲染 + DOM 提取
|
|
||||||
- [ ] 文字提取:位置、内容、font-size、color、font-weight、text-align
|
|
||||||
- [ ] PPTX 生成:pptxgenjs 建 slide,画 TextBox
|
|
||||||
- [ ] 分页识别:按 `<section>` 或 `[data-slide]` 切割
|
|
||||||
- [ ] 背景色还原:slide 背景色
|
|
||||||
- [ ] 对齐方式:left/center/right
|
|
||||||
|
|
||||||
第一版不做的:
|
|
||||||
- 图片提取
|
|
||||||
- 表格识别
|
|
||||||
- 列表识别
|
|
||||||
- 渐变/阴影/圆角
|
|
||||||
|
|||||||
@@ -1,408 +0,0 @@
|
|||||||
#!/usr/bin/env node
|
|
||||||
/**
|
|
||||||
* html2pptx — HTML to editable PPTX export engine
|
|
||||||
*
|
|
||||||
* Usage:
|
|
||||||
* html2pptx <input.html> [output.pptx]
|
|
||||||
* html2pptx <input.html> -o <output.pptx> -p <port>
|
|
||||||
*
|
|
||||||
* Process:
|
|
||||||
* 1. Start local HTTP server to host the HTML
|
|
||||||
* 2. Chrome headless (Playwright) opens the page
|
|
||||||
* 3. Extract each slide's element positions, text, and styles
|
|
||||||
* 4. Generate PPTX with pptxgenjs
|
|
||||||
*/
|
|
||||||
|
|
||||||
const { chromium } = require('playwright');
|
|
||||||
const http = require('http');
|
|
||||||
const fs = require('fs');
|
|
||||||
const path = require('path');
|
|
||||||
const PptxGenJS = require('pptxgenjs');
|
|
||||||
|
|
||||||
// ====== CLI argument parsing ======
|
|
||||||
|
|
||||||
function parseArgs() {
|
|
||||||
const args = process.argv.slice(2);
|
|
||||||
if (args.length === 0) {
|
|
||||||
console.error('Usage: html2pptx <input.html> [output.pptx]');
|
|
||||||
console.error(' html2pptx <input.html> -o <output.pptx> -p <port>');
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
const inputPath = path.resolve(args[0]);
|
|
||||||
const parsed = path.parse(inputPath);
|
|
||||||
let outputPath = path.join(parsed.dir, parsed.name + '.pptx');
|
|
||||||
let port;
|
|
||||||
|
|
||||||
for (let i = 1; i < args.length; i++) {
|
|
||||||
if (args[i] === '-o' && i + 1 < args.length) {
|
|
||||||
outputPath = path.resolve(args[++i]);
|
|
||||||
} else if (args[i] === '-p' && i + 1 < args.length) {
|
|
||||||
port = parseInt(args[++i], 10);
|
|
||||||
} else if (!args[i].startsWith('-')) {
|
|
||||||
outputPath = path.resolve(args[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return { inputPath, outputPath, port };
|
|
||||||
}
|
|
||||||
|
|
||||||
// ====== Color parsing (Node.js side) ======
|
|
||||||
|
|
||||||
function parseCSSColor(color, fallback) {
|
|
||||||
if (!color || color === 'transparent' || color === 'rgba(0, 0, 0, 0)') return fallback || '#000000';
|
|
||||||
|
|
||||||
// Already #rrggbb
|
|
||||||
if (/^#[0-9a-f]{6}$/i.test(color)) return color.toLowerCase();
|
|
||||||
// #rgb → #rrggbb
|
|
||||||
if (/^#[0-9a-f]{3}$/i.test(color)) {
|
|
||||||
return '#' + color[1] + color[1] + color[2] + color[2] + color[3] + color[3];
|
|
||||||
}
|
|
||||||
|
|
||||||
// rgb(r, g, b) / rgba(r, g, b, a)
|
|
||||||
const m = color.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);
|
|
||||||
if (m) {
|
|
||||||
return '#' + m.slice(1, 4).map(c => parseInt(c).toString(16).padStart(2, '0')).join('');
|
|
||||||
}
|
|
||||||
|
|
||||||
return fallback || '#000000';
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseFontWeight(w) {
|
|
||||||
if (w === 'bold') return true;
|
|
||||||
const n = parseInt(w, 10);
|
|
||||||
return n >= 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
function normalizeAlign(align) {
|
|
||||||
switch (align) {
|
|
||||||
case 'left': case 'center': case 'right': case 'justify': return align;
|
|
||||||
case 'start': return 'left';
|
|
||||||
case 'end': return 'right';
|
|
||||||
default: return 'left';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ====== Main ======
|
|
||||||
|
|
||||||
async function main() {
|
|
||||||
const { inputPath, outputPath, port: cliPort } = parseArgs();
|
|
||||||
|
|
||||||
if (!fs.existsSync(inputPath)) {
|
|
||||||
console.error('文件不存在:', inputPath);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
const TIMEOUT_MS = 30000;
|
|
||||||
const startTime = Date.now();
|
|
||||||
let server, browser;
|
|
||||||
|
|
||||||
try {
|
|
||||||
// ------ 1. HTTP server ------
|
|
||||||
const baseDir = path.dirname(inputPath);
|
|
||||||
const MIME = {
|
|
||||||
'.html': 'text/html; charset=utf-8',
|
|
||||||
'.css': 'text/css; charset=utf-8',
|
|
||||||
'.js': 'application/javascript; charset=utf-8',
|
|
||||||
'.png': 'image/png',
|
|
||||||
'.jpg': 'image/jpeg',
|
|
||||||
'.jpeg': 'image/jpeg',
|
|
||||||
'.svg': 'image/svg+xml',
|
|
||||||
};
|
|
||||||
|
|
||||||
server = http.createServer((req, res) => {
|
|
||||||
const reqPath = req.url === '/' ? '/' + path.basename(inputPath) : req.url;
|
|
||||||
const filePath = path.join(baseDir, reqPath);
|
|
||||||
// Prevent directory traversal
|
|
||||||
if (!filePath.startsWith(baseDir)) {
|
|
||||||
res.writeHead(404);
|
|
||||||
res.end('Not Found');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) {
|
|
||||||
const ext = path.extname(filePath).toLowerCase();
|
|
||||||
res.writeHead(200, { 'Content-Type': MIME[ext] || 'application/octet-stream' });
|
|
||||||
res.end(fs.readFileSync(filePath));
|
|
||||||
} else {
|
|
||||||
res.writeHead(404);
|
|
||||||
res.end('Not Found');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const port = cliPort || 0;
|
|
||||||
await new Promise((resolve, reject) => {
|
|
||||||
server.listen(port, '127.0.0.1', resolve);
|
|
||||||
server.on('error', reject);
|
|
||||||
});
|
|
||||||
const actualPort = server.address().port;
|
|
||||||
console.log('HTTP 服务已启动: http://127.0.0.1:' + actualPort + '/');
|
|
||||||
|
|
||||||
if (Date.now() - startTime > TIMEOUT_MS) throw new Error('导出超时');
|
|
||||||
|
|
||||||
// ------ 2. Chrome headless ------
|
|
||||||
browser = await chromium.launch({ headless: true });
|
|
||||||
const page = await browser.newPage({ viewport: { width: 1280, height: 720 } });
|
|
||||||
|
|
||||||
if (Date.now() - startTime > TIMEOUT_MS) throw new Error('导出超时');
|
|
||||||
|
|
||||||
await page.goto('http://127.0.0.1:' + actualPort + '/', {
|
|
||||||
waitUntil: 'networkidle',
|
|
||||||
timeout: 15000,
|
|
||||||
});
|
|
||||||
console.log('页面加载完成');
|
|
||||||
|
|
||||||
// ------ 3. DOM extraction (in-browser) ------
|
|
||||||
const slidesData = await page.evaluate(() => {
|
|
||||||
// ----- Slide detection -----
|
|
||||||
var slides;
|
|
||||||
// DashiAI format: .ppt-deck > .ppt-slide
|
|
||||||
var deck = document.querySelector('.ppt-deck');
|
|
||||||
if (deck) {
|
|
||||||
slides = deck.querySelectorAll(':scope > .ppt-slide');
|
|
||||||
}
|
|
||||||
if (!slides || slides.length <= 1) {
|
|
||||||
slides = document.querySelectorAll(
|
|
||||||
'section[data-slide], section.slide, section'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (slides.length <= 1) {
|
|
||||||
slides = document.querySelectorAll('[data-slide]');
|
|
||||||
}
|
|
||||||
slides = Array.from(slides).filter(function (s) {
|
|
||||||
var r = s.getBoundingClientRect();
|
|
||||||
var style = window.getComputedStyle(s);
|
|
||||||
return r.width > 0 && r.height > 0 &&
|
|
||||||
style.opacity !== '0' &&
|
|
||||||
style.display !== 'none' &&
|
|
||||||
style.visibility !== 'hidden';
|
|
||||||
});
|
|
||||||
|
|
||||||
// Recursively extract inline text runs with computed styles
|
|
||||||
function extractRuns(node) {
|
|
||||||
var runs = [];
|
|
||||||
for (var ri = 0; ri < node.childNodes.length; ri++) {
|
|
||||||
var child = node.childNodes[ri];
|
|
||||||
if (child.nodeType === 3) {
|
|
||||||
var t = (child.textContent || '').trim();
|
|
||||||
if (t) runs.push({ text: t });
|
|
||||||
} else if (child.nodeType === 1) {
|
|
||||||
var cs = window.getComputedStyle(child);
|
|
||||||
var childRuns = extractRuns(child);
|
|
||||||
for (var cj = 0; cj < childRuns.length; cj++) {
|
|
||||||
childRuns[cj].bold = childRuns[cj].bold || parseInt(cs.fontWeight) >= 600;
|
|
||||||
childRuns[cj].italic = childRuns[cj].italic || cs.fontStyle === 'italic';
|
|
||||||
childRuns[cj].color = childRuns[cj].color || cs.color;
|
|
||||||
childRuns[cj].fontSize = childRuns[cj].fontSize || parseFloat(cs.fontSize);
|
|
||||||
childRuns[cj].fontFamily = childRuns[cj].fontFamily || cs.fontFamily;
|
|
||||||
}
|
|
||||||
runs = runs.concat(childRuns);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return runs.length > 0 ? runs : [{ text: node.textContent || '' }];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tags whose full text content should be a single text box
|
|
||||||
var TEXT_BLOCK_TAGS = new Set([
|
|
||||||
'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
|
|
||||||
'li', 'td', 'th', 'blockquote', 'figcaption', 'dt', 'dd', 'caption',
|
|
||||||
]);
|
|
||||||
|
|
||||||
return slides.map(function (slide) {
|
|
||||||
var slideRect = slide.getBoundingClientRect();
|
|
||||||
var elements = [];
|
|
||||||
|
|
||||||
// Collect block-level text containers; each becomes one text box
|
|
||||||
var textBlocks = slide.querySelectorAll(
|
|
||||||
'p, h1, h2, h3, h4, h5, h6, li, td, th, blockquote, ' +
|
|
||||||
'figcaption, dt, dd, caption'
|
|
||||||
);
|
|
||||||
|
|
||||||
for (var i = 0; i < textBlocks.length; i++) {
|
|
||||||
var el = textBlocks[i];
|
|
||||||
// Skip if this block is nested inside another text block (e.g. li inside another li)
|
|
||||||
var parent = el.parentElement;
|
|
||||||
var skip = false;
|
|
||||||
while (parent && parent !== slide) {
|
|
||||||
if (TEXT_BLOCK_TAGS.has(parent.tagName.toLowerCase())) {
|
|
||||||
skip = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
parent = parent.parentElement;
|
|
||||||
}
|
|
||||||
if (skip) continue;
|
|
||||||
|
|
||||||
var runs = extractRuns(el);
|
|
||||||
if (!runs || runs.length === 0) continue;
|
|
||||||
var text = runs.map(function (r) { return r.text || ''; }).join(' ').trim();
|
|
||||||
if (!text) continue;
|
|
||||||
|
|
||||||
var rect = el.getBoundingClientRect();
|
|
||||||
if (rect.width === 0 || rect.height === 0) continue;
|
|
||||||
|
|
||||||
var style = window.getComputedStyle(el);
|
|
||||||
|
|
||||||
// Truncate overly long runs
|
|
||||||
runs.forEach(function (r) {
|
|
||||||
if (r.text && r.text.length > 2000) r.text = r.text.substring(0, 2000);
|
|
||||||
});
|
|
||||||
|
|
||||||
elements.push({
|
|
||||||
runs: runs,
|
|
||||||
text: text.substring(0, 2000),
|
|
||||||
x: rect.left - slideRect.left,
|
|
||||||
y: rect.top - slideRect.top,
|
|
||||||
width: rect.width,
|
|
||||||
height: rect.height,
|
|
||||||
fontSize: parseFloat(style.fontSize) || 14,
|
|
||||||
fontFamily: style.fontFamily || 'Arial',
|
|
||||||
color: style.color || '#000000',
|
|
||||||
fontWeight: style.fontWeight || 'normal',
|
|
||||||
fontStyle: style.fontStyle || 'normal',
|
|
||||||
textAlign: style.textAlign || 'left',
|
|
||||||
backgroundColor: style.backgroundColor || 'transparent',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Slide background
|
|
||||||
var bg = window.getComputedStyle(slide).backgroundColor;
|
|
||||||
var bgColor =
|
|
||||||
bg &&
|
|
||||||
bg !== 'rgba(0, 0, 0, 0)' &&
|
|
||||||
bg !== 'transparent'
|
|
||||||
? bg
|
|
||||||
: '#FFFFFF';
|
|
||||||
|
|
||||||
return {
|
|
||||||
width: slideRect.width,
|
|
||||||
height: slideRect.height,
|
|
||||||
bgColor: bgColor,
|
|
||||||
elements: elements,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('提取到 ' + slidesData.length + ' 页幻灯片');
|
|
||||||
|
|
||||||
if (Date.now() - startTime > TIMEOUT_MS) throw new Error('导出超时');
|
|
||||||
|
|
||||||
// ------ 4. PPTX generation ------
|
|
||||||
var pptx = new PptxGenJS();
|
|
||||||
pptx.defineLayout({ name: 'CUSTOM', width: 10, height: 5.625 });
|
|
||||||
pptx.layout = 'CUSTOM';
|
|
||||||
|
|
||||||
for (var s = 0; s < slidesData.length; s++) {
|
|
||||||
var slideData = slidesData[s];
|
|
||||||
var slide = pptx.addSlide();
|
|
||||||
|
|
||||||
// Background
|
|
||||||
slide.background = { fill: parseCSSColor(slideData.bgColor, '#FFFFFF') };
|
|
||||||
|
|
||||||
// Scale factors: HTML pixels → PPT inches
|
|
||||||
var scaleX = 10 / slideData.width;
|
|
||||||
var scaleY = 5.625 / slideData.height;
|
|
||||||
|
|
||||||
// Sort top-to-bottom, left-to-right
|
|
||||||
slideData.elements.sort(function (a, b) {
|
|
||||||
return a.y - b.y || a.x - b.x;
|
|
||||||
});
|
|
||||||
|
|
||||||
for (var e = 0; e < slideData.elements.length; e++) {
|
|
||||||
var el = slideData.elements[e];
|
|
||||||
var t = (el.text || '').trim();
|
|
||||||
if (!t) continue;
|
|
||||||
|
|
||||||
try {
|
|
||||||
var x = Math.max(el.x * scaleX, 0);
|
|
||||||
var y = Math.max(el.y * scaleY, 0);
|
|
||||||
var w = Math.max(el.width * scaleX, 0.3);
|
|
||||||
var h = Math.max(el.height * scaleY, 0.2);
|
|
||||||
|
|
||||||
// Bounds constraints: prevent textbox from overflowing slide
|
|
||||||
var MAX_W = 10 - x;
|
|
||||||
var MAX_H = 5.625 - y;
|
|
||||||
w = Math.min(w, MAX_W);
|
|
||||||
h = Math.min(h, MAX_H);
|
|
||||||
|
|
||||||
// Base font size: CSS px → pt (1pt = 1/72in, CSS 1px = 1/96in)
|
|
||||||
var fontSize = el.fontSize * 72 / 96;
|
|
||||||
fontSize = Math.max(fontSize, 8);
|
|
||||||
|
|
||||||
// Check if rich text is needed (runs with differing styles)
|
|
||||||
var hasRuns = el.runs && el.runs.length > 0;
|
|
||||||
var isSimpleRun = hasRuns && el.runs.length === 1 &&
|
|
||||||
el.runs[0].bold === undefined &&
|
|
||||||
el.runs[0].italic === undefined &&
|
|
||||||
el.runs[0].color === undefined &&
|
|
||||||
el.runs[0].fontSize === undefined &&
|
|
||||||
el.runs[0].fontFamily === undefined;
|
|
||||||
|
|
||||||
if (hasRuns && !isSimpleRun && el.runs.length > 1) {
|
|
||||||
// Rich text: render each run with its own style
|
|
||||||
var pptxRuns = el.runs.map(function (run) {
|
|
||||||
if (!run.text || !run.text.trim()) return null;
|
|
||||||
var baseSize = el.fontSize * 72 / 96;
|
|
||||||
return {
|
|
||||||
text: run.text,
|
|
||||||
options: {
|
|
||||||
fontSize: run.fontSize ? Math.max(run.fontSize * 72 / 96, 8) : baseSize,
|
|
||||||
bold: run.bold === true || (run.bold === undefined && parseFontWeight(el.fontWeight)),
|
|
||||||
italic: run.italic === true || (run.italic === undefined && (el.fontStyle === 'italic' || el.fontStyle === 'oblique')),
|
|
||||||
color: run.color ? parseCSSColor(run.color, '#000000') : parseCSSColor(el.color, '#000000'),
|
|
||||||
fontFace: run.fontFamily
|
|
||||||
? run.fontFamily.split(',')[0].trim().replace(/['"]/g, '')
|
|
||||||
: (el.fontFamily || 'Arial').split(',')[0].trim().replace(/['"]/g, ''),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}).filter(Boolean);
|
|
||||||
|
|
||||||
if (pptxRuns.length > 0) {
|
|
||||||
slide.addText(pptxRuns, {
|
|
||||||
x: x,
|
|
||||||
y: y,
|
|
||||||
w: w,
|
|
||||||
h: h,
|
|
||||||
align: normalizeAlign(el.textAlign),
|
|
||||||
valign: 'top',
|
|
||||||
wrap: true,
|
|
||||||
margin: 0,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Plain text: original single-style logic
|
|
||||||
slide.addText(t, {
|
|
||||||
x: x,
|
|
||||||
y: y,
|
|
||||||
w: w,
|
|
||||||
h: h,
|
|
||||||
fontSize: fontSize,
|
|
||||||
fontFace: (el.fontFamily || 'Arial').split(',')[0].trim().replace(/['"]/g, ''),
|
|
||||||
color: parseCSSColor(el.color, '#000000'),
|
|
||||||
bold: parseFontWeight(el.fontWeight),
|
|
||||||
italic: el.fontStyle === 'italic' || el.fontStyle === 'oblique',
|
|
||||||
align: normalizeAlign(el.textAlign),
|
|
||||||
valign: 'top',
|
|
||||||
wrap: true,
|
|
||||||
margin: 0,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
// Skip individual element failure
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await pptx.writeFile({ fileName: outputPath });
|
|
||||||
console.log('\nPPTX 已导出: ' + outputPath);
|
|
||||||
} finally {
|
|
||||||
if (browser) await browser.close().catch(function () {});
|
|
||||||
if (server)
|
|
||||||
await new Promise(function (resolve) {
|
|
||||||
server.close(resolve);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
main().catch(function (err) {
|
|
||||||
console.error('导出失败:', err.message);
|
|
||||||
process.exit(1);
|
|
||||||
});
|
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
/**
|
||||||
|
* web-to-pixso JSON → 翻译引擎 Schema 转换器
|
||||||
|
*
|
||||||
|
* 把 web-to-pixso Chrome 扩展导出的 JSON 转成 renderer/index.js 能吃的标准格式
|
||||||
|
*/
|
||||||
|
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
function convert(inputPath, outputPath) {
|
||||||
|
const raw = JSON.parse(fs.readFileSync(inputPath, 'utf8'));
|
||||||
|
const nodes = raw.nodes;
|
||||||
|
const canvas = raw.canvas || {};
|
||||||
|
|
||||||
|
// 画布尺寸
|
||||||
|
const canvasW = canvas.width || 1920;
|
||||||
|
const canvasH = canvas.height || 1080;
|
||||||
|
|
||||||
|
// 收集所有页面(node.children 包含多页时按页面分组)
|
||||||
|
const pages = [];
|
||||||
|
|
||||||
|
function extractPage(node, parentY) {
|
||||||
|
if (!node || !node.rect) return null;
|
||||||
|
|
||||||
|
const rect = node.rect;
|
||||||
|
const styles = node.styles || {};
|
||||||
|
const layout = node.layout || {};
|
||||||
|
const children = node.children || [];
|
||||||
|
|
||||||
|
// 跳过不可见节点
|
||||||
|
if (styles.display === 'none' || styles.visibility === 'hidden') return null;
|
||||||
|
|
||||||
|
const elements = [];
|
||||||
|
|
||||||
|
// 如果是文字节点
|
||||||
|
if (node.type === 'text' && node.src) {
|
||||||
|
elements.push({
|
||||||
|
type: 'text',
|
||||||
|
text: node.src,
|
||||||
|
options: {
|
||||||
|
x: rect.x / 96,
|
||||||
|
y: (rect.y - parentY) / 96,
|
||||||
|
w: rect.w / 96,
|
||||||
|
h: rect.h / 96,
|
||||||
|
fontSize: styles.fontSize || 14,
|
||||||
|
fontFace: (styles.fontFamily || 'Arial').split(',')[0].replace(/['"]/g, '').trim(),
|
||||||
|
color: (styles.color || '#000000').replace('#', ''),
|
||||||
|
bold: styles.fontWeight >= 700 || styles.fontWeight === 'bold',
|
||||||
|
italic: styles.fontStyle === 'italic',
|
||||||
|
align: styles.textAlign || 'left'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果有背景色或背景图,生成 shape
|
||||||
|
if (styles.backgroundColor && styles.backgroundColor !== 'rgba(0,0,0,0)' && styles.backgroundColor !== 'transparent') {
|
||||||
|
elements.push({
|
||||||
|
type: 'shape',
|
||||||
|
shapeName: 'rect',
|
||||||
|
options: {
|
||||||
|
x: rect.x / 96,
|
||||||
|
y: (rect.y - parentY) / 96,
|
||||||
|
w: rect.w / 96,
|
||||||
|
h: rect.h / 96,
|
||||||
|
fill: { color: styles.backgroundColor.replace('#', '') }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理子节点
|
||||||
|
for (const childId of children) {
|
||||||
|
const childNode = nodes[childId];
|
||||||
|
if (childNode) {
|
||||||
|
const childElements = extractPage(childNode, parentY || rect.y);
|
||||||
|
if (childElements) elements.push(...childElements);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return elements;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 找顶层容器
|
||||||
|
const root = nodes.children ? findRoot(nodes) : null;
|
||||||
|
if (root) {
|
||||||
|
const pageElements = extractPage(root, 0);
|
||||||
|
if (pageElements && pageElements.length > 0) {
|
||||||
|
pages.push({ objects: pageElements });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果没有分页,把整个 canvas 当一页
|
||||||
|
if (pages.length === 0) {
|
||||||
|
const allElements = [];
|
||||||
|
for (const [id, node] of Object.entries(nodes)) {
|
||||||
|
if (node.rect && node.type) {
|
||||||
|
const elems = extractPage(node, 0);
|
||||||
|
if (elems) allElements.push(...elems);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pages.push({ objects: allElements });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 输出 Schema
|
||||||
|
const spec = {
|
||||||
|
presentation: { layout: 'LAYOUT_16x9' },
|
||||||
|
slides: pages
|
||||||
|
};
|
||||||
|
|
||||||
|
fs.writeFileSync(outputPath, JSON.stringify(spec, null, 2));
|
||||||
|
console.log(`Converted: ${inputPath} → ${outputPath}`);
|
||||||
|
console.log(`Pages: ${pages.length}, Total objects: ${pages.reduce((s,p) => s + p.objects.length, 0)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function findRoot(nodes) {
|
||||||
|
// 找没有 parent 引用的节点作为根
|
||||||
|
const allIds = new Set(Object.keys(nodes));
|
||||||
|
const childIds = new Set();
|
||||||
|
for (const [id, node] of Object.entries(nodes)) {
|
||||||
|
if (node.children) {
|
||||||
|
for (const cid of node.children) childIds.add(cid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const id of allIds) {
|
||||||
|
if (!childIds.has(id)) return nodes[id];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// CLI
|
||||||
|
const args = process.argv.slice(2);
|
||||||
|
if (args.length < 1) {
|
||||||
|
console.error('Usage: node convert-w2p.js <input.json> [output.json]');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
const inputPath = args[0];
|
||||||
|
const outputPath = args[1] || inputPath.replace('.json', '_schema.json');
|
||||||
|
convert(inputPath, outputPath);
|
||||||
@@ -0,0 +1,203 @@
|
|||||||
|
# html2pptx 通用转化架构
|
||||||
|
|
||||||
|
## 1. 目标 Schema 定义
|
||||||
|
|
||||||
|
### 顶层结构
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"presentation": {
|
||||||
|
"layout": "CUSTOM",
|
||||||
|
"slideWidth": 24.18, // 英寸
|
||||||
|
"slideHeight": 17.08 // 英寸
|
||||||
|
},
|
||||||
|
"slides": [
|
||||||
|
{
|
||||||
|
"background": { "color": "1A1A1A" }, // 可选
|
||||||
|
"objects": [
|
||||||
|
// text / shape / image / table / chart
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### text 对象
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "文字内容",
|
||||||
|
"options": {
|
||||||
|
"x": 1.463, // 英寸(左上角 x)
|
||||||
|
"y": 1.039, // 英寸(左上角 y)
|
||||||
|
"w": 2.282, // 英寸(宽度)
|
||||||
|
"h": 0.25, // 英寸(高度)
|
||||||
|
"fontSize": 14, // pt
|
||||||
|
"fontFace": "Arial", // 字体名
|
||||||
|
"color": "000000", // 6位hex
|
||||||
|
"bold": false, // 可选
|
||||||
|
"italic": false, // 可选
|
||||||
|
"align": "left", // left/center/right/justify
|
||||||
|
"underline": false, // 可选
|
||||||
|
"charSpacing": 0 // 可选,字符间距
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### shape 对象
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "shape",
|
||||||
|
"shapeName": "rect", // rect/roundRect/ellipse
|
||||||
|
"options": {
|
||||||
|
"x": 0.83,
|
||||||
|
"y": 3.07,
|
||||||
|
"w": 17.78,
|
||||||
|
"h": 3.12,
|
||||||
|
"fill": { "color": "ffffff" },
|
||||||
|
"line": { "type": "none" }, // 可选
|
||||||
|
"shadow": { // 可选
|
||||||
|
"type": "outer",
|
||||||
|
"blur": 15,
|
||||||
|
"offset": 5,
|
||||||
|
"color": "999999",
|
||||||
|
"opacity": 0.5
|
||||||
|
},
|
||||||
|
"rectRadius": 0.1, // 可选,英寸
|
||||||
|
"opacity": 1.0 // 可选
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### image 对象
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "image",
|
||||||
|
"options": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"w": 10,
|
||||||
|
"h": 5,
|
||||||
|
"data": "base64..." // 或 "path": "file.png"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. 通用转化逻辑
|
||||||
|
|
||||||
|
### 核心原则
|
||||||
|
- **不靠命名,靠结构**:slide 容器 = 包含内容子节点的 FRAME
|
||||||
|
- **不靠猜测,靠数据**:所有属性从 JSON 字段直接读取
|
||||||
|
- **不硬编码规则**:通过配置或自动推断
|
||||||
|
|
||||||
|
### 2.1 自动识别 Slide 容器
|
||||||
|
|
||||||
|
```
|
||||||
|
输入:web-to-pixso JSON 的 nodes 树
|
||||||
|
输出:slide 容器列表
|
||||||
|
|
||||||
|
策略:
|
||||||
|
1. 遍历 nodes 树,找 FRAME 类型节点
|
||||||
|
2. 判断条件(满足任一):
|
||||||
|
a) name 匹配已知模式:slide-N / page / slide+空格 / slide+横杠
|
||||||
|
b) 结构特征:
|
||||||
|
- children 数量 > 0
|
||||||
|
- 面积接近 canvas 面积(> 50% canvas 面积)
|
||||||
|
- 不是 BODY / HTML / HEAD 等结构性节点
|
||||||
|
3. 排除条件:
|
||||||
|
- layerGroup === 'comparison'(截图底图)
|
||||||
|
- 没有 children
|
||||||
|
- 面积太小(< 100px × 100px)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 收集子节点(递归)
|
||||||
|
|
||||||
|
```
|
||||||
|
collectSlideChildren(node, slideX, slideY, parentW, depth):
|
||||||
|
对于每个子节点:
|
||||||
|
- 计算相对坐标:rect.x - slideX, rect.y - slideY
|
||||||
|
- 记录 parentW(父容器宽度)
|
||||||
|
- 记录 depth(层级深度)
|
||||||
|
- 跳过 RECTANGLE 类型(截图底图)
|
||||||
|
- 跳过 layerGroup === 'comparison'
|
||||||
|
- 递归处理 children
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.3 收集游离节点
|
||||||
|
|
||||||
|
```
|
||||||
|
collectOrphans(root, slideIds):
|
||||||
|
slideIds = 所有 slide 容器的 id 集合
|
||||||
|
|
||||||
|
遍历 root 树:
|
||||||
|
- 如果节点 id 在 slideIds 中 → 跳过整个子树
|
||||||
|
- 否则 → 收集该节点,递归 children
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.4 映射 TEXT 节点
|
||||||
|
|
||||||
|
```
|
||||||
|
输入:TEXT 类型节点
|
||||||
|
输出:text 对象
|
||||||
|
|
||||||
|
映射规则:
|
||||||
|
fontSize = parseFloat(styles.fontSize) || 14
|
||||||
|
fontFace = styles.fontFamily.split(',')[0].trim()
|
||||||
|
color = rgbaToHex(styles.color) || '000000'
|
||||||
|
bold = parseInt(styles.fontWeight) >= 700
|
||||||
|
italic = styles.fontStyle === 'italic'
|
||||||
|
align = styles.textAlign(非 'start' 时)
|
||||||
|
underline = styles.textDecorationLine 包含 'underline'
|
||||||
|
charSpacing = styles.letterSpacing 转换
|
||||||
|
|
||||||
|
宽度计算:
|
||||||
|
contentW = slide 内容宽度(从子节点 x 范围推算)
|
||||||
|
textW = parentW || rect.w
|
||||||
|
if (depth === 1) textW = contentW
|
||||||
|
if (textW > contentW) textW = contentW
|
||||||
|
if (textW < 108) textW = 108 // 最小 1.5in
|
||||||
|
if (x + textW > contentW) textW = contentW - x
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.5 映射 FRAME 节点 → shape
|
||||||
|
|
||||||
|
```
|
||||||
|
输入:FRAME 类型节点(有背景色)
|
||||||
|
输出:shape 对象
|
||||||
|
|
||||||
|
映射规则:
|
||||||
|
fillColor = rgbaToHex(styles.backgroundColor)
|
||||||
|
渐变背景 = backgroundImage 提取起始色
|
||||||
|
圆角 = borderTopLeftRadius 转换
|
||||||
|
边框 = 四边都有 border 时加 line
|
||||||
|
阴影 = boxShadow 解析
|
||||||
|
单边 border = 独立线条 shape
|
||||||
|
默认 line = { type: 'none' }(去掉 pptxgenjs 默认边框)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.6 背景色推断
|
||||||
|
|
||||||
|
```
|
||||||
|
优先级:
|
||||||
|
1. slide 容器的 styles.backgroundColor(非透明时)
|
||||||
|
2. canvas.backgroundColor(全局兜底)
|
||||||
|
3. 从 slide name 推断(dark → #1A1A1A, light → #FAFAFA)
|
||||||
|
4. 从文字颜色推断(浅色文字 → 深色背景,深色文字 → 浅色背景)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. 当前硬编码 → 通用化改造清单
|
||||||
|
|
||||||
|
| 硬编码 | 当前逻辑 | 通用化方案 |
|
||||||
|
|--------|---------|-----------|
|
||||||
|
| slide 匹配 | `slide-N` / `page` / `slide ` | 结构识别 + 已知模式列表 |
|
||||||
|
| 游离节点跳过 | `startsWith('slide-')` / `startsWith('slide ')` | 用 slide id 集合判断 |
|
||||||
|
| 背景色 | `dark`/`light` 关键词 | 优先取 styles,兜底取 canvas |
|
||||||
|
| TEXT 宽度 | `depth === 1` 判断顶层 | 用 parentW 与 contentW 的关系判断 |
|
||||||
|
| 边框 | 只处理 borderTop | 四边独立处理 |
|
||||||
|
|
||||||
|
## 4. 下一步
|
||||||
|
|
||||||
|
1. 重构 `findSlides()` — 支持结构识别
|
||||||
|
2. 重构 `collectOrphans()` — 用 id 集合判断
|
||||||
|
3. 重构背景色逻辑 — 多级兜底
|
||||||
|
4. 补全 styles 映射 — opacity / lineHeight / letterSpacing / textDecoration
|
||||||
|
5. 测试多个 HTML 样本验证通用性
|
||||||
@@ -0,0 +1,152 @@
|
|||||||
|
# web-to-pixso → PPT 映射分析
|
||||||
|
|
||||||
|
## 1. JSON 结构总览
|
||||||
|
|
||||||
|
### 顶层字段
|
||||||
|
| 字段 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| `canvas` | 画布尺寸 `{width, height, x, y, backgroundColor}` |
|
||||||
|
| `nodes` | 根节点树(type=FRAME, name=html) |
|
||||||
|
| `assets` | 图片资源 `{key: base64}` |
|
||||||
|
| `fonts` | 字体列表 |
|
||||||
|
| `schemaVersion` | JSON 版本号 |
|
||||||
|
|
||||||
|
### 节点类型
|
||||||
|
| 类型 | 数量 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| FRAME | 471 | HTML 容器(div, section, table, header 等) |
|
||||||
|
| TEXT | 393 | 文字节点 |
|
||||||
|
| RECTANGLE | 10 | 截图底图(comparison layer,用于视觉对比) |
|
||||||
|
|
||||||
|
### 节点属性
|
||||||
|
```
|
||||||
|
type - FRAME/TEXT/RECTANGLE
|
||||||
|
rect - {x, y, width, height} 像素坐标
|
||||||
|
styles - 57个CSS属性(见下表)
|
||||||
|
name - HTML元素名或文字内容
|
||||||
|
tag - HTML标签名(HTML/DIV/TABLE/TR/TD/SPAN/#text等)
|
||||||
|
text / src - 文字内容(TEXT节点用text,收集时映射为src)
|
||||||
|
layerGroup - editable/text/comparison
|
||||||
|
backgroundImages - 图片资源引用数组
|
||||||
|
clipRect - 裁剪区域
|
||||||
|
visibleRect - 可见区域
|
||||||
|
clipped - 是否被裁剪
|
||||||
|
id - 节点ID
|
||||||
|
section - document
|
||||||
|
visualRole - layout-wrapper/text-node/raster-fallback
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. 57个 styles 字段分类
|
||||||
|
|
||||||
|
### 已处理(15个)
|
||||||
|
| 字段 | 用途 | 当前处理方式 |
|
||||||
|
|------|------|-------------|
|
||||||
|
| backgroundColor | 背景色 | → shape fill |
|
||||||
|
| backgroundImage | 渐变背景 | → 提取起始色做纯色 fill |
|
||||||
|
| borderTopWidth/Color/Style | 顶部边框 | → shape line(四边都有时)或单独线条 |
|
||||||
|
| borderBottomWidth/Color/Style | 底部边框 | 同上 |
|
||||||
|
| borderLeftWidth/Color/Style | 左边框 | 同上 |
|
||||||
|
| borderRightWidth/Color/Style | 右边框 | 同上 |
|
||||||
|
| borderTopLeftRadius | 圆角 | → rectRadius |
|
||||||
|
| boxShadow | 阴影 | → shadow |
|
||||||
|
| fontSize | 字号 | → fontSize(pt) |
|
||||||
|
| fontFamily | 字体 | → fontFace |
|
||||||
|
| color | 文字颜色 | → color |
|
||||||
|
| fontWeight | 粗体 | → bold |
|
||||||
|
| fontStyle | 斜体 | → italic |
|
||||||
|
| textAlign | 对齐 | → align |
|
||||||
|
|
||||||
|
### 未处理(42个)
|
||||||
|
| 字段 | 重要性 | 说明 |
|
||||||
|
|------|--------|------|
|
||||||
|
| **opacity** | 高 | 元素透明度,PPT支持 `opacity` |
|
||||||
|
| **lineHeight** | 高 | 行高,影响文字换行和间距 |
|
||||||
|
| **letterSpacing** | 中 | 字间距,PPT支持 `charSpacing` |
|
||||||
|
| **textDecorationLine** | 中 | 下划线/删除线 |
|
||||||
|
| **textTransform** | 低 | 大小写转换 |
|
||||||
|
| **whiteSpace** | 高 | 控制换行行为(nowrap等) |
|
||||||
|
| **wordBreak** | 中 | 断词规则 |
|
||||||
|
| **visibility** | 中 | visible/hidden |
|
||||||
|
| **display** | 中 | none时不渲染 |
|
||||||
|
| **transform** | 中 | 旋转/缩放/位移 |
|
||||||
|
| **transformOrigin** | 低 | 变换原点 |
|
||||||
|
| **zIndex** | 高 | 层级顺序 |
|
||||||
|
| **overflow** | 中 | 溢出处理 |
|
||||||
|
| **clipPath** | 低 | 裁剪路径 |
|
||||||
|
| **filter** | 低 | CSS滤镜 |
|
||||||
|
| **borderTopRightRadius** | 中 | 右上圆角(目前只用左上) |
|
||||||
|
| **borderBottomLeftRadius** | 中 | 左下圆角 |
|
||||||
|
| **borderBottomRightRadius** | 中 | 右下圆角 |
|
||||||
|
| **backgroundPosition** | 低 | 背景定位 |
|
||||||
|
| **backgroundSize** | 低 | 背景尺寸 |
|
||||||
|
| **backgroundRepeat** | 低 | 背景重复 |
|
||||||
|
| **backgroundClip** | 低 | 背景裁剪 |
|
||||||
|
| **backgroundOrigin** | 低 | 背景原点 |
|
||||||
|
| **position** | 低 | 定位方式 |
|
||||||
|
| **objectFit** | 低 | 图片适配 |
|
||||||
|
| **objectPosition** | 低 | 图片定位 |
|
||||||
|
| **maskImage/MaskPosition/MaskSize** | 低 | 遮罩 |
|
||||||
|
| **webkitMaskImage/MaskPosition/MaskSize** | 低 | 遮罩(webkit) |
|
||||||
|
| **backdropFilter** | 低 | 背景滤镜 |
|
||||||
|
|
||||||
|
## 3. 映射优先级
|
||||||
|
|
||||||
|
### P0(必须处理)
|
||||||
|
1. **opacity** → shape/text 的 opacity 属性
|
||||||
|
2. **whiteSpace** → 控制文字是否换行
|
||||||
|
3. **zIndex** → 调整 objects 数组顺序(后渲染在上层)
|
||||||
|
4. **lineHeight** → 文字行高(paraSpaceBefore/after 或 lineSpacingMultiple)
|
||||||
|
|
||||||
|
### P1(应该处理)
|
||||||
|
5. **letterSpacing** → charSpacing(字符间距)
|
||||||
|
6. **textDecorationLine** → underline/strike
|
||||||
|
7. **display:none** → 跳过该节点
|
||||||
|
8. **visibility:hidden** → 跳过该节点
|
||||||
|
9. **四个圆角独立处理** → 当前只用了 borderTopLeftRadius
|
||||||
|
|
||||||
|
### P2(可以处理)
|
||||||
|
10. **transform** → rotate 属性
|
||||||
|
11. **wordBreak** → 辅助判断换行
|
||||||
|
12. **overflow** → 辅助判断裁剪
|
||||||
|
13. **filter** → 特殊效果(大部分PPT不支持)
|
||||||
|
|
||||||
|
## 4. 当前代码架构
|
||||||
|
|
||||||
|
```
|
||||||
|
HTML → web-to-pixso 插件 → JSON
|
||||||
|
↓
|
||||||
|
convert-w2p.js(提取+映射)
|
||||||
|
↓
|
||||||
|
Schema JSON
|
||||||
|
↓
|
||||||
|
pptxgenjs(渲染)
|
||||||
|
↓
|
||||||
|
.pptx 文件
|
||||||
|
```
|
||||||
|
|
||||||
|
### convert-w2p.js 处理流程
|
||||||
|
1. findSlides() — 找 page/slide-N 容器
|
||||||
|
2. collectSlideChildren() — 递归提取子节点(保留层级)
|
||||||
|
3. collectOrphans() — 收集游离节点归入最近 slide
|
||||||
|
4. 排序:FRAME 在前,TEXT 在后
|
||||||
|
5. 映射循环:遍历每个子节点
|
||||||
|
- TEXT → text 对象(含字号/字体/颜色/粗体/对齐)
|
||||||
|
- IMAGE → image 对象
|
||||||
|
- FRAME with bg → shape 对象(含 fill/border/shadow/rectRadius)
|
||||||
|
- 四边 border → 独立线条 shape
|
||||||
|
6. 输出 Schema JSON + 调用 pptxgenjs 生成 PPTX
|
||||||
|
|
||||||
|
## 5. 下一步行动
|
||||||
|
|
||||||
|
### 短期(本次迭代)
|
||||||
|
- [ ] 实现 P0:opacity、whiteSpace、zIndex、lineHeight
|
||||||
|
- [ ] 实现 P1:letterSpacing、textDecoration、display/visibility 过滤
|
||||||
|
|
||||||
|
### 中期
|
||||||
|
- [ ] 建立 web-to-pixso JSON 的完整字段文档
|
||||||
|
- [ ] 验证不同 HTML 结构的 JSON 输出一致性
|
||||||
|
- [ ] 处理 transform(旋转)
|
||||||
|
|
||||||
|
### 长期
|
||||||
|
- [ ] 参考设计稿,实现高度还原的 PPT 输出
|
||||||
|
- [ ] 处理复杂布局(grid/flex 嵌套)
|
||||||
@@ -0,0 +1,781 @@
|
|||||||
|
# 图表参数 (Chart Props)
|
||||||
|
|
||||||
|
图表参数来自 `IChartOpts` 及其继承的所有接口,用于 `slide.addChart()` 方法。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 图表类型 (CHART_NAME)
|
||||||
|
|
||||||
|
- 类型:`'area' | 'bar' | 'bar3D' | 'bubble' | 'doughnut' | 'line' | 'pie' | 'radar' | 'scatter'`
|
||||||
|
|
||||||
|
可通过 `pptxgen.ChartType.XXX` 常量引用:
|
||||||
|
- `ChartType.area`、`ChartType.bar`、`ChartType.bar3d`、`ChartType.bubble`、`ChartType.bubble3d`、
|
||||||
|
`ChartType.doughnut`、`ChartType.line`、`ChartType.pie`、`ChartType.radar`、`ChartType.scatter`
|
||||||
|
|
||||||
|
## 多图类型 (IChartMulti)
|
||||||
|
|
||||||
|
```
|
||||||
|
{ type: CHART_NAME, data: OptsChartData[], options: IChartOpts }
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 数据接口 (OptsChartData)
|
||||||
|
|
||||||
|
- **labels**:分类标签
|
||||||
|
- 类型:`string[] | string[][]`
|
||||||
|
- 示例:`['Year 2000', 'Year 2010', 'Year 2020']`
|
||||||
|
- 说明:`string[][]` 用于多级分类轴,始于 v3.11.0
|
||||||
|
|
||||||
|
- **name**:系列名称
|
||||||
|
- 类型:`string`
|
||||||
|
- 示例:`'Locations'`
|
||||||
|
|
||||||
|
- **sizes**:气泡大小
|
||||||
|
- 类型:`number[]`
|
||||||
|
- 说明:仅用于气泡图
|
||||||
|
- 示例:`[5, 1, 5, 1]`
|
||||||
|
|
||||||
|
- **values**:分类数值
|
||||||
|
- 类型:`number[]`
|
||||||
|
- 示例:`[2000, 2010, 2020]`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 位置属性 (PositionProps)
|
||||||
|
|
||||||
|
- **x**:水平位置
|
||||||
|
- 类型:`Coord`(`number | `${number}%``)
|
||||||
|
- 单位:英寸(number)或百分比(`'50%'`)
|
||||||
|
|
||||||
|
- **y**:垂直位置
|
||||||
|
- 类型:`Coord`
|
||||||
|
- 单位:英寸或百分比
|
||||||
|
|
||||||
|
- **w**:宽度
|
||||||
|
- 类型:`Coord`
|
||||||
|
- 单位:英寸或百分比
|
||||||
|
|
||||||
|
- **h**:高度
|
||||||
|
- 类型:`Coord`
|
||||||
|
- 单位:英寸或百分比
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 对象名称属性 (ObjectNameProps)
|
||||||
|
|
||||||
|
- **objectName**:对象名称
|
||||||
|
- 类型:`string`
|
||||||
|
- 说明:替代默认的 "Object N" 名称
|
||||||
|
- 默认值:`'Object 1'`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 基础图表属性 (IChartPropsBase)
|
||||||
|
|
||||||
|
- **axisPos**:坐标轴位置
|
||||||
|
- 类型:`'b' | 'l' | 'r' | 't'`
|
||||||
|
- 取值范围:`b`(下)、`l`(左)、`r`(右)、`t`(上)
|
||||||
|
|
||||||
|
- **chartColors**:图表系列颜色
|
||||||
|
- 类型:`HexColor[]`
|
||||||
|
- 格式:6 位十六进制颜色数组(无 `#`)
|
||||||
|
|
||||||
|
- **chartColorsOpacity**:系列颜色不透明度
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`0`–`100`
|
||||||
|
- 示例:`50`(50% 不透明)
|
||||||
|
|
||||||
|
- **dataBorder**:数据点边框
|
||||||
|
- 类型:`BorderProps`
|
||||||
|
- 子参数:
|
||||||
|
- **dataBorder.type**:`'none' | 'dash' | 'solid'`
|
||||||
|
- **dataBorder.color**:`HexColor`
|
||||||
|
- **dataBorder.pt**:边框粗细(pt)
|
||||||
|
|
||||||
|
- **displayBlanksAs**:空值显示方式
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **invertedColors**:反色数组
|
||||||
|
- 类型:`HexColor[]`
|
||||||
|
|
||||||
|
- **lang**:语言
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **layout**:图表在幻灯片中的位置
|
||||||
|
- 类型:`PositionProps`
|
||||||
|
|
||||||
|
- **shadow**:阴影
|
||||||
|
- 类型:`ShadowProps`
|
||||||
|
|
||||||
|
- **showLabel**:显示数据标签
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 默认值:`false`
|
||||||
|
|
||||||
|
- **showLeaderLines**:显示引导线
|
||||||
|
- 类型:`boolean`
|
||||||
|
|
||||||
|
- **showLegend**:显示图例
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 默认值:`false`
|
||||||
|
|
||||||
|
- **showPercent**:显示百分比
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 默认值:`false`
|
||||||
|
|
||||||
|
- **showSerName**:显示系列名称
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 默认值:`false`
|
||||||
|
|
||||||
|
- **showTitle**:显示图表标题
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 默认值:`false`
|
||||||
|
|
||||||
|
- **showValue**:显示数值
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 默认值:`false`
|
||||||
|
|
||||||
|
- **v3DPerspective**:3D 透视
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`0`–`120`
|
||||||
|
- 默认值:`30`
|
||||||
|
|
||||||
|
- **v3DRAngAx**:直角坐标轴
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 说明:启用时覆盖 `v3DPerspective`,以第一人称视角显示图表
|
||||||
|
- 默认值:`false`
|
||||||
|
|
||||||
|
- **v3DRotX**:X 轴旋转
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`0`–`359.9`
|
||||||
|
- 默认值:`30`
|
||||||
|
|
||||||
|
- **v3DRotY**:Y 轴旋转
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`0`–`359.9`
|
||||||
|
- 默认值:`30`
|
||||||
|
|
||||||
|
- **chartArea**:图表区域样式
|
||||||
|
- 类型:`IChartAreaProps`(继承 `IChartPropsFillLine`)
|
||||||
|
- 子参数:
|
||||||
|
- **chartArea.border**:边框
|
||||||
|
- **chartArea.fill**:填充
|
||||||
|
- **chartArea.roundedCorners**:圆角
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 默认值:`true`
|
||||||
|
- 始于:v3.11
|
||||||
|
|
||||||
|
- **plotArea**:绘图区样式
|
||||||
|
- 类型:`IChartPropsFillLine`
|
||||||
|
- 子参数:
|
||||||
|
- **plotArea.border**:绘图区边框
|
||||||
|
- **plotArea.fill**:绘图区填充
|
||||||
|
- 始于:v3.11
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 分类轴属性 (IChartPropsAxisCat)
|
||||||
|
|
||||||
|
- **catAxes**:多图多轴设置
|
||||||
|
- 类型:`IChartPropsAxisCat[]`
|
||||||
|
|
||||||
|
- **catAxisBaseTimeUnit**:分类轴基础时间单位
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **catAxisCrossesAt**:数值轴交叉位置
|
||||||
|
- 类型:`number | 'autoZero'`
|
||||||
|
|
||||||
|
- **catAxisHidden**:隐藏分类轴
|
||||||
|
- 类型:`boolean`
|
||||||
|
|
||||||
|
- **catAxisLabelColor**:轴标签颜色
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **catAxisLabelFontBold**:轴标签粗体
|
||||||
|
- 类型:`boolean`
|
||||||
|
|
||||||
|
- **catAxisLabelFontFace**:轴标签字体
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **catAxisLabelFontItalic**:轴标签斜体
|
||||||
|
- 类型:`boolean`
|
||||||
|
|
||||||
|
- **catAxisLabelFontSize**:轴标签字号
|
||||||
|
- 类型:`number`
|
||||||
|
- 单位:pt
|
||||||
|
- 取值范围:无特殊约束范围
|
||||||
|
- 无默认值
|
||||||
|
|
||||||
|
- **catAxisLabelFrequency**:标签频率
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **catAxisLabelPos**:标签位置
|
||||||
|
- 类型:`'none' | 'low' | 'high' | 'nextTo'`
|
||||||
|
|
||||||
|
- **catAxisLabelRotate**:标签旋转角度
|
||||||
|
- 类型:`number`
|
||||||
|
- 单位:度
|
||||||
|
- 取值范围:无特殊约束范围
|
||||||
|
- 无默认值
|
||||||
|
|
||||||
|
- **catAxisLineColor**:轴线颜色
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **catAxisLineShow**:显示轴线
|
||||||
|
- 类型:`boolean`
|
||||||
|
|
||||||
|
- **catAxisLineSize**:轴线粗细
|
||||||
|
- 类型:`number`
|
||||||
|
- 单位:pt
|
||||||
|
- 取值范围:无特殊约束范围
|
||||||
|
- 无默认值
|
||||||
|
|
||||||
|
- **catAxisLineStyle**:轴线样式
|
||||||
|
- 类型:`'solid' | 'dash' | 'dot'`
|
||||||
|
|
||||||
|
- **catAxisMajorTickMark**:主轴刻度标记
|
||||||
|
- 类型:`ChartAxisTickMark`(`'none' | 'inside' | 'outside' | 'cross'`)
|
||||||
|
|
||||||
|
- **catAxisMajorTimeUnit**:主要时间单位
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **catAxisMajorUnit**:主要刻度单位
|
||||||
|
- 类型:`number`
|
||||||
|
- 单位:无单位(数值刻度)
|
||||||
|
- 取值范围:无特殊约束范围
|
||||||
|
- 无默认值
|
||||||
|
|
||||||
|
- **catAxisMaxVal**:轴最大值
|
||||||
|
- 类型:`number`
|
||||||
|
|
||||||
|
- **catAxisMinorTickMark**:副轴刻度标记
|
||||||
|
- 类型:`ChartAxisTickMark`
|
||||||
|
|
||||||
|
- **catAxisMinorTimeUnit**:次要时间单位
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **catAxisMinorUnit**:次要刻度单位
|
||||||
|
- 类型:`number`
|
||||||
|
- 单位:无单位(数值刻度)
|
||||||
|
- 取值范围:无特殊约束范围
|
||||||
|
- 无默认值
|
||||||
|
|
||||||
|
- **catAxisMinVal**:轴最小值
|
||||||
|
- 类型:`number`
|
||||||
|
|
||||||
|
- **catAxisMultiLevelLabels**:多级标签
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 始于:v3.11.0
|
||||||
|
|
||||||
|
- **catAxisOrientation**:轴方向
|
||||||
|
- 类型:`'minMax'`
|
||||||
|
|
||||||
|
- **catAxisTitle**:轴标题
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **catAxisTitleColor**:轴标题颜色
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **catAxisTitleFontFace**:轴标题字体
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **catAxisTitleFontSize**:轴标题字号
|
||||||
|
- 类型:`number`
|
||||||
|
|
||||||
|
- **catAxisTitleRotate**:轴标题旋转
|
||||||
|
- 类型:`number`
|
||||||
|
- 单位:度
|
||||||
|
- 取值范围:无特殊约束范围
|
||||||
|
- 无默认值
|
||||||
|
|
||||||
|
- **catGridLine**:分类网格线
|
||||||
|
- 类型:`OptsChartGridLine`
|
||||||
|
|
||||||
|
- **catLabelFormatCode**:分类标签格式代码
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **secondaryCatAxis**:使用次要分类轴
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 默认值:`false`
|
||||||
|
|
||||||
|
- **showCatAxisTitle**:显示分类轴标题
|
||||||
|
- 类型:`boolean`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 系列轴属性 (IChartPropsAxisSer)
|
||||||
|
|
||||||
|
- **serAxisBaseTimeUnit**:系列轴基础时间单位
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **serAxisHidden**:隐藏系列轴
|
||||||
|
- 类型:`boolean`
|
||||||
|
|
||||||
|
- **serAxisLabelColor**:系列轴标签颜色
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **serAxisLabelFontBold**:系列轴标签粗体
|
||||||
|
- 类型:`boolean`
|
||||||
|
|
||||||
|
- **serAxisLabelFontFace**:系列轴标签字体
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **serAxisLabelFontItalic**:系列轴标签斜体
|
||||||
|
- 类型:`boolean`
|
||||||
|
|
||||||
|
- **serAxisLabelFontSize**:系列轴标签字号
|
||||||
|
- 类型:`number`
|
||||||
|
- 单位:pt
|
||||||
|
- 取值范围:无特殊约束范围
|
||||||
|
- 无默认值
|
||||||
|
|
||||||
|
- **serAxisLabelFrequency**:系列轴标签频率
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **serAxisLabelPos**:系列轴标签位置
|
||||||
|
- 类型:`'none' | 'low' | 'high' | 'nextTo'`
|
||||||
|
|
||||||
|
- **serAxisLineColor**:系列轴线颜色
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **serAxisLineShow**:显示系列轴线
|
||||||
|
- 类型:`boolean`
|
||||||
|
|
||||||
|
- **serAxisMajorTimeUnit**:系列轴主要时间单位
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **serAxisMajorUnit**:系列轴主要刻度单位
|
||||||
|
- 类型:`number`
|
||||||
|
|
||||||
|
- **serAxisMinorTimeUnit**:系列轴次要时间单位
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **serAxisMinorUnit**:系列轴次要刻度单位
|
||||||
|
- 类型:`number`
|
||||||
|
|
||||||
|
- **serAxisOrientation**:系列轴方向
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **serAxisTitle**:系列轴标题
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **serAxisTitleColor**:系列轴标题颜色
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **serAxisTitleFontFace**:系列轴标题字体
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **serAxisTitleFontSize**:系列轴标题字号
|
||||||
|
- 类型:`number`
|
||||||
|
|
||||||
|
- **serAxisTitleRotate**:系列轴标题旋转
|
||||||
|
- 类型:`number`
|
||||||
|
|
||||||
|
- **serGridLine**:系列网格线
|
||||||
|
- 类型:`OptsChartGridLine`
|
||||||
|
|
||||||
|
- **serLabelFormatCode**:系列标签格式代码
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **showSerAxisTitle**:显示系列轴标题
|
||||||
|
- 类型:`boolean`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 数值轴属性 (IChartPropsAxisVal)
|
||||||
|
|
||||||
|
- **secondaryValAxis**:使用次要数值轴
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 默认值:`false`
|
||||||
|
|
||||||
|
- **showValAxisTitle**:显示数值轴标题
|
||||||
|
- 类型:`boolean`
|
||||||
|
|
||||||
|
- **valAxes**:多图多数值轴
|
||||||
|
- 类型:`IChartPropsAxisVal[]`
|
||||||
|
|
||||||
|
- **valAxisCrossesAt**:数值轴交叉位置
|
||||||
|
- 类型:`number | 'autoZero'`
|
||||||
|
|
||||||
|
- **valAxisDisplayUnit**:显示单位
|
||||||
|
- 类型:`'billions' | 'hundredMillions' | 'hundreds' | 'hundredThousands' | 'millions' | 'tenMillions' | 'tenThousands' | 'thousands' | 'trillions'`
|
||||||
|
|
||||||
|
- **valAxisDisplayUnitLabel**:显示单位标签
|
||||||
|
- 类型:`boolean`
|
||||||
|
|
||||||
|
- **valAxisHidden**:隐藏数值轴
|
||||||
|
- 类型:`boolean`
|
||||||
|
|
||||||
|
- **valAxisLabelColor**:数值轴标签颜色
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **valAxisLabelFontBold**:数值轴标签粗体
|
||||||
|
- 类型:`boolean`
|
||||||
|
|
||||||
|
- **valAxisLabelFontFace**:数值轴标签字体
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **valAxisLabelFontItalic**:数值轴标签斜体
|
||||||
|
- 类型:`boolean`
|
||||||
|
|
||||||
|
- **valAxisLabelFontSize**:数值轴标签字号
|
||||||
|
- 类型:`number`
|
||||||
|
- 单位:pt
|
||||||
|
- 取值范围:无特殊约束范围
|
||||||
|
- 无默认值
|
||||||
|
|
||||||
|
- **valAxisLabelFormatCode**:数值轴标签格式代码
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **valAxisLabelPos**:数值轴标签位置
|
||||||
|
- 类型:`'none' | 'low' | 'high' | 'nextTo'`
|
||||||
|
|
||||||
|
- **valAxisLabelRotate**:数值轴标签旋转
|
||||||
|
- 类型:`number`
|
||||||
|
- 单位:度
|
||||||
|
- 取值范围:无特殊约束范围
|
||||||
|
- 无默认值
|
||||||
|
|
||||||
|
- **valAxisLineColor**:数值轴线颜色
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **valAxisLineShow**:显示数值轴线
|
||||||
|
- 类型:`boolean`
|
||||||
|
|
||||||
|
- **valAxisLineSize**:数值轴线粗细
|
||||||
|
- 类型:`number`
|
||||||
|
- 单位:pt
|
||||||
|
- 取值范围:无特殊约束范围
|
||||||
|
- 无默认值
|
||||||
|
|
||||||
|
- **valAxisLineStyle**:数值轴线样式
|
||||||
|
- 类型:`'solid' | 'dash' | 'dot'`
|
||||||
|
|
||||||
|
- **valAxisLogScaleBase**:对数刻度基数
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`2`–`99`
|
||||||
|
- 始于:v3.5.0
|
||||||
|
|
||||||
|
- **valAxisMajorTickMark**:数值轴主刻度标记
|
||||||
|
- 类型:`ChartAxisTickMark`
|
||||||
|
|
||||||
|
- **valAxisMajorUnit**:数值轴主要刻度单位
|
||||||
|
- 类型:`number`
|
||||||
|
- 单位:无单位(数值刻度)
|
||||||
|
- 取值范围:无特殊约束范围
|
||||||
|
- 无默认值
|
||||||
|
|
||||||
|
- **valAxisMaxVal**:数值轴最大值
|
||||||
|
- 类型:`number`
|
||||||
|
- 单位:无单位(数值与数据一致)
|
||||||
|
- 取值范围:无特殊约束范围
|
||||||
|
- 无默认值
|
||||||
|
|
||||||
|
- **valAxisMinorTickMark**:数值轴副刻度标记
|
||||||
|
- 类型:`ChartAxisTickMark`
|
||||||
|
|
||||||
|
- **valAxisMinVal**:数值轴最小值
|
||||||
|
- 类型:`number`
|
||||||
|
- 单位:无单位(数值与数据一致)
|
||||||
|
- 取值范围:无特殊约束范围
|
||||||
|
- 无默认值
|
||||||
|
|
||||||
|
- **valAxisOrientation**:数值轴方向
|
||||||
|
- 类型:`'minMax'`
|
||||||
|
|
||||||
|
- **valAxisTitle**:数值轴标题
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **valAxisTitleColor**:数值轴标题颜色
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **valAxisTitleFontFace**:数值轴标题字体
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **valAxisTitleFontSize**:数值轴标题字号
|
||||||
|
- 类型:`number`
|
||||||
|
|
||||||
|
- **valAxisTitleRotate**:数值轴标题旋转
|
||||||
|
- 类型:`number`
|
||||||
|
|
||||||
|
- **valGridLine**:数值网格线
|
||||||
|
- 类型:`OptsChartGridLine`
|
||||||
|
|
||||||
|
- **valLabelFormatCode**:数值标签格式代码
|
||||||
|
- 类型:`string`
|
||||||
|
- 始于:v3.3.0
|
||||||
|
- 示例:`'#%'`、`'0.00%'`、`'$0.00'`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 柱状图属性 (IChartPropsChartBar)
|
||||||
|
|
||||||
|
- **bar3DShape**:3D 柱形形状
|
||||||
|
- 类型:`string`
|
||||||
|
- 取值范围:`'box' | 'cylinder' | 'cone' | 'pyramid'`(PowerPoint 标准值),类型声明为 string,也可接受其他值
|
||||||
|
- 默认值:`'box'`
|
||||||
|
|
||||||
|
- **barDir**:柱形方向
|
||||||
|
- 类型:`string`
|
||||||
|
- 取值范围:`'bar'`(水平)、`'col'`(垂直)
|
||||||
|
- 默认值:`'col'`
|
||||||
|
|
||||||
|
- **barGapDepthPct**:3D 柱形深度间距百分比
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`0`–`500`
|
||||||
|
- 默认值:`150`
|
||||||
|
- 单位:%
|
||||||
|
|
||||||
|
- **barGapWidthPct**:间距宽度百分比
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`0`–`500`
|
||||||
|
- 默认值:`150`
|
||||||
|
|
||||||
|
- **barGrouping**:分组方式
|
||||||
|
- 类型:`string`
|
||||||
|
- 取值范围:`'clustered' | 'stacked' | 'standard'`(PowerPoint 标准值),类型声明为 string,也可接受其他值
|
||||||
|
- 默认值:`'clustered'`
|
||||||
|
|
||||||
|
- **barOverlapPct**:系列重叠百分比
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`-100`–`100`
|
||||||
|
- 默认值:`0`
|
||||||
|
- 始于:v3.9.0
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 圆环图属性 (IChartPropsChartDoughnut)
|
||||||
|
|
||||||
|
- **dataNoEffects**:禁用数据点效果
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 默认值:`false`
|
||||||
|
|
||||||
|
- **holeSize**:中心孔大小
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`0`–`90`(百分比)
|
||||||
|
- 默认值:`50`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 折线图属性 (IChartPropsChartLine)
|
||||||
|
|
||||||
|
- **lineCap**:线帽类型
|
||||||
|
- 类型:`ChartLineCap`(`'flat' | 'round' | 'square'`)
|
||||||
|
- 默认值:`'flat'`
|
||||||
|
|
||||||
|
- **lineDash**:线型
|
||||||
|
- 类型:`'dash' | 'dashDot' | 'lgDash' | 'lgDashDot' | 'lgDashDotDot' | 'solid' | 'sysDash' | 'sysDot'`
|
||||||
|
- 默认值:`'solid'`
|
||||||
|
|
||||||
|
- **lineDataSymbol**:数据点标记类型
|
||||||
|
- 类型:`'circle' | 'dash' | 'diamond' | 'dot' | 'none' | 'square' | 'triangle'`
|
||||||
|
- 默认值:`'circle'`
|
||||||
|
|
||||||
|
- **lineDataSymbolLineColor**:标记边框颜色
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **lineDataSymbolLineSize**:标记边框宽度
|
||||||
|
- 类型:`number`
|
||||||
|
- 默认值:`0.75`
|
||||||
|
|
||||||
|
- **lineDataSymbolSize**:标记大小
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`2`–`72`
|
||||||
|
- 默认值:`6`
|
||||||
|
|
||||||
|
- **lineSize**:线条宽度
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`0`–`1584`
|
||||||
|
- 默认值:`2`
|
||||||
|
|
||||||
|
- **lineSmooth**:平滑线
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 默认值:`false`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 饼图属性 (IChartPropsChartPie)
|
||||||
|
|
||||||
|
- **dataNoEffects**:禁用数据点效果
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 默认值:`false`
|
||||||
|
|
||||||
|
- **firstSliceAng**:第一扇区起始角度
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`0`–`359`
|
||||||
|
- 默认值:`0`
|
||||||
|
- 始于:v3.4.0
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 雷达图属性 (IChartPropsChartRadar)
|
||||||
|
|
||||||
|
- **radarStyle**:雷达图样式
|
||||||
|
- 类型:`'standard' | 'marker' | 'filled'`
|
||||||
|
- 默认值:`'standard'`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 数据标签属性 (IChartPropsDataLabel)
|
||||||
|
|
||||||
|
- **dataLabelBkgrdColors**:数据标签背景色
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 默认值:`false`
|
||||||
|
|
||||||
|
- **dataLabelColor**:数据标签颜色
|
||||||
|
- 类型:`string`
|
||||||
|
- 取值范围:无特殊约束范围
|
||||||
|
- 无默认值
|
||||||
|
|
||||||
|
- **dataLabelFontBold**:数据标签粗体
|
||||||
|
- 类型:`boolean`
|
||||||
|
|
||||||
|
- **dataLabelFontFace**:数据标签字体
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **dataLabelFontItalic**:数据标签斜体
|
||||||
|
- 类型:`boolean`
|
||||||
|
|
||||||
|
- **dataLabelFontSize**:数据标签字号
|
||||||
|
- 类型:`number`
|
||||||
|
- 单位:pt
|
||||||
|
- 取值范围:无特殊约束范围
|
||||||
|
- 无默认值
|
||||||
|
|
||||||
|
- **dataLabelFormatCode**:数据标签格式代码
|
||||||
|
- 类型:`string`
|
||||||
|
- 示例:`'#%'`、`'0.00%'`、`'$0.00'`
|
||||||
|
|
||||||
|
- **dataLabelFormatScatter**:散点图标签格式
|
||||||
|
- 类型:`'custom' | 'customXY' | 'XY'`
|
||||||
|
|
||||||
|
- **dataLabelPosition**:数据标签位置
|
||||||
|
- 类型:`'b' | 'bestFit' | 'ctr' | 'l' | 'r' | 't' | 'inEnd' | 'outEnd'`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 数据表属性 (IChartPropsDataTable)
|
||||||
|
|
||||||
|
- **dataTableFontSize**:数据表字号
|
||||||
|
- 类型:`number`
|
||||||
|
- 单位:pt
|
||||||
|
- 取值范围:无特殊约束范围
|
||||||
|
- 无默认值
|
||||||
|
|
||||||
|
- **dataTableFormatCode**:数据表格式代码
|
||||||
|
- 类型:`string`
|
||||||
|
- 始于:v3.3.0
|
||||||
|
|
||||||
|
- **showDataTable**:显示数据表
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 默认值:`false`
|
||||||
|
|
||||||
|
- **showDataTableHorzBorder**:显示水平边框
|
||||||
|
- 类型:`boolean`
|
||||||
|
|
||||||
|
- **showDataTableKeys**:显示图例标示
|
||||||
|
- 类型:`boolean`
|
||||||
|
|
||||||
|
- **showDataTableOutline**:显示外框
|
||||||
|
- 类型:`boolean`
|
||||||
|
|
||||||
|
- **showDataTableVertBorder**:显示垂直边框
|
||||||
|
- 类型:`boolean`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 图例属性 (IChartPropsLegend)
|
||||||
|
|
||||||
|
- **legendColor**:图例文字颜色
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **legendFontFace**:图例字体
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **legendFontSize**:图例字号
|
||||||
|
- 类型:`number`
|
||||||
|
|
||||||
|
- **legendPos**:图例位置
|
||||||
|
- 类型:`'b' | 'l' | 'r' | 't' | 'tr'`
|
||||||
|
- 取值范围:`b`(底部)、`l`(左侧)、`r`(右侧)、`t`(顶部)、`tr`(右上)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 标题属性 (IChartPropsTitle)
|
||||||
|
|
||||||
|
继承 `TextBaseProps`。
|
||||||
|
|
||||||
|
- **title**:图表标题文字
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **titleAlign**:标题对齐
|
||||||
|
- 类型:`string`
|
||||||
|
- 取值范围:`'l' | 'ctr' | 'r'`
|
||||||
|
- 默认值:`'ctr'`
|
||||||
|
|
||||||
|
- **titleBold**:标题粗体
|
||||||
|
- 类型:`boolean`
|
||||||
|
|
||||||
|
- **titleColor**:标题颜色
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **titleFontFace**:标题字体
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **titleFontSize**:标题字号
|
||||||
|
- 类型:`number`
|
||||||
|
|
||||||
|
- **titlePos**:标题位置
|
||||||
|
- 类型:`{ x: number, y: number }`
|
||||||
|
|
||||||
|
- **titleRotate**:标题旋转
|
||||||
|
- 类型:`number`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 网格线属性 (OptsChartGridLine)
|
||||||
|
|
||||||
|
- **cap**:线帽类型
|
||||||
|
- 类型:`ChartLineCap`(`'flat' | 'round' | 'square'`)
|
||||||
|
- 默认值:`'flat'`
|
||||||
|
|
||||||
|
- **color**:网格线颜色
|
||||||
|
- 类型:`HexColor`
|
||||||
|
|
||||||
|
- **size**:网格线粗细
|
||||||
|
- 类型:`number`
|
||||||
|
- 单位:pt
|
||||||
|
- 取值范围:无特殊约束范围
|
||||||
|
- 无默认值
|
||||||
|
|
||||||
|
- **style**:网格线样式
|
||||||
|
- 类型:`'solid' | 'dash' | 'dot' | 'none'`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## IChartPropsFillLine
|
||||||
|
|
||||||
|
用于 `chartArea`、`plotArea` 的填充与边框基础类型。
|
||||||
|
|
||||||
|
- **border**:边框
|
||||||
|
- 类型:`BorderProps`
|
||||||
|
|
||||||
|
- **fill**:填充
|
||||||
|
- 类型:`ShapeFillProps`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## IChartOpts 附加属性
|
||||||
|
|
||||||
|
- **altText**:替代文字(无障碍)
|
||||||
|
- 类型:`string`
|
||||||
|
- 说明:PowerPoint 中右击图表 > "编辑替代文字"
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 已废弃参数
|
||||||
|
|
||||||
|
| 已废弃参数 | 替代参数 | 废弃版本 |
|
||||||
|
|---|---|---|
|
||||||
|
| `border` | `plotArea.border` | v3.11.0 |
|
||||||
|
| `fill` | `plotArea.fill` | v3.11.0 |
|
||||||
@@ -0,0 +1,175 @@
|
|||||||
|
# 坐标与尺寸参数 (Coordinate Props)
|
||||||
|
|
||||||
|
坐标与尺寸参数用于指定元素在幻灯片上的位置和大小。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Coord 类型
|
||||||
|
|
||||||
|
```ts
|
||||||
|
type Coord = number | `${number}%`
|
||||||
|
```
|
||||||
|
|
||||||
|
- **number**:英寸值
|
||||||
|
- 说明:以英寸为单位的数值
|
||||||
|
- 示例:`1.5`(1.5 英寸)、`10`(10 英寸)
|
||||||
|
|
||||||
|
- **`${number}%`**:百分比
|
||||||
|
- 说明:相对于幻灯片尺寸的百分比
|
||||||
|
- 示例:`'50%'`(一半宽度/高度)、`'100%'`(全宽/全高)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## PositionProps
|
||||||
|
|
||||||
|
所有可放置元素通用的位置属性。
|
||||||
|
|
||||||
|
- **x**:水平位置
|
||||||
|
- 类型:`Coord`
|
||||||
|
- 单位:英寸或百分比
|
||||||
|
- 示例:`10.25`、`'75%'`
|
||||||
|
|
||||||
|
- **y**:垂直位置
|
||||||
|
- 类型:`Coord`
|
||||||
|
- 单位:英寸或百分比
|
||||||
|
- 示例:`10.25`、`'75%'`
|
||||||
|
|
||||||
|
- **w**:宽度
|
||||||
|
- 类型:`Coord`
|
||||||
|
- 单位:英寸或百分比
|
||||||
|
- 示例:`10.25`、`'75%'`
|
||||||
|
|
||||||
|
- **h**:高度
|
||||||
|
- 类型:`Coord`
|
||||||
|
- 单位:英寸或百分比
|
||||||
|
- 示例:`10.25`、`'75%'`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 标准布局尺寸
|
||||||
|
|
||||||
|
| 布局常量名 | 宽度 | 高度 | 宽高比 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `LAYOUT_4x3` | 10" | 7.5" | 4:3 |
|
||||||
|
| `LAYOUT_16x9` | 10" | 5.625" | 16:9 |
|
||||||
|
| `LAYOUT_16x10` | 10" | 6.25" | 16:10 |
|
||||||
|
| `LAYOUT_WIDE` | 13.33" | 7.5" | ~16:9 |
|
||||||
|
|
||||||
|
可通过 `pptx.defineLayout({ name, width, height })` 自定义布局。
|
||||||
|
|
||||||
|
### px 换算参考
|
||||||
|
|
||||||
|
在 96dpi 屏幕上的参考换算关系:
|
||||||
|
- 1 英寸 = 96px
|
||||||
|
- 1pt = 1/72 英寸 ≈ 1.33px
|
||||||
|
- 1cm ≈ 0.394 英寸
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Margin 类型
|
||||||
|
|
||||||
|
```ts
|
||||||
|
type Margin = number | [number, number, number, number]
|
||||||
|
```
|
||||||
|
|
||||||
|
- **number**:统一边距
|
||||||
|
- 说明:四边使用相同值
|
||||||
|
- 单位:pt
|
||||||
|
- 示例:`0`(无边距)、`10`(四边 10pt)
|
||||||
|
|
||||||
|
- **[top, right, bottom, left]**:四边独立边距
|
||||||
|
- 说明:按上右下左(TRBL)顺序分别设置
|
||||||
|
- 单位:pt
|
||||||
|
- 示例:`[10, 5, 10, 5]`(上下 10pt、左右 5pt)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Color 类型
|
||||||
|
|
||||||
|
```ts
|
||||||
|
type HexColor = string // 6 位十六进制,无 # 前缀
|
||||||
|
type ThemeColor = 'tx1' | 'tx2' | 'bg1' | 'bg2'
|
||||||
|
| 'accent1' | 'accent2' | 'accent3'
|
||||||
|
| 'accent4' | 'accent5' | 'accent6'
|
||||||
|
type Color = HexColor | ThemeColor
|
||||||
|
```
|
||||||
|
|
||||||
|
### 十六进制颜色 (HexColor)
|
||||||
|
|
||||||
|
- 格式:6 位十六进制值,不含 `#` 前缀
|
||||||
|
- 示例:`'FF0000'`(红色)、`'0088CC'`(蓝色)、`'FFFFFF'`(白色)
|
||||||
|
|
||||||
|
### 主题色 (ThemeColor)
|
||||||
|
|
||||||
|
| 值 | 说明 | PowerPoint 名称 |
|
||||||
|
|---|---|---|
|
||||||
|
| `'tx1'` | 文字 1 | Theme Color Text 1 |
|
||||||
|
| `'tx2'` | 文字 2 | Theme Color Text 2 |
|
||||||
|
| `'bg1'` | 背景 1 | Theme Color Background 1 |
|
||||||
|
| `'bg2'` | 背景 2 | Theme Color Background 2 |
|
||||||
|
| `'accent1'` | 强调色 1 | Accent 1 |
|
||||||
|
| `'accent2'` | 强调色 2 | Accent 2 |
|
||||||
|
| `'accent3'` | 强调色 3 | Accent 3 |
|
||||||
|
| `'accent4'` | 强调色 4 | Accent 4 |
|
||||||
|
| `'accent5'` | 强调色 5 | Accent 5 |
|
||||||
|
| `'accent6'` | 强调色 6 | Accent 6 |
|
||||||
|
|
||||||
|
可通过 `pptx.SchemeColor.XXX` 常量引用:
|
||||||
|
- `pptx.SchemeColor.text1` → `'tx1'`
|
||||||
|
- `pptx.SchemeColor.background1` → `'bg1'`
|
||||||
|
- `pptx.SchemeColor.accent1` → `'accent1'`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 对齐类型
|
||||||
|
|
||||||
|
### HAlign
|
||||||
|
|
||||||
|
```ts
|
||||||
|
type HAlign = 'left' | 'center' | 'right' | 'justify'
|
||||||
|
```
|
||||||
|
|
||||||
|
| 值 | 说明 |
|
||||||
|
|---|---|
|
||||||
|
| `'left'` | 左对齐 |
|
||||||
|
| `'center'` | 居中对齐 |
|
||||||
|
| `'right'` | 右对齐 |
|
||||||
|
| `'justify'` | 两端对齐 |
|
||||||
|
|
||||||
|
### VAlign
|
||||||
|
|
||||||
|
```ts
|
||||||
|
type VAlign = 'top' | 'middle' | 'bottom'
|
||||||
|
```
|
||||||
|
|
||||||
|
| 值 | 说明 |
|
||||||
|
|---|---|
|
||||||
|
| `'top'` | 顶部对齐 |
|
||||||
|
| `'middle'` | 垂直居中 |
|
||||||
|
| `'bottom'` | 底部对齐 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 其他常用类型
|
||||||
|
|
||||||
|
### ChartAxisTickMark
|
||||||
|
|
||||||
|
```ts
|
||||||
|
type ChartAxisTickMark = 'none' | 'inside' | 'outside' | 'cross'
|
||||||
|
```
|
||||||
|
|
||||||
|
### ChartLineCap
|
||||||
|
|
||||||
|
```ts
|
||||||
|
type ChartLineCap = 'flat' | 'round' | 'square'
|
||||||
|
```
|
||||||
|
|
||||||
|
### SHAPE_NAME
|
||||||
|
|
||||||
|
形状名称类型,包含约 180 种 PowerPoint 形状的字面量联合类型。
|
||||||
|
|
||||||
|
### CHART_NAME
|
||||||
|
|
||||||
|
```ts
|
||||||
|
type CHART_NAME = 'area' | 'bar' | 'bar3D' | 'bubble' | 'doughnut' | 'line' | 'pie' | 'radar' | 'scatter'
|
||||||
|
```
|
||||||
@@ -0,0 +1,197 @@
|
|||||||
|
# 图片参数 (Image Props)
|
||||||
|
|
||||||
|
图片参数来自 `ImageProps`(继承 `PositionProps`、`DataOrPathProps`、`ObjectNameProps`),用于 `slide.addImage()` 方法。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 位置属性 (PositionProps)
|
||||||
|
|
||||||
|
- **x**:水平位置
|
||||||
|
- 类型:`Coord`(`number | `${number}%``)
|
||||||
|
- 单位:英寸(number)或百分比(`'50%'`)
|
||||||
|
|
||||||
|
- **y**:垂直位置
|
||||||
|
- 类型:`Coord`
|
||||||
|
- 单位:英寸或百分比
|
||||||
|
|
||||||
|
- **w**:宽度
|
||||||
|
- 类型:`Coord`
|
||||||
|
- 单位:英寸或百分比
|
||||||
|
|
||||||
|
- **h**:高度
|
||||||
|
- 类型:`Coord`
|
||||||
|
- 单位:英寸或百分比
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 数据/路径属性 (DataOrPathProps)
|
||||||
|
|
||||||
|
- **path**:图片 URL 或本地路径
|
||||||
|
- 类型:`string`
|
||||||
|
- 示例:`'https://onedrives.com/myimg.png'`、`'/home/gitbrent/images/myimg.png'`
|
||||||
|
|
||||||
|
- **data**:base64 编码数据
|
||||||
|
- 类型:`string`
|
||||||
|
- 格式:`'image/png;base64,iVtDafDrBF[...]='`
|
||||||
|
- 说明:base64 编码的图片数据,用时可避免路径/服务器问题
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 对象名称属性 (ObjectNameProps)
|
||||||
|
|
||||||
|
- **objectName**:对象名称
|
||||||
|
- 类型:`string`
|
||||||
|
- 说明:替代默认的 "Object N" 名称,在 PowerPoint 选择窗格中显示
|
||||||
|
- 默认值:`'Object 1'`
|
||||||
|
- 始于:v3.10.0
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 图片专有属性 (ImageProps)
|
||||||
|
|
||||||
|
- **altText**:替代文字(无障碍)
|
||||||
|
- 类型:`string`
|
||||||
|
- 说明:PowerPoint 中右击图片 > "编辑替代文字"
|
||||||
|
|
||||||
|
- **flipH**:水平翻转
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 取值范围:`true` / `false`
|
||||||
|
- 默认值:`false`
|
||||||
|
|
||||||
|
- **flipV**:垂直翻转
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 取值范围:`true` / `false`
|
||||||
|
- 默认值:`false`
|
||||||
|
|
||||||
|
- **hyperlink**:超链接
|
||||||
|
- 类型:`HyperlinkProps`
|
||||||
|
- 子参数:
|
||||||
|
- **hyperlink.slide**:链接到页码
|
||||||
|
- 类型:`number`
|
||||||
|
- **hyperlink.url**:链接 URL
|
||||||
|
- 类型:`string`
|
||||||
|
- **hyperlink.tooltip**:超链接提示
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **placeholder**:占位符类型
|
||||||
|
- 类型:`string`
|
||||||
|
- 取值范围:`'body'`、`'header'`、`'footer'`、`'title'` 等
|
||||||
|
- 示例:`'body'`
|
||||||
|
|
||||||
|
- **rotate**:旋转角度
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`-360`–`360`(度)
|
||||||
|
- 默认值:`0`
|
||||||
|
- 示例:`180`(旋转 180 度)
|
||||||
|
|
||||||
|
- **rounding**:启用图片圆角
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 默认值:`false`
|
||||||
|
|
||||||
|
- **shadow**:阴影
|
||||||
|
- 类型:`ShadowProps`
|
||||||
|
- 子参数:
|
||||||
|
- **shadow.type**:阴影类型
|
||||||
|
- 类型:`'outer' | 'inner' | 'none'`
|
||||||
|
- 默认值:`'none'`
|
||||||
|
- **shadow.opacity**:不透明度
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`0.0`–`1.0`
|
||||||
|
- **shadow.blur**:模糊
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`0`–`100`(pt)
|
||||||
|
- 默认值:`0`
|
||||||
|
- **shadow.angle**:角度
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`0`–`359`(度)
|
||||||
|
- 默认值:`0`
|
||||||
|
- **shadow.offset**:偏移距离
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`0`–`200`(pt)
|
||||||
|
- 默认值:`0`
|
||||||
|
- **shadow.color**:阴影颜色
|
||||||
|
- 类型:`HexColor`
|
||||||
|
- 格式:6 位十六进制(无 `#`)
|
||||||
|
- **shadow.rotateWithShape**:阴影是否随形状旋转
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 默认值:`false`
|
||||||
|
|
||||||
|
- **sizing**:图片大小调整
|
||||||
|
- 类型:`{ type: 'contain' | 'cover' | 'crop', w: Coord, h: Coord, x?: Coord, y?: Coord }`
|
||||||
|
- 子参数:
|
||||||
|
- **sizing.type**:调整类型
|
||||||
|
- 类型:`'contain' | 'cover' | 'crop'`
|
||||||
|
- 取值范围:
|
||||||
|
- `contain` — 包含(保持比例适配)
|
||||||
|
- `cover` — 覆盖(填充区域)
|
||||||
|
- `crop` — 裁剪
|
||||||
|
- **sizing.w**:图片宽度
|
||||||
|
- 类型:`Coord`(英寸或百分比)
|
||||||
|
- **sizing.h**:图片高度
|
||||||
|
- 类型:`Coord`(英寸或百分比)
|
||||||
|
- **sizing.x**:裁剪左偏移
|
||||||
|
- 类型:`Coord`(仅用于 `crop` 模式)
|
||||||
|
- **sizing.y**:裁剪上偏移
|
||||||
|
- 类型:`Coord`(仅用于 `crop` 模式)
|
||||||
|
|
||||||
|
- **transparency**:透明度
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`0`–`100`(百分比)
|
||||||
|
- 默认值:`0`
|
||||||
|
- 示例:`25`(25% 透明)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ShadowProps
|
||||||
|
|
||||||
|
用于 `shadow` 属性的类型定义(图表、图片、文字通用)。
|
||||||
|
|
||||||
|
- **type**:阴影类型
|
||||||
|
- 类型:`'outer' | 'inner' | 'none'`
|
||||||
|
- 默认值:`'none'`
|
||||||
|
|
||||||
|
- **opacity**:不透明度
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`0.0`–`1.0`
|
||||||
|
- 示例:`0.5`(50% 不透明)
|
||||||
|
|
||||||
|
- **blur**:模糊大小
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`0`–`100`(pt)
|
||||||
|
- 默认值:`0`
|
||||||
|
|
||||||
|
- **angle**:阴影角度
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`0`–`359`(度)
|
||||||
|
- 默认值:`0`
|
||||||
|
|
||||||
|
- **offset**:阴影偏移距离
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`0`–`200`(pt)
|
||||||
|
- 默认值:`0`
|
||||||
|
|
||||||
|
- **color**:阴影颜色
|
||||||
|
- 类型:`HexColor`
|
||||||
|
- 格式:6 位十六进制(无 `#`)
|
||||||
|
|
||||||
|
- **rotateWithShape**:是否随形状旋转
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 默认值:`false`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## HyperlinkProps
|
||||||
|
|
||||||
|
用于 `hyperlink` 属性的类型定义。
|
||||||
|
|
||||||
|
- **slide**:链接到页码
|
||||||
|
- 类型:`number`
|
||||||
|
- 说明:跳转到演示文稿的指定幻灯片
|
||||||
|
|
||||||
|
- **url**:超链接 URL
|
||||||
|
- 类型:`string`
|
||||||
|
- 说明:链接到外部网址
|
||||||
|
|
||||||
|
- **tooltip**:超链接提示
|
||||||
|
- 类型:`string`
|
||||||
|
- 说明:鼠标悬停时显示的文字
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
# 母版参数 (Master Props)
|
||||||
|
|
||||||
|
母版参数来自 `SlideMasterProps`,通过 `pptx.defineSlideMaster()` 定义幻灯片母版(布局)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## SlideMasterProps
|
||||||
|
|
||||||
|
- **title**:母版唯一名称
|
||||||
|
- 类型:`string`
|
||||||
|
- 说明:母版的唯一标识名,`addSlide({ masterName })` 时引用
|
||||||
|
|
||||||
|
- **background**:母版背景
|
||||||
|
- 类型:`BackgroundProps`
|
||||||
|
- 说明:背景颜色或图片,继承 `DataOrPathProps` 和 `ShapeFillProps`
|
||||||
|
- 子参数:
|
||||||
|
- **background.color**:背景颜色
|
||||||
|
- 类型:`Color`
|
||||||
|
- **background.transparency**:透明度
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`0`–`100`
|
||||||
|
- **background.path**:背景图片 URL 或本地路径
|
||||||
|
- 类型:`string`
|
||||||
|
- **background.data**:背景图片 base64 数据
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **margin**:母版边距
|
||||||
|
- 类型:`Margin`(`number | [number, number, number, number]`)
|
||||||
|
- 单位:pt
|
||||||
|
|
||||||
|
- **slideNumber**:幻灯片编号
|
||||||
|
- 类型:`SlideNumberProps`
|
||||||
|
- 说明:继承 `PositionProps` 和 `TextBaseProps`
|
||||||
|
- 子参数:
|
||||||
|
- **slideNumber.margin**:编号边距
|
||||||
|
- 类型:`Margin`
|
||||||
|
|
||||||
|
- **objects**:母版对象数组
|
||||||
|
- 类型:`Array<{ chart: IChartOpts } | { image: ImageProps } | { line: ShapeProps } | { rect: ShapeProps } | { text: TextProps } | { placeholder: { options: PlaceholderProps, text?: string } }>`
|
||||||
|
- 说明:母版上的固定对象,包括图表、图片、线条、矩形、文字和占位符
|
||||||
|
- 占位符子参数:
|
||||||
|
- **placeholder.options**:占位符选项
|
||||||
|
- 类型:`PlaceholderProps`
|
||||||
|
- **placeholder.text**:占位符默认文字
|
||||||
|
- 类型:`string`
|
||||||
|
- 说明:留空则 PowerPoint 显示默认提示文字(如"单击此处添加标题")
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## PlaceholderProps
|
||||||
|
|
||||||
|
继承 `PositionProps` 和 `TextBaseProps`。
|
||||||
|
|
||||||
|
- **name**:占位符名称
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **type**:占位符类型
|
||||||
|
- 类型:`PLACEHOLDER_TYPE`
|
||||||
|
- 取值范围:`'title' | 'body' | 'pic' | 'chart' | 'tbl' | 'media'`
|
||||||
|
|
||||||
|
- **margin**:占位符边距
|
||||||
|
- 类型:`Margin`(`number | [number, number, number, number]`)
|
||||||
|
- 单位:pt
|
||||||
|
|
||||||
|
- **x**:水平位置
|
||||||
|
- 类型:`Coord`
|
||||||
|
|
||||||
|
- **y**:垂直位置
|
||||||
|
- 类型:`Coord`
|
||||||
|
|
||||||
|
- **w**:宽度
|
||||||
|
- 类型:`Coord`
|
||||||
|
|
||||||
|
- **h**:高度
|
||||||
|
- 类型:`Coord`
|
||||||
|
|
||||||
|
### PLACEHOLDER_TYPES 枚举
|
||||||
|
|
||||||
|
| 常量 | 值 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| `PLACEHOLDER_TYPES.title` | `'title'` | 标题 |
|
||||||
|
| `PLACEHOLDER_TYPES.body` | `'body'` | 正文 |
|
||||||
|
| `PLACEHOLDER_TYPES.image` | `'pic'` | 图片 |
|
||||||
|
| `PLACEHOLDER_TYPES.chart` | `'chart'` | 图表 |
|
||||||
|
| `PLACEHOLDER_TYPES.table` | `'tbl'` | 表格 |
|
||||||
|
| `PLACEHOLDER_TYPES.media` | `'media'` | 媒体 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ThemeProps
|
||||||
|
|
||||||
|
用于 `pptx.theme` 属性。
|
||||||
|
|
||||||
|
- **headFontFace**:标题字体
|
||||||
|
- 类型:`string`
|
||||||
|
- 默认值:`'Calibri Light'`
|
||||||
|
- 示例:`'Arial Narrow'`
|
||||||
|
|
||||||
|
- **bodyFontFace**:正文字体
|
||||||
|
- 类型:`string`
|
||||||
|
- 默认值:`'Calibri'`
|
||||||
|
- 示例:`'Arial'`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 使用示例
|
||||||
|
|
||||||
|
```ts
|
||||||
|
pptx.defineSlideMaster({
|
||||||
|
title: 'MyMaster',
|
||||||
|
background: { color: 'FFFFFF' },
|
||||||
|
margin: 0,
|
||||||
|
objects: [
|
||||||
|
{ rect: { x: 0, y: 0, w: 10, h: 0.5, fill: { color: '003366' } } },
|
||||||
|
{ text: { text: '即稿即用', options: { x: 0.5, y: 0.05, w: 9, h: 0.4, color: 'FFFFFF', fontSize: 14 } } },
|
||||||
|
{ placeholder: { options: { name: 'title', type: 'title', x: 0.5, y: 1, w: 9, h: 1 } } },
|
||||||
|
],
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 已废弃参数
|
||||||
|
|
||||||
|
| 已废弃参数 | 替代参数 | 废弃版本 |
|
||||||
|
|---|---|---|
|
||||||
|
| `bkgd` | `background` | v3.3.0 |
|
||||||
@@ -0,0 +1,164 @@
|
|||||||
|
# 输出参数 (Output Props)
|
||||||
|
|
||||||
|
输出参数用于导出演示文稿,支持 `writeFile()`(写入文件)、`write()`(返回数据)和 `stream()`(返回流)三种方式。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 输出方法
|
||||||
|
|
||||||
|
### pptx.writeFile()
|
||||||
|
|
||||||
|
```ts
|
||||||
|
pptx.writeFile(props?: WriteFileProps): Promise<string>
|
||||||
|
```
|
||||||
|
|
||||||
|
- **fileName**:导出文件名
|
||||||
|
- 类型:`string`
|
||||||
|
- 默认值:`'Presentation.pptx'`
|
||||||
|
|
||||||
|
- **compression**:是否启用压缩
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 默认值:`false`
|
||||||
|
- 说明:可显著减小文件体积,但导出耗时略长
|
||||||
|
- 始于:v3.5.0
|
||||||
|
|
||||||
|
### pptx.write()
|
||||||
|
|
||||||
|
```ts
|
||||||
|
pptx.write(props?: WriteProps): Promise<string | ArrayBuffer | Blob | Uint8Array>
|
||||||
|
```
|
||||||
|
|
||||||
|
- **outputType**:输出类型
|
||||||
|
- 类型:`WRITE_OUTPUT_TYPE`
|
||||||
|
- 取值范围:`'arraybuffer' | 'base64' | 'binarystring' | 'blob' | 'nodebuffer' | 'uint8array' | 'STREAM'`
|
||||||
|
- 默认值:`'blob'`
|
||||||
|
|
||||||
|
- **compression**:是否启用压缩
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 默认值:`false`
|
||||||
|
- 始于:v3.5.0
|
||||||
|
|
||||||
|
### pptx.stream()
|
||||||
|
|
||||||
|
```ts
|
||||||
|
pptx.stream(props?: WriteBaseProps): Promise<string | ArrayBuffer | Blob | Uint8Array>
|
||||||
|
```
|
||||||
|
|
||||||
|
- **compression**:是否启用压缩
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 默认值:`false`
|
||||||
|
- 始于:v3.5.0
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## WriteBaseProps
|
||||||
|
|
||||||
|
`writeFile()` 和 `stream()` 的输出基础属性。
|
||||||
|
|
||||||
|
- **compression**:压缩选项
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 默认值:`false`
|
||||||
|
- 始于:v3.5.0
|
||||||
|
- 说明:启用 Zip 压缩以减小文件体积
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## WriteProps
|
||||||
|
|
||||||
|
继承 `WriteBaseProps`,用于 `write()` 方法。
|
||||||
|
|
||||||
|
- **outputType**:输出类型
|
||||||
|
- 类型:`WRITE_OUTPUT_TYPE`
|
||||||
|
- 取值范围:`'arraybuffer' | 'base64' | 'binarystring' | 'blob' | 'nodebuffer' | 'uint8array' | 'STREAM'`
|
||||||
|
- 默认值:`'blob'`
|
||||||
|
|
||||||
|
- **compression**:压缩选项
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 默认值:`false`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## WriteFileProps
|
||||||
|
|
||||||
|
继承 `WriteBaseProps`,用于 `writeFile()` 方法。
|
||||||
|
|
||||||
|
- **fileName**:导出文件名
|
||||||
|
- 类型:`string`
|
||||||
|
- 默认值:`'Presentation.pptx'`
|
||||||
|
|
||||||
|
- **compression**:压缩选项
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 默认值:`false`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## OutputType 枚举
|
||||||
|
|
||||||
|
| 常量 | 值 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| `OutputType.arraybuffer` | `'arraybuffer'` | ArrayBuffer |
|
||||||
|
| `OutputType.base64` | `'base64'` | Base64 字符串 |
|
||||||
|
| `OutputType.binarystring` | `'binarystring'` | 二进制字符串 |
|
||||||
|
| `OutputType.blob` | `'blob'` | Blob 对象(默认) |
|
||||||
|
| `OutputType.nodebuffer` | `'nodebuffer'` | Node.js Buffer |
|
||||||
|
| `OutputType.uint8array` | `'uint8array'` | Uint8Array |
|
||||||
|
|
||||||
|
## JSZIP_OUTPUT_TYPE
|
||||||
|
|
||||||
|
```ts
|
||||||
|
type JSZIP_OUTPUT_TYPE = 'arraybuffer' | 'base64' | 'binarystring' | 'blob' | 'nodebuffer' | 'uint8array'
|
||||||
|
```
|
||||||
|
|
||||||
|
## WRITE_OUTPUT_TYPE
|
||||||
|
|
||||||
|
```ts
|
||||||
|
type WRITE_OUTPUT_TYPE = JSZIP_OUTPUT_TYPE | 'STREAM'
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Presentation 属性
|
||||||
|
|
||||||
|
导出前的演示文稿元数据属性。
|
||||||
|
|
||||||
|
- **author**:作者
|
||||||
|
- 类型:`string`
|
||||||
|
- 说明:演示文稿作者名称
|
||||||
|
|
||||||
|
- **company**:公司
|
||||||
|
- 类型:`string`
|
||||||
|
- 说明:公司名称
|
||||||
|
|
||||||
|
- **layout**:布局名称
|
||||||
|
- 类型:`string`
|
||||||
|
- 说明:当前演示文稿布局
|
||||||
|
- 标准值:`'LAYOUT_4x3'`、`'LAYOUT_16x9'`、`'LAYOUT_16x10'`、`'LAYOUT_WIDE'`
|
||||||
|
|
||||||
|
- **rtlMode**:从右向左模式
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 默认值:`false`
|
||||||
|
|
||||||
|
- **revision**:修订号
|
||||||
|
- 类型:`string`
|
||||||
|
- 说明:必须为整数(不含小数点或逗号),否则 PowerPoint 打开时会报错
|
||||||
|
|
||||||
|
- **subject**:主题
|
||||||
|
- 类型:`string`
|
||||||
|
- 说明:演示文稿主题
|
||||||
|
|
||||||
|
- **theme**:主题字体
|
||||||
|
- 类型:`ThemeProps`
|
||||||
|
- 子参数:
|
||||||
|
- **theme.headFontFace**:标题字体(默认 `'Calibri Light'`)
|
||||||
|
- **theme.bodyFontFace**:正文字体(默认 `'Calibri'`)
|
||||||
|
|
||||||
|
- **title**:演示文稿标题
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **masterSlide**:当前母版
|
||||||
|
- 类型:`PresSlide`
|
||||||
|
- 说明:当前演示文稿的母版幻灯片对象
|
||||||
|
|
||||||
|
- **presLayout**:布局信息
|
||||||
|
- 类型:`PresLayout`
|
||||||
|
- 说明:只读,包含 `name`(布局名称)、`width`(宽度,英寸)、`height`(高度,英寸)
|
||||||
@@ -0,0 +1,440 @@
|
|||||||
|
# 形状参数 (Shape Props)
|
||||||
|
|
||||||
|
形状参数来自 `ShapeProps`(继承 `PositionProps`、`ObjectNameProps`),配合 `ShapeFillProps`、`ShapeLineProps` 使用,用于 `slide.addShape()` 方法。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 位置属性 (PositionProps)
|
||||||
|
|
||||||
|
- **x**:水平位置
|
||||||
|
- 类型:`Coord`(`number | `${number}%``)
|
||||||
|
- 单位:英寸(number)或百分比(`'50%'`)
|
||||||
|
|
||||||
|
- **y**:垂直位置
|
||||||
|
- 类型:`Coord`
|
||||||
|
- 单位:英寸或百分比
|
||||||
|
|
||||||
|
- **w**:宽度
|
||||||
|
- 类型:`Coord`
|
||||||
|
- 单位:英寸或百分比
|
||||||
|
|
||||||
|
- **h**:高度
|
||||||
|
- 类型:`Coord`
|
||||||
|
- 单位:英寸或百分比
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 对象名称属性 (ObjectNameProps)
|
||||||
|
|
||||||
|
- **objectName**:对象名称
|
||||||
|
- 类型:`string`
|
||||||
|
- 说明:替代默认的 "Object N" 名称,在 PowerPoint 选择窗格中显示
|
||||||
|
- 默认值:`'Object 1'`
|
||||||
|
- 始于:v3.10.0
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 形状专有属性 (ShapeProps)
|
||||||
|
|
||||||
|
- **align**:水平对齐方式
|
||||||
|
- 类型:`HAlign`
|
||||||
|
- 取值范围:`'left'`、`'center'`、`'right'`、`'justify'`
|
||||||
|
- 默认值:`'left'`
|
||||||
|
|
||||||
|
- **angleRange**:饼图/弧的角度范围
|
||||||
|
- 类型:`[number, number]`
|
||||||
|
- 说明:仅适用于 `pie`、`arc`、`blockArc` 形状
|
||||||
|
- 取值范围:`[0–359, 0–359]`
|
||||||
|
- 默认值:`[270, 0]`
|
||||||
|
- 始于:v3.4.0
|
||||||
|
|
||||||
|
- **arcThicknessRatio**:弧的厚度比
|
||||||
|
- 类型:`number`
|
||||||
|
- 说明:仅适用于 `blockArc` 形状(需同时设置 `angleRange`)
|
||||||
|
- 取值范围:`0.0`–`1.0`
|
||||||
|
- 默认值:`0.5`
|
||||||
|
- 始于:v3.4.0
|
||||||
|
|
||||||
|
- **fill**:形状填充
|
||||||
|
- 类型:`ShapeFillProps`
|
||||||
|
- 子参数:
|
||||||
|
- **fill.color**:填充颜色
|
||||||
|
- 类型:`Color`(`HexColor | ThemeColor`)
|
||||||
|
- **fill.transparency**:透明度
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`0`–`100`
|
||||||
|
- 默认值:`0`
|
||||||
|
- **fill.type**:填充类型
|
||||||
|
- 类型:`'none' | 'solid'`
|
||||||
|
- 默认值:`'solid'`
|
||||||
|
|
||||||
|
- **flipH**:水平翻转
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 取值范围:`true` / `false`
|
||||||
|
- 默认值:`false`
|
||||||
|
|
||||||
|
- **flipV**:垂直翻转
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 取值范围:`true` / `false`
|
||||||
|
- 默认值:`false`
|
||||||
|
|
||||||
|
- **hyperlink**:超链接
|
||||||
|
- 类型:`HyperlinkProps`
|
||||||
|
- 子参数:
|
||||||
|
- **hyperlink.slide**:链接到页码
|
||||||
|
- 类型:`number`
|
||||||
|
- **hyperlink.url**:链接 URL
|
||||||
|
- 类型:`string`
|
||||||
|
- **hyperlink.tooltip**:超链接提示
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **line**:线条(边框/轮廓线)
|
||||||
|
- 类型:`ShapeLineProps`
|
||||||
|
- 详见下方 ShapeLineProps 参数
|
||||||
|
|
||||||
|
- **points**:自定义形状顶点
|
||||||
|
- 类型:`Array<{ x: Coord, y: Coord } | { x: Coord, y: Coord, curve: ... } | { close: true }>`
|
||||||
|
- 说明:仅适用于自定义几何形状(CUSTOM_GEOMETRY)
|
||||||
|
- 顶点类型:
|
||||||
|
- `{ x, y }` — 直线线段
|
||||||
|
- `{ x, y, moveTo }` — 移动到新位置
|
||||||
|
- `{ x, y, curve: { type: 'arc', hR, wR, stAng, swAng } }` — 弧线
|
||||||
|
- `{ x, y, curve: { type: 'cubic', x1, y1, x2, y2 } }` — 三次贝塞尔曲线
|
||||||
|
- `{ x, y, curve: { type: 'quadratic', x1, y1 } }` — 二次贝塞尔曲线
|
||||||
|
- `{ close: true }` — 闭合路径
|
||||||
|
|
||||||
|
- **rectRadius**:圆角矩形半径
|
||||||
|
- 类型:`number`
|
||||||
|
- 说明:仅适用于 `roundedRect`(ROUNDED_RECTANGLE)形状
|
||||||
|
- 取值范围:`0.0`–`1.0`
|
||||||
|
- 默认值:`0`
|
||||||
|
|
||||||
|
- **rotate**:旋转角度
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`-360`–`360`(度)
|
||||||
|
- 默认值:`0`
|
||||||
|
|
||||||
|
- **shadow**:阴影
|
||||||
|
- 类型:`ShadowProps`
|
||||||
|
- 子参数:
|
||||||
|
- **shadow.type**:阴影类型
|
||||||
|
- 类型:`'outer' | 'inner' | 'none'`
|
||||||
|
- 默认值:`'none'`
|
||||||
|
- **shadow.opacity**:不透明度
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`0.0`–`1.0`
|
||||||
|
- **shadow.blur**:模糊
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`0`–`100`(pt)
|
||||||
|
- 默认值:`0`
|
||||||
|
- **shadow.angle**:角度
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`0`–`359`(度)
|
||||||
|
- 默认值:`0`
|
||||||
|
- **shadow.offset**:偏移距离
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`0`–`200`(pt)
|
||||||
|
- 默认值:`0`
|
||||||
|
- **shadow.color**:阴影颜色
|
||||||
|
- 类型:`HexColor`
|
||||||
|
- 格式:6 位十六进制(无 `#`)
|
||||||
|
- **shadow.rotateWithShape**:阴影是否随形状旋转
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 默认值:`false`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 形状填充 (ShapeFillProps)
|
||||||
|
|
||||||
|
用于 `fill` 属性的类型定义。
|
||||||
|
|
||||||
|
- **color**:填充颜色
|
||||||
|
- 类型:`Color`(`HexColor | ThemeColor`)
|
||||||
|
- 取值范围:6 位十六进制颜色值(不含 `#`)或主题色
|
||||||
|
- 示例:`'FF0000'`、`pptx.SchemeColor.text1`
|
||||||
|
|
||||||
|
- **transparency**:透明度
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`0`–`100`(百分比)
|
||||||
|
- 默认值:`0`
|
||||||
|
|
||||||
|
- **type**:填充类型
|
||||||
|
- 类型:`'none' | 'solid'`
|
||||||
|
- 默认值:`'solid'`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 形状线条 (ShapeLineProps)
|
||||||
|
|
||||||
|
继承 `ShapeFillProps`,用于 `line` 属性。
|
||||||
|
|
||||||
|
- **width**:线条宽度
|
||||||
|
- 类型:`number`
|
||||||
|
- 单位:pt
|
||||||
|
- 默认值:`1`
|
||||||
|
|
||||||
|
- **dashType**:虚线类型
|
||||||
|
- 类型:`'solid' | 'dash' | 'dashDot' | 'lgDash' | 'lgDashDot' | 'lgDashDotDot' | 'sysDash' | 'sysDot'`
|
||||||
|
- 默认值:`'solid'`
|
||||||
|
|
||||||
|
- **beginArrowType**:起点箭头类型
|
||||||
|
- 类型:`'none' | 'arrow' | 'diamond' | 'oval' | 'stealth' | 'triangle'`
|
||||||
|
- 始于:v3.3.0
|
||||||
|
|
||||||
|
- **endArrowType**:终点箭头类型
|
||||||
|
- 类型:`'none' | 'arrow' | 'diamond' | 'oval' | 'stealth' | 'triangle'`
|
||||||
|
- 始于:v3.3.0
|
||||||
|
|
||||||
|
- **color**:线条颜色
|
||||||
|
- 类型:`Color`
|
||||||
|
|
||||||
|
- **transparency**:线条透明度
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`0`–`100`
|
||||||
|
|
||||||
|
- **type**:填充类型
|
||||||
|
- 类型:`'none' | 'solid'`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 形状类型列表
|
||||||
|
|
||||||
|
`SHAPE_NAME` 类型包含以下所有取值(约180个),可通过 `pptxgen.shapes.XXX` 常量引用。
|
||||||
|
|
||||||
|
### 动作按钮 (Action Buttons)
|
||||||
|
|
||||||
|
| 常量名 | 值 |
|
||||||
|
|---|---|
|
||||||
|
| `ACTION_BUTTON_BACK_OR_PREVIOUS` | `actionButtonBackPrevious` |
|
||||||
|
| `ACTION_BUTTON_BEGINNING` | `actionButtonBeginning` |
|
||||||
|
| `ACTION_BUTTON_CUSTOM` | `actionButtonBlank` |
|
||||||
|
| `ACTION_BUTTON_DOCUMENT` | `actionButtonDocument` |
|
||||||
|
| `ACTION_BUTTON_END` | `actionButtonEnd` |
|
||||||
|
| `ACTION_BUTTON_FORWARD_OR_NEXT` | `actionButtonForwardNext` |
|
||||||
|
| `ACTION_BUTTON_HELP` | `actionButtonHelp` |
|
||||||
|
| `ACTION_BUTTON_HOME` | `actionButtonHome` |
|
||||||
|
| `ACTION_BUTTON_INFORMATION` | `actionButtonInformation` |
|
||||||
|
| `ACTION_BUTTON_MOVIE` | `actionButtonMovie` |
|
||||||
|
| `ACTION_BUTTON_RETURN` | `actionButtonReturn` |
|
||||||
|
| `ACTION_BUTTON_SOUND` | `actionButtonSound` |
|
||||||
|
|
||||||
|
### 箭头 (Arrows)
|
||||||
|
|
||||||
|
| 常量名 | 值 |
|
||||||
|
|---|---|
|
||||||
|
| `BENT_ARROW` | `bentArrow` |
|
||||||
|
| `BENT_UP_ARROW` | `bentUpArrow` |
|
||||||
|
| `CHEVRON` | `chevron` |
|
||||||
|
| `CIRCULAR_ARROW` | `circularArrow` |
|
||||||
|
| `CURVED_DOWN_ARROW` | `curvedDownArrow` |
|
||||||
|
| `CURVED_LEFT_ARROW` | `curvedLeftArrow` |
|
||||||
|
| `CURVED_RIGHT_ARROW` | `curvedRightArrow` |
|
||||||
|
| `CURVED_UP_ARROW` | `curvedUpArrow` |
|
||||||
|
| `DOWN_ARROW` | `downArrow` |
|
||||||
|
| `DOWN_ARROW_CALLOUT` | `downArrowCallout` |
|
||||||
|
| `LEFT_ARROW` | `leftArrow` |
|
||||||
|
| `LEFT_ARROW_CALLOUT` | `leftArrowCallout` |
|
||||||
|
| `LEFT_CIRCULAR_ARROW` | `leftCircularArrow` |
|
||||||
|
| `LEFT_RIGHT_ARROW` | `leftRightArrow` |
|
||||||
|
| `LEFT_RIGHT_ARROW_CALLOUT` | `leftRightArrowCallout` |
|
||||||
|
| `LEFT_RIGHT_CIRCULAR_ARROW` | `leftRightCircularArrow` |
|
||||||
|
| `LEFT_RIGHT_UP_ARROW` | `leftRightUpArrow` |
|
||||||
|
| `LEFT_UP_ARROW` | `leftUpArrow` |
|
||||||
|
| `NOTCHED_RIGHT_ARROW` | `notchedRightArrow` |
|
||||||
|
| `QUAD_ARROW` | `quadArrow` |
|
||||||
|
| `QUAD_ARROW_CALLOUT` | `quadArrowCallout` |
|
||||||
|
| `RIGHT_ARROW` | `rightArrow` |
|
||||||
|
| `RIGHT_ARROW_CALLOUT` | `rightArrowCallout` |
|
||||||
|
| `STRIPED_RIGHT_ARROW` | `stripedRightArrow` |
|
||||||
|
| `SWOOSH_ARROW` | `swooshArrow` |
|
||||||
|
| `UP_ARROW` | `upArrow` |
|
||||||
|
| `UP_ARROW_CALLOUT` | `upArrowCallout` |
|
||||||
|
| `UP_DOWN_ARROW` | `upDownArrow` |
|
||||||
|
| `UP_DOWN_ARROW_CALLOUT` | `upDownArrowCallout` |
|
||||||
|
| `U_TURN_ARROW` | `uturnArrow` |
|
||||||
|
|
||||||
|
### 标注框 (Callouts)
|
||||||
|
|
||||||
|
| 常量名 | 值 |
|
||||||
|
|---|---|
|
||||||
|
| `LINE_CALLOUT_1` | `borderCallout1` |
|
||||||
|
| `LINE_CALLOUT_1_ACCENT_BAR` | `accentCallout1` |
|
||||||
|
| `LINE_CALLOUT_1_BORDER_AND_ACCENT_BAR` | `accentBorderCallout1` |
|
||||||
|
| `LINE_CALLOUT_1_NO_BORDER` | `callout1` |
|
||||||
|
| `LINE_CALLOUT_2` | `borderCallout2` |
|
||||||
|
| `LINE_CALLOUT_2_ACCENT_BAR` | `accentCallout2` |
|
||||||
|
| `LINE_CALLOUT_2_BORDER_AND_ACCENT_BAR` | `accentBorderCallout2` |
|
||||||
|
| `LINE_CALLOUT_2_NO_BORDER` | `callout2` |
|
||||||
|
| `LINE_CALLOUT_3` | `borderCallout3` |
|
||||||
|
| `LINE_CALLOUT_3_ACCENT_BAR` | `accentCallout3` |
|
||||||
|
| `LINE_CALLOUT_3_BORDER_AND_ACCENT_BAR` | `accentBorderCallout3` |
|
||||||
|
| `LINE_CALLOUT_3_NO_BORDER` | `callout3` |
|
||||||
|
| `LINE_CALLOUT_4` | `borderCallout4` |
|
||||||
|
| `LINE_CALLOUT_4_ACCENT_BAR` | `accentCallout4` |
|
||||||
|
| `LINE_CALLOUT_4_BORDER_AND_ACCENT_BAR` | `accentBorderCallout4` |
|
||||||
|
| `LINE_CALLOUT_4_NO_BORDER` | `callout4` |
|
||||||
|
| `CLOUD_CALLOUT` | `cloudCallout` |
|
||||||
|
| `OVAL_CALLOUT` | `wedgeEllipseCallout` |
|
||||||
|
| `RECTANGULAR_CALLOUT` | `wedgeRectCallout` |
|
||||||
|
| `ROUNDED_RECTANGULAR_CALLOUT` | `wedgeRoundRectCallout` |
|
||||||
|
|
||||||
|
### 基本形状 (Basic Shapes)
|
||||||
|
|
||||||
|
| 常量名 | 值 |
|
||||||
|
|---|---|
|
||||||
|
| `ARC` | `arc` |
|
||||||
|
| `BALLOON` | `wedgeRoundRectCallout` |
|
||||||
|
| `BEVEL` | `bevel` |
|
||||||
|
| `BLOCK_ARC` | `blockArc` |
|
||||||
|
| `CAN` | `can` |
|
||||||
|
| `CHORD` | `chord` |
|
||||||
|
| `CLOUD` | `cloud` |
|
||||||
|
| `CORNER` | `corner` |
|
||||||
|
| `CROSS` | `plus` |
|
||||||
|
| `CUBE` | `cube` |
|
||||||
|
| `DIAMOND` | `diamond` |
|
||||||
|
| `DONUT` | `donut` |
|
||||||
|
| `DOUBLE_BRACE` | `bracePair` |
|
||||||
|
| `DOUBLE_BRACKET` | `bracketPair` |
|
||||||
|
| `DOUBLE_WAVE` | `doubleWave` |
|
||||||
|
| `FOLDED_CORNER` | `folderCorner` |
|
||||||
|
| `FRAME` | `frame` |
|
||||||
|
| `FUNNEL` | `funnel` |
|
||||||
|
| `GEAR_6` | `gear6` |
|
||||||
|
| `GEAR_9` | `gear9` |
|
||||||
|
| `HALF_FRAME` | `halfFrame` |
|
||||||
|
| `HEART` | `heart` |
|
||||||
|
| `HORIZONTAL_SCROLL` | `horizontalScroll` |
|
||||||
|
| `ISOSCELES_TRIANGLE` | `triangle` |
|
||||||
|
| `LEFT_BRACE` | `leftBrace` |
|
||||||
|
| `LEFT_BRACKET` | `leftBracket` |
|
||||||
|
| `LIGHTNING_BOLT` | `lightningBolt` |
|
||||||
|
| `LINE` | `line` |
|
||||||
|
| `LINE_INVERSE` | `lineInv` |
|
||||||
|
| `MOON` | `moon` |
|
||||||
|
| `NO_SYMBOL` | `noSmoking` |
|
||||||
|
| `OVAL` | `ellipse` |
|
||||||
|
| `PARALLELOGRAM` | `parallelogram` |
|
||||||
|
| `PENTAGON` | `homePlate` |
|
||||||
|
| `PIE` | `pie` |
|
||||||
|
| `PIE_WEDGE` | `pieWedge` |
|
||||||
|
| `PLAQUE` | `plaque` |
|
||||||
|
| `PLAQUE_TABS` | `plaqueTabs` |
|
||||||
|
| `RECTANGLE` | `rect` |
|
||||||
|
| `REGULAR_PENTAGON` | `pentagon` |
|
||||||
|
| `RIGHT_BRACE` | `rightBrace` |
|
||||||
|
| `RIGHT_BRACKET` | `rightBracket` |
|
||||||
|
| `RIGHT_TRIANGLE` | `rtTriangle` |
|
||||||
|
| `ROUNDED_RECTANGLE` | `roundRect` |
|
||||||
|
| `ROUND_1_RECTANGLE` | `round1Rect` |
|
||||||
|
| `ROUND_2_DIAG_RECTANGLE` | `round2DiagRect` |
|
||||||
|
| `ROUND_2_SAME_RECTANGLE` | `round2SameRect` |
|
||||||
|
| `SMILEY_FACE` | `smileyFace` |
|
||||||
|
| `SNIP_1_RECTANGLE` | `snip1Rect` |
|
||||||
|
| `SNIP_2_DIAG_RECTANGLE` | `snip2DiagRect` |
|
||||||
|
| `SNIP_2_SAME_RECTANGLE` | `snip2SameRect` |
|
||||||
|
| `SNIP_ROUND_RECTANGLE` | `snipRoundRect` |
|
||||||
|
| `SUN` | `sun` |
|
||||||
|
| `TEAR` | `teardrop` |
|
||||||
|
| `TRAPEZOID` | `trapezoid` |
|
||||||
|
| `NON_ISOSCELES_TRAPEZOID` | `nonIsoscelesTrapezoid` |
|
||||||
|
| `VERTICAL_SCROLL` | `verticalScroll` |
|
||||||
|
| `WAVE` | `wave` |
|
||||||
|
|
||||||
|
### 流程图 (Flowchart)
|
||||||
|
|
||||||
|
| 常量名 | 值 |
|
||||||
|
|---|---|
|
||||||
|
| `FLOWCHART_ALTERNATE_PROCESS` | `flowChartAlternateProcess` |
|
||||||
|
| `FLOWCHART_CARD` | `flowChartPunchedCard` |
|
||||||
|
| `FLOWCHART_COLLATE` | `flowChartCollate` |
|
||||||
|
| `FLOWCHART_CONNECTOR` | `flowChartConnector` |
|
||||||
|
| `FLOWCHART_DATA` | `flowChartInputOutput` |
|
||||||
|
| `FLOWCHART_DECISION` | `flowChartDecision` |
|
||||||
|
| `FLOWCHART_DELAY` | `flowChartDelay` |
|
||||||
|
| `FLOWCHART_DIRECT_ACCESS_STORAGE` | `flowChartMagneticDrum` |
|
||||||
|
| `FLOWCHART_DISPLAY` | `flowChartDisplay` |
|
||||||
|
| `FLOWCHART_DOCUMENT` | `flowChartDocument` |
|
||||||
|
| `FLOWCHART_EXTRACT` | `flowChartExtract` |
|
||||||
|
| `FLOWCHART_INTERNAL_STORAGE` | `flowChartInternalStorage` |
|
||||||
|
| `FLOWCHART_MAGNETIC_DISK` | `flowChartMagneticDisk` |
|
||||||
|
| `FLOWCHART_MANUAL_INPUT` | `flowChartManualInput` |
|
||||||
|
| `FLOWCHART_MANUAL_OPERATION` | `flowChartManualOperation` |
|
||||||
|
| `FLOWCHART_MERGE` | `flowChartMerge` |
|
||||||
|
| `FLOWCHART_MULTIDOCUMENT` | `flowChartMultidocument` |
|
||||||
|
| `FLOWCHART_OFFLINE_STORAGE` | `flowChartOfflineStorage` |
|
||||||
|
| `FLOWCHART_OFFPAGE_CONNECTOR` | `flowChartOffpageConnector` |
|
||||||
|
| `FLOWCHART_OR` | `flowChartOr` |
|
||||||
|
| `FLOWCHART_PREDEFINED_PROCESS` | `flowChartPredefinedProcess` |
|
||||||
|
| `FLOWCHART_PREPARATION` | `flowChartPreparation` |
|
||||||
|
| `FLOWCHART_PROCESS` | `flowChartProcess` |
|
||||||
|
| `FLOWCHART_PUNCHED_TAPE` | `flowChartPunchedTape` |
|
||||||
|
| `FLOWCHART_SEQUENTIAL_ACCESS_STORAGE` | `flowChartMagneticTape` |
|
||||||
|
| `FLOWCHART_SORT` | `flowChartSort` |
|
||||||
|
| `FLOWCHART_STORED_DATA` | `flowChartOnlineStorage` |
|
||||||
|
| `FLOWCHART_SUMMING_JUNCTION` | `flowChartSummingJunction` |
|
||||||
|
| `FLOWCHART_TERMINATOR` | `flowChartTerminator` |
|
||||||
|
|
||||||
|
### 星形与旗帜 (Stars & Ribbons)
|
||||||
|
|
||||||
|
| 常量名 | 值 |
|
||||||
|
|---|---|
|
||||||
|
| `STAR_4_POINT` | `star4` |
|
||||||
|
| `STAR_5_POINT` | `star5` |
|
||||||
|
| `STAR_6_POINT` | `star6` |
|
||||||
|
| `STAR_7_POINT` | `star7` |
|
||||||
|
| `STAR_8_POINT` | `star8` |
|
||||||
|
| `STAR_10_POINT` | `star10` |
|
||||||
|
| `STAR_12_POINT` | `star12` |
|
||||||
|
| `STAR_16_POINT` | `star16` |
|
||||||
|
| `STAR_24_POINT` | `star24` |
|
||||||
|
| `STAR_32_POINT` | `star32` |
|
||||||
|
| `CURVED_DOWN_RIBBON` | `ellipseRibbon` |
|
||||||
|
| `CURVED_UP_RIBBON` | `ellipseRibbon2` |
|
||||||
|
| `DOWN_RIBBON` | `ribbon` |
|
||||||
|
| `LEFT_RIGHT_RIBBON` | `leftRightRibbon` |
|
||||||
|
| `UP_RIBBON` | `ribbon2` |
|
||||||
|
| `CHART_PLUS` | `chartPlus` |
|
||||||
|
| `CHART_STAR` | `chartStar` |
|
||||||
|
| `CHART_X` | `chartX` |
|
||||||
|
| `CORNER_TABS` | `cornerTabs` |
|
||||||
|
| `SQUARE_TABS` | `squareTabs` |
|
||||||
|
| `EXPLOSION1` | `irregularSeal1` |
|
||||||
|
| `EXPLOSION2` | `irregularSeal2` |
|
||||||
|
|
||||||
|
### 数学符号 (Math)
|
||||||
|
|
||||||
|
| 常量名 | 值 |
|
||||||
|
|---|---|
|
||||||
|
| `MATH_DIVIDE` | `mathDivide` |
|
||||||
|
| `MATH_EQUAL` | `mathEqual` |
|
||||||
|
| `MATH_MINUS` | `mathMinus` |
|
||||||
|
| `MATH_MULTIPLY` | `mathMultiply` |
|
||||||
|
| `MATH_NOT_EQUAL` | `mathNotEqual` |
|
||||||
|
| `MATH_PLUS` | `mathPlus` |
|
||||||
|
|
||||||
|
### 多边形 (Polygons)
|
||||||
|
|
||||||
|
| 常量名 | 值 |
|
||||||
|
|---|---|
|
||||||
|
| `DECAGON` | `decagon` |
|
||||||
|
| `DODECAGON` | `dodecagon` |
|
||||||
|
| `HEPTAGON` | `heptagon` |
|
||||||
|
| `HEXAGON` | `hexagon` |
|
||||||
|
| `OCTAGON` | `octagon` |
|
||||||
|
| `DIAGONAL_STRIPE` | `diagStripe` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 已废弃参数
|
||||||
|
|
||||||
|
| 已废弃参数 | 替代参数 | 废弃版本 |
|
||||||
|
|---|---|---|
|
||||||
|
| `lineSize` | `line.width` | v3.3.0 |
|
||||||
|
| `lineDash` | `line.dashType` | v3.3.0 |
|
||||||
|
| `lineHead` | `line.beginArrowType` | v3.3.0 |
|
||||||
|
| `lineTail` | `line.endArrowType` | v3.3.0 |
|
||||||
|
| `shapeName` | `objectName` | v3.10.0 |
|
||||||
|
| `pt`(ShapeLineProps) | `width` | v3.3.0 |
|
||||||
|
| `size`(ShapeLineProps) | `width` | v3.3.0 |
|
||||||
|
| `alpha`(ShapeFillProps) | `transparency` | v3.3.0 |
|
||||||
@@ -0,0 +1,223 @@
|
|||||||
|
# 幻灯片参数 (Slide Props)
|
||||||
|
|
||||||
|
幻灯片相关类型包括 `Slide` 类、`PresSlide` 接口和 `BackgroundProps`,通过 `pptx.addSlide()` 创建。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 添加幻灯片 (addSlide)
|
||||||
|
|
||||||
|
```ts
|
||||||
|
pptx.addSlide(props?: AddSlideProps): PptxGenJS.Slide
|
||||||
|
```
|
||||||
|
|
||||||
|
### AddSlideProps
|
||||||
|
|
||||||
|
- **masterName**:母版名称
|
||||||
|
- 类型:`string`
|
||||||
|
- 说明:使用已定义的 Slide Master 名称
|
||||||
|
|
||||||
|
- **sectionTitle**:节标题
|
||||||
|
- 类型:`string`
|
||||||
|
- 说明:将幻灯片添加到指定节
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Slide 类属性
|
||||||
|
|
||||||
|
- **background**:幻灯片背景
|
||||||
|
- 类型:`BackgroundProps`
|
||||||
|
- 说明:背景颜色或图片
|
||||||
|
|
||||||
|
- **color**:默认文字颜色
|
||||||
|
- 类型:`HexColor`
|
||||||
|
- 格式:6 位十六进制(无 `#`)
|
||||||
|
- 默认值:`'000000'`
|
||||||
|
|
||||||
|
- **hidden**:幻灯片是否隐藏
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 默认值:`false`
|
||||||
|
|
||||||
|
- **slideNumber**:幻灯片编号选项
|
||||||
|
- 类型:`SlideNumberProps`
|
||||||
|
|
||||||
|
- **newAutoPagedSlides**:自动分页创建的新幻灯片
|
||||||
|
- 类型:`PresSlide[]`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Slide 类方法
|
||||||
|
|
||||||
|
- **addChart(type, data, options?)**
|
||||||
|
- 说明:添加图表
|
||||||
|
- 参数:
|
||||||
|
- `type`:`CHART_NAME | IChartMulti[]`
|
||||||
|
- `data`:`any[]`
|
||||||
|
- `options?`:`IChartOpts`
|
||||||
|
- 返回:`Slide`
|
||||||
|
|
||||||
|
- **addImage(options)**
|
||||||
|
- 说明:添加图片
|
||||||
|
- 参数:`options` — `ImageProps`
|
||||||
|
- 返回:`Slide`
|
||||||
|
|
||||||
|
- **addMedia(options)**
|
||||||
|
- 说明:添加媒体(音频/视频)
|
||||||
|
- 参数:`options` — `MediaProps`
|
||||||
|
- 返回:`Slide`
|
||||||
|
|
||||||
|
- **addNotes(notes)**
|
||||||
|
- 说明:添加演讲者备注
|
||||||
|
- 参数:`notes` — `string`
|
||||||
|
- 返回:`Slide`
|
||||||
|
|
||||||
|
- **addShape(shapeName, options?)**
|
||||||
|
- 说明:添加形状
|
||||||
|
- 参数:
|
||||||
|
- `shapeName`:`SHAPE_NAME`
|
||||||
|
- `options?`:`ShapeProps`
|
||||||
|
- 返回:`Slide`
|
||||||
|
|
||||||
|
- **addTable(tableRows, options?)**
|
||||||
|
- 说明:添加表格
|
||||||
|
- 参数:
|
||||||
|
- `tableRows`:`TableRow[]`
|
||||||
|
- `options?`:`TableProps`
|
||||||
|
- 返回:`Slide`
|
||||||
|
|
||||||
|
- **addText(text, options?)**
|
||||||
|
- 说明:添加文字
|
||||||
|
- 参数:
|
||||||
|
- `text`:`string | TextProps[]`
|
||||||
|
- `options?`:`TextPropsOptions`
|
||||||
|
- 返回:`Slide`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## PresSlide 接口
|
||||||
|
|
||||||
|
`PresSlide` 为内部幻灯片表示,包含以下属性和方法:
|
||||||
|
|
||||||
|
- **addChart**:添加图表
|
||||||
|
- 类型:`Function`
|
||||||
|
|
||||||
|
- **addImage**:添加图片
|
||||||
|
- 类型:`Function`
|
||||||
|
|
||||||
|
- **addMedia**:添加媒体
|
||||||
|
- 类型:`Function`
|
||||||
|
|
||||||
|
- **addNotes**:添加备注
|
||||||
|
- 类型:`Function`
|
||||||
|
|
||||||
|
- **addShape**:添加形状
|
||||||
|
- 类型:`Function`
|
||||||
|
|
||||||
|
- **addTable**:添加表格
|
||||||
|
- 类型:`Function`
|
||||||
|
|
||||||
|
- **addText**:添加文字
|
||||||
|
- 类型:`Function`
|
||||||
|
|
||||||
|
- **background**:背景
|
||||||
|
- 类型:`BackgroundProps`
|
||||||
|
|
||||||
|
- **color**:默认文字颜色
|
||||||
|
- 类型:`HexColor`
|
||||||
|
|
||||||
|
- **hidden**:是否隐藏
|
||||||
|
- 类型:`boolean`
|
||||||
|
|
||||||
|
- **slideNumber**:幻灯片编号
|
||||||
|
- 类型:`SlideNumberProps`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## BackgroundProps
|
||||||
|
|
||||||
|
继承 `DataOrPathProps`(`path`、`data`)和 `ShapeFillProps`(`color`、`transparency`、`type`)。
|
||||||
|
|
||||||
|
- **path**:背景图片 URL 或本地路径
|
||||||
|
- 类型:`string`
|
||||||
|
- 示例:`'https://onedrives.com/myimg.png'`
|
||||||
|
|
||||||
|
- **data**:背景图片 base64 数据
|
||||||
|
- 类型:`string`
|
||||||
|
- 示例:`'image/png;base64,iVtDafDrBF[...]='`
|
||||||
|
|
||||||
|
- **color**:背景颜色
|
||||||
|
- 类型:`Color`(来自 ShapeFillProps)
|
||||||
|
- 格式:6 位十六进制(无 `#`)或主题色
|
||||||
|
|
||||||
|
- **transparency**:背景透明度
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`0`–`100`
|
||||||
|
- 默认值:`0`
|
||||||
|
|
||||||
|
- **type**:填充类型
|
||||||
|
- 类型:`'none' | 'solid'`
|
||||||
|
- 默认值:`'solid'`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## SlideNumberProps
|
||||||
|
|
||||||
|
继承 `PositionProps` 和 `TextBaseProps`。
|
||||||
|
|
||||||
|
- **margin**:边距
|
||||||
|
- 类型:`Margin`(`number | [number, number, number, number]`)
|
||||||
|
- 单位:pt
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 幻灯片布局 (PresLayout)
|
||||||
|
|
||||||
|
```ts
|
||||||
|
pptx.defineLayout(layout: PresLayout)
|
||||||
|
```
|
||||||
|
|
||||||
|
- **name**:布局名称
|
||||||
|
- 类型:`string`
|
||||||
|
- 示例:`'LAYOUT_WIDE'`
|
||||||
|
|
||||||
|
- **width**:宽度
|
||||||
|
- 类型:`number`
|
||||||
|
- 单位:英寸
|
||||||
|
|
||||||
|
- **height**:高度
|
||||||
|
- 类型:`number`
|
||||||
|
- 单位:英寸
|
||||||
|
|
||||||
|
### 标准布局尺寸
|
||||||
|
|
||||||
|
| 常量名 | 宽度 | 高度 |
|
||||||
|
|---|---|---|
|
||||||
|
| `LAYOUT_4x3` | 10" | 7.5" |
|
||||||
|
| `LAYOUT_16x9` | 10" | 5.625" |
|
||||||
|
| `LAYOUT_16x10` | 10" | 6.25" |
|
||||||
|
| `LAYOUT_WIDE` | 13.33" | 7.5" |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 节属性 (SectionProps)
|
||||||
|
|
||||||
|
```ts
|
||||||
|
pptx.addSection(props: SectionProps)
|
||||||
|
```
|
||||||
|
|
||||||
|
- **title**:节标题
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **order**:节顺序
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`1`–`n`
|
||||||
|
- 说明:在指定索引添加节
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 已废弃参数 (BackgroundProps)
|
||||||
|
|
||||||
|
| 已废弃参数 | 替代参数 | 废弃版本 |
|
||||||
|
|---|---|---|
|
||||||
|
| `fill` | `ShapeFillProps.color` | v3.6.0 |
|
||||||
|
| `src` | `DataOrPathProps.path` | v3.6.0 |
|
||||||
|
| `bkgd` (Slide 类) | `background` | v3.3.0 |
|
||||||
@@ -0,0 +1,203 @@
|
|||||||
|
# 表格参数 (Table Props)
|
||||||
|
|
||||||
|
表格参数来自 `TableProps`(继承 `TextBaseProps`、`PositionProps`)和 `TableCellProps`(继承 `TextBaseProps`),用于 `slide.addTable()` 方法。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 位置属性 (PositionProps)
|
||||||
|
|
||||||
|
- **x**:水平位置
|
||||||
|
- 类型:`Coord`(`number | `${number}%``)
|
||||||
|
- 单位:英寸(number)或百分比(`'50%'`)
|
||||||
|
|
||||||
|
- **y**:垂直位置
|
||||||
|
- 类型:`Coord`
|
||||||
|
- 单位:英寸或百分比
|
||||||
|
|
||||||
|
- **w**:宽度
|
||||||
|
- 类型:`Coord`
|
||||||
|
- 单位:英寸或百分比
|
||||||
|
|
||||||
|
- **h**:高度
|
||||||
|
- 类型:`Coord`
|
||||||
|
- 单位:英寸或百分比
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 表格专有属性 (TableProps)
|
||||||
|
|
||||||
|
- **autoPage**:是否启用自动分页
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 说明:内容溢出时自动创建新幻灯片
|
||||||
|
- 默认值:`false`
|
||||||
|
|
||||||
|
- **autoPageCharWeight**:自动分页字符权重
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`-1.0`–`1.0`
|
||||||
|
- 默认值:`0.0`
|
||||||
|
- 说明:调整每行字符数,正值使行更长
|
||||||
|
|
||||||
|
- **autoPageLineWeight**:自动分页行权重
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`-1.0`–`1.0`
|
||||||
|
- 默认值:`0.0`
|
||||||
|
- 说明:调整每页行数,正值使表格更高
|
||||||
|
|
||||||
|
- **autoPageRepeatHeader**:自动分页时重复表头
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 默认值:`false`
|
||||||
|
- 始于:v3.3.0
|
||||||
|
|
||||||
|
- **autoPageHeaderRows**:表头行数
|
||||||
|
- 类型:`number`
|
||||||
|
- 说明:`autoPageRepeatHeader` 为 `true` 时需设置
|
||||||
|
- 默认值:`1`
|
||||||
|
- 示例:`2`(在前两行在新页上重复)
|
||||||
|
- 始于:v3.3.0
|
||||||
|
|
||||||
|
- **autoPageSlideStartY**:自动分页新幻灯片的起始 Y 位置
|
||||||
|
- 类型:`number`
|
||||||
|
- 默认值:幻灯片上边距
|
||||||
|
|
||||||
|
- **border**:表格边框
|
||||||
|
- 类型:`BorderProps | [BorderProps, BorderProps, BorderProps, BorderProps]`
|
||||||
|
- 取值范围:单个值应用于所有四边;数组按上右下左(TRBL)顺序应用于各边
|
||||||
|
|
||||||
|
- **colW**:列宽
|
||||||
|
- 类型:`number | number[]`
|
||||||
|
- 单位:英寸
|
||||||
|
- 说明:单个值均匀分配;数组按列依次应用
|
||||||
|
- 默认值:基于 `w` 的等宽列
|
||||||
|
|
||||||
|
- **fill**:单元格填充
|
||||||
|
- 类型:`ShapeFillProps`
|
||||||
|
- 子参数:
|
||||||
|
- **fill.color**:填充颜色
|
||||||
|
- 类型:`Color`
|
||||||
|
- **fill.transparency**:透明度
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`0`–`100`
|
||||||
|
- 默认值:`0`
|
||||||
|
- **fill.type**:填充类型
|
||||||
|
- 类型:`'none' | 'solid'`
|
||||||
|
- 默认值:`'solid'`
|
||||||
|
|
||||||
|
- **margin**:单元格边距
|
||||||
|
- 类型:`Margin`(`number | [number, number, number, number]`)
|
||||||
|
- 单位:pt
|
||||||
|
- 说明:影响所有单元格,可被单元格级选项覆盖
|
||||||
|
- 默认值:PowerPoint 默认边距
|
||||||
|
|
||||||
|
- **objectName**:对象名称
|
||||||
|
- 类型:`string`
|
||||||
|
- 说明:替代默认的 "Object N" 名称
|
||||||
|
- 默认值:`'Object 1'`
|
||||||
|
|
||||||
|
- **rowH**:行高
|
||||||
|
- 类型:`number | number[]`
|
||||||
|
- 单位:英寸
|
||||||
|
- 说明:单个值均匀分配;数组按行依次应用
|
||||||
|
- 默认值:基于 `h` 的等高行
|
||||||
|
|
||||||
|
- **verbose**:详细模式(开发工具)
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 说明:在控制台输出自动分页计算的详细信息
|
||||||
|
- 默认值:`false`
|
||||||
|
|
||||||
|
- **align**:水平对齐
|
||||||
|
- 类型:`'left' | 'center' | 'right' | 'justify'`
|
||||||
|
- 默认值:`'left'`
|
||||||
|
|
||||||
|
- **valign**:垂直对齐
|
||||||
|
- 类型:`'top' | 'middle' | 'bottom'`
|
||||||
|
- 默认值:`'top'`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 表格行与单元格
|
||||||
|
|
||||||
|
- **TableCell**:单个单元格
|
||||||
|
- 类型:`{ text?: string | TableCell[], options?: TableCellProps }`
|
||||||
|
- 说明:`text` 可为字符串或嵌套表格;`options` 为单元格样式
|
||||||
|
|
||||||
|
- **TableRow**:表格行
|
||||||
|
- 类型:`TableCell[]`
|
||||||
|
- 说明:每行由单元格数组组成
|
||||||
|
|
||||||
|
- **TableRowSlide**:自动分页产出
|
||||||
|
- 类型:`{ rows: TableRow[] }`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 表格单元格属性 (TableCellProps)
|
||||||
|
|
||||||
|
继承 `TextBaseProps`。
|
||||||
|
|
||||||
|
- **autoPageCharWeight**:自动分页字符权重
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`-1.0`–`1.0`
|
||||||
|
- 默认值:`0.0`
|
||||||
|
|
||||||
|
- **autoPageLineWeight**:自动分页行权重
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`-1.0`–`1.0`
|
||||||
|
- 默认值:`0.0`
|
||||||
|
|
||||||
|
- **border**:单元格边框
|
||||||
|
- 类型:`BorderProps | [BorderProps, BorderProps, BorderProps, BorderProps]`
|
||||||
|
- 说明:单个值应用于所有四边;数组按上右下左(TRBL)顺序
|
||||||
|
|
||||||
|
- **colspan**:列合并
|
||||||
|
- 类型:`number`
|
||||||
|
- 说明:跨列数
|
||||||
|
|
||||||
|
- **fill**:单元格填充
|
||||||
|
- 类型:`ShapeFillProps`
|
||||||
|
|
||||||
|
- **hyperlink**:单元格超链接
|
||||||
|
- 类型:`HyperlinkProps`
|
||||||
|
- 子参数:
|
||||||
|
- **hyperlink.slide**:链接到页码
|
||||||
|
- 类型:`number`
|
||||||
|
- **hyperlink.url**:链接 URL
|
||||||
|
- 类型:`string`
|
||||||
|
- **hyperlink.tooltip**:超链接提示
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **margin**:单元格边距
|
||||||
|
- 类型:`Margin`
|
||||||
|
- 单位:pt
|
||||||
|
- 默认值:`0`
|
||||||
|
|
||||||
|
- **rowspan**:行合并
|
||||||
|
- 类型:`number`
|
||||||
|
- 说明:跨行数
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## BorderProps
|
||||||
|
|
||||||
|
用于 `border` 属性的类型定义。
|
||||||
|
|
||||||
|
- **type**:边框类型
|
||||||
|
- 类型:`'none' | 'dash' | 'solid'`
|
||||||
|
- 默认值:`'solid'`
|
||||||
|
|
||||||
|
- **color**:边框颜色
|
||||||
|
- 类型:`HexColor`
|
||||||
|
- 格式:6 位十六进制(无 `#`)
|
||||||
|
- 默认值:`'666666'`
|
||||||
|
|
||||||
|
- **pt**:边框粗细
|
||||||
|
- 类型:`number`
|
||||||
|
- 单位:pt
|
||||||
|
- 默认值:`1`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 已废弃参数 (TableProps)
|
||||||
|
|
||||||
|
| 已废弃参数 | 替代参数 | 废弃版本 |
|
||||||
|
|---|---|---|
|
||||||
|
| `addHeaderToEach` | `autoPageRepeatHeader` | v3.3.0 |
|
||||||
|
| `newSlideStartY` | `autoPageSlideStartY` | v3.3.0 |
|
||||||
@@ -0,0 +1,405 @@
|
|||||||
|
# 文字参数 (Text Props)
|
||||||
|
|
||||||
|
文字参数来自 `TextPropsOptions`(继承 `TextBaseProps`、`PositionProps`、`DataOrPathProps`、`ObjectNameProps`)和 `TextGlowProps`,用于 `slide.addText()` 方法。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 基础文字属性 (TextBaseProps)
|
||||||
|
|
||||||
|
- **align**:水平对齐方式
|
||||||
|
- 类型:`'left' | 'center' | 'right' | 'justify'`
|
||||||
|
- 取值范围:`left`、`center`、`right`、`justify`
|
||||||
|
- 默认值:`'left'`
|
||||||
|
|
||||||
|
- **bold**:粗体样式
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 取值范围:`true` / `false`
|
||||||
|
- 默认值:`false`
|
||||||
|
|
||||||
|
- **breakLine**:添加换行符
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 取值范围:`true` / `false`
|
||||||
|
- 默认值:`false`
|
||||||
|
|
||||||
|
- **bullet**:添加标准或自定义项目符号
|
||||||
|
- 类型:`boolean | BulletOptions`
|
||||||
|
- 取值范围:
|
||||||
|
- `true` — 使用标准项目符号
|
||||||
|
- `false` — 无项目符号
|
||||||
|
- 对象 — 自定义项目符号配置
|
||||||
|
- 默认值:`false`
|
||||||
|
- 子参数:
|
||||||
|
- **bullet.type**:项目符号类型
|
||||||
|
- 类型:`'bullet' | 'number'`
|
||||||
|
- 取值范围:`bullet`、`number`
|
||||||
|
- 默认值:`'bullet'`
|
||||||
|
- **bullet.characterCode**:项目符号字符代码(Unicode)
|
||||||
|
- 类型:`string`
|
||||||
|
- 示例:`'25BA'`(U+25BA 黑色右指向指针)
|
||||||
|
- **bullet.indent**:缩进量(项目符号与文字之间的距离,单位 pt)
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:任意正整数
|
||||||
|
- 默认值:`27`
|
||||||
|
- **bullet.numberType**:编号类型
|
||||||
|
- 类型:`string`
|
||||||
|
- 取值范围:
|
||||||
|
`alphaLcParenBoth` | `alphaLcParenR` | `alphaLcPeriod` | `alphaUcParenBoth` | `alphaUcParenR` |
|
||||||
|
`alphaUcPeriod` | `arabicParenBoth` | `arabicParenR` | `arabicPeriod` | `arabicPlain` |
|
||||||
|
`romanLcParenBoth` | `romanLcParenR` | `romanLcPeriod` | `romanUcParenBoth` | `romanUcParenR` |
|
||||||
|
`romanUcPeriod`
|
||||||
|
- **bullet.numberStartAt**:编号起始值
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:任意正整数
|
||||||
|
- 默认值:`1`
|
||||||
|
|
||||||
|
- **color**:文字颜色
|
||||||
|
- 类型:`Color`(`HexColor | ThemeColor`)
|
||||||
|
- 取值范围:6 位十六进制颜色值(不含 `#`)或主题色(`tx1`、`tx2`、`bg1`、`bg2`、`accent1`–`accent6`)
|
||||||
|
- 示例:`'FF0000'`、`pptx.SchemeColor.text1`
|
||||||
|
|
||||||
|
- **fontFace**:字体名称
|
||||||
|
- 类型:`string`
|
||||||
|
- 示例:`'Arial'`、`'微软雅黑'`
|
||||||
|
|
||||||
|
- **fontSize**:字号
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`1`–`400`(pt)
|
||||||
|
- 示例:`12`、`24`
|
||||||
|
|
||||||
|
- **highlight**:文字高亮颜色
|
||||||
|
- 类型:`HexColor`
|
||||||
|
- 格式:6 位十六进制颜色值(不含 `#`)
|
||||||
|
- 示例:`'FFFF00'`(黄色)
|
||||||
|
|
||||||
|
- **italic**:斜体样式
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 取值范围:`true` / `false`
|
||||||
|
- 默认值:`false`
|
||||||
|
|
||||||
|
- **lang**:语言
|
||||||
|
- 类型:`string`
|
||||||
|
- 取值范围:ISO 639-1 标准语言代码
|
||||||
|
- 默认值:`'en-US'`
|
||||||
|
- 示例:`'fr-CA'`
|
||||||
|
|
||||||
|
- **softBreakBefore**:在文字内容前添加软换行(Shift+Enter)
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 取值范围:`true` / `false`
|
||||||
|
- 默认值:`false`
|
||||||
|
- 始于:v3.5.0
|
||||||
|
|
||||||
|
- **tabStops**:制表位
|
||||||
|
- 类型:`Array<{ position: number, alignment?: 'l' | 'r' | 'ctr' | 'dec' }>`
|
||||||
|
- 说明:PowerPoint 段落 > 制表位 > 制表位位置
|
||||||
|
- 示例:`[{ position: 1 }, { position: 3 }]`
|
||||||
|
- 子参数:
|
||||||
|
- **position**:制表位位置(英寸)
|
||||||
|
- **alignment**:对齐方式(可选),`'l'`(左)、`'r'`(右)、`'ctr'`(居中)、`'dec'`(小数点)
|
||||||
|
|
||||||
|
- **textDirection**:文字方向
|
||||||
|
- 类型:`'horz' | 'vert' | 'vert270' | 'wordArtVert'`
|
||||||
|
- 取值范围:
|
||||||
|
- `horz` — 水平
|
||||||
|
- `vert` — 旋转 90 度
|
||||||
|
- `vert270` — 旋转 270 度
|
||||||
|
- `wordArtVert` — 堆叠
|
||||||
|
- 默认值:`'horz'`
|
||||||
|
|
||||||
|
- **transparency**:透明度
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`0`–`100`(百分比)
|
||||||
|
- 默认值:`0`
|
||||||
|
|
||||||
|
- **underline**:下划线属性
|
||||||
|
- 类型:`{ style?: string, color?: Color }`
|
||||||
|
- 子参数:
|
||||||
|
- **underline.style**:下划线样式
|
||||||
|
- 类型:`string`
|
||||||
|
- 取值范围:
|
||||||
|
`'dash'` | `'dashHeavy'` | `'dashLong'` | `'dashLongHeavy'` | `'dbl'` | `'dotDash'` |
|
||||||
|
`'dotDashHeave'` | `'dotDotDash'` | `'dotDotDashHeavy'` | `'dotted'` | `'dottedHeavy'` |
|
||||||
|
`'heavy'` | `'none'` | `'sng'` | `'wavy'` | `'wavyDbl'` | `'wavyHeavy'`
|
||||||
|
- **underline.color**:下划线颜色
|
||||||
|
- 类型:`Color`
|
||||||
|
|
||||||
|
- **valign**:垂直对齐
|
||||||
|
- 类型:`'top' | 'middle' | 'bottom'`
|
||||||
|
- 取值范围:`top`、`middle`、`bottom`
|
||||||
|
- 默认值:`'top'`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 文字专有属性 (TextPropsOptions 特有)
|
||||||
|
|
||||||
|
继承 `DataOrPathProps`,支持以下嵌入图片的属性:
|
||||||
|
|
||||||
|
- **path**:图片路径
|
||||||
|
- 类型:`string`
|
||||||
|
- 说明:URL 或本地路径,可在文本框中嵌入图片
|
||||||
|
|
||||||
|
- **data**:图片 Base64 数据
|
||||||
|
- 类型:`string`
|
||||||
|
- 说明:Base64 编码的图片数据,替代 `path`
|
||||||
|
|
||||||
|
- **baseline**:基线偏移
|
||||||
|
- 类型:`number`
|
||||||
|
|
||||||
|
- **charSpacing**:字符间距
|
||||||
|
- 类型:`number`
|
||||||
|
|
||||||
|
- **fit**:文本适配方式
|
||||||
|
- 类型:`'none' | 'shrink' | 'resize'`
|
||||||
|
- 取值范围:
|
||||||
|
- `none` — 不自动调整
|
||||||
|
- `shrink` — 溢出时缩小文字
|
||||||
|
- `resize` — 调整形状以适配文字
|
||||||
|
- 默认值:`'none'`
|
||||||
|
- 始于:v3.3.0
|
||||||
|
|
||||||
|
- **fill**:形状填充
|
||||||
|
- 类型:`ShapeFillProps`
|
||||||
|
- 子参数:
|
||||||
|
- **fill.color**:填充颜色
|
||||||
|
- 类型:`Color`
|
||||||
|
- 格式:`HexColor`(6 位十六进制无 `#`)或 `ThemeColor`
|
||||||
|
- **fill.transparency**:透明度
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`0`–`100`
|
||||||
|
- 默认值:`0`
|
||||||
|
- **fill.type**:填充类型
|
||||||
|
- 类型:`'none' | 'solid'`
|
||||||
|
- 默认值:`'solid'`
|
||||||
|
|
||||||
|
- **flipH**:水平翻转
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 取值范围:`true` / `false`
|
||||||
|
- 默认值:`false`
|
||||||
|
|
||||||
|
- **flipV**:垂直翻转
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 取值范围:`true` / `false`
|
||||||
|
- 默认值:`false`
|
||||||
|
|
||||||
|
- **glow**:发光效果
|
||||||
|
- 类型:`TextGlowProps`
|
||||||
|
- 子参数:
|
||||||
|
- **glow.color**:发光颜色
|
||||||
|
- 类型:`HexColor`
|
||||||
|
- 格式:6 位十六进制(无 `#`)
|
||||||
|
- **glow.opacity**:不透明度
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`0.0`–`1.0`
|
||||||
|
- **glow.size**:发光大小
|
||||||
|
- 类型:`number`
|
||||||
|
- 单位:pt
|
||||||
|
|
||||||
|
- **hyperlink**:超链接
|
||||||
|
- 类型:`HyperlinkProps`
|
||||||
|
- 子参数:
|
||||||
|
- **hyperlink.slide**:链接到页码
|
||||||
|
- 类型:`number`
|
||||||
|
- **hyperlink.url**:链接 URL
|
||||||
|
- 类型:`string`
|
||||||
|
- **hyperlink.tooltip**:超链接提示
|
||||||
|
- 类型:`string`
|
||||||
|
|
||||||
|
- **indentLevel**:缩进层级
|
||||||
|
- 类型:`number`
|
||||||
|
|
||||||
|
- **isTextBox**:是否为文本框
|
||||||
|
- 类型:`boolean`
|
||||||
|
|
||||||
|
- **line**:线条(边框/轮廓线)
|
||||||
|
- 类型:`ShapeLineProps`
|
||||||
|
- 子参数(继承 `ShapeFillProps`):
|
||||||
|
- **line.color**:线条颜色
|
||||||
|
- 类型:`Color`
|
||||||
|
- **line.transparency**:线条透明度
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`0`–`100`
|
||||||
|
- **line.type**:填充类型
|
||||||
|
- 类型:`'none' | 'solid'`
|
||||||
|
- **line.width**:线条宽度
|
||||||
|
- 类型:`number`
|
||||||
|
- 单位:pt
|
||||||
|
- 默认值:`1`
|
||||||
|
- **line.dashType**:虚线类型
|
||||||
|
- 类型:`string`
|
||||||
|
- 取值范围:`'solid' | 'dash' | 'dashDot' | 'lgDash' | 'lgDashDot' | 'lgDashDotDot' | 'sysDash' | 'sysDot'`
|
||||||
|
- 默认值:`'solid'`
|
||||||
|
- **line.beginArrowType**:起点箭头类型
|
||||||
|
- 类型:`'none' | 'arrow' | 'diamond' | 'oval' | 'stealth' | 'triangle'`
|
||||||
|
- **line.endArrowType**:终点箭头类型
|
||||||
|
- 类型:`'none' | 'arrow' | 'diamond' | 'oval' | 'stealth' | 'triangle'`
|
||||||
|
|
||||||
|
- **lineSpacing**:行间距(固定值)
|
||||||
|
- 类型:`number`
|
||||||
|
- 单位:pt
|
||||||
|
- 说明:PowerPoint 段落 > 缩进和间距 > 行距 > "固定值"
|
||||||
|
- 示例:`28`
|
||||||
|
|
||||||
|
- **lineSpacingMultiple**:行间距(倍数)
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`0.0`–`9.99`
|
||||||
|
- 单位:倍数(非百分比)
|
||||||
|
- 说明:PowerPoint 段落 > 缩进和间距 > 行距 > "多倍行距"
|
||||||
|
- 示例:`1.5`(1.5 倍行距)
|
||||||
|
- 始于:v3.5.0
|
||||||
|
|
||||||
|
- **margin**:边距
|
||||||
|
- 类型:`Margin`(`number | [number, number, number, number]`)
|
||||||
|
- 单位:pt
|
||||||
|
- 说明:PowerPoint 设置形状格式 > 形状选项 > 大小与属性 > 文本框 > 左右上下边距
|
||||||
|
- 默认值:PowerPoint 默认 "常规" 边距
|
||||||
|
- 示例:`0`(四边统一)、`[10, 5, 10, 5]`(上、右、下、左)
|
||||||
|
|
||||||
|
- **outline**:文字轮廓
|
||||||
|
- 类型:`{ color: Color, size: number }`
|
||||||
|
- 子参数:
|
||||||
|
- **outline.color**:轮廓颜色
|
||||||
|
- 类型:`Color`
|
||||||
|
- **outline.size**:轮廓大小
|
||||||
|
- 类型:`number`
|
||||||
|
- 单位:pt
|
||||||
|
|
||||||
|
- **paraSpaceAfter**:段后间距
|
||||||
|
- 类型:`number`
|
||||||
|
|
||||||
|
- **paraSpaceBefore**:段前间距
|
||||||
|
- 类型:`number`
|
||||||
|
|
||||||
|
- **placeholder**:占位符类型
|
||||||
|
- 类型:`string`
|
||||||
|
- 示例:`'body'`
|
||||||
|
|
||||||
|
- **rectRadius**:圆角矩形半径
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`0.0`–`1.0`
|
||||||
|
- 默认值:`0`
|
||||||
|
- 说明:仅适用于 `ROUNDED_RECTANGLE` 形状
|
||||||
|
|
||||||
|
- **rotate**:旋转角度
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`-360`–`360`(度)
|
||||||
|
- 默认值:`0`
|
||||||
|
|
||||||
|
- **rtlMode**:从右向左模式
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 取值范围:`true` / `false`
|
||||||
|
- 默认值:`false`
|
||||||
|
|
||||||
|
- **shadow**:阴影
|
||||||
|
- 类型:`ShadowProps`
|
||||||
|
- 子参数:
|
||||||
|
- **shadow.type**:阴影类型
|
||||||
|
- 类型:`'outer' | 'inner' | 'none'`
|
||||||
|
- 默认值:`'none'`
|
||||||
|
- **shadow.opacity**:不透明度
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`0.0`–`1.0`
|
||||||
|
- **shadow.blur**:模糊
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`0`–`100`(pt)
|
||||||
|
- 默认值:`0`
|
||||||
|
- **shadow.angle**:角度
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`0`–`359`(度)
|
||||||
|
- 默认值:`0`
|
||||||
|
- **shadow.offset**:偏移距离
|
||||||
|
- 类型:`number`
|
||||||
|
- 取值范围:`0`–`200`(pt)
|
||||||
|
- 默认值:`0`
|
||||||
|
- **shadow.color**:阴影颜色
|
||||||
|
- 类型:`HexColor`
|
||||||
|
- 格式:6 位十六进制(无 `#`)
|
||||||
|
- **shadow.rotateWithShape**:阴影是否随形状旋转
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 默认值:`false`
|
||||||
|
|
||||||
|
- **shape**:形状名称
|
||||||
|
- 类型:`SHAPE_NAME`
|
||||||
|
- 说明:将文字转为指定形状
|
||||||
|
|
||||||
|
- **strike**:删除线
|
||||||
|
- 类型:`boolean | 'dblStrike' | 'sngStrike'`
|
||||||
|
- 取值范围:`true`(单删除线)、`false`、`'dblStrike'`(双删除线)、`'sngStrike'`(单删除线)
|
||||||
|
|
||||||
|
- **subscript**:下标
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 取值范围:`true` / `false`
|
||||||
|
- 默认值:`false`
|
||||||
|
|
||||||
|
- **superscript**:上标
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 取值范围:`true` / `false`
|
||||||
|
- 默认值:`false`
|
||||||
|
|
||||||
|
- **vert**:垂直文字模式
|
||||||
|
- 类型:`string`
|
||||||
|
- 取值范围:
|
||||||
|
`'eaVert'` | `'horz'` | `'mongolianVert'` | `'vert'` | `'vert270'` | `'wordArtVert'` | `'wordArtVertRtl'`
|
||||||
|
|
||||||
|
- **wrap**:文字自动换行
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 取值范围:`true` / `false`
|
||||||
|
- 默认值:`true`
|
||||||
|
- 始于:v3.3.0
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 位置属性 (PositionProps)
|
||||||
|
|
||||||
|
- **x**:水平位置
|
||||||
|
- 类型:`Coord`(`number | `${number}%``)
|
||||||
|
- 单位:英寸(number)或百分比(`'50%'`)
|
||||||
|
|
||||||
|
- **y**:垂直位置
|
||||||
|
- 类型:`Coord`
|
||||||
|
- 单位:英寸或百分比
|
||||||
|
|
||||||
|
- **w**:宽度
|
||||||
|
- 类型:`Coord`
|
||||||
|
- 单位:英寸或百分比
|
||||||
|
|
||||||
|
- **h**:高度
|
||||||
|
- 类型:`Coord`
|
||||||
|
- 单位:英寸或百分比
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 对象名称属性 (ObjectNameProps)
|
||||||
|
|
||||||
|
- **objectName**:对象名称
|
||||||
|
- 类型:`string`
|
||||||
|
- 说明:替代默认的 "Object N" 名称,在 PowerPoint 选择窗格中显示
|
||||||
|
- 默认值:`'Object 1'`
|
||||||
|
- 始于:v3.10.0
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## TextProps
|
||||||
|
|
||||||
|
- **text**:文字内容
|
||||||
|
- 类型:`string`
|
||||||
|
- **options**:文字选项
|
||||||
|
- 类型:`TextPropsOptions`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 已废弃参数
|
||||||
|
|
||||||
|
以下参数已废弃,建议使用替代参数:
|
||||||
|
|
||||||
|
| 已废弃参数 | 替代参数 | 废弃版本 |
|
||||||
|
|---|---|---|
|
||||||
|
| `autoFit` | `fit` | v3.3.0 |
|
||||||
|
| `shrinkText` | `fit` | v3.3.0 |
|
||||||
|
| `inset` | `margin` | v3.10.0 |
|
||||||
|
| `bullet.code` | `bullet.characterCode` | v3.3.0 |
|
||||||
|
| `bullet.marginPt` | `bullet.indent` | v3.3.0 |
|
||||||
|
| `bullet.startAt` | `bullet.numberStartAt` | v3.3.0 |
|
||||||
|
| `bullet.style` | `bullet.numberType` | v3.3.0 |
|
||||||
|
| `lineDash` | `line.dashType` | v3.3.0 |
|
||||||
|
| `lineHead` | `line.beginArrowType` | v3.3.0 |
|
||||||
|
| `lineSize` | `line.width` | v3.3.0 |
|
||||||
|
| `lineTail` | `line.endArrowType` | v3.3.0 |
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
var { normalizePosition, normalizeColor, normalizeColorInOptions, validateEnum } = require('./index');
|
||||||
|
|
||||||
|
function renderChart(slide, chartData) {
|
||||||
|
var opts = {};
|
||||||
|
if (chartData.options) {
|
||||||
|
for (var k in chartData.options) {
|
||||||
|
if (chartData.options.hasOwnProperty(k)) opts[k] = chartData.options[k];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
normalizePosition(opts);
|
||||||
|
|
||||||
|
var validChartTypes = ['area', 'bar', 'bar3D', 'bubble', 'doughnut', 'line', 'pie', 'radar', 'scatter'];
|
||||||
|
validateEnum(chartData.chartType, validChartTypes, 'chartType');
|
||||||
|
|
||||||
|
if (chartData.data) {
|
||||||
|
for (var i = 0; i < chartData.data.length; i++) {
|
||||||
|
var series = chartData.data[i];
|
||||||
|
if (series.labels && series.values && series.labels.length !== series.values.length) {
|
||||||
|
console.warn('Chart series "' + (series.name || i) + '" has mismatched labels (' + series.labels.length + ') and values (' + series.values.length + ')');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
validateEnum(opts.barDir, ['bar', 'col'], 'barDir');
|
||||||
|
validateEnum(opts.barGrouping, ['clustered', 'stacked', 'standard'], 'barGrouping');
|
||||||
|
validateEnum(opts.radarStyle, ['standard', 'marker', 'filled'], 'radarStyle');
|
||||||
|
validateEnum(opts.legendPos, ['b', 'l', 'r', 't', 'tr'], 'legendPos');
|
||||||
|
validateEnum(opts.lineCap, ['flat', 'round', 'square'], 'lineCap');
|
||||||
|
validateEnum(opts.lineDataSymbol, ['circle', 'dash', 'diamond', 'dot', 'none', 'square', 'triangle'], 'lineDataSymbol');
|
||||||
|
validateEnum(opts.displayBlanksAs, ['gap', 'span', 'zero'], 'displayBlanksAs');
|
||||||
|
validateEnum(opts.bar3DShape, ['box', 'cylinder', 'cone', 'pyramid'], 'bar3DShape');
|
||||||
|
validateEnum(opts.dataLabelPosition, ['b', 'bestFit', 'ctr', 'l', 'r', 't', 'inEnd', 'outEnd'], 'dataLabelPosition');
|
||||||
|
validateEnum(opts.titleAlign, ['l', 'ctr', 'r'], 'titleAlign');
|
||||||
|
|
||||||
|
if (opts.chartColors) {
|
||||||
|
for (var i = 0; i < opts.chartColors.length; i++) {
|
||||||
|
var nc = normalizeColor(opts.chartColors[i]);
|
||||||
|
if (nc) opts.chartColors[i] = nc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
slide.addChart(chartData.chartType, chartData.data, opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { renderChart };
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
var { normalizePosition, normalizeColorInOptions, validateEnum } = require('./index');
|
||||||
|
|
||||||
|
function renderImage(slide, imageData) {
|
||||||
|
var opts = {};
|
||||||
|
if (imageData.options) {
|
||||||
|
for (var k in imageData.options) {
|
||||||
|
if (imageData.options.hasOwnProperty(k)) opts[k] = imageData.options[k];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
normalizePosition(opts);
|
||||||
|
|
||||||
|
if (opts.sizing && typeof opts.sizing === 'object') {
|
||||||
|
validateEnum(opts.sizing.type, ['contain', 'cover', 'crop'], 'sizing.type');
|
||||||
|
}
|
||||||
|
if (opts.shadow && typeof opts.shadow === 'object') {
|
||||||
|
normalizeColorInOptions(opts.shadow, 'color');
|
||||||
|
validateEnum(opts.shadow.type, ['outer', 'inner', 'none'], 'shadow.type');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
slide.addImage(opts);
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('Failed to load image: ' + (opts.path || 'unknown'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { renderImage };
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
const pptxgen = require('pptxgenjs');
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
function normalizePosition(options) {
|
||||||
|
if (!options || typeof options !== 'object') return options;
|
||||||
|
['x', 'y', 'w', 'h'].forEach(function(key) {
|
||||||
|
if (options[key] === undefined) return;
|
||||||
|
var val = options[key];
|
||||||
|
if (typeof val === 'number' && !isNaN(val)) return;
|
||||||
|
if (typeof val === 'string' && /^\d+(\.\d+)?%$/.test(val)) return;
|
||||||
|
console.warn('Invalid position value for "' + key + '": got ' + JSON.stringify(val) + ', expected a number or percentage string (e.g. "50%")');
|
||||||
|
delete options[key];
|
||||||
|
});
|
||||||
|
if ((options.x !== undefined || options.y !== undefined) && options.w === undefined) options.w = 4;
|
||||||
|
if ((options.x !== undefined || options.y !== undefined) && options.h === undefined) options.h = 3;
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeColor(color) {
|
||||||
|
if (color === undefined || color === null) return color;
|
||||||
|
if (typeof color !== 'string') {
|
||||||
|
console.warn('Invalid color value: expected a string, got ' + typeof color);
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
var themeColors = ['tx1', 'tx2', 'bg1', 'bg2', 'accent1', 'accent2', 'accent3', 'accent4', 'accent5', 'accent6'];
|
||||||
|
for (var i = 0; i < themeColors.length; i++) {
|
||||||
|
if (color === themeColors[i]) return color;
|
||||||
|
}
|
||||||
|
var clean = color.replace(/^#/, '');
|
||||||
|
if (/^[0-9A-Fa-f]{6}$/.test(clean)) return clean;
|
||||||
|
console.warn('Invalid color value: "' + color + '" is not a valid color format (expected 6-digit hex or theme color)');
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeColorInOptions(options, key) {
|
||||||
|
if (!options || options[key] === undefined) return;
|
||||||
|
var nc = normalizeColor(options[key]);
|
||||||
|
if (nc === undefined) delete options[key];
|
||||||
|
else options[key] = nc;
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateEnum(value, validValues, name) {
|
||||||
|
if (value === undefined || value === null) return value;
|
||||||
|
if (validValues.indexOf(value) === -1) {
|
||||||
|
console.warn('Invalid value for ' + name + ': got "' + value + '", expected one of [' + validValues.join(', ') + ']');
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function withDefaults(options, defaults) {
|
||||||
|
if (!options || typeof options !== 'object') return options;
|
||||||
|
for (var key in defaults) {
|
||||||
|
if (defaults.hasOwnProperty(key) && options[key] === undefined) {
|
||||||
|
options[key] = defaults[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports.normalizePosition = normalizePosition;
|
||||||
|
module.exports.normalizeColor = normalizeColor;
|
||||||
|
module.exports.normalizeColorInOptions = normalizeColorInOptions;
|
||||||
|
module.exports.validateEnum = validateEnum;
|
||||||
|
module.exports.withDefaults = withDefaults;
|
||||||
|
|
||||||
|
const { renderSlide } = require('./slide-renderer');
|
||||||
|
|
||||||
|
function render(pptxJson) {
|
||||||
|
const pptx = new pptxgen();
|
||||||
|
const data = typeof pptxJson === 'string' ? JSON.parse(pptxJson) : pptxJson;
|
||||||
|
const pres = data.presentation || {};
|
||||||
|
const slides = data.slides || [];
|
||||||
|
|
||||||
|
if (pres.layout) pptx.layout = pres.layout;
|
||||||
|
if (pres.author) pptx.author = pres.author;
|
||||||
|
if (pres.title) pptx.title = pres.title;
|
||||||
|
if (pres.subject) pptx.subject = pres.subject;
|
||||||
|
if (pres.company) pptx.company = pres.company;
|
||||||
|
if (pres.revision) pptx.revision = pres.revision;
|
||||||
|
if (pres.rtlMode !== undefined) pptx.rtlMode = pres.rtlMode;
|
||||||
|
if (pres.theme) pptx.theme = pres.theme;
|
||||||
|
|
||||||
|
slides.forEach(slideData => renderSlide(pptx, slideData));
|
||||||
|
|
||||||
|
return pptx;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function renderFile(inputPath, outputPath) {
|
||||||
|
const json = fs.readFileSync(inputPath, 'utf8');
|
||||||
|
const pptx = render(json);
|
||||||
|
|
||||||
|
// pptxgenjs v4 uses https.get() to download URL-based images but doesn't
|
||||||
|
// handle connection errors on the request object (only on the response
|
||||||
|
// stream). A failed TLS handshake emits an unhandled 'error' event on the
|
||||||
|
// request that crashes Node.js, and the internal promise never resolves.
|
||||||
|
// Patch to pipe request errors into a fake response that ends cleanly so
|
||||||
|
// writeFile completes with an empty placeholder image.
|
||||||
|
var https = require('https');
|
||||||
|
var _origGet = https.get;
|
||||||
|
var EventEmitter = require('events').EventEmitter;
|
||||||
|
https.get = function() {
|
||||||
|
var args = arguments;
|
||||||
|
var cb = typeof args[args.length - 1] === 'function' ? args[args.length - 1] : null;
|
||||||
|
var urlStr = typeof args[0] === 'string' ? args[0] : '';
|
||||||
|
var req = _origGet.apply(this, args);
|
||||||
|
req.on('error', function(err) {
|
||||||
|
console.warn('Failed to load image: ' + (urlStr || err.hostname || err.host || 'unknown'));
|
||||||
|
if (cb) {
|
||||||
|
var fakeRes = new EventEmitter();
|
||||||
|
fakeRes.setEncoding = function() {};
|
||||||
|
cb(fakeRes);
|
||||||
|
process.nextTick(function() {
|
||||||
|
fakeRes.emit('data', '');
|
||||||
|
fakeRes.emit('end');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return req;
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
return await pptx.writeFile({ fileName: outputPath });
|
||||||
|
} finally {
|
||||||
|
https.get = _origGet;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports.render = render;
|
||||||
|
module.exports.renderFile = renderFile;
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
var { normalizePosition, normalizeColorInOptions, validateEnum } = require('./index');
|
||||||
|
|
||||||
|
function renderShape(slide, shapeData) {
|
||||||
|
var opts = {};
|
||||||
|
if (shapeData.options) {
|
||||||
|
for (var k in shapeData.options) {
|
||||||
|
if (shapeData.options.hasOwnProperty(k)) opts[k] = shapeData.options[k];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
normalizePosition(opts);
|
||||||
|
|
||||||
|
if (opts.fill && typeof opts.fill === 'object') {
|
||||||
|
normalizeColorInOptions(opts.fill, 'color');
|
||||||
|
validateEnum(opts.fill.type, ['none', 'solid'], 'fill.type');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts.line && typeof opts.line === 'object') {
|
||||||
|
normalizeColorInOptions(opts.line, 'color');
|
||||||
|
validateEnum(opts.line.dashType, ['solid', 'dash', 'dashDot', 'lgDash', 'lgDashDot', 'lgDashDotDot', 'sysDash', 'sysDot'], 'line.dashType');
|
||||||
|
validateEnum(opts.line.beginArrowType, ['none', 'arrow', 'diamond', 'oval', 'stealth', 'triangle'], 'line.beginArrowType');
|
||||||
|
validateEnum(opts.line.endArrowType, ['none', 'arrow', 'diamond', 'oval', 'stealth', 'triangle'], 'line.endArrowType');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts.shadow && typeof opts.shadow === 'object') {
|
||||||
|
normalizeColorInOptions(opts.shadow, 'color');
|
||||||
|
validateEnum(opts.shadow.type, ['outer', 'inner', 'none'], 'shadow.type');
|
||||||
|
}
|
||||||
|
|
||||||
|
validateEnum(opts.align, ['left', 'center', 'right', 'justify'], 'align');
|
||||||
|
|
||||||
|
slide.addShape(shapeData.shapeName, opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { renderShape };
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
const { normalizeColor } = require('./index');
|
||||||
|
const { renderText } = require('./text-renderer');
|
||||||
|
const { renderShape } = require('./shape-renderer');
|
||||||
|
const { renderTable } = require('./table-renderer');
|
||||||
|
const { renderImage } = require('./image-renderer');
|
||||||
|
const { renderChart } = require('./chart-renderer');
|
||||||
|
|
||||||
|
function renderSlide(pptx, slideData) {
|
||||||
|
const slide = pptx.addSlide();
|
||||||
|
|
||||||
|
if (slideData.background) {
|
||||||
|
if (typeof slideData.background === 'object') {
|
||||||
|
slide.background = {};
|
||||||
|
for (var k in slideData.background) {
|
||||||
|
if (slideData.background.hasOwnProperty(k)) {
|
||||||
|
slide.background[k] = slideData.background[k];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (slide.background.color) {
|
||||||
|
var nc = normalizeColor(slide.background.color);
|
||||||
|
if (nc) slide.background.color = nc;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
slide.background = slideData.background;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (slideData.color) {
|
||||||
|
var nc = normalizeColor(slideData.color);
|
||||||
|
if (nc) slide.color = nc;
|
||||||
|
}
|
||||||
|
if (slideData.hidden) slide.hidden = slideData.hidden;
|
||||||
|
|
||||||
|
const objects = slideData.objects || [];
|
||||||
|
objects.forEach(obj => {
|
||||||
|
switch (obj.type) {
|
||||||
|
case 'text':
|
||||||
|
renderText(slide, obj);
|
||||||
|
break;
|
||||||
|
case 'shape':
|
||||||
|
renderShape(slide, obj);
|
||||||
|
break;
|
||||||
|
case 'table':
|
||||||
|
renderTable(slide, obj);
|
||||||
|
break;
|
||||||
|
case 'image':
|
||||||
|
renderImage(slide, obj);
|
||||||
|
break;
|
||||||
|
case 'chart':
|
||||||
|
renderChart(slide, obj);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { renderSlide };
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
var { normalizePosition, normalizeColorInOptions, validateEnum } = require('./index');
|
||||||
|
|
||||||
|
function renderTable(slide, tableData) {
|
||||||
|
var opts = {};
|
||||||
|
if (tableData.options) {
|
||||||
|
for (var k in tableData.options) {
|
||||||
|
if (tableData.options.hasOwnProperty(k)) opts[k] = tableData.options[k];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
normalizePosition(opts);
|
||||||
|
|
||||||
|
if (opts.fill && typeof opts.fill === 'object') {
|
||||||
|
normalizeColorInOptions(opts.fill, 'color');
|
||||||
|
validateEnum(opts.fill.type, ['none', 'solid'], 'fill.type');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts.border) {
|
||||||
|
if (Array.isArray(opts.border)) {
|
||||||
|
if (opts.border.length !== 4) {
|
||||||
|
console.warn('Invalid table border array: expected 4 sides, got ' + opts.border.length);
|
||||||
|
}
|
||||||
|
} else if (typeof opts.border === 'object') {
|
||||||
|
normalizeColorInOptions(opts.border, 'color');
|
||||||
|
if (opts.border.type !== undefined) {
|
||||||
|
validateEnum(opts.border.type, ['none', 'dash', 'solid'], 'border.type');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var rows = [];
|
||||||
|
if (tableData.rows) {
|
||||||
|
for (var r = 0; r < tableData.rows.length; r++) {
|
||||||
|
var row = tableData.rows[r];
|
||||||
|
var newRow = [];
|
||||||
|
for (var c = 0; c < row.length; c++) {
|
||||||
|
var cell = row[c];
|
||||||
|
var newCell = { text: cell.text };
|
||||||
|
|
||||||
|
if (cell.colspan !== undefined) {
|
||||||
|
if (typeof cell.colspan !== 'number' || !isFinite(cell.colspan) || cell.colspan < 1 || cell.colspan !== Math.floor(cell.colspan)) {
|
||||||
|
console.warn('Invalid colspan: got ' + JSON.stringify(cell.colspan) + ', expected integer >= 1');
|
||||||
|
}
|
||||||
|
newCell.colspan = cell.colspan;
|
||||||
|
}
|
||||||
|
if (cell.rowspan !== undefined) {
|
||||||
|
if (typeof cell.rowspan !== 'number' || !isFinite(cell.rowspan) || cell.rowspan < 1 || cell.rowspan !== Math.floor(cell.rowspan)) {
|
||||||
|
console.warn('Invalid rowspan: got ' + JSON.stringify(cell.rowspan) + ', expected integer >= 1');
|
||||||
|
}
|
||||||
|
newCell.rowspan = cell.rowspan;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cell.options) {
|
||||||
|
newCell.options = {};
|
||||||
|
for (var k in cell.options) {
|
||||||
|
if (cell.options.hasOwnProperty(k)) newCell.options[k] = cell.options[k];
|
||||||
|
}
|
||||||
|
normalizeColorInOptions(newCell.options, 'color');
|
||||||
|
normalizeColorInOptions(newCell.options, 'highlight');
|
||||||
|
if (newCell.options.fill && typeof newCell.options.fill === 'object') {
|
||||||
|
normalizeColorInOptions(newCell.options.fill, 'color');
|
||||||
|
}
|
||||||
|
if (newCell.options.border) {
|
||||||
|
if (Array.isArray(newCell.options.border)) {
|
||||||
|
if (newCell.options.border.length !== 4) {
|
||||||
|
console.warn('Invalid cell border array: expected 4 sides, got ' + newCell.options.border.length);
|
||||||
|
}
|
||||||
|
} else if (typeof newCell.options.border === 'object') {
|
||||||
|
normalizeColorInOptions(newCell.options.border, 'color');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
validateEnum(newCell.options.align, ['left', 'center', 'right', 'justify'], 'cell.align');
|
||||||
|
validateEnum(newCell.options.valign, ['top', 'middle', 'bottom'], 'cell.valign');
|
||||||
|
}
|
||||||
|
|
||||||
|
newRow.push(newCell);
|
||||||
|
}
|
||||||
|
rows.push(newRow);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
slide.addTable(rows, opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { renderTable };
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
var { normalizePosition, normalizeColor, normalizeColorInOptions, validateEnum } = require('./index');
|
||||||
|
|
||||||
|
function renderText(slide, textData) {
|
||||||
|
var opts = {};
|
||||||
|
if (textData.options) {
|
||||||
|
for (var k in textData.options) {
|
||||||
|
if (textData.options.hasOwnProperty(k)) opts[k] = textData.options[k];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
normalizePosition(opts);
|
||||||
|
normalizeColorInOptions(opts, 'color');
|
||||||
|
normalizeColorInOptions(opts, 'highlight');
|
||||||
|
|
||||||
|
if (opts.underline && typeof opts.underline === 'object') {
|
||||||
|
normalizeColorInOptions(opts.underline, 'color');
|
||||||
|
}
|
||||||
|
if (opts.bullet && typeof opts.bullet === 'object') {
|
||||||
|
normalizeColorInOptions(opts.bullet, 'color');
|
||||||
|
}
|
||||||
|
if (opts.glow && typeof opts.glow === 'object') {
|
||||||
|
normalizeColorInOptions(opts.glow, 'color');
|
||||||
|
}
|
||||||
|
if (opts.outline && typeof opts.outline === 'object') {
|
||||||
|
normalizeColorInOptions(opts.outline, 'color');
|
||||||
|
}
|
||||||
|
|
||||||
|
validateEnum(opts.align, ['left', 'center', 'right', 'justify'], 'align');
|
||||||
|
validateEnum(opts.valign, ['top', 'middle', 'bottom'], 'valign');
|
||||||
|
validateEnum(opts.textDirection, ['horz', 'vert', 'vert270', 'wordArtVert'], 'textDirection');
|
||||||
|
validateEnum(opts.vert, ['eaVert', 'horz', 'mongolianVert', 'vert', 'vert270', 'wordArtVert', 'wordArtVertRtl'], 'vert');
|
||||||
|
validateEnum(opts.fit, ['none', 'shrink', 'resize'], 'fit');
|
||||||
|
|
||||||
|
if (typeof opts.strike === 'string') {
|
||||||
|
validateEnum(opts.strike, ['dblStrike', 'sngStrike'], 'strike');
|
||||||
|
}
|
||||||
|
if (opts.underline && opts.underline.style !== undefined) {
|
||||||
|
validateEnum(opts.underline.style, ['dash', 'dashHeavy', 'dashLong', 'dashLongHeavy', 'dbl', 'dotDash', 'dotDashHeave', 'dotDotDash', 'dotDotDashHeavy', 'dotted', 'dottedHeavy', 'heavy', 'none', 'sng', 'wavy', 'wavyDbl', 'wavyHeavy'], 'underline.style');
|
||||||
|
}
|
||||||
|
if (opts.bullet && typeof opts.bullet === 'object' && opts.bullet.type !== undefined) {
|
||||||
|
validateEnum(opts.bullet.type, ['bullet', 'number'], 'bullet.type');
|
||||||
|
}
|
||||||
|
|
||||||
|
slide.addText(textData.text, opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { renderText };
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
{
|
||||||
|
"presentation": {
|
||||||
|
"layout": "LAYOUT_16x9",
|
||||||
|
"author": "Demo Author",
|
||||||
|
"title": "Basic Presentation"
|
||||||
|
},
|
||||||
|
"slides": [
|
||||||
|
{
|
||||||
|
"objects": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "Welcome to PPTX Demo",
|
||||||
|
"options": {
|
||||||
|
"x": 1.0,
|
||||||
|
"y": 2.0,
|
||||||
|
"w": 8.0,
|
||||||
|
"h": 1.5,
|
||||||
|
"fontSize": 44,
|
||||||
|
"fontFace": "Arial",
|
||||||
|
"color": "2E75B6",
|
||||||
|
"bold": true,
|
||||||
|
"align": "center",
|
||||||
|
"valign": "middle"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"objects": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "Section Title",
|
||||||
|
"options": {
|
||||||
|
"x": 0.5,
|
||||||
|
"y": 0.3,
|
||||||
|
"w": 9.0,
|
||||||
|
"h": 0.8,
|
||||||
|
"fontSize": 28,
|
||||||
|
"fontFace": "Arial",
|
||||||
|
"color": "2E75B6",
|
||||||
|
"bold": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "This is the body content of the slide. It demonstrates a typical title and body layout used in presentations.",
|
||||||
|
"options": {
|
||||||
|
"x": 0.5,
|
||||||
|
"y": 1.5,
|
||||||
|
"w": 9.0,
|
||||||
|
"h": 3.0,
|
||||||
|
"fontSize": 18,
|
||||||
|
"fontFace": "Calibri",
|
||||||
|
"color": "333333",
|
||||||
|
"align": "left",
|
||||||
|
"valign": "top",
|
||||||
|
"wrap": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,374 @@
|
|||||||
|
{
|
||||||
|
"presentation": {
|
||||||
|
"layout": "LAYOUT_16x9",
|
||||||
|
"author": "Jane Developer",
|
||||||
|
"title": "Full Feature Demo",
|
||||||
|
"subject": "Demonstrating all PPTX slide object types",
|
||||||
|
"company": "Acme Corp",
|
||||||
|
"revision": "3",
|
||||||
|
"rtlMode": false,
|
||||||
|
"theme": {
|
||||||
|
"headFontFace": "Calibri Light",
|
||||||
|
"bodyFontFace": "Calibri"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"slides": [
|
||||||
|
{
|
||||||
|
"background": {
|
||||||
|
"color": "2E75B6",
|
||||||
|
"transparency": 0
|
||||||
|
},
|
||||||
|
"objects": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "Full Feature Demo",
|
||||||
|
"options": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 1.5,
|
||||||
|
"w": 10,
|
||||||
|
"h": 1.5,
|
||||||
|
"fontSize": 48,
|
||||||
|
"fontFace": "Arial",
|
||||||
|
"color": "FFFFFF",
|
||||||
|
"bold": true,
|
||||||
|
"align": "center",
|
||||||
|
"valign": "middle"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "PowerPoint Generation via JSON",
|
||||||
|
"options": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 3.2,
|
||||||
|
"w": 10,
|
||||||
|
"h": 0.8,
|
||||||
|
"fontSize": 20,
|
||||||
|
"fontFace": "Calibri",
|
||||||
|
"color": "D9E2F3",
|
||||||
|
"align": "center",
|
||||||
|
"valign": "top"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"objects": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "Text Formatting",
|
||||||
|
"options": {
|
||||||
|
"x": 0.5,
|
||||||
|
"y": 0.2,
|
||||||
|
"w": 9.0,
|
||||||
|
"h": 0.6,
|
||||||
|
"fontSize": 28,
|
||||||
|
"fontFace": "Arial",
|
||||||
|
"color": "2E75B6",
|
||||||
|
"bold": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "Bold and italic text",
|
||||||
|
"options": {
|
||||||
|
"x": 0.5,
|
||||||
|
"y": 1.0,
|
||||||
|
"w": 4.0,
|
||||||
|
"h": 0.5,
|
||||||
|
"fontSize": 16,
|
||||||
|
"fontFace": "Calibri",
|
||||||
|
"bold": true,
|
||||||
|
"italic": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "Left aligned with underline",
|
||||||
|
"options": {
|
||||||
|
"x": 0.5,
|
||||||
|
"y": 1.7,
|
||||||
|
"w": 4.0,
|
||||||
|
"h": 0.5,
|
||||||
|
"fontSize": 14,
|
||||||
|
"fontFace": "Calibri",
|
||||||
|
"color": "0070C0",
|
||||||
|
"underline": { "style": "sng", "color": "0070C0" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "Right aligned text",
|
||||||
|
"options": {
|
||||||
|
"x": 5.5,
|
||||||
|
"y": 1.0,
|
||||||
|
"w": 4.0,
|
||||||
|
"h": 0.5,
|
||||||
|
"fontSize": 14,
|
||||||
|
"fontFace": "Calibri",
|
||||||
|
"color": "C00000",
|
||||||
|
"align": "right"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "Bullet point 1\nBullet point 2\nBullet point 3",
|
||||||
|
"options": {
|
||||||
|
"x": 0.5,
|
||||||
|
"y": 2.5,
|
||||||
|
"w": 9.0,
|
||||||
|
"h": 1.5,
|
||||||
|
"fontSize": 14,
|
||||||
|
"fontFace": "Calibri",
|
||||||
|
"bullet": { "type": "bullet", "characterCode": "25BA" },
|
||||||
|
"valign": "top"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"objects": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "Shapes",
|
||||||
|
"options": {
|
||||||
|
"x": 0.5,
|
||||||
|
"y": 0.2,
|
||||||
|
"w": 9.0,
|
||||||
|
"h": 0.6,
|
||||||
|
"fontSize": 28,
|
||||||
|
"fontFace": "Arial",
|
||||||
|
"color": "2E75B6",
|
||||||
|
"bold": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "shape",
|
||||||
|
"shapeName": "rect",
|
||||||
|
"options": {
|
||||||
|
"x": 0.5,
|
||||||
|
"y": 1.2,
|
||||||
|
"w": 2.5,
|
||||||
|
"h": 2.0,
|
||||||
|
"fill": { "color": "4472C4", "transparency": 20 },
|
||||||
|
"line": { "color": "000000", "width": 1 },
|
||||||
|
"shadow": { "type": "outer", "blur": 8, "offset": 4, "angle": 45, "color": "999999" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "shape",
|
||||||
|
"shapeName": "ellipse",
|
||||||
|
"options": {
|
||||||
|
"x": 3.8,
|
||||||
|
"y": 1.2,
|
||||||
|
"w": 2.5,
|
||||||
|
"h": 2.0,
|
||||||
|
"fill": { "color": "ED7D31" },
|
||||||
|
"line": { "color": "333333", "width": 2 }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "shape",
|
||||||
|
"shapeName": "roundRect",
|
||||||
|
"options": {
|
||||||
|
"x": 7.0,
|
||||||
|
"y": 1.2,
|
||||||
|
"w": 2.5,
|
||||||
|
"h": 2.0,
|
||||||
|
"fill": { "color": "70AD47" },
|
||||||
|
"line": { "color": "000000", "width": 1.5, "dashType": "dash" },
|
||||||
|
"rectRadius": 0.2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"slideNumber": {
|
||||||
|
"x": 9.0,
|
||||||
|
"y": 5.0,
|
||||||
|
"fontSize": 10,
|
||||||
|
"color": "888888"
|
||||||
|
},
|
||||||
|
"objects": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "Images and Hyperlinks",
|
||||||
|
"options": {
|
||||||
|
"x": 0.5,
|
||||||
|
"y": 0.2,
|
||||||
|
"w": 9.0,
|
||||||
|
"h": 0.6,
|
||||||
|
"fontSize": 28,
|
||||||
|
"fontFace": "Arial",
|
||||||
|
"color": "2E75B6",
|
||||||
|
"bold": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "image",
|
||||||
|
"options": {
|
||||||
|
"x": 0.5,
|
||||||
|
"y": 1.2,
|
||||||
|
"w": 4.0,
|
||||||
|
"h": 3.0,
|
||||||
|
"path": "https://via.placeholder.com/800x600.png?text=Sample+Image",
|
||||||
|
"sizing": { "type": "contain", "w": 4.0, "h": 3.0 },
|
||||||
|
"shadow": { "type": "outer", "blur": 6, "offset": 3, "angle": 45, "color": "666666" },
|
||||||
|
"hyperlink": { "url": "https://example.com", "tooltip": "Visit Example" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "Click the image above to visit Example.com",
|
||||||
|
"options": {
|
||||||
|
"x": 0.5,
|
||||||
|
"y": 4.5,
|
||||||
|
"w": 4.0,
|
||||||
|
"h": 0.5,
|
||||||
|
"fontSize": 12,
|
||||||
|
"fontFace": "Calibri",
|
||||||
|
"color": "4472C4",
|
||||||
|
"align": "center"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"objects": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "Table with colspan and rowspan",
|
||||||
|
"options": {
|
||||||
|
"x": 0.5,
|
||||||
|
"y": 0.2,
|
||||||
|
"w": 9.0,
|
||||||
|
"h": 0.6,
|
||||||
|
"fontSize": 28,
|
||||||
|
"fontFace": "Arial",
|
||||||
|
"color": "2E75B6",
|
||||||
|
"bold": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "table",
|
||||||
|
"rows": [
|
||||||
|
[
|
||||||
|
{ "text": "Product", "options": { "bold": true, "fill": { "color": "4472C4" }, "color": "FFFFFF", "fontSize": 14 } },
|
||||||
|
{ "text": "Q1 Sales", "options": { "bold": true, "fill": { "color": "4472C4" }, "color": "FFFFFF", "fontSize": 14 } },
|
||||||
|
{ "text": "Q2 Sales", "options": { "bold": true, "fill": { "color": "4472C4" }, "color": "FFFFFF", "fontSize": 14 } },
|
||||||
|
{ "text": "Total", "options": { "bold": true, "fill": { "color": "4472C4" }, "color": "FFFFFF", "fontSize": 14 } }
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ "text": "Widget A", "options": { "fill": { "color": "D9E2F3" } } },
|
||||||
|
{ "text": "$12,000", "options": { "fill": { "color": "D9E2F3" }, "align": "right" } },
|
||||||
|
{ "text": "$15,000", "options": { "fill": { "color": "D9E2F3" }, "align": "right" } },
|
||||||
|
{ "text": "$27,000", "options": { "fill": { "color": "D9E2F3" }, "align": "right", "bold": true } }
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ "text": "Widget B", "options": { "fill": { "color": "EDEDED" } } },
|
||||||
|
{ "text": "$8,500", "options": { "fill": { "color": "EDEDED" }, "align": "right" } },
|
||||||
|
{ "text": "$9,200", "options": { "fill": { "color": "EDEDED" }, "align": "right" } },
|
||||||
|
{ "text": "$17,700", "options": { "fill": { "color": "EDEDED" }, "align": "right", "bold": true } }
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ "text": "Combined Regions", "options": { "bold": true, "fill": { "color": "E2EFDA" } }, "colspan": 3 },
|
||||||
|
{ "text": "$44,700", "options": { "bold": true, "fill": { "color": "E2EFDA" }, "align": "right" } }
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"options": {
|
||||||
|
"x": 0.5,
|
||||||
|
"y": 1.2,
|
||||||
|
"w": 9.0,
|
||||||
|
"h": 3.5,
|
||||||
|
"colW": [3.0, 2.0, 2.0, 2.0],
|
||||||
|
"border": { "type": "solid", "color": "000000", "pt": 1 },
|
||||||
|
"fontSize": 12
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"objects": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "Bar Chart Example",
|
||||||
|
"options": {
|
||||||
|
"x": 0.5,
|
||||||
|
"y": 0.2,
|
||||||
|
"w": 9.0,
|
||||||
|
"h": 0.6,
|
||||||
|
"fontSize": 28,
|
||||||
|
"fontFace": "Arial",
|
||||||
|
"color": "2E75B6",
|
||||||
|
"bold": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "chart",
|
||||||
|
"chartType": "bar",
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"name": "North Region",
|
||||||
|
"labels": ["Q1", "Q2", "Q3", "Q4"],
|
||||||
|
"values": [45, 52, 38, 61]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "South Region",
|
||||||
|
"labels": ["Q1", "Q2", "Q3", "Q4"],
|
||||||
|
"values": [33, 41, 47, 55]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"options": {
|
||||||
|
"x": 0.5,
|
||||||
|
"y": 1.2,
|
||||||
|
"w": 9.0,
|
||||||
|
"h": 4.5,
|
||||||
|
"chartColors": ["4472C4", "ED7D31"],
|
||||||
|
"showTitle": true,
|
||||||
|
"showLegend": true,
|
||||||
|
"showValue": false,
|
||||||
|
"catAxisTitle": "Quarter",
|
||||||
|
"valAxisTitle": "Revenue ($K)",
|
||||||
|
"barGapWidthPct": 80
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"hidden": false,
|
||||||
|
"objects": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "Thank You!",
|
||||||
|
"options": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 1.8,
|
||||||
|
"w": 10,
|
||||||
|
"h": 1.2,
|
||||||
|
"fontSize": 40,
|
||||||
|
"fontFace": "Arial",
|
||||||
|
"color": "2E75B6",
|
||||||
|
"bold": true,
|
||||||
|
"align": "center",
|
||||||
|
"valign": "middle"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "Questions?",
|
||||||
|
"options": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 3.2,
|
||||||
|
"w": 10,
|
||||||
|
"h": 0.8,
|
||||||
|
"fontSize": 20,
|
||||||
|
"fontFace": "Calibri",
|
||||||
|
"color": "666666",
|
||||||
|
"align": "center",
|
||||||
|
"valign": "top"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,984 @@
|
|||||||
|
{
|
||||||
|
"presentation": {
|
||||||
|
"layout": "CUSTOM"
|
||||||
|
},
|
||||||
|
"slides": [
|
||||||
|
{
|
||||||
|
"background": {
|
||||||
|
"color": "F5F6F8"
|
||||||
|
},
|
||||||
|
"objects": [
|
||||||
|
{
|
||||||
|
"type": "shape",
|
||||||
|
"shapeName": "rect",
|
||||||
|
"options": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"w": 24.14,
|
||||||
|
"h": 0.667,
|
||||||
|
"fill": {
|
||||||
|
"color": "1A1A2E"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u817e\u8baf\u4e91",
|
||||||
|
"options": {
|
||||||
|
"x": 0.542,
|
||||||
|
"y": 0.056,
|
||||||
|
"w": 1.499,
|
||||||
|
"h": 0.278,
|
||||||
|
"fontSize": 16,
|
||||||
|
"color": "FFFFFF",
|
||||||
|
"bold": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u63a7\u5236\u53f0",
|
||||||
|
"options": {
|
||||||
|
"x": 2.278,
|
||||||
|
"y": 0.056,
|
||||||
|
"w": 1.201,
|
||||||
|
"h": 0.278,
|
||||||
|
"fontSize": 13,
|
||||||
|
"color": "FFFFFF"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u641c\u7d22\u4e91\u4ea7\u54c1\u3001\u8d44\u6e90\u4e0eAPI",
|
||||||
|
"options": {
|
||||||
|
"x": 6.875,
|
||||||
|
"y": 0.056,
|
||||||
|
"w": 7.0,
|
||||||
|
"h": 0.278,
|
||||||
|
"fontSize": 13,
|
||||||
|
"color": "8E95A1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u96c6\u56e2\u8d26\u53f7",
|
||||||
|
"options": {
|
||||||
|
"x": 8.986,
|
||||||
|
"y": 0.056,
|
||||||
|
"w": 1.049,
|
||||||
|
"h": 0.278,
|
||||||
|
"fontSize": 13,
|
||||||
|
"color": "FFFFFF"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u5907\u6848",
|
||||||
|
"options": {
|
||||||
|
"x": 9.708,
|
||||||
|
"y": 0.056,
|
||||||
|
"w": 0.526,
|
||||||
|
"h": 0.278,
|
||||||
|
"fontSize": 13,
|
||||||
|
"color": "FFFFFF"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u5de5\u5177",
|
||||||
|
"options": {
|
||||||
|
"x": 10.153,
|
||||||
|
"y": 0.056,
|
||||||
|
"w": 0.526,
|
||||||
|
"h": 0.278,
|
||||||
|
"fontSize": 13,
|
||||||
|
"color": "FFFFFF"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u5ba2\u670d\u652f\u6301",
|
||||||
|
"options": {
|
||||||
|
"x": 10.583,
|
||||||
|
"y": 0.056,
|
||||||
|
"w": 1.049,
|
||||||
|
"h": 0.278,
|
||||||
|
"fontSize": 13,
|
||||||
|
"color": "FFFFFF"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u8bd5\u7528",
|
||||||
|
"options": {
|
||||||
|
"x": 11.167,
|
||||||
|
"y": 0.056,
|
||||||
|
"w": 0.526,
|
||||||
|
"h": 0.278,
|
||||||
|
"fontSize": 13,
|
||||||
|
"color": "FFFFFF"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u8d39\u7528",
|
||||||
|
"options": {
|
||||||
|
"x": 11.667,
|
||||||
|
"y": 0.056,
|
||||||
|
"w": 0.526,
|
||||||
|
"h": 0.278,
|
||||||
|
"fontSize": 13,
|
||||||
|
"color": "FFFFFF"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "shape",
|
||||||
|
"shapeName": "ellipse",
|
||||||
|
"options": {
|
||||||
|
"x": 12.917,
|
||||||
|
"y": 0.042,
|
||||||
|
"w": 0.25,
|
||||||
|
"h": 0.25,
|
||||||
|
"fill": {
|
||||||
|
"color": "FF4D4F"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "1",
|
||||||
|
"options": {
|
||||||
|
"x": 12.917,
|
||||||
|
"y": 0.042,
|
||||||
|
"w": 0.45,
|
||||||
|
"h": 0.25,
|
||||||
|
"fontSize": 8,
|
||||||
|
"color": "FFFFFF",
|
||||||
|
"bold": true,
|
||||||
|
"align": "center"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "oldi \u4e3b\u8d26\u53f7",
|
||||||
|
"options": {
|
||||||
|
"x": 13.431,
|
||||||
|
"y": 0.056,
|
||||||
|
"w": 1.049,
|
||||||
|
"h": 0.278,
|
||||||
|
"fontSize": 11,
|
||||||
|
"color": "FFFFFF",
|
||||||
|
"align": "right"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "shape",
|
||||||
|
"shapeName": "rect",
|
||||||
|
"options": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0.667,
|
||||||
|
"w": 2.403,
|
||||||
|
"h": 13.306,
|
||||||
|
"fill": {
|
||||||
|
"color": "1A1A2E"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "ICP\u5907\u6848",
|
||||||
|
"options": {
|
||||||
|
"x": 0.333,
|
||||||
|
"y": 0.944,
|
||||||
|
"w": 1.201,
|
||||||
|
"h": 0.236,
|
||||||
|
"fontSize": 13,
|
||||||
|
"color": "FFFFFF",
|
||||||
|
"bold": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "shape",
|
||||||
|
"shapeName": "rect",
|
||||||
|
"options": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 1.389,
|
||||||
|
"w": 2.403,
|
||||||
|
"h": 0.528,
|
||||||
|
"fill": {
|
||||||
|
"color": "2D2D44"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u6211\u7684\u5907\u6848",
|
||||||
|
"options": {
|
||||||
|
"x": 0.514,
|
||||||
|
"y": 1.528,
|
||||||
|
"w": 1.1,
|
||||||
|
"h": 0.236,
|
||||||
|
"fontSize": 13,
|
||||||
|
"color": "FFFFFF"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u5907\u6848\u77e5\u8bc6\u5e93",
|
||||||
|
"options": {
|
||||||
|
"x": 0.514,
|
||||||
|
"y": 2.056,
|
||||||
|
"w": 1.474,
|
||||||
|
"h": 0.236,
|
||||||
|
"fontSize": 13,
|
||||||
|
"color": "999999"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u5907\u6848\u67e5\u8be2",
|
||||||
|
"options": {
|
||||||
|
"x": 0.514,
|
||||||
|
"y": 2.583,
|
||||||
|
"w": 1.249,
|
||||||
|
"h": 0.236,
|
||||||
|
"fontSize": 13,
|
||||||
|
"color": "999999"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u5907\u6848\u8ba2\u5355",
|
||||||
|
"options": {
|
||||||
|
"x": 0.514,
|
||||||
|
"y": 3.111,
|
||||||
|
"w": 1.249,
|
||||||
|
"h": 0.236,
|
||||||
|
"fontSize": 13,
|
||||||
|
"color": "999999"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u5907\u6848\u6388\u6743\u7801",
|
||||||
|
"options": {
|
||||||
|
"x": 0.514,
|
||||||
|
"y": 3.639,
|
||||||
|
"w": 1.474,
|
||||||
|
"h": 0.236,
|
||||||
|
"fontSize": 13,
|
||||||
|
"color": "999999"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u5907\u6848\u8bb0\u5f55",
|
||||||
|
"options": {
|
||||||
|
"x": 0.514,
|
||||||
|
"y": 4.167,
|
||||||
|
"w": 1.249,
|
||||||
|
"h": 0.236,
|
||||||
|
"fontSize": 13,
|
||||||
|
"color": "999999"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u6536\u8d77",
|
||||||
|
"options": {
|
||||||
|
"x": 0.514,
|
||||||
|
"y": 13.583,
|
||||||
|
"w": 0.526,
|
||||||
|
"h": 0.236,
|
||||||
|
"fontSize": 13,
|
||||||
|
"color": "8E95A1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "WorkBuddy \u4f60\u7684\u529e\u516c\u597d\u642d\u5b50\uff0c\u4e00\u4eba\u6307\u6325\uff0cAI\u4e13\u5bb6\u56e2\u6267\u884c\uff0c\u6587\u6863/\u8bbe\u8ba1/\u5f00\u53d1\u4e00\u53e5\u8bdd\u4ea4\u4ed8\uff0c\u9650\u65f6\u4eab\u53cc\u500d Credits \u67e5\u770b\u8be6\u60c5 \u203a",
|
||||||
|
"options": {
|
||||||
|
"x": 3.028,
|
||||||
|
"y": 0.986,
|
||||||
|
"w": 18.751,
|
||||||
|
"h": 0.208,
|
||||||
|
"fontSize": 10,
|
||||||
|
"color": "333333"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u6211\u7684\u5907\u6848",
|
||||||
|
"options": {
|
||||||
|
"x": 1.75,
|
||||||
|
"y": 1.625,
|
||||||
|
"w": 1.201,
|
||||||
|
"h": 0.278,
|
||||||
|
"fontSize": 14,
|
||||||
|
"color": "333333",
|
||||||
|
"bold": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u63a5\u5165\u5907\u6848 - \u5ba1\u6838\u4e2d",
|
||||||
|
"options": {
|
||||||
|
"x": 2.556,
|
||||||
|
"y": 2.569,
|
||||||
|
"w": 3.499,
|
||||||
|
"h": 0.208,
|
||||||
|
"fontSize": 13,
|
||||||
|
"color": "333333",
|
||||||
|
"bold": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u5907\u6848\u8ba2\u5355\u53f7 30178470045009931 \u8ba2\u5355\u7c7b\u578b \u63a5\u5165\u5907\u6848 \u63d0\u4ea4\u5ba1\u6838\u65f6\u95f4 2026-07-23 06:20:29",
|
||||||
|
"options": {
|
||||||
|
"x": 2.556,
|
||||||
|
"y": 2.986,
|
||||||
|
"w": 17.5,
|
||||||
|
"h": 0.208,
|
||||||
|
"fontSize": 11,
|
||||||
|
"color": "666666"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "shape",
|
||||||
|
"shapeName": "ellipse",
|
||||||
|
"options": {
|
||||||
|
"x": 4.306,
|
||||||
|
"y": 3.611,
|
||||||
|
"w": 0.333,
|
||||||
|
"h": 0.333,
|
||||||
|
"fill": {
|
||||||
|
"color": "1677FF"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u2713",
|
||||||
|
"options": {
|
||||||
|
"x": 4.306,
|
||||||
|
"y": 3.611,
|
||||||
|
"w": 0.599,
|
||||||
|
"h": 0.333,
|
||||||
|
"fontSize": 11,
|
||||||
|
"color": "FFFFFF",
|
||||||
|
"bold": true,
|
||||||
|
"align": "center"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u63d0\u4ea4\u521d\u5ba1",
|
||||||
|
"options": {
|
||||||
|
"x": 4.722,
|
||||||
|
"y": 3.611,
|
||||||
|
"w": 1.049,
|
||||||
|
"h": 0.208,
|
||||||
|
"fontSize": 11,
|
||||||
|
"color": "333333"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u5df2\u63d0\u4ea4",
|
||||||
|
"options": {
|
||||||
|
"x": 4.722,
|
||||||
|
"y": 3.819,
|
||||||
|
"w": 1.049,
|
||||||
|
"h": 0.167,
|
||||||
|
"fontSize": 10,
|
||||||
|
"color": "666666"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "shape",
|
||||||
|
"shapeName": "ellipse",
|
||||||
|
"options": {
|
||||||
|
"x": 6.667,
|
||||||
|
"y": 3.611,
|
||||||
|
"w": 0.333,
|
||||||
|
"h": 0.333,
|
||||||
|
"fill": {
|
||||||
|
"color": "1677FF"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u2713",
|
||||||
|
"options": {
|
||||||
|
"x": 6.667,
|
||||||
|
"y": 3.611,
|
||||||
|
"w": 0.599,
|
||||||
|
"h": 0.333,
|
||||||
|
"fontSize": 11,
|
||||||
|
"color": "FFFFFF",
|
||||||
|
"bold": true,
|
||||||
|
"align": "center"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u817e\u8baf\u4e91\u5ba1\u6838",
|
||||||
|
"options": {
|
||||||
|
"x": 7.083,
|
||||||
|
"y": 3.611,
|
||||||
|
"w": 1.249,
|
||||||
|
"h": 0.208,
|
||||||
|
"fontSize": 11,
|
||||||
|
"color": "333333"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u5df2\u901a\u8fc7",
|
||||||
|
"options": {
|
||||||
|
"x": 7.083,
|
||||||
|
"y": 3.819,
|
||||||
|
"w": 1.249,
|
||||||
|
"h": 0.167,
|
||||||
|
"fontSize": 10,
|
||||||
|
"color": "666666"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "shape",
|
||||||
|
"shapeName": "ellipse",
|
||||||
|
"options": {
|
||||||
|
"x": 9.028,
|
||||||
|
"y": 3.611,
|
||||||
|
"w": 0.333,
|
||||||
|
"h": 0.333,
|
||||||
|
"fill": {
|
||||||
|
"color": "1677FF"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u2713",
|
||||||
|
"options": {
|
||||||
|
"x": 9.028,
|
||||||
|
"y": 3.611,
|
||||||
|
"w": 0.599,
|
||||||
|
"h": 0.333,
|
||||||
|
"fontSize": 11,
|
||||||
|
"color": "FFFFFF",
|
||||||
|
"bold": true,
|
||||||
|
"align": "center"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u5f85\u63d0\u4ea4\u7ba1\u5c40",
|
||||||
|
"options": {
|
||||||
|
"x": 9.444,
|
||||||
|
"y": 3.611,
|
||||||
|
"w": 1.249,
|
||||||
|
"h": 0.208,
|
||||||
|
"fontSize": 11,
|
||||||
|
"color": "333333"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u5df2\u63d0\u4ea4",
|
||||||
|
"options": {
|
||||||
|
"x": 9.444,
|
||||||
|
"y": 3.819,
|
||||||
|
"w": 1.249,
|
||||||
|
"h": 0.167,
|
||||||
|
"fontSize": 10,
|
||||||
|
"color": "666666"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "shape",
|
||||||
|
"shapeName": "ellipse",
|
||||||
|
"options": {
|
||||||
|
"x": 11.389,
|
||||||
|
"y": 3.611,
|
||||||
|
"w": 0.333,
|
||||||
|
"h": 0.333,
|
||||||
|
"fill": {
|
||||||
|
"color": "1677FF"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u2713",
|
||||||
|
"options": {
|
||||||
|
"x": 11.389,
|
||||||
|
"y": 3.611,
|
||||||
|
"w": 0.599,
|
||||||
|
"h": 0.333,
|
||||||
|
"fontSize": 11,
|
||||||
|
"color": "FFFFFF",
|
||||||
|
"bold": true,
|
||||||
|
"align": "center"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u5de5\u4fe1\u90e8\u77ed\u4fe1\u6838\u9a8c",
|
||||||
|
"options": {
|
||||||
|
"x": 11.806,
|
||||||
|
"y": 3.611,
|
||||||
|
"w": 1.499,
|
||||||
|
"h": 0.208,
|
||||||
|
"fontSize": 11,
|
||||||
|
"color": "333333"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u5df2\u6838\u9a8c",
|
||||||
|
"options": {
|
||||||
|
"x": 11.806,
|
||||||
|
"y": 3.819,
|
||||||
|
"w": 1.499,
|
||||||
|
"h": 0.167,
|
||||||
|
"fontSize": 10,
|
||||||
|
"color": "666666"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "shape",
|
||||||
|
"shapeName": "ellipse",
|
||||||
|
"options": {
|
||||||
|
"x": 13.75,
|
||||||
|
"y": 3.611,
|
||||||
|
"w": 0.333,
|
||||||
|
"h": 0.333,
|
||||||
|
"fill": {
|
||||||
|
"color": "FF9800"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "5",
|
||||||
|
"options": {
|
||||||
|
"x": 13.75,
|
||||||
|
"y": 3.611,
|
||||||
|
"w": 0.599,
|
||||||
|
"h": 0.333,
|
||||||
|
"fontSize": 11,
|
||||||
|
"color": "FFFFFF",
|
||||||
|
"bold": true,
|
||||||
|
"align": "center"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u7ba1\u5c40\u5ba1\u6838",
|
||||||
|
"options": {
|
||||||
|
"x": 14.167,
|
||||||
|
"y": 3.611,
|
||||||
|
"w": 1.049,
|
||||||
|
"h": 0.208,
|
||||||
|
"fontSize": 11,
|
||||||
|
"color": "333333"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u5ba1\u6838\u4e2d | \u8fdb\u5ea6\u54a8\u8be2 \u25be",
|
||||||
|
"options": {
|
||||||
|
"x": 14.167,
|
||||||
|
"y": 3.819,
|
||||||
|
"w": 1.35,
|
||||||
|
"h": 0.167,
|
||||||
|
"fontSize": 10,
|
||||||
|
"color": "1677FF"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u5ba1\u6838\u8bf4\u660e",
|
||||||
|
"options": {
|
||||||
|
"x": 2.556,
|
||||||
|
"y": 5.0,
|
||||||
|
"w": 1.249,
|
||||||
|
"h": 0.208,
|
||||||
|
"fontSize": 13,
|
||||||
|
"color": "333333",
|
||||||
|
"bold": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u6839\u636e\u8ba2\u5355\u91cf\u63a8\u7b97\uff0c\u7ba1\u5c40\u5ba1\u6838\u9884\u8ba18\u4e2a\u5de5\u4f5c\u65e5\u5de6\u53f3\u5b8c\u6210\u5ba1\u6838\uff0c\u6700\u957f\u4e0d\u8d85\u8fc720\u4e2a\u5de5\u4f5c\u65e5\uff0c\u5177\u4f53\u4ee5\u5b9e\u9645\u5ba1\u6838\u65f6\u95f4\u4e3a\u51c6\uff0c\u8bf7\u60a8\u8010\u5fc3\u7b49\u5f85",
|
||||||
|
"options": {
|
||||||
|
"x": 2.556,
|
||||||
|
"y": 5.417,
|
||||||
|
"w": 17.5,
|
||||||
|
"h": 0.25,
|
||||||
|
"fontSize": 10,
|
||||||
|
"color": "666666"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u7531\u4e8e\u7ba1\u5c40\u4e0b\u53d1\u5b58\u5728\u5ef6\u8fdf\uff0c\u5982\u6536\u5230\u5de5\u4fe1\u90e8\u201c\u9a73\u56de\u201d\u3001\u201c\u5df2\u53d8\u66f4\u201d\u6216\u201c\u6210\u529f\u201d\u7684\u77ed\u4fe1\u901a\u77e5\uff0c\u817e\u8baf\u4e91\u4f1a\u57286-24\u5c0f\u65f6\u5185\u6536\u5230\u53cd\u9988\uff08\u9ad8\u5cf0\u671f\u53ef\u80fd\u66f4\u957f\uff09\uff0c\u5e76\u66f4\u65b0\u72b6\u6001\uff0c\u6b64\u8fc7\u7a0b\u65e0\u6cd5\u52a0\u901f\uff0c\u8bf7\u7a0d\u540e\u518d\u67e5\u8be2\uff0c\u611f\u8c22\u60a8\u7684\u7406\u89e3\u3002",
|
||||||
|
"options": {
|
||||||
|
"x": 2.556,
|
||||||
|
"y": 5.833,
|
||||||
|
"w": 17.5,
|
||||||
|
"h": 0.25,
|
||||||
|
"fontSize": 10,
|
||||||
|
"color": "666666"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u5f53\u524d\u8ba2\u5355\u6b63\u5728\u901a\u4fe1\u7ba1\u7406\u5c40\u5ba1\u6838\u4e2d\u3002\u5ba1\u6838\u671f\u95f4\u8ba2\u5355\u5c06\u88ab\u9501\u5b9a\uff0c\u4e0d\u652f\u6301\u64a4\u56de\u6216\u4fee\u6539\u5907\u6848\u4fe1\u606f\uff0c\u8bf7\u7b49\u5f85\u5ba1\u6838\u5b8c\u6210\u540e\u518d\u8fdb\u884c\u540e\u7eed\u64cd\u4f5c\u3002",
|
||||||
|
"options": {
|
||||||
|
"x": 2.556,
|
||||||
|
"y": 6.25,
|
||||||
|
"w": 17.5,
|
||||||
|
"h": 0.25,
|
||||||
|
"fontSize": 10,
|
||||||
|
"color": "666666"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u7ba1\u5c40\u65e0\u6cd5\u50ac\u5ba1",
|
||||||
|
"options": {
|
||||||
|
"x": 2.556,
|
||||||
|
"y": 6.944,
|
||||||
|
"w": 1.499,
|
||||||
|
"h": 0.333,
|
||||||
|
"fontSize": 11,
|
||||||
|
"color": "999999"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u67e5\u770b\u5907\u6848\u4fe1\u606f",
|
||||||
|
"options": {
|
||||||
|
"x": 3.889,
|
||||||
|
"y": 6.944,
|
||||||
|
"w": 1.499,
|
||||||
|
"h": 0.333,
|
||||||
|
"fontSize": 11,
|
||||||
|
"color": "1677FF"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u4e3b\u4f53\u4fe1\u606f",
|
||||||
|
"options": {
|
||||||
|
"x": 2.556,
|
||||||
|
"y": 7.778,
|
||||||
|
"w": 1.249,
|
||||||
|
"h": 0.208,
|
||||||
|
"fontSize": 13,
|
||||||
|
"color": "333333",
|
||||||
|
"bold": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u53d8\u66f4\u5907\u6848",
|
||||||
|
"options": {
|
||||||
|
"x": 8.056,
|
||||||
|
"y": 7.778,
|
||||||
|
"w": 1.001,
|
||||||
|
"h": 0.167,
|
||||||
|
"fontSize": 10,
|
||||||
|
"color": "1677FF"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u6ce8\u9500\u4e3b\u4f53",
|
||||||
|
"options": {
|
||||||
|
"x": 8.889,
|
||||||
|
"y": 7.778,
|
||||||
|
"w": 1.001,
|
||||||
|
"h": 0.167,
|
||||||
|
"fontSize": 10,
|
||||||
|
"color": "1677FF"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u8fc1\u79fb\u5907\u6848\u8d26\u53f7",
|
||||||
|
"options": {
|
||||||
|
"x": 9.722,
|
||||||
|
"y": 7.778,
|
||||||
|
"w": 1.249,
|
||||||
|
"h": 0.167,
|
||||||
|
"fontSize": 10,
|
||||||
|
"color": "1677FF"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u5bfc\u51fa\u57fa\u7840\u4fe1\u606f",
|
||||||
|
"options": {
|
||||||
|
"x": 10.694,
|
||||||
|
"y": 7.778,
|
||||||
|
"w": 1.249,
|
||||||
|
"h": 0.167,
|
||||||
|
"fontSize": 10,
|
||||||
|
"color": "1677FF"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u6682\u65e0\u5907\u6848",
|
||||||
|
"options": {
|
||||||
|
"x": 6.25,
|
||||||
|
"y": 8.611,
|
||||||
|
"w": 1.001,
|
||||||
|
"h": 0.167,
|
||||||
|
"fontSize": 11,
|
||||||
|
"color": "999999"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u4e92\u8054\u7f51\u4fe1\u606f\u670d\u52a1",
|
||||||
|
"options": {
|
||||||
|
"x": 2.556,
|
||||||
|
"y": 9.306,
|
||||||
|
"w": 2.0,
|
||||||
|
"h": 0.208,
|
||||||
|
"fontSize": 13,
|
||||||
|
"color": "333333",
|
||||||
|
"bold": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u65b0\u589e/\u63a5\u5165\u670d\u52a1",
|
||||||
|
"options": {
|
||||||
|
"x": 7.361,
|
||||||
|
"y": 9.306,
|
||||||
|
"w": 1.499,
|
||||||
|
"h": 0.333,
|
||||||
|
"fontSize": 11,
|
||||||
|
"color": "1677FF"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u6ce8\u9500\u670d\u52a1",
|
||||||
|
"options": {
|
||||||
|
"x": 8.472,
|
||||||
|
"y": 9.306,
|
||||||
|
"w": 1.001,
|
||||||
|
"h": 0.333,
|
||||||
|
"fontSize": 11,
|
||||||
|
"color": "999999"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u53d6\u6d88\u817e\u8baf\u4e91\u63a5\u5165",
|
||||||
|
"options": {
|
||||||
|
"x": 9.306,
|
||||||
|
"y": 9.306,
|
||||||
|
"w": 1.75,
|
||||||
|
"h": 0.333,
|
||||||
|
"fontSize": 11,
|
||||||
|
"color": "999999"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u901a\u77e5\u516c\u544a",
|
||||||
|
"options": {
|
||||||
|
"x": 18.056,
|
||||||
|
"y": 1.042,
|
||||||
|
"w": 1.499,
|
||||||
|
"h": 0.208,
|
||||||
|
"fontSize": 13,
|
||||||
|
"color": "333333",
|
||||||
|
"bold": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u5173\u4e8e\u5b58\u91cfAPP\u5907\u6848\u901a\u77e5",
|
||||||
|
"options": {
|
||||||
|
"x": 18.056,
|
||||||
|
"y": 1.458,
|
||||||
|
"w": 3.749,
|
||||||
|
"h": 0.167,
|
||||||
|
"fontSize": 10,
|
||||||
|
"color": "666666"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u817e\u8baf\u4e91\u4e13\u9879\u6838\u67e5\u901a\u77e5",
|
||||||
|
"options": {
|
||||||
|
"x": 18.056,
|
||||||
|
"y": 1.806,
|
||||||
|
"w": 3.749,
|
||||||
|
"h": 0.167,
|
||||||
|
"fontSize": 10,
|
||||||
|
"color": "666666"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u5173\u4e8e\u9884\u9632\u5907\u6848\u8fc7\u7a0b\u4e2d\u906d\u9047\u5feb\u9012\u8bc8\u9a97\u7684\u516c\u544a",
|
||||||
|
"options": {
|
||||||
|
"x": 18.056,
|
||||||
|
"y": 2.153,
|
||||||
|
"w": 3.749,
|
||||||
|
"h": 0.208,
|
||||||
|
"fontSize": 10,
|
||||||
|
"color": "666666"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u8d26\u53f7\u76f8\u5173\u95ee\u9898",
|
||||||
|
"options": {
|
||||||
|
"x": 18.056,
|
||||||
|
"y": 2.778,
|
||||||
|
"w": 1.75,
|
||||||
|
"h": 0.208,
|
||||||
|
"fontSize": 13,
|
||||||
|
"color": "333333",
|
||||||
|
"bold": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u4e00\u4e2a\u8d26\u53f7\u53ef\u4ee5\u7ed9\u591a\u4e2a\u4e0d\u540c\u4e3b\u4f53\u5907\u6848\u5417\uff1f",
|
||||||
|
"options": {
|
||||||
|
"x": 18.056,
|
||||||
|
"y": 3.194,
|
||||||
|
"w": 3.749,
|
||||||
|
"h": 0.167,
|
||||||
|
"fontSize": 10,
|
||||||
|
"color": "666666"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u4e91\u8d44\u6e90\u76f8\u5173\u95ee\u9898",
|
||||||
|
"options": {
|
||||||
|
"x": 18.056,
|
||||||
|
"y": 3.819,
|
||||||
|
"w": 1.75,
|
||||||
|
"h": 0.208,
|
||||||
|
"fontSize": 13,
|
||||||
|
"color": "333333",
|
||||||
|
"bold": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u5982\u4f55\u83b7\u53d6\u548c\u4f7f\u7528\u201c\u5907\u6848\u6388\u6743\u7801\u201d\uff1f",
|
||||||
|
"options": {
|
||||||
|
"x": 18.056,
|
||||||
|
"y": 4.236,
|
||||||
|
"w": 3.749,
|
||||||
|
"h": 0.167,
|
||||||
|
"fontSize": 10,
|
||||||
|
"color": "666666"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u4e00\u53f0\u670d\u52a1\u5668\u53ef\u4ee5\u5907\u6848\u51e0\u4e2a\u7f51\u7ad9\u6216APP\uff1f",
|
||||||
|
"options": {
|
||||||
|
"x": 18.056,
|
||||||
|
"y": 4.514,
|
||||||
|
"w": 3.749,
|
||||||
|
"h": 0.167,
|
||||||
|
"fontSize": 10,
|
||||||
|
"color": "666666"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u57df\u540d\u76f8\u5173\u95ee\u9898",
|
||||||
|
"options": {
|
||||||
|
"x": 18.056,
|
||||||
|
"y": 4.931,
|
||||||
|
"w": 1.499,
|
||||||
|
"h": 0.208,
|
||||||
|
"fontSize": 13,
|
||||||
|
"color": "333333",
|
||||||
|
"bold": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u5907\u6848\u524d\u57df\u540d\u5fc5\u987b\u5b9e\u540d\u8ba4\u8bc1\u5417\uff1f",
|
||||||
|
"options": {
|
||||||
|
"x": 18.056,
|
||||||
|
"y": 5.347,
|
||||||
|
"w": 3.749,
|
||||||
|
"h": 0.167,
|
||||||
|
"fontSize": 10,
|
||||||
|
"color": "666666"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u515a\u653f\u673a\u5173\u6216\u4e8b\u4e1a\u5355\u4f4d\u6027\u8d28\u5907\u6848\uff08\u7f51\u7ad9\u6216APP\uff09\u57df\u540d\u6709\u4ec0\u4e48\u8981\u6c42\uff1f",
|
||||||
|
"options": {
|
||||||
|
"x": 18.056,
|
||||||
|
"y": 5.625,
|
||||||
|
"w": 3.749,
|
||||||
|
"h": 0.25,
|
||||||
|
"fontSize": 10,
|
||||||
|
"color": "666666"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u65b0\u589e\u5907\u6848\u76f8\u5173\u95ee\u9898",
|
||||||
|
"options": {
|
||||||
|
"x": 18.056,
|
||||||
|
"y": 6.181,
|
||||||
|
"w": 2.0,
|
||||||
|
"h": 0.208,
|
||||||
|
"fontSize": 13,
|
||||||
|
"color": "333333",
|
||||||
|
"bold": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u4e00\u4e2a\u8ba2\u5355\u53ef\u4ee5\u65b0\u589e\u51e0\u4e2a\u7f51\u7ad9\u4e0eAPP\uff1f",
|
||||||
|
"options": {
|
||||||
|
"x": 18.056,
|
||||||
|
"y": 6.597,
|
||||||
|
"w": 3.749,
|
||||||
|
"h": 0.167,
|
||||||
|
"fontSize": 10,
|
||||||
|
"color": "666666"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "\u65b0\u589e\u7f51\u7ad9\u6216 APP \u8fc7\u7a0b\u4e2d\uff0c\u7f51\u7ad9\u6216 APP \u662f\u5426\u80fd\u591f\u8bbf\u95ee\uff1f",
|
||||||
|
"options": {
|
||||||
|
"x": 18.056,
|
||||||
|
"y": 6.875,
|
||||||
|
"w": 3.749,
|
||||||
|
"h": 0.208,
|
||||||
|
"fontSize": 10,
|
||||||
|
"color": "666666"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,367 @@
|
|||||||
|
{
|
||||||
|
"presentation": {
|
||||||
|
"layout": "LAYOUT_16x9",
|
||||||
|
"title": "金融数据看板 - Advanced UI"
|
||||||
|
},
|
||||||
|
"slides": [
|
||||||
|
{
|
||||||
|
"background": {
|
||||||
|
"color": "0D0D2B"
|
||||||
|
},
|
||||||
|
"objects": [
|
||||||
|
{
|
||||||
|
"type": "shape",
|
||||||
|
"shapeName": "rect",
|
||||||
|
"options": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"w": 10,
|
||||||
|
"h": 5.625,
|
||||||
|
"fill": { "color": "0D0D2B" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "shape",
|
||||||
|
"shapeName": "rect",
|
||||||
|
"options": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"w": 10,
|
||||||
|
"h": 5.625,
|
||||||
|
"fill": { "color": "1A0A3E", "transparency": 50 }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "shape",
|
||||||
|
"shapeName": "rect",
|
||||||
|
"options": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"w": 10,
|
||||||
|
"h": 0.65,
|
||||||
|
"fill": { "color": "1A1A3A" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "金融数据看板",
|
||||||
|
"options": {
|
||||||
|
"x": 0.4,
|
||||||
|
"y": 0.12,
|
||||||
|
"w": 5,
|
||||||
|
"h": 0.4,
|
||||||
|
"fontSize": 20,
|
||||||
|
"fontFace": "Arial",
|
||||||
|
"color": "FFFFFF",
|
||||||
|
"bold": true,
|
||||||
|
"charSpacing": 3,
|
||||||
|
"glow": { "color": "6669E3", "opacity": 0.6, "size": 4 }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "2026-07-23 14:30:00",
|
||||||
|
"options": {
|
||||||
|
"x": 7.2,
|
||||||
|
"y": 0.15,
|
||||||
|
"w": 2.5,
|
||||||
|
"h": 0.35,
|
||||||
|
"fontSize": 11,
|
||||||
|
"fontFace": "Arial",
|
||||||
|
"color": "FFFFFF",
|
||||||
|
"transparency": 40,
|
||||||
|
"align": "right"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "shape",
|
||||||
|
"shapeName": "roundRect",
|
||||||
|
"options": {
|
||||||
|
"x": 0.4,
|
||||||
|
"y": 0.9,
|
||||||
|
"w": 2.1,
|
||||||
|
"h": 1.3,
|
||||||
|
"fill": { "color": "1E1E3A" },
|
||||||
|
"rectRadius": 0.15,
|
||||||
|
"shadow": { "type": "outer", "blur": 8, "offset": 3, "color": "000000", "opacity": 0.4 }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "¥12.8亿",
|
||||||
|
"options": {
|
||||||
|
"x": 0.4,
|
||||||
|
"y": 1.0,
|
||||||
|
"w": 2.1,
|
||||||
|
"h": 0.5,
|
||||||
|
"fontSize": 26,
|
||||||
|
"fontFace": "Arial",
|
||||||
|
"color": "FFFFFF",
|
||||||
|
"bold": true,
|
||||||
|
"align": "center"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "总资产",
|
||||||
|
"options": {
|
||||||
|
"x": 0.4,
|
||||||
|
"y": 1.65,
|
||||||
|
"w": 2.1,
|
||||||
|
"h": 0.3,
|
||||||
|
"fontSize": 12,
|
||||||
|
"fontFace": "Arial",
|
||||||
|
"color": "AAAAAA",
|
||||||
|
"align": "center"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "shape",
|
||||||
|
"shapeName": "roundRect",
|
||||||
|
"options": {
|
||||||
|
"x": 2.75,
|
||||||
|
"y": 0.9,
|
||||||
|
"w": 2.1,
|
||||||
|
"h": 1.3,
|
||||||
|
"fill": { "color": "1E1E3A" },
|
||||||
|
"rectRadius": 0.15,
|
||||||
|
"shadow": { "type": "outer", "blur": 8, "offset": 3, "color": "000000", "opacity": 0.4 }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "¥3,240万",
|
||||||
|
"options": {
|
||||||
|
"x": 2.75,
|
||||||
|
"y": 1.0,
|
||||||
|
"w": 2.1,
|
||||||
|
"h": 0.5,
|
||||||
|
"fontSize": 26,
|
||||||
|
"fontFace": "Arial",
|
||||||
|
"color": "3BC798",
|
||||||
|
"bold": true,
|
||||||
|
"align": "center"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "今日交易额",
|
||||||
|
"options": {
|
||||||
|
"x": 2.75,
|
||||||
|
"y": 1.65,
|
||||||
|
"w": 2.1,
|
||||||
|
"h": 0.3,
|
||||||
|
"fontSize": 12,
|
||||||
|
"fontFace": "Arial",
|
||||||
|
"color": "AAAAAA",
|
||||||
|
"align": "center"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "shape",
|
||||||
|
"shapeName": "roundRect",
|
||||||
|
"options": {
|
||||||
|
"x": 5.1,
|
||||||
|
"y": 0.9,
|
||||||
|
"w": 2.1,
|
||||||
|
"h": 1.3,
|
||||||
|
"fill": { "color": "1E1E3A" },
|
||||||
|
"rectRadius": 0.15,
|
||||||
|
"shadow": { "type": "outer", "blur": 8, "offset": 3, "color": "000000", "opacity": 0.4 }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "-2.34%",
|
||||||
|
"options": {
|
||||||
|
"x": 5.1,
|
||||||
|
"y": 1.0,
|
||||||
|
"w": 2.1,
|
||||||
|
"h": 0.5,
|
||||||
|
"fontSize": 26,
|
||||||
|
"fontFace": "Arial",
|
||||||
|
"color": "FF6752",
|
||||||
|
"bold": true,
|
||||||
|
"align": "center"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "市场指数",
|
||||||
|
"options": {
|
||||||
|
"x": 5.1,
|
||||||
|
"y": 1.65,
|
||||||
|
"w": 2.1,
|
||||||
|
"h": 0.3,
|
||||||
|
"fontSize": 12,
|
||||||
|
"fontFace": "Arial",
|
||||||
|
"color": "AAAAAA",
|
||||||
|
"align": "center"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "shape",
|
||||||
|
"shapeName": "roundRect",
|
||||||
|
"options": {
|
||||||
|
"x": 7.45,
|
||||||
|
"y": 0.9,
|
||||||
|
"w": 2.1,
|
||||||
|
"h": 1.3,
|
||||||
|
"fill": { "color": "1E1E3A" },
|
||||||
|
"rectRadius": 0.15,
|
||||||
|
"shadow": { "type": "outer", "blur": 8, "offset": 3, "color": "000000", "opacity": 0.4 }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "shape",
|
||||||
|
"shapeName": "roundRect",
|
||||||
|
"options": {
|
||||||
|
"x": 7.45,
|
||||||
|
"y": 0.9,
|
||||||
|
"w": 2.1,
|
||||||
|
"h": 1.3,
|
||||||
|
"fill": { "color": "6669E3", "transparency": 80 },
|
||||||
|
"rectRadius": 0.15
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "+8.56%",
|
||||||
|
"options": {
|
||||||
|
"x": 7.45,
|
||||||
|
"y": 1.0,
|
||||||
|
"w": 2.1,
|
||||||
|
"h": 0.5,
|
||||||
|
"fontSize": 26,
|
||||||
|
"fontFace": "Arial",
|
||||||
|
"color": "3BC798",
|
||||||
|
"bold": true,
|
||||||
|
"align": "center"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "年化收益率",
|
||||||
|
"options": {
|
||||||
|
"x": 7.45,
|
||||||
|
"y": 1.65,
|
||||||
|
"w": 2.1,
|
||||||
|
"h": 0.3,
|
||||||
|
"fontSize": 12,
|
||||||
|
"fontFace": "Arial",
|
||||||
|
"color": "AAAAAA",
|
||||||
|
"align": "center"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "chart",
|
||||||
|
"chartType": "line",
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"name": "上证指数",
|
||||||
|
"labels": ["09:30", "10:30", "11:30", "13:30", "14:30", "15:00"],
|
||||||
|
"values": [3250, 3280, 3265, 3295, 3310, 3305]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "沪深300",
|
||||||
|
"labels": ["09:30", "10:30", "11:30", "13:30", "14:30", "15:00"],
|
||||||
|
"values": [3980, 4020, 4005, 4045, 4070, 4060]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"options": {
|
||||||
|
"x": 0.4,
|
||||||
|
"y": 2.5,
|
||||||
|
"w": 9.2,
|
||||||
|
"h": 1.7,
|
||||||
|
"chartColors": ["6669E3", "3BC798"],
|
||||||
|
"lineSize": 2.5,
|
||||||
|
"showLegend": true,
|
||||||
|
"showTitle": true,
|
||||||
|
"title": "市场走势",
|
||||||
|
"titleColor": "FFFFFF",
|
||||||
|
"titleFontSize": 12,
|
||||||
|
"legendColor": "AAAAAA",
|
||||||
|
"legendFontSize": 9,
|
||||||
|
"legendPos": "b",
|
||||||
|
"catAxisLabelColor": "888888",
|
||||||
|
"catAxisLabelFontSize": 8,
|
||||||
|
"valAxisLabelColor": "888888",
|
||||||
|
"valAxisLabelFontSize": 8,
|
||||||
|
"catGridLine": { "color": "2A2A4A", "size": 0.5, "style": "dash" },
|
||||||
|
"valGridLine": { "color": "2A2A4A", "size": 0.5, "style": "solid" },
|
||||||
|
"lineDataSymbol": "circle",
|
||||||
|
"lineDataSymbolSize": 4,
|
||||||
|
"lineDataSymbolLineColor": "FFFFFF",
|
||||||
|
"plotArea": { "fill": { "color": "16162E" } }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "table",
|
||||||
|
"rows": [
|
||||||
|
[
|
||||||
|
{ "text": "股票代码", "options": { "bold": true, "fill": { "color": "262650" }, "color": "FFFFFF", "fontSize": 10, "align": "center" } },
|
||||||
|
{ "text": "名称", "options": { "bold": true, "fill": { "color": "262650" }, "color": "FFFFFF", "fontSize": 10, "align": "center" } },
|
||||||
|
{ "text": "最新价", "options": { "bold": true, "fill": { "color": "262650" }, "color": "FFFFFF", "fontSize": 10, "align": "center" } },
|
||||||
|
{ "text": "涨跌幅", "options": { "bold": true, "fill": { "color": "262650" }, "color": "FFFFFF", "fontSize": 10, "align": "center" } }
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ "text": "600001", "options": { "fill": { "color": "1E1E3A" }, "color": "CCCCCC", "fontSize": 9, "align": "center" } },
|
||||||
|
{ "text": "平安银行", "options": { "fill": { "color": "1E1E3A" }, "color": "CCCCCC", "fontSize": 9, "align": "center" } },
|
||||||
|
{ "text": "14.58", "options": { "fill": { "color": "1E1E3A" }, "color": "3BC798", "fontSize": 9, "align": "center" } },
|
||||||
|
{ "text": "+2.15%", "options": { "fill": { "color": "1E1E3A" }, "color": "3BC798", "fontSize": 9, "align": "center" } }
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ "text": "600002", "options": { "fill": { "color": "252540" }, "color": "CCCCCC", "fontSize": 9, "align": "center" } },
|
||||||
|
{ "text": "科技创新", "options": { "fill": { "color": "252540" }, "color": "CCCCCC", "fontSize": 9, "align": "center" } },
|
||||||
|
{ "text": "28.90", "options": { "fill": { "color": "252540" }, "color": "FF6752", "fontSize": 9, "align": "center" } },
|
||||||
|
{ "text": "-1.34%", "options": { "fill": { "color": "252540" }, "color": "FF6752", "fontSize": 9, "align": "center" } }
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ "text": "600003", "options": { "fill": { "color": "1E1E3A" }, "color": "CCCCCC", "fontSize": 9, "align": "center" } },
|
||||||
|
{ "text": "绿色能源", "options": { "fill": { "color": "1E1E3A" }, "color": "CCCCCC", "fontSize": 9, "align": "center" } },
|
||||||
|
{ "text": "45.20", "options": { "fill": { "color": "1E1E3A" }, "color": "3BC798", "fontSize": 9, "align": "center" } },
|
||||||
|
{ "text": "+3.67%", "options": { "fill": { "color": "1E1E3A" }, "color": "3BC798", "fontSize": 9, "align": "center" } }
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ "text": "600004", "options": { "fill": { "color": "252540" }, "color": "CCCCCC", "fontSize": 9, "align": "center" } },
|
||||||
|
{ "text": "医药健康", "options": { "fill": { "color": "252540" }, "color": "CCCCCC", "fontSize": 9, "align": "center" } },
|
||||||
|
{ "text": "62.10", "options": { "fill": { "color": "252540" }, "color": "FF6752", "fontSize": 9, "align": "center" } },
|
||||||
|
{ "text": "-0.52%", "options": { "fill": { "color": "252540" }, "color": "FF6752", "fontSize": 9, "align": "center" } }
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"options": {
|
||||||
|
"x": 0.4,
|
||||||
|
"y": 4.4,
|
||||||
|
"w": 9.2,
|
||||||
|
"h": 1.0,
|
||||||
|
"colW": [1.5, 3.2, 2.0, 2.5],
|
||||||
|
"border": { "type": "solid", "color": "2A2A4A", "pt": 0.5 },
|
||||||
|
"align": "center"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "shape",
|
||||||
|
"shapeName": "rect",
|
||||||
|
"options": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0.7,
|
||||||
|
"w": 0.06,
|
||||||
|
"h": 3.5,
|
||||||
|
"fill": { "color": "6669E3", "transparency": 60 }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "shape",
|
||||||
|
"shapeName": "ellipse",
|
||||||
|
"options": {
|
||||||
|
"x": 9.3,
|
||||||
|
"y": 4.9,
|
||||||
|
"w": 0.4,
|
||||||
|
"h": 0.4,
|
||||||
|
"fill": { "color": "6669E3", "transparency": 50 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,317 @@
|
|||||||
|
{
|
||||||
|
"presentation": {
|
||||||
|
"layout": "LAYOUT_16x9",
|
||||||
|
"title": "数据看板 - UI Dashboard"
|
||||||
|
},
|
||||||
|
"slides": [
|
||||||
|
{
|
||||||
|
"background": {
|
||||||
|
"color": "F5F5F7"
|
||||||
|
},
|
||||||
|
"objects": [
|
||||||
|
{
|
||||||
|
"type": "shape",
|
||||||
|
"shapeName": "rect",
|
||||||
|
"options": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"w": 10,
|
||||||
|
"h": 0.7,
|
||||||
|
"fill": { "color": "1A1A2E" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "数据看板",
|
||||||
|
"options": {
|
||||||
|
"x": 0.4,
|
||||||
|
"y": 0.15,
|
||||||
|
"w": 4,
|
||||||
|
"h": 0.4,
|
||||||
|
"fontSize": 22,
|
||||||
|
"fontFace": "Arial",
|
||||||
|
"color": "FFFFFF",
|
||||||
|
"bold": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "2026年7月",
|
||||||
|
"options": {
|
||||||
|
"x": 7.2,
|
||||||
|
"y": 0.15,
|
||||||
|
"w": 2.4,
|
||||||
|
"h": 0.4,
|
||||||
|
"fontSize": 13,
|
||||||
|
"fontFace": "Arial",
|
||||||
|
"color": "AAAAAA",
|
||||||
|
"align": "right"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "shape",
|
||||||
|
"shapeName": "rect",
|
||||||
|
"options": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0.7,
|
||||||
|
"w": 2.0,
|
||||||
|
"h": 4.925,
|
||||||
|
"fill": { "color": "F0F0F2" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "● 概览",
|
||||||
|
"options": {
|
||||||
|
"x": 0.3,
|
||||||
|
"y": 1.0,
|
||||||
|
"w": 1.5,
|
||||||
|
"h": 0.4,
|
||||||
|
"fontSize": 14,
|
||||||
|
"fontFace": "Arial",
|
||||||
|
"color": "6669E3",
|
||||||
|
"bold": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "○ 分析",
|
||||||
|
"options": {
|
||||||
|
"x": 0.3,
|
||||||
|
"y": 1.5,
|
||||||
|
"w": 1.5,
|
||||||
|
"h": 0.4,
|
||||||
|
"fontSize": 14,
|
||||||
|
"fontFace": "Arial",
|
||||||
|
"color": "666666"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "○ 设置",
|
||||||
|
"options": {
|
||||||
|
"x": 0.3,
|
||||||
|
"y": 2.0,
|
||||||
|
"w": 1.5,
|
||||||
|
"h": 0.4,
|
||||||
|
"fontSize": 14,
|
||||||
|
"fontFace": "Arial",
|
||||||
|
"color": "666666"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "○ 帮助",
|
||||||
|
"options": {
|
||||||
|
"x": 0.3,
|
||||||
|
"y": 2.5,
|
||||||
|
"w": 1.5,
|
||||||
|
"h": 0.4,
|
||||||
|
"fontSize": 14,
|
||||||
|
"fontFace": "Arial",
|
||||||
|
"color": "666666"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "shape",
|
||||||
|
"shapeName": "roundRect",
|
||||||
|
"options": {
|
||||||
|
"x": 2.3,
|
||||||
|
"y": 0.9,
|
||||||
|
"w": 2.3,
|
||||||
|
"h": 1.3,
|
||||||
|
"fill": { "color": "FFFFFF" },
|
||||||
|
"rectRadius": 0.15,
|
||||||
|
"line": { "color": "E0E0E0", "width": 1 }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "1,286",
|
||||||
|
"options": {
|
||||||
|
"x": 2.3,
|
||||||
|
"y": 1.0,
|
||||||
|
"w": 2.3,
|
||||||
|
"h": 0.4,
|
||||||
|
"fontSize": 28,
|
||||||
|
"fontFace": "Arial",
|
||||||
|
"color": "1A1A2E",
|
||||||
|
"bold": true,
|
||||||
|
"align": "center"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "总用户",
|
||||||
|
"options": {
|
||||||
|
"x": 2.3,
|
||||||
|
"y": 1.6,
|
||||||
|
"w": 2.3,
|
||||||
|
"h": 0.3,
|
||||||
|
"fontSize": 13,
|
||||||
|
"fontFace": "Arial",
|
||||||
|
"color": "666666",
|
||||||
|
"align": "center"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "shape",
|
||||||
|
"shapeName": "roundRect",
|
||||||
|
"options": {
|
||||||
|
"x": 4.8,
|
||||||
|
"y": 0.9,
|
||||||
|
"w": 2.3,
|
||||||
|
"h": 1.3,
|
||||||
|
"fill": { "color": "FFFFFF" },
|
||||||
|
"rectRadius": 0.15,
|
||||||
|
"line": { "color": "E0E0E0", "width": 1 }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "¥58.2万",
|
||||||
|
"options": {
|
||||||
|
"x": 4.8,
|
||||||
|
"y": 1.0,
|
||||||
|
"w": 2.3,
|
||||||
|
"h": 0.4,
|
||||||
|
"fontSize": 28,
|
||||||
|
"fontFace": "Arial",
|
||||||
|
"color": "1A1A2E",
|
||||||
|
"bold": true,
|
||||||
|
"align": "center"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "营收",
|
||||||
|
"options": {
|
||||||
|
"x": 4.8,
|
||||||
|
"y": 1.6,
|
||||||
|
"w": 2.3,
|
||||||
|
"h": 0.3,
|
||||||
|
"fontSize": 13,
|
||||||
|
"fontFace": "Arial",
|
||||||
|
"color": "666666",
|
||||||
|
"align": "center"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "shape",
|
||||||
|
"shapeName": "roundRect",
|
||||||
|
"options": {
|
||||||
|
"x": 7.3,
|
||||||
|
"y": 0.9,
|
||||||
|
"w": 2.3,
|
||||||
|
"h": 1.3,
|
||||||
|
"fill": { "color": "FFFFFF" },
|
||||||
|
"rectRadius": 0.15,
|
||||||
|
"line": { "color": "E0E0E0", "width": 1 }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "96.5%",
|
||||||
|
"options": {
|
||||||
|
"x": 7.3,
|
||||||
|
"y": 1.0,
|
||||||
|
"w": 2.3,
|
||||||
|
"h": 0.4,
|
||||||
|
"fontSize": 28,
|
||||||
|
"fontFace": "Arial",
|
||||||
|
"color": "1A1A2E",
|
||||||
|
"bold": true,
|
||||||
|
"align": "center"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "满意度",
|
||||||
|
"options": {
|
||||||
|
"x": 7.3,
|
||||||
|
"y": 1.6,
|
||||||
|
"w": 2.3,
|
||||||
|
"h": 0.3,
|
||||||
|
"fontSize": 13,
|
||||||
|
"fontFace": "Arial",
|
||||||
|
"color": "666666",
|
||||||
|
"align": "center"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "table",
|
||||||
|
"rows": [
|
||||||
|
[
|
||||||
|
{ "text": "项目", "options": { "bold": true, "fill": { "color": "6669E3" }, "color": "FFFFFF", "fontSize": 13 } },
|
||||||
|
{ "text": "进度", "options": { "bold": true, "fill": { "color": "6669E3" }, "color": "FFFFFF", "fontSize": 13 } },
|
||||||
|
{ "text": "负责人", "options": { "bold": true, "fill": { "color": "6669E3" }, "color": "FFFFFF", "fontSize": 13 } },
|
||||||
|
{ "text": "状态", "options": { "bold": true, "fill": { "color": "6669E3" }, "color": "FFFFFF", "fontSize": 13 } }
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ "text": "首页改版", "options": { "fill": { "color": "D9E2F3" } } },
|
||||||
|
{ "text": "75%", "options": { "fill": { "color": "D9E2F3" } } },
|
||||||
|
{ "text": "张三", "options": { "fill": { "color": "D9E2F3" } } },
|
||||||
|
{ "text": "进行中", "options": { "fill": { "color": "D9E2F3" } } }
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ "text": "用户系统", "options": { "fill": { "color": "FFFFFF" } } },
|
||||||
|
{ "text": "90%", "options": { "fill": { "color": "FFFFFF" } } },
|
||||||
|
{ "text": "李四", "options": { "fill": { "color": "FFFFFF" } } },
|
||||||
|
{ "text": "测试中", "options": { "fill": { "color": "FFFFFF" } } }
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ "text": "数据迁移", "options": { "fill": { "color": "D9E2F3" } } },
|
||||||
|
{ "text": "60%", "options": { "fill": { "color": "D9E2F3" } } },
|
||||||
|
{ "text": "王五", "options": { "fill": { "color": "D9E2F3" } } },
|
||||||
|
{ "text": "进行中", "options": { "fill": { "color": "D9E2F3" } } }
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ "text": "API优化", "options": { "fill": { "color": "FFFFFF" } } },
|
||||||
|
{ "text": "100%", "options": { "fill": { "color": "FFFFFF" } } },
|
||||||
|
{ "text": "赵六", "options": { "fill": { "color": "FFFFFF" } } },
|
||||||
|
{ "text": "已完成", "options": { "fill": { "color": "FFFFFF" } } }
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ "text": "安全审计", "options": { "fill": { "color": "D9E2F3" } } },
|
||||||
|
{ "text": "45%", "options": { "fill": { "color": "D9E2F3" } } },
|
||||||
|
{ "text": "陈七", "options": { "fill": { "color": "D9E2F3" } } },
|
||||||
|
{ "text": "进行中", "options": { "fill": { "color": "D9E2F3" } } }
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"options": {
|
||||||
|
"x": 2.3,
|
||||||
|
"y": 2.5,
|
||||||
|
"w": 7.3,
|
||||||
|
"h": 2.0,
|
||||||
|
"colW": [2.3, 1.5, 1.5, 2.0],
|
||||||
|
"border": { "type": "solid", "color": "E0E0E0", "pt": 1 },
|
||||||
|
"fontSize": 12
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "chart",
|
||||||
|
"chartType": "bar",
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"name": "营收",
|
||||||
|
"labels": ["1月", "2月", "3月"],
|
||||||
|
"values": [42, 58, 73]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"options": {
|
||||||
|
"x": 2.3,
|
||||||
|
"y": 4.7,
|
||||||
|
"w": 7.3,
|
||||||
|
"h": 0.9,
|
||||||
|
"barDir": "col",
|
||||||
|
"chartColors": ["6669E3"],
|
||||||
|
"showTitle": false,
|
||||||
|
"showLegend": false,
|
||||||
|
"catAxisLabelColor": "666666",
|
||||||
|
"valAxisLabelColor": "666666"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,845 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"$id": "https://example.com/pptx-presentation.schema.json",
|
||||||
|
"title": "PPTX Presentation Schema",
|
||||||
|
"description": "JSON Schema for PPTX presentation intermediate representation covering all pptxgenjs parameters",
|
||||||
|
"type": "object",
|
||||||
|
"definitions": {
|
||||||
|
"coord": {
|
||||||
|
"oneOf": [
|
||||||
|
{ "type": "number" },
|
||||||
|
{ "type": "string", "pattern": "^\\d+(\\.\\d+)?%$" }
|
||||||
|
],
|
||||||
|
"description": "Coordinate value in inches (number) or percentage (e.g. '50%')"
|
||||||
|
},
|
||||||
|
"hexColor": {
|
||||||
|
"type": "string",
|
||||||
|
"pattern": "^[0-9A-Fa-f]{6}$",
|
||||||
|
"description": "6-digit hex color without # prefix"
|
||||||
|
},
|
||||||
|
"themeColor": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["tx1", "tx2", "bg1", "bg2", "accent1", "accent2", "accent3", "accent4", "accent5", "accent6"],
|
||||||
|
"description": "PowerPoint theme/scheme color"
|
||||||
|
},
|
||||||
|
"color": {
|
||||||
|
"oneOf": [
|
||||||
|
{ "$ref": "#/definitions/hexColor" },
|
||||||
|
{ "$ref": "#/definitions/themeColor" }
|
||||||
|
],
|
||||||
|
"description": "Color value: hex string or theme color"
|
||||||
|
},
|
||||||
|
"margin": {
|
||||||
|
"oneOf": [
|
||||||
|
{ "type": "number" },
|
||||||
|
{ "type": "array", "items": { "type": "number" }, "minItems": 4, "maxItems": 4 }
|
||||||
|
],
|
||||||
|
"description": "Margin in points: single value or [top, right, bottom, left]"
|
||||||
|
},
|
||||||
|
"position": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"x": { "$ref": "#/definitions/coord" },
|
||||||
|
"y": { "$ref": "#/definitions/coord" },
|
||||||
|
"w": { "$ref": "#/definitions/coord" },
|
||||||
|
"h": { "$ref": "#/definitions/coord" }
|
||||||
|
},
|
||||||
|
"description": "Position and size properties"
|
||||||
|
},
|
||||||
|
"dataOrPath": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"path": { "type": "string", "description": "URL or local file path" },
|
||||||
|
"data": { "type": "string", "description": "Base64-encoded data" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"borderProps": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"type": { "type": "string", "enum": ["none", "dash", "solid"] },
|
||||||
|
"color": { "$ref": "#/definitions/hexColor" },
|
||||||
|
"pt": { "type": "number", "description": "Border width in points" }
|
||||||
|
},
|
||||||
|
"description": "Border properties"
|
||||||
|
},
|
||||||
|
"shapeFill": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"color": { "$ref": "#/definitions/color" },
|
||||||
|
"transparency": { "type": "number", "minimum": 0, "maximum": 100, "description": "Fill transparency percent" },
|
||||||
|
"type": { "type": "string", "enum": ["none", "solid"] }
|
||||||
|
},
|
||||||
|
"description": "Shape fill properties"
|
||||||
|
},
|
||||||
|
"shapeLine": {
|
||||||
|
"allOf": [
|
||||||
|
{ "$ref": "#/definitions/shapeFill" },
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"width": { "type": "number", "description": "Line width in points" },
|
||||||
|
"dashType": { "type": "string", "enum": ["solid", "dash", "dashDot", "lgDash", "lgDashDot", "lgDashDotDot", "sysDash", "sysDot"] },
|
||||||
|
"beginArrowType": { "type": "string", "enum": ["none", "arrow", "diamond", "oval", "stealth", "triangle"] },
|
||||||
|
"endArrowType": { "type": "string", "enum": ["none", "arrow", "diamond", "oval", "stealth", "triangle"] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Shape line/border properties"
|
||||||
|
},
|
||||||
|
"shadow": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"type": { "type": "string", "enum": ["outer", "inner", "none"] },
|
||||||
|
"opacity": { "type": "number", "minimum": 0, "maximum": 1, "description": "Shadow opacity (0.0-1.0)" },
|
||||||
|
"blur": { "type": "number", "minimum": 0, "maximum": 100, "description": "Shadow blur in points" },
|
||||||
|
"angle": { "type": "number", "minimum": 0, "maximum": 359, "description": "Shadow angle in degrees" },
|
||||||
|
"offset": { "type": "number", "minimum": 0, "maximum": 200, "description": "Shadow offset in points" },
|
||||||
|
"color": { "$ref": "#/definitions/hexColor" },
|
||||||
|
"rotateWithShape": { "type": "boolean" }
|
||||||
|
},
|
||||||
|
"required": ["type"],
|
||||||
|
"description": "Shadow properties"
|
||||||
|
},
|
||||||
|
"hyperlink": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"slide": { "type": "integer", "description": "Slide number to link to" },
|
||||||
|
"url": { "type": "string", "description": "URL to link to" },
|
||||||
|
"tooltip": { "type": "string" }
|
||||||
|
},
|
||||||
|
"description": "Hyperlink properties"
|
||||||
|
},
|
||||||
|
"textBaseProps": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"align": { "type": "string", "enum": ["left", "center", "right", "justify"] },
|
||||||
|
"bold": { "type": "boolean" },
|
||||||
|
"breakLine": { "type": "boolean" },
|
||||||
|
"bullet": {
|
||||||
|
"oneOf": [
|
||||||
|
{ "type": "boolean" },
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"type": { "type": "string", "enum": ["bullet", "number"] },
|
||||||
|
"characterCode": { "type": "string" },
|
||||||
|
"indent": { "type": "number" },
|
||||||
|
"numberType": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["alphaLcParenBoth", "alphaLcParenR", "alphaLcPeriod", "alphaUcParenBoth", "alphaUcParenR", "alphaUcPeriod", "arabicParenBoth", "arabicParenR", "arabicPeriod", "arabicPlain", "romanLcParenBoth", "romanLcParenR", "romanLcPeriod", "romanUcParenBoth", "romanUcParenR", "romanUcPeriod"]
|
||||||
|
},
|
||||||
|
"numberStartAt": { "type": "number" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"color": { "$ref": "#/definitions/color" },
|
||||||
|
"fontFace": { "type": "string" },
|
||||||
|
"fontSize": { "type": "number" },
|
||||||
|
"highlight": { "$ref": "#/definitions/hexColor" },
|
||||||
|
"italic": { "type": "boolean" },
|
||||||
|
"lang": { "type": "string" },
|
||||||
|
"textDirection": { "type": "string", "enum": ["horz", "vert", "vert270", "wordArtVert"] },
|
||||||
|
"transparency": { "type": "number", "minimum": 0, "maximum": 100 },
|
||||||
|
"underline": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"style": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["dash", "dashHeavy", "dashLong", "dashLongHeavy", "dbl", "dotDash", "dotDashHeave", "dotDotDash", "dotDotDashHeavy", "dotted", "dottedHeavy", "heavy", "none", "sng", "wavy", "wavyDbl", "wavyHeavy"]
|
||||||
|
},
|
||||||
|
"color": { "$ref": "#/definitions/color" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"valign": { "type": "string", "enum": ["top", "middle", "bottom"] },
|
||||||
|
"softBreakBefore": { "type": "boolean" },
|
||||||
|
"tabStops": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"position": { "type": "number" },
|
||||||
|
"alignment": { "type": "string", "enum": ["l", "r", "ctr", "dec"] }
|
||||||
|
},
|
||||||
|
"required": ["position"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "Base text properties shared by text objects and table cells"
|
||||||
|
},
|
||||||
|
"textOptions": {
|
||||||
|
"allOf": [
|
||||||
|
{ "$ref": "#/definitions/textBaseProps" },
|
||||||
|
{ "$ref": "#/definitions/position" },
|
||||||
|
{ "$ref": "#/definitions/shapeFill" },
|
||||||
|
{ "$ref": "#/definitions/dataOrPath" },
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"objectName": { "type": "string" },
|
||||||
|
"baseline": { "type": "number" },
|
||||||
|
"charSpacing": { "type": "number" },
|
||||||
|
"fit": { "type": "string", "enum": ["none", "shrink", "resize"] },
|
||||||
|
"flipH": { "type": "boolean" },
|
||||||
|
"flipV": { "type": "boolean" },
|
||||||
|
"glow": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"color": { "$ref": "#/definitions/hexColor" },
|
||||||
|
"opacity": { "type": "number" },
|
||||||
|
"size": { "type": "number" }
|
||||||
|
},
|
||||||
|
"required": ["opacity", "size"]
|
||||||
|
},
|
||||||
|
"hyperlink": { "$ref": "#/definitions/hyperlink" },
|
||||||
|
"indentLevel": { "type": "number" },
|
||||||
|
"isTextBox": { "type": "boolean" },
|
||||||
|
"line": { "$ref": "#/definitions/shapeLine" },
|
||||||
|
"lineSpacing": { "type": "number" },
|
||||||
|
"lineSpacingMultiple": { "type": "number", "minimum": 0, "maximum": 9.99 },
|
||||||
|
"margin": { "$ref": "#/definitions/margin" },
|
||||||
|
"outline": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"color": { "$ref": "#/definitions/color" },
|
||||||
|
"size": { "type": "number" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"paraSpaceAfter": { "type": "number" },
|
||||||
|
"paraSpaceBefore": { "type": "number" },
|
||||||
|
"placeholder": { "type": "string" },
|
||||||
|
"rectRadius": { "type": "number" },
|
||||||
|
"rotate": { "type": "number" },
|
||||||
|
"rtlMode": { "type": "boolean" },
|
||||||
|
"shadow": { "$ref": "#/definitions/shadow" },
|
||||||
|
"shape": { "type": "string", "description": "Valid pptxgenjs shape name" },
|
||||||
|
"strike": { "oneOf": [{ "type": "boolean" }, { "type": "string", "enum": ["dblStrike", "sngStrike"] }] },
|
||||||
|
"subscript": { "type": "boolean" },
|
||||||
|
"superscript": { "type": "boolean" },
|
||||||
|
"vert": { "type": "string", "enum": ["eaVert", "horz", "mongolianVert", "vert", "vert270", "wordArtVert", "wordArtVertRtl"] },
|
||||||
|
"wrap": { "type": "boolean" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Full text object options including all TextPropsOptions parameters"
|
||||||
|
},
|
||||||
|
"textObject": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"type": { "type": "string", "pattern": "^text$" },
|
||||||
|
"text": { "type": "string", "description": "Text content" },
|
||||||
|
"options": { "$ref": "#/definitions/textOptions" }
|
||||||
|
},
|
||||||
|
"required": ["type", "text"],
|
||||||
|
"description": "Text slide object"
|
||||||
|
},
|
||||||
|
"shapeOptions": {
|
||||||
|
"allOf": [
|
||||||
|
{ "$ref": "#/definitions/position" },
|
||||||
|
{ "$ref": "#/definitions/shapeFill" },
|
||||||
|
{ "$ref": "#/definitions/shapeLine" },
|
||||||
|
{ "$ref": "#/definitions/shadow" },
|
||||||
|
{ "$ref": "#/definitions/hyperlink" },
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"objectName": { "type": "string" },
|
||||||
|
"align": { "type": "string", "enum": ["left", "center", "right", "justify"] },
|
||||||
|
"angleRange": {
|
||||||
|
"type": "array",
|
||||||
|
"items": { "type": "number" },
|
||||||
|
"minItems": 2,
|
||||||
|
"maxItems": 2
|
||||||
|
},
|
||||||
|
"arcThicknessRatio": { "type": "number" },
|
||||||
|
"flipH": { "type": "boolean" },
|
||||||
|
"flipV": { "type": "boolean" },
|
||||||
|
"points": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"x": { "$ref": "#/definitions/coord" },
|
||||||
|
"y": { "$ref": "#/definitions/coord" },
|
||||||
|
"moveTo": { "type": "boolean" }
|
||||||
|
},
|
||||||
|
"required": ["x", "y"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"x": { "$ref": "#/definitions/coord" },
|
||||||
|
"y": { "$ref": "#/definitions/coord" },
|
||||||
|
"curve": { "type": "object" }
|
||||||
|
},
|
||||||
|
"required": ["x", "y", "curve"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"close": { "type": "boolean", "const": true }
|
||||||
|
},
|
||||||
|
"required": ["close"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"rectRadius": { "type": "number", "description": "Rounded rectangle radius (0.0-1.0)" },
|
||||||
|
"rotate": { "type": "number", "description": "Rotation in degrees (-360 to 360)" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Shape object options"
|
||||||
|
},
|
||||||
|
"shapeObject": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"type": { "type": "string", "pattern": "^shape$" },
|
||||||
|
"shapeName": { "type": "string", "description": "Valid pptxgenjs shape type (e.g. 'rect', 'ellipse', 'roundRect')" },
|
||||||
|
"options": { "$ref": "#/definitions/shapeOptions" }
|
||||||
|
},
|
||||||
|
"required": ["type", "shapeName"],
|
||||||
|
"description": "Shape slide object"
|
||||||
|
},
|
||||||
|
"tableCell": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"text": { "type": "string" },
|
||||||
|
"options": {
|
||||||
|
"allOf": [
|
||||||
|
{ "$ref": "#/definitions/textBaseProps" },
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"autoPageCharWeight": { "type": "number", "minimum": -1.0, "maximum": 1.0, "description": "Auto-page character weight adjustment" },
|
||||||
|
"autoPageLineWeight": { "type": "number", "minimum": -1.0, "maximum": 1.0, "description": "Auto-page line weight adjustment" },
|
||||||
|
"border": {
|
||||||
|
"oneOf": [
|
||||||
|
{ "$ref": "#/definitions/borderProps" },
|
||||||
|
{ "type": "array", "items": { "$ref": "#/definitions/borderProps" }, "minItems": 4, "maxItems": 4 }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"colspan": { "type": "integer", "minimum": 1 },
|
||||||
|
"fill": { "$ref": "#/definitions/shapeFill" },
|
||||||
|
"hyperlink": { "$ref": "#/definitions/hyperlink" },
|
||||||
|
"margin": { "$ref": "#/definitions/margin" },
|
||||||
|
"rowspan": { "type": "integer", "minimum": 1 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "Table cell with text and styling options"
|
||||||
|
},
|
||||||
|
"tableOptions": {
|
||||||
|
"allOf": [
|
||||||
|
{ "$ref": "#/definitions/position" },
|
||||||
|
{ "$ref": "#/definitions/textBaseProps" },
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"objectName": { "type": "string" },
|
||||||
|
"autoPage": { "type": "boolean" },
|
||||||
|
"autoPageCharWeight": { "type": "number" },
|
||||||
|
"autoPageLineWeight": { "type": "number" },
|
||||||
|
"autoPageRepeatHeader": { "type": "boolean" },
|
||||||
|
"autoPageHeaderRows": { "type": "integer", "minimum": 1 },
|
||||||
|
"autoPageSlideStartY": { "type": "number" },
|
||||||
|
"border": {
|
||||||
|
"oneOf": [
|
||||||
|
{ "$ref": "#/definitions/borderProps" },
|
||||||
|
{ "type": "array", "items": { "$ref": "#/definitions/borderProps" }, "minItems": 4, "maxItems": 4 }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"colW": {
|
||||||
|
"oneOf": [
|
||||||
|
{ "type": "number" },
|
||||||
|
{ "type": "array", "items": { "type": "number" } }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"fill": { "$ref": "#/definitions/shapeFill" },
|
||||||
|
"margin": { "$ref": "#/definitions/margin" },
|
||||||
|
"rowH": {
|
||||||
|
"oneOf": [
|
||||||
|
{ "type": "number" },
|
||||||
|
{ "type": "array", "items": { "type": "number" } }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"verbose": { "type": "boolean", "description": "Verbose mode for auto-paging debug output" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Table options including all TableProps parameters"
|
||||||
|
},
|
||||||
|
"tableObject": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"type": { "type": "string", "pattern": "^table$" },
|
||||||
|
"rows": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "array",
|
||||||
|
"items": { "$ref": "#/definitions/tableCell" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"options": { "$ref": "#/definitions/tableOptions" }
|
||||||
|
},
|
||||||
|
"required": ["type", "rows"],
|
||||||
|
"description": "Table slide object"
|
||||||
|
},
|
||||||
|
"imageOptions": {
|
||||||
|
"allOf": [
|
||||||
|
{ "$ref": "#/definitions/position" },
|
||||||
|
{ "$ref": "#/definitions/dataOrPath" },
|
||||||
|
{ "$ref": "#/definitions/shadow" },
|
||||||
|
{ "$ref": "#/definitions/hyperlink" },
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"objectName": { "type": "string" },
|
||||||
|
"altText": { "type": "string" },
|
||||||
|
"flipH": { "type": "boolean" },
|
||||||
|
"flipV": { "type": "boolean" },
|
||||||
|
"placeholder": { "type": "string" },
|
||||||
|
"rotate": { "type": "number" },
|
||||||
|
"rounding": { "type": "boolean" },
|
||||||
|
"sizing": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"type": { "type": "string", "enum": ["contain", "cover", "crop"] },
|
||||||
|
"w": { "$ref": "#/definitions/coord" },
|
||||||
|
"h": { "$ref": "#/definitions/coord" },
|
||||||
|
"x": { "$ref": "#/definitions/coord" },
|
||||||
|
"y": { "$ref": "#/definitions/coord" }
|
||||||
|
},
|
||||||
|
"required": ["type", "w", "h"]
|
||||||
|
},
|
||||||
|
"transparency": { "type": "number", "minimum": 0, "maximum": 100 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Image options including all ImageProps parameters"
|
||||||
|
},
|
||||||
|
"imageObject": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"type": { "type": "string", "pattern": "^image$" },
|
||||||
|
"options": { "$ref": "#/definitions/imageOptions" }
|
||||||
|
},
|
||||||
|
"required": ["type", "options"],
|
||||||
|
"description": "Image slide object"
|
||||||
|
},
|
||||||
|
"chartData": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"name": { "type": "string", "description": "Series name" },
|
||||||
|
"labels": {
|
||||||
|
"oneOf": [
|
||||||
|
{ "type": "array", "items": { "type": "string" } },
|
||||||
|
{ "type": "array", "items": { "type": "array", "items": { "type": "string" } } }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"sizes": { "type": "array", "items": { "type": "number" }, "description": "Bubble sizes" },
|
||||||
|
"values": { "type": "array", "items": { "type": "number" }, "description": "Category values" }
|
||||||
|
},
|
||||||
|
"description": "Chart data series"
|
||||||
|
},
|
||||||
|
"optsChartGridLine": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"cap": { "type": "string", "enum": ["flat", "round", "square"] },
|
||||||
|
"color": { "$ref": "#/definitions/hexColor" },
|
||||||
|
"size": { "type": "number" },
|
||||||
|
"style": { "type": "string", "enum": ["solid", "dash", "dot", "none"] }
|
||||||
|
},
|
||||||
|
"description": "Chart grid line options"
|
||||||
|
},
|
||||||
|
"catAxisItem": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"hidden": { "type": "boolean" },
|
||||||
|
"labelColor": { "type": "string" },
|
||||||
|
"labelFontBold": { "type": "boolean" },
|
||||||
|
"labelFontFace": { "type": "string" },
|
||||||
|
"labelFontItalic": { "type": "boolean" },
|
||||||
|
"labelFontSize": { "type": "number" },
|
||||||
|
"labelFrequency": { "type": "string" },
|
||||||
|
"labelPos": { "type": "string", "enum": ["none", "low", "high", "nextTo"] },
|
||||||
|
"labelRotate": { "type": "number" },
|
||||||
|
"lineColor": { "type": "string" },
|
||||||
|
"lineShow": { "type": "boolean" },
|
||||||
|
"lineSize": { "type": "number" },
|
||||||
|
"lineStyle": { "type": "string", "enum": ["solid", "dash", "dot"] },
|
||||||
|
"majorTickMark": { "type": "string", "enum": ["none", "inside", "outside", "cross"] },
|
||||||
|
"majorTimeUnit": { "type": "string" },
|
||||||
|
"majorUnit": { "type": "number" },
|
||||||
|
"maxVal": { "type": "number" },
|
||||||
|
"minorTickMark": { "type": "string", "enum": ["none", "inside", "outside", "cross"] },
|
||||||
|
"minorTimeUnit": { "type": "string" },
|
||||||
|
"minorUnit": { "type": "number" },
|
||||||
|
"minVal": { "type": "number" },
|
||||||
|
"multiLevelLabels": { "type": "boolean" },
|
||||||
|
"orientation": { "type": "string", "enum": ["minMax"] },
|
||||||
|
"title": { "type": "string" },
|
||||||
|
"titleColor": { "type": "string" },
|
||||||
|
"titleFontFace": { "type": "string" },
|
||||||
|
"titleFontSize": { "type": "number" },
|
||||||
|
"titleRotate": { "type": "number" },
|
||||||
|
"gridLine": { "$ref": "#/definitions/optsChartGridLine" },
|
||||||
|
"labelFormatCode": { "type": "string" },
|
||||||
|
"baseTimeUnit": { "type": "string" },
|
||||||
|
"crossesAt": { "oneOf": [{ "type": "number" }, { "type": "string", "enum": ["autoZero"] }] }
|
||||||
|
},
|
||||||
|
"description": "Category axis item for catAxes array"
|
||||||
|
},
|
||||||
|
"valAxisItem": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"crossesAt": { "oneOf": [{ "type": "number" }, { "type": "string", "enum": ["autoZero"] }] },
|
||||||
|
"displayUnit": { "type": "string", "enum": ["billions", "hundredMillions", "hundreds", "hundredThousands", "millions", "tenMillions", "tenThousands", "thousands", "trillions"] },
|
||||||
|
"displayUnitLabel": { "type": "boolean" },
|
||||||
|
"hidden": { "type": "boolean" },
|
||||||
|
"labelColor": { "type": "string" },
|
||||||
|
"labelFontBold": { "type": "boolean" },
|
||||||
|
"labelFontFace": { "type": "string" },
|
||||||
|
"labelFontItalic": { "type": "boolean" },
|
||||||
|
"labelFontSize": { "type": "number" },
|
||||||
|
"labelFormatCode": { "type": "string" },
|
||||||
|
"labelPos": { "type": "string", "enum": ["none", "low", "high", "nextTo"] },
|
||||||
|
"labelRotate": { "type": "number" },
|
||||||
|
"lineColor": { "type": "string" },
|
||||||
|
"lineShow": { "type": "boolean" },
|
||||||
|
"lineSize": { "type": "number" },
|
||||||
|
"lineStyle": { "type": "string", "enum": ["solid", "dash", "dot"] },
|
||||||
|
"logScaleBase": { "type": "number", "minimum": 2, "maximum": 99 },
|
||||||
|
"majorTickMark": { "type": "string", "enum": ["none", "inside", "outside", "cross"] },
|
||||||
|
"majorUnit": { "type": "number" },
|
||||||
|
"maxVal": { "type": "number" },
|
||||||
|
"minorTickMark": { "type": "string", "enum": ["none", "inside", "outside", "cross"] },
|
||||||
|
"minVal": { "type": "number" },
|
||||||
|
"orientation": { "type": "string", "enum": ["minMax"] },
|
||||||
|
"title": { "type": "string" },
|
||||||
|
"titleColor": { "type": "string" },
|
||||||
|
"titleFontFace": { "type": "string" },
|
||||||
|
"titleFontSize": { "type": "number" },
|
||||||
|
"titleRotate": { "type": "number" },
|
||||||
|
"gridLine": { "$ref": "#/definitions/optsChartGridLine" }
|
||||||
|
},
|
||||||
|
"description": "Value axis item for valAxes array"
|
||||||
|
},
|
||||||
|
"chartOptions": {
|
||||||
|
"allOf": [
|
||||||
|
{ "$ref": "#/definitions/position" },
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"objectName": { "type": "string" },
|
||||||
|
"altText": { "type": "string" },
|
||||||
|
|
||||||
|
"axisPos": { "type": "string", "enum": ["b", "l", "r", "t"] },
|
||||||
|
"chartColors": { "type": "array", "items": { "$ref": "#/definitions/hexColor" } },
|
||||||
|
"chartColorsOpacity": { "type": "number" },
|
||||||
|
"dataBorder": { "$ref": "#/definitions/borderProps" },
|
||||||
|
"displayBlanksAs": { "type": "string", "enum": ["gap", "span", "zero"] },
|
||||||
|
"invertedColors": { "type": "array", "items": { "$ref": "#/definitions/hexColor" } },
|
||||||
|
"lang": { "type": "string" },
|
||||||
|
"layout": { "$ref": "#/definitions/position" },
|
||||||
|
"shadow": { "$ref": "#/definitions/shadow" },
|
||||||
|
"showLabel": { "type": "boolean" },
|
||||||
|
"showLeaderLines": { "type": "boolean" },
|
||||||
|
"showLegend": { "type": "boolean" },
|
||||||
|
"showPercent": { "type": "boolean" },
|
||||||
|
"showSerName": { "type": "boolean" },
|
||||||
|
"showTitle": { "type": "boolean" },
|
||||||
|
"showValue": { "type": "boolean" },
|
||||||
|
|
||||||
|
"v3DPerspective": { "type": "number", "minimum": 0, "maximum": 120 },
|
||||||
|
"v3DRAngAx": { "type": "boolean" },
|
||||||
|
"v3DRotX": { "type": "number", "minimum": 0, "maximum": 359.9 },
|
||||||
|
"v3DRotY": { "type": "number", "minimum": 0, "maximum": 359.9 },
|
||||||
|
|
||||||
|
"chartArea": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"border": { "$ref": "#/definitions/borderProps" },
|
||||||
|
"fill": { "$ref": "#/definitions/shapeFill" },
|
||||||
|
"roundedCorners": { "type": "boolean" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"plotArea": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"border": { "$ref": "#/definitions/borderProps" },
|
||||||
|
"fill": { "$ref": "#/definitions/shapeFill" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"catAxes": { "type": "array", "items": { "$ref": "#/definitions/catAxisItem" } },
|
||||||
|
"catAxisBaseTimeUnit": { "type": "string" },
|
||||||
|
"catAxisCrossesAt": { "oneOf": [{ "type": "number" }, { "type": "string", "enum": ["autoZero"] }] },
|
||||||
|
"catAxisHidden": { "type": "boolean" },
|
||||||
|
"catAxisLabelColor": { "type": "string" },
|
||||||
|
"catAxisLabelFontBold": { "type": "boolean" },
|
||||||
|
"catAxisLabelFontFace": { "type": "string" },
|
||||||
|
"catAxisLabelFontItalic": { "type": "boolean" },
|
||||||
|
"catAxisLabelFontSize": { "type": "number" },
|
||||||
|
"catAxisLabelFrequency": { "type": "string" },
|
||||||
|
"catAxisLabelPos": { "type": "string", "enum": ["none", "low", "high", "nextTo"] },
|
||||||
|
"catAxisLabelRotate": { "type": "number" },
|
||||||
|
"catAxisLineColor": { "type": "string" },
|
||||||
|
"catAxisLineShow": { "type": "boolean" },
|
||||||
|
"catAxisLineSize": { "type": "number" },
|
||||||
|
"catAxisLineStyle": { "type": "string", "enum": ["solid", "dash", "dot"] },
|
||||||
|
"catAxisMajorTickMark": { "type": "string", "enum": ["none", "inside", "outside", "cross"] },
|
||||||
|
"catAxisMajorTimeUnit": { "type": "string" },
|
||||||
|
"catAxisMajorUnit": { "type": "number" },
|
||||||
|
"catAxisMaxVal": { "type": "number" },
|
||||||
|
"catAxisMinorTickMark": { "type": "string", "enum": ["none", "inside", "outside", "cross"] },
|
||||||
|
"catAxisMinorTimeUnit": { "type": "string" },
|
||||||
|
"catAxisMinorUnit": { "type": "number" },
|
||||||
|
"catAxisMinVal": { "type": "number" },
|
||||||
|
"catAxisMultiLevelLabels": { "type": "boolean" },
|
||||||
|
"catAxisOrientation": { "type": "string", "enum": ["minMax"] },
|
||||||
|
"catAxisTitle": { "type": "string" },
|
||||||
|
"catAxisTitleColor": { "type": "string" },
|
||||||
|
"catAxisTitleFontFace": { "type": "string" },
|
||||||
|
"catAxisTitleFontSize": { "type": "number" },
|
||||||
|
"catAxisTitleRotate": { "type": "number" },
|
||||||
|
"catGridLine": { "$ref": "#/definitions/optsChartGridLine" },
|
||||||
|
"catLabelFormatCode": { "type": "string" },
|
||||||
|
"secondaryCatAxis": { "type": "boolean" },
|
||||||
|
"showCatAxisTitle": { "type": "boolean" },
|
||||||
|
|
||||||
|
"serAxisBaseTimeUnit": { "type": "string" },
|
||||||
|
"serAxisHidden": { "type": "boolean" },
|
||||||
|
"serAxisLabelColor": { "type": "string" },
|
||||||
|
"serAxisLabelFontBold": { "type": "boolean" },
|
||||||
|
"serAxisLabelFontFace": { "type": "string" },
|
||||||
|
"serAxisLabelFontItalic": { "type": "boolean" },
|
||||||
|
"serAxisLabelFontSize": { "type": "number" },
|
||||||
|
"serAxisLabelFrequency": { "type": "string" },
|
||||||
|
"serAxisLabelPos": { "type": "string", "enum": ["none", "low", "high", "nextTo"] },
|
||||||
|
"serAxisLineColor": { "type": "string" },
|
||||||
|
"serAxisLineShow": { "type": "boolean" },
|
||||||
|
"serAxisMajorTimeUnit": { "type": "string" },
|
||||||
|
"serAxisMajorUnit": { "type": "number" },
|
||||||
|
"serAxisMinorTimeUnit": { "type": "string" },
|
||||||
|
"serAxisMinorUnit": { "type": "number" },
|
||||||
|
"serAxisOrientation": { "type": "string", "enum": ["minMax"] },
|
||||||
|
"serAxisTitle": { "type": "string" },
|
||||||
|
"serAxisTitleColor": { "type": "string" },
|
||||||
|
"serAxisTitleFontFace": { "type": "string" },
|
||||||
|
"serAxisTitleFontSize": { "type": "number" },
|
||||||
|
"serAxisTitleRotate": { "type": "number" },
|
||||||
|
"serGridLine": { "$ref": "#/definitions/optsChartGridLine" },
|
||||||
|
"serLabelFormatCode": { "type": "string" },
|
||||||
|
"showSerAxisTitle": { "type": "boolean" },
|
||||||
|
|
||||||
|
"secondaryValAxis": { "type": "boolean" },
|
||||||
|
"showValAxisTitle": { "type": "boolean" },
|
||||||
|
"valAxes": { "type": "array", "items": { "$ref": "#/definitions/valAxisItem" } },
|
||||||
|
"valAxisCrossesAt": { "oneOf": [{ "type": "number" }, { "type": "string", "enum": ["autoZero"] }] },
|
||||||
|
"valAxisDisplayUnit": { "type": "string", "enum": ["billions", "hundredMillions", "hundreds", "hundredThousands", "millions", "tenMillions", "tenThousands", "thousands", "trillions"] },
|
||||||
|
"valAxisDisplayUnitLabel": { "type": "boolean" },
|
||||||
|
"valAxisHidden": { "type": "boolean" },
|
||||||
|
"valAxisLabelColor": { "type": "string" },
|
||||||
|
"valAxisLabelFontBold": { "type": "boolean" },
|
||||||
|
"valAxisLabelFontFace": { "type": "string" },
|
||||||
|
"valAxisLabelFontItalic": { "type": "boolean" },
|
||||||
|
"valAxisLabelFontSize": { "type": "number" },
|
||||||
|
"valAxisLabelFormatCode": { "type": "string" },
|
||||||
|
"valAxisLabelPos": { "type": "string", "enum": ["none", "low", "high", "nextTo"] },
|
||||||
|
"valAxisLabelRotate": { "type": "number" },
|
||||||
|
"valAxisLineColor": { "type": "string" },
|
||||||
|
"valAxisLineShow": { "type": "boolean" },
|
||||||
|
"valAxisLineSize": { "type": "number" },
|
||||||
|
"valAxisLineStyle": { "type": "string", "enum": ["solid", "dash", "dot"] },
|
||||||
|
"valAxisLogScaleBase": { "type": "number", "minimum": 2, "maximum": 99 },
|
||||||
|
"valAxisMajorTickMark": { "type": "string", "enum": ["none", "inside", "outside", "cross"] },
|
||||||
|
"valAxisMajorUnit": { "type": "number" },
|
||||||
|
"valAxisMaxVal": { "type": "number" },
|
||||||
|
"valAxisMinorTickMark": { "type": "string", "enum": ["none", "inside", "outside", "cross"] },
|
||||||
|
"valAxisMinVal": { "type": "number" },
|
||||||
|
"valAxisOrientation": { "type": "string", "enum": ["minMax"] },
|
||||||
|
"valAxisTitle": { "type": "string" },
|
||||||
|
"valAxisTitleColor": { "type": "string" },
|
||||||
|
"valAxisTitleFontFace": { "type": "string" },
|
||||||
|
"valAxisTitleFontSize": { "type": "number" },
|
||||||
|
"valAxisTitleRotate": { "type": "number" },
|
||||||
|
"valGridLine": { "$ref": "#/definitions/optsChartGridLine" },
|
||||||
|
"valLabelFormatCode": { "type": "string" },
|
||||||
|
|
||||||
|
"bar3DShape": { "type": "string", "enum": ["box", "cylinder", "cone", "pyramid"] },
|
||||||
|
"barDir": { "type": "string", "enum": ["bar", "col"] },
|
||||||
|
"barGapDepthPct": { "type": "number" },
|
||||||
|
"barGapWidthPct": { "type": "number" },
|
||||||
|
"barGrouping": { "type": "string", "enum": ["clustered", "stacked", "standard"] },
|
||||||
|
"barOverlapPct": { "type": "number" },
|
||||||
|
|
||||||
|
"dataNoEffects": { "type": "boolean" },
|
||||||
|
"holeSize": { "type": "number" },
|
||||||
|
|
||||||
|
"lineCap": { "type": "string", "enum": ["flat", "round", "square"] },
|
||||||
|
"lineDash": { "type": "string", "enum": ["dash", "dashDot", "lgDash", "lgDashDot", "lgDashDotDot", "solid", "sysDash", "sysDot"] },
|
||||||
|
"lineDataSymbol": { "type": "string", "enum": ["circle", "dash", "diamond", "dot", "none", "square", "triangle"] },
|
||||||
|
"lineDataSymbolLineColor": { "type": "string" },
|
||||||
|
"lineDataSymbolLineSize": { "type": "number" },
|
||||||
|
"lineDataSymbolSize": { "type": "number" },
|
||||||
|
"lineSize": { "type": "number" },
|
||||||
|
"lineSmooth": { "type": "boolean" },
|
||||||
|
|
||||||
|
"firstSliceAng": { "type": "number", "minimum": 0, "maximum": 359 },
|
||||||
|
|
||||||
|
"radarStyle": { "type": "string", "enum": ["standard", "marker", "filled"] },
|
||||||
|
|
||||||
|
"dataLabelBkgrdColors": { "type": "boolean" },
|
||||||
|
"dataLabelColor": { "type": "string" },
|
||||||
|
"dataLabelFontBold": { "type": "boolean" },
|
||||||
|
"dataLabelFontFace": { "type": "string" },
|
||||||
|
"dataLabelFontItalic": { "type": "boolean" },
|
||||||
|
"dataLabelFontSize": { "type": "number" },
|
||||||
|
"dataLabelFormatCode": { "type": "string" },
|
||||||
|
"dataLabelFormatScatter": { "type": "string", "enum": ["custom", "customXY", "XY"] },
|
||||||
|
"dataLabelPosition": { "type": "string", "enum": ["b", "bestFit", "ctr", "l", "r", "t", "inEnd", "outEnd"] },
|
||||||
|
|
||||||
|
"dataTableFontSize": { "type": "number" },
|
||||||
|
"dataTableFormatCode": { "type": "string" },
|
||||||
|
"showDataTable": { "type": "boolean" },
|
||||||
|
"showDataTableHorzBorder": { "type": "boolean" },
|
||||||
|
"showDataTableKeys": { "type": "boolean" },
|
||||||
|
"showDataTableOutline": { "type": "boolean" },
|
||||||
|
"showDataTableVertBorder": { "type": "boolean" },
|
||||||
|
|
||||||
|
"legendColor": { "type": "string" },
|
||||||
|
"legendFontFace": { "type": "string" },
|
||||||
|
"legendFontSize": { "type": "number" },
|
||||||
|
"legendPos": { "type": "string", "enum": ["b", "l", "r", "t", "tr"] },
|
||||||
|
|
||||||
|
"title": { "type": "string" },
|
||||||
|
"titleAlign": { "type": "string", "enum": ["l", "ctr", "r"] },
|
||||||
|
"titleBold": { "type": "boolean" },
|
||||||
|
"titleColor": { "type": "string" },
|
||||||
|
"titleFontFace": { "type": "string" },
|
||||||
|
"titleFontSize": { "type": "number" },
|
||||||
|
"titlePos": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"x": { "type": "number" },
|
||||||
|
"y": { "type": "number" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"titleRotate": { "type": "number" },
|
||||||
|
|
||||||
|
"cap": { "type": "string", "enum": ["flat", "round", "square"] },
|
||||||
|
"color": { "$ref": "#/definitions/hexColor" },
|
||||||
|
"size": { "type": "number" },
|
||||||
|
"style": { "type": "string", "enum": ["solid", "dash", "dot", "none"] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Chart options including all IChartOpts parameters"
|
||||||
|
},
|
||||||
|
"chartObject": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"type": { "type": "string", "pattern": "^chart$" },
|
||||||
|
"chartType": { "type": "string", "enum": ["area", "bar", "bar3D", "bubble", "doughnut", "line", "pie", "radar", "scatter"] },
|
||||||
|
"data": { "type": "array", "items": { "$ref": "#/definitions/chartData" } },
|
||||||
|
"options": { "$ref": "#/definitions/chartOptions" }
|
||||||
|
},
|
||||||
|
"required": ["type", "chartType", "data"],
|
||||||
|
"description": "Chart slide object"
|
||||||
|
},
|
||||||
|
"slideNumberProps": {
|
||||||
|
"allOf": [
|
||||||
|
{ "$ref": "#/definitions/position" },
|
||||||
|
{ "$ref": "#/definitions/textBaseProps" },
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"margin": { "$ref": "#/definitions/margin" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Slide number display properties"
|
||||||
|
},
|
||||||
|
"backgroundProps": {
|
||||||
|
"allOf": [
|
||||||
|
{ "$ref": "#/definitions/dataOrPath" },
|
||||||
|
{ "$ref": "#/definitions/shapeFill" },
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"fill": { "$ref": "#/definitions/hexColor", "description": "Deprecated: use shapeFill.color instead" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Slide background properties (color or image with DataOrPathProps + ShapeFillProps)"
|
||||||
|
},
|
||||||
|
"slideObject": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"background": { "$ref": "#/definitions/backgroundProps" },
|
||||||
|
"color": { "$ref": "#/definitions/hexColor", "description": "Default text color for this slide" },
|
||||||
|
"hidden": { "type": "boolean", "description": "Whether slide is hidden" },
|
||||||
|
"notes": { "type": "string", "description": "Speaker notes for this slide" },
|
||||||
|
"slideNumber": { "$ref": "#/definitions/slideNumberProps" },
|
||||||
|
"objects": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"oneOf": [
|
||||||
|
{ "$ref": "#/definitions/textObject" },
|
||||||
|
{ "$ref": "#/definitions/shapeObject" },
|
||||||
|
{ "$ref": "#/definitions/tableObject" },
|
||||||
|
{ "$ref": "#/definitions/imageObject" },
|
||||||
|
{ "$ref": "#/definitions/chartObject" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "A single slide with background, properties, and content objects"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"properties": {
|
||||||
|
"presentation": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"layout": { "type": "string", "description": "Presentation layout (e.g. LAYOUT_16x9, LAYOUT_4x3)" },
|
||||||
|
"author": { "type": "string" },
|
||||||
|
"title": { "type": "string" },
|
||||||
|
"subject": { "type": "string" },
|
||||||
|
"company": { "type": "string" },
|
||||||
|
"revision": { "type": "string" },
|
||||||
|
"rtlMode": { "type": "boolean" },
|
||||||
|
"masterSlide": { "type": "object", "description": "Master slide reference object" },
|
||||||
|
"presLayout": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"name": { "type": "string", "description": "Layout name (e.g. LAYOUT_16x9)" },
|
||||||
|
"width": { "type": "number", "description": "Slide width in inches" },
|
||||||
|
"height": { "type": "number", "description": "Slide height in inches" }
|
||||||
|
},
|
||||||
|
"description": "Presentation layout dimensions"
|
||||||
|
},
|
||||||
|
"theme": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"headFontFace": { "type": "string" },
|
||||||
|
"bodyFontFace": { "type": "string" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "Presentation-level metadata and layout settings"
|
||||||
|
},
|
||||||
|
"slides": {
|
||||||
|
"type": "array",
|
||||||
|
"items": { "$ref": "#/definitions/slideObject" },
|
||||||
|
"minItems": 1,
|
||||||
|
"description": "Array of slides in the presentation"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["slides"],
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
# Web to Pixso
|
||||||
|
|
||||||
|
一套对标 `figma-capture-extension` 使用体验的网页采集工具,包含 Chrome 扩展和 Pixso 导入插件。
|
||||||
|
|
||||||
|
## 目录
|
||||||
|
|
||||||
|
- `manifest.json`、`popup.*`、`background.js`、`capture.js`、`runner.js`:Chrome 扩展
|
||||||
|
- `pixso-plugin/`:Pixso 插件,用于导入扩展导出的 JSON 文件
|
||||||
|
- `logo/`:扩展与插件图标
|
||||||
|
|
||||||
|
## 使用
|
||||||
|
|
||||||
|
1. 打开 `chrome://extensions/`,开启开发者模式。
|
||||||
|
2. 点击“加载已解压的扩展程序”,选择本目录 `web-to-pixso`。
|
||||||
|
3. 打开要采集的网页,点击扩展图标,按需开启“跨域图片代理模式”,点击“开始采集”。
|
||||||
|
4. 扩展会下载一个 `web-to-pixso/*.json` 文件。
|
||||||
|
5. 在 Pixso 中导入 `pixso-plugin/manifest.json`,运行插件并选择上一步下载的 JSON 文件。若旧版导入器要求 `plugin.json`,目录内也保留了同内容兼容文件。
|
||||||
|
|
||||||
|
## 数据格式
|
||||||
|
|
||||||
|
扩展导出的文件格式为 `pixso-design-capture`,包含页面来源、画布尺寸、DOM 节点树、图片资源、字体和诊断信息。Pixso 插件会尽量还原:
|
||||||
|
|
||||||
|
- 文本图层
|
||||||
|
- 图片和背景图片
|
||||||
|
- 背景色、边框、圆角、透明度
|
||||||
|
- DOM 层级和基础坐标
|
||||||
|
|
||||||
|
## 注意
|
||||||
|
|
||||||
|
网页到设计稿的转换无法做到 100% 语义等价,复杂 CSS、canvas、视频帧、伪元素和部分字体效果可能需要在 Pixso 中二次微调。
|
||||||
@@ -0,0 +1,419 @@
|
|||||||
|
const CAPTURE_FILE = "capture.js";
|
||||||
|
const RUNNER_FILE = "runner.js";
|
||||||
|
const POPUP_PANEL_FILE = "popup-panel.js";
|
||||||
|
const ELEMENT_PICKER_FILE = "element-picker.js";
|
||||||
|
const SETTINGS_KEY = "webToPixsoSettings";
|
||||||
|
const DEFAULT_SETTINGS = {
|
||||||
|
useProxy: false,
|
||||||
|
concurrency: "8",
|
||||||
|
captureMode: "mixed",
|
||||||
|
captureWidth: null
|
||||||
|
};
|
||||||
|
const MIN_CAPTURE_WIDTH = 320;
|
||||||
|
const MAX_CAPTURE_WIDTH = 3840;
|
||||||
|
|
||||||
|
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
|
||||||
|
|
||||||
|
function normalizeSettings(value = {}) {
|
||||||
|
const concurrency = String(value.concurrency || DEFAULT_SETTINGS.concurrency);
|
||||||
|
const captureWidth = normalizeCaptureWidth(value.captureWidth, null);
|
||||||
|
return {
|
||||||
|
useProxy: Boolean(value.useProxy),
|
||||||
|
concurrency: ["4", "6", "8", "10", "12", "16", "20", "infinite"].includes(concurrency)
|
||||||
|
? concurrency
|
||||||
|
: DEFAULT_SETTINGS.concurrency,
|
||||||
|
captureMode: value.captureMode === "editable" ? "editable" : "mixed",
|
||||||
|
captureWidth
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeCaptureWidth(value, fallback = null) {
|
||||||
|
const number = Number.parseInt(String(value || "").replace(/\D+/g, ""), 10);
|
||||||
|
if (!Number.isFinite(number)) return fallback;
|
||||||
|
return Math.max(MIN_CAPTURE_WIDTH, Math.min(MAX_CAPTURE_WIDTH, number));
|
||||||
|
}
|
||||||
|
|
||||||
|
function definedWindowBounds(bounds = {}) {
|
||||||
|
return Object.fromEntries(
|
||||||
|
Object.entries(bounds).filter(([, value]) => Number.isFinite(value))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function assertCaptureableTab(tab) {
|
||||||
|
if (!tab?.id || !tab.url) {
|
||||||
|
throw new Error("没有可采集的当前标签页");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (/^(chrome|edge|about|devtools|chrome-extension):/i.test(tab.url)) {
|
||||||
|
throw new Error("浏览器内置页面不支持采集,请切换到普通网页");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getActiveTab() {
|
||||||
|
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
|
||||||
|
assertCaptureableTab(tab);
|
||||||
|
return tab;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function runCapture(tabId, options) {
|
||||||
|
await chrome.scripting.executeScript({
|
||||||
|
target: { tabId },
|
||||||
|
files: [CAPTURE_FILE]
|
||||||
|
});
|
||||||
|
|
||||||
|
await chrome.scripting.executeScript({
|
||||||
|
target: { tabId },
|
||||||
|
files: [RUNNER_FILE]
|
||||||
|
});
|
||||||
|
|
||||||
|
const [{ result }] = await chrome.scripting.executeScript({
|
||||||
|
target: { tabId },
|
||||||
|
func: captureOptions => window.__webToPixsoRunCapture(captureOptions),
|
||||||
|
args: [options]
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
throw new Error("页面没有返回采集结果");
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getTabViewportWidth(tabId) {
|
||||||
|
try {
|
||||||
|
const [{ result }] = await chrome.scripting.executeScript({
|
||||||
|
target: { tabId },
|
||||||
|
func: () => Math.round(window.innerWidth || document.documentElement.clientWidth || 0)
|
||||||
|
});
|
||||||
|
return normalizeCaptureWidth(result, null);
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function prepareCaptureViewport(tab, requestedWidth) {
|
||||||
|
const targetWidth = normalizeCaptureWidth(requestedWidth, null);
|
||||||
|
const beforeViewportWidth = await getTabViewportWidth(tab.id);
|
||||||
|
const noop = async () => {};
|
||||||
|
if (!targetWidth || !beforeViewportWidth || Math.abs(beforeViewportWidth - targetWidth) <= 2) {
|
||||||
|
return {
|
||||||
|
restore: noop,
|
||||||
|
requestedWidth: targetWidth || beforeViewportWidth,
|
||||||
|
beforeViewportWidth,
|
||||||
|
actualViewportWidth: beforeViewportWidth,
|
||||||
|
resizedWindow: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!tab.windowId || !chrome.windows?.get || !chrome.windows?.update) {
|
||||||
|
throw new Error("当前浏览器不支持临时调整采集视口宽度");
|
||||||
|
}
|
||||||
|
|
||||||
|
const originalWindow = await chrome.windows.get(tab.windowId);
|
||||||
|
const originalState = originalWindow.state || "normal";
|
||||||
|
const originalBounds = {
|
||||||
|
left: originalWindow.left,
|
||||||
|
top: originalWindow.top,
|
||||||
|
width: originalWindow.width,
|
||||||
|
height: originalWindow.height
|
||||||
|
};
|
||||||
|
|
||||||
|
const restore = async () => {
|
||||||
|
try {
|
||||||
|
if (originalState !== "normal") {
|
||||||
|
await chrome.windows.update(tab.windowId, { state: "normal" });
|
||||||
|
await delay(120);
|
||||||
|
}
|
||||||
|
const restoreBounds = definedWindowBounds(originalBounds);
|
||||||
|
if (Object.keys(restoreBounds).length) {
|
||||||
|
await chrome.windows.update(tab.windowId, restoreBounds);
|
||||||
|
}
|
||||||
|
if (originalState !== "normal") {
|
||||||
|
await delay(120);
|
||||||
|
await chrome.windows.update(tab.windowId, { state: originalState });
|
||||||
|
}
|
||||||
|
await delay(250);
|
||||||
|
} catch {
|
||||||
|
// Restoring the user's window is best effort only.
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (originalState !== "normal") {
|
||||||
|
await chrome.windows.update(tab.windowId, { state: "normal" });
|
||||||
|
await delay(250);
|
||||||
|
}
|
||||||
|
|
||||||
|
let currentViewportWidth = beforeViewportWidth;
|
||||||
|
let currentWindow = await chrome.windows.get(tab.windowId);
|
||||||
|
for (let attempt = 0; attempt < 2; attempt += 1) {
|
||||||
|
const delta = targetWidth - currentViewportWidth;
|
||||||
|
const nextWidth = Math.max(360, Math.round((currentWindow.width || targetWidth) + delta));
|
||||||
|
await chrome.windows.update(tab.windowId, { width: nextWidth });
|
||||||
|
await delay(650);
|
||||||
|
currentViewportWidth = await getTabViewportWidth(tab.id) || currentViewportWidth;
|
||||||
|
if (Math.abs(currentViewportWidth - targetWidth) <= 2) break;
|
||||||
|
currentWindow = await chrome.windows.get(tab.windowId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Math.abs(currentViewportWidth - targetWidth) > 2) {
|
||||||
|
throw new Error(`采集视口未生效:目标 ${targetWidth}px,实际 ${currentViewportWidth}px。请退出全屏或手动放宽浏览器窗口后重试。`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
restore,
|
||||||
|
requestedWidth: targetWidth,
|
||||||
|
beforeViewportWidth,
|
||||||
|
actualViewportWidth: currentViewportWidth,
|
||||||
|
resizedWindow: true
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
await restore();
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function captureCurrentTab(tab, settings) {
|
||||||
|
const viewport = await prepareCaptureViewport(tab, settings.captureWidth);
|
||||||
|
try {
|
||||||
|
const data = await runCapture(tab.id, {
|
||||||
|
...settings,
|
||||||
|
captureWidth: viewport.requestedWidth
|
||||||
|
});
|
||||||
|
data.capture = {
|
||||||
|
...(data.capture || {}),
|
||||||
|
resizedWindow: viewport.resizedWindow,
|
||||||
|
usedTemporaryWindow: viewport.resizedWindow,
|
||||||
|
requestedWidth: viewport.requestedWidth || data.source?.actualViewportWidth || data.canvas?.width,
|
||||||
|
beforeViewportWidth: viewport.beforeViewportWidth,
|
||||||
|
actualViewportWidth: data.source?.actualViewportWidth || viewport.actualViewportWidth
|
||||||
|
};
|
||||||
|
return data;
|
||||||
|
} finally {
|
||||||
|
await viewport.restore();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function downloadCapture(data) {
|
||||||
|
const json = JSON.stringify(data, null, 2);
|
||||||
|
const encodedJson = arrayBufferToBase64(new TextEncoder().encode(json));
|
||||||
|
const url = `data:application/json;charset=utf-8;base64,${encodedJson}`;
|
||||||
|
const title = data.source?.title || "webpage";
|
||||||
|
const safeTitle = title
|
||||||
|
.replace(/[\\/:*?"<>|]+/g, "-")
|
||||||
|
.replace(/\s+/g, "-")
|
||||||
|
.slice(0, 64) || "webpage";
|
||||||
|
const filename = `web-to-pixso/${safeTitle}-${Date.now()}.json`;
|
||||||
|
|
||||||
|
await chrome.downloads.download({
|
||||||
|
url,
|
||||||
|
filename,
|
||||||
|
saveAs: true
|
||||||
|
});
|
||||||
|
|
||||||
|
return filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function startCapture(options) {
|
||||||
|
const settings = normalizeSettings(options);
|
||||||
|
await chrome.storage.local.set({ [SETTINGS_KEY]: settings });
|
||||||
|
const tab = await getActiveTab();
|
||||||
|
const data = await captureCurrentTab(tab, settings);
|
||||||
|
const filename = await downloadCapture(data);
|
||||||
|
return {
|
||||||
|
ok: true,
|
||||||
|
filename,
|
||||||
|
actualViewportWidth: data.source?.actualViewportWidth,
|
||||||
|
requestedViewportWidth: data.source?.requestedViewportWidth,
|
||||||
|
usedTemporaryWindow: Boolean(data.capture?.usedTemporaryWindow)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function startCaptureFromSender(options, sender) {
|
||||||
|
const settings = normalizeSettings(options);
|
||||||
|
await chrome.storage.local.set({ [SETTINGS_KEY]: settings });
|
||||||
|
const tab = sender?.tab || await getActiveTab();
|
||||||
|
assertCaptureableTab(tab);
|
||||||
|
const data = await captureCurrentTab(tab, settings);
|
||||||
|
const filename = await downloadCapture(data);
|
||||||
|
return {
|
||||||
|
ok: true,
|
||||||
|
filename,
|
||||||
|
actualViewportWidth: data.source?.actualViewportWidth,
|
||||||
|
requestedViewportWidth: data.source?.requestedViewportWidth,
|
||||||
|
usedTemporaryWindow: Boolean(data.capture?.usedTemporaryWindow)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function captureClipboardFromSender(options, sender) {
|
||||||
|
const settings = normalizeSettings(options);
|
||||||
|
await chrome.storage.local.set({ [SETTINGS_KEY]: settings });
|
||||||
|
const tab = sender?.tab || await getActiveTab();
|
||||||
|
assertCaptureableTab(tab);
|
||||||
|
const data = await captureCurrentTab(tab, settings);
|
||||||
|
return {
|
||||||
|
ok: true,
|
||||||
|
json: JSON.stringify(data, null, 2),
|
||||||
|
actualViewportWidth: data.source?.actualViewportWidth,
|
||||||
|
requestedViewportWidth: data.source?.requestedViewportWidth,
|
||||||
|
usedTemporaryWindow: Boolean(data.capture?.usedTemporaryWindow)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function startElementCapture(options, sender) {
|
||||||
|
const settings = {
|
||||||
|
...normalizeSettings(options),
|
||||||
|
captureWidth: null,
|
||||||
|
selectionId: options?.selectionId,
|
||||||
|
selectionWidth: Math.max(1, Math.round(Number(options?.selectionWidth || 0))),
|
||||||
|
captureMode: "mixed"
|
||||||
|
};
|
||||||
|
const tab = sender?.tab || await getActiveTab();
|
||||||
|
assertCaptureableTab(tab);
|
||||||
|
const data = await runCapture(tab.id, settings);
|
||||||
|
const filename = await downloadCapture(data);
|
||||||
|
return {
|
||||||
|
ok: true,
|
||||||
|
filename,
|
||||||
|
actualViewportWidth: data.source?.actualViewportWidth,
|
||||||
|
requestedViewportWidth: data.source?.requestedViewportWidth,
|
||||||
|
selectionWidth: data.import?.defaultWidth
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchWithTimeout(url, timeout = 10000) {
|
||||||
|
const controller = new AbortController();
|
||||||
|
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
||||||
|
|
||||||
|
try {
|
||||||
|
return await fetch(url, {
|
||||||
|
signal: controller.signal,
|
||||||
|
credentials: "include",
|
||||||
|
cache: "force-cache"
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function arrayBufferToBase64(buffer) {
|
||||||
|
const bytes = new Uint8Array(buffer);
|
||||||
|
const chunkSize = 0x8000;
|
||||||
|
let binary = "";
|
||||||
|
|
||||||
|
for (let index = 0; index < bytes.length; index += chunkSize) {
|
||||||
|
binary += String.fromCharCode(...bytes.subarray(index, index + chunkSize));
|
||||||
|
}
|
||||||
|
|
||||||
|
return btoa(binary);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function proxyFetchAsset(url) {
|
||||||
|
if (!/^https?:\/\//i.test(url)) {
|
||||||
|
return { ok: false, error: "仅支持 http/https 图片代理" };
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetchWithTimeout(url, 12000);
|
||||||
|
if (!response.ok) {
|
||||||
|
return { ok: false, status: response.status, error: `HTTP ${response.status}` };
|
||||||
|
}
|
||||||
|
|
||||||
|
const contentType = response.headers.get("content-type") || "application/octet-stream";
|
||||||
|
const buffer = await response.arrayBuffer();
|
||||||
|
|
||||||
|
return {
|
||||||
|
ok: true,
|
||||||
|
status: response.status,
|
||||||
|
contentType,
|
||||||
|
base64: arrayBufferToBase64(buffer)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function captureVisibleTab(sender) {
|
||||||
|
if (!sender?.tab?.windowId || !chrome.tabs?.captureVisibleTab) {
|
||||||
|
return { ok: false, error: "当前标签页截图不可用" };
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const dataUrl = await chrome.tabs.captureVisibleTab(sender.tab.windowId, {
|
||||||
|
format: "png"
|
||||||
|
});
|
||||||
|
return { ok: true, dataUrl };
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
ok: false,
|
||||||
|
error: error.message || String(error),
|
||||||
|
nonFatal: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function showInPagePanel(tab) {
|
||||||
|
assertCaptureableTab(tab);
|
||||||
|
await chrome.scripting.executeScript({
|
||||||
|
target: { tabId: tab.id },
|
||||||
|
files: [ELEMENT_PICKER_FILE, POPUP_PANEL_FILE]
|
||||||
|
});
|
||||||
|
await chrome.scripting.executeScript({
|
||||||
|
target: { tabId: tab.id },
|
||||||
|
func: () => window.__webToPixsoShowPanel?.()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
chrome.runtime.onInstalled.addListener(() => {
|
||||||
|
chrome.storage.local.get({ [SETTINGS_KEY]: DEFAULT_SETTINGS }).then(result => {
|
||||||
|
chrome.storage.local.set({ [SETTINGS_KEY]: normalizeSettings(result[SETTINGS_KEY]) });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
chrome.action.onClicked.addListener(tab => {
|
||||||
|
showInPagePanel(tab).catch(error => {
|
||||||
|
console.warn("[Web to Pixso] Failed to open panel", error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
||||||
|
if (message?.type === "PIXSO_CAPTURE_START") {
|
||||||
|
startCapture(message.options)
|
||||||
|
.then(sendResponse)
|
||||||
|
.catch(error => sendResponse({ ok: false, error: error.message || String(error) }));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message?.type === "PIXSO_CAPTURE_ELEMENT_START") {
|
||||||
|
startElementCapture(message.options, sender)
|
||||||
|
.then(sendResponse)
|
||||||
|
.catch(error => sendResponse({ ok: false, error: error.message || String(error) }));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message?.type === "PIXSO_CAPTURE_CURRENT_TAB") {
|
||||||
|
startCaptureFromSender(message.options, sender)
|
||||||
|
.then(sendResponse)
|
||||||
|
.catch(error => sendResponse({ ok: false, error: error.message || String(error) }));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message?.type === "PIXSO_CAPTURE_CLIPBOARD") {
|
||||||
|
captureClipboardFromSender(message.options, sender)
|
||||||
|
.then(sendResponse)
|
||||||
|
.catch(error => sendResponse({ ok: false, error: error.message || String(error) }));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message?.type === "PIXSO_CAPTURE_FETCH_ASSET") {
|
||||||
|
proxyFetchAsset(message.url)
|
||||||
|
.then(sendResponse)
|
||||||
|
.catch(error => sendResponse({ ok: false, error: error.message || String(error) }));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message?.type === "PIXSO_CAPTURE_VISIBLE_TAB") {
|
||||||
|
captureVisibleTab(sender)
|
||||||
|
.then(sendResponse)
|
||||||
|
.catch(error => sendResponse({ ok: false, error: error.message || String(error) }));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
@@ -0,0 +1,455 @@
|
|||||||
|
(function () {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const ROOT_ID = "__web_to_pixso_picker_root__";
|
||||||
|
const BOX_ID = "__web_to_pixso_picker_box__";
|
||||||
|
const LABEL_ID = "__web_to_pixso_picker_label__";
|
||||||
|
const ATTR = "data-web-to-pixso-selection-id";
|
||||||
|
const STORAGE_KEY = "__web_to_pixso_island_position__";
|
||||||
|
|
||||||
|
let currentElement = null;
|
||||||
|
let currentSettings = null;
|
||||||
|
let mode = "toolbar";
|
||||||
|
let dragging = null;
|
||||||
|
|
||||||
|
function removePicker() {
|
||||||
|
document.getElementById(ROOT_ID)?.remove();
|
||||||
|
document.getElementById(BOX_ID)?.remove();
|
||||||
|
document.getElementById(LABEL_ID)?.remove();
|
||||||
|
document.removeEventListener("mousemove", onMouseMove, true);
|
||||||
|
document.removeEventListener("click", onClick, true);
|
||||||
|
document.removeEventListener("keydown", onKeyDown, true);
|
||||||
|
document.removeEventListener("pointermove", onDragMove, true);
|
||||||
|
document.removeEventListener("pointerup", onDragEnd, true);
|
||||||
|
currentElement = null;
|
||||||
|
mode = "toolbar";
|
||||||
|
dragging = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function elementName(element) {
|
||||||
|
if (!element) return "";
|
||||||
|
const tag = element.tagName ? element.tagName.toLowerCase() : "element";
|
||||||
|
const id = element.id ? `#${element.id}` : "";
|
||||||
|
const className = String(element.className || "")
|
||||||
|
.trim()
|
||||||
|
.split(/\s+/)
|
||||||
|
.filter(Boolean)
|
||||||
|
.slice(0, 2)
|
||||||
|
.map(item => `.${item}`)
|
||||||
|
.join("");
|
||||||
|
return `${tag}${id}${className}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function icon(type) {
|
||||||
|
if (type === "screen") {
|
||||||
|
return '<svg viewBox="0 0 24 24" aria-hidden="true"><rect x="3" y="5" width="18" height="14" rx="2"></rect><path d="M7 9h10"></path></svg>';
|
||||||
|
}
|
||||||
|
if (type === "select") {
|
||||||
|
return '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 3v3"></path><path d="M12 18v3"></path><path d="M3 12h3"></path><path d="M18 12h3"></path><path d="M5.6 5.6l2.1 2.1"></path><path d="M16.3 16.3l2.1 2.1"></path><path d="M18.4 5.6l-2.1 2.1"></path><path d="M7.7 16.3l-2.1 2.1"></path><path d="M12 9l2.2 6.1L16 13.2l2.8 2.8"></path></svg>';
|
||||||
|
}
|
||||||
|
if (type === "copy") {
|
||||||
|
return '<svg viewBox="0 0 24 24" aria-hidden="true"><rect x="8" y="8" width="12" height="12" rx="2"></rect><path d="M4 16V6a2 2 0 0 1 2-2h10"></path></svg>';
|
||||||
|
}
|
||||||
|
return '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M18 6 6 18"></path><path d="m6 6 12 12"></path></svg>';
|
||||||
|
}
|
||||||
|
|
||||||
|
function rootCss() {
|
||||||
|
return `
|
||||||
|
#${ROOT_ID} {
|
||||||
|
color-scheme: light;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
|
||||||
|
left: 50%;
|
||||||
|
position: fixed;
|
||||||
|
top: 24px;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
user-select: none;
|
||||||
|
z-index: 2147483647;
|
||||||
|
}
|
||||||
|
#${ROOT_ID} .w2p-island {
|
||||||
|
align-items: stretch;
|
||||||
|
background: rgba(36, 36, 38, 0.97);
|
||||||
|
border-radius: 22px;
|
||||||
|
box-shadow: 0 8px 24px rgba(0,0,0,.22);
|
||||||
|
color: white;
|
||||||
|
display: flex;
|
||||||
|
min-height: 56px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
#${ROOT_ID} .w2p-action,
|
||||||
|
#${ROOT_ID} .w2p-close,
|
||||||
|
#${ROOT_ID} .w2p-cancel {
|
||||||
|
align-items: center;
|
||||||
|
background: transparent;
|
||||||
|
border: 0;
|
||||||
|
color: white;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
font: inherit;
|
||||||
|
gap: 10px;
|
||||||
|
justify-content: center;
|
||||||
|
min-height: 56px;
|
||||||
|
padding: 0 18px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
#${ROOT_ID} .w2p-action {
|
||||||
|
border-right: 1px solid rgba(255,255,255,.14);
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 650;
|
||||||
|
}
|
||||||
|
#${ROOT_ID} .w2p-action:hover,
|
||||||
|
#${ROOT_ID} .w2p-close:hover,
|
||||||
|
#${ROOT_ID} .w2p-cancel:hover {
|
||||||
|
background: rgba(255,255,255,.1);
|
||||||
|
}
|
||||||
|
#${ROOT_ID} .w2p-action.active {
|
||||||
|
background: rgba(255,255,255,.12);
|
||||||
|
}
|
||||||
|
#${ROOT_ID} svg {
|
||||||
|
fill: none;
|
||||||
|
height: 20px;
|
||||||
|
stroke: currentColor;
|
||||||
|
stroke-linecap: round;
|
||||||
|
stroke-linejoin: round;
|
||||||
|
stroke-width: 2;
|
||||||
|
width: 20px;
|
||||||
|
}
|
||||||
|
#${ROOT_ID} .w2p-close {
|
||||||
|
min-width: 56px;
|
||||||
|
padding: 0 16px;
|
||||||
|
}
|
||||||
|
#${ROOT_ID} .w2p-status {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
min-height: 56px;
|
||||||
|
padding: 0 20px;
|
||||||
|
}
|
||||||
|
#${ROOT_ID} .w2p-text {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 650;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
#${ROOT_ID} .w2p-spinner {
|
||||||
|
animation: w2p-spin .9s linear infinite;
|
||||||
|
border: 2px solid rgba(255,255,255,.32);
|
||||||
|
border-radius: 50%;
|
||||||
|
border-top-color: #fff;
|
||||||
|
height: 20px;
|
||||||
|
width: 20px;
|
||||||
|
}
|
||||||
|
#${ROOT_ID} .w2p-cancel {
|
||||||
|
border-left: 1px solid rgba(255,255,255,.14);
|
||||||
|
font-size: 15px;
|
||||||
|
padding: 0 18px;
|
||||||
|
}
|
||||||
|
#${BOX_ID} {
|
||||||
|
background: rgba(46, 156, 255, .16);
|
||||||
|
border: 2px dashed #1593ff;
|
||||||
|
border-radius: 6px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: none;
|
||||||
|
left: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
z-index: 2147483646;
|
||||||
|
}
|
||||||
|
#${LABEL_ID} {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 6px;
|
||||||
|
box-shadow: 0 8px 22px rgba(0,0,0,.2);
|
||||||
|
color: #333;
|
||||||
|
display: none;
|
||||||
|
font: 14px/1.2 -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||||||
|
max-width: 280px;
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 8px 10px;
|
||||||
|
pointer-events: none;
|
||||||
|
position: fixed;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
z-index: 2147483647;
|
||||||
|
}
|
||||||
|
@keyframes w2p-spin { to { transform: rotate(360deg); } }
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureBase() {
|
||||||
|
document.getElementById(ROOT_ID)?.remove();
|
||||||
|
const root = document.createElement("div");
|
||||||
|
root.id = ROOT_ID;
|
||||||
|
const style = document.createElement("style");
|
||||||
|
style.textContent = rootCss();
|
||||||
|
root.appendChild(style);
|
||||||
|
document.documentElement.appendChild(root);
|
||||||
|
|
||||||
|
let box = document.getElementById(BOX_ID);
|
||||||
|
if (!box) {
|
||||||
|
box = document.createElement("div");
|
||||||
|
box.id = BOX_ID;
|
||||||
|
document.documentElement.appendChild(box);
|
||||||
|
}
|
||||||
|
|
||||||
|
let label = document.getElementById(LABEL_ID);
|
||||||
|
if (!label) {
|
||||||
|
label = document.createElement("div");
|
||||||
|
label.id = LABEL_ID;
|
||||||
|
document.documentElement.appendChild(label);
|
||||||
|
}
|
||||||
|
|
||||||
|
restorePosition(root);
|
||||||
|
root.addEventListener("pointerdown", onDragStart, true);
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
function restorePosition(root) {
|
||||||
|
try {
|
||||||
|
const saved = JSON.parse(sessionStorage.getItem(STORAGE_KEY) || "null");
|
||||||
|
if (saved && Number.isFinite(saved.left) && Number.isFinite(saved.top)) {
|
||||||
|
root.style.left = `${saved.left}px`;
|
||||||
|
root.style.top = `${saved.top}px`;
|
||||||
|
root.style.transform = "none";
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Keep the default centered position when storage is unavailable.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function savePosition(root) {
|
||||||
|
const rect = root.getBoundingClientRect();
|
||||||
|
try {
|
||||||
|
sessionStorage.setItem(STORAGE_KEY, JSON.stringify({
|
||||||
|
left: Math.round(rect.left),
|
||||||
|
top: Math.round(rect.top)
|
||||||
|
}));
|
||||||
|
} catch {
|
||||||
|
// Position persistence is only a convenience.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function clampIsland(root, snap) {
|
||||||
|
const rect = root.getBoundingClientRect();
|
||||||
|
let left = rect.left;
|
||||||
|
let top = rect.top;
|
||||||
|
const gap = 10;
|
||||||
|
left = Math.max(gap, Math.min(window.innerWidth - rect.width - gap, left));
|
||||||
|
top = Math.max(gap, Math.min(window.innerHeight - rect.height - gap, top));
|
||||||
|
if (snap) {
|
||||||
|
const distances = [
|
||||||
|
{ side: "left", value: left },
|
||||||
|
{ side: "right", value: window.innerWidth - left - rect.width },
|
||||||
|
{ side: "top", value: top },
|
||||||
|
{ side: "bottom", value: window.innerHeight - top - rect.height }
|
||||||
|
].sort((a, b) => a.value - b.value);
|
||||||
|
if (distances[0].value < 96) {
|
||||||
|
if (distances[0].side === "left") left = gap;
|
||||||
|
if (distances[0].side === "right") left = window.innerWidth - rect.width - gap;
|
||||||
|
if (distances[0].side === "top") top = gap;
|
||||||
|
if (distances[0].side === "bottom") top = window.innerHeight - rect.height - gap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
root.style.left = `${Math.round(left)}px`;
|
||||||
|
root.style.top = `${Math.round(top)}px`;
|
||||||
|
root.style.transform = "none";
|
||||||
|
savePosition(root);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onDragStart(event) {
|
||||||
|
const target = event.target;
|
||||||
|
if (target?.closest?.("button")) return;
|
||||||
|
const root = document.getElementById(ROOT_ID);
|
||||||
|
if (!root) return;
|
||||||
|
const rect = root.getBoundingClientRect();
|
||||||
|
dragging = {
|
||||||
|
offsetX: event.clientX - rect.left,
|
||||||
|
offsetY: event.clientY - rect.top
|
||||||
|
};
|
||||||
|
root.style.transform = "none";
|
||||||
|
document.addEventListener("pointermove", onDragMove, true);
|
||||||
|
document.addEventListener("pointerup", onDragEnd, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onDragMove(event) {
|
||||||
|
if (!dragging) return;
|
||||||
|
event.preventDefault();
|
||||||
|
const root = document.getElementById(ROOT_ID);
|
||||||
|
if (!root) return;
|
||||||
|
root.style.left = `${event.clientX - dragging.offsetX}px`;
|
||||||
|
root.style.top = `${event.clientY - dragging.offsetY}px`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function onDragEnd() {
|
||||||
|
const root = document.getElementById(ROOT_ID);
|
||||||
|
dragging = null;
|
||||||
|
document.removeEventListener("pointermove", onDragMove, true);
|
||||||
|
document.removeEventListener("pointerup", onDragEnd, true);
|
||||||
|
if (root) clampIsland(root, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setToolbarStatus(text, busy = true) {
|
||||||
|
const root = document.getElementById(ROOT_ID) || ensureBase();
|
||||||
|
root.innerHTML = `<style>${rootCss()}</style>
|
||||||
|
<div class="w2p-island">
|
||||||
|
<div class="w2p-status">
|
||||||
|
${busy ? '<span class="w2p-spinner"></span>' : ""}
|
||||||
|
<span class="w2p-text"></span>
|
||||||
|
</div>
|
||||||
|
<button class="w2p-cancel" type="button">取消</button>
|
||||||
|
</div>`;
|
||||||
|
root.querySelector(".w2p-text").textContent = text;
|
||||||
|
root.querySelector(".w2p-cancel").addEventListener("click", removePicker);
|
||||||
|
root.addEventListener("pointerdown", onDragStart, true);
|
||||||
|
clampIsland(root, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
function showToolbar(settings) {
|
||||||
|
removePicker();
|
||||||
|
currentSettings = settings || {};
|
||||||
|
mode = "toolbar";
|
||||||
|
const root = ensureBase();
|
||||||
|
root.innerHTML = `<style>${rootCss()}</style>
|
||||||
|
<div class="w2p-island">
|
||||||
|
<button class="w2p-action" data-action="clipboard" type="button">${icon("copy")}<span>复制到剪贴板</span></button>
|
||||||
|
<button class="w2p-action" data-action="screen" type="button">${icon("screen")}<span>整个屏幕</span></button>
|
||||||
|
<button class="w2p-action active" data-action="select" type="button">${icon("select")}<span>选择元素</span></button>
|
||||||
|
<button class="w2p-close" data-action="close" type="button" aria-label="关闭">${icon("close")}</button>
|
||||||
|
</div>`;
|
||||||
|
root.addEventListener("pointerdown", onDragStart, true);
|
||||||
|
root.querySelector('[data-action="close"]').addEventListener("click", removePicker);
|
||||||
|
root.querySelector('[data-action="select"]').addEventListener("click", () => startElementPicker(currentSettings));
|
||||||
|
root.querySelector('[data-action="screen"]').addEventListener("click", () => startFullPageCapture(false));
|
||||||
|
root.querySelector('[data-action="clipboard"]').addEventListener("click", () => startFullPageCapture(true));
|
||||||
|
clampIsland(root, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function copyText(text) {
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(text);
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
const textarea = document.createElement("textarea");
|
||||||
|
textarea.value = text;
|
||||||
|
textarea.style.cssText = "position:fixed;left:-9999px;top:0;opacity:0";
|
||||||
|
document.documentElement.appendChild(textarea);
|
||||||
|
textarea.focus();
|
||||||
|
textarea.select();
|
||||||
|
const ok = document.execCommand("copy");
|
||||||
|
textarea.remove();
|
||||||
|
if (!ok) throw new Error("浏览器拒绝写入剪贴板");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function startFullPageCapture(copyToClipboard) {
|
||||||
|
setToolbarStatus(copyToClipboard ? "正在将页面捕获到剪贴板" : "正在捕获整个页面");
|
||||||
|
try {
|
||||||
|
const response = await chrome.runtime.sendMessage({
|
||||||
|
type: copyToClipboard ? "PIXSO_CAPTURE_CLIPBOARD" : "PIXSO_CAPTURE_CURRENT_TAB",
|
||||||
|
options: currentSettings || {}
|
||||||
|
});
|
||||||
|
if (!response?.ok) throw new Error(response?.error || "采集失败");
|
||||||
|
if (copyToClipboard) {
|
||||||
|
await copyText(response.json);
|
||||||
|
setToolbarStatus("已复制 JSON 到剪贴板", false);
|
||||||
|
} else {
|
||||||
|
setToolbarStatus("整页采集完成", false);
|
||||||
|
}
|
||||||
|
setTimeout(removePicker, 1100);
|
||||||
|
} catch (error) {
|
||||||
|
setToolbarStatus(error.message || String(error), false);
|
||||||
|
setTimeout(showToolbar, 2200, currentSettings);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideHighlight() {
|
||||||
|
const box = document.getElementById(BOX_ID);
|
||||||
|
const label = document.getElementById(LABEL_ID);
|
||||||
|
if (box) box.style.display = "none";
|
||||||
|
if (label) label.style.display = "none";
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateHighlight(element) {
|
||||||
|
const box = document.getElementById(BOX_ID);
|
||||||
|
const label = document.getElementById(LABEL_ID);
|
||||||
|
if (!box || !label || !element) return;
|
||||||
|
const rect = element.getBoundingClientRect();
|
||||||
|
if (rect.width <= 0 || rect.height <= 0) return;
|
||||||
|
|
||||||
|
box.style.display = "block";
|
||||||
|
box.style.left = `${Math.max(0, rect.left)}px`;
|
||||||
|
box.style.top = `${Math.max(0, rect.top)}px`;
|
||||||
|
box.style.width = `${rect.width}px`;
|
||||||
|
box.style.height = `${rect.height}px`;
|
||||||
|
|
||||||
|
label.style.display = "block";
|
||||||
|
label.textContent = elementName(element);
|
||||||
|
label.style.left = `${Math.max(8, rect.left)}px`;
|
||||||
|
label.style.top = `${Math.max(8, Math.min(window.innerHeight - 36, rect.bottom + 8))}px`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isPickerNode(element) {
|
||||||
|
return Boolean(element?.closest?.(`#${ROOT_ID}, #${BOX_ID}, #${LABEL_ID}`));
|
||||||
|
}
|
||||||
|
|
||||||
|
function onMouseMove(event) {
|
||||||
|
if (mode !== "select") return;
|
||||||
|
const element = event.target;
|
||||||
|
if (!element || isPickerNode(element)) return;
|
||||||
|
currentElement = element;
|
||||||
|
updateHighlight(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onKeyDown(event) {
|
||||||
|
if (event.key === "Escape") {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
removePicker();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onClick(event) {
|
||||||
|
if (mode !== "select" || !currentElement || isPickerNode(event.target)) return;
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
|
||||||
|
const selected = currentElement;
|
||||||
|
const rect = selected.getBoundingClientRect();
|
||||||
|
const selectionId = `w2p-${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
||||||
|
selected.setAttribute(ATTR, selectionId);
|
||||||
|
setToolbarStatus("正在捕获所选元素");
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await chrome.runtime.sendMessage({
|
||||||
|
type: "PIXSO_CAPTURE_ELEMENT_START",
|
||||||
|
options: {
|
||||||
|
...(currentSettings || {}),
|
||||||
|
captureMode: "mixed",
|
||||||
|
selectionId,
|
||||||
|
selectionWidth: Math.max(1, Math.round(rect.width))
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (!response?.ok) throw new Error(response?.error || "元素采集失败");
|
||||||
|
setToolbarStatus(`元素采集完成,宽度 ${response.selectionWidth || Math.round(rect.width)}px`, false);
|
||||||
|
setTimeout(removePicker, 1100);
|
||||||
|
} catch (error) {
|
||||||
|
setToolbarStatus(error.message || String(error), false);
|
||||||
|
setTimeout(() => startElementPicker(currentSettings), 2200);
|
||||||
|
} finally {
|
||||||
|
selected.removeAttribute(ATTR);
|
||||||
|
hideHighlight();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function startElementPicker(settings) {
|
||||||
|
removePicker();
|
||||||
|
currentSettings = settings || {};
|
||||||
|
mode = "select";
|
||||||
|
setToolbarStatus("选择要捕获的元素");
|
||||||
|
document.addEventListener("mousemove", onMouseMove, true);
|
||||||
|
document.addEventListener("click", onClick, true);
|
||||||
|
document.addEventListener("keydown", onKeyDown, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.__webToPixsoStartElementPicker = startElementPicker;
|
||||||
|
window.__webToPixsoShowCaptureToolbar = showToolbar;
|
||||||
|
})();
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128" width="128" height="128">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||||
|
<stop offset="0%" style="stop-color:#00D2FF;stop-opacity:1" />
|
||||||
|
<stop offset="100%" style="stop-color:#7C3AED;stop-opacity:1" />
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
|
||||||
|
<!-- 背景圆 -->
|
||||||
|
<circle cx="64" cy="64" r="60" fill="url(#grad1)"/>
|
||||||
|
|
||||||
|
<!-- 网页图标 -->
|
||||||
|
<rect x="24" y="32" width="80" height="60" rx="4" fill="#fff" opacity="0.95"/>
|
||||||
|
|
||||||
|
<!-- 网页内容线 -->
|
||||||
|
<rect x="32" y="44" width="40" height="4" rx="2" fill="#00D2FF"/>
|
||||||
|
<rect x="32" y="54" width="64" height="3" rx="1.5" fill="#E2E8F0"/>
|
||||||
|
<rect x="32" y="62" width="56" height="3" rx="1.5" fill="#E2E8F0"/>
|
||||||
|
<rect x="32" y="70" width="48" height="3" rx="1.5" fill="#E2E8F0"/>
|
||||||
|
|
||||||
|
<!-- 箭头 -->
|
||||||
|
<path d="M72 80 L88 96 L104 80" stroke="#fff" stroke-width="4" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
|
||||||
|
<!-- Pixso 标志 -->
|
||||||
|
<circle cx="96" cy="56" r="16" fill="#fff"/>
|
||||||
|
<text x="96" y="61" text-anchor="middle" font-size="14" font-weight="bold" fill="url(#grad1)">P</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,4 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16">
|
||||||
|
<rect width="16" height="16" rx="2" fill="#00D2FF"/>
|
||||||
|
<text x="8" y="12" text-anchor="middle" font-size="10" font-weight="bold" fill="white">P</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 244 B |
@@ -0,0 +1,4 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="32" height="32">
|
||||||
|
<rect width="32" height="32" rx="4" fill="#00D2FF"/>
|
||||||
|
<text x="16" y="22" text-anchor="middle" font-size="14" font-weight="bold" fill="white">P</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 245 B |
@@ -0,0 +1,4 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48" height="48">
|
||||||
|
<rect width="48" height="48" rx="6" fill="#00D2FF"/>
|
||||||
|
<text x="24" y="32" text-anchor="middle" font-size="20" font-weight="bold" fill="white">P</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 245 B |
|
After Width: | Height: | Size: 112 KiB |
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"manifest_version": 3,
|
||||||
|
"name": "Web to Pixso",
|
||||||
|
"version": "1.1.1",
|
||||||
|
"description": "Capture a webpage and convert it into editable Pixso layers.",
|
||||||
|
"permissions": ["activeTab", "scripting", "downloads", "storage"],
|
||||||
|
"host_permissions": ["<all_urls>"],
|
||||||
|
"background": {
|
||||||
|
"service_worker": "background.js"
|
||||||
|
},
|
||||||
|
"action": {
|
||||||
|
"default_title": "Web to Pixso",
|
||||||
|
"default_icon": {
|
||||||
|
"16": "logo/plugin-logo.png",
|
||||||
|
"32": "logo/plugin-logo.png",
|
||||||
|
"48": "logo/plugin-logo.png",
|
||||||
|
"128": "logo/plugin-logo.png"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"icons": {
|
||||||
|
"16": "logo/plugin-logo.png",
|
||||||
|
"32": "logo/plugin-logo.png",
|
||||||
|
"48": "logo/plugin-logo.png",
|
||||||
|
"128": "logo/plugin-logo.png"
|
||||||
|
},
|
||||||
|
"web_accessible_resources": [
|
||||||
|
{
|
||||||
|
"resources": ["capture.js", "runner.js", "element-picker.js", "popup-panel.js", "logo/plugin-logo.png"],
|
||||||
|
"matches": ["<all_urls>"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 112 KiB |
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"identifier": "web-to-pixso",
|
||||||
|
"id": "web-to-pixso-local",
|
||||||
|
"name": "Web to Pixso",
|
||||||
|
"description": "Import a Web to Pixso capture JSON file as editable Pixso layers.",
|
||||||
|
"version": "1.1.1",
|
||||||
|
"api": "1.0.0",
|
||||||
|
"author": "大非",
|
||||||
|
"editorType": ["pixso", "preview"],
|
||||||
|
"main": "./main.js",
|
||||||
|
"ui": "./ui.html",
|
||||||
|
"icon": "./plugin-logo.png",
|
||||||
|
"menu": [
|
||||||
|
{
|
||||||
|
"name": "导入网页采集文件",
|
||||||
|
"command": "import"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 112 KiB |
@@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"identifier": "web-to-pixso",
|
||||||
|
"id": "web-to-pixso-local",
|
||||||
|
"name": "Web to Pixso",
|
||||||
|
"description": "Import a Web to Pixso capture JSON file as editable Pixso layers.",
|
||||||
|
"version": "1.1.1",
|
||||||
|
"api": "1.0.0",
|
||||||
|
"author": "大非",
|
||||||
|
"editorType": ["pixso", "preview"],
|
||||||
|
"main": "./main.js",
|
||||||
|
"ui": "./ui.html",
|
||||||
|
"icon": "./plugin-logo.png",
|
||||||
|
"menu": [
|
||||||
|
{
|
||||||
|
"name": "导入网页采集文件",
|
||||||
|
"command": "import"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"commands": [
|
||||||
|
{
|
||||||
|
"name": "import",
|
||||||
|
"description": "导入 Web to Pixso 采集文件"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"i18nManifest": {
|
||||||
|
"zh-CN": {
|
||||||
|
"name": "Web to Pixso",
|
||||||
|
"description": "导入网页采集文件并生成 Pixso 可编辑图层"
|
||||||
|
},
|
||||||
|
"en-US": {
|
||||||
|
"name": "Web to Pixso",
|
||||||
|
"description": "Import webpage captures as editable Pixso layers"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,521 @@
|
|||||||
|
(function () {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const ROOT_ID = "__web_to_pixso_panel_root__";
|
||||||
|
const SETTINGS_KEY = "webToPixsoSettings";
|
||||||
|
const MIN_CAPTURE_WIDTH = 320;
|
||||||
|
const MAX_CAPTURE_WIDTH = 3840;
|
||||||
|
const DEFAULT_SETTINGS = {
|
||||||
|
useProxy: false,
|
||||||
|
concurrency: "8",
|
||||||
|
captureMode: "mixed",
|
||||||
|
captureWidth: null
|
||||||
|
};
|
||||||
|
|
||||||
|
function normalizeCaptureWidth(value, fallback = null) {
|
||||||
|
const number = Number.parseInt(String(value || "").replace(/\D+/g, ""), 10);
|
||||||
|
if (!Number.isFinite(number)) return fallback;
|
||||||
|
return Math.max(MIN_CAPTURE_WIDTH, Math.min(MAX_CAPTURE_WIDTH, number));
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeSettings(value = {}) {
|
||||||
|
const concurrency = String(value.concurrency || DEFAULT_SETTINGS.concurrency);
|
||||||
|
return {
|
||||||
|
useProxy: Boolean(value.useProxy),
|
||||||
|
concurrency: ["4", "6", "8", "10", "12", "16", "20", "infinite"].includes(concurrency)
|
||||||
|
? concurrency
|
||||||
|
: DEFAULT_SETTINGS.concurrency,
|
||||||
|
captureMode: value.captureMode === "editable" ? "editable" : "mixed",
|
||||||
|
captureWidth: normalizeCaptureWidth(value.captureWidth, null)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function css() {
|
||||||
|
return `
|
||||||
|
:host {
|
||||||
|
all: initial;
|
||||||
|
color-scheme: light;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
|
||||||
|
}
|
||||||
|
.backdrop {
|
||||||
|
background: transparent;
|
||||||
|
inset: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
position: fixed;
|
||||||
|
z-index: 2147483647;
|
||||||
|
}
|
||||||
|
.panel {
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid rgba(10, 10, 18, 0.14);
|
||||||
|
border-radius: 22px;
|
||||||
|
box-shadow: 0 18px 46px rgba(10, 10, 18, 0.18);
|
||||||
|
box-sizing: border-box;
|
||||||
|
color: #1a1a2e;
|
||||||
|
overflow: hidden;
|
||||||
|
pointer-events: auto;
|
||||||
|
position: fixed;
|
||||||
|
right: 28px;
|
||||||
|
top: 24px;
|
||||||
|
width: 320px;
|
||||||
|
}
|
||||||
|
.header {
|
||||||
|
align-items: center;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 16px 20px 15px;
|
||||||
|
}
|
||||||
|
.logo-title {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
.logo {
|
||||||
|
border-radius: 10px;
|
||||||
|
box-shadow: 0 4px 12px rgba(10, 10, 18, 0.12);
|
||||||
|
display: block;
|
||||||
|
height: 28px;
|
||||||
|
object-fit: cover;
|
||||||
|
width: 28px;
|
||||||
|
}
|
||||||
|
.title {
|
||||||
|
color: #1a1a2e;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 650;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
.version-badge {
|
||||||
|
background: #f2f4ff;
|
||||||
|
border: 1px solid #dfe5ff;
|
||||||
|
border-radius: 999px;
|
||||||
|
color: #2450ff;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1;
|
||||||
|
padding: 3px 6px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
button, select, input {
|
||||||
|
font: inherit;
|
||||||
|
}
|
||||||
|
.close-btn {
|
||||||
|
align-items: center;
|
||||||
|
background: transparent;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 12px;
|
||||||
|
color: #999;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
font-size: 22px;
|
||||||
|
height: 28px;
|
||||||
|
justify-content: center;
|
||||||
|
line-height: 1;
|
||||||
|
transition: background 0.2s, color 0.2s;
|
||||||
|
width: 28px;
|
||||||
|
}
|
||||||
|
.close-btn:hover {
|
||||||
|
background: #f4f4f6;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
.setting-row {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
.setting-label {
|
||||||
|
color: #1a1a2e;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
.setting-select {
|
||||||
|
appearance: none;
|
||||||
|
background: #fff;
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23666' d='M6 8L1 3h10z'/%3E%3C/svg%3E");
|
||||||
|
background-position: right 10px center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
border: 1px solid #e4e4e4;
|
||||||
|
border-radius: 12px;
|
||||||
|
color: #1a1a2e;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
min-width: 80px;
|
||||||
|
outline: none;
|
||||||
|
padding: 8px 30px 8px 13px;
|
||||||
|
}
|
||||||
|
.mode-select {
|
||||||
|
min-width: 116px;
|
||||||
|
}
|
||||||
|
.toggle-switch {
|
||||||
|
display: inline-block;
|
||||||
|
height: 26px;
|
||||||
|
position: relative;
|
||||||
|
width: 48px;
|
||||||
|
}
|
||||||
|
.toggle-switch input {
|
||||||
|
height: 0;
|
||||||
|
opacity: 0;
|
||||||
|
width: 0;
|
||||||
|
}
|
||||||
|
.toggle-slider {
|
||||||
|
background-color: #e4e4e4;
|
||||||
|
border-radius: 999px;
|
||||||
|
bottom: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
left: 0;
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
transition: 0.3s;
|
||||||
|
}
|
||||||
|
.toggle-slider::before {
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 50%;
|
||||||
|
bottom: 3px;
|
||||||
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
|
||||||
|
content: "";
|
||||||
|
height: 20px;
|
||||||
|
left: 3px;
|
||||||
|
position: absolute;
|
||||||
|
transition: 0.3s;
|
||||||
|
width: 20px;
|
||||||
|
}
|
||||||
|
input:checked + .toggle-slider {
|
||||||
|
background-color: #1a1a2e;
|
||||||
|
}
|
||||||
|
input:checked + .toggle-slider::before {
|
||||||
|
transform: translateX(22px);
|
||||||
|
}
|
||||||
|
.width-input-wrap {
|
||||||
|
align-items: center;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #e4e4e4;
|
||||||
|
border-radius: 12px;
|
||||||
|
display: flex;
|
||||||
|
height: 36px;
|
||||||
|
min-width: 116px;
|
||||||
|
padding: 0 10px 0 12px;
|
||||||
|
}
|
||||||
|
.width-input {
|
||||||
|
background: transparent;
|
||||||
|
border: 0;
|
||||||
|
color: #1a1a2e;
|
||||||
|
font-size: 14px;
|
||||||
|
min-width: 0;
|
||||||
|
outline: none;
|
||||||
|
text-align: right;
|
||||||
|
width: 68px;
|
||||||
|
}
|
||||||
|
.width-input.invalid {
|
||||||
|
color: #ef4444;
|
||||||
|
}
|
||||||
|
.width-unit {
|
||||||
|
color: #999;
|
||||||
|
font-size: 12px;
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
.description {
|
||||||
|
color: #999;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.6;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.capture-btn,
|
||||||
|
.select-btn {
|
||||||
|
border: 0;
|
||||||
|
border-radius: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
padding: 14px 24px;
|
||||||
|
transition: background 0.2s, transform 0.2s;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.capture-btn {
|
||||||
|
background: #1a1a2e;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.capture-btn:hover {
|
||||||
|
background: #2d2d44;
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
.select-btn {
|
||||||
|
background: #f4f4f6;
|
||||||
|
color: #1a1a2e;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
.select-btn:hover {
|
||||||
|
background: #ececf1;
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
.capture-btn:disabled,
|
||||||
|
.select-btn:disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 0.6;
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
.capture-btn.success {
|
||||||
|
background: #10b981;
|
||||||
|
}
|
||||||
|
.capture-btn.error {
|
||||||
|
background: #ef4444;
|
||||||
|
}
|
||||||
|
.status {
|
||||||
|
margin-top: 16px;
|
||||||
|
padding: 12px 0 0;
|
||||||
|
}
|
||||||
|
.progress-bar {
|
||||||
|
background: #f0f0f0;
|
||||||
|
border-radius: 999px;
|
||||||
|
height: 4px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.progress-fill {
|
||||||
|
background: linear-gradient(90deg, #00d2ff, #7c3aed);
|
||||||
|
border-radius: 999px;
|
||||||
|
height: 100%;
|
||||||
|
transition: width 0.3s ease;
|
||||||
|
width: 0;
|
||||||
|
}
|
||||||
|
.status-text {
|
||||||
|
color: #666;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.help-link {
|
||||||
|
align-items: center;
|
||||||
|
border: 1px solid #e6e8f2;
|
||||||
|
border-radius: 12px;
|
||||||
|
color: #2450ff;
|
||||||
|
display: flex;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
justify-content: center;
|
||||||
|
margin-top: 16px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: background 0.18s, border-color 0.18s, color 0.18s;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.help-link:hover {
|
||||||
|
background: #f6f8ff;
|
||||||
|
border-color: #dfe5ff;
|
||||||
|
}
|
||||||
|
.footer {
|
||||||
|
align-items: center;
|
||||||
|
background: #fff;
|
||||||
|
border-top: 1px solid #f0f0f0;
|
||||||
|
color: #666;
|
||||||
|
display: flex;
|
||||||
|
font-size: 12px;
|
||||||
|
gap: 8px;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 12px 20px;
|
||||||
|
}
|
||||||
|
.support-email {
|
||||||
|
color: #888;
|
||||||
|
font-size: 10px;
|
||||||
|
line-height: 1.35;
|
||||||
|
min-width: 0;
|
||||||
|
text-align: right;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
.support-email:hover {
|
||||||
|
color: #2450ff;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function panelHtml() {
|
||||||
|
return `
|
||||||
|
<div class="backdrop">
|
||||||
|
<main class="panel" role="dialog" aria-label="Web to Pixso">
|
||||||
|
<div class="header">
|
||||||
|
<div class="logo-title">
|
||||||
|
<img src="${chrome.runtime.getURL("logo/plugin-logo.png")}" alt="" class="logo">
|
||||||
|
<span class="title">Web to Pixso</span>
|
||||||
|
<span class="version-badge">v1.1.1</span>
|
||||||
|
</div>
|
||||||
|
<button class="close-btn" type="button" aria-label="关闭">×</button>
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
|
<div class="setting-row">
|
||||||
|
<span class="setting-label">采集模式</span>
|
||||||
|
<select class="setting-select mode-select" data-field="captureMode">
|
||||||
|
<option value="mixed">混合高保真</option>
|
||||||
|
<option value="editable">可编辑优先</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="setting-row">
|
||||||
|
<span class="setting-label">跨域图片代理模式</span>
|
||||||
|
<label class="toggle-switch">
|
||||||
|
<input type="checkbox" data-field="useProxy">
|
||||||
|
<span class="toggle-slider"></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="setting-row">
|
||||||
|
<span class="setting-label">页面采集宽度</span>
|
||||||
|
<label class="width-input-wrap">
|
||||||
|
<input class="width-input" data-field="captureWidth" type="text" inputmode="numeric" autocomplete="off">
|
||||||
|
<span class="width-unit">px</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="setting-row">
|
||||||
|
<span class="setting-label">图片采集并发</span>
|
||||||
|
<select class="setting-select" data-field="concurrency">
|
||||||
|
<option value="4">4</option>
|
||||||
|
<option value="6">6</option>
|
||||||
|
<option value="8">8</option>
|
||||||
|
<option value="10">10</option>
|
||||||
|
<option value="12">12</option>
|
||||||
|
<option value="16">16</option>
|
||||||
|
<option value="20">20</option>
|
||||||
|
<option value="infinite">无限</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<p class="description">页面采集宽度默认使用当前窗口宽度,可输入 320-3840px 触发响应式布局后采集。</p>
|
||||||
|
<button class="capture-btn" type="button">开始采集</button>
|
||||||
|
<button class="select-btn" type="button">打开页面浮窗</button>
|
||||||
|
<div class="status" hidden>
|
||||||
|
<div class="progress-bar" aria-hidden="true"><div class="progress-fill"></div></div>
|
||||||
|
<span class="status-text">准备中...</span>
|
||||||
|
</div>
|
||||||
|
<a class="help-link" href="https://z8qrcvi3n5.feishu.cn/wiki/RV8TwlhFyiGsEekQXk8cX5SHn6f" target="_blank" rel="noopener noreferrer">使用说明</a>
|
||||||
|
</div>
|
||||||
|
<div class="footer">
|
||||||
|
<span>by 大非</span>
|
||||||
|
<a class="support-email" href="mailto:270310136@qq.com">270310136@qq.com 给我发邮件哦,我光速改</a>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getField(root, name) {
|
||||||
|
return root.shadowRoot.querySelector(`[data-field="${name}"]`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function sanitizeWidth(input, clamp = false) {
|
||||||
|
const digits = input.value.replace(/\D+/g, "");
|
||||||
|
input.value = digits;
|
||||||
|
const raw = Number.parseInt(digits, 10);
|
||||||
|
const invalid = Boolean(digits) && Number.isFinite(raw) && (raw < MIN_CAPTURE_WIDTH || raw > MAX_CAPTURE_WIDTH);
|
||||||
|
input.classList.toggle("invalid", invalid);
|
||||||
|
const normalized = normalizeCaptureWidth(digits, null);
|
||||||
|
if (clamp && digits) {
|
||||||
|
input.value = String(normalized);
|
||||||
|
input.classList.remove("invalid");
|
||||||
|
}
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getSettings() {
|
||||||
|
const result = await chrome.storage.local.get({ [SETTINGS_KEY]: DEFAULT_SETTINGS });
|
||||||
|
return normalizeSettings(result[SETTINGS_KEY]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveSettings(settings) {
|
||||||
|
await chrome.storage.local.set({ [SETTINGS_KEY]: normalizeSettings(settings) });
|
||||||
|
}
|
||||||
|
|
||||||
|
function readSettings(root, options = {}) {
|
||||||
|
const { clampWidth = false } = options;
|
||||||
|
return normalizeSettings({
|
||||||
|
captureMode: getField(root, "captureMode").value,
|
||||||
|
useProxy: getField(root, "useProxy").checked,
|
||||||
|
concurrency: getField(root, "concurrency").value,
|
||||||
|
captureWidth: sanitizeWidth(getField(root, "captureWidth"), clampWidth)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function setProgress(root, percent, text) {
|
||||||
|
const status = root.shadowRoot.querySelector(".status");
|
||||||
|
const fill = root.shadowRoot.querySelector(".progress-fill");
|
||||||
|
const statusText = root.shadowRoot.querySelector(".status-text");
|
||||||
|
status.hidden = false;
|
||||||
|
fill.style.width = `${Math.max(0, Math.min(100, percent))}%`;
|
||||||
|
statusText.textContent = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setBusy(root, isBusy) {
|
||||||
|
const captureButton = root.shadowRoot.querySelector(".capture-btn");
|
||||||
|
const selectButton = root.shadowRoot.querySelector(".select-btn");
|
||||||
|
captureButton.disabled = isBusy;
|
||||||
|
selectButton.disabled = isBusy;
|
||||||
|
captureButton.classList.remove("success", "error");
|
||||||
|
captureButton.textContent = isBusy ? "采集中..." : "开始采集";
|
||||||
|
selectButton.textContent = isBusy ? "处理中..." : "打开页面浮窗";
|
||||||
|
}
|
||||||
|
|
||||||
|
function setResult(root, kind, text) {
|
||||||
|
const captureButton = root.shadowRoot.querySelector(".capture-btn");
|
||||||
|
captureButton.classList.remove("success", "error");
|
||||||
|
captureButton.classList.add(kind);
|
||||||
|
captureButton.textContent = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function startCapture(root) {
|
||||||
|
setBusy(root, true);
|
||||||
|
setProgress(root, 12, "准备当前网页...");
|
||||||
|
const settings = readSettings(root, { clampWidth: true });
|
||||||
|
await saveSettings(settings);
|
||||||
|
try {
|
||||||
|
setProgress(root, 28, "注入采集脚本...");
|
||||||
|
const response = await chrome.runtime.sendMessage({
|
||||||
|
type: "PIXSO_CAPTURE_CURRENT_TAB",
|
||||||
|
options: settings
|
||||||
|
});
|
||||||
|
if (!response?.ok) throw new Error(response?.error || "采集失败");
|
||||||
|
const widthText = response.actualViewportWidth ? `已按 ${response.actualViewportWidth}px 采集` : "采集完成";
|
||||||
|
setProgress(root, 100, `${widthText}:${response.filename || ""}`);
|
||||||
|
setResult(root, "success", "采集完成");
|
||||||
|
setTimeout(() => root.remove(), 900);
|
||||||
|
} catch (error) {
|
||||||
|
setProgress(root, 0, error.message || String(error));
|
||||||
|
setResult(root, "error", "采集失败");
|
||||||
|
setTimeout(() => setBusy(root, false), 2600);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function openToolbar(root) {
|
||||||
|
const settings = readSettings(root, { clampWidth: true });
|
||||||
|
await saveSettings(settings);
|
||||||
|
root.remove();
|
||||||
|
window.__webToPixsoShowCaptureToolbar?.(settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function bind(root) {
|
||||||
|
const settings = await getSettings();
|
||||||
|
getField(root, "captureMode").value = settings.captureMode;
|
||||||
|
getField(root, "useProxy").checked = settings.useProxy;
|
||||||
|
getField(root, "concurrency").value = settings.concurrency;
|
||||||
|
getField(root, "captureWidth").value = String(settings.captureWidth || Math.round(window.innerWidth || document.documentElement.clientWidth || 1440));
|
||||||
|
|
||||||
|
for (const field of ["captureMode", "useProxy", "concurrency"]) {
|
||||||
|
getField(root, field).addEventListener("change", () => saveSettings(readSettings(root)));
|
||||||
|
}
|
||||||
|
getField(root, "captureWidth").addEventListener("input", event => {
|
||||||
|
sanitizeWidth(event.currentTarget);
|
||||||
|
saveSettings(readSettings(root, { clampWidth: false }));
|
||||||
|
});
|
||||||
|
getField(root, "captureWidth").addEventListener("blur", event => {
|
||||||
|
sanitizeWidth(event.currentTarget, true);
|
||||||
|
saveSettings(readSettings(root, { clampWidth: true }));
|
||||||
|
});
|
||||||
|
root.shadowRoot.querySelector(".close-btn").addEventListener("click", () => root.remove());
|
||||||
|
root.shadowRoot.querySelector(".capture-btn").addEventListener("click", () => startCapture(root));
|
||||||
|
root.shadowRoot.querySelector(".select-btn").addEventListener("click", () => openToolbar(root));
|
||||||
|
}
|
||||||
|
|
||||||
|
window.__webToPixsoShowPanel = async function showPanel() {
|
||||||
|
document.getElementById(ROOT_ID)?.remove();
|
||||||
|
const root = document.createElement("div");
|
||||||
|
root.id = ROOT_ID;
|
||||||
|
const shadow = root.attachShadow({ mode: "open" });
|
||||||
|
shadow.innerHTML = `<style>${css()}</style>${panelHtml()}`;
|
||||||
|
document.documentElement.appendChild(root);
|
||||||
|
await bind(root);
|
||||||
|
};
|
||||||
|
})();
|
||||||
@@ -0,0 +1,379 @@
|
|||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
background: transparent !important;
|
||||||
|
border-radius: 16px;
|
||||||
|
overflow: hidden;
|
||||||
|
width: 320px;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background: transparent !important;
|
||||||
|
color: #1a1a2e;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
|
||||||
|
padding: 0;
|
||||||
|
width: 320px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shell {
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid rgba(10, 10, 18, 0.14);
|
||||||
|
border-radius: 16px;
|
||||||
|
box-shadow: none;
|
||||||
|
overflow: hidden;
|
||||||
|
width: 320px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
align-items: center;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 16px 20px 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-title {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
border-radius: 10px;
|
||||||
|
box-shadow: 0 4px 12px rgba(10, 10, 18, 0.12);
|
||||||
|
display: block;
|
||||||
|
height: 28px;
|
||||||
|
margin-left: -2px;
|
||||||
|
object-fit: cover;
|
||||||
|
width: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
color: #1a1a2e;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.version-badge {
|
||||||
|
background: #f2f4ff;
|
||||||
|
border: 1px solid #dfe5ff;
|
||||||
|
border-radius: 999px;
|
||||||
|
color: #2450ff;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1;
|
||||||
|
padding: 3px 6px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-btn {
|
||||||
|
align-items: center;
|
||||||
|
background: transparent;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 12px;
|
||||||
|
color: #999;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
font-size: 18px;
|
||||||
|
height: 24px;
|
||||||
|
justify-content: center;
|
||||||
|
line-height: 1;
|
||||||
|
transition: background 0.2s, color 0.2s;
|
||||||
|
width: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-btn:hover {
|
||||||
|
background: #f4f4f6;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-row {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-label {
|
||||||
|
color: #1a1a2e;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-switch {
|
||||||
|
display: inline-block;
|
||||||
|
height: 26px;
|
||||||
|
position: relative;
|
||||||
|
width: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-switch input {
|
||||||
|
height: 0;
|
||||||
|
opacity: 0;
|
||||||
|
width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-slider {
|
||||||
|
background-color: #e4e4e4;
|
||||||
|
border-radius: 999px;
|
||||||
|
bottom: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
left: 0;
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
transition: 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-slider::before {
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 50%;
|
||||||
|
bottom: 3px;
|
||||||
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
|
||||||
|
content: "";
|
||||||
|
height: 20px;
|
||||||
|
left: 3px;
|
||||||
|
position: absolute;
|
||||||
|
transition: 0.3s;
|
||||||
|
width: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:checked + .toggle-slider {
|
||||||
|
background-color: #1a1a2e;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:checked + .toggle-slider::before {
|
||||||
|
transform: translateX(22px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-select {
|
||||||
|
appearance: none;
|
||||||
|
background: #fff;
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23666' d='M6 8L1 3h10z'/%3E%3C/svg%3E");
|
||||||
|
background-position: right 10px center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
border: 1px solid #e4e4e4;
|
||||||
|
border-radius: 9px;
|
||||||
|
color: #1a1a2e;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
min-width: 80px;
|
||||||
|
padding: 7px 30px 7px 13px;
|
||||||
|
transition: border-color 0.18s, box-shadow 0.18s, background 0.18s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-select:focus {
|
||||||
|
border-color: #2450ff;
|
||||||
|
box-shadow: 0 0 0 4px rgba(36, 80, 255, 0.1);
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mode-select {
|
||||||
|
min-width: 116px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.width-input-wrap {
|
||||||
|
align-items: center;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #e4e4e4;
|
||||||
|
border-radius: 9px;
|
||||||
|
display: flex;
|
||||||
|
height: 36px;
|
||||||
|
min-width: 116px;
|
||||||
|
padding: 0 10px 0 12px;
|
||||||
|
transition: border-color 0.18s, box-shadow 0.18s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.width-input-wrap:focus-within {
|
||||||
|
border-color: #2450ff;
|
||||||
|
box-shadow: 0 0 0 4px rgba(36, 80, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.width-input {
|
||||||
|
background: transparent;
|
||||||
|
border: 0;
|
||||||
|
color: #1a1a2e;
|
||||||
|
font: inherit;
|
||||||
|
font-size: 14px;
|
||||||
|
min-width: 0;
|
||||||
|
outline: none;
|
||||||
|
text-align: right;
|
||||||
|
width: 68px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.width-input.invalid {
|
||||||
|
color: #ef4444;
|
||||||
|
}
|
||||||
|
|
||||||
|
.width-unit {
|
||||||
|
color: #999;
|
||||||
|
font-size: 12px;
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
color: #999;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.6;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.capture-btn {
|
||||||
|
background: #1a1a2e;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 9px;
|
||||||
|
color: #fff;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 500;
|
||||||
|
padding: 14px 24px;
|
||||||
|
box-shadow: 0 8px 20px rgba(26, 26, 46, 0.12);
|
||||||
|
transition: background 0.2s, box-shadow 0.2s, transform 0.2s;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.capture-btn:hover {
|
||||||
|
background: #2d2d44;
|
||||||
|
box-shadow: 0 10px 24px rgba(26, 26, 46, 0.16);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.capture-btn:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.capture-btn:disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 0.6;
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.capture-btn.loading {
|
||||||
|
background: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.capture-btn.success {
|
||||||
|
background: #10b981;
|
||||||
|
}
|
||||||
|
|
||||||
|
.capture-btn.error {
|
||||||
|
background: #ef4444;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-btn {
|
||||||
|
background: #f4f4f6;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 9px;
|
||||||
|
color: #1a1a2e;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-top: 10px;
|
||||||
|
padding: 13px 24px;
|
||||||
|
transition: background 0.2s, box-shadow 0.2s, transform 0.2s;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-btn:hover {
|
||||||
|
background: #ececf1;
|
||||||
|
box-shadow: 0 8px 18px rgba(10, 10, 18, 0.08);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-btn:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-btn:disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 0.6;
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-btn.loading {
|
||||||
|
background: #e7e7ee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status {
|
||||||
|
margin-top: 16px;
|
||||||
|
padding: 12px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-bar {
|
||||||
|
background: #f0f0f0;
|
||||||
|
border-radius: 999px;
|
||||||
|
height: 4px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-fill {
|
||||||
|
background: linear-gradient(90deg, #00d2ff, #7c3aed);
|
||||||
|
border-radius: 999px;
|
||||||
|
height: 100%;
|
||||||
|
transition: width 0.3s ease;
|
||||||
|
width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-text {
|
||||||
|
color: #666;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-link {
|
||||||
|
align-items: center;
|
||||||
|
border: 1px solid #e6e8f2;
|
||||||
|
border-radius: 9px;
|
||||||
|
color: #2450ff;
|
||||||
|
display: flex;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
justify-content: center;
|
||||||
|
margin-top: 16px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: background 0.18s, border-color 0.18s, color 0.18s;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-link:hover {
|
||||||
|
background: #f6f8ff;
|
||||||
|
border-color: #dfe5ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
align-items: center;
|
||||||
|
background: #fff;
|
||||||
|
border-top: 1px solid #f0f0f0;
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 12px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.author {
|
||||||
|
color: #666;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.support-email {
|
||||||
|
color: #888;
|
||||||
|
font-size: 10px;
|
||||||
|
line-height: 1.35;
|
||||||
|
min-width: 0;
|
||||||
|
text-align: right;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.support-email:hover {
|
||||||
|
color: #2450ff;
|
||||||
|
}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="zh-CN" style="background: transparent;">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Web to Pixso</title>
|
||||||
|
<link rel="stylesheet" href="popup.css">
|
||||||
|
</head>
|
||||||
|
<body style="background: transparent;">
|
||||||
|
<main class="shell">
|
||||||
|
<div class="header">
|
||||||
|
<div class="logo-title">
|
||||||
|
<img src="logo/plugin-logo.png" alt="" class="logo">
|
||||||
|
<span class="title">Web to Pixso</span>
|
||||||
|
<span class="version-badge">v1.1.1</span>
|
||||||
|
</div>
|
||||||
|
<button class="close-btn" id="closeBtn" type="button" aria-label="关闭">x</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="content">
|
||||||
|
<div class="setting-row">
|
||||||
|
<span class="setting-label">采集模式</span>
|
||||||
|
<select class="setting-select mode-select" id="captureMode">
|
||||||
|
<option value="mixed" selected>混合高保真</option>
|
||||||
|
<option value="editable">可编辑优先</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="setting-row">
|
||||||
|
<span class="setting-label">跨域图片代理模式</span>
|
||||||
|
<label class="toggle-switch" for="proxyToggle">
|
||||||
|
<input type="checkbox" id="proxyToggle">
|
||||||
|
<span class="toggle-slider"></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="setting-row">
|
||||||
|
<span class="setting-label">页面采集宽度</span>
|
||||||
|
<label class="width-input-wrap" for="captureWidth">
|
||||||
|
<input class="width-input" id="captureWidth" type="text" inputmode="numeric" autocomplete="off" aria-label="页面采集宽度">
|
||||||
|
<span class="width-unit">px</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="setting-row">
|
||||||
|
<span class="setting-label">图片采集并发</span>
|
||||||
|
<select class="setting-select" id="concurrency">
|
||||||
|
<option value="4">4</option>
|
||||||
|
<option value="6">6</option>
|
||||||
|
<option value="8" selected>8</option>
|
||||||
|
<option value="10">10</option>
|
||||||
|
<option value="12">12</option>
|
||||||
|
<option value="16">16</option>
|
||||||
|
<option value="20">20</option>
|
||||||
|
<option value="infinite">无限</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="description">页面采集宽度默认使用当前窗口宽度,可输入 320-3840px 触发响应式布局后采集。</p>
|
||||||
|
|
||||||
|
<button class="capture-btn" id="captureBtn" type="button">
|
||||||
|
<span id="btnText">开始采集</span>
|
||||||
|
</button>
|
||||||
|
<button class="select-btn" id="selectBtn" type="button">
|
||||||
|
<span id="selectBtnText">打开页面浮窗</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="status" id="status" hidden>
|
||||||
|
<div class="progress-bar" aria-hidden="true">
|
||||||
|
<div class="progress-fill" id="progressFill"></div>
|
||||||
|
</div>
|
||||||
|
<span class="status-text" id="statusText">准备中...</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a class="help-link" id="helpLink" href="https://z8qrcvi3n5.feishu.cn/wiki/RV8TwlhFyiGsEekQXk8cX5SHn6f" target="_blank" rel="noopener noreferrer">使用说明</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="footer">
|
||||||
|
<span class="author">by 大非</span>
|
||||||
|
<a class="support-email" href="mailto:270310136@qq.com">270310136@qq.com 给我发邮件哦,我光速改</a>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<script src="popup.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,221 @@
|
|||||||
|
const SETTINGS_KEY = "webToPixsoSettings";
|
||||||
|
const HELP_URL = "https://z8qrcvi3n5.feishu.cn/wiki/RV8TwlhFyiGsEekQXk8cX5SHn6f";
|
||||||
|
const DEFAULT_SETTINGS = {
|
||||||
|
useProxy: false,
|
||||||
|
concurrency: "8",
|
||||||
|
captureMode: "mixed",
|
||||||
|
captureWidth: null
|
||||||
|
};
|
||||||
|
const MIN_CAPTURE_WIDTH = 320;
|
||||||
|
const MAX_CAPTURE_WIDTH = 3840;
|
||||||
|
|
||||||
|
const captureModeSelect = document.getElementById("captureMode");
|
||||||
|
const proxyToggle = document.getElementById("proxyToggle");
|
||||||
|
const captureWidthInput = document.getElementById("captureWidth");
|
||||||
|
const concurrencySelect = document.getElementById("concurrency");
|
||||||
|
const captureBtn = document.getElementById("captureBtn");
|
||||||
|
const btnText = document.getElementById("btnText");
|
||||||
|
const selectBtn = document.getElementById("selectBtn");
|
||||||
|
const selectBtnText = document.getElementById("selectBtnText");
|
||||||
|
const status = document.getElementById("status");
|
||||||
|
const statusText = document.getElementById("statusText");
|
||||||
|
const progressFill = document.getElementById("progressFill");
|
||||||
|
const closeBtn = document.getElementById("closeBtn");
|
||||||
|
const helpLink = document.getElementById("helpLink");
|
||||||
|
|
||||||
|
function normalizeSettings(value = {}) {
|
||||||
|
const concurrency = String(value.concurrency || DEFAULT_SETTINGS.concurrency);
|
||||||
|
const captureWidth = normalizeCaptureWidth(value.captureWidth, null);
|
||||||
|
return {
|
||||||
|
useProxy: Boolean(value.useProxy),
|
||||||
|
concurrency: ["4", "6", "8", "10", "12", "16", "20", "infinite"].includes(concurrency)
|
||||||
|
? concurrency
|
||||||
|
: DEFAULT_SETTINGS.concurrency,
|
||||||
|
captureMode: value.captureMode === "editable" ? "editable" : "mixed",
|
||||||
|
captureWidth
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeCaptureWidth(value, fallback = null) {
|
||||||
|
const number = Number.parseInt(String(value || "").replace(/\D+/g, ""), 10);
|
||||||
|
if (!Number.isFinite(number)) return fallback;
|
||||||
|
return Math.max(MIN_CAPTURE_WIDTH, Math.min(MAX_CAPTURE_WIDTH, number));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getCurrentViewportWidth() {
|
||||||
|
try {
|
||||||
|
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
|
||||||
|
if (!tab?.id) return null;
|
||||||
|
const [{ result }] = await chrome.scripting.executeScript({
|
||||||
|
target: { tabId: tab.id },
|
||||||
|
func: () => Math.round(window.innerWidth || document.documentElement.clientWidth || 0)
|
||||||
|
});
|
||||||
|
return normalizeCaptureWidth(result, null);
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getSettings() {
|
||||||
|
const result = await chrome.storage.local.get({ [SETTINGS_KEY]: DEFAULT_SETTINGS });
|
||||||
|
return normalizeSettings(result[SETTINGS_KEY]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveSettings(settings) {
|
||||||
|
await chrome.storage.local.set({ [SETTINGS_KEY]: normalizeSettings(settings) });
|
||||||
|
}
|
||||||
|
|
||||||
|
function setProgress(percent, text) {
|
||||||
|
status.hidden = false;
|
||||||
|
progressFill.style.width = `${Math.max(0, Math.min(100, percent))}%`;
|
||||||
|
statusText.textContent = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setBusy(isBusy) {
|
||||||
|
captureBtn.disabled = isBusy;
|
||||||
|
selectBtn.disabled = isBusy;
|
||||||
|
captureBtn.classList.toggle("loading", isBusy);
|
||||||
|
selectBtn.classList.toggle("loading", isBusy);
|
||||||
|
captureBtn.classList.remove("success", "error");
|
||||||
|
btnText.textContent = isBusy ? "采集中..." : "开始采集";
|
||||||
|
selectBtnText.textContent = isBusy ? "正在打开浮窗..." : "打开页面浮窗";
|
||||||
|
}
|
||||||
|
|
||||||
|
function setResult(kind, text) {
|
||||||
|
captureBtn.classList.remove("loading", "success", "error");
|
||||||
|
captureBtn.classList.add(kind);
|
||||||
|
btnText.textContent = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function syncSettingsFromUI() {
|
||||||
|
await saveSettings({
|
||||||
|
captureMode: captureModeSelect.value,
|
||||||
|
useProxy: proxyToggle.checked,
|
||||||
|
concurrency: concurrencySelect.value,
|
||||||
|
captureWidth: normalizeCaptureWidth(captureWidthInput.value, null)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function sanitizeCaptureWidthInput({ clamp = false } = {}) {
|
||||||
|
const digits = captureWidthInput.value.replace(/\D+/g, "");
|
||||||
|
captureWidthInput.value = digits;
|
||||||
|
const width = normalizeCaptureWidth(digits, null);
|
||||||
|
const rawNumber = Number.parseInt(digits, 10);
|
||||||
|
let invalid = Boolean(digits) && Number.isFinite(rawNumber) &&
|
||||||
|
(rawNumber < MIN_CAPTURE_WIDTH || rawNumber > MAX_CAPTURE_WIDTH);
|
||||||
|
captureWidthInput.classList.toggle("invalid", invalid);
|
||||||
|
if (clamp && digits) {
|
||||||
|
captureWidthInput.value = String(width);
|
||||||
|
invalid = false;
|
||||||
|
captureWidthInput.classList.remove("invalid");
|
||||||
|
}
|
||||||
|
return width;
|
||||||
|
}
|
||||||
|
|
||||||
|
function readSettingsFromUI() {
|
||||||
|
return normalizeSettings({
|
||||||
|
captureMode: captureModeSelect.value,
|
||||||
|
useProxy: proxyToggle.checked,
|
||||||
|
concurrency: concurrencySelect.value,
|
||||||
|
captureWidth: sanitizeCaptureWidthInput({ clamp: true })
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function startCapture() {
|
||||||
|
setBusy(true);
|
||||||
|
setProgress(12, "准备当前网页...");
|
||||||
|
|
||||||
|
const settings = readSettingsFromUI();
|
||||||
|
await saveSettings(settings);
|
||||||
|
|
||||||
|
try {
|
||||||
|
setProgress(28, "注入采集脚本...");
|
||||||
|
const response = await chrome.runtime.sendMessage({
|
||||||
|
type: "PIXSO_CAPTURE_START",
|
||||||
|
options: settings
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response || !response.ok) {
|
||||||
|
throw new Error(response?.error || "采集失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
const viewportText = response.actualViewportWidth
|
||||||
|
? `已按 ${response.actualViewportWidth}px 采集`
|
||||||
|
: "已按当前页面尺寸采集";
|
||||||
|
setProgress(100, `${viewportText}:${response.filename}`);
|
||||||
|
setResult("success", "采集完成");
|
||||||
|
setTimeout(() => window.close(), 900);
|
||||||
|
} catch (error) {
|
||||||
|
setProgress(0, error.message || String(error));
|
||||||
|
setResult("error", "采集失败");
|
||||||
|
setTimeout(() => {
|
||||||
|
setBusy(false);
|
||||||
|
status.hidden = true;
|
||||||
|
progressFill.style.width = "0";
|
||||||
|
}, 2600);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function startFloatingToolbar() {
|
||||||
|
setBusy(true);
|
||||||
|
setProgress(18, "正在打开页面采集浮窗...");
|
||||||
|
|
||||||
|
const settings = readSettingsFromUI();
|
||||||
|
await saveSettings(settings);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
|
||||||
|
if (!tab?.id) throw new Error("没有可选择的当前标签页");
|
||||||
|
|
||||||
|
await chrome.scripting.executeScript({
|
||||||
|
target: { tabId: tab.id },
|
||||||
|
files: ["element-picker.js"]
|
||||||
|
});
|
||||||
|
|
||||||
|
await chrome.scripting.executeScript({
|
||||||
|
target: { tabId: tab.id },
|
||||||
|
func: pickerSettings => window.__webToPixsoShowCaptureToolbar?.(pickerSettings),
|
||||||
|
args: [settings]
|
||||||
|
});
|
||||||
|
|
||||||
|
setProgress(100, "请在页面浮窗中选择整页或元素采集。");
|
||||||
|
setTimeout(() => window.close(), 500);
|
||||||
|
} catch (error) {
|
||||||
|
setProgress(0, error.message || String(error));
|
||||||
|
setResult("error", "采集失败");
|
||||||
|
setTimeout(() => {
|
||||||
|
setBusy(false);
|
||||||
|
status.hidden = true;
|
||||||
|
progressFill.style.width = "0";
|
||||||
|
}, 2600);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener("DOMContentLoaded", async () => {
|
||||||
|
const settings = await getSettings();
|
||||||
|
const currentWidth = await getCurrentViewportWidth();
|
||||||
|
captureModeSelect.value = settings.captureMode;
|
||||||
|
proxyToggle.checked = settings.useProxy;
|
||||||
|
concurrencySelect.value = settings.concurrency;
|
||||||
|
captureWidthInput.value = String(settings.captureWidth || currentWidth || "");
|
||||||
|
});
|
||||||
|
|
||||||
|
captureModeSelect.addEventListener("change", syncSettingsFromUI);
|
||||||
|
proxyToggle.addEventListener("change", syncSettingsFromUI);
|
||||||
|
concurrencySelect.addEventListener("change", syncSettingsFromUI);
|
||||||
|
captureWidthInput.addEventListener("input", () => {
|
||||||
|
sanitizeCaptureWidthInput();
|
||||||
|
syncSettingsFromUI();
|
||||||
|
});
|
||||||
|
captureWidthInput.addEventListener("blur", () => {
|
||||||
|
sanitizeCaptureWidthInput({ clamp: true });
|
||||||
|
syncSettingsFromUI();
|
||||||
|
});
|
||||||
|
captureBtn.addEventListener("click", startCapture);
|
||||||
|
selectBtn.addEventListener("click", startFloatingToolbar);
|
||||||
|
closeBtn.addEventListener("click", () => window.close());
|
||||||
|
helpLink?.addEventListener("click", async event => {
|
||||||
|
event.preventDefault();
|
||||||
|
await chrome.tabs.create({ url: HELP_URL });
|
||||||
|
window.close();
|
||||||
|
});
|
||||||
@@ -0,0 +1,162 @@
|
|||||||
|
(function () {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
|
||||||
|
|
||||||
|
function freezeAnimations() {
|
||||||
|
const style = document.createElement("style");
|
||||||
|
style.id = "__web-to-pixso-freeze";
|
||||||
|
style.textContent = `
|
||||||
|
*, *::before, *::after {
|
||||||
|
animation-play-state: paused !important;
|
||||||
|
transition-duration: 0s !important;
|
||||||
|
scroll-behavior: auto !important;
|
||||||
|
}
|
||||||
|
.slick-track,
|
||||||
|
.swiper-wrapper {
|
||||||
|
transition-duration: 0s !important;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
document.documentElement.appendChild(style);
|
||||||
|
|
||||||
|
for (const media of document.querySelectorAll("video, audio")) {
|
||||||
|
try {
|
||||||
|
media.pause();
|
||||||
|
} catch {
|
||||||
|
// Ignore media elements that cannot be controlled by content scripts.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function stabilizeCarousels() {
|
||||||
|
try {
|
||||||
|
if (window.jQuery) {
|
||||||
|
window.jQuery(".slick-slider").each((_, element) => {
|
||||||
|
try {
|
||||||
|
window.jQuery(element).slick("slickPause");
|
||||||
|
window.jQuery(element).slick("slickGoTo", 0, true);
|
||||||
|
} catch {
|
||||||
|
// Some pages expose slick classes without the jQuery plugin instance.
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Best effort only.
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const element of document.querySelectorAll(".swiper, .swiper-container")) {
|
||||||
|
try {
|
||||||
|
if (element.swiper) {
|
||||||
|
element.swiper.autoplay?.stop?.();
|
||||||
|
element.swiper.slideToLoop?.(0, 0, false);
|
||||||
|
element.swiper.slideTo?.(0, 0, false);
|
||||||
|
element.swiper.update?.();
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Keep DOM capture running even if a carousel API rejects.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const wrapper of document.querySelectorAll(".swiper-wrapper, .slick-track")) {
|
||||||
|
wrapper.style.transitionDuration = "0s";
|
||||||
|
wrapper.style.animationPlayState = "paused";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function waitForStableTopLayer(timeout = 2500) {
|
||||||
|
const start = Date.now();
|
||||||
|
const selector = [
|
||||||
|
"header",
|
||||||
|
"nav",
|
||||||
|
"[role='navigation']",
|
||||||
|
"[class*='header' i]",
|
||||||
|
"[class*='nav' i]",
|
||||||
|
"[class*='top' i]"
|
||||||
|
].join(",");
|
||||||
|
|
||||||
|
while (Date.now() - start < timeout) {
|
||||||
|
const candidates = Array.from(document.querySelectorAll(selector));
|
||||||
|
const hasVisibleCandidate = candidates.some(element => {
|
||||||
|
const rect = element.getBoundingClientRect();
|
||||||
|
const style = window.getComputedStyle(element);
|
||||||
|
return rect.width > 20 &&
|
||||||
|
rect.height > 10 &&
|
||||||
|
rect.bottom >= 0 &&
|
||||||
|
rect.top < Math.max(160, window.innerHeight * 0.2) &&
|
||||||
|
style.display !== "none" &&
|
||||||
|
style.visibility !== "hidden" &&
|
||||||
|
Number(style.opacity || 1) > 0;
|
||||||
|
});
|
||||||
|
if (hasVisibleCandidate || document.readyState === "complete") {
|
||||||
|
await delay(300);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await delay(120);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function scrollToLoadLazyContent() {
|
||||||
|
const maxScroll = Math.max(
|
||||||
|
document.documentElement.scrollHeight,
|
||||||
|
document.body.scrollHeight
|
||||||
|
) - window.innerHeight;
|
||||||
|
|
||||||
|
if (maxScroll <= 0) return;
|
||||||
|
|
||||||
|
const step = Math.max(480, Math.floor(window.innerHeight * 0.8));
|
||||||
|
for (let y = 0; y <= maxScroll; y += step) {
|
||||||
|
window.scrollTo(0, Math.min(y, maxScroll));
|
||||||
|
await delay(160);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.scrollTo(0, maxScroll);
|
||||||
|
await delay(220);
|
||||||
|
window.scrollTo(0, 0);
|
||||||
|
await delay(220);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function waitForImages(timeout = 3500) {
|
||||||
|
const images = Array.from(document.images || []);
|
||||||
|
await Promise.race([
|
||||||
|
Promise.allSettled(images.map(image => {
|
||||||
|
if (image.complete) return Promise.resolve();
|
||||||
|
return new Promise(resolve => {
|
||||||
|
image.addEventListener("load", resolve, { once: true });
|
||||||
|
image.addEventListener("error", resolve, { once: true });
|
||||||
|
});
|
||||||
|
})),
|
||||||
|
delay(timeout)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function waitForFonts(timeout = 3000) {
|
||||||
|
if (!document.fonts?.ready) return;
|
||||||
|
await Promise.race([document.fonts.ready, delay(timeout)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.__webToPixsoRunCapture = async function runCapture(options = {}) {
|
||||||
|
if (!window.__webToPixsoCapture) {
|
||||||
|
throw new Error("采集引擎未加载");
|
||||||
|
}
|
||||||
|
|
||||||
|
freezeAnimations();
|
||||||
|
stabilizeCarousels();
|
||||||
|
await waitForStableTopLayer();
|
||||||
|
stabilizeCarousels();
|
||||||
|
await scrollToLoadLazyContent();
|
||||||
|
stabilizeCarousels();
|
||||||
|
await waitForImages();
|
||||||
|
await waitForFonts();
|
||||||
|
stabilizeCarousels();
|
||||||
|
await delay(200);
|
||||||
|
|
||||||
|
return window.__webToPixsoCapture({
|
||||||
|
useProxy: Boolean(options.useProxy),
|
||||||
|
concurrency: options.concurrency || "8",
|
||||||
|
captureMode: options.captureMode === "editable" ? "editable" : "mixed",
|
||||||
|
captureWidth: options.captureWidth,
|
||||||
|
selectionId: options.selectionId,
|
||||||
|
selectionWidth: options.selectionWidth
|
||||||
|
});
|
||||||
|
};
|
||||||
|
})();
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
# Web to Pixso 使用说明
|
||||||
|
|
||||||
|
Web to Pixso 可以将网页采集为可导入 Pixso 的高保真设计稿,适合网页参考稿采集、运营页转设计稿、竞品页面还原、开发页面转 Pixso 资产等场景。
|
||||||
|
|
||||||
|
插件包:`web-to-pixso-v1.1.1.zip`
|
||||||
|
|
||||||
|
使用说明在线文档:[Web to Pixso 使用说明](https://z8qrcvi3n5.feishu.cn/wiki/RV8TwlhFyiGsEekQXk8cX5SHn6f)
|
||||||
|
|
||||||
|
反馈邮箱:`270310136@qq.com`。给我发邮件哦,我光速改。
|
||||||
|
|
||||||
|
## 一、安装 Chrome 浏览器扩展
|
||||||
|
|
||||||
|
1. 解压 `web-to-pixso-v1.1.1.zip`。
|
||||||
|
2. 打开 Chrome 浏览器,进入 `chrome://extensions/`。
|
||||||
|
3. 打开右上角「开发者模式」。
|
||||||
|
4. 点击「加载已解压的扩展程序」。
|
||||||
|
5. 选择解压后的 `web-to-pixso` 文件夹。
|
||||||
|
6. 安装成功后,浏览器右上角会出现 Web to Pixso 扩展图标。
|
||||||
|
|
||||||
|
## 二、安装 Pixso 插件
|
||||||
|
|
||||||
|
1. 打开 Pixso。
|
||||||
|
2. 进入插件开发/导入插件入口。
|
||||||
|
3. 选择 `web-to-pixso/pixso-plugin` 目录中的插件文件。
|
||||||
|
4. 导入成功后,在 Pixso 插件面板中可以看到 Web to Pixso。
|
||||||
|
|
||||||
|
## 三、采集网页
|
||||||
|
|
||||||
|
1. 在 Chrome 中打开需要采集的目标网页。
|
||||||
|
2. 点击浏览器右上角 Web to Pixso 扩展图标。
|
||||||
|
3. 选择采集模式:
|
||||||
|
- 混合高保真:优先保留关键模块截图兜底,同时尽量保留文字可编辑。
|
||||||
|
- 可编辑优先:优先转换为 Pixso 原生文本、图片、形状和组件图层。
|
||||||
|
4. 可按需要设置:
|
||||||
|
- 跨域图片代理模式:用于减少图片丢失。
|
||||||
|
- 页面采集宽度:用于按指定视口宽度触发响应式页面布局。
|
||||||
|
- 图片采集并发:用于控制图片下载并发数量。
|
||||||
|
5. 点击「开始采集」。
|
||||||
|
6. 采集完成后,会下载一个 `.json` 文件。
|
||||||
|
|
||||||
|
## 四、导入 Pixso
|
||||||
|
|
||||||
|
1. 在 Pixso 中打开 Web to Pixso 插件。
|
||||||
|
2. 将 Chrome 扩展下载的 `.json` 文件拖拽到插件面板,或点击选择文件。
|
||||||
|
3. 插件会自动识别页面宽度和页面高度。
|
||||||
|
4. 点击「导入到 Pixso」。
|
||||||
|
5. 导入完成后,会创建一个以网页标题命名的画板。
|
||||||
|
|
||||||
|
## 五、导入后的图层结构
|
||||||
|
|
||||||
|
导入后的设计稿会按用途分层,方便设计师编辑和对比:
|
||||||
|
|
||||||
|
1. 对比底图层
|
||||||
|
- 放置完整页面截图或模块截图。
|
||||||
|
- 用于和上层可编辑元素进行还原度对比。
|
||||||
|
- 可隐藏或锁定。
|
||||||
|
|
||||||
|
2. 可编辑元素层
|
||||||
|
- 包含按钮、卡片、输入框、背景、图片、Logo、图标等。
|
||||||
|
- 尽量保留尺寸、位置、圆角、边框、阴影、透明度和背景样式。
|
||||||
|
|
||||||
|
3. 文字编辑层
|
||||||
|
- 包含网页中的可见文字。
|
||||||
|
- 尽量保留字体、字号、颜色、行高、字重、对齐方式等样式。
|
||||||
|
- 文字可在 Pixso 中直接编辑。
|
||||||
|
|
||||||
|
## 六、常见问题
|
||||||
|
|
||||||
|
### 1. 为什么有些区域是截图?
|
||||||
|
|
||||||
|
部分网页使用复杂动画、轮播、视频、Canvas、伪元素、复杂背景或特殊渲染方式。为了保证视觉效果不丢失,插件会为这些区域生成截图兜底,同时尽量保留文字和主要元素可编辑。
|
||||||
|
|
||||||
|
### 2. 为什么有些图片没有显示?
|
||||||
|
|
||||||
|
可能是目标网站启用了跨域限制、懒加载、防盗链或动态鉴权。可以尝试开启「跨域图片代理模式」后重新采集。
|
||||||
|
|
||||||
|
### 3. 为什么 Header 或 Hero 区域有时不可编辑?
|
||||||
|
|
||||||
|
部分网站的导航栏、轮播图或首屏区域可能由复杂脚本、Shadow DOM、Canvas、视频或异步渲染生成。插件会优先转换为可编辑图层,并使用兜底截图保证视觉可对比。
|
||||||
|
|
||||||
|
### 4. 页面采集宽度有什么用?
|
||||||
|
|
||||||
|
页面采集宽度用于模拟不同视口下的响应式布局。例如输入 `1920` 可以采集桌面宽屏布局,输入 `375` 可以采集移动端布局。
|
||||||
|
|
||||||
|
### 5. 导入后如何检查还原度?
|
||||||
|
|
||||||
|
可以先显示「对比底图层」,再查看上方的可编辑元素层和文字编辑层是否与底图对齐。对比完成后,可以隐藏或锁定底图。
|
||||||
|
|
||||||
|
## 七、适用场景
|
||||||
|
|
||||||
|
- 网页转 Pixso 设计稿
|
||||||
|
- 竞品页面采集
|
||||||
|
- 运营活动页还原
|
||||||
|
- 开发页面转设计资产
|
||||||
|
- 设计走查和页面对比
|
||||||
|
- 旧页面重构前的视觉备份
|
||||||
|
|
||||||
|
## 八、版本说明
|
||||||
|
|
||||||
|
当前版本:V1.1.1
|
||||||
|
|
||||||
|
支持从 Web to Pixso Chrome 扩展导出的 JSON 文件导入 Pixso,生成包含可编辑文本、图片、背景、按钮、卡片和对比底图的高保真网页设计稿。
|
||||||
|
|
||||||
|
本版重点:
|
||||||
|
|
||||||
|
- Chrome 扩展和 Pixso 插件版本号统一为 `1.1.1`。
|
||||||
|
- 插件面板显示版本号,避免用户混淆安装包。
|
||||||
|
- Chrome 扩展和 Pixso 插件均新增「使用说明」入口,点击后打开飞书文档。
|
||||||
|
- 两端插件底部均新增反馈邮箱:`270310136@qq.com`。
|
||||||
|
- 采集 JSON 协议版本同步为 `1.1.1`,方便问题排查。
|
||||||
|
- 保留页面采集宽度、混合高保真、可编辑优先、跨域图片代理和图片采集并发能力。
|
||||||
|
- 继续强化三层结构:对比底图层、可编辑元素层、文字编辑层。
|
||||||
|
|
||||||
|
已知边界:
|
||||||
|
|
||||||
|
- 复杂动画、Canvas、视频、动态轮播和强跨域资源可能仍需要兜底截图或人工微调。
|
||||||
|
- 真实网页还原质量会受目标网站资源加载、登录态、懒加载和浏览器环境影响。
|
||||||
|
- 对外使用时建议先用对比底图层检查还原度,再进行设计稿编辑。
|
||||||