【My0vvn】wlChat-前端代码解析

写在前面

  • 因为没有系统学,只是速成一下
  • 所以跟着代码学会结构之后再上手

前端代码解析

AI生成的代码需要先进行结构分析,然后在这个基础上进行修改

UserSettings.vue

模板部分(<template>


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
<template>
<div :class="['user-settings', settings.theme]">
<div class="container">
<h1>User Settings</h1>

<form @submit.prevent="saveSettings">
<!-- Theme Selection -->
<div class="setting-group">
<h2>Theme</h2>
<div class="radio-group">
<label>
<input type="radio" v-model="settings.theme" value="dark">
<span>Dark</span>
</label>
<label>
<input type="radio" v-model="settings.theme" value="light">
<span>Light</span>
</label>
</div>
</div>
...
</form>
</div>
</div>
</template>
  1. 根元素<div :class="['user-settings', settings.theme]">
  • 使用动态类绑定,类名根据 settings.theme 的值(darklight)来改变
  1. 表单<form @submit.prevent="saveSettings">
  • 当表单提交时,调用 saveSettings 方法并阻止默认提交行为
  1. 主题选择
  • 包含两个单选按钮,通过 v-model 双向绑定到 settings.theme

脚本部分(<script setup>


script变量部分
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
<script setup>
import { ref, onMounted } from 'vue';

const settings = ref({
theme: 'dark',
fontSize: 16,
language: 'en',
notifications: {
email: true,
push: false
},
privacy: {
dataSharing: false
}
});

const updateFontSize = () => {
document.documentElement.style.fontSize = `${settings.value.fontSize}px`;
};

const saveSettings = () => {
console.log('Saving settings:', settings.value);
alert('Settings saved successfully!');
};

onMounted(() => {
updateFontSize();
});
</script>
  1. 引入 Vue API:引入 refonMounted

  2. 设置变量:定义了一个响应式的 settings 对象,包含:

  • theme: 默认值为 'dark'
  • fontSize: 默认值为 16
  • language: 默认值为 'en'
  • notifications: 包含 emailpush 两个布尔值,默认分别为 truefalse
  • privacy: 包含一个布尔值 dataSharing,默认为 false

方法:updateFontSize
1
2
3
const updateFontSize = () => {
document.documentElement.style.fontSize = `${settings.value.fontSize}px`;
};
  1. 方法updateFontSize 用于更新 HTML 根元素的字体大小,根据 settings.fontSize 的值

方法:saveSettings
1
2
3
4
const saveSettings = () => {
console.log('Saving settings:', settings.value);
alert('Settings saved successfully!');
};
  1. 方法saveSettings 用于打印当前设置并弹出保存成功的提示

方法:生命周期钩子
1
2
3
onMounted(() => {
updateFontSize();
});
  1. 生命周期钩子onMounted 用于组件挂载后调用 updateFontSize 方法,以确保页面加载时字体大小正确

样式部分(<style scoped>

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
<style scoped>
.user-settings {
min-height: 100vh;
padding: 1rem;
font-family: Arial, sans-serif;
}

.user-settings.dark {
background-color: #1a202c;
color: #ffffff;
}

.user-settings.light {
background-color: #ffffff;
color: #000000;
}

.container {
max-width: 32rem;
margin: 0 auto;
background-color: #2d3748;
border-radius: 0.5rem;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
overflow: hidden;
}

h1 {
font-size: 1.875rem;
font-weight: bold;
padding: 1.5rem;
background-color: #4a5568;
margin: 0;
}

form {
padding: 1.5rem;
}

.setting-group {
margin-bottom: 1.5rem;
}

h2 {
font-size: 1.25rem;
font-weight: 600;
margin-bottom: 0.75rem;
}

.radio-group, .checkbox-group {
display: flex;
gap: 1rem;
}

label {
display: flex;
align-items: center;
cursor: pointer;
}

input[type="radio"], input[type="checkbox"] {
margin-right: 0.5rem;
}

input[type="range"] {
width: 100%;
margin-bottom: 0.5rem;
}

.range-labels {
display: flex;
justify-content: space-between;
font-size: 0.875rem;
}

select {
width: 100%;
padding: 0.5rem;
border-radius: 0.25rem;
background-color: #4a5568;
color: #ffffff;
border: none;
}

.save-button {
width: 100%;
background-color: #3182ce;
color: #ffffff;
padding: 0.75rem;
border: none;
border-radius: 0.25rem;
font-size: 1rem;
cursor: pointer;
transition: background-color 0.3s;
}

.save-button:hover {
background-color: #2c5282;
}

/* Custom styles for range input */
input[type="range"] {
-webkit-appearance: none;
width: 100%;
height: 8px;
background: #4B5563;
outline: none;
opacity: 0.7;
transition: opacity .2s;
border-radius: 5px;
}

input[type="range"]:hover {
opacity: 1;
}

input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 20px;
height: 20px;
background: #3B82F6;
cursor: pointer;
border-radius: 50%;
}

input[type="range"]::-moz-range-thumb {
width: 20px;
height: 20px;
background: #3B82F6;
cursor: pointer;
border-radius: 50%;
}
</style>

变量

  • settings: 包含用户设置的响应式对象。
  • settings.theme: 用户选择的主题。
  • settings.fontSize: 用户选择的字体大小。
  • settings.language: 用户选择的语言。
  • settings.notifications: 用户的通知设置。
  • settings.privacy: 用户的隐私设置。

方法

  • updateFontSize: 更新字体大小。
  • saveSettings: 保存用户设置并打印当前设置

ChatHistoryManager.vue

模板部分


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
<template>
<div class="chat-history">
<div class="container">
<h1>Chat History</h1>

<div class="content">
<div class="search-delete">
<input
v-model="searchQuery"
type="text"
placeholder="Search history..."
class="search-input"
/>
<button
@click="deleteSelected"
class="delete-button"
:disabled="selectedItems.length === 0"
>
Delete Selected
</button>
</div>

<table>
<thead>
<tr>
<th>
<input
type="checkbox"
@change="toggleSelectAll"
:checked="selectedItems.length === filteredHistory.length"
/>
</th>
<th>Date</th>
<th>Question</th>
<th>Answer Summary</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<tr v-for="item in paginatedHistory" :key="item.id">
<td>
<input
type="checkbox"
:value="item.id"
v-model="selectedItems"
/>
</td>
<td>{{ formatDate(item.date) }}</td>
<td>{{ truncate(item.question, 30) }}</td>
<td>{{ truncate(item.answer, 50) }}</td>
<td>
<button @click="viewDetails(item)" class="view-button">
View
</button>
<button @click="deleteItem(item.id)" class="delete-button">
Delete
</button>
</td>
</tr>
</tbody>
</table>

<div class="pagination">
<div>
<span>
Showing {{ startIndex + 1 }}-{{ endIndex }} of {{ filteredHistory.length }} items
</span>
</div>
<div>
<button
@click="prevPage"
:disabled="currentPage === 1"
class="page-button"
>
Previous
</button>
<button
@click="nextPage"
:disabled="currentPage === totalPages"
class="page-button"
>
Next
</button>
</div>
</div>
</div>

<!-- Modal for viewing details -->
<div v-if="selectedItem" class="modal">
<div class="modal-content">
<h2>Conversation Details</h2>
<p><strong>Date:</strong> {{ formatDate(selectedItem.date) }}</p>
<p><strong>Question:</strong> {{ selectedItem.question }}</p>
<p><strong>Answer:</strong> {{ selectedItem.answer }}</p>
<button @click="selectedItem = null" class="close-button">
Close
</button>
</div>
</div>
</div>
</div>
</template>
  • 这一部分定义了组件的结构,使用 HTML 结合 Vue.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
<script setup>
import { ref, computed } from 'vue';

// Sample data (replace with actual data fetching logic)
const chatHistory = ref([
{ id: 1, date: new Date('2023-09-15'), question: "What is the capital of France?", answer: "The capital of France is Paris. It is known for its iconic landmarks such as the Eiffel Tower and the Louvre Museum." },
{ id: 2, date: new Date('2023-09-16'), question: "How does photosynthesis work?", answer: "Photosynthesis is the process by which plants use sunlight, water and carbon dioxide to produce oxygen and energy in the form of sugar." },
// Add more items as needed
]);

const searchQuery = ref('');
const selectedItems = ref([]);
const currentPage = ref(1);
const itemsPerPage = 10;
const selectedItem = ref(null);

const filteredHistory = computed(() => {
return chatHistory.value.filter(item =>
item.question.toLowerCase().includes(searchQuery.value.toLowerCase()) ||
item.answer.toLowerCase().includes(searchQuery.value.toLowerCase())
);
});

const totalPages = computed(() => Math.ceil(filteredHistory.value.length / itemsPerPage));

const startIndex = computed(() => (currentPage.value - 1) * itemsPerPage);
const endIndex = computed(() => Math.min(startIndex.value + itemsPerPage, filteredHistory.value.length));

const paginatedHistory = computed(() => {
return filteredHistory.value.slice(startIndex.value, endIndex.value);
});

const formatDate = (date) => {
return new Date(date).toLocaleDateString();
};

const truncate = (str, length) => {
return str.length > length ? str.substring(0, length) + '...' : str;
};

const toggleSelectAll = (event) => {
if (event.target.checked) {
selectedItems.value = filteredHistory.value.map(item => item.id);
} else {
selectedItems.value = [];
}
};

const deleteSelected = () => {
chatHistory.value = chatHistory.value.filter(item => !selectedItems.value.includes(item.id));
selectedItems.value = [];
};

const deleteItem = (id) => {
chatHistory.value = chatHistory.value.filter(item => item.id !== id);
selectedItems.value = selectedItems.value.filter(itemId => itemId !== id);
};

const viewDetails = (item) => {
selectedItem.value = item;
};

const prevPage = () => {
if (currentPage.value > 1) {
currentPage.value--;
}
};

const nextPage = () => {
if (currentPage.value < totalPages.value) {
currentPage.value++;
}
};
</script>

这一部分包含组件的 JavaScript 逻辑,使用 Vue 3 的组合 API,通过 refcomputed

变量:

  • chatHistory:存储聊天记录的列表。
  • searchQuery:绑定搜索输入框。
  • selectedItems:跟踪被选中的聊天项目以便删除。
  • currentPage:当前的分页页码。
  • itemsPerPage:每页显示的项目数量。
  • selectedItem:当前选中的聊天项目,用于查看详情。

计算属性:

  • filteredHistory:根据 searchQuery 过滤 chatHistory
  • totalPages:计算基于过滤历史记录和每页项目数量的总页数。
  • startIndexendIndex:根据当前页确定显示项目的范围。
  • paginatedHistory:切片过滤后的历史记录,仅显示当前页的项目。

方法:

  • formatDate(date):将日期格式化为可读字符串。
  • truncate(str, length):将字符串截断到指定长度,并在过长时添加省略号。
  • toggleSelectAll(event):根据复选框的状态切换全选或全不选。
  • deleteSelected():删除选中的聊天项目。
  • deleteItem(id):根据 ID 删除特定聊天项目。
  • viewDetails(item):设置当前选中的聊天项目以查看详情
  • prevPage()nextPage():改变当前的分页。

样式部分

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
<style scoped>
.chat-history {
min-height: 100vh;
background-color: #1a202c;
padding: 2rem;
}

.container {
max-width: 1200px;
margin: 0 auto;
background-color: #2d3748;
border-radius: 0.5rem;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
overflow: hidden;
}

h1 {
font-size: 1.875rem;
font-weight: bold;
color: white;
padding: 1.5rem;
background-color: #4a5568;
margin: 0;
}

.content {
padding: 1.5rem;
}

.search-delete {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1.5rem;
}

.search-input {
padding: 0.5rem 1rem;
background-color: #4a5568;
color: white;
border: none;
border-radius: 0.375rem;
width: 16rem;
}

.search-input:focus {
outline: none;
box-shadow: 0 0 0 2px #4299e1;
}

.delete-button {
background-color: #e53e3e;
color: white;
padding: 0.5rem 1rem;
border: none;
border-radius: 0.375rem;
cursor: pointer;
transition: background-color 0.3s;
}

.delete-button:hover {
background-color: #c53030;
}

.delete-button:disabled {
opacity: 0.5;
cursor: not-allowed;
}

table {
width: 100%;
color: white;
border-collapse: collapse;
}

th, td {
padding: 0.75rem;
text-align: left;
border-bottom: 1px solid #4a5568;
}

th {
background-color: #4a5568;
}

.view-button, .page-button {
background-color: #4299e1;
color: white;
padding: 0.25rem 0.75rem;
border: none;
border-radius: 0.25rem;
cursor: pointer;
transition: background-color 0.3s;
margin-right: 0.5rem;
}

.view-button:hover, .page-button:hover {
background-color: #3182ce;
}

.pagination {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 1.5rem;
color: white;
}

.modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
}

.modal-content {
background-color: #2d3748;
padding: 1.5rem;
border-radius: 0.5rem;
max-width: 42rem;
width: 100%;
}

.modal-content h2 {
font-size: 1.5rem;
font-weight: bold;
color: white;
margin-bottom: 1rem;
}

.modal-content p {
color: white;
margin-bottom: 0.5rem;
}

.close-button {
background-color: #4299e1;
color: white;
padding: 0.5rem 1rem;
border: none;
border-radius: 0.375rem;
cursor: pointer;
transition: background-color 0.3s;
margin-top: 1rem;
}

.close-button:hover {
background-color: #3182ce;
}
</style>
  • 这一部分包含组件的样式,定义颜色、填充、边距和其他视觉属性,以创建一个统一的设计。

变量和方法总结

  • 变量

    • chatHistory
    • searchQuery
    • selectedItems
    • currentPage
    • itemsPerPage
    • selectedItem
  • 计算属性

    • filteredHistory
    • totalPages
    • startIndex
    • endIndex
    • paginatedHistory
  • 方法

    • formatDate(date)
    • truncate(str, length)
    • toggleSelectAll(event)
    • deleteSelected()
    • deleteItem(id)
    • viewDetails(item)
    • prevPage()
    • nextPage()

AIChatInterface.vue

模板部分


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
<template>
<div class="chat-container">
<div class="chat-window">
<header class="chat-header">
<h1>AI Chat Assistant</h1>
</header>

<div class="chat-messages" ref="chatWindow">
<div v-for="(message, index) in messages" :key="index" class="message-container">
<div :class="['message', message.isUser ? 'user-message' : 'ai-message']">
{{ message.text }}
</div>
</div>
<div v-if="isThinking" class="thinking-indicator">
<span>AI is thinking</span>
<div class="loading-dots">
<span></span>
<span></span>
<span></span>
</div>
</div>
</div>

<div class="chat-input">
<form @submit.prevent="sendMessage" class="input-form">
<input
v-model="userInput"
type="text"
placeholder="Type your message here..."
class="message-input"
/>
<button type="submit" class="send-button">
Send
</button>
</form>
</div>
</div>
</div>
</template>
  • 组件的模板部分定义了聊天界面的结构。
  • 包含聊天窗口的头部、消息显示区域和用户输入区域。
  • 使用 v-for 指令循环渲染消息。
  • 根据 isThinking 变量控制 AI 思考时的指示器显示

脚本部分

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
<script setup>
import { ref, onMounted, nextTick } from 'vue';

const messages = ref([]);
const userInput = ref('');
const isThinking = ref(false);
const chatWindow = ref(null);

const sendMessage = async () => {
if (userInput.value.trim() === '') return;

// Add user message
messages.value.push({ text: userInput.value, isUser: true });

// Clear input and scroll to bottom
userInput.value = '';
await nextTick();
scrollToBottom();

// Simulate AI thinking
isThinking.value = true;
setTimeout(async () => {
// Simulate AI response
const aiResponse = "This is a simulated AI response. In a real application, you would send the user's input to your AI model's API and receive a response.";
messages.value.push({ text: aiResponse, isUser: false });
isThinking.value = false;

await nextTick();
scrollToBottom();
}, 1500);
};

const scrollToBottom = () => {
if (chatWindow.value) {
chatWindow.value.scrollTop = chatWindow.value.scrollHeight;
}
};

onMounted(() => {
messages.value.push({ text: "Hello! How can I assist you today?", isUser: false });
});
</script>
  • 使用 Vue 3 的组合 API 进行状态管理。

变量:

  • messages:存储聊天记录的数组。
  • userInput:绑定用户输入的文本框。
  • isThinking:指示 AI 是否正在思考的布尔值。
  • chatWindow:引用聊天窗口 DOM 元素。

方法:

  • sendMessage :处理发送消息的逻辑。

    • 检查输入是否为空→不为空
      • 将用户消息添加到 messages 中。
      • 清空输入框并滚动到底部。
      • 模拟 AI 思考时间并生成响应。
  • scrollToBottom:将聊天窗口滚动到底部。

生命周期钩子:

  • onMounted:组件挂载后自动添加一条初始消息

样式部分

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
<style scoped>
.chat-container {
min-height: 100vh;
background-color: #1a202c;
display: flex;
align-items: center;
justify-content: center;
padding: 1rem;
}

.chat-window {
width: 100%;
max-width: 56rem;
background-color: #2d3748;
border-radius: 0.5rem;
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1);
overflow: hidden;
display: flex;
flex-direction: column;
}

