added ready player page

This commit is contained in:
mi1468 2025-05-10 13:57:19 +03:30
parent 74afb1f48d
commit b30442dfc9
3 changed files with 471 additions and 1 deletions

View File

@ -99,7 +99,8 @@ export default {
},
profileImageUrl() {
if (!this.customer?.profile_img) return this.defaultProfileImage;
return `http://194.62.43.230:8000/media/${this.customer.profile_img}`;
return `${this.customer.profile_img}`;
// return `http://194.62.43.230:8000/media/${this.customer.profile_img}`;
}
},
methods: {

View File

@ -0,0 +1,463 @@
<template>
<SidebarMenu />
<div class="dashboard-page">
<div class="content">
<!-- Header -->
<div class="header-row">
<div class="right-actions">
<button class="subscription-button">
<img src="https://c.animaapp.com/m9nvumalUMfQbN/img/frame-6.svg" class="button-icon" />
خرید اشتراک
</button>
</div>
<div class="user-info">
<span class="user-name">{{ userData.user.first_name }} {{ userData.user.last_name }}</span>
<div class="avatar-box">
<img class="avatar-icon" src="https://c.animaapp.com/m9nvumalUMfQbN/img/frame.svg" />
</div>
</div>
</div>
<div class="page-title">ساخت آواتار جدید</div>
<!-- Main Content -->
<div class="profile-edit-container">
<div class="column">
<div class="form-section">
<h3>آواتار واقعیت مجازی شما</h3>
<p class="section-description">
میتوانید با استفاده از ابزار Ready Player Me آواتار خود را شخصیسازی کنید.
</p>
<div v-if="avatarUrl" class="avatar-preview">
<img :src="getAvatarThumbnail(avatarUrl)" class="avatar-image" />
<p class="avatar-url">آواتار شما با موفقیت ایجاد شد</p>
</div>
<div v-else class="avatar-placeholder">
<p>برای ساخت آواتار روی دکمه زیر کلیک کنید</p>
</div>
<button class="save-btn" @click="openAvatarEditor" :disabled="saving">
{{ saving ? 'در حال ذخیره...' : avatarUrl ? 'ویرایش آواتار' : 'ساخت آواتار' }}
</button>
</div>
</div>
<div class="column">
<div class="form-section instructions">
<h3>راهنمای ساخت آواتار</h3>
<ul class="instruction-list">
<li>روی دکمه "ساخت آواتار" کلیک کنید</li>
<li>در پنجره باز شده، آواتار خود را شخصیسازی کنید</li>
<li>پس از اتمام، روی دکمه "Done" کلیک کنید</li>
<li>آواتار شما ذخیره شده و در این صفحه نمایش داده میشود</li>
</ul>
<div v-if="avatarUrl" class="form-section">
<h3>آواتارهای پیشفرض</h3>
<div class="default-avatars">
<h4>مردان</h4>
<div class="avatar-grid">
<div
v-for="avatar in maleAvatars"
:key="avatar.id"
class="avatar-option"
@click="selectAvatar(avatar)"
>
<img :src="getAvatarThumbnail(avatar.src)" class="avatar-thumbnail" />
<span>{{ avatar.name }}</span>
</div>
</div>
<h4>زنان</h4>
<div class="avatar-grid">
<div
v-for="avatar in femaleAvatars"
:key="avatar.id"
class="avatar-option"
@click="selectAvatar(avatar)"
>
<img :src="getAvatarThumbnail(avatar.src)" class="avatar-thumbnail" />
<span>{{ avatar.name }}</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Hidden iframe for Ready Player Me -->
<iframe
id="frame"
class="rpm-frame"
:src="iframeSrc"
allow="camera *; microphone *; clipboard-write"
hidden
></iframe>
</div>
</div>
</template>
<script>
import SidebarMenu from '@/components/SidebarMenu.vue'
import axios from '@/axios';
export default {
name: 'ChangeAvatar',
components: {
SidebarMenu
},
data() {
return {
subdomain: 'xroom',
avatarUrl: '',
frame: null,
saving: false,
userData: {
user: { first_name: '', last_name: '' }
},
maleAvatars: [
{ id: 1, name: 'مرد ۱', src: 'http://194.62.43.230:8000/media/2025/5/5/men1.glb' },
{ id: 2, name: 'مرد ۲', src: 'http://194.62.43.230:8000/media/2025/5/5/men1.glb' },
{ id: 7, name: 'مرد ۳', src: 'http://194.62.43.230:8000/media/2025/5/5/men1.glb' },
],
femaleAvatars: [
{ id: 4, name: 'زن ۱', src: 'http://194.62.43.230:8000/media/2025/5/5/men1.glb' },
{ id: 10, name: 'زن ۳', src: 'http://194.62.43.230:8000/media/2025/5/5/men1.glb' },
],
baseUrl: 'http://194.62.43.230:8000'
};
},
computed: {
iframeSrc() {
return `https://${this.subdomain}.readyplayer.me/avatar?frameApi`;
}
},
mounted() {
this.frame = document.getElementById('frame');
window.addEventListener('message', this.subscribe);
document.addEventListener('message', this.subscribe);
// Load existing avatar if available
this.loadUserData();
},
beforeUnmount() {
window.removeEventListener('message', this.subscribe);
document.removeEventListener('message', this.subscribe);
},
methods: {
async saveAvatarUrl(glbUrl) {
try {
const customer = JSON.parse(localStorage.getItem('customer') || '{}');
const payload = {
profile_glb_url: glbUrl
};
// If profile_img is null, set it to the thumbnail of the GLB
if (!customer.profile_img) {
payload.profile_img = this.getAvatarThumbnail(glbUrl);
}
const response = await axios.post(`${this.baseUrl}/editProfile/`, payload, {
headers: {
'Content-Type': 'application/json'
}
});
// Update local storage
if (!customer.profile_img) {
customer.profile_img = payload.profile_img;
}
customer.profile_glb_url = glbUrl;
localStorage.setItem('customer', JSON.stringify(customer));
return response.data;
} catch (error) {
console.error('Error saving avatar URL:', error);
throw error;
}
},
getAvatarThumbnail(glbUrl) {
// Replace .glb with .png for thumbnail
// Adjust this based on your actual thumbnail URLs
if (glbUrl.includes('readyplayer.me')) {
// For Ready Player Me avatars, use their thumbnail service
return glbUrl.replace('.glb', '.png');
}
return glbUrl.replace('.glb', '.png') || 'https://i.imgur.com/QbXfV6C.png';
},
loadUserData() {
const customer = JSON.parse(localStorage.getItem('customer') || '{}');
if (customer.profile_glb_url) {
this.avatarUrl = customer.profile_glb_url;
}
},
async subscribe(event) {
const json = this.parse(event);
if (json?.source !== 'readyplayerme') {
return;
}
if (json.eventName === 'v1.frame.ready') {
this.frame.contentWindow.postMessage(
JSON.stringify({
target: 'readyplayerme',
type: 'subscribe',
eventName: 'v1.**'
}),
'*'
);
}
if (json.eventName === 'v1.avatar.exported') {
this.saving = true;
try {
console.log(`Avatar URL: ${json.data.url}`);
this.avatarUrl = json.data.url;
this.frame.hidden = true;
// Save the avatar URL to your backend
await this.saveAvatarUrl(json.data.url);
alert('آواتار با موفقیت ذخیره شد');
} catch (error) {
console.error('Error saving avatar:', error);
alert('خطا در ذخیره آواتار');
} finally {
this.saving = false;
}
}
},
parse(event) {
try {
return JSON.parse(event.data);
} catch (error) {
return null;
}
},
openAvatarEditor() {
this.frame.hidden = false;
},
async selectAvatar(avatar) {
this.saving = true;
try {
await this.saveAvatarUrl(avatar.src);
this.avatarUrl = avatar.src;
alert('آواتار پیش‌فرض با موفقیت انتخاب شد');
} catch (error) {
console.error('Error selecting avatar:', error);
alert(error.response?.data?.detail || error.message || 'خطا در انتخاب آواتار');
} finally {
this.saving = false;
}
},
async fetchUserData() {
try {
const response = await axios.get('/getInfo');
this.userData = response.data;
} catch (error) {
console.error('Error fetching user data:', error);
}
}
}
};
</script>
<style scoped>
/* Previous styles remain the same, add these new styles */
.default-avatars {
margin-top: 20px;
}
.avatar-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 15px;
margin: 10px 0 20px;
}
.avatar-option {
display: flex;
flex-direction: column;
align-items: center;
cursor: pointer;
padding: 10px;
border-radius: 8px;
transition: background-color 0.2s;
}
.avatar-option:hover {
background-color: #f0f0f0;
}
.avatar-thumbnail {
width: 80px;
height: 80px;
border-radius: 8px;
object-fit: cover;
margin-bottom: 5px;
}
/* Rest of your existing styles... */
.dashboard-page {
margin-right: 360px;
padding: 20px;
direction: rtl;
font-family: IRANSansXFaNum, sans-serif;
}
.content {
background-color: #f8f9fa;
border-radius: 20px;
padding: 35px 80px;
display: flex;
flex-direction: column;
gap: 32px;
}
.page-title {
font-size: 22px;
font-weight: bold;
margin: 24px 0;
color: #333;
}
.profile-edit-container {
display: flex;
gap: 32px;
flex-direction: row-reverse;
}
.column {
flex: 1;
}
.form-section {
background: #fff;
border-radius: 12px;
padding: 24px;
margin-bottom: 24px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
}
.form-section h3 {
margin-bottom: 8px;
color: #222;
font-size: 18px;
}
.section-description {
color: #777;
font-size: 14px;
margin-bottom: 12px;
}
.avatar-image {
width: 200px;
height: 200px;
border-radius: 12px;
margin: 16px 0;
object-fit: cover;
}
.avatar-placeholder {
padding: 40px;
text-align: center;
color: #777;
background-color: #f5f5f5;
border-radius: 8px;
margin: 16px 0;
}
.avatar-url {
color: #4CAF50;
font-size: 14px;
margin-top: 8px;
}
.save-btn {
background: #3a57e8;
color: #fff;
border: none;
padding: 10px 24px;
border-radius: 8px;
font-weight: 500;
cursor: pointer;
margin-top: 8px;
}
.save-btn:disabled {
background-color: #ccc;
cursor: not-allowed;
}
.instruction-list {
padding-right: 20px;
color: #555;
}
.instruction-list li {
margin-bottom: 8px;
}
.rpm-frame {
width: 100%;
height: 600px;
border: none;
border-radius: 12px;
}
.header-row {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.user-info {
display: flex;
align-items: center;
gap: 12px;
}
.user-name {
font-weight: 500;
}
.avatar-box {
width: 40px;
height: 40px;
border-radius: 50%;
overflow: hidden;
}
.avatar-icon {
width: 100%;
height: 100%;
object-fit: cover;
}
.subscription-button {
display: flex;
align-items: center;
gap: 8px;
background: #3a57e8;
color: white;
border: none;
padding: 8px 16px;
border-radius: 8px;
cursor: pointer;
}
.button-icon {
width: 16px;
height: 16px;
}
</style>

View File

@ -40,6 +40,12 @@ const routes = [
component: () => import('@/pages/dashboard/EditProfile.vue'),
meta: { requiresAuth: true }
},
{
path: '/dashboard/readyPlayer',
name: 'ReadyPlayer',
component: () => import('@/pages/dashboard/readyPlayer.vue'),
meta: { requiresAuth: true }
},
{
path: '/dashboard/ChangeAvatar',
name: 'ChangeAvatar',