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
c5535c8d
Commit
c5535c8d
authored
Dec 09, 2025
by
lijiabin
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
【需求 17679】 feat: 优化发布等
parent
f4265cd7
Show whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
56 additions
and
296 deletions
+56
-296
index.vue
src/components/common/PublishBox/index.vue
+45
-11
guards.ts
src/router/guards.ts
+11
-1
publishPractice.vue
src/views/homePage/yaTab/components/publishPractice.vue
+0
-238
selectTagsDialog.vue
src/views/homePage/yaTab/components/selectTagsDialog.vue
+0
-46
No files found.
src/components/common/PublishBox/index.vue
View file @
c5535c8d
...
@@ -2,7 +2,7 @@
...
@@ -2,7 +2,7 @@
<div
class=
"bg-white p-6 mb-6 rounded-lg shadow-sm"
>
<div
class=
"bg-white p-6 mb-6 rounded-lg shadow-sm"
>
<div
class=
"flex-1 bg-white rounded-lg border border-gray-200"
>
<div
class=
"flex-1 bg-white rounded-lg border border-gray-200"
>
<!-- 主输入区域 -->
<!-- 主输入区域 -->
<div
class=
"flex gap-3 mb-
4
items-start"
>
<div
class=
"flex gap-3 mb-
2
items-start"
>
<!-- 用户头像 -->
<!-- 用户头像 -->
<el-avatar
:size=
"48"
:src=
"userAvatar"
class=
"flex-shrink-0"
>
<el-avatar
:size=
"48"
:src=
"userAvatar"
class=
"flex-shrink-0"
>
<el-icon><User
/></el-icon>
<el-icon><User
/></el-icon>
...
@@ -78,11 +78,14 @@
...
@@ -78,11 +78,14 @@
<div
class=
"flex items-center justify-between pl-15"
>
<div
class=
"flex items-center justify-between pl-15"
>
<!-- 左侧工具按钮 -->
<!-- 左侧工具按钮 -->
<div
class=
"flex items-center gap-1"
>
<div
class=
"flex items-center gap-1"
>
<el-tooltip
content=
"添加标签"
placement=
"top"
>
<el-tooltip
content=
"添加标签"
placement=
"top"
:visible=
"visibleTagTooltip"
>
<el-button
<el-button
ref=
"tagButtonRef"
text
text
class=
"w-10 h-10 text-gray-500 hover:bg-gray-100 hover:text-gray-700 rounded-lg"
class=
"w-10 h-10 text-gray-500 hover:bg-gray-100 hover:text-gray-700 rounded-lg"
@
click=
"handleAddTag"
@
click=
"handleAddTag"
@
mouseenter=
"visibleTagTooltip = true"
@
mouseleave=
"visibleTagTooltip = false"
>
>
<el-icon
size=
"18"
><CollectionTag
/></el-icon>
<el-icon
size=
"18"
><CollectionTag
/></el-icon>
</el-button>
</el-button>
...
@@ -130,6 +133,7 @@
...
@@ -130,6 +133,7 @@
<el-button
<el-button
type=
"primary"
type=
"primary"
:disabled=
"disabledSubmit"
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"
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)"
@
click=
"handlePublish(ReleaseStatusTypeEnum.PUBLISH)"
>
>
...
@@ -143,6 +147,10 @@
...
@@ -143,6 +147,10 @@
v-model:tagList=
"form.tagList"
v-model:tagList=
"form.tagList"
ref=
"selectTagsDialogRef"
ref=
"selectTagsDialogRef"
/>
/>
<el-tour
v-model=
"openTour"
:mask=
"false"
placement=
"left-start"
type=
"primary"
>
<el-tour-step
:target=
"tagButtonRef?.$el"
description=
"在这里选择标签"
placement=
"top"
/>
</el-tour>
</div>
</div>
</
template
>
</
template
>
<!-- 发布实践 或者 问吧 的 发布框 -->
<!-- 发布实践 或者 问吧 的 发布框 -->
...
@@ -158,6 +166,9 @@ import { Close } from '@element-plus/icons-vue'
...
@@ -158,6 +166,9 @@ import { Close } from '@element-plus/icons-vue'
import
{
addOrUpdatePractice
,
addOrUpdateArticle
}
from
'@/api'
import
{
addOrUpdatePractice
,
addOrUpdateArticle
}
from
'@/api'
import
type
{
AddOrUpdatePracticeDto
}
from
'@/api'
import
type
{
AddOrUpdatePracticeDto
}
from
'@/api'
import
type
{
BooleanFlag
}
from
'@/constants'
import
type
{
BooleanFlag
}
from
'@/constants'
import
type
{
ElButton
}
from
'element-plus'
import
{
useAnimate
}
from
'@vueuse/core'
import
{
isVisible
}
from
'element-plus/es/utils/index.mjs'
type
ArticleType
=
ArticleTypeEnum
.
QUESTION
|
ArticleTypeEnum
.
PRACTICE
type
ArticleType
=
ArticleTypeEnum
.
QUESTION
|
ArticleTypeEnum
.
PRACTICE
...
@@ -194,6 +205,31 @@ const selectTagsDialogRef =
...
@@ -194,6 +205,31 @@ const selectTagsDialogRef =
const
fileInputRef
=
useTemplateRef
<
HTMLInputElement
>
(
'fileInputRef'
)
const
fileInputRef
=
useTemplateRef
<
HTMLInputElement
>
(
'fileInputRef'
)
const
openTour
=
ref
(
false
)
const
disabledSubmit
=
computed
(()
=>
{
return
!
form
.
value
.
title
||
!
form
.
value
.
content
})
const
tagButtonRef
=
useTemplateRef
<
InstanceType
<
typeof
ElButton
>>
(
'tagButtonRef'
)
const
tagButtonEl
=
computed
(()
=>
tagButtonRef
.
value
?.
$el
)
const
visibleTagTooltip
=
ref
(
false
)
const
DURATION
=
500
const
{
play
}
=
useAnimate
(
tagButtonEl
,
[
{
transform
:
'translateY(0)'
},
{
transform
:
'translateY(-10px)'
},
{
transform
:
'translateY(0)'
},
],
{
duration
:
DURATION
,
easing
:
'ease-in-out'
,
immediate
:
false
,
},
)
const
[
form
,
resetForm
]
=
useResetData
({
const
[
form
,
resetForm
]
=
useResetData
({
title
:
''
,
title
:
''
,
content
:
''
,
content
:
''
,
...
@@ -230,16 +266,14 @@ const handleDeleteImg = (img: string) => {
...
@@ -230,16 +266,14 @@ const handleDeleteImg = (img: string) => {
}
}
const
validateForm
=
()
=>
{
const
validateForm
=
()
=>
{
if
(
!
form
.
value
.
title
)
{
ElMessage
.
error
(
'请输入标题'
)
return
false
}
if
(
!
form
.
value
.
content
)
{
ElMessage
.
error
(
'请输入内容'
)
return
false
}
if
(
!
form
.
value
.
mainTagId
)
{
if
(
!
form
.
value
.
mainTagId
)
{
ElMessage
.
error
(
'请选择主标签'
)
ElMessage
.
warning
({
message
:
'请选择主标签'
,
offset
:
200
,
})
play
()
visibleTagTooltip
.
value
=
true
return
false
return
false
}
}
...
...
src/router/guards.ts
View file @
c5535c8d
...
@@ -37,7 +37,17 @@ export function registerRouterGuards(router: Router) {
...
@@ -37,7 +37,17 @@ export function registerRouterGuards(router: Router) {
path
:
to
.
path
,
// 重定向到首页 去除code 重定向到首页 没有code 不会进入到这里了
path
:
to
.
path
,
// 重定向到首页 去除code 重定向到首页 没有code 不会进入到这里了
replace
:
true
,
replace
:
true
,
}
}
}
else
{
}
// else if (
// to.fullPath.includes('/homePage/askTab#tabsRef?t=') &&
// !from.fullPath.includes('/homePage')
// ) {
// return {
// path: to.path,
// // replace: true,
// }
// }
else
{
return
true
return
true
}
}
})
})
...
...
src/views/homePage/yaTab/components/publishPractice.vue
deleted
100644 → 0
View file @
f4265cd7
<
template
>
<div
class=
"bg-white p-6 mb-6 rounded-lg shadow-sm"
>
<div
class=
"flex-1 bg-white rounded-lg border border-gray-200"
>
<!-- 主输入区域 -->
<div
class=
"flex gap-3 mb-4 items-start"
>
<!-- 用户头像 -->
<el-avatar
:size=
"48"
:src=
"userInfo.avatar"
class=
"flex-shrink-0"
>
<el-icon><User
/></el-icon>
</el-avatar>
<!-- 输入区域 -->
<div
class=
"flex-1"
>
<!-- 话题标签输入 -->
<div
class=
"mb-4"
>
<el-input
v-model=
"form.title"
placeholder=
"实践标题"
class=
"tag-input"
clearable
/>
</div>
<!-- 主要内容输入 -->
<div
class=
"relative mb-3"
>
<el-input
type=
"textarea"
placeholder=
"请输入实践内容"
:rows=
"3"
:maxlength=
"500"
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"
>
<!-- 选择的标签内容 -->
<div
class=
"flex items-center gap-2"
>
<span
v-if=
"mainTagText"
class=
"text-sm text-gray-500"
>
主标签:
<el-tag>
{{
mainTagText
}}
</el-tag>
</span>
<span
v-if=
"subTagTextList.length > 0"
class=
"text-sm text-gray-500"
>
副标签:
<el-tag
class=
"mr-2"
v-for=
"tag in subTagTextList"
:key=
"tag"
>
{{
tag
}}
</el-tag>
</span>
</div>
</div>
<!-- 图片相关 -->
<div
v-if=
"form.imgUrl.length"
class=
"flex flex-wrap gap-2"
>
<!-- 删除图片 -->
<div
class=
"relative w-20 h-20 rounded-lg overflow-hidden group"
v-for=
"img in form.imgUrl"
: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"
>
<Close
/>
</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
class=
"flex items-center justify-between pl-15"
>
<!-- 左侧工具按钮 -->
<div
class=
"flex items-center gap-1"
>
<el-tooltip
content=
"添加标签"
placement=
"top"
>
<el-button
text
class=
"w-10 h-10 text-gray-500 hover:bg-gray-100 hover:text-gray-700 rounded-lg"
@
click=
"handleAddTag"
>
<el-icon
size=
"18"
><CollectionTag
/></el-icon>
</el-button>
</el-tooltip>
<!-- 隐藏上传文件的input -->
<input
type=
"file"
class=
"hidden"
ref=
"fileInputRef"
@
change=
"handleFileChange"
/>
<el-tooltip
content=
"添加图片"
placement=
"top"
>
<el-button
text
class=
"w-10 h-10 text-gray-500 hover:bg-gray-100 hover:text-gray-700 rounded-lg"
@
click=
"fileInputRef?.click()"
>
<el-icon
size=
"18"
><Picture
/></el-icon>
</el-button>
</el-tooltip>
<!--
<el-tooltip
content=
"添加视频"
placement=
"top"
>
<el-button
text
class=
"w-10 h-10 text-gray-500 hover:bg-gray-100 hover:text-gray-700 rounded-lg"
>
<el-icon
size=
"18"
><VideoPlay
/></el-icon>
</el-button>
</el-tooltip>
<el-tooltip
content=
"添加附件"
placement=
"top"
>
<el-button
text
class=
"w-10 h-10 text-gray-500 hover:bg-gray-100 hover:text-gray-700 rounded-lg"
>
<el-icon
size=
"18"
><Paperclip
/></el-icon>
</el-button>
</el-tooltip>
-->
</div>
<!-- 右侧操作按钮 -->
<div
class=
"flex items-center gap-3"
>
<el-button
class=
"px-4 py-2 text-gray-600 hover:text-gray-800 hover:bg-gray-50 rounded-lg border border-gray-200 text-sm"
@
click=
"handlePublish(ReleaseStatusTypeEnum.DRAFT)"
>
存草稿
</el-button>
<el-button
type=
"primary"
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)"
>
发布实践
</el-button>
</div>
</div>
</div>
<SelectTagsDialog
v-model:mainTagId=
"form.mainTagId"
v-model:tagList=
"form.tagList"
ref=
"selectTagsDialogRef"
/>
</div>
</
template
>
<
script
setup
lang=
"ts"
>
import
{
useUserStore
}
from
'@/stores'
import
{
storeToRefs
}
from
'pinia'
import
SelectTagsDialog
from
'./selectTagsDialog.vue'
import
{
useResetData
}
from
'@/hooks'
import
{
ReleaseStatusTypeEnum
,
SendTypeEnum
}
from
'@/constants'
import
{
useTagsStore
}
from
'@/stores'
import
{
uploadFile
}
from
'@/api'
import
{
Close
}
from
'@element-plus/icons-vue'
import
{
addOrUpdatePractice
}
from
'@/api'
import
type
{
AddOrUpdatePracticeDto
}
from
'@/api/practice/types'
const
tagsStore
=
useTagsStore
()
const
{
tagList
}
=
storeToRefs
(
tagsStore
)
const
userStore
=
useUserStore
()
const
{
userInfo
}
=
storeToRefs
(
userStore
)
const
selectTagsDialogRef
=
useTemplateRef
<
InstanceType
<
typeof
SelectTagsDialog
>>
(
'selectTagsDialogRef'
)
const
fileInputRef
=
useTemplateRef
<
HTMLInputElement
>
(
'fileInputRef'
)
const
[
form
,
resetForm
]
=
useResetData
({
title
:
''
,
content
:
''
,
imgUrl
:
[],
releaseStatus
:
ReleaseStatusTypeEnum
.
PUBLISH
,
mainTagId
:
''
,
tagList
:
[],
sendType
:
SendTypeEnum
.
IMMEDIATE
,
sendTime
:
''
,
})
const
mainTagText
=
computed
(()
=>
{
return
tagList
.
value
.
find
((
tag
)
=>
tag
.
id
===
Number
(
form
.
value
.
mainTagId
))?.
title
})
const
subTagTextList
=
computed
(()
=>
{
return
form
.
value
.
tagList
.
map
((
tag
)
=>
tagList
.
value
.
find
((
t
)
=>
t
.
id
===
tag
)?.
title
)
})
const
handleAddTag
=
()
=>
{
selectTagsDialogRef
.
value
?.
open
()
}
const
handleFileChange
=
async
(
e
:
Event
)
=>
{
const
file
=
(
e
.
target
as
HTMLInputElement
).
files
?.[
0
]
if
(
file
)
{
const
{
data
}
=
await
uploadFile
(
file
)
form
.
value
.
imgUrl
.
push
(
data
.
data
[
0
].
filePath
)
}
}
const
handleDeleteImg
=
(
img
:
string
)
=>
{
form
.
value
.
imgUrl
=
form
.
value
.
imgUrl
.
filter
((
item
)
=>
item
!==
img
)
}
const
validateForm
=
()
=>
{
if
(
!
form
.
value
.
title
)
{
ElMessage
.
error
(
'请输入实践标题'
)
return
false
}
if
(
!
form
.
value
.
content
)
{
ElMessage
.
error
(
'请输入实践内容'
)
return
false
}
if
(
!
form
.
value
.
mainTagId
)
{
ElMessage
.
error
(
'请选择主标签'
)
return
false
}
return
true
}
const
transformForm
=
(
releaseStatus
:
ReleaseStatusTypeEnum
):
AddOrUpdatePracticeDto
=>
{
return
{
...
form
.
value
,
releaseStatus
,
faceUrl
:
form
.
value
.
imgUrl
[
0
]
||
''
,
imgUrl
:
form
.
value
.
imgUrl
.
join
(
','
),
tagList
:
[
form
.
value
.
mainTagId
,
...
form
.
value
.
tagList
].
map
((
item
,
index
)
=>
({
sort
:
index
,
tagId
:
Number
(
item
),
})),
}
}
const
handlePublish
=
async
(
releaseStatus
:
ReleaseStatusTypeEnum
)
=>
{
if
(
!
validateForm
())
return
await
addOrUpdatePractice
(
transformForm
(
releaseStatus
))
ElMessage
.
success
(
releaseStatus
===
ReleaseStatusTypeEnum
.
PUBLISH
?
'发布成功'
:
'存草稿成功'
)
resetForm
()
}
</
script
>
<
style
scoped
></
style
>
src/views/homePage/yaTab/components/selectTagsDialog.vue
deleted
100644 → 0
View file @
f4265cd7
<
template
>
<el-dialog
v-model=
"dialogVisible"
title=
"选择标签"
width=
"500px"
:close-on-click-modal=
"false"
>
<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>
<div
class=
"flex-1"
>
<SelectTags
v-model=
"mainTagId"
/>
</div>
</div>
<div
class=
"flex items-start gap-4"
>
<div
class=
"text-sm text-gray-700 w-16 flex-shrink-0"
>
副标签
</div>
<div
class=
"flex-1"
>
<SelectTags
v-model=
"subTagIdList"
:max-selected-tags=
"3"
:filter-tags-fn=
"filterTagsFn"
/>
</div>
</div>
</div>
</el-dialog>
</
template
>
<
script
setup
lang=
"ts"
>
import
SelectTags
from
'@/components/common/SelectTags/index.vue'
import
type
{
TagItemDto
}
from
'@/api'
const
dialogVisible
=
ref
(
false
)
const
mainTagId
=
defineModel
<
string
>
(
'mainTagId'
,
{
required
:
true
})
const
subTagIdList
=
defineModel
<
number
[]
>
(
'tagList'
,
{
required
:
true
})
const
open
=
()
=>
{
dialogVisible
.
value
=
true
}
const
filterTagsFn
=
(
allTags
:
TagItemDto
[])
=>
{
return
allTags
.
filter
((
tag
)
=>
tag
.
id
!==
Number
(
mainTagId
.
value
))
}
defineExpose
({
open
,
})
</
script
>
<
style
scoped
></
style
>
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