.chat-header {
background-color: #4a5568;
padding: 1rem;
}

.chat-header h1 {
color: white;
font-size: 1.5rem;
font-weight: bold;
margin: 0;
}

.chat-messages {
flex-grow: 1;
padding: 1.5rem;
overflow-y: auto;
max-height: 60vh;
}

.message-container {
margin-bottom: 1rem;
}

.message {
max-width: 75%;
border-radius: 0.5rem;
padding: 0.75rem;
color: white;
}

.user-message {
background-color: #3182ce;
margin-left: auto;
}

.ai-message {
background-color: #4a5568;
}

.thinking-indicator {
display: flex;
align-items: center;
color: white;
}

.thinking-indicator span {
margin-right: 0.75rem;
}

.loading-dots span {
display: inline-block;
width: 8px;
height: 8px;
border-radius: 50%;
background-color: #fff;
margin: 0 2px;
animation: pulse 1s infinite ease-in-out;
}

.loading-dots span:nth-child(2) {
animation-delay: 0.2s;
}

.loading-dots span:nth-child(3) {
animation-delay: 0.4s;
}

@keyframes pulse {
0%, 100% {
transform: scale(0.5);
opacity: 0.5;
}
50% {
transform: scale(1);
opacity: 1;
}
}

.chat-input {
background-color: #4a5568;
padding: 1rem;
}

