From 9646ba4b026e4dbee0c0d5ac3d52b428bfef7b29 Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期五, 28 八月 2026 10:49:44 +0800
Subject: [PATCH] 新疆-新聚 1.销售台账中的销售合同按框架合同处理 2.设备台账需按车间/区域汇总设备,车间/位置可自行新增,且支持子父级递归 3.计量器具台账支持通过 Excel 文件批量新增数据 4.设备保养定时任务新增“年度保养”任务频率
---
src/views/equipmentManagement/upkeep/index.vue | 47 +
src/api/salesManagement/salesLedger.js | 29 +
src/views/equipmentManagement/measurementEquipment/index.vue | 65 ++
src/views/procurementManagement/procurementReport/index.vue | 2
src/views/equipmentManagement/inspectionManagement/components/formDia.vue | 179 ++++++-
src/views/equipmentManagement/upkeep/Form/formDia.vue | 47 ++
src/views/salesManagement/salesLedger/index.vue | 284 +++++++++++
src/views/equipmentManagement/ledger/index.vue | 646 +++++++++++++++++++++------
src/api/equipmentManagement/ledger.js | 3
src/views/equipmentManagement/ledger/Form.vue | 26 +
10 files changed, 1,105 insertions(+), 223 deletions(-)
diff --git a/src/api/equipmentManagement/ledger.js b/src/api/equipmentManagement/ledger.js
index d1b65b0..ccd2c95 100644
--- a/src/api/equipmentManagement/ledger.js
+++ b/src/api/equipmentManagement/ledger.js
@@ -36,9 +36,10 @@
});
};
-export const getDeviceLedger = () => {
+export const getDeviceLedger = (params) => {
return request({
url: "/device/ledger/getDeviceLedger",
method: "get",
+ params,
});
};
diff --git a/src/api/salesManagement/salesLedger.js b/src/api/salesManagement/salesLedger.js
index 6548927..b9ef283 100644
--- a/src/api/salesManagement/salesLedger.js
+++ b/src/api/salesManagement/salesLedger.js
@@ -116,4 +116,31 @@
method: "get",
params: query,
});
-}
\ No newline at end of file
+}
+
+// 鍒涘缓妗嗘灦璁㈠崟
+export function createFrameworkOrder(query) {
+ return request({
+ url: "/sales/ledger/createFrameworkOrder",
+ method: "post",
+ data: query,
+ });
+}
+
+// 鏌ヨ妗嗘灦璁㈠崟鍒楄〃
+export function listFrameworkOrders(query) {
+ return request({
+ url: "/sales/ledger/listFrameworkOrders",
+ method: "get",
+ params: query,
+ });
+}
+
+// 鏌ヨ妗嗘灦鍚堝悓绱鍙戣揣鏁伴噺
+export function frameworkContractShippedQuantity(query) {
+ return request({
+ url: "/sales/ledger/frameworkContractShippedQuantity",
+ method: "get",
+ params: query,
+ });
+}
diff --git a/src/views/equipmentManagement/inspectionManagement/components/formDia.vue b/src/views/equipmentManagement/inspectionManagement/components/formDia.vue
index 74cbb58..e1a0b22 100644
--- a/src/views/equipmentManagement/inspectionManagement/components/formDia.vue
+++ b/src/views/equipmentManagement/inspectionManagement/components/formDia.vue
@@ -5,15 +5,11 @@
<el-form ref="formRef" :model="form" :rules="rules" label-width="120px">
<el-row>
<el-col :span="12">
- <el-form-item label="璁惧鍚嶇О" prop="taskId">
- <el-select v-model="form.taskId" @change="setDeviceModel">
- <el-option
- v-for="(item, index) in deviceOptions"
- :key="index"
- :label="item.deviceName"
- :value="item.id"
- ></el-option>
- </el-select>
+ <el-form-item label="璁惧鍚嶇О" prop="taskName">
+ <div style="display: flex; gap: 8px; width: 100%">
+ <el-input v-model="form.taskName" placeholder="璇烽�夋嫨璁惧" readonly style="flex: 1" />
+ <el-button @click="openDeviceDialog">閫夋嫨璁惧</el-button>
+ </div>
</el-form-item>
</el-col>
<el-col :span="12">
@@ -111,6 +107,50 @@
</div>
</template>
</el-dialog>
+
+ <!-- 閫夋嫨宸℃璁惧寮圭獥 -->
+ <el-dialog v-model="deviceDialogVisible" title="閫夋嫨宸℃璁惧" width="760px" draggable append-to-body>
+ <div class="device-select-wrap">
+ <div class="device-select-tree">
+ <div class="all-device-node"
+ :class="{ active: deviceAreaId === null }"
+ @click="selectAllDevice">
+ 鍏ㄩ儴璁惧
+ </div>
+ <el-tree
+ ref="deviceAreaTreeRef"
+ v-loading="deviceAreaLoading"
+ :data="areaTree"
+ node-key="id"
+ :props="{ label: 'label', children: 'children' }"
+ default-expand-all
+ :expand-on-click-node="false"
+ highlight-current
+ :current-node-key="deviceAreaId"
+ @node-click="onDeviceAreaClick"
+ />
+ </div>
+ <div class="device-select-list">
+ <el-table
+ :data="deviceList"
+ v-loading="deviceListLoading"
+ border
+ height="420"
+ style="cursor: pointer"
+ @row-click="selectDevice"
+ >
+ <el-table-column prop="deviceName" label="璁惧鍚嶇О" min-width="140" />
+ <el-table-column prop="deviceModel" label="瑙勬牸鍨嬪彿" min-width="120" />
+ <el-table-column prop="areaName" label="鎵�灞炲尯鍩�" min-width="120" />
+ <el-table-column label="鎿嶄綔" width="80" align="center">
+ <template #default="scope">
+ <el-button link type="primary" @click.stop="selectDevice(scope.row)">閫夋嫨</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ </div>
+ </div>
+ </el-dialog>
</div>
</template>
@@ -120,6 +160,7 @@
import {addOrEditTimingTask} from "@/api/inspectionManagement/index.js";
import {userListNoPageByTenantId} from "@/api/system/user.js";
import { getDeviceLedger } from "@/api/equipmentManagement/ledger";
+import { getDeviceAreaTree } from "@/api/equipmentManagement/area";
const { proxy } = getCurrentInstance()
const emit = defineEmits()
@@ -127,6 +168,16 @@
const dialogVisitable = ref(false);
const operationType = ref('add');
const deviceOptions = ref([]);
+
+// 璁惧閫夋嫨寮圭獥鐩稿叧
+const deviceDialogVisible = ref(false);
+const areaTree = ref([]);
+const deviceAreaId = ref(null);
+const deviceAreaLoading = ref(false);
+const deviceAreaTreeRef = ref();
+const deviceList = ref([]);
+const deviceListLoading = ref(false);
+
const data = reactive({
form: {
taskId: undefined,
@@ -142,7 +193,7 @@
time: ''
},
rules: {
- taskId: [{ required: true, message: "璇烽�夋嫨璁惧", trigger: "change" },],
+ taskName: [{ required: true, message: "璇烽�夋嫨璁惧", trigger: "change" },],
inspector: [{ required: true, message: "璇疯緭鍏ュ贰妫�浜�", trigger: "blur" },],
dateStr: [{ required: true, message: "璇烽�夋嫨鐧昏鏃堕棿", trigger: "change" }],
frequencyType: [{ required: true, message: "璇烽�夋嫨浠诲姟棰戠巼", trigger: "change" }],
@@ -207,7 +258,7 @@
const loadDeviceName = async () => {
const { data } = await getDeviceLedger();
- deviceOptions.value = data;
+ deviceOptions.value = data || [];
};
const setDeviceModel = (id) => {
@@ -217,29 +268,82 @@
}
}
+// 鍔犺浇鍖哄煙鏍�
+const loadDeviceAreaTree = () => {
+ deviceAreaLoading.value = true;
+ getDeviceAreaTree()
+ .then((res) => {
+ areaTree.value = res.data || [];
+ })
+ .finally(() => {
+ deviceAreaLoading.value = false;
+ });
+};
+
+// 鍔犺浇璁惧鍒楄〃
+const loadDevices = (areaId) => {
+ deviceListLoading.value = true;
+ getDeviceLedger(areaId ? { areaId } : undefined)
+ .then((res) => {
+ deviceList.value = res.data || [];
+ })
+ .finally(() => {
+ deviceListLoading.value = false;
+ });
+};
+
+// 鎵撳紑璁惧閫夋嫨寮圭獥
+const openDeviceDialog = () => {
+ deviceDialogVisible.value = true;
+ deviceAreaId.value = null;
+ loadDeviceAreaTree();
+ loadDevices();
+};
+
+// 閫夋嫨鍏ㄩ儴璁惧
+const selectAllDevice = () => {
+ deviceAreaId.value = null;
+ loadDevices();
+};
+
+// 鐐瑰嚮鍖哄煙鑺傜偣
+const onDeviceAreaClick = (data) => {
+ deviceAreaId.value = data.id;
+ loadDevices(data.id);
+};
+
+// 閫変腑璁惧鍥炲~
+const selectDevice = (row) => {
+ form.value.taskId = row.id;
+ form.value.taskName = row.deviceName;
+ deviceDialogVisible.value = false;
+};
+
// 鎵撳紑寮规
const openDialog = async (type, row) => {
dialogVisitable.value = true
operationType.value = type
-
+
// 閲嶇疆琛ㄥ崟
resetForm();
-
+
// 鍔犺浇鐢ㄦ埛鍒楄〃
userListNoPageByTenantId().then((res) => {
userList.value = res.data;
});
-
- // 鍔犺浇璁惧鍒楄〃
+
+ // 鍔犺浇璁惧鍒楄〃锛堢紪杈戝洖鏄剧敤锛�
await loadDeviceName();
-
+
if (type === 'edit' && row) {
form.value = {...row}
form.value.inspector = form.value.inspectorIds.split(',').map(Number)
-
+
// 濡傛灉鏈夎澶嘔D锛岃嚜鍔ㄨ缃澶囦俊鎭�
if (form.value.taskId) {
- setDeviceModel(form.value.taskId);
+ if (!form.value.taskName) {
+ setDeviceModel(form.value.taskId);
+ }
}
}
}
@@ -279,16 +383,16 @@
try {
form.value.inspectorIds = form.value.inspector.join(',')
delete form.value.inspector
-
+
if (form.value.frequencyType === 'WEEKLY') {
let frequencyDetail = ''
frequencyDetail = form.value.week + ',' + form.value.time
form.value.frequencyDetail = frequencyDetail
}
-
+
let res = await userStore.getInfo()
form.value.registrantId = res.user.userId
-
+
await addOrEditTimingTask(form.value)
cancel()
proxy.$modal.msgSuccess('鎻愪氦鎴愬姛')
@@ -302,5 +406,34 @@
</script>
<style scoped>
-
-</style>
\ No newline at end of file
+.device-select-wrap {
+ display: flex;
+}
+.device-select-tree {
+ width: 260px;
+ flex-shrink: 0;
+ padding-right: 12px;
+ border-right: 1px solid #ebeef5;
+ max-height: 460px;
+ overflow-y: auto;
+}
+.device-select-list {
+ flex: 1;
+ min-width: 0;
+ padding-left: 12px;
+}
+.all-device-node {
+ padding: 8px 12px;
+ cursor: pointer;
+ font-size: 14px;
+ border-radius: 4px;
+ margin-bottom: 8px;
+}
+.all-device-node:hover {
+ background: #f5f7fa;
+}
+.all-device-node.active {
+ background: #ecf5ff;
+ color: #409eff;
+}
+</style>
diff --git a/src/views/equipmentManagement/ledger/Form.vue b/src/views/equipmentManagement/ledger/Form.vue
index f1d21f2..46d5c98 100644
--- a/src/views/equipmentManagement/ledger/Form.vue
+++ b/src/views/equipmentManagement/ledger/Form.vue
@@ -38,6 +38,20 @@
</el-form-item>
</el-col>
<el-col :span="12">
+ <el-form-item label="鎵�灞炲尯鍩�" prop="areaId">
+ <el-tree-select
+ v-model="form.areaId"
+ :data="areaTree"
+ node-key="id"
+ :props="{ label: 'label', children: 'children' }"
+ check-strictly
+ clearable
+ placeholder="璇烽�夋嫨鎵�灞炲尯鍩�"
+ style="width: 100%"
+ />
+ </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>
@@ -174,6 +188,7 @@
import useFormData from "@/hooks/useFormData";
// import useUserStore from "@/store/modules/user";
import { getLedgerById } from "@/api/equipmentManagement/ledger";
+import { getDeviceAreaTree } from "@/api/equipmentManagement/area";
import dayjs from "dayjs";
import {
calculateTaxIncludeTotalPrice,
@@ -190,6 +205,7 @@
name: "璁惧鍙拌处琛ㄥ崟",
});
const formRef = ref(null);
+const areaTree = ref([]);
const operationType = ref('');
// 璁惧绫诲瀷鍥哄畾閫夐」
const deviceTypeOptions = ref([
@@ -228,6 +244,7 @@
deviceModel: undefined, // 瑙勬牸鍨嬪彿
deviceBrand: undefined, // 璁惧鍝佺墝
type: undefined, // 璁惧绫诲瀷
+ areaId: undefined, // 鎵�灞炲尯鍩�
supplierName: undefined, // 渚涘簲鍟�
storageLocation: undefined, // 瀛樻斁浣嶇疆
isDepr: 2, // 鏄惁鍚敤鎶樻棫 1-鏄� 2-鍚�
@@ -255,7 +272,15 @@
}
});
+const loadAreaTree = () => {
+ getDeviceAreaTree().then((res) => {
+ areaTree.value = res.data || [];
+ });
+};
+loadAreaTree();
+
const loadForm = async (id) => {
+ loadAreaTree();
if (id) {
operationType.value = 'edit'
}
@@ -266,6 +291,7 @@
form.deviceBrand = data.deviceBrand;
form.type = data.type;
form.supplierName = data.supplierName;
+ form.areaId = data.areaId;
form.storageLocation = data.storageLocation;
form.isDepr = data.isDepr;
form.annualDepreciationAmount = data.annualDepreciationAmount;
diff --git a/src/views/equipmentManagement/ledger/index.vue b/src/views/equipmentManagement/ledger/index.vue
index af10532..95baaca 100644
--- a/src/views/equipmentManagement/ledger/index.vue
+++ b/src/views/equipmentManagement/ledger/index.vue
@@ -1,84 +1,160 @@
<template>
<div class="app-container">
- <el-form :model="filters" :inline="true">
- <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))"
- >
- 鎵归噺鍒犻櫎
+ <div class="area-view">
+ <!-- 宸︿晶鍖哄煙鏍� -->
+ <div class="area-left">
+ <div class="area-left-header">
+ <el-button type="primary" @click="openAreaDialog('add')" icon="Plus">
+ 鏂板鍖哄煙
</el-button>
</div>
+ <div
+ class="all-device-node"
+ :class="{ active: currentAreaId === null }"
+ @click="handleAllDeviceClick"
+ >
+ 鍏ㄩ儴璁惧
+ </div>
+ <el-tree
+ ref="areaTreeRef"
+ v-loading="areaTreeLoading"
+ :data="areaTree"
+ node-key="id"
+ :props="{ label: 'label', children: 'children' }"
+ :expand-on-click-node="false"
+ default-expand-all
+ draggable
+ highlight-current
+ :current-node-key="currentAreaId"
+ @node-click="handleAreaNodeClick"
+ @node-drop="handleAreaNodeDrop"
+ >
+ <template #default="{ node, data }">
+ <div class="area-tree-node">
+ <span class="area-node-label">{{ data.label }}</span>
+ <span class="area-node-count">{{ data.deviceCount ?? 0 }}</span>
+ <span class="area-node-actions">
+ <el-button
+ link
+ type="primary"
+ size="small"
+ @click.stop="openAreaDialog('addChild', data)"
+ >
+ 瀛愮骇
+ </el-button>
+ <el-button
+ link
+ type="primary"
+ size="small"
+ @click.stop="openAreaDialog('edit', data)"
+ >
+ 缂栬緫
+ </el-button>
+ <el-button
+ link
+ type="danger"
+ size="small"
+ @click.stop="handleAreaDelete(node, data)"
+ >
+ 鍒犻櫎
+ </el-button>
+ </span>
+ </div>
+ </template>
+ </el-tree>
</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 class="area-right">
+ <el-form :model="filters" :inline="true">
+ <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="handleReset">閲嶇疆</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>
+ </div>
</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;">
+ <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
@@ -98,7 +174,14 @@
<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>
+ <el-link
+ type="primary"
+ :underline="false"
+ style="font-size: 12px; vertical-align: baseline; margin-left: 5px"
+ @click="importTemplate"
+ >
+ 涓嬭浇妯℃澘
+ </el-link>
</div>
</template>
</el-upload>
@@ -118,6 +201,7 @@
<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.areaName }}</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>
@@ -128,17 +212,22 @@
<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="棰勮杩愯鏃堕棿">{{
+ 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)"
+ <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"
+ style="width: 100px; height: 100px"
+ fit="cover"
/>
</div>
<span v-else>鏃犲浘鐗�</span>
@@ -148,14 +237,57 @@
<el-button @click="detailDialogVisible = false">鍏抽棴</el-button>
</template>
</el-dialog>
+
+ <!-- 鍖哄煙鏂板/缂栬緫寮圭獥 -->
+ <el-dialog
+ v-model="areaDialogVisible"
+ :title="areaDialogTitle"
+ width="420px"
+ draggable
+ @closed="areaFormRef && areaFormRef.resetFields()"
+ >
+ <el-form :model="areaForm" :rules="areaRules" ref="areaFormRef" label-width="90px">
+ <el-form-item label="涓婄骇鍖哄煙" prop="parentId">
+ <el-tree-select
+ v-model="areaForm.parentId"
+ :data="areaTree"
+ node-key="id"
+ :props="{ label: 'label', children: 'children' }"
+ check-strictly
+ clearable
+ placeholder="涓嶉�夊垯涓烘牴鍖哄煙"
+ style="width: 100%"
+ />
+ </el-form-item>
+ <el-form-item label="鍖哄煙鍚嶇О" prop="name">
+ <el-input v-model="areaForm.name" placeholder="璇疯緭鍏ュ尯鍩�/杞﹂棿/浣嶇疆鍚嶇О" clearable />
+ </el-form-item>
+ <el-form-item label="鎺掑簭" prop="sort">
+ <el-input-number v-model="areaForm.sort" :min="0" style="width: 100%" placeholder="鍙��" />
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <el-button type="primary" @click="submitAreaForm">纭畾</el-button>
+ <el-button @click="areaDialogVisible = 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, getLedgerById } from "@/api/equipmentManagement/ledger";
-import { onMounted, getCurrentInstance, ref, reactive } from "vue";
+import {
+ getLedgerPage,
+ delLedger,
+ getLedgerById,
+} from "@/api/equipmentManagement/ledger";
+import {
+ getDeviceAreaTree,
+ addDeviceArea,
+ editDeviceArea,
+ delDeviceArea,
+} from "@/api/equipmentManagement/area";
+import { onMounted, getCurrentInstance, ref, reactive, computed } from "vue";
import Modal from "./Modal.vue";
import { ElMessageBox, ElMessage } from "element-plus";
import { UploadFilled } from "@element-plus/icons-vue";
@@ -178,20 +310,38 @@
const detailDialogVisible = ref(false);
const detailData = ref({});
+// 鍖哄煙鐩稿叧
+const areaTree = ref([]);
+const areaTreeLoading = ref(false);
+const areaTreeRef = ref();
+const currentAreaId = ref(null);
+const areaDialogVisible = ref(false);
+const areaDialogType = ref("add");
+const areaFormRef = ref();
+const areaForm = reactive({
+ id: undefined,
+ name: "",
+ parentId: null,
+ sort: 0,
+});
+const areaRules = {
+ name: [{ required: true, message: "璇疯緭鍏ュ尯鍩熷悕绉�", trigger: "blur" }],
+};
+const areaDialogTitle = computed(() => {
+ if (areaDialogType.value === "edit") return "缂栬緫鍖哄煙";
+ if (areaDialogType.value === "addChild") return "鏂板瀛愬尯鍩�";
+ return "鏂板鍖哄煙";
+});
+
// 瀵煎叆鐩稿叧
-const uploadRef = ref(null)
+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"
-})
+ url: import.meta.env.VITE_APP_BASE_API + "/device/ledger/import",
+});
const {
filters,
@@ -209,6 +359,7 @@
supplierName: undefined,
entryDateStart: undefined,
entryDateEnd: undefined,
+ areaId: undefined,
},
[
{
@@ -226,6 +377,10 @@
{
label: "璁惧绫诲瀷",
prop: "type",
+ },
+ {
+ label: "鎵�灞炲尯鍩�",
+ prop: "areaName",
},
{
label: "渚涘簲鍟�",
@@ -247,43 +402,167 @@
label: "褰曞叆鏃ユ湡",
prop: "createTime",
formatData: (v) => {
- if (!v) return '';
- // 濡傛灉鍖呭惈鏃跺垎绉掞紝鍙彇鏃ユ湡閮ㄥ垎
- if (v.includes(' ')) {
- return v.split(' ')[0];
+ if (!v) return "";
+ if (v.includes(" ")) {
+ return v.split(" ")[0];
}
return v;
},
},
- {
- dataType: "action",
- label: "鎿嶄綔",
- align: "center",
- fixed: 'right',
- width: 180,
- operation: [
+ {
+ dataType: "action",
+ label: "鎿嶄綔",
+ align: "center",
+ fixed: "right",
+ width: 180,
+ operation: [
{
name: "璇︽儏",
clickFun: (row) => {
handleDetail(row);
},
},
- {
- name: "缂栬緫",
- clickFun: (row) => {
- edit(row.id)
- },
- },
- {
- name: "浜岀淮鐮�",
- clickFun: (row) => {
- showQRCode(row)
- },
- },
- ],
- },
+ {
+ name: "缂栬緫",
+ clickFun: (row) => {
+ edit(row.id);
+ },
+ },
+ {
+ name: "浜岀淮鐮�",
+ clickFun: (row) => {
+ showQRCode(row);
+ },
+ },
+ ],
+ },
]
);
+
+// 鍔犺浇鍖哄煙鏍�
+const getAreaTree = () => {
+ areaTreeLoading.value = true;
+ getDeviceAreaTree()
+ .then((res) => {
+ areaTree.value = res.data || [];
+ })
+ .finally(() => {
+ areaTreeLoading.value = false;
+ });
+};
+
+// 閲嶇疆鎼滅储涓庡尯鍩熺瓫閫�
+const handleReset = () => {
+ currentAreaId.value = null;
+ resetFilters();
+};
+
+// 鐐瑰嚮"鍏ㄩ儴璁惧"
+const handleAllDeviceClick = () => {
+ currentAreaId.value = null;
+ filters.areaId = undefined;
+ getTableData();
+};
+
+// 鐐瑰嚮鍖哄煙鑺傜偣
+const handleAreaNodeClick = (data) => {
+ currentAreaId.value = data.id;
+ filters.areaId = data.id;
+ getTableData();
+};
+
+// 鎷栨嫿璋冩暣鐖跺瓙鍏崇郴
+const handleAreaNodeDrop = (draggingNode, dropNode, dropType) => {
+ const node = draggingNode.data;
+ let parentId = null;
+ if (dropNode && dropType === "inner") {
+ parentId = dropNode.data.id;
+ } else if (dropNode) {
+ parentId = dropNode.data.parentId ?? null;
+ } else {
+ parentId = null;
+ }
+ if (parentId === node.id) {
+ proxy.$modal.msgWarning("涓嶈兘灏嗗尯鍩熸寕杞藉埌鑷韩");
+ getAreaTree();
+ return;
+ }
+ editDeviceArea({
+ id: node.id,
+ name: node.label,
+ parentId,
+ sort: node.sort,
+ })
+ .then(() => {
+ proxy.$modal.msgSuccess("璋冩暣鎴愬姛");
+ getAreaTree();
+ })
+ .catch(() => {
+ getAreaTree();
+ });
+};
+
+// 鎵撳紑鍖哄煙寮圭獥
+const openAreaDialog = (type, data) => {
+ areaDialogType.value = type;
+ areaForm.id = undefined;
+ areaForm.name = "";
+ areaForm.parentId = null;
+ areaForm.sort = 0;
+ if (type === "addChild" && data) {
+ areaForm.parentId = data.id;
+ } else if (type === "edit" && data) {
+ areaForm.id = data.id;
+ areaForm.name = data.label;
+ areaForm.parentId = data.parentId ?? null;
+ areaForm.sort = data.sort ?? 0;
+ }
+ areaDialogVisible.value = true;
+};
+
+// 鎻愪氦鍖哄煙琛ㄥ崟
+const submitAreaForm = () => {
+ if (!areaFormRef.value) return;
+ areaFormRef.value.validate((valid) => {
+ if (!valid) return;
+ const payload = {
+ name: areaForm.name,
+ parentId: areaForm.parentId ?? null,
+ sort: areaForm.sort ?? 0,
+ };
+ const api =
+ areaDialogType.value === "edit"
+ ? editDeviceArea({ ...payload, id: areaForm.id })
+ : addDeviceArea(payload);
+ api
+ .then(() => {
+ proxy.$modal.msgSuccess("鎿嶄綔鎴愬姛");
+ areaDialogVisible.value = false;
+ getAreaTree();
+ })
+ .catch(() => {});
+ });
+};
+
+// 鍒犻櫎鍖哄煙
+const handleAreaDelete = (node, data) => {
+ ElMessageBox.confirm("纭鍒犻櫎璇ュ尯鍩熷悧锛�", "鎻愮ず", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(() => delDeviceArea(data.id))
+ .then(() => {
+ proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+ if (currentAreaId.value === data.id) {
+ currentAreaId.value = null;
+ filters.areaId = undefined;
+ }
+ getAreaTree();
+ getTableData();
+ })
+ .catch(() => {});
+};
// 澶氶�夊悗鍋氫粈涔�
const handleSelectionChange = (selectionList) => {
@@ -305,7 +584,7 @@
};
const changePage = ({ page, limit }) => {
pagination.currentPage = page;
- pagination.pageSize = limit;
+ pagination.pageSize = limit;
onCurrentChange(page);
};
const deleteRow = (id) => {
@@ -351,36 +630,33 @@
};
const showQRCode = async (row) => {
- // 鐩存帴浣跨敤URL锛屼笉瑕佺敤JSON.stringify鍖呰
- const qrContent = proxy.javaApi + '/device-info?deviceId=' + row.id;
+ 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 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.fillStyle = "#ffffff";
ctx.fillRect(0, 0, canvas.width, canvas.height);
-
- // 缁樺埗浜岀淮鐮�
+
const qrImg = new Image();
qrImg.src = qrDataUrl;
- await new Promise((resolve) => { qrImg.onload = resolve; });
+ 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');
+
+ 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;
};
@@ -392,42 +668,100 @@
a.click();
};
-// 瀵煎叆鎸夐挳鎿嶄綔
const handleImport = () => {
- upload.title = "璁惧鍙拌处瀵煎叆"
- upload.open = true
-}
+ upload.title = "璁惧鍙拌处瀵煎叆";
+ upload.open = true;
+};
-// 涓嬭浇妯℃澘鎿嶄綔
const importTemplate = () => {
- proxy.download("/device/ledger/downloadTemplate", {}, `璁惧鍙拌处瀵煎叆妯℃澘_${new Date().getTime()}.xlsx`)
-}
+ proxy.download(
+ "/device/ledger/downloadTemplate",
+ {},
+ `璁惧鍙拌处瀵煎叆妯℃澘_${new Date().getTime()}.xlsx`
+ );
+};
-// 鏂囦欢涓婁紶涓鐞�
const handleFileUploadProgress = (event, file, fileList) => {
- upload.isUploading = true
-}
+ 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()
-}
+ 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()
-}
+ proxy.$refs["uploadRef"].submit();
+};
onMounted(() => {
+ getAreaTree();
getTableData();
});
</script>
<style lang="scss" scoped>
+.area-view {
+ display: flex;
+}
+.area-left {
+ width: 320px;
+ flex-shrink: 0;
+ padding: 16px;
+ background: #ffffff;
+ margin-right: 16px;
+}
+.area-left-header {
+ margin-bottom: 10px;
+}
+.all-device-node {
+ padding: 8px 12px;
+ cursor: pointer;
+ font-size: 14px;
+ border-radius: 4px;
+ margin-bottom: 8px;
+}
+.all-device-node:hover {
+ background: #f5f7fa;
+}
+.all-device-node.active {
+ background: #ecf5ff;
+ color: #409eff;
+}
+.area-tree-node {
+ flex: 1;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ font-size: 14px;
+ padding-right: 4px;
+}
+.area-node-count {
+ color: #909399;
+ font-size: 12px;
+ margin-left: 6px;
+}
+.area-node-actions {
+ display: none;
+}
+.area-tree-node:hover .area-node-actions {
+ display: inline-flex;
+}
+.area-right {
+ flex: 1;
+ min-width: 0;
+ padding: 16px;
+ background: #ffffff;
+}
.table_list {
margin-top: unset;
}
diff --git a/src/views/equipmentManagement/measurementEquipment/index.vue b/src/views/equipmentManagement/measurementEquipment/index.vue
index 007eef6..3ece930 100644
--- a/src/views/equipmentManagement/measurementEquipment/index.vue
+++ b/src/views/equipmentManagement/measurementEquipment/index.vue
@@ -37,6 +37,10 @@
<div>
<el-button type="primary"
@click="openForm('add')">鏂板璁¢噺鍣ㄥ叿</el-button>
+ <el-button type="primary"
+ plain
+ @click="handleImport">鎵归噺瀵煎叆</el-button>
+ <el-button @click="handleDownloadTemplate">涓嬭浇妯℃澘</el-button>
<el-button type="danger"
plain
@click="handleDelete">鍒犻櫎</el-button>
@@ -61,6 +65,17 @@
@close="handleQuery"></calibration-dia>
<files-dia ref="filesDia"></files-dia>
<rowClickDataForm ref="rowClickData"></rowClickDataForm>
+ <ImportDialog ref="importDialogRef"
+ v-model="importDialogVisible"
+ title="鎵归噺瀵煎叆璁¢噺鍣ㄥ叿"
+ :action="importAction"
+ :headers="importHeaders"
+ :auto-upload="false"
+ :on-success="handleImportSuccess"
+ :on-error="handleImportError"
+ @confirm="handleImportConfirm"
+ @download-template="handleDownloadTemplate"
+ @close="handleImportClose" />
</div>
</template>
@@ -76,6 +91,8 @@
import FormDia from "@/views/equipmentManagement/measurementEquipment/components/formDia.vue";
import { ElMessageBox } from "element-plus";
import useUserStore from "@/store/modules/user.js";
+ import { getToken } from "@/utils/auth";
+ import ImportDialog from "@/components/Dialog/ImportDialog.vue";
import CalibrationDia from "@/views/equipmentManagement/measurementEquipment/components/calibrationDia.vue";
import {
measuringInstrumentDelete,
@@ -336,6 +353,54 @@
proxy.$modal.msg("宸插彇娑�");
});
};
+ // 鎵归噺瀵煎叆
+ const importDialogRef = ref();
+ const importDialogVisible = ref(false);
+ const importAction =
+ import.meta.env.VITE_APP_BASE_API + "/measuringInstrumentLedger/import";
+ const importHeaders = ref({
+ Authorization: `Bearer ${getToken()}`,
+ });
+
+ const handleImport = () => {
+ importDialogVisible.value = true;
+ };
+
+ const handleImportSuccess = response => {
+ if (response.code === 200) {
+ proxy.$modal.msgSuccess(response.msg || "瀵煎叆鎴愬姛");
+ importDialogVisible.value = false;
+ if (importDialogRef.value) {
+ importDialogRef.value.clearFiles();
+ }
+ getList();
+ } else {
+ proxy.$modal.msgError(response.msg || "瀵煎叆澶辫触");
+ }
+ };
+
+ const handleImportError = error => {
+ proxy.$modal.msgError("瀵煎叆澶辫触锛岃妫�鏌ユ枃浠舵牸寮忔槸鍚︽纭�");
+ };
+
+ const handleImportConfirm = () => {
+ if (importDialogRef.value) {
+ importDialogRef.value.submit();
+ }
+ };
+
+ const handleDownloadTemplate = () => {
+ proxy.download(
+ "/measuringInstrumentLedger/downloadTemplate",
+ {},
+ "璁¢噺鍣ㄥ叿鍙拌处瀵煎叆妯℃澘.xlsx"
+ );
+ };
+
+ const handleImportClose = () => {
+ importDialogVisible.value = false;
+ };
+
onMounted(() => {
getList();
});
diff --git a/src/views/equipmentManagement/upkeep/Form/formDia.vue b/src/views/equipmentManagement/upkeep/Form/formDia.vue
index 6856ae1..0415d3a 100644
--- a/src/views/equipmentManagement/upkeep/Form/formDia.vue
+++ b/src/views/equipmentManagement/upkeep/Form/formDia.vue
@@ -95,6 +95,7 @@
<el-option label="姣忓懆" value="WEEKLY"/>
<el-option label="姣忔湀" value="MONTHLY"/>
<el-option label="瀛e害" value="QUARTERLY"/>
+ <el-option label="骞村害淇濆吇" value="YEARLY"/>
</el-select>
</el-form-item>
</el-col>
@@ -143,6 +144,20 @@
/>
</el-form-item>
</el-col>
+ <el-col :span="12" v-if="form.frequencyType === 'YEARLY'">
+ <el-form-item label="鎵ц鏃ユ湡" prop="frequencyDetail">
+ <div style="display: flex; gap: 4px; width: 100%">
+ <el-select v-model="form.yearMonth" placeholder="鏈�" clearable style="flex: 1">
+ <el-option v-for="m in 12" :key="m" :label="m + '鏈�'" :value="m" />
+ </el-select>
+ <el-select v-model="form.yearDay" placeholder="鏃�" clearable style="flex: 1">
+ <el-option v-for="d in 31" :key="d" :label="d + '鏃�'" :value="d" />
+ </el-select>
+ <el-time-picker v-model="form.yearTime" placeholder="鏃堕棿" format="HH:mm" value-format="HH:mm" style="flex: 1.2" />
+ </div>
+ </el-form-item>
+ </el-col>
+
</el-row>
<el-row>
<el-col :span="12">
@@ -182,6 +197,9 @@
frequencyDetail: '',
week: '',
time: '',
+ yearMonth: undefined,
+ yearDay: undefined,
+ yearTime: '',
deviceModel: undefined, // 瑙勬牸鍨嬪彿
registrationDate: '',
maintenancePerson: '' // 淇濆吇浜�
@@ -238,6 +256,13 @@
if (form.value.taskId) {
setDeviceModel(form.value.taskId);
}
+ // YEARLY 棰戠巼鍥炴樉锛氳В鏋� 鏈堜唤,鏃ユ湡,鏃堕棿
+ if (form.value.frequencyType === 'YEARLY' && form.value.frequencyDetail) {
+ const parts = String(form.value.frequencyDetail).split(',');
+ form.value.yearMonth = Number(parts[0]);
+ form.value.yearDay = Number(parts[1]);
+ form.value.yearTime = parts[2] || '';
+ }
} else if (type === 'add') {
// 鏂板鏃惰缃櫥璁版棩鏈熶负褰撳ぉ
form.value.registrationDate = getCurrentDate();
@@ -269,6 +294,9 @@
frequencyDetail: '',
week: '',
time: '',
+ yearMonth: undefined,
+ yearDay: undefined,
+ yearTime: '',
deviceModel: undefined,
registrationDate: '',
maintenancePerson: ''
@@ -300,6 +328,25 @@
frequencyDetail = payload.week + ',' + payload.time
payload.frequencyDetail = frequencyDetail
}
+
+ if (payload.frequencyType === 'YEARLY') {
+ const month = Number(payload.yearMonth);
+ const day = Number(payload.yearDay);
+ const time = payload.yearTime;
+ if (!month || month < 1 || month > 12) {
+ proxy.$modal.msgError('鏈堜唤蹇呴』鍦�1-12涔嬮棿');
+ return;
+ }
+ if (!day || day < 1 || day > 31) {
+ proxy.$modal.msgError('鏃ユ湡蹇呴』鍦�1-31涔嬮棿');
+ return;
+ }
+ if (!time || !/^\d{2}:\d{2}$/.test(time)) {
+ proxy.$modal.msgError('鏃堕棿鏍煎紡蹇呴』涓� HH:mm');
+ return;
+ }
+ payload.frequencyDetail = month + ',' + day + ',' + time;
+ }
// 褰曞叆鏃ユ湡锛氱洿鎺ヤ娇鐢ㄨ〃鍗曢噷鐨� registrationDate 瀛楁
// 涓�浜涢粯璁ょ姸鎬佸瓧娈�
diff --git a/src/views/equipmentManagement/upkeep/index.vue b/src/views/equipmentManagement/upkeep/index.vue
index 245a162..560b70d 100644
--- a/src/views/equipmentManagement/upkeep/index.vue
+++ b/src/views/equipmentManagement/upkeep/index.vue
@@ -63,6 +63,9 @@
}"
@selection-change="handleScheduledSelectionChange"
@pagination="changeScheduledPage">
+ <template #frequencyDetailRef="{ row }">
+ {{ formatFrequencyDetail(row) }}
+ </template>
<template #statusRef="{ row }">
<el-tag v-if="row.status === 1"
type="success">鍚敤</el-tag>
@@ -316,31 +319,15 @@
WEEKLY: "姣忓懆",
MONTHLY: "姣忔湀",
QUARTERLY: "瀛e害",
+ YEARLY: "骞村害淇濆吇",
}[cell] || ""),
},
{
prop: "frequencyDetail",
label: "寮�濮嬫棩鏈熶笌鏃堕棿",
minWidth: 150,
- // 鍚屾牱鏀圭敤 formatData锛孭IMTable 鍐呴儴浼氭妸鍗曞厓鏍煎�间紶杩涙潵
- formatData: cell => {
- if (typeof cell !== "string") return "";
- let val = cell;
- const replacements = {
- MON: "鍛ㄤ竴",
- TUE: "鍛ㄤ簩",
- WED: "鍛ㄤ笁",
- THU: "鍛ㄥ洓",
- FRI: "鍛ㄤ簲",
- SAT: "鍛ㄥ叚",
- SUN: "鍛ㄦ棩",
- };
- // 浣跨敤姝e垯涓�娆℃�ф浛鎹㈡墍鏈夊尮閰嶉」
- return val.replace(
- /MON|TUE|WED|THU|FRI|SAT|SUN/g,
- match => replacements[match]
- );
- },
+ dataType: "slot",
+ slot: "frequencyDetailRef",
},
{ prop: "maintenancePerson", label: "淇濆吇浜�", minWidth: 100 },
{ prop: "registrant", label: "鐧昏浜�", minWidth: 100 },
@@ -362,6 +349,28 @@
]);
// 淇濆吇璁板綍琛ㄦ牸鍒楅厤缃紙鍘熻澶囦繚鍏昏〃鏍煎垪锛�
+ const formatFrequencyDetail = (row) => {
+ if (!row) return "";
+ const val = String(row.frequencyDetail || "");
+ if (row.frequencyType === "YEARLY") {
+ const parts = val.split(",");
+ if (parts.length === 3) {
+ return `姣忓勾${parts[0]}鏈�${parts[1]}鏃� ${parts[2]}`;
+ }
+ return val;
+ }
+ const replacements = {
+ MON: "鍛ㄤ竴",
+ TUE: "鍛ㄤ簩",
+ WED: "鍛ㄤ笁",
+ THU: "鍛ㄥ洓",
+ FRI: "鍛ㄤ簲",
+ SAT: "鍛ㄥ叚",
+ SUN: "鍛ㄦ棩",
+ };
+ return val.replace(/MON|TUE|WED|THU|FRI|SAT|SUN/g, (m) => replacements[m]);
+ };
+
const columns = ref([
{
label: "璁惧鍚嶇О",
diff --git a/src/views/procurementManagement/procurementReport/index.vue b/src/views/procurementManagement/procurementReport/index.vue
index 26e682d..bf07e6e 100644
--- a/src/views/procurementManagement/procurementReport/index.vue
+++ b/src/views/procurementManagement/procurementReport/index.vue
@@ -139,7 +139,7 @@
}
},
{
- label: '閫�娆惧偍閲�',
+ label: '閲囪喘閲戦',
prop: 'purchaseAmount',
formatData: (val) => {
return val ? `楼${parseFloat(val).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}` : '楼0.00'
diff --git a/src/views/salesManagement/salesLedger/index.vue b/src/views/salesManagement/salesLedger/index.vue
index 610c17c..ccf90b4 100644
--- a/src/views/salesManagement/salesLedger/index.vue
+++ b/src/views/salesManagement/salesLedger/index.vue
@@ -177,7 +177,7 @@
<el-button
link
type="primary"
- :disabled="!canShip(scope.row)"
+ :disabled="!canShip(scope.row) || props.row.contractType === 2"
@click="openDeliveryForm(scope.row)"
>
鍙戣揣
@@ -261,7 +261,7 @@
width="200"
show-overflow-tooltip
/>
- <el-table-column fixed="right" label="鎿嶄綔" width="220" align="center">
+ <el-table-column fixed="right" label="鎿嶄綔" width="320" align="center">
<template #default="scope">
<el-button link type="primary" @click="openForm('view', scope.row)"
>璇︽儏
@@ -276,6 +276,20 @@
!canEditLedger(scope.row)
"
>缂栬緫
+ </el-button>
+ <el-button
+ v-if="scope.row.contractType === 2"
+ link
+ type="success"
+ @click="openFrameworkOrderDialog(scope.row)"
+ >鍒涘缓妗嗘灦璁㈠崟
+ </el-button>
+ <el-button
+ v-if="scope.row.contractType === 2"
+ link
+ type="primary"
+ @click="openFrameworkOrderList(scope.row)"
+ >妗嗘灦璁㈠崟
</el-button>
<el-button link type="primary" @click="openFileDialog(scope.row)"
>闄勪欢
@@ -320,6 +334,21 @@
<el-button type="primary" plain @click="openQuotationDialog">
浠庨攢鍞姤浠峰鍏�
</el-button>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="鍚堝悓绫诲瀷锛�" prop="contractType">
+ <el-select
+ v-model="form.contractType"
+ placeholder="璇烽�夋嫨"
+ clearable
+ :disabled="operationType === 'view' || operationType === 'edit'"
+ >
+ <el-option label="鏅�氶攢鍞悎鍚�" :value="1" />
+ <el-option label="妗嗘灦鍚堝悓" :value="2" />
+ </el-select>
+ </el-form-item>
</el-col>
</el-row>
<el-row :gutter="30">
@@ -467,7 +496,7 @@
</el-form-item>
</el-col>
</el-row>
- <el-row :gutter="30">
+ <el-row :gutter="30" v-if="!isFrameworkContract">
<el-col :span="12">
<el-form-item label="浜よ揣鏃ユ湡锛�" prop="entryDate">
<el-date-picker
@@ -750,7 +779,7 @@
/>
</el-form-item>
</el-col>
- <el-col :span="12">
+ <el-col :span="12" v-if="!isFrameworkContract">
<el-form-item label="绋庣巼(%)锛�" prop="taxRate">
<el-select
v-model="productForm.taxRate"
@@ -783,7 +812,7 @@
/>
</el-form-item>
</el-col>
- <el-col :span="12">
+ <el-col :span="12" v-if="!isFrameworkContract">
<el-form-item label="鏁伴噺锛�" prop="quantity">
<el-input-number
:step="0.1"
@@ -799,7 +828,7 @@
</el-col>
</el-row>
<el-row :gutter="30">
- <el-col :span="12">
+ <el-col :span="12" v-if="!isFrameworkContract">
<el-form-item label="鍚◣鎬讳环(鍏�)锛�" prop="taxInclusiveTotalPrice">
<el-input
v-model="productForm.taxInclusiveTotalPrice"
@@ -809,7 +838,7 @@
/>
</el-form-item>
</el-col>
- <el-col :span="12">
+ <el-col :span="12" v-if="!isFrameworkContract">
<el-form-item
label="涓嶅惈绋庢�讳环(鍏�)锛�"
prop="taxExclusiveTotalPrice"
@@ -824,7 +853,7 @@
</el-col>
</el-row>
<el-row :gutter="30">
- <el-col :span="12">
+ <el-col :span="12" v-if="!isFrameworkContract">
<el-form-item label="鍙戠エ绫诲瀷锛�" prop="invoiceType">
<el-select
v-model="productForm.invoiceType"
@@ -836,7 +865,7 @@
</el-select>
</el-form-item>
</el-col>
- <el-col :span="12">
+ <el-col :span="12" v-if="!isFrameworkContract">
<el-form-item label="鏄惁鐢熶骇锛�" prop="isProduction">
<el-radio-group v-model="productForm.isProduction">
<el-radio label="鏄�" :value="true" />
@@ -1177,6 +1206,104 @@
</div>
</template>
</el-dialog>
+ <!-- 鍒涘缓妗嗘灦璁㈠崟寮圭獥 -->
+ <el-dialog
+ v-model="frameworkOrderDialogVisible"
+ title="鍒涘缓妗嗘灦璁㈠崟"
+ width="70%"
+ :close-on-click-modal="false"
+ >
+ <el-form label-width="120px" label-position="top">
+ <el-form-item label="鐖舵鏋跺悎鍚岋細">
+ <el-input
+ :model-value="currentFrameworkContract?.salesContractNo"
+ disabled
+ />
+ </el-form-item>
+ <el-form-item label="澶囨敞锛�">
+ <el-input
+ v-model="frameworkOrderForm.remarks"
+ type="textarea"
+ :rows="2"
+ placeholder="璇疯緭鍏ュ娉�"
+ />
+ </el-form-item>
+ <el-form-item label="浜у搧锛堝~鍐欎氦璐ф棩鏈燂級锛�">
+ <el-table :data="frameworkOrderProducts" border>
+ <el-table-column label="浜у搧澶х被" prop="productCategory" />
+ <el-table-column label="瑙勬牸鍨嬪彿" prop="specificationModel" />
+ <el-table-column label="鍗曚綅" prop="unit" />
+ <el-table-column label="浜よ揣鏃ユ湡" align="center">
+ <template #default="scope">
+ <el-date-picker
+ v-model="scope.row.deliveryDate"
+ value-format="YYYY-MM-DD"
+ format="YYYY-MM-DD"
+ type="date"
+ placeholder="璇烽�夋嫨"
+ clearable
+ />
+ </template>
+ </el-table-column>
+ </el-table>
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <el-button type="primary" @click="submitFrameworkOrder"
+ >纭鍒涘缓
+ </el-button>
+ <el-button @click="frameworkOrderDialogVisible = false">鍙栨秷</el-button>
+ </template>
+ </el-dialog>
+ <!-- 妗嗘灦璁㈠崟鍒楄〃寮圭獥 -->
+ <el-dialog
+ v-model="frameworkOrderListVisible"
+ title="妗嗘灦璁㈠崟鍒楄〃"
+ width="80%"
+ :close-on-click-modal="false"
+ >
+ <el-alert
+ v-if="frameworkContractShippedQty !== null"
+ type="info"
+ :closable="false"
+ style="margin-bottom: 12px"
+ :title="'鐖舵鏋跺悎鍚岀疮璁″凡鍙戣揣鏁伴噺锛�' + frameworkContractShippedQty"
+ />
+ <el-table :data="frameworkOrderList" border :row-key="(row) => row.id">
+ <el-table-column type="expand">
+ <template #default="props">
+ <el-table :data="props.row.products" border size="small">
+ <el-table-column label="浜у搧澶х被" prop="productCategory" />
+ <el-table-column label="瑙勬牸鍨嬪彿" prop="specificationModel" />
+ <el-table-column label="鍗曚綅" prop="unit" />
+ <el-table-column label="鎿嶄綔" width="80" align="center">
+ <template #default="scope">
+ <el-button
+ link
+ type="primary"
+ @click="shipFrameworkOrderProduct(scope.row, props.row)"
+ >鍙戣揣
+ </el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ </template>
+ </el-table-column>
+ <el-table-column
+ label="璁㈠崟缂栧彿"
+ prop="salesContractNo"
+ min-width="180"
+ />
+ <el-table-column label="椤圭洰鍚嶇О" prop="projectName" min-width="160" />
+ <el-table-column label="澶囨敞" prop="remarks" min-width="160" />
+ <el-table-column
+ label="绱鍙戣揣鏁伴噺"
+ prop="shippedQuantity"
+ width="140"
+ align="center"
+ />
+ </el-table>
+ </el-dialog>
</div>
</template>
@@ -1201,6 +1328,9 @@
delProduct,
delLedgerFile,
getProductInventory,
+ createFrameworkOrder,
+ listFrameworkOrders,
+ frameworkContractShippedQuantity,
} from "@/api/salesManagement/salesLedger.js";
import { getStockInventoryByModelId } from "@/api/inventoryManagement/stockInventory.js";
import { modelList, productTreeList } from "@/api/basicData/product.js";
@@ -1250,6 +1380,7 @@
},
form: {
salesContractNo: "",
+ contractType: 2,
autoGenerateContractNo: true,
salesman: "",
customerId: "",
@@ -1267,11 +1398,31 @@
customerId: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
entryPerson: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
entryDate: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
- deliveryDate: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
+ deliveryDate: [
+ {
+ validator: (rule, value, callback) => {
+ if (form.value.contractType === 2) return callback();
+ if (!value) return callback(new Error("璇烽�夋嫨"));
+ callback();
+ },
+ trigger: "change",
+ },
+ ],
executionDate: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
},
});
const { form, rules } = toRefs(data);
+const isFrameworkContract = computed(() => form.value.contractType === 2);
+const frameworkOptional = (message, trigger = "blur") => ({
+ validator: (_rule, value, callback) => {
+ if (isFrameworkContract.value) return callback();
+ if (value === undefined || value === null || value === "") {
+ return callback(new Error(message));
+ }
+ callback();
+ },
+ trigger,
+});
const { form: searchForm } = useFormData(data.searchForm);
// 浜у搧琛ㄥ崟寮规鏁版嵁
const productFormVisible = ref(false);
@@ -1297,18 +1448,12 @@
{ required: true, message: "璇烽�夋嫨", trigger: "change" },
],
unit: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
- quantity: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
- taxInclusiveUnitPrice: [
- { required: true, message: "璇疯緭鍏�", trigger: "blur" },
- ],
- taxRate: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
- taxInclusiveTotalPrice: [
- { required: true, message: "璇疯緭鍏�", trigger: "blur" },
- ],
- taxExclusiveTotalPrice: [
- { required: true, message: "璇疯緭鍏�", trigger: "blur" },
- ],
- invoiceType: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
+ quantity: [frameworkOptional("璇疯緭鍏�")],
+ taxInclusiveUnitPrice: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
+ taxRate: [frameworkOptional("璇烽�夋嫨", "change")],
+ taxInclusiveTotalPrice: [frameworkOptional("璇疯緭鍏�")],
+ taxExclusiveTotalPrice: [frameworkOptional("璇疯緭鍏�")],
+ invoiceType: [frameworkOptional("璇烽�夋嫨", "change")],
isProduction: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
},
});
@@ -1740,6 +1885,7 @@
form.value.entryPerson = userStore.id;
if (type === "add") {
// 鏂板鏃惰缃綍鍏ユ棩鏈熶负褰撳ぉ
+ form.value.contractType = 2;
form.value.entryDate = getCurrentDate();
// 绛捐鏃ユ湡榛樿涓哄綋澶�
form.value.executionDate = getCurrentDate();
@@ -1892,6 +2038,14 @@
}
form.value.storageBlobDTOs = fileList;
form.value.type = 1;
+ if (isFrameworkContract.value) {
+ form.value.productData = form.value.productData.map((item) => ({
+ ...item,
+ quantity: null,
+ deliveryDate: null,
+ isProduction: false,
+ }));
+ }
if (form.value.autoGenerateContractNo) {
form.value.salesContractNo = "";
}
@@ -2933,6 +3087,92 @@
deliveryFormVisible.value = false;
currentDeliveryRow.value = null;
};
+
+// 妗嗘灦鍚堝悓 / 妗嗘灦璁㈠崟鐩稿叧
+const frameworkOrderDialogVisible = ref(false);
+const frameworkOrderListVisible = ref(false);
+const currentFrameworkContract = ref(null);
+const frameworkOrderForm = reactive({
+ remarks: "",
+});
+const frameworkOrderProducts = ref([]);
+const frameworkOrderList = ref([]);
+const frameworkContractShippedQty = ref(null);
+
+const openFrameworkOrderDialog = async (row) => {
+ currentFrameworkContract.value = row;
+ frameworkOrderForm.remarks = "";
+ frameworkOrderProducts.value = [];
+ try {
+ const res = await productList({ salesLedgerId: row.id, type: 1 });
+ frameworkOrderProducts.value = (res.data || []).map((p) => ({
+ parentProductId: p.id,
+ productCategory: p.productCategory,
+ specificationModel: p.specificationModel,
+ unit: p.unit,
+ deliveryDate: "",
+ }));
+ } catch (e) {
+ frameworkOrderProducts.value = [];
+ }
+ frameworkOrderDialogVisible.value = true;
+};
+
+const submitFrameworkOrder = async () => {
+ const selected = frameworkOrderProducts.value.filter((p) => p.deliveryDate);
+ if (selected.length === 0) {
+ proxy.$modal.msgWarning("璇疯嚦灏戜负涓�涓骇鍝佸~鍐欎氦璐ф棩鏈�");
+ return;
+ }
+ try {
+ await createFrameworkOrder({
+ id: currentFrameworkContract.value.id,
+ remarks: frameworkOrderForm.remarks,
+ productData: selected.map((p) => ({
+ parentProductId: p.parentProductId,
+ deliveryDate: p.deliveryDate,
+ })),
+ });
+ proxy.$modal.msgSuccess("妗嗘灦璁㈠崟鍒涘缓鎴愬姛");
+ frameworkOrderDialogVisible.value = false;
+ } catch (e) {
+ // 閿欒宸茬敱璇锋眰鎷︽埅鍣ㄧ粺涓�鎻愮ず
+ }
+};
+
+const openFrameworkOrderList = async (row) => {
+ currentFrameworkContract.value = row;
+ frameworkContractShippedQty.value = null;
+ frameworkOrderList.value = [];
+ frameworkOrderListVisible.value = true;
+ listFrameworkOrders({ parentContractId: row.id }).then(async (res) => {
+ const orders = res.data || [];
+ const withProducts = [];
+ for (const order of orders) {
+ let products = [];
+ try {
+ const productRes = await productList({ salesLedgerId: order.id, type: 1 });
+ products = productRes.data || [];
+ } catch (e) {
+ products = [];
+ }
+ withProducts.push({ ...order, products });
+ }
+ frameworkOrderList.value = withProducts;
+ });
+ frameworkContractShippedQuantity({ parentContractId: row.id }).then((res) => {
+ frameworkContractShippedQty.value = res.data ?? 0;
+ });
+};
+
+const shipFrameworkOrderProduct = (productRow, orderRow) => {
+ if (!orderRow || !orderRow.id) return;
+ frameworkOrderListVisible.value = false;
+ openDeliveryForm({
+ ...productRow,
+ salesLedgerId: orderRow.id,
+ });
+};
const currentFactoryName = ref("");
const getCurrentFactoryName = async () => {
let res = await userStore.getInfo();
--
Gitblit v1.9.3