Compare commits

...

No commits in common. "master" and "main" have entirely different histories.
master ... main

23 changed files with 907 additions and 2471 deletions

4
.gitignore vendored
View File

@ -1,4 +0,0 @@
.env
.venv/
__pycache__/
.DS_Store

View File

@ -1 +0,0 @@
3.13

View File

@ -1,47 +0,0 @@
# 英语学习日常场景对话练习系统
## 2.1 团队成员与贡献 (必填)
| 姓名 | 学号 | 主要贡献 (具体分工) |
|------|------|-------------------|
| 张扬 | 2411020205 | 核心逻辑开发、Prompt 编写 |
| 刘诣卓 | 2411020209 | 创意提供、网页测试 |
| 顾浩晨 | 2411020108 | 文档撰写、测试与 Bug 修复 |
## 2.2 项目简介 & 运行指南
### 简介
本项目是一个基于网页的英语学习系统,通过模拟真实日常场景对话,提供交互式的选择题练习,帮助学习者提升英语口语表达能力和场景应用能力。
### 如何运行
1. 安装依赖:本项目使用 Python 的 http.server 模块,无需额外安装依赖
2. 进入项目目录:`cd english-learning-uv`
3. 启动服务器:`python -m http.server 8000`
4. 访问网页:在浏览器中输入 `http://localhost:8000`
## 2.3 开发心得
### 选题思考
在英语学习过程中,很多学习者面临着"学了不会用"的痛点。传统的英语学习往往注重词汇和语法的记忆,而缺乏真实场景的应用练习。我们的项目旨在解决这一问题,通过模拟餐厅点餐、超市购物、问路等真实日常场景,让学习者在交互式对话中练习英语,提高实际应用能力。
### AI 协作体验
作为第一次尝试使用 AI 辅助开发的团队,我们的体验可以说是充满惊喜和挑战。
最初使用 AI 写代码时感觉就像是拥有了一个经验丰富的编程助手。只需要描述我们的需求AI 就能快速生成基础代码框架大大提高了开发效率。特别是在处理一些重复性工作时比如创建多个相似的场景数据AI 能够快速生成模板并根据需求进行调整。
让我们印象最深刻的"牛逼"Prompt 是:"帮我创建一个英语学习网页,包含日常场景对话和选择题练习功能,使用 uv 进行包管理"。AI 不仅生成了完整的 HTML、CSS 和 JavaScript 代码,还考虑了用户体验和代码结构的合理性,为我们的项目提供了良好的起点。
当然,也有一些让我们感到挫败的时刻。比如在处理图片加载逻辑时,我们的 Prompt 描述不够精确,导致 AI 生成的代码虽然能运行,但存在性能问题。还有一次,我们要求 AI 实现选项随机排序功能,结果生成的代码打乱了选项但没有正确跟踪正确答案的位置,导致答题逻辑出错。这些经历让我们认识到,与 AI 协作需要清晰、精确的指令,并且需要对生成的代码进行仔细的检查和测试。
### 自我反思
在使用 AI 辅助开发的过程中我们深刻地思考了一个问题AI 时代,程序员的核心竞争力到底是什么?
通过这次项目我们认识到AI 确实能够快速生成基础代码,处理一些常规的编程任务。但它缺乏对项目整体架构的理解,无法把握业务逻辑的本质,也不能创造性地解决复杂问题。作为程序员,我们的核心竞争力应该在于:
1. **问题分析能力**:能够深入理解用户需求,将实际问题转化为清晰的技术方案。
2. **系统设计能力**:能够设计合理的系统架构,考虑代码的可维护性、可扩展性和性能。
3. **创造性思维**:能够创造性地解决复杂问题,提出创新的解决方案。
4. **代码质量意识**:能够编写高质量、可读性强、易于维护的代码。
5. **持续学习能力**:能够不断学习新技术,适应快速变化的技术环境。
AI 是一个强大的工具,但它不能替代程序员的核心能力。相反,它让我们能够从繁琐的重复性工作中解放出来,将更多的精力放在创造性的思考和复杂问题的解决上。在未来的开发工作中,我们应该学会与 AI 协作,充分发挥它的优势,同时不断提升自己的核心竞争力。
通过这次项目,我们不仅完成了一个功能完整的英语学习系统,更重要的是获得了宝贵的 AI 协作经验和对程序员职业发展的深刻思考。这些收获将对我们未来的学习和工作产生积极的影响。

690
data.js
View File

