<template>
|
<el-form :model="form" label-width="120px" :rules="formRules" ref="formRef">
|
<el-row :gutter="20">
|
<el-col :span="12">
|
<el-form-item label="设备名称" prop="deviceName">
|
<el-input v-model="form.deviceName" placeholder="请输入设备名称" />
|
</el-form-item>
|
</el-col>
|
<el-col :span="12">
|
<el-form-item label="规格型号" prop="deviceModel">
|
<el-input v-model="form.deviceModel" placeholder="请输入规格型号" />
|
</el-form-item>
|
</el-col>
|
<el-col :span="12">
|
<el-form-item label="设备品牌" prop="deviceBrand">
|
<el-input v-model="form.deviceBrand" placeholder="请输入设备品牌" />
|
</el-form-item>
|
</el-col>
|
<el-col :span="12">
|
<el-form-item label="设备类型" prop="type">
|
<el-select
|
v-model="form.type"
|
placeholder="请选择或输入设备类型"
|
clearable
|
filterable
|
allow-create
|
default-first-option
|
style="width: 100%"
|
@change="handleDeviceTypeChange"
|
>
|
<el-option
|
v-for="item in deviceTypeOptions"
|
:key="item"
|
:label="item"
|
:value="item"
|
/>
|
</el-select>
|
</el-form-item>
|
</el-col>
|
<el-col :span="12">
|
<el-form-item label="工序" prop="productProcessId">
|
<el-select
|
v-model="form.productProcessId"
|
placeholder="请选择工序"
|
clearable
|
filterable
|
style="width: 100%"
|
>
|
<el-option
|
v-for="item in processOptions"
|
:key="item.value"
|
:label="item.label"
|
:value="item.value"
|
/>
|
</el-select>
|
</el-form-item>
|
</el-col>
|
<el-col :span="12">
|
<el-form-item label="供应商" prop="supplierName">
|
<el-input v-model="form.supplierName" placeholder="请输入供应商" />
|
</el-form-item>
|
</el-col>
|
<el-col :span="12">
|
<el-form-item label="存放位置" prop="storageLocation">
|
<el-input v-model="form.storageLocation" placeholder="请输入存放位置" />
|
</el-form-item>
|
</el-col>
|
<el-col :span="12">
|
<el-form-item label="单位" prop="unit">
|
<el-input v-model="form.unit" placeholder="请输入单位" />
|
</el-form-item>
|
</el-col>
|
<el-col :span="12">
|
<el-form-item label="启用折旧" prop="isDepr">
|
<el-switch v-model="form.isDepr" :active-value="1" :inactive-value="2" />
|
</el-form-item>
|
</el-col>
|
<el-col :span="12" v-if="form.isDepr === 1">
|
<el-form-item label="每年折旧金额" prop="annualDepreciationAmount">
|
<el-input-number
|
:step="0.01"
|
:min="0"
|
style="width: 100%"
|
v-model="form.annualDepreciationAmount"
|
placeholder="请输入每年折旧金额"
|
/>
|
</el-form-item>
|
</el-col>
|
<el-col :span="12">
|
<el-form-item label="数量" prop="number">
|
<el-input-number :min="1" style="width: 100%"
|
v-model="form.number"
|
disabled
|
placeholder="请输入数量"
|
@change="mathNum"
|
/>
|
</el-form-item>
|
</el-col>
|
<el-col :span="12">
|
<el-form-item label="含税单价" prop="taxIncludingPriceUnit">
|
<el-input-number :step="0.01" :min="0" style="width: 100%"
|
v-model="form.taxIncludingPriceUnit"
|
placeholder="请输入含税单价"
|
maxlength="10"
|
@change="mathNum"
|
/>
|
</el-form-item>
|
</el-col>
|
<el-col :span="12">
|
<el-form-item label="含税总价" prop="taxIncludingPriceTotal">
|
<el-input
|
v-model="form.taxIncludingPriceTotal"
|
placeholder="自动生成"
|
type="number"
|
disabled
|
/>
|
</el-form-item>
|
</el-col>
|
<el-col :span="12">
|
<el-form-item label="税率(%)" prop="taxRate">
|
<!-- <el-input
|
v-model="form.taxRate"
|
placeholder="请输入税率"
|
type="number"
|
>
|
<template #append> % </template>
|
</el-input> -->
|
<el-select
|
v-model="form.taxRate"
|
placeholder="请选择"
|
clearable
|
@change="mathNum"
|
>
|
<el-option label="1" :value="1" />
|
<el-option label="3" :value="3" />
|
<el-option label="6" :value="6" />
|
<el-option label="9" :value="9" />
|
<el-option label="13" :value="13" />
|
</el-select>
|
</el-form-item>
|
</el-col>
|
<el-col :span="12">
|
<el-form-item label="不含税总价" prop="unTaxIncludingPriceTotal">
|
<el-input
|
v-model="form.unTaxIncludingPriceTotal"
|
placeholder="自动生成"
|
type="number"
|
disabled
|
/>
|
</el-form-item>
|
</el-col>
|
<!-- <el-col :span="12">
|
<el-form-item label="录入人" prop="createUser">
|
<el-input v-model="form.createUser" placeholder="请输入录入人" />
|
</el-form-item>
|
</el-col> -->
|
<el-col :span="12">
|
<el-form-item label="录入日期" prop="createTime">
|
<el-date-picker
|
style="width: 100%"
|
v-model="form.createTime"
|
format="YYYY-MM-DD"
|
value-format="YYYY-MM-DD HH:mm:ss"
|
type="date"
|
placeholder="请选择录入日期"
|
clearable
|
/>
|
</el-form-item>
|
</el-col>
|
<el-col :span="12">
|
<el-form-item label="预计运行时间" prop="planRuntimeTime">
|
<el-date-picker
|
style="width: 100%"
|
v-model="form.planRuntimeTime"
|
format="YYYY-MM-DD"
|
value-format="YYYY-MM-DD"
|
type="date"
|
placeholder="请选择录入日期"
|
clearable
|
/>
|
</el-form-item>
|
</el-col>
|
<el-col :span="24">
|
<el-form-item label="设备图片" prop="deviceImage">
|
<div v-if="displayedExistingProblems.length" class="problem-existing-wrap">
|
<div v-for="img in displayedExistingProblems" :key="img.id" class="problem-thumb">
|
<el-image :src="img.url" fit="cover" class="problem-thumb-img" @click="previewProblem(img.url)" />
|
<el-icon class="problem-thumb-remove" @click.stop="markProblemRemove(img.id)"><Close /></el-icon>
|
</div>
|
</div>
|
<el-upload
|
class="repair-attachment-upload"
|
v-model:file-list="attachmentFileList"
|
drag
|
multiple
|
:auto-upload="false"
|
:limit="9"
|
accept="image/png,image/jpeg,image/jpg"
|
:before-upload="beforeUpload"
|
>
|
<el-icon class="repair-upload-icon"><UploadFilled /></el-icon>
|
<div class="el-upload__text">将文件拖到此处,或 <em>点击选择文件</em></div>
|
<template #tip>
|
<div class="el-upload__tip">
|
支持 png / jpg / jpeg,单张不超过 50MB,最多 9 张
|
</div>
|
</template>
|
</el-upload>
|
</el-form-item>
|
</el-col>
|
</el-row>
|
</el-form>
|
</template>
|
|
<script setup>
|
import useFormData from "@/hooks/useFormData";
|
import { getLedgerById, getLedgerFileList, uploadLedgerFile, deleteLedgerFile } from "@/api/equipmentManagement/ledger";
|
import { processList } from "@/api/productionManagement/productionProcess";
|
import dayjs from "dayjs";
|
import {
|
calculateTaxIncludeTotalPrice,
|
calculateTaxExclusiveTotalPrice,
|
} from "@/utils/summarizeTable";
|
import { ElMessage } from "element-plus";
|
import { ref, onMounted, getCurrentInstance } from "vue";
|
import { UploadFilled, Close } from "@element-plus/icons-vue";
|
import { getToken } from "@/utils/auth";
|
|
defineOptions({
|
name: "设备台账表单",
|
});
|
const formRef = ref(null);
|
const operationType = ref('');
|
// 设备类型固定选项
|
const deviceTypeOptions = ref([
|
'生产设备',
|
'办公设备',
|
'检测设备',
|
'运输设备',
|
'其他设备'
|
]);
|
const processOptions = ref([]);
|
const formRules = {
|
deviceName: [{ required: true, trigger: "blur", message: "请输入" }],
|
deviceModel: [{ required: true, trigger: "blur", message: "请输入" }],
|
type: [{ required: true, trigger: "change", message: "请选择或输入设备类型" }],
|
supplierName: [{ required: true, trigger: "blur", message: "请输入" }],
|
unit: [{ required: true, trigger: "blur", message: "请输入" }],
|
number: [{ required: true, trigger: "blur", message: "请输入" }],
|
taxIncludingPriceUnit: [{ required: true, trigger: "blur", message: "请输入" }],
|
taxRate: [{ required: true, trigger: "change", message: "请输入" }],
|
planRuntimeTime: [{ required: true, trigger: "change", message: "请选择" }],
|
annualDepreciationAmount: [
|
{
|
validator: (rule, value, callback) => {
|
if (form.isDepr === 1 && (value === undefined || value === null || value === '')) {
|
callback(new Error('启用折旧时,请输入每年折旧金额'));
|
} else {
|
callback();
|
}
|
},
|
trigger: "blur"
|
}
|
],
|
}
|
|
const { form, resetForm } = useFormData({
|
deviceName: undefined, // 设备名称
|
deviceModel: undefined, // 规格型号
|
deviceBrand: undefined, // 设备品牌
|
type: undefined, // 设备类型
|
productProcessId: undefined, // 工序ID
|
supplierName: undefined, // 供应商
|
storageLocation: undefined, // 存放位置
|
isDepr: 2, // 是否启用折旧 1-是 2-否
|
annualDepreciationAmount: undefined, // 每年折旧金额
|
unit: undefined, // 单位
|
number: 1, // 数量
|
taxIncludingPriceUnit: undefined, // 含税单价
|
taxIncludingPriceTotal: undefined, // 含税总价
|
taxRate: undefined, // 税率
|
unTaxIncludingPriceTotal: undefined, // 不含税总价
|
// createUser: useUserStore().nickName, // 录入人
|
createTime: dayjs().format("YYYY-MM-DD HH:mm:ss"), // 录入日期
|
planRuntimeTime: dayjs().format("YYYY-MM-DD"), // 录入日期
|
});
|
|
const attachmentFileList = ref([]);
|
const existingProblemImages = ref([]);
|
const pendingRemoveProblemIds = ref([]);
|
|
const displayedExistingProblems = computed(() =>
|
existingProblemImages.value.filter((img) => !pendingRemoveProblemIds.value.includes(img.id))
|
);
|
|
const markProblemRemove = (fileId) => {
|
if (!pendingRemoveProblemIds.value.includes(fileId)) {
|
pendingRemoveProblemIds.value.push(fileId);
|
}
|
};
|
|
const loadForm = async (id) => {
|
if (id) {
|
operationType.value = 'edit'
|
}
|
const { code, data } = await getLedgerById(id);
|
if (code == 200) {
|
form.deviceName = data.deviceName;
|
form.deviceModel = data.deviceModel;
|
form.deviceBrand = data.deviceBrand;
|
form.type = data.type;
|
const processName = data.productProcessName || data.processName || data.process;
|
form.productProcessId = data.productProcessId;
|
if (!form.productProcessId && processName) {
|
const matched = processOptions.value.find(item => item.label === processName);
|
form.productProcessId = matched?.value;
|
}
|
ensureProcessOptionExists(form.productProcessId, processName);
|
form.supplierName = data.supplierName;
|
form.storageLocation = data.storageLocation;
|
form.isDepr = data.isDepr;
|
form.annualDepreciationAmount = data.annualDepreciationAmount;
|
form.unit = data.unit;
|
form.number = 1;
|
form.taxIncludingPriceUnit = data.taxIncludingPriceUnit;
|
form.taxIncludingPriceTotal = data.taxIncludingPriceTotal;
|
form.taxRate = data.taxRate;
|
form.unTaxIncludingPriceTotal = data.unTaxIncludingPriceTotal;
|
form.createTime = data.createTime;
|
// 预计运行时间:后端返回后转为 YYYY-MM-DD 以便日期选择器正确展示
|
if (data.planRuntimeTime) {
|
form.planRuntimeTime = dayjs(data.planRuntimeTime).format('YYYY-MM-DD');
|
} else {
|
form.planRuntimeTime = undefined;
|
}
|
}
|
|
existingProblemImages.value = [];
|
pendingRemoveProblemIds.value = [];
|
attachmentFileList.value = [];
|
if (id) {
|
try {
|
const res = await getLedgerFileList(id);
|
const list = Array.isArray(res?.data) ? res.data : [];
|
existingProblemImages.value = list.map((item) => ({
|
id: item.id,
|
name: item.name,
|
url: getFileAccessUrl(item),
|
}));
|
} catch {
|
existingProblemImages.value = [];
|
}
|
}
|
};
|
|
const ensureProcessOptionExists = (id, name) => {
|
if (!id) return;
|
if (!processOptions.value.some(item => item.value === id)) {
|
processOptions.value.push({
|
value: id,
|
label: name || `${id}`,
|
});
|
}
|
};
|
|
const getProcessOptions = async () => {
|
try {
|
const { code, data } = await processList({});
|
if (code === 200 && Array.isArray(data)) {
|
processOptions.value = data
|
.filter(item => item?.id !== undefined && item?.id !== null)
|
.map(item => ({
|
value: item.id,
|
label: item?.name || `${item.id}`,
|
}));
|
ensureProcessOptionExists(form.productProcessId);
|
}
|
} catch (error) {
|
processOptions.value = [];
|
}
|
};
|
|
const handleDeviceTypeChange = (value) => {
|
// 如果输入的新值不在固定选项中,则添加到选项列表
|
if (value && !deviceTypeOptions.value.includes(value)) {
|
deviceTypeOptions.value.push(value);
|
}
|
};
|
|
const mathNum = () => {
|
if (!form.taxIncludingPriceUnit) {
|
ElMessage.error("请输入单价");
|
return;
|
}
|
form.taxIncludingPriceTotal = calculateTaxIncludeTotalPrice(
|
form.taxIncludingPriceUnit,
|
form.number
|
);
|
if (form.taxRate) {
|
form.unTaxIncludingPriceTotal = calculateTaxExclusiveTotalPrice(
|
form.taxIncludingPriceTotal,
|
form.taxRate
|
);
|
}
|
};
|
|
// 清除表单校验状态
|
const clearValidate = () => {
|
formRef.value?.clearValidate();
|
};
|
|
// 重置表单数据和校验状态
|
const resetFormAndValidate = () => {
|
resetForm();
|
clearValidate();
|
};
|
|
onMounted(() => {
|
getProcessOptions();
|
});
|
|
const uploadUrl = ref(import.meta.env.VITE_APP_BASE_API + "/common/upload");
|
const headers = ref({ Authorization: "Bearer " + getToken() });
|
const ATTACH_MAX_MB = 50;
|
|
const beforeUpload = (rawFile) => {
|
const okType = ["image/jpeg", "image/jpg", "image/png"].includes(rawFile.type);
|
if (!okType) {
|
ElMessage.error("只能上传 png / jpg / jpeg 图片");
|
return false;
|
}
|
const maxBytes = ATTACH_MAX_MB * 1024 * 1024;
|
if (rawFile.size > maxBytes) {
|
ElMessage.error(`单个文件不能超过 ${ATTACH_MAX_MB}MB`);
|
return false;
|
}
|
return true;
|
};
|
|
const submitFiles = async (ledgerId) => {
|
for (const fileId of pendingRemoveProblemIds.value) {
|
await deleteLedgerFile(fileId);
|
}
|
pendingRemoveProblemIds.value = [];
|
|
const rows = attachmentFileList.value || [];
|
const files = rows.map((f) => f.raw).filter(Boolean);
|
if (!files.length || ledgerId == null) return;
|
|
for (const file of files) {
|
const fd = new FormData();
|
fd.append("file", file);
|
fd.append("deviceLedgerId", String(ledgerId));
|
// 16 is the FileNameType for EQUIPMENT_LEDGER that we added
|
fd.append("fileType", "16");
|
const res = await uploadLedgerFile(fd);
|
if (res.code !== 200) {
|
throw new Error(res.msg || "附件上传失败");
|
}
|
}
|
attachmentFileList.value = [];
|
};
|
|
const getFileAccessUrl = (file = {}) => {
|
let raw = file?.link || file?.url || "";
|
if (String(raw).startsWith("http")) return raw;
|
const javaApi = getCurrentInstance()?.proxy?.javaApi || import.meta.env.VITE_APP_BASE_API || "/dev-api";
|
let fileUrl = raw;
|
if (fileUrl.indexOf("\\") > -1) {
|
const lowerPath = fileUrl.toLowerCase();
|
const uploadPathIndex = lowerPath.indexOf("uploadpath");
|
if (uploadPathIndex > -1) {
|
fileUrl = fileUrl.substring(uploadPathIndex).replace(/\\/g, "/");
|
} else {
|
fileUrl = fileUrl.replace(/\\/g, "/");
|
}
|
}
|
fileUrl = fileUrl.replace(/^\/?uploadPath/, "/profile");
|
if (fileUrl.startsWith("upload/")) {
|
fileUrl = "/profile/" + fileUrl;
|
}
|
if (!fileUrl.startsWith("http")) {
|
if (!fileUrl.startsWith("/")) fileUrl = "/" + fileUrl;
|
fileUrl = javaApi + fileUrl;
|
}
|
return fileUrl;
|
};
|
|
const previewProblem = (url) => {
|
window.open(url, "_blank");
|
};
|
|
defineExpose({
|
form,
|
loadForm,
|
resetForm,
|
clearValidate,
|
resetFormAndValidate,
|
formRef,
|
submitFiles,
|
});
|
</script>
|
|
<style lang="scss" scoped>
|
.repair-attachment-upload {
|
width: 100%;
|
:deep(.el-upload) {
|
width: 100%;
|
}
|
:deep(.el-upload-dragger) {
|
width: 100%;
|
padding: 24px 16px;
|
}
|
}
|
.repair-upload-icon {
|
font-size: 48px;
|
color: var(--el-color-primary);
|
margin-bottom: 8px;
|
}
|
|
.problem-existing-wrap {
|
display: flex;
|
flex-wrap: wrap;
|
gap: 10px;
|
margin-bottom: 12px;
|
}
|
|
.problem-thumb {
|
position: relative;
|
width: 88px;
|
height: 88px;
|
border-radius: 6px;
|
overflow: hidden;
|
border: 1px solid var(--el-border-color);
|
}
|
|
.problem-thumb-img {
|
width: 100%;
|
height: 100%;
|
cursor: pointer;
|
}
|
|
.problem-thumb-remove {
|
position: absolute;
|
top: 2px;
|
right: 2px;
|
font-size: 16px;
|
color: #fff;
|
background: rgba(0, 0, 0, 0.45);
|
border-radius: 50%;
|
padding: 2px;
|
cursor: pointer;
|
}
|
</style>
|