|
|
@@ -4,62 +4,226 @@
|
|
|
Created Date: 2025-1-8
|
|
|
-->
|
|
|
<template>
|
|
|
- <div class="editor-container">
|
|
|
- <Editor :onTextChangeRef="lastChange" :onSelectionChangeRef="range" :onGetHTML="getHTML" :onGetText="getText" @updateValue="getMsg"/>
|
|
|
+ <div class="editor-container" @keydown="handleKeydown" @keyup="handleKeyup">
|
|
|
+ <Editor :onTextChangeRef="lastChange" :onSelectionChangeRef="range" :onGetHTML="getHTML" :onGetText="getText"/>
|
|
|
+ <el-button @click="download">导出</el-button>
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
-import "./QuillEditor.scss"
|
|
|
-import Editor from './Editor.vue';
|
|
|
-import { onUnmounted, reactive, ref, watch } from 'vue';
|
|
|
-import emitter from '@/utils/emitter';
|
|
|
-// import * as XLSX from 'xlsx'
|
|
|
-
|
|
|
-let range = reactive({})
|
|
|
-let lastChange = reactive({})
|
|
|
-let getHTML = ref("")
|
|
|
-let getText = ref("")
|
|
|
-let getQuillContent = reactive({})
|
|
|
-// 行为记录
|
|
|
-let changeRecordList = reactive([])
|
|
|
-
|
|
|
-
|
|
|
-const emailForm = reactive({
|
|
|
- test_msg: '1'
|
|
|
-})
|
|
|
-
|
|
|
-const getMsg = (val) => {
|
|
|
- emailForm.test_msg = val
|
|
|
-}
|
|
|
+ import "./QuillEditor.scss"
|
|
|
+ import Editor from './Editor.vue';
|
|
|
+ import { onUnmounted, reactive, ref, watch } from 'vue';
|
|
|
+ import emitter from '@/utils/emitter';
|
|
|
+ import dayjs from "dayjs";
|
|
|
+ import * as XLSX from 'xlsx'
|
|
|
+
|
|
|
+ let range = reactive({})
|
|
|
+ let lastChange = reactive({})
|
|
|
+ let getHTML = ref("")
|
|
|
+ let getText = ref("")
|
|
|
+ let getQuillContent = reactive({})
|
|
|
+
|
|
|
+ // 新增:用于存储每个按键的down事件信息
|
|
|
+ let keyEventMap = reactive({})
|
|
|
+ let keyEventRecordList = reactive([])
|
|
|
+ // 行为记录
|
|
|
+ let changeRecordList = reactive([])
|
|
|
+
|
|
|
+ emitter.on('change-record', (value) => {
|
|
|
+ changeRecordList.push(value)
|
|
|
+ })
|
|
|
+
|
|
|
+ const handleKeydown = (event: KeyboardEvent) => {
|
|
|
+ // 增加对Shift组合键的识别
|
|
|
+ // 在handleKeyup中增加对组合键的识别
|
|
|
+ let key = event.key
|
|
|
+ if (event.shiftKey && /^[0-9]$/.test(key)) {
|
|
|
+ key = "!@#$%^&*()"[parseInt(key) - 1]; // 映射Shift+数字到符号
|
|
|
+ } else if(event.shiftKey && /^[a-z]$/i.test(event.key) ){
|
|
|
+ key = event.key.toUpperCase()
|
|
|
+ }
|
|
|
+
|
|
|
+ // 存储down事件信息到keyEventMap中
|
|
|
+ if (!keyEventMap[key]) {
|
|
|
+ keyEventMap[key] = [];
|
|
|
+ }
|
|
|
+
|
|
|
+ // 将每次down事件信息存入数组
|
|
|
+ keyEventMap[key].push({
|
|
|
+ downKey: event.key,
|
|
|
+ startStamp: event.timeStamp,
|
|
|
+ startTime: dayjs(new Date()).format("YYYY-MM-DD HH:mm:ss.SSS")
|
|
|
+ });
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ function formatMilliseconds(milliseconds) {
|
|
|
+ // 把毫秒数转换为秒
|
|
|
+ const totalSeconds = Math.floor(milliseconds / 1000);
|
|
|
+ // 计算小时数
|
|
|
+ const hours = Math.floor(totalSeconds / 3600);
|
|
|
+ // 计算剩余的秒数
|
|
|
+ const remainingSecondsAfterHours = totalSeconds % 3600;
|
|
|
+ // 计算分钟数
|
|
|
+ const minutes = Math.floor(remainingSecondsAfterHours / 60);
|
|
|
+ // 计算最终的秒数
|
|
|
+ const seconds = remainingSecondsAfterHours % 60;
|
|
|
+
|
|
|
+ // 格式化小时、分钟和秒,不足两位前面补 0
|
|
|
+ const formattedHours = String(hours).padStart(2, '0');
|
|
|
+ const formattedMinutes = String(minutes).padStart(2, '0');
|
|
|
+ const formattedSeconds = String(seconds).padStart(2, '0');
|
|
|
+
|
|
|
+ // 返回格式化后的字符串
|
|
|
+ return `${formattedHours}:${formattedMinutes}:${formattedSeconds}`;
|
|
|
+ }
|
|
|
+
|
|
|
+ const getPause = (i: number) => {
|
|
|
+ let result = 0
|
|
|
+ for(let k = i-1; k >= 0; k--){
|
|
|
+ if('startStamp' in changeRecordList[k]){
|
|
|
+ result = changeRecordList[k].startStamp
|
|
|
+ break
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return result
|
|
|
+ }
|
|
|
+
|
|
|
+ const handleKeyup = (event: KeyboardEvent) => {
|
|
|
+ // console.log("keyup")
|
|
|
+ // console.log(event)
|
|
|
+ const realTime = dayjs(new Date()).format("YYYY-MM-DD HH:mm:ss.SSS")
|
|
|
+ const key = event.key;
|
|
|
+ // 处理所有与该按键key有关的keydown行为
|
|
|
+ while (keyEventMap[key] && keyEventMap[key].length > 0) {
|
|
|
+ // 取得keydown事件信息
|
|
|
+ const downEvent = keyEventMap[key].pop();
|
|
|
+ // console.log("keydown事件存储的信息")
|
|
|
+ // console.log(downEvent)
|
|
|
+ // 将keydown信息与keyup整合
|
|
|
+ const combinedEvent = {
|
|
|
+ timeKey: key,
|
|
|
+ startStamp: downEvent.startStamp | 0,
|
|
|
+ startClock: formatMilliseconds(downEvent.startStamp | 0),
|
|
|
+ startTime: downEvent.startTime,
|
|
|
+ endStamp: event.timeStamp | 0,
|
|
|
+ endClock: formatMilliseconds(event.timeStamp | 0),
|
|
|
+ endTime: realTime,
|
|
|
+ actionTime: (event.timeStamp | 0) - (downEvent.startStamp | 0)
|
|
|
+ };
|
|
|
+ // 将得到的key事件的完整信息保存
|
|
|
+ keyEventRecordList.push(combinedEvent);
|
|
|
+
|
|
|
+ // 根据key的信息对行为进行分析记录
|
|
|
+ // 包括
|
|
|
+ // 1.单个字母的键入
|
|
|
+ // 2.删除按键
|
|
|
+ // 3.复制
|
|
|
+ // 4.粘贴
|
|
|
+ // 5.换行
|
|
|
+
|
|
|
+ for (let i = changeRecordList.length - 1; i >= 0; i--) {
|
|
|
+ // 删除按键的记录
|
|
|
+ if (key === 'Backspace') {
|
|
|
+ if (changeRecordList[i].type === 'delete') {
|
|
|
+ changeRecordList[i] = {
|
|
|
+ ...changeRecordList[i],
|
|
|
+ ...combinedEvent,
|
|
|
+ pauseTime: combinedEvent.startStamp - getPause(i)
|
|
|
+ }
|
|
|
+ break
|
|
|
+ } else {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+ // 粘贴
|
|
|
+ else if ((key === 'c' || key === 'C') && event.ctrlKey === true) {
|
|
|
+ changeRecordList[i] = {
|
|
|
+ ...changeRecordList[i],
|
|
|
+ ...combinedEvent,
|
|
|
+ pauseTime: combinedEvent.startStamp - getPause(i),
|
|
|
+ type: 'paste'
|
|
|
+ }
|
|
|
+ break
|
|
|
+ }
|
|
|
+ // 复制
|
|
|
+ else if ((key === 'v' || key === 'V') && event.ctrlKey === true) {
|
|
|
+ changeRecordList[i] = {
|
|
|
+ ...changeRecordList[i],
|
|
|
+ ...combinedEvent,
|
|
|
+ pauseTime: combinedEvent.startStamp - getPause(i),
|
|
|
+ type: 'copy'
|
|
|
+ }
|
|
|
+ break
|
|
|
+ }
|
|
|
+ // 换行
|
|
|
+ else if (key === 'Enter') {
|
|
|
+ changeRecordList[i] = {
|
|
|
+ ...changeRecordList[i],
|
|
|
+ ...combinedEvent,
|
|
|
+ pauseTime: combinedEvent.startStamp - getPause(i),
|
|
|
+ }
|
|
|
+ break
|
|
|
+ }
|
|
|
+ // 字母的键入
|
|
|
+ else if ('key' in changeRecordList[i] && changeRecordList[i].key === combinedEvent.timeKey) {
|
|
|
+ changeRecordList[i] = {
|
|
|
+ ...changeRecordList[i],
|
|
|
+ ...combinedEvent,
|
|
|
+ pauseTime: combinedEvent.startStamp - getPause(i)
|
|
|
+ }
|
|
|
+ break
|
|
|
+ }
|
|
|
+ // TODO: 处理输入法键入
|
|
|
+ }
|
|
|
+
|
|
|
+ // 所有key相关信息处理完毕,删除map
|
|
|
+ if (keyEventMap[key].length === 0) {
|
|
|
+ delete keyEventMap[key];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const download = () => {
|
|
|
+ // const ws = XLSX.utils.json_to_sheet(flattenOps(changeRecordList))
|
|
|
+ const ws = XLSX.utils.json_to_sheet(changeRecordList)
|
|
|
+ /* 新建空workbook */
|
|
|
+ const wb = XLSX.utils.book_new()
|
|
|
+ /* 添加worksheet,当然你可以添加多个,这里我只添加一个 */
|
|
|
+ XLSX.utils.book_append_sheet(wb, ws, 'result')
|
|
|
+
|
|
|
+ const wbout = XLSX.write(wb, {
|
|
|
+ bookType: 'xlsx',
|
|
|
+ bookSST: true,
|
|
|
+ type: 'array'
|
|
|
+ })
|
|
|
+
|
|
|
+ let url = window.URL.createObjectURL(new Blob([wbout]))
|
|
|
+ let link = document.createElement('a')
|
|
|
+ link.style.display = 'none'
|
|
|
+ link.href = url
|
|
|
+ link.setAttribute('download', '测试数据' + '.xls')
|
|
|
+ document.body.appendChild(link)
|
|
|
+ link.click()
|
|
|
+ document.body.removeChild(link) //下载完成移除元素
|
|
|
+ window.URL.revokeObjectURL(url) //释放掉blob对象
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ // 监听 changeRecordList 数组变化
|
|
|
+ watch(
|
|
|
+ () => changeRecordList,
|
|
|
+ (newValue, oldValue) => {
|
|
|
+ console.log(changeRecordList);
|
|
|
+ },
|
|
|
+ { deep: true }
|
|
|
+ )
|
|
|
|
|
|
-emitter.on('get-html', (value: string) => {
|
|
|
- console.log(value)
|
|
|
- getHTML.value = value
|
|
|
-})
|
|
|
-
|
|
|
-emitter.on('get-text', (value: string) => {
|
|
|
- console.log(value)
|
|
|
- getText.value = value
|
|
|
-})
|
|
|
-
|
|
|
-emitter.on('get-quill-content', (value: Object) => {
|
|
|
- console.log(value)
|
|
|
- Object.assign(getQuillContent, value)
|
|
|
-})
|
|
|
-
|
|
|
-// 接收行为数据
|
|
|
-emitter.on('change-record', (value) => {
|
|
|
- console.log(value)
|
|
|
- changeRecordList.push(value)
|
|
|
-})
|
|
|
-
|
|
|
-onUnmounted(() => {
|
|
|
- emitter.off('get-html')
|
|
|
- emitter.off('get-text')
|
|
|
- emitter.off('change-record')
|
|
|
- emitter.off('get-quill-content')
|
|
|
-})
|
|
|
+ onUnmounted(() => {
|
|
|
+ emitter.off("change-record")
|
|
|
+ })
|
|
|
|
|
|
</script>
|
|
|
|