refactor(developer-config): 移除开发者配置页面相关代码和文档
- 删除应用配置页面及相关组件,重构路由为 /developer/config/[id].vue - 移除开发者文档页面及其导航与样式实现 - 清理开发者侧功能完善工作日志文件 - 删除全局.gitignore配置文件,清理无用忽略规则 - 优化应用配置页面的参数读取和路由结构,解决刷新404问题 - 解决数据库配置唯一键冲突,调整保存逻辑避免重复插入 - 移除对后端配置加密字段的 secret 标记,修正加密异常问题
This commit is contained in:
@@ -1,415 +1,234 @@
|
||||
<template>
|
||||
<div class="developers-page">
|
||||
<div class="page-header">
|
||||
<div>
|
||||
<h2 class="page-title">🧑💻 开发者管理</h2>
|
||||
<p class="page-desc">管理平台上有应用发布记录的开发者账号</p>
|
||||
</div>
|
||||
<a-space>
|
||||
<a-button @click="loadData" :loading="loading">
|
||||
<template #icon><ReloadOutlined /></template>
|
||||
刷新
|
||||
</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
<a-card :bordered="false">
|
||||
<template #title>开发者管理</template>
|
||||
|
||||
<!-- 统计卡片 -->
|
||||
<a-row :gutter="[16, 16]" class="mb-6">
|
||||
<a-col :xs="12" :md="6" v-for="stat in stats" :key="stat.label">
|
||||
<div class="stat-card" :class="stat.color">
|
||||
<div class="stat-icon">{{ stat.icon }}</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-value">{{ stat.value }}</div>
|
||||
<div class="stat-label">{{ stat.label }}</div>
|
||||
<a-tabs v-model:activeKey="activeTab">
|
||||
<!-- 开发者申请 -->
|
||||
<a-tab-pane key="apply" tab="开发者申请">
|
||||
<div class="filter-bar">
|
||||
<a-input-search v-model:value="searchKeyword" placeholder="搜索申请人、企业..." style="width: 280px" allow-clear />
|
||||
<a-select v-model:value="filterType" placeholder="申请类型" style="width: 150px" allow-clear>
|
||||
<a-select-option value="api">API 开发者</a-select-option>
|
||||
<a-select-option value="plugin">插件开发者</a-select-option>
|
||||
<a-select-option value="template">模板开发者</a-select-option>
|
||||
</a-select>
|
||||
<a-button @click="resetFilter">重置</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<!-- 筛选栏 -->
|
||||
<div class="filter-bar">
|
||||
<a-radio-group v-model:value="filterType" button-style="solid" @change="handleFilterChange">
|
||||
<a-radio-button :value="2">开发者用户</a-radio-button>
|
||||
<a-radio-button :value="null">全部用户</a-radio-button>
|
||||
</a-radio-group>
|
||||
<a-input-search
|
||||
v-model:value="searchKeyword"
|
||||
placeholder="搜索用户名/昵称/手机号"
|
||||
style="width: 240px"
|
||||
allow-clear
|
||||
@search="handleSearch"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 开发者列表 -->
|
||||
<div class="panel">
|
||||
<div class="panel-header">
|
||||
<span class="panel-title">🧑💻 用户列表</span>
|
||||
<a-tag color="blue">共 {{ pagination.total }} 人</a-tag>
|
||||
</div>
|
||||
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="developers"
|
||||
:loading="loading"
|
||||
:pagination="pagination"
|
||||
row-key="userId"
|
||||
@change="handleTableChange"
|
||||
size="middle"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<!-- 开发者信息 -->
|
||||
<template v-if="column.key === 'devInfo'">
|
||||
<div class="dev-info-cell">
|
||||
<a-avatar :size="38" :src="record.avatar || record.avatarUrl">
|
||||
<template #icon><UserOutlined /></template>
|
||||
</a-avatar>
|
||||
<div class="dev-info-text">
|
||||
<div class="dev-name">{{ record.nickname || record.username || '-' }}</div>
|
||||
<div class="dev-sub" v-if="record.username">@{{ record.username }}</div>
|
||||
<div class="dev-sub" v-if="record.phone || record.mobile">
|
||||
📱 {{ record.phone || record.mobile }}
|
||||
<a-table :columns="applyColumns" :data-source="applyData" row-key="id">
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'applicant'">
|
||||
<div class="user-cell">
|
||||
<a-avatar style="background: linear-gradient(135deg, #11998e, #38ef7d)" :size="36">{{ record.name[0] }}</a-avatar>
|
||||
<div>
|
||||
<p class="name-text">{{ record.name }}</p>
|
||||
<p class="sub-text">{{ record.email }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'type'">
|
||||
<a-tag :color="typeColor[record.type]">{{ typeMap[record.type] }}</a-tag>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'status'">
|
||||
<a-badge :status="statusBadge[record.status]" :text="statusText[record.status]" />
|
||||
</template>
|
||||
<template v-else-if="column.key === 'actions'">
|
||||
<a-space>
|
||||
<a-button v-if="record.status === 'pending'" type="primary" size="small" @click="handleApprove(record)">审核</a-button>
|
||||
<a-button type="link" size="small" @click="handleViewApply(record)">详情</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-tab-pane>
|
||||
|
||||
<!-- 用户类型 -->
|
||||
<template v-if="column.key === 'userType'">
|
||||
<a-tag v-if="record.type === 2" color="purple">开发者</a-tag>
|
||||
<a-tag v-else-if="record.type === 1" color="blue">企业用户</a-tag>
|
||||
<a-tag v-else color="default">普通用户</a-tag>
|
||||
</template>
|
||||
<!-- 权限审核 -->
|
||||
<a-tab-pane key="audit" tab="权限审核">
|
||||
<div class="filter-bar">
|
||||
<a-select v-model:value="auditFilterType" placeholder="权限类型" style="width: 150px" allow-clear>
|
||||
<a-select-option value="api">API 权限</a-select-option>
|
||||
<a-select-option value="plugin">插件权限</a-select-option>
|
||||
<a-select-option value="admin">管理权限</a-select-option>
|
||||
</a-select>
|
||||
<a-button type="primary" @click="auditModalVisible = true">新增审核</a-button>
|
||||
</div>
|
||||
|
||||
<!-- 应用数量 -->
|
||||
<template v-if="column.key === 'appCount'">
|
||||
<a-space>
|
||||
<a-tag color="blue">{{ appCountMap[record.userId!] ?? 0 }} 个应用</a-tag>
|
||||
<a-tag color="success" v-if="publishedCountMap[record.userId!]">
|
||||
{{ publishedCountMap[record.userId!] }} 已上架
|
||||
</a-tag>
|
||||
</a-space>
|
||||
</template>
|
||||
<a-table :columns="auditColumns" :data-source="auditData" row-key="id">
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'type'">
|
||||
<a-tag :color="auditTypeColor[record.type]">{{ auditTypeMap[record.type] }}</a-tag>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'level'">
|
||||
<a-tag :color="levelColor[record.level]">{{ levelMap[record.level] }}</a-tag>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'status'">
|
||||
<a-badge :status="statusBadge[record.status]" :text="statusText[record.status]" />
|
||||
</template>
|
||||
<template v-else-if="column.key === 'actions'">
|
||||
<a-space>
|
||||
<a-button v-if="record.status === 'pending'" type="primary" size="small" @click="handleAudit(record)">审核</a-button>
|
||||
<a-button type="link" size="small" @click="handleViewAudit(record)">详情</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</a-card>
|
||||
|
||||
<!-- 注册时间 -->
|
||||
<template v-if="column.key === 'createTime'">
|
||||
<span class="text-sm text-gray">{{ record.createTime?.substring(0, 10) || '-' }}</span>
|
||||
</template>
|
||||
|
||||
<!-- 状态 -->
|
||||
<template v-if="column.key === 'status'">
|
||||
<a-tag :color="record.status === 0 ? 'success' : 'error'">
|
||||
{{ record.status === 0 ? '正常' : '已冻结' }}
|
||||
</a-tag>
|
||||
</template>
|
||||
|
||||
<!-- 操作 -->
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button type="link" size="small" @click="handleViewDev(record)">查看应用</a-button>
|
||||
<a-popconfirm
|
||||
v-if="record.type !== 2"
|
||||
title="确认将该用户设为开发者?"
|
||||
ok-text="确认"
|
||||
cancel-text="取消"
|
||||
@confirm="handleSetDeveloper(record, 2)"
|
||||
>
|
||||
<a-button type="link" size="small">设为开发者</a-button>
|
||||
</a-popconfirm>
|
||||
<a-popconfirm
|
||||
v-else
|
||||
title="确认取消该用户的开发者资质?"
|
||||
ok-text="确认"
|
||||
cancel-text="取消"
|
||||
@confirm="handleSetDeveloper(record, 0)"
|
||||
>
|
||||
<a-button type="link" size="small" danger>取消资质</a-button>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
|
||||
<!-- 开发者应用列表弹窗 -->
|
||||
<!-- 审核弹窗 -->
|
||||
<a-modal
|
||||
v-model:open="showAppsModal"
|
||||
:title="`${currentDev?.nickname || currentDev?.username || '开发者'} 的应用`"
|
||||
width="780px"
|
||||
:footer="null"
|
||||
v-model:open="auditModalVisible"
|
||||
:title="editingApply ? '审核开发者申请' : '权限审核'"
|
||||
width="560px"
|
||||
:confirm-loading="submitting"
|
||||
@ok="handleSubmitAudit"
|
||||
@cancel="auditModalVisible = false"
|
||||
>
|
||||
<div v-if="loadingApps" class="modal-spin">
|
||||
<a-spin />
|
||||
</div>
|
||||
<template v-else>
|
||||
<a-empty v-if="devApps.length === 0" description="该开发者暂无应用" />
|
||||
<div v-else class="dev-apps-grid">
|
||||
<div v-for="app in devApps" :key="app.productId" class="dev-app-card">
|
||||
<div class="dev-app-header">
|
||||
<img v-if="app.icon" :src="app.icon" class="dev-app-icon" />
|
||||
<div v-else class="dev-app-icon-placeholder" :style="{ background: iconBgColor(app.productName) }">
|
||||
{{ (app.productName || 'A').charAt(0).toUpperCase() }}
|
||||
</div>
|
||||
<div class="dev-app-info">
|
||||
<div class="dev-app-name">
|
||||
{{ app.productName }}
|
||||
<a-tag color="blue" style="margin-left:6px;font-size:11px">{{ APP_TYPE_NAME[app.appType ?? 10] || '网站' }}</a-tag>
|
||||
</div>
|
||||
<div class="dev-app-code">{{ app.productCode }}</div>
|
||||
</div>
|
||||
<a-tag :color="pubStatusColor(app.publishStatus)" style="margin-left:auto">
|
||||
{{ pubStatusText(app.publishStatus) }}
|
||||
</a-tag>
|
||||
</div>
|
||||
<div class="dev-app-desc">{{ app.description || '暂无简介' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<a-alert v-if="editingApply" :message="`正在审核:${editingApply.name} (${editingApply.email})`" type="info" show-icon class="mb-4" />
|
||||
|
||||
<a-form :model="auditForm" :label-col="{ span: 6 }" :wrapper-col="{ span: 16 }">
|
||||
<a-form-item label="申请人">
|
||||
<a-input v-model:value="auditForm.name" />
|
||||
</a-form-item>
|
||||
<a-form-item label="权限类型">
|
||||
<a-select v-model:value="auditForm.type">
|
||||
<a-select-option value="api">API 权限</a-select-option>
|
||||
<a-select-option value="plugin">插件权限</a-select-option>
|
||||
<a-select-option value="admin">管理权限</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="权限等级">
|
||||
<a-select v-model:value="auditForm.level">
|
||||
<a-select-option value="basic">基础</a-select-option>
|
||||
<a-select-option value="standard">标准</a-select-option>
|
||||
<a-select-option value="advance">高级</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="审核结果">
|
||||
<a-radio-group v-model:value="auditForm.result">
|
||||
<a-radio value="approved">通过</a-radio>
|
||||
<a-radio value="rejected">拒绝</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-form-item label="审核备注">
|
||||
<a-textarea v-model:value="auditForm.remark" :rows="3" placeholder="选填" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ReloadOutlined, UserOutlined } from '@ant-design/icons-vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { getUserAppStats, pageAppProductAll } from '@/api/app/appProduct'
|
||||
import { pageUsers, updateUser } from '@/api/system/user/index'
|
||||
import type { AppProduct } from '@/api/app/appProduct/model'
|
||||
import { APP_TYPE_NAME } from '@/api/app/appProduct/model'
|
||||
import type { User } from '@/api/system/user/model'
|
||||
|
||||
definePageMeta({ layout: 'admin' })
|
||||
useHead({ title: '开发者管理 - 平台管理' })
|
||||
|
||||
const loading = ref(false)
|
||||
const loadingApps = ref(false)
|
||||
const developers = ref<User[]>([])
|
||||
const activeTab = ref('apply')
|
||||
const searchKeyword = ref('')
|
||||
const filterType = ref<number | null>(2) // 默认只看开发者
|
||||
const filterType = ref<string | undefined>()
|
||||
const auditFilterType = ref<string | undefined>()
|
||||
const auditModalVisible = ref(false)
|
||||
const submitting = ref(false)
|
||||
const editingApply = ref<any>(null)
|
||||
|
||||
// 应用数量映射 userId -> count
|
||||
const appCountMap = ref<Record<number, number>>({})
|
||||
const publishedCountMap = ref<Record<number, number>>({})
|
||||
|
||||
const showAppsModal = ref(false)
|
||||
const currentDev = ref<User | null>(null)
|
||||
const devApps = ref<AppProduct[]>([])
|
||||
|
||||
const pagination = reactive({
|
||||
current: 1,
|
||||
pageSize: 20,
|
||||
total: 0,
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
const auditForm = reactive({
|
||||
name: '',
|
||||
type: 'api',
|
||||
level: 'basic',
|
||||
result: 'approved',
|
||||
remark: '',
|
||||
})
|
||||
|
||||
const stats = reactive([
|
||||
{ icon: '🧑💻', label: '开发者总数', value: 0, color: 'blue' },
|
||||
{ icon: '📦', label: '应用总数', value: 0, color: 'green' },
|
||||
{ icon: '✅', label: '已上架应用', value: 0, color: 'orange' },
|
||||
{ icon: '⏳', label: '待审核', value: 0, color: 'red' },
|
||||
])
|
||||
const typeMap: Record<string, string> = { api: 'API 开发者', plugin: '插件开发者', template: '模板开发者' }
|
||||
const typeColor: Record<string, string> = { api: 'blue', plugin: 'green', template: 'purple' }
|
||||
const statusText: Record<string, string> = { pending: '待审核', approved: '已通过', rejected: '已拒绝' }
|
||||
const statusBadge: Record<string, any> = { pending: 'warning', approved: 'success', rejected: 'error' }
|
||||
const auditTypeMap: Record<string, string> = { api: 'API 权限', plugin: '插件权限', admin: '管理权限' }
|
||||
const auditTypeColor: Record<string, string> = { api: 'blue', plugin: 'green', admin: 'purple' }
|
||||
const levelMap: Record<string, string> = { basic: '基础', standard: '标准', advance: '高级' }
|
||||
const levelColor: Record<string, string> = { basic: 'default', standard: 'blue', advance: 'red' }
|
||||
|
||||
const columns = [
|
||||
{ title: '用户', key: 'devInfo', width: 240 },
|
||||
{ title: '类型', key: 'userType', width: 100 },
|
||||
{ title: '应用数量', key: 'appCount', width: 180 },
|
||||
{ title: '注册时间', key: 'createTime', width: 120 },
|
||||
{ title: '状态', key: 'status', width: 90 },
|
||||
{ title: '操作', key: 'action', width: 160 },
|
||||
const applyColumns = [
|
||||
{ title: '申请人', key: 'applicant', width: 220 },
|
||||
{ title: '企业', dataIndex: 'enterprise', key: 'enterprise', width: 160 },
|
||||
{ title: '申请类型', key: 'type', width: 110 },
|
||||
{ title: '申请理由', dataIndex: 'reason', key: 'reason', ellipsis: true },
|
||||
{ title: '申请时间', dataIndex: 'date', key: 'date', width: 120 },
|
||||
{ title: '状态', key: 'status', width: 100 },
|
||||
{ title: '操作', key: 'actions', width: 140, fixed: 'right' },
|
||||
]
|
||||
|
||||
async function loadData() {
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await pageUsers({
|
||||
page: pagination.current,
|
||||
limit: pagination.pageSize,
|
||||
keywords: searchKeyword.value || undefined,
|
||||
type: filterType.value ?? undefined,
|
||||
})
|
||||
developers.value = res?.list || []
|
||||
pagination.total = res?.count || 0
|
||||
stats[0].value = pagination.total
|
||||
// 加载完用户后,单次请求批量加载应用数量
|
||||
loadAppCounts()
|
||||
} catch {
|
||||
message.error('加载用户列表失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
const applyData = ref([
|
||||
{ id: 1, name: '陈志远', email: 'chenzy@example.com', enterprise: '腾云科技', type: 'api', reason: '需要调用平台 API 实现数据对接功能', date: '2026-04-08', status: 'pending' },
|
||||
{ id: 2, name: '林晓东', email: 'linxd@example.com', enterprise: '华创数据', type: 'plugin', reason: '希望开发企业级插件产品', date: '2026-04-07', status: 'approved' },
|
||||
{ id: 3, name: '周文博', email: 'zhouwb@example.com', enterprise: '云智科技', type: 'api', reason: '进行系统集成开发', date: '2026-04-06', status: 'approved' },
|
||||
{ id: 4, name: '吴浩宇', email: 'wuhao@example.com', enterprise: '数智科技', type: 'template', reason: '发布企业模板到应用市场', date: '2026-04-05', status: 'pending' },
|
||||
{ id: 5, name: '郑海峰', email: 'zhenghf@example.com', enterprise: '腾云科技', type: 'api', reason: '需要高级 API 权限进行批量操作', date: '2026-04-04', status: 'approved' },
|
||||
])
|
||||
|
||||
async function loadAppCounts() {
|
||||
if (!developers.value.length) return
|
||||
try {
|
||||
// 单次 POST 请求,一条 SQL 批量统计所有用户的应用数
|
||||
const userIds = developers.value.map(u => u.userId!).filter(Boolean)
|
||||
const rows = await getUserAppStats(userIds)
|
||||
const countMap: Record<number, number> = {}
|
||||
const pubMap: Record<number, number> = {}
|
||||
let totalApps = 0
|
||||
let totalPublished = 0
|
||||
for (const row of rows) {
|
||||
const uid = Number(row.userId)
|
||||
const total = Number(row.totalCount) || 0
|
||||
const pub = Number(row.publishedCount) || 0
|
||||
countMap[uid] = total
|
||||
if (pub > 0) pubMap[uid] = pub
|
||||
totalApps += total
|
||||
totalPublished += pub
|
||||
}
|
||||
appCountMap.value = countMap
|
||||
publishedCountMap.value = pubMap
|
||||
stats[1].value = totalApps
|
||||
stats[2].value = totalPublished
|
||||
} catch { /* ignore */ }
|
||||
// 异步加载全局统计(待审核数)
|
||||
loadPendingCount()
|
||||
}
|
||||
const auditColumns = [
|
||||
{ title: '申请人', dataIndex: 'name', key: 'name', width: 140 },
|
||||
{ title: '企业', dataIndex: 'enterprise', key: 'enterprise', width: 140 },
|
||||
{ title: '权限类型', key: 'type', width: 110 },
|
||||
{ title: '权限等级', key: 'level', width: 100 },
|
||||
{ title: '申请时间', dataIndex: 'date', key: 'date', width: 120 },
|
||||
{ title: '审核人', dataIndex: 'auditor', key: 'auditor', width: 100 },
|
||||
{ title: '状态', key: 'status', width: 100 },
|
||||
{ title: '操作', key: 'actions', width: 140, fixed: 'right' },
|
||||
]
|
||||
|
||||
async function loadPendingCount() {
|
||||
try {
|
||||
const res = await pageAppProductAll({ current: 1, size: 1, publishStatus: 'pending_review' })
|
||||
stats[3].value = res?.count ?? 0
|
||||
} catch { /* ignore */ }
|
||||
}
|
||||
const auditData = ref([
|
||||
{ id: 1, name: '陈志远', enterprise: '腾云科技', type: 'api', level: 'standard', date: '2026-04-08', auditor: '李明', status: 'pending' },
|
||||
{ id: 2, name: '吴浩宇', enterprise: '数智科技', type: 'plugin', level: 'advance', date: '2026-04-05', auditor: '李明', status: 'pending' },
|
||||
{ id: 3, name: '林晓东', enterprise: '华创数据', type: 'plugin', level: 'standard', date: '2026-04-07', auditor: '李明', status: 'approved' },
|
||||
{ id: 4, name: '周文博', enterprise: '云智科技', type: 'api', level: 'basic', date: '2026-04-06', auditor: '李明', status: 'approved' },
|
||||
])
|
||||
|
||||
function handleSearch() {
|
||||
pagination.current = 1
|
||||
loadData()
|
||||
const resetFilter = () => { searchKeyword.value = ''; filterType.value = undefined }
|
||||
const handleViewApply = (r: any) => message.info('查看申请:' + r.name)
|
||||
const handleViewAudit = (r: any) => message.info('查看审核:' + r.name)
|
||||
const handleApprove = (r: any) => { editingApply.value = r; Object.assign(auditForm, { name: r.name, type: r.type }); auditModalVisible.value = true }
|
||||
const handleAudit = (r: any) => { editingApply.value = r; Object.assign(auditForm, { name: r.name, type: r.type }); auditModalVisible.value = true }
|
||||
const handleSubmitAudit = async () => {
|
||||
submitting.value = true
|
||||
await new Promise((r) => setTimeout(r, 800))
|
||||
message.success('审核提交成功')
|
||||
auditModalVisible.value = false
|
||||
submitting.value = false
|
||||
editingApply.value = null
|
||||
}
|
||||
|
||||
function handleFilterChange() {
|
||||
pagination.current = 1
|
||||
loadData()
|
||||
}
|
||||
|
||||
function handleTableChange(pag: any) {
|
||||
pagination.current = pag.current
|
||||
pagination.pageSize = pag.pageSize
|
||||
loadData()
|
||||
}
|
||||
|
||||
async function handleSetDeveloper(record: User, type: number) {
|
||||
try {
|
||||
await updateUser({ userId: record.userId, type })
|
||||
record.type = type
|
||||
message.success(type === 2 ? '已设为开发者用户' : '已取消开发者资质')
|
||||
// 如果当前只展示开发者,取消后刷新列表
|
||||
if (filterType.value === 2 && type !== 2) loadData()
|
||||
} catch (e: any) {
|
||||
message.error(e?.message || '操作失败')
|
||||
}
|
||||
}
|
||||
|
||||
async function handleViewDev(record: User) {
|
||||
currentDev.value = record
|
||||
showAppsModal.value = true
|
||||
loadingApps.value = true
|
||||
try {
|
||||
const res = await pageAppProductAll({
|
||||
current: 1,
|
||||
size: 100,
|
||||
userId: record.userId,
|
||||
})
|
||||
devApps.value = res?.list || []
|
||||
} catch {
|
||||
message.error('加载应用列表失败')
|
||||
} finally {
|
||||
loadingApps.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function pubStatusColor(status?: string) {
|
||||
const map: Record<string, string> = {
|
||||
developing: 'default', pending_review: 'orange',
|
||||
published: 'success', rejected: 'error', deprecated: 'default',
|
||||
}
|
||||
return map[status || ''] || 'default'
|
||||
}
|
||||
|
||||
function pubStatusText(status?: string) {
|
||||
const map: Record<string, string> = {
|
||||
developing: '开发中', pending_review: '待审核',
|
||||
published: '已上架', rejected: '已拒绝', deprecated: '已下架',
|
||||
}
|
||||
return map[status || ''] || '-'
|
||||
}
|
||||
|
||||
const PALETTE = ['#4e6ef2', '#f4a261', '#e76f51', '#2a9d8f', '#e9c46a', '#457b9d']
|
||||
function iconBgColor(name?: string) {
|
||||
if (!name) return PALETTE[0]
|
||||
let h = 0
|
||||
for (let i = 0; i < name.length; i++) h = (h * 31 + name.charCodeAt(i)) & 0xffffffff
|
||||
return PALETTE[Math.abs(h) % PALETTE.length]
|
||||
}
|
||||
|
||||
onMounted(() => loadData())
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.developers-page { min-height: 100%; }
|
||||
|
||||
.page-header {
|
||||
display: flex; align-items: center;
|
||||
justify-content: space-between; margin-bottom: 20px;
|
||||
}
|
||||
.page-title { font-size: 18px; font-weight: 700; color: #1f2937; margin: 0; }
|
||||
.page-desc { font-size: 13px; color: #9ca3af; margin: 2px 0 0; }
|
||||
|
||||
.stat-card {
|
||||
display: flex; align-items: center;
|
||||
gap: 12px; padding: 16px;
|
||||
border-radius: 12px; border: 2px solid transparent; transition: all 0.2s;
|
||||
}
|
||||
.stat-card:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0,0,0,0.08); }
|
||||
.stat-card.blue { background: #eff6ff; border-color: #dbeafe; }
|
||||
.stat-card.green { background: #f0fdf4; border-color: #bbf7d0; }
|
||||
.stat-card.orange { background: #fff7ed; border-color: #fed7aa; }
|
||||
.stat-card.red { background: #fff1f2; border-color: #fecdd3; }
|
||||
.stat-icon { font-size: 28px; flex-shrink: 0; }
|
||||
.stat-value { font-size: 22px; font-weight: 700; color: rgba(0,0,0,0.85); line-height: 1.2; }
|
||||
.stat-label { font-size: 12px; color: rgba(0,0,0,0.45); margin-top: 2px; }
|
||||
|
||||
.panel { background: #fff; border: 1px solid #f0f0f0; border-radius: 12px; overflow: hidden; }
|
||||
.panel-header {
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
padding: 14px 18px; border-bottom: 1px solid #f5f5f5; flex-wrap: wrap; gap: 10px;
|
||||
}
|
||||
.panel-title { font-size: 14px; font-weight: 600; color: rgba(0,0,0,0.85); }
|
||||
|
||||
.filter-bar {
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
flex-wrap: wrap; gap: 12px; margin-bottom: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 16px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.dev-info-cell { display: flex; align-items: center; gap: 10px; }
|
||||
.dev-info-text { flex: 1; min-width: 0; }
|
||||
.dev-name { font-size: 14px; font-weight: 500; color: rgba(0,0,0,0.85); }
|
||||
.dev-sub { font-size: 12px; color: rgba(0,0,0,0.45); margin-top: 1px; }
|
||||
|
||||
/* 应用弹窗卡片 */
|
||||
.dev-apps-grid { display: flex; flex-direction: column; gap: 12px; max-height: 520px; overflow-y: auto; padding-right: 4px; }
|
||||
.dev-app-card {
|
||||
border: 1px solid #f0f0f0; border-radius: 10px; padding: 14px;
|
||||
transition: all 0.15s;
|
||||
.user-cell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
.dev-app-card:hover { border-color: #d0d0ff; box-shadow: 0 2px 8px rgba(0,0,0,0.06); }
|
||||
.dev-app-header { display: flex; align-items: center; gap: 12px; margin-bottom: 8px; }
|
||||
.dev-app-icon { width: 40px; height: 40px; border-radius: 8px; object-fit: cover; }
|
||||
.dev-app-icon-placeholder {
|
||||
width: 40px; height: 40px; border-radius: 8px;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
font-size: 16px; font-weight: 600; color: #fff; flex-shrink: 0;
|
||||
}
|
||||
.dev-app-info { flex: 1; }
|
||||
.dev-app-name { font-size: 14px; font-weight: 500; color: rgba(0,0,0,0.85); }
|
||||
.dev-app-code { font-size: 12px; color: rgba(0,0,0,0.45); }
|
||||
.dev-app-desc { font-size: 12px; color: rgba(0,0,0,0.45); padding-left: 52px; }
|
||||
|
||||
.modal-spin { display: flex; align-items: center; justify-content: center; min-height: 200px; }
|
||||
.text-sm { font-size: 12px; }
|
||||
.text-gray { color: rgba(0,0,0,0.45); }
|
||||
.mb-6 { margin-bottom: 24px; }
|
||||
.name-text {
|
||||
font-weight: 500;
|
||||
color: #111827;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.sub-text {
|
||||
font-size: 12px;
|
||||
color: #9ca3af;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.mb-4 {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user