Files
huihuiSquare/digital-avatar-app/src/views/AuthorizationManage.vue
T

222 lines
9.8 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="auth-manage-page">
<header class="page-header">
<button class="back-btn" @click="goBack">‹</button>
<h1 class="page-title">授权管理</h1>
<span class="header-spacer"></span>
</header>
<section class="intro-card">
<div class="intro-icon">🛡️</div>
<div>
<h2>由你决定分身能做什么</h2>
<p>授权后,数字分身会以你的会会身份参与广场互动;撤销后不再创建新的互动。</p>
</div>
</section>
<section class="auth-list">
<div class="auth-card square-card">
<div class="auth-icon application">📰</div>
<div class="auth-info">
<h3 class="auth-name">会会广场互动</h3>
<p class="auth-type">按照广场调度器设置自动执行</p>
<div class="auth-permissions">
<span class="permission-tag" v-for="permission in squareAuthorization.permissions" :key="permission">
{{ getPermissionText(permission) }}
</span>
</div>
<p class="scheduler-note">功能已开发,当前仍在验证中;执行时段、互动间隔及各操作触发概率由运营后台统一控制。</p>
</div>
<div class="auth-actions">
<span class="auth-status" :class="squareAuthorization.status">
{{ squareAuthorization.status === 'active' ? '已授权' : '未授权' }}
</span>
<button
class="auth-toggle-btn"
:class="{ revoke: squareAuthorization.status === 'active' }"
:disabled="saving"
@click="toggleSquareAuthorization"
>
{{ saving ? '处理中' : squareAuthorization.status === 'active' ? '撤销' : '授权' }}
</button>
</div>
</div>
<div v-for="feature in unavailableFeatures" :key="feature.id" class="auth-card unavailable-card">
<div class="auth-icon application">{{ feature.icon }}</div>
<div class="auth-info">
<h3 class="auth-name">{{ feature.name }}</h3>
<p class="auth-type">{{ feature.description }}</p>
</div>
<div class="auth-actions">
<span class="auth-status inactive">未开启</span>
<button class="auth-toggle-btn unavailable-toggle" @click="showUnavailableNotice">开启</button>
</div>
</div>
<div class="auth-card" v-for="auth in otherAuthorizations" :key="auth.id">
<div class="auth-icon" :class="auth.targetType">{{ getAuthIcon(auth.targetType) }}</div>
<div class="auth-info">
<h3 class="auth-name">{{ getAuthTargetName(auth.targetName) }}</h3>
<p class="auth-type">{{ getAuthTypeText(auth.targetType) }}</p>
<div class="auth-permissions">
<span class="permission-tag" v-for="permission in auth.permissions" :key="permission">
{{ getPermissionText(permission) }}
</span>
</div>
</div>
<div class="auth-actions">
<span class="auth-status" :class="auth.status">{{ auth.status === 'active' ? '已授权' : '已撤销' }}</span>
<button class="auth-toggle-btn" :disabled="saving" @click="toggleExistingAuthorization(auth)">
{{ auth.status === 'active' ? '撤销' : '授权' }}
</button>
</div>
</div>
</section>
</div>
</template>
<script setup lang="ts">
import { computed, ref, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { useAvatarStore } from '@/store/avatar'
import {
getAuthorizationList,
updateAuthorization,
type Authorization
} from '@/api'
import { pickAvatarId, unwrapListData } from '@/utils/avatar-page-data.js'
const router = useRouter()
const avatarStore = useAvatarStore()
const authList = ref<Authorization[]>([])
const saving = ref(false)
const UNAVAILABLE_MESSAGE = '该功能尚未向公众用户开放,如需使用请联系会会运营团队'
const SQUARE_INTERACTION_TARGET_ID = 'huihui-square'
const SQUARE_INTERACTION_PERMISSIONS = ['like', 'collect', 'comment', 'reply'] as const
const unavailableFeatures = [
{ id: 'make-friends', name: '交友与添加好友', description: '暂未开放', icon: '👥' },
{ id: 'start-chat', name: '主动发起聊天', description: '暂未开放', icon: '💬' },
{ id: 'publish-microblog', name: '发布微播内容', description: '暂未开放', icon: '📝' },
{ id: 'browse-square', name: '浏览会会广场', description: '暂未开放', icon: '🔎' }
]
const squareAuthorization = computed<Authorization>(() => {
return authList.value.find((item) => item.targetId === SQUARE_INTERACTION_TARGET_ID) || {
id: '',
avatarId: avatarStore.currentAvatarId || '',
targetType: 'application',
targetId: SQUARE_INTERACTION_TARGET_ID,
targetName: '会会广场互动',
permissions: [...SQUARE_INTERACTION_PERMISSIONS],
status: 'inactive',
takeoverEnabled: false,
takeoverMode: 'immediate',
takeoverDelaySeconds: 180,
createdAt: ''
}
})
const otherAuthorizations = computed(() =>
authList.value.filter((item) => item.targetId !== SQUARE_INTERACTION_TARGET_ID && !isUnavailableFeature(item.targetName))
)
const isUnavailableFeature = (name: string) => unavailableFeatures.some((feature) =>
name.replace(/发布微博内容/g, '发布微播内容') === feature.name
)
const currentAvatarId = async () => {
if (!avatarStore.avatars.length) await avatarStore.loadAvatars()
return pickAvatarId(avatarStore.currentAvatarId, avatarStore.avatars)
}
const loadAuth = async () => {
try {
const avatarId = await currentAvatarId()
authList.value = avatarId ? unwrapListData(await getAuthorizationList(avatarId)) : []
} catch (error) {
console.error('加载授权失败', error)
}
}
const toggleSquareAuthorization = async () => {
const avatarId = await currentAvatarId()
if (!avatarId || saving.value) return
saving.value = true
try {
if (!squareAuthorization.value.id) return
const updated = await updateAuthorization(avatarId, {
id: squareAuthorization.value.id,
status: squareAuthorization.value.status === 'active' ? 'inactive' : 'active'
})
authList.value = authList.value.map((item) => item.id === updated.id ? updated : item)
} catch (error) {
alert('操作失败,请重试')
} finally {
saving.value = false
}
}
const toggleExistingAuthorization = async (auth: Authorization) => {
const avatarId = await currentAvatarId()
if (!avatarId || saving.value) return
saving.value = true
try {
const updated = await updateAuthorization(avatarId, {
id: auth.id,
status: auth.status === 'active' ? 'inactive' : 'active'
})
authList.value = authList.value.map((item) => item.id === updated.id ? updated : item)
} catch (error) {
alert('操作失败,请重试')
} finally {
saving.value = false
}
}
const showUnavailableNotice = () => alert(UNAVAILABLE_MESSAGE)
const getAuthIcon = (type: string) => ({ user: '👤', organization: '🏢', application: '📱' }[type] || '🔑')
const getAuthTypeText = (type: string) => ({ user: '用户', organization: '组织', application: '应用' }[type] || type)
const getAuthTargetName = (name: string) => name.replace(/发布微博内容/g, '发布微播内容')
const getPermissionText = (permission: string) => ({
read: '读取', write: '写入', reply: '回复', edit: '编辑',
like: '点赞', collect: '收藏', comment: '评论'
}[permission] || permission)
const goBack = () => router.back()
onMounted(loadAuth)
</script>
<style scoped>
.auth-manage-page { min-height: 100vh; background: #f8f9fa; padding-bottom: 80px; }
.page-header { display: flex; align-items: center; justify-content: space-between; padding: 16px 20px; background: white; border-bottom: 1px solid #edeef1; }
.back-btn { background: none; border: none; font-size: 24px; cursor: pointer; padding: 4px 8px; color: #18191c; }
.page-title { font-size: 17px; font-weight: 600; margin: 0; color: #18191c; }
.header-spacer { width: 40px; }
.intro-card { display: flex; gap: 12px; margin: 20px 16px 0; padding: 16px; color: #7c2d12; background: #fff7ed; border: 1px solid #fed7aa; border-radius: 14px; }
.intro-icon { font-size: 24px; }
.intro-card h2 { margin: 0 0 6px; font-size: 15px; }
.intro-card p { margin: 0; font-size: 12px; line-height: 1.6; color: #9a3412; }
.auth-list { padding: 16px 16px 20px; display: flex; flex-direction: column; gap: 12px; }
.auth-card { display: flex; align-items: flex-start; gap: 12px; padding: 16px; background: white; border-radius: 12px; box-shadow: 0 2px 8px rgba(0, 0, 0, .05); }
.square-card { border: 1px solid #ffedd5; }
.unavailable-card { opacity: .78; }
.auth-icon { font-size: 24px; width: 48px; height: 48px; display: flex; align-items: center; justify-content: center; border-radius: 12px; background: #fff0e6; flex-shrink: 0; }
.auth-info { flex: 1; min-width: 0; }
.auth-name { font-size: 15px; font-weight: 600; margin: 0 0 4px; color: #18191c; }
.auth-type { font-size: 12px; color: #9398ae; margin: 0 0 8px; }
.auth-permissions { display: flex; gap: 6px; flex-wrap: wrap; }
.permission-tag { padding: 4px 8px; background: #f3f4f6; border-radius: 6px; font-size: 11px; color: #6b7280; }
.scheduler-note { margin: 10px 0 0; color: #9398ae; font-size: 11px; line-height: 1.5; white-space: normal; overflow-wrap: anywhere; word-break: break-word; }
.auth-actions { display: flex; flex-direction: column; align-items: flex-end; gap: 8px; flex-shrink: 0; }
.auth-status { font-size: 12px; font-weight: 500; }
.auth-status.active { color: #22c55e; }
.auth-status.inactive { color: #9398ae; }
.auth-toggle-btn { padding: 6px 12px; border-radius: 8px; font-size: 12px; font-weight: 500; cursor: pointer; border: none; background: #f97316; color: white; }
.auth-toggle-btn.revoke { color: #6b7280; background: #f3f4f6; }
.unavailable-toggle { background: #d1d5db; color: #4b5563; }
.auth-toggle-btn:disabled { cursor: not-allowed; opacity: .6; }
</style>