@ -1,690 +0,0 @@
// 日常英语学习数据
const englishData = {
scenes: [
{
id: 0,
title: "餐厅点餐",
description: "在餐厅点餐的日常对话练习",
image: "", // 更简单的图片导入:
// 方式1将图片放在 images 目录下命名为场景ID+扩展名(推荐)
// 例如0.jpg, 1.png, 2.jpeg 等
// 方式2将图片放在 images 目录下,命名为场景标题拼音+扩展名
// 例如:餐厅点餐 -> cantingdiancan.jpg
// 支持的格式:.jpg, .jpeg, .png, .gif, .webp, .bmp, .svg
// 不需要手动修改此字段,系统会自动查找所有可能的图片
conversation: [
{
speaker: "服务员",
text: "Good afternoon! Welcome to our restaurant. May I take your order?",
type: "other"
},
{
speaker: "我",
text: "I'd like to see the menu first, please.",
type: "user"
},
{
speaker: "服务员",
text: "Sure, here you are. Take your time.",
type: "other"
},
{
speaker: "服务员",
text: "Are you ready to order now?",
type: "other"
},
{
speaker: "我",
question: "请选择合适的回答:",
options: [
"Yes, I'll have the steak, please.",
"No, I don't like this restaurant.",
"I want to go home.",
"The menu is too expensive."
],
correctAnswer: 0,
type: "user_question"
},
{
speaker: "服务员",
text: "How would you like your steak cooked?",
type: "other"
},
{
speaker: "我",
question: "请选择合适的回答:",
options: [
"Medium rare, please.",
"With fries and salad.",
"I want a drink.",
"That's all."
],
correctAnswer: 0,
type: "user_question"
},
{
speaker: "服务员",
text: "Would you like anything to drink?",
type: "other"
},
{
speaker: "我",
question: "请选择合适的回答:",
options: [
"A glass of water, please.",
"No, I'm not hungry.",
"I'll pay now.",
"Thank you."
],
correctAnswer: 0,
type: "user_question"
},
{
speaker: "服务员",
text: "Okay, your order will be ready in 15 minutes.",
type: "other"
},
{
speaker: "我",
question: "请选择合适的回答:",
options: [
"Thank you very much.",
"Hurry up!",
"I want it now.",
"That's too long."
],
correctAnswer: 0,
type: "user_question"
}
]
},
{
id: 1,
title: "超市购物",
description: "在超市购物的日常对话练习",
image: "", // 更简单的图片导入:
// 方式1将图片放在 images 目录下命名为场景ID+扩展名(推荐)
// 例如0.jpg, 1.png, 2.jpeg 等
// 方式2将图片放在 images 目录下,命名为场景标题拼音+扩展名
// 例如:餐厅点餐 -> cantingdiancan.jpg
// 支持的格式:.jpg, .jpeg, .png, .gif, .webp, .bmp, .svg
// 不需要手动修改此字段,系统会自动查找所有可能的图片
conversation: [
{
speaker: "顾客",
text: "Excuse me, where can I find the milk?",
type: "user"
},
{
speaker: "超市员工",
text: "It's in aisle 5, next to the bread.",
type: "other"
},
{
speaker: "顾客",
question: "请选择合适的回答:",
options: [
"Thank you very much.",
"I don't like milk.",
"Where is aisle 5?",
"This store is too big."
],
correctAnswer: 0,
type: "user_question"
},
{
speaker: "超市员工",
text: "You're welcome. Let me know if you need anything else.",
type: "other"
},
{
speaker: "顾客",
text: "Actually, do you have any organic vegetables?",
type: "user"
},
{
speaker: "超市员工",
text: "Yes, they're in the fresh produce section at the back of the store.",
type: "other"
},
{
speaker: "顾客",
question: "请选择合适的回答:",
options: [
"Great, thank you.",
"I don't want organic.",
"That's too far.",
"Why are they there?"
],
correctAnswer: 0,
type: "user_question"
}
]
},
{
id: 2,
title: "问路",
description: "向陌生人问路的日常对话练习",
image: "", // 更简单的图片导入:
// 方式1将图片放在 images 目录下命名为场景ID+扩展名(推荐)
// 例如0.jpg, 1.png, 2.jpeg 等
// 方式2将图片放在 images 目录下,命名为场景标题拼音+扩展名
// 例如:餐厅点餐 -> cantingdiancan.jpg
// 支持的格式:.jpg, .jpeg, .png, .gif, .webp, .bmp, .svg
// 不需要手动修改此字段,系统会自动查找所有可能的图片
conversation: [
{
speaker: "我",
text: "Excuse me, could you tell me how to get to the nearest subway station?",
type: "user"
},
{
speaker: "路人",
text: "Sure! Go straight for two blocks, then turn left at the traffic light. You'll see it on your right.",
type: "other"
},
{
speaker: "我",
question: "请选择合适的回答:",
options: [
"Is it far from here?",
"I don't want to go there.",
"That's too complicated.",
"I'll take a taxi instead."
],
correctAnswer: 0,
type: "user_question"
},
{
speaker: "路人",
text: "No, it's only about a 5-minute walk.",
type: "other"
},
{
speaker: "我",
question: "请选择合适的回答:",
options: [
"Perfect! Thank you so much.",
"5 minutes is too long.",
"I can't walk that far.",
"Do you have a map?"
],
correctAnswer: 0,
type: "user_question"
},
{
speaker: "路人",
text: "You're welcome! Have a nice day.",
type: "other"
},
{
speaker: "我",
question: "请选择合适的回答:",
options: [
"You too!",
"Bye, I'm in a hurry.",
"I hope I don't get lost.",
"This city is confusing."
],
correctAnswer: 0,
type: "user_question"
}
]
},
{
id: 3,
title: "医院就诊",
description: "在医院看病的日常对话练习",
image: "", // 更简单的图片导入:
// 方式1将图片放在 images 目录下命名为场景ID+扩展名(推荐)
// 例如0.jpg, 1.png, 2.jpeg 等
// 方式2将图片放在 images 目录下,命名为场景标题拼音+扩展名
// 例如:餐厅点餐 -> cantingdiancan.jpg
// 支持的格式:.jpg, .jpeg, .png, .gif, .webp, .bmp, .svg
// 不需要手动修改此字段,系统会自动查找所有可能的图片
conversation: [
{
speaker: "护士",
text: "Good morning, how can I help you today?",
type: "other"
},
{
speaker: "我",
text: "I'm not feeling well. I have a fever and a headache.",
type: "user"
},
{
speaker: "护士",
text: "I'm sorry to hear that. Please fill out this form and take a seat. The doctor will see you shortly.",
type: "other"
},
{
speaker: "医生",
text: "Hello, I'm Dr. Smith. What seems to be the problem?",
type: "other"
},
{
speaker: "我",
question: "请选择合适的回答:",
options: [
"I've had a fever and headache for two days.",
"I don't like doctors.",
"How much does this cost?",
"Can I leave now?"
],
correctAnswer: 0,
type: "user_question"
},
{
speaker: "医生",
text: "Let me check your temperature and blood pressure.",
type: "other"
},
{
speaker: "医生",
text: "You have a slight fever. I'll prescribe some medicine for you.",
type: "other"
},
{
speaker: "我",
question: "请选择合适的回答:",
options: [
"How often should I take it?",
"I don't want any medicine.",
"Is it expensive?",
"When will I get better?"
],
correctAnswer: 0,
type: "user_question"
}
]
},
{
id: 4,
title: "银行办理业务",
description: "在银行办理业务的日常对话练习",
image: "", // 更简单的图片导入:
// 方式1将图片放在 images 目录下命名为场景ID+扩展名(推荐)
// 例如0.jpg, 1.png, 2.jpeg 等
// 方式2将图片放在 images 目录下,命名为场景标题拼音+扩展名
// 例如:餐厅点餐 -> cantingdiancan.jpg
// 支持的格式:.jpg, .jpeg, .png, .gif, .webp, .bmp, .svg
// 不需要手动修改此字段,系统会自动查找所有可能的图片
conversation: [
{
speaker: "银行职员",
text: "Good morning! How can I assist you today?",
type: "other"
},
{
speaker: "我",
question: "请选择合适的回答:",
options: [
"I'd like to open a savings account.",
"I want to rob the bank.",
"Your bank is too slow.",
"I need a loan immediately."
],
correctAnswer: 0,
type: "user_question"
},
{
speaker: "银行职员",
text: "Certainly! Could you please provide your ID and proof of address?",
type: "other"
},
{
speaker: "我",
question: "请选择合适的回答:",
options: [
"Here you are.",
"I don't have any ID.",
"Why do you need that?",
"This is too troublesome."
],
correctAnswer: 0,
type: "user_question"
},
{
speaker: "银行职员",
text: "Thank you. Please fill out this form and sign here.",
type: "other"
},
{
speaker: "我",
question: "请选择合适的回答:",
options: [
"Okay, thank you.",
"I can't read this.",
"This is taking too long.",
"I changed my mind."
],
correctAnswer: 0,
type: "user_question"
}
]
},
{
id: 5,
title: "酒店入住",
description: "酒店入住登记的日常对话练习",
image: "", // 更简单的图片导入:
// 方式1将图片放在 images 目录下命名为场景ID+扩展名(推荐)
// 例如0.jpg, 1.png, 2.jpeg 等
// 方式2将图片放在 images 目录下,命名为场景标题拼音+扩展名
// 例如:餐厅点餐 -> cantingdiancan.jpg
// 支持的格式:.jpg, .jpeg, .png, .gif, .webp, .bmp, .svg
// 不需要手动修改此字段,系统会自动查找所有可能的图片
conversation: [
{
speaker: "前台接待",
text: "Good evening! Welcome to our hotel. Do you have a reservation?",
type: "other"
},
{
speaker: "我",
question: "请选择合适的回答:",
options: [
"Yes, I have a reservation under the name Zhang.",
"No, I want the best room.",
"How much is a room?",
"I'm here to visit a friend."
],
correctAnswer: 0,
type: "user_question"
},
{
speaker: "前台接待",
text: "Let me check... Yes, Mr. Zhang. You booked a single room for two nights.",
type: "other"
},
{
speaker: "前台接待",
text: "Could you please show me your ID and credit card?",
type: "other"
},
{
speaker: "我",
question: "请选择合适的回答:",
options: [
"Here they are.",
"I don't have a credit card.",
"Why do you need that?",
"Can I pay cash?"
],
correctAnswer: 0,
type: "user_question"
},
{
speaker: "前台接待",
text: "Thank you. Here's your room key. You're in room 302 on the third floor.",
type: "other"
},
{
speaker: "我",
question: "请选择合适的回答:",
options: [
"Thank you! Where is the elevator?",
"The room is too small.",
"I want a better view.",
"Can I check out late?"
],
correctAnswer: 0,
type: "user_question"
}
]
},
{
id: 6,
title: "电话预约",
description: "用电话预约的日常对话练习",
image: "", // 更简单的图片导入:
// 方式1将图片放在 images 目录下命名为场景ID+扩展名(推荐)
// 例如0.jpg, 1.png, 2.jpeg 等
// 方式2将图片放在 images 目录下,命名为场景标题拼音+扩展名
// 例如:餐厅点餐 -> cantingdiancan.jpg
// 支持的格式:.jpg, .jpeg, .png, .gif, .webp, .bmp, .svg
// 不需要手动修改此字段,系统会自动查找所有可能的图片
conversation: [
{
speaker: "接听者",
text: "Hello, this is the dental clinic. How can I help you?",
type: "other"
},
{
speaker: "我",
question: "请选择合适的回答:",
options: [
"I'd like to make an appointment for a check-up.",
"I have a toothache.",
"Can I speak to Dr. Wang?",
"What are your opening hours?"
],
correctAnswer: 0,
type: "user_question"
},
{
speaker: "接听者",
text: "Certainly! When would you like to come in?",
type: "other"
},
{
speaker: "我",
question: "请选择合适的回答:",
options: [
"Is tomorrow morning available?",
"I want to come now.",
"How much does it cost?",
"I'm scared of dentists."
],
correctAnswer: 0,
type: "user_question"
},
{
speaker: "接听者",
text: "Yes, we have openings at 9:00 and 10:30. Which one works better for you?",
type: "other"
},
{
speaker: "我",
question: "请选择合适的回答:",
options: [
"9:00 would be perfect.",
"Both times are bad.",
"Can I come at noon?",
"I'll call back later."
],
correctAnswer: 0,
type: "user_question"
}
]
},
{
id: 7,
title: "机场登机",
description: "机场办理登机手续的日常对话练习",
image: "", // 更简单的图片导入:
// 方式1将图片放在 images 目录下命名为场景ID+扩展名(推荐)
// 例如0.jpg, 1.png, 2.jpeg 等
// 方式2将图片放在 images 目录下,命名为场景标题拼音+扩展名
// 例如:餐厅点餐 -> cantingdiancan.jpg
// 支持的格式:.jpg, .jpeg, .png, .gif, .webp, .bmp, .svg
// 不需要手动修改此字段,系统会自动查找所有可能的图片
conversation: [
{
speaker: "工作人员",
text: "Good morning! May I see your passport and ticket, please?",
type: "other"
},
{
speaker: "我",
question: "请选择合适的回答:",
options: [
"Here they are.",
"I lost my ticket.",
"When is boarding time?",
"Where is my gate?"
],
correctAnswer: 0,
type: "user_question"
},
{
speaker: "工作人员",
text: "Thank you. Would you like a window seat or an aisle seat?",
type: "other"
},
{
speaker: "我",
question: "请选择合适的回答:",
options: [
"A window seat, please.",
"I want the best seat.",
"How much extra?",
"I don't care."
],
correctAnswer: 0,
type: "user_question"
},
{
speaker: "工作人员",
text: "Here's your boarding pass. Your gate is B12, and boarding starts at 10:30.",
type: "other"
},
{
speaker: "我",
question: "请选择合适的回答:",
options: [
"Thank you very much.",
"Where is gate B12?",
"Is the flight on time?",
"Can I bring food on board?"
],
correctAnswer: 0,
type: "user_question"
}
]
},
{
id: 8,
title: "公交车让座",
description: "在公交车上让座的日常对话练习",
image: "", // 简单图片导入:将图片放在 images 目录下,可使用以下命名规则之一
// 1. 按场景ID命名8.jpg, 8.png
// 2. 按场景标题命名:公交车让座.jpg, bus.jpg
// 不需要手动修改此字段,系统会自动查找
conversation: [
{
speaker: "老人",
text: "Excuse me, is this seat taken?",
type: "other"
},
{
speaker: "我",
question: "请选择合适的回答:",
options: [
"No, please sit here.",
"Yes, it's taken.",
"I'm tired too.",
"Find another seat."
],
correctAnswer: 0,
type: "user_question"
},
{
speaker: "老人",
text: "Thank you so much, young man/lady.",
type: "other"
},
{
speaker: "我",
question: "请选择合适的回答:",
options: [
"You're welcome. It's my pleasure.",
"It's okay.",
"Don't mention it.",
"Sit down quickly."
],
correctAnswer: 0,
type: "user_question"
},
{
speaker: "老人",
text: "You're such a kind person.",
type: "other"
},
{
speaker: "我",
question: "请选择合适的回答:",
options: [
"Thank you for your kind words.",
"It's nothing special.",
"I have to get off soon.",
"I do this every day."
],
correctAnswer: 0,
type: "user_question"
}
]
},
{
id: 9,
title: "图书馆借书",
description: "在图书馆借书的日常对话练习",
image: "", // 简单图片导入:将图片放在 images 目录下,可使用以下命名规则之一
// 1. 按场景ID命名9.jpg, 9.png
// 2. 按场景标题命名:图书馆借书.jpg, library.jpg
// 不需要手动修改此字段,系统会自动查找
conversation: [
{
speaker: "我",
text: "Excuse me, how can I borrow books from the library?",
type: "user"
},
{
speaker: "图书馆职员",
text: "You need a library card. Do you have one?",
type: "other"
},
{
speaker: "我",
question: "请选择合适的回答:",
options: [
"No, how can I get one?",
"I forgot it at home.",
"Can I use my ID instead?",
"Why do I need one?"
],
correctAnswer: 0,
type: "user_question"
},
{
speaker: "图书馆职员",
text: "You can apply for one at the information desk with your ID.",
type: "other"
},
{
speaker: "我",
text: "Great, thank you. And how many books can I borrow at a time?",
type: "user"
},
{
speaker: "图书馆职员",
text: "You can borrow up to 5 books for 3 weeks.",
type: "other"
},
{
speaker: "我",
question: "请选择合适的回答:",
options: [
"Perfect, that's enough for me.",
"That's too few.",
"3 weeks is too short.",
"Can I renew them online?"
],
correctAnswer: 0,
type: "user_question"
}
]
}
]
};

