Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
J
jilinzhongdianrenqun-web
概览
Overview
Details
Activity
Cycle Analytics
版本库
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
问题
0
Issues
0
列表
Board
标记
里程碑
合并请求
0
Merge Requests
0
CI / CD
CI / CD
流水线
作业
日程表
图表
维基
Wiki
代码片段
Snippets
成员
Members
Collapse sidebar
Close sidebar
活动
图像
聊天
创建新问题
作业
提交
Issue Boards
Open sidebar
吴超
jilinzhongdianrenqun-web
Commits
df7a87f2
Commit
df7a87f2
authored
Nov 17, 2025
by
wuchao
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
回访计划
parent
c390e525
隐藏空白字符变更
内嵌
并排
正在显示
5 个修改的文件
包含
726 行增加
和
634 行删除
+726
-634
src/api/key-person.js
+42
-61
src/view/key-person/key-person-dual/index.vue
+24
-2
src/view/key-person/key-person-visit-record/components/VisitHistoryModal.vue
+8
-8
src/view/key-person/key-person-visit-record/index.vue
+537
-542
src/view/key-person/key-person-visit/index.vue
+115
-21
没有找到文件。
src/api/key-person.js
View file @
df7a87f2
...
...
@@ -121,67 +121,6 @@ export const getKeyPersonUser = (param) => {
})
}
// ===== 回访记录(办理)模块 =====
// 列表
export
const
findVisitRecordList
=
(
param
)
=>
{
return
axios
.
request
({
url
:
'/api/ac/jilinsscgsdp/keyPersonVisitRecord/findVisitRecordList'
,
method
:
'post'
,
data
:
param
})
}
// 获取本次评分统计数据
export
const
getScoreStatistics
=
(
param
)
=>
{
return
axios
.
request
({
url
:
'/api/ac/jilinsscgsdp/keyPersonStatistics/getScoreStatistics'
,
method
:
'post'
,
data
:
param
})
}
// 详情(含基本信息、计划信息、历史记录)
export
const
getVisitRecordDetail
=
(
param
)
=>
{
return
axios
.
request
({
url
:
'/api/ac/jilinsscgsdp/keyPersonVisitRecord/getVisitRecordDetail'
,
method
:
'post'
,
data
:
param
})
}
// 新增回访记录
export
const
insertVisitRecord
=
(
param
)
=>
{
return
axios
.
request
({
url
:
'/api/ac/jilinsscgsdp/keyPersonVisitRecord/insertVisitRecord'
,
method
:
'post'
,
data
:
param
})
}
// 获取回访按钮权限
export
const
getVisitButtonPower
=
(
param
)
=>
{
return
axios
.
request
({
url
:
'/api/ac/jilinsscgsdp/keyPersonVisitRecord/getVisitButtonPower'
,
method
:
'post'
,
data
:
param
})
}
// 历史回访记录分页
export
const
selectVisitHistoryPage
=
(
param
)
=>
{
return
axios
.
request
({
url
:
'/api/ac/jilinsscgsdp/keyPersonVisitRecord/selectVisitHistoryPage'
,
method
:
'post'
,
data
:
param
})
}
// 待办统计(正常/临期)
export
const
getPendingStats
=
(
param
)
=>
{
return
axios
.
request
({
url
:
'/api/ac/jilinsscgsdp/keyPersonVisitRecord/getPendingStats'
,
method
:
'post'
,
data
:
param
})
}
// ===== 按钮可见权限配置 =====
// 列表(来源于字典 page_button,并合并已配置的可见部门)
export
const
selectPageButtonPowerList
=
(
param
)
=>
{
...
...
@@ -380,3 +319,45 @@ export const getDualDetail = (param) => {
data
:
param
})
}
// ===== 回访记录办理 =====
// 查询回访记录列表
export
const
findVisitRecordList
=
(
param
)
=>
{
return
axios
.
request
({
url
:
'/api/ac/jilinsscgsdp/keyPersonVisitRecord/findVisitRecordList'
,
method
:
'post'
,
data
:
param
})
}
// 获取人员信息(用于回访)
export
const
getPersonInfoForVisit
=
(
param
)
=>
{
return
axios
.
request
({
url
:
'/api/ac/jilinsscgsdp/keyPersonVisitRecord/getPersonInfoForVisit'
,
method
:
'post'
,
data
:
param
})
}
// 保存回访记录
export
const
saveVisitRecord
=
(
param
)
=>
{
return
axios
.
request
({
url
:
'/api/ac/jilinsscgsdp/keyPersonVisitRecord/saveVisitRecord'
,
method
:
'post'
,
data
:
param
})
}
// 查询回访历史(分页)
export
const
selectVisitHistoryPage
=
(
param
)
=>
{
return
axios
.
request
({
url
:
'/api/ac/jilinsscgsdp/keyPersonVisitRecord/selectVisitHistoryPage'
,
method
:
'post'
,
data
:
param
})
}
// 查询回访记录详情
export
const
getVisitRecordDetail
=
(
param
)
=>
{
return
axios
.
request
({
url
:
'/api/ac/jilinsscgsdp/keyPersonVisitRecord/getVisitRecordDetail'
,
method
:
'post'
,
data
:
param
})
}
src/view/key-person/key-person-dual/index.vue
View file @
df7a87f2
...
...
@@ -576,6 +576,14 @@ const buildFilters = {
forward
:
()
=>
({
name
:
''
,
card_no
:
''
,
city_id
:
''
,
area_id
:
''
,
street_id
:
''
,
community_id
:
''
,
push_status
:
''
})
}
// 身份证号统一转码、规整(去除所有空白并大写),避免查询、筛选不生效
function
normalizeCardNo
(
val
)
{
if
(
!
val
)
return
''
const
s
=
String
(
val
).
replace
(
/
\s
+/g
,
''
).
trim
()
if
(
!
s
)
return
''
return
s
.
toUpperCase
()
}
export
default
{
name
:
'key-person-dual-index'
,
props
:
{
...
...
@@ -816,7 +824,15 @@ export default {
const
api
=
apiMap
[
tab
]
if
(
!
api
)
return
this
.
loading
[
tab
]
=
true
const
params
=
Object
.
assign
({},
this
.
filters
[
tab
],
{
sys_type
:
this
.
currentCategory
})
// 统一处理身份证号(空白 -> 删除,末位 X -> 大写),避免查询/筛选失败
const
rawFilters
=
this
.
filters
[
tab
]
||
{}
const
normalizedCardNo
=
normalizeCardNo
(
rawFilters
.
card_no
)
const
params
=
Object
.
assign
(
{},
rawFilters
,
{
card_no
:
normalizedCardNo
,
cardNo
:
normalizedCardNo
},
{
sys_type
:
this
.
currentCategory
}
)
const
payload
=
Object
.
assign
({},
this
.
pagers
[
tab
],
{
params
})
api
(
payload
).
then
(
ret
=>
{
if
(
ret
.
data
&&
ret
.
data
.
errcode
===
0
)
{
...
...
@@ -926,10 +942,16 @@ export default {
if
(
pageNo
!==
undefined
&&
pageNo
!==
null
)
{
this
.
pushModal
.
personPager
.
pageNo
=
pageNo
}
const
normalizedCardNo
=
normalizeCardNo
(
this
.
pushModal
.
personFilters
.
card_no
)
const
payload
=
{
pageNo
:
this
.
pushModal
.
personPager
.
pageNo
,
pageSize
:
this
.
pushModal
.
personPager
.
pageSize
,
params
:
Object
.
assign
({},
this
.
pushModal
.
personFilters
,
{
sys_type
:
this
.
currentCategory
})
params
:
Object
.
assign
(
{},
this
.
pushModal
.
personFilters
,
{
card_no
:
normalizedCardNo
,
cardNo
:
normalizedCardNo
},
{
sys_type
:
this
.
currentCategory
}
)
}
this
.
pushModal
.
personLoading
=
true
getDualPersonOptions
(
payload
).
then
(
ret
=>
{
...
...
src/view/key-person/key-person-visit-record/components/VisitHistoryModal.vue
View file @
df7a87f2
...
...
@@ -19,7 +19,6 @@
<Form
:label-width=
"100"
>
<FormItem
label=
"风险等级"
><Input
:value=
"renderRatingType(detail.rating_type)"
disabled
/></FormItem>
<FormItem
label=
"回访人"
><Input
:value=
"detail.visit_user || '-'"
disabled
/></FormItem>
<FormItem
label=
"回访部门"
><Input
:value=
"detail.visit_user_company || '-'"
disabled
/></FormItem>
<FormItem
label=
"回访时间"
><Input
:value=
"formatDateTime(detail.visit_time)"
disabled
/></FormItem>
<FormItem
label=
"回访记录"
><Input
type=
"textarea"
:rows=
"4"
:value=
"detail.visit_record || '-'"
disabled
/></FormItem>
<FormItem
label=
"回访图片"
>
...
...
@@ -44,9 +43,8 @@
<div>
<Form
:label-width=
"100"
>
<FormItem
label=
"风险等级"
><Input
:value=
"renderRatingType(recordModal.data.rating_type)"
disabled
/></FormItem>
<FormItem
label=
"回访时间"
><Input
:value=
"
recordModal.data.visit_time || '-'
"
disabled
/></FormItem>
<FormItem
label=
"回访时间"
><Input
:value=
"
formatDateTime(recordModal.data.visit_time)
"
disabled
/></FormItem>
<FormItem
label=
"回访人"
><Input
:value=
"recordModal.data.visit_user || recordModal.data.do_user_name || recordModal.data.operator_name || recordModal.data.create_user_name || '-'"
disabled
/></FormItem>
<FormItem
label=
"回访部门"
><Input
:value=
"recordModal.data.visit_user_company || '-'"
disabled
/></FormItem>
<FormItem
label=
"回访记录"
><Input
type=
"textarea"
:rows=
"4"
:value=
"recordModal.data.visit_record || '-'"
disabled
/></FormItem>
<FormItem
label=
"回访图片"
>
<div
class=
"img-list"
>
...
...
@@ -87,11 +85,9 @@ export default {
page
:
{
pageNo
:
1
,
pageSize
:
10
,
totalRecord
:
0
},
visitHistoryColumns
:
[
{
title
:
'风险等级'
,
key
:
'rating_type'
,
align
:
'center'
,
width
:
120
,
render
:
(
h
,
{
row
})
=>
h
(
'span'
,
this
.
renderRatingType
(
row
.
rating_type
))
},
{
title
:
'回访人'
,
key
:
'visit_user'
,
align
:
'center'
,
width
:
120
},
{
title
:
'回访部门'
,
key
:
'visit_user_company'
,
align
:
'center'
,
width
:
180
},
{
title
:
'回访时间'
,
key
:
'visit_time'
,
align
:
'center'
,
width
:
180
,
render
:
(
h
,
{
row
})
=>
h
(
'span'
,
this
.
formatDateTime
(
row
.
visit_time
))
},
{
title
:
'回访
状态'
,
key
:
'do_status'
,
align
:
'center'
,
width
:
120
,
render
:
(
h
,
{
row
})
=>
h
(
'span'
,
this
.
renderVisitStatus
(
row
.
do_status
))
},
{
title
:
'操作'
,
key
:
'action'
,
align
:
'center'
,
width
:
100
,
render
:
(
h
,
{
row
})
=>
(
String
(
row
.
do_status
)
===
'2'
?
h
(
'span'
,
''
)
:
h
(
'Button'
,
{
props
:
{
type
:
'primary'
,
size
:
'small'
},
on
:
{
click
:
()
=>
this
.
openRecord
(
row
)
}
},
'查看'
)
)
}
{
title
:
'回访
人'
,
key
:
'visit_user'
,
align
:
'center'
,
width
:
120
},
{
title
:
'操作'
,
key
:
'action'
,
align
:
'center'
,
width
:
100
,
render
:
(
h
,
{
row
})
=>
h
(
'Button'
,
{
props
:
{
type
:
'primary'
,
size
:
'small'
},
on
:
{
click
:
()
=>
this
.
openRecord
(
row
)
}
},
'详情'
)
}
],
ratingTypeOptions
:
[],
nationMap
:
{},
...
...
@@ -142,7 +138,11 @@ export default {
this
.
fetchHistory
({
key_person_id
:
this
.
key_person_id
,
order_by
:
'visit_time desc'
})
},
fetchHistory
(
extraParams
)
{
const
params
=
Object
.
assign
({},
extraParams
)
const
params
=
Object
.
assign
(
{},
extraParams
,
{
rating_dict_type
:
String
(
this
.
rating_dict_type
||
''
)
}
)
const
payload
=
{
params
,
pageNo
:
this
.
page
.
pageNo
,
pageSize
:
this
.
page
.
pageSize
}
selectVisitHistoryPage
(
payload
).
then
((
ret
)
=>
{
if
(
ret
&&
ret
.
data
&&
ret
.
data
.
errcode
===
0
)
{
...
...
src/view/key-person/key-person-visit-record/index.vue
View file @
df7a87f2
<
template
>
<div
class=
"
contradiction-div
"
>
<div
class=
"
visit-record-wrapper
"
>
<div
class=
"search-div"
>
<Row
type=
"flex"
>
<Row
type=
"flex"
:gutter=
"16"
class=
"mb10"
>
<Col
span=
"4"
>
<span>
姓名:
</span>
<Input
v-model=
"filters.name"
placeholder=
"请输入"
style=
"width: 65%"
/>
</Col>
<Col
span=
"4"
>
<span>
身份证号:
</span>
<Input
v-model=
"filters.card_no"
placeholder=
"请输入"
style=
"width:
5
5%"
/>
<Input
v-model=
"filters.card_no"
placeholder=
"请输入"
style=
"width:
6
5%"
/>
</Col>
<Col
span=
"4"
>
<span>
选择市:
</span>
<Select
v-model=
"filters.city_id"
clearable
style=
"width:
55%"
@
on-change=
"changeArea('2', filters.city_id
)"
>
<Option
v-for=
"c in city
List
"
:key=
"c.parent_id"
:value=
"c.parent_id"
>
{{
c
.
name
}}
</Option>
<Select
v-model=
"filters.city_id"
clearable
style=
"width:
65%"
@
on-change=
"handleFilterCascade('city', $event
)"
>
<Option
v-for=
"c in city
Options
"
:key=
"c.parent_id"
:value=
"c.parent_id"
>
{{
c
.
name
}}
</Option>
</Select>
</Col>
<Col
span=
"4"
>
<span>
选择区:
</span>
<Select
v-model=
"filters.area_id"
clearable
style=
"width:
55%"
@
on-change=
"changeArea('3', filters.area_id
)"
>
<Option
v-for=
"c in areaList"
:key=
"c.parent_id"
:value=
"c.parent_id"
>
{{
c
.
name
}}
</Option>
<Select
v-model=
"filters.area_id"
clearable
style=
"width:
65%"
@
on-change=
"handleFilterCascade('area', $event
)"
>
<Option
v-for=
"c in area
State.area
List"
:key=
"c.parent_id"
:value=
"c.parent_id"
>
{{
c
.
name
}}
</Option>
</Select>
</Col>
<Col
span=
"4"
>
<span>
选择街道:
</span>
<Select
v-model=
"filters.street_id"
clearable
style=
"width:
55%"
@
on-change=
"changeArea('4', filters.street_id
)"
>
<Option
v-for=
"c in streetList"
:key=
"c.parent_id"
:value=
"c.parent_id"
>
{{
c
.
name
}}
</Option>
<Select
v-model=
"filters.street_id"
clearable
style=
"width:
65%"
@
on-change=
"handleFilterCascade('street', $event
)"
>
<Option
v-for=
"c in
areaState.
streetList"
:key=
"c.parent_id"
:value=
"c.parent_id"
>
{{
c
.
name
}}
</Option>
</Select>
</Col>
<Col
span=
"4"
>
<span>
选择社区:
</span>
<Select
v-model=
"filters.community_id"
clearable
style=
"width:
5
5%"
>
<Option
v-for=
"c in communityList"
:key=
"c.parent_id"
:value=
"c.parent_id"
>
{{
c
.
name
}}
</Option>
<Select
v-model=
"filters.community_id"
clearable
style=
"width:
6
5%"
>
<Option
v-for=
"c in
areaState.
communityList"
:key=
"c.parent_id"
:value=
"c.parent_id"
>
{{
c
.
name
}}
</Option>
</Select>
</Col>
</Row>
<Row
type=
"flex"
style=
"margin-top: 15px
"
>
<Row
type=
"flex"
:gutter=
"16"
class=
"mb10
"
>
<Col
span=
"4"
>
<span>
办理状态:
</span>
<Select
v-model=
"filters.do_status"
style=
"width: 49%"
>
<Option
value=
"0"
>
待办
</Option>
<Option
value=
"1"
>
已办
</Option>
<Option
value=
"2"
>
超期未办
</Option>
</Select>
</Col>
<Col
span=
"4"
>
<span>
是否临期:
</span>
<Select
v-model=
"filters.is_overdue"
clearable
style=
"width: 49%"
>
<Option
value=
"1"
>
是
</Option>
<Option
value=
"0"
>
否
</Option>
<span>
风险等级:
</span>
<Select
v-model=
"filters.rating_level"
clearable
style=
"width: 65%"
>
<Option
v-for=
"opt in ratingLevelOptions"
:key=
"opt.value"
:value=
"opt.value"
>
{{
opt
.
label
}}
</Option>
</Select>
</Col>
<Col
span=
"4"
>
<span>
风险等级
:
</span>
<Select
v-model=
"filters.
rating_type"
clearable
style=
"width: 5
5%"
>
<Option
v-for=
"opt in
ratingType
Options"
:key=
"opt.value"
:value=
"opt.value"
>
{{
opt
.
label
}}
</Option>
<span>
回访状态
:
</span>
<Select
v-model=
"filters.
visit_status"
clearable
style=
"width: 6
5%"
>
<Option
v-for=
"opt in
visitStatus
Options"
:key=
"opt.value"
:value=
"opt.value"
>
{{
opt
.
label
}}
</Option>
</Select>
</Col>
<Col
style=
"text-align: right"
>
<Button
type=
"primary"
@
click=
"search"
style=
"margin-right: 10px"
>
搜索
</Button>
<Button
type=
"primary"
@
click=
"reset"
>
重置
</Button>
<Button
style=
"margin-left: 10px;"
@
click=
"$router.go(-1)"
v-if=
"this.$route.query.do_status"
>
返回重点人员统计
</Button>
<Col
span=
"8"
class=
"text-right"
>
<Button
type=
"primary"
class=
"mr10"
@
click=
"handleSearch"
>
搜索
</Button>
<Button
@
click=
"handleReset"
>
重置
</Button>
</Col>
</Row>
</div>
<div
class=
"pending-stats
"
>
<span
v-if=
"false"
>
正常待办:
{{
statsPending
.
normal
}}
</span>
<
span>
临期待办:
{{
statsPending
.
nearDue
}}
</span
>
</div
>
<Table
border
:loading=
"tableLoading"
:columns=
"tableColumns"
:data=
"tableData"
>
<template
slot=
"action"
slot-scope=
"
{ row }">
<Button
v-if=
"canShowVisit(row) && String(row.do_status) === '0'"
type=
"primary"
size=
"small"
style=
"margin-right:6px
"
@
click=
"openVisitModal(row)"
>
回访
</Button>
<Button
v-if=
"hasHistory(row)"
type=
"info"
size=
"small"
@
click=
"openHistoryModal(row)"
>
查看
历史
</Button>
</
template
>
<Table
border
:loading=
"loading"
:columns=
"columns"
:data=
"tableData"
>
<template
slot=
"rating_level"
slot-scope=
"
{ row }
">
<span>
{{
renderRatingLevel
(
row
.
rating_level
)
}}
</span>
<
/
template
>
<
template
slot=
"visit_status"
slot-scope=
"{ row }"
>
<span>
{{
renderVisitStatus
(
row
.
visit_status
)
}}
</span>
</
template
>
<
template
slot=
"action"
slot-scope=
"{ row }"
>
<Button
size=
"small"
type=
"primary"
class=
"mr5
"
@
click=
"openVisitModal(row)"
>
回访
</Button>
<Button
size=
"small"
@
click=
"openHistoryModal(row)"
>
回访
历史
</Button>
</
template
>
</Table>
<Page
class=
"page_style"
:total=
"params.totalRecord"
:current=
"params.pageNo"
:page-size=
"params.pageSize"
show-total
show-sizer
@
on-change=
"changePageNo"
@
on-page-size-change=
"size"
/>
<Page
class=
"page_style"
:total=
"pager.totalRecord"
:current=
"pager.pageNo"
:page-size=
"pager.pageSize"
show-total
show-sizer
@
on-change=
"pageChange"
@
on-page-size-change=
"sizeChange"
/>
<!-- 回访弹窗(参照样式:基本信息 + 计划信息 + 历史回访信息 + 填写回访记录) -->
<Modal
v-model=
"visitModal.visible"
title=
"回访办理"
width=
"900"
>
<div>
<!-- 回访弹窗 -->
<Modal
v-model=
"visitModal.visible"
title=
"回访"
width=
"800"
:mask-closable=
"false"
>
<div
v-if=
"visitModal.loading"
>
<Spin
fix
></Spin>
</div>
<div
v-else
>
<h4
class=
"section-title"
>
人员基本信息
</h4>
<Form
:label-width=
"100"
inline
class=
"info-form"
>
<FormItem
label=
"姓名"
>
<Input
:value=
"detail.name || '-'"
disabled
style=
"width: 220px"
/>
</FormItem>
<FormItem
label=
"性别"
>
<Input
:value=
"renderSex(detail.sex)"
disabled
style=
"width: 220px"
/>
</FormItem>
<FormItem
label=
"身份证号"
>
<Input
:value=
"detail.card_no || '-'"
disabled
style=
"width: 220px"
/>
</FormItem>
<FormItem
label=
"民族"
>
<Input
:value=
"renderFolk(detail.folk) || '-'"
disabled
style=
"width: 220px"
/>
</FormItem>
<FormItem
label=
"本人联系方式"
>
<Input
:value=
"detail.phone || '-'"
disabled
style=
"width: 220px"
/>
</FormItem>
<FormItem
label=
"现住址"
style=
"width: 100%"
>
<Input
:value=
"`${detail.city || '-'} / ${detail.area || '-'} / ${detail.street || '-'} / ${detail.community || '-'}`"
disabled
/>
</FormItem>
<Form
:label-width=
"120"
inline
class=
"info-form"
>
<FormItem
label=
"姓名"
><Input
:value=
"visitModal.personInfo.name || '-'"
disabled
style=
"width: 220px"
/></FormItem>
<FormItem
label=
"身份证号"
><Input
:value=
"visitModal.personInfo.card_no || '-'"
disabled
style=
"width: 220px"
/></FormItem>
<FormItem
label=
"联系方式"
><Input
:value=
"visitModal.personInfo.phone || '-'"
disabled
style=
"width: 220px"
/></FormItem>
<FormItem
label=
"风险等级"
><Input
:value=
"renderRatingLevel(visitModal.personInfo.rating_level) || '-'"
disabled
style=
"width: 220px"
/></FormItem>
<FormItem
label=
"市(州)"
><Input
:value=
"visitModal.personInfo.city_name || '-'"
disabled
style=
"width: 220px"
/></FormItem>
<FormItem
label=
"县(市、区)"
><Input
:value=
"visitModal.personInfo.area_name || '-'"
disabled
style=
"width: 220px"
/></FormItem>
<FormItem
label=
"街道(乡镇)"
><Input
:value=
"visitModal.personInfo.street_name || '-'"
disabled
style=
"width: 220px"
/></FormItem>
<FormItem
label=
"社区(村)"
><Input
:value=
"visitModal.personInfo.community_name || '-'"
disabled
style=
"width: 220px"
/></FormItem>
</Form>
<h4
class=
"section-title"
>
计划
信息
</h4>
<Form
:label-width=
"100"
inline
class=
"info-form
"
>
<FormItem
label=
"
计划名称"
>
<
Input
:value=
"detail.plan_name || '-'"
disabled
style=
"width: 220px"
/>
</FormItem>
<FormItem
label=
"计划类别"
>
<Input
:value=
"detail.plan_type || '-'"
disabled
style=
"width: 220px"
/>
</FormItem>
<FormItem
label=
"重点人员类别"
>
<Input
:value=
"renderRatingDictType(currentCategory || filters.rating_dict_type)"
disabled
style=
"width: 220px"
/>
<h4
class=
"section-title"
>
回访
信息
</h4>
<Form
ref=
"visitForm"
:model=
"visitModal.form"
:rules=
"visitModal.rules"
:label-width=
"120
"
>
<FormItem
label=
"
回访时间"
prop=
"visit_time"
required
>
<
DatePicker
v-model=
"visitModal.form.visit_time"
type=
"datetime"
format=
"yyyy-MM-dd HH:mm:ss"
placeholder=
"请选择回访时间"
style=
"width: 100%"
/>
</FormItem>
<FormItem
label=
"
风险等级"
>
<Input
:value=
"renderRatingType(detail.record_rating_type || detail.rating_type)"
disabled
style=
"width: 220px
"
/>
<FormItem
label=
"
回访记录"
prop=
"visit_record"
required
>
<Input
type=
"textarea"
:rows=
"4"
v-model=
"visitModal.form.visit_record"
placeholder=
"请输入回访记录
"
/>
</FormItem>
<FormItem
label=
"计划开始日期"
>
<Input
:value=
"formatDate(detail.plan_start_data)"
disabled
style=
"width: 220px"
/>
</FormItem>
<FormItem
label=
"计划结束日期"
>
<Input
:value=
"formatDate(detail.plan_end_data)"
disabled
style=
"width: 220px"
/>
</FormItem>
<FormItem
label=
"剩余天数"
>
<Input
:value=
"renderRemainDays(detail.remain_days)"
disabled
style=
"width: 220px"
/>
</FormItem>
</Form>
<h4
class=
"section-title"
>
填写回访记录
</h4>
<Form
:model=
"visitForm"
ref=
"visitForm"
:label-width=
"100"
:rules=
"visitRules"
>
<FormItem
label=
"回访时间"
prop=
"visit_time"
>
<DatePicker
type=
"datetime"
format=
"yyyy-MM-dd HH:mm:ss"
:value=
"visitForm.visit_time"
@
on-change=
"onVisitTimeChange"
style=
"width: 220px"
placeholder=
"选择时间"
/>
</FormItem>
<FormItem
label=
"回访记录"
prop=
"visit_record"
>
<Input
type=
"textarea"
:rows=
"4"
v-model=
"visitForm.visit_record"
placeholder=
"请输入"
/>
</FormItem>
<FormItem
label=
"回访图片"
>
<FormItem
label=
"上传图片"
>
<Upload
:show-upload-list=
"false"
:before-upload=
"beforeUploadVisitImg"
:format=
"['jpg','jpeg','png']"
accept=
".jpg,.jpeg,.png"
:max-size=
"4096"
>
ref=
"upload"
:action=
"uploadAction"
:headers=
"uploadHeaders"
:on-success=
"handleUploadSuccess"
:on-remove=
"handleUploadRemove"
:on-error=
"handleUploadError"
:before-upload=
"beforeUpload"
:file-list=
"visitModal.form.visit_img_list"
multiple
accept=
"image/*"
>
<Button
icon=
"ios-cloud-upload-outline"
>
上传图片
</Button>
</Upload>
<div
class=
"img-list"
>
<img
v-for=
"(img,idx) in visitForm.imgs"
:key=
"idx"
:src=
"img"
style=
"width:80px;height:80px;margin-right:8px"
/>
</div>
<div
style=
"color: #999; font-size: 12px; margin-top: 8px;"
>
支持多张图片上传,单张图片不超过5MB
</div>
</FormItem>
</Form>
</div>
<div
slot=
"footer"
>
<Button
@
click=
"visitModal.visible=false"
>
取消
</Button>
<Poptip
confirm
title=
"确认保存?"
placement=
"top"
transfer
@
on-ok=
"submitVisit"
>
<Button
type=
"primary"
:loading=
"visitModal.submitting"
>
保存
</Button>
</Poptip>
<Button
type=
"primary"
:loading=
"visitModal.submitting"
:disabled=
"visitModal.loading"
@
click=
"submitVisit"
>
保存
</Button>
</div>
</Modal>
<VisitHistoryModal
ref=
"historyModal"
:rating_dict_type=
"String(currentCategory || filters.rating_dict_type || '')"
:key_person_id=
"historyParams.key_person_id"
:jl_key_person_visit_record_id=
"historyParams.current_record_id"
/>
<!-- 回访历史弹窗 -->
<VisitHistoryModal
ref=
"historyModal"
:rating_dict_type=
"currentRatingDictType"
:key_person_id=
"historyModal.key_person_id"
/>
</div>
</template>
<
script
>
import
{
findVisitRecordList
,
getVisitRecordDetail
,
insertVisitRecord
,
getVisitButtonPower
,
getPendingStats
}
from
'@/api/key-person'
import
{
getCardNoById
,
uploadFile
,
selectCityList
}
from
'@/api/contradiction'
import
{
getDictList
}
from
'@/api/common'
import
{
findMZList
}
from
'@/api/peopleInfo'
import
{
mapState
}
from
'vuex'
import
{
selectCityList
}
from
'@/api/contradiction'
import
{
getDictList
,
uploadPic
}
from
'@/api/common'
import
{
findVisitRecordList
,
getPersonInfoForVisit
,
saveVisitRecord
,
selectCurrentUserInfo
}
from
'@/api/key-person'
import
VisitHistoryModal
from
'./components/VisitHistoryModal.vue'
const
createVisitForm
=
()
=>
({
key_person_id
:
''
,
key_person_visit_id
:
''
,
rating_dict_type
:
''
,
sys_type_office_label
:
''
,
sys_type_office_value
:
''
,
plan_name
:
''
,
plan_type
:
''
,
rating_type
:
''
,
visit_time
:
''
,
visit_record
:
''
,
visit_img_list
:
[]
})
// 身份证号统一转码(移除空白并大写),避免查询条件失效
const
normalizeCardNo
=
(
val
)
=>
{
if
(
!
val
)
return
''
const
cleaned
=
String
(
val
).
replace
(
/
\s
+/g
,
''
).
trim
()
if
(
!
cleaned
)
return
''
return
cleaned
.
toUpperCase
()
}
export
default
{
name
:
'key-person-visit-record-index'
,
props
:
{
category
:
{
type
:
String
,
default
:
''
}
},
components
:
{
VisitHistoryModal
},
props
:
{
// 重点人员类别(rating_dict_type),对应sys_type
category
:
{
type
:
String
,
default
:
''
}
},
data
()
{
return
{
tableLoading
:
false
,
tableData
:
[],
cardNoCache
:
{},
cityList
:
[],
areaList
:
[],
streetList
:
[],
communityList
:
[],
ratingTypeOptions
:
[],
mzList
:
[],
nationMap
:
{},
ratingDictTypeOptions
:
[
{
label
:
'刑满释放人员'
,
value
:
'2'
},
{
label
:
'严重精神障碍患者'
,
value
:
'3'
},
{
label
:
'重点未成年人'
,
value
:
'6'
}
],
filters
:
{
rating_dict_type
:
''
,
name
:
''
,
card_no
:
''
,
city_id
:
''
,
area_id
:
''
,
street_id
:
''
,
community_id
:
''
,
do_status
:
'0'
,
rating_type
:
''
,
is_overdue
:
''
rating_level
:
''
,
visit_status
:
''
},
params
:
{
pageNo
:
1
,
pageSize
:
10
,
totalRecord
:
0
},
statsPending
:
{
normal
:
0
,
nearDue
:
0
},
tableColumns
:
[
{
type
:
'index'
,
title
:
'序号'
,
align
:
'center'
,
width
:
80
},
{
title
:
'市(州)'
,
key
:
'city_name'
,
align
:
'center'
,
width
:
100
},
{
title
:
'县(市、区)'
,
key
:
'area_name'
,
align
:
'center'
,
width
:
100
},
{
title
:
'街道(乡镇)'
,
key
:
'street_name'
,
align
:
'center'
,
width
:
120
},
{
title
:
'社区(村)'
,
key
:
'community_name'
,
align
:
'center'
,
width
:
120
},
{
title
:
'姓名'
,
key
:
'name'
,
align
:
'center'
,
width
:
100
},
{
title
:
'身份证号'
,
key
:
'card_no'
,
align
:
'center'
,
width
:
220
,
render
:
(
h
,
{
row
})
=>
h
(
'span'
,
this
.
getDecryptedCardNo
(
row
.
key_person_id
,
row
.
card_no
))
},
{
title
:
'联系方式'
,
key
:
'phone'
,
align
:
'center'
,
width
:
180
},
{
title
:
'计划开始日期'
,
key
:
'plan_start_data'
,
align
:
'center'
,
width
:
140
,
render
:
(
h
,
{
row
})
=>
h
(
'span'
,
this
.
formatDate
(
row
.
plan_start_data
))
},
{
title
:
'计划结束日期'
,
key
:
'plan_end_data'
,
align
:
'center'
,
width
:
140
,
render
:
(
h
,
{
row
})
=>
h
(
'span'
,
this
.
formatDate
(
row
.
plan_end_data
))
},
{
title
:
'剩余办理天数'
,
key
:
'remain_days'
,
align
:
'center'
,
width
:
120
,
render
:
(
h
,
{
row
})
=>
h
(
'span'
,
this
.
renderRemainDays
(
row
.
remain_days
))
},
{
title
:
'是否临期'
,
key
:
'is_overdue'
,
align
:
'center'
,
width
:
100
,
render
:
(
h
,
{
row
})
=>
h
(
'span'
,
(
String
(
row
.
is_overdue
)
===
'1'
)
?
'是'
:
'否'
)
},
{
title
:
'风险等级'
,
key
:
'rating_type'
,
align
:
'center'
,
width
:
120
,
render
:
(
h
,
{
row
})
=>
h
(
'span'
,
this
.
renderRatingType
(
row
.
rating_type
))
},
{
title
:
'办理状态'
,
key
:
'do_status'
,
align
:
'center'
,
width
:
120
,
render
:
(
h
,
{
row
})
=>
h
(
'span'
,
this
.
renderDoStatus
(
row
.
do_status
))
},
{
title
:
'操作'
,
slot
:
'action'
,
align
:
'center'
,
width
:
200
,
fixed
:
'right'
}
cityOptions
:
[],
areaState
:
{
areaList
:
[],
streetList
:
[],
communityList
:
[]
},
ratingLevelOptions
:
[],
visitStatusOptions
:
[],
loading
:
false
,
tableData
:
[],
columns
:
[
{
type
:
'index'
,
title
:
'序号'
,
width
:
60
,
align
:
'center'
},
{
title
:
'市(州)'
,
key
:
'city_name'
,
align
:
'center'
},
{
title
:
'县(市、区)'
,
key
:
'area_name'
,
align
:
'center'
},
{
title
:
'街道(乡镇)'
,
key
:
'street_name'
,
align
:
'center'
},
{
title
:
'社区(村)'
,
key
:
'community_name'
,
align
:
'center'
},
{
title
:
'姓名'
,
key
:
'name'
,
align
:
'center'
},
{
title
:
'身份证号'
,
key
:
'card_no'
,
align
:
'center'
},
{
title
:
'联系方式'
,
key
:
'phone'
,
align
:
'center'
},
{
title
:
'风险等级'
,
key
:
'rating_level'
,
align
:
'center'
,
slot
:
'rating_level'
},
{
title
:
'回访状态'
,
key
:
'visit_status'
,
align
:
'center'
,
slot
:
'visit_status'
},
{
title
:
'操作'
,
key
:
'action'
,
align
:
'center'
,
width
:
180
,
slot
:
'action'
}
],
visitModal
:
{
visible
:
false
,
submitting
:
false
},
historyModal
:
{
visible
:
false
},
historyParams
:
{
pageNo
:
1
,
pageSize
:
10
,
totalRecord
:
0
,
key_person_id
:
''
,
current_record_id
:
''
},
recordModal
:
{
visible
:
false
,
data
:
{}
},
detail
:
{},
visitButtonPower
:
{},
visitForm
:
{
key_person_id
:
''
,
key_person_visit_id
:
''
,
id
:
''
,
visit_time
:
''
,
visit_record
:
''
,
imgs
:
[]
},
visitRules
:
{
visit_time
:
[{
required
:
true
,
message
:
'请选择回访时间'
,
trigger
:
'change'
}],
visit_record
:
[{
required
:
true
,
message
:
'请输入回访记录'
,
trigger
:
'blur'
}]
pager
:
{
pageNo
:
1
,
pageSize
:
10
,
totalRecord
:
0
},
historyData
:
[],
visitHistoryColumns
:
[]
}
},
components
:
{
VisitHistoryModal
},
computed
:
{
...
mapState
({
userInfo
:
state
=>
state
.
user
.
userInfo
||
{}
}),
currentCategory
()
{
const
route
=
this
.
$route
||
{}
const
q
=
(
route
.
query
||
{})
const
p
=
(
route
.
params
||
{})
const
fromRoute
=
q
.
category
||
q
.
rating_dict_type
||
p
.
category
||
p
.
rating_dict_type
||
''
const
fromProp
=
this
.
category
||
''
return
String
(
fromProp
||
fromRoute
||
''
)
},
recordModalImgs
()
{
const
data
=
this
.
recordModal
&&
this
.
recordModal
.
data
?
this
.
recordModal
.
data
:
{}
const
img
=
data
.
img
if
(
!
img
)
return
[]
if
(
Array
.
isArray
(
img
))
return
img
.
filter
(
Boolean
)
if
(
typeof
img
===
'string'
)
return
img
.
split
(
','
).
map
(
s
=>
s
.
trim
()).
filter
(
Boolean
)
return
[]
visitModal
:
{
visible
:
false
,
loading
:
false
,
submitting
:
false
,
personInfo
:
{},
form
:
createVisitForm
(),
rules
:
{
visit_time
:
[{
required
:
true
,
message
:
'请选择回访时间'
,
trigger
:
'change'
}],
visit_record
:
[{
required
:
true
,
message
:
'请输入回访记录'
,
trigger
:
'blur'
}]
}
},
historyModal
:
{
key_person_id
:
''
},
currentRatingDictType
:
''
,
uploadAction
:
'/api/uc/mzsc/uploadService/uploadPic'
,
uploadHeaders
:
{}
}
},
created
()
{
this
.
filters
.
rating_dict_type
=
this
.
currentCategory
||
''
this
.
loadRatingLevelDict
()
// 读取 URL 参数中的 do_status / is_overdue,并赋值到筛选项
this
.
applyUrlFilterParams
(
false
)
this
.
fetchVisitButtonPower
().
then
(()
=>
this
.
init
())
this
.
loadNationDict
()
// 载入市级下拉
selectCityList
({}).
then
((
res
)
=>
{
if
(
res
&&
res
.
data
&&
res
.
data
.
errcode
===
0
)
{
this
.
cityList
=
res
.
data
.
data
||
[]
}
})
},
watch
:
{
category
()
{
this
.
applyDefaultCategory
(
true
)
},
'$route.query.category'
()
{
this
.
applyDefaultCategory
(
true
)
},
'$route.query.rating_dict_type'
()
{
this
.
applyDefaultCategory
(
true
)
},
// 监听 URL 参数变化,同步到筛选项并刷新
'$route.query.do_status'
()
{
this
.
applyUrlFilterParams
(
true
)
},
'$route.query.is_overdue'
()
{
this
.
applyUrlFilterParams
(
true
)
}
this
.
init
()
},
methods
:
{
applyUrlFilterParams
(
triggerInit
)
{
const
q
=
(
this
.
$route
&&
this
.
$route
.
query
)
||
{}
const
hasVal
=
(
v
)
=>
v
!==
undefined
&&
v
!==
null
&&
String
(
v
)
!==
''
let
changed
=
false
if
(
hasVal
(
q
.
do_status
))
{
this
.
filters
.
do_status
=
String
(
q
.
do_status
)
changed
=
true
}
if
(
hasVal
(
q
.
is_overdue
))
{
this
.
filters
.
is_overdue
=
String
(
q
.
is_overdue
)
changed
=
true
}
if
(
triggerInit
&&
changed
)
this
.
changePageNo
(
1
)
async
init
()
{
await
this
.
loadCityOptions
()
await
this
.
loadDicts
()
this
.
loadList
()
},
fetchVisitButtonPower
()
{
const
payload
=
{}
return
getVisitButtonPower
(
payload
).
then
((
ret
)
=>
{
if
(
ret
&&
ret
.
data
&&
ret
.
data
.
errcode
===
0
)
{
this
.
visitButtonPower
=
ret
.
data
.
data
||
{}
}
else
{
this
.
visitButtonPower
=
{}
async
loadCityOptions
()
{
try
{
const
res
=
await
selectCityList
({
parent_id
:
'0'
})
if
(
res
.
data
&&
res
.
data
.
errcode
===
0
)
{
this
.
cityOptions
=
res
.
data
.
data
||
[]
}
}).
catch
(()
=>
{
this
.
visitButtonPower
=
{}
})
},
applyDefaultCategory
(
triggerInit
)
{
const
cat
=
this
.
currentCategory
||
''
this
.
filters
.
rating_dict_type
=
cat
if
(
triggerInit
)
this
.
changePageNo
(
1
)
}
catch
(
e
)
{
console
.
error
(
'加载城市列表失败'
,
e
)
}
},
loadRatingLevelDict
()
{
getDictList
({
type
:
'rating_level'
}).
then
((
ret
)
=>
{
if
(
ret
.
data
&&
ret
.
data
.
errcode
===
0
)
{
const
results
=
ret
.
data
.
data
&&
ret
.
data
.
data
.
results
?
ret
.
data
.
data
.
results
:
[]
this
.
ratingTypeOptions
=
results
.
map
(
it
=>
({
label
:
it
.
label
||
it
.
name
,
value
:
it
.
value
||
it
.
dictValue
}))
async
loadDicts
()
{
// 加载风险等级字典
try
{
const
res
=
await
getDictList
({
type
:
'rating_level'
})
if
(
res
&&
res
.
data
&&
res
.
data
.
errcode
===
0
)
{
const
results
=
res
.
data
.
data
&&
res
.
data
.
data
.
results
?
res
.
data
.
data
.
results
:
[]
this
.
ratingLevelOptions
=
results
.
map
(
it
=>
({
label
:
it
.
label
||
it
.
name
,
value
:
it
.
value
||
it
.
dictValue
}))
}
})
},
async
changeArea
(
level
,
id
)
{
// 清空当前及以下级联的值与列表
const
chain
=
[
''
,
'city_id'
,
'area_id'
,
'street_id'
,
'community_id'
]
const
lists
=
[
''
,
'cityList'
,
'areaList'
,
'streetList'
,
'communityList'
]
const
lv
=
Number
(
level
)
if
(
lv
>
1
&&
!
id
)
{
for
(
let
i
=
lv
;
i
<
chain
.
length
;
i
++
)
{
this
.
$set
(
this
.
filters
,
chain
[
i
],
''
)
this
[
lists
[
i
]]
=
[]
}
catch
(
e
)
{
console
.
error
(
'加载风险等级字典失败'
,
e
)
}
// 加载回访状态字典
try
{
const
res
=
await
getDictList
({
type
:
'visit_status'
})
if
(
res
&&
res
.
data
&&
res
.
data
.
errcode
===
0
)
{
const
results
=
res
.
data
.
data
&&
res
.
data
.
data
.
results
?
res
.
data
.
data
.
results
:
[]
this
.
visitStatusOptions
=
results
.
map
(
it
=>
({
label
:
it
.
label
||
it
.
name
,
value
:
it
.
value
||
it
.
dictValue
}))
}
return
}
catch
(
e
)
{
console
.
error
(
'加载回访状态字典失败'
,
e
)
}
if
(
lv
===
2
)
{
// 选择市 -> 加载区
const
ret
=
await
selectCityList
({
parent_id
:
id
})
const
data
=
(
ret
&&
ret
.
data
&&
ret
.
data
.
data
)
||
[]
this
.
areaList
=
data
// 级联清空下级
},
handleFilterCascade
(
level
,
value
)
{
if
(
level
===
'city'
)
{
this
.
filters
.
city_id
=
value
this
.
filters
.
area_id
=
''
this
.
filters
.
street_id
=
''
this
.
filters
.
community_id
=
''
this
.
streetList
=
[]
this
.
communityList
=
[]
}
else
if
(
lv
===
3
)
{
// 选择区 -> 加载街道
const
ret
=
await
selectCityList
({
parent_id
:
id
})
const
data
=
(
ret
&&
ret
.
data
&&
ret
.
data
.
data
)
||
[]
this
.
streetList
=
data
this
.
areaState
.
areaList
=
[]
this
.
areaState
.
streetList
=
[]
this
.
areaState
.
communityList
=
[]
if
(
!
value
)
return
selectCityList
({
parent_id
:
value
}).
then
(
res
=>
{
if
(
res
.
data
&&
res
.
data
.
errcode
===
0
)
{
this
.
areaState
.
areaList
=
res
.
data
.
data
||
[]
}
})
}
else
if
(
level
===
'area'
)
{
this
.
filters
.
area_id
=
value
this
.
filters
.
street_id
=
''
this
.
filters
.
community_id
=
''
this
.
communityList
=
[]
}
else
if
(
lv
===
4
)
{
// 选择街道 -> 加载社区
const
ret
=
await
selectCityList
({
parent_id
:
id
})
const
data
=
(
ret
&&
ret
.
data
&&
ret
.
data
.
data
)
||
[]
this
.
communityList
=
data
this
.
areaState
.
streetList
=
[]
this
.
areaState
.
communityList
=
[]
if
(
!
value
)
return
selectCityList
({
parent_id
:
value
}).
then
(
res
=>
{
if
(
res
.
data
&&
res
.
data
.
errcode
===
0
)
{
this
.
areaState
.
streetList
=
res
.
data
.
data
||
[]
}
})
}
else
if
(
level
===
'street'
)
{
this
.
filters
.
street_id
=
value
this
.
filters
.
community_id
=
''
this
.
areaState
.
communityList
=
[]
if
(
!
value
)
return
selectCityList
({
parent_id
:
value
}).
then
(
res
=>
{
if
(
res
.
data
&&
res
.
data
.
errcode
===
0
)
{
this
.
areaState
.
communityList
=
res
.
data
.
data
||
[]
}
})
}
},
loadNationDict
()
{
findMZList
().
then
((
ret
)
=>
{
if
(
ret
&&
ret
.
data
&&
ret
.
data
.
errcode
===
0
)
{
const
list
=
ret
.
data
.
data
||
[]
this
.
mzList
=
list
const
map
=
{}
list
.
forEach
(
it
=>
{
if
(
it
&&
(
it
.
mzcode
||
it
.
value
))
{
map
[
String
(
it
.
mzcode
||
it
.
value
)]
=
it
.
mzname
||
it
.
label
||
it
.
name
}
})
this
.
nationMap
=
map
}
})
handleSearch
()
{
this
.
pager
.
pageNo
=
1
this
.
loadList
()
},
init
()
{
this
.
tableLoading
=
true
const
enforced
=
Object
.
assign
({},
this
.
filters
,
{
rating_dict_type
:
this
.
currentCategory
||
this
.
filters
.
rating_dict_type
||
''
})
const
query
=
Object
.
assign
({
params
:
enforced
},
this
.
params
)
findVisitRecordList
(
query
).
then
((
ret
)
=>
{
if
(
ret
.
data
.
errcode
===
0
)
{
this
.
params
.
totalRecord
=
ret
.
data
.
data
.
totalRecord
this
.
tableData
=
ret
.
data
.
data
.
results
// 异步补全身份证号明文(按需加载)
this
.
prefetchCardNos
(
this
.
tableData
)
}
else
{
this
.
$Notice
.
error
({
title
:
'查询失败!'
,
desc
:
ret
.
data
.
errmsg
})
}
}).
finally
(()
=>
{
this
.
tableLoading
=
false
})
// 同步刷新顶部统计:按当前筛选条件统计待办(正常/临期)数量
this
.
fetchPendingStats
(
enforced
)
handleReset
()
{
this
.
filters
=
{
name
:
''
,
card_no
:
''
,
city_id
:
''
,
area_id
:
''
,
street_id
:
''
,
community_id
:
''
,
rating_level
:
''
,
visit_status
:
''
}
this
.
areaState
=
{
areaList
:
[],
streetList
:
[],
communityList
:
[]
}
this
.
pager
.
pageNo
=
1
this
.
loadList
()
},
async
fetchPendingStats
(
baseFilters
)
{
// 新接口一次返回 { normal, nearDue }
const
payload
=
Object
.
assign
({},
baseFilters
,
{
do_status
:
'0'
})
async
loadList
()
{
this
.
loading
=
true
try
{
const
ret
=
await
getPendingStats
(
payload
)
if
(
ret
&&
ret
.
data
&&
ret
.
data
.
errcode
===
0
&&
ret
.
data
.
data
)
{
const
d
=
ret
.
data
.
data
this
.
statsPending
.
normal
=
Number
(
d
.
normal
)
||
0
this
.
statsPending
.
nearDue
=
Number
(
d
.
nearDue
)
||
0
// 调用 selectCurrentUserInfo 来重置用户信息并自动填充 area_id
// 这个 API 后端会调用 DataScopeUtil.resetUser(),根据 company_grade 和 area_id 联合确定匹配哪个字段
let
userInfoParams
=
{}
try
{
const
userInfoRes
=
await
selectCurrentUserInfo
({})
if
(
userInfoRes
&&
userInfoRes
.
data
&&
userInfoRes
.
data
.
errcode
===
0
)
{
userInfoParams
=
userInfoRes
.
data
.
data
||
{}
}
}
catch
(
e
)
{
console
.
error
(
'获取用户信息失败'
,
e
)
// 即使获取用户信息失败,也继续执行查询
}
const
normalizedCardNo
=
normalizeCardNo
(
this
.
filters
.
card_no
)
const
params
=
{
...
this
.
filters
,
card_no
:
normalizedCardNo
,
cardNo
:
normalizedCardNo
,
pageNo
:
this
.
pager
.
pageNo
,
pageSize
:
this
.
pager
.
pageSize
,
// 将重置后的用户信息合并到参数中
...
userInfoParams
}
// 如果传入了category(重点人员类别),则转换为sys_type传给后端
if
(
this
.
category
)
{
params
.
sys_type
=
this
.
category
}
const
res
=
await
findVisitRecordList
(
params
)
if
(
res
&&
res
.
data
&&
res
.
data
.
errcode
===
0
)
{
const
page
=
res
.
data
.
data
||
{
results
:
[],
totalRecord
:
0
}
this
.
tableData
=
page
.
results
||
[]
this
.
pager
.
totalRecord
=
page
.
totalRecord
||
0
}
else
{
this
.
statsPending
.
normal
=
0
this
.
statsPending
.
nearDue
=
0
this
.
$Message
.
error
((
res
.
data
&&
res
.
data
.
errmsg
)
||
'查询失败'
)
this
.
tableData
=
[]
this
.
pager
.
totalRecord
=
0
}
}
catch
(
e
)
{
this
.
statsPending
.
normal
=
0
this
.
statsPending
.
nearDue
=
0
}
},
extractCountFromStatsResponse
(
ret
)
{
if
(
!
ret
||
!
ret
.
data
)
return
0
if
(
ret
.
data
.
errcode
!==
0
)
return
0
const
d
=
ret
.
data
.
data
if
(
d
==
null
)
return
0
// 兼容多种后端返回结构
const
possible
=
[
d
.
count
,
d
.
total
,
d
.
totalRecord
,
d
.
value
,
Array
.
isArray
(
d
.
results
)
?
d
.
results
.
length
:
undefined
]
const
num
=
possible
.
find
(
v
=>
typeof
v
===
'number'
)
if
(
typeof
num
===
'number'
)
return
num
const
strNum
=
possible
.
find
(
v
=>
typeof
v
===
'string'
)
return
Number
(
strNum
)
||
0
},
async
prefetchCardNos
(
rows
)
{
// 批量拉取明文,接口是逐个查;缓存到本地 map,渲染器读取
const
ids
=
(
rows
||
[]).
map
(
r
=>
r
.
key_person_id
).
filter
(
Boolean
)
const
uniq
=
Array
.
from
(
new
Set
(
ids
))
for
(
const
id
of
uniq
)
{
if
(
this
.
cardNoCache
[
id
])
continue
try
{
const
ret
=
await
getCardNoById
({
id
})
if
(
ret
&&
ret
.
data
&&
ret
.
data
.
errcode
===
0
)
{
const
real
=
(
ret
.
data
.
data
&&
ret
.
data
.
data
.
cardno
)
||
''
this
.
$set
(
this
.
cardNoCache
,
id
,
real
)
}
}
catch
(
e
)
{}
console
.
error
(
'查询列表失败'
,
e
)
this
.
$Message
.
error
(
'查询列表失败'
)
this
.
tableData
=
[]
this
.
pager
.
totalRecord
=
0
}
finally
{
this
.
loading
=
false
}
},
getDecryptedCardNo
(
id
,
fallback
)
{
const
cache
=
this
.
cardNoCache
||
{}
if
(
!
id
)
return
fallback
||
'-'
return
cache
[
id
]
||
fallback
||
'-'
pageChange
(
pageNo
)
{
this
.
pager
.
pageNo
=
pageNo
this
.
loadList
()
},
formatDate
(
val
)
{
if
(
!
val
)
return
'-'
const
d
=
new
Date
(
val
)
if
(
Number
.
isNaN
(
d
.
getTime
()))
{
const
m
=
/^
\d{4}
-
\d{2}
-
\d{2}
/
.
exec
(
String
(
val
))
return
m
?
m
[
0
]
:
String
(
val
)
}
const
y
=
d
.
getFullYear
()
const
mm
=
String
(
d
.
getMonth
()
+
1
).
padStart
(
2
,
'0'
)
const
dd
=
String
(
d
.
getDate
()).
padStart
(
2
,
'0'
)
return
`
${
y
}
-
${
mm
}
-
${
dd
}
`
sizeChange
(
pageSize
)
{
this
.
pager
.
pageSize
=
pageSize
this
.
pager
.
pageNo
=
1
this
.
loadList
()
},
formatDateTime
(
val
)
{
renderRatingLevel
(
val
)
{
if
(
!
val
)
return
'-'
const
d
=
new
Date
(
val
)
if
(
Number
.
isNaN
(
d
.
getTime
()))
{
const
m
=
/^
(\d{4}
-
\d{2}
-
\d{2})[
T
]?(\d{2}
:
\d{2}
:
\d{2})?
/
.
exec
(
String
(
val
))
if
(
m
)
{
return
`
${
m
[
1
]}${
m
[
2
]
?
' '
+
m
[
2
]
:
' 00:00:00'
}
`
}
return
String
(
val
)
}
const
y
=
d
.
getFullYear
()
const
mm
=
String
(
d
.
getMonth
()
+
1
).
padStart
(
2
,
'0'
)
const
dd
=
String
(
d
.
getDate
()).
padStart
(
2
,
'0'
)
const
HH
=
String
(
d
.
getHours
()).
padStart
(
2
,
'0'
)
const
MM
=
String
(
d
.
getMinutes
()).
padStart
(
2
,
'0'
)
const
SS
=
String
(
d
.
getSeconds
()).
padStart
(
2
,
'0'
)
return
`
${
y
}
-
${
mm
}
-
${
dd
}
${
HH
}
:
${
MM
}
:
${
SS
}
`
const
found
=
this
.
ratingLevelOptions
.
find
(
it
=>
it
.
value
===
val
)
return
found
?
found
.
label
:
val
},
renderVisitStatus
(
val
)
{
const
s
=
String
(
val
)
if
(
s
===
'2'
)
return
'超期未回访'
if
(
s
===
'1'
)
return
'已回访'
return
'待回访'
},
renderDoStatus
(
val
)
{
const
s
=
String
(
val
)
if
(
s
===
'2'
)
return
'超期未办'
if
(
s
===
'1'
)
return
'已办'
return
'待办'
},
search
()
{
this
.
changePageNo
(
1
)
},
reset
()
{
this
.
filters
=
{
rating_dict_type
:
this
.
currentCategory
||
''
,
name
:
''
,
card_no
:
''
,
city_id
:
''
,
area_id
:
''
,
street_id
:
''
,
community_id
:
''
,
do_status
:
'0'
,
rating_type
:
''
,
is_overdue
:
''
}
this
.
changePageNo
(
1
)
},
size
(
pageSize
)
{
this
.
params
.
pageSize
=
pageSize
;
this
.
changePageNo
(
1
)
},
changePageNo
(
pageNo
)
{
this
.
params
.
pageNo
=
pageNo
;
this
.
init
()
},
sizeHistory
(
pageSize
)
{
this
.
historyParams
.
pageSize
=
pageSize
;
this
.
changeHistoryPageNo
(
1
)
},
changeHistoryPageNo
(
pageNo
)
{
this
.
historyParams
.
pageNo
=
pageNo
;
this
.
loadHistoryPage
()
},
renderSex
(
s
)
{
return
s
===
'1'
?
'男'
:
s
===
'2'
?
'女'
:
'-'
},
renderRatingType
(
val
)
{
const
found
=
this
.
ratingTypeOptions
.
find
(
it
=>
it
.
value
===
val
)
return
found
?
found
.
label
:
val
||
'-'
},
renderRatingDictType
(
val
)
{
const
found
=
this
.
ratingDictTypeOptions
.
find
(
it
=>
String
(
it
.
value
)
===
String
(
val
))
return
found
?
found
.
label
:
val
||
'-'
},
renderFolk
(
val
)
{
if
(
val
===
null
||
val
===
undefined
||
val
===
''
)
return
'-'
const
s
=
String
(
val
)
const
name
=
this
.
nationMap
&&
this
.
nationMap
[
s
]
return
name
||
s
},
renderRemainDays
(
val
)
{
if
(
val
===
null
||
val
===
undefined
||
val
===
''
)
return
'-'
const
n
=
Number
(
val
)
if
(
Number
.
isNaN
(
n
))
return
String
(
val
)
return
String
(
Math
.
max
(
0
,
n
))
},
hasHistory
(
row
)
{
const
s
=
String
(
row
&&
row
.
do_status
)
// 待办(0)、已办(1)、超期未办(2) 都应当可以查看历史
return
s
===
'0'
||
s
===
'1'
||
s
===
'2'
const
found
=
this
.
visitStatusOptions
.
find
(
it
=>
it
.
value
===
s
)
return
found
?
found
.
label
:
(
s
===
'0'
?
'无待访'
:
s
===
'1'
?
'正常待访'
:
s
===
'2'
?
'临期待访'
:
s
===
'3'
?
'超期待访'
:
s
)
},
canShowVisit
(
row
)
{
const
isAdmin
=
(
this
.
userInfo
.
login_name
===
'admin'
)
const
power
=
this
.
visitButtonPower
||
{}
const
type
=
String
(
row
.
sys_type
||
''
)
if
(
isAdmin
)
return
true
if
(
type
===
'2'
)
return
!!
power
[
'rucs_visit'
]
if
(
type
===
'3'
)
return
!!
power
[
'psmd_visit'
]
if
(
type
===
'6'
)
return
!!
power
[
'km_visit'
]
return
false
},
openVisitModal
(
row
)
{
this
.
detail
=
{}
this
.
visitForm
=
{
key_person_id
:
row
.
key_person_id
,
key_person_visit_id
:
row
.
key_person_visit_id
||
row
.
key_person_visit_id
,
id
:
row
.
id
||
row
.
visit_record_id
||
''
,
visit_time
:
''
,
visit_record
:
''
,
imgs
:
[]
}
getVisitRecordDetail
({
id
:
row
.
id
||
row
.
visit_record_id
,
rating_dict_type
:
String
(
row
.
sys_type
||
this
.
currentCategory
||
this
.
filters
.
rating_dict_type
||
''
)
}).
then
((
ret
)
=>
{
if
(
ret
.
data
.
errcode
===
0
)
{
const
d
=
ret
.
data
.
data
||
{}
// 计划信息展示补充:取当前计划记录中的字段
this
.
detail
=
Object
.
assign
({},
d
,
{
record_rating_type
:
(
row
&&
row
.
rating_type
)
||
d
.
rating_type
,
plan_name
:
(
row
&&
row
.
plan_name
)
||
d
.
plan_name
,
plan_type
:
(
row
&&
row
.
plan_type
)
||
d
.
plan_type
,
plan_start_data
:
(
row
&&
row
.
plan_start_data
)
||
d
.
plan_start_data
,
plan_end_data
:
(
row
&&
row
.
plan_end_data
)
||
d
.
plan_end_data
,
remain_days
:
(
row
&&
row
.
remain_days
)
||
d
.
remain_days
})
this
.
visitForm
.
key_person_visit_id
=
this
.
detail
.
key_person_visit_id
||
this
.
visitForm
.
key_person_visit_id
this
.
visitForm
.
id
=
this
.
detail
.
id
||
this
.
visitForm
.
id
||
this
.
detail
.
current_visit_record_id
// 历史回访信息
this
.
historyData
=
((
this
.
detail
.
history
)
||
[]).
map
(
it
=>
({
rating_type
:
it
.
rating_type
,
visit_user
:
it
.
visit_user
,
visit_user_company
:
it
.
visit_user_company
,
visit_time
:
it
.
visit_time
,
visit_record
:
it
.
visit_record
,
img
:
it
.
img
,
do_status
:
it
.
do_status
}))
this
.
visitModal
.
visible
=
true
this
.
$nextTick
(()
=>
{
this
.
$refs
.
visitForm
&&
this
.
$refs
.
visitForm
.
clearValidate
()
})
async
openVisitModal
(
row
)
{
this
.
visitModal
.
visible
=
true
this
.
visitModal
.
loading
=
true
const
nextForm
=
createVisitForm
()
nextForm
.
key_person_id
=
row
.
person_id
nextForm
.
key_person_visit_id
=
row
.
key_person_visit_id
||
row
.
visit_plan_id
||
''
nextForm
.
rating_dict_type
=
row
.
rating_dict_type
nextForm
.
sys_type_office_label
=
row
.
sys_type_office_label
||
''
nextForm
.
sys_type_office_value
=
row
.
sys_type_office_value
||
''
nextForm
.
plan_name
=
row
.
plan_name
||
''
nextForm
.
plan_type
=
row
.
plan_type
||
''
nextForm
.
rating_type
=
row
.
plan_rating_type
||
row
.
rating_level
||
''
nextForm
.
visit_time
=
this
.
formatDateTime
(
new
Date
())
this
.
$set
(
this
.
visitModal
,
'form'
,
nextForm
)
this
.
currentRatingDictType
=
row
.
rating_dict_type
||
''
try
{
const
visitParams
=
{
person_id
:
row
.
person_id
,
rating_dict_type
:
row
.
rating_dict_type
}
// 如果传入了category(重点人员类别),则转换为sys_type传给后端
if
(
this
.
category
)
{
visitParams
.
sys_type
=
this
.
category
}
else
if
(
row
.
sys_type
)
{
// 如果row中有sys_type,也传入
visitParams
.
sys_type
=
row
.
sys_type
}
const
res
=
await
getPersonInfoForVisit
(
visitParams
)
if
(
res
&&
res
.
data
&&
res
.
data
.
errcode
===
0
)
{
this
.
visitModal
.
personInfo
=
res
.
data
.
data
||
{}
}
else
{
this
.
$
Notice
.
error
({
title
:
'加载详情失败'
,
desc
:
ret
.
data
.
errmsg
}
)
this
.
$
Message
.
error
((
res
.
data
&&
res
.
data
.
errmsg
)
||
'获取人员信息失败'
)
}
}
)
},
openHistoryModal
(
row
)
{
// 通过回访记录主键展示:显示本次回访信息 + 历史按现有逻辑
this
.
historyParams
.
key_person_id
=
row
.
key_person_id
this
.
historyParams
.
current_record_id
=
row
.
id
||
row
.
visit_record_id
this
.
$nextTick
(()
=>
{
this
.
$refs
.
historyModal
&&
this
.
$refs
.
historyModal
.
open
()
})
}
catch
(
e
)
{
console
.
error
(
'获取人员信息失败'
,
e
)
this
.
$Message
.
error
(
'获取人员信息失败'
)
}
finally
{
this
.
visitModal
.
loading
=
false
this
.
$nextTick
(
this
.
clearVisitFormValidate
)
}
},
async
beforeUploadVisitImg
(
file
)
{
const
name
=
(
file
&&
file
.
name
)
||
''
const
lower
=
name
.
toLowerCase
()
const
validExt
=
/
(\.
jpg|
\.
jpeg|
\.
png
)
$/i
.
test
(
lower
)
const
validMime
=
file
&&
(
file
.
type
===
'image/jpeg'
||
file
.
type
===
'image/png'
)
if
(
!
(
validExt
||
validMime
))
{
this
.
$Message
.
error
(
'仅支持 jpg/jpeg/png 格式图片'
)
beforeUpload
(
file
)
{
const
isValidType
=
file
.
type
.
startsWith
(
'image/'
)
const
isLt5M
=
file
.
size
/
1024
/
1024
<
5
if
(
!
isValidType
)
{
this
.
$Message
.
error
(
'只能上传图片文件!'
)
return
false
}
if
(
!
isLt5M
)
{
this
.
$Message
.
error
(
'图片大小不能超过5MB!'
)
return
false
}
// 本地预览占位
let
previewIndex
=
-
1
// 使用自定义上传
this
.
uploadFile
(
file
)
return
false
},
async
uploadFile
(
file
)
{
const
formData
=
new
FormData
()
formData
.
append
(
'file'
,
file
)
try
{
await
new
Promise
((
resolve
)
=>
{
const
reader
=
new
FileReader
()
reader
.
onload
=
(
e
)
=>
{
this
.
visitForm
.
imgs
.
push
(
e
.
target
.
result
)
previewIndex
=
this
.
visitForm
.
imgs
.
length
-
1
resolve
()
const
res
=
await
uploadPic
(
formData
)
if
(
res
&&
res
.
data
&&
res
.
data
.
errcode
===
0
)
{
const
url
=
res
.
data
.
data
||
res
.
data
.
url
if
(
url
)
{
this
.
visitModal
.
form
.
visit_img_list
.
push
({
name
:
file
.
name
,
url
:
url
,
status
:
'finished'
})
this
.
$Message
.
success
(
'上传成功'
)
}
reader
.
readAsDataURL
(
file
)
})
// 构建上传表单并调用通用上传服务
const
formdata
=
new
FormData
()
formdata
.
append
(
'file'
,
file
)
formdata
.
append
(
'fileName'
,
file
.
name
)
const
meta
=
{
business_type
:
'jlEvent'
}
formdata
.
append
(
'json'
,
JSON
.
stringify
(
meta
))
const
ret
=
await
uploadFile
(
formdata
,
meta
)
if
(
ret
&&
ret
.
data
&&
ret
.
data
.
errcode
===
0
&&
ret
.
data
.
data
)
{
const
d
=
ret
.
data
.
data
const
url
=
d
.
url
||
(
`/api/ac/jilinsscgsdp/uploadService/getObsObject?uuid=
${
d
.
uuid
}
&&suffix=
${
d
.
suffix
}
`
)
this
.
$set
(
this
.
visitForm
.
imgs
,
previewIndex
,
url
)
}
else
{
// 失败回滚预览
if
(
previewIndex
>
-
1
)
this
.
visitForm
.
imgs
.
splice
(
previewIndex
,
1
)
this
.
$Notice
.
error
({
title
:
'上传失败'
,
desc
:
(
ret
&&
ret
.
data
&&
ret
.
data
.
errmsg
)
||
''
})
this
.
$Message
.
error
((
res
.
data
&&
res
.
data
.
errmsg
)
||
'上传失败'
)
}
}
catch
(
e
)
{
if
(
previewIndex
>
-
1
)
this
.
visitForm
.
imgs
.
splice
(
previewIndex
,
1
)
this
.
$
Notice
.
error
({
title
:
'上传失败'
}
)
console
.
error
(
'上传失败'
,
e
)
this
.
$
Message
.
error
(
'上传失败'
)
}
// 阻止组件默认上传
return
false
},
submitVisit
()
{
this
.
$refs
.
visitForm
.
validate
((
valid
)
=>
{
if
(
!
valid
)
return
this
.
visitModal
.
submitting
=
true
if
(
!
this
.
visitForm
.
id
)
{
this
.
$Notice
.
error
({
title
:
'保存失败'
,
desc
:
'缺少回访记录主键,请刷新后重试'
})
this
.
visitModal
.
submitting
=
false
return
handleUploadSuccess
(
response
,
file
,
fileList
)
{
// 不使用默认上传,已在beforeUpload中处理
},
handleUploadRemove
(
file
,
fileList
)
{
this
.
visitModal
.
form
.
visit_img_list
=
fileList
},
handleUploadError
(
error
,
file
,
fileList
)
{
console
.
error
(
'图片上传失败'
,
error
)
const
message
=
(
error
&&
(
error
.
message
||
error
.
errMsg
))
||
'图片上传失败'
this
.
$Message
.
error
(
message
)
},
async
submitVisit
()
{
const
formRef
=
this
.
$refs
.
visitForm
if
(
!
formRef
)
{
this
.
$Message
.
warning
(
'表单尚未加载完成,请稍后再试'
)
return
}
formRef
.
validate
(
async
(
valid
)
=>
{
console
.
log
(
'submitVisit'
,
valid
)
if
(
!
valid
)
{
return
false
}
const
visitTimeStr
=
this
.
normalizeVisitTimeValue
(
this
.
visitModal
.
form
.
visit_time
)
if
(
!
visitTimeStr
)
{
this
.
$Message
.
error
(
'请选择有效的回访时间'
)
return
false
}
const
payload
=
{
key_person_id
:
this
.
visitForm
.
key_person_id
,
key_person_visit_id
:
this
.
visitForm
.
key_person_visit_id
,
id
:
this
.
visitForm
.
id
,
plan_name
:
this
.
detail
.
plan_name
,
plan_type
:
this
.
detail
.
plan_type
,
rating_type
:
this
.
detail
.
record_rating_type
||
this
.
detail
.
rating_type
,
plan_start_data
:
this
.
detail
.
plan_start_data
,
plan_end_data
:
this
.
detail
.
plan_end_data
,
visit_time
:
this
.
visitForm
.
visit_time
,
visit_record
:
this
.
visitForm
.
visit_record
,
img
:
(
this
.
visitForm
.
imgs
||
[]).
join
(
','
)
const
visitRecord
=
(
this
.
visitModal
.
form
.
visit_record
||
''
).
trim
()
if
(
!
visitRecord
)
{
this
.
$Message
.
error
(
'请输入回访记录'
)
return
false
}
insertVisitRecord
(
payload
).
then
((
ret
)
=>
{
if
(
ret
.
data
&&
ret
.
data
.
errcode
===
0
)
{
this
.
$Notice
.
success
({
title
:
'保存成功'
})
this
.
visitModal
.
form
.
visit_record
=
visitRecord
this
.
visitModal
.
submitting
=
true
try
{
const
visitImg
=
this
.
visitModal
.
form
.
visit_img_list
.
map
(
item
=>
item
.
url
||
(
item
.
response
&&
item
.
response
.
data
&&
item
.
response
.
data
.
url
)
||
(
item
.
response
&&
item
.
response
.
data
)).
filter
(
Boolean
).
join
(
','
)
const
params
=
{
key_person_id
:
this
.
visitModal
.
form
.
key_person_id
,
key_person_visit_id
:
this
.
visitModal
.
form
.
key_person_visit_id
,
rating_dict_type
:
this
.
visitModal
.
form
.
rating_dict_type
,
sys_type_office_label
:
this
.
visitModal
.
form
.
sys_type_office_label
,
sys_type_office_value
:
this
.
visitModal
.
form
.
sys_type_office_value
,
plan_name
:
this
.
visitModal
.
form
.
plan_name
,
plan_type
:
this
.
visitModal
.
form
.
plan_type
,
rating_type
:
this
.
visitModal
.
form
.
rating_type
,
visit_time
:
visitTimeStr
,
visit_record
:
this
.
visitModal
.
form
.
visit_record
,
visit_img
:
visitImg
}
const
res
=
await
saveVisitRecord
(
params
)
if
(
res
&&
res
.
data
&&
res
.
data
.
errcode
===
0
)
{
this
.
$Message
.
success
(
'保存成功'
)
this
.
visitModal
.
visible
=
false
this
.
ini
t
()
this
.
loadLis
t
()
}
else
{
this
.
$
Notice
.
error
({
title
:
'保存失败'
,
desc
:
(
ret
&&
ret
.
data
&&
ret
.
data
.
errmsg
)
||
''
}
)
this
.
$
Message
.
error
((
res
.
data
&&
res
.
data
.
errmsg
)
||
'保存失败'
)
}
}).
finally
(()
=>
{
this
.
visitModal
.
submitting
=
false
})
}
catch
(
e
)
{
console
.
error
(
'保存回访记录失败'
,
e
)
this
.
$Message
.
error
(
'保存回访记录失败'
)
}
finally
{
this
.
visitModal
.
submitting
=
false
}
})
},
openHistoryModal
(
row
)
{
this
.
historyModal
.
key_person_id
=
row
.
person_id
this
.
currentRatingDictType
=
row
.
rating_dict_type
||
''
// 等待 props 更新到子组件后再打开弹窗,避免首次点击时 key_person_id 还是旧值
this
.
$nextTick
(()
=>
{
if
(
this
.
$refs
.
historyModal
&&
typeof
this
.
$refs
.
historyModal
.
open
===
'function'
)
{
this
.
$refs
.
historyModal
.
open
()
}
})
},
onVisitTimeChange
(
val
)
{
// DatePicker on-change returns formatted string per format
this
.
visitForm
.
visit_time
=
val
||
''
// proactively clear error once value selected
if
(
val
&&
this
.
$refs
.
visitForm
)
this
.
$refs
.
visitForm
.
validateField
(
'visit_time'
)
formatDateTime
(
date
)
{
if
(
!
date
)
return
''
const
d
=
date
instanceof
Date
?
new
Date
(
date
.
getTime
())
:
new
Date
(
date
)
if
(
Number
.
isNaN
(
d
.
getTime
()))
return
String
(
date
)
return
this
.
formatDateSegments
(
d
)
},
formatDateSegments
(
d
)
{
const
y
=
d
.
getFullYear
()
const
mm
=
String
(
d
.
getMonth
()
+
1
).
padStart
(
2
,
'0'
)
const
dd
=
String
(
d
.
getDate
()).
padStart
(
2
,
'0'
)
const
HH
=
String
(
d
.
getHours
()).
padStart
(
2
,
'0'
)
const
MM
=
String
(
d
.
getMinutes
()).
padStart
(
2
,
'0'
)
const
SS
=
String
(
d
.
getSeconds
()).
padStart
(
2
,
'0'
)
return
`
${
y
}
-
${
mm
}
-
${
dd
}
${
HH
}
:
${
MM
}
:
${
SS
}
`
},
clearVisitFormValidate
()
{
const
visitFormRef
=
this
.
$refs
.
visitForm
if
(
!
visitFormRef
)
return
if
(
typeof
visitFormRef
.
clearValidate
===
'function'
)
{
visitFormRef
.
clearValidate
()
return
}
if
(
visitFormRef
.
fields
&&
Array
.
isArray
(
visitFormRef
.
fields
))
{
visitFormRef
.
fields
.
forEach
(
field
=>
{
if
(
!
field
)
return
field
.
validateState
=
''
field
.
validateMessage
=
''
})
}
},
normalizeVisitTimeValue
(
value
)
{
if
(
!
value
&&
value
!==
0
)
return
''
if
(
value
instanceof
Date
)
{
return
Number
.
isNaN
(
value
.
getTime
())
?
''
:
this
.
formatDateSegments
(
value
)
}
if
(
typeof
value
===
'number'
&&
!
Number
.
isNaN
(
value
))
{
const
fromNumber
=
new
Date
(
value
)
return
Number
.
isNaN
(
fromNumber
.
getTime
())
?
''
:
this
.
formatDateSegments
(
fromNumber
)
}
if
(
typeof
value
===
'string'
)
{
const
trimmed
=
value
.
trim
()
if
(
!
trimmed
)
return
''
const
direct
=
new
Date
(
trimmed
)
if
(
!
Number
.
isNaN
(
direct
.
getTime
()))
return
this
.
formatDateSegments
(
direct
)
const
withSlash
=
new
Date
(
trimmed
.
replace
(
/-/g
,
'/'
))
if
(
!
Number
.
isNaN
(
withSlash
.
getTime
()))
return
this
.
formatDateSegments
(
withSlash
)
const
match
=
trimmed
.
match
(
/^
(\d{4})
-
(\d{2})
-
(\d{2})(?:[
T
](\d{2})
:
(\d{2})(?:
:
(\d{2}))?)?
$/
)
if
(
match
)
{
const
[,
y
,
m
,
d
,
hh
=
'00'
,
mm
=
'00'
,
ss
=
'00'
]
=
match
return
`
${
y
}
-
${
m
}
-
${
d
}
${
hh
}
:
${
mm
}
:
${
ss
}
`
}
return
''
}
if
(
value
&&
typeof
value
===
'object'
&&
typeof
value
.
valueOf
===
'function'
)
{
const
timestamp
=
value
.
valueOf
()
if
(
!
Number
.
isNaN
(
timestamp
))
{
const
fromValue
=
new
Date
(
timestamp
)
return
Number
.
isNaN
(
fromValue
.
getTime
())
?
''
:
this
.
formatDateSegments
(
fromValue
)
}
}
return
''
}
}
}
</
script
>
<
style
scoped
>
.contradiction-div
{
padding
:
10px
;
}
.contradiction-div
.search-div
{
width
:
100%
;
padding
:
15px
;
border
:
1px
solid
skyblue
;
background-color
:
azure
;
margin-bottom
:
15px
;
}
.page_style
{
margin-top
:
10px
;
text-align
:
right
;
}
.grid
{
display
:
grid
;
grid-template-columns
:
repeat
(
4
,
1
fr
);
grid-gap
:
10px
;
margin-bottom
:
12px
;
}
.section-title
{
margin
:
10px
0
;
font-weight
:
600
;
}
.info-form
{
margin-bottom
:
8px
;
}
.img-list
{
margin-top
:
8px
;
}
::v-deep
.history
table
{
width
:
100%
!important
;
border-collapse
:
collapse
;
.visit-record-wrapper
{
padding
:
16px
;
}
.search-div
{
background
:
#fff
;
padding
:
16px
;
margin-bottom
:
16px
;
}
.mb10
{
margin-bottom
:
10px
;
}
.text-right
{
text-align
:
right
;
}
.page_style
{
margin-top
:
16px
;
text-align
:
right
;
}
.section-title
{
margin
:
16px
0
12px
0
;
font-weight
:
600
;
font-size
:
14px
;
color
:
#333
;
}
.info-form
{
margin-bottom
:
16px
;
}
</
style
>
src/view/key-person/key-person-visit/index.vue
View file @
df7a87f2
...
...
@@ -13,6 +13,12 @@
</Select>
</Col>
<Col
span=
"4"
>
<span>
部门类型:
</span>
<Select
v-model=
"filters.sys_type_office_value"
clearable
style=
"width: 55%"
placeholder=
"请选择"
>
<Option
v-for=
"opt in officeTypeOptions"
:key=
"opt.value"
:value=
"opt.value"
>
{{
opt
.
label
}}
</Option>
</Select>
</Col>
<Col
span=
"4"
>
<span>
启用状态:
</span>
<Select
v-model=
"filters.status"
clearable
style=
"width: 55%"
placeholder=
"请选择"
>
<Option
v-for=
"opt in statusOptions"
:key=
"opt.value"
:value=
"opt.value"
>
{{
opt
.
label
}}
</Option>
...
...
@@ -30,6 +36,9 @@
<template
slot-scope=
"
{ row }" slot="rating_dict_type">
<span>
{{
renderRatingDictType
(
row
.
rating_dict_type
)
}}
</span>
</
template
>
<
template
slot-scope=
"{ row }"
slot=
"sys_type_office_label"
>
<span>
{{
row
.
sys_type_office_label
||
'-'
}}
</span>
</
template
>
<
template
slot-scope=
"{ row }"
slot=
"rating_type"
>
<span>
{{
renderRatingType
(
row
.
rating_type
)
}}
</span>
</
template
>
...
...
@@ -56,12 +65,16 @@
@
on-page-size-change=
"size"
/>
<Modal
v-model=
"planModal.visible"
:title=
"planModal.title"
width=
"520"
>
<Modal
v-model=
"planModal.visible"
:title=
"planModal.title"
width=
"520"
@
on-visible-change=
"handleModalVisibleChange"
>
<Form
:model=
"planModel"
ref=
"planForm"
:rules=
"planRules"
:label-width=
"100"
>
<FormItem
label=
"重点人员类别"
prop=
"rating_dict_type"
>
<Input
:value=
"renderRatingDictType(planModel.rating_dict_type)"
disabled
/>
</FormItem>
<FormItem
label=
"部门类型"
prop=
"sys_type_office_value"
>
<Select
v-model=
"planModel.sys_type_office_value"
placeholder=
"请选择部门类型"
@
on-change=
"handleOfficeTypeChange"
>
<Option
v-for=
"opt in officeTypeOptions"
:key=
"opt.value"
:value=
"opt.value"
>
{{ opt.label }}
</Option>
</Select>
</FormItem>
<FormItem
label=
"计划名称"
prop=
"plan_name"
>
<Input
v-model=
"planModel.plan_name"
placeholder=
"请输入计划名称"
/>
</FormItem>
...
...
@@ -123,6 +136,7 @@ export default {
],
ratingDictTypeMap
:
{},
ratingTypeOptions
:
[],
officeTypeOptions
:
[],
statusOptions
:
[
{
label
:
'启用'
,
value
:
'1'
},
{
label
:
'停用'
,
value
:
'2'
}
...
...
@@ -131,6 +145,7 @@ export default {
rating_dict_type
:
''
,
plan_name
:
''
,
rating_type
:
''
,
sys_type_office_value
:
''
,
status
:
''
},
params
:
{
...
...
@@ -141,6 +156,7 @@ export default {
tableColumns
:
[
{
type
:
'index'
,
title
:
'序号'
,
align
:
'center'
,
width
:
80
},
{
title
:
'重点人员类别'
,
slot
:
'rating_dict_type'
,
align
:
'center'
},
{
title
:
'部门类型'
,
slot
:
'sys_type_office_label'
,
align
:
'center'
,
width
:
120
},
{
title
:
'计划名称'
,
key
:
'plan_name'
,
align
:
'center'
},
{
title
:
'计划类别'
,
key
:
'plan_type'
,
align
:
'center'
},
{
title
:
'风险等级'
,
slot
:
'rating_type'
,
align
:
'center'
,
width
:
120
},
...
...
@@ -157,6 +173,8 @@ export default {
planModel
:
{
id
:
''
,
rating_dict_type
:
''
,
sys_type_office_label
:
''
,
sys_type_office_value
:
''
,
plan_name
:
''
,
plan_type
:
''
,
rating_type
:
''
,
...
...
@@ -167,6 +185,7 @@ export default {
},
planRules
:
{
rating_dict_type
:
[{
required
:
true
,
message
:
'请选择重点人员类别'
,
trigger
:
'change'
}],
sys_type_office_value
:
[{
required
:
true
,
message
:
'请选择部门类型'
,
trigger
:
'change'
}],
plan_name
:
[{
required
:
true
,
message
:
'请输入计划名称'
,
trigger
:
'blur'
}],
rating_type
:
[{
required
:
true
,
message
:
'请选择风险等级'
,
trigger
:
'change'
}],
status
:
[{
required
:
true
,
message
:
'请选择状态'
,
trigger
:
'change'
}],
...
...
@@ -208,6 +227,7 @@ export default {
created
()
{
this
.
filters
.
rating_dict_type
=
this
.
currentCategory
||
''
this
.
loadRatingLevelDict
()
this
.
loadOfficeTypeDict
()
this
.
init
()
},
watch
:
{
...
...
@@ -254,6 +274,25 @@ export default {
}
})
},
loadOfficeTypeDict
()
{
getDictList
({
type
:
'sys_type_office'
}).
then
((
ret
)
=>
{
if
(
ret
.
data
&&
ret
.
data
.
errcode
===
0
)
{
const
results
=
ret
.
data
.
data
&&
ret
.
data
.
data
.
results
?
ret
.
data
.
data
.
results
:
[]
// ensure structure: [{ label, value }]
this
.
officeTypeOptions
=
results
.
map
(
it
=>
({
label
:
it
.
label
||
it
.
name
,
value
:
it
.
value
||
it
.
dictValue
}))
}
else
{
this
.
$Notice
.
error
({
title
:
'加载部门类型失败!'
,
desc
:
(
ret
.
data
&&
ret
.
data
.
errmsg
)
||
''
})
}
})
},
handleOfficeTypeChange
(
value
)
{
const
selected
=
this
.
officeTypeOptions
.
find
(
it
=>
it
.
value
===
value
)
if
(
selected
)
{
this
.
planModel
.
sys_type_office_label
=
selected
.
label
}
else
{
this
.
planModel
.
sys_type_office_label
=
''
}
},
search
()
{
this
.
changePageNo
(
1
)
},
...
...
@@ -261,6 +300,7 @@ export default {
this
.
filters
.
rating_dict_type
=
this
.
currentCategory
||
''
this
.
filters
.
plan_name
=
''
this
.
filters
.
rating_type
=
''
this
.
filters
.
sys_type_office_value
=
''
this
.
filters
.
status
=
''
this
.
changePageNo
(
1
)
},
...
...
@@ -284,17 +324,54 @@ export default {
const
found
=
this
.
statusOptions
.
find
(
it
=>
it
.
value
===
val
)
return
found
?
found
.
label
:
val
||
'-'
},
handleModalVisibleChange
(
visible
)
{
if
(
visible
)
{
// 弹窗打开时,清除所有验证状态(字段值已在 openAddModal/openEditModal 中设置)
this
.
$nextTick
(()
=>
{
if
(
this
.
$refs
.
planForm
&&
typeof
this
.
$refs
.
planForm
.
resetFields
===
'function'
)
{
// iView Form 的 resetFields 会清除验证状态,但会重置字段值
// 为了不影响编辑模式的数据,这里只清除验证提示(通过移除错误类)
const
formItems
=
this
.
$refs
.
planForm
.
$el
.
querySelectorAll
(
'.ivu-form-item-error'
)
formItems
.
forEach
(
item
=>
{
item
.
classList
.
remove
(
'ivu-form-item-error'
)
})
}
})
}
},
openAddModal
()
{
this
.
planModal
.
title
=
'新增回访计划'
this
.
planModel
=
{
id
:
''
,
rating_dict_type
:
this
.
currentCategory
||
''
,
plan_name
:
''
,
plan_type
:
''
,
rating_type
:
''
,
status
:
'2'
,
visit_start_day
:
null
,
visit_end_day
:
null
,
overdue_warning_day
:
null
}
// 确保从路由参数或props中获取最新的类别值(重新计算以确保获取最新值)
const
route
=
this
.
$route
||
{}
const
q
=
(
route
.
query
||
{})
const
p
=
(
route
.
params
||
{})
const
fromRoute
=
q
.
category
||
q
.
rating_dict_type
||
p
.
category
||
p
.
rating_dict_type
||
''
const
fromProp
=
this
.
category
||
''
const
category
=
String
(
fromProp
||
fromRoute
||
''
)
// 先重置表单数据,确保重点人员类别有值
this
.
planModel
=
{
id
:
''
,
rating_dict_type
:
category
,
sys_type_office_label
:
''
,
sys_type_office_value
:
''
,
plan_name
:
''
,
plan_type
:
''
,
rating_type
:
''
,
status
:
'2'
,
visit_start_day
:
null
,
visit_end_day
:
null
,
overdue_warning_day
:
null
}
// 打开弹窗
this
.
planModal
.
visible
=
true
this
.
$nextTick
(()
=>
this
.
$refs
.
planForm
&&
this
.
$refs
.
planForm
.
clearValidate
())
// 打开弹窗后清除验证,但保留重点人员类别值
this
.
$nextTick
(()
=>
{
if
(
this
.
$refs
.
planForm
&&
typeof
this
.
$refs
.
planForm
.
resetFields
===
'function'
)
{
// 保存当前的重点人员类别值
const
savedCategory
=
this
.
planModel
.
rating_dict_type
// 清除验证状态(resetFields 会重置字段值,所以需要重新设置)
this
.
$refs
.
planForm
.
resetFields
()
// 重新设置重点人员类别,确保显示正确
this
.
planModel
.
rating_dict_type
=
savedCategory
||
category
}
})
},
openEditModal
(
row
)
{
this
.
planModal
.
title
=
'修改回访计划'
// 先设置表单数据
this
.
planModel
=
{
id
:
row
.
id
,
rating_dict_type
:
row
.
rating_dict_type
,
sys_type_office_label
:
row
.
sys_type_office_label
||
''
,
sys_type_office_value
:
row
.
sys_type_office_value
||
''
,
plan_name
:
row
.
plan_name
,
plan_type
:
row
.
plan_type
,
rating_type
:
row
.
rating_type
,
...
...
@@ -303,8 +380,19 @@ export default {
visit_end_day
:
row
.
visit_end_day
,
overdue_warning_day
:
row
.
overdue_warning_day
}
// 打开弹窗前先清除验证(编辑模式不清除字段值,只清除验证状态)
this
.
$nextTick
(()
=>
{
if
(
this
.
$refs
.
planForm
&&
this
.
$refs
.
planForm
.
$el
)
{
// 手动清除验证提示(移除错误类)
const
formItems
=
this
.
$refs
.
planForm
.
$el
.
querySelectorAll
(
'.ivu-form-item-error'
)
if
(
formItems
&&
formItems
.
length
>
0
)
{
formItems
.
forEach
(
item
=>
{
item
.
classList
.
remove
(
'ivu-form-item-error'
)
})
}
}
})
this
.
planModal
.
visible
=
true
this
.
$nextTick
(()
=>
this
.
$refs
.
planForm
&&
this
.
$refs
.
planForm
.
clearValidate
())
},
submitPlan
()
{
this
.
$refs
[
'planForm'
].
validate
((
valid
)
=>
{
...
...
@@ -324,28 +412,29 @@ export default {
this
.
planModal
.
submitting
=
false
})
}
// 若设置为启用,需保证同一风险等级仅存在一个启用配置
// 若设置为启用,需保证同一部门、同一类别、同一风险等级仅存在一个启用配置
// 注意:允许同一组合存在多条记录,但只能有一条启用状态
if
(
String
(
this
.
planModel
.
status
)
===
'1'
)
{
const
p
arams
=
{
const
enabledP
arams
=
{
params
:
{
rating_dict_type
:
this
.
planModel
.
rating_dict_type
||
this
.
currentCategory
||
''
,
rating_type
:
this
.
planModel
.
rating_type
,
sys_type_office_value
:
this
.
planModel
.
sys_type_office_value
,
status
:
'1'
},
pageNo
:
1
,
pageSize
:
1
}
findPersonVisitList
(
params
).
then
((
ret
)
=>
{
if
(
ret
&&
ret
.
data
&&
ret
.
data
.
errcode
===
0
)
{
const
list
=
(
ret
.
data
.
data
&&
ret
.
data
.
data
.
results
)
||
[]
const
e
xist
=
l
ist
.
find
(
it
=>
String
(
it
.
id
)
!==
String
(
this
.
planModel
.
id
))
if
(
exist
)
{
this
.
$Notice
.
error
({
title
:
'保存失败!'
,
desc
:
'同一风险等级已存在启用的回访计划,请先停用后再新增/启用。'
})
findPersonVisitList
(
enabledParams
).
then
((
ret2
)
=>
{
if
(
ret
2
&&
ret2
.
data
&&
ret2
.
data
.
errcode
===
0
)
{
const
enabledList
=
(
ret2
.
data
.
data
&&
ret2
.
data
.
data
.
results
)
||
[]
const
e
nabledExist
=
enabledL
ist
.
find
(
it
=>
String
(
it
.
id
)
!==
String
(
this
.
planModel
.
id
))
if
(
e
nabledE
xist
)
{
this
.
$Notice
.
error
({
title
:
'保存失败!'
,
desc
:
'同一
部门、同一类别、同一
风险等级已存在启用的回访计划,请先停用后再新增/启用。'
})
return
}
doSave
()
}
else
{
// 查询失败时,兜底允许保存,避免误伤;后端仍可校验
doSave
()
}
}).
catch
(()
=>
{
doSave
()
})
...
...
@@ -355,10 +444,15 @@ export default {
})
},
toggleStatus
(
row
,
status
)
{
// 若要启用,需确保同一风险等级没有其他启用项
// 若要启用,需确保同一
部门、同一类别、同一
风险等级没有其他启用项
if
(
String
(
status
)
===
'1'
)
{
const
params
=
{
params
:
{
rating_dict_type
:
row
.
rating_dict_type
,
rating_type
:
row
.
rating_type
,
status
:
'1'
},
params
:
{
rating_dict_type
:
row
.
rating_dict_type
,
rating_type
:
row
.
rating_type
,
sys_type_office_value
:
row
.
sys_type_office_value
,
status
:
'1'
},
pageNo
:
1
,
pageSize
:
1
}
...
...
@@ -367,10 +461,10 @@ export default {
const
list
=
(
ret
.
data
.
data
&&
ret
.
data
.
data
.
results
)
||
[]
const
exist
=
list
.
find
(
it
=>
String
(
it
.
id
)
!==
String
(
row
.
id
))
if
(
exist
)
{
this
.
$Notice
.
error
({
title
:
'操作失败!'
,
desc
:
'同一风险等级只能存在一个启用配置,请先停用已启用项。'
})
this
.
$Notice
.
error
({
title
:
'操作失败!'
,
desc
:
'同一
部门、同一类别、同一
风险等级只能存在一个启用配置,请先停用已启用项。'
})
return
}
const
payload
=
{
id
:
row
.
id
,
status
,
rating_dict_type
:
row
.
rating_dict_type
,
rating_type
:
row
.
rating_type
}
const
payload
=
{
id
:
row
.
id
,
status
,
rating_dict_type
:
row
.
rating_dict_type
,
rating_type
:
row
.
rating_type
,
sys_type_office_value
:
row
.
sys_type_office_value
}
updatePersonVisitStatus
(
payload
).
then
((
ret2
)
=>
{
if
(
ret2
.
data
.
errcode
===
0
)
{
this
.
$Notice
.
success
({
title
:
'操作成功'
})
...
...
@@ -381,7 +475,7 @@ export default {
})
}
else
{
// 查询失败,兜底直接尝试操作(后端仍应校验)
const
payload
=
{
id
:
row
.
id
,
status
,
rating_dict_type
:
row
.
rating_dict_type
,
rating_type
:
row
.
rating_type
}
const
payload
=
{
id
:
row
.
id
,
status
,
rating_dict_type
:
row
.
rating_dict_type
,
rating_type
:
row
.
rating_type
,
sys_type_office_value
:
row
.
sys_type_office_value
}
updatePersonVisitStatus
(
payload
).
then
((
ret2
)
=>
{
if
(
ret2
.
data
.
errcode
===
0
)
{
this
.
$Notice
.
success
({
title
:
'操作成功'
})
...
...
@@ -392,7 +486,7 @@ export default {
})
}
}).
catch
(()
=>
{
const
payload
=
{
id
:
row
.
id
,
status
,
rating_dict_type
:
row
.
rating_dict_type
,
rating_type
:
row
.
rating_type
}
const
payload
=
{
id
:
row
.
id
,
status
,
rating_dict_type
:
row
.
rating_dict_type
,
rating_type
:
row
.
rating_type
,
sys_type_office_value
:
row
.
sys_type_office_value
}
updatePersonVisitStatus
(
payload
).
then
((
ret2
)
=>
{
if
(
ret2
.
data
.
errcode
===
0
)
{
this
.
$Notice
.
success
({
title
:
'操作成功'
})
...
...
@@ -404,7 +498,7 @@ export default {
})
return
}
const
payload
=
{
id
:
row
.
id
,
status
,
rating_dict_type
:
row
.
rating_dict_type
,
rating_type
:
row
.
rating_type
}
const
payload
=
{
id
:
row
.
id
,
status
,
rating_dict_type
:
row
.
rating_dict_type
,
rating_type
:
row
.
rating_type
,
sys_type_office_value
:
row
.
sys_type_office_value
}
updatePersonVisitStatus
(
payload
).
then
((
ret
)
=>
{
if
(
ret
.
data
.
errcode
===
0
)
{
this
.
$Notice
.
success
({
title
:
'操作成功'
})
...
...
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论