Browse Source

页面修改 页面修复 增加企业信息管理 美化界面 修复维修工单

bzd_lxf 1 month ago
parent
commit
c57ef03a2b
34 changed files with 6756 additions and 675 deletions
  1. 101 0
      pm-admin/src/main/java/com/pm/web/controller/enterpriseInfo/EnterpriseController.java
  2. 1 1
      pm-admin/src/main/java/com/pm/web/controller/repairOrder/RepairOrderController.java
  3. 6 2
      pm-admin/src/main/java/com/pm/web/controller/system/SysProfileController.java
  4. 24 0
      pm-system/src/main/java/com/pm/enterpriseInfo/mapper/EnterpriseMapper.java
  5. 22 0
      pm-system/src/main/java/com/pm/enterpriseInfo/service/IEnterpriseService.java
  6. 68 0
      pm-system/src/main/java/com/pm/enterpriseInfo/service/impl/EnterpriseServiceImpl.java
  7. 128 0
      pm-system/src/main/resources/mapper/enterpriseInfo/EnterpriseBranchMapper.xml
  8. 124 0
      pm-system/src/main/resources/mapper/enterpriseInfo/EnterpriseCertificateMapper.xml
  9. 146 0
      pm-system/src/main/resources/mapper/enterpriseInfo/EnterpriseInfoMapper.xml
  10. BIN
      pm_ui/public/favicon.ico
  11. 52 0
      pm_ui/src/api/enterpriseInfo/enterprise.js
  12. 2 1
      pm_ui/src/layout/components/Navbar.vue
  13. 86 12
      pm_ui/src/layout/index.vue
  14. 8 1
      pm_ui/src/views/buildingEquipmentMonitoring/index2.vue
  15. 3 3
      pm_ui/src/views/buildingEquipmentMonitoring/index3.vue
  16. 3 3
      pm_ui/src/views/buildingEquipmentMonitoring/index4.vue
  17. 16 16
      pm_ui/src/views/dtxt/index.vue
  18. 1123 100
      pm_ui/src/views/energyManagement/dataComparison/index.vue
  19. 1098 89
      pm_ui/src/views/energyManagement/layerManage/index.vue
  20. 891 86
      pm_ui/src/views/energyManagement/layerManage/index2.vue
  21. 742 63
      pm_ui/src/views/energyManagement/quotaManagement/index.vue
  22. 1207 0
      pm_ui/src/views/enterpriseInfo/enterprise.vue
  23. 3 3
      pm_ui/src/views/fkxt/VisitorSystem.vue
  24. 23 23
      pm_ui/src/views/index.vue
  25. 19 19
      pm_ui/src/views/nygl/index.vue
  26. 4 4
      pm_ui/src/views/parking/index.vue
  27. 6 6
      pm_ui/src/views/patrol/index.vue
  28. 22 22
      pm_ui/src/views/publicBroadcasting/index5.vue
  29. 285 112
      pm_ui/src/views/repairOrder/repairOrder/index.vue
  30. 467 89
      pm_ui/src/views/repairOrder/repairOrder/index2.vue
  31. 8 8
      pm_ui/src/views/rqbjxt/index.vue
  32. 4 4
      pm_ui/src/views/spafjkxt/index.vue
  33. 8 8
      pm_ui/src/views/zmxt/index.vue
  34. 56 0
      sql/2025613企业信息.sql

+ 101 - 0
pm-admin/src/main/java/com/pm/web/controller/enterpriseInfo/EnterpriseController.java

@@ -0,0 +1,101 @@
+package com.pm.web.controller.enterpriseInfo;
+
+import com.pm.common.annotation.Log;
+import com.pm.common.config.PageData;
+import com.pm.common.core.controller.BaseController;
+import com.pm.common.core.domain.AjaxResult;
+import com.pm.common.core.page.TableDataInfo;
+import com.pm.common.enums.BusinessType;
+import com.pm.common.utils.StringUtils;
+import com.pm.common.utils.poi.ExcelUtil;
+import com.pm.enterpriseInfo.service.IEnterpriseService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletResponse;
+import java.util.List;
+
+@RestController
+@RequestMapping("/system/enterprise")
+public class EnterpriseController extends BaseController {
+
+    @Autowired
+    private IEnterpriseService enterpriseService;
+
+    /**
+     * 查询企业信息列表
+     */
+    @PreAuthorize("@ss.hasPermi('system:enterprise:list')")
+    @GetMapping("/list")
+    public TableDataInfo list() {
+        PageData pd = this.getPageData();
+        startPage();
+        List<PageData> list = enterpriseService.selectEnterpriseList(pd);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出企业信息列表
+     */
+    @PreAuthorize("@ss.hasPermi('system:enterprise:export')")
+    @Log(title = "企业信息", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(HttpServletResponse response) {
+        PageData pd = this.getPageData();
+        List<PageData> list = enterpriseService.selectEnterpriseList(pd);
+        ExcelUtil<PageData> util = new ExcelUtil<PageData>(PageData.class);
+        util.exportExcel(response, list, "企业信息数据");
+    }
+
+    /**
+     * 获取企业信息详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('system:enterprise:query')")
+    @GetMapping(value = "/{enterpriseId}")
+    public AjaxResult getInfo(@PathVariable("enterpriseId") Long enterpriseId) {
+        return AjaxResult.success(enterpriseService.selectEnterpriseByEnterpriseId(enterpriseId));
+    }
+
+    /**
+     * 新增企业信息
+     */
+    @PreAuthorize("@ss.hasPermi('system:enterprise:add')")
+    @Log(title = "企业信息", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@Validated @RequestBody PageData pd) {
+        pd.put("createBy", getUsername());
+        return toAjax(enterpriseService.insertEnterprise(pd));
+    }
+
+    /**
+     * 修改企业信息
+     */
+    @PreAuthorize("@ss.hasPermi('system:enterprise:edit')")
+    @Log(title = "企业信息", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@Validated @RequestBody PageData pd) {
+        pd.put("updateBy", getUsername());
+        return toAjax(enterpriseService.updateEnterprise(pd));
+    }
+
+    /**
+     * 删除企业信息
+     */
+    @PreAuthorize("@ss.hasPermi('system:enterprise:remove')")
+    @Log(title = "企业信息", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{enterpriseIds}")
+    public AjaxResult remove(@PathVariable Long[] enterpriseIds) {
+        return toAjax(enterpriseService.deleteEnterpriseByEnterpriseIds(enterpriseIds));
+    }
+    /**
+     * 获取企业统计数据
+     */
+    @PreAuthorize("@ss.hasPermi('system:enterprise:list')")
+    @GetMapping("/statistics")
+    public AjaxResult getStatistics() {
+        PageData statistics = enterpriseService.getEnterpriseStatistics();
+        return success(statistics);
+    }
+}

+ 1 - 1
pm-admin/src/main/java/com/pm/web/controller/repairOrder/RepairOrderController.java

@@ -49,7 +49,7 @@ public class RepairOrderController extends BaseController
     public TableDataInfo list()
     {
         PageData pd = this.getPageData();
-        startPage();
+        //startPage();
         List<PageData> list = repairOrderService.selectRepairOrderList(pd);
         return getDataTable(list);
     }

+ 6 - 2
pm-admin/src/main/java/com/pm/web/controller/system/SysProfileController.java

@@ -23,6 +23,8 @@ import com.pm.common.utils.file.MimeTypeUtils;
 import com.pm.framework.web.service.TokenService;
 import com.pm.system.service.ISysUserService;
 
+import java.util.Map;
+
 /**
  * 个人信息 业务处理
  *
@@ -87,12 +89,14 @@ public class SysProfileController extends BaseController
      */
     @Log(title = "个人信息", businessType = BusinessType.UPDATE)
     @PutMapping("/updatePwd")
-    public AjaxResult updatePwd(String oldPassword, String newPassword)
+    public AjaxResult updatePwd(@RequestBody Map<String, String> params)
     {
+        String oldPassword = params.get("oldPassword");
+        String newPassword = params.get("newPassword");
         LoginUser loginUser = getLoginUser();
         String userName = loginUser.getUsername();
         String password = loginUser.getPassword();
-        if (!SecurityUtils.matchesPassword(oldPassword, password))
+         if (!SecurityUtils.matchesPassword(oldPassword, password))
         {
             return error("修改密码失败,旧密码错误");
         }

+ 24 - 0
pm-system/src/main/java/com/pm/enterpriseInfo/mapper/EnterpriseMapper.java

@@ -0,0 +1,24 @@
+package com.pm.enterpriseInfo.mapper;
+
+import com.pm.common.config.PageData;
+
+import java.util.List;
+
+public interface EnterpriseMapper {
+
+    public List<PageData> selectEnterpriseList(PageData pd);
+
+    public PageData selectEnterpriseByEnterpriseId(Long enterpriseId);
+
+    public int insertEnterprise(PageData pd);
+
+    public int updateEnterprise(PageData pd);
+
+    public int deleteEnterpriseByEnterpriseId(Long enterpriseId);
+
+    public int deleteEnterpriseByEnterpriseIds(Long[] enterpriseIds);
+
+    public PageData selectEnterpriseByCreditCode(String creditCode);
+
+    public PageData selectEnterpriseCount();
+}

+ 22 - 0
pm-system/src/main/java/com/pm/enterpriseInfo/service/IEnterpriseService.java

@@ -0,0 +1,22 @@
+package com.pm.enterpriseInfo.service;
+
+import com.pm.common.config.PageData;
+
+import java.util.List;
+
+public interface IEnterpriseService {
+
+    public List<PageData> selectEnterpriseList(PageData pd);
+
+    public PageData selectEnterpriseByEnterpriseId(Long enterpriseId);
+
+    public int insertEnterprise(PageData pd);
+
+    public int updateEnterprise(PageData pd);
+
+    public int deleteEnterpriseByEnterpriseIds(Long[] enterpriseIds);
+
+    public int deleteEnterpriseByEnterpriseId(Long enterpriseId);
+
+    PageData getEnterpriseStatistics();
+}

+ 68 - 0
pm-system/src/main/java/com/pm/enterpriseInfo/service/impl/EnterpriseServiceImpl.java

@@ -0,0 +1,68 @@
+package com.pm.enterpriseInfo.service.impl;
+
+import com.pm.common.config.PageData;
+import com.pm.common.constant.UserConstants;
+import com.pm.common.utils.DateUtils;
+import com.pm.common.utils.StringUtils;
+import com.pm.enterpriseInfo.mapper.EnterpriseMapper;
+import com.pm.enterpriseInfo.service.IEnterpriseService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+@Service
+public class EnterpriseServiceImpl implements IEnterpriseService {
+
+    @Autowired
+    private EnterpriseMapper enterpriseMapper;
+
+    @Override
+    public List<PageData> selectEnterpriseList(PageData pd) {
+        return enterpriseMapper.selectEnterpriseList(pd);
+    }
+
+    @Override
+    public PageData selectEnterpriseByEnterpriseId(Long enterpriseId) {
+        return enterpriseMapper.selectEnterpriseByEnterpriseId(enterpriseId);
+    }
+
+    @Override
+    public int insertEnterprise(PageData pd) {
+        pd.put("createTime", DateUtils.getNowDate());
+        // 根据信用代码查询企业信息
+        PageData info = enterpriseMapper.selectEnterpriseByCreditCode(pd.getString("creditCode"));
+
+        if (info != null) {
+            throw new RuntimeException("修改企业'" + pd.getString("enterpriseName") + "'失败,统一社会信用代码已存在");
+        }
+        return enterpriseMapper.insertEnterprise(pd);
+    }
+
+    @Override
+    public int updateEnterprise(PageData pd) {
+        pd.put("updateTime", DateUtils.getNowDate());
+        // 根据信用代码查询企业信息
+        PageData info = enterpriseMapper.selectEnterpriseByCreditCode(pd.getString("creditCode"));
+        if (info != null && !info.getString("enterpriseId").equals(pd.getString("enterpriseId"))) {
+            throw new RuntimeException("修改企业'" + pd.getString("enterpriseName") + "'失败,统一社会信用代码已存在");
+        }
+        return enterpriseMapper.updateEnterprise(pd);
+    }
+
+    @Override
+    public int deleteEnterpriseByEnterpriseIds(Long[] enterpriseIds) {
+        return enterpriseMapper.deleteEnterpriseByEnterpriseIds(enterpriseIds);
+    }
+
+    @Override
+    public int deleteEnterpriseByEnterpriseId(Long enterpriseId) {
+        return enterpriseMapper.deleteEnterpriseByEnterpriseId(enterpriseId);
+    }
+
+    @Override
+    public PageData getEnterpriseStatistics() {
+        return enterpriseMapper.selectEnterpriseCount();
+    }
+
+}

+ 128 - 0
pm-system/src/main/resources/mapper/enterpriseInfo/EnterpriseBranchMapper.xml

@@ -0,0 +1,128 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.pm.enterpriseInfo.mapper.EnterpriseBranchMapper">
+
+    <resultMap type="pd" id="EnterpriseBranchResult">
+        <result property="branchId"            column="branch_id"            />
+        <result property="enterpriseId"        column="enterprise_id"        />
+        <result property="branchName"          column="branch_name"          />
+        <result property="branchCode"          column="branch_code"          />
+        <result property="branchType"          column="branch_type"          />
+        <result property="address"             column="address"              />
+        <result property="contactPerson"       column="contact_person"       />
+        <result property="contactPhone"        column="contact_phone"        />
+        <result property="longitude"           column="longitude"            />
+        <result property="latitude"            column="latitude"             />
+        <result property="status"              column="status"               />
+        <result property="createBy"            column="create_by"            />
+        <result property="createTime"          column="create_time"          />
+        <result property="updateBy"            column="update_by"            />
+        <result property="updateTime"          column="update_time"          />
+    </resultMap>
+
+    <sql id="selectEnterpriseBranchVo">
+        select branch_id, enterprise_id, branch_name, branch_code, branch_type, address,
+               contact_person, contact_phone, longitude, latitude, status, create_by,
+               create_time, update_by, update_time
+        from sys_enterprise_branch
+    </sql>
+
+    <select id="selectEnterpriseBranchList" parameterType="pd" resultMap="EnterpriseBranchResult">
+        <include refid="selectEnterpriseBranchVo"/>
+        <where>
+            <if test="enterpriseId != null ">
+                and enterprise_id = #{enterpriseId}
+            </if>
+            <if test="branchName != null  and branchName != ''">
+                and branch_name like concat('%', #{branchName}, '%')
+            </if>
+            <if test="branchCode != null  and branchCode != ''">
+                and branch_code = #{branchCode}
+            </if>
+            <if test="branchType != null  and branchType != ''">
+                and branch_type = #{branchType}
+            </if>
+            <if test="status != null  and status != ''">
+                and status = #{status}
+            </if>
+        </where>
+        order by create_time desc
+    </select>
+
+    <select id="selectEnterpriseBranchByBranchId" parameterType="Long" resultMap="EnterpriseBranchResult">
+        <include refid="selectEnterpriseBranchVo"/>
+        where branch_id = #{branchId}
+    </select>
+
+    <select id="selectBranchesByEnterpriseId" parameterType="Long" resultMap="EnterpriseBranchResult">
+        <include refid="selectEnterpriseBranchVo"/>
+        where enterprise_id = #{enterpriseId}
+        order by create_time desc
+    </select>
+
+    <insert id="insertEnterpriseBranch" parameterType="pd" useGeneratedKeys="true" keyProperty="branchId">
+        insert into sys_enterprise_branch
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="enterpriseId != null">enterprise_id,</if>
+            <if test="branchName != null and branchName != ''">branch_name,</if>
+            <if test="branchCode != null and branchCode != ''">branch_code,</if>
+            <if test="branchType != null">branch_type,</if>
+            <if test="address != null">address,</if>
+            <if test="contactPerson != null">contact_person,</if>
+            <if test="contactPhone != null">contact_phone,</if>
+            <if test="longitude != null">longitude,</if>
+            <if test="latitude != null">latitude,</if>
+            <if test="status != null">status,</if>
+            <if test="createBy != null">create_by,</if>
+            create_time,
+            <if test="updateBy != null">update_by,</if>
+        </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="enterpriseId != null">#{enterpriseId},</if>
+            <if test="branchName != null and branchName != ''">#{branchName},</if>
+            <if test="branchCode != null and branchCode != ''">#{branchCode},</if>
+            <if test="branchType != null">#{branchType},</if>
+            <if test="address != null">#{address},</if>
+            <if test="contactPerson != null">#{contactPerson},</if>
+            <if test="contactPhone != null">#{contactPhone},</if>
+            <if test="longitude != null">#{longitude},</if>
+            <if test="latitude != null">#{latitude},</if>
+            <if test="status != null">#{status},</if>
+            <if test="createBy != null">#{createBy},</if>
+            sysdate(),
+            <if test="updateBy != null">#{updateBy},</if>
+        </trim>
+    </insert>
+
+    <update id="updateEnterpriseBranch" parameterType="pd">
+        update sys_enterprise_branch
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="enterpriseId != null">enterprise_id = #{enterpriseId},</if>
+            <if test="branchName != null and branchName != ''">branch_name = #{branchName},</if>
+            <if test="branchCode != null and branchCode != ''">branch_code = #{branchCode},</if>
+            <if test="branchType != null">branch_type = #{branchType},</if>
+            <if test="address != null">address = #{address},</if>
+            <if test="contactPerson != null">contact_person = #{contactPerson},</if>
+            <if test="contactPhone != null">contact_phone = #{contactPhone},</if>
+            <if test="longitude != null">longitude = #{longitude},</if>
+            <if test="latitude != null">latitude = #{latitude},</if>
+            <if test="status != null">status = #{status},</if>
+            <if test="updateBy != null">update_by = #{updateBy},</if>
+            update_time = sysdate(),
+        </trim>
+        where branch_id = #{branchId}
+    </update>
+
+    <delete id="deleteEnterpriseBranchByBranchId" parameterType="Long">
+        delete from sys_enterprise_branch where branch_id = #{branchId}
+    </delete>
+
+    <delete id="deleteEnterpriseBranchByBranchIds" parameterType="String">
+        delete from sys_enterprise_branch where branch_id in
+        <foreach item="branchId" collection="array" open="(" separator="," close=")">
+            #{branchId}
+        </foreach>
+    </delete>
+</mapper>

+ 124 - 0
pm-system/src/main/resources/mapper/enterpriseInfo/EnterpriseCertificateMapper.xml

@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.pm.enterpriseInfo.mapper.EnterpriseCertificateMapper">
+
+    <resultMap type="pd" id="EnterpriseCertificateResult">
+        <result property="certificateId"       column="certificate_id"       />
+        <result property="enterpriseId"        column="enterprise_id"        />
+        <result property="certificateName"     column="certificate_name"     />
+        <result property="certificateType"     column="certificate_type"     />
+        <result property="certificateNumber"   column="certificate_number"   />
+        <result property="issueDate"           column="issue_date"           />
+        <result property="expireDate"          column="expire_date"          />
+        <result property="issueAuthority"      column="issue_authority"      />
+        <result property="certificateFile"     column="certificate_file"     />
+        <result property="status"              column="status"               />
+        <result property="createBy"            column="create_by"            />
+        <result property="createTime"          column="create_time"          />
+        <result property="updateBy"            column="update_by"            />
+        <result property="updateTime"          column="update_time"          />
+    </resultMap>
+
+    <sql id="selectEnterpriseCertificateVo">
+        select certificate_id, enterprise_id, certificate_name, certificate_type, certificate_number,
+               issue_date, expire_date, issue_authority, certificate_file, status, create_by, create_time,
+               update_by, update_time
+        from sys_enterprise_certificate
+    </sql>
+
+    <select id="selectEnterpriseCertificateList" parameterType="pd" resultMap="EnterpriseCertificateResult">
+        <include refid="selectEnterpriseCertificateVo"/>
+        <where>
+            <if test="enterpriseId != null ">
+                and enterprise_id = #{enterpriseId}
+            </if>
+            <if test="certificateName != null  and certificateName != ''">
+                and certificate_name like concat('%', #{certificateName}, '%')
+            </if>
+            <if test="certificateType != null  and certificateType != ''">
+                and certificate_type = #{certificateType}
+            </if>
+            <if test="certificateNumber != null  and certificateNumber != ''">
+                and certificate_number = #{certificateNumber}
+            </if>
+            <if test="status != null  and status != ''">
+                and status = #{status}
+            </if>
+        </where>
+        order by create_time desc
+    </select>
+
+    <select id="selectEnterpriseCertificateByCertificateId" parameterType="Long" resultMap="EnterpriseCertificateResult">
+        <include refid="selectEnterpriseCertificateVo"/>
+        where certificate_id = #{certificateId}
+    </select>
+
+    <select id="selectCertificatesByEnterpriseId" parameterType="Long" resultMap="EnterpriseCertificateResult">
+        <include refid="selectEnterpriseCertificateVo"/>
+        where enterprise_id = #{enterpriseId}
+        order by create_time desc
+    </select>
+
+    <insert id="insertEnterpriseCertificate" parameterType="pd" useGeneratedKeys="true" keyProperty="certificateId">
+        insert into sys_enterprise_certificate
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="enterpriseId != null">enterprise_id,</if>
+            <if test="certificateName != null and certificateName != ''">certificate_name,</if>
+            <if test="certificateType != null and certificateType != ''">certificate_type,</if>
+            <if test="certificateNumber != null">certificate_number,</if>
+            <if test="issueDate != null">issue_date,</if>
+            <if test="expireDate != null">expire_date,</if>
+            <if test="issueAuthority != null">issue_authority,</if>
+            <if test="certificateFile != null">certificate_file,</if>
+            <if test="status != null">status,</if>
+            <if test="createBy != null">create_by,</if>
+            create_time,
+            <if test="updateBy != null">update_by,</if>
+        </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="enterpriseId != null">#{enterpriseId},</if>
+            <if test="certificateName != null and certificateName != ''">#{certificateName},</if>
+            <if test="certificateType != null and certificateType != ''">#{certificateType},</if>
+            <if test="certificateNumber != null">#{certificateNumber},</if>
+            <if test="issueDate != null">#{issueDate},</if>
+            <if test="expireDate != null">#{expireDate},</if>
+            <if test="issueAuthority != null">#{issueAuthority},</if>
+            <if test="certificateFile != null">#{certificateFile},</if>
+            <if test="status != null">#{status},</if>
+            <if test="createBy != null">#{createBy},</if>
+            sysdate(),
+            <if test="updateBy != null">#{updateBy},</if>
+        </trim>
+    </insert>
+
+    <update id="updateEnterpriseCertificate" parameterType="pd">
+        update sys_enterprise_certificate
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="enterpriseId != null">enterprise_id = #{enterpriseId},</if>
+            <if test="certificateName != null and certificateName != ''">certificate_name = #{certificateName},</if>
+            <if test="certificateType != null and certificateType != ''">certificate_type = #{certificateType},</if>
+            <if test="certificateNumber != null">certificate_number = #{certificateNumber},</if>
+            <if test="issueDate != null">issue_date = #{issueDate},</if>
+            <if test="expireDate != null">expire_date = #{expireDate},</if>
+            <if test="issueAuthority != null">issue_authority = #{issueAuthority},</if>
+            <if test="certificateFile != null">certificate_file = #{certificateFile},</if>
+            <if test="status != null">status = #{status},</if>
+            <if test="updateBy != null">update_by = #{updateBy},</if>
+            update_time = sysdate(),
+        </trim>
+        where certificate_id = #{certificateId}
+    </update>
+
+    <delete id="deleteEnterpriseCertificateByCertificateId" parameterType="Long">
+        delete from sys_enterprise_certificate where certificate_id = #{certificateId}
+    </delete>
+
+    <delete id="deleteEnterpriseCertificateByCertificateIds" parameterType="String">
+        delete from sys_enterprise_certificate where certificate_id in
+        <foreach item="certificateId" collection="array" open="(" separator="," close=")">
+            #{certificateId}
+        </foreach>
+    </delete>
+</mapper>

+ 146 - 0
pm-system/src/main/resources/mapper/enterpriseInfo/EnterpriseInfoMapper.xml

@@ -0,0 +1,146 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
+<mapper namespace="com.pm.enterpriseInfo.mapper.EnterpriseMapper">
+
+    <resultMap type="pd" id="EnterpriseResult">
+        <result property="enterpriseId" column="enterprise_id" />
+        <result property="enterpriseName" column="enterprise_name" />
+        <result property="creditCode" column="credit_code" />
+        <result property="legalPerson" column="legal_person" />
+        <result property="registeredCapital" column="registered_capital" />
+        <result property="establishDate" column="establish_date" />
+        <result property="businessScope" column="business_scope" />
+        <result property="registeredAddress" column="registered_address" />
+        <result property="officeAddress" column="office_address" />
+        <result property="contactPhone" column="contact_phone" />
+        <result property="email" column="email" />
+        <result property="website" column="website" />
+        <result property="enterpriseStatus" column="enterprise_status" />
+        <result property="certStatus" column="cert_status" />
+        <result property="remark" column="remark" />
+        <result property="createBy" column="create_by" />
+        <result property="createTime" column="create_time" />
+        <result property="updateBy" column="update_by" />
+        <result property="updateTime" column="update_time" />
+    </resultMap>
+
+    <sql id="selectEnterpriseVo">
+        select enterprise_id, enterprise_name, credit_code, legal_person, registered_capital,
+               establish_date, business_scope, registered_address, office_address, contact_phone,
+               email, website, enterprise_status, cert_status, remark, create_by, create_time,
+               update_by, update_time from sys_enterprise
+    </sql>
+
+    <select id="selectEnterpriseList" parameterType="pd" resultMap="EnterpriseResult">
+        <include refid="selectEnterpriseVo"/>
+        <where>
+            <if test="enterpriseName != null and enterpriseName != ''">
+                and enterprise_name like concat('%', #{enterpriseName}, '%')
+            </if>
+            <if test="creditCode != null and creditCode != ''">
+                and credit_code = #{creditCode}
+            </if>
+            <if test="legalPerson != null and legalPerson != ''">
+                and legal_person like concat('%', #{legalPerson}, '%')
+            </if>
+            <if test="enterpriseStatus != null and enterpriseStatus != ''">
+                and enterprise_status = #{enterpriseStatus}
+            </if>
+            <if test="certStatus != null and certStatus != ''">
+                and cert_status = #{certStatus}
+            </if>
+        </where>
+    </select>
+
+    <select id="selectEnterpriseByEnterpriseId" parameterType="Long" resultMap="EnterpriseResult">
+        <include refid="selectEnterpriseVo"/>
+        where enterprise_id = #{enterpriseId}
+    </select>
+
+    <insert id="insertEnterprise" parameterType="pd" useGeneratedKeys="true" keyProperty="enterpriseId">
+        insert into sys_enterprise
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="enterpriseName != null and enterpriseName != ''">enterprise_name,</if>
+            <if test="creditCode != null and creditCode != ''">credit_code,</if>
+            <if test="legalPerson != null and legalPerson != ''">legal_person,</if>
+            <if test="registeredCapital != null and registeredCapital!=''">registered_capital,</if> <!-- 数值类型仅判null -->
+            <if test="establishDate != null and establishDate!=''">establish_date,</if> <!-- 日期类型仅判null -->
+            <if test="businessScope != null and businessScope != ''">business_scope,</if>
+            <if test="registeredAddress != null and registeredAddress != ''">registered_address,</if>
+            <if test="officeAddress != null and officeAddress != ''">office_address,</if>
+            <if test="contactPhone != null and contactPhone != ''">contact_phone,</if>
+            <if test="email != null and email != ''">email,</if>
+            <if test="website != null and website != ''">website,</if>
+            <if test="enterpriseStatus != null and enterpriseStatus != ''">enterprise_status,</if>
+            <if test="certStatus != null and certStatus != ''">cert_status,</if>
+            <if test="remark != null and remark != ''">remark,</if>
+            <if test="createBy != null and createBy != ''">create_by,</if>
+            create_time
+        </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="enterpriseName != null and enterpriseName != ''">#{enterpriseName},</if>
+            <if test="creditCode != null and creditCode != ''">#{creditCode},</if>
+            <if test="legalPerson != null and legalPerson != ''">#{legalPerson},</if>
+            <if test="registeredCapital != null and registeredCapital!=''">#{registeredCapital},</if>
+            <if test="establishDate != null and establishDate!=''">#{establishDate},</if>
+            <if test="businessScope != null and businessScope != ''">#{businessScope},</if>
+            <if test="registeredAddress != null and registeredAddress != ''">#{registeredAddress},</if>
+            <if test="officeAddress != null and officeAddress != ''">#{officeAddress},</if>
+            <if test="contactPhone != null and contactPhone != ''">#{contactPhone},</if>
+            <if test="email != null and email != ''">#{email},</if>
+            <if test="website != null and website != ''">#{website},</if>
+            <if test="enterpriseStatus != null and enterpriseStatus != ''">#{enterpriseStatus},</if>
+            <if test="certStatus != null and certStatus != ''">#{certStatus},</if>
+            <if test="remark != null and remark != ''">#{remark},</if>
+            <if test="createBy != null and createBy != ''">#{createBy},</if>
+            sysdate()
+        </trim>
+    </insert>
+
+    <update id="updateEnterprise" parameterType="pd">
+        update sys_enterprise
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="enterpriseName != null and enterpriseName != ''">enterprise_name = #{enterpriseName},</if>
+            <if test="creditCode != null and creditCode != ''">credit_code = #{creditCode},</if>
+            <if test="legalPerson != null and legalPerson != ''">legal_person = #{legalPerson},</if>
+            <if test="registeredCapital != null and registeredCapital!=''">registered_capital = #{registeredCapital},</if>
+            <if test="establishDate != null and establishDate!=''">establish_date = #{establishDate},</if>
+            <if test="businessScope != null and businessScope != ''">business_scope = #{businessScope},</if>
+            <if test="registeredAddress != null and registeredAddress != ''">registered_address = #{registeredAddress},</if>
+            <if test="officeAddress != null and officeAddress != ''">office_address = #{officeAddress},</if>
+            <if test="contactPhone != null and contactPhone != ''">contact_phone = #{contactPhone},</if>
+            <if test="email != null and email != ''">email = #{email},</if>
+            <if test="website != null and website != ''">website = #{website},</if>
+            <if test="enterpriseStatus != null and enterpriseStatus != ''">enterprise_status = #{enterpriseStatus},</if>
+            <if test="certStatus != null and certStatus != ''">cert_status = #{certStatus},</if>
+            <if test="remark != null and remark != ''">remark = #{remark},</if>
+            <if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if>
+            update_time = sysdate()
+        </trim>
+        where enterprise_id = #{enterpriseId}
+    </update>
+
+    <delete id="deleteEnterpriseByEnterpriseId" parameterType="Long">
+        delete from sys_enterprise where enterprise_id = #{enterpriseId}
+    </delete>
+
+    <delete id="deleteEnterpriseByEnterpriseIds" parameterType="String">
+        delete from sys_enterprise where enterprise_id in
+        <foreach item="enterpriseId" collection="array" open="(" separator="," close=")">
+            #{enterpriseId}
+        </foreach>
+    </delete>
+
+    <select id="selectEnterpriseByCreditCode" parameterType="String" resultMap="EnterpriseResult">
+        <include refid="selectEnterpriseVo"/>
+        where credit_code = #{creditCode}
+    </select>
+    <select id="selectEnterpriseCount" resultType="pd">
+        SELECT
+            COUNT(*) as total,
+            COUNT(CASE WHEN cert_status = '1' THEN 1 END) as certified,
+            COUNT(CASE WHEN enterprise_status = '0' THEN 1 END) as active,
+            COUNT(CASE WHEN enterprise_status = '1' THEN 1 END) as inactive
+        FROM sys_enterprise
+    </select>
+</mapper>

BIN
pm_ui/public/favicon.ico


+ 52 - 0
pm_ui/src/api/enterpriseInfo/enterprise.js

@@ -0,0 +1,52 @@
+import request from '@/utils/request'
+
+// 查询企业信息列表
+export function listEnterprise(query) {
+    return request({
+        url: '/system/enterprise/list',
+        method: 'get',
+        params: query
+    })
+}
+
+// 查询企业信息详细
+export function getEnterprise(enterpriseId) {
+    return request({
+        url: '/system/enterprise/' + enterpriseId,
+        method: 'get'
+    })
+}
+
+// 新增企业信息
+export function addEnterprise(data) {
+    return request({
+        url: '/system/enterprise',
+        method: 'post',
+        data: data
+    })
+}
+
+// 修改企业信息
+export function updateEnterprise(data) {
+    return request({
+        url: '/system/enterprise',
+        method: 'put',
+        data: data
+    })
+}
+
+// 删除企业信息
+export function delEnterprise(enterpriseId) {
+    return request({
+        url: '/system/enterprise/' + enterpriseId,
+        method: 'delete'
+    })
+}
+
+// 获取企业统计数据
+export function getEnterpriseStatistics() {
+    return request({
+        url: '/system/enterprise/statistics',
+        method: 'get'
+    })
+}

+ 2 - 1
pm_ui/src/layout/components/Navbar.vue

@@ -163,7 +163,8 @@ connectToWebSocket((newAnnounce) => {
     //   duration: 5000,
     //   position: 'top-right'
     // });
-    if (!newAnnounce==="del" || !newAnnounce==="update"){ // 如果删除公告则不提示信息
+    debugger
+    if (newAnnounce !== "del" && newAnnounce !== "update"){ // 如果删除公告则不提示信息
       txt2("新公告", "您有新的公告,请注意查收",'warning',3000);
     }
     getlistNotice()

+ 86 - 12
pm_ui/src/layout/index.vue

@@ -3,11 +3,15 @@
     <div v-if="device === 'mobile' && sidebar.opened" class="drawer-bg" @click="handleClickOutside"/>
     <sidebar v-if="!sidebar.hide" class="sidebar-container" />
     <div :class="{ hasTagsView: needTagsView, sidebarHide: sidebar.hide }" class="main-container">
-      <div :class="{ 'fixed-header': fixedHeader }">
+      <!-- 头部区域始终固定 -->
+      <div class="header-wrapper">
         <navbar @setLayout="setLayout" />
         <tags-view v-if="needTagsView" />
       </div>
-      <app-main />
+      <!-- 主内容区域可滚动 -->
+      <div class="content-wrapper">
+        <app-main />
+      </div>
       <settings ref="settingRef" />
     </div>
   </div>
@@ -63,17 +67,39 @@ const settingRef = ref(null);
 function setLayout() {
   settingRef.value.openSetting();
 }
+// 计算头部实际高度
+const headerHeight = ref(0);
+
+onMounted(() => {
+  nextTick(() => {
+    const headerEl = document.querySelector('.header-wrapper');
+    if (headerEl) {
+      headerHeight.value = headerEl.offsetHeight;
+    }
+  });
+});
+
+// 监听 needTagsView 变化重新计算高度
+watch(needTagsView, () => {
+  nextTick(() => {
+    const headerEl = document.querySelector('.header-wrapper');
+    if (headerEl) {
+      headerHeight.value = headerEl.offsetHeight;
+    }
+  });
+});
 </script>
 
 <style lang="scss" scoped>
-  @import "@/assets/styles/mixin.scss";
-  @import "@/assets/styles/variables.module.scss";
+@import "@/assets/styles/mixin.scss";
+@import "@/assets/styles/variables.module.scss";
 
 .app-wrapper {
   @include clearfix;
   position: relative;
-  height: 100%;
+  height: 100vh; // 使用视口高度
   width: 100%;
+  overflow: hidden; // 防止整体页面滚动
 
   &.mobile.openSidebar {
     position: fixed;
@@ -91,24 +117,72 @@ function setLayout() {
   z-index: 999;
 }
 
-.fixed-header {
+.main-container {
+  height: 100vh;
+  display: flex;
+  flex-direction: column;
+  margin-left: $base-sidebar-width;
+  transition: margin-left 0.28s;
+
+  &.sidebarHide {
+    margin-left: 0;
+  }
+}
+
+// 头部固定区域
+.header-wrapper {
+  position: sticky;
+  top: 0;
+  z-index: 1000;
+  background: #fff;
+  box-shadow: 0 1px 4px rgba(0,21,41,.08);
+  flex-shrink: 0; // 防止被压缩
+}
+
+// 内容可滚动区域
+.content-wrapper {
+  flex: 1;
+  overflow-y: auto;
+  overflow-x: hidden;
+  height: 0; // 配合 flex: 1 使用
+}
+
+// 侧边栏隐藏时的样式调整
+.hideSidebar .main-container {
+  margin-left: 54px;
+}
+
+.sidebarHide .main-container {
+  margin-left: 0;
+}
+
+// 移动端样式
+.mobile .main-container {
+  margin-left: 0;
+}
+
+// 如果你仍然需要原来的 fixed-header 功能
+.fixed-header .header-wrapper {
   position: fixed;
   top: 0;
   right: 0;
-  z-index: 9;
   width: calc(100% - #{$base-sidebar-width});
-  transition: width 0.28s;
+  z-index: 1000;
+}
+
+.fixed-header .content-wrapper {
+  padding-top: 84px; // 根据实际头部高度调整
 }
 
-.hideSidebar .fixed-header {
+.hideSidebar .fixed-header .header-wrapper {
   width: calc(100% - 54px);
 }
 
-.sidebarHide .fixed-header {
+.sidebarHide .fixed-header .header-wrapper {
   width: 100%;
 }
 
-.mobile .fixed-header {
+.mobile .fixed-header .header-wrapper {
   width: 100%;
 }
-</style>
+</style>

+ 8 - 1
pm_ui/src/views/buildingEquipmentMonitoring/index2.vue

@@ -45,12 +45,19 @@
         <el-table-column label="组态图ID" prop="configId" align="center" width="140" />
         <el-table-column label="图纸预览" align="center" width="120">
           <template #default="scope">
-            <el-image
+<!--            <el-image
                 :src="generateThumbnail(scope.row)"
                 :preview-src-list="[generateFullDrawing(scope.row)]"
                 fit="contain"
                 style="width: 80px; height: 60px; cursor: pointer;"
                 :preview-teleported="true"
+            >--> <!--设置为空的数组 不显示预览图片的功能-->
+              <el-image
+                :src="generateThumbnail(scope.row)"
+                :preview-src-list="[]"
+                fit="contain"
+                style="width: 80px; height: 60px; cursor: pointer;"
+                :preview-teleported="true"
             >
               <template #error>
                 <div class="image-slot">

+ 3 - 3
pm_ui/src/views/buildingEquipmentMonitoring/index3.vue

@@ -143,7 +143,7 @@
             </el-badge>
           </template>
         </el-table-column>
-        <el-table-column label="平均在线率" align="center" width="120" sortable>
+<!--        <el-table-column label="平均在线率" align="center" width="120" sortable>
           <template #default="scope">
             <div class="online-rate">
               <el-progress
@@ -158,11 +158,11 @@
               </el-progress>
             </div>
           </template>
-        </el-table-column>
+        </el-table-column>-->
         <el-table-column label="健康指数" align="center" width="160" sortable>
           <template #default="scope">
             <div class="health-index">
-              <span class="health-score">{{ parseKeyMetrics(scope.row.keyMetrics).healthIndex }}</span>
+<!--              <span class="health-score">{{ parseKeyMetrics(scope.row.keyMetrics).healthIndex }}</span>-->
               <el-progress
                   :percentage="Number(parseKeyMetrics(scope.row.keyMetrics).healthIndex)"
                   :stroke-width="6"

+ 3 - 3
pm_ui/src/views/buildingEquipmentMonitoring/index4.vue

@@ -120,7 +120,7 @@
           :row-class-name="tableRowClassName"
       >
         <el-table-column label="指令ID" prop="cmdId" align="center" width="140" fixed="left" />
-        <el-table-column label="目标设备" align="center" min-width="200">
+        <el-table-column label="目标设备" align="center" >
           <template #default="scope">
             <div class="device-tags">
               <el-tag
@@ -135,7 +135,7 @@
             </div>
           </template>
         </el-table-column>
-        <el-table-column label="指令类型" prop="cmdType" align="center" width="120">
+        <el-table-column label="指令类型" prop="cmdType" align="center" width="280">
           <template #default="scope">
             <el-tag :type="getCmdTypeColor(scope.row.cmdType)">
               {{ mapCmdType(scope.row.cmdType) }}
@@ -169,7 +169,7 @@
             </span>
           </template>
         </el-table-column>
-        <el-table-column label="操作" align="center" width="150" fixed="right">
+        <el-table-column label="操作" align="center" width="180" fixed="right">
           <template #default="scope">
             <el-button
                 v-if="scope.row.status === 'FAILED'"

+ 16 - 16
pm_ui/src/views/dtxt/index.vue

@@ -5,7 +5,7 @@
       <el-tab-pane label="电梯监控" name="monitor">
         <el-form :model="elevatorQuery" ref="elevatorQueryRef" :inline="true" label-width="80px">
           <el-form-item label="楼栋" prop="buildingId">
-            <el-select v-model="elevatorQuery.buildingId" placeholder="请选择楼栋" clearable style="width: 150px">
+            <el-select v-model="elevatorQuery.buildingId" placeholder="请选择楼栋" clearable style="width: 180px;">
               <el-option
                   v-for="item in buildingList"
                   :key="item.id"
@@ -15,14 +15,14 @@
             </el-select>
           </el-form-item>
           <el-form-item label="电梯类型" prop="elevatorType">
-            <el-select v-model="elevatorQuery.elevatorType" placeholder="请选择类型" clearable style="width: 150px">
+            <el-select v-model="elevatorQuery.elevatorType" placeholder="请选择类型" clearable style="width: 180px;">
               <el-option label="客梯" value="客梯" />
               <el-option label="货梯" value="货梯" />
               <el-option label="扶梯" value="扶梯" />
             </el-select>
           </el-form-item>
           <el-form-item label="运行状态" prop="runStatus">
-            <el-select v-model="elevatorQuery.runStatus" placeholder="请选择状态" clearable style="width: 150px">
+            <el-select v-model="elevatorQuery.runStatus" placeholder="请选择状态" clearable style="width: 180px;">
               <el-option label="停止" :value="0" />
               <el-option label="上行" :value="1" />
               <el-option label="下行" :value="2" />
@@ -208,7 +208,7 @@
             <el-input v-model="alarmQuery.deviceName" placeholder="请输入设备名称" clearable />
           </el-form-item>
           <el-form-item label="报警类型" prop="alarmType">
-            <el-select v-model="alarmQuery.alarmType" placeholder="请选择报警类型" clearable style="width: 150px">
+            <el-select v-model="alarmQuery.alarmType" placeholder="请选择报警类型" clearable style="width: 180px;">
               <el-option label="超载报警" value="overload" />
               <el-option label="困人报警" value="trapped" />
               <el-option label="故障报警" value="fault" />
@@ -216,14 +216,14 @@
             </el-select>
           </el-form-item>
           <el-form-item label="报警级别" prop="alarmLevel">
-            <el-select v-model="alarmQuery.alarmLevel" placeholder="请选择报警级别" clearable style="width: 150px">
+            <el-select v-model="alarmQuery.alarmLevel" placeholder="请选择报警级别" clearable style="width: 180px;">
               <el-option label="提示" :value="1" />
               <el-option label="一般" :value="2" />
               <el-option label="严重" :value="3" />
             </el-select>
           </el-form-item>
           <el-form-item label="处理状态" prop="handleStatus">
-            <el-select v-model="alarmQuery.handleStatus" placeholder="请选择处理状态" clearable style="width: 150px">
+            <el-select v-model="alarmQuery.handleStatus" placeholder="请选择处理状态" clearable style="width: 180px;">
               <el-option label="未处理" :value="0" />
               <el-option label="已处理" :value="1" />
             </el-select>
@@ -333,12 +333,12 @@
                       </div>
                     </div>
                   </div>
-                  
+
                   <!-- 电梯轿厢 -->
-                  <div 
+                  <div
                     class="elevator-cabin"
                     :style="{ top: getElevatorPosition() + 'px' }"
-                    :class="{ 
+                    :class="{
                       'moving-up': currentElevator.direction === '1',
                       'moving-down': currentElevator.direction === '2'
                     }"
@@ -356,7 +356,7 @@
                     </div>
                   </div>
                 </div>
-                
+
                 <!-- 状态指示器 -->
                 <div class="status-indicators">
                   <div class="indicator" :class="{ active: currentElevator.direction === '1' }">
@@ -437,8 +437,8 @@
                         <span class="value-number">{{ currentElevator.currentLoad || 450 }}</span>
                         <span class="value-unit">kg</span>
                       </div>
-                      <el-progress 
-                        :percentage="((currentElevator.currentLoad || 450) / currentElevator.ratedLoad) * 100" 
+                      <el-progress
+                        :percentage="((currentElevator.currentLoad || 450) / currentElevator.ratedLoad) * 100"
                         :color="getLoadColor"
                       />
                     </div>
@@ -448,13 +448,13 @@
                         <span class="value-number">{{ currentElevator.currentSpeed || 1.5 }}</span>
                         <span class="value-unit">m/s</span>
                       </div>
-                      <el-progress 
-                        :percentage="((currentElevator.currentSpeed || 1.5) / currentElevator.ratedSpeed) * 100" 
+                      <el-progress
+                        :percentage="((currentElevator.currentSpeed || 1.5) / currentElevator.ratedSpeed) * 100"
                         color="#67C23A"
                       />
                     </div>
                   </div>
-                  
+
                   <div class="data-row">
                     <div class="data-item full-width">
                       <div class="data-label">今日运行统计</div>
@@ -1673,4 +1673,4 @@ onMounted(() => {
   font-weight: 600;
   color: #333;
 }
-</style>
+</style>

+ 1123 - 100
pm_ui/src/views/energyManagement/dataComparison/index.vue

@@ -3,62 +3,148 @@
     <!-- 上半部分:两个卡片 -->
     <div class="top-section">
       <!-- 本月 -->
-      <div class="card">
-        <h3>本月</h3>
+      <div class="card month-card">
+        <div class="card-header">
+          <h3>本月能耗</h3>
+          <span class="date-info">{{ currentMonth }}</span>
+        </div>
         <div class="data-container">
           <div class="data-row">
             <div class="data-item">
-              <span class="value">8.405</span><span class="unit">t</span>
+              <span class="value">{{ monthData.current }}</span><span class="unit">t</span>
               <span class="label">当月</span>
             </div>
             <div class="data-item">
-              <span class="value">8.477</span><span class="unit">t</span>
+              <span class="value">{{ monthData.lastMonth }}</span><span class="unit">t</span>
               <span class="label">上月同期</span>
             </div>
             <div class="data-item trend">
-              <span class="value" style="color: #00c853;">-0.072</span><span class="unit">↓</span>
-              <span class="label">趋势</span>
+              <span class="value" :style="{ color: monthTrend.color }">
+                {{ monthTrend.value }}
+              </span>
+              <span class="unit">{{ monthTrend.icon }}</span>
+              <span class="label">{{ monthTrend.percentage }}</span>
             </div>
           </div>
         </div>
+        <div class="progress-bar">
+          <div class="progress-fill" :style="{ width: monthProgress + '%' }"></div>
+        </div>
       </div>
 
       <!-- 本年 -->
-      <div class="card">
-        <h3>本年</h3>
+      <div class="card year-card">
+        <div class="card-header">
+          <h3>本年能耗</h3>
+          <span class="date-info">{{ currentYear }}年</span>
+        </div>
         <div class="data-container">
           <div class="data-row">
             <div class="data-item">
-              <span class="value">144.747</span><span class="unit">t</span>
+              <span class="value">{{ yearData.current }}</span><span class="unit">t</span>
               <span class="label">当年</span>
             </div>
             <div class="data-item">
-              <span class="value">123.812</span><span class="unit">t</span>
+              <span class="value">{{ yearData.lastYear }}</span><span class="unit">t</span>
               <span class="label">去年同期</span>
             </div>
             <div class="data-item trend">
-              <span class="value" style="color: #d50000;">20.935</span><span class="unit">↑</span>
-              <span class="label">趋势</span>
+              <span class="value" :style="{ color: yearTrend.color }">
+                {{ yearTrend.value }}
+              </span>
+              <span class="unit">{{ yearTrend.icon }}</span>
+              <span class="label">{{ yearTrend.percentage }}</span>
             </div>
           </div>
         </div>
+        <div class="progress-bar">
+          <div class="progress-fill year" :style="{ width: yearProgress + '%' }"></div>
+        </div>
       </div>
     </div>
 
     <!-- 筛选区域 -->
-    <div class="filter-container">
-      <span>分类能耗</span>
-      <select v-model="selectedCategory">
-        <option value="电">电</option>
-        <option value="水">水</option>
-        <option value="天然气">天然气</option>
-      </select>
-      <input type="text" v-model="selectedYear" placeholder="输入年份" />
-      <button @click="fetchData">查询</button>
+    <div class="filter-section">
+      <div class="filter-container">
+        <div class="filter-group">
+          <label>能源类型</label>
+          <select v-model="selectedCategory">
+            <option value="all">全部</option>
+            <option value="electricity">电力</option>
+            <option value="water">水</option>
+            <option value="gas">天然气</option>
+          </select>
+        </div>
+
+        <div class="filter-group">
+          <label>年份</label>
+          <select v-model="selectedYear">
+            <option v-for="year in yearOptions" :key="year" :value="year">
+              {{ year }}年
+            </option>
+          </select>
+        </div>
+
+        <div class="filter-group">
+          <label>时间范围</label>
+          <select v-model="timeRange">
+            <option value="month">按月</option>
+            <option value="quarter">按季度</option>
+            <option value="year">按年</option>
+          </select>
+        </div>
+
+        <button @click="handleQuery" class="query-btn" :disabled="loading">
+          <i class="icon-search"></i>
+          {{ loading ? '查询中...' : '查询' }}
+        </button>
+
+        <button @click="exportData" class="export-btn" :disabled="!hasData">
+          <i class="icon-export"></i> 导出数据
+        </button>
+      </div>
+
+      <div class="summary-info">
+        <span class="info-item">
+          <i class="icon-info"></i>
+          总能耗: <strong>{{ totalConsumption }}</strong> t
+        </span>
+        <span class="info-item">
+          同比: <strong :class="yearOverYearClass">{{ yearOverYear }}</strong>
+        </span>
+      </div>
     </div>
 
     <!-- 图表区域 -->
-    <div id="chart" ref="chartRef" style="width: 100%; height: 400px;"></div>
+    <div class="chart-section">
+      <div class="chart-header">
+        <h3>能耗趋势分析</h3>
+        <div class="chart-controls">
+          <button
+              v-for="type in chartTypes"
+              :key="type.value"
+              @click="switchChartType(type.value)"
+              :class="['chart-type-btn', { active: currentChartType === type.value }]"
+          >
+            {{ type.label }}
+          </button>
+        </div>
+      </div>
+
+      <!-- 无数据提示 -->
+      <div v-if="!hasData" class="no-data">
+        <i class="icon-empty"></i>
+        <p>请选择查询条件并点击查询按钮</p>
+      </div>
+
+      <!-- 图表 -->
+      <div v-show="hasData" ref="chartRef" class="chart-container"></div>
+    </div>
+
+    <!-- 加载状态 -->
+    <div v-if="loading" class="loading-overlay">
+      <div class="spinner"></div>
+    </div>
   </div>
 </template>
 
@@ -68,62 +154,609 @@ import * as echarts from 'echarts';
 export default {
   data() {
     return {
-      selectedCategory: '电',
-      selectedYear: '',
+      selectedCategory: 'all',
+      selectedYear: new Date().getFullYear(),
+      timeRange: 'month',
+      currentChartType: 'bar',
       chartInstance: null,
+      loading: false,
+      hasData: false,
+      resizeObserver: null,
+
+      // 数据
+      monthData: {
+        current: 0,
+        lastMonth: 0
+      },
+      yearData: {
+        current: 0,
+        lastYear: 0
+      },
+
+      // 图表类型
+      chartTypes: [
+        { value: 'bar', label: '柱状图' },
+        { value: 'line', label: '折线图' },
+        { value: 'pie', label: '饼图' }
+      ],
+
+      // 模拟数据
+      chartData: {
+        month: [],
+        values: []
+      }
     };
   },
+
+  computed: {
+    currentMonth() {
+      const months = ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'];
+      return months[new Date().getMonth()];
+    },
+
+    currentYear() {
+      return new Date().getFullYear();
+    },
+
+    yearOptions() {
+      const currentYear = new Date().getFullYear();
+      return Array.from({ length: 5 }, (_, i) => currentYear - i);
+    },
+
+    monthTrend() {
+      const diff = this.monthData.current - this.monthData.lastMonth;
+      const percentage = this.monthData.lastMonth > 0
+          ? ((Math.abs(diff) / this.monthData.lastMonth) * 100).toFixed(1)
+          : '0.0';
+      return {
+        value: Math.abs(diff).toFixed(3),
+        color: diff < 0 ? '#00c853' : '#ff5252',
+        icon: diff < 0 ? '↓' : '↑',
+        percentage: `${percentage}%`
+      };
+    },
+
+    yearTrend() {
+      const diff = this.yearData.current - this.yearData.lastYear;
+      const percentage = this.yearData.lastYear > 0
+          ? ((Math.abs(diff) / this.yearData.lastYear) * 100).toFixed(1)
+          : '0.0';
+      return {
+        value: Math.abs(diff).toFixed(3),
+        color: diff < 0 ? '#00c853' : '#ff5252',
+        icon: diff < 0 ? '↓' : '↑',
+        percentage: `${percentage}%`
+      };
+    },
+
+    monthProgress() {
+      const total = this.monthData.current + this.monthData.lastMonth;
+      return total > 0 ? (this.monthData.current / total) * 100 : 0;
+    },
+
+    yearProgress() {
+      const total = this.yearData.current + this.yearData.lastYear;
+      return total > 0 ? (this.yearData.current / total) * 100 : 0;
+    },
+
+    totalConsumption() {
+      return this.chartData.values.reduce((sum, val) => sum + val, 0).toFixed(2);
+    },
+
+    yearOverYear() {
+      if (this.yearData.lastYear === 0) return '0.0%';
+      const percentage = ((this.yearData.current - this.yearData.lastYear) / this.yearData.lastYear * 100).toFixed(1);
+      return percentage > 0 ? `+${percentage}%` : `${percentage}%`;
+    },
+
+    yearOverYearClass() {
+      return this.yearData.current > this.yearData.lastYear ? 'increase' : 'decrease';
+    }
+  },
+
   mounted() {
-    this.initChart();
+    this.$nextTick(() => {
+      this.initChart();
+      this.setupResizeObserver();
+    });
+    window.addEventListener('resize', this.handleResize);
   },
+
+  beforeUnmount() {
+    window.removeEventListener('resize', this.handleResize);
+    if (this.resizeObserver) {
+      this.resizeObserver.disconnect();
+    }
+    if (this.chartInstance) {
+      this.chartInstance.dispose();
+    }
+  },
+
   methods: {
     initChart() {
-      const chartDom = this.$refs.chartRef;
-      this.chartInstance = echarts.init(chartDom);
+      if (!this.$refs.chartRef) {
+        console.error('Chart container not found');
+        return;
+      }
+
+      try {
+        // 如果已存在实例,先销毁
+        if (this.chartInstance) {
+          this.chartInstance.dispose();
+        }
+
+        this.chartInstance = echarts.init(this.$refs.chartRef);
+        console.log('Chart initialized successfully');
+      } catch (error) {
+        console.error('Failed to initialize chart:', error);
+      }
+    },
+
+    // 设置ResizeObserver来监听容器尺寸变化
+    setupResizeObserver() {
+      if (!this.$refs.chartRef || !window.ResizeObserver) return;
+
+      this.resizeObserver = new ResizeObserver((entries) => {
+        for (let entry of entries) {
+          if (entry.target === this.$refs.chartRef && this.chartInstance) {
+            // 延迟执行resize,避免频繁调用
+            clearTimeout(this.resizeTimer);
+            this.resizeTimer = setTimeout(() => {
+              this.chartInstance.resize();
+            }, 100);
+          }
+        }
+      });
+
+      this.resizeObserver.observe(this.$refs.chartRef);
+    },
+
+    updateChart() {
+      if (!this.chartInstance || !this.hasData) {
+        console.log('Chart instance not ready or no data');
+        return;
+      }
+
+      let option = {};
+
+      try {
+        if (this.currentChartType === 'bar') {
+          option = this.getBarOption();
+        } else if (this.currentChartType === 'line') {
+          option = this.getLineOption();
+        } else if (this.currentChartType === 'pie') {
+          option = this.getPieOption();
+        }
+
+        console.log('Setting chart option:', option);
+        this.chartInstance.setOption(option, true);
+
+        // 使用requestAnimationFrame确保在下一帧渲染
+        requestAnimationFrame(() => {
+          if (this.chartInstance) {
+            this.chartInstance.resize();
+          }
+        });
+      } catch (error) {
+        console.error('Failed to update chart:', error);
+      }
+    },
+
+    // 强制重新渲染图表 - 改进版本
+    forceResizeChart() {
+      if (!this.chartInstance) return;
+
+      // 使用requestAnimationFrame和多次尝试
+      const attemptResize = (attempts = 0) => {
+        if (attempts >= 5) return; // 最多尝试5次
+
+        requestAnimationFrame(() => {
+          if (this.chartInstance) {
+            try {
+              this.chartInstance.resize();
+              console.log(`Chart resize attempt ${attempts + 1} completed`);
+            } catch (error) {
+              console.error(`Chart resize attempt ${attempts + 1} failed:`, error);
+            }
+          }
+
+          // 如果还有尝试次数,继续尝试
+          if (attempts < 4) {
+            setTimeout(() => attemptResize(attempts + 1), 100 * (attempts + 1));
+          }
+        });
+      };
+
+      attemptResize();
+    },
+
+    getBarOption() {
+      return {
+        title: {
+          text: `${this.selectedYear}年${this.getCategoryLabel(this.selectedCategory)}能耗分布`,
+          left: 'center',
+          textStyle: { fontSize: 16, fontWeight: 'normal' }
+        },
+        tooltip: {
+          trigger: 'axis',
+          axisPointer: {
+            type: 'shadow',
+            shadowStyle: {
+              color: 'rgba(150,150,150,0.1)'
+            }
+          },
+          backgroundColor: 'rgba(255, 255, 255, 0.95)',
+          borderColor: '#ddd',
+          borderWidth: 1,
+          padding: [10, 15],
+          textStyle: {
+            color: '#333',
+            fontSize: 14
+          },
+          formatter: function(params) {
+            const data = params[0];
+            return `
+              <div style="font-weight: 600; margin-bottom: 5px;">${data.name}</div>
+              <div style="display: flex; align-items: center;">
+                <span style="display: inline-block; width: 10px; height: 10px; background: ${data.color}; border-radius: 50%; margin-right: 8px;"></span>
+                <span>能耗:${data.value} t</span>
+              </div>
+            `;
+          }
+        },
+        xAxis: {
+          type: 'category',
+          data: this.chartData.month,
+          axisLabel: {
+            color: '#666',
+            rotate: this.chartData.month.length > 12 ? 45 : 0
+          }
+        },
+        yAxis: {
+          type: 'value',
+          name: '能耗 (t)',
+          axisLabel: { color: '#666' }
+        },
+        series: [{
+          name: '能耗',
+          type: 'bar',
+          data: this.chartData.values,
+          label: {
+            show: true,
+            position: 'top',
+            color: '#666',
+            fontSize: 12,
+            formatter: '{c}'
+          },
+          itemStyle: {
+            color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+              { offset: 0, color: '#2196f3' },
+              { offset: 1, color: '#1976d2' }
+            ]),
+            borderRadius: [4, 4, 0, 0]
+          },
+          emphasis: {
+            itemStyle: {
+              color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+                { offset: 0, color: '#42a5f5' },
+                { offset: 1, color: '#2196f3' }
+              ])
+            }
+          }
+        }],
+        grid: {
+          top: 60,
+          bottom: 60,
+          left: 60,
+          right: 40
+        }
+      };
+    },
 
-      const option = {
-        title: { text: '能耗分布', left: 'center' },
+    getLineOption() {
+      return {
+        title: {
+          text: `${this.selectedYear}年${this.getCategoryLabel(this.selectedCategory)}能耗趋势`,
+          left: 'center',
+          textStyle: { fontSize: 16, fontWeight: 'normal' }
+        },
         tooltip: {
           trigger: 'axis',
-          axisPointer: { type: 'shadow' }
+          backgroundColor: 'rgba(255, 255, 255, 0.95)',
+          borderColor: '#ddd',
+          borderWidth: 1,
+          padding: [10, 15],
+          textStyle: {
+            color: '#333',
+            fontSize: 14
+          },
+          formatter: function(params) {
+            const data = params[0];
+            return `
+              <div style="font-weight: 600; margin-bottom: 5px;">${data.name}</div>
+              <div style="display: flex; align-items: center;">
+                <span style="display: inline-block; width: 10px; height: 10px; background: ${data.color}; border-radius: 50%; margin-right: 8px;"></span>
+                <span>能耗:${data.value} t</span>
+              </div>
+            `;
+          }
         },
         xAxis: {
           type: 'category',
-          data: ['1月', '2月', '3月', '4月', '5月'],
-          axisLabel: { color: '#333' }
+          data: this.chartData.month,
+          boundaryGap: false,
+          axisLabel: { color: '#666' }
         },
         yAxis: {
           type: 'value',
-          axisLabel: { color: '#333' }
+          name: '能耗 (t)',
+          axisLabel: { color: '#666' }
         },
-        series: [
-          {
-            name: '能耗',
-            type: 'bar',
-            data: [39, 30, 28, 22, 6],
-            itemStyle: { color: '#2196f3' }
+        series: [{
+          name: '能耗',
+          type: 'line',
+          data: this.chartData.values,
+          smooth: true,
+          symbol: 'circle',
+          symbolSize: 8,
+          showSymbol: true,
+          label: {
+            show: true,
+            color: '#666',
+            fontSize: 12,
+            formatter: '{c}'
+          },
+          lineStyle: {
+            color: '#2196f3',
+            width: 3
+          },
+          areaStyle: {
+            color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+              { offset: 0, color: 'rgba(33, 150, 243, 0.3)' },
+              { offset: 1, color: 'rgba(33, 150, 243, 0.05)' }
+            ])
           }
-        ],
+        }],
         grid: {
-          top: 30,
-          bottom: 30,
+          top: 60,
+          bottom: 60,
           left: 60,
-          right: 20
+          right: 40
         }
       };
+    },
+
+    getPieOption() {
+      const pieData = this.chartData.month.map((month, index) => ({
+        name: month,
+        value: this.chartData.values[index]
+      }));
+
+      return {
+        title: {
+          text: `${this.selectedYear}年${this.getCategoryLabel(this.selectedCategory)}能耗占比`,
+          left: 'center',
+          textStyle: { fontSize: 16, fontWeight: 'normal' }
+        },
+        tooltip: {
+          trigger: 'item',
+          backgroundColor: 'rgba(255, 255, 255, 0.95)',
+          borderColor: '#ddd',
+          borderWidth: 1,
+          padding: [10, 15],
+          textStyle: {
+            color: '#333',
+            fontSize: 14
+          },
+          formatter: function(params) {
+            const total = pieData.reduce((sum, item) => sum + item.value, 0);
+            const percent = ((params.value / total) * 100).toFixed(1);
+            return `
+              <div style="font-weight: 600; margin-bottom: 5px;">${params.name}</div>
+              <div style="display: flex; align-items: center; margin-bottom: 3px;">
+                <span style="display: inline-block; width: 10px; height: 10px; background: ${params.color}; border-radius: 50%; margin-right: 8px;"></span>
+                <span>能耗:${params.value} t</span>
+              </div>
+              <div style="margin-left: 18px; color: #666;">
+                占比:${percent}%
+              </div>
+            `;
+          }
+        },
+        legend: {
+          type: 'scroll',
+          bottom: 10,
+          data: this.chartData.month
+        },
+        series: [{
+          name: '能耗',
+          type: 'pie',
+          radius: ['40%', '70%'],
+          center: ['50%', '45%'],
+          data: pieData,
+          emphasis: {
+            itemStyle: {
+              shadowBlur: 10,
+              shadowOffsetX: 0,
+              shadowColor: 'rgba(0, 0, 0, 0.5)'
+            },
+            label: {
+              show: true,
+              fontSize: 14,
+              fontWeight: 'bold'
+            }
+          },
+          label: {
+            show: true,
+            position: 'outside',
+            formatter: '{b}: {c}t',
+            fontSize: 12
+          },
+          labelLine: {
+            show: true,
+            length: 15,
+            length2: 10
+          }
+        }]
+      };
+    },
+
+    async handleQuery() {
+      await this.fetchData();
+    },
+
+    async fetchData() {
+      this.loading = true;
+
+      try {
+        // 模拟API调用
+        await new Promise(resolve => setTimeout(resolve, 800));
+
+        // 根据选择更新数据
+        if (this.timeRange === 'month') {
+          this.chartData.month = ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'];
+          this.chartData.values = this.generateRandomData(12);
+        } else if (this.timeRange === 'quarter') {
+          this.chartData.month = ['Q1', 'Q2', 'Q3', 'Q4'];
+          this.chartData.values = this.generateRandomData(4, 80, 120);
+        } else if (this.timeRange === 'year') {
+          this.chartData.month = this.yearOptions.slice(0, 5).reverse().map(y => `${y}年`);
+          this.chartData.values = this.generateRandomData(5, 100, 200);
+        }
 
-      this.chartInstance.setOption(option);
+        // 更新月度和年度数据
+        this.updateSummaryData();
+
+        // 标记已有数据
+        this.hasData = true;
+
+        console.log('Data fetched:', this.chartData);
+
+        // 等待DOM更新
+        await this.$nextTick();
+
+        // 确保图表容器存在且可见
+        if (this.$refs.chartRef && this.hasData) {
+          // 如果图表实例不存在,重新初始化
+          if (!this.chartInstance) {
+            this.initChart();
+            await this.$nextTick(); // 等待初始化完成
+          }
+
+          // 延迟更新图表,确保容器完全显示
+          setTimeout(() => {
+            this.updateChart();
+            // 强制重新调整图表尺寸
+            setTimeout(() => {
+              this.forceResizeChart();
+            }, 200);
+          }, 200);
+        }
+
+      } catch (error) {
+        console.error('数据获取失败:', error);
+        if (this.$message) {
+          this.$message.error('数据加载失败,请重试');
+        } else {
+          alert('数据加载失败,请重试');
+        }
+      } finally {
+        this.loading = false;
+      }
+    },
+
+    generateRandomData(count, min = 20, max = 50) {
+      return Array.from({ length: count }, () =>
+          Math.floor(Math.random() * (max - min + 1)) + min
+      );
+    },
+
+    updateSummaryData() {
+      // 模拟更新汇总数据
+      const baseMonth = 8.5;
+      const baseYear = 140;
+
+      this.monthData.current = (baseMonth + Math.random() * 2 - 1).toFixed(3);
+      this.monthData.lastMonth = (baseMonth + Math.random() * 2 - 1).toFixed(3);
+
+      this.yearData.current = (baseYear + Math.random() * 20 - 10).toFixed(3);
+      this.yearData.lastYear = (baseYear + Math.random() * 20 - 10).toFixed(3);
+    },
+
+    switchChartType(type) {
+      this.currentChartType = type;
+      // 延迟更新图表,确保状态已更新
+      this.$nextTick(() => {
+        this.updateChart();
+        // 切换图表类型后也强制resize
+        setTimeout(() => {
+          this.forceResizeChart();
+        }, 100);
+      });
+    },
+
+    exportData() {
+      if (!this.hasData) {
+        if (this.$message) {
+          this.$message.warning('请先查询数据');
+        } else {
+          alert('请先查询数据');
+        }
+        return;
+      }
+
+      // 准备导出数据
+      const data = this.chartData.month.map((month, index) => ({
+        时间: month,
+        能耗: this.chartData.values[index],
+        单位: 't',
+        类型: this.getCategoryLabel(this.selectedCategory)
+      }));
+
+      // 转换为CSV
+      const csv = this.convertToCSV(data);
+
+      // 下载文件
+      const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
+      const link = document.createElement('a');
+      const url = URL.createObjectURL(blob);
+
+      link.setAttribute('href', url);
+      link.setAttribute('download', `能耗数据_${this.selectedYear}年_${new Date().toLocaleDateString()}.csv`);
+      link.style.visibility = 'hidden';
+
+      document.body.appendChild(link);
+      link.click();
+      document.body.removeChild(link);
     },
-    fetchData() {
-      console.log('查询:', this.selectedCategory, this.selectedYear);
-      // 模拟数据更新
-      this.updateChart([40, 32, 29, 23, 7]);
+
+    convertToCSV(data) {
+      const headers = Object.keys(data[0]);
+      const csvHeaders = headers.join(',');
+      const csvRows = data.map(row =>
+          headers.map(header => `"${row[header]}"`).join(',')
+      );
+
+      return `\ufeff${csvHeaders}\n${csvRows.join('\n')}`;
+    },
+
+    getCategoryLabel(category) {
+      const labels = {
+        'all': '全部',
+        'electricity': '电力',
+        'water': '水',
+        'gas': '天然气'
+      };
+      return labels[category] || category;
     },
-    updateChart(data) {
+
+    handleResize() {
       if (this.chartInstance) {
-        this.chartInstance.setOption({
-          series: [{ data: data }]
-        });
+        // 延迟执行resize,确保容器尺寸已更新
+        clearTimeout(this.resizeTimer);
+        this.resizeTimer = setTimeout(() => {
+          this.chartInstance.resize();
+        }, 100);
       }
     }
   }
@@ -132,115 +765,505 @@ export default {
 
 <style scoped>
 .app {
-  background-color: #fff;
+  background-color: #f5f7fa;
   color: #333;
-  font-family: "Segoe UI", sans-serif;
-  padding: 30px;
+  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
+  padding: 24px;
+  min-height: 90vh;
 }
 
-/* 上方两个卡片并列 */
+/* 上方卡片区域 */
 .top-section {
-  display: flex;
-  justify-content: space-between;
-  gap: 30px;
-  margin-bottom: 30px;
+  display: grid;
+  grid-template-columns: 1fr 1fr;
+  gap: 24px;
+  margin-bottom: 24px;
 }
 
 .card {
-  flex: 1;
-  background-color: #f8f9fa;
-  border-radius: 10px;
-  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
-  border: 1px solid #e0e0e0;
-  height: 300px; /* 固定高度 */
-  position: relative; /* 用于绝对定位标题 */
+  background: white;
+  border-radius: 12px;
+  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
+  padding: 24px;
+  transition: all 0.3s ease;
+  position: relative;
+  overflow: hidden;
+}
+
+.card:hover {
+  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.12);
+  transform: translateY(-2px);
+}
+
+.card-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 24px;
 }
 
 .card h3 {
-  margin: 15px;
+  margin: 0;
   font-size: 18px;
   font-weight: 600;
-  color: #212121;
-  position: absolute;
-  top: 15px;
-  left: 15px; /* 标题固定在左上角 */
+  color: #1a1a1a;
+}
+
+.date-info {
+  font-size: 14px;
+  color: #666;
+  background: #f0f2f5;
+  padding: 4px 12px;
+  border-radius: 16px;
 }
 
 .data-container {
-  width: 100%;
-  display: flex;
-  justify-content: center; /* 水平居中 */
-  align-items: center; /* 垂直居中 */
-  height: calc(100% - 40px); /* 减去标题的高度 */
+  margin-bottom: 20px;
 }
 
 .data-row {
   display: flex;
-  justify-content: space-around;
+  justify-content: space-between;
   align-items: center;
-  width: 80%; /* 调整宽度以适应居中 */
 }
 
 .data-item {
   text-align: center;
+  flex: 1;
 }
 
 .value {
-  font-size: 24px;
-  font-weight: bold;
-  color: #000;
+  font-size: 28px;
+  font-weight: 700;
+  color: #1a1a1a;
+  display: inline-block;
 }
 
 .unit {
   font-size: 14px;
   margin-left: 4px;
   color: #666;
+  font-weight: normal;
 }
 
 .label {
-  font-size: 12px;
+  font-size: 13px;
   color: #999;
   display: block;
-  margin-top: 4px;
+  margin-top: 8px;
 }
 
 .trend .value {
-  font-size: 20px;
+  font-size: 24px;
+}
+
+/* 进度条 */
+.progress-bar {
+  height: 6px;
+  background: #e8eaf0;
+  border-radius: 3px;
+  overflow: hidden;
+  margin-top: 20px;
+}
+
+.progress-fill {
+  height: 100%;
+  background: linear-gradient(90deg, #2196f3 0%, #1976d2 100%);
+  border-radius: 3px;
+  transition: width 0.6s ease;
+}
+
+.progress-fill.year {
+  background: linear-gradient(90deg, #4caf50 0%, #388e3c 100%);
 }
 
 /* 筛选区域 */
+.filter-section {
+  background: white;
+  border-radius: 12px;
+  padding: 20px 24px;
+  margin-bottom: 24px;
+  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
+}
+
 .filter-container {
   display: flex;
   align-items: center;
-  margin-bottom: 25px;
-  gap: 15px;
+  gap: 20px;
+  flex-wrap: wrap;
+}
+
+.filter-group {
+  display: flex;
+  align-items: center;
+  gap: 8px;
 }
 
-.filter-container span {
+.filter-group label {
+  font-size: 14px;
+  color: #666;
   font-weight: 500;
-  min-width: 70px;
 }
 
-.filter-container select,
-.filter-container input {
-  padding: 8px 12px;
-  border-radius: 6px;
-  border: 1px solid #ccc;
+.filter-group select {
+  padding: 8px 32px 8px 12px;
+  border: 1px solid #dcdfe6;
+  border-radius: 8px;
+  font-size: 14px;
+  background: white;
+  cursor: pointer;
+  transition: all 0.3s;
+  appearance: none;
+  background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg width='14' height='8' viewBox='0 0 14 8' fill='none' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M1 1L7 7L13 1' stroke='%23909399' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3e%3c/svg%3e");
+  background-repeat: no-repeat;
+  background-position: right 12px center;
+}
+
+.filter-group select:hover {
+  border-color: #2196f3;
+}
+
+.filter-group select:focus {
   outline: none;
+  border-color: #2196f3;
+  box-shadow: 0 0 0 2px rgba(33, 150, 243, 0.1);
+}
+
+/* 查询按钮 */
+.query-btn {
+  padding: 8px 24px;
+  background: #2196f3;
+  color: white;
+  border: none;
+  border-radius: 8px;
   font-size: 14px;
+  font-weight: 500;
+  cursor: pointer;
+  transition: all 0.3s;
+  display: flex;
+  align-items: center;
+  gap: 6px;
+}
+
+.query-btn:hover:not(:disabled) {
+  background: #1976d2;
+  transform: translateY(-1px);
+  box-shadow: 0 4px 12px rgba(33, 150, 243, 0.3);
+}
+
+.query-btn:disabled {
+  background: #b0bec5;
+  cursor: not-allowed;
+  transform: none;
 }
 
-.filter-container button {
-  padding: 8px 16px;
-  background-color: #2196f3;
+.icon-search::before {
+  content: "🔍";
+  font-size: 16px;
+}
+
+.export-btn {
+  margin-left: auto;
+  padding: 8px 20px;
+  background: #4caf50;
   color: white;
   border: none;
+  border-radius: 8px;
+  font-size: 14px;
+  cursor: pointer;
+  transition: all 0.3s;
+  display: flex;
+  align-items: center;
+  gap: 6px;
+}
+
+.export-btn:hover:not(:disabled) {
+  background: #388e3c;
+  transform: translateY(-1px);
+  box-shadow: 0 4px 12px rgba(76, 175, 80, 0.3);
+}
+
+.export-btn:disabled {
+  background: #b0bec5;
+  cursor: not-allowed;
+  transform: none;
+}
+
+.icon-export::before {
+  content: "⬇";
+  font-size: 16px;
+}
+
+.summary-info {
+  margin-top: 16px;
+  padding-top: 16px;
+  border-top: 1px solid #e8eaf0;
+  display: flex;
+  gap: 24px;
+}
+
+.info-item {
+  font-size: 14px;
+  color: #666;
+  display: flex;
+  align-items: center;
+  gap: 6px;
+}
+
+.info-item strong {
+  color: #1a1a1a;
+  font-weight: 600;
+}
+
+.info-item .increase {
+  color: #ff5252;
+}
+
+.info-item .decrease {
+  color: #00c853;
+}
+
+.icon-info::before {
+  content: "ℹ";
+  font-size: 16px;
+  color: #2196f3;
+}
+
+/* 图表区域 */
+.chart-section {
+  background: white;
+  border-radius: 12px;
+  padding: 24px;
+  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
+  min-height: 500px;
+}
+
+.chart-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 20px;
+}
+
+.chart-header h3 {
+  margin: 0;
+  font-size: 18px;
+  font-weight: 600;
+  color: #1a1a1a;
+}
+
+.chart-controls {
+  display: flex;
+  gap: 8px;
+}
+
+.chart-type-btn {
+  padding: 6px 16px;
+  border: 1px solid #dcdfe6;
+  background: white;
   border-radius: 6px;
+  font-size: 14px;
   cursor: pointer;
-  transition: background-color 0.3s ease;
+  transition: all 0.3s;
+}
+
+.chart-type-btn:hover {
+  border-color: #2196f3;
+  color: #2196f3;
+}
+
+.chart-type-btn.active {
+  background: #2196f3;
+  color: white;
+  border-color: #2196f3;
+}
+
+/* 无数据提示 */
+.no-data {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  height: 400px;
+  color: #999;
+}
+
+.no-data .icon-empty::before {
+  content: "📊";
+  font-size: 64px;
+  opacity: 0.3;
+  display: block;
+  margin-bottom: 16px;
+}
+
+.no-data p {
+  font-size: 16px;
+  margin: 0;
+}
+
+/* 图表容器样式 - 关键修复 */
+.chart-container {
+  width: 100%;
+  height: 400px;
+  min-height: 400px;
+  position: relative;
+  background: transparent;
+  /* 确保容器有明确的尺寸 */
+  box-sizing: border-box;
+}
+
+/* 确保图表在显示时有正确的尺寸 */
+.chart-container > div {
+  width: 100% !important;
+  height: 100% !important;
+  position: absolute !important;
+  top: 0 !important;
+  left: 0 !important;
+}
+
+/* 图表容器可见性控制 */
+.chart-container[style*="display: none"] {
+  display: block !important;
+  visibility: hidden;
+}
+
+.chart-container:not([style*="display: none"]) {
+  visibility: visible;
+}
+
+/* 加载状态 */
+.loading-overlay {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: rgba(255, 255, 255, 0.9);
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  z-index: 1000;
+}
+
+.spinner {
+  width: 40px;
+  height: 40px;
+  border: 3px solid #f3f3f3;
+  border-top: 3px solid #2196f3;
+  border-radius: 50%;
+  animation: spin 1s linear infinite;
+}
+
+@keyframes spin {
+  0% { transform: rotate(0deg); }
+  100% { transform: rotate(360deg); }
+}
+
+/* 响应式设计 */
+@media (max-width: 768px) {
+  .top-section {
+    grid-template-columns: 1fr;
+  }
+
+  .filter-container {
+    flex-direction: column;
+    align-items: stretch;
+  }
+
+  .filter-group {
+    width: 100%;
+    justify-content: space-between;
+  }
+
+  .query-btn,
+  .export-btn {
+    margin-left: 0;
+    width: 100%;
+    justify-content: center;
+  }
+
+  .chart-header {
+    flex-direction: column;
+    gap: 16px;
+  }
+
+  .data-item .value {
+    font-size: 24px;
+  }
+
+  .chart-container {
+    height: 300px;
+    min-height: 300px;
+  }
+}
+
+/* 动画效果 */
+@keyframes fadeIn {
+  from {
+    opacity: 0;
+    transform: translateY(10px);
+  }
+  to {
+    opacity: 1;
+    transform: translateY(0);
+  }
+}
+
+.card {
+  animation: fadeIn 0.5s ease-out;
+}
+
+.card:nth-child(2) {
+  animation-delay: 0.1s;
+}
+
+.filter-section {
+  animation: fadeIn 0.5s ease-out 0.2s both;
+}
+
+.chart-section {
+  animation: fadeIn 0.5s ease-out 0.3s both;
+}
+
+/* 数据更新动画 */
+@keyframes pulse {
+  0% {
+    transform: scale(1);
+  }
+  50% {
+    transform: scale(1.05);
+  }
+  100% {
+    transform: scale(1);
+  }
+}
+
+.value {
+  transition: all 0.3s ease;
+}
+
+.value.updating {
+  animation: pulse 0.6s ease;
+}
+
+/* 图表显示优化 */
+.chart-section[data-loading="false"] .chart-container {
+  opacity: 1;
+  transition: opacity 0.3s ease;
+}
+
+.chart-section[data-loading="true"] .chart-container {
+  opacity: 0.5;
+}
+
+/* 强制图表容器正确显示 */
+.chart-container {
+  display: block !important;
+  overflow: hidden;
 }
 
-.filter-container button:hover {
-  background-color: #1976d2;
+/* 确保ECharts容器正确渲染 */
+.chart-container canvas {
+  display: block !important;
 }
 </style>

+ 1098 - 89
pm_ui/src/views/energyManagement/layerManage/index.vue

@@ -32,23 +32,189 @@
         </el-table>
       </div>
     </div>
+
     <!-- 右侧区域 -->
     <div class="main-content">
-      <!-- 总体能耗展示 -->
-      <div class="floor-summary" v-if="selectedNode">
-        <h2>{{ selectedNode.itemName }} 总体能耗</h2>
-        <el-table :data="energyStatistics" border style="width: 100%">
-          <el-table-column prop="category" label="能耗类型"></el-table-column>
-          <el-table-column prop="value" label="数值"></el-table-column>
-          <el-table-column prop="ringRatio" label="环比"></el-table-column>
-          <el-table-column prop="yearOnYearRatio" label="同比"></el-table-column>
-        </el-table>
-        <div class="summary-details">
-          <p>总用电量: {{ electricityUsage }} kWh</p>
-          <p>总用水量: {{ waterUsage }} t</p>
-          <p>综合能耗: {{ totalEnergyConsumption }} kgce</p>
-          <p>峰值用电量: {{ peakElectricity }} kW</p>
-          <p>峰值时间: {{ peakTime }}</p>
+      <!-- 空状态提示 -->
+      <div v-if="!selectedNode" class="empty-state">
+        <el-empty description="请选择左侧节点查看能耗详情">
+          <template #image>
+            <i class="el-icon-data-line" style="font-size: 64px; color: #409eff;"></i>
+          </template>
+        </el-empty>
+      </div>
+
+      <!-- 选中节点的详细信息 -->
+      <div v-else class="content-wrapper">
+        <!-- 头部信息卡片 -->
+        <div class="header-card">
+          <div class="node-info">
+            <h2 class="node-title">
+              <i class="el-icon-office-building"></i>
+              {{ selectedNode.itemName }}
+            </h2>
+            <p class="node-desc">{{ getNodeTypeDesc(selectedNode) }}</p>
+          </div>
+          <div class="quick-stats">
+            <div class="stat-item">
+              <span class="stat-value">{{ electricityUsage }}</span>
+              <span class="stat-label">总用电量 (kWh)</span>
+            </div>
+            <div class="stat-item">
+              <span class="stat-value">{{ waterUsage }}</span>
+              <span class="stat-label">总用水量 (t)</span>
+            </div>
+            <div class="stat-item">
+              <span class="stat-value">{{ totalEnergyConsumption }}</span>
+              <span class="stat-label">综合能耗 (kgce)</span>
+            </div>
+          </div>
+        </div>
+
+        <!-- 时间范围选择器 -->
+        <div class="time-selector">
+          <el-radio-group v-model="timeRange" @change="handleTimeRangeChange">
+            <el-radio-button label="today">今日</el-radio-button>
+            <el-radio-button label="week">本周</el-radio-button>
+            <el-radio-button label="month">本月</el-radio-button>
+            <el-radio-button label="year">本年</el-radio-button>
+          </el-radio-group>
+          <el-date-picker
+              v-model="customDateRange"
+              type="daterange"
+              range-separator="至"
+              start-placeholder="开始日期"
+              end-placeholder="结束日期"
+              format="YYYY-MM-DD"
+              value-format="YYYY-MM-DD"
+              @change="handleCustomDateChange"
+              style="margin-left: 20px;"
+          />
+        </div>
+
+        <!-- 能耗统计表格 -->
+        <div class="energy-table-card">
+          <div class="card-header">
+            <h3>能耗统计详情</h3>
+            <el-button type="primary" size="small" @click="exportData">
+              <i class="el-icon-download"></i>
+              导出数据
+            </el-button>
+          </div>
+          <el-table
+              :data="energyStatistics"
+              border
+              style="width: 100%"
+              :loading="tableLoading"
+              class="energy-table"
+          >
+            <el-table-column prop="category" label="能耗类型" width="120">
+              <template #default="{ row }">
+                <div class="category-cell">
+                  <i :class="getCategoryIcon(row.category)"></i>
+                  {{ row.category }}
+                </div>
+              </template>
+            </el-table-column>
+            <el-table-column prop="value" label="当前数值" width="150">
+              <template #default="{ row }">
+                <span class="value-text">{{ row.value }}</span>
+              </template>
+            </el-table-column>
+            <el-table-column prop="ringRatio" label="环比" width="120">
+              <template #default="{ row }">
+                <span :class="getRatioClass(row.ringRatio)">
+                  <i :class="getRatioIcon(row.ringRatio)"></i>
+                  {{ row.ringRatio }}
+                </span>
+              </template>
+            </el-table-column>
+            <el-table-column prop="yearOnYearRatio" label="同比" width="120">
+              <template #default="{ row }">
+                <span :class="getRatioClass(row.yearOnYearRatio)">
+                  <i :class="getRatioIcon(row.yearOnYearRatio)"></i>
+                  {{ row.yearOnYearRatio }}
+                </span>
+              </template>
+            </el-table-column>
+            <el-table-column prop="trend" label="趋势" width="100">
+              <template #default="{ row }">
+                <el-tag :type="getTrendType(row.trend)" size="small">
+                  {{ row.trend }}
+                </el-tag>
+              </template>
+            </el-table-column>
+            <el-table-column label="操作" width="120">
+              <template #default="{ row }">
+                <el-button type="text" size="small" @click="viewDetails(row)">
+                  查看详情
+                </el-button>
+              </template>
+            </el-table-column>
+          </el-table>
+        </div>
+
+        <!-- 峰值信息卡片 -->
+        <div class="peak-info-card">
+          <h3>峰值信息</h3>
+          <div class="peak-grid">
+            <div class="peak-item">
+              <div class="peak-icon electricity">
+                <i class="el-icon-lightning"></i>
+              </div>
+              <div class="peak-content">
+                <h4>峰值用电量</h4>
+                <p class="peak-value">{{ peakElectricity }} kW</p>
+                <p class="peak-time">时间: {{ peakTime }}</p>
+              </div>
+            </div>
+            <div class="peak-item">
+              <div class="peak-icon water">
+                <i class="el-icon-water-cup"></i>
+              </div>
+              <div class="peak-content">
+                <h4>峰值用水量</h4>
+                <p class="peak-value">{{ peakWater }} t/h</p>
+                <p class="peak-time">时间: {{ peakWaterTime }}</p>
+              </div>
+            </div>
+          </div>
+        </div>
+
+        <!-- 能耗分析图表 -->
+        <!-- 能耗分析图表 -->
+        <div class="chart-card">
+          <div class="card-header">
+            <h3>能耗趋势分析</h3>
+            <el-radio-group v-model="chartType" size="small" @change="updateChart">
+              <el-radio-button label="line">折线图</el-radio-button>
+              <el-radio-button label="bar">柱状图</el-radio-button>
+              <el-radio-button label="pie">饼图</el-radio-button>
+            </el-radio-group>
+          </div>
+          <div class="chart-container" ref="chartContainer" id="energyChart"></div>
+        </div>
+
+        <!-- 告警信息 -->
+        <div class="alert-card" v-if="alerts.length > 0">
+          <h3>能耗告警</h3>
+          <div class="alert-list">
+            <div
+                v-for="alert in alerts"
+                :key="alert.id"
+                class="alert-item"
+                :class="alert.level"
+            >
+              <i :class="getAlertIcon(alert.level)"></i>
+              <div class="alert-content">
+                <p class="alert-message">{{ alert.message }}</p>
+                <p class="alert-time">{{ alert.time }}</p>
+              </div>
+              <el-button type="text" size="small" @click="handleAlert(alert)">
+                处理
+              </el-button>
+            </div>
+          </div>
         </div>
       </div>
     </div>
@@ -56,14 +222,23 @@
 </template>
 
 <script setup>
-import { ref, onMounted } from 'vue';
-import { listTopology } from '@/api/device/topology'; // 假设你有这些接口
-import { ElMessage } from 'element-plus';
+import { listTopology } from '@/api/device/topology';
+import { ElMessage, ElMessageBox } from 'element-plus';
+import { ref, onMounted, nextTick, onUnmounted } from 'vue';
+import * as echarts from 'echarts';
 
 // 数据定义
 const topologyList = ref([]);
 const loading = ref(true);
 const selectedNode = ref(null);
+const tableLoading = ref(false);
+
+// 时间范围
+const timeRange = ref('today');
+const customDateRange = ref([]);
+
+// 图表类型
+const chartType = ref('line');
 
 // 总体能耗数据
 const energyStatistics = ref([]);
@@ -72,6 +247,409 @@ const peakElectricity = ref('');
 const peakTime = ref('');
 const waterUsage = ref('');
 const totalEnergyConsumption = ref('');
+const peakWater = ref('');
+const peakWaterTime = ref('');
+
+// 告警数据
+const alerts = ref([]);
+
+const chartContainer = ref(null);
+let chartInstance = null;
+
+// 模拟图表数据
+const chartData = ref({
+  dates: ['01-01', '01-02', '01-03', '01-04', '01-05', '01-06', '01-07'],
+  electricity: [120, 132, 101, 134, 90, 230, 210],
+  water: [20, 25, 18, 30, 22, 35, 28],
+  gas: [15, 18, 12, 22, 16, 28, 24],
+  comprehensive: [155, 175, 131, 186, 128, 293, 262]
+});
+
+// 图表配置
+const getLineChartOption = () => ({
+  title: {
+    text: '能耗趋势分析',
+    left: 'center',
+    textStyle: {
+      fontSize: 16,
+      color: '#333'
+    }
+  },
+  tooltip: {
+    trigger: 'axis',
+    axisPointer: {
+      type: 'cross'
+    }
+  },
+  legend: {
+    data: ['用电量', '用水量', '燃气量', '综合能耗'],
+    top: 30
+  },
+  grid: {
+    left: '3%',
+    right: '4%',
+    bottom: '3%',
+    top: '15%',
+    containLabel: true
+  },
+  xAxis: {
+    type: 'category',
+    boundaryGap: false,
+    data: chartData.value.dates
+  },
+  yAxis: {
+    type: 'value',
+    name: '能耗值'
+  },
+  series: [
+    {
+      name: '用电量',
+      type: 'line',
+      stack: false,
+      smooth: true,
+      data: chartData.value.electricity,
+      itemStyle: {
+        color: '#409EFF'
+      },
+      areaStyle: {
+        opacity: 0.3,
+        color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+          { offset: 0, color: '#409EFF' },
+          { offset: 1, color: 'rgba(64, 158, 255, 0.1)' }
+        ])
+      }
+    },
+    {
+      name: '用水量',
+      type: 'line',
+      stack: false,
+      smooth: true,
+      data: chartData.value.water,
+      itemStyle: {
+        color: '#67C23A'
+      },
+      areaStyle: {
+        opacity: 0.3,
+        color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+          { offset: 0, color: '#67C23A' },
+          { offset: 1, color: 'rgba(103, 194, 58, 0.1)' }
+        ])
+      }
+    },
+    {
+      name: '燃气量',
+      type: 'line',
+      stack: false,
+      smooth: true,
+      data: chartData.value.gas,
+      itemStyle: {
+        color: '#E6A23C'
+      },
+      areaStyle: {
+        opacity: 0.3,
+        color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+          { offset: 0, color: '#E6A23C' },
+          { offset: 1, color: 'rgba(230, 162, 60, 0.1)' }
+        ])
+      }
+    },
+    {
+      name: '综合能耗',
+      type: 'line',
+      stack: false,
+      smooth: true,
+      data: chartData.value.comprehensive,
+      itemStyle: {
+        color: '#F56C6C'
+      },
+      lineStyle: {
+        width: 3
+      }
+    }
+  ]
+});
+
+const getBarChartOption = () => ({
+  title: {
+    text: '能耗对比分析',
+    left: 'center',
+    textStyle: {
+      fontSize: 16,
+      color: '#333'
+    }
+  },
+  tooltip: {
+    trigger: 'axis',
+    axisPointer: {
+      type: 'shadow'
+    }
+  },
+  legend: {
+    data: ['用电量', '用水量', '燃气量'],
+    top: 30
+  },
+  grid: {
+    left: '3%',
+    right: '4%',
+    bottom: '3%',
+    top: '15%',
+    containLabel: true
+  },
+  xAxis: {
+    type: 'category',
+    data: chartData.value.dates
+  },
+  yAxis: {
+    type: 'value',
+    name: '能耗值'
+  },
+  series: [
+    {
+      name: '用电量',
+      type: 'bar',
+      data: chartData.value.electricity,
+      itemStyle: {
+        color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+          { offset: 0, color: '#409EFF' },
+          { offset: 1, color: '#79bbff' }
+        ])
+      }
+    },
+    {
+      name: '用水量',
+      type: 'bar',
+      data: chartData.value.water,
+      itemStyle: {
+        color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+          { offset: 0, color: '#67C23A' },
+          { offset: 1, color: '#95d475' }
+        ])
+      }
+    },
+    {
+      name: '燃气量',
+      type: 'bar',
+      data: chartData.value.gas,
+      itemStyle: {
+        color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+          { offset: 0, color: '#E6A23C' },
+          { offset: 1, color: '#eebe77' }
+        ])
+      }
+    }
+  ]
+});
+
+const getPieChartOption = () => {
+  const totalElectricity = chartData.value.electricity.reduce((a, b) => a + b, 0);
+  const totalWater = chartData.value.water.reduce((a, b) => a + b, 0);
+  const totalGas = chartData.value.gas.reduce((a, b) => a + b, 0);
+
+  return {
+    title: {
+      text: '能耗占比分析',
+      left: 'center',
+      textStyle: {
+        fontSize: 16,
+        color: '#333'
+      }
+    },
+    tooltip: {
+      trigger: 'item',
+      formatter: '{a} <br/>{b}: {c} ({d}%)'
+    },
+    legend: {
+      orient: 'vertical',
+      left: 'left',
+      top: 'middle'
+    },
+    series: [
+      {
+        name: '能耗占比',
+        type: 'pie',
+        radius: ['40%', '70%'],
+        center: ['60%', '50%'],
+        avoidLabelOverlap: false,
+        itemStyle: {
+          borderRadius: 10,
+          borderColor: '#fff',
+          borderWidth: 2
+        },
+        label: {
+          show: false,
+          position: 'center'
+        },
+        emphasis: {
+          label: {
+            show: true,
+            fontSize: '18',
+            fontWeight: 'bold'
+          }
+        },
+        labelLine: {
+          show: false
+        },
+        data: [
+          {
+            value: totalElectricity,
+            name: '用电量',
+            itemStyle: { color: '#409EFF' }
+          },
+          {
+            value: totalWater,
+            name: '用水量',
+            itemStyle: { color: '#67C23A' }
+          },
+          {
+            value: totalGas,
+            name: '燃气量',
+            itemStyle: { color: '#E6A23C' }
+          }
+        ]
+      }
+    ]
+  };
+};
+
+// 初始化图表
+const initChart = async () => {
+  await nextTick();
+  if (chartContainer.value) {
+    chartInstance = echarts.init(chartContainer.value);
+    updateChart();
+
+    // 监听窗口大小变化
+    window.addEventListener('resize', () => {
+      chartInstance?.resize();
+    });
+  }
+};
+
+// 更新图表
+const updateChart = () => {
+  if (!chartInstance) return;
+
+  let option;
+  switch (chartType.value) {
+    case 'line':
+      option = getLineChartOption();
+      break;
+    case 'bar':
+      option = getBarChartOption();
+      break;
+    case 'pie':
+      option = getPieChartOption();
+      break;
+    default:
+      option = getLineChartOption();
+  }
+
+  chartInstance.setOption(option, true);
+};
+
+// 生成随机数据(模拟实时更新)
+const generateRandomData = () => {
+  chartData.value = {
+    dates: Array.from({ length: 7 }, (_, i) => {
+      const date = new Date();
+      date.setDate(date.getDate() - 6 + i);
+      return `${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`;
+    }),
+    electricity: Array.from({ length: 7 }, () => Math.floor(Math.random() * 200) + 100),
+    water: Array.from({ length: 7 }, () => Math.floor(Math.random() * 30) + 15),
+    gas: Array.from({ length: 7 }, () => Math.floor(Math.random() * 25) + 10),
+    comprehensive: []
+  };
+
+  // 计算综合能耗
+  chartData.value.comprehensive = chartData.value.electricity.map((elec, index) =>
+      elec + chartData.value.water[index] * 2 + chartData.value.gas[index] * 1.5
+  );
+};
+
+// 修改原有的 loadFloorEnergyData 函数
+const loadFloorEnergyData = () => {
+  tableLoading.value = true;
+
+  // 生成新的图表数据
+  generateRandomData();
+
+  // 模拟异步加载
+  setTimeout(() => {
+    // 原有的表格数据生成代码...
+    energyStatistics.value = [
+      {
+        category: '用电量',
+        value: `${(Math.random() * 1000 + 1000).toFixed(2)} kWh`,
+        ringRatio: `${(Math.random() * 10).toFixed(2)}%↑`,
+        yearOnYearRatio: `${(Math.random() * 15 + 100).toFixed(2)}%↑`,
+        trend: '上升'
+      },
+      {
+        category: '用水量',
+        value: `${(Math.random() * 50).toFixed(2)} t`,
+        ringRatio: `${(Math.random() * 10).toFixed(2)}%↓`,
+        yearOnYearRatio: `${(Math.random() * 15 + 100).toFixed(2)}%↑`,
+        trend: '稳定'
+      },
+      {
+        category: '综合能耗',
+        value: `${(Math.random() * 300).toFixed(2)} kgce`,
+        ringRatio: `${(Math.random() * 10).toFixed(2)}%↑`,
+        yearOnYearRatio: `${(Math.random() * 15 + 100).toFixed(2)}%↑`,
+        trend: '下降'
+      },
+      {
+        category: '燃气用量',
+        value: `${(Math.random() * 100).toFixed(2)} m³`,
+        ringRatio: `${(Math.random() * 10).toFixed(2)}%↓`,
+        yearOnYearRatio: `${(Math.random() * 15 + 100).toFixed(2)}%↓`,
+        trend: '稳定'
+      }
+    ];
+
+    electricityUsage.value = (Math.random() * 1000 + 1000).toFixed(2);
+    peakElectricity.value = (Math.random() * 100 + 200).toFixed(2);
+    peakTime.value = `${Math.floor(Math.random() * 12) + 1}-${Math.floor(Math.random() * 30) + 1} 14:30`;
+    waterUsage.value = (Math.random() * 50).toFixed(2);
+    totalEnergyConsumption.value = (Math.random() * 300).toFixed(2);
+    peakWater.value = (Math.random() * 10 + 5).toFixed(2);
+    peakWaterTime.value = `${Math.floor(Math.random() * 12) + 1}-${Math.floor(Math.random() * 30) + 1} 10:15`;
+
+    tableLoading.value = false;
+
+    // 更新图表
+    if (chartInstance) {
+      updateChart();
+    }
+  }, 500);
+};
+
+// 修改点击节点函数
+const handleNodeClick = (node) => {
+  selectedNode.value = node;
+  loadFloorEnergyData();
+  loadAlerts();
+
+  // 初始化图表(如果还没初始化)
+  if (!chartInstance) {
+    setTimeout(() => {
+      initChart();
+    }, 100);
+  }
+};
+
+// 组件卸载时清理
+onUnmounted(() => {
+  if (chartInstance) {
+    chartInstance.dispose();
+    chartInstance = null;
+  }
+  window.removeEventListener('resize', () => {
+    chartInstance?.resize();
+  });
+});
+
 
 // 获取树形数据
 const getList = async () => {
@@ -107,41 +685,140 @@ const handleTree = (data, id, parentId, childrenName = 'children') => {
   return tree;
 };
 
-// 点击节点
-const handleNodeClick = (node) => {
-  selectedNode.value = node;
+// 加载楼层总体能耗数据
+// 加载告警数据
+const loadAlerts = () => {
+  const alertTypes = ['warning', 'error', 'info'];
+  const alertMessages = [
+    '用电量超过预设阈值',
+    '用水量异常增长',
+    '设备运行异常',
+    '能耗效率偏低'
+  ];
+
+  alerts.value = Array.from({ length: Math.floor(Math.random() * 3) + 1 },
+      (_, index) => ({
+        id: index + 1,
+        level: alertTypes[Math.floor(Math.random() * alertTypes.length)],
+        message: alertMessages[Math.floor(Math.random() * alertMessages.length)],
+        time: `${new Date().getFullYear()}-${String(new Date().getMonth() + 1).padStart(2, '0')}-${String(new Date().getDate()).padStart(2, '0')} ${String(Math.floor(Math.random() * 24)).padStart(2, '0')}:${String(Math.floor(Math.random() * 60)).padStart(2, '0')}`
+      }));
+};
+
+// 获取节点类型描述
+const getNodeTypeDesc = (node) => {
+  if (node.children && node.children.length > 0) {
+    return `包含 ${node.children.length} 个子节点`;
+  }
+  return '终端节点';
+};
+
+// 获取分类图标
+const getCategoryIcon = (category) => {
+  const iconMap = {
+    '用电量': 'el-icon-lightning',
+    '用水量': 'el-icon-water-cup',
+    '综合能耗': 'el-icon-data-analysis',
+    '燃气用量': 'el-icon-hot-water'
+  };
+  return iconMap[category] || 'el-icon-data-line';
+};
+
+// 获取比率样式类
+const getRatioClass = (ratio) => {
+  if (ratio.includes('↑')) return 'ratio-up';
+  if (ratio.includes('↓')) return 'ratio-down';
+  return 'ratio-stable';
+};
+
+// 获取比率图标
+const getRatioIcon = (ratio) => {
+  if (ratio.includes('↑')) return 'el-icon-top';
+  if (ratio.includes('↓')) return 'el-icon-bottom';
+  return 'el-icon-minus';
+};
+
+// 获取趋势标签类型
+const getTrendType = (trend) => {
+  const typeMap = {
+    '上升': 'danger',
+    '下降': 'success',
+    '稳定': 'info'
+  };
+  return typeMap[trend] || 'info';
+};
+
+// 获取告警图标
+const getAlertIcon = (level) => {
+  const iconMap = {
+    'error': 'el-icon-error',
+    'warning': 'el-icon-warning',
+    'info': 'el-icon-info'
+  };
+  return iconMap[level] || 'el-icon-info';
+};
+
+// 时间范围变化处理
+const handleTimeRangeChange = (value) => {
+  customDateRange.value = [];
   loadFloorEnergyData();
+  ElMessage.success(`已切换到${getTimeRangeText(value)}数据`);
 };
 
-// 加载楼层总体能耗数据
-const loadFloorEnergyData = () => {
-  // 模拟数据 - 实际项目中应该调用API获取
-  energyStatistics.value = [
-    {
-      category: '用电量',
-      value: `${(Math.random() * 1000 + 1000).toFixed(2)} kWh`,
-      ringRatio: `${(Math.random() * 10).toFixed(2)}↑`,
-      yearOnYearRatio: `${(Math.random() * 15 + 100).toFixed(2)}↑`
-    },
-    {
-      category: '用水量',
-      value: `${(Math.random() * 50).toFixed(2)} t`,
-      ringRatio: `${(Math.random() * 10).toFixed(2)}↑`,
-      yearOnYearRatio: `${(Math.random() * 15 + 100).toFixed(2)}↑`
-    },
-    {
-      category: '综合能耗',
-      value: `${(Math.random() * 300).toFixed(2)} kgce`,
-      ringRatio: `${(Math.random() * 10).toFixed(2)}↑`,
-      yearOnYearRatio: `${(Math.random() * 15 + 100).toFixed(2)}↑`
-    }
-  ];
-  electricityUsage.value = (Math.random() * 1000 + 1000).toFixed(2);
-  peakElectricity.value = (Math.random() * 100 + 200).toFixed(2);
-  peakTime.value = `${Math.floor(Math.random() * 12) + 1}-${Math.floor(Math.random() * 30) + 1}`;
-  // 新增用水量和综合能耗数据
-  waterUsage.value = (Math.random() * 50).toFixed(2);
-  totalEnergyConsumption.value = (Math.random() * 300).toFixed(2);
+// 自定义日期范围变化处理
+const handleCustomDateChange = (dates) => {
+  if (dates && dates.length === 2) {
+    timeRange.value = '';
+    loadFloorEnergyData();
+    ElMessage.success(`已切换到自定义时间范围: ${dates[0]} 至 ${dates[1]}`);
+  }
+};
+
+// 获取时间范围文本
+const getTimeRangeText = (range) => {
+  const textMap = {
+    'today': '今日',
+    'week': '本周',
+    'month': '本月',
+    'year': '本年'
+  };
+  return textMap[range] || '自定义';
+};
+
+// 查看详情
+const viewDetails = (row) => {
+  ElMessageBox.alert(
+      `${row.category}的详细信息:\n当前值: ${row.value}\n环比: ${row.ringRatio}\n同比: ${row.yearOnYearRatio}\n趋势: ${row.trend}`,
+      '详细信息',
+      {
+        confirmButtonText: '确定',
+        type: 'info'
+      }
+  );
+};
+
+// 导出数据
+const exportData = () => {
+  ElMessage.success('数据导出功能开发中...');
+  // 这里可以实现真实的导出逻辑
+};
+
+// 处理告警
+const handleAlert = (alert) => {
+  ElMessageBox.confirm(
+      `确定要处理这个${alert.level}级别的告警吗?`,
+      '处理告警',
+      {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }
+  ).then(() => {
+    alerts.value = alerts.value.filter(item => item.id !== alert.id);
+    ElMessage.success('告警已处理');
+  }).catch(() => {
+    ElMessage.info('已取消处理');
+  });
 };
 
 // 页面初始化时加载数据
@@ -151,87 +828,419 @@ onMounted(() => {
 </script>
 
 <style scoped>
-/* 左侧树形表格高亮样式 */
+/* 原有样式保持不变 */
 :deep(.custom-table .el-table__body tr.current-row > td) {
-  background-color: #a6b9dd !important; /* 可以替换为您想要的颜色 */
-  border-radius: 12px !important;  /* 添加圆角效果 */
-  margin: 8px 0 !important;      /* 添加间距使圆角更明显 */
+  background-color: #a6b9dd !important;
+  border-radius: 12px !important;
+  margin: 8px 0 !important;
 }
-/* 如果需要同时修改文字颜色 */
+
 :deep(.custom-table .el-table__body tr.current-row > td .cell) {
   color: #fff !important;
 }
+
 .custom-table :deep(.el-table__row) {
   background: transparent !important;
 }
+
 .custom-table :deep(.el-table__cell) {
-  padding: 6px 0 !important;  /* 减小上下内边距 */
+  padding: 6px 0 !important;
   border: none !important;
 }
+
 .custom-table :deep(.el-table__cell .cell) {
   line-height: 15px !important;
   height: 15px !important;
 }
+
 .custom-table :deep(.el-table__inner-wrapper::before) {
   display: none;
 }
+
 .tree-container {
-  height: 90vh; /* 调整高度计算方式 */
+  height: 90vh;
   overflow-y: auto;
   background-color: white;
   color: #000000;
 }
+
+.app {
+  display: flex;
+  height: 90vh;
+}
+
+.sidebar {
+  width: 300px;
+  height: 88%;
+  overflow: auto;
+  background-color: white;
+  border-right: 1px solid #e6e6e6;
+}
+
 .main-content {
   flex: 1;
   padding: 20px;
-  background-color: #fff;
-  color: #333;
-  overflow-y: hidden;
+  overflow-y: auto;
+  background-color: #f5f7fa;
 }
-.floor-summary {
-  margin-bottom: 20px;
-  padding: 15px;
-  background-color: white;
-  border-radius: 8px;
-  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
+
+/* 新增样式 */
+.empty-state {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  height: 60vh;
+}
+
+.content-wrapper {
+  display: flex;
+  flex-direction: column;
+  gap: 20px;
 }
-.summary-details {
-  margin-top: 10px;
+
+.header-card {
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+  border-radius: 12px;
+  padding: 24px;
+  color: white;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
+}
+
+.node-info .node-title {
+  margin: 0 0 8px 0;
+  font-size: 24px;
+  font-weight: 600;
+  display: flex;
+  align-items: center;
+  gap: 12px;
+}
+
+.node-info .node-desc {
+  margin: 0;
+  opacity: 0.8;
   font-size: 14px;
-  color: #555;
 }
-.el-table {
-  margin-top: 20px;
+
+.quick-stats {
+  display: flex;
+  gap: 32px;
+}
+
+.stat-item {
+  text-align: center;
+}
+
+.stat-value {
+  display: block;
+  font-size: 28px;
+  font-weight: bold;
+  margin-bottom: 4px;
+}
+
+.stat-label {
+  font-size: 12px;
+  opacity: 0.8;
+}
+
+.time-selector {
+  background: white;
+  padding: 16px 20px;
+  border-radius: 8px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+  display: flex;
+  align-items: center;
+}
+
+.energy-table-card,
+.peak-info-card,
+.chart-card,
+.alert-card {
+  background: white;
+  border-radius: 8px;
+  padding: 20px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+}
+
+.card-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 16px;
+  padding-bottom: 12px;
+  border-bottom: 1px solid #ebeef5;
+}
+
+.card-header h3 {
+  margin: 0;
+  color: #303133;
+  font-size: 18px;
+  font-weight: 600;
+}
+
+.energy-table {
+  margin-top: 0;
+}
+
+.category-cell {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+
+.value-text {
+  font-weight: 600;
+  color: #409eff;
+}
+
+.ratio-up {
+  color: #f56c6c;
+}
+
+.ratio-down {
+  color: #67c23a;
+}
+
+.ratio-stable {
+  color: #909399;
+}
+
+.peak-grid {
+  display: grid;
+  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
+  gap: 20px;
+}
+
+.peak-item {
+  display: flex;
+  align-items: center;
+  gap: 16px;
+  padding: 16px;
+  background: #f8f9fa;
+  border-radius: 8px;
+  border-left: 4px solid #409eff;
 }
-.el-table th {
-  background-color: #2196f3;
+
+.peak-icon {
+  width: 48px;
+  height: 48px;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 20px;
   color: white;
 }
 
-.app {
+.peak-icon.electricity {
+  background: linear-gradient(45deg, #ffd700, #ffed4e);
+  color: #333;
+}
+
+.peak-icon.water {
+  background: linear-gradient(45deg, #4facfe, #00f2fe);
+}
+
+.peak-content h4 {
+  margin: 0 0 8px 0;
+  color: #303133;
+  font-size: 16px;
+}
+
+.peak-value {
+  margin: 0 0 4px 0;
+  font-size: 20px;
+  font-weight: bold;
+  color: #409eff;
+}
+
+.peak-time {
+  margin: 0;
+  font-size: 12px;
+  color: #909399;
+}
+
+.chart-container {
+  height: 300px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background: #fafafa;
+  border-radius: 8px;
+  border: 2px dashed #ddd;
+}
+
+.chart-placeholder {
+  text-align: center;
+  color: #999;
+}
+
+.alert-list {
   display: flex;
+  flex-direction: column;
+  gap: 12px;
 }
 
-.sidebar {
-  width: 300px;
-  height: 100%;
-  overflow: auto;
-  background-color: white;
-  border-right: 1px solid #e6e6e6;
+.alert-item {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+  padding: 12px 16px;
+  border-radius: 8px;
+  border-left: 4px solid;
 }
 
-.main-content {
+.alert-item.error {
+  background: #fef0f0;
+  border-left-color: #f56c6c;
+}
+
+.alert-item.warning {
+  background: #fdf6ec;
+  border-left-color: #e6a23c;
+}
+
+.alert-item.info {
+  background: #f4f4f5;
+  border-left-color: #909399;
+}
+
+.alert-content {
   flex: 1;
-  padding: 20px;
-  overflow-y: auto;
-  background-color: #fff;
 }
 
-.floor-summary {
-  margin-left: 20px;
-  padding: 15px;
-  background-color: white;
+.alert-message {
+  margin: 0 0 4px 0;
+  font-weight: 500;
+  color: #303133;
+}
+
+.alert-time {
+  margin: 0;
+  font-size: 12px;
+  color: #909399;
+}
+
+/* 响应式设计 */
+@media (max-width: 768px) {
+  .app {
+    flex-direction: column;
+  }
+
+  .sidebar {
+    width: 100%;
+    height: 300px;
+  }
+
+  .header-card {
+    flex-direction: column;
+    gap: 20px;
+    text-align: center;
+  }
+
+  .quick-stats {
+    justify-content: center;
+  }
+
+  .time-selector {
+    flex-direction: column;
+    gap: 12px;
+    align-items: stretch;
+  }
+
+  .peak-grid {
+    grid-template-columns: 1fr;
+  }
+}
+
+/* 动画效果 */
+.content-wrapper > * {
+  animation: fadeInUp 0.3s ease-out;
+}
+
+@keyframes fadeInUp {
+  from {
+    opacity: 0;
+    transform: translateY(20px);
+  }
+  to {
+    opacity: 1;
+    transform: translateY(0);
+  }
+}
+
+/* 滚动条样式 */
+.main-content::-webkit-scrollbar {
+  width: 6px;
+}
+
+.main-content::-webkit-scrollbar-track {
+  background: #f1f1f1;
+  border-radius: 3px;
+}
+
+.main-content::-webkit-scrollbar-thumb {
+  background: #c1c1c1;
+  border-radius: 3px;
+}
+
+.main-content::-webkit-scrollbar-thumb:hover {
+  background: #a8a8a8;
+}
+
+.chart-container {
+  height: 400px;
+  width: 100%;
+  background: white;
   border-radius: 8px;
-  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
+  border: none;
+}
+
+/* 移除原来的占位符样式 */
+.chart-placeholder {
+  display: none;
+}
+
+/* 图表加载状态 */
+.chart-loading {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  height: 100%;
+  color: #999;
+}
+
+/* 图表工具栏样式优化 */
+.card-header .el-radio-group {
+  background: #f5f7fa;
+  border-radius: 6px;
+  padding: 2px;
+}
+
+.card-header .el-radio-button__inner {
+  border: none;
+  background: transparent;
+  padding: 8px 16px;
+  font-size: 12px;
+}
+
+.card-header .el-radio-button__orig-radio:checked + .el-radio-button__inner {
+  background: white;
+  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+}
+
+/* 响应式图表 */
+@media (max-width: 768px) {
+  .chart-container {
+    height: 300px;
+  }
+
+  .card-header {
+    flex-direction: column;
+    gap: 12px;
+    align-items: flex-start;
+  }
 }
 </style>

File diff suppressed because it is too large
+ 891 - 86
pm_ui/src/views/energyManagement/layerManage/index2.vue


+ 742 - 63
pm_ui/src/views/energyManagement/quotaManagement/index.vue

@@ -32,44 +32,213 @@
         </el-table>
       </div>
     </div>
+
     <!-- 右侧区域:能耗进度条展示 -->
     <div class="main-content">
-      <!-- 选择能源类型 -->
-      <div class="query-section">
-        <el-select
-            v-model="selectedEnergyType"
-            placeholder="请选择能源类型"
-            style="width: 200px"
-        >
-          <el-option label="用电量 (kWh)" value="electricity" />
-          <el-option label="用水量 (t)" value="water" />
-          <el-option label="用气量 (m³)" value="gas" />
-        </el-select>
-        <el-button
-            type="primary"
-            @click="fetchEnergyData"
-        >
-          查询
-        </el-button>
+      <!-- 顶部标题栏 -->
+      <div class="content-header">
+        <h1 class="page-title">
+          <i class="el-icon-data-analysis"></i>
+          能耗监控系统
+          <div class="breadcrumb" v-if="selectedNodeName">
+            <span>当前选择:</span>
+            <el-tag type="primary" size="large">{{ selectedNodeName }}</el-tag>
+          </div>
+        </h1>
+
       </div>
-      <h2 v-if="selectedNodeName">{{ selectedNodeName }} 能耗使用情况</h2>
-
-      <div class="progress-card" v-if="energyData">
-        <div class="progress-header">
-          <span>总限值:{{ energyData.limit }} {{ unit }}</span>
-          <span>已使用:{{ energyData.used }} {{ unit }}</span>
-          <span>剩余:{{ energyData.remaining }} {{ unit }}</span>
-          <span>使用率:{{ energyData.percentage }}%</span>
+
+      <!-- 查询控制面板 -->
+      <div class="control-panel">
+        <div class="query-section">
+          <div class="query-item">
+            <label>能源类型:</label>
+            <el-select
+                v-model="selectedEnergyType"
+                placeholder="请选择能源类型"
+                size="large"
+                style="width: 200px"
+            >
+              <el-option label="用电量 (kWh)" value="electricity">
+                <i class="el-icon-lightning" style="margin-right: 8px;"></i>
+                用电量 (kWh)
+              </el-option>
+              <el-option label="用水量 (t)" value="water">
+                <i class="el-icon-water-cup" style="margin-right: 8px;"></i>
+                用水量 (t)
+              </el-option>
+              <el-option label="用气量 (m³)" value="gas">
+                <i class="el-icon-wind-power" style="margin-right: 8px;"></i>
+                用气量 (m³)
+              </el-option>
+            </el-select>
+          </div>
+
+          <div class="query-item">
+            <label>时间范围:</label>
+            <el-select v-model="timeRange" size="large" style="width: 150px">
+              <el-option label="今日" value="today" />
+              <el-option label="本周" value="week" />
+              <el-option label="本月" value="month" />
+              <el-option label="本年" value="year" />
+            </el-select>
+          </div>
+
+          <el-button
+              type="primary"
+              size="large"
+              @click="fetchEnergyData"
+              :loading="queryLoading"
+              icon="el-icon-search"
+          >
+            查询数据
+          </el-button>
         </div>
-        <el-progress
-            :percentage="energyData.percentage"
-            :status="progressStatus"
-            :stroke-width="20"
-        ></el-progress>
       </div>
 
-      <div class="tip" v-else>
-        请选择左侧区域和能源类型后点击“查询”按钮
+      <!-- 数据展示区域 -->
+      <div class="data-display" v-if="energyData">
+        <!-- 统计卡片 -->
+        <div class="stats-cards">
+          <div class="stat-card limit-card">
+            <div class="card-icon">
+              <i class="el-icon-aim"></i>
+            </div>
+            <div class="card-content">
+              <div class="card-title">总限值</div>
+              <div class="card-value">{{ energyData.limit }} <span class="unit">{{ unit }}</span></div>
+            </div>
+          </div>
+
+          <div class="stat-card used-card">
+            <div class="card-icon">
+              <i class="el-icon-pie-chart"></i>
+            </div>
+            <div class="card-content">
+              <div class="card-title">已使用</div>
+              <div class="card-value">{{ energyData.used }} <span class="unit">{{ unit }}</span></div>
+            </div>
+          </div>
+
+          <div class="stat-card remaining-card">
+            <div class="card-icon">
+              <i class="el-icon-coin"></i>
+            </div>
+            <div class="card-content">
+              <div class="card-title">剩余量</div>
+              <div class="card-value">{{ energyData.remaining }} <span class="unit">{{ unit }}</span></div>
+            </div>
+          </div>
+
+          <div class="stat-card percentage-card" :class="{ 'warning': energyData.percentage >= 80, 'danger': energyData.percentage >= 95 }">
+            <div class="card-icon">
+              <i class="el-icon-data-line"></i>
+            </div>
+            <div class="card-content">
+              <div class="card-title">使用率</div>
+              <div class="card-value">{{ energyData.percentage }}%</div>
+            </div>
+          </div>
+        </div>
+
+        <!-- 进度条展示 -->
+        <div class="progress-section">
+          <div class="section-title">
+            <h3>使用情况详情</h3>
+            <div class="progress-legend">
+              <span class="legend-item normal">
+                <span class="legend-color normal"></span>
+                正常 (0-79%)
+              </span>
+              <span class="legend-item warning">
+                <span class="legend-color warning"></span>
+                警告 (80-94%)
+              </span>
+              <span class="legend-item danger">
+                <span class="legend-color danger"></span>
+                超限 (95%+)
+              </span>
+            </div>
+          </div>
+
+          <div class="progress-card">
+            <div class="progress-info">
+              <div class="progress-text">
+                <span class="current-usage">当前使用:{{ energyData.used }} {{ unit }}</span>
+                <span class="usage-rate" :class="getUsageRateClass()">{{ energyData.percentage }}%</span>
+              </div>
+              <div class="progress-bar-container">
+                <el-progress
+                    :percentage="energyData.percentage"
+                    :status="progressStatus"
+                    :stroke-width="24"
+                    :show-text="false"
+                    class="custom-progress"
+                ></el-progress>
+              </div>
+              <div class="progress-marks">
+                <span class="mark start">0</span>
+                <span class="mark warning">80%</span>
+                <span class="mark danger">95%</span>
+                <span class="mark end">{{ energyData.limit }} {{ unit }}</span>
+              </div>
+            </div>
+          </div>
+        </div>
+
+        <!-- 趋势分析 -->
+        <div class="trend-section">
+          <div class="section-title">
+            <h3>使用趋势分析</h3>
+          </div>
+          <div class="trend-cards">
+            <div class="trend-card">
+              <div class="trend-header">
+                <span class="trend-title">日均使用量</span>
+                <i class="el-icon-trend-charts"></i>
+              </div>
+              <div class="trend-value">{{ dailyAverage }} {{ unit }}</div>
+              <div class="trend-change positive">
+                <i class="el-icon-top"></i>
+                较昨日 +5.2%
+              </div>
+            </div>
+
+            <div class="trend-card">
+              <div class="trend-header">
+                <span class="trend-title">预计剩余天数</span>
+                <i class="el-icon-time"></i>
+              </div>
+              <div class="trend-value">{{ remainingDays }} 天</div>
+              <div class="trend-change negative">
+                <i class="el-icon-bottom"></i>
+                较上周 -2天
+              </div>
+            </div>
+
+            <div class="trend-card">
+              <div class="trend-header">
+                <span class="trend-title">效率评级</span>
+                <i class="el-icon-medal"></i>
+              </div>
+              <div class="trend-value">{{ efficiencyGrade }}</div>
+              <div class="trend-change">
+                <el-rate v-model="efficiencyRating" disabled show-score text-color="#ff9900" score-template="{value}"></el-rate>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+
+      <!-- 空状态 -->
+      <div class="empty-state" v-else>
+        <div class="empty-icon">
+          <i class="el-icon-data-board"></i>
+        </div>
+        <div class="empty-text">
+          <h3>暂无数据</h3>
+          <p>请选择左侧区域节点和能源类型后点击"查询数据"按钮</p>
+        </div>
       </div>
     </div>
   </div>
@@ -82,9 +251,11 @@ import { ElMessage } from 'element-plus';
 
 const topologyList = ref([]);
 const loading = ref(true);
+const queryLoading = ref(false);
 const selectedNode = ref(null);
 const selectedNodeName = computed(() => selectedNode.value ? selectedNode.value.itemName : '');
 const selectedEnergyType = ref('electricity');
+const timeRange = ref('today');
 const unit = computed(() => {
   switch (selectedEnergyType.value) {
     case 'electricity':
@@ -99,11 +270,49 @@ const unit = computed(() => {
 });
 const energyData = ref(null);
 
+// 计算属性
 const progressStatus = computed(() => {
   if (!energyData.value) return '';
-  return energyData.value.percentage >= 100 ? 'exception' : '';
+  if (energyData.value.percentage >= 95) return 'exception';
+  if (energyData.value.percentage >= 80) return 'warning';
+  return 'success';
+});
+
+const dailyAverage = computed(() => {
+  if (!energyData.value) return 0;
+  return (energyData.value.used / 30).toFixed(2);
+});
+
+const remainingDays = computed(() => {
+  if (!energyData.value || dailyAverage.value <= 0) return 0;
+  return Math.floor(energyData.value.remaining / dailyAverage.value);
+});
+
+const efficiencyGrade = computed(() => {
+  if (!energyData.value) return 'N/A';
+  const percentage = energyData.value.percentage;
+  if (percentage < 60) return '优秀';
+  if (percentage < 80) return '良好';
+  if (percentage < 95) return '一般';
+  return '需改进';
+});
+
+const efficiencyRating = computed(() => {
+  if (!energyData.value) return 0;
+  const percentage = energyData.value.percentage;
+  if (percentage < 60) return 5;
+  if (percentage < 80) return 4;
+  if (percentage < 95) return 3;
+  return 2;
 });
 
+const getUsageRateClass = () => {
+  if (!energyData.value) return '';
+  if (energyData.value.percentage >= 95) return 'danger';
+  if (energyData.value.percentage >= 80) return 'warning';
+  return 'normal';
+};
+
 const getList = async () => {
   try {
     loading.value = true;
@@ -138,28 +347,40 @@ const handleTree = (data, id, parentId, childrenName = 'children') => {
 
 const handleNodeClick = (node) => {
   selectedNode.value = node;
-  selectedEnergyType.value = 'electricity'; // 默认选择电
-  fetchEnergyData(); // 自动触发查询
+  energyData.value = null; // 清空之前的数据
 };
 
 const fetchEnergyData = async () => {
   if (!selectedNode.value) {
-    alert('请先选择一个节点');
+    ElMessage.warning('请先选择一个节点');
     return;
   }
 
-  // 模拟数据 - 实际项目中应该调用API获取
-  const limit = Math.random() * 10000 + 5000; // 总限值
-  const used = Math.random() * limit; // 已使用
-  const remaining = limit - used; // 剩余
-  const percentage = (used / limit) * 100; // 使用率
-
-  energyData.value = {
-    limit: parseFloat(limit.toFixed(2)),
-    used: parseFloat(used.toFixed(2)),
-    remaining: parseFloat(remaining.toFixed(2)),
-    percentage: parseFloat(percentage.toFixed(2))
-  };
+  try {
+    queryLoading.value = true;
+
+    // 模拟API调用延迟
+    await new Promise(resolve => setTimeout(resolve, 1000));
+
+    // 模拟数据 - 实际项目中应该调用API获取
+    const limit = Math.random() * 10000 + 5000;
+    const used = Math.random() * limit;
+    const remaining = limit - used;
+    const percentage = (used / limit) * 100;
+
+    energyData.value = {
+      limit: parseFloat(limit.toFixed(2)),
+      used: parseFloat(used.toFixed(2)),
+      remaining: parseFloat(remaining.toFixed(2)),
+      percentage: parseFloat(percentage.toFixed(2))
+    };
+
+    ElMessage.success('数据查询成功');
+  } catch (error) {
+    ElMessage.error('查询数据失败');
+  } finally {
+    queryLoading.value = false;
+  }
 };
 
 onMounted(() => {
@@ -173,6 +394,7 @@ onMounted(() => {
   background-color: #f5f7fa;
   color: #333;
   overflow: hidden;
+  height: 90vh;
 }
 
 .sidebar {
@@ -191,40 +413,450 @@ onMounted(() => {
   background-color: inherit;
   color: inherit;
 }
-
 .main-content {
   flex: 1;
-  padding: 20px;
-  background-color: #fff;
+  padding: 0;
+  background-color: #f8fafc;
+  overflow-y: auto;
+}
+
+/* 顶部标题栏 */
+.content-header {
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+  color: white;
+  padding: 24px 32px;
+  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+}
+
+.page-title {
+  margin: 0 0 12px 0;
+  font-size: 28px;
+  font-weight: 600;
+  display: flex;
+  align-items: center;
+  gap: 12px;
+}
+
+.breadcrumb {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  font-size: 14px;
+  opacity: 0.9;
+}
+
+/* 控制面板 */
+.control-panel {
+  background: white;
+  margin: 24px 32px;
+  padding: 24px;
+  border-radius: 12px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
 }
 
 .query-section {
   display: flex;
-  gap: 10px;
-  margin-bottom: 20px;
+  gap: 24px;
+  align-items: end;
+  flex-wrap: wrap;
+}
+
+.query-item {
+  display: flex;
+  flex-direction: column;
+  gap: 8px;
+}
+
+.query-item label {
+  font-weight: 500;
+  color: #374151;
+  font-size: 14px;
+}
+
+/* 数据展示区域 */
+.data-display {
+  margin: 0 32px 32px 32px;
+}
+
+/* 统计卡片 */
+.stats-cards {
+  display: grid;
+  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
+  gap: 20px;
+  margin-bottom: 32px;
+}
+
+.stat-card {
+  background: white;
+  padding: 24px;
+  border-radius: 12px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
+  display: flex;
+  align-items: center;
+  gap: 16px;
+  transition: all 0.3s ease;
+  border-left: 4px solid #e5e7eb;
+}
+
+.stat-card:hover {
+  transform: translateY(-2px);
+  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
+}
+
+.limit-card {
+  border-left-color: #3b82f6;
+}
+
+.used-card {
+  border-left-color: #f59e0b;
+}
+
+.remaining-card {
+  border-left-color: #10b981;
+}
+
+.percentage-card {
+  border-left-color: #6366f1;
+}
+
+.percentage-card.warning {
+  border-left-color: #f59e0b;
+  background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%);
+}
+
+.percentage-card.danger {
+  border-left-color: #ef4444;
+  background: linear-gradient(135deg, #fee2e2 0%, #fecaca 100%);
+}
+
+.card-icon {
+  width: 48px;
+  height: 48px;
+  border-radius: 12px;
+  display: flex;
   align-items: center;
+  justify-content: center;
+  font-size: 24px;
+  color: white;
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+}
+
+.limit-card .card-icon {
+  background: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%);
+}
+
+.used-card .card-icon {
+  background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);
+}
+
+.remaining-card .card-icon {
+  background: linear-gradient(135deg, #10b981 0%, #059669 100%);
+}
+
+.percentage-card .card-icon {
+  background: linear-gradient(135deg, #6366f1 0%, #4f46e5 100%);
+}
+
+.card-content {
+  flex: 1;
+}
+
+.card-title {
+  font-size: 14px;
+  color: #6b7280;
+  margin-bottom: 4px;
+  font-weight: 500;
+}
+
+.card-value {
+  font-size: 24px;
+  font-weight: 700;
+  color: #111827;
+  display: flex;
+  align-items: baseline;
+  gap: 4px;
+}
+
+.unit {
+  font-size: 14px;
+  color: #6b7280;
+  font-weight: 400;
+}
+
+/* 进度条区域 */
+.progress-section {
+  background: white;
+  padding: 32px;
+  border-radius: 12px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
+  margin-bottom: 32px;
+}
+
+.section-title {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 24px;
+  padding-bottom: 16px;
+  border-bottom: 2px solid #f3f4f6;
+}
+
+.section-title h3 {
+  margin: 0;
+  font-size: 20px;
+  font-weight: 600;
+  color: #111827;
+}
+
+.progress-legend {
+  display: flex;
+  gap: 16px;
+}
+
+.legend-item {
+  display: flex;
+  align-items: center;
+  gap: 6px;
+  font-size: 12px;
+  color: #6b7280;
+}
+
+.legend-color {
+  width: 12px;
+  height: 12px;
+  border-radius: 2px;
+}
+
+.legend-color.normal {
+  background-color: #10b981;
+}
+
+.legend-color.warning {
+  background-color: #f59e0b;
+}
+
+.legend-color.danger {
+  background-color: #ef4444;
 }
 
 .progress-card {
-  margin-bottom: 20px;
-  padding: 15px;
-  background-color: #f9f9f9;
+  background: #f8fafc;
+  padding: 24px;
   border-radius: 8px;
-  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
+  border: 1px solid #e5e7eb;
+}
+
+.progress-info {
+  width: 100%;
+}
+
+.progress-text {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 16px;
+}
+
+.current-usage {
+  font-size: 16px;
+  font-weight: 500;
+  color: #374151;
+}
+
+.usage-rate {
+  font-size: 20px;
+  font-weight: 700;
+  padding: 4px 12px;
+  border-radius: 20px;
+}
+
+.usage-rate.normal {
+  color: #059669;
+  background-color: #d1fae5;
+}
+
+.usage-rate.warning {
+  color: #d97706;
+  background-color: #fef3c7;
+}
+
+.usage-rate.danger {
+  color: #dc2626;
+  background-color: #fee2e2;
+}
+
+.progress-bar-container {
+  margin-bottom: 12px;
 }
 
-.progress-header {
+.progress-marks {
   display: flex;
   justify-content: space-between;
-  margin-bottom: 10px;
+  font-size: 12px;
+  color: #6b7280;
+}
+
+.mark {
+  position: relative;
+}
+
+.mark.warning::before,
+.mark.danger::before {
+  content: '';
+  position: absolute;
+  top: -28px;
+  left: 50%;
+  transform: translateX(-50%);
+  width: 2px;
+  height: 20px;
+  background-color: currentColor;
+}
+
+.mark.warning {
+  color: #f59e0b;
+}
+
+.mark.danger {
+  color: #ef4444;
+}
+
+/* 趋势分析 */
+.trend-section {
+  background: white;
+  padding: 32px;
+  border-radius: 12px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
+}
+
+.trend-cards {
+  display: grid;
+  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
+  gap: 20px;
 }
 
-.tip {
-  color: #999;
-  margin-top: 20px;
+.trend-card {
+  background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
+  padding: 24px;
+  border-radius: 12px;
+  border: 1px solid #e2e8f0;
+  transition: all 0.3s ease;
 }
 
-/* 左侧树形表格高亮样式 */
+.trend-card:hover {
+  transform: translateY(-2px);
+  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1);
+}
+
+.trend-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 12px;
+}
+
+.trend-title {
+  font-size: 14px;
+  color: #64748b;
+  font-weight: 500;
+}
+
+.trend-header i {
+  font-size: 20px;
+  color: #94a3b8;
+}
+
+.trend-value {
+  font-size: 28px;
+  font-weight: 700;
+  color: #1e293b;
+  margin-bottom: 8px;
+}
+
+.trend-change {
+  display: flex;
+  align-items: center;
+  gap: 4px;
+  font-size: 12px;
+  font-weight: 500;
+}
+
+.trend-change.positive {
+  color: #059669;
+}
+
+.trend-change.negative {
+  color: #dc2626;
+}
+
+.trend-change i {
+  font-size: 12px;
+}
+
+/* 空状态 */
+.empty-state {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  padding: 80px 32px;
+  text-align: center;
+}
+
+.empty-icon {
+  width: 120px;
+  height: 120px;
+  border-radius: 50%;
+  background: linear-gradient(135deg, #f3f4f6 0%, #e5e7eb 100%);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-bottom: 24px;
+}
+
+.empty-icon i {
+  font-size: 48px;
+  color: #9ca3af;
+}
+
+.empty-text h3 {
+  margin: 0 0 8px 0;
+  font-size: 20px;
+  color: #374151;
+}
+
+.empty-text p {
+  margin: 0;
+  color: #6b7280;
+  font-size: 14px;
+}
+
+/* 自定义进度条样式 */
+:deep(.custom-progress .el-progress-bar__outer) {
+  background-color: #f3f4f6;
+  border-radius: 12px;
+}
+
+:deep(.custom-progress .el-progress-bar__inner) {
+  border-radius: 12px;
+  transition: all 0.3s ease;
+}
+
+/* Element Plus 组件样式覆盖 */
+:deep(.el-select .el-input__inner) {
+  border-radius: 8px;
+}
+
+:deep(.el-button) {
+  border-radius: 8px;
+  font-weight: 500;
+}
+
+:deep(.el-tag) {
+  border-radius: 20px;
+  font-weight: 500;
+}
+
+/* 左侧树形表格样式保持不变 */
 :deep(.custom-table .el-table__body tr.current-row > td) {
   background-color: #a6b9dd !important;
   border-radius: 12px !important;
@@ -252,4 +884,51 @@ onMounted(() => {
 .custom-table :deep(.el-table__inner-wrapper::before) {
   display: none;
 }
+
+/* 响应式设计 */
+@media (max-width: 1200px) {
+  .stats-cards {
+    grid-template-columns: repeat(2, 1fr);
+  }
+
+  .trend-cards {
+    grid-template-columns: 1fr;
+  }
+}
+
+@media (max-width: 768px) {
+  .main-content {
+    padding: 0;
+  }
+
+  .content-header {
+    padding: 16px 20px;
+  }
+
+  .page-title {
+    font-size: 24px;
+  }
+
+  .control-panel,
+  .data-display {
+    margin: 16px 20px;
+  }
+
+  .stats-cards {
+    grid-template-columns: 1fr;
+  }
+
+  .query-section {
+    flex-direction: column;
+    align-items: stretch;
+  }
+
+  .query-item {
+    width: 100%;
+  }
+
+  .query-item .el-select {
+    width: 100% !important;
+  }
+}
 </style>

+ 1207 - 0
pm_ui/src/views/enterpriseInfo/enterprise.vue

@@ -0,0 +1,1207 @@
+<template>
+  <div class="app-container">
+    <!-- 统计卡片区域 -->
+    <el-row :gutter="20" class="mb20">
+      <el-col :span="6">
+        <el-card class="statistics-card" @click="filterByStatus('all')">
+          <div class="statistics-content">
+            <div class="statistics-icon total">
+              <el-icon><OfficeBuilding /></el-icon>
+            </div>
+            <div class="statistics-info">
+              <div class="statistics-number">{{ statistics.total }}</div>
+              <div class="statistics-label">企业总数</div>
+            </div>
+          </div>
+        </el-card>
+      </el-col>
+      <el-col :span="6">
+        <el-card class="statistics-card" @click="filterByStatus('certified')">
+          <div class="statistics-content">
+            <div class="statistics-icon certified">
+              <el-icon><Medal /></el-icon>
+            </div>
+            <div class="statistics-info">
+              <div class="statistics-number">{{ statistics.certified }}</div>
+              <div class="statistics-label">已认证企业</div>
+            </div>
+          </div>
+        </el-card>
+      </el-col>
+      <el-col :span="6">
+        <el-card class="statistics-card" @click="filterByStatus('active')">
+          <div class="statistics-content">
+            <div class="statistics-icon active">
+              <el-icon><CircleCheck /></el-icon>
+            </div>
+            <div class="statistics-info">
+              <div class="statistics-number">{{ statistics.active }}</div>
+              <div class="statistics-label">正常企业</div>
+            </div>
+          </div>
+        </el-card>
+      </el-col>
+      <el-col :span="6">
+        <el-card class="statistics-card" @click="filterByStatus('inactive')">
+          <div class="statistics-content">
+            <div class="statistics-icon warning">
+              <el-icon><Warning /></el-icon>
+            </div>
+            <div class="statistics-info">
+              <div class="statistics-number">{{ statistics.inactive }}</div>
+              <div class="statistics-label">异常企业</div>
+            </div>
+          </div>
+        </el-card>
+      </el-col>
+    </el-row>
+
+    <!-- 搜索表单 -->
+    <el-card class="search-card mb20">
+      <el-form :model="queryParams" ref="queryFormRef" :inline="true" v-show="showSearch" label-width="90px">
+        <el-row :gutter="20">
+          <el-col :span="6">
+            <el-form-item label="企业名称" prop="enterpriseName">
+              <el-input
+                  v-model="queryParams.enterpriseName"
+                  placeholder="请输入企业名称"
+                  clearable
+                  @keyup.enter="handleQuery"
+              />
+            </el-form-item>
+          </el-col>
+          <el-col :span="6">
+            <el-form-item label="信用代码" prop="creditCode">
+              <el-input
+                  v-model="queryParams.creditCode"
+                  placeholder="请输入统一社会信用代码"
+                  clearable
+                  @keyup.enter="handleQuery"
+              />
+            </el-form-item>
+          </el-col>
+          <el-col :span="6">
+            <el-form-item label="法定代表人" prop="legalPerson">
+              <el-input
+                  v-model="queryParams.legalPerson"
+                  placeholder="请输入法定代表人"
+                  clearable
+                  @keyup.enter="handleQuery"
+              />
+            </el-form-item>
+          </el-col>
+          <el-col :span="6">
+            <el-form-item label="企业状态" prop="enterpriseStatus">
+              <el-select v-model="queryParams.enterpriseStatus" placeholder="请选择企业状态" clearable style="width: 180px">
+                <el-option
+                    v-for="dict in sys_enterprise_status"
+                    :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="6">
+            <el-form-item label="认证状态" prop="certStatus">
+              <el-select v-model="queryParams.certStatus" placeholder="请选择认证状态" clearable style="width: 180px">
+                <el-option
+                    v-for="dict in sys_cert_status"
+                    :key="dict.value"
+                    :label="dict.label"
+                    :value="dict.value"
+                />
+              </el-select>
+            </el-form-item>
+          </el-col>
+<!--          <el-col :span="6">
+            <el-form-item label="成立时间" prop="establishDateRange">
+              <el-date-picker
+                  v-model="queryParams.establishDateRange"
+                  type="daterange"
+                  range-separator="至"
+                  start-placeholder="开始日期"
+                  end-placeholder="结束日期"
+                  value-format="YYYY-MM-DD"
+                  style="width: 180px"
+              />
+            </el-form-item>
+          </el-col>
+          <el-col :span="6">
+            <el-form-item label="注册资本" prop="capitalRange">
+              <el-input-number
+                  v-model="queryParams.minCapital"
+                  placeholder="最小值"
+                  :min="0"
+                  style="width: 85px"
+              />
+              <span style="margin: 0 5px">-</span>
+              <el-input-number
+                  v-model="queryParams.maxCapital"
+                  placeholder="最大值"
+                  :min="0"
+                  style="width: 85px"
+              />
+            </el-form-item>
+          </el-col>-->
+          <el-col :span="6">
+            <el-form-item>
+              <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
+              <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+    </el-card>
+
+    <!-- 操作按钮 -->
+    <el-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-button
+            type="primary"
+            plain
+            icon="Plus"
+            @click="handleAdd"
+            v-hasPermi="['system:enterprise:add']"
+        >新增</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+            type="success"
+            plain
+            icon="Edit"
+            :disabled="single"
+            @click="handleUpdate"
+            v-hasPermi="['system:enterprise:edit']"
+        >修改</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+            type="danger"
+            plain
+            icon="Delete"
+            :disabled="multiple"
+            @click="handleDelete"
+            v-hasPermi="['system:enterprise:remove']"
+        >删除</el-button>
+      </el-col>
+<!--      <el-col :span="1.5">
+        <el-button
+            type="warning"
+            plain
+            icon="Download"
+            @click="handleExport"
+            v-hasPermi="['system:enterprise:export']"
+        >导出</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+            type="info"
+            plain
+            icon="Upload"
+            @click="handleImport"
+            v-hasPermi="['system:enterprise:import']"
+        >导入</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+            type="success"
+            plain
+            icon="Refresh"
+            @click="handleRefresh"
+        >刷新</el-button>
+      </el-col>-->
+      <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <!-- 数据表格 -->
+    <el-table
+        v-loading="loading"
+        :data="enterpriseList"
+        @selection-change="handleSelectionChange"
+        @sort-change="handleSortChange"
+        stripe
+        border
+        :default-sort="{prop: 'createTime', order: 'descending'}"
+    >
+      <el-table-column type="selection" width="55" align="center" />
+      <el-table-column label="序号" type="index" width="60" align="center" />
+      <el-table-column label="企业名称" align="center" prop="enterpriseName" :show-overflow-tooltip="true" min-width="150" sortable="custom" />
+      <el-table-column label="统一社会信用代码" align="center" prop="creditCode" min-width="180" />
+      <el-table-column label="法定代表人" align="center" prop="legalPerson" width="100" />
+      <el-table-column label="注册资本(万元)" align="center" prop="registeredCapital" width="120" sortable="custom">
+        <template #default="scope">
+          <span v-if="scope.row.registeredCapital" class="capital-amount">{{ formatNumber(scope.row.registeredCapital) }}</span>
+          <span v-else>-</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="成立日期" align="center" prop="establishDate" width="110" sortable="custom" />
+      <el-table-column label="联系电话" align="center" prop="contactPhone" width="120" />
+      <el-table-column label="企业状态" align="center" prop="enterpriseStatus" width="100">
+        <template #default="scope">
+          <dict-tag :options="sys_enterprise_status" :value="scope.row.enterpriseStatus"/>
+        </template>
+      </el-table-column>
+      <el-table-column label="认证状态" align="center" prop="certStatus" width="100">
+        <template #default="scope">
+          <dict-tag :options="sys_cert_status" :value="scope.row.certStatus"/>
+        </template>
+      </el-table-column>
+      <el-table-column label="创建时间" align="center" prop="createTime" width="160" sortable="custom">
+        <template #default="scope">
+          <span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="200" fixed="right">
+        <template #default="scope">
+          <el-tooltip content="查看详情" placement="top">
+            <el-button
+                link
+                type="primary"
+                icon="View"
+                @click="handleView(scope.row)"
+                v-hasPermi="['system:enterprise:query']"
+            ></el-button>
+          </el-tooltip>
+          <el-tooltip content="编辑" placement="top">
+            <el-button
+                link
+                type="primary"
+                icon="Edit"
+                @click="handleUpdate(scope.row)"
+                v-hasPermi="['system:enterprise:edit']"
+            ></el-button>
+          </el-tooltip>
+          <el-tooltip content="复制" placement="top">
+            <el-button
+                link
+                type="success"
+                icon="CopyDocument"
+                @click="handleCopy(scope.row)"
+                v-hasPermi="['system:enterprise:add']"
+            ></el-button>
+          </el-tooltip>
+          <el-tooltip content="删除" placement="top">
+            <el-button
+                link
+                type="danger"
+                icon="Delete"
+                @click="handleDelete(scope.row)"
+                v-hasPermi="['system:enterprise:remove']"
+            ></el-button>
+          </el-tooltip>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <pagination
+        v-show="total > 0"
+        :total="total"
+        v-model:page="queryParams.pageNum"
+        v-model:limit="queryParams.pageSize"
+        @pagination="getList"
+    />
+
+    <!-- 添加或修改企业信息对话框 -->
+    <el-dialog :title="title" v-model="open" width="900px" append-to-body destroy-on-close>
+      <el-form ref="enterpriseFormRef" :model="form" :rules="rules" label-width="140px">
+        <el-tabs v-model="activeTab" type="border-card">
+          <!-- 基础信息 -->
+          <el-tab-pane label="基础信息" name="basic">
+            <el-row :gutter="20">
+              <el-col :span="12">
+                <el-form-item label="企业名称" prop="enterpriseName">
+                  <el-input v-model="form.enterpriseName" placeholder="请输入企业名称" />
+                </el-form-item>
+              </el-col>
+              <el-col :span="12">
+                <el-form-item label="统一社会信用代码" prop="creditCode">
+                  <el-input
+                      v-model="form.creditCode"
+                      placeholder="请输入统一社会信用代码"
+                      @blur="checkCreditCode"
+                  />
+                </el-form-item>
+              </el-col>
+            </el-row>
+            <el-row :gutter="20">
+              <el-col :span="12">
+                <el-form-item label="法定代表人" prop="legalPerson">
+                  <el-input v-model="form.legalPerson" placeholder="请输入法定代表人" />
+                </el-form-item>
+              </el-col>
+              <el-col :span="12">
+                <el-form-item label="注册资本(万元)" prop="registeredCapital">
+                  <el-input-number
+                      v-model="form.registeredCapital"
+                      :precision="2"
+                      :min="0"
+                      :max="999999999"
+                      placeholder="请输入注册资本"
+                      style="width: 100%"
+                  />
+                </el-form-item>
+              </el-col>
+            </el-row>
+            <el-row :gutter="20">
+              <el-col :span="12">
+                <el-form-item label="成立日期" prop="establishDate">
+                  <el-date-picker
+                      v-model="form.establishDate"
+                      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="contactPhone">
+                  <el-input v-model="form.contactPhone" placeholder="请输入联系电话" />
+                </el-form-item>
+              </el-col>
+            </el-row>
+            <el-row :gutter="20">
+              <el-col :span="12">
+                <el-form-item label="邮箱" prop="email">
+                  <el-input v-model="form.email" placeholder="请输入邮箱" />
+                </el-form-item>
+              </el-col>
+              <el-col :span="12">
+                <el-form-item label="官网" prop="website">
+                  <el-input v-model="form.website" placeholder="请输入官网地址" />
+                </el-form-item>
+              </el-col>
+            </el-row>
+            <el-row :gutter="20">
+              <el-col :span="12">
+                <el-form-item label="企业状态">
+                  <el-radio-group v-model="form.enterpriseStatus">
+                    <el-radio
+                        v-for="dict in sys_enterprise_status"
+                        :key="dict.value"
+                        :label="dict.value"
+                    >{{ dict.label }}</el-radio>
+                  </el-radio-group>
+                </el-form-item>
+              </el-col>
+              <el-col :span="12">
+                <el-form-item label="认证状态">
+                  <el-radio-group v-model="form.certStatus">
+                    <el-radio
+                        v-for="dict in sys_cert_status"
+                        :key="dict.value"
+                        :label="dict.value"
+                    >{{ dict.label }}</el-radio>
+                  </el-radio-group>
+                </el-form-item>
+              </el-col>
+            </el-row>
+          </el-tab-pane>
+
+          <!-- 地址信息 -->
+          <el-tab-pane label="地址信息" name="address">
+            <el-row>
+              <el-col :span="24">
+                <el-form-item label="注册地址" prop="registeredAddress">
+                  <el-input
+                      v-model="form.registeredAddress"
+                      type="textarea"
+                      :rows="3"
+                      placeholder="请输入注册地址"
+                  />
+                </el-form-item>
+              </el-col>
+            </el-row>
+            <el-row>
+              <el-col :span="24">
+                <el-form-item label="办公地址" prop="officeAddress">
+                  <el-input
+                      v-model="form.officeAddress"
+                      type="textarea"
+                      :rows="3"
+                      placeholder="请输入办公地址"
+                  />
+                </el-form-item>
+              </el-col>
+            </el-row>
+            <el-row>
+              <el-col :span="24">
+                <el-form-item>
+                  <el-checkbox v-model="sameAsRegistered" @change="copyRegisteredAddress">
+                    办公地址与注册地址相同
+                  </el-checkbox>
+                </el-form-item>
+              </el-col>
+            </el-row>
+          </el-tab-pane>
+
+          <!-- 经营信息 -->
+          <el-tab-pane label="经营信息" name="business">
+            <el-row>
+              <el-col :span="24">
+                <el-form-item label="经营范围" prop="businessScope">
+                  <el-input
+                      v-model="form.businessScope"
+                      type="textarea"
+                      :rows="5"
+                      placeholder="请输入经营范围"
+                      show-word-limit
+                      maxlength="1000"
+                  />
+                </el-form-item>
+              </el-col>
+            </el-row>
+            <el-row>
+              <el-col :span="24">
+                <el-form-item label="备注" prop="remark">
+                  <el-input
+                      v-model="form.remark"
+                      type="textarea"
+                      :rows="3"
+                      placeholder="请输入备注信息"
+                      show-word-limit
+                      maxlength="500"
+                  />
+                </el-form-item>
+              </el-col>
+            </el-row>
+          </el-tab-pane>
+        </el-tabs>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button type="primary" @click="submitForm" :loading="submitLoading">确 定</el-button>
+          <el-button @click="cancel">取 消</el-button>
+        </div>
+      </template>
+    </el-dialog>
+
+    <!-- 企业信息详情对话框 -->
+    <el-dialog title="企业信息详情" v-model="viewOpen" width="900px" append-to-body>
+      <el-descriptions :column="2" border size="large">
+        <el-descriptions-item label="企业名称" label-class-name="desc-label">
+          <span class="desc-value">{{ viewForm.enterpriseName }}</span>
+        </el-descriptions-item>
+        <el-descriptions-item label="统一社会信用代码" label-class-name="desc-label">
+          <span class="desc-value">{{ viewForm.creditCode }}</span>
+        </el-descriptions-item>
+        <el-descriptions-item label="法定代表人" label-class-name="desc-label">
+          <span class="desc-value">{{ viewForm.legalPerson }}</span>
+        </el-descriptions-item>
+        <el-descriptions-item label="注册资本" label-class-name="desc-label">
+          <span class="desc-value">{{ formatNumber(viewForm.registeredCapital) }}万元</span>
+        </el-descriptions-item>
+        <el-descriptions-item label="成立日期" label-class-name="desc-label">
+          <span class="desc-value">{{ viewForm.establishDate }}</span>
+        </el-descriptions-item>
+        <el-descriptions-item label="联系电话" label-class-name="desc-label">
+          <span class="desc-value">{{ viewForm.contactPhone }}</span>
+        </el-descriptions-item>
+        <el-descriptions-item label="邮箱" label-class-name="desc-label">
+          <span class="desc-value">{{ viewForm.email }}</span>
+        </el-descriptions-item>
+        <el-descriptions-item label="官网" label-class-name="desc-label">
+          <el-link :href="viewForm.website" target="_blank" type="primary" v-if="viewForm.website">
+            {{ viewForm.website }}
+          </el-link>
+          <span v-else>-</span>
+        </el-descriptions-item>
+        <el-descriptions-item label="企业状态" label-class-name="desc-label">
+          <dict-tag :options="sys_enterprise_status" :value="viewForm.enterpriseStatus"/>
+        </el-descriptions-item>
+        <el-descriptions-item label="认证状态" label-class-name="desc-label">
+          <dict-tag :options="sys_cert_status" :value="viewForm.certStatus"/>
+        </el-descriptions-item>
+        <el-descriptions-item label="注册地址" :span="2" label-class-name="desc-label">
+          <span class="desc-value">{{ viewForm.registeredAddress }}</span>
+        </el-descriptions-item>
+        <el-descriptions-item label="办公地址" :span="2" label-class-name="desc-label">
+          <span class="desc-value">{{ viewForm.officeAddress }}</span>
+        </el-descriptions-item>
+        <el-descriptions-item label="经营范围" :span="2" label-class-name="desc-label">
+          <div class="business-scope">{{ viewForm.businessScope }}</div>
+        </el-descriptions-item>
+        <el-descriptions-item label="备注" :span="2" label-class-name="desc-label">
+          <span class="desc-value">{{ viewForm.remark || '-' }}</span>
+        </el-descriptions-item>
+        <el-descriptions-item label="创建时间" label-class-name="desc-label">
+          <span class="desc-value">{{ parseTime(viewForm.createTime) }}</span>
+        </el-descriptions-item>
+        <el-descriptions-item label="更新时间" label-class-name="desc-label">
+          <span class="desc-value">{{ parseTime(viewForm.updateTime) }}</span>
+        </el-descriptions-item>
+      </el-descriptions>
+    </el-dialog>
+
+    <!-- 导入对话框 -->
+    <el-dialog title="企业信息导入" v-model="importOpen" width="400px" append-to-body>
+      <el-upload
+          ref="uploadRef"
+          :limit="1"
+          accept=".xlsx, .xls"
+          :headers="upload.headers"
+          :action="upload.url"
+          :disabled="upload.isUploading"
+          :on-progress="handleFileUploadProgress"
+          :on-success="handleFileSuccess"
+          :auto-upload="false"
+          drag
+      >
+        <el-icon class="el-icon--upload"><upload-filled /></el-icon>
+        <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
+        <template #tip>
+          <div class="el-upload__tip text-center">
+            <div class="el-upload__tip">
+              <el-checkbox v-model="upload.updateSupport" />
+              是否更新已经存在的企业数据
+            </div>
+            <span>仅允许导入xls、xlsx格式文件。</span>
+            <el-link type="primary" :underline="false" style="font-size:12px;vertical-align: baseline;" @click="importTemplate">下载模板</el-link>
+          </div>
+        </template>
+      </el-upload>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button type="primary" @click="submitFileForm">确 定</el-button>
+          <el-button @click="importOpen = false">取 消</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup name="Enterprise">
+import {
+  listEnterprise,
+  getEnterprise,
+  delEnterprise,
+  addEnterprise,
+  updateEnterprise,
+  getEnterpriseStatistics
+} from "@/api/enterpriseInfo/enterprise";
+import { getToken } from "@/utils/auth";
+
+const { proxy } = getCurrentInstance();
+const { sys_enterprise_status, sys_cert_status } = proxy.useDict('sys_enterprise_status', 'sys_cert_status');
+
+const enterpriseList = ref([]);
+const open = ref(false);
+const viewOpen = ref(false);
+const importOpen = ref(false);
+const loading = ref(true);
+const showSearch = ref(true);
+const ids = ref([]);
+const single = ref(true);
+const multiple = ref(true);
+const total = ref(0);
+const title = ref("");
+const activeTab = ref("basic");
+const sameAsRegistered = ref(false);
+const submitLoading = ref(false);
+
+// 统计数据
+const statistics = ref({
+  total: 0,
+  certified: 0,
+  active: 0,
+  inactive: 0
+});
+
+const queryFormRef = ref();
+const enterpriseFormRef = ref();
+const uploadRef = ref();
+
+// 上传参数
+const upload = reactive({
+  open: false,
+  title: "",
+  isUploading: false,
+  updateSupport: 0,
+  headers: { Authorization: "Bearer " + getToken() },
+  url: import.meta.env.VITE_APP_BASE_API + "/system/enterprise/importData"
+});
+
+const initFormData = {
+  enterpriseId: null,
+  enterpriseName: null,
+  creditCode: null,
+  legalPerson: null,
+  registeredCapital: null,
+  establishDate: null,
+  businessScope: null,
+  registeredAddress: null,
+  officeAddress: null,
+  contactPhone: null,
+  email: null,
+  website: null,
+  enterpriseStatus: "0",
+  certStatus: "0",
+  remark: null
+}
+
+const data = reactive({
+  form: { ...initFormData },
+  viewForm: {},
+  queryParams: {
+    pageNum: 1,
+    pageSize: 10,
+    enterpriseName: null,
+    creditCode: null,
+    legalPerson: null,
+    enterpriseStatus: null,
+    certStatus: null,
+    establishDateRange: null,
+    minCapital: null,
+    maxCapital: null,
+    orderByColumn: 'createTime',
+    isAsc: 'desc'
+  },
+  rules: {
+    enterpriseName: [
+      { required: true, message: "企业名称不能为空", trigger: "blur" },
+      { min: 2, max: 100, message: "企业名称长度在2到100个字符", trigger: "blur" }
+    ],
+    creditCode: [
+      { required: true, message: "统一社会信用代码不能为空", trigger: "blur" },
+      {
+        pattern: /^[0-9A-HJ-NPQRTUWXY]{2}\d{6}[0-9A-HJ-NPQRTUWXY]{10}$/,
+        message: "统一社会信用代码格式不正确",
+        trigger: "blur"
+      }
+    ],
+    legalPerson: [
+      { required: true, message: "法定代表人不能为空", trigger: "blur" },
+      { min: 2, max: 50, message: "法定代表人长度在2到50个字符", trigger: "blur" }
+    ],
+    email: [
+      {
+        type: "email",
+        message: "请输入正确的邮箱地址",
+        trigger: ["blur", "change"]
+      }
+    ],
+    contactPhone: [
+      {
+        pattern: /^1[3-9]\d{9}$|^0\d{2,3}-?\d{7,8}$/,
+        message: "请输入正确的联系电话",
+        trigger: "blur"
+      }
+    ],
+    website: [
+      {
+        pattern: /^https?:\/\/.+/,
+        message: "请输入正确的网址格式",
+        trigger: "blur"
+      }
+    ]
+  }
+});
+
+const { queryParams, form, viewForm, rules } = toRefs(data);
+
+/** 查询企业信息列表 */
+function getList() {
+  loading.value = true;
+  listEnterprise(queryParams.value).then(response => {
+    enterpriseList.value = response.rows;
+    total.value = response.total;
+    loading.value = false;
+  }).catch(() => {
+    loading.value = false;
+  });
+}
+
+/** 获取统计数据 */
+function getStatistics() {
+  getEnterpriseStatistics().then(response => {
+    statistics.value = response.data;
+  }).catch(() => {
+    console.error('获取统计数据失败');
+  });
+}
+
+// 取消按钮
+function cancel() {
+  open.value = false;
+  reset();
+}
+
+// 表单重置
+function reset() {
+  form.value = { ...initFormData };
+  activeTab.value = "basic";
+  sameAsRegistered.value = false;
+  enterpriseFormRef.value?.resetFields();
+}
+
+/** 搜索按钮操作 */
+function handleQuery() {
+  queryParams.value.pageNum = 1;
+  getList();
+}
+
+/** 重置按钮操作 */
+function resetQuery() {
+  queryFormRef.value?.resetFields();
+  queryParams.value = {
+    pageNum: 1,
+    pageSize: 10,
+    enterpriseName: null,
+    creditCode: null,
+    legalPerson: null,
+    enterpriseStatus: null,
+    certStatus: null,
+    establishDateRange: null,
+    minCapital: null,
+    maxCapital: null,
+    orderByColumn: 'createTime',
+    isAsc: 'desc'
+  };
+  handleQuery();
+}
+
+// 多选框选中数据
+function handleSelectionChange(selection) {
+  ids.value = selection.map(item => item.enterpriseId);
+  single.value = selection.length !== 1;
+  multiple.value = !selection.length;
+}
+
+/** 新增按钮操作 */
+function handleAdd() {
+  reset();
+  open.value = true;
+  title.value = "添加企业信息";
+}
+
+/** 修改按钮操作 */
+function handleUpdate(row) {
+  reset();
+  const enterpriseId = row.enterpriseId || ids.value[0];
+  getEnterprise(enterpriseId).then(response => {
+    form.value = response.data;
+    open.value = true;
+    title.value = "修改企业信息";
+  });
+}
+
+/** 查看按钮操作 */
+function handleView(row) {
+  getEnterprise(row.enterpriseId).then(response => {
+    viewForm.value = response.data;
+    viewOpen.value = true;
+  });
+}
+
+/** 复制按钮操作 */
+function handleCopy(row) {
+  getEnterprise(row.enterpriseId).then(response => {
+    const copyData = { ...response.data };
+    // 清除主键和唯一字段
+    delete copyData.enterpriseId;
+    copyData.enterpriseName = copyData.enterpriseName + "_副本";
+    copyData.creditCode = "";
+    form.value = copyData;
+    open.value = true;
+    title.value = "复制企业信息";
+  });
+}
+
+/** 复制注册地址到办公地址 */
+function copyRegisteredAddress(checked) {
+  if (checked && form.value.registeredAddress) {
+    form.value.officeAddress = form.value.registeredAddress;
+  } else if (!checked) {
+    form.value.officeAddress = '';
+  }
+}
+
+/** 统一社会信用代码校验 */
+function checkCreditCode() {
+  if (form.value.creditCode && form.value.creditCode.length === 18) {
+    // 这里可以添加更复杂的校验逻辑
+    console.log("校验统一社会信用代码:", form.value.creditCode);
+  }
+}
+
+/** 提交按钮 */
+function submitForm() {
+  enterpriseFormRef.value?.validate(valid => {
+    if (valid) {
+      submitLoading.value = true;
+
+      // 确保新增时不传递 enterpriseId
+      const submitData = { ...form.value };
+      if (!submitData.enterpriseId) {
+        delete submitData.enterpriseId;
+      }
+
+      if (form.value.enterpriseId != null) {
+        updateEnterprise(submitData).then(response => {
+          proxy.$modal.msgSuccess("修改成功");
+          open.value = false;
+          getList();
+          getStatistics(); // 刷新统计数据
+        }).finally(() => {
+          submitLoading.value = false;
+        });
+      } else {
+        addEnterprise(submitData).then(response => {
+          proxy.$modal.msgSuccess("新增成功");
+          open.value = false;
+          getList();
+          getStatistics(); // 刷新统计数据
+        }).finally(() => {
+          submitLoading.value = false;
+        });
+      }
+    }
+  });
+}
+
+/** 删除按钮操作 */
+function handleDelete(row) {
+  const enterpriseIds = row.enterpriseId || ids.value;
+  const enterpriseNames = row.enterpriseName || enterpriseList.value.filter(item => ids.value.includes(item.enterpriseId)).map(item => item.enterpriseName).join('、');
+
+  proxy.$modal.confirm(`是否确认删除企业"${enterpriseNames}"的数据项?`).then(function() {
+    return delEnterprise(enterpriseIds);
+  }).then(() => {
+    getList();
+    getStatistics(); // 刷新统计数据
+    proxy.$modal.msgSuccess("删除成功");
+  }).catch(() => {});
+}
+
+/** 导出按钮操作 */
+function handleExport() {
+  proxy.download('system/enterprise/export', {
+    ...queryParams.value
+  }, `enterprise_${new Date().getTime()}.xlsx`)
+}
+
+/** 导入按钮操作 */
+function handleImport() {
+  importOpen.value = true;
+}
+
+/** 刷新按钮操作 */
+function handleRefresh() {
+  getList();
+  getStatistics(); // 同时刷新统计数据
+  proxy.$modal.msgSuccess("刷新成功");
+}
+
+/** 下载模板 */
+function importTemplate() {
+  proxy.download('system/enterprise/importTemplate', {}, `enterprise_template_${new Date().getTime()}.xlsx`)
+}
+
+/** 文件上传中处理 */
+function handleFileUploadProgress(event, file, fileList) {
+  upload.isUploading = true;
+}
+
+/** 文件上传成功处理 */
+function handleFileSuccess(response, file, fileList) {
+  upload.isUploading = false;
+  if (response.code === 200) {
+    importOpen.value = false;
+    proxy.$alert(
+        `<div style="overflow: auto;overflow-x: hidden;max-height: 70vh;padding: 10px 20px 0;">
+        <p>数据导入成功!</p>
+        <p>新增:${response.data.addCount} 条</p>
+        <p>更新:${response.data.updateCount} 条</p>
+        <p>失败:${response.data.failureCount} 条</p>
+        ${response.data.failureList && response.data.failureList.length > 0 ?
+            '<p>失败数据:</p>' + response.data.failureList.map(item => `<p>${item}</p>`).join('') : ''}
+      </div>`,
+        "导入结果",
+        { dangerouslyUseHTMLString: true }
+    );
+    getList();
+    getStatistics(); // 刷新统计数据
+  } else {
+    proxy.$modal.msgError(response.msg);
+  }
+  uploadRef.value?.clearFiles();
+}
+
+/** 提交上传文件 */
+function submitFileForm() {
+  uploadRef.value?.submit();
+}
+
+/** 表格排序 */
+function handleSortChange({ column, prop, order }) {
+  queryParams.value.orderByColumn = prop;
+  queryParams.value.isAsc = order === 'ascending' ? 'asc' : 'desc';
+  getList();
+}
+
+/** 统计卡片点击筛选 */
+function filterByStatus(type) {
+  // 重置查询参数但不立即查询
+  queryFormRef.value?.resetFields();
+  queryParams.value = {
+    pageNum: 1,
+    pageSize: 10,
+    enterpriseName: null,
+    creditCode: null,
+    legalPerson: null,
+    enterpriseStatus: null,
+    certStatus: null,
+    establishDateRange: null,
+    minCapital: null,
+    maxCapital: null,
+    orderByColumn: 'createTime',
+    isAsc: 'desc'
+  };
+
+  // 根据类型设置对应的查询条件
+  switch (type) {
+    case 'certified':
+      queryParams.value.certStatus = '1';
+      break;
+    case 'active':
+      queryParams.value.enterpriseStatus = '0';
+      break;
+    case 'inactive':
+      queryParams.value.enterpriseStatus = '1';
+      break;
+    default:
+      break;
+  }
+
+  // 执行查询
+  handleQuery();
+}
+
+// 格式化数字
+function formatNumber(num) {
+  if (!num) return '0';
+  return parseFloat(num).toLocaleString();
+}
+
+onMounted(() => {
+  getList();
+  getStatistics(); // 页面加载时获取统计数据
+});
+</script>
+
+<style scoped>
+.statistics-card {
+  cursor: pointer;
+  transition: all 0.3s;
+  border-radius: 8px;
+  overflow: hidden;
+}
+
+.statistics-card:hover {
+  transform: translateY(-4px);
+  box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
+}
+
+.statistics-content {
+  display: flex;
+  align-items: center;
+  padding: 20px;
+}
+
+.statistics-icon {
+  width: 60px;
+  height: 60px;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-right: 20px;
+  font-size: 24px;
+  color: white;
+  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+}
+
+.statistics-icon.total {
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+}
+
+.statistics-icon.certified {
+  background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
+}
+
+.statistics-icon.active {
+  background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
+}
+
+.statistics-icon.warning {
+  background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%);
+}
+
+.statistics-info {
+  flex: 1;
+}
+
+.statistics-number {
+  font-size: 32px;
+  font-weight: bold;
+  color: #303133;
+  line-height: 1;
+  margin-bottom: 8px;
+}
+
+.statistics-label {
+  font-size: 14px;
+  color: #909399;
+  font-weight: 500;
+}
+
+.search-card {
+  margin-bottom: 20px;
+  border-radius: 8px;
+  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
+}
+
+.search-card .el-card__body {
+  padding: 20px;
+}
+
+.mb20 {
+  margin-bottom: 20px;
+}
+
+.mb8 {
+  margin-bottom: 8px;
+}
+
+.text-center {
+  text-align: center;
+}
+
+.capital-amount {
+  font-weight: 600;
+  color: #409eff;
+}
+
+.desc-label {
+  font-weight: bold;
+  color: #606266;
+  background-color: #f5f7fa;
+}
+
+.desc-value {
+  color: #303133;
+  font-weight: 500;
+}
+
+.business-scope {
+  max-height: 120px;
+  overflow-y: auto;
+  line-height: 1.8;
+  word-break: break-all;
+  padding: 8px;
+  background-color: #f9f9f9;
+  border-radius: 4px;
+}
+
+:deep(.el-descriptions__label) {
+  width: 140px;
+}
+
+:deep(.el-table .cell) {
+  word-break: break-all;
+}
+
+:deep(.el-dialog__body) {
+  padding: 10px 20px;
+}
+
+:deep(.el-tabs--border-card > .el-tabs__content) {
+  padding: 20px;
+}
+
+:deep(.el-table) {
+  border-radius: 8px;
+  overflow: hidden;
+}
+
+:deep(.el-table th) {
+  background-color: #f8f9fa;
+  color: #606266;
+  font-weight: 600;
+}
+
+:deep(.el-table tr:hover > td) {
+  background-color: #f5f7fa;
+}
+
+:deep(.el-card) {
+  border-radius: 8px;
+  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
+}
+
+:deep(.el-button) {
+  border-radius: 6px;
+}
+
+:deep(.el-input__wrapper) {
+  border-radius: 6px;
+}
+
+:deep(.el-select .el-input__wrapper) {
+  border-radius: 6px;
+}
+
+:deep(.el-date-editor.el-input) {
+  border-radius: 6px;
+}
+
+/* 响应式设计 */
+@media (max-width: 768px) {
+  .statistics-content {
+    padding: 15px;
+  }
+
+  .statistics-icon {
+    width: 50px;
+    height: 50px;
+    font-size: 20px;
+    margin-right: 15px;
+  }
+
+  .statistics-number {
+    font-size: 24px;
+  }
+
+  .search-card .el-card__body {
+    padding: 15px;
+  }
+}
+
+/* 动画效果 */
+.el-table tbody tr {
+  transition: all 0.3s ease;
+}
+
+.statistics-card {
+  position: relative;
+  overflow: hidden;
+}
+
+.statistics-card::before {
+  content: '';
+  position: absolute;
+  top: 0;
+  left: -100%;
+  width: 100%;
+  height: 100%;
+  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
+  transition: left 0.5s;
+}
+
+.statistics-card:hover::before {
+  left: 100%;
+}
+</style>

+ 3 - 3
pm_ui/src/views/fkxt/VisitorSystem.vue

@@ -11,7 +11,7 @@
             <el-input v-model="visitorQuery.visitedPerson" placeholder="请输入被访人" clearable />
           </el-form-item>
           <el-form-item label="状态" prop="visitStatus">
-            <el-select v-model="visitorQuery.visitStatus" placeholder="请选择状态" clearable style="width: 150px">
+            <el-select v-model="visitorQuery.visitStatus" placeholder="请选择状态" clearable  style="width: 180px;">
               <el-option label="预约" :value="0" />
               <el-option label="已到达" :value="1" />
               <el-option label="访问中" :value="2" />
@@ -96,7 +96,7 @@
             <el-input v-model="accessQuery.gateLocation" placeholder="请输入门禁位置" clearable />
           </el-form-item>
           <el-form-item label="刷卡类型" prop="swipeType">
-            <el-select v-model="accessQuery.swipeType" placeholder="请选择类型" clearable style="width: 150px">
+            <el-select v-model="accessQuery.swipeType" placeholder="请选择类型" clearable style="width: 180px;">
               <el-option label="进入" :value="1" />
               <el-option label="离开" :value="2" />
             </el-select>
@@ -389,4 +389,4 @@ getVisitorList()
 .el-descriptions {
   margin: 20px;
 }
-</style>
+</style>

+ 23 - 23
pm_ui/src/views/index.vue

@@ -474,22 +474,22 @@ const viewAllNotices = () => {
 };
 
 // WebSocket消息处理
-const handleWebSocketMessage = (message) => {
-  if (message && message !== "del" && message !== "update") {
-    ElNotification({
-      title: '新公告',
-      message: '您有新的公告,请注意查收',
-      type: 'warning',
-      duration: 5000,
-      position: 'top-right',
-      onClick: () => {
-        getNoticeList();
-      }
-    });
-  }
-  // 刷新公告列表
-  getNoticeList();
-};
+// const handleWebSocketMessage = (message) => {
+//   if (message && message !== "del" && message !== "update") {
+//     ElNotification({
+//       title: '新公告',
+//       message: '您有新的公告,请注意查收',
+//       type: 'warning',
+//       duration: 5000,
+//       position: 'top-right',
+//       onClick: () => {
+//         getNoticeList();
+//       }
+//     });
+//   }
+//   // 刷新公告列表
+//   getNoticeList();
+// };
 
 // 其他原有的方法保持不变...
 
@@ -501,17 +501,17 @@ onMounted(() => {
   getNoticeList();
 
   // 连接WebSocket
-  wsConnection = connectToWebSocket(handleWebSocketMessage);
+  //wsConnection = connectToWebSocket(handleWebSocketMessage);
 
   // 其他原有的初始化代码...
 });
 
-onUnmounted(() => {
-  // 清理WebSocket连接
-  if (wsConnection) {
-    // 根据实际的WebSocket实现进行清理
-  }
-});
+// onUnmounted(() => {
+//   // 清理WebSocket连接
+//   if (wsConnection) {
+//     // 根据实际的WebSocket实现进行清理
+//   }
+// });
 
 // 图表尺寸
 const chartWidth = ref(600);

+ 19 - 19
pm_ui/src/views/nygl/index.vue

@@ -11,7 +11,7 @@
             <el-input v-model="energyQuery.deviceName" placeholder="请输入设备名称" clearable />
           </el-form-item>
           <el-form-item label="能耗类型" prop="energyType">
-            <el-select v-model="energyQuery.energyType" placeholder="请选择能耗类型" clearable style="width: 150px">
+            <el-select v-model="energyQuery.energyType" placeholder="请选择能耗类型" clearable style="width: 180px;">
               <el-option label="电" value="electricity" />
               <el-option label="水" value="water" />
               <el-option label="煤" value="coal" />
@@ -19,7 +19,7 @@
             </el-select>
           </el-form-item>
           <el-form-item label="楼栋" prop="building">
-            <el-select v-model="energyQuery.building" placeholder="请选择楼栋" clearable style="width: 150px">
+            <el-select v-model="energyQuery.building" placeholder="请选择楼栋" clearable style="width: 180px;">
               <el-option label="A栋" value="A" />
               <el-option label="B栋" value="B" />
               <el-option label="C栋" value="C" />
@@ -780,7 +780,7 @@ function initStatCharts() {
 function generateTimeLabels(type) {
   const now = new Date()
   const labels = []
-  
+
   switch (type) {
     case 'day':
       for (let i = 0; i < 24; i++) {
@@ -802,7 +802,7 @@ function generateTimeLabels(type) {
       }
       break
   }
-  
+
   return labels
 }
 
@@ -820,7 +820,7 @@ function updateStatCharts() {
   if (trendChart) {
     const labels = generateTimeLabels(trendTimeRange.value)
     const dataCount = labels.length
-    
+
     trendChart.setOption({
       xAxis: [{
         data: labels
@@ -889,7 +889,7 @@ function generateChartData(timeType, dataType, energyType) {
   const data = []
   let count = 0
   let dateFormat = ''
-  
+
   switch (timeType) {
     case 'hour':
       count = 24
@@ -916,7 +916,7 @@ function generateChartData(timeType, dataType, energyType) {
       dateFormat = (i) => `${i + 1}月`
       break
   }
-  
+
   // 根据能耗类型生成基础值
   const baseValues = {
     electricity: { consumption: 100, power: 50 },
@@ -924,10 +924,10 @@ function generateChartData(timeType, dataType, energyType) {
     gas: { consumption: 30, power: 15 },
     coal: { consumption: 10, power: 5 }
   }
-  
+
   const base = baseValues[energyType] || baseValues.electricity
   const baseValue = dataType === 'consumption' ? base.consumption : base.power
-  
+
   for (let i = 0; i < count; i++) {
     const randomFactor = 0.5 + Math.random() * 1
     const value = (baseValue * randomFactor).toFixed(2)
@@ -936,7 +936,7 @@ function generateChartData(timeType, dataType, energyType) {
       value: parseFloat(value)
     })
   }
-  
+
   return data
 }
 
@@ -947,10 +947,10 @@ function initChart() {
     const lineChartDom = document.getElementById('energyChart')
     if (lineChartDom) {
       lineChart = echarts.init(lineChartDom)
-      
+
       const data = generateChartData(chartTimeType.value, chartDataType.value, currentDevice.value.energyType)
       const unit = chartDataType.value === 'consumption' ? getUnit(currentDevice.value.energyType) : 'kW'
-      
+
       const option = {
         title: {
           text: `${currentDevice.value.deviceName} - ${chartDataType.value === 'consumption' ? '能耗趋势' : '功率变化'}`,
@@ -1002,15 +1002,15 @@ function initChart() {
           data: data.map(item => item.value)
         }]
       }
-      
+
       lineChart.setOption(option)
     }
-    
+
     // 初始化饼图(能耗构成分析)
     const pieChartDom = document.getElementById('energyPieChart')
     if (pieChartDom) {
       pieChart = echarts.init(pieChartDom)
-      
+
       const pieOption = {
         title: {
           text: '能耗时段分布',
@@ -1055,10 +1055,10 @@ function initChart() {
           ]
         }]
       }
-      
+
       pieChart.setOption(pieOption)
     }
-    
+
     // 监听窗口大小变化
     window.addEventListener('resize', () => {
       lineChart && lineChart.resize()
@@ -1072,7 +1072,7 @@ function updateChart() {
   if (lineChart && currentDevice.value) {
     const data = generateChartData(chartTimeType.value, chartDataType.value, currentDevice.value.energyType)
     const unit = chartDataType.value === 'consumption' ? getUnit(currentDevice.value.energyType) : 'kW'
-    
+
     lineChart.setOption({
       title: {
         text: `${currentDevice.value.deviceName} - ${chartDataType.value === 'consumption' ? '能耗趋势' : '功率变化'}`
@@ -1412,4 +1412,4 @@ onMounted(() => {
 .stat-card:nth-child(4) {
   animation-delay: 0.4s;
 }
-</style>
+</style>

+ 4 - 4
pm_ui/src/views/parking/index.vue

@@ -11,12 +11,12 @@
             <el-input v-model="spaceQuery.spaceName" placeholder="请输入车位名称" clearable />
           </el-form-item>
           <el-form-item label="楼栋">
-            <el-select v-model="spaceQuery.buildingId" placeholder="请选择楼栋" clearable>
+            <el-select v-model="spaceQuery.buildingId" placeholder="请选择楼栋" clearable style="width: 180px;">
               <el-option v-for="building in buildingList" :key="building.id" :label="building.name" :value="building.id" />
             </el-select>
           </el-form-item>
           <el-form-item label="车位状态">
-            <el-select v-model="spaceQuery.spaceStatus" placeholder="请选择状态" clearable>
+            <el-select v-model="spaceQuery.spaceStatus" placeholder="请选择状态" clearable style="width: 180px;">
               <el-option label="空闲" :value="0" />
               <el-option label="占用" :value="1" />
               <el-option label="预约" :value="2" />
@@ -179,7 +179,7 @@
         <div class="report-container">
           <el-form :model="reportQuery" :inline="true" label-width="80px">
             <el-form-item label="统计类型">
-              <el-select v-model="reportQuery.reportType" placeholder="请选择" style="width: 150px">
+              <el-select v-model="reportQuery.reportType" placeholder="请选择" style="width: 180px;">
                 <el-option label="日报表" value="daily" />
                 <el-option label="周报表" value="weekly" />
                 <el-option label="月报表" value="monthly" />
@@ -695,4 +695,4 @@ onUnmounted(() => {
 .dialog-footer {
   text-align: right;
 }
-</style>
+</style>

+ 6 - 6
pm_ui/src/views/patrol/index.vue

@@ -11,24 +11,24 @@
             <el-input v-model="pointQuery.pointName" placeholder="请输入巡更点名称" clearable />
           </el-form-item>
           <el-form-item label="楼栋" prop="buildingId">
-            <el-select v-model="pointQuery.buildingId" placeholder="请选择楼栋" clearable style="width: 150px">
+            <el-select v-model="pointQuery.buildingId" placeholder="请选择楼栋" clearable style="width: 180px;">
               <el-option v-for="building in buildingList" :key="building.id" :label="building.name" :value="building.id" />
             </el-select>
           </el-form-item>
           <el-form-item label="楼层" prop="floorId">
-            <el-select v-model="pointQuery.floorId" placeholder="请选择楼层" clearable style="width: 150px">
+            <el-select v-model="pointQuery.floorId" placeholder="请选择楼层" clearable style="width: 180px;">
               <el-option v-for="floor in floorList" :key="floor.id" :label="floor.name" :value="floor.id" />
             </el-select>
           </el-form-item>
           <el-form-item label="巡更点类型" prop="pointType">
-            <el-select v-model="pointQuery.pointType" placeholder="请选择类型" clearable style="width: 150px">
+            <el-select v-model="pointQuery.pointType" placeholder="请选择类型" clearable style="width: 180px;">
               <el-option label="普通" value="normal" />
               <el-option label="重点" value="key" />
               <el-option label="紧急" value="emergency" />
             </el-select>
           </el-form-item>
           <el-form-item label="是否启用" prop="isActive">
-            <el-select v-model="pointQuery.isActive" placeholder="请选择" clearable style="width: 150px">
+            <el-select v-model="pointQuery.isActive" placeholder="请选择" clearable style="width: 180px;">
               <el-option label="启用" :value="1" />
               <el-option label="禁用" :value="0" />
             </el-select>
@@ -462,7 +462,7 @@ function getRecordList() {
     recordTotal.value = response.total
     recordLoading.value = false
   })
-  
+
 }
 
 // 重置查询
@@ -775,4 +775,4 @@ onUnmounted(() => {
   background: #000;
   padding: 20px;
 }
-</style>
+</style>

+ 22 - 22
pm_ui/src/views/publicBroadcasting/index5.vue

@@ -8,7 +8,7 @@
             <el-input v-model="zoneQuery.zoneName" placeholder="请输入分区名称" clearable />
           </el-form-item>
           <el-form-item label="分区类型" prop="zoneType">
-            <el-select v-model="zoneQuery.zoneType" placeholder="请选择分区类型" clearable>
+            <el-select v-model="zoneQuery.zoneType" placeholder="请选择分区类型" clearable style="width: 180px;">
               <el-option label="公共区域" value="公共区域" />
               <el-option label="办公区域" value="办公区域" />
               <el-option label="会议室" value="会议室" />
@@ -17,7 +17,7 @@
             </el-select>
           </el-form-item>
           <el-form-item label="楼栋" prop="buildingName">
-            <el-select v-model="zoneQuery.buildingName" placeholder="请选择楼栋" clearable>
+            <el-select v-model="zoneQuery.buildingName" placeholder="请选择楼栋" clearable style="width: 180px;">
               <el-option label="A栋" value="A栋" />
               <el-option label="B栋" value="B栋" />
               <el-option label="C栋" value="C栋" />
@@ -28,7 +28,7 @@
             </el-select>
           </el-form-item>
           <el-form-item label="广播状态" prop="broadcastStatus">
-            <el-select v-model="zoneQuery.broadcastStatus" placeholder="请选择状态" clearable>
+            <el-select v-model="zoneQuery.broadcastStatus" placeholder="请选择状态" clearable style="width: 180px;">
               <el-option label="空闲" :value="0" />
               <el-option label="广播中" :value="1" />
               <el-option label="紧急广播" :value="2" />
@@ -71,11 +71,11 @@
           </el-col>
         </el-row>
 
-        <el-table 
-          v-loading="zoneLoading" 
-          :data="zoneList" 
-          stripe 
-          border 
+        <el-table
+          v-loading="zoneLoading"
+          :data="zoneList"
+          stripe
+          border
           @selection-change="handleSelectionChange"
           :row-class-name="getRowClassName"
         >
@@ -97,8 +97,8 @@
           </el-table-column>
           <el-table-column label="广播状态" prop="broadcastStatus" width="120" align="center">
             <template #default="scope">
-              <el-tag 
-                :type="getBroadcastStatusType(scope.row.broadcastStatus)" 
+              <el-tag
+                :type="getBroadcastStatusType(scope.row.broadcastStatus)"
                 effect="dark"
               >
                 <el-icon v-if="scope.row.broadcastStatus === 1" class="is-loading">
@@ -131,10 +131,10 @@
           </el-table-column>
           <el-table-column label="操作" width="150" fixed="right">
             <template #default="scope">
-              <el-button 
-                link 
-                type="primary" 
-                icon="VideoPlay" 
+              <el-button
+                link
+                type="primary"
+                icon="VideoPlay"
                 @click="handleControl(scope.row)"
                 :disabled="scope.row.isOnline === 0"
               >控制</el-button>
@@ -387,9 +387,9 @@
           </el-radio-group>
         </el-form-item>
         <el-form-item label="选择分区" v-if="emergencyForm.broadcastRange === 2" prop="selectedZones">
-          <el-select 
-            v-model="emergencyForm.selectedZones" 
-            multiple 
+          <el-select
+            v-model="emergencyForm.selectedZones"
+            multiple
             placeholder="请选择分区"
             style="width: 100%"
           >
@@ -444,10 +444,10 @@
 
 <script setup>
 import { ref, reactive, computed } from 'vue'
-import { 
-  listBroadcastZone, 
-  listBroadcastDevice, 
-  updateZoneVolume, 
+import {
+  listBroadcastZone,
+  listBroadcastDevice,
+  updateZoneVolume,
   controlBroadcast,
   emergencyBroadcast,
   getZoneDevices,
@@ -841,4 +841,4 @@ getZoneList()
     transform: rotate(360deg);
   }
 }
-</style>
+</style>

+ 285 - 112
pm_ui/src/views/repairOrder/repairOrder/index.vue

@@ -4,7 +4,9 @@
     <div class="page-header">
       <div class="header-left">
         <h2 class="page-title">
-          <el-icon><Tools /></el-icon>
+          <el-icon>
+            <Tools/>
+          </el-icon>
           维修工单管理
         </h2>
         <el-tag type="info" size="small">共 {{ total }} 条记录</el-tag>
@@ -21,7 +23,9 @@
       <el-col :xs="24" :sm="12" :md="6" v-for="stat in statisticsData" :key="stat.type">
         <div class="stat-card" :class="`stat-${stat.type}`">
           <div class="stat-icon">
-            <el-icon><component :is="stat.icon" /></el-icon>
+            <el-icon>
+              <component :is="stat.icon"/>
+            </el-icon>
           </div>
           <div class="stat-content">
             <div class="stat-value">{{ stat.value }}</div>
@@ -104,10 +108,10 @@
           <el-col :span="24" class="search-buttons">
             <el-button type="primary" :icon="Search" @click="handleQuery">搜索</el-button>
             <el-button :icon="Refresh" @click="resetQuery">重置</el-button>
-            <el-button type="text" @click="toggleAdvanced">
-              {{ showAdvanced ? '收起' : '高级筛选' }}
-              <el-icon><component :is="showAdvanced ? 'ArrowUp' : 'ArrowDown'" /></el-icon>
-            </el-button>
+            <!--            <el-button type="text" @click="toggleAdvanced">
+                          {{ showAdvanced ? '收起' : '高级筛选' }}
+                          <el-icon><component :is="showAdvanced ? 'ArrowUp' : 'ArrowDown'" /></el-icon>
+                        </el-button>-->
           </el-col>
         </el-row>
       </el-form>
@@ -122,7 +126,8 @@
               :disabled="single"
               @click="handleUpdate"
               v-hasPermi="['repairOrder:repairOrder:edit']"
-          >修改</el-button>
+          >修改
+          </el-button>
           <el-button
               type="danger"
               plain
@@ -130,24 +135,26 @@
               :disabled="multiple"
               @click="handleDelete"
               v-hasPermi="['repairOrder:repairOrder:remove']"
-          >删除</el-button>
+          >删除
+          </el-button>
           <el-button
               type="warning"
               plain
               :icon="Download"
               @click="handleExport"
               v-hasPermi="['repairOrder:repairOrder:export']"
-          >导出</el-button>
+          >导出
+          </el-button>
         </div>
         <div class="operations-right">
           <el-tooltip content="刷新" placement="top">
-            <el-button :icon="Refresh" circle @click="getList" />
+            <el-button :icon="Refresh" circle @click="getList"/>
           </el-tooltip>
           <el-tooltip content="显示/隐藏搜索" placement="top">
-            <el-button :icon="showSearch ? View : Hide" circle @click="showSearch = !showSearch" />
+            <el-button :icon="showSearch ? View : Hide" circle @click="showSearch = !showSearch"/>
           </el-tooltip>
           <el-tooltip content="列设置" placement="top">
-            <el-button :icon="Setting" circle />
+            <el-button :icon="Setting" circle/>
           </el-tooltip>
         </div>
       </div>
@@ -163,7 +170,7 @@
           :row-class-name="tableRowClassName"
           :default-sort="{ prop: 'assignTime', order: 'descending' }"
       >
-        <el-table-column type="selection" width="50" align="center" />
+        <el-table-column type="selection" width="50" align="center"/>
         <el-table-column label="工单编号" align="center" prop="orderNo" width="120">
           <template #default="scope">
             <el-link type="primary" @click="showDetail(scope.row)">
@@ -171,12 +178,14 @@
             </el-link>
           </template>
         </el-table-column>
-        <el-table-column label="工单内容" align="left" prop="orderContent" min-width="200" show-overflow-tooltip />
-        <el-table-column label="项目名称" align="center" prop="projectName" min-width="150" show-overflow-tooltip />
+        <el-table-column label="工单内容" align="left" prop="orderContent" min-width="200" show-overflow-tooltip/>
+        <el-table-column label="项目名称" align="center" prop="projectName" min-width="150" show-overflow-tooltip/>
         <el-table-column label="派单时间" align="center" prop="assignTime" width="180" sortable>
           <template #default="scope">
             <div class="time-cell">
-              <el-icon><Clock /></el-icon>
+              <el-icon>
+                <Clock/>
+              </el-icon>
               <span>{{ parseTime(scope.row.assignTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
             </div>
           </template>
@@ -217,7 +226,9 @@
                 link
                 :icon="Paperclip"
                 @click="downloadFile(scope.row.annex)"
-            />
+            >
+              下载
+            </el-button>
             <span v-else class="text-muted">-</span>
           </template>
         </el-table-column>
@@ -237,7 +248,10 @@
             </el-button>
             <el-dropdown trigger="click" style="margin-left: 10px">
               <el-button link type="primary" size="small">
-                更多<el-icon><ArrowDown /></el-icon>
+                更多
+                <el-icon>
+                  <ArrowDown/>
+                </el-icon>
               </el-button>
               <template #dropdown>
                 <el-dropdown-menu>
@@ -300,13 +314,17 @@
         </el-descriptions-item>
         <el-descriptions-item label="派单时间">
           <div class="time-info">
-            <el-icon><Clock /></el-icon>
+            <el-icon>
+              <Clock/>
+            </el-icon>
             {{ parseTime(detailData.assignTime, '{y}-{m}-{d} {h}:{i}:{s}') }}
           </div>
         </el-descriptions-item>
         <el-descriptions-item label="完成时间">
           <div class="time-info" v-if="detailData.finishTime">
-            <el-icon><CircleCheck /></el-icon>
+            <el-icon>
+              <CircleCheck/>
+            </el-icon>
             {{ parseTime(detailData.finishTime, '{y}-{m}-{d} {h}:{i}:{s}') }}
           </div>
           <el-tag v-else type="info" size="small">未完成</el-tag>
@@ -335,10 +353,16 @@
       <div v-if="detailData.annex" class="attachment-section">
         <h4>附件信息</h4>
         <div class="attachment-list">
-          <div class="attachment-item">
-            <el-icon><Document /></el-icon>
-            <span>{{ getFileName(detailData.annex) }}</span>
-            <el-button type="primary" link @click="downloadFile(detailData.annex)">
+          <div
+              v-for="(filePath, index) in getFileList(detailData.annex)"
+              :key="index"
+              class="attachment-item"
+          >
+            <el-icon>
+              <Document/>
+            </el-icon>
+            <span>{{ getFileName(filePath) }}</span>
+            <el-button type="primary" link @click="downloadFile(filePath)">
               下载
             </el-button>
           </div>
@@ -362,7 +386,7 @@
             </div>
           </el-timeline-item>
         </el-timeline>
-        <el-empty v-else description="暂无操作记录" :image-size="80" />
+        <el-empty v-else description="暂无操作记录" :image-size="80"/>
       </div>
     </el-drawer>
 
@@ -385,7 +409,9 @@
                   :disabled="isEdit"
               >
                 <template #prefix>
-                  <el-icon><Tickets /></el-icon>
+                  <el-icon>
+                    <Tickets/>
+                  </el-icon>
                 </template>
               </el-input>
             </el-form-item>
@@ -398,7 +424,9 @@
                   :disabled="isEdit"
               >
                 <template #prefix>
-                  <el-icon><OfficeBuilding /></el-icon>
+                  <el-icon>
+                    <OfficeBuilding/>
+                  </el-icon>
                 </template>
               </el-input>
             </el-form-item>
@@ -417,8 +445,8 @@
           />
         </el-form-item>
 
-        <el-row :gutter="20" v-if="isEdit">
-          <el-col :span="12">
+        <el-row :gutter="20">
+          <el-col :span="12" v-if="isEdit">
             <el-form-item label="派单时间" prop="assignTime">
               <el-date-picker
                   v-model="form.assignTime"
@@ -431,7 +459,7 @@
             </el-form-item>
           </el-col>
           <el-col :span="12">
-            <el-form-item label="责任人" prop="userId" :rules="isEdit ? rules.userId : []">
+            <el-form-item label="责任人" prop="userId">
               <el-select
                   v-model="form.userId"
                   placeholder="请选择责任人"
@@ -457,19 +485,12 @@
         </el-row>
 
         <el-form-item label="附件上传" prop="annex">
-          <div class="upload-container">
-            <file-upload
-                v-model="form.annex"
-                :on-remove="handleRemoveFile"
-                :limit="5"
-                :file-size="20"
-                :disabled="isEdit && form.annex"
-            />
-            <div v-if="isEdit && form.annex" class="upload-tip">
-              <el-icon><InfoFilled /></el-icon>
-              <span>已上传附件,点击文件名可下载</span>
-            </div>
-          </div>
+          <file-upload
+              v-model="form.annex"
+              :on-remove="handleRemoveFile"
+              :limit="5"
+              :file-size="10"
+          />
         </el-form-item>
 
         <el-form-item label="工单备注" prop="orderRemark">
@@ -497,7 +518,7 @@
 </template>
 
 <script setup name="RepairOrder">
-import { ref, reactive, toRefs, onMounted, computed } from 'vue';
+import {ref, reactive, toRefs, onMounted, computed} from 'vue';
 import {
   listRepairOrder,
   getRepairOrder,
@@ -508,8 +529,8 @@ import {
   getRepairOrderStatistics,
   getRepairOrderLogs
 } from "@/api/repairOrder/repairOrder";
-import { listUser } from "@/api/system/user";
-import { ElMessage, ElMessageBox } from 'element-plus';
+import {listUser} from "@/api/system/user";
+import {ElMessage, ElMessageBox, ElLoading} from 'element-plus';
 // 导入图标
 import {
   Tools,
@@ -537,8 +558,8 @@ import {
   Loading
 } from '@element-plus/icons-vue';
 
-const { proxy } = getCurrentInstance();
-const { repair_status } = proxy.useDict('repair_status');
+const {proxy} = getCurrentInstance();
+const {repair_status} = proxy.useDict('repair_status');
 
 // 响应式变量
 const repairOrderList = ref([]);
@@ -634,21 +655,18 @@ const data = reactive({
   },
   rules: {
     orderNo: [
-      { required: true, message: "工单编号不能为空", trigger: "blur" }
+      {required: true, message: "工单编号不能为空", trigger: "blur"}
     ],
     orderContent: [
-      { required: true, message: "工单内容不能为空", trigger: "blur" }
+      {required: true, message: "工单内容不能为空", trigger: "blur"}
     ],
     projectName: [
-      { required: true, message: "项目名称不能为空", trigger: "blur" }
-    ],
-    userId: [
-      { required: true, message: "请选择责任人", trigger: "change" }
+      {required: true, message: "项目名称不能为空", trigger: "blur"}
     ]
   }
 });
 
-const { queryParams, form, rules } = toRefs(data);
+const {queryParams, form, rules} = toRefs(data);
 
 // 获取统计数据
 async function getStatistics() {
@@ -675,44 +693,15 @@ async function getStatistics() {
 async function getOperationLogs(orderId) {
   try {
     // 模拟操作日志数据,实际应该从后端获取
-    operationLogs.value = [
-      {
-        operateTime: detailData.value.createTime,
-        operateUser: detailData.value.createBy || '系统',
-        operateDesc: '创建了工单',
-        operateType: 'create'
-      }
-    ];
-
-    // 如果有派单记录
-    if (detailData.value.finishBy && detailData.value.assignTime) {
-      operationLogs.value.push({
-        operateTime: detailData.value.assignTime,
-        operateUser: detailData.value.updateBy || '管理员',
-        operateDesc: `派单给 ${detailData.value.finishBy}`,
-        operateType: 'assign'
-      });
-    }
-
-    // 如果有完成记录
-    if (detailData.value.finishTime) {
-      operationLogs.value.push({
-        operateTime: detailData.value.finishTime,
-        operateUser: detailData.value.finishBy,
-        operateDesc: '完成了工单',
-        operateType: 'complete'
-      });
-    }
-
-    // 如果有更新记录
-    if (detailData.value.updateTime && detailData.value.updateTime !== detailData.value.createTime) {
-      operationLogs.value.push({
-        operateTime: detailData.value.updateTime,
-        operateUser: detailData.value.updateBy || '系统',
-        operateDesc: '更新了工单信息',
-        operateType: 'update'
-      });
-    }
+    const response = await getRepairOrderLogs(orderId);
+    operationLogs.value = response.data.map(log => ({
+      logId: log.log_id,
+      operateUser: log.operate_user,
+      operateType: log.operate_type,
+      operateDesc: log.operate_desc,
+      orderId: log.order_id,
+      operateTime: log.operate_time
+    }));
 
     // 按时间倒序排序
     operationLogs.value.sort((a, b) => new Date(b.operateTime) - new Date(a.operateTime));
@@ -751,11 +740,14 @@ function getStatusText(status) {
 
 // 判断是否可以派单
 function canDispatch(row) {
+  // admin 可以编辑所有工单
+  const isAdmin = proxy.$auth.hasRole('admin');
+  if (isAdmin) return true;
   return row.orderStatus === '0' || row.orderStatus === '1';
 }
 
 // 表格行样式
-function tableRowClassName({ row }) {
+function tableRowClassName({row}) {
   if (row.orderStatus === '0') {
     return 'warning-row';
   } else if (row.orderStatus === '2') {
@@ -765,10 +757,24 @@ function tableRowClassName({ row }) {
 }
 
 // 获取文件名
+// 将逗号分隔的文件路径字符串转换为数组
+function getFileList(annexString) {
+  if (!annexString) return [];
+  // 分割字符串并去除每个路径的前后空格
+  return annexString.split(',').map(path => path.trim()).filter(path => path);
+}
+
+// 获取文件数量
+function getFileCount(annexString) {
+  if (!annexString) return 0;
+  return getFileList(annexString).length;
+}
+
+// 修改原有的 getFileName 方法(如果需要的话)
 function getFileName(url) {
   if (!url) return '';
-  const parts = url.split('/');
-  return parts[parts.length - 1];
+  const parts = url.trim().split('/');
+  return parts[parts.length - 1] || '附件';
 }
 
 // 切换高级筛选
@@ -778,7 +784,7 @@ function toggleAdvanced() {
 
 // 显示详情
 async function showDetail(row) {
-  detailData.value = { ...row };
+  detailData.value = {...row};
   detailVisible.value = true;
   // 获取操作日志
   await getOperationLogs(row.id);
@@ -797,7 +803,7 @@ function handlePrint() {
 
 // 复制工单
 function handleCopy(row) {
-  const newForm = { ...row };
+  const newForm = {...row};
   delete newForm.id;
   newForm.orderNo = `${row.orderNo}-COPY-${Date.now()}`;
   newForm.orderStatus = '0'; // 复制的工单状态为待处理
@@ -811,19 +817,121 @@ function handleCopy(row) {
 }
 
 // 下载文件
-function downloadFile(url) {
+/*function downloadFile(url) {
   if (!url) {
     ElMessage.warning('暂无附件');
     return;
   }
   window.open(import.meta.env.VITE_APP_BASE_API + url);
+}*/
+// 下载文件
+async function downloadFile(url) {
+  if (!url) {
+    ElMessage.warning('暂无附件');
+    return;
+  }
+
+  // 将逗号分隔的URL字符串转换为数组
+  const urls = url.split(',').map(u => u.trim()).filter(u => u);
+
+  if (urls.length === 0) {
+    ElMessage.warning('暂无附件');
+    return;
+  }
+
+  const loading = ElLoading.service({
+    lock: true,
+    text: `正在下载文件 (0/${urls.length})...`,
+    background: 'rgba(0, 0, 0, 0.7)',
+  });
+
+  let successCount = 0;
+  let failCount = 0;
+
+  try {
+    for (let i = 0; i < urls.length; i++) {
+      const currentUrl = urls[i];
+
+      // 更新加载提示
+      loading.setText(`正在下载文件 (${i + 1}/${urls.length})...`);
+
+      try {
+        const response = await fetch(import.meta.env.VITE_APP_BASE_API + currentUrl, {
+          method: 'GET',
+          headers: {
+            'Authorization': 'Bearer ',// + getToken(),
+          }
+        });
+
+        if (!response.ok) {
+          throw new Error('下载失败');
+        }
+
+        const blob = await response.blob();
+        const fileName = currentUrl.split('/').pop() || `download_${i + 1}`;
+        const fileExt = fileName.split('.').pop().toLowerCase();
+
+        const mimeTypes = {
+          'txt': 'text/plain;charset=utf-8',
+          'pdf': 'application/pdf',
+          'doc': 'application/msword',
+          'docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
+          'xls': 'application/vnd.ms-excel',
+          'xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
+          'ppt': 'application/vnd.ms-powerpoint',
+          'pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation'
+        };
+
+        const typedBlob = new Blob([blob], {
+          type: mimeTypes[fileExt] || 'application/octet-stream'
+        });
+
+        const downloadUrl = window.URL.createObjectURL(typedBlob);
+        const link = document.createElement('a');
+        link.href = downloadUrl;
+        link.download = fileName;
+        link.style.display = 'none';
+
+        document.body.appendChild(link);
+        link.click();
+
+        setTimeout(() => {
+          window.URL.revokeObjectURL(downloadUrl);
+          document.body.removeChild(link);
+        }, 100);
+
+        successCount++;
+
+        // 添加延迟,避免同时触发太多下载
+        if (i < urls.length - 1) {
+          await new Promise(resolve => setTimeout(resolve, 500));
+        }
+      } catch (error) {
+        console.error(`下载文件失败: ${currentUrl}`, error);
+        failCount++;
+      }
+    }
+
+    if (successCount > 0 && failCount === 0) {
+      ElMessage.success(`成功下载 ${successCount} 个文件`);
+    } else if (successCount > 0 && failCount > 0) {
+      ElMessage.warning(`成功下载 ${successCount} 个文件,失败 ${failCount} 个`);
+    } else {
+      ElMessage.error('所有文件下载失败');
+    }
+  } catch (error) {
+    console.error('下载过程出错:', error);
+    ElMessage.error('下载失败,请重试');
+  } finally {
+    loading.close();
+  }
 }
 
 /** 查询维修工单列表 */
 async function getList() {
   loading.value = true;
   try {
-    const params = { ...queryParams.value };
+    const params = {...queryParams.value};
 
     // 处理日期范围
     if (dateRange.value && dateRange.value.length === 2) {
@@ -858,7 +966,8 @@ function handleRemoveFile(file) {
     }).catch(() => {
       ElMessage.error("删除失败");
     });
-  }).catch(() => {});
+  }).catch(() => {
+  });
 }
 
 // 取消按钮
@@ -950,7 +1059,7 @@ async function handleUpdate(row) {
       }
     }
   }
-
+  isEdit.value = true;
 
   const _id = row.id || ids.value;
 
@@ -1092,7 +1201,6 @@ onMounted(() => {
 
   // 统计卡片
   .statistics-cards {
-    margin-bottom: 20px;
 
     .stat-card {
       background: #fff;
@@ -1120,22 +1228,34 @@ onMounted(() => {
 
       &.stat-total {
         color: #409eff;
-        .stat-icon { background: #e6f2ff; }
+
+        .stat-icon {
+          background: #e6f2ff;
+        }
       }
 
       &.stat-pending {
         color: #e6a23c;
-        .stat-icon { background: #fdf6ec; }
+
+        .stat-icon {
+          background: #fdf6ec;
+        }
       }
 
       &.stat-processing {
         color: #909399;
-        .stat-icon { background: #f4f4f5; }
+
+        .stat-icon {
+          background: #f4f4f5;
+        }
       }
 
       &.stat-completed {
         color: #67c23a;
-        .stat-icon { background: #e8f5e9; }
+
+        .stat-icon {
+          background: #e8f5e9;
+        }
       }
 
       .stat-icon {
@@ -1172,8 +1292,13 @@ onMounted(() => {
         right: 20px;
         font-size: 14px;
 
-        .up { color: #67c23a; }
-        .down { color: #f56c6c; }
+        .up {
+          color: #67c23a;
+        }
+
+        .down {
+          color: #f56c6c;
+        }
       }
     }
   }
@@ -1274,9 +1399,17 @@ onMounted(() => {
       height: 8px;
       border-radius: 50%;
 
-      &.status-0 { background-color: #e6a23c; }
-      &.status-1 { background-color: #409eff; }
-      &.status-2 { background-color: #67c23a; }
+      &.status-0 {
+        background-color: #e6a23c;
+      }
+
+      &.status-1 {
+        background-color: #409eff;
+      }
+
+      &.status-2 {
+        background-color: #67c23a;
+      }
     }
   }
 
@@ -1488,4 +1621,44 @@ onMounted(() => {
   padding: 20px;
   background: #fff;
 }
+
+.attachment-section {
+  margin: 20px;
+
+  h4 {
+    margin: 0 0 12px 0;
+    font-size: 16px;
+    color: #303133;
+  }
+
+  .attachment-list {
+    background: #f5f7fa;
+    padding: 16px;
+    border-radius: 4px;
+
+    .attachment-item {
+      display: flex;
+      align-items: center;
+      gap: 8px;
+      padding: 8px 0;
+
+      &:not(:last-child) {
+        border-bottom: 1px solid #e4e7ed;
+      }
+
+      .el-icon {
+        color: #909399;
+        font-size: 20px;
+      }
+
+      span {
+        flex: 1;
+        overflow: hidden;
+        text-overflow: ellipsis;
+        white-space: nowrap;
+      }
+    }
+  }
+}
+
 </style>

+ 467 - 89
pm_ui/src/views/repairOrder/repairOrder/index2.vue

@@ -233,7 +233,7 @@
                 :icon="Paperclip"
                 @click="downloadFile(scope.row.annex)"
             >
-              查看
+              下载
             </el-button>
             <span v-else class="text-muted">-</span>
           </template>
@@ -259,7 +259,7 @@
             >
               处理
             </el-button>
-            <el-button
+<!--            <el-button
                 link
                 type="danger"
                 :icon="Delete"
@@ -267,7 +267,7 @@
                 v-hasPermi="['repairOrder:repairOrder:remove']"
             >
               删除
-            </el-button>
+            </el-button>-->
           </template>
         </el-table-column>
       </el-table>
@@ -284,61 +284,100 @@
     <!-- 工单详情抽屉 -->
     <el-drawer
         v-model="detailVisible"
-        title="工单详情"
+        :title="`工单详情 - ${detailData.orderNo}`"
         direction="rtl"
         size="50%"
         class="detail-drawer"
     >
-      <template #header>
-        <div class="drawer-header">
-          <h3>工单详情</h3>
-          <el-tag :type="getStatusTagType(detailData.orderStatus)" size="large">
-            {{ getStatusLabel(detailData.orderStatus) }}
+      <div class="detail-header">
+        <el-tag :type="getStatusType(detailData.orderStatus)" size="large">
+          {{ getStatusText(detailData.orderStatus) }}
+        </el-tag>
+        <el-button-group>
+          <el-button :icon="Edit" @click="handleUpdateFromDetail" v-if="canDispatch(detailData)">编辑</el-button>
+          <el-button :icon="Printer" @click="handlePrint">打印</el-button>
+        </el-button-group>
+      </div>
+
+      <el-descriptions :column="2" border class="detail-descriptions">
+        <el-descriptions-item label="工单编号">
+          <span class="detail-value">{{ detailData.orderNo }}</span>
+        </el-descriptions-item>
+        <el-descriptions-item label="项目名称">
+          {{ detailData.projectName }}
+        </el-descriptions-item>
+        <el-descriptions-item label="工单内容" :span="2">
+          <div class="content-box">{{ detailData.orderContent }}</div>
+        </el-descriptions-item>
+        <el-descriptions-item label="派单时间">
+          <div class="time-info">
+            <el-icon><Clock /></el-icon>
+            {{ parseTime(detailData.assignTime, '{y}-{m}-{d} {h}:{i}:{s}') }}
+          </div>
+        </el-descriptions-item>
+        <el-descriptions-item label="完成时间">
+          <div class="time-info" v-if="detailData.finishTime">
+            <el-icon><CircleCheck /></el-icon>
+            {{ parseTime(detailData.finishTime, '{y}-{m}-{d} {h}:{i}:{s}') }}
+          </div>
+          <el-tag v-else type="info" size="small">未完成</el-tag>
+        </el-descriptions-item>
+        <el-descriptions-item label="责任人">
+          <div class="user-detail" v-if="detailData.finishBy">
+            <el-avatar>{{ detailData.finishBy.charAt(0) }}</el-avatar>
+            <span>{{ detailData.finishBy }}</span>
+          </div>
+          <el-tag v-else type="warning" size="small">待分配</el-tag>
+        </el-descriptions-item>
+        <el-descriptions-item label="工单状态">
+          <el-tag :type="getStatusType(detailData.orderStatus)" size="small">
+            {{ getStatusText(detailData.orderStatus) }}
           </el-tag>
+        </el-descriptions-item>
+        <el-descriptions-item label="工单备注" :span="2">
+          <div class="remark-box">{{ detailData.orderRemark || '暂无备注' }}</div>
+        </el-descriptions-item>
+        <el-descriptions-item label="创建人">{{ detailData.createBy || '-' }}</el-descriptions-item>
+        <el-descriptions-item label="创建时间">{{ parseTime(detailData.createTime) }}</el-descriptions-item>
+        <el-descriptions-item label="更新人">{{ detailData.updateBy || '-' }}</el-descriptions-item>
+        <el-descriptions-item label="更新时间">{{ parseTime(detailData.updateTime) }}</el-descriptions-item>
+      </el-descriptions>
+
+      <div v-if="detailData.annex" class="attachment-section">
+        <h4>附件信息</h4>
+        <div class="attachment-list">
+          <div
+              v-for="(filePath, index) in getFileList(detailData.annex)"
+              :key="index"
+              class="attachment-item"
+          >
+            <el-icon><Document /></el-icon>
+            <span>{{ getFileName(filePath) }}</span>
+            <el-button type="primary" link @click="downloadFile(filePath)">
+              下载
+            </el-button>
+          </div>
         </div>
-      </template>
+      </div>
 
-      <div class="detail-content">
-        <el-descriptions :column="2" border>
-          <el-descriptions-item label="工单编号" :span="1">
-            <span class="detail-value">{{ detailData.orderNo }}</span>
-          </el-descriptions-item>
-          <el-descriptions-item label="项目名称" :span="1">
-            {{ detailData.projectName }}
-          </el-descriptions-item>
-          <el-descriptions-item label="工单内容" :span="2">
-            {{ detailData.orderContent }}
-          </el-descriptions-item>
-          <el-descriptions-item label="派单时间">
-            {{ parseTime(detailData.assignTime, '{y}-{m}-{d} {h}:{i}:{s}') }}
-          </el-descriptions-item>
-          <el-descriptions-item label="完成时间">
-            {{ detailData.finishTime ? parseTime(detailData.finishTime, '{y}-{m}-{d} {h}:{i}:{s}') : '未完成' }}
-          </el-descriptions-item>
-          <el-descriptions-item label="责任人">
-            <div class="user-info">
-              <el-avatar size="small" :src="getUserAvatar(detailData.finishBy)">
-                {{ detailData.finishBy?.charAt(0) || '无' }}
-              </el-avatar>
-              <span>{{ detailData.finishBy || '未分配' }}</span>
+      <!-- 操作日志 -->
+      <div class="operation-log">
+        <h4>操作记录</h4>
+        <el-timeline v-if="operationLogs.length > 0">
+          <el-timeline-item
+              v-for="(log, index) in operationLogs"
+              :key="index"
+              :timestamp="log.operateTime"
+              :type="getLogType(log.operateType)"
+              placement="top"
+          >
+            <div class="log-content">
+              <span class="log-user">{{ log.operateUser }}</span>
+              <span class="log-action">{{ log.operateDesc }}</span>
             </div>
-          </el-descriptions-item>
-          <el-descriptions-item label="工单状态">
-            <el-tag :type="getStatusTagType(detailData.orderStatus)">
-              {{ getStatusLabel(detailData.orderStatus) }}
-            </el-tag>
-          </el-descriptions-item>
-          <el-descriptions-item label="工单备注" :span="2">
-            {{ detailData.orderRemark || '无' }}
-          </el-descriptions-item>
-        </el-descriptions>
-
-        <div v-if="detailData.annex" class="attachment-section">
-          <h4>附件信息</h4>
-          <el-button type="primary" :icon="Download" @click="downloadFile(detailData.annex)">
-            下载附件
-          </el-button>
-        </div>
+          </el-timeline-item>
+        </el-timeline>
+        <el-empty v-else description="暂无操作记录" :image-size="80" />
       </div>
     </el-drawer>
 
@@ -467,26 +506,36 @@ import {
   delRepairOrder,
   addRepairOrder,
   updateRepairOrder,
-  deleteFile, getRepairOrderStatistics
+  deleteFile, getRepairOrderStatistics,
+  getRepairOrderLogs
 } from "@/api/repairOrder/repairOrder";
 import { listUser } from "@/api/system/user";
-import { ElMessage, ElMessageBox } from 'element-plus';
+import { ElMessage, ElMessageBox, ElLoading } from 'element-plus';
 // 导入图标
 import {
   Tools,
   Plus,
   Download,
-  DocumentCopy,
-  Clock,
-  Loading,
-  CircleCheck,
+  Edit,
+  Delete,
   Search,
   Refresh,
   View,
   Hide,
-  Edit,
-  Delete,
-  Paperclip
+  Setting,
+  Clock,
+  CircleCheck,
+  Paperclip,
+  ArrowDown,
+  ArrowUp,
+  DocumentCopy,
+  Printer,
+  Document,
+  Tickets,
+  OfficeBuilding,
+  InfoFilled,
+  Warning,
+  Loading
 } from '@element-plus/icons-vue';
 
 const { proxy } = getCurrentInstance();
@@ -515,6 +564,8 @@ const statisticsData = ref({
 
 // 统计数据
 const statistics = computed(() => statisticsData.value);
+// 详情相关
+const operationLogs = ref([]); // 操作日志数据
 
 const data = reactive({
   form: {},
@@ -551,6 +602,136 @@ function getStatusTagType(status) {
   return statusMap[status] || 'info';
 }
 
+// 将逗号分隔的文件路径字符串转换为数组
+function getFileList(annexString) {
+  if (!annexString) return [];
+  // 分割字符串并去除每个路径的前后空格
+  return annexString.split(',').map(path => path.trim()).filter(path => path);
+}
+
+// 获取文件数量
+function getFileCount(annexString) {
+  if (!annexString) return 0;
+  return getFileList(annexString).length;
+}
+
+// 修改原有的 getFileName 方法(如果需要的话)
+function getFileName(url) {
+  if (!url) return '';
+  const parts = url.trim().split('/');
+  return parts[parts.length - 1] || '附件';
+}
+// 获取日志类型
+function getLogType(type) {
+  const typeMap = {
+    'create': 'primary',
+    'assign': 'warning',
+    'update': 'info',
+    'complete': 'success'
+  };
+  return typeMap[type] || 'info';
+}
+// 获取状态类型(用于 el-tag 的 type)
+function getStatusType(status) {
+  const statusMap = {
+    '0': 'warning',  // 待处理
+    '1': 'primary',  // 处理中
+    '2': 'success'   // 已完成
+  };
+  return statusMap[status] || 'info';
+}
+
+// 获取状态文本
+function getStatusText(status) {
+  const dict = repair_status.value.find(item => item.value === status);
+  return dict ? dict.label : '未知状态';
+}
+
+// 判断是否可以派单/编辑
+function canDispatch(row) {
+  // admin 可以编辑所有工单
+  const isAdmin = proxy.$auth.hasRole('admin');
+  if (isAdmin) return true;
+
+  // 其他用户只能编辑未完成的工单
+  return row.orderStatus !== '2';
+}
+
+// 从详情页编辑
+function handleUpdateFromDetail() {
+  if (!detailData.value.id) {
+    ElMessage.error('工单信息不完整');
+    return;
+  }
+
+  // 关闭详情抽屉
+  detailVisible.value = false;
+
+  // 调用编辑方法
+  handleUpdate(detailData.value);
+}
+
+// 打印功能
+function handlePrint() {
+  // 方法1:使用浏览器打印
+  window.print();
+
+  // 方法2:打印特定内容
+  // const printContent = document.querySelector('.detail-drawer .el-drawer__body').innerHTML;
+  // const printWindow = window.open('', '_blank');
+  // printWindow.document.write(`
+  //   <html>
+  //     <head>
+  //       <title>工单详情 - ${detailData.value.orderNo}</title>
+  //       <style>
+  //         /* 添加打印样式 */
+  //         body { font-family: Arial, sans-serif; }
+  //         .detail-header { margin-bottom: 20px; }
+  //         /* ... 其他样式 */
+  //       </style>
+  //     </head>
+  //     <body>
+  //       ${printContent}
+  //     </body>
+  //   </html>
+  // `);
+  // printWindow.document.close();
+  // printWindow.print();
+}
+
+
+// 显示详情(修改原有的 showDetail 方法)
+async function showDetail(row) {
+  detailData.value = { ...row };
+  detailVisible.value = true;
+
+  // 获取操作日志(如果有接口的话)
+  await getOperationLogs(row.id);
+}
+
+// 获取操作日志
+async function getOperationLogs(orderId) {
+  try {
+    // 模拟操作日志数据,实际应该从后端获取
+    const response = await getRepairOrderLogs(orderId);
+    operationLogs.value = response.data.map(log => ({
+      logId: log.log_id,
+      operateUser: log.operate_user,
+      operateType: log.operate_type,
+      operateDesc: log.operate_desc,
+      orderId: log.order_id,
+      operateTime: log.operate_time
+    }));
+
+    // 按时间倒序排序
+    operationLogs.value.sort((a, b) => new Date(b.operateTime) - new Date(a.operateTime));
+  } catch (error) {
+    console.error('获取操作日志失败:', error);
+    operationLogs.value = [];
+  }
+}
+
+
 // 获取状态标签文本
 function getStatusLabel(status) {
   const dict = repair_status.value.find(item => item.value === status);
@@ -581,19 +762,107 @@ function disabledDate(time) {
   return false;
 }
 
-// 显示详情
-function showDetail(row) {
-  detailData.value = { ...row };
-  detailVisible.value = true;
-}
-
 // 下载文件
-function downloadFile(url) {
+async function downloadFile(url) {
   if (!url) {
     ElMessage.warning('暂无附件');
     return;
   }
-  window.open(import.meta.env.VITE_APP_BASE_API + url);
+
+  // 将逗号分隔的URL字符串转换为数组
+  const urls = url.split(',').map(u => u.trim()).filter(u => u);
+
+  if (urls.length === 0) {
+    ElMessage.warning('暂无附件');
+    return;
+  }
+
+  const loading = ElLoading.service({
+    lock: true,
+    text: `正在下载文件 (0/${urls.length})...`,
+    background: 'rgba(0, 0, 0, 0.7)',
+  });
+
+  let successCount = 0;
+  let failCount = 0;
+
+  try {
+    for (let i = 0; i < urls.length; i++) {
+      const currentUrl = urls[i];
+
+      // 更新加载提示
+      loading.setText(`正在下载文件 (${i + 1}/${urls.length})...`);
+
+      try {
+        const response = await fetch(import.meta.env.VITE_APP_BASE_API + currentUrl, {
+          method: 'GET',
+          headers: {
+            'Authorization': 'Bearer ' ,// + getToken(),
+          }
+        });
+
+        if (!response.ok) {
+          throw new Error('下载失败');
+        }
+
+        const blob = await response.blob();
+        const fileName = currentUrl.split('/').pop() || `download_${i + 1}`;
+        const fileExt = fileName.split('.').pop().toLowerCase();
+
+        const mimeTypes = {
+          'txt': 'text/plain;charset=utf-8',
+          'pdf': 'application/pdf',
+          'doc': 'application/msword',
+          'docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
+          'xls': 'application/vnd.ms-excel',
+          'xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
+          'ppt': 'application/vnd.ms-powerpoint',
+          'pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation'
+        };
+
+        const typedBlob = new Blob([blob], {
+          type: mimeTypes[fileExt] || 'application/octet-stream'
+        });
+
+        const downloadUrl = window.URL.createObjectURL(typedBlob);
+        const link = document.createElement('a');
+        link.href = downloadUrl;
+        link.download = fileName;
+        link.style.display = 'none';
+
+        document.body.appendChild(link);
+        link.click();
+
+        setTimeout(() => {
+          window.URL.revokeObjectURL(downloadUrl);
+          document.body.removeChild(link);
+        }, 100);
+
+        successCount++;
+
+        // 添加延迟,避免同时触发太多下载
+        if (i < urls.length - 1) {
+          await new Promise(resolve => setTimeout(resolve, 500));
+        }
+      } catch (error) {
+        console.error(`下载文件失败: ${currentUrl}`, error);
+        failCount++;
+      }
+    }
+
+    if (successCount > 0 && failCount === 0) {
+      ElMessage.success(`成功下载 ${successCount} 个文件`);
+    } else if (successCount > 0 && failCount > 0) {
+      ElMessage.warning(`成功下载 ${successCount} 个文件,失败 ${failCount} 个`);
+    } else {
+      ElMessage.error('所有文件下载失败');
+    }
+  } catch (error) {
+    console.error('下载过程出错:', error);
+    ElMessage.error('下载失败,请重试');
+  } finally {
+    loading.close();
+  }
 }
 
 
@@ -637,9 +906,9 @@ async function getStatistics() {
     };
 
     // 计算趋势(这里模拟数据,实际应该对比上期数据)
-    statisticsData.value.forEach(stat => {
+    /*statisticsData.value.forEach(stat => {
       stat.trend = Math.floor(Math.random() * 30) - 15; // 随机生成-15到15的趋势
-    });
+    });*/
   } catch (error) {
     console.error('获取统计数据失败:', error);
   }
@@ -1024,45 +1293,116 @@ onMounted(() => {
   .detail-drawer {
     :deep(.el-drawer__header) {
       margin-bottom: 0;
-      padding-bottom: 20px;
-      border-bottom: 1px solid #ebeef5;
+      padding-bottom: 0;
     }
 
-    .drawer-header {
+    .detail-header {
       display: flex;
       justify-content: space-between;
       align-items: center;
-      width: 100%;
-
-      h3 {
-        margin: 0;
-        font-size: 18px;
-        color: #303133;
-      }
+      padding: 0 20px 20px;
+      border-bottom: 1px solid #ebeef5;
     }
 
-    .detail-content {
-      padding: 20px;
+    .detail-descriptions {
+      margin: 20px;
 
       .detail-value {
         font-weight: 600;
         color: #409eff;
       }
 
-      .attachment-section {
-        margin-top: 30px;
-        padding-top: 20px;
-        border-top: 1px solid #ebeef5;
+      .content-box {
+        background: #f5f7fa;
+        padding: 12px;
+        border-radius: 4px;
+        line-height: 1.6;
+      }
 
-        h4 {
-          margin: 0 0 15px 0;
-          font-size: 16px;
-          color: #303133;
+      .time-info {
+        display: flex;
+        align-items: center;
+        gap: 4px;
+
+        .el-icon {
+          color: #909399;
+        }
+      }
+
+      .user-detail {
+        display: flex;
+        align-items: center;
+        gap: 8px;
+
+        .el-avatar {
+          background-color: #409eff;
+          color: #fff;
+        }
+      }
+
+      .remark-box {
+        background: #fef0f0;
+        padding: 12px;
+        border-radius: 4px;
+        color: #e6a23c;
+      }
+    }
+
+    .attachment-section {
+      margin: 20px;
+
+      h4 {
+        margin: 0 0 12px 0;
+        font-size: 16px;
+        color: #303133;
+      }
+
+      .attachment-list {
+        background: #f5f7fa;
+        padding: 16px;
+        border-radius: 4px;
+
+        .attachment-item {
+          display: flex;
+          align-items: center;
+          gap: 8px;
+
+          .el-icon {
+            color: #909399;
+            font-size: 20px;
+          }
+
+          span {
+            flex: 1;
+          }
+        }
+      }
+    }
+
+    .operation-log {
+      margin: 20px;
+
+      h4 {
+        margin: 0 0 16px 0;
+        font-size: 16px;
+        color: #303133;
+      }
+
+      .log-content {
+        .log-user {
+          font-weight: 500;
+          color: #409eff;
+          margin-right: 8px;
+        }
+
+        .log-action {
+          color: #606266;
         }
       }
     }
   }
 
+
   // 对话框样式
   :deep(.el-dialog) {
     .el-dialog__header {
@@ -1165,4 +1505,42 @@ onMounted(() => {
   padding: 20px;
   background: #fff;
 }
+.attachment-section {
+  margin: 20px;
+
+  h4 {
+    margin: 0 0 12px 0;
+    font-size: 16px;
+    color: #303133;
+  }
+
+  .attachment-list {
+    background: #f5f7fa;
+    padding: 16px;
+    border-radius: 4px;
+
+    .attachment-item {
+      display: flex;
+      align-items: center;
+      gap: 8px;
+      padding: 8px 0;
+
+      &:not(:last-child) {
+        border-bottom: 1px solid #e4e7ed;
+      }
+
+      .el-icon {
+        color: #909399;
+        font-size: 20px;
+      }
+
+      span {
+        flex: 1;
+        overflow: hidden;
+        text-overflow: ellipsis;
+        white-space: nowrap;
+      }
+    }
+  }
+}
 </style>

+ 8 - 8
pm_ui/src/views/rqbjxt/index.vue

@@ -11,7 +11,7 @@
             <el-input v-model="alarmPointQuery.deviceName" placeholder="请输入设备名称" clearable />
           </el-form-item>
           <el-form-item label="报警类型" prop="alarmType">
-            <el-select v-model="alarmPointQuery.alarmType" placeholder="请选择报警类型" clearable style="width: 150px">
+            <el-select v-model="alarmPointQuery.alarmType" placeholder="请选择报警类型" clearable style="width: 180px;">
               <el-option label="入侵检测" value="intrusion" />
               <el-option label="门磁报警" value="door_magnetic" />
               <el-option label="红外探测" value="infrared" />
@@ -20,7 +20,7 @@
             </el-select>
           </el-form-item>
           <el-form-item label="防区" prop="zone">
-            <el-select v-model="alarmPointQuery.zone" placeholder="请选择防区" clearable style="width: 150px">
+            <el-select v-model="alarmPointQuery.zone" placeholder="请选择防区" clearable style="width: 180px;">
               <el-option label="防区1" value="zone1" />
               <el-option label="防区2" value="zone2" />
               <el-option label="防区3" value="zone3" />
@@ -28,7 +28,7 @@
             </el-select>
           </el-form-item>
           <el-form-item label="设备状态" prop="deviceStatus">
-            <el-select v-model="alarmPointQuery.deviceStatus" placeholder="请选择状态" clearable style="width: 150px">
+            <el-select v-model="alarmPointQuery.deviceStatus" placeholder="请选择状态" clearable style="width: 180px;">
               <el-option label="正常" :value="1" />
               <el-option label="报警" :value="2" />
               <el-option label="故障" :value="3" />
@@ -117,7 +117,7 @@
       <el-tab-pane label="实时报警" name="realTimeAlarms">
         <el-form :model="alarmQuery" ref="alarmQueryRef" :inline="true" label-width="80px">
           <el-form-item label="报警级别" prop="alarmLevel">
-            <el-select v-model="alarmQuery.alarmLevel" placeholder="请选择报警级别" clearable style="width: 150px">
+            <el-select v-model="alarmQuery.alarmLevel" placeholder="请选择报警级别" clearable style="width: 180px;">
               <el-option label="低级" :value="1" />
               <el-option label="中级" :value="2" />
               <el-option label="高级" :value="3" />
@@ -125,7 +125,7 @@
             </el-select>
           </el-form-item>
           <el-form-item label="处理状态" prop="handleStatus">
-            <el-select v-model="alarmQuery.handleStatus" placeholder="请选择处理状态" clearable style="width: 150px">
+            <el-select v-model="alarmQuery.handleStatus" placeholder="请选择处理状态" clearable style="width: 180px;">
               <el-option label="未处理" :value="0" />
               <el-option label="处理中" :value="1" />
               <el-option label="已处理" :value="2" />
@@ -133,7 +133,7 @@
             </el-select>
           </el-form-item>
           <el-form-item label="防区" prop="zone">
-            <el-select v-model="alarmQuery.zone" placeholder="请选择防区" clearable style="width: 150px">
+            <el-select v-model="alarmQuery.zone" placeholder="请选择防区" clearable style="width: 180px;">
               <el-option label="防区1" value="zone1" />
               <el-option label="防区2" value="zone2" />
               <el-option label="防区3" value="zone3" />
@@ -250,7 +250,7 @@
             <el-input v-model="zoneQuery.zoneName" placeholder="请输入防区名称" clearable />
           </el-form-item>
           <el-form-item label="防区状态" prop="zoneStatus">
-            <el-select v-model="zoneQuery.zoneStatus" placeholder="请选择状态" clearable style="width: 150px">
+            <el-select v-model="zoneQuery.zoneStatus" placeholder="请选择状态" clearable style="width: 180px;">
               <el-option label="布防" :value="1" />
               <el-option label="撤防" :value="0" />
             </el-select>
@@ -845,4 +845,4 @@ onMounted(() => {
   color: #6b7280;
   font-size: 12px;
 }
-</style>
+</style>

+ 4 - 4
pm_ui/src/views/spafjkxt/index.vue

@@ -14,7 +14,7 @@
             <el-input v-model="cameraQuery.monitorArea" placeholder="请输入监控区域" clearable />
           </el-form-item>
           <el-form-item label="楼栋" prop="building">
-            <el-select v-model="cameraQuery.building" placeholder="请选择楼栋" clearable style="width: 150px">
+            <el-select v-model="cameraQuery.building" placeholder="请选择楼栋" clearable style="width: 180px;">
               <el-option label="A栋" value="A" />
               <el-option label="B栋" value="B" />
               <el-option label="C栋" value="C" />
@@ -22,7 +22,7 @@
             </el-select>
           </el-form-item>
           <el-form-item label="设备状态" prop="deviceStatus">
-            <el-select v-model="cameraQuery.deviceStatus" placeholder="请选择状态" clearable style="width: 150px">
+            <el-select v-model="cameraQuery.deviceStatus" placeholder="请选择状态" clearable style="width: 180px;">
               <el-option label="在线" :value="1" />
               <el-option label="离线" :value="0" />
               <el-option label="故障" :value="2" />
@@ -106,7 +106,7 @@
             <el-input v-model="linkageQuery.linkageCamera" placeholder="请输入联动摄像机" clearable />
           </el-form-item>
           <el-form-item label="规则状态" prop="ruleStatus">
-            <el-select v-model="linkageQuery.ruleStatus" placeholder="请选择状态" clearable style="width: 150px">
+            <el-select v-model="linkageQuery.ruleStatus" placeholder="请选择状态" clearable style="width: 180px;">
               <el-option label="启用" :value="1" />
               <el-option label="禁用" :value="0" />
             </el-select>
@@ -765,4 +765,4 @@ onMounted(() => {
 .zoom-controls .el-button, .focus-controls .el-button {
   margin: 0 10px;
 }
-</style>
+</style>

+ 8 - 8
pm_ui/src/views/zmxt/index.vue

@@ -5,7 +5,7 @@
       <el-tab-pane label="照明监控" name="monitor">
         <el-form :model="lightingQuery" ref="lightingQueryRef" :inline="true" label-width="80px">
           <el-form-item label="楼栋" prop="buildingId">
-            <el-select v-model="lightingQuery.buildingId" placeholder="请选择楼栋" clearable @change="handleBuildingChange">
+            <el-select v-model="lightingQuery.buildingId" placeholder="请选择楼栋" clearable @change="handleBuildingChange" style="width: 180px;">
               <el-option
                   v-for="item in buildingList"
                   :key="item.id"
@@ -15,7 +15,7 @@
             </el-select>
           </el-form-item>
           <el-form-item label="楼层" prop="floorId">
-            <el-select v-model="lightingQuery.floorId" placeholder="请选择楼层" clearable :disabled="!lightingQuery.buildingId">
+            <el-select v-model="lightingQuery.floorId" placeholder="请选择楼层" clearable :disabled="!lightingQuery.buildingId" style="width: 180px;">
               <el-option
                   v-for="item in floorList"
                   :key="item.id"
@@ -28,13 +28,13 @@
             <el-input v-model="lightingQuery.areaName" placeholder="请输入区域名称" clearable />
           </el-form-item>
           <el-form-item label="状态" prop="status">
-            <el-select v-model="lightingQuery.status" placeholder="请选择状态" clearable>
+            <el-select v-model="lightingQuery.status" placeholder="请选择状态" clearable style="width: 180px;">
               <el-option label="关闭" :value="0" />
               <el-option label="开启" :value="1" />
             </el-select>
           </el-form-item>
           <el-form-item label="控制模式" prop="controlMode">
-            <el-select v-model="lightingQuery.controlMode" placeholder="请选择控制模式" clearable>
+            <el-select v-model="lightingQuery.controlMode" placeholder="请选择控制模式" clearable style="width: 180px;">
               <el-option label="手动" :value="0" />
               <el-option label="自动" :value="1" />
               <el-option label="定时" :value="2" />
@@ -144,7 +144,7 @@
             <el-input v-model="alarmQuery.deviceName" placeholder="请输入设备名称" clearable />
           </el-form-item>
           <el-form-item label="报警类型" prop="alarmType">
-            <el-select v-model="alarmQuery.alarmType" placeholder="请选择报警类型" clearable>
+            <el-select v-model="alarmQuery.alarmType" placeholder="请选择报警类型" clearable style="width: 180px;">
               <el-option label="通信故障" value="communication" />
               <el-option label="灯具故障" value="lamp" />
               <el-option label="线路故障" value="circuit" />
@@ -152,14 +152,14 @@
             </el-select>
           </el-form-item>
           <el-form-item label="报警级别" prop="alarmLevel">
-            <el-select v-model="alarmQuery.alarmLevel" placeholder="请选择报警级别" clearable>
+            <el-select v-model="alarmQuery.alarmLevel" placeholder="请选择报警级别" clearable style="width: 180px;">
               <el-option label="提示" :value="1" />
               <el-option label="一般" :value="2" />
               <el-option label="严重" :value="3" />
             </el-select>
           </el-form-item>
           <el-form-item label="处理状态" prop="handleStatus">
-            <el-select v-model="alarmQuery.handleStatus" placeholder="请选择处理状态" clearable>
+            <el-select v-model="alarmQuery.handleStatus" placeholder="请选择处理状态" clearable style="width: 180px;">
               <el-option label="未处理" :value="0" />
               <el-option label="已处理" :value="1" />
             </el-select>
@@ -813,4 +813,4 @@ onMounted(() => {
 .mx8 {
   margin: 0 8px;
 }
-</style>
+</style>

+ 56 - 0
sql/2025613企业信息.sql

@@ -0,0 +1,56 @@
+-- 企业信息表
+CREATE TABLE sys_enterprise (
+                                enterprise_id BIGINT NOT NULL AUTO_INCREMENT COMMENT '企业ID',
+                                enterprise_name VARCHAR(100) NOT NULL COMMENT '企业名称',
+                                credit_code VARCHAR(50) UNIQUE COMMENT '统一社会信用代码',
+                                legal_person VARCHAR(50) COMMENT '法定代表人',
+                                registered_capital DECIMAL(15,2) COMMENT '注册资本(万元)',
+                                establish_date DATE COMMENT '成立日期',
+                                business_scope TEXT COMMENT '经营范围',
+                                registered_address VARCHAR(500) COMMENT '注册地址',
+                                office_address VARCHAR(500) COMMENT '办公地址',
+                                contact_phone VARCHAR(20) COMMENT '联系电话',
+                                email VARCHAR(100) COMMENT '邮箱',
+                                website VARCHAR(200) COMMENT '官网',
+                                enterprise_status CHAR(1) DEFAULT '0' COMMENT '企业状态(0正常 1注销 2吊销)',
+                                cert_status CHAR(1) DEFAULT '0' COMMENT '认证状态(0未认证 1已认证)',
+                                remark VARCHAR(500) COMMENT '备注',
+                                create_by VARCHAR(64) COMMENT '创建者',
+                                create_time DATETIME COMMENT '创建时间',
+                                update_by VARCHAR(64) COMMENT '更新者',
+                                update_time DATETIME COMMENT '更新时间',
+                                PRIMARY KEY (enterprise_id)
+) COMMENT = '企业信息表';
+
+
+-- 插入15条企业信息模拟数据
+INSERT INTO sys_enterprise (enterprise_name, credit_code, legal_person, registered_capital, establish_date, business_scope, registered_address, office_address, contact_phone, email, website, enterprise_status, cert_status, remark, create_by, create_time) VALUES
+('北京阿里巴巴科技有限公司', '91110000MA01234567', '张勇', 50000.00, '2010-03-15', '互联网信息服务;技术开发、技术推广、技术转让、技术咨询、技术服务;软件开发;计算机系统服务;数据处理;基础软件服务;应用软件服务', '北京市海淀区中关村软件园二期10号楼', '北京市海淀区中关村软件园二期10号楼', '13800138001', 'contact@alibaba-bj.com', 'https://www.alibaba.com', '0', '1', '互联网龙头企业', 'admin', NOW()),
+
+('深圳腾讯计算机系统有限公司', '91440300279467732L', '马化腾', 100000.00, '1998-11-11', '计算机软硬件的技术开发;计算机技术咨询;计算机数据库服务;通信设备、计算机软硬件及周边设备的销售;从事广告业务', '深圳市南山区科技中一路腾讯大厦', '深圳市南山区科技中一路腾讯大厦35-37楼', '13900139001', 'service@tencent.com', 'https://www.tencent.com', '0', '1', '社交软件巨头', 'admin', NOW()),
+
+('杭州海康威视数字技术股份有限公司', '91330000142104391J', '陈宗年', 12000.00, '2001-11-30', '电子产品及通信设备的技术开发、技术服务、成果转让;计算机软件的技术开发;安防设备、电子产品的制造、销售', '杭州市滨江区阡陌路555号', '杭州市滨江区阡陌路555号海康威视科技园', '13700137001', 'info@hikvision.com', 'https://www.hikvision.com', '0', '1', '安防监控领域领军企业', 'admin', NOW()),
+
+('上海美团网络科技有限公司', '91310000MA1FL5E15X', '王兴', 8000.00, '2010-03-04', '网络科技、计算机科技领域内的技术开发、技术咨询、技术服务、技术转让;设计、制作各类广告;电子商务', '上海市黄浦区淮海中路138号上海广场', '上海市黄浦区淮海中路138号上海广场20楼', '13600136001', 'contact@meituan.com', 'https://www.meituan.com', '0', '1', '本地生活服务平台', 'admin', NOW()),
+
+('北京字节跳动科技有限公司', '91110108MA00CEQX7E', '张一鸣', 15000.00, '2012-03-09', '技术开发、技术推广、技术转让、技术咨询、技术服务;软件开发;软件咨询;计算机系统服务;应用软件服务', '北京市海淀区北三环西路甲18号', '北京市海淀区知春路甲48号盈都大厦C座', '13500135001', 'hr@bytedance.com', 'https://www.bytedance.com', '0', '1', '短视频和信息分发平台', 'admin', NOW()),
+
+('广州小鹏汽车科技有限公司', '91440101MA59M2E66D', '何小鹏', 25000.00, '2014-06-11', '新能源汽车整车制造;汽车零部件及配件制造;软件开发;人工智能应用软件开发;新能源汽车电附件销售', '广州市番禺区化龙镇潮龙村潮龙路2号', '广州市天河区珠江新城花城大道85号高德置地广场', '13400134001', 'service@xiaopeng.com', 'https://www.xiaopeng.com', '0', '1', '智能电动汽车制造商', 'admin', NOW()),
+
+('苏州金螳螂建筑装饰股份有限公司', '91320500138218176H', '朱兴良', 8500.00, '1993-01-08', '建筑装饰装修工程设计与施工;建筑幕墙工程设计与施工;园林古建筑工程施工;机电设备安装工程施工', '苏州市吴中区木渎镇金山南路205号', '苏州市吴中区木渎镇金山南路205号金螳螂大厦', '13300133001', 'info@goldmantis.com', 'https://www.goldmantis.com', '0', '1', '建筑装饰行业龙头', 'admin', NOW()),
+
+('青岛海尔智家股份有限公司', '91370200163063799Q', '梁海山', 7200.00, '1984-12-26', '家用电器制造;智能家居设备制造;物联网设备制造;家用电器销售;智能家居设备销售', '青岛市崂山区海尔路1号', '青岛市崂山区海尔路1号海尔工业园', '13200132001', 'service@haier.com', 'https://www.haier.com', '0', '1', '智能家电制造商', 'admin', NOW()),
+
+('宁德时代新能源科技股份有限公司', '91350900MA2XN2KQ1E', '曾毓群', 50000.00, '2011-12-16', '锂离子电池及其他电池、充电器、电源管理系统及相关设备的研发、生产和销售;新能源汽车动力电池回收', '福建省宁德市蕉城区漳湾镇新港路2号', '福建省宁德市蕉城区漳湾镇新港路2号时代大厦', '13100131001', 'info@catl.com', 'https://www.catl.com', '0', '1', '动力电池全球领军企业', 'admin', NOW()),
+
+('成都新希望六和饲料有限公司', '91510100201812456X', '刘永好', 3000.00, '1995-05-20', '饲料生产、销售;农副产品收购、销售;畜禽养殖技术咨询服务;食品生产、销售', '成都市锦江区红星路三段1号IFS国际金融中心', '成都市锦江区红星路三段1号IFS国际金融中心二期T2-42楼', '13000130001', 'contact@newhopegroup.com', 'https://www.newhopegroup.com', '0', '1', '农牧食品行业领军企业', 'admin', NOW()),
+
+('武汉东湖新技术开发区光谷软件园发展有限公司', '91420100MA4KL8N95F', '李华', 1500.00, '2016-08-12', '软件开发;信息系统集成服务;信息技术咨询服务;数据处理和存储服务;互联网信息服务', '武汉市东湖新技术开发区关山大道1号', '武汉市东湖新技术开发区关山大道1号光谷软件园A1栋', '12900129001', 'info@opticvalley.com', 'https://www.opticvalley.com', '0', '0', '光谷软件园区运营商', 'admin', NOW()),
+
+('西安中兴新软件有限责任公司', '91610131MA6U2E8G7K', '王建军', 2000.00, '2018-03-25', '计算机软件开发;系统集成;技术咨询;技术服务;计算机及辅助设备销售;通信设备销售', '西安市高新区科技路37号海星城市广场', '西安市高新区科技路37号海星城市广场A座15楼', '12800128001', 'hr@zte-xa.com', 'https://www.zte.com.cn', '0', '0', '通信软件开发企业', 'admin', NOW()),
+
+('长沙比亚迪汽车有限公司', '91430100MA4L5RQ66Y', '王传福', 18000.00, '2009-07-15', '新能源汽车整车制造;传统燃油汽车整车制造;汽车零部件及配件制造;电池制造;充电桩销售', '长沙市雨花区万家丽南路二段688号', '长沙市雨花区万家丽南路二段688号比亚迪工业园', '12700127001', 'service@byd.com', 'https://www.byd.com', '0', '1', '新能源汽车制造商', 'admin', NOW()),
+
+('厦门美图网科技有限公司', '91350200MA2XKQH95L', '吴欣鸿', 800.00, '2008-10-08', '软件开发;互联网信息服务;移动互联网研发和维护;图像处理软件开发;人工智能软件开发', '厦门市思明区软件园二期观日路26号', '厦门市思明区软件园二期观日路26号美图大厦', '12600126001', 'contact@meitu.com', 'https://www.meitu.com', '0', '1', '图像处理软件开发商', 'admin', NOW()),
+
+('重庆长安汽车股份有限公司', '91500000203817160E', '朱华荣', 40000.00, '1996-10-31', '汽车(含轿车)和摩托车的开发、制造、销售;汽车发动机的开发、制造、销售;汽车零部件开发、制造、销售', '重庆市江北区建新东路260号', '重庆市江北区建新东路260号长安汽车全球研发中心', '12500125001', 'info@changan.com.cn', 'https://www.changan.com.cn', '0', '1', '自主品牌汽车制造商', 'admin', NOW());

Some files were not shown because too many files changed in this diff