wad-lab 1.0.7 → 1.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/index.js +24 -18
- package/package.json +1 -1
- package/templates/game-bingo/index.html +32 -0
- package/templates/game-bingo/script.js +118 -0
- package/templates/game-bingo/style.css +104 -0
- package/templates/springboot-course-registration/.mvn/wrapper/maven-wrapper.properties +3 -0
- package/templates/springboot-course-registration/mvnw +295 -0
- package/templates/springboot-course-registration/mvnw.cmd +189 -0
- package/templates/springboot-course-registration/pom.xml +41 -0
- package/templates/springboot-course-registration/src/main/java/com/example/coursereg/CourseRegApplication.java +12 -0
- package/templates/springboot-course-registration/src/main/java/com/example/coursereg/controller/CourseController.java +42 -0
- package/templates/springboot-course-registration/src/main/java/com/example/coursereg/model/Course.java +99 -0
- package/templates/springboot-course-registration/src/main/java/com/example/coursereg/repository/CourseRepository.java +16 -0
- package/templates/springboot-course-registration/src/main/java/com/example/coursereg/service/CourseService.java +60 -0
- package/templates/springboot-course-registration/src/main/resources/application.properties +3 -0
- package/templates/springboot-course-registration/src/test/java/com/example/coursereg/CourseRegApplicationTests.java +12 -0
- package/templates/vue-banking-app/index.html +12 -0
- package/templates/vue-banking-app/package.json +20 -0
- package/templates/vue-banking-app/src/App.vue +80 -0
- package/templates/vue-banking-app/src/main.js +9 -0
- package/templates/vue-banking-app/src/router/index.js +17 -0
- package/templates/vue-banking-app/src/stores/bankStore.js +48 -0
- package/templates/vue-banking-app/src/views/DashboardView.vue +88 -0
- package/templates/vue-banking-app/src/views/StatementView.vue +171 -0
- package/templates/vue-banking-app/src/views/TransactionFormView.vue +144 -0
- package/templates/vue-banking-app/vite.config.js +6 -0
- package/templates/vue-employee-system/index.html +12 -0
- package/templates/vue-employee-system/package.json +19 -0
- package/templates/vue-employee-system/src/App.vue +388 -0
- package/templates/vue-employee-system/src/main.js +5 -0
- package/templates/vue-employee-system/src/stores/employeeStore.js +54 -0
- package/templates/vue-employee-system/vite.config.js +6 -0
- package/templates/vue-food-ordering/index.html +12 -0
- package/templates/vue-food-ordering/package.json +20 -0
- package/templates/vue-food-ordering/src/App.vue +109 -0
- package/templates/vue-food-ordering/src/main.js +6 -0
- package/templates/vue-food-ordering/src/router/index.js +17 -0
- package/templates/vue-food-ordering/src/stores/foodStore.js +65 -0
- package/templates/vue-food-ordering/src/views/MenuView.vue +145 -0
- package/templates/vue-food-ordering/src/views/OrderCartView.vue +181 -0
- package/templates/vue-food-ordering/src/views/OrderSuccessView.vue +93 -0
- package/templates/vue-food-ordering/vite.config.js +6 -0
- package/templates/vue-symposium-system/index.html +12 -0
- package/templates/vue-symposium-system/package.json +19 -0
- package/templates/vue-symposium-system/src/App.vue +297 -0
- package/templates/vue-symposium-system/src/main.js +5 -0
- package/templates/vue-symposium-system/src/stores/symposiumStore.js +44 -0
- package/templates/vue-symposium-system/vite.config.js +6 -0
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { reactive, computed } from 'vue'
|
|
3
|
+
import { useEmployeeStore } from './stores/employeeStore'
|
|
4
|
+
|
|
5
|
+
const store = useEmployeeStore()
|
|
6
|
+
|
|
7
|
+
const form = reactive({
|
|
8
|
+
id: '',
|
|
9
|
+
name: '',
|
|
10
|
+
department: 'Engineering',
|
|
11
|
+
experience: '',
|
|
12
|
+
baseSalary: ''
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
const formDepartments = computed(() => {
|
|
16
|
+
return store.departments.filter(dept => dept !== 'All')
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
const handleAddEmployee = () => {
|
|
20
|
+
if (!form.id || !form.name || !form.department || form.experience === '' || form.baseSalary === '') {
|
|
21
|
+
return
|
|
22
|
+
}
|
|
23
|
+
store.addEmployee({
|
|
24
|
+
id: Number(form.id),
|
|
25
|
+
name: form.name.trim(),
|
|
26
|
+
department: form.department,
|
|
27
|
+
experience: Number(form.experience),
|
|
28
|
+
baseSalary: Number(form.baseSalary)
|
|
29
|
+
})
|
|
30
|
+
form.id = ''
|
|
31
|
+
form.name = ''
|
|
32
|
+
form.department = 'Engineering'
|
|
33
|
+
form.experience = ''
|
|
34
|
+
form.baseSalary = ''
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const formatCurrency = (amount) => {
|
|
38
|
+
return Number(amount || 0).toLocaleString('en-US', {
|
|
39
|
+
minimumFractionDigits: 2,
|
|
40
|
+
maximumFractionDigits: 2
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
</script>
|
|
44
|
+
|
|
45
|
+
<template>
|
|
46
|
+
<div class="container">
|
|
47
|
+
<header class="header">
|
|
48
|
+
<h1>Employee Management System</h1>
|
|
49
|
+
</header>
|
|
50
|
+
|
|
51
|
+
<div class="summary-cards">
|
|
52
|
+
<div class="card">
|
|
53
|
+
<h3>Total Employees</h3>
|
|
54
|
+
<p class="card-value">{{ store.totalEmployees }}</p>
|
|
55
|
+
</div>
|
|
56
|
+
<div class="card">
|
|
57
|
+
<h3>Total Payroll ($)</h3>
|
|
58
|
+
<p class="card-value">${{ formatCurrency(store.totalPayroll) }}</p>
|
|
59
|
+
</div>
|
|
60
|
+
<div class="card">
|
|
61
|
+
<h3>Average Salary ($)</h3>
|
|
62
|
+
<p class="card-value">${{ formatCurrency(store.averageSalary) }}</p>
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
|
|
66
|
+
<section class="section form-section">
|
|
67
|
+
<h2>Add New Employee</h2>
|
|
68
|
+
<form @submit.prevent="handleAddEmployee" class="employee-form">
|
|
69
|
+
<div class="form-group">
|
|
70
|
+
<label for="emp-id">Employee ID</label>
|
|
71
|
+
<input
|
|
72
|
+
id="emp-id"
|
|
73
|
+
v-model="form.id"
|
|
74
|
+
type="number"
|
|
75
|
+
min="1"
|
|
76
|
+
placeholder="e.g. 5"
|
|
77
|
+
required
|
|
78
|
+
/>
|
|
79
|
+
</div>
|
|
80
|
+
<div class="form-group">
|
|
81
|
+
<label for="emp-name">Name</label>
|
|
82
|
+
<input
|
|
83
|
+
id="emp-name"
|
|
84
|
+
v-model="form.name"
|
|
85
|
+
type="text"
|
|
86
|
+
placeholder="e.g. Jane Doe"
|
|
87
|
+
required
|
|
88
|
+
/>
|
|
89
|
+
</div>
|
|
90
|
+
<div class="form-group">
|
|
91
|
+
<label for="emp-department">Department</label>
|
|
92
|
+
<select id="emp-department" v-model="form.department" required>
|
|
93
|
+
<option v-for="dept in formDepartments" :key="dept" :value="dept">
|
|
94
|
+
{{ dept }}
|
|
95
|
+
</option>
|
|
96
|
+
</select>
|
|
97
|
+
</div>
|
|
98
|
+
<div class="form-group">
|
|
99
|
+
<label for="emp-experience">Experience (Years)</label>
|
|
100
|
+
<input
|
|
101
|
+
id="emp-experience"
|
|
102
|
+
v-model="form.experience"
|
|
103
|
+
type="number"
|
|
104
|
+
min="0"
|
|
105
|
+
step="1"
|
|
106
|
+
placeholder="e.g. 3"
|
|
107
|
+
required
|
|
108
|
+
/>
|
|
109
|
+
</div>
|
|
110
|
+
<div class="form-group">
|
|
111
|
+
<label for="emp-salary">Base Salary ($)</label>
|
|
112
|
+
<input
|
|
113
|
+
id="emp-salary"
|
|
114
|
+
v-model="form.baseSalary"
|
|
115
|
+
type="number"
|
|
116
|
+
min="0"
|
|
117
|
+
step="0.01"
|
|
118
|
+
placeholder="e.g. 50000"
|
|
119
|
+
required
|
|
120
|
+
/>
|
|
121
|
+
</div>
|
|
122
|
+
<div class="form-actions">
|
|
123
|
+
<button type="submit" class="btn btn-primary">Add Employee</button>
|
|
124
|
+
</div>
|
|
125
|
+
</form>
|
|
126
|
+
</section>
|
|
127
|
+
|
|
128
|
+
<section class="section">
|
|
129
|
+
<div class="tabs-header">
|
|
130
|
+
<h2>Department Filter</h2>
|
|
131
|
+
<div class="filter-tabs">
|
|
132
|
+
<button
|
|
133
|
+
v-for="dept in store.departments"
|
|
134
|
+
:key="dept"
|
|
135
|
+
type="button"
|
|
136
|
+
class="tab-btn"
|
|
137
|
+
:class="{ active: store.selectedDept === dept }"
|
|
138
|
+
@click="store.setDepartment(dept)"
|
|
139
|
+
>
|
|
140
|
+
{{ dept }}
|
|
141
|
+
</button>
|
|
142
|
+
</div>
|
|
143
|
+
</div>
|
|
144
|
+
|
|
145
|
+
<div class="table-container">
|
|
146
|
+
<table class="employee-table">
|
|
147
|
+
<thead>
|
|
148
|
+
<tr>
|
|
149
|
+
<th>ID</th>
|
|
150
|
+
<th>Name</th>
|
|
151
|
+
<th>Department</th>
|
|
152
|
+
<th>Experience</th>
|
|
153
|
+
<th>Base Salary</th>
|
|
154
|
+
<th>Bonus</th>
|
|
155
|
+
<th>Tax</th>
|
|
156
|
+
<th>Net Salary</th>
|
|
157
|
+
<th>Actions</th>
|
|
158
|
+
</tr>
|
|
159
|
+
</thead>
|
|
160
|
+
<tbody>
|
|
161
|
+
<tr v-if="store.filteredEmployees.length === 0">
|
|
162
|
+
<td colspan="9" class="no-records">No employees found in this department.</td>
|
|
163
|
+
</tr>
|
|
164
|
+
<tr v-for="emp in store.filteredEmployees" :key="emp.id">
|
|
165
|
+
<td>{{ emp.id }}</td>
|
|
166
|
+
<td>{{ emp.name }}</td>
|
|
167
|
+
<td>{{ emp.department }}</td>
|
|
168
|
+
<td>{{ emp.experience }} yrs</td>
|
|
169
|
+
<td>${{ formatCurrency(emp.baseSalary) }}</td>
|
|
170
|
+
<td>${{ formatCurrency(store.calculateBonus(emp)) }}</td>
|
|
171
|
+
<td>${{ formatCurrency(store.calculateTax(emp)) }}</td>
|
|
172
|
+
<td>${{ formatCurrency(store.calculateNetSalary(emp)) }}</td>
|
|
173
|
+
<td>
|
|
174
|
+
<button
|
|
175
|
+
type="button"
|
|
176
|
+
class="btn btn-danger"
|
|
177
|
+
@click="store.deleteEmployee(emp.id)"
|
|
178
|
+
>
|
|
179
|
+
Delete
|
|
180
|
+
</button>
|
|
181
|
+
</td>
|
|
182
|
+
</tr>
|
|
183
|
+
</tbody>
|
|
184
|
+
</table>
|
|
185
|
+
</div>
|
|
186
|
+
</section>
|
|
187
|
+
</div>
|
|
188
|
+
</template>
|
|
189
|
+
|
|
190
|
+
<style>
|
|
191
|
+
* {
|
|
192
|
+
box-sizing: border-box;
|
|
193
|
+
margin: 0;
|
|
194
|
+
padding: 0;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
body {
|
|
198
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
|
|
199
|
+
background-color: #f7f9fc;
|
|
200
|
+
color: #333333;
|
|
201
|
+
padding: 24px 16px;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.container {
|
|
205
|
+
max-width: 1000px;
|
|
206
|
+
margin: 0 auto;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
.header {
|
|
210
|
+
margin-bottom: 24px;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
.header h1 {
|
|
214
|
+
font-size: 28px;
|
|
215
|
+
color: #1a202c;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
.summary-cards {
|
|
219
|
+
display: grid;
|
|
220
|
+
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
221
|
+
gap: 16px;
|
|
222
|
+
margin-bottom: 24px;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
.card {
|
|
226
|
+
background-color: #ffffff;
|
|
227
|
+
border: 1px solid #e2e8f0;
|
|
228
|
+
border-radius: 8px;
|
|
229
|
+
padding: 16px;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
.card h3 {
|
|
233
|
+
font-size: 14px;
|
|
234
|
+
font-weight: 500;
|
|
235
|
+
color: #718096;
|
|
236
|
+
margin-bottom: 8px;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
.card-value {
|
|
240
|
+
font-size: 24px;
|
|
241
|
+
font-weight: 700;
|
|
242
|
+
color: #2b6cb0;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
.section {
|
|
246
|
+
background-color: #ffffff;
|
|
247
|
+
border: 1px solid #e2e8f0;
|
|
248
|
+
border-radius: 8px;
|
|
249
|
+
padding: 20px;
|
|
250
|
+
margin-bottom: 24px;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
.section h2 {
|
|
254
|
+
font-size: 18px;
|
|
255
|
+
color: #2d3748;
|
|
256
|
+
margin-bottom: 16px;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
.employee-form {
|
|
260
|
+
display: grid;
|
|
261
|
+
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
|
|
262
|
+
gap: 12px;
|
|
263
|
+
align-items: flex-end;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
.form-group {
|
|
267
|
+
display: flex;
|
|
268
|
+
flex-direction: column;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
.form-group label {
|
|
272
|
+
font-size: 13px;
|
|
273
|
+
font-weight: 600;
|
|
274
|
+
color: #4a5568;
|
|
275
|
+
margin-bottom: 6px;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
.form-group input,
|
|
279
|
+
.form-group select {
|
|
280
|
+
padding: 8px 10px;
|
|
281
|
+
font-size: 14px;
|
|
282
|
+
border: 1px solid #cbd5e0;
|
|
283
|
+
border-radius: 4px;
|
|
284
|
+
background-color: #ffffff;
|
|
285
|
+
outline: none;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
.form-group input:focus,
|
|
289
|
+
.form-group select:focus {
|
|
290
|
+
border-color: #3182ce;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
.form-actions {
|
|
294
|
+
display: flex;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
.btn {
|
|
298
|
+
padding: 8px 16px;
|
|
299
|
+
font-size: 14px;
|
|
300
|
+
font-weight: 500;
|
|
301
|
+
border-radius: 4px;
|
|
302
|
+
border: none;
|
|
303
|
+
cursor: pointer;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
.btn-primary {
|
|
307
|
+
background-color: #3182ce;
|
|
308
|
+
color: #ffffff;
|
|
309
|
+
width: 100%;
|
|
310
|
+
height: 38px;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
.btn-primary:hover {
|
|
314
|
+
background-color: #2b6cb0;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
.btn-danger {
|
|
318
|
+
background-color: #e53e3e;
|
|
319
|
+
color: #ffffff;
|
|
320
|
+
padding: 4px 10px;
|
|
321
|
+
font-size: 12px;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
.btn-danger:hover {
|
|
325
|
+
background-color: #c53030;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
.tabs-header {
|
|
329
|
+
display: flex;
|
|
330
|
+
flex-wrap: wrap;
|
|
331
|
+
justify-content: space-between;
|
|
332
|
+
align-items: center;
|
|
333
|
+
gap: 12px;
|
|
334
|
+
margin-bottom: 16px;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
.filter-tabs {
|
|
338
|
+
display: flex;
|
|
339
|
+
flex-wrap: wrap;
|
|
340
|
+
gap: 8px;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
.tab-btn {
|
|
344
|
+
padding: 6px 14px;
|
|
345
|
+
font-size: 13px;
|
|
346
|
+
border: 1px solid #cbd5e0;
|
|
347
|
+
background-color: #edf2f7;
|
|
348
|
+
color: #4a5568;
|
|
349
|
+
border-radius: 4px;
|
|
350
|
+
cursor: pointer;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
.tab-btn.active {
|
|
354
|
+
background-color: #3182ce;
|
|
355
|
+
color: #ffffff;
|
|
356
|
+
border-color: #3182ce;
|
|
357
|
+
font-weight: 600;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
.table-container {
|
|
361
|
+
overflow-x: auto;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
.employee-table {
|
|
365
|
+
width: 100%;
|
|
366
|
+
border-collapse: collapse;
|
|
367
|
+
text-align: left;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
.employee-table th,
|
|
371
|
+
.employee-table td {
|
|
372
|
+
padding: 10px 12px;
|
|
373
|
+
border-bottom: 1px solid #e2e8f0;
|
|
374
|
+
font-size: 14px;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
.employee-table th {
|
|
378
|
+
background-color: #f7fafc;
|
|
379
|
+
color: #4a5568;
|
|
380
|
+
font-weight: 600;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
.no-records {
|
|
384
|
+
text-align: center;
|
|
385
|
+
color: #a0aec0;
|
|
386
|
+
padding: 24px;
|
|
387
|
+
}
|
|
388
|
+
</style>
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { defineStore } from 'pinia'
|
|
2
|
+
|
|
3
|
+
export const useEmployeeStore = defineStore('employee', {
|
|
4
|
+
state: () => ({
|
|
5
|
+
employees: [
|
|
6
|
+
{ id: 1, name: 'Alice Smith', department: 'Engineering', experience: 4, baseSalary: 60000 },
|
|
7
|
+
{ id: 2, name: 'Bob Jones', department: 'HR', experience: 2, baseSalary: 45000 },
|
|
8
|
+
{ id: 3, name: 'Charlie Brown', department: 'Marketing', experience: 5, baseSalary: 52000 },
|
|
9
|
+
{ id: 4, name: 'Diana Prince', department: 'Finance', experience: 1, baseSalary: 58000 }
|
|
10
|
+
],
|
|
11
|
+
departments: ['All', 'Engineering', 'HR', 'Marketing', 'Finance'],
|
|
12
|
+
selectedDept: 'All'
|
|
13
|
+
}),
|
|
14
|
+
getters: {
|
|
15
|
+
filteredEmployees(state) {
|
|
16
|
+
if (state.selectedDept === 'All') {
|
|
17
|
+
return state.employees
|
|
18
|
+
}
|
|
19
|
+
return state.employees.filter(emp => emp.department === state.selectedDept)
|
|
20
|
+
},
|
|
21
|
+
totalEmployees(state) {
|
|
22
|
+
return state.employees.length
|
|
23
|
+
},
|
|
24
|
+
totalPayroll() {
|
|
25
|
+
return this.employees.reduce((sum, emp) => sum + this.calculateNetSalary(emp), 0)
|
|
26
|
+
},
|
|
27
|
+
averageSalary() {
|
|
28
|
+
if (this.totalEmployees === 0) return 0
|
|
29
|
+
return this.totalPayroll / this.totalEmployees
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
actions: {
|
|
33
|
+
calculateBonus(emp) {
|
|
34
|
+
return emp.experience >= 3 ? emp.baseSalary * 0.10 : emp.baseSalary * 0.05
|
|
35
|
+
},
|
|
36
|
+
calculateTax(emp) {
|
|
37
|
+
return emp.baseSalary * 0.12
|
|
38
|
+
},
|
|
39
|
+
calculateNetSalary(emp) {
|
|
40
|
+
const bonus = this.calculateBonus(emp)
|
|
41
|
+
const tax = this.calculateTax(emp)
|
|
42
|
+
return emp.baseSalary + bonus - tax
|
|
43
|
+
},
|
|
44
|
+
addEmployee(emp) {
|
|
45
|
+
this.employees.push(emp)
|
|
46
|
+
},
|
|
47
|
+
deleteEmployee(id) {
|
|
48
|
+
this.employees = this.employees.filter(emp => emp.id !== id)
|
|
49
|
+
},
|
|
50
|
+
setDepartment(dept) {
|
|
51
|
+
this.selectedDept = dept
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
})
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>Food Ordering App</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="app"></div>
|
|
10
|
+
<script type="module" src="/src/main.js"></script>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "vue-food-ordering",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "vite",
|
|
8
|
+
"build": "vite build",
|
|
9
|
+
"preview": "vite preview"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"pinia": "^4.1.0",
|
|
13
|
+
"vue": "^3.5.44",
|
|
14
|
+
"vue-router": "^5.4.0"
|
|
15
|
+
},
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"@vitejs/plugin-vue": "^5.2.1",
|
|
18
|
+
"vite": "^6.2.0"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { useFoodStore } from './stores/foodStore'
|
|
3
|
+
|
|
4
|
+
const store = useFoodStore()
|
|
5
|
+
</script>
|
|
6
|
+
|
|
7
|
+
<template>
|
|
8
|
+
<div class="app-layout">
|
|
9
|
+
<header class="app-header">
|
|
10
|
+
<div class="header-inner">
|
|
11
|
+
<h1 class="logo">Food Ordering App</h1>
|
|
12
|
+
<nav class="nav-links">
|
|
13
|
+
<router-link to="/" class="nav-item">Menu</router-link>
|
|
14
|
+
<router-link to="/cart" class="nav-item cart-link">
|
|
15
|
+
Cart
|
|
16
|
+
<span v-if="store.cartCount > 0" class="cart-badge">{{ store.cartCount }}</span>
|
|
17
|
+
</router-link>
|
|
18
|
+
</nav>
|
|
19
|
+
</div>
|
|
20
|
+
</header>
|
|
21
|
+
|
|
22
|
+
<main class="main-content">
|
|
23
|
+
<router-view />
|
|
24
|
+
</main>
|
|
25
|
+
</div>
|
|
26
|
+
</template>
|
|
27
|
+
|
|
28
|
+
<style>
|
|
29
|
+
* {
|
|
30
|
+
box-sizing: border-box;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
body {
|
|
34
|
+
margin: 0;
|
|
35
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
|
|
36
|
+
color: #333;
|
|
37
|
+
background-color: #f4f6f9;
|
|
38
|
+
}
|
|
39
|
+
</style>
|
|
40
|
+
|
|
41
|
+
<style scoped>
|
|
42
|
+
.app-layout {
|
|
43
|
+
min-height: 100vh;
|
|
44
|
+
display: flex;
|
|
45
|
+
flex-direction: column;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.app-header {
|
|
49
|
+
background-color: #ffffff;
|
|
50
|
+
border-bottom: 1px solid #e0e0e0;
|
|
51
|
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.04);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.header-inner {
|
|
55
|
+
max-width: 1000px;
|
|
56
|
+
margin: 0 auto;
|
|
57
|
+
padding: 1rem 1.5rem;
|
|
58
|
+
display: flex;
|
|
59
|
+
justify-content: space-between;
|
|
60
|
+
align-items: center;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.logo {
|
|
64
|
+
margin: 0;
|
|
65
|
+
font-size: 1.4rem;
|
|
66
|
+
color: #222;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.nav-links {
|
|
70
|
+
display: flex;
|
|
71
|
+
gap: 1.5rem;
|
|
72
|
+
align-items: center;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.nav-item {
|
|
76
|
+
text-decoration: none;
|
|
77
|
+
color: #555;
|
|
78
|
+
font-weight: 500;
|
|
79
|
+
position: relative;
|
|
80
|
+
display: inline-flex;
|
|
81
|
+
align-items: center;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.nav-item.router-link-exact-active {
|
|
85
|
+
color: #007bff;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.cart-badge {
|
|
89
|
+
background-color: #dc3545;
|
|
90
|
+
color: #fff;
|
|
91
|
+
border-radius: 50%;
|
|
92
|
+
font-size: 0.75rem;
|
|
93
|
+
min-width: 18px;
|
|
94
|
+
height: 18px;
|
|
95
|
+
display: inline-flex;
|
|
96
|
+
align-items: center;
|
|
97
|
+
justify-content: center;
|
|
98
|
+
padding: 0 4px;
|
|
99
|
+
margin-left: 0.4rem;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.main-content {
|
|
103
|
+
max-width: 1000px;
|
|
104
|
+
width: 100%;
|
|
105
|
+
margin: 0 auto;
|
|
106
|
+
padding: 1.5rem;
|
|
107
|
+
flex: 1;
|
|
108
|
+
}
|
|
109
|
+
</style>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { createRouter, createWebHistory } from 'vue-router'
|
|
2
|
+
import MenuView from '../views/MenuView.vue'
|
|
3
|
+
import OrderCartView from '../views/OrderCartView.vue'
|
|
4
|
+
import OrderSuccessView from '../views/OrderSuccessView.vue'
|
|
5
|
+
|
|
6
|
+
const routes = [
|
|
7
|
+
{ path: '/', name: 'Menu', component: MenuView },
|
|
8
|
+
{ path: '/cart', name: 'OrderCart', component: OrderCartView },
|
|
9
|
+
{ path: '/order-success', name: 'OrderSuccess', component: OrderSuccessView }
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
const router = createRouter({
|
|
13
|
+
history: createWebHistory(),
|
|
14
|
+
routes
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
export default router
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { defineStore } from 'pinia'
|
|
2
|
+
|
|
3
|
+
export const useFoodStore = defineStore('food', {
|
|
4
|
+
state: () => ({
|
|
5
|
+
categories: ['All', 'Starters', 'Main Course', 'Beverages', 'Desserts'],
|
|
6
|
+
menuItems: [
|
|
7
|
+
{ id: 1, name: 'Spring Rolls', category: 'Starters', price: 5.99, description: 'Crispy vegetable rolls served with sweet chili sauce' },
|
|
8
|
+
{ id: 2, name: 'Garlic Bread', category: 'Starters', price: 4.49, description: 'Toasted baguette slices with garlic butter and herbs' },
|
|
9
|
+
{ id: 3, name: 'Grilled Chicken Burger', category: 'Main Course', price: 11.99, description: 'Juicy chicken breast with lettuce, tomato, and special sauce' },
|
|
10
|
+
{ id: 4, name: 'Pasta Primavera', category: 'Main Course', price: 13.50, description: 'Fettuccine tossed with fresh garden vegetables and parmesan' },
|
|
11
|
+
{ id: 5, name: 'Margherita Pizza', category: 'Main Course', price: 12.99, description: 'Classic cheese and tomato pizza with basil leaves' },
|
|
12
|
+
{ id: 6, name: 'Iced Lemon Tea', category: 'Beverages', price: 3.25, description: 'Refreshing brewed tea with lemon and mint' },
|
|
13
|
+
{ id: 7, name: 'Berry Smoothie', category: 'Beverages', price: 4.95, description: 'Blended mixed berries with yogurt and honey' },
|
|
14
|
+
{ id: 8, name: 'Chocolate Lava Cake', category: 'Desserts', price: 6.50, description: 'Warm molten chocolate cake with vanilla ice cream' }
|
|
15
|
+
],
|
|
16
|
+
cart: []
|
|
17
|
+
}),
|
|
18
|
+
getters: {
|
|
19
|
+
cartCount: (state) => state.cart.reduce((sum, item) => sum + item.quantity, 0),
|
|
20
|
+
cartSubtotal: (state) => Number(state.cart.reduce((sum, item) => sum + item.price * item.quantity, 0).toFixed(2)),
|
|
21
|
+
taxAmount: (state) => Number((state.cart.reduce((sum, item) => sum + item.price * item.quantity, 0) * 0.05).toFixed(2)),
|
|
22
|
+
totalBill: (state) => {
|
|
23
|
+
const subtotal = state.cart.reduce((sum, item) => sum + item.price * item.quantity, 0)
|
|
24
|
+
const tax = subtotal * 0.05
|
|
25
|
+
return Number((subtotal + tax).toFixed(2))
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
actions: {
|
|
29
|
+
addToCart(item) {
|
|
30
|
+
const existing = this.cart.find((i) => i.id === item.id)
|
|
31
|
+
if (existing) {
|
|
32
|
+
existing.quantity += 1
|
|
33
|
+
} else {
|
|
34
|
+
this.cart.push({
|
|
35
|
+
id: item.id,
|
|
36
|
+
name: item.name,
|
|
37
|
+
price: item.price,
|
|
38
|
+
quantity: 1
|
|
39
|
+
})
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
incrementQuantity(id) {
|
|
43
|
+
const item = this.cart.find((i) => i.id === id)
|
|
44
|
+
if (item) {
|
|
45
|
+
item.quantity += 1
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
decrementQuantity(id) {
|
|
49
|
+
const item = this.cart.find((i) => i.id === id)
|
|
50
|
+
if (item) {
|
|
51
|
+
if (item.quantity > 1) {
|
|
52
|
+
item.quantity -= 1
|
|
53
|
+
} else {
|
|
54
|
+
this.removeFromCart(id)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
removeFromCart(id) {
|
|
59
|
+
this.cart = this.cart.filter((i) => i.id !== id)
|
|
60
|
+
},
|
|
61
|
+
clearCart() {
|
|
62
|
+
this.cart = []
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
})
|