小程序初始化

This commit is contained in:
2026-02-27 11:32:50 +08:00
parent 93574dbb45
commit 97e1ea2706
252 changed files with 32427 additions and 12363 deletions
+207
View File
@@ -0,0 +1,207 @@
<script setup>
import { ref } from 'vue'
import { onLaunch, onShow, onHide } from '@dcloudio/uni-app'
import { useAppStore } from './stores/app.js'
const statusBarHeight = ref(0)
const safeAreaTop = ref(0)
const safeAreaBottom = ref(0)
onLaunch(async () => {
console.log('App Launch')
const store = useAppStore()
await store.initialize()
const systemInfo = uni.getSystemInfoSync()
statusBarHeight.value = systemInfo.statusBarHeight || 20
safeAreaTop.value = systemInfo.safeAreaInsets?.top || systemInfo.statusBarHeight || 20
safeAreaBottom.value = systemInfo.safeAreaInsets?.bottom || 0
uni.setStorageSync('statusBarHeight', statusBarHeight.value)
uni.setStorageSync('safeAreaTop', safeAreaTop.value)
uni.setStorageSync('safeAreaBottom', safeAreaBottom.value)
})
onShow(() => {
console.log('App Show')
})
onHide(() => {
console.log('App Hide')
})
</script>
<style>
page {
background-color: #0F071A;
color: #F3E8FF;
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
padding-top: 0;
padding-bottom: 0;
}
.status-bar-space {
width: 100%;
background: transparent;
flex-shrink: 0;
}
.safe-area-bottom {
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
}
.safe-area-top {
padding-top: constant(safe-area-inset-top);
padding-top: env(safe-area-inset-top);
}
.glass-card {
background: rgba(168, 85, 247, 0.05);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border: 1px solid rgba(168, 85, 247, 0.15);
border-radius: 24rpx;
}
.glass-card-gold {
background: linear-gradient(135deg, rgba(168, 85, 247, 0.15), rgba(232, 121, 249, 0.1));
border: 1px solid rgba(168, 85, 247, 0.3);
border-radius: 32rpx;
}
.glass-input {
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 24rpx;
padding: 24rpx 32rpx;
color: #F3E8FF;
font-size: 28rpx;
}
.glass-input::placeholder {
color: rgba(255, 255, 255, 0.3);
}
.btn-primary {
background: linear-gradient(135deg, #9333EA 0%, #7C3AED 100%);
border-radius: 32rpx;
padding: 28rpx 48rpx;
color: white;
font-weight: 600;
font-size: 30rpx;
border: none;
box-shadow: 0 8rpx 32rpx rgba(168, 85, 247, 0.3);
}
.btn-primary:active {
transform: scale(0.98);
opacity: 0.9;
}
.btn-secondary {
background: rgba(255, 255, 255, 0.08);
border: 1px solid rgba(255, 255, 255, 0.15);
border-radius: 24rpx;
padding: 24rpx 40rpx;
color: rgba(255, 255, 255, 0.8);
font-size: 28rpx;
}
.bottom-nav {
background: rgba(15, 7, 26, 0.85);
backdrop-filter: blur(40rpx);
-webkit-backdrop-filter: blur(40rpx);
border-top: 1px solid rgba(255, 255, 255, 0.05);
}
.nav-item {
color: rgba(255, 255, 255, 0.4);
transition: all 0.3s ease;
}
.nav-item.active {
color: #C084FC;
transform: translateY(-8rpx);
}
.step-indicator {
display: flex;
gap: 12rpx;
}
.step-dot {
width: 24rpx;
height: 8rpx;
background: rgba(255, 255, 255, 0.1);
border-radius: 4rpx;
transition: all 0.4s ease;
}
.step-dot.active {
width: 40rpx;
background: #A855F7;
box-shadow: 0 0 24rpx rgba(168, 85, 247, 0.6);
}
.hint-chip {
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 32rpx;
padding: 12rpx 24rpx;
font-size: 22rpx;
color: rgba(243, 232, 255, 0.8);
}
.hint-chip:active {
background: rgba(168, 85, 247, 0.2);
border-color: rgba(168, 85, 247, 0.4);
}
::-webkit-scrollbar {
width: 4rpx;
}
::-webkit-scrollbar-thumb {
background: rgba(168, 85, 247, 0.3);
border-radius: 10rpx;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(20rpx); }
to { opacity: 1; transform: translateY(0); }
}
.animate-fade-in {
animation: fadeIn 0.5s ease-out;
}
@keyframes pulse {
0%, 100% { opacity: 0.3; }
50% { opacity: 0.7; }
}
.animate-pulse-slow {
animation: pulse 6s ease-in-out infinite;
}
.text-primary { color: #A855F7; }
.text-primary-light { color: #C084FC; }
.text-accent { color: #E879F9; }
.text-muted { color: rgba(255, 255, 255, 0.4); }
.font-serif { font-family: 'Cinzel', serif; }
.bg-dark { background-color: #0F071A; }
.bg-gradient-purple {
background: linear-gradient(180deg, #1A0B2E 0%, #0F071A 50%, #050208 100%);
}
.safe-area-bottom {
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
}
.safe-area-top {
padding-top: constant(safe-area-inset-top);
padding-top: env(safe-area-inset-top);
}
</style>
+84
View File
@@ -0,0 +1,84 @@
const ENV_TYPE = {
DEV: 'dev',
TEST: 'test',
PROD: 'prod'
}
const normalizeEnv = (value) => {
if (!value) return ENV_TYPE.DEV
if (value === 'development' || value === 'dev') return ENV_TYPE.DEV
if (value === 'production' || value === 'prod') return ENV_TYPE.PROD
if (value === 'test') return ENV_TYPE.TEST
return ENV_TYPE.DEV
}
const currentEnv = normalizeEnv(import.meta.env.VITE_APP_ENV || import.meta.env.MODE)
const envConfig = {
[ENV_TYPE.DEV]: {
API_BASE_URL: 'http://localhost:19089',
WS_URL: 'ws://localhost:19089',
DEBUG: true
},
[ENV_TYPE.TEST]: {
API_BASE_URL: 'http://101.200.208.45:19089/api',
WS_URL: 'ws://101.200.208.45:19089',
DEBUG: true
},
[ENV_TYPE.PROD]: {
API_BASE_URL: 'http://101.200.208.45:19089/api',
WS_URL: 'ws://101.200.208.45:19089',
DEBUG: false
}
}
const getConfig = () => {
const base = envConfig[currentEnv] || envConfig[ENV_TYPE.DEV]
const apiBaseUrl = import.meta.env.VITE_API_BASE_URL || base.API_BASE_URL
const wsUrl = import.meta.env.VITE_WS_URL || base.WS_URL
const debug = import.meta.env.VITE_DEBUG !== undefined
? import.meta.env.VITE_DEBUG === 'true'
: base.DEBUG
return {
API_BASE_URL: apiBaseUrl,
WS_URL: wsUrl,
DEBUG: debug
}
}
const getEnvValue = (key) => {
const config = getConfig()
return config[key]
}
const isDev = () => {
return currentEnv === ENV_TYPE.DEV
}
const isTest = () => {
return currentEnv === ENV_TYPE.TEST
}
const isProd = () => {
return currentEnv === ENV_TYPE.PROD
}
export {
ENV_TYPE,
currentEnv,
getConfig,
getEnvValue,
isDev,
isTest,
isProd
}
export default {
ENV_TYPE,
currentEnv,
getConfig,
getEnvValue,
isDev,
isTest,
isProd
}
+7
View File
@@ -0,0 +1,7 @@
import App from './App.vue'
import { createSSRApp } from 'vue'
export function createApp() {
const app = createSSRApp(App)
return { app }
}
+41
View File
@@ -0,0 +1,41 @@
{
"name" : "EmotionMuseum",
"appid" : "",
"description" : "",
"versionName" : "1.0.0",
"versionCode" : "100",
"uni-app-x" : {},
"quickapp" : {},
"mp-weixin" : {
"appid" : "wxaf2eaba72d28f0e4",
"setting" : {
"urlCheck" : false
},
"usingComponents" : true
},
"mp-alipay" : {
"usingComponents" : true
},
"mp-baidu" : {
"usingComponents" : true
},
"mp-toutiao" : {
"usingComponents" : true
},
"uniStatistics" : {
"enable" : false
},
"vueVersion" : "3",
"app" : {
"distribute" : {
"icons" : {
"android" : {
"hdpi" : "",
"xhdpi" : "",
"xxhdpi" : "",
"xxxhdpi" : ""
}
}
}
}
}
+41
View File
@@ -0,0 +1,41 @@
{
"pages": [
{
"path": "pages/splash/index",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/login/index",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/onboarding/index",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/main/index",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/profile/index",
"style": {
"navigationBarTitleText": "个人中心"
}
}
],
"globalStyle": {
"navigationBarTextStyle": "white",
"navigationBarTitleText": "人生OS",
"navigationBarBackgroundColor": "#0F071A",
"backgroundColor": "#0F071A"
},
"uniIdRouter": {}
}
+343
View File
@@ -0,0 +1,343 @@
<template>
<view class="login-page">
<view class="status-bar" :style="{ height: statusBarHeight + 'px' }"></view>
<view class="bg-decoration">
<view class="aurora-top"></view>
<view class="aurora-bottom"></view>
</view>
<view class="content" :style="{ paddingTop: safeAreaTop + 20 + 'px' }">
<view class="login-card">
<view class="header">
<text class="title font-serif">欢迎回来</text>
<text class="subtitle">开启你的数字生命档案</text>
</view>
<view class="form">
<view class="input-group">
<text class="input-label">手机号码</text>
<input
class="glass-input"
type="number"
maxlength="11"
placeholder="输入手机号"
v-model="phone"
/>
</view>
<view class="input-group">
<text class="input-label">验证码</text>
<view class="code-row">
<input
class="glass-input code-input"
type="number"
maxlength="6"
placeholder="请输入验证码"
v-model="code"
/>
<view
class="code-btn"
:class="{ disabled: isCountingDown || phone.length !== 11 }"
@click="handleGetCode"
>
<text v-if="isCountingDown" class="countdown">{{ countdown }}s</text>
<text v-else>获取验证码</text>
</view>
</view>
</view>
</view>
<view
class="btn-primary login-btn"
:class="{ disabled: !canSubmit || loading }"
@click="handleLogin"
>
<text v-if="loading">登录中...</text>
<text v-else>开启旅程</text>
</view>
<text class="agreement">
登录即代表同意用户协议隐私政策
</text>
</view>
</view>
</view>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
import { useAppStore } from '../../stores/app.js'
import { getSmsCode } from '../../services/auth.js'
const store = useAppStore()
const statusBarHeight = ref(uni.getStorageSync('statusBarHeight') || 20)
const safeAreaTop = ref(uni.getStorageSync('safeAreaTop') || 20)
const safeAreaBottom = ref(uni.getStorageSync('safeAreaBottom') || 0)
onMounted(() => {
const systemInfo = uni.getSystemInfoSync()
statusBarHeight.value = systemInfo.statusBarHeight || 20
safeAreaTop.value = systemInfo.safeAreaInsets?.top || systemInfo.statusBarHeight || 20
safeAreaBottom.value = systemInfo.safeAreaInsets?.bottom || 0
})
const phone = ref('')
const code = ref('')
const loading = ref(false)
const countdown = ref(60)
const isCountingDown = ref(false)
const canSubmit = computed(() => {
return phone.value.length === 11 && code.value.length === 6
})
const handleGetCode = async () => {
if (isCountingDown.value || loading.value) return
if (phone.value.length !== 11) {
uni.showToast({ title: '请输入正确的手机号', icon: 'none' })
return
}
try {
await getSmsCode(phone.value)
uni.showToast({ title: '验证码已发送', icon: 'success' })
startCountdown()
} catch (error) {
uni.showToast({ title: '验证码已发送 (模拟: 888888)', icon: 'none' })
startCountdown()
}
}
const startCountdown = () => {
isCountingDown.value = true
countdown.value = 60
const timer = setInterval(() => {
countdown.value--
if (countdown.value <= 0) {
clearInterval(timer)
isCountingDown.value = false
}
}, 1000)
}
const handleLogin = async () => {
if (!canSubmit.value || loading.value) return
loading.value = true
try {
const result = await store.login(phone.value, code.value)
if (result.success) {
if (result.hasProfile) {
uni.redirectTo({ url: '/pages/main/index' })
} else {
uni.redirectTo({ url: '/pages/onboarding/index' })
}
} else {
uni.showToast({ title: result.error || '登录失败', icon: 'none' })
}
} catch (error) {
uni.showToast({ title: '登录失败', icon: 'none' })
} finally {
loading.value = false
}
}
</script>
<style scoped>
.login-page {
min-height: 100vh;
background: linear-gradient(180deg, #0F071A 0%, #1A0B2E 50%, #0F071A 100%);
position: relative;
}
.status-bar {
height: constant(safe-area-inset-top);
height: env(safe-area-inset-top);
width: 100%;
background: transparent;
flex-shrink: 0;
}
.bg-decoration {
position: absolute;
inset: 0;
pointer-events: none;
}
.aurora-top {
position: absolute;
top: -10%;
left: -10%;
width: 120%;
height: 60%;
background: rgba(168, 85, 247, 0.08);
filter: blur(120rpx);
border-radius: 50%;
}
.aurora-bottom {
position: absolute;
bottom: -10%;
right: -10%;
width: 100%;
height: 50%;
background: rgba(139, 92, 246, 0.05);
filter: blur(100rpx);
border-radius: 50%;
}
.content {
position: relative;
z-index: 1;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 40rpx;
padding-top: calc(40rpx + constant(safe-area-inset-top));
padding-top: calc(40rpx + env(safe-area-inset-top));
}
.login-card {
width: 100%;
max-width: 720rpx;
background: rgba(168, 85, 247, 0.05);
backdrop-filter: blur(40rpx);
border: 1px solid rgba(168, 85, 247, 0.15);
border-radius: 48rpx;
padding: 64rpx 40rpx;
}
.header {
text-align: center;
margin-bottom: 64rpx;
}
.title {
display: block;
font-size: 48rpx;
font-weight: 300;
color: rgba(255, 255, 255, 0.9);
letter-spacing: 6rpx;
margin-bottom: 16rpx;
}
.subtitle {
display: block;
font-size: 28rpx;
color: rgba(255, 255, 255, 0.4);
font-style: italic;
}
.form {
margin-bottom: 48rpx;
}
.input-group {
margin-bottom: 32rpx;
}
.input-label {
display: block;
font-size: 18rpx;
color: rgba(255, 255, 255, 0.35);
margin-bottom: 16rpx;
letter-spacing: 4rpx;
text-transform: uppercase;
}
.glass-input {
width: 100%;
height: 92rpx;
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 24rpx;
padding: 0 32rpx;
color: #F3E8FF;
font-size: 28rpx;
}
.glass-input::placeholder {
color: rgba(255, 255, 255, 0.3);
}
.code-row {
display: flex;
flex-direction: row;
gap: 16rpx;
align-items: stretch;
width: 100%;
}
.code-input {
flex: 1;
width: auto;
min-width: 0;
}
.code-btn {
width: 220rpx;
height: 92rpx;
background: linear-gradient(135deg, rgba(147, 51, 234, 0.5), rgba(124, 58, 237, 0.4));
border: 1px solid rgba(168, 85, 247, 0.4);
border-radius: 24rpx;
color: rgba(255, 255, 255, 0.95);
font-size: 22rpx;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
letter-spacing: 2rpx;
transition: all 0.2s ease;
}
.code-btn:active {
transform: scale(0.97);
opacity: 0.85;
}
.code-btn.disabled {
background: rgba(255, 255, 255, 0.05);
border-color: rgba(255, 255, 255, 0.1);
color: rgba(255, 255, 255, 0.3);
}
.countdown {
font-size: 26rpx;
color: rgba(255, 255, 255, 0.6);
}
.btn-primary {
width: 100%;
height: 100rpx;
background: linear-gradient(135deg, #9333EA 0%, #7C3AED 100%);
border-radius: 32rpx;
color: white;
font-weight: 600;
font-size: 30rpx;
display: flex;
align-items: center;
justify-content: center;
border: none;
box-shadow: 0 8rpx 32rpx rgba(168, 85, 247, 0.3);
margin-bottom: 32rpx;
}
.btn-primary.disabled {
opacity: 0.5;
}
.agreement {
display: block;
text-align: center;
font-size: 22rpx;
color: rgba(255, 255, 255, 0.25);
line-height: 1.6;
}
</style>
+258
View File
@@ -0,0 +1,258 @@
<template>
<view class="path-view">
<text class="page-title">实现路径</text>
<view v-if="!selectedScript" class="empty-state glass-card">
<text class="empty-icon">🗺</text>
<text class="empty-text">先生成剧本方能洞察路径</text>
<button class="btn-secondary empty-btn" @click="goToScript">去生成剧本</button>
</view>
<view v-else-if="!currentPath" class="empty-state glass-card">
<text class="empty-icon">🎯</text>
<text class="empty-text">等待开启人生导航...</text>
</view>
<view v-else class="path-content">
<view class="target-card glass-card-gold">
<text class="target-label">目标{{ currentPath.title }}</text>
<text class="target-summary">{{ (currentPath.description || currentPath.summary || currentPath.content || '').slice(0, 80) }}...</text>
</view>
<view class="timeline">
<view class="timeline-line"></view>
<view
v-for="(step, index) in currentPath.steps"
:key="index"
class="timeline-item"
>
<view class="timeline-dot" :class="{ done: step.done }">
<view class="dot-inner" :class="{ pulse: step.done }"></view>
</view>
<view class="step-card glass-card" :class="{ done: step.done }">
<text class="step-phase">节点 {{ index + 1 }}</text>
<text class="step-task">{{ step.task }}</text>
<text class="step-desc">{{ step.desc }}</text>
</view>
</view>
</view>
</view>
</view>
</template>
<script setup>
import { computed, ref, onMounted, watch } from 'vue'
import { useAppStore } from '../../stores/app.js'
import * as lifePathService from '../../services/lifePath.js'
const store = useAppStore()
const pathData = ref(null)
const selectedScript = computed(() => {
return store.scripts.find(s => s.isSelected)
})
const currentPath = computed(() => {
if (pathData.value) return pathData.value
if (store.currentPath) return store.currentPath
return null
})
const loadPath = async (scriptId) => {
if (!scriptId) return
try {
const res = await lifePathService.getPathByScriptId(scriptId)
pathData.value = res.data || null
store.setCurrentPath(pathData.value)
} catch (error) {
pathData.value = null
}
}
const goToScript = () => {
uni.$emit('switchTab', 'script')
}
watch(selectedScript, (val) => {
if (val?.id) {
loadPath(val.id)
} else {
pathData.value = null
}
})
onMounted(() => {
if (selectedScript.value?.id) {
loadPath(selectedScript.value.id)
}
})
</script>
<style scoped>
.path-view {
display: flex;
flex-direction: column;
gap: 32rpx;
}
.page-title {
font-size: 36rpx;
font-weight: 400;
color: rgba(255, 255, 255, 0.9);
margin-bottom: 8rpx;
letter-spacing: 4rpx;
}
.empty-state {
padding: 120rpx 64rpx;
display: flex;
flex-direction: column;
align-items: center;
gap: 32rpx;
}
.empty-icon {
font-size: 80rpx;
opacity: 0.3;
}
.empty-text {
font-size: 24rpx;
color: rgba(192, 132, 252, 0.5);
font-style: italic;
text-align: center;
line-height: 1.6;
}
.empty-btn {
font-size: 22rpx;
padding: 18rpx 36rpx;
}
.path-content {
display: flex;
flex-direction: column;
gap: 48rpx;
}
.target-card {
padding: 40rpx;
margin-bottom: 16rpx;
}
.target-label {
display: block;
font-size: 18rpx;
color: #C084FC;
font-weight: 600;
letter-spacing: 4rpx;
text-transform: uppercase;
margin-bottom: 16rpx;
}
.target-summary {
display: block;
font-size: 28rpx;
color: rgba(255, 255, 255, 0.9);
line-height: 1.6;
}
.timeline {
position: relative;
padding-left: 60rpx;
display: flex;
flex-direction: column;
gap: 48rpx;
}
.timeline-line {
position: absolute;
left: 30rpx;
top: 30rpx;
bottom: 30rpx;
width: 2rpx;
background: linear-gradient(to bottom, #C084FC, rgba(192, 132, 252, 0.2), transparent);
opacity: 0.3;
}
.timeline-item {
position: relative;
}
.timeline-dot {
position: absolute;
left: -46rpx;
top: 24rpx;
width: 48rpx;
height: 48rpx;
border-radius: 50%;
border: 2rpx solid rgba(255, 255, 255, 0.1);
background: #0F071A;
display: flex;
align-items: center;
justify-content: center;
z-index: 2;
}
.timeline-dot.done {
border-color: #C084FC;
box-shadow: 0 0 30rpx rgba(192, 132, 252, 0.4);
}
.dot-inner {
width: 16rpx;
height: 16rpx;
border-radius: 50%;
background: rgba(255, 255, 255, 0.1);
}
.dot-inner.done {
background: #C084FC;
}
.dot-inner.pulse {
animation: pulse 2s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; transform: scale(1); }
50% { opacity: 0.6; transform: scale(1.2); }
}
.step-card {
padding: 32rpx;
opacity: 0.6;
}
.step-card.done {
opacity: 1;
border-color: rgba(192, 132, 252, 0.3);
}
.step-phase {
display: block;
font-size: 18rpx;
color: #C084FC;
font-weight: 600;
letter-spacing: 4rpx;
margin-bottom: 12rpx;
}
.step-task {
display: block;
font-size: 30rpx;
font-weight: 500;
color: rgba(255, 255, 255, 0.95);
margin-bottom: 12rpx;
}
.step-desc {
display: block;
font-size: 22rpx;
color: rgba(255, 255, 255, 0.5);
line-height: 1.5;
}
</style>
+277
View File
@@ -0,0 +1,277 @@
<template>
<view class="record-view">
<view class="input-card glass-card">
<view class="card-header">
<view class="dot"></view>
<text class="card-title">记叙当下</text>
</view>
<input
class="title-input"
placeholder="为这段记忆命名"
v-model="newEvent.title"
/>
<picker class="date-picker" mode="date" :value="newEvent.time" @change="onDateChange">
<view class="date-value">{{ newEvent.time || '选择日期' }}</view>
</picker>
<textarea
class="content-textarea"
placeholder="此刻的心境或发生的事..."
v-model="newEvent.content"
rows="3"
/>
<button
class="btn-primary save-btn"
:disabled="!canSave"
:class="{ disabled: !canSave }"
@click="saveEvent"
>
镌刻至星海
</button>
</view>
<view class="events-list">
<view
v-for="event in events"
:key="event.id"
class="event-card glass-card"
>
<view class="event-header">
<text class="event-title">{{ event.title }}</text>
<text class="event-date">{{ event.time }}</text>
</view>
<text class="event-content">{{ event.content }}</text>
<view class="ai-reply">
<view class="ai-header">
<text class="ai-icon"></text>
<text class="ai-title">Life Harmony AI</text>
</view>
<text class="ai-content" :class="{ typing: event.isNew }">
{{ event.aiFeedback }}
</text>
</view>
</view>
</view>
</view>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
import { useAppStore } from '../../stores/app.js'
const store = useAppStore()
const newEvent = ref({
title: '',
time: getTodayDate(),
content: ''
})
const events = computed(() => store.events)
const canSave = computed(() => {
return newEvent.value.title && newEvent.value.content
})
function getTodayDate() {
const today = new Date()
return today.toISOString().split('T')[0]
}
const onDateChange = (e) => {
newEvent.value.time = e.detail.value
}
const saveEvent = async () => {
if (!canSave.value) return
const eventData = {
...newEvent.value,
aiFeedback: '星河守望者正在解读这段紫色波频...',
isNew: true
}
await store.createEvent(eventData)
newEvent.value = {
title: '',
time: getTodayDate(),
content: ''
}
setTimeout(async () => {
const fullReply = `这段记忆碎片在星海中漾起涟漪。你在${eventData.title}中展现的特质,正在重新定义你人生OS的底层代码。继续保持这份觉知。`
eventData.aiFeedback = fullReply
eventData.isNew = false
await store.fetchEvents()
}, 1500)
}
onMounted(() => {
store.fetchEvents()
})
</script>
<style scoped>
.record-view {
display: flex;
flex-direction: column;
gap: 32rpx;
}
.input-card {
padding: 32rpx;
}
.card-header {
display: flex;
align-items: center;
gap: 12rpx;
margin-bottom: 24rpx;
}
.dot {
width: 16rpx;
height: 16rpx;
background: #C084FC;
border-radius: 50%;
}
.card-title {
font-size: 18rpx;
color: rgba(192, 132, 252, 0.8);
font-weight: 600;
letter-spacing: 4rpx;
text-transform: uppercase;
}
.title-input {
width: 100%;
height: 80rpx;
background: transparent;
border-bottom: 1px solid rgba(255, 255, 255, 0.12);
color: rgba(255, 255, 255, 0.9);
font-size: 28rpx;
margin-bottom: 16rpx;
}
.title-input::placeholder {
color: rgba(255, 255, 255, 0.3);
}
.date-picker {
margin-bottom: 16rpx;
}
.date-value {
font-size: 22rpx;
color: rgba(168, 85, 247, 0.6);
}
.content-textarea {
width: 100%;
height: 160rpx;
background: transparent;
color: rgba(255, 255, 255, 0.8);
font-size: 26rpx;
margin-bottom: 24rpx;
}
.content-textarea::placeholder {
color: rgba(255, 255, 255, 0.3);
}
.save-btn {
width: 100%;
height: 88rpx;
background: linear-gradient(90deg, rgba(147, 51, 234, 0.4), rgba(124, 58, 237, 0.4));
border: 1px solid rgba(168, 85, 247, 0.3);
}
.save-btn.disabled {
opacity: 0.5;
}
.events-list {
display: flex;
flex-direction: column;
gap: 24rpx;
}
.event-card {
padding: 32rpx;
}
.event-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 16rpx;
}
.event-title {
font-size: 30rpx;
font-weight: 500;
color: rgba(255, 255, 255, 0.9);
}
.event-date {
font-size: 20rpx;
color: rgba(168, 85, 247, 0.5);
}
.event-content {
display: block;
font-size: 26rpx;
color: rgba(255, 255, 255, 0.7);
line-height: 1.6;
margin-bottom: 24rpx;
}
.ai-reply {
background: rgba(168, 85, 247, 0.08);
border: 1px solid rgba(168, 85, 247, 0.2);
border-radius: 20rpx;
padding: 24rpx;
}
.ai-header {
display: flex;
align-items: center;
gap: 8rpx;
margin-bottom: 12rpx;
}
.ai-icon {
font-size: 24rpx;
}
.ai-title {
font-size: 18rpx;
color: rgba(192, 132, 252, 0.8);
font-weight: 600;
letter-spacing: 4rpx;
text-transform: uppercase;
}
.ai-content {
font-size: 22rpx;
color: rgba(243, 232, 255, 0.8);
font-style: italic;
line-height: 1.5;
}
.ai-content.typing {
animation: typing 2s ease-out;
}
@keyframes typing {
from { opacity: 0; transform: translateY(10rpx); }
to { opacity: 1; transform: translateY(0); }
}
</style>
+576
View File
@@ -0,0 +1,576 @@
<template>
<view class="script-view">
<text class="page-title font-serif">剧本生成器</text>
<view class="section-card glass-card">
<view class="section-header">
<text class="section-title">我的基础人设</text>
<text class="section-hint">可自由修改</text>
</view>
<view class="profile-grid">
<input
class="glass-input"
placeholder="姓名"
v-model="nickname"
/>
<picker class="glass-picker" mode="selector" :range="zodiacOptions" :value="zodiacIndex" @change="onZodiacChange">
<view class="picker-value">{{ registrationData.zodiac || '星座' }}</view>
</picker>
<picker class="glass-picker" mode="selector" :range="mbtiOptions" :value="mbtiIndex" @change="onMbtiChange">
<view class="picker-value">{{ registrationData.mbti || 'MBTI' }}</view>
</picker>
<input
class="glass-input"
placeholder="职业"
v-model="profession"
/>
</view>
</view>
<view class="section-card glass-card">
<view class="input-group">
<text class="label">剧本主题</text>
<input
class="glass-input"
placeholder="如:巅峰重现、治愈之旅、赛博觉醒..."
v-model="scriptConfig.theme"
/>
</view>
<view class="input-group">
<view class="npc-header">
<text class="label">关键配角/新的人设</text>
<button class="add-btn" @click="addNpc">+ 添加</button>
</view>
<view class="npc-form">
<input
class="glass-input npc-input"
placeholder="姓名"
v-model="npcConfig.name"
/>
<picker class="glass-picker npc-picker" mode="selector" :range="npcRoleOptions" :value="npcRoleIndex" @change="onNpcRoleChange">
<view class="picker-value">{{ npcConfig.role || '角色' }}</view>
</picker>
<picker class="glass-picker npc-picker" mode="selector" :range="npcRelationOptions" :value="npcRelationIndex" @change="onNpcRelationChange">
<view class="picker-value">{{ npcConfig.relation || '关系' }}</view>
</picker>
</view>
<textarea
class="glass-textarea"
placeholder="自由描述TA的人设特点或关键剧情点..."
v-model="npcConfig.desc"
rows="2"
/>
<view class="npc-list">
<view
v-for="(npc, index) in customNpcs"
:key="index"
class="npc-tag"
>
<text>{{ npc.name }} ({{ npc.role }})</text>
<text class="delete-btn" @click="removeNpc(index)">×</text>
</view>
</view>
</view>
<view class="params-row">
<view class="param-group">
<text class="param-label">叙事风格</text>
<view class="param-options">
<text
v-for="style in scriptStyles"
:key="style"
class="param-option"
:class="{ active: scriptConfig.style === style }"
@click="scriptConfig.style = style"
>
{{ style }}
</text>
</view>
</view>
<view class="param-group">
<text class="param-label">故事篇幅</text>
<view class="param-options">
<text
v-for="length in scriptLengths"
:key="length"
class="param-option"
:class="{ active: scriptConfig.length === length }"
@click="scriptConfig.length = length"
>
{{ length }}
</text>
</view>
</view>
</view>
<button
class="btn-primary generate-btn"
:loading="isGenerating"
:disabled="isGenerating || !scriptConfig.theme"
@click="generateScript"
>
<text v-if="isGenerating">命运编织中...</text>
<text v-else>生成平行人生剧本</text>
</button>
</view>
<view v-if="scripts.length > 0" class="scripts-list">
<view
v-for="script in scripts"
:key="script.id"
class="script-card glass-card"
:class="{ selected: script.isSelected }"
>
<view class="script-header">
<text class="script-title">{{ script.title }}</text>
<text class="script-persona">{{ script.theme || '追光者' }}</text>
</view>
<text class="script-summary" lines="3">{{ getScriptSummary(script) }}</text>
<view class="script-footer">
<text class="script-style">{{ script.style || '风格' }}</text>
<button class="select-btn" @click="selectScript(script.id)">
路径映射
</button>
</view>
</view>
</view>
<view v-else-if="!isGenerating" class="empty-state glass-card">
<text class="empty-icon">🎬</text>
<text class="empty-text">尚未生成剧本定义你的未来篇章</text>
</view>
<view v-if="isGenerating" class="generating-state glass-card">
<view class="spinner"></view>
<text class="generating-text">正在采集星海中的深紫色碎屑...</text>
</view>
</view>
</template>
<script setup>
import { ref, computed, reactive, onMounted } from 'vue'
import { useAppStore } from '../../stores/app.js'
const store = useAppStore()
const zodiacOptions = ['白羊座', '金牛座', '双子座', '巨蟹座', '狮子座', '处女座', '天秤座', '天蝎座', '射手座', '摩羯座', '水瓶座', '双鱼座']
const mbtiOptions = ['INTJ', 'INTP', 'ENTJ', 'ENTP', 'INFJ', 'INFP', 'ENFJ', 'ENFP', 'ISTJ', 'ISFJ', 'ESTJ', 'ESFJ', 'ISTP', 'ISFP', 'ESTP', 'ESFP']
const npcRoleOptions = ['伙伴', '宿敌', '导师', '挚爱', '下属', '路人']
const npcRelationOptions = ['信任', '对立', '暧昧', '敬畏', '背叛', '守护']
const scriptStyles = ['爽文', '治愈', '热血', '玄幻', '职场', '赛博']
const scriptLengths = ['短篇', '中篇', '长篇', '史诗']
const registrationData = computed(() => store.registrationData || {})
const scripts = computed(() => store.scripts || [])
const nickname = computed({
get: () => registrationData.value.nickname || '',
set: (val) => store.updateRegistration({ nickname: val })
})
const profession = computed({
get: () => registrationData.value.profession || '',
set: (val) => store.updateRegistration({ profession: val })
})
const zodiacIndex = computed(() => zodiacOptions.indexOf(registrationData.value.zodiac))
const mbtiIndex = computed(() => mbtiOptions.indexOf(registrationData.value.mbti))
const scriptConfig = reactive({
theme: '',
style: '爽文',
length: '中篇'
})
const npcConfig = reactive({
name: '',
role: '伙伴',
relation: '信任',
desc: ''
})
const customNpcs = ref([])
const isGenerating = ref(false)
const npcRoleIndex = computed(() => npcRoleOptions.indexOf(npcConfig.role))
const npcRelationIndex = computed(() => npcRelationOptions.indexOf(npcConfig.relation))
const onZodiacChange = (e) => {
store.updateRegistration({ zodiac: zodiacOptions[e.detail.value] })
}
const onMbtiChange = (e) => {
store.updateRegistration({ mbti: mbtiOptions[e.detail.value] })
}
const onNpcRoleChange = (e) => {
npcConfig.role = npcRoleOptions[e.detail.value]
}
const onNpcRelationChange = (e) => {
npcConfig.relation = npcRelationOptions[e.detail.value]
}
const addNpc = () => {
if (npcConfig.name) {
customNpcs.value.push({ ...npcConfig })
npcConfig.name = ''
npcConfig.desc = ''
}
}
const removeNpc = (index) => {
customNpcs.value.splice(index, 1)
}
const generateScript = async () => {
if (!scriptConfig.theme || isGenerating.value) return
isGenerating.value = true
try {
await store.createScript({
theme: scriptConfig.theme,
style: scriptConfig.style,
length: scriptConfig.length,
character: registrationData.value,
events: store.events
})
scriptConfig.theme = ''
} finally {
isGenerating.value = false
}
}
const selectScript = async (id) => {
await store.selectScript(id)
uni.$emit('switchTab', 'path')
}
const getScriptSummary = (script) => {
const text = script.summary || script.content || ''
return text.replace(/\s+/g, ' ').trim()
}
onMounted(async () => {
await store.fetchUserProfile()
await store.fetchEvents()
await store.fetchScripts()
})
</script>
<style scoped>
.script-view {
display: flex;
flex-direction: column;
gap: 32rpx;
}
.page-title {
font-size: 36rpx;
font-weight: 400;
color: rgba(255, 255, 255, 0.9);
margin-bottom: 8rpx;
letter-spacing: 4rpx;
}
.section-card {
padding: 32rpx;
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24rpx;
}
.section-title {
font-size: 22rpx;
color: rgba(192, 132, 252, 0.6);
font-weight: 600;
letter-spacing: 4rpx;
text-transform: uppercase;
}
.section-hint {
font-size: 16rpx;
color: rgba(255, 255, 255, 0.35);
font-style: italic;
}
.profile-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16rpx;
}
.glass-input, .glass-picker {
width: 100%;
height: 80rpx;
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 16rpx;
padding: 0 24rpx;
color: rgba(255, 255, 255, 0.9);
font-size: 26rpx;
}
.glass-input::placeholder {
color: rgba(255, 255, 255, 0.3);
}
.picker-value {
line-height: 80rpx;
color: rgba(255, 255, 255, 0.9);
}
.input-group {
margin-bottom: 24rpx;
}
.label {
display: block;
font-size: 18rpx;
color: rgba(255, 255, 255, 0.35);
font-weight: 600;
letter-spacing: 4rpx;
text-transform: uppercase;
margin-bottom: 16rpx;
}
.npc-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16rpx;
}
.add-btn {
font-size: 20rpx;
color: #C084FC;
border: 1px solid rgba(192, 132, 252, 0.3);
padding: 8rpx 16rpx;
border-radius: 12rpx;
background: transparent;
}
.npc-form {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 12rpx;
margin-bottom: 16rpx;
}
.npc-input, .npc-picker {
height: 72rpx;
padding: 0 16rpx;
font-size: 24rpx;
}
.npc-picker .picker-value {
line-height: 72rpx;
font-size: 24rpx;
}
.glass-textarea {
width: 100%;
height: 120rpx;
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 16rpx;
padding: 20rpx;
color: rgba(255, 255, 255, 0.9);
font-size: 24rpx;
margin-bottom: 16rpx;
}
.npc-list {
display: flex;
flex-wrap: wrap;
gap: 12rpx;
}
.npc-tag {
background: rgba(168, 85, 247, 0.1);
border: 1px solid rgba(168, 85, 247, 0.3);
border-radius: 24rpx;
padding: 8rpx 20rpx;
font-size: 20rpx;
color: rgba(243, 232, 255, 0.8);
display: flex;
align-items: center;
gap: 8rpx;
}
.delete-btn {
color: rgba(255, 255, 255, 0.4);
font-size: 28rpx;
padding: 0 4rpx;
}
.params-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 24rpx;
margin-bottom: 32rpx;
}
.param-group {
display: flex;
flex-direction: column;
gap: 12rpx;
}
.param-label {
font-size: 18rpx;
color: rgba(255, 255, 255, 0.35);
margin-left: 8rpx;
}
.param-options {
display: flex;
flex-wrap: wrap;
gap: 12rpx;
}
.param-option {
padding: 10rpx 20rpx;
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 16rpx;
font-size: 22rpx;
color: rgba(255, 255, 255, 0.5);
}
.param-option.active {
background: rgba(168, 85, 247, 0.2);
border-color: rgba(168, 85, 247, 0.5);
color: #C084FC;
}
.generate-btn {
width: 100%;
height: 96rpx;
box-shadow: 0 8rpx 40rpx rgba(168, 85, 247, 0.3);
}
.scripts-list {
display: flex;
flex-direction: column;
gap: 24rpx;
}
.script-card {
padding: 32rpx;
border-left: 4rpx solid transparent;
}
.script-card.selected {
border-left-color: #C084FC;
}
.script-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16rpx;
}
.script-title {
font-size: 32rpx;
font-weight: 500;
color: rgba(255, 255, 255, 0.9);
}
.script-persona {
font-size: 18rpx;
color: rgba(168, 85, 247, 0.6);
background: rgba(168, 85, 247, 0.1);
padding: 6rpx 16rpx;
border-radius: 12rpx;
border: 1px solid rgba(168, 85, 247, 0.2);
}
.script-summary {
display: block;
font-size: 24rpx;
color: rgba(255, 255, 255, 0.5);
line-height: 1.6;
margin-bottom: 24rpx;
}
.script-footer {
display: flex;
justify-content: space-between;
align-items: center;
}
.script-style {
font-size: 20rpx;
color: #C084FC;
}
.select-btn {
font-size: 24rpx;
color: #C084FC;
font-weight: 600;
background: transparent;
}
.empty-state {
padding: 80rpx 48rpx;
display: flex;
flex-direction: column;
align-items: center;
gap: 24rpx;
opacity: 0.5;
}
.empty-icon {
font-size: 64rpx;
}
.empty-text {
font-size: 24rpx;
color: rgba(255, 255, 255, 0.5);
text-align: center;
}
.generating-state {
padding: 80rpx 48rpx;
display: flex;
flex-direction: column;
align-items: center;
gap: 32rpx;
}
.spinner {
width: 64rpx;
height: 64rpx;
border: 4rpx solid #A855F7;
border-top-color: transparent;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.generating-text {
font-size: 26rpx;
color: rgba(192, 132, 252, 0.6);
font-style: italic;
letter-spacing: 2rpx;
}
</style>
+273
View File
@@ -0,0 +1,273 @@
<template>
<view class="main-page">
<view class="bg-decoration">
<view class="aurora-top"></view>
<view class="aurora-bottom"></view>
</view>
<view class="header" :style="{ paddingTop: safeAreaTop + 20 + 'px' }">
<view class="header-left">
<view class="logo-box">
<image class="logo" src="/static/logo.svg" mode="aspectFit" />
</view>
<view class="brand">
<text class="brand-title font-serif">人生OS</text>
<text class="brand-subtitle">LIFE HARMONY v3.1</text>
</view>
</view>
<view class="header-right" @click="goToProfile">
<view class="avatar-box">
<image class="avatar" :src="userAvatar" mode="aspectFill" />
</view>
</view>
</view>
<scroll-view class="page-content" scroll-y>
<view v-if="activeTab === 'record'" class="tab-page">
<RecordView></RecordView>
</view>
<view v-if="activeTab === 'script'" class="tab-page">
<ScriptView></ScriptView>
</view>
<view v-if="activeTab === 'path'" class="tab-page">
<PathView></PathView>
</view>
</scroll-view>
<view class="bottom-nav" :style="{ paddingBottom: safeAreaBottom + 'px' }">
<view
class="nav-item"
:class="{ active: activeTab === 'record' }"
@click="switchTab('record')"
>
<text class="nav-icon">📖</text>
<text class="nav-label">回溯过去</text>
</view>
<view
class="nav-item"
:class="{ active: activeTab === 'script' }"
@click="switchTab('script')"
>
<text class="nav-icon"></text>
<text class="nav-label">创造未来</text>
</view>
<view
class="nav-item"
:class="{ active: activeTab === 'path' }"
@click="switchTab('path')"
>
<text class="nav-icon">🗺</text>
<text class="nav-label">路径实现</text>
</view>
</view>
</view>
</template>
<script setup>
import { ref, computed, onMounted, onUnmounted } from 'vue'
import { useAppStore } from '../../stores/app.js'
import RecordView from './RecordView.vue'
import ScriptView from './ScriptView.vue'
import PathView from './PathView.vue'
const store = useAppStore()
const activeTab = ref('record')
const safeAreaTop = ref(uni.getStorageSync('safeAreaTop') || 20)
const safeAreaBottom = ref(uni.getStorageSync('safeAreaBottom') || 0)
onMounted(() => {
const systemInfo = uni.getSystemInfoSync()
safeAreaTop.value = systemInfo.safeAreaInsets?.top || systemInfo.statusBarHeight || 20
safeAreaBottom.value = systemInfo.safeAreaInsets?.bottom || 0
store.initialize()
uni.$on('switchTab', switchTab)
})
const userAvatar = computed(() => {
const nickname = store.userProfile?.nickname || 'user'
return `https://api.dicebear.com/7.x/avataaars/svg?seed=${nickname}&backgroundColor=A855F7`
})
const switchTab = (tab) => {
activeTab.value = tab
}
onUnmounted(() => {
uni.$off('switchTab', switchTab)
})
const goToProfile = () => {
uni.navigateTo({ url: '/pages/profile/index' })
}
</script>
<style scoped>
.main-page {
min-height: 100vh;
background: linear-gradient(180deg, #0F071A 0%, #1A0B2E 50%, #0F071A 100%);
position: relative;
display: flex;
flex-direction: column;
}
.bg-decoration {
position: absolute;
inset: 0;
pointer-events: none;
}
.aurora-top {
position: absolute;
top: -10%;
left: -10%;
width: 120%;
height: 60%;
background: rgba(168, 85, 247, 0.08);
filter: blur(120rpx);
border-radius: 50%;
}
.aurora-bottom {
position: absolute;
bottom: -10%;
right: -10%;
width: 100%;
height: 50%;
background: rgba(139, 92, 246, 0.05);
filter: blur(100rpx);
border-radius: 50%;
}
.header {
position: relative;
z-index: 10;
display: flex;
justify-content: space-between;
align-items: center;
padding: 24rpx 32rpx;
padding-top: calc(24rpx + constant(safe-area-inset-top));
padding-top: calc(24rpx + env(safe-area-inset-top));
background: rgba(15, 7, 26, 0.6);
backdrop-filter: blur(20rpx);
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
}
.header-left {
display: flex;
align-items: center;
gap: 16rpx;
}
.logo-box {
width: 80rpx;
height: 80rpx;
border-radius: 20rpx;
background: linear-gradient(135deg, #9333EA 0%, #7C3AED 100%);
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
}
.logo {
width: 64rpx;
height: 64rpx;
}
.brand-title {
display: block;
font-size: 26rpx;
font-weight: 600;
color: rgba(255, 255, 255, 0.9);
letter-spacing: 6rpx;
}
.brand-subtitle {
display: block;
font-size: 16rpx;
color: rgba(168, 85, 247, 0.6);
letter-spacing: 4rpx;
}
.avatar-box {
width: 72rpx;
height: 72rpx;
border-radius: 50%;
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.1);
overflow: hidden;
}
.avatar {
width: 100%;
height: 100%;
}
.page-content {
flex: 1;
position: relative;
z-index: 1;
overflow-y: auto;
}
.tab-page {
padding: 32rpx;
padding-bottom: calc(180rpx + constant(safe-area-inset-bottom));
padding-bottom: calc(180rpx + env(safe-area-inset-bottom));
}
.bottom-nav {
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: 140rpx;
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
background: rgba(15, 7, 26, 0.95);
backdrop-filter: blur(40rpx);
-webkit-backdrop-filter: blur(40rpx);
border-top: 1px solid rgba(255, 255, 255, 0.08);
display: flex;
justify-content: space-around;
align-items: center;
z-index: 100;
}
.nav-item {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 6rpx;
padding: 16rpx 32rpx;
color: rgba(255, 255, 255, 0.4);
transition: all 0.3s ease;
min-width: 160rpx;
}
.nav-item.active {
color: #C084FC;
transform: translateY(-8rpx);
}
.nav-icon {
font-size: 40rpx;
}
.nav-label {
font-size: 18rpx;
font-weight: 600;
letter-spacing: 4rpx;
text-transform: uppercase;
}
.safe-area-bottom {
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
}
</style>
+657
View File
@@ -0,0 +1,657 @@
<template>
<view class="onboarding-page">
<view class="bg-decoration">
<view class="aurora-top"></view>
<view class="aurora-bottom"></view>
</view>
<view class="content" :style="{ paddingTop: safeAreaTop + 20 + 'px', paddingBottom: safeAreaBottom + 20 + 'px' }">
<scroll-view class="step-content" scroll-y>
<view class="step-inner">
<view v-if="currentStep === 1" class="step animate-fade-in">
<view class="step-header">
<text class="step-title font-serif">系统初始化</text>
<text class="step-subtitle">定义你在人生OS中的唯一代码</text>
</view>
<view class="form-grid">
<view class="input-group">
<text class="label">你的昵称</text>
<input
class="glass-input"
placeholder="输入昵称"
v-model="formData.nickname"
/>
</view>
<view class="input-group">
<text class="label">性别</text>
<picker class="glass-picker" mode="selector" :range="genderOptions" :value="genderIndex" @change="onGenderChange">
<view class="picker-value">{{ formData.gender || '请选择' }}</view>
</picker>
</view>
<view class="input-group">
<text class="label">星座</text>
<picker class="glass-picker" mode="selector" :range="zodiacOptions" :value="zodiacIndex" @change="onZodiacChange">
<view class="picker-value">{{ formData.zodiac || '请选择' }}</view>
</picker>
</view>
<view class="input-group">
<text class="label">MBTI人格</text>
<picker class="glass-picker" mode="selector" :range="mbtiOptions" :value="mbtiIndex" @change="onMbtiChange">
<view class="picker-value">{{ formData.mbti || '请选择' }}</view>
</picker>
</view>
<view class="input-group full-width">
<text class="label">兴趣爱好</text>
<input
class="glass-input"
placeholder="用逗号分隔,如:阅读,旅行,摄影"
v-model="hobbiesText"
/>
</view>
</view>
</view>
<view v-if="currentStep === 2" class="step animate-fade-in">
<view class="step-header">
<text class="step-title font-serif">回溯起源</text>
<text class="step-subtitle">一段让你感到温暖的早期时光</text>
</view>
<view class="memory-form">
<view class="input-group">
<text class="label">日期</text>
<picker class="glass-picker" mode="date" :value="formData.childhood.date" @change="onChildhoodDateChange">
<view class="picker-value">{{ formData.childhood.date || '请选择日期' }}</view>
</picker>
</view>
<view class="input-group">
<text class="label">回忆一段具体且温暖的午后...</text>
<textarea
class="glass-textarea"
rows="4"
placeholder="描述那段时光..."
v-model="formData.childhood.text"
/>
</view>
<view class="hint-section">
<view class="hint-title">
<text class="icon"></text>
<text>灵感气泡</text>
</view>
<view class="hint-tags">
<text
v-for="(hint, index) in childhoodHints"
:key="index"
class="hint-tag"
@click="addChildhoodHint(hint)"
>
{{ hint }}
</text>
</view>
</view>
</view>
</view>
<view v-if="currentStep === 3" class="step animate-fade-in">
<view class="step-header">
<text class="step-title font-serif">悦然时刻</text>
<text class="step-subtitle">一段感受到生命跃动的经历</text>
</view>
<view class="memory-form">
<view class="input-group">
<text class="label">日期</text>
<picker class="glass-picker" mode="date" :value="formData.joy.date" @change="onJoyDateChange">
<view class="picker-value">{{ formData.joy.date || '请选择日期' }}</view>
</picker>
</view>
<view class="input-group">
<text class="label">想一个令你会心一笑的瞬间...</text>
<textarea
class="glass-textarea"
rows="4"
placeholder="描述那个瞬间..."
v-model="formData.joy.text"
/>
</view>
<view class="hint-section">
<view class="hint-title">
<text class="icon"></text>
<text>灵感气泡</text>
</view>
<view class="hint-tags">
<text
v-for="(hint, index) in joyHints"
:key="index"
class="hint-tag"
@click="addJoyHint(hint)"
>
{{ hint }}
</text>
</view>
</view>
</view>
</view>
<view v-if="currentStep === 4" class="step animate-fade-in">
<view class="step-header">
<text class="step-title font-serif">破茧成蝶</text>
<text class="step-subtitle">在宁静中默默积蓄力量的日子</text>
</view>
<view class="memory-form">
<view class="input-group">
<text class="label">日期</text>
<picker class="glass-picker" mode="date" :value="formData.low.date" @change="onLowDateChange">
<view class="picker-value">{{ formData.low.date || '请选择日期' }}</view>
</picker>
</view>
<view class="input-group">
<text class="label">描述那段有些艰难但让你成长的日子...</text>
<textarea
class="glass-textarea"
rows="4"
placeholder="描述那段经历..."
v-model="formData.low.text"
/>
</view>
<view class="hint-section">
<view class="hint-title">
<text class="icon"></text>
<text>灵感气泡</text>
</view>
<view class="hint-tags">
<text
v-for="(hint, index) in lowHints"
:key="index"
class="hint-tag"
@click="addLowHint(hint)"
>
{{ hint }}
</text>
</view>
</view>
</view>
</view>
<view v-if="currentStep === 5" class="step animate-fade-in">
<view class="step-header">
<text class="step-title font-serif">未来憧憬</text>
<text class="step-subtitle">对未来理想生活状态的预见</text>
</view>
<view class="memory-form">
<view class="input-group">
<text class="label">你想成为怎样的人</text>
<textarea
class="glass-textarea"
rows="3"
placeholder="描述你对未来的愿景..."
v-model="formData.future.vision"
/>
</view>
<view class="input-group">
<text class="label">你的理想生活状态</text>
<textarea
class="glass-textarea"
rows="3"
placeholder="描述理想的一天..."
v-model="formData.future.ideal"
/>
</view>
<view class="quote-box">
<text class="quote-icon"></text>
<text class="quote-text">"照见未来,便是创造的开始。"</text>
</view>
</view>
</view>
</view>
</scroll-view>
<view class="bottom-bar">
<view class="step-dots">
<view
v-for="step in 5"
:key="step"
class="step-dot"
:class="{ active: currentStep === step }"
/>
</view>
<view class="actions">
<button
v-if="currentStep > 1"
class="btn-secondary"
@click="prevStep"
>
返回
</button>
<button
class="btn-primary next-btn"
:loading="isSaving"
@click="nextStep"
>
<text v-if="currentStep === 5">{{ isSaving ? '保存中...' : '开启人生' }}</text>
<text v-else>继续</text>
</button>
</view>
</view>
</view>
</view>
</template>
<script setup>
import { ref, computed, reactive, onMounted, watch } from 'vue'
import { useAppStore } from '../../stores/app.js'
import * as lifeEventService from '../../services/lifeEvent.js'
const store = useAppStore()
const safeAreaTop = ref(uni.getStorageSync('safeAreaTop') || 20)
const safeAreaBottom = ref(uni.getStorageSync('safeAreaBottom') || 0)
onMounted(async () => {
const systemInfo = uni.getSystemInfoSync()
safeAreaTop.value = systemInfo.safeAreaInsets?.top || systemInfo.statusBarHeight || 20
safeAreaBottom.value = systemInfo.safeAreaInsets?.bottom || 0
await store.fetchUserProfile()
Object.assign(formData, store.registrationData)
currentStep.value = store.currentStep || 1
})
const currentStep = ref(store.currentStep || 1)
const isSaving = ref(false)
const formData = reactive({
nickname: '',
gender: '',
mbti: '',
zodiac: '',
profession: '',
hobbies: [],
childhood: { date: '', text: '' },
joy: { date: '', text: '' },
low: { date: '', text: '' },
future: { vision: '', ideal: '' }
})
const genderOptions = ['男', '女']
const zodiacOptions = ['白羊座', '金牛座', '双子座', '巨蟹座', '狮子座', '处女座', '天秤座', '天蝎座', '射手座', '摩羯座', '水瓶座', '双鱼座']
const mbtiOptions = ['INTJ', 'INTP', 'ENTJ', 'ENTP', 'INFJ', 'INFP', 'ENFJ', 'ENFP', 'ISTJ', 'ISFJ', 'ESTJ', 'ESFJ', 'ISTP', 'ISFP', 'ESTP', 'ESFP']
const childhoodHints = ['秘密花园', '老旧弄堂', '秋千', '夏蝉', '被保护的', '好奇心']
const joyHints = ['突破', '共鸣', '清晨阳光', '认可', '旅行终点', '深呼吸']
const lowHints = ['迷茫', '无力感', '雨后街道', '重新出发', '裂痕中的光', '蜕变']
const hobbiesText = computed({
get: () => formData.hobbies.join(''),
set: (val) => {
formData.hobbies = val.split(/[,]/).map(s => s.trim()).filter(s => s)
}
})
const genderIndex = computed(() => genderOptions.indexOf(formData.gender))
const zodiacIndex = computed(() => zodiacOptions.indexOf(formData.zodiac))
const mbtiIndex = computed(() => mbtiOptions.indexOf(formData.mbti))
const onGenderChange = (e) => {
formData.gender = genderOptions[e.detail.value]
}
const onZodiacChange = (e) => {
formData.zodiac = zodiacOptions[e.detail.value]
}
const onMbtiChange = (e) => {
formData.mbti = mbtiOptions[e.detail.value]
}
const onChildhoodDateChange = (e) => {
formData.childhood.date = e.detail.value
}
const onJoyDateChange = (e) => {
formData.joy.date = e.detail.value
}
const onLowDateChange = (e) => {
formData.low.date = e.detail.value
}
const addChildhoodHint = (hint) => {
formData.childhood.text += hint + ' '
}
const addJoyHint = (hint) => {
formData.joy.text += hint + ' '
}
const addLowHint = (hint) => {
formData.low.text += hint + ' '
}
const prevStep = () => {
if (currentStep.value > 1) {
saveStepData()
currentStep.value--
}
}
const nextStep = async () => {
saveStepData()
if (currentStep.value < 5) {
currentStep.value++
} else {
isSaving.value = true
try {
const result = await store.saveUserProfile()
if (result.success) {
const eventsToSave = [
{ data: formData.childhood, type: 'childhood', title: '童年记忆' },
{ data: formData.joy, type: 'joy', title: '光芒闪耀的时刻' },
{ data: formData.low, type: 'low', title: '在暗夜中潜行' }
]
await Promise.all(
eventsToSave.map(({ data, type, title }) => {
if (!data?.date || !data?.text) return Promise.resolve()
return lifeEventService.createEvent({
title,
time: data.date,
content: data.text,
eventType: 'milestone',
tags: [type]
})
})
)
await store.fetchEvents()
uni.showToast({ title: '欢迎开启人生OS', icon: 'success' })
setTimeout(() => {
uni.redirectTo({ url: '/pages/main/index' })
}, 1500)
} else {
uni.showToast({ title: result.error || '保存失败', icon: 'none' })
}
} catch (error) {
uni.showToast({ title: '保存失败', icon: 'none' })
} finally {
isSaving.value = false
}
}
}
const saveStepData = () => {
store.updateRegistration({ ...formData })
}
watch(currentStep, (val) => {
store.setCurrentStep(val)
})
</script>
<style scoped>
.onboarding-page {
min-height: 100vh;
background: linear-gradient(180deg, #0F071A 0%, #1A0B2E 50%, #0F071A 100%);
position: relative;
}
.bg-decoration {
position: absolute;
inset: 0;
pointer-events: none;
}
.aurora-top {
position: absolute;
top: -10%;
left: -10%;
width: 120%;
height: 60%;
background: rgba(168, 85, 247, 0.08);
filter: blur(120rpx);
border-radius: 50%;
}
.aurora-bottom {
position: absolute;
bottom: -10%;
right: -10%;
width: 100%;
height: 50%;
background: rgba(139, 92, 246, 0.05);
filter: blur(100rpx);
border-radius: 50%;
}
.content {
position: relative;
z-index: 1;
min-height: 100vh;
display: flex;
flex-direction: column;
padding: 40rpx;
padding-top: calc(40rpx + constant(safe-area-inset-top));
padding-top: calc(40rpx + env(safe-area-inset-top));
}
.step-content {
flex: 1;
overflow-y: auto;
}
.step-inner {
padding-bottom: 40rpx;
}
.step {
animation: fadeIn 0.5s ease-out;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(20rpx); }
to { opacity: 1; transform: translateY(0); }
}
.step-header {
margin-bottom: 48rpx;
}
.step-title {
display: block;
font-size: 46rpx;
font-weight: 400;
color: rgba(255, 255, 255, 0.95);
margin-bottom: 12rpx;
letter-spacing: 4rpx;
}
.step-subtitle {
display: block;
font-size: 24rpx;
color: rgba(192, 132, 252, 0.6);
font-style: italic;
}
.form-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 24rpx;
}
.full-width {
grid-column: 1 / -1;
}
.input-group {
margin-bottom: 8rpx;
}
.label {
display: block;
font-size: 18rpx;
color: rgba(255, 255, 255, 0.35);
margin-bottom: 12rpx;
letter-spacing: 4rpx;
text-transform: uppercase;
}
.glass-input, .glass-picker {
width: 100%;
height: 88rpx;
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 20rpx;
padding: 0 24rpx;
color: #F3E8FF;
font-size: 28rpx;
}
.glass-input::placeholder {
color: rgba(255, 255, 255, 0.3);
}
.picker-value {
line-height: 88rpx;
color: rgba(255, 255, 255, 0.8);
}
.glass-textarea {
width: 100%;
height: 200rpx;
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 20rpx;
padding: 24rpx;
color: #F3E8FF;
font-size: 28rpx;
}
.glass-textarea::placeholder {
color: rgba(255, 255, 255, 0.3);
}
.memory-form {
display: flex;
flex-direction: column;
gap: 24rpx;
}
.hint-section {
background: rgba(168, 85, 247, 0.08);
border: 1px solid rgba(168, 85, 247, 0.15);
border-radius: 32rpx;
padding: 32rpx;
margin-top: 16rpx;
box-shadow: inset 0 0 30rpx rgba(168, 85, 247, 0.08);
}
.hint-title {
display: flex;
align-items: center;
gap: 8rpx;
font-size: 18rpx;
color: rgba(192, 132, 252, 0.6);
text-transform: uppercase;
letter-spacing: 4rpx;
margin-bottom: 20rpx;
}
.icon {
font-size: 24rpx;
}
.hint-tags {
display: flex;
flex-wrap: wrap;
gap: 16rpx;
}
.hint-tag {
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 32rpx;
padding: 10rpx 22rpx;
font-size: 22rpx;
color: rgba(243, 232, 255, 0.8);
}
.hint-tag:active {
background: rgba(168, 85, 247, 0.2);
border-color: rgba(168, 85, 247, 0.4);
}
.quote-box {
background: linear-gradient(135deg, rgba(168, 85, 247, 0.15), rgba(232, 121, 249, 0.1));
border: 1px solid rgba(168, 85, 247, 0.3);
border-radius: 20rpx;
padding: 32rpx;
display: flex;
align-items: center;
gap: 16rpx;
margin-top: 16rpx;
}
.quote-icon {
font-size: 32rpx;
}
.quote-text {
font-size: 26rpx;
color: rgba(243, 232, 255, 0.7);
font-style: italic;
}
.bottom-bar {
padding-top: 32rpx;
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
border-top: 1px solid rgba(255, 255, 255, 0.05);
}
.step-dots {
display: flex;
gap: 8rpx;
margin-bottom: 32rpx;
}
.step-dot {
flex: 1;
height: 8rpx;
background: rgba(255, 255, 255, 0.1);
border-radius: 4rpx;
transition: all 0.4s ease;
}
.step-dot.active {
flex: 1;
height: 8rpx;
background: #A855F7;
box-shadow: 0 0 24rpx rgba(168, 85, 247, 0.6);
}
.actions {
display: flex;
justify-content: flex-end;
gap: 24rpx;
align-items: center;
}
.next-btn {
min-width: 200rpx;
}
</style>
+296
View File
@@ -0,0 +1,296 @@
<template>
<view class="profile-page">
<view class="bg-decoration">
<view class="aurora-top"></view>
<view class="aurora-bottom"></view>
</view>
<scroll-view class="content" scroll-y :style="{ paddingTop: safeAreaTop + 20 + 'px', paddingBottom: safeAreaBottom + 20 + 'px' }">
<view class="user-card">
<view class="avatar-box">
<image class="avatar" :src="userAvatar" mode="aspectFill" />
<view class="verified-badge"></view>
</view>
<view class="user-info">
<text class="nickname font-serif">{{ userProfile.nickname || '未同步系统' }}</text>
<text class="user-tags">{{ userTags }}</text>
</view>
</view>
<view class="stats-row">
<view class="stat-card glass-card">
<text class="stat-label">觉醒深度</text>
<text class="stat-value">Lv.4</text>
</view>
<view class="stat-card glass-card">
<text class="stat-label">星历契合</text>
<text class="stat-value">98%</text>
</view>
</view>
<view class="menu-list">
<view class="menu-item glass-card" @click="editProfile">
<view class="menu-left">
<text class="menu-icon">👤</text>
<text class="menu-title">个人档案设置</text>
</view>
<text class="menu-arrow"></text>
</view>
<view class="menu-item glass-card">
<view class="menu-left">
<text class="menu-icon">🔄</text>
<text class="menu-title">多账号切换</text>
</view>
<text class="menu-arrow"></text>
</view>
<view class="menu-item glass-card">
<view class="menu-left">
<text class="menu-icon">📧</text>
<text class="menu-title">与开发者对话</text>
</view>
<text class="menu-arrow"></text>
</view>
</view>
<button class="logout-btn" @click="handleLogout">
TERMINATE LIFE HARMONY
</button>
</scroll-view>
</view>
</template>
<script setup>
import { computed, ref, onMounted } from 'vue'
import { useAppStore } from '../../stores/app.js'
const store = useAppStore()
const safeAreaTop = ref(uni.getStorageSync('safeAreaTop') || 20)
const safeAreaBottom = ref(uni.getStorageSync('safeAreaBottom') || 0)
onMounted(() => {
const systemInfo = uni.getSystemInfoSync()
safeAreaTop.value = systemInfo.safeAreaInsets?.top || systemInfo.statusBarHeight || 20
safeAreaBottom.value = systemInfo.safeAreaInsets?.bottom || 0
})
const userProfile = computed(() => store.userProfile || {})
const userAvatar = computed(() => {
const nickname = userProfile.value.nickname || 'user'
return `https://api.dicebear.com/7.x/avataaars/svg?seed=${nickname}&backgroundColor=A855F7`
})
const userTags = computed(() => {
const { mbti, zodiac, profession } = userProfile.value
const tags = [mbti || 'QUESTER', zodiac || 'STAR', profession || '星民']
return tags.join(' · ')
})
const editProfile = () => {
uni.navigateTo({ url: '/pages/onboarding/index?edit=1' })
}
const handleLogout = () => {
uni.showModal({
title: '确认退出',
content: '确定要退出登录吗?',
success: async (res) => {
if (res.confirm) {
await store.logout()
uni.reLaunch({ url: '/pages/login/index' })
}
}
})
}
</script>
<style scoped>
.profile-page {
min-height: 100vh;
background: linear-gradient(180deg, #0F071A 0%, #1A0B2E 50%, #0F071A 100%);
position: relative;
}
.bg-decoration {
position: absolute;
inset: 0;
pointer-events: none;
}
.aurora-top {
position: absolute;
top: -10%;
left: -10%;
width: 120%;
height: 60%;
background: rgba(168, 85, 247, 0.08);
filter: blur(120rpx);
border-radius: 50%;
}
.aurora-bottom {
position: absolute;
bottom: -10%;
right: -10%;
width: 100%;
height: 50%;
background: rgba(139, 92, 246, 0.05);
filter: blur(100rpx);
border-radius: 50%;
}
.content {
position: relative;
z-index: 1;
padding: 40rpx;
padding-top: calc(60rpx + constant(safe-area-inset-top));
padding-top: calc(60rpx + env(safe-area-inset-top));
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
}
.user-card {
display: flex;
flex-direction: column;
align-items: center;
padding: 48rpx 0;
margin-bottom: 32rpx;
}
.avatar-box {
position: relative;
width: 160rpx;
height: 160rpx;
border-radius: 50%;
border: 4rpx solid rgba(168, 85, 247, 0.3);
padding: 8rpx;
margin-bottom: 32rpx;
}
.avatar {
width: 100%;
height: 100%;
border-radius: 50%;
background: rgba(168, 85, 247, 0.1);
}
.verified-badge {
position: absolute;
bottom: 8rpx;
right: 8rpx;
width: 40rpx;
height: 40rpx;
background: #9333EA;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 20rpx;
color: white;
border: 4rpx solid #0F071A;
}
.user-info {
text-align: center;
}
.nickname {
display: block;
font-size: 40rpx;
font-weight: 300;
color: rgba(255, 255, 255, 0.95);
margin-bottom: 16rpx;
letter-spacing: 4rpx;
}
.user-tags {
display: block;
font-size: 18rpx;
color: rgba(168, 85, 247, 0.6);
letter-spacing: 4rpx;
text-transform: uppercase;
}
.stats-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 24rpx;
margin-bottom: 48rpx;
}
.stat-card {
padding: 32rpx;
display: flex;
flex-direction: column;
align-items: center;
gap: 12rpx;
}
.stat-label {
font-size: 18rpx;
color: rgba(255, 255, 255, 0.35);
letter-spacing: 4rpx;
text-transform: uppercase;
}
.stat-value {
font-size: 36rpx;
font-weight: 300;
color: rgba(243, 232, 255, 0.9);
}
.menu-list {
display: flex;
flex-direction: column;
gap: 20rpx;
margin-bottom: 64rpx;
}
.menu-item {
padding: 32rpx;
display: flex;
justify-content: space-between;
align-items: center;
}
.menu-left {
display: flex;
align-items: center;
gap: 24rpx;
}
.menu-icon {
font-size: 32rpx;
}
.menu-title {
font-size: 26rpx;
color: rgba(255, 255, 255, 0.8);
}
.menu-arrow {
font-size: 32rpx;
color: rgba(255, 255, 255, 0.3);
}
.logout-btn {
width: 100%;
padding: 32rpx;
background: transparent;
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 24rpx;
color: rgba(255, 255, 255, 0.3);
font-size: 20rpx;
letter-spacing: 6rpx;
text-transform: uppercase;
}
.logout-btn:active {
color: rgba(168, 85, 247, 0.5);
border-color: rgba(168, 85, 247, 0.2);
}
</style>
+131
View File
@@ -0,0 +1,131 @@
<template>
<view class="container">
<view class="bg-decoration">
<view class="aurora-top"></view>
<view class="aurora-bottom"></view>
</view>
<view class="overlay">
<view class="status-bar-space" :style="{ height: statusBarHeight + 'px' }"></view>
<view class="content-area" :style="{ paddingBottom: safeAreaBottom + 'px' }">
<image class="logo" src="/static/logo.svg" mode="widthFix"></image>
<text class="app-name font-serif">人生OS</text>
<text class="app-version">LIFE HARMONY v3.1</text>
</view>
</view>
</view>
</template>
<script setup>
import { ref } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import { useAppStore } from '../../stores/app.js'
const statusBarHeight = ref(20)
const safeAreaBottom = ref(0)
onLoad(() => {
const systemInfo = uni.getSystemInfoSync()
statusBarHeight.value = systemInfo.statusBarHeight || 20
safeAreaBottom.value = systemInfo.safeAreaInsets?.bottom || 0
setTimeout(async () => {
const store = useAppStore()
const token = uni.getStorageSync('access_token')
if (token) {
await store.fetchUserProfile()
if (store.hasProfile) {
uni.reLaunch({ url: '/pages/main/index' })
} else {
uni.reLaunch({ url: '/pages/onboarding/index' })
}
} else {
uni.reLaunch({ url: '/pages/login/index' })
}
}, 2000)
})
</script>
<style>
.container {
position: relative;
width: 750rpx;
min-height: 100vh;
height: 100vh;
background: linear-gradient(180deg, #0F071A 0%, #1A0B2E 50%, #0F071A 100%);
}
.status-bar-space {
height: constant(safe-area-inset-top);
height: env(safe-area-inset-top);
width: 100%;
background: transparent;
flex-shrink: 0;
}
.bg-decoration {
position: absolute;
inset: 0;
pointer-events: none;
}
.aurora-top {
position: absolute;
top: -10%;
left: -10%;
width: 120%;
height: 60%;
background: rgba(168, 85, 247, 0.08);
filter: blur(120rpx);
border-radius: 50%;
}
.aurora-bottom {
position: absolute;
bottom: -10%;
right: -10%;
width: 100%;
height: 50%;
background: rgba(139, 92, 246, 0.05);
filter: blur(100rpx);
border-radius: 50%;
}
.overlay {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
display: flex;
flex-direction: column;
z-index: 1;
}
.content-area {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
}
.logo {
width: 180rpx;
margin-bottom: 32rpx;
}
.app-name {
font-size: 52rpx;
font-weight: 300;
color: rgba(255, 255, 255, 0.95);
letter-spacing: 8rpx;
margin-bottom: 20rpx;
}
.app-version {
font-size: 24rpx;
color: rgba(168, 85, 247, 0.7);
letter-spacing: 4rpx;
}
</style>
+123
View File
@@ -0,0 +1,123 @@
/**
* 认证服务
* 处理登录、注册、验证码等认证相关接口
* 与Web端(life-script/src/services/auth.js)保持一致
*/
import { get, post } from './request.js'
/**
* 获取短信验证码
* @param {string} phone - 手机号
* @returns {Promise<Object>} 验证码响应
*/
export const getSmsCode = async (phone) => {
const response = await get('/auth/sms-code', { phone })
return response
}
/**
* 用户登录(手机号 + 验证码)
* @param {Object} params - 登录参数
* @param {string} params.phone - 手机号
* @param {string} params.smsCode - 短信验证码
* @returns {Promise<Object>} 登录响应(包含 token
*/
export const login = async ({ phone, smsCode }) => {
const response = await post('/auth/login', { phone, smsCode })
// 保存token
if (response.data) {
const { accessToken, refreshToken } = response.data
if (accessToken) {
uni.setStorageSync('access_token', accessToken)
}
if (refreshToken) {
uni.setStorageSync('refresh_token', refreshToken)
}
}
return response
}
/**
* 用户登出
* @returns {Promise<void>}
*/
export const logout = async () => {
try {
await post('/auth/logout')
} finally {
// 无论成功失败都清除本地token
uni.removeStorageSync('access_token')
uni.removeStorageSync('refresh_token')
}
}
/**
* 刷新token
* @returns {Promise<Object>} 新的token
*/
export const refreshToken = async () => {
const refreshToken = uni.getStorageSync('refresh_token')
if (!refreshToken) {
throw new Error('No refresh token')
}
const response = await post('/auth/refreshToken', { refreshToken })
// 更新token
if (response.data) {
const { accessToken, refreshToken: newRefreshToken } = response.data
if (accessToken) {
uni.setStorageSync('access_token', accessToken)
}
if (newRefreshToken) {
uni.setStorageSync('refresh_token', newRefreshToken)
}
}
return response
}
/**
* 验证token是否有效
* @returns {Promise<boolean>}
*/
export const validateToken = async () => {
try {
const response = await get('/auth/validateToken')
return response.data === true
} catch {
return false
}
}
/**
* 获取当前用户信息
* @returns {Promise<Object>} 用户信息
*/
export const getCurrentUserInfo = async () => {
const response = await get('/auth/userInfo')
return response
}
/**
* 检查手机号是否已注册
* @param {string} phone - 手机号
* @returns {Promise<boolean>}
*/
export const checkPhone = async (phone) => {
const response = await get('/auth/checkPhone', { phone })
return response.data
}
export default {
getSmsCode,
login,
logout,
refreshToken,
validateToken,
getCurrentUserInfo,
checkPhone
}
+202
View File
@@ -0,0 +1,202 @@
/**
* 爽文剧本服务
* 处理剧本的增删改查
*/
import { get, post, put, del } from './request.js'
export const getScriptList = async () => {
const response = await get('/epicScript/listAll')
return response
}
export const getScriptPage = async ({ pageNum = 1, pageSize = 10 }) => {
const response = await get('/epicScript/page', { pageNum, pageSize })
return response
}
export const getScriptById = async (id) => {
const response = await get('/epicScript/detail', { id })
return response
}
export const createScript = async (scriptData) => {
const requestData = transformToBackendFormat(scriptData)
const response = await post('/epicScript/create', requestData)
return response
}
export const updateScript = async (scriptData) => {
const requestData = transformToBackendFormat(scriptData)
const response = await put('/epicScript/update', requestData)
return response
}
export const selectScript = async (id) => {
const response = await put('/epicScript/select', null, { params: { id } })
return response
}
export const deleteScript = async (id) => {
const response = await del('/epicScript/delete', { id })
return response
}
const transformToBackendFormat = (frontendData) => {
const {
id,
theme,
style,
length,
content,
isSelected,
character,
events
} = frontendData
let title = theme || '我的剧本'
let plotIntro = ''
let plotTurning = ''
let plotClimax = ''
let plotEnding = ''
if (content) {
const sections = content.split(/【[^】]+】/)
const titles = content.match(/【[^】]+】/g) || []
titles.forEach((t, index) => {
const sectionContent = sections[index + 1]?.trim() || ''
if (t.includes('序幕') || t.includes('低谷')) {
plotIntro = sectionContent
} else if (t.includes('转折') || t.includes('契机')) {
plotTurning = sectionContent
} else if (t.includes('高潮') || t.includes('抉择')) {
plotClimax = sectionContent
} else if (t.includes('结局') || t.includes('开始')) {
plotEnding = sectionContent
}
})
}
let characterInfo = ''
if (character) {
const parts = [
`姓名:${character.nickname || '未设置'}`,
`性别:${character.gender || '未设置'}`,
`MBTI${character.mbti || '未设置'}`,
`星座:${character.zodiac || '未设置'}`,
`职业:${character.profession || '未设置'}`,
`兴趣爱好:${character.hobbies?.join(',') || '无'}`
]
if (character.future) {
if (character.future.vision) parts.push(`未来愿景:${character.future.vision}`)
if (character.future.ideal) parts.push(`理想生活:${character.future.ideal}`)
}
characterInfo = parts.join('\n')
}
let lifeEventsSummary = ''
const eventParts = []
if (character) {
if (character.childhood?.text) {
eventParts.push(`【童年记忆】(${character.childhood.date || '未知时间'}): ${character.childhood.text}`)
}
if (character.joy?.text) {
eventParts.push(`【高光时刻】(${character.joy.date || '未知时间'}): ${character.joy.text}`)
}
if (character.low?.text) {
eventParts.push(`【至暗时刻】(${character.low.date || '未知时间'}): ${character.low.text}`)
}
}
if (events && Array.isArray(events)) {
events.forEach(e => {
const dateStr = e.time || e.eventDate || '未知时间'
const titleStr = e.title || '无标题'
const contentStr = e.content || ''
eventParts.push(`【人生事件】(${dateStr}) ${titleStr}${contentStr ? ': ' + contentStr : ''}`)
})
}
lifeEventsSummary = eventParts.join('\n')
return {
id,
title,
theme,
style,
length,
plotIntro,
plotTurning,
plotClimax,
plotEnding,
plotJson: content ? { fullContent: content } : null,
isSelected,
characterInfo,
lifeEventsSummary
}
}
export const transformToFrontendFormat = (backendData) => {
if (!backendData) return null
const {
id,
userId,
title,
theme,
style,
length,
plotIntro,
plotTurning,
plotClimax,
plotEnding,
plotJson,
isSelected,
createTime
} = backendData
let content = ''
if (plotJson?.fullContent) {
content = plotJson.fullContent
} else {
const parts = []
if (plotIntro) parts.push(`【序幕:低谷回响】\n${plotIntro}`)
if (plotTurning) parts.push(`【转折:契机出现】\n${plotTurning}`)
if (plotClimax) parts.push(`【高潮:命运抉择】\n${plotClimax}`)
if (plotEnding) parts.push(`【结局:新的开始】\n${plotEnding}`)
content = parts.join('\n\n')
}
return {
id,
userId,
title: title || theme || '未命名剧本',
theme: theme || '',
style: style || '',
length: length || 'medium',
content,
isSelected: isSelected || false,
date: createTime ? new Date(createTime).toLocaleDateString() : new Date().toLocaleDateString()
}
}
export const transformListToFrontend = (backendList) => {
if (!Array.isArray(backendList)) return []
return backendList.map(transformToFrontendFormat)
}
export default {
getScriptList,
getScriptPage,
getScriptById,
createScript,
updateScript,
selectScript,
deleteScript,
transformToFrontendFormat,
transformListToFrontend
}
+112
View File
@@ -0,0 +1,112 @@
/**
* 生命事件服务
* 处理生命事件的增删改查
*/
import { get, post, put, del } from './request.js'
export const getEventList = async () => {
const response = await get('/lifeEvent/list')
return response
}
export const getEventPage = async ({ pageNum = 1, pageSize = 10 }) => {
const response = await get('/lifeEvent/page', { pageNum, pageSize })
return response
}
export const getEventById = async (id) => {
const response = await get('/lifeEvent/detail', { id })
return response
}
export const createEvent = async (eventData) => {
const requestData = transformToBackendFormat(eventData)
const response = await post('/lifeEvent/create', requestData)
return response
}
export const updateEvent = async (eventData) => {
const requestData = transformToBackendFormat(eventData)
const response = await put('/lifeEvent/update', requestData)
return response
}
export const deleteEvent = async (id) => {
const response = await del('/lifeEvent/delete', { id })
return response
}
const transformToBackendFormat = (frontendData) => {
const {
id,
title,
time,
content,
aiFeedback,
eventType = 'daily_log',
emotionType,
emotionScore,
tags
} = frontendData
return {
id,
title,
eventDate: time,
content,
aiReply: aiFeedback,
eventType,
emotionType,
emotionScore,
tags
}
}
export const transformToFrontendFormat = (backendData) => {
if (!backendData) return null
const {
id,
userId,
title,
eventDate,
content,
aiReply,
eventType,
emotionType,
emotionScore,
tags,
createTime
} = backendData
return {
id,
userId,
title: title || '',
time: eventDate ? eventDate.split('T')[0] : '',
content: content || '',
aiFeedback: aiReply || '',
eventType: eventType || 'daily_log',
emotionType,
emotionScore,
tags: tags || [],
createTime
}
}
export const transformListToFrontend = (backendList) => {
if (!Array.isArray(backendList)) return []
return backendList.map(transformToFrontendFormat)
}
export default {
getEventList,
getEventPage,
getEventById,
createEvent,
updateEvent,
deleteEvent,
transformToFrontendFormat,
transformListToFrontend
}
+148
View File
@@ -0,0 +1,148 @@
/**
* 实现路径服务
* 处理路径的增删改查
*/
import { get, post, put, del } from './request.js'
export const getPathList = async () => {
const response = await get('/lifePath/listAll')
return response
}
export const getPathPage = async ({ pageNum = 1, pageSize = 10 }) => {
const response = await get('/lifePath/page', { pageNum, pageSize })
return response
}
export const getPathByScriptId = async (scriptId) => {
const response = await get('/lifePath/byScript', { scriptId })
return response
}
export const getPathById = async (id) => {
const response = await get('/lifePath/detail', { id })
return response
}
export const createPath = async (pathData) => {
const requestData = transformToBackendFormat(pathData)
const response = await post('/lifePath/create', requestData)
return response
}
export const updatePath = async (pathData) => {
const requestData = transformToBackendFormat(pathData)
const response = await put('/lifePath/update', requestData)
return response
}
export const deletePath = async (id) => {
const response = await del('/lifePath/delete', { id })
return response
}
const transformToBackendFormat = (frontendData) => {
const {
id,
scriptId,
title,
description,
content,
status = 'active',
progress = 0
} = frontendData
let steps = []
if (content) {
const stepMatches = content.match(/(\d+)\.\s*([^:]+)[:]\s*([^\n]+)/g)
if (stepMatches) {
steps = stepMatches.map((match, index) => {
const parts = match.match(/(\d+)\.\s*([^:]+)[:]\s*(.+)/)
return {
phase: `阶段${index + 1}`,
time: parts?.[2]?.trim() || '',
content: parts?.[3]?.trim() || match,
action: '',
resources: '',
habit: ''
}
})
} else {
const lines = content.split('\n').filter(line => line.trim())
steps = lines.map((line, index) => ({
phase: `阶段${index + 1}`,
time: '',
content: line.trim(),
action: '',
resources: '',
habit: ''
}))
}
}
return {
id,
scriptId,
title: title || '实现路径',
description,
steps,
status,
progress
}
}
export const transformToFrontendFormat = (backendData) => {
if (!backendData) return null
const {
id,
userId,
scriptId,
title,
description,
steps,
status,
progress,
createTime
} = backendData
let content = ''
if (Array.isArray(steps) && steps.length > 0) {
content = steps.map((step, index) => {
const phase = step.phase || `阶段${index + 1}`
const time = step.time ? `${step.time}` : ''
return `${index + 1}. ${phase}${time}${step.content || ''}`
}).join('\n')
}
return {
id,
userId,
scriptId,
title: title || '实现路径',
description: description || '',
content,
steps: steps || [],
status: status || 'active',
progress: progress || 0,
createTime
}
}
export const transformListToFrontend = (backendList) => {
if (!Array.isArray(backendList)) return []
return backendList.map(transformToFrontendFormat)
}
export default {
getPathList,
getPathPage,
getPathByScriptId,
getPathById,
createPath,
updatePath,
deletePath,
transformToFrontendFormat,
transformListToFrontend
}
+103
View File
@@ -0,0 +1,103 @@
import { getEnvValue, isDev } from '../config/env.js'
const API_BASE_URL = getEnvValue('API_BASE_URL')
/**
* 请求拦截处理
* 自动添加token到请求头
*/
const getHeaders = () => {
const token = uni.getStorageSync('access_token')
const headers = {
'Content-Type': 'application/json'
}
if (token) {
headers.Authorization = `Bearer ${token}`
}
return headers
}
/**
* 统一请求方法
* @param {Object} options - 请求配置
* @returns {Promise} 请求Promise
*/
const request = (options) => {
return new Promise((resolve, reject) => {
uni.request({
url: `${API_BASE_URL}${options.url}`,
method: options.method || 'GET',
data: options.data,
header: getHeaders(),
timeout: 30000,
success: (res) => {
const { data, statusCode } = res
// 处理401未授权
if (statusCode === 401) {
uni.removeStorageSync('access_token')
uni.removeStorageSync('refresh_token')
uni.redirectTo({ url: '/pages/login/index' })
reject(new Error('登录已过期,请重新登录'))
return
}
// 后端返回格式: { code, message, data }
if (data.code === 200 || data.code === 0) {
resolve(data)
} else {
reject(new Error(data.message || '请求失败'))
}
},
fail: (err) => {
reject(new Error(err.errMsg || '网络错误'))
}
})
})
}
/**
* GET请求
*/
export const get = (url, params = {}) => {
// 构建查询字符串
const queryString = Object.keys(params)
.map(key => `${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`)
.join('&')
const fullUrl = queryString ? `${url}?${queryString}` : url
return request({ url: fullUrl, method: 'GET' })
}
/**
* POST请求
*/
export const post = (url, data = {}) => {
return request({ url, method: 'POST', data })
}
/**
* PUT请求
*/
export const put = (url, data = {}) => {
return request({ url, method: 'PUT', data })
}
/**
* DELETE请求
*/
export const del = (url, params = {}) => {
const queryString = Object.keys(params)
.map(key => `${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`)
.join('&')
const fullUrl = queryString ? `${url}?${queryString}` : url
return request({ url: fullUrl, method: 'DELETE' })
}
export default {
get,
post,
put,
del
}
+151
View File
@@ -0,0 +1,151 @@
/**
* 用户档案服务
* 处理用户档案的增删改查
* 与Web端(life-script/src/services/userProfile.js)保持一致
*/
import { get, post, put } from './request.js'
/**
* 获取当前用户档案
* @returns {Promise<Object|null>} 用户档案
*/
export const getCurrentProfile = async () => {
const response = await get('/user-profile/me')
return response
}
/**
* 创建用户档案
* @param {Object} profileData - 档案数据
* @returns {Promise<Object>} 创建的档案
*/
export const createProfile = async (profileData) => {
// 转换前端数据格式为后端格式
const requestData = transformToBackendFormat(profileData)
const response = await post('/user-profile/create', requestData)
return response
}
/**
* 更新用户档案
* @param {Object} profileData - 档案数据(必须包含id)
* @returns {Promise<Object>} 更新后的档案
*/
export const updateProfile = async (profileData) => {
const requestData = transformToBackendFormat(profileData)
const response = await put('/user-profile/update', requestData)
return response
}
/**
* 将前端数据格式转换为后端格式
* @param {Object} frontendData - 前端数据
* @returns {Object} 后端格式数据
*/
const transformToBackendFormat = (frontendData) => {
const {
id,
nickname,
gender,
zodiac,
profession,
mbti,
hobbies,
childhood,
joy,
low,
future
} = frontendData
return {
id,
nickname,
gender,
zodiac,
profession,
mbti,
// 兴趣爱好转为JSON字符串
hobbies: Array.isArray(hobbies) ? JSON.stringify(hobbies) : hobbies,
// 童年经历
childhoodDate: childhood?.date || null,
childhoodContent: childhood?.text || null,
// 高光时刻(对应前端的joy
peakDate: joy?.date || null,
peakContent: joy?.text || null,
// 低谷时期(对应前端的low
valleyDate: low?.date || null,
valleyContent: low?.text || null,
// 未来期许
futureVision: future?.vision || null,
// 理想生活状态
idealLife: future?.ideal || null
}
}
/**
* 将后端数据格式转换为前端格式
* @param {Object} backendData - 后端数据
* @returns {Object} 前端格式数据
*/
export const transformToFrontendFormat = (backendData) => {
if (!backendData) return null
const {
id,
userId,
nickname,
gender,
zodiac,
profession,
mbti,
hobbies,
childhoodDate,
childhoodContent,
peakDate,
peakContent,
valleyDate,
valleyContent,
futureVision,
idealLife
} = backendData
return {
id,
userId,
nickname: nickname || '',
gender: gender || '',
zodiac: zodiac || '',
profession: profession || '',
mbti: mbti || '',
// 兴趣爱好从JSON字符串解析
hobbies: hobbies ? (typeof hobbies === 'string' ? JSON.parse(hobbies) : hobbies) : [],
// 童年经历
childhood: {
date: childhoodDate || '',
text: childhoodContent || ''
},
// 高光时刻
joy: {
date: peakDate || '',
text: peakContent || ''
},
// 低谷时期
low: {
date: valleyDate || '',
text: valleyContent || ''
},
// 未来期许
future: {
vision: futureVision || '',
ideal: idealLife || ''
}
}
}
export default {
getCurrentProfile,
createProfile,
updateProfile,
transformToFrontendFormat
}
@@ -0,0 +1,3 @@
<svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M25.9966 29.8365C24.4459 31.3668 22.7527 31.1252 21.1229 30.4003C19.3981 29.6593 17.8157 29.6271 15.996 30.4003C13.7174 31.3991 12.5148 31.1091 11.1539 29.8365C3.43193 21.7336 4.57124 9.39407 13.3376 8.94301C15.4738 9.05578 16.9612 10.1351 18.2113 10.2317C20.0785 9.84512 21.8666 8.73359 23.8604 8.87858C26.2498 9.07188 28.0537 10.0384 29.2405 11.7782C24.3035 14.7906 25.4744 21.4114 30 23.264C29.098 25.6803 27.9271 28.0806 25.9808 29.8526L25.9966 29.8365ZM18.0531 8.84636C17.8157 5.25403 20.6798 2.28996 23.9712 2C24.43 6.15614 20.2684 9.24908 18.0531 8.84636Z" fill="#090909"/>
</svg>

After

Width:  |  Height:  |  Size: 693 B

+10
View File
@@ -0,0 +1,10 @@
<svg width="33" height="33" viewBox="0 0 33 33" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_435_546)">
<path d="M15.5 3.75C13.2907 3.74977 11.1652 4.59569 9.56028 6.11395C7.95534 7.6322 6.99282 9.70748 6.87049 11.9134L6.66974 15.5296C6.37016 16.168 6.08818 16.8145 5.82412 17.4684C4.11912 21.6938 3.44812 25.4063 4.32812 25.7624C4.78874 25.948 5.58349 25.1766 6.48137 23.7934C6.84279 25.6833 7.78945 27.4116 9.18737 28.7337C7.77524 29.215 6.56112 29.8722 6.56112 30.5625C6.56112 31.2624 9.97112 31.2541 12.3897 31.25H12.3911C13.146 31.2472 13.7799 31.239 14.2886 31.1716C15.0918 31.278 15.9055 31.278 16.7086 31.1716C17.2174 31.2404 17.854 31.2473 18.6089 31.2486C21.0289 31.2541 24.4375 31.2624 24.4375 30.5625C24.4375 29.8722 23.2234 29.2164 21.8126 28.7337C23.2083 27.414 24.1543 25.6893 24.5172 23.803C25.411 25.1807 26.2044 25.948 26.6622 25.7624C27.5422 25.4063 26.874 21.6924 25.1662 17.4684C24.9055 16.8216 24.6271 16.1819 24.3316 15.5503L24.1295 11.9134C24.0072 9.70748 23.0446 7.6322 21.4397 6.11395C19.8348 4.59569 17.7093 3.74977 15.5 3.75Z" fill="#007AFF"/>
</g>
<defs>
<clipPath id="clip0_435_546">
<rect width="33" height="33" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 941 B

+7
View File
@@ -0,0 +1,7 @@
Reserved auth/login assets (copy your images here):
- login_bg.png
- login_illustration.png
- icon_eye_on.png
- icon_eye_off.png
@@ -0,0 +1,4 @@
<svg width="38" height="38" viewBox="0 0 38 38" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M25.0958 12.8883C25.7133 12.8883 26.315 12.9358 26.9008 13.015C25.8242 8.3125 20.8842 4.75 14.9467 4.75C8.2175 4.75 2.755 9.31 2.755 14.9308C2.755 18.1767 4.57584 21.0425 7.41 22.9108L5.81084 26.125L10.1808 24.2408C11.115 24.5733 12.0967 24.8425 13.1417 24.985C12.9992 24.3675 12.92 23.7342 12.92 23.0692C12.9042 17.4642 18.3667 12.8883 25.0958 12.8883ZM19 9.32583C19.1996 9.32583 19.3973 9.36515 19.5817 9.44154C19.7661 9.51792 19.9337 9.62989 20.0748 9.77103C20.216 9.91218 20.3279 10.0797 20.4043 10.2642C20.4807 10.4486 20.52 10.6462 20.52 10.8458C20.52 11.0454 20.4807 11.2431 20.4043 11.4275C20.3279 11.6119 20.216 11.7795 20.0748 11.9206C19.9337 12.0618 19.7661 12.1737 19.5817 12.2501C19.3973 12.3265 19.1996 12.3658 19 12.3658C18.5969 12.3658 18.2103 12.2057 17.9252 11.9206C17.6401 11.6356 17.48 11.249 17.48 10.8458C17.48 10.4427 17.6401 10.0561 17.9252 9.77103C18.2103 9.48598 18.5969 9.32583 19 9.32583ZM10.8775 12.3817C10.4744 12.3817 10.0878 12.2215 9.8027 11.9365C9.51765 11.6514 9.35751 11.2648 9.35751 10.8617C9.35751 10.4585 9.51765 10.0719 9.8027 9.78686C10.0878 9.50181 10.4744 9.34167 10.8775 9.34167C11.2806 9.34167 11.6673 9.50181 11.9523 9.78686C12.2374 10.0719 12.3975 10.4585 12.3975 10.8617C12.3975 11.2648 12.2374 11.6514 11.9523 11.9365C11.6673 12.2215 11.2806 12.3817 10.8775 12.3817Z" fill="#34C759"/>
<path d="M35.245 23.0691C35.245 18.5725 30.7008 14.9308 25.0958 14.9308C19.4908 14.9308 14.9467 18.5725 14.9467 23.0691C14.9467 27.5658 19.4908 31.2075 25.0958 31.2075C26.0142 31.2075 26.9008 31.0808 27.74 30.8908L33.2183 33.25L31.3183 29.45C33.6933 27.9616 35.245 25.6816 35.245 23.0691ZM22.04 22.5625C21.7394 22.5625 21.4455 22.4733 21.1955 22.3063C20.9456 22.1393 20.7507 21.9019 20.6357 21.6241C20.5206 21.3464 20.4905 21.0408 20.5492 20.7459C20.6078 20.4511 20.7526 20.1802 20.9652 19.9676C21.1778 19.7551 21.4486 19.6103 21.7435 19.5517C22.0383 19.493 22.3439 19.5231 22.6217 19.6382C22.8994 19.7532 23.1368 19.948 23.3038 20.198C23.4708 20.4479 23.56 20.7418 23.56 21.0425C23.5758 21.8816 22.8792 22.5625 22.04 22.5625ZM28.1358 22.5625C27.7327 22.5625 27.3461 22.4023 27.061 22.1173C26.776 21.8322 26.6158 21.4456 26.6158 21.0425C26.6158 20.6393 26.776 20.2527 27.061 19.9676C27.3461 19.6826 27.7327 19.5225 28.1358 19.5225C28.539 19.5225 28.9256 19.6826 29.2106 19.9676C29.4957 20.2527 29.6558 20.6393 29.6558 21.0425C29.6558 21.4456 29.4957 21.8322 29.2106 22.1173C28.9256 22.4023 28.539 22.5625 28.1358 22.5625Z" fill="#34C759"/>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

@@ -0,0 +1,4 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10 18.75L6.14001 16.6919C5.0395 16.1066 4.11919 15.2325 3.4779 14.1637C2.83661 13.0948 2.49854 11.8715 2.5 10.625V2.5C2.5 2.16848 2.6317 1.85054 2.86612 1.61612C3.10054 1.3817 3.41848 1.25 3.75 1.25H16.25C16.5815 1.25 16.8995 1.3817 17.1339 1.61612C17.3683 1.85054 17.5 2.16848 17.5 2.5V10.625C17.5015 11.8715 17.1634 13.0948 16.5221 14.1637C15.8808 15.2325 14.9605 16.1066 13.86 16.6919L10 18.75ZM3.75 2.5V10.625C3.74931 11.6448 4.02618 12.6456 4.55093 13.52C5.07568 14.3945 5.82853 15.1096 6.72875 15.5887L10 17.3331L13.2713 15.5894C14.1716 15.1102 14.9245 14.3949 15.4492 13.5204C15.974 12.6458 16.2508 11.6449 16.25 10.625V2.5H3.75Z" fill="#FE904B" fill-opacity="0.68"/>
<path d="M10 15.7981V3.75H15V10.5031C15 11.2953 14.7848 12.0726 14.3776 12.7521C13.9703 13.4316 13.3861 13.9878 12.6875 14.3612L10 15.7981Z" fill="#FE904B" fill-opacity="0.68"/>
</svg>

After

Width:  |  Height:  |  Size: 966 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 195 KiB

+3
View File
@@ -0,0 +1,3 @@
<svg width="13" height="21" viewBox="0 0 13 21" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.6667 0.083313H2.33333C1.7808 0.083313 1.25089 0.302806 0.860194 0.693507C0.469493 1.08421 0.25 1.61411 0.25 2.16665V18.8333C0.25 19.3858 0.469493 19.9158 0.860194 20.3065C1.25089 20.6972 1.7808 20.9166 2.33333 20.9166H10.6667C11.2192 20.9166 11.7491 20.6972 12.1398 20.3065C12.5305 19.9158 12.75 19.3858 12.75 18.8333V2.16665C12.75 1.61411 12.5305 1.08421 12.1398 0.693507C11.7491 0.302806 11.2192 0.083313 10.6667 0.083313ZM7.54167 19.875H5.45833V18.8333H7.54167V19.875ZM10.6667 17.7916H2.33333V3.20831H10.6667V17.7916Z" fill="#FE904B" fill-opacity="0.68"/>
</svg>

After

Width:  |  Height:  |  Size: 675 B

@@ -0,0 +1,3 @@
<svg width="14" height="19" viewBox="0 0 14 19" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M2 5C2 2.238 4.238 0 7 0C9.762 0 12 2.238 12 5V8H12.4C13.28 8 14 8.72 14 9.6V16.6C14 17.92 12.92 19 11.6 19H2.4C1.08 19 0 17.92 0 16.6V9.6C0 8.72 0.72 8 1.6 8H2V5ZM10 5V8H4V5C4 3.342 5.342 2 7 2C8.658 2 10 3.342 10 5ZM7 10.25C6.60234 10.2496 6.21639 10.3846 5.90573 10.6329C5.59507 10.8811 5.37822 11.2278 5.2909 11.6157C5.20357 12.0037 5.25098 12.4098 5.42531 12.7672C5.59965 13.1246 5.89051 13.412 6.25 13.582V16C6.25 16.1989 6.32902 16.3897 6.46967 16.5303C6.61032 16.671 6.80109 16.75 7 16.75C7.19891 16.75 7.38968 16.671 7.53033 16.5303C7.67098 16.3897 7.75 16.1989 7.75 16V13.582C8.10949 13.412 8.40035 13.1246 8.57469 12.7672C8.74902 12.4098 8.79643 12.0037 8.7091 11.6157C8.62178 11.2278 8.40493 10.8811 8.09427 10.6329C7.78361 10.3846 7.39766 10.2496 7 10.25Z" fill="#FE904B" fill-opacity="0.68"/>
</svg>

After

Width:  |  Height:  |  Size: 959 B

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 1.1 MiB

+7
View File
@@ -0,0 +1,7 @@
Reserved common assets (copy your images here with the exact names):
- logo.png (already present, replace if needed)
- icon_back.png
- icon_close.png
- icon_bluetooth.png
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 47 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 670 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 79 KiB

@@ -0,0 +1,8 @@
Reserved device assets (copy your images here):
- device_placeholder.png
- bluetooth_on.png
- bluetooth_off.png
- search_illustration.png
- device_connected.png
+9
View File
@@ -0,0 +1,9 @@
Reserved home assets (copy your images here):
- banner.png
- tab_home.png
- tab_home_active.png
- tab_profile.png
- tab_profile_active.png
- icon_settings.png
@@ -0,0 +1,4 @@
<svg width="23" height="24" viewBox="0 0 23 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M20.5135 0H9.32432C8.66487 0 8.03242 0.252856 7.56611 0.702944C7.09981 1.15303 6.83784 1.76348 6.83784 2.4V4.8H9.32432V2.4H20.5135V21.6H9.32432V19.2H6.83784V21.6C6.83784 22.2365 7.09981 22.847 7.56611 23.2971C8.03242 23.7471 8.66487 24 9.32432 24H20.5135C21.173 24 21.8054 23.7471 22.2717 23.2971C22.738 22.847 23 22.2365 23 21.6V2.4C23 1.76348 22.738 1.15303 22.2717 0.702944C21.8054 0.252856 21.173 0 20.5135 0Z" fill="#FF8D28" fill-opacity="0.41"/>
<path d="M7.96922 16.308L6.21624 18L2.86102e-05 12L6.21624 6L7.96922 7.692L4.76165 10.8H16.7838V13.2H4.76165L7.96922 16.308Z" fill="#FF8D28" fill-opacity="0.41"/>
</svg>

After

Width:  |  Height:  |  Size: 727 B

@@ -0,0 +1,6 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="24" height="24" fill="white"/>
<path d="M3.05273 8C3.05273 5.172 3.05273 3.757 3.978 2.879C4.90221 2 6.39168 2 9.36852 2H15.6843C18.6612 2 20.1506 2 21.0748 2.879C22.0001 3.757 22.0001 5.172 22.0001 8V16C22.0001 18.828 22.0001 20.243 21.0748 21.121C20.1506 22 18.6612 22 15.6843 22H9.36852C6.39168 22 4.90221 22 3.978 21.121C3.05273 20.243 3.05273 18.828 3.05273 16V8Z" stroke="black" stroke-width="1.5"/>
<path opacity="0.5" d="M8.31579 2.5V22M2 12H4.10526M2 16H4.10526M2 8H4.10526" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
<path d="M12 6.5H17.2632M12 10H17.2632" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 758 B

@@ -0,0 +1,3 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M19.9999 3.33337C29.2049 3.33337 36.6666 10.795 36.6666 20C36.6666 29.205 29.2049 36.6667 19.9999 36.6667C17.3399 36.6704 14.718 36.0348 12.3549 34.8134L5.97827 36.5917C5.6225 36.691 5.24674 36.6939 4.88947 36.6002C4.53221 36.5064 4.20629 36.3194 3.94512 36.0582C3.68394 35.797 3.49689 35.4711 3.40314 35.1138C3.3094 34.7566 3.31231 34.3808 3.4116 34.025L5.1916 27.6534C3.96719 25.2883 3.3298 22.6633 3.33327 20C3.33327 10.795 10.7949 3.33337 19.9999 3.33337ZM19.9999 5.83337C16.2427 5.83337 12.6394 7.32593 9.98259 9.98269C7.32582 12.6395 5.83327 16.2428 5.83327 20C5.83327 22.45 6.45493 24.805 7.6216 26.895L7.8716 27.345L6.01827 33.985L12.6633 32.1317L13.1133 32.3817C15.0039 33.4329 17.1102 34.0371 19.2705 34.1481C21.4309 34.259 23.5879 33.8738 25.5763 33.0219C27.5647 32.17 29.3316 30.874 30.7415 29.2334C32.1514 27.5928 33.1669 25.6511 33.71 23.5572C34.2532 21.4633 34.3096 19.2728 33.875 17.1537C33.4404 15.0346 32.5262 13.0432 31.2027 11.3321C29.8791 9.62111 28.1813 8.23591 26.2394 7.28275C24.2975 6.32959 22.1631 5.83379 19.9999 5.83337Z" fill="#FFA629"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

+3
View File
@@ -0,0 +1,3 @@
<svg width="39" height="39" viewBox="0 0 39 39" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M17.5 33.2C20.5765 33.2 23.527 32.02 25.7024 29.9196C27.8779 27.8192 29.1 24.9704 29.1 22C29.1 19.0296 27.8779 16.1808 25.7024 14.0804C23.527 11.98 20.5765 10.8 17.5 10.8C14.4235 10.8 11.473 11.98 9.29756 14.0804C7.12214 16.1808 5.9 19.0296 5.9 22C5.9 24.9704 7.12214 27.8192 9.29756 29.9196C11.473 32.02 14.4235 33.2 17.5 33.2ZM17.5 8C19.4042 8 21.2897 8.36212 23.0489 9.06569C24.8081 9.76925 26.4066 10.8005 27.753 12.1005C29.0995 13.4005 30.1676 14.9439 30.8963 16.6424C31.6249 18.341 32 20.1615 32 22C32 25.713 30.4723 29.274 27.753 31.8995C25.0338 34.525 21.3456 36 17.5 36C9.4815 36 3 29.7 3 22C3 18.287 4.52767 14.726 7.24695 12.1005C9.96623 9.475 13.6544 8 17.5 8ZM18.225 15V22.35L24.75 26.088L23.6625 27.81L16.05 23.4V15H18.225Z" fill="#FE904B" fill-opacity="0.5"/>
</svg>

After

Width:  |  Height:  |  Size: 887 B

@@ -0,0 +1,3 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.2732 7.39199L7.80658 9.36533C10.0385 11.0562 12.2143 12.8198 14.3306 14.6533C15.1839 15.3933 15.2532 16.692 14.4839 17.5187C12.6326 19.5102 10.7169 21.441 8.73991 23.308L8.82391 23.448C11.0826 21.6893 13.3096 19.8904 15.5039 18.052C15.8789 17.7351 16.3573 17.5671 16.848 17.5802C17.3388 17.5932 17.8076 17.7863 18.1652 18.1227C19.916 19.7728 21.619 21.4731 23.2719 23.2213L23.9892 22.7413C22.5472 20.8286 21.0506 18.9576 19.5012 17.1307C19.1682 16.7376 18.9991 16.2316 19.0289 15.7173C19.0586 15.2031 19.2851 14.7199 19.6612 14.368C21.2248 12.9126 22.7275 11.3931 24.1652 9.81333L22.6199 7.82666C21.3479 9.50548 20.0346 11.1526 18.6812 12.7667C18.4989 12.9836 18.2726 13.1593 18.0172 13.2821C17.7619 13.4049 17.4833 13.4721 17.2001 13.4792C16.9168 13.4862 16.6353 13.433 16.3741 13.3231C16.113 13.2131 15.8781 13.0489 15.6852 12.8413C13.9386 10.9688 12.1337 9.1515 10.2732 7.39199ZM9.05991 4.94666C9.43538 4.64684 9.90624 4.49221 10.3864 4.51106C10.8665 4.52991 11.3238 4.72099 11.6746 5.04933C13.5447 6.80123 15.3607 8.61008 17.1199 10.4733C18.4561 8.86038 19.752 7.21454 21.0066 5.53733C21.1913 5.2905 21.4304 5.08959 21.7054 4.95019C21.9803 4.81079 22.2837 4.73666 22.592 4.73357C22.9003 4.73047 23.2051 4.79849 23.4828 4.93234C23.7605 5.06619 24.0036 5.26226 24.1932 5.50533L26.6132 8.61866C27.1999 9.37333 27.1746 10.448 26.5306 11.172C25.0668 12.8064 23.5352 14.3789 21.9399 15.8853C23.5327 17.783 25.0707 19.726 26.5519 21.712C27.2386 22.6333 27.0052 23.936 26.0572 24.568L24.2826 25.752C23.8881 26.0153 23.4128 26.1298 22.9417 26.075C22.4706 26.0203 22.0343 25.8 21.7106 25.4533C20.1113 23.7461 18.4635 22.085 16.7692 20.472C14.5106 22.3653 12.1959 24.1947 9.87324 26.0133C9.64999 26.1878 9.39221 26.3129 9.11697 26.3803C8.84173 26.4476 8.55532 26.4557 8.27671 26.4041C7.9981 26.3524 7.73366 26.2421 7.5009 26.0805C7.26814 25.9189 7.07237 25.7096 6.92658 25.4667L6.17724 24.2213C5.94132 23.8287 5.8473 23.3671 5.91088 22.9135C5.97447 22.4599 6.19181 22.0419 6.52658 21.7293C8.42732 19.9498 10.2709 18.1102 12.0546 16.2133C9.93732 14.399 7.76011 12.6558 5.52658 10.9867C5.28145 10.8035 5.08153 10.5666 4.94214 10.2942C4.80275 10.0218 4.7276 9.72104 4.72245 9.41508C4.7173 9.10911 4.78229 8.80603 4.91243 8.52907C5.04258 8.25212 5.23442 8.00865 5.47324 7.81733L9.05991 4.94666Z" fill="#FFA629" fill-opacity="0.59"/>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

+4
View File
@@ -0,0 +1,4 @@
<svg width="114" height="114" viewBox="0 0 114 114" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M21.9643 11C17.4651 11 13.1501 12.6012 9.96872 15.4512C6.7873 18.3013 5 22.1668 5 26.1975V62.6713C5 66.702 6.7873 70.5675 9.96872 73.4176C13.1501 76.2676 17.4651 77.8688 21.9643 77.8688H25.3571V83.9174C25.3564 85.0402 25.7028 86.1413 26.3579 87.0985C27.0131 88.0557 27.9513 88.8317 29.0687 89.3404C30.1861 89.8491 31.4388 90.0706 32.6882 89.9803C33.9375 89.8901 35.1345 89.4917 36.1464 88.8292L52.8868 77.8688H83.0357C87.5349 77.8688 91.8499 76.2676 95.0313 73.4176C98.2127 70.5675 100 66.702 100 62.6713V26.1975C100 22.1668 98.2127 18.3013 95.0313 15.4512C91.8499 12.6012 87.5349 11 83.0357 11H21.9643ZM11.7857 26.1975C11.7857 23.7791 12.8581 21.4598 14.7669 19.7497C16.6758 18.0397 19.2648 17.079 21.9643 17.079H83.0357C85.7352 17.079 88.3242 18.0397 90.2331 19.7497C92.1419 21.4598 93.2143 23.7791 93.2143 26.1975V62.6713C93.2143 65.0897 92.1419 67.409 90.2331 69.1191C88.3242 70.8291 85.7352 71.7898 83.0357 71.7898H50.6679L32.1429 83.9174V71.7898H21.9643C19.2648 71.7898 16.6758 70.8291 14.7669 69.1191C12.8581 67.409 11.7857 65.0897 11.7857 62.6713V26.1975Z" fill="#FFCD29"/>
<circle cx="92" cy="20" r="15" fill="#FF9500"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

@@ -0,0 +1,46 @@
<svg width="53" height="53" viewBox="0 0 53 53" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12.8896 45.7746C12.8896 43.9941 15.3782 37.7045 15.3782 37.7045C17.7756 36.9634 35.0875 37.5927 37.7251 37.5927C37.7251 37.5927 40.1101 42.6981 40.1101 45.7746C40.1101 48.8511 32.7895 51.3438 26.5247 51.3438C20.26 51.3438 12.8896 48.8511 12.8896 45.7746Z" fill="#8B5738"/>
<path d="M26.5249 43.5801C32.7107 43.5801 37.7252 41.4018 37.7252 38.7148C37.7252 36.0278 32.7107 33.8496 26.5249 33.8496C20.339 33.8496 15.3245 36.0278 15.3245 38.7148C15.3245 41.4018 20.339 43.5801 26.5249 43.5801Z" fill="#FFB17A"/>
<path d="M16.0119 43.9527C17.2623 44.6649 19.2416 44.6111 20.7777 44.8885C22.7321 45.2446 23.8377 46.7352 27.6056 46.669C28.9513 46.6441 32.0899 46.4909 31.916 46.4951C31.8084 46.2756 31.1955 46.0645 30.003 45.8781C28.9099 45.7042 28.1232 45.7208 26.8562 45.7208C25.8873 45.7208 24.5581 45.4309 23.2166 44.6939C22.6037 44.3585 21.6514 43.9859 20.7198 43.7954C19.2871 43.5014 18.2892 43.812 16.2893 42.8638C15.3328 42.4124 14.5585 41.4766 14.3763 40.3877L13.9043 41.7458C14.5254 42.8845 15.0927 43.431 16.0119 43.9527ZM36.6198 42.8389C33.4149 44.8761 30.707 44.6484 30.707 44.6484C30.4792 44.7974 32.3466 45.1991 33.7337 44.9672C36.4583 44.5117 38.1145 43.1453 39.1911 41.3152C39.042 40.8514 38.7149 39.9736 38.7149 39.9736C38.4044 40.7231 38.0441 41.9321 36.6198 42.8389Z" fill="#CC8552"/>
<path d="M30.8519 48.6192C29.0797 48.9422 27.382 48.3915 25.6388 47.9816C23.2497 47.4226 18.7612 47.5013 18.7612 47.5013C18.4921 47.6338 20.8771 48.4992 22.0365 48.5985C23.0923 48.6896 24.2103 48.5985 25.2123 49.0084C26.6243 49.584 27.9907 49.8738 29.3571 49.8738C31.9533 49.8738 35.3444 48.3087 36.1519 46.2756C36.1519 46.2756 34.6695 47.1452 33.5184 47.729C32.682 48.1555 31.7794 48.4536 30.8519 48.6192Z" fill="#A86D44"/>
<path d="M14.8317 46.1597C15.1836 46.7228 15.5438 47.2445 16.0159 47.7124C16.815 48.5074 17.8253 48.967 18.4754 48.8511C18.4754 48.8511 16.1732 46.9919 16.4175 46.5075C16.5417 46.259 21.32 46.6524 21.32 46.6524C21.32 46.6524 19.8708 45.8905 18.4588 45.6172C17.5438 45.4392 15.8709 45.7953 14.3472 44.7229C13.5067 44.1308 13.3783 43.4186 13.3783 43.4186C13.3783 43.4186 13.076 44.4496 13.0139 44.843C13.8959 45.7663 14.7157 45.9733 14.8317 46.1597ZM39.046 44.1142C38.2096 44.8637 36.8888 46.3791 36.8888 47.1617C36.8888 47.1617 38.3711 45.7249 39.369 45.0665C39.6174 44.9051 39.7955 44.698 39.9901 44.4786C39.9901 44.4786 39.8907 43.7374 39.7334 43.2115C39.6713 43.5635 39.4187 43.783 39.046 44.1142Z" fill="#A86D44"/>
<path d="M26.5 42.0356C37.6504 42.0356 46.6897 32.9964 46.6897 21.8459C46.6897 10.6955 37.6504 1.65625 26.5 1.65625C15.3495 1.65625 6.3103 10.6955 6.3103 21.8459C6.3103 32.9964 15.3495 42.0356 26.5 42.0356Z" fill="url(#paint0_radial_739_1078)"/>
<path d="M26.5 42.0356C37.6504 42.0356 46.6897 32.9964 46.6897 21.8459C46.6897 10.6955 37.6504 1.65625 26.5 1.65625C15.3495 1.65625 6.3103 10.6955 6.3103 21.8459C6.3103 32.9964 15.3495 42.0356 26.5 42.0356Z" fill="url(#paint1_radial_739_1078)"/>
<path opacity="0.7" d="M19.0096 9.26257C20.8895 9.42406 24.7403 10.8319 21.9371 14.4425C20.0945 16.8151 17.7923 19.022 16.9766 20.9309C14.6206 26.4503 20.5251 28.7028 22.3594 29.2784C23.9494 29.7794 27.0135 30.8973 27.8747 32.3259C31.1996 37.8329 21.0882 39.3359 21.0882 39.3359C21.0882 39.3359 28.5289 42.048 34.6571 37.9033C36.9758 36.334 39.0171 32.6819 37.0214 29.3943C36.0525 27.8001 33.3818 26.3634 32.7275 25.8499C29.9285 23.6388 28.2018 21.3532 29.5931 18.7901C30.1562 17.755 31.3321 17.2167 32.4708 16.902C35.6591 16.0201 39.6382 16.1774 40.6775 12.2397C41.576 8.84437 35.0173 1.25874 24.8438 2.49265C20.6203 3.00195 16.8193 4.76999 13.7096 7.67671C7.97074 13.0471 8.46761 17.49 8.46761 17.49C8.46761 17.49 12.2066 8.67046 19.0096 9.26257Z" fill="url(#paint2_linear_739_1078)"/>
<path opacity="0.39" d="M41.311 10.0286C39.87 7.93344 37.8784 6.26891 35.7791 4.83211C32.4293 2.54235 28.0982 2.35602 28.0982 2.35602C33.4148 4.89836 32.8186 8.26883 31.0836 10.6787C28.9719 13.6144 24.7361 15.5977 23.6305 19.1711C22.9846 21.258 23.3821 23.4525 25.4151 24.7858C27.4482 26.1191 34.7646 29.6593 35.0172 33.8496C35.2077 37.0048 32.6985 38.9923 32.6985 38.9923C35.6342 38.3587 41.812 34.0359 39.9818 29.5889C39.0957 27.4358 35.0669 26.0073 37.0958 23.5519C37.8494 22.6368 43.1121 22.1482 43.6049 16.8234C43.8326 14.3348 42.4911 11.7428 41.311 10.0286Z" fill="url(#paint3_linear_739_1078)"/>
<path opacity="0.85" d="M10.2315 25.5311C10.2315 25.5311 7.20883 17.5149 13.4156 10.0907C18.5293 3.97087 23.1461 6.44696 22.4339 9.05555C21.7176 11.6641 19.4941 12.4674 16.1443 15.0884C11.718 18.55 10.2315 25.5311 10.2315 25.5311Z" fill="url(#paint4_linear_739_1078)"/>
<path d="M38.4912 14.397C37.6838 13.9953 37.0379 13.4902 36.7563 12.633L35.5597 8.67047L34.363 12.633C34.0815 13.486 33.4314 13.9953 32.6281 14.397L30.6282 15.2623L32.6861 16.1153C33.4935 16.517 34.0815 17.0677 34.3672 17.9206L35.5638 21.8501L36.7604 17.9206C37.042 17.0677 37.6341 16.517 38.4415 16.1153L40.4994 15.2623L38.4912 14.397Z" fill="url(#paint5_linear_739_1078)"/>
<path d="M34.4956 28.7235C33.6882 28.3219 33.0423 27.8167 32.7607 26.9596L31.5641 22.997L30.3674 26.9596C30.0858 27.8126 29.4358 28.3219 28.6325 28.7235L26.6326 29.5889L28.6905 30.4419C29.4979 30.8435 30.0858 31.3942 30.3716 32.2472L31.5682 36.1767L32.7648 32.2472C33.0464 31.3942 33.6385 30.8435 34.4459 30.4419L36.5038 29.5889L34.4956 28.7235Z" fill="url(#paint6_linear_739_1078)"/>
<path d="M44.0314 22.347C43.4227 22.0447 42.9341 21.6596 42.7188 21.0178L41.8162 18.0241L40.9135 21.0178C40.6982 21.6637 40.2096 22.0447 39.6009 22.347L38.0938 23.0012L39.6465 23.6471C40.2552 23.9494 40.7023 24.3634 40.9135 25.0094L41.8162 27.9741L42.7188 25.0094C42.9341 24.3634 43.3772 23.9494 43.9859 23.6471L45.5386 23.0012L44.0314 22.347Z" fill="white"/>
<defs>
<radialGradient id="paint0_radial_739_1078" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(33.0529 9.1753) rotate(-3.714) scale(31.5449)">
<stop offset="0.104" stop-color="#CE93D8"/>
<stop offset="1" stop-color="#AB47BC"/>
</radialGradient>
<radialGradient id="paint1_radial_739_1078" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(22.0898 19.1028) scale(28.7314)">
<stop offset="0.28" stop-color="#81D4FA" stop-opacity="0"/>
<stop offset="0.964" stop-color="#81D4FA" stop-opacity="0.9"/>
</radialGradient>
<linearGradient id="paint2_linear_739_1078" x1="30.9264" y1="4.41017" x2="14.8823" y2="45.9638" gradientUnits="userSpaceOnUse">
<stop stop-color="#673AB7"/>
<stop offset="0.937" stop-color="#673AB7" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint3_linear_739_1078" x1="33.4885" y1="10.3242" x2="33.4885" y2="44.7506" gradientUnits="userSpaceOnUse">
<stop offset="0.235" stop-color="#1D44B3"/>
<stop offset="0.884" stop-color="#2044B3" stop-opacity="0.074"/>
<stop offset="0.936" stop-color="#2144B3" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint4_linear_739_1078" x1="13.6256" y1="6.82476" x2="17.2398" y2="20.2969" gradientUnits="userSpaceOnUse">
<stop offset="0.227" stop-color="white"/>
<stop offset="1" stop-color="white" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint5_linear_739_1078" x1="35.5559" y1="11.9913" x2="35.5559" y2="21.6915" gradientUnits="userSpaceOnUse">
<stop offset="0.261" stop-color="white"/>
<stop offset="1" stop-color="white" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint6_linear_739_1078" x1="31.5603" y1="24.0823" x2="31.5603" y2="35.291" gradientUnits="userSpaceOnUse">
<stop stop-color="white"/>
<stop offset="1" stop-color="white" stop-opacity="0"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 7.5 KiB

@@ -0,0 +1,11 @@
<svg width="45" height="45" viewBox="0 0 45 45" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_838_293)">
<path d="M35 37.5H7.5V10H24.025L26.525 7.5H7.5C6.83696 7.5 6.20107 7.76339 5.73223 8.23223C5.26339 8.70107 5 9.33696 5 10V37.5C5 38.163 5.26339 38.7989 5.73223 39.2678C6.20107 39.7366 6.83696 40 7.5 40H35C35.663 40 36.2989 39.7366 36.7678 39.2678C37.2366 38.7989 37.5 38.163 37.5 37.5V18.75L35 21.25V37.5Z" fill="#FFA629"/>
<path d="M41.9126 7.29999L37.7001 3.08749C37.5131 2.90003 37.291 2.7513 37.0465 2.64981C36.802 2.54833 36.5398 2.49609 36.2751 2.49609C36.0103 2.49609 35.7482 2.54833 35.5036 2.64981C35.2591 2.7513 35.037 2.90003 34.8501 3.08749L17.7126 20.325L16.3251 26.3375C16.266 26.6289 16.2722 26.9299 16.3433 27.2186C16.4144 27.5074 16.5486 27.7768 16.7362 28.0075C16.9239 28.2382 17.1603 28.4244 17.4286 28.5528C17.6968 28.6812 17.9902 28.7485 18.2876 28.75C18.4413 28.7668 18.5964 28.7668 18.7501 28.75L24.8126 27.4125L41.9126 10.15C42.1 9.96304 42.2488 9.74094 42.3502 9.49642C42.4517 9.25189 42.504 8.98974 42.504 8.72499C42.504 8.46024 42.4517 8.19809 42.3502 7.95357C42.2488 7.70904 42.1 7.48694 41.9126 7.29999ZM23.5126 25.1L18.9376 26.1125L20.0001 21.575L32.9001 8.58749L36.4251 12.1125L23.5126 25.1ZM37.8376 10.7L34.3126 7.17499L36.2501 5.19999L39.8001 8.74999L37.8376 10.7Z" fill="#FFA629"/>
</g>
<defs>
<clipPath id="clip0_838_293">
<rect width="45" height="45" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

+3
View File
@@ -0,0 +1,3 @@
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5.99948 1C6.19836 1 6.38909 1.07524 6.52972 1.20918C6.67034 1.34311 6.74935 1.52476 6.74935 1.71417V2.42834H9.74883V1.71417C9.74883 1.52476 9.82783 1.34311 9.96846 1.20918C10.1091 1.07524 10.2998 1 10.4987 1C10.6976 1 10.8883 1.07524 11.0289 1.20918C11.1696 1.34311 11.2486 1.52476 11.2486 1.71417V2.42834H14.248V1.71417C14.248 1.52476 14.327 1.34311 14.4677 1.20918C14.6083 1.07524 14.799 1 14.9979 1C15.1968 1 15.3875 1.07524 15.5282 1.20918C15.6688 1.34311 15.7478 1.52476 15.7478 1.71417V2.42834C16.3444 2.42834 16.9166 2.65406 17.3385 3.05586C17.7604 3.45766 17.9974 4.00261 17.9974 4.57084V8.1931C17.4598 8.28228 16.9484 8.48001 16.4977 8.77301V4.57084C16.4977 4.38143 16.4187 4.19978 16.278 4.06585C16.1374 3.93192 15.9467 3.85667 15.7478 3.85667H5.24961C5.05073 3.85667 4.86 3.93192 4.71937 4.06585C4.57874 4.19978 4.49974 4.38143 4.49974 4.57084V18.8542C4.49974 19.0436 4.57874 19.2253 4.71937 19.3592C4.86 19.4931 5.05073 19.5684 5.24961 19.5684H7.56971V19.5712C7.44286 20.0446 7.4786 20.5441 7.67169 20.9967H5.24961C4.65298 20.9967 4.08078 20.771 3.6589 20.3692C3.23701 19.9674 3 19.4224 3 18.8542V4.57084C3 4.00261 3.23701 3.45766 3.6589 3.05586C4.08078 2.65406 4.65298 2.42834 5.24961 2.42834V1.71417C5.24961 1.52476 5.32861 1.34311 5.46924 1.20918C5.60987 1.07524 5.8006 1 5.99948 1ZM13.4982 10.9984C13.6586 10.9984 13.8071 11.0469 13.9301 11.1283L12.5653 12.4267H7.49922C7.30034 12.4267 7.10961 12.3514 6.96898 12.2175C6.82835 12.0836 6.74935 11.9019 6.74935 11.7125C6.74935 11.5231 6.82835 11.3415 6.96898 11.2075C7.10961 11.0736 7.30034 10.9984 7.49922 10.9984H13.4982ZM8.99296 15.9047C8.96929 15.7328 8.88076 15.5749 8.74389 15.4606C8.60701 15.3463 8.43114 15.2833 8.24909 15.2834H7.49922C7.30034 15.2834 7.10961 15.3586 6.96898 15.4925C6.82835 15.6265 6.74935 15.8081 6.74935 15.9975C6.74935 16.1869 6.82835 16.3686 6.96898 16.5025C7.10961 16.6365 7.30034 16.7117 7.49922 16.7117H8.24909C8.32907 16.7118 8.40856 16.6998 8.48455 16.676C8.63052 16.4056 8.79999 16.1485 8.99296 15.9047ZM7.49922 6.71335C7.30034 6.71335 7.10961 6.78859 6.96898 6.92252C6.82835 7.05645 6.74935 7.2381 6.74935 7.42751C6.74935 7.61692 6.82835 7.79857 6.96898 7.93251C7.10961 8.06644 7.30034 8.14168 7.49922 8.14168H13.4982C13.6971 8.14168 13.8878 8.06644 14.0284 7.93251C14.169 7.79857 14.248 7.61692 14.248 7.42751C14.248 7.2381 14.169 7.05645 14.0284 6.92252C13.8878 6.78859 13.6971 6.71335 13.4982 6.71335H7.49922ZM17.0046 10.2199C17.4435 9.80189 18.0388 9.56704 18.6595 9.56704C19.2803 9.56704 19.8756 9.80189 20.3145 10.2199C20.7534 10.6379 21 11.2049 21 11.7961C21 12.3873 20.7534 12.9542 20.3145 13.3723L13.8821 19.497C13.3475 20.0042 12.6791 20.3649 11.9474 20.5411L10.1313 20.9739C9.97793 21.0105 9.81726 21.0086 9.66491 20.9685C9.51256 20.9283 9.37373 20.8513 9.26196 20.7448C9.15018 20.6384 9.06928 20.5061 9.02713 20.361C8.98498 20.2159 8.98302 20.0629 9.02145 19.9169L9.47737 18.1886C9.66034 17.4901 10.0383 16.8531 10.5722 16.346L17.0046 10.2199Z" fill="#FFA629"/>
</svg>

After

Width:  |  Height:  |  Size: 3.0 KiB

+14
View File
@@ -0,0 +1,14 @@
<svg width="32" height="31" viewBox="0 0 32 31" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_35_520)">
<path d="M15.7574 0C7.28215 0 0.411621 6.87053 0.411621 15.3458C0.411621 23.821 7.28215 30.6915 15.7574 30.6915C24.2326 30.6915 31.1032 23.821 31.1032 15.3458C31.1032 6.87053 24.2326 0 15.7574 0ZM19.1536 8.78553C20.9771 8.75704 22.6262 9.74837 23.4079 11.4384C24.131 13.6726 23.5259 15.9456 22.2506 17.6585C21.4088 18.8201 20.4028 19.8219 19.3742 20.6972C18.428 21.578 16.31 23.3211 15.7486 23.3695C15.2525 23.2746 14.6956 22.7127 14.3017 22.424C12.0885 20.7415 9.7065 18.6964 8.49848 16.4594C7.48564 14.3117 7.48379 11.6545 9.06014 10.008C11.1041 8.16528 14.1854 8.52595 15.7486 10.4511C16.1685 9.90646 16.6849 9.47752 17.2976 9.16546C17.9186 8.91763 18.5458 8.79504 19.1536 8.78553Z" fill="url(#paint0_linear_35_520)"/>
</g>
<defs>
<linearGradient id="paint0_linear_35_520" x1="15.7574" y1="0" x2="15.7574" y2="30.6915" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFA629"/>
<stop offset="1" stop-color="#FB784C"/>
</linearGradient>
<clipPath id="clip0_35_520">
<rect width="30.6915" height="30.6915" fill="white" transform="translate(0.411621)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 1.0 MiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 986 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 1.0 MiB

+5
View File
@@ -0,0 +1,5 @@
<svg width="41" height="41" viewBox="0 0 41 41" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.55212 9.95834C5.55212 9.61853 5.68711 9.29264 5.92739 9.05236C6.16767 8.81208 6.49357 8.67709 6.83337 8.67709H34.1667C34.5065 8.67709 34.8324 8.81208 35.0727 9.05236C35.313 9.29264 35.448 9.61853 35.448 9.95834C35.448 10.2982 35.313 10.624 35.0727 10.8643C34.8324 11.1046 34.5065 11.2396 34.1667 11.2396H6.83337C6.49357 11.2396 6.16767 11.1046 5.92739 10.8643C5.68711 10.624 5.55212 10.2982 5.55212 9.95834Z" fill="#FFA629"/>
<path opacity="0.7" d="M5.55212 20.5C5.55212 20.1602 5.68711 19.8343 5.92739 19.594C6.16767 19.3537 6.49357 19.2188 6.83337 19.2188H25.625C25.9648 19.2188 26.2907 19.3537 26.531 19.594C26.7713 19.8343 26.9063 20.1602 26.9063 20.5C26.9063 20.8398 26.7713 21.1657 26.531 21.406C26.2907 21.6463 25.9648 21.7812 25.625 21.7812H6.83337C6.49357 21.7812 6.16767 21.6463 5.92739 21.406C5.68711 21.1657 5.55212 20.8398 5.55212 20.5Z" fill="#FFA629"/>
<path opacity="0.4" d="M5.55212 31.0417C5.55212 30.7018 5.68711 30.376 5.92739 30.1357C6.16767 29.8954 6.49357 29.7604 6.83337 29.7604H15.375C15.7148 29.7604 16.0407 29.8954 16.281 30.1357C16.5213 30.376 16.6563 30.7018 16.6563 31.0417C16.6563 31.3815 16.5213 31.7074 16.281 31.9476C16.0407 32.1879 15.7148 32.3229 15.375 32.3229H6.83337C6.49357 32.3229 6.16767 32.1879 5.92739 31.9476C5.68711 31.7074 5.55212 31.3815 5.55212 31.0417Z" fill="#FFA629"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

+4
View File
@@ -0,0 +1,4 @@
<svg width="34" height="34" viewBox="0 0 34 34" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M17.0002 14.1667C20.1298 14.1667 22.6668 11.6296 22.6668 8.50001C22.6668 5.3704 20.1298 2.83334 17.0002 2.83334C13.8705 2.83334 11.3335 5.3704 11.3335 8.50001C11.3335 11.6296 13.8705 14.1667 17.0002 14.1667Z" fill="#FFA629"/>
<path opacity="0.5" d="M28.3334 24.7917C28.3334 28.3121 28.3334 31.1667 17.0001 31.1667C5.66675 31.1667 5.66675 28.3121 5.66675 24.7917C5.66675 21.2713 10.7412 18.4167 17.0001 18.4167C23.2589 18.4167 28.3334 21.2713 28.3334 24.7917Z" fill="#FFA629"/>
</svg>

After

Width:  |  Height:  |  Size: 589 B

+14
View File
@@ -0,0 +1,14 @@
<svg width="80" height="81" viewBox="0 0 80 81" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_f_153_663)">
<ellipse cx="40" cy="40.5" rx="36" ry="36.5" fill="#FECE4B"/>
<path d="M40 7C58.1865 7 73 21.9593 73 40.5C73 59.0407 58.1865 74 40 74C21.8135 74 7 59.0407 7 40.5C7 21.9593 21.8135 7 40 7Z" stroke="white" stroke-opacity="0.27" stroke-width="6"/>
</g>
<path d="M38.1034 42.3967H26.7229V38.6032H38.1034V27.2227H41.897V38.6032H53.2775V42.3967H41.897V53.7773H38.1034V42.3967Z" fill="white" fill-opacity="0.91"/>
<defs>
<filter id="filter0_f_153_663" x="0" y="0" width="80" height="81" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="2" result="effect1_foregroundBlur_153_663"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 919 B

