diff --git a/pages/contract/index.vue b/pages/contract/index.vue index 8c7ff75..3e119d8 100644 --- a/pages/contract/index.vue +++ b/pages/contract/index.vue @@ -60,6 +60,21 @@ 认证信息变更 + + + + 🤖 AI 客服 + + + 📋 合同审查 + + + 📈 我的报告 + + + 💎 积分管理 + + @@ -470,6 +485,222 @@
+ + + + 🤖 金久信签约宝 AI 智能客服 + + + + 🤖 + + 您好!我是金久信签约宝AI智能客服,可以帮您解答金久信签约宝使用过程中注册、操作、适用场景等问题。请问有什么可以帮您? + + 注册准备资料事项 + 关键字无法识别 + 金久信签约宝法律效力 + 电子签约不适用场景 + + + + + 🤖 + + + + 🤖 + 思考中... + + + + + 发送 + + + + + + 📋 AI 合同审查 + + + 💎 + + 合同审查消耗积分 + 根据文件大小自动计算,每 100KB 约 200 积分。审查后余额需保留至少 200 积分。 + + 余额:{{ pointsBalance }} 分 + + + + ⚠️ + 请确保合同内容清晰可见。如因合同不清晰导致分析失败,平台不承担后果,积分正常消耗。 + + + + 📤 + 上传合同文件 + 仅支持 PDF / Word(.pdf .doc .docx),最大 20MB + 选择文件上传 + + + + +

正在上传文件到审查系统...

+
+ + + + 📄 + + {{ reviewFile.name }} + {{ formatFileSize(reviewFile.size) }} + + ✕ + + + + 🔍 + 标准审查 + 风险条款识别 · 合规性检查 + + + + + + + {{ reviewLoading ? '审查中...' : '开始审查' }} + +

+ ⚠️ 积分不足,当前余额 {{ pointsBalance }},需要 {{ reviewPoints }} 积分且审查后余额需保留至少 200 积分。 + 去充值 +

+
+
+ + +
🤖
+
AI 正在分析合同...
+ +
+ + + +

📋 {{ reviewStreaming ? 'AI 正在分析...' : '审查结果' }} — {{ reviewFile.name }}

+ {{ reviewStreaming ? '⏳ 分析中...' : '✅ 审查完成' }} +
+ +
+
+ + AI 正在输出分析结果... +
+
+ + 📄 查看完整 HTML 报告 + 重新审查 + +
+
+ + + + + 📈 我的报告 + 🔍 查询报告 + + +
📋
+ 暂无审查报告,点击上方「查询报告」从服务器加载 +
+ + + 📄 {{ r.fileName }} + {{ r.time }} · 消耗 {{ Math.abs(r.amount) }} 积分 + + 待审查 + 排队中 + 审查中 + 已完成 + 失败 + 查看报告 + 查看文件 + 查看摘要 + +
+ + + + 💎 积分管理 + + + 可用积分 + {{ pointsBalance }} + + + 累计获得 + {{ pointsTotalEarned }} + + + + 💰 积分充值 + + + {{ pkg.points }} + {{ pkg.price }} + {{ pkg.bonus }} + + + + + 立即充值 {{ rechargePackages[selectedRecharge].price }} + ({{ rechargePackages[selectedRecharge].points }} 积分) + +

💡 积分长期有效,可用于合同审查等功能

+
+ + 📖 积分使用说明 + +

• 合同审查:每 100KB 约 200 积分(根据文件大小自动计算)

+

• AI 客服:免费 — 签约宝专属问答

+

• 新用户注册自动赠送 500 体验积分

+

• 积分长期有效,不过期

