Vue3 简单实现虚拟Table,展示海量单词.利用WebAPI speechSynthesis,朗读英语单词

news2024/11/25 6:44:52

目录

本页面完整代码

视频演示

完整的页面代码


利用webapi speechSynthesis帮助我们自动郎读英语单词,可以利用这个API,做一些小说朗读或到账提示。

 

本页面完整代码

用Vue写了一个简单页面,里面还写了一个简单的虚拟Table支持海量数据展示。

视频演示

20231106-223410

完整的页面代码

里面的all.js文件是英语四级的单词,在文章内自行下载,也可以去这里面把JSON下载。

GitHub - cuttlin/Vocabulary-of-CET-4: 英语四级词库

复制里面的代码,放到html文件就可以运行

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>


    <script src="all.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/vue@3.3.7/dist/vue.global.js"></script>


    <style>
        body{
            background-color: rgba(0,0,0,0.04);
        }
        .table-wrapper{
            background-color: #fff;
          border: solid 1px #efefef;
          box-shadow: 0 0px 3px 1px rgba(0,0,0,0.05);
        }
        .table-wrapper table {
            width: 100%;
            border-spacing: 0;
            table-layout: fixed;
        }

        .header-table th {
            background-color: #00a674;
            height: 40px;
            line-height: 40px;
            color: rgb(158, 255, 205);
        }

        .body-table td {
            background-color: #fff;
            text-align: center;
        }

        .body-table tr:nth-of-type(n+2) td {
            border-top: solid 1px rgba(0, 0, 0, 0.06);
        }

        .body-table tr:hover td {
            background-color: #f7f7f7;
        }
        .form-wrap{
            background-color: #fff;
            margin-bottom: 15px;
            padding: 15px;
            box-shadow: 0 1px 3px 1px rgba(0,0,0,0.05);
        }
        .table-form {
            table-layout:fixed;
        }
        .table-form th,.table-form td{
            height: 25px;
            line-height: 25px;
        }
        .table-form th{
            width: 80px;
            font-weight: 400;
            font-size: 14px;
            text-align: right;
        }
        .table-form th::after{
             content:':';
        }
    </style>
