fix:轮胎数据汇总

This commit is contained in:
2026-01-05 20:47:14 +08:00
parent f6631c2389
commit 15b0020e61
15 changed files with 2645 additions and 0 deletions

View File

@@ -0,0 +1,438 @@
<template>
<Dialog :title="dialogTitle" v-model="dialogVisible" width="1200px">
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="120px"
v-loading="formLoading"
>
<el-divider content-position="left">车辆信息</el-divider>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="汽车品牌" prop="carBrand">
<el-input v-model="formData.carBrand" placeholder="请输入汽车品牌" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="车型" prop="carModel">
<el-input v-model="formData.carModel" placeholder="请输入车型" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="车牌号" prop="licensePlate">
<el-input v-model="formData.licensePlate" placeholder="请输入车牌号" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="厂牌型号" prop="factoryModel">
<el-input v-model="formData.factoryModel" placeholder="请输入厂牌型号" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="车架号" prop="vin">
<el-input v-model="formData.vin" placeholder="请输入车架号" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="发动机号" prop="engineNo">
<el-input v-model="formData.engineNo" placeholder="请输入发动机号" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="发票日期" prop="invoiceDate">
<el-date-picker
v-model="formData.invoiceDate"
type="date"
value-format="YYYY-MM-DD"
placeholder="选择发票日期"
style="width: 100%"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="发票金额" prop="invoiceAmount">
<el-input-number
v-model="formData.invoiceAmount"
:min="0"
:precision="2"
:step="0.01"
controls-position="right"
placeholder="请输入发票金额"
style="width: 100%"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="购买时公里数" prop="purchaseMileage">
<el-input v-model="formData.purchaseMileage" placeholder="请输入购买时公里数" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="24">
<el-form-item label="发票图片" prop="invoiceUrl">
<UploadImg v-model="formData.invoiceUrl" :file-size="10" :file-type="['image/jpeg', 'image/png', 'image/jpg', 'image/gif']" />
</el-form-item>
</el-col>
</el-row>
<el-divider content-position="left">购买方信息</el-divider>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="服务购买方" prop="serviceBuyer">
<el-input v-model="formData.serviceBuyer" placeholder="请输入服务购买方" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="车辆购买方" prop="carBuyer">
<el-input v-model="formData.carBuyer" placeholder="请输入车辆购买方" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="证件类型" prop="certType">
<el-select v-model="formData.certType" placeholder="请选择证件类型" style="width: 100%">
<el-option label="请选择字典生成" value="" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="证件号码" prop="certNo">
<el-input v-model="formData.certNo" placeholder="请输入证件号码" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="联系电话" prop="mobile">
<el-input v-model="formData.mobile" placeholder="请输入联系电话" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="会员邮箱" prop="memberEmail">
<el-input v-model="formData.memberEmail" placeholder="请输入会员邮箱" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="24">
<el-form-item label="联系地址" prop="contactAddress">
<el-input v-model="formData.contactAddress" placeholder="请输入联系地址" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="门店" prop="storeId">
<el-select
v-model="formData.storeId"
placeholder="请选择门店"
filterable
style="width: 100%"
>
<el-option
v-for="store in storeList"
:key="store.id"
:label="store.storeName"
:value="store.id"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-divider content-position="left">产品信息</el-divider>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="续保产品" prop="productId">
<el-select
v-model="formData.productId"
placeholder="请选择续保产品"
filterable
style="width: 100%"
@change="handleProductChange"
>
<el-option
v-for="product in productList"
:key="product.id"
:label="product.productName"
:value="product.id"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="服务产品" prop="serviceProduct">
<el-input v-model="formData.serviceProduct" placeholder="选择产品后自动填充" readonly />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="产品时效" prop="productValidity">
<el-input v-model="formData.productValidity" placeholder="选择产品后自动填充" readonly />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="产品费用" prop="productFee">
<el-input-number
v-model="formData.productFee"
:min="0"
:precision="2"
:step="0.01"
controls-position="right"
placeholder="请输入产品费用"
style="width: 100%"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="原厂质保时长" prop="originalWarrantyYears">
<el-input v-model="formData.originalWarrantyYears" placeholder="请输入原厂质保时长" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="原厂质保里程" prop="originalWarrantyMileage">
<el-input v-model="formData.originalWarrantyMileage" placeholder="请输入原厂质保里程" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="结算方式" prop="settlementMethod">
<el-input v-model="formData.settlementMethod" placeholder="请输入结算方式" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="录单人" prop="inputUser">
<el-input v-model="formData.inputUser" placeholder="请输入录单人" />
</el-form-item>
</el-col>
</el-row>
<el-divider content-position="left">其他信息</el-divider>
<el-row :gutter="20">
<el-col :span="24">
<el-form-item label="备注" prop="remark">
<el-input v-model="formData.remark" type="textarea" :rows="3" placeholder="请输入备注" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="24">
<el-form-item label="合同备注" prop="contractRemark">
<el-input v-model="formData.contractRemark" type="textarea" :rows="3" placeholder="请输入合同备注" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<el-button @click="submitForm" type="primary" :disabled="formLoading"> </el-button>
<el-button @click="dialogVisible = false"> </el-button>
</template>
</Dialog>
</template>
<script setup lang="ts">
import { RenewalOrderApi, RenewalOrderVO } from '@/api/car/renewalorder'
import { RenewalProductApi, RenewalProductVO } from '@/api/car/renewalproduct'
import { StoreApi, StoreVO } from '@/api/tire/store'
import UploadImg from '@/components/UploadFile/src/UploadImg.vue'
/** 车辆续保订单 表单 */
defineOptions({ name: 'RenewalOrderForm' })
const { t } = useI18n() // 国际化
const message = useMessage() // 消息弹窗
const dialogVisible = ref(false) // 弹窗的是否展示
const dialogTitle = ref('') // 弹窗的标题
const formLoading = ref(false) // 表单的加载中1修改时的数据加载2提交的按钮禁用
const formType = ref('') // 表单的类型create - 新增update - 修改
const productList = ref<RenewalProductVO[]>([]) // 续保产品列表
const storeList = ref<StoreVO[]>([]) // 门店列表
const formData = ref({
id: undefined,
carBrand: undefined,
carModel: undefined,
licensePlate: undefined,
factoryModel: undefined,
invoiceAmount: undefined,
purchaseMileage: undefined,
engineNo: undefined,
vin: undefined,
invoiceDate: undefined,
invoiceUrl: undefined,
serviceBuyer: undefined,
carBuyer: undefined,
certType: undefined,
mobile: undefined,
certNo: undefined,
contactAddress: undefined,
memberEmail: undefined,
storeId: undefined,
productId: undefined,
serviceProduct: undefined,
productValidity: undefined,
originalWarrantyYears: undefined,
originalWarrantyMileage: undefined,
productFee: undefined,
settlementMethod: undefined,
remark: undefined,
inputUser: undefined,
contractRemark: undefined
})
const formRules = reactive({
licensePlate: [{ required: true, message: '车牌号不能为空', trigger: 'blur' }],
productId: [{ required: true, message: '续保产品不能为空', trigger: 'change' }]
})
const formRef = ref() // 表单 Ref
/** 获取门店列表 */
const getStoreList = async () => {
try {
const data = await StoreApi.getStorePage({ pageNo: 1, pageSize: -1 })
storeList.value = data.list || []
} catch (error) {
console.error('获取门店列表失败:', error)
}
}
/** 获取续保产品列表 */
const getProductList = async () => {
try {
const data = await RenewalProductApi.getRenewalProductPage({ pageNo: 1, pageSize: -1 })
productList.value = data.list || []
} catch (error) {
console.error('获取续保产品列表失败:', error)
}
}
/** 处理产品选择变化 */
const handleProductChange = async (productId: number) => {
if (!productId) {
formData.value.serviceProduct = undefined
formData.value.productValidity = undefined
return
}
try {
const product = await RenewalProductApi.getRenewalProduct(productId)
// 回显服务产品和产品时效(产品时效 = 产品内容)
formData.value.serviceProduct = product.productName || ''
formData.value.productValidity = product.productContent || ''
} catch (error) {
console.error('获取产品详情失败:', error)
message.error('获取产品详情失败')
}
}
/** 打开弹窗 */
const open = async (type: string, id?: number) => {
dialogVisible.value = true
dialogTitle.value = t('action.' + type)
formType.value = type
resetForm()
// 加载门店列表和产品列表
await Promise.all([getStoreList(), getProductList()])
// 修改时,设置数据
if (id) {
formLoading.value = true
try {
const data = await RenewalOrderApi.getRenewalOrder(id)
// 处理发票日期如果是Date对象或时间戳转换为字符串格式 YYYY-MM-DD
if (data.invoiceDate) {
if (data.invoiceDate instanceof Date) {
const year = data.invoiceDate.getFullYear()
const month = String(data.invoiceDate.getMonth() + 1).padStart(2, '0')
const day = String(data.invoiceDate.getDate()).padStart(2, '0')
data.invoiceDate = `${year}-${month}-${day}`
} else if (typeof data.invoiceDate === 'number' || typeof data.invoiceDate === 'string') {
// 如果是时间戳或字符串,尝试转换
const date = new Date(data.invoiceDate)
if (!isNaN(date.getTime())) {
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
data.invoiceDate = `${year}-${month}-${day}`
}
}
}
formData.value = data
} finally {
formLoading.value = false
}
}
}
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
/** 提交表单 */
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
const submitForm = async () => {
// 校验表单
await formRef.value.validate()
// 提交请求
formLoading.value = true
try {
const data = formData.value as unknown as RenewalOrderVO
if (formType.value === 'create') {
await RenewalOrderApi.createRenewalOrder(data)
message.success(t('common.createSuccess'))
} else {
await RenewalOrderApi.updateRenewalOrder(data)
message.success(t('common.updateSuccess'))
}
dialogVisible.value = false
// 发送操作成功的事件
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
id: undefined,
carBrand: undefined,
carModel: undefined,
licensePlate: undefined,
factoryModel: undefined,
invoiceAmount: undefined,
purchaseMileage: undefined,
engineNo: undefined,
vin: undefined,
invoiceDate: undefined,
invoiceUrl: undefined,
serviceBuyer: undefined,
carBuyer: undefined,
certType: undefined,
mobile: undefined,
certNo: undefined,
contactAddress: undefined,
memberEmail: undefined,
storeId: undefined,
productId: undefined,
serviceProduct: undefined,
productValidity: undefined,
originalWarrantyYears: undefined,
originalWarrantyMileage: undefined,
productFee: undefined,
settlementMethod: undefined,
remark: undefined,
inputUser: undefined,
contractRemark: undefined
}
formRef.value?.resetFields()
}
</script>

