refactor(developer-config): 移除开发者配置页面相关代码和文档

- 删除应用配置页面及相关组件,重构路由为 /developer/config/[id].vue
- 移除开发者文档页面及其导航与样式实现
- 清理开发者侧功能完善工作日志文件
- 删除全局.gitignore配置文件,清理无用忽略规则
- 优化应用配置页面的参数读取和路由结构,解决刷新404问题
- 解决数据库配置唯一键冲突,调整保存逻辑避免重复插入
- 移除对后端配置加密字段的 secret 标记,修正加密异常问题
This commit is contained in:
2026-04-09 07:35:34 +08:00
parent 3209d92cc5
commit f9e1286ab1
130 changed files with 18656 additions and 22143 deletions

View File

@@ -1,664 +0,0 @@
<template>
<a-layout class="min-h-screen layout-shell">
<a-layout class="w-full">
<SiteHeader />
<a-layout class="body w-full mx-auto max-w-screen-xl">
<!-- 未授权提示页面 -->
<div v-if="!accessible && ready" class="w-full">
<!-- 场景 1已登录但既不是平台开发者也没有被邀请到任何应用 -->
<div v-if="accessChecked && !hasJoinedApps" class="apply-developer-page">
<div class="apply-card">
<div class="icon-wrapper">
<span class="icon">🛠</span>
</div>
<h1 class="title">加入开发者中心</h1>
<p class="subtitle">
你还没有加入任何应用请联系应用管理员发送邀请链接或申请平台开发者资质以创建自己的应用
</p>
<div class="features">
<div class="feature-item">
<span class="feature-icon">🔑</span>
<span>获取 API Key调用 200+ REST 接口</span>
</div>
<div class="feature-item">
<span class="feature-icon">📦</span>
<span>创建和管理您的应用</span>
</div>
<div class="feature-item">
<span class="feature-icon">💻</span>
<span>申请源码访问权限</span>
</div>
<div class="feature-item">
<span class="feature-icon">🤖</span>
<span>接入 AI 智能体 API</span>
</div>
</div>
<div class="actions">
<a-button
type="primary"
size="large"
class="apply-btn"
:loading="applying"
@click="handleApply"
>
申请平台开发者资质
</a-button>
<a-button
size="large"
class="back-btn"
@click="navigateTo('/console')"
>
返回用户中心
</a-button>
</div>
<div class="tips">
<p>💡 收到应用邀请后刷新此页面即可进入开发者中心</p>
<p>如有疑问请联系客服或发送邮件至 support@websopy.com</p>
</div>
</div>
</div>
</div>
<template v-else>
<!-- 侧边栏 -->
<a-layout-sider
class="sider"
:width="240"
breakpoint="lg"
theme="light"
:trigger="null"
collapsible
v-model:collapsed="collapsed"
>
<!-- 品牌区 -->
<div class="sider-brand" :class="{ collapsed }">
<div class="sider-brand-icon">
<span class="text-lg">🛠</span>
</div>
<div v-if="!collapsed" class="sider-brand-text">
<div class="sider-title">开发者中心</div>
<div class="sider-subtitle">Developer Console</div>
</div>
</div>
<!-- 用户信息非折叠状态 -->
<div v-if="!collapsed && user" class="sider-user">
<a-avatar :size="32" class="sider-user-avatar">
{{ userDisplayName.charAt(0).toUpperCase() }}
</a-avatar>
<div class="sider-user-info">
<div class="sider-user-name">{{ userDisplayName }}</div>
<a-tag :color="isPlatformDev ? 'green' : 'blue'" class="sider-user-role">
{{ isPlatformDev ? '平台开发者' : '协作成员' }}
</a-tag>
</div>
</div>
<!-- 分组导航菜单 -->
<div class="sider-nav">
<template v-for="(group, gIdx) in navGroups" :key="gIdx">
<!-- 分组标题 -->
<div v-if="group.name" class="nav-group-label">{{ group.name }}</div>
<!-- 菜单项 -->
<div
v-for="item in group.items"
:key="item.key"
class="nav-item"
:class="{ active: isActive(item.to), collapsed }"
@click="navigateTo(item.to)"
>
<span class="nav-item-icon">{{ item.icon }}</span>
<span v-if="!collapsed" class="nav-item-label">{{ item.label }}</span>
<a-tag
v-if="!collapsed && item.badge"
:color="item.badge === 'NEW' ? 'green' : 'orange'"
class="nav-item-badge"
>{{ item.badge }}</a-tag>
</div>
</template>
</div>
<!-- 底部返回入口 -->
<div v-if="!collapsed" class="sider-footer">
<div class="sider-footer-link" @click="navigateTo('/developer-center')">
<span>📄</span> 开发文档
</div>
<div class="sider-footer-link" @click="navigateTo('/')">
<span>🏠</span> 返回首页
</div>
</div>
<!-- 折叠触发器 -->
<div
class="sider-collapse-trigger"
role="button"
tabindex="0"
:aria-label="collapsed ? '展开菜单' : '收起菜单'"
@click="toggleCollapsed"
@keydown.enter.prevent="toggleCollapsed"
@keydown.space.prevent="toggleCollapsed"
>
<RightOutlined :style="{ fontSize: '10px' }" v-if="collapsed" />
<LeftOutlined :style="{ fontSize: '10px' }" v-else />
</div>
</a-layout-sider>
<!-- 主内容区 -->
<a-layout class="main">
<a-layout-content class="content">
<a-spin v-if="!ready && isClient" size="large" tip="加载中..." class="spin" />
<NuxtPage />
</a-layout-content>
</a-layout>
</template>
</a-layout>
<SiteFooter />
</a-layout>
<!-- 邀请通知组件 -->
<ClientOnly>
<InviteNotification />
</ClientOnly>
</a-layout>
</template>
<script setup lang="ts">
import { LeftOutlined, RightOutlined } from '@ant-design/icons-vue'
import { message } from 'ant-design-vue'
import { developerNav } from '@/config/developer-nav'
import { getUserInfo } from '@/api/layout'
import { getToken } from '@/utils/token-util'
import { clearAuthz, setAuthzFromUser } from '@/utils/permission'
import type { User } from '@/api/system/user/model'
import { useAppPermission } from '@/composables/useAppPermission'
import SiteHeader from '~/components/SiteHeader.vue'
import SiteFooter from '~/components/SiteFooter.vue'
import InviteNotification from '~/components/invite/InviteNotification.vue'
const route = useRoute()
const collapsed = ref(false)
const user = ref<User | null>(null)
const isClient = ref(false)
onMounted(() => {
isClient.value = true
})
const userDisplayName = computed(() => {
const u = user.value
const nickname = u?.nickname?.trim()
const username = u?.username?.trim()
const phone = u?.phone?.trim()
const mobile = u?.mobile?.trim()
if (nickname) return nickname
if (username) return username
if (phone) return phone
if (mobile) return mobile
return '开发者'
})
// 按分组整理导航
const navGroups = computed(() => {
const groups: { name: string; items: typeof developerNav }[] = []
const groupMap = new Map<string, typeof developerNav[0][]>()
for (const item of developerNav) {
const g = item.group ?? ''
if (!groupMap.has(g)) groupMap.set(g, [])
groupMap.get(g)!.push(item)
}
for (const [name, items] of groupMap) {
groups.push({ name, items })
}
return groups
})
function isActive(to: string) {
if (to === '/developer') return route.path === '/developer'
return route.path === to || route.path.startsWith(to + '/')
}
function toggleCollapsed() {
collapsed.value = !collapsed.value
}
// ============ 权限控制 ============
const ready = ref(false)
const accessible = ref(false)
const accessChecked = ref(false)
const hasJoinedApps = ref(false)
const isPlatformDev = ref(false)
const applying = ref(false)
const { checkDeveloperAccess, loadAppPermissions, isPlatformDeveloper } = useAppPermission()
onMounted(async () => {
const token = getToken()
if (!token) {
clearAuthz()
message.warning('请先登录后访问开发者中心')
await navigateTo(`/login?from=${encodeURIComponent(route.path)}`)
ready.value = true
return
}
try {
const me = await getUserInfo()
user.value = me
setAuthzFromUser(me)
// 检查开发者访问权限type===2 或有参与的应用都可以进入
const result = await checkDeveloperAccess()
accessChecked.value = true
accessible.value = result.accessible
hasJoinedApps.value = result.hasJoinedApps
isPlatformDev.value = result.isPlatformDeveloper
// 如果有权限,加载完整的应用权限列表
if (result.accessible) {
await loadAppPermissions()
}
}
catch {
// check-access 接口失败时降级type=2 仍然放行
const userType = localStorage.getItem('UserType')
if (userType === '2') {
accessible.value = true
accessChecked.value = true
isPlatformDev.value = true
}
else {
accessible.value = false
accessChecked.value = true
}
}
ready.value = true
})
// 申请开发者资质
async function handleApply() {
applying.value = true
try {
message.info('开发者资质申请功能即将上线,敬请期待!')
}
finally {
applying.value = false
}
}
</script>
<style scoped>
.layout-shell {
background: #f0f2f5;
}
/* 侧边栏 */
.sider {
border-radius: 14px;
overflow: visible;
position: relative;
background: #fff;
border: 1px solid rgba(0, 0, 0, 0.06);
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.04);
}
.sider-brand {
display: flex;
align-items: center;
gap: 10px;
padding: 18px 16px 12px;
border-bottom: 1px solid #f0f0f0;
}
.sider-brand.collapsed {
justify-content: center;
padding: 18px 8px 12px;
}
.sider-brand-icon {
flex-shrink: 0;
width: 36px;
height: 36px;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 10px;
font-size: 18px;
}
.sider-brand-text {
flex: 1;
min-width: 0;
}
.sider-title {
color: rgba(0, 0, 0, 0.88);
font-size: 14px;
font-weight: 600;
line-height: 1.3;
}
.sider-subtitle {
color: rgba(0, 0, 0, 0.35);
font-size: 11px;
line-height: 1.3;
margin-top: 1px;
}
/* 用户信息 */
.sider-user {
display: flex;
align-items: center;
gap: 10px;
padding: 12px 16px;
background: linear-gradient(135deg, #f5f0ff 0%, #eff6ff 100%);
margin: 10px 10px 0;
border-radius: 10px;
}
.sider-user-avatar {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
flex-shrink: 0;
font-size: 14px;
font-weight: 600;
}
.sider-user-info {
flex: 1;
min-width: 0;
}
.sider-user-name {
font-size: 13px;
font-weight: 500;
color: rgba(0, 0, 0, 0.88);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.sider-user-role {
font-size: 10px;
margin: 0;
line-height: 1.5;
}
/* 导航 */
.sider-nav {
padding: 8px 8px 0;
flex: 1;
}
.nav-group-label {
font-size: 11px;
font-weight: 600;
color: rgba(0, 0, 0, 0.35);
text-transform: uppercase;
letter-spacing: 0.05em;
padding: 14px 10px 5px;
user-select: none;
}
.nav-item {
display: flex;
align-items: center;
gap: 9px;
padding: 9px 10px;
border-radius: 9px;
cursor: pointer;
transition: all 0.15s ease;
color: rgba(0, 0, 0, 0.72);
font-size: 14px;
margin-bottom: 2px;
user-select: none;
}
.nav-item:hover {
background: rgba(99, 102, 241, 0.07);
color: #5145cd;
}
.nav-item.active {
background: rgba(99, 102, 241, 0.12);
color: #4f46e5;
font-weight: 500;
}
.nav-item.collapsed {
justify-content: center;
padding: 9px;
}
.nav-item-icon {
font-size: 16px;
flex-shrink: 0;
width: 20px;
text-align: center;
line-height: 1;
}
.nav-item-label {
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.nav-item-badge {
font-size: 10px;
padding: 0 5px;
line-height: 16px;
height: 16px;
flex-shrink: 0;
}
/* 底部快捷入口 */
.sider-footer {
padding: 12px 10px 14px;
border-top: 1px solid #f0f0f0;
margin-top: 8px;
}
.sider-footer-link {
display: flex;
align-items: center;
gap: 8px;
padding: 7px 10px;
border-radius: 8px;
cursor: pointer;
color: rgba(0, 0, 0, 0.45);
font-size: 13px;
transition: all 0.15s;
margin-bottom: 2px;
}
.sider-footer-link:hover {
background: #f5f5f5;
color: rgba(0, 0, 0, 0.72);
}
/* 折叠触发器 */
.sider-collapse-trigger {
position: absolute;
top: 50%;
right: -16px;
transform: translateY(-50%);
width: 16px;
height: 36px;
display: flex;
align-items: center;
justify-content: center;
background: #fff;
border: 1px solid rgba(0, 0, 0, 0.1);
border-left: 0;
border-radius: 0 9999px 9999px 0;
box-shadow: 2px 0 8px rgba(0, 0, 0, 0.06);
color: rgba(0, 0, 0, 0.45);
cursor: pointer;
z-index: 2;
transition: all 0.15s;
}
.sider-collapse-trigger:hover {
background: #f5f5f5;
color: #4f46e5;
}
/* 内容区 */
.body {
margin: 16px auto;
padding: 0 16px;
}
.main {
margin-left: 16px;
background: transparent;
}
.content {
min-height: 520px;
background: #fff;
border-radius: 14px;
padding: 0;
overflow: hidden;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.04);
border: 1px solid rgba(0, 0, 0, 0.05);
}
.forbidden-wrap {
display: flex;
align-items: center;
justify-content: center;
min-height: 480px;
}
.spin {
display: flex;
align-items: center;
justify-content: center;
min-height: 480px;
}
/* 申请开发者页面 */
.apply-developer-page {
display: flex;
align-items: center;
justify-content: center;
min-height: 600px;
padding: 40px 20px;
}
.apply-card {
max-width: 560px;
width: 100%;
background: #fff;
border-radius: 16px;
padding: 48px 40px;
text-align: center;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
border: 1px solid rgba(0, 0, 0, 0.06);
}
.icon-wrapper {
width: 80px;
height: 80px;
margin: 0 auto 24px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 20px;
display: flex;
align-items: center;
justify-content: center;
}
.icon {
font-size: 40px;
}
.title {
font-size: 24px;
font-weight: 600;
color: rgba(0, 0, 0, 0.88);
margin-bottom: 12px;
}
.subtitle {
font-size: 14px;
color: rgba(0, 0, 0, 0.45);
margin-bottom: 32px;
line-height: 1.6;
}
.features {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
margin-bottom: 32px;
text-align: left;
}
.feature-item {
display: flex;
align-items: center;
gap: 10px;
padding: 12px 16px;
background: #f8f9fa;
border-radius: 10px;
font-size: 13px;
color: rgba(0, 0, 0, 0.65);
}
.feature-icon {
font-size: 18px;
}
.actions {
display: flex;
gap: 12px;
justify-content: center;
margin-bottom: 24px;
}
.apply-btn {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border: none;
font-weight: 500;
}
.apply-btn:hover {
opacity: 0.9;
}
.back-btn {
color: rgba(0, 0, 0, 0.65);
}
.tips {
font-size: 12px;
color: rgba(0, 0, 0, 0.35);
line-height: 1.8;
}
@media (max-width: 640px) {
.apply-card {
padding: 32px 24px;
}
.features {
grid-template-columns: 1fr;
}
.actions {
flex-direction: column;
}
}
</style>