本次学习围绕“拍照记单词”项目展开,该项目是一款结合AI多模态技术、前端开发、后端部署的轻量化英语学习工具,核心功能是通过拍照或上传图片,调用大模型接口解析图片内容,提取核心英文单词、生成例句及场景化解释,同时支持音频播放,帮助用户在真实场景中高效记忆单词。通过对该项目的全面学习,我系统掌握了Vue3+TS前端开发、NestJS后端技术、多模态大模型接口调用、产品原型设计等相关知识,也深刻理解了AI时代下产品开发的思路与逻辑。本笔记将从项目背景、产品分析、技术架构、代码解析、问题总结与拓展思考六个维度,详细记录学习过程中的知识点、难点及收获,确保内容全面、逻辑清晰,为后续同类项目开发提供参考。
一、项目背景与AI时代发展趋势
1.1 AI时代的技术变革与产品机遇
随着人工智能技术的快速迭代,尤其是大模型、多模态技术的突破,互联网行业迎来了“所有产品值得用AI重新做一遍”的全新机遇。在AI时代,技术不再是单纯的工具,而是深度融入产品设计、用户体验的核心要素,能够极大地提升产品的效率、智能化水平和用户粘性。
项目中提到的“vibe coding”理念,正是AI时代开发模式的体现——代码和项目开发变得快速且靠谱,AI能够辅助开发者完成需求分析、代码编写、bug调试等一系列工作,降低开发门槛,提升开发效率。同时,“one person company(一人公司)”的概念也逐渐成为可能,借助AI工具,个人能够完成创意构思、产品规划、商业分析、用户共情等多个环节的工作,尤其是AI产品经理的角色,需要具备将AI技术与用户需求结合的能力,打造出贴合用户痛点的智能化产品。
1.2 单词学习类产品的市场现状与痛点
在英语学习领域,单词记忆是核心痛点之一,传统的单词记忆方式枯燥、低效,难以结合真实场景,导致用户记忆不牢固、遗忘速度快。当前市场上已有多款单词类APP,通过不同的模式解决用户的单词记忆需求,其中最具代表性的是百词斩和扇贝,通过对这两款产品的分析,能够为“拍照记单词”项目提供借鉴。
百词斩的核心优势的是“细分领域背单词”,将单词与形象的图片结合,通过视觉联想帮助用户记忆,例如“awkward(尴尬的)”“giraffe(长颈鹿)”等单词,搭配对应的场景图片,能够让用户快速建立单词与含义的关联,降低记忆难度。这种“图文结合”的模式,也为“拍照记单词”项目提供了核心灵感——借助图片解析,让单词记忆更贴近真实场景。
扇贝的核心优势是“智能间隔重复算法”,该算法能够精准规划用户的复习时间,确保单词在即将遗忘时被强化记忆,从而实现长期记忆。这种“科学复习”的理念,也是“拍照记单词”项目需要借鉴的点,后续可以通过优化产品功能,加入复习规划模块,提升用户的单词记忆效果。
尽管现有单词类APP各有优势,但仍存在明显的痛点:一是场景化不足,多数APP的单词记忆脱离真实生活场景,用户在实际使用中难以灵活运用;二是交互不够便捷,需要用户手动输入单词或选择单词本,操作繁琐;三是个性化不足,无法根据用户的英语水平(如A1~A2级别)定制单词难度和例句。而“拍照记单词”项目,正是针对这些痛点,结合AI多模态技术,打造出“拍照即记词、场景化学习、个性化适配”的轻量化工具,填补市场空白。
1.3 项目核心定位与价值
“拍照记单词”项目的核心定位是:一款面向有基础英语学习需求(A1~A2级别)、注重场景化记忆的轻量化单词学习工具,适用于跨国生活、旅游、点餐等真实场景,帮助用户快速识别图片中的核心单词,掌握单词的用法和场景应用。
项目的核心价值主要体现在三个方面:一是便捷性,用户无需手动输入单词,只需拍照或上传图片,即可快速获取单词、例句及解释,降低操作门槛;二是场景化,结合图片场景记忆单词,让用户在真实场景中理解单词的用法,提升单词运用能力;三是智能化,借助多模态大模型和TTS技术,实现单词解析、音频播放等功能,提升用户学习体验。同时,项目还注重无障碍访问,通过label for + input#id等技术,帮助使用读屏器的盲人用户使用,体现产品的包容性。
二、产品分析与原型设计
2.1 产品核心需求与场景分析
产品的核心需求来源于用户在真实场景中对单词识别和记忆的需求,具体可分为以下几类:
-
识别需求:用户在跨国旅游、点餐、购物时,遇到不认识的英文单词(如菜单上的菜品名称、商品标签、路标等),需要快速识别单词含义;
-
记忆需求:识别单词后,需要掌握单词的发音、例句及用法,实现快速记忆,便于后续运用;
-
便捷需求:操作流程简单,无需复杂步骤,拍照即可完成单词识别和学习,节省时间;
-
个性化需求:根据用户的英语水平(A1~A2级别),提供简单易懂的单词和例句,避免过于复杂的词汇和句式。
结合核心需求,产品的核心应用场景主要包括:
-
跨国生活场景:用户在国外生活时,遇到家具、日用品、食品等物品的英文标签,拍照即可识别单词,了解物品名称和用法;
-
旅游场景:在国外旅游时,识别路标、景点介绍、菜单等内容中的英文单词,解决语言沟通障碍;
-
日常学习场景:用户看到身边的物品,拍照识别对应的英文单词,结合场景记忆,提升单词积累效率。
产品的核心痛点是“足够痛、强需求”——用户在真实场景中遇到不认识的英文单词时,往往没有时间手动查询词典,需要快速、便捷的识别和学习方式,而“拍照记单词”项目正好解决了这一痛点,能够在用户需要时,快速提供单词解析和学习内容。
2.2 竞品分析与产品差异化
除了前文提到的百词斩、扇贝,“拍照记单词”项目的竞品还包括多邻国等APP。多邻国的核心优势是“游戏化学习”,通过趣味关卡、打卡等方式,提升用户的学习积极性,但其拍照记词功能较为简单,主要以单词识别为主,缺乏场景化解释和个性化适配。
与现有竞品相比,“拍照记单词”项目的差异化优势主要体现在以下几点:
-
场景化深度结合:以图片为核心,不仅识别单词,还会结合图片场景生成例句和解释,让用户在理解场景的基础上记忆单词,提升记忆效果;
-
个性化适配:针对A1~A2级别用户,提供简单易懂的单词和例句,避免过于复杂的内容,贴合用户的英语水平;
-
轻量化设计:核心功能聚焦于拍照记词,操作流程简单,无需注册登录(可后续优化),打开即可使用,适合碎片化学习;
-
无障碍访问:考虑到特殊用户群体的需求,加入无障碍设计,提升产品的包容性;
-
多模态技术应用:结合多模态大模型(kimi-shot、moonshot-v1-8k-vision-preview)和TTS技术,实现图片解析、音频播放等功能,提升产品的智能化水平。
2.3 产品原型设计思路
产品原型的核心是围绕“拍照/上传图片→解析图片→获取单词学习内容→音频播放”的核心流程,设计简洁、便捷的交互界面,确保用户能够快速完成操作。根据项目文档,产品原型的核心功能模块及交互流程如下:
- 核心功能模块:
(1)拍照/上传图片模块:用户可通过点击摄像头图标,调用手机摄像头拍照,或上传本地图片,实现图片输入;
(2)图片解析模块:调用kimi大模型接口,解析图片内容,提取最能描述图片的英文单词(A1~A2级别),生成图片描述、例句、场景化解释及回复;
(3)音频播放模块:点击播放按钮,调用TTS技术,播放单词或例句的发音,帮助用户掌握正确的发音;
(4)详情展开模块:用户可点击“Talk bout it”按钮,展开详情页面,查看图片预览、单词解释及回复内容,收起则隐藏详情,保持界面简洁。
- 交互流程设计:
用户打开产品→点击摄像头图标→拍照/上传图片→系统自动解析图片→显示单词、例句→点击播放按钮听发音→点击详情按钮查看完整解释→完成单词学习。
- 页面设计要点:
(1)主页面:简洁明了,核心突出拍照功能,避免多余的元素干扰用户操作;
(2)图片上传/拍照页面:适配手机屏幕,提供清晰的摄像头调用入口和图片上传入口;
(3)结果展示页面:单词居中显示,字体清晰,例句和发音按钮便于用户查看和操作,详情模块隐藏,避免界面杂乱;
(4)详情页面:图片预览清晰,解释内容分行显示,回复内容带有边框,便于区分,整体布局简洁、易读。
2.4 设计稿相关思考
项目文档中未提供具体的设计稿,但结合代码中的样式部分,能够看出设计稿的核心思路是“简洁、美观、贴合场景”。设计风格以暖色调为主,背景采用线性渐变(从rgb(235,189,166)到rgb(71,49,32)),营造出温馨、舒适的学习氛围;卡片式设计,搭配阴影效果,提升界面的层次感;按钮、图片等元素采用圆角设计,增强界面的柔和度;文字颜色对比清晰,确保用户能够轻松阅读。
设计过程中需要注意的细节:一是适配不同屏幕尺寸,确保在手机、平板等设备上都能正常显示;二是优化图片上传和预览的体验,确保图片显示清晰、加载快速;三是无障碍设计,确保读屏器能够正常识别界面元素,帮助特殊用户使用;四是交互反馈,如图片上传中显示加载状态、解析完成后显示提示等,提升用户体验。
三、技术架构与技术调研
3.1 技术架构整体设计
“拍照记单词”项目采用前后端分离的技术架构,前端负责用户交互、界面展示、图片上传、音频播放等功能,后端负责接口开发、大模型调用、数据处理等功能,整体架构清晰、易于维护和扩展。具体架构如下:
-
前端层:采用Vue3+TS+Composition API开发,结合组件化思想,将页面拆分为PictureCard组件和主页面,实现代码复用和维护;使用FileReader实现图片本地读取和base64编码,用于多模态接口调用;使用TTS技术实现文本转语音,提供音频播放功能;
-
接口层:前端通过调用kimi大模型接口(moonshot-v1-8k-vision-preview),实现图片解析和单词生成;后端(NestJS)负责接口转发、鉴权、数据处理等,确保接口调用的安全性和稳定性;
-
数据层:暂时未涉及数据库存储(可后续优化,加入用户单词本、复习记录等功能),当前主要通过前端响应式数据存储用户的图片数据、单词信息、音频地址等;
-
技术支撑层:包括多模态大模型(kimi-shot、moonshot-v1-8k-vision-preview)、TTS文本转语音技术、前端构建工具(Vite)等,为项目提供核心技术支持。
3.2 核心技术调研与选型
3.2.1 大模型选型与调研
项目的核心技术之一是多模态大模型的选型,多模态模型能够同时处理图片和文本信息,实现图片内容解析和单词生成。经过调研,项目选择了kimi的多模态模型,具体包括kimi-shot和moonshot-v1-8k-vision-preview,选型理由如下:
-
多模态能力强:能够精准解析图片内容,提取核心元素,生成贴合场景的单词和例句,符合项目的核心需求;
-
接口友好:提供清晰的API接口文档,支持图片base64编码传入,便于前端调用;
-
响应速度快:对于简单图片的解析,响应时间较短,能够提升用户体验;
-
支持自定义Prompt:能够通过Prompt设计,控制单词难度(A1~A2级别)、输出格式(JSON),满足项目的个性化需求。
多模态模型的调用流程:前端将图片转换为base64编码,结合自定义Prompt,通过POST请求调用kimi接口,接口返回JSON格式的响应数据,包含图片描述、代表单词、例句、解释等内容,前端解析数据后展示给用户。
3.2.2 TTS技术调研与选型
TTS(Text to Speech,文本转语音)技术用于实现单词和例句的音频播放,提升用户的学习体验。项目中采用的TTS技术,核心是将文本(单词、例句)转换为音频文件(如MP3),并通过前端播放。选型时主要考虑以下几点:
-
发音标准:确保英语发音准确、清晰,符合 native speaker 的发音习惯;
-
响应速度快:能够快速将文本转换为音频,避免用户等待;
-
接口便捷:支持前端直接调用,或通过后端接口转发,集成难度低;
-
兼容性好:支持不同浏览器、不同设备,确保音频能够正常播放。
项目中通过generateAudio函数调用TTS接口,将例句转换为音频URL,然后通过Audio对象实现播放,流程简洁、高效。
3.2.3 前端技术栈选型
前端采用Vue3+TS+Composition API的技术栈,选型理由如下:
-
Vue3的优势:相比Vue2,Vue3具有更好的性能、更小的体积,支持Composition API,能够更灵活地组织代码,提升代码的复用性和维护性;
-
TypeScript的优势:提供静态类型检查,能够在开发阶段发现代码中的错误,提升代码的健壮性和可维护性,尤其适合团队开发;
-
Composition API的优势:相比Options API,Composition API能够将相关的逻辑代码聚合在一起,避免代码分散,便于逻辑的复用和维护,适合复杂组件的开发;
-
组件化思想:将页面拆分为PictureCard组件和主页面,实现代码复用,降低开发难度,便于后续功能扩展和维护。
此外,前端还使用了Vite作为构建工具,Vite具有快速的冷启动、热更新等优势,能够提升开发效率;使用CSS Scoped实现样式隔离,避免样式冲突,提升代码的可维护性。
3.2.4 后端技术栈选型
后端采用NestJS作为开发框架,NestJS是一款基于Node.js的后端框架,具有以下优势:
-
模块化设计:支持模块化开发,能够将不同的功能模块拆分,提升代码的复用性和维护性;
-
依赖注入:支持依赖注入,便于代码的测试和解耦;
-
类型安全:与TypeScript完美结合,提供静态类型检查,提升代码的健壮性;
-
丰富的生态:提供丰富的中间件、插件,支持多种数据库、缓存、认证等功能,便于项目的扩展。
后端的核心功能是接口转发、鉴权、数据处理等,例如将前端的图片解析请求转发给kimi大模型接口,对接口返回的数据进行处理后,返回给前端;同时,后端还可以实现用户认证、单词本存储等功能(后续优化)。
3.3 技术难点与解决方案
3.3.1 图片处理与base64编码
难点:多模态大模型接口需要传入图片的base64编码,而前端需要将用户上传的图片或拍照的图片转换为base64编码,同时要确保图片加载快速、编码正确,避免出现接口调用失败的情况。
解决方案:使用HTML5的FileReader API,实现图片的本地读取和base64编码。具体流程如下:用户上传图片或拍照后,获取文件对象,创建FileReader实例,调用readAsDataURL方法读取文件,在onload事件中获取base64编码的图片数据,然后将其传入大模型接口。同时,优化图片加载体验,在图片读取过程中显示加载状态,避免用户误以为操作失败。
3.3.2 大模型接口调用与数据解析
难点:大模型接口的调用需要注意请求格式、鉴权方式,同时接口返回的数据格式需要严格按照Prompt中定义的JSON格式,否则会导致前端解析失败;此外,接口响应时间可能受到网络影响,需要处理加载状态和异常情况。
解决方案:
-
严格按照接口文档要求,设置请求头(Content-Type、Authorization),请求体中传入模型名称、messages(包含图片base64和Prompt)、stream(关闭流式输出)等参数;
-
自定义Prompt,明确要求大模型返回JSON格式的数据,指定JSON中的字段(image_description、representative_word、example_sentence等),确保数据格式正确;
-
处理接口调用的异常情况,例如网络错误、接口返回错误信息等,通过try-catch捕获异常,给用户显示错误提示;
-
在接口调用过程中,显示“分析中...”的提示,让用户了解当前状态,提升用户体验。
3.3.3 音频播放与兼容性
难点:不同浏览器、不同设备对音频播放的支持存在差异,可能出现音频无法播放、播放卡顿等问题;同时,音频文件的加载速度也会影响用户体验。
解决方案:
-
使用HTML5的Audio对象实现音频播放,确保兼容性;
-
优化音频加载速度,通过TTS接口生成的音频URL尽量轻量化,减少加载时间;
-
处理音频播放的异常情况,例如音频加载失败、播放失败等,给用户显示提示信息;
-
提供清晰的播放按钮,用户点击后立即播放,提升交互体验。
3.3.4 无障碍访问实现
难点:实现无障碍访问,需要确保读屏器能够正常识别界面元素,尤其是图片、按钮、输入框等,同时要处理好样式控制与无障碍的兼容性(如input[type="file"]难以控制样式)。
解决方案:
-
使用label for + input#id的方式,将标签与输入框关联,读屏器能够通过标签识别输入框的功能;
-
对于难以控制样式的input[type="file"],使用display: none;隐藏,通过label标签触发输入框的点击事件,既保证样式美观,又不影响无障碍访问;
-
为图片、按钮等元素添加alt属性和清晰的文本提示,确保读屏器能够正确识别元素的功能和内容。
四、代码详细解析
4.1 前端代码整体结构
前端代码分为两个主要组件:主页面(App.vue)和PictureCard组件(PictureCard.vue),采用Vue3+TS+Composition API开发,代码结构清晰,逻辑连贯。具体结构如下:
-
主页面(App.vue):负责整体界面布局、图片解析逻辑、音频播放、详情展开/收起等功能,引入PictureCard组件,接收组件传递的图片数据,调用大模型接口,处理响应数据并展示;
-
PictureCard组件:负责图片上传、拍照、图片预览等功能,通过props接收主页面传递的单词、音频信息,通过emit向主页面传递图片数据,实现组件间的通信。
4.2 主页面(App.vue)代码解析
4.2.1 脚本部分(script setup lang="ts")
脚本部分主要实现响应式数据定义、大模型接口调用、音频生成、数据处理等功能,具体解析如下:
- 引入依赖:
import PictureCard from './components/PictureCard.vue';
import { ref } from 'vue';
import { generateAudio } from './lib/audio.ts';
-
引入PictureCard组件,用于图片上传和预览;
-
引入ref函数,用于创建响应式数据(Vue3中,ref用于创建基本类型的响应式数据,包裹成带.value属性的响应式对象);
-
引入generateAudio函数,用于调用TTS接口,生成音频URL。
- 定义响应式数据:
const imagePreview = ref(''); // 图片预览地址(base64)
const userPrompt = `...`; // 自定义Prompt,用于大模型接口调用
const word = ref('请上传文件'); // 识别出的单词
const audio = ref(''); // 音频URL
const sentence = ref(''); // 例句
const detailExpand = ref(false); // 详情展开/收起状态
const explanations = ref([]); // 单词解释(分行存储)
const expReplys = ref([]); // 解释对应的回复
-
imagePreview:存储图片的base64编码,用于图片预览和大模型接口调用;
-
userPrompt:自定义的Prompt,明确要求大模型解析图片,返回A1~A2级别单词、JSON格式数据,包含图片描述、单词、例句、解释等字段;
-
word、audio、sentence:分别存储识别出的单词、音频URL、例句,用于界面展示;
-
detailExpand:控制详情模块的展开和收起,默认收起;
-
explanations、expReplys:分别存储单词解释(分行处理后)和对应的回复,用于详情模块展示。
- 核心函数:update函数(图片解析核心函数)
const update = async(imageDate: string) => {
imagePreview.value = imageDate; // 设置图片预览地址
const endpoint = import.meta.env.VITE_KIMI_API_ENDPOINT + '/chat/completions'; // 大模型接口地址
const headers = {
'Content-Type': 'application/json',
'Authorization': `Bearer ${import.meta.env.VITE_KIMI_API_KEY}`, // 鉴权信息
}
word.value = '分析中...'; // 显示加载提示
try {
const response = await fetch(endpoint,{
method: 'POST',
headers,
body: JSON.stringify({
model: 'moonshot-v1-8k-vision-preview', // 模型名称
messages: [
{
role: 'user',
content: [{
type: 'image_url',
image_url: {
url: imageDate, // 图片base64编码
}
},{
type: 'text',
text: userPrompt, // 自定义Prompt
}]
}
],
stream: false, // 关闭流式输出
})
});
const data = await response.json(); // 解析接口响应数据
const replyData = JSON.parse(data.choices[0].message.content); // 解析JSON格式的回复内容
// 更新响应式数据
word.value = replyData.representative_word;
sentence.value = replyData.example_sentence;
explanations.value = replyData.explaination.split('\n').filter((item:string) => item !== ''); // 分行处理解释内容,过滤空行
expReplys.value = replyData.explanation_replys;
// 生成音频URL
const audioUrl = await generateAudio(replyData.example_sentence);
audio.value = audioUrl;
} catch (error) {
word.value = '解析失败,请重新上传'; // 错误提示
console.error('解析失败:', error);
}
}
函数解析:
-
接收参数imageDate(图片base64编码),设置图片预览地址;
-
拼接大模型接口地址,设置请求头(Content-Type为application/json,Authorization为鉴权信息,通过环境变量获取,避免硬编码);
-
设置word为“分析中...”,提示用户当前正在解析;
-
使用fetch发起POST请求,请求体中传入模型名称、messages(包含图片和Prompt)、stream(关闭流式输出);
-
解析接口响应数据,将大模型返回的JSON字符串解析为对象,提取单词、例句、解释等内容,更新响应式数据;
-
调用generateAudio函数,生成例句的音频URL,更新audio响应式数据;
-
使用try-catch捕获异常,出现错误时显示“解析失败,请重新上传”的提示,并打印错误信息。
- 提交函数:submit函数
const submit = (imageData: string) => {
update(imageData);
}
函数解析:接收PictureCard组件传递的图片base64编码,调用update函数,触发图片解析流程。
4.2.2 模板部分(template)
模板部分负责界面布局和交互,结合响应式数据,实现动态展示,具体解析如下:
<!-- 引入PictureCard组件,传递单词、音频信息,监听update-image事件 -->
<PictureCard
:word="word"
:audio="audio"
@update-image="submit"
/>
<!-- 结果展示区域 -->
{{ sentence }}<!-- 显示例句 -->
<!-- 详情展开/收起按钮 -->
<button @Talk bout it<!-- 收起状态 -->
<!-- 展开状态:显示图片预览、单词解释、回复 -->
{{ item }}{{ item }}
模板解析:
-
container:主容器,采用flex布局,垂直排列,居中对齐,设置背景渐变;
-
PictureCard组件:传递word(单词)、audio(音频URL) props,监听update-image事件,当组件传递图片数据时,调用submit函数;
-
output:结果展示区域,显示例句,设置居中对齐、加粗字体;
-
details:详情模块,固定在页面底部,居中显示;
-
按钮:点击后切换detailExpand的状态,实现详情的展开和收起;
-
fold:详情收起状态,显示白色圆角矩形,保持界面简洁;
-
expand:详情展开状态,显示图片预览、单词解释(循环渲染explanations数组)、回复(循环渲染expReplys数组),设置白色背景、圆角,提升可读性。
4.2.3 样式部分(style scoped)
样式部分采用CSS Scoped,实现样式隔离,避免与其他组件冲突,具体解析如下:
.container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: start;
margin: 0;
padding: 0;
width: 100vw;
height: 100vh;
font-size: .85rem;
background: linear-gradient(180deg,rgb(235,189,166) 0%,rgb(71,49,32) 100%);
}
#selecteImage {
display: none;
}
.input {
width: 200px;
}
.output {
margin-top: 20px;
width: 80%;
text-align: center;
font-weight: bold;
}
.preview img {
max-width: 100%;
}
button {
padding: 0 10px;
margin-left: 6px;
}
.details {
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
}
.details button {
background-color: black;
color: white;
width: 160px;
height: 32px;
border-radius: 8px 8px 0 0;
border: none;
font-size: 12px;
font-weight: bold;
cursor: pointer;
}
.details .fold {
width: 200px;
height: 30px;
background-color: white;
border-top-left-radius: 8px;
border-top-right-radius: 8px;
}
.details .expand {
width: 200px;
height: 88vh;
background-color: white;
border-top-left-radius: 8px;
border-top-right-radius: 8px;
}
.expand img {
width: 60%;
margin-top: 20px;
border-radius: 6px;
}
.expand .explaination {
color: black;
font-weight: normal;
}
.expand .explaination p {
margin: 0 10px 10px 10px;
}
.expand .reply {
color: black;
font-weight: normal;
margin-top: 20px;
}
.expand .reply p {
padding: 4px 10px;
margin: 0 10px 10px 10px;
border-radius: 6px;
border: solid 1px grey;
}
样式解析:
-
container:设置全屏显示,背景为线性渐变(暖色调),字体大小0.85rem,flex布局垂直排列,居中对齐;
-
#selecteImage:隐藏文件输入框,通过label标签触发;
-
output:设置margin-top为20px,宽度80%,居中对齐,字体加粗,突出例句显示;
-
details:固定在页面底部,居中显示(left:50% + transform: translateX(-50%));
-
按钮样式:黑色背景、白色字体,圆角设计,鼠标悬停显示指针,提升交互体验;
-
fold和expand:均为白色背景、圆角设计,fold高度30px(收起状态),expand高度88vh(展开状态),显示图片、解释和回复;
-
解释和回复样式:解释内容为黑色、常规字体,回复内容带有灰色边框和圆角,便于区分。
4.3 PictureCard组件代码解析
4.3.1 脚本部分(script setup lang="ts")
脚本部分主要实现图片上传、拍照、图片预览、组件通信等功能,具体解析如下:
import { ref } from 'vue';
import defaultImg from '../assets/camera.png';
import voiceIcon from '../assets/voice.png';
const imgPreview = ref(defaultImg); // 图片预览地址,默认显示相机图标
// 定义组件props,接收主页面传递的单词和音频信息
const props = defineProps({
word: {
type: String,
default: ''
},
audio: {
type: String,
default: ''
},
})
// 定义组件事件,向主页面传递图片数据
const emit = defineEmits(['updateImage'])
// 图片上传/拍照处理函数
const updateImageData = async (e:Event): Promise<any> => {
const file = (e.target as HTMLInputElement).files?.[0]; // 获取上传的文件对象
if (!file) return; // 若没有文件,直接返回
return new Promise((resolve, reject) => {
const reader = new FileReader(); // 创建FileReader实例
reader.readAsDataURL(file); // 读取文件,转换为base64编码
reader.onload = () => {
const data = reader.result as string; // 获取base64编码数据
imgPreview.value = data; // 更新图片预览地址
emit('updateImage', data); // 向主页面传递图片数据
resolve(data);
}
reader.onerror = (error) => {
reject(error); // 处理读取错误
}
})
}
// 音频播放函数
const playAudio = () => {
const audio = new Audio(props.audio); // 创建Audio对象,传入音频URL
audio.play(); // 播放音频
}
脚本解析:
-
引入依赖:引入ref函数、默认相机图标(defaultImg)、音频图标(voiceIcon);
-
响应式数据:imgPreview用于存储图片预览地址,默认显示相机图标;
-
defineProps:定义组件的props,接收主页面传递的word(单词)和audio(音频URL),设置默认值为空字符串;
-
defineEmits:定义组件的事件updateImage,用于向主页面传递图片的base64编码;
-
updateImageData函数:处理图片上传/拍照事件,获取文件对象,使用FileReader将文件转换为base64编码,更新图片预览地址,通过emit向主页面传递数据,返回Promise对象,便于处理异步操作;
-
playAudio函数:创建Audio对象,传入props.audio(音频URL),调用play()方法播放音频。
4.3.2 模板部分(template)
<!-- 文件输入框,隐藏显示,用于接收图片上传/拍照数据 -->
<input type="file" id="selecteImage" class="input"
accept="image*" @ />
<!-- 标签,触发文件输入框,显示图片预览 -->
<!-- 显示单词 -->
{{ props.word }}
<!-- 音频播放按钮,只有音频存在时显示 -->
<div class="playAudio" v-if="audio" @
模板解析:
-
card:组件容器,采用卡片式设计,设置圆角、阴影、背景色,提升界面层次感;
-
input[type="file"]:文件输入框,隐藏显示(#selecteImage设置display: none),accept="image/*"表示只接收图片文件,@change事件绑定updateImageData函数,当用户上传图片或拍照后,触发函数处理;
-
label:与文件输入框关联(for="selecteImage"),点击标签即可触发文件输入框的点击事件,显示图片预览(img标签绑定imgPreview);
-
word:显示主页面传递的单词,设置白色字体,突出显示;
-
playAudio:音频播放按钮,只有audio存在时显示(v-if="audio"),点击触发playAudio函数,显示音频图标。
4.3.3 样式部分(style scoped)
#selecteImage {
display: none;
}
.card {
border-radius: 8px;
padding: 20px;
margin-top: 40px;
height: 280px;
box-shadow: rgb(63,38,21) 0 3px 0px 0;
background-color: rgb(105,78,62);
box-sizing: border-box;
}
.upload {
width: 160px;
height: 160px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.upload img {
width: 100%;
height: 100%;
object-fit: contain;
}
.word {
margin-top: 20px;
font-size: 16px;
color: rgb(255,255,255);
}
.playAudio {
margin-top: 16px;
}
.playAudio img {
cursor: pointer;
}
样式解析:
-
#selecteImage:隐藏文件输入框;
-
card:设置圆角8px,内边距20px,margin-top40px,高度280px,阴影效果(rgb(63,38,21)),背景色rgb(105,78,62)(深棕色),box-sizing: border-box确保内边距不影响整体尺寸;
-
upload:设置宽度和高度160px,flex布局居中对齐,确保图片预览居中显示;
-
upload img:宽度和高度100%,object-fit: contain确保图片按比例显示,不拉伸;
-
word:margin-top20px,字体大小16px,白色字体,突出显示单词;
-
playAudio:margin-top16px,音频图标鼠标悬停显示指针,提升交互体验。
4.4 核心技术点补充解析
4.4.1 Vue3 Composition API 核心用法
项目中大量使用了Vue3的Composition API,核心用法包括ref、defineProps、defineEmits等,补充解析如下:
-
ref:用于创建基本类型的响应式数据,如string、number、boolean等,返回一个带.value属性的响应式对象。例如,const word = ref('请上传文件'),修改时需要使用word.value = '分析中...',界面会自动更新;
-
defineProps:用于定义组件的属性,接收一个对象,指定属性的类型、默认值等,组件外部可以通过props向组件传递数据。例如,PictureCard组件通过defineProps接收主页面传递的word和audio;
-
defineEmits:用于定义组件的事件,组件内部可以通过emit向外部传递数据。例如,PictureCard组件通过emit('updateImage', data)向主页面传递图片的base64编码;
-
脚本设置语法(script setup):Vue3的语法糖,无需导出组件,直接编写脚本代码,简化了组件的编写流程,提升开发效率。
4.4.2 Prompt设计技巧
Prompt设计是AIGC产品的核心,项目中的Prompt设计具有很强的参考价值,具体技巧如下:
-
明确指令:清晰告知大模型需要完成的任务,例如“分析图片内容,找出最能描述图片的一个英文单词,尽量选择更简单的A1~A2的词汇”;
-
指定输出格式:明确要求大模型返回JSON格式的数据,并指定JSON中的字段,例如image_description、representative_word、example_sentence等,确保数据格式统一,便于前端解析;
-
控制输出内容:对输出的内容进行约束,例如“解释的最后给一个日常生活有关的问句”“回复根据explaination给出”,确保输出内容贴合项目需求;
-
适配用户水平:明确要求单词级别为A1~A2,确保输出的单词和例句简单易懂,贴合目标用户的英语水平。
4.4.3 环境变量的使用
项目中使用了环境变量(import.meta.env)存储大模型接口地址和API Key,避免将敏感信息硬编码到代码中,提升代码的安全性和可维护性。具体用法如下:
-
在项目根目录创建.env文件,定义环境变量:VITE_KIMI_API_ENDPOINT=接口地址,VITE_KIMI_API_KEY=API Key;
-
在代码中通过import.meta.env.VITE_XXX获取环境变量,例如const endpoint = import.meta.env.VITE_KIMI_API_ENDPOINT + '/chat/completions';
-
注意:环境变量名必须以VITE_开头,否则无法在前端代码中访问。
五、总结
本次“拍照记单词”项目学习,围绕AI多模态技术与前后端开发的结合展开,全面覆盖了产品设计、技术实现、问题解决等多个核心环节,不仅掌握了具体的技术栈用法,更理解了AI时代轻量化产品的开发逻辑与核心思路,收获颇丰。
项目层面,“拍照记单词”作为一款贴合真实场景的英语学习工具,精准抓住了传统单词学习产品场景化不足、操作繁琐的痛点,以“拍照即记词”为核心,结合多模态大模型与TTS技术,实现了场景化、便捷化、个性化的学习体验,同时兼顾无障碍设计,体现了产品的包容性与人文关怀。通过对百词斩、扇贝、多邻国等竞品的分析,明确了项目的差异化优势,也为后续产品优化提供了清晰方向。
技术层面,本次学习系统掌握了Vue3+TS+Composition API的前端开发方法,理解了组件化思想在项目中的实际应用,能够熟练运用FileReader实现图片base64编码、调用多模态大模型接口、通过TTS技术实现音频播放等核心功能;同时了解了NestJS后端框架的选型逻辑与核心作用,掌握了前后端分离架构的基本设计思路。在技术实践中,针对图片处理、接口调用、音频播放、无障碍适配等难点,通过合理的技术方案解决了实际问题,提升了问题排查与解决能力,也深刻认识到细节处理(如环境变量使用、Prompt设计)在项目开发中的重要性。
整体而言,通过本次项目学习,不仅夯实了前端开发、AI接口调用等技术基础,更建立了“产品需求→技术实现→问题优化”的完整思维模式,理解了AI技术与用户需求结合的核心逻辑。后续将把本次学习收获运用到同类项目开发中,重点关注产品细节优化与用户体验提升,同时持续深耕多模态技术、前端框架等相关知识,不断提升自身的开发能力与产品思维。