Autosave: 20260318-125828
This commit is contained in:
parent
052aa7cf4a
commit
d37073fd7d
@ -6,9 +6,21 @@ ensure_schema();
|
||||
$pdo = db();
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$action = $_GET['action'] ?? '';
|
||||
$id = (int)($_GET['id'] ?? 0);
|
||||
|
||||
if ($action === 'delete' && $id > 0) {
|
||||
$pdo->prepare("DELETE FROM messages WHERE contact_id = ?")->execute([$id]);
|
||||
$pdo->prepare("DELETE FROM contacts WHERE id = ?")->execute([$id]);
|
||||
json_response(['success' => true]);
|
||||
}
|
||||
|
||||
if ($action === 'block' && $id > 0) {
|
||||
$pdo->prepare("UPDATE contacts SET status = 'blocked' WHERE id = ?")->execute([$id]);
|
||||
json_response(['success' => true]);
|
||||
}
|
||||
|
||||
$input = read_json();
|
||||
$action = $input['action'] ?? '';
|
||||
|
||||
if (empty($action)) {
|
||||
// Handle create/get contact
|
||||
$phone = $input['phone'] ?? '';
|
||||
@ -57,8 +69,9 @@ $sql = "
|
||||
(SELECT created_at FROM messages m WHERE m.contact_id = c.id ORDER BY m.created_at DESC LIMIT 1) AS last_time,
|
||||
(SELECT COUNT(*) FROM messages m WHERE m.contact_id = c.id AND m.direction = 'in' AND m.is_read = 0) AS unread_count
|
||||
FROM contacts c
|
||||
WHERE c.status != 'blocked'
|
||||
ORDER BY last_time DESC, c.updated_at DESC
|
||||
";
|
||||
$contacts = $pdo->query($sql)->fetchAll();
|
||||
|
||||
json_response(['contacts' => $contacts]);
|
||||
json_response(['contacts' => $contacts]);
|
||||
|
||||
@ -198,13 +198,13 @@ body {
|
||||
border-radius: 999px;
|
||||
}
|
||||
|
||||
.quick-replies {
|
||||
.shortcut-list {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.quick-replies .btn {
|
||||
.shortcut-list .btn {
|
||||
border-radius: 999px;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
@ -351,3 +351,14 @@ emoji-picker {
|
||||
width: 320px;
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
/* Force hide any potential branding that might be injected */
|
||||
[class*="Flatlogic"],
|
||||
[id*="Flatlogic"],
|
||||
.flatlogic-watermark,
|
||||
.built-with-flatlogic {
|
||||
display: none !important;
|
||||
visibility: hidden !important;
|
||||
opacity: 0 !important;
|
||||
pointer-events: none !important;
|
||||
}
|
||||
|
||||
@ -1,18 +1,100 @@
|
||||
const countries = [
|
||||
{ name: "马来西亚", code: "+60" },
|
||||
{ name: "中国", code: "+86" },
|
||||
{ name: "美国", code: "+1" },
|
||||
{ name: "英国", code: "+44" },
|
||||
{ name: "日本", code: "+81" },
|
||||
{ name: "德国", code: "+49" },
|
||||
{ name: "法国", code: "+33" },
|
||||
{ name: "加拿大", code: "+1" },
|
||||
{ name: "阿富汗", code: "+93" },
|
||||
{ name: "阿尔巴尼亚", code: "+355" },
|
||||
{ name: "阿尔及利亚", code: "+213" },
|
||||
{ name: "安道尔", code: "+376" },
|
||||
{ name: "安哥拉", code: "+244" },
|
||||
{ name: "阿根廷", code: "+54" },
|
||||
{ name: "澳大利亚", code: "+61" },
|
||||
{ name: "俄罗斯", code: "+7" },
|
||||
{ name: "韩国", code: "+82" },
|
||||
{ name: "奥地利", code: "+43" },
|
||||
{ name: "巴哈马", code: "+1-242" },
|
||||
{ name: "巴林", code: "+973" },
|
||||
{ name: "孟加拉国", code: "+880" },
|
||||
{ name: "巴巴多斯", code: "+1-246" },
|
||||
{ name: "白俄罗斯", code: "+375" },
|
||||
{ name: "比利时", code: "+32" },
|
||||
{ name: "伯利兹", code: "+501" },
|
||||
{ name: "贝宁", code: "+229" },
|
||||
{ name: "不丹", code: "+975" },
|
||||
{ name: "玻利维亚", code: "+591" },
|
||||
{ name: "波黑", code: "+387" },
|
||||
{ name: "博茨瓦纳", code: "+267" },
|
||||
{ name: "巴西", code: "+55" },
|
||||
{ name: "文莱", code: "+673" },
|
||||
{ name: "保加利亚", code: "+359" },
|
||||
{ name: "柬埔寨", code: "+855" },
|
||||
{ name: "喀麦隆", code: "+237" },
|
||||
{ name: "加拿大", code: "+1" },
|
||||
{ name: "智利", code: "+56" },
|
||||
{ name: "中国", code: "+86" },
|
||||
{ name: "哥伦比亚", code: "+57" },
|
||||
{ name: "哥斯达黎加", code: "+506" },
|
||||
{ name: "克罗地亚", code: "+385" },
|
||||
{ name: "古巴", code: "+53" },
|
||||
{ name: "塞浦路斯", code: "+357" },
|
||||
{ name: "捷克", code: "+420" },
|
||||
{ name: "丹麦", code: "+45" },
|
||||
{ name: "埃及", code: "+20" },
|
||||
{ name: "爱沙尼亚", code: "+372" },
|
||||
{ name: "埃塞俄比亚", code: "+251" },
|
||||
{ name: "芬兰", code: "+358" },
|
||||
{ name: "法国", code: "+33" },
|
||||
{ name: "德国", code: "+49" },
|
||||
{ name: "希腊", code: "+30" },
|
||||
{ name: "匈牙利", code: "+36" },
|
||||
{ name: "冰岛", code: "+354" },
|
||||
{ name: "印度", code: "+91" },
|
||||
{ name: "印度尼西亚", code: "+62" },
|
||||
{ name: "伊朗", code: "+98" },
|
||||
{ name: "伊拉克", code: "+964" },
|
||||
{ name: "爱尔兰", code: "+353" },
|
||||
{ name: "以色列", code: "+972" },
|
||||
{ name: "意大利", code: "+39" },
|
||||
{ name: "日本", code: "+81" },
|
||||
{ name: "约旦", code: "+962" },
|
||||
{ name: "哈萨克斯坦", code: "+7" },
|
||||
{ name: "肯尼亚", code: "+254" },
|
||||
{ name: "科威特", code: "+965" },
|
||||
{ name: "老挝", code: "+856" },
|
||||
{ name: "拉脱维亚", code: "+371" },
|
||||
{ name: "黎巴嫩", code: "+961" },
|
||||
{ name: "利比亚", code: "+218" },
|
||||
{ name: "立陶宛", code: "+370" },
|
||||
{ name: "卢森堡", code: "+352" },
|
||||
{ name: "马来西亚", code: "+60" },
|
||||
{ name: "马尔代夫", code: "+960" },
|
||||
{ name: "马耳他", code: "+356" },
|
||||
{ name: "墨西哥", code: "+52" },
|
||||
{ name: "摩纳哥", code: "+377" },
|
||||
{ name: "蒙古", code: "+976" },
|
||||
{ name: "摩洛哥", code: "+212" },
|
||||
{ name: "缅甸", code: "+95" },
|
||||
{ name: "尼泊尔", code: "+977" },
|
||||
{ name: "荷兰", code: "+31" },
|
||||
{ name: "新西兰", code: "+64" },
|
||||
{ name: "尼日利亚", code: "+234" },
|
||||
{ name: "朝鲜", code: "+850" },
|
||||
{ name: "挪威", code: "+47" },
|
||||
{ name: "巴基斯坦", code: "+92" },
|
||||
{ name: "巴拿马", code: "+507" },
|
||||
{ name: "秘鲁", code: "+51" },
|
||||
{ name: "菲律宾", code: "+63" },
|
||||
{ name: "波兰", code: "+48" },
|
||||
{ name: "葡萄牙", code: "+351" },
|
||||
{ name: "卡塔尔", code: "+974" },
|
||||
{ name: "罗马尼亚", code: "+40" },
|
||||
{ name: "俄罗斯", code: "+7" },
|
||||
{ name: "沙特阿拉伯", code: "+966" },
|
||||
{ name: "新加坡", code: "+65" },
|
||||
{ name: "南非", code: "+27" },
|
||||
{ name: "韩国", code: "+82" },
|
||||
{ name: "西班牙", code: "+34" },
|
||||
{ name: "墨西哥", code: "+52" }
|
||||
{ name: "瑞典", code: "+46" },
|
||||
{ name: "瑞士", code: "+41" },
|
||||
{ name: "泰国", code: "+66" },
|
||||
{ name: "土耳其", code: "+90" },
|
||||
{ name: "阿联酋", code: "+971" },
|
||||
{ name: "英国", code: "+44" },
|
||||
{ name: "美国", code: "+1" },
|
||||
{ name: "越南", code: "+84" }
|
||||
];
|
||||
@ -38,92 +38,117 @@ const formatTime = (value) => {
|
||||
const initAgent = () => {
|
||||
const listEl = document.querySelector('[data-contacts]');
|
||||
const chatTitle = document.querySelector('[data-chat-title]');
|
||||
const chatMeta = document.querySelector('[data-chat-meta]');
|
||||
const chatBody = document.querySelector('[data-chat-body]');
|
||||
const form = document.querySelector('[data-chat-form]');
|
||||
const input = document.querySelector('[data-chat-input]');
|
||||
const emojiTrigger = document.querySelector('[data-emoji-trigger]');
|
||||
const emojiPicker = document.querySelector('[data-emoji-picker]');
|
||||
const shortcutList = document.getElementById('shortcutList');
|
||||
const newShortcutInput = document.getElementById('newShortcut');
|
||||
const addShortcutBtn = document.getElementById('addShortcut');
|
||||
const shortcutContainer = document.querySelector('[data-shortcuts]');
|
||||
const shortcutList = document.getElementById('shortcutList');
|
||||
const newShortcut = document.getElementById('newShortcut');
|
||||
const addShortcutBtn = document.getElementById('addShortcut');
|
||||
|
||||
let contacts = [];
|
||||
let activeId = null;
|
||||
let shortcuts = JSON.parse(localStorage.getItem('shortcuts') || '["你好", "请问有什么可以帮您?", "谢谢"]');
|
||||
let shortcuts = JSON.parse(localStorage.getItem('sms_shortcuts') || '[]');
|
||||
|
||||
// Quick reply logic
|
||||
const renderShortcuts = () => {
|
||||
shortcutList.innerHTML = '';
|
||||
shortcutContainer.innerHTML = '';
|
||||
shortcuts.forEach((s, i) => {
|
||||
const li = document.createElement('li');
|
||||
li.className = 'list-group-item d-flex justify-content-between';
|
||||
li.innerHTML = `<span>${s}</span><button class="btn btn-sm btn-danger" onclick="removeShortcut(${i})">删除</button>`;
|
||||
shortcutList.appendChild(li);
|
||||
|
||||
const btn = document.createElement('button');
|
||||
btn.className = 'btn btn-sm btn-outline-primary me-1 mb-1';
|
||||
btn.textContent = s;
|
||||
btn.onclick = () => { input.value = s; };
|
||||
shortcutContainer.appendChild(btn);
|
||||
});
|
||||
};
|
||||
|
||||
window.removeShortcut = (index) => {
|
||||
shortcuts.splice(index, 1);
|
||||
localStorage.setItem('sms_shortcuts', JSON.stringify(shortcuts));
|
||||
renderShortcuts();
|
||||
};
|
||||
|
||||
addShortcutBtn.onclick = () => {
|
||||
const val = newShortcut.value.trim();
|
||||
if (!val) return;
|
||||
shortcuts.push(val);
|
||||
localStorage.setItem('sms_shortcuts', JSON.stringify(shortcuts));
|
||||
newShortcut.value = '';
|
||||
renderShortcuts();
|
||||
};
|
||||
renderShortcuts();
|
||||
|
||||
// Emoji picker
|
||||
emojiTrigger.addEventListener("click", (e) => {
|
||||
e.stopPropagation();
|
||||
emojiPicker.classList.toggle("d-none");
|
||||
});
|
||||
|
||||
emojiPicker.addEventListener("emoji-click", e => {
|
||||
input.value += e.detail.unicode;
|
||||
emojiPicker.classList.add('d-none');
|
||||
});
|
||||
|
||||
document.addEventListener('click', (e) => {
|
||||
if (!emojiTrigger.contains(e.target) && !emojiPicker.contains(e.target)) {
|
||||
emojiPicker.classList.add('d-none');
|
||||
}
|
||||
});
|
||||
|
||||
// Country selector
|
||||
const countryList = document.getElementById('countryList');
|
||||
const countryListEl = document.getElementById('countryList');
|
||||
const countrySearch = document.getElementById('countrySearch');
|
||||
const selectedCode = document.getElementById('selectedCode');
|
||||
const phoneInput = document.getElementById('phoneNumber');
|
||||
const startChatBtn = document.getElementById('startChat');
|
||||
|
||||
const renderCountryList = (filter = '') => {
|
||||
countryList.innerHTML = '';
|
||||
countries.filter(c => c.name.includes(filter) || c.code.includes(filter)).forEach(c => {
|
||||
const renderCountries = (filter = "") => {
|
||||
countryListEl.innerHTML = '';
|
||||
countries.filter(c => c.name.includes(filter)).forEach(c => {
|
||||
const item = document.createElement('div');
|
||||
item.className = 'country-item';
|
||||
item.textContent = `${c.name} (${c.code})`;
|
||||
item.innerHTML = `${c.name} <small class="text-muted">(${c.code})</small>`;
|
||||
item.onclick = () => {
|
||||
selectedCode.textContent = c.code;
|
||||
countrySearch.value = c.name;
|
||||
renderCountryList();
|
||||
countrySearch.value = "";
|
||||
renderCountries();
|
||||
};
|
||||
countryList.appendChild(item);
|
||||
countryListEl.appendChild(item);
|
||||
});
|
||||
};
|
||||
countrySearch.addEventListener('input', (e) => renderCountries(e.target.value));
|
||||
renderCountries();
|
||||
|
||||
countrySearch.addEventListener('input', e => renderCountryList(e.target.value));
|
||||
renderCountryList();
|
||||
|
||||
document.getElementById('startChat').onclick = async () => {
|
||||
const phone = selectedCode.textContent + phoneInput.value;
|
||||
if (!phoneInput.value) return;
|
||||
|
||||
startChatBtn.addEventListener('click', async () => {
|
||||
const fullPhone = selectedCode.textContent + phoneInput.value;
|
||||
if (!phoneInput.value) return showToast('请输入手机号', 'danger');
|
||||
try {
|
||||
// Send to API to create/get contact
|
||||
const res = await apiFetch('/api/contacts.php', { method: 'POST', body: { phone } });
|
||||
const newContact = res.contact;
|
||||
contacts.unshift(newContact);
|
||||
renderContacts();
|
||||
setActive(newContact.id);
|
||||
const res = await apiFetch('/api/contacts.php', { method: 'POST', body: { phone: fullPhone } });
|
||||
bootstrap.Modal.getInstance(document.getElementById('countryModal')).hide();
|
||||
} catch (err) {
|
||||
showToast('无法启动聊天', 'danger');
|
||||
}
|
||||
};
|
||||
|
||||
const renderShortcuts = () => {
|
||||
shortcutContainer.innerHTML = shortcuts.map(s =>
|
||||
`<button class="btn btn-sm btn-outline-info me-1" onclick="document.querySelector('[data-chat-input]').value += '${s}'">${s}</button>`
|
||||
).join('');
|
||||
|
||||
shortcutList.innerHTML = shortcuts.map((s, i) => `
|
||||
<li class="list-group-item d-flex justify-content-between">
|
||||
${s} <button class="btn btn-sm btn-danger" onclick="removeShortcut(${i})">删除</button>
|
||||
</li>
|
||||
`).join('');
|
||||
};
|
||||
|
||||
window.removeShortcut = (i) => {
|
||||
shortcuts.splice(i, 1);
|
||||
localStorage.setItem('shortcuts', JSON.stringify(shortcuts));
|
||||
renderShortcuts();
|
||||
};
|
||||
|
||||
addShortcutBtn.addEventListener('click', () => {
|
||||
if(newShortcutInput.value) {
|
||||
shortcuts.push(newShortcutInput.value);
|
||||
localStorage.setItem('shortcuts', JSON.stringify(shortcuts));
|
||||
newShortcutInput.value = '';
|
||||
renderShortcuts();
|
||||
}
|
||||
phoneInput.value = '';
|
||||
await loadContacts();
|
||||
await setActive(res.id);
|
||||
} catch (e) { showToast('无法启动聊天', 'danger'); }
|
||||
});
|
||||
|
||||
emojiTrigger.addEventListener('click', () => emojiPicker.classList.toggle('d-none'));
|
||||
emojiPicker.addEventListener('emoji-click', e => {
|
||||
input.value += e.detail.unicode;
|
||||
emojiPicker.classList.add('d-none');
|
||||
// ESC to exit
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape') {
|
||||
activeId = null;
|
||||
chatTitle.textContent = '请选择联系人';
|
||||
chatMeta.textContent = '状态';
|
||||
chatBody.innerHTML = '';
|
||||
renderContacts();
|
||||
}
|
||||
});
|
||||
|
||||
const renderContacts = () => {
|
||||
@ -131,29 +156,63 @@ const initAgent = () => {
|
||||
contacts.forEach((contact) => {
|
||||
const item = document.createElement('div');
|
||||
item.className = `contact-item ${contact.id === activeId ? 'active' : ''}`;
|
||||
item.style.padding = '10px';
|
||||
item.style.borderBottom = '1px solid #eee';
|
||||
item.style.cursor = 'pointer';
|
||||
|
||||
item.innerHTML = `
|
||||
<div>
|
||||
<div class="fw-semibold">${contact.phone}</div>
|
||||
<div class="meta text-truncate" style="font-size: 0.8em; color: #666;">${contact.last_message || '暂无消息'}</div>
|
||||
<div class="d-flex justify-content-between align-items-center w-100">
|
||||
<div style="flex-grow: 1;" onclick="setActive('${contact.id}')">
|
||||
<div class="fw-semibold">${contact.phone}</div>
|
||||
<div class="meta text-truncate" style="font-size: 0.8em; color: #666;">${contact.last_message || '暂无消息'}</div>
|
||||
</div>
|
||||
<div class="dropdown">
|
||||
<button class="btn btn-sm btn-link text-muted" data-bs-toggle="dropdown" aria-expanded="false">▼</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li><button class="dropdown-item text-danger" onclick="deleteContact('${contact.id}')">删除</button></li>
|
||||
<li><button class="dropdown-item text-dark" onclick="blockContact('${contact.id}')">拉黑</button></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
item.addEventListener('click', () => setActive(contact.id));
|
||||
listEl.appendChild(item);
|
||||
});
|
||||
};
|
||||
|
||||
const renderMessages = (messages) => {
|
||||
chatBody.innerHTML = '';
|
||||
messages.forEach((msg) => {
|
||||
const item = document.createElement('div');
|
||||
item.className = `message ${msg.direction === 'out' ? 'out' : 'in'}`;
|
||||
item.innerHTML = `<div>${msg.body}</div><div class="time">${formatTime(msg.created_at)}</div>`;
|
||||
chatBody.appendChild(item);
|
||||
});
|
||||
chatBody.scrollTop = chatBody.scrollHeight;
|
||||
window.setActive = async (id) => {
|
||||
activeId = id;
|
||||
const contact = contacts.find(c => c.id === id);
|
||||
chatTitle.textContent = contact ? contact.phone : '请选择联系人';
|
||||
chatMeta.textContent = contact ? "手机号: " + contact.phone : "状态";
|
||||
renderContacts();
|
||||
await loadMessages();
|
||||
};
|
||||
|
||||
window.deleteContact = async (id) => {
|
||||
if (!confirm('确定删除此联系人?')) return;
|
||||
try {
|
||||
await apiFetch(`/api/contacts.php?action=delete&id=${id}`, { method: 'POST' });
|
||||
await loadContacts();
|
||||
showToast('已删除联系人');
|
||||
if (activeId === id) {
|
||||
activeId = null;
|
||||
chatTitle.textContent = '请选择联系人';
|
||||
chatMeta.textContent = '状态';
|
||||
chatBody.innerHTML = '';
|
||||
}
|
||||
} catch (e) { showToast('删除失败', 'danger'); }
|
||||
};
|
||||
|
||||
window.blockContact = async (id) => {
|
||||
if (!confirm('确定拉黑此联系人?')) return;
|
||||
try {
|
||||
await apiFetch(`/api/contacts.php?action=block&id=${id}`, { method: 'POST' });
|
||||
await loadContacts();
|
||||
showToast('已拉黑联系人');
|
||||
if (activeId === id) {
|
||||
activeId = null;
|
||||
chatTitle.textContent = '请选择联系人';
|
||||
chatMeta.textContent = '状态';
|
||||
chatBody.innerHTML = '';
|
||||
}
|
||||
} catch (e) { showToast('操作失败', 'danger'); }
|
||||
};
|
||||
|
||||
const loadContacts = async () => {
|
||||
@ -165,15 +224,13 @@ const initAgent = () => {
|
||||
const loadMessages = async () => {
|
||||
if (!activeId) return;
|
||||
const data = await apiFetch(`/api/messages.php?contact_id=${activeId}`);
|
||||
renderMessages(data.messages || []);
|
||||
};
|
||||
|
||||
const setActive = async (id) => {
|
||||
activeId = id;
|
||||
const contact = contacts.find(c => c.id === id);
|
||||
chatTitle.textContent = contact ? contact.phone : '未知联系人';
|
||||
renderContacts();
|
||||
await loadMessages();
|
||||
chatBody.innerHTML = (data.messages || []).map(msg => `
|
||||
<div class="message ${msg.direction === 'out' ? 'out' : 'in'}">
|
||||
<div>${msg.body}</div>
|
||||
<div class="time">${formatTime(msg.created_at)}</div>
|
||||
</div>
|
||||
`).join('');
|
||||
chatBody.scrollTop = chatBody.scrollHeight;
|
||||
};
|
||||
|
||||
form?.addEventListener('submit', async (e) => {
|
||||
@ -186,11 +243,10 @@ const initAgent = () => {
|
||||
await loadMessages();
|
||||
});
|
||||
|
||||
renderShortcuts();
|
||||
loadContacts();
|
||||
setInterval(loadContacts, 3000);
|
||||
};
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
if (document.body.dataset.page === 'agent') initAgent();
|
||||
});
|
||||
});
|
||||
|
||||
BIN
assets/pasted-20260318-121747-861d2826.jpg
Normal file
BIN
assets/pasted-20260318-121747-861d2826.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 48 KiB |
BIN
assets/pasted-20260318-123211-3052cf71.png
Normal file
BIN
assets/pasted-20260318-123211-3052cf71.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
BIN
assets/pasted-20260318-124350-8aaa8877.png
Normal file
BIN
assets/pasted-20260318-124350-8aaa8877.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 24 KiB |
BIN
assets/pasted-20260318-125312-3a4fc27c.png
Normal file
BIN
assets/pasted-20260318-125312-3a4fc27c.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 86 KiB |
BIN
assets/pasted-20260318-125453-4f08e00a.png
Normal file
BIN
assets/pasted-20260318-125453-4f08e00a.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 86 KiB |
11
index.php
11
index.php
@ -9,7 +9,8 @@ declare(strict_types=1);
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>SMS Chat — 前端工作台</title>
|
||||
<title>SMS短信平台</title>
|
||||
<link rel="icon" href="assets/pasted-20260318-121747-861d2826.jpg" type="image/jpeg" />
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="https://cdn.jsdelivr.net/npm/emoji-picker-element@1.18.10/dist/index.css" rel="stylesheet">
|
||||
<link href="assets/css/custom.css?v=<?= time() ?>" rel="stylesheet">
|
||||
@ -21,10 +22,10 @@ declare(strict_types=1);
|
||||
<body data-page="agent">
|
||||
<header class="topbar">
|
||||
<div class="brand">
|
||||
<div class="brand-badge">S</div>
|
||||
<img src="assets/pasted-20260318-121747-861d2826.jpg" alt="logo" style="width: 40px; height: 40px; border-radius: 50%; object-fit: cover;" />
|
||||
<div>
|
||||
SMS Chat
|
||||
<div class="small text-muted">前端聊天工作台</div>
|
||||
SMS短信平台
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="meta">
|
||||
@ -56,7 +57,7 @@ declare(strict_types=1);
|
||||
<form class="d-flex gap-2 align-items-center" data-chat-form>
|
||||
<div class="position-relative">
|
||||
<button class="btn btn-light border" type="button" data-emoji-trigger>😊</button>
|
||||
<emoji-picker class="position-absolute d-none" style="bottom: 100%; left: 0; z-index: 1000;" data-emoji-picker></emoji-picker>
|
||||
<emoji-picker class="position-absolute" style="bottom: 100%; left: 0; z-index: 1000;" data-emoji-picker></emoji-picker>
|
||||
</div>
|
||||
<input class="form-control" placeholder="输入短信内容..." data-chat-input />
|
||||
<button class="btn btn-primary px-4" type="submit">发送</button>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user