在 JavaScript 中管理演示文稿超链接
介绍
超链接将演示文稿内容连接到网站或演示文稿内部的某个位置。在 PowerPoint 中,超链接通常有两种用途:
- 从文本、形状或媒体框打开网站。
- 导航到另一张幻灯片,例如从目录跳转。
Aspose.Slides for Node.js via Java 允许您添加这些链接、控制其外观和声音、更新属性并删除它们。下面的示例展示了如何在单个元素上使用超链接,以及如何在演示文稿、幻灯片或文本框级别访问超链接。
Note
您还可以使用免费在线 Aspose PowerPoint 编辑器编辑演示文稿。添加 URL 超链接
您可以将网站 URL 分配给文本、形状或媒体框。分配超链接的元素决定了可点击区域:文本部分链接所选文本,形状或框则链接整个幻灯片对象。
对于包含相对超链接的演示文稿,请参阅Manage the Hyperlink Base以读取、修改或清除文档级别的基址。
向文本添加 URL 超链接
要将文本链接到网站,请将一个Hyperlink传递给文本部分的setHyperlinkClick方法,如下所示。只有该文本部分会变为可点击。
const aspose = { slides: require("aspose.slides.via.java") };
const presentation = new aspose.slides.Presentation();
try {
const textShape = presentation.getSlides().get_Item(0).getShapes().addAutoShape(aspose.slides.ShapeType.Rectangle, 100, 100, 600, 50, false);
textShape.addTextFrame("Aspose: File Format APIs");
const portionFormat = textShape.getTextFrame().getParagraphs().get_Item(0).getPortions().get_Item(0).getPortionFormat();
portionFormat.setHyperlinkClick(new aspose.slides.Hyperlink("https://www.aspose.com/"));
portionFormat.getHyperlinkClick().setTooltip("Explore Aspose file format APIs");
portionFormat.setFontHeight(32);
presentation.save("presentation-out.pptx", aspose.slides.SaveFormat.Pptx);
} finally {
presentation.dispose();
}
向形状和媒体框添加 URL 超链接
要使形状或框可点击,调用其setHyperlinkClick方法。超链接属于对象本身,而不是其中的文本部分。
相同的做法适用于图片、音频和视频框:将超链接分配给框,并在需要时调用setTooltip。
下面的示例使一个矩形可点击:
const aspose = { slides: require("aspose.slides.via.java") };
const presentation = new aspose.slides.Presentation();
try {
const shape = presentation.getSlides().get_Item(0).getShapes().addAutoShape(aspose.slides.ShapeType.Rectangle, 100, 100, 600, 50);
shape.setHyperlinkClick(new aspose.slides.Hyperlink("https://www.aspose.com/"));
shape.getHyperlinkClick().setTooltip("Explore Aspose file format APIs");
presentation.save("presentation-out.pptx", aspose.slides.SaveFormat.Pptx);
} finally {
presentation.dispose();
}
使用超链接创建目录
内部超链接让读者可以从目录跳转到指定幻灯片。以下示例使用setInternalHyperlinkClick将第一张幻灯片上的“第2页”文本链接到第二张幻灯片。
const aspose = { slides: require("aspose.slides.via.java") };
const java = require("java");
const presentation = new aspose.slides.Presentation();
try {
const firstSlide = presentation.getSlides().get_Item(0);
const secondSlide = presentation.getSlides().addEmptySlide(firstSlide.getLayoutSlide());
const tableOfContents = firstSlide.getShapes().addAutoShape(aspose.slides.ShapeType.Rectangle, 40, 40, 300, 100);
tableOfContents.getFillFormat().setFillType(java.newByte(aspose.slides.FillType.NoFill));
tableOfContents.getLineFormat().getFillFormat().setFillType(java.newByte(aspose.slides.FillType.NoFill));
tableOfContents.getTextFrame().getParagraphs().clear();
const paragraph = new aspose.slides.Paragraph();
paragraph.getParagraphFormat().getDefaultPortionFormat().getFillFormat().setFillType(java.newByte(aspose.slides.FillType.Solid));
paragraph.getParagraphFormat().getDefaultPortionFormat().getFillFormat().getSolidFillColor().setColor(java.getStaticFieldValue("java.awt.Color", "BLACK"));
paragraph.setText("Title of slide 2 .......... ");
const linkPortion = new aspose.slides.Portion();
linkPortion.setText("Page 2");
linkPortion.getPortionFormat().getHyperlinkManager().setInternalHyperlinkClick(secondSlide);
paragraph.getPortions().add(linkPortion);
tableOfContents.getTextFrame().getParagraphs().add(paragraph);
presentation.save("link_to_slide.pptx", aspose.slides.SaveFormat.Pptx);
} finally {
presentation.dispose();
}
格式化超链接
颜色
Hyperlink的setColorSource方法决定超链接是使用演示文稿的超链接颜色还是文本部分的格式。要应用自定义文本颜色,请选择HyperlinkColorSource.PortionFormat并设置该部分的填充颜色。此功能在 PowerPoint 2019 中引入;较早版本不支持此设置。
下面的示例在同一张幻灯片上添加了两个文本超链接。第一个使用红色填充,第二个保留默认的超链接颜色。
const aspose = { slides: require("aspose.slides.via.java") };
const java = require("java");
const presentation = new aspose.slides.Presentation();
try {
const coloredShape = presentation.getSlides().get_Item(0).getShapes().addAutoShape(aspose.slides.ShapeType.Rectangle, 100, 100, 450, 50, false);
coloredShape.addTextFrame("This hyperlink uses a custom color.");
const coloredPortionFormat = coloredShape.getTextFrame().getParagraphs().get_Item(0).getPortions().get_Item(0).getPortionFormat();
coloredPortionFormat.setHyperlinkClick(new aspose.slides.Hyperlink("https://www.aspose.com/"));
coloredPortionFormat.getHyperlinkClick().setColorSource(aspose.slides.HyperlinkColorSource.PortionFormat);
coloredPortionFormat.getFillFormat().setFillType(java.newByte(aspose.slides.FillType.Solid));
coloredPortionFormat.getFillFormat().getSolidFillColor().setColor(java.getStaticFieldValue("java.awt.Color", "RED"));
const defaultShape = presentation.getSlides().get_Item(0).getShapes().addAutoShape(aspose.slides.ShapeType.Rectangle, 100, 200, 450, 50, false);
defaultShape.addTextFrame("This hyperlink uses the default color.");
defaultShape.getTextFrame().getParagraphs().get_Item(0).getPortions().get_Item(0).getPortionFormat().setHyperlinkClick(new aspose.slides.Hyperlink("https://www.aspose.com/"));
presentation.save("presentation-out-hyperlink.pptx", aspose.slides.SaveFormat.Pptx);
} finally {
presentation.dispose();
}
声音
超链接可以在激活时播放声音,或停止已经在播放的声音。使用以下方法配置这些行为:
- Hyperlink.setSound指定与超链接关联的音频。
- Hyperlink.setStopSoundOnClick控制激活超链接时是否停止先前的声音。
添加超链接声音
下面的示例加载sampleaudio.wav并将其关联到第一张幻灯片上的按钮。单击按钮会播放声音并跳转到下一张幻灯片。该幻灯片上的第二个形状在单击时停止先前的声音,但不执行跳转操作。
const aspose = { slides: require("aspose.slides.via.java") };
const java = require("java");
const presentation = new aspose.slides.Presentation();
try {
const audioStream = java.newInstanceSync("java.io.FileInputStream", "sampleaudio.wav");
let hyperlinkSound;
try {
hyperlinkSound = presentation.getAudios().addAudio(audioStream);
} finally {
audioStream.close();
}
const firstSlide = presentation.getSlides().get_Item(0);
const playButton = firstSlide.getShapes().addAutoShape(aspose.slides.ShapeType.SoundButton, 100, 100, 100, 50);
playButton.setHyperlinkClick(aspose.slides.Hyperlink.getNextSlide());
if (!playButton.getHyperlinkClick().getStopSoundOnClick() && playButton.getHyperlinkClick().getSound() == null)
{
playButton.getHyperlinkClick().setSound(hyperlinkSound);
}
const secondSlide = presentation.getSlides().addEmptySlide(firstSlide.getLayoutSlide());
const stopButton = secondSlide.getShapes().addAutoShape(aspose.slides.ShapeType.Rectangle, 100, 100, 100, 50);
stopButton.setHyperlinkClick(aspose.slides.Hyperlink.getNoAction());
stopButton.getHyperlinkClick().setStopSoundOnClick(true);
presentation.save("hyperlink-sound.pptx", aspose.slides.SaveFormat.Pptx);
} finally {
presentation.dispose();
}
提取超链接声音
下面的示例打开上述创建的演示文稿,并通过getSound和getBinaryData读取第一个形状的超链接音频到内存中。
const aspose = { slides: require("aspose.slides.via.java") };
const presentation = new aspose.slides.Presentation("hyperlink-sound.pptx");
try {
if (presentation.getSlides().size() > 0 && presentation.getSlides().get_Item(0).getShapes().size() > 0) {
const hyperlink = presentation.getSlides().get_Item(0).getShapes().get_Item(0).getHyperlinkClick();
const sound = hyperlink == null ? null : hyperlink.getSound();
if (sound != null) {
const audioData = sound.getBinaryData();
console.log("Extracted " + audioData.length + " bytes of hyperlink audio.");
} else {
console.log("The first shape has no hyperlink sound.");
}
} else {
console.log("The presentation has no first slide or shape to inspect.");
}
} finally {
presentation.dispose();
}
提示和交互设置
在为文本或形状分配超链接后,您可以调用以下Hyperlink方法:
- setTooltip设置观众在悬停时看到的提示文本。
- setTargetFrame在适用时指定父 HTML 框架集中的目标框架。
- setHistory控制激活链接时是否将其目标加入已查看超链接列表。
- setHighlightClick控制单击时是否高亮显示超链接。
从演示文稿中移除超链接
使用getAnyHyperlinks收集包括文本部分链接在内的超链接容器,然后再对其进行修改。下面的示例从第一张幻灯片移除两种激活类型。若只想移除一种类型,只调用removeHyperlinkClick或removeHyperlinkMouseOver即可;移除点击操作并不会移除其鼠标悬停对应的操作。
const aspose = { slides: require("aspose.slides.via.java") };
const presentation = new aspose.slides.Presentation("pres.pptx");
try {
if (presentation.getSlides().size() > 0) {
const found = presentation.getSlides().get_Item(0).getHyperlinkQueries().getAnyHyperlinks();
const containers = [];
for (let index = 0; index < found.size(); index++) {
containers.push(found.get_Item(index));
}
for (const container of containers) {
container.getHyperlinkManager().removeHyperlinkClick();
container.getHyperlinkManager().removeHyperlinkMouseOver();
}
presentation.save("pres-removed-hyperlinks.pptx", aspose.slides.SaveFormat.Pptx);
} else {
console.log("The presentation has no slides to process.");
}
} finally {
presentation.dispose();
}
若要无条件移除,removeAllHyperlinks在一次调用中删除选定范围内的两种激活类型。若需有针对性地清理并覆盖母版、布局和备注,请参阅报告、清理和验证超链接。
构建完整的超链接清单
在分发演示文稿之前,先盘点其交互操作以及网络链接。getAnyHyperlinks返回的是超链接容器,而不是 URL 字符串的平面列表。检查每个容器的getHyperlinkClick和getHyperlinkMouseOver。它们是独立的:同一容器可以同时暴露两种操作,因此完整报告可能需要每个容器最多两行。
仅扫描形状级别的超链接可能会遗漏附加在文本部分上的链接。请改为查询适当的范围,并保留返回的容器,以便后续更新或移除其操作。
查询演示文稿、幻灯片和文本框范围
HyperlinkQueries类可通过Presentation.getHyperlinkQueries、BaseSlide.getHyperlinkQueries和TextFrame.getHyperlinkQueries获取。每个范围支持相同的查询:
- getHyperlinkClicks返回包含点击操作的容器。
- getHyperlinkMouseOvers返回包含鼠标悬停操作的容器。
- getAnyHyperlinks返回包含任一或两种操作的容器。
下面的示例创建 hyperlink-audit-input.pptx,其中包含外部点击链接、文件鼠标悬停链接、内部幻灯片导航、文本鼠标悬停链接以及宏操作。示例本身不会执行这些操作。相同的三个查询在每个范围内均可使用;计数描述的是容器数量,而不是操作总数。文本框范围不包括其所属形状的链接。
const aspose = { slides: require("aspose.slides.via.java") };
function printQueryCounts(scope, queries) {
const clickCount = queries.getHyperlinkClicks().size();
const mouseOverCount = queries.getHyperlinkMouseOvers().size();
const anyCount = queries.getAnyHyperlinks().size();
console.log(scope + ": click=" + clickCount + ", mouse-over=" + mouseOverCount + ", any=" + anyCount);
}
const presentation = new aspose.slides.Presentation();
try {
const slide = presentation.getSlides().get_Item(0);
const destination = presentation.getSlides().addEmptySlide(slide.getLayoutSlide());
const shape = slide.getShapes().addAutoShape(aspose.slides.ShapeType.Rectangle, 20, 20, 400, 60);
shape.getTextFrame().setText("Click the text to go to slide 2");
shape.getHyperlinkManager().setExternalHyperlinkClick("https://example.com/");
shape.getHyperlinkClick().setTooltip("Public website");
shape.getHyperlinkManager().setExternalHyperlinkMouseOver("file:///C:/private/report.xlsx");
const portionFormat = shape.getTextFrame().getParagraphs().get_Item(0).getPortions().get_Item(0).getPortionFormat();
portionFormat.getHyperlinkManager().setInternalHyperlinkClick(destination);
portionFormat.getHyperlinkManager().setExternalHyperlinkMouseOver("https://example.com/help");
const macroButton = slide.getShapes().addAutoShape(aspose.slides.ShapeType.Rectangle, 20, 120, 200, 60);
macroButton.getHyperlinkManager().setMacroHyperlinkClick("ReviewPresentation");
printQueryCounts("Presentation", presentation.getHyperlinkQueries());
printQueryCounts("Slide 1", slide.getHyperlinkQueries());
printQueryCounts("Text frame", shape.getTextFrame().getHyperlinkQueries());
presentation.save("hyperlink-audit-input.pptx", aspose.slides.SaveFormat.Pptx);
} finally {
presentation.dispose();
}
在本例中,演示文稿和幻灯片查询各报告 3 个点击容器、2 个鼠标悬停容器以及 3 个包含任一操作的容器。文本框查询在每个类别中报告 1 个容器。
对操作和目标进行分类
使用Hyperlink.getActionType在解释目标之前先判断操作类型。HyperlinkActionType的取值涵盖了除网页导航之外的多种情形:
| Values | Meaning for an audit |
|---|---|
Hyperlink |
外部超链接;检查 URL 及其协议。 |
JumpSpecificSlide |
跳转到特定幻灯片的内部导航。 |
JumpFirstSlide, JumpPreviousSlide, JumpNextSlide, JumpLastSlide, JumpLastViewedSlide |
内置的幻灯片放映导航,在放映环境中解析。 |
JumpEndShow, StartCustomSlideShow |
结束当前放映或启动自定义放映。 |
StartMacro |
执行宏。 |
StartProgram |
启动程序。 |
OpenFile, OpenPresentation |
打开文件或其他演示文稿;需单独审查,区别于网页 URL。 |
StartStopMedia |
开始或停止媒体播放。 |
NoAction, Unknown |
无导航操作,或未识别的操作,需要审查。 |
通过getExternalUrl读取外部目标,通过getTargetSlide读取具体的内部目标。内部操作和内置命令可能没有外部 URL;空 URL 并不意味着容器没有操作。若getExternalUrlOriginal返回的值与规范化后的 URL 不同,请保留原始值,并在有可用提示时包含getTooltip返回的提示文本。
报告、清理和验证超链接
下面的 JavaScript 示例读取已有演示文稿(使用上面创建的文件),写入 hyperlink-audit.json,应用策略后保存为 hyperlink-sanitized.pptx,并再次打开检查两种激活类型。示例在更改前收集容器,并使用引用相等性避免对同一容器重复处理。演示文稿查询覆盖普通幻灯片;若需对整个包进行盘点,还会显式查询母版、布局、备注以及存在的备注和讲义母版。
报告记录基于 1 的幻灯片索引和getSlideId(若可用)。getSlide提供支持容器的所属幻灯片。母版、布局和备注没有普通幻灯片索引,以其范围标识。形状容器和文本部分格式容器单独标记;其他容器类型保留其运行时类型名称。每个容器获得报告本地 ID,以便关联其两种操作。报告将操作类型存为 HyperlinkActionType 枚举定义的整数常量。
此严格的应用策略仅允许绝对的 HTTPS URL 和有效的内部幻灯片目标。它会拒绝宏、程序、文件操作、其他放映操作、未知操作以及其他 URL 方案。这些拒绝属于策略决定,而非 Aspose.Slides 的安全判定。仅 HTTPS 并不能保证可信:请为您的应用添加主机白名单及其他检查。原始和规范化的外部 URL 都会被检查。示例仅审计元数据,不会跟随链接或执行操作。
针对整改,容器的getHyperlinkManager支持setExternalHyperlinkClick、removeHyperlinkClick和removeHyperlinkMouseOver。在此示例中,受限的外部点击链接被替换为固定的 HTTPS 着陆页;其他受限的点击和受限的鼠标悬停操作则分别被移除。将 replaceExternalClicks 设置为 false 可直接删除所有策略违规项。请在部署前准备好应用自有的替换页面。
报告的导出标记采用保守的 PDF 审核策略:将鼠标悬停操作以及除外部链接或特定幻灯片跳转之外的所有内容标记为可能不受支持。这仅是审查提示,而非功能测试,也不保证未标记的链接在导出后一定可用。受支持的PDF和HTML导出可能保留超链接,具体取决于操作、导出选项和查看器。栅格图像和视频则无法保留交互式超链接;在针对这些输出进行审计时,请为每个操作标记。
const aspose = { slides: require("aspose.slides.via.java") };
const java = require("java");
const fs = require("fs");
function slideIndex(presentation, slide) {
if (slide == null) return null;
for (let index = 0; index < presentation.getSlides().size(); index++) {
if (presentation.getSlides().get_Item(index).equals(slide)) return index + 1;
}
return null;
}
function isHttps(value) {
if (value == null || value.length === 0) return false;
try {
const uri = java.newInstanceSync("java.net.URI", value);
const scheme = uri.getScheme();
return uri.isAbsolute() && scheme != null && scheme.toLowerCase() === "https" && uri.getHost() != null;
} catch (exception) {
return false;
}
}
function policyViolation(link) {
if (link == null) return null;
if (link.getActionType() === aspose.slides.HyperlinkActionType.JumpSpecificSlide) {
return link.getTargetSlide() == null ? "Missing target slide" : null;
}
if (link.getActionType() !== aspose.slides.HyperlinkActionType.Hyperlink) return "Action is not allowed";
if (!isHttps(link.getExternalUrl())) return "Normalized URL is not absolute HTTPS";
const original = link.getExternalUrlOriginal();
if (original != null && original.length > 0 && !isHttps(original)) return "Original URL is not absolute HTTPS";
return null;
}
function collectContainers(presentation) {
const found = [];
function addQueries(queries) {
const containers = queries.getAnyHyperlinks();
for (let index = 0; index < containers.size(); index++) {
found.push(containers.get_Item(index));
}
}
function addScope(slide) {
if (slide != null) addQueries(slide.getHyperlinkQueries());
}
addQueries(presentation.getHyperlinkQueries());
for (let index = 0; index < presentation.getMasters().size(); index++) {
addScope(presentation.getMasters().get_Item(index));
}
for (let index = 0; index < presentation.getLayoutSlides().size(); index++) {
addScope(presentation.getLayoutSlides().get_Item(index));
}
for (let index = 0; index < presentation.getSlides().size(); index++) {
addScope(presentation.getSlides().get_Item(index).getNotesSlideManager().getNotesSlide());
}
addScope(presentation.getMasterNotesSlideManager().getMasterNotesSlide());
addScope(presentation.getMasterHandoutSlideManager().getMasterHandoutSlide());
const seen = java.newInstanceSync("java.util.IdentityHashMap");
const unique = [];
for (const container of found) {
if (!seen.containsKey(container)) {
seen.put(container, true);
unique.push(container);
}
}
return unique;
}
function addRow(rows, presentation, link, activation, container, containerId) {
if (link == null) return;
const ownerSlide = java.instanceOf(container, "com.aspose.slides.ISlideComponent") ? container.getSlide() : null;
const targetSlide = link.getTargetSlide();
const violation = policyViolation(link);
const ownerType = java.instanceOf(container, "com.aspose.slides.IShape") ? "Shape" : java.instanceOf(container, "com.aspose.slides.IPortionFormat") ? "Text portion" : container.getClass().getSimpleName();
const ordinaryAction = link.getActionType() === aspose.slides.HyperlinkActionType.Hyperlink || link.getActionType() === aspose.slides.HyperlinkActionType.JumpSpecificSlide;
rows.push({
ContainerId: containerId,
SlideIndex: slideIndex(presentation, ownerSlide),
SlideId: ownerSlide == null ? null : ownerSlide.getSlideId(),
Scope: ownerSlide == null ? null : ownerSlide.getClass().getSimpleName(),
OwnerType: ownerType,
Activation: activation,
ActionType: link.getActionType(),
ExternalUrl: link.getExternalUrl(),
TargetSlideIndex: slideIndex(presentation, targetSlide),
TargetSlideId: targetSlide == null ? null : targetSlide.getSlideId(),
Tooltip: link.getTooltip(),
OriginalExternalUrl: link.getExternalUrlOriginal() === link.getExternalUrl() ? null : link.getExternalUrlOriginal(),
PotentiallyUnsafe: violation != null,
PolicyViolation: violation,
TargetExport: "PDF",
PotentiallyUnsupportedByExport: activation === "mouse-over" || !ordinaryAction
});
}
const replaceExternalClicks = true;
const replacementUrl = "https://example.com/blocked-link";
const presentation = new aspose.slides.Presentation("hyperlink-audit-input.pptx");
try {
const containers = collectContainers(presentation);
const rows = [];
for (let index = 0; index < containers.length; index++) {
const container = containers[index];
addRow(rows, presentation, container.getHyperlinkClick(), "click", container, index + 1);
addRow(rows, presentation, container.getHyperlinkMouseOver(), "mouse-over", container, index + 1);
}
const json = JSON.stringify(rows, null, 2);
fs.writeFileSync("hyperlink-audit.json", json, "utf8");
for (const container of containers) {
const click = container.getHyperlinkClick();
if (policyViolation(click) != null) {
if (replaceExternalClicks && click.getActionType() === aspose.slides.HyperlinkActionType.Hyperlink) {
container.getHyperlinkManager().setExternalHyperlinkClick(replacementUrl);
} else {
container.getHyperlinkManager().removeHyperlinkClick();
}
}
if (policyViolation(container.getHyperlinkMouseOver()) != null) {
container.getHyperlinkManager().removeHyperlinkMouseOver();
}
}
presentation.save("hyperlink-sanitized.pptx", aspose.slides.SaveFormat.Pptx);
const reopened = new aspose.slides.Presentation("hyperlink-sanitized.pptx");
try {
const remainingContainers = collectContainers(reopened);
let violations = 0;
for (const container of remainingContainers) {
if (policyViolation(container.getHyperlinkClick()) != null) violations++;
if (policyViolation(container.getHyperlinkMouseOver()) != null) violations++;
}
console.log("Audit rows: " + rows.length + "; prohibited actions after reopening: " + violations);
if (violations !== 0) {
console.log("Verification failed: do not distribute the saved presentation.");
}
} finally {
reopened.dispose();
}
} finally {
presentation.dispose();
}
使用上面创建的输入,报告包含五行操作。文件鼠标悬停链接和宏点击被移除,HTTPS 链接和内部幻灯片导航保留。验证阶段打印零个违规操作。包含受限外部点击 URL 的输入还演示了替换分支。一个允许点击但受限鼠标悬停的容器保留其点击操作。
此有选择的清理不同于removeAllHyperlinks,后者会在所选范围内不顾策略统一移除两种激活类型。此处的验证仅检查超链接操作;它不会移除嵌入的 VBA 项目、OLE 对象或其他活动内容,也不对导出的 PDF 或 HTML 文件进行有效性验证。
常见问题
如何链接到某个章节或其第一张幻灯片?
PowerPoint 中的章节用于分组幻灯片,但内部超链接只能定位到单张幻灯片。若要实现章节导航,请链接到该章节的第一张幻灯片。
我可以将超链接附加到母版幻灯片元素,使其在所有幻灯片上生效吗?
可以。母版幻灯片和布局元素都支持超链接。这些链接在使用相应母版或布局的幻灯片放映期间可用。
导出为 PDF、HTML、图像或视频时,超链接会被保留吗?
受支持的 PDF 和 HTML 导出可能会保留超链接;栅格图像和视频则无法保留。有关导出注意事项,请参阅报告、清理和验证超链接。