@mangjuxiong/elpis 1.0.2

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.
Files changed (81) hide show
  1. package/.babelrc +5 -0
  2. package/.eslintignore +3 -0
  3. package/.eslintrc +66 -0
  4. package/README.md +214 -0
  5. package/app/controller/base.js +39 -0
  6. package/app/controller/project.js +75 -0
  7. package/app/controller/view.js +19 -0
  8. package/app/extend/database.js +5 -0
  9. package/app/extend/logger.js +38 -0
  10. package/app/middleware/api-params-verify.js +74 -0
  11. package/app/middleware/api-sign-verify.js +36 -0
  12. package/app/middleware/error-handle.js +32 -0
  13. package/app/middleware/project-handler.js +26 -0
  14. package/app/middleware.js +39 -0
  15. package/app/pages/asserts/custom.css +14 -0
  16. package/app/pages/boot.js +48 -0
  17. package/app/pages/common/curl.js +80 -0
  18. package/app/pages/common/utils.js +34 -0
  19. package/app/pages/dashboard/complex-view/header-view/complex-view/sub-menu/sub-menu.vue +24 -0
  20. package/app/pages/dashboard/complex-view/header-view/head-view.vue +113 -0
  21. package/app/pages/dashboard/complex-view/iframe-view/iframe-view.vue +43 -0
  22. package/app/pages/dashboard/complex-view/schema-view/complex-view/search-panel/search-panel.vue +35 -0
  23. package/app/pages/dashboard/complex-view/schema-view/complex-view/table-panel/table-panel.vue +118 -0
  24. package/app/pages/dashboard/complex-view/schema-view/components/components-config.js +23 -0
  25. package/app/pages/dashboard/complex-view/schema-view/components/create-form/create-form.vue +85 -0
  26. package/app/pages/dashboard/complex-view/schema-view/components/detail-panel/detail-panel.vue +103 -0
  27. package/app/pages/dashboard/complex-view/schema-view/components/edit-form/edit-form.vue +112 -0
  28. package/app/pages/dashboard/complex-view/schema-view/hook/schema.js +124 -0
  29. package/app/pages/dashboard/complex-view/schema-view/schema-view.vue +90 -0
  30. package/app/pages/dashboard/complex-view/sider-view/complex-view/sub-menu/sub-menu.vue +22 -0
  31. package/app/pages/dashboard/complex-view/sider-view/sider-view.vue +113 -0
  32. package/app/pages/dashboard/dashboard.vue +85 -0
  33. package/app/pages/dashboard/entry.dashboard.js +46 -0
  34. package/app/pages/store/index.js +4 -0
  35. package/app/pages/store/menu.js +54 -0
  36. package/app/pages/store/project.js +13 -0
  37. package/app/pages/widgets/head-container/asserts/logo.png +0 -0
  38. package/app/pages/widgets/head-container/head-container.vue +77 -0
  39. package/app/pages/widgets/schema-form/complex-view/input/input.vue +134 -0
  40. package/app/pages/widgets/schema-form/complex-view/input-number/input-number.vue +135 -0
  41. package/app/pages/widgets/schema-form/complex-view/select/select.vue +120 -0
  42. package/app/pages/widgets/schema-form/form-item-config.js +23 -0
  43. package/app/pages/widgets/schema-form/schema-form.vue +130 -0
  44. package/app/pages/widgets/schema-search-bar/complex-view/date-range/date-range.vue +51 -0
  45. package/app/pages/widgets/schema-search-bar/complex-view/dynamic-select/dynamic-select.vue +55 -0
  46. package/app/pages/widgets/schema-search-bar/complex-view/input/input.vue +38 -0
  47. package/app/pages/widgets/schema-search-bar/complex-view/select/select.vue +40 -0
  48. package/app/pages/widgets/schema-search-bar/schema-search-bar.vue +100 -0
  49. package/app/pages/widgets/schema-search-bar/search-item-config.js +31 -0
  50. package/app/pages/widgets/schema-table/schema-table.vue +225 -0
  51. package/app/pages/widgets/sider-container/sider-container.vue +27 -0
  52. package/app/public/output/entry.page1.tpl +42 -0
  53. package/app/public/static/logo.jpg +0 -0
  54. package/app/public/static/normalize.css +267 -0
  55. package/app/router/project.js +6 -0
  56. package/app/router/view.js +7 -0
  57. package/app/router-schema/project.js +30 -0
  58. package/app/service/base.js +13 -0
  59. package/app/service/project.js +42 -0
  60. package/app/view/entry.tpl +25 -0
  61. package/app/webpack/config/babel.config.js +4 -0
  62. package/app/webpack/config/webpack.base.js +229 -0
  63. package/app/webpack/config/webpack.dev.js +57 -0
  64. package/app/webpack/config/webpack.prod.js +120 -0
  65. package/app/webpack/libs/blank.js +1 -0
  66. package/app/webpack/libs/dev.js +53 -0
  67. package/app/webpack/libs/prod.js +22 -0
  68. package/config/config.default.js +4 -0
  69. package/elpis-core/env.js +20 -0
  70. package/elpis-core/index.js +81 -0
  71. package/elpis-core/loader/config.js +52 -0
  72. package/elpis-core/loader/controller.js +59 -0
  73. package/elpis-core/loader/extend.js +47 -0
  74. package/elpis-core/loader/middleware.js +57 -0
  75. package/elpis-core/loader/router-schema.js +41 -0
  76. package/elpis-core/loader/router.js +42 -0
  77. package/elpis-core/loader/service.js +59 -0
  78. package/index.js +39 -0
  79. package/model/index.js +107 -0
  80. package/package.json +90 -0
  81. package/test/controller/project.test.js +194 -0
