befly-admin-ui 1.10.1 → 1.10.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/detailPanel.vue +52 -54
- package/components/pageDialog.vue +131 -117
- package/components/pageTableDetail.vue +381 -402
- package/layouts/default.vue +212 -215
- package/package.json +2 -2
- package/views/config/dict/components/edit.vue +83 -60
- package/views/config/dict/index.vue +51 -55
- package/views/config/dictType/components/edit.vue +81 -57
- package/views/config/dictType/index.vue +37 -39
- package/views/config/system/components/edit.vue +97 -86
- package/views/config/system/index.vue +35 -37
- package/views/index/components/addonList.vue +14 -11
- package/views/index/components/environmentInfo.vue +25 -22
- package/views/index/components/operationLogs.vue +20 -16
- package/views/index/components/performanceMetrics.vue +24 -21
- package/views/index/components/serviceStatus.vue +26 -22
- package/views/index/components/systemNotifications.vue +24 -19
- package/views/index/components/systemOverview.vue +368 -385
- package/views/index/components/systemResources.vue +22 -19
- package/views/index/components/userInfo.vue +56 -56
- package/views/log/email/index.vue +96 -85
- package/views/log/error/index.vue +111 -117
- package/views/log/login/index.vue +15 -13
- package/views/log/operate/index.vue +31 -31
- package/views/login_1/index.vue +50 -32
- package/views/people/admin/components/edit.vue +1 -1
- package/views/people/admin/index.vue +27 -28
- package/views/permission/api/index.vue +44 -45
- package/views/permission/menu/index.vue +35 -35
- package/views/permission/role/components/api.vue +143 -144
- package/views/permission/role/components/edit.vue +74 -52
- package/views/permission/role/components/menu.vue +119 -121
- package/views/permission/role/index.vue +47 -49
- package/views/resource/gallery/index.vue +77 -78
- package/LICENSE +0 -201
|
@@ -9,22 +9,22 @@
|
|
|
9
9
|
<div class="resource-compact-item">
|
|
10
10
|
<div class="resource-compact-header">
|
|
11
11
|
<span class="resource-label">CPU</span>
|
|
12
|
-
<span class="resource-value">{{ systemResources.cpu.usage }}%</span>
|
|
13
|
-
<span class="resource-desc">{{ systemResources.cpu.cores }}核心</span>
|
|
12
|
+
<span class="resource-value">{{ $Data.systemResources.cpu.usage }}%</span>
|
|
13
|
+
<span class="resource-desc">{{ $Data.systemResources.cpu.cores }}核心</span>
|
|
14
14
|
</div>
|
|
15
15
|
</div>
|
|
16
16
|
<div class="resource-compact-item">
|
|
17
17
|
<div class="resource-compact-header">
|
|
18
18
|
<span class="resource-label">内存</span>
|
|
19
|
-
<span class="resource-value">{{ systemResources.memory.percentage }}%</span>
|
|
20
|
-
<span class="resource-desc">{{ systemResources.memory.used }}GB / {{ systemResources.memory.total }}GB</span>
|
|
19
|
+
<span class="resource-value">{{ $Data.systemResources.memory.percentage }}%</span>
|
|
20
|
+
<span class="resource-desc">{{ $Data.systemResources.memory.used }}GB / {{ $Data.systemResources.memory.total }}GB</span>
|
|
21
21
|
</div>
|
|
22
22
|
</div>
|
|
23
23
|
<div class="resource-compact-item">
|
|
24
24
|
<div class="resource-compact-header">
|
|
25
25
|
<span class="resource-label">磁盘</span>
|
|
26
|
-
<span class="resource-value">{{ systemResources.disk.percentage }}%</span>
|
|
27
|
-
<span class="resource-desc">{{ systemResources.disk.used }}GB / {{ systemResources.disk.total }}GB</span>
|
|
26
|
+
<span class="resource-value">{{ $Data.systemResources.disk.percentage }}%</span>
|
|
27
|
+
<span class="resource-desc">{{ $Data.systemResources.disk.used }}GB / {{ $Data.systemResources.disk.total }}GB</span>
|
|
28
28
|
</div>
|
|
29
29
|
</div>
|
|
30
30
|
</div>
|
|
@@ -35,26 +35,29 @@
|
|
|
35
35
|
<script setup>
|
|
36
36
|
import { reactive } from "vue";
|
|
37
37
|
import { ChartIcon } from "tdesign-icons-vue-next";
|
|
38
|
-
import { $Http } from "@/plugins/http";
|
|
38
|
+
import { $Http } from "@/plugins/http.js";
|
|
39
39
|
|
|
40
40
|
// 组件内部数据
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
41
|
+
const $Data = reactive({
|
|
42
|
+
systemResources: {
|
|
43
|
+
cpu: { usage: 0, cores: 0 },
|
|
44
|
+
memory: { used: 0, total: 0, percentage: 0 },
|
|
45
|
+
disk: { used: 0, total: 0, percentage: 0 }
|
|
46
|
+
}
|
|
45
47
|
});
|
|
46
48
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
49
|
+
const $Method = {
|
|
50
|
+
async fetchData() {
|
|
51
|
+
try {
|
|
52
|
+
const res = await $Http("/core/dashboard/systemResources", {}, [""]);
|
|
53
|
+
Object.assign($Data.systemResources, res.data);
|
|
54
|
+
} catch (_error) {
|
|
55
|
+
// 静默失败:不阻断页面展示
|
|
56
|
+
}
|
|
54
57
|
}
|
|
55
58
|
};
|
|
56
59
|
|
|
57
|
-
fetchData();
|
|
60
|
+
$Method.fetchData();
|
|
58
61
|
</script>
|
|
59
62
|
|
|
60
63
|
<style scoped lang="scss">
|
|
@@ -22,13 +22,13 @@
|
|
|
22
22
|
</div>
|
|
23
23
|
<div v-if="$Data.userInfo.lastLoginTime" class="detail-item">
|
|
24
24
|
<TimeIcon />
|
|
25
|
-
<span>{{ formatTime($Data.userInfo.lastLoginTime) }}</span>
|
|
25
|
+
<span>{{ $Method.formatTime($Data.userInfo.lastLoginTime) }}</span>
|
|
26
26
|
</div>
|
|
27
27
|
</div>
|
|
28
28
|
|
|
29
29
|
<!-- 仅 dev 角色显示刷新缓存按钮 -->
|
|
30
30
|
<div v-if="$Data.userInfo.roleCode === 'dev'" class="user-actions">
|
|
31
|
-
<TButton theme="primary" size="mini" :loading="$Data.refreshing" @click="handleRefreshCache">
|
|
31
|
+
<TButton theme="primary" size="mini" :loading="$Data.refreshing" @click="$Method.handleRefreshCache">
|
|
32
32
|
<template #icon>
|
|
33
33
|
<RefreshIcon />
|
|
34
34
|
</template>
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
import { reactive } from "vue";
|
|
43
43
|
import { Button as TButton, MessagePlugin } from "tdesign-vue-next";
|
|
44
44
|
import { CallIcon, MailIcon, RefreshIcon, TimeIcon, UserIcon } from "tdesign-icons-vue-next";
|
|
45
|
-
import { $Http } from "@/plugins/http";
|
|
45
|
+
import { $Http } from "@/plugins/http.js";
|
|
46
46
|
|
|
47
47
|
// 响应式数据
|
|
48
48
|
const $Data = reactive({
|
|
@@ -50,65 +50,65 @@ const $Data = reactive({
|
|
|
50
50
|
refreshing: false
|
|
51
51
|
});
|
|
52
52
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
async
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
53
|
+
const $Method = {
|
|
54
|
+
async fetchData() {
|
|
55
|
+
try {
|
|
56
|
+
const res = await $Http("/core/admin/detail", {}, [""]);
|
|
57
|
+
Object.assign($Data.userInfo, res.data);
|
|
58
|
+
} catch (error) {
|
|
59
|
+
MessagePlugin.error(error.msg || error.message || "获取用户信息失败");
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
async handleRefreshCache() {
|
|
63
|
+
try {
|
|
64
|
+
$Data.refreshing = true;
|
|
65
|
+
const result = await $Http("/core/admin/cacheRefresh");
|
|
66
|
+
|
|
67
|
+
const apis = result.data.apis;
|
|
68
|
+
const menus = result.data.menus;
|
|
69
|
+
const roles = result.data.roles;
|
|
70
|
+
const messages = [];
|
|
71
|
+
|
|
72
|
+
if (apis && apis.success) {
|
|
73
|
+
messages.push(`接口缓存: ${apis.count} 个`);
|
|
74
|
+
}
|
|
75
|
+
if (menus && menus.success) {
|
|
76
|
+
messages.push(`菜单缓存: ${menus.count} 个`);
|
|
77
|
+
}
|
|
78
|
+
if (roles && roles.success) {
|
|
79
|
+
messages.push(`角色缓存: ${roles.count} 个`);
|
|
80
|
+
}
|
|
71
81
|
|
|
72
|
-
|
|
73
|
-
|
|
82
|
+
MessagePlugin.success(`缓存刷新成功!${messages.join(",")}`);
|
|
83
|
+
} catch (error) {
|
|
84
|
+
MessagePlugin.error(error.msg || error.message || "刷新缓存失败,请稍后重试");
|
|
85
|
+
} finally {
|
|
86
|
+
$Data.refreshing = false;
|
|
74
87
|
}
|
|
75
|
-
|
|
76
|
-
|
|
88
|
+
},
|
|
89
|
+
formatTime(timestamp) {
|
|
90
|
+
if (!timestamp) return "";
|
|
91
|
+
const date = new Date(Number(timestamp));
|
|
92
|
+
const now = new Date();
|
|
93
|
+
const diff = now.getTime() - date.getTime();
|
|
94
|
+
|
|
95
|
+
if (diff < 60000) {
|
|
96
|
+
return "刚刚";
|
|
77
97
|
}
|
|
78
|
-
if (
|
|
79
|
-
|
|
98
|
+
if (diff < 3600000) {
|
|
99
|
+
return `${Math.floor(diff / 60000)}分钟前`;
|
|
80
100
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function formatTime(timestamp) {
|
|
91
|
-
if (!timestamp) return "";
|
|
92
|
-
const date = new Date(Number(timestamp));
|
|
93
|
-
const now = new Date();
|
|
94
|
-
const diff = now.getTime() - date.getTime();
|
|
95
|
-
|
|
96
|
-
if (diff < 60000) {
|
|
97
|
-
return "刚刚";
|
|
98
|
-
}
|
|
99
|
-
if (diff < 3600000) {
|
|
100
|
-
return `${Math.floor(diff / 60000)}分钟前`;
|
|
101
|
-
}
|
|
102
|
-
if (diff < 86400000) {
|
|
103
|
-
return `${Math.floor(diff / 3600000)}小时前`;
|
|
104
|
-
}
|
|
105
|
-
if (diff < 604800000) {
|
|
106
|
-
return `${Math.floor(diff / 86400000)}天前`;
|
|
101
|
+
if (diff < 86400000) {
|
|
102
|
+
return `${Math.floor(diff / 3600000)}小时前`;
|
|
103
|
+
}
|
|
104
|
+
if (diff < 604800000) {
|
|
105
|
+
return `${Math.floor(diff / 86400000)}天前`;
|
|
106
|
+
}
|
|
107
|
+
return `${date.getMonth() + 1}月${date.getDate()}日`;
|
|
107
108
|
}
|
|
108
|
-
|
|
109
|
-
}
|
|
109
|
+
};
|
|
110
110
|
|
|
111
|
-
fetchData();
|
|
111
|
+
$Method.fetchData();
|
|
112
112
|
</script>
|
|
113
113
|
|
|
114
114
|
<style scoped lang="scss">
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<PageTableDetail class="page-email page-table" :columns="$Data.columns" :endpoints="$Data.endpoints" :table-slot-names="['sendResult', 'sendTime']">
|
|
3
3
|
<template #toolLeft>
|
|
4
|
-
<TButton theme="primary" @click="openSendDialog">
|
|
4
|
+
<TButton theme="primary" @click="$Method.openSendDialog">
|
|
5
5
|
<template #icon>
|
|
6
6
|
<SendIcon />
|
|
7
7
|
</template>
|
|
8
8
|
发送邮件
|
|
9
9
|
</TButton>
|
|
10
|
-
<TButton variant="outline" @click="onVerify">
|
|
10
|
+
<TButton variant="outline" @click="$Method.onVerify">
|
|
11
11
|
<template #icon>
|
|
12
12
|
<CheckCircleIcon />
|
|
13
13
|
</template>
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
</template>
|
|
22
22
|
|
|
23
23
|
<template #sendTime="{ row }">
|
|
24
|
-
{{ formatTime(row.sendTime) }}
|
|
24
|
+
{{ $Method.formatTime(row.sendTime) }}
|
|
25
25
|
</template>
|
|
26
26
|
|
|
27
27
|
<template #detail="scope">
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
<TTag v-else shape="round" theme="danger" variant="light-outline">失败</TTag>
|
|
32
32
|
</template>
|
|
33
33
|
<template #sendTime="slotScope">
|
|
34
|
-
{{ formatTime(slotScope.value) }}
|
|
34
|
+
{{ $Method.formatTime(slotScope.value) }}
|
|
35
35
|
</template>
|
|
36
36
|
<template #content="slotScope">
|
|
37
37
|
<div class="email-content" v-html="slotScope.value"></div>
|
|
@@ -40,8 +40,17 @@
|
|
|
40
40
|
</template>
|
|
41
41
|
|
|
42
42
|
<template #dialogs="scope">
|
|
43
|
-
<PageDialog v-model="$Data.sendDialogVisible" title="发送邮件" :confirm-loading="$Data.sending" @confirm="(context) => onSend(scope.reload, context)" @cancel="onCancelSend" @close="onCancelSend">
|
|
44
|
-
<TForm
|
|
43
|
+
<PageDialog v-model="$Data.sendDialogVisible" title="发送邮件" :confirm-loading="$Data.sending" @confirm="(context) => $Method.onSend(scope.reload, context)" @cancel="$Method.onCancelSend" @close="$Method.onCancelSend">
|
|
44
|
+
<TForm
|
|
45
|
+
:ref="
|
|
46
|
+
(value) => {
|
|
47
|
+
$From.sendFormRef = value;
|
|
48
|
+
}
|
|
49
|
+
"
|
|
50
|
+
:data="$Data.sendForm"
|
|
51
|
+
:rules="$Const.sendRules"
|
|
52
|
+
label-width="80px"
|
|
53
|
+
>
|
|
45
54
|
<TFormItem label="收件人" name="to">
|
|
46
55
|
<TInput v-model="$Data.sendForm.to" placeholder="请输入收件人邮箱" />
|
|
47
56
|
</TFormItem>
|
|
@@ -61,15 +70,34 @@
|
|
|
61
70
|
</template>
|
|
62
71
|
|
|
63
72
|
<script setup>
|
|
64
|
-
import { reactive
|
|
65
|
-
import { Button as TButton, Form as TForm, FormItem as TFormItem, Input as TInput, MessagePlugin,
|
|
73
|
+
import { reactive } from "vue";
|
|
74
|
+
import { Button as TButton, Form as TForm, FormItem as TFormItem, Input as TInput, MessagePlugin, Tag as TTag, Textarea as TTextarea } from "tdesign-vue-next";
|
|
66
75
|
import { CheckCircleIcon, SendIcon } from "tdesign-icons-vue-next";
|
|
67
76
|
import PageDialog from "befly-admin-ui/components/pageDialog.vue";
|
|
68
77
|
import DetailPanel from "befly-admin-ui/components/detailPanel.vue";
|
|
69
|
-
import { $Http } from "@/plugins/http";
|
|
78
|
+
import { $Http } from "@/plugins/http.js";
|
|
70
79
|
import PageTableDetail from "befly-admin-ui/components/pageTableDetail.vue";
|
|
71
80
|
import { withDefaultColumns } from "befly-admin-ui/utils/withDefaultColumns";
|
|
72
|
-
|
|
81
|
+
|
|
82
|
+
const $Const = {
|
|
83
|
+
createSendForm() {
|
|
84
|
+
return {
|
|
85
|
+
to: "",
|
|
86
|
+
cc: "",
|
|
87
|
+
subject: "",
|
|
88
|
+
content: ""
|
|
89
|
+
};
|
|
90
|
+
},
|
|
91
|
+
sendRules: {
|
|
92
|
+
to: [{ required: true, message: "请输入收件人邮箱", trigger: "blur" }],
|
|
93
|
+
subject: [{ required: true, message: "请输入邮件主题", trigger: "blur" }],
|
|
94
|
+
content: [{ required: true, message: "请输入邮件内容", trigger: "blur" }]
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const $From = {
|
|
99
|
+
sendFormRef: null
|
|
100
|
+
};
|
|
73
101
|
|
|
74
102
|
// 响应式数据
|
|
75
103
|
const $Data = reactive({
|
|
@@ -94,89 +122,72 @@ const $Data = reactive({
|
|
|
94
122
|
},
|
|
95
123
|
sendDialogVisible: false,
|
|
96
124
|
sending: false,
|
|
97
|
-
sendForm:
|
|
98
|
-
to: "",
|
|
99
|
-
cc: "",
|
|
100
|
-
subject: "",
|
|
101
|
-
content: ""
|
|
102
|
-
},
|
|
103
|
-
sendRules: {
|
|
104
|
-
to: [{ required: true, message: "请输入收件人邮箱", trigger: "blur" }],
|
|
105
|
-
subject: [{ required: true, message: "请输入邮件主题", trigger: "blur" }],
|
|
106
|
-
content: [{ required: true, message: "请输入邮件内容", trigger: "blur" }]
|
|
107
|
-
}
|
|
125
|
+
sendForm: $Const.createSendForm()
|
|
108
126
|
});
|
|
109
127
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
async function onSend(reload, context) {
|
|
125
|
-
const form = sendFormRef.value;
|
|
126
|
-
if (!form) {
|
|
127
|
-
MessagePlugin.warning("表单未就绪");
|
|
128
|
-
return;
|
|
129
|
-
}
|
|
128
|
+
const $Method = {
|
|
129
|
+
onCancelSend() {
|
|
130
|
+
$Data.sendDialogVisible = false;
|
|
131
|
+
},
|
|
132
|
+
openSendDialog() {
|
|
133
|
+
$Data.sendForm = $Const.createSendForm();
|
|
134
|
+
$Data.sendDialogVisible = true;
|
|
135
|
+
},
|
|
136
|
+
async onSend(reload, context) {
|
|
137
|
+
const form = $From.sendFormRef;
|
|
138
|
+
if (!form) {
|
|
139
|
+
MessagePlugin.warning("表单未就绪");
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
130
142
|
|
|
131
|
-
|
|
132
|
-
|
|
143
|
+
const valid = await form.validate();
|
|
144
|
+
if (valid !== true) return;
|
|
133
145
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
146
|
+
$Data.sending = true;
|
|
147
|
+
try {
|
|
148
|
+
await $Http("/core/email/send", {
|
|
149
|
+
to: $Data.sendForm.to,
|
|
150
|
+
subject: $Data.sendForm.subject,
|
|
151
|
+
content: $Data.sendForm.content,
|
|
152
|
+
cc: $Data.sendForm.cc || undefined,
|
|
153
|
+
isHtml: true
|
|
154
|
+
});
|
|
143
155
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
156
|
+
MessagePlugin.success("发送成功");
|
|
157
|
+
if (context && typeof context.close === "function") {
|
|
158
|
+
context.close();
|
|
159
|
+
} else {
|
|
160
|
+
$Data.sendDialogVisible = false;
|
|
161
|
+
}
|
|
162
|
+
if (reload) {
|
|
163
|
+
reload({ keepSelection: false, resetPage: true });
|
|
164
|
+
}
|
|
165
|
+
} catch (error) {
|
|
166
|
+
MessagePlugin.error(error.msg || error.message || "发送失败");
|
|
167
|
+
} finally {
|
|
168
|
+
$Data.sending = false;
|
|
149
169
|
}
|
|
150
|
-
|
|
151
|
-
|
|
170
|
+
},
|
|
171
|
+
async onVerify() {
|
|
172
|
+
try {
|
|
173
|
+
await $Http("/core/email/verify");
|
|
174
|
+
MessagePlugin.success("邮件服务配置正常");
|
|
175
|
+
} catch (error) {
|
|
176
|
+
MessagePlugin.error(error.msg || error.message || "验证失败");
|
|
152
177
|
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
} catch (error) {
|
|
165
|
-
MessagePlugin.error(error.msg || error.message || "验证失败");
|
|
178
|
+
},
|
|
179
|
+
formatTime(timestamp) {
|
|
180
|
+
if (!timestamp) return "-";
|
|
181
|
+
const date = new Date(timestamp);
|
|
182
|
+
const year = date.getFullYear();
|
|
183
|
+
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
184
|
+
const day = String(date.getDate()).padStart(2, "0");
|
|
185
|
+
const hours = String(date.getHours()).padStart(2, "0");
|
|
186
|
+
const minutes = String(date.getMinutes()).padStart(2, "0");
|
|
187
|
+
const seconds = String(date.getSeconds()).padStart(2, "0");
|
|
188
|
+
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
|
166
189
|
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
function formatTime(timestamp) {
|
|
170
|
-
if (!timestamp) return "-";
|
|
171
|
-
const date = new Date(timestamp);
|
|
172
|
-
const year = date.getFullYear();
|
|
173
|
-
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
174
|
-
const day = String(date.getDate()).padStart(2, "0");
|
|
175
|
-
const hours = String(date.getHours()).padStart(2, "0");
|
|
176
|
-
const minutes = String(date.getMinutes()).padStart(2, "0");
|
|
177
|
-
const seconds = String(date.getSeconds()).padStart(2, "0");
|
|
178
|
-
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
|
179
|
-
}
|
|
190
|
+
};
|
|
180
191
|
</script>
|
|
181
192
|
|
|
182
193
|
<style scoped lang="scss">
|