feat(users): govern profiles and access
Normalize fixed profile fields, profile actions, and the five-column capability matrix for comments, uploads, and downloads.
This commit is contained in:
1 parent
0b06b2c7df
commit
8241570ca2
67 files changed
+780
-1127
No files matched your search
@@ -1,49 +1,30 @@
|
||||
<template>
|
||||
<div class="aksrt-page-container">
|
||||
<PageHeader
|
||||
title="用户功能"
|
||||
desc="统一配置用户中心和公开主页的功能模块,让前台页面和后台字段使用同一份契约。"
|
||||
>
|
||||
<PageHeader title="用户功能" desc="用户中心的资料、背景图与公开资料均使用统一的能力矩阵和隐私规则。">
|
||||
<template #actions>
|
||||
<n-space>
|
||||
<n-button quaternary @click="go('/permissions/capabilities')">基本权限</n-button>
|
||||
<n-button quaternary @click="go('/page-display/user-home')">公开主页</n-button>
|
||||
<n-button quaternary @click="go('/permissions/attachment')">附件权限</n-button>
|
||||
</n-space>
|
||||
</template>
|
||||
</PageHeader>
|
||||
|
||||
<n-spin :show="loading" style="width: 100%; min-height: 200px;">
|
||||
<n-spin :show="loading" style="width: 100%; min-height: 180px;">
|
||||
<n-card class="aksrt-section-card">
|
||||
<div class="aksrt-section-header">
|
||||
<h3>当前联动摘要</h3>
|
||||
</div>
|
||||
<n-space>
|
||||
<n-tag type="info" round>固定入口:个人信息 / 账号设置 / 消息通知</n-tag>
|
||||
<n-tag round :type="form.user_cover_upload ? 'success' : 'default'">
|
||||
背景图编辑位:{{ form.user_cover_upload ? '已展示' : '已隐藏' }}
|
||||
</n-tag>
|
||||
<n-tag round :type="backgroundUploadAllowed ? 'success' : 'warning'">
|
||||
背景图上传权限:{{ backgroundUploadAllowed ? '已开放' : '未开放' }}
|
||||
</n-tag>
|
||||
<n-tag round :type="homeSummary.sidebar ? 'success' : 'default'">
|
||||
公开主页侧栏:{{ homeSummary.sidebar ? '开启' : '关闭' }}
|
||||
</n-tag>
|
||||
<div class="aksrt-section-header"><h3>固定资料与操作入口</h3></div>
|
||||
<n-space vertical size="large">
|
||||
<n-alert type="info" :show-icon="false">
|
||||
个人资料固定为昵称、个人签名、隐私设置、性别、个人网站、QQ、微信和 GitHub。公开资料由访问者身份过滤,原始社交资料不会下发给无权访客。
|
||||
</n-alert>
|
||||
<n-alert type="info" :show-icon="false">
|
||||
背景图操作入口始终显示在资料卡右下角的三点菜单中;“上传头像”和“上传用户资料卡背景图”由基本权限矩阵控制。
|
||||
</n-alert>
|
||||
</n-space>
|
||||
</n-card>
|
||||
|
||||
<n-card class="aksrt-section-card">
|
||||
<div class="aksrt-section-header">
|
||||
<h3>用户中心面板</h3>
|
||||
</div>
|
||||
<div class="aksrt-section-header"><h3>用户中心面板</h3></div>
|
||||
<n-form label-placement="left" label-width="170" class="aksrt-form">
|
||||
<n-form-item label="固定面板">
|
||||
<n-space>
|
||||
<n-tag round>个人信息</n-tag>
|
||||
<n-tag round>账号设置</n-tag>
|
||||
<n-tag round>消息通知</n-tag>
|
||||
</n-space>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="可切换业务面板">
|
||||
<n-checkbox-group v-model:value="form.dynamicTabs">
|
||||
<n-space vertical>
|
||||
@@ -53,40 +34,10 @@
|
||||
<n-checkbox value="credits">账户余额 / 签到</n-checkbox>
|
||||
</n-space>
|
||||
</n-checkbox-group>
|
||||
<template #feedback>
|
||||
<span class="aksrt-hint-text">“个人信息”始终保留在首位;积分面板仅在积分系统启用时显示。</span>
|
||||
</template>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
</n-card>
|
||||
|
||||
<n-card class="aksrt-section-card">
|
||||
<div class="aksrt-section-header">
|
||||
<h3>公开资料编辑位</h3>
|
||||
</div>
|
||||
<n-form :model="form" label-placement="left" label-width="170" class="aksrt-form">
|
||||
<n-form-item label="背景图编辑位">
|
||||
<n-switch v-model:value="form.user_cover_upload" />
|
||||
<template #feedback>
|
||||
<span class="aksrt-hint-text">这里只控制前台入口显示;真正的上传权限仍由“附件权限”页治理。</span>
|
||||
</template>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="公开社交链接">
|
||||
<n-switch v-model:value="form.user_social_links" />
|
||||
<template #feedback>
|
||||
<span class="aksrt-hint-text">关闭后,用户中心联系方式编辑区和公开主页联系卡片会一起隐藏。</span>
|
||||
</template>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
</n-card>
|
||||
|
||||
<n-card class="aksrt-section-card">
|
||||
<n-alert type="info" :show-icon="false">
|
||||
公开主页的封面、侧栏、关注按钮和公开 Tab 仍在“页面&显示 / 用户主页”维护;本页负责“用户中心能否编辑哪些内容”。
|
||||
</n-alert>
|
||||
</n-card>
|
||||
|
||||
<div class="aksrt-form-actions">
|
||||
<n-button type="primary" :loading="saving" @click="handleSave">保存配置</n-button>
|
||||
</div>
|
||||
@@ -103,61 +54,22 @@ import PageHeader from '@/components/PageHeader.vue'
|
||||
|
||||
const router = useRouter()
|
||||
const message = useMessage()
|
||||
|
||||
const loading = ref(true)
|
||||
const saving = ref(false)
|
||||
const backgroundUploadAllowed = ref(false)
|
||||
const homeSummary = reactive({
|
||||
tabs: [],
|
||||
sidebar: true,
|
||||
})
|
||||
|
||||
const form = reactive({
|
||||
dynamicTabs: ['posts', 'comments', 'favorites'],
|
||||
user_cover_upload: true,
|
||||
user_social_links: true,
|
||||
})
|
||||
|
||||
function go(path) {
|
||||
router.push(path)
|
||||
}
|
||||
|
||||
function extractData(response) {
|
||||
return response?.data ?? response ?? {}
|
||||
}
|
||||
const form = reactive({ dynamicTabs: ['posts', 'comments', 'favorites'] })
|
||||
|
||||
function go(path) { router.push(path) }
|
||||
function extractData(response) { return response?.data ?? response ?? {} }
|
||||
function parseTabs(value) {
|
||||
const items = Array.isArray(value)
|
||||
? value
|
||||
: String(value || '')
|
||||
.split(/[\s,]+/)
|
||||
.filter(Boolean)
|
||||
|
||||
return items
|
||||
.map((item) => String(item).trim())
|
||||
.filter((item) => ['posts', 'comments', 'favorites', 'credits'].includes(item))
|
||||
const values = Array.isArray(value) ? value : String(value || '').split(/[\s,]+/)
|
||||
return values.map((value) => String(value).trim()).filter((value) => ['posts', 'comments', 'favorites', 'credits'].includes(value))
|
||||
}
|
||||
|
||||
async function load() {
|
||||
loading.value = true
|
||||
try {
|
||||
const [featureRes, homeRes, permissionRes] = await Promise.all([
|
||||
api.getSchemaPage('users', 'features'),
|
||||
api.getSchemaPage('page_display', 'user-home'),
|
||||
api.getPermissionsAttachmentSettings(),
|
||||
])
|
||||
|
||||
const featureData = extractData(featureRes)
|
||||
const homeData = extractData(homeRes)
|
||||
const permissionData = extractData(permissionRes)
|
||||
|
||||
form.dynamicTabs = parseTabs(featureData.user_center_tabs || 'profile,posts,comments,favorites')
|
||||
form.user_cover_upload = featureData.user_cover_upload !== false
|
||||
form.user_social_links = featureData.user_social_links !== false
|
||||
|
||||
homeSummary.tabs = parseTabs(homeData.user_home_tabs || 'posts,comments,favorites')
|
||||
homeSummary.sidebar = homeData.user_home_sidebar !== false
|
||||
backgroundUploadAllowed.value = !!permissionData.allow_background_upload
|
||||
const data = extractData(await api.getSchemaPage('users', 'features'))
|
||||
form.dynamicTabs = parseTabs(data.user_center_tabs || 'profile,posts,comments,favorites')
|
||||
} catch (error) {
|
||||
message.error(error.message || '加载用户功能配置失败')
|
||||
} finally {
|
||||
@@ -168,11 +80,7 @@ async function load() {
|
||||
async function handleSave() {
|
||||
saving.value = true
|
||||
try {
|
||||
await api.saveSchemaPage('users', 'features', {
|
||||
user_center_tabs: ['profile', ...form.dynamicTabs].join(','),
|
||||
user_cover_upload: form.user_cover_upload,
|
||||
user_social_links: form.user_social_links,
|
||||
})
|
||||
await api.saveSchemaPage('users', 'features', { user_center_tabs: ['profile', ...form.dynamicTabs].join(',') })
|
||||
message.success('用户功能配置已保存')
|
||||
} catch (error) {
|
||||
message.error(error.message || '保存失败')
|
||||
|
||||
Reference in new issue
Block a user