From 530297eda34d030e9b3c8bd168ad818fde1a6ded Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期一, 29 九月 2025 17:24:51 +0800
Subject: [PATCH] 临期相关页面

---
 src/views/qualityManagement/nearExpiryReturn/index.vue            |  519 ++++++++++++++++++++++++
 src/views/customerService/expiryAfterSales/index.vue              |  330 +++++++++++++++
 src/api/customerService/index.js                                  |   36 +
 bin/build.bat                                                     |   22 
 src/api/qualityManagement/nearExpiryReturn.js                     |   45 ++
 src/views/customerService/expiryAfterSales/components/formDia.vue |  291 +++++++++++++
 6 files changed, 1,232 insertions(+), 11 deletions(-)

diff --git a/bin/build.bat b/bin/build.bat
index 8868727..a4cc0df 100644
--- a/bin/build.bat
+++ b/bin/build.bat
@@ -1,12 +1,12 @@
-@echo off
-echo.
-echo [信息] 打包Web工程,生成dist文件。
-echo.
-
-%~d0
-cd %~dp0
-
-cd ..
-yarn build:prod
-
+@echo off
+echo.
+echo [锟斤拷息] 锟斤拷锟絎eb锟斤拷锟教o拷锟斤拷锟斤拷dist锟侥硷拷锟斤拷
+echo.
+
+%~d0
+cd %~dp0
+
+cd ..
+yarn build:prod
+
 pause
\ No newline at end of file
diff --git a/src/api/customerService/index.js b/src/api/customerService/index.js
index e83cdfb..b92033b 100644
--- a/src/api/customerService/index.js
+++ b/src/api/customerService/index.js
@@ -40,4 +40,40 @@
     method: 'post',
     data: query,
   })
+}
+
+// 涓存湡鍞悗绠$悊-鍒嗛〉鏌ヨ
+export function expiryAfterSalesListPage(query) {
+  return request({
+    url: '/expiryAfterSales/listPage',
+    method: 'get',
+    params: query,
+  })
+}
+
+// 涓存湡鍞悗绠$悊-鏂板
+export function expiryAfterSalesAdd(query) {
+  return request({
+    url: '/expiryAfterSales/add',
+    method: 'post',
+    data: query,
+  })
+}
+
+// 涓存湡鍞悗绠$悊-鏇存柊
+export function expiryAfterSalesUpdate(query) {
+  return request({
+    url: '/expiryAfterSales/update',
+    method: 'post',
+    data: query,
+  })
+}
+
+// 涓存湡鍞悗绠$悊-鍒犻櫎
+export function expiryAfterSalesDelete(query) {
+  return request({
+    url: '/expiryAfterSales/delete',
+    method: 'delete',
+    data: query,
+  })
 }
