@l1yp/file-viewer 0.1.5 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +55 -6
- package/dist/assets/step.worker-CfDTYOf_.js.map +1 -0
- package/dist/components/CertViewer.js +1 -1
- package/dist/components/CertViewer.js.map +1 -1
- package/dist/components/CertViewer.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/DocxNavigationPane.js +1 -1
- package/dist/components/DocxNavigationPane.js.map +1 -1
- package/dist/components/DocxNavigationPane.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/DocxViewer.js +1 -1
- package/dist/components/DocxViewer.js.map +1 -1
- package/dist/components/DocxViewer.vue.d.ts +1 -1
- package/dist/components/DocxViewer.vue_vue_type_script_setup_true_lang.js +80 -78
- package/dist/components/DocxViewer.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/EplViewer.js +1 -1
- package/dist/components/EplViewer.js.map +1 -1
- package/dist/components/EplViewer.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/FileViewerBody.js +1 -1
- package/dist/components/FileViewerBody.js.map +1 -1
- package/dist/components/FileViewerBody.vue_vue_type_script_setup_true_lang.js +265 -257
- package/dist/components/FileViewerBody.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/FileViewerPanel.js +1 -1
- package/dist/components/FileViewerPanel.js.map +1 -1
- package/dist/components/FileViewerPanel.vue.d.ts +6 -1
- package/dist/components/FileViewerPanel.vue_vue_type_script_setup_true_lang.js +42 -40
- package/dist/components/FileViewerPanel.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/HarViewer.js +1 -1
- package/dist/components/HarViewer.js.map +1 -1
- package/dist/components/HarViewer.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/JksViewer.js +1 -1
- package/dist/components/JksViewer.js.map +1 -1
- package/dist/components/JksViewer.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/ModelViewModes.js +9 -0
- package/dist/components/ModelViewModes.js.map +1 -0
- package/dist/components/ModelViewModes.vue.d.ts +11 -0
- package/dist/components/ModelViewModes.vue_vue_type_script_setup_true_lang.js +49 -0
- package/dist/components/ModelViewModes.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/ModelViewer.js +1 -1
- package/dist/components/ModelViewer.js.map +1 -1
- package/dist/components/ModelViewer.vue.d.ts +2 -1
- package/dist/components/ModelViewer.vue_vue_type_script_setup_true_lang.js +183 -96
- package/dist/components/ModelViewer.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/PdfNavigationPane.js +1 -1
- package/dist/components/PdfNavigationPane.js.map +1 -1
- package/dist/components/PdfNavigationPane.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/PdfViewer.js +1 -1
- package/dist/components/PdfViewer.js.map +1 -1
- package/dist/components/PdfViewer.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/PptxNavigationPane.js +1 -1
- package/dist/components/PptxNavigationPane.js.map +1 -1
- package/dist/components/PptxNavigationPane.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/PsdViewer.js +1 -1
- package/dist/components/PsdViewer.js.map +1 -1
- package/dist/components/PsdViewer.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/SqliteViewer.js +1 -1
- package/dist/components/SqliteViewer.js.map +1 -1
- package/dist/components/SqliteViewer.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/ViewerLoadError.js +9 -0
- package/dist/components/ViewerLoadError.js.map +1 -0
- package/dist/components/ViewerLoadError.vue.d.ts +17 -0
- package/dist/components/ViewerLoadError.vue_vue_type_script_setup_true_lang.js +58 -0
- package/dist/components/ViewerLoadError.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/XlsxChart.js +1 -1
- package/dist/components/XlsxChart.js.map +1 -1
- package/dist/components/XlsxChart.vue_vue_type_script_setup_true_lang.js +9 -9
- package/dist/components/XlsxChart.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/XlsxImageLayer.js +9 -0
- package/dist/components/XlsxImageLayer.js.map +1 -0
- package/dist/components/XlsxImageLayer.vue.d.ts +9 -0
- package/dist/components/XlsxImageLayer.vue_vue_type_script_setup_true_lang.js +46 -0
- package/dist/components/XlsxImageLayer.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/XlsxViewer.js +1 -1
- package/dist/components/XlsxViewer.js.map +1 -1
- package/dist/components/XlsxViewer.vue_vue_type_script_setup_true_lang.js +230 -216
- package/dist/components/XlsxViewer.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/epl/EplResource.js +1 -1
- package/dist/components/epl/EplResource.js.map +1 -1
- package/dist/components/epl/EplResource.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/optionalViewer.d.ts +3 -2
- package/dist/components/optionalViewer.js +6 -3
- package/dist/components/optionalViewer.js.map +1 -1
- package/dist/composables/useFileViewer.d.ts +11 -3
- package/dist/composables/useFileViewer.js.map +1 -1
- package/dist/composables/useTeleportTheme.d.ts +7 -0
- package/dist/composables/useTeleportTheme.js +42 -0
- package/dist/composables/useTeleportTheme.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +29 -28
- package/dist/lib/docxTheme.d.ts +6 -0
- package/dist/lib/docxTheme.js +64 -0
- package/dist/lib/docxTheme.js.map +1 -0
- package/dist/lib/epl/code/structures.js +82 -0
- package/dist/lib/epl/code/structures.js.map +1 -0
- package/dist/lib/epl/code/variable.js +16 -0
- package/dist/lib/epl/code/variable.js.map +1 -0
- package/dist/lib/epl/eplDocument.js +28 -22
- package/dist/lib/epl/eplDocument.js.map +1 -1
- package/dist/lib/epl/index.d.ts +2 -1
- package/dist/lib/epl/index.js.map +1 -1
- package/dist/lib/epl/sections/code.js +37 -0
- package/dist/lib/epl/sections/code.js.map +1 -0
- package/dist/lib/epl/sections/esystemInfo.js +29 -0
- package/dist/lib/epl/sections/esystemInfo.js.map +1 -0
- package/dist/lib/epl/sections/losable.js +33 -0
- package/dist/lib/epl/sections/losable.js.map +1 -0
- package/dist/lib/epl/sections/projectConfig.js +39 -0
- package/dist/lib/epl/sections/projectConfig.js.map +1 -0
- package/dist/lib/epl/sections/resource.js +84 -0
- package/dist/lib/epl/sections/resource.js.map +1 -0
- package/dist/lib/fileType.d.ts +3 -1
- package/dist/lib/fileType.js +150 -137
- package/dist/lib/fileType.js.map +1 -1
- package/dist/lib/modelAppearance.d.ts +7 -0
- package/dist/lib/modelAppearance.js +41 -0
- package/dist/lib/modelAppearance.js.map +1 -0
- package/dist/lib/step.d.ts +10 -0
- package/dist/lib/step.js +35 -0
- package/dist/lib/step.js.map +1 -0
- package/dist/lib/step.worker.d.ts +1 -0
- package/dist/lib/step.worker.js +18 -0
- package/dist/lib/step.worker.js.map +1 -0
- package/dist/lib/stepObject.d.ts +4 -0
- package/dist/lib/stepObject.js +52 -0
- package/dist/lib/stepObject.js.map +1 -0
- package/dist/lib/stepTypes.d.ts +60 -0
- package/dist/lib/viewerKind.js +10 -5
- package/dist/lib/viewerKind.js.map +1 -1
- package/dist/lib/xlsx.d.ts +1 -1
- package/dist/lib/xlsx.js +106 -89
- package/dist/lib/xlsx.js.map +1 -1
- package/dist/lib/xlsxCharts.js +71 -119
- package/dist/lib/xlsxCharts.js.map +1 -1
- package/dist/lib/xlsxImages.d.ts +43 -0
- package/dist/lib/xlsxImages.js +114 -0
- package/dist/lib/xlsxImages.js.map +1 -0
- package/dist/lib/xlsxParts.d.ts +17 -0
- package/dist/lib/xlsxParts.js +86 -0
- package/dist/lib/xlsxParts.js.map +1 -0
- package/dist/style.css +1 -1
- package/package.json +8 -3
|
@@ -1,159 +1,246 @@
|
|
|
1
|
-
import { fileExt as e } from "../lib/fileType.js";
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
1
|
+
import { fileExt as e, isStepFile as t } from "../lib/fileType.js";
|
|
2
|
+
import n from "./ViewerLoadError.js";
|
|
3
|
+
import { formatBytes as r } from "../lib/format.js";
|
|
4
|
+
import i from "./ModelViewModes.js";
|
|
5
|
+
import { createModelAppearance as a } from "../lib/modelAppearance.js";
|
|
6
|
+
import { computed as o, createBlock as s, createCommentVNode as c, createElementBlock as l, createElementVNode as u, defineComponent as d, normalizeClass as ee, onBeforeUnmount as te, onMounted as ne, openBlock as f, shallowRef as p, toDisplayString as m, useTemplateRef as re, watch as h } from "vue";
|
|
7
|
+
import * as g from "three";
|
|
8
|
+
import { OrbitControls as ie } from "three/examples/jsm/controls/OrbitControls.js";
|
|
9
|
+
import { STLLoader as _ } from "three/examples/jsm/loaders/STLLoader.js";
|
|
10
|
+
import { OBJLoader as v } from "three/examples/jsm/loaders/OBJLoader.js";
|
|
11
|
+
import { PLYLoader as y } from "three/examples/jsm/loaders/PLYLoader.js";
|
|
12
|
+
import { GLTFLoader as b } from "three/examples/jsm/loaders/GLTFLoader.js";
|
|
10
13
|
//#region src/components/ModelViewer.vue?vue&type=script&setup=true&lang.ts
|
|
11
|
-
var
|
|
12
|
-
key: 0,
|
|
13
|
-
class: "mdl__msg"
|
|
14
|
-
}, S = {
|
|
14
|
+
var x = { class: "mdl" }, S = {
|
|
15
15
|
key: 1,
|
|
16
|
-
class: "mdl__msg mdl__gate"
|
|
16
|
+
class: "mdl__msg mdl__gate",
|
|
17
|
+
role: "status"
|
|
17
18
|
}, C = {
|
|
18
19
|
key: 2,
|
|
19
|
-
class: "mdl__msg
|
|
20
|
+
class: "mdl__msg mdl__gate"
|
|
20
21
|
}, w = {
|
|
21
|
-
key:
|
|
22
|
+
key: 4,
|
|
23
|
+
class: "mdl__msg mdl__msg--error"
|
|
24
|
+
}, ae = {
|
|
25
|
+
key: 0,
|
|
26
|
+
class: "mdl__hint"
|
|
27
|
+
}, oe = {
|
|
28
|
+
key: 1,
|
|
29
|
+
class: "mdl__hint"
|
|
30
|
+
}, T = {
|
|
31
|
+
key: 5,
|
|
32
|
+
class: "mdl__msg mdl__gate"
|
|
33
|
+
}, E = {
|
|
34
|
+
key: 6,
|
|
22
35
|
class: "mdl__hint mdl__hint--float"
|
|
23
|
-
},
|
|
36
|
+
}, D = 96 * 1024 * 1024, O = 20 * 1024 * 1024, k = /*@__PURE__*/ d({
|
|
24
37
|
__name: "ModelViewer",
|
|
25
38
|
props: {
|
|
26
39
|
url: {},
|
|
27
40
|
name: {},
|
|
28
|
-
size: {}
|
|
41
|
+
size: {},
|
|
42
|
+
format: {}
|
|
29
43
|
},
|
|
30
|
-
setup(
|
|
31
|
-
let
|
|
32
|
-
function
|
|
44
|
+
setup(d) {
|
|
45
|
+
let k = d, A = p("loading"), j = p(""), M = p("正在加载模型…"), N = p(null), P = p("original"), F = re("host"), I = o(() => k.format === "step" || t(k.name)), L = p(), R = o(() => r(L.value ?? k.size) || "大小未知"), z = 0, B = null, V = null, H = null, U = null, W = null, G = null, K = null, q = 0, J = null;
|
|
46
|
+
function Y(e) {
|
|
47
|
+
let t = /* @__PURE__ */ new Set(), n = /* @__PURE__ */ new Set();
|
|
33
48
|
e.traverse((e) => {
|
|
34
|
-
let
|
|
35
|
-
|
|
36
|
-
let
|
|
37
|
-
Array.isArray(
|
|
38
|
-
});
|
|
49
|
+
let r = e;
|
|
50
|
+
r.geometry && t.add(r.geometry);
|
|
51
|
+
let i = r.material;
|
|
52
|
+
Array.isArray(i) ? i.forEach((e) => n.add(e)) : i && n.add(i);
|
|
53
|
+
}), t.forEach((e) => e.dispose()), n.forEach((e) => e.dispose());
|
|
39
54
|
}
|
|
40
|
-
function
|
|
41
|
-
|
|
55
|
+
function X() {
|
|
56
|
+
K?.dispose(), K = null, G && H && (H.remove(G), Y(G), G = null);
|
|
42
57
|
}
|
|
43
|
-
function
|
|
44
|
-
let e =
|
|
45
|
-
if (!e ||
|
|
58
|
+
function se() {
|
|
59
|
+
let e = F.value;
|
|
60
|
+
if (!e || V) return;
|
|
46
61
|
let t = e.clientWidth || 1, n = e.clientHeight || 1;
|
|
47
|
-
|
|
62
|
+
H = new g.Scene(), U = new g.PerspectiveCamera(50, t / n, .1, 2e3), U.position.set(2, 1.5, 3), V = new g.WebGLRenderer({
|
|
48
63
|
antialias: !0,
|
|
49
64
|
alpha: !0
|
|
50
|
-
}),
|
|
51
|
-
let r = new
|
|
52
|
-
r.position.set(4, 6, 5),
|
|
53
|
-
let i = new
|
|
54
|
-
i.position.set(-5, -2, -4),
|
|
65
|
+
}), V.setPixelRatio(Math.min(2, window.devicePixelRatio || 1)), V.setSize(t, n), e.appendChild(V.domElement), H.add(new g.HemisphereLight(16777215, 4473941, 1.1));
|
|
66
|
+
let r = new g.DirectionalLight(16777215, 1.4);
|
|
67
|
+
r.position.set(4, 6, 5), H.add(r);
|
|
68
|
+
let i = new g.DirectionalLight(16777215, .5);
|
|
69
|
+
i.position.set(-5, -2, -4), H.add(i), W = new ie(U, V.domElement), W.enableDamping = !0, W.dampingFactor = .08, J = new ResizeObserver(ce), J.observe(e);
|
|
55
70
|
let a = () => {
|
|
56
|
-
|
|
71
|
+
q = requestAnimationFrame(a), W?.update(), V && H && U && V.render(H, U);
|
|
57
72
|
};
|
|
58
73
|
a();
|
|
59
74
|
}
|
|
60
|
-
function
|
|
61
|
-
let e =
|
|
62
|
-
if (!e || !
|
|
75
|
+
function ce() {
|
|
76
|
+
let e = F.value;
|
|
77
|
+
if (!e || !V || !U) return;
|
|
63
78
|
let t = e.clientWidth || 1, n = e.clientHeight || 1;
|
|
64
|
-
|
|
79
|
+
V.setSize(t, n), U.aspect = t / n, U.updateProjectionMatrix();
|
|
65
80
|
}
|
|
66
|
-
function
|
|
67
|
-
if (!
|
|
68
|
-
let t = new
|
|
81
|
+
function le(e) {
|
|
82
|
+
if (!U || !W) return;
|
|
83
|
+
let t = new g.Box3().setFromObject(e), n = t.getSize(new g.Vector3()), r = t.getCenter(new g.Vector3());
|
|
69
84
|
e.position.sub(r);
|
|
70
|
-
let i = Math.max(n.x, n.y, n.z) || 1, a =
|
|
71
|
-
|
|
85
|
+
let i = Math.max(n.x, n.y, n.z) || 1, a = U.fov * Math.PI / 180, o = i / (2 * Math.tan(a / 2)) * 1.6;
|
|
86
|
+
U.position.set(o * .6, o * .45, o), U.near = Math.max(o / 1e3, .001), U.far = o * 1e3, U.updateProjectionMatrix(), W.target.set(0, 0, 0), W.update();
|
|
72
87
|
}
|
|
73
|
-
let
|
|
88
|
+
let Z = () => new g.MeshStandardMaterial({
|
|
74
89
|
color: 10135476,
|
|
75
90
|
metalness: .1,
|
|
76
91
|
roughness: .7,
|
|
77
|
-
side:
|
|
92
|
+
side: g.DoubleSide
|
|
78
93
|
});
|
|
79
|
-
async function
|
|
94
|
+
async function ue(e, t, n) {
|
|
95
|
+
if (t === "step" || t === "stp") {
|
|
96
|
+
let [t, r] = await Promise.all([import("../lib/step.js").catch((e) => {
|
|
97
|
+
throw n.aborted || (N.value = e), e;
|
|
98
|
+
}), import("../lib/stepObject.js")]);
|
|
99
|
+
n.throwIfAborted();
|
|
100
|
+
let i = await t.parseStep(e, {
|
|
101
|
+
signal: n,
|
|
102
|
+
onPhase: (e) => {
|
|
103
|
+
n.aborted || (M.value = e === "engine" ? "正在加载 STEP 解析器…" : "正在解析 STEP 模型…");
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
return n.throwIfAborted(), r.buildStepObject(i);
|
|
107
|
+
}
|
|
80
108
|
if (t === "stl") {
|
|
81
|
-
let t = new
|
|
82
|
-
return t.computeVertexNormals(), new
|
|
109
|
+
let t = new _().parse(e);
|
|
110
|
+
return t.computeVertexNormals(), new g.Mesh(t, Z());
|
|
83
111
|
}
|
|
84
112
|
if (t === "ply") {
|
|
85
|
-
let t = new
|
|
113
|
+
let t = new y().parse(e);
|
|
86
114
|
t.computeVertexNormals();
|
|
87
|
-
let n = t.hasAttribute("color") ? new
|
|
115
|
+
let n = t.hasAttribute("color") ? new g.MeshStandardMaterial({
|
|
88
116
|
vertexColors: !0,
|
|
89
|
-
side:
|
|
117
|
+
side: g.DoubleSide,
|
|
90
118
|
roughness: .7
|
|
91
|
-
}) :
|
|
92
|
-
return new
|
|
119
|
+
}) : Z();
|
|
120
|
+
return new g.Mesh(t, n);
|
|
93
121
|
}
|
|
94
122
|
if (t === "obj") {
|
|
95
|
-
let t = new
|
|
123
|
+
let t = new v().parse(new TextDecoder().decode(e));
|
|
96
124
|
return t.traverse((e) => {
|
|
97
125
|
let t = e;
|
|
98
|
-
t.isMesh && (t.material =
|
|
126
|
+
t.isMesh && (t.material = Z());
|
|
99
127
|
}), t;
|
|
100
128
|
}
|
|
101
|
-
let
|
|
129
|
+
let r = t === "gltf" ? new TextDecoder().decode(e) : e;
|
|
102
130
|
return (await new Promise((e, t) => {
|
|
103
|
-
new
|
|
131
|
+
new b().parse(r, "", (t) => e(t), t);
|
|
104
132
|
})).scene;
|
|
105
133
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
134
|
+
function Q() {
|
|
135
|
+
z++, B?.abort(), B = null;
|
|
136
|
+
}
|
|
137
|
+
function de() {
|
|
138
|
+
Q(), A.value = "cancelled";
|
|
139
|
+
}
|
|
140
|
+
async function $(t = !1) {
|
|
141
|
+
Q(), X();
|
|
142
|
+
let n = z, r = k.url, i = k.format ?? e(k.name), o = I.value ? O : D;
|
|
143
|
+
B = new AbortController();
|
|
144
|
+
let s = B.signal;
|
|
145
|
+
A.value = "loading", j.value = "", N.value = null, M.value = "正在加载模型…";
|
|
146
|
+
try {
|
|
147
|
+
if (!r) throw Error("未提供模型文件地址。");
|
|
148
|
+
let e = await fetch(r, { signal: s });
|
|
149
|
+
if (!e.ok) throw Error(`HTTP ${e.status}`);
|
|
150
|
+
let c = Number(e.headers.get("Content-Length"));
|
|
151
|
+
if (!t && c > o) {
|
|
152
|
+
L.value = c, e.body?.cancel(), A.value = "gate";
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
let l = await e.arrayBuffer();
|
|
156
|
+
if (n !== z) return;
|
|
157
|
+
if (!t && l.byteLength > o) {
|
|
158
|
+
L.value = l.byteLength, A.value = "gate";
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
if (se(), !H) throw Error("WebGL 初始化失败");
|
|
162
|
+
let u = await ue(l, i, s);
|
|
163
|
+
if (n !== z) {
|
|
164
|
+
Y(u);
|
|
165
|
+
return;
|
|
119
166
|
}
|
|
167
|
+
G = u, K = a(u), K.setMode(P.value), H.add(u), le(u), A.value = "ready";
|
|
168
|
+
} catch (e) {
|
|
169
|
+
if (n !== z) return;
|
|
170
|
+
j.value = e instanceof Error ? e.message : String(e), A.value = "error";
|
|
171
|
+
} finally {
|
|
172
|
+
n === z && (B = null);
|
|
120
173
|
}
|
|
121
174
|
}
|
|
122
|
-
function
|
|
123
|
-
|
|
124
|
-
|
|
175
|
+
function fe() {
|
|
176
|
+
Q(), X(), N.value = null, L.value = void 0;
|
|
177
|
+
let e = I.value ? O : D;
|
|
178
|
+
if (k.size && k.size > e || I.value && k.size == null) {
|
|
179
|
+
A.value = "gate";
|
|
125
180
|
return;
|
|
126
181
|
}
|
|
127
|
-
|
|
182
|
+
$();
|
|
128
183
|
}
|
|
129
|
-
return
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
184
|
+
return h(P, (e) => K?.setMode(e)), ne(() => {
|
|
185
|
+
h([
|
|
186
|
+
() => k.url,
|
|
187
|
+
() => k.name,
|
|
188
|
+
() => k.format,
|
|
189
|
+
() => k.size
|
|
190
|
+
], fe, { immediate: !0 });
|
|
191
|
+
}), te(() => {
|
|
192
|
+
Q(), cancelAnimationFrame(q), J?.disconnect(), X(), W?.dispose(), V && (V.dispose(), V.domElement.remove()), V = null, H = null, U = null, W = null;
|
|
193
|
+
}), (e, t) => (f(), l("div", x, [
|
|
194
|
+
A.value === "ready" ? (f(), s(i, {
|
|
195
|
+
key: 0,
|
|
196
|
+
modelValue: P.value,
|
|
197
|
+
"onUpdate:modelValue": t[0] ||= (e) => P.value = e,
|
|
198
|
+
class: "mdl__modes"
|
|
199
|
+
}, null, 8, ["modelValue"])) : c("", !0),
|
|
200
|
+
A.value === "loading" ? (f(), l("div", S, [u("p", null, m(M.value), 1), u("button", {
|
|
135
201
|
type: "button",
|
|
136
202
|
class: "mdl__btn",
|
|
137
|
-
onClick:
|
|
138
|
-
}, "
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
203
|
+
onClick: de
|
|
204
|
+
}, "取消加载")])) : A.value === "gate" ? (f(), l("div", C, [u("p", null, "该模型较大或大小未知(" + m(R.value) + "),解析可能需要较长时间和较多内存。", 1), u("button", {
|
|
205
|
+
type: "button",
|
|
206
|
+
class: "mdl__btn",
|
|
207
|
+
onClick: t[1] ||= (e) => $(!0)
|
|
208
|
+
}, "加载并预览")])) : N.value ? (f(), s(n, {
|
|
209
|
+
key: 3,
|
|
210
|
+
error: N.value,
|
|
211
|
+
pkg: ["occt-import-js"],
|
|
212
|
+
label: "STEP 模型",
|
|
213
|
+
"download-url": d.url,
|
|
214
|
+
"download-name": d.name,
|
|
215
|
+
"retry-label": "重试加载",
|
|
216
|
+
onRetry: t[2] ||= (e) => $(!0)
|
|
217
|
+
}, null, 8, [
|
|
218
|
+
"error",
|
|
219
|
+
"download-url",
|
|
220
|
+
"download-name"
|
|
221
|
+
])) : A.value === "error" ? (f(), l("div", w, [
|
|
222
|
+
u("p", null, "加载失败:" + m(j.value), 1),
|
|
223
|
+
I.value ? (f(), l("p", ae, " 复杂装配体或不完整的 STEP 文件可能无法预览,可下载后用本地 CAD 工具打开。 ")) : (f(), l("p", oe, " 外部引用的 .gltf(需配套 .bin/贴图)或压缩网格可能无法在此渲染,可下载后用本地工具打开。 ")),
|
|
224
|
+
u("button", {
|
|
142
225
|
type: "button",
|
|
143
226
|
class: "mdl__btn",
|
|
144
|
-
onClick:
|
|
227
|
+
onClick: t[3] ||= (e) => $(!0)
|
|
145
228
|
}, "重试")
|
|
146
|
-
])) :
|
|
147
|
-
|
|
229
|
+
])) : A.value === "cancelled" ? (f(), l("div", T, [t[5] ||= u("p", null, "已取消模型加载。", -1), u("button", {
|
|
230
|
+
type: "button",
|
|
231
|
+
class: "mdl__btn",
|
|
232
|
+
onClick: t[4] ||= (e) => $(!0)
|
|
233
|
+
}, "重新加载")])) : c("", !0),
|
|
234
|
+
u("div", {
|
|
148
235
|
ref_key: "host",
|
|
149
|
-
ref:
|
|
150
|
-
class:
|
|
236
|
+
ref: F,
|
|
237
|
+
class: ee(["mdl__host", { "mdl__host--hidden": A.value !== "ready" }])
|
|
151
238
|
}, null, 2),
|
|
152
|
-
|
|
239
|
+
A.value === "ready" ? (f(), l("div", E, " 拖动旋转 · 滚轮缩放 · 右键平移 ")) : c("", !0)
|
|
153
240
|
]));
|
|
154
241
|
}
|
|
155
242
|
});
|
|
156
243
|
//#endregion
|
|
157
|
-
export {
|
|
244
|
+
export { k as default };
|
|
158
245
|
|
|
159
246
|
//# sourceMappingURL=ModelViewer.vue_vue_type_script_setup_true_lang.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ModelViewer.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../src/components/ModelViewer.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { onBeforeUnmount, onMounted, ref, watch } from 'vue'\nimport * as THREE from 'three'\nimport { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'\nimport { STLLoader } from 'three/examples/jsm/loaders/STLLoader.js'\nimport { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js'\nimport { PLYLoader } from 'three/examples/jsm/loaders/PLYLoader.js'\nimport { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'\nimport { fileExt } from '@/lib/fileType'\nimport { formatBytes } from '@/lib/format'\n\n/**\n * Side-viewer 3D model renderer. Fetches the model bytes and renders them with three.js (async-loaded so the\n * ~1MB library only ships when a model is opened): STL / PLY / OBJ / glTF / GLB. Auto-centers and frames the\n * model, lights it, and gives OrbitControls (drag to rotate, wheel to zoom, right-drag to pan).\n *\n * Best-effort by format: STL/PLY/OBJ are self-contained. A `.glb` is fully embedded and renders cleanly; a\n * `.gltf` that references external `.bin`/textures can't resolve them here (we only hold the one file), so it\n * may render untextured or fail — download it with its assets to view fully. No materials library for OBJ\n * (.mtl isn't fetched), so OBJ shows in a neutral material.\n */\nconst props = defineProps<{ url?: string; name: string; size?: number }>()\n\ntype Status = 'loading' | 'gate' | 'ready' | 'error'\nconst status = ref<Status>('loading')\nconst errorMsg = ref('')\nconst host = ref<HTMLElement | null>(null)\n\n// Models can be large (dense meshes); gate very large files behind a confirm (everything decodes in-memory).\nconst SIZE_LIMIT = 96 * 1024 * 1024\n\n// three.js handles — kept out of Vue reactivity on purpose (mutated every animation frame).\nlet renderer: THREE.WebGLRenderer | null = null\nlet scene: THREE.Scene | null = null\nlet camera: THREE.PerspectiveCamera | null = null\nlet controls: OrbitControls | null = null\nlet current: THREE.Object3D | null = null\nlet raf = 0\nlet ro: ResizeObserver | null = null\n\nfunction disposeObject(obj: THREE.Object3D): void {\n obj.traverse((child) => {\n const mesh = child as THREE.Mesh\n if (mesh.geometry) mesh.geometry.dispose()\n const mat = mesh.material\n if (Array.isArray(mat)) mat.forEach((m) => m.dispose())\n else if (mat) mat.dispose()\n })\n}\n\nfunction clearModel(): void {\n if (current && scene) {\n scene.remove(current)\n disposeObject(current)\n current = null\n }\n}\n\nfunction initScene(): void {\n const el = host.value\n if (!el || renderer) return\n const w = el.clientWidth || 1\n const h = el.clientHeight || 1\n\n scene = new THREE.Scene()\n camera = new THREE.PerspectiveCamera(50, w / h, 0.1, 2000)\n camera.position.set(2, 1.5, 3)\n\n renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true })\n renderer.setPixelRatio(Math.min(2, window.devicePixelRatio || 1))\n renderer.setSize(w, h)\n el.appendChild(renderer.domElement)\n\n scene.add(new THREE.HemisphereLight(0xffffff, 0x444455, 1.1))\n const key = new THREE.DirectionalLight(0xffffff, 1.4)\n key.position.set(4, 6, 5)\n scene.add(key)\n const fill = new THREE.DirectionalLight(0xffffff, 0.5)\n fill.position.set(-5, -2, -4)\n scene.add(fill)\n\n controls = new OrbitControls(camera, renderer.domElement)\n controls.enableDamping = true\n controls.dampingFactor = 0.08\n\n ro = new ResizeObserver(onResize)\n ro.observe(el)\n\n const tick = (): void => {\n raf = requestAnimationFrame(tick)\n controls?.update()\n if (renderer && scene && camera) renderer.render(scene, camera)\n }\n tick()\n}\n\nfunction onResize(): void {\n const el = host.value\n if (!el || !renderer || !camera) return\n const w = el.clientWidth || 1\n const h = el.clientHeight || 1\n renderer.setSize(w, h)\n camera.aspect = w / h\n camera.updateProjectionMatrix()\n}\n\n// Center the model at the origin and pull the camera back to frame its bounding sphere.\nfunction frame(obj: THREE.Object3D): void {\n if (!camera || !controls) return\n const box = new THREE.Box3().setFromObject(obj)\n const size = box.getSize(new THREE.Vector3())\n const center = box.getCenter(new THREE.Vector3())\n obj.position.sub(center)\n const maxDim = Math.max(size.x, size.y, size.z) || 1\n const fov = (camera.fov * Math.PI) / 180\n const dist = (maxDim / (2 * Math.tan(fov / 2))) * 1.6\n camera.position.set(dist * 0.6, dist * 0.45, dist)\n camera.near = Math.max(dist / 1000, 0.001)\n camera.far = dist * 1000\n camera.updateProjectionMatrix()\n controls.target.set(0, 0, 0)\n controls.update()\n}\n\nconst NEUTRAL = (): THREE.Material =>\n new THREE.MeshStandardMaterial({\n color: 0x9aa7b4,\n metalness: 0.1,\n roughness: 0.7,\n side: THREE.DoubleSide,\n })\n\nasync function buildObject(buf: ArrayBuffer, ext: string): Promise<THREE.Object3D> {\n if (ext === 'stl') {\n const geom = new STLLoader().parse(buf)\n geom.computeVertexNormals()\n return new THREE.Mesh(geom, NEUTRAL())\n }\n if (ext === 'ply') {\n const geom = new PLYLoader().parse(buf)\n geom.computeVertexNormals()\n const mat = geom.hasAttribute('color')\n ? new THREE.MeshStandardMaterial({\n vertexColors: true,\n side: THREE.DoubleSide,\n roughness: 0.7,\n })\n : NEUTRAL()\n return new THREE.Mesh(geom, mat)\n }\n if (ext === 'obj') {\n const obj = new OBJLoader().parse(new TextDecoder().decode(buf))\n // OBJ without an .mtl comes in with no material — give meshes the neutral one.\n obj.traverse((child) => {\n const mesh = child as THREE.Mesh\n if (mesh.isMesh) mesh.material = NEUTRAL()\n })\n return obj\n }\n // glTF / GLB\n const data: ArrayBuffer | string = ext === 'gltf' ? new TextDecoder().decode(buf) : buf\n const gltf = await new Promise<{ scene: THREE.Group }>((resolve, reject) => {\n new GLTFLoader().parse(data, '', (g) => resolve(g as unknown as { scene: THREE.Group }), reject)\n })\n return gltf.scene\n}\n\nasync function doLoad(): Promise<void> {\n if (!props.url) return\n status.value = 'loading'\n errorMsg.value = ''\n try {\n const res = await fetch(props.url)\n if (!res.ok) throw new Error(`HTTP ${res.status}`)\n const buf = await res.arrayBuffer()\n initScene()\n if (!scene) throw new Error('WebGL 初始化失败')\n clearModel()\n const obj = await buildObject(buf, fileExt(props.name))\n current = obj\n scene.add(obj)\n frame(obj)\n status.value = 'ready'\n } catch (e) {\n errorMsg.value = e instanceof Error ? e.message : String(e)\n status.value = 'error'\n }\n}\n\nfunction load(): void {\n if (props.size && props.size > SIZE_LIMIT) {\n status.value = 'gate'\n return\n }\n void doLoad()\n}\n\nonMounted(() => {\n watch(() => props.url, load, { immediate: true })\n})\n\nonBeforeUnmount(() => {\n cancelAnimationFrame(raf)\n ro?.disconnect()\n clearModel()\n controls?.dispose()\n if (renderer) {\n renderer.dispose()\n renderer.domElement.remove()\n }\n renderer = null\n scene = null\n camera = null\n controls = null\n})\n</script>\n\n<template>\n <div class=\"mdl\">\n <p v-if=\"status === 'loading'\" class=\"mdl__msg\">正在加载模型…</p>\n\n <div v-else-if=\"status === 'gate'\" class=\"mdl__msg mdl__gate\">\n <p>该模型较大({{ formatBytes(props.size) }}),将在浏览器内解析。是否继续?</p>\n <button type=\"button\" class=\"mdl__btn\" @click=\"doLoad\">加载并预览</button>\n </div>\n\n <div v-else-if=\"status === 'error'\" class=\"mdl__msg mdl__msg--error\">\n <p>加载失败:{{ errorMsg }}</p>\n <p class=\"mdl__hint\">\n 外部引用的 .gltf(需配套 .bin/贴图)或压缩网格可能无法在此渲染,可下载后用本地工具打开。\n </p>\n <button type=\"button\" class=\"mdl__btn\" @click=\"doLoad\">重试</button>\n </div>\n\n <!-- the canvas host is always present so three.js can mount into it; overlaid messages sit above it -->\n <div ref=\"host\" class=\"mdl__host\" :class=\"{ 'mdl__host--hidden': status !== 'ready' }\" />\n <div v-if=\"status === 'ready'\" class=\"mdl__hint mdl__hint--float\">\n 拖动旋转 · 滚轮缩放 · 右键平移\n </div>\n </div>\n</template>\n\n<style scoped>\n.mdl {\n position: relative;\n flex: 1 1 auto;\n min-height: 0;\n display: flex;\n flex-direction: column;\n background: radial-gradient(circle at 50% 35%, var(--surface-2), var(--code-bg));\n}\n\n.mdl__msg {\n position: relative;\n z-index: 2;\n padding: 20px 16px;\n color: var(--muted);\n font-size: 13px;\n}\n.mdl__msg--error {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 8px;\n color: var(--danger);\n}\n.mdl__gate {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 12px;\n}\n.mdl__btn {\n font-family: inherit;\n font-size: 12.5px;\n color: var(--text);\n background: var(--surface);\n border: 1px solid var(--border-2);\n border-radius: 8px;\n padding: 5px 12px;\n cursor: pointer;\n}\n.mdl__btn:hover {\n border-color: var(--accent);\n color: var(--accent);\n}\n\n.mdl__host {\n flex: 1 1 auto;\n min-height: 0;\n touch-action: none;\n}\n.mdl__host--hidden {\n visibility: hidden;\n}\n.mdl__host :deep(canvas) {\n display: block;\n}\n\n.mdl__hint {\n color: var(--faint);\n font-size: 12px;\n line-height: 1.5;\n}\n.mdl__hint--float {\n position: absolute;\n left: 12px;\n bottom: 10px;\n z-index: 2;\n padding: 4px 10px;\n border-radius: 999px;\n background: color-mix(in srgb, var(--surface) 70%, transparent);\n pointer-events: none;\n}\n</style>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ModelViewer.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../src/components/ModelViewer.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, onMounted, shallowRef, useTemplateRef, watch } from 'vue'\nimport * as THREE from 'three'\nimport { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'\nimport { STLLoader } from 'three/examples/jsm/loaders/STLLoader.js'\nimport { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js'\nimport { PLYLoader } from 'three/examples/jsm/loaders/PLYLoader.js'\nimport { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'\nimport { fileExt, isStepFile } from '@/lib/fileType'\nimport { formatBytes } from '@/lib/format'\nimport ViewerLoadError from './ViewerLoadError.vue'\nimport ModelViewModes from './ModelViewModes.vue'\nimport { createModelAppearance, type ModelViewMode } from '@/lib/modelAppearance'\n\n/**\n * Side-viewer 3D model renderer. Fetches the model bytes and renders them with three.js (async-loaded so the\n * ~1MB library only ships when a model is opened): STL / PLY / OBJ / glTF / GLB / STEP. Auto-centers and frames the\n * model, lights it, and gives OrbitControls (drag to rotate, wheel to zoom, right-drag to pan).\n *\n * Best-effort by format: STL/PLY/OBJ are self-contained. A `.glb` is fully embedded and renders cleanly; a\n * `.gltf` that references external `.bin`/textures can't resolve them here (we only hold the one file), so it\n * may render untextured or fail — download it with its assets to view fully. No materials library for OBJ\n * (.mtl isn't fetched), so OBJ shows in a neutral material.\n */\nconst props = defineProps<{ url?: string; name: string; size?: number; format?: 'step' }>()\n\ntype Status = 'loading' | 'gate' | 'ready' | 'error' | 'cancelled'\nconst status = shallowRef<Status>('loading')\nconst errorMsg = shallowRef('')\nconst loadingLabel = shallowRef('正在加载模型…')\nconst stepLoadError = shallowRef<unknown>(null)\nconst viewMode = shallowRef<ModelViewMode>('original')\nconst host = useTemplateRef<HTMLElement>('host')\nconst isStep = computed(() => props.format === 'step' || isStepFile(props.name))\nconst detectedSize = shallowRef<number>()\nconst sizeLabel = computed(() => formatBytes(detectedSize.value ?? props.size) || '大小未知')\nlet loadId = 0\nlet controller: AbortController | null = null\n\n// Models can be large (dense meshes); gate very large files behind a confirm (everything decodes in-memory).\nconst SIZE_LIMIT = 96 * 1024 * 1024\nconst STEP_SIZE_LIMIT = 20 * 1024 * 1024\n\n// three.js handles — kept out of Vue reactivity on purpose (mutated every animation frame).\nlet renderer: THREE.WebGLRenderer | null = null\nlet scene: THREE.Scene | null = null\nlet camera: THREE.PerspectiveCamera | null = null\nlet controls: OrbitControls | null = null\nlet current: THREE.Object3D | null = null\nlet appearance: ReturnType<typeof createModelAppearance> | null = null\nlet raf = 0\nlet ro: ResizeObserver | null = null\n\nfunction disposeObject(obj: THREE.Object3D): void {\n const geometries = new Set<THREE.BufferGeometry>()\n const materials = new Set<THREE.Material>()\n obj.traverse((child) => {\n const mesh = child as THREE.Mesh\n if (mesh.geometry) geometries.add(mesh.geometry)\n const mat = mesh.material\n if (Array.isArray(mat)) mat.forEach((m) => materials.add(m))\n else if (mat) materials.add(mat)\n })\n geometries.forEach((geometry) => geometry.dispose())\n materials.forEach((mat) => mat.dispose())\n}\n\nfunction clearModel(): void {\n appearance?.dispose()\n appearance = null\n if (current && scene) {\n scene.remove(current)\n disposeObject(current)\n current = null\n }\n}\n\nfunction initScene(): void {\n const el = host.value\n if (!el || renderer) return\n const w = el.clientWidth || 1\n const h = el.clientHeight || 1\n\n scene = new THREE.Scene()\n camera = new THREE.PerspectiveCamera(50, w / h, 0.1, 2000)\n camera.position.set(2, 1.5, 3)\n\n renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true })\n renderer.setPixelRatio(Math.min(2, window.devicePixelRatio || 1))\n renderer.setSize(w, h)\n el.appendChild(renderer.domElement)\n\n scene.add(new THREE.HemisphereLight(0xffffff, 0x444455, 1.1))\n const key = new THREE.DirectionalLight(0xffffff, 1.4)\n key.position.set(4, 6, 5)\n scene.add(key)\n const fill = new THREE.DirectionalLight(0xffffff, 0.5)\n fill.position.set(-5, -2, -4)\n scene.add(fill)\n\n controls = new OrbitControls(camera, renderer.domElement)\n controls.enableDamping = true\n controls.dampingFactor = 0.08\n\n ro = new ResizeObserver(onResize)\n ro.observe(el)\n\n const tick = (): void => {\n raf = requestAnimationFrame(tick)\n controls?.update()\n if (renderer && scene && camera) renderer.render(scene, camera)\n }\n tick()\n}\n\nfunction onResize(): void {\n const el = host.value\n if (!el || !renderer || !camera) return\n const w = el.clientWidth || 1\n const h = el.clientHeight || 1\n renderer.setSize(w, h)\n camera.aspect = w / h\n camera.updateProjectionMatrix()\n}\n\n// Center the model at the origin and pull the camera back to frame its bounding sphere.\nfunction frame(obj: THREE.Object3D): void {\n if (!camera || !controls) return\n const box = new THREE.Box3().setFromObject(obj)\n const size = box.getSize(new THREE.Vector3())\n const center = box.getCenter(new THREE.Vector3())\n obj.position.sub(center)\n const maxDim = Math.max(size.x, size.y, size.z) || 1\n const fov = (camera.fov * Math.PI) / 180\n const dist = (maxDim / (2 * Math.tan(fov / 2))) * 1.6\n camera.position.set(dist * 0.6, dist * 0.45, dist)\n camera.near = Math.max(dist / 1000, 0.001)\n camera.far = dist * 1000\n camera.updateProjectionMatrix()\n controls.target.set(0, 0, 0)\n controls.update()\n}\n\nconst NEUTRAL = (): THREE.Material =>\n new THREE.MeshStandardMaterial({\n color: 0x9aa7b4,\n metalness: 0.1,\n roughness: 0.7,\n side: THREE.DoubleSide,\n })\n\nasync function buildObject(\n buf: ArrayBuffer,\n ext: string,\n signal: AbortSignal,\n): Promise<THREE.Object3D> {\n if (ext === 'step' || ext === 'stp') {\n // The optional OCCT assets are loaded only for STEP, never when opening an STL/OBJ/glTF.\n const [parser, builder] = await Promise.all([\n import('@/lib/step').catch((error: unknown) => {\n if (!signal.aborted) stepLoadError.value = error\n throw error\n }),\n import('@/lib/stepObject'),\n ])\n signal.throwIfAborted()\n const model = await parser.parseStep(buf, {\n signal,\n onPhase: (phase) => {\n if (!signal.aborted) {\n loadingLabel.value = phase === 'engine' ? '正在加载 STEP 解析器…' : '正在解析 STEP 模型…'\n }\n },\n })\n signal.throwIfAborted()\n return builder.buildStepObject(model)\n }\n if (ext === 'stl') {\n const geom = new STLLoader().parse(buf)\n geom.computeVertexNormals()\n return new THREE.Mesh(geom, NEUTRAL())\n }\n if (ext === 'ply') {\n const geom = new PLYLoader().parse(buf)\n geom.computeVertexNormals()\n const mat = geom.hasAttribute('color')\n ? new THREE.MeshStandardMaterial({\n vertexColors: true,\n side: THREE.DoubleSide,\n roughness: 0.7,\n })\n : NEUTRAL()\n return new THREE.Mesh(geom, mat)\n }\n if (ext === 'obj') {\n const obj = new OBJLoader().parse(new TextDecoder().decode(buf))\n // OBJ without an .mtl comes in with no material — give meshes the neutral one.\n obj.traverse((child) => {\n const mesh = child as THREE.Mesh\n if (mesh.isMesh) mesh.material = NEUTRAL()\n })\n return obj\n }\n // glTF / GLB\n const data: ArrayBuffer | string = ext === 'gltf' ? new TextDecoder().decode(buf) : buf\n const gltf = await new Promise<{ scene: THREE.Group }>((resolve, reject) => {\n new GLTFLoader().parse(data, '', (g) => resolve(g as unknown as { scene: THREE.Group }), reject)\n })\n return gltf.scene\n}\n\nfunction stopLoad(): void {\n loadId++\n controller?.abort()\n controller = null\n}\n\nfunction cancelLoad(): void {\n stopLoad()\n status.value = 'cancelled'\n}\n\nasync function doLoad(confirmed = false): Promise<void> {\n stopLoad()\n clearModel()\n const id = loadId\n const url = props.url\n const ext = props.format ?? fileExt(props.name)\n const limit = isStep.value ? STEP_SIZE_LIMIT : SIZE_LIMIT\n controller = new AbortController()\n const signal = controller.signal\n status.value = 'loading'\n errorMsg.value = ''\n stepLoadError.value = null\n loadingLabel.value = '正在加载模型…'\n try {\n if (!url) throw new Error('未提供模型文件地址。')\n const res = await fetch(url, { signal })\n if (!res.ok) throw new Error(`HTTP ${res.status}`)\n // Do not trust a stale/incorrect manifest size to bypass the gate.\n const contentLength = Number(res.headers.get('Content-Length'))\n if (!confirmed && contentLength > limit) {\n detectedSize.value = contentLength\n void res.body?.cancel()\n status.value = 'gate'\n return\n }\n const buf = await res.arrayBuffer()\n if (id !== loadId) return\n if (!confirmed && buf.byteLength > limit) {\n detectedSize.value = buf.byteLength\n status.value = 'gate'\n return\n }\n initScene()\n if (!scene) throw new Error('WebGL 初始化失败')\n const obj = await buildObject(buf, ext, signal)\n if (id !== loadId) {\n disposeObject(obj)\n return\n }\n current = obj\n appearance = createModelAppearance(obj)\n appearance.setMode(viewMode.value)\n scene.add(obj)\n frame(obj)\n status.value = 'ready'\n } catch (e) {\n if (id !== loadId) return\n errorMsg.value = e instanceof Error ? e.message : String(e)\n status.value = 'error'\n } finally {\n if (id === loadId) controller = null\n }\n}\n\nfunction load(): void {\n stopLoad()\n clearModel()\n stepLoadError.value = null\n detectedSize.value = undefined\n const limit = isStep.value ? STEP_SIZE_LIMIT : SIZE_LIMIT\n if ((props.size && props.size > limit) || (isStep.value && props.size == null)) {\n status.value = 'gate'\n return\n }\n void doLoad()\n}\n\nwatch(viewMode, (mode) => appearance?.setMode(mode))\n\nonMounted(() => {\n watch([() => props.url, () => props.name, () => props.format, () => props.size], load, {\n immediate: true,\n })\n})\n\nonBeforeUnmount(() => {\n stopLoad()\n cancelAnimationFrame(raf)\n ro?.disconnect()\n clearModel()\n controls?.dispose()\n if (renderer) {\n renderer.dispose()\n renderer.domElement.remove()\n }\n renderer = null\n scene = null\n camera = null\n controls = null\n})\n</script>\n\n<template>\n <div class=\"mdl\">\n <ModelViewModes v-if=\"status === 'ready'\" v-model=\"viewMode\" class=\"mdl__modes\" />\n <div v-if=\"status === 'loading'\" class=\"mdl__msg mdl__gate\" role=\"status\">\n <p>{{ loadingLabel }}</p>\n <button type=\"button\" class=\"mdl__btn\" @click=\"cancelLoad\">取消加载</button>\n </div>\n\n <div v-else-if=\"status === 'gate'\" class=\"mdl__msg mdl__gate\">\n <p>该模型较大或大小未知({{ sizeLabel }}),解析可能需要较长时间和较多内存。</p>\n <button type=\"button\" class=\"mdl__btn\" @click=\"doLoad(true)\">加载并预览</button>\n </div>\n\n <ViewerLoadError\n v-else-if=\"stepLoadError\"\n :error=\"stepLoadError\"\n :pkg=\"['occt-import-js']\"\n label=\"STEP 模型\"\n :download-url=\"url\"\n :download-name=\"name\"\n retry-label=\"重试加载\"\n @retry=\"doLoad(true)\"\n />\n <div v-else-if=\"status === 'error'\" class=\"mdl__msg mdl__msg--error\">\n <p>加载失败:{{ errorMsg }}</p>\n <p v-if=\"isStep\" class=\"mdl__hint\">\n 复杂装配体或不完整的 STEP 文件可能无法预览,可下载后用本地 CAD 工具打开。\n </p>\n <p v-else class=\"mdl__hint\">\n 外部引用的 .gltf(需配套 .bin/贴图)或压缩网格可能无法在此渲染,可下载后用本地工具打开。\n </p>\n <button type=\"button\" class=\"mdl__btn\" @click=\"doLoad(true)\">重试</button>\n </div>\n <div v-else-if=\"status === 'cancelled'\" class=\"mdl__msg mdl__gate\">\n <p>已取消模型加载。</p>\n <button type=\"button\" class=\"mdl__btn\" @click=\"doLoad(true)\">重新加载</button>\n </div>\n\n <!-- the canvas host is always present so three.js can mount into it; overlaid messages sit above it -->\n <div ref=\"host\" class=\"mdl__host\" :class=\"{ 'mdl__host--hidden': status !== 'ready' }\" />\n <div v-if=\"status === 'ready'\" class=\"mdl__hint mdl__hint--float\">\n 拖动旋转 · 滚轮缩放 · 右键平移\n </div>\n </div>\n</template>\n\n<style scoped>\n.mdl {\n position: relative;\n flex: 1 1 auto;\n min-height: 0;\n display: flex;\n flex-direction: column;\n background: radial-gradient(circle at 50% 35%, var(--surface-2), var(--code-bg));\n}\n\n.mdl__msg {\n position: relative;\n z-index: 2;\n padding: 20px 16px;\n color: var(--muted);\n font-size: 13px;\n}\n.mdl__modes {\n position: absolute;\n top: 12px;\n right: 12px;\n z-index: 2;\n}\n.mdl__msg--error {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 8px;\n color: var(--danger);\n}\n.mdl__gate {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 12px;\n}\n.mdl__btn {\n font-family: inherit;\n font-size: 12.5px;\n color: var(--text);\n background: var(--surface);\n border: 1px solid var(--border-2);\n border-radius: 8px;\n padding: 5px 12px;\n cursor: pointer;\n}\n.mdl__btn:hover {\n border-color: var(--accent);\n color: var(--accent);\n}\n\n.mdl__host {\n flex: 1 1 auto;\n min-height: 0;\n touch-action: none;\n}\n.mdl__host--hidden {\n visibility: hidden;\n}\n.mdl__host :deep(canvas) {\n display: block;\n}\n\n.mdl__hint {\n color: var(--faint);\n font-size: 12px;\n line-height: 1.5;\n}\n.mdl__hint--float {\n position: absolute;\n left: 12px;\n bottom: 10px;\n z-index: 2;\n padding: 4px 10px;\n border-radius: 999px;\n background: color-mix(in srgb, var(--surface) 70%, transparent);\n pointer-events: none;\n}\n</style>\n"],"mappings":"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"}
|
|
@@ -2,7 +2,7 @@ import e from "../_virtual/_plugin-vue_export-helper.js";
|
|
|
2
2
|
import t from "./PdfNavigationPane.vue_vue_type_script_setup_true_lang.js";
|
|
3
3
|
/* empty css */
|
|
4
4
|
//#region src/components/PdfNavigationPane.vue
|
|
5
|
-
var n = /*#__PURE__*/ e(t, [["__scopeId", "data-v-
|
|
5
|
+
var n = /*#__PURE__*/ e(t, [["__scopeId", "data-v-0fc9d668"]]);
|
|
6
6
|
//#endregion
|
|
7
7
|
export { n as default };
|
|
8
8
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PdfNavigationPane.js","names":[],"sources":["../../src/components/PdfNavigationPane.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, nextTick, shallowRef, useTemplateRef, watch } from 'vue'\n\nimport Icon from '@/components/icons/Icon.vue'\nimport type { PdfNavigationItem } from '../lib/pdfNavigation'\n\nconst props = defineProps<{\n outlineItems: readonly PdfNavigationItem[]\n pageCount: number\n pageLabels: readonly string[]\n pageThumbnails: readonly (string | null)[]\n activePage: number\n}>()\n\nconst collapsed = defineModel<boolean>('collapsed', { default: false })\nconst view = defineModel<'outline' | 'pages'>('view', { default: 'outline' })\nconst emit = defineEmits<{\n navigate: [pageNumber: number]\n}>()\n\nconst listEl = useTemplateRef<HTMLElement>('listEl')\nconst autoScrollPending = shallowRef(false)\nconst hasOutline = computed(() => props.outlineItems.length > 0)\nconst hasThumbnails = computed(() => props.pageThumbnails.some(Boolean))\nconst panelClasses = computed(() => ({\n 'pn--collapsed': collapsed.value,\n 'pn--outline': hasOutline.value && view.value === 'outline',\n 'pn--compact': view.value === 'pages' && !hasThumbnails.value,\n}))\n\nconst activeOutlineId = computed(() => {\n let active: PdfNavigationItem | undefined\n for (const item of props.outlineItems) {\n if (item.pageNumber === null || item.pageNumber > props.activePage) continue\n const activePageNumber = active?.pageNumber ?? 0\n if (\n !active ||\n item.pageNumber > activePageNumber ||\n (item.pageNumber === activePageNumber && item.level > active.level)\n )\n active = item\n }\n return active?.id ?? ''\n})\n\nfunction pageLabel(pageNumber: number): string {\n return props.pageLabels[pageNumber - 1] || `第 ${pageNumber} 页`\n}\n\nfunction navigate(pageNumber: number | null): void {\n if (pageNumber !== null) emit('navigate', pageNumber)\n}\n\nasync function keepActiveItemVisible(): Promise<void> {\n if (collapsed.value || autoScrollPending.value) return\n autoScrollPending.value = true\n await nextTick()\n listEl.value\n ?.querySelector<HTMLElement>('.pn__item--active')\n ?.scrollIntoView({ block: 'nearest' })\n autoScrollPending.value = false\n}\n\nwatch(\n () => props.outlineItems.length,\n (count) => {\n if (!count && view.value === 'outline') view.value = 'pages'\n },\n { immediate: true },\n)\nwatch(() => [props.activePage, view.value, collapsed.value], keepActiveItemVisible)\n</script>\n\n<template>\n <aside class=\"pn\" :class=\"panelClasses\" aria-label=\"PDF 导航\">\n <button\n v-if=\"collapsed\"\n type=\"button\"\n class=\"pn__reopen\"\n aria-label=\"展开 PDF 导航\"\n title=\"展开 PDF 导航\"\n @click=\"collapsed = false\"\n >\n <Icon name=\"chevronRight\" :size=\"15\" />\n </button>\n\n <template v-else>\n <header class=\"pn__header\">\n <div v-if=\"hasOutline\" class=\"pn__tabs\" role=\"tablist\" aria-label=\"导航类型\">\n <button\n class=\"pn__tab\"\n :class=\"{ 'pn__tab--active': view === 'outline' }\"\n type=\"button\"\n role=\"tab\"\n :aria-selected=\"view === 'outline'\"\n @click=\"view = 'outline'\"\n >\n 目录\n </button>\n <button\n class=\"pn__tab\"\n :class=\"{ 'pn__tab--active': view === 'pages' }\"\n type=\"button\"\n role=\"tab\"\n :aria-selected=\"view === 'pages'\"\n @click=\"view = 'pages'\"\n >\n 页面\n </button>\n </div>\n <div v-else class=\"pn__meta\">\n <strong class=\"pn__title\">页面</strong>\n <span class=\"pn__count\">{{ pageCount }} 页</span>\n </div>\n <button\n class=\"pn__collapse\"\n type=\"button\"\n title=\"收起 PDF 导航\"\n aria-label=\"收起 PDF 导航\"\n @click=\"collapsed = true\"\n >\n <Icon name=\"chevronLeft\" :size=\"15\" />\n </button>\n </header>\n\n <nav ref=\"listEl\" class=\"pn__list\" aria-label=\"PDF 导航列表\">\n <template v-if=\"view === 'outline' && hasOutline\">\n <button\n v-for=\"item in outlineItems\"\n :key=\"item.id\"\n class=\"pn__item pn__outline\"\n :class=\"{\n 'pn__item--active': item.id === activeOutlineId,\n 'pn__item--disabled': item.pageNumber === null,\n }\"\n :style=\"{ '--pn-level': item.level }\"\n type=\"button\"\n :disabled=\"item.pageNumber === null\"\n :title=\"item.title\"\n :aria-current=\"item.id === activeOutlineId ? 'location' : undefined\"\n @click=\"navigate(item.pageNumber)\"\n >\n <span class=\"pn__outline-title\">{{ item.title }}</span>\n <span v-if=\"item.pageNumber !== null\" class=\"pn__outline-page\">{{\n item.pageNumber\n }}</span>\n </button>\n </template>\n\n <template v-else>\n <button\n v-for=\"pageNumber in pageCount\"\n :key=\"pageNumber\"\n class=\"pn__item pn__page\"\n :class=\"{\n 'pn__item--active': pageNumber === activePage,\n 'pn__page--fallback': !pageThumbnails[pageNumber - 1],\n }\"\n type=\"button\"\n :data-nav-page-number=\"pageNumber\"\n :aria-current=\"pageNumber === activePage ? 'page' : undefined\"\n :title=\"`跳转到第 ${pageNumber} 页`\"\n @click=\"navigate(pageNumber)\"\n >\n <template v-if=\"pageThumbnails[pageNumber - 1]\">\n <span class=\"pn__preview-wrap\">\n <img\n class=\"pn__preview\"\n :src=\"pageThumbnails[pageNumber - 1] || ''\"\n alt=\"\"\n loading=\"lazy\"\n draggable=\"false\"\n />\n </span>\n <span class=\"pn__number\">{{ pageNumber }}</span>\n </template>\n <span v-else class=\"pn__sheet\">{{ pageNumber }}</span>\n <span class=\"pn__page-label\">{{ pageLabel(pageNumber) }}</span>\n </button>\n </template>\n </nav>\n </template>\n </aside>\n</template>\n\n<style scoped>\n.pn {\n position: relative;\n width: 142px;\n min-width: 0;\n display: flex;\n flex: 0 0 142px;\n flex-direction: column;\n color: var(--muted);\n background: color-mix(in srgb, var(--surface) 94%, var(--surface-2));\n border-right: 1px solid var(--border);\n transition:\n flex-basis 160ms ease,\n width 160ms ease;\n}\n\n.pn--outline {\n width: 190px;\n flex-basis: 190px;\n}\n\n.pn--compact {\n width: 96px;\n flex-basis: 96px;\n}\n\n.pn--collapsed {\n width: 0;\n flex-basis: 0;\n border-right: 0;\n}\n\n.pn__header {\n min-height: 45px;\n padding: 7px 7px 7px 10px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 6px;\n border-bottom: 1px solid var(--border);\n}\n\n.pn__tabs {\n min-width: 0;\n display: flex;\n flex: 1;\n gap: 3px;\n}\n\n.pn__tab,\n.pn__collapse,\n.pn__reopen,\n.pn__item {\n color: inherit;\n font: inherit;\n cursor: pointer;\n background: transparent;\n border: 0;\n}\n\n.pn__tab {\n position: relative;\n min-width: 0;\n padding: 7px 5px;\n flex: 1;\n color: var(--faint);\n font-size: 11.5px;\n border-radius: 6px;\n}\n\n.pn__tab:hover {\n color: var(--text-strong);\n background: var(--bubble);\n}\n\n.pn__tab--active {\n color: var(--text-strong);\n background: var(--accent-soft);\n}\n\n.pn__tab--active::after {\n position: absolute;\n right: 10px;\n bottom: 2px;\n left: 10px;\n height: 2px;\n content: '';\n background: var(--accent);\n border-radius: 2px;\n}\n\n.pn__meta {\n min-width: 0;\n}\n\n.pn__title {\n display: block;\n color: var(--text-strong);\n font-size: 12.5px;\n font-weight: 650;\n}\n\n.pn__count {\n display: block;\n margin-top: 1px;\n color: var(--faint);\n font-size: 10.5px;\n}\n\n.pn__collapse {\n width: 27px;\n height: 27px;\n display: grid;\n flex: none;\n place-items: center;\n border-radius: 7px;\n}\n\n.pn__collapse:hover {\n color: var(--text-strong);\n background: var(--bubble);\n}\n\n.pn__reopen {\n position: absolute;\n z-index: 3;\n top: 12px;\n right: -28px;\n width: 28px;\n height: 36px;\n padding: 0;\n display: grid;\n place-items: center;\n color: var(--muted);\n background: var(--surface);\n border: 1px solid var(--border-2);\n border-radius: 0 9px 9px 0;\n box-shadow: 2px 1px 6px rgba(0, 0, 0, 0.12);\n}\n\n.pn__reopen:hover {\n color: var(--text-strong);\n background: var(--bubble);\n}\n\n.pn__list {\n min-height: 0;\n padding: 8px 7px 22px;\n flex: 1 1 auto;\n overflow: auto;\n scrollbar-width: thin;\n}\n\n.pn__item {\n width: 100%;\n min-width: 0;\n color: var(--muted);\n text-align: left;\n border: 1px solid transparent;\n border-radius: 8px;\n}\n\n.pn__item:hover {\n color: var(--text-strong);\n background: var(--bubble);\n}\n\n.pn__item--active {\n color: var(--text-strong);\n background: var(--accent-soft);\n border-color: color-mix(in srgb, var(--accent) 70%, transparent);\n}\n\n.pn__item--disabled {\n cursor: default;\n opacity: 0.52;\n}\n\n.pn__item--disabled:hover {\n color: var(--muted);\n background: transparent;\n}\n\n.pn__outline {\n gap: 8px;\n padding: 7px 8px 7px calc(9px + min(var(--pn-level), 5) * 14px);\n display: flex;\n align-items: center;\n margin: 1px 0;\n font-size: 12px;\n line-height: 1.35;\n}\n\n.pn__outline-title {\n min-width: 0;\n flex: 1;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.pn__outline-page {\n color: var(--faint);\n font-size: 10px;\n font-variant-numeric: tabular-nums;\n}\n\n.pn__page {\n position: relative;\n padding: 5px;\n display: block;\n margin: 0 0 8px;\n}\n\n.pn__preview-wrap {\n min-width: 0;\n min-height: 62px;\n overflow: hidden;\n display: flex;\n align-items: center;\n justify-content: center;\n background: #fff;\n border: 1px solid var(--border-2);\n border-radius: 4px;\n box-shadow: 0 1px 4px rgba(0, 0, 0, 0.12);\n}\n\n.pn__item--active .pn__preview-wrap {\n border-color: var(--accent);\n box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent) 35%, transparent);\n}\n\n.pn__preview {\n width: 104px;\n max-width: 100%;\n max-height: 142px;\n display: block;\n object-fit: contain;\n pointer-events: none;\n}\n\n.pn__number {\n position: absolute;\n z-index: 2;\n top: 9px;\n left: 9px;\n min-width: 20px;\n height: 20px;\n padding: 0 5px;\n display: grid;\n place-items: center;\n color: #fff;\n font-size: 9.5px;\n font-weight: 650;\n font-variant-numeric: tabular-nums;\n background: rgba(20, 20, 20, 0.68);\n border-radius: 5px;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);\n backdrop-filter: blur(4px);\n}\n\n.pn__item--active .pn__number {\n background: var(--accent);\n}\n\n.pn__page-label {\n min-width: 0;\n margin-top: 5px;\n overflow: hidden;\n display: -webkit-box;\n color: inherit;\n font-size: 10.5px;\n line-height: 1.35;\n -webkit-box-orient: vertical;\n -webkit-line-clamp: 2;\n}\n\n.pn__page--fallback {\n padding: 5px;\n display: flex;\n align-items: center;\n gap: 6px;\n margin-bottom: 5px;\n}\n\n.pn__sheet {\n width: 28px;\n height: 38px;\n display: grid;\n flex: none;\n place-items: center;\n color: #526072;\n font-size: 9.5px;\n font-variant-numeric: tabular-nums;\n background: #fff;\n border: 1px solid var(--border-2);\n border-radius: 3px;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12);\n}\n\n.pn__item--active .pn__sheet {\n color: var(--accent);\n border-color: var(--accent);\n}\n\n.pn__page--fallback .pn__page-label {\n margin-top: 0;\n flex: 1;\n}\n\n@container (max-width: 500px) {\n .pn {\n width: 124px;\n flex-basis: 124px;\n }\n\n .pn--outline {\n width: 158px;\n flex-basis: 158px;\n }\n\n .pn--compact {\n width: 84px;\n flex-basis: 84px;\n }\n\n .pn--collapsed {\n width: 0;\n flex-basis: 0;\n }\n\n .pn__preview {\n max-height: 126px;\n }\n}\n</style>\n"],"mappings":""}
|
|
1
|
+
{"version":3,"file":"PdfNavigationPane.js","names":[],"sources":["../../src/components/PdfNavigationPane.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, nextTick, shallowRef, useTemplateRef, watch } from 'vue'\n\nimport Icon from '@/components/icons/Icon.vue'\nimport type { PdfNavigationItem } from '../lib/pdfNavigation'\n\nconst props = defineProps<{\n outlineItems: readonly PdfNavigationItem[]\n pageCount: number\n pageLabels: readonly string[]\n pageThumbnails: readonly (string | null)[]\n activePage: number\n}>()\n\nconst collapsed = defineModel<boolean>('collapsed', { default: false })\nconst view = defineModel<'outline' | 'pages'>('view', { default: 'outline' })\nconst emit = defineEmits<{\n navigate: [pageNumber: number]\n}>()\n\nconst listEl = useTemplateRef<HTMLElement>('listEl')\nconst autoScrollPending = shallowRef(false)\nconst hasOutline = computed(() => props.outlineItems.length > 0)\nconst hasThumbnails = computed(() => props.pageThumbnails.some(Boolean))\nconst panelClasses = computed(() => ({\n 'pn--collapsed': collapsed.value,\n 'pn--outline': hasOutline.value && view.value === 'outline',\n 'pn--compact': view.value === 'pages' && !hasThumbnails.value,\n}))\n\nconst activeOutlineId = computed(() => {\n let active: PdfNavigationItem | undefined\n for (const item of props.outlineItems) {\n if (item.pageNumber === null || item.pageNumber > props.activePage) continue\n const activePageNumber = active?.pageNumber ?? 0\n if (\n !active ||\n item.pageNumber > activePageNumber ||\n (item.pageNumber === activePageNumber && item.level > active.level)\n )\n active = item\n }\n return active?.id ?? ''\n})\n\nfunction pageLabel(pageNumber: number): string {\n return props.pageLabels[pageNumber - 1] || `第 ${pageNumber} 页`\n}\n\nfunction navigate(pageNumber: number | null): void {\n if (pageNumber !== null) emit('navigate', pageNumber)\n}\n\nasync function keepActiveItemVisible(): Promise<void> {\n if (collapsed.value || autoScrollPending.value) return\n autoScrollPending.value = true\n await nextTick()\n listEl.value\n ?.querySelector<HTMLElement>('.pn__item--active')\n ?.scrollIntoView({ block: 'nearest' })\n autoScrollPending.value = false\n}\n\nwatch(\n () => props.outlineItems.length,\n (count) => {\n if (!count && view.value === 'outline') view.value = 'pages'\n },\n { immediate: true },\n)\nwatch(() => [props.activePage, view.value, collapsed.value], keepActiveItemVisible)\n</script>\n\n<template>\n <aside class=\"pn\" :class=\"panelClasses\" aria-label=\"PDF 导航\">\n <button\n v-if=\"collapsed\"\n type=\"button\"\n class=\"pn__reopen\"\n aria-label=\"展开 PDF 导航\"\n title=\"展开 PDF 导航\"\n @click=\"collapsed = false\"\n >\n <Icon name=\"chevronRight\" :size=\"15\" />\n </button>\n\n <template v-else>\n <header class=\"pn__header\">\n <div v-if=\"hasOutline\" class=\"pn__tabs\" role=\"tablist\" aria-label=\"导航类型\">\n <button\n class=\"pn__tab\"\n :class=\"{ 'pn__tab--active': view === 'outline' }\"\n type=\"button\"\n role=\"tab\"\n :aria-selected=\"view === 'outline'\"\n @click=\"view = 'outline'\"\n >\n 目录\n </button>\n <button\n class=\"pn__tab\"\n :class=\"{ 'pn__tab--active': view === 'pages' }\"\n type=\"button\"\n role=\"tab\"\n :aria-selected=\"view === 'pages'\"\n @click=\"view = 'pages'\"\n >\n 页面\n </button>\n </div>\n <div v-else class=\"pn__meta\">\n <strong class=\"pn__title\">页面</strong>\n <span class=\"pn__count\">{{ pageCount }} 页</span>\n </div>\n <button\n class=\"pn__collapse\"\n type=\"button\"\n title=\"收起 PDF 导航\"\n aria-label=\"收起 PDF 导航\"\n @click=\"collapsed = true\"\n >\n <Icon name=\"chevronLeft\" :size=\"15\" />\n </button>\n </header>\n\n <nav ref=\"listEl\" class=\"pn__list\" aria-label=\"PDF 导航列表\">\n <template v-if=\"view === 'outline' && hasOutline\">\n <button\n v-for=\"item in outlineItems\"\n :key=\"item.id\"\n class=\"pn__item pn__outline\"\n :class=\"{\n 'pn__item--active': item.id === activeOutlineId,\n 'pn__item--disabled': item.pageNumber === null,\n }\"\n :style=\"{ '--pn-level': item.level }\"\n type=\"button\"\n :disabled=\"item.pageNumber === null\"\n :title=\"item.title\"\n :aria-current=\"item.id === activeOutlineId ? 'location' : undefined\"\n @click=\"navigate(item.pageNumber)\"\n >\n <span class=\"pn__outline-title\">{{ item.title }}</span>\n <span v-if=\"item.pageNumber !== null\" class=\"pn__outline-page\">{{\n item.pageNumber\n }}</span>\n </button>\n </template>\n\n <template v-else>\n <button\n v-for=\"pageNumber in pageCount\"\n :key=\"pageNumber\"\n class=\"pn__item pn__page\"\n :class=\"{\n 'pn__item--active': pageNumber === activePage,\n 'pn__page--fallback': !pageThumbnails[pageNumber - 1],\n }\"\n type=\"button\"\n :data-nav-page-number=\"pageNumber\"\n :aria-current=\"pageNumber === activePage ? 'page' : undefined\"\n :title=\"`跳转到第 ${pageNumber} 页`\"\n @click=\"navigate(pageNumber)\"\n >\n <template v-if=\"pageThumbnails[pageNumber - 1]\">\n <span class=\"pn__preview-wrap\">\n <img\n class=\"pn__preview\"\n :src=\"pageThumbnails[pageNumber - 1] || ''\"\n alt=\"\"\n loading=\"lazy\"\n draggable=\"false\"\n />\n </span>\n <span class=\"pn__number\">{{ pageNumber }}</span>\n </template>\n <span v-else class=\"pn__sheet\">{{ pageNumber }}</span>\n <span class=\"pn__page-label\">{{ pageLabel(pageNumber) }}</span>\n </button>\n </template>\n </nav>\n </template>\n </aside>\n</template>\n\n<style scoped>\n.pn {\n position: relative;\n width: 142px;\n min-width: 0;\n display: flex;\n flex: 0 0 142px;\n flex-direction: column;\n color: var(--muted);\n background: color-mix(in srgb, var(--surface) 94%, var(--surface-2));\n border-right: 1px solid var(--border);\n transition:\n flex-basis 160ms ease,\n width 160ms ease;\n}\n\n.pn--outline {\n width: 190px;\n flex-basis: 190px;\n}\n\n.pn--compact {\n width: 96px;\n flex-basis: 96px;\n}\n\n.pn--collapsed {\n width: 0;\n flex-basis: 0;\n border-right: 0;\n}\n\n.pn__header {\n min-height: 45px;\n padding: 7px 7px 7px 10px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 6px;\n border-bottom: 1px solid var(--border);\n}\n\n.pn__tabs {\n min-width: 0;\n display: flex;\n flex: 1;\n gap: 3px;\n}\n\n.pn__tab,\n.pn__collapse,\n.pn__reopen,\n.pn__item {\n color: inherit;\n font: inherit;\n cursor: pointer;\n background: transparent;\n border: 0;\n}\n\n.pn__tab {\n position: relative;\n min-width: 0;\n padding: 7px 5px;\n flex: 1;\n color: var(--faint);\n font-size: 11.5px;\n border-radius: 6px;\n}\n\n.pn__tab:hover {\n color: var(--text-strong);\n background: var(--bubble);\n}\n\n.pn__tab--active {\n color: var(--text-strong);\n background: var(--accent-soft);\n}\n\n.pn__tab--active::after {\n position: absolute;\n right: 10px;\n bottom: 2px;\n left: 10px;\n height: 2px;\n content: '';\n background: var(--accent);\n border-radius: 2px;\n}\n\n.pn__meta {\n min-width: 0;\n}\n\n.pn__title {\n display: block;\n color: var(--text-strong);\n font-size: 12.5px;\n font-weight: 650;\n}\n\n.pn__count {\n display: block;\n margin-top: 1px;\n color: var(--faint);\n font-size: 10.5px;\n}\n\n.pn__collapse {\n width: 27px;\n height: 27px;\n display: grid;\n flex: none;\n place-items: center;\n border-radius: 7px;\n}\n\n.pn__collapse:hover {\n color: var(--text-strong);\n background: var(--bubble);\n}\n\n.pn__reopen {\n position: absolute;\n z-index: 3;\n top: 12px;\n right: -28px;\n width: 28px;\n height: 36px;\n padding: 0;\n display: grid;\n place-items: center;\n color: var(--muted);\n background: var(--surface);\n border: 1px solid var(--border-2);\n border-radius: 0 9px 9px 0;\n box-shadow: 2px 1px 6px rgba(0, 0, 0, 0.12);\n}\n\n.pn__reopen:hover {\n color: var(--text-strong);\n background: var(--bubble);\n}\n\n.pn__list {\n min-height: 0;\n padding: 8px 7px 22px;\n flex: 1 1 auto;\n overflow: auto;\n scrollbar-width: thin;\n}\n\n.pn__item {\n width: 100%;\n min-width: 0;\n color: var(--muted);\n text-align: left;\n border: 1px solid transparent;\n border-radius: 8px;\n}\n\n.pn__item:hover {\n color: var(--text-strong);\n background: var(--bubble);\n}\n\n.pn__item--active {\n color: var(--text-strong);\n background: var(--accent-soft);\n border-color: color-mix(in srgb, var(--accent) 70%, transparent);\n}\n\n.pn__item--disabled {\n cursor: default;\n opacity: 0.52;\n}\n\n.pn__item--disabled:hover {\n color: var(--muted);\n background: transparent;\n}\n\n.pn__outline {\n gap: 8px;\n padding: 7px 8px 7px calc(9px + min(var(--pn-level), 5) * 14px);\n display: flex;\n align-items: center;\n margin: 1px 0;\n font-size: 12px;\n line-height: 1.35;\n}\n\n.pn__outline-title {\n min-width: 0;\n flex: 1;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.pn__outline-page {\n color: var(--faint);\n font-size: 10px;\n font-variant-numeric: tabular-nums;\n}\n\n.pn__page {\n position: relative;\n padding: 5px;\n display: block;\n margin: 0 0 8px;\n}\n\n.pn__preview-wrap {\n min-width: 0;\n min-height: 62px;\n overflow: hidden;\n display: flex;\n align-items: center;\n justify-content: center;\n background: var(--surface-2);\n border: 1px solid var(--border-2);\n border-radius: 4px;\n box-shadow: 0 1px 4px rgba(0, 0, 0, 0.12);\n}\n\n.pn__item--active .pn__preview-wrap {\n border-color: var(--accent);\n box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent) 35%, transparent);\n}\n\n.pn__preview {\n width: 104px;\n max-width: 100%;\n max-height: 142px;\n display: block;\n object-fit: contain;\n pointer-events: none;\n}\n\n.pn__number {\n position: absolute;\n z-index: 2;\n top: 9px;\n left: 9px;\n min-width: 20px;\n height: 20px;\n padding: 0 5px;\n display: grid;\n place-items: center;\n color: var(--text-strong);\n font-size: 9.5px;\n font-weight: 650;\n font-variant-numeric: tabular-nums;\n background: var(--surface);\n border: 1px solid var(--border-2);\n border-radius: 5px;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);\n backdrop-filter: blur(4px);\n}\n\n.pn__item--active .pn__number {\n color: var(--accent);\n background: color-mix(in srgb, var(--accent) 16%, var(--surface));\n border-color: var(--accent);\n}\n\n.pn__page-label {\n min-width: 0;\n margin-top: 5px;\n overflow: hidden;\n display: -webkit-box;\n color: inherit;\n font-size: 10.5px;\n line-height: 1.35;\n -webkit-box-orient: vertical;\n -webkit-line-clamp: 2;\n}\n\n.pn__page--fallback {\n padding: 5px;\n display: flex;\n align-items: center;\n gap: 6px;\n margin-bottom: 5px;\n}\n\n.pn__sheet {\n width: 28px;\n height: 38px;\n display: grid;\n flex: none;\n place-items: center;\n color: var(--muted);\n font-size: 9.5px;\n font-variant-numeric: tabular-nums;\n background: var(--surface);\n border: 1px solid var(--border-2);\n border-radius: 3px;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12);\n}\n\n.pn__item--active .pn__sheet {\n color: var(--accent);\n border-color: var(--accent);\n}\n\n.pn__page--fallback .pn__page-label {\n margin-top: 0;\n flex: 1;\n}\n\n@container (max-width: 500px) {\n .pn {\n width: 124px;\n flex-basis: 124px;\n }\n\n .pn--outline {\n width: 158px;\n flex-basis: 158px;\n }\n\n .pn--compact {\n width: 84px;\n flex-basis: 84px;\n }\n\n .pn--collapsed {\n width: 0;\n flex-basis: 0;\n }\n\n .pn__preview {\n max-height: 126px;\n }\n}\n</style>\n"],"mappings":""}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PdfNavigationPane.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../src/components/PdfNavigationPane.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, nextTick, shallowRef, useTemplateRef, watch } from 'vue'\n\nimport Icon from '@/components/icons/Icon.vue'\nimport type { PdfNavigationItem } from '../lib/pdfNavigation'\n\nconst props = defineProps<{\n outlineItems: readonly PdfNavigationItem[]\n pageCount: number\n pageLabels: readonly string[]\n pageThumbnails: readonly (string | null)[]\n activePage: number\n}>()\n\nconst collapsed = defineModel<boolean>('collapsed', { default: false })\nconst view = defineModel<'outline' | 'pages'>('view', { default: 'outline' })\nconst emit = defineEmits<{\n navigate: [pageNumber: number]\n}>()\n\nconst listEl = useTemplateRef<HTMLElement>('listEl')\nconst autoScrollPending = shallowRef(false)\nconst hasOutline = computed(() => props.outlineItems.length > 0)\nconst hasThumbnails = computed(() => props.pageThumbnails.some(Boolean))\nconst panelClasses = computed(() => ({\n 'pn--collapsed': collapsed.value,\n 'pn--outline': hasOutline.value && view.value === 'outline',\n 'pn--compact': view.value === 'pages' && !hasThumbnails.value,\n}))\n\nconst activeOutlineId = computed(() => {\n let active: PdfNavigationItem | undefined\n for (const item of props.outlineItems) {\n if (item.pageNumber === null || item.pageNumber > props.activePage) continue\n const activePageNumber = active?.pageNumber ?? 0\n if (\n !active ||\n item.pageNumber > activePageNumber ||\n (item.pageNumber === activePageNumber && item.level > active.level)\n )\n active = item\n }\n return active?.id ?? ''\n})\n\nfunction pageLabel(pageNumber: number): string {\n return props.pageLabels[pageNumber - 1] || `第 ${pageNumber} 页`\n}\n\nfunction navigate(pageNumber: number | null): void {\n if (pageNumber !== null) emit('navigate', pageNumber)\n}\n\nasync function keepActiveItemVisible(): Promise<void> {\n if (collapsed.value || autoScrollPending.value) return\n autoScrollPending.value = true\n await nextTick()\n listEl.value\n ?.querySelector<HTMLElement>('.pn__item--active')\n ?.scrollIntoView({ block: 'nearest' })\n autoScrollPending.value = false\n}\n\nwatch(\n () => props.outlineItems.length,\n (count) => {\n if (!count && view.value === 'outline') view.value = 'pages'\n },\n { immediate: true },\n)\nwatch(() => [props.activePage, view.value, collapsed.value], keepActiveItemVisible)\n</script>\n\n<template>\n <aside class=\"pn\" :class=\"panelClasses\" aria-label=\"PDF 导航\">\n <button\n v-if=\"collapsed\"\n type=\"button\"\n class=\"pn__reopen\"\n aria-label=\"展开 PDF 导航\"\n title=\"展开 PDF 导航\"\n @click=\"collapsed = false\"\n >\n <Icon name=\"chevronRight\" :size=\"15\" />\n </button>\n\n <template v-else>\n <header class=\"pn__header\">\n <div v-if=\"hasOutline\" class=\"pn__tabs\" role=\"tablist\" aria-label=\"导航类型\">\n <button\n class=\"pn__tab\"\n :class=\"{ 'pn__tab--active': view === 'outline' }\"\n type=\"button\"\n role=\"tab\"\n :aria-selected=\"view === 'outline'\"\n @click=\"view = 'outline'\"\n >\n 目录\n </button>\n <button\n class=\"pn__tab\"\n :class=\"{ 'pn__tab--active': view === 'pages' }\"\n type=\"button\"\n role=\"tab\"\n :aria-selected=\"view === 'pages'\"\n @click=\"view = 'pages'\"\n >\n 页面\n </button>\n </div>\n <div v-else class=\"pn__meta\">\n <strong class=\"pn__title\">页面</strong>\n <span class=\"pn__count\">{{ pageCount }} 页</span>\n </div>\n <button\n class=\"pn__collapse\"\n type=\"button\"\n title=\"收起 PDF 导航\"\n aria-label=\"收起 PDF 导航\"\n @click=\"collapsed = true\"\n >\n <Icon name=\"chevronLeft\" :size=\"15\" />\n </button>\n </header>\n\n <nav ref=\"listEl\" class=\"pn__list\" aria-label=\"PDF 导航列表\">\n <template v-if=\"view === 'outline' && hasOutline\">\n <button\n v-for=\"item in outlineItems\"\n :key=\"item.id\"\n class=\"pn__item pn__outline\"\n :class=\"{\n 'pn__item--active': item.id === activeOutlineId,\n 'pn__item--disabled': item.pageNumber === null,\n }\"\n :style=\"{ '--pn-level': item.level }\"\n type=\"button\"\n :disabled=\"item.pageNumber === null\"\n :title=\"item.title\"\n :aria-current=\"item.id === activeOutlineId ? 'location' : undefined\"\n @click=\"navigate(item.pageNumber)\"\n >\n <span class=\"pn__outline-title\">{{ item.title }}</span>\n <span v-if=\"item.pageNumber !== null\" class=\"pn__outline-page\">{{\n item.pageNumber\n }}</span>\n </button>\n </template>\n\n <template v-else>\n <button\n v-for=\"pageNumber in pageCount\"\n :key=\"pageNumber\"\n class=\"pn__item pn__page\"\n :class=\"{\n 'pn__item--active': pageNumber === activePage,\n 'pn__page--fallback': !pageThumbnails[pageNumber - 1],\n }\"\n type=\"button\"\n :data-nav-page-number=\"pageNumber\"\n :aria-current=\"pageNumber === activePage ? 'page' : undefined\"\n :title=\"`跳转到第 ${pageNumber} 页`\"\n @click=\"navigate(pageNumber)\"\n >\n <template v-if=\"pageThumbnails[pageNumber - 1]\">\n <span class=\"pn__preview-wrap\">\n <img\n class=\"pn__preview\"\n :src=\"pageThumbnails[pageNumber - 1] || ''\"\n alt=\"\"\n loading=\"lazy\"\n draggable=\"false\"\n />\n </span>\n <span class=\"pn__number\">{{ pageNumber }}</span>\n </template>\n <span v-else class=\"pn__sheet\">{{ pageNumber }}</span>\n <span class=\"pn__page-label\">{{ pageLabel(pageNumber) }}</span>\n </button>\n </template>\n </nav>\n </template>\n </aside>\n</template>\n\n<style scoped>\n.pn {\n position: relative;\n width: 142px;\n min-width: 0;\n display: flex;\n flex: 0 0 142px;\n flex-direction: column;\n color: var(--muted);\n background: color-mix(in srgb, var(--surface) 94%, var(--surface-2));\n border-right: 1px solid var(--border);\n transition:\n flex-basis 160ms ease,\n width 160ms ease;\n}\n\n.pn--outline {\n width: 190px;\n flex-basis: 190px;\n}\n\n.pn--compact {\n width: 96px;\n flex-basis: 96px;\n}\n\n.pn--collapsed {\n width: 0;\n flex-basis: 0;\n border-right: 0;\n}\n\n.pn__header {\n min-height: 45px;\n padding: 7px 7px 7px 10px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 6px;\n border-bottom: 1px solid var(--border);\n}\n\n.pn__tabs {\n min-width: 0;\n display: flex;\n flex: 1;\n gap: 3px;\n}\n\n.pn__tab,\n.pn__collapse,\n.pn__reopen,\n.pn__item {\n color: inherit;\n font: inherit;\n cursor: pointer;\n background: transparent;\n border: 0;\n}\n\n.pn__tab {\n position: relative;\n min-width: 0;\n padding: 7px 5px;\n flex: 1;\n color: var(--faint);\n font-size: 11.5px;\n border-radius: 6px;\n}\n\n.pn__tab:hover {\n color: var(--text-strong);\n background: var(--bubble);\n}\n\n.pn__tab--active {\n color: var(--text-strong);\n background: var(--accent-soft);\n}\n\n.pn__tab--active::after {\n position: absolute;\n right: 10px;\n bottom: 2px;\n left: 10px;\n height: 2px;\n content: '';\n background: var(--accent);\n border-radius: 2px;\n}\n\n.pn__meta {\n min-width: 0;\n}\n\n.pn__title {\n display: block;\n color: var(--text-strong);\n font-size: 12.5px;\n font-weight: 650;\n}\n\n.pn__count {\n display: block;\n margin-top: 1px;\n color: var(--faint);\n font-size: 10.5px;\n}\n\n.pn__collapse {\n width: 27px;\n height: 27px;\n display: grid;\n flex: none;\n place-items: center;\n border-radius: 7px;\n}\n\n.pn__collapse:hover {\n color: var(--text-strong);\n background: var(--bubble);\n}\n\n.pn__reopen {\n position: absolute;\n z-index: 3;\n top: 12px;\n right: -28px;\n width: 28px;\n height: 36px;\n padding: 0;\n display: grid;\n place-items: center;\n color: var(--muted);\n background: var(--surface);\n border: 1px solid var(--border-2);\n border-radius: 0 9px 9px 0;\n box-shadow: 2px 1px 6px rgba(0, 0, 0, 0.12);\n}\n\n.pn__reopen:hover {\n color: var(--text-strong);\n background: var(--bubble);\n}\n\n.pn__list {\n min-height: 0;\n padding: 8px 7px 22px;\n flex: 1 1 auto;\n overflow: auto;\n scrollbar-width: thin;\n}\n\n.pn__item {\n width: 100%;\n min-width: 0;\n color: var(--muted);\n text-align: left;\n border: 1px solid transparent;\n border-radius: 8px;\n}\n\n.pn__item:hover {\n color: var(--text-strong);\n background: var(--bubble);\n}\n\n.pn__item--active {\n color: var(--text-strong);\n background: var(--accent-soft);\n border-color: color-mix(in srgb, var(--accent) 70%, transparent);\n}\n\n.pn__item--disabled {\n cursor: default;\n opacity: 0.52;\n}\n\n.pn__item--disabled:hover {\n color: var(--muted);\n background: transparent;\n}\n\n.pn__outline {\n gap: 8px;\n padding: 7px 8px 7px calc(9px + min(var(--pn-level), 5) * 14px);\n display: flex;\n align-items: center;\n margin: 1px 0;\n font-size: 12px;\n line-height: 1.35;\n}\n\n.pn__outline-title {\n min-width: 0;\n flex: 1;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.pn__outline-page {\n color: var(--faint);\n font-size: 10px;\n font-variant-numeric: tabular-nums;\n}\n\n.pn__page {\n position: relative;\n padding: 5px;\n display: block;\n margin: 0 0 8px;\n}\n\n.pn__preview-wrap {\n min-width: 0;\n min-height: 62px;\n overflow: hidden;\n display: flex;\n align-items: center;\n justify-content: center;\n background: #fff;\n border: 1px solid var(--border-2);\n border-radius: 4px;\n box-shadow: 0 1px 4px rgba(0, 0, 0, 0.12);\n}\n\n.pn__item--active .pn__preview-wrap {\n border-color: var(--accent);\n box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent) 35%, transparent);\n}\n\n.pn__preview {\n width: 104px;\n max-width: 100%;\n max-height: 142px;\n display: block;\n object-fit: contain;\n pointer-events: none;\n}\n\n.pn__number {\n position: absolute;\n z-index: 2;\n top: 9px;\n left: 9px;\n min-width: 20px;\n height: 20px;\n padding: 0 5px;\n display: grid;\n place-items: center;\n color: #fff;\n font-size: 9.5px;\n font-weight: 650;\n font-variant-numeric: tabular-nums;\n background: rgba(20, 20, 20, 0.68);\n border-radius: 5px;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);\n backdrop-filter: blur(4px);\n}\n\n.pn__item--active .pn__number {\n background: var(--accent);\n}\n\n.pn__page-label {\n min-width: 0;\n margin-top: 5px;\n overflow: hidden;\n display: -webkit-box;\n color: inherit;\n font-size: 10.5px;\n line-height: 1.35;\n -webkit-box-orient: vertical;\n -webkit-line-clamp: 2;\n}\n\n.pn__page--fallback {\n padding: 5px;\n display: flex;\n align-items: center;\n gap: 6px;\n margin-bottom: 5px;\n}\n\n.pn__sheet {\n width: 28px;\n height: 38px;\n display: grid;\n flex: none;\n place-items: center;\n color: #526072;\n font-size: 9.5px;\n font-variant-numeric: tabular-nums;\n background: #fff;\n border: 1px solid var(--border-2);\n border-radius: 3px;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12);\n}\n\n.pn__item--active .pn__sheet {\n color: var(--accent);\n border-color: var(--accent);\n}\n\n.pn__page--fallback .pn__page-label {\n margin-top: 0;\n flex: 1;\n}\n\n@container (max-width: 500px) {\n .pn {\n width: 124px;\n flex-basis: 124px;\n }\n\n .pn--outline {\n width: 158px;\n flex-basis: 158px;\n }\n\n .pn--compact {\n width: 84px;\n flex-basis: 84px;\n }\n\n .pn--collapsed {\n width: 0;\n flex-basis: 0;\n }\n\n .pn__preview {\n max-height: 126px;\n }\n}\n</style>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"PdfNavigationPane.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../src/components/PdfNavigationPane.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, nextTick, shallowRef, useTemplateRef, watch } from 'vue'\n\nimport Icon from '@/components/icons/Icon.vue'\nimport type { PdfNavigationItem } from '../lib/pdfNavigation'\n\nconst props = defineProps<{\n outlineItems: readonly PdfNavigationItem[]\n pageCount: number\n pageLabels: readonly string[]\n pageThumbnails: readonly (string | null)[]\n activePage: number\n}>()\n\nconst collapsed = defineModel<boolean>('collapsed', { default: false })\nconst view = defineModel<'outline' | 'pages'>('view', { default: 'outline' })\nconst emit = defineEmits<{\n navigate: [pageNumber: number]\n}>()\n\nconst listEl = useTemplateRef<HTMLElement>('listEl')\nconst autoScrollPending = shallowRef(false)\nconst hasOutline = computed(() => props.outlineItems.length > 0)\nconst hasThumbnails = computed(() => props.pageThumbnails.some(Boolean))\nconst panelClasses = computed(() => ({\n 'pn--collapsed': collapsed.value,\n 'pn--outline': hasOutline.value && view.value === 'outline',\n 'pn--compact': view.value === 'pages' && !hasThumbnails.value,\n}))\n\nconst activeOutlineId = computed(() => {\n let active: PdfNavigationItem | undefined\n for (const item of props.outlineItems) {\n if (item.pageNumber === null || item.pageNumber > props.activePage) continue\n const activePageNumber = active?.pageNumber ?? 0\n if (\n !active ||\n item.pageNumber > activePageNumber ||\n (item.pageNumber === activePageNumber && item.level > active.level)\n )\n active = item\n }\n return active?.id ?? ''\n})\n\nfunction pageLabel(pageNumber: number): string {\n return props.pageLabels[pageNumber - 1] || `第 ${pageNumber} 页`\n}\n\nfunction navigate(pageNumber: number | null): void {\n if (pageNumber !== null) emit('navigate', pageNumber)\n}\n\nasync function keepActiveItemVisible(): Promise<void> {\n if (collapsed.value || autoScrollPending.value) return\n autoScrollPending.value = true\n await nextTick()\n listEl.value\n ?.querySelector<HTMLElement>('.pn__item--active')\n ?.scrollIntoView({ block: 'nearest' })\n autoScrollPending.value = false\n}\n\nwatch(\n () => props.outlineItems.length,\n (count) => {\n if (!count && view.value === 'outline') view.value = 'pages'\n },\n { immediate: true },\n)\nwatch(() => [props.activePage, view.value, collapsed.value], keepActiveItemVisible)\n</script>\n\n<template>\n <aside class=\"pn\" :class=\"panelClasses\" aria-label=\"PDF 导航\">\n <button\n v-if=\"collapsed\"\n type=\"button\"\n class=\"pn__reopen\"\n aria-label=\"展开 PDF 导航\"\n title=\"展开 PDF 导航\"\n @click=\"collapsed = false\"\n >\n <Icon name=\"chevronRight\" :size=\"15\" />\n </button>\n\n <template v-else>\n <header class=\"pn__header\">\n <div v-if=\"hasOutline\" class=\"pn__tabs\" role=\"tablist\" aria-label=\"导航类型\">\n <button\n class=\"pn__tab\"\n :class=\"{ 'pn__tab--active': view === 'outline' }\"\n type=\"button\"\n role=\"tab\"\n :aria-selected=\"view === 'outline'\"\n @click=\"view = 'outline'\"\n >\n 目录\n </button>\n <button\n class=\"pn__tab\"\n :class=\"{ 'pn__tab--active': view === 'pages' }\"\n type=\"button\"\n role=\"tab\"\n :aria-selected=\"view === 'pages'\"\n @click=\"view = 'pages'\"\n >\n 页面\n </button>\n </div>\n <div v-else class=\"pn__meta\">\n <strong class=\"pn__title\">页面</strong>\n <span class=\"pn__count\">{{ pageCount }} 页</span>\n </div>\n <button\n class=\"pn__collapse\"\n type=\"button\"\n title=\"收起 PDF 导航\"\n aria-label=\"收起 PDF 导航\"\n @click=\"collapsed = true\"\n >\n <Icon name=\"chevronLeft\" :size=\"15\" />\n </button>\n </header>\n\n <nav ref=\"listEl\" class=\"pn__list\" aria-label=\"PDF 导航列表\">\n <template v-if=\"view === 'outline' && hasOutline\">\n <button\n v-for=\"item in outlineItems\"\n :key=\"item.id\"\n class=\"pn__item pn__outline\"\n :class=\"{\n 'pn__item--active': item.id === activeOutlineId,\n 'pn__item--disabled': item.pageNumber === null,\n }\"\n :style=\"{ '--pn-level': item.level }\"\n type=\"button\"\n :disabled=\"item.pageNumber === null\"\n :title=\"item.title\"\n :aria-current=\"item.id === activeOutlineId ? 'location' : undefined\"\n @click=\"navigate(item.pageNumber)\"\n >\n <span class=\"pn__outline-title\">{{ item.title }}</span>\n <span v-if=\"item.pageNumber !== null\" class=\"pn__outline-page\">{{\n item.pageNumber\n }}</span>\n </button>\n </template>\n\n <template v-else>\n <button\n v-for=\"pageNumber in pageCount\"\n :key=\"pageNumber\"\n class=\"pn__item pn__page\"\n :class=\"{\n 'pn__item--active': pageNumber === activePage,\n 'pn__page--fallback': !pageThumbnails[pageNumber - 1],\n }\"\n type=\"button\"\n :data-nav-page-number=\"pageNumber\"\n :aria-current=\"pageNumber === activePage ? 'page' : undefined\"\n :title=\"`跳转到第 ${pageNumber} 页`\"\n @click=\"navigate(pageNumber)\"\n >\n <template v-if=\"pageThumbnails[pageNumber - 1]\">\n <span class=\"pn__preview-wrap\">\n <img\n class=\"pn__preview\"\n :src=\"pageThumbnails[pageNumber - 1] || ''\"\n alt=\"\"\n loading=\"lazy\"\n draggable=\"false\"\n />\n </span>\n <span class=\"pn__number\">{{ pageNumber }}</span>\n </template>\n <span v-else class=\"pn__sheet\">{{ pageNumber }}</span>\n <span class=\"pn__page-label\">{{ pageLabel(pageNumber) }}</span>\n </button>\n </template>\n </nav>\n </template>\n </aside>\n</template>\n\n<style scoped>\n.pn {\n position: relative;\n width: 142px;\n min-width: 0;\n display: flex;\n flex: 0 0 142px;\n flex-direction: column;\n color: var(--muted);\n background: color-mix(in srgb, var(--surface) 94%, var(--surface-2));\n border-right: 1px solid var(--border);\n transition:\n flex-basis 160ms ease,\n width 160ms ease;\n}\n\n.pn--outline {\n width: 190px;\n flex-basis: 190px;\n}\n\n.pn--compact {\n width: 96px;\n flex-basis: 96px;\n}\n\n.pn--collapsed {\n width: 0;\n flex-basis: 0;\n border-right: 0;\n}\n\n.pn__header {\n min-height: 45px;\n padding: 7px 7px 7px 10px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 6px;\n border-bottom: 1px solid var(--border);\n}\n\n.pn__tabs {\n min-width: 0;\n display: flex;\n flex: 1;\n gap: 3px;\n}\n\n.pn__tab,\n.pn__collapse,\n.pn__reopen,\n.pn__item {\n color: inherit;\n font: inherit;\n cursor: pointer;\n background: transparent;\n border: 0;\n}\n\n.pn__tab {\n position: relative;\n min-width: 0;\n padding: 7px 5px;\n flex: 1;\n color: var(--faint);\n font-size: 11.5px;\n border-radius: 6px;\n}\n\n.pn__tab:hover {\n color: var(--text-strong);\n background: var(--bubble);\n}\n\n.pn__tab--active {\n color: var(--text-strong);\n background: var(--accent-soft);\n}\n\n.pn__tab--active::after {\n position: absolute;\n right: 10px;\n bottom: 2px;\n left: 10px;\n height: 2px;\n content: '';\n background: var(--accent);\n border-radius: 2px;\n}\n\n.pn__meta {\n min-width: 0;\n}\n\n.pn__title {\n display: block;\n color: var(--text-strong);\n font-size: 12.5px;\n font-weight: 650;\n}\n\n.pn__count {\n display: block;\n margin-top: 1px;\n color: var(--faint);\n font-size: 10.5px;\n}\n\n.pn__collapse {\n width: 27px;\n height: 27px;\n display: grid;\n flex: none;\n place-items: center;\n border-radius: 7px;\n}\n\n.pn__collapse:hover {\n color: var(--text-strong);\n background: var(--bubble);\n}\n\n.pn__reopen {\n position: absolute;\n z-index: 3;\n top: 12px;\n right: -28px;\n width: 28px;\n height: 36px;\n padding: 0;\n display: grid;\n place-items: center;\n color: var(--muted);\n background: var(--surface);\n border: 1px solid var(--border-2);\n border-radius: 0 9px 9px 0;\n box-shadow: 2px 1px 6px rgba(0, 0, 0, 0.12);\n}\n\n.pn__reopen:hover {\n color: var(--text-strong);\n background: var(--bubble);\n}\n\n.pn__list {\n min-height: 0;\n padding: 8px 7px 22px;\n flex: 1 1 auto;\n overflow: auto;\n scrollbar-width: thin;\n}\n\n.pn__item {\n width: 100%;\n min-width: 0;\n color: var(--muted);\n text-align: left;\n border: 1px solid transparent;\n border-radius: 8px;\n}\n\n.pn__item:hover {\n color: var(--text-strong);\n background: var(--bubble);\n}\n\n.pn__item--active {\n color: var(--text-strong);\n background: var(--accent-soft);\n border-color: color-mix(in srgb, var(--accent) 70%, transparent);\n}\n\n.pn__item--disabled {\n cursor: default;\n opacity: 0.52;\n}\n\n.pn__item--disabled:hover {\n color: var(--muted);\n background: transparent;\n}\n\n.pn__outline {\n gap: 8px;\n padding: 7px 8px 7px calc(9px + min(var(--pn-level), 5) * 14px);\n display: flex;\n align-items: center;\n margin: 1px 0;\n font-size: 12px;\n line-height: 1.35;\n}\n\n.pn__outline-title {\n min-width: 0;\n flex: 1;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.pn__outline-page {\n color: var(--faint);\n font-size: 10px;\n font-variant-numeric: tabular-nums;\n}\n\n.pn__page {\n position: relative;\n padding: 5px;\n display: block;\n margin: 0 0 8px;\n}\n\n.pn__preview-wrap {\n min-width: 0;\n min-height: 62px;\n overflow: hidden;\n display: flex;\n align-items: center;\n justify-content: center;\n background: var(--surface-2);\n border: 1px solid var(--border-2);\n border-radius: 4px;\n box-shadow: 0 1px 4px rgba(0, 0, 0, 0.12);\n}\n\n.pn__item--active .pn__preview-wrap {\n border-color: var(--accent);\n box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent) 35%, transparent);\n}\n\n.pn__preview {\n width: 104px;\n max-width: 100%;\n max-height: 142px;\n display: block;\n object-fit: contain;\n pointer-events: none;\n}\n\n.pn__number {\n position: absolute;\n z-index: 2;\n top: 9px;\n left: 9px;\n min-width: 20px;\n height: 20px;\n padding: 0 5px;\n display: grid;\n place-items: center;\n color: var(--text-strong);\n font-size: 9.5px;\n font-weight: 650;\n font-variant-numeric: tabular-nums;\n background: var(--surface);\n border: 1px solid var(--border-2);\n border-radius: 5px;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);\n backdrop-filter: blur(4px);\n}\n\n.pn__item--active .pn__number {\n color: var(--accent);\n background: color-mix(in srgb, var(--accent) 16%, var(--surface));\n border-color: var(--accent);\n}\n\n.pn__page-label {\n min-width: 0;\n margin-top: 5px;\n overflow: hidden;\n display: -webkit-box;\n color: inherit;\n font-size: 10.5px;\n line-height: 1.35;\n -webkit-box-orient: vertical;\n -webkit-line-clamp: 2;\n}\n\n.pn__page--fallback {\n padding: 5px;\n display: flex;\n align-items: center;\n gap: 6px;\n margin-bottom: 5px;\n}\n\n.pn__sheet {\n width: 28px;\n height: 38px;\n display: grid;\n flex: none;\n place-items: center;\n color: var(--muted);\n font-size: 9.5px;\n font-variant-numeric: tabular-nums;\n background: var(--surface);\n border: 1px solid var(--border-2);\n border-radius: 3px;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12);\n}\n\n.pn__item--active .pn__sheet {\n color: var(--accent);\n border-color: var(--accent);\n}\n\n.pn__page--fallback .pn__page-label {\n margin-top: 0;\n flex: 1;\n}\n\n@container (max-width: 500px) {\n .pn {\n width: 124px;\n flex-basis: 124px;\n }\n\n .pn--outline {\n width: 158px;\n flex-basis: 158px;\n }\n\n .pn--compact {\n width: 84px;\n flex-basis: 84px;\n }\n\n .pn--collapsed {\n width: 0;\n flex-basis: 0;\n }\n\n .pn__preview {\n max-height: 126px;\n }\n}\n</style>\n"],"mappings":"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"}
|
|
@@ -2,7 +2,7 @@ import e from "../_virtual/_plugin-vue_export-helper.js";
|
|
|
2
2
|
import t from "./PdfViewer.vue_vue_type_script_setup_true_lang.js";
|
|
3
3
|
/* empty css */
|
|
4
4
|
//#region src/components/PdfViewer.vue
|
|
5
|
-
var n = /*#__PURE__*/ e(t, [["__scopeId", "data-v-
|
|
5
|
+
var n = /*#__PURE__*/ e(t, [["__scopeId", "data-v-b84a4531"]]);
|
|
6
6
|
//#endregion
|
|
7
7
|
export { n as default };
|
|
8
8
|
|