|
|
|
@ -1,11 +1,14 @@
|
|
|
|
<script setup lang="ts">
|
|
|
|
<script setup lang="ts">
|
|
|
|
import IconDeleteBtn from "@/components/atoms/button/IconDeleteBtn.vue";
|
|
|
|
import IconDeleteBtn from "@/components/atoms/button/IconDeleteBtn.vue";
|
|
|
|
|
|
|
|
import IconDownloadBtn from "@/components/atoms/button/IconDownloadBtn.vue";
|
|
|
|
import IconModifyBtn from "@/components/atoms/button/IconModifyBtn.vue";
|
|
|
|
import IconModifyBtn from "@/components/atoms/button/IconModifyBtn.vue";
|
|
|
|
import IconInfoBtn from "@/components/atoms/button/IconInfoBtn.vue";
|
|
|
|
import IconInfoBtn from "@/components/atoms/button/IconInfoBtn.vue";
|
|
|
|
import { computed, onMounted, ref, watch } from "vue";
|
|
|
|
import { computed, onMounted, ref, watch } from "vue";
|
|
|
|
import { storage } from "@/utils/storage";
|
|
|
|
import { storage } from "@/utils/storage";
|
|
|
|
import ViewComponent from "@/components/templates/trainingscript/ViewComponent.vue";
|
|
|
|
import ViewComponent from "@/components/templates/trainingscript/ViewComponent.vue";
|
|
|
|
import TrainingScriptBaseDoalog from "@/components/atoms/organisms/TrainingScriptBaseDoalog.vue";
|
|
|
|
import TrainingScriptBaseDoalog from "@/components/atoms/organisms/TrainingScriptBaseDoalog.vue";
|
|
|
|
|
|
|
|
import AutoScriptDialog from "@/components/atoms/organisms/AutoScriptDialog.vue";
|
|
|
|
|
|
|
|
import ScriptCompileDialog from "@/components/atoms/organisms/ScriptCompileDialog.vue";
|
|
|
|
import { AttachmentsService } from "@/components/service/management/AttachmentsService";
|
|
|
|
import { AttachmentsService } from "@/components/service/management/AttachmentsService";
|
|
|
|
import { commonStore } from "@/stores/commonStore";
|
|
|
|
import { commonStore } from "@/stores/commonStore";
|
|
|
|
import { useRoute, useRouter } from "vue-router";
|
|
|
|
import { useRoute, useRouter } from "vue-router";
|
|
|
|
@ -41,8 +44,9 @@ const pageSizeOptions = [
|
|
|
|
{ text: "100 페이지", value: 100 },
|
|
|
|
{ text: "100 페이지", value: 100 },
|
|
|
|
];
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
// 테이블 헤더
|
|
|
|
// 테이블 헤더 (맨 앞 컬럼은 선택 체크박스용)
|
|
|
|
const tableHeader = [
|
|
|
|
const tableHeader = [
|
|
|
|
|
|
|
|
{ label: "", width: "4%", style: "word-break: keep-all;" },
|
|
|
|
{ label: "No", width: "5%", style: "word-break: keep-all;" },
|
|
|
|
{ label: "No", width: "5%", style: "word-break: keep-all;" },
|
|
|
|
{ label: "Title", width: "7%", style: "word-break: keep-all;" },
|
|
|
|
{ label: "Title", width: "7%", style: "word-break: keep-all;" },
|
|
|
|
{ label: "File Name", width: "7%", style: "word-break: keep-all;" },
|
|
|
|
{ label: "File Name", width: "7%", style: "word-break: keep-all;" },
|
|
|
|
@ -68,6 +72,7 @@ const data = ref({
|
|
|
|
selected: [] as Array<{ deviceKey: number }>,
|
|
|
|
selected: [] as Array<{ deviceKey: number }>,
|
|
|
|
isCreateVisible: false,
|
|
|
|
isCreateVisible: false,
|
|
|
|
isUploadVisible: false,
|
|
|
|
isUploadVisible: false,
|
|
|
|
|
|
|
|
isAutoScriptVisible: false,
|
|
|
|
isModalVisible: false,
|
|
|
|
isModalVisible: false,
|
|
|
|
isConfirmDialogVisible: false,
|
|
|
|
isConfirmDialogVisible: false,
|
|
|
|
userOption: [] as any[],
|
|
|
|
userOption: [] as any[],
|
|
|
|
@ -150,7 +155,8 @@ const fetchList = async () => {
|
|
|
|
sortField: "id",
|
|
|
|
sortField: "id",
|
|
|
|
sortDirection: "DESC",
|
|
|
|
sortDirection: "DESC",
|
|
|
|
refType: "TRAINING_SCRIPT",
|
|
|
|
refType: "TRAINING_SCRIPT",
|
|
|
|
refId: activeRefId.value,
|
|
|
|
// 그룹 미선택 시 refId=0으로 조회 (Auto Script 등 refId 0 저장 항목 포함)
|
|
|
|
|
|
|
|
refId: activeRefId.value ?? 0,
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
try {
|
|
|
|
@ -288,6 +294,59 @@ const closeDetail = () => {
|
|
|
|
openView.value = false;
|
|
|
|
openView.value = false;
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function downloadScript(item: any) {
|
|
|
|
|
|
|
|
const objectName = item?.filePath || item?.storagePath;
|
|
|
|
|
|
|
|
if (!objectName) {
|
|
|
|
|
|
|
|
store.setSnackbarMsg({ color: "warning", text: "다운로드 경로가 없습니다.", result: 400 });
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
const res = await AttachmentsService.downloadFile(objectName);
|
|
|
|
|
|
|
|
const ct = String(res.headers["content-type"] || "").toLowerCase();
|
|
|
|
|
|
|
|
if (ct.includes("application/json")) {
|
|
|
|
|
|
|
|
const text = await (res.data as Blob).text();
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
const json = JSON.parse(text);
|
|
|
|
|
|
|
|
throw new Error(json.message || text);
|
|
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
|
|
throw new Error(text);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
const cd = res.headers["content-disposition"] || "";
|
|
|
|
|
|
|
|
const mUtf8 = cd.match(/filename\*\s*=\s*UTF-8''([^;]+)/i);
|
|
|
|
|
|
|
|
const mStd = cd.match(/filename\s*=\s*(?:"([^"]+)"|([^;]+))/i);
|
|
|
|
|
|
|
|
const filename =
|
|
|
|
|
|
|
|
(mUtf8 && decodeURIComponent(mUtf8[1].trim())) ||
|
|
|
|
|
|
|
|
(mStd && (mStd[1] || mStd[2])?.trim()) ||
|
|
|
|
|
|
|
|
item?.fileName ||
|
|
|
|
|
|
|
|
objectName.split(/[\\/]/).pop() ||
|
|
|
|
|
|
|
|
"download";
|
|
|
|
|
|
|
|
const blob = new Blob([res.data]);
|
|
|
|
|
|
|
|
const url = URL.createObjectURL(blob);
|
|
|
|
|
|
|
|
const a = document.createElement("a");
|
|
|
|
|
|
|
|
a.href = url;
|
|
|
|
|
|
|
|
a.setAttribute("download", filename);
|
|
|
|
|
|
|
|
document.body.appendChild(a);
|
|
|
|
|
|
|
|
a.click();
|
|
|
|
|
|
|
|
a.remove();
|
|
|
|
|
|
|
|
URL.revokeObjectURL(url);
|
|
|
|
|
|
|
|
store.setSnackbarMsg({ color: "success", text: "다운로드되었습니다.", result: 200 });
|
|
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
|
|
console.error("[TrainingScript] 다운로드 실패:", e);
|
|
|
|
|
|
|
|
store.setSnackbarMsg({ color: "warning", text: "다운로드에 실패했습니다.", result: 500 });
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const compileDialogOpen = ref(false);
|
|
|
|
|
|
|
|
const compileDialogItem = ref<{ deviceKey?: number; id?: number; title?: string; fileName?: string; filePath?: string } | null>(null);
|
|
|
|
|
|
|
|
function openCompileDialog(item: any) {
|
|
|
|
|
|
|
|
compileDialogItem.value = item ? { deviceKey: item.deviceKey, id: item.id, title: item.title, fileName: item.fileName, filePath: item.filePath } : null;
|
|
|
|
|
|
|
|
compileDialogOpen.value = true;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
function onCompiled() {
|
|
|
|
|
|
|
|
store.setSnackbarMsg({ color: "success", text: "스크립트 컴파일이 완료되었습니다.", result: 200 });
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const openDetailModal = (selectedItem: any) => {
|
|
|
|
const openDetailModal = (selectedItem: any) => {
|
|
|
|
data.value.selectedData = selectedItem;
|
|
|
|
data.value.selectedData = selectedItem;
|
|
|
|
openView.value = true;
|
|
|
|
openView.value = true;
|
|
|
|
@ -312,6 +371,94 @@ const openModifyModal = (item: any) => {
|
|
|
|
};
|
|
|
|
};
|
|
|
|
data.value.isCreateVisible = true;
|
|
|
|
data.value.isCreateVisible = true;
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const openAutoScriptModal = () => {
|
|
|
|
|
|
|
|
data.value.isAutoScriptVisible = true;
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
const closeAutoScriptModal = () => {
|
|
|
|
|
|
|
|
data.value.isAutoScriptVisible = false;
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
const openMergeScriptModal = async () => {
|
|
|
|
|
|
|
|
const selected = data.value.selected;
|
|
|
|
|
|
|
|
if (!selected || selected.length === 0) {
|
|
|
|
|
|
|
|
store.setSnackbarMsg({
|
|
|
|
|
|
|
|
color: "warning",
|
|
|
|
|
|
|
|
text: "머지할 스크립트를 선택하세요.",
|
|
|
|
|
|
|
|
result: 400,
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const projectId = getProjectId();
|
|
|
|
|
|
|
|
if (!projectId) {
|
|
|
|
|
|
|
|
store.setSnackbarMsg({
|
|
|
|
|
|
|
|
color: "warning",
|
|
|
|
|
|
|
|
text: "프로젝트를 먼저 선택하세요.",
|
|
|
|
|
|
|
|
result: 400,
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const scriptIds = selected.map((x) => x.deviceKey);
|
|
|
|
|
|
|
|
const defaultTitle = `merged-${scriptIds.join("-")}`;
|
|
|
|
|
|
|
|
const title = window.prompt("머지 결과 스크립트 제목을 입력하세요.", defaultTitle);
|
|
|
|
|
|
|
|
if (!title) {
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
const raw =
|
|
|
|
|
|
|
|
storage?.get?.("autoflow-auth") ??
|
|
|
|
|
|
|
|
storage?.getAuth?.() ??
|
|
|
|
|
|
|
|
localStorage.getItem("autoflow-auth") ??
|
|
|
|
|
|
|
|
null;
|
|
|
|
|
|
|
|
const auth = typeof raw === "string" ? JSON.parse(raw) : raw;
|
|
|
|
|
|
|
|
const userInfo = auth?.userInfo ?? auth?.userinfo ?? auth ?? {};
|
|
|
|
|
|
|
|
const userId = userInfo.username ?? userInfo.id ?? "unknown";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const payload = {
|
|
|
|
|
|
|
|
scriptIds,
|
|
|
|
|
|
|
|
title,
|
|
|
|
|
|
|
|
description: "",
|
|
|
|
|
|
|
|
refId: activeRefId.value ?? 0,
|
|
|
|
|
|
|
|
refType: "TRAINING_SCRIPT",
|
|
|
|
|
|
|
|
regUserId: String(userId),
|
|
|
|
|
|
|
|
projectId,
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const res = await AttachmentsService.mergeScripts(payload as any);
|
|
|
|
|
|
|
|
const att = (res as any)?.data?.attachment ?? (res as any)?.attachment ?? null;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
store.setSnackbarMsg({
|
|
|
|
|
|
|
|
color: "success",
|
|
|
|
|
|
|
|
text: "Merge Script가 생성되었습니다.",
|
|
|
|
|
|
|
|
result: 200,
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (att?.id) {
|
|
|
|
|
|
|
|
// 새로 생성된 스크립트가 보이도록 첫 페이지로 이동 후 리프레시
|
|
|
|
|
|
|
|
data.value.params.pageNum = 1;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
fetchList();
|
|
|
|
|
|
|
|
} catch (e: any) {
|
|
|
|
|
|
|
|
console.error("[TrainingScript] Merge Script 실패:", e);
|
|
|
|
|
|
|
|
const msg =
|
|
|
|
|
|
|
|
e?.response?.data?.error ??
|
|
|
|
|
|
|
|
e?.response?.data?.message ??
|
|
|
|
|
|
|
|
e?.message ??
|
|
|
|
|
|
|
|
"Merge Script 생성에 실패했습니다.";
|
|
|
|
|
|
|
|
store.setSnackbarMsg({
|
|
|
|
|
|
|
|
color: "warning",
|
|
|
|
|
|
|
|
text: msg,
|
|
|
|
|
|
|
|
result: e?.response?.status ?? 500,
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
// Auto Script 저장 후: 목록만 갱신 (현재 그룹이면 해당 그룹 refId로 저장되어 목록에 표시)
|
|
|
|
|
|
|
|
const onAutoScriptSaved = () => {
|
|
|
|
|
|
|
|
fetchList();
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const closeCreateModal = () => {
|
|
|
|
const closeCreateModal = () => {
|
|
|
|
data.value.isModalVisible = false;
|
|
|
|
data.value.isModalVisible = false;
|
|
|
|
data.value.isCreateVisible = false;
|
|
|
|
data.value.isCreateVisible = false;
|
|
|
|
@ -457,8 +604,12 @@ watch(
|
|
|
|
</v-sheet>
|
|
|
|
</v-sheet>
|
|
|
|
</v-sheet>
|
|
|
|
</v-sheet>
|
|
|
|
|
|
|
|
|
|
|
|
<v-sheet class="justify-end mb-2">
|
|
|
|
<v-sheet class="d-flex align-center justify-end mb-2">
|
|
|
|
<v-btn color="info" @click="openCreateModal">Create Script</v-btn>
|
|
|
|
<v-btn color="info" class="mr-2" @click="openCreateModal"
|
|
|
|
|
|
|
|
>Upload Script</v-btn
|
|
|
|
|
|
|
|
>
|
|
|
|
|
|
|
|
<v-btn color="info" class="mr-2" @click="openAutoScriptModal">Auto Script</v-btn>
|
|
|
|
|
|
|
|
<v-btn color="info" @click="openMergeScriptModal">Merge Script</v-btn>
|
|
|
|
</v-sheet>
|
|
|
|
</v-sheet>
|
|
|
|
</v-sheet>
|
|
|
|
</v-sheet>
|
|
|
|
|
|
|
|
|
|
|
|
@ -488,7 +639,17 @@ watch(
|
|
|
|
class="text-center font-weight-bold"
|
|
|
|
class="text-center font-weight-bold"
|
|
|
|
:style="item.style"
|
|
|
|
:style="item.style"
|
|
|
|
>
|
|
|
|
>
|
|
|
|
{{ item.label }}
|
|
|
|
<template v-if="i === 0">
|
|
|
|
|
|
|
|
<v-checkbox
|
|
|
|
|
|
|
|
v-model="data.allSelected"
|
|
|
|
|
|
|
|
density="compact"
|
|
|
|
|
|
|
|
hide-details
|
|
|
|
|
|
|
|
@change="getSelectedAllData"
|
|
|
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<template v-else>
|
|
|
|
|
|
|
|
{{ item.label }}
|
|
|
|
|
|
|
|
</template>
|
|
|
|
</th>
|
|
|
|
</th>
|
|
|
|
</tr>
|
|
|
|
</tr>
|
|
|
|
</thead>
|
|
|
|
</thead>
|
|
|
|
@ -499,6 +660,14 @@ watch(
|
|
|
|
:key="i"
|
|
|
|
:key="i"
|
|
|
|
class="text-center"
|
|
|
|
class="text-center"
|
|
|
|
>
|
|
|
|
>
|
|
|
|
|
|
|
|
<td>
|
|
|
|
|
|
|
|
<v-checkbox
|
|
|
|
|
|
|
|
v-model="data.selected"
|
|
|
|
|
|
|
|
:value="{ deviceKey: item.deviceKey }"
|
|
|
|
|
|
|
|
density="compact"
|
|
|
|
|
|
|
|
hide-details
|
|
|
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
</td>
|
|
|
|
<td>
|
|
|
|
<td>
|
|
|
|
{{
|
|
|
|
{{
|
|
|
|
data.totalElements -
|
|
|
|
data.totalElements -
|
|
|
|
@ -512,6 +681,21 @@ watch(
|
|
|
|
<td>{{ item.createdData }}</td>
|
|
|
|
<td>{{ item.createdData }}</td>
|
|
|
|
|
|
|
|
|
|
|
|
<td style="white-space: nowrap">
|
|
|
|
<td style="white-space: nowrap">
|
|
|
|
|
|
|
|
<v-tooltip location="bottom" text="스크립트 컴파일">
|
|
|
|
|
|
|
|
<template #activator="{ props: tooltipProps }">
|
|
|
|
|
|
|
|
<v-btn
|
|
|
|
|
|
|
|
v-bind="tooltipProps"
|
|
|
|
|
|
|
|
icon="mdi-hammer-wrench"
|
|
|
|
|
|
|
|
color="secondary"
|
|
|
|
|
|
|
|
density="comfortable"
|
|
|
|
|
|
|
|
elevation="0"
|
|
|
|
|
|
|
|
size="small"
|
|
|
|
|
|
|
|
class="ma-1"
|
|
|
|
|
|
|
|
@click="openCompileDialog(item)"
|
|
|
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
</v-tooltip>
|
|
|
|
|
|
|
|
<IconDownloadBtn @on-click="downloadScript(item)" />
|
|
|
|
<IconInfoBtn @on-click="openDetailModal(item)" />
|
|
|
|
<IconInfoBtn @on-click="openDetailModal(item)" />
|
|
|
|
<IconModifyBtn @on-click="openModifyModal(item)" />
|
|
|
|
<IconModifyBtn @on-click="openModifyModal(item)" />
|
|
|
|
<IconDeleteBtn
|
|
|
|
<IconDeleteBtn
|
|
|
|
@ -551,6 +735,22 @@ watch(
|
|
|
|
:user-option="data.userOption"
|
|
|
|
:user-option="data.userOption"
|
|
|
|
/>
|
|
|
|
/>
|
|
|
|
</v-dialog>
|
|
|
|
</v-dialog>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<!-- Auto Script 다이얼로그 (선택된 그룹 refId 전달 → 해당 그룹 목록에 저장) -->
|
|
|
|
|
|
|
|
<v-dialog v-model="data.isAutoScriptVisible" max-width="720" persistent>
|
|
|
|
|
|
|
|
<AutoScriptDialog
|
|
|
|
|
|
|
|
:current-ref-id="activeRefId"
|
|
|
|
|
|
|
|
@close-modal="closeAutoScriptModal"
|
|
|
|
|
|
|
|
@generated="onAutoScriptSaved"
|
|
|
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
</v-dialog>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 스크립트 컴파일 창 (로그 확인 후 컴파일 실행) -->
|
|
|
|
|
|
|
|
<ScriptCompileDialog
|
|
|
|
|
|
|
|
v-model="compileDialogOpen"
|
|
|
|
|
|
|
|
:item="compileDialogItem"
|
|
|
|
|
|
|
|
@compiled="onCompiled"
|
|
|
|
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="w-100" v-else>
|
|
|
|
<div class="w-100" v-else>
|
|
|
|
|