@befly-addon/admin 1.8.2 → 1.8.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.
@@ -21,7 +21,7 @@
21
21
  <p class="login-subtitle">请登录您的账户</p>
22
22
  </div>
23
23
 
24
- <TForm :model="$Data.formData" :rules="$Data2.formRules" :ref="(el) => ($From.form = el)" class="login-form" :show-message="false" label-width="0">
24
+ <TForm :model="$Data.formData" :rules="$Data.formRules" :ref="(el) => ($From.form = el)" class="login-form" :show-message="false" label-width="0">
25
25
  <TFormItem prop="account">
26
26
  <TInputAdornment>
27
27
  <template #prepend>
@@ -31,7 +31,7 @@
31
31
  <TOption value="phone" label="手机号" />
32
32
  </TSelect>
33
33
  </template>
34
- <TInput v-model="$Data.formData.account" :placeholder="$Data.formData.loginType === 'username' ? '请输入用户名' : $Data.formData.loginType === 'email' ? '请输入邮箱' : '请输入手机号'" size="large" clearable @enter="$Method.apiLogin">
34
+ <TInput v-model="$Data.formData.account" :placeholder="$Data.formData.loginType === 'username' ? '请输入用户名' : $Data.formData.loginType === 'email' ? '请输入邮箱' : '请输入手机号'" size="large" clearable @enter="apiLogin">
35
35
  <template #prefix-icon>
36
36
  <ILucideUser />
37
37
  </template>
@@ -40,7 +40,7 @@
40
40
  </TFormItem>
41
41
 
42
42
  <TFormItem prop="password">
43
- <TInput v-model="$Data.formData.password" type="password" placeholder="密码" size="large" clearable @enter="$Method.apiLogin">
43
+ <TInput v-model="$Data.formData.password" type="password" placeholder="密码" size="large" clearable @enter="apiLogin">
44
44
  <template #prefix-icon>
45
45
  <ILucideLock />
46
46
  </template>
@@ -52,7 +52,7 @@
52
52
  <a href="#" class="link-text">忘记密码?</a>
53
53
  </div>
54
54
 
55
- <TButton theme="primary" class="login-btn" size="large" block :loading="$Data.loading" @click="$Method.apiLogin"> 登录 </TButton>
55
+ <TButton theme="primary" class="login-btn" size="large" block :loading="$Data.loading" @click="apiLogin"> 登录 </TButton>
56
56
  </TForm>
57
57
 
58
58
  <div class="login-footer">
