| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <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="请è¾å
¥ææ£å¼" |
| | | 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="å£èæ§è°æ´" 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="请è¾å
¥ææ£å¼" |
| | | 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('请å
éæ©è¦è®¾ç½®ææ£çåå') |
| | | 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> |