\ No newline at end of file
diff --git a/src/api/qualityManagement/nearExpiryReturn.js b/src/api/qualityManagement/nearExpiryReturn.js
new file mode 100644
index 0000000..af1f55a
--- /dev/null
+++ b/src/api/qualityManagement/nearExpiryReturn.js
@@ -0,0 +1,45 @@
+import request from '@/utils/request'
+
+// 鏌ヨ涓存湡閫�鍥炲彴璐﹀垪琛�
+export function nearExpiryReturnListPage(query) {
+    return request({
+        url: '/quality/nearExpiryReturn/listPage',
+        method: 'get',
+        params: query,
+    })
+}
+
+// 鏂板涓存湡閫�鍥炲彴璐�
+export function nearExpiryReturnAdd(data) {
+    return request({
+        url: '/quality/nearExpiryReturn/add',
+        method: 'post',
+        data: data,
+    })
+}
+
+// 淇敼涓存湡閫�鍥炲彴璐�
+export function nearExpiryReturnUpdate(data) {
+    return request({
+        url: '/quality/nearExpiryReturn/update',
+        method: 'post',
+        data: data,
+    })
+}
+
+// 鍒犻櫎涓存湡閫�鍥炲彴璐�
+export function nearExpiryReturnDel(ids) {
+    return request({
+        url: '/quality/nearExpiryReturn/del',
+        method: 'delete',
+        data: ids,
+    })
+}
+
+// 鑾峰彇涓存湡閫�鍥炲彴璐﹁鎯�
+export function nearExpiryReturnDetail(id) {
+    return request({
+        url: '/quality/nearExpiryReturn/' + id,
+        method: 'get',
+    })
+}
diff --git a/src/views/customerService/expiryAfterSales/components/formDia.vue b/src/views/customerService/expiryAfterSales/components/formDia.vue
new file mode 100644
index 0000000..a6ae725
--- /dev/null
+++ b/src/views/customerService/expiryAfterSales/components/formDia.vue
@@ -0,0 +1,291 @@
+<template>
+  <div>
+    <el-dialog
+        v-model="dialogFormVisible"
+        :title="dialogTitle"
+        width="70%"
+        @close="closeDia"
+    >
+			<el-form
+				:model="form"
+				label-width="140px"
+				label-position="top"
+				:rules="rules"
+				ref="formRef"
+			>
+				<el-row :gutter="30">
+					<el-col :span="12">
+						<el-form-item label="涓存湡浜у搧鍚嶇О锛�" prop="productName">
+							<el-input
+								v-model="form.productName"
+								placeholder="璇疯緭鍏ヤ骇鍝佸悕绉�"
+								clearable
+								:disabled="operationType === 'view'"
+							/>
+						</el-form-item>
+					</el-col>
+					<el-col :span="12">
+						<el-form-item label="浜у搧鎵瑰彿锛�" prop="batchNumber">
+							<el-input
+								v-model="form.batchNumber"
+								placeholder="璇疯緭鍏ヤ骇鍝佹壒鍙�"
+								clearable
+								:disabled="operationType === 'view'"
+							/>
+						</el-form-item>
+					</el-col>
+				</el-row>
+				<el-row :gutter="30">
+					<el-col :span="12">
+						<el-form-item label="涓存湡鏃ユ湡锛�" prop="expiryDate">
+							<el-date-picker
+								style="width: 100%"
+								v-model="form.expiryDate"
+								value-format="YYYY-MM-DD"
+								format="YYYY-MM-DD"
+								type="date"
+								placeholder="璇烽�夋嫨涓存湡鏃ユ湡"
+								clearable
+								:disabled="operationType === 'view'"
+							/>
+						</el-form-item>
+					</el-col>
+					<el-col :span="12">
+						<el-form-item label="搴撳瓨鏁伴噺锛�" prop="stockQuantity">
+							<el-input-number
+								v-model="form.stockQuantity"
+								:min="0"
+								placeholder="璇疯緭鍏ュ簱瀛樻暟閲�"
+								style="width: 100%"
+								:disabled="operationType === 'view'"
+							/>
+						</el-form-item>
+					</el-col>
+				</el-row>
+				<el-row :gutter="30">
+					<el-col :span="12">
+						<el-form-item label="瀹㈡埛鍚嶇О锛�" prop="customerName">
+							<el-input
+								v-model="form.customerName"
+								placeholder="璇疯緭鍏ュ鎴峰悕绉�"
+								clearable
+								:disabled="operationType === 'view'"
+							/>
+						</el-form-item>
+					</el-col>
+					<el-col :span="12">
+						<el-form-item label="鑱旂郴鐢佃瘽锛�" prop="contactPhone">
+							<el-input
+								v-model="form.contactPhone"
+								placeholder="璇疯緭鍏ヨ仈绯荤數璇�"
+								clearable
+								:disabled="operationType === 'view'"
+							/>
+						</el-form-item>
+					</el-col>
+				</el-row>
+				<el-row :gutter="30">
+					<el-col :span="24">
+						<el-form-item label="闂鎻忚堪锛�" prop="problemDesc">
+							<el-input
+								v-model="form.problemDesc"
+								placeholder="璇疯緭鍏ラ棶棰樻弿杩�"
+								clearable
+								:disabled="operationType === 'view'"
+								type="textarea"
+								:rows="3"
+							/>
+						</el-form-item>
+					</el-col>
+				</el-row>
+				<el-row :gutter="30" v-if="operationType !== 'add'">
+					<el-col :span="12">
+						<el-form-item label="澶勭悊浜猴細" prop="handlerId">
+							<el-select
+								v-model="form.handlerId"
+								placeholder="璇烽�夋嫨澶勭悊浜�"
+								clearable
+								:disabled="operationType === 'view'"
+								style="width: 100%"
+							>
+								<el-option
+									v-for="item in userList"
+									:key="item.userId"
+									:label="item.nickName"
+									:value="item.userId"
+								></el-option>
+							</el-select>
+						</el-form-item>
+					</el-col>
+					<el-col :span="12">
+						<el-form-item label="澶勭悊鏃ユ湡锛�" prop="handleDate">
+							<el-date-picker
+								style="width: 100%"
+								v-model="form.handleDate"
+								value-format="YYYY-MM-DD"
+								format="YYYY-MM-DD"
+								type="date"
+								placeholder="璇烽�夋嫨澶勭悊鏃ユ湡"
+								clearable
+								:disabled="operationType === 'view'"
+							/>
+						</el-form-item>
+					</el-col>
+				</el-row>
+				<el-row :gutter="30" v-if="operationType !== 'add'">
+					<el-col :span="24">
+						<el-form-item label="澶勭悊缁撴灉锛�" prop="handleResult">
+							<el-input
+								v-model="form.handleResult"
+								placeholder="璇疯緭鍏ュ鐞嗙粨鏋�"
+								clearable
+								:disabled="operationType === 'view'"
+								type="textarea"
+								:rows="3"
+							/>
+						</el-form-item>
+					</el-col>
+				</el-row>
+			</el-form>
+			<template #footer>
+				<div class="dialog-footer">
+					<el-button type="primary" @click="submitForm" v-if="operationType !== 'view'">纭</el-button>
+					<el-button @click="closeDia">{{ operationType === 'view' ? '鍏抽棴' : '鍙栨秷' }}</el-button>
+				</div>
+			</template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup>
+import {ref, computed} from "vue";
+import useUserStore from "@/store/modules/user.js";
+// import {userListNoPageByTenantId} from "@/api/system/user.js"; // 鏆傛椂娉ㄩ噴鎺夛紝浣跨敤鍋囨暟鎹�
+// import {expiryAfterSalesAdd, expiryAfterSalesUpdate} from "@/api/customerService/index.js"; // 鏆傛椂娉ㄩ噴鎺夛紝浣跨敤鍋囨暟鎹�
+const { proxy } = getCurrentInstance()
+const emit = defineEmits(['close'])
+const dialogFormVisible = ref(false);
+const operationType = ref('')
+const userStore = useUserStore();
+
+const dialogTitle = computed(() => {
+	switch (operationType.value) {
+		case 'add':
+			return '鏂板涓存湡鍞悗';
+		case 'edit':
+			return '缂栬緫涓存湡鍞悗';
+		case 'view':
+			return '鏌ョ湅涓存湡鍞悗';
+		default:
+			return '涓存湡鍞悗绠$悊';
+	}
+});
+
+const data = reactive({
+	form: {
+		id: "",
+		productName: "",
+		batchNumber: "",
+		expiryDate: "",
+		stockQuantity: 0,
+		customerName: "",
+		contactPhone: "",
+		problemDesc: "",
+		handlerId: "",
+		handleDate: "",
+		handleResult: "",
+		status: 1
+	},
+	rules: {
+		productName: [{required: true, message: "璇疯緭鍏ヤ骇鍝佸悕绉�", trigger: "blur"}],
+		batchNumber: [{required: true, message: "璇疯緭鍏ヤ骇鍝佹壒鍙�", trigger: "blur"}],
+		expiryDate: [{required: true, message: "璇烽�夋嫨涓存湡鏃ユ湡", trigger: "change"}],
+		stockQuantity: [{required: true, message: "璇疯緭鍏ュ簱瀛樻暟閲�", trigger: "blur"}],
+		customerName: [{required: true, message: "璇疯緭鍏ュ鎴峰悕绉�", trigger: "blur"}],
+		contactPhone: [
+			{required: true, message: "璇疯緭鍏ヨ仈绯荤數璇�", trigger: "blur"},
+			{pattern: /^1[3-9]\d{9}$/, message: "璇疯緭鍏ユ纭殑鎵嬫満鍙风爜", trigger: "blur"}
+		],
+		problemDesc: [{required: true, message: "璇疯緭鍏ラ棶棰樻弿杩�", trigger: "blur"}],
+	}
+})
+const { form, rules } = toRefs(data);
+const userList = ref([])
+
+// 鎵撳紑寮规
+const openDialog = (type, row) => {
+  operationType.value = type;
+  dialogFormVisible.value = true;
+	
+	// 妯℃嫙鑾峰彇鐢ㄦ埛鍒楄〃
+	userList.value = [
+		{ userId: 1, nickName: "寮犱笁" },
+		{ userId: 2, nickName: "鏉庡洓" },
+		{ userId: 3, nickName: "鐜嬩簲" },
+		{ userId: 4, nickName: "璧靛叚" },
+		{ userId: 5, nickName: "瀛欏叓" }
+	];
+
+	if (type === 'add') {
+		// 鏂板鏃堕噸缃〃鍗�
+		form.value = {
+			id: "",
+			productName: "",
+			batchNumber: "",
+			expiryDate: "",
+			stockQuantity: 0,
+			customerName: "",
+			contactPhone: "",
+			problemDesc: "",
+			handlerId: "",
+			handleDate: "",
+			handleResult: "",
+			status: 1
+		};
+	} else {
+		// 缂栬緫鎴栨煡鐪嬫椂濉厖鏁版嵁
+		form.value = { ...row };
+		if (type === 'edit' && !form.value.handlerId) {
+			form.value.handlerId = userStore.id;
+			form.value.handleDate = getCurrentDate();
+		}
+	}
+}
+
+const submitForm = () => {
+	proxy.$refs["formRef"].validate(valid => {
+		if (valid) {
+			// 妯℃嫙鎻愪氦鎿嶄綔
+			setTimeout(() => {
+				console.log("妯℃嫙鎻愪氦鐨勬暟鎹�:", form.value);
+				proxy.$modal.msgSuccess(operationType.value === 'add' ? "鏂板鎴愬姛" : "鏇存柊鎴愬姛");
+				closeDia();
+			}, 300);
+		}
+	});
+}
+
+// 鍏抽棴寮规
+const closeDia = () => {
+	proxy.resetForm("formRef");
+  dialogFormVisible.value = false;
+  emit('close')
+};
+
+// 鑾峰彇褰撳墠鏃ユ湡骞舵牸寮忓寲涓� YYYY-MM-DD
+function getCurrentDate() {
+	const today = new Date();
+	const year = today.getFullYear();
+	const month = String(today.getMonth() + 1).padStart(2, "0");
+	const day = String(today.getDate()).padStart(2, "0");
+	return `${year}-${month}-${day}`;
+}
+
+defineExpose({
+  openDialog,
+});
+</script>
+
+<style scoped>
+
+</style>
diff --git a/src/views/customerService/expiryAfterSales/index.vue b/src/views/customerService/expiryAfterSales/index.vue
new file mode 100644
index 0000000..b958acb
--- /dev/null
+++ b/src/views/customerService/expiryAfterSales/index.vue
@@ -0,0 +1,330 @@
+<template>
+	<div class="app-container">
+		<div class="search_form">
+			<div>
+				<span class="search_title">涓存湡鏃ユ湡锛�</span>
+				<el-date-picker
+					v-model="searchForm.expiryDate"
+					value-format="YYYY-MM-DD"
+					format="YYYY-MM-DD"
+					type="date"
+					placeholder="璇烽�夋嫨"
+					clearable
+					@change="handleQuery"
+				/>
+				<span class="search_title ml10">澶勭悊鏃ユ湡锛�</span>
+				<el-date-picker
+					v-model="searchForm.handleDate"
+					value-format="YYYY-MM-DD"
+					format="YYYY-MM-DD"
+					type="date"
+					placeholder="璇烽�夋嫨"
+					clearable
+					@change="handleQuery"
+				/>
+        <span style = "margin-left: 10px;" class="search_title">澶勭悊鐘舵�侊細</span>
+        <el-select v-model="searchForm.status" placeholder="璇烽�夋嫨鐘舵��" @change="handleQuery" style="width: 140px" clearable>
+          <el-option label="寰呭鐞�" :value="1"></el-option>
+          <el-option label="宸插鐞�" :value="2"></el-option>
+        </el-select>
+				<el-button type="primary" @click="handleQuery" style="margin-left: 10px"
+				>鎼滅储</el-button
+				>
+				<el-button @click="resetQuery" style="margin-left: 10px"
+				>閲嶇疆</el-button
+				>
+			</div>
+		</div>
+		<div class="table_actions" style="margin-bottom: 10px;">
+			<el-button type="primary" @click="openForm('add')">鏂板</el-button>
+			<el-button type="danger" @click="handleDelete">鍒犻櫎</el-button>
+		</div>
+		<div class="table_list">
+			<PIMTable
+				rowKey="id"
+				:column="tableColumn"
+				:tableData="tableData"
+				:page="page"
+				:isSelection="true"
+				@selection-change="handleSelectionChange"
+				:tableLoading="tableLoading"
+				@pagination="pagination"
+			>
+				<!-- 琛ㄦ牸鎻掓Ы -->
+				<template #status="{ row }">
+					<el-tag :type="row.status === 1 ? 'warning' : 'success'">
+						{{ row.status === 1 ? '寰呭鐞�' : '宸插鐞�' }}
+					</el-tag>
+				</template>
+
+				<template #operation="{ row }">
+					<el-button type="primary" link @click="openForm('view', row)">鏌ョ湅</el-button>
+					<el-button type="primary" link @click="openForm('edit', row)" v-if="row.status === 1">缂栬緫</el-button>
+				</template>
+			</PIMTable>
+		</div>
+		<form-dia ref="formDia" @close="handleQuery"></form-dia>
+	</div>
+</template>
+
+<script setup>
+import {Search} from "@element-plus/icons-vue";
+import {onMounted, ref} from "vue";
+import FormDia from "@/views/customerService/expiryAfterSales/components/formDia.vue";
+import {ElMessageBox} from "element-plus";
+// import {expiryAfterSalesDelete, expiryAfterSalesListPage} from "@/api/customerService/index.js"; // 鏆傛椂娉ㄩ噴鎺夛紝浣跨敤鍋囨暟鎹�
+import useUserStore from "@/store/modules/user.js";
+const { proxy } = getCurrentInstance();
+const userStore = useUserStore()
+
+const data = reactive({
+	searchForm: {
+		expiryDate: "",
+		handleDate: "",
+		status: ""
+	},
+	tableData: [],
+	page: {
+		current: 1,
+		size: 10,
+		total: 0,
+	},
+	selectedRows: [],
+	tableLoading: false,
+	formDia: null,
+	tableColumn: [
+		{
+			label: "涓存湡浜у搧鍚嶇О",
+			prop: "productName",
+			width: "",
+		},
+		{
+			label: "浜у搧鎵瑰彿",
+			prop: "batchNumber",
+			width: "",
+		},
+		{
+			label: "涓存湡鏃ユ湡",
+			prop: "expiryDate",
+			width: "",
+		},
+		{
+			label: "搴撳瓨鏁伴噺",
+			prop: "stockQuantity",
+			width: "",
+		},
+		{
+			label: "瀹㈡埛鍚嶇О",
+			prop: "customerName",
+			width: "",
+		},
+		{
+			label: "闂鎻忚堪",
+			prop: "problemDesc",
+			width: "",
+		},
+		{
+			label: "澶勭悊鐘舵��",
+			prop: "status",
+			width: "",
+			slot: true,
+		},
+		{
+			label: "澶勭悊浜�",
+			prop: "handlerName",
+			width: "",
+		},
+		{
+			label: "澶勭悊鏃ユ湡",
+			prop: "handleDate",
+			width: "",
+		},
+		{
+			label: "鎿嶄綔",
+			prop: "operation",
+			slot: true,
+			width: "200",
+		},
+	],
+});
+
+const {
+	searchForm,
+	tableData,
+	page,
+	selectedRows,
+	tableLoading,
+	formDia,
+	tableColumn,
+} = toRefs(data);
+
+// 鏌ヨ
+const handleQuery = () => {
+	page.value.current = 1;
+	getList();
+};
+
+// 閫夋嫨
+const handleSelectionChange = (selection) => {
+	selectedRows.value = selection;
+};
+
+// 閲嶇疆
+const resetQuery = () => {
+	proxy.resetForm("queryRef");
+	searchForm.value = {
+		expiryDate: "",
+		handleDate: "",
+		status: ""
+	};
+	handleQuery();
+};
+
+// 鍒嗛〉
+const pagination = (obj) => {
+	page.value.current = obj.page;
+	page.value.size = obj.limit;
+	getList();
+};
+
+// 鑾峰彇鍒楄〃鏁版嵁
+const getList = () => {
+	tableLoading.value = true;
+	
+	// 妯℃嫙寮傛璇锋眰
+	setTimeout(() => {
+		// 鍋囨暟鎹�
+		const mockData = [
+			{
+				id: 1,
+				productName: "缁寸敓绱燙鐗�",
+				batchNumber: "VC20240801",
+				expiryDate: "2024-12-15",
+				stockQuantity: 150,
+				customerName: "寮犱笁鑽簵",
+				problemDesc: "涓磋繎淇濊川鏈熼渶瑕佸鐞�",
+				status: 1,
+				handlerName: "",
+				handleDate: "",
+			},
+			{
+				id: 2,
+				productName: "闃胯帿瑗挎灄鑳跺泭",
+				batchNumber: "AM20240715",
+				expiryDate: "2024-11-20",
+				stockQuantity: 80,
+				customerName: "鏉庡洓鍖婚櫌",
+				problemDesc: "搴撳瓨绉帇锛屼复鏈熷鐞�",
+				status: 2,
+				handlerName: "鐜嬩簲",
+				handleDate: "2024-09-25",
+			},
+			{
+				id: 3,
+				productName: "鎰熷啋鐏甸绮�",
+				batchNumber: "GM20240620",
+				expiryDate: "2024-10-30",
+				stockQuantity: 200,
+				customerName: "璧靛叚璇婃墍",
+				problemDesc: "瀛h妭鎬ц嵂鍝侊紝闇�瑕佹竻鐞嗗簱瀛�",
+				status: 1,
+				handlerName: "",
+				handleDate: "",
+			},
+			{
+				id: 4,
+				productName: "澶嶅悎缁寸敓绱犵墖",
+				batchNumber: "FH20240510",
+				expiryDate: "2024-12-01",
+				stockQuantity: 300,
+				customerName: "閽变竷杩為攣",
+				problemDesc: "涓存湡浜у搧閫�鎹㈢敵璇�",
+				status: 2,
+				handlerName: "瀛欏叓",
+				handleDate: "2024-09-20",
+			},
+			{
+				id: 5,
+				productName: "鏉胯摑鏍归绮�",
+				batchNumber: "BL20240430",
+				expiryDate: "2024-11-10",
+				stockQuantity: 120,
+				customerName: "鍛ㄤ節鑽埧",
+				problemDesc: "鎵规闂锛岄渶瑕佸彫鍥�",
+				status: 1,
+				handlerName: "",
+				handleDate: "",
+			}
+		];
+
+		// 绠�鍗曠殑鎼滅储杩囨护
+		let filteredData = mockData;
+		
+		if (searchForm.value.status !== "" && searchForm.value.status !== null) {
+			filteredData = filteredData.filter(item => item.status === searchForm.value.status);
+		}
+		
+		if (searchForm.value.expiryDate) {
+			filteredData = filteredData.filter(item => item.expiryDate === searchForm.value.expiryDate);
+		}
+		
+		if (searchForm.value.handleDate) {
+			filteredData = filteredData.filter(item => item.handleDate === searchForm.value.handleDate);
+		}
+
+		// 鍒嗛〉澶勭悊
+		const start = (page.value.current - 1) * page.value.size;
+		const end = start + page.value.size;
+		const paginatedData = filteredData.slice(start, end);
+
+		tableLoading.value = false;
+		tableData.value = paginatedData;
+		page.value.total = filteredData.length;
+	}, 500); // 妯℃嫙缃戠粶寤惰繜
+};
+
+// 鎵撳紑寮规
+const openForm = (type, row) => {
+	nextTick(() => {
+		formDia.value?.openDialog(type, row)
+	})
+};
+
+// 鍒犻櫎
+const handleDelete = () => {
+	let ids = [];
+	if (selectedRows.value.length > 0) {
+		ids = selectedRows.value.map((item) => item.id);
+	} else {
+		proxy.$modal.msgWarning("璇烽�夋嫨鏁版嵁");
+		return;
+	}
+	ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚垹闄わ紝鏄惁纭鍒犻櫎锛�", "鍒犻櫎鎻愮ず", {
+		confirmButtonText: "纭",
+		cancelButtonText: "鍙栨秷",
+		type: "warning",
+	})
+		.then(() => {
+			tableLoading.value = true;
+			
+			// 妯℃嫙鍒犻櫎鎿嶄綔
+			setTimeout(() => {
+				tableLoading.value = false;
+				proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+				console.log("妯℃嫙鍒犻櫎鐨勬暟鎹甀D:", ids);
+				getList(); // 閲嶆柊鑾峰彇鏁版嵁
+			}, 300);
+		})
+		.catch(() => {
+			proxy.$modal.msg("宸插彇娑�");
+		});
+};
+
+onMounted(() => {
+	getList();
+});
+</script>
+
+<style scoped>
+
+</style>
diff --git a/src/views/qualityManagement/nearExpiryReturn/index.vue b/src/views/qualityManagement/nearExpiryReturn/index.vue
new file mode 100644
index 0000000..d2fe85b
--- /dev/null
+++ b/src/views/qualityManagement/nearExpiryReturn/index.vue
@@ -0,0 +1,519 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
+      <el-form-item label="浜у搧鍚嶇О" prop="productName">
+        <el-input
+          v-model="queryParams.productName"
+          placeholder="璇疯緭鍏ヤ骇鍝佸悕绉�"
+          clearable
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="鎵规鍙�" prop="batchNumber">
+        <el-input
+          v-model="queryParams.batchNumber"
+          placeholder="璇疯緭鍏ユ壒娆″彿"
+          clearable
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="閫�鍥炴棩鏈�" prop="returnDate">
+        <el-date-picker
+          clearable
+          v-model="queryParams.returnDate"
+          type="date"
+          value-format="YYYY-MM-DD"
+          placeholder="璇烽�夋嫨閫�鍥炴棩鏈�">
+        </el-date-picker>
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" icon="Search" @click="handleQuery">鎼滅储</el-button>
+        <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button>
+      </el-form-item>
+    </el-form>
+
+    <el-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-button
+          type="primary"
+          plain
+          icon="Plus"
+          @click="handleAdd"
+        >鏂板</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="success"
+          plain
+          icon="Edit"
+          :disabled="single"
+          @click="handleUpdate"
+        >淇敼</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="danger"
+          plain
+          icon="Delete"
+          :disabled="multiple"
+          @click="handleDelete"
+        >鍒犻櫎</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="warning"
+          plain
+          icon="Download"
+          @click="handleExport"
+        >瀵煎嚭</el-button>
+      </el-col>
+      <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <el-table v-loading="loading" :data="nearExpiryReturnList" @selection-change="handleSelectionChange">
+      <el-table-column type="selection" width="55" align="center" />
+      <el-table-column label="搴忓彿" type="index" width="50" align="center" />
+      <el-table-column label="浜у搧鍚嶇О" prop="productName" />
+      <el-table-column label="浜у搧瑙勬牸" prop="productSpec" />
+      <el-table-column label="鎵规鍙�" prop="batchNumber" />
+      <el-table-column label="鐢熶骇鏃ユ湡" prop="productionDate" align="center">
+        <template #default="scope">
+          <span>{{ parseTime(scope.row.productionDate, '{y}-{m}-{d}') }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="鍒版湡鏃ユ湡" prop="expiryDate" align="center">
+        <template #default="scope">
+          <span>{{ parseTime(scope.row.expiryDate, '{y}-{m}-{d}') }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="閫�鍥炴暟閲�" prop="returnQuantity" />
+      <el-table-column label="閫�鍥炲師鍥�" prop="returnReason" />
+      <el-table-column label="閫�鍥炴棩鏈�" prop="returnDate" align="center">
+        <template #default="scope">
+          <span>{{ parseTime(scope.row.returnDate, '{y}-{m}-{d}') }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="澶勭悊鐘舵��" prop="status" align="center">
+        <template #default="scope">
+          <dict-tag :options="statusOptions" :value="scope.row.status"/>
+        </template>
+      </el-table-column>
+      <el-table-column label="鎿嶄綔" align="center" class-name="small-padding fixed-width">
+        <template #default="scope">
+          <el-button size="mini" type="text" icon="Edit" @click="handleUpdate(scope.row)">淇敼</el-button>
+          <el-button size="mini" type="text" icon="Delete" @click="handleDelete(scope.row)">鍒犻櫎</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    
+    <pagination
+      v-show="total>0"
+      :total="total"
+      v-model:page="queryParams.pageNum"
+      v-model:limit="queryParams.pageSize"
+      @pagination="getList"
+    />
+
+    <!-- 娣诲姞鎴栦慨鏀逛复鏈熼��鍥炲彴璐﹀璇濇 -->
+    <el-dialog :title="title" v-model="open" width="800px" append-to-body>
+      <el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
+        <el-row>
+          <el-col :span="12">
+            <el-form-item label="浜у搧鍚嶇О" prop="productName">
+              <el-input v-model="form.productName" placeholder="璇疯緭鍏ヤ骇鍝佸悕绉�" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="浜у搧瑙勬牸" prop="productSpec">
+              <el-input v-model="form.productSpec" placeholder="璇疯緭鍏ヤ骇鍝佽鏍�" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row>
+          <el-col :span="12">
+            <el-form-item label="鎵规鍙�" prop="batchNumber">
+              <el-input v-model="form.batchNumber" placeholder="璇疯緭鍏ユ壒娆″彿" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="閫�鍥炴暟閲�" prop="returnQuantity">
+              <el-input-number v-model="form.returnQuantity" controls-position="right" :min="1" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row>
+          <el-col :span="12">
+            <el-form-item label="鐢熶骇鏃ユ湡" prop="productionDate">
+              <el-date-picker
+                clearable
+                v-model="form.productionDate"
+                type="date"
+                value-format="YYYY-MM-DD"
+                placeholder="璇烽�夋嫨鐢熶骇鏃ユ湡">
+              </el-date-picker>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="鍒版湡鏃ユ湡" prop="expiryDate">
+              <el-date-picker
+                clearable
+                v-model="form.expiryDate"
+                type="date"
+                value-format="YYYY-MM-DD"
+                placeholder="璇烽�夋嫨鍒版湡鏃ユ湡">
+              </el-date-picker>
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row>
+          <el-col :span="12">
+            <el-form-item label="閫�鍥炴棩鏈�" prop="returnDate">
+              <el-date-picker
+                clearable
+                v-model="form.returnDate"
+                type="date"
+                value-format="YYYY-MM-DD"
+                placeholder="璇烽�夋嫨閫�鍥炴棩鏈�">
+              </el-date-picker>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="澶勭悊鐘舵��" prop="status">
+              <el-select v-model="form.status" placeholder="璇烽�夋嫨澶勭悊鐘舵��">
+                <el-option
+                  v-for="dict in statusOptions"
+                  :key="dict.value"
+                  :label="dict.label"
+                  :value="dict.value"
+                ></el-option>
+              </el-select>
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row>
+          <el-col :span="24">
+            <el-form-item label="閫�鍥炲師鍥�" prop="returnReason">
+              <el-input v-model="form.returnReason" type="textarea" placeholder="璇疯緭鍏ラ��鍥炲師鍥�" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row>
+          <el-col :span="24">
+            <el-form-item label="澶囨敞" prop="remark">
+              <el-input v-model="form.remark" type="textarea" placeholder="璇疯緭鍏ュ娉�" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button type="primary" @click="submitForm">纭� 瀹�</el-button>
+          <el-button @click="cancel">鍙� 娑�</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup name="NearExpiryReturn">
+import { ref, reactive, onMounted } from "vue";
+import { ElMessageBox } from "element-plus";
+import {
+  nearExpiryReturnListPage,
+  nearExpiryReturnAdd,
+  nearExpiryReturnUpdate,
+  nearExpiryReturnDel,
+  nearExpiryReturnDetail
+} from "@/api/qualityManagement/nearExpiryReturn";
+
+const { proxy } = getCurrentInstance();
+const { parseTime } = proxy;
+
+const nearExpiryReturnList = ref([]);
+const open = ref(false);
+const loading = ref(true);
+const showSearch = ref(true);
+const ids = ref([]);
+const single = ref(true);
+const multiple = ref(true);
+const total = ref(0);
+const title = ref("");
+
+// 鐘舵�佸瓧鍏�
+const statusOptions = ref([
+  { label: "寰呭鐞�", value: "0" },
+  { label: "澶勭悊涓�", value: "1" },
+  { label: "宸插畬鎴�", value: "2" }
+]);
+
+const data = reactive({
+  form: {},
+  queryParams: {
+    pageNum: 1,
+    pageSize: 10,
+    productName: null,
+    batchNumber: null,
+    returnDate: null
+  },
+  rules: {
+    productName: [
+      { required: true, message: "浜у搧鍚嶇О涓嶈兘涓虹┖", trigger: "blur" }
+    ],
+    productSpec: [
+      { required: true, message: "浜у搧瑙勬牸涓嶈兘涓虹┖", trigger: "blur" }
+    ],
+    batchNumber: [
+      { required: true, message: "鎵规鍙蜂笉鑳戒负绌�", trigger: "blur" }
+    ],
+    returnQuantity: [
+      { required: true, message: "閫�鍥炴暟閲忎笉鑳戒负绌�", trigger: "blur" }
+    ],
+    productionDate: [
+      { required: true, message: "鐢熶骇鏃ユ湡涓嶈兘涓虹┖", trigger: "blur" }
+    ],
+    expiryDate: [
+      { required: true, message: "鍒版湡鏃ユ湡涓嶈兘涓虹┖", trigger: "blur" }
+    ],
+    returnDate: [
+      { required: true, message: "閫�鍥炴棩鏈熶笉鑳戒负绌�", trigger: "blur" }
+    ],
+    returnReason: [
+      { required: true, message: "閫�鍥炲師鍥犱笉鑳戒负绌�", trigger: "blur" }
+    ],
+    status: [
+      { required: true, message: "澶勭悊鐘舵�佷笉鑳戒负绌�", trigger: "change" }
+    ]
+  }
+});
+
+const { queryParams, form, rules } = toRefs(data);
+
+/** 鏌ヨ涓存湡閫�鍥炲彴璐﹀垪琛� */
+function getList() {
+  loading.value = true;
+  // 浣跨敤鍋囨暟鎹�
+  const mockData = {
+    records: [
+      {
+        id: 1,
+        productName: "缁寸敓绱燙鐗�",
+        productSpec: "100mg脳30鐗�",
+        batchNumber: "VC20240315001",
+        productionDate: "2024-03-15",
+        expiryDate: "2024-09-15",
+        returnQuantity: 50,
+        returnReason: "涓磋繎淇濊川鏈�",
+        returnDate: "2024-09-10",
+        status: "1",
+        remark: "閫�鍥炰粨搴撳鐞�"
+      },
+      {
+        id: 2,
+        productName: "闃胯帿瑗挎灄鑳跺泭",
+        productSpec: "250mg脳24绮�",
+        batchNumber: "AMX20240220002",
+        productionDate: "2024-02-20",
+        expiryDate: "2024-08-20",
+        returnQuantity: 30,
+        returnReason: "鍖呰鐮存崯涓斾复鏈�",
+        returnDate: "2024-08-18",
+        status: "2",
+        remark: "宸查攢姣佸鐞�"
+      },
+      {
+        id: 3,
+        productName: "鎰熷啋鐏甸绮�",
+        productSpec: "10g脳12琚�",
+        batchNumber: "GML20240110003",
+        productionDate: "2024-01-10",
+        expiryDate: "2024-07-10",
+        returnQuantity: 25,
+        returnReason: "涓磋繎淇濊川鏈�",
+        returnDate: "2024-07-08",
+        status: "0",
+        remark: "寰呴噸鏂板寘瑁�"
+      },
+      {
+        id: 4,
+        productName: "澶嶅悎缁寸敓绱犵墖",
+        productSpec: "60鐗�/鐡�",
+        batchNumber: "VB20240405004",
+        productionDate: "2024-04-05",
+        expiryDate: "2025-04-05",
+        returnQuantity: 80,
+        returnReason: "涓磋繎淇濊川鏈�",
+        returnDate: "2024-09-25",
+        status: "1",
+        remark: "姝e湪鑱旂郴閿�鍞笭閬�"
+      },
+      {
+        id: 5,
+        productName: "閽欑墖",
+        productSpec: "600mg脳100鐗�",
+        batchNumber: "CA20240301005",
+        productionDate: "2024-03-01",
+        expiryDate: "2024-09-01",
+        returnQuantity: 120,
+        returnReason: "鍖呰闂涓斾复鏈�",
+        returnDate: "2024-08-30",
+        status: "2",
+        remark: "宸插畬鎴愰��璐у鐞�"
+      }
+    ],
+    total: 5
+  };
+
+  // 妯℃嫙杩囨护閫昏緫
+  let filteredRecords = mockData.records;
+  
+  if (queryParams.value.productName) {
+    filteredRecords = filteredRecords.filter(item => 
+      item.productName.includes(queryParams.value.productName)
+    );
+  }
+  
+  if (queryParams.value.batchNumber) {
+    filteredRecords = filteredRecords.filter(item => 
+      item.batchNumber.includes(queryParams.value.batchNumber)
+    );
+  }
+  
+  if (queryParams.value.returnDate) {
+    filteredRecords = filteredRecords.filter(item => 
+      item.returnDate === queryParams.value.returnDate
+    );
+  }
+
+  nearExpiryReturnList.value = filteredRecords;
+  total.value = filteredRecords.length;
+  loading.value = false;
+}
+
+// 鍙栨秷鎸夐挳
+function cancel() {
+  open.value = false;
+  reset();
+}
+
+// 琛ㄥ崟閲嶇疆
+function reset() {
+  form.value = {
+    id: null,
+    productName: null,
+    productSpec: null,
+    batchNumber: null,
+    productionDate: null,
+    expiryDate: null,
+    returnQuantity: null,
+    returnReason: null,
+    returnDate: null,
+    status: null,
+    remark: null
+  };
+  proxy.resetForm("formRef");
+}
+
+/** 鎼滅储鎸夐挳鎿嶄綔 */
+function handleQuery() {
+  queryParams.value.pageNum = 1;
+  getList();
+}
+
+/** 閲嶇疆鎸夐挳鎿嶄綔 */
+function resetQuery() {
+  proxy.resetForm("queryForm");
+  handleQuery();
+}
+
+// 澶氶�夋閫変腑鏁版嵁
+function handleSelectionChange(selection) {
+  ids.value = selection.map(item => item.id);
+  single.value = selection.length !== 1;
+  multiple.value = !selection.length;
+}
+
+/** 鏂板鎸夐挳鎿嶄綔 */
+function handleAdd() {
+  reset();
+  open.value = true;
+  title.value = "娣诲姞涓存湡閫�鍥炲彴璐�";
+}
+
+/** 淇敼鎸夐挳鎿嶄綔 */
+function handleUpdate(row) {
+  reset();
+  const id = row.id || ids.value;
+  
+  // 浣跨敤鍋囨暟鎹幏鍙栬鎯�
+  const mockDetail = nearExpiryReturnList.value.find(item => item.id === (Array.isArray(id) ? id[0] : id));
+  if (mockDetail) {
+    form.value = { ...mockDetail };
+    open.value = true;
+    title.value = "淇敼涓存湡閫�鍥炲彴璐�";
+  }
+}
+
+/** 鎻愪氦鎸夐挳 */
+function submitForm() {
+  proxy.$refs["formRef"].validate(valid => {
+    if (valid) {
+      if (form.value.id != null) {
+        // 妯℃嫙鏇存柊
+        const index = nearExpiryReturnList.value.findIndex(item => item.id === form.value.id);
+        if (index !== -1) {
+          nearExpiryReturnList.value[index] = { ...form.value };
+        }
+        proxy.$modal.msgSuccess("淇敼鎴愬姛");
+        open.value = false;
+        getList();
+      } else {
+        // 妯℃嫙鏂板
+        const newId = Math.max(...nearExpiryReturnList.value.map(item => item.id)) + 1;
+        nearExpiryReturnList.value.push({ ...form.value, id: newId });
+        total.value = nearExpiryReturnList.value.length;
+        proxy.$modal.msgSuccess("鏂板鎴愬姛");
+        open.value = false;
+        getList();
+      }
+    }
+  });
+}
+
+/** 鍒犻櫎鎸夐挳鎿嶄綔 */
+function handleDelete(row) {
+  const deleteIds = row.id || ids.value;
+  ElMessageBox.confirm('鏄惁纭鍒犻櫎涓存湡閫�鍥炲彴璐︾紪鍙蜂负"' + deleteIds + '"鐨勬暟鎹」锛�', "璀﹀憡", {
+    confirmButtonText: "纭畾",
+    cancelButtonText: "鍙栨秷",
+    type: "warning"
+  }).then(function() {
+    // 妯℃嫙鍒犻櫎
+    if (Array.isArray(deleteIds)) {
+      deleteIds.forEach(id => {
+        const index = nearExpiryReturnList.value.findIndex(item => item.id === id);
+        if (index !== -1) {
+          nearExpiryReturnList.value.splice(index, 1);
+        }
+      });
+    } else {
+      const index = nearExpiryReturnList.value.findIndex(item => item.id === deleteIds);
+      if (index !== -1) {
+        nearExpiryReturnList.value.splice(index, 1);
+      }
+    }
+    getList();
+    proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+  }).catch(() => {});
+}
+
+/** 瀵煎嚭鎸夐挳鎿嶄綔 */
+function handleExport() {
+  proxy.download('quality/nearExpiryReturn/export', {
+    ...queryParams.value
+  }, `涓存湡閫�鍥炲彴璐${new Date().getTime()}.xlsx`);
+}
+
+onMounted(() => {
+  getList();
+});
+</script>

--
Gitblit v1.9.3