feat: add user and admin page
This commit is contained in:
33
frontend/assets/js/admin.js
Normal file
33
frontend/assets/js/admin.js
Normal file
@@ -0,0 +1,33 @@
|
||||
import { adminGetItems, createItem, deleteItem } from './api.js';
|
||||
|
||||
const list = document.getElementById('items-list');
|
||||
const input = document.getElementById('new-item');
|
||||
const addBtn = document.getElementById('add-btn');
|
||||
|
||||
async function renderItems() {
|
||||
const items = await adminGetItems();
|
||||
list.innerHTML = '';
|
||||
items.forEach(item => {
|
||||
const li = document.createElement('li');
|
||||
li.innerHTML = `
|
||||
<span>${item.title}</span>
|
||||
<button onclick="removeItem(${item.id})">حذف</button>
|
||||
`;
|
||||
list.appendChild(li);
|
||||
});
|
||||
}
|
||||
|
||||
window.removeItem = async (id) => {
|
||||
await deleteItem(id);
|
||||
renderItems();
|
||||
};
|
||||
|
||||
addBtn.onclick = async () => {
|
||||
const title = input.value.trim();
|
||||
if (!title) return;
|
||||
await createItem(title);
|
||||
input.value = '';
|
||||
renderItems();
|
||||
};
|
||||
|
||||
renderItems();
|
||||
20
frontend/assets/js/api.js
Normal file
20
frontend/assets/js/api.js
Normal file
@@ -0,0 +1,20 @@
|
||||
const BASE = 'http://127.0.0.1:8000';
|
||||
|
||||
export const getItems = () =>
|
||||
fetch(`${BASE}/user/items`).then(r => r.json());
|
||||
|
||||
export const spinWheel = () =>
|
||||
fetch(`${BASE}/user/spin`, { method: 'POST' }).then(r => r.json());
|
||||
|
||||
export const adminGetItems = () =>
|
||||
fetch(`${BASE}/admin/items`).then(r => r.json());
|
||||
|
||||
export const createItem = (title) =>
|
||||
fetch(`${BASE}/admin/items`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ title })
|
||||
}).then(r => r.json());
|
||||
|
||||
export const deleteItem = (id) =>
|
||||
fetch(`${BASE}/admin/items/${id}`, { method: 'DELETE' }).then(r => r.json());
|
||||
82
frontend/assets/js/wheel.js
Normal file
82
frontend/assets/js/wheel.js
Normal file
@@ -0,0 +1,82 @@
|
||||
import { getItems, spinWheel } from './api.js';
|
||||
|
||||
const canvas = document.getElementById('wheel');
|
||||
const ctx = canvas.getContext('2d');
|
||||
const btn = document.getElementById('spin-btn');
|
||||
const result = document.getElementById('result');
|
||||
const colors = ['#e74c3c','#3498db','#2ecc71','#f39c12','#9b59b6','#1abc9c'];
|
||||
|
||||
let items = [];
|
||||
let angle = 0;
|
||||
let spinning = false;
|
||||
|
||||
async function init() {
|
||||
items = await getItems();
|
||||
drawWheel(angle);
|
||||
}
|
||||
|
||||
function drawWheel(rotation) {
|
||||
if (items.length === 0) return;
|
||||
const cx = canvas.width / 2;
|
||||
const cy = canvas.height / 2;
|
||||
const r = cx - 10;
|
||||
const slice = (2 * Math.PI) / items.length;
|
||||
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
items.forEach((item, i) => {
|
||||
const start = rotation + i * slice;
|
||||
const end = start + slice;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(cx, cy);
|
||||
ctx.arc(cx, cy, r, start, end);
|
||||
ctx.fillStyle = colors[i % colors.length];
|
||||
ctx.fill();
|
||||
ctx.stroke();
|
||||
ctx.save();
|
||||
ctx.translate(cx, cy);
|
||||
ctx.rotate(start + slice / 2);
|
||||
ctx.textAlign = 'right';
|
||||
ctx.fillStyle = '#fff';
|
||||
ctx.font = 'bold 16px Tahoma';
|
||||
ctx.fillText(item.title, r - 10, 5);
|
||||
ctx.restore();
|
||||
});
|
||||
}
|
||||
|
||||
btn.onclick = async () => {
|
||||
if (spinning || items.length === 0) return;
|
||||
spinning = true;
|
||||
btn.disabled = true;
|
||||
result.textContent = '';
|
||||
|
||||
const data = await spinWheel();
|
||||
const winner = data.winner;
|
||||
const winnerIdx = items.findIndex(i => i.id === winner.id);
|
||||
const slice = (2 * Math.PI) / items.length;
|
||||
const targetAngle = angle + (Math.PI * 2 * 5) +
|
||||
(Math.PI * 2 - (winnerIdx * slice + slice / 2));
|
||||
|
||||
const duration = 4000;
|
||||
const start = performance.now();
|
||||
const from = angle;
|
||||
|
||||
function animate(now) {
|
||||
const elapsed = now - start;
|
||||
const t = Math.min(elapsed / duration, 1);
|
||||
const ease = 1 - Math.pow(1 - t, 3);
|
||||
angle = from + (targetAngle - from) * ease;
|
||||
drawWheel(angle);
|
||||
|
||||
if (t < 1) {
|
||||
requestAnimationFrame(animate);
|
||||
} else {
|
||||
spinning = false;
|
||||
btn.disabled = false;
|
||||
result.textContent = `🎉 ${winner.title}`;
|
||||
}
|
||||
}
|
||||
|
||||
requestAnimationFrame(animate);
|
||||
};
|
||||
|
||||
init();
|
||||
Reference in New Issue
Block a user