</head>
<body>

    <div id="app">
    </div>
    <template id="tplApp">
        <div>
            <div class="form-wrap">
                <table class="table-form">
                    <tr>
                        <th>声音</th>
                        <td colspan="5"> <select v-model="voice.lang">
                            <option v-for="(v,i) in voices" :key="v.key" :value="v.name">{{v.name}}</option>
                         </select></td>
                    </tr>
                    <tr>
                        <th>语速</th>
                        <td><input v-model.number="voice.rate" type="number" min="0.1" max="10" step="0.1"/></td>
                        <th>音调</th>
                        <td><input v-model.number="voice.pitch" type="number" min="0" max="2" step="0.1"/></td>
                        <th>音量</th>
                        <td><input v-model.number="voice.volume" type="number" min="0" max="1" step="0.1"/></td>
                    </tr>
                </table>
            </div>
            </div>
            <Virtual-Table :columns="columns" :data-source="dataSource" row-key="word" :row-height="50" :scroll="scroll"></VirtualTable>
        </div>
    </template>
    <script>
        const { ref, shallowRef, h, toRaw, renderSlot,reactive,shallowReactive, toRefs, toRef, computed } = Vue


        const useVirtualList = (options) => {
            const { rowHeight, height, dataSource, columnCount = 1 } = options
            const scrollTop = ref(0)
            const onScroll = (e) => {
                scrollTop.value = e.target.scrollTop
            }
            const scrollRowIndex = computed(() => {
                return Math.floor(scrollTop.value / rowHeight.value)
            })
            const visibilityRowCount = computed(() => {
                return Math.ceil(height / rowHeight.value)
            })
            const start = computed(() => {
                return scrollRowIndex.value * columnCount
            })
            const end = computed(() => {
                return start.value + visibilityRowCount.value * columnCount
            })
            const rowCount = computed(() => {
                return Math.ceil(dataSource.value.length / columnCount)
            })
            const scrollHeight = computed(() => {
                return rowCount.value * rowHeight.value
            })
            const currentList = computed(() => {
                return dataSource.value.slice(start.value, end.value)
            })
            const containerProps = computed(() => {
                return {
                    style: {
                        height: height + 'px',
                        overflowY: 'auto'
                    },
                    onScroll: onScroll
                }
            })
            const invisibleHeight = computed(() => {
                return (scrollRowIndex.value * rowHeight.value)
            })
            const scrollProps = computed(() => {
                return {
                    style: {
                        height: scrollHeight.value + 'px',
                        paddingTop: invisibleHeight.value + 'px',
                        boxSizing: 'border-box',
                    },
                }
            })
            return [{
                containerProps,
                scrollProps,
                data: currentList
            }]
        }
        const VirtualTable = {
            props: ['columns', 'rowKey', 'dataSource', 'scroll', 'rowHeight'],
            setup(props, { slots }) {
                const rowHeight = toRef(props, 'rowHeight')
                console.log('rowHeight',rowHeight.value)
                const scroll = props.scroll
                const rowKey = props.rowKey
                const columns = toRef(props, 'columns')
                const dataSource = toRef(props, 'dataSource')
                const [{ containerProps, scrollProps, data: currentData }] = useVirtualList({
                    rowKey: rowKey,
                    rowHeight: rowHeight,
                    height: scroll.y,
                    dataSource: dataSource
                })
                const renderCol = (columns) => {
                    return h('colgroup', {}, columns.map((c, i) => {
                        return h('col', {
                            key: c.dataIndex || i,
                            style: {
                                ...(c.width ? { width: c.width + 'px' } : {})
                            }
                        })
                    }))
                }
                const renderHeader = (columns) => {
                    return h('thead', {}, h('tr', {}, columns.map((c, i) => {
                        return h('th', {
                            key: c.dataIndex || i,
                        }, c.title)
                    })))
                }
                const renderCell = (columns, dataItem) => {
                    return columns.map((c, i) => {
                        return h('td', {
                            key: c.dataIndex || i,
                        }, c.render ? c.render(dataItem[c.dataIndex], dataItem, i) : dataItem[c.dataIndex])
                    })
                }
                const renderRow = (data) => {
                    return h('tbody', {}, data.map((d, i) => {
                        return h('tr', {
                            key: d[rowKey],
                            style: {
                                height: rowHeight.value + 'px'
                            }
                        }, renderCell(columns.value, d))
                    }))
                }
                return () => {

                    return h('div', {
                        class: 'table-wrapper'
                    },
                        h('div', {
                            class: 'header-wrap'
                        }, h('table', {
                            class: 'header-table'
                        },
                            renderCol(columns.value),
                            renderHeader(columns.value),
                        )),
                        h('div', {
                            class: 'body-wrap',
                            ...containerProps.value
                        }, h('div', {
                            class: 'body-scroll-wrap',
                            ...scrollProps.value
                        },
                            h('table', {
                                class: 'body-table'
                            },
                                renderCol(columns.value),
                                renderRow(currentData.value))
                        ))
                    )
                }
            }
        }
        const app = Vue.createApp({
            template: '#tplApp',
            components:{
                VirtualTable:VirtualTable
            },
            setup() {

                const voices=shallowRef([])
                const voice=shallowReactive({
                    lang:"",
                    pitch:1,
                    rate:1,
                    volume:1
                })
                
                speechSynthesis.addEventListener('voiceschanged', () => {
                        voices.value = speechSynthesis.getVoices()
                        voice.lang=voices.value[0].name
                })
                // 语音合成
                const speak=(word, options = {})=> {

                    return new Promise((resolve, reject) => {
                        const utterThis = new SpeechSynthesisUtterance(word);
                        for (let i = 0; i < voices.value.length; i++) {
                            if ( voices.value[i].name === voice.lang) {
                                utterThis.voice =  voices.value[i];
                            }
                        }
                        utterThis.pitch = voice.pitch;
                        utterThis.rate = voice.rate;
                        utterThis.volume = voice.volume
                        utterThis.onend = function () {
                            resolve()
                        }
                        utterThis.onerror = (e) => {
                            reject(e)
                        }
                        speechSynthesis.speak(utterThis);

                    })

                }
                const columns = shallowRef([
                    {
                        title: '单词',
                        dataIndex: 'word',
                        width: 220
                    },
                    {
                        title: '音标',
                        dataIndex: 'phonetic_symbol',
                        width: 220
                    },
                    {
                        title: '中文意思',
                        dataIndex: 'mean'
                    },
                    {
                        title: '操作',
                        width: 160,
                        render(v, record) {
                            return h('div',{

                            },h('button', {
                                onClick: () => {
                                    speak(record.word)
                                }
                            }, '朗读单词'),h('button', {
                                style:{
                                    marginLeft:'5px'
                                },
                                onClick: () => {
                                    speak(record.mean)
                                }
                            }, '朗读中文'))
                        }
                    }
                ])
                const dataSource = shallowRef(english_word_cet4_all)
                return {
                    voices,
                    voice,
                    dataSource,
                    columns:columns,
                    scroll:{
                        y:window.innerHeight-150
                    }
                }
               
            }
        })

        app.mount('#app')
    </script>
