From 28c8c43997baa2dc9301ee75308afa762815890f Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期三, 17 九月 2025 14:15:34 +0800
Subject: [PATCH] 采购价格管理前端页面
---
src/api/procurementManagement/advancedPriceManagement.js | 302 +++++++++++++++
src/views/procurementManagement/advancedPriceManagement/index.vue | 874 +++++++++++++++++++++++++++++++++++++++++++
2 files changed, 1,176 insertions(+), 0 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/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>
--
Gitblit v1.9.3