403 lines
11 KiB
Vue
403 lines
11 KiB
Vue
<script setup>
|
|
import {
|
|
ref,
|
|
defineProps,
|
|
watch,
|
|
inject,
|
|
nextTick,
|
|
onMounted,
|
|
toRaw,
|
|
} from "vue";
|
|
import dayjs from "dayjs";
|
|
import { postOperationRecord } from "@/apis/alert";
|
|
import * as yup from "yup";
|
|
import "yup-phone-lite";
|
|
import useFormErrorMessage from "@/hooks/useFormErrorMessage";
|
|
import { useI18n } from "vue-i18n";
|
|
const { t } = useI18n();
|
|
const FILE_BASEURL = import.meta.env.VITE_FILE_API_BASEURL;
|
|
|
|
const props = defineProps({
|
|
editRecord: { type: Object, default: null },
|
|
});
|
|
|
|
const form = ref(null);
|
|
|
|
const dateItem = ref([
|
|
{
|
|
key: "start_time",
|
|
name: "start_time",
|
|
value: dayjs(),
|
|
dateFormat: "yyyy-MM-dd",
|
|
placeholder: t("alert.start_time"),
|
|
},
|
|
]);
|
|
|
|
const formState = ref({
|
|
formId: null,
|
|
uuid: null,
|
|
work_type: 2,
|
|
fix_do: "",
|
|
fix_do_code: "",
|
|
fix_firm: "",
|
|
status: 0,
|
|
device_number: "",
|
|
work_person_id: "",
|
|
start_time: dayjs().format("YYYY-MM-DD"),
|
|
notice: "",
|
|
description: "",
|
|
lorf: [],
|
|
});
|
|
|
|
const { model_data, updateEditRecord, search } = inject("alert_modal") || {
|
|
model_data: [],
|
|
updateEditRecord: null,
|
|
search: null,
|
|
};
|
|
|
|
let alertSchema = yup.object({
|
|
start_time: yup.date().required(t("button.required")),
|
|
fix_do: yup.string().required(t("button.required")),
|
|
fix_firm: yup.string().required(t("button.required")),
|
|
status: yup.number().required(t("button.required")),
|
|
work_person_id: yup.string().required(t("button.required")),
|
|
notice: yup.string().nullable(true),
|
|
description: yup.string().nullable(true),
|
|
});
|
|
|
|
const { formErrorMsg, handleSubmit, handleErrorReset } =
|
|
useFormErrorMessage(alertSchema);
|
|
|
|
const updateFileList = (files) => {
|
|
formState.value.lorf = files;
|
|
};
|
|
|
|
// ---------------------- 告警影片儲存位置:顯示 API 的 video_url ----------------------
|
|
const videoLocation = ref({ videoLocation: "" });
|
|
const showTooltip = ref(false);
|
|
|
|
async function copyToClipboard() {
|
|
const text = videoLocation.value.videoLocation || "";
|
|
try {
|
|
await navigator.clipboard.writeText(text);
|
|
// 觸發 tooltip 動畫
|
|
showTooltip.value = false;
|
|
await nextTick();
|
|
showTooltip.value = true;
|
|
setTimeout(() => {
|
|
showTooltip.value = false;
|
|
}, 1500);
|
|
} catch (err) {
|
|
console.error("複製失敗:", err);
|
|
}
|
|
}
|
|
|
|
const onOk = async () => {
|
|
const formData = new FormData(form.value);
|
|
formData.delete("oriFile");
|
|
|
|
(formState.value?.lorf ?? []).forEach((file, index) => {
|
|
formData.append(`lorf[${index}].id`, file?.id ? file.id : "");
|
|
// 只有新檔案才上傳檔案本體
|
|
if (!file?.id && file) {
|
|
formData.append(`lorf[${index}].file`, file);
|
|
}
|
|
formData.append(
|
|
`lorf[${index}].save_file_name`,
|
|
file?.id ? file.save_file_name : ""
|
|
);
|
|
formData.append(`lorf[${index}].ori_file_name`, file?.name ?? "");
|
|
});
|
|
|
|
formData.append(
|
|
"start_time",
|
|
dayjs(dateItem.value[0].value).format("YYYY-MM-DD")
|
|
);
|
|
|
|
if (props.editRecord.id) formData.append("id", props.editRecord.id);
|
|
if (props.editRecord.uuid)
|
|
formData.append("error_code", props.editRecord.uuid);
|
|
|
|
formData.append("work_type", 2);
|
|
formData.append(
|
|
"fix_do_code",
|
|
props.editRecord.main_id
|
|
? props.editRecord.main_id
|
|
: props.editRecord.fix_do_code
|
|
);
|
|
|
|
try {
|
|
await handleSubmit(alertSchema, formState.value);
|
|
const res = await postOperationRecord(formData);
|
|
if (res.isSuccess) {
|
|
search?.();
|
|
onCancel();
|
|
}
|
|
} catch (err) {
|
|
console.error("Form submission failed:", err);
|
|
}
|
|
};
|
|
|
|
const onCancel = () => {
|
|
formState.value = {
|
|
formId: null,
|
|
uuid: null,
|
|
work_type: 2,
|
|
fix_do: "",
|
|
fix_do_code: "",
|
|
fix_firm: "",
|
|
status: 0,
|
|
device_number: "",
|
|
work_person_id: "",
|
|
start_time: dayjs().format("YYYY-MM-DD"),
|
|
notice: "",
|
|
description: "",
|
|
lorf: [],
|
|
};
|
|
// 重置顯示用的影片路徑
|
|
videoLocation.value.videoLocation = "";
|
|
handleErrorReset();
|
|
updateEditRecord?.(null);
|
|
alert_action_item.close();
|
|
};
|
|
|
|
// 同步 props.editRecord -> formState / 日期 / 維修項目 / 影片網址
|
|
watch(
|
|
() => props.editRecord,
|
|
(newVal) => {
|
|
if (newVal) {
|
|
for (let [key, value] of Object.entries(newVal)) {
|
|
if (key in formState.value) {
|
|
formState.value[key] = value;
|
|
}
|
|
if (key === "start_time") {
|
|
const d = value ? dayjs(value) : dayjs();
|
|
formState.value.start_time = d.format("YYYY-MM-DD");
|
|
dateItem.value[0].value = d; // 一律用 dayjs 物件
|
|
}
|
|
if (key === "full_name") {
|
|
formState.value.fix_do = value ?? "";
|
|
}
|
|
}
|
|
// 取 API 回傳的影片位址(與 device_number 同來源物件)
|
|
videoLocation.value.videoLocation =
|
|
newVal?.video_url ?? newVal?.videoUrl ?? newVal?.video_path ?? "";
|
|
}
|
|
},
|
|
{ immediate: true }
|
|
);
|
|
</script>
|
|
|
|
<template>
|
|
<Modal
|
|
id="alert_action_item"
|
|
:title="t('alert.repair_order')"
|
|
:onCancel="onCancel"
|
|
:width="710"
|
|
>
|
|
<template #modalContent>
|
|
<form
|
|
ref="form"
|
|
class="mt-5 w-full flex flex-wrap justify-between"
|
|
@submit.prevent
|
|
>
|
|
<Input
|
|
v-if="formState && formState.formId"
|
|
class="my-2"
|
|
:value="formState"
|
|
name="formId"
|
|
readonly
|
|
>
|
|
<template #topLeft>{{ $t("alert.form_number") }}</template>
|
|
</Input>
|
|
<Input :value="formState" class="my-2" name="uuid" readonly>
|
|
<template #topLeft>{{ $t("alert.uuid") }}</template>
|
|
</Input>
|
|
<DateGroup
|
|
class="my-2"
|
|
:items="dateItem"
|
|
inputClass="w-full shadow-none"
|
|
:required="true"
|
|
>
|
|
<template #topLeft>{{ $t("alert.start_time") }}</template>
|
|
<template #bottomLeft>
|
|
<span class="text-error text-base">
|
|
{{ formErrorMsg.start_time }}
|
|
</span>
|
|
</template>
|
|
</DateGroup>
|
|
<Select
|
|
:value="formState"
|
|
class="my-2"
|
|
selectClass="border-info focus-within:border-info"
|
|
name="work_type"
|
|
Attribute="title"
|
|
:options="[
|
|
{
|
|
key: 1,
|
|
value: 1,
|
|
title: $t('alert.maintenance'),
|
|
},
|
|
{
|
|
key: 2,
|
|
value: 2,
|
|
title: $t('alert.repair'),
|
|
},
|
|
]"
|
|
:required="true"
|
|
:disabled="true"
|
|
>
|
|
<template #topLeft>{{ $t("alert.item") }}</template>
|
|
</Select>
|
|
<Input class="my-2" :value="formState" name="fix_do" :required="true">
|
|
<template #topLeft>{{ $t("alert.repair_item") }}</template>
|
|
<template #bottomLeft
|
|
><span class="text-error text-base">
|
|
{{ formErrorMsg.fix_do }}
|
|
</span>
|
|
</template>
|
|
</Input>
|
|
<Input
|
|
class="my-2"
|
|
:value="formState"
|
|
name="device_number"
|
|
:required="true"
|
|
:disabled="true"
|
|
>
|
|
<template #topLeft>{{ $t("alert.repair_item_code") }}</template>
|
|
<template #bottomLeft
|
|
><span class="text-error text-base">
|
|
{{ formErrorMsg.fix_do_code }}
|
|
</span></template
|
|
>
|
|
</Input>
|
|
<Select
|
|
:value="formState"
|
|
class="my-2"
|
|
selectClass="border-info focus-within:border-info"
|
|
name="fix_firm"
|
|
Attribute="name"
|
|
:options="model_data.model_companyList"
|
|
:required="true"
|
|
>
|
|
<template #topLeft>{{ $t("alert.responsible_vendor") }}</template>
|
|
<template #bottomLeft
|
|
><span class="text-error text-base">
|
|
{{ formErrorMsg.fix_firm }}
|
|
</span></template
|
|
>
|
|
</Select>
|
|
<RadioGroup
|
|
class="my-2"
|
|
name="status"
|
|
:value="formState"
|
|
:items="[
|
|
{
|
|
key: 0,
|
|
value: 0,
|
|
title: $t('alert.not_completed'),
|
|
},
|
|
{
|
|
key: 1,
|
|
value: 1,
|
|
title: $t('alert.completed'),
|
|
},
|
|
]"
|
|
:required="true"
|
|
>
|
|
<template #topLeft>{{ $t("alert.status") }}</template>
|
|
</RadioGroup>
|
|
<Select
|
|
:value="formState"
|
|
class="my-2"
|
|
selectClass="border-info focus-within:border-info"
|
|
name="work_person_id"
|
|
Attribute="full_name"
|
|
:options="model_data.model_userList"
|
|
:required="true"
|
|
>
|
|
<template #topLeft>{{ $t("alert.worker_id") }}</template>
|
|
<template #bottomLeft
|
|
><span class="text-error text-base">
|
|
{{ formErrorMsg.work_person_id }}
|
|
</span></template
|
|
>
|
|
</Select>
|
|
|
|
<!-- 注意事項 -->
|
|
<Textarea :value="formState" name="notice" class="w-full my-2">
|
|
<template #topLeft>{{ $t("alert.notice") }}</template>
|
|
</Textarea>
|
|
|
|
<!-- 告警影片儲存位置-->
|
|
<div class="my-4 w-full">
|
|
<label class="text-lg">
|
|
{{ $t("alert.video_storage_location") }}
|
|
</label>
|
|
<div class="flex items-center gap-3">
|
|
<Input
|
|
class="flex-1"
|
|
name="videoLocation"
|
|
:value="videoLocation"
|
|
readonly
|
|
/>
|
|
<div class="relative inline-flex items-center">
|
|
<button
|
|
type="button"
|
|
class="btn btn-success"
|
|
@click.stop="copyToClipboard"
|
|
>
|
|
{{ $t("alert.copy") }}
|
|
</button>
|
|
|
|
<transition name="fade">
|
|
<span
|
|
v-if="showTooltip"
|
|
class="absolute left-full ml-4 top-1/2 -translate-y-1/2 text-white text-xs px-2 py-1 bg-gray-800 rounded shadow whitespace-nowrap"
|
|
role="status"
|
|
aria-live="polite"
|
|
>
|
|
{{ $t("alert.copied") }}
|
|
</span>
|
|
</transition>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 結果描述 -->
|
|
<Textarea :value="formState" name="description" class="w-full my-2">
|
|
<template #topLeft>{{ $t("alert.result_description") }}</template>
|
|
</Textarea>
|
|
<Upload
|
|
class="my-2"
|
|
name="oriFile"
|
|
:fileList="formState?.lorf"
|
|
:getFileList="updateFileList"
|
|
:multiple="true"
|
|
:baseUrl="`${FILE_BASEURL}/upload/operation`"
|
|
>
|
|
<template #topLeft>{{ $t("alert.upload_file") }}</template>
|
|
</Upload>
|
|
</form>
|
|
</template>
|
|
<template #modalAction>
|
|
<button
|
|
type="reset"
|
|
class="btn btn-outline-success mr-2"
|
|
@click.prevent="onCancel"
|
|
>
|
|
{{ $t("button.cancel") }}
|
|
</button>
|
|
<button
|
|
type="submit"
|
|
class="btn btn-outline-success"
|
|
@click.stop.prevent="onOk"
|
|
>
|
|
{{ $t("button.submit") }}
|
|
</button>
|
|
</template>
|
|
</Modal>
|
|
</template>
|
|
|
|
<style lang="scss" scoped></style>
|