502 lines
13 KiB
JavaScript
502 lines
13 KiB
JavaScript
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: [],
|
|
inspirationRecommendations: [],
|
|
paths: [],
|
|
currentPath: null,
|
|
registrationData: {
|
|
nickname: '',
|
|
gender: '',
|
|
mbti: '',
|
|
zodiac: '',
|
|
profession: '',
|
|
hobbies: [],
|
|
childhood: { date: '', text: '' },
|
|
joy: { date: '', text: '' },
|
|
low: { date: '', text: '' },
|
|
future: { vision: '', ideal: '' },
|
|
city: '',
|
|
industry: '',
|
|
company: '',
|
|
personalityTags: [],
|
|
personalityTagLibrary: [],
|
|
hobbyLibrary: [],
|
|
birthday: ''
|
|
}
|
|
})
|
|
|
|
const SESSION_STATUS = {
|
|
AUTHENTICATED: 'authenticated',
|
|
UNAUTHENTICATED: 'unauthenticated',
|
|
ERROR: 'error'
|
|
}
|
|
|
|
const logAuth = (label, payload = {}) => {
|
|
console.log(`[AUTH] ${label}`, payload)
|
|
}
|
|
|
|
const hasProfile = computed(() => {
|
|
return state.userProfile && state.userProfile.nickname
|
|
})
|
|
|
|
const resetAuthState = () => {
|
|
state.isLoggedIn = false
|
|
state.userInfo = null
|
|
state.userProfile = null
|
|
}
|
|
|
|
const applyProfileResponse = (profileData) => {
|
|
const profile = userProfileService.transformToFrontendFormat(profileData)
|
|
state.userProfile = profile
|
|
if (profile) {
|
|
Object.assign(state.registrationData, profile)
|
|
}
|
|
return profile
|
|
}
|
|
|
|
const clearStoredTokens = () => {
|
|
uni.removeStorageSync('access_token')
|
|
uni.removeStorageSync('refresh_token')
|
|
}
|
|
|
|
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 loginWithWechat = async (payload = {}) => {
|
|
state.isLoading = true
|
|
try {
|
|
await authService.wechatLogin(payload)
|
|
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()
|
|
resetAuthState()
|
|
}
|
|
|
|
const fetchUserProfile = async (options = {}) => {
|
|
const { throwOnError = false } = options
|
|
try {
|
|
const res = await userProfileService.getCurrentProfile()
|
|
if (res.data) {
|
|
const profile = applyProfileResponse(res.data)
|
|
logAuth('profile:success', { hasProfile: true })
|
|
return profile
|
|
} else {
|
|
state.userProfile = null
|
|
logAuth('profile:empty', { hasProfile: false })
|
|
}
|
|
return null
|
|
} catch (error) {
|
|
state.userProfile = null
|
|
logAuth('profile:fail', {
|
|
message: error.message,
|
|
statusCode: error.statusCode,
|
|
code: error.code,
|
|
isNetworkError: Boolean(error.isNetworkError)
|
|
})
|
|
if (throwOnError) {
|
|
throw error
|
|
}
|
|
return null
|
|
}
|
|
}
|
|
|
|
const saveUserProfile = async () => {
|
|
state.isLoading = true
|
|
try {
|
|
const dataToSave = { ...state.registrationData }
|
|
let response
|
|
if (state.userProfile?.id) {
|
|
response = await userProfileService.updateProfile({
|
|
id: state.userProfile.id,
|
|
...dataToSave
|
|
})
|
|
} else {
|
|
response = await userProfileService.createProfile(dataToSave)
|
|
}
|
|
if (response?.data) {
|
|
applyProfileResponse(response.data)
|
|
}
|
|
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 updateEvent = async (eventData) => {
|
|
try {
|
|
await lifeEventService.updateEvent(eventData)
|
|
await fetchEvents()
|
|
return { success: true }
|
|
} catch (error) {
|
|
return { success: false, error: error.message }
|
|
}
|
|
}
|
|
|
|
const deleteEvent = async (id) => {
|
|
try {
|
|
await lifeEventService.deleteEvent(id)
|
|
await fetchEvents()
|
|
return { success: true }
|
|
} catch (error) {
|
|
return { success: false, error: error.message }
|
|
}
|
|
}
|
|
|
|
const assistEventWriting = async (eventData, callbacks = {}) => {
|
|
try {
|
|
const res = await lifeEventService.assistEventWriting(eventData, callbacks)
|
|
return { success: true, data: res.data }
|
|
} catch (error) {
|
|
return { success: false, error: error.message }
|
|
}
|
|
}
|
|
|
|
const chatAboutEvent = async (eventData) => {
|
|
try {
|
|
const res = await lifeEventService.chatAboutEvent(eventData)
|
|
return { success: true, data: res.data }
|
|
} catch (error) {
|
|
return { success: false, error: error.message }
|
|
}
|
|
}
|
|
|
|
const shareEvent = async (eventData) => {
|
|
try {
|
|
const res = await lifeEventService.shareEvent(eventData)
|
|
return { success: true, data: res.data }
|
|
} catch (error) {
|
|
return { success: false, error: error.message }
|
|
}
|
|
}
|
|
|
|
const favoriteEvent = async ({ id, favorite }) => {
|
|
try {
|
|
const res = await lifeEventService.favoriteEvent({ id, favorite })
|
|
return { success: true, data: res.data }
|
|
} catch (error) {
|
|
return { success: false, error: error.message }
|
|
}
|
|
}
|
|
|
|
const getEventById = (id) => {
|
|
return state.events.find(event => String(event.id) === String(id)) || null
|
|
}
|
|
|
|
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 {
|
|
const res = await epicScriptService.createScript(scriptData)
|
|
const script = epicScriptService.transformToFrontendFormat(res.data)
|
|
await fetchScripts()
|
|
return { success: true, data: script || res.data }
|
|
} catch (error) {
|
|
return { success: false, error: error.message }
|
|
}
|
|
}
|
|
|
|
const updateScript = async (scriptData) => {
|
|
try {
|
|
const res = await epicScriptService.updateScript(scriptData)
|
|
const script = epicScriptService.transformToFrontendFormat(res.data)
|
|
await fetchScripts()
|
|
return { success: true, data: script || res.data }
|
|
} catch (error) {
|
|
return { success: false, error: error.message }
|
|
}
|
|
}
|
|
|
|
const deleteScript = async (id) => {
|
|
try {
|
|
await epicScriptService.deleteScript(id)
|
|
await fetchScripts()
|
|
return { success: true }
|
|
} catch (error) {
|
|
return { success: false, error: error.message }
|
|
}
|
|
}
|
|
|
|
const fetchInspirationRecommendations = async () => {
|
|
try {
|
|
const res = await epicScriptService.getInspirationRecommendations()
|
|
state.inspirationRecommendations = res.data || []
|
|
return state.inspirationRecommendations
|
|
} catch (error) {
|
|
state.inspirationRecommendations = []
|
|
return []
|
|
}
|
|
}
|
|
|
|
const fetchRandomInspirations = async (size = 3) => {
|
|
try {
|
|
const res = await epicScriptService.getRandomInspirations(size)
|
|
return res.data || []
|
|
} catch (error) {
|
|
return []
|
|
}
|
|
}
|
|
|
|
const generateScriptFromInspiration = async ({ prompt, style, length, useSocialInsights = true }) => {
|
|
try {
|
|
const profile = state.userProfile || state.registrationData
|
|
const res = await epicScriptService.generateFromInspiration({
|
|
prompt,
|
|
style,
|
|
length,
|
|
characterInfo: epicScriptService.buildCharacterInfo(profile),
|
|
lifeEventsSummary: epicScriptService.buildLifeEventsSummary(state.events, profile),
|
|
useSocialInsights,
|
|
source: 'mini-program'
|
|
})
|
|
await fetchScripts()
|
|
return { success: true, data: res.data }
|
|
} catch (error) {
|
|
return { success: false, error: error.message }
|
|
}
|
|
}
|
|
|
|
const getScriptById = (id) => {
|
|
return state.scripts.find(script => String(script.id) === String(id)) || null
|
|
}
|
|
|
|
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 restoreSession = async () => {
|
|
const token = uni.getStorageSync('access_token')
|
|
const refreshToken = uni.getStorageSync('refresh_token')
|
|
|
|
logAuth('restore:start', {
|
|
hasAccessToken: Boolean(token),
|
|
hasRefreshToken: Boolean(refreshToken)
|
|
})
|
|
|
|
if (!token) {
|
|
clearStoredTokens()
|
|
resetAuthState()
|
|
logAuth('restore:unauthenticated', { reason: 'missing_access_token' })
|
|
return { status: SESSION_STATUS.UNAUTHENTICATED, reason: 'missing_access_token' }
|
|
}
|
|
|
|
try {
|
|
const isValid = await authService.validateTokenStrict()
|
|
if (!isValid) {
|
|
throw new Error('Token validation returned false')
|
|
}
|
|
|
|
logAuth('validate:success')
|
|
state.isLoggedIn = true
|
|
await fetchUserProfile({ throwOnError: true })
|
|
return {
|
|
status: SESSION_STATUS.AUTHENTICATED,
|
|
hasProfile: hasProfile.value,
|
|
reason: 'access_token_valid'
|
|
}
|
|
} catch (validateError) {
|
|
logAuth('validate:fail', {
|
|
message: validateError.message,
|
|
statusCode: validateError.statusCode,
|
|
code: validateError.code,
|
|
isNetworkError: Boolean(validateError.isNetworkError)
|
|
})
|
|
|
|
if (validateError.isNetworkError) {
|
|
resetAuthState()
|
|
return {
|
|
status: SESSION_STATUS.ERROR,
|
|
reason: 'validate_network_error',
|
|
error: validateError
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!refreshToken) {
|
|
clearStoredTokens()
|
|
resetAuthState()
|
|
logAuth('refresh:skip', { reason: 'missing_refresh_token' })
|
|
return { status: SESSION_STATUS.UNAUTHENTICATED, reason: 'missing_refresh_token' }
|
|
}
|
|
|
|
try {
|
|
logAuth('refresh:start')
|
|
await authService.refreshToken()
|
|
logAuth('refresh:success')
|
|
state.isLoggedIn = true
|
|
await fetchUserProfile({ throwOnError: true })
|
|
return {
|
|
status: SESSION_STATUS.AUTHENTICATED,
|
|
hasProfile: hasProfile.value,
|
|
reason: 'refresh_token_valid'
|
|
}
|
|
} catch (refreshError) {
|
|
logAuth('refresh:fail', {
|
|
message: refreshError.message,
|
|
statusCode: refreshError.statusCode,
|
|
code: refreshError.code,
|
|
isNetworkError: Boolean(refreshError.isNetworkError)
|
|
})
|
|
|
|
if (refreshError.isNetworkError) {
|
|
resetAuthState()
|
|
return {
|
|
status: SESSION_STATUS.ERROR,
|
|
reason: 'refresh_network_error',
|
|
error: refreshError
|
|
}
|
|
}
|
|
|
|
clearStoredTokens()
|
|
resetAuthState()
|
|
return {
|
|
status: SESSION_STATUS.UNAUTHENTICATED,
|
|
reason: 'refresh_failed',
|
|
error: refreshError
|
|
}
|
|
}
|
|
}
|
|
|
|
const initialize = async () => {
|
|
return restoreSession()
|
|
}
|
|
|
|
export const useAppStore = () => {
|
|
return readonly({
|
|
get isLoggedIn() { return state.isLoggedIn },
|
|
get isLoading() { return state.isLoading },
|
|
get currentStep() { return state.currentStep },
|
|
get userInfo() { return state.userInfo },
|
|
get userProfile() { return state.userProfile },
|
|
get events() { return state.events },
|
|
get scripts() { return state.scripts },
|
|
get inspirationRecommendations() { return state.inspirationRecommendations },
|
|
get paths() { return state.paths },
|
|
get currentPath() { return state.currentPath },
|
|
get registrationData() { return state.registrationData },
|
|
get hasProfile() { return hasProfile.value },
|
|
login,
|
|
loginWithWechat,
|
|
logout,
|
|
fetchUserProfile,
|
|
saveUserProfile,
|
|
updateRegistration,
|
|
setCurrentStep,
|
|
fetchEvents,
|
|
createEvent,
|
|
updateEvent,
|
|
deleteEvent,
|
|
assistEventWriting,
|
|
chatAboutEvent,
|
|
shareEvent,
|
|
favoriteEvent,
|
|
getEventById,
|
|
fetchScripts,
|
|
createScript,
|
|
updateScript,
|
|
deleteScript,
|
|
fetchInspirationRecommendations,
|
|
fetchRandomInspirations,
|
|
generateScriptFromInspiration,
|
|
getScriptById,
|
|
selectScript,
|
|
fetchPaths,
|
|
setCurrentPath,
|
|
initialize,
|
|
restoreSession,
|
|
SESSION_STATUS
|
|
})
|
|
}
|
|
|
|
export default useAppStore
|