232
data.py
View File

@ -1,232 +0,0 @@
# 英语学习数据Python版本
english_data = {
"scenes": [
{
"id": 0,
"title": "餐厅点餐",
"description": "在餐厅点餐的日常对话练习",
"image": "",
"conversation": [
{
"speaker": "服务员",
"text": "Good afternoon! Welcome to our restaurant. May I take your order?",
"type": "other"
},
{
"speaker": "",
"text": "I'd like to see the menu first, please.",
"type": "user"
},
{
"speaker": "服务员",
"text": "Sure, here you are. Take your time.",
"type": "other"
},
{
"speaker": "服务员",
"text": "Are you ready to order now?",
"type": "other"
},
{
"speaker": "",
"question": "请选择合适的回答:",
"options": [
"Yes, I'll have the steak, please.",
"No, I don't like this restaurant.",
"I want to go home.",
"The menu is too expensive."
],
"correctAnswer": 0,
"type": "user_question"
},
{
"speaker": "服务员",
"text": "How would you like your steak cooked?",
"type": "other"
},
{
"speaker": "",
"question": "请选择合适的回答:",
"options": [
"Medium rare, please.",
"With fries and salad.",
"I want a drink.",
"That's all."
],
"correctAnswer": 0,
"type": "user_question"
},
{
"speaker": "服务员",
"text": "Would you like anything to drink?",
"type": "other"
},
{
"speaker": "",
"question": "请选择合适的回答:",
"options": [
"A glass of water, please.",
"No, I'm not hungry.",
"I'll pay now.",
"Thank you."
],
"correctAnswer": 0,
"type": "user_question"
},
{
"speaker": "服务员",
"text": "Okay, your order will be ready in 15 minutes.",
"type": "other"
},
{
"speaker": "",
"question": "请选择合适的回答:",
"options": [
"Thank you very much.",
"Hurry up!",
"I want it now.",
"That's too long."
],
"correctAnswer": 0,
"type": "user_question"
}
]
},
{
"id": 1,
"title": "超市购物",
"description": "在超市购物的日常对话练习",
"image": "",
"conversation": [
{
"speaker": "顾客",
"text": "Excuse me, where can I find the milk?",
"type": "user"
},
{
"speaker": "超市员工",
"text": "It's in aisle 5, next to the bread.",
"type": "other"
},
{
"speaker": "顾客",
"question": "请选择合适的回答:",
"options": [
"Thank you very much.",
"I don't like milk.",
"Where is aisle 5?",
"This store is too big."
],
"correctAnswer": 0,
"type": "user_question"
},
{
"speaker": "超市员工",
"text": "You're welcome. Let me know if you need anything else.",
"type": "other"
},
{
"speaker": "顾客",
"text": "Actually, do you have any organic vegetables?",
"type": "user"
},
{
"speaker": "超市员工",
"text": "Yes, they're in the fresh produce section at the back of the store.",
"type": "other"
},
{
"speaker": "顾客",
"question": "请选择合适的回答:",
"options": [
"Great, thank you.",
"I don't want organic.",
"That's too far.",
"Why are they there?"
],
"correctAnswer": 0,
"type": "user_question"
}
]
},
{
"id": 2,
"title": "问路",
"description": "向陌生人问路的日常对话练习",
"image": "",
"conversation": [
{
"speaker": "",
"text": "Excuse me, could you tell me how to get to the nearest subway station?",
"type": "user"
},
{
"speaker": "路人",
"text": "Sure! Go straight for two blocks, then turn left at the traffic light. You'll see it on your right.",
"type": "other"
},
{
"speaker": "",
"question": "请选择合适的回答:",
"options": [
"Is it far from here?",
"I don't want to go there.",
"That's too complicated.",
"I'll take a taxi instead."
],
"correctAnswer": 0,
"type": "user_question"
},
{
"speaker": "路人",
"text": "No, it's only about a 5-minute walk.",
"type": "other"
}
]
},
{
"id": 5,
"title": "机场值机",
"description": "在机场办理值机手续的日常对话练习",
"image": "",
"conversation": [
{
"speaker": "值机人员",
"text": "Good morning! How can I help you today?",
"type": "other"
},
{
"speaker": "",
"text": "I need to check in for my flight, please.",
"type": "user"
},
{
"speaker": "值机人员",
"text": "Certainly! What's your flight number and destination?",
"type": "other"
},
{
"speaker": "",
"question": "请输入您的航班号和目的地例如CA1234 to Beijing:",
"type": "user_question"
},
{
"speaker": "值机人员",
"text": "Thank you! Do you have any baggage to check?",
"type": "other"
},
{
"speaker": "",
"question": "请回答是否有行李要托运例如Yes, one suitcase please. 或 No, just carry-on.:",
"type": "user_question"
},
{
"speaker": "值机人员",
"text": "Perfect! Here's your boarding pass...",
"type": "other"
}
]
}
]
}

