软件工程课程设计 & 理论课实验报告
1-6为课程设计报告,7为理论课报告。
如果帮助到了你,可以进Github页面点个Star噢!
报告仓库:dekrt/Reports: HUST SSE Courses Reports | 华科软件学院课程报告 (github.com)
1. 项目概述
1.1 项目基本介绍
TransWe
Translate WeChat Mini Program
简体中文 | English📱 UI界面
📖 项目介绍
TransWe意为Translation+Wechat
,是一个功能强大的机器翻译微信小程序,它能够通过后台机器翻译服务快速、准确地翻译各种语言。它还支持第三方OCR、语音识别和语音合成集成,为用户提供更便捷、高效的翻译服务。
TransWe功能包括:
- 机器翻译:TransWe使用后台机器翻译服务,支持多种语言翻译,包括英语、中文、法语、德语、日语、韩语等,能够准确、快速地翻译用户的文本。
- OCR识别:TransWe支持第三方OCR识别,用户只需要上传图片或拍摄照片,就能将图片中的文字转换为文本进行翻译。
- 语音识别:TransWe支持第三方语音识别,用户只需要录制音频,就能将音频中的语音转换为文本进行翻译。
- 语音合成:TransWe支持第三方语音合成,用户能够将翻译结果通过语音合成功能转换为语音输出,提高用户的交互体验。
TransWe使用简单,功能强大,只需选择需要翻译的语言,输入要翻译的文本,点击“翻译”按钮,TransWe将自动完成翻译,如果需要使用OCR识别、语音识别或语音合成功能,可以选择相应功能按钮,按照提示进行操作即可。同时TransWe还是一款完全免费的小程序,旨在为用户提供更便捷、高效的翻译服务。无论是旅行、学习还是工作,TransWe都能帮助用户轻松应对语言难题。
🚀 功能特性
TransWe是一款集多种功能于一身的微信小程序,旨在为用户提供便捷、高效的翻译服务。以下是TransWe的主要功能特性:
- 多语言机器翻译:TransWe支持多种语言的翻译,包括但不限于英语、中文、法语、德语、日语、韩语等。我们的后台机器翻译服务能够快速、准确地翻译用户的文本,满足用户在不同场景下的翻译需求。
- OCR识别:TransWe集成了第三方OCR识别技术,用户只需上传图片或拍摄照片,我们的小程序就能将图片中的文字识别出来,转换为文本进行翻译。这一功能特别适用于处理图片中的外语文字,极大地提高了用户的翻译效率。
- 语音识别:TransWe支持第三方语音识别技术,用户只需录制音频,我们的小程序就能将音频中的语音识别并转换为文本进行翻译。这一功能使得用户在无法输入文字时,仍然可以轻松获取翻译服务。
- 语音合成:TransWe集成了第三方语音合成技术,用户可以将翻译结果转换为语音输出,提高了用户的交互体验,特别适用于视力不便或者需要听力辅助的用户。
TransWe的使用非常简单,用户只需选择需要翻译的语言,输入要翻译的文本,我们的小程序就会自动完成翻译。如果用户需要使用OCR识别、语音识别或语音合成功能,只需选择相应的功能按钮,按照提示进行操作即可。
TransWe是一款完全免费的小程序,我们的目标是为用户提供最便捷、高效的翻译服务。无论是旅行、学习还是工作,TransWe都能帮助用户轻松应对语言难题,让语言交流变得无障碍。
🧱 目录结构描述
1 | . |
⬇️ 版本内容更新
- 2023/06/02 TransWe v1.0: 基本实现全部功能
📃 协议
警告
除GPLv3许可下的源代码外,其他方均禁止使用TransWe的名义作为下载器应用,TransWe的衍生产品亦同。 衍生品包括但不限于分叉和非官方构建。
1.2 Github仓库地址
TransWe: https://github.com/dekrt/TransWe
1.3 人员基本分工
1.3.1 dekrt(负责人):
作为项目的负责人,dekrt将承担项目的后端开发工作,工作内容丰富且富有挑战性,主要包括:
- 负责Github仓库管理:dekrt将负责整个Github仓库的管理工作,包括代码的整理、归档以及版本的管理。他还将负责编写详实的README文档,以便于其他团队成员以及可能接触项目的人对项目有明确的了解。
- 将需求作为Issue录入:为了保证项目开发的流畅进行,dekrt将负责将所有的需求作为Issue录入Github,这样可以保证每一个需求都能被详细跟踪,从而高效地进行项目管理。
- 共同进行系统设计:他将积极参与系统设计,利用他的技术专长帮助设计出高效且易于维护的系统。
- 用户界面设计:dekrt将负责用户界面的设计和开发,他会注重用户体验,致力于创建出既易于使用又美观的用户界面。
- 系统流程分析:他将通过绘制时序图进行系统流程分析,这有助于更好地理解和设计系统。
- TDD测试:dekrt将采用测试驱动开发(TDD)的方法,为代码编写详尽的单元测试,以确保代码的质量和稳定性。
- 机器翻译服务:dekrt将负责与后台机器翻译服务的接口开发和维护,他将通过技术手段,确保翻译服务的准确性和效率,提供优质的用户体验。
- 拍照翻译服务:他还将负责开发拍照翻译服务,使用户能够通过拍照的方式获得翻译结果,为用户提供更多的便利。
- 前端页面编写:dekrt将完成
index(小程序主页)、getPic拍照界面)、OCR(获取翻译结果)、history(翻译历史)
等页面的前端和后端代码编写,他将负责实现用户界面设计,同时也会对代码进行详细的测试,以保证其正确性和稳定性。 - 数据库管理:他将负责数据库的设计和管理,通过合理的数据结构设计和索引优化,确保用户数据的安全和完整。
- 性能优化:dekrt将专注于系统的性能优化,通过合理的代码架构和算法优化,他将确保系统在高并发情况下的稳定运行。
- 编写文档:为了保证项目的可维护性和持续性,他将负责编写清晰且详细的开发文档,以便于后续的开发和维护工作。
1.3.2 cdt(副负责人):
cdt将主要负责项目的前端开发和用户体验设计,包括但不限于:
- 共同撰写需求:一起参与需求讨论和构思,确保对项目的理解一致,从而能够产生详尽而有用的项目需求文档。在撰写需求过程中,努力保证每个要求都准确、清晰,易于理解且可行。
- 共同进行系统设计: 在深入理解了项目需求之后,参与到系统设计中来。确保每个设计决策都能满足项目的需求,同时也会考虑到系统的可扩展性和可维护性。
- 辅助用户界面设计:优化部分用户页面,以用户为中心,优化用户体验,使其简单易用。
- 系统设计:参与系统架构的设计,负责组件设计以及组件接口的设计。
- 语音合成功能:负责语音合成功能的开发和测试,和相关页面集成,确保这些功能的正常运行。
- 组件开发:负责components中五个小组件的开发和测试,每一个小组件都将被精心设计和编写,以确保它们在整个系统中都能发挥关键的作用。
- 页面编写:完成了choose_languege(语言选择页面)、edit(文本编辑页面)、voice_translation(语音翻译页面)这几个页面的前端、后端代码编写,实现了用户界面设计,和界面的逻辑功能,并进行了测试
- 编写文档:包括但不限于设计文档、开发文档、测试文档。
2. 需求描述
2.1 功能性描述
2.1.1 用户需求
1 集成翻译服务(Translation Service)
- 用户需求:
- 用户在输入框中输入文字,选择输入语言与目标语言,程序在输出框中给出翻译结果。
- 用户需求标识:TransWe-UR-TS
2 集成第三方OCR功能(Optical Character Recognition)
- 用户需求:
- 用户可以选择使用图片转文字服务(OCR),并进行拍照(或选择图库中的图片)进行翻译。
- 用户需求标识 :TransWe-UR-OCR
3 集成第三方语音识别(Speech Recognition):
- 用户需求:
- 用户选择输入语言及目标语言,通过语音进行输入,程序以文字形式展示输入结果与翻译结果。
- 用户需求标识:TransWe-UR-SR
4 集成第三方语音合成(Speech Synthesis)
- 用户需求:
- 用户在翻译完成后点击发声按钮,程序将翻译结果以语音的形式进行输出。
- 用户需求标识:TransWe-UR-SS
2.1.2 系统需求
1 基础翻译功能(TransWe-SR-TS)
- 初始假设:
- 用户在输入框中输入文字,选择输入语言与目标语言,程序在输出框中给出翻译结果。
- 正常状态:
- 用户在输入框中输入待翻译的文本,程序会自动检测输入语言,用户在下拉菜单中选择相应的目标语言。
- 用户也可以手动选择输入语言。输入完成后,程序将进行翻译并在输出框中显示翻译结果。
- 有哪些会出错:
- 输入的文本中包含无法识别的字符或语言。程序会提示用户重新输入或手动选择语言。
- 输入的文本过长或复杂,程序无法进行翻译。程序会提示用户缩短输入文本或尝试其他翻译方式。
- 网络连接不稳定,程序无法进行翻译。程序会提示用户检查网络连接并重试。
- 其他活动:
- 用户可以在下拉菜单中选择默认语言,程序会在下一次启动时自动选择该语言。
- 程序会记录用户的翻译历史,并允许用户在历史记录中查看以前的翻译结果。
- 完成的系统状态:
- 用户可以通过打开程序,并进入翻译界面来进行翻译。程序会自动检测输入语言,并在下拉菜单中选择相应的目标语言。
- 用户在输入框中输入待翻译的文本后,程序会进行翻译并在输出框中显示翻译结果。
- 程序记录了用户的翻译历史,并允许用户在历史记录中查看以前的翻译结果。
2 集成第三方OCR功能的脚本(TransWe-SR-OCR)
- 初始假设:
- 用户需要使用一个微信翻译小程序,该小程序集成了第三方OCR功能,用户可以通过拍照或上传照片将图片中的待翻译文本识别成目标语言并显示到图片上。
- 正常状态:
用户打开小程序,选择OCR功能,进入拍照界面。用户可以使用手机摄像头拍下待识别的图片,也可以按下图库按钮上传照片。进入图片编辑界面后,用户可以选择整张图片或者框选一部分图片,用户需要选择待翻译的语言种类和目标语言种类。检测到用户按下翻译按钮时,系统应该调用OCR服务对目标图片进行文字识别,并在界面上显示识别结果。
系统应该允许用户在翻译后重新选择图片,重新选择翻译语言并进行翻译。
- 有哪些会出错:
- 系统权限不足,无法访问用户图库。
- 第三方OCR功能出现故障,导致无法完成文字识别。
- 其他活动:
- 系统应该保证用户隐私,不记录用户的OCR识别记录。
- 系统应该对用户图库进行保护,确保不被未授权的其他脚本访问。
- 完成的系统状态:
- 用户可以通过OCR功能成功识别图片中的文字并进行翻译。
3 语音识别功能(TransWe-SR-SR)
- 初始假设:
- 用户希望通过语音输入来输入翻译内容,系统需要进行语音识别功能,将语音转换为文本,再进行翻译操作。
- 正常状态:
- 用户点击语音输入按钮,系统开始录音并将录音转换为文本格式,并输出到屏幕上。
- 系统对文本进行分词并进行翻译操作。
- 翻译结果以文本形式呈现在界面上。
- 有哪些会出错:
- 语音输入的质量不好,无法被识别成文本。系统应该提示用户录音质量不好,请重试。
- 翻译服务不可用或异常。系统应该提示用户翻译服务暂时不可用,请稍后再试。
- 其他活动:
- 系统应该保证用户隐私,不记录用户的语音输入内容。
- 系统应该对录音文件进行保护,确保不被未授权的人访问。
- 完成的系统状态:
- 用户可以通过语音输入方式进行翻译操作。
- 系统可以对语音进行识别并将其转换为文本格式。
- 系统可以对文本进行分词和翻译操作,将翻译结果呈现在界面上。
4 语音合成功能(TransWe-UR-SS)
- 初始假设:
- 用户希望通过语音的形式来输出翻译内容,系统需要进行语音合成功能,将文本转换为语音,再通过扬声器播放。
- 正常状态:
- 用户正常进行翻译后,点击语音输出按钮,系统开始进行语音合成并将翻译结果以语音的形式进行输出。
- 翻译结果以文本形式呈现在界面上。
- 有哪些会出错:
- 系统语音合成功能出现故障,无法将文本正确转换为语音。
- 扬声器或音频设备出现故障,无法正常播放语音。
- 其他活动:
- 当正在播放合成的语音时,图标的颜色将会改变;播放完成后回到原来的颜色。
- 完成的系统状态:
- 用户可以通过语音输入并输出翻译内容,系统可以将文本转换为语音,并通过扬声器播放出来。系统记录了用户的输入和输出内容,并可以对其进行分析和统计。
2.2 非功能性需求
2.2.1 性能需求:
- 翻译响应时间:对于用户输入的文本,系统应在1秒内返回翻译结果。
- OCR识别和语音识别的处理时间:对于用户上传的图片或音频,系统应在5秒内完成识别并返回结果。
- 系统应能够支持高并发请求,即在用户量剧增的情况下,系统的性能不会显著下降。
2.2.2 安全性需求:
- 用户的个人信息和使用数据应得到充分保护,不得泄露给第三方。
- 系统应具备防止恶意攻击的能力,如DDoS攻击、SQL注入等。
2.2.3 可用性需求:
- 系统的正常运行时间应达到99.9%。
- 在出现故障时,系统应能在1小时内恢复正常。
2.2.4 可维护性需求:
- 系统应具备良好的模块化和文档化,以便进行维护和升级。
- 系统应能够容易地添加新的语言支持和新的功能。
2.2.5 可扩展性需求:
- 系统应设计成可扩展的架构,以便在未来可以添加更多的功能,如多语言支持、语音翻译等。
2.2.6 用户体验需求:
- 系统的用户界面应简洁易用,用户能够快速理解如何使用各项功能。
- 系统应提供用户反馈功能,用户可以方便地报告问题和提出建议。
3. 系统设计
3.1 架构设计
我们的翻译小程序采用分层架构的架构模式,架构示意图如下:
3.1.1 表示层(Presentation Layer)
表示层是用户与系统交互的界面,包括用户界面、数据输入、输出等。这一层主要负责将用户请求传递给下一层,并将处理结果返回给用户。在我们的翻译软件中,表示层包括用户输入文本的界面、显示翻译结果的界面、对翻译结果进行输出的页面等,以及获取用户的授权信息。
3.1.2 应用层(Application Layer)
应用层是系统的核心层,它实现了翻译的核心算法和业务逻辑,包括文本处理、翻译算法等。这一层主要负责接收并处理表示层传递的请求,然后调用其他层的服务,最后将处理结果返回给表示层。在我们的小程序中,应用层负责:
- 获取用户输入:
- 手动输入
- OCR识别
- 语音输入
- 获取输出结果:
- 文本输出
- 语音合成输出
3.1.3 服务层(Service Layer)
服务层为应用层提供支持,包括网络通信、数据访问、存储等服务。这一层主要负责处理数据的存储和访问,以及与其他系统的交互。在我们的翻译软件中,服务层可以包括调用翻译接口获取翻译结果、调取OCR接口获取OCR识别结果、调取语音合成API对翻译结果进行语音输出等。
总的来说,以上三个层级构成了一个完整的翻译软件系统,每个层级都负责不同的功能,各司其职。这种分层架构模式使得系统更加清晰、易于扩展和维护。
3.2界面原型设计
3.2.1. 主页面(Main Page)
主页是小程序的入口,包括小程序的基本信息和主要功能模块。页面包含四个模块,分别是文字翻译,录音按钮、OCR翻译按钮和翻译历史记录。
- 文本输入框:用户可以在这个框中手动输入需要翻译的文本。
- 翻译框:用户输入文本、点击这个按钮开始翻译。
- 语音播放器:用户点击播放语音。
- 语言选择器:用户可以在这里选择源语言和目标语言。
- 语音按钮:用户可以点击这个按钮,跳转到语音翻译页面。
- OCR按钮:用户可以点击这个按钮,跳转到语音OCR页面。
- 翻译历史按钮:用户可以点击这个按钮,跳转到翻译历史页面。
3.2.2.语言选择页(Settings Page)
语言选择页提供用户多种翻译语言。用户可以选择支持的语言。
- 语言设置:用户可以在这里更改默认的源语言和目标语言。
3.2.3.语音翻译页(Voice Translation Page)
语音翻译页是用户输入需要翻译的语音的页面。页面主要包括录音按钮、语言选择器和翻译结果框。翻译结果框会以卡片形式保存下来,用户可以编辑录入的文字。
- 录音按钮:位于页面下方,用户可以点击此按钮开始进行语音输入,输入的语音将被实时转化为文字并显示在结果框内。
- 语言选择器:位于页面顶部,用户可以在此一键切换中英文。
- 翻译结果框显示用户录音输入的文字以及翻译结果。翻译结果以卡片形式保存并展示,每个卡片包括原文和译文,用户可以删除不要的卡片。
- 编辑按钮:每个翻译结果卡片右上角都会有一个编辑按钮,用户点击后可以对录入的文字进行编辑,编辑完成后翻译结果将自动更新。
- 返回按钮:页面左上角有一个返回按钮,用户点击后可以返回主页面。
3.2.4.OCR拍照翻译页面(OCR Translation Page)
OCR拍照翻译页面是用户能够通过拍照进行翻译的地方。
- 拍照按钮:用户可以点击这个按钮,利用手机相机进行拍摄,完成拍摄后,系统会自动进行识别并将图片中的文字进行翻译。
- 翻译结果展示区:系统完成翻译后,翻译结果将会在这个区域显示,用户可以查看翻译结果。
3.2.5.翻译历史页(Translation History Page)
翻译历史页面保存用户之前的文字翻译和语音翻译记录。
- 翻译历史:用户可以划动屏幕查询所有的本地翻译记录。每条历史以卡片形式保存并展示,每个卡片包括原文和译文。
3.3 详细设计
3.3.1. 组件设计
3.3.2. 组件接口设计
1 数据库接口
saveTranslationRecord(record: TranslationRecord): void
:保存翻译记录。getTranslationRecords(userId: String): List<TranslationRecord>
:获取指定用户的翻译记录列表。
2 语言设置接口
sourceLanguage: String
:源语言属性。targetLanguage: String
:目标语言属性。setSourceLanguage(sourceLanguage: String): void
:设置源语言。setTargetLanguage(targetLanguage: String): void
:设置目标语言。getSourceLanguage(): String
:获取源语言。getTargetLanguage(): String
:获取目标语言。
3 OCR翻译接口
recognizeImage(image: ImageData, sourceLanguage: String, targetLanguage: String): String
:将图像数据识别为文本,并将其翻译成指定的目标语言。
4 语音翻译接口
<img src="./se-report/Interface_Speech_Translation.png" />
recognizeSpeech(audio: AudioData, sourceLanguage: String, targetLanguage: String): String
:将音频数据识别为文本,并将其翻译成指定的目标语言。
5 文本翻译接口
<img src="./se-report/Interface_TextTranslation.png" />
translateText(text: String, sourceLanguage: String, targetLanguage: String): String
:将指定的文本翻译成指定的目标语言。
6 用户界面接口
<img src="./se-report/Interface_UserInterface.png" />
showTextInputBox(): void
:显示文本输入框。showTranslationResult(text: String): void
:显示翻译结果。showVoiceInputButton(): void
:显示语音输入按钮。showOCRButton(): void
:显示 OCR 按钮。showHistoryButton(): void
:显示历史记录按钮。showVoiceAndCopy(): void
:显示语音合成和复制按钮。
7 语音录入接口
<img src="./se-report/Interface_VoiceAndCopy.png" />
getVoice(text: String, targetLanguage: String): AudioData
:将指定的文本转换为语音,并返回音频数据。copyText(text: String): void
:将指定的文本复制到剪贴板。
8 页面控制接口
<img src="./se-report/Interface_PageController.png" />
+getToPage(page: String)
:表示该接口具有一个公共方法getToPage
,该方法接受一个参数page
,类型为String
,用于获取指定页面的内容。
9 API信息配置接口
<img src="./se-report/Interface_API_Info_Config.png" />
-api_config: ApiConfig
:表示该接口具有一个私有属性api_config
,其类型为ApiConfig
。私有属性只能在该类内部访问。+setApiConfig(api_config : ApiConfig)
:表示该接口具有一个公共方法setApiConfig
,该方法接受一个参数api_config
,类型为ApiConfig
,用于设置api_config
的值。+getApiConfig(): ApiConfig
:表示该接口具有一个公共方法getApiConfig
,该方法返回api_config
的值,类型为ApiConfig
。
10 翻译记录
<img src="./se-report/Class_TranslationRecord.png" />
userId: String
:用户 ID 属性。sourceText: String
:源文本属性。sourceLanguage: String
:源语言属性。targetText: String
:目标文本属性。targetLanguage: String
:目标语言属性。timestamp: DateTime
:时间戳属性。getUserId(): String
:获取用户 ID。getSourceText(): String
:获取源文本。getSourceLanguage(): String
:获取源语言。getTargetText(): String
:获取目标文本。getTargetLanguage(): String
:获取目标语言。getTimestamp(): DateTime
: 获取时间戳。
3.3.3 系统流程分析
<img src="./se-report/UseCase.png" width = "40%"/>
结合上述用例图,我们得出以下的时序图:
1 文本翻译时序图
<img src="./se-report/Sequence_Text_Translation.png" />
2 拍照翻译时序图
<img src="./se-report/Sequence_OCR_Translation.png" />
3 语音翻译时序图
<img src="./se-report/Sequence_Speech_Recognition.png" />
4 语音合成时序图
<img src="./se-report/Sequence_Speech_Synthesis.png" />
5 查看翻译历史时序图
<img src="./se-report/Sequence_Get_Translation_History.png" />
4. 系统实现和测试
4.1 系统实现
开发语言:Html(Wxml) + Css(Wxss) + javascript;
开发框架:微信原生框架 + Less
开发环境:微信开发者工具 + Vscode
4.1.1 assets/
4.1.1.1 assets/iconfont.wxss
这段代码在全局在全局引用了Iconfont的Icon图标。
1 | @font-face {font-family: "iconfont"; |
4.1.2 components/
翻译组件介绍(语音翻译页面配置)
4.1.2.1 components/bottom-button
录音按钮组
以下代码主要功能是:
- 根据语言配置生成对应的录音按钮。
- 当按下按钮时,开始录音,并将按钮样式改为按下状态。
- 当松开按钮时,结束录音,并将按钮样式改回正常状态。
- 当按钮被禁用时,修改按钮样式为禁用状态。
- bottom-button.js
1 | // 引入语言配置文件 |
- bottom-button.json
1
2
3{
"component": true
} - bottom-button.wxml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18<!-- 按钮组容器,当hidden为true时隐藏 -->
<view class="button-wrap" hidden="{{hidden}}">
<!-- 图片大容器 -->
<view class="img-big-wrap">
<!-- 按钮容器 -->
<view class="button-container">
<!-- 使用wx:for指令遍历buttons数组,生成对应的按钮 -->
<view wx:for="{{buttons}}" wx:for-item="button" wx:key="lang" class="button-item">
<view catchtouchstart="streamRecord"
catchtouchend="endStreamRecord"
data-conf="{{button}}"
class="button-press">
<image class="button-background" src="{{buttonBackground[button.lang][button.buttonType]}}"></image>
</view>
</view>
</view>
</view>
</view> - bottom-button.wxss
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79/* 按钮组容器样式,使用flex布局,内容居中 */
.button-wrap {
display: -webkit-flex;
display: flex;
-webkit-justify-content: center;
justify-content: center;
}
/* 图片大容器样式,宽度100%,使用flex布局,背景色为#1494fc */
.img-big-wrap {
width: 100%;
display: -webkit-flex;
display: flex;
background: #1494fc;
}
/* 按钮容器样式,使用flex布局,高度100%,宽度100%,内容居中,底部外边距20px */
.button-container{
display: flex;
display: -webkit-flex;
height: 100%;
width: 100%;
box-sizing: border-box;
justify-content: space-between;
-webkit-justify-content: space-between;
align-items: center;
-webkit-align-items:flex-start;
justify-content: center;
margin-bottom: 20px;
padding: 50rpx 0 38rpx 0;
z-index: 1;
}
/* 按钮项样式,使用flex布局,方向为列,内容从上开始,居中对齐,宽度75px */
.button-item {
display: flex;
display: -webkit-flex;
flex-direction: column;
-webkit-flex-direction: column;
justify-content: flex-start;
-webkit-justify-content: flex-start;
align-items: center;
-webkit-align-items: center;
width: 75px;
box-sizing: border-box;
z-index: 2;
}
/* 按钮标签样式,字体大小28rpx,颜色#9B9B9B,字母间距0,上外边距15rpx */
.button-label {
font-size: 28rpx;
color: #9B9B9B;
letter-spacing: 0;
margin: 15rpx 0 0 0;
}
/* 按钮按下样式,位置相对,使用flex布局,高度150rpx,宽度100%,圆角100rpx,内容居中对齐 */
.button-press {
position: relative;
display: flex;
display: -webkit-flex;
height: 150rpx;
width: 100%;
border-radius: 100rpx;
justify-content: center;
-webkit-justify-content: center;
align-items: center;
-wekbit-align-items:center;
}
/* 按钮背景样式,位置相对,高度150rpx,宽度100%,左边距0,z-index为3 */
.button-background {
position: relative;
height: 150rpx;
width: 100%;
/* border-radius: 100rpx; */
left: 0;
z-index: 3;
}
4.1.2.2 components/modal
工具组件
以下代码主要功能是:
- 三个操作项:复制源文本、复制目标文本和删除条目。
- 当点击复制源文本或复制目标文本时,会调用
setClip
- modal.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108// 导入语言配置
import { language } from '../../utils/conf.js'
// 获取第一种语言配置
const tips_language = language[0]
// 定义模态框中的操作项
let modalItems = [
{
type: 'copySource', // 复制源文本
text: tips_language.copy_source_text
},
{
type: 'delete', // 删除条目
text: tips_language.delete_item
},
{
type: 'copyTarget', // 复制目标文本
text: tips_language.copy_target_text
},
]
// 定义组件
Component({
// 组件的属性列表
properties: {
// 条目数据
item: {
type: Object,
value: {},
},
// 是否显示模态框
modalShow: {
type: Boolean,
value: true,
},
// 条目索引
index: {
type: Number,
},
},
// 组件的初始数据
data: {
modalItems: modalItems, // 模态框操作项
},
// 组件的方法列表
methods: {
// 删除条目并关闭模态框
deleteBubbleModal: function() {
this.triggerEvent('modaldelete', {
item: this.data.item,
index: this.data.index,
},{ bubbles: true, composed: true })
this.leaveBubbleModal()
},
// 点击操作项
itemTap: function(e) {
let itemType = e.currentTarget.dataset.type
let item = this.data.item
switch(itemType) {
case 'copySource': // 复制源文本
this.setClip(item.text)
break;
case 'copyTarget': // 复制目标文本
this.setClip(item.translateText)
break
case 'delete': // 删除条目
this.deleteBubbleModal()
break
default:
break
}
},
// 复制到剪贴板
setClip: function(text) {
wx.setClipboardData({
data: text,
success: (res) => {
this.leaveBubbleModal()
wx.showToast({
title: "已复制到剪切板",
icon: "success",
duration: 1000,
success: function (res) {
console.log("show succ");
},
fail: function (res) {
console.log(res);
}
});
}
})
},
// 关闭模态框
leaveBubbleModal: function() {
this.triggerEvent('modalleave', {
modalShow: this.data.modalShow
})
},
}
}); - modal.json
1
2
3{
"component": true
} - modal.wxml
1
2
3
4
5
6
7
8
9
10
11
12
13
14<!-- 如果modalShow为true,则显示模态框 -->
<view wx:if="{{modalShow}}" style="height:100%;width:100%">
<view class="modal-wrapper">
<!-- 模态框的三角形部分 -->
<view class="modal-triangle"></view>
<!-- 模态框的主体部分,包含一些可点击的选项 -->
<view class="menu-modal">
<!-- 遍历modalItems数组,为每个选项创建一个视图元素 -->
<view wx:for="{{modalItems}}" wx:key="type" class="menu-modal-item" data-type="{{item.type}}" bindtap="itemTap">{{item.text}}</view>
</view>
</view>
</view>
<!-- 如果modalShow为true,则显示一个透明的遮罩层,点击遮罩层可以关闭模态框 -->
<view wx:if="{{modalShow}}" class="modal-hidden" bindtouchstart="leaveBubbleModal"></view> - modal.wxss #### 4.1.2.3 components/play-icon
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86/* 模态框容器样式 */
.modal-wrapper {
position: relative;
color: #FFFFFF;
height: 70rpx;
width: 80%;
margin: 0 auto;
z-index: 70;
opacity: 0.9;
}
/* 模态框中三角形的样式 */
.modal-triangle {
position: relative;
margin: 0 auto;
top: 28px;
height: 0;
width: 0;
border: 5px solid #000000;
transform: rotate(45deg);
}
/* 模态框隐藏状态的样式 */
.modal-hidden {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: #FFFFFF;
opacity: 0;
z-index: 69;
}
/* 模态菜单的样式 */
.menu-modal {
height: 70rpx;
font-size: 14px;
position: absolute;
top: 0;
width: 100%;
display: flex;
display: -webkit-flex;
-webkit-align-items: center;
align-items: center;
box-sizing: border-box;
}
/* 模态菜单项的样式 */
.menu-modal-item {
color: #FFFFFF;
position: relative;
width: 35%;
height: 100%;
display: flex;
display: -webkit-flex;
align-items: center;
-webkit-align-items: center;
justify-content: center;
-webkit-justify-content: center;
background-clip: content-box;
background-color: #000000;
}
/* 第一个模态菜单项的样式,添加左上和左下的圆角 */
.menu-modal-item:first-child {
border-top-left-radius: 8px;
border-bottom-left-radius: 8px;
}
/* 最后一个模态菜单项的样式,添加右上和右下的圆角 */
.menu-modal-item:last-child {
border-top-right-radius: 8px;
border-bottom-right-radius: 8px;
}
/* 模态菜单项之间的分隔线样式 */
.menu-modal-item + .menu-modal-item {
border-left: 1rpx solid #FFFFFF;
}
/* 模态菜单项被按下时的样式 */
.menu-modal-item:active {
background-color: #9e9e9e;
}
播放加载组件
以下代码主要功能是:
- 监听播放状态的变化,当播放状态从'loading'变为'playing'时,根据加载动画的播放次数和剩余的播放时间,决定是立即将播放状态设置为'playing',还是等待剩余的播放时间后再将播放状态设置为'playing'。
- 当播放状态变为'loading'时,记录加载开始的时间。
- play-icon.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76// 加载图标的路径
const loadingIcon = '../../imgs/loading.gif'
Component({
properties: {
// 播放状态,可能的值有'wait'、'loading'和'playing'
playType: {
type: String,
value: 'wait',
// 当playType的值发生变化时,会触发这个函数
observer: function(newVal, oldVal){
// 当播放状态从'loading'变为'playing'时
if(oldVal == 'loading' && newVal == 'playing') {
// 加载动画的周期为1240ms
let loadingTransitionTime = 1240;
// 获取当前时间
let nowTime = + new Date()
// 获取加载开始的时间
let loadingStartTime = this.data.loadingStartTime
// 计算加载的时间
let loadingTime = nowTime - loadingStartTime
// 计算加载动画播放的完整次数
let loadingCount = parseInt(loadingTime / loadingTransitionTime);
// 计算加载动画剩余的播放时间
let timeLeft = loadingTransitionTime - loadingTime % loadingTransitionTime;
// 如果加载动画播放了至少一次,并且剩余的播放时间大于1秒
if(loadingCount > 0 && timeLeft > 1000) {
// 直接将播放状态设置为'playing',并清空加载图标
this.setData({
realPlayType: newVal,
loadingImg: '',
})
} else {
// 否则,等待剩余的播放时间后,再将播放状态设置为'playing'
setTimeout( ()=>{
this.setData({
realPlayType: newVal,
})
}, timeLeft)
}
} else if (newVal == 'loading'){
// 当播放状态变为'loading'时,记录加载开始的时间,并将播放状态设置为'loading'
this.setData({
loadingStartTime: + new Date(),
realPlayType: newVal,
})
} else {
// 对于其他的播放状态,直接更新播放状态
this.setData({
realPlayType: newVal,
})
}
},
}
},
data: {
// 实际在wxml中使用的播放状态
realPlayType: 'wait',
// 加载开始的时间
loadingStartTime: 0,
},
ready: function () {
// 组件准备就绪时执行的函数
},
detached: function() {
// 组件被移除时执行的函数
},
methods: {
// 组件的方法列表
}
}); - play-icon.json
1
2
3
4
5{
"component": true,
"usingComponents": {
}
} - play-icon.wxml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25<!-- 主视图,包含音乐播放图标 -->
<view class="play-loud-icon">
<!-- 根据播放状态显示或隐藏的主播放图标 -->
<image src="../../imgs/play_loud.png" class="play-loud-img play-icon-main {{realPlayType == 'loading' ? 'is-hide' : ''}}" ></image>
<!-- 当播放状态不是'loading'时,以下内容生效 -->
<block wx:if="{{realPlayType != 'loading'}}">
<!-- 当播放状态是'playing'时,显示动画效果 -->
<block wx:if="{{realPlayType=='playing'}}">
<image src="../../imgs/play_loud_1.png" class="play-loud-img play-animation" ></image>
<image src="../../imgs/play_loud_2.png" class="play-loud-img play-animation1"></image>
</block>
<!-- 当播放状态不是'playing'时,显示静态图标 -->
<block wx:else>
<image src="../../imgs/play_loud_1.png" class="play-loud-img"></image>
</block>
</block>
<!-- 当播放状态是'loading'时,显示过渡效果 -->
<block wx:else="{{realPlayType != 'loading'}}">
<view class="play-transition"></view>
</block>
</view> - play-icon.wxss #### 4.1.2.4 components/result-bubble
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117/* 容器的样式 */
.play-loud-icon {
position: relative;
height: 40rpx;
width: 40rpx;
}
/* 音乐播放图标的样式 */
.play-loud-img {
position: absolute;
left: 0;
bottom: 0;
height: 40rpx;
width: 40rpx;
}
/* 主播放图标的过渡效果 */
.play-icon-main {
transition: all .2s ease-out;
}
/* 加载状态的图标样式 */
.play-loading-img {
position: absolute;
height: 40rpx;
width: 40rpx;
left: -1rpx;
top: 0rpx;
}
/* 隐藏元素的样式 */
.is-hide {
opacity: 0;
}
/* 过渡效果的样式,采用背景图base64实现 */
.play-transition {
position: absolute;
height: 40rpx;
width: 40rpx;
background: transparent url(...) no-repeat;
background-size: 40rpx 40rpx;
left: -1rpx;
top: 0rpx;
}
/* 音乐播放动画的共享样式 */
.play-animation,
.play-animation1 {
-webkit-animation-delay: 200ms;
animation-delay: 200ms;
-webkit-animation: tranOpacity 1200ms ease-in-out infinite;
animation: tranOpacity 1200ms ease-in-out infinite;
}
/* 音乐播放动画1的样式 */
.play-animation {
-webkit-animation-name: tranOpacity;
animation-name: tranOpacity;
}
/* 音乐播放动画2的样式 */
.play-animation1 {
-webkit-animation-name: tranOpacity1;
animation-name: tranOpacity1;
}
/* 动画1的关键帧定义 */
@-webkit-keyframes tranOpacity {
0% {
opacity: 0;
}
35% {
opacity: 1;
}
100% {
opacity: 1;
}
}
@keyframes tranOpacity {
0% {
opacity: 0;
}
35% {
opacity: 1;
}
100% {
opacity: 1;
}
}
/* 动画2的关键帧定义 */
@-webkit-keyframes tranOpacity1 {
0% {
opacity: 0;
}
35% {
opacity: 0;
}
100% {
opacity: 1;
}
}
@keyframes tranOpacity1 {
0% {
opacity: 0;
}
35% {
opacity: 0;
}
100% {
opacity: 1;
}
}
翻译框组件
以下代码主要功能是: 1. 接收一个对象item作为输入,该对象包含文本信息以及音频路径等数据。 2. 根据item对象中的数据,触发文字的翻译,并且播放翻译后的音频。 3. 提供了一些界面操作,例如弹出和关闭模态框,以及触发播放和停止音频的操作。
- result-bubble.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164// 引入语言配置
import { language } from '../../utils/conf.js'
// 定义一个组件
Component({
// 定义组件的属性
properties: {
// 属性:item,类型:Object,观察函数进行数据监听
item: {
type: Object,
value: {},
observer: function(newVal, oldVal) {
// 当记录状态为2(翻译完成),且文本有变化,触发重新翻译事件
if(this.data.recordStatus == 2 && oldVal.text && oldVal.text != '' && newVal.text != oldVal.text) {
this.triggerEvent('translate', {
item: this.data.item,
index: this.data.index,
})
}
// 翻译内容改变触发音频播放,或者结束播放动画
if(newVal.autoPlay && newVal.translateVoicePath != oldVal.translateVoicePath){
this.autoPlayTranslateVoice()
} else if(newVal.translateVoicePath == "") {
this.playAnimationEnd()
}
}
},
// 编辑界面展示标志
editShow: {
type: Boolean,
value: false,
},
// 项目索引
index: {
type: Number,
},
// 当前翻译的音频路径
currentTranslateVoice: {
type: String,
observer: function(newVal, oldVal){
if(newVal != '' && newVal != this.data.item.translateVoicePath) {
this.playAnimationEnd()
}
},
},
// 记录状态:0-正在识别,1-正在翻译,2-翻译完成
recordStatus: {
type: Number,
value: 2,
},
},
// 定义组件的内部数据
data: {
// 语言类型
tips_language: language[0],
// 是否显示模态框
modalShow: false,
// 语音播放状态
playType: 'wait',
// 待定义动画
waiting_animation: {},
waiting_animation_1: {},
// 编辑图标路径
edit_icon_path: '../../imgs/edit.png'
},
// 组件生命周期函数-在组件布局完成后执行
ready: function () {
if(this.data.item.autoPlay) {
this.autoPlayTranslateVoice()
}
},
// 组件生命周期函数-在组件实例被从页面节点树移除时执行
detached: function() {
// console.log("detach")
},
// 定义方法
methods: {
// 显示模态框
showModal: function() {
this.setData({modalShow: true})
},
// 离开模态框
modalLeave: function() {
this.setData({modalShow: false})
},
// 点击播放图标,根据播放状态和音频过期时间,来决定播放,停止还是触发过期事件
playTranslateVoice: function() {
let nowTime = parseInt(+ new Date() / 1000)
let voiceExpiredTime = this.data.item.translateVoiceExpiredTime || 0
if(this.data.playType == 'playing') {
wx.stopBackgroundAudio()
this.playAnimationEnd()
} else if(nowTime < voiceExpiredTime) {
this.autoPlayTranslateVoice()
} else {
this.setData({
playType: 'loading',
})
this.triggerEvent('expired', {
item: this.data.item,
index: this.data.index,
})
}
},
// 自动播放翻译后的音频,音频播放结束后,会结束播放动画
autoPlayTranslateVoice: function (path,index) {
let play_path = this.data.item.translateVoicePath
if(!play_path) {
console.warn("no translate voice path")
return
}
wx.onBackgroundAudioStop(res => {
console.log("play voice end",res)
this.playAnimationEnd()
})
this.playAnimationStart()
wx.playBackgroundAudio({
dataUrl: play_path,
title: '',
success: (res) => {
this.playAnimationStart()
},
fail: (res) => {
console.log("failed played", play_path);
this.playAnimationEnd()
},
complete: function (res) {
console.log("complete played");
}
})
},
// 开始播放动画
playAnimationStart: function() {
this.setData({
playType: 'playing',
})
},
// 结束播放动画
playAnimationEnd: function() {
this.setData({
playType: 'wait',
})
},
}
}); - result-bubble.json
1
2
3
4
5
6
7
8{
"component": true,
"usingComponents": {
"modal": "/components/modal/index",
"waiting-icon": "/components/waiting-icon/index",
"play-icon": "/components/play-icon/index"
}
} - result-bubble.wxml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65<!-- 消息气泡容器,长按显示模态框 -->
<view class="bubble-wrap" bindlongpress="showModal" >
<!-- 模态框容器,当状态为2(翻译完成)时显示 -->
<view class="modal-wrap" wx:if="{{recordStatus == 2}}">
<!-- 模态框组件 -->
<modal
modal-show="{{modalShow}}" <!-- 控制模态框显示隐藏 -->
index="{{index}}" <!-- 项目索引 -->
item="{{item}}" <!-- 当前项目 -->
bindmodalleave="modalLeave"> <!-- 模态框离开事件处理函数 -->
</modal>
</view>
<!-- 创建时间显示 -->
<view class="create-time">{{item.create}}</view>
<!-- 消息内容区域 -->
<view class="section-body" data-index="{{index}}" >
<!-- 发送消息区域 -->
<view class="send-message">
<!-- 消息文本内容 -->
<view data-id="{{item.id}}" class="text-content" data-index="{{index}}" >
<!-- 消息详情 -->
<view class="text-detail text-detail-{{item.lfrom}}" >
<!-- 消息文本 -->
{{item.text}}
<!-- 若正在识别(状态为0),则显示等待图标 -->
<waiting-icon wx:if="{{recordStatus == 0}}"></waiting-icon>
</view>
</view>
<!-- 编辑图标,点击进入编辑页面 -->
<navigator
hover-class="navigator-hover"
data-text="{{item.text}}"
data-id="{{item.id}}"
data-index="{{index}}"
class="edit-icon"
wx:if="{{editShow}}"
data-item="{{item}}"
url="{{'/pages/edit/edit?content='+item.text+'&index='+index}}">
<!-- 编辑图标图片 -->
<image class="edit-icon-img" src="{{edit_icon_path}}" ></image>
</navigator>
</view>
<!-- 若正在翻译(状态大于0),显示分割线 -->
<view class="line-between" wx:if="{{recordStatus > 0}}"></view>
<!-- 翻译后的消息区域 -->
<view class="translate-message" >
<!-- 消息文本内容 -->
<view class="text-content">
<!-- 消息详情 -->
<view class="text-detail text-detail-{{item.lto}}">
<!-- 翻译后的文本 -->
{{item.translateText}}
<!-- 若正在翻译(状态为1),则显示等待图标 -->
<waiting-icon wx:if="{{recordStatus == 1}}"></waiting-icon>
</view>
</view>
<!-- 若翻译完成(状态为2),显示播放图标,点击播放翻译音频 -->
<view class="play-icon" catchtap="playTranslateVoice" catchtouchstart="playTranslateVoice" wx:if="{{recordStatus == 2 - result-bubble.wxss #### 4.1.2.5 components/waiting-icon
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130/* 消息气泡容器 */
.bubble-wrap {
position: relative;
}
/* 等待点样式 */
.wait-point {
display:inline-block;
width:6px;
height:6px;
border-radius:3px;
background-color: #ddd;
margin: 0 2px;
}
/* 加载状态样式 */
.loading {
position: relative;
}
/* 分割线样式 */
.line-between {
height: 1px;
width: 100%;
background: #F1F1F1;
overflow: hidden;
margin: 30rpx 0;
}
/* 创建时间文本样式 */
.create-time {
font-size:28rpx;
color: #B2B2B2;
margin-bottom:10px;
display: flex;
justify-content: center;
}
/* 消息内容区域样式 */
.section-body{
word-wrap: break-word;
border-radius: 10px;
position: relative;
width:100%;
background: #FFFFFF;
box-shadow: 0 2px 16px 2px rgba(0,0,0,0.03);
padding:50rpx 60rpx;
box-sizing: border-box;
min-height: 260rpx;
}
/* 消息详情文本样式 */
.text-detail {
font-size: 36rpx;
line-height: 1.231;
vertical-align: text-bottom;
box-sizing: border-box;
font-family: "PingFang-SC-Regular","SimSun","Microsoft Yahei";
}
/* 英文和中文消息详情样式 */
.text-detail-en_US {
line-height: 1.231;
}
.text-detail-zh_CN {
line-height: 1.41;
}
/* 发送和翻译消息样式 */
.translate-message,
.send-message {
position: relative;
padding: 0 2px;
}
.send-message .text-detail {
color: #9B9B9B;
}
/* 编辑图标样式 */
.edit-icon {
position: absolute;
display: flex;
align-items: center;
right: 8rpx;
bottom: 7rpx;
padding: 0 8rpx;
}
.edit-icon-img {
width:40rpx;
height:40rpx;
}
/* 播放图标样式 */
.play-icon {
position: absolute;
right: 3rpx;
bottom: 7rpx;
padding: 0 8rpx;
display: flex;
align-items: center;
}
/* 编辑和播放图标点击范围扩大 */
.edit-icon::before,
.play-icon::before {
content:"";
position:absolute;
top:-10rpx;
left:-10rpx;
bottom:-10rpx;
right:-10rpx;
}
/* 消息文本内容样式 */
.text-content {
margin: 0 48px 0 0;
box-sizing: border-box;
}
/* 模态框容器样式 */
.modal-wrap {
position: absolute;
width: 100%;
box-sizing:border-box;
}
/* 重置navigator样式 */
.navigator-hover {
background-color: #fff;
}
等待加载组件
以下代码主要功能是:
- 控制等待动画的开始,停止
- 设置等待动画的间隔
- waiting-icon.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70// 定义小程序组件
Component({
properties: {
// 这里定义了组件的属性
},
// 组件的初始数据
data: {
waiting_animation: {}, // 定义等待动画对象
waiting_animation_1: {}, // 定义另一个等待动画对象
},
// 组件的生命周期函数,在组件布局完成后执行
ready: function () {
console.log("ready waitting")
// 创建两个等待动画,一个持续600毫秒,一个持续400毫秒
this.waiting_animation = wx.createAnimation({
duration: 600
})
this.waiting_animation_1 = wx.createAnimation({
duration: 400
})
// 设置动画循环
this.setWaitInterval()
},
// 组件生命周期函数,在组件实例被从页面节点树移除时执行
detached: function() {
// 当组件被移除时,清除动画
this.clearAnimation()
},
methods: {
// 清除动画的函数
clearAnimation: function() {
this.endWaitAnimation()
},
// 结束动画的函数,将清除循环,并重置动画对象
endWaitAnimation: function() {
clearInterval(this.data.waiting_interval)
this.setData({ waiting_animation : {}})
this.setData({ waiting_animation_1: {} })
},
// 开始动画的函数,设置动画的参数并启动动画
startWaitAnimation: function () {
this.waiting_animation.opacity(0).scale(1.2, 1.2).step()
this.waiting_animation.opacity(1).scale(1, 1).step()
this.setData({ waiting_animation: this.waiting_animation.export() })
this.waiting_animation_1.opacity(0).scale(1.2, 1.2).step()
this.waiting_animation_1.opacity(1).scale(1, 1).step()
this.setData({ waiting_animation_1: this.waiting_animation_1.export() })
},
// 设置动画循环的函数,将清除并重新启动动画循环
setWaitInterval: function() {
this.endWaitAnimation()
// 创建一个新的循环,每600毫秒启动一次动画
this.data.waiting_interval = setInterval( ()=>{
this.startWaitAnimation()
},600 )
},
}
}); - waiting-icon.json
1
2
3
4
5
6{
"component": true,
"usingComponents": {
}
} - waiting-icon.wxml
1
2
3
4
5<view class="loading">
<view class="loading-icon">.</view>
<view animation="{{waiting_animation}}" class="loading-icon">.</view>
<view animation="{{waiting_animation_1}}" class="loading-icon">.</view>
</view> - waiting-icon.wxss ### 4.1.3 imgs/
1
2
3
4
5
6
7
8
9
10
.loading {
position: relative;
display: inline;
}
.loading-icon {
display: inline;
}
包含微信小程序内部需要用到的静态图片。
4.1.4 pages/
4.1.4.1 pages/choose_language
切换翻译语言的页面,该页面包含两个语言列表,分别代表源语言和目标语言,用户可以通过点击列表中的项来选择语言。选择的语言信息会被保存到本地存储和全局变量中,并在页面显示时更新。
1 choose_language.js
1 | // 获取全局应用实例 |
2 choose_language.json
1 | { |
3 choose_language.wxml
该段代码的主要功能是显示一个语言列表,用户可以通过点击来选择“翻译语言”和“目标语言”。选择的语言会以不同的样式显示,以区分当前
1 | <!-- 页面主体是一个垂直方向的 flex 布局 --> |
4 choose_language.wxss
1 | /* 页面样式,包含内边距,背景色,宽度,溢出处理等样式 */ |
4.1.4.2 pages/edit
编辑文本的输入框页面,编辑文本的最大长度限制为200个字符,页面数据中的edit_text
字段存储输入的文本,remain_length
字段存储剩余可输入的字符数。
当用户在输入框中输入或删除文本时,页面会更新剩余可输入的字符数并显示给用户。当用户提交输入文本时,如果文本长度超过0且与旧文本不相同,页面会将新文本保存到上一页面的对话列表中,并返回上一页面;否则,如果用户提交的文本为空,页面会提供相应的处理。
1 edit.js
1 | //初始化底部高度 |
2 edit.json
1 | {} |
3 edit.wxml
1 | <!-- edit.wxml --> |
3 edit.wxss
1 | /* pages/edit/edit.wxss */ |
4.1.4.3 pages/getPic
1 getPic.js
这段代码的主要功能是:
- 拍照:用户可以点击按钮触发
takeShot
方法,调用微信的摄像头接口进行拍照,拍照成功后,会将图片转换为base64格式,并保存到全局变量中,然后跳转到OCR页面。 - 提示:用户可以点击按钮触发
onTap
方法,显示一个“只支持中译英”的提示。
1 | // 引入api模块,该模块可能包含了一些工具函数 |
2 getPic.json
缺省代码。
1 | { |
3 getPic.wxml
这段代码的主要功能是:
- 头部视图:包含了一个语言切换视图,用户可以点击中文或英文图标和文字来切换语言。
- 摄像头视图:显示一个摄像头,用户可以通过这个摄像头来拍照。
- 工具栏视图:包含了一个拍照图标,用户可以点击这个图标来拍照。
1 | <!-- 头部视图 --> |
4 getPic.wxss
这段CSS样式表的主要功能是:
.head
:设置头部视图的样式,包括位置、大小和背景颜色。.btn
:设置按钮的样式,包括背景颜色、大小、内边距、颜色、边框圆角、外边距和文本对齐方式。.toolbar
:设置工具栏的样式,使用Flex布局,设置宽度、高度、主轴对齐方式和交叉轴对齐方式。.shot
:设置拍照图标的样式,包括大小、文本对齐方式和上外边距。image
:设置图片的样式,包括大小和垂直对齐方式。.language
:设置语言切换视图的样式,使用Flex布局,设置上内边距、宽度、左内边距和垂直对齐方式。.language_pic
:设置语言图标的样式,包括上内边距和垂直对齐方式。.language_text
:设置语言文字的样式,包括上内边距、字体大小、字体家族、行高、颜色、上下左外边距、字体家族和行高。.switch
:设置切换图标的样式,使用Flex布局,设置对齐方式、方向、边框圆角、大小和自动外边距。
这些样式主要用于设置微信小程序中的元素样式,使得元素在页面上的布局和外观符合设计要求。
1 | /* 头部视图样式 */ |
4.1.4.4 pages/history
1 history.js
这段代码的主要功能是:
- 显示历史记录:当页面显示时,会从本地存储中获取历史记录,并显示到页面上。
- 点击历史记录项:用户可以点击历史记录项,页面会重新加载首页,并将点击的历史记录项的查询参数传递给首页。
- 清除历史记录:用户可以点击按钮触发
onClearHistory
方法,清除页面数据中的历史记录数组,并清除本地存储中的历史记录。
1 | // 引入全局的app实例 |
2 history.json
缺省代码。
1 | { |
3 history.wxml
这段代码的主要功能是:
- 显示翻译历史:页面上有一个滚动视图,里面包含了一个历史记录列表视图,列表中的每一项都是一个历史记录项,显示了查询的语言、查询的文本、结果的语言和结果的文本。
- 清除历史记录:用户可以点击“清除历史记录”文本,触发
onClearHistory
事件,清除历史记录。 - 查看历史记录:用户可以点击历史记录项,触发
onTapItem
事件,查看历史记录的详细信息。
1 | <!-- 可滚动视图,设置为纵向滚动 --> |
4 history.wxss
这些样式主要用于设置微信小程序中的元素样式,使得元素在页面上的布局和外观符合设计要求。具体包括:
.history-list
:设置历史记录列表的样式,使用Flex布局,设置为列方向,设置内边距。.header
:设置头部视图的样式,使用Flex布局,设置上外边距。.title
:设置标题文本的样式,设置字体大小和颜色。.icon-close
:设置清除历史记录图标的样式,设置左外边距为自动,设置字体大小和颜色。.item
:设置历史记录项的样式,包括上外边距、内边距、背景颜色、边框圆角和阴影。.item .query
:设置查询视图的样式,包括内边距、底部边框、字体大小、字体家族、行高和颜色。.item .query .language
:设置查询语言的样式,包括字体大小、字体家族、行高、颜色、上下外边距。.item .result
:设置结果视图的样式,包括上外边距、内边距、字体大小、字体家族、行高和颜色。.item .result .language
:设置结果语言的样式,包括字体大小、字体家族、行高、颜色和下外边距。image
:设置图片的样式,包括宽度、高度和垂直对齐方式。
这些样式主要用于设置微信小程序中的元素样式,使得元素在页面上的布局和外观符合设计要求。
1 | /* 历史记录列表样式 */ |
4.1.4.5 pages/index
1 index.js
这段代码的主要功能包括:
- 用户输入文本进行翻译,翻译结果会保存在历史记录中。
- 用户可以点击清除图标清除输入的文本和翻译结果。
- 用户可以复制输入的文本和翻译结果。
- 用户可以播放翻译的语音。
- 用户可以查看翻译的历史记录。
以下是代码的流程图:
1 | classDiagram |
1 | // 引入翻译工具和全局应用实例 |
2 index.json
缺省代码。
1 | {} |
3 index.wxml
这段代码是微信小程序的WXML模板,用于构建用户界面。主要包括以下部分:
- container:这是页面的主容器。
- background:包含logo和背景图片。
- change:这部分包含语言切换部分,用户可以在这里选择翻译的源语言和目标语言。
- input-area:这是用户输入要翻译的文本的地方。输入区域包含一个文本框和一个语音按钮,用户可以通过输入或语音输入文本。
- output-area:这是显示翻译结果的地方。输出区域包含一个文本结果区域和一个语音按钮,用户可以听到翻译的结果。
- trans_history_area:这部分显示用户的翻译历史。用户可以看到他们过去翻译的文本和结果。
这是该代码的框架图:
1 | graph TB |
1 | <!--index.wxml--> |
4 index.wxss
这段代码是微信小程序的WXSS样式表,用于设置页面的样式。主要包括以下部分:
- container:设置页面的主容器的位置、大小和背景颜色。
- logo和background:设置logo和背景图片的位置、大小和显示方式。
- change:设置语言切换部分的样式,包括颜色、字体大小、内外边距、显示方式、对齐方式等。
- input-area和textarea-wrap:设置用户输入要翻译的文本的区域的样式,包括位置、背景颜色、边距、边框、阴影等。
- output-area:设置显示翻译结果的区域的样式,包括显示方式、最小高度、边距、边框、背景颜色、阴影等。
- trans_history_area:设置显示用户的翻译历史的区域的样式,包括边距、标题的样式、列表项的样式等。
1 | .container { |
4.1.4.6 pages/OCR
1 OCR.js
这段代码的主要功能是:
- 上传图片:用户可以通过点击按钮上传图片,然后跳转到另一个页面进行图片的选择和处理。
- 获取OCR:通过调用API,将上传的图片进行OCR识别,提取出图片中的文字,并将识别结果进行翻译。
- 返回按钮:用户可以通过点击返回按钮返回到上一个页面。
1 | // 导入翻译工具 |
2 OCR.json
缺省代码。
1 | { |
3 OCR.wxml
这段代码的主要功能是:
- 显示一个返回按钮,用户可以通过点击返回按钮返回到上一个页面。
- 显示一个图片,图片的源数据是base64编码的。
- 提供一个按钮,用户可以通过点击按钮触发OCR识别和翻译的功能。
- 显示OCR识别和翻译的结果,包括源语言(中文)的文本和目标语言(英语)的文本。
1 | <!-- 头部视图 --> |
4 OCR.wxss
这段代码定义了一些样式规则,用于美化小程序中的各个元素的外观和布局。
其中包括头部视图、返回图标、箭头图标、按钮、项目视图、查询视图、查询语言、结果视图、结果语言和图片的样式。
通过设置不同的样式属性,如位置、大小、颜色等,可以使页面元素呈现出不同的效果,增强用户体验。
1 | /* 容器样式 */ |
4.1.4.7 pages/voice_translation
语音翻译页面,主要功能是通过微信的语音插件实现语音的录制、识别和翻译。
以下代码的主要功能:
- 语音录制:用户按下按钮时,开始进行语音录制,录制过程中会实时显示识别结果。当用户松开按钮时,结束录制,并进行语音识别。
- 语音识别:语音识别的结果会添加到对话列表中,显示在页面上。如果识别结果为空,则会显示提示信息。
- 语音翻译:识别后的文字会被发送到插件进行翻译,翻译的结果会被更新到对话列表中的相应位置。
- 语音播放:当翻译完成后,页面会自动播放翻译后的语音。如果语音文件过期,会重新进行语音合成。
- 语言切换:用户可以点击按钮切换输入语言,支持中英文切换。
- 历史记录:用户的历史录音和翻译记录会被保存,在用户再次进入页面时可以查看。
- 滚动显示:当识别或翻译的内容添加到对话列表时,页面会自动滚动到最新的内容。
1 voice_translation.js
页面数据的配置 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42// 获取应用实例
const app = getApp()
// 引入工具库
const util = require('../../utils/util.js')
// 引入微信语音插件
const plugin = requirePlugin("WechatSI")
// 引入语言配置文件
import { language } from '../../utils/conf.js'
// 获取全局唯一的语音识别管理器
const manager = plugin.getRecordRecognitionManager()
Page({
// 页面的初始数据
data: {
dialogList: [], // 对话列表,初始为空
lan_type: true, // 语言类型
scroll_top: 10000, // 竖向滚动条位置
bottomButtonDisabled: false, // 底部按钮是否禁用
tips_language: language[0], // 提示语言,初始为中文
// 初始时的翻译卡片
initTranslate: {
create: '04/27 15:37',
text: '等待说话',
},
// 当前的翻译卡片
currentTranslate: {
create: '04/27 15:37',
text: '等待说话',
},
recording: false, // 是否正在录音
recordStatus: 0, // 录音状态: 0 - 录音中 1- 翻译中 2 - 翻译完成/二次翻译
toView: 'fake', // 滚动位置
lastId: -1, // dialogList 最后一个item的 id
currentTranslateVoice: '', // 当前播放语音路径
// 图片路径
image_c:'https://codefun-proj-user-res-1256085488.cos.ap-guangzhou.myqcloud.com/644bb0005a7e3f03102917b5/644bb06fb98f5d0011665f39/153cd789341a2b9a6a2d1ac163978ba0.png',
image_e:'https://codefun-proj-user-res-1256085488.cos.ap-guangzhou.myqcloud.com/644bb0005a7e3f03102917b5/644bb06fb98f5d0011665f39/e9fc70c625980d75443bf2ae1516d24f.png'
},
接着是一个函数changelanguage,该函数用于切换语音识别的语言。它首先切换lan_type的值(这可能是一个布尔值,表示使用的是哪种语言)。然后,根据新的lan_type的值,设置适当的初始化翻译文本。
1 | // 按住按钮开始语音识别 |
以下代码定义了函数 translateText ,该函数用于翻译文本。它接受两个参数:一个是要翻译的文本项,另一个是该项在对话列表中的索引。
这个函数首先确定源语言和目标语言,默认为从中文到英文。然后,它调用翻译插件,传入要翻译的文本,并启用文本到语音功能。
如果翻译失败, fail 回调函数会被调用,并记录一个错误。无论成功或失败, complete 回调函数都会被调用,用来更新录音状态,并隐藏加载提示。
1 | /** |
在下面的代码段中,定义了函数translateTextAction,这个函数会在修改文本信息后触发,它调用translateText函数进行翻译。
定义了expiredAction函数,这个函数用于处理语音文件过期的情况,它会调用插件的文本到语音功能重新生成语音文件。
1 | /** |
定义了initCard函数,这个函数用于初始化一张空白的卡片。
然后定义了deleteItem函数,用于删除某一条目。
当列表为空时,deleteItem函数将会调用initCard函数创建一张空白的卡片。
1 | /** |
以下代码包含了对历史记录的获取和设置。
1 | /** |
2 voice_translation.json
1 | { |
3 voice_translationt.wxml
1 | <!--index.wxml--> |
3 voice_translation.wxss
1 | /* 主容器样式 */ |
4.1.5 TDD_test_cdt/
1 | translateText: function(item, index) { |
4.1.6 TDD_test_zxk/
4.1.6.1 node_modules
Mocha 测试框架调用的npm组件
4.1.6.2 translate.js
基于TDD测试开发的获取文本翻译内容的JavaScript代码。
1 | // translate.js |
4.1.6.3 ranslate.test.js
基于Mocha框架编写的测试文本翻译Javascript的测试程序。
1 | // 引入测试库 |
4.1.7 utils/
4.1.7.1 utils/api.js
这段代码在utils/目录内继承了所需要用到的api服务,在小程序内需要用到时可以直接调用,提高了代码的可读性与可维护性。
1 | import md5 from './md5.min.js' |
4.1.7.2 utils/conf.js
语音翻译的语言配置
1 | let language = [ |
4.1.7.3 md5.min.js
这段代码用于获取字符串的十六进制32位下的MD5。
1 | ! function(n) { |
4.1.7.4 util.js
这段代码中定义了三个函数:formatTime,recordTime,和formatNumber。
- formatTime函数接收一个date对象作为参数,获取date对象的年、月、日、时、分、秒,并将它们格式化为字符串。
- recordTime只获取date对象的月、日、时、分,并将它们格式化为字符串。
- formatNumber函数接收一个数值n作为参数,将其转化为字符串并检查是否需要在前面补0。
- 通过module.exports将formatTime和recordTime两个函数导出,使得它们可以在其他文件中被引用。
1 | const formatTime = date => { |
4.1.8 ./
4.8.8.1 ./app.js
在全局的App
对象中,onLaunch
函数定义了当小程序启动时的操作,例如从本地获取当前语言和历史记录。getRecordAuth
函数则用于获取录音权限。onHide
函数定义了当小程序隐藏时的操作,例如停止后台音频。globalData
对象则定义了全局的数据,包括历史记录、当前语言、按钮列表、图片的Base64编码、词语列表和语言列表等。
在语言列表langList
中,每个元素都是一个对象,包含了语言的中文名(chs
)、语言代码(lang
)、索引(index
)、和图标源(src
,对于部分语言)。其中,语言代码是用于识别和设置语言的标识,索引是语言在列表中的位置,图标源是显示语言图标的URL地址。每一个对象代表一种语言。在这个应用中,langList
定义了全局支持的语言列表,包括英语、中文、日语等。每种语言的定义都包括中文名(chs
)、语言代码(lang
)和语言在列表中的索引(index
)。部分语言定义还包括了图标的URL地址(src
),用于在界面上显示对应的图标。
1 | // 导入工具模块 |
4.8.8.2 ./app.json
这段代码是微信小程序的配置文件app.json
中的一部分。app.json
是小程序的全局配置,包括了小程序的所有页面路径、界面表现、网络超时时间、多
tab
等等。简单解释一下,这段代码配置了小程序的页面、窗口表现、使用的插件、sitemap
文件位置以及底部 tab
栏的表现。其中,pages
数组包含了所有页面的路径,window
对象设置了全局窗口的外观,plugins
对象声明了小程序使用的插件,sitemapLocation
字符串指明了sitemap
文件的位置,tabBar
对象则描述了底部 tab 栏的表现。
1 | { |
4.8.8.3 ./app.wxss
这段代码是微信小程序的全局样式表app.wxss
中的一部分。.wxss
是微信小程序的样式语言,类似于
Web 的
CSS,可以设置小程序中的组件样式。这里定义了全局的样式规则,将在整个小程序范围内生效。
这段代码的主要作用是:
- 定义了全局容器的样式,例如背景色、字体颜色和大小、布局方式等。
- 定义了版权信息的样式,例如字体颜色和大小、在容器中的位置等。
- 定义了视图在悬停时的背景颜色。
1 | /* 引入 iconfont 的样式文件 */ |
4.8.8.4 ./project.config.json
这段代码是微信小程序的项目配置文件 project.config.json
的一部分。project.config.json
是微信小程序的项目配置文件,用于配置项目的相关信息,包括项目名,appid,编译设置等。
这个文件对于小程序项目的运行具有重要影响。例如,appid
字段设置了小程序的唯一标识,es6
字段决定了是否需要将 ES6
代码转为 ES5 代码以适应更多环境,minified
字段决定了是否需要压缩代码等。
1 | { |
4.8.8.5 ./project.private.config.json
这段代码是微信小程序的 project.private.config.json
文件的一部分。这个文件是一个私有配置文件,用于覆盖
project.config.json
中的同名字段,存储开发者在开发工具中改动的项目配置。当
project.private.config.json
与
project.config.json
中的字段冲突时,
project.private.config.json
的配置项会优先被应用。
这个配置文件有助于个别开发者或者团队在本地进行特定的设置,比如禁止URL检查或者开启热重载等,并且不会影响到其他的开发人员。
1 | { |
4.2 系统测试
4.2.1 TDD测试
4.2.1.1 TDD_test_cdt/
语音翻译测试
- 测试用例(中翻英)
输入 | 期望输出 |
---|---|
华中科技大学 | Huazhong University of Science and Technology |
我来自武汉 | I am from Wuhan |
如何进入校园 | How to enter the campus |
用户的要求是绝对的 | User requirements are absolute |
- 测试结果
- 测试点1
<img src="./se-report/SS_TDD_VoiceTrans_1.png" width="50%" />
- 测试点2
<img src="./se-report/SS_TDD_VoiceTrans_2.png" width="50%" />
- 测试点3
<img src="./se-report/SS_TDD_VoiceTrans_3.png" width="50%" />
- 测试点4
<img src="./se-report/SS_TDD_VoiceTrans_4.png" width="50%" />
测试通过数(4/4)
4.2.1.2 TDD_test_zxk/
基于Mocha框架编写的测试文本翻译Javascript的测试程序。
1 | // 引入测试库 |
若要进行测试,请在正确安装相关框架的情况下运行:
1 | mocha .\translate.test.js |
测试成功会显示下列截图:
5. 系统界面展示
5.1 index | 主页
在主页中:
- 用户在输入框中输入待翻译的文本,程序会自动检测输入语言,用户在下拉菜单中选择相应的目标语言。
- 用户也可以手动选择输入语言。输入完成后,程序将进行翻译并在输出框中显示翻译结果。
- 用户可以点击小喇叭按钮,调用语音合成功能,听到翻译结果。
- 用户可以点击剪贴板按钮,待翻译文本或翻译结果会自动复制到用户的剪贴板。
- 用户可以点击下方的翻译历史板块,跳转到翻译历史页面。
- 用户可以点击最下方的导航栏跳转到对应的界面。
<img src="./se-report/ScreenShots/SS_index_1.jpg" width="30%" />
<img src="./se-report/ScreenShots/SS_index_2.jpg" width="30%" />
<img src="./se-report/ScreenShots/SS_index_3.jpg" width="30%" />
5.2 choose_language | 选择语言界面
- 在选择语言界面选择翻译语言和目标语言
<img src="./se-report/ScreenShots/SS_chooselanguage1.jpg" width="30%" />
<img src="./se-report/ScreenShots/SS_chooselanguage4.jpg" width="30%" />
<img src="./se-report/ScreenShots/SS_chooselanguage3.jpg" width="30%" />
- 可以看到,翻译页面根据语言选择进行了及时的更新
<img src="./se-report/ScreenShots/SS_chooselanguage2.jpg" width="30%" />
<img src="./se-report/ScreenShots/SS_chooselanguage5.jpg" width="30%" />
<img src="./se-report/ScreenShots/SS_chooselanguage6.jpg" width="30%" />
5.3 getPic | 拍照界面
在这个界面,用户点击拍照按钮可以进行拍照,并进入拍照翻译的结果页面。
<img src="./se-report/ScreenShots/SS_getPic.jpg" width="30%" />
5.4 OCR | 拍照翻译结果
在这个界面,点击翻译图片,小程序会进行OCR识别并进行翻译,将结果显示在屏幕上。
<img src="./se-report/ScreenShots/SS_OCR.jpg" width="30%" />
5.5 voice_translation | 语音翻译页面
语音翻译页面,点击切换按钮改变录音语言。
长按录音按钮,按钮样式改变,出现文字提示正在录音,翻译的结果会以卡片的形式保存在本地。
<img src="./se-report/ScreenShots/SS_voice3.jpg" width="30%" />
<img src="./se-report/ScreenShots/SS_voice4.jpg" width="30%" />
<img src="./se-report/ScreenShots/SS_voice5.jpg" width="30%" />
在卡片右侧对应两个组件,分别代表编辑文本和语音合成。
<img src="./se-report/ScreenShots/SS_voice6.png" width="30%" />
<img src="./se-report/ScreenShots/SS_voice11.jpg" width="30%" />
长按卡片出现弹窗,功能包括复制文本以及删除不用的卡片。
<img src="./se-report/ScreenShots/SS_voice12.jpg" width="30%" />
<img src="./se-report/ScreenShots/SS_voice13.jpg" width="30%" />
<img src="./se-report/ScreenShots/SS_voice14.jpg" width="30%" />
5.6 edit | 文本编辑界面
考虑到用户录音时可能会因为录音失误导致录入的文本有误,为避免用户重新录音的麻烦,允许编辑录音文本,为用户带来更好的体验。
<img src="./se-report/ScreenShots/SS_voice7.jpg" width="30%" />
<img src="./se-report/ScreenShots/SS_voice8.jpg" width="30%" />
设置了最大输入文本限制,用户可以看到剩余可输入文字,点击清空按钮可以快速清除文字
<img src="./se-report/ScreenShots/SS_voice9.jpg" width="30%" />
<img src="./se-report/ScreenShots/SS_voice10.jpg" width="30%" />
5.7 history | 翻译历史
在这个界面,用户可以查看使用小程序的翻译历史,点击对应的翻译历史可以跳转到主页查看翻译结果。
<img src="./se-report/ScreenShots/SS_history.jpg" width="30%" />
6. 总结
在这个课程设计项目中,我们的团队开发了一个名为TransWe的微信小程序。这是一个强大的机器翻译工具,它的核心功能是能够快速准确地翻译各种语言。但是,我们并没有止步于此,我们还集成了第三方OCR、语音识别和语音合成服务,这些功能的加入使得TransWe不仅仅是一个翻译工具,更是一个全方位的语言服务平台,为用户提供了更便捷、高效的翻译服务。
在这个过程中,我们深入了解了微信小程序的开发流程和特性。微信小程序的特性如页面跳转、图片上传、音频播放等,都被我们充分利用,以实现TransWe的各项功能。同时,我们也学习了如何利用第三方服务来增强小程序的功能性和用户体验。例如,我们利用OCR服务实现了图片中文本的识别,利用语音识别和语音合成服务实现了语音翻译功能,这些都大大提高了TransWe的用户体验。
在代码实现方面,我们遵循了良好的编程习惯。我们的代码结构清晰,每个函数、每个模块都有其明确的职责;我们的命名规范,变量名、函数名都能准确地反映其功能;我们的注释详细,每一段重要的代码都有相应的注释,方便后续的维护和修改。这些都是我们在软件工程课程中学到的重要知识,也是我们在实际开发过程中得到应用的地方。
然而,这个项目的开发过程并非一帆风顺。我们遇到了一些挑战,如API调用的问题、图片大小限制的问题等。但是,我们并没有因此而退缩,我们通过查阅文档、搜索解决方案、反复测试等方式,最终都成功地解决了这些问题。这个过程不仅锻炼了我们的问题解决能力,也让我们更加深入地理解了软件开发的实际过程。
总的来说,这个课程设计项目是一次非常宝贵的实践经验。它不仅提升了我们的编程技能,也锻炼了我们的问题解决能力。同时,看到自己的作品能够真正地帮助到用户,也是一种非常满足的感觉。我们深感,软件开发不仅仅是编写代码,更是解决问题,满足用户需求的过程。我们将带着这次的经验和教训,继续在软件工程的道路上探索和前进。
7. 理论课实验报告
下面是软件工程理论课的实验报告:重构实验,寻找代码中的坏味道。
一、实验目的、内容和要求
1.1 实验名称
- 重构实验
1.2 实验目的
理解重构在软件开发中的作用
熟悉常见的代码环味道和重构方法
1.3 实验内容和要求
阅读:Martin Fowler 《重构-改善既有代码的设计》
掌握你认为最常见的8种代码坏味道及其重构方法
从你过去写过的代码或Github等开源代码库上寻找这8种坏味道,并对代码进行重构;反对拷贝别人重构例子。
二、代码重构
2.1 重构1:神秘命名(Mysterious Name)
2.1.1 坏味道代码
1 | def func(a, b): |
这段代码来自我初学python时写的HelloWorld.py
文件,代码定义了一个函数,这个函数的输入是两个数a, b;输出是a + b。
2.1.2 坏味道说明
“神秘命名”是一种常见的坏味道,它指的是在代码中使用没有明确含义的变量、函数、类、模块等命名。这些命名往往缺乏清晰的语义和上下文,让其他开发人员难以理解代码的意图和目的。这可能导致代码可读性和可维护性的下降,并且增加了调试和重构代码的难度。
神秘命名的危害在于它会导致代码难以理解和维护。当其他开发人员需要在代码中添加新的功能或者修改代码时,他们可能需要花费更多的时间来理解这些命名的含义和作用。这可能导致开发时间的延长和错误的引入,从而降低了代码质量和开发效率。
2.1.3 重构方法
为了解决神秘命名的问题,可以采取以下重构方法:
- 更改命名:将神秘的命名更改为更有意义和描述性的名称,使代码更易于理解和维护。
- 引入注释:在代码中添加注释可以帮助其他开发人员理解变量、函数、类等的含义和用途,特别是在一些情况下,无法通过名称清楚地表达其意图时。
- 优化命名规范:制定良好的命名规范并将其应用于整个代码库可以减少神秘命名的发生。在规范中,定义变量、函数、类、模块等的命名规则,并尽可能遵循这些规则。
2.1.4 重构后代码
1 | def add(a, b): # a, b represent the two addend number |
2.2 重构2:重复代码(Duplicated Code)
2.2.1 坏味道代码
1 | // cow movement |
代码来源:我自己写的一道算法题USACO2.4两只塔姆沃斯牛 The Tamworth Two,这段代码的作用是控制农夫和牛在地图中方向的移动。
2.2.2 坏味道说明
“重复代码”是一种常见的坏味道,它指的是代码中存在多个相同或非常相似的代码片段。这些重复的代码可能存在于同一个文件、不同的文件或不同的代码库中,但它们执行的功能相同或者非常相似。
重复代码的危害在于它会导致代码冗余和维护困难。如果存在多个相同或相似的代码片段,每次需要修改功能时,必须修改所有重复的代码。这会增加代码的维护难度,并且可能导致错误的引入。此外,重复的代码还会占用更多的内存和磁盘空间,从而导致代码库变得更加庞大和不易维护。
2.2.3 重构方法
为了解决重复代码的问题,我们可以采取以下重构方法:
- 提取方法:将重复的代码段提取到一个独立的方法中,并在需要时调用该方法。这可以减少重复代码并提高代码的可重用性。
- 抽象公共方法:如果有多个代码段具有相同的结构,可以将它们抽象为一个通用方法,并在需要时使用。这可以减少重复代码的数量并提高代码的可维护性。
2.2.4 重构后代码
1 | void move(object& obj) |
2.3 重构3:过长函数(Long Function)
2.3.1 坏味道代码
1 |
|
这段代码是我利用C语言实现的高精度乘法,可以对两个\(10^{20}\) 的超级大整数相乘,采用数组模拟乘法的相乘与进位,思路是消耗空间换取时间。
2.3.2 坏味道说明
过长函数是指代码中某个函数过于冗长复杂,超过应有的长度限制,使得代码难以阅读、理解和维护。这种坏味道的存在会导致代码质量下降、可读性差、出错率高等问题,并且难以重用或调试。比如这段代码中一连串的for代码根本让人不知所云。
2.3.3 重构方法
对于过长函数的重构,考虑以下思路:
- 拆分函数:将一个函数按照不同的职责或功能进行拆分,形成多个小函数,每个小函数只负责一项具体的工作,这样可以降低单个函数的复杂度和长度。
- 提取方法:将函数中的某些独立操作提取为新的方法,以减少代码重复和提高重用性。
- 优化参数列表:如果函数参数列表过长,可以考虑将其中的相关参数放置在同一个对象中,以简化函数的参数列表并提高代码可读性。
- 使用注释:对于一些长函数,可以使用注释来标识代码的不同执行分支或处理步骤,以提高代码可读性。
2.3.4 重构后代码
1 |
|
2.4 重构4:过长参数列表(Long Parameter List)
2.4.1 坏味道代码
1 | def calculate_score(name, age, gender, height, weight, math_score, english_score, chinese_score): |
该代码来自我在学习python的面向对象的概念是完成的学生成绩管理系统,这段代码的作用是根据学生的性别,身高,体重,成绩等数据计算学生的分数并进行返回。
2.4.2 坏味道说明
“过长参数列表”是指函数或方法的参数数量过多或者参数类型过于复杂,导致函数声明或调用代码难以阅读和理解。这种坏味道可能导致代码的可维护性和可读性降低,同时也会增加代码的复杂度和错误的引入。
过长参数列表的危害在于它会导致代码难以理解和维护。当其他开发人员需要在代码中添加新的功能或者修改代码时,他们可能需要花费更多的时间来理解参数的作用和顺序。这可能导致开发时间的延长和错误的引入,从而降低了代码质量和开发效率。
2.4.3 重构方法
为了解决过长参数列表的问题,我们可以采取以下重构方法:
- 重构为对象:将参数封装成一个对象,并将该对象作为参数传递给函数。这样可以减少函数的参数数量,提高代码的可读性和可维护性。
- 使用默认值:对于一些不是必须的参数,可以设置默认值来避免在调用函数时传递参数。
- 重构为多个函数:如果一个函数的参数过多,可以考虑将其拆分成多个较小的函数,每个函数只需要少量的参数。
- 重构为参数对象:将多个参数封装成一个参数对象,并在函数声明中只传递一个参数对象。这样可以减少函数的参数数量,提高代码的可读性和可维护性。
通过上述重构方法,我们可以减少函数的参数数量,提高代码的可读性和可维护性,避免过长参数列表带来的问题。
2.4.4 重构后代码
1 | class Student: |
2.5 重构5: 全局数据(Global Data)
2.5.1 坏味道代码
1 |
|
2.5.2 坏味道说明
全局数据是指在整个程序中可被访问的数据,它们可以是全局变量、静态变量或常量等。这种坏味道的存在会导致代码的耦合度高、可维护性差、扩展性低等问题。
全局数据的危害包括:
- 导致代码依赖复杂:由于全局数据可以被整个程序的任何部分引用和修改,因此当多个模块之间共享同一个全局数据时,代码的依赖关系变得非常复杂,使得代码难以理解和维护。
- 难以进行单元测试:全局数据的存在会影响到模块的独立性,使得模块的单元测试变得困难,需要考虑全局数据的状态和影响范围。
- 安全性问题:全局数据容易被不同模块同时访问和修改,这可能导致数据的竞争条件和安全漏洞。
2.5.3 重构方法
针对全局数据的坏味道,考虑以下重构方法:将全局数据转换为局部数据:将全局变量转化为函数参数或返回值,将静态变量转化为函数内的局部变量,这样可以减少对全局数据的依赖,提高代码的独立性和可维护性。
2.5.4 重构后代码
1 |
|
2.6 可变数据
2.6.1 坏味道代码
1 |
|
这段代码是我自己写的快速幂,通过二分的思想将求取\(a^b\)的算法从O(n)的复杂度降低至O(log n),在处理需要大量幂运算的程序中大大提高了程序的运行效率。
2.6.2 坏味道说明
可变数据是指在函数中修改了参数的值,导致代码的可读性差、维护成本高、易出现意外错误。重构的方法是尽可能避免修改参数的值,可以采用复制、封装成类等方式避免修改参数的值。
2.6.3 重构方法
要消除可变数据的坏味道,可以使用以下重构方法:将可变数据的修改范围限制在单个函数内部。通过定义局部变量,可以确保变量的生命周期仅限于该函数,并且不会泄露到程序的其他部分。
2.6.4 重构后代码
1 |
|
2.7 重构7: 发散式变化(Divergent Change)
2.7.1 坏味道代码
1 | class Rectangle: |
这段代码是我在学习python面向对象——继承的时候写的代码,代码定义了长方形类和正方形类,其中get_area
函数用来求对应对象的面积。
2.7.2 坏味道说明
发散式变化是指修改一个类需要修改多个地方的代码,导致代码的可维护性差、扩展性差。这种坏味道的存在会导致代码的耦合度高、可维护性差、扩展性低等问题。
2.7.3 重构方法
对于发散式变化的重构,可以考虑以下思路:
- 应用面向对象设计原则:例如单一职责原则(SRP)和开放封闭原则(OCP),确保代码的职责分明,易于扩展和维护。
- 重构共享代码:将不同职责之间共享的代码提取出来,形成通用的类或模块,以降低代码的冗余度。
2.7.4 重构后代码
1 | class Rectangle: |
2.8 数据泥团(Data Clumps)
2.8.1 坏味道代码
1 | int calculate_total_cost(int num_items, float price_per_item, float tax_rate) |
2.8.2 坏味道说明
数据泥团指的是一组数据在代码中反复出现,这些数据之间存在某种联系或者依赖关系,但是却没有被单独封装起来。这样的代码会导致代码重复和可维护性下降。
在上述代码中,num_items
、price_per_item
、tax_rate
、subtotal
、tax_amount
等数据反复出现,这些数据之间存在联系,但是没有被封装起来,导致代码重复,难以维护。
2.8.3 重构方法
重构思路是将这些数据封装起来,形成一个独立的数据结构。这个数据结构可以是一个类、一个结构体、一个数组等等,具体形式取决于具体场景。
对于上述代码,我们可以将num_items、price_per_item、tax_rate
封装成一个结构体,这样,我们就将相关的数据封装成一个Order结构体,并将计算subtotal、tax_amount
的逻辑封装到了相应的函数中,代码变得更清晰、易于维护。
2.8.4 重构后代码
1 | typedef struct { |
三、实验总结
通过这次重构实验,我深刻理解了重构在软件开发中的重要性。重构不仅可以改善代码的设计,提高代码的可读性和可维护性,还可以使代码更加清晰、简洁、易于扩展和修改。在重构过程中,我们需要熟悉常见的代码坏味道和重构方法,只有在深入理解和掌握了这些概念和技巧之后,才能正确地进行代码重构。
在本次实验中,我阅读了Martin Fowler的《重构-改善既有代码的设计》,并掌握了8种常见的代码坏味道及其重构方法。对于每一种坏味道,我都学会了相应的重构方法,例如,对于长方法,可以采取函数分解、函数组合以及提炼函数的方式进行重构;对于大类,可以采取提炼子类、提炼接口、提炼模块、提炼聚集以及提炼超类的方式进行重构。
在完成实验要求时,我选择了从自己过去写过的代码和Github等开源代码库中寻找这8种常见的坏味道,并对代码进行重构。在实践中,我深刻感受到了代码重构的必要性和难度。在重构过程中,需要仔细分析每一行代码以及代码之间的关系,考虑如何改进代码的结构和逻辑。同时还需要避免破坏代码的原有功能。
总之,通过这次实验,我对代码重构有了更深入的理解和认识。我认识到,在日后的编程工作中,我需要注意以下几个方面:首先,我的代码应该尽可能地简洁、可读、易于维护;其次,我应该时刻关注代码中存在的坏味道,及时采取相应的重构方法优化代码;最后,我需要不断学习和掌握新的技术和工具,以便更加高效和优雅地完成编程任务。