@uxda/appkit 4.0.3 → 4.0.14

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 (116) hide show
  1. package/.eslintrc.mjs +7 -7
  2. package/README.md +187 -187
  3. package/babel.config.js +12 -12
  4. package/package.json +75 -80
  5. package/project.config.json +15 -15
  6. package/project.tt.json +13 -13
  7. package/rollup.config.mjs +56 -56
  8. package/src/Appkit.ts +65 -65
  9. package/src/balance/api/endpoints.ts +126 -126
  10. package/src/balance/api/index.ts +82 -82
  11. package/src/balance/components/AccountView.vue +748 -748
  12. package/src/balance/components/BalanceCard.vue +205 -209
  13. package/src/balance/components/BalanceReminder.vue +85 -85
  14. package/src/balance/components/ConsumptionFilter.vue +218 -218
  15. package/src/balance/components/ConsumptionRules.vue +68 -68
  16. package/src/balance/components/DateFilter.vue +227 -226
  17. package/src/balance/components/DateRange.vue +78 -0
  18. package/src/balance/components/ListFilter.vue +64 -0
  19. package/src/balance/components/ListFilterPicker.vue +187 -0
  20. package/src/balance/components/SecondBalance.vue +71 -71
  21. package/src/balance/components/Tip.vue +45 -45
  22. package/src/balance/components/index.ts +13 -9
  23. package/src/balance/types.ts +91 -90
  24. package/src/components/bt-cropper/index.vue +774 -774
  25. package/src/components/bt-cropper/utils/calcCropper.js +42 -42
  26. package/src/components/bt-cropper/utils/calcImagePosition.js +23 -23
  27. package/src/components/bt-cropper/utils/calcImageSize.js +37 -37
  28. package/src/components/bt-cropper/utils/calcPointDistance.js +12 -12
  29. package/src/components/bt-cropper/utils/calcRightAndBottom.js +7 -7
  30. package/src/components/bt-cropper/utils/ratio.js +3 -3
  31. package/src/components/bt-cropper/utils/tools.js +25 -25
  32. package/src/components/dd-area/index.vue +225 -225
  33. package/src/components/dd-icon/doc.md +21 -21
  34. package/src/components/dd-icon/index.vue +23 -23
  35. package/src/components/dd-notice-bar/index.vue +78 -78
  36. package/src/components/dd-search/doc.md +34 -34
  37. package/src/components/dd-search/index.vue +168 -168
  38. package/src/components/dd-selector/index.vue +124 -124
  39. package/src/components/dd-skeleton/doc.md +19 -19
  40. package/src/components/dd-skeleton/index.vue +36 -36
  41. package/src/components/ocr-id/index.vue +114 -114
  42. package/src/components/ocr-id/types.d.ts +12 -12
  43. package/src/global.ts +6 -6
  44. package/src/index.ts +89 -89
  45. package/src/main.scss +1 -1
  46. package/src/notice/api/endpoints.ts +17 -17
  47. package/src/notice/api/index.ts +82 -82
  48. package/src/notice/components/NoticeBanner.vue +243 -243
  49. package/src/notice/components/NoticeEntry.vue +99 -99
  50. package/src/notice/components/NoticeList.vue +315 -315
  51. package/src/notice/components/NoticePopup.vue +161 -161
  52. package/src/notice/components/index.ts +5 -5
  53. package/src/notice/components/useCommonList.ts +86 -86
  54. package/src/notice/components/useNotice.ts +35 -35
  55. package/src/notice/index.ts +1 -1
  56. package/src/notice/types.ts +25 -25
  57. package/src/payment/api/config.ts +7 -7
  58. package/src/payment/api/endpoints.ts +103 -103
  59. package/src/payment/api/index.ts +71 -71
  60. package/src/payment/components/AmountPicker.vue +93 -93
  61. package/src/payment/components/RechargeResult.vue +69 -69
  62. package/src/payment/components/RechargeView.vue +154 -154
  63. package/src/payment/components/RightsPicker.vue +105 -105
  64. package/src/payment/components/TradeView.vue +298 -298
  65. package/src/payment/components/UserAgreement.vue +234 -234
  66. package/src/payment/components/index.ts +22 -22
  67. package/src/payment/index.ts +5 -5
  68. package/src/payment/services/index.ts +16 -16
  69. package/src/payment/services/invoke-recharge.ts +25 -25
  70. package/src/payment/services/request-payment.ts +58 -58
  71. package/src/payment/types.ts +28 -28
  72. package/src/register/components/SelfRegistration.vue +254 -254
  73. package/src/register/components/index.ts +2 -2
  74. package/src/shared/components/AppDrawer.vue +58 -58
  75. package/src/shared/components/AppVerify.vue +98 -0
  76. package/src/shared/components/DeviceVersion.vue +68 -68
  77. package/src/shared/components/EmptyView.vue +33 -33
  78. package/src/shared/components/OcrIcon.vue +119 -0
  79. package/src/shared/components/PageHeader.vue +79 -79
  80. package/src/shared/components/index.ts +7 -5
  81. package/src/shared/composables/index.ts +7 -5
  82. package/src/shared/composables/useCountdown.ts +46 -46
  83. package/src/shared/composables/useCrypto.ts +76 -0
  84. package/src/shared/composables/useDragBox.ts +97 -97
  85. package/src/shared/composables/useEncode.ts +43 -43
  86. package/src/shared/composables/useSafeArea.ts +46 -46
  87. package/src/shared/composables/useTabbar.ts +24 -24
  88. package/src/shared/composables/useUpload.ts +55 -0
  89. package/src/shared/composables/useValidator.ts +31 -31
  90. package/src/shared/http/Http.ts +136 -136
  91. package/src/shared/http/index.ts +1 -1
  92. package/src/shared/http/types.ts +157 -157
  93. package/src/shared/index.ts +3 -3
  94. package/src/shared/weixin/payment.ts +38 -38
  95. package/src/styles/fonts.scss +2 -2
  96. package/src/styles/vars.scss +3 -3
  97. package/src/user/api/endpoints.ts +17 -17
  98. package/src/user/api/index.ts +87 -87
  99. package/src/user/components/LoginSetting.vue +114 -114
  100. package/src/user/components/UserBinding.vue +307 -307
  101. package/src/user/components/UserBindingSuccess.vue +80 -80
  102. package/src/user/components/UserEntry.vue +137 -142
  103. package/src/user/components/UserFeedback.vue +440 -440
  104. package/src/user/components/UserFeedbackEntry.vue +192 -192
  105. package/src/user/components/UserHeadCrop.vue +65 -65
  106. package/src/user/components/UserInfo.vue +632 -632
  107. package/src/user/components/UserResourceEmpty.vue +75 -75
  108. package/src/user/components/index.ts +21 -21
  109. package/src/user/index.ts +1 -1
  110. package/tsconfig.json +30 -30
  111. package/types/global.d.ts +21 -21
  112. package/types/vue.d.ts +10 -10
  113. package/dist/appkit.css +0 -2331
  114. package/dist/assets/asset-3B_CoPto +0 -1
  115. package/dist/assets/asset-DcH8Kg-2 +0 -1
  116. package/dist/index.js +0 -7176
@@ -1,440 +1,440 @@
1
- <template>
2
- <div class="user-feedback" :class="{ isSuccess }">
3
- <div v-if="!isSuccess" class="user-feedback-wrap">
4
- <div class="user-feedback-head">
5
- <div class="user-feedback-head-info">1.系统使用过程中,遇到系统问题可通过此途径反馈。</div>
6
- <div class="user-feedback-head-info">
7
- 2.尽量在出问题的页面直接点击“问题反馈”,系统将后台获取当前页面信息,以便快速定位问题页。
8
- </div>
9
- <div class="user-feedback-head-info">
10
- 3.问题描述请尽可能详细描述操作步骤,如涉及订单或客户信息,尽量附上订单编号/客户姓名等关键信息,以便快速定位问题。
11
- </div>
12
- <div class="user-feedback-head-info">4.问题描述支持上传图片及截图,视频不能超过50M。</div>
13
- </div>
14
-
15
- <div class="user-feedback-tit">补充描述</div>
16
- <div class="user-feedback-body">
17
- <editor
18
- id="myEditor"
19
- :show-img-resize="true"
20
- class="user-feedback-editor"
21
- placeholder="请输入补充描述"
22
- />
23
- <div class="user-feedback-handle">
24
- <div
25
- class="user-feedback-handle-item"
26
- v-for="(item, key) in formState.attachment"
27
- :key="key"
28
- >
29
- <template v-if="item.status === 'done'">
30
- <img
31
- v-if="item.type === 'image'"
32
- class="user-feedback-handle-item-img"
33
- mode="aspectFit"
34
- :src="item.url"
35
- />
36
- <video class="user-feedback-handle-item-img" :src="item.url" v-else />
37
- <div class="user-feedback-handle-item-close" @click="onDelete(key)">
38
- <img
39
- class="user-feedback-handle-item-close-img"
40
- src="https://cdn.ddjf.com/static/images/customer-center/close-filled.png"
41
- />
42
- </div>
43
- </template>
44
- <img
45
- class="user-feedback-handle-item-loading"
46
- mode="aspectFit"
47
- src="https://cdn.ddjf.com/static/images/customer-center/loading.png"
48
- alt=""
49
- />
50
- </div>
51
- <div class="user-feedback-handle-item" @click="toUpload">+</div>
52
- </div>
53
- </div>
54
-
55
- <div class="user-feedback-footer">
56
- <nut-button class="user-feedback-footer-btn" plain type="primary" @click="onCancel"
57
- >最小化</nut-button
58
- >
59
- <nut-button class="user-feedback-footer-btn" type="primary" @click="onOk">反馈</nut-button>
60
- </div>
61
- </div>
62
- <div class="user-feedback-wrap" v-else>
63
- <img
64
- class="user-feedback-success-img"
65
- mode="aspectFit"
66
- src="https://cdn.ddjf.com/static/images/customer-center/success-icon.png"
67
- />
68
- <div class="user-feedback-success-info">提交成功</div>
69
- </div>
70
- </div>
71
- </template>
72
-
73
- <script lang="ts" setup>
74
- import Taro from '@tarojs/taro'
75
- import { onMounted, reactive, ref } from 'vue'
76
- import { useAppKitOptions } from '../../Appkit'
77
- import { useHttp } from '../api'
78
-
79
- interface FormStateType {
80
- content: string
81
- attachment: any[]
82
- }
83
-
84
- const props = withDefaults(
85
- defineProps<{
86
- app?: string
87
- traceIds: string
88
- value?: FormStateType | null
89
- captureScreen?: any
90
- }>(),
91
- {
92
- app: '',
93
- traceIds: '',
94
- value: null,
95
- captureScreen: () => {},
96
- }
97
- )
98
-
99
- onMounted(() => {
100
- setTimeout(() => {
101
- initEditor()
102
- }, 500)
103
- })
104
-
105
- const formState = reactive<FormStateType>({
106
- content: '',
107
- attachment: [],
108
- })
109
- const isSuccess = ref(false) // 反馈提交成功标记
110
-
111
- const editorCtx = ref()
112
- // 编辑器初始化
113
- function initEditor() {
114
- const query = Taro.createSelectorQuery()
115
- query
116
- .select('#myEditor')
117
- .context((res) => {
118
- editorCtx.value = res.context
119
-
120
- if (props.value) {
121
- if (props.value.content) {
122
- formState.content = props.value.content
123
- editorCtx.value.setContents({
124
- html: props.value.content,
125
- })
126
- }
127
- if (props.value.attachment && props.value.attachment.length) {
128
- formState.attachment = props.value.attachment
129
- }
130
- }
131
- })
132
- .exec()
133
- }
134
-
135
- // 上传图片、视频
136
- function toUpload() {
137
- if (formState.attachment.length >= 20) {
138
- return Taro.showToast({
139
- title: '最多上传20份附件',
140
- icon: 'none',
141
- })
142
- }
143
- Taro.chooseMedia({
144
- count: 20 - formState.attachment.length,
145
- mediaType: ['mix'],
146
- maxDuration: 60,
147
- success: async (res) => {
148
- console.log(res.tempFiles, 'res.tempFiles')
149
- for (const item of res.tempFiles) {
150
- if (item.fileType === 'video') {
151
- if (item.size > 1024 * 1024 * 50) {
152
- return Taro.showToast({
153
- title: '视频大小不能超过50M',
154
- icon: 'none',
155
- })
156
- }
157
- }
158
-
159
- formState.attachment.push({
160
- url: item.tempFilePath,
161
- type: item.fileType,
162
- status: 'uploading',
163
- })
164
- await uploadFile(formState.attachment[formState.attachment.length - 1])
165
- }
166
- },
167
- })
168
- }
169
-
170
- // 文件上传请求
171
- async function uploadFile(item: any) {
172
- const appkitOptions = useAppKitOptions()
173
- const token = appkitOptions.tempToken() || appkitOptions.token()
174
-
175
- let Res: any = await Taro.uploadFile({
176
- url: `${appkitOptions.baseUrl()}/saas-base/file/upload`,
177
- filePath: item.url,
178
- name: 'file',
179
- formData: {
180
- objectNo: `${token}${Date.now()}`,
181
- objectTypeCode: `PROBLEM_FEEDBACK_MINI`,
182
- },
183
- header: { token },
184
- })
185
-
186
- const res = JSON.parse(Res.data)
187
- if (res.success) {
188
- item.url = res.result
189
- item.status = 'done'
190
- }
191
- }
192
-
193
- // 删除附件
194
- function onDelete(index: number) {
195
- formState.attachment.splice(index, 1)
196
- }
197
-
198
- // 点击最小化
199
- function onCancel() {
200
- editorCtx.value.getContents({
201
- success: (res) => {
202
- const html = res.html
203
- formState.content = html
204
-
205
- if (html && html !== '<p><br></p>') {
206
- formState.content = html
207
- }
208
- emits('minimize', formState)
209
- Taro.navigateBack()
210
- },
211
- })
212
- }
213
-
214
- // 点击确定反馈时
215
- function onOk() {
216
- editorCtx.value.getContents({
217
- success: (res) => {
218
- const html = res.html
219
- if (html === '<p><br></p>' || !html) {
220
- Taro.showToast({
221
- title: '请输入问题描述',
222
- icon: 'none',
223
- })
224
- } else {
225
- formState.content = html
226
- requestFeedback()
227
- }
228
- },
229
- })
230
- }
231
-
232
- // 请求反馈接口
233
- function requestFeedback() {
234
- const $http = useHttp()
235
- const appkitOptions = useAppKitOptions()
236
-
237
- Taro.showLoading({
238
- title: '反馈中...',
239
- })
240
-
241
- const attachment = JSON.parse(
242
- JSON.stringify(
243
- formState.attachment
244
- .filter((item) => item.status === 'done')
245
- .map((item) => ({ url: item.url, type: item.type }))
246
- )
247
- )
248
- if (props.captureScreen && Object.keys(props.captureScreen).length) {
249
- attachment.push({
250
- captureScreen: props.captureScreen,
251
- })
252
- }
253
-
254
- const sendData = {
255
- appCode: props.app || appkitOptions.app(),
256
- content: formState.content,
257
- id: '',
258
- remarks: '',
259
- title: '',
260
- traceIds: props.traceIds,
261
- device: 'MINI',
262
- attachment: JSON.stringify(attachment),
263
- }
264
- $http
265
- .post('/saas-base/problemFeedback/add', sendData)
266
- .then(() => {
267
- Taro.hideLoading()
268
- emits('success')
269
- // Taro.showToast({ title: '反馈提交成功', icon: 'none' })
270
- // Taro.navigateBack()
271
- isSuccess.value = true
272
- clearFormState()
273
- })
274
- .catch(() => {
275
- Taro.hideLoading()
276
- })
277
- }
278
-
279
- // 反馈成功后,清空反馈内容
280
- function clearFormState() {
281
- formState.content = ''
282
- formState.attachment = []
283
- }
284
-
285
- // 父组件事件
286
- const emits = defineEmits(['minimize', 'success'])
287
- </script>
288
-
289
- <style lang="scss">
290
- .user-feedback {
291
- margin: 10px 12px;
292
- padding-bottom: calc(75px + env(safe-area-inset-bottom, 0px));
293
- min-height: 100vh;
294
- box-sizing: border-box;
295
- overflow: hidden;
296
- &.isSuccess {
297
- margin: 0;
298
- padding: 0;
299
- height: 100%;
300
- .user-feedback-wrap {
301
- height: 100vh;
302
- align-items: center;
303
- }
304
- }
305
- &-wrap {
306
- min-height: calc(100vh - calc(75px + env(safe-area-inset-bottom, 0px)));
307
- height: 100%;
308
- background: #fff;
309
- display: flex;
310
- flex-direction: column;
311
- box-sizing: border-box;
312
- padding: 10px;
313
- border-radius: 5px;
314
- }
315
- &-head {
316
- background: #f2f9ff;
317
- padding: 15px;
318
- border-radius: 2px;
319
- font-size: 12px;
320
- margin-bottom: 8px;
321
- &-info {
322
- padding-left: 9px;
323
- text-indent: -9px;
324
- }
325
- }
326
- &-tit {
327
- height: 44px;
328
- display: flex;
329
- align-items: center;
330
- color: #1a1a1a;
331
- font-weight: 500;
332
- font-size: 17px;
333
- }
334
- &-body {
335
- border-radius: 4px 4px 0px 0px;
336
- display: flex;
337
- flex-direction: column;
338
- overflow: hidden;
339
- }
340
- &-editor {
341
- padding: 10px;
342
- font-size: 14px;
343
- background: #f5f5f5;
344
- overflow-y: auto;
345
- min-height: 165px;
346
- }
347
- &-handle {
348
- display: flex;
349
- align-items: center;
350
- flex-wrap: wrap;
351
- margin-top: 10px;
352
- margin-right: -4px;
353
- &-item {
354
- position: relative;
355
- border-radius: 2px;
356
- display: flex;
357
- align-items: center;
358
- justify-content: center;
359
- width: 79px;
360
- height: 79px;
361
- background: #f5f5f5;
362
- font-size: 30px;
363
- color: rgba(53, 53, 53, 0.3);
364
- margin-right: 4px;
365
- margin-bottom: 4px;
366
- &-img {
367
- max-width: 100%;
368
- max-height: 100%;
369
- }
370
- &-close {
371
- position: absolute;
372
- top: -2px;
373
- right: -2px;
374
- width: 20px;
375
- height: 20px;
376
- display: flex;
377
- justify-content: center;
378
- align-items: center;
379
- &-img {
380
- width: 10px;
381
- height: 10px;
382
- }
383
- }
384
- &-loading {
385
- width: 17px;
386
- height: 17px;
387
- animation: rotate 2s linear infinite;
388
- }
389
- }
390
- }
391
- .ql-editor.ql-blank:before {
392
- color: rgba(0, 0, 0, 0.3);
393
- font-style: normal;
394
- }
395
-
396
- &-footer {
397
- position: fixed;
398
- z-index: 10;
399
- bottom: 0;
400
- left: 0;
401
- width: 100%;
402
- min-height: 63px;
403
- background: #ffffff;
404
- box-shadow: 0 -3px 11px 0 rgba(224, 224, 224, 0.5);
405
- padding: 10px 12px calc(8px + env(safe-area-inset-bottom, 0px));
406
- display: flex;
407
- justify-content: space-between;
408
- box-sizing: border-box;
409
- &-btn {
410
- flex: 1;
411
- &:first-child {
412
- margin-right: 10px;
413
- }
414
- }
415
- }
416
-
417
- &-success {
418
- &-img {
419
- width: 87px;
420
- height: 90px;
421
- margin-top: 110px;
422
- margin-bottom: 20px;
423
- }
424
- &-info {
425
- color: #353535;
426
- font-size: 16px;
427
- font-weight: 500;
428
- }
429
- }
430
-
431
- @keyframes rotate {
432
- from {
433
- transform: rotate(0);
434
- }
435
- to {
436
- transform: rotate(360deg);
437
- }
438
- }
439
- }
440
- </style>
1
+ <template>
2
+ <div class="user-feedback" :class="{ isSuccess }">
3
+ <div v-if="!isSuccess" class="user-feedback-wrap">
4
+ <div class="user-feedback-head">
5
+ <div class="user-feedback-head-info">1.系统使用过程中,遇到系统问题可通过此途径反馈。</div>
6
+ <div class="user-feedback-head-info">
7
+ 2.尽量在出问题的页面直接点击“问题反馈”,系统将后台获取当前页面信息,以便快速定位问题页。
8
+ </div>
9
+ <div class="user-feedback-head-info">
10
+ 3.问题描述请尽可能详细描述操作步骤,如涉及订单或客户信息,尽量附上订单编号/客户姓名等关键信息,以便快速定位问题。
11
+ </div>
12
+ <div class="user-feedback-head-info">4.问题描述支持上传图片及截图,视频不能超过50M。</div>
13
+ </div>
14
+
15
+ <div class="user-feedback-tit">补充描述</div>
16
+ <div class="user-feedback-body">
17
+ <editor
18
+ id="myEditor"
19
+ :show-img-resize="true"
20
+ class="user-feedback-editor"
21
+ placeholder="请输入补充描述"
22
+ />
23
+ <div class="user-feedback-handle">
24
+ <div
25
+ class="user-feedback-handle-item"
26
+ v-for="(item, key) in formState.attachment"
27
+ :key="key"
28
+ >
29
+ <template v-if="item.status === 'done'">
30
+ <img
31
+ v-if="item.type === 'image'"
32
+ class="user-feedback-handle-item-img"
33
+ mode="aspectFit"
34
+ :src="item.url"
35
+ />
36
+ <video class="user-feedback-handle-item-img" :src="item.url" v-else />
37
+ <div class="user-feedback-handle-item-close" @click="onDelete(key)">
38
+ <img
39
+ class="user-feedback-handle-item-close-img"
40
+ src="https://cdn.ddjf.com/static/images/customer-center/close-filled.png"
41
+ />
42
+ </div>
43
+ </template>
44
+ <img
45
+ class="user-feedback-handle-item-loading"
46
+ mode="aspectFit"
47
+ src="https://cdn.ddjf.com/static/images/customer-center/loading.png"
48
+ alt=""
49
+ />
50
+ </div>
51
+ <div class="user-feedback-handle-item" @click="toUpload">+</div>
52
+ </div>
53
+ </div>
54
+
55
+ <div class="user-feedback-footer">
56
+ <nut-button class="user-feedback-footer-btn" plain type="primary" @click="onCancel"
57
+ >最小化</nut-button
58
+ >
59
+ <nut-button class="user-feedback-footer-btn" type="primary" @click="onOk">反馈</nut-button>
60
+ </div>
61
+ </div>
62
+ <div class="user-feedback-wrap" v-else>
63
+ <img
64
+ class="user-feedback-success-img"
65
+ mode="aspectFit"
66
+ src="https://cdn.ddjf.com/static/images/customer-center/success-icon.png"
67
+ />
68
+ <div class="user-feedback-success-info">提交成功</div>
69
+ </div>
70
+ </div>
71
+ </template>
72
+
73
+ <script lang="ts" setup>
74
+ import Taro from '@tarojs/taro'
75
+ import { onMounted, reactive, ref } from 'vue'
76
+ import { useAppKitOptions } from '../../Appkit'
77
+ import { useHttp } from '../api'
78
+
79
+ interface FormStateType {
80
+ content: string
81
+ attachment: any[]
82
+ }
83
+
84
+ const props = withDefaults(
85
+ defineProps<{
86
+ app?: string
87
+ traceIds: string
88
+ value?: FormStateType | null
89
+ captureScreen?: any
90
+ }>(),
91
+ {
92
+ app: '',
93
+ traceIds: '',
94
+ value: null,
95
+ captureScreen: () => {},
96
+ }
97
+ )
98
+
99
+ onMounted(() => {
100
+ setTimeout(() => {
101
+ initEditor()
102
+ }, 500)
103
+ })
104
+
105
+ const formState = reactive<FormStateType>({
106
+ content: '',
107
+ attachment: [],
108
+ })
109
+ const isSuccess = ref(false) // 反馈提交成功标记
110
+
111
+ const editorCtx = ref()
112
+ // 编辑器初始化
113
+ function initEditor() {
114
+ const query = Taro.createSelectorQuery()
115
+ query
116
+ .select('#myEditor')
117
+ .context((res) => {
118
+ editorCtx.value = res.context
119
+
120
+ if (props.value) {
121
+ if (props.value.content) {
122
+ formState.content = props.value.content
123
+ editorCtx.value.setContents({
124
+ html: props.value.content,
125
+ })
126
+ }
127
+ if (props.value.attachment && props.value.attachment.length) {
128
+ formState.attachment = props.value.attachment
129
+ }
130
+ }
131
+ })
132
+ .exec()
133
+ }
134
+
135
+ // 上传图片、视频
136
+ function toUpload() {
137
+ if (formState.attachment.length >= 20) {
138
+ return Taro.showToast({
139
+ title: '最多上传20份附件',
140
+ icon: 'none',
141
+ })
142
+ }
143
+ Taro.chooseMedia({
144
+ count: 20 - formState.attachment.length,
145
+ mediaType: ['mix'],
146
+ maxDuration: 60,
147
+ success: async (res) => {
148
+ console.log(res.tempFiles, 'res.tempFiles')
149
+ for (const item of res.tempFiles) {
150
+ if (item.fileType === 'video') {
151
+ if (item.size > 1024 * 1024 * 50) {
152
+ return Taro.showToast({
153
+ title: '视频大小不能超过50M',
154
+ icon: 'none',
155
+ })
156
+ }
157
+ }
158
+
159
+ formState.attachment.push({
160
+ url: item.tempFilePath,
161
+ type: item.fileType,
162
+ status: 'uploading',
163
+ })
164
+ await uploadFile(formState.attachment[formState.attachment.length - 1])
165
+ }
166
+ },
167
+ })
168
+ }
169
+
170
+ // 文件上传请求
171
+ async function uploadFile(item: any) {
172
+ const appkitOptions = useAppKitOptions()
173
+ const token = appkitOptions.tempToken() || appkitOptions.token()
174
+
175
+ let Res: any = await Taro.uploadFile({
176
+ url: `${appkitOptions.baseUrl()}/saas-base/file/upload`,
177
+ filePath: item.url,
178
+ name: 'file',
179
+ formData: {
180
+ objectNo: `${token}${Date.now()}`,
181
+ objectTypeCode: `PROBLEM_FEEDBACK_MINI`,
182
+ },
183
+ header: { token },
184
+ })
185
+
186
+ const res = JSON.parse(Res.data)
187
+ if (res.success) {
188
+ item.url = res.result
189
+ item.status = 'done'
190
+ }
191
+ }
192
+
193
+ // 删除附件
194
+ function onDelete(index: number) {
195
+ formState.attachment.splice(index, 1)
196
+ }
197
+
198
+ // 点击最小化
199
+ function onCancel() {
200
+ editorCtx.value.getContents({
201
+ success: (res) => {
202
+ const html = res.html
203
+ formState.content = html
204
+
205
+ if (html && html !== '<p><br></p>') {
206
+ formState.content = html
207
+ }
208
+ emits('minimize', formState)
209
+ Taro.navigateBack()
210
+ },
211
+ })
212
+ }
213
+
214
+ // 点击确定反馈时
215
+ function onOk() {
216
+ editorCtx.value.getContents({
217
+ success: (res) => {
218
+ const html = res.html
219
+ if (html === '<p><br></p>' || !html) {
220
+ Taro.showToast({
221
+ title: '请输入问题描述',
222
+ icon: 'none',
223
+ })
224
+ } else {
225
+ formState.content = html
226
+ requestFeedback()
227
+ }
228
+ },
229
+ })
230
+ }
231
+
232
+ // 请求反馈接口
233
+ function requestFeedback() {
234
+ const $http = useHttp()
235
+ const appkitOptions = useAppKitOptions()
236
+
237
+ Taro.showLoading({
238
+ title: '反馈中...',
239
+ })
240
+
241
+ const attachment = JSON.parse(
242
+ JSON.stringify(
243
+ formState.attachment
244
+ .filter((item) => item.status === 'done')
245
+ .map((item) => ({ url: item.url, type: item.type }))
246
+ )
247
+ )
248
+ if (props.captureScreen && Object.keys(props.captureScreen).length) {
249
+ attachment.push({
250
+ captureScreen: props.captureScreen,
251
+ })
252
+ }
253
+
254
+ const sendData = {
255
+ appCode: props.app || appkitOptions.app(),
256
+ content: formState.content,
257
+ id: '',
258
+ remarks: '',
259
+ title: '',
260
+ traceIds: props.traceIds,
261
+ device: 'MINI',
262
+ attachment: JSON.stringify(attachment),
263
+ }
264
+ $http
265
+ .post('/saas-base/problemFeedback/add', sendData)
266
+ .then(() => {
267
+ Taro.hideLoading()
268
+ emits('success')
269
+ // Taro.showToast({ title: '反馈提交成功', icon: 'none' })
270
+ // Taro.navigateBack()
271
+ isSuccess.value = true
272
+ clearFormState()
273
+ })
274
+ .catch(() => {
275
+ Taro.hideLoading()
276
+ })
277
+ }
278
+
279
+ // 反馈成功后,清空反馈内容
280
+ function clearFormState() {
281
+ formState.content = ''
282
+ formState.attachment = []
283
+ }
284
+
285
+ // 父组件事件
286
+ const emits = defineEmits(['minimize', 'success'])
287
+ </script>
288
+
289
+ <style lang="scss">
290
+ .user-feedback {
291
+ margin: 10px 12px;
292
+ padding-bottom: calc(75px + env(safe-area-inset-bottom, 0px));
293
+ min-height: 100vh;
294
+ box-sizing: border-box;
295
+ overflow: hidden;
296
+ &.isSuccess {
297
+ margin: 0;
298
+ padding: 0;
299
+ height: 100%;
300
+ .user-feedback-wrap {
301
+ height: 100vh;
302
+ align-items: center;
303
+ }
304
+ }
305
+ &-wrap {
306
+ min-height: calc(100vh - calc(75px + env(safe-area-inset-bottom, 0px)));
307
+ height: 100%;
308
+ background: #fff;
309
+ display: flex;
310
+ flex-direction: column;
311
+ box-sizing: border-box;
312
+ padding: 10px;
313
+ border-radius: 5px;
314
+ }
315
+ &-head {
316
+ background: #f2f9ff;
317
+ padding: 15px;
318
+ border-radius: 2px;
319
+ font-size: 12px;
320
+ margin-bottom: 8px;
321
+ &-info {
322
+ padding-left: 9px;
323
+ text-indent: -9px;
324
+ }
325
+ }
326
+ &-tit {
327
+ height: 44px;
328
+ display: flex;
329
+ align-items: center;
330
+ color: #1a1a1a;
331
+ font-weight: 500;
332
+ font-size: 17px;
333
+ }
334
+ &-body {
335
+ border-radius: 4px 4px 0px 0px;
336
+ display: flex;
337
+ flex-direction: column;
338
+ overflow: hidden;
339
+ }
340
+ &-editor {
341
+ padding: 10px;
342
+ font-size: 14px;
343
+ background: #f5f5f5;
344
+ overflow-y: auto;
345
+ min-height: 165px;
346
+ }
347
+ &-handle {
348
+ display: flex;
349
+ align-items: center;
350
+ flex-wrap: wrap;
351
+ margin-top: 10px;
352
+ margin-right: -4px;
353
+ &-item {
354
+ position: relative;
355
+ border-radius: 2px;
356
+ display: flex;
357
+ align-items: center;
358
+ justify-content: center;
359
+ width: 79px;
360
+ height: 79px;
361
+ background: #f5f5f5;
362
+ font-size: 30px;
363
+ color: rgba(53, 53, 53, 0.3);
364
+ margin-right: 4px;
365
+ margin-bottom: 4px;
366
+ &-img {
367
+ max-width: 100%;
368
+ max-height: 100%;
369
+ }
370
+ &-close {
371
+ position: absolute;
372
+ top: -2px;
373
+ right: -2px;
374
+ width: 20px;
375
+ height: 20px;
376
+ display: flex;
377
+ justify-content: center;
378
+ align-items: center;
379
+ &-img {
380
+ width: 10px;
381
+ height: 10px;
382
+ }
383
+ }
384
+ &-loading {
385
+ width: 17px;
386
+ height: 17px;
387
+ animation: rotate 2s linear infinite;
388
+ }
389
+ }
390
+ }
391
+ .ql-editor.ql-blank:before {
392
+ color: rgba(0, 0, 0, 0.3);
393
+ font-style: normal;
394
+ }
395
+
396
+ &-footer {
397
+ position: fixed;
398
+ z-index: 10;
399
+ bottom: 0;
400
+ left: 0;
401
+ width: 100%;
402
+ min-height: 63px;
403
+ background: #ffffff;
404
+ box-shadow: 0 -3px 11px 0 rgba(224, 224, 224, 0.5);
405
+ padding: 10px 12px calc(8px + env(safe-area-inset-bottom, 0px));
406
+ display: flex;
407
+ justify-content: space-between;
408
+ box-sizing: border-box;
409
+ &-btn {
410
+ flex: 1;
411
+ &:first-child {
412
+ margin-right: 10px;
413
+ }
414
+ }
415
+ }
416
+
417
+ &-success {
418
+ &-img {
419
+ width: 87px;
420
+ height: 90px;
421
+ margin-top: 110px;
422
+ margin-bottom: 20px;
423
+ }
424
+ &-info {
425
+ color: #353535;
426
+ font-size: 16px;
427
+ font-weight: 500;
428
+ }
429
+ }
430
+
431
+ @keyframes rotate {
432
+ from {
433
+ transform: rotate(0);
434
+ }
435
+ to {
436
+ transform: rotate(360deg);
437
+ }
438
+ }
439
+ }
440
+ </style>