0325-海宁pad走访

This commit is contained in:
2026-03-25 14:37:28 +08:00
parent 59e05e85b1
commit 15891708de
9 changed files with 344 additions and 6 deletions

View File

@@ -312,6 +312,13 @@ public class SysCampaignController extends BaseController {
.stream().collect(HashMap::new, (m, v) -> m.put(v.getUuid(),v.getModelName()), HashMap::putAll)));
}
@PostMapping("/updateVisitInfoFeedback")
@ApiOperation("更新PAD走访反馈")
@Log(title ="pad走访记录-更新走访反馈", businessType = BusinessType.UPDATE)
public AjaxResult updateVisitInfoFeedback(@RequestBody VisitInfoFeedbackUpdateDTO updateDTO) {
return toAjax(sysCampaignService.updateVisitInfoFeedback(updateDTO));
}
@PostMapping("/delete")
@ApiOperation("根据campaignId删除任务")
@Log(title = "走访-根据campaignId删除任务")

View File

@@ -0,0 +1,19 @@
package com.ruoyi.ibs.list.domain;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
@Data
public class VisitFeedbackItemDTO implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "反馈类型")
private String feedbackType;
@ApiModelProperty(value = "反馈产品列表")
private List<String> products;
}

View File

@@ -0,0 +1,31 @@
package com.ruoyi.ibs.list.domain;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
@Data
public class VisitInfoFeedbackUpdateDTO implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "走访记录ID")
private Long id;
@ApiModelProperty(value = "走访渠道")
private String source;
@ApiModelProperty(value = "客户意愿结构化数据")
private List<VisitFeedbackItemDTO> feedbackItems;
@ApiModelProperty(value = "客户意愿拼接值")
private String intentionProductValue;
private String userName;
private String userRole;
private String deptId;
}

View File

@@ -156,6 +156,8 @@ public interface SysCampaignMapper extends BaseMapper<SysCampaign> {
List<VisitInfoVO> selectVisitInfoList(VisitInfoDTO visitInfoDTO);
int updateVisitInfoFeedback(VisitInfoFeedbackUpdateDTO updateDTO);
@Update("UPDATE sys_campaign SET del_flag = '2' where campaign_id = #{campaignId}")
int deleteSysCampaignByCampaignId(@Param("campaignId") String campaignId);

View File

@@ -115,5 +115,7 @@ public interface ISysCampaignService {
public List<VisitInfoVO> selectVisitInfoVoList(VisitInfoDTO visitInfoDTO);
int updateVisitInfoFeedback(VisitInfoFeedbackUpdateDTO updateDTO);
public int deleteSysCampaign(String campaignId);
}

View File

@@ -2178,6 +2178,23 @@ public class SysCampaignServiceImpl implements ISysCampaignService
return sysCampaignMapper.selectVisitInfoList(visitInfoDTO);
}
@Override
public int updateVisitInfoFeedback(VisitInfoFeedbackUpdateDTO updateDTO) {
if (updateDTO == null || updateDTO.getId() == null) {
throw new ServiceException("走访记录ID不能为空");
}
updateDTO.setUserName(SecurityUtils.getUsername());
updateDTO.setUserRole(SecurityUtils.userRole());
updateDTO.setDeptId(String.valueOf(SecurityUtils.getDeptId()));
updateDTO.setSource(StringUtils.trimToNull(updateDTO.getSource()));
updateDTO.setIntentionProductValue(buildIntentionProductValue(updateDTO.getFeedbackItems()));
int rows = sysCampaignMapper.updateVisitInfoFeedback(updateDTO);
if (rows <= 0) {
throw new ServiceException("走访记录不存在或无权限修改");
}
return rows;
}
@Override
public int deleteSysCampaign(String campaignId) {
SysCampaign sysCampaign = sysCampaignMapper.selectSysCampaignByCampaignId(campaignId);
@@ -2186,4 +2203,28 @@ public class SysCampaignServiceImpl implements ISysCampaignService
}
return sysCampaignMapper.deleteSysCampaignByCampaignId(campaignId);
}
private String buildIntentionProductValue(List<VisitFeedbackItemDTO> feedbackItems) {
if (feedbackItems == null || feedbackItems.isEmpty()) {
return null;
}
String joinedValue = feedbackItems.stream()
.filter(Objects::nonNull)
.map(item -> {
String feedbackType = StringUtils.trimToNull(item.getFeedbackType());
List<String> products = Optional.ofNullable(item.getProducts())
.orElse(Collections.emptyList())
.stream()
.map(StringUtils::trimToNull)
.filter(Objects::nonNull)
.distinct()
.collect(Collectors.toList());
if (feedbackType == null || products.isEmpty()) {
return null;
}
return feedbackType + ":" + String.join(",", products);
})
.filter(Objects::nonNull)
.collect(Collectors.joining(";"));
return StringUtils.trimToNull(joinedValue);
}
}

View File

