开发了多角色登录功能:实现了普通用户、企业用户和管理员可以分别通过不同入口登录系统,并且支持用账号、邮箱或手机号登录。
开发了权限分配功能:实现了一个可以在后台勾选页面的功能,通过给角色勾选菜单,就能直接控制不同身份的人登录后能看到哪些页面。 开发了实名认证功能:实现了企业可以提交营业执照认证,个人可以提交身份证件和技能认证的功能,管理员在后台可以进行审核。 开发了任务大厅功能:实现了企业可以发布需要做的任务,个人用户能在任务大厅里看到这些任务,并且可以点击申请接单,大家都能看到任务是“进行中”还是“已完成”状态。 开发了专家库与邀约功能:实现了企业可以去专家库里搜索合适的人才,并且可以直接给他们发送工作邀约。 开发了平台数据大屏展示功能:实现了在首页和各自的工作台页面,展示任务数量、收益金额等核心数据的概览面板。
This commit is contained in:
93
src/views/user/models/ApiKeyListView.vue
Normal file
93
src/views/user/models/ApiKeyListView.vue
Normal file
@@ -0,0 +1,93 @@
|
||||
<script setup lang="ts">
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { onMounted, ref } from 'vue';
|
||||
import api from '@/api';
|
||||
import { Key, Copy, Check, Trash2 } from 'lucide-vue-next';
|
||||
|
||||
const apiKeys = ref<any[]>([]);
|
||||
|
||||
const fetchKeys = async () => {
|
||||
try {
|
||||
const res: any = await api.get('/tokens/');
|
||||
apiKeys.value = res;
|
||||
} catch (error) { console.error('Fetch keys failed', error); }
|
||||
};
|
||||
|
||||
onMounted(fetchKeys);
|
||||
|
||||
const copiedId = ref<string | null>(null);
|
||||
const copyKey = (id: string, key: string) => {
|
||||
navigator.clipboard.writeText(key);
|
||||
copiedId.value = id;
|
||||
ElMessage.success('已复制到剪贴板');
|
||||
setTimeout(() => { copiedId.value = null; }, 2000);
|
||||
};
|
||||
|
||||
const revokeKey = async (id: string) => {
|
||||
try {
|
||||
await api.delete(`/tokens/${id}/`);
|
||||
await fetchKeys();
|
||||
ElMessage.success('凭证已注销');
|
||||
} catch (error) { ElMessage.error('操作失败'); }
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="space-y-5">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 class="text-xl font-bold text-gray-800">API 管理</h1>
|
||||
<p class="text-sm text-gray-400">您申请的所有模型访问凭证</p>
|
||||
</div>
|
||||
<router-link to="/user/models">
|
||||
<el-button type="primary">申请新模型</el-button>
|
||||
</router-link>
|
||||
</div>
|
||||
|
||||
<el-table :data="apiKeys" stripe v-if="apiKeys.length > 0">
|
||||
<el-table-column label="模型" min-width="200">
|
||||
<template #default="{ row }">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-8 h-8 rounded-lg bg-gray-50 flex items-center justify-center text-gray-400">
|
||||
<Key class="w-4 h-4" />
|
||||
</div>
|
||||
<div>
|
||||
<div class="font-semibold">{{ row.model_name }} Access</div>
|
||||
<div class="text-xs text-gray-400">模型: {{ row.model_name }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" width="100">
|
||||
<template #default><el-tag type="success" size="small">Active</el-tag></template>
|
||||
</el-table-column>
|
||||
<el-table-column label="Token" min-width="280">
|
||||
<template #default="{ row }">
|
||||
<div class="flex items-center gap-2">
|
||||
<code class="text-xs font-mono text-gray-600 bg-gray-50 px-2 py-1 rounded flex-1 overflow-hidden text-ellipsis">{{ row.token_value }}</code>
|
||||
<el-button text size="small" @click="copyKey(row.id, row.token_value)">
|
||||
<Check v-if="copiedId === row.id" class="w-4 h-4 text-green-500" />
|
||||
<Copy v-else class="w-4 h-4" />
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="创建时间" width="130">
|
||||
<template #default="{ row }">{{ new Date(row.created_at).toLocaleDateString() }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="80" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-popconfirm title="确定要注销此凭证吗?" @confirm="revokeKey(row.id)">
|
||||
<template #reference>
|
||||
<el-button text type="danger" size="small"><Trash2 class="w-4 h-4" /></el-button>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-empty v-else description="暂无可用凭证" :image-size="80">
|
||||
<el-button type="primary" @click="$router.push('/user/models')">前往模型市场申请</el-button>
|
||||
</el-empty>
|
||||
</div>
|
||||
</template>
|
||||
142
src/views/user/models/ModelMarketView.vue
Normal file
142
src/views/user/models/ModelMarketView.vue
Normal file
@@ -0,0 +1,142 @@
|
||||
<script setup lang="ts">
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { onMounted, ref, computed } from 'vue';
|
||||
import { useSystemStore } from '@/stores/system';
|
||||
import { Box, Key, Star, Zap, Copy, Check } from 'lucide-vue-next';
|
||||
|
||||
const systemStore = useSystemStore();
|
||||
const models = computed(() => systemStore.models);
|
||||
|
||||
onMounted(async () => { await systemStore.fetchModels(); });
|
||||
|
||||
const showDialog = ref(false);
|
||||
const selectedModel = ref<any>(null);
|
||||
const keyPurpose = ref('');
|
||||
const generatedKey = ref('');
|
||||
const isGenerating = ref(false);
|
||||
const showKeyResult = ref(false);
|
||||
const copied = ref(false);
|
||||
|
||||
const openApplyModal = (model: any) => {
|
||||
selectedModel.value = model;
|
||||
keyPurpose.value = '';
|
||||
generatedKey.value = '';
|
||||
showKeyResult.value = false;
|
||||
showDialog.value = true;
|
||||
};
|
||||
|
||||
const generateApiKey = async () => {
|
||||
if (!keyPurpose.value) return;
|
||||
isGenerating.value = true;
|
||||
try {
|
||||
const res = await systemStore.getToken(selectedModel.value.id);
|
||||
generatedKey.value = res.token_value;
|
||||
showKeyResult.value = true;
|
||||
} catch (error) {
|
||||
ElMessage.error('申请失败');
|
||||
} finally { isGenerating.value = false; }
|
||||
};
|
||||
|
||||
const copyKey = () => {
|
||||
navigator.clipboard.writeText(generatedKey.value);
|
||||
copied.value = true;
|
||||
setTimeout(() => { copied.value = false; }, 2000);
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="space-y-5">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 class="text-xl font-bold text-gray-800">模型市场</h1>
|
||||
<p class="text-sm text-gray-400">加速您的业务数字化、智能化进程</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<router-link to="/user/models/keys">
|
||||
<el-button plain><Key class="w-4 h-4 mr-1" />API 管理</el-button>
|
||||
</router-link>
|
||||
<el-input placeholder="搜索模型..." prefix-icon="Search" class="!w-48" clearable />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Models Grid -->
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="6" v-for="model in models" :key="model.id" class="mb-4">
|
||||
<el-card shadow="hover" class="h-full">
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<div class="w-9 h-9 bg-gray-900 rounded-lg flex items-center justify-center text-white">
|
||||
<Box class="w-4 h-4" />
|
||||
</div>
|
||||
<el-tag size="small" type="primary" effect="plain">{{ model.category }}</el-tag>
|
||||
</div>
|
||||
<h3 class="font-bold text-gray-800 mb-1 line-clamp-1">{{ model.name }}</h3>
|
||||
<p class="text-sm text-gray-500 line-clamp-2 mb-3 h-10">{{ model.description }}</p>
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<div class="flex items-center gap-0.5">
|
||||
<Star v-for="i in 5" :key="i" :class="['w-3 h-3', i > 4 ? 'text-gray-200' : 'text-yellow-400 fill-current']" />
|
||||
<span class="text-xs text-gray-400 ml-1">{{ model.rating }}</span>
|
||||
</div>
|
||||
<el-tag size="small" type="warning" effect="plain">{{ model.tag }}</el-tag>
|
||||
</div>
|
||||
<el-divider class="!my-2" />
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<span class="text-xs text-gray-400">起步价</span>
|
||||
<div class="font-bold text-gray-800">¥{{ model.price_per_token }}<span class="text-xs font-normal text-gray-400">/token</span></div>
|
||||
</div>
|
||||
<el-button type="primary" size="small" @click="openApplyModal(model)">申请模型</el-button>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="6" class="mb-4">
|
||||
<el-card shadow="hover" class="h-full flex items-center justify-center cursor-pointer">
|
||||
<div class="text-center py-6">
|
||||
<div class="w-10 h-10 bg-gray-50 rounded-full flex items-center justify-center mx-auto mb-2">
|
||||
<Zap class="w-5 h-5 text-gray-400" />
|
||||
</div>
|
||||
<div class="font-bold text-sm">定制专属模型</div>
|
||||
<div class="text-xs text-gray-400">Contact Support</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- API Key Dialog -->
|
||||
<el-dialog v-model="showDialog" title="申请访问凭证" width="440px" top="5vh">
|
||||
<div v-if="!showKeyResult" class="space-y-4">
|
||||
<el-alert type="info" :closable="false" show-icon>
|
||||
<template #title>
|
||||
正在申请访问 <strong>{{ selectedModel?.name }}</strong>。凭证可用于 API 调用,请妥善保管。
|
||||
</template>
|
||||
</el-alert>
|
||||
<el-input v-model="keyPurpose" placeholder="凭证用途名称(如:在线聊天生产环境)" />
|
||||
</div>
|
||||
|
||||
<div v-else class="space-y-4 text-center">
|
||||
<el-result icon="success" title="凭证生成成功" sub-title="请立即复制并保存,离开后将无法再次查看" />
|
||||
<div class="bg-gray-900 rounded-lg p-4 relative">
|
||||
<code class="text-sm font-mono text-blue-400 break-all">{{ generatedKey }}</code>
|
||||
<el-button circle size="small" class="!absolute right-3 top-3" @click="copyKey">
|
||||
<Check v-if="copied" class="w-3 h-3 text-green-400" />
|
||||
<Copy v-else class="w-3 h-3" />
|
||||
</el-button>
|
||||
</div>
|
||||
<el-tag v-if="copied" type="success" effect="dark">已复制到剪贴板!</el-tag>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<template v-if="!showKeyResult">
|
||||
<el-button @click="showDialog = false">取消</el-button>
|
||||
<el-button type="primary" :loading="isGenerating" :disabled="!keyPurpose" @click="generateApiKey">
|
||||
{{ isGenerating ? '正在生成...' : '确认生成 API Key' }}
|
||||
</el-button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<router-link to="/user/models/keys"><el-button>查看全部凭证</el-button></router-link>
|
||||
<el-button type="primary" @click="showDialog = false">完成</el-button>
|
||||
</template>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user