Просмотр исходного кода

feat(router): 添加用水统计功能模块

- 在路由配置中新增用水统计页面路由
- 创建用水统计仪表板页面,包含统计概览和图表展示
- 添加月度用水趋势图表和TOP客户用水图表
- 实现客户用水详细统计表格功能
- 在用水信息页面添加趋势分析按钮和对话框
- 集成echarts图表库实现数据可视化
- 优化用水信息页面的搜索字段标签
- 添加用水趋势分析的API接口调用
- 实现图表响应式调整和数据统计功能
nahida 5 месяцев назад
Родитель
Сommit
96c4a913bc
4 измененных файлов с 769 добавлено и 38 удалено
  1. 6 0
      src/router/route.ts
  2. 5 5
      src/views/zhyg/ygxx.vue
  3. 494 0
      src/views/zhys/ystj.vue
  4. 264 33
      src/views/zhys/ysxx.vue

+ 6 - 0
src/router/route.ts

@@ -538,6 +538,12 @@ export const routeList:RouterType[] = [
     icon: 'CirclePlusFilled',
     addr: 'zhys/ysxx'
   },
+  {
+    path: 'zhys/ystj',
+    name: '用水统计',
+    icon: 'CirclePlusFilled',
+    addr: 'zhys/ystj'
+  },
 
 ]
 export const useDynamicRoutes: () => RouterType[] = () => {

+ 5 - 5
src/views/zhyg/ygxx.vue

@@ -359,7 +359,7 @@ onMounted(()=>{
         <el-table-column type="selection" width="55" />
         <el-table-column prop="companyName" label="公司名称" min-width="120" />
         <el-table-column prop="jobOpenings" label="岗位名称" min-width="120" />
-        <el-table-column prop="keyTerms" label="岗位描述" min-width="150" show-overflow-tooltip />
+        <el-table-column prop="keyTerms" label="关键词条" min-width="150" show-overflow-tooltip />
         <el-table-column prop="numberRecruits" label="招聘人数" width="100" />
         <el-table-column prop="salary" label="薪资" width="120" />
         <el-table-column prop="workLocation" label="工作地点" width="120" />
@@ -430,11 +430,11 @@ onMounted(()=>{
         <el-form-item label="联系方式">
           <el-input v-model="formData.contactInformation" placeholder="请输入联系方式" />
         </el-form-item>
-        <el-form-item label="岗位描述">
-          <el-input v-model="formData.keyTerms" type="textarea" :rows="3" placeholder="请输入岗位描述" />
+        <el-form-item label="关键词条">
+          <el-input v-model="formData.keyTerms" type="textarea" :rows="3" placeholder="请输入关键词条" />
         </el-form-item>
-        <el-form-item label="岗位要求">
-          <el-input v-model="formData.jobRequirements" type="textarea" :rows="3" placeholder="请输入岗位要求" />
+        <el-form-item label="招聘详情">
+          <el-input v-model="formData.jobRequirements" type="textarea" :rows="3" placeholder="请输入招聘详情" />
         </el-form-item>
         <el-form-item label="备注">
           <el-input v-model="formData.remarks" type="textarea" :rows="2" placeholder="请输入备注信息" />

+ 494 - 0
src/views/zhys/ystj.vue

@@ -0,0 +1,494 @@
+<script setup lang="ts">
+import { onMounted, ref, computed, nextTick } from 'vue'
+import { clientGet } from '@/utils/request.ts'
+import { ElMessage } from 'element-plus'
+import type { BaseResponse } from '@/utils/type.ts'
+import * as echarts from 'echarts'
+import { TrendingUp, Users, Droplets, DollarSign, Trophy, Calendar } from 'lucide-vue-next'
+
+interface CustomerSummaryResponse extends BaseResponse{
+  data:{
+    customerName:string,
+    totalWater:number,
+    totalAmount:number
+  }[]
+}
+
+interface MonthSummaryResponse extends BaseResponse{
+  data:{
+    totalAmount:number,
+    totalWater:number,
+    statTime:string
+  }[]
+}
+
+interface TopCustomersResponse extends BaseResponse{
+  data:{
+    customerName:string,
+    totalWater:number,
+    totalAmount:number
+  }[]
+}
+
+const loading = ref(true)
+const customerSummaryData = ref<CustomerSummaryResponse['data']>([])
+const monthSummaryData = ref<MonthSummaryResponse['data']>([])
+const topCustomersData = ref<TopCustomersResponse['data']>([])
+const topN = ref(10)
+
+const totalStats = computed(() => {
+  const totalWater = customerSummaryData.value.reduce((sum, item) => sum + item.totalWater, 0)
+  const totalAmount = customerSummaryData.value.reduce((sum, item) => sum + item.totalAmount, 0)
+  const customerCount = customerSummaryData.value.length
+  const avgWaterPerCustomer = customerCount > 0 ? totalWater / customerCount : 0
+
+  return {
+    totalWater: totalWater.toFixed(2),
+    totalAmount: totalAmount.toFixed(2),
+    customerCount,
+    avgWaterPerCustomer: avgWaterPerCustomer.toFixed(2)
+  }
+})
+
+const getCustomerSummary = async () => {
+  try {
+    const res = await clientGet<null, CustomerSummaryResponse>('/park/waterUsageData/customer-summary')
+    if(res.code !== 200){
+      ElMessage.error(res.msg)
+      return
+    }
+    customerSummaryData.value = res.data
+  } catch (error) {
+    ElMessage.error('获取客户统计数据失败')
+  }
+}
+
+const getMonthSummary = async () => {
+  try {
+    const res = await clientGet<null, MonthSummaryResponse>('/park/waterUsageData/month-summary')
+    if(res.code !== 200){
+      ElMessage.error(res.msg)
+      return
+    }
+    monthSummaryData.value = res.data
+    nextTick(() => {
+      renderMonthTrendChart()
+    })
+  } catch (error) {
+    ElMessage.error('获取月度统计数据失败')
+  }
+}
+
+const getTopCustomers = async (topNum: number) => {
+  try {
+    const res = await clientGet<null, TopCustomersResponse>('/park/waterUsageData/top-customers',{
+      params: {
+        topN: topNum
+      }
+    })
+    if(res.code !== 200){
+      ElMessage.error(res.msg)
+      return
+    }
+    topCustomersData.value = res.data
+    nextTick(() => {
+      renderTopCustomersChart()
+    })
+  } catch (error) {
+    ElMessage.error('获取TOP客户数据失败')
+  }
+}
+
+const renderMonthTrendChart = () => {
+  const chartDom = document.getElementById('monthTrendChart')
+  if (!chartDom || monthSummaryData.value.length === 0) return
+
+  const myChart = echarts.init(chartDom)
+  const option = {
+    title: {
+      text: '月度用水趋势',
+      left: 'center',
+      textStyle: {
+        color: '#2c3e50',
+        fontSize: 16,
+        fontWeight: 'bold'
+      }
+    },
+    tooltip: {
+      trigger: 'axis',
+      axisPointer: {
+        type: 'cross'
+      }
+    },
+    legend: {
+      data: ['用水量(吨)', '金额(元)'],
+      top: 30
+    },
+    grid: {
+      left: '3%',
+      right: '4%',
+      bottom: '3%',
+      containLabel: true
+    },
+    xAxis: {
+      type: 'category',
+      data: monthSummaryData.value.map(item => item.statTime),
+      axisLabel: {
+        rotate: 45
+      }
+    },
+    yAxis: [
+      {
+        type: 'value',
+        name: '用水量(吨)',
+        position: 'left',
+        axisLabel: {
+          formatter: '{value} 吨'
+        }
+      },
+      {
+        type: 'value',
+        name: '金额(元)',
+        position: 'right',
+        axisLabel: {
+          formatter: '{value} 元'
+        }
+      }
+    ],
+    series: [
+      {
+        name: '用水量(吨)',
+        type: 'line',
+        data: monthSummaryData.value.map(item => item.totalWater),
+        smooth: true,
+        itemStyle: {
+          color: '#3b82f6'
+        },
+        areaStyle: {
+          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+            { offset: 0, color: 'rgba(59, 130, 246, 0.3)' },
+            { offset: 1, color: 'rgba(59, 130, 246, 0.1)' }
+          ])
+        }
+      },
+      {
+        name: '金额(元)',
+        type: 'line',
+        yAxisIndex: 1,
+        data: monthSummaryData.value.map(item => item.totalAmount),
+        smooth: true,
+        itemStyle: {
+          color: '#10b981'
+        },
+        areaStyle: {
+          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+            { offset: 0, color: 'rgba(16, 185, 129, 0.3)' },
+            { offset: 1, color: 'rgba(16, 185, 129, 0.1)' }
+          ])
+        }
+      }
+    ]
+  }
+  myChart.setOption(option)
+}
+
+const renderTopCustomersChart = () => {
+  const chartDom = document.getElementById('topCustomersChart')
+  if (!chartDom || topCustomersData.value.length === 0) return
+
+  const myChart = echarts.init(chartDom)
+  const option = {
+    title: {
+      text: `TOP ${topN.value} 用水客户`,
+      left: 'center',
+      textStyle: {
+        color: '#2c3e50',
+        fontSize: 16,
+        fontWeight: 'bold'
+      }
+    },
+    tooltip: {
+      trigger: 'axis',
+      axisPointer: {
+        type: 'shadow'
+      },
+      formatter: function(params: any) {
+        const data = params[0]
+        const rank = topCustomersData.value.findIndex(item => item.customerName === data.name) + 1
+        return `排名: 第${rank}名<br/>${data.name}<br/>用水量: ${data.value} 吨`
+      }
+    },
+    grid: {
+      left: '0%',
+      right: '10%',
+      bottom: '5%',
+      top: '15%',
+      containLabel: true
+    },
+    xAxis: {
+      type: 'value',
+      name: '用水量(吨)',
+      nameLocation: 'middle',
+      nameGap: 30,
+      axisLabel: {
+        formatter: '{value} 吨'
+      }
+    },
+    yAxis: {
+      type: 'category',
+      data: topCustomersData.value.map((item, index) => `${index + 1}. ${item.customerName}`),
+      axisLabel: {
+        interval: 0,
+        formatter: function(value: string) {
+          // 如果公司名太长,进行截断
+          const parts = value.split('. ')
+          const rank = parts[0]
+          const name = parts[1]
+          if (name && name.length > 12) {
+            return `${rank}. ${name.substring(0, 12)}...`
+          }
+          return value
+        }
+      },
+      inverse: true // 让排名第一的显示在最上面
+    },
+    series: [
+      {
+        name: '用水量',
+        type: 'bar',
+        data: topCustomersData.value.map(item => item.totalWater),
+        itemStyle: {
+          color: function(params: any) {
+            // 根据排名设置不同的颜色
+            const colors = [
+              '#FFD700', // 金色 - 第1名
+              '#C0C0C0', // 银色 - 第2名
+              '#CD7F32', // 铜色 - 第3名
+              '#f59e0b', // 橙色 - 其他
+            ]
+            return params.dataIndex < 3 ? colors[params.dataIndex] : colors[3]
+          }
+        },
+        emphasis: {
+          itemStyle: {
+            color: function(params: any) {
+              const colors = [
+                '#FFE55C', // 亮金色
+                '#D4D4D4', // 亮银色
+                '#DEB887', // 亮铜色
+                '#fbbf24', // 亮橙色
+              ]
+              return params.dataIndex < 3 ? colors[params.dataIndex] : colors[3]
+            }
+          }
+        },
+        label: {
+          show: true,
+          position: 'right',
+          formatter: function(params: any) {
+            return `${params.value} 吨`
+          },
+          color: '#666',
+          fontSize: 12
+        },
+        barWidth: '60%'
+      }
+    ]
+  }
+  myChart.setOption(option)
+}
+
+const refreshTopCustomers = async () => {
+  await getTopCustomers(topN.value)
+}
+
+onMounted(async () => {
+  loading.value = true
+  try {
+    await Promise.all([
+      getCustomerSummary(),
+      getMonthSummary(),
+      getTopCustomers(topN.value)
+    ])
+  } finally {
+    loading.value = false
+  }
+})
+</script>
+
+<template>
+  <div class="water-statistics-container p-6 bg-gray-50 min-h-screen">
+    <!-- 添加页面标题 -->
+    <div class="mb-6">
+      <h1 class="text-3xl font-bold text-gray-800 mb-2">用水统计仪表板</h1>
+      <p class="text-gray-600">实时监控园区用水情况,分析用水趋势</p>
+    </div>
+
+    <!-- 添加统计概览卡片 -->
+    <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
+      <el-card class="stat-card bg-gradient-to-br from-blue-500 to-blue-600 text-white!">
+        <div class="flex items-center justify-between">
+          <div>
+            <p class="text-blue-100 text-sm mb-1">总用水量</p>
+            <p class="text-2xl font-bold">{{ totalStats.totalWater }} 吨</p>
+          </div>
+          <Droplets class="w-12 h-12 text-blue-200" />
+        </div>
+      </el-card>
+
+      <el-card class="stat-card bg-gradient-to-br from-green-500 to-green-600 text-white!">
+        <div class="flex items-center justify-between">
+          <div>
+            <p class="text-green-100 text-sm mb-1">总金额</p>
+            <p class="text-2xl font-bold">¥{{ totalStats.totalAmount }}</p>
+          </div>
+          <DollarSign class="w-12 h-12 text-green-200" />
+        </div>
+      </el-card>
+
+      <el-card class="stat-card bg-gradient-to-br from-purple-500 to-purple-600 text-white!">
+        <div class="flex items-center justify-between">
+          <div>
+            <p class="text-purple-100 text-sm mb-1">客户数量</p>
+            <p class="text-2xl font-bold">{{ totalStats.customerCount }}</p>
+          </div>
+          <Users class="w-12 h-12 text-purple-200" />
+        </div>
+      </el-card>
+
+      <el-card class="stat-card bg-gradient-to-br from-orange-500 to-orange-600 text-white!">
+        <div class="flex items-center justify-between">
+          <div>
+            <p class="text-orange-100 text-sm mb-1">平均用水量</p>
+            <p class="text-2xl font-bold">{{ totalStats.avgWaterPerCustomer }} 吨</p>
+          </div>
+          <TrendingUp class="w-12 h-12 text-orange-200" />
+        </div>
+      </el-card>
+    </div>
+
+    <!-- 添加图表展示区域 -->
+    <div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8">
+      <!-- 月度趋势图表 -->
+      <el-card>
+        <template #header>
+          <div class="flex items-center">
+            <Calendar class="w-5 h-5 mr-2 text-blue-500" />
+            <span class="font-semibold">月度用水趋势</span>
+          </div>
+        </template>
+        <div id="monthTrendChart" style="width: 100%; height: 400px;"></div>
+      </el-card>
+
+      <!-- TOP客户图表 -->
+      <el-card>
+        <template #header>
+          <div class="flex items-center justify-between">
+            <div class="flex items-center">
+              <Trophy class="w-5 h-5 mr-2 text-yellow-500" />
+              <span class="font-semibold">TOP用水客户</span>
+            </div>
+            <div class="flex items-center space-x-2">
+              <span class="text-sm text-gray-600">显示前</span>
+              <el-select v-model="topN" @change="refreshTopCustomers" size="small" style="width: 80px;">
+                <el-option label="5" :value="5" />
+                <el-option label="10" :value="10" />
+                <el-option label="15" :value="15" />
+                <el-option label="20" :value="20" />
+              </el-select>
+              <span class="text-sm text-gray-600">名</span>
+            </div>
+          </div>
+        </template>
+        <div id="topCustomersChart" style="width: 100%; height: 400px;"></div>
+      </el-card>
+    </div>
+
+    <!-- 添加客户详细统计表格 -->
+    <el-card>
+      <template #header>
+        <div class="flex items-center">
+          <Users class="w-5 h-5 mr-2 text-indigo-500" />
+          <span class="font-semibold">客户用水详细统计</span>
+        </div>
+      </template>
+      <el-table
+        :data="customerSummaryData"
+        v-loading="loading"
+        stripe
+        style="width: 100%"
+        :default-sort="{ prop: 'totalWater', order: 'descending' }"
+      >
+        <el-table-column
+          prop="customerName"
+          label="客户名称"
+          min-width="200"
+          show-overflow-tooltip
+        />
+        <el-table-column
+          prop="totalWater"
+          label="总用水量(吨)"
+          min-width="150"
+          sortable
+        >
+          <template #default="{ row }">
+            <el-tag type="info" size="small">{{ row.totalWater.toFixed(2) }} 吨</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column
+          prop="totalAmount"
+          label="总金额(元)"
+          min-width="150"
+          sortable
+        >
+          <template #default="{ row }">
+            <el-tag type="success" size="small">¥{{ row.totalAmount.toFixed(2) }}</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column
+          label="单价(元/吨)"
+          min-width="120"
+          sortable
+        >
+          <template #default="{ row }">
+            <span class="text-gray-600">
+              ¥{{ row.totalWater > 0 ? (row.totalAmount / row.totalWater).toFixed(2) : '0.00' }}
+            </span>
+          </template>
+        </el-table-column>
+      </el-table>
+    </el-card>
+  </div>
+</template>
+
+<style scoped>
+.water-statistics-container {
+  font-family: 'Inter', sans-serif;
+}
+
+.stat-card {
+  border: none;
+  box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
+  transition: transform 0.2s ease-in-out;
+}
+
+.stat-card:hover {
+  transform: translateY(-2px);
+  box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
+}
+
+:deep(.el-card__header) {
+  background-color: #f8fafc;
+  border-bottom: 1px solid #e2e8f0;
+}
+
+:deep(.el-table th) {
+  background-color: #f1f5f9;
+  color: #475569;
+  font-weight: 600;
+}
+
+:deep(.el-table--striped .el-table__body tr.el-table__row--striped td) {
+  background-color: #f8fafc;
+}
+</style>

+ 264 - 33
src/views/zhys/ysxx.vue

@@ -3,7 +3,9 @@ import { clientDel, clientGet, clientPost, clientPut } from '@/utils/request.ts'
 import { onMounted, ref, reactive } from 'vue'
 import type { BaseResponse } from '@/utils/type.ts'
 import { ElMessage, ElMessageBox, ElUpload } from 'element-plus'
-import { Search, Plus, Edit, Delete, RefreshCw, Upload, Download } from 'lucide-vue-next'
+import { Search, Plus, Edit, Delete, RefreshCw, Upload, Download, TrendingUp, BarChart3 } from 'lucide-vue-next'
+import * as echarts from 'echarts'
+import { nextTick } from 'vue'
 
 interface WaterUsage {
   id: number;
@@ -15,8 +17,8 @@ interface WaterUsage {
   createTime: string;
 }
 
-interface WaterUsageReponse extends BaseResponse{
-  data:WaterUsage
+interface WaterUsageReponse extends BaseResponse {
+  data: WaterUsage
 }
 
 interface WaterUsageList extends BaseResponse {
@@ -37,6 +39,13 @@ const BASE_URL = import.meta.env.VITE_APP_BASE_API
 
 const uploadRef = ref() // 上传组件引用
 
+const trendDialogVisible = ref(false)
+const trendLoading = ref(false)
+const trendData = ref<{ statTime: string, totalWater: number, totalAmount: number }[]>([])
+const selectedCustomerCode = ref<number>(0)
+const selectedCustomerName = ref<string>('')
+const chartRef = ref<HTMLDivElement>()
+
 // 分页参数
 const pagination = reactive({
   pageNum: 1,
@@ -62,40 +71,40 @@ const formData = ref<Partial<WaterUsage>>({
 const getList = async () => {
   loading.value = true
   try {
-    const arr:{
-      value:string,
+    const arr: {
+      value: string,
       column: string,
-      type:string
+      type: string
     }[] = [];
-    if(searchForm.customerCode){
+    if (searchForm.customerCode) {
       arr.push({
         value: searchForm.customerCode,
         column: 'customer_code',
         type: 'like'
       })
     }
-    if(searchForm.customerName){
+    if (searchForm.customerName) {
       arr.push({
         value: searchForm.customerName,
         column: 'customer_name',
         type: 'like'
       })
     }
-    if(searchForm.statisticalTime){
+    if (searchForm.statisticalTime) {
       arr.push({
         value: searchForm.statisticalTime,
         column: 'statistical_time',
         type: 'like'
       })
     }
-    const res = await clientGet<{pageNum: number, pageSize: number, conditionJson: string},WaterUsageList>('/park/waterUsageData/findByPage',{
+    const res = await clientGet<{ pageNum: number, pageSize: number, conditionJson: string }, WaterUsageList>('/park/waterUsageData/findByPage', {
       params: {
         pageNum: pagination.pageNum,
         pageSize: pagination.pageSize,
         conditionJson: encodeURIComponent(JSON.stringify(arr))
       }
     })
-    if(res.code !== 200){
+    if (res.code !== 200) {
       ElMessage.error(res.msg)
       return
     }
@@ -135,7 +144,7 @@ const handleAdd = () => {
 }
 
 const getById = async (id: number) => {
-  const res = await clientGet<null,WaterUsageReponse>(`/park/waterUsageData/getById/${id}`)
+  const res = await clientGet<null, WaterUsageReponse>(`/park/waterUsageData/getById/${id}`)
   return res.data // res.data 就是 WaterUsage
 }
 
@@ -148,6 +157,7 @@ const handleEdit = async (row: WaterUsage) => {
     formData.value = { ...data }
     dialogVisible.value = true
   } catch (error) {
+    console.log(error)
     ElMessage.error('获取用水记录详情失败')
   } finally {
     loading.value = false
@@ -159,11 +169,11 @@ const handleSave = async () => {
   try {
     let res: BaseResponse
     if (isEdit.value) {
-      res = await clientPut<WaterUsage,BaseResponse>('/park/waterUsageData/updateById', formData.value as WaterUsage)
+      res = await clientPut<WaterUsage, BaseResponse>('/park/waterUsageData/updateById', formData.value as WaterUsage)
     } else {
-      res = await clientPost<WaterUsage,BaseResponse>('/park/waterUsageData/save', formData.value as WaterUsage)
+      res = await clientPost<WaterUsage, BaseResponse>('/park/waterUsageData/save', formData.value as WaterUsage)
     }
-    if(res.code !== 200){
+    if (res.code !== 200) {
       ElMessage.error(res.msg)
       return
     }
@@ -183,19 +193,19 @@ const handleDelete = async (row: WaterUsage) => {
       type: 'warning'
     })
 
-    const res = await clientDel<string[],BaseResponse>('/park/waterUsageData/deleteBatchById',{
-      params:{
+    const res = await clientDel<string[], BaseResponse>('/park/waterUsageData/deleteBatchById', {
+      params: {
         ids: [row.id]
       }
-    },'comma')
+    }, 'comma')
 
-    if(res.code !== 200){
+    if (res.code !== 200) {
       ElMessage.error(res.msg)
       return
     }
     ElMessage.success(res.msg)
     getList()
-  } catch {}
+  } catch { }
 }
 
 const handleBatchDelete = async () => {
@@ -209,19 +219,19 @@ const handleBatchDelete = async () => {
       cancelButtonText: '取消',
       type: 'warning'
     })
-    const res = await clientDel<string[],BaseResponse>('/park/waterUsageData/deleteBatchById',{
-      params:{
+    const res = await clientDel<string[], BaseResponse>('/park/waterUsageData/deleteBatchById', {
+      params: {
         ids: selectedIds.value
       }
-    },'comma')
-    if(res.code !== 200){
+    }, 'comma')
+    if (res.code !== 200) {
       ElMessage.error(res.msg)
       return
     }
     ElMessage.success(res.msg)
     selectedIds.value = []
     getList()
-  } catch {}
+  } catch { }
 }
 
 const handleSelectionChange = (selection: WaterUsage[]) => {
@@ -239,6 +249,138 @@ const handleSizeChange = (size: number) => {
   getList()
 }
 
+const customerTrendByCode = async (customerCode: number) => {
+  trendLoading.value = true
+  try {
+    const res = await clientGet<{ customerCode: number }, TrendResponse>('/park/waterUsageData/customer-trend/' + customerCode)
+    if (res.code !== 200) {
+      ElMessage.error(res.msg)
+      return
+    }
+    trendData.value = res.data
+    await nextTick()
+    renderChart()
+  } catch (error) {
+    console.log(error)
+    ElMessage.error('获取客户用水趋势失败')
+  } finally {
+    trendLoading.value = false
+  }
+}
+
+const renderChart = () => {
+  if (!chartRef.value || trendData.value.length === 0) return
+
+  const chart = echarts.init(chartRef.value)
+
+  const option = {
+    title: {
+      text: `${selectedCustomerName.value} 用水趋势分析`,
+      left: 'center',
+      textStyle: {
+        fontSize: 16,
+        fontWeight: 'bold'
+      }
+    },
+    tooltip: {
+      trigger: 'axis',
+      axisPointer: {
+        type: 'cross'
+      },
+      formatter: (params: any) => {
+        let result = `${params[0].axisValue}<br/>`
+        params.forEach((param: any) => {
+          const unit = param.seriesName === '用水量' ? '吨' : '元'
+          result += `${param.marker}${param.seriesName}: ${param.value}${unit}<br/>`
+        })
+        return result
+      }
+    },
+    legend: {
+      data: ['用水量', '金额'],
+      top: 30
+    },
+    grid: {
+      left: '3%',
+      right: '4%',
+      bottom: '3%',
+      top: '15%',
+      containLabel: true
+    },
+    xAxis: {
+      type: 'category',
+      boundaryGap: false,
+      data: trendData.value.map(item => item.statTime),
+      axisLabel: {
+        rotate: 45
+      }
+    },
+    yAxis: [
+      {
+        type: 'value',
+        name: '用水量(吨)',
+        position: 'left',
+        axisLabel: {
+          formatter: '{value} 吨'
+        }
+      },
+      {
+        type: 'value',
+        name: '金额(元)',
+        position: 'right',
+        axisLabel: {
+          formatter: '{value} 元'
+        }
+      }
+    ],
+    series: [
+      {
+        name: '用水量',
+        type: 'line',
+        yAxisIndex: 0,
+        data: trendData.value.map(item => item.totalWater),
+        smooth: true,
+        symbol: 'circle',
+        symbolSize: 6,
+        lineStyle: {
+          width: 3
+        },
+        areaStyle: {
+          opacity: 0.3
+        },
+        itemStyle: {
+          color: '#409EFF'
+        }
+      },
+      {
+        name: '金额',
+        type: 'line',
+        yAxisIndex: 1,
+        data: trendData.value.map(item => item.totalAmount),
+        smooth: true,
+        symbol: 'circle',
+        symbolSize: 6,
+        lineStyle: {
+          width: 3
+        },
+        areaStyle: {
+          opacity: 0.3
+        },
+        itemStyle: {
+          color: '#67C23A'
+        }
+      }
+    ]
+  }
+
+  chart.setOption(option)
+
+  // 响应式调整
+  window.addEventListener('resize', () => {
+    chart.resize()
+  })
+}
+
 /** ===== Excel 相关接口 ===== **/
 
 // 导出 Excel
@@ -249,7 +391,7 @@ const handleExport = async () => {
       params: { conditionJson },
       responseType: 'blob'
     })
-    const blob = new Blob([res as any], { type: 'application/vnd.ms-excel' })
+    const blob = new Blob([res as Blob], { type: 'application/vnd.ms-excel' })
     const url = window.URL.createObjectURL(blob)
     const a = document.createElement('a')
     a.href = url
@@ -257,6 +399,7 @@ const handleExport = async () => {
     a.click()
     window.URL.revokeObjectURL(url)
   } catch (e) {
+    console.log(e)
     ElMessage.error('导出失败')
   }
 }
@@ -267,7 +410,7 @@ const handleDownloadTemplate = async () => {
     const res = await clientGet<null, Blob>('/park/waterUsageData/downloadTemplateWord', {
       responseType: 'blob'
     })
-    const blob = new Blob([res as any], { type: 'application/vnd.ms-excel' })
+    const blob = new Blob([res as Blob], { type: 'application/vnd.ms-excel' })
     const url = window.URL.createObjectURL(blob)
     const a = document.createElement('a')
     a.href = url
@@ -275,12 +418,13 @@ const handleDownloadTemplate = async () => {
     a.click()
     window.URL.revokeObjectURL(url)
   } catch (e) {
+    console.log(e)
     ElMessage.error('下载模板失败')
   }
 }
 
 // 导入 Excel
-const handleImportSuccess = (res: any) => {
+const handleImportSuccess = (res: BaseResponse) => {
   if (res.code === 200) {
     ElMessage.success('导入成功')
     getList()
@@ -289,7 +433,22 @@ const handleImportSuccess = (res: any) => {
   }
 }
 
-onMounted(()=>{
+const handleViewTrend = (row: WaterUsage) => {
+  selectedCustomerCode.value = row.customerCode
+  selectedCustomerName.value = row.customerName
+  trendDialogVisible.value = true
+  customerTrendByCode(row.customerCode)
+}
+
+interface TrendResponse extends BaseResponse {
+  data: {
+    statTime: string,
+    totalWater: number,
+    totalAmount: number
+  }[]
+}
+
+onMounted(() => {
   getList();
 })
 </script>
@@ -305,10 +464,10 @@ onMounted(()=>{
     <!-- 搜索区域 -->
     <el-card class="mb-4" shadow="never">
       <el-form :model="searchForm" inline class="search-form">
-        <el-form-item label="公司名称">
+        <el-form-item label="客户代码">
           <el-input v-model="searchForm.customerCode" placeholder="请输入客户代码" clearable style="width: 200px" />
         </el-form-item>
-        <el-form-item label="用水记录名称">
+        <el-form-item label="客户名称">
           <el-input v-model="searchForm.customerName" placeholder="请输入客户名称" clearable style="width: 200px" />
         </el-form-item>
         <el-form-item label="统计时间">
@@ -330,7 +489,7 @@ onMounted(()=>{
           <el-button type="success" @click="handleExport" :icon="Download">导出Excel</el-button>
           <el-upload
             ref="uploadRef"
-            :action="BASE_URL+'/park/waterUsageData/importExcel'"
+            :action="BASE_URL + '/park/waterUsageData/importExcel'"
             :show-file-list="false"
             accept=".xls,.xlsx"
             :on-success="handleImportSuccess"
@@ -351,10 +510,12 @@ onMounted(()=>{
         <el-table-column prop="statisticalTime" label="统计时间" min-width="120" />
         <el-table-column prop="amount" label="金额" width="100" />
         <el-table-column prop="createTime" label="创建时间" width="180" />
-        <el-table-column label="操作" width="150" fixed="right">
+        <!-- 修改操作列,添加趋势分析按钮 -->
+        <el-table-column label="操作" width="200" fixed="right">
           <template #default="{ row }">
             <el-button type="primary" size="small" @click="handleEdit(row)" :icon="Edit" link>编辑</el-button>
             <el-button type="danger" size="small" @click="handleDelete(row)" :icon="Delete" link>删除</el-button>
+            <el-button type="success" size="small" @click="handleViewTrend(row)" :icon="TrendingUp" link>趋势</el-button>
           </template>
         </el-table-column>
       </el-table>
@@ -415,6 +576,64 @@ onMounted(()=>{
         </div>
       </template>
     </el-dialog>
+
+    <!-- 添加趋势分析对话框 -->
+    <el-dialog
+      v-model="trendDialogVisible"
+      title="用水趋势分析"
+      fullscreen
+      :close-on-click-modal="false"
+      top="5vh"
+    >
+      <div v-loading="trendLoading" class="trend-content">
+        <div class="mb-4 p-4 bg-blue-50 rounded-lg">
+          <div class="flex items-center gap-2 mb-2">
+            <BarChart3 class="w-5 h-5 text-blue-600" />
+            <span class="font-semibold text-blue-800">客户信息</span>
+          </div>
+          <div class="text-sm text-gray-600">
+            <span class="mr-4">客户代码: <strong>{{ selectedCustomerCode }}</strong></span>
+            <span>客户名称: <strong>{{ selectedCustomerName }}</strong></span>
+          </div>
+        </div>
+
+        <div v-if="trendData.length > 0" class="chart-container">
+          <div ref="chartRef" class="w-full h-96"></div>
+
+          <!-- 数据统计卡片 -->
+          <div class="mt-6 grid grid-cols-1 md:grid-cols-3 gap-4">
+            <div class="bg-gradient-to-r from-blue-500 to-blue-600 text-white p-4 rounded-lg">
+              <div class="text-sm opacity-90">总用水量</div>
+              <div class="text-2xl font-bold">
+                {{ trendData.reduce((sum, item) => sum + item.totalWater, 0).toFixed(2) }} 吨
+              </div>
+            </div>
+            <div class="bg-gradient-to-r from-green-500 to-green-600 text-white p-4 rounded-lg">
+              <div class="text-sm opacity-90">总金额</div>
+              <div class="text-2xl font-bold">
+                ¥{{ trendData.reduce((sum, item) => sum + item.totalAmount, 0).toFixed(2) }}
+              </div>
+            </div>
+            <div class="bg-gradient-to-r from-purple-500 to-purple-600 text-white p-4 rounded-lg">
+              <div class="text-sm opacity-90">平均用水量</div>
+              <div class="text-2xl font-bold">
+                {{ (trendData.reduce((sum, item) => sum + item.totalWater, 0) / trendData.length).toFixed(2) }} 吨
+              </div>
+            </div>
+          </div>
+        </div>
+
+        <div v-else-if="!trendLoading" class="text-center py-12">
+          <div class="text-gray-400 text-lg">暂无趋势数据</div>
+        </div>
+      </div>
+
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="trendDialogVisible = false">关闭</el-button>
+        </div>
+      </template>
+    </el-dialog>
   </div>
 </template>
 
@@ -425,4 +644,16 @@ onMounted(()=>{
 .dialog-footer {
   text-align: right;
 }
+
+/* 添加趋势分析相关样式 */
+.trend-content {
+  min-height: 400px;
+}
+
+.chart-container {
+  background: white;
+  border-radius: 8px;
+  padding: 16px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+}
 </style>