View File

@@ -0,0 +1,160 @@
<template>
<el-card shadow="never">
<template #header>
<div class="flex items-center justify-between">
<span class="font-bold">销售趋势分析</span>
</div>
</template>
<el-skeleton :loading="loading" animated>
<Echart :height="400" :options="echartsOption" />
</el-skeleton>
</el-card>
</template>
<script setup lang="ts">
import { CarStatisticsApi } from '@/api/car/statistics'
import { EChartsOption } from 'echarts'
import { erpPriceTableColumnFormatter } from '@/utils'
import { propTypes } from '@/utils/propTypes'
import Echart from '@/components/Echart/src/Echart.vue'
defineOptions({ name: 'SalesTrendChart' })
const props = defineProps({
queryParams: propTypes.object.def({})
})
const loading = ref(false)
/** 折线图配置 */
const echartsOption = reactive<EChartsOption>({
dataset: {
dimensions: ['date', 'amount'],
source: []
},
grid: {
left: 20,
right: 20,
bottom: 20,
top: 40,
containLabel: true
},
legend: {
top: 10
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross'
},
formatter: (params: any) => {
let result = params[0].name + '<br/>'
params.forEach((item: any) => {
result += `${item.seriesName}: ${erpPriceTableColumnFormatter(null, null, item.value, null)}<br/>`
})
return result
}
},
xAxis: {
type: 'category',
boundaryGap: false,
axisTick: {
show: false
}
},
yAxis: {
type: 'value',
name: '金额',
axisTick: {
show: false
}
},
series: [
{
name: '销售金额',
type: 'line',
smooth: true,
areaStyle: {},
itemStyle: {
color: '#409EFF'
}
}
],
toolbox: {
feature: {
dataZoom: {
yAxisIndex: false
},
brush: {
type: ['lineX', 'clear']
},
saveAsImage: { show: true, name: '销售趋势分析' }
}
}
}) as EChartsOption
/** 加载数据 */
const loadData = async () => {
loading.value = true
try {
// 获取门店销售趋势和业务员销售趋势
const [storeTrend, salespersonTrend] = await Promise.all([
CarStatisticsApi.getStoreSalesTrend(props.queryParams),
CarStatisticsApi.getSalespersonSalesTrend(props.queryParams)
])
// 合并数据,按日期汇总
const trendMap = new Map()
// 处理门店趋势数据
if (storeTrend && Array.isArray(storeTrend)) {
storeTrend.forEach((item: any) => {
const key = item.date
if (!trendMap.has(key)) {
trendMap.set(key, { date: key, amount: 0 })
}
trendMap.get(key).amount += item.amount || 0
})
}
// 处理业务员趋势数据
if (salespersonTrend && Array.isArray(salespersonTrend)) {
salespersonTrend.forEach((item: any) => {
const key = item.date
if (!trendMap.has(key)) {
trendMap.set(key, { date: key, amount: 0 })
}
trendMap.get(key).amount += item.amount || 0
})
}
// 转换为数组并排序
const trendData = Array.from(trendMap.values()).sort((a, b) =>
new Date(a.date).getTime() - new Date(b.date).getTime()
)
// 更新图表数据
if (echartsOption.dataset && echartsOption.dataset['source']) {
echartsOption.dataset['source'] = trendData
}
} catch (error) {
console.error('获取销售趋势失败:', error)
} finally {
loading.value = false
}
}
watch(
() => props.queryParams,
() => {
loadData()
},
{ deep: true }
)
onMounted(() => {
loadData()
})
defineExpose({ loadData })
</script>

