面试题3道
This commit is contained in:
182
README.md
182
README.md
@@ -1,75 +1,151 @@
|
||||
# Nuxt Minimal Starter
|
||||
# AI Chat Interview - Vue 3 + Nuxt 3 面试题
|
||||
|
||||
Look at the [Nuxt documentation](https://nuxt.com/docs/getting-started/introduction) to learn more.
|
||||
## 项目背景
|
||||
|
||||
## Setup
|
||||
这是一个简单的 AI 聊天应用。前任开发者为了赶进度,将所有逻辑都写在了一个文件中,虽然能运行,但存在交互问题和代码维护性问题。
|
||||
|
||||
Make sure to install dependencies:
|
||||
你的任务是完成功能实现、修复 Bug,并对代码进行重构。
|
||||
|
||||
**建议时间**: 60 分钟
|
||||
|
||||
---
|
||||
|
||||
## 启动项目
|
||||
|
||||
### 安装依赖
|
||||
|
||||
```bash
|
||||
# npm
|
||||
npm install
|
||||
|
||||
# pnpm
|
||||
pnpm install
|
||||
|
||||
# yarn
|
||||
yarn install
|
||||
|
||||
# bun
|
||||
bun install
|
||||
```
|
||||
|
||||
## Development Server
|
||||
|
||||
Start the development server on `http://localhost:3000`:
|
||||
### 启动开发服务器
|
||||
|
||||
```bash
|
||||
# npm
|
||||
npm run dev
|
||||
|
||||
# pnpm
|
||||
pnpm dev
|
||||
|
||||
# yarn
|
||||
yarn dev
|
||||
|
||||
# bun
|
||||
bun run dev
|
||||
```
|
||||
|
||||
## Production
|
||||
访问: http://localhost:3000
|
||||
|
||||
Build the application for production:
|
||||
---
|
||||
|
||||
## 你的任务
|
||||
|
||||
请按顺序完成以下 3 个任务,每完成一个任务后提交一次 Git Commit。
|
||||
|
||||
### 任务 1: 实现 Loading 加载状态
|
||||
|
||||
**需求**:
|
||||
|
||||
- 点击 "发送" 按钮后,按钮文字应该变为 "思考中..." 并且处于禁用状态
|
||||
- AI 回复完成后,按钮恢复为 "发送" 并可点击
|
||||
- 加载中时,用户不能重复发送消息
|
||||
|
||||
**要求**:
|
||||
|
||||
1. 创建一个响应式的 loading 状态变量
|
||||
2. 在发送消息时设置 loading 为 true
|
||||
3. 在 AI 回复完成后设置 loading 为 false
|
||||
4. 按钮的文字和禁用状态根据 loading 变量动态变化
|
||||
|
||||
**提示**:
|
||||
|
||||
- 查看 `app.vue` 中的 TODO 注释
|
||||
- 使用 Vue 3 的 `ref()` 创建响应式变量
|
||||
- 模板中使用三元表达式显示不同文字
|
||||
|
||||
**完成后**:
|
||||
|
||||
```bash
|
||||
# npm
|
||||
npm run build
|
||||
|
||||
# pnpm
|
||||
pnpm build
|
||||
|
||||
# yarn
|
||||
yarn build
|
||||
|
||||
# bun
|
||||
bun run build
|
||||
git 提交代码
|
||||
```
|
||||
|
||||
Locally preview production build:
|
||||
---
|
||||
|
||||
### 任务 2: 修复聊天框滚动异常 Bug
|
||||
|
||||
**现象**:
|
||||
|
||||
- 当发送消息或收到 AI 回复时,聊天框应该自动滚动到最底部
|
||||
- 目前的 `scrollToBottom` 函数时灵时不灵,经常卡在倒数第二条消息
|
||||
|
||||
**要求**:
|
||||
|
||||
- 修复滚动逻辑,确保新消息出现后视图都能自动滚到底部
|
||||
|
||||
**提示**:
|
||||
|
||||
- Vue 的 DOM 更新不是同步的,考虑使用 `nextTick`
|
||||
- 可以使用 `watch` 监听 `messages` 的变化
|
||||
|
||||
**完成后**:
|
||||
|
||||
```bash
|
||||
# npm
|
||||
npm run preview
|
||||
|
||||
# pnpm
|
||||
pnpm preview
|
||||
|
||||
# yarn
|
||||
yarn preview
|
||||
|
||||
# bun
|
||||
bun run preview
|
||||
git 提交代码
|
||||
```
|
||||
|
||||
Check out the [deployment documentation](https://nuxt.com/docs/getting-started/deployment) for more information.
|
||||
---
|
||||
|
||||
### 任务 3: 组件化重构
|
||||
|
||||
**现象**:
|
||||
|
||||
- `app.vue` 文件中,消息列表的渲染逻辑和主界面耦合太紧,代码结构混乱
|
||||
|
||||
**要求**:
|
||||
|
||||
1. 将单条消息的渲染逻辑提取为独立组件: `components/ChatMessage.vue`
|
||||
2. 通过 `props` 传递消息数据
|
||||
3. 加分项: 为组件 Props 添加 TypeScript 类型定义
|
||||
|
||||
**提示**:
|
||||
|
||||
- 参考 `app.vue:89-101` 的消息渲染部分
|
||||
- 组件样式也需要一并迁移
|
||||
|
||||
**完成后**:
|
||||
|
||||
```bash
|
||||
git 提交代码
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 提交代码
|
||||
|
||||
完成所有任务后:
|
||||
|
||||
1. 创建一个新分支用于你的修改
|
||||
|
||||
```bash
|
||||
git 提交 PR
|
||||
```
|
||||
|
||||
2. 确保所有任务的 commit 都已提交
|
||||
|
||||
3. 推送分支到远程仓库
|
||||
|
||||
|
||||
4. 在 gitea 上创建 Pull Request (PR)
|
||||
- PR 标题: `完成 AI Chat 面试任务`
|
||||
- PR 描述中简要说明你完成的 3 个任务
|
||||
|
||||
---
|
||||
|
||||
## 注意事项
|
||||
|
||||
- 本项目不依赖真实后端,所有接口均为模拟 (Mock)
|
||||
- 不需要关注 UI 美观度,重点考察 Vue 3 响应式原理、DOM 更新机制和代码组织能力
|
||||
- 可以参考 [Nuxt 3 官方文档](https://nuxt.com/docs) 和 [Vue 3 官方文档](https://vuejs.org/)
|
||||
- 每个任务完成后必须提交一次 commit,commit message 需要清晰描述改动内容
|
||||
|
||||
---
|
||||
|
||||
## 验收标准
|
||||
|
||||
完成所有任务后,请确保:
|
||||
|
||||
1. 项目能正常运行 (`npm run dev`)
|
||||
2. 所有功能正常工作
|
||||
3. 代码结构清晰,易于维护
|
||||
4. Git 提交历史清晰,至少包含 3 个独立的 commit(每个任务一个)
|
||||
5. 成功创建 Pull Request
|
||||
|
||||
210
app.vue
210
app.vue
@@ -1,6 +1,210 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
// --- 类型定义 ---
|
||||
interface Message {
|
||||
id: number
|
||||
role: 'user' | 'ai'
|
||||
content: string
|
||||
timestamp: string
|
||||
}
|
||||
|
||||
// --- 状态管理 ---
|
||||
const messages = ref<Message[]>([
|
||||
{
|
||||
id: 1,
|
||||
role: 'ai',
|
||||
content: '你好!我是你的 AI 助手,有什么可以帮你的吗?',
|
||||
timestamp: new Date().toLocaleTimeString()
|
||||
}
|
||||
])
|
||||
const inputVal = ref('')
|
||||
const chatContainerRef = ref<HTMLElement | null>(null)
|
||||
|
||||
|
||||
|
||||
// --- 模拟 API 调用 ---
|
||||
const mockAiResponse = async () => {
|
||||
// 模拟网络延迟
|
||||
setTimeout(() => {
|
||||
messages.value.push({
|
||||
id: Date.now(),
|
||||
role: 'ai',
|
||||
content: '这是一个模拟的 AI 回复。它可以是很长很长很长很长很长很长很长很长很长很长很长很长的一段话,用来测试滚动条是否能正常工作。Vue 3 的性能非常棒!',
|
||||
timestamp: new Date().toLocaleTimeString()
|
||||
})
|
||||
|
||||
|
||||
// 尝试滚动 (task2)
|
||||
scrollToBottom()
|
||||
}, 1500)
|
||||
}
|
||||
|
||||
// --- 事件处理 ---
|
||||
const sendMessage = () => {
|
||||
if (!inputVal.value.trim()) return
|
||||
|
||||
|
||||
|
||||
// 添加用户消息
|
||||
messages.value.push({
|
||||
id: Date.now(),
|
||||
role: 'user',
|
||||
content: inputVal.value,
|
||||
timestamp: new Date().toLocaleTimeString()
|
||||
})
|
||||
|
||||
// 3. 清空输入
|
||||
inputVal.value = ''
|
||||
|
||||
// task2
|
||||
//
|
||||
scrollToBottom()
|
||||
|
||||
// 4. 请求 AI
|
||||
mockAiResponse()
|
||||
}
|
||||
|
||||
// 滚动到底部
|
||||
const scrollToBottom = () => {
|
||||
if (chatContainerRef.value) {
|
||||
// 滚动
|
||||
chatContainerRef.value.scrollTop = chatContainerRef.value.scrollHeight
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<NuxtRouteAnnouncer />
|
||||
<NuxtWelcome />
|
||||
<div class="chat-wrapper">
|
||||
<header class="header">
|
||||
<h1>🤖 AI Chat Lite</h1>
|
||||
</header>
|
||||
|
||||
<div class="chat-container" ref="chatContainerRef">
|
||||
<div
|
||||
v-for="msg in messages"
|
||||
:key="msg.id"
|
||||
class="message-row"
|
||||
:class="msg.role === 'user' ? 'row-user' : 'row-ai'"
|
||||
>
|
||||
<div class="avatar">
|
||||
{{ msg.role === 'ai' ? '🤖' : '👤' }}
|
||||
</div>
|
||||
<div class="bubble">
|
||||
<div class="text">{{ msg.content }}</div>
|
||||
<div class="time">{{ msg.timestamp }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="input-area">
|
||||
<input
|
||||
v-model="inputVal"
|
||||
@keyup.enter="sendMessage"
|
||||
type="text"
|
||||
placeholder="输入你的问题..."
|
||||
/>
|
||||
<button @click="sendMessage" >
|
||||
发送
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* 基础样式,无需修改 */
|
||||
.chat-wrapper {
|
||||
max-width: 600px;
|
||||
margin: 40px auto;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
font-family: sans-serif;
|
||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.header {
|
||||
background: #f3f4f6;
|
||||
padding: 16px;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
}
|
||||
.header h1 { margin: 0; font-size: 1.25rem; color: #1f2937; }
|
||||
|
||||
.chat-container {
|
||||
height: 400px;
|
||||
overflow-y: auto;
|
||||
padding: 20px;
|
||||
background: #fff;
|
||||
/* scroll-behavior: smooth; */
|
||||
/* 平滑滚动 */
|
||||
}
|
||||
|
||||
/* 消息行样式 */
|
||||
.message-row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 16px;
|
||||
gap: 10px;
|
||||
}
|
||||
.row-user { flex-direction: row-reverse; }
|
||||
|
||||
.avatar {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
background: #eee;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 20px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.bubble {
|
||||
max-width: 70%;
|
||||
padding: 10px 14px;
|
||||
border-radius: 10px;
|
||||
position: relative;
|
||||
}
|
||||
.row-ai .bubble { background: #f3f4f6; color: #1f2937; border-top-left-radius: 2px; }
|
||||
.row-user .bubble { background: #3b82f6; color: white; border-top-right-radius: 2px; }
|
||||
|
||||
.time {
|
||||
font-size: 10px;
|
||||
opacity: 0.7;
|
||||
margin-top: 4px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.input-area {
|
||||
padding: 16px;
|
||||
background: #f9fafb;
|
||||
border-top: 1px solid #e5e7eb;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
input {
|
||||
flex: 1;
|
||||
padding: 10px;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 6px;
|
||||
outline: none;
|
||||
}
|
||||
input:focus { border-color: #3b82f6; }
|
||||
|
||||
button {
|
||||
padding: 0 20px;
|
||||
background: #3b82f6;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
button:disabled {
|
||||
background: #9ca3af;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
// https://nuxt.com/docs/api/configuration/nuxt-config
|
||||
export default defineNuxtConfig({
|
||||
compatibilityDate: '2025-07-15',
|
||||
devtools: { enabled: true }
|
||||
compatibilityDate: '2025-01-22',
|
||||
devtools: { enabled: true },
|
||||
app: {
|
||||
head: {
|
||||
title: 'AI Chat Interview',
|
||||
meta: [
|
||||
{ charset: 'utf-8' },
|
||||
{ name: 'viewport', content: 'width=device-width, initial-scale=1' }
|
||||
]
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "my-app",
|
||||
"name": "ai-chat-interview",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
Reference in New Issue
Block a user