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

目录

本页面完整代码

视频演示

完整的页面代码


利用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>

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值