vome-core 0.0.44 → 0.0.46
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/dist/admin/config/plugin-dev.js +54 -1
- package/dist/admin/crud/components/vm-preview-viewer.vue +19 -10
- package/dist/admin/crud/components/vm-upload-item.vue +31 -14
- package/dist/admin/crud/components/vm-upload.vue +3 -0
- package/dist/admin/crud/config.js +33 -1
- package/dist/admin/crud/confirm.js +97 -1
- package/dist/admin/crud/dict.js +48 -1
- package/dist/admin/crud/index.js +74 -1
- package/dist/admin/crud/key.js +20 -1
- package/dist/admin/crud/mitt.js +21 -1
- package/dist/admin/crud/plugins.js +216 -1
- package/dist/admin/crud/span.js +35 -1
- package/dist/admin/crud/style.js +74 -1
- package/dist/admin/crud/validate.js +92 -1
- package/dist/admin/directives/perm.js +14 -1
- package/dist/admin/hooks/useUpload.js +63 -1
- package/dist/admin/lib/browser.js +24 -1
- package/dist/admin/lib/cn.js +5 -1
- package/dist/admin/lib/dialog-float.js +13 -1
- package/dist/admin/lib/export-excel.js +64 -1
- package/dist/admin/lib/file-preview.js +74 -1
- package/dist/admin/lib/import-excel.js +61 -1
- package/dist/admin/lib/json.js +42 -1
- package/dist/admin/lib/menu.js +11 -1
- package/dist/admin/lib/tree.js +1 -1
- package/dist/admin/lib/upload.js +88 -1
- package/dist/admin/lib/video-frame.js +80 -1
- package/dist/index.js +28021 -1
- package/dist/server/index.js +31407 -1
- package/dist/shared/excel.js +93 -1
- package/dist/shared/index.js +14 -1
- package/dist/shared/tree.js +39 -1
- package/package.json +1 -1
|
@@ -1 +1,64 @@
|
|
|
1
|
-
|
|
1
|
+
import { toValue } from "vue";
|
|
2
|
+
import {
|
|
3
|
+
buildExcelBuffer,
|
|
4
|
+
excelFilename
|
|
5
|
+
} from "../../shared/excel";
|
|
6
|
+
import { flattenTree } from "./tree";
|
|
7
|
+
export {
|
|
8
|
+
buildExcelBuffer,
|
|
9
|
+
excelFilename,
|
|
10
|
+
excelResponseHeaders
|
|
11
|
+
} from "../../shared/excel";
|
|
12
|
+
export function pickExportColumns(columns) {
|
|
13
|
+
return columns.filter((c) => Boolean(c.prop) && c.type !== "selection" && c.type !== "op" && c.type !== "expand" && c.type !== "index" && !c.hidden);
|
|
14
|
+
}
|
|
15
|
+
function formatExportCell(col, row) {
|
|
16
|
+
if (!col.prop)
|
|
17
|
+
return "";
|
|
18
|
+
const val = row[col.prop];
|
|
19
|
+
if (col.formatter)
|
|
20
|
+
return col.formatter(row, val);
|
|
21
|
+
const dict = col.dict == null ? [] : toValue(col.dict);
|
|
22
|
+
if (Array.isArray(dict) && dict.length && val != null && val !== "") {
|
|
23
|
+
const hit = dict.find((d) => d.value === val || String(d.value) === String(val));
|
|
24
|
+
if (hit)
|
|
25
|
+
return hit.label;
|
|
26
|
+
}
|
|
27
|
+
if (val == null)
|
|
28
|
+
return "";
|
|
29
|
+
return String(val);
|
|
30
|
+
}
|
|
31
|
+
export function flattenExportRows(rows, tree) {
|
|
32
|
+
if (!tree)
|
|
33
|
+
return rows;
|
|
34
|
+
const nested = rows.some((r) => Array.isArray(r.children) && r.children.length);
|
|
35
|
+
if (!nested)
|
|
36
|
+
return rows;
|
|
37
|
+
return flattenTree(rows);
|
|
38
|
+
}
|
|
39
|
+
export function crudColumnsToExcel(columns) {
|
|
40
|
+
return pickExportColumns(columns).map((c) => ({
|
|
41
|
+
prop: c.prop,
|
|
42
|
+
label: c.label || c.prop
|
|
43
|
+
}));
|
|
44
|
+
}
|
|
45
|
+
export function downloadExcelBuffer(buffer, filename) {
|
|
46
|
+
const blob = new Blob([buffer], {
|
|
47
|
+
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
|
48
|
+
});
|
|
49
|
+
const url = URL.createObjectURL(blob);
|
|
50
|
+
const link = document.createElement("a");
|
|
51
|
+
link.href = url;
|
|
52
|
+
link.download = excelFilename(filename);
|
|
53
|
+
link.click();
|
|
54
|
+
URL.revokeObjectURL(url);
|
|
55
|
+
}
|
|
56
|
+
export function exportCrudExcel(columns, rows, filename) {
|
|
57
|
+
const exportCols = pickExportColumns(columns);
|
|
58
|
+
const excelCols = crudColumnsToExcel(exportCols);
|
|
59
|
+
const buffer = buildExcelBuffer(excelCols, rows, (prop, row) => {
|
|
60
|
+
const col = exportCols.find((c) => c.prop === prop);
|
|
61
|
+
return col ? formatExportCell(col, row) : String(row[prop] ?? "");
|
|
62
|
+
});
|
|
63
|
+
downloadExcelBuffer(buffer, filename);
|
|
64
|
+
}
|
|
@@ -1 +1,74 @@
|
|
|
1
|
-
|
|
1
|
+
import { extname, fileRule } from "./upload";
|
|
2
|
+
const TEXT_EXTS = new Set([
|
|
3
|
+
"txt",
|
|
4
|
+
"md",
|
|
5
|
+
"markdown",
|
|
6
|
+
"json",
|
|
7
|
+
"log",
|
|
8
|
+
"csv",
|
|
9
|
+
"xml",
|
|
10
|
+
"html",
|
|
11
|
+
"htm",
|
|
12
|
+
"css",
|
|
13
|
+
"js",
|
|
14
|
+
"ts",
|
|
15
|
+
"jsx",
|
|
16
|
+
"tsx",
|
|
17
|
+
"vue",
|
|
18
|
+
"yaml",
|
|
19
|
+
"yml",
|
|
20
|
+
"ini",
|
|
21
|
+
"conf",
|
|
22
|
+
"sql"
|
|
23
|
+
]);
|
|
24
|
+
export function getMediaPreviewKind(path = "") {
|
|
25
|
+
const rule = fileRule(path);
|
|
26
|
+
if (rule.type === "image")
|
|
27
|
+
return "image";
|
|
28
|
+
if (rule.type === "video")
|
|
29
|
+
return "video";
|
|
30
|
+
if (rule.type === "audio")
|
|
31
|
+
return "audio";
|
|
32
|
+
return getFilePreviewKind(path);
|
|
33
|
+
}
|
|
34
|
+
export function getFilePreviewKind(path = "") {
|
|
35
|
+
const rule = fileRule(path);
|
|
36
|
+
if (rule.type === "pdf")
|
|
37
|
+
return "pdf";
|
|
38
|
+
if (rule.type === "excel")
|
|
39
|
+
return "excel";
|
|
40
|
+
const ext = extname(path);
|
|
41
|
+
if (TEXT_EXTS.has(ext))
|
|
42
|
+
return "text";
|
|
43
|
+
if (rule.type === "word" || rule.type === "ppt")
|
|
44
|
+
return "office";
|
|
45
|
+
return "unsupported";
|
|
46
|
+
}
|
|
47
|
+
export function fileDisplayName(path = "", fallback = "文件") {
|
|
48
|
+
const base = String(path).split("?")[0]?.split("/").pop() || "";
|
|
49
|
+
return base || fallback;
|
|
50
|
+
}
|
|
51
|
+
export async function fetchFileText(url) {
|
|
52
|
+
const res = await fetch(url, { credentials: "include" });
|
|
53
|
+
if (!res.ok)
|
|
54
|
+
throw new Error(`加载失败 (${res.status})`);
|
|
55
|
+
return res.text();
|
|
56
|
+
}
|
|
57
|
+
export async function fetchFileArrayBuffer(url) {
|
|
58
|
+
const res = await fetch(url, { credentials: "include" });
|
|
59
|
+
if (!res.ok)
|
|
60
|
+
throw new Error(`加载失败 (${res.status})`);
|
|
61
|
+
return res.arrayBuffer();
|
|
62
|
+
}
|
|
63
|
+
export async function loadExcelPreviewHtml(url) {
|
|
64
|
+
const XLSX = await import("xlsx");
|
|
65
|
+
const buf = await fetchFileArrayBuffer(url);
|
|
66
|
+
const wb = XLSX.read(buf, { type: "array" });
|
|
67
|
+
const name = wb.SheetNames[0];
|
|
68
|
+
if (!name)
|
|
69
|
+
return "<p>空工作簿</p>";
|
|
70
|
+
const sheet = wb.Sheets[name];
|
|
71
|
+
if (!sheet)
|
|
72
|
+
return "<p>空工作表</p>";
|
|
73
|
+
return XLSX.utils.sheet_to_html(sheet, { id: "vm-file-preview-sheet", editable: false });
|
|
74
|
+
}
|
|
@@ -1 +1,61 @@
|
|
|
1
|
-
|
|
1
|
+
import { apiUrl, getAccessToken } from "../api/client";
|
|
2
|
+
import { downloadExcelBuffer } from "./export-excel";
|
|
3
|
+
import { parseExcelBuffer } from "../../shared/excel";
|
|
4
|
+
function authHeaders(extra) {
|
|
5
|
+
const headers = new Headers(extra);
|
|
6
|
+
const token = getAccessToken();
|
|
7
|
+
if (token)
|
|
8
|
+
headers.set("authorization", `Bearer ${token}`);
|
|
9
|
+
return headers;
|
|
10
|
+
}
|
|
11
|
+
function resolveNamespace(ns) {
|
|
12
|
+
if (!ns)
|
|
13
|
+
return "";
|
|
14
|
+
return ns.startsWith("/") ? ns : `/${ns}`;
|
|
15
|
+
}
|
|
16
|
+
function headersFromRows(rows) {
|
|
17
|
+
const ordered = [];
|
|
18
|
+
const seen = new Set;
|
|
19
|
+
for (const row of rows) {
|
|
20
|
+
for (const key of Object.keys(row)) {
|
|
21
|
+
if (seen.has(key))
|
|
22
|
+
continue;
|
|
23
|
+
seen.add(key);
|
|
24
|
+
ordered.push(key);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return ordered;
|
|
28
|
+
}
|
|
29
|
+
export async function parseImportExcelPreview(file) {
|
|
30
|
+
const buf = new Uint8Array(await file.arrayBuffer());
|
|
31
|
+
const rows = parseExcelBuffer(buf);
|
|
32
|
+
return { headers: headersFromRows(rows), rows };
|
|
33
|
+
}
|
|
34
|
+
export async function downloadImportTemplate(namespace, filename = "导入模板") {
|
|
35
|
+
const url = apiUrl(`${resolveNamespace(namespace)}/importTemplate`);
|
|
36
|
+
const res = await fetch(url, { headers: authHeaders() });
|
|
37
|
+
if (!res.ok) {
|
|
38
|
+
const text = await res.text();
|
|
39
|
+
throw new Error(text || `下载模板失败 (${res.status})`);
|
|
40
|
+
}
|
|
41
|
+
const buf = new Uint8Array(await res.arrayBuffer());
|
|
42
|
+
downloadExcelBuffer(buf, filename);
|
|
43
|
+
}
|
|
44
|
+
export async function uploadImportExcel(namespace, file) {
|
|
45
|
+
const url = apiUrl(`${resolveNamespace(namespace)}/import`);
|
|
46
|
+
const fd = new FormData;
|
|
47
|
+
fd.append("file", file);
|
|
48
|
+
const res = await fetch(url, {
|
|
49
|
+
method: "POST",
|
|
50
|
+
headers: authHeaders(),
|
|
51
|
+
body: fd
|
|
52
|
+
});
|
|
53
|
+
const raw = await res.text();
|
|
54
|
+
let json = null;
|
|
55
|
+
try {
|
|
56
|
+
json = raw ? JSON.parse(raw) : null;
|
|
57
|
+
} catch {}
|
|
58
|
+
if (json?.code === 1000 && json.data)
|
|
59
|
+
return json.data;
|
|
60
|
+
throw new Error(json?.message || raw || `导入失败 (${res.status})`);
|
|
61
|
+
}
|
package/dist/admin/lib/json.js
CHANGED
|
@@ -1 +1,42 @@
|
|
|
1
|
-
|
|
1
|
+
export function textOfJson(v) {
|
|
2
|
+
if (v == null)
|
|
3
|
+
return "";
|
|
4
|
+
if (typeof v === "string")
|
|
5
|
+
return v;
|
|
6
|
+
try {
|
|
7
|
+
return JSON.stringify(v);
|
|
8
|
+
} catch {
|
|
9
|
+
return String(v);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export function prettyJson(raw, empty = "(空)") {
|
|
13
|
+
const s = textOfJson(raw).trim();
|
|
14
|
+
if (!s)
|
|
15
|
+
return empty;
|
|
16
|
+
try {
|
|
17
|
+
return JSON.stringify(JSON.parse(s), null, 2);
|
|
18
|
+
} catch {
|
|
19
|
+
if (typeof raw === "object" && raw !== null) {
|
|
20
|
+
try {
|
|
21
|
+
return JSON.stringify(raw, null, 2);
|
|
22
|
+
} catch {}
|
|
23
|
+
}
|
|
24
|
+
return s;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export function hasJsonContent(raw) {
|
|
28
|
+
return Boolean(textOfJson(raw).trim());
|
|
29
|
+
}
|
|
30
|
+
export function parseJsonInput(raw) {
|
|
31
|
+
const s = raw.trim();
|
|
32
|
+
if (!s)
|
|
33
|
+
return { ok: true, value: null };
|
|
34
|
+
try {
|
|
35
|
+
return { ok: true, value: JSON.parse(s) };
|
|
36
|
+
} catch (e) {
|
|
37
|
+
return {
|
|
38
|
+
ok: false,
|
|
39
|
+
error: e instanceof Error ? e.message : "JSON 格式错误"
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
}
|
package/dist/admin/lib/menu.js
CHANGED
|
@@ -1 +1,11 @@
|
|
|
1
|
-
|
|
1
|
+
export function filterShowMenus(nodes) {
|
|
2
|
+
if (!Array.isArray(nodes) || !nodes.length)
|
|
3
|
+
return [];
|
|
4
|
+
return nodes.filter((n) => n && n.isShow !== false).map((n) => ({
|
|
5
|
+
...n,
|
|
6
|
+
children: n.children?.length ? filterShowMenus(n.children) : undefined
|
|
7
|
+
})).filter((n) => {
|
|
8
|
+
const t = Number(n.type);
|
|
9
|
+
return t === 0 || t === 1 && n.router || (n.children?.length ?? 0) > 0;
|
|
10
|
+
});
|
|
11
|
+
}
|
package/dist/admin/lib/tree.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
export { deepTree, flattenTree } from "../../shared/tree";
|
package/dist/admin/lib/upload.js
CHANGED
|
@@ -1 +1,88 @@
|
|
|
1
|
-
|
|
1
|
+
export const DEFAULT_UPLOAD_RULES = [
|
|
2
|
+
{
|
|
3
|
+
name: "图片",
|
|
4
|
+
type: "image",
|
|
5
|
+
color: "#67C23A",
|
|
6
|
+
exts: ["bmp", "jpg", "jpeg", "png", "tif", "gif", "svg", "webp"]
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
name: "视频",
|
|
10
|
+
type: "video",
|
|
11
|
+
color: "#826aec",
|
|
12
|
+
exts: ["avi", "wmv", "mpg", "mpeg", "mov", "rm", "ram", "swf", "flv", "mp4", "3gp"]
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
name: "音频",
|
|
16
|
+
type: "audio",
|
|
17
|
+
color: "#826aec",
|
|
18
|
+
exts: ["mp3", "wav", "wma", "mp2", "flac", "midi", "ra", "ape", "aac", "cda"]
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
name: "文档",
|
|
22
|
+
type: "word",
|
|
23
|
+
color: "#53B7F4",
|
|
24
|
+
exts: ["doc", "docx", "docm", "dot", "dotx", "dotm"]
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
name: "表格",
|
|
28
|
+
type: "excel",
|
|
29
|
+
color: "#53D39C",
|
|
30
|
+
exts: ["xls", "xlsx", "xlsm", "xlt", "xltx", "xltm"]
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
name: "演示",
|
|
34
|
+
type: "ppt",
|
|
35
|
+
color: "#F56C6C",
|
|
36
|
+
exts: ["ppt", "pptx", "pptm", "ppsx", "ppsm", "pps", "potx", "potm", "dpt", "dps"]
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: "PDF",
|
|
40
|
+
type: "pdf",
|
|
41
|
+
color: "#8f3500",
|
|
42
|
+
exts: ["pdf"]
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
name: "压缩包",
|
|
46
|
+
type: "rar",
|
|
47
|
+
color: "#FFC757",
|
|
48
|
+
exts: ["rar", "zip", "7z", "tar", "gz"]
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
name: "文件",
|
|
52
|
+
type: "file",
|
|
53
|
+
color: "#909399",
|
|
54
|
+
exts: []
|
|
55
|
+
}
|
|
56
|
+
];
|
|
57
|
+
export function extname(path = "") {
|
|
58
|
+
const m = String(path).split("?")[0]?.match(/\.([^.]+)$/);
|
|
59
|
+
return m ? m[1].toLowerCase() : "";
|
|
60
|
+
}
|
|
61
|
+
export function filename(path = "") {
|
|
62
|
+
const base = String(path).split("?")[0]?.split("/").pop() || "";
|
|
63
|
+
const ext = extname(base);
|
|
64
|
+
return ext ? base.slice(0, -(ext.length + 1)) : base;
|
|
65
|
+
}
|
|
66
|
+
export function fileRule(path, rules = DEFAULT_UPLOAD_RULES) {
|
|
67
|
+
if (path?.includes("data:image/")) {
|
|
68
|
+
return rules.find((r) => r.type === "image");
|
|
69
|
+
}
|
|
70
|
+
const ext = extname(path || "");
|
|
71
|
+
const hit = rules.find((r) => r.exts.includes(ext));
|
|
72
|
+
return hit || rules.find((r) => r.type === "file");
|
|
73
|
+
}
|
|
74
|
+
export function getFileType(path, rules = DEFAULT_UPLOAD_RULES) {
|
|
75
|
+
return fileRule(path, rules).type;
|
|
76
|
+
}
|
|
77
|
+
export function getUploadRule(type, rules = DEFAULT_UPLOAD_RULES) {
|
|
78
|
+
return rules.find((r) => r.type === type) || rules[rules.length - 1];
|
|
79
|
+
}
|
|
80
|
+
export function getUrls(list) {
|
|
81
|
+
return list.map((e) => String(e.url || "").replace(/,/g, encodeURIComponent(",")));
|
|
82
|
+
}
|
|
83
|
+
export function pathJoin(...parts) {
|
|
84
|
+
return parts.map((p) => p.replace(/(^\/+|\/+$)/g, "")).filter(Boolean).join("/");
|
|
85
|
+
}
|
|
86
|
+
export function uploadUid() {
|
|
87
|
+
return `${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
|
|
88
|
+
}
|
|
@@ -1 +1,80 @@
|
|
|
1
|
-
|
|
1
|
+
import { onUnmounted, ref, watch } from "vue";
|
|
2
|
+
export const FRAME_CAPTURE_TIMEOUT_MS = 8000;
|
|
3
|
+
export function captureVideoFrame(url) {
|
|
4
|
+
if (typeof document === "undefined")
|
|
5
|
+
return Promise.resolve(null);
|
|
6
|
+
return new Promise((resolve) => {
|
|
7
|
+
const video = document.createElement("video");
|
|
8
|
+
video.crossOrigin = "anonymous";
|
|
9
|
+
video.muted = true;
|
|
10
|
+
video.playsInline = true;
|
|
11
|
+
video.preload = "auto";
|
|
12
|
+
let settled = false;
|
|
13
|
+
const finish = (value) => {
|
|
14
|
+
if (settled)
|
|
15
|
+
return;
|
|
16
|
+
settled = true;
|
|
17
|
+
window.clearTimeout(timer);
|
|
18
|
+
video.removeAttribute("src");
|
|
19
|
+
video.load();
|
|
20
|
+
resolve(value);
|
|
21
|
+
};
|
|
22
|
+
const timer = window.setTimeout(() => finish(null), FRAME_CAPTURE_TIMEOUT_MS);
|
|
23
|
+
video.addEventListener("error", () => finish(null), { once: true });
|
|
24
|
+
video.addEventListener("loadeddata", () => {
|
|
25
|
+
try {
|
|
26
|
+
video.currentTime = 0.01;
|
|
27
|
+
} catch {
|
|
28
|
+
finish(null);
|
|
29
|
+
}
|
|
30
|
+
}, { once: true });
|
|
31
|
+
video.addEventListener("seeked", () => {
|
|
32
|
+
try {
|
|
33
|
+
const w = video.videoWidth;
|
|
34
|
+
const h = video.videoHeight;
|
|
35
|
+
if (!w || !h) {
|
|
36
|
+
finish(null);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const canvas = document.createElement("canvas");
|
|
40
|
+
canvas.width = w;
|
|
41
|
+
canvas.height = h;
|
|
42
|
+
const ctx = canvas.getContext("2d");
|
|
43
|
+
if (!ctx) {
|
|
44
|
+
finish(null);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
ctx.drawImage(video, 0, 0, w, h);
|
|
48
|
+
finish(canvas.toDataURL("image/jpeg", 0.82));
|
|
49
|
+
} catch {
|
|
50
|
+
finish(null);
|
|
51
|
+
}
|
|
52
|
+
}, { once: true });
|
|
53
|
+
video.src = url;
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
export function useVideoFrame(source) {
|
|
57
|
+
const videoFrameSrc = ref("");
|
|
58
|
+
const frameFailed = ref(false);
|
|
59
|
+
let captureToken = 0;
|
|
60
|
+
watch(source, (url) => {
|
|
61
|
+
captureToken += 1;
|
|
62
|
+
const token = captureToken;
|
|
63
|
+
videoFrameSrc.value = "";
|
|
64
|
+
frameFailed.value = false;
|
|
65
|
+
if (!url)
|
|
66
|
+
return;
|
|
67
|
+
captureVideoFrame(url).then((dataUrl) => {
|
|
68
|
+
if (token !== captureToken)
|
|
69
|
+
return;
|
|
70
|
+
if (dataUrl)
|
|
71
|
+
videoFrameSrc.value = dataUrl;
|
|
72
|
+
else
|
|
73
|
+
frameFailed.value = true;
|
|
74
|
+
});
|
|
75
|
+
}, { immediate: true });
|
|
76
|
+
onUnmounted(() => {
|
|
77
|
+
captureToken += 1;
|
|
78
|
+
});
|
|
79
|
+
return { videoFrameSrc, frameFailed };
|
|
80
|
+
}
|