1
english-learning-uv Submodule

@ -0,0 +1 @@
Subproject commit 5db1a62e0f38a65f5c393f2102ea72ec7f5c0813

View File

@ -1,66 +0,0 @@
# HTML与Python集成指南
## 技术限制说明
HTML文件**不能直接引用或执行Python文件**,这是因为:
1. **运行环境不同**
- HTML/JavaScript 运行在浏览器端(客户端)
- Python 运行在服务器端或本地命令行环境
2. **执行方式不同**
- JavaScript 是解释型语言浏览器内置了JavaScript引擎
- Python 需要安装Python解释器才能执行
3. **语法和API完全不同**
- HTML只能识别并加载JavaScript文件通过`<script>`
- Python文件.py无法被浏览器直接解析和执行
## 可行的解决方案
### 方案1使用PyScript推荐
PyScript是一个允许在浏览器中直接运行Python代码的框架。它提供了以下功能
```html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Python in HTML</title>
<!-- 引入PyScript -->
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css">
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
</head>
<body>
<!-- 直接在HTML中编写Python代码 -->
<py-script>
print("Hello from Python in HTML!")
# 可以导入Python模块和使用Python语法
</py-script>
</body>
</html>
```
### 方案2创建Web应用高级
使用Python Web框架如Flask、Django创建完整的Web应用
1. **服务器端**使用Python处理业务逻辑和API调用
2. **客户端**使用HTML/JavaScript构建用户界面
3. **通信**通过AJAX/API实现前后端数据交互
### 方案3保留现有两个版本
- **网页版**继续使用index.html + data.js + script.js
- **命令行版**使用Python版main.py + data.py + script.py
## 建议
如果您希望:
- **快速使用**:保留两个版本,分别使用不同的方式运行
- **网页界面+Python功能**尝试PyScript方案
- **完整Web应用**学习并使用Python Web框架
请根据您的需求选择合适的方案。

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 MiB

