You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

164 lines
5.6 KiB

2 weeks ago
const API = 'http://localhost:8080';
let token = null;
let currentView = 'MINE';
const $ = (id) => document.getElementById(id);
function toast(msg) {
const t = $('toast');
t.textContent = msg;
t.classList.remove('hidden');
clearTimeout(t._h);
t._h = setTimeout(() => t.classList.add('hidden'), 2500);
}
function authHeaders(extra = {}) {
const h = { ...extra };
if (token) h['Authorization'] = 'Bearer ' + token;
return h;
}
const STATUS_TEXT = { 1: '待领取', 2: '推进中', 3: '暂缓中', 4: '已关闭', 5: '已转项目' };
// ---- 登录(调试 token)----
async function login() {
const userId = $('userId').value.trim();
try {
const res = await fetch(`${API}/api/auth/debug/token?userId=${encodeURIComponent(userId)}`);
const body = await res.json();
if (body.success && body.data) {
token = body.data;
$('tokenState').textContent = '已登录 uid=' + userId;
$('tokenState').className = 'pill pill-on';
toast('登录成功');
loadList();
} else {
toast('登录失败: ' + (body.message || res.status));
}
} catch (e) {
toast('登录请求异常: ' + e.message);
}
}
// ---- 列表 ----
async function loadList() {
if (!token) { toast('请先签发调试 Token'); return; }
const params = new URLSearchParams({ viewType: currentView, current: '1', size: '20' });
try {
const res = await fetch(`${API}/api/opportunity/page`, {
method: 'POST',
headers: authHeaders({ 'Content-Type': 'application/x-www-form-urlencoded' }),
body: params.toString(),
});
const body = await res.json();
if (!body.success) { renderRows([]); $('listMeta').textContent = '错误: ' + body.message; return; }
const page = body.data || {};
renderRows(page.content || []);
$('listMeta').textContent = `视图 ${currentView} · 共 ${page.total ?? 0}`;
} catch (e) {
toast('列表加载异常: ' + e.message);
}
}
function renderRows(rows) {
const tbody = $('oppRows');
if (!rows.length) {
tbody.innerHTML = '<tr><td colspan="8" class="empty">无数据</td></tr>';
return;
}
tbody.innerHTML = rows.map((r) => `
<tr data-id="${r.id}">
<td>${r.id ?? ''}</td>
<td>${esc(r.oppName)}</td>
<td>${esc(r.oppSourceName || r.oppSource || '')}</td>
<td class="status-${r.oppStatus}">${STATUS_TEXT[r.oppStatus] || r.oppStatus || ''}</td>
<td>${esc(r.currentStageName || '')}</td>
<td>${esc(r.ownerNameSnapshot || '')}</td>
<td>${esc(r.primaryCustomerNameSnapshot || '')}</td>
<td>${r.projectAmount ?? ''}</td>
</tr>`).join('');
}
function esc(s) {
return String(s ?? '').replace(/[&<>"]/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' }[c]));
}
// ---- 新建商机 ----
async function openNewModal() {
if (!token) { toast('请先签发调试 Token'); return; }
$('newForm').reset();
$('formMsg').textContent = '';
onSourceChange();
$('newModal').classList.remove('hidden');
}
function closeNewModal() { $('newModal').classList.add('hidden'); }
async function onSourceChange() {
const isLead = $('f_oppSource').value === 'opp_source_01';
$('leadRow').classList.toggle('hidden', !isLead);
if (isLead) await loadConvertibleLeads();
}
async function loadConvertibleLeads() {
const sel = $('f_sourceLeadId');
sel.innerHTML = '<option value="">(加载中…)</option>';
try {
const res = await fetch(`${API}/api/opportunity/convertible-leads`, { headers: authHeaders() });
const body = await res.json();
const list = (body.success && body.data) || [];
sel.innerHTML = '<option value="">请选择</option>' +
list.map((l) => `<option value="${l.id}">${esc(l.leadName || l.name || l.id)}</option>`).join('');
if (!list.length) sel.innerHTML = '<option value="">(无可关联线索)</option>';
} catch (e) {
sel.innerHTML = '<option value="">加载失败</option>';
}
}
async function submitNew(ev) {
ev.preventDefault();
const msg = $('formMsg');
msg.textContent = '提交中…'; msg.className = 'msg';
const payload = {
oppSource: $('f_oppSource').value,
opportunityName: $('f_oppName').value.trim(),
partyA: $('f_partyA').value.trim() || null,
customerName: $('f_customerName').value.trim() || null,
remark: $('f_remark').value.trim() || null,
sourceLeadId: $('f_sourceLeadId').value ? Number($('f_sourceLeadId').value) : null,
};
try {
const res = await fetch(`${API}/api/opportunity`, {
method: 'POST',
headers: authHeaders({ 'Content-Type': 'application/json' }),
body: JSON.stringify(payload),
});
const body = await res.json();
if (body.success) {
msg.textContent = '创建成功 id=' + body.data; msg.className = 'msg ok';
toast('商机已创建 id=' + body.data);
setTimeout(() => { closeNewModal(); loadList(); }, 700);
} else {
msg.textContent = '失败: ' + (body.message || res.status); msg.className = 'msg err';
}
} catch (e) {
msg.textContent = '异常: ' + e.message; msg.className = 'msg err';
}
}
// ---- 事件绑定 ----
$('btnLogin').addEventListener('click', login);
$('btnRefresh').addEventListener('click', loadList);
$('btnNew').addEventListener('click', openNewModal);
$('btnCancel').addEventListener('click', closeNewModal);
$('f_oppSource').addEventListener('change', onSourceChange);
$('newForm').addEventListener('submit', submitNew);
document.querySelectorAll('#viewTabs button').forEach((b) => {
b.addEventListener('click', () => {
document.querySelectorAll('#viewTabs button').forEach((x) => x.classList.remove('active'));
b.classList.add('active');
currentView = b.dataset.view;
loadList();
});
});