jmash-core-mp 0.1.0

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 (58) hide show
  1. package/.editorconfig +9 -0
  2. package/README.md +49 -0
  3. package/babel.config.json +34 -0
  4. package/eslint.config.mjs +7 -0
  5. package/mpx.config.js +27 -0
  6. package/package.json +69 -0
  7. package/postcss.config.js +11 -0
  8. package/project.config.json +25 -0
  9. package/project.private.config.json +14 -0
  10. package/public/index.html +15 -0
  11. package/src/api/auth/index.ts +166 -0
  12. package/src/api/auth/types.ts +115 -0
  13. package/src/api/cms/index.ts +58 -0
  14. package/src/api/cms/types.ts +137 -0
  15. package/src/api/constant.ts +6 -0
  16. package/src/api/dict/index.ts +44 -0
  17. package/src/api/dict/types.ts +50 -0
  18. package/src/api/dicts.ts +72 -0
  19. package/src/api/files/index.ts +177 -0
  20. package/src/api/files/types.ts +68 -0
  21. package/src/api/index.ts +11 -0
  22. package/src/api/myorgan/index.ts +0 -0
  23. package/src/api/myorgan/types.ts +43 -0
  24. package/src/app.mpx +62 -0
  25. package/src/components/auth-user/jmash-update-user/index.mpx +31 -0
  26. package/src/components/auth-user/jmash-user.mpx +119 -0
  27. package/src/components/common/auth-avatar/avatar-edit/index.mpx +31 -0
  28. package/src/components/common/auth-search/index.mpx +31 -0
  29. package/src/components/common/jmash-tab-bar.mpx +50 -0
  30. package/src/components/core/jmash-cms-protocol.mpx +67 -0
  31. package/src/components/core/jmash-login.mpx +343 -0
  32. package/src/components/core/jmash-popup-login.mpx +414 -0
  33. package/src/custom-tab-bar/index.mpx +11 -0
  34. package/src/global.d.ts +4 -0
  35. package/src/index.ts +9 -0
  36. package/src/packages/index.mpx +8 -0
  37. package/src/packages/pages/auth/cms-protocol.mpx +31 -0
  38. package/src/packages/pages/auth/login.mpx +53 -0
  39. package/src/pages/basic-component.mpx +31 -0
  40. package/src/pages/half-home.mpx +58 -0
  41. package/src/pages/home.mpx +38 -0
  42. package/src/pages/index.mpx +45 -0
  43. package/src/pages/tabbar/home.mpx +69 -0
  44. package/src/pages/tabbar/my.mpx +40 -0
  45. package/src/stores/setup.js +15 -0
  46. package/src/styles/index.scss +27 -0
  47. package/src/types/core.ts +41 -0
  48. package/src/utils/auth.ts +270 -0
  49. package/src/utils/config.ts +31 -0
  50. package/src/utils/db.ts +100 -0
  51. package/src/utils/grpc.ts +21 -0
  52. package/src/utils/request.ts +123 -0
  53. package/static/ali/mini.project.json +4 -0
  54. package/static/dd/project.config.json +15 -0
  55. package/static/swan/project.swan.json +14 -0
  56. package/static/tt/project.config.json +11 -0
  57. package/static/wx/project.config.json +41 -0
  58. package/uno.config.js +9 -0