View File

@ -3,77 +3,83 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>日常英语学习 - 场景对话练习</title>
<title>斗地主游戏</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="container">
<header class="header">
<h1>日常英语学习</h1>
<p class="subtitle">场景对话练习</p>
<div class="game-container">
<header class="game-header">
<h1>斗地主游戏</h1>
<div class="game-info">
<span id="game-status">等待开始游戏</span>
<span id="current-player">当前玩家:</span>
</div>
</header>
<div class="status-bar">
<div class="score">
<span id="score">得分: 0</span>
<main class="game-table">
<!-- 地主牌区域 -->
<div class="landlord-cards">
<div id="landlord-indicator">地主牌:</div>
<div id="landlord-cards-area"></div>
</div>
<div class="progress">
<span id="progress">进度: 1/10</span>
</div>
</div>
<div class="content">
<div class="scene-info">
<h2 id="scene-title">餐厅点餐</h2>
<p id="scene-description">在餐厅点餐的日常对话练习</p>
<div class="scene-image-container">
<img id="scene-image" src="" alt="场景插图" />
<!-- 电脑玩家1 -->
<div class="player player-1">
<div class="player-info">
<span class="player-name">电脑玩家1</span>
<span id="player-1-cards-count" class="cards-count">17</span>
<span id="player-1-role" class="player-role"></span>
</div>
<div id="player-1-cards" class="player-cards"></div>
<div id="player-1-last-play" class="last-play"></div>
</div>
<div class="conversation-area">
<div id="conversation" class="conversation">
<!-- 对话内容将通过JavaScript动态添加 -->
<!-- 电脑玩家2 -->
<div class="player player-2">
<div class="player-info">
<span class="player-name">电脑玩家2</span>
<span id="player-2-cards-count" class="cards-count">17</span>
<span id="player-2-role" class="player-role"></span>
</div>
<div id="player-2-cards" class="player-cards"></div>
<div id="player-2-last-play" class="last-play"></div>
</div>
<div id="question-area" class="question-area" style="display: none;">
<div class="question">
<h3 id="question-text">请选择合适的回答:</h3>
<div class="options">
<button class="option" data-index="0">选项1</button>
<button class="option" data-index="1">选项2</button>
<button class="option" data-index="2">选项3</button>
<button class="option" data-index="3">选项4</button>
</div>
<!-- 当前出牌区域 -->
<div class="current-play-area">
<div id="current-play-info">当前出牌:</div>
<div id="current-play-cards"></div>
</div>
<!-- 玩家自己(下) -->
<div class="player player-self">
<div class="player-info">
<span class="player-name"></span>
<span id="self-cards-count" class="cards-count">17</span>
<span id="self-role" class="player-role"></span>
</div>
<div id="self-cards" class="player-cards"></div>
<div id="self-last-play" class="last-play"></div>
</div>
</main>
<div id="feedback" class="feedback" style="display: none;">
<p id="feedback-text">反馈信息</p>
<div class="game-controls">
<button id="start-game">开始游戏</button>
<div class="landlord-buttons" style="display: none;">
<button id="call-landlord">叫地主</button>
<button id="no-call">不叫</button>
</div>
<div class="controls">
<button id="start-button" class="btn btn-primary">开始学习</button>
<button id="reset-button" class="btn btn-secondary">重新开始</button>
<button id="next-scene-button" class="btn btn-success" disabled>下一个场景</button>
<div class="play-buttons" style="display: none;">
<button id="play-cards">出牌</button>
<button id="pass">不出</button>
</div>
<!-- 实时对话功能区域 -->
<div class="real-time-chat" id="real-time-chat">
<h3>实时英语对话</h3>
<div id="chat-history" class="chat-history">
<!-- 实时对话历史 -->
</div>
<div class="chat-input">
<input type="text" id="chat-message" placeholder="输入英文句子进行实时对话..." />
<button id="send-button" class="btn btn-primary">发送</button>
</div>
<div class="game-stats">
<span>积分:</span>
<span id="score">0</span>
</div>
</div>
</div>
<script src="data.js"></script>
<script src="script.js"></script>
</body>
</html>

33
main.py
View File