View File

@@ -0,0 +1,85 @@
<template>
<el-card shadow="never">
<template #header>
<div class="flex items-center justify-between">
<span class="font-bold">业务员开单率分析</span>
</div>
</template>
<el-table v-loading="loading" :data="list" max-height="500">
<el-table-column label="业务员" align="center" prop="salespersonName" min-width="120" />
<el-table-column label="门店" align="center" prop="storeName" min-width="120" />
<el-table-column label="订单数" align="center" prop="orderCount" min-width="100" />
<el-table-column label="平均开单天数" align="center" prop="averageOrderDays" min-width="120">
<template #default="scope">
{{ scope.row.averageOrderDays ? scope.row.averageOrderDays + '天' : '-' }}
</template>
</el-table-column>
<el-table-column label="30天开单率" align="center" prop="orderRate30Days" min-width="120">
<template #default="scope">
{{ scope.row.orderRate30Days ? (scope.row.orderRate30Days * 100).toFixed(2) + '%' : '-' }}
</template>
</el-table-column>
<el-table-column label="平均订单金额" align="center" prop="averageOrderAmount" :formatter="priceFormatter" min-width="120" />
<el-table-column label="距上次开单" align="center" prop="lastOrderDays" min-width="120">
<template #default="scope">
<el-tag :type="getLastOrderDaysType(scope.row.lastOrderDays)">
{{ scope.row.lastOrderDays !== null ? scope.row.lastOrderDays + '天' : '-' }}
</el-tag>
</template>
</el-table-column>
</el-table>
</el-card>
</template>
<script setup lang="ts">
import { CarStatisticsApi, SalespersonStatisticsVO } from '@/api/car/statistics'
import { erpPriceTableColumnFormatter } from '@/utils'
import { propTypes } from '@/utils/propTypes'
defineOptions({ name: 'SalespersonOrderRateAnalysis' })
const props = defineProps({
queryParams: propTypes.object.def({})
})
const loading = ref(false)
const list = ref<SalespersonStatisticsVO[]>([])
/** 金额格式化 */
const priceFormatter = erpPriceTableColumnFormatter
/** 根据距上次开单天数返回标签类型 */
const getLastOrderDaysType = (days: number | null) => {
if (days === null) return 'info'
if (days <= 7) return 'success'
if (days <= 30) return 'warning'
return 'danger'
}
/** 加载数据 */
const loadData = async () => {
loading.value = true
try {
const data = await CarStatisticsApi.getSalespersonOrderRate(props.queryParams)
list.value = data || []
} catch (error) {
console.error('获取业务员开单率分析失败:', error)
} finally {
loading.value = false
}
}
watch(
() => props.queryParams,
() => {
loadData()
},
{ deep: true }
)
onMounted(() => {
loadData()
})
defineExpose({ loadData })
</script>

View File