+
+ + 📜 积分明细 + 🔍 查看 + + + + + + + + + + + + +
@@ -646,6 +877,66 @@ export default { showInvoice: false, isSignTips: false, signTips: '', + // AI 客服数据 + chatMessages: [], + chatInput: '', + chatLoading: false, + chatConversationId: null, + chatScrollId: '', + // 积分数据 + pointsBalance: 0, + pointsTotalEarned: 0, + pointsLogList: [], + // 合同审查数据 + reviewFile: null, + reviewFileId: null, + reviewRecordId: null, + reviewHtmlUrl: '', + reviewPdfUrl: '', + reviewContractInfo: {}, + reviewStreaming: false, + reviewUploading: false, + reviewMode: 0, + reviewPoints: 100, + reviewLoading: false, + reviewProgress: 0, + reviewResult: null, + reviewConversationId: null, + // 积分充值 + selectedRecharge: 1, + rechargePackages: [{ + points: 10000, + price: '20元', + bonus: '' + }, + { + points: 50000, + price: '100元', + bonus: '' + }, + { + points: 100000, + price: '200元', + bonus: '' + }, + { + points: 450000, + price: '900元', + bonus: '' + }, + { + points: 1000000, + price: '2000元', + bonus: '' + }, + { + points: 2000000, + price: '4000元', + bonus: '' + } + ], + // 我的报告 + reportList: [], invoiceForm: { id: null, name: null, @@ -1161,6 +1452,9 @@ export default { onShow() { this.userInfo = vk.getVuex('$user.userInfo') this.getUserInfo() + this.getPointsBalance() + if (this.menuIndex === '6') this.getPointsLog(); + if (this.menuIndex === '5') this.getReportList(); }, methods: { dayjs, @@ -1169,8 +1463,14 @@ export default { this.formData.status = Number(e) this.isSign = false this.isSignIntro = false - this.isSignFlow = false this.isSignLog = false + this.isSignFlow = false + this.isSignTips = false + // AI 智能服务菜单(3/4/5/6)不调 query,加载对应数据 + if (e >= 3) { + if (e === 4 || e === 6) this.getPointsBalance(); + return; + } this.query(1) }, selectSign() { @@ -1198,6 +1498,342 @@ export default { this.isSignTips = false this.menuIndex = '' }, + // AI 客服方法 + chatQuickSend(text) { + this.chatInput = text; + this.chatSend(); + }, + async chatSend() { + const msg = this.chatInput.trim(); + if (!msg || this.chatLoading) return; + this.chatInput = ''; + this.chatMessages.push({ + role: 'user', + content: msg + }); + this.chatLoading = true; + this.chatScrollToBottom(); + + try { + const res = await vk.callFunction({ + url: 'client/ai/pub/chat', + data: { + message: msg, + bot_id: '7683509749475819554' + } + }); + if (res.data.conversation_id) this.chatConversationId = res.data.conversation_id; + this.chatMessages.push({ + role: 'assistant', + content: res.data.answer + }); + } catch (e) { + this.chatMessages.push({ + role: 'assistant', + content: '网络异常,请稍后重试' + }); + } + this.chatLoading = false; + this.chatScrollToBottom(); + }, + chatScrollToBottom() { + setTimeout(() => { + if (this.chatMessages.length === 0) { + this.chatScrollId = 'chat-welcome'; + } else if (this.chatLoading) { + this.chatScrollId = 'chat-loading'; + } else { + this.chatScrollId = 'chat-msg' + (this.chatMessages.length - 1); + } + }, 100); + }, + // ===== 格式化 AI 回复内容 ===== + formatChatContent(text) { + if (!text) return ''; + let html = text.replace(/\*\*(.*?)\*\*/g, '$1'); + html = html.replace(/^[\-\•]\s+(.+)$/gm, '
• $1
'); + html = html.replace(/^(\d+)\.\s+(.+)$/gm, '
$1. $2
'); + html = html.replace(/\n/g, '
'); + return html; + }, + openUrl(url) { + if (url) window.open(url, '_blank'); + }, + fileToBase64(file) { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onload = () => resolve(reader.result.split(',')[1]); + reader.onerror = reject; + reader.readAsDataURL(file); + }); + }, + formatReviewResult(text) { + if (!text) return ''; + let html = text; + // 处理 markdown 标题 ## / ### + html = html.replace(/^###\s+(.+)$/gm, '

$1

'); + html = html.replace(/^##\s+(.+)$/gm, '

$1

'); + // 处理加粗 + html = html.replace(/\*\*(.*?)\*\*/g, '$1'); + // 处理链接(PDF下载链接等)— 自动识别 http/https 链接 + html = html.replace(/(https?:\/\/[^\s\)\]\"\<]+)/g, '$1'); + // 处理列表 + html = html.replace(/^[\-\•]\s+(.+)$/gm, '
• $1
'); + html = html.replace(/^(\d+)\.\s+(.+)$/gm, '
$1. $2
'); + // 处理表格行(简单的 | 分隔表格) + html = html.replace(/\|(.+)\|/g, function(match) { + const cells = match.split('|').filter(c => c.trim()); + if (cells.length > 1) { + return '
' + + cells.map(c => '' + c.trim() + '').join('') + '
'; + } + return match; + }); + // 换行 + html = html.replace(/\n/g, '
'); + return html; + }, + // ===== 积分相关 ===== + // 积分不足时跳转到「积分管理」并刷新余额与明细 + goRecharge() { + this.menuIndex = '6'; + this.isSign = false; + this.isSignIntro = false; + this.isSignLog = false; + this.isSignFlow = false; + this.isSignTips = false; + this.getPointsBalance(); + this.getPointsLog(); + }, + async getPointsBalance() { + try { + const res = await vk.callFunction({ + url: 'client/points/kh/getBalance' + }); + if (res.code === 0) { + this.pointsBalance = res.data.balance || 0; + this.pointsTotalEarned = res.data.total_earned || 0; + } + } catch (e) { + /* 静默 */ } + }, + async getPointsLog() { + try { + const res = await vk.callFunction({ + url: 'client/points/kh/getLog', + data: { + pageIndex: 1, + pageSize: 50 + } + }); + if (res.code === 0) { + this.pointsLogList = (res.data.rows || []).map(r => ({ + ...r, + _add_time_str: dayjs(r._add_time && r._add_time['$date'] ? r._add_time['$date'] : r._add_time).format('YYYY-MM-DD HH:mm:ss') + })); + } + } catch (e) { + /* 静默 */ } + }, + async getReportList() { + try { + const reports = JSON.parse(localStorage.getItem('jjx_review_reports') || '[]'); + this.reportList = reports.map(r => ({ + fileName: r.fileName, + fileSize: r.fileSize, + time: r.time, + amount: r.points, + result: r.result, + status: 'completed' + })); + } catch (e) { /* 静默 */ } + }, + async queryReports() { + try { + const res = await vk.callFunction({ + url: 'client/ai/pub/getMyReports', + data: { pageIndex: 1, pageSize: 50 } + }); + if (res.code === 0 && res.data && res.data.rows) { + this.reportList = res.data.rows.map(r => ({ + fileName: r.file_name, + fileSize: r.file_size, + fileUrl: r.file_url || '', + htmlUrl: r.html_url || '', + time: dayjs(r._add_time && r._add_time['$date'] ? r._add_time['$date'] : r._add_time).format('YYYY-MM-DD HH:mm:ss'), + amount: r.points_used || 0, + result: r.result, + status: r.status, + recordId: r._id + })); + this.$message.success('加载了 ' + this.reportList.length + ' 条报告'); + } + } catch (e) { + this.$message.error('查询失败'); + } + }, + viewReport(r) { + // 跳转到合同审查页并显示该报告的结果 + this.menuIndex = '4'; + this.reviewResult = r.result || '暂无审查结果'; + this.reviewFile = { name: r.fileName, size: r.fileSize || 0 }; + }, + previewFile(r) { + if (!r.fileUrl) { + this.$message.warning('文件链接不存在'); + return; + } + // 新窗口打开文件(PDF直接预览,Word下载) + window.open(r.fileUrl, '_blank'); + }, + formatFileSize(bytes) { + if (bytes < 1024) return bytes + ' B'; + if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB'; + return (bytes / (1024 * 1024)).toFixed(1) + ' MB'; + }, + // ===== 合同审查相关 ===== + selectReviewFile() { + const input = document.createElement('input'); + input.type = 'file'; + input.accept = '.pdf,.doc,.docx'; + input.onchange = async (e) => { + const file = e.target.files[0]; + if (!file) return; + if (file.size > 20 * 1024 * 1024) { + this.$message.error('文件大小超过 20MB'); + return; + } + this.reviewFile = file; + this.reviewFileId = null; + this.reviewPoints = Math.max(200, Math.ceil(file.size / (100 * 1024)) * 200); + + // 立即上传文件到扣子 + this.reviewUploading = true; + try { + const fileBase64 = await new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onload = () => resolve(reader.result.split(',')[1]); + reader.onerror = reject; + reader.readAsDataURL(file); + }); + + const res = await vk.callFunction({ + url: 'client/ai/pub/review', + data: { + file_base64: fileBase64, + file_name: file.name, + file_size: file.size, + upload_only: true + } + }); + + if (res.code === 0 && res.data && res.data.file_id) { + this.reviewFileId = res.data.file_id; + this.reviewRecordId = res.data.record_id; // 保存待审查记录ID + this.$message.success('文件上传成功'); + } else { + this.$message.error(res.msg || '文件上传失败'); + this.reviewFile = null; + } + } catch (err) { + this.$message.error('文件上传异常'); + this.reviewFile = null; + } + this.reviewUploading = false; + }; + input.click(); + }, + // 前端 startReview() 完整改造 + async startReview() { + if (!this.reviewFileId) { + this.$message.warning('请先选择合同文件并等待上传完成'); + return; + } + + // 1. 检查积分 + const balanceRes = await vk.callFunction({ + url: 'client/points/kh/getBalance' + }); + if (balanceRes.code !== 0 || !balanceRes.data || balanceRes.data.balance < 200) { + this.$message.warning('积分不足,请先充值'); + return; + } + + this.reviewLoading = true; + + // 2. 发起审查(更新已有记录状态为 reviewing,用 selectReviewFile 时保存的 file_id) + const reviewRes = await vk.callFunction({ + url: 'client/ai/pub/review', + data: { + record_id: this.reviewRecordId, + file_id: this.reviewFileId, + file_name: this.reviewFile.name, + file_size: this.reviewFile.size, + uid: this.userInfo.uid + } + }); + + this.reviewLoading = false; + + if (reviewRes.code !== 0) { + this.$message.error(reviewRes.msg || '发起审查失败'); + return; + } + + // 3. 提示用户 + 跳转到我的报告 + this.$alert( + '合同已提交审查,AI 正在分析中,预计 3-5 分钟完成。您可以在「我的报告」中查看审查结果。', + '审查已提交', + { + confirmButtonText: '查看我的报告', + callback: () => { + this.menuSelect('5'); + } + } + ); + + // 重置上传区 + this.resetReview(); + }, + resetReview() { + this.reviewFile = null; + this.reviewFileId = null; + this.reviewRecordId = null; + this.reviewUploading = false; + this.reviewResult = null; + this.reviewProgress = 0; + this.reviewConversationId = null; + }, + async doRecharge() { + const pkg = this.rechargePackages[this.selectedRecharge]; + const priceMap = { + '20元': 2000, '100元': 10000, '200元': 20000, + '900元': 90000, '2000元': 200000, '4000元': 400000 + } + const price = priceMap[pkg.price] || 2000; + const pointsMatch = pkg.points; + + try { + // 创建充值订单 + const res = await vk.callFunction({ + url: 'client/points/kh/createOrder', + data: { + points: pointsMatch, + price: price + } + }); + if (res.code === 0 && res.data) { + // 跳转到支付页面(复用现有银联支付) + vk.navigateTo({ + url: '/pages/index/shop/pay?id=' + res.data + }); + } else { + this.$message.error(res.msg || '创建订单失败'); + } + } catch (e) { + this.$message.error('充值异常,请稍后重试'); + } + }, changeAuthInfo() { vk.navigateTo({url: '/pages/user/user?menu=fadada'}) }, @@ -2014,4 +2650,472 @@ export default { line-height: 20px; } } + +/* ===== 新增AI功能菜单组 ===== */ +.menu-group-title { + padding: 8px 20px 4px; + font-size: 11px; + color: #999; + letter-spacing: 2px; + text-transform: uppercase; +} + +/* AI 客服嵌入样式 */ +.ai-chat-embed { + background: #fff; + border-radius: 8px; + display: flex; + flex-direction: column; + height: calc(100vh - 260px); + overflow: hidden; +} + +.chat-header-bar { + padding: 14px 20px; + border-bottom: 1px solid #eee; + display: flex; + align-items: center; + justify-content: space-between; +} + +.chat-title { + font-size: 15px; + font-weight: 600; +} + +.chat-subtitle { + font-size: 12px; + color: #999; +} + +.chat-msg-list { + flex: 1; + padding: 16px 20px; + background: #f7f8fa; +} + +.chat-row { + display: flex; + gap: 10px; + margin-bottom: 12px; +} + +.chat-row.right { + flex-direction: row-reverse; +} + +.chat-avatar { + width: 32px; + height: 32px; + border-radius: 50%; + background: linear-gradient(135deg, #a92924, #d4574e); + display: flex; + align-items: center; + justify-content: center; + color: #fff; + font-size: 14px; + flex-shrink: 0; +} + +.chat-bubble { + max-width: 65%; + padding: 10px 14px; + border-radius: 12px; + font-size: 13px; + line-height: 1.6; + word-break: break-word; +} + +.chat-row:not(.right) .chat-bubble { + background: #fff; + border: 1px solid #eee; + border-top-left-radius: 4px; +} + +.chat-row.right .chat-bubble { + background: #a92924; + color: #fff; + border-top-right-radius: 4px; +} + +.chat-bubble.typing { + color: #999; +} + +.quick-btns { + display: flex; + flex-wrap: wrap; + gap: 6px; + margin-top: 10px; +} + +.quick-btn { + padding: 5px 12px; + border-radius: 14px; + font-size: 12px; + cursor: pointer; + border: 1px solid #a92924; + color: #a92924; + background: #fff; + transition: all 0.2s; +} + +.quick-btn:hover { + background: #a92924; + color: #fff; +} + +.chat-input-bar { + padding: 12px 20px; + border-top: 1px solid #eee; + display: flex; + gap: 10px; + background: #fff; +} + +.chat-input-bar input { + flex: 1; + height: 38px; + border: 1px solid #ddd; + border-radius: 8px; + padding: 0 12px; + font-size: 13px; + outline: none; +} + +.chat-input-bar input:focus { + border-color: #a92924; +} + +.chat-send-btn { + padding: 0 20px; + height: 38px; + line-height: 38px; + background: #a92924; + color: #fff; + border-radius: 8px; + font-size: 13px; + cursor: pointer; +} + +.chat-send-btn:hover { + background: #982520; +} + +/* 合同审查样式 */ +.review-container { + background: #fff; + border-radius: 8px; + padding: 24px; +} + +.review-title { + font-size: 16px; + font-weight: 600; + margin-bottom: 20px; + display: flex; + align-items: center; + gap: 8px; +} + +@keyframes pulse { + 0%, 100% { opacity: 1; transform: scale(1); } + 50% { opacity: 0.5; transform: scale(1.2); } +} +.disclaimer-notice { + display: flex; align-items: flex-start; gap: 8px; + background: #fff8e1; border: 1px solid #ffe082; border-radius: 8px; + padding: 12px 16px; margin-bottom: 20px; font-size: 12px; color: #e6a23c; +} +.dn-icon { font-size: 16px; flex-shrink: 0; margin-top: 1px; } +.dn-text { line-height: 1.6; } +.points-notice { + display: flex; + align-items: center; + gap: 12px; + background: #fff8e1; + border: 1px solid #ffe082; + border-radius: 8px; + padding: 14px 20px; + margin-bottom: 20px; +} + +.pn-icon { + font-size: 24px; +} + +.pn-text { + flex: 1; +} + +.pn-text .pn-title { + font-size: 13px; + font-weight: 600; + color: #333; +} + +.pn-text .pn-desc { + font-size: 12px; + color: #666; + margin-top: 2px; +} + +.pn-balance { + font-size: 18px; + font-weight: 700; + color: #a92924; +} + +.upload-area { + border: 2px dashed #d9d9d9; + border-radius: 12px; + padding: 48px; + text-align: center; + cursor: pointer; + transition: all 0.3s; +} + +.upload-area:hover { + border-color: #a92924; + background: #fafafa; +} + +.upload-area .upload-icon { + font-size: 48px; + margin-bottom: 16px; +} + +.upload-area .upload-title { + font-size: 16px; + font-weight: 600; + margin-bottom: 8px; +} + +.upload-area .upload-desc { + font-size: 13px; + color: #999; + margin-bottom: 16px; +} + +.upload-area .upload-btn { + display: inline-block; + padding: 10px 32px; + background: #a92924; + color: #fff; + border-radius: 8px; + font-size: 14px; +} + +.file-info { + display: flex; + align-items: center; + gap: 12px; + background: #f5f5f5; + border-radius: 8px; + padding: 12px 16px; + margin-bottom: 16px; +} + +.file-info .fi-icon { + font-size: 24px; +} + +.file-info .fi-name { + font-size: 13px; + font-weight: 600; +} + +.file-info .fi-size { + font-size: 11px; + color: #999; +} + +.file-info .fi-remove { + margin-left: auto; + color: #f56c6c; + cursor: pointer; + font-size: 18px; +} + +.review-opts { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 16px; + margin-bottom: 16px; +} + +.review-opt { + border: 1px solid #eee; + border-radius: 8px; + padding: 20px; + text-align: center; + cursor: pointer; + transition: all 0.2s; +} + +.review-opt:hover { + border-color: #a92924; +} + +.review-opt.selected { + border-color: #a92924; + background: #fef8f7; +} + +.review-opt .opt-icon { + font-size: 28px; + margin-bottom: 8px; +} + +.review-opt .opt-title { + font-size: 14px; + font-weight: 600; + margin-bottom: 4px; +} + +.review-opt .opt-desc { + font-size: 11px; + color: #999; +} + +.review-opt .opt-points { + font-size: 12px; + color: #a92924; + font-weight: 600; + margin-top: 8px; +} + +.result-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 16px; +} + +.result-header h3 { + font-size: 14px; +} + +.result-status { + padding: 4px 12px; + border-radius: 12px; + font-size: 12px; + background: #e8f5e9; + color: #67c23a; +} + +.result-body { + background: #f9f9f9; + border-radius: 8px; + padding: 20px; + font-size: 13px; + line-height: 1.8; + margin-bottom: 16px; +} + +.result-footer { + text-align: center; +} + +/* 积分管理样式 */ +.points-summary { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 16px; + margin-bottom: 20px; +} + +.ps-card { + background: linear-gradient(135deg, #a92924, #d4574e); + border-radius: 12px; + padding: 24px; + color: #fff; + text-align: center; +} + +.ps-card.secondary { + background: linear-gradient(135deg, #333, #555); +} + +.ps-card .ps-label { + font-size: 12px; + opacity: 0.8; + margin-bottom: 8px; +} + +.ps-card .ps-value { + font-size: 32px; + font-weight: 700; +} + +/* 充值卡片样式 */ +.recharge-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 12px; + margin-bottom: 20px; +} + +.recharge-card { + border: 2px solid #eee; + border-radius: 8px; + padding: 16px; + text-align: center; + cursor: pointer; + transition: all 0.2s; +} + +.recharge-card:hover { + border-color: #a92924; +} + +.recharge-card.selected { + border-color: #a92924; + background: #fef8f7; +} + +.recharge-card .rc-points { + font-size: 24px; + font-weight: 700; + color: #a92924; +} + +.recharge-card .rc-price { + font-size: 13px; + color: #666; + margin-top: 4px; +} + +.recharge-card .rc-bonus { + font-size: 11px; + color: #fff; + background: #e6a23c; + padding: 2px 8px; + border-radius: 10px; + margin-top: 8px; + display: inline-block; +} + +/* 我的报告样式 */ +.report-item { + display: flex; + align-items: center; + padding: 14px 0; + border-bottom: 1px solid #f5f5f5; +} + +.report-item .report-name { + font-size: 13px; + font-weight: 600; +} + +.report-item .report-time { + font-size: 11px; + color: #999; + margin-top: 2px; +} + +.report-item .report-info { + flex: 1; +}