Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
C
corporate-culture-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
王立鹏
corporate-culture-qd
Commits
2816ddfe
Commit
2816ddfe
authored
Sep 14, 2026
by
lijiabin
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
【需求 25171】refactor: 抽取富文本样式与图片预览能力
parent
8557ea8c
Show whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
146 additions
and
114 deletions
+146
-114
index.vue
src/components/common/ArticleContent/index.vue
+2
-114
index.ts
src/hooks/index.ts
+1
-0
useImagePreview.ts
src/hooks/useImagePreview.ts
+48
-0
index.css
src/style/index.css
+1
-0
rich-content.css
src/style/rich-content.css
+94
-0
No files found.
src/components/common/ArticleContent/index.vue
View file @
2816ddfe
...
@@ -299,8 +299,7 @@ import ActionMore from '@/components/common/ActionMore/index.vue'
...
@@ -299,8 +299,7 @@ import ActionMore from '@/components/common/ActionMore/index.vue'
import
SendMessageDialog
from
'@/components/common/SendMessageDialog/index.vue'
import
SendMessageDialog
from
'@/components/common/SendMessageDialog/index.vue'
import
{
useUserStore
}
from
'@/stores/user'
import
{
useUserStore
}
from
'@/stores/user'
import
{
storeToRefs
}
from
'pinia'
import
{
storeToRefs
}
from
'pinia'
import
type
{
Directive
}
from
'vue'
import
{
useImagePreview
,
useNavigation
}
from
'@/hooks'
import
{
useNavigation
}
from
'@/hooks'
const
userStore
=
useUserStore
()
const
userStore
=
useUserStore
()
const
{
userInfo
}
=
storeToRefs
(
userStore
)
const
{
userInfo
}
=
storeToRefs
(
userStore
)
...
@@ -386,116 +385,5 @@ const showBottomVideo = computed(
...
@@ -386,116 +385,5 @@ const showBottomVideo = computed(
articleDetail
.
articleVideoUrl
,
articleDetail
.
articleVideoUrl
,
)
)
const
showPreview
=
ref
(
false
)
const
{
showPreview
,
currentPreviewIndex
,
srcList
,
vImagePreview
}
=
useImagePreview
()
const
currentPreviewIndex
=
ref
(
0
)
const
srcList
=
ref
<
string
[]
>
([])
// 图片预览的指令
const
vImagePreview
:
Directive
=
{
mounted
(
el
:
HTMLElement
)
{
const
imgs
=
el
.
querySelectorAll
(
'img'
)
imgs
.
forEach
((
item
,
index
)
=>
{
item
.
style
.
cursor
=
'pointer'
if
(
item
.
src
)
{
srcList
.
value
.
push
(
item
.
src
)
item
.
addEventListener
(
'click'
,
()
=>
{
currentPreviewIndex
.
value
=
index
showPreview
.
value
=
true
})
}
})
},
}
</
script
>
</
script
>
<
style
scoped
>
.html-content
{
font-size
:
18px
;
line-height
:
1.8
;
color
:
#374151
;
word-break
:
break-word
;
}
.html-content
:deep
(
p
)
{
margin
:
1em
0
;
}
.html-content
:deep
(
h1
),
.html-content
:deep
(
h2
),
.html-content
:deep
(
h3
),
.html-content
:deep
(
h4
),
.html-content
:deep
(
h5
),
.html-content
:deep
(
h6
)
{
margin
:
1.2em
0
0.6em
;
line-height
:
1.4
;
font-weight
:
600
;
color
:
#111827
;
}
.html-content
:deep
(
h1
)
{
font-size
:
1.7em
;
}
.html-content
:deep
(
h2
)
{
font-size
:
1.5em
;
}
.html-content
:deep
(
h3
)
{
font-size
:
1.3em
;
}
.html-content
:deep
(
ul
),
.html-content
:deep
(
ol
)
{
margin
:
1em
0
;
padding-left
:
1.5em
;
}
.html-content
:deep
(
ul
)
{
list-style
:
disc
;
}
.html-content
:deep
(
ol
)
{
list-style
:
decimal
;
}
.html-content
:deep
(
li
)
{
margin
:
0.35em
0
;
}
.html-content
:deep
(
blockquote
)
{
margin
:
1em
0
;
padding
:
0.6em
1em
;
border-left
:
4px
solid
#d1d5db
;
color
:
#6b7280
;
background
:
#f9fafb
;
}
.html-content
:deep
(
pre
),
.html-content
:deep
(
code
)
{
font-family
:
SFMono-Regular
,
Consolas
,
'Liberation Mono'
,
Menlo
,
monospace
;
}
.html-content
:deep
(
pre
)
{
margin
:
1em
0
;
padding
:
0.8em
1em
;
border-radius
:
6px
;
overflow-x
:
auto
;
background
:
#f3f4f6
;
}
.html-content
:deep
(
code
)
{
padding
:
0.1em
0.35em
;
border-radius
:
4px
;
background
:
#f3f4f6
;
}
.html-content
:deep
(
pre
code
)
{
padding
:
0
;
background
:
transparent
;
}
.html-content
:deep
(
a
)
{
color
:
#2563eb
;
text-decoration
:
underline
;
}
</
style
>
src/hooks/index.ts
View file @
2816ddfe
...
@@ -6,3 +6,4 @@ export * from './useUploadImg'
...
@@ -6,3 +6,4 @@ export * from './useUploadImg'
export
*
from
'./useNavigation'
export
*
from
'./useNavigation'
export
*
from
'./useMessageBox.tsx'
export
*
from
'./useMessageBox.tsx'
export
*
from
'./useMediaInfo'
export
*
from
'./useMediaInfo'
export
*
from
'./useImagePreview'
src/hooks/useImagePreview.ts
0 → 100644
View file @
2816ddfe
import
{
ref
,
type
Directive
}
from
'vue'
// 页面内共用一个预览弹窗,指令只挂在需要预览的正文 DOM 上。
export
function
useImagePreview
()
{
const
showPreview
=
ref
(
false
)
const
currentPreviewIndex
=
ref
(
0
)
const
srcList
=
ref
<
string
[]
>
([])
const
imageHandlers
=
new
WeakMap
<
HTMLElement
,
EventListener
>
()
// 使用捕获阶段的事件委托,动态富文本更新后无需重复扫描和绑定图片事件。
const
vImagePreview
:
Directive
=
{
mounted
(
el
:
HTMLElement
)
{
const
handler
:
EventListener
=
(
event
)
=>
{
const
target
=
event
.
target
if
(
!
(
target
instanceof
HTMLImageElement
))
return
// 点击时只读取当前正文的图片,不跨问题收集,也不在更新钩子中写入响应式状态。
const
imgs
=
Array
.
from
(
el
.
querySelectorAll
(
'img'
)).
filter
((
item
)
=>
item
.
src
)
const
index
=
imgs
.
indexOf
(
target
)
if
(
index
<
0
)
return
// 图片预览属于当前内容的独立操作,不能继续触发问吧卡片跳转。
event
.
stopPropagation
()
event
.
preventDefault
()
srcList
.
value
=
imgs
.
map
((
item
)
=>
item
.
src
)
currentPreviewIndex
.
value
=
index
showPreview
.
value
=
true
}
imageHandlers
.
set
(
el
,
handler
)
el
.
addEventListener
(
'click'
,
handler
,
true
)
},
beforeUnmount
(
el
:
HTMLElement
)
{
const
handler
=
imageHandlers
.
get
(
el
)
if
(
handler
)
{
el
.
removeEventListener
(
'click'
,
handler
,
true
)
imageHandlers
.
delete
(
el
)
}
},
}
return
{
showPreview
,
currentPreviewIndex
,
srcList
,
vImagePreview
,
}
}
src/style/index.css
View file @
2816ddfe
@import
'./reset.css'
;
@import
'./reset.css'
;
@import
'./uiLib.scss'
;
@import
'./uiLib.scss'
;
@import
'./rich-content.css'
;
/* 解决弹窗滚轮 */
/* 解决弹窗滚轮 */
src/style/rich-content.css
0 → 100644
View file @
2816ddfe
/* 统一文章与问吧富文本内容的排版规则,保持已验证的 ArticleContent 样式不变。 */
.html-content
{
font-size
:
18px
;
line-height
:
1.8
;
color
:
#374151
;
word-break
:
break-word
;
}
.html-content
img
{
cursor
:
pointer
;
}
.html-content
p
{
margin
:
1em
0
;
}
.html-content
h1
,
.html-content
h2
,
.html-content
h3
,
.html-content
h4
,
.html-content
h5
,
.html-content
h6
{
margin
:
1.2em
0
0.6em
;
line-height
:
1.4
;
font-weight
:
600
;
color
:
#111827
;
}
.html-content
h1
{
font-size
:
1.7em
;
}
.html-content
h2
{
font-size
:
1.5em
;
}
.html-content
h3
{
font-size
:
1.3em
;
}
.html-content
ul
,
.html-content
ol
{
margin
:
1em
0
;
padding-left
:
1.5em
;
}
.html-content
ul
{
list-style
:
disc
;
}
.html-content
ol
{
list-style
:
decimal
;
}
.html-content
li
{
margin
:
0.35em
0
;
}
.html-content
blockquote
{
margin
:
1em
0
;
padding
:
0.6em
1em
;
border-left
:
4px
solid
#d1d5db
;
color
:
#6b7280
;
background
:
#f9fafb
;
}
.html-content
pre
,
.html-content
code
{
font-family
:
SFMono-Regular
,
Consolas
,
'Liberation Mono'
,
Menlo
,
monospace
;
}
.html-content
pre
{
margin
:
1em
0
;
padding
:
0.8em
1em
;
border-radius
:
6px
;
overflow-x
:
auto
;
background
:
#f3f4f6
;
}
.html-content
code
{
padding
:
0.1em
0.35em
;
border-radius
:
4px
;
background
:
#f3f4f6
;
}
.html-content
pre
code
{
padding
:
0
;
background
:
transparent
;
}
.html-content
a
{
color
:
#2563eb
;
text-decoration
:
underline
;
}
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