@@ -0,0 +1,125 @@
<template>
<el-card shadow="never">
<template #header>
<div class="flex items-center justify-between">
<span class="font-bold">业务员销售排行</span>
</div>
</template>
<el-skeleton :loading="loading" animated>
<Echart :height="400" :options="echartsOption" />
</el-skeleton>
<!-- 排行列表 -->
<el-table v-loading="loading" :data="list" class="mt-4" max-height="300">
<el-table-column label="排名" align="center" type="index" width="60" />
<el-table-column label="业务员" align="center" prop="salespersonName" min-width="120" />
<el-table-column label="门店" align="center" prop="storeName" min-width="120" />
<el-table-column label="销售金额" align="center" prop="totalAmount" :formatter="priceFormatter" min-width="120" />
<el-table-column label="订单数" align="center" prop="orderCount" min-width="100" />
<el-table-column label="平均订单金额" align="center" prop="averageOrderAmount" :formatter="priceFormatter" min-width="120" />
</el-table>
</el-card>
</template>
<script setup lang="ts">
import { CarStatisticsApi, SalespersonStatisticsVO } from '@/api/car/statistics'
import { EChartsOption } from 'echarts'
import { erpPriceTableColumnFormatter } from '@/utils'
import { propTypes } from '@/utils/propTypes'
import Echart from '@/components/Echart/src/Echart.vue'
defineOptions({ name: 'SalespersonRank' })
const props = defineProps({
queryParams: propTypes.object.def({})
})
const loading = ref(false)
const list = ref<SalespersonStatisticsVO[]>([])
/** 金额格式化 */
const priceFormatter = erpPriceTableColumnFormatter
/** 横向柱状图配置 */
const echartsOption = reactive<EChartsOption>({
dataset: {
dimensions: ['salespersonName', 'totalAmount'],
source: []
},
grid: {
left: 20,
right: 20,
bottom: 20,
top: 20,
containLabel: true
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
},
formatter: (params: any) => {
const data = params[0]
return `${data.name}<br/>销售金额: ${erpPriceTableColumnFormatter(null, null, data.value, null)}`
}
},
xAxis: {
type: 'value',
name: '销售金额'
},
yAxis: {
type: 'category',
name: '业务员',
axisLabel: {
interval: 0
}
},
series: [
{
name: '销售金额',
type: 'bar',
itemStyle: {
color: '#67C23A'
}
}
],
toolbox: {
feature: {
saveAsImage: { show: true, name: '业务员销售排行' }
}
}
}) as EChartsOption
/** 加载数据 */
const loadData = async () => {
loading.value = true
try {
const data = await CarStatisticsApi.getSalespersonRank(props.queryParams)
list.value = (data || []).slice(0, 10) // 取前10名
// 更新图表数据
if (echartsOption.dataset && echartsOption.dataset['source']) {
echartsOption.dataset['source'] = list.value.map(item => ({
salespersonName: item.salespersonName,
totalAmount: item.totalAmount
}))
}
} catch (error) {
console.error('获取业务员销售排行失败:', error)
} finally {
loading.value = false
}
}
watch(
() => props.queryParams,
() => {
loadData()
},
{ deep: true }
)
onMounted(() => {
loadData()
})
defineExpose({ loadData })
</script>

View File

@@ -0,0 +1,84 @@
<template>
<el-card shadow="never">
<template #header>
<div class="flex items-center justify-between">
<span class="font-bold">门店分析数据</span>
</div>
</template>
<el-table v-loading="loading" :data="list" max-height="500">
<el-table-column label="门店名称" align="center" prop="storeName" min-width="150" />
<el-table-column label="订单数" align="center" prop="orderCount" min-width="100" />
<el-table-column label="平均开单天数" align="center" prop="averageOrderDays" min-width="120">
<template #default="scope">
{{ scope.row.averageOrderDays ? scope.row.averageOrderDays + '天' : '-' }}
</template>
</el-table-column>
<el-table-column label="30天开单率" align="center" prop="orderRate30Days" min-width="120">
<template #default="scope">
{{ scope.row.orderRate30Days ? (scope.row.orderRate30Days * 100).toFixed(2) + '%' : '-' }}
</template>
</el-table-column>
<el-table-column label="平均订单金额" align="center" prop="averageOrderAmount" :formatter="priceFormatter" min-width="120" />
<el-table-column label="距上次开单" align="center" prop="lastOrderDays" min-width="120">
<template #default="scope">
<el-tag :type="getLastOrderDaysType(scope.row.lastOrderDays)">
{{ scope.row.lastOrderDays !== null ? scope.row.lastOrderDays + '天' : '-' }}
</el-tag>
</template>
</el-table-column>
</el-table>
</el-card>
</template>
<script setup lang="ts">
import { CarStatisticsApi, StoreSalesStatisticsVO } from '@/api/car/statistics'
import { erpPriceTableColumnFormatter } from '@/utils'
import { propTypes } from '@/utils/propTypes'
defineOptions({ name: 'StoreAnalysis' })
const props = defineProps({
queryParams: propTypes.object.def({})
})
const loading = ref(false)
const list = ref<StoreSalesStatisticsVO[]>([])
/** 金额格式化 */
const priceFormatter = erpPriceTableColumnFormatter
/** 根据距上次开单天数返回标签类型 */
const getLastOrderDaysType = (days: number | null) => {
if (days === null) return 'info'
if (days <= 7) return 'success'
if (days <= 30) return 'warning'
return 'danger'
}
/** 加载数据 */
const loadData = async () => {
loading.value = true
try {
const data = await CarStatisticsApi.getStoreOrderRate(props.queryParams)
list.value = data || []
} catch (error) {
console.error('获取门店分析数据失败:', error)
} finally {
loading.value = false
}
}
watch(
() => props.queryParams,
() => {
loadData()
},
{ deep: true }
)
onMounted(() => {
loadData()
})
defineExpose({ loadData })
</script>

View File

