完善模型库权限与工艺数据解析

This commit is contained in:
zhangshun
2026-05-25 18:41:25 +08:00
parent 318105443a
commit a31c620420
15 changed files with 1013 additions and 123 deletions

View File

@@ -134,6 +134,12 @@
margin: 0 0 4px;
}
.preview-process-empty {
padding: 28px 8px;
color: #7a8997;
text-align: center;
}
.preview-process-tree button {
width: 100%;
text-align: left;
@@ -155,6 +161,14 @@
border-top: 1px solid #d8e0e8;
}
.preview-process-status {
padding: 6px 8px 8px;
border-top: 1px solid #eef2f5;
color: #647586;
font-size: 12px;
line-height: 1.4;
}
.preview-scene-section {
border-top: 1px solid #d8e0e8;
}

View File

@@ -37,7 +37,7 @@ export async function renderApp() {
<div class="content-actions">
${isAdmin ? `<button id="manageUsersBtn" type="button">人员权限</button>` : ""}
${isAdmin ? `<button id="manageDictionariesBtn" type="button">字典维护</button>` : ""}
${isAdmin ? `<button id="addModelBtn" class="primary-btn" type="button">增加模型</button>` : ""}
<button id="addModelBtn" class="primary-btn" type="button" hidden>增加模型</button>
</div>
</div>
<div class="filter-toolbar">

View File

@@ -12,6 +12,10 @@ type JsTreeNode = {
text: string;
};
function isRealFolderId(id: string) {
return /^\d+$/.test(id);
}
function selectFolder(node: JsTreeNode) {
appState.selectedFolderId = Number(node.id);
appState.selectedFolderName = appState.folders.find((folder) => folder.id === Number(node.id))?.name ?? node.text;
@@ -60,7 +64,6 @@ async function runFolderAction(action: () => Promise<void>) {
}
export async function loadFolders() {
const isAdmin = getCurrentUser()?.role === "admin";
const result = await api<FolderTreeResponse>("/api/folders");
appState.folders = result.folders;
const root = appState.folders.find((folder) => folder.parent_id === null);
@@ -73,16 +76,19 @@ export async function loadFolders() {
data: result.tree,
multiple: false
},
plugins: isAdmin ? ["contextmenu"] : [],
plugins: ["contextmenu"],
contextmenu: {
items(node: JsTreeNode) {
if (!isRealFolderId(node.id)) return {};
const folderId = Number(node.id);
const folder = appState.folders.find((item) => item.id === folderId);
const isRoot = folder?.parent_id === null;
const canWrite = Boolean(folder?.permissions.write);
const isSystem = Boolean(folder?.is_system);
return {
create: {
label: "新建目录",
icon: "tree-menu-icon tree-menu-icon-add",
_disabled: !canWrite,
action: () => runFolderAction(async () => {
selectFolder(node);
await createFolder(folderId);
@@ -91,7 +97,7 @@ export async function loadFolders() {
rename: {
label: "重命名",
icon: "tree-menu-icon tree-menu-icon-edit",
_disabled: isRoot,
_disabled: !canWrite || isSystem,
action: () => runFolderAction(async () => {
selectFolder(node);
await renameFolder(folderId);
@@ -100,7 +106,7 @@ export async function loadFolders() {
remove: {
label: "删除",
icon: "tree-menu-icon tree-menu-icon-delete",
_disabled: isRoot,
_disabled: !canWrite || isSystem,
action: () => runFolderAction(async () => {
selectFolder(node);
await deleteFolder(folderId);
@@ -110,6 +116,7 @@ export async function loadFolders() {
}
}
}).on("select_node.jstree", async (_event: JQuery.Event, data: { node: { id: string; text: string } }) => {
if (!isRealFolderId(data.node.id)) return;
appState.selectedFolderId = Number(data.node.id);
appState.selectedFolderName = appState.folders.find((folder) => folder.id === appState.selectedFolderId)?.name ?? data.node.text;
appState.page = 1;

View File

@@ -10,6 +10,8 @@ type UploadFormState = {
file: File | null;
};
const defaultOperationTree = JSON.stringify({ OperationTree: "[]" });
export function bindModelActions() {
const isAdmin = getCurrentUser()?.role === "admin";
if (isAdmin) {
@@ -31,15 +33,16 @@ export function bindModelActions() {
}
});
document.querySelector("#addModelBtn")?.addEventListener("click", async () => {
try {
await openUploadModelDialog();
} catch (error) {
notifyError(error);
}
});
}
document.querySelector("#addModelBtn")?.addEventListener("click", async () => {
try {
await openUploadModelDialog();
} catch (error) {
notifyError(error);
}
});
document.querySelector<HTMLSelectElement>("#brandFilter")!.addEventListener("change", async (event) => {
appState.filters.brandId = (event.currentTarget as HTMLSelectElement).value;
appState.page = 1;
@@ -77,6 +80,7 @@ export async function loadModels() {
const grid = document.querySelector<HTMLDivElement>("#modelGrid");
if (!grid || !appState.selectedFolderId) return;
await loadDictionaries();
syncSelectedFolderActions();
document.querySelector("#folderCrumb")!.textContent = appState.selectedFolderName || "模型库";
const params = new URLSearchParams({
folderId: String(appState.selectedFolderId),
@@ -144,7 +148,7 @@ function syncDictionarySelect(selector: string, items: { id: number; name: strin
}
function renderModelCard(item: ModelItem) {
const isAdmin = getCurrentUser()?.role === "admin";
const canWrite = item.permissions.write;
const prop = item.properties ?? {};
const thumb = item.thumbnail_url
? `<img src="${item.thumbnail_url}" alt="" />`
@@ -158,9 +162,9 @@ function renderModelCard(item: ModelItem) {
${thumb}
<div class="thumb-actions">
<button data-action="preview" data-id="${item.id}">预览</button>
${isAdmin ? `<button data-action="edit" data-id="${item.id}">编辑</button>` : ""}
${canWrite ? `<button data-action="edit" data-id="${item.id}">编辑</button>` : ""}
<button data-action="import" data-id="${item.id}">导入</button>
${isAdmin ? `<button data-action="delete" data-id="${item.id}">删除</button>` : ""}
${canWrite ? `<button data-action="delete" data-id="${item.id}">删除</button>` : ""}
</div>
</div>
<dl>
@@ -174,6 +178,13 @@ function renderModelCard(item: ModelItem) {
`;
}
function syncSelectedFolderActions() {
const currentFolder = appState.folders.find((folder) => folder.id === appState.selectedFolderId);
const canWrite = Boolean(currentFolder?.permissions.write);
const addModelBtn = document.querySelector<HTMLButtonElement>("#addModelBtn");
if (addModelBtn) addModelBtn.hidden = !canWrite;
}
async function openUploadModelDialog() {
if (!appState.selectedFolderId) {
notify("请先选择目录");
@@ -226,6 +237,7 @@ async function openUploadModelDialog() {
payload.set("name", name);
payload.set("brandName", String(form.get("brandName") ?? ""));
payload.set("typeName", String(form.get("typeName") ?? ""));
payload.set("operationTree", defaultOperationTree);
payload.set("file", state.file);
for (const key of ["model", "price", "weight"]) {
payload.set(`prop.${key}`, String(form.get(key) ?? ""));

View File

@@ -3,10 +3,11 @@ import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
import { w2popup } from "w2ui";
import type { ModelItem } from "../../../types";
import type { OperationTree, OperationTreeDB } from "../../../types/OperationTree";
import { P_OPERATION } from "../../../types/OPERATION_BaseClass";
import { escapeHtml, formatBytes } from "../../../utils/format";
import { notify, notifyError } from "../../../ui/dialogs";
import { api } from "../../../services/api";
import { getCurrentUser } from "../../../services/authState";
type PreviewRuntime = {
renderer: THREE.WebGLRenderer;
@@ -17,14 +18,21 @@ type PreviewRuntime = {
resizeObserver: ResizeObserver;
};
type PreviewOperation = OperationTree & {
parsedCraftPlayData: P_OPERATION | null;
frameCount: number;
parseError: string | null;
};
let runtime: PreviewRuntime | null = null;
export function openModelPreview(model: ModelItem, onThumbnailSaved?: () => Promise<void> | void) {
disposePreview();
const isAdmin = getCurrentUser()?.role === "admin";
const canWrite = model.permissions.write;
const url = model.file_url;
const operations = parseModelOperations(model.operation_tree);
const popup = w2popup.open({
title: `模型预览 - ${escapeHtml(model.name)}`,
title: `模型预览 - ${escapeHtml(model.name)}${formatBytes(model.file_size)}`,
width: 860,
height: 620,
modal: true,
@@ -34,35 +42,22 @@ export function openModelPreview(model: ModelItem, onThumbnailSaved?: () => Prom
<div class="preview-process-section">
<div class="preview-process-header">
<strong>工艺播放</strong>
<span>占位</span>
</div>
<div class="preview-model-meta">
<span>模型大小</span>
<strong>${formatBytes(model.file_size)}</strong>
<span>${operations.length}</span>
</div>
<ul class="preview-process-tree">
<li class="is-active">
<button type="button" data-process-id="process-1">工艺 1</button>
</li>
<li>
<button type="button" data-process-id="process-2">工艺 2</button>
</li>
<li>
<button type="button" data-process-id="process-3">工艺 3</button>
</li>
${operationListHtml(operations)}
</ul>
<div class="preview-process-actions">
<button id="previewPlayBtn" type="button">播放</button>
<button id="previewPauseBtn" type="button">暂停</button>
</div>
<div id="previewProcessStatus" class="preview-process-status">
${operationStatusHtml(operations[0] ?? null)}
</div>
</div>
<div class="preview-scene-section">
<div class="preview-process-header">
<strong>导入场景</strong>
<span>占位</span>
</div>
<div class="preview-scene-body">
${isAdmin ? `
${canWrite ? `
<button id="previewCaptureThumbBtn" type="button">截缩略图</button>
<label class="preview-switch">
<input id="previewTransparentThumb" type="checkbox" />
@@ -102,13 +97,81 @@ export function openModelPreview(model: ModelItem, onThumbnailSaved?: () => Prom
popup.self
.on("open:after", () => {
bindProcessPlaceholder();
if (isAdmin) bindThumbnailCapture(model.id, onThumbnailSaved);
bindProcessPlaceholder(operations);
if (canWrite) bindThumbnailCapture(model.id, onThumbnailSaved);
initPreview(url).catch((error) => notifyError(error));
})
.on("close:after", () => disposePreview());
}
function parseModelOperations(value: string): PreviewOperation[] {
try {
const dbValue = JSON.parse(value || "{}") as Partial<OperationTreeDB>;
const list = JSON.parse(dbValue.OperationTree || "[]") as unknown;
if (!Array.isArray(list)) return [];
return list
.filter((item): item is OperationTree => Boolean(item && typeof item === "object"))
.map((operation) => {
const parsed = parseCraftPlayData(operation.CraftPlayData);
return {
...operation,
parsedCraftPlayData: parsed.data,
frameCount: parsed.data?.OPERATION.frames.length ?? 0,
parseError: parsed.error
};
});
} catch {
return [];
}
}
function parseCraftPlayData(value?: string | null): { data: P_OPERATION | null; error: string | null } {
const text = value?.trim();
if (!text) return { data: null, error: null };
try {
const jsonData = parseJsonText(text);
return { data: P_OPERATION.fromjson(jsonData), error: null };
} catch (error) {
return {
data: null,
error: error instanceof Error ? error.message : "工艺播放数据解析失败"
};
}
}
function parseJsonText(text: string): unknown {
let value: unknown = text;
for (let index = 0; index < 2 && typeof value === "string"; index += 1) {
value = JSON.parse(value);
}
return value;
}
function operationListHtml(operations: PreviewOperation[]) {
if (operations.length === 0) {
return `<li class="preview-process-empty">暂无工艺数据</li>`;
}
return operations.map((operation, index) => {
const name = operation.CraftName?.trim() || `工艺 ${index + 1}`;
const id = operation.ID || operation.CraftCode || String(index + 1);
const title = operation.parseError
? `${name} / 播放数据解析失败`
: `${name} / ${operation.frameCount}`;
return `
<li class="${index === 0 ? "is-active" : ""}">
<button type="button" data-process-id="${escapeHtml(String(id))}" data-process-index="${index}" title="${escapeHtml(title)}">${escapeHtml(name)}</button>
</li>
`;
}).join("");
}
function operationStatusHtml(operation: PreviewOperation | null) {
if (!operation) return "未选择工艺";
if (operation.parseError) return `播放数据解析失败:${escapeHtml(operation.parseError)}`;
if (!operation.parsedCraftPlayData) return "当前工艺暂无播放数据";
return `已读取 ${operation.frameCount} 帧播放数据`;
}
function bindThumbnailCapture(modelId: number, onThumbnailSaved?: () => Promise<void> | void) {
document.querySelector<HTMLButtonElement>("#previewCaptureThumbBtn")?.addEventListener("click", async () => {
try {
@@ -142,13 +205,51 @@ function bindThumbnailCapture(modelId: number, onThumbnailSaved?: () => Promise<
});
}
function bindProcessPlaceholder() {
function bindProcessPlaceholder(operations: PreviewOperation[]) {
const getSelectedOperation = () => {
const selectedButton = document.querySelector<HTMLButtonElement>(".preview-process-tree li.is-active button");
const index = Number(selectedButton?.dataset.processIndex ?? 0);
return operations[index] ?? null;
};
const updateStatus = (operation: PreviewOperation | null) => {
const status = document.querySelector<HTMLDivElement>("#previewProcessStatus");
if (status) {
status.innerHTML = operationStatusHtml(operation);
}
};
document.querySelectorAll<HTMLButtonElement>(".preview-process-tree button").forEach((button) => {
button.addEventListener("click", () => {
document.querySelectorAll(".preview-process-tree li").forEach((item) => item.classList.remove("is-active"));
button.closest("li")?.classList.add("is-active");
const index = Number(button.dataset.processIndex ?? 0);
updateStatus(operations[index] ?? null);
});
});
document.querySelector<HTMLButtonElement>("#previewPlayBtn")?.addEventListener("click", () => {
const operation = getSelectedOperation();
updateStatus(operation);
if (!operation) {
notify("请先选择工艺");
return;
}
if (operation.parseError) {
notifyError(`工艺播放数据解析失败:${operation.parseError}`);
return;
}
if (!operation.parsedCraftPlayData) {
notify("当前工艺暂无播放数据");
return;
}
notify(`${operation.CraftName || "当前工艺"} 已读取 ${operation.frameCount} 帧,播放逻辑待接入`);
});
document.querySelector<HTMLButtonElement>("#previewPauseBtn")?.addEventListener("click", () => {
updateStatus(getSelectedOperation());
notify("暂停逻辑待接入");
});
}
async function initPreview(url: string) {

View File

@@ -29,7 +29,17 @@ export type Folder = {
parent_id: number | null;
name: string;
path: string;
library_type: "system" | "user";
owner_user_id: number | null;
is_system: number;
owner_username?: string | null;
model_count: number;
permissions: PermissionPayload;
};
export type PermissionPayload = {
read: boolean;
write: boolean;
};
export type ModelItem = {
@@ -41,14 +51,17 @@ export type ModelItem = {
original_filename: string;
file_path: string;
file_url: string;
library_type: "system" | "user";
owner_user_id: number | null;
storage_provider: "local" | "cos";
file_size: number;
thumbnail: string | null;
thumbnail_path: string | null;
thumbnail_provider: "local" | "cos" | null;
thumbnail_url: string | null;
operation_tree: string;
brand_name: string | null;
type_name: string | null;
permissions: PermissionPayload;
properties: Record<string, string>;
};

View File

@@ -0,0 +1,449 @@
type JsonRecord = Record<string, unknown>;
function isRecord(value: unknown): value is JsonRecord {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
function readString(data: JsonRecord, key: string, fallback = "") {
const value = data[key];
return value === null || value === undefined ? fallback : String(value);
}
function readNumber(data: JsonRecord, key: string, fallback = 0) {
const value = data[key];
if (typeof value === "number") return Number.isFinite(value) ? value : fallback;
if (typeof value === "string" && value.trim() !== "") {
const parsed = Number(value);
return Number.isFinite(parsed) ? parsed : fallback;
}
return fallback;
}
function readBoolean(data: JsonRecord, key: string, fallback = false) {
const value = data[key];
if (typeof value === "boolean") return value;
if (typeof value === "number") return value !== 0;
if (typeof value === "string") {
if (value.toLowerCase() === "true") return true;
if (value.toLowerCase() === "false") return false;
}
return fallback;
}
function readArray(data: JsonRecord, key: string) {
const value = data[key];
return Array.isArray(value) ? value : [];
}
class P_OPERATION {
/**
* 一组项目帧
*/
OPERATION = new C_OPERATION();
constructor(operation?: C_OPERATION) {
if (operation) {
this.OPERATION = operation;
}
}
/**
* 从JSON数据反序列化
* @param jsonData JSON格式的数据
* @returns P_OPERATION
*/
static fromjson(jsonData: unknown) {
const operation = new P_OPERATION();
if (isRecord(jsonData) && isRecord(jsonData.OPERATION)) {
operation.OPERATION = C_OPERATION.fromjson(jsonData.OPERATION);
}
return operation;
}
}
class C_OPERATION {
/**
* 帧集合
*/
frames: C_Frames[] = [];
constructor(frames?: C_Frames[]) {
this.frames = frames || [];
}
/**
* 从JSON数据反序列化
* @param jsonData JSON格式的数据
* @returns C_OPERATION
*/
static fromjson(jsonData: unknown) {
const operation = new C_OPERATION();
if (isRecord(jsonData)) {
operation.frames = readArray(jsonData, "frames").map((frameData) => C_Frames.fromjson(frameData));
}
return operation;
}
}
class C_Frames {
/**
* 帧时间
*/
time = "0.01";
/**
* 帧内容集合
*/
objStates: C_ObjStates[] = [];
crafts: C_Craft[] = [];
signalwrites: C_Signalwrite[] = [];
visibles: C_Visible[] = [];
rotations: C_Rotation[] = [];
waits: C_Wait[] = [];
attachs: C_Attach[] = [];
constructor(frame?: Partial<C_Frames>) {
if (frame) {
this.time = frame.time ?? this.time;
this.objStates = frame.objStates ?? this.objStates;
this.crafts = frame.crafts ?? this.crafts;
this.signalwrites = frame.signalwrites ?? this.signalwrites;
this.visibles = frame.visibles ?? this.visibles;
this.rotations = frame.rotations ?? this.rotations;
this.waits = frame.waits ?? this.waits;
this.attachs = frame.attachs ?? this.attachs;
}
}
/**
* 从JSON数据反序列化
* @param jsonData JSON格式的数据
* @returns C_Frames实例
*/
static fromjson(jsonData: unknown) {
const frame = new C_Frames();
if (isRecord(jsonData)) {
frame.time = readString(jsonData, "time", frame.time);
frame.objStates = readArray(jsonData, "objStates").map((objStateData) => C_ObjStates.fromjson(objStateData));
frame.crafts = readArray(jsonData, "crafts").map((craftData) => C_Craft.fromjson(craftData));
frame.signalwrites = readArray(jsonData, "signalwrites").map((signalData) => C_Signalwrite.fromjson(signalData));
frame.visibles = readArray(jsonData, "visibles").map((visibleData) => C_Visible.fromjson(visibleData));
frame.rotations = readArray(jsonData, "rotations").map((rotationData) => C_Rotation.fromjson(rotationData));
frame.waits = readArray(jsonData, "waits").map((waitData) => C_Wait.fromjson(waitData));
frame.attachs = readArray(jsonData, "attachs").map((attachData) => C_Attach.fromjson(attachData));
}
return frame;
}
}
class C_ObjStates {
/**
* 模型代码
*/
i = "";
/**
* tx
*/
tx = "";
/**
* ty
*/
ty = "";
/**
* tz
*/
tz = "";
/**
* qx
*/
qx = "";
/**
* qy
*/
qy = "";
/**
* qz
*/
qz = "";
/**
* qw
*/
qw = "";
constructor(objState?: Partial<C_ObjStates>) {
if (objState) {
this.i = objState.i ?? this.i;
this.tx = objState.tx ?? this.tx;
this.ty = objState.ty ?? this.ty;
this.tz = objState.tz ?? this.tz;
this.qx = objState.qx ?? this.qx;
this.qy = objState.qy ?? this.qy;
this.qz = objState.qz ?? this.qz;
this.qw = objState.qw ?? this.qw;
}
}
/**
* 从JSON数据反序列化
* @param jsonData JSON格式的数据
* @returns C_ObjStates实例
*/
static fromjson(jsonData: unknown) {
const objState = new C_ObjStates();
if (isRecord(jsonData)) {
objState.i = readString(jsonData, "i", objState.i);
objState.tx = readString(jsonData, "tx", objState.tx);
objState.ty = readString(jsonData, "ty", objState.ty);
objState.tz = readString(jsonData, "tz", objState.tz);
objState.qx = readString(jsonData, "qx", objState.qx);
objState.qy = readString(jsonData, "qy", objState.qy);
objState.qz = readString(jsonData, "qz", objState.qz);
objState.qw = readString(jsonData, "qw", objState.qw);
}
return objState;
}
}
class C_Craft {
CraftCode = "";
CraftValue = "";
constructor(craft?: Partial<C_Craft>) {
if (craft) {
this.CraftCode = craft.CraftCode ?? this.CraftCode;
this.CraftValue = craft.CraftValue ?? this.CraftValue;
}
}
/**
* 从JSON数据反序列化
* @param jsonData JSON格式的数据
* @returns C_Craft实例
*/
static fromjson(jsonData: unknown) {
const craft = new C_Craft();
if (isRecord(jsonData)) {
craft.CraftCode = readString(jsonData, "CraftCode", craft.CraftCode);
craft.CraftValue = readString(jsonData, "CraftValue", craft.CraftValue);
}
return craft;
}
}
class C_Signalwrite {
/**
* TagID
*/
TagID = "";
/**
* TagValue
*/
TagValue = "";
constructor(signal?: Partial<C_Signalwrite>) {
if (signal) {
this.TagID = signal.TagID ?? this.TagID;
this.TagValue = signal.TagValue ?? this.TagValue;
}
}
/**
* 从JSON数据反序列化
* @param jsonData JSON格式的数据
* @returns C_Signalwrite实例
*/
static fromjson(jsonData: unknown) {
const signal = new C_Signalwrite();
if (isRecord(jsonData)) {
signal.TagID = readString(jsonData, "TagID", signal.TagID);
signal.TagValue = readString(jsonData, "TagValue", signal.TagValue);
}
return signal;
}
}
class C_Visible {
ModelCode = "";
Visible = "";
constructor(visible?: Partial<C_Visible>) {
if (visible) {
this.ModelCode = visible.ModelCode ?? this.ModelCode;
this.Visible = visible.Visible ?? this.Visible;
}
}
/**
* 从JSON数据反序列化
* @param jsonData JSON格式的数据
* @returns C_Visible
*/
static fromjson(jsonData: unknown) {
const visible = new C_Visible();
if (isRecord(jsonData)) {
visible.ModelCode = readString(jsonData, "ModelCode", visible.ModelCode);
visible.Visible = readString(jsonData, "Visible", visible.Visible);
}
return visible;
}
}
class C_Rotation {
ModelCode = "";
Axis = "";
Rate = 0;
constructor(rotation?: Partial<C_Rotation>) {
if (rotation) {
this.ModelCode = rotation.ModelCode ?? this.ModelCode;
this.Axis = rotation.Axis ?? this.Axis;
this.Rate = rotation.Rate ?? this.Rate;
}
}
/**
* 从JSON数据反序列化
* @param jsonData JSON格式的数据
* @returns C_Rotation
*/
static fromjson(jsonData: unknown) {
const rotation = new C_Rotation();
if (isRecord(jsonData)) {
rotation.ModelCode = readString(jsonData, "ModelCode", rotation.ModelCode);
rotation.Axis = readString(jsonData, "Axis", rotation.Axis);
rotation.Rate = readNumber(jsonData, "Rate", rotation.Rate);
}
return rotation;
}
}
class C_Wait {
WaitStr = "";
constructor(wait?: Partial<C_Wait>) {
if (wait) {
this.WaitStr = wait.WaitStr ?? this.WaitStr;
}
}
/**
* 从JSON数据反序列化
* @param jsonData JSON格式的数据
* @returns C_Wait实例
*/
static fromjson(jsonData: unknown) {
const wait = new C_Wait();
if (isRecord(jsonData)) {
wait.WaitStr = readString(jsonData, "WaitStr", wait.WaitStr);
}
return wait;
}
}
class C_Attach {
AttachToModelCode = "";
AttachToModelName = "";
List_AttachModel: ModelGroup[] = [];
IsTwoWay = false;
IsAttach = false;
constructor(attach?: Partial<C_Attach>) {
if (attach) {
this.AttachToModelCode = attach.AttachToModelCode ?? this.AttachToModelCode;
this.AttachToModelName = attach.AttachToModelName ?? this.AttachToModelName;
this.List_AttachModel = attach.List_AttachModel ?? this.List_AttachModel;
this.IsTwoWay = attach.IsTwoWay ?? this.IsTwoWay;
this.IsAttach = attach.IsAttach ?? this.IsAttach;
}
}
/**
* 从JSON数据反序列化
* @param jsonData JSON格式的数据
* @returns C_Attach实例
*/
static fromjson(jsonData: unknown) {
const attach = new C_Attach();
if (isRecord(jsonData)) {
attach.AttachToModelCode = readString(jsonData, "AttachToModelCode", attach.AttachToModelCode);
attach.AttachToModelName = readString(jsonData, "AttachToModelName", attach.AttachToModelName);
attach.IsTwoWay = readBoolean(jsonData, "IsTwoWay", attach.IsTwoWay);
attach.IsAttach = readBoolean(jsonData, "IsAttach", attach.IsAttach);
attach.List_AttachModel = readArray(jsonData, "List_AttachModel")
.filter(isRecord)
.map((modelData) => new ModelGroup(
readString(modelData, "ModelCode"),
readString(modelData, "ModelName")
));
}
return attach;
}
}
// / <summary>
// / 焦点选择描述
// / 2025.08.17.2050.LLX
// / </summary>
class C_Focus {
id = "";
name = "";
time = "3000";
constructor(focus?: Partial<C_Focus>) {
if (focus) {
this.id = focus.id ?? this.id;
this.name = focus.name ?? this.name;
this.time = focus.time ?? this.time;
}
}
/**
* 从JSON数据反序列化
* @param jsonData JSON格式的数据
* @returns C_Focus
*/
static fromjson(jsonData: unknown) {
const focus = new C_Focus();
if (isRecord(jsonData)) {
focus.id = readString(jsonData, "id", focus.id);
focus.name = readString(jsonData, "name", focus.name);
focus.time = readString(jsonData, "time", focus.time);
}
return focus;
}
}
class ModelGroup {
/**
* 模型代码
*/
ModelCode = "";
/**
* 模型名称
*/
ModelName = "";
/**
* 构造函数
* @param modelCode 模型代码
* @param modelName 模型名称
*/
constructor(modelCode = "", modelName = "") {
this.ModelCode = modelCode;
this.ModelName = modelName;
}
}
export {
C_Attach,
C_Craft,
C_Frames,
C_OPERATION,
C_ObjStates,
C_Rotation,
C_Signalwrite,
C_Visible,
C_Wait,
ModelGroup,
P_OPERATION,
C_Focus
};

View File

@@ -0,0 +1,148 @@
// 数据库存储的数据
interface OperationTreeDB {
OperationTree: string; // JSON字符串格式化之后是 OperationTree[] 列表
}
// 工艺数据机构
interface OperationTree {
/** 主键ID */
ID: string;
/** 是否自动插入 */
IsAutoInsert?: boolean | null;
/** 工艺代码(主键) */
CraftCode: string;
/** 工艺类型代码 */
CraftTypeCode: string;
/** 品种 */
Varieties: string;
/** 工艺名称 */
CraftName: string;
/** 工艺设备类型代码 */
CraftDeviceTypeCode: number;
/** 项目代码(主键) */
ProjectCode: string;
/** 操作时间 */
OperationTime: Date | string;
/** 序列号 */
SequenceNo?: number | null;
/** 开始时间 */
StartTime?: number | null;
/** 持续时间 */
KeepTime?: number | null;
/** 模型代码 */
ModelCode: string | null;
/** 工艺播放数据 */
CraftPlayData?: string | null;
/** 关节工艺播放数据 */
CraftPlayData_Joint?: string | null;
/** 原始工艺播放数据 */
CraftPlayData_Raw: string;
/** 速率 */
Rate: number | 0;
/** 说明 */
Explain?: string | null;
/** 反向 */
Reverse?: number | null;
/** 当前播放 */
CurrPlay?: boolean | null;
/** 脚本文本 */
ScriptText?: string | null;
/** 是否存在脚本 */
ExistScript?: boolean | null;
/** 甘特图父节点 */
GanttParent?: string | null;
/** 父模型代码 */
ModelCodeParent?: string | null;
/** 父模型名称 */
ModelNameParent?: string | null;
/** 模型名称 */
ModelName?: string | null;
/** 标签ID */
TagID?: string | null;
/** 标签值 */
TagValue?: string | null;
}
// 如果需要创建新对象的接口(所有字段可选)
interface OperationTreeCreate {
ID?: string;
IsAutoInsert?: boolean;
CraftCode: string;
CraftTypeCode?: string;
Varieties?: string;
CraftName?: string;
CraftDeviceTypeCode?: number;
ProjectCode: string;
OperationTime?: Date | string;
SequenceNo?: number;
StartTime?: number;
KeepTime?: number;
ModelCode?: string;
CraftPlayData?: string;
CraftPlayData_Joint?: string;
CraftPlayData_Raw?: string;
Rate?: number;
Explain?: string;
Reverse?: number;
CurrPlay?: boolean;
ScriptText?: string;
ExistScript?: boolean;
GanttParent?: string;
ModelCodeParent?: string;
ModelNameParent?: string;
ModelName?: string;
TagID?: string;
TagValue?: string;
}
interface OperationTreeExtra extends OperationTree {
pId: string;
id: string;
parent: string;
state: any;
text: string;
name: string;
icon: string;
checked: boolean;
open: boolean;
}
interface JsTreeData_node_OperationTree {
id: string;
text: string;
original: OperationTreeExtra;
icon: string;
children: string[]
children_d: string[]
parent: string
parents: string[]
state: {
loaded: boolean;
checked: boolean;
opened: boolean;
disabled: boolean;
selected: boolean;
};
}
interface JsTreeData_OperationTree_drop {
node: JsTreeData_node_OperationTree;
old_parent: string;
parent: string;
old_position: number;
position: number;
}
interface OperationTree_SequenceNo {
CraftTypeCode: string;
CraftCode: string;
SequenceNo: number;
}
export type {
OperationTreeDB,
OperationTree,
OperationTreeCreate,
OperationTreeExtra,
OperationTree_SequenceNo,
JsTreeData_node_OperationTree,
JsTreeData_OperationTree_drop
}