+3
View File
@@ -0,0 +1,3 @@
<svg width="39" height="39" viewBox="0 0 39 39" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M22.3438 16.25C22.3438 16.9227 22.8898 17.4687 23.5625 17.4687H24.6838L21.4126 20.7399C21.3749 20.7777 21.3301 20.8077 21.2807 20.8282C21.2314 20.8487 21.1784 20.8592 21.125 20.8592C21.0716 20.8592 21.0187 20.8487 20.9693 20.8282C20.9199 20.8077 20.8751 20.7777 20.8374 20.7399L18.2601 18.1626C17.7269 17.6298 17.0039 17.3304 16.25 17.3304C15.4961 17.3304 14.7731 17.6298 14.2399 18.1626L10.5138 21.8888C10.394 22.0003 10.298 22.1349 10.2314 22.2844C10.1647 22.4339 10.1289 22.5953 10.126 22.7589C10.1232 22.9225 10.1533 23.0851 10.2146 23.2368C10.2759 23.3886 10.3671 23.5265 10.4828 23.6422C10.5985 23.7579 10.7364 23.8492 10.8882 23.9104C11.0399 23.9717 11.2025 24.0018 11.3661 23.999C11.5297 23.9961 11.6911 23.9603 11.8406 23.8936C11.9901 23.827 12.1247 23.731 12.2363 23.6112L15.9624 19.8851C16.0001 19.8473 16.0449 19.8173 16.0943 19.7968C16.1437 19.7763 16.1966 19.7658 16.25 19.7658C16.3034 19.7658 16.3564 19.7763 16.4057 19.7968C16.4551 19.8173 16.4999 19.8473 16.5376 19.8851L19.1149 22.4624C19.6481 22.9952 20.3711 23.2946 21.125 23.2946C21.8789 23.2946 22.6019 22.9952 23.1351 22.4624L26.4063 19.1929V20.3125C26.4063 20.6357 26.5347 20.9457 26.7632 21.1743C26.9918 21.4028 27.3018 21.5312 27.625 21.5312C27.9482 21.5312 28.2582 21.4028 28.4868 21.1743C28.7154 20.9457 28.8438 20.6357 28.8438 20.3125V16.25C28.8438 15.9268 28.7154 15.6168 28.4868 15.3882C28.2582 15.1597 27.9482 15.0312 27.625 15.0312H23.5625C23.2393 15.0312 22.9293 15.1597 22.7007 15.3882C22.4722 15.6168 22.3438 15.9268 22.3438 16.25Z" fill="#FFA629"/>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

