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
6360213e
Commit
6360213e
authored
Oct 08, 2026
by
lijiabin
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
【需求 25171】feat: 实践新增查看权限设置,支持指定部门/人员可见
parent
7d0ad70f
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
270 additions
and
17 deletions
+270
-17
types.ts
src/api/article/types.ts
+13
-0
types.ts
src/api/practice/types.ts
+11
-0
index.vue
src/components/common/PublishBox/index.vue
+47
-0
index.vue
src/views/backend/contentsMenu/practiceManage/index.vue
+111
-2
PracticeForm.vue
src/views/publishLongArticle/components/PracticeForm.vue
+88
-15
No files found.
src/api/article/types.ts
View file @
6360213e
...
...
@@ -261,6 +261,19 @@ export interface ArticleItemDto {
publishTime
:
number
// 官方账号推送的文章才有这个字段,列表接口直接带出来,不用再单独查一次推送状态
pushInfo
?:
ArticlePushInfoDto
// 实践的查看权限回显(详情接口才有)。跟保存时传的 valueList(纯 id 数组)不一样,
// 这里是 finalPermissions(带 valueName),方便直接拿来回显名字,不用再反查一次。
typeUpdates
?:
ArticleTypeUpdateEchoDto
[]
}
/**
* 实践查看权限的回显(getArticleDetail 返回),跟保存时的 PracticeTypeUpdateDto 结构不同:
* 保存传 valueList(纯 id 数组),这里返回 finalPermissions(带 valueName,方便直接回显)。
*/
export
interface
ArticleTypeUpdateEchoDto
{
/** 1 部门 / 2 人员 */
valueType
:
number
finalPermissions
:
{
valueId
:
string
;
valueName
:
string
}[]
}
/**
...
...
src/api/practice/types.ts
View file @
6360213e
...
...
@@ -2,6 +2,15 @@ import { ArticleTypeEnum, ReleaseStatusTypeEnum, BooleanFlag, SendTypeEnum } fro
import
type
{
PageSearchParams
}
from
'@/utils/request/types'
/**
* 保存查看权限时传的一组:valueType 1 部门 / 2 人员,valueList 是纯 id 数组
* (valueType=2 时 valueId 存的是 user_id,不是企微账号)。
*/
export
interface
PracticeTypeUpdateDto
{
valueType
:
1
|
2
valueList
:
string
[]
}
/**
* 添加或更新实践DTO
*/
export
interface
AddOrUpdatePracticeDto
{
...
...
@@ -17,6 +26,8 @@ export interface AddOrUpdatePracticeDto {
sendTime
:
string
deptId
?:
string
type
?:
ArticleTypeEnum
// 查看权限:一个都没选就是公开,不传这个字段(不要传空数组硬凑)
typeUpdates
?:
PracticeTypeUpdateDto
[]
}
/**
...
...
src/components/common/PublishBox/index.vue
View file @
6360213e
...
...
@@ -144,6 +144,31 @@
<span
class=
"ml-1 text-xs"
>
使用富文本编辑
</span>
</el-button>
</el-tooltip>
<!-- 实践可以设置查看权限:不选就是所有人可见,选了部门/人员之后只有被选中的能看到 -->
<DeptUserPicker
v-if=
"type === ArticleTypeEnum.PRACTICE"
v-model:dept-value=
"permissionDeptIds"
v-model:user-value=
"permissionUserIds"
selection-mode=
"both"
@
confirm=
"(items) => (permissionSelection = items)"
v-slot=
"
{ echoText }"
>
<el-tooltip
:content=
"echoText ? `仅以下部门/人员可见:$
{echoText}` : '设置查看权限(不设置则所有人可见)'"
placement="top"
>
<el-button
text
class=
"w-10 h-10 hover:bg-gray-100 rounded-lg"
:class=
"echoText ? 'text-blue-500' : 'text-gray-500 hover:text-gray-700'"
>
<el-icon
size=
"18"
>
<IEpLock
/>
</el-icon>
</el-button>
</el-tooltip>
</DeptUserPicker>
</div>
<!-- 右侧操作按钮 -->
...
...
@@ -193,6 +218,8 @@ import type { ElButton } from 'element-plus'
import
{
useAnimate
}
from
'@vueuse/core'
import
RichTextarea
from
'../RichTextarea/index.vue'
import
UploadImgIcon
from
'../UploadImgIcon/index.vue'
import
DeptUserPicker
from
'../DeptUserPicker/index.vue'
import
type
{
DepartmentUserSelection
}
from
'../DepartmentUserModal/types'
import
{
push
}
from
'notivue'
import
{
useTourStore
}
from
'@/stores'
import
{
useRouter
}
from
'vue-router'
...
...
@@ -284,6 +311,11 @@ const { imgList, handleFileChange, handleDeleteImg, uploadPercent } = useUploadI
toRef
(
form
.
value
,
'imgUrl'
),
)
// 实践"查看权限":不选就是公开,选了部门/人员之后只有被选中的能看到
const
permissionDeptIds
=
ref
<
string
[]
>
([])
const
permissionUserIds
=
ref
<
string
[]
>
([])
const
permissionSelection
=
ref
<
DepartmentUserSelection
[]
>
([])
const
mainTagText
=
computed
(()
=>
{
return
tagList
.
value
.
find
((
tag
)
=>
tag
.
id
===
Number
(
form
.
value
.
mainTagId
))?.
title
})
...
...
@@ -316,7 +348,18 @@ const validateForm = () => {
return
true
}
// 把选中的部门/人员按 valueType 分组,一个都没选就不传 typeUpdates(不要传空数组硬凑)
const
buildTypeUpdates
=
()
=>
{
const
depts
=
permissionSelection
.
value
.
filter
((
item
)
=>
item
.
valueType
===
1
)
const
users
=
permissionSelection
.
value
.
filter
((
item
)
=>
item
.
valueType
===
2
)
const
typeUpdates
:
{
valueType
:
1
|
2
;
valueList
:
string
[]
}[]
=
[]
if
(
depts
.
length
)
typeUpdates
.
push
({
valueType
:
1
,
valueList
:
depts
.
map
((
item
)
=>
item
.
valueId
)
})
if
(
users
.
length
)
typeUpdates
.
push
({
valueType
:
2
,
valueList
:
users
.
map
((
item
)
=>
item
.
valueId
)
})
return
typeUpdates
}
const
transformForm
=
(
releaseStatus
:
ReleaseStatusTypeEnum
):
AddOrUpdatePracticeDto
=>
{
const
typeUpdates
=
buildTypeUpdates
()
return
{
...
form
.
value
,
releaseStatus
,
...
...
@@ -327,6 +370,7 @@ const transformForm = (releaseStatus: ReleaseStatusTypeEnum): AddOrUpdatePractic
tagId
:
Number
(
item
),
})),
type
:
type
,
...(
typeUpdates
.
length
?
{
typeUpdates
}
:
{}),
}
}
const
handlePublish
=
async
(
releaseStatus
:
ReleaseStatusTypeEnum
)
=>
{
...
...
@@ -339,6 +383,9 @@ const handlePublish = async (releaseStatus: ReleaseStatusTypeEnum) => {
push
.
success
(
releaseStatus
===
ReleaseStatusTypeEnum
.
PUBLISH
?
'发布成功'
:
'存草稿成功'
)
// resetForm()
forReset
()
permissionDeptIds
.
value
=
[]
permissionUserIds
.
value
=
[]
permissionSelection
.
value
=
[]
// form.value.imgUrl = ''
}
catch
(
error
)
{
console
.
error
(
error
)
...
...
src/views/backend/contentsMenu/practiceManage/index.vue
View file @
6360213e
...
...
@@ -2,15 +2,17 @@
import
{
Search
,
Upload
}
from
'@element-plus/icons-vue'
import
{
usePageSearch
,
useResetData
}
from
'@/hooks'
import
{
addOrUpdateColumn
}
from
'@/api/backend'
import
{
getArticleList
,
updateArticleRecommendAndSort
}
from
'@/api'
import
{
getArticleList
,
updateArticleRecommendAndSort
,
getArticleDetail
,
addOrUpdatePractice
}
from
'@/api'
import
type
{
FormInstance
,
FormRules
}
from
'element-plus'
import
type
{
BackendColumnListItemDto
,
AddOrUpdateColumnDto
}
from
'@/api/backend'
import
dayjs
from
'dayjs'
import
{
ArticleTypeEnum
,
BooleanFlag
}
from
'@/constants'
import
type
{
ArticleItemDto
}
from
'@/api/article/types'
import
type
{
ArticleItemDto
,
ArticleTypeUpdateEchoDto
}
from
'@/api/article/types'
import
{
push
}
from
'notivue'
import
{
useMessageBox
}
from
'@/hooks'
import
ArticleContentDialog
from
'@/components/common/ArticleContentDialog/index.vue'
import
DeptUserPicker
from
'@/components/common/DeptUserPicker/index.vue'
import
type
{
DepartmentUserSelection
}
from
'@/components/common/DepartmentUserModal/types'
const
{
prompt
}
=
useMessageBox
()
const
contentDialogRef
=
useTemplateRef
<
InstanceType
<
typeof
ArticleContentDialog
>>
(
'contentDialogRef'
)
...
...
@@ -105,6 +107,74 @@ const handleSelectionChange = (selection: BackendColumnListItemDto[]) => {
const
handleShowContent
=
(
row
:
ArticleItemDto
)
=>
{
contentDialogRef
.
value
?.
open
(
row
.
content
)
}
// 查看权限:管理员在后台也能改,点开先拉一次详情拿到完整数据(保存时要把其他字段原样带回去)
const
permissionDialogVisible
=
ref
(
false
)
const
permissionSubmitting
=
ref
(
false
)
const
permissionDetail
=
ref
<
ArticleItemDto
>
()
const
permissionDeptIds
=
ref
<
string
[]
>
([])
const
permissionUserIds
=
ref
<
string
[]
>
([])
const
permissionSelection
=
ref
<
DepartmentUserSelection
[]
>
([])
const
permissionInitialSelected
=
ref
<
DepartmentUserSelection
[]
>
([])
// 详情接口回显的查看权限是 finalPermissions(带名字),转成 DeptUserPicker 认的 DepartmentUserSelection
const
toSelectionFromTypeUpdates
=
(
typeUpdates
?:
ArticleTypeUpdateEchoDto
[],
):
DepartmentUserSelection
[]
=>
{
if
(
!
typeUpdates
)
return
[]
return
typeUpdates
.
flatMap
((
group
)
=>
group
.
finalPermissions
.
map
((
item
)
=>
({
valueType
:
group
.
valueType
as
1
|
2
,
valueId
:
item
.
valueId
,
valueName
:
item
.
valueName
,
})),
)
}
const
handleEditPermission
=
async
(
row
:
ArticleItemDto
)
=>
{
const
{
data
}
=
await
getArticleDetail
(
row
.
id
)
permissionDetail
.
value
=
data
const
items
=
toSelectionFromTypeUpdates
(
data
.
typeUpdates
)
permissionDeptIds
.
value
=
items
.
filter
((
item
)
=>
item
.
valueType
===
1
).
map
((
item
)
=>
item
.
valueId
)
permissionUserIds
.
value
=
items
.
filter
((
item
)
=>
item
.
valueType
===
2
).
map
((
item
)
=>
item
.
valueId
)
permissionSelection
.
value
=
items
permissionInitialSelected
.
value
=
items
permissionDialogVisible
.
value
=
true
}
const
handleSubmitPermission
=
async
()
=>
{
if
(
!
permissionDetail
.
value
)
return
const
depts
=
permissionSelection
.
value
.
filter
((
item
)
=>
item
.
valueType
===
1
)
const
users
=
permissionSelection
.
value
.
filter
((
item
)
=>
item
.
valueType
===
2
)
const
typeUpdates
:
{
valueType
:
1
|
2
;
valueList
:
string
[]
}[]
=
[]
if
(
depts
.
length
)
typeUpdates
.
push
({
valueType
:
1
,
valueList
:
depts
.
map
((
item
)
=>
item
.
valueId
)
})
if
(
users
.
length
)
typeUpdates
.
push
({
valueType
:
2
,
valueList
:
users
.
map
((
item
)
=>
item
.
valueId
)
})
const
detail
=
permissionDetail
.
value
permissionSubmitting
.
value
=
true
try
{
await
addOrUpdatePractice
({
id
:
detail
.
id
,
title
:
detail
.
title
,
content
:
detail
.
content
,
faceUrl
:
detail
.
faceUrl
,
imgUrl
:
detail
.
imgUrl
,
releaseStatus
:
detail
.
releaseStatus
,
mainTagId
:
detail
.
tagIdList
[
0
]
??
''
,
tagList
:
detail
.
tagIdList
.
map
((
tagId
,
index
)
=>
({
tagId
,
sort
:
index
})),
sendType
:
detail
.
sendType
,
sendTime
:
detail
.
sendTime
,
deptId
:
detail
.
deptId
,
...(
typeUpdates
.
length
?
{
typeUpdates
}
:
{}),
})
push
.
success
(
'修改成功'
)
permissionDialogVisible
.
value
=
false
search
()
}
finally
{
permissionSubmitting
.
value
=
false
}
}
</
script
>
<
template
>
...
...
@@ -180,6 +250,14 @@ const handleShowContent = (row: ArticleItemDto) => {
</
template
>
</el-table-column>
<el-table-column
label=
"查看权限"
min-width=
"120"
>
<
template
#
default=
"{ row }"
>
<el-link
type=
"primary"
:underline=
"false"
@
click=
"handleEditPermission(row)"
>
设置权限
</el-link>
</
template
>
</el-table-column>
<el-table-column
prop=
"showName"
label=
"创建人"
min-width=
"200"
/>
<el-table-column
prop=
"createTime"
label=
"创建时间"
min-width=
"200"
>
...
...
@@ -235,6 +313,37 @@ const handleShowContent = (row: ArticleItemDto) => {
</el-dialog>
<ArticleContentDialog
ref=
"contentDialogRef"
/>
<!-- 查看权限:本人和管理员都能改,这里是后台管理员改的入口 -->
<el-dialog
v-model=
"permissionDialogVisible"
title=
"设置查看权限"
width=
"500px"
:close-on-click-modal=
"false"
>
<DeptUserPicker
v-model:dept-value=
"permissionDeptIds"
v-model:user-value=
"permissionUserIds"
selection-mode=
"both"
:initial-selected=
"permissionInitialSelected"
@
confirm=
"(items) => (permissionSelection = items)"
v-slot=
"{ echoText }"
>
<el-input
readonly
class=
"w-full cursor-pointer"
placeholder=
"不设置则所有人可见"
:model-value=
"echoText"
/>
</DeptUserPicker>
<
template
#
footer
>
<el-button
@
click=
"permissionDialogVisible = false"
>
取消
</el-button>
<el-button
type=
"primary"
:loading=
"permissionSubmitting"
@
click=
"handleSubmitPermission"
>
保存
</el-button>
</
template
>
</el-dialog>
</div>
</template>
...
...
src/views/publishLongArticle/components/PracticeForm.vue
View file @
6360213e
...
...
@@ -18,6 +18,25 @@
/>
</el-form-item>
<!-- 查看权限:不选就是所有人可见,选了部门/人员之后只有被选中的能看到,本人和管理员都能改 -->
<el-form-item
label=
"查看权限"
>
<DeptUserPicker
v-model:dept-value=
"permissionDeptIds"
v-model:user-value=
"permissionUserIds"
selection-mode=
"both"
:initial-selected=
"permissionInitialSelected"
@
confirm=
"(items) => (permissionSelection = items)"
v-slot=
"
{ echoText }"
>
<el-input
readonly
class=
"w-full cursor-pointer"
placeholder=
"不设置则所有人可见"
:model-value=
"echoText"
/>
</DeptUserPicker>
</el-form-item>
<!-- 实践没有推送功能(不管是不是官方账号);官方账号可以选一个所属一级部门,选填 -->
<el-form-item
v-if=
"userInfo.isOfficialAccount"
label=
"所属一级部门"
>
<el-select
v-model=
"form.deptId"
placeholder=
"请选择一级部门"
clearable
>
...
...
@@ -71,6 +90,8 @@
* - 主/副标签必填
* - 完全没有推送功能,不管是不是官方账号——这是跟帖子/问吧/专栏/专访最大的区别
* - 官方账号可以选一个"所属一级部门",选填,不是必填项
* - 查看权限:不选部门/人员就是公开,选了之后只有被选中的能看到,作者本人和管理员都能改;
* 这个不区分官方账号,任何人发实践都能设置
* - 提交走的是单独的 addOrUpdatePractice 接口(不是 addOrUpdateArticle),这个在外层
* publishLongArticle/index.vue 里按 type 分支处理,这个组件只管收集字段
*/
...
...
@@ -78,7 +99,9 @@ import type { FormInstance, FormItemRule } from 'element-plus'
import
{
firstDepListOptions
,
BooleanFlag
,
SendTypeEnum
}
from
'@/constants'
import
UploadFile
from
'@/components/common/UploadFile/index.vue'
import
SelectTags
from
'@/components/common/SelectTags/index.vue'
import
type
{
ArticleItemDto
}
from
'@/api/article/types'
import
DeptUserPicker
from
'@/components/common/DeptUserPicker/index.vue'
import
type
{
DepartmentUserSelection
}
from
'@/components/common/DepartmentUserModal/types'
import
type
{
ArticleItemDto
,
ArticleTypeUpdateEchoDto
}
from
'@/api/article/types'
import
{
useResetData
}
from
'@/hooks'
import
{
useUserStore
}
from
'@/stores/user'
import
{
storeToRefs
}
from
'pinia'
...
...
@@ -97,7 +120,15 @@ const [form, resetForm] = useResetData({
regionHide
:
BooleanFlag
.
NO
,
})
const
filterTagsFn
=
(
allTags
:
any
[])
=>
allTags
.
filter
((
tag
)
=>
tag
.
id
!==
Number
(
form
.
value
.
mainTagId
))
// 查看权限选中的部门/人员
const
permissionDeptIds
=
ref
<
string
[]
>
([])
const
permissionUserIds
=
ref
<
string
[]
>
([])
const
permissionSelection
=
ref
<
DepartmentUserSelection
[]
>
([])
// 编辑回显用,给 DeptUserPicker 做首次回显的名字种子
const
permissionInitialSelected
=
ref
<
DepartmentUserSelection
[]
>
([])
const
filterTagsFn
=
(
allTags
:
any
[])
=>
allTags
.
filter
((
tag
)
=>
tag
.
id
!==
Number
(
form
.
value
.
mainTagId
))
// 时间选择只允许整十分(:00、:10...:50),秒固定为 0
const
disabledSecondsOnlyZero
=
()
=>
Array
.
from
({
length
:
59
},
(
_
,
i
)
=>
i
+
1
)
...
...
@@ -123,21 +154,49 @@ const rules: Record<string, FormItemRule[]> = {
regionHide
:
[{
required
:
true
,
message
:
'请选择是否隐藏IP信息'
,
trigger
:
'trigger'
}],
}
// 把选中的部门/人员按 valueType 分组,一个都没选就不传 typeUpdates(不要传空数组硬凑)
const
buildTypeUpdates
=
()
=>
{
const
depts
=
permissionSelection
.
value
.
filter
((
item
)
=>
item
.
valueType
===
1
)
const
users
=
permissionSelection
.
value
.
filter
((
item
)
=>
item
.
valueType
===
2
)
const
typeUpdates
:
{
valueType
:
1
|
2
;
valueList
:
string
[]
}[]
=
[]
if
(
depts
.
length
)
typeUpdates
.
push
({
valueType
:
1
,
valueList
:
depts
.
map
((
item
)
=>
item
.
valueId
)
})
if
(
users
.
length
)
typeUpdates
.
push
({
valueType
:
2
,
valueList
:
users
.
map
((
item
)
=>
item
.
valueId
)
})
return
typeUpdates
}
// 详情接口回显的查看权限是 finalPermissions(带名字),转成 DeptUserPicker 认的 DepartmentUserSelection
const
toSelectionFromTypeUpdates
=
(
typeUpdates
?:
ArticleTypeUpdateEchoDto
[],
):
DepartmentUserSelection
[]
=>
{
if
(
!
typeUpdates
)
return
[]
return
typeUpdates
.
flatMap
((
group
)
=>
group
.
finalPermissions
.
map
((
item
)
=>
({
valueType
:
group
.
valueType
as
1
|
2
,
valueId
:
item
.
valueId
,
valueName
:
item
.
valueName
,
})),
)
}
defineExpose
({
validate
:
()
=>
formRef
.
value
!
.
validate
(),
getSubmitData
:
()
=>
({
imgUrl
:
form
.
value
.
imgUrl
,
faceUrl
:
form
.
value
.
imgUrl
?
form
.
value
.
imgUrl
.
split
(
','
)[
0
]
||
''
:
''
,
mainTagId
:
form
.
value
.
mainTagId
,
tagList
:
[
form
.
value
.
mainTagId
,
...
form
.
value
.
tagList
].
map
((
item
,
index
)
=>
({
tagId
:
Number
(
item
),
sort
:
index
,
})),
sendType
:
form
.
value
.
sendType
,
sendTime
:
form
.
value
.
sendTime
,
regionHide
:
form
.
value
.
regionHide
,
...(
userInfo
.
value
.
isOfficialAccount
?
{
deptId
:
form
.
value
.
deptId
||
''
}
:
{}),
}),
getSubmitData
:
()
=>
{
const
typeUpdates
=
buildTypeUpdates
()
return
{
imgUrl
:
form
.
value
.
imgUrl
,
faceUrl
:
form
.
value
.
imgUrl
?
form
.
value
.
imgUrl
.
split
(
','
)[
0
]
||
''
:
''
,
mainTagId
:
form
.
value
.
mainTagId
,
tagList
:
[
form
.
value
.
mainTagId
,
...
form
.
value
.
tagList
].
map
((
item
,
index
)
=>
({
tagId
:
Number
(
item
),
sort
:
index
,
})),
sendType
:
form
.
value
.
sendType
,
sendTime
:
form
.
value
.
sendTime
,
regionHide
:
form
.
value
.
regionHide
,
...(
userInfo
.
value
.
isOfficialAccount
?
{
deptId
:
form
.
value
.
deptId
||
''
}
:
{}),
...(
typeUpdates
.
length
?
{
typeUpdates
}
:
{}),
}
},
setEchoData
:
(
data
:
ArticleItemDto
)
=>
{
form
.
value
.
imgUrl
=
data
.
imgUrl
form
.
value
.
mainTagId
=
data
.
tagIdList
[
0
]
?
String
(
data
.
tagIdList
[
0
])
:
''
...
...
@@ -148,9 +207,23 @@ defineExpose({
if
(
userInfo
.
value
.
isOfficialAccount
)
{
form
.
value
.
deptId
=
data
.
deptId
===
'0'
?
''
:
data
.
deptId
}
const
items
=
toSelectionFromTypeUpdates
(
data
.
typeUpdates
)
permissionDeptIds
.
value
=
items
.
filter
((
item
)
=>
item
.
valueType
===
1
)
.
map
((
item
)
=>
item
.
valueId
)
permissionUserIds
.
value
=
items
.
filter
((
item
)
=>
item
.
valueType
===
2
)
.
map
((
item
)
=>
item
.
valueId
)
permissionSelection
.
value
=
items
permissionInitialSelected
.
value
=
items
},
reset
:
()
=>
{
resetForm
()
permissionDeptIds
.
value
=
[]
permissionUserIds
.
value
=
[]
permissionSelection
.
value
=
[]
permissionInitialSelected
.
value
=
[]
},
})
</
script
>
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