@ -1,33 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
英语学习程序入口文件
"""
from script import EnglishLearningApp
def main():
"""主函数"""
print("英语学习程序")
print("=" * 50)
print("这是一个交互式的英语对话练习程序")
print("通过模拟日常场景,帮助您提高英语对话能力")
print("=" * 50)
try:
# 创建应用实例
app = EnglishLearningApp()
# 启动应用
app.start()
except KeyboardInterrupt:
print("\n\n程序已中断")
print("感谢使用英语学习程序!")
except Exception as e:
print(f"\n\n程序发生错误: {e}")
print("请检查配置或联系技术支持")
if __name__ == "__main__":
main()

View File

@ -1,7 +0,0 @@
[project]
name = "english-learning-uv"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.13"
dependencies = []

1323
script.js

File diff suppressed because it is too large Load Diff

329
script.py
View File

@ -1,329 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
英语学习程序主脚本Python版本
实现对话流程API集成和用户交互功能
"""
import json
import time
import requests
import os
from typing import Dict, List, Optional
# 导入数据
from data import english_data
class GameState:
"""游戏状态管理类"""
def __init__(self):
self.current_scene_index = 0
self.current_dialog_index = 0
self.score = 0
self.total_questions = 0
self.correct_answers = 0
def reset(self):
"""重置游戏状态"""
self.current_scene_index = 0
self.current_dialog_index = 0
self.score = 0
self.total_questions = 0
self.correct_answers = 0
class EnglishLearningApp:
"""英语学习应用主类"""
def __init__(self):
self.game_state = GameState()
self.current_scene = None
self.api_key = os.getenv("DEEPSEEK_API_KEY", "your-api-key-here")
self.api_url = "https://api.deepseek.com/v1/chat/completions"
def load_scene(self, scene_index: int) -> bool:
"""加载指定场景"""
if scene_index < 0 or scene_index >= len(english_data["scenes"]):
print("所有场景已完成!")
return False
self.current_scene = english_data["scenes"][scene_index]
self.game_state.current_scene_index = scene_index
self.game_state.current_dialog_index = 0
print("\n" + "=" * 50)
print(f"场景:{self.current_scene['title']}")
print(f"描述:{self.current_scene['description']}")
print("=" * 50)
# 加载场景图片(在命令行环境下仅显示图片路径)
self._load_scene_image()
# 开始对话
self.show_next_dialog()
return True
def _load_scene_image(self):
"""加载场景图片"""
scene = self.current_scene
image_path = scene.get("image", "")
if not image_path:
# 自动查找图片
possible_extensions = [".jpg", ".jpeg", ".png", ".gif", ".webp", ".bmp", ".svg"]
base_name_id = str(scene["id"])
# 简单的拼音转换(仅保留字母数字)
base_name_title = ''.join(c for c in scene["title"] if c.isalnum()).lower()
all_possible_paths = [
# 优先使用ID命名
*[f"images/{base_name_id}{ext}" for ext in possible_extensions],
# 支持标题命名
*[f"images/{base_name_title}{ext}" for ext in possible_extensions]
]
for path in all_possible_paths:
if os.path.exists(path):
image_path = path
break
if image_path and os.path.exists(image_path):
print(f"图片:{image_path}")
else:
print("图片:无")
def show_next_dialog(self):
"""显示下一条对话"""
if not self.current_scene:
return
conversation = self.current_scene["conversation"]
dialog_index = self.game_state.current_dialog_index
if dialog_index >= len(conversation):
# 当前场景对话结束
self._scene_complete()
return
dialog = conversation[dialog_index]
if dialog["type"] == "user_question":
# 区分选择题和自由回答题
if "options" in dialog:
self._show_question(dialog)
else:
self._show_dynamic_question(dialog)
else:
# 显示普通对话
self._add_message(dialog)
# 自动显示下一个对话
self.game_state.current_dialog_index += 1
time.sleep(1)
self.show_next_dialog()
def _add_message(self, dialog: Dict):
"""添加对话消息"""
speaker = dialog["speaker"]
text = dialog["text"]
print(f"\n{speaker}: {text}")
def _show_question(self, dialog: Dict):
"""显示选择题"""
print(f"\n{dialog['question']}")
for i, option in enumerate(dialog["options"]):
print(f"{i + 1}. {option}")
# 获取用户输入
try:
user_choice = int(input("请输入选项编号:")) - 1
if 0 <= user_choice < len(dialog["options"]):
self._check_answer(dialog, user_choice)
else:
print("无效的选项,请重新选择!")
self._show_question(dialog)
except ValueError:
print("请输入有效的数字!")
self._show_question(dialog)
def _show_dynamic_question(self, dialog: Dict):
"""显示动态问题使用API调用"""
print(f"\n{dialog['question']}")
user_input = input("请输入您的回答:")
# 保存用户输入到对话历史
self._save_user_input(dialog, user_input)
# 获取AI响应
ai_response = self._get_ai_response(user_input)
# 显示AI响应
print(f"\nAI响应: {ai_response}")
# 继续对话
self.game_state.current_dialog_index += 2 # 跳过用户问题和AI响应
self.show_next_dialog()
def _save_user_input(self, dialog: Dict, user_input: str):
"""保存用户输入到对话历史"""
if not self.current_scene:
return
conversation = self.current_scene["conversation"]
dialog_index = self.game_state.current_dialog_index
# 插入用户输入
user_message = {
"speaker": "",
"text": user_input,
"type": "user"
}
conversation.insert(dialog_index + 1, user_message)
# 插入AI响应占位符
ai_message = {
"speaker": "AI",
"text": "",
"type": "other"
}
conversation.insert(dialog_index + 2, ai_message)
def _get_ai_response(self, user_input: str) -> str:
"""获取AI响应"""
if not self.api_key:
return "抱歉API密钥未配置无法获取AI响应。"
try:
# 构建对话历史
messages = [
{"role": "system", "content": "你是一位英语学习助手,帮助用户进行日常对话练习。请用英语回复,保持对话自然流畅。"}
]
# 添加当前对话历史
if self.current_scene:
for dialog in self.current_scene["conversation"][:self.game_state.current_dialog_index + 1]:
role = "assistant" if dialog["speaker"] != "" else "user"
content = dialog.get("text", dialog.get("question", ""))
messages.append({"role": role, "content": content})
# 添加用户最新输入
messages.append({"role": "user", "content": user_input})
# 调用API
headers = {
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json"
}
payload = {
"model": "deepseek-chat",
"messages": messages,
"stream": True
}
response = requests.post(self.api_url, headers=headers, json=payload, stream=True)
response.raise_for_status()
# 处理流式响应
ai_response = ""
print("\nAI正在思考...", end="", flush=True)
for chunk in response.iter_content(chunk_size=None):
if chunk:
decoded_chunk = chunk.decode("utf-8")
# 解析SSE格式
lines = decoded_chunk.split("\n")
for line in lines:
if line.startswith("data: "):
data = line[6:].strip()
if data != "[DONE]":
try:
json_data = json.loads(data)
if "choices" in json_data and json_data["choices"]:
delta = json_data["choices"][0].get("delta", {})
if "content" in delta:
content = delta["content"]
ai_response += content
print(content, end="", flush=True)
except json.JSONDecodeError:
continue
print() # 换行
return ai_response if ai_response else "抱歉,无法生成响应。"
except requests.exceptions.RequestException as e:
print(f"\nAPI调用错误: {e}")
return "抱歉API调用失败。"
def _check_answer(self, dialog: Dict, user_choice: int):
"""检查答案"""
is_correct = user_choice == dialog["correctAnswer"]
if is_correct:
self.game_state.correct_answers += 1
print("\n✅ 回答正确!")
else:
correct_answer = dialog["options"][dialog["correctAnswer"]]
print(f"\n❌ 回答错误!正确答案是:{correct_answer}")
self.game_state.total_questions += 1
self.game_state.score += 1 if is_correct else 0
# 更新进度
self.update_progress()
# 继续对话
self.game_state.current_dialog_index += 1
time.sleep(1)
self.show_next_dialog()
def _scene_complete(self):
"""场景完成"""
print("\n" + "=" * 50)
print("当前场景对话已完成!")
print("=" * 50)
if self.game_state.current_scene_index < len(english_data["scenes"]) - 1:
# 询问是否进入下一个场景
next_scene = input("是否进入下一个场景?(y/n): ")
if next_scene.lower() == "y":
next_index = self.game_state.current_scene_index + 1
self.load_scene(next_index)
else:
print("\n🎉 所有场景已完成!")
self._show_final_score()
def _show_final_score(self):
"""显示最终得分"""
if self.game_state.total_questions > 0:
accuracy = (self.game_state.correct_answers / self.game_state.total_questions) * 100
print(f"\n最终得分: {self.game_state.score}")
print(f"正确率: {accuracy:.1f}%")
print(f"总题目数: {self.game_state.total_questions}")
print(f"正确答案: {self.game_state.correct_answers}")
else:
print("\n未完成任何题目")
def update_progress(self):
"""更新进度信息"""
total_scenes = len(english_data["scenes"])
current_scene = self.game_state.current_scene_index + 1
print(f"\n进度:场景 {current_scene}/{total_scenes} | 得分:{self.game_state.score}")
def start(self):
"""启动应用"""
print("欢迎使用英语学习程序!")
print("=" * 50)
# 加载第一个场景
self.load_scene(0)
if __name__ == "__main__":
# 创建应用实例
app = EnglishLearningApp()
# 启动应用
app.start()