@@ -0,0 +1,414 @@
1
+ <template>
2
+ <!-- 授权登录 -->
3
+ <cube-modal wx:ref="loginModal" visible="{{ isVisible }}" noBuiltInBtns="true" maskClosable="false" z-index="999"
4
+ bind:cancel="onClose" mask-opacity="0.1" class="login-popup">
5
+ <!-- 标题区域 -->
6
+ <view class="popup-header" slot="header">
7
+ <view class="header-info">
8
+ <h2 class="header-title">授权登录{{ appName }}</h2>
9
+ </view>
10
+ <view class="header-close" bind:tap="onClose">
11
+ <text class="close-icon">×</text>
12
+ </view>
13
+ </view>
14
+
15
+ <!-- 内容区域 -->
16
+ <view class="popup-body" slot="content">
17
+ <!-- 头像区域 -->
18
+ <view class="avatar-section">
19
+ <button class="avatar-wrapper" open-type="chooseAvatar" bind:chooseavatar="onChooseAvatar">
20
+ <image class="avatar-img" src="{{ avatarUrl || avatar }}" mode="aspectFill" />
21
+ <view class="avatar-badge">
22
+ <text class="badge-icon">✎</text>
23
+ </view>
24
+ </button>
25
+ <text class="avatar-tip">点击更换头像</text>
26
+ </view>
27
+
28
+ <!-- 姓名输入 -->
29
+ <view class="name-section">
30
+ <view class="name-field">
31
+ <text class="field-icon">👤</text>
32
+ <input type="nickname" class="weui-input name-input" bind:input="validate" placeholder="请输入您的姓名"
33
+ wx:model="{{ nameValue }}" wx:model-prop="value" />
34
+ </view>
35
+ </view>
36
+
37
+ <!-- 协议勾选 -->
38
+ <view class="agreement-row">
39
+ <cube-checkbox wx:model="{{ isCheckBox }}" bind:input="validate" wx:model-prop="value" shape="square"
40
+ class="agreement-check">
41
+ <text class="agreement-text">已阅读</text>
42
+ <text id="user" class="agreement-link" catch:tap="goInfo">《用户使用协议》</text>
43
+ <text class="agreement-text">和</text>
44
+ <text id="privacy" class="agreement-link" catch:tap="goInfo">《隐私权政策》</text>
45
+ </cube-checkbox>
46
+ </view>
47
+ <!-- 按钮组 -->
48
+ <view class="action-area">
49
+ <button primary wx:if="{{ !isValidate }}" bind:tap="onLogin" class="btn-login">
50
+ 授权登录
51
+ </button>
52
+ <button wx:else open-type="getPhoneNumber" bind:getphonenumber="wxPhoneLogin" class="btn-login">
53
+ 授权登录
54
+ </button>
55
+ <view class="btn-cancel" bind:tap="onClose">
56
+ <text class="cancel-text">暂不登录</text>
57
+ </view>
58
+ </view>
59
+ </view>
60
+ </cube-modal>
61
+ </template>
62
+
63
+ <script>
64
+ import mpx, { createComponent, ref, watch, toRefs } from '@mpxjs/core'
65
+ import { auth } from '../../utils/auth'
66
+
67
+ createComponent({
68
+ properties: {
69
+ visible: {
70
+ type: Boolean,
71
+ value: false,
72
+ }
73
+ },
74
+ setup(props) {
75
+ let config = getApp().globalData.config || { resourceUrl: '', name: '应用' };
76
+ let resourceUrl = config.resourceUrl;
77
+ let appName = config.name;
78
+ let avatar = resourceUrl + "/images/components/gray_head2x.png";
79
+ let avatarUrl = ref('');
80
+ let nameValue = ref('');
81
+ let isCheckBox = ref(false);
82
+ let isValidate = ref(false);
83
+ let isVisible = ref(props.visible);
84
+
85
+ const { visible } = toRefs(props)
86
+ watch(visible, (newVal) => {
87
+ isVisible.value = newVal;
88
+ if (newVal) {
89
+ avatarUrl.value = '';
90
+ nameValue.value = '';
91
+ isCheckBox.value = false;
92
+ isValidate.value = false;
93
+ }
94
+ });
95
+
96
+ return {
97
+ resourceUrl,
98
+ appName,
99
+ avatar,
100
+ avatarUrl,
101
+ nameValue,
102
+ isCheckBox,
103
+ isValidate,
104
+ isVisible
105
+ };
106
+ },
107
+ methods: {
108
+ onClose() {
109
+ this.isVisible = false;
110
+ this.triggerEvent('close', false);
111
+ },
112
+
113
+ onLogin() {
114
+ let res = this.validate();
115
+ if (!res.validate) {
116
+ mpx.showToast({
117
+ icon: "error",
118
+ title: res.message,
119
+ duration: 2000
120
+ });
121
+ }
122
+ },
123
+
124
+ onChooseAvatar(e) {
125
+ this.avatarUrl = e.detail.avatarUrl;
126
+ this.validate();
127
+ },
128
+
129
+ wxPhoneLogin(event) {
130
+ console.log(event);
131
+ if (event.detail.errMsg === "getPhoneNumber:ok") {
132
+ mpx.showLoading({
133
+ title: '登录中...',
134
+ mask: true
135
+ });
136
+
137
+ auth.phoneCodeLogin(event.detail.code, this.nameValue).then((resp) => {
138
+ mpx.hideLoading();
139
+ if (resp.status) {
140
+ this.triggerEvent("status", resp.status);
141
+ this.onClose();
142
+ } else {
143
+ mpx.showToast({
144
+ icon: "error",
145
+ title: resp.message || '登录失败',
146
+ duration: 2000
147
+ });
148
+ }
149
+ }).catch(() => {
150
+ mpx.hideLoading();
151
+ mpx.showToast({
152
+ icon: "error",
153
+ title: '登录失败,请重试',
154
+ duration: 2000
155
+ });
156
+ });
157
+ }
158
+ },
159
+
160
+ validate() {
161
+ if (!this.isCheckBox) {
162
+ this.isValidate = false;
163
+ return { validate: false, message: "请阅读并同意用户协议" };
164
+ }
165
+ if (!this.nameValue) {
166
+ this.isValidate = false;
167
+ return { validate: false, message: "请输入姓名" };
168
+ }
169
+ this.isValidate = true;
170
+ return { validate: true, message: "" };
171
+ },
172
+
173
+ goInfo(e) {
174
+ const id = e.currentTarget?.id;
175
+ this.triggerEvent("policy", id);
176
+ mpx.navigateTo({
177
+ url: '/jmash/pages/auth/cms-protocol?channelAlias=' + id
178
+ });
179
+ },
180
+ }
181
+ })
182
+ </script>
183
+
184
+ <style lang="scss">
185
+ $primary: #07c160;
186
+ $primary-dark: #06ad56;
187
+ $text-1: #111111;
188
+ $text-2: #555555;
189
+ $text-3: #888888;
190
+ $border: #d0d0d0;
191
+ $bg-page: #f2f2f2;
192
+ $bg-card: #ffffff;
193
+
194
+ .login-popup {
195
+ background-color: $bg-card !important;
196
+ border-radius: 32rpx 32rpx 0 0 !important;
197
+ overflow: hidden;
198
+ }
199
+
200
+ .popup-header {
201
+ display: flex;
202
+ align-items: center;
203
+ justify-content: space-between;
204
+ padding: 36rpx 40rpx 28rpx;
205
+ background: $bg-card;
206
+
207
+ .header-info {
208
+ flex: 1;
209
+
210
+ .header-title {
211
+ font-size: 36rpx;
212
+ font-weight: 700;
213
+ color: $text-1;
214
+ margin: 0;
215
+ letter-spacing: 1rpx;
216
+ }
217
+ }
218
+
219
+ .header-close {
220
+ width: 56rpx;
221
+ height: 56rpx;
222
+ display: flex;
223
+ align-items: center;
224
+ justify-content: center;
225
+ background: $bg-page;
226
+ border-radius: 50%;
227
+
228
+ .close-icon {
229
+ font-size: 28rpx;
230
+ color: $text-3;
231
+ line-height: 1;
232
+ margin-top: -2rpx;
233
+ }
234
+ }
235
+ }
236
+
237
+ .popup-body {
238
+ padding: 20rpx 48rpx 56rpx;
239
+ background: $bg-card;
240
+ }
241
+
242
+ .avatar-section {
243
+ display: flex;
244
+ flex-direction: column;
245
+ align-items: center;
246
+ margin-bottom: 48rpx;
247
+
248
+ .avatar-wrapper {
249
+ position: relative;
250
+ margin: 0;
251
+ padding: 0;
252
+ background: none;
253
+ border: none;
254
+ line-height: 1;
255
+
256
+ .avatar-img {
257
+ width: 120rpx;
258
+ height: 120rpx;
259
+ border-radius: 50%;
260
+ border: 4rpx solid $border;
261
+ display: block;
262
+ }
263
+
264
+ .avatar-badge {
265
+ position: absolute;
266
+ right: 0;
267
+ bottom: 0;
268
+ width: 36rpx;
269
+ height: 36rpx;
270
+ background: $primary;
271
+ border-radius: 50%;
272
+ border: 3rpx solid $bg-card;
273
+ display: flex;
274
+ align-items: center;
275
+ justify-content: center;
276
+
277
+ .badge-icon {
278
+ font-size: 16rpx;
279
+ color: #fff;
280
+ }
281
+ }
282
+ }
283
+
284
+ .avatar-tip {
285
+ margin-top: 16rpx;
286
+ font-size: 20rpx;
287
+ color: $text-3;
288
+ }
289
+ }
290
+
291
+ .name-section {
292
+ margin-bottom: 36rpx;
293
+
294
+ .name-field {
295
+ display: flex;
296
+ align-items: center;
297
+ background: $bg-page;
298
+ border-radius: 20rpx;
299
+ padding: 0 28rpx;
300
+ height: 88rpx;
301
+ border: 2rpx solid transparent;
302
+ transition: border-color 0.25s, background-color 0.25s;
303
+
304
+ .field-icon {
305
+ font-size: 32rpx;
306
+ margin-right: 16rpx;
307
+ flex-shrink: 0;
308
+ }
309
+
310
+ .name-input {
311
+ flex: 1;
312
+ min-width: 0;
313
+ font-size: 28rpx;
314
+ color: $text-1;
315
+ background: transparent;
316
+ border: none;
317
+ outline: none;
318
+ height: 80rpx;
319
+ line-height: 80rpx;
320
+ padding: 0;
321
+ }
322
+
323
+ .name-input::placeholder {
324
+ font-size: 28rpx;
325
+ color: $text-3;
326
+ }
327
+ }
328
+
329
+ .name-field:focus-within {
330
+ border-color: $primary;
331
+ background: $bg-card;
332
+ }
333
+ }
334
+
335
+ .agreement-row {
336
+ margin-bottom: 48rpx;
337
+ padding: 0 4rpx;
338
+
339
+ .agreement-check {
340
+ font-size: 24rpx;
341
+
342
+ .agreement-text {
343
+ color: $text-3;
344
+ font-size: 24rpx;
345
+ }
346
+
347
+ .agreement-link {
348
+ color: $primary;
349
+ font-size: 24rpx;
350
+ }
351
+ }
352
+ }
353
+
354
+ .action-area {
355
+ display: flex;
356
+ flex-direction: column;
357
+ align-items: center;
358
+
359
+ .btn-login {
360
+ width: 100%;
361
+ height: 88rpx;
362
+ border-radius: 44rpx;
363
+ font-size: 30rpx;
364
+ font-weight: 600;
365
+ letter-spacing: 2rpx;
366
+ background-color: $primary;
367
+ color: #fff;
368
+
369
+ &.btn-disabled {
370
+ opacity: 0.5;
371
+ }
372
+ }
373
+
374
+ .btn-cancel {
375
+ margin-top: 28rpx;
376
+ padding: 16rpx 32rpx;
377
+
378
+ .cancel-text {
379
+ font-size: 26rpx;
380
+ color: $text-3;
381
+ }
382
+ }
383
+ }
384
+
385
+ :deep(.cube-button--primary) {
386
+ background-color: $primary;
387
+ border: none;
388
+
389
+ &:active {
390
+ background-color: $primary-dark;
391
+ }
392
+ }
393
+
394
+ :deep(.cube-checkbox__input) {
395
+ margin-right: 8rpx;
396
+ }
397
+
398
+ :deep(.cube-checkbox__content) {
399
+ font-size: 20rpx;
400
+ line-height: 1.5;
401
+ }
402
+ </style>
403
+
404
+ <script type="application/json">
405
+ {
406
+ "component": true,
407
+ "usingComponents": {
408
+ "cube-modal": "@mpxjs/mpx-cube-ui/lib/components/modal/index.mpx",
409
+ "cube-button": "@mpxjs/mpx-cube-ui/lib/components/button/index.mpx",
410
+ "cube-checkbox": "@mpxjs/mpx-cube-ui/lib/components/checkbox/index.mpx",
411
+ "cube-checkbox-group": "@mpxjs/mpx-cube-ui/lib/components/checkbox-group/index.mpx"
412
+ }
413
+ }
414
+ </script>
@@ -0,0 +1,11 @@
1
+ <template>
2
+ <jmash-tab-bar wx:ref="tabBar"></jmash-tab-bar>
3
+ </template>
4
+ <style lang="scss"></style>
5
+ <script type="application/json">
6
+ {
7
+ "usingComponents": {
8
+ "jmash-tab-bar": "../components/common/jmash-tab-bar.mpx"
9
+ }
10
+ }
11
+ </script>
@@ -0,0 +1,4 @@
1
+ declare module '*?fallback=true' {
2
+ const URL: string
3
+ export default URL
4
+ }
package/src/index.ts ADDED
@@ -0,0 +1,9 @@
1
+ export type { AppConfig, ResponseData, ValidateData } from "./types/core";
2
+ export { mpxFetch } from "./utils/request";
3
+ export { config } from "./utils/config";
4
+ export { grpc } from "./utils/grpc";
5
+ export { auth } from "./utils/auth";
6
+ export { authApi } from "./api/auth";
7
+ export { cmsApi } from "./api/cms";
8
+ export { dictApi } from "./api/dict";
9
+ export { fileApi } from "./api/files";
@@ -0,0 +1,8 @@
1
+ <script type="application/json">
2
+ {
3
+ "pages": [
4
+ "./pages/auth/login",
5
+ "./pages/auth/cms-protocol"
6
+ ]
7
+ }
8
+ </script>
@@ -0,0 +1,31 @@
1
+ <template>
2
+ <cms-protocol siteId="{{ siteId }}" tenant="{{ tenant }}" channelAlias="{{ channelAlias }}"></cms-protocol>
3
+ </template>
4
+
5
+ <script>
6
+ import { createPage, ref, onLoad } from '@mpxjs/core'
7
+
8
+ createPage({
9
+ setup(props) {
10
+ console.log("123", props);
11
+ let siteId = ref(getApp().globalData.config.siteId);
12
+ let tenant = ref(getApp().globalData.config.tenant);
13
+ let channelAlias = ref('');
14
+ onLoad((options) => {
15
+ console.log(options);
16
+ channelAlias.value = options.channelAlias || 'privacy';
17
+ })
18
+ return { siteId, tenant, channelAlias };
19
+ },
20
+ })
21
+ </script>
22
+
23
+ <style lang="scss"></style>
24
+
25
+ <script type="application/json">
26
+ {
27
+ "usingComponents": {
28
+ "cms-protocol": "../../../components/core/jmash-cms-protocol.mpx"
29
+ }
30
+ }
31
+ </script>
@@ -0,0 +1,53 @@
1
+ <template>
2
+ <jmash-login tabbar="{{ tabbar }}" backurl="{{ backurl }}">
3
+ <image mode="widthFix" src="{{ resourceUrl }}/images/login_bg.png" class="index_bj" slot="before">
4
+ <image mode="widthFix" src="{{ resourceUrl }}/images/logo.png" class="login_logo"></image>
5
+ </image>
6
+ <view class="tips_box" slot="after">温馨提示:{{ appName }}手机号授权登录。</view>
7
+ </jmash-login>
8
+ </template>
9
+
10
+ <script>
11
+ import { createPage, ref } from '@mpxjs/core'
12
+
13
+ createPage({
14
+ setup(props) {
15
+ let resourceUrl = ref(getApp().globalData.config.resourceUrl);
16
+ let appName = ref(getApp().globalData.config.name);
17
+ let tabbar = ref(props.tabbar === 'true');
18
+ let backurl = ref(props.backurl || '');
19
+ console.log(appName);
20
+ return { resourceUrl, appName, tabbar, backurl };
21
+ }
22
+ })
23
+ </script>
24
+
25
+ <style lang="scss">
26
+ .index_bj {
27
+ width: 100%;
28
+ position: relative;
29
+
30
+ .login_logo {
31
+ position: absolute;
32
+ left: 50%;
33
+ top: 20%;
34
+ transform: translateX(-50%);
35
+ width: 120rpx;
36
+ }
37
+ }
38
+
39
+ .tips_box {
40
+ text-align: center;
41
+ color: #666;
42
+ font-size: 24rpx;
43
+ margin-top: 40rpx;
44
+ }
45
+ </style>
46
+
47
+ <script type="application/json">
48
+ {
49
+ "usingComponents": {
50
+ "jmash-login": "../../../components/core/jmash-login.mpx"
51
+ }
52
+ }
53
+ </script>
@@ -0,0 +1,31 @@
1
+ <template>
2
+ <view class="basic-component-page">
3
+ <text>{{ title }}</text>
4
+ </view>
5
+ </template>
6
+
7
+ <script>
8
+ import { createPage, ref } from '@mpxjs/core'
9
+
10
+ createPage({
11
+ setup(props, context) {
12
+ let title = ref('组件演示');
13
+ return { title };
14
+ },
15
+ })
16
+ </script>
17
+
18
+ <style lang="scss">
19
+ .basic-component-page {
20
+ padding: 40rpx;
21
+ text-align: center;
22
+ font-size: 32rpx;
23
+ color: #333;
24
+ }
25
+ </style>
26
+
27
+ <script type="application/json">
28
+ {
29
+ "usingComponents": {}
30
+ }
31
+ </script>
@@ -0,0 +1,58 @@
1
+ <template>
2
+ <view class="half-home-page">
3
+ <text>主页(半屏登录)</text>
4
+ <!-- 授权登录弹出层 -->
5
+ <jmash-popup-login visible="{{ isLoginPopup }}" bind:close="onLoginClose"
6
+ bind:status="loginSuccess"></jmash-popup-login>
7
+ </view>
8
+ </template>
9
+
10
+ <script>
11
+ import { createPage, ref } from '@mpxjs/core'
12
+ import { auth } from "../utils/auth.ts";
13
+
14
+ createPage({
15
+ setup() {
16
+ let isLoginPopup = ref(false);
17
+ //检查登录并跳转登录
18
+ auth.loginOnlyAC().then(() => {
19
+ //登录成功,加载业务
20
+ console.log("静默登录成功");
21
+ }).catch(() => {
22
+ isLoginPopup.value = true;
23
+ });
24
+ return { isLoginPopup };
25
+ },
26
+ methods: {
27
+ // 授权登录弹出层关闭
28
+ onLoginClose: function (visible) {
29
+ console.log(visible.detail);
30
+ this.isLoginPopup = visible.detail;
31
+ },
32
+ // 登录成功
33
+ loginSuccess: function (status) {
34
+ if (status.detail) {
35
+ // 登录成功,加载业务
36
+ console.log("登录成功");
37
+ }
38
+ }
39
+ }
40
+ })
41
+ </script>
42
+
43
+ <style lang="scss">
44
+ .half-home-page {
45
+ padding: 40rpx;
46
+ text-align: center;
47
+ font-size: 32rpx;
48
+ color: #333;
49
+ }
50
+ </style>
51
+
52
+ <script type="application/json">
53
+ {
54
+ "usingComponents": {
55
+ "jmash-popup-login": "../components/core/jmash-popup-login.mpx"
56
+ }
57
+ }
58
+ </script>
@@ -0,0 +1,38 @@
1
+ <template>
2
+ <view class="home-page">
3
+ <text>主页(全屏登录)</text>
4
+ </view>
5
+ </template>
6
+
7
+ <script>
8
+ import { createPage } from '@mpxjs/core'
9
+ import { db } from '../utils/db'
10
+ import mpx from '@mpxjs/core'
11
+
12
+ createPage({
13
+ setup() {
14
+ // 无法退回,检查登录状态
15
+ if (!db.getToken()) {
16
+ mpx.navigateTo({
17
+ url: '/jmash/pages/auth/login'
18
+ })
19
+ }
20
+ return {}
21
+ }
22
+ })
23
+ </script>
24
+
25
+ <style lang="scss">
26
+ .home-page {
27
+ padding: 40rpx;
28
+ text-align: center;
29
+ font-size: 32rpx;
30
+ color: #333;
31
+ }
32
+ </style>
33
+
34
+ <script type="application/json">
35
+ {
36
+ "usingComponents": {}
37
+ }
38
+ </script>
@@ -0,0 +1,45 @@
1
+ <template>
2
+ <view class="page-body">
3
+ <button type="default" bind:tap="toHomePage">Welcome to Jmash Core MP</button>
4
+ </view>
5
+ </template>
6
+
7
+ <script>
8
+ import mpx, { createPage } from '@mpxjs/core'
9
+
10
+ createPage({
11
+ setup(props, context) {
12
+ console.log(context.refs);
13
+ return {};
14
+ },
15
+ methods: {
16
+ toHomePage() {
17
+ mpx.switchTab({
18
+ url: '/pages/tabbar/home'
19
+ })
20
+ },
21
+ }
22
+ })
23
+ </script>
24
+
25
+ <style lang="scss">
26
+ .page-body {
27
+ display: flex;
28
+ flex-direction: column;
29
+ align-items: center;
30
+ padding: 40rpx;
31
+ gap: 30rpx;
32
+
33
+ button {
34
+ width: 80%;
35
+ height: 80rpx;
36
+ font-size: 32rpx;
37
+ }
38
+ }
39
+ </style>
40
+
41
+ <script type="application/json">
42
+ {
43
+ "usingComponents": {}
44
+ }
45
+ </script>