@@ -0,0 +1,3 @@
<svg width="29" height="29" viewBox="0 0 29 29" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M16.9168 12.0833L13.2918 15.7083M24.5148 3.66245C24.6294 3.62289 24.7529 3.61637 24.8711 3.64366C24.9893 3.67095 25.0974 3.73094 25.1831 3.81677C25.2688 3.9026 25.3287 4.01081 25.3558 4.12904C25.3829 4.24726 25.3762 4.37073 25.3365 4.48533L18.1783 24.9424C18.1355 25.0647 18.0569 25.1713 17.9527 25.2484C17.8485 25.3254 17.7236 25.3694 17.5942 25.3746C17.4647 25.3799 17.3367 25.346 17.2266 25.2776C17.1166 25.2091 17.0297 25.1092 16.9772 24.9907L13.0876 16.24C13.0221 16.0945 12.9056 15.978 12.7601 15.9125L4.00937 12.0217C3.89131 11.969 3.79177 11.8821 3.7236 11.7722C3.65544 11.6624 3.62178 11.5346 3.62699 11.4054C3.63219 11.2762 3.67602 11.1515 3.7528 11.0475C3.82958 10.9435 3.93578 10.8649 4.0577 10.8218L24.5148 3.66245Z" stroke="#FFA629" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 927 B

