@uxda/appkit 4.3.30 → 4.3.33

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