</body>
</html>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1178148.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Docker 持久化存储和数据共享_Volume

有些容器会自动产生一些数据&#xff0c;为了不让数据随着 container 的消失而消失&#xff0c;保证数据的安全性。例如&#xff1a;数据库容器&#xff0c;数据表的表会产生一些数据&#xff0c;如果我把 container 给删除&#xff0c;数据就丢失。为了保证数据不丢失&#xf…

GET 请求和 POST 请求

浅析HTTP中请求GET/POST - 知乎 (zhihu.com) 什么是GET GET&#xff1a;从服务器请求数据后获取服务端数据 常见发起get请求的方式&#xff1a; URL、src/href、表单(form) 格式&#xff1a; index.php?userNamejack&password123 语法&#xff08;keyvalue&keyva…

【Linux】 JumpServer 堡垒机远程访问

文章目录 前言1. 安装Jump server2. 本地访问jump server3. 安装 cpolar内网穿透软件4. 配置Jump server公网访问地址5. 公网远程访问Jump server6. 固定Jump server公网地址 前言 JumpServer 是广受欢迎的开源堡垒机&#xff0c;是符合 4A 规范的专业运维安全审计系统。JumpS…

【1++的Linux】之线程(二)

&#x1f44d;作者主页&#xff1a;进击的1 &#x1f929; 专栏链接&#xff1a;【1的Linux】 文章目录 一&#xff0c;对上一篇内容的补充二&#xff0c;Linux线程互斥1. 互斥的引出2. 互斥量3. 剖析锁的原理 一&#xff0c;对上一篇内容的补充 线程创建&#xff1a; pthread…

人工智能AI 全栈体系(十二)

第二章 计算机是如何学会下棋的 下棋一直被认为是人类的高智商游戏&#xff0c;从人工智能诞生的那一天开始&#xff0c;研究者就开始研究计算机如何下棋。著名人工智能学者、图灵奖获得者约翰麦卡锡在 50 年代就开始从事计算机下棋方面的研究工作&#xff0c;并提出了著名的 …

关键字驱动自动化测试框架搭建详解

前言 那么这篇文章我们将了解关键字驱动测试又是如何驱动自动化测试完成整个测试过程的。关键字驱动框架是一种功能自动化测试框架&#xff0c;它也被称为表格驱动测试或者基于动作字的测试。关键字驱动的框架的基本工作是将测试用例分成四个不同的部分。首先是测试步骤&#…

用HTML + javaScript快速完成excel表格信息除重并合并

今天突然接到一个工作&#xff0c;要把两个存储在.xls的主体信息表&#xff0c;除重后合并成一个主体信息表&#xff0c;并且补充主体类型和所在县区这两列信息。 完成这项工作的方法有很多&#xff0c;如果信息表中的信息量不大的话&#xff0c;手工处理一下也行&#xff0c;如…

MYSQL运维篇(已完结)

一、日志 1. 错误日志 2. 二进制日志 &#x1f60e; 介绍 &#x1f60e; 日志格式 &#x1f60e; 日志查看 &#x1f60e; 日志删除 3. 查询日志 4. 慢查询日志 二、主从复制 1. 概述 2. 原理 3. 搭建 4. 总结 三、分库分表 1. 介绍 &#x1f364; 问题分析 &#x1f364;…

WPF布局与控件分类

Refer&#xff1a;WPF从假入门到真的入门 - 知乎 (zhihu.com) Refer&#xff1a;WPF从假入门到真的入门 - 知乎 (zhihu.com) https://www.zhihu.com/column/c_1397867519101755392 https://blog.csdn.net/qq_44034384/article/details/106154954 https://www.cnblogs.com/mq0…

报错“this.bookDao“ is null

这是我的报错&#xff1a; 原因是我的BookServiceImpl方法中的对象没有装配&#xff1a; 添加上自动装配注释即可实现自动装配&#xff1a;

Python---字符串的修改方法---replace()替换

修改字符串&#xff0c;指的就是通过函数&#xff08;方法&#xff09;的形式修改字符串中的数据。 编号函数作用1replace()返回替换后的字符串2split()返回切割后的列表序列3capitalize()首字母大写4title()所有单词首字母大写5upper()与lower()返回全部大写或小写的字符串6l…

文件夹批量改名:轻松实现文件夹随机重命名

无论是在我们的日常生活还是工作中&#xff0c;批量重命名文件夹是一项非常常见的任务。当我们需要整理或分类大量的文件时&#xff0c;往往需要对相应的文件夹进行重命名。然而&#xff0c;手动一个接一个地完成这个任务不仅会消耗大量的时间&#xff0c;还容易在重命名过程中…

Apache Doris (五十二): Doris Join类型 - Broadcast Join

🏡 个人主页:IT贫道_大数据OLAP体系技术栈,Apache Doris,Clickhouse 技术-CSDN博客 🚩 私聊博主:加入大数据技术讨论群聊,获取更多大数据资料。 🔔 博主个人B栈地址:豹哥教你大数据的个人空间-豹哥教你大数据个人主页-哔哩哔哩视频 目录 1. Broadcast Join原理

AD7792/AD7793 备忘

AD7792/AD7793 是一款 ∑-Δ ADC&#xff0c;3 通道、低噪声&#xff0c;内部集成仪表放大器和参考源。AD7792 为 16 位&#xff0c;AD7793为 24 位。 供电电压&#xff1a;2.7 ~ 5.25 V&#xff0c;并不支持负电压。转换速率&#xff1a;4.17 Hz ~ 470 Hz内置参考基准&#x…

图片怎么转换成pdf?

图片怎么转换成pdf&#xff1f;图片可以转换成PDF格式文档吗&#xff1f;当然是可以的呀&#xff0c;当图片转换成PDF文件类型时&#xff0c;我们就会发现图片更加方便的打开分享和传播&#xff0c;而且还可以更加安全的保证我们的图片所有性。我们知道PDF文档是可以加密的&…

mac系统快速切换不同版本JDK

1.安装所需jdk版本 下载地址&#xff1a;http://www.codebaoku.com/jdk/jdk-index.html 本示例安装了jdk8和jdk19两个版本 2.查看对应安装路径 安装好后&#xff0c;通过终端输入以下命令查看相关路径&#xff08;后续需在.bash_profile中配置&#xff09; /usr/libexec/ja…

技术分享 | 抓包分析 TCP 协议

TCP 协议是在传输层中&#xff0c;一种面向连接的、可靠的、基于字节流的传输层通信协议。 环境准备 对接口测试工具进行分类&#xff0c;可以如下几类&#xff1a; 网络嗅探工具&#xff1a;tcpdump&#xff0c;wireshark代理工具&#xff1a;fiddler&#xff0c;charles&a…

OmniPlan Pro 4:一站式项目流程管理神器

&#x1f916; OmniPlan Pro 4 for Mac 是一款强大的项目管理软件&#xff0c;具备许多功能。以下是它的一些主要功能介绍&#xff1a; &#x1f5d3;️ 强大的项目计划&#xff1a; OmniPlan Pro 4 可以帮助您创建详细的项目计划。您可以创建任务、设置任务之间的依赖关系、分…

软件测试/测试开发丨Python安装指南(Windows版)

点此获取更多相关资料 下载 Python 解释器 下载地址: https://www.Python.org/downloads/ 通过下载页面&#xff0c;可以在该页面上看到下载链接。 在下载列表中以“(64-bit)”结尾的链接是 64 位的 Python 安装程序&#xff0c;以“(32-bit)”开头的链接是 32 位的 Python 安…

Chromebook文件夹应用新功能

种种迹象表明 Google 旗下的 Chromebooks 近期要有大动作了。根据 Google 团队成员透露&#xff0c;公司计划在 Chrome OS 的资源管理器中新增“Recents”&#xff08;最近使用&#xff09;文件&#xff0c;以便于用户更快找到所需要的文件。 种种迹象表明 Google 旗下的 Chro…