@@ -0,0 +1,26 @@
<svg width="81" height="80" viewBox="0 0 81 80" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_f_612_521)">
<ellipse cx="5.76768" cy="3.91141" rx="5.76768" ry="3.91141" transform="matrix(0.994377 0.105902 -0.109566 0.99398 13.9968 41.0223)" fill="#FE904B"/>
</g>
<g filter="url(#filter1_f_612_521)">
<ellipse cx="5.76768" cy="3.91141" rx="5.76768" ry="3.91141" transform="matrix(-0.994377 0.105902 0.109566 0.99398 63.5996 41.0223)" fill="#FE904B"/>
</g>
<path d="M39 3.5C52.9073 3.5 62.5245 7.55998 68.6895 14.1172C74.8857 20.7078 78 30.2512 78 42C78 53.6196 74.465 62.0849 68.1357 67.6924C61.7467 73.3528 52.0631 76.5 39 76.5C16.7941 76.5 3.5 59.8138 3.5 42C3.5 32.9304 6.44414 23.2398 12.3418 15.877C18.1765 8.59281 26.9561 3.5 39 3.5Z" fill="white" fill-opacity="0.21" stroke="white" stroke-width="6"/>
<ellipse cx="26.4678" cy="37.3073" rx="5.76882" ry="8.01676" fill="#115CB0"/>
<ellipse cx="51.9302" cy="37.3073" rx="5.76882" ry="8.01676" fill="#115CB0"/>
<ellipse cx="26.4678" cy="35.7431" rx="1.79032" ry="1.75978" fill="white"/>
<ellipse cx="51.9302" cy="35.7431" rx="1.79032" ry="1.75978" fill="white"/>
<path d="M30.6453 56.6648C30.6453 56.6648 34.027 60.3799 38.8012 60.3799C43.5754 60.3799 46.9571 56.6648 46.9571 56.6648" stroke="#115CB0" stroke-width="3"/>
<defs>
<filter id="filter0_f_612_521" x="7.552" y="35.5848" width="23.5029" height="19.8723" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="3" result="effect1_foregroundBlur_612_521"/>
</filter>
<filter id="filter1_f_612_521" x="46.5415" y="35.5848" width="23.5029" height="19.8723" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="3" result="effect1_foregroundBlur_612_521"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

