<template>
|
<div class="app-container">
|
<el-form :model="filters" :inline="true">
|
<el-form-item label="设备编号">
|
<el-input
|
v-model="filters.uniqueCode"
|
style="width: 200px"
|
placeholder="请输入设备编号"
|
clearable
|
@change="getTableData"
|
/>
|
</el-form-item>
|
<el-form-item label="设备名称">
|
<el-input
|
v-model="filters.deviceName"
|
style="width: 200px"
|
placeholder="请输入设备名称"
|
clearable
|
@change="getTableData"
|
/>
|
</el-form-item>
|
<el-form-item label="规格型号">
|
<el-input
|
v-model="filters.deviceModel"
|
style="width: 200px"
|
placeholder="请输入规格型号"
|
clearable
|
@change="getTableData"
|
/>
|
</el-form-item>
|
<el-form-item label="供应商">
|
<el-input
|
v-model="filters.supplierName"
|
style="width: 200px"
|
placeholder="请输入供应商"
|
clearable
|
@change="getTableData"
|
/>
|
</el-form-item>
|
<el-form-item label="录入日期:">
|
<el-date-picker v-model="filters.entryDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="daterange"
|
placeholder="请选择" clearable @change="changeDaterange" />
|
</el-form-item>
|
<el-form-item>
|
<el-button type="primary" @click="getTableData">搜索</el-button>
|
<el-button @click="resetFilters">重置</el-button>
|
</el-form-item>
|
</el-form>
|
<div class="table_list">
|
<div class="actions">
|
<div></div>
|
<div>
|
<el-button type="primary" @click="add" icon="Plus"> 新增 </el-button>
|
<el-button type="info" @click="handleImport" icon="Upload">导入</el-button>
|
<el-button @click="handleOut" icon="download">导出</el-button>
|
<el-button
|
type="danger"
|
icon="Delete"
|
:disabled="multipleList.length <= 0"
|
@click="deleteRow(multipleList.map((item) => item.id))"
|
>
|
批量删除
|
</el-button>
|
</div>
|
</div>
|
<PIMTable
|
rowKey="id"
|
isSelection
|
:column="columns"
|
:tableData="dataList"
|
:page="{
|
current: pagination.currentPage,
|
size: pagination.pageSize,
|
total: pagination.total,
|
}"
|
@selection-change="handleSelectionChange"
|
@pagination="changePage"
|
>
|
</PIMTable>
|
</div>
|
<Modal ref="modalRef" @success="getTableData"></Modal>
|
<el-dialog v-model="qrDialogVisible" title="二维码" width="300px" draggable>
|
<div style="text-align:center;">
|
<img :src="qrCodeUrl" alt="二维码" style="width:200px;height:200px;" />
|
<div style="margin:10px 0;">
|
<el-button type="primary" @click="downloadQRCode">下载二维码图片</el-button>
|
</div>
|
</div>
|
</el-dialog>
|
|
<!-- 导入对话框 -->
|
<el-dialog :title="upload.title" v-model="upload.open" width="400px" append-to-body>
|
<el-upload
|
ref="uploadRef"
|
:limit="1"
|
accept=".xlsx, .xls"
|
:headers="upload.headers"
|
:action="upload.url"
|
:disabled="upload.isUploading"
|
:on-progress="handleFileUploadProgress"
|
:on-success="handleFileSuccess"
|
:auto-upload="false"
|
drag
|
>
|
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
|
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
|
<template #tip>
|
<div class="el-upload__tip text-center">
|
<span>仅允许导入xls、xlsx格式文件。</span>
|
<el-link type="primary" :underline="false" style="font-size: 12px; vertical-align: baseline; margin-left: 5px;" @click="importTemplate">下载模板</el-link>
|
</div>
|
</template>
|
</el-upload>
|
<template #footer>
|
<div class="dialog-footer">
|
<el-button type="primary" @click="submitFileForm">确 定</el-button>
|
<el-button @click="upload.open = false">取 消</el-button>
|
</div>
|
</template>
|
</el-dialog>
|
|
<!-- 详情对话框 -->
|
<el-dialog v-model="detailDialogVisible" title="设备台账详情" width="60%" draggable>
|
<el-descriptions :column="2" border>
|
<el-descriptions-item label="设备编号">{{ detailData.uniqueCode }}</el-descriptions-item>
|
<el-descriptions-item label="设备名称">{{ detailData.deviceName }}</el-descriptions-item>
|
<el-descriptions-item label="规格型号">{{ detailData.deviceModel }}</el-descriptions-item>
|
<el-descriptions-item label="设备品牌">{{ detailData.deviceBrand }}</el-descriptions-item>
|
<el-descriptions-item label="设备类型">{{ detailData.type }}</el-descriptions-item>
|
<el-descriptions-item label="供应商">{{ detailData.supplierName }}</el-descriptions-item>
|
<el-descriptions-item label="存放位置">{{ detailData.storageLocation }}</el-descriptions-item>
|
<el-descriptions-item label="单位">{{ detailData.unit }}</el-descriptions-item>
|
<el-descriptions-item label="数量">{{ detailData.number }}</el-descriptions-item>
|
<el-descriptions-item label="启用折旧">{{ detailData.isDepr === 1 ? '是' : '否' }}</el-descriptions-item>
|
<el-descriptions-item label="每年折旧金额">{{ detailData.annualDepreciationAmount }}</el-descriptions-item>
|
<el-descriptions-item label="含税单价">{{ detailData.taxIncludingPriceUnit }}</el-descriptions-item>
|
<el-descriptions-item label="含税总价">{{ detailData.taxIncludingPriceTotal }}</el-descriptions-item>
|
<el-descriptions-item label="税率(%)">{{ detailData.taxRate }}</el-descriptions-item>
|
<el-descriptions-item label="不含税总价">{{ detailData.unTaxIncludingPriceTotal }}</el-descriptions-item>
|
<el-descriptions-item label="录入日期">{{ detailData.createTime }}</el-descriptions-item>
|
<el-descriptions-item label="预计运行时间">{{ detailData.planRuntimeTime ? dayjs(detailData.planRuntimeTime).format('YYYY-MM-DD') : '' }}</el-descriptions-item>
|
<el-descriptions-item label="设备图片" :span="2">
|
<div v-if="detailData.storageBlobVOs && detailData.storageBlobVOs.length > 0" style="display: flex; gap: 10px; flex-wrap: wrap;">
|
<el-image
|
v-for="(file, index) in detailData.storageBlobVOs"
|
:key="index"
|
:src="file.previewURL || file.url"
|
:preview-src-list="detailData.storageBlobVOs.map(u => u.previewURL || u.url)"
|
:initial-index="index"
|
style="width: 100px; height: 100px"
|
fit="cover"
|
/>
|
</div>
|
<span v-else>无图片</span>
|
</el-descriptions-item>
|
</el-descriptions>
|
<template #footer>
|
<el-button @click="detailDialogVisible = false">关闭</el-button>
|
</template>
|
</el-dialog>
|
|
<!-- 状态变更对话框 -->
|
<el-dialog v-model="statusDialog.visible" title="设备状态变更申请" width="500px" draggable>
|
<el-form label-width="100px">
|
<el-form-item label="当前状态">
|
<el-tag :type="statusDialog.row?.status == 1 ? 'success' : 'danger'">
|
{{ statusDialog.row?.status == 1 ? '启用' : '停用' }}
|
</el-tag>
|
</el-form-item>
|
<el-form-item label="目标状态" required>
|
<el-radio-group v-model="statusDialog.targetStatus">
|
<el-radio :label="1">启用</el-radio>
|
<el-radio :label="0">停用</el-radio>
|
</el-radio-group>
|
</el-form-item>
|
<el-form-item label="变更原因" required>
|
<el-input type="textarea" :rows="3" v-model="statusDialog.reason" placeholder="请输入状态变更原因" />
|
</el-form-item>
|
</el-form>
|
<template #footer>
|
<el-button @click="statusDialog.visible = false">取消</el-button>
|
<el-button type="primary" :loading="statusDialog.loading" @click="submitStatusChange">提交审批</el-button>
|
</template>
|
</el-dialog>
|
|
<!-- 状态变更记录对话框 -->
|
<el-dialog v-model="recordDialog.visible" title="设备状态变更记录" width="800px" draggable>
|
<el-table v-loading="recordDialog.loading" :data="recordDialog.list" border>
|
<el-table-column label="变更时间" prop="createTime" width="160" />
|
<el-table-column label="原状态" width="100">
|
<template #default="scope">
|
<el-tag :type="scope.row.originalStatus == 1 ? 'success' : 'danger'">
|
{{ scope.row.originalStatus == 1 ? '启用' : '停用' }}
|
</el-tag>
|
</template>
|
</el-table-column>
|
<el-table-column label="目标状态" width="100">
|
<template #default="scope">
|
<el-tag :type="scope.row.targetStatus == 1 ? 'success' : 'danger'">
|
{{ scope.row.targetStatus == 1 ? '启用' : '停用' }}
|
</el-tag>
|
</template>
|
</el-table-column>
|
<el-table-column label="变更原因" prop="reason" show-overflow-tooltip />
|
<el-table-column label="申请人" prop="createUserName" width="100" />
|
<el-table-column label="审批状态" width="100">
|
<template #default="scope">
|
<el-tag v-if="scope.row.approvalStatus === 'PENDING'" type="warning">审批中</el-tag>
|
<el-tag v-else-if="scope.row.approvalStatus === 'APPROVED'" type="success">已通过</el-tag>
|
<el-tag v-else-if="scope.row.approvalStatus === 'REJECTED'" type="danger">已驳回</el-tag>
|
<span v-else>{{ scope.row.approvalStatus }}</span>
|
</template>
|
</el-table-column>
|
<el-table-column label="驳回原因" prop="rejectReason" show-overflow-tooltip>
|
<template #default="scope">
|
<span v-if="scope.row.approvalStatus === 'REJECTED'">{{ scope.row.rejectReason || '--' }}</span>
|
<span v-else>--</span>
|
</template>
|
</el-table-column>
|
</el-table>
|
<div style="display: flex; justify-content: flex-end; margin-top: 15px;">
|
<el-pagination
|
v-model:current-page="recordDialog.queryParams.pageNum"
|
v-model:page-size="recordDialog.queryParams.pageSize"
|
:total="recordDialog.total"
|
layout="total, prev, pager, next"
|
@current-change="getRecordList"
|
/>
|
</div>
|
<template #footer>
|
<el-button @click="recordDialog.visible = false">关闭</el-button>
|
</template>
|
</el-dialog>
|
</div>
|
</template>
|
|
<script setup>
|
import { usePaginationApi } from "@/hooks/usePaginationApi";
|
import { getLedgerPage, delLedger, getLedgerById } from "@/api/equipmentManagement/ledger";
|
import { listStatusChangeRecord } from "@/api/device/statusChangeRecord";
|
import { saveApprovalInstance } from "@/api/officeProcessAutomation/approvalInstance";
|
import { listApprovalTemplate, getApprovalTemplateDetail, TEMPLATE_TYPE_CUSTOM } from "@/api/officeProcessAutomation/approvalTemplate";
|
import { onMounted, getCurrentInstance, ref, reactive } from "vue";
|
import Modal from "./Modal.vue";
|
import { ElMessageBox, ElMessage } from "element-plus";
|
import { UploadFilled } from "@element-plus/icons-vue";
|
import { getToken } from "@/utils/auth";
|
import dayjs from "dayjs";
|
import QRCode from "qrcode";
|
|
defineOptions({
|
name: "设备台账",
|
});
|
|
// 表格多选框选中项
|
const multipleList = ref([]);
|
const { proxy } = getCurrentInstance();
|
const modalRef = ref();
|
const qrDialogVisible = ref(false);
|
const qrCodeUrl = ref("");
|
const qrRowData = ref(null);
|
|
const detailDialogVisible = ref(false);
|
const detailData = ref({});
|
|
// 导入相关
|
const uploadRef = ref(null)
|
const upload = reactive({
|
// 是否显示弹出层
|
open: false,
|
// 弹出层标题
|
title: "",
|
// 是否禁用上传
|
isUploading: false,
|
// 设置上传的请求头部
|
headers: { Authorization: "Bearer " + getToken() },
|
// 上传的地址
|
url: import.meta.env.VITE_APP_BASE_API + "/device/ledger/import"
|
})
|
|
const {
|
filters,
|
columns,
|
dataList,
|
pagination,
|
getTableData,
|
resetFilters,
|
onCurrentChange,
|
} = usePaginationApi(
|
getLedgerPage,
|
{
|
uniqueCode: undefined,
|
deviceName: undefined,
|
deviceModel: undefined,
|
supplierName: undefined,
|
entryDateStart: undefined,
|
entryDateEnd: undefined,
|
},
|
[
|
{
|
label: "设备编号",
|
prop: "uniqueCode",
|
},
|
{
|
label: "设备名称",
|
prop: "deviceName",
|
},
|
{
|
label: "规格型号",
|
prop: "deviceModel",
|
},
|
{
|
label: "设备品牌",
|
prop: "deviceBrand",
|
},
|
{
|
label: "设备类型",
|
prop: "type",
|
},
|
{
|
label: "供应商",
|
prop: "supplierName",
|
},
|
{
|
label: "存放位置",
|
prop: "storageLocation",
|
},
|
{
|
label: "数量",
|
prop: "number",
|
},
|
{
|
label: "设备状态",
|
prop: "status",
|
width: 100,
|
dataType: "tag",
|
formatData: (v) => v == 1 ? '启用' : '停用',
|
formatType: (v) => v == 1 ? 'success' : 'danger',
|
},
|
{
|
label: "启用日期",
|
prop: "activationDate",
|
width: 120,
|
formatData: (v) => {
|
if (!v) return '';
|
if (v.includes(' ')) return v.split(' ')[0];
|
return v;
|
},
|
},
|
{
|
label: "录入人",
|
prop: "createUser",
|
},
|
{
|
label: "建档日期",
|
prop: "filingDate",
|
width: 120,
|
formatData: (v) => {
|
if (!v) return '';
|
if (v.includes(' ')) return v.split(' ')[0];
|
return v;
|
},
|
},
|
{
|
dataType: "action",
|
label: "操作",
|
align: "center",
|
fixed: 'right',
|
width: 180,
|
operation: [
|
{
|
name: "详情",
|
clickFun: (row) => {
|
handleDetail(row);
|
},
|
},
|
{
|
name: "状态变更",
|
clickFun: (row) => {
|
openStatusChange(row);
|
},
|
},
|
{
|
name: "变更记录",
|
clickFun: (row) => {
|
openRecordDialog(row);
|
},
|
},
|
{
|
name: "编辑",
|
clickFun: (row) => {
|
edit(row.id)
|
},
|
},
|
{
|
name: "二维码",
|
clickFun: (row) => {
|
showQRCode(row)
|
},
|
},
|
],
|
},
|
]
|
);
|
|
// 多选后做什么
|
const handleSelectionChange = (selectionList) => {
|
multipleList.value = selectionList;
|
};
|
|
const add = () => {
|
modalRef.value.openModal();
|
};
|
const edit = (id) => {
|
modalRef.value.loadForm(id);
|
};
|
const handleDetail = async (row) => {
|
const { code, data } = await getLedgerById(row.id);
|
if (code == 200) {
|
detailData.value = data;
|
detailDialogVisible.value = true;
|
}
|
};
|
|
const statusDialog = reactive({ visible: false, row: null, targetStatus: 1, reason: '', loading: false });
|
|
const openStatusChange = (row) => {
|
statusDialog.row = row;
|
statusDialog.targetStatus = row.status == 1 ? 0 : 1;
|
statusDialog.reason = '';
|
statusDialog.visible = true;
|
};
|
|
const submitStatusChange = async () => {
|
if (!statusDialog.reason.trim()) {
|
ElMessage.error("请填写状态变更原因");
|
return;
|
}
|
if (statusDialog.targetStatus === statusDialog.row.status) {
|
ElMessage.warning("目标状态与当前状态一致");
|
return;
|
}
|
statusDialog.loading = true;
|
try {
|
const listRes = await listApprovalTemplate(TEMPLATE_TYPE_CUSTOM);
|
const templates = listRes.data?.records || listRes.data || [];
|
const tpl = templates.find(t => t.businessType === 19 && (t.enabled === 1 || t.enabled === true));
|
if (!tpl) {
|
ElMessage.error("未找到启用的设备状态变更审批模板");
|
return;
|
}
|
const detailRes = await getApprovalTemplateDetail(tpl.id);
|
const detail = detailRes.data;
|
|
const payload = {
|
templateId: detail.id,
|
templateName: detail.templateName,
|
businessType: 19,
|
templateSnapshot: JSON.stringify(detail),
|
title: `设备状态变更审批 - ${statusDialog.row.deviceName}`,
|
reason: statusDialog.reason,
|
businessId: statusDialog.row.id,
|
formConfig: JSON.stringify({
|
targetStatus: statusDialog.targetStatus,
|
targetStatusName: statusDialog.targetStatus == 1 ? '启用' : '停用',
|
deviceId: statusDialog.row.id,
|
deviceName: statusDialog.row.deviceName,
|
reason: statusDialog.reason
|
}),
|
flowNodes: detail.flowNodes || []
|
};
|
|
const res = await saveApprovalInstance(payload);
|
if (res.code === 200) {
|
ElMessage.success("已发起状态变更审批");
|
statusDialog.visible = false;
|
getTableData();
|
}
|
} catch (error) {
|
console.error(error);
|
} finally {
|
statusDialog.loading = false;
|
}
|
};
|
|
const recordDialog = reactive({
|
visible: false,
|
loading: false,
|
list: [],
|
total: 0,
|
queryParams: {
|
pageNum: 1,
|
pageSize: 10,
|
deviceId: null
|
}
|
});
|
|
const openRecordDialog = (row) => {
|
recordDialog.queryParams.deviceId = row.id;
|
recordDialog.queryParams.pageNum = 1;
|
recordDialog.visible = true;
|
getRecordList();
|
};
|
|
const getRecordList = async () => {
|
recordDialog.loading = true;
|
try {
|
const res = await listStatusChangeRecord(recordDialog.queryParams);
|
if (res.code === 200) {
|
recordDialog.list = res.data.records;
|
recordDialog.total = res.data.total;
|
}
|
} catch (error) {
|
console.error(error);
|
} finally {
|
recordDialog.loading = false;
|
}
|
};
|
|
const changePage = ({ page, limit }) => {
|
pagination.currentPage = page;
|
pagination.pageSize = limit;
|
onCurrentChange(page);
|
};
|
const deleteRow = (id) => {
|
ElMessageBox.confirm("此操作将永久删除该文件, 是否继续?", "提示", {
|
confirmButtonText: "确定",
|
cancelButtonText: "取消",
|
type: "warning",
|
}).then(async () => {
|
const { code } = await delLedger(id);
|
if (code == 200) {
|
ElMessage({
|
type: "success",
|
message: "删除成功",
|
});
|
getTableData();
|
}
|
});
|
};
|
|
const changeDaterange = (value) => {
|
if (value) {
|
filters.entryDateStart = dayjs(value[0]).format("YYYY-MM-DD");
|
filters.entryDateEnd = dayjs(value[1]).format("YYYY-MM-DD");
|
} else {
|
filters.entryDateStart = undefined;
|
filters.entryDateEnd = undefined;
|
}
|
getTableData();
|
};
|
|
const handleOut = () => {
|
ElMessageBox.confirm("选中的内容将被导出,是否确认导出?", "导出", {
|
confirmButtonText: "确认",
|
cancelButtonText: "取消",
|
type: "warning",
|
})
|
.then(() => {
|
proxy.download(`/device/ledger/export`, {}, "设备台账档案.xlsx");
|
})
|
.catch(() => {
|
proxy.$modal.msg("已取消");
|
});
|
};
|
|
const showQRCode = async (row) => {
|
// 直接使用URL,不要用JSON.stringify包装
|
const qrContent = proxy.javaApi + '/device-info?deviceId=' + row.id;
|
const qrDataUrl = await QRCode.toDataURL(qrContent, { width: 200, margin: 2 });
|
|
// 创建canvas合成带名称的二维码图片
|
const canvas = document.createElement('canvas');
|
const ctx = canvas.getContext('2d');
|
const qrSize = 200;
|
const textHeight = 30;
|
const padding = 10;
|
canvas.width = qrSize + padding * 2;
|
canvas.height = qrSize + textHeight + padding * 2;
|
|
// 填充白色背景
|
ctx.fillStyle = '#ffffff';
|
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
// 绘制二维码
|
const qrImg = new Image();
|
qrImg.src = qrDataUrl;
|
await new Promise((resolve) => { qrImg.onload = resolve; });
|
ctx.drawImage(qrImg, padding, padding, qrSize, qrSize);
|
|
// 绘制设备名称
|
ctx.fillStyle = '#333333';
|
ctx.font = 'bold 14px Arial';
|
ctx.textAlign = 'center';
|
ctx.fillText(row.deviceName || '', canvas.width / 2, qrSize + padding + 20);
|
|
qrCodeUrl.value = canvas.toDataURL('image/png');
|
qrRowData.value = row;
|
qrDialogVisible.value = true;
|
};
|
|
const downloadQRCode = () => {
|
const a = document.createElement("a");
|
a.href = qrCodeUrl.value;
|
a.download = `${qrRowData.value.deviceName || "二维码"}.png`;
|
a.click();
|
};
|
|
// 导入按钮操作
|
const handleImport = () => {
|
upload.title = "设备台账导入"
|
upload.open = true
|
}
|
|
// 下载模板操作
|
const importTemplate = () => {
|
proxy.download("/device/ledger/downloadTemplate", {}, `设备台账导入模板_${new Date().getTime()}.xlsx`)
|
}
|
|
// 文件上传中处理
|
const handleFileUploadProgress = (event, file, fileList) => {
|
upload.isUploading = true
|
}
|
|
// 文件上传成功处理
|
const handleFileSuccess = (response, file, fileList) => {
|
upload.open = false
|
upload.isUploading = false
|
proxy.$refs["uploadRef"].handleRemove(file)
|
proxy.$alert("<div style='overflow: auto;overflow-x: hidden;max-height: 70vh;padding: 10px 20px 0;'>" + response.msg + "</div>", "导入结果", { dangerouslyUseHTMLString: true })
|
getTableData()
|
}
|
|
// 提交上传文件
|
const submitFileForm = () => {
|
proxy.$refs["uploadRef"].submit()
|
}
|
|
onMounted(() => {
|
getTableData();
|
});
|
</script>
|
|
<style lang="scss" scoped>
|
.table_list {
|
margin-top: unset;
|
}
|
.actions {
|
display: flex;
|
justify-content: space-between;
|
margin-bottom: 10px;
|
}
|
</style>
|