@@ -0,0 +1,138 @@
<template>
<el-card shadow="never">
<template #header>
<div class="flex items-center justify-between">
<span class="font-bold">门店销售统计</span>
</div>
</template>
<el-skeleton :loading="loading" animated>
<Echart :height="400" :options="echartsOption" />
</el-skeleton>
<!-- 数据表格 -->
<el-table v-loading="loading" :data="list" class="mt-4" max-height="300">
<el-table-column label="门店名称" align="center" prop="storeName" min-width="150" />
<el-table-column label="销售金额" align="center" prop="totalAmount" :formatter="priceFormatter" min-width="120" />
<el-table-column label="订单数" align="center" prop="orderCount" min-width="100" />
<el-table-column label="平均订单金额" align="center" prop="averageOrderAmount" :formatter="priceFormatter" min-width="120" />
<el-table-column label="平均开单天数" align="center" prop="averageOrderDays" min-width="120">
<template #default="scope">
{{ scope.row.averageOrderDays ? scope.row.averageOrderDays + '天' : '-' }}
</template>
</el-table-column>
<el-table-column label="30天开单率" align="center" prop="orderRate30Days" min-width="120">
<template #default="scope">
{{ scope.row.orderRate30Days ? (scope.row.orderRate30Days * 100).toFixed(2) + '%' : '-' }}
</template>
</el-table-column>
<el-table-column label="距上次开单" align="center" prop="lastOrderDays" min-width="120">
<template #default="scope">
{{ scope.row.lastOrderDays !== null ? scope.row.lastOrderDays + '天' : '-' }}
</template>
</el-table-column>
</el-table>
</el-card>
</template>
<script setup lang="ts">
import { CarStatisticsApi, StoreSalesStatisticsVO } from '@/api/car/statistics'
import { EChartsOption } from 'echarts'
import { erpPriceTableColumnFormatter } from '@/utils'
import { propTypes } from '@/utils/propTypes'
import Echart from '@/components/Echart/src/Echart.vue'
defineOptions({ name: 'StoreSalesStatistics' })
const props = defineProps({
queryParams: propTypes.object.def({})
})
const loading = ref(false)
const list = ref<StoreSalesStatisticsVO[]>([])
/** 金额格式化 */
const priceFormatter = erpPriceTableColumnFormatter
/** 柱状图配置 */
const echartsOption = reactive<EChartsOption>({
dataset: {
dimensions: ['storeName', 'totalAmount'],
source: []
},
grid: {
left: 20,
right: 20,
bottom: 40,
top: 20,
containLabel: true
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
},
formatter: (params: any) => {
const data = params[0]
return `${data.name}<br/>销售金额: ${erpPriceTableColumnFormatter(null, null, data.value, null)}`
}
},
xAxis: {
type: 'category',
axisLabel: {
rotate: 45,
interval: 0
}
},
yAxis: {
type: 'value',
name: '销售金额'
},
series: [
{
name: '销售金额',
type: 'bar',
itemStyle: {
color: '#409EFF'
}
}
],
toolbox: {
feature: {
saveAsImage: { show: true, name: '门店销售统计' }
}
}
}) as EChartsOption
/** 加载数据 */
const loadData = async () => {
loading.value = true
try {
const data = await CarStatisticsApi.getStoreSalesStatistics(props.queryParams)
list.value = data || []
// 更新图表数据
if (echartsOption.dataset && echartsOption.dataset['source']) {
echartsOption.dataset['source'] = list.value.map(item => ({
storeName: item.storeName,
totalAmount: item.totalAmount
}))
}
} catch (error) {
console.error('获取门店销售统计失败:', error)
} finally {
loading.value = false
}
}
watch(
() => props.queryParams,
() => {
loadData()
},
{ deep: true }
)
onMounted(() => {
loadData()
})
defineExpose({ loadData })
</script>

View File

@@ -0,0 +1,41 @@
<template>
<el-card shadow="never" v-loading="loading">
<div class="flex items-center justify-between">
<div>
<div class="text-gray-500 text-sm mb-2">{{ title }}</div>
<div class="text-2xl font-bold text-gray-800">
{{ formatValue(value) }}
</div>
</div>
<div class="text-4xl text-gray-300">
<Icon :icon="icon" />
</div>
</div>
</el-card>
</template>
<script setup lang="ts">
import { erpPriceTableColumnFormatter } from '@/utils'
import { propTypes } from '@/utils/propTypes'
defineOptions({ name: 'SummaryCard' })
const props = defineProps({
title: propTypes.string.def(''),
value: propTypes.oneOfType([Number, String]).def(0),
loading: propTypes.bool.def(false),
icon: propTypes.string.def('ep:money'),
isAmount: propTypes.bool.def(false)
})
const formatValue = (val: any) => {
if (val === null || val === undefined) return '0'
if (props.isAmount) {
return erpPriceTableColumnFormatter(null, null, val, null)
}
if (typeof val === 'number') {
return val.toLocaleString()
}
return val
}
</script>

View File

