| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222 |
- <!doctype html>
- <html lang="zh-CN">
- <head>
- <meta charset="utf-8" />
- <meta name="viewport" content="width=device-width, initial-scale=1" />
- <title>OnlyOffice 预览</title>
- <style>
- html,body{height:100%;margin:0;font-family:system-ui,-apple-system,Segoe UI,Roboto,Arial,sans-serif;background:#f5f7fa}
- .bar{padding:10px 14px;background:#111827;color:#fff;font-size:14px;word-break:break-all}
- .bar a{color:#93c5fd;text-decoration:none}
- .err{color:#fecaca}
- .ok{color:#86efac}
- .warn{color:#fde68a}
- #editor{height:calc(100% - 44px)}
- </style>
- <script src="/onlyoffice/web-apps/apps/api/documents/api.js"></script>
- </head>
- <body>
- <div class="bar" id="hint"></div>
- <div id="editor"></div>
- <script>
- const hintEl = document.getElementById("hint");
- const p = new URLSearchParams(location.search);
- const rawUrl = p.get("url") || "";
- const title = p.get("title") || (rawUrl ? rawUrl.split("/").pop() : "untitled.docx");
- const fileType = (p.get("type") || title.split(".").pop() || "docx").toLowerCase();
- const fileDocType = docType(fileType);
- const isCellDoc = fileDocType === "cell";
- const requestedMode = (p.get("mode") || "").toLowerCase();
- const forceReadOnly = (p.get("readonly") || "").toLowerCase() === "1";
- const editorMode = requestedMode || (isCellDoc ? "edit" : "view");
- function docType(ext){
- if(["xlsx","xls","csv","ods"].includes(ext)) return "cell";
- if(["pptx","ppt","odp"].includes(ext)) return "slide";
- return "word";
- }
- function stableKey(str){
- let h = 2166136261;
- for (const ch of str) {
- h ^= ch.codePointAt(0);
- h += (h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24);
- }
- return (h >>> 0).toString(16);
- }
- function hasExt(name){ return /\.[^\/]+$/.test(name || ""); }
- function inferDirFromReferrer(){
- if(!document.referrer) return "";
- try {
- const r = new URL(document.referrer);
- let pth = decodeURIComponent(r.pathname || "");
- if (!pth.startsWith("/files/")) return "";
- let rel = pth.slice(7).replace(/^\/+|\/+$/g, "");
- if(!rel) return "";
- const arr = rel.split("/");
- if(arr.length && hasExt(arr[arr.length - 1])) arr.pop();
- return arr.join("/");
- } catch (e) {
- return "";
- }
- }
- function normalizeRawUrl(input, titleText) {
- try {
- const u = new URL(input, location.origin);
- let path = decodeURIComponent(u.pathname);
- const t = decodeURIComponent(titleText || "").replace(/^\/+/, "");
- const dup = path.match(/^(.*\/)([^\/]+\.[^\/]+)\/\2$/);
- if (dup) path = dup[1] + dup[2];
- const mFile = path.match(/^(\/raw\/files\/)([^\/]+\.[^\/]+)$/);
- if (mFile) {
- const base = mFile[2];
- let resolved = "";
- if (t.includes("/") && (t === base || t.endsWith("/" + base))) {
- resolved = t;
- }
- if (!resolved) {
- const refDir = inferDirFromReferrer();
- if (refDir) resolved = refDir + "/" + base;
- }
- if (resolved) path = mFile[1] + resolved;
- }
- u.pathname = path;
- return u.toString();
- } catch (e) {
- return input;
- }
- }
- if(!rawUrl){
- hintEl.innerHTML = '<span class="err">缺少参数: ?url=<文件URL>&type=<扩展名>&title=<文件名></span>';
- throw new Error("missing file url");
- }
- const normalizedRawUrl = normalizeRawUrl(rawUrl, title);
- let fileUrl = normalizedRawUrl;
- try {
- fileUrl = encodeURI(decodeURIComponent(normalizedRawUrl));
- } catch (e) {
- fileUrl = encodeURI(normalizedRawUrl);
- }
- const nonce = `${Date.now()}-${Math.random().toString(16).slice(2,8)}`;
- const fileUrlWithNonce = fileUrl + (fileUrl.includes("?") ? "&" : "?") + `_v=${nonce}`;
- const docKey = `${stableKey(fileUrlWithNonce)}-${stableKey(title)}-${fileType}-${nonce}`.slice(0, 120);
- hintEl.innerHTML = `预览中: ${title} | <a href='${fileUrl}' target='_blank'>原文件</a> | <span class='warn'>正在检测文件可达性...</span>`;
- async function precheck(){
- try {
- const r = await fetch(fileUrlWithNonce, { method: "GET", cache: "no-store" });
- if (!r.ok) {
- hintEl.innerHTML = `预览中: ${title} | <a href='${fileUrl}' target='_blank'>原文件</a> | <span class='err'>源文件检测失败: HTTP ${r.status}</span>`;
- return false;
- }
- hintEl.innerHTML = `预览中: ${title} | <a href='${fileUrl}' target='_blank'>原文件</a> | <span class='ok'>源文件检测通过</span>`;
- return true;
- } catch (e) {
- hintEl.innerHTML = `预览中: ${title} | <a href='${fileUrl}' target='_blank'>原文件</a> | <span class='err'>源文件检测异常: ${String(e && e.message ? e.message : e)}</span>`;
- return false;
- }
- }
- function render(){
- const permissions = isCellDoc
- ? {
- edit: !forceReadOnly,
- review: false,
- comment: false,
- chat: false,
- copy: true,
- download: true,
- print: true
- }
- : {
- edit: false,
- review: false,
- comment: false,
- copy: true,
- download: true,
- print: true
- };
- const customization = {
- autosave: false,
- forcesave: false,
- compactHeader: false,
- toolbarNoTabs: false,
- statusBar: true
- };
- if (isCellDoc) {
- customization.showVerticalScroll = true;
- customization.showHorizontalScroll = true;
- customization.toolbar = true;
- customization.leftMenu = true;
- customization.rightMenu = false;
- customization.layout = {
- toolbar: true,
- leftMenu: true,
- rightMenu: false,
- statusBar: true
- };
- }
- try {
- new DocsAPI.DocEditor("editor", {
- documentType: fileDocType,
- type: "desktop",
- width: "100%",
- height: "100%",
- document: {
- title: title,
- url: fileUrlWithNonce,
- fileType: fileType,
- key: docKey,
- permissions: permissions
- },
- editorConfig: {
- mode: editorMode,
- lang: "zh-CN",
- callbackUrl: location.origin + "/onlyoffice-callback",
- user: { id: "preview-user", name: "preview-user" },
- customization: customization
- },
- events: {
- onAppReady: function(){
- hintEl.innerHTML = `预览中: ${title} | <a href='${fileUrl}' target='_blank'>原文件</a> | <span class='ok'>编辑器已启动(模式: ${editorMode})</span>`;
- },
- onDocumentReady: function(){
- hintEl.innerHTML = `预览中: ${title} | <a href='${fileUrl}' target='_blank'>原文件</a> | <span class='ok'>文档已加载</span>`;
- },
- onError: function(e){
- const code = e && e.data && e.data.errorCode;
- const desc = e && e.data && e.data.errorDescription;
- hintEl.innerHTML = `预览中: ${title} | <a href='${fileUrl}' target='_blank'>原文件</a> | <span class='err'>OnlyOffice错误: code=${code||"unknown"} ${desc||""}</span>`;
- },
- onWarning: function(e){
- const code = e && e.data && e.data.warningCode;
- const desc = e && e.data && e.data.warningDescription;
- hintEl.innerHTML = `预览中: ${title} | <a href='${fileUrl}' target='_blank'>原文件</a> | <span class='warn'>OnlyOffice警告: code=${code||"unknown"} ${desc||""}</span>`;
- }
- }
- });
- } catch (e) {
- hintEl.innerHTML = `<span class='err'>预览初始化失败: ${String(e && e.message ? e.message : e)}</span>`;
- throw e;
- }
- }
- precheck().then(render);
- </script>
- </body>
- </html>
|