mirror of
https://github.com/wangwangit/qywx-push.git
synced 2026-09-07 00:36:51 +08:00
Initial commit
This commit is contained in:
@@ -0,0 +1,584 @@
|
||||
// 企业微信通知配置前端交互脚本
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
// 元素引用
|
||||
const callbackForm = document.getElementById('callbackForm');
|
||||
const configForm = document.getElementById('configForm');
|
||||
const validateBtn = document.getElementById('validateBtn');
|
||||
const userListSection = document.getElementById('userListSection');
|
||||
const userList = document.getElementById('userList');
|
||||
const lookupForm = document.getElementById('lookupForm');
|
||||
const lookupResultDiv = document.getElementById('lookup-result');
|
||||
const resultDiv = document.getElementById('result');
|
||||
const saveAlert = document.getElementById('save-alert');
|
||||
const step1Container = document.getElementById('step1-container');
|
||||
const step2Container = document.getElementById('step2-container');
|
||||
const callbackResult = document.getElementById('callbackResult');
|
||||
|
||||
let usersCache = [];
|
||||
let currentCode = null; // 存储当前的code
|
||||
|
||||
// 第一步:生成回调URL
|
||||
callbackForm.addEventListener('submit', async function (e) {
|
||||
e.preventDefault();
|
||||
resultDiv.innerHTML = '';
|
||||
|
||||
const corpid = callbackForm.corpid.value.trim();
|
||||
const callbackToken = callbackForm.callback_token.value.trim();
|
||||
const encodingAesKey = callbackForm.encoding_aes_key.value.trim();
|
||||
|
||||
if (!corpid || !callbackToken || !encodingAesKey) {
|
||||
showError('请填写所有必填项');
|
||||
return;
|
||||
}
|
||||
if (encodingAesKey.length !== 43) {
|
||||
showError('EncodingAESKey必须是43位字符');
|
||||
return;
|
||||
}
|
||||
|
||||
const submitBtn = callbackForm.querySelector('button[type=submit]');
|
||||
submitBtn.disabled = true;
|
||||
submitBtn.textContent = '生成中...';
|
||||
|
||||
try {
|
||||
const res = await fetch('/api/generate-callback', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
corpid,
|
||||
callback_token: callbackToken,
|
||||
encoding_aes_key: encodingAesKey
|
||||
})
|
||||
});
|
||||
const data = await res.json();
|
||||
if (!res.ok) throw new Error(data.error || '生成失败');
|
||||
|
||||
currentCode = data.code;
|
||||
showCallbackResult(data);
|
||||
|
||||
// 显示第二步
|
||||
step2Container.classList.remove('hidden');
|
||||
gsap.from(step2Container, { opacity: 0, y: 20, duration: 0.5 });
|
||||
|
||||
// 将CorpID传递到第二步
|
||||
configForm.corpid = { value: corpid };
|
||||
|
||||
} catch (err) {
|
||||
showError('生成回调URL失败: ' + err.message);
|
||||
} finally {
|
||||
submitBtn.disabled = false;
|
||||
submitBtn.textContent = '生成回调URL';
|
||||
}
|
||||
});
|
||||
|
||||
// 验证并获取成员列表
|
||||
validateBtn.addEventListener('click', async function (e) {
|
||||
e.preventDefault();
|
||||
resultDiv.innerHTML = '';
|
||||
userList.innerHTML = '';
|
||||
userListSection.classList.add('hidden');
|
||||
|
||||
const corpid = callbackForm.corpid.value.trim(); // 从第一步获取
|
||||
const corpsecret = configForm.corpsecret.value.trim();
|
||||
if (!corpid || !corpsecret) {
|
||||
showError('请填写CorpSecret');
|
||||
return;
|
||||
}
|
||||
validateBtn.disabled = true;
|
||||
validateBtn.textContent = '验证中...';
|
||||
try {
|
||||
const res = await fetch('/api/validate', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ corpid, corpsecret })
|
||||
});
|
||||
const data = await res.json();
|
||||
if (!res.ok) throw new Error(data.error || '验证失败');
|
||||
usersCache = data.users || [];
|
||||
if (usersCache.length === 0) {
|
||||
showError('未获取到任何成员');
|
||||
return;
|
||||
}
|
||||
userList.innerHTML = usersCache.map(user =>
|
||||
`<label class="flex items-center gap-2 cursor-pointer">
|
||||
<input type="checkbox" class="checkbox checkbox-sm" value="${user.userid}">
|
||||
<span>${user.name} <span class="text-xs text-gray-400">(${user.userid})</span></span>
|
||||
</label>`
|
||||
).join('');
|
||||
userListSection.classList.remove('hidden');
|
||||
gsap.from(userListSection, { opacity: 0, y: 20, duration: 0.5 });
|
||||
} catch (err) {
|
||||
showError(err.message);
|
||||
} finally {
|
||||
validateBtn.disabled = false;
|
||||
validateBtn.textContent = '验证并获取成员列表';
|
||||
}
|
||||
});
|
||||
|
||||
// 查找配置
|
||||
lookupForm.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
const code = lookupForm.code.value.trim();
|
||||
if (!code) return;
|
||||
|
||||
lookupResultDiv.innerHTML = '<div class="loading loading-spinner loading-md mx-auto"></div>';
|
||||
|
||||
try {
|
||||
const res = await fetch(`/api/configuration/${code}`);
|
||||
const data = await res.json();
|
||||
|
||||
if (!res.ok) throw new Error(data.error || '查找配置失败');
|
||||
|
||||
// 显示配置信息
|
||||
const apiUrl = `/api/notify/${data.code}`;
|
||||
lookupResultDiv.innerHTML = `
|
||||
<div class="card bg-base-100 shadow-md">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title flex items-center gap-2">
|
||||
<i data-lucide="settings" class="h-5 w-5"></i>
|
||||
配置详情
|
||||
</h2>
|
||||
<div class="space-y-2 mt-2">
|
||||
<p><span class="font-medium">CorpID:</span> ${data.corpid}</p>
|
||||
<p><span class="font-medium">AgentID:</span> ${data.agentid}</p>
|
||||
<p><span class="font-medium">接收用户:</span> ${data.touser.join(', ')}</p>
|
||||
<p><span class="font-medium">描述:</span> ${data.description || '无'}</p>
|
||||
<p><span class="font-medium">回调状态:</span> ${data.callback_enabled ? '已启用' : '未启用'}</p>
|
||||
${data.callback_enabled ? `<p><span class="font-medium">回调Token:</span> ${data.callback_token || '未设置'}</p>` : ''}
|
||||
<p><span class="font-medium">创建时间:</span> ${new Date(data.created_at).toLocaleString()}</p>
|
||||
</div>
|
||||
<div class="card-actions justify-end mt-4">
|
||||
<button class="btn btn-primary btn-sm" id="edit-config-btn" data-code="${data.code}">
|
||||
<i data-lucide="edit" class="h-4 w-4"></i>
|
||||
编辑配置
|
||||
</button>
|
||||
<button class="btn btn-outline btn-sm" id="copy-api-btn" data-code="${data.code}">
|
||||
<i data-lucide="copy" class="h-4 w-4"></i>
|
||||
复制API地址
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="divider">API使用说明</div>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<h3 class="font-medium mb-2">请求方式</h3>
|
||||
<div class="bg-base-200 p-3 rounded-md">
|
||||
<code class="text-sm">POST ${apiUrl}</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="font-medium mb-2">请求头</h3>
|
||||
<div class="bg-base-200 p-3 rounded-md">
|
||||
<code class="text-sm">Content-Type: application/json</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="font-medium mb-2">请求参数</h3>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="table table-zebra w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>参数名</th>
|
||||
<th>类型</th>
|
||||
<th>必填</th>
|
||||
<th>说明</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="font-mono">title</td>
|
||||
<td>String</td>
|
||||
<td>否</td>
|
||||
<td>消息标题,可选</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="font-mono">content</td>
|
||||
<td>String</td>
|
||||
<td>是</td>
|
||||
<td>消息内容,必填</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="font-medium mb-2">请求示例</h3>
|
||||
<div class="bg-base-200 p-3 rounded-md">
|
||||
<pre class="text-sm whitespace-pre-wrap">curl -X POST "${apiUrl}" \\
|
||||
-H "Content-Type: application/json" \\
|
||||
-d '{
|
||||
"title": "服务器告警",
|
||||
"content": "CPU使用率超过90%,请及时处理!"
|
||||
}'</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="font-medium mb-2">返回示例</h3>
|
||||
<div class="bg-base-200 p-3 rounded-md">
|
||||
<pre class="text-sm whitespace-pre-wrap">{
|
||||
"message": "发送成功",
|
||||
"response": {
|
||||
"errcode": 0,
|
||||
"errmsg": "ok",
|
||||
"msgid": "MSGID1234567890"
|
||||
}
|
||||
}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
lucide.createIcons();
|
||||
gsap.from(lookupResultDiv.firstElementChild, { opacity: 0, y: 20, duration: 0.5 });
|
||||
|
||||
// 绑定编辑和复制按钮事件
|
||||
document.getElementById('edit-config-btn').addEventListener('click', (e) => {
|
||||
const code = e.currentTarget.dataset.code;
|
||||
// 这里可以添加编辑功能的实现
|
||||
showToast('编辑功能待实现');
|
||||
});
|
||||
|
||||
document.getElementById('copy-api-btn').addEventListener('click', (e) => {
|
||||
const code = e.currentTarget.dataset.code;
|
||||
navigator.clipboard.writeText(`/api/notify/${code}`);
|
||||
showToast('API地址已复制到剪贴板');
|
||||
});
|
||||
|
||||
} catch (err) {
|
||||
lookupResultDiv.innerHTML = `
|
||||
<div class="alert alert-error">
|
||||
<i data-lucide="alert-circle" class="h-5 w-5"></i>
|
||||
<span>${err.message}</span>
|
||||
</div>
|
||||
`;
|
||||
lucide.createIcons();
|
||||
}
|
||||
});
|
||||
|
||||
// 第二步:完善配置
|
||||
configForm.addEventListener('submit', async function (e) {
|
||||
e.preventDefault();
|
||||
resultDiv.innerHTML = '';
|
||||
|
||||
if (!currentCode) {
|
||||
showError('请先完成第一步生成回调URL');
|
||||
return;
|
||||
}
|
||||
|
||||
const corpsecret = configForm.corpsecret.value.trim();
|
||||
const agentid = configForm.agentid.value.trim();
|
||||
const description = configForm.description.value.trim();
|
||||
const checked = userListSection.classList.contains('hidden')
|
||||
? []
|
||||
: Array.from(userList.querySelectorAll('input[type=checkbox]:checked')).map(cb => cb.value);
|
||||
if (!corpsecret || !agentid || checked.length === 0) {
|
||||
showError('请填写所有必填项并选择至少一个成员');
|
||||
return;
|
||||
}
|
||||
|
||||
const payload = {
|
||||
code: currentCode,
|
||||
corpsecret,
|
||||
agentid: Number(agentid),
|
||||
touser: checked,
|
||||
description
|
||||
};
|
||||
configForm.querySelector('button[type=submit]').disabled = true;
|
||||
configForm.querySelector('button[type=submit]').textContent = '完成中...';
|
||||
try {
|
||||
const res = await fetch('/api/complete-config', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
const data = await res.json();
|
||||
if (!res.ok) throw new Error(data.error || '完成失败');
|
||||
showFinalResult(data);
|
||||
|
||||
// 显示一次性保存提醒
|
||||
saveAlert.classList.remove('hidden');
|
||||
gsap.from(saveAlert, { opacity: 0, y: -50, duration: 0.5 });
|
||||
setTimeout(() => {
|
||||
gsap.to(saveAlert, { opacity: 0, y: -50, duration: 0.5, onComplete: () => {
|
||||
saveAlert.classList.add('hidden');
|
||||
saveAlert.style.opacity = 1;
|
||||
saveAlert.style.transform = 'none';
|
||||
}});
|
||||
}, 5000);
|
||||
} catch (err) {
|
||||
showError(err.message);
|
||||
} finally {
|
||||
configForm.querySelector('button[type=submit]').disabled = false;
|
||||
configForm.querySelector('button[type=submit]').textContent = '完成配置';
|
||||
}
|
||||
});
|
||||
|
||||
function showError(msg) {
|
||||
resultDiv.innerHTML = `<div class="alert alert-error"><span>${msg}</span></div>`;
|
||||
gsap.from(resultDiv, { opacity: 0, y: 20, duration: 0.5 });
|
||||
}
|
||||
|
||||
function showCallbackResult(data) {
|
||||
callbackResult.innerHTML = `
|
||||
<div class="card bg-base-100 shadow-md">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title text-primary flex items-center gap-2">
|
||||
<i data-lucide="check-circle" class="h-6 w-6"></i>
|
||||
回调URL生成成功!
|
||||
</h2>
|
||||
|
||||
<div class="space-y-4 mt-4">
|
||||
<div>
|
||||
<div class="font-medium">您的配置Code</div>
|
||||
<div class="bg-base-200 p-2 rounded-md font-mono text-sm overflow-x-auto">${data.code}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="font-medium">回调URL</div>
|
||||
<div class="bg-base-200 p-2 rounded-md font-mono text-sm overflow-x-auto">${window.location.origin}${data.callbackUrl}</div>
|
||||
<button class="btn btn-sm btn-outline mt-1" id="copy-callback-url-btn">
|
||||
<i data-lucide="copy" class="h-4 w-4 mr-1"></i>复制回调URL
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-info mt-4">
|
||||
<i data-lucide="info" class="h-5 w-5"></i>
|
||||
<div>
|
||||
<div class="font-medium">下一步操作</div>
|
||||
<div class="text-sm">
|
||||
1. 复制上方回调URL到企业微信管理后台<br>
|
||||
2. 配置IP白名单(添加您的服务器IP)<br>
|
||||
3. 完成下方第二步配置
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// 绑定复制按钮事件
|
||||
document.getElementById('copy-callback-url-btn').addEventListener('click', () => {
|
||||
navigator.clipboard.writeText(window.location.origin + data.callbackUrl);
|
||||
showToast('回调URL已复制到剪贴板');
|
||||
});
|
||||
|
||||
callbackResult.classList.remove('hidden');
|
||||
lucide.createIcons();
|
||||
gsap.from(callbackResult.firstElementChild, { opacity: 0, y: 20, duration: 0.5 });
|
||||
}
|
||||
|
||||
function showFinalResult(data) {
|
||||
resultDiv.innerHTML = `
|
||||
<div class="card bg-base-100 shadow-md">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title text-success flex items-center gap-2">
|
||||
<i data-lucide="check-circle-2" class="h-6 w-6"></i>
|
||||
配置完成!
|
||||
</h2>
|
||||
|
||||
<div class="space-y-4 mt-4">
|
||||
<div>
|
||||
<div class="font-medium">配置Code</div>
|
||||
<div class="bg-base-200 p-2 rounded-md font-mono text-sm overflow-x-auto">${data.code}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="font-medium">通知API地址</div>
|
||||
<div class="bg-base-200 p-2 rounded-md font-mono text-sm overflow-x-auto">${window.location.origin}${data.apiUrl}</div>
|
||||
<button class="btn btn-sm btn-outline mt-1" id="copy-api-url-btn">
|
||||
<i data-lucide="copy" class="h-4 w-4 mr-1"></i>复制API地址
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<div class="font-medium">回调地址</div>
|
||||
<div class="bg-base-200 p-2 rounded-md font-mono text-sm overflow-x-auto">${window.location.origin}${data.callbackUrl}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-success mt-4">
|
||||
<i data-lucide="check" class="h-5 w-5"></i>
|
||||
<span>配置已完成!您现在可以使用API发送通知,也可以接收企业微信回调消息。</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// 绑定复制按钮事件
|
||||
document.getElementById('copy-api-url-btn').addEventListener('click', () => {
|
||||
navigator.clipboard.writeText(window.location.origin + data.apiUrl);
|
||||
showToast('API地址已复制到剪贴板');
|
||||
});
|
||||
|
||||
lucide.createIcons();
|
||||
gsap.from(resultDiv.firstElementChild, { opacity: 0, y: 20, duration: 0.5 });
|
||||
}
|
||||
function showResult(data) {
|
||||
resultDiv.innerHTML = `
|
||||
<div class="card bg-base-100 shadow-md">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title text-primary flex items-center gap-2">
|
||||
<i data-lucide="check-circle" class="h-6 w-6"></i>
|
||||
API生成成功!
|
||||
</h2>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mt-4">
|
||||
<div class="space-y-2">
|
||||
<div class="font-medium">您的唯一调用ID</div>
|
||||
<div class="bg-base-200 p-2 rounded-md font-mono text-sm overflow-x-auto">${data.code}</div>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<div class="font-medium">API地址</div>
|
||||
<div class="bg-base-200 p-2 rounded-md font-mono text-sm overflow-x-auto">${data.apiUrl}</div>
|
||||
<button class="btn btn-sm btn-outline mt-1" id="copy-new-api-btn">
|
||||
<i data-lucide="copy" class="h-4 w-4 mr-1"></i>复制
|
||||
</button>
|
||||
</div>
|
||||
${data.callbackUrl ? `
|
||||
<div class="space-y-2 md:col-span-2">
|
||||
<div class="font-medium">回调地址</div>
|
||||
<div class="bg-base-200 p-2 rounded-md font-mono text-sm overflow-x-auto">${data.callbackUrl}</div>
|
||||
<button class="btn btn-sm btn-outline mt-1" id="copy-callback-btn">
|
||||
<i data-lucide="copy" class="h-4 w-4 mr-1"></i>复制回调地址
|
||||
</button>
|
||||
<div class="text-sm text-base-content/60 mt-1">
|
||||
<i data-lucide="info" class="h-4 w-4 inline mr-1"></i>
|
||||
在企业微信管理后台配置此回调地址以接收消息
|
||||
</div>
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
|
||||
<div class="divider">API使用说明</div>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<h3 class="font-medium mb-2">请求方式</h3>
|
||||
<div class="bg-base-200 p-3 rounded-md">
|
||||
<code class="text-sm">POST ${data.apiUrl}</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="font-medium mb-2">请求头</h3>
|
||||
<div class="bg-base-200 p-3 rounded-md">
|
||||
<code class="text-sm">Content-Type: application/json</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="font-medium mb-2">请求参数</h3>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="table table-zebra w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>参数名</th>
|
||||
<th>类型</th>
|
||||
<th>必填</th>
|
||||
<th>说明</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="font-mono">title</td>
|
||||
<td>String</td>
|
||||
<td>否</td>
|
||||
<td>消息标题,可选</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="font-mono">content</td>
|
||||
<td>String</td>
|
||||
<td>是</td>
|
||||
<td>消息内容,必填</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="font-medium mb-2">请求示例</h3>
|
||||
<div class="bg-base-200 p-3 rounded-md">
|
||||
<pre class="text-sm whitespace-pre-wrap">curl -X POST "${data.apiUrl}" \\
|
||||
-H "Content-Type: application/json" \\
|
||||
-d '{
|
||||
"title": "服务器告警",
|
||||
"content": "CPU使用率超过90%,请及时处理!"
|
||||
}'</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="font-medium mb-2">返回示例</h3>
|
||||
<div class="bg-base-200 p-3 rounded-md">
|
||||
<pre class="text-sm whitespace-pre-wrap">{
|
||||
"message": "发送成功",
|
||||
"response": {
|
||||
"errcode": 0,
|
||||
"errmsg": "ok",
|
||||
"msgid": "MSGID1234567890"
|
||||
}
|
||||
}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-warning mt-4">
|
||||
<i data-lucide="alert-triangle" class="h-5 w-5"></i>
|
||||
<span>请妥善保存您的配置Code,它是调用API的唯一凭证。出于安全考虑,它只会显示一次!</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// 绑定复制按钮事件
|
||||
document.getElementById('copy-new-api-btn').addEventListener('click', () => {
|
||||
navigator.clipboard.writeText(data.apiUrl);
|
||||
showToast('API地址已复制到剪贴板');
|
||||
});
|
||||
|
||||
// 绑定回调地址复制按钮事件(如果存在)
|
||||
if (data.callbackUrl) {
|
||||
document.getElementById('copy-callback-btn').addEventListener('click', () => {
|
||||
navigator.clipboard.writeText(window.location.origin + data.callbackUrl);
|
||||
showToast('回调地址已复制到剪贴板');
|
||||
});
|
||||
}
|
||||
|
||||
// 创建图标
|
||||
lucide.createIcons();
|
||||
|
||||
gsap.from(resultDiv, { opacity: 0, y: 20, duration: 0.5 });
|
||||
}
|
||||
|
||||
// 显示提示消息
|
||||
function showToast(message) {
|
||||
const toast = document.createElement('div');
|
||||
toast.className = 'toast toast-top toast-center';
|
||||
toast.innerHTML = `
|
||||
<div class="alert alert-info">
|
||||
<span>${message}</span>
|
||||
</div>
|
||||
`;
|
||||
document.body.appendChild(toast);
|
||||
|
||||
gsap.fromTo(toast,
|
||||
{ opacity: 0, y: -20 },
|
||||
{ opacity: 1, y: 0, duration: 0.3 }
|
||||
);
|
||||
|
||||
setTimeout(() => {
|
||||
gsap.to(toast, {
|
||||
opacity: 0,
|
||||
y: -20,
|
||||
duration: 0.3,
|
||||
onComplete: () => toast.remove()
|
||||
});
|
||||
}, 3000);
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user