@@ -0,0 +1,167 @@
<template>
<ContentWrap>
<!-- 搜索工作栏 -->
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="100px"
>
<el-form-item label="时间范围" prop="times">
<el-date-picker
v-model="queryParams.times"
:shortcuts="defaultShortcuts"
class="!w-240px"
end-placeholder="结束日期"
start-placeholder="开始日期"
type="daterange"
value-format="YYYY-MM-DD HH:mm:ss"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
/>
</el-form-item>
<el-form-item label="门店" prop="storeId">
<el-select
v-model="queryParams.storeId"
placeholder="请选择门店"
clearable
class="!w-240px"
>
<el-option
v-for="store in storeList"
:key="store.id"
:label="store.name"
:value="store.id"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 看板内容 -->
<div class="flex flex-col">
<!-- 统计卡片 -->
<el-row :gutter="16" class="row">
<el-col :md="6" :sm="12" :xs="24">
<SummaryCard title="总订单金额" :value="summaryData?.totalAmount" :loading="loading" />
</el-col>
<el-col :md="6" :sm="12" :xs="24">
<SummaryCard title="总订单数" :value="summaryData?.totalOrderCount" :loading="loading" />
</el-col>
<el-col :md="6" :sm="12" :xs="24">
<SummaryCard title="平均订单金额" :value="summaryData?.averageOrderAmount" :loading="loading" />
</el-col>
<el-col :md="6" :sm="12" :xs="24">
<SummaryCard title="活跃门店数" :value="summaryData?.activeStoreCount" :loading="loading" />
</el-col>
</el-row>
<!-- 图表区域 -->
<el-row :gutter="16" class="row">
<!-- 门店销售统计 -->
<el-col :md="12" :sm="24" :xs="24">
<StoreSalesStatistics :query-params="queryParams" />
</el-col>
<!-- 业务员销售排行 -->
<el-col :md="12" :sm="24" :xs="24">
<SalespersonRank :query-params="queryParams" />
</el-col>
</el-row>
<!-- 分析数据 -->
<el-row :gutter="16" class="row">
<!-- 业务员开单率分析 -->
<el-col :md="12" :sm="24" :xs="24">
<SalespersonOrderRateAnalysis :query-params="queryParams" />
</el-col>
<!-- 门店分析数据 -->
<el-col :md="12" :sm="24" :xs="24">
<StoreAnalysis :query-params="queryParams" />
</el-col>
</el-row>
<!-- 销售趋势 -->
<el-row :gutter="16" class="row">
<el-col :span="24">
<SalesTrendChart :query-params="queryParams" />
</el-col>
</el-row>
</div>
</template>
<script setup lang="ts">
import { defaultShortcuts } from '@/utils/formatTime'
import { CarStatisticsApi } from '@/api/car/statistics'
import SummaryCard from './components/SummaryCard.vue'
import StoreSalesStatistics from './components/StoreSalesStatistics.vue'
import SalespersonRank from './components/SalespersonRank.vue'
import SalespersonOrderRateAnalysis from './components/SalespersonOrderRateAnalysis.vue'
import StoreAnalysis from './components/StoreAnalysis.vue'
import SalesTrendChart from './components/SalesTrendChart.vue'
import dayjs from 'dayjs'
/** 车辆续保订单看板 */
defineOptions({ name: 'RenewalOrderDashboard' })
const loading = ref(true)
const queryFormRef = ref()
const storeList = ref<any[]>([]) // 门店列表
const summaryData = ref<any>({})
const queryParams = reactive({
times: [
// 默认显示最近30天的数据
dayjs().subtract(30, 'day').format('YYYY-MM-DD 00:00:00'),
dayjs().format('YYYY-MM-DD 23:59:59')
],
storeId: undefined
})
/** 获取汇总数据 */
const getSummaryData = async () => {
try {
loading.value = true
// 这里可以调用汇总接口,暂时使用模拟数据
summaryData.value = {
totalAmount: 0,
totalOrderCount: 0,
averageOrderAmount: 0,
activeStoreCount: 0
}
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
getSummaryData()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
queryParams.times = [
dayjs().subtract(30, 'day').format('YYYY-MM-DD 00:00:00'),
dayjs().format('YYYY-MM-DD 23:59:59')
]
handleQuery()
}
/** 初始化 **/
onMounted(() => {
getSummaryData()
})
</script>
<style lang="scss" scoped>
.row {
.el-col {
margin-bottom: 1rem;
}
}
</style>

View File

@@ -0,0 +1,313 @@
<template>
<ContentWrap>
<!-- 搜索工作栏 -->
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="100px"
>
<el-form-item label="车牌号" prop="licensePlate">
<el-input
v-model="queryParams.licensePlate"
placeholder="请输入车牌号"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="服务购买方" prop="serviceBuyer">
<el-input
v-model="queryParams.serviceBuyer"
placeholder="请输入服务购买方"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="联系电话" prop="mobile">
<el-input
v-model="queryParams.mobile"
placeholder="请输入联系电话"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="车架号" prop="vin">
<el-input
v-model="queryParams.vin"
placeholder="请输入车架号"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="门店" prop="storeId">
<el-select
v-model="queryParams.storeId"
placeholder="请选择门店"
clearable
filterable
class="!w-240px"
>
<el-option
v-for="store in storeList"
:key="store.id"
:label="store.storeName"
:value="store.id"
/>
</el-select>
</el-form-item>
<el-form-item label="创建时间" prop="createTime">
<el-date-picker
v-model="queryParams.createTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-220px"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
<el-button
type="primary"
plain
@click="openForm('create')"
v-hasPermi="['car:renewal-order:create']"
>
<Icon icon="ep:plus" class="mr-5px" /> 新增
</el-button>
<el-button
type="success"
plain
@click="handleExport"
:loading="exportLoading"
v-hasPermi="['car:renewal-order:export']"
>
<Icon icon="ep:download" class="mr-5px" /> 导出
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
<el-table-column type="index" label="序号" align="center" width="60" />
<el-table-column label="车牌号" align="center" prop="licensePlate" width="120">
<template #default="scope">
<span
v-if="scope.row.licensePlate"
class="license-plate-link"
@click="openForm('update', scope.row.id)"
>
{{ scope.row.licensePlate }}
</span>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column label="汽车品牌" align="center" prop="carBrand" />
<el-table-column label="车型" align="center" prop="carModel" />
<el-table-column label="厂牌型号" align="center" prop="factoryModel" />
<el-table-column label="发票金额" align="center" prop="invoiceAmount" :formatter="priceFormatter" />
<el-table-column label="购买时公里数" align="center" prop="purchaseMileage" />
<el-table-column label="发动机号" align="center" prop="engineNo" />
<el-table-column label="车架号" align="center" prop="vin" />
<el-table-column label="发票日期" align="center" prop="invoiceDate" :formatter="dateFormatter2" />
<el-table-column label="发票图片" align="center" prop="invoiceUrl" width="120">
<template #default="scope">
<el-image
v-if="scope.row.invoiceUrl"
:src="scope.row.invoiceUrl"
:preview-src-list="[scope.row.invoiceUrl]"
preview-teleported
fit="cover"
class="h-60px w-60px cursor-pointer"
lazy
/>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column label="服务购买方" align="center" prop="serviceBuyer" />
<el-table-column label="车辆购买方" align="center" prop="carBuyer" />
<el-table-column label="证件类型" align="center" prop="certType" />
<el-table-column label="联系电话" align="center" prop="mobile" />
<el-table-column label="证件号码" align="center" prop="certNo" />
<el-table-column label="联系地址" align="center" prop="contactAddress" />
<el-table-column label="会员邮箱" align="center" prop="memberEmail" />
<el-table-column label="门店" align="center" prop="storeName" />
<el-table-column label="服务产品" align="center" prop="serviceProduct" />
<el-table-column label="产品时效" align="center" prop="productValidity" />
<el-table-column label="原厂质保时长" align="center" prop="originalWarrantyYears" />
<el-table-column label="原厂质保里程" align="center" prop="originalWarrantyMileage" />
<el-table-column label="产品费用" align="center" prop="productFee" :formatter="priceFormatter" />
<el-table-column label="结算方式" align="center" prop="settlementMethod" />
<el-table-column label="备注" align="center" prop="remark" />
<el-table-column label="录单人" align="center" prop="inputUser" />
<el-table-column label="合同备注" align="center" prop="contractRemark" />
<el-table-column
label="创建时间"
align="center"
prop="createTime"
:formatter="dateFormatter"
width="180px"
/>
<el-table-column label="操作" align="center" min-width="120px">
<template #default="scope">
<el-button
link
type="primary"
@click="openForm('update', scope.row.id)"
v-hasPermi="['car:renewal-order:update']"
>
编辑
</el-button>
<el-button
link
type="danger"
@click="handleDelete(scope.row.id)"
v-hasPermi="['car:renewal-order:delete']"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
<!-- 表单弹窗添加/修改 -->
<RenewalOrderForm ref="formRef" @success="getList" />
</template>
<script setup lang="ts">
import { dateFormatter, dateFormatter2 } from '@/utils/formatTime'
import { erpPriceTableColumnFormatter } from '@/utils'
import download from '@/utils/download'
import { RenewalOrderApi, RenewalOrderVO } from '@/api/car/renewalorder'
import { StoreApi, StoreVO } from '@/api/tire/store'
import RenewalOrderForm from './RenewalOrderForm.vue'
/** 车辆续保订单 列表 */
defineOptions({ name: 'RenewalOrder' })
const message = useMessage() // 消息弹窗
const { t } = useI18n() // 国际化
const loading = ref(true) // 列表的加载中
const list = ref<RenewalOrderVO[]>([]) // 列表的数据
const total = ref(0) // 列表的总页数
const storeList = ref<StoreVO[]>([]) // 门店列表
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
licensePlate: undefined,
serviceBuyer: undefined,
mobile: undefined,
vin: undefined,
storeId: undefined,
createTime: []
})
const queryFormRef = ref() // 搜索的表单
const exportLoading = ref(false) // 导出的加载中
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await RenewalOrderApi.getRenewalOrderPage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/** 添加/修改操作 */
const formRef = ref()
const openForm = (type: string, id?: number) => {
formRef.value.open(type, id)
}
/** 删除按钮操作 */
const handleDelete = async (id: number) => {
try {
// 删除的二次确认
await message.delConfirm()
// 发起删除
await RenewalOrderApi.deleteRenewalOrder(id)
message.success(t('common.delSuccess'))
// 刷新列表
await getList()
} catch {}
}
/** 导出按钮操作 */
const handleExport = async () => {
try {
// 导出的二次确认
await message.exportConfirm()
// 发起导出
exportLoading.value = true
const data = await RenewalOrderApi.exportRenewalOrder(queryParams)
download.excel(data, '车辆续保订单.xls')
} catch {
} finally {
exportLoading.value = false
}
}
/** 获取门店列表 */
const getStoreList = async () => {
try {
const data = await StoreApi.getStorePage({ pageNo: 1, pageSize: -1 })
storeList.value = data.list || []
} catch (error) {
console.error('获取门店列表失败:', error)
}
}
/** 金额格式化 */
const priceFormatter = erpPriceTableColumnFormatter
/** 初始化 **/
onMounted(() => {
getStoreList()
getList()
})
</script>
<style scoped>
.license-plate-link {
color: #409eff;
text-decoration: underline;
cursor: pointer;
}
.license-plate-link:hover {
color: #66b1ff;
}
</style>