@@ -0,0 +1,4 @@
<svg width="39" height="38" viewBox="0 0 39 38" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M31.1678 24.6647L31.8169 26.2661C32.4534 27.8386 33.7348 29.0814 35.3513 29.7017L36.9997 30.3324L35.3513 30.9651C33.7348 31.5834 32.4555 32.8282 31.8169 34.3987L31.1678 36.0001L30.5165 34.3987C30.2007 33.6218 29.7241 32.9162 29.1161 32.3255C28.5082 31.7349 27.7818 31.2719 26.9821 30.9651L25.3337 30.3324L26.9821 29.7017C27.782 29.3947 28.5085 28.9313 29.1165 28.3403C29.7244 27.7494 30.201 27.0433 30.5165 26.2661L31.1678 24.6647Z" stroke="#FFA629" stroke-width="3" stroke-linejoin="round"/>
<path d="M14.4233 2.45754C14.593 1.84749 15.4841 1.84749 15.6538 2.45754L15.9805 3.63642C16.6532 6.06015 17.9689 8.26993 19.796 10.045C21.6231 11.82 23.8978 13.0982 26.3927 13.7517L27.6061 14.0691C28.2341 14.234 28.2341 15.0996 27.6061 15.2645L26.3927 15.5819C23.8978 16.2354 21.6231 17.5136 19.796 19.2886C17.9689 21.0636 16.6532 23.2734 15.9805 25.6972L15.6538 26.876C15.4841 27.4861 14.593 27.4861 14.4233 26.876L14.0966 25.6972C13.4239 23.2734 12.1082 21.0636 10.2811 19.2886C8.45395 17.5136 6.17931 16.2354 3.68445 15.5819L2.47097 15.2645C1.84301 15.0996 1.84301 14.234 2.47097 14.0691L3.68445 13.7517C6.17931 13.0982 8.45395 11.82 10.2811 10.045C12.1082 8.26993 13.4239 6.06015 14.0966 3.63642L14.4233 2.45754Z" stroke="#FFA629" stroke-width="3" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

