Yolov/templates/task_create.html

987 lines
43 KiB
HTML

<!DOCTYPE html>
<html>
<head>
<title>创建AI检测任务</title>
<meta charset="UTF-8">
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
<style>
#app{
padding-bottom: 300px;
}
body {
font-family: Arial, sans-serif;
max-width: 1200px;
margin: 0 auto;
padding: 20px;
background-color: #f5f5f5;
}
.container {
display: flex;
gap: 20px;
}
.left-panel {
flex: 3;
}
.right-panel {
flex: 2;
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.card {
background: white;
padding: 20px;
margin-bottom: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.form-group {
margin-bottom: 15px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: bold;
color: #333;
}
input[type="text"], input[type="password"], select, textarea {
width: 100%;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
box-sizing: border-box;
}
textarea {
min-height: 100px;
resize: vertical;
}
.btn {
padding: 10px 20px;
background-color: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
}
.btn:hover {
background-color: #0056b3;
}
.btn:disabled {
background-color: #ccc;
cursor: not-allowed;
}
.btn-success {
background-color: #28a745;
}
.btn-success:hover {
background-color: #218838;
}
.btn-danger {
background-color: #dc3545;
}
.btn-danger:hover {
background-color: #c82333;
}
.btn-sm {
padding: 5px 10px;
font-size: 14px;
}
.error {
color: #dc3545;
font-size: 14px;
margin-top: 5px;
}
.success {
color: #28a745;
font-size: 14px;
margin-top: 5px;
}
.warning {
color: #ffc107;
font-size: 14px;
margin-top: 5px;
}
.model-item {
border: 1px solid #ddd;
padding: 15px;
margin-bottom: 10px;
border-radius: 4px;
background: #f8f9fa;
}
.model-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
}
.model-status {
display: inline-block;
padding: 2px 8px;
border-radius: 12px;
font-size: 12px;
}
.status-valid {
background-color: #d4edda;
color: #155724;
}
.status-invalid {
background-color: #f8d7da;
color: #721c24;
}
.status-pending {
background-color: #fff3cd;
color: #856404;
}
.step {
display: flex;
align-items: center;
margin-bottom: 20px;
}
.step-number {
width: 30px;
height: 30px;
background-color: #6c757d;
color: white;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-right: 10px;
}
.step-number.active {
background-color: #007bff;
}
.step-number.completed {
background-color: #28a745;
}
.step-content {
flex: 1;
}
.progress-bar {
height: 4px;
background-color: #e9ecef;
border-radius: 2px;
margin: 20px 0;
overflow: hidden;
}
.progress-fill {
height: 100%;
background-color: #007bff;
width: 0%;
transition: width 0.3s ease;
}
.task-preview {
background: #f8f9fa;
padding: 15px;
border-radius: 4px;
margin-top: 20px;
}
.task-info-item {
margin-bottom: 8px;
display: flex;
justify-content: space-between;
}
.task-info-label {
font-weight: bold;
color: #666;
}
.model-selector {
max-height: 300px;
overflow-y: auto;
border: 1px solid #ddd;
border-radius: 4px;
padding: 10px;
}
.model-option {
padding: 10px;
border-bottom: 1px solid #eee;
cursor: pointer;
transition: background-color 0.2s;
}
.model-option:hover {
background-color: #f0f8ff;
}
.model-option.selected {
background-color: #e7f3ff;
border-left: 3px solid #007bff;
}
.model-details {
font-size: 12px;
color: #666;
margin-top: 5px;
}
.validation-result {
padding: 10px;
margin-top: 10px;
border-radius: 4px;
}
.validation-success {
background-color: #d4edda;
border: 1px solid #c3e6cb;
}
.validation-error {
background-color: #f8d7da;
border: 1px solid #f5c6cb;
}
table {
width: 100%;
border-collapse: collapse;
margin-top: 10px;
}
th, td {
padding: 8px;
text-align: left;
border-bottom: 1px solid #ddd;
}
th {
background-color: #f8f9fa;
font-weight: bold;
}
</style>
</head>
<body>
<div id="app">
<h1>创建AI检测任务</h1>
<!-- 步骤指示器 -->
<div class="progress-bar">
<div class="progress-fill" :style="{width: (currentStep / 4 * 100) + '%'}"></div>
</div>
<div class="step">
<div class="step-number" :class="{'active': currentStep === 1, 'completed': currentStep > 1}">1</div>
<div class="step-content">
<h3>选择模型</h3>
<p v-if="currentStep === 1">选择要使用的加密模型文件</p>
</div>
</div>
<div class="step">
<div class="step-number" :class="{'active': currentStep === 2, 'completed': currentStep > 2}">2</div>
<div class="step-content">
<h3>配置密钥</h3>
<p v-if="currentStep === 2">为每个模型配置加密密钥</p>
</div>
</div>
<div class="step">
<div class="step-number" :class="{'active': currentStep === 3, 'completed': currentStep > 3}">3</div>
<div class="step-content">
<h3>任务设置</h3>
<p v-if="currentStep === 3">配置RTMP流和任务参数</p>
</div>
</div>
<div class="step">
<div class="step-number" :class="{'active': currentStep === 4, 'completed': currentStep > 4}">4</div>
<div class="step-content">
<h3>验证和创建</h3>
<p v-if="currentStep === 4">验证所有配置并创建任务</p>
</div>
</div>
<div class="container">
<!-- 左侧主内容 -->
<div class="left-panel">
<!-- 步骤1: 选择模型 -->
<div class="card" v-if="currentStep === 1">
<h3>选择加密模型</h3>
<div class="form-group">
<label>可用加密模型:</label>
<div class="model-selector">
<div v-if="loadingModels">加载模型中...</div>
<div v-else-if="availableModels.length === 0">没有找到加密模型文件</div>
<div v-for="model in availableModels"
:key="model.filename"
class="model-option"
:class="{'selected': selectedModels.some(m => m.filename === model.filename)}"
@click="toggleModelSelection(model)">
<div><strong>{{ model.filename }}</strong></div>
<div class="model-details">
大小: {{ formatFileSize(model.size) }} |
哈希: {{ model.model_hash || '未知' }} |
修改: {{ formatTimestamp(model.modified) }}
</div>
</div>
</div>
</div>
<div v-if="selectedModels.length > 0">
<h4>已选模型 ({{ selectedModels.length }})</h4>
<div v-for="(model, index) in selectedModels" :key="index" class="model-item">
<div class="model-header">
<div><strong>{{ model.filename }}</strong></div>
<button class="btn btn-sm btn-danger" @click="removeModel(index)">移除</button>
</div>
<div class="model-details">
路径: {{ model.path }}<br>
模型哈希: {{ model.model_hash || '未知' }}
</div>
</div>
</div>
<div class="form-group">
<button class="btn" @click="loadAvailableModels" :disabled="loadingModels">
{{ loadingModels ? '加载中...' : '刷新列表' }}
</button>
<button class="btn btn-success" @click="nextStep" :disabled="selectedModels.length === 0">
下一步: 配置密钥
</button>
</div>
</div>
<!-- 步骤2: 配置密钥 -->
<div class="card" v-if="currentStep === 2">
<h3>配置模型加密密钥</h3>
<p>为每个选中的模型配置加密密钥。密钥将在创建任务前进行验证。</p>
<div v-for="(model, index) in selectedModels" :key="index" class="model-item">
<div class="model-header">
<div>
<strong>{{ model.filename }}</strong>
<span class="model-status" :class="{
'status-valid': model.validation.valid,
'status-invalid': model.validation.checked && !model.validation.valid,
'status-pending': !model.validation.checked
}">
{{ model.validation.checked ? (model.validation.valid ? '有效' : '无效') : '待验证' }}
</span>
</div>
</div>
<div class="form-group">
<label>加密密钥:</label>
<input type="password"
v-model="model.encryption_key"
placeholder="输入加密密钥"
@input="validateSingleModel(index)">
<div v-if="model.validation.checked" class="validation-result"
:class="{'validation-success': model.validation.valid, 'validation-error': !model.validation.valid}">
<div v-if="model.validation.valid">
✓ 密钥验证成功<br>
<small>模型哈希: {{ model.validation.model_hash }}</small>
</div>
<div v-else>
✗ 密钥验证失败: {{ model.validation.error }}
</div>
</div>
</div>
<div class="form-group">
<button class="btn btn-sm" @click="testModelDecryption(index)"
:disabled="!model.encryption_key">
测试解密
</button>
<button class="btn btn-sm" @click="generateKeyForModel(index)">
生成新密钥
</button>
</div>
</div>
<div class="form-group">
<button class="btn" @click="prevStep">上一步</button>
<button class="btn btn-success" @click="validateAllModels"
:disabled="!allModelsHaveKeys || validatingAll">
{{ validatingAll ? '验证中...' : '验证所有模型' }}
</button>
<button class="btn" @click="nextStep" :disabled="!allModelsValid">
下一步: 任务设置
</button>
</div>
</div>
<!-- 步骤3: 任务设置 -->
<div class="card" v-if="currentStep === 3">
<h3>任务配置</h3>
<div class="form-group">
<label>任务名称:</label>
<input type="text" v-model="taskConfig.taskname" placeholder="输入任务名称">
</div>
<div class="form-group">
<label>RTMP视频流地址:</label>
<input type="text" v-model="taskConfig.rtmp_url"
placeholder="rtmp://server:port/stream/key">
</div>
<div class="form-group">
<label>推流地址 (可选):</label>
<input type="text" v-model="taskConfig.push_url"
placeholder="rtmp://server:port/stream/key">
</div>
<div class="form-group">
<label>算法ID (可选):</label>
<input type="text" v-model="taskConfig.AlgoId" placeholder="输入算法ID">
</div>
<div class="form-group">
<label>模型配置:</label>
<div class="task-preview">
<div class="task-info-item">
<span class="task-info-label">模型数量:</span>
<span>{{ selectedModels.length }}</span>
</div>
<div class="task-info-item">
<span class="task-info-label">已验证模型:</span>
<span>{{ validModelsCount }} / {{ selectedModels.length }}</span>
</div>
<div class="task-info-item">
<span class="task-info-label">加密要求:</span>
<span>强制加密 ✓</span>
</div>
</div>
</div>
<div class="form-group">
<button class="btn" @click="prevStep">上一步</button>
<button class="btn btn-success" @click="nextStep">
下一步: 验证和创建
</button>
</div>
</div>
<!-- 步骤4: 验证和创建 -->
<div class="card" v-if="currentStep === 4">
<h3>验证并创建任务</h3>
<div v-if="creatingTask" class="validation-result validation-success">
<h4>正在创建任务...</h4>
<p>{{ createTaskMessage }}</p>
</div>
<div v-else>
<h4>配置概览</h4>
<div class="task-preview">
<div class="task-info-item">
<span class="task-info-label">任务名称:</span>
<span>{{ taskConfig.taskname }}</span>
</div>
<div class="task-info-item">
<span class="task-info-label">视频流:</span>
<span>{{ taskConfig.rtmp_url }}</span>
</div>
<div class="task-info-item">
<span class="task-info-label">推流地址:</span>
<span>{{ taskConfig.push_url || '未设置' }}</span>
</div>
<div class="task-info-item">
<span class="task-info-label">模型数量:</span>
<span>{{ selectedModels.length }} 个</span>
</div>
<div class="task-info-item">
<span class="task-info-label">密钥验证:</span>
<span>{{ validModelsCount === selectedModels.length ? '全部通过 ✓' : '未通过 ✗' }}</span>
</div>
</div>
<h4>最终验证</h4>
<div class="form-group">
<button class="btn" @click="performFinalValidation"
:disabled="validatingFinal || creatingTask">
{{ validatingFinal ? '验证中...' : '执行最终验证' }}
</button>
</div>
<div v-if="finalValidationResult" class="validation-result"
:class="{'validation-success': finalValidationResult.success, 'validation-error': !finalValidationResult.success}">
<h4 v-if="finalValidationResult.success">✓ 验证通过</h4>
<h4 v-else>✗ 验证失败</h4>
<div v-if="finalValidationResult.success">
<p>所有模型验证成功,可以创建任务</p>
<p>有效模型: {{ finalValidationResult.valid_models }} / {{ finalValidationResult.total_models }}</p>
</div>
<div v-else>
<p>验证失败: {{ finalValidationResult.error }}</p>
<div v-if="finalValidationResult.validation_results">
<p>详细错误:</p>
<ul>
<li v-for="(result, idx) in finalValidationResult.validation_results"
v-if="!result.key_valid">
模型 {{ result.model_index }}: {{ result.error }}
</li>
</ul>
</div>
</div>
</div>
<div class="form-group">
<button class="btn" @click="prevStep">上一步</button>
<button class="btn btn-success" @click="createTask"
:disabled="!finalValidationResult || !finalValidationResult.success || creatingTask">
{{ creatingTask ? '创建中...' : '创建任务' }}
</button>
</div>
</div>
</div>
</div>
<!-- 右侧信息面板 -->
<div class="right-panel">
<h3>任务状态</h3>
<div v-if="currentStep === 1">
<p><strong>步骤1/4:</strong> 选择模型</p>
<p>请从已上传的加密模型中选择要使用的模型。可以多选。</p>
<p><em>提示:</em> 模型文件必须已经通过上传接口加密上传。</p>
</div>
<div v-if="currentStep === 2">
<p><strong>步骤2/4:</strong> 配置密钥</p>
<p>为每个模型输入加密密钥。密钥强度要求:</p>
<ul>
<li>长度至少16位</li>
<li>包含大小写字母</li>
<li>包含数字</li>
<li>包含特殊字符</li>
</ul>
<p>点击"测试解密"可以验证密钥是否正确。</p>
</div>
<div v-if="currentStep === 3">
<p><strong>步骤3/4:</strong> 任务设置</p>
<p>配置任务的基本参数:</p>
<ul>
<li><strong>RTMP流:</strong> 输入视频流地址</li>
<li><strong>推流地址:</strong> 处理后的视频推流地址(可选)</li>
<li><strong>任务名称:</strong> 用于标识任务的名称</li>
</ul>
</div>
<div v-if="currentStep === 4">
<p><strong>步骤4/4:</strong> 验证和创建</p>
<p>系统将执行最终验证:</p>
<ul>
<li>验证所有模型密钥</li>
<li>检查系统资源</li>
<li>确认任务参数</li>
</ul>
<p>验证通过后,点击"创建任务"启动检测。</p>
</div>
<div class="task-preview" v-if="createdTaskId">
<h4>任务创建成功</h4>
<p><strong>任务ID:</strong> {{ createdTaskId }}</p>
<p><strong>状态:</strong> {{ taskStatus }}</p>
<p><strong>创建时间:</strong> {{ new Date().toLocaleString() }}</p>
<div class="form-group">
<button class="btn btn-sm" @click="goToTaskManagement">查看任务管理</button>
<button class="btn btn-sm" @click="copyTaskId">复制任务ID</button>
</div>
</div>
<div v-if="errorMessage" class="error">
<h4>错误信息</h4>
<p>{{ errorMessage }}</p>
</div>
</div>
</div>
</div>
<script>
// 等待DOM加载完成后初始化Vue
document.addEventListener('DOMContentLoaded', function() {
new Vue({
el: '#app',
data: function() {
return {
currentStep: 1,
availableModels: [],
selectedModels: [],
loadingModels: false,
validatingAll: false,
validatingFinal: false,
creatingTask: false,
taskConfig: {
taskname: '检测任务_' + new Date().toLocaleDateString('zh-CN').replace(/\//g, '-'),
rtmp_url: 'rtmp://localhost:1935/live/stream',
push_url: '',
AlgoId: ''
},
finalValidationResult: null,
createTaskMessage: '',
createdTaskId: null,
taskStatus: '',
errorMessage: '',
base_url:'http://192.168.10.131:9309'
};
},
computed: {
allModelsHaveKeys: function() {
return this.selectedModels.every(function(model) {
return model.encryption_key && model.encryption_key.trim().length > 0;
});
},
allModelsValid: function() {
return this.selectedModels.every(function(model) {
return model.validation.valid;
});
},
validModelsCount: function() {
return this.selectedModels.filter(function(model) {
return model.validation.valid;
}).length;
}
},
methods: {
// 步骤导航
nextStep: function() {
if (this.currentStep < 4) {
this.currentStep++;
}
},
prevStep: function() {
if (this.currentStep > 1) {
this.currentStep--;
}
},
// 加载可用模型
loadAvailableModels: function() {
var self = this;
self.loadingModels = true;
self.errorMessage = '';
axios.get(this.base_url + '/api/models/encrypted/list_available')
.then(function(response) {
if (response.data.status === 'success') {
self.availableModels = response.data.data.models.map(function(model) {
return {
filename: model.filename,
path: model.path,
size: model.size,
modified: model.modified,
model_hash: model.model_hash || '',
encryption_key: '',
validation: {
checked: false,
valid: false,
error: '',
model_hash: ''
}
};
});
} else {
self.errorMessage = response.data.message;
}
})
.catch(function(error) {
self.errorMessage = error.response && error.response.data ? error.response.data.message : error.message;
})
.finally(function() {
self.loadingModels = false;
});
},
// 模型选择
toggleModelSelection: function(model) {
var index = this.selectedModels.findIndex(function(m) {
return m.filename === model.filename;
});
if (index === -1) {
// 深拷贝模型对象
var newModel = JSON.parse(JSON.stringify(model));
this.selectedModels.push(newModel);
} else {
this.selectedModels.splice(index, 1);
}
},
removeModel: function(index) {
this.selectedModels.splice(index, 1);
},
// 单个模型验证
validateSingleModel: function(index) {
var self = this;
var model = self.selectedModels[index];
if (!model.encryption_key || model.encryption_key.trim().length < 16) {
model.validation = {
checked: true,
valid: false,
error: '密钥长度不足或为空',
model_hash: ''
};
return;
}
// 验证密钥强度(前端简单验证)
if (model.encryption_key.length < 16) {
model.validation = {
checked: true,
valid: false,
error: '密钥长度至少16位',
model_hash: ''
};
return;
}
// 调用API验证
axios.post(this.base_url + '/api/models/process/verify_key', {
model_path: model.path,
encryption_key: model.encryption_key
})
.then(function(response) {
if (response.data.status === 'success') {
var verification = response.data.data.model_verification;
if (verification && verification.success) {
model.validation = {
checked: true,
valid: true,
error: '',
model_hash: verification.model_hash || ''
};
} else {
model.validation = {
checked: true,
valid: false,
error: verification ? verification.error : '验证失败',
model_hash: ''
};
}
} else {
model.validation = {
checked: true,
valid: false,
error: response.data.message,
model_hash: ''
};
}
})
.catch(function(error) {
model.validation = {
checked: true,
valid: false,
error: error.response && error.response.data ? error.response.data.message : error.message,
model_hash: ''
};
});
},
// 测试解密
testModelDecryption: function(index) {
var self = this;
var model = self.selectedModels[index];
self.errorMessage = '';
if (!model.encryption_key) {
self.errorMessage = '请输入加密密钥';
return;
}
axios.post(this.base_url + '/api/models/process/test_decrypt', {
model_path: model.path,
encryption_key: model.encryption_key
})
.then(function(response) {
if (response.data.status === 'success') {
alert('解密测试成功!\n模型哈希: ' + response.data.data.model_hash +
'\n解密时间: ' + response.data.data.decryption_time.toFixed(3) + '秒');
} else {
self.errorMessage = response.data.message;
}
})
.catch(function(error) {
self.errorMessage = error.response && error.response.data ? error.response.data.message : error.message;
});
},
// 生成新密钥
generateKeyForModel: function(index) {
var self = this;
axios.post(this.base_url + '/api/models/generate_key')
.then(function(response) {
if (response.data.status === 'success') {
self.selectedModels[index].encryption_key = response.data.data.key;
self.validateSingleModel(index);
}
})
.catch(function(error) {
self.errorMessage = error.response && error.response.data ? error.response.data.message : error.message;
});
},
// 验证所有模型
validateAllModels: function() {
var self = this;
self.validatingAll = true;
self.errorMessage = '';
// 依次验证每个模型
var validateNext = function(index) {
if (index >= self.selectedModels.length) {
self.validatingAll = false;
if (self.allModelsValid) {
self.errorMessage = '所有模型验证通过 (' + self.validModelsCount + '/' + self.selectedModels.length + ')';
} else {
self.errorMessage = '部分模型验证失败,请检查密钥';
}
return;
}
self.validateSingleModel(index);
setTimeout(function() {
validateNext(index + 1);
}, 500);
};
validateNext(0);
},
// 最终验证
performFinalValidation: function() {
var self = this;
self.validatingFinal = true;
self.errorMessage = '';
self.finalValidationResult = null;
// 准备验证数据
var validationData = {
models: self.selectedModels.map(function(model) {
return {
path: model.path,
encryption_key: model.encryption_key
};
})
};
axios.post(this.base_url + '/api/models/process/validate_task', validationData)
.then(function(response) {
if (response.data.status === 'success') {
self.finalValidationResult = response.data.data;
self.errorMessage = '验证通过: ' + self.finalValidationResult.valid_models + '/' + self.finalValidationResult.total_models + ' 个模型有效';
} else {
self.finalValidationResult = response.data.data;
self.errorMessage = response.data.message;
}
})
.catch(function(error) {
self.errorMessage = error.response && error.response.data ? error.response.data.message : error.message;
})
.finally(function() {
self.validatingFinal = false;
});
},
// 创建任务
createTask: function() {
var self = this;
self.creatingTask = true;
self.createTaskMessage = '正在创建任务...';
self.errorMessage = '';
// 准备任务数据
var taskData = {
taskname: self.taskConfig.taskname,
rtmp_url: self.taskConfig.rtmp_url,
push_url: self.taskConfig.push_url || undefined,
AlgoId: self.taskConfig.AlgoId || undefined,
models: self.selectedModels.map(function(model) {
return {
path: model.path,
encryption_key: model.encryption_key,
tags: model.tags || {},
conf_thres: model.conf_thres || 0.25,
iou_thres: model.iou_thres || 0.45,
imgsz: model.imgsz || 640,
device: model.device || 'cuda:0',
half: model.half !== undefined ? model.half : true,
enabled: true
};
})
};
// 先执行最终验证
self.performFinalValidation();
// 等待验证完成
var checkValidation = function() {
if (self.finalValidationResult && self.finalValidationResult.success) {
// 验证通过,提交任务创建请求
self.createTaskMessage = '正在提交任务创建请求...';
axios.post(self.base_url + '/api/tasks/create', taskData)
.then(function(response) {
if (response.data.status === 'success') {
self.createdTaskId = response.data.data.task_id;
self.taskStatus = '已创建';
self.createTaskMessage = '任务创建成功!';
} else {
throw new Error(response.data.message);
}
})
.catch(function(error) {
self.errorMessage = error.response && error.response.data ? error.response.data.message : error.message;
self.createTaskMessage = '任务创建失败';
})
.finally(function() {
self.creatingTask = false;
});
} else if (self.finalValidationResult && !self.finalValidationResult.success) {
// 验证失败
self.errorMessage = '模型验证失败,无法创建任务: ' + (self.finalValidationResult.error || '未知错误');
self.createTaskMessage = '任务创建失败';
self.creatingTask = false;
} else {
// 等待验证结果
setTimeout(checkValidation, 500);
}
};
checkValidation();
},
// 辅助函数
formatFileSize: function(bytes) {
if (bytes === 0) return '0 Bytes';
var k = 1024;
var sizes = ['Bytes', 'KB', 'MB', 'GB'];
var i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
},
formatTimestamp: function(timestamp) {
var date = new Date(timestamp * 1000);
return date.toLocaleDateString();
},
copyTaskId: function() {
if (this.createdTaskId) {
navigator.clipboard.writeText(this.createdTaskId)
.then(function() {
alert('任务ID已复制到剪贴板');
})
.catch(function() {
// 备用方案
var textArea = document.createElement("textarea");
textArea.value = this.createdTaskId;
document.body.appendChild(textArea);
textArea.select();
document.execCommand("copy");
document.body.removeChild(textArea);
alert('任务ID已复制到剪贴板');
});
}
},
goToTaskManagement: function() {
window.location.href = '/';
}
},
mounted: function() {
this.loadAvailableModels();
}
});
});
</script>
</body>
</html>