feat: 完善人生轨迹详情页功能及微信小程序登录优化

This commit is contained in:
2026-06-15 10:17:30 +08:00
parent cb3c5e6724
commit 74a2c15b6b
37 changed files with 1974 additions and 516 deletions
+256 -18
View File
@@ -1,5 +1,5 @@
<template>
<view class="script-library">
<view class="script-library" @click="closeScriptMenu">
<view class="page-head">
<view class="back-title" @click="backToScript">
<text class="back-arrow"></text>
@@ -71,7 +71,27 @@
</view>
<view class="right-state">
<text class="state-pill" :class="'state-' + getStatus(script)">{{ getStatusLabel(script) }}</text>
<text class="ellipsis" @click.stop="openScriptMenu(script)"></text>
<view class="menu-wrap">
<text class="ellipsis" :class="{ active: activeMenuId === String(script.id) }" @click.stop="toggleScriptMenu(script)"></text>
<view v-if="activeMenuId === String(script.id)" class="card-menu" @click.stop>
<view class="menu-action" @click="toggleFavorite(script)">
<text class="menu-dot"></text>
<text>{{ isFavorite(script) ? '取消收藏' : '收藏剧本' }}</text>
</view>
<view class="menu-action" @click="continueScript(script)">
<text class="menu-dot"></text>
<text>继续生成</text>
</view>
<view class="menu-action" @click="openScriptDetailFromMenu(script)">
<text class="menu-dot"></text>
<text>查看详情</text>
</view>
<view class="menu-action danger" @click="requestDeleteScript(script)">
<text class="menu-dot"></text>
<text>删除剧本</text>
</view>
</view>
</view>
</view>
</view>
@@ -112,6 +132,17 @@
<text class="empty-text">去爽文生成页写下一句灵感生成你的第一段平行人生</text>
<view class="empty-action" @click="createScript">新建剧本</view>
</view>
<view v-if="deleteTarget" class="confirm-mask" @click.stop="cancelDeleteScript">
<view class="confirm-panel" @click.stop>
<text class="confirm-title">删除剧本</text>
<text class="confirm-copy">确定删除{{ deleteTarget.title || '未命名剧本' }}删除后将无法在历史剧本中查看</text>
<view class="confirm-actions">
<view class="confirm-btn secondary" @click="cancelDeleteScript">取消</view>
<view class="confirm-btn danger" :class="{ disabled: deletingScript }" @click="confirmDeleteScript">{{ deletingScript ? '删除中' : '确认删除' }}</view>
</view>
</view>
</view>
</view>
</template>
@@ -126,6 +157,9 @@ const keyword = ref('')
const sortMode = ref('updated')
const viewMode = ref('list')
const localFavorites = ref(uni.getStorageSync('script_favorites') || {})
const activeMenuId = ref('')
const deleteTarget = ref(null)
const deletingScript = ref(false)
const typeTabs = [
{ label: '长篇', value: 'long' },
@@ -350,23 +384,73 @@ const toggleViewMode = () => {
viewMode.value = viewMode.value === 'list' ? 'grid' : 'list'
}
const openScriptMenu = (script) => {
const closeScriptMenu = () => {
activeMenuId.value = ''
}
const toggleScriptMenu = (script) => {
const id = String(script?.id || '')
activeMenuId.value = activeMenuId.value === id ? '' : id
}
const toggleFavorite = (script) => {
const favorite = isFavorite(script)
uni.showActionSheet({
itemList: [favorite ? '取消收藏' : '收藏剧本', '继续生成', '查看详情'],
success: ({ tapIndex }) => {
if (tapIndex === 0) {
const next = { ...localFavorites.value }
if (favorite) delete next[String(script.id)]
else next[String(script.id)] = true
localFavorites.value = next
uni.setStorageSync('script_favorites', next)
uni.showToast({ title: favorite ? '已取消收藏' : '已收藏', icon: 'success' })
}
if (tapIndex === 1) viewScript(script)
if (tapIndex === 2) openScriptDetail(script)
}
})
const next = { ...localFavorites.value }
if (favorite) delete next[String(script.id)]
else next[String(script.id)] = true
localFavorites.value = next
uni.setStorageSync('script_favorites', next)
closeScriptMenu()
uni.showToast({ title: favorite ? '已取消收藏' : '已收藏', icon: 'success' })
}
const continueScript = (script) => {
closeScriptMenu()
viewScript(script)
}
const openScriptDetailFromMenu = (script) => {
closeScriptMenu()
openScriptDetail(script)
}
const requestDeleteScript = (script) => {
closeScriptMenu()
if (!script?.id || String(script.id).startsWith('demo-')) {
uni.showToast({ title: '示例剧本不可删除', icon: 'none' })
return
}
deleteTarget.value = script
}
const cancelDeleteScript = () => {
if (deletingScript.value) return
deleteTarget.value = null
}
const confirmDeleteScript = async () => {
if (!deleteTarget.value?.id || deletingScript.value) return
deletingScript.value = true
const id = String(deleteTarget.value.id)
const res = await store.deleteScript(id)
deletingScript.value = false
if (!res.success) {
uni.showToast({ title: res.error || '删除失败', icon: 'none' })
return
}
const next = { ...localFavorites.value }
delete next[id]
localFavorites.value = next
uni.setStorageSync('script_favorites', next)
const conversationId = deleteTarget.value.conversationId || deleteTarget.value.plotJson?.conversationId
if (conversationId) uni.removeStorageSync(`script_conversation_history_${conversationId}`)
uni.removeStorageSync(`script_chat_history_${id}`)
const pending = uni.getStorageSync('pending_open_script_chat')
if (pending?.id === id) uni.removeStorageSync('pending_open_script_chat')
deleteTarget.value = null
uni.showToast({ title: '已删除剧本', icon: 'success' })
}
</script>
@@ -670,6 +754,7 @@ const openScriptMenu = (script) => {
}
.right-state {
position: relative;
flex-shrink: 0;
gap: 14rpx;
}
@@ -699,11 +784,89 @@ const openScriptMenu = (script) => {
}
.ellipsis {
min-width: 48rpx;
height: 44rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: 18rpx;
color: rgba(224, 214, 243, 0.66);
font-size: 24rpx;
letter-spacing: 3rpx;
}
.ellipsis.active {
color: #fff;
background: rgba(149, 55, 255, 0.22);
box-shadow: 0 0 20rpx rgba(168, 67, 255, 0.28);
}
.menu-wrap {
position: relative;
}
.card-menu {
position: absolute;
top: 52rpx;
right: 0;
z-index: 30;
width: 202rpx;
padding: 10rpx;
border-radius: 22rpx;
border: 1rpx solid rgba(192, 132, 252, 0.32);
background:
radial-gradient(circle at 90% 0%, rgba(168, 85, 247, 0.22), transparent 36%),
rgba(12, 8, 34, 0.96);
box-shadow:
inset 0 1rpx 0 rgba(255, 255, 255, 0.08),
0 18rpx 42rpx rgba(0, 0, 0, 0.34);
backdrop-filter: blur(22rpx);
-webkit-backdrop-filter: blur(22rpx);
}
.card-menu::before {
content: '';
position: absolute;
right: 22rpx;
top: -10rpx;
width: 18rpx;
height: 18rpx;
border-left: 1rpx solid rgba(192, 132, 252, 0.3);
border-top: 1rpx solid rgba(192, 132, 252, 0.3);
background: rgba(12, 8, 34, 0.96);
transform: rotate(45deg);
}
.menu-action {
position: relative;
z-index: 1;
height: 58rpx;
padding: 0 14rpx;
display: flex;
align-items: center;
gap: 12rpx;
border-radius: 16rpx;
color: rgba(240, 232, 255, 0.9);
font-size: 23rpx;
font-weight: 800;
}
.menu-action:active {
background: rgba(149, 55, 255, 0.18);
}
.menu-action.danger {
color: #ff8fa3;
}
.menu-dot {
width: 8rpx;
height: 8rpx;
border-radius: 50%;
background: currentColor;
opacity: 0.72;
}
.tag-row {
display: flex;
flex-wrap: wrap;
@@ -826,4 +989,79 @@ const openScriptMenu = (script) => {
font-weight: 800;
background: linear-gradient(135deg, #b346ff, #7330ff);
}
.confirm-mask {
position: fixed;
inset: 0;
z-index: 80;
display: flex;
align-items: center;
justify-content: center;
padding: 0 48rpx;
background: rgba(3, 2, 13, 0.62);
backdrop-filter: blur(12rpx);
-webkit-backdrop-filter: blur(12rpx);
}
.confirm-panel {
width: 100%;
box-sizing: border-box;
padding: 34rpx 30rpx 28rpx;
border-radius: 28rpx;
border: 1rpx solid rgba(192, 132, 252, 0.34);
background:
radial-gradient(circle at 92% 0%, rgba(168, 85, 247, 0.2), transparent 36%),
linear-gradient(145deg, rgba(22, 13, 58, 0.98), rgba(8, 7, 26, 0.98));
box-shadow: 0 24rpx 72rpx rgba(0, 0, 0, 0.42);
}
.confirm-title {
display: block;
color: #fff;
font-size: 32rpx;
font-weight: 900;
text-align: center;
}
.confirm-copy {
display: block;
margin-top: 18rpx;
color: rgba(229, 219, 247, 0.76);
font-size: 24rpx;
line-height: 1.55;
text-align: center;
}
.confirm-actions {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 18rpx;
margin-top: 30rpx;
}
.confirm-btn {
height: 70rpx;
border-radius: 24rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 25rpx;
font-weight: 900;
}
.confirm-btn.secondary {
color: #e8ccff;
border: 1rpx solid rgba(192, 132, 252, 0.3);
background: rgba(88, 28, 135, 0.2);
}
.confirm-btn.danger {
color: #fff;
background: linear-gradient(145deg, #e05276, #8d2444);
box-shadow: 0 12rpx 30rpx rgba(224, 82, 118, 0.24);
}
.confirm-btn.disabled {
opacity: 0.55;
}
</style>