541
style.css
View File

@ -1,4 +1,3 @@
/* 全局样式 */
* {
margin: 0;
padding: 0;
@ -7,373 +6,349 @@
body {
font-family: 'Arial', sans-serif;
background-color: #f5f7fa;
color: #333;
line-height: 1.6;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
/* 头部样式 */
.header {
text-align: center;
margin-bottom: 30px;
padding: 20px;
background-color: #4a90e2;
background-color: #1a532a;
color: white;
border-radius: 10px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
.header h1 {
font-size: 2.5em;
margin-bottom: 10px;
}
.subtitle {
font-size: 1.2em;
opacity: 0.9;
}
/* 状态栏样式 */
.status-bar {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
padding: 15px;
background-color: white;
border-radius: 8px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
}
.score, .progress {
font-size: 1.1em;
font-weight: bold;
color: #4a90e2;
}
/* 内容区域样式 */
.content {
background-color: white;
border-radius: 10px;
padding: 30px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.08);
}
/* 场景信息样式 */
.scene-info {
margin-bottom: 30px;
padding-bottom: 20px;
border-bottom: 2px solid #e8f0fe;
}
.scene-info h2 {
color: #4a90e2;
margin-bottom: 10px;
font-size: 1.8em;
}
.scene-info p {
color: #666;
font-size: 1.1em;
}
/* 场景图片样式 */
.scene-image-container {
margin-top: 20px;
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
#scene-image {
max-width: 100%;
height: auto;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
max-height: 300px;
object-fit: cover;
}
/* 对话区域样式 */
.conversation-area {
margin-bottom: 30px;
}
.conversation {
max-height: 400px;
overflow-y: auto;
.game-container {
width: 90vw;
max-width: 1200px;
min-height: 90vh;
background-color: rgba(0, 0, 0, 0.3);
border-radius: 20px;
padding: 20px;
background-color: #f8f9fa;
border-radius: 8px;
border: 1px solid #e9ecef;
box-shadow: 0 0 20px rgba(0, 0, 0, 0.5);
}
.message {
margin-bottom: 15px;
padding: 10px 15px;
border-radius: 18px;
max-width: 80%;
clear: both;
}
.message.other {
background-color: #e3f2fd;
color: #1565c0;
float: left;
border-bottom-left-radius: 5px;
}
.message.user {
background-color: #4a90e2;
color: white;
float: right;
border-bottom-right-radius: 5px;
}
.message .speaker {
font-weight: bold;
margin-right: 10px;
}
/* 问题区域样式 */
.question-area {
margin-bottom: 30px;
padding: 20px;
background-color: #f8f9fa;
border-radius: 8px;
border: 1px solid #e9ecef;
}
.question h3 {
color: #4a90e2;
.game-header {
text-align: center;
margin-bottom: 20px;
font-size: 1.3em;
}
.options {
.game-header h1 {
font-size: 2.5em;
color: #ffd700;
margin-bottom: 10px;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
}
.game-info {
display: flex;
justify-content: center;
gap: 20px;
font-size: 1.2em;
}
.game-table {
position: relative;
width: 100%;
min-height: 500px;
background-color: rgba(255, 255, 255, 0.1);
border-radius: 15px;
padding: 20px;
display: flex;
flex-direction: column;
gap: 20px;
}
/* 地主牌区域 */
.landlord-cards {
text-align: center;
margin-bottom: 20px;
}
#landlord-indicator {
font-size: 1.2em;
margin-bottom: 10px;
color: #ffd700;
}
#landlord-cards-area {
display: flex;
justify-content: center;
gap: 5px;
}
/* 玩家区域 */
.player {
width: 100%;
text-align: center;
}
.player-info {
display: flex;
justify-content: center;
align-items: center;
gap: 15px;
}
.option {
padding: 15px 20px;
border: 2px solid #e0e0e0;
background-color: white;
border-radius: 8px;
margin-bottom: 10px;
font-size: 1.1em;
cursor: pointer;
transition: all 0.3s ease;
text-align: left;
}
.option:hover {
border-color: #4a90e2;
background-color: #f0f8ff;
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
.player-name {
font-weight: bold;
}
.option.selected {
border-color: #4a90e2;
background-color: #e3f2fd;
.cards-count {
background-color: rgba(255, 255, 255, 0.2);
padding: 5px 10px;
border-radius: 10px;
}
.option.correct {
border-color: #4caf50;
background-color: #e8f5e9;
color: #2e7d32;
.player-role {
color: #ff6b6b;
font-weight: bold;
}
.option.incorrect {
border-color: #f44336;
background-color: #ffebee;
color: #c62828;
.player-cards {
display: flex;
justify-content: center;
gap: 5px;
flex-wrap: wrap;
margin-bottom: 10px;
}
.option.disabled {
cursor: not-allowed;
.last-play {
display: flex;
justify-content: center;
gap: 5px;
margin-top: 10px;
opacity: 0.7;
}
.option.disabled:hover {
transform: none;
box-shadow: none;
/* 自己的玩家区域 */
.player-self {
margin-top: 30px;
}
/* 反馈区域样式 */
.feedback {
margin-bottom: 20px;
padding: 15px 20px;
.player-self .player-cards {
gap: 2px;
}
/* 卡片样式 */
.card {
width: 80px;
height: 120px;
background-color: white;
border-radius: 8px;
border: 1px solid #ddd;
text-align: center;
font-weight: bold;
}
.feedback.correct {
background-color: #e8f5e9;
border-color: #4caf50;
color: #2e7d32;
}
.feedback.incorrect {
background-color: #ffebee;
border-color: #f44336;
color: #c62828;
}
/* 控制按钮样式 */
.controls {
display: flex;
gap: 15px;
justify-content: center;
margin-top: 30px;
}
.btn {
padding: 12px 24px;
border: none;
border-radius: 8px;
font-size: 1.1em;
flex-direction: column;
justify-content: space-between;
padding: 5px;
color: black;
font-weight: bold;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3);
cursor: pointer;
transition: all 0.3s ease;
min-width: 120px;
transition: all 0.2s ease;
position: relative;
border: 2px solid transparent;
}
.btn-primary {
background-color: #4a90e2;
color: white;
.card:hover {
transform: translateY(-5px);
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5);
}
.btn-primary:hover {
background-color: #357abd;
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
.card.selected {
border-color: #ffd700;
background-color: #fff9c4;
}
.btn-secondary {
background-color: #90a4ae;
color: white;
.card-back {
background-color: #424242;
background-image: radial-gradient(circle, #666 20%, transparent 20%),
radial-gradient(circle, #666 20%, transparent 20%);
background-size: 10px 10px;
background-position: 0 0, 5px 5px;
color: transparent;
}
.btn-secondary:hover {
background-color: #78909c;
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
.card .rank-top {
font-size: 1.5em;
align-self: flex-start;
}
.btn-success {
background-color: #4caf50;
color: white;
.card .suit-middle {
font-size: 2em;
align-self: center;
margin: 10px 0;
}
.btn-success:hover {
background-color: #388e3c;
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
.card .rank-bottom {
font-size: 1.5em;
align-self: flex-end;
transform: rotate(180deg);
}
.btn:disabled {
opacity: 0.6;
cursor: not-allowed;
transform: none;
box-shadow: none;
/* 花色颜色 */
.card.spades, .card.clubs {
color: black;
}
/* 响应式设计 */
/* 实时对话功能样式 */
.real-time-chat {
margin-top: 30px;
padding-top: 20px;
border-top: 2px solid #e8f0fe;
.card.hearts, .card.diamonds {
color: red;
}
.real-time-chat h3 {
color: #4a90e2;
margin-bottom: 15px;
/* 大王小王 */
.card.joker-big .suit-middle,
.card.joker-small .suit-middle {
color: black;
font-size: 1.5em;
}
.chat-history {
max-height: 300px;
overflow-y: auto;
padding: 20px;
background-color: #f8f9fa;
border-radius: 8px;
border: 1px solid #e9ecef;
margin-bottom: 15px;
/* 当前出牌区域 */
.current-play-area {
text-align: center;
margin: 20px 0;
}
.chat-input {
#current-play-info {
font-size: 1.2em;
margin-bottom: 10px;
color: #ffd700;
}
#current-play-cards {
display: flex;
gap: 10px;
justify-content: center;
gap: 5px;
}
#chat-message {
flex: 1;
padding: 12px 15px;
border: 2px solid #e0e0e0;
/* 游戏控制按钮 */
.game-controls {
display: flex;
justify-content: center;
align-items: center;
gap: 15px;
margin-top: 20px;
flex-wrap: wrap;
}
button {
padding: 12px 24px;
font-size: 1.1em;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 8px;
font-size: 1em;
transition: all 0.3s ease;
cursor: pointer;
transition: background-color 0.3s ease;
font-weight: bold;
}
#chat-message:focus {
outline: none;
border-color: #4a90e2;
box-shadow: 0 0 0 2px rgba(74, 144, 226, 0.2);
button:hover {
background-color: #45a049;
}
.chat-input .btn {
min-width: 100px;
button:active {
transform: translateY(1px);
}
button:disabled {
background-color: #cccccc;
cursor: not-allowed;
transform: none;
}
/* 叫地主按钮区域 */
.landlord-buttons {
display: flex;
gap: 15px;
}
#call-landlord {
background-color: #ff9800;
}
#call-landlord:hover {
background-color: #f57c00;
}
#no-call {
background-color: #f44336;
}
#no-call:hover {
background-color: #da190b;
}
/* 出牌按钮区域 */
.play-buttons {
display: flex;
gap: 15px;
}
#play-cards {
background-color: #2196F3;
}
#play-cards:hover {
background-color: #0b7dda;
}
#pass {
background-color: #9e9e9e;
}
#pass:hover {
background-color: #757575;
}
/* 游戏统计 */
.game-stats {
margin-left: 30px;
font-size: 1.2em;
color: #ffd700;
}
/* 响应式设计 */
@media (max-width: 768px) {
.container {
.game-container {
width: 98vw;
padding: 10px;
}
.header h1 {
.game-header h1 {
font-size: 2em;
}
.content {
padding: 20px;
.card {
width: 60px;
height: 90px;
font-size: 0.9em;
}
.status-bar {
flex-direction: column;
.player-info {
font-size: 1em;
gap: 10px;
align-items: flex-start;
}
.message {
max-width: 95%;
.game-controls {
gap: 10px;
}
.controls {
button {
padding: 10px 20px;
font-size: 1em;
}
}
@media (max-width: 480px) {
.card {
width: 50px;
height: 75px;
font-size: 0.8em;
}
.game-info {
flex-direction: column;
align-items: center;
gap: 5px;
}
.btn {
width: 100%;
max-width: 300px;
}
.chat-input {
.game-controls {
flex-direction: column;
}
.chat-input .btn {
min-width: auto;
}
}