139 lines
3.3 KiB
Vue
139 lines
3.3 KiB
Vue
<script lang="ts">
|
||
export default { name: 'AiCreateShell' }
|
||
</script>
|
||
|
||
<template>
|
||
<div class="ai-create-shell">
|
||
<!-- 初始化加载 -->
|
||
<div v-if="loading" class="loading-wrapper">
|
||
<div class="spinner"></div>
|
||
<p class="loading-text">正在加载创作工坊...</p>
|
||
<p v-if="loadError" class="load-error">{{ loadError }}</p>
|
||
<a-button v-if="loadError" type="primary" @click="initToken" style="margin-top: 12px">
|
||
重新加载
|
||
</a-button>
|
||
</div>
|
||
<!-- 子路由渲染(不 keep-alive,避免创作状态错乱) -->
|
||
<router-view v-else v-slot="{ Component }">
|
||
<transition name="ai-slide" mode="out-in">
|
||
<component :is="Component" />
|
||
</transition>
|
||
</router-view>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { ref, onMounted, watch } from 'vue'
|
||
import { useRoute } from 'vue-router'
|
||
import { message } from 'ant-design-vue'
|
||
import { leaiApi } from '@/api/public'
|
||
import { useAicreateStore } from '@/stores/aicreate'
|
||
|
||
const store = useAicreateStore()
|
||
const route = useRoute()
|
||
const loading = ref(true)
|
||
const loadError = ref('')
|
||
|
||
watch(
|
||
() => route.path,
|
||
(path) => {
|
||
if (path.startsWith('/p/create')) {
|
||
store.setLastCreateRoute(path)
|
||
}
|
||
},
|
||
{ immediate: true },
|
||
)
|
||
|
||
/** 获取乐读派 Token 并存入 store;子路由(如 Welcome)再处理 resumeWorkId / 内存恢复 */
|
||
const initToken = async () => {
|
||
loading.value = true
|
||
loadError.value = ''
|
||
try {
|
||
const data = await leaiApi.getToken()
|
||
store.setSession(data.orgId, data.token)
|
||
} catch (err: any) {
|
||
const errMsg = err?.response?.data?.message || err?.message || '加载失败'
|
||
loadError.value = errMsg
|
||
message.error('加载创作工坊失败:' + errMsg)
|
||
} finally {
|
||
if (!loadError.value) {
|
||
loading.value = false
|
||
}
|
||
}
|
||
}
|
||
|
||
onMounted(() => {
|
||
if (store.sessionToken && store.orgId) {
|
||
loading.value = false
|
||
} else {
|
||
void initToken()
|
||
}
|
||
})
|
||
</script>
|
||
|
||
<style lang="scss">
|
||
// 壳组件样式不使用 scoped,因为 aicreate.scss 已通过 .ai-create-shell 隔离
|
||
// 覆盖 PublicLayout 的 public-main 样式:限制为 H5 单列宽度,子页面自己控制内边距
|
||
.public-main:has(> .ai-create-shell) {
|
||
padding-left: 0 !important;
|
||
padding-right: 0 !important;
|
||
padding-top: 0 !important;
|
||
// 保留 padding-bottom 为底部 tabbar 留空间(桌面 40px / 移动 80px)
|
||
max-width: 430px;
|
||
}
|
||
|
||
.ai-create-shell {
|
||
.loading-wrapper {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
min-height: 60vh;
|
||
text-align: center;
|
||
}
|
||
|
||
.spinner {
|
||
width: 48px;
|
||
height: 48px;
|
||
border: 4px solid rgba(99, 102, 241, 0.15);
|
||
border-top-color: #6366f1;
|
||
border-radius: 50%;
|
||
animation: spin 1s linear infinite;
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
@keyframes spin {
|
||
to {
|
||
transform: rotate(360deg);
|
||
}
|
||
}
|
||
|
||
.loading-text {
|
||
font-size: 15px;
|
||
color: #6b7280;
|
||
margin: 0;
|
||
}
|
||
|
||
.load-error {
|
||
font-size: 13px;
|
||
color: #ef4444;
|
||
margin: 8px 0 0;
|
||
}
|
||
|
||
.ai-slide-enter-active,
|
||
.ai-slide-leave-active {
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.ai-slide-enter-from {
|
||
opacity: 0;
|
||
transform: translateX(30px);
|
||
}
|
||
|
||
.ai-slide-leave-to {
|
||
opacity: 0;
|
||
transform: translateX(-30px);
|
||
}
|
||
}
|
||
</style>
|