shared.js 6.4 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061
  1. /* 评审数据只存在页面内存中;角色切换用于检查同一业务的交互,不提供实际授权。 */
  2. const initialState = () => ({
  3. loggedIn: false, ticket: 'NONE', version: 1, reason: '键盘空格键无法使用', quantity: 1,
  4. owner: 'AGENT', humanRequested: false, connected: true, blocked: false, empty: false, replyActive: false,
  5. stale: false, expired: false, selfApproval: false, customerConfirmed: false,
  6. memoryConsent: false, memorySaved: false, memoryStyle: '简洁回复', memoryLanguage: '中文',
  7. linked: false, identityVerified: false, ruleVersion: 1, ruleActive: true, ruleSaved: false,
  8. proposalRuleVersion: 1, ruleDays: 7, proposalRuleDays: 7,
  9. knowledge: 'DRAFT', knowledgeTitle: '键盘售后说明', knowledgeVersion: 1,
  10. staffEnabled: true, storeEnabled: true, channelEnabled: true, tenantEnabled: true,
  11. delivery: 'FAILED', budgetRequest: 'NONE', budget: 100, pendingBudget: 100,
  12. messages: [], audit: [], lastAction: '', release: '草稿', formValues: {},
  13. invites: [], tenantRegistrations: [], billingReviewer: false, returnPage: 'home', orderFilter: '全部', aiContext: '',
  14. });
  15. let state = initialState();
  16. let currentRole = 'customer';
  17. let merchantRole = 'merchant';
  18. const adminAuth = { merchant: false, platform: false };
  19. const adminReturn = { merchant: null, platform: null };
  20. let currentPage = 'home';
  21. let notice = '';
  22. let pendingConfirmation = null;
  23. const screens = {};
  24. const roleLabels = { customer: '购物商城 H5', staff: '商家后台', supervisor: '商家后台', merchant: '商家后台', platform: '平台后台' };
  25. const statusLabels = { NONE: '尚未申请', DRAFT: '待客户确认', WAITING_REVIEW: '等待主管审批', APPROVED: '审批通过,准备提交', PROCESSING: '换货处理中', RESULT_UNKNOWN: '正在核实受理结果', COMPLETED: '换货完成', REJECTED: '审批未通过', NEEDS_REVISION: '需要修订方案', CANCELLED: '申请已取消', FAILED: '换货申请失败' };
  26. const escapeHtml = value => String(value ?? '').replace(/[&<>"']/g, char => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[char]));
  27. const button = (label, action, extra = '') => `<button type="button" data-action="${action}" ${extra}>${label}</button>`;
  28. const link = (label, page, role = currentRole) => {
  29. const restricted = ['merchant', 'staff', 'supervisor'].includes(role) && role !== merchantRole;
  30. return `<button type="button" data-page="${page}" data-role="${role}" ${restricted ? 'disabled title="当前员工未获该功能权限"' : ''}>${label}</button>`;
  31. };
  32. const tag = text => `<span class="tag">${escapeHtml(text)}</span>`;
  33. const hint = text => `<div class="hint">${text}</div>`;
  34. const box = (title, content) => `<section class="box"><h3>${title}</h3>${content}</section>`;
  35. const table = (headers, rows) => `<div class="table-wrap"><table><thead><tr>${headers.map(h => `<th scope="col">${h}</th>`).join('')}</tr></thead><tbody>${rows.map(row => `<tr>${row.map(cell => `<td>${cell}</td>`).join('')}</tr>`).join('')}</tbody></table></div>`;
  36. const field = (label, name, value = '', type = 'text', extra = '') => `<label>${label}<input name="${name}" type="${type}" value="${escapeHtml(value)}" ${extra}></label>`;
  37. const area = (label, name, value = '', extra = '') => `<label>${label}<textarea name="${name}" ${extra}>${escapeHtml(value)}</textarea></label>`;
  38. const select = (label, name, options, value) => `<label>${label}<select name="${name}">${options.map(option => `<option${option === value ? ' selected' : ''}>${escapeHtml(option)}</option>`).join('')}</select></label>`;
  39. const actions = content => `<div class="actions">${content}</div>`;
  40. const form = (action, content, label = '保存', extra = '') => `<form data-submit="${action}">${content}<button class="primary" type="submit" ${extra}>${label}</button></form>`;
  41. const consent = (name, label) => `<label class="check"><input type="checkbox" name="${name}" required><span>${label}</span></label>`;
  42. const canCancel = () => ['DRAFT', 'WAITING_REVIEW', 'APPROVED'].includes(state.ticket);
  43. const canRevise = () => ['DRAFT', 'WAITING_REVIEW', 'NEEDS_REVISION'].includes(state.ticket);
  44. const canCreate = () => ['NONE', 'CANCELLED', 'REJECTED', 'FAILED'].includes(state.ticket);
  45. function register(role, page, code, title, render, notes) { screens[`${role}/${page}`] = { role, page, code, title, render, notes }; }
  46. function mobile(title, content) {
  47. const root = ['home', 'categories', 'cart', 'profile'].includes(currentPage);
  48. const tabs = [['home', '首页'], ['categories', '分类'], ['cart', '购物车'], ['profile', '我的']];
  49. return `<div class="surface mobile"><div class="device-status"><span>9:41</span><span>网络 电量</span></div><header class="mobile-header">${root ? '<strong>智选商城</strong>' : link('返回', currentPage === 'chat' ? 'home' : 'profile', 'customer')}<strong>${title}</strong>${link(currentPage === 'chat' ? '我的订单' : '消息', currentPage === 'chat' ? 'orders' : 'messages', 'customer')}</header><div class="content">${content}</div>${root ? `<footer class="shop-tabs">${tabs.map(([page, label]) => `<button type="button" data-page="${page}" data-role="customer" ${page === currentPage ? 'aria-current="page"' : ''}>${label}</button>`).join('')}</footer>` : ''}${currentPage !== 'chat' && currentPage !== 'login' ? '<button type="button" class="ai-float" data-page="chat" data-role="customer"><strong>AI</strong><span>购物助手</span></button>' : ''}</div>`;
  50. }
  51. function desktop(content) { return adminShell(content); }
  52. function orderSummary() { return `<p>订单 A-20260918-001</p><p>键盘 × 1 · 签收后第 2 天</p><p>可申请数量:1</p>`; }
  53. function proposalSummary() { return `${orderSummary()}<p>申请数量:${state.quantity}</p><p>故障描述:${escapeHtml(state.reason)}</p><p>方案版本:V${state.version}</p><p>依据:《键盘换货规则》V${state.proposalRuleVersion},签收后 ${state.proposalRuleDays} 天内功能故障可申请,需客户确认及主管审批。</p>`; }
  54. function ticketSummary() { return `${tag(statusLabels[state.ticket])}<p>工单 SH-001 · 当前方案 V${state.version}</p>${proposalSummary()}`; }
  55. function record(text) { state.audit.unshift(text); state.lastAction = text; }
  56. function confirmAction(title, body, callback) {
  57. pendingConfirmation = callback;
  58. document.getElementById('dialog-title').textContent = title;
  59. document.getElementById('dialog-body').textContent = body;
  60. document.getElementById('confirm-dialog').showModal();
  61. }