Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
C
corporateCulture-qd
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
王立鹏
corporateCulture-qd
Commits
bed64a7f
Commit
bed64a7f
authored
Feb 02, 2026
by
lijiabin
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
【需求 20331】 feat: 关于评论相关的加入表情和图片
parent
c137a089
Show whitespace changes
Inline
Side-by-side
Showing
13 changed files
with
768 additions
and
194 deletions
+768
-194
types.ts
src/api/article/types.ts
+2
-0
icon_face.svg
src/assets/svg/icon_face.svg
+2
-0
icon_picture.svg
src/assets/svg/icon_picture.svg
+2
-0
ReplyBox.vue
src/components/common/Comment/components/ReplyBox.vue
+176
-0
index.vue
src/components/common/Comment/index.vue
+115
-27
index.vue
src/components/common/CommentDialog/index.vue
+135
-22
index.vue
src/components/common/CommentListDialog/index.vue
+44
-4
selectTagsDialog.vue
...ponents/common/PublishBox/components/selectTagsDialog.vue
+7
-1
index.vue
src/components/common/PublishBox/index.vue
+69
-13
useUploadImg.ts
src/hooks/useUploadImg.ts
+49
-9
face.json
src/utils/emoji/face.json
+100
-100
index.ts
src/utils/emoji/index.ts
+28
-0
index.vue
src/views/questionDetail/index.vue
+39
-18
No files found.
src/api/article/types.ts
View file @
bed64a7f
...
...
@@ -326,6 +326,7 @@ export interface AddCommentDto {
articleId
:
number
|
string
content
:
string
pid
?:
number
|
string
imgUrl
?:
string
}
/**
...
...
@@ -359,6 +360,7 @@ export interface CommentItemDto {
showComment
:
boolean
isExpand
:
boolean
childNum
:
number
imgUrl
:
string
}
/**
...
...
src/assets/svg/icon_face.svg
0 → 100644
View file @
bed64a7f
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg
t=
"1653463274928"
class=
"icon"
viewBox=
"0 0 1024 1024"
version=
"1.1"
xmlns=
"http://www.w3.org/2000/svg"
p-id=
"23443"
xmlns:xlink=
"http://www.w3.org/1999/xlink"
width=
"200"
height=
"200"
><defs><style
type=
"text/css"
></style></defs><path
d=
"M514.286299 2.505864h3.565417V895.785974h-3.565417z"
fill=
"#EDA639"
p-id=
"23444"
></path><path
d=
"M69.430967 447.365225h893.276081v3.55736H69.430967z"
fill=
"#EDA639"
p-id=
"23445"
></path><path
d=
"M517.811429 452.578389m-430.932074 0a430.932074 430.932074 0 1 0 861.864148 0 430.932074 430.932074 0 1 0-861.864148 0Z"
fill=
"#FFE770"
p-id=
"23446"
></path><path
d=
"M520.027225 493.522274v160.145663"
fill=
"#FFE770"
p-id=
"23447"
></path><path
d=
"M520.027225 675.024184a21.352218 21.352218 0 0 1-21.352218-21.356247v-160.145663a21.352218 21.352218 0 0 1 42.708465 0v160.145663a21.352218 21.352218 0 0 1-21.356247 21.356247z"
fill=
"#6E6E96"
p-id=
"23448"
></path><path
d=
"M300.917211 313.051716l62.279988-62.279988 62.279988 62.279988"
fill=
"#FFE770"
p-id=
"23449"
></path><path
d=
"M425.477187 334.403934a21.299845 21.299845 0 0 1-15.099644-6.252574L363.197199 280.971016 316.016855 328.15136a21.352218 21.352218 0 1 1-30.199288-30.199288l62.279988-62.279988a21.352218 21.352218 0 0 1 30.199288 0l62.279988 62.279988a21.352218 21.352218 0 0 1-15.099644 36.451862z"
fill=
"#6E6E96"
p-id=
"23450"
></path><path
d=
"M610.536457 313.051716l62.284017-62.279988 62.279988 62.279988"
fill=
"#FFE770"
p-id=
"23451"
></path><path
d=
"M735.100462 334.403934a21.299845 21.299845 0 0 1-15.099644-6.252574l-47.180344-47.180344-47.184373 47.180344a21.356247 21.356247 0 0 1-30.195259-30.199288l62.279988-62.279988a21.352218 21.352218 0 0 1 30.195259 0l62.279989 62.279988a21.352218 21.352218 0 0 1-15.095616 36.451862z"
fill=
"#6E6E96"
p-id=
"23452"
></path><path
d=
"M519.624353 45.947556c-3.831313 0-7.65054 0.068488-11.449623 0.173235 203.095851 5.785242 365.960901 172.231825 365.960901 376.72967 0 208.159953-168.746981 376.898877-376.906934 376.898877-208.155924 0-376.902905-168.738924-376.902906-376.898877a377.615989 377.615989 0 0 1 12.061989-94.924709 406.368966 406.368966 0 0 0-19.523179 124.777527c0 224.645477 182.114276 406.755723 406.759752 406.755724s406.755723-182.110247 406.755724-406.755724c0-224.645477-182.110247-406.755723-406.755724-406.755723z"
fill=
"#FF9900"
opacity=
".24"
p-id=
"23453"
></path><path
d=
"M517.811429 901.305321c-103.779837 0-204.98935-36.262512-284.979593-102.095831-78.906517-64.947001-133.733373-155.508606-154.380564-255.005914a451.740415 451.740415 0 0 1-9.366775-91.629216C69.084497 205.14647 270.383539 3.847428 517.811429 3.847428c247.42789 0 448.726932 201.299042 448.726932 448.726932 0 247.42789-201.299042 448.730961-448.726932 448.730961z m0-861.864149c-227.803993 0-413.137216 185.333223-413.137216 413.137217 0 28.43068 2.900679 56.821072 8.621461 84.397663 19.003474 91.576843 69.487369 174.951211 142.153398 234.757565 73.636951 60.604041 166.813196 93.981989 262.366386 93.981988 227.808022 0 413.137216-185.333223 413.137216-413.137216S745.619451 39.441172 517.811429 39.441172z"
fill=
"#6E6E96"
p-id=
"23454"
></path><path
d=
"M809.635818 752.069432m-135.542268 0a135.542268 135.542268 0 1 0 271.084536 0 135.542268 135.542268 0 1 0-271.084536 0Z"
fill=
"#FFE770"
p-id=
"23455"
></path><path
d=
"M809.635818 905.406558c-84.546726 0-153.337126-68.786372-153.337126-153.341154 0-84.546726 68.786372-153.337126 153.337126-153.337126s153.341155 68.786372 153.341155 153.337126c-0.004029 84.554783-68.7904 153.341155-153.341155 153.341154z m0-271.084536c-64.926857 0-117.74741 52.816524-117.74741 117.74741s52.816524 117.751439 117.74741 117.751439 117.751439-52.816524 117.751439-117.751439c-0.004029-64.930886-52.820553-117.74741-117.751439-117.74741z"
fill=
"#6E6E96"
p-id=
"23456"
></path><path
d=
"M218.864263 752.069432m-135.542268 0a135.542268 135.542268 0 1 0 271.084536 0 135.542268 135.542268 0 1 0-271.084536 0Z"
fill=
"#FFE770"
p-id=
"23457"
></path><path
d=
"M218.864263 905.406558c-84.550754 0-153.337126-68.786372-153.337126-153.341154 0-84.546726 68.786372-153.337126 153.337126-153.337126s153.337126 68.786372 153.337126 153.337126c0 84.554783-68.786372 153.341155-153.337126 153.341154z m0-271.084536c-64.926857 0-117.74741 52.816524-117.74741 117.74741s52.820553 117.751439 117.74741 117.751439 117.751439-52.816524 117.751439-117.751439c-0.004029-64.930886-52.824581-117.74741-117.751439-117.74741z"
fill=
"#6E6E96"
p-id=
"23458"
></path><path
d=
"M305.248085 455.213172a61.824743 28.845638 0 1 0 123.649486 0 61.824743 28.845638 0 1 0-123.649486 0Z"
fill=
"#FF0000"
opacity=
".18"
p-id=
"23459"
></path><path
d=
"M615.403151 455.213172a61.820714 28.845638 0 1 0 123.641429 0 61.820714 28.845638 0 1 0-123.641429 0Z"
fill=
"#FF0000"
opacity=
".18"
p-id=
"23460"
></path><path
d=
"M410.079418 993.43007h193.378577a16.114881 16.114881 0 0 0 0-32.229763h-193.378577a16.114881 16.114881 0 0 0 0 32.229763z"
fill=
"#6E6E96"
opacity=
".29"
p-id=
"23461"
></path><path
d=
"M675.435114 993.43007h56.402085a16.114881 16.114881 0 0 0 0-32.229763h-56.402085a16.114881 16.114881 0 0 0 0 32.229763z"
fill=
"#6E6E96"
opacity=
".17"
p-id=
"23462"
></path><path
d=
"M332.993882 961.200307h-44.315924a16.114881 16.114881 0 0 0 0 32.229763h44.315924a16.114881 16.114881 0 0 0 0-32.229763z"
fill=
"#6E6E96"
opacity=
".17"
p-id=
"23463"
></path></svg>
\ No newline at end of file
src/assets/svg/icon_picture.svg
0 → 100644
View file @
bed64a7f
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg
t=
"1770017871834"
class=
"icon"
viewBox=
"0 0 1024 1024"
version=
"1.1"
xmlns=
"http://www.w3.org/2000/svg"
p-id=
"1986"
xmlns:xlink=
"http://www.w3.org/1999/xlink"
width=
"256"
height=
"256"
><path
d=
"M515.9424 512.768m-450.816 0a450.816 450.816 0 1 0 901.632 0 450.816 450.816 0 1 0-901.632 0Z"
fill=
"#FFF1DC"
p-id=
"1987"
></path><path
d=
"M516.4544 251.1872c69.4784 0 138.9568-0.1024 208.4352 0.0512 50.4832 0.1024 74.5472 23.9616 74.5472 73.8816 0.0512 124.9792 0.0512 250.0096 0 374.9888 0 49.8176-24.2176 74.0864-74.5984 74.1888-138.9568 0.1536-277.9136 0.1536-416.8192 0-52.0192-0.0512-75.4176-23.4496-75.4688-75.1616-0.1536-124.3648-0.1536-248.7296 0-373.0944 0.0512-51.3536 23.7568-74.7008 75.52-74.8032 69.4272-0.1536 138.9056-0.0512 208.384-0.0512z m-110.5408 281.5488c-36.864 48.896-72.2944 95.8976-109.6704 145.4592h439.6544c-48.0256-65.2288-94.0032-127.7952-141.2096-191.8976-37.632 48.1792-73.472 94.0544-110.0288 140.9024-26.9824-32.3584-51.968-62.4128-78.7456-94.464zM371.5072 439.296c32.768 0.1536 57.7024-24.4736 57.5488-56.9344-0.1536-30.976-25.4976-56.7808-56.1664-57.1904-31.3856-0.4608-58.4192 25.6512-58.7264 56.7296-0.3584 32.2048 24.6272 57.1904 57.344 57.3952z"
fill=
"#FC7032"
p-id=
"1988"
></path><path
d=
"M516.4544 251.1872c-69.4784 0-138.9568-0.1024-208.4352 0.0512-51.7632 0.1024-75.4688 23.4496-75.52 74.8032-0.1536 124.3648-0.1536 248.7296 0 373.0944 0.0512 51.712 23.4496 75.1104 75.4688 75.1616 85.0432 0.1024 170.1376 0 255.1808 0a452.77184 452.77184 0 0 0 98.7136-96.0512H296.192c37.376-49.5616 72.8064-96.5632 109.6704-145.4592 26.7264 32.0512 51.7632 62.1056 78.7456 94.464 36.608-46.848 72.448-92.7232 110.0288-140.9024 34.2016 46.4384 67.7376 92.1088 101.888 138.496 35.7888-64.6144 56.1664-138.9568 56.1664-218.0096 0-54.6816-9.728-107.0592-27.5456-155.5456h-0.3584c-69.376-0.2048-138.8544-0.1024-208.3328-0.1024zM371.5072 439.296c-32.7168-0.1536-57.7024-25.1904-57.3952-57.3952 0.3072-31.0784 27.3408-57.1904 58.7264-56.7296 30.6688 0.4608 56.0128 26.2144 56.1664 57.1904 0.2048 32.4096-24.7296 57.088-57.4976 56.9344z"
fill=
"#FF7E3E"
p-id=
"1989"
></path><path
d=
"M309.4528 678.1952h-13.1584c37.376-49.5616 72.8064-96.5632 109.6704-145.4592 18.6368 22.3744 36.5056 43.776 54.7328 65.6384 101.8368-81.7664 167.3728-206.7968 168.7552-347.2384-37.632 0-75.3152 0.0512-112.9472 0.0512-69.4784 0-138.9568-0.1024-208.4352 0.0512-51.7632 0.1024-75.4688 23.4496-75.52 74.8032-0.1536 122.7776-0.1024 245.504 0 368.2816a448.6656 448.6656 0 0 0 76.9024-16.128z m63.4368-353.024c30.6688 0.4608 56.0128 26.2144 56.1664 57.1904 0.1536 32.4096-24.7808 57.088-57.5488 56.9344-32.7168-0.1536-57.7024-25.1904-57.3952-57.3952 0.3072-31.0784 27.392-57.1904 58.7776-56.7296z"
fill=
"#FF9552"
p-id=
"1990"
></path><path
d=
"M351.232 435.6608c-22.1696-7.9872-37.376-28.672-37.12-53.8112 0.3072-31.0784 27.3408-57.1904 58.7264-56.7296 22.784 0.3072 42.5984 14.6432 51.3536 34.7136a451.1744 451.1744 0 0 0 61.0816-108.6976c-59.0848 0-118.1696-0.0512-177.2544 0.0512-51.7632 0.1024-75.4688 23.4496-75.52 74.8032-0.0512 59.9552 0 119.9104 0 179.8656A448.06656 448.06656 0 0 0 351.232 435.6608z"
fill=
"#FFA56A"
p-id=
"1991"
></path></svg>
\ No newline at end of file
src/components/common/Comment/components/ReplyBox.vue
0 → 100644
View file @
bed64a7f
<
template
>
<div
class=
"flex-1"
>
<div>
<!--
<el-input
v-model=
"commentStr"
type=
"textarea"
:placeholder=
"placeholder"
:rows=
"3"
></el-input>
-->
<div
style=
"border: 1px solid rgb(229, 231, 235)"
class=
"relative w-full rounded-lg border border-gray-200 px-3 py-2 transition focus-within:border-[var(--el-color-primary)] focus-within:ring-1 focus-within:ring-[var(--el-color-primary)] bg-white"
>
<!-- 文本输入区 -->
<textarea
ref=
"commentInputRef"
v-model=
"commentStr"
class=
"w-full resize-none border-none outline-none text-sm leading-5 text-gray-800 min-h-14"
:placeholder=
"placeholder"
/>
<!-- 定位到右边 -->
<!--
<span
class=
"flex justify-end text-xs text-gray-400"
>
{{
form
.
content
.
length
}}
/ 500
</span>
-->
<div
class=
"flex justify-between items-center mt-2"
>
<div
v-if=
"imgList.length"
class=
"flex flex-wrap gap-2"
v-loading=
"uploadPercent > 0"
:element-loading-text=
"uploadPercent + '%'"
>
<div
class=
"relative w-20 h-20 rounded-lg overflow-hidden group"
v-for=
"img in imgList"
:key=
"img"
>
<!-- 删除按钮 -->
<div
class=
"absolute top-1 right-1 z-10 w-5 h-5 flex items-center justify-center bg-black/60 rounded-full cursor-pointer opacity-0 group-hover:opacity-100 transition-all duration-200 hover:bg-black/80 hover:scale-110"
@
click=
"handleDeleteImg(img)"
>
<el-icon
class=
"text-white text-xs"
>
<IEpClose
/>
</el-icon>
</div>
<el-image
:src=
"img"
class=
"w-full h-full rounded-lg border border-gray-200"
fit=
"cover"
/>
</div>
</div>
</div>
</div>
</div>
<div
class=
"flex justify-between items-center mt-3"
>
<div>
<!-- 操作栏 添加图片 表情包等 -->
<div
class=
"flex items-center gap-2"
>
<div
class=
"w-8 h-8 text-gray-500 hover:bg-gray-100 hover:text-gray-700 rounded-lg flex items-center justify-center"
>
<input
accept=
"image/*"
@
change=
"handleFileChange"
type=
"file"
class=
"hidden"
ref=
"fileInputRef"
/>
<el-icon
size=
"30"
class=
"cursor-pointer"
@
click=
"fileInputRef?.click()"
>
<svg-icon
name=
"icon_picture"
/>
</el-icon>
</div>
<div
class=
"w-8 h-8 text-gray-500 hover:bg-gray-100 hover:text-gray-700 rounded-lg flex items-center justify-center"
>
<el-popover
placement=
"bottom"
trigger=
"click"
width=
"384"
>
<template
#
reference
>
<el-icon
size=
"20"
@
mousedown
.
prevent
>
<svg-icon
name=
"icon_face"
class=
"cursor-pointer"
/></el-icon>
</
template
>
<!-- 表情面板 -->
<el-scrollbar
class=
"h-50"
>
<div
class=
"flex flex-wrap"
>
<span
v-for=
"item in emojis"
:key=
"item.name"
class=
"cursor-pointer hover:bg-gray-100 rounded p-1 flex items-center justify-center"
@
click=
"selectEmoji(item)"
>
<img
:src=
"item.url"
alt=
""
class=
"w-6 h-6"
/>
</span>
</div>
</el-scrollbar>
</el-popover>
</div>
</div>
</div>
<div
class=
"flex items-center gap-2 text-sm text-gray-500"
>
<button
class=
"hover:text-blue-500 transition-colors"
>
<i
class=
"i-carbon-face-satisfied"
></i>
</button>
<button
class=
"hover:text-blue-500 transition-colors"
>
<i
class=
"i-carbon-image"
></i>
</button>
</div>
<button
class=
"cursor-pointer disabled:opacity-50 px-6 py-2 bg-gradient-to-r from-blue-500 to-purple-500 text-white rounded-full text-sm hover:shadow-lg transition-all"
:disabled=
"!displayComment || loading"
@
click=
"emit('submit')"
>
<div
v-show=
"loading"
class=
"flex items-center gap-2"
>
<el-icon><IEpLoading
/></el-icon>
<span>
发表中...
</span>
</div>
<div
v-show=
"!loading"
>
发表
</div>
</button>
</div>
</div>
</template>
<
script
setup
lang=
"ts"
>
import
emojis
from
'@/utils/emoji/face.json'
import
{
useUploadImg
}
from
'@/hooks'
import
type
{
IEmoji
}
from
'@/utils/emoji/type'
const
commentStr
=
defineModel
<
string
>
(
'modelValue'
,
{
required
:
true
,
})
const
commentImgStr
=
defineModel
<
string
>
(
'commentImgStr'
,
{
required
:
true
,
})
const
{
placeholder
=
'写下你的评论...'
,
loading
}
=
defineProps
<
{
placeholder
?:
string
loading
:
boolean
}
>
()
const
emit
=
defineEmits
<
{
(
e
:
'submit'
):
void
}
>
()
const
displayComment
=
computed
(()
=>
commentStr
.
value
?.
trim
())
const
fileInputRef
=
useTemplateRef
(
'fileInputRef'
)
const
commentInputRef
=
useTemplateRef
<
HTMLTextAreaElement
>
(
'commentInputRef'
)
const
{
handleFileChange
,
handleDeleteImg
,
uploadPercent
,
imgList
}
=
useUploadImg
(
commentImgStr
)
const
selectEmoji
=
async
(
item
:
IEmoji
)
=>
{
const
textarea
=
commentInputRef
.
value
if
(
!
textarea
)
return
// 当选中一段文本时 这俩值是不一样
const
start
=
textarea
.
selectionStart
const
end
=
textarea
.
selectionEnd
const
value
=
commentStr
.
value
// 插入内容(你可以是 [微笑],也可以是 😀)
commentStr
.
value
=
value
.
slice
(
0
,
start
)
+
item
.
name
+
value
.
slice
(
end
)
// 插入后把光标放到表情后面
await
nextTick
()
textarea
.
focus
()
textarea
.
selectionStart
=
textarea
.
selectionEnd
=
start
+
item
.
name
.
length
}
defineExpose
({
focus
:
async
()
=>
{
await
nextTick
()
commentInputRef
.
value
?.
focus
()
},
})
</
script
>
src/components/common/Comment/index.vue
View file @
bed64a7f
...
...
@@ -66,7 +66,7 @@
class=
"w-10 h-10 rounded-full object-cover cursor-pointer"
@
click=
"jumpToUserHomePage(
{ userId: userInfo.userId, isReal: 0 })"
/>
<div
class=
"flex-1"
>
<
!--
<
div
class=
"flex-1"
>
<div
ref=
"commentInputRef"
>
<el-input
v-model=
"myComment"
...
...
@@ -92,9 +92,16 @@
发表
</button>
</div>
</div>
-->
<ReplyBox
v-model=
"myComment"
v-model:commentImgStr=
"myCommentImgStr"
@
submit=
"handleMyComment"
:loading=
"myCommentLoading"
/>
</div>
</div>
<
/div
>
<
!-- 分割线 --
>
<!-- 评论列表 -->
<div
v-loading=
"loading"
class=
"divide-y divide-gray-100"
v-if=
"list.length"
>
...
...
@@ -122,9 +129,27 @@
<!--
<span
class=
"px-2 py-0.5 text-xs bg-red-100 text-red-600 rounded-full"
>
置顶
</span>
-->
</div>
<!-- 换行 -->
<p
class=
"text-gray-800 my-2 break-all whitespace-pre-wrap"
>
{{
item
.
content
}}
</p>
<p
class=
"text-gray-800 my-2 break-all whitespace-pre-wrap"
v-html=
"parseEmoji(item.content)"
></p>
<!-- 评论图片列表 -->
<div
class=
"flex flex-wrap gap-2"
>
<div
v-for=
"(img, imgIndex) in item.imgUrl.split(',').filter(Boolean)"
:key=
"imgIndex"
class=
"w-20 h-20 rounded-lg overflow-hidden mb-2 cursor-pointer"
>
<el-image
:src=
"img"
:preview-teleported=
"true"
class=
"w-full h-full object-cover"
:preview-src-list=
"item.imgUrl.split(',').filter(Boolean)"
:initial-index=
"imgIndex"
fit=
"cover"
/>
</div>
</div>
<div
class=
"flex items-center justify-between"
>
<div
class=
"flex items-center gap-8 text-sm text-gray-500"
>
<span>
{{
dayjs
(
item
.
createTime
*
1000
).
format
(
'YYYY-MM-DD HH:mm:ss'
)
}}
</span>
...
...
@@ -141,7 +166,7 @@
</div>
<button
class=
"cursor-pointer hover:text-blue-500 transition-colors"
@
click=
"handleReply(item)"
@
click=
"handleReply(item
, index
)"
>
回复
</button>
...
...
@@ -171,9 +196,27 @@
<span>
{{
child
.
replyName
}}
</span></span
>
</div>
<p
class=
"text-gray-800 my-2 break-all whitespace-pre-wrap"
>
{{
child
.
content
}}
</p>
<p
class=
"text-gray-800 my-2 break-all whitespace-pre-wrap"
v-html=
"parseEmoji(child.content)"
></p>
<!-- 评论图片列表 -->
<div
class=
"flex flex-wrap gap-2"
>
<div
v-for=
"(img, imgIndex) in child.imgUrl.split(',').filter(Boolean)"
:key=
"imgIndex"
class=
"w-20 h-20 rounded-lg overflow-hidden mb-2"
>
<el-image
:src=
"img"
:preview-teleported=
"true"
class=
"w-full h-full object-cover"
:preview-src-list=
"child.imgUrl.split(',').filter(Boolean)"
:initial-index=
"imgIndex"
fit=
"cover"
/>
</div>
</div>
<div
class=
"flex items-center justify-between"
>
<div
class=
"flex items-center gap-4 text-sm text-gray-500"
>
<span>
{{
...
...
@@ -193,7 +236,7 @@
</div>
</div>
<button
@
click=
"handleReply(child)"
@
click=
"handleReply(child
, index
)"
class=
"cursor-pointer hover:text-blue-500 transition-colors"
>
回复
...
...
@@ -250,7 +293,7 @@
</div>
</div>
<!-- 展示 回复评论的输入框 -->
<transition
name=
"fadeToComment"
mode=
"out-in"
>
<transition
name=
"fadeToComment"
>
<div
v-show=
"showCommentBox(item)"
class=
"flex gap-3 mt-4"
>
<img
:src=
"userAvatar"
...
...
@@ -258,7 +301,7 @@
class=
"w-10 h-10 rounded-full object-cover cursor-pointer"
@
click=
"jumpToUserHomePage({ userId: userInfo.userId, isReal: isReal })"
/>
<div
class=
"flex-1"
>
<
!-- <
div class="flex-1">
<el-input
v-model="comment"
type="textarea"
...
...
@@ -282,7 +325,15 @@
发表
</button>
</div>
</div>
</div> -->
<ReplyBox
v-model=
"commentToOther"
v-model:commentImgStr=
"commentToOtherImgStr"
:loading=
"commentToOtherLoading"
:placeholder=
"replyPlaceholder"
@
submit=
"handleComment(index)"
:ref=
"(el) => (replyToOtherBoxRefList[index] = el as HTMLElement)"
/>
</div>
</transition>
</div>
...
...
@@ -329,6 +380,8 @@ import { useUserStore } from '@/stores'
import
{
storeToRefs
}
from
'pinia'
import
CommentListDialog
from
'../CommentListDialog/index.vue'
import
{
jumpToUserHomePage
}
from
'@/utils'
import
ReplyBox
from
'./components/ReplyBox.vue'
import
{
parseEmoji
}
from
'@/utils/emoji'
const
{
id
,
defaultSize
=
10
,
...
...
@@ -356,7 +409,7 @@ const { userInfo } = storeToRefs(userStore)
const
userAvatar
=
computed
(()
=>
(
isReal
?
userInfo
.
value
.
avatar
:
userInfo
.
value
.
hiddenAvatar
))
const
commentRef
=
useTemplateRef
<
HTMLElement
|
null
>
(
'commentRef'
)
const
commentListDialogRef
=
useTemplateRef
<
typeof
CommentListDialog
>
(
'commentListDialogRef'
)
const
commentInputRef
=
useTemplateRef
<
HTMLElement
|
null
>
(
'commentInputRef'
)
const
replyToOtherBoxRefList
=
ref
<
HTMLElement
[]
>
([]
)
const
commentItemRefList
=
ref
<
HTMLElement
[]
>
([])
// 回滚到评论框
const
{
handleBackTop
}
=
useScrollTop
(
commentRef
)
...
...
@@ -413,12 +466,16 @@ const handleCurrentChange = async (e: number) => {
// 自己发出的评论
const
myComment
=
ref
(
''
)
const
myCommentLoading
=
ref
(
false
)
// 回复别人的
const
comment
=
ref
(
''
)
const
commentToOther
=
ref
(
''
)
const
commentToOtherLoading
=
ref
(
false
)
// 回复别人placeholder
const
replyPlaceholder
=
ref
(
'回复@'
)
// 自己发出的评论图片
const
myCommentImgStr
=
ref
(
''
)
// 回复别人的图片
const
commentToOtherImgStr
=
ref
(
''
)
const
currentCommentId
=
ref
(
-
1
)
const
handleLickComment
=
async
(
item
:
CommentItemDto
)
=>
{
...
...
@@ -434,12 +491,11 @@ const handleLickComment = async (item: CommentItemDto) => {
}
}
const
handleReply
=
(
item
:
CommentItemDto
)
=>
{
console
.
log
(
'回复'
,
item
)
const
handleReply
=
(
item
:
CommentItemDto
,
index
:
number
)
=>
{
replyPlaceholder
.
value
=
`回复@
${
item
.
replyUser
}
:`
comment
.
value
=
''
comment
ToOther
.
value
=
''
currentCommentId
.
value
=
item
.
id
console
.
log
(
currentCommentId
.
value
)
replyToOtherBoxRefList
.
value
[
index
]?.
focus
(
)
}
// 是否展示子评论回复框
...
...
@@ -451,31 +507,49 @@ const showCommentBox = (item: CommentItemDto) => {
}
const
handleMyComment
=
async
()
=>
{
try
{
myCommentLoading
.
value
=
true
await
addComment
({
articleId
:
id
,
content
:
myComment
.
value
,
...(
commentId
?
{
pid
:
commentId
}
:
{}),
imgUrl
:
myCommentImgStr
.
value
,
})
ElMessage
.
success
(
'发表评论成功'
)
refresh
()
myComment
.
value
=
''
myCommentImgStr
.
value
=
''
total
.
value
++
emit
(
'commentSuccess'
)
}
catch
(
error
)
{
console
.
error
(
error
)
}
finally
{
myCommentLoading
.
value
=
false
}
}
const
handleComment
=
async
(
index
:
number
)
=>
{
try
{
commentToOtherLoading
.
value
=
true
await
addComment
({
articleId
:
id
,
content
:
comment
.
value
,
content
:
commentToOther
.
value
,
...(
currentCommentId
.
value
?
{
pid
:
currentCommentId
.
value
}
:
{}),
imgUrl
:
commentToOtherImgStr
.
value
,
})
ElMessage
.
success
(
'发表评论成功'
)
comment
.
value
=
''
commentToOther
.
value
=
''
commentToOtherImgStr
.
value
=
''
total
.
value
++
handleBackTopChildren
(
index
)
// 只需要刷新当前的评论
search
()
emit
(
'commentSuccess'
)
}
catch
(
error
)
{
console
.
error
(
error
)
}
finally
{
commentToOtherLoading
.
value
=
false
}
}
// 展开回复 获取子评论列表
...
...
@@ -538,12 +612,26 @@ defineExpose({
})
</
script
>
<
style
scoped
lang=
"scss"
>
.fadeToComment-enter-from
{
/* 进入 & 离开公共属性 */
.fadeToComment-enter-active
,
.fadeToComment-leave-active
{
transition
:
opacity
0.25s
ease
,
transform
0.25s
cubic-bezier
(
0.22
,
1
,
0.36
,
1
);
will-change
:
opacity
,
transform
;
}
/* 进入前 & 离开后 */
.fadeToComment-enter-from
,
.fadeToComment-leave-to
{
opacity
:
0
;
transform
:
translateY
(
-10
px
);
transform
:
translateY
(
8
px
);
}
.fadeToComment-enter-active
{
transition
:
all
0.5s
ease-out
;
/* 进入后 & 离开前 */
.fadeToComment-enter-to
,
.fadeToComment-leave-from
{
opacity
:
1
;
transform
:
translateY
(
0
);
}
</
style
>
src/components/common/CommentDialog/index.vue
View file @
bed64a7f
...
...
@@ -11,21 +11,101 @@
<el-avatar
:size=
"40"
:src=
"userInfo.hiddenAvatar"
/>
<!-- 评论输入框 -->
<el-input
v-model=
"commentContent"
type=
"textarea"
:rows=
"4"
<div
style=
"border: 1px solid rgb(229, 231, 235)"
class=
"relative flex-1 rounded-lg border border-gray-200 px-3 py-2 transition focus-within:border-[var(--el-color-primary)] focus-within:ring-1 focus-within:ring-[var(--el-color-primary)] bg-white"
>
<!-- 文本输入区 -->
<textarea
ref=
"commentInputRef"
v-model=
"commentStr"
class=
"w-full resize-none border-none outline-none text-sm leading-5 text-gray-800 min-h-30"
placeholder=
"写下你的评论..."
maxlength=
"500"
show-word-limit
class=
"flex-1"
/>
<!-- 定位到右边 -->
<!--
<span
class=
"flex justify-end text-xs text-gray-400"
>
{{
form
.
content
.
length
}}
/ 500
</span>
-->
<!-- 底部工具栏 -->
<div
class=
"flex justify-between items-center mt-2"
>
<div
v-if=
"imgList.length"
class=
"flex flex-wrap gap-2"
v-loading=
"uploadPercent > 0"
:element-loading-text=
"uploadPercent + '%'"
>
<div
class=
"relative w-20 h-20 rounded-lg overflow-hidden group"
v-for=
"img in imgList"
:key=
"img"
>
<!-- 删除按钮 -->
<div
class=
"absolute top-1 right-1 z-10 w-5 h-5 flex items-center justify-center bg-black/60 rounded-full cursor-pointer opacity-0 group-hover:opacity-100 transition-all duration-200 hover:bg-black/80 hover:scale-110"
@
click=
"handleDeleteImg(img)"
>
<el-icon
class=
"text-white text-xs"
>
<IEpClose
/>
</el-icon>
</div>
<el-image
:src=
"img"
class=
"w-full h-full rounded-lg border border-gray-200"
fit=
"cover"
/>
</div>
</div>
</div>
</div>
</div>
<template
#
footer
>
<div
class=
"flex justify-end gap-2"
>
<el-button
@
click=
"handleClose"
class=
"rounded-lg"
>
取消
</el-button>
<div
class=
"flex justify-end gap-2 items-center"
>
<!-- 上传文件 和 表情 -->
<div
class=
"w-8 h-8 cursor-pointer text-gray-500 hover:bg-gray-100 hover:text-gray-700 rounded-lg flex items-center justify-center"
>
<input
accept=
"image/*"
@
change=
"handleFileChange"
type=
"file"
class=
"hidden"
ref=
"fileInputRef"
/>
<el-icon
size=
"30"
@
click=
"fileInputRef?.click()"
>
<svg-icon
name=
"icon_picture"
/>
</el-icon>
</div>
<div
class=
"w-8 h-8 text-gray-500 hover:bg-gray-100 hover:text-gray-700 rounded-lg flex items-center justify-center"
>
<el-popover
placement=
"bottom"
trigger=
"click"
width=
"384"
>
<template
#
reference
>
<el-icon
size=
"20"
@
mousedown
.
prevent
>
<svg-icon
name=
"icon_face"
class=
"cursor-pointer"
/></el-icon>
</
template
>
<!-- 表情面板 -->
<el-scrollbar
class=
"h-50"
>
<div
class=
"flex flex-wrap"
>
<span
v-for=
"item in emojis"
:key=
"item.name"
class=
"cursor-pointer hover:bg-gray-100 rounded p-1 flex items-center justify-center"
@
click=
"selectEmoji(item)"
>
<img
:src=
"item.url"
alt=
""
class=
"w-6 h-6"
/>
</span>
</div>
</el-scrollbar>
</el-popover>
</div>
<el-button
:disabled=
"isDisabled || loading"
:loading=
"loading"
type=
"primary"
@
click=
"handleSubmit"
class=
"px-6 py-2 bg-blue-500 hover:bg-blue-600 rounded-lg text-white text-sm font-medium shadow-sm hover:shadow-md transition-all duration-200"
...
...
@@ -40,48 +120,81 @@
import
{
useUserStore
}
from
'@/stores'
import
{
storeToRefs
}
from
'pinia'
import
{
addComment
}
from
'@/api'
import
{
useUploadImg
}
from
'@/hooks'
import
emojis
from
'@/utils/emoji/face.json'
import
type
{
IEmoji
}
from
'@/utils/emoji/type'
const
emit
=
defineEmits
<
{
(
e
:
'commentSuccess'
):
void
}
>
()
const
userStore
=
useUserStore
()
const
{
userInfo
}
=
storeToRefs
(
userStore
)
// 弹窗显示状态
const
visible
=
ref
(
false
)
// 评论内容
const
commentContent
=
ref
(
''
)
const
commentStr
=
ref
(
''
)
const
commentImgStr
=
ref
(
''
)
const
loading
=
ref
(
false
)
const
isDisabled
=
computed
(()
=>
!
commentStr
.
value
.
trim
())
const
userStore
=
useUserStore
()
const
{
userInfo
}
=
storeToRefs
(
userStore
)
const
{
handleFileChange
,
handleDeleteImg
,
uploadPercent
,
imgList
}
=
useUploadImg
(
commentImgStr
)
const
fileInputRef
=
useTemplateRef
<
HTMLInputElement
>
(
'fileInputRef'
)
const
commentInputRef
=
useTemplateRef
<
HTMLTextAreaElement
>
(
'commentInputRef'
)
let
articleId
=
0
// 暴露 open 方法
const
open
=
(
id
:
number
)
=>
{
articleId
=
id
visible
.
value
=
true
commentContent
.
value
=
''
commentStr
.
value
=
''
commentImgStr
.
value
=
''
}
// 关闭弹窗
const
handleClose
=
()
=>
{
visible
.
value
=
false
commentContent
.
value
=
''
commentStr
.
value
=
''
commentImgStr
.
value
=
''
}
const
selectEmoji
=
async
(
item
:
IEmoji
)
=>
{
const
textarea
=
commentInputRef
.
value
if
(
!
textarea
)
return
// 当选中一段文本时 这俩值是不一样
const
start
=
textarea
.
selectionStart
const
end
=
textarea
.
selectionEnd
const
value
=
commentStr
.
value
// 插入内容(你可以是 [微笑],也可以是 😀)
commentStr
.
value
=
value
.
slice
(
0
,
start
)
+
item
.
name
+
value
.
slice
(
end
)
// 插入后把光标放到表情后面
await
nextTick
()
textarea
.
focus
()
textarea
.
selectionStart
=
textarea
.
selectionEnd
=
start
+
item
.
name
.
length
}
// 提交评论
const
handleSubmit
=
async
()
=>
{
if
(
!
commentContent
.
value
.
trim
())
{
ElMessage
.
warning
(
'请输入评论内容'
)
return
}
loading
.
value
=
true
// TODO: 这里处理提交逻辑
try
{
await
addComment
({
articleId
:
articleId
,
content
:
commentContent
.
value
,
content
:
commentStr
.
value
,
imgUrl
:
commentImgStr
.
value
,
})
console
.
log
(
'评论内容:'
,
commentContent
.
value
)
console
.
log
(
'评论内容:'
,
commentStr
.
value
)
ElMessage
.
success
(
'评论发表成功'
)
handleClose
()
emit
(
'commentSuccess'
)
}
catch
(
error
)
{
console
.
log
(
error
)
}
finally
{
loading
.
value
=
false
}
}
// 暴露方法给父组件
...
...
src/components/common/CommentListDialog/index.vue
View file @
bed64a7f
...
...
@@ -49,7 +49,28 @@
</div>
</div>
<p
class=
"text-gray-800 text-base leading-relaxed mb-3"
>
{{ parentComment.content }}
</p>
<p
class=
"text-gray-800 text-base leading-relaxed mb-3"
v-html=
"parseEmoji(parentComment.content)"
></p>
<!-- 下方图片 -->
<div
class=
"flex flex-wrap gap-2"
v-if=
"parentComment.imgUrl"
>
<div
v-for=
"(img, imgIndex) in parentComment.imgUrl.split(',').filter(Boolean)"
:key=
"imgIndex"
class=
"w-20 h-20 rounded-lg overflow-hidden mb-2 cursor-pointer"
>
<el-image
:src=
"img"
:preview-teleported=
"true"
class=
"w-full h-full object-cover"
:preview-src-list=
"parentComment.imgUrl.split(',').filter(Boolean)"
:initial-index=
"imgIndex"
fit=
"cover"
/>
</div>
</div>
<div
class=
"text-sm text-gray-400 flex items-center gap-4"
>
<span>
{{ dayjs(parentComment.createTime * 1000).format('MM-DD HH:mm') }}
</span>
...
...
@@ -102,9 +123,27 @@
</div>
</div>
<p
class=
"text-gray-700 text-base mb-3 break-all leading-relaxed"
>
{{ item.content }}
</p>
<p
class=
"text-gray-700 text-base mb-3 break-all leading-relaxed"
v-html=
"parseEmoji(item.content)"
></p>
<!-- 下方图片 -->
<div
class=
"flex flex-wrap gap-2"
v-if=
"item.imgUrl"
>
<div
v-for=
"(img, imgIndex) in item.imgUrl.split(',').filter(Boolean)"
:key=
"imgIndex"
class=
"w-20 h-20 rounded-lg overflow-hidden mb-2 cursor-pointer"
>
<el-image
:src=
"img"
:preview-teleported=
"true"
class=
"w-full h-full object-cover"
:preview-src-list=
"item.imgUrl.split(',').filter(Boolean)"
:initial-index=
"imgIndex"
fit=
"cover"
/>
</div>
</div>
<div
class=
"text-sm text-gray-400"
>
<span>
{{ formatDate(item.createTime) }}
</span>
...
...
@@ -210,6 +249,7 @@ import {
import
type
{
CommentItemDto
}
from
'@/api'
import
{
BooleanFlag
}
from
'@/constants'
import
{
usePageSearch
}
from
'@/hooks'
// 假设你有这个hook
import
{
parseEmoji
}
from
'@/utils/emoji'
// Props
const
{
articleId
,
pid
}
=
defineProps
<
{
...
...
src/components/common/PublishBox/components/selectTagsDialog.vue
View file @
bed64a7f
<
template
>
<el-dialog
v-model=
"dialogVisible"
title=
"选择标签"
width=
"500px"
:close-on-click-modal=
"false"
>
<el-dialog
v-model=
"dialogVisible"
title=
"选择标签"
width=
"500px"
:close-on-click-modal=
"false"
top=
"30vh"
>
<div
class=
"space-y-6 px-2"
>
<div
class=
"flex items-start gap-4"
>
<div
class=
"text-sm text-gray-700 w-16 flex-shrink-0"
>
主标签
</div>
...
...
src/components/common/PublishBox/index.vue
View file @
bed64a7f
...
...
@@ -28,18 +28,64 @@
</div>
<!-- 主要内容输入 -->
<div
class=
"relative mb-3"
>
<el-input
<div
style=
"border: 1px solid rgb(229, 231, 235)"
class=
"relative w-full rounded-lg border border-gray-200 px-3 py-2 transition focus-within:border-[var(--el-color-primary)] focus-within:ring-1 focus-within:ring-[var(--el-color-primary)] bg-white"
>
<!-- 文本输入区 -->
<textarea
v-model=
"form.content"
:placeholder=
"textMap[type].content"
class=
"w-full resize-none border-none outline-none text-sm leading-5 text-gray-800 min-h-30"
:maxlength=
"maxLength"
/>
<!-- 定位到右边 -->
<span
class=
"flex justify-end text-xs text-gray-400"
>
{{
form
.
content
.
length
}}
/ 500
</span>
<!-- 底部工具栏 -->
<div
class=
"flex justify-between items-center mt-2"
>
<!-- 图片列表 -->
<div
v-if=
"imgList.length"
class=
"flex flex-wrap gap-2"
v-loading=
"uploadPercent > 0"
:element-loading-text=
"uploadPercent + '%'"
>
<div
class=
"relative w-20 h-20 rounded-lg overflow-hidden group"
v-for=
"img in imgList"
:key=
"img"
>
<!-- 删除按钮 -->
<div
class=
"absolute top-1 right-1 z-10 w-5 h-5 flex items-center justify-center bg-black/60 rounded-full cursor-pointer opacity-0 group-hover:opacity-100 transition-all duration-200 hover:bg-black/80 hover:scale-110"
@
click=
"handleDeleteImg(img)"
>
<el-icon
class=
"text-white text-xs"
>
<IEpClose
/>
</el-icon>
</div>
<el-image
:src=
"img"
class=
"w-full h-full rounded-lg border border-gray-200"
fit=
"cover"
/>
</div>
</div>
</div>
</div>
<!--
<el-input
type=
"textarea"
:placeholder=
"textMap[type].content"
:rows=
"6"
:maxlength=
"maxLength"
show-word-limit
resize=
"none"
class=
"main-textarea"
v-model=
"form.content"
/>
<!-- 字符计数 -->
<!--
<div
class=
"absolute bottom-3 right-3 text-xs text-gray-400"
>
1/30
</div>
-->
/>
-->
</div>
<!-- 标签内容 -->
<div
class=
"mb-2"
>
...
...
@@ -56,13 +102,12 @@
</div>
</div>
<!-- 图片相关 -->
<div
<
!--
<
div
v-if=
"form.imgUrl.length"
class=
"flex flex-wrap gap-2 w-fit"
v-loading=
"uploadPercent > 0"
:element-loading-text=
"uploadPercent + '%'"
>
<!-- 删除图片 -->
<div
class=
"relative w-20 h-20 rounded-lg overflow-hidden group"
v-for=
"img in form.imgUrl"
...
...
@@ -82,7 +127,7 @@
fit=
"cover"
/>
</div>
</div>
</div>
-->
</div>
</div>
...
...
@@ -122,7 +167,7 @@
:disabled=
"uploadPercent > 0"
>
<el-icon
size=
"18"
>
<span
v-if=
"!
form.imgUrl
.length && uploadPercent > 0"
>
<IEpLoading
/></span>
<span
v-if=
"!
imgList
.length && uploadPercent > 0"
>
<IEpLoading
/></span>
<span
v-else
>
<IEpPicture
/></span>
</el-icon>
</el-button>
...
...
@@ -159,6 +204,7 @@
<el-button
type=
"primary"
:disabled=
"disabledSubmit"
:loading=
"loading"
class=
"px-6 py-2 bg-blue-500 hover:bg-blue-600 disabled:opacity-50 disabled:cursor-not-allowed rounded-lg text-white text-sm font-medium shadow-sm hover:shadow-md transition-all duration-200"
@
click=
"handlePublish(ReleaseStatusTypeEnum.PUBLISH)"
>
...
...
@@ -204,6 +250,8 @@ const {
maxLength
?:
number
}
>
()
const
loading
=
ref
(
false
)
const
textMap
:
Record
<
ArticleType
,
{
title
:
string
;
content
:
string
;
api
:
(
data
:
any
)
=>
Promise
<
any
>
}
...
...
@@ -260,7 +308,7 @@ const { play } = useAnimate(
const
[
form
,
resetForm
]
=
useResetData
({
title
:
''
,
content
:
''
,
imgUrl
:
[]
as
string
[]
,
imgUrl
:
''
,
releaseStatus
:
ReleaseStatusTypeEnum
.
PUBLISH
,
mainTagId
:
''
,
tagList
:
[],
...
...
@@ -268,7 +316,7 @@ const [form, resetForm] = useResetData({
sendTime
:
''
,
})
const
{
img
sStr
,
handleFileChange
,
handleDeleteImg
,
uploadPercent
}
=
useUploadImg
(
const
{
img
List
,
handleFileChange
,
handleDeleteImg
,
uploadPercent
}
=
useUploadImg
(
toRef
(
form
.
value
,
'imgUrl'
),
)
...
...
@@ -302,8 +350,8 @@ const transformForm = (releaseStatus: ReleaseStatusTypeEnum): AddOrUpdatePractic
return
{
...
form
.
value
,
releaseStatus
,
faceUrl
:
form
.
value
.
imgUrl
[
0
]
||
''
,
imgUrl
:
imgsStr
.
value
,
faceUrl
:
imgList
.
value
[
0
]
||
''
,
imgUrl
:
form
.
value
.
imgUrl
,
tagList
:
[
form
.
value
.
mainTagId
,
...
form
.
value
.
tagList
].
map
((
item
,
index
)
=>
({
sort
:
index
,
tagId
:
Number
(
item
),
...
...
@@ -314,9 +362,17 @@ const transformForm = (releaseStatus: ReleaseStatusTypeEnum): AddOrUpdatePractic
const
handlePublish
=
async
(
releaseStatus
:
ReleaseStatusTypeEnum
)
=>
{
if
(
!
validateForm
())
return
loading
.
value
=
true
try
{
await
textMap
[
type
].
api
(
transformForm
(
releaseStatus
))
loading
.
value
=
false
ElMessage
.
success
(
releaseStatus
===
ReleaseStatusTypeEnum
.
PUBLISH
?
'发布成功'
:
'存草稿成功'
)
resetForm
()
}
catch
(
error
)
{
console
.
error
(
error
)
}
finally
{
loading
.
value
=
false
}
}
</
script
>
...
...
src/hooks/useUploadImg.ts
View file @
bed64a7f
import
{
uploadFile
}
from
'@/api'
// 默认参数
export
const
useUploadImg
=
(
imgList
:
Ref
<
string
[]
>
=
ref
([]))
=>
{
const
uploadPercent
=
ref
(
0
)
// 字符串拼
const
imgsStr
=
computed
(()
=>
imgList
.
value
.
join
(
','
))
// 类型定义
type
BaseReturn
=
{
handleFileChange
:
(
e
:
Event
)
=>
Promise
<
void
>
uploadPercent
:
Ref
<
number
>
handleDeleteImg
:
(
urlStr
:
string
)
=>
void
}
// 传单字符串时多返回 imgList
type
UseUploadImgReturnString
=
BaseReturn
&
{
imgList
:
ComputedRef
<
string
[]
>
}
// 传字符串数组时只返回基础
type
UseUploadImgReturnArray
=
BaseReturn
// 直接传ref('imgs1,imgs2') 或者 ref(['img1','img2]) 传字符串的时候 会多返回一个imgList数组 便于模板使用遍历等
export
function
useUploadImg
(
imgs
:
Ref
<
string
>
):
UseUploadImgReturnString
export
function
useUploadImg
(
imgs
:
Ref
<
string
[]
>
):
UseUploadImgReturnArray
export
function
useUploadImg
(
imgs
:
Ref
<
string
>
|
Ref
<
string
[]
>
)
{
const
uploadPercent
=
ref
(
0
)
// 上传图片的change事件
const
handleFileChange
=
async
(
e
:
Event
)
=>
{
try
{
...
...
@@ -18,7 +31,11 @@ export const useUploadImg = (imgList: Ref<string[]> = ref([])) => {
},
})
const
data
=
await
promise
imgList
.
value
=
[...
imgList
.
value
,
data
.
filePath
]
if
(
Array
.
isArray
(
imgs
.
value
))
{
imgs
.
value
=
[...
imgs
.
value
,
data
.
filePath
]
}
else
{
imgs
.
value
=
[...
imgs
.
value
.
split
(
','
).
filter
(
Boolean
),
data
.
filePath
].
join
(
','
)
}
}
catch
(
error
)
{
console
.
error
(
'上传失败:'
,
error
)
}
finally
{
...
...
@@ -30,14 +47,37 @@ export const useUploadImg = (imgList: Ref<string[]> = ref([])) => {
// 删除图片
const
handleDeleteImg
=
(
urlStr
:
string
)
=>
{
imgList
.
value
=
imgList
.
value
.
filter
((
item
)
=>
item
!==
urlStr
)
if
(
Array
.
isArray
(
imgs
.
value
))
{
imgs
.
value
=
imgs
.
value
.
filter
((
item
)
=>
item
!==
urlStr
)
}
else
{
imgs
.
value
=
imgs
.
value
.
split
(
','
)
.
filter
((
item
)
=>
item
!==
urlStr
)
.
join
(
','
)
||
''
}
}
const
imgList
=
computed
(()
=>
{
if
(
Array
.
isArray
(
imgs
.
value
))
{
return
imgs
.
value
}
else
{
return
imgs
.
value
.
split
(
','
).
filter
(
Boolean
)
}
})
if
(
Array
.
isArray
(
imgs
.
value
))
{
return
{
imgsStr
,
imgList
,
handleFileChange
,
uploadPercent
,
handleDeleteImg
,
}
}
else
{
return
{
handleFileChange
,
uploadPercent
,
handleDeleteImg
,
imgList
,
}
}
}
src/utils/emoji/face.json
View file @
bed64a7f
[
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f600.png"
,
"name"
:
"
face_嘿嘿
"
,
"name"
:
"
[face_嘿嘿]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f603.png"
,
"name"
:
"
face_哈哈
"
,
"name"
:
"
[face_哈哈]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f604.png"
,
"name"
:
"
face_大笑
"
,
"name"
:
"
[face_大笑]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f601.png"
,
"name"
:
"
face_嘻嘻
"
,
"name"
:
"
[face_嘻嘻]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f606.png"
,
"name"
:
"
face_斜眼笑
"
,
"name"
:
"
[face_斜眼笑]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f605.png"
,
"name"
:
"
face_苦笑
"
,
"name"
:
"
[face_苦笑]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f923.png"
,
"name"
:
"
face_笑得满地打滚
"
,
"name"
:
"
[face_笑得满地打滚]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f602.png"
,
"name"
:
"
face_笑哭了
"
,
"name"
:
"
[face_笑哭了]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f642.png"
,
"name"
:
"
face_呵呵
"
,
"name"
:
"
[face_呵呵]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f643.png"
,
"name"
:
"
face_倒脸
"
,
"name"
:
"
[face_倒脸]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f609.png"
,
"name"
:
"
face_眨眼
"
,
"name"
:
"
[face_眨眼]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f60a.png"
,
"name"
:
"
face_羞涩微笑
"
,
"name"
:
"
[face_羞涩微笑]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f607.png"
,
"name"
:
"
face_微笑天使
"
,
"name"
:
"
[face_微笑天使]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f970.png"
,
"name"
:
"
face_喜笑颜开
"
,
"name"
:
"
[face_喜笑颜开]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f60d.png"
,
"name"
:
"
face_花痴
"
,
"name"
:
"
[face_花痴]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f929.png"
,
"name"
:
"
face_好崇拜哦
"
,
"name"
:
"
[face_好崇拜哦]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f617.png"
,
"name"
:
"
face_亲亲
"
,
"name"
:
"
[face_亲亲]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f61a.png"
,
"name"
:
"
face_羞涩亲亲
"
,
"name"
:
"
[face_羞涩亲亲]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f619.png"
,
"name"
:
"
face_微笑亲亲
"
,
"name"
:
"
[face_微笑亲亲]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f60b.png"
,
"name"
:
"
face_好吃
"
,
"name"
:
"
[face_好吃]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f61b.png"
,
"name"
:
"
face_吐舌
"
,
"name"
:
"
[face_吐舌]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f61c.png"
,
"name"
:
"
face_单眼吐舌
"
,
"name"
:
"
[face_单眼吐舌]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f92a.png"
,
"name"
:
"
face_滑稽
"
,
"name"
:
"
[face_滑稽]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f911.png"
,
"name"
:
"
face_发财
"
,
"name"
:
"
[face_发财]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f917.png"
,
"name"
:
"
face_抱抱
"
,
"name"
:
"
[face_抱抱]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f92d.png"
,
"name"
:
"
face_不说
"
,
"name"
:
"
[face_不说]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f92b.png"
,
"name"
:
"
face_安静的脸
"
,
"name"
:
"
[face_安静的脸]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f914.png"
,
"name"
:
"
face_想一想
"
,
"name"
:
"
[face_想一想]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f910.png"
,
"name"
:
"
face_闭嘴
"
,
"name"
:
"
[face_闭嘴]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f928.png"
,
"name"
:
"
face_挑眉
"
,
"name"
:
"
[face_挑眉]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f610.png"
,
"name"
:
"
face_冷漠
"
,
"name"
:
"
[face_冷漠]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f611.png"
,
"name"
:
"
face_无语
"
,
"name"
:
"
[face_无语]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f636.png"
,
"name"
:
"
face_沉默
"
,
"name"
:
"
[face_沉默]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f60f.png"
,
"name"
:
"
face_得意
"
,
"name"
:
"
[face_得意]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f612.png"
,
"name"
:
"
face_不高兴
"
,
"name"
:
"
[face_不高兴]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f644.png"
,
"name"
:
"
face_翻白眼
"
,
"name"
:
"
[face_翻白眼]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f62c.png"
,
"name"
:
"
face_龇牙咧嘴
"
,
"name"
:
"
[face_龇牙咧嘴]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f925.png"
,
"name"
:
"
face_说谎
"
,
"name"
:
"
[face_说谎]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f60c.png"
,
"name"
:
"
face_松了口气
"
,
"name"
:
"
[face_松了口气]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f614.png"
,
"name"
:
"
face_沉思
"
,
"name"
:
"
[face_沉思]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f62a.png"
,
"name"
:
"
face_困
"
,
"name"
:
"
[face_困]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f634.png"
,
"name"
:
"
face_睡着了
"
,
"name"
:
"
[face_睡着了]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f637.png"
,
"name"
:
"
face_感冒
"
,
"name"
:
"
[face_感冒]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f912.png"
,
"name"
:
"
face_发烧
"
,
"name"
:
"
[face_发烧]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f915.png"
,
"name"
:
"
face_受伤
"
,
"name"
:
"
[face_受伤]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f922.png"
,
"name"
:
"
face_恶心
"
,
"name"
:
"
[face_恶心]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f92e.png"
,
"name"
:
"
face_呕吐
"
,
"name"
:
"
[face_呕吐]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f975.png"
,
"name"
:
"
face_脸发烧
"
,
"name"
:
"
[face_脸发烧]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f976.png"
,
"name"
:
"
face_冷脸
"
,
"name"
:
"
[face_冷脸]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f974.png"
,
"name"
:
"
face_头昏眼花
"
,
"name"
:
"
[face_头昏眼花]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f92f.png"
,
"name"
:
"
face_爆炸头
"
,
"name"
:
"
[face_爆炸头]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f920.png"
,
"name"
:
"
face_牛仔帽脸
"
,
"name"
:
"
[face_牛仔帽脸]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f973.png"
,
"name"
:
"
face_聚会笑脸
"
,
"name"
:
"
[face_聚会笑脸]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f60e.png"
,
"name"
:
"
face_墨镜笑脸
"
,
"name"
:
"
[face_墨镜笑脸]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f913.png"
,
"name"
:
"
face_书呆子脸
"
,
"name"
:
"
[face_书呆子脸]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f9d0.png"
,
"name"
:
"
face_带单片眼镜的脸
"
,
"name"
:
"
[face_带单片眼镜的脸]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f615.png"
,
"name"
:
"
face_困扰
"
,
"name"
:
"
[face_困扰]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f641.png"
,
"name"
:
"
face_微微不满
"
,
"name"
:
"
[face_微微不满]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/2639-fe0f.png"
,
"name"
:
"
face_不满
"
,
"name"
:
"
[face_不满]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f62f.png"
,
"name"
:
"
face_缄默
"
,
"name"
:
"
[face_缄默]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f632.png"
,
"name"
:
"
face_震惊
"
,
"name"
:
"
[face_震惊]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f633.png"
,
"name"
:
"
face_脸红
"
,
"name"
:
"
[face_脸红]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f97a.png"
,
"name"
:
"
face_恳求的脸
"
,
"name"
:
"
[face_恳求的脸]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f626.png"
,
"name"
:
"
face_啊
"
,
"name"
:
"
[face_啊]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f627.png"
,
"name"
:
"
face_极度痛苦
"
,
"name"
:
"
[face_极度痛苦]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f628.png"
,
"name"
:
"
face_害怕
"
,
"name"
:
"
[face_害怕]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f630.png"
,
"name"
:
"
face_冷汗
"
,
"name"
:
"
[face_冷汗]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f625.png"
,
"name"
:
"
face_失望但如释重负
"
,
"name"
:
"
[face_失望但如释重负]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f622.png"
,
"name"
:
"
face_哭
"
,
"name"
:
"
[face_哭]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f62d.png"
,
"name"
:
"
face_放声大哭
"
,
"name"
:
"
[face_放声大哭]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f631.png"
,
"name"
:
"
face_吓死了
"
,
"name"
:
"
[face_吓死了]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f616.png"
,
"name"
:
"
face_困惑
"
,
"name"
:
"
[face_困惑]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f61e.png"
,
"name"
:
"
face_失望
"
,
"name"
:
"
[face_失望]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f613.png"
,
"name"
:
"
face_汗
"
,
"name"
:
"
[face_汗]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f629.png"
,
"name"
:
"
face_累死了
"
,
"name"
:
"
[face_累死了]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f62b.png"
,
"name"
:
"
face_累
"
,
"name"
:
"
[face_累]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f971.png"
,
"name"
:
"
face_打呵欠
"
,
"name"
:
"
[face_打呵欠]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f624.png"
,
"name"
:
"
face_傲慢
"
,
"name"
:
"
[face_傲慢]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f621.png"
,
"name"
:
"
face_怒火中烧
"
,
"name"
:
"
[face_怒火中烧]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f620.png"
,
"name"
:
"
face_生气
"
,
"name"
:
"
[face_生气]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f92c.png"
,
"name"
:
"
face_嘴上有符号的脸
"
,
"name"
:
"
[face_嘴上有符号的脸]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f608.png"
,
"name"
:
"
face_恶魔微笑
"
,
"name"
:
"
[face_恶魔微笑]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f47f.png"
,
"name"
:
"
face_生气的恶魔
"
,
"name"
:
"
[face_生气的恶魔]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f480.png"
,
"name"
:
"
face_头骨
"
,
"name"
:
"
[face_头骨]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/2620-fe0f.png"
,
"name"
:
"
face_骷髅
"
,
"name"
:
"
[face_骷髅]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f4a9.png"
,
"name"
:
"
face_大便
"
,
"name"
:
"
[face_大便]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f921.png"
,
"name"
:
"
face_小丑脸
"
,
"name"
:
"
[face_小丑脸]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f479.png"
,
"name"
:
"
face_食人魔
"
,
"name"
:
"
[face_食人魔]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f47a.png"
,
"name"
:
"
face_小妖精
"
,
"name"
:
"
[face_小妖精]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f47b.png"
,
"name"
:
"
face_鬼
"
,
"name"
:
"
[face_鬼]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f47d.png"
,
"name"
:
"
face_外星人
"
,
"name"
:
"
[face_外星人]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f47e.png"
,
"name"
:
"
face_外星怪物
"
,
"name"
:
"
[face_外星怪物]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f916.png"
,
"name"
:
"
face_机器人
"
,
"name"
:
"
[face_机器人]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f63a.png"
,
"name"
:
"
face_大笑的猫
"
,
"name"
:
"
[face_大笑的猫]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f639.png"
,
"name"
:
"
face_笑出眼泪的猫
"
,
"name"
:
"
[face_笑出眼泪的猫]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f63b.png"
,
"name"
:
"
face_花痴的猫
"
,
"name"
:
"
[face_花痴的猫]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f63c.png"
,
"name"
:
"
face_奸笑的猫
"
,
"name"
:
"
[face_奸笑的猫]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f63d.png"
,
"name"
:
"
face_亲亲猫
"
,
"name"
:
"
[face_亲亲猫]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f640.png"
,
"name"
:
"
face_疲倦的猫
"
,
"name"
:
"
[face_疲倦的猫]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
},
{
"url"
:
"https://www.emojiall.com/images/60/skype/1f63f.png"
,
"name"
:
"
face_哭泣的猫
"
,
"name"
:
"
[face_哭泣的猫]
"
,
"group"
:
"face"
,
"className"
:
"emoji_small"
}
...
...
src/utils/emoji/index.ts
0 → 100644
View file @
bed64a7f
import
emojis
from
'./face.json'
function
escapeHTML
(
str
:
string
)
{
return
str
.
replace
(
/&/g
,
'&'
).
replace
(
/</g
,
'<'
).
replace
(
/>/g
,
'>'
)
}
export
const
parseEmoji
=
(
content
:
string
)
=>
{
if
(
!
content
)
return
''
let
html
=
escapeHTML
(
content
)
emojis
.
forEach
((
item
)
=>
{
const
escapedName
=
item
.
name
.
replace
(
/
[
.*+?^${}()|[
\]\\]
/g
,
'
\\
$&'
)
const
reg
=
new
RegExp
(
escapedName
,
'g'
)
html
=
html
.
replace
(
reg
,
`<img
src="
${
item
.
url
}
"
alt="
${
item
.
name
}
"
class="
${
item
.
className
||
'emoji_small'
}
inline-block align-text-bottom w-6 h-6"
/>`
,
)
})
return
html
}
src/views/questionDetail/index.vue
View file @
bed64a7f
...
...
@@ -11,7 +11,7 @@
<span
v-for=
"tag in questionDetail.tagNameList"
:key=
"tag"
class=
"px-2.5 py-0.5 rounded-full bg-blue-50 text-blue-500 text-xs font-semibold hover:bg-blue-100 transition-colors cursor-pointer"
class=
"px-2.5 py-0.5 rounded-full bg-blue-50 text-blue-500 text-xs font-semibold hover:bg-blue-100 transition-colors cursor-pointer
mr-2
"
>
#
{{
tag
}}
</span>
...
...
@@ -70,7 +70,21 @@
/></el-icon>
</button>
</div>
<div
v-if=
"questionDetail.imgUrl"
class=
"mt-3 flex gap-2 flex-wrap items-center justify-start"
>
<el-image
v-for=
"(item, i) in questionDetail.imgUrl.split(',')"
:key=
"item"
:src=
"item"
fit=
"cover"
class=
"rounded-lg w-24 h-24 hover:scale-105 transition-transform cursor-pointer"
:preview-src-list=
"questionDetail.imgUrl.split(',')"
:initial-index=
"i"
:preview-teleported=
"true"
/>
</div>
<!-- 底部操作栏 -->
<div
class=
"flex items-center justify-between mt-4"
>
<div
class=
"flex gap-3"
>
...
...
@@ -84,7 +98,7 @@
</div>
<!-- 右侧数据 -->
<div
class=
"flex items-center gap-6 text-slate-
4
00 text-sm select-none"
>
<div
class=
"flex items-center gap-6 text-slate-
5
00 text-sm select-none"
>
<span
@
click=
"handleLikeArticle"
class=
"hover:text-slate-600 cursor-pointer transition-colors flex items-center gap-1"
...
...
@@ -112,17 +126,6 @@
</div>
</div>
<!-- 展示图片相关 -->
<div
v-if=
"questionDetail.imgUrl"
class=
"grid grid-cols-1 md:grid-cols-2 gap-4 mt-6"
>
<el-image
v-for=
"item in questionDetail.imgUrl.split(',')"
:key=
"item"
:src=
"item"
fit=
"cover"
class=
"rounded-lg w-full h-64 hover:scale-105 transition-transform cursor-pointer"
:preview-src-list=
"questionDetail.imgUrl.split(',')"
:preview-teleported=
"true"
/>
</div>
</div>
<!-- 2. 列表控制栏 -->
...
...
@@ -187,24 +190,41 @@
>
优秀回答
</span
>
-->
</div>
<!--
<div
class=
"text-xs text-slate-
4
00 mt-0.5 max-w-md truncate"
>
<!--
<div
class=
"text-xs text-slate-
5
00 mt-0.5 max-w-md truncate"
>
{{
answer
.
description
||
'暂无简介'
}}
</div>
-->
</div>
</div>
<!-- 赞同票数 (微小的灰色文字,增加信息密度) -->
<div
v-if=
"answer.postPriseCount"
class=
"text-xs text-slate-
4
00 mb-2"
>
<div
v-if=
"answer.postPriseCount"
class=
"text-xs text-slate-
5
00 mb-2"
>
{{
answer
.
postPriseCount
||
0
}}
人赞同了该回答
</div>
<!-- 正文 换行 -->
<div
class=
"text-slate-800 leading-7 text-[15px] mb-4 rich-text-content break-all"
v-html=
"
answer.content
"
v-html=
"
parseEmoji(answer.content)
"
></div>
<!-- 评论图片列表 -->
<div
class=
"flex flex-wrap gap-3 mb-3"
>
<div
v-for=
"(img, imgIndex) in answer.imgUrl.split(',').filter(Boolean)"
:key=
"imgIndex"
class=
"w-24 h-24 rounded-lg overflow-hidden mb-2"
>
<el-image
:src=
"img"
:preview-teleported=
"true"
class=
"w-full h-full object-cover"
:preview-src-list=
"answer.imgUrl.split(',').filter(Boolean)"
:initial-index=
"imgIndex"
fit=
"cover"
/>
</div>
</div>
<div
class=
"text-xs text-slate-
400 mb-4
"
>
<div
class=
"text-xs text-slate-
500 mb-3
"
>
发布于
{{
dayjs
(
answer
.
createTime
*
1000
).
format
(
'YYYY-MM-DD HH:mm'
)
}}
</div>
...
...
@@ -294,6 +314,7 @@ import dayjs from 'dayjs'
import
{
useUserStore
}
from
'@/stores/user'
import
{
storeToRefs
}
from
'pinia'
import
{
jumpToUserHomePage
}
from
'@/utils'
import
{
parseEmoji
}
from
'@/utils/emoji'
const
userStore
=
useUserStore
()
const
{
userInfo
}
=
storeToRefs
(
userStore
)
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment