浏览代码

语音播报

bihuisong 7 月之前
父节点
当前提交
71ecbbf600

+ 12 - 0
zhjq-admin/pom.xml

@@ -61,6 +61,18 @@
             <artifactId>zhjq-generator</artifactId>
         </dependency>
 
+        <!-- zxing生成二维码 -->
+        <dependency>
+            <groupId>com.google.zxing</groupId>
+            <artifactId>core</artifactId>
+            <version>3.3.3</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.google.zxing</groupId>
+            <artifactId>javase</artifactId>
+            <version>3.3.3</version>
+        </dependency>
     </dependencies>
 
     <build>

+ 89 - 0
zhjq-admin/src/main/java/com/zhjq/web/controller/system/QrCodeController.java

@@ -0,0 +1,89 @@
+package com.zhjq.web.controller.system;
+
+
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.extra.qrcode.QrCodeUtil;
+import cn.hutool.extra.qrcode.QrConfig;
+import com.google.zxing.client.j2se.MatrixToImageWriter;
+import com.google.zxing.common.BitMatrix;
+import com.zhjq.common.config.RuoYiConfig;
+import com.zhjq.common.core.domain.AjaxResult;
+import com.zhjq.common.exception.ServiceException;
+import com.zhjq.common.utils.file.FileUploadUtils;
+import com.zhjq.common.utils.file.MimeTypeUtils;
+import com.zhjq.system.domain.QrCodeGenerateDTO;
+import com.zhjq.system.domain.ZhjqVoice;
+import com.zhjq.system.service.IZhjqVoiceService;
+import com.zhjq.web.controller.tool.InMemoryMultipartFile;
+import com.zhjq.web.controller.tool.QRCodeUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.imageio.ImageIO;
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.file.*;
+import java.nio.file.attribute.BasicFileAttributes;
+
+import static cn.hutool.core.img.ImgUtil.IMAGE_TYPE_JPG;
+
+
+@RestController
+@RequestMapping("/infra/QrCode")
+public class QrCodeController {
+
+    @Autowired
+    private IZhjqVoiceService zhjqVoiceService;
+
+    private final String reportFileUrlWin = "D:\\qrcode\\";
+
+    private final String reportFileUrlLinux = "/u01/qrcode/";
+
+
+    @PostMapping("/generate")
+    public AjaxResult generate(@RequestBody QrCodeGenerateDTO dto) {
+        String content = dto.getVoiceUrl();
+        if (StrUtil.isBlank(content)) {
+            throw new ServiceException();
+        }
+        String base64 = QrCodeUtil.generateAsBase64(content, QrConfig.create(), IMAGE_TYPE_JPG);
+        return AjaxResult.success(base64);
+    }
+
+    @PostMapping("/getQcCode")
+    public AjaxResult getQcCode(@RequestBody QrCodeGenerateDTO dto) {
+        byte[] qrCodeBytes = null;
+        String fileName = dto.getVoiceName() + ".png";
+        try {
+            qrCodeBytes = generateQRCodeBytes(dto.getVoiceUrl());
+            // 创建 MultipartFile 对象
+            MultipartFile file = new InMemoryMultipartFile("file", fileName, "image/png", qrCodeBytes);
+            String avatar = FileUploadUtils.upload(RuoYiConfig.getQrcodePath(), file, MimeTypeUtils.IMAGE_EXTENSION);
+            ZhjqVoice zhjqVoice = new ZhjqVoice();
+            zhjqVoice.setId(dto.getId());
+            zhjqVoice.setQrCodeUrl(avatar);
+            zhjqVoiceService.updateZhjqVoice(zhjqVoice);
+            return AjaxResult.success();
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public static BufferedImage generateQRCodeImage(String text) throws IOException {
+        BitMatrix bitMatrix = QRCodeUtils.createCode(text);
+        return MatrixToImageWriter.toBufferedImage(bitMatrix);
+    }
+
+    public static byte[] generateQRCodeBytes(String text) throws IOException {
+        BufferedImage image = generateQRCodeImage(text);
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        ImageIO.write(image, "png", outputStream);
+        return outputStream.toByteArray();
+    }
+
+}

+ 116 - 0
zhjq-admin/src/main/java/com/zhjq/web/controller/system/ZhjqVoiceController.java

@@ -0,0 +1,116 @@
+package com.zhjq.web.controller.system;
+
+import cn.hutool.core.io.FileUtil;
+import com.zhjq.common.annotation.Log;
+import com.zhjq.common.core.controller.BaseController;
+import com.zhjq.common.core.domain.AjaxResult;
+import com.zhjq.common.core.page.TableDataInfo;
+import com.zhjq.common.enums.BusinessType;
+import com.zhjq.common.utils.DateUtils;
+import com.zhjq.common.utils.poi.ExcelUtil;
+import com.zhjq.system.domain.ZhjqVoice;
+import com.zhjq.system.service.IZhjqVoiceService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.URLEncoder;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 语音播报Controller
+ *
+ * @author ruoyi
+ * @date 2024-12-13
+ */
+@RestController
+@RequestMapping("/system/voice")
+public class ZhjqVoiceController extends BaseController {
+    @Autowired
+    private IZhjqVoiceService zhjqVoiceService;
+
+    /**
+     * 查询语音播报列表
+     */
+    @GetMapping("/list")
+    public TableDataInfo list(ZhjqVoice zhjqVoice) {
+        startPage();
+        List<ZhjqVoice> list = zhjqVoiceService.selectZhjqVoiceList(zhjqVoice);
+        return getDataTable(list);
+    }
+
+//    /**
+//     * 导出语音播报列表
+//     */
+//    @Log(title = "语音播报", businessType = BusinessType.EXPORT)
+//    @PostMapping("/export")
+//    public void export(HttpServletResponse response, ZhjqVoice zhjqVoice) {
+//        List<ZhjqVoice> list = zhjqVoiceService.selectZhjqVoiceList(zhjqVoice);
+//        ExcelUtil<ZhjqVoice> util = new ExcelUtil<ZhjqVoice>(ZhjqVoice.class);
+//        util.exportExcel(response, list, "语音播报数据");
+//    }
+
+    /**
+     * 获取语音播报详细信息
+     */
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id) {
+        return success(zhjqVoiceService.selectZhjqVoiceById(id));
+    }
+
+    /**
+     * 新增语音播报
+     */
+    @Log(title = "语音播报", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody ZhjqVoice zhjqVoice) {
+        return toAjax(zhjqVoiceService.insertZhjqVoice(zhjqVoice));
+    }
+
+    /**
+     * 修改语音播报
+     */
+    @Log(title = "语音播报", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody ZhjqVoice zhjqVoice) {
+        return toAjax(zhjqVoiceService.updateZhjqVoice(zhjqVoice));
+    }
+
+    /**
+     * 删除语音播报
+     */
+    @Log(title = "语音播报", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids) {
+        return toAjax(zhjqVoiceService.deleteZhjqVoiceByIds(ids));
+    }
+
+    /**
+     * 导出景区播报导出列表
+     */
+    @Log(title = "导出景区播报导出列表", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public AjaxResult export(HttpServletResponse response, ZhjqVoice zhjqVoice) throws IOException {
+        try{
+            String fileName = "景区播报统计" + DateUtils.dateTimeNow() + ".xlsx";
+            Map<String, String> map = zhjqVoiceService.export(response, zhjqVoice);
+            File file = new File(map.get("localFilePath"));
+            response.setContentType("application/octet-stream;charset=utf-8");
+            String responseName = URLEncoder.encode(fileName, "UTF-8");
+            response.setHeader("Content-Disposition", "attachment;filename=" + responseName);
+            OutputStream toClient = new BufferedOutputStream(response.getOutputStream());
+            toClient.write(FileUtil.readBytes(file));
+            toClient.flush();
+            toClient.close();
+        }catch (Exception e){
+            e.printStackTrace();
+        }
+        return null;
+    }
+}

+ 63 - 0
zhjq-admin/src/main/java/com/zhjq/web/controller/tool/InMemoryMultipartFile.java

@@ -0,0 +1,63 @@
+package com.zhjq.web.controller.tool;
+
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+
+public class InMemoryMultipartFile implements MultipartFile {
+
+    private final String name;
+    private final String originalFilename;
+    private final String contentType;
+    private final byte[] content;
+
+    public InMemoryMultipartFile(String name, String originalFilename, String contentType, byte[] content) {
+        this.name = name;
+        this.originalFilename = originalFilename;
+        this.contentType = contentType;
+        this.content = content;
+    }
+
+    @Override
+    public String getName() {
+        return name;
+    }
+
+    @Override
+    public String getOriginalFilename() {
+        return originalFilename;
+    }
+
+    @Override
+    public String getContentType() {
+        return contentType;
+    }
+
+    @Override
+    public boolean isEmpty() {
+        return content.length == 0;
+    }
+
+    @Override
+    public long getSize() {
+        return content.length;
+    }
+
+    @Override
+    public byte[] getBytes() {
+        return content;
+    }
+
+    @Override
+    public InputStream getInputStream() throws IOException {
+        return new ByteArrayInputStream(content);
+    }
+
+    @Override
+    public void transferTo(File dest) throws IOException, IllegalStateException {
+        throw new UnsupportedOperationException("Not implemented");
+    }
+}

+ 70 - 0
zhjq-admin/src/main/java/com/zhjq/web/controller/tool/QRCodeUtils.java

@@ -0,0 +1,70 @@
+package com.zhjq.web.controller.tool;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.EncodeHintType;
+import com.google.zxing.MultiFormatWriter;
+import com.google.zxing.WriterException;
+import com.google.zxing.client.j2se.MatrixToImageWriter;
+import com.google.zxing.common.BitMatrix;
+import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
+
+public class QRCodeUtils {
+
+    /**
+     *  生成二维码
+     * @param content 二维码的内容
+     * @return BitMatrix对象
+     * */
+    public static BitMatrix createCode(String content) throws IOException {
+        //二维码的宽高
+        int width = 200;
+        int height = 200;
+
+        //其他参数,如字符集编码
+        Map<EncodeHintType, Object> hints = new HashMap<EncodeHintType, Object>();
+        hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
+        //容错级别为H
+        hints.put(EncodeHintType.ERROR_CORRECTION , ErrorCorrectionLevel.H);
+        //白边的宽度,可取0~4
+        hints.put(EncodeHintType.MARGIN , 0);
+
+        BitMatrix bitMatrix = null;
+        try {
+            //生成矩阵,因为我的业务场景传来的是编码之后的URL,所以先解码
+            bitMatrix = new MultiFormatWriter().encode(content,
+                    BarcodeFormat.QR_CODE, width, height, hints);
+
+            //bitMatrix = deleteWhite(bitMatrix);
+        } catch (WriterException e) {
+            e.printStackTrace();
+        }
+
+        return bitMatrix;
+    }
+
+    /**
+     *  删除生成的二维码周围的白边,根据审美决定是否删除
+     * @param matrix BitMatrix对象
+     * @return BitMatrix对象
+     * */
+    private static BitMatrix deleteWhite(BitMatrix matrix) {
+        int[] rec = matrix.getEnclosingRectangle();
+        int resWidth = rec[2] + 1;
+        int resHeight = rec[3] + 1;
+
+        BitMatrix resMatrix = new BitMatrix(resWidth, resHeight);
+        resMatrix.clear();
+        for (int i = 0; i < resWidth; i++) {
+            for (int j = 0; j < resHeight; j++) {
+                if (matrix.get(i + rec[0], j + rec[1]))
+                    resMatrix.set(i, j);
+            }
+        }
+        return resMatrix;
+    }
+}

+ 11 - 0
zhjq-common/pom.xml

@@ -119,6 +119,17 @@
             <artifactId>javax.servlet-api</artifactId>
         </dependency>
 
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <version>1.18.26</version>
+        </dependency>
+
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-all</artifactId>
+            <version>5.8.15</version>
+        </dependency>
     </dependencies>
 
 </project>

+ 7 - 0
zhjq-common/src/main/java/com/zhjq/common/config/RuoYiConfig.java

@@ -75,6 +75,13 @@ public class RuoYiConfig {
     }
 
     /**
+     * 获取头像上传路径
+     */
+    public static String getQrcodePath() {
+        return getProfile() + "/qrcode";
+    }
+
+    /**
      * 获取下载路径
      */
     public static String getDownloadPath() {

+ 52 - 0
zhjq-common/src/main/java/com/zhjq/common/utils/file/ImageUtils.java

@@ -76,4 +76,56 @@ public class ImageUtils {
             IOUtils.closeQuietly(in);
         }
     }
+
+    /**
+     * 读取文件为字节数据
+     *
+     * @param url 地址
+     * @return 字节数据
+     */
+    public static byte[] readQrcodeFile(String url) {
+        InputStream in = null;
+        try {
+            if (url.startsWith("http")) {
+                // 网络地址
+                URL urlObj = new URL(url);
+                URLConnection urlConnection = urlObj.openConnection();
+                urlConnection.setConnectTimeout(30 * 1000);
+                urlConnection.setReadTimeout(60 * 1000);
+                urlConnection.setDoInput(true);
+                in = urlConnection.getInputStream();
+            } else {
+                // 本机地址
+                String localPath = RuoYiConfig.getQrcodePath();
+                String downloadPath = StringUtils.substringBefore(localPath, "/qrcode") + StringUtils.substringAfter(url, Constants.RESOURCE_PREFIX);
+                in = new FileInputStream(downloadPath);
+            }
+            return IOUtils.toByteArray(in);
+        } catch (Exception e) {
+            log.error("获取文件路径异常 {}", e);
+            return null;
+        } finally {
+            IOUtils.closeQuietly(in);
+        }
+    }
+
+    /**
+     * 判断操作系统是否是 Windows
+     *
+     * @return true:操作系统是 Windows
+     * false:其它操作系统
+     */
+    public static boolean isWindows() {
+        String osName = getOsName();
+        return osName != null && osName.startsWith("Windows");
+    }
+
+    /**
+     * 获取操作系统名称
+     *
+     * @return os.name 属性值
+     */
+    public static String getOsName() {
+        return System.getProperty("os.name");
+    }
 }

+ 1 - 1
zhjq-common/src/main/java/com/zhjq/common/utils/file/MimeTypeUtils.java

@@ -33,7 +33,7 @@ public class MimeTypeUtils {
             // 压缩文件
             "rar", "zip", "gz", "bz2",
             // 视频格式
-            "mp4", "avi", "rmvb",
+            "mp3", "mp4", "avi", "rmvb" ,
             // pdf
             "pdf"};
 

+ 18 - 0
zhjq-system/src/main/java/com/zhjq/system/domain/QrCodeGenerateDTO.java

@@ -0,0 +1,18 @@
+package com.zhjq.system.domain;
+
+
+import lombok.Data;
+
+/**
+ * @author pepis
+ * @apiNote
+ **/
+@Data
+public class QrCodeGenerateDTO {
+
+    private Long id;
+
+    private String voiceName;
+
+    private String voiceUrl;
+}

+ 85 - 0
zhjq-system/src/main/java/com/zhjq/system/domain/ZhjqVoice.java

@@ -0,0 +1,85 @@
+package com.zhjq.system.domain;
+
+import com.zhjq.common.annotation.Excel;
+import com.zhjq.common.core.domain.BaseEntity;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+
+/**
+ * 语音播报对象 zhjq_voice
+ *
+ * @author ruoyi
+ * @date 2024-12-13
+ */
+public class ZhjqVoice extends BaseEntity {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键id
+     */
+    private Long id;
+
+    /**
+     * 景点名称
+     */
+    @Excel(name = "景点名称")
+    private String voiceName;
+
+    /**
+     * 语音地址
+     */
+    private String voiceUrl;
+
+    /**
+     * 二维码
+     */
+    @Excel(name = "二维码")
+    private String qrCodeUrl;
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setVoiceName(String voiceName) {
+        this.voiceName = voiceName;
+    }
+
+    public String getVoiceName() {
+        return voiceName;
+    }
+
+    public void setVoiceUrl(String voiceUrl) {
+        this.voiceUrl = voiceUrl;
+    }
+
+    public String getVoiceUrl() {
+        return voiceUrl;
+    }
+
+    public void setQrCodeUrl(String qrCodeUrl) {
+        this.qrCodeUrl = qrCodeUrl;
+    }
+
+    public String getQrCodeUrl() {
+        return qrCodeUrl;
+    }
+
+    @Override
+    public String toString() {
+        return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
+                .append("id", getId())
+                .append("voiceName", getVoiceName())
+                .append("voiceUrl", getVoiceUrl())
+                .append("qrCodeUrl", getQrCodeUrl())
+                .append("createBy", getCreateBy())
+                .append("createTime", getCreateTime())
+                .append("updateBy", getUpdateBy())
+                .append("updateTime", getUpdateTime())
+                .append("remark", getRemark())
+                .toString();
+    }
+}

+ 61 - 0
zhjq-system/src/main/java/com/zhjq/system/mapper/ZhjqVoiceMapper.java

@@ -0,0 +1,61 @@
+package com.zhjq.system.mapper;
+
+import com.zhjq.system.domain.ZhjqVoice;
+
+import java.util.List;
+
+/**
+ * 语音播报Mapper接口
+ *
+ * @author ruoyi
+ * @date 2024-12-13
+ */
+public interface ZhjqVoiceMapper {
+    /**
+     * 查询语音播报
+     *
+     * @param id 语音播报主键
+     * @return 语音播报
+     */
+    public ZhjqVoice selectZhjqVoiceById(Long id);
+
+    /**
+     * 查询语音播报列表
+     *
+     * @param zhjqVoice 语音播报
+     * @return 语音播报集合
+     */
+    public List<ZhjqVoice> selectZhjqVoiceList(ZhjqVoice zhjqVoice);
+
+    /**
+     * 新增语音播报
+     *
+     * @param zhjqVoice 语音播报
+     * @return 结果
+     */
+    public int insertZhjqVoice(ZhjqVoice zhjqVoice);
+
+    /**
+     * 修改语音播报
+     *
+     * @param zhjqVoice 语音播报
+     * @return 结果
+     */
+    public int updateZhjqVoice(ZhjqVoice zhjqVoice);
+
+    /**
+     * 删除语音播报
+     *
+     * @param id 语音播报主键
+     * @return 结果
+     */
+    public int deleteZhjqVoiceById(Long id);
+
+    /**
+     * 批量删除语音播报
+     *
+     * @param ids 需要删除的数据主键集合
+     * @return 结果
+     */
+    public int deleteZhjqVoiceByIds(Long[] ids);
+}

+ 65 - 0
zhjq-system/src/main/java/com/zhjq/system/service/IZhjqVoiceService.java

@@ -0,0 +1,65 @@
+package com.zhjq.system.service;
+
+import com.zhjq.system.domain.ZhjqVoice;
+
+import javax.servlet.http.HttpServletResponse;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 语音播报Service接口
+ *
+ * @author ruoyi
+ * @date 2024-12-13
+ */
+public interface IZhjqVoiceService {
+    /**
+     * 查询语音播报
+     *
+     * @param id 语音播报主键
+     * @return 语音播报
+     */
+    public ZhjqVoice selectZhjqVoiceById(Long id);
+
+    /**
+     * 查询语音播报列表
+     *
+     * @param zhjqVoice 语音播报
+     * @return 语音播报集合
+     */
+    public List<ZhjqVoice> selectZhjqVoiceList(ZhjqVoice zhjqVoice);
+
+    /**
+     * 新增语音播报
+     *
+     * @param zhjqVoice 语音播报
+     * @return 结果
+     */
+    public int insertZhjqVoice(ZhjqVoice zhjqVoice);
+
+    /**
+     * 修改语音播报
+     *
+     * @param zhjqVoice 语音播报
+     * @return 结果
+     */
+    public int updateZhjqVoice(ZhjqVoice zhjqVoice);
+
+    /**
+     * 批量删除语音播报
+     *
+     * @param ids 需要删除的语音播报主键集合
+     * @return 结果
+     */
+    public int deleteZhjqVoiceByIds(Long[] ids);
+
+    /**
+     * 删除语音播报信息
+     *
+     * @param id 语音播报主键
+     * @return 结果
+     */
+    public int deleteZhjqVoiceById(Long id);
+
+    Map<String, String> export(HttpServletResponse response, ZhjqVoice zhjqVoice);
+}

+ 206 - 0
zhjq-system/src/main/java/com/zhjq/system/service/impl/ZhjqVoiceServiceImpl.java

@@ -0,0 +1,206 @@
+package com.zhjq.system.service.impl;
+
+import com.zhjq.common.config.RuoYiConfig;
+import com.zhjq.common.utils.DateUtils;
+import com.zhjq.common.utils.file.ImageUtils;
+import com.zhjq.system.domain.ZhjqVoice;
+import com.zhjq.system.mapper.ZhjqVoiceMapper;
+import com.zhjq.system.service.IZhjqVoiceService;
+import org.apache.poi.ss.usermodel.*;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.file.*;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 语音播报Service业务层处理
+ *
+ * @author ruoyi
+ * @date 2024-12-13
+ */
+@Service
+public class ZhjqVoiceServiceImpl implements IZhjqVoiceService {
+    @Autowired
+    private ZhjqVoiceMapper zhjqVoiceMapper;
+
+    private final String reportFileUrlWin = "D:\\excel\\";
+
+    private final String reportFileUrlLinux = "/u01/excel/";
+
+    /**
+     * 查询语音播报
+     *
+     * @param id 语音播报主键
+     * @return 语音播报
+     */
+    @Override
+    public ZhjqVoice selectZhjqVoiceById(Long id) {
+        return zhjqVoiceMapper.selectZhjqVoiceById(id);
+    }
+
+    /**
+     * 查询语音播报列表
+     *
+     * @param zhjqVoice 语音播报
+     * @return 语音播报
+     */
+    @Override
+    public List<ZhjqVoice> selectZhjqVoiceList(ZhjqVoice zhjqVoice) {
+        return zhjqVoiceMapper.selectZhjqVoiceList(zhjqVoice);
+    }
+
+    /**
+     * 新增语音播报
+     *
+     * @param zhjqVoice 语音播报
+     * @return 结果
+     */
+    @Override
+    public int insertZhjqVoice(ZhjqVoice zhjqVoice) {
+        zhjqVoice.setCreateTime(DateUtils.getNowDate());
+        return zhjqVoiceMapper.insertZhjqVoice(zhjqVoice);
+    }
+
+    /**
+     * 修改语音播报
+     *
+     * @param zhjqVoice 语音播报
+     * @return 结果
+     */
+    @Override
+    public int updateZhjqVoice(ZhjqVoice zhjqVoice) {
+        zhjqVoice.setUpdateTime(DateUtils.getNowDate());
+        return zhjqVoiceMapper.updateZhjqVoice(zhjqVoice);
+    }
+
+    /**
+     * 批量删除语音播报
+     *
+     * @param ids 需要删除的语音播报主键
+     * @return 结果
+     */
+    @Override
+    public int deleteZhjqVoiceByIds(Long[] ids) {
+        return zhjqVoiceMapper.deleteZhjqVoiceByIds(ids);
+    }
+
+    /**
+     * 删除语音播报信息
+     *
+     * @param id 语音播报主键
+     * @return 结果
+     */
+    @Override
+    public int deleteZhjqVoiceById(Long id) {
+        return zhjqVoiceMapper.deleteZhjqVoiceById(id);
+    }
+
+    @Override
+    public Map<String, String> export(HttpServletResponse response, ZhjqVoice zhjqVoice) {
+        List<ZhjqVoice> list = zhjqVoiceMapper.selectZhjqVoiceList(zhjqVoice);
+        // 创建
+        Workbook workbook = new XSSFWorkbook();
+        Sheet sheet = workbook.createSheet("景区播报统计");
+        CellStyle style = workbook.createCellStyle(); // 创建单元格样式
+        style.setAlignment(HorizontalAlignment.CENTER); // 设置水平居中对齐
+        style.setVerticalAlignment(VerticalAlignment.CENTER); // 设置垂直居中
+        // 创建子标题行
+        Row subHeaderRow = sheet.createRow(0);
+        Cell sonCell1 = subHeaderRow.createCell(0);
+        sonCell1.setCellValue("景点名称");
+        sonCell1.setCellStyle(style);
+        Cell sonCell2 = subHeaderRow.createCell(1);
+        sonCell2.setCellValue("二维码");
+        sonCell2.setCellStyle(style);
+        // 填充数据
+        for (int i = 0; i < list.size(); i++) {
+            ZhjqVoice data = list.get(i);
+            Row dataRow = sheet.createRow(i + 1); // 从第二行开始填充数据
+            dataRow.setHeight((short) 1000);
+            sheet.setColumnWidth(i + 1, 20 * 260); // 设置单元格宽度
+            Cell dataCell1 = dataRow.createCell(0);
+            dataCell1.setCellValue(data.getVoiceName());
+            dataCell1.setCellStyle(style);
+            // 插入图片
+            try {
+                byte[] bytes = ImageUtils.readQrcodeFile(data.getQrCodeUrl());
+                int pictureIdx = workbook.addPicture(bytes, Workbook.PICTURE_TYPE_PNG);
+                CreationHelper helper = workbook.getCreationHelper();
+                Drawing<?> drawing = sheet.createDrawingPatriarch();
+                ClientAnchor anchor = helper.createClientAnchor();
+                anchor.setCol1(1); // 图片所在列
+                anchor.setRow1(i + 1); // 图片所在行
+                Picture picture = drawing.createPicture(anchor, pictureIdx);
+                picture.resize(1);
+            } catch (Exception e) {
+                throw new RuntimeException(e);
+            }
+        }
+        String fileName = "景区播报统计" + DateUtils.dateTimeNow() + ".xlsx";
+        //生成文档
+        String dssFileDownloadUrl = isWindows() ? reportFileUrlWin : reportFileUrlLinux;
+        String localFilePath = dssFileDownloadUrl + fileName;
+        //先把这个文件夹下的历史文件删除,再生成新的
+        deleteAll(dssFileDownloadUrl);
+        try (FileOutputStream fileOut = new FileOutputStream(localFilePath)) {
+            workbook.write(fileOut);
+            workbook.close();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        Map<String, String> map = new HashMap<>();
+        map.put("fileName", fileName);
+        map.put("filePath", dssFileDownloadUrl);
+        map.put("localFilePath", localFilePath);
+        return map;
+    }
+
+    /**
+     * 判断操作系统是否是 Windows
+     *
+     * @return true:操作系统是 Windows
+     * false:其它操作系统
+     */
+    public static boolean isWindows() {
+        String osName = getOsName();
+        return osName != null && osName.startsWith("Windows");
+    }
+
+    /**
+     * 获取操作系统名称
+     *
+     * @return os.name 属性值
+     */
+    public static String getOsName() {
+        return System.getProperty("os.name");
+    }
+
+    /**
+     * 按照名称删除文件
+     *
+     * @param path 文件
+     * @return
+     */
+    public static void deleteAll(String path) {
+        try {
+            Files.walkFileTree(Paths.get(path), new SimpleFileVisitor<Path>() {
+                // 先去遍历删除文件
+                public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
+                    Files.delete(file);
+                    System.out.printf("文件被删除 : %s%n", file);
+                    return FileVisitResult.CONTINUE;
+                }
+            });
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+}

+ 3 - 0
zhjq-system/src/main/resources/mapper/system/SysNoticeMapper.xml

@@ -45,6 +45,9 @@
             <if test="noticeType != null and noticeType != ''">
                 AND notice_type = #{noticeType}
             </if>
+            <if test="status != null and status != ''">
+                AND status = #{status}
+            </if>
             <if test="createBy != null and createBy != ''">
                 AND create_by like concat('%', #{createBy}, '%')
             </if>

+ 134 - 0
zhjq-system/src/main/resources/mapper/system/ZhjqVoiceMapper.xml

@@ -0,0 +1,134 @@
+<?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.zhjq.system.mapper.ZhjqVoiceMapper">
+
+    <resultMap type="ZhjqVoice" id="ZhjqVoiceResult">
+        <result property="id" column="id"/>
+        <result property="voiceName" column="voice_name"/>
+        <result property="voiceUrl" column="voice_url"/>
+        <result property="qrCodeUrl" column="qr_code_url"/>
+        <result property="createBy" column="create_by"/>
+        <result property="createTime" column="create_time"/>
+        <result property="updateBy" column="update_by"/>
+        <result property="updateTime" column="update_time"/>
+        <result property="remark" column="remark"/>
+    </resultMap>
+
+    <sql id="selectZhjqVoiceVo">
+        select id,
+               voice_name,
+               voice_url,
+               qr_code_url,
+               create_by,
+               create_time,
+               update_by,
+               update_time,
+               remark
+        from zhjq_voice
+    </sql>
+
+    <select id="selectZhjqVoiceList" parameterType="ZhjqVoice" resultMap="ZhjqVoiceResult">
+        <include refid="selectZhjqVoiceVo"/>
+        <where>
+            <if test="voiceName != null  and voiceName != ''">
+                and voice_name like concat('%', #{voiceName}, '%')
+            </if>
+            <if test="voiceUrl != null  and voiceUrl != ''">
+                and voice_url = #{voiceUrl}
+            </if>
+        </where>
+    </select>
+
+    <select id="selectZhjqVoiceById" parameterType="Long"
+            resultMap="ZhjqVoiceResult">
+        <include refid="selectZhjqVoiceVo"/>
+        where id = #{id}
+    </select>
+
+    <insert id="insertZhjqVoice" parameterType="ZhjqVoice" useGeneratedKeys="true"
+            keyProperty="id">
+        insert into zhjq_voice
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="voiceName != null">voice_name,
+            </if>
+            <if test="voiceUrl != null">voice_url,
+            </if>
+            <if test="qrCodeUrl != null">qr_code_url,
+            </if>
+            <if test="createBy != null">create_by,
+            </if>
+            <if test="createTime != null">create_time,
+            </if>
+            <if test="updateBy != null">update_by,
+            </if>
+            <if test="updateTime != null">update_time,
+            </if>
+            <if test="remark != null">remark,
+            </if>
+        </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="voiceName != null">#{voiceName},
+            </if>
+            <if test="voiceUrl != null">#{voiceUrl},
+            </if>
+            <if test="qrCodeUrl != null">#{qrCodeUrl},
+            </if>
+            <if test="createBy != null">#{createBy},
+            </if>
+            <if test="createTime != null">#{createTime},
+            </if>
+            <if test="updateBy != null">#{updateBy},
+            </if>
+            <if test="updateTime != null">#{updateTime},
+            </if>
+            <if test="remark != null">#{remark},
+            </if>
+        </trim>
+    </insert>
+
+    <update id="updateZhjqVoice" parameterType="ZhjqVoice">
+        update zhjq_voice
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="voiceName != null">voice_name =
+                #{voiceName},
+            </if>
+            <if test="voiceUrl != null">voice_url =
+                #{voiceUrl},
+            </if>
+            <if test="qrCodeUrl != null">qr_code_url =
+                #{qrCodeUrl},
+            </if>
+            <if test="createBy != null">create_by =
+                #{createBy},
+            </if>
+            <if test="createTime != null">create_time =
+                #{createTime},
+            </if>
+            <if test="updateBy != null">update_by =
+                #{updateBy},
+            </if>
+            <if test="updateTime != null">update_time =
+                #{updateTime},
+            </if>
+            <if test="remark != null">remark =
+                #{remark},
+            </if>
+        </trim>
+        where id = #{id}
+    </update>
+
+    <delete id="deleteZhjqVoiceById" parameterType="Long">
+        delete
+        from zhjq_voice
+        where id = #{id}
+    </delete>
+
+    <delete id="deleteZhjqVoiceByIds" parameterType="String">
+        delete from zhjq_voice where id in
+        <foreach item="id" collection="array" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </delete>
+</mapper>

+ 55 - 0
zhjq-ui/src/api/system/voice.js

@@ -0,0 +1,55 @@
+import request from '@/utils/request'
+
+// 查询语音播报列表
+export function listVoice(query) {
+    return request({
+        url: '/system/voice/list',
+        method: 'get',
+        params: query
+    })
+}
+
+// 查询语音播报详细
+export function getVoice(id) {
+    return request({
+        url: '/system/voice/' + id,
+        method: 'get'
+    })
+}
+
+// 新增语音播报
+export function addVoice(data) {
+    return request({
+        url: '/system/voice',
+        method: 'post',
+        data: data
+    })
+}
+
+// 修改语音播报
+export function updateVoice(data) {
+    return request({
+        url: '/system/voice',
+        method: 'put',
+        data: data
+    })
+}
+
+// 删除语音播报
+export function delVoice(id) {
+    return request({
+        url: '/system/voice/' + id,
+        method: 'delete'
+    })
+}
+
+// 生成二维码
+export function createQrCode(data) {
+    return request({
+        url: '/infra/QrCode/getQcCode',
+      method: 'post',
+      data: data
+    })
+}
+
+

+ 1 - 1
zhjq-ui/src/components/FileUpload/index.vue

@@ -55,7 +55,7 @@ export default {
     // 大小限制(MB)
     fileSize: {
       type: Number,
-      default: 5,
+      default: 200,
     },
     // 文件类型, 例如['png', 'jpg', 'jpeg']
     fileType: {

+ 4 - 31
zhjq-ui/src/views/system/notice/index.vue

@@ -9,21 +9,14 @@
           @keyup.enter.native="handleQuery"
         />
       </el-form-item>
-      <el-form-item label="操作人员" prop="createBy">
-        <el-input
-          v-model="queryParams.createBy"
-          clearable
-          placeholder="请输入操作人员"
-          @keyup.enter.native="handleQuery"
-        />
-      </el-form-item>
-      <el-form-item label="类型" prop="noticeType">
-        <el-select v-model="queryParams.noticeType" clearable placeholder="公告类型">
+      <el-form-item label="公告状态" prop="status">
+        <el-select v-model="queryParams.status" clearable placeholder="公告状态">
           <el-option
-            v-for="dict in dict.type.sys_notice_type"
+            v-for="dict in dict.type.sys_notice_status"
             :key="dict.value"
             :label="dict.label"
             :value="dict.value"
+            @keyup.enter.native="handleQuery"
           />
         </el-select>
       </el-form-item>
@@ -81,11 +74,6 @@
         label="公告标题"
         prop="noticeTitle"
       />
-      <el-table-column align="center" label="公告类型" prop="noticeType" width="100">
-        <template slot-scope="scope">
-          <dict-tag :options="dict.type.sys_notice_type" :value="scope.row.noticeType"/>
-        </template>
-      </el-table-column>
       <el-table-column align="center" label="状态" prop="status" width="100">
         <template slot-scope="scope">
           <dict-tag :options="dict.type.sys_notice_status" :value="scope.row.status"/>
@@ -136,18 +124,6 @@
               <el-input v-model="form.noticeTitle" placeholder="请输入公告标题"/>
             </el-form-item>
           </el-col>
-          <el-col :span="12">
-            <el-form-item label="公告类型" prop="noticeType">
-              <el-select v-model="form.noticeType" placeholder="请选择公告类型">
-                <el-option
-                  v-for="dict in dict.type.sys_notice_type"
-                  :key="dict.value"
-                  :label="dict.label"
-                  :value="dict.value"
-                ></el-option>
-              </el-select>
-            </el-form-item>
-          </el-col>
           <el-col :span="24">
             <el-form-item label="状态">
               <el-radio-group v-model="form.status">
@@ -216,9 +192,6 @@ export default {
         noticeTitle: [
           {required: true, message: "公告标题不能为空", trigger: "blur"}
         ],
-        noticeType: [
-          {required: true, message: "公告类型不能为空", trigger: "change"}
-        ]
       }
     };
   },

+ 347 - 0
zhjq-ui/src/views/system/voice/index.vue

@@ -0,0 +1,347 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
+      <el-form-item label="景点名称" prop="voiceName">
+        <el-input
+          v-model="queryParams.voiceName"
+          placeholder="请输入景点名称"
+          clearable
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
+        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
+      </el-form-item>
+    </el-form>
+
+    <el-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-button
+          type="primary"
+          plain
+          icon="el-icon-plus"
+          size="mini"
+          @click="handleAdd"
+        >新增
+        </el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="success"
+          plain
+          icon="el-icon-edit"
+          size="mini"
+          :disabled="single"
+          @click="handleUpdate"
+        >修改
+        </el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="danger"
+          plain
+          icon="el-icon-delete"
+          size="mini"
+          :disabled="multiple"
+          @click="handleDelete"
+        >删除
+        </el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          icon="el-icon-download"
+          plain size="mini"
+          type="warning"
+          @click="handleExport">
+          导出
+        </el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <el-table v-loading="loading" :data="voiceList" @selection-change="handleSelectionChange">
+      <el-table-column type="selection" width="55" align="center"/>
+      <el-table-column label="序号" align="center" type="index"/>
+      <el-table-column label="景点名称" align="center" prop="voiceName"/>
+      <el-table-column label="二维码" align="center" prop="qrCodeUrl">
+        <!--        <div ref="qrcode" class="qrcode" style="margin-left: 163px" @mouseover="hover = true" @mouseleave="hover = false"></div>-->
+        <!--        <div ref="qrcode" style="margin-left: 163px"></div>-->
+        <template slot-scope="scope">
+          <img :src="getFullUrl(scope.row.qrCodeUrl)" min-width="70" height="70"/>
+        </template>
+      </el-table-column>
+      <el-table-column label="备注" align="center" prop="remark"/>
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+        <template slot-scope="scope">
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-edit"
+            @click="handleUpdate(scope.row)"
+          >修改
+          </el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-delete"
+            @click="handleDelete(scope.row)"
+          >删除
+          </el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-thumb"
+            @click="handleQrCode(scope.row)"
+          >生成二维码
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <pagination
+      v-show="total>0"
+      :total="total"
+      :page.sync="queryParams.pageNum"
+      :limit.sync="queryParams.pageSize"
+      @pagination="getList"
+    />
+
+    <!-- 添加或修改语音播报对话框 -->
+    <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
+      <el-form ref="form" :model="form" :rules="rules" label-width="80px">
+        <el-form-item label="景点名称" prop="voiceName">
+          <el-input v-model="form.voiceName" placeholder="请输入景点名称"/>
+        </el-form-item>
+        <el-form-item label="语音文件" prop="voiceUrl">
+          <file-upload v-model="form.voiceUrl"/>
+        </el-form-item>
+        <el-form-item label="备注" prop="remark">
+          <el-input v-model="form.remark" type="textarea" placeholder="请输入内容"/>
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitForm">确 定</el-button>
+        <el-button @click="cancel">取 消</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import {getVoice, listVoice, addVoice, delVoice, updateVoice, createQrCode} from "@/api/system/voice";
+import QRCode from "qrcodejs2";
+
+export default {
+  name: "Voice",
+  data() {
+    return {
+      // 遮罩层
+      loading: true,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 语音播报表格数据
+      voiceList: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        voiceName: null,
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {
+        voiceName: [
+          {required: true, message: "景点名称不能为空", trigger: "blur"}
+        ],
+        voiceUrl: [
+          {required: true, message: "语音文件不能为空", trigger: "blur"}
+        ],
+      },
+      // showImg: false,// 是否显示企业签名二维码的弹框,默认false
+      // qrcode: '',// 二维码数据
+      localIp: '',
+      localPort: '',
+    };
+  },
+  created() {
+    this.getList();
+    // this.$nextTick(() => {
+    //   this.handleQrCode();
+    // });
+  },
+  mounted() {
+    this.localIp = this.getLocalIp();
+    console.log("localIp", this.localIp)
+    this.localPort = this.getPort()
+    console.log("localPort", this.localPort)
+  },
+  methods: {
+    // 生成二维码
+    // handleQrCode(row) {
+    //   // 清除上一次的二维码
+    //   if (this.$refs.qrcode) {
+    //     this.$refs.qrcode.innerHTML = ''; // 清除二维码方法
+    //   }
+    //   // 生成二维码(这里有个坑,一定要在 new关键字前面用 等于号,赋给一个变量,否则vue运行会报错)
+    //   this.qrcode = new QRCode(this.$refs.qrcode, {
+    //     width: 50,  // 二维码宽度 (不支持100%)
+    //     height: 50, // 二维码高度(不支持100%)
+    //     text: row.voiceUrl, // 后端返回的二维码地址
+    //     render: 'canvas', // 设置渲染方式(有两种方式 table和canvas,默认是canvas)
+    //   });
+    //   const canvas = document.querySelector('canvas');
+    //   const imageUrl = canvas.toDataURL('image/png');
+    //   console.log("imageUrl",imageUrl)
+    // },
+    getLocalIp() {
+      console.log("window.location",window.location)
+      const {hostname} = window.location;
+      return hostname === 'localhost' || hostname === '127.0.0.1'
+        ? 'http://localhost'
+        : `http://${hostname}`;
+    },
+    getPort() {
+      return window.location.port ? `:${window.location.port}` : '8080';
+    },
+    getFullUrl(qrCodeUrl) {
+      return `${this.localIp}:${this.localPort}${qrCodeUrl}`;
+    },
+    handleQrCode(row) {
+      let that = this;
+      let params = {
+        id: row.id,
+        voiceName: row.voiceName,
+        voiceUrl: that.localIp + ":" + that.localPort + row.voiceUrl,
+      }
+      createQrCode(params).then(res => {
+        if(res.code == 200) {
+          that.$modal.msgSuccess("生成成功");
+        } else {
+          that.$modal.msgError("生成失败");
+        }
+      });
+      this.getList()
+    },
+    /** 查询语音播报列表 */
+    getList() {
+      this.loading = true;
+      listVoice(this.queryParams).then(response => {
+        this.voiceList = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      });
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        id: null,
+        voiceName: null,
+        voiceUrl: null,
+        createBy: null,
+        createTime: null,
+        updateBy: null,
+        updateTime: null,
+        remark: null
+      };
+      this.resetForm("form");
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.handleQuery();
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.id)
+      this.single = selection.length !== 1
+      this.multiple = !selection.length
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      this.reset();
+      this.open = true;
+      this.title = "添加语音播报";
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.reset();
+      const id = row.id || this.ids
+      getVoice(id).then(response => {
+        this.form = response.data;
+        this.open = true;
+        this.title = "修改语音播报";
+      });
+    },
+    /** 提交按钮 */
+    submitForm: function () {
+      this.$refs["form"].validate(valid => {
+        if (valid) {
+          if (this.form.id != undefined) {
+            updateVoice(this.form).then(response => {
+              this.$modal.msgSuccess("修改成功");
+              this.open = false;
+              this.getList();
+            });
+          } else {
+            addVoice(this.form).then(response => {
+              this.$modal.msgSuccess("新增成功");
+              this.open = false;
+              this.getList();
+            });
+          }
+        }
+      });
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      this.$modal.confirm('是否确认删除名称为"' + row.voiceName + '"的数据项?').then(function () {
+        return delVoice(row.id);
+      }).then(() => {
+        this.getList();
+        this.$modal.msgSuccess("删除成功");
+      }).catch(() => {
+      });
+    },
+    /** 导出按钮操作 */
+    handleExport() {
+      this.download('system/voice/export', {
+        ...this.queryParams
+      }, `user_${new Date().getTime()}.xlsx`)
+    },
+  }
+};
+</script>
+<style>
+.qrcode {
+  display: inline-block;
+  transition: transform 0.3s; /* 添加平滑过渡 */
+}
+
+.qrcode:hover {
+  transform: scale(3); /* 鼠标悬浮时放大1.5倍 */
+}
+</style>