From 265820c7f0df74a1c69308dcc2b1b61812ccd892 Mon Sep 17 00:00:00 2001
From: spring <2396852758@qq.com>
Date: 星期三, 17 九月 2025 15:35:11 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/dev' into dev
---
src/views/procurementManagement/procurementReport/index.vue | 847 +++++++++++
src/store/modules/user.js | 1
src/views/reportAnalysis/dataDashboard/index.vue | 19
src/views/procurementManagement/arrivalManagement/index.vue | 149 +
src/views/inspectionManagement/components/formDia.vue | 174 ++
src/views/inspectionManagement/components/viewFiles.vue | 246 +++
src/views/salesManagement/paymentShipping/index.vue | 8
src/api/procurementManagement/advancedPriceManagement.js | 302 ++++
src/views/inspectionManagement/components/qrCodeDia.vue | 132 +
src/api/procurementManagement/returnManagement.js | 35
src/api/procurementManagement/arrivalManagement.js | 43
src/views/procurementManagement/procurementPlan/index.vue | 821 ++++++++++
src/api/procurementManagement/procurementPlan.js | 127 +
src/assets/BI/biaoti.png | 0
src/views/inspectionManagement/index.vue | 391 +++++
src/views/procurementManagement/advancedPriceManagement/index.vue | 874 +++++++++++
src/views/inspectionManagement/components/viewQrCodeFiles.vue | 169 ++
src/views/procurementManagement/returnManagement/index.vue | 144 +
18 files changed, 4,375 insertions(+), 107 deletions(-)
diff --git a/src/api/procurementManagement/advancedPriceManagement.js b/src/api/procurementManagement/advancedPriceManagement.js
new file mode 100644
index 0000000..c1dd47c
--- /dev/null
+++ b/src/api/procurementManagement/advancedPriceManagement.js
@@ -0,0 +1,302 @@
+// 楂樼骇閲囪喘浠锋牸绠$悊API鎺ュ彛
+import request from "@/utils/request";
+
+// 鍒嗛〉鏌ヨ浠锋牸鍒楄〃
+export function getPriceList(query) {
+ return request({
+ url: "/procurement/price/list",
+ method: "get",
+ params: query,
+ });
+}
+
+// 鑾峰彇浠锋牸璇︽儏
+export function getPriceDetail(id) {
+ return request({
+ url: `/procurement/price/detail/${id}`,
+ method: "get",
+ });
+}
+
+// 鏂板浠锋牸
+export function addPrice(data) {
+ return request({
+ url: "/procurement/price/add",
+ method: "post",
+ data: data,
+ });
+}
+
+// 鏇存柊浠锋牸
+export function updatePrice(data) {
+ return request({
+ url: "/procurement/price/update",
+ method: "put",
+ data: data,
+ });
+}
+
+// 鍒犻櫎浠锋牸
+export function deletePrice(id) {
+ return request({
+ url: `/procurement/price/delete/${id}`,
+ method: "delete",
+ });
+}
+
+// 鎵归噺鍒犻櫎浠锋牸
+export function batchDeletePrice(ids) {
+ return request({
+ url: "/procurement/price/batchDelete",
+ method: "delete",
+ data: { ids },
+ });
+}
+
+// 澶嶅埗浠锋牸
+export function copyPrice(id) {
+ return request({
+ url: `/procurement/price/copy/${id}`,
+ method: "post",
+ });
+}
+
+// 搴旂敤浠锋牸锛堝皢寰呯敓鏁堢姸鎬佹敼涓烘湁鏁堬級
+export function applyPrice(id) {
+ return request({
+ url: `/procurement/price/apply/${id}`,
+ method: "put",
+ });
+}
+
+// 鏆傚仠浠锋牸
+export function suspendPrice(id) {
+ return request({
+ url: `/procurement/price/suspend/${id}`,
+ method: "put",
+ });
+}
+
+// 鎵归噺璁剧疆鎶樻墸
+export function batchSetDiscount(data) {
+ return request({
+ url: "/procurement/price/batchDiscount",
+ method: "post",
+ data: data,
+ });
+}
+
+// 鑾峰彇鎶樻墸閰嶇疆
+export function getDiscountConfig(id) {
+ return request({
+ url: `/procurement/price/discount/${id}`,
+ method: "get",
+ });
+}
+
+// 璁剧疆鍗曚釜鍟嗗搧鎶樻墸
+export function setDiscount(data) {
+ return request({
+ url: "/procurement/price/setDiscount",
+ method: "post",
+ data: data,
+ });
+}
+
+// 鑾峰彇闃舵鎶樻墸閰嶇疆
+export function getTieredDiscount(id) {
+ return request({
+ url: `/procurement/price/tieredDiscount/${id}`,
+ method: "get",
+ });
+}
+
+// 璁剧疆闃舵鎶樻墸
+export function setTieredDiscount(data) {
+ return request({
+ url: "/procurement/price/setTieredDiscount",
+ method: "post",
+ data: data,
+ });
+}
+
+// 鑾峰彇浠锋牸鎺у埗璁剧疆
+export function getPriceControlConfig() {
+ return request({
+ url: "/procurement/price/controlConfig",
+ method: "get",
+ });
+}
+
+// 鏇存柊浠锋牸鎺у埗璁剧疆
+export function updatePriceControlConfig(data) {
+ return request({
+ url: "/procurement/price/controlConfig",
+ method: "put",
+ data: data,
+ });
+}
+
+// 鑾峰彇浠锋牸棰勮鍒楄〃
+export function getPriceWarnings(query) {
+ return request({
+ url: "/procurement/price/warnings",
+ method: "get",
+ params: query,
+ });
+}
+
+// 澶勭悊浠锋牸棰勮
+export function handlePriceWarning(id, action) {
+ return request({
+ url: `/procurement/price/warning/${id}`,
+ method: "put",
+ data: { action },
+ });
+}
+
+// 鑾峰彇浠锋牸鍘嗗彶璁板綍
+export function getPriceHistory(id, query) {
+ return request({
+ url: `/procurement/price/history/${id}`,
+ method: "get",
+ params: query,
+ });
+}
+
+// 鑾峰彇浠锋牸缁熻鏁版嵁
+export function getPriceStatistics(query) {
+ return request({
+ url: "/procurement/price/statistics",
+ method: "get",
+ params: query,
+ });
+}
+
+// 瀵煎嚭浠锋牸鏁版嵁
+export function exportPriceData(query) {
+ return request({
+ url: "/procurement/price/export",
+ method: "get",
+ params: query,
+ responseType: 'blob',
+ });
+}
+
+// 瀵煎叆浠锋牸鏁版嵁
+export function importPriceData(file) {
+ const formData = new FormData();
+ formData.append('file', file);
+ return request({
+ url: "/procurement/price/import",
+ method: "post",
+ data: formData,
+ headers: {
+ 'Content-Type': 'multipart/form-data',
+ },
+ });
+}
+
+// 鑾峰彇浠锋牸妯℃澘
+export function downloadPriceTemplate() {
+ return request({
+ url: "/procurement/price/template",
+ method: "get",
+ responseType: 'blob',
+ });
+}
+
+// 浠锋牸瀹℃壒
+export function approvePrice(id, data) {
+ return request({
+ url: `/procurement/price/approve/${id}`,
+ method: "put",
+ data: data,
+ });
+}
+
+// 浠锋牸椹冲洖
+export function rejectPrice(id, data) {
+ return request({
+ url: `/procurement/price/reject/${id}`,
+ method: "put",
+ data: data,
+ });
+}
+
+// 鑾峰彇渚涘簲鍟嗗垪琛紙鐢ㄤ簬涓嬫媺閫夋嫨锛�
+export function getSupplierOptions() {
+ return request({
+ url: "/procurement/price/suppliers",
+ method: "get",
+ });
+}
+
+// 鑾峰彇鍟嗗搧鍒楄〃锛堢敤浜庝笅鎷夐�夋嫨锛�
+export function getProductOptions(query) {
+ return request({
+ url: "/procurement/price/products",
+ method: "get",
+ params: query,
+ });
+}
+
+// 鑾峰彇鍟嗗搧璇︾粏淇℃伅
+export function getProductInfo(productId) {
+ return request({
+ url: `/procurement/price/productInfo/${productId}`,
+ method: "get",
+ });
+}
+
+// 浠锋牸姣旇緝鍒嗘瀽
+export function comparePrices(data) {
+ return request({
+ url: "/procurement/price/compare",
+ method: "post",
+ data: data,
+ });
+}
+
+// 鑾峰彇浠锋牸瓒嬪娍鏁版嵁
+export function getPriceTrend(id, period) {
+ return request({
+ url: `/procurement/price/trend/${id}`,
+ method: "get",
+ params: { period },
+ });
+}
+
+// 浠锋牸棰勬祴
+export function predictPrice(id, data) {
+ return request({
+ url: `/procurement/price/predict/${id}`,
+ method: "post",
+ data: data,
+ });
+}
+
+// 鑾峰彇甯傚満浠锋牸鍙傝��
+export function getMarketPriceReference(productCode) {
+ return request({
+ url: `/procurement/price/marketRef/${productCode}`,
+ method: "get",
+ });
+}
+
+// 浠锋牸鍙樺姩閫氱煡璁剧疆
+export function updateNotificationSettings(data) {
+ return request({
+ url: "/procurement/price/notifications",
+ method: "put",
+ data: data,
+ });
+}
+
+// 鑾峰彇浠锋牸鍙樺姩閫氱煡璁剧疆
+export function getNotificationSettings() {
+ return request({
+ url: "/procurement/price/notifications",
+ method: "get",
+ });
+}
diff --git a/src/api/procurementManagement/arrivalManagement.js b/src/api/procurementManagement/arrivalManagement.js
new file mode 100644
index 0000000..107fc3c
--- /dev/null
+++ b/src/api/procurementManagement/arrivalManagement.js
@@ -0,0 +1,43 @@
+// 閿�鍞彴璐﹂〉闈㈡帴鍙�
+import request from "@/utils/request";
+
+// 鍒嗛〉鏌ヨ
+export function listPage(query) {
+ return request({
+ url: "/inboundManagement/listPage",
+ method: "get",
+ params: query,
+ });
+}
+
+export function listPageCopy(query) {
+ return request({
+ url: "/inboundManagement/listPage",
+ method: "get",
+ params: query,
+ });
+}
+// 鏂板
+export function add(data) {
+ return request({
+ url: "/inboundManagement/add",
+ method: "post",
+ data
+ });
+}
+// 淇敼
+export function update(data) {
+ return request({
+ url: "/inboundManagement/update",
+ method: "post",
+ data
+ });
+}
+// 鍒犻櫎閿�鍞彴璐�
+export function del(data) {
+ return request({
+ url: "/inboundManagement/del",
+ method: "delete",
+ data
+ });
+}
\ No newline at end of file
diff --git a/src/api/procurementManagement/procurementPlan.js b/src/api/procurementManagement/procurementPlan.js
new file mode 100644
index 0000000..04c58b3
--- /dev/null
+++ b/src/api/procurementManagement/procurementPlan.js
@@ -0,0 +1,127 @@
+import request from "@/utils/request";
+
+// 鍒嗛〉鏌ヨ閲囪喘璁″垝鍒楄〃
+export function getProcurementPlanList(query) {
+ return request({
+ url: "/procurement/plan/list",
+ method: "get",
+ params: query,
+ });
+}
+
+// 鏂板閲囪喘璁″垝
+export function addProcurementPlan(data) {
+ return request({
+ url: "/procurement/plan/add",
+ method: "post",
+ data: data,
+ });
+}
+
+// 淇敼閲囪喘璁″垝
+export function updateProcurementPlan(data) {
+ return request({
+ url: "/procurement/plan/update",
+ method: "put",
+ data: data,
+ });
+}
+
+// 鍒犻櫎閲囪喘璁″垝
+export function deleteProcurementPlan(ids) {
+ return request({
+ url: "/procurement/plan/delete",
+ method: "delete",
+ data: ids,
+ });
+}
+
+// 鏍规嵁ID鑾峰彇閲囪喘璁″垝璇︽儏
+export function getProcurementPlanById(id) {
+ return request({
+ url: `/procurement/plan/${id}`,
+ method: "get",
+ });
+}
+
+// 鎵ц閲囪喘璁″垝璁$畻
+export function calculateProcurementPlan(data) {
+ return request({
+ url: "/procurement/plan/calculate",
+ method: "post",
+ data: data,
+ });
+}
+
+
+// 鑾峰彇浜у搧鐜版湁搴撳瓨淇℃伅
+export function getProductExistingStock(productIds) {
+ return request({
+ url: "/inventory/existingStock/productInfo",
+ method: "post",
+ data: productIds,
+ });
+}
+
+// 鑾峰彇浜у搧瀹夊叏搴撳瓨淇℃伅
+export function getProductSafetyStock(productIds) {
+ return request({
+ url: "/inventory/safetyStock/productInfo",
+ method: "post",
+ data: productIds,
+ });
+}
+
+// 鑾峰彇浜у搧棰勮鍑哄簱淇℃伅
+export function getProductExpectedOutbound(productIds) {
+ return request({
+ url: "/inventory/expectedOutbound/productInfo",
+ method: "post",
+ data: productIds,
+ });
+}
+
+// 鑾峰彇浜у搧棰勮鍏ュ簱淇℃伅
+export function getProductExpectedInbound(productIds) {
+ return request({
+ url: "/inventory/expectedInbound/productInfo",
+ method: "post",
+ data: productIds,
+ });
+}
+
+// 瀵煎嚭閲囪喘璁″垝
+export function exportProcurementPlan(query) {
+ return request({
+ url: "/procurement/plan/export",
+ method: "get",
+ params: query,
+ responseType: "blob",
+ });
+}
+
+// 鐢熸垚閲囪喘璁㈠崟
+export function generatePurchaseOrder(data) {
+ return request({
+ url: "/procurement/plan/generateOrder",
+ method: "post",
+ data: data,
+ });
+}
+
+// 楠岃瘉璁$畻鍏紡
+export function validateFormula(formula) {
+ return request({
+ url: "/procurement/plan/validateFormula",
+ method: "post",
+ data: { formula },
+ });
+}
+
+// 鑾峰彇璁$畻鍏紡妯℃澘
+export function getFormulaTemplates() {
+ return request({
+ url: "/procurement/plan/formulaTemplates",
+ method: "get",
+ });
+}
diff --git a/src/api/procurementManagement/returnManagement.js b/src/api/procurementManagement/returnManagement.js
new file mode 100644
index 0000000..e765701
--- /dev/null
+++ b/src/api/procurementManagement/returnManagement.js
@@ -0,0 +1,35 @@
+// 閿�鍞彴璐﹂〉闈㈡帴鍙�
+import request from "@/utils/request";
+
+// 鍒嗛〉鏌ヨ
+export function listPage(query) {
+ return request({
+ url: "/returnManagement/listPage",
+ method: "get",
+ params: query,
+ });
+}
+// 鏂板
+export function add(data) {
+ return request({
+ url: "/returnManagement/add",
+ method: "post",
+ data
+ });
+}
+// 淇敼
+export function update(data) {
+ return request({
+ url: "/returnManagement/update",
+ method: "post",
+ data
+ });
+}
+// 鍒犻櫎閿�鍞彴璐�
+export function del(data) {
+ return request({
+ url: "/returnManagement/del",
+ method: "delete",
+ data
+ });
+}
\ No newline at end of file
diff --git a/src/assets/BI/biaoti.png b/src/assets/BI/biaoti.png
index f905f1c..3c5ccb9 100644
--- a/src/assets/BI/biaoti.png
+++ b/src/assets/BI/biaoti.png
Binary files differ
diff --git a/src/store/modules/user.js b/src/store/modules/user.js
index d52efa6..057af50 100644
--- a/src/store/modules/user.js
+++ b/src/store/modules/user.js
@@ -2,6 +2,7 @@
import { getToken, setToken, removeToken } from '@/utils/auth'
import { isHttp, isEmpty } from "@/utils/validate"
import defAva from '@/assets/images/profile.jpg'
+import { defineStore } from 'pinia'
const useUserStore = defineStore(
'user',
diff --git a/src/views/inspectionManagement/components/formDia.vue b/src/views/inspectionManagement/components/formDia.vue
new file mode 100644
index 0000000..9d9a9fd
--- /dev/null
+++ b/src/views/inspectionManagement/components/formDia.vue
@@ -0,0 +1,174 @@
+<template>
+ <div>
+ <el-dialog :title="operationType === 'add' ? '鏂板宸℃浠诲姟' : '缂栬緫宸℃浠诲姟'"
+ v-model="dialogVisitable" width="800px" @close="cancel">
+ <el-form :model="form" :rules="rules" ref="formRef" label-width="120px">
+ <el-row>
+ <el-col :span="12">
+ <el-form-item label="浠诲姟鍚嶇О" prop="taskName">
+ <el-input v-model="form.taskName" placeholder="璇疯緭鍏ヤ换鍔″悕绉�" maxlength="30" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鍦扮偣" prop="inspectionLocation">
+ <el-input v-model="form.inspectionLocation" placeholder="璇疯緭鍏ュ湴鐐�" maxlength="30" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row>
+ <el-col :span="12">
+ <el-form-item label="宸℃浜�" prop="inspector">
+ <el-select v-model="form.inspector" placeholder="璇烽�夋嫨" multiple clearable>
+ <el-option v-for="item in userList" :label="item.nickName" :value="item.userId" :key="item.userId"/>
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="澶囨敞" prop="remarks">
+ <el-input v-model="form.remarks" placeholder="璇疯緭鍏ュ娉�" type="textarea" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row>
+ <el-col :span="12">
+ <el-form-item label="浠诲姟棰戠巼" prop="frequencyType">
+ <el-select v-model="form.frequencyType" placeholder="璇烽�夋嫨" clearable>
+ <el-option label="姣忔棩" value="DAILY"/>
+ <el-option label="姣忓懆" value="WEEKLY"/>
+ <el-option label="姣忔湀" value="MONTHLY"/>
+ <el-option label="瀛e害" value="QUARTERLY"/>
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12" v-if="form.frequencyType === 'DAILY' && form.frequencyType">
+ <el-form-item label="鏃ユ湡" prop="frequencyDetail">
+ <el-time-picker v-model="form.frequencyDetail" placeholder="閫夋嫨鏃堕棿" format="HH:mm"
+ value-format="HH:mm" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12" v-if="form.frequencyType === 'WEEKLY' && form.frequencyType">
+ <el-form-item label="鏃ユ湡" prop="frequencyDetail">
+ <el-select v-model="form.week" placeholder="璇烽�夋嫨" clearable style="width: 50%">
+ <el-option label="鍛ㄤ竴" value="MON"/>
+ <el-option label="鍛ㄤ簩" value="TUE"/>
+ <el-option label="鍛ㄤ笁" value="WED"/>
+ <el-option label="鍛ㄥ洓" value="THU"/>
+ <el-option label="鍛ㄤ簲" value="FRI"/>
+ <el-option label="鍛ㄥ叚" value="SAT"/>
+ <el-option label="鍛ㄦ棩" value="SUN"/>
+ </el-select>
+ <el-time-picker v-model="form.time" placeholder="閫夋嫨鏃堕棿" format="HH:mm"
+ value-format="HH:mm" style="width: 50%"/>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12" v-if="form.frequencyType === 'MONTHLY' && form.frequencyType">
+ <el-form-item label="鏃ユ湡" prop="frequencyDetail">
+ <el-date-picker
+ v-model="form.frequencyDetail"
+ type="datetime"
+ clearable
+ placeholder="閫夋嫨寮�濮嬫棩鏈�"
+ format="DD,HH:mm"
+ value-format="DD,HH:mm"
+ />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12" v-if="form.frequencyType === 'QUARTERLY' && form.frequencyType">
+ <el-form-item label="鏃ユ湡" prop="frequencyDetail">
+ <el-date-picker
+ v-model="form.frequencyDetail"
+ type="datetime"
+ clearable
+ placeholder="閫夋嫨寮�濮嬫棩鏈�"
+ format="MM,DD,HH:mm"
+ value-format="MM,DD,HH:mm"
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ </el-form>
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button @click="cancel">鍙栨秷</el-button>
+ <el-button type="primary" @click="submitForm">淇濆瓨</el-button>
+ </div>
+ </template>
+ </el-dialog>
+ </div>
+</template>
+
+<script setup>
+import {reactive, ref} from "vue";
+import useUserStore from '@/store/modules/user'
+import {addOrEditTimingTask} from "@/api/inspectionManagement/index.js";
+import {userListAll} from "@/api/publicApi/index.js";
+
+const { proxy } = getCurrentInstance()
+const emit = defineEmits()
+const userStore = useUserStore()
+const dialogVisitable = ref(false);
+const operationType = ref('add');
+const data = reactive({
+ form: {
+ taskName: '',
+ inspectionLocation: '',
+ inspector: '',
+ inspectorIds: '',
+ remarks: '',
+ frequencyType: '',
+ frequencyDetail: '',
+ },
+ rules: {
+ taskName: [{ required: true, message: "璇疯緭鍏ヤ换鍔″悕绉�", trigger: "blur" },],
+ inspectionLocation: [{ required: true, message: "璇疯緭鍏ュ湴鐐�", trigger: "blur" },],
+ inspector: [{ required: true, message: "璇疯緭鍏ュ贰妫�浜�", trigger: "blur" },],
+ }
+})
+const { form, rules } = toRefs(data)
+const userList = ref([])
+
+// 鎵撳紑寮规
+const openDialog = async (type, row) => {
+ dialogVisitable.value = true
+ userListAll().then(res => {
+ userList.value = res.data
+ })
+ if (type === 'edit') {
+ form.value = {...row}
+ form.value.inspector = form.value.inspectorIds.split(',').map(Number)
+ }
+}
+
+// 鍏抽棴鍚堝苟琛ㄥ崟
+const cancel = () => {
+ proxy.resetForm("formRef")
+ dialogVisitable.value = false
+ emit('closeDia')
+}
+
+// 鎻愪氦鍚堝苟琛ㄥ崟
+const submitForm = () => {
+ proxy.$refs["formRef"].validate(async valid => {
+ if (valid) {
+ 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
+ addOrEditTimingTask(form.value).then(() => {
+ cancel()
+ proxy.$modal.msgSuccess('鎻愪氦鎴愬姛')
+ })
+ }
+ })
+}
+defineExpose({ openDialog })
+</script>
+
+<style scoped>
+
+</style>
\ No newline at end of file
diff --git a/src/views/inspectionManagement/components/qrCodeDia.vue b/src/views/inspectionManagement/components/qrCodeDia.vue
new file mode 100644
index 0000000..136c18c
--- /dev/null
+++ b/src/views/inspectionManagement/components/qrCodeDia.vue
@@ -0,0 +1,132 @@
+<template>
+ <div>
+ <el-dialog :title="operationType === 'add' ? '鏂板浜岀淮鐮�' : '缂栬緫浜岀淮鐮�'"
+ v-model="dialogVisitable" width="500px" @close="cancel">
+ <el-form :model="form" :rules="rules" ref="formRef" label-width="120px">
+ <el-row>
+ <el-col :span="24">
+ <el-form-item label="璁惧鍚嶇О" prop="deviceName">
+ <el-input v-model="form.deviceName" placeholder="璇疯緭鍏ヨ澶囧悕绉�" maxlength="30" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row>
+ <el-col :span="24">
+ <el-form-item label="鎵�鍦ㄤ綅缃弿杩�" prop="location">
+ <el-input v-model="form.location" placeholder="璇疯緭鍏ユ墍鍦ㄤ綅缃弿杩�" maxlength="30"/>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ </el-form>
+ <div>
+ <el-button type="primary" @click="submitForm">鐢熸垚骞舵墦鍗颁簩缁寸爜</el-button>
+ </div>
+ <div v-if="isShowQrCode" class="print-section" ref="qrCodeContainer" id="qrCodeContainer">
+ <vue-qrcode :value="qrCodeValue" :width="qrCodeSize"></vue-qrcode>
+ </div>
+ </el-dialog>
+ </div>
+</template>
+
+<script setup>
+import useUserStore from "@/store/modules/user.js";
+import {reactive, ref} from "vue";
+import printJS from 'print-js';
+import {addOrEditQrCode} from "@/api/inspectionUpload/index.js";
+
+const { proxy } = getCurrentInstance()
+const emit = defineEmits()
+const userStore = useUserStore()
+const dialogVisitable = ref(false);
+const isShowQrCode = ref(false);
+const operationType = ref('add');
+
+const qrCodeValue = ref('');
+const qrCodeSize = ref(100);
+const data = reactive({
+ form: {
+ deviceName: '',
+ location: '',
+ qrCodeId: '',
+ id: ''
+ },
+ rules: {
+ deviceName: [{ required: true, message: '璇疯緭鍏ヨ澶囧悕绉�', trigger: 'blur' }],
+ location: [{ required: true, message: '璇疯緭鍏ュ湴鐐�', trigger: 'blur' }]
+ }
+})
+const { form, rules } = toRefs(data)
+
+
+// 鎵撳紑寮规
+const openDialog = async (type, row) => {
+ dialogVisitable.value = true
+ qrCodeValue.value = ''
+ isShowQrCode.value = false;
+ if (type === 'edit') {
+ form.value.id = row.id
+ form.value.qrCodeId = row.id
+ form.value.deviceName = row.deviceName
+ form.value.location = row.location
+ // 灏嗚〃鍗曟暟鎹浆涓� JSON 瀛楃涓蹭綔涓轰簩缁寸爜鍐呭
+ qrCodeValue.value = JSON.stringify(form.value);
+ isShowQrCode.value = true;
+ }
+}
+// 鎻愪氦鍚堝苟琛ㄥ崟
+const submitForm = () => {
+ proxy.$refs["formRef"].validate(valid => {
+ if (valid) {
+ addOrEditQrCode(form.value).then((res) => {
+ form.value.qrCodeId = res.data
+ })
+ // 灏嗚〃鍗曟暟鎹浆涓� JSON 瀛楃涓蹭綔涓轰簩缁寸爜鍐呭
+ qrCodeValue.value = JSON.stringify(form.value);
+ isShowQrCode.value = true;
+ showQrCode()
+ }
+ })
+}
+const showQrCode = () => {
+ // 寤惰繜鎵ц鎵撳嵃锛岄伩鍏� DOM 鏇存柊鍓嶅氨璋冪敤鎵撳嵃
+ setTimeout(() => {
+ printJS({
+ printable: 'qrCodeContainer',//椤甸潰
+ type: "html",//鏂囨。绫诲瀷
+ maxWidth: 360,
+ style: `@page {
+ margin:0;
+ size: 400px 75px collapse;
+ margin-top:3px;
+ &:first-of-type{
+ margin-top:0 !important;
+ }
+ }
+ html{
+ zoom:100%;
+ }
+ @media print{
+ width: 400px;
+ height: 75px;
+ margin:0;
+ }`,
+ targetStyles: ["*"], // 浣跨敤dom鐨勬墍鏈夋牱寮忥紝寰堥噸瑕�
+ font_size: '0.20cm',
+ });
+ }, 300);
+}
+// 鍏抽棴鍚堝苟琛ㄥ崟
+const cancel = () => {
+ proxy.resetForm("formRef")
+ dialogVisitable.value = false
+ emit('closeDia')
+}
+defineExpose({ openDialog })
+</script>
+
+<style scoped>
+.print-section {
+ text-align: center;
+ margin-top: 30px;
+}
+</style>
\ No newline at end of file
diff --git a/src/views/inspectionManagement/components/viewFiles.vue b/src/views/inspectionManagement/components/viewFiles.vue
new file mode 100644
index 0000000..d1c9d8d
--- /dev/null
+++ b/src/views/inspectionManagement/components/viewFiles.vue
@@ -0,0 +1,246 @@
+<template>
+ <div>
+ <el-dialog title="鏌ョ湅闄勪欢"
+ v-model="dialogVisitable" width="800px" @close="cancel">
+ <div class="upload-container">
+ <!-- 鐢熶骇鍓� -->
+ <div class="form-container">
+ <div class="title">鐢熶骇鍓�</div>
+
+ <!-- 鍥剧墖鍒楄〃 -->
+ <div style="display: flex; flex-wrap: wrap;">
+ <img v-for="(item, index) in beforeProductionImgs" :key="index"
+ @click="showMedia(beforeProductionImgs, index, 'image')"
+ :src="item" style="max-width: 100px; height: 100px; margin: 5px;" alt="">
+ </div>
+
+ <!-- 瑙嗛鍒楄〃 -->
+ <div style="display: flex; flex-wrap: wrap;">
+ <div
+ v-for="(videoUrl, index) in beforeProductionVideos"
+ :key="index"
+ @click="showMedia(beforeProductionVideos, index, 'video')"
+ style="position: relative; margin: 10px; cursor: pointer;"
+ >
+ <div style="width: 160px; height: 90px; background-color: #333; display: flex; align-items: center; justify-content: center;">
+ <img src="@/assets/images/video.png" alt="鎾斁" style="width: 30px; height: 30px; opacity: 0.8;" />
+ </div>
+ <div style="text-align: center; font-size: 12px; color: #666;">鐐瑰嚮鎾斁</div>
+ </div>
+ </div>
+ </div>
+
+ <!-- 鐢熶骇鍚� -->
+ <div class="form-container">
+ <div class="title">鐢熶骇鍚�</div>
+
+ <!-- 鍥剧墖鍒楄〃 -->
+ <div style="display: flex; flex-wrap: wrap;">
+ <img v-for="(item, index) in afterProductionImgs" :key="index"
+ @click="showMedia(afterProductionImgs, index, 'image')"
+ :src="item" style="max-width: 100px; height: 100px; margin: 5px;" alt="">
+ </div>
+
+ <!-- 瑙嗛鍒楄〃 -->
+ <div style="display: flex; flex-wrap: wrap;">
+ <div
+ v-for="(videoUrl, index) in afterProductionVideos"
+ :key="index"
+ @click="showMedia(afterProductionVideos, index, 'video')"
+ style="position: relative; margin: 10px; cursor: pointer;"
+ >
+ <div style="width: 160px; height: 90px; background-color: #333; display: flex; align-items: center; justify-content: center;">
+ <img src="@/assets/images/video.png" alt="鎾斁" style="width: 30px; height: 30px; opacity: 0.8;" />
+ </div>
+ <div style="text-align: center; font-size: 12px; color: #666;">鐐瑰嚮鎾斁</div>
+ </div>
+ </div>
+ </div>
+
+ <!-- 鐢熶骇闂 -->
+ <div class="form-container">
+ <div class="title">鐢熶骇闂</div>
+
+ <!-- 鍥剧墖鍒楄〃 -->
+ <div style="display: flex; flex-wrap: wrap;">
+ <img v-for="(item, index) in productionIssuesImgs" :key="index"
+ @click="showMedia(productionIssuesImgs, index, 'image')"
+ :src="item" style="max-width: 100px; height: 100px; margin: 5px;" alt="">
+ </div>
+
+ <!-- 瑙嗛鍒楄〃 -->
+ <div style="display: flex; flex-wrap: wrap;">
+ <div
+ v-for="(videoUrl, index) in productionIssuesVideos"
+ :key="index"
+ @click="showMedia(productionIssuesVideos, index, 'video')"
+ style="position: relative; margin: 10px; cursor: pointer;"
+ >
+ <div style="width: 160px; height: 90px; background-color: #333; display: flex; align-items: center; justify-content: center;">
+ <img src="@/assets/images/video.png" alt="鎾斁" style="width: 30px; height: 30px; opacity: 0.8;" />
+ </div>
+ <div style="text-align: center; font-size: 12px; color: #666;">鐐瑰嚮鎾斁</div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </el-dialog>
+
+ <!-- 缁熶竴濯掍綋鏌ョ湅鍣� -->
+ <div v-if="isMediaViewerVisible" class="media-viewer-overlay" @click.self="closeMediaViewer">
+ <div class="media-viewer-content" @click.stop>
+ <!-- 鍥剧墖 -->
+ <vue-easy-lightbox
+ v-if="mediaType === 'image'"
+ :visible="isMediaViewerVisible"
+ :imgs="mediaList"
+ :index="currentMediaIndex"
+ @hide="closeMediaViewer"
+ ></vue-easy-lightbox>
+
+ <!-- 瑙嗛 -->
+ <div v-else-if="mediaType === 'video'" style="position: relative;">
+ <Video
+ :src="mediaList[currentMediaIndex]"
+ autoplay
+ controls
+ style="max-width: 90vw; max-height: 80vh;"
+ />
+ </div>
+ </div>
+ </div>
+ </div>
+</template>
+<script setup>
+import { ref } from 'vue';
+import VueEasyLightbox from 'vue-easy-lightbox';
+
+// 鎺у埗寮圭獥鏄剧ず
+const dialogVisitable = ref(false);
+
+// 鍥剧墖鏁扮粍
+const beforeProductionImgs = ref([]);
+const afterProductionImgs = ref([]);
+const productionIssuesImgs = ref([]);
+
+// 瑙嗛鏁扮粍
+const beforeProductionVideos = ref([]);
+const afterProductionVideos = ref([]);
+const productionIssuesVideos = ref([]);
+
+// 濯掍綋鏌ョ湅鍣ㄧ姸鎬�
+const isMediaViewerVisible = ref(false);
+const currentMediaIndex = ref(0);
+const mediaList = ref([]); // 瀛樺偍褰撳墠瑕佹煡鐪嬬殑濯掍綋鍒楄〃锛堝惈鍥剧墖鍜岃棰戝璞★級
+const mediaType = ref('image'); // image | video
+
+// 澶勭悊姣忎竴绫绘暟鎹細鍒嗙鍥剧墖鍜岃棰�
+function processItems(items) {
+ const images = [];
+ const videos = [];
+ items.forEach(item => {
+ if (item.contentType?.startsWith('image/')) {
+ images.push(item.url);
+ } else if (item.contentType?.startsWith('video/')) {
+ videos.push(item.url);
+ }
+ });
+ return { images, videos };
+}
+
+// 鎵撳紑寮圭獥骞跺姞杞芥暟鎹�
+const openDialog = async (row) => {
+ const { images: beforeImgs, videos: beforeVids } = processItems(row.beforeProduction);
+ const { images: afterImgs, videos: afterVids } = processItems(row.afterProduction);
+ const { images: issueImgs, videos: issueVids } = processItems(row.productionIssues);
+
+ beforeProductionImgs.value = beforeImgs;
+ beforeProductionVideos.value = beforeVids;
+
+ afterProductionImgs.value = afterImgs;
+ afterProductionVideos.value = afterVids;
+
+ productionIssuesImgs.value = issueImgs;
+ productionIssuesVideos.value = issueVids;
+
+ dialogVisitable.value = true;
+};
+
+// 鏄剧ず濯掍綋锛堝浘鐗� or 瑙嗛锛�
+function showMedia(mediaArray, index, type) {
+ mediaList.value = mediaArray;
+ currentMediaIndex.value = index;
+ mediaType.value = type;
+ isMediaViewerVisible.value = true;
+}
+
+// 鍏抽棴濯掍綋鏌ョ湅鍣�
+function closeMediaViewer() {
+ isMediaViewerVisible.value = false;
+ mediaList.value = [];
+ mediaType.value = 'image';
+}
+
+// 琛ㄥ崟鍏抽棴鏂规硶
+const cancel = () => {
+ dialogVisitable.value = false;
+};
+
+defineExpose({ openDialog });
+</script>
+<style scoped lang="scss">
+.upload-container {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding: 20px;
+ border: 1px solid #dcdfe6;
+ box-sizing: border-box;
+
+ .form-container {
+ flex: 1;
+ width: 100%;
+ margin-bottom: 20px;
+ }
+}
+
+.title {
+ font-size: 14px;
+ color: #165dff;
+ line-height: 20px;
+ font-weight: 600;
+ padding-left: 10px;
+ position: relative;
+ margin: 6px 0;
+
+ &::before {
+ content: "";
+ position: absolute;
+ left: 0;
+ top: 3px;
+ width: 4px;
+ height: 14px;
+ background-color: #165dff;
+ }
+}
+
+.media-viewer-overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-color: rgba(0, 0, 0, 0.8);
+ z-index: 9999;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.media-viewer-content {
+ position: relative;
+ max-width: 90vw;
+ max-height: 90vh;
+ overflow: hidden;
+}
+</style>
\ No newline at end of file
diff --git a/src/views/inspectionManagement/components/viewQrCodeFiles.vue b/src/views/inspectionManagement/components/viewQrCodeFiles.vue
new file mode 100644
index 0000000..f8e923a
--- /dev/null
+++ b/src/views/inspectionManagement/components/viewQrCodeFiles.vue
@@ -0,0 +1,169 @@
+<template>
+ <div>
+ <el-dialog title="鏌ョ湅闄勪欢"
+ v-model="dialogVisitable" width="800px" @close="cancel">
+ <div class="upload-container">
+ <div class="form-container">
+ <div class="title">宸℃闄勪欢</div>
+ <!-- 鍥剧墖鍒楄〃 -->
+ <div style="display: flex; flex-wrap: wrap;">
+ <img v-for="(item, index) in beforeProductionImgs" :key="index"
+ @click="showMedia(beforeProductionImgs, index, 'image')"
+ :src="item" style="max-width: 100px; height: 100px; margin: 5px;" alt="">
+ </div>
+
+ <!-- 瑙嗛鍒楄〃 -->
+ <div style="display: flex; flex-wrap: wrap;">
+ <div
+ v-for="(videoUrl, index) in beforeProductionVideos"
+ :key="index"
+ @click="showMedia(beforeProductionVideos, index, 'video')"
+ style="position: relative; margin: 10px; cursor: pointer;"
+ >
+ <div style="width: 160px; height: 90px; background-color: #333; display: flex; align-items: center; justify-content: center;">
+ <img src="@/assets/images/video.png" alt="鎾斁" style="width: 30px; height: 30px; opacity: 0.8;" />
+ </div>
+ <div style="text-align: center; font-size: 12px; color: #666;">鐐瑰嚮鎾斁</div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </el-dialog>
+ <!-- 缁熶竴濯掍綋鏌ョ湅鍣� -->
+ <div v-if="isMediaViewerVisible" class="media-viewer-overlay" @click.self="closeMediaViewer">
+ <div class="media-viewer-content" @click.stop>
+ <!-- 鍥剧墖 -->
+ <vue-easy-lightbox
+ v-if="mediaType === 'image'"
+ :visible="isMediaViewerVisible"
+ :imgs="mediaList"
+ :index="currentMediaIndex"
+ @hide="closeMediaViewer"
+ ></vue-easy-lightbox>
+
+ <!-- 瑙嗛 -->
+ <div v-else-if="mediaType === 'video'" style="position: relative;">
+ <Video
+ :src="mediaList[currentMediaIndex]"
+ autoplay
+ controls
+ style="max-width: 90vw; max-height: 80vh;"
+ />
+ </div>
+ </div>
+ </div>
+ </div>
+</template>
+
+<script setup>
+// 鎺у埗寮圭獥鏄剧ず
+import VueEasyLightbox from "vue-easy-lightbox";
+
+const dialogVisitable = ref(false);
+// 鍥剧墖鏁扮粍
+const beforeProductionImgs = ref([]);
+// 瑙嗛鏁扮粍
+const beforeProductionVideos = ref([]);
+// 濯掍綋鏌ョ湅鍣ㄧ姸鎬�
+const isMediaViewerVisible = ref(false);
+const currentMediaIndex = ref(0);
+const mediaList = ref([]); // 瀛樺偍褰撳墠瑕佹煡鐪嬬殑濯掍綋鍒楄〃锛堝惈鍥剧墖鍜岃棰戝璞★級
+const mediaType = ref('image'); // image | video
+
+// 鎵撳紑寮圭獥骞跺姞杞芥暟鎹�
+const openDialog = async (row) => {
+ const { images: beforeImgs, videos: beforeVids } = processItems(row.storageBlobDTO);
+
+ beforeProductionImgs.value = beforeImgs;
+ beforeProductionVideos.value = beforeVids;
+ dialogVisitable.value = true;
+};
+// 鏄剧ず濯掍綋锛堝浘鐗� or 瑙嗛锛�
+function showMedia(mediaArray, index, type) {
+ mediaList.value = mediaArray;
+ currentMediaIndex.value = index;
+ mediaType.value = type;
+ isMediaViewerVisible.value = true;
+}
+// 鍏抽棴濯掍綋鏌ョ湅鍣�
+function closeMediaViewer() {
+ isMediaViewerVisible.value = false;
+ mediaList.value = [];
+ mediaType.value = 'image';
+}
+// 琛ㄥ崟鍏抽棴鏂规硶
+const cancel = () => {
+ dialogVisitable.value = false;
+};
+// 澶勭悊姣忎竴绫绘暟鎹細鍒嗙鍥剧墖鍜岃棰�
+function processItems(items) {
+ const images = [];
+ const videos = [];
+ items.forEach(item => {
+ if (item.contentType?.startsWith('image/')) {
+ images.push(item.url);
+ } else if (item.contentType?.startsWith('video/')) {
+ videos.push(item.url);
+ }
+ });
+ return { images, videos };
+}
+defineExpose({ openDialog });
+</script>
+
+<style scoped lang="scss">
+.upload-container {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding: 20px;
+ border: 1px solid #dcdfe6;
+ box-sizing: border-box;
+
+ .form-container {
+ flex: 1;
+ width: 100%;
+ margin-bottom: 20px;
+ }
+}
+
+.title {
+ font-size: 14px;
+ color: #165dff;
+ line-height: 20px;
+ font-weight: 600;
+ padding-left: 10px;
+ position: relative;
+ margin: 6px 0;
+
+ &::before {
+ content: "";
+ position: absolute;
+ left: 0;
+ top: 3px;
+ width: 4px;
+ height: 14px;
+ background-color: #165dff;
+ }
+}
+
+.media-viewer-overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-color: rgba(0, 0, 0, 0.8);
+ z-index: 9999;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.media-viewer-content {
+ position: relative;
+ max-width: 90vw;
+ max-height: 90vh;
+ overflow: hidden;
+}
+</style>
\ No newline at end of file
diff --git a/src/views/inspectionManagement/index.vue b/src/views/inspectionManagement/index.vue
new file mode 100644
index 0000000..7e5edc0
--- /dev/null
+++ b/src/views/inspectionManagement/index.vue
@@ -0,0 +1,391 @@
+<template>
+ <div class="app-container">
+ <el-form :inline="true" :model="queryParams" class="search-form">
+ <el-form-item label="鎼滅储">
+ <el-input
+ v-model="queryParams.searchAll"
+ placeholder="璇疯緭鍏ュ叧閿瓧"
+ clearable
+ :style="{ width: '100%' }"
+ />
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" @click="handleQuery">鏌ヨ</el-button>
+ <el-button @click="resetQuery">閲嶇疆</el-button>
+ </el-form-item>
+ </el-form>
+ <el-card>
+ <!-- 鏍囩椤� -->
+ <el-tabs
+ v-model="activeTab"
+ class="info-tabs"
+ @tab-click="handleTabClick"
+ >
+ <el-tab-pane
+ v-for="tab in tabs"
+ :key="tab.name"
+ :label="tab.label"
+ :name="tab.name"
+ />
+ </el-tabs>
+ <div style="display: flex;flex-direction: row;justify-content: space-between;" v-if="tabName === 'task'">
+ <el-radio-group v-model="activeRadio" @change="radioChange">
+ <el-radio-button v-for="tab in radios"
+ :key="tab.name"
+ :label="tab.label"
+ :value="tab.name"/>
+ </el-radio-group>
+ <!-- 鎿嶄綔鎸夐挳鍖� -->
+ <el-space v-if="activeRadio !== 'task'">
+ <el-button type="primary" :icon="Plus" @click="handleAdd(undefined)">鏂板缓</el-button>
+ <el-button type="danger" :icon="Delete" @click="handleDelete">鍒犻櫎</el-button>
+ <!-- <el-button type="info" plain :icon="Download">瀵煎嚭</el-button> -->
+ </el-space>
+ </div>
+ <div>
+ <div>
+ <ETable :loading="tableLoading"
+ :table-data="tableData"
+ :columns="tableColumns"
+ @selection-change="handleSelectionChange"
+ :show-selection="true"
+ :border="true"
+ style="width: 100%;height: calc(100vh - 30em)"
+ operationsWidth="130"
+ :operations="operationsArr"
+ @edit="handleAdd"
+ @viewFile="viewFile"
+ v-if="tabName === 'task'"
+ >
+ <template #inspector="{ row }">
+ <div class="person-tags">
+ <!-- 璋冭瘯淇℃伅锛屼笂绾挎椂鍒犻櫎 -->
+ <!-- {{ console.log('inspector data:', row.inspector) }} -->
+ <template v-if="row.inspector && row.inspector.length > 0">
+ <el-tag
+ v-for="(person, index) in row.inspector"
+ :key="index"
+ size="small"
+ type="primary"
+ class="person-tag"
+ >
+ {{ person }}
+ </el-tag>
+ </template>
+ <span v-else class="no-data">--</span>
+ </div>
+ </template>
+ </ETable>
+ <el-table ref="table" :data="tableData" height="480" v-loading="tableLoading" border v-else style="width: 100%;height: calc(100vh - 25em)">
+ <el-table-column label="搴忓彿" type="index" width="60" align="center" />
+ <el-table-column prop="deviceName" label="璁惧鍚嶇О" :show-overflow-tooltip="true">
+ <template #default="scope">
+ {{scope.row.qrCode.deviceName}}
+ </template>
+ </el-table-column>
+ <el-table-column prop="location" label="鎵�鍦ㄤ綅缃弿杩�" :show-overflow-tooltip="true">
+ <template #default="scope">
+ {{scope.row.qrCode.location}}
+ </template>
+ </el-table-column>
+ <el-table-column prop="scanner" label="宸℃浜�"></el-table-column>
+ <el-table-column prop="scanTime" label="宸℃鏃堕棿"></el-table-column>
+ <el-table-column fixed="right" label="鎿嶄綔">
+ <template #default="scope">
+ <el-button link type="primary" @click="handleAdd(scope.row)">鏌ョ湅闄勪欢</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ </div>
+ <pagination
+ v-if="total>0"
+ :page="pageNum"
+ :limit="pageSize"
+ :total="total"
+ @pagination="handlePagination"
+ :layout="'total, prev, pager, next, jumper'"
+ />
+ </div>
+ </el-card>
+ <form-dia ref="formDia" @closeDia="handleQuery"></form-dia>
+ <qr-code-dia ref="qrCodeDia" @closeDia="handleQuery"></qr-code-dia>
+ <view-files ref="viewFiles"></view-files>
+ <view-qr-code-files ref="viewQrCodeFiles"></view-qr-code-files>
+ </div>
+</template>
+
+<script setup>
+import { Delete, Plus } from "@element-plus/icons-vue";
+import { onMounted, ref, reactive, getCurrentInstance, nextTick } from "vue";
+
+// 缁勪欢寮曞叆
+import Pagination from "@/components/Pagination/index.vue";
+import ETable from "@/components/Table/ETable.vue";
+import FormDia from "@/views/inspectionManagement/components/formDia.vue";
+import QrCodeDia from "@/views/inspectionManagement/components/qrCodeDia.vue";
+import ViewFiles from "@/views/inspectionManagement/components/viewFiles.vue";
+import ViewQrCodeFiles from "@/views/inspectionManagement/components/viewQrCodeFiles.vue";
+
+// 鎺ュ彛寮曞叆
+import {
+ delTimingTask,
+ inspectionTaskList,
+ timingTaskList
+} from "@/api/inspectionManagement/index.js";
+import {
+ delQrCode,
+ qrCodeList,
+ qrCodeScanRecordList
+} from "@/api/inspectionUpload/index.js";
+
+// 鍏ㄥ眬鍙橀噺
+const { proxy } = getCurrentInstance();
+const formDia = ref();
+const qrCodeDia = ref();
+const viewFiles = ref();
+const viewQrCodeFiles = ref();
+
+// 鏌ヨ鍙傛暟
+const queryParams = reactive({
+ searchAll: "",
+});
+
+// 鏍囩椤甸厤缃�
+const activeTab = ref("task");
+const tabName = ref("task");
+const tabs = reactive([
+ { name: "task", label: "鐢熶骇宸℃" },
+ { name: "qrCodeScanRecord", label: "鐜板満宸℃璁板綍" },
+]);
+
+// 鍗曢�夋閰嶇疆
+const activeRadio = ref("taskManage");
+const radios = reactive([
+ { name: "taskManage", label: "瀹氭椂浠诲姟绠$悊" },
+ { name: "task", label: "瀹氭椂浠诲姟璁板綍" },
+ { name: "qrCode", label: "浜岀淮鐮佺鐞�" },
+]);
+
+// 琛ㄦ牸鏁版嵁
+const selectedRows = ref([]);
+const tableData = ref([]);
+const operationsArr = ref([]);
+const tableColumns = ref([]);
+const tableLoading = ref(false);
+const total = ref(0);
+const pageNum = ref(1);
+const pageSize = ref(10);
+
+// 鍒楅厤缃�
+const columns = ref([
+ { prop: "taskName", label: "宸℃浠诲姟鍚嶇О", minWidth: 160 },
+ { prop: "inspectionLocation", label: "鍦扮偣", minWidth: 120 },
+ { prop: "remarks", label: "澶囨敞", minWidth: 150 },
+ { prop: "inspector", label: "鎵ц宸℃浜�", minWidth: 150, slot: "inspector" },
+ {
+ prop: "frequencyType",
+ label: "棰戞",
+ minWidth: 150,
+ formatter: (_, __, val) => ({
+ DAILY: "姣忔棩",
+ WEEKLY: "姣忓懆",
+ MONTHLY: "姣忔湀",
+ QUARTERLY: "瀛e害"
+ }[val] || "")
+ },
+ {
+ prop: "frequencyDetail",
+ label: "寮�濮嬫棩鏈熶笌鏃堕棿",
+ minWidth: 150,
+ formatter: (row, column, cellValue) => {
+ // 鍏堝垽鏂槸鍚︽槸瀛楃涓�
+ if (typeof cellValue !== 'string') return '';
+ let val = cellValue;
+ const replacements = {
+ MON: '鍛ㄤ竴',
+ TUE: '鍛ㄤ簩',
+ WED: '鍛ㄤ笁',
+ THU: '鍛ㄥ洓',
+ FRI: '鍛ㄤ簲',
+ SAT: '鍛ㄥ叚',
+ SUN: '鍛ㄦ棩'
+ };
+ // 浣跨敤姝e垯涓�娆℃�ф浛鎹㈡墍鏈夊尮閰嶉」
+ return val.replace(/MON|TUE|WED|THU|FRI|SAT|SUN/g, match => replacements[match]);
+ }
+ },
+ { prop: "registrant", label: "鐧昏浜�", minWidth: 100 },
+ { prop: "createTime", label: "鐧昏鏃ユ湡", minWidth: 100 },
+]);
+
+const columns1 = ref([
+ { prop: "deviceName", label: "璁惧鍚嶇О", minWidth: 160 },
+ { prop: "location", label: "鎵�鍦ㄤ綅缃弿杩�", minWidth: 120 },
+ { prop: "createBy", label: "鍒涘缓鑰�", minWidth: 100 },
+ { prop: "createTime", label: "鍒涘缓鏃堕棿", minWidth: 100 },
+]);
+
+onMounted(() => {
+ radioChange('taskManage');
+});
+
+// 鏍囩椤电偣鍑讳簨浠�
+const handleTabClick = (tab) => {
+ tabName.value = tab.props.name;
+ tableData.value = [];
+ getList();
+};
+
+// 鍗曢�夊彉鍖�
+const radioChange = (value) => {
+ if (value === "taskManage") {
+ tableColumns.value = columns.value;
+ operationsArr.value = ['edit'];
+ } else if (value === "task") {
+ tableColumns.value = columns.value;
+ operationsArr.value = ['viewFile'];
+ } else {
+ tableColumns.value = columns1.value;
+ operationsArr.value = ['edit'];
+ }
+ pageNum.value = 1;
+ pageSize.value = 10;
+ getList();
+};
+
+// 鏌ヨ鎿嶄綔
+const handleQuery = () => {
+ pageNum.value = 1;
+ pageSize.value = 10;
+ getList();
+};
+// 鍒嗛〉澶勭悊
+const handlePagination = (val) => {
+ pageNum.value = val.page;
+ pageSize.value = val.limit;
+ getList();
+};
+// 鑾峰彇鍒楄〃鏁版嵁
+const getList = () => {
+ tableLoading.value = true;
+
+ const params = { ...queryParams, size: pageSize.value, current: pageNum.value };
+
+ let apiCall;
+ if (tabName.value === 'task') {
+ switch (activeRadio.value) {
+ case "task":
+ apiCall = inspectionTaskList(params);
+ break;
+ case "qrCode":
+ apiCall = qrCodeList(params);
+ break;
+ default:
+ apiCall = timingTaskList(params);
+ }
+ } else {
+ apiCall = qrCodeScanRecordList(params);
+ }
+
+ apiCall.then(res => {
+ const rawData = res.data.records || [];
+ // 澶勭悊 inspector 瀛楁锛屽皢瀛楃涓茶浆鎹负鏁扮粍锛堥�傜敤浜庢墍鏈夋儏鍐碉級
+ tableData.value = rawData.map(item => {
+ const processedItem = { ...item };
+
+ // 澶勭悊 inspector 瀛楁
+ if (processedItem.inspector) {
+ if (typeof processedItem.inspector === 'string') {
+ // 瀛楃涓叉寜閫楀彿鍒嗗壊
+ processedItem.inspector = processedItem.inspector.split(',').map(s => s.trim()).filter(s => s);
+ } else if (!Array.isArray(processedItem.inspector)) {
+ // 闈炴暟缁勮浆涓烘暟缁�
+ processedItem.inspector = [processedItem.inspector];
+ }
+ } else {
+ // 绌哄�艰涓虹┖鏁扮粍
+ processedItem.inspector = [];
+ }
+
+ return processedItem;
+ });
+ total.value = res.data.total || 0;
+ }).finally(() => {
+ tableLoading.value = false;
+ });
+};
+
+// 閲嶇疆鏌ヨ
+const resetQuery = () => {
+ for (const key in queryParams) {
+ if (!["pageNum", "pageSize"].includes(key)) {
+ queryParams[key] = "";
+ }
+ }
+ handleQuery();
+};
+
+// 鏂板 / 缂栬緫
+const handleAdd = (row) => {
+ const type = row ? 'edit' : 'add';
+ nextTick(() => {
+ if (tabName.value === 'task') {
+ if (activeRadio.value === "taskManage") {
+ formDia.value?.openDialog(type, row);
+ } else if (activeRadio.value === "qrCode") {
+ qrCodeDia.value?.openDialog(type, row);
+ }
+ } else {
+ viewQrCodeFiles.value?.openDialog(row);
+ }
+ });
+};
+
+// 鏌ョ湅闄勪欢
+const viewFile = (row) => {
+ nextTick(() => {
+ viewFiles.value?.openDialog(row);
+ });
+};
+
+// 鍒犻櫎鎿嶄綔
+const handleDelete = () => {
+ if (!selectedRows.value.length) {
+ proxy.$modal.msgWarning("璇烽�夋嫨瑕佸垹闄ょ殑鏁版嵁");
+ return;
+ }
+
+ const deleteIds = selectedRows.value.map(item => item.id);
+ const api = activeRadio.value === "taskManage" ? delTimingTask : delQrCode;
+
+ proxy.$modal.confirm('鏄惁纭鍒犻櫎鎵�閫夋暟鎹」锛�').then(() => {
+ return api(deleteIds);
+ }).then(() => {
+ proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+ handleQuery();
+ }).catch(() => {});
+};
+
+// 澶氶�夊彉鏇�
+const handleSelectionChange = (selection) => {
+ selectedRows.value = selection;
+};
+</script>
+
+<style scoped>
+.person-tags {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 4px;
+}
+
+.person-tag {
+ margin-right: 4px;
+ margin-bottom: 2px;
+}
+
+.no-data {
+ color: #909399;
+ font-size: 14px;
+}
+</style>
\ No newline at end of file
diff --git a/src/views/procurementManagement/advancedPriceManagement/index.vue b/src/views/procurementManagement/advancedPriceManagement/index.vue
new file mode 100644
index 0000000..89fbc4b
--- /dev/null
+++ b/src/views/procurementManagement/advancedPriceManagement/index.vue
@@ -0,0 +1,874 @@
+<template>
+ <div class="app-container">
+ <!-- 鎼滅储鍖哄煙 -->
+ <el-card class="search-card" shadow="never">
+ <el-form :model="searchForm" :inline="true" label-width="100px">
+ <el-form-item label="鍟嗗搧鍚嶇О锛�">
+ <el-input v-model="searchForm.productName" placeholder="璇疯緭鍏ュ晢鍝佸悕绉�" clearable style="width: 200px" />
+ </el-form-item>
+ <el-form-item label="渚涘簲鍟嗭細">
+ <el-select v-model="searchForm.supplierId" placeholder="璇烽�夋嫨渚涘簲鍟�" clearable style="width: 200px">
+ <el-option v-for="supplier in supplierList" :key="supplier.id" :label="supplier.name" :value="supplier.id" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="浠锋牸鐘舵�侊細">
+ <el-select v-model="searchForm.priceStatus" placeholder="璇烽�夋嫨鐘舵��" clearable style="width: 150px">
+ <el-option label="鏈夋晥" value="active" />
+ <el-option label="寰呯敓鏁�" value="pending" />
+ <el-option label="宸茶繃鏈�" value="expired" />
+ <el-option label="宸叉殏鍋�" value="suspended" />
+ </el-select>
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" @click="handleSearch" :loading="loading">
+ <el-icon><Search /></el-icon>
+ 鎼滅储
+ </el-button>
+ <el-button @click="resetSearch">
+ <el-icon><Refresh /></el-icon>
+ 閲嶇疆
+ </el-button>
+ </el-form-item>
+ </el-form>
+ </el-card>
+
+ <!-- 鍔熻兘鎸夐挳鍖哄煙 -->
+ <el-card class="action-card" shadow="never">
+ <div class="action-buttons">
+ <el-button type="primary" @click="openDialog('add')">
+ <el-icon><Plus /></el-icon>
+ 鏂板浠锋牸
+ </el-button>
+ <el-button type="success" @click="openBatchDiscountDialog">
+ <el-icon><Discount /></el-icon>
+ 鎵归噺鎶樻墸
+ </el-button>
+ <el-button type="warning" @click="openPriceControlDialog">
+ <el-icon><Setting /></el-icon>
+ 浠锋牸鎺у埗
+ </el-button>
+ <el-button type="info" @click="exportData">
+ <el-icon><Download /></el-icon>
+ 瀵煎嚭鏁版嵁
+ </el-button>
+ <el-button type="danger" @click="handleBatchDelete" :disabled="selectedRows.length === 0">
+ <el-icon><Delete /></el-icon>
+ 鎵归噺鍒犻櫎
+ </el-button>
+ </div>
+ </el-card>
+
+
+ <!-- 涓昏〃鏍� -->
+ <el-card class="table-card" shadow="never">
+ <el-table
+ :data="tableData"
+ border
+ v-loading="loading"
+ @selection-change="handleSelectionChange"
+ :default-sort="{ prop: 'updateTime', order: 'descending' }"
+ >
+ <el-table-column type="selection" width="55" align="center" />
+ <el-table-column label="鍟嗗搧淇℃伅" min-width="200">
+ <template #default="{ row }">
+ <div class="product-info">
+ <div class="product-name">{{ row.productName }}</div>
+ <div class="product-spec">{{ row.specification }}</div>
+ <div class="product-code">缂栫爜: {{ row.productCode }}</div>
+ </div>
+ </template>
+ </el-table-column>
+ <el-table-column label="渚涘簲鍟�" prop="supplierName" width="150" />
+ <el-table-column label="鍩虹浠锋牸" width="120" align="right">
+ <template #default="{ row }">
+ <span class="price-text">楼{{ row.basePrice.toFixed(2) }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="鎶樻墸淇℃伅" width="150">
+ <template #default="{ row }">
+ <div v-if="row.discountType">
+ <el-tag :type="getDiscountTagType(row.discountType)" size="small">
+ {{ getDiscountText(row.discountType) }}
+ </el-tag>
+ <div class="discount-value">{{ row.discountValue }}{{ row.discountType === 'percentage' ? '%' : '鍏�' }}</div>
+ </div>
+ <span v-else class="no-discount">鏃犳姌鎵�</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="瀹為檯浠锋牸" width="120" align="right">
+ <template #default="{ row }">
+ <span class="final-price">楼{{ calculateFinalPrice(row).toFixed(2) }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="浠锋牸鎺у埗" width="120">
+ <template #default="{ row }">
+ <div class="price-control">
+ <div v-if="row.priceControl?.minPrice" class="control-item">
+ 鏈�浣�: 楼{{ row.priceControl.minPrice.toFixed(2) }}
+ </div>
+ <div v-if="row.priceControl?.maxPrice" class="control-item">
+ 鏈�楂�: 楼{{ row.priceControl.maxPrice.toFixed(2) }}
+ </div>
+ </div>
+ </template>
+ </el-table-column>
+ <el-table-column label="鐘舵��" width="100" align="center">
+ <template #default="{ row }">
+ <el-tag :type="getStatusType(row.status)">{{ getStatusText(row.status) }}</el-tag>
+ <div v-if="isPriceWarning(row)" class="warning-indicator">
+ <el-icon color="#F56C6C"><Warning /></el-icon>
+ </div>
+ </template>
+ </el-table-column>
+ <el-table-column label="鐢熸晥鏃堕棿" prop="effectiveTime" width="180" />
+ <el-table-column label="鏇存柊鏃堕棿" prop="updateTime" width="180" sortable />
+ <el-table-column label="鎿嶄綔" width="250" align="center" fixed="right">
+ <template #default="{ row }">
+ <el-button type="primary" link @click="openDialog('edit', row)">
+ <el-icon><Edit /></el-icon>
+ 缂栬緫
+ </el-button>
+ <el-button type="success" link @click="openDiscountDialog(row)">
+ <el-icon><Discount /></el-icon>
+ 鎶樻墸
+ </el-button>
+ <el-button type="danger" link @click="handleDelete(row)">
+ <el-icon><Delete /></el-icon>
+ 鍒犻櫎
+ </el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+
+ <!-- 鍒嗛〉 -->
+ <div class="pagination-wrapper">
+ <el-pagination
+ v-model:current-page="pagination.currentPage"
+ v-model:page-size="pagination.pageSize"
+ :page-sizes="[10, 20, 50, 100]"
+ :total="pagination.total"
+ layout="total, sizes, prev, pager, next, jumper"
+ @size-change="handleSizeChange"
+ @current-change="handleCurrentChange"
+ />
+ </div>
+ </el-card>
+
+ <!-- 鏂板/缂栬緫瀵硅瘽妗� -->
+ <el-dialog v-model="dialogVisible" :title="dialogType === 'add' ? '鏂板浠锋牸' : '缂栬緫浠锋牸'" width="800px">
+ <el-form :model="formData" :rules="formRules" ref="formRef" label-width="120px">
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="鍟嗗搧鍚嶇О" prop="productName">
+ <el-select v-model="formData.productName" placeholder="璇烽�夋嫨鍟嗗搧" style="width: 100%" filterable>
+ <el-option v-for="product in productList" :key="product.id" :label="product.name" :value="product.name" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鍟嗗搧缂栫爜" prop="productCode">
+ <el-input v-model="formData.productCode" placeholder="璇疯緭鍏ュ晢鍝佺紪鐮�" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="瑙勬牸鍨嬪彿" prop="specification">
+ <el-input v-model="formData.specification" placeholder="璇疯緭鍏ヨ鏍煎瀷鍙�" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="渚涘簲鍟�" prop="supplierName">
+ <el-select v-model="formData.supplierName" placeholder="璇烽�夋嫨渚涘簲鍟�" style="width: 100%">
+ <el-option v-for="supplier in supplierList" :key="supplier.id" :label="supplier.name" :value="supplier.name" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="鍩虹浠锋牸" prop="basePrice">
+ <el-input-number v-model="formData.basePrice" :min="0" :precision="2" placeholder="璇疯緭鍏ュ熀纭�浠锋牸" style="width: 100%" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鍗曚綅">
+ <el-input v-model="formData.unit" placeholder="璇疯緭鍏ュ崟浣�" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+
+ <!-- 鎶樻墸璁剧疆 -->
+ <el-divider content-position="left">鎶樻墸璁剧疆</el-divider>
+ <el-row :gutter="20">
+ <el-col :span="8">
+ <el-form-item label="鎶樻墸绫诲瀷">
+ <el-select v-model="formData.discountType" placeholder="璇烽�夋嫨鎶樻墸绫诲瀷" style="width: 100%">
+ <el-option label="鏃犳姌鎵�" value="" />
+ <el-option label="鐧惧垎姣旀姌鎵�" value="percentage" />
+ <el-option label="鍥哄畾閲戦" value="fixed" />
+ <el-option label="闃舵鎶樻墸" value="tiered" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="8">
+ <el-form-item label="鎶樻墸鍊�" v-if="formData.discountType && formData.discountType !== 'tiered'">
+ <el-input-number
+ v-model="formData.discountValue"
+ :min="0"
+ :max="formData.discountType === 'percentage' ? 100 : undefined"
+ :precision="2"
+ placeholder="璇疯緭鍏ユ姌鎵e��"
+ style="width: 100%"
+ />
+ </el-form-item>
+ </el-col>
+ <el-col :span="8">
+ <el-form-item label="鎶樻墸鏈夋晥鏈�">
+ <el-date-picker
+ v-model="formData.discountEndTime"
+ type="datetime"
+ placeholder="閫夋嫨缁撴潫鏃堕棿"
+ style="width: 100%"
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+
+ <!-- 闃舵鎶樻墸璁剧疆 -->
+ <div v-if="formData.discountType === 'tiered'">
+ <el-form-item label="闃舵鎶樻墸">
+ <el-table :data="formData.tieredDiscount" border size="small">
+ <el-table-column label="鏈�灏忔暟閲�" width="120">
+ <template #default="{ row, $index }">
+ <el-input-number v-model="row.minQty" :min="0" size="small" />
+ </template>
+ </el-table-column>
+ <el-table-column label="鏈�澶ф暟閲�" width="120">
+ <template #default="{ row, $index }">
+ <el-input-number v-model="row.maxQty" :min="0" size="small" />
+ </template>
+ </el-table-column>
+ <el-table-column label="鎶樻墸鐜�(%)" width="120">
+ <template #default="{ row, $index }">
+ <el-input-number v-model="row.discount" :min="0" :max="100" :precision="2" size="small" />
+ </template>
+ </el-table-column>
+ <el-table-column label="鎿嶄綔" width="80">
+ <template #default="{ row, $index }">
+ <el-button type="danger" link @click="removeTieredRow($index)">鍒犻櫎</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ <el-button type="primary" link @click="addTieredRow" class="mt-2">娣诲姞闃舵</el-button>
+ </el-form-item>
+ </div>
+
+ <!-- 浠锋牸鎺у埗 -->
+ <el-divider content-position="left">浠锋牸鎺у埗</el-divider>
+ <el-row :gutter="20">
+ <el-col :span="8">
+ <el-form-item label="鏈�浣庝环鏍�">
+ <el-input-number v-model="formData.minPrice" :min="0" :precision="2" placeholder="鏈�浣庝环鏍�" style="width: 100%" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="8">
+ <el-form-item label="鏈�楂樹环鏍�">
+ <el-input-number v-model="formData.maxPrice" :min="0" :precision="2" placeholder="鏈�楂樹环鏍�" style="width: 100%" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="8">
+ <el-form-item label="棰勮闃堝��(%)">
+ <el-input-number v-model="formData.warningThreshold" :min="0" :max="100" :precision="1" placeholder="棰勮闃堝��" style="width: 100%" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="鐢熸晥鏃堕棿" prop="effectiveTime">
+ <el-date-picker v-model="formData.effectiveTime" type="datetime" placeholder="閫夋嫨鐢熸晥鏃堕棿" style="width: 100%" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="澶辨晥鏃堕棿">
+ <el-date-picker v-model="formData.expireTime" type="datetime" placeholder="閫夋嫨澶辨晥鏃堕棿" style="width: 100%" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+
+ <el-form-item label="璋冧环鍘熷洜" prop="reason">
+ <el-select v-model="formData.reason" placeholder="璇烽�夋嫨璋冧环鍘熷洜" style="width: 100%">
+ <el-option label="甯傚満浠锋牸鍙樺姩" value="market" />
+ <el-option label="鎴愭湰鍙樺寲" value="cost" />
+ <el-option label="渚涘簲鍟嗚皟鏁�" value="supplier" />
+ <el-option label="瀛h妭鎬ц皟鏁�" value="seasonal" />
+ <el-option label="淇冮攢娲诲姩" value="promotion" />
+ <el-option label="鍏朵粬鍘熷洜" value="other" />
+ </el-select>
+ </el-form-item>
+
+ <el-form-item label="澶囨敞">
+ <el-input v-model="formData.remark" type="textarea" :rows="3" placeholder="璇疯緭鍏ュ娉ㄤ俊鎭�" />
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <el-button @click="dialogVisible = false">鍙栨秷</el-button>
+ <el-button type="primary" @click="handleSubmit" :loading="submitLoading">纭畾</el-button>
+ </template>
+ </el-dialog>
+
+ <!-- 鎵归噺鎶樻墸瀵硅瘽妗� -->
+ <el-dialog v-model="batchDiscountVisible" title="鎵归噺璁剧疆鎶樻墸" width="600px">
+ <el-form :model="batchDiscountForm" label-width="120px">
+ <el-form-item label="鎶樻墸绫诲瀷">
+ <el-select v-model="batchDiscountForm.discountType" placeholder="璇烽�夋嫨鎶樻墸绫诲瀷" style="width: 100%">
+ <el-option label="鐧惧垎姣旀姌鎵�" value="percentage" />
+ <el-option label="鍥哄畾閲戦" value="fixed" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="鎶樻墸鍊�">
+ <el-input-number
+ v-model="batchDiscountForm.discountValue"
+ :min="0"
+ :max="batchDiscountForm.discountType === 'percentage' ? 100 : undefined"
+ :precision="2"
+ placeholder="璇疯緭鍏ユ姌鎵e��"
+ style="width: 100%"
+ />
+ </el-form-item>
+ <el-form-item label="鐢熸晥鏃堕棿">
+ <el-date-picker v-model="batchDiscountForm.effectiveTime" type="datetime" placeholder="閫夋嫨鐢熸晥鏃堕棿" style="width: 100%" />
+ </el-form-item>
+ <el-form-item label="澶辨晥鏃堕棿">
+ <el-date-picker v-model="batchDiscountForm.expireTime" type="datetime" placeholder="閫夋嫨澶辨晥鏃堕棿" style="width: 100%" />
+ </el-form-item>
+ <el-form-item label="閫傜敤鍟嗗搧">
+ <div class="selected-items">
+ 宸查�夋嫨 {{ selectedRows.length }} 涓晢鍝�
+ </div>
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <el-button @click="batchDiscountVisible = false">鍙栨秷</el-button>
+ <el-button type="primary" @click="handleBatchDiscount">纭畾</el-button>
+ </template>
+ </el-dialog>
+
+ <!-- 浠锋牸鎺у埗瀵硅瘽妗� -->
+ <el-dialog v-model="priceControlVisible" title="浠锋牸鎺у埗璁剧疆" width="700px">
+ <el-form :model="priceControlForm" label-width="120px">
+ <el-form-item label="榛樿鏈�浣庝环鏍�">
+ <el-input-number v-model="priceControlForm.defaultMinPrice" :min="0" :precision="2" style="width: 200px" />
+ </el-form-item>
+ <el-form-item label="榛樿鏈�楂樹环鏍�">
+ <el-input-number v-model="priceControlForm.defaultMaxPrice" :min="0" :precision="2" style="width: 200px" />
+ </el-form-item>
+ <el-form-item label="浠锋牸鍙樺姩闃堝��">
+ <el-input-number v-model="priceControlForm.changeThreshold" :min="0" :max="100" :precision="1" style="width: 200px" />
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <el-button @click="priceControlVisible = false">鍙栨秷</el-button>
+ <el-button type="primary" @click="handlePriceControl">淇濆瓨璁剧疆</el-button>
+ </template>
+ </el-dialog>
+
+ </div>
+</template>
+
+<script setup>
+import { ref, reactive, computed, onMounted } from 'vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import {
+ Search, Refresh, Plus, Discount, Setting, Download, Delete, Edit,
+ Warning
+} from '@element-plus/icons-vue'
+
+// 鍝嶅簲寮忔暟鎹�
+const loading = ref(false)
+const submitLoading = ref(false)
+const dialogVisible = ref(false)
+const batchDiscountVisible = ref(false)
+const priceControlVisible = ref(false)
+const dialogType = ref('add')
+const selectedRows = ref([])
+const formRef = ref()
+
+// 鎼滅储琛ㄥ崟
+const searchForm = reactive({
+ productName: '',
+ supplierId: '',
+ priceStatus: ''
+})
+
+// 鍒嗛〉
+const pagination = reactive({
+ currentPage: 1,
+ pageSize: 20,
+ total: 0
+})
+
+
+// 琛ㄥ崟鏁版嵁
+const formData = reactive({
+ productName: '',
+ productCode: '',
+ specification: '',
+ supplierName: '',
+ basePrice: 0,
+ unit: '',
+ discountType: '',
+ discountValue: 0,
+ discountEndTime: '',
+ tieredDiscount: [],
+ minPrice: null,
+ maxPrice: null,
+ warningThreshold: 10,
+ effectiveTime: '',
+ expireTime: '',
+ reason: '',
+ remark: ''
+})
+
+// 鎵归噺鎶樻墸琛ㄥ崟
+const batchDiscountForm = reactive({
+ discountType: 'percentage',
+ discountValue: 0,
+ effectiveTime: '',
+ expireTime: ''
+})
+
+// 浠锋牸鎺у埗琛ㄥ崟
+const priceControlForm = reactive({
+ defaultMinPrice: 0,
+ defaultMaxPrice: 0,
+ changeThreshold: 10,
+})
+
+// 琛ㄥ崟楠岃瘉瑙勫垯
+const formRules = {
+ productName: [{ required: true, message: '璇烽�夋嫨鍟嗗搧鍚嶇О', trigger: 'change' }],
+ productCode: [{ required: true, message: '璇疯緭鍏ュ晢鍝佺紪鐮�', trigger: 'blur' }],
+ supplierName: [{ required: true, message: '璇烽�夋嫨渚涘簲鍟�', trigger: 'change' }],
+ basePrice: [{ required: true, message: '璇疯緭鍏ュ熀纭�浠锋牸', trigger: 'blur' }],
+ effectiveTime: [{ required: true, message: '璇烽�夋嫨鐢熸晥鏃堕棿', trigger: 'change' }],
+ reason: [{ required: true, message: '璇烽�夋嫨璋冧环鍘熷洜', trigger: 'change' }]
+}
+
+// 妯℃嫙鏁版嵁
+const tableData = ref([
+ {
+ id: 1,
+ productName: '楂樺己搴﹁灪鏍�',
+ productCode: 'HQ001',
+ specification: 'M12脳80',
+ supplierName: '浼樿川浜旈噾渚涘簲鍟�',
+ basePrice: 2.50,
+ discountType: 'percentage',
+ discountValue: 10,
+ priceControl: { minPrice: 2.00, maxPrice: 3.00 },
+ status: 'active',
+ effectiveTime: '2025-01-01 00:00:00',
+ updateTime: '2025-09-17 10:30:00',
+ unit: '涓�',
+ reason: 'market',
+ remark: '甯傚満浠锋牸璋冩暣'
+ },
+ {
+ id: 2,
+ productName: '涓嶉攬閽㈢',
+ productCode: 'BXG002',
+ specification: '桅25脳2.0',
+ supplierName: '閽㈡潗璐告槗鍏徃',
+ basePrice: 45.80,
+ discountType: 'fixed',
+ discountValue: 5,
+ priceControl: { minPrice: 40.00, maxPrice: 50.00 },
+ status: 'pending',
+ effectiveTime: '2025-10-01 00:00:00',
+ updateTime: '2025-09-16 14:20:00',
+ unit: '绫�',
+ reason: 'cost',
+ remark: '鍘熸潗鏂欐垚鏈笂娑�'
+ }
+])
+
+const supplierList = ref([
+ { id: 1, name: '浼樿川浜旈噾渚涘簲鍟�' },
+ { id: 2, name: '閽㈡潗璐告槗鍏徃' },
+ { id: 3, name: '寤烘潗鎵瑰彂鍟�' }
+])
+
+const productList = ref([
+ { id: 1, name: '楂樺己搴﹁灪鏍�' },
+ { id: 2, name: '涓嶉攬閽㈢' },
+ { id: 3, name: '閾濆悎閲戝瀷鏉�' }
+])
+
+
+// 璁$畻灞炴��
+const finalTableData = computed(() => {
+ return tableData.value.filter(item => {
+ if (searchForm.productName && !item.productName.includes(searchForm.productName)) return false
+ if (searchForm.supplierId && item.supplierId !== searchForm.supplierId) return false
+ if (searchForm.priceStatus && item.status !== searchForm.priceStatus) return false
+
+ return true
+ })
+})
+
+// 鏂规硶
+const calculateFinalPrice = (row) => {
+ let finalPrice = row.basePrice
+ if (row.discountType === 'percentage') {
+ finalPrice = row.basePrice * (1 - row.discountValue / 100)
+ } else if (row.discountType === 'fixed') {
+ finalPrice = row.basePrice - row.discountValue
+ }
+ return Math.max(finalPrice, 0)
+}
+
+const getDiscountTagType = (discountType) => {
+ const typeMap = {
+ percentage: 'success',
+ fixed: 'warning',
+ tiered: 'info'
+ }
+ return typeMap[discountType] || 'info'
+}
+
+const getDiscountText = (discountType) => {
+ const textMap = {
+ percentage: '鐧惧垎姣�',
+ fixed: '鍥哄畾閲戦',
+ tiered: '闃舵鎶樻墸'
+ }
+ return textMap[discountType] || '鏈煡'
+}
+
+const getStatusType = (status) => {
+ const statusMap = {
+ active: 'success',
+ pending: 'warning',
+ expired: 'info',
+ suspended: 'danger'
+ }
+ return statusMap[status] || 'info'
+}
+
+const getStatusText = (status) => {
+ const statusMap = {
+ active: '鏈夋晥',
+ pending: '寰呯敓鏁�',
+ expired: '宸茶繃鏈�',
+ suspended: '宸叉殏鍋�'
+ }
+ return statusMap[status] || '鏈煡'
+}
+
+const isPriceWarning = (row) => {
+ if (!row.priceControl) return false
+ const finalPrice = calculateFinalPrice(row)
+ return finalPrice < row.priceControl.minPrice || finalPrice > row.priceControl.maxPrice
+}
+
+
+const handleSearch = () => {
+ loading.value = true
+ // 妯℃嫙API璋冪敤
+ setTimeout(() => {
+ loading.value = false
+ }, 500)
+}
+
+const resetSearch = () => {
+ Object.assign(searchForm, {
+ productName: '',
+ supplierId: '',
+ priceStatus: ''
+ })
+ handleSearch()
+}
+
+
+const openDialog = (type, row = {}) => {
+ dialogType.value = type
+ if (type === 'edit' && row.id) {
+ Object.assign(formData, {
+ ...row,
+ minPrice: row.priceControl?.minPrice,
+ maxPrice: row.priceControl?.maxPrice,
+ tieredDiscount: row.tieredDiscount || []
+ })
+ } else {
+ resetFormData()
+ }
+ dialogVisible.value = true
+}
+
+const resetFormData = () => {
+ Object.assign(formData, {
+ productName: '',
+ productCode: '',
+ specification: '',
+ supplierName: '',
+ basePrice: 0,
+ unit: '',
+ discountType: '',
+ discountValue: 0,
+ discountEndTime: '',
+ tieredDiscount: [],
+ minPrice: null,
+ maxPrice: null,
+ warningThreshold: 10,
+ effectiveTime: '',
+ expireTime: '',
+ reason: '',
+ remark: ''
+ })
+}
+
+const addTieredRow = () => {
+ formData.tieredDiscount.push({
+ minQty: 0,
+ maxQty: 0,
+ discount: 0
+ })
+}
+
+const removeTieredRow = (index) => {
+ formData.tieredDiscount.splice(index, 1)
+}
+
+const handleSubmit = async () => {
+ if (!formRef.value) return
+
+ try {
+ await formRef.value.validate()
+ submitLoading.value = true
+
+ // 妯℃嫙API璋冪敤
+ setTimeout(() => {
+ if (dialogType.value === 'add') {
+ const newItem = {
+ id: Date.now(),
+ ...formData,
+ priceControl: {
+ minPrice: formData.minPrice,
+ maxPrice: formData.maxPrice
+ },
+ status: 'pending',
+ updateTime: new Date().toLocaleString()
+ }
+ tableData.value.unshift(newItem)
+ ElMessage.success('鏂板鎴愬姛')
+ } else {
+ // 缂栬緫閫昏緫
+ ElMessage.success('缂栬緫鎴愬姛')
+ }
+
+ dialogVisible.value = false
+ submitLoading.value = false
+ }, 1000)
+ } catch (error) {
+ console.error('琛ㄥ崟楠岃瘉澶辫触:', error)
+ }
+}
+
+const openBatchDiscountDialog = () => {
+ if (selectedRows.value.length === 0) {
+ ElMessage.warning('璇峰厛閫夋嫨瑕佽缃姌鎵g殑鍟嗗搧')
+ return
+ }
+ batchDiscountVisible.value = true
+}
+
+const handleBatchDiscount = () => {
+ // 鎵归噺璁剧疆鎶樻墸閫昏緫
+ selectedRows.value.forEach(row => {
+ row.discountType = batchDiscountForm.discountType
+ row.discountValue = batchDiscountForm.discountValue
+ })
+
+ ElMessage.success(`宸蹭负 ${selectedRows.value.length} 涓晢鍝佽缃姌鎵)
+ batchDiscountVisible.value = false
+}
+
+const openPriceControlDialog = () => {
+ priceControlVisible.value = true
+}
+
+const handlePriceControl = () => {
+ ElMessage.success('浠锋牸鎺у埗璁剧疆宸蹭繚瀛�')
+ priceControlVisible.value = false
+}
+
+const openDiscountDialog = (row) => {
+ // 鍗曚釜鍟嗗搧鎶樻墸璁剧疆
+ openDialog('edit', row)
+}
+
+
+const handleDelete = (row) => {
+ ElMessageBox.confirm('纭畾瑕佸垹闄よ繖鏉¤褰曞悧锛�', '鎻愮ず', {
+ confirmButtonText: '纭畾',
+ cancelButtonText: '鍙栨秷',
+ type: 'warning'
+ }).then(() => {
+ const index = tableData.value.findIndex(item => item.id === row.id)
+ if (index !== -1) {
+ tableData.value.splice(index, 1)
+ ElMessage.success('鍒犻櫎鎴愬姛')
+ }
+ })
+}
+
+const handleBatchDelete = () => {
+ if (selectedRows.value.length === 0) {
+ ElMessage.warning('璇峰厛閫夋嫨瑕佸垹闄ょ殑璁板綍')
+ return
+ }
+
+ ElMessageBox.confirm(`纭畾瑕佸垹闄ら�変腑鐨� ${selectedRows.value.length} 鏉¤褰曞悧锛焋, '鎻愮ず', {
+ confirmButtonText: '纭畾',
+ cancelButtonText: '鍙栨秷',
+ type: 'warning'
+ }).then(() => {
+ selectedRows.value.forEach(row => {
+ const index = tableData.value.findIndex(item => item.id === row.id)
+ if (index !== -1) {
+ tableData.value.splice(index, 1)
+ }
+ })
+ ElMessage.success('鎵归噺鍒犻櫎鎴愬姛')
+ selectedRows.value = []
+ })
+}
+
+const handleSelectionChange = (rows) => {
+ selectedRows.value = rows
+}
+
+const handleSizeChange = (size) => {
+ pagination.pageSize = size
+ handleSearch()
+}
+
+const handleCurrentChange = (page) => {
+ pagination.currentPage = page
+ handleSearch()
+}
+
+const exportData = () => {
+ ElMessage.success('鏁版嵁瀵煎嚭鍔熻兘寮�鍙戜腑...')
+}
+
+// 鐢熷懡鍛ㄦ湡
+onMounted(() => {
+ handleSearch()
+})
+</script>
+
+<style scoped>
+.app-container {
+ padding: 20px;
+}
+
+.search-card, .action-card, .table-card {
+ margin-bottom: 20px;
+}
+
+.action-buttons {
+ display: flex;
+ gap: 10px;
+ flex-wrap: wrap;
+}
+
+
+.product-info {
+ line-height: 1.4;
+}
+
+.product-name {
+ font-weight: bold;
+ color: #303133;
+}
+
+.product-spec, .product-code {
+ font-size: 12px;
+ color: #909399;
+}
+
+.price-text {
+ font-weight: bold;
+ color: #409EFF;
+}
+
+.final-price {
+ font-weight: bold;
+ color: #67C23A;
+ font-size: 16px;
+}
+
+.discount-value {
+ font-size: 12px;
+ color: #E6A23C;
+ margin-top: 2px;
+}
+
+.no-discount {
+ color: #C0C4CC;
+ font-size: 12px;
+}
+
+.price-control {
+ font-size: 12px;
+ line-height: 1.3;
+}
+
+.control-item {
+ color: #909399;
+}
+
+.warning-indicator {
+ margin-top: 2px;
+}
+
+.pagination-wrapper {
+ display: flex;
+ justify-content: end;
+ margin-top: 20px;
+}
+
+.selected-items {
+ color: #409EFF;
+ font-weight: bold;
+}
+
+
+.mt-2 {
+ margin-top: 8px;
+}
+
+.ml-2 {
+ margin-left: 8px;
+}
+
+:deep(.el-table) {
+ font-size: 13px;
+}
+
+:deep(.el-table th) {
+ background-color: #fafafa;
+}
+
+:deep(.el-card__body) {
+ padding: 15px;
+}
+
+:deep(.el-divider__text) {
+ font-weight: bold;
+ color: #409EFF;
+}
+</style>
diff --git a/src/views/procurementManagement/arrivalManagement/index.vue b/src/views/procurementManagement/arrivalManagement/index.vue
index d2c89b1..060d2f1 100644
--- a/src/views/procurementManagement/arrivalManagement/index.vue
+++ b/src/views/procurementManagement/arrivalManagement/index.vue
@@ -18,7 +18,6 @@
<el-card class="table-card" shadow="never">
<div class="table-header">
<el-button type="primary" @click="openDialog('add')">鏂板鍒拌揣</el-button>
- <el-button type="success" @click="handleBatchReceive">鎵归噺鏀惰揣</el-button>
<el-button type="danger" @click="handleBatchDelete">鎵归噺鍒犻櫎</el-button>
</div>
@@ -37,23 +36,34 @@
<el-table-column label="鎿嶄綔" width="200" align="center">
<template #default="{ row }">
<el-button type="primary" link @click="openDialog('edit', row)">缂栬緫</el-button>
- <el-button type="success" link @click="handleReceive(row)">鏀惰揣</el-button>
+ <el-button type="success" v-if="row.status === 'pending'" link @click="handleReceive(row)">鏀惰揣</el-button>
<el-button type="danger" link @click="handleDelete(row)">鍒犻櫎</el-button>
</template>
</el-table-column>
</el-table>
+ <!-- 鍒嗛〉 -->
+ <pagination
+ :total="total"
+ layout="total, sizes, prev, pager, next, jumper"
+ :page="pagination.current"
+ :limit="pagination.size"
+ @pagination="handleCurrentChange"
+ />
</el-card>
<el-dialog v-model="dialogVisible" :title="dialogType === 'add' ? '鏂板鍒拌揣' : '缂栬緫鍒拌揣'" width="600px">
<el-form :model="formData" label-width="120px">
+ <el-form-item label="鍒拌揣鍗曞彿">
+ <el-input v-model="formData.arrivalNo" placeholder="鍒拌揣鍗曞彿" />
+ </el-form-item>
<el-form-item label="閲囪喘璁㈠崟鍙�">
- <el-select v-model="formData.orderNo" placeholder="璇烽�夋嫨閲囪喘璁㈠崟" style="width: 100%">
- <el-option label="PO20241201001" value="PO20241201001" />
- <el-option label="PO20241201002" value="PO20241201002" />
- </el-select>
+ <el-input v-model="formData.orderNo" placeholder="閲囪喘璁㈠崟鍙�" />
</el-form-item>
<el-form-item label="渚涘簲鍟嗗悕绉�">
<el-input v-model="formData.supplierName" placeholder="渚涘簲鍟嗗悕绉�" />
+ </el-form-item>
+ <el-form-item label="鍒拌揣鏁伴噺">
+ <el-input-number :min="0" v-model="formData.arrivalQuantity" placeholder="鍒拌揣鏁伴噺" />
</el-form-item>
<el-form-item label="澶囨敞">
<el-input v-model="formData.remark" type="textarea" :rows="3" placeholder="璇疯緭鍏ュ娉ㄤ俊鎭�" />
@@ -68,8 +78,40 @@
</template>
<script setup>
-import { ref, reactive } from 'vue'
+import { ref, reactive,onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
+import {listPage,add,update,del} from "@/api/procurementManagement/arrivalManagement.js"
+import Pagination from '@/components/PIMTable/Pagination.vue'
+
+onMounted(() => {
+ getList()
+})
+
+const tableData = ref([])
+
+const getList = () => {
+ loading.value = true
+ listPage({...searchForm,...pagination}).then(res =>{
+ if(res.code === 200){
+ tableData.value = res.data.records
+ total.value = res.data.total
+ loading.value = false
+ }
+ })
+}
+
+const pagination = reactive({
+ current: 1,
+ size: 10
+})
+
+const total = ref(0)
+
+const handleCurrentChange = (val) => {
+ pagination.current = val.page
+ pagination.size = val.limit
+ getList()
+}
const loading = ref(false)
const dialogVisible = ref(false)
@@ -82,25 +124,13 @@
})
const formData = reactive({
+ arrivalNo: '',
+ arrivalQuantity: 0,
orderNo: '',
supplierName: '',
- remark: ''
+ remark: '',
+ status: 'pending'
})
-
-const mockData = [
- {
- id: 1,
- arrivalNo: 'AR20241201001',
- orderNo: 'PO20241201001',
- supplierName: '渚涘簲鍟咥',
- status: 'received',
- arrivalQuantity: 250,
- arrivalTime: '2025-12-01 15:30:00',
- remark: '姝e父鍒拌揣'
- }
-]
-
-const tableData = ref([...mockData])
const getStatusType = (status) => {
const statusMap = { pending: 'warning', received: 'success', stored: 'info' }
@@ -114,7 +144,7 @@
const handleSearch = () => {
loading.value = true
- setTimeout(() => { loading.value = false }, 500)
+ getList()
}
const resetSearch = () => {
@@ -124,34 +154,45 @@
const openDialog = (type, row = {}) => {
dialogType.value = type
if (type === 'edit' && row.id) {
- Object.assign(formData, { orderNo: row.orderNo, supplierName: row.supplierName, remark: row.remark })
+ obj.id = row.id
+ Object.assign(formData, { orderNo: row.orderNo, supplierName: row.supplierName, remark: row.remark, arrivalQuantity: row.arrivalQuantity,arrivalNo: row.arrivalNo })
} else {
- Object.assign(formData, { orderNo: '', supplierName: '', remark: '' })
+ Object.assign(formData, { orderNo: '', supplierName: '', remark: '',arrivalQuantity: 0,arrivalNo: '' })
}
dialogVisible.value = true
}
+const obj = reactive({
+ id:''
+})
+
const handleSubmit = () => {
if (dialogType.value === 'add') {
- const newArrival = {
- id: Date.now(),
- arrivalNo: `AR${Date.now()}`,
- orderNo: formData.orderNo,
- supplierName: formData.supplierName,
- status: 'pending',
- arrivalQuantity: 0,
- arrivalTime: new Date().toLocaleString(),
- remark: formData.remark
- }
- tableData.value.unshift(newArrival)
- ElMessage.success('鏂板鎴愬姛')
+ add(formData).then(res => {
+ if(res.code === 200){
+ ElMessage.success('鏂板鎴愬姛')
+ getList()
+ }
+ })
+ }else{
+ update({...formData, ...obj}).then(res => {
+ if(res.code === 200){
+ ElMessage.success('缂栬緫鎴愬姛')
+ getList()
+ }
+ })
}
dialogVisible.value = false
}
const handleReceive = (row) => {
row.status = 'received'
- ElMessage.success('鏀惰揣鎴愬姛')
+ update(row).then(res => {
+ if(res.code === 200){
+ ElMessage.success('鏀惰揣鎴愬姛')
+ getList()
+ }
+ })
}
const handleDelete = (row) => {
@@ -160,20 +201,30 @@
cancelButtonText: '鍙栨秷',
type: 'warning'
}).then(() => {
- const index = tableData.value.findIndex(item => item.id === row.id)
- if (index !== -1) {
- tableData.value.splice(index, 1)
- ElMessage.success('鍒犻櫎鎴愬姛')
- }
+ let ids = [row.id]
+ del(ids).then(res => {
+ if(res.code === 200){
+ ElMessage.success('鍒犻櫎鎴愬姛')
+ getList()
+ }
+ })
})
}
-const handleBatchReceive = () => {
- ElMessage.success('鎵归噺鏀惰揣鎴愬姛')
-}
-
const handleBatchDelete = () => {
- ElMessage.success('鎵归噺鍒犻櫎鎴愬姛')
+ ElMessageBox.confirm('纭畾瑕佸垹闄ら�変腑鐨勮褰曞悧锛�', '鎻愮ず', {
+ confirmButtonText: '纭畾',
+ cancelButtonText: '鍙栨秷',
+ type: 'warning'
+ }).then(() => {
+ let ids = selectedRows.value.map(item => item.id)
+ del(ids).then(res => {
+ if(res.code === 200){
+ ElMessage.success('鍒犻櫎鎴愬姛')
+ getList()
+ }
+ })
+ })
}
const handleSelectionChange = (rows) => {
diff --git a/src/views/procurementManagement/procurementPlan/index.vue b/src/views/procurementManagement/procurementPlan/index.vue
new file mode 100644
index 0000000..fec52ed
--- /dev/null
+++ b/src/views/procurementManagement/procurementPlan/index.vue
@@ -0,0 +1,821 @@
+<template>
+ <div class="app-container">
+ <!-- 鎼滅储鍖哄煙 -->
+ <el-card class="search-card" shadow="never">
+ <el-form :model="searchForm" :inline="true" class="search-form">
+ <el-form-item label="璁″垝鍚嶇О">
+ <el-input v-model="searchForm.planName" placeholder="璇疯緭鍏ヨ鍒掑悕绉�" clearable />
+ </el-form-item>
+ <el-form-item label="鐘舵��">
+ <el-select v-model="searchForm.status" placeholder="璇烽�夋嫨鐘舵��" clearable style="width: 150px">
+ <el-option label="鍚敤" value="active" />
+ <el-option label="绂佺敤" value="inactive" />
+ </el-select>
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" @click="handleSearch">
+ <el-icon><Search /></el-icon>
+ 鎼滅储
+ </el-button>
+ <el-button @click="handleReset">
+ <el-icon><Refresh /></el-icon>
+ 閲嶇疆
+ </el-button>
+ </el-form-item>
+ </el-form>
+ </el-card>
+
+ <!-- 鎿嶄綔鎸夐挳 -->
+ <el-card class="table-card" shadow="never">
+ <div class="table-header">
+ <div class="table-title">閲囪喘璁″垝鍒楄〃</div>
+ <div class="table-actions">
+ <el-button type="primary" @click="handleAdd">
+ <el-icon><Plus /></el-icon>
+ 鏂板璁″垝
+ </el-button>
+ <el-button type="info" @click="handleExport">
+ <el-icon><Download /></el-icon>
+ 瀵煎嚭
+ </el-button>
+ </div>
+ </div>
+
+ <!-- 鏁版嵁琛ㄦ牸 -->
+ <el-table
+ v-loading="loading"
+ :data="tableData"
+ stripe
+ border
+ style="width: 100%"
+ >
+ <el-table-column prop="planName" label="璁″垝鍚嶇О" min-width="150" />
+ <el-table-column prop="description" label="鎻忚堪" min-width="200" show-overflow-tooltip />
+ <el-table-column prop="formula" label="璁$畻鍏紡" min-width="200" show-overflow-tooltip>
+ <template #default="{ row }">
+ <el-tag type="info" size="small">{{ row.formula }}</el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column prop="status" label="鐘舵��" width="80" align="center">
+ <template #default="{ row }">
+ <el-tag :type="row.status === 'active' ? 'success' : 'info'" size="small">
+ {{ row.status === 'active' ? '鍚敤' : '绂佺敤' }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column prop="lastCalculateTime" label="鏈�鍚庤绠楁椂闂�" width="160" />
+ <el-table-column label="鎿嶄綔" width="200" fixed="right" align="center">
+ <template #default="{ row }">
+ <el-button type="primary" link @click="handleEdit(row)">缂栬緫</el-button>
+ <el-button type="success" link @click="handleCalculate(row)">璁$畻</el-button>
+ <el-button type="danger" link @click="handleDelete(row)">鍒犻櫎</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+
+ <!-- 鍒嗛〉 -->
+ <div class="pagination-container">
+ <el-pagination
+ v-model:current-page="pagination.current"
+ v-model:page-size="pagination.size"
+ :page-sizes="[10, 20, 50, 100]"
+ :total="pagination.total"
+ layout="total, sizes, prev, pager, next, jumper"
+ @size-change="handleSizeChange"
+ @current-change="handleCurrentChange"
+ />
+ </div>
+ </el-card>
+
+ <!-- 鏂板/缂栬緫瀵硅瘽妗� -->
+ <el-dialog
+ v-model="dialogVisible"
+ :title="dialogType === 'add' ? '鏂板閲囪喘璁″垝' : '缂栬緫閲囪喘璁″垝'"
+ width="1000px"
+ :close-on-click-modal="false"
+ >
+ <div class="form-container">
+ <!-- 鍩烘湰淇℃伅 -->
+ <div class="form-section">
+ <div class="section-title">鍩烘湰淇℃伅</div>
+ <el-form
+ ref="formRef"
+ :model="formData"
+ :rules="formRules"
+ label-width="120px"
+ >
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="缂栫爜" prop="code">
+ <el-input v-model="formData.code" placeholder="绯荤粺鑷姩鐢熸垚" disabled />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鍚嶇О" prop="planName" required>
+ <el-input v-model="formData.planName" placeholder="璇疯緭鍏ヨ鍒掑悕绉�" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+
+ <el-form-item label="鎻忚堪" prop="description">
+ <el-input
+ v-model="formData.description"
+ type="textarea"
+ :rows="3"
+ placeholder="璇疯緭鍏ヨ鍒掓弿杩�"
+ />
+ </el-form-item>
+
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="鏁版嵁鐘舵��" prop="dataStatus">
+ <el-select v-model="formData.dataStatus" placeholder="璇烽�夋嫨鐘舵��" style="width: 100%">
+ <el-option label="鑽夌" value="draft" />
+ <el-option label="宸插鏍�" value="approved" />
+ <el-option label="宸茬鐢�" value="disabled" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鏄惁绯荤粺棰勭疆">
+ <el-checkbox v-model="formData.isSystemPreset">鏄�</el-checkbox>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ </el-form>
+ </div>
+
+ <!-- 璁$畻鍙傛暟 -->
+ <div class="form-section">
+ <div class="section-title">璁$畻鍙傛暟</div>
+ <el-tabs v-model="activeTab" class="param-tabs">
+ <el-tab-pane label="闇�姹傚弬鏁�" name="demand">
+ <div class="checkbox-group">
+ <el-checkbox v-model="formData.considerExistingStock">鑰冭檻鐜版湁搴撳瓨</el-checkbox>
+ <el-checkbox v-model="formData.warehouseMRPControl">浠撳簱杩愯MRP鐨勬帶鍒�</el-checkbox>
+ <el-checkbox v-model="formData.calculateTotalDemand">璁$畻鎬婚渶姹�</el-checkbox>
+ <el-checkbox v-model="formData.considerSafetyStock">鑰冭檻瀹夊叏搴撳瓨</el-checkbox>
+ <el-checkbox v-model="formData.considerLockedStock">鑰冭檻閿佸簱</el-checkbox>
+ <el-checkbox v-model="formData.notConsiderMaterialAux">涓嶈�冭檻鐗╂枡杈呭姪灞炴��</el-checkbox>
+ <el-checkbox v-model="formData.negativeStockAsDemand">璐熷簱瀛樹綔涓洪渶姹�</el-checkbox>
+ </div>
+ </el-tab-pane>
+ <el-tab-pane label="璁$畻鍙傛暟" name="calculation">
+ <div class="checkbox-group">
+ <el-checkbox v-model="formData.considerExistingStock">鑰冭檻鐜版湁搴撳瓨</el-checkbox>
+ <el-checkbox v-model="formData.warehouseMRPControl">浠撳簱杩愯MRP鐨勬帶鍒�</el-checkbox>
+ <el-checkbox v-model="formData.calculateTotalDemand">璁$畻鎬婚渶姹�</el-checkbox>
+ <el-checkbox v-model="formData.considerSafetyStock">鑰冭檻瀹夊叏搴撳瓨</el-checkbox>
+ <el-checkbox v-model="formData.considerLockedStock">鑰冭檻閿佸簱</el-checkbox>
+ <el-checkbox v-model="formData.notConsiderMaterialAux">涓嶈�冭檻鐗╂枡杈呭姪灞炴��</el-checkbox>
+ <el-checkbox v-model="formData.negativeStockAsDemand">璐熷簱瀛樹綔涓洪渶姹�</el-checkbox>
+ </div>
+ </el-tab-pane>
+ </el-tabs>
+ </div>
+
+ <!-- 姹囨�诲悎骞堕�夐」 -->
+ <div class="form-section">
+ <div class="section-title">姹囨�诲悎骞堕�夐」</div>
+ <div class="checkbox-group">
+ <el-checkbox v-model="formData.summaryMaterial">鐗╂枡</el-checkbox>
+ <el-checkbox v-model="formData.summaryAuxAttributes">杈呭姪灞炴��</el-checkbox>
+ <el-checkbox v-model="formData.summaryDemandDate">闇�姹傛棩鏈�</el-checkbox>
+ </div>
+ </div>
+
+ <!-- 璁$畻鍏紡 -->
+ <div class="form-section">
+ <div class="section-title">璁$畻鍏紡</div>
+ <div class="formula-input-section">
+ <el-form-item label="璁$畻鍏紡" prop="formula" required>
+ <el-input
+ v-model="formData.formula"
+ placeholder="渚嬪: 棰勮鍑哄簱鏁伴噺 - 鐜版湁搴撳瓨 + 瀹夊叏搴撳瓨 - 棰勮鍏ュ簱鏁伴噺"
+ @input="validateFormula"
+ />
+ </el-form-item>
+ <div class="formula-help">
+ <el-text type="info" size="small">
+ 鏀寔鍙橀噺锛氶璁″嚭搴撴暟閲忋�佺幇鏈夊簱瀛樸�佸畨鍏ㄥ簱瀛樸�侀璁″叆搴撴暟閲�
+ </el-text>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button @click="dialogVisible = false">鍙栨秷</el-button>
+ <el-button type="primary" @click="handleSubmit" :loading="submitLoading">纭畾</el-button>
+ </div>
+ </template>
+ </el-dialog>
+
+ <!-- 浜у搧閫夋嫨瀵硅瘽妗� -->
+ <el-dialog
+ v-model="productSelectDialogVisible"
+ title="閫夋嫨浜у搧"
+ width="800px"
+ :close-on-click-modal="false"
+ >
+ <div class="product-select">
+ <el-alert
+ title="璇烽�夋嫨瑕佽绠楃殑浜у搧"
+ type="info"
+ :closable="false"
+ show-icon
+ >
+ <template #default>
+ <p>閫夋嫨浜у搧鍚庯紝绯荤粺灏嗘牴鎹綋鍓嶈绠楀叕寮忓拰浜у搧搴撳瓨鎯呭喌杩涜璁$畻銆�</p>
+ </template>
+ </el-alert>
+
+ <el-table
+ v-loading="productLoading"
+ :data="productList"
+ @selection-change="handleProductSelectionChange"
+ stripe
+ border
+ style="width: 100%; margin-top: 20px;"
+ >
+ <el-table-column type="selection" width="55" />
+ <el-table-column prop="productName" label="浜у搧鍚嶇О" min-width="150" />
+ <el-table-column prop="productCode" label="浜у搧缂栫爜" width="120" />
+ <el-table-column prop="existingStock" label="鐜版湁搴撳瓨" width="100" align="right" />
+ <el-table-column prop="safetyStock" label="瀹夊叏搴撳瓨" width="100" align="right" />
+ <el-table-column prop="expectedOutbound" label="棰勮鍑哄簱" width="100" align="right" />
+ <el-table-column prop="expectedInbound" label="棰勮鍏ュ簱" width="100" align="right" />
+ </el-table>
+ </div>
+
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button @click="productSelectDialogVisible = false">鍙栨秷</el-button>
+ <el-button type="primary" @click="handleConfirmProductSelection" :disabled="selectedProducts.length === 0">
+ 纭璁$畻
+ </el-button>
+ </div>
+ </template>
+ </el-dialog>
+
+ <!-- 璁$畻缁撴灉瀵硅瘽妗� -->
+ <el-dialog
+ v-model="calculateDialogVisible"
+ title="閲囪喘璁$畻缁撴灉"
+ width="1000px"
+ :close-on-click-modal="false"
+ >
+ <div class="calculate-result">
+ <el-alert
+ title="璁$畻缁撴灉"
+ type="success"
+ :closable="false"
+ show-icon
+ >
+ <template #default>
+ <p>鍩轰簬褰撳墠閰嶇疆鐨勮绠楀叕寮忓拰搴撳瓨鎯呭喌锛岀郴缁熷凡璁$畻鍑哄悇浜у搧鐨勯噰璐渶姹傘��</p>
+ </template>
+ </el-alert>
+
+ <el-table :data="calculateResult" stripe border style="width: 100%; margin-top: 20px;">
+ <el-table-column prop="productName" label="浜у搧鍚嶇О" min-width="150" />
+ <el-table-column prop="productCode" label="浜у搧缂栫爜" width="120" />
+ <el-table-column prop="existingStock" label="鐜版湁搴撳瓨" width="100" align="right" />
+ <el-table-column prop="safetyStock" label="瀹夊叏搴撳瓨" width="100" align="right" />
+ <el-table-column prop="expectedOutbound" label="棰勮鍑哄簱鏁伴噺" width="120" align="right" />
+ <el-table-column prop="expectedInbound" label="棰勮鍏ュ簱鏁伴噺" width="120" align="right" />
+ <el-table-column prop="weeklyNetDemand" label="鎸夊懆鍑�闇�姹�" width="120" align="right">
+ <template #default="{ row }">
+ <el-tag :type="row.weeklyNetDemand > 0 ? 'warning' : 'success'" size="small">
+ {{ row.weeklyNetDemand }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column prop="suggestedPurchase" label="寤鸿閲囪喘" width="100" align="right">
+ <template #default="{ row }">
+ <el-tag :type="row.suggestedPurchase > 0 ? 'danger' : 'success'" size="small">
+ {{ row.suggestedPurchase }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ </el-table>
+ </div>
+
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button @click="calculateDialogVisible = false">鍏抽棴</el-button>
+ <el-button type="primary" @click="handleCreatePurchaseOrder">鐢熸垚閲囪喘璁㈠崟</el-button>
+ </div>
+ </template>
+ </el-dialog>
+ </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted } from 'vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import { Search, Refresh, Plus, Download } from '@element-plus/icons-vue'
+
+// 鍝嶅簲寮忔暟鎹�
+const loading = ref(false)
+const submitLoading = ref(false)
+const dialogVisible = ref(false)
+const productSelectDialogVisible = ref(false)
+const calculateDialogVisible = ref(false)
+const dialogType = ref('add')
+const productLoading = ref(false)
+const selectedProducts = ref([])
+const currentPlan = ref(null)
+
+// 鎼滅储琛ㄥ崟
+const searchForm = reactive({
+ planName: '',
+ status: ''
+})
+
+// 鍒嗛〉鏁版嵁
+const pagination = reactive({
+ current: 1,
+ size: 20,
+ total: 0
+})
+
+// 琛ㄥ崟鏁版嵁
+const formData = reactive({
+ code: '',
+ planName: '',
+ description: '',
+ dataStatus: '',
+ isSystemPreset: false,
+ formula: '',
+ // 璁$畻鍙傛暟
+ considerExistingStock: false,
+ warehouseMRPControl: false,
+ calculateTotalDemand: false,
+ considerSafetyStock: false,
+ considerLockedStock: false,
+ notConsiderMaterialAux: false,
+ negativeStockAsDemand: false,
+ // 姹囨�诲悎骞堕�夐」
+ summaryMaterial: false,
+ summaryAuxAttributes: false,
+ summaryDemandDate: false
+})
+
+// 褰撳墠婵�娲荤殑鏍囩椤�
+const activeTab = ref('demand')
+
+// 琛ㄥ崟楠岃瘉瑙勫垯
+const formRules = {
+ planName: [
+ { required: true, message: '璇疯緭鍏ヨ鍒掑悕绉�', trigger: 'blur' }
+ ],
+ dataStatus: [
+ { required: true, message: '璇烽�夋嫨鏁版嵁鐘舵��', trigger: 'change' }
+ ],
+ formula: [
+ { required: true, message: '璇疯緭鍏ヨ绠楀叕寮�', trigger: 'blur' }
+ ]
+}
+
+// 琛ㄦ牸鏁版嵁
+const tableData = ref([
+ {
+ id: 1,
+ planName: '甯歌浜у搧閲囪喘璁″垝',
+ description: '鍩轰簬鍘嗗彶閿�鍞暟鎹殑甯歌浜у搧閲囪喘璁″垝',
+ formula: '棰勮鍑哄簱鏁伴噺 - 鐜版湁搴撳瓨 + 瀹夊叏搴撳瓨 - 棰勮鍏ュ簱鏁伴噺',
+ status: 'active',
+ lastCalculateTime: '2024-12-01 10:30:00',
+ remark: '閫傜敤浜庡父瑙勪骇鍝�'
+ },
+ {
+ id: 2,
+ planName: '瀛h妭鎬т骇鍝侀噰璐鍒�',
+ description: '鑰冭檻瀛h妭鍥犵礌鐨勫鑺傛�т骇鍝侀噰璐鍒�',
+ formula: '棰勮鍑哄簱鏁伴噺 - 鐜版湁搴撳瓨 + 瀹夊叏搴撳瓨 * 1.2 - 棰勮鍏ュ簱鏁伴噺',
+ status: 'active',
+ lastCalculateTime: '2024-11-28 15:20:00',
+ remark: '閫傜敤浜庡鑺傛�т骇鍝�'
+ }
+])
+
+// 浜у搧鍒楄〃鏁版嵁
+const productList = ref([
+ {
+ id: 1,
+ productName: '浜у搧A',
+ productCode: 'PA001',
+ existingStock: 100,
+ safetyStock: 50,
+ expectedOutbound: 200,
+ expectedInbound: 30
+ },
+ {
+ id: 2,
+ productName: '浜у搧B',
+ productCode: 'PB002',
+ existingStock: 80,
+ safetyStock: 30,
+ expectedOutbound: 150,
+ expectedInbound: 20
+ },
+ {
+ id: 3,
+ productName: '浜у搧C',
+ productCode: 'PC003',
+ existingStock: 120,
+ safetyStock: 60,
+ expectedOutbound: 180,
+ expectedInbound: 25
+ },
+ {
+ id: 4,
+ productName: '浜у搧D',
+ productCode: 'PD004',
+ existingStock: 90,
+ safetyStock: 40,
+ expectedOutbound: 160,
+ expectedInbound: 35
+ }
+])
+
+// 璁$畻缁撴灉鏁版嵁
+const calculateResult = ref([
+ {
+ productName: '浜у搧A',
+ existingStock: 100,
+ safetyStock: 50,
+ expectedOutbound: 200,
+ expectedInbound: 30,
+ weeklyNetDemand: 120,
+ suggestedPurchase: 150
+ },
+ {
+ productName: '浜у搧B',
+ existingStock: 80,
+ safetyStock: 30,
+ expectedOutbound: 150,
+ expectedInbound: 20,
+ weeklyNetDemand: 100,
+ suggestedPurchase: 120
+ }
+])
+
+// 鏂规硶
+const handleSearch = () => {
+ pagination.current = 1
+ loadData()
+}
+
+const handleReset = () => {
+ Object.assign(searchForm, {
+ planName: '',
+ status: ''
+ })
+ handleSearch()
+}
+
+const loadData = () => {
+ loading.value = true
+ // 妯℃嫙API璋冪敤
+ setTimeout(() => {
+ pagination.total = tableData.value.length
+ loading.value = false
+ }, 500)
+}
+
+const handleAdd = () => {
+ dialogType.value = 'add'
+ resetForm()
+ // 鑷姩鐢熸垚缂栫爜
+ formData.code = 'CGJH' + String(Date.now()).slice(-4)
+ dialogVisible.value = true
+}
+
+const handleEdit = (row) => {
+ dialogType.value = 'edit'
+ Object.assign(formData, row)
+ dialogVisible.value = true
+}
+
+const handleDelete = async (row) => {
+ try {
+ await ElMessageBox.confirm('纭畾瑕佸垹闄よ繖涓噰璐鍒掑悧锛�', '鎻愮ず', {
+ confirmButtonText: '纭畾',
+ cancelButtonText: '鍙栨秷',
+ type: 'warning'
+ })
+
+ const index = tableData.value.findIndex(item => item.id === row.id)
+ if (index > -1) {
+ tableData.value.splice(index, 1)
+ ElMessage.success('鍒犻櫎鎴愬姛')
+ loadData()
+ }
+ } catch {
+ // 鐢ㄦ埛鍙栨秷鍒犻櫎
+ }
+}
+
+const handleSubmit = async () => {
+ try {
+ // 琛ㄥ崟楠岃瘉
+ if (!formData.planName || !formData.formula) {
+ ElMessage.error('璇峰~鍐欏繀濉」')
+ return
+ }
+
+ submitLoading.value = true
+
+ if (dialogType.value === 'add') {
+ // 鏂板
+ const newItem = {
+ ...formData,
+ id: Date.now(),
+ lastCalculateTime: '-'
+ }
+ tableData.value.unshift(newItem)
+ ElMessage.success('鏂板鎴愬姛')
+ } else {
+ // 缂栬緫
+ const index = tableData.value.findIndex(item => item.id === formData.id)
+ if (index > -1) {
+ tableData.value[index] = { ...formData }
+ ElMessage.success('缂栬緫鎴愬姛')
+ }
+ }
+
+ dialogVisible.value = false
+ loadData()
+ } catch (error) {
+ ElMessage.error('鎿嶄綔澶辫触')
+ } finally {
+ submitLoading.value = false
+ }
+}
+
+const resetForm = () => {
+ Object.assign(formData, {
+ code: '',
+ planName: '',
+ description: '',
+ dataStatus: '',
+ isSystemPreset: false,
+ formula: '',
+ // 璁$畻鍙傛暟
+ considerExistingStock: false,
+ warehouseMRPControl: false,
+ calculateTotalDemand: false,
+ considerSafetyStock: false,
+ considerLockedStock: false,
+ notConsiderMaterialAux: false,
+ negativeStockAsDemand: false,
+ // 姹囨�诲悎骞堕�夐」
+ summaryMaterial: false,
+ summaryAuxAttributes: false,
+ summaryDemandDate: false
+ })
+ activeTab.value = 'demand'
+}
+
+const validateFormula = () => {
+ // 绠�鍗曠殑鍏紡楠岃瘉
+ const formula = formData.formula
+ if (formula && !/^[a-zA-Z\u4e00-\u9fa5\s\*\+\-\/\(\)\d\.]+$/.test(formula)) {
+ ElMessage.warning('鍏紡鏍煎紡鍙兘涓嶆纭紝璇锋鏌�')
+ }
+}
+
+const handleCalculate = (row) => {
+ currentPlan.value = row
+ productSelectDialogVisible.value = true
+ loadProductList()
+}
+
+const loadProductList = () => {
+ productLoading.value = true
+ // 妯℃嫙鍔犺浇浜у搧鏁版嵁
+ setTimeout(() => {
+ productLoading.value = false
+ }, 500)
+}
+
+const handleProductSelectionChange = (selection) => {
+ selectedProducts.value = selection
+}
+
+const handleConfirmProductSelection = () => {
+ if (selectedProducts.value.length === 0) {
+ ElMessage.warning('璇烽�夋嫨瑕佽绠楃殑浜у搧')
+ return
+ }
+
+ ElMessage.success(`姝e湪璁$畻 ${currentPlan.value.planName} 鐨勯噰璐渶姹�...`)
+ productSelectDialogVisible.value = false
+
+ // 鏍规嵁閫夋嫨鐨勪骇鍝佸拰璁$畻鍏紡杩涜璁$畻
+ calculateWithSelectedProducts()
+}
+
+const calculateWithSelectedProducts = () => {
+ // 妯℃嫙璁$畻杩囩▼
+ setTimeout(() => {
+ // 鏍规嵁閫夋嫨鐨勪骇鍝佹洿鏂拌绠楃粨鏋�
+ const result = selectedProducts.value.map(product => {
+ // 杩欓噷搴旇鏍规嵁瀹為檯鐨勮绠楀叕寮忚繘琛岃绠�
+ // 绀轰緥锛氶璁″嚭搴撴暟閲� - 鐜版湁搴撳瓨 + 瀹夊叏搴撳瓨 - 棰勮鍏ュ簱鏁伴噺
+ const weeklyNetDemand = product.expectedOutbound - product.existingStock + product.safetyStock - product.expectedInbound
+ const suggestedPurchase = Math.max(0, weeklyNetDemand)
+
+ return {
+ productName: product.productName,
+ productCode: product.productCode,
+ existingStock: product.existingStock,
+ safetyStock: product.safetyStock,
+ expectedOutbound: product.expectedOutbound,
+ expectedInbound: product.expectedInbound,
+ weeklyNetDemand: weeklyNetDemand,
+ suggestedPurchase: suggestedPurchase
+ }
+ })
+
+ calculateResult.value = result
+ calculateDialogVisible.value = true
+ }, 1000)
+}
+
+
+const handleCreatePurchaseOrder = () => {
+ ElMessage.success('姝e湪鐢熸垚閲囪喘璁㈠崟...')
+ calculateDialogVisible.value = false
+}
+
+const handleExport = () => {
+ ElMessage.success('姝e湪瀵煎嚭鏁版嵁...')
+}
+
+
+const handleSizeChange = (size) => {
+ pagination.size = size
+ loadData()
+}
+
+const handleCurrentChange = (current) => {
+ pagination.current = current
+ loadData()
+}
+
+// 鐢熷懡鍛ㄦ湡
+onMounted(() => {
+ loadData()
+})
+</script>
+
+<style scoped>
+.app-container {
+ padding: 20px;
+}
+
+.page-header {
+ margin-bottom: 20px;
+}
+
+.page-header h2 {
+ margin: 0 0 8px 0;
+ color: #303133;
+ font-size: 24px;
+ font-weight: 600;
+}
+
+.page-header p {
+ margin: 0;
+ color: #909399;
+ font-size: 14px;
+}
+
+.search-card {
+ margin-bottom: 20px;
+}
+
+.search-form {
+ margin-bottom: 0;
+}
+
+.table-card {
+ margin-bottom: 20px;
+}
+
+.table-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 20px;
+}
+
+.table-title {
+ font-size: 16px;
+ font-weight: 600;
+ color: #303133;
+}
+
+.table-actions {
+ display: flex;
+ gap: 10px;
+}
+
+.pagination-container {
+ margin-top: 20px;
+ display: flex;
+ justify-content: end;
+}
+
+.form-container {
+ padding: 0 20px;
+}
+
+.formula-help {
+ margin-top: 5px;
+}
+
+.calculate-result {
+ padding: 20px 0;
+}
+
+.dialog-footer {
+ text-align: right;
+}
+
+:deep(.el-card__body) {
+ padding: 20px;
+}
+
+:deep(.el-table) {
+ font-size: 14px;
+}
+
+:deep(.el-form-item__label) {
+ font-weight: 500;
+}
+
+.form-container {
+ padding: 0;
+}
+
+.form-section {
+ margin-bottom: 24px;
+ border: 1px solid #e4e7ed;
+ border-radius: 6px;
+ overflow: hidden;
+}
+
+.section-title {
+ background-color: #f5f7fa;
+ padding: 12px 16px;
+ font-weight: 600;
+ color: #303133;
+ border-bottom: 1px solid #e4e7ed;
+}
+
+.form-section .el-form {
+ padding: 20px;
+}
+
+.param-tabs {
+ padding: 20px;
+}
+
+.param-tabs :deep(.el-tabs__header) {
+ margin-bottom: 20px;
+}
+
+.param-tabs :deep(.el-tabs__item.is-active) {
+ color: #f56c6c;
+ border-bottom-color: #f56c6c;
+}
+
+.checkbox-group {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 16px;
+}
+
+.checkbox-group .el-checkbox {
+ margin-right: 0;
+ margin-bottom: 8px;
+}
+
+.formula-input-section {
+ padding: 20px;
+}
+
+.formula-input-section .el-form-item {
+ margin-bottom: 12px;
+}
+
+.formula-help {
+ text-align: center;
+ margin-top: 8px;
+}
+</style>
diff --git a/src/views/procurementManagement/procurementReport/index.vue b/src/views/procurementManagement/procurementReport/index.vue
new file mode 100644
index 0000000..a12b4ba
--- /dev/null
+++ b/src/views/procurementManagement/procurementReport/index.vue
@@ -0,0 +1,847 @@
+<template>
+ <div class="app-container">
+ <!-- 鎶ヨ〃閫夋嫨鍣� -->
+ <el-card class="report-selector" shadow="never">
+ <el-tabs v-model="activeReport" @tab-change="handleReportChange">
+ <el-tab-pane label="閲囪喘璁㈠崟鎵ц姹囨�昏〃" name="orderSummary">
+ <template #label>
+ <span class="tab-label">
+ <el-icon><Document /></el-icon>
+ 閲囪喘璁㈠崟鎵ц姹囨�昏〃
+ </span>
+ </template>
+ </el-tab-pane>
+ <el-tab-pane label="閲囪喘璁㈠崟鎵ц鏄庣粏琛�" name="orderDetail">
+ <template #label>
+ <span class="tab-label">
+ <el-icon><List /></el-icon>
+ 閲囪喘璁㈠崟鎵ц鏄庣粏琛�
+ </span>
+ </template>
+ </el-tab-pane>
+ <el-tab-pane label="閲囪喘涓氬姟姹囨�昏〃" name="businessSummary">
+ <template #label>
+ <span class="tab-label">
+ <el-icon><TrendCharts /></el-icon>
+ 閲囪喘涓氬姟姹囨�昏〃
+ </span>
+ </template>
+ </el-tab-pane>
+ <el-tab-pane label="渚涘簲鍟嗕緵璐ф眹鎬昏〃" name="supplierSummary">
+ <template #label>
+ <span class="tab-label">
+ <el-icon><Shop /></el-icon>
+ 渚涘簲鍟嗕緵璐ф眹鎬昏〃
+ </span>
+ </template>
+ </el-tab-pane>
+ </el-tabs>
+ </el-card>
+
+ <!-- 鏌ヨ鏉′欢 -->
+ <el-card class="search-card" shadow="never">
+ <el-form :model="searchForm" :inline="true" class="search-form">
+ <el-form-item label="鏃堕棿鑼冨洿锛�">
+ <el-date-picker
+ v-model="searchForm.dateRange"
+ type="daterange"
+ range-separator="鑷�"
+ start-placeholder="寮�濮嬫棩鏈�"
+ end-placeholder="缁撴潫鏃ユ湡"
+ format="YYYY-MM-DD"
+ value-format="YYYY-MM-DD"
+ style="width: 240px"
+ />
+ </el-form-item>
+ <el-form-item label="渚涘簲鍟嗭細" v-if="activeReport === 'supplierSummary'">
+ <el-select v-model="searchForm.supplierId" placeholder="璇烽�夋嫨渚涘簲鍟�" clearable style="width: 200px">
+ <el-option
+ v-for="supplier in supplierList"
+ :key="supplier.id"
+ :label="supplier.name"
+ :value="supplier.id"
+ />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="鍟嗗搧绫诲埆锛�" v-if="activeReport === 'businessSummary'">
+ <el-select v-model="searchForm.categoryId" placeholder="璇烽�夋嫨鍟嗗搧绫诲埆" clearable style="width: 200px">
+ <el-option
+ v-for="category in categoryList"
+ :key="category.id"
+ :label="category.name"
+ :value="category.id"
+ />
+ </el-select>
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" @click="handleSearch" :loading="loading">
+ <el-icon><Search /></el-icon>
+ 鏌ヨ
+ </el-button>
+ <el-button @click="resetSearch">
+ <el-icon><Refresh /></el-icon>
+ 閲嶇疆
+ </el-button>
+ <el-button type="success" @click="exportReport">
+ <el-icon><Download /></el-icon>
+ 瀵煎嚭
+ </el-button>
+ </el-form-item>
+ </el-form>
+ </el-card>
+
+ <!-- 鎶ヨ〃鍐呭 -->
+ <el-card class="report-content" shadow="never">
+ <!-- 閲囪喘璁㈠崟鎵ц姹囨�昏〃 -->
+ <div v-if="activeReport === 'orderSummary'" class="report-section">
+ <div class="section-header">
+ <h3>閲囪喘璁㈠崟鎵ц姹囨�昏〃</h3>
+ <div class="summary-stats">
+ <div class="stat-item">
+ <span class="stat-label">鎬昏鍗曟暟锛�</span>
+ <span class="stat-value">{{ orderSummaryStats.totalOrders }}</span>
+ </div>
+ <div class="stat-item">
+ <span class="stat-label">鎬婚噾棰濓細</span>
+ <span class="stat-value">楼{{ orderSummaryStats.totalAmount.toLocaleString() }}</span>
+ </div>
+ <div class="stat-item">
+ <span class="stat-label">瀹屾垚鐜囷細</span>
+ <span class="stat-value">{{ orderSummaryStats.completionRate }}%</span>
+ </div>
+ </div>
+ </div>
+
+ <el-table :data="orderSummaryData" border v-loading="loading" stripe>
+ <el-table-column label="璁㈠崟缂栧彿" prop="orderNo" width="180" fixed="left" />
+ <el-table-column label="渚涘簲鍟嗗悕绉�" prop="supplierName" width="150" />
+ <el-table-column label="璁㈠崟鏃ユ湡" prop="orderDate" width="120" />
+ <el-table-column label="璁″垝浜ゆ湡" prop="plannedDelivery" width="120" />
+ <el-table-column label="瀹為檯浜ゆ湡" prop="actualDelivery" width="120" />
+ <el-table-column label="璁㈠崟閲戦" prop="orderAmount" width="120">
+ <template #default="{ row }">楼{{ row.orderAmount.toLocaleString() }}</template>
+ </el-table-column>
+ <el-table-column label="宸蹭粯閲戦" prop="paidAmount" width="120">
+ <template #default="{ row }">楼{{ row.paidAmount.toLocaleString() }}</template>
+ </el-table-column>
+ <el-table-column label="瀹屾垚鐘舵��" prop="status" width="100">
+ <template #default="{ row }">
+ <el-tag :type="getStatusType(row.status)">{{ getStatusText(row.status) }}</el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column label="瀹屾垚鐜�" prop="completionRate" width="100">
+ <template #default="{ row }">{{ row.completionRate }}%</template>
+ </el-table-column>
+ <el-table-column label="寤惰繜澶╂暟" prop="delayDays" width="100">
+ <template #default="{ row }">
+ <span :class="{ 'delay-text': row.delayDays > 0 }">{{ row.delayDays }}</span>
+ </template>
+ </el-table-column>
+ </el-table>
+ </div>
+
+ <!-- 閲囪喘璁㈠崟鎵ц鏄庣粏琛� -->
+ <div v-if="activeReport === 'orderDetail'" class="report-section">
+ <div class="section-header">
+ <h3>閲囪喘璁㈠崟鎵ц鏄庣粏琛�</h3>
+ <div class="summary-stats">
+ <div class="stat-item">
+ <span class="stat-label">鏄庣粏鏉℃暟锛�</span>
+ <span class="stat-value">{{ orderDetailStats.totalItems }}</span>
+ </div>
+ <div class="stat-item">
+ <span class="stat-label">宸叉敹璐э細</span>
+ <span class="stat-value">{{ orderDetailStats.receivedItems }}</span>
+ </div>
+ <div class="stat-item">
+ <span class="stat-label">寰呮敹璐э細</span>
+ <span class="stat-value">{{ orderDetailStats.pendingItems }}</span>
+ </div>
+ </div>
+ </div>
+
+ <el-table :data="orderDetailData" border v-loading="loading" stripe>
+ <el-table-column label="璁㈠崟缂栧彿" prop="orderNo" width="150" fixed="left" />
+ <el-table-column label="鍟嗗搧缂栫爜" prop="productCode" width="120" />
+ <el-table-column label="鍟嗗搧鍚嶇О" prop="productName" width="200" />
+ <el-table-column label="瑙勬牸鍨嬪彿" prop="specification" width="150" />
+ <el-table-column label="鍗曚綅" prop="unit" width="80" />
+ <el-table-column label="璁″垝鏁伴噺" prop="plannedQuantity" width="100" />
+ <el-table-column label="宸叉敹璐ф暟閲�" prop="receivedQuantity" width="120" />
+ <el-table-column label="寰呮敹璐ф暟閲�" prop="pendingQuantity" width="120" />
+ <el-table-column label="鍗曚环" prop="unitPrice" width="100">
+ <template #default="{ row }">楼{{ row.unitPrice.toFixed(2) }}</template>
+ </el-table-column>
+ <el-table-column label="灏忚" prop="subtotal" width="120">
+ <template #default="{ row }">楼{{ row.subtotal.toLocaleString() }}</template>
+ </el-table-column>
+ <el-table-column label="鏀惰揣鐘舵��" prop="status" width="100">
+ <template #default="{ row }">
+ <el-tag :type="getReceiptStatusType(row.status)">{{ getReceiptStatusText(row.status) }}</el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column label="鏈�鍚庢敹璐ф棩鏈�" prop="lastReceiptDate" width="120" />
+ </el-table>
+ </div>
+
+ <!-- 閲囪喘涓氬姟姹囨�昏〃 -->
+ <div v-if="activeReport === 'businessSummary'" class="report-section">
+ <div class="section-header">
+ <h3>閲囪喘涓氬姟姹囨�昏〃</h3>
+ <div class="summary-stats">
+ <div class="stat-item">
+ <span class="stat-label">閲囪喘鎬婚锛�</span>
+ <span class="stat-value">楼{{ businessSummaryStats.totalAmount.toLocaleString() }}</span>
+ </div>
+ <div class="stat-item">
+ <span class="stat-label">鍟嗗搧绉嶇被锛�</span>
+ <span class="stat-value">{{ businessSummaryStats.productTypes }}</span>
+ </div>
+ <div class="stat-item">
+ <span class="stat-label">渚涘簲鍟嗘暟锛�</span>
+ <span class="stat-value">{{ businessSummaryStats.supplierCount }}</span>
+ </div>
+ </div>
+ </div>
+
+ <el-table :data="businessSummaryData" border v-loading="loading" stripe>
+ <el-table-column label="鍟嗗搧绫诲埆" prop="category" width="150" fixed="left" />
+ <el-table-column label="鍟嗗搧缂栫爜" prop="productCode" width="120" />
+ <el-table-column label="鍟嗗搧鍚嶇О" prop="productName" width="200" />
+ <el-table-column label="瑙勬牸鍨嬪彿" prop="specification" width="150" />
+ <el-table-column label="閲囪喘鏁伴噺" prop="purchaseQuantity" width="120" />
+ <el-table-column label="閲囪喘閲戦" prop="purchaseAmount" width="120">
+ <template #default="{ row }">楼{{ row.purchaseAmount.toLocaleString() }}</template>
+ </el-table-column>
+ <el-table-column label="骞冲潎鍗曚环" prop="avgPrice" width="100">
+ <template #default="{ row }">楼{{ row.avgPrice.toFixed(2) }}</template>
+ </el-table-column>
+ <el-table-column label="閲囪喘娆℃暟" prop="purchaseCount" width="100" />
+ <el-table-column label="涓昏渚涘簲鍟�" prop="mainSupplier" width="150" />
+ <el-table-column label="鏈�鍚庨噰璐棩鏈�" prop="lastPurchaseDate" width="120" />
+ </el-table>
+ </div>
+
+ <!-- 渚涘簲鍟嗕緵璐ф眹鎬昏〃 -->
+ <div v-if="activeReport === 'supplierSummary'" class="report-section">
+ <div class="section-header">
+ <h3>渚涘簲鍟嗕緵璐ф眹鎬昏〃</h3>
+ <div class="summary-stats">
+ <div class="stat-item">
+ <span class="stat-label">渚涘簲鍟嗘�绘暟锛�</span>
+ <span class="stat-value">{{ supplierSummaryStats.totalSuppliers }}</span>
+ </div>
+ <div class="stat-item">
+ <span class="stat-label">渚涜揣鎬婚锛�</span>
+ <span class="stat-value">楼{{ supplierSummaryStats.totalAmount.toLocaleString() }}</span>
+ </div>
+ <div class="stat-item">
+ <span class="stat-label">骞冲潎璇勫垎锛�</span>
+ <span class="stat-value">{{ supplierSummaryStats.avgRating.toFixed(1) }}</span>
+ </div>
+ </div>
+ </div>
+
+ <el-table :data="supplierSummaryData" border v-loading="loading" stripe>
+ <el-table-column label="渚涘簲鍟嗙紪鐮�" prop="supplierCode" width="120" fixed="left" />
+ <el-table-column label="渚涘簲鍟嗗悕绉�" prop="supplierName" width="200" />
+ <el-table-column label="鑱旂郴浜�" prop="contactPerson" width="120" />
+ <el-table-column label="鑱旂郴鐢佃瘽" prop="phone" width="130" />
+ <el-table-column label="渚涜揣璁㈠崟鏁�" prop="orderCount" width="120" />
+ <el-table-column label="渚涜揣閲戦" prop="supplyAmount" width="120">
+ <template #default="{ row }">楼{{ row.supplyAmount.toLocaleString() }}</template>
+ </el-table-column>
+ <el-table-column label="宸蹭粯閲戦" prop="paidAmount" width="120">
+ <template #default="{ row }">楼{{ row.paidAmount.toLocaleString() }}</template>
+ </el-table-column>
+ <el-table-column label="鏈粯閲戦" prop="unpaidAmount" width="120">
+ <template #default="{ row }">楼{{ row.unpaidAmount.toLocaleString() }}</template>
+ </el-table-column>
+ <el-table-column label="鎸夋椂浜よ揣鐜�" prop="onTimeRate" width="120">
+ <template #default="{ row }">{{ row.onTimeRate }}%</template>
+ </el-table-column>
+ <el-table-column label="璐ㄩ噺璇勫垎" prop="qualityRating" width="100">
+ <template #default="{ row }">
+ <el-rate v-model="row.qualityRating" disabled show-score text-color="#ff9900" />
+ </template>
+ </el-table-column>
+ <el-table-column label="鍚堜綔鐘舵��" prop="status" width="100">
+ <template #default="{ row }">
+ <el-tag :type="getSupplierStatusType(row.status)">{{ getSupplierStatusText(row.status) }}</el-tag>
+ </template>
+ </el-table-column>
+ </el-table>
+ </div>
+ </el-card>
+ </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted } from 'vue'
+import { ElMessage } from 'element-plus'
+import { Document, List, TrendCharts, Shop, Search, Refresh, Download } from '@element-plus/icons-vue'
+
+// 鍝嶅簲寮忔暟鎹�
+const loading = ref(false)
+const activeReport = ref('orderSummary')
+
+// 鎼滅储琛ㄥ崟
+const searchForm = reactive({
+ dateRange: [],
+ supplierId: '',
+ categoryId: ''
+})
+
+// 渚涘簲鍟嗗垪琛�
+const supplierList = ref([
+ { id: 1, name: '姹熻嫃鍗庤仈鐢靛瓙绉戞妧鏈夐檺鍏徃' },
+ { id: 2, name: '涓婃捣绮惧瘑鏈烘鍒堕�犳湁闄愬叕鍙�' },
+ { id: 3, name: '娣卞湷鏅鸿兘璁惧鏈夐檺鍏徃' },
+ { id: 4, name: '鍖椾含鏂版潗鏂欑鎶�鏈夐檺鍏徃' },
+ { id: 5, name: '骞垮窞鐢靛瓙鍏冨櫒浠舵湁闄愬叕鍙�' }
+])
+
+// 鍟嗗搧绫诲埆鍒楄〃
+const categoryList = ref([
+ { id: 1, name: '鐢靛瓙鍏冨櫒浠�' },
+ { id: 2, name: '鏈烘璁惧' },
+ { id: 3, name: '鍘熸潗鏂�' },
+ { id: 4, name: '鍔炲叕鐢ㄥ搧' },
+ { id: 5, name: '鍖呰鏉愭枡' }
+])
+
+// 缁熻鏁版嵁
+const orderSummaryStats = ref({
+ totalOrders: 156,
+ totalAmount: 2580000,
+ completionRate: 87.5
+})
+
+const orderDetailStats = ref({
+ totalItems: 1248,
+ receivedItems: 1089,
+ pendingItems: 159
+})
+
+const businessSummaryStats = ref({
+ totalAmount: 2580000,
+ productTypes: 89,
+ supplierCount: 25
+})
+
+const supplierSummaryStats = ref({
+ totalSuppliers: 25,
+ totalAmount: 2580000,
+ avgRating: 4.2
+})
+
+// 閲囪喘璁㈠崟鎵ц姹囨�昏〃鏁版嵁
+const orderSummaryData = ref([
+ {
+ orderNo: 'PO20241201001',
+ supplierName: '姹熻嫃鍗庤仈鐢靛瓙绉戞妧鏈夐檺鍏徃',
+ orderDate: '2024-12-01',
+ plannedDelivery: '2024-12-15',
+ actualDelivery: '2024-12-14',
+ orderAmount: 125000,
+ paidAmount: 100000,
+ status: 'completed',
+ completionRate: 100,
+ delayDays: -1
+ },
+ {
+ orderNo: 'PO20241201002',
+ supplierName: '涓婃捣绮惧瘑鏈烘鍒堕�犳湁闄愬叕鍙�',
+ orderDate: '2024-12-02',
+ plannedDelivery: '2024-12-20',
+ actualDelivery: '2024-12-22',
+ orderAmount: 280000,
+ paidAmount: 140000,
+ status: 'partial',
+ completionRate: 75,
+ delayDays: 2
+ },
+ {
+ orderNo: 'PO20241201003',
+ supplierName: '娣卞湷鏅鸿兘璁惧鏈夐檺鍏徃',
+ orderDate: '2024-12-03',
+ plannedDelivery: '2024-12-25',
+ actualDelivery: '',
+ orderAmount: 180000,
+ paidAmount: 0,
+ status: 'pending',
+ completionRate: 0,
+ delayDays: 0
+ },
+ {
+ orderNo: 'PO20241201004',
+ supplierName: '鍖椾含鏂版潗鏂欑鎶�鏈夐檺鍏徃',
+ orderDate: '2024-12-04',
+ plannedDelivery: '2024-12-18',
+ actualDelivery: '2024-12-18',
+ orderAmount: 95000,
+ paidAmount: 95000,
+ status: 'completed',
+ completionRate: 100,
+ delayDays: 0
+ },
+ {
+ orderNo: 'PO20241201005',
+ supplierName: '骞垮窞鐢靛瓙鍏冨櫒浠舵湁闄愬叕鍙�',
+ orderDate: '2024-12-05',
+ plannedDelivery: '2024-12-28',
+ actualDelivery: '',
+ orderAmount: 220000,
+ paidAmount: 0,
+ status: 'pending',
+ completionRate: 0,
+ delayDays: 0
+ }
+])
+
+// 閲囪喘璁㈠崟鎵ц鏄庣粏琛ㄦ暟鎹�
+const orderDetailData = ref([
+ {
+ orderNo: 'PO20241201001',
+ productCode: 'EL001',
+ productName: '鐢甸樆鍣� 1K惟 卤5%',
+ specification: '1/4W 纰宠啘鐢甸樆',
+ unit: '涓�',
+ plannedQuantity: 1000,
+ receivedQuantity: 1000,
+ pendingQuantity: 0,
+ unitPrice: 0.15,
+ subtotal: 150,
+ status: 'completed',
+ lastReceiptDate: '2024-12-14'
+ },
+ {
+ orderNo: 'PO20241201001',
+ productCode: 'EL002',
+ productName: '鐢靛鍣� 100渭F',
+ specification: '25V 閾濈數瑙g數瀹�',
+ unit: '涓�',
+ plannedQuantity: 500,
+ receivedQuantity: 500,
+ pendingQuantity: 0,
+ unitPrice: 0.85,
+ subtotal: 425,
+ status: 'completed',
+ lastReceiptDate: '2024-12-14'
+ },
+ {
+ orderNo: 'PO20241201002',
+ productCode: 'ME001',
+ productName: '绮惧瘑杞存壙',
+ specification: '6205-2RS 娣辨矡鐞冭酱鎵�',
+ unit: '涓�',
+ plannedQuantity: 200,
+ receivedQuantity: 150,
+ pendingQuantity: 50,
+ unitPrice: 25.5,
+ subtotal: 5100,
+ status: 'partial',
+ lastReceiptDate: '2024-12-20'
+ },
+ {
+ orderNo: 'PO20241201002',
+ productCode: 'ME002',
+ productName: '涓嶉攬閽㈣灪涓�',
+ specification: 'M8脳20 304涓嶉攬閽�',
+ unit: '涓�',
+ plannedQuantity: 1000,
+ receivedQuantity: 1000,
+ pendingQuantity: 0,
+ unitPrice: 0.8,
+ subtotal: 800,
+ status: 'completed',
+ lastReceiptDate: '2024-12-20'
+ },
+ {
+ orderNo: 'PO20241201003',
+ productCode: 'SM001',
+ productName: '鏅鸿兘浼犳劅鍣�',
+ specification: '娓╁害浼犳劅鍣� DS18B20',
+ unit: '涓�',
+ plannedQuantity: 300,
+ receivedQuantity: 0,
+ pendingQuantity: 300,
+ unitPrice: 12.5,
+ subtotal: 3750,
+ status: 'pending',
+ lastReceiptDate: ''
+ }
+])
+
+// 閲囪喘涓氬姟姹囨�昏〃鏁版嵁
+const businessSummaryData = ref([
+ {
+ category: '鐢靛瓙鍏冨櫒浠�',
+ productCode: 'EL001',
+ productName: '鐢甸樆鍣� 1K惟 卤5%',
+ specification: '1/4W 纰宠啘鐢甸樆',
+ purchaseQuantity: 5000,
+ purchaseAmount: 750,
+ avgPrice: 0.15,
+ purchaseCount: 8,
+ mainSupplier: '姹熻嫃鍗庤仈鐢靛瓙绉戞妧鏈夐檺鍏徃',
+ lastPurchaseDate: '2024-12-01'
+ },
+ {
+ category: '鐢靛瓙鍏冨櫒浠�',
+ productCode: 'EL002',
+ productName: '鐢靛鍣� 100渭F',
+ specification: '25V 閾濈數瑙g數瀹�',
+ purchaseQuantity: 2500,
+ purchaseAmount: 2125,
+ avgPrice: 0.85,
+ purchaseCount: 6,
+ mainSupplier: '姹熻嫃鍗庤仈鐢靛瓙绉戞妧鏈夐檺鍏徃',
+ lastPurchaseDate: '2024-12-01'
+ },
+ {
+ category: '鏈烘璁惧',
+ productCode: 'ME001',
+ productName: '绮惧瘑杞存壙',
+ specification: '6205-2RS 娣辨矡鐞冭酱鎵�',
+ purchaseQuantity: 800,
+ purchaseAmount: 20400,
+ avgPrice: 25.5,
+ purchaseCount: 4,
+ mainSupplier: '涓婃捣绮惧瘑鏈烘鍒堕�犳湁闄愬叕鍙�',
+ lastPurchaseDate: '2024-12-02'
+ },
+ {
+ category: '鏈烘璁惧',
+ productCode: 'ME002',
+ productName: '涓嶉攬閽㈣灪涓�',
+ specification: 'M8脳20 304涓嶉攬閽�',
+ purchaseQuantity: 5000,
+ purchaseAmount: 4000,
+ avgPrice: 0.8,
+ purchaseCount: 12,
+ mainSupplier: '涓婃捣绮惧瘑鏈烘鍒堕�犳湁闄愬叕鍙�',
+ lastPurchaseDate: '2024-12-02'
+ },
+ {
+ category: '鏅鸿兘璁惧',
+ productCode: 'SM001',
+ productName: '鏅鸿兘浼犳劅鍣�',
+ specification: '娓╁害浼犳劅鍣� DS18B20',
+ purchaseQuantity: 1200,
+ purchaseAmount: 15000,
+ avgPrice: 12.5,
+ purchaseCount: 5,
+ mainSupplier: '娣卞湷鏅鸿兘璁惧鏈夐檺鍏徃',
+ lastPurchaseDate: '2024-12-03'
+ }
+])
+
+// 渚涘簲鍟嗕緵璐ф眹鎬昏〃鏁版嵁
+const supplierSummaryData = ref([
+ {
+ supplierCode: 'SUP001',
+ supplierName: '姹熻嫃鍗庤仈鐢靛瓙绉戞妧鏈夐檺鍏徃',
+ contactPerson: '寮犵粡鐞�',
+ phone: '0512-88888888',
+ orderCount: 45,
+ supplyAmount: 850000,
+ paidAmount: 680000,
+ unpaidAmount: 170000,
+ onTimeRate: 95,
+ qualityRating: 4.5,
+ status: 'active'
+ },
+ {
+ supplierCode: 'SUP002',
+ supplierName: '涓婃捣绮惧瘑鏈烘鍒堕�犳湁闄愬叕鍙�',
+ contactPerson: '鏉庢��',
+ phone: '021-66666666',
+ orderCount: 32,
+ supplyAmount: 1200000,
+ paidAmount: 900000,
+ unpaidAmount: 300000,
+ onTimeRate: 88,
+ qualityRating: 4.2,
+ status: 'active'
+ },
+ {
+ supplierCode: 'SUP003',
+ supplierName: '娣卞湷鏅鸿兘璁惧鏈夐檺鍏徃',
+ contactPerson: '鐜嬪伐绋嬪笀',
+ phone: '0755-77777777',
+ orderCount: 28,
+ supplyAmount: 680000,
+ paidAmount: 400000,
+ unpaidAmount: 280000,
+ onTimeRate: 92,
+ qualityRating: 4.3,
+ status: 'active'
+ },
+ {
+ supplierCode: 'SUP004',
+ supplierName: '鍖椾含鏂版潗鏂欑鎶�鏈夐檺鍏徃',
+ contactPerson: '闄堝崥澹�',
+ phone: '010-55555555',
+ orderCount: 18,
+ supplyAmount: 320000,
+ paidAmount: 250000,
+ unpaidAmount: 70000,
+ onTimeRate: 85,
+ qualityRating: 4.0,
+ status: 'active'
+ },
+ {
+ supplierCode: 'SUP005',
+ supplierName: '骞垮窞鐢靛瓙鍏冨櫒浠舵湁闄愬叕鍙�',
+ contactPerson: '鍒樼粡鐞�',
+ phone: '020-44444444',
+ orderCount: 22,
+ supplyAmount: 480000,
+ paidAmount: 200000,
+ unpaidAmount: 280000,
+ onTimeRate: 78,
+ qualityRating: 3.8,
+ status: 'warning'
+ }
+])
+
+// 鏂规硶
+const handleReportChange = (tabName) => {
+ activeReport.value = tabName
+ handleSearch()
+}
+
+const handleSearch = () => {
+ loading.value = true
+ // 妯℃嫙API璋冪敤
+ setTimeout(() => {
+ loading.value = false
+ ElMessage.success('鏌ヨ瀹屾垚')
+ }, 1000)
+}
+
+const resetSearch = () => {
+ Object.assign(searchForm, {
+ dateRange: [],
+ supplierId: '',
+ categoryId: ''
+ })
+ handleSearch()
+}
+
+const exportReport = () => {
+ ElMessage.success('瀵煎嚭鍔熻兘寮�鍙戜腑...')
+}
+
+// 鐘舵�佺浉鍏虫柟娉�
+const getStatusType = (status) => {
+ const statusMap = {
+ completed: 'success',
+ partial: 'warning',
+ pending: 'info'
+ }
+ return statusMap[status] || 'info'
+}
+
+const getStatusText = (status) => {
+ const statusMap = {
+ completed: '宸插畬鎴�',
+ partial: '閮ㄥ垎瀹屾垚',
+ pending: '寰呮墽琛�'
+ }
+ return statusMap[status] || '鏈煡'
+}
+
+const getReceiptStatusType = (status) => {
+ const statusMap = {
+ completed: 'success',
+ partial: 'warning',
+ pending: 'info'
+ }
+ return statusMap[status] || 'info'
+}
+
+const getReceiptStatusText = (status) => {
+ const statusMap = {
+ completed: '宸叉敹璐�',
+ partial: '閮ㄥ垎鏀惰揣',
+ pending: '寰呮敹璐�'
+ }
+ return statusMap[status] || '鏈煡'
+}
+
+const getSupplierStatusType = (status) => {
+ const statusMap = {
+ active: 'success',
+ warning: 'warning',
+ inactive: 'info'
+ }
+ return statusMap[status] || 'info'
+}
+
+const getSupplierStatusText = (status) => {
+ const statusMap = {
+ active: '姝e父鍚堜綔',
+ warning: '闇�鍏虫敞',
+ inactive: '鏆傚仠鍚堜綔'
+ }
+ return statusMap[status] || '鏈煡'
+}
+
+onMounted(() => {
+ // 璁剧疆榛樿鏃堕棿鑼冨洿涓烘渶杩�30澶�
+ const endDate = new Date()
+ const startDate = new Date()
+ startDate.setDate(startDate.getDate() - 30)
+
+ searchForm.dateRange = [
+ startDate.toISOString().split('T')[0],
+ endDate.toISOString().split('T')[0]
+ ]
+})
+</script>
+
+<style scoped>
+.app-container {
+ padding: 20px;
+ background-color: #f5f7fa;
+ min-height: 100vh;
+}
+
+.page-header {
+ text-align: center;
+ margin-bottom: 20px;
+ padding: 20px;
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ border-radius: 10px;
+ color: white;
+}
+
+.page-header h2 {
+ margin: 0 0 10px 0;
+ font-size: 28px;
+ font-weight: 600;
+}
+
+.page-header p {
+ margin: 0;
+ font-size: 16px;
+ opacity: 0.9;
+}
+
+.report-selector {
+ margin-bottom: 20px;
+ border-radius: 8px;
+}
+
+.tab-label {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+
+.search-card {
+ margin-bottom: 20px;
+ border-radius: 8px;
+}
+
+.search-form {
+ margin-bottom: 0;
+}
+
+.report-content {
+ border-radius: 8px;
+}
+
+.report-section {
+ min-height: 400px;
+}
+
+.section-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 20px;
+ padding-bottom: 15px;
+ border-bottom: 2px solid #e4e7ed;
+}
+
+.section-header h3 {
+ margin: 0;
+ font-size: 20px;
+ font-weight: 600;
+ color: #303133;
+}
+
+.summary-stats {
+ display: flex;
+ gap: 30px;
+}
+
+.stat-item {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+}
+
+.stat-label {
+ font-size: 14px;
+ color: #606266;
+ margin-bottom: 5px;
+}
+
+.stat-value {
+ font-size: 18px;
+ font-weight: 600;
+ color: #409EFF;
+}
+
+.delay-text {
+ color: #F56C6C;
+ font-weight: 600;
+}
+
+:deep(.el-table) {
+ border-radius: 8px;
+ overflow: hidden;
+}
+
+:deep(.el-table th) {
+ background-color: #f8f9fa;
+ color: #606266;
+ font-weight: 600;
+}
+
+:deep(.el-table--striped .el-table__body tr.el-table__row--striped td) {
+ background-color: #fafafa;
+}
+
+:deep(.el-tabs__header) {
+ margin-bottom: 0;
+}
+
+:deep(.el-tabs__nav-wrap) {
+ padding: 0 20px;
+}
+
+:deep(.el-tabs__item) {
+ font-size: 16px;
+ font-weight: 500;
+}
+
+:deep(.el-tabs__item.is-active) {
+ color: #409EFF;
+}
+
+:deep(.el-rate) {
+ display: flex;
+ align-items: center;
+}
+
+:deep(.el-rate__text) {
+ margin-left: 8px;
+ font-size: 14px;
+ color: #606266;
+}
+</style>
diff --git a/src/views/procurementManagement/returnManagement/index.vue b/src/views/procurementManagement/returnManagement/index.vue
index d86e524..2a54083 100644
--- a/src/views/procurementManagement/returnManagement/index.vue
+++ b/src/views/procurementManagement/returnManagement/index.vue
@@ -21,7 +21,6 @@
<el-card class="table-card" shadow="never">
<div class="table-header">
<el-button type="primary" @click="openDialog('add')">鏂板閫�璐у崟</el-button>
- <el-button type="success" @click="handleBatchApprove">鎵归噺瀹℃牳</el-button>
<el-button type="danger" @click="handleBatchDelete">鎵归噺鍒犻櫎</el-button>
</div>
@@ -42,9 +41,6 @@
<el-tag :type="getStatusType(row.status)">{{ getStatusText(row.status) }}</el-tag>
</template>
</el-table-column>
- <el-table-column label="閫�璐ч噾棰�" prop="returnAmount" width="120">
- <template #default="{ row }">楼{{ row.returnAmount.toFixed(2) }}</template>
- </el-table-column>
<el-table-column label="鍒涘缓鏃堕棿" prop="createTime" width="180" />
<el-table-column label="鎿嶄綔" width="200" align="center">
<template #default="{ row }">
@@ -54,6 +50,14 @@
</template>
</el-table-column>
</el-table>
+ <!-- 鍒嗛〉 -->
+ <pagination
+ :total="total"
+ layout="total, sizes, prev, pager, next, jumper"
+ :page="pagination.current"
+ :limit="pagination.size"
+ @pagination="handleCurrentChange"
+ />
</el-card>
<el-dialog v-model="dialogVisible" :title="dialogType === 'add' ? '鏂板閫�璐у崟' : '缂栬緫閫�璐у崟'" width="600px">
@@ -65,7 +69,9 @@
</el-select>
</el-form-item>
<el-form-item label="鍏宠仈鍗曞彿">
- <el-input v-model="formData.relatedNo" placeholder="璇疯緭鍏ュ叧鑱斿崟鍙�" />
+ <el-select v-model="formData.relatedNo" placeholder="璇烽�夋嫨鍏宠仈鍗曞彿" style="width: 100%">
+ <el-option v-for="item in onList" :key="item.arrivalNo" :label="item.arrivalNo" :value="item.arrivalNo" />
+ </el-select>
</el-form-item>
<el-form-item label="渚涘簲鍟嗗悕绉�">
<el-input v-model="formData.supplierName" placeholder="璇疯緭鍏ヤ緵搴斿晢鍚嶇О" />
@@ -90,13 +96,49 @@
</template>
<script setup>
-import { ref, reactive } from 'vue'
+import { ref, reactive,onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
+import Pagination from '@/components/PIMTable/Pagination.vue'
+import {listPage,add,update,del} from "@/api/procurementManagement/returnManagement.js"
+import {listPageCopy} from "@/api/procurementManagement/arrivalManagement.js"
+
+onMounted(() => {
+ getList()
+ list()
+})
+const onList = ref([])
+const list = () =>{
+ listPageCopy({current:-1}).then(res=>{
+ if(res.code === 200){
+ onList.value = res.data.records
+ }
+ })
+}
+const tableData = ref([])
+const getList = () => {
+ loading.value = true
+ listPage({...searchForm,...pagination}).then(res =>{
+ if(res.code === 200){
+ tableData.value = res.data.records
+ console.log(tableData.value)
+ total.value = res.data.total
+ loading.value = false
+ }
+ })
+}
const loading = ref(false)
const dialogVisible = ref(false)
const dialogType = ref('add')
const selectedRows = ref([])
+
+
+const pagination = reactive({
+ current: 1,
+ size: 10
+})
+
+const total = ref(0)
const searchForm = reactive({
returnNo: '',
@@ -108,25 +150,9 @@
relatedNo: '',
supplierName: '',
returnReason: '',
- remark: ''
+ remark: '',
+ status: ''
})
-
-const mockData = [
- {
- id: 1,
- returnNo: 'RT20241201001',
- relatedNo: 'PO20241201001',
- returnType: 'purchase',
- supplierName: '渚涘簲鍟咥',
- status: 'approved',
- returnAmount: 500.00,
- createTime: '2025-12-01 17:30:00',
- returnReason: '璐ㄩ噺闂',
- remark: '鍟嗗搧瀛樺湪璐ㄩ噺闂'
- }
-]
-
-const tableData = ref([...mockData])
const getReturnTypeText = (type) => {
const typeMap = { purchase: '閲囪喘閫�璐�', quality: '璐ㄦ閫�璐�' }
@@ -145,7 +171,7 @@
const handleSearch = () => {
loading.value = true
- setTimeout(() => { loading.value = false }, 500)
+ getList()
}
const resetSearch = () => {
@@ -154,13 +180,15 @@
const openDialog = (type, row = {}) => {
dialogType.value = type
+ obj.id = row.id
if (type === 'edit' && row.id) {
Object.assign(formData, {
returnType: row.returnType,
relatedNo: row.relatedNo,
supplierName: row.supplierName,
returnReason: row.returnReason,
- remark: row.remark
+ remark: row.remark,
+ status: row.status
})
} else {
Object.assign(formData, {
@@ -168,35 +196,43 @@
relatedNo: '',
supplierName: '',
returnReason: '',
- remark: ''
+ remark: '',
+ status: 'pending'
})
}
dialogVisible.value = true
}
-
+const obj = reactive({
+ id: ''
+})
const handleSubmit = () => {
if (dialogType.value === 'add') {
- const newReturn = {
- id: Date.now(),
- returnNo: `RT${Date.now()}`,
- relatedNo: formData.relatedNo,
- returnType: formData.returnType,
- supplierName: formData.supplierName,
- status: 'pending',
- returnAmount: 0,
- createTime: new Date().toLocaleString(),
- returnReason: formData.returnReason,
- remark: formData.remark
+ formData.status = 'pending'
+ add(formData).then(res => {
+ if(res.code === 200){
+ ElMessage.success('鏂板鎴愬姛')
+ getList()
}
- tableData.value.unshift(newReturn)
- ElMessage.success('鏂板鎴愬姛')
+ })
+ }else{
+ update({...formData,...obj}).then(res => {
+ if(res.code === 200){
+ ElMessage.success('淇敼鎴愬姛')
+ getList()
+ }
+ })
}
dialogVisible.value = false
}
const handleApprove = (row) => {
row.status = 'approved'
- ElMessage.success('瀹℃牳閫氳繃')
+ update(row).then(res => {
+ if(res.code === 200){
+ ElMessage.success('瀹℃牳鎴愬姛')
+ getList()
+ }
+ })
}
const handleDelete = (row) => {
@@ -205,20 +241,24 @@
cancelButtonText: '鍙栨秷',
type: 'warning'
}).then(() => {
- const index = tableData.value.findIndex(item => item.id === row.id)
- if (index !== -1) {
- tableData.value.splice(index, 1)
- ElMessage.success('鍒犻櫎鎴愬姛')
- }
+ let ids = [row.id]
+ del(ids).then(res => {
+ if(res.code === 200){
+ ElMessage.success('鍒犻櫎鎴愬姛')
+ getList()
+ }
+ })
})
}
-const handleBatchApprove = () => {
- ElMessage.success('鎵归噺瀹℃牳鎴愬姛')
-}
-
const handleBatchDelete = () => {
- ElMessage.success('鎵归噺鍒犻櫎鎴愬姛')
+ let ids = selectedRows.value.map(item => item.id)
+ del(ids).then(res => {
+ if(res.code === 200){
+ ElMessage.success('鎵归噺鍒犻櫎鎴愬姛')
+ getList()
+ }
+ })
}
const handleSelectionChange = (rows) => {
diff --git a/src/views/reportAnalysis/dataDashboard/index.vue b/src/views/reportAnalysis/dataDashboard/index.vue
index 6fe840b..9848898 100644
--- a/src/views/reportAnalysis/dataDashboard/index.vue
+++ b/src/views/reportAnalysis/dataDashboard/index.vue
@@ -12,6 +12,7 @@
<!-- 椤堕儴鏍囬鏍� -->
<div class="dashboard-header">
+ <div class="factory-name">{{ userStore.currentFactoryName }}</div>
</div>
<!-- 涓昏鍐呭鍖哄煙 -->
@@ -241,6 +242,7 @@
import { ref, onMounted, onBeforeUnmount, nextTick } from 'vue'
import autofit from 'autofit.js'
import Echarts from "@/components/Echarts/echarts.vue";
+import useUserStore from '@/store/modules/user'
import {
analysisCustomerContractAmounts, getAmountHalfYear,
homeTodos,
@@ -258,6 +260,9 @@
// 鍏ㄥ睆鐩稿叧鐘舵��
const isFullscreen = ref(false);
+
+// 鐢ㄦ埛store
+const userStore = useUserStore()
// 鍝嶅簲寮忔暟鎹�
const currentTime = ref('')
@@ -915,7 +920,7 @@
// 浣跨敤nextTick纭繚DOM瀹屽叏娓叉煋鍚庡啀鍒濆鍖栧浘琛�
nextTick(() => {
// 鍒濆鍖朼utofit鑷�傚簲
- autofit.init({ dh: 1440, dw: 2560, el: '.data-dashboard', resize: true }, false)
+ autofit.init({ dh: 1080, dw: 1920, el: '.data-dashboard', resize: true }, false)
// 娣诲姞鑷姩婊氬姩鍔ㄧ敾鏁堟灉 - 瀹㈡埛淇℃伅鍒楄〃
const contractList = refContractList.value
@@ -1044,7 +1049,6 @@
position: relative;
width: 100%;
height: 100%;
- overflow: hidden;
background-image: url("@/assets/BI/backImage@2x.png");
background-size: cover;
background-position: center;
@@ -1090,6 +1094,17 @@
background-size: cover;
background-position: center;
background-repeat: no-repeat;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.factory-name {
+ font-weight: 600;
+font-size: 52px;
+color: #FFFFFF;
+top: 32px;
+position: absolute;
}
.fullscreen-btn {
diff --git a/src/views/salesManagement/paymentShipping/index.vue b/src/views/salesManagement/paymentShipping/index.vue
index ae59d08..0bcfd87 100644
--- a/src/views/salesManagement/paymentShipping/index.vue
+++ b/src/views/salesManagement/paymentShipping/index.vue
@@ -91,8 +91,8 @@
<pagination
:total="total"
layout="total, sizes, prev, pager, next, jumper"
- :page="pagination.currentPage"
- :limit="pagination.pageSize"
+ :page="pagination.current"
+ :limit="pagination.size"
@pagination="handleCurrentChange"
/>
</el-card>
@@ -496,8 +496,8 @@
}
const handleCurrentChange = (val) => {
- pagination.currentPage = val.page
- pagination.pageSize = val.limit
+ pagination.current = val.page
+ pagination.size = val.limit
}
</script>
--
Gitblit v1.9.3