@@ -0,0 +1,4 @@
<svg width="26" height="26" viewBox="0 0 26 26" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M17.8749 14.2545L17.304 14.833C17.456 14.9832 17.6611 15.0674 17.8749 15.0674C18.0886 15.0674 18.2937 14.9832 18.4458 14.833L17.8749 14.2545ZM12.729 16.315C12.729 15.2642 13.353 14.378 14.2132 14.0043C15.0333 13.6479 16.174 13.7183 17.304 14.833L18.4458 13.6771C16.8934 12.1442 15.055 11.8669 13.5665 12.5136C12.8311 12.8393 12.2066 13.3722 11.7693 14.0471C11.332 14.722 11.1008 15.5097 11.104 16.3139L12.729 16.315ZM15.5782 22.5009C15.8794 22.7262 16.226 22.9862 16.5835 23.1834C16.941 23.3827 17.3809 23.5625 17.8749 23.5625V21.9375C17.773 21.9375 17.617 21.8985 17.3733 21.7642C17.0875 21.5943 16.8132 21.4057 16.5521 21.1998L15.5782 22.5009ZM20.1715 22.5009C21.0003 21.8801 22.128 21.1218 23.0099 20.1771C23.9199 19.2032 24.6457 17.9595 24.6457 16.315H23.0207C23.0207 17.4244 22.5484 18.291 21.8225 19.0678C21.0696 19.8749 20.1239 20.5064 19.1976 21.1998L20.1715 22.5009ZM19.1976 21.1998C18.9365 21.4053 18.6622 21.5935 18.3765 21.7631C18.1327 21.8985 17.9767 21.9375 17.8749 21.9375V23.5625C18.3689 23.5625 18.8076 23.3827 19.1662 23.1834C19.5237 22.9862 19.8693 22.7262 20.1715 22.5009L19.1976 21.1998ZM24.6457 16.3139C24.6488 15.2413 24.2374 14.2099 23.4974 13.4334L22.322 14.5557C22.752 15.0053 23.0207 15.6249 23.0207 16.315L24.6457 16.3139ZM23.4974 13.4334C23.1284 13.0439 22.6847 12.7318 22.1927 12.518C21.7007 12.3041 21.1706 12.1917 20.6341 12.1875C19.5031 12.1821 18.3364 12.6577 17.304 13.6771L18.4458 14.833C19.215 14.0747 19.982 13.8093 20.6265 13.8125C21.2798 13.8158 21.8821 14.0953 22.322 14.5557L23.4974 13.4334ZM16.5521 21.1998L16.082 20.8531L15.1221 22.1639L15.5771 22.4998L16.5521 21.1998ZM16.082 20.8531C14.3205 19.5629 12.729 18.3539 12.729 16.3139H11.104C11.104 19.3115 13.5166 20.9885 15.1221 22.1639L16.082 20.8531Z" fill="black"/>
<path opacity="0.5" d="M12.9998 5.76003L12.4018 6.31036C12.4779 6.39309 12.5704 6.45914 12.6733 6.50431C12.7762 6.54949 12.8874 6.57281 12.9998 6.57281C13.1122 6.57281 13.2234 6.54949 13.3264 6.50431C13.4293 6.45914 13.5217 6.39309 13.5978 6.31036L12.9998 5.76003ZM10.2254 20.3233C6.91692 17.5954 2.979 14.9207 2.979 9.87886H1.354C1.354 15.8545 6.125 19.0493 9.19192 21.5767L10.2254 20.3233ZM2.979 9.87886C2.979 7.40886 4.31367 5.35703 6.1055 4.50119C7.8345 3.67461 10.168 3.88369 12.4018 6.31036L13.5978 5.20969C10.9567 2.33994 7.8735 1.85461 5.40459 3.03328C2.9985 4.18486 1.354 6.83903 1.354 9.87886H2.979ZM9.19192 21.5767C9.74659 22.0339 10.3424 22.5214 10.9469 22.8908C11.5493 23.2591 12.2426 23.5624 12.9998 23.5624V21.9374C12.6738 21.9374 12.2848 21.8042 11.793 21.5041C11.3033 21.2051 10.7953 20.7924 10.2254 20.3233L9.19192 21.5767ZM24.6457 9.87994C24.6457 6.84119 23.0012 4.18594 20.594 3.03544C18.1262 1.85569 15.043 2.34211 12.4018 5.21078L13.5978 6.31144C15.8317 3.88478 18.1652 3.67569 19.8942 4.50228C21.686 5.35811 23.0207 7.40994 23.0207 9.87994H24.6457ZM23.6425 14.3454C24.3114 12.9523 24.6544 11.4253 24.6457 9.87994H23.0207C23.0292 11.1828 22.7402 12.4703 22.1757 13.6445L23.6425 14.3454ZM15.1004 20.8704C14.2045 21.5724 13.548 21.9374 12.9998 21.9374V23.5624C14.1731 23.5624 15.2369 22.8279 16.1036 22.1487L15.1004 20.8704Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

