Toggle navigation
Toggle navigation
This project
Loading...
Sign in
zhouhui.jiang
/
test_cursor
Go to a project
Toggle navigation
Toggle navigation pinning
Projects
Groups
Snippets
Help
Project
Activity
Repository
Pipelines
Graphs
Issues
0
Merge Requests
0
Wiki
Snippets
Network
Create a new issue
Builds
Commits
Issue Boards
Authored by
zhouhui.jiang
2025-10-13 14:49:07 +0800
Browse Files
Options
Browse Files
Download
Email Patches
Plain Diff
Commit
2bf069faa4bb8845c77c19cec6de44d66ede82ee
2bf069fa
1 parent
8c919b54
update 经销商
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
378 additions
and
61 deletions
backend/src/main/java/com/apple/erp/dto/DealerAddReq.java
backend/src/main/java/com/apple/erp/dto/DealerUpdateReq.java
backend/src/main/resources/mapper/RebateMapper.xml
frontend/src/api/dealer.ts
frontend/src/api/rebate.ts
frontend/src/views/dealer/index.vue
backend/src/main/java/com/apple/erp/dto/DealerAddReq.java
View file @
2bf069f
...
...
@@ -14,7 +14,6 @@ import java.time.LocalDate;
public
class
DealerAddReq
{
@NotBlank
(
message
=
"经销商编码不能为空"
)
@Pattern
(
regexp
=
"^[A-Z0-9]{6,20}$"
,
message
=
"经销商编码格式不正确,应为6-20位大写字母和数字"
)
@Schema
(
description
=
"经销商编码"
,
required
=
true
)
private
String
dealerCode
;
...
...
backend/src/main/java/com/apple/erp/dto/DealerUpdateReq.java
View file @
2bf069f
...
...
@@ -18,7 +18,6 @@ public class DealerUpdateReq {
private
Long
dealerId
;
@NotBlank
(
message
=
"经销商编码不能为空"
)
@Pattern
(
regexp
=
"^[A-Z0-9]{6,20}$"
,
message
=
"经销商编码格式不正确,应为6-20位大写字母和数字"
)
@Schema
(
description
=
"经销商编码"
,
required
=
true
)
private
String
dealerCode
;
...
...
backend/src/main/resources/mapper/RebateMapper.xml
View file @
2bf069f
...
...
@@ -42,7 +42,7 @@
<if
test=
"params.rebateEndDate != null and params.rebateEndDate != ''"
>
AND rebate_date
<
= #{params.rebateEndDate}
</if>
ORDER BY
create_tim
e DESC
ORDER BY
rebate_dat
e DESC
</select>
<!-- 根据经销商编码统计返利总金额 -->
...
...
frontend/src/api/dealer.ts
View file @
2bf069f
...
...
@@ -127,49 +127,49 @@ export interface ApiResponse<T = any> {
// 经销商管理API
export
const
dealerApi
=
{
// 分页查询经销商列表
getDealerList
:
(
params
:
DealerQueryReq
)
=>
{
getDealerList
:
(
params
:
DealerQueryReq
)
:
Promise
<
ApiResponse
<
PageData
<
DealerInfo
>>>
=>
{
return
api
.
get
<
ApiResponse
<
PageData
<
DealerInfo
>>>
(
'/dealer/list'
,
{
params
})
},
// 获取经销商详情
getDealerDetail
:
(
dealerId
:
number
)
=>
{
getDealerDetail
:
(
dealerId
:
number
)
:
Promise
<
ApiResponse
<
DealerInfo
>>
=>
{
return
api
.
get
<
ApiResponse
<
DealerInfo
>>
(
`/dealer/
${
dealerId
}
`
)
},
// 新增经销商
addDealer
:
(
data
:
DealerAddReq
)
=>
{
addDealer
:
(
data
:
DealerAddReq
)
:
Promise
<
ApiResponse
>
=>
{
return
api
.
post
<
ApiResponse
>
(
'/dealer'
,
data
)
},
// 修改经销商
updateDealer
:
(
data
:
DealerUpdateReq
)
=>
{
updateDealer
:
(
data
:
DealerUpdateReq
)
:
Promise
<
ApiResponse
>
=>
{
return
api
.
post
<
ApiResponse
>
(
'/dealer/update'
,
data
)
},
// 删除经销商
deleteDealer
:
(
dealerId
:
number
)
=>
{
deleteDealer
:
(
dealerId
:
number
)
:
Promise
<
ApiResponse
>
=>
{
return
api
.
delete
<
ApiResponse
>
(
`/dealer/
${
dealerId
}
`
)
},
// 批量删除经销商
batchDeleteDealer
:
(
dealerIds
:
number
[])
=>
{
batchDeleteDealer
:
(
dealerIds
:
number
[])
:
Promise
<
ApiResponse
>
=>
{
return
api
.
delete
<
ApiResponse
>
(
'/dealer/batch'
,
{
data
:
dealerIds
})
},
// 修改经销商合作状态
updateCooperateStatus
:
(
dealerId
:
number
,
cooperateStatus
:
number
)
=>
{
updateCooperateStatus
:
(
dealerId
:
number
,
cooperateStatus
:
number
)
:
Promise
<
ApiResponse
>
=>
{
return
api
.
post
<
ApiResponse
>
(
`/dealer/
${
dealerId
}
/cooperateStatus/
${
cooperateStatus
}
`
)
},
// 修改经销商资质审核状态
updateAuditStatus
:
(
dealerId
:
number
,
qualificationAuditStatus
:
number
,
auditOpinion
?:
string
)
=>
{
updateAuditStatus
:
(
dealerId
:
number
,
qualificationAuditStatus
:
number
,
auditOpinion
?:
string
)
:
Promise
<
ApiResponse
>
=>
{
return
api
.
post
<
ApiResponse
>
(
`/dealer/
${
dealerId
}
/auditStatus`
,
null
,
{
params
:
{
qualificationAuditStatus
,
auditOpinion
}
})
},
// 获取所有经销商(用于下拉选择)
getAllDealers
:
()
=>
{
getAllDealers
:
()
:
Promise
<
ApiResponse
<
DealerInfo
[]
>>
=>
{
return
api
.
get
<
ApiResponse
<
DealerInfo
[]
>>
(
'/dealer/all'
)
}
}
...
...
frontend/src/api/rebate.ts
View file @
2bf069f
import
request
from
'@/utils/request'
// API响应接口
export
interface
ApiResponse
<
T
=
any
>
{
code
:
number
message
:
string
data
:
T
}
// 分页数据接口
export
interface
PageData
<
T
>
{
records
:
T
[]
total
:
number
pageNum
:
number
pageSize
:
number
}
// 返利台账明细类型定义
export
interface
Rebate
{
rebateId
?:
number
...
...
@@ -57,7 +72,7 @@ export interface RebateUpdateParams extends RebateAddParams {
}
// 分页查询返利列表
export
const
getRebatePage
=
(
params
:
RebateSearchParams
)
=>
{
export
const
getRebatePage
=
(
params
:
RebateSearchParams
)
:
Promise
<
ApiResponse
<
PageData
<
Rebate
>>>
=>
{
return
request
({
url
:
'/api/rebate/page'
,
method
:
'post'
,
...
...
frontend/src/views/dealer/index.vue
View file @
2bf069f
...
...
@@ -84,7 +84,6 @@
<th>统一社会信用代码</th>
<th>经销商等级</th>
<th>所在区域</th>
<th>合作起始日期</th>
<th>合作状态</th>
<th>累计应返金额</th>
<th>累计已返金额</th>
...
...
@@ -106,7 +105,6 @@
<td>{{ dealer.creditCode }}</td>
<td>{{ getDealerLevelText(dealer.dealerLevel) }}</td>
<td>{{ dealer.region }}</td>
<td>{{ dealer.cooperateStartDate }}</td>
<td>
<span :class="getCooperateStatusClass(dealer.cooperateStatus)">
{{ getCooperateStatusText(dealer.cooperateStatus) }}
...
...
@@ -119,6 +117,7 @@
<button @click="handleEdit(dealer)" class="table-btn edit">✏️ 编辑</button>
<button @click="handleDelete(dealer)" class="table-btn delete">🗑️ 删除</button>
<button @click="handleStatusChange(dealer)" class="table-btn status">🔄 状态</button>
<button @click="handleRebateDetail(dealer)" class="table-btn rebate">💰 返利明细</button>
</td>
</tr>
</tbody>
...
...
@@ -185,9 +184,7 @@
<input
v-model="formData.dealerCode"
class="form-input"
placeholder="请输入经销商编码(6-20位大写字母和数字)"
pattern="^[A-Z0-9]{6,20}$"
title="经销商编码应为6-20位大写字母和数字"
placeholder="请输入经销商编码"
required
/>
</div>
...
...
@@ -277,7 +274,7 @@
</div>
</div>
<div class="form-row">
<div class="form-item">
<div class="form-item
full-width
">
<label>合作协议URL:</label>
<input
v-model="formData.cooperationAgreementUrl"
...
...
@@ -285,22 +282,14 @@
placeholder="请输入合作协议URL"
/>
</div>
<div class="form-item">
<label>审核意见:</label>
<input
v-model="formData.auditOpinion"
class="form-input"
placeholder="请输入审核意见"
/>
</div>
</div>
<div class="form-row">
<div class="form-item full-width">
<label>
备注
:</label>
<label>
审核意见
:</label>
<textarea
v-model="formData.
remark
"
v-model="formData.
auditOpinion
"
class="form-textarea"
placeholder="请输入
备注
"
placeholder="请输入
审核意见
"
rows="3"
></textarea>
</div>
...
...
@@ -346,12 +335,103 @@
</div>
</div>
<!-- 返利明细对话框 -->
<div v-if="showRebateDialog" class="dialog-overlay" @click="handleRebateDialogClose">
<div class="dialog-content rebate-dialog" @click.stop>
<div class="dialog-header">
<h3>返利明细 - {{ currentDealer.dealerName }} ({{ currentDealer.dealerCode }})</h3>
<button @click="handleRebateDialogClose" class="dialog-close">×</button>
</div>
<div class="dialog-body">
<div v-if="rebateLoading" class="loading-overlay">
<div class="loading-spinner">加载中...</div>
</div>
<div v-else>
<table class="rebate-table">
<thead>
<tr>
<th>返利编号</th>
<th>订单编号</th>
<th>经销商代码</th>
<th>产品编码</th>
<th>返利金额</th>
<th>返利日期</th>
<th>操作类型</th>
<th>计算状态</th>
</tr>
</thead>
<tbody>
<tr v-for="rebate in rebateList" :key="rebate.rebateId">
<td>{{ rebate.rebateNo }}</td>
<td>{{ rebate.orderNo }}</td>
<td>{{ rebate.dealerCode }}</td>
<td>{{ rebate.productCode }}</td>
<td class="amount-cell">{{ formatAmount(rebate.rebateAmount) }}</td>
<td>{{ rebate.rebateDate }}</td>
<td>
<span :class="getOperateTypeClass(rebate.operateType || 0)">
{{ getOperateTypeText(rebate.operateType || 0) }}
</span>
</td>
<td>
<span :class="getCalcFlagClass(rebate.calcFlag || 0)">
{{ getCalcFlagText(rebate.calcFlag || 0) }}
</span>
</td>
</tr>
<tr v-if="rebateList.length === 0">
<td colspan="8" class="no-data">暂无返利明细数据</td>
</tr>
</tbody>
</table>
<!-- 返利明细分页 -->
<div class="rebate-pagination">
<div class="pagination-info">
共 {{ rebatePagination.total || 0 }} 条记录,第 {{ rebatePagination.pageNum || 1 }} / {{ rebateTotalPages || 1 }} 页
</div>
<div class="pagination-container">
<button
@click="handleRebatePageChange(rebatePagination.pageNum - 1)"
:disabled="rebatePagination.pageNum <= 1"
class="pagination-btn prev-btn"
>
上一页
</button>
<div class="pagination-pages">
<button
v-for="page in getRebatePageNumbers()"
:key="page"
@click="handleRebatePageChange(page)"
:class="['page-number', { active: page === rebatePagination.pageNum }]"
>
{{ page }}
</button>
</div>
<button
@click="handleRebatePageChange(rebatePagination.pageNum + 1)"
:disabled="rebatePagination.pageNum >= rebateTotalPages"
class="pagination-btn next-btn"
>
下一页
</button>
</div>
</div>
</div>
</div>
<div class="dialog-footer">
<button @click="handleRebateDialogClose" class="dialog-btn confirm">关闭</button>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, computed, onMounted } from 'vue'
import { dealerApi, type DealerInfo, type DealerQueryReq, type DealerAddReq, type DealerUpdateReq } from '../../api/dealer'
import { getRebatePage, type Rebate, type RebateSearchParams } from '../../api/rebate'
// 响应式数据
const loading = ref(false)
...
...
@@ -363,6 +443,16 @@ const showStatusDialog = ref(false)
const currentDealer = ref<DealerInfo>({} as DealerInfo)
const newCooperateStatus = ref<number>(1)
// 返利明细相关数据
const showRebateDialog = ref(false)
const rebateLoading = ref(false)
const rebateList = ref<Rebate[]>([])
const rebatePagination = reactive({
total: 0,
pageNum: 1,
pageSize: 10
})
// 搜索参数
const searchParams = reactive<DealerQueryReq>({
dealerCode: '',
...
...
@@ -393,6 +483,12 @@ const totalPages = computed(() => {
return Math.ceil(pagination.total / pagination.pageSize) || 1
})
// 返利明细分页计算属性
const rebateTotalPages = computed(() => {
if (rebatePagination.pageSize <= 0) return 1
return Math.ceil(rebatePagination.total / rebatePagination.pageSize) || 1
})
// 表单数据
const formData = reactive<DealerAddReq & { dealerId?: number }>({
dealerCode: '',
...
...
@@ -455,6 +551,42 @@ const formatAmount = (amount: number | undefined) => {
return `¥${amount.toFixed(2)}`
}
// 获取操作类型文本
const getOperateTypeText = (type: number) => {
const typeMap: { [key: number]: string } = {
1: '新增',
2: '扣减'
}
return typeMap[type] || '未知'
}
// 获取操作类型样式
const getOperateTypeClass = (type: number) => {
const classMap: { [key: number]: string } = {
1: 'operate-add',
2: 'operate-subtract'
}
return classMap[type] || ''
}
// 获取计算状态文本
const getCalcFlagText = (flag: number) => {
const flagMap: { [key: number]: string } = {
0: '未计算',
1: '已计算'
}
return flagMap[flag] || '未知'
}
// 获取计算状态样式
const getCalcFlagClass = (flag: number) => {
const classMap: { [key: number]: string } = {
0: 'calc-pending',
1: 'calc-completed'
}
return classMap[flag] || ''
}
// 获取分页页码数组
const getPageNumbers = () => {
...
...
@@ -471,11 +603,26 @@ const getPageNumbers = () => {
return pages
}
// 获取返利明细分页页码数组
const getRebatePageNumbers = () => {
const pages = []
const totalPagesValue = rebateTotalPages.value
if (totalPagesValue <= 0) return []
const start = Math.max(1, rebatePagination.pageNum - 2)
const end = Math.min(totalPagesValue, start + 4)
for (let i = start; i <= end; i++) {
pages.push(i)
}
return pages
}
// 获取经销商列表
const fetchDealers = async () => {
try {
loading.value = true
const response = await dealerApi.getDealerList(searchParams)
const response = await dealerApi.getDealerList(searchParams)
as any
if (response.code === 200) {
dealerList.value = response.data.records
pagination.total = response.data.total
...
...
@@ -531,30 +678,47 @@ const handleAdd = () => {
}
// 编辑
const handleEdit = (dealer: DealerInfo) => {
isEdit.value = true
Object.assign(formData, {
dealerId: dealer.dealerId,
dealerCode: dealer.dealerCode,
dealerName: dealer.dealerName,
creditCode: dealer.creditCode,
dealerLevel: dealer.dealerLevel,
region: dealer.region,
contactPerson: dealer.contactPerson,
contactPhone: dealer.contactPhone,
cooperateStartDate: dealer.cooperateStartDate,
cooperateStatus: dealer.cooperateStatus,
businessLicenseUrl: dealer.businessLicenseUrl,
cooperationAgreementUrl: dealer.cooperationAgreementUrl
})
showDialog.value = true
const handleEdit = async (dealer: DealerInfo) => {
try {
isEdit.value = true
showDialog.value = true
// 获取最新的经销商详情
const response = await dealerApi.getDealerDetail(dealer.dealerId) as any
if (response.code === 200) {
const dealerDetail = response.data
Object.assign(formData, {
dealerId: dealerDetail.dealerId,
dealerCode: dealerDetail.dealerCode,
dealerName: dealerDetail.dealerName,
creditCode: dealerDetail.creditCode,
dealerLevel: dealerDetail.dealerLevel,
region: dealerDetail.region,
contactPerson: dealerDetail.contactPerson,
contactPhone: dealerDetail.contactPhone,
cooperateStartDate: dealerDetail.cooperateStartDate,
cooperateStatus: dealerDetail.cooperateStatus,
businessLicenseUrl: dealerDetail.businessLicenseUrl,
cooperationAgreementUrl: dealerDetail.cooperationAgreementUrl,
qualificationAuditStatus: dealerDetail.qualificationAuditStatus,
auditOpinion: dealerDetail.auditOpinion
})
} else {
alert('获取经销商详情失败')
showDialog.value = false
}
} catch (error: any) {
console.error('获取经销商详情失败:', error)
alert('获取经销商详情失败')
showDialog.value = false
}
}
// 删除
const handleDelete = async (dealer: DealerInfo) => {
if (confirm(`确定要删除经销商"${dealer.dealerName}"吗?`)) {
try {
const response = await dealerApi.deleteDealer(dealer.dealerId)
const response = await dealerApi.deleteDealer(dealer.dealerId)
as any
if (response.code === 200) {
alert('删除成功')
fetchDealers()
...
...
@@ -594,7 +758,7 @@ const handleBatchDelete = async () => {
if (confirm(`确定要删除选中的 ${selectedDealers.value.length} 个经销商吗?`)) {
try {
const response = await dealerApi.batchDeleteDealer(selectedDealers.value)
const response = await dealerApi.batchDeleteDealer(selectedDealers.value)
as any
if (response.code === 200) {
alert('批量删除成功')
selectedDealers.value = []
...
...
@@ -637,14 +801,7 @@ const handleStatusChange = (dealer: DealerInfo) => {
// 提交表单
const handleSubmit = async () => {
try {
// 前端验证经销商编码格式
const dealerCodePattern = /^[A-Z0-9]{6,20}$/
if (!dealerCodePattern.test(formData.dealerCode)) {
alert('经销商编码格式不正确,应为6-20位大写字母和数字')
return
}
let response
let response: any
if (isEdit.value) {
response = await dealerApi.updateDealer(formData as DealerUpdateReq)
} else {
...
...
@@ -695,7 +852,7 @@ const handleSubmit = async () => {
// 状态提交
const handleStatusSubmit = async () => {
try {
const response = await dealerApi.updateCooperateStatus(currentDealer.value.dealerId, newCooperateStatus.value)
const response = await dealerApi.updateCooperateStatus(currentDealer.value.dealerId, newCooperateStatus.value)
as any
if (response.code === 200) {
alert('状态修改成功')
showStatusDialog.value = false
...
...
@@ -726,6 +883,52 @@ const handleStatusSubmit = async () => {
}
}
// 返利明细相关方法
const handleRebateDetail = (dealer: DealerInfo) => {
currentDealer.value = dealer
rebatePagination.pageNum = 1
showRebateDialog.value = true
fetchRebateList()
}
const fetchRebateList = async () => {
try {
rebateLoading.value = true
const params: RebateSearchParams = {
dealerCode: currentDealer.value.dealerCode,
pageNum: rebatePagination.pageNum,
pageSize: rebatePagination.pageSize
}
const response = await getRebatePage(params) as any
// @/utils/request 的响应拦截器已经返回了 data 部分
rebateList.value = response.records
rebatePagination.total = response.total
rebatePagination.pageNum = response.current
rebatePagination.pageSize = response.size
} catch (error: any) {
console.error('获取返利明细失败:', error)
alert('获取返利明细失败')
} finally {
rebateLoading.value = false
}
}
const handleRebatePageChange = (page: number) => {
const totalPagesValue = rebateTotalPages.value
if (page >= 1 && page <= totalPagesValue && totalPagesValue > 0) {
rebatePagination.pageNum = page
fetchRebateList()
}
}
const handleRebateDialogClose = () => {
showRebateDialog.value = false
rebateList.value = []
rebatePagination.total = 0
rebatePagination.pageNum = 1
}
// 全选
const handleSelectAll = () => {
...
...
@@ -743,8 +946,7 @@ const handlePageChange = (page: number) => {
}
const handlePageSizeChange = () => {
pagination.pageSize = pageSize.value
searchParams.pageSize = pageSize.value
searchParams.pageSize = pagination.pageSize
pagination.pageNum = 1
searchParams.pageNum = 1
fetchDealers()
...
...
@@ -1301,4 +1503,106 @@ onMounted(() => {
color: #1890ff;
font-family: 'Courier New', monospace;
}
/* 返利明细相关样式 */
.table-btn.rebate {
background: linear-gradient(135deg, #ffd700, #ffed4e);
color: #333;
border: 1px solid #ffd700;
}
.table-btn.rebate:hover {
background: linear-gradient(135deg, #ffed4e, #ffd700);
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(255, 215, 0, 0.3);
}
/* 返利明细对话框样式 */
.rebate-dialog {
max-width: 1200px;
width: 90%;
max-height: 80vh;
}
.rebate-table {
width: 100%;
border-collapse: collapse;
margin-bottom: 20px;
font-size: 14px;
}
.rebate-table th,
.rebate-table td {
padding: 12px 8px;
text-align: left;
border-bottom: 1px solid #eee;
}
.rebate-table th {
background: #f8f9fa;
font-weight: 600;
color: #333;
position: sticky;
top: 0;
z-index: 10;
}
.rebate-table tbody tr:hover {
background: #f5f7fa;
}
.rebate-table .no-data {
text-align: center;
color: #999;
font-style: italic;
padding: 40px;
}
/* 返利明细分页样式 */
.rebate-pagination {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 20px;
padding: 15px 0;
border-top: 1px solid #eee;
}
/* 操作类型样式 */
.operate-add {
color: #67c23a;
font-weight: 500;
background: #f0f9ff;
padding: 2px 8px;
border-radius: 4px;
font-size: 12px;
}
.operate-subtract {
color: #f56c6c;
font-weight: 500;
background: #fef0f0;
padding: 2px 8px;
border-radius: 4px;
font-size: 12px;
}
/* 计算状态样式 */
.calc-pending {
color: #e6a23c;
font-weight: 500;
background: #fdf6ec;
padding: 2px 8px;
border-radius: 4px;
font-size: 12px;
}
.calc-completed {
color: #67c23a;
font-weight: 500;
background: #f0f9ff;
padding: 2px 8px;
border-radius: 4px;
font-size: 12px;
}
</style>
\ No newline at end of file
...
...
Please
register
or
login
to post a comment