.input-form {
display: flex;
}

.message-input {
flex-grow: 1;
padding: 0.5rem 1rem;
background-color: #2d3748;
color: white;
border: none;
border-top-left-radius: 0.5rem;
border-bottom-left-radius: 0.5rem;
}

.message-input:focus {
outline: none;
}

.send-button {
background-color: #3182ce;
color: white;
padding: 0.5rem 1.5rem;
border: none;
border-top-right-radius: 0.5rem;
border-bottom-right-radius: 0.5rem;
cursor: pointer;
transition: background-color 0.3s;
}

.send-button:hover {
background-color: #2c5282;
}
</style>
  • 这一部分定义了组件的样式,设置了背景色、布局和消息的外观。
  • 通过 scoped 关键字确保样式仅适用于当前组件。

总结

变量
  • messages:存储聊天消息的数组。
  • userInput:用户输入的文本。
  • isThinking:指示 AI 是否在思考的布尔值。
  • chatWindow:引用聊天窗口的 DOM 元素。
方法
  • sendMessage():处理发送消息和生成 AI 响应的逻辑。
  • scrollToBottom():将聊天窗口滚动到最底部。
  • Copyrights © 2024-2025 brocademaple
  • 访问人数: | 浏览次数:

      请我喝杯咖啡吧~

      支付宝
      微信