feat: 重大功能更新 v1.4.0 - 飞书集成、AI语义相似度、前端优化
主要更新内容: - 🚀 飞书多维表格集成,支持工单数据同步 - 🤖 AI建议与人工描述语义相似度计算 - 🎨 前端UI全面优化,现代化设计 - 📊 智能知识库入库策略(AI准确率<90%使用人工描述) - 🔧 代码重构,模块化架构优化 - 📚 完整文档整合和更新 - 🐛 修复配置导入和数据库字段问题 技术特性: - 使用sentence-transformers进行语义相似度计算 - 快速模式结合TF-IDF和语义方法 - 响应式设计,支持移动端 - 加载状态和动画效果 - 配置化AI准确率阈值
This commit is contained in:
@@ -18,19 +18,51 @@ class TSPDashboard {
|
||||
}
|
||||
|
||||
async generateAISuggestion(workorderId) {
|
||||
const button = document.querySelector(`button[onclick="dashboard.generateAISuggestion(${workorderId})"]`);
|
||||
const textarea = document.getElementById(`aiSuggestion_${workorderId}`);
|
||||
|
||||
try {
|
||||
// 添加加载状态
|
||||
if (button) {
|
||||
button.classList.add('btn-loading');
|
||||
button.disabled = true;
|
||||
}
|
||||
if (textarea) {
|
||||
textarea.classList.add('ai-loading');
|
||||
textarea.value = '正在生成AI建议,请稍候...';
|
||||
}
|
||||
|
||||
const resp = await fetch(`/api/workorders/${workorderId}/ai-suggestion`, { method: 'POST' });
|
||||
const data = await resp.json();
|
||||
|
||||
if (data.success) {
|
||||
const ta = document.getElementById(`aiSuggestion_${workorderId}`);
|
||||
if (ta) ta.value = data.ai_suggestion || '';
|
||||
if (textarea) {
|
||||
textarea.value = data.ai_suggestion || '';
|
||||
textarea.classList.remove('ai-loading');
|
||||
textarea.classList.add('success-animation');
|
||||
|
||||
// 移除成功动画类
|
||||
setTimeout(() => {
|
||||
textarea.classList.remove('success-animation');
|
||||
}, 600);
|
||||
}
|
||||
this.showNotification('AI建议已生成', 'success');
|
||||
} else {
|
||||
throw new Error(data.error || '生成失败');
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('生成AI建议失败:', e);
|
||||
if (textarea) {
|
||||
textarea.value = 'AI建议生成失败,请重试';
|
||||
textarea.classList.remove('ai-loading');
|
||||
}
|
||||
this.showNotification('生成AI建议失败: ' + e.message, 'error');
|
||||
} finally {
|
||||
// 移除加载状态
|
||||
if (button) {
|
||||
button.classList.remove('btn-loading');
|
||||
button.disabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,10 +81,60 @@ class TSPDashboard {
|
||||
const apprEl = document.getElementById(`aiApproved_${workorderId}`);
|
||||
const approveBtn = document.getElementById(`approveBtn_${workorderId}`);
|
||||
const percent = Math.round((data.similarity || 0) * 100);
|
||||
if (simEl) { simEl.textContent = `相似度: ${percent}%`; simEl.className = `badge ${percent>=95?'bg-success':percent>=70?'bg-warning':'bg-secondary'}`; }
|
||||
if (apprEl) { apprEl.textContent = data.approved ? '已自动审批' : '未审批'; apprEl.className = `badge ${data.approved?'bg-success':'bg-secondary'}`; }
|
||||
if (approveBtn) approveBtn.disabled = !data.approved;
|
||||
this.showNotification('人工描述已保存并评估完成', 'success');
|
||||
|
||||
// 更新相似度显示,使用语义相似度
|
||||
if (simEl) {
|
||||
simEl.innerHTML = `<i class="fas fa-percentage"></i>语义相似度: ${percent}%`;
|
||||
|
||||
// 使用新的CSS类
|
||||
if (percent >= 90) {
|
||||
simEl.className = 'similarity-badge high';
|
||||
} else if (percent >= 80) {
|
||||
simEl.className = 'similarity-badge medium';
|
||||
} else {
|
||||
simEl.className = 'similarity-badge low';
|
||||
}
|
||||
|
||||
simEl.title = this.getSimilarityExplanation(percent);
|
||||
}
|
||||
|
||||
// 更新审批状态
|
||||
if (apprEl) {
|
||||
if (data.use_human_resolution) {
|
||||
apprEl.textContent = '将使用人工描述入库';
|
||||
apprEl.className = 'status-badge human-resolution';
|
||||
} else if (data.approved) {
|
||||
apprEl.textContent = '已自动审批';
|
||||
apprEl.className = 'status-badge approved';
|
||||
} else {
|
||||
apprEl.textContent = '未审批';
|
||||
apprEl.className = 'status-badge pending';
|
||||
}
|
||||
}
|
||||
|
||||
// 更新审批按钮状态
|
||||
if (approveBtn) {
|
||||
const canApprove = data.approved || data.use_human_resolution;
|
||||
approveBtn.disabled = !canApprove;
|
||||
|
||||
if (data.use_human_resolution) {
|
||||
approveBtn.textContent = '使用人工描述入库';
|
||||
approveBtn.className = 'approve-btn';
|
||||
approveBtn.title = 'AI准确率低于90%,将使用人工描述入库';
|
||||
} else if (data.approved) {
|
||||
approveBtn.textContent = '已自动审批';
|
||||
approveBtn.className = 'approve-btn approved';
|
||||
approveBtn.title = 'AI建议与人工描述高度一致';
|
||||
} else {
|
||||
approveBtn.textContent = '审批入库';
|
||||
approveBtn.className = 'approve-btn';
|
||||
approveBtn.title = '手动审批入库';
|
||||
}
|
||||
}
|
||||
|
||||
// 显示更详细的反馈信息
|
||||
const message = this.getSimilarityMessage(percent, data.approved, data.use_human_resolution);
|
||||
this.showNotification(message, data.approved ? 'success' : data.use_human_resolution ? 'warning' : 'info');
|
||||
} else {
|
||||
throw new Error(data.error || '保存失败');
|
||||
}
|
||||
@@ -67,7 +149,9 @@ class TSPDashboard {
|
||||
const resp = await fetch(`/api/workorders/${workorderId}/approve-to-knowledge`, { method: 'POST' });
|
||||
const data = await resp.json();
|
||||
if (data.success) {
|
||||
this.showNotification('已入库为知识条目', 'success');
|
||||
const contentType = data.used_content === 'human_resolution' ? '人工描述' : 'AI建议';
|
||||
const confidence = Math.round((data.confidence_score || 0) * 100);
|
||||
this.showNotification(`已入库为知识条目!使用${contentType},置信度: ${confidence}%`, 'success');
|
||||
} else {
|
||||
throw new Error(data.error || '入库失败');
|
||||
}
|
||||
@@ -1713,29 +1797,43 @@ class TSPDashboard {
|
||||
<small class="text-muted">${workorder.satisfaction_score}/5.0</small>
|
||||
</div>
|
||||
` : ''}
|
||||
<h6 class="mt-3">AI建议与人工描述</h6>
|
||||
<div class="border p-3 rounded">
|
||||
<div class="mb-2">
|
||||
<button class="btn btn-sm btn-outline-primary" onclick="dashboard.generateAISuggestion(${workorder.id})">
|
||||
<div class="ai-suggestion-section">
|
||||
<div class="ai-suggestion-header">
|
||||
<h6 class="ai-suggestion-title">
|
||||
<i class="fas fa-robot"></i>AI建议与人工描述
|
||||
</h6>
|
||||
<button class="generate-ai-btn" onclick="dashboard.generateAISuggestion(${workorder.id})">
|
||||
<i class="fas fa-magic me-1"></i>生成AI建议
|
||||
</button>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label class="form-label">AI建议</label>
|
||||
<textarea id="aiSuggestion_${workorder.id}" class="form-control" rows="4" placeholder="点击上方按钮生成..." readonly></textarea>
|
||||
|
||||
<div class="ai-suggestion-content">
|
||||
<label class="form-label fw-bold text-primary mb-2">
|
||||
<i class="fas fa-brain me-1"></i>AI建议
|
||||
</label>
|
||||
<textarea id="aiSuggestion_${workorder.id}" class="form-control" rows="4" placeholder="点击上方按钮生成AI建议..." readonly></textarea>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label class="form-label">人工描述</label>
|
||||
|
||||
<div class="human-resolution-content">
|
||||
<label class="form-label fw-bold text-warning mb-2">
|
||||
<i class="fas fa-user-edit me-1"></i>人工描述
|
||||
</label>
|
||||
<textarea id="humanResolution_${workorder.id}" class="form-control" rows="3" placeholder="请填写人工处理描述..."></textarea>
|
||||
</div>
|
||||
<div class="d-flex align-items-center gap-2 mb-2">
|
||||
<button class="btn btn-sm btn-outline-success" onclick="dashboard.saveHumanResolution(${workorder.id})">
|
||||
|
||||
<div class="similarity-indicator">
|
||||
<button class="save-human-btn" onclick="dashboard.saveHumanResolution(${workorder.id})">
|
||||
<i class="fas fa-save me-1"></i>保存人工描述并评估
|
||||
</button>
|
||||
<span id="aiSim_${workorder.id}" class="badge bg-secondary">相似度: --</span>
|
||||
<span id="aiApproved_${workorder.id}" class="badge bg-secondary">未审批</span>
|
||||
<button id="approveBtn_${workorder.id}" class="btn btn-sm btn-outline-primary" onclick="dashboard.approveToKnowledge(${workorder.id})" disabled>
|
||||
<i class="fas fa-check me-1"></i>入库
|
||||
<span id="aiSim_${workorder.id}" class="similarity-badge bg-secondary">
|
||||
<i class="fas fa-percentage"></i>相似度: --
|
||||
</span>
|
||||
<span id="aiApproved_${workorder.id}" class="status-badge pending">未审批</span>
|
||||
</div>
|
||||
|
||||
<div class="action-buttons">
|
||||
<button id="approveBtn_${workorder.id}" class="approve-btn" onclick="dashboard.approveToKnowledge(${workorder.id})" disabled>
|
||||
<i class="fas fa-check me-1"></i>审批入库
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -4043,6 +4141,34 @@ class TSPDashboard {
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
getSimilarityExplanation(percent) {
|
||||
if (percent >= 95) {
|
||||
return "语义高度相似,AI建议与人工描述基本一致,建议自动审批";
|
||||
} else if (percent >= 90) {
|
||||
return "语义较为相似,AI建议与人工描述大体一致,建议人工审核";
|
||||
} else if (percent >= 80) {
|
||||
return "语义部分相似,AI建议与人工描述有一定差异,需要人工判断";
|
||||
} else if (percent >= 60) {
|
||||
return "语义相似度较低,AI建议与人工描述差异较大,建议使用人工描述";
|
||||
} else {
|
||||
return "语义差异很大,AI建议与人工描述差异很大,优先使用人工描述";
|
||||
}
|
||||
}
|
||||
|
||||
getSimilarityMessage(percent, approved, useHumanResolution = false) {
|
||||
if (useHumanResolution) {
|
||||
return `人工描述已保存!语义相似度: ${percent}%,AI准确率低于90%,将使用人工描述入库`;
|
||||
} else if (approved) {
|
||||
return `人工描述已保存!语义相似度: ${percent}%,已自动审批入库`;
|
||||
} else if (percent >= 90) {
|
||||
return `人工描述已保存!语义相似度: ${percent}%,建议人工审核后审批`;
|
||||
} else if (percent >= 80) {
|
||||
return `人工描述已保存!语义相似度: ${percent}%,需要人工判断是否审批`;
|
||||
} else {
|
||||
return `人工描述已保存!语义相似度: ${percent}%,建议使用人工描述入库`;
|
||||
}
|
||||
}
|
||||
|
||||
showCreateWorkOrderModal() {
|
||||
const modal = new bootstrap.Modal(document.getElementById('createWorkOrderModal'));
|
||||
modal.show();
|
||||
@@ -4166,8 +4292,390 @@ class TSPDashboard {
|
||||
}
|
||||
}
|
||||
|
||||
// 飞书同步管理器
|
||||
class FeishuSyncManager {
|
||||
constructor() {
|
||||
this.loadConfig();
|
||||
this.refreshStatus();
|
||||
}
|
||||
|
||||
async loadConfig() {
|
||||
try {
|
||||
const response = await fetch('/api/feishu-sync/config');
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
const config = data.config;
|
||||
document.getElementById('appId').value = config.feishu.app_id || '';
|
||||
document.getElementById('appSecret').value = '';
|
||||
document.getElementById('appToken').value = config.feishu.app_token || '';
|
||||
document.getElementById('tableId').value = config.feishu.table_id || '';
|
||||
|
||||
// 显示配置状态
|
||||
const statusBadge = config.feishu.status === 'active' ?
|
||||
'<span class="badge bg-success">已配置</span>' :
|
||||
'<span class="badge bg-warning">未配置</span>';
|
||||
|
||||
// 可以在这里添加状态显示
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载配置失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async saveConfig() {
|
||||
const config = {
|
||||
app_id: document.getElementById('appId').value,
|
||||
app_secret: document.getElementById('appSecret').value,
|
||||
app_token: document.getElementById('appToken').value,
|
||||
table_id: document.getElementById('tableId').value
|
||||
};
|
||||
|
||||
if (!config.app_id || !config.app_secret || !config.app_token || !config.table_id) {
|
||||
this.showNotification('请填写完整的配置信息', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/feishu-sync/config', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(config)
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
this.showNotification('配置保存成功', 'success');
|
||||
} else {
|
||||
this.showNotification('配置保存失败: ' + data.error, 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
this.showNotification('配置保存失败: ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
async testConnection() {
|
||||
try {
|
||||
this.showNotification('正在测试连接...', 'info');
|
||||
|
||||
const response = await fetch('/api/feishu-sync/test-connection');
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
this.showNotification('飞书连接正常', 'success');
|
||||
} else {
|
||||
this.showNotification('连接失败: ' + data.error, 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
this.showNotification('连接测试失败: ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
async syncFromFeishu() {
|
||||
try {
|
||||
const limit = document.getElementById('syncLimit').value;
|
||||
this.showNotification('开始从飞书同步数据...', 'info');
|
||||
this.showProgress(true);
|
||||
|
||||
const response = await fetch('/api/feishu-sync/sync-from-feishu', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
generate_ai_suggestions: false,
|
||||
limit: parseInt(limit)
|
||||
})
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
this.showNotification(data.message, 'success');
|
||||
this.addSyncLog(data.message);
|
||||
this.refreshStatus();
|
||||
} else {
|
||||
this.showNotification('同步失败: ' + data.error, 'error');
|
||||
this.addSyncLog('同步失败: ' + data.error);
|
||||
}
|
||||
} catch (error) {
|
||||
this.showNotification('同步失败: ' + error.message, 'error');
|
||||
this.addSyncLog('同步失败: ' + error.message);
|
||||
} finally {
|
||||
this.showProgress(false);
|
||||
}
|
||||
}
|
||||
|
||||
async syncWithAI() {
|
||||
try {
|
||||
const limit = document.getElementById('syncLimit').value;
|
||||
this.showNotification('开始同步数据并生成AI建议...', 'info');
|
||||
this.showProgress(true);
|
||||
|
||||
const response = await fetch('/api/feishu-sync/sync-from-feishu', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
generate_ai_suggestions: true,
|
||||
limit: parseInt(limit)
|
||||
})
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
this.showNotification(data.message, 'success');
|
||||
this.addSyncLog(data.message);
|
||||
this.refreshStatus();
|
||||
} else {
|
||||
this.showNotification('同步失败: ' + data.error, 'error');
|
||||
this.addSyncLog('同步失败: ' + data.error);
|
||||
}
|
||||
} catch (error) {
|
||||
this.showNotification('同步失败: ' + error.message, 'error');
|
||||
this.addSyncLog('同步失败: ' + error.message);
|
||||
} finally {
|
||||
this.showProgress(false);
|
||||
}
|
||||
}
|
||||
|
||||
async previewFeishuData() {
|
||||
try {
|
||||
this.showNotification('正在获取飞书数据预览...', 'info');
|
||||
|
||||
const response = await fetch('/api/feishu-sync/preview-feishu-data');
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
this.displayPreviewData(data.preview_data);
|
||||
this.showNotification(`获取到 ${data.total_count} 条预览数据`, 'success');
|
||||
} else {
|
||||
this.showNotification('获取预览数据失败: ' + data.error, 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
this.showNotification('获取预览数据失败: ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
displayPreviewData(data) {
|
||||
const tbody = document.querySelector('#previewTable tbody');
|
||||
tbody.innerHTML = '';
|
||||
|
||||
data.forEach(item => {
|
||||
const row = document.createElement('tr');
|
||||
row.innerHTML = `
|
||||
<td>${item.record_id}</td>
|
||||
<td>${item.fields['TR Number'] || '-'}</td>
|
||||
<td>${item.fields['TR Description'] || '-'}</td>
|
||||
<td>${item.fields['Type of problem'] || '-'}</td>
|
||||
<td>${item.fields['Source'] || '-'}</td>
|
||||
<td>${item.fields['TR (Priority/Status)'] || '-'}</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-primary" onclick="feishuSync.createWorkorder('${item.record_id}')">
|
||||
<i class="fas fa-plus"></i> 创建工单
|
||||
</button>
|
||||
</td>
|
||||
`;
|
||||
tbody.appendChild(row);
|
||||
});
|
||||
|
||||
document.getElementById('previewSection').style.display = 'block';
|
||||
}
|
||||
|
||||
async refreshStatus() {
|
||||
try {
|
||||
const response = await fetch('/api/feishu-sync/status');
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
const status = data.status;
|
||||
document.getElementById('totalLocalWorkorders').textContent = status.total_local_workorders || 0;
|
||||
document.getElementById('syncedWorkorders').textContent = status.synced_workorders || 0;
|
||||
document.getElementById('unsyncedWorkorders').textContent = status.unsynced_workorders || 0;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('刷新状态失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
showProgress(show) {
|
||||
const progress = document.getElementById('syncProgress');
|
||||
if (show) {
|
||||
progress.style.display = 'block';
|
||||
const bar = progress.querySelector('.progress-bar');
|
||||
bar.style.width = '100%';
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
progress.style.display = 'none';
|
||||
const bar = progress.querySelector('.progress-bar');
|
||||
bar.style.width = '0%';
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
addSyncLog(message) {
|
||||
const log = document.getElementById('syncLog');
|
||||
const timestamp = new Date().toLocaleString();
|
||||
const logEntry = document.createElement('div');
|
||||
logEntry.innerHTML = `<small class="text-muted">[${timestamp}]</small> ${message}`;
|
||||
|
||||
if (log.querySelector('.text-muted')) {
|
||||
log.innerHTML = '';
|
||||
}
|
||||
|
||||
log.appendChild(logEntry);
|
||||
log.scrollTop = log.scrollHeight;
|
||||
}
|
||||
|
||||
async exportConfig() {
|
||||
try {
|
||||
const response = await fetch('/api/feishu-sync/config/export');
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
// 创建下载链接
|
||||
const blob = new Blob([data.config], { type: 'application/json' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `feishu_config_${new Date().toISOString().split('T')[0]}.json`;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
|
||||
this.showNotification('配置导出成功', 'success');
|
||||
} else {
|
||||
this.showNotification('配置导出失败: ' + data.error, 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
this.showNotification('配置导出失败: ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
showImportModal() {
|
||||
const modal = new bootstrap.Modal(document.getElementById('importConfigModal'));
|
||||
modal.show();
|
||||
}
|
||||
|
||||
async importConfig() {
|
||||
try {
|
||||
const configJson = document.getElementById('configJson').value.trim();
|
||||
|
||||
if (!configJson) {
|
||||
this.showNotification('请输入配置JSON数据', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await fetch('/api/feishu-sync/config/import', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ config: configJson })
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
this.showNotification('配置导入成功', 'success');
|
||||
this.loadConfig();
|
||||
this.refreshStatus();
|
||||
|
||||
// 关闭模态框
|
||||
const modal = bootstrap.Modal.getInstance(document.getElementById('importConfigModal'));
|
||||
modal.hide();
|
||||
document.getElementById('configJson').value = '';
|
||||
} else {
|
||||
this.showNotification('配置导入失败: ' + data.error, 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
this.showNotification('配置导入失败: ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
async resetConfig() {
|
||||
if (confirm('确定要重置所有配置吗?此操作不可撤销!')) {
|
||||
try {
|
||||
const response = await fetch('/api/feishu-sync/config/reset', {
|
||||
method: 'POST'
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
this.showNotification('配置重置成功', 'success');
|
||||
this.loadConfig();
|
||||
this.refreshStatus();
|
||||
} else {
|
||||
this.showNotification('配置重置失败: ' + data.error, 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
this.showNotification('配置重置失败: ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async createWorkorder(recordId) {
|
||||
if (confirm(`确定要从飞书记录 ${recordId} 创建工单吗?`)) {
|
||||
try {
|
||||
this.showNotification('正在创建工单...', 'info');
|
||||
|
||||
const response = await fetch('/api/feishu-sync/create-workorder', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
record_id: recordId
|
||||
})
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
this.showNotification(data.message, 'success');
|
||||
// 刷新工单列表(如果用户在工单页面)
|
||||
if (typeof window.refreshWorkOrders === 'function') {
|
||||
window.refreshWorkOrders();
|
||||
}
|
||||
} else {
|
||||
this.showNotification('创建工单失败: ' + data.message, 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
this.showNotification('创建工单失败: ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
showNotification(message, type = 'info') {
|
||||
const container = document.getElementById('notificationContainer');
|
||||
const alert = document.createElement('div');
|
||||
alert.className = `alert alert-${type === 'error' ? 'danger' : type} alert-dismissible fade show`;
|
||||
alert.innerHTML = `
|
||||
${message}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||
`;
|
||||
|
||||
container.appendChild(alert);
|
||||
|
||||
setTimeout(() => {
|
||||
if (alert.parentNode) {
|
||||
alert.parentNode.removeChild(alert);
|
||||
}
|
||||
}, 5000);
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化应用
|
||||
let dashboard;
|
||||
let feishuSync;
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
dashboard = new TSPDashboard();
|
||||
feishuSync = new FeishuSyncManager();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user