@@ -1425,4 +1425,22 @@
order by vi.sign_in_time desc
</select>
<update id="updateVisitInfoFeedback" parameterType="VisitInfoFeedbackUpdateDTO">
update visit_info vi
left join sys_dept d on vi.dept_id = d.dept_id
set vi.source = #{source},
vi.intention_product_value = #{intentionProductValue},
vi.update_by = #{userName},
vi.update_time = sysdate()
where vi.id = #{id}
<if test="userRole != null">
<choose>
<when test="userRole == 'manager'"> and vi.vis_id = #{userName} </when>
<when test="userRole == 'outlet'"> and d.dept_id = #{deptId} </when>
<when test="userRole == 'branch'"> and (d.dept_id = #{deptId} or find_in_set(#{deptId},d.ancestors)) </when>
<when test="userRole in {'head', 'ops', 'public', 'private'}"> and left(d.dept_id,3) = left(#{deptId},3) </when>
</choose>
</if>
</update>
</mapper>

View File

@@ -7,3 +7,11 @@ export function getPADVisitRecord(query) {
params: query
})
}
export function updatePADVisitFeedback(data) {
return request({
url: `/system/campaign/updateVisitInfoFeedback`,
method: 'post',
data: data
})
}

View File

@@ -380,9 +380,14 @@
<el-table-column align="left" prop="filename" label="批量导入文件名" show-overflow-tooltip width="180px" v-if="columns[23].visible"></el-table-column>
<el-table-column align="left" prop="outCallStatus" label="外呼状态" show-overflow-tooltip width="120px" v-if="columns[24].visible"></el-table-column>
<el-table-column align="left" prop="outCallIntention" label="客户意愿" show-overflow-tooltip width="140px" v-if="columns[25].visible"></el-table-column>
<el-table-column align="left" prop="source" label="来源" show-overflow-tooltip width="120px" v-if="columns[26].visible"></el-table-column>
<el-table-column align="left" prop="source" label="走访渠道" show-overflow-tooltip width="120px" v-if="columns[26].visible"></el-table-column>
<el-table-column align="left" prop="analysisValue" label="nlp模型提取" show-overflow-tooltip width="140px" v-if="columns[27].visible"></el-table-column>
<el-table-column align="left" prop="facility" label="预授信额度" show-overflow-tooltip width="140px" v-if="columns[28].visible"></el-table-column>
<el-table-column align="center" label="操作" fixed="right" width="100">
<template slot-scope="scope">
<el-button type="text" size="mini" @click="handleEditFeedback(scope.row)">编辑</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
@size-change="handleSizeChange"
@@ -393,22 +398,87 @@
:total="total"
:current-page="pageNum"
></el-pagination>
<el-dialog
title="编辑走访反馈"
:visible.sync="feedbackDialogVisible"
width="960px"
custom-class="feedback-dialog"
append-to-body
@close="resetFeedbackForm"
>
<el-form ref="feedbackFormRef" :model="feedbackForm" label-width="100px" class="feedback-form">
<el-form-item label="走访渠道" required>
<el-radio-group v-model="feedbackForm.source">
<el-radio v-for="item in sourceOptions" :key="item" :label="item">{{ item }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="客户意愿" required>
<div class="feedback-groups">
<section v-for="type in feedbackTypeOptions" :key="type" class="feedback-group">
<div class="feedback-group__title">{{ type }}</div>
<el-checkbox-group v-model="feedbackForm.feedbackSelections[type]">
<el-checkbox
v-for="product in feedbackProductOptions"
:key="`${type}-${product}`"
:label="product"
>
{{ product }}
</el-checkbox>
</el-checkbox-group>
</section>
</div>
</el-form-item>
<el-form-item label="预览结果">
<div class="feedback-preview">{{ feedbackPreview || "-" }}</div>
</el-form-item>
</el-form>
<span slot="footer">
<el-button @click="feedbackDialogVisible = false">取消</el-button>
<el-button type="primary" :loading="feedbackSubmitting" @click="handleSubmitFeedback">确定</el-button>
</span>
</el-dialog>
</div>
</div>
</template>
<script>
import { mapGetters } from "vuex";
import { getPADVisitRecord } from "@/api/task/PADvisitRecord.js";
import { Message } from "element-ui";
import { getPADVisitRecord, updatePADVisitFeedback } from "@/api/task/PADvisitRecord.js";
const SOURCE_OPTIONS = ["企业微信", "PAD"];
const FEEDBACK_TYPE_OPTIONS = ["拒绝", "考虑", "意愿", "其他", "愿意", "现场办理"];
const FEEDBACK_PRODUCT_OPTIONS = [
"丰收互联",
"贷款",
"电子社保卡签约",
"基金",
"贵金属",
"信用卡",
"医保电子凭证",
"社保卡",
"理财签约"
];
function createEmptyFeedbackSelections() {
return FEEDBACK_TYPE_OPTIONS.reduce((result, item) => {
result[item] = [];
return result;
}, {});
}
export default {
data() {
return {
selectedTab: "0",
tableData: [],
Loading: false,
feedbackDialogVisible: false,
feedbackSubmitting: false,
total: 0,
pageSize: 10,
pageNum: 1,
sourceOptions: SOURCE_OPTIONS,
feedbackTypeOptions: FEEDBACK_TYPE_OPTIONS,
feedbackProductOptions: FEEDBACK_PRODUCT_OPTIONS,
searchForm: {
visName: "",
visId: "",
@@ -447,14 +517,19 @@ export default {
{ key: 23, label: "批量导入文件名", visible: true },
{ key: 24, label: "外呼状态", visible: true },
{ key: 25, label: "客户意愿", visible: true },
{ key: 26, label: "来源", visible: true },
{ key: 26, label: "走访渠道", visible: true },
{ key: 27, label: "nlp模型提取", visible: true },
{ key: 28, label: "预授信额度", visible: true }
],
columns875: [
{ key: 29, label: "异常走访标签", visible: true },
{ key: 30, label: "异常走访信息", visible: true },
]
],
feedbackForm: {
id: null,
source: "",
feedbackSelections: createEmptyFeedbackSelections()
}
};
},
computed: {
@@ -493,6 +568,9 @@ export default {
// 海宁
is875() {
return this.userName.slice(0, 3) === '875'
},
feedbackPreview() {
return this.buildFeedbackValue(this.feedbackForm.feedbackSelections)
}
},
created() {
@@ -516,6 +594,77 @@ export default {
this.initVisitingTaskList();
},
methods: {
resetFeedbackForm() {
this.feedbackSubmitting = false;
this.feedbackForm = {
id: null,
source: "",
feedbackSelections: createEmptyFeedbackSelections()
};
},
parseFeedbackValue(value) {
const feedbackSelections = createEmptyFeedbackSelections();
if (!value) {
return feedbackSelections;
}
value.split(";").forEach((segment) => {
const [type, products] = segment.split(":");
if (!type || !feedbackSelections[type]) {
return;
}
feedbackSelections[type] = (products || "")
.split(",")
.map(item => item && item.trim())
.filter(Boolean);
});
return feedbackSelections;
},
buildFeedbackItems(feedbackSelections) {
return this.feedbackTypeOptions
.map((type) => ({
feedbackType: type,
products: (feedbackSelections[type] || []).filter(Boolean)
}))
.filter(item => item.products.length > 0);
},
buildFeedbackValue(feedbackSelections) {
return this.buildFeedbackItems(feedbackSelections)
.map(item => `${item.feedbackType}:${item.products.join(",")}`)
.join(";");
},
handleEditFeedback(row) {
this.feedbackForm = {
id: row.id,
source: row.source || "",
feedbackSelections: this.parseFeedbackValue(row.intentionProductValue)
};
this.feedbackDialogVisible = true;
},
handleSubmitFeedback() {
const feedbackItems = this.buildFeedbackItems(this.feedbackForm.feedbackSelections);
if (!this.feedbackForm.source) {
this.$message.warning("请选择走访渠道");
return;
}
if (!feedbackItems.length) {
this.$message.warning("请至少选择一组客户意愿和营销产品");
return;
}
const payload = {
id: this.feedbackForm.id,
source: this.feedbackForm.source || null,
feedbackItems
};
this.feedbackSubmitting = true;
updatePADVisitFeedback(payload).then(() => {
this.$message.success("保存成功");
this.feedbackDialogVisible = false;
this.resetFeedbackForm();
this.initVisitingTaskList();
}).finally(() => {
this.feedbackSubmitting = false;
});
},
handleChange(val) {
this.pageSize = 10;
this.pageNum = 1;
@@ -752,6 +901,67 @@ export default {
}
}
.feedback-form {
::v-deep .el-form-item.is-required:not(.is-no-asterisk) > .el-form-item__label::before {
color: #f56c6c;
margin-right: 4px;
}
.feedback-groups {
display: flex;
flex-direction: column;
gap: 12px;
}
.feedback-group {
padding: 10px 12px;
border: 1px solid #ebeef5;
border-radius: 8px;
background: #fafbfc;
}
.feedback-group__title {
margin-bottom: 10px;
color: #303133;
font-weight: 600;
}
::v-deep .el-checkbox-group {
display: flex;
flex-wrap: wrap;
gap: 6px 8px;
}
::v-deep .el-checkbox {
margin-right: 0;
min-width: auto;
white-space: nowrap;
}
.feedback-preview {
min-height: 20px;
color: #606266;
word-break: break-all;
}
}
::v-deep .feedback-dialog {
border-radius: 18px;
overflow: hidden;
.el-dialog__header {
padding: 20px 24px 16px;
}
.el-dialog__body {
padding: 12px 24px 20px;
}
.el-dialog__footer {
padding: 10px 24px 20px;
}
}
.quesiton {
color: #b9b9b9;
}