@@ -83,6 +83,10 @@ const $From = $shallowRef({
83
83
  const $Data = $ref({
84
84
  loading: false,
85
85
  rememberMe: false,
86
+ formRules: {
87
+ account: [{ required: true, message: "请输入账号", trigger: "blur" }],
88
+ password: [{ required: true, message: "请输入密码", trigger: "blur" }]
89
+ },
86
90
  formData: {
87
91
  loginType: "username",
88
92
  account: "",
@@ -90,49 +94,35 @@ const $Data = $ref({
90
94
  }
91
95
  });
92
96
 
93
- const $Data2 = $shallowRef({
94
- formRules: {
95
- account: [{ required: true, message: "请输入账号", trigger: "blur" }],
96
- password: [{ required: true, message: "请输入密码", trigger: "blur" }]
97
- }
98
- });
99
-
100
- // 方法定义
101
- const $Method = {
102
- async apiLogin() {
103
- try {
104
- const valid = await $From.form.validate();
97
+ async function apiLogin(): Promise<void> {
98
+ try {
99
+ await $From.form.validate();
105
100
 
106
- $Data.loading = true;
101
+ $Data.loading = true;
107
102
 
108
- // 对密码进行 SHA-256 加密
109
- const hashedPassword = await hashPassword($Data.formData.password);
103
+ const hashedPassword = await hashPassword($Data.formData.password);
110
104
 
111
- const res = await $Http.post("/addon/admin/auth/login", {
112
- loginType: $Data.formData.loginType,
113
- account: $Data.formData.account,
114
- password: hashedPassword
115
- });
105
+ const res = await $Http.post("/addon/admin/auth/login", {
106
+ loginType: $Data.formData.loginType,
107
+ account: $Data.formData.account,
108
+ password: hashedPassword
109
+ });
116
110
 
117
- // 先保存 token
118
- $Storage.local.set("token", res.data.token);
111
+ $Storage.local.set("token", res.data.token);
119
112
 
120
- // 如果返回用户信息,也可以存储
121
- if (res.data.userInfo) {
122
- $Storage.local.set("userInfo", res.data.userInfo);
123
- }
113
+ if (res.data.userInfo) {
114
+ $Storage.local.set("userInfo", res.data.userInfo);
115
+ }
124
116
 
125
- MessagePlugin.success(res.msg || "登录成功");
117
+ MessagePlugin.success(res.msg || "登录成功");
126
118
 
127
- // 跳转到首页,路由守卫会自动加载菜单
128
- await router.push("/");
129
- } catch (error) {
130
- MessagePlugin.error("登录失败");
131
- } finally {
132
- $Data.loading = false;
133
- }
119
+ await router.push("/");
120
+ } catch (error) {
121
+ MessagePlugin.error("登录失败");
122
+ } finally {
123
+ $Data.loading = false;
134
124
  }
135
- };
125
+ }
136
126
  </script>
137
127
 
138
128
  <style scoped lang="scss">
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <TDialog v-model:visible="$Data.visible" :header="$Prop.actionType === 'upd' ? '编辑管理员' : '添加管理员'" width="600px" :append-to-body="true" :show-footer="true" :esc-closable="false" top="10vh" @close="$Method.onClose">
2
+ <PageDialog v-model="dialogVisible" :title="$Prop.actionType === 'upd' ? '编辑管理员' : '添加管理员'" :confirm-loading="$Data.submitting" @confirm="onSubmit">
3
3
  <div class="dialog-wrapper">
4
4
  <TForm :model="$Data.formData" label-width="80px" label-position="left" label-align="left" :rules="$Data2.formRules" :ref="(el) => ($From.form = el)">
5
5
  <TFormItem label="角色" prop="roleCode">
@@ -22,30 +22,23 @@
22
22
  </TFormItem>
23
23
  </TForm>
24
24
  </div>
25
- <template #footer>
26
- <div class="dialog-footer">
27
- <t-space>
28
- <TButton theme="default" @click="$Method.onClose">取消</TButton>
29
- <TButton theme="primary" :loading="$Data.submitting" @click="$Method.onSubmit">确定</TButton>
30
- </t-space>
31
- </div>
32
- </template>
33
- </TDialog>
25
+ </PageDialog>
34
26
  </template>
35
27
 
36
28
  <script setup lang="ts">
29
+ import { computed } from "vue";
30
+
37
31
  import {
38
32
  //
39
- Dialog as TDialog,
40
33
  Form as TForm,
41
34
  FormItem as TFormItem,
42
35
  Input as TInput,
43
36
  Select as TSelect,
44
37
  RadioGroup as TRadioGroup,
45
38
  Radio as TRadio,
46
- Button as TButton,
47
39
  MessagePlugin
48
40
  } from "tdesign-vue-next";
41
+ import PageDialog from "@/components/pageDialog.vue";
49
42
  import { $Http } from "@/plugins/http";
50
43
  import { fieldClear } from "befly-shared/utils/fieldClear";
51
44
  import { hashPassword } from "befly-shared/utils/hashPassword";
@@ -65,15 +58,28 @@ const $Prop = defineProps({
65
58
  }
66
59
  });
67
60
 
68
- const $Emit = defineEmits(["update:modelValue", "success"]);
61
+ const $Emit = defineEmits<{
62
+ (e: "update:modelValue", value: boolean): void;
63
+ (e: "success"): void;
64
+ }>();
65
+
66
+ type PageDialogEventContext = {
67
+ close: () => void;
68
+ };
69
69
 
70
70
  // 表单引用
71
71
  const $From = $shallowRef({
72
72
  form: null
73
73
  });
74
74
 
75
+ const dialogVisible = computed({
76
+ get: () => $Prop.modelValue,
77
+ set: (value) => {
78
+ $Emit("update:modelValue", value);
79
+ }
80
+ });
81
+
75
82
  const $Data = $ref({
76
- visible: false,
77
83
  submitting: false,
78
84
  allRoleLists: [],
79
85
  keys: {
@@ -102,71 +108,56 @@ const $Data2 = $shallowRef({
102
108
  }
103
109
  });
104
110
 
105
- // 方法集合
106
- const $Method = {
107
- async initData() {
108
- $Method.onShow();
109
- await $Method.apiRoleLists();
110
- if ($Prop.actionType === "upd" && $Prop.rowData.id) {
111
- $Data.formData = { ...$Prop.rowData };
112
- }
113
- },
111
+ async function initData(): Promise<void> {
112
+ await apiRoleLists();
113
+ if ($Prop.actionType === "upd" && $Prop.rowData.id) {
114
+ $Data.formData = Object.assign({}, $Prop.rowData);
115
+ }
116
+ }
117
+
118
+ async function apiRoleLists(): Promise<void> {
119
+ try {
120
+ const result = await $Http.post(
121
+ "/addon/admin/role/all",
122
+ {},
123
+ {
124
+ dropValues: [""]
125
+ }
126
+ );
127
+ $Data.allRoleLists = result.data || [];
128
+ } catch (error) {
129
+ MessagePlugin.error("加载角色列表失败");
130
+ }
131
+ }
114
132
 
115
- onShow() {
116
- setTimeout(() => {
117
- $Data.visible = $Prop.modelValue;
118
- }, 100);
119
- },
133
+ async function onSubmit(context?: PageDialogEventContext): Promise<void> {
134
+ try {
135
+ const valid = await $From.form.validate();
136
+ if (!valid) return;
120
137
 
121
- onClose() {
122
- $Data.visible = false;
123
- setTimeout(() => {
124
- $Emit("update:modelValue", false);
125
- }, 300);
126
- },
138
+ $Data.submitting = true;
139
+ const formData = ($Prop.actionType === "add" ? fieldClear($Data.formData, { omitKeys: ["id", "state"] }) : fieldClear($Data.formData, { omitKeys: ["password"] })) as Record<string, unknown>;
127
140
 
128
- async apiRoleLists() {
129
- try {
130
- const result = await $Http.post(
131
- "/addon/admin/role/all",
132
- {},
133
- {
134
- dropValues: [""]
135
- }
136
- );
137
- $Data.allRoleLists = result.data || [];
138
- } catch (error) {
139
- MessagePlugin.error("加载角色列表失败");
141
+ const password = typeof formData["password"] === "string" ? String(formData["password"]) : "";
142
+ if ($Prop.actionType === "add" && password) {
143
+ formData["password"] = await hashPassword(password);
140
144
  }
141
- },
142
-
143
- async onSubmit() {
144
- try {
145
- const valid = await $From.form.validate();
146
- if (!valid) return;
147
145
 
148
- $Data.submitting = true;
149
- const formData = $Prop.actionType === "add" ? fieldClear($Data.formData, { omitKeys: ["id", "state"] }) : fieldClear($Data.formData, { omitKeys: ["password"] });
146
+ const result = await $Http.post($Prop.actionType === "upd" ? "/addon/admin/admin/upd" : "/addon/admin/admin/ins", formData);
150
147
 
151
- // 添加管理员时,对密码进行 SHA-256 加密
152
- if ($Prop.actionType === "add" && formData.password) {
153
- formData.password = await hashPassword(formData.password);
154
- }
155
-
156
- const result = await $Http.post($Prop.actionType === "upd" ? "/addon/admin/admin/upd" : "/addon/admin/admin/ins", formData);
157
-
158
- MessagePlugin.success(result.msg);
159
- $Emit("success");
160
- $Method.onClose();
161
- } catch (error) {
162
- MessagePlugin.error(error.msg || "提交失败");
163
- } finally {
164
- $Data.submitting = false;
148
+ MessagePlugin.success(result.msg);
149
+ $Emit("success");
150
+ if (context && typeof context.close === "function") {
151
+ context.close();
165
152
  }
153
+ } catch (error) {
154
+ MessagePlugin.error((error as { msg?: string }).msg || "提交失败");
155
+ } finally {
156
+ $Data.submitting = false;
166
157
  }
167
- };
158
+ }
168
159
 
169
- $Method.initData();
160
+ initData();
170
161
  </script>
171
162
 
172
163
  <style scoped lang="scss">
@@ -1,90 +1,65 @@
1
1
  <template>
2
- <div class="page-admin page-table">
3
- <div class="main-tool">
4
- <div class="left">
5
- <TButton theme="primary" @click="$Method.onAction('add', {})">
6
- <template #icon>
7
- <ILucidePlus />
8
- </template>
2
+ <PagedTableDetail class="page-admin" :columns="$Data.columns" :endpoints="$Data.endpoints">
3
+ <template #toolLeft>
4
+ <TButton theme="primary" @click="onAdd">
5
+ <template #icon>
6
+ <ILucidePlus />
7
+ </template>
8
+ </TButton>
9
+ </template>
10
+
11
+ <template #toolRight="scope">
12
+ <TButton shape="circle" @click="onReload(scope.reload)">
13
+ <template #icon>
14
+ <ILucideRotateCw />
15
+ </template>
16
+ </TButton>
17
+ </template>
18
+
19
+ <template #state="{ row }">
20
+ <TTag v-if="row.state === 1" shape="round" theme="success" variant="light-outline">正常</TTag>
21
+ <TTag v-else-if="row.state === 2" shape="round" theme="warning" variant="light-outline">禁用</TTag>
22
+ <TTag v-else-if="row.state === 0" shape="round" theme="danger" variant="light-outline">删除</TTag>
23
+ </template>
24
+
25
+ <template #operation="{ row, deleteRow }">
26
+ <TDropdown trigger="click" placement="bottom-right" @click="onDropdownAction($event, row, deleteRow)">
27
+ <TButton theme="primary" size="small">
28
+ 操作
29
+ <template #suffix> <ILucideChevronDown /></template>
9
30
  </TButton>
10
- </div>
11
- <div class="right">
12
- <TButton shape="circle" @click="$Method.handleRefresh">
13
- <template #icon>
14
- <ILucideRotateCw />
15
- </template>
16
- </TButton>
17
- </div>
18
- </div>
19
-
20
- <div class="main-content">
21
- <div class="main-table">
22
- <TTable
23
- :data="$Data.tableData"
24
- :columns="$Data.columns"
25
- :loading="$Data.loading"
26
- :active-row-keys="$Data.activeRowKeys"
27
- row-key="id"
28
- height="calc(100vh - var(--search-height) - var(--pagination-height) - var(--layout-gap) * 4)"
29
- active-row-type="single"
30
- @active-change="$Method.onActiveChange"
31
- >
32
- <template #state="{ row }">
33
- <TTag v-if="row.state === 1" shape="round" theme="success" variant="light-outline">正常</TTag>
34
- <TTag v-else-if="row.state === 2" shape="round" theme="warning" variant="light-outline">禁用</TTag>
35
- <TTag v-else-if="row.state === 0" shape="round" theme="danger" variant="light-outline">删除</TTag>
36
- </template>
37
- <template #operation="{ row }">
38
- <TDropdown trigger="click" placement="bottom-right" @click="(data) => $Method.onAction(data.value, row)">
39
- <TButton theme="primary" size="small">
40
- 操作
41
- <template #suffix> <ILucideChevronDown /></template>
42
- </TButton>
43
- <TDropdownMenu slot="dropdown">
44
- <TDropdownItem value="upd">
45
- <ILucidePencil />
46
- 编辑
47
- </TDropdownItem>
48
- <TDropdownItem value="del" :divider="true">
49
- <ILucideTrash2 />
50
- 删除
51
- </TDropdownItem>
52
- </TDropdownMenu>
53
- </TDropdown>
54
- </template>
55
- </TTable>
56
- </div>
57
-
58
- <div class="main-detail">
59
- <DetailPanel :data="$Data.currentRow" :fields="$Data.columns" />
60
- </div>
61
- </div>
62
-
63
- <div class="main-page">
64
- <TPagination :current-page="$Data.pagerConfig.currentPage" :page-size="$Data.pagerConfig.limit" :total="$Data.pagerConfig.total" @current-change="$Method.onPageChange" @page-size-change="$Method.handleSizeChange" />
65
- </div>
66
-
67
- <!-- 编辑对话框组件 -->
68
- <EditDialog v-if="$Data.editVisible" v-model="$Data.editVisible" :action-type="$Data.actionType" :row-data="$Data.rowData" @success="$Method.apiAdminList" />
69
- </div>
31
+ <TDropdownMenu slot="dropdown">
32
+ <TDropdownItem value="upd">
33
+ <ILucidePencil />
34
+ 编辑
35
+ </TDropdownItem>
36
+ <TDropdownItem value="del" :divider="true">
37
+ <ILucideTrash2 />
38
+ 删除
39
+ </TDropdownItem>
40
+ </TDropdownMenu>
41
+ </TDropdown>
42
+ </template>
43
+
44
+ <template #dialogs="scope">
45
+ <EditDialog v-if="$Data.editVisible" v-model="$Data.editVisible" :action-type="$Data.actionType" :row-data="$Data.rowData" @success="onDialogSuccess(scope.reload)" />
46
+ </template>
47
+ </PagedTableDetail>
70
48
  </template>
71
49
 
72
50
  <script setup lang="ts">
73
- import { Button as TButton, Table as TTable, Tag as TTag, Dropdown as TDropdown, DropdownMenu as TDropdownMenu, DropdownItem as TDropdownItem, Pagination as TPagination, MessagePlugin, DialogPlugin } from "tdesign-vue-next";
51
+ import { Button as TButton, Dropdown as TDropdown, DropdownItem as TDropdownItem, DropdownMenu as TDropdownMenu, Tag as TTag } from "tdesign-vue-next";
74
52
  import ILucidePlus from "~icons/lucide/plus";
75
53
  import ILucideRotateCw from "~icons/lucide/rotate-cw";
76
54
  import ILucidePencil from "~icons/lucide/pencil";
77
55
  import ILucideTrash2 from "~icons/lucide/trash-2";
78
56
  import ILucideChevronDown from "~icons/lucide/chevron-down";
79
57
  import EditDialog from "./components/edit.vue";
80
- import DetailPanel from "@/components/DetailPanel.vue";
81
- import { $Http } from "@/plugins/http";
58
+ import PagedTableDetail from "@/components/pagedTableDetail.vue";
82
59
  import { withDefaultColumns } from "befly-shared/utils/withDefaultColumns";
83
60
 
84
61
  // 响应式数据
85
62
  const $Data = $ref({
86
- tableData: [],
87
- loading: false,
88
63
  columns: withDefaultColumns([
89
64
  { colKey: "username", title: "用户名", fixed: "left" },
90
65
  { colKey: "nickname", title: "昵称" },
@@ -92,147 +67,56 @@ const $Data = $ref({
92
67
  { colKey: "state", title: "状态" },
93
68
  { colKey: "operation", title: "操作" }
94
69
  ]),
95
- pagerConfig: {
96
- currentPage: 1,
97
- limit: 30,
98
- total: 0,
99
- align: "right",
100
- layout: "total, prev, pager, next, jumper"
70
+ endpoints: {
71
+ list: {
72
+ path: "/addon/admin/admin/list",
73
+ dropValues: [""]
74
+ },
75
+ delete: {
76
+ path: "/addon/admin/admin/del",
77
+ idKey: "id",
78
+ confirm: (row) => ({
79
+ header: "确认删除",
80
+ body: `确认删除“${row.username}”吗?`,
81
+ confirmBtn: "删除"
82
+ })
83
+ }
101
84
  },
102
85
  editVisible: false,
103
86
  actionType: "add",
104
- rowData: {},
105
- currentRow: null,
106
- activeRowKeys: []
87
+ rowData: {}
107
88
  });
108
89
 
109
- // 方法
110
- const $Method = {
111
- async initData() {
112
- await $Method.apiAdminList();
113
- },
114
-
115
- // 加载管理员列表
116
- async apiAdminList() {
117
- $Data.loading = true;
118
- try {
119
- const res = await $Http.post(
120
- "/addon/admin/admin/list",
121
- {
122
- page: $Data.pagerConfig.currentPage,
123
- limit: $Data.pagerConfig.limit
124
- },
125
- {
126
- dropValues: [""]
127
- }
128
- );
129
- $Data.tableData = res.data.lists || [];
130
- $Data.pagerConfig.total = res.data.total || 0;
131
-
132
- // 自动高亮第一行
133
- if ($Data.tableData.length > 0) {
134
- $Data.currentRow = $Data.tableData[0];
135
- $Data.activeRowKeys = [$Data.tableData[0].id];
136
- } else {
137
- $Data.currentRow = null;
138
- $Data.activeRowKeys = [];
139
- }
140
- } catch (error) {
141
- MessagePlugin.error("加载数据失败");
142
- } finally {
143
- $Data.loading = false;
144
- }
145
- },
90
+ function onAdd(): void {
91
+ onAction("add", {});
92
+ }
146
93
 
147
- // 删除管理员
148
- async apiAdminDel(row) {
149
- let dialog = null;
150
- let destroyed = false;
94
+ function onReload(reload: (options: { keepSelection?: boolean }) => void): void {
95
+ reload({ keepSelection: true });
96
+ }
151
97
 
152
- const destroy = () => {
153
- if (destroyed) return;
154
- destroyed = true;
155
- if (dialog && typeof dialog.destroy === "function") {
156
- dialog.destroy();
157
- }
158
- };
159
-
160
- dialog = DialogPlugin.confirm({
161
- header: "确认删除",
162
- body: `确认删除管理员“${row.username}”吗?`,
163
- status: "warning",
164
- confirmBtn: "删除",
165
- cancelBtn: "取消",
166
- onConfirm: async () => {
167
- if (dialog && typeof dialog.setConfirmLoading === "function") {
168
- dialog.setConfirmLoading(true);
169
- }
170
-
171
- try {
172
- await $Http.post("/addon/admin/admin/del", {
173
- id: row.id
174
- });
175
- MessagePlugin.success("删除成功");
176
- destroy();
177
- await $Method.apiAdminList();
178
- } catch (error) {
179
- MessagePlugin.error("删除失败");
180
- } finally {
181
- if (dialog && typeof dialog.setConfirmLoading === "function") {
182
- dialog.setConfirmLoading(false);
183
- }
184
- }
185
- },
186
- onClose: () => {
187
- destroy();
188
- }
189
- });
190
- },
98
+ function onDialogSuccess(reload: (options: { keepSelection?: boolean }) => void): void {
99
+ reload({ keepSelection: true });
100
+ }
191
101
 
192
- // 刷新
193
- handleRefresh() {
194
- $Method.apiAdminList();
195
- },
196
-
197
- // 分页改变
198
- onPageChange({ currentPage }) {
199
- $Data.pagerConfig.currentPage = currentPage;
200
- $Method.apiAdminList();
201
- },
202
-
203
- // 每页条数改变
204
- handleSizeChange({ pageSize }) {
205
- $Data.pagerConfig.limit = pageSize;
206
- $Data.pagerConfig.currentPage = 1;
207
- $Method.apiAdminList();
208
- },
209
-
210
- // 高亮行变化
211
- onActiveChange(value, context) {
212
- // 禁止取消高亮:如果新值为空,保持当前选中
213
- if (value.length === 0 && $Data.activeRowKeys.length > 0) {
214
- return;
215
- }
216
- $Data.activeRowKeys = value;
217
- // 更新当前高亮的行数据
218
- if (context.activeRowList && context.activeRowList.length > 0) {
219
- $Data.currentRow = context.activeRowList[0].row;
220
- }
221
- },
222
-
223
- // 操作菜单点击
224
- onAction(command, rowData) {
225
- $Data.actionType = command;
226
- $Data.rowData = rowData;
227
- if (command === "add" || command === "upd") {
228
- $Data.editVisible = true;
229
- } else if (command === "del") {
230
- $Method.apiAdminDel(rowData);
231
- }
102
+ function onAction(command: string, rowData: Record<string, unknown>): void {
103
+ $Data.actionType = command;
104
+ $Data.rowData = rowData;
105
+ if (command === "add" || command === "upd") {
106
+ $Data.editVisible = true;
232
107
  }
233
- };
234
-
235
- $Method.initData();
108
+ }
109
+
110
+ function onDropdownAction(data: unknown, rowData: Record<string, unknown>, deleteRow: (row: Record<string, unknown>) => void): void {
111
+ const record = data as Record<string, unknown>;
112
+ const rawValue = record && record["value"] ? record["value"] : "";
113
+ const cmd = rawValue ? String(rawValue) : "";
114
+ if (cmd === "del") {
115
+ deleteRow(rowData);
116
+ return;
117
+ }
118
+ onAction(cmd, rowData);
119
+ }
236
120
  </script>
237
121
 
238
122
  <style scoped lang="scss">