up
This commit is contained in:
@@ -2,10 +2,17 @@ layui.use(['layer'], function(){
|
||||
var layer = layui.layer;
|
||||
var $ = layui.$;
|
||||
|
||||
// 初始化图表
|
||||
var registerTrendChart = echarts.init(document.getElementById('register-trend'));
|
||||
var deviceTypesChart = echarts.init(document.getElementById('device-types'));
|
||||
|
||||
// 加载统计数据
|
||||
function loadStats() {
|
||||
fetch('/api/dashboard/stats', {
|
||||
credentials: 'include'
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'Authorization': 'Bearer ' + localStorage.getItem('token')
|
||||
}
|
||||
})
|
||||
.then(response => {
|
||||
if (response.status === 401) {
|
||||
@@ -21,21 +28,157 @@ layui.use(['layer'], function(){
|
||||
}
|
||||
|
||||
// 更新统计数据
|
||||
$('#total-devices').text(result.data.total_devices);
|
||||
$('#total-licenses').text(result.data.total_licenses);
|
||||
$('#today-new').text(result.data.today_new);
|
||||
$('#online-devices').text(result.data.online_devices);
|
||||
$('#active-devices').text(result.data.active_devices);
|
||||
$('#expired-devices').text(result.data.expired_devices);
|
||||
updateStats(result.data);
|
||||
// 更新图表
|
||||
updateCharts(result.data);
|
||||
// 更新系统状态
|
||||
updateSystemStatus(result.data);
|
||||
})
|
||||
.catch(error => {
|
||||
layer.msg('加载统计数据失败:' + error.message);
|
||||
});
|
||||
}
|
||||
|
||||
// 更新统计数据
|
||||
function updateStats(data) {
|
||||
$('#total-devices').text(data.total_devices);
|
||||
$('#total-licenses').text(data.total_licenses);
|
||||
$('#online-devices').text(data.online_devices);
|
||||
$('#expired-devices').text(data.expired_devices);
|
||||
$('#today-new').text(data.today_new);
|
||||
$('#unused-licenses').text(data.unused_licenses);
|
||||
|
||||
// 计算比率
|
||||
var activeRate = data.total_devices > 0 ?
|
||||
((data.online_devices / data.total_devices) * 100).toFixed(1) : 0;
|
||||
var expiredRate = data.total_devices > 0 ?
|
||||
((data.expired_devices / data.total_devices) * 100).toFixed(1) : 0;
|
||||
|
||||
$('#active-rate').text(activeRate + '%');
|
||||
$('#expired-rate').text(expiredRate + '%');
|
||||
}
|
||||
|
||||
// 更新图表
|
||||
function updateCharts(data) {
|
||||
// 设备注册趋势图
|
||||
var trendOption = {
|
||||
title: {
|
||||
text: '最近7天设备注册趋势'
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis'
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: data.trend_dates
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value'
|
||||
},
|
||||
series: [{
|
||||
name: '新增设备',
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
data: data.trend_counts,
|
||||
itemStyle: {
|
||||
color: '#009688'
|
||||
},
|
||||
areaStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
|
||||
offset: 0,
|
||||
color: 'rgba(0, 150, 136, 0.3)'
|
||||
}, {
|
||||
offset: 1,
|
||||
color: 'rgba(0, 150, 136, 0.1)'
|
||||
}])
|
||||
}
|
||||
}]
|
||||
};
|
||||
registerTrendChart.setOption(trendOption);
|
||||
|
||||
// 设备类型分布图
|
||||
var typesOption = {
|
||||
title: {
|
||||
text: '设备类型分布'
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
formatter: '{b}: {c} ({d}%)'
|
||||
},
|
||||
series: [{
|
||||
type: 'pie',
|
||||
radius: ['50%', '70%'],
|
||||
avoidLabelOverlap: false,
|
||||
itemStyle: {
|
||||
borderRadius: 10,
|
||||
borderColor: '#fff',
|
||||
borderWidth: 2
|
||||
},
|
||||
label: {
|
||||
show: false,
|
||||
position: 'center'
|
||||
},
|
||||
emphasis: {
|
||||
label: {
|
||||
show: true,
|
||||
fontSize: '20',
|
||||
fontWeight: 'bold'
|
||||
}
|
||||
},
|
||||
labelLine: {
|
||||
show: false
|
||||
},
|
||||
data: data.device_types.map(item => ({
|
||||
name: item.type,
|
||||
value: item.count
|
||||
}))
|
||||
}]
|
||||
};
|
||||
deviceTypesChart.setOption(typesOption);
|
||||
}
|
||||
|
||||
// 更新系统状态
|
||||
function updateSystemStatus(data) {
|
||||
$('#cpu-usage').text(data.cpu_usage + '%');
|
||||
$('#memory-usage').text(data.memory_usage + '%');
|
||||
$('#disk-usage').text(data.disk_usage + '%');
|
||||
$('#uptime').text(formatDuration(data.uptime));
|
||||
$('#load-avg').text(data.load_avg.join(' '));
|
||||
$('#network-traffic').text(formatBytes(data.network_traffic) + '/s');
|
||||
$('#online-users').text(data.online_users);
|
||||
$('#last-update').text(new Date().toLocaleString());
|
||||
}
|
||||
|
||||
// 格式化时间
|
||||
function formatDuration(seconds) {
|
||||
var days = Math.floor(seconds / 86400);
|
||||
var hours = Math.floor((seconds % 86400) / 3600);
|
||||
var minutes = Math.floor((seconds % 3600) / 60);
|
||||
var parts = [];
|
||||
if (days > 0) parts.push(days + '天');
|
||||
if (hours > 0) parts.push(hours + '小时');
|
||||
if (minutes > 0) parts.push(minutes + '分钟');
|
||||
return parts.join(' ') || '0分钟';
|
||||
}
|
||||
|
||||
// 格式化字节大小
|
||||
function formatBytes(bytes) {
|
||||
if (bytes === 0) return '0 B';
|
||||
var k = 1024;
|
||||
var sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||||
var i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
return (bytes / Math.pow(k, i)).toFixed(2) + ' ' + sizes[i];
|
||||
}
|
||||
|
||||
// 初始加载
|
||||
loadStats();
|
||||
|
||||
// 定时刷新(每30秒)
|
||||
setInterval(loadStats, 30000);
|
||||
|
||||
// 窗口大小改变时重绘图表
|
||||
window.onresize = function() {
|
||||
registerTrendChart.resize();
|
||||
deviceTypesChart.resize();
|
||||
};
|
||||
});
|
@@ -1,36 +1,24 @@
|
||||
layui.use(['table', 'form', 'layer'], function(){
|
||||
layui.use(['table', 'form', 'layer', 'laytpl'], function(){
|
||||
var table = layui.table;
|
||||
var form = layui.form;
|
||||
var layer = layui.layer;
|
||||
var laytpl = layui.laytpl;
|
||||
var $ = layui.$;
|
||||
|
||||
// 加载设备型号列表
|
||||
fetch('/api/devices/models', {
|
||||
credentials: 'include'
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(result => {
|
||||
if(result.data) {
|
||||
var options = '<option value="">全部</option>';
|
||||
result.data.forEach(function(model) {
|
||||
options += '<option value="' + model.model_name + '">' + model.model_name + '</option>';
|
||||
});
|
||||
$('select[name=device_model]').html(options);
|
||||
form.render('select');
|
||||
}
|
||||
});
|
||||
|
||||
// 初始化表格
|
||||
table.render({
|
||||
elem: '#device-table',
|
||||
url: '/api/devices/registered', // 已注册设备列表接口
|
||||
headers: undefined,
|
||||
url: '/api/devices/registered',
|
||||
headers: {
|
||||
'Authorization': 'Bearer ' + localStorage.getItem('token')
|
||||
},
|
||||
toolbar: '#tableToolbar',
|
||||
defaultToolbar: ['filter', 'exports', 'print'],
|
||||
cols: [[
|
||||
{type: 'checkbox'},
|
||||
{field: 'uid', title: '设备UID', width: 180},
|
||||
{field: 'device_model', title: '设备型号', width: 120},
|
||||
{field: 'device_type', title: '设备类型', width: 120},
|
||||
{field: 'license_code', title: '授权码', width: 180},
|
||||
{field: 'license_type', title: '授权类型', width: 100, templet: function(d){
|
||||
var types = {
|
||||
@@ -40,15 +28,17 @@ layui.use(['table', 'form', 'layer'], function(){
|
||||
};
|
||||
return types[d.license_type] || '-';
|
||||
}},
|
||||
{field: 'expire_time', title: '过期时间', width: 160, templet: function(d){
|
||||
return d.expire_time ? new Date(d.expire_time).toLocaleString() : '-';
|
||||
}},
|
||||
{field: 'start_count', title: '启动次数', width: 100},
|
||||
{field: 'status', title: '状态', width: 100, templet: function(d){
|
||||
if(d.status === 'active') return '<span class="layui-badge layui-bg-green">正常</span>';
|
||||
if(d.status === 'expired') return '<span class="layui-badge layui-bg-orange">已过期</span>';
|
||||
return '<span class="layui-badge layui-bg-gray">未激活</span>';
|
||||
}},
|
||||
{field: 'start_count', title: '启动次数', width: 100, templet: function(d){
|
||||
if(d.license_type === 'count') {
|
||||
return d.start_count + ' / ' + d.max_uses;
|
||||
}
|
||||
return d.start_count || 0;
|
||||
}},
|
||||
{field: 'last_active_at', title: '最后活跃', width: 160, templet: function(d){
|
||||
return d.last_active_at ? new Date(d.last_active_at).toLocaleString() : '-';
|
||||
}},
|
||||
@@ -79,11 +69,14 @@ layui.use(['table', 'form', 'layer'], function(){
|
||||
|
||||
switch(obj.event){
|
||||
case 'view':
|
||||
layer.open({
|
||||
type: 1,
|
||||
title: '设备详情',
|
||||
area: ['600px', '500px'],
|
||||
content: laytpl($('#deviceDetailTpl').html()).render(data)
|
||||
// 使用laytpl渲染设备详情
|
||||
laytpl(document.getElementById('deviceDetailTpl').innerHTML).render(data, function(html){
|
||||
layer.open({
|
||||
type: 1,
|
||||
title: '设备详情',
|
||||
area: ['600px', '600px'],
|
||||
content: html
|
||||
});
|
||||
});
|
||||
break;
|
||||
case 'bind':
|
||||
@@ -161,42 +154,4 @@ layui.use(['table', 'form', 'layer'], function(){
|
||||
});
|
||||
return false;
|
||||
});
|
||||
|
||||
// 导出设备列表
|
||||
$('#export-devices').on('click', function(){
|
||||
var checkStatus = table.checkStatus('device-table');
|
||||
var data = checkStatus.data;
|
||||
if(data.length === 0){
|
||||
layer.msg('请选择要导出的设备');
|
||||
return;
|
||||
}
|
||||
|
||||
// 创建CSV内容
|
||||
var csv = '设备UID,设备型号,授权码,授权类型,过期时间,启动次数,状态,最后活跃\n';
|
||||
data.forEach(function(item){
|
||||
csv += [
|
||||
item.uid,
|
||||
item.device_model,
|
||||
item.license_code || '',
|
||||
item.license_type || '',
|
||||
item.expire_time ? new Date(item.expire_time).toLocaleString() : '',
|
||||
item.start_count,
|
||||
item.status,
|
||||
item.last_active_at ? new Date(item.last_active_at).toLocaleString() : ''
|
||||
].join(',') + '\n';
|
||||
});
|
||||
|
||||
// 下载文件
|
||||
var blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
|
||||
var link = document.createElement("a");
|
||||
if (link.download !== undefined) {
|
||||
var url = URL.createObjectURL(blob);
|
||||
link.setAttribute("href", url);
|
||||
link.setAttribute("download", "devices.csv");
|
||||
link.style.visibility = 'hidden';
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
}
|
||||
});
|
||||
});
|
@@ -8,39 +8,22 @@ layui.use(['table', 'form', 'layer'], function(){
|
||||
table.render({
|
||||
elem: '#device-table',
|
||||
url: '/api/devices/models',
|
||||
headers: undefined,
|
||||
headers: {
|
||||
'Authorization': 'Bearer ' + localStorage.getItem('token')
|
||||
},
|
||||
toolbar: '#tableToolbar',
|
||||
defaultToolbar: ['filter', 'exports', 'print'],
|
||||
cols: [[
|
||||
{type: 'checkbox'},
|
||||
{field: 'ID', hide: true},
|
||||
{field: 'model_name', title: '设备型号', width: 180},
|
||||
{field: 'device_type', title: '设备类型', width: 120, templet: function(d){
|
||||
var types = {
|
||||
'software': '软件',
|
||||
'website': '网站',
|
||||
'embedded': '嵌入式设备',
|
||||
'mcu': '单片机设备'
|
||||
};
|
||||
return types[d.device_type] || d.device_type;
|
||||
}},
|
||||
{field: 'device_type', title: '设备类型', width: 120},
|
||||
{field: 'company', title: '所属公司', width: 150},
|
||||
{field: 'remark', title: '备注说明'},
|
||||
{field: 'device_count', title: '设备数量', width: 100},
|
||||
{field: 'status', title: '状态', width: 100, templet: function(d){
|
||||
if(d.status === 'active') return '<span class="layui-badge layui-bg-green">已激活</span>';
|
||||
if(d.status === 'expired') return '<span class="layui-badge layui-bg-orange">已过期</span>';
|
||||
return '<span class="layui-badge layui-bg-gray">未激活</span>';
|
||||
}},
|
||||
{field: 'CreatedAt', title: '创建时间', width: 180, templet: function(d){
|
||||
return d.CreatedAt ? new Date(d.CreatedAt).toLocaleString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
hour12: false
|
||||
}) : '';
|
||||
if(d.status === 'active') return '<span class="layui-badge layui-bg-green">启用</span>';
|
||||
return '<span class="layui-badge layui-bg-gray">禁用</span>';
|
||||
}},
|
||||
{fixed: 'right', title: '操作', toolbar: '#tableRowBar', width: 180}
|
||||
]],
|
||||
@@ -69,7 +52,7 @@ layui.use(['table', 'form', 'layer'], function(){
|
||||
return;
|
||||
}
|
||||
layer.confirm('确定删除选中的设备型号吗?', function(index){
|
||||
var ids = data.map(item => item.id);
|
||||
var ids = data.map(item => item.ID);
|
||||
fetch('/api/devices/models/batch', {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
@@ -108,22 +91,21 @@ layui.use(['table', 'form', 'layer'], function(){
|
||||
area: ['500px', '400px'],
|
||||
content: $('#deviceFormTpl').html(),
|
||||
success: function(){
|
||||
form.val('deviceForm', data);
|
||||
form.val('deviceForm', {
|
||||
id: data.ID,
|
||||
model_name: data.model_name,
|
||||
device_type: data.device_type,
|
||||
company: data.company,
|
||||
status: data.status || 'active',
|
||||
remark: data.remark
|
||||
});
|
||||
form.render();
|
||||
}
|
||||
});
|
||||
break;
|
||||
case 'files':
|
||||
layer.open({
|
||||
type: 2,
|
||||
title: '设备文件管理',
|
||||
area: ['900px', '600px'],
|
||||
content: '/admin/device-files?model=' + encodeURIComponent(data.deviceModel)
|
||||
});
|
||||
break;
|
||||
case 'del':
|
||||
layer.confirm('确定删除该设备型号吗?', function(index){
|
||||
fetch('/api/devices/models/' + data.id, {
|
||||
fetch('/api/devices/models/' + data.ID, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Authorization': 'Bearer ' + localStorage.getItem('token')
|
||||
@@ -150,7 +132,10 @@ layui.use(['table', 'form', 'layer'], function(){
|
||||
// 搜索表单提交
|
||||
form.on('submit(search)', function(data){
|
||||
table.reload('device-table', {
|
||||
where: data.field
|
||||
where: data.field,
|
||||
page: {
|
||||
curr: 1
|
||||
}
|
||||
});
|
||||
return false;
|
||||
});
|
||||
@@ -163,10 +148,6 @@ layui.use(['table', 'form', 'layer'], function(){
|
||||
area: ['500px', '400px'],
|
||||
content: $('#deviceFormTpl').html(),
|
||||
success: function(){
|
||||
// 初始化设备类型选择
|
||||
form.val('deviceForm', {
|
||||
'device_type': 'software' // 设置默认值
|
||||
});
|
||||
form.render();
|
||||
}
|
||||
});
|
||||
@@ -174,34 +155,27 @@ layui.use(['table', 'form', 'layer'], function(){
|
||||
|
||||
// 设备型号表单提交
|
||||
form.on('submit(deviceSubmit)', function(data){
|
||||
// 如果是编辑模式,确保 id 是数字类型
|
||||
if(data.field.id) {
|
||||
data.field.id = parseInt(data.field.id);
|
||||
}
|
||||
|
||||
// 构造提交数据,使用下划线命名
|
||||
// 构造请求数据
|
||||
const submitData = {
|
||||
model_name: data.field.model_name,
|
||||
device_type: data.field.device_type,
|
||||
company: data.field.company,
|
||||
remark: data.field.remark,
|
||||
status: 'active'
|
||||
status: data.field.status,
|
||||
remark: data.field.remark
|
||||
};
|
||||
|
||||
// 如果是编辑模式,添加 ID
|
||||
if(data.field.id) {
|
||||
submitData.id = data.field.id;
|
||||
}
|
||||
|
||||
var url = data.field.id ? '/api/devices/models/' + data.field.id : '/api/devices/models';
|
||||
var method = data.field.id ? 'PUT' : 'POST';
|
||||
// 确定请求URL和方法
|
||||
const url = data.field.id ?
|
||||
`/api/devices/models/${data.field.id}` :
|
||||
'/api/devices/models';
|
||||
const method = data.field.id ? 'PUT' : 'POST';
|
||||
|
||||
fetch(url, {
|
||||
method: method,
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': 'Bearer ' + localStorage.getItem('token')
|
||||
},
|
||||
credentials: 'include',
|
||||
body: JSON.stringify(submitData)
|
||||
})
|
||||
.then(response => response.json())
|
||||
|
@@ -17,7 +17,9 @@ layui.use(['table', 'form', 'layer'], function(){
|
||||
table.render({
|
||||
elem: '#license-table',
|
||||
url: '/api/licenses',
|
||||
headers: undefined,
|
||||
headers: {
|
||||
'Authorization': 'Bearer ' + localStorage.getItem('token')
|
||||
},
|
||||
toolbar: '#tableToolbar',
|
||||
defaultToolbar: ['filter', 'exports', 'print'],
|
||||
cols: [[
|
||||
@@ -215,7 +217,7 @@ layui.use(['table', 'form', 'layer'], function(){
|
||||
type: 2,
|
||||
title: '使用日志',
|
||||
area: ['800px', '600px'],
|
||||
content: '/admin/license-logs?id=' + data.id
|
||||
content: '/admin/license-logs?id=' + data.id + '&token=' + localStorage.getItem('token')
|
||||
});
|
||||
break;
|
||||
case 'revoke':
|
||||
@@ -316,7 +318,8 @@ layui.use(['table', 'form', 'layer'], function(){
|
||||
fetch('/api/licenses', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': 'Bearer ' + localStorage.getItem('token')
|
||||
},
|
||||
credentials: 'include',
|
||||
body: JSON.stringify(submitData)
|
||||
|
@@ -3,18 +3,8 @@ layui.use(['form', 'layer'], function(){
|
||||
var layer = layui.layer;
|
||||
var $ = layui.$;
|
||||
|
||||
// 检查用户是否已登录
|
||||
function checkIfLoggedIn() {
|
||||
const token = localStorage.getItem('token');
|
||||
if (token) {
|
||||
window.location.href = '/';
|
||||
}
|
||||
}
|
||||
|
||||
// 页面加载时检查登录状态
|
||||
checkIfLoggedIn();
|
||||
|
||||
// 加载验证码
|
||||
var captchaId = '';
|
||||
function loadCaptcha() {
|
||||
fetch('/api/captcha')
|
||||
.then(response => {
|
||||
@@ -32,6 +22,7 @@ layui.use(['form', 'layer'], function(){
|
||||
if (data.imageBase64) {
|
||||
$('#captchaImg').attr('src', 'data:image/png;base64,' + data.imageBase64);
|
||||
$('input[name=captchaId]').val(data.captchaId);
|
||||
captchaId = data.captchaId;
|
||||
} else {
|
||||
throw new Error('验证码图片数据无效');
|
||||
}
|
||||
@@ -42,65 +33,41 @@ layui.use(['form', 'layer'], function(){
|
||||
$('#captchaImg').attr('src', '/static/images/captcha-error.png');
|
||||
});
|
||||
}
|
||||
|
||||
// 页面加载时获取验证码
|
||||
loadCaptcha();
|
||||
|
||||
// 点击验证码图片刷新
|
||||
$('#captchaImg').on('click', function() {
|
||||
loadCaptcha();
|
||||
});
|
||||
// 点击验证码刷新
|
||||
$('#captchaImg').on('click', loadCaptcha);
|
||||
|
||||
// 登录表单提交
|
||||
// 表单提交
|
||||
form.on('submit(login)', function(data){
|
||||
var field = data.field;
|
||||
|
||||
// 添加验证码验证
|
||||
if (!field.captcha) {
|
||||
layer.msg('请输入验证码');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!field.captchaId) {
|
||||
layer.msg('验证码已失效,请刷新');
|
||||
loadCaptcha();
|
||||
return false;
|
||||
}
|
||||
data.field.captchaId = captchaId;
|
||||
|
||||
fetch('/api/login', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
username: field.username,
|
||||
password: field.password,
|
||||
captcha: field.captcha,
|
||||
captchaId: field.captchaId
|
||||
})
|
||||
})
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error('Network response was not ok');
|
||||
}
|
||||
return response.json();
|
||||
body: JSON.stringify(data.field)
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(result => {
|
||||
if (result.error) {
|
||||
if(result.error) {
|
||||
layer.msg(result.error);
|
||||
loadCaptcha(); // 刷新验证码
|
||||
loadCaptcha();
|
||||
return;
|
||||
}
|
||||
// 确保 token 被正确设置
|
||||
document.cookie = `token=${result.token}; path=/; secure; samesite=strict`;
|
||||
localStorage.setItem('token', result.token);
|
||||
// 保存token到localStorage
|
||||
localStorage.setItem('token', result.data.token);
|
||||
localStorage.setItem('user', JSON.stringify(result.data.user));
|
||||
|
||||
// 跳转到首页
|
||||
window.location.href = '/';
|
||||
})
|
||||
.catch(error => {
|
||||
layer.msg('登录失败:' + error.message);
|
||||
loadCaptcha(); // 刷新验证码
|
||||
loadCaptcha();
|
||||
});
|
||||
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
|
@@ -24,28 +24,42 @@ layui.use(["element", "layer"], function () {
|
||||
}
|
||||
}
|
||||
|
||||
// 添加通用的 fetch 封装,自动处理认证
|
||||
// 添加请求拦截器
|
||||
function addAuthHeader(url) {
|
||||
const token = localStorage.getItem('token');
|
||||
if (!token) {
|
||||
window.location.href = '/login';
|
||||
return false;
|
||||
}
|
||||
|
||||
window.authFetch = function (url, options = {}) {
|
||||
return fetch(url, {
|
||||
return {
|
||||
'Authorization': 'Bearer ' + token,
|
||||
'Content-Type': 'application/json'
|
||||
};
|
||||
}
|
||||
|
||||
// 封装fetch请求
|
||||
async function request(url, options = {}) {
|
||||
const headers = addAuthHeader(url);
|
||||
if (!headers) return;
|
||||
|
||||
const response = await fetch(url, {
|
||||
...options,
|
||||
credentials: 'include', // 自动携带 cookie
|
||||
})
|
||||
.then((response) => {
|
||||
if (!response.ok) {
|
||||
if (response.status === 401) {
|
||||
window.location.href = '/login';
|
||||
throw new Error('认证失败');
|
||||
}
|
||||
throw new Error(`请求失败,状态码:${response.status}`);
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("请求处理失败:", error);
|
||||
throw error;
|
||||
});
|
||||
};
|
||||
headers: {
|
||||
...headers,
|
||||
...options.headers
|
||||
}
|
||||
});
|
||||
|
||||
if (response.status === 401) {
|
||||
localStorage.removeItem('token');
|
||||
localStorage.removeItem('user');
|
||||
window.location.href = '/login';
|
||||
return;
|
||||
}
|
||||
|
||||
return response.json();
|
||||
}
|
||||
|
||||
// 在页面加载时检查认证
|
||||
|
||||
@@ -99,7 +113,7 @@ layui.use(["element", "layer"], function () {
|
||||
|
||||
// 加载页面内容
|
||||
|
||||
$("#content-frame").attr("src", url);
|
||||
$("#content-frame").attr("src", url+"?token="+localStorage.getItem('token'));
|
||||
|
||||
// 更新选中状态
|
||||
|
||||
|
@@ -6,7 +6,10 @@ layui.use(['form', 'upload', 'layer'], function(){
|
||||
|
||||
// 加载当前配置
|
||||
fetch('/api/site/settings', {
|
||||
credentials: 'include'
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'Authorization': 'Bearer ' + localStorage.getItem('token')
|
||||
}
|
||||
})
|
||||
.then(response => {
|
||||
if (response.status === 401) {
|
||||
@@ -112,7 +115,8 @@ layui.use(['form', 'upload', 'layer'], function(){
|
||||
fetch('/api/site/settings', {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': 'Bearer ' + localStorage.getItem('token')
|
||||
},
|
||||
credentials: 'include',
|
||||
body: JSON.stringify(submitData)
|
||||
|
@@ -8,7 +8,9 @@ layui.use(['table', 'form', 'layer'], function(){
|
||||
table.render({
|
||||
elem: '#user-table',
|
||||
url: '/api/users',
|
||||
headers: undefined,
|
||||
headers: {
|
||||
'Authorization': 'Bearer ' + localStorage.getItem('token')
|
||||
},
|
||||
toolbar: '#tableToolbar',
|
||||
defaultToolbar: ['filter', 'exports', 'print'],
|
||||
cols: [[
|
||||
|
Reference in New Issue
Block a user