Implement playground access control with configuration, authentication, and UI

Co-authored-by: qaiu <29825328+qaiu@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2025-12-07 05:44:04 +00:00
parent 32beb4f2f2
commit 367c7f35ec
7 changed files with 530 additions and 18 deletions

View File

@@ -4,6 +4,36 @@ import axios from 'axios';
* 演练场API服务
*/
export const playgroundApi = {
/**
* 获取Playground状态
* @returns {Promise} 状态信息 {enabled, needPassword, authed}
*/
async getStatus() {
try {
const response = await axios.get('/v2/playground/status');
if (response.data && response.data.data) {
return response.data.data;
}
return response.data;
} catch (error) {
throw new Error(error.response?.data?.msg || error.message || '获取状态失败');
}
},
/**
* Playground登录
* @param {string} password - 密码
* @returns {Promise} 登录结果
*/
async login(password) {
try {
const response = await axios.post('/v2/playground/login', { password });
return response.data;
} catch (error) {
throw new Error(error.response?.data?.msg || error.message || '登录失败');
}
},
/**
* 测试执行JavaScript代码
* @param {string} jsCode - JavaScript代码

View File

@@ -1,6 +1,64 @@
<template>
<div ref="playgroundContainer" class="playground-container" :class="{ 'dark-theme': isDarkMode, 'fullscreen': isFullscreen }">
<el-card class="playground-card">
<!-- 加载状态 -->
<el-card v-if="statusLoading" class="playground-card" v-loading="true" element-loading-text="正在加载...">
<div style="height: 400px;"></div>
</el-card>
<!-- Playground未开启 -->
<el-card v-else-if="!enabled" class="playground-card">
<el-empty description="Playground未开启">
<template #extra>
<p style="color: #909399; font-size: 14px; margin-top: 10px;">
Playground功能目前未启用请联系管理员在配置中开启此功能
</p>
</template>
</el-empty>
</el-card>
<!-- 需要密码但未认证 -->
<el-card v-else-if="needPassword && !authed" class="playground-card">
<div class="password-container">
<h2>🔒 Playground访问认证</h2>
<p style="color: #909399; margin: 20px 0;">
此Playground需要密码访问请输入密码后继续使用
</p>
<el-form @submit.prevent="submitPassword" style="max-width: 400px; margin: 0 auto;">
<el-form-item>
<el-input
v-model="password"
type="password"
placeholder="请输入访问密码"
size="large"
show-password
clearable
@keyup.enter="submitPassword"
>
<template #prefix>
<el-icon><Lock /></el-icon>
</template>
</el-input>
</el-form-item>
<el-form-item v-if="authError" style="margin-bottom: 10px;">
<el-alert type="error" :title="authError" :closable="false" />
</el-form-item>
<el-form-item>
<el-button
type="primary"
size="large"
style="width: 100%;"
:loading="authenticating"
@click="submitPassword"
>
{{ authenticating ? '验证中...' : '验证并进入' }}
</el-button>
</el-form-item>
</el-form>
</div>
</el-card>
<!-- 已启用且已认证或公开模式 -->
<el-card v-else class="playground-card">
<template #header>
<div class="card-header">
<div class="header-left">
@@ -505,6 +563,15 @@ export default {
const loadingList = ref(false);
const publishDialogVisible = ref(false);
const publishing = ref(false);
// Playground状态相关
const statusLoading = ref(true);
const enabled = ref(false);
const needPassword = ref(false);
const authed = ref(false);
const password = ref('');
const authError = ref('');
const authenticating = ref(false);
const publishForm = ref({
jsCode: ''
});
@@ -656,6 +723,63 @@ function parseById(shareLinkInfo, http, logger) {
}
};
// 获取Playground状态
const fetchStatus = async () => {
try {
const result = await playgroundApi.getStatus();
enabled.value = result.enabled;
needPassword.value = result.needPassword;
authed.value = result.authed;
} catch (error) {
console.error('获取Playground状态失败:', error);
ElMessage.error('获取Playground状态失败: ' + error.message);
// 默认为未启用
enabled.value = false;
}
};
// 提交密码
const submitPassword = async () => {
if (!password.value) {
authError.value = '请输入密码';
return;
}
authError.value = '';
authenticating.value = true;
try {
const result = await playgroundApi.login(password.value);
if (result.success || result.code === 200) {
authed.value = true;
ElMessage.success('认证成功');
// 初始化Playground
await nextTick();
await initPlayground();
} else {
authError.value = result.msg || '密码错误';
}
} catch (error) {
console.error('Playground登录失败:', error);
authError.value = error.message || '登录失败';
} finally {
authenticating.value = false;
}
};
// 初始化Playground加载编辑器等
const initPlayground = async () => {
await initMonacoTypes();
// 加载保存的代码
const saved = localStorage.getItem('playground_code');
if (saved) {
jsCode.value = saved;
} else {
jsCode.value = exampleCode;
}
};
// 代码变化处理
const onCodeChange = (value) => {
jsCode.value = value;
@@ -1156,14 +1280,19 @@ curl "${baseUrl}/json/parser?url=${encodeURIComponent(exampleUrl)}"</pre>
onMounted(async () => {
await nextTick();
checkDarkMode();
await initMonacoTypes();
// 加载保存的代码
const saved = localStorage.getItem('playground_code');
if (saved) {
jsCode.value = saved;
} else {
jsCode.value = exampleCode;
// 首先获取Playground状态
await fetchStatus();
statusLoading.value = false;
// 如果未启用,直接返回
if (!enabled.value) {
return;
}
// 如果不需要密码或已认证初始化Playground
if (!needPassword.value || authed.value) {
await initPlayground();
}
// 加载保存的主题
@@ -1244,6 +1373,17 @@ curl "${baseUrl}/json/parser?url=${encodeURIComponent(exampleUrl)}"</pre>
helpCollapseActive,
consoleLogs,
clearConsoleLogs,
// Playground状态相关
statusLoading,
enabled,
needPassword,
authed,
password,
authError,
authenticating,
fetchStatus,
submitPassword,
initPlayground,
// 新增功能
collapsedPanels,
togglePanel,
@@ -1266,6 +1406,27 @@ curl "${baseUrl}/json/parser?url=${encodeURIComponent(exampleUrl)}"</pre>
</script>
<style>
/* Password container styles */
.password-container {
text-align: center;
padding: 60px 20px;
min-height: 400px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.password-container h2 {
font-size: 28px;
margin-bottom: 10px;
color: var(--el-text-color-primary);
}
.dark-theme .password-container {
color: rgba(255, 255, 255, 0.85);
}
/* API示例对话框样式 */
.api-example-dialog {
width: 80%;