712 lines
23 KiB
HTML
712 lines
23 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="zh-CN">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>LawRisk 超级管理员控制台</title>
|
|
<style>
|
|
* {
|
|
box-sizing: border-box;
|
|
}
|
|
body {
|
|
margin: 0;
|
|
padding: 0;
|
|
font-family: "Microsoft YaHei", "PingFang SC", -apple-system, BlinkMacSystemFont, sans-serif;
|
|
background: linear-gradient(135deg, #eef2ff 0%, #e0e7ff 100%);
|
|
min-height: 100vh;
|
|
color: #1f2937;
|
|
}
|
|
.page {
|
|
max-width: 1400px;
|
|
margin: 0 auto;
|
|
padding: 32px 20px 60px;
|
|
}
|
|
.header {
|
|
background: #fff;
|
|
border-radius: 18px;
|
|
padding: 28px;
|
|
box-shadow: 0 20px 60px rgba(79, 70, 229, 0.12);
|
|
margin-bottom: 24px;
|
|
position: relative;
|
|
}
|
|
.title {
|
|
text-align: center;
|
|
}
|
|
.title h1 {
|
|
font-size: 30px;
|
|
margin: 0;
|
|
color: #111827;
|
|
}
|
|
.title p {
|
|
margin: 6px 0 0;
|
|
color: #4b5563;
|
|
font-size: 15px;
|
|
}
|
|
.user-chip {
|
|
position: absolute;
|
|
top: 28px;
|
|
right: 28px;
|
|
padding: 16px 20px;
|
|
border-radius: 16px;
|
|
border: 1px solid #e5e7eb;
|
|
background: rgba(255,255,255,0.9);
|
|
box-shadow: 0 10px 30px rgba(17, 24, 39, 0.08);
|
|
display: flex;
|
|
flex-direction: column;
|
|
min-width: 220px;
|
|
gap: 6px;
|
|
}
|
|
.user-name {
|
|
font-weight: 600;
|
|
font-size: 16px;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
.user-tag {
|
|
font-size: 12px;
|
|
padding: 2px 10px;
|
|
border-radius: 999px;
|
|
background: #eef2ff;
|
|
color: #4f46e5;
|
|
}
|
|
.user-meta {
|
|
font-size: 12px;
|
|
color: #6b7280;
|
|
}
|
|
.logout-btn {
|
|
align-self: flex-start;
|
|
background: #ef4444;
|
|
color: #fff;
|
|
border: none;
|
|
border-radius: 999px;
|
|
padding: 6px 14px;
|
|
font-size: 13px;
|
|
cursor: pointer;
|
|
}
|
|
.grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
|
|
gap: 20px;
|
|
}
|
|
.card {
|
|
background: #fff;
|
|
border-radius: 16px;
|
|
padding: 20px;
|
|
box-shadow: 0 12px 32px rgba(15, 23, 42, 0.08);
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 16px;
|
|
}
|
|
h2 {
|
|
margin: 0;
|
|
font-size: 20px;
|
|
color: #111827;
|
|
}
|
|
h3 {
|
|
margin: 0;
|
|
font-size: 16px;
|
|
color: #374151;
|
|
}
|
|
label {
|
|
font-size: 13px;
|
|
color: #6b7280;
|
|
}
|
|
input, select, textarea {
|
|
width: 100%;
|
|
border: 1px solid #d1d5db;
|
|
border-radius: 10px;
|
|
padding: 10px 12px;
|
|
font-size: 14px;
|
|
margin-top: 4px;
|
|
}
|
|
textarea {
|
|
resize: vertical;
|
|
}
|
|
button.primary {
|
|
background: linear-gradient(135deg, #4f46e5, #6366f1);
|
|
border: none;
|
|
color: #fff;
|
|
padding: 10px 18px;
|
|
border-radius: 999px;
|
|
cursor: pointer;
|
|
font-weight: 600;
|
|
margin-top: 8px;
|
|
}
|
|
table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
font-size: 13px;
|
|
}
|
|
th, td {
|
|
padding: 8px 6px;
|
|
border-bottom: 1px solid #f3f4f6;
|
|
text-align: left;
|
|
}
|
|
th {
|
|
font-weight: 600;
|
|
color: #4b5563;
|
|
}
|
|
.table-wrap {
|
|
max-height: 260px;
|
|
overflow: auto;
|
|
}
|
|
.pill {
|
|
padding: 2px 8px;
|
|
border-radius: 999px;
|
|
background: #eef2ff;
|
|
color: #4f46e5;
|
|
font-size: 12px;
|
|
}
|
|
.action {
|
|
border: none;
|
|
background: none;
|
|
color: #2563eb;
|
|
cursor: pointer;
|
|
padding: 0;
|
|
font-size: 13px;
|
|
}
|
|
.danger {
|
|
color: #dc2626;
|
|
}
|
|
.message {
|
|
margin-bottom: 16px;
|
|
padding: 12px 16px;
|
|
border-radius: 12px;
|
|
background: #eef2ff;
|
|
color: #312e81;
|
|
display: none;
|
|
}
|
|
.message.show {
|
|
display: block;
|
|
}
|
|
.template-meta {
|
|
font-size: 13px;
|
|
color: #4b5563;
|
|
line-height: 1.6;
|
|
}
|
|
.section {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 12px;
|
|
}
|
|
@media (max-width: 768px) {
|
|
.user-chip {
|
|
position: static;
|
|
margin-top: 16px;
|
|
}
|
|
.header {
|
|
padding-top: 48px;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="page">
|
|
<header class="header">
|
|
<div class="title">
|
|
<h1>LawRisk 法律风险提示系统</h1>
|
|
<p>超级管理员 · 用户、部门、主题与模板一站式管控</p>
|
|
</div>
|
|
<div class="user-chip" id="userChip">
|
|
<div class="user-name">
|
|
<span id="currentUserName">未登录</span>
|
|
<span class="user-tag" id="currentUserRole">role</span>
|
|
</div>
|
|
<div class="user-meta" id="currentUserDept">—</div>
|
|
<button class="logout-btn" id="logoutBtn">退出登录</button>
|
|
</div>
|
|
</header>
|
|
|
|
<div class="message" id="messageBar"></div>
|
|
|
|
<div class="grid">
|
|
<section class="card">
|
|
<h2>用户管理</h2>
|
|
<div class="section">
|
|
<h3>用户添加</h3>
|
|
<form id="userCreateForm">
|
|
<label>用户名
|
|
<input type="text" name="username" required placeholder="请输入登录账号">
|
|
</label>
|
|
<label>初始密码
|
|
<input type="password" name="password" required placeholder="设置登录密码">
|
|
</label>
|
|
<label>显示名称
|
|
<input type="text" name="display_name" placeholder="可选">
|
|
</label>
|
|
<label>绑定服务部门
|
|
<select name="service_department_id" id="userCreateDept">
|
|
<option value="">暂不绑定</option>
|
|
</select>
|
|
</label>
|
|
<button class="primary" type="submit">创建用户</button>
|
|
</form>
|
|
</div>
|
|
<div class="section">
|
|
<h3>密码 / 部门调整</h3>
|
|
<form id="userUpdateForm">
|
|
<label>选择用户
|
|
<select name="user_id" id="userUpdateSelect" required></select>
|
|
</label>
|
|
<label>新密码(留空表示不修改)
|
|
<input type="password" name="password" placeholder="可选">
|
|
</label>
|
|
<label>新的服务部门
|
|
<select name="service_department_id" id="userUpdateDept">
|
|
<option value="">保持不变 / 清除绑定</option>
|
|
</select>
|
|
</label>
|
|
<button class="primary" type="submit">保存调整</button>
|
|
</form>
|
|
</div>
|
|
<div class="section">
|
|
<h3>用户列表</h3>
|
|
<div class="table-wrap">
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>用户名</th>
|
|
<th>显示名</th>
|
|
<th>部门</th>
|
|
<th>角色</th>
|
|
<th>创建时间</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="userTableBody"></tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="card">
|
|
<h2>服务部门管理</h2>
|
|
<form id="deptCreateForm">
|
|
<label>部门名称
|
|
<input type="text" name="name" required>
|
|
</label>
|
|
<label>联系电话
|
|
<input type="text" name="phone" placeholder="座机或手机号">
|
|
</label>
|
|
<label>上级部门
|
|
<select name="parent_id" id="deptParentSelect">
|
|
<option value="">设为顶级</option>
|
|
</select>
|
|
</label>
|
|
<label>备注
|
|
<textarea name="description" rows="2" placeholder="可填写简要职责"></textarea>
|
|
</label>
|
|
<button class="primary" type="submit">新增服务部门</button>
|
|
</form>
|
|
<div class="table-wrap">
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>名称</th>
|
|
<th>上级</th>
|
|
<th>电话</th>
|
|
<th>操作</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="deptTableBody"></tbody>
|
|
</table>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="card">
|
|
<h2>主题列表管理</h2>
|
|
<form id="themeCreateForm">
|
|
<label>主题名称
|
|
<input type="text" name="name" required>
|
|
</label>
|
|
<button class="primary" type="submit">添加主题</button>
|
|
</form>
|
|
<div class="table-wrap">
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>主题</th>
|
|
<th>关联许可</th>
|
|
<th>覆盖区划</th>
|
|
<th>操作</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="themeTableBody"></tbody>
|
|
</table>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="card">
|
|
<h2>模板管理</h2>
|
|
<div class="template-meta" id="templateMeta">
|
|
模板未加载
|
|
</div>
|
|
<form id="templateForm">
|
|
<label>上传新的 Excel 模板
|
|
<input type="file" name="file" accept=".xlsx,.xls" required>
|
|
</label>
|
|
<button class="primary" type="submit">覆盖模板</button>
|
|
</form>
|
|
<button class="primary" type="button" id="downloadTemplateBtn">下载当前模板</button>
|
|
</section>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
const API_BASE = '/fs-ai-asistant/api/workflow/lawrisk';
|
|
const messageBar = document.getElementById('messageBar');
|
|
const userTableBody = document.getElementById('userTableBody');
|
|
const userCreateDept = document.getElementById('userCreateDept');
|
|
const userUpdateDept = document.getElementById('userUpdateDept');
|
|
const userUpdateSelect = document.getElementById('userUpdateSelect');
|
|
const deptParentSelect = document.getElementById('deptParentSelect');
|
|
const deptTableBody = document.getElementById('deptTableBody');
|
|
const themeTableBody = document.getElementById('themeTableBody');
|
|
const templateMetaBox = document.getElementById('templateMeta');
|
|
let state = {
|
|
users: [],
|
|
departments: [],
|
|
themes: [],
|
|
templateMeta: {}
|
|
};
|
|
|
|
function showMessage(text, type = 'info') {
|
|
if (!text) {
|
|
messageBar.classList.remove('show');
|
|
messageBar.textContent = '';
|
|
return;
|
|
}
|
|
messageBar.textContent = text;
|
|
messageBar.style.background = type === 'error' ? '#fee2e2' : '#eef2ff';
|
|
messageBar.style.color = type === 'error' ? '#b91c1c' : '#312e81';
|
|
messageBar.classList.add('show');
|
|
setTimeout(() => {
|
|
messageBar.classList.remove('show');
|
|
}, 4000);
|
|
}
|
|
|
|
async function fetchJSON(url, options = {}) {
|
|
const resp = await fetch(url, {
|
|
credentials: 'include',
|
|
headers: options.method === 'GET' || options.body instanceof FormData
|
|
? options.headers
|
|
: {'Content-Type': 'application/json', ...(options.headers || {})},
|
|
...options
|
|
});
|
|
let data = {};
|
|
try {
|
|
data = await resp.json();
|
|
} catch (_) {
|
|
data = {};
|
|
}
|
|
if (!resp.ok || data.success === false) {
|
|
throw new Error(data.message || '操作失败');
|
|
}
|
|
return data;
|
|
}
|
|
|
|
async function loadCurrentUser() {
|
|
try {
|
|
const resp = await fetchJSON('/auth/me', {method: 'GET'});
|
|
const user = resp.user || {};
|
|
document.getElementById('currentUserName').textContent = user.display_name || user.username || '未知管理员';
|
|
document.getElementById('currentUserRole').textContent = user.role || 'admin';
|
|
const dept = user.department;
|
|
document.getElementById('currentUserDept').textContent = dept ? `${dept.name || ''}${dept.code ? ' · ' + dept.code : ''}` : '未绑定部门';
|
|
} catch (err) {
|
|
showMessage(err.message, 'error');
|
|
}
|
|
}
|
|
|
|
function renderDepartmentOptions() {
|
|
const options = ['<option value="">暂不绑定</option>'];
|
|
const updateOptions = ['<option value="">保持不变 / 清除绑定</option>'];
|
|
const parentOptions = ['<option value="">设为顶级</option>'];
|
|
state.departments.forEach(dept => {
|
|
const label = `${dept.name}${dept.region_name ? ' · ' + dept.region_name : ''}`;
|
|
options.push(`<option value="${dept.id}">${label}</option>`);
|
|
updateOptions.push(`<option value="${dept.id}">${label}</option>`);
|
|
parentOptions.push(`<option value="${dept.id}">${label}</option>`);
|
|
});
|
|
userCreateDept.innerHTML = options.join('');
|
|
userUpdateDept.innerHTML = updateOptions.join('');
|
|
deptParentSelect.innerHTML = parentOptions.join('');
|
|
}
|
|
|
|
function renderUserSelect() {
|
|
const opts = state.users.map(user => `<option value="${user.id}">${user.username} (${user.display_name || '未命名'})</option>`);
|
|
userUpdateSelect.innerHTML = opts.join('');
|
|
}
|
|
|
|
function renderUserTable() {
|
|
userTableBody.innerHTML = state.users.map(user => {
|
|
const dept = user.department ? user.department.name : '—';
|
|
return `<tr>
|
|
<td>${user.username}</td>
|
|
<td>${user.display_name || '—'}</td>
|
|
<td>${dept}</td>
|
|
<td><span class="pill">${user.role}</span></td>
|
|
<td>${user.created_at || '—'}</td>
|
|
</tr>`;
|
|
}).join('');
|
|
}
|
|
|
|
function renderDeptTable() {
|
|
deptTableBody.innerHTML = state.departments.map(dept => `
|
|
<tr>
|
|
<td>${dept.name}</td>
|
|
<td>${dept.parent_name || '—'}</td>
|
|
<td>${dept.phone || '—'}</td>
|
|
<td>
|
|
<button class="action danger" data-id="${dept.id}" data-action="delete-dept">删除</button>
|
|
</td>
|
|
</tr>
|
|
`).join('');
|
|
}
|
|
|
|
function renderThemeTable() {
|
|
themeTableBody.innerHTML = state.themes.map(theme => `
|
|
<tr>
|
|
<td>${theme.name}</td>
|
|
<td>${theme.permit_count}</td>
|
|
<td>${theme.region_count}</td>
|
|
<td>
|
|
<button class="action" data-id="${theme.id}" data-name="${theme.name}" data-action="rename-theme">重命名</button>
|
|
|
|
|
<button class="action danger" data-id="${theme.id}" data-name="${theme.name}" data-action="delete-theme">删除</button>
|
|
</td>
|
|
</tr>
|
|
`).join('');
|
|
}
|
|
|
|
function renderTemplateMeta() {
|
|
const meta = state.templateMeta;
|
|
if (!meta || !meta.exists) {
|
|
templateMetaBox.textContent = '暂未上传模板,请尽快上传标准模板文件。';
|
|
return;
|
|
}
|
|
templateMetaBox.innerHTML = `
|
|
<div>文件名:${meta.filename}</div>
|
|
<div>大小:${(meta.filesize / 1024).toFixed(1)} KB</div>
|
|
<div>更新时间:${meta.updated_at || '—'}</div>
|
|
<div>上传人:${meta.uploaded_by || '—'}</div>
|
|
`;
|
|
}
|
|
|
|
async function refreshUsers() {
|
|
const data = await fetchJSON(`${API_BASE}/admin/users`, {method: 'GET'});
|
|
state.users = data.data.users || [];
|
|
renderUserSelect();
|
|
renderUserTable();
|
|
}
|
|
|
|
async function refreshDepartments() {
|
|
const data = await fetchJSON(`${API_BASE}/admin/service-departments`, {method: 'GET'});
|
|
state.departments = data.data.departments || [];
|
|
renderDepartmentOptions();
|
|
renderDeptTable();
|
|
}
|
|
|
|
async function refreshThemes() {
|
|
const data = await fetchJSON(`${API_BASE}/admin/themes/catalog`, {method: 'GET'});
|
|
state.themes = data.data.themes || [];
|
|
renderThemeTable();
|
|
}
|
|
|
|
async function refreshTemplateMeta() {
|
|
const data = await fetchJSON(`${API_BASE}/admin/templates/permit`, {method: 'GET'});
|
|
state.templateMeta = data.data || {};
|
|
renderTemplateMeta();
|
|
}
|
|
|
|
document.getElementById('logoutBtn').addEventListener('click', async () => {
|
|
try {
|
|
await fetchJSON('/auth/logout', {method: 'POST', headers: {'Content-Type': 'application/json'}});
|
|
window.location.href = '/fs-ai-asistant/lawrisk/login';
|
|
} catch (err) {
|
|
showMessage(err.message, 'error');
|
|
}
|
|
});
|
|
|
|
document.getElementById('userCreateForm').addEventListener('submit', async (evt) => {
|
|
evt.preventDefault();
|
|
const form = evt.target;
|
|
const payload = {
|
|
username: form.username.value.trim(),
|
|
password: form.password.value.trim(),
|
|
display_name: form.display_name.value.trim(),
|
|
service_department_id: form.service_department_id.value || null
|
|
};
|
|
try {
|
|
await fetchJSON(`${API_BASE}/admin/users`, {
|
|
method: 'POST',
|
|
body: JSON.stringify(payload)
|
|
});
|
|
showMessage('用户创建成功');
|
|
form.reset();
|
|
await refreshUsers();
|
|
} catch (err) {
|
|
showMessage(err.message, 'error');
|
|
}
|
|
});
|
|
|
|
document.getElementById('userUpdateForm').addEventListener('submit', async (evt) => {
|
|
evt.preventDefault();
|
|
const form = evt.target;
|
|
const userId = form.user_id.value;
|
|
if (!userId) {
|
|
showMessage('请选择要调整的用户', 'error');
|
|
return;
|
|
}
|
|
const payload = {};
|
|
if (form.password.value) {
|
|
payload.password = form.password.value;
|
|
}
|
|
payload.service_department_id = form.service_department_id.value || null;
|
|
try {
|
|
await fetchJSON(`${API_BASE}/admin/users/${userId}`, {
|
|
method: 'PATCH',
|
|
body: JSON.stringify(payload)
|
|
});
|
|
showMessage('用户信息已更新');
|
|
form.password.value = '';
|
|
await refreshUsers();
|
|
} catch (err) {
|
|
showMessage(err.message, 'error');
|
|
}
|
|
});
|
|
|
|
document.getElementById('deptCreateForm').addEventListener('submit', async (evt) => {
|
|
evt.preventDefault();
|
|
const form = evt.target;
|
|
const payload = {
|
|
name: form.name.value.trim(),
|
|
phone: form.phone.value.trim(),
|
|
parent_id: form.parent_id.value || null,
|
|
description: form.description.value.trim()
|
|
};
|
|
try {
|
|
await fetchJSON(`${API_BASE}/admin/service-departments`, {
|
|
method: 'POST',
|
|
body: JSON.stringify(payload)
|
|
});
|
|
showMessage('服务部门已创建');
|
|
form.reset();
|
|
await refreshDepartments();
|
|
} catch (err) {
|
|
showMessage(err.message, 'error');
|
|
}
|
|
});
|
|
|
|
deptTableBody.addEventListener('click', async (evt) => {
|
|
const target = evt.target;
|
|
if (!target.dataset || target.dataset.action !== 'delete-dept') {
|
|
return;
|
|
}
|
|
const deptId = target.dataset.id;
|
|
if (!deptId) return;
|
|
if (!confirm('删除前请确认没有账号绑定该部门,确定要删除吗?')) {
|
|
return;
|
|
}
|
|
try {
|
|
await fetchJSON(`${API_BASE}/admin/service-departments/${deptId}`, {method: 'DELETE'});
|
|
showMessage('服务部门已删除');
|
|
await refreshDepartments();
|
|
await refreshUsers();
|
|
} catch (err) {
|
|
showMessage(err.message, 'error');
|
|
}
|
|
});
|
|
|
|
document.getElementById('themeCreateForm').addEventListener('submit', async (evt) => {
|
|
evt.preventDefault();
|
|
const form = evt.target;
|
|
const payload = {name: form.name.value.trim()};
|
|
try {
|
|
await fetchJSON(`${API_BASE}/admin/themes/catalog`, {
|
|
method: 'POST',
|
|
body: JSON.stringify(payload)
|
|
});
|
|
showMessage('主题添加成功');
|
|
form.reset();
|
|
await refreshThemes();
|
|
} catch (err) {
|
|
showMessage(err.message, 'error');
|
|
}
|
|
});
|
|
|
|
themeTableBody.addEventListener('click', async (evt) => {
|
|
const target = evt.target;
|
|
if (!target.dataset) return;
|
|
const action = target.dataset.action;
|
|
const themeId = target.dataset.id;
|
|
const themeName = target.dataset.name;
|
|
if (!themeId || !action) return;
|
|
if (action === 'rename-theme') {
|
|
const value = prompt('请输入新的主题名称', themeName || '');
|
|
if (!value) return;
|
|
try {
|
|
await fetchJSON(`${API_BASE}/admin/themes/catalog/${themeId}`, {
|
|
method: 'PATCH',
|
|
body: JSON.stringify({name: value.trim()})
|
|
});
|
|
showMessage('主题名称已更新');
|
|
await refreshThemes();
|
|
} catch (err) {
|
|
showMessage(err.message, 'error');
|
|
}
|
|
} else if (action === 'delete-theme') {
|
|
if (!confirm(`确定删除主题「${themeName}」及其关联?`)) return;
|
|
try {
|
|
await fetchJSON(`${API_BASE}/admin/themes/catalog/${themeId}`, {method: 'DELETE'});
|
|
showMessage('主题已删除');
|
|
await refreshThemes();
|
|
} catch (err) {
|
|
showMessage(err.message, 'error');
|
|
}
|
|
}
|
|
});
|
|
|
|
document.getElementById('templateForm').addEventListener('submit', async (evt) => {
|
|
evt.preventDefault();
|
|
const form = evt.target;
|
|
const fileInput = form.file;
|
|
if (!fileInput.files.length) {
|
|
showMessage('请选择要上传的模板文件', 'error');
|
|
return;
|
|
}
|
|
const formData = new FormData();
|
|
formData.append('file', fileInput.files[0]);
|
|
try {
|
|
await fetchJSON(`${API_BASE}/admin/templates/permit`, {
|
|
method: 'POST',
|
|
body: formData,
|
|
headers: {}
|
|
});
|
|
showMessage('模板已更新');
|
|
form.reset();
|
|
await refreshTemplateMeta();
|
|
} catch (err) {
|
|
showMessage(err.message, 'error');
|
|
}
|
|
});
|
|
|
|
document.getElementById('downloadTemplateBtn').addEventListener('click', () => {
|
|
window.open(`${API_BASE}/admin/permit-import/template`, '_blank');
|
|
});
|
|
|
|
async function bootstrap() {
|
|
await loadCurrentUser();
|
|
await Promise.all([
|
|
refreshUsers(),
|
|
refreshDepartments(),
|
|
refreshThemes(),
|
|
refreshTemplateMeta()
|
|
]);
|
|
}
|
|
|
|
bootstrap().catch(err => showMessage(err.message, 'error'));
|
|
</script>
|
|
</body>
|
|
</html>
|