jiaxing.zhou

feat(rebate):重构返利页面搜索和表格功能

- 使用原生HTML元素替换Element UI组件
- 新增操作类型、计算状态、审核状态、数据来源等搜索条件
- 添加表格控制按钮和自定义分页组件
- 实现状态标签样式和操作链接样式
- 优化表格样式和加载状态显示
- 完善搜索表单重置逻辑
- 调整页面布局和样式细节
......@@ -32,6 +32,8 @@ export interface RebateSearchParams {
productCode?: string
operateType?: number
calcFlag?: number
auditStatus?: number
dataSource?: string
rebateStartDate?: string
rebateEndDate?: string
}
......
......@@ -2,51 +2,64 @@
<div class="rebate-page">
<!-- 搜索区域 -->
<div class="search-section">
<div class="search-form">
<div class="search-row">
<div class="search-item">
<span class="search-label">经销商名称</span>
<el-input
v-model="searchForm.dealerName"
<div class="search-row">
<div class="search-item">
<label>经销商名称:</label>
<input
v-model="searchForm.dealerName"
class="search-input"
placeholder="请输入经销商名称"
clearable
size="small"
style="width: 160px"
/>
</div>
<div class="search-item">
<span class="search-label">返利编号</span>
<el-input
v-model="searchForm.rebateNo"
placeholder="请输入返利编号"
clearable
size="small"
style="width: 160px"
/>
</div>
<div class="search-item">
<span class="search-label">日期</span>
<el-date-picker
v-model="dateRange"
type="daterange"
range-separator="至"
start-placeholder="开始日期"
end-placeholder="结束日期"
size="small"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
@change="handleDateRangeChange"
style="width: 240px"
/>
</div>
<div class="search-actions">
<el-button type="primary" size="small" @click="handleSearch" :loading="loading">
查询
</el-button>
<el-button size="small" @click="handleReset">
重置
</el-button>
</div>
/>
</div>
<div class="search-item">
<label>返利编号:</label>
<input
v-model="searchForm.rebateNo"
class="search-input"
placeholder="请输入返利编号"
/>
</div>
<div class="search-item">
<label>操作类型:</label>
<select v-model="searchForm.operateType" class="search-select">
<option value="">所有</option>
<option value="1">新增</option>
<option value="2">修改</option>
<option value="3">删除</option>
</select>
</div>
<div class="search-item">
<label>计算状态:</label>
<select v-model="searchForm.calcFlag" class="search-select">
<option value="">所有</option>
<option value="0">未计算</option>
<option value="1">已计算</option>
</select>
</div>
</div>
<div class="search-row">
<div class="search-item">
<label>审核状态:</label>
<select v-model="searchForm.auditStatus" class="search-select">
<option value="">所有</option>
<option value="0">待审核</option>
<option value="1">审核通过</option>
<option value="2">审核中</option>
<option value="3">审核拒绝</option>
</select>
</div>
<div class="search-item">
<label>数据来源:</label>
<select v-model="searchForm.dataSource" class="search-select">
<option value="">所有</option>
<option value="ERP系统">ERP系统</option>
<option value="手工录入">手工录入</option>
<option value="API导入">API导入</option>
</select>
</div>
<div class="search-actions">
<button @click="handleSearch" class="search-btn" :disabled="loading">🔍 搜索</button>
<button @click="handleReset" class="reset-btn">🔄 重置</button>
</div>
</div>
</div>
......@@ -96,95 +109,103 @@
</div>
</div>
<!-- 操作按钮区域 -->
<div class="action-section">
<div class="action-buttons">
<button @click="handleExport" class="action-btn secondary" :disabled="exportLoading">
📋 导出
</button>
</div>
</div>
<!-- 数据表格 -->
<div class="table-section">
<div class="table-header">
<div class="table-title">
返利记录
</div>
<div class="table-actions">
<el-button type="primary" size="small" @click="handleExport" :loading="exportLoading">
<el-icon><Download /></el-icon>
导出
</el-button>
<div class="table-info">
数据状态: {{ loading ? '加载中...' : `共${rebateList.length}条记录` }}
</div>
<div class="table-controls">
<button @click="handleTableSearch" class="control-btn">🔍</button>
<button @click="handleTableRefresh" class="control-btn">🔄</button>
<button @click="handleTableExport" class="control-btn">📋</button>
<button @click="handleTableViewToggle" class="control-btn">⊞</button>
</div>
</div>
<el-table
v-loading="loading"
:data="rebateList"
style="width: 100%"
:header-cell-style="{ background: '#fafafa', color: '#333', fontWeight: 'normal' }"
:empty-text="rebateList.length === 0 ? '暂无数据' : ''"
size="small"
>
<el-table-column prop="rebateNo" label="返利编号" width="140" align="center" />
<el-table-column prop="orderNo" label="订单编号" width="140" align="center" />
<el-table-column prop="dealerCode" label="经销商代码" width="120" align="center" />
<el-table-column prop="dealerName" label="经销商名称" min-width="150" />
<el-table-column prop="productCode" label="产品编码" width="120" align="center" />
<el-table-column prop="rebateAmount" label="返利金额" width="120" align="right">
<template #default="{ row }">
<span class="amount-text">{{ formatAmount(row.rebateAmount) }}</span>
</template>
</el-table-column>
<el-table-column prop="rebateDate" label="返利日期" width="120" align="center" />
<el-table-column prop="operateTypeText" label="操作类型" width="100" align="center">
<template #default="{ row }">
<el-tag :type="getOperateTypeTagType(row.operateType)" size="small">
{{ row.operateTypeText || getOperateTypeText(row.operateType) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="calcFlagText" label="计算状态" width="100" align="center">
<template #default="{ row }">
<el-tag :type="getCalcFlagTagType(row.calcFlag)" size="small">
{{ row.calcFlagText || getCalcFlagText(row.calcFlag) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="updateTime" label="更新时间" width="150" align="center">
<template #default="{ row }">
{{ formatDate(row.updateTime) }}
</template>
</el-table-column>
<el-table-column label="操作" width="80" align="center">
<template #default="{ row }">
<el-button type="primary" size="small" link @click="handleViewDetail(row)">
详情
</el-button>
</template>
</el-table-column>
</el-table>
<div class="table-container">
<div v-if="loading" class="loading-overlay">
<div class="loading-spinner">加载中...</div>
</div>
<table class="data-table">
<thead>
<tr>
<th>返利编号</th>
<th>订单编号</th>
<th>经销商代码</th>
<th>经销商名称</th>
<th>产品编码</th>
<th>返利金额</th>
<th>返利日期</th>
<th>操作类型</th>
<th>计算状态</th>
<th>更新时间</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr v-if="rebateList.length === 0">
<td colspan="11" class="empty-data">暂无数据</td>
</tr>
<tr v-for="row in rebateList" :key="row.rebateId">
<td>{{ row.rebateNo }}</td>
<td>{{ row.orderNo }}</td>
<td>{{ row.dealerCode }}</td>
<td>{{ row.dealerName }}</td>
<td>{{ row.productCode }}</td>
<td class="amount-text">{{ formatAmount(row.rebateAmount) }}</td>
<td>{{ row.rebateDate }}</td>
<td>
<span :class="getOperateTypeClass(row.operateType || 0)">
{{ row.operateTypeText || getOperateTypeText(row.operateType || 0) }}
</span>
</td>
<td>
<span :class="getCalcFlagClass(row.calcFlag || 0)">
{{ row.calcFlagText || getCalcFlagText(row.calcFlag || 0) }}
</span>
</td>
<td>{{ formatDate(row.updateTime || '') }}</td>
<td>
<button @click="handleViewDetail(row)" class="action-link">详情</button>
</td>
</tr>
</tbody>
</table>
</div>
<!-- 分页 -->
<div class="pagination-wrapper">
<div class="pagination-info">
共 {{ pagination.total }} 条,{{ pagination.pageSize }}/页
</div>
<el-pagination
v-model:current-page="pagination.pageNum"
v-model:page-size="pagination.pageSize"
:page-sizes="[10, 20, 50, 100]"
:total="pagination.total"
layout="sizes, prev, pager, next"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
small
/>
<div class="pagination-controls">
<button @click="handlePrevPage" :disabled="pagination.pageNum <= 1" class="page-btn">‹</button>
<button
v-for="page in getPageNumbers()"
:key="page"
@click="handlePageChange(page)"
:class="['page-btn', { active: page === pagination.pageNum }]"
>
{{ page }}
</button>
<button @click="handleNextPage" :disabled="pagination.pageNum >= getTotalPages()" class="page-btn">›</button>
</div>
<div class="pagination-jump">
前往
<el-input
<input
v-model="jumpPage"
size="small"
style="width: 50px; margin: 0 8px;"
class="jump-input"
@keyup.enter="handleJumpPage"
/>
</div>
</div>
</div>
</div>
......@@ -435,6 +456,8 @@ const searchForm = reactive<RebateSearchParams>({
productCode: '',
operateType: undefined,
calcFlag: undefined,
auditStatus: undefined,
dataSource: '',
rebateStartDate: '',
rebateEndDate: ''
})
......@@ -730,15 +753,17 @@ const handleReset = () => {
Object.assign(searchForm, {
pageNum: 1,
pageSize: 10,
rebateNo: undefined,
dealerName: undefined,
productName: undefined,
rebateType: undefined,
status: undefined,
applyStartTime: undefined,
applyEndTime: undefined
rebateNo: '',
dealerCode: '',
dealerName: '',
productCode: '',
operateType: undefined,
calcFlag: undefined,
auditStatus: undefined,
dataSource: '',
rebateStartDate: '',
rebateEndDate: ''
})
applyDateRange.value = null
pagination.pageNum = 1
getRebateList()
}
......@@ -748,6 +773,104 @@ const handleRefresh = () => {
getRebateList()
}
// 表格控制按钮
const handleTableSearch = () => {
handleSearch()
}
const handleTableRefresh = () => {
handleRefresh()
}
const handleTableExport = () => {
handleExport()
}
const handleTableViewToggle = () => {
// 切换视图模式
console.log('切换视图模式')
}
// 分页控制
const handlePrevPage = () => {
if (pagination.pageNum > 1) {
pagination.pageNum--
getRebateList()
}
}
const handleNextPage = () => {
if (pagination.pageNum < getTotalPages()) {
pagination.pageNum++
getRebateList()
}
}
const handlePageChange = (page: number | string) => {
if (typeof page === 'number') {
pagination.pageNum = page
getRebateList()
}
}
const getPageNumbers = () => {
const totalPages = getTotalPages()
const current = pagination.pageNum
const pages = []
if (totalPages <= 7) {
for (let i = 1; i <= totalPages; i++) {
pages.push(i)
}
} else {
if (current <= 4) {
for (let i = 1; i <= 5; i++) {
pages.push(i)
}
pages.push('...')
pages.push(totalPages)
} else if (current >= totalPages - 3) {
pages.push(1)
pages.push('...')
for (let i = totalPages - 4; i <= totalPages; i++) {
pages.push(i)
}
} else {
pages.push(1)
pages.push('...')
for (let i = current - 1; i <= current + 1; i++) {
pages.push(i)
}
pages.push('...')
pages.push(totalPages)
}
}
return pages
}
const getTotalPages = () => {
return Math.ceil(pagination.total / pagination.pageSize)
}
// 状态样式类
const getOperateTypeClass = (type: number) => {
const classes = {
1: 'status-tag success',
2: 'status-tag warning',
3: 'status-tag danger'
}
return classes[type as keyof typeof classes] || 'status-tag'
}
const getCalcFlagClass = (flag: number) => {
const classes = {
0: 'status-tag warning',
1: 'status-tag success'
}
return classes[flag as keyof typeof classes] || 'status-tag'
}
// 分页变化
const handleSizeChange = (size: number) => {
pagination.pageSize = size
......@@ -784,11 +907,11 @@ const handleViewDetail = async (row: Rebate) => {
const response = await rebateApi.getRebateById(row.rebateId)
console.log('详情接口响应:', response)
// 根据request.ts拦截器的处理,response.data已经是实际数据
if (response && response.data) {
rebateDetail.value = response.data
detailDialogVisible.value = true
console.log('详情数据:', response.data)
// 根据request.ts拦截器的处理,response直接就是数据对象
if (response) {
rebateDetail.value = response as unknown as Rebate
detailDialogVisible.value = true
console.log('详情数据:', response)
} else {
ElMessage.error('获取返利详情失败')
}
......@@ -1478,30 +1601,154 @@ onMounted(async () => {
border-radius: 4px;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
.search-form {
.search-row {
.search-row {
display: flex;
align-items: center;
gap: 24px;
flex-wrap: wrap;
margin-bottom: 12px;
&:last-child {
margin-bottom: 0;
}
.search-item {
display: flex;
align-items: center;
gap: 24px;
flex-wrap: wrap;
gap: 8px;
.search-item {
display: flex;
align-items: center;
gap: 8px;
label {
font-size: 14px;
color: #333;
white-space: nowrap;
min-width: 80px;
}
.search-label {
font-size: 14px;
color: #333;
white-space: nowrap;
min-width: 70px;
.search-input {
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
width: 160px;
transition: border-color 0.3s;
&:focus {
outline: none;
border-color: #409eff;
}
}
.search-actions {
margin-left: auto;
display: flex;
gap: 8px;
.search-select {
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
width: 120px;
background: white;
cursor: pointer;
transition: border-color 0.3s;
&:focus {
outline: none;
border-color: #409eff;
}
}
}
.search-actions {
margin-left: auto;
display: flex;
gap: 8px;
.search-btn, .reset-btn {
padding: 8px 16px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
cursor: pointer;
transition: all 0.3s;
background: white;
&:hover {
background: #f5f7fa;
}
&:disabled {
opacity: 0.6;
cursor: not-allowed;
}
}
.search-btn {
background: #409eff;
color: white;
border-color: #409eff;
&:hover {
background: #66b1ff;
}
}
}
}
}
.action-section {
background: white;
padding: 12px 20px;
margin-bottom: 16px;
border-radius: 4px;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
.action-buttons {
display: flex;
gap: 12px;
.action-btn {
padding: 8px 16px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
cursor: pointer;
transition: all 0.3s;
background: white;
&:hover {
background: #f5f7fa;
}
&:disabled {
opacity: 0.6;
cursor: not-allowed;
}
&.primary {
background: #409eff;
color: white;
border-color: #409eff;
&:hover {
background: #66b1ff;
}
}
&.secondary {
background: #67c23a;
color: white;
border-color: #67c23a;
&:hover {
background: #85ce61;
}
}
&.danger {
background: #f56c6c;
color: white;
border-color: #f56c6c;
&:hover {
background: #f78989;
}
}
}
}
......@@ -1580,61 +1827,113 @@ onMounted(async () => {
.table-section {
background: white;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
border-radius: 4px;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
overflow: hidden;
.table-header {
display: flex;
justify-content: space-between;
justify-content: flex-end;
align-items: center;
padding: 20px 20px 0 20px;
margin-bottom: 16px;
padding: 12px 20px;
border-bottom: 1px solid #f0f0f0;
background: #fafafa;
.table-title {
font-size: 16px;
font-weight: 500;
color: #333;
.table-controls {
display: flex;
gap: 8px;
.control-btn {
padding: 6px 12px;
border: 1px solid #ddd;
border-radius: 4px;
background: white;
cursor: pointer;
font-size: 14px;
transition: all 0.3s;
&:hover {
background: #f5f7fa;
border-color: #409eff;
}
}
}
}
.table-actions {
.table-container {
position: relative;
.loading-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(255, 255, 255, 0.8);
display: flex;
align-items: center;
gap: 12px;
justify-content: center;
z-index: 10;
.table-info {
font-size: 12px;
color: #999;
.loading-spinner {
font-size: 14px;
color: #666;
}
}
}
.el-table {
border: none;
.data-table {
width: 100%;
border-collapse: collapse;
font-size: 14px;
:deep(.el-table__header) {
th {
background-color: #fafafa;
border: none;
font-weight: 500;
color: #333;
}
}
thead {
background: #fafafa;
:deep(.el-table__body) {
tr:hover > td {
background-color: #f5f7fa;
th {
padding: 12px 16px;
text-align: left;
font-weight: 500;
color: #333;
border-bottom: 1px solid #e0e0e0;
white-space: nowrap;
}
}
td {
border: none;
border-bottom: 1px solid #f0f0f0;
tbody {
tr {
transition: background-color 0.3s;
&:hover {
background: #f5f7fa;
}
&:nth-child(even) {
background: #fafafa;
}
&:nth-child(even):hover {
background: #f0f2f5;
}
td {
padding: 12px 16px;
border-bottom: 1px solid #f0f0f0;
vertical-align: middle;
&.empty-data {
text-align: center;
color: #999;
font-style: italic;
padding: 40px;
}
}
}
}
}
}
.pagination-wrapper {
display: flex;
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 20px;
......@@ -1646,15 +1945,100 @@ onMounted(async () => {
color: #666;
}
.pagination-controls {
display: flex;
gap: 4px;
align-items: center;
.page-btn {
padding: 6px 12px;
border: 1px solid #ddd;
border-radius: 4px;
background: white;
cursor: pointer;
font-size: 14px;
transition: all 0.3s;
min-width: 32px;
&:hover:not(:disabled) {
background: #f5f7fa;
border-color: #409eff;
}
&:disabled {
opacity: 0.5;
cursor: not-allowed;
}
&.active {
background: #409eff;
color: white;
border-color: #409eff;
}
}
}
.pagination-jump {
display: flex;
align-items: center;
gap: 8px;
font-size: 14px;
color: #666;
.jump-input {
width: 50px;
padding: 4px 8px;
border: 1px solid #ddd;
border-radius: 4px;
text-align: center;
font-size: 14px;
&:focus {
outline: none;
border-color: #409eff;
}
}
}
}
}
.status-tag {
padding: 2px 8px;
border-radius: 12px;
font-size: 12px;
font-weight: 500;
&.success {
background: #f0f9ff;
color: #67c23a;
}
&.warning {
background: #fdf6ec;
color: #e6a23c;
}
&.danger {
background: #fef0f0;
color: #f56c6c;
}
}
.action-link {
color: #409eff;
text-decoration: none;
cursor: pointer;
font-size: 14px;
background: none;
border: none;
padding: 0;
&:hover {
color: #66b1ff;
text-decoration: underline;
}
}
.amount-text {
color: #f56c6c;
font-weight: 500;
......