@@ -0,0 +1,51 @@
1
+ <template>
2
+ <el-date-picker
3
+ v-model="dtoValue"
4
+ v-bind="schema.options"
5
+ type="daterange"
6
+ range-separator="至"
7
+ :start-placeholder="`${schema.label}开始日期`"
8
+ :end-placeholder="`${schema.label}结束日期`"
9
+ class="date-range"
10
+ />
11
+ </template>
12
+
13
+ <script setup>
14
+ import { ref, onMounted } from 'vue';
15
+ import moment from 'moment';
16
+
17
+ const { schemaKey, schema } = defineProps({
18
+ schemaKey: { type: String, default: '' },
19
+ schema: { type: Object, default: () => ({}) },
20
+ });
21
+
22
+ const emit = defineEmits(['loaded']);
23
+
24
+ const dtoValue = ref();
25
+ const getValue = () => {
26
+ return dtoValue.value?.length === 2
27
+ ? {
28
+ [`${schemaKey}_start`]: moment(dtoValue.value[0]).format('YYYY-MM-DD'),
29
+ [`${schemaKey}_end`]: moment(dtoValue.value[1]).format('YYYY-MM-DD'),
30
+ }
31
+ : {};
32
+ };
33
+
34
+ onMounted(() => {
35
+ reset();
36
+ emit('loaded');
37
+ });
38
+
39
+ const reset = () => {
40
+ dtoValue.value = [];
41
+ };
42
+ defineExpose({
43
+ getValue,
44
+ reset,
45
+ });
46
+ </script>
47
+ <style lang="less" scoped>
48
+ .date-range {
49
+ width: 280px;
50
+ }
51
+ </style>
@@ -0,0 +1,55 @@
1
+ <template>
2
+ <el-select v-model="dtoValue" v-bind="schema.options" class="select">
3
+ <el-option v-for="(item, index) in enumList" :key="index" :label="item.label" :value="item.value" />
4
+ </el-select>
5
+ </template>
6
+
7
+ <script setup>
8
+ import { ref, onMounted } from 'vue';
9
+ import $curl from '@elpisCommon/curl';
10
+
11
+ const { schemaKey, schema } = defineProps({
12
+ schemaKey: { type: String, default: '' },
13
+ schema: { type: Object, default: () => ({}) },
14
+ });
15
+
16
+ const emit = defineEmits(['loaded']);
17
+
18
+ const dtoValue = ref();
19
+ const getValue = () => {
20
+ return dtoValue.value !== undefined
21
+ ? {
22
+ [schemaKey]: dtoValue.value,
23
+ }
24
+ : {};
25
+ };
26
+
27
+ const enumList = ref([]);
28
+ const fecthEnumList = async () => {
29
+ const res = await $curl({
30
+ url: schema?.options?.api,
31
+ method: 'get',
32
+ data: {},
33
+ errorMessage: '获取枚举列表失败',
34
+ });
35
+ if (res?.data?.length > 0) {
36
+ enumList.value.push(...res.data);
37
+ }
38
+ };
39
+
40
+ onMounted(async () => {
41
+ await fecthEnumList();
42
+ reset();
43
+ emit('loaded');
44
+ });
45
+
46
+ const reset = () => {
47
+ dtoValue.value = schema?.options?.default ?? enumList.value[0]?.value ?? '';
48
+ };
49
+ defineExpose({
50
+ getValue,
51
+ reset,
52
+ });
53
+ </script>
54
+
55
+ <style lang="less" scoped></style>
@@ -0,0 +1,38 @@
1
+ <template>
2
+ <el-input v-model="dtoValue" v-bind="schema.options" class="input" />
3
+ </template>
4
+
5
+ <script setup>
6
+ import { ref, onMounted } from 'vue';
7
+
8
+ const { schemaKey, schema } = defineProps({
9
+ schemaKey: { type: String, default: '' },
10
+ schema: { type: Object, default: () => ({}) },
11
+ });
12
+
13
+ const emit = defineEmits(['loaded']);
14
+
15
+ const dtoValue = ref();
16
+ const getValue = () => {
17
+ return dtoValue.value !== undefined
18
+ ? {
19
+ [schemaKey]: dtoValue.value,
20
+ }
21
+ : {};
22
+ };
23
+
24
+ onMounted(() => {
25
+ reset();
26
+ emit('loaded');
27
+ });
28
+
29
+ const reset = () => {
30
+ dtoValue.value = schema?.options?.default;
31
+ };
32
+ defineExpose({
33
+ getValue,
34
+ reset,
35
+ });
36
+ </script>
37
+
38
+ <style lang="less" scoped></style>
@@ -0,0 +1,40 @@
1
+ <template>
2
+ <el-select v-model="dtoValue" v-bind="schema.options" class="select">
3
+ <el-option v-for="(item, index) in schema.options?.enumList" :key="index" :label="item.label" :value="item.value" />
4
+ </el-select>
5
+ </template>
6
+
7
+ <script setup>
8
+ import { ref, onMounted } from 'vue';
9
+
10
+ const { schemaKey, schema } = defineProps({
11
+ schemaKey: { type: String, default: '' },
12
+ schema: { type: Object, default: () => ({}) },
13
+ });
14
+
15
+ const emit = defineEmits(['loaded']);
16
+
17
+ const dtoValue = ref();
18
+ const getValue = () => {
19
+ return dtoValue.value !== undefined
20
+ ? {
21
+ [schemaKey]: dtoValue.value,
22
+ }
23
+ : {};
24
+ };
25
+
26
+ onMounted(() => {
27
+ reset();
28
+ emit('loaded');
29
+ });
30
+
31
+ const reset = () => {
32
+ dtoValue.value = schema?.options?.default ?? schema?.options?.enumList[0]?.value ?? '';
33
+ };
34
+ defineExpose({
35
+ getValue,
36
+ reset,
37
+ });
38
+ </script>
39
+
40
+ <style lang="less" scoped></style>
@@ -0,0 +1,100 @@
1
+ <template>
2
+ <el-form v-if="schema && schema.properties" :inline="true" class="schema-search-bar">
3
+ <!-- 动态组件区域 -->
4
+ <el-form-item v-for="(SchemaItem, key) in schema.properties" :key="key" :label="SchemaItem.label">
5
+ <!-- 展示子组件 -->
6
+ <component
7
+ :is="SearchItemConfig[SchemaItem?.options?.comType]?.component"
8
+ ref="SearchComList"
9
+ :schema-key="key"
10
+ :schema="SchemaItem"
11
+ @loaded="handleChildLoaded"
12
+ />
13
+ </el-form-item>
14
+ <!-- 操作区域 -->
15
+ <el-form-item>
16
+ <el-button type="primary" plain class="search-btn" @click="search">搜索</el-button>
17
+ <el-button plain class="reset-btn" @click="reset">清除</el-button>
18
+ </el-form-item>
19
+ </el-form>
20
+ </template>
21
+
22
+ <script setup>
23
+ import { ref, toRefs } from 'vue';
24
+ import SearchItemConfig from './search-item-config';
25
+
26
+ const props = defineProps({
27
+ /**
28
+ * schema配置,结构如下
29
+ * type:'object',
30
+ properties:{
31
+ key:{
32
+ ...schema,// 标准的shema配置
33
+ type:'',// 字段类型
34
+ label:'',// 字段的中文名
35
+ // 字段在search-bar中的相关配置
36
+ options:{
37
+ ...eleComponentConfig,// 标准的el-component-column配置
38
+ comType: '',// 配置组件类型,input/select/...
39
+ default: '',// 默认值
40
+ }
41
+ },
42
+ */
43
+ schema: {
44
+ type: Object,
45
+ default: () => ({}),
46
+ },
47
+ });
48
+
49
+ const { schema } = toRefs(props);
50
+
51
+ const emit = defineEmits(['loaded', 'search', 'reset']);
52
+
53
+ const SearchComList = ref([]);
54
+
55
+ const getValue = () => {
56
+ let dtoObj = {};
57
+ SearchComList.value.forEach((item) => {
58
+ dtoObj = { ...dtoObj, ...item?.getValue() };
59
+ });
60
+ return dtoObj;
61
+ };
62
+ let childComLoadedCount = 0;
63
+ const handleChildLoaded = () => {
64
+ childComLoadedCount++;
65
+ if (childComLoadedCount === Object.keys(schema?.value?.properties).length) {
66
+ emit('loaded', getValue());
67
+ }
68
+ };
69
+
70
+ const search = () => {
71
+ emit('search', getValue());
72
+ };
73
+ const reset = () => {
74
+ SearchComList.value.forEach((item) => {
75
+ item?.reset();
76
+ });
77
+ emit('reset');
78
+ };
79
+
80
+ defineExpose({
81
+ reset,
82
+ search,
83
+ getValue,
84
+ });
85
+ </script>
86
+
87
+ <style lang="less">
88
+ .schema-search-bar {
89
+ min-width: 500px;
90
+ .search-btn {
91
+ width: 100px;
92
+ }
93
+ .reset-btn {
94
+ width: 100px;
95
+ }
96
+ .select {
97
+ width: 180px;
98
+ }
99
+ }
100
+ </style>
@@ -0,0 +1,31 @@
1
+ import input from './complex-view/input/input';
2
+ import select from './complex-view/select/select';
3
+ import dynamicSelect from './complex-view/dynamic-select/dynamic-select';
4
+ import dataRange from './complex-view/date-range/date-range';
5
+
6
+ // 业务拓展 search-item 配置
7
+ import BusinessSearchItemConfig from '@businessSearchItemConfig';
8
+
9
+ const SearchItemConfig = {
10
+ // 输入框
11
+ input: {
12
+ component: input,
13
+ },
14
+ // 选择框
15
+ select: {
16
+ component: select,
17
+ },
18
+ // 动态选择框
19
+ dynamicSelect: {
20
+ component: dynamicSelect,
21
+ },
22
+ // 日期选择
23
+ dateRange: {
24
+ component: dataRange,
25
+ },
26
+ };
27
+
28
+ export default {
29
+ ...SearchItemConfig,
30
+ ...BusinessSearchItemConfig,
31
+ };
@@ -0,0 +1,225 @@
1
+ <template>
2
+ <div class="schema-table">
3
+ <el-table v-if="schema && schema.properties" v-loading="loading" :data="tableData" class="table">
4
+ <template v-for="(schemaItem, key) in schema.properties">
5
+ <el-table-column
6
+ v-if="schemaItem.options.visible !== false"
7
+ :key="key"
8
+ :prop="key"
9
+ :label="schemaItem.label"
10
+ v-bind="schemaItem.options"
11
+ />
12
+ </template>
13
+ <el-table-column v-if="buttons?.length > 0" label="操作" fixed="right" :width="operationWidth">
14
+ <template #default="scope">
15
+ <el-button
16
+ v-for="(button, index) in buttons"
17
+ :key="index"
18
+ link
19
+ v-bind="button"
20
+ @click="operationHandler({ rowData: scope.row, btnConfig: button })"
21
+ >
22
+ {{ button.label }}
23
+ </el-button>
24
+ </template>
25
+ </el-table-column>
26
+ </el-table>
27
+ <el-row class="pagination" justify="end">
28
+ <el-pagination
29
+ :current-page="currentPage"
30
+ :page-size="pageSize"
31
+ :page-sizes="[10, 20, 50, 200]"
32
+ :total="total"
33
+ layout="total,sizes,prev,pager,next,jumper"
34
+ @current-change="onCurrentPageChange"
35
+ @size-change="onPageSizeChange"
36
+ />
37
+ </el-row>
38
+ </div>
39
+ </template>
40
+
41
+ <script setup>
42
+ import { ref, toRefs, computed, onMounted, nextTick, watch } from 'vue';
43
+ import $curl from '@elpisCommon/curl';
44
+ import utils from '@elpisCommon/utils';
45
+
46
+ const { debounce } = utils;
47
+
48
+ const props = defineProps({
49
+ /**
50
+ * schema配置, 结构如下
51
+ * {
52
+ type:'object',
53
+ properties:{
54
+ key:{
55
+ ...schema,// 标准的shema配置
56
+ type:'',// 字段类型
57
+ label:'',// 字段的中文名
58
+ // 字段在 table中的相关配置
59
+ options:{
60
+ ...elTableColumnConfig,// 标准的el-table-column配置
61
+ visible: true,// 是否在表单中显示,默认为true
62
+ }
63
+ },
64
+ ...
65
+ }
66
+ */
67
+ schema: {
68
+ type: Object,
69
+ required: true,
70
+ },
71
+ /**
72
+ * 表格数据源APi
73
+ */
74
+ api: {
75
+ type: String,
76
+ default: '',
77
+ },
78
+ /**
79
+ * 调用接口传参
80
+ */
81
+ apiParams: {
82
+ type: Object,
83
+ default: () => ({}),
84
+ },
85
+ /**
86
+ * 操作按钮相关配置,结构如下
87
+ * [{
88
+ label: '',// 按钮名称
89
+ eventKey: '',// 按钮事件key
90
+ eventOptions:{} // 按钮事件具体配置
91
+ ...elButtonConfig,// 标准的el-button配置
92
+ },...]
93
+ */
94
+ buttons: {
95
+ type: Array,
96
+ default: () => [],
97
+ },
98
+ });
99
+ const { schema, api, buttons, apiParams } = toRefs(props);
100
+
101
+ const emit = defineEmits(['operate']);
102
+
103
+ const operationWidth = computed(() => {
104
+ return buttons?.value?.length > 0
105
+ ? buttons.value.reduce((pre, cur) => {
106
+ return pre + cur.label.length * 18;
107
+ }, 50)
108
+ : 50;
109
+ });
110
+
111
+ const loading = ref(false);
112
+ const tableData = ref([]);
113
+ const currentPage = ref(1);
114
+ const pageSize = ref(10);
115
+ const total = ref(0);
116
+
117
+ onMounted(() => {
118
+ initData();
119
+ });
120
+ watch(
121
+ [api, schema, apiParams],
122
+ () => {
123
+ initData();
124
+ },
125
+ { deep: true }
126
+ );
127
+
128
+ const initData = () => {
129
+ currentPage.value = 1;
130
+ pageSize.value = 10;
131
+ nextTick(() => {
132
+ fetchTableData();
133
+ });
134
+ };
135
+
136
+ const fetchTableData = debounce(async () => {
137
+ if (!api.value) return;
138
+ showLoading();
139
+
140
+ // 请求table数据
141
+ const res = await $curl({
142
+ url: `${api.value}/list`,
143
+ method: 'get',
144
+ query: {
145
+ ...apiParams.value,
146
+ page: currentPage.value,
147
+ pageSize: pageSize.value,
148
+ },
149
+ });
150
+
151
+ hideLoading();
152
+
153
+ if (!res || !res.success || !Array.isArray(res.data)) {
154
+ tableData.value = [];
155
+ total.value = 0;
156
+ return;
157
+ }
158
+
159
+ tableData.value = buildTableData(res.data);
160
+ total.value = res.metadatas?.total ?? 0;
161
+ }, 500);
162
+
163
+ /**
164
+ * 对后端返回的数据进行渲染前的预处理
165
+ * @param {Array} data 后端返回的数据
166
+ */
167
+ const buildTableData = (listData) => {
168
+ if (!schema.value?.properties) return listData;
169
+ return listData.map((rowData) => {
170
+ Object.keys(rowData).forEach((key) => {
171
+ const schemaItem = schema.value.properties[key];
172
+ // 处理toFixed
173
+ if (schemaItem?.options?.toFixed) {
174
+ rowData[key] = rowData[key].toFixed && rowData[key].toFixed(schemaItem.options.toFixed);
175
+ }
176
+ });
177
+ return rowData;
178
+ });
179
+ };
180
+
181
+ const showLoading = () => {
182
+ loading.value = true;
183
+ };
184
+
185
+ const hideLoading = () => {
186
+ loading.value = false;
187
+ };
188
+
189
+ const operationHandler = ({ rowData, btnConfig }) => {
190
+ emit('operate', { rowData, btnConfig });
191
+ };
192
+
193
+ const onCurrentPageChange = (page) => {
194
+ currentPage.value = page;
195
+ fetchTableData();
196
+ };
197
+
198
+ const onPageSizeChange = (pageSize) => {
199
+ pageSize.value = pageSize;
200
+ fetchTableData();
201
+ };
202
+
203
+ defineExpose({
204
+ initData,
205
+ loadTableData: fetchTableData,
206
+ showLoading,
207
+ hideLoading,
208
+ });
209
+ </script>
210
+
211
+ <style lang="less" scoped>
212
+ .schema-table {
213
+ flex: 1;
214
+ display: flex;
215
+ flex-direction: column;
216
+ overflow: auto;
217
+ .table {
218
+ flex: 1;
219
+ }
220
+ .pagination {
221
+ margin: 10px 0;
222
+ text-align: right;
223
+ }
224
+ }
225
+ </style>
@@ -0,0 +1,27 @@
1
+ <template>
2
+ <el-container class="sider-container">
3
+ <el-aside width="200px" class="aside">
4
+ <slot name="menu-content"></slot>
5
+ </el-aside>
6
+ <el-main class="main">
7
+ <slot name="main-content"></slot>
8
+ </el-main>
9
+ </el-container>
10
+ </template>
11
+
12
+ <script setup></script>
13
+
14
+ <style lang="less" scoped>
15
+ .sider-container {
16
+ height: 100%;
17
+ .aside {
18
+ border-right: 1px solid #e8e8e8;
19
+ }
20
+ .main {
21
+ overflow: scroll;
22
+ }
23
+ }
24
+ :deep(.el-menu) {
25
+ border-right: none;
26
+ }
27
+ </style>
@@ -0,0 +1,42 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>{{name}}</title>
5
+ <link href="/static/normalize.css" rel="stylesheet"/>
6
+ <link href="/static/logo.jpg" rel="icon" type="image/x-icon"/>
7
+ </head>
8
+ <body style="color: red;">
9
+ <h1>Hello World 1</h1>
10
+ <input id="env" value="{{env}}" style="display: none;"/>
11
+ <input id="options" value="{{options}}" style="display: none;"/>
12
+ <button onclick="handleClick()">发送请求</button>
13
+ </body>
14
+ <script src="https://cdn.bootcss.com/axios/0.18.0/axios.min.js" type="text/javascript"></script>
15
+ <script src="https://cdn.jsdelivr.net/npm/js-md5@0.8.3/src/md5.min.js" type="text/javascript"></script>
16
+ <script type="text/javascript">
17
+ try{
18
+ window.env = document.getElementById("env").value;
19
+ console.log("🚀 ~ window.env:", window.env)
20
+ const options = document.getElementById("options").value;
21
+ window.options = JSON.parse(options);
22
+ console.log("🚀 ~ window.options:", window.options)
23
+ }catch(e){
24
+ console.log("🚀 ~ e:", e)
25
+ }
26
+ const handleClick = ()=>{
27
+ const signKey = 'mshsiksjksnnmsma'
28
+ const st = Date.now()
29
+ axios.request({
30
+ url: "/api/project/list",
31
+ method: "get",
32
+ params: {proj_key:1},
33
+ headers: {
34
+ "s_t": st,
35
+ "s_sign": md5(`${signKey}_${st}`)
36
+ }
37
+ }).then(res=>{
38
+ console.log("🚀 ~ res:", res)
39
+ })
40
+ }
41
+ </script>
42
+ </html>
Binary file