+4
View File
@@ -0,0 +1,4 @@
<svg width="19" height="20" viewBox="0 0 19 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15.1673 12.9988L15.4826 13.8466C15.7918 14.6791 16.4141 15.3371 17.1993 15.6655L18 15.9994L17.1993 16.3343C16.4141 16.6617 15.7928 17.3207 15.4826 18.1521L15.1673 18.9999L14.851 18.1521C14.6976 17.7408 14.4661 17.3673 14.1708 17.0546C13.8755 16.7419 13.5227 16.4968 13.1343 16.3343L12.3336 15.9994L13.1343 15.6655C13.5228 15.503 13.8756 15.2577 14.171 14.9448C14.4663 14.6319 14.6977 14.2581 14.851 13.8466L15.1673 12.9988Z" stroke="#FFA629" stroke-width="2" stroke-linejoin="round"/>
<path d="M7.03424 1.24223C7.11667 0.919258 7.54945 0.919258 7.63189 1.24223L7.79058 1.86634C8.11733 3.14949 8.75637 4.31937 9.64385 5.2591C10.5313 6.19883 11.6362 6.87549 12.848 7.22148L13.4374 7.38951C13.7424 7.4768 13.7424 7.93506 13.4374 8.02235L12.848 8.19038C11.6362 8.53637 10.5313 9.21304 9.64385 10.1528C8.75637 11.0925 8.11733 12.2624 7.79058 13.5455L7.63189 14.1696C7.54945 14.4926 7.11667 14.4926 7.03424 14.1696L6.87555 13.5455C6.5488 12.2624 5.90976 11.0925 5.02228 10.1528C4.1348 9.21304 3.02997 8.53637 1.81817 8.19038L1.22876 8.02235C0.923748 7.93506 0.923748 7.4768 1.22876 7.38951L1.81817 7.22148C3.02997 6.87549 4.1348 6.19883 5.02228 5.2591C5.90976 4.31937 6.5488 3.14949 6.87555 1.86634L7.03424 1.24223Z" stroke="#FFA629" stroke-width="2" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

