按需求调整

This commit is contained in:
andy committed 2026-02-03 15:37:25 +08:00
1 parent 8aa3af986f
commit daacd7d742
17 files changed
+602 -165

No files matched your search

+6 -1
View File
@@ -1,5 +1,5 @@
import { http } from '@/utils/request'
import type { Report, ReportForm, ReportAuditForm, PageQuery, PageResult } from '@/types'
import type { Report, ReportForm, ReportAuditForm, ReportUpdateForm, PageQuery, PageResult } from '@/types'
// 分页查询报备
export const getReportPage = (params: PageQuery & {
@@ -21,6 +21,11 @@ export const createReport = (data: ReportForm) => {
return http.post('/report', data)
}
// 更新报备
export const updateReport = (id: number, data: ReportUpdateForm) => {
return http.put(`/report/${id}`, data)
}
// 审核报备
export const auditReport = (id: number, data: ReportAuditForm) => {
return http.put(`/report/${id}/audit`, data)
+17 -20
View File
@@ -60,13 +60,15 @@ export interface Report {
id: number
dealerId: number
dealerName: string
customerId: number
customerName: string
customerPhone?: string
schoolId?: number
schoolName?: string
product?: string
projectType?: string
description?: string
status: number
statusDesc: string
rejectReason?: string
cancelReason?: string
protectStartDate?: string
protectEndDate?: string
remainDays?: number
@@ -75,7 +77,10 @@ export interface Report {
}
export interface ReportForm {
customerId: number | null
schoolId?: number
schoolName: string
product: string
projectType: string
description?: string
}
@@ -84,6 +89,14 @@ export interface ReportAuditForm {
rejectReason?: string
}
export interface ReportUpdateForm {
product: string
projectType: string
description?: string
status: number
cancelReason?: string
}
// 分页相关类型
export interface PageQuery {
current: number
@@ -104,22 +117,6 @@ export interface DictItem {
value: string | number
}
// 报备进展记录相关类型
export interface ReportProgress {
id: number
reportId: number
progressContent: string
createdBy: number
creatorName?: string
createdAt: string
updatedAt: string
}
export interface ReportProgressForm {
reportId: number
progressContent: string
}
// 系统配置相关类型
export interface SystemConfig {
id: number
+38 -9
View File
@@ -1,7 +1,7 @@
<template>
<div class="dashboard">
<el-row :gutter="20">
<el-col :span="6">
<!-- <el-col :span="userStore.isAdmin ? 6 : 8">
<el-card class="stat-card">
<div class="stat-content">
<div class="stat-icon" style="background-color: #409eff">
@@ -14,8 +14,9 @@
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card class="stat-card">
-->
<el-col :span="userStore.isAdmin ? 8 : 12">
<el-card class="stat-card" @click="handleReportCountClick">
<div class="stat-content">
<div class="stat-icon" style="background-color: #67c23a">
<el-icon :size="30"><Document /></el-icon>
@@ -27,8 +28,8 @@
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card class="stat-card">
<el-col :span="userStore.isAdmin ? 8 : 12">
<el-card class="stat-card" @click="handlePendingClick">
<div class="stat-content">
<div class="stat-icon" style="background-color: #e6a23c">
<el-icon :size="30"><Clock /></el-icon>
@@ -40,8 +41,8 @@
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card class="stat-card">
<el-col :span="8" v-if="userStore.isAdmin">
<el-card class="stat-card" @click="handleDealerCountClick">
<div class="stat-content">
<div class="stat-icon" style="background-color: #f56c6c">
<el-icon :size="30"><Shop /></el-icon>
@@ -57,18 +58,20 @@
<el-card class="welcome-card" style="margin-top: 20px">
<h3>欢迎使用泊云智销通</h3>
<p v-if="userStore.isAdmin">您是管理员,可以管理所有客户、报备和经销商信息。</p>
<p v-else>您是经销商用户,可以管理客户和提交报备申请。</p>
<p v-if="userStore.isAdmin">您是管理员,可以管理所有报备和经销商信息。</p>
<p v-else>您是经销商用户,可以提交报备申请。</p>
</el-card>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
import { useUserStore } from '@/stores/user'
import { getStatistics } from '@/api/dashboard'
const router = useRouter()
const userStore = useUserStore()
const stats = ref({
@@ -78,6 +81,32 @@ const stats = ref({
dealerCount: 0
})
// 点击报备总数卡片
const handleReportCountClick = () => {
router.push({
path: '/report',
query: {}
})
}
// 点击待审核卡片
const handlePendingClick = () => {
router.push({
path: '/report',
query: {
status: '0'
}
})
}
// 点击经销商数卡片
const handleDealerCountClick = () => {
router.push({
path: '/dealer',
query: {}
})
}
// 加载统计数据
const fetchStatistics = async () => {
try {
+4 -4
View File
@@ -13,10 +13,10 @@
<el-icon><HomeFilled /></el-icon>
<span>首页</span>
</el-menu-item>
<el-menu-item index="/customer">
<!-- <el-menu-item index="/customer">
<el-icon><User /></el-icon>
<span>客户管理</span>
</el-menu-item>
</el-menu-item> -->
<el-menu-item index="/report">
<el-icon><Document /></el-icon>
<span>报备管理</span>
@@ -43,11 +43,11 @@
<el-dropdown>
<span class="user-name">
<el-icon><Avatar /></el-icon>
{{ userStore.userInfo?.realName }}
{{ userStore.userInfo?.dealerName }}
<el-tag v-if="userStore.isAdmin" type="danger" size="small" style="margin-left: 8px">
管理员
</el-tag>
<el-tag v-else type="success" size="small" style="margin-left: 8px">经销商</el-tag>
<!-- <el-tag v-else type="success" size="small" style="margin-left: 8px">经销商</el-tag> -->
</span>
<template #dropdown>
<el-dropdown-menu>
+277 -60
View File
@@ -18,8 +18,8 @@
<!-- 查询表单 -->
<el-form :inline="true" :model="queryForm" class="query-form">
<el-form-item label="客户名称">
<el-input v-model="queryForm.customerName" placeholder="请输入客户名称" clearable />
<el-form-item label="学校名称">
<el-input v-model="queryForm.customerName" placeholder="请输入学校名称" clearable />
</el-form-item>
<el-form-item label="状态" style="width: 180px;">
<el-select v-model="queryForm.status" placeholder="请选择状态" clearable>
@@ -27,6 +27,7 @@
<el-option label="已通过" :value="1" />
<el-option label="已驳回" :value="2" />
<el-option label="已失效" :value="3" />
<el-option label="已作废" :value="4" />
</el-select>
</el-form-item>
<el-form-item>
@@ -40,16 +41,18 @@
<!-- 数据表格 -->
<el-table :data="tableData" border stripe v-loading="loading">
<el-table-column prop="dealerName" label="经销商" />
<el-table-column prop="customerName" label="客户名称" />
<el-table-column prop="customerPhone" label="联系电话" />
<el-table-column prop="dealerName" label="经销商" v-if="userStore.isAdmin"/>
<el-table-column prop="schoolName" label="学校名称" />
<el-table-column prop="product" label="所属产品" show-overflow-tooltip />
<el-table-column prop="projectType" label="项目类型" show-overflow-tooltip />
<el-table-column prop="description" label="报备说明" show-overflow-tooltip />
<el-table-column prop="status" label="状态">
<template #default="{ row }">
<el-tag v-if="row.status === 0" type="warning">待审核</el-tag>
<el-tag v-else-if="row.status === 1" type="success">已通过</el-tag>
<el-tag v-else-if="row.status === 2" type="danger">已驳回</el-tag>
<el-tag v-else type="info">已失效</el-tag>
<el-tag v-else-if="row.status === 3" type="info">已失效</el-tag>
<el-tag v-else-if="row.status === 4" type="warning">已作废</el-tag>
</template>
</el-table-column>
<el-table-column prop="protectEndDate" label="保护期截止" width="180">
@@ -58,7 +61,7 @@
</template>
</el-table-column>
<el-table-column prop="createdAt" label="创建时间" width="180" />
<el-table-column label="操作" width="250" fixed="right">
<el-table-column label="操作" width="280" fixed="right">
<template #default="{ row }">
<el-button link type="primary" @click="handleView(row)">查看</el-button>
<el-button
@@ -77,6 +80,14 @@
>
审核
</el-button>
<el-button
v-if="userStore.isAdmin && row.status === 1"
link
type="warning"
@click="handleEdit(row)"
>
编辑
</el-button>
<el-button
v-if="!userStore.isAdmin && row.status === 0"
link
@@ -109,23 +120,36 @@
<!-- 提交报备对话框 -->
<el-dialog v-model="dialogVisible" title="提交报备" width="600px" @close="handleDialogClose">
<el-form ref="formRef" :model="formData" :rules="rules" label-width="100px">
<el-form-item label="客户" prop="customerId">
<el-select
v-model="formData.customerId"
placeholder="请选择客户"
filterable
remote
:remote-method="searchCustomer"
:loading="searchLoading"
<el-form-item label="学校" prop="schoolName">
<el-autocomplete
v-model="formData.schoolName"
:fetch-suggestions="searchSchool"
placeholder="请输入学校名称"
:trigger-on-focus="false"
style="width: 100%"
@select="handleSchoolSelect"
value-key="schoolName"
clearable
>
<el-option
v-for="item in customerOptions"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
<template #default="{ item }">
<div>{{ item.schoolName }}</div>
<div style="font-size: 12px; color: #999;">{{ item.location || '无地址信息' }}</div>
</template>
</el-autocomplete>
</el-form-item>
<el-form-item label="所属产品" prop="product">
<el-input
v-model="formData.product"
placeholder="请输入所属产品"
clearable
/>
</el-form-item>
<el-form-item label="项目类型" prop="projectType">
<el-input
v-model="formData.projectType"
placeholder="请输入项目类型"
clearable
/>
</el-form-item>
<el-form-item label="报备说明" prop="description">
<el-input
@@ -166,6 +190,109 @@
</template>
</el-dialog>
<!-- 编辑报备对话框 -->
<el-dialog v-model="editDialogVisible" title="编辑报备" width="600px" @close="handleEditDialogClose">
<el-form ref="editFormRef" :model="editFormData" :rules="editRules" label-width="100px">
<el-form-item label="学校名称">
<el-input :model-value="currentReport?.schoolName" disabled />
</el-form-item>
<el-form-item label="所属产品" prop="product">
<el-input
v-model="editFormData.product"
placeholder="请输入所属产品"
clearable
/>
</el-form-item>
<el-form-item label="项目类型" prop="projectType">
<el-input
v-model="editFormData.projectType"
placeholder="请输入项目类型"
clearable
/>
</el-form-item>
<el-form-item label="报备说明" prop="description">
<el-input
v-model="editFormData.description"
type="textarea"
:rows="4"
placeholder="请输入报备说明"
/>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="editFormData.status" placeholder="请选择状态" style="width: 100%">
<el-option label="已通过" :value="1" />
<el-option label="已作废" :value="4" />
</el-select>
</el-form-item>
<el-form-item label="作废原因" prop="cancelReason" v-if="editFormData.status === 4">
<el-input
v-model="editFormData.cancelReason"
type="textarea"
:rows="4"
placeholder="请填写作废原因"
/>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="editDialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleEditSubmit">确定</el-button>
</template>
</el-dialog>
<!-- 报备详情对话框 -->
<el-dialog v-model="detailDialogVisible" title="报备详情" width="700px">
<el-descriptions :column="2" border>
<el-descriptions-item label="经销商">
{{ currentReport?.dealerName || '-' }}
</el-descriptions-item>
<el-descriptions-item label="学校名称">
{{ currentReport?.schoolName || '-' }}
</el-descriptions-item>
<el-descriptions-item label="所属产品">
{{ currentReport?.product || '-' }}
</el-descriptions-item>
<el-descriptions-item label="项目类型">
{{ currentReport?.projectType || '-' }}
</el-descriptions-item>
<el-descriptions-item label="状态" :span="2">
<el-tag v-if="currentReport?.status === 0" type="warning">待审核</el-tag>
<el-tag v-else-if="currentReport?.status === 1" type="success">已通过</el-tag>
<el-tag v-else-if="currentReport?.status === 2" type="danger">已驳回</el-tag>
<el-tag v-else-if="currentReport?.status === 3" type="info">已失效</el-tag>
<el-tag v-else-if="currentReport?.status === 4" type="warning">已作废</el-tag>
</el-descriptions-item>
<el-descriptions-item label="保护期开始" :span="1">
{{ formatDate(currentReport?.protectStartDate) }}
</el-descriptions-item>
<el-descriptions-item label="保护期结束" :span="1">
{{ formatDate(currentReport?.protectEndDate) }}
</el-descriptions-item>
<el-descriptions-item label="剩余保护天数" v-if="currentReport?.status === 1 && currentReport?.remainDays !== undefined">
<el-tag :type="currentReport.remainDays > 7 ? 'success' : currentReport.remainDays > 0 ? 'warning' : 'danger'">
{{ currentReport.remainDays }} 天
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="驳回原因" v-if="currentReport?.status === 2" :span="2">
<span style="color: #f56c6c;">{{ currentReport?.rejectReason || '-' }}</span>
</el-descriptions-item>
<el-descriptions-item label="作废原因" v-if="currentReport?.status === 4" :span="2">
<span style="color: #e6a23c;">{{ currentReport?.cancelReason || '-' }}</span>
</el-descriptions-item>
<el-descriptions-item label="报备说明" :span="2">
<div style="white-space: pre-wrap;">{{ currentReport?.description || '-' }}</div>
</el-descriptions-item>
<el-descriptions-item label="创建时间" :span="2">
{{ currentReport?.createdAt || '-' }}
</el-descriptions-item>
<el-descriptions-item label="更新时间" :span="2">
{{ currentReport?.updatedAt || '-' }}
</el-descriptions-item>
</el-descriptions>
<template #footer>
<el-button type="primary" @click="detailDialogVisible = false">关闭</el-button>
</template>
</el-dialog>
<!-- 进展记录对话框 -->
<el-dialog v-model="progressDialogVisible" title="报备进展记录" width="1000px" @close="handleProgressDialogClose">
<el-card>
@@ -215,15 +342,17 @@
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import { useRoute } from 'vue-router'
import { ElMessage, ElMessageBox, type FormInstance, type FormRules } from 'element-plus'
import { useUserStore } from '@/stores/user'
import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
import { getReportPage, createReport, auditReport, withdrawReport } from '@/api/report'
import { getReportPage, createReport, auditReport, withdrawReport, updateReport } from '@/api/report'
import { getProgressByReportId, createProgress, updateProgress } from '@/api/report-progress'
import { searchCustomerByName } from '@/api/customer'
import { searchSchoolByName } from '@/api/school'
import { getConfigValue } from '@/api/system'
import type { Report, ReportForm, ReportProgress, ReportProgressForm } from '@/types'
import type { Report, ReportForm, ReportProgress, ReportProgressForm, ReportUpdateForm } from '@/types'
const route = useRoute()
const userStore = useUserStore()
const protectDays = ref<number>(90)
@@ -233,10 +362,14 @@ const tableData = ref<Report[]>([])
const total = ref(0)
const dialogVisible = ref(false)
const auditDialogVisible = ref(false)
const detailDialogVisible = ref(false)
const editDialogVisible = ref(false)
const auditReportId = ref<number>()
const editingReportId = ref<number>()
const formRef = ref<FormInstance>()
const searchLoading = ref(false)
const customerOptions = ref<Array<{ id: number; name: string }>>([])
const editFormRef = ref<FormInstance>()
const selectedSchool = ref<any>(null)
const currentReport = ref<Report | null>(null)
// 进展记录相关
const progressDialogVisible = ref(false)
@@ -261,7 +394,10 @@ const queryForm = reactive({
})
const formData = reactive<ReportForm>({
customerId: null,
schoolId: undefined,
schoolName: '',
product: '',
projectType: '',
description: ''
})
@@ -270,8 +406,25 @@ const auditForm = reactive({
rejectReason: ''
})
const editFormData = reactive<ReportUpdateForm>({
product: '',
projectType: '',
description: '',
status: 1,
cancelReason: ''
})
const rules: FormRules = {
customerId: [{ required: true, message: '请选择客户', trigger: 'change' }]
schoolName: [{ required: true, message: '请输入学校名称', trigger: 'blur' }],
product: [{ required: true, message: '请输入所属产品', trigger: 'blur' }],
projectType: [{ required: true, message: '请输入项目类型', trigger: 'blur' }]
}
const editRules: FormRules = {
product: [{ required: true, message: '请输入所属产品', trigger: 'blur' }],
projectType: [{ required: true, message: '请输入项目类型', trigger: 'blur' }],
status: [{ required: true, message: '请选择状态', trigger: 'change' }],
cancelReason: [{ required: true, message: '请填写作废原因', trigger: 'blur' }]
}
// 格式化日期,去掉时分秒
@@ -365,18 +518,29 @@ const fetchData = async () => {
}
}
// 搜索客户
const searchCustomer = async (query: string) => {
if (!query) return
searchLoading.value = true
try {
const res = await searchCustomerByName(query)
customerOptions.value = res
} catch (error) {
console.error('搜索客户失败', error)
} finally {
searchLoading.value = false
// 搜索学校
const searchSchool = async (queryString: string, cb: any) => {
if (!queryString || queryString.trim().length < 1) {
cb([])
return
}
try {
const schools = await searchSchoolByName(queryString)
const suggestions = schools.map((school: any) => ({
...school,
value: school.schoolName
}))
cb(suggestions)
} catch (error) {
console.error('搜索学校失败', error)
cb([])
}
}
// 学校选择回调
const handleSchoolSelect = (item: any) => {
selectedSchool.value = item
formData.schoolId = item.id
}
// 查询按钮
@@ -400,24 +564,8 @@ const handleAdd = () => {
// 查看详情
const handleView = (row: Report) => {
ElMessageBox.alert(
`
<div style="line-height: 2;">
<p><strong>经销商:</strong>${row.dealerName}</p>
<p><strong>客户名称:</strong>${row.customerName}</p>
<p><strong>联系电话:</strong>${row.customerPhone || '-'}</p>
<p><strong>报备说明:</strong>${row.description || '-'}</p>
<p><strong>状态:</strong>${row.statusDesc}</p>
<p><strong>驳回原因:</strong>${row.rejectReason || '-'}</p>
<p><strong>保护期:</strong>${formatDate(row.protectStartDate)} ~ ${formatDate(row.protectEndDate)}</p>
</div>
`,
'报备详情',
{
dangerouslyUseHTMLString: true,
confirmButtonText: '关闭'
}
)
currentReport.value = row
detailDialogVisible.value = true
}
// 审核按钮
@@ -428,6 +576,51 @@ const handleAudit = (row: Report) => {
auditDialogVisible.value = true
}
// 编辑按钮
const handleEdit = (row: Report) => {
editingReportId.value = row.id
currentReport.value = row
editFormData.product = row.product || ''
editFormData.projectType = row.projectType || ''
editFormData.description = row.description || ''
editFormData.status = row.status
editFormData.cancelReason = row.cancelReason || ''
editDialogVisible.value = true
}
// 编辑对话框关闭
const handleEditDialogClose = () => {
editFormRef.value?.resetFields()
Object.assign(editFormData, {
product: '',
projectType: '',
description: '',
status: 1,
cancelReason: ''
})
editingReportId.value = undefined
}
// 编辑提交
const handleEditSubmit = async () => {
if (!editingReportId.value) return
// 验证表单
if (editFormData.status === 4 && !editFormData.cancelReason) {
ElMessage.warning('请填写作废原因')
return
}
try {
await updateReport(editingReportId.value, editFormData)
ElMessage.success('编辑成功')
editDialogVisible.value = false
fetchData()
} catch (error) {
console.error('编辑失败', error)
}
}
// 撤回按钮
const handleWithdraw = async (row: Report) => {
try {
@@ -486,10 +679,13 @@ const handleAuditSubmit = async () => {
const handleDialogClose = () => {
formRef.value?.resetFields()
Object.assign(formData, {
customerId: null,
schoolId: undefined,
schoolName: '',
product: '',
projectType: '',
description: ''
})
customerOptions.value = []
selectedSchool.value = null
}
// 获取保护期配置
@@ -504,6 +700,13 @@ const fetchProtectDays = async () => {
onMounted(() => {
fetchProtectDays()
// 从路由查询参数中读取初始查询条件
const { status } = route.query
if (status !== undefined) {
queryForm.status = status === '' ? undefined : parseInt(status as string)
}
fetchData()
})
</script>
@@ -522,4 +725,18 @@ onMounted(() => {
.query-form {
margin-bottom: 20px;
}
/* 详情对话框样式优化 */
:deep(.el-descriptions) {
margin-top: 10px;
}
:deep(.el-descriptions__label) {
font-weight: 600;
background-color: #fafafa;
}
:deep(.el-descriptions__content) {
color: #333;
}
</style>