哪吒面板 MDUI 主题添加分组

效果图:加了之后
改了之后的 html(没格式化是因为我的格式化插件有 bug 格式化会导致哪吒插入的关键字段被优化掉):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
{{define "theme-mdui/home"}}
<!doctype html>
<html lang="{{.Conf.Language}}">

<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<title>{{.Title}}</title>
<link rel="shortcut icon" type="image/png" href="/static/logo.svg?v20210804" />

<!-- MDUI CSS -->
<link rel="stylesheet" href="https://cdn.staticfile.org/mdui/1.0.2/css/mdui.min.css"/>
<link rel="stylesheet" href="/static/theme-mdui/mdui.css" type="text/css">
<style>
.mdui-table td, .mdui-table th{padding: 6px;}
.progress{width: 10%;min-width: 75px;}
.progress-text{font-size: 16px;font-weight: 800;position: relative;top: 4px;left: 6px;}
.offline st,.offline at,.offline gt,.offline .progress-text{color: grey;}
a{text-decoration:none;color:#333;}.mdui-theme-layout-dark a{color:#fff;}
</style>
{{if ts .CustomCode}}
{{.CustomCode|safe}}
{{end}}
</head>

<body>
{{template "theme-mdui/menu" .}}

<div id="app">
<div id="container" class="mdui-container">
<button @click="toggleView" class="mdui-fab mdui-fab-wrapper mdui-fab-fixed mdui-ripple mdui-color-pink-accent">
<i v-if="showCard" class="mdui-icon material-icons">list</i>
<i v-else class="mdui-icon material-icons">apps</i>
</button>
<div v-if="showCard" class="mdui-row-xs-1 mdui-row-sm-2 mdui-row-md-3 mdui-row-lg-4">
<div class="mdui-panel" mdui-panel="{accordion: false}">
<div class="mdui-panel-item mdui-panel-item-open " v-for="group in groups">
<div class="mdui-panel-item-header">@#(group.Tag!==''?group.Tag:'{{tr "Default"}}')#@</div>
<div class="mdui-panel-item-body">
<div id="servers">
<div class="mdui-col" v-for='server in group.data' :id="server.ID">
<div :class="'mdui-card mt' + (server.live?'':' offline')">
<div class="mdui-card-header">
<img class="mdui-card-header-avatar" :src="'https://cdn.staticfile.org/flag-icon-css/4.1.5/flags/1x1/' + (server.Host.CountryCode?server.Host.CountryCode:'cn') + '.svg'"/>
<div class="mdui-card-header-title">@#server.Name#@</div>
<div class="mdui-card-header-subtitle">@#server.Host.CountryCode.toUpperCase()#@ | @#server.Host.Platform#@ @#server.Host.PlatformVersion#@</div>
</div>
<div v-if="server.live" class="mdui-card-menu">
<i :id="'info-' + server.ID" class="mdui-icon material-icons">info_outline</i>
</div>
<div v-else class="mdui-card-menu mdui-typo-title mdui-text-color-grey">Offline</div>
<div class="mdui-card-content">
<ul class="mdui-list">
<li class="mdui-list-item">
<i class="mdui-list-item-icon mdui-icon material-icons">memory</i>
<div class="mdui-list-item-content">
<st class="mdui-list-item-title mdui-list-item-one-line">CPU <span>@#server.live?parseInt(server.State.CPU):'NaN'#@%</span></st>
<div class="mdui-list-item-text" style="opacity:1;">
<div class="mdui-progress">
<div class="mdui-progress-determinate mdui-color-indigo-400" :style="'width: ' + (server.live?server.State.CPU:'0') + '%;'"></div>
</div>
</div>
</div>
</li>
<li class="mdui-list-item" :id="'mem-' + server.ID">
<i class="mdui-list-item-icon mdui-icon material-icons">straighten</i>
<div class="mdui-list-item-content">
<at class="mdui-list-item-title mdui-list-item-one-line">MEM <span>@#server.live?parseInt(server.State?server.State.MemUsed/server.Host.MemTotal*100:0):'NaN'#@%</span></at>
<div class="mdui-progress">
<div class="mdui-progress-determinate mdui-color-pink-400" :style="'width: ' + (server.live?parseInt(server.State?server.State.MemUsed/server.Host.MemTotal*100:0):'0') + '%;'"></div>
</div>
</div>
</li>
<li class="mdui-list-item">
<i class="mdui-list-item-icon mdui-icon material-icons">swap_vert</i>
<div class="mdui-list-item-content">
<div class="mdui-list-item-title">{{tr "UpNetTransfer"}}</div>
<div class="mdui-list-item-text mdui-list-item-one-line" style="opacity:1;">
<at><span>@#formatNetByteSize(server.State.NetOutSpeed)#@</span></at>
</div>
</div>
<div class="mdui-list-item-content">
<div class="mdui-list-item-title">{{tr "DownNetTransfer"}}</div>
<div class="mdui-list-item-text mdui-list-item-one-line" style="opacity:1;">
<st><span>@#formatNetByteSize(server.State.NetInSpeed)#@</span></st>
</div>
</div>
</li>
<li class="mdui-list-item">
<i class="mdui-list-item-icon mdui-icon material-icons">swap_horiz</i>
<div class="mdui-list-item-content">
<div class="mdui-list-item-title">{{tr "TotalUpNetTransfer"}}</div>
<div class="mdui-list-item-text mdui-list-item-one-line" style="opacity:1;">
<at><span>@#formatByteSize(server.State.NetOutTransfer)#@</span></at>
</div>
</div>
<div class="mdui-list-item-content">
<div class="mdui-list-item-title">{{tr "TotalDownNetTransfer"}}</div>
<div class="mdui-list-item-text mdui-list-item-one-line" style="opacity:1;">
<st><span>@#formatByteSize(server.State.NetInTransfer)#@</span></st>
</div>
</div>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>

<div v-else class="mdui-table-fluid mdui-m-t-1">
<table class="mdui-table mdui-table-hoverable">
<thead>
<tr>
<th class="mdui-text-center">ID</th>
<th class="mdui-text-center">{{tr "Name"}}</th>
<th class="mdui-text-center">{{tr "UpNetTransfer"}}</th>
<th class="mdui-text-center">{{tr "DownNetTransfer"}}</th>
<th class="mdui-text-center">{{tr "TotalUpNetTransfer"}}</th>
<th class="mdui-text-center">{{tr "TotalDownNetTransfer"}}</th>
<th class="mdui-text-center">CPU</th>
<th class="mdui-text-center">RAM</th>
<th class="mdui-text-center">{{tr "Uptime"}}</th>
</tr>
</thead>
<tbody>
<tr :class="(server.live?'':'offline')" v-for="server in servers">
<td class="mdui-text-center">@#server.ID#@</td>
<td class="mdui-text-center">@#server.Name#@</td>
<td class="mdui-text-center"><at>@#formatNetByteSize(server.State.NetOutSpeed)#@</at></td>
<td class="mdui-text-center"><st>@#formatNetByteSize(server.State.NetInSpeed)#@</st></td>
<td class="mdui-text-center"><at>@#formatByteSize(server.State.NetOutTransfer)#@</at></td>
<td class="mdui-text-center"><st>@#formatByteSize(server.State.NetInTransfer)#@</st></td>
<td class="progress">
<div class="mdui-progress" style="height: 30px; background-color: #edbbd2;">
<div class="mdui-progress-determinate mdui-color-pink-a400" :style="'width: ' + (server.live?server.State.CPU:'0') + '%;'">
<span class="mdui-text-truncate progress-text">@#server.live?parseInt(server.State.CPU):'NaN'#@%</span>
</div>
</div>
</td>
<td class="progress">
<div class="mdui-progress" style="height: 30px;">
<div class="mdui-progress-determinate mdui-color-indigo-400" :style="'width: ' + parseInt(server.State?server.State.MemUsed/server.Host.MemTotal*100:0) + '%;'">
<span class="mdui-text-truncate progress-text">@#parseInt(server.State?server.State.MemUsed/server.Host.MemTotal*100:0)#@%</span>
</div>
</div>
</td>
<td class="mdui-text-center">@#secondToDate(server.State.Uptime)#@</td>
</tr>
</tbody>
</table>
</div>

</div>
</div>

{{template "theme-mdui/footer" .}}

<script src="/static/theme-mdui/mdui.js"></script>
<script src="https://cdn.staticfile.org/mdui/1.0.2/js/mdui.min.js"></script>
<script src="https://cdn.staticfile.org/jquery/3.6.0/jquery.min.js"></script>
<script src="https://cdn.staticfile.org/vue/2.6.14/vue.min.js"></script>

<script>
var container = document.querySelector("#container");
container.style.minHeight = window.innerHeight-document.body.clientHeight+container.clientHeight+'px';
mdui.mutation();
const initData = JSON.parse('{{.Servers}}').servers;
var statusCards = new Vue({
el: '#app',
delimiters: ['@#', '#@'],
data: {
servers: initData,
cache: [],
groups: [],
showCard: true
},
created() {
this.group()
},
methods: {
toggleView() {
this.showCard = !this.showCard
},
toFixed2(f) {
return f.toFixed(2)
},
group() {
this.groups = groupingData(this.servers, "Tag")
},
secondToDate(s) {
var d = Math.floor(s / 3600 / 24);
if (d > 0) {
return d + " {{tr "Day"}}"
}
var h = Math.floor(s / 3600 % 24);
var m = Math.floor(s / 60 % 60);
var s = Math.floor(s % 60);
return h + ":" + ("0" + m).slice(-2) + ":" + ("0" + s).slice(-2);
},
readableBytes(bytes) {
if (!bytes) {
return '0B'
}
var i = Math.floor(Math.log(bytes) / Math.log(1024)),
sizes = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
return parseFloat((bytes / Math.pow(1024, i)).toFixed(2)) + sizes[i];
},
readableNetBytes(bytes) {
if (!bytes) {
return '0B'
}
var Kbps=125, Mbps=Kbps*1000, Gbps=Mbps*1000, Tbps=Gbps*1000;
if (bytes < Kbps) return (bytes * 8).toFixed(2) + 'bps';
if (bytes < Mbps) return (bytes / Kbps).toFixed(2) + 'Kbps';
if (bytes < Gbps) return (bytes / Mbps).toFixed(2) + 'Mbps';
if (bytes < Tbps) return (bytes / Gbps).toFixed(2) + 'Gbps';
else return (bytes / Tbps).toFixed(2) + 'Tbps';
},
formatTimestamp(t) {
return new Date(t * 1000).toLocaleString()
},
formatByteSize(bs) {
const x = this.readableBytes(bs)
return x != "NaN undefined" ? x : 'NaN'
},
formatNetByteSize(bs) {
const x = this.readableNetBytes(bs)
return x != "NaN undefined" ? x : 'NaN'
},
formatTooltip(server) {
var disk = this.formatByteSize(server.State.DiskUsed) + '/' + this.formatByteSize(server.Host.DiskTotal);
var upTime = this.secondToDate(server.State.Uptime);
var tooltip = `{content: 'System: ${server.Host.Platform}-${server.Host.PlatformVersion}[${server.Host.Arch}]<br>CPU: ${server.Host.CPU}<br>Disk: ${disk}<br>Online: ${upTime}<br>Version: ${server.Host.Version}'}`;
return tooltip
}
}
})

function groupingData(data, field) {
if (!data) {
return
}
let map = {};
let dest = [];
data.forEach(item => {
if (!map[item[field]]) {
dest.push({
[field]: item[field],
data: [item]
});
map[item[field]] = item;
} else {
dest.forEach(dItem => {
if (dItem[field] == item[field]) {
dItem.data.push(item);
}
});
}
})
return dest;
}
const wsProtocol = window.location.protocol == "https:" ? "wss" : "ws"
let canShowError = true;
function connect() {
const ws = new WebSocket(wsProtocol + '://' + window.location.host + '/ws');
ws.onopen = function (evt) {
canShowError = true;
mdui.snackbar({
message: '{{tr "RealtimeChannelEstablished"}}',
timeout: 2000,
position: 'top',
onClosed: function () {
mdui.mutation();
}
});
}
var infoTooltip = {}, memTooltip = {};
ws.onmessage = function (evt) {
const data = JSON.parse(evt.data)
statusCards.servers = data.servers
for (let i = 0; i < statusCards.servers.length; i++) {
const ns = statusCards.servers[i];
if (!ns.Host) ns.live = false
else {
const lastActive = new Date(ns.LastActive).getTime()
if (data.now - lastActive > 10 * 1000) {
ns.live = false
} else {
ns.live = true
if (statusCards.showCard) {
if (infoTooltip[ns.ID]) {
var disk = statusCards.formatByteSize(ns.State.DiskUsed) + '/' + statusCards.formatByteSize(ns.Host.DiskTotal);
var upTime = statusCards.secondToDate(ns.State.Uptime);
var content =
`System: ${ns.Host.Platform}-${ns.Host.PlatformVersion}[${ns.Host.Arch}]
CPU: ${ns.Host.CPU}
Disk: ${disk}
Online: ${upTime}
Version: ${ns.Host.Version}`;
infoTooltip[ns.ID].$element[0].innerText = content;
}
else {
if (document.getElementById(`info-${ns.ID}`)) infoTooltip[ns.ID] = new mdui.Tooltip(`#info-${ns.ID}`, {});
}

if (memTooltip[ns.ID]) {
var content = `${statusCards.formatByteSize(ns.State.MemUsed)}/${statusCards.formatByteSize(ns.Host.MemTotal)}`;
memTooltip[ns.ID].$element[0].innerText = content;
}
else {
if (document.getElementById(`mem-${ns.ID}`)) memTooltip[ns.ID] = new mdui.Tooltip(`#mem-${ns.ID}`, {});
}
} else { mdui.$('div').remove('.mdui-tooltip'); infoTooltip = {}; memTooltip = {}; }
}
}
}
statusCards.groups = groupingData(statusCards.servers, "Tag")
mdui.mutation();
}
ws.onclose = function () {
if (canShowError) {
canShowError = false;
mdui.snackbar({
message: '{{tr "RealtimeChannelDisconnect"}}',
timeout: 2000,
position: 'top',
});
}
setTimeout(function () {
connect()
}, 3000);
}
ws.onerror = function () {
ws.close()
}
}
connect();
</script>
</body>
</html>
{{end}}

JS编写仿Ant Design的消息提示框

最近在改一个项目,前端使用了Ant Design且我没有源码,因此只能自己实现一个MessageBox组件。
在掘金上看见了一个现成的组件,略加修改使其适应Ant Design的风格。
用法:

1
2
3
4
5
const message = useMessage();
message.success("success");
message.error("error");
message.info("info");
message.warning("warning");

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
/**
* 消息提示条
* @param {object} params
* @param {number} params.duration 持续时间(毫秒),默认`3000`
* @param {number} params.zIndex 起始定位层级,默认`1000`
*/
function useMessage(params = {}) {
const doc = document;
const cssModule = `__${Math.random().toString(36).slice(2, 7)}`;
const className = {
box: `msg-box${cssModule}`,
hide: `hide${cssModule}`,
text: `msg-text${cssModule}`,
icon: `msg-icon${cssModule}`
}
const style = doc.createElement("style");
style.textContent = `
.${className.box}, .${className.icon}, .${className.text} {
padding: 0;
margin: 0;
box-sizing: border-box;
}
.${className.box} {
position: fixed;
top: 0;
left: 50%;
display: flex;
padding: 12px 16px;
border-radius: 2px;
background-color: #fff;
box-shadow: 0 3px 3px -2px rgba(0,0,0,.2),0 3px 4px 0 rgba(0,0,0,.14),0 1px 8px 0 rgba(0,0,0,.12);
white-space: nowrap;
animation: ${className.box}-move .4s;
transition: .4s all;
transform: translate3d(-50%, 0%, 0);
opacity: 1;
overflow: hidden;
padding: 9px 12px;
margin: 0;
box-sizing: border-box;
border-radius: 8px;
box-shadow: 0 6px 16px 0 rgba(0, 0, 0, 0.08),
0 3px 6px -4px rgba(0, 0, 0, 0.12),
0 9px 28px 8px rgba(0, 0, 0, 0.05);
pointer-events: all;
}
@keyframes ${className.box}-move {
0% {
opacity: 0;
transform: translate3d(-50%, -100%, 0);
}
100% {
opacity: 1;
transform: translate3d(-50%, 0%, 0);
}
}
.${className.box}.${className.hide} {
opacity: 0;
transform: translate3d(-50%, -100%, 0);
}
.${className.icon} {
display: inline-block;
width: 16px;
height: 16px;
border-radius: 50%;
overflow: hidden;
margin-right: 6px;
position: relative;
align-items: center;
color: inherit;
font-style: normal;
line-height: 0;
text-align: center;
text-transform: none;
vertical-align: -0.125em;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.${className.text} {
font-size: 14px;
line-height: 18px;
color: #555;
}
.${className.icon}::after,
.${className.icon}::before {
position: absolute;
content: "";
background-color: #fff;
}
.${className.box}.info .${className.icon}, .${className.box}.info::after {
background-color: #1677ff;
}
.${className.box}.success .${className.icon}, .${className.box}.success::after {
background-color: #52c41a;
}
.${className.box}.warning .${className.icon}, .${className.box}.warning::after {
background-color: #faad14;
}
.${className.box}.error .${className.icon}, .${className.box}.error::after {
background-color: #ff4d4f;
}
.${className.box}.info .${className.icon}::after,
.${className.box}.warning .${className.icon}::after {
top: 15%;
left: 50%;
margin-left: -1px;
width: 2px;
height: 2px;
border-radius: 50%;
}
.${className.box}.info .${className.icon}::before,
.${className.box}.warning .${className.icon}::before {
top: calc(15% + 4px);
left: 50%;
margin-left: -1px;
width: 2px;
height: 40%;
}
.${className.box}.error .${className.icon}::after,
.${className.box}.error .${className.icon}::before {
top: 20%;
left: 50%;
width: 2px;
height: 60%;
margin-left: -1px;
border-radius: 1px;
}
.${className.box}.error .${className.icon}::after {
transform: rotate(-45deg);
}
.${className.box}.error .${className.icon}::before {
transform: rotate(45deg);
}
.${className.box}.success .${className.icon}::after {
box-sizing: content-box;
background-color: transparent;
border: 2px solid #fff;
border-left: 0;
border-top: 0;
height: 50%;
left: 35%;
top: 13%;
transform: rotate(45deg);
width: 20%;
transform-origin: center;
}
`.replace(/(\n|\t|\s)*/ig, "$1").replace(/\n|\t|\s(\{|\}|\,|\:|\;)/ig, "$1").replace(/(\{|\}|\,|\:|\;)\s/ig, "$1");
doc.head.appendChild(style);
/** 一直累加的定位层级 */
let zIndex = params.zIndex || 1000;
/**
* 消息队列
* @type {Array<HTMLElement>}
*/
const messageList = [];

/**
* 获取指定`item`的定位`top`
* @param {HTMLElement=} el
*/
function getItemTop(el) {
let top = 8;
for (let i = 0; i < messageList.length; i++) {
const item = messageList[i];
if (el && el === item) {
break;
}
top += item.clientHeight + 20;
}
return top;
}

/**
* 删除指定列表项
* @param {HTMLElement} el
*/
function removeItem(el) {
for (let i = 0; i < messageList.length; i++) {
const item = messageList[i];
if (item === el) {
messageList.splice(i, 1);
break;
}
}
el.classList.add(className.hide);
messageList.forEach(function(item) {
item.style.top = `${getItemTop(item)}px`;
});
}

/**
* 显示一条消息
* @param {string} content 内容
* @param {"info"|"success"|"warning"|"error"} type 消息类型
* @param {number} duration 持续时间,优先级比默认值高
*/
function show(content, type = "info", duration) {
const el = doc.createElement("div");
el.className = `${className.box} ${type}`;
el.style.top = `${getItemTop()}px`;
el.style.zIndex = zIndex;
el.innerHTML = `
<span class="${className.icon}"></span>
<span class="${className.text}">${content}</span>
`;
zIndex++;
messageList.push(el);
doc.body.appendChild(el);
// 添加动画监听事件
function animationEnd() {
el.removeEventListener("animationend", animationEnd);
setTimeout(removeItem, duration || params.duration || 3000, el);
}
el.addEventListener("animationend", animationEnd);
function transitionEnd() {
if (getComputedStyle(el).opacity !== "0") return;
el.removeEventListener("transitionend", transitionEnd);
el.remove();
}
el.addEventListener("transitionend", transitionEnd);
}

return {
show,
/**
* 普通描述提示
* @param {string} msg
*/
info(msg) {
show(msg, "info");
},
/**
* 成功提示
* @param {string} msg
*/
success(msg) {
show(msg, "success");
},
/**
* 警告提示
* @param {string} msg
*/
warning(msg) {
show(msg, "warning");
},
/**
* 错误提示
* @param {string} msg
*/
error(msg) {
show(msg, "error");
}
}
}

修复跨域 (CORS) 问题

如果你在进行前后端分离的开发时发现在测试环境内没有任何问题,而一上生产环境就会出现前端根本无法向后端发出请求的问题,那么恭喜你,你成为了万恶的CORS的受害者之一!!!
你可以按F12打开开发者工具,选择网络选项卡,按F5刷新网页。不出意外的话你会看见一长串的CORS Error。同时,控制台也会输出错误。

什么是 CORS

@> 跨源资源共享 (CORS)(或通俗地译为跨域资源共享)是一种基于 HTTP 头的机制,该机制通过允许服务器标示除了它自己以外的其它origin(域,协议和端口),这样浏览器可以访问加载这些资源。跨源资源共享还通过一种机制来检查服务器是否会允许要发送的真实请求,该机制通过浏览器发起一个到服务器托管的跨源资源的”预检”请求。在预检中,浏览器发送的头中标示有HTTP方法和真实请求中会用到的头。
跨源HTTP请求的一个例子:运行在 https://domain-a.com 的 JavaScript 代码使用 XMLHttpRequest 来发起一个到 https://domain-b.com/data.json 的请求。
出于安全性,浏览器限制脚本内发起的跨源HTTP请求。 例如,XMLHttpRequest 和 Fetch API 遵循同源策略。这意味着使用这些 API 的 Web 应用程序只能从加载应用程序的同一个域请求 HTTP 资源,除非响应报文包含了正确 CORS 响应头。
@>
@> 来源: 跨源资源共享(CORS) - HTTP | MDN

如何修复

如果你使用了支持修改返回头的CDN,你可以直接在CDN侧配置。
CDN侧添加如下四个返回头:

标头名称
Access-Control-Allow-Methods GET,POST,HEAD,OPTIONS
Access-Control-Allow-Origin *
Cross-Origin-Opener-Policy cross-origin
Cross-Origin-Resource-Policy cross-origin
如果你没有使用CDN,那么直接在Nginx配置文件中写规则即可。
Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×