ai-chat-bot-interface 1.1.4 → 1.1.6
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.
- package/package.json +3 -3
- package/src/App.vue +1 -5
- package/src/ChatUi.less +26 -0
- package/src/ChatUi.vue +128 -15
- package/src/components/OperateModule.less +165 -0
- package/src/components/OperateModule.vue +209 -91
- package/src/components/PlanCard.vue +71 -0
- package/src/components/icons/addIcon.vue +18 -0
- package/src/components/icons/cameraIcon.vue +22 -0
- package/src/components/icons/closeBorderIcon.vue +35 -0
- package/src/components/icons/fileIcon.vue +18 -0
- package/src/components/icons/pictureIcon.vue +22 -0
- package/src/components/icons/progressRing.vue +63 -0
- package/src/components/imgeList.vue +39 -0
- package/src/utils/tools.js +20 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai-chat-bot-interface",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "1.1.
|
|
4
|
+
"version": "1.1.6",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"description": "A AI chat bot interface. (private)",
|
|
@@ -22,8 +22,8 @@
|
|
|
22
22
|
"devDependencies": {
|
|
23
23
|
"@vitejs/plugin-vue": "^5.2.1",
|
|
24
24
|
"less": "^4.2.2",
|
|
25
|
-
"
|
|
26
|
-
"
|
|
25
|
+
"prettier": "^2.6.2",
|
|
26
|
+
"vite": "^6.1.0"
|
|
27
27
|
},
|
|
28
28
|
"author": "lin_26",
|
|
29
29
|
"license": "MIT"
|
package/src/App.vue
CHANGED
|
@@ -4,11 +4,7 @@ import ChatUi from './ChatUi.vue';
|
|
|
4
4
|
|
|
5
5
|
<template>
|
|
6
6
|
<div style="width: 100vw; height: 100vh">
|
|
7
|
-
<chat-ui
|
|
8
|
-
bot-id="7471093458661720104"
|
|
9
|
-
token="pat_2yVcSFJUZB6c9Kdcv9iktIVFeGRuzyK3bAJY6GcqKrdGxTdjKMd1iDB09ipJ6YX8"
|
|
10
|
-
uid="262598"
|
|
11
|
-
/>
|
|
7
|
+
<chat-ui bot-id="00" token="pat_08888" uid="262598" />
|
|
12
8
|
</div>
|
|
13
9
|
</template>
|
|
14
10
|
<style></style>
|
package/src/ChatUi.less
CHANGED
|
@@ -244,3 +244,29 @@
|
|
|
244
244
|
text-align: center;
|
|
245
245
|
}
|
|
246
246
|
}
|
|
247
|
+
|
|
248
|
+
.btn {
|
|
249
|
+
cursor: pointer;
|
|
250
|
+
width: 100%;
|
|
251
|
+
line-height: 22px;
|
|
252
|
+
box-sizing: border-box;
|
|
253
|
+
background-color: #e6f5eb;
|
|
254
|
+
border-radius: 19px;
|
|
255
|
+
text-align: center;
|
|
256
|
+
font-weight: 600;
|
|
257
|
+
font-size: 13px;
|
|
258
|
+
color: #039938;
|
|
259
|
+
margin-top: 15px;
|
|
260
|
+
|
|
261
|
+
&_2 {
|
|
262
|
+
color: #fff;
|
|
263
|
+
background-color: #039938;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
&_group {
|
|
267
|
+
display: grid;
|
|
268
|
+
grid-template-columns: 1fr;
|
|
269
|
+
grid-column-gap: 10px;
|
|
270
|
+
margin-top: 20px;
|
|
271
|
+
}
|
|
272
|
+
}
|
package/src/ChatUi.vue
CHANGED
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
v-for="(item, idx) in botInfo.onboarding_info.suggested_questions"
|
|
48
48
|
:key="idx"
|
|
49
49
|
class="board_sug"
|
|
50
|
-
@click.stop="chatConv({
|
|
50
|
+
@click.stop="chatConv([{ content: item, text: item }])"
|
|
51
51
|
>{{ item }}</span
|
|
52
52
|
>
|
|
53
53
|
</div>
|
|
@@ -65,6 +65,13 @@
|
|
|
65
65
|
<p class="text" v-html="conv.content"></p>
|
|
66
66
|
<div v-if="conv.extra.length && !isAnswering">
|
|
67
67
|
<template v-for="(comp, idx) in conv.extra" :key="idx">
|
|
68
|
+
<!-- <p
|
|
69
|
+
v-if="comp.showType === 'plan' && comp.planParse"
|
|
70
|
+
class="text"
|
|
71
|
+
style="margin-top: 1em"
|
|
72
|
+
>
|
|
73
|
+
{{ comp.planParse }}
|
|
74
|
+
</p>-->
|
|
68
75
|
<dishes-list
|
|
69
76
|
v-if="comp.showType === 'card'"
|
|
70
77
|
:sku-list="comp.skuList"
|
|
@@ -72,10 +79,19 @@
|
|
|
72
79
|
/>
|
|
73
80
|
<plan-card
|
|
74
81
|
v-if="comp.showType === 'plan'"
|
|
82
|
+
:info="comp"
|
|
75
83
|
@select="handleCardTap({ type: 'match' }, comp)"
|
|
76
84
|
/>
|
|
77
85
|
</template>
|
|
78
86
|
</div>
|
|
87
|
+
<div v-else-if="handleTextNeedBtn(conv.content)">
|
|
88
|
+
<div
|
|
89
|
+
class="btn btn_2"
|
|
90
|
+
@click.stop="handleCardTap({ type: 'match' }, {})"
|
|
91
|
+
>
|
|
92
|
+
用該方案配餐
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
79
95
|
</template>
|
|
80
96
|
<loading-icon2 v-else />
|
|
81
97
|
</div>
|
|
@@ -90,6 +106,9 @@
|
|
|
90
106
|
</div>
|
|
91
107
|
<div class="box">
|
|
92
108
|
<p class="text" v-html="conv.content"></p>
|
|
109
|
+
<div v-if="conv.extra.length">
|
|
110
|
+
<imge-list :list="conv.extra" />
|
|
111
|
+
</div>
|
|
93
112
|
</div>
|
|
94
113
|
</div>
|
|
95
114
|
</div>
|
|
@@ -98,6 +117,7 @@
|
|
|
98
117
|
</div>
|
|
99
118
|
<operate-module
|
|
100
119
|
v-model="inputText"
|
|
120
|
+
:token="token"
|
|
101
121
|
:tag-list="tagList"
|
|
102
122
|
@send="chatConv"
|
|
103
123
|
@tag="handleTagSel"
|
|
@@ -121,6 +141,7 @@ import BackIcon from './components/icons/BackIcon.vue';
|
|
|
121
141
|
import NewChat from './components/icons/newChat.vue';
|
|
122
142
|
import LoadingIcon from './components/icons/loadingIcon.vue';
|
|
123
143
|
import LoadingIcon2 from './components/icons/loadingIcon2.vue';
|
|
144
|
+
import ImgeList from './components/imgeList.vue';
|
|
124
145
|
|
|
125
146
|
const chatOptions = computed(() => {
|
|
126
147
|
return {
|
|
@@ -165,6 +186,7 @@ const props = defineProps({
|
|
|
165
186
|
default: () => [
|
|
166
187
|
{ name: '人工客服', value: 'kefu', type: 'chat', msg: '人工客服' },
|
|
167
188
|
{ name: '查看菜单', value: 'menu', type: 'call', msg: '查看菜单' },
|
|
189
|
+
{ name: '体检报告', value: 'exam', type: 'upload', msg: '体检报告' },
|
|
168
190
|
],
|
|
169
191
|
},
|
|
170
192
|
});
|
|
@@ -236,22 +258,65 @@ const createConv = async () => {
|
|
|
236
258
|
},
|
|
237
259
|
);
|
|
238
260
|
};*/
|
|
261
|
+
|
|
239
262
|
const chatConv = async (data) => {
|
|
240
|
-
let isInCode = data.hasOwnProperty('text') && data.hasOwnProperty('code');
|
|
263
|
+
let isInCode = Array.isArray(data); // data.hasOwnProperty('text') && data.hasOwnProperty('code');
|
|
264
|
+
console.log('=========', isInCode, data);
|
|
265
|
+
let botId = props.botId;
|
|
241
266
|
if (!(!isAnswering.value && (inputText.value || isInCode))) {
|
|
242
267
|
return;
|
|
243
268
|
}
|
|
244
269
|
const inText = inputText.value;
|
|
245
270
|
console.log('== user send ==', isInCode, data, inText);
|
|
246
271
|
isAnswering.value = true;
|
|
247
|
-
|
|
272
|
+
|
|
273
|
+
const additional_messages = [];
|
|
274
|
+
const uObj = {
|
|
248
275
|
conversation_id: conversationId.value,
|
|
249
|
-
bot_id:
|
|
276
|
+
bot_id: botId,
|
|
250
277
|
role: 'user',
|
|
251
|
-
content:
|
|
278
|
+
content: '',
|
|
252
279
|
status: 'ended',
|
|
253
280
|
extra: [],
|
|
281
|
+
};
|
|
282
|
+
const markStr = `chat_${new Date().getTime()};`;
|
|
283
|
+
data.forEach((item) => {
|
|
284
|
+
if (item.hasOwnProperty('content') && item.content) {
|
|
285
|
+
if (item.hasOwnProperty('type') && item.type === 'object_string') {
|
|
286
|
+
console.log('=== item ====', item);
|
|
287
|
+
botId = '7474884145253023795';
|
|
288
|
+
additional_messages.push({
|
|
289
|
+
content: JSON.stringify(
|
|
290
|
+
item.content.map((con) => ({
|
|
291
|
+
type: con.type,
|
|
292
|
+
file_id: con.file_id,
|
|
293
|
+
})),
|
|
294
|
+
),
|
|
295
|
+
content_type: 'object_string',
|
|
296
|
+
role: 'user',
|
|
297
|
+
meta_data: {
|
|
298
|
+
chat_group: markStr,
|
|
299
|
+
},
|
|
300
|
+
});
|
|
301
|
+
uObj.extra = item.content.map((con) => ({
|
|
302
|
+
...con,
|
|
303
|
+
showType: con.type,
|
|
304
|
+
}));
|
|
305
|
+
} else {
|
|
306
|
+
uObj.content = item.text;
|
|
307
|
+
additional_messages.push({
|
|
308
|
+
content: item.content,
|
|
309
|
+
content_type: 'text',
|
|
310
|
+
role: 'user',
|
|
311
|
+
meta_data: {
|
|
312
|
+
chat_group: markStr,
|
|
313
|
+
},
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
}
|
|
254
317
|
});
|
|
318
|
+
|
|
319
|
+
historyList.value.push(uObj);
|
|
255
320
|
const res = await fetch(
|
|
256
321
|
`https://api.coze.cn/v3/chat?conversation_id=${conversationId.value}`,
|
|
257
322
|
{
|
|
@@ -261,17 +326,22 @@ const chatConv = async (data) => {
|
|
|
261
326
|
// 'Content-Type': 'text/event-stream',
|
|
262
327
|
},
|
|
263
328
|
body: JSON.stringify({
|
|
264
|
-
bot_id:
|
|
329
|
+
bot_id: botId,
|
|
265
330
|
user_id: props.uid,
|
|
266
331
|
stream: true,
|
|
267
332
|
connector_id: '999',
|
|
268
|
-
additional_messages
|
|
333
|
+
additional_messages /*[
|
|
334
|
+
{
|
|
335
|
+
content: '[{"type":"image","file_id":"7475569020654436390"}]',
|
|
336
|
+
content_type: 'object_string',
|
|
337
|
+
role: 'user',
|
|
338
|
+
},
|
|
269
339
|
{
|
|
270
340
|
role: 'user',
|
|
271
341
|
content_type: 'text',
|
|
272
342
|
content: isInCode ? data.code : inText, // '配餐1600kcal,身高173,体重60kg,生成一天的套餐',
|
|
273
343
|
},
|
|
274
|
-
]
|
|
344
|
+
]*/,
|
|
275
345
|
custom_variables: {
|
|
276
346
|
uid: props.uid,
|
|
277
347
|
},
|
|
@@ -338,6 +408,7 @@ const chatConv = async (data) => {
|
|
|
338
408
|
historyList.value[idx].content = handleText(
|
|
339
409
|
historyList.value[idx].content + strObj.content,
|
|
340
410
|
);
|
|
411
|
+
scrollToEnd();
|
|
341
412
|
} else if (
|
|
342
413
|
strObj.hasOwnProperty('type') &&
|
|
343
414
|
strObj.type === 'tool_response'
|
|
@@ -351,6 +422,7 @@ const chatConv = async (data) => {
|
|
|
351
422
|
historyList.value[idx].extra.push({ ...modelObj });
|
|
352
423
|
}
|
|
353
424
|
}
|
|
425
|
+
scrollToEnd();
|
|
354
426
|
}
|
|
355
427
|
}
|
|
356
428
|
});
|
|
@@ -367,6 +439,7 @@ const queryHistoryList = async () => {
|
|
|
367
439
|
if (res.code === 0 && res.data && res.data.length) {
|
|
368
440
|
historyList.value = [];
|
|
369
441
|
const cardList = [];
|
|
442
|
+
const userFileList = [];
|
|
370
443
|
res.data.forEach((row) => {
|
|
371
444
|
if (row.hasOwnProperty('content_type')) {
|
|
372
445
|
if (row.content_type === 'text') {
|
|
@@ -375,6 +448,7 @@ const queryHistoryList = async () => {
|
|
|
375
448
|
conversation_id: row.conversation_id,
|
|
376
449
|
bot_id: row.bot_id,
|
|
377
450
|
role: row.role,
|
|
451
|
+
meta_data: row.meta_data,
|
|
378
452
|
content: handleText(row.content),
|
|
379
453
|
status: 'ended',
|
|
380
454
|
extra: [],
|
|
@@ -403,19 +477,44 @@ const queryHistoryList = async () => {
|
|
|
403
477
|
}
|
|
404
478
|
}
|
|
405
479
|
} catch (e) {
|
|
406
|
-
console.log('== 解析错误 ==');
|
|
480
|
+
console.log('== 解析错误 sys==');
|
|
481
|
+
}
|
|
482
|
+
} else if (
|
|
483
|
+
row.content_type === 'object_string' &&
|
|
484
|
+
row.role === 'user'
|
|
485
|
+
) {
|
|
486
|
+
try {
|
|
487
|
+
const strObj = JSON.parse(row.content);
|
|
488
|
+
cardList.push({
|
|
489
|
+
chat_id: row.chat_id,
|
|
490
|
+
conversation_id: row.conversation_id,
|
|
491
|
+
bot_id: row.bot_id,
|
|
492
|
+
role: row.role,
|
|
493
|
+
meta_data: row.meta_data,
|
|
494
|
+
extra: strObj.map((item) => ({ ...item, showType: item.type })),
|
|
495
|
+
});
|
|
496
|
+
} catch (e) {
|
|
497
|
+
console.log('== 解析错误 user==');
|
|
407
498
|
}
|
|
408
499
|
}
|
|
409
500
|
}
|
|
410
501
|
});
|
|
411
502
|
cardList.forEach((card) => {
|
|
412
|
-
const idx =
|
|
413
|
-
|
|
414
|
-
|
|
503
|
+
const idx =
|
|
504
|
+
card.role === 'user'
|
|
505
|
+
? historyList.value.findIndex(
|
|
506
|
+
(c) =>
|
|
507
|
+
c.meta_data.chat_group === card.meta_data.chat_group &&
|
|
508
|
+
c.role === card.role,
|
|
509
|
+
)
|
|
510
|
+
: historyList.value.findIndex(
|
|
511
|
+
(c) => c.chat_id === card.chat_id && c.role === card.role,
|
|
512
|
+
);
|
|
415
513
|
if (idx > -1) {
|
|
416
514
|
historyList.value[idx].extra = [...card.extra];
|
|
417
515
|
}
|
|
418
516
|
});
|
|
517
|
+
|
|
419
518
|
console.log(historyList.value, cardList);
|
|
420
519
|
}
|
|
421
520
|
};
|
|
@@ -445,7 +544,7 @@ const handleBack = () => {
|
|
|
445
544
|
const handleTagSel = (info) => {
|
|
446
545
|
switch (info.type) {
|
|
447
546
|
case 'chat':
|
|
448
|
-
chatConv({
|
|
547
|
+
chatConv([{ content: info.msg, text: info.msg }]);
|
|
449
548
|
break;
|
|
450
549
|
case 'call':
|
|
451
550
|
Emits('call', { ...info });
|
|
@@ -456,21 +555,35 @@ const handleTagSel = (info) => {
|
|
|
456
555
|
const handleCardTap = ({ type }, info) => {
|
|
457
556
|
switch (type) {
|
|
458
557
|
case 'change':
|
|
459
|
-
chatConv({
|
|
558
|
+
chatConv([{ content: '換一套菜品', text: '換一套菜品' }]);
|
|
460
559
|
break;
|
|
461
560
|
case 'match':
|
|
462
|
-
chatConv(
|
|
561
|
+
chatConv([
|
|
562
|
+
{ content: '請用以上方案為我配餐', text: '請用以上方案為我配餐' },
|
|
563
|
+
]);
|
|
463
564
|
break;
|
|
464
565
|
default:
|
|
465
566
|
Emits('call', { type, info });
|
|
466
567
|
}
|
|
467
568
|
};
|
|
468
569
|
|
|
570
|
+
const handlePlanParse = (list) => {
|
|
571
|
+
const pIdx = list.findIndex((p) => p.showType === 'plan');
|
|
572
|
+
return pIdx > -1
|
|
573
|
+
? { show: true, text: list[pIdx].planParse }
|
|
574
|
+
: { show: false };
|
|
575
|
+
};
|
|
576
|
+
|
|
469
577
|
const scrollToEnd = () => {
|
|
470
578
|
nextTick(() => {
|
|
471
579
|
endTarget.value.scrollIntoView();
|
|
472
580
|
});
|
|
473
581
|
};
|
|
582
|
+
|
|
583
|
+
const handleTextNeedBtn = (str) => {
|
|
584
|
+
const regExp = /#全日總熱量:(\d*?)kcal/g;
|
|
585
|
+
return regExp.test(str);
|
|
586
|
+
};
|
|
474
587
|
</script>
|
|
475
588
|
|
|
476
589
|
<style scoped lang="less">
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
@import '../assets/styles/public';
|
|
2
|
+
.om {
|
|
3
|
+
&_wrap {
|
|
4
|
+
box-sizing: border-box;
|
|
5
|
+
width: 100%;
|
|
6
|
+
padding: 10px;
|
|
7
|
+
background-color: #fff;
|
|
8
|
+
}
|
|
9
|
+
&_quick {
|
|
10
|
+
display: grid;
|
|
11
|
+
grid-auto-flow: column;
|
|
12
|
+
gap: 10px;
|
|
13
|
+
overflow-x: auto;
|
|
14
|
+
justify-content: start;
|
|
15
|
+
scroll-behavior: smooth;
|
|
16
|
+
scrollbar-width: none; /* 隐藏滚动条(Firefox) */
|
|
17
|
+
&::-webkit-scrollbar {
|
|
18
|
+
display: none;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.tag {
|
|
22
|
+
.flexrsc();
|
|
23
|
+
cursor: pointer;
|
|
24
|
+
width: fit-content;
|
|
25
|
+
white-space: nowrap;
|
|
26
|
+
font-weight: 400;
|
|
27
|
+
font-size: 12px;
|
|
28
|
+
color: #333;
|
|
29
|
+
line-height: 16px;
|
|
30
|
+
text-align: left;
|
|
31
|
+
padding: 6px 10px;
|
|
32
|
+
border-radius: 10px;
|
|
33
|
+
border: 1px solid #d0d0d0;
|
|
34
|
+
}
|
|
35
|
+
.icon {
|
|
36
|
+
font-size: 16px;
|
|
37
|
+
color: @primary-color;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
&_operate {
|
|
41
|
+
display: grid;
|
|
42
|
+
grid-template-columns: 1fr auto;
|
|
43
|
+
background: #fff;
|
|
44
|
+
align-items: center;
|
|
45
|
+
&_wrap {
|
|
46
|
+
margin: 10px 0;
|
|
47
|
+
width: 100%;
|
|
48
|
+
min-height: 48px;
|
|
49
|
+
padding: 10px;
|
|
50
|
+
border-radius: 24px;
|
|
51
|
+
box-sizing: border-box;
|
|
52
|
+
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.2);
|
|
53
|
+
.file_card {
|
|
54
|
+
width: 64px;
|
|
55
|
+
height: 64px;
|
|
56
|
+
border-radius: 15px;
|
|
57
|
+
background-color: #f7f7f7;
|
|
58
|
+
position: relative;
|
|
59
|
+
.img {
|
|
60
|
+
width: 100%;
|
|
61
|
+
height: 100%;
|
|
62
|
+
object-fit: contain;
|
|
63
|
+
}
|
|
64
|
+
.close {
|
|
65
|
+
cursor: pointer;
|
|
66
|
+
font-size: 16px;
|
|
67
|
+
color: #fff;
|
|
68
|
+
position: absolute;
|
|
69
|
+
top: 0;
|
|
70
|
+
right: 0;
|
|
71
|
+
z-index: 5;
|
|
72
|
+
}
|
|
73
|
+
.process {
|
|
74
|
+
.flexrcc();
|
|
75
|
+
width: 100%;
|
|
76
|
+
height: 100%;
|
|
77
|
+
background-color: rgba(0, 0, 0, 0.6);
|
|
78
|
+
position: absolute;
|
|
79
|
+
top: 0;
|
|
80
|
+
left: 0;
|
|
81
|
+
z-index: 8;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
.file_add {
|
|
85
|
+
.flexrcc();
|
|
86
|
+
.file_card();
|
|
87
|
+
cursor: pointer;
|
|
88
|
+
font-size: 24px;
|
|
89
|
+
}
|
|
90
|
+
.file_list {
|
|
91
|
+
.flexrsc();
|
|
92
|
+
gap: 10px;
|
|
93
|
+
padding-bottom: 10px;
|
|
94
|
+
position: relative;
|
|
95
|
+
&_box {
|
|
96
|
+
transition: all 200ms ease-in-out;
|
|
97
|
+
overflow: hidden;
|
|
98
|
+
height: 74px;
|
|
99
|
+
opacity: 1;
|
|
100
|
+
&.hidden {
|
|
101
|
+
height: 0;
|
|
102
|
+
opacity: 0;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
.file_close {
|
|
107
|
+
.flexrcc();
|
|
108
|
+
cursor: pointer;
|
|
109
|
+
color: #fff;
|
|
110
|
+
width: 20px;
|
|
111
|
+
height: 20px;
|
|
112
|
+
border-radius: 50%;
|
|
113
|
+
background-color: rgba(0, 0, 0, 0.5);
|
|
114
|
+
position: absolute;
|
|
115
|
+
right: 0;
|
|
116
|
+
top: 0;
|
|
117
|
+
z-index: 10;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
.input {
|
|
121
|
+
font-size: 16px;
|
|
122
|
+
line-height: 24px;
|
|
123
|
+
overflow-y: auto;
|
|
124
|
+
resize: none;
|
|
125
|
+
padding: 0 0 0 10px;
|
|
126
|
+
border-radius: 24px;
|
|
127
|
+
border: none;
|
|
128
|
+
&:focus-visible {
|
|
129
|
+
outline: none;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
.btn {
|
|
133
|
+
.flexrcc();
|
|
134
|
+
height: 100%;
|
|
135
|
+
color: @primary-color;
|
|
136
|
+
font-size: 32px;
|
|
137
|
+
padding: 0 10px;
|
|
138
|
+
&_group {
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
&_extra {
|
|
143
|
+
.flexrss();
|
|
144
|
+
gap: 10px;
|
|
145
|
+
padding: 10px 0 20px;
|
|
146
|
+
.card {
|
|
147
|
+
text-align: center;
|
|
148
|
+
}
|
|
149
|
+
.text {
|
|
150
|
+
color: #666;
|
|
151
|
+
font-size: 14px;
|
|
152
|
+
line-height: 24px;
|
|
153
|
+
margin-top: 6px;
|
|
154
|
+
}
|
|
155
|
+
.icon {
|
|
156
|
+
.flexrcc();
|
|
157
|
+
width: 72px;
|
|
158
|
+
height: 72px;
|
|
159
|
+
color: #000;
|
|
160
|
+
font-size: 24px;
|
|
161
|
+
background-color: #f7f7f7;
|
|
162
|
+
border-radius: 15px;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
@@ -7,24 +7,87 @@
|
|
|
7
7
|
:key="item.value"
|
|
8
8
|
@click.stop="handleTag(item)"
|
|
9
9
|
>
|
|
10
|
-
<tag-icon class="icon" />
|
|
10
|
+
<tag-icon class="icon" />
|
|
11
|
+
{{ item.name }}
|
|
11
12
|
</div>
|
|
12
13
|
</div>
|
|
13
|
-
<div class="
|
|
14
|
-
<
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
14
|
+
<div class="om_operate_wrap">
|
|
15
|
+
<div class="file_list_box" :class="{ hidden: !uploadPlane }">
|
|
16
|
+
<div class="file_list">
|
|
17
|
+
<div
|
|
18
|
+
v-for="(item, index) in uFileList"
|
|
19
|
+
:key="index"
|
|
20
|
+
class="file_card"
|
|
21
|
+
>
|
|
22
|
+
<img class="img" :src="item.file_url" width="100%" alt="img" />
|
|
23
|
+
<div v-if="item.percent < 100" class="process">
|
|
24
|
+
<progress-ring :percent="item.percent" />
|
|
25
|
+
</div>
|
|
26
|
+
<close-border-icon
|
|
27
|
+
v-else
|
|
28
|
+
class="close"
|
|
29
|
+
@click.stop="clearFile(index)"
|
|
30
|
+
/>
|
|
31
|
+
</div>
|
|
32
|
+
<div
|
|
33
|
+
v-if="uFileList.length < 4"
|
|
34
|
+
class="file_add"
|
|
35
|
+
@click.stop="triggerUploadInput"
|
|
36
|
+
>
|
|
37
|
+
<add-icon />
|
|
38
|
+
</div>
|
|
39
|
+
<div class="file_close" @click.stop="closeUpload">
|
|
40
|
+
<close-icon />
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
|
|
45
|
+
<div class="om_operate">
|
|
46
|
+
<textarea
|
|
47
|
+
v-model="textValue"
|
|
48
|
+
ref="txtEle"
|
|
49
|
+
class="input"
|
|
50
|
+
rows="1"
|
|
51
|
+
placeholder="發消息⋯"
|
|
52
|
+
@input="handleInput"
|
|
53
|
+
@keyup.enter="sendMsg"
|
|
54
|
+
/>
|
|
55
|
+
<div class="btn_group">
|
|
56
|
+
<div class="btn" @click.stop="sendMsg">
|
|
57
|
+
<send-icon :style="{ color: textValue ? '#039938' : '#ccc' }" />
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
<div class="om_extra" style="display: none">
|
|
63
|
+
<input
|
|
64
|
+
ref="captureInput"
|
|
65
|
+
type="file"
|
|
66
|
+
accept="image/*"
|
|
67
|
+
capture="environment"
|
|
68
|
+
style="display: none"
|
|
69
|
+
@change="handleFile"
|
|
22
70
|
/>
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
71
|
+
|
|
72
|
+
<input
|
|
73
|
+
ref="uploadInput"
|
|
74
|
+
type="file"
|
|
75
|
+
accept="image/*"
|
|
76
|
+
style="display: none"
|
|
77
|
+
@change="handleFileChange"
|
|
78
|
+
/>
|
|
79
|
+
<div
|
|
80
|
+
v-for="card in extraList"
|
|
81
|
+
:key="card.type"
|
|
82
|
+
class="card"
|
|
83
|
+
@click.stop="triggerUploadInput"
|
|
84
|
+
>
|
|
85
|
+
<div class="icon">
|
|
86
|
+
<component :is="card.icon" />
|
|
26
87
|
</div>
|
|
88
|
+
<div class="text">{{ card.name }}</div>
|
|
27
89
|
</div>
|
|
90
|
+
<progress-ring :percent="15" />
|
|
28
91
|
</div>
|
|
29
92
|
</div>
|
|
30
93
|
</template>
|
|
@@ -33,14 +96,35 @@
|
|
|
33
96
|
import TagIcon from './icons/tagIcon.vue';
|
|
34
97
|
import SendIcon from './icons/SendIcon.vue';
|
|
35
98
|
import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue';
|
|
99
|
+
import CameraIcon from './icons/cameraIcon.vue';
|
|
100
|
+
import PictureIcon from './icons/pictureIcon.vue';
|
|
101
|
+
import FileIcon from './icons/fileIcon.vue';
|
|
102
|
+
import ProgressRing from './icons/progressRing.vue';
|
|
103
|
+
import AddIcon from './icons/addIcon.vue';
|
|
104
|
+
import CloseBorderIcon from './icons/closeBorderIcon.vue';
|
|
105
|
+
import CloseIcon from './icons/CloseIcon.vue';
|
|
106
|
+
|
|
107
|
+
const extraList = [
|
|
108
|
+
// { name: '相機', icon: CameraIcon, type: 'camera' },
|
|
109
|
+
{ name: '相冊', icon: PictureIcon, type: 'photo' },
|
|
110
|
+
{ name: '文件', icon: FileIcon, type: 'file' },
|
|
111
|
+
];
|
|
36
112
|
|
|
37
113
|
const txtEle = ref(null);
|
|
114
|
+
const uploadInput = ref(null);
|
|
115
|
+
const captureInput = ref(null);
|
|
116
|
+
const uploadPlane = ref(false);
|
|
38
117
|
|
|
118
|
+
const uFileList = ref([]);
|
|
39
119
|
const props = defineProps({
|
|
40
120
|
modelValue: {
|
|
41
121
|
type: String,
|
|
42
122
|
default: '',
|
|
43
123
|
},
|
|
124
|
+
token: {
|
|
125
|
+
type: String,
|
|
126
|
+
required: true,
|
|
127
|
+
},
|
|
44
128
|
tagList: {
|
|
45
129
|
type: Array,
|
|
46
130
|
required: true,
|
|
@@ -48,6 +132,15 @@ const props = defineProps({
|
|
|
48
132
|
});
|
|
49
133
|
const Emits = defineEmits(['update:modelValue', 'send', 'tag']);
|
|
50
134
|
|
|
135
|
+
const fetchOptions = computed(() => {
|
|
136
|
+
return {
|
|
137
|
+
headers: {
|
|
138
|
+
Authorization: `Bearer ${props.token}`,
|
|
139
|
+
'Content-Type': 'multipart/form-data',
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
});
|
|
143
|
+
|
|
51
144
|
watch(
|
|
52
145
|
() => props.modelValue,
|
|
53
146
|
() => {
|
|
@@ -79,12 +172,110 @@ const handleInput = (event) => {
|
|
|
79
172
|
textValue.value = event.target.value.replace(/^\n+|\n+$/g, '');
|
|
80
173
|
};
|
|
81
174
|
const handleTag = (info) => {
|
|
82
|
-
|
|
175
|
+
if (info.type === 'upload') {
|
|
176
|
+
showUpload();
|
|
177
|
+
} else {
|
|
178
|
+
Emits('tag', { ...info });
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
const sendMsg = (e) => {
|
|
182
|
+
const list = [{ content: textValue.value, text: textValue.value }];
|
|
183
|
+
if (uFileList.value.length) {
|
|
184
|
+
console.log(uFileList.value);
|
|
185
|
+
list.push({
|
|
186
|
+
type: 'object_string',
|
|
187
|
+
content: uFileList.value.map((file) => ({
|
|
188
|
+
type: 'image',
|
|
189
|
+
file_id: file.data.id,
|
|
190
|
+
file_url: file.file_url,
|
|
191
|
+
})),
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
Emits('send', [...list]);
|
|
195
|
+
clearAllFile();
|
|
196
|
+
};
|
|
197
|
+
const handleFile = (e) => {
|
|
198
|
+
const file = e.target.files[0];
|
|
199
|
+
const reader = new FileReader();
|
|
200
|
+
reader.onload = () => (previewUrl.value = reader.result);
|
|
201
|
+
reader.readAsDataURL(file);
|
|
202
|
+
};
|
|
203
|
+
const triggerUploadInput = () => {
|
|
204
|
+
uploadInput.value ? uploadInput.value.click() : '';
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
const handleFileChange = (e) => {
|
|
208
|
+
const selFile = e.target.files[0];
|
|
209
|
+
|
|
210
|
+
if (selFile) {
|
|
211
|
+
uFileList.value.push({
|
|
212
|
+
file: selFile,
|
|
213
|
+
file_url: URL.createObjectURL(selFile),
|
|
214
|
+
size: (selFile.size / 1024).toFixed(2),
|
|
215
|
+
percent: 0,
|
|
216
|
+
data: {},
|
|
217
|
+
});
|
|
218
|
+
console.log('=== 文件列表 ===', uFileList.value);
|
|
219
|
+
handleUpload(uFileList.value.length - 1);
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
const handleUpload = async (idx) => {
|
|
223
|
+
const file = uFileList.value[idx].file;
|
|
224
|
+
console.log('========', file);
|
|
225
|
+
if (!textValue.value) {
|
|
226
|
+
textValue.value = '請根據我上傳的體檢報告為我生成飲食方案';
|
|
227
|
+
}
|
|
228
|
+
const formDate = new FormData();
|
|
229
|
+
formDate.append('file', file);
|
|
230
|
+
const xhr = new XMLHttpRequest();
|
|
231
|
+
xhr.open('POST', 'https://api.coze.cn/v1/files/upload');
|
|
232
|
+
xhr.setRequestHeader(
|
|
233
|
+
'Authorization',
|
|
234
|
+
fetchOptions.value.headers.Authorization,
|
|
235
|
+
);
|
|
236
|
+
xhr.upload.onprogress = (e) => {
|
|
237
|
+
if (e.lengthComputable) {
|
|
238
|
+
const percent = Math.round((e.loaded / e.total) * 100);
|
|
239
|
+
console.log(`======进度======:${percent}%`);
|
|
240
|
+
uFileList.value[idx].percent = percent;
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
xhr.onreadystatechange = function () {
|
|
244
|
+
console.log('============', xhr);
|
|
245
|
+
if (xhr.readyState === XMLHttpRequest.DONE) {
|
|
246
|
+
if (xhr.status === 200) {
|
|
247
|
+
console.log('上传成功:', xhr.responseText);
|
|
248
|
+
const res = JSON.parse(xhr.response);
|
|
249
|
+
uFileList.value[idx].data = res.data;
|
|
250
|
+
} else {
|
|
251
|
+
console.error('上传失败:', xhr.status);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
xhr.send(formDate);
|
|
83
256
|
};
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
257
|
+
const showUpload = () => {
|
|
258
|
+
clearAllFile(true);
|
|
259
|
+
uploadPlane.value = true;
|
|
260
|
+
};
|
|
261
|
+
const closeUpload = () => {
|
|
262
|
+
clearAllFile(true);
|
|
263
|
+
uploadPlane.value = false;
|
|
264
|
+
};
|
|
265
|
+
const clearAllFile = (deep = false) => {
|
|
266
|
+
if (deep) {
|
|
267
|
+
for (let i = uFileList.value.length - 1; i >= 0; i -= 1) {
|
|
268
|
+
clearFile(i);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
uFileList.value = [];
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
const clearFile = (idx) => {
|
|
275
|
+
URL.revokeObjectURL(uFileList.value[idx].file_url);
|
|
276
|
+
uFileList.value.splice(idx, 1);
|
|
87
277
|
};
|
|
278
|
+
|
|
88
279
|
const resizeTextarea = () => {
|
|
89
280
|
nextTick(() => {
|
|
90
281
|
if (txtEle.value) {
|
|
@@ -97,78 +288,5 @@ const resizeTextarea = () => {
|
|
|
97
288
|
</script>
|
|
98
289
|
|
|
99
290
|
<style scoped lang="less">
|
|
100
|
-
@import '
|
|
101
|
-
.om {
|
|
102
|
-
&_wrap {
|
|
103
|
-
box-sizing: border-box;
|
|
104
|
-
width: 100%;
|
|
105
|
-
padding: 10px;
|
|
106
|
-
background-color: #fff;
|
|
107
|
-
}
|
|
108
|
-
&_quick {
|
|
109
|
-
display: grid;
|
|
110
|
-
grid-auto-flow: column;
|
|
111
|
-
gap: 10px;
|
|
112
|
-
overflow-x: auto;
|
|
113
|
-
justify-content: start;
|
|
114
|
-
scroll-behavior: smooth;
|
|
115
|
-
scrollbar-width: none; /* 隐藏滚动条(Firefox) */
|
|
116
|
-
&::-webkit-scrollbar {
|
|
117
|
-
display: none;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
.tag {
|
|
121
|
-
.flexrsc();
|
|
122
|
-
cursor: pointer;
|
|
123
|
-
width: fit-content;
|
|
124
|
-
white-space: nowrap;
|
|
125
|
-
font-weight: 400;
|
|
126
|
-
font-size: 12px;
|
|
127
|
-
color: #333;
|
|
128
|
-
line-height: 16px;
|
|
129
|
-
text-align: left;
|
|
130
|
-
padding: 6px 10px;
|
|
131
|
-
border-radius: 10px;
|
|
132
|
-
border: 1px solid #d0d0d0;
|
|
133
|
-
}
|
|
134
|
-
.icon {
|
|
135
|
-
font-size: 16px;
|
|
136
|
-
color: @primary-color;
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
&_operate {
|
|
140
|
-
display: grid;
|
|
141
|
-
grid-template-columns: 1fr auto;
|
|
142
|
-
box-sizing: border-box;
|
|
143
|
-
margin: 10px 0;
|
|
144
|
-
width: 100%;
|
|
145
|
-
min-height: 48px;
|
|
146
|
-
padding: 10px;
|
|
147
|
-
background: #fff;
|
|
148
|
-
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.2);
|
|
149
|
-
border-radius: 24px;
|
|
150
|
-
align-items: center;
|
|
151
|
-
.input {
|
|
152
|
-
font-size: 16px;
|
|
153
|
-
line-height: 24px;
|
|
154
|
-
overflow-y: auto;
|
|
155
|
-
resize: none;
|
|
156
|
-
padding: 0 0 0 10px;
|
|
157
|
-
border-radius: 24px;
|
|
158
|
-
border: none;
|
|
159
|
-
&:focus-visible {
|
|
160
|
-
outline: none;
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
.btn {
|
|
164
|
-
.flexrcc();
|
|
165
|
-
height: 100%;
|
|
166
|
-
color: @primary-color;
|
|
167
|
-
font-size: 32px;
|
|
168
|
-
padding: 0 10px;
|
|
169
|
-
&_group {
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
}
|
|
291
|
+
@import './OperateModule';
|
|
174
292
|
</style>
|
|
@@ -1,10 +1,52 @@
|
|
|
1
1
|
<template>
|
|
2
|
+
<div class="card" style="margin-top: 15px">
|
|
3
|
+
<div class="row">
|
|
4
|
+
<div class="name">每日攝入能量</div>
|
|
5
|
+
<div class="content">{{ info.totalIntake }} kcal</div>
|
|
6
|
+
</div>
|
|
7
|
+
</div>
|
|
8
|
+
<div v-for="cate in info.planDetailList" :key="category" class="card">
|
|
9
|
+
<div class="title">{{ textMap[cate.category] }}</div>
|
|
10
|
+
<div class="row">
|
|
11
|
+
<div class="name">{{ textMap[cate.category] }}熱量</div>
|
|
12
|
+
<div class="content">{{ cate.totalKcal }} kcal</div>
|
|
13
|
+
</div>
|
|
14
|
+
<div class="sep" />
|
|
15
|
+
<div class="row">
|
|
16
|
+
<div class="name">{{ textMap.carbohydrateTotal }}</div>
|
|
17
|
+
<div class="content">{{ cate.carbohydrateTotal }} g</div>
|
|
18
|
+
</div>
|
|
19
|
+
<div class="row">
|
|
20
|
+
<div class="name">{{ textMap.proteinTotal }}</div>
|
|
21
|
+
<div class="content">{{ cate.proteinTotal }} g</div>
|
|
22
|
+
</div>
|
|
23
|
+
<div class="row">
|
|
24
|
+
<div class="name">{{ textMap.fatTotal }}</div>
|
|
25
|
+
<div class="content">{{ cate.fatTotal }} g</div>
|
|
26
|
+
</div>
|
|
27
|
+
</div>
|
|
2
28
|
<div class="btn_group">
|
|
3
29
|
<div class="btn btn_2" @click.stop="handleSel">用該方案配餐</div>
|
|
4
30
|
</div>
|
|
5
31
|
</template>
|
|
6
32
|
|
|
7
33
|
<script setup>
|
|
34
|
+
const textMap = {
|
|
35
|
+
'01': '早餐',
|
|
36
|
+
'02': '午餐',
|
|
37
|
+
'03': '晚餐',
|
|
38
|
+
'04': '加餐',
|
|
39
|
+
carbohydrateTotal: '碳水化合物',
|
|
40
|
+
proteinTotal: '蛋白質',
|
|
41
|
+
fatTotal: '脂肪',
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const props = defineProps({
|
|
45
|
+
info: {
|
|
46
|
+
type: Object,
|
|
47
|
+
required: true,
|
|
48
|
+
},
|
|
49
|
+
});
|
|
8
50
|
const Emits = defineEmits(['select']);
|
|
9
51
|
const handleSel = () => {
|
|
10
52
|
Emits('select', {});
|
|
@@ -12,6 +54,35 @@ const handleSel = () => {
|
|
|
12
54
|
</script>
|
|
13
55
|
|
|
14
56
|
<style scoped lang="less">
|
|
57
|
+
.card {
|
|
58
|
+
padding: 10px 15px;
|
|
59
|
+
margin-bottom: 10px;
|
|
60
|
+
background-color: #fff;
|
|
61
|
+
border-radius: 10px;
|
|
62
|
+
.title {
|
|
63
|
+
font-size: 14px;
|
|
64
|
+
font-weight: 600;
|
|
65
|
+
line-height: 24px;
|
|
66
|
+
}
|
|
67
|
+
.row {
|
|
68
|
+
.flexrbc();
|
|
69
|
+
margin: 10px 0;
|
|
70
|
+
}
|
|
71
|
+
.sep {
|
|
72
|
+
border-bottom: 1px solid #ddd;
|
|
73
|
+
}
|
|
74
|
+
.name {
|
|
75
|
+
font-weight: 400;
|
|
76
|
+
font-size: 14px;
|
|
77
|
+
color: #666;
|
|
78
|
+
line-height: 24px;
|
|
79
|
+
}
|
|
80
|
+
.content {
|
|
81
|
+
.name();
|
|
82
|
+
color: #000;
|
|
83
|
+
font-weight: 600;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
15
86
|
.btn {
|
|
16
87
|
cursor: pointer;
|
|
17
88
|
width: 100%;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<svg
|
|
3
|
+
width="1em"
|
|
4
|
+
height="1em"
|
|
5
|
+
viewBox="0 0 24 24"
|
|
6
|
+
fill="none"
|
|
7
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
8
|
+
>
|
|
9
|
+
<path
|
|
10
|
+
d="M13.5 4.5C13.5 3.67157 12.8284 3 12 3C11.1716 3 10.5 3.67157 10.5 4.5L10.5 10.5H4.5C3.67157 10.5 3 11.1716 3 12C3 12.8284 3.67157 13.5 4.5 13.5H10.5L10.5 19.5C10.5 20.3284 11.1716 21 12 21C12.8284 21 13.5 20.3284 13.5 19.5V13.5H19.5C20.3284 13.5 21 12.8284 21 12C21 11.1716 20.3284 10.5 19.5 10.5H13.5V4.5Z"
|
|
11
|
+
fill="currentColor"
|
|
12
|
+
/>
|
|
13
|
+
</svg>
|
|
14
|
+
</template>
|
|
15
|
+
|
|
16
|
+
<script></script>
|
|
17
|
+
|
|
18
|
+
<style scoped></style>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<svg
|
|
3
|
+
width="1em"
|
|
4
|
+
height="1em"
|
|
5
|
+
viewBox="0 0 25 24"
|
|
6
|
+
fill="none"
|
|
7
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
8
|
+
>
|
|
9
|
+
<path
|
|
10
|
+
d="M6.03791 5.43733C4.02837 5.73154 2.36942 7.19356 2.01374 9.19314C1.80186 10.3843 1.625 11.7046 1.625 12.7932C1.625 14.1142 1.88543 15.7626 2.15286 17.1069C2.47288 18.7157 3.53388 20.0703 5.08995 20.5892C6.81466 21.1644 9.4087 21.7782 12.6246 21.7782C15.8579 21.7782 18.4626 21.1577 20.1872 20.5799C21.7264 20.0641 22.781 18.729 23.1036 17.138C23.3774 15.788 23.6415 14.1244 23.6242 12.7932C23.6125 11.8974 23.4692 10.8334 23.2896 9.83524C22.9086 7.71845 21.2484 6.11084 19.1702 5.55694C18.4877 5.37505 17.8729 4.99872 17.4004 4.47375L16.4669 3.4365C15.7716 2.66386 14.7809 2.22266 13.7415 2.22266H11.5077C10.4683 2.22266 9.47763 2.66385 8.78228 3.4365L7.85962 4.46171C7.38473 4.98939 6.74034 5.3345 6.03791 5.43733ZM15.0694 12.6113C15.0694 13.9614 13.975 15.0558 12.625 15.0558C11.275 15.0558 10.1805 13.9614 10.1805 12.6113C10.1805 11.2613 11.275 10.1669 12.625 10.1669C13.975 10.1669 15.0694 11.2613 15.0694 12.6113ZM17.5139 12.6113C17.5139 15.3114 15.325 17.5002 12.625 17.5002C9.92493 17.5002 7.7361 15.3114 7.7361 12.6113C7.7361 9.91127 9.92493 7.72244 12.625 7.72244C15.325 7.72244 17.5139 9.91127 17.5139 12.6113Z"
|
|
11
|
+
fill="currentColor"
|
|
12
|
+
/>
|
|
13
|
+
</svg>
|
|
14
|
+
</template>
|
|
15
|
+
|
|
16
|
+
<script>
|
|
17
|
+
export default {
|
|
18
|
+
name: 'camreIcon',
|
|
19
|
+
};
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
<style scoped></style>
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<svg
|
|
3
|
+
width="1em"
|
|
4
|
+
height="1em"
|
|
5
|
+
viewBox="0 0 16 16"
|
|
6
|
+
fill="none"
|
|
7
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
8
|
+
>
|
|
9
|
+
<rect
|
|
10
|
+
x="0.5"
|
|
11
|
+
y="0.5"
|
|
12
|
+
width="15"
|
|
13
|
+
height="15"
|
|
14
|
+
rx="7.5"
|
|
15
|
+
fill="black"
|
|
16
|
+
fill-opacity="0.5"
|
|
17
|
+
/>
|
|
18
|
+
<rect
|
|
19
|
+
x="0.5"
|
|
20
|
+
y="0.5"
|
|
21
|
+
width="15"
|
|
22
|
+
height="15"
|
|
23
|
+
rx="7.5"
|
|
24
|
+
stroke="currentColor"
|
|
25
|
+
/>
|
|
26
|
+
<path
|
|
27
|
+
d="M10.8284 6.58588C11.2189 6.19535 11.2189 5.56219 10.8284 5.17167C10.4379 4.78114 9.80471 4.78114 9.41419 5.17167L8 6.58586L6.58581 5.17167C6.19529 4.78114 5.56212 4.78114 5.1716 5.17167C4.78107 5.56219 4.78107 6.19535 5.1716 6.58588L6.58579 8.00007L5.17155 9.41431C4.78103 9.80483 4.78103 10.438 5.17155 10.8285C5.56207 11.219 6.19524 11.219 6.58576 10.8285L8 9.41428L9.41424 10.8285C9.80476 11.219 10.4379 11.219 10.8285 10.8285C11.219 10.438 11.219 9.80483 10.8285 9.41431L9.41421 8.00007L10.8284 6.58588Z"
|
|
28
|
+
fill="currentColor"
|
|
29
|
+
/>
|
|
30
|
+
</svg>
|
|
31
|
+
</template>
|
|
32
|
+
|
|
33
|
+
<script></script>
|
|
34
|
+
|
|
35
|
+
<style scoped></style>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<svg
|
|
3
|
+
width="1em"
|
|
4
|
+
height="1em"
|
|
5
|
+
viewBox="0 0 24 24"
|
|
6
|
+
fill="none"
|
|
7
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
8
|
+
>
|
|
9
|
+
<path
|
|
10
|
+
d="M18.0849 10.6565C19.9734 8.76795 19.9734 5.70607 18.0849 3.81756C16.1964 1.92905 13.1345 1.92905 11.246 3.81756L5.05399 10.0096C2.55177 12.5118 2.55177 16.5687 5.05399 19.0709C7.55622 21.5732 11.6131 21.5732 14.1154 19.0709L20.2421 12.9442C20.6326 12.5537 20.6326 11.9205 20.2421 11.53C19.8516 11.1394 19.2184 11.1394 18.8279 11.53L12.7011 17.6567C10.98 19.3779 8.18938 19.3779 6.46821 17.6567C4.74703 15.9355 4.74703 13.145 6.4682 11.4238L12.6602 5.23178C13.7677 4.12431 15.5632 4.12431 16.6707 5.23178C17.7781 6.33924 17.7781 8.13478 16.6707 9.24224L10.5401 15.3729C10.0481 15.8648 9.25051 15.8648 8.75856 15.3729C8.26661 14.8809 8.26661 14.0833 8.75856 13.5913L13.9435 8.40645C14.334 8.01592 14.334 7.38276 13.9435 6.99223C13.5529 6.60171 12.9198 6.60171 12.5292 6.99223L7.34435 12.1771C6.07135 13.4501 6.07135 15.5141 7.34435 16.7871C8.61735 18.0601 10.6813 18.0601 11.9543 16.7871L18.0849 10.6565Z"
|
|
11
|
+
fill="currentColor"
|
|
12
|
+
/>
|
|
13
|
+
</svg>
|
|
14
|
+
</template>
|
|
15
|
+
|
|
16
|
+
<script></script>
|
|
17
|
+
|
|
18
|
+
<style scoped></style>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<svg
|
|
3
|
+
width="1em"
|
|
4
|
+
height="1em"
|
|
5
|
+
viewBox="0 0 24 24"
|
|
6
|
+
fill="none"
|
|
7
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
8
|
+
>
|
|
9
|
+
<path
|
|
10
|
+
d="M2.875 6C2.875 4.34315 4.21815 3 5.875 3H19.875C21.5319 3 22.875 4.34315 22.875 6V18C22.875 19.6569 21.5319 21 19.875 21H5.875C4.21815 21 2.875 19.6569 2.875 18V6ZM5.875 5C5.32272 5 4.875 5.44772 4.875 6V16.0852L10.475 11.8852C11.8972 10.8185 13.8528 10.8185 15.275 11.8852L20.875 16.0852V6C20.875 5.44772 20.4273 5 19.875 5H5.875ZM8.28442 10.0852C9.11285 10.0852 9.78442 9.41363 9.78442 8.58521C9.78442 7.75678 9.11285 7.08521 8.28442 7.08521C7.456 7.08521 6.78442 7.75678 6.78442 8.58521C6.78442 9.41363 7.456 10.0852 8.28442 10.0852Z"
|
|
11
|
+
fill="currentColor"
|
|
12
|
+
/>
|
|
13
|
+
</svg>
|
|
14
|
+
</template>
|
|
15
|
+
|
|
16
|
+
<script>
|
|
17
|
+
export default {
|
|
18
|
+
name: 'pictureIcon',
|
|
19
|
+
};
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
<style scoped></style>
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<svg
|
|
3
|
+
:width="size"
|
|
4
|
+
:height="size"
|
|
5
|
+
viewBox="0 0 100 100"
|
|
6
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
7
|
+
>
|
|
8
|
+
<!-- 绘制底层浅色圆环 -->
|
|
9
|
+
<circle
|
|
10
|
+
cx="50"
|
|
11
|
+
cy="50"
|
|
12
|
+
r="45"
|
|
13
|
+
fill="none"
|
|
14
|
+
:stroke="bgColor"
|
|
15
|
+
stroke-width="8"
|
|
16
|
+
/>
|
|
17
|
+
<!-- 绘制上层白色圆弧(带圆角端点) -->
|
|
18
|
+
<path
|
|
19
|
+
id="progressPath"
|
|
20
|
+
d="M 50 5
|
|
21
|
+
a 45 45 0 1 1 0 90
|
|
22
|
+
a 45 45 0 1 1 0 -90"
|
|
23
|
+
fill="none"
|
|
24
|
+
:stroke="color"
|
|
25
|
+
stroke-width="10"
|
|
26
|
+
stroke-linecap="round"
|
|
27
|
+
:stroke-dasharray="num.l"
|
|
28
|
+
:stroke-dashoffset="num.p"
|
|
29
|
+
/>
|
|
30
|
+
</svg>
|
|
31
|
+
</template>
|
|
32
|
+
|
|
33
|
+
<script setup>
|
|
34
|
+
import { computed } from 'vue';
|
|
35
|
+
|
|
36
|
+
const props = defineProps({
|
|
37
|
+
percent: {
|
|
38
|
+
type: Number,
|
|
39
|
+
default: 75,
|
|
40
|
+
},
|
|
41
|
+
size: {
|
|
42
|
+
type: Number,
|
|
43
|
+
default: 48,
|
|
44
|
+
},
|
|
45
|
+
color: {
|
|
46
|
+
type: String,
|
|
47
|
+
default: '#fff',
|
|
48
|
+
},
|
|
49
|
+
bgColor: {
|
|
50
|
+
type: String,
|
|
51
|
+
default: '#888',
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const num = computed(() => {
|
|
56
|
+
return {
|
|
57
|
+
l: 2 * Math.PI * 45,
|
|
58
|
+
p: (2 * Math.PI * 45 * (100 - props.percent)) / 100,
|
|
59
|
+
};
|
|
60
|
+
});
|
|
61
|
+
</script>
|
|
62
|
+
|
|
63
|
+
<style scoped></style>
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="img_wrap" :class="list.length > 1 ? 'grid_2' : 'grid_1'">
|
|
3
|
+
<img
|
|
4
|
+
v-for="info in list"
|
|
5
|
+
:key="info.file_id"
|
|
6
|
+
class="img"
|
|
7
|
+
width="100%"
|
|
8
|
+
:src="info.file_url"
|
|
9
|
+
:alt="info.name"
|
|
10
|
+
/>
|
|
11
|
+
</div>
|
|
12
|
+
</template>
|
|
13
|
+
|
|
14
|
+
<script setup>
|
|
15
|
+
const props = defineProps({
|
|
16
|
+
list: {
|
|
17
|
+
type: Array,
|
|
18
|
+
required: true,
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
</script>
|
|
22
|
+
|
|
23
|
+
<style scoped lang="less">
|
|
24
|
+
.img_wrap {
|
|
25
|
+
display: grid;
|
|
26
|
+
gap: 10px;
|
|
27
|
+
justify-content: right;
|
|
28
|
+
padding: 10px 0;
|
|
29
|
+
.img {
|
|
30
|
+
}
|
|
31
|
+
&.grid_1 {
|
|
32
|
+
grid-template-columns: 200px;
|
|
33
|
+
}
|
|
34
|
+
&.grid_2 {
|
|
35
|
+
display: grid;
|
|
36
|
+
grid-template-columns: 100px 100px;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
</style>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export const useDeviceDetect = () => {
|
|
2
|
+
const device = {
|
|
3
|
+
isMobile: false,
|
|
4
|
+
isIOS: false,
|
|
5
|
+
isAndroid: false,
|
|
6
|
+
isPC: false,
|
|
7
|
+
};
|
|
8
|
+
const ua = navigator.userAgent;
|
|
9
|
+
const mobileRegex =
|
|
10
|
+
/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i;
|
|
11
|
+
const iosRegex = /iPhone|iPad|iPod/i;
|
|
12
|
+
// 移动端检测
|
|
13
|
+
device.isMobile = mobileRegex.test(ua);
|
|
14
|
+
// iOS检测
|
|
15
|
+
device.isIOS = iosRegex.test(ua) && !ua.match(/CriOS/);
|
|
16
|
+
// Android检测
|
|
17
|
+
device.isAndroid = /Android/i.test(ua);
|
|
18
|
+
device.isPC = !device.isMobile;
|
|
19
|
+
return device;
|
|
20
|
+
};
|