@@ -0,0 +1,9 @@
<svg width="46" height="46" viewBox="0 0 46 46" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M22.9999 7.66666C31.4524 7.66666 38.3333 14.5475 38.3333 23C38.3333 31.4525 31.4524 38.3333 22.9999 38.3333C14.5474 38.3333 7.66659 31.4525 7.66659 23C7.66659 14.5475 14.5474 7.66666 22.9999 7.66666ZM22.9999 3.83333C12.4583 3.83333 3.83325 12.4583 3.83325 23C3.83325 33.5417 12.4583 42.1667 22.9999 42.1667C33.5416 42.1667 42.1666 33.5417 42.1666 23C42.1666 12.4583 33.5416 3.83333 22.9999 3.83333ZM16.1574 27.6767L13.5508 26.5075C14.0874 25.4342 14.3749 24.2842 14.3749 23.0958C14.3749 21.8308 14.0874 20.6233 13.5508 19.4925L16.1574 18.3233C16.8858 19.8758 17.2499 21.4667 17.2499 23.0958C17.2499 24.5333 16.8858 26.0667 16.1574 27.6767ZM22.0991 30.5517L19.5883 29.3058C20.6041 27.14 21.0833 24.9167 21.0833 22.7317C21.0833 20.5658 20.6041 18.5725 19.5883 16.6942L22.0991 15.2567C23.3641 17.48 23.9583 19.9717 23.9583 22.7317C23.9583 25.5492 23.3641 28.1558 22.0991 30.5517ZM28.1366 33.2542L25.5108 31.9125C27.0249 28.9608 27.7916 26.0092 27.7916 23C27.7916 19.9908 27.0249 17.0008 25.5108 14.0108L28.1366 12.7458C29.8041 16.1575 30.6666 19.5883 30.6666 23C30.6666 26.4883 29.8041 29.9 28.1366 33.2542Z" fill="url(#paint0_linear_73_538)" fill-opacity="0.58"/>
<defs>
<linearGradient id="paint0_linear_73_538" x1="22.9999" y1="3.83333" x2="22.9999" y2="42.1667" gradientUnits="userSpaceOnUse">
<stop stop-color="#FE904B"/>
<stop offset="0.990385" stop-color="#98562D" stop-opacity="0.4"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 1.0 MiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 673 KiB

+6
View File
@@ -0,0 +1,6 @@
Reserved splash assets (copy your images here):
- background.png
- logo_lockup.png
- loading_spinner.png
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 1.1 MiB

+192
View File
@@ -0,0 +1,192 @@
import { reactive, computed, readonly } from 'vue'
import * as authService from '../services/auth.js'
import * as userProfileService from '../services/userProfile.js'
import * as lifeEventService from '../services/lifeEvent.js'
import * as epicScriptService from '../services/epicScript.js'
import * as lifePathService from '../services/lifePath.js'
const state = reactive({
isLoggedIn: false,
isLoading: false,
currentStep: 1,
userInfo: null,
userProfile: null,
events: [],
scripts: [],
paths: [],
currentPath: null,
registrationData: {
nickname: '',
gender: '',
mbti: '',
zodiac: '',
profession: '',
hobbies: [],
childhood: { date: '', text: '' },
joy: { date: '', text: '' },
low: { date: '', text: '' },
future: { vision: '', ideal: '' }
}
})
const hasProfile = computed(() => {
return state.userProfile && state.userProfile.nickname
})
const login = async (phone, smsCode) => {
state.isLoading = true
try {
const res = await authService.login({ phone, smsCode })
state.isLoggedIn = true
await fetchUserProfile()
return { success: true, hasProfile: hasProfile.value }
} catch (error) {
return { success: false, error: error.message }
} finally {
state.isLoading = false
}
}
const logout = async () => {
await authService.logout()
state.isLoggedIn = false
state.userInfo = null
state.userProfile = null
}
const fetchUserProfile = async () => {
try {
const res = await userProfileService.getCurrentProfile()
if (res.data) {
state.userProfile = userProfileService.transformToFrontendFormat(res.data)
Object.assign(state.registrationData, state.userProfile)
}
return res.data
} catch (error) {
return null
}
}
const saveUserProfile = async () => {
state.isLoading = true
try {
const dataToSave = { ...state.registrationData }
if (state.userProfile?.id) {
await userProfileService.updateProfile({
id: state.userProfile.id,
...dataToSave
})
} else {
await userProfileService.createProfile(dataToSave)
}
await fetchUserProfile()
return { success: true }
} catch (error) {
return { success: false, error: error.message }
} finally {
state.isLoading = false
}
}
const updateRegistration = (data) => {
Object.assign(state.registrationData, data)
}
const setCurrentStep = (step) => {
state.currentStep = step
}
const fetchEvents = async () => {
try {
const res = await lifeEventService.getEventList()
state.events = lifeEventService.transformListToFrontend(res.data || [])
return state.events
} catch (error) {
return []
}
}
const createEvent = async (eventData) => {
try {
await lifeEventService.createEvent(eventData)
await fetchEvents()
return { success: true }
} catch (error) {
return { success: false, error: error.message }
}
}
const fetchScripts = async () => {
try {
const res = await epicScriptService.getScriptList()
state.scripts = epicScriptService.transformListToFrontend(res.data || [])
return state.scripts
} catch (error) {
return []
}
}
const createScript = async (scriptData) => {
try {
await epicScriptService.createScript(scriptData)
await fetchScripts()
return { success: true }
} catch (error) {
return { success: false, error: error.message }
}
}
const selectScript = async (id) => {
try {
await epicScriptService.selectScript(id)
await fetchScripts()
return { success: true }
} catch (error) {
return { success: false, error: error.message }
}
}
const fetchPaths = async () => {
try {
const res = await lifePathService.getPathList()
state.paths = lifePathService.transformListToFrontend(res.data || [])
return state.paths
} catch (error) {
return []
}
}
const setCurrentPath = (path) => {
state.currentPath = path
}
const initialize = async () => {
const token = uni.getStorageSync('access_token')
if (token) {
state.isLoggedIn = true
await fetchUserProfile()
}
}
export const useAppStore = () => {
return readonly({
...state,
hasProfile,
login,
logout,
fetchUserProfile,
saveUserProfile,
updateRegistration,
setCurrentStep,
fetchEvents,
createEvent,
fetchScripts,
createScript,
selectScript,
fetchPaths,
setCurrentPath,
initialize
})
}
export default useAppStore
+76
View File
@@ -0,0 +1,76 @@
/**
* 这里是uni-app内置的常用样式变量
*
* uni-app 官方扩展插件及插件市场(https://ext.dcloud.net.cn)上很多三方插件均使用了这些样式变量
* 如果你是插件开发者,建议你使用scss预处理,并在插件代码中直接使用这些变量(无需 import 这个文件),方便用户通过搭积木的方式开发整体风格一致的App
*
*/
/**
* 如果你是App开发者(插件使用者),你可以通过修改这些变量来定制自己的插件主题,实现自定义主题功能
*
* 如果你的项目同样使用了scss预处理,你也可以直接在你的 scss 代码中使用如下变量,同时无需 import 这个文件
*/
/* 颜色变量 */
/* 行为相关颜色 */
$uni-color-primary: #007aff;
$uni-color-success: #4cd964;
$uni-color-warning: #f0ad4e;
$uni-color-error: #dd524d;
/* 文字基本颜色 */
$uni-text-color:#333;//基本色
$uni-text-color-inverse:#fff;//反色
$uni-text-color-grey:#999;//辅助灰色,如加载更多的提示信息
$uni-text-color-placeholder: #808080;
$uni-text-color-disable:#c0c0c0;
/* 背景颜色 */
$uni-bg-color:#ffffff;
$uni-bg-color-grey:#f8f8f8;
$uni-bg-color-hover:#f1f1f1;//点击状态颜色
$uni-bg-color-mask:rgba(0, 0, 0, 0.4);//遮罩颜色
/* 边框颜色 */
$uni-border-color:#c8c7cc;
/* 尺寸变量 */
/* 文字尺寸 */
$uni-font-size-sm:12px;
$uni-font-size-base:14px;
$uni-font-size-lg:16px;
/* 图片尺寸 */
$uni-img-size-sm:20px;
$uni-img-size-base:26px;
$uni-img-size-lg:40px;
/* Border Radius */
$uni-border-radius-sm: 2px;
$uni-border-radius-base: 3px;
$uni-border-radius-lg: 6px;
$uni-border-radius-circle: 50%;
/* 水平间距 */
$uni-spacing-row-sm: 5px;
$uni-spacing-row-base: 10px;
$uni-spacing-row-lg: 15px;
/* 垂直间距 */
$uni-spacing-col-sm: 4px;
$uni-spacing-col-base: 8px;
$uni-spacing-col-lg: 12px;
/* 透明度 */
$uni-opacity-disabled: 0.3; // 组件禁用态的透明度
/* 文章场景相关 */
$uni-color-title: #2C405A; // 文章标题颜色
$uni-font-size-title:20px;
$uni-color-subtitle: #555555; // 二级标题颜色
$uni-font-size-subtitle:26px;
$uni-color-paragraph: #3F536E; // 文章段落颜色
$uni-font-size-paragraph:15px;