From 2d3530a7c11557a40807ad7c0b9e302ce6ce3a9f Mon Sep 17 00:00:00 2001
From: hsy <1029150275@qq.com>
Date: 星期五, 28 八月 2026 10:16:32 +0800
Subject: [PATCH] feat: 综合业务模块页面重构与交互完善
---
src/views/equipmentManagement/ledger/index.vue | 466 ++++++++++++++++++++++++++++++++++++++++++++++++++-------
1 files changed, 406 insertions(+), 60 deletions(-)
diff --git a/src/views/equipmentManagement/ledger/index.vue b/src/views/equipmentManagement/ledger/index.vue
index 9b4f8026..fbc9869 100644
--- a/src/views/equipmentManagement/ledger/index.vue
+++ b/src/views/equipmentManagement/ledger/index.vue
@@ -1,43 +1,39 @@
<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: 240px"
+ style="width: 200px"
placeholder="璇疯緭鍏ヨ澶囧悕绉�"
clearable
- :prefix-icon="Search"
@change="getTableData"
/>
</el-form-item>
<el-form-item label="瑙勬牸鍨嬪彿">
<el-input
v-model="filters.deviceModel"
- style="width: 240px"
+ style="width: 200px"
placeholder="璇疯緭鍏ヨ鏍煎瀷鍙�"
clearable
- :prefix-icon="Search"
@change="getTableData"
/>
</el-form-item>
<el-form-item label="渚涘簲鍟�">
<el-input
v-model="filters.supplierName"
- style="width: 240px"
+ style="width: 200px"
placeholder="璇疯緭鍏ヤ緵搴斿晢"
clearable
- :prefix-icon="Search"
- @change="getTableData"
- />
- </el-form-item>
- <el-form-item label="鍗曚綅">
- <el-input
- v-model="filters.unit"
- style="width: 240px"
- placeholder="璇疯緭鍏ュ崟浣�"
- clearable
- :prefix-icon="Search"
@change="getTableData"
/>
</el-form-item>
@@ -55,6 +51,7 @@
<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"
@@ -82,7 +79,7 @@
</PIMTable>
</div>
<Modal ref="modalRef" @success="getTableData"></Modal>
- <el-dialog v-model="qrDialogVisible" title="浜岀淮鐮�" width="300px">
+ <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;">
@@ -90,19 +87,166 @@
</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>浠呭厑璁稿鍏ls銆亁lsx鏍煎紡鏂囦欢銆�</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 { Search } from "@element-plus/icons-vue";
-import { getLedgerPage, delLedger } from "@/api/equipmentManagement/ledger";
-import { onMounted, getCurrentInstance } from "vue";
+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";
-import { ref } from "vue";
defineOptions({
name: "璁惧鍙拌处",
@@ -116,6 +260,24 @@
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,
@@ -127,81 +289,111 @@
} = usePaginationApi(
getLedgerPage,
{
- searchText: undefined,
+ uniqueCode: undefined,
+ deviceName: undefined,
+ deviceModel: undefined,
+ supplierName: undefined,
+ entryDateStart: undefined,
+ entryDateEnd: undefined,
},
[
{
+ label: "璁惧缂栧彿",
+ prop: "uniqueCode",
+ },
+ {
label: "璁惧鍚嶇О",
- align: "center",
prop: "deviceName",
},
{
label: "瑙勬牸鍨嬪彿",
- align: "center",
prop: "deviceModel",
},
{
+ label: "璁惧鍝佺墝",
+ prop: "deviceBrand",
+ },
+ {
+ label: "璁惧绫诲瀷",
+ prop: "type",
+ },
+ {
label: "渚涘簲鍟�",
- align: "center",
prop: "supplierName",
},
{
- label: "鍗曚綅",
- align: "center",
- prop: "unit",
+ label: "瀛樻斁浣嶇疆",
+ prop: "storageLocation",
},
{
label: "鏁伴噺",
- align: "center",
prop: "number",
},
{
- label: "鍚◣鍗曚环",
- align: "center",
- prop: "taxIncludingPriceUnit",
+ label: "璁惧鐘舵��",
+ prop: "status",
+ width: 100,
+ dataType: "tag",
+ formatData: (v) => v == 1 ? '鍚敤' : '鍋滅敤',
+ formatType: (v) => v == 1 ? 'success' : 'danger',
},
{
- label: "鍚◣鎬讳环",
- align: "center",
- prop: "taxIncludingPriceTotal",
- },
- {
- label: "绋庣巼",
- align: "center",
- prop: "taxRate",
- },
- {
- label: "涓嶅惈绋庢�讳环",
- align: "center",
- prop: "unTaxIncludingPriceTotal",
+ label: "鍚敤鏃ユ湡",
+ prop: "activationDate",
+ width: 120,
+ formatData: (v) => {
+ if (!v) return '';
+ if (v.includes(' ')) return v.split(' ')[0];
+ return v;
+ },
},
{
label: "褰曞叆浜�",
- align: "center",
prop: "createUser",
},
{
- label: "褰曞叆鏃ユ湡",
- align: "center",
- prop: "createTime",
+ 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: 140,
+ width: 180,
operation: [
+ {
+ name: "璇︽儏",
+ clickFun: (row) => {
+ handleDetail(row);
+ },
+ },
+ {
+ name: "鐘舵�佸彉鏇�",
+ clickFun: (row) => {
+ openStatusChange(row);
+ },
+ },
+ {
+ name: "鍙樻洿璁板綍",
+ clickFun: (row) => {
+ openRecordDialog(row);
+ },
+ },
{
name: "缂栬緫",
- type: "text",
clickFun: (row) => {
edit(row.id)
},
},
{
- name: "鐢熸垚浜岀淮鐮�",
- type: "text",
+ name: "浜岀淮鐮�",
clickFun: (row) => {
showQRCode(row)
},
@@ -222,6 +414,109 @@
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;
@@ -272,7 +567,34 @@
const showQRCode = async (row) => {
// 鐩存帴浣跨敤URL锛屼笉瑕佺敤JSON.stringify鍖呰
const qrContent = proxy.javaApi + '/device-info?deviceId=' + row.id;
- qrCodeUrl.value = await QRCode.toDataURL(qrContent);
+ 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;
};
@@ -284,13 +606,37 @@
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(() => {
- filters.entryDate = [
- dayjs().format("YYYY-MM-DD"),
- dayjs().add(1, "day").format("YYYY-MM-DD"),
- ]
- filters.entryDateStart = dayjs().format("YYYY-MM-DD")
- filters.entryDateEnd = dayjs().add(1, "day").format("YYYY-MM-DD")
getTableData();
});
</script>
--
Gitblit v1.9.3