refactor: 个人自用,移除登录与云同步全链路

站点只给本人用,云乐坊 SSO 授权、收藏/历史云同步、获客归因和账号切换记账都没有收益,却被登录状态检查和同步失败提示持续打扰。

- 删除 useYunlefunAuth / yunlefunAuth / yunlefunSsoConfig / yunlefunAuth 适配器、useUserDataSync / userDataSync / userData 适配器、三个 client 插件与 /auth/callback 回调页
- 抽出 adapters/cloudbase.ts,让「反馈一下」和匿名筛选信号不再依赖登录态;cook-feedback 云函数保留
- 删除 cook-user-data 云函数与 cook_user_data 权限规则,移除 @yunlefun/sso 依赖
- 「我的」页删掉登录按钮、账号卡、四个统计格和退出登录;「关于」页删掉项目作者、致谢名单和上游宣传链接
- 本地收藏/历史与导出导入保留,删除同步专用的 lastUid 记账

取舍见 docs/decisions/0005-drop-login-and-sync.md
This commit is contained in:
toor
2026-10-22 15:52:54 +08:00
committed by trae-solo-agent
parent 7408591fac
commit 8ff7b879b9
54 changed files with 214 additions and 3049 deletions
+2 -197
View File
@@ -1,5 +1,4 @@
<script lang="ts" setup>
import dayjs from 'dayjs'
import { customRecipes } from '~/composables/store/customRecipe'
import { favoriteEntries } from '~/composables/store/favorite'
import { recipeHistories } from '~/composables/store/history'
@@ -9,60 +8,14 @@ definePageMeta({
alias: ['/my'],
})
const {
account,
configurationMessage,
errorMessage,
isAuthenticated,
signIn,
signOut,
status,
} = useYunlefunAuth()
const authBusy = computed(() => status.value === 'checking' || status.value === 'signing-in')
const {
errorMessage: syncErrorMessage,
lastSyncedAt,
status: syncStatus,
syncNow,
} = useUserDataSync()
const syncText = computed(() => {
switch (syncStatus.value) {
case 'syncing':
return '正在同步…'
case 'synced':
return `已同步 ${dayjs(lastSyncedAt.value).format('HH:mm')}`
case 'error':
return `${syncErrorMessage.value},点击重试`
default:
return '等待同步'
}
})
const rStore = useRecipeStore()
const favCount = computed(() => favoriteEntries.value.length)
const historyCount = computed(() => recipeHistories.value.length)
const mineCount = computed(() => customRecipes.value.length)
const syncedText = computed(() => lastSyncedAt.value ? dayjs(lastSyncedAt.value).format('MM-DD HH:mm') : '—')
const pantryText = computed(() => rStore.selectedPantry.join('、') || '无')
const avoidText = computed(() => rStore.selectedAvoid.join('、') || '无')
const spicyText = computed(() => rStore.noSpicy ? '开' : '关')
function handleSyncClick(): void {
if (syncStatus.value === 'error')
syncNow()
}
async function handleSignIn(): Promise<void> {
await signIn()
}
async function handleSignOut(): Promise<void> {
await signOut()
}
</script>
<template>
@@ -83,66 +36,13 @@ async function handleSignOut(): Promise<void> {
</ion-header>
<div class="ledger-desktop-block">
<PageHead title="我的" subtitle="收藏、历史和自己记的菜谱都在这儿">
<button
v-if="!isAuthenticated"
type="button"
class="ledger-btn is-ghost is-sm"
:disabled="authBusy || Boolean(configurationMessage)"
@click="handleSignIn"
>
<ion-icon :icon="ioniconsLogInOutline" aria-hidden="true" />
<span>{{ authBusy ? '检查中…' : '登录' }}</span>
</button>
</PageHead>
<section class="ledger-section">
<div class="ledger-account">
<span class="avatar">
<img v-if="account?.avatarUrl" :alt="account.displayName" :src="account.avatarUrl">
<ion-icon v-else :icon="ioniconsPersonCircle" aria-hidden="true" />
</span>
<div class="ledger-account-info">
<b>{{ account?.displayName ?? '未登录' }}</b>
<small>{{ isAuthenticated ? '云乐坊账号' : '登录后可以同步收藏和历史' }}</small>
</div>
<button
type="button"
class="ledger-btn is-ghost is-sm"
:disabled="!isAuthenticated || syncStatus === 'syncing'"
@click="handleSyncClick"
>
<ion-icon :icon="ioniconsCloudDoneOutline" aria-hidden="true" />
<span>{{ syncStatus === 'syncing' ? '同步中' : '同步' }}</span>
</button>
</div>
</section>
<section class="ledger-section">
<div class="ledger-stat-grid">
<div class="ledger-stat">
<span>收藏</span>
<b>{{ favCount }}</b>
</div>
<div class="ledger-stat">
<span>浏览历史</span>
<b>{{ historyCount }}</b>
</div>
<div class="ledger-stat">
<span>我的菜谱</span>
<b>{{ mineCount }}</b>
</div>
<div class="ledger-stat">
<span>上次同步</span>
<b>{{ syncedText }}</b>
</div>
</div>
</section>
<PageHead title="我的" subtitle="收藏、历史和自己记的菜谱都在这儿" />
<div class="ledger-menu-grid">
<section class="ledger-section">
<div class="ledger-section-head">
<h2>我的菜谱</h2>
<span class="hint">共 {{ favCount + historyCount + mineCount }} 条记录</span>
</div>
<div class="ledger-menu">
<NuxtLink to="/recipes/favorites">
@@ -220,69 +120,9 @@ async function handleSignOut(): Promise<void> {
</div>
</section>
</div>
<section v-if="isAuthenticated" class="ledger-section">
<button type="button" class="ledger-btn is-ghost" @click="handleSignOut">
退出登录
</button>
</section>
</div>
<div class="ledger-mobile-only">
<ion-list :inset="true">
<ion-item v-if="isAuthenticated && account" class="my-account" lines="full">
<ion-avatar v-if="account.avatarUrl" slot="start">
<img :alt="account.displayName" :src="account.avatarUrl">
</ion-avatar>
<ion-icon v-else slot="start" class="my-account-placeholder" :icon="ioniconsPersonCircle" />
<ion-label>
<h2>{{ account.displayName }}</h2>
<p>云乐坊账号</p>
</ion-label>
</ion-item>
<ion-item
v-if="isAuthenticated"
:button="syncStatus === 'error'"
:detail="false"
@click="handleSyncClick"
>
<span v-if="syncStatus === 'syncing'" slot="start" class="my-sync-spinner">
<ion-spinner name="crescent" />
</span>
<CookListIcon
v-else
slot="start"
:icon="syncStatus === 'error' ? ioniconsCloudOfflineOutline : ioniconsCloudDoneOutline"
:color="syncStatus === 'error' ? '#ff3b30' : '#34c759'"
/>
<ion-label>
<h2>收藏和历史云同步</h2>
<p>{{ syncText }}</p>
</ion-label>
</ion-item>
<ion-item
v-if="!isAuthenticated"
:button="true"
:disabled="authBusy || Boolean(configurationMessage)"
@click="handleSignIn"
>
<span v-if="authBusy" slot="start" class="my-sync-spinner">
<ion-spinner name="crescent" />
</span>
<CookListIcon v-else slot="start" :icon="ioniconsLogInOutline" color="#007aff" />
<ion-label>
<h2>{{ authBusy ? '正在检查登录状态' : '使用云乐坊账号登录' }}</h2>
<p>安全授权,无需在 Cook 输入密码</p>
</ion-label>
</ion-item>
<ion-item v-if="configurationMessage || errorMessage" lines="none">
<ion-icon slot="start" color="danger" :icon="ioniconsAlertCircleOutline" />
<ion-label class="ion-text-wrap" color="danger">
{{ configurationMessage || errorMessage }}
</ion-label>
</ion-item>
</ion-list>
<ion-list :inset="true">
<ion-item router-link="/recipes/history">
<CookListIcon slot="start" :icon="ioniconsTimeOutline" color="#5856d6" />
@@ -337,42 +177,7 @@ async function handleSignOut(): Promise<void> {
<ion-label>关于</ion-label>
</ion-item>
</ion-list>
<ion-list v-if="isAuthenticated" :inset="true">
<ion-item :button="true" :detail="false" :disabled="authBusy" @click="handleSignOut">
<ion-label class="ion-text-center" color="danger">
退出登录
</ion-label>
</ion-item>
</ion-list>
</div>
</ion-content>
</ion-page>
</template>
<style scoped>
.my-account {
--padding-top: 6px;
--padding-bottom: 6px;
}
.my-account ion-avatar {
width: 56px;
height: 56px;
margin-inline-end: 14px;
}
.my-account-placeholder {
font-size: 56px;
margin-inline-end: 14px;
color: var(--ion-color-medium);
}
.my-account h2 {
font-size: 20px;
font-weight: 600;
}
.my-sync-spinner {
display: inline-flex;
justify-content: center;
width: 29px;
margin-inline-end: 14px;
}
</style>