View File

@@ -0,0 +1,130 @@
<template>
<Dialog :title="dialogTitle" v-model="dialogVisible" width="900px">
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="120px"
v-loading="formLoading"
>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="产品名称" prop="productName">
<el-input v-model="formData.productName" placeholder="请输入产品名称" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="产品类别" prop="productType">
<el-select v-model="formData.productType" placeholder="请选择产品类别" style="width: 100%">
<el-option
v-for="dict in getStrDictOptions(DICT_TYPE.CAR_RENEWAL_PRODUCT_TYPE)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="24">
<el-form-item label="产品内容" prop="productContent">
<el-input v-model="formData.productContent" type="textarea" :rows="3" placeholder="请输入产品内容" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="24">
<el-form-item label="产品备注" prop="remark">
<el-input v-model="formData.remark" type="textarea" :rows="3" placeholder="请输入备注" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<el-button @click="submitForm" type="primary" :disabled="formLoading"> </el-button>
<el-button @click="dialogVisible = false"> </el-button>
</template>
</Dialog>
</template>
<script setup lang="ts">
import { getStrDictOptions, DICT_TYPE } from '@/utils/dict'
import { RenewalProductApi, RenewalProductVO } from '@/api/car/renewalproduct'
/** 车辆续保产品信息 表单 */
defineOptions({ name: 'RenewalProductForm' })
const { t } = useI18n() // 国际化
const message = useMessage() // 消息弹窗
const dialogVisible = ref(false) // 弹窗的是否展示
const dialogTitle = ref('') // 弹窗的标题
const formLoading = ref(false) // 表单的加载中1修改时的数据加载2提交的按钮禁用
const formType = ref('') // 表单的类型create - 新增update - 修改
const formData = ref({
id: undefined,
productName: undefined,
productContent: undefined,
productType: undefined,
remark: undefined
})
const formRules = reactive({
productName: [{ required: true, message: '产品名称不能为空', trigger: 'blur' }],
productType: [{ required: true, message: '产品类别不能为空', trigger: 'change' }]
})
const formRef = ref() // 表单 Ref
/** 打开弹窗 */
const open = async (type: string, id?: number) => {
dialogVisible.value = true
dialogTitle.value = t('action.' + type)
formType.value = type
resetForm()
// 修改时,设置数据
if (id) {
formLoading.value = true
try {
formData.value = await RenewalProductApi.getRenewalProduct(id)
} finally {
formLoading.value = false
}
}
}
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
/** 提交表单 */
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
const submitForm = async () => {
// 校验表单
await formRef.value.validate()
// 提交请求
formLoading.value = true
try {
const data = formData.value as unknown as RenewalProductVO
if (formType.value === 'create') {
await RenewalProductApi.createRenewalProduct(data)
message.success(t('common.createSuccess'))
} else {
await RenewalProductApi.updateRenewalProduct(data)
message.success(t('common.updateSuccess'))
}
dialogVisible.value = false
// 发送操作成功的事件
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
id: undefined,
productName: undefined,
productContent: undefined,
productType: undefined,
remark: undefined
}
formRef.value?.resetFields()
}
</script>

View File

@@ -0,0 +1,211 @@
<template>
<ContentWrap>
<!-- 搜索工作栏 -->
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="68px"
>
<el-form-item label="产品名称" prop="productName">
<el-input
v-model="queryParams.productName"
placeholder="请输入产品名称"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="产品类别" prop="productType">
<el-select
v-model="queryParams.productType"
placeholder="请选择产品类别"
clearable
class="!w-240px"
>
<el-option
v-for="dict in getStrDictOptions(DICT_TYPE.CAR_RENEWAL_PRODUCT_TYPE)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="创建时间" prop="createTime">
<el-date-picker
v-model="queryParams.createTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-220px"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
<el-button
type="primary"
plain
@click="openForm('create')"
v-hasPermi="['car:renewal-product:create']"
>
<Icon icon="ep:plus" class="mr-5px" /> 新增
</el-button>
<el-button
type="success"
plain
@click="handleExport"
:loading="exportLoading"
v-hasPermi="['car:renewal-product:export']"
>
<Icon icon="ep:download" class="mr-5px" /> 导出
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
<el-table-column type="index" label="序号" align="center" width="60" />
<el-table-column label="产品名称" align="center" prop="productName" />
<el-table-column label="产品内容" align="center" prop="productContent" />
<el-table-column label="产品类别" align="center" prop="productType">
<template #default="scope">
<dict-tag :type="DICT_TYPE.CAR_RENEWAL_PRODUCT_TYPE" :value="scope.row.productType" />
</template>
</el-table-column>
<el-table-column label="备注" align="center" prop="remark" />
<el-table-column
label="创建时间"
align="center"
prop="createTime"
:formatter="dateFormatter"
width="180px"
/>
<el-table-column label="操作" align="center" min-width="120px">
<template #default="scope">
<el-button
link
type="primary"
@click="openForm('update', scope.row.id)"
v-hasPermi="['car:renewal-product:update']"
>
编辑
</el-button>
<el-button
link
type="danger"
@click="handleDelete(scope.row.id)"
v-hasPermi="['car:renewal-product:delete']"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
<!-- 表单弹窗添加/修改 -->
<RenewalProductForm ref="formRef" @success="getList" />
</template>
<script setup lang="ts">
import { getStrDictOptions, DICT_TYPE } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import download from '@/utils/download'
import { RenewalProductApi, RenewalProductVO } from '@/api/car/renewalproduct'
import RenewalProductForm from './RenewalProductForm.vue'
/** 车辆续保产品信息 列表 */
defineOptions({ name: 'RenewalProduct' })
const message = useMessage() // 消息弹窗
const { t } = useI18n() // 国际化
const loading = ref(true) // 列表的加载中
const list = ref<RenewalProductVO[]>([]) // 列表的数据
const total = ref(0) // 列表的总页数
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
productName: undefined,
productType: undefined,
createTime: []
})
const queryFormRef = ref() // 搜索的表单
const exportLoading = ref(false) // 导出的加载中
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await RenewalProductApi.getRenewalProductPage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/** 添加/修改操作 */
const formRef = ref()
const openForm = (type: string, id?: number) => {
formRef.value.open(type, id)
}
/** 删除按钮操作 */
const handleDelete = async (id: number) => {
try {
// 删除的二次确认
await message.delConfirm()
// 发起删除
await RenewalProductApi.deleteRenewalProduct(id)
message.success(t('common.delSuccess'))
// 刷新列表
await getList()
} catch {}
}
/** 导出按钮操作 */
const handleExport = async () => {
try {
// 导出的二次确认
await message.exportConfirm()
// 发起导出
exportLoading.value = true
const data = await RenewalProductApi.exportRenewalProduct(queryParams)
download.excel(data, '车辆续保产品信息.xls')
} catch {
} finally {
exportLoading.value = false
}
}
/** 初始化 **/
onMounted(() => {
getList()
})
</script>