根据"需求与现有实现对比分析",实施P0优先级任务:
| 序号 | 文件路径 | 说明 |
|---|---|---|
| 1 | jeecg-boot/.../flyway/sql/mysql/V20260604_1__init_focus_personnel_dict.sql |
字典数据初始化SQL脚本 |
| 2 | jeecg-boot/.../flyway/sql/mysql/V20260604_2__menu_insert_FocusPersonnel_buttons.sql |
补充5个按钮权限SQL |
| 3 | jeecg-boot/.../flyway/sql/mysql/V20260604_3__alter_view_focus_personnel_list.sql |
更新视图,添加birthDate和age计算字段(达梦兼容) |
| 4 | jeecg-boot/.../flyway/sql/mysql/V20260604_4__fix_minor_tag_dict.sql |
修复旧数据与字典不匹配的映射更新SQL |
| 5 | jeecg-boot/.../focuspersonnel/entity/FocusPersonnelDetailVo.java |
详情VO(含个人信息30个字段) |
| 6 | jeecgboot-vue3/.../focuspersonnel/components/FocusPersonnelDetail.vue |
前端详情展示组件(Tab页签+概要头部) |
| 序号 | 文件路径 | 修改内容 |
|---|---|---|
| 1 | FocusPersonnel.java |
minorTag字段添加@Dict注解 |
| 2 | FocusPersonnelPageVo.java |
添加@Dict注解和@Excel注解 |
| 3 | FocusPersonnelMapper.java |
新增queryDetailById方法 |
| 4 | FocusPersonnelMapper.xml |
新增detail查询SQL;列表补充5个查询条件;增加age范围过滤 |
| 5 | IFocusPersonnelService.java |
新增queryDetailById接口 |
| 6 | FocusPersonnelServiceImpl.java |
实现queryDetailById |
| 7 | FocusPersonnelController.java |
新增queryDetailById;补充查询参数;增加ageBegin/ageEnd |
| 8 | FocusPersonnel.api.ts |
新增queryDetailById API |
| 9 | FocusPersonnelList.vue |
多次迭代:最终改为查询条件10个、顶部按钮仅刷新生成+导出、操作栏显示3个按钮(查看+岗位推送+服务跟进)、年龄范围查询、重置按钮手动清空年龄范围 |
| 10 | FocusPersonnelModal.vue |
新增detail方法,支持详情/编辑双模式 |
| 11 | FocusPersonnelForm.vue |
minorTag改为字典选择器 |
| 12 | FocusPersonnel.data.ts |
minorTag配置dictCode;清空superQuerySchema |
| 字典编码 | 字典名称 | 字典项数 | 说明 |
|---|---|---|---|
focus_major_tag |
人员大类标签 | 2 | 就业困难人员、脱贫人员 |
focus_minor_tag |
人员小类标签 | 16 | 就业困难13类 + 脱贫3类 |
| 权限标识 | 名称 | 状态 |
|---|---|---|
| focus_personnel:refresh | 刷新生成 | 占位 |
| focus_personnel:messagePush | 消息推送 | 占位 |
| focus_personnel:jobPush | 岗位推送 | 占位 |
| focus_personnel:serviceFollow | 服务跟进 | 占位 |
| focus_personnel:customTag | 自定义标签 | 占位 |
在 V20260604_3 中更新 v_focus_personnel_list,添加了:
birthDate 字段(来自personal_info)age 计算字段(达梦兼容使用 FLOOR(MONTHS_BETWEEN(CURRENT_DATE, pi.birth_date) / 12))姓名、性别、学历、户口所在地、现居住地、年龄(范围)、大类标签、小类标签、自定义标签、就业状态
刷新生成、导出
FLOOR(MONTHS_BETWEEN(CURRENT_DATE, pi.birth_date) / 12)),达梦兼容根据用户需求:
focus_personnel 表中提取去重)| 序号 | 文件路径 | 修改内容 |
|---|---|---|
| 1 | FocusPersonnelMapper.xml |
新增 listCustomTags SQL,查询所有非空 custom_tag 原始数据 |
| 2 | FocusPersonnelMapper.java |
新增 listCustomTags() 方法定义,导入 List |
| 3 | IFocusPersonnelService.java |
新增 listCustomTags() 接口方法 |
| 4 | FocusPersonnelServiceImpl.java |
实现 listCustomTags():拆分逗号分隔值、去重、排序 |
| 5 | FocusPersonnelController.java |
新增 GET /listCustomTags 端点 |
| 序号 | 文件路径 | 修改内容 |
|---|---|---|
| 1 | FocusPersonnel.api.ts |
新增 listCustomTags API 枚举和导出函数 |
| 2 | FocusPersonnelList.vue |
自定义标签搜索改为 <a-select> 下拉框(从后端动态加载标签选项);头部按钮区改为 flex 左右布局:左侧"刷新生成",右侧"自定义标签"+"推送消息"+"导出";新增"自定义标签管理弹窗"(查看现有标签+添加新标签到选项列表);页面加载时调用 listCustomTags 初始化下拉选项 |
| 3 | FocusPersonnelDetail.vue |
标签信息Tab自定义标签改为可编辑:非编辑态显示标签+"编辑"按钮;编辑态显示可关闭的标签列表+输入添加框+保存/取消按钮;保存后调用 /focusPersonnel/edit 更新 custom_tag 字段;导入 PlusOutlined 图标、saveOrUpdate API、useMessage |
GET /focusPersonnel/listCustomTags 获取所有标签focus_personnel 表提取 custom_tag 字段,拆分逗号分隔值、去重、排序[刷新生成] [自定义标签] [推送消息] [导出]
a-row justify="space-between" 实现左右分布v-auth 权限控制/focusPersonnel/edit 接口更新数据| 接口 | 方法 | 说明 |
|---|---|---|
/focusPersonnel/listCustomTags |
GET | 获取所有自定义标签(去重排序后的列表) |
/focusPersonnel/edit |
PUT/POST | 保存自定义标签(通过 id + customTag 字段) |
focus_personnel.custom_tag 字段提取(已有数据的逗号分隔值)。新添加的标签若只在管理弹窗中添加而未分配给任何人,页面刷新后会消失(因为没有持久化到任何人的记录中)focus_personnel:customTag 和 focus_personnel:messagePush 已在 V20260604_2 SQL 中定义,需要执行该脚本才能生效customTag = '选中值' 的精确匹配方式查询(而非之前的 LIKE 模糊匹配)。如果希望模糊搜索,需要在后端 XML 中修改为 LIKE 查询/focusPersonnel/edit 接口,只更新 id 和 customTag 字段,不影响其他字段根据用户反馈:
focus_personnel 表中 major_tag/minor_tag 存储的是文本值(如"就业困难人员"),不是字典code,应该关联字典使用数字codefocus_custom_tagv-auth 权限未配置未显示(V20260604_2 SQL 未执行)| 序号 | 文件路径 | 说明 |
|---|---|---|
| 1 | flyway/sql/mysql/V20260604_5__fix_tag_dict_codes.sql |
字典code重构 + 自定义标签字典 + 数据迁移 |
| 序号 | 文件路径 | 修改内容 |
|---|---|---|
| 1 | FocusPersonnel.java |
customTag 字段添加 @Dict(dicCode = "focus_custom_tag") 注解 |
| 2 | FocusPersonnelPageVo.java |
customTag 字段添加 @Dict(dicCode = "focus_custom_tag") 注解 |
| 3 | FocusPersonnelDetailVo.java |
导入 @Dict 并给 customTag 字段添加 @Dict(dicCode = "focus_custom_tag") 注解 |
| 序号 | 文件路径 | 修改内容 |
|---|---|---|
| 1 | FocusPersonnel.data.ts |
自定义标签列添加 dictCode: 'focus_custom_tag' |
| 2 | FocusPersonnelList.vue |
暂时移除"自定义标签"和"推送消息"按钮的 v-auth 指令(等 V20260604_2 SQL 执行后再加回) |
| item_value(code) | item_text(显示) |
|---|---|
| 1 | 就业困难人员 |
| 2 | 脱贫人员 |
| item_value(code) | item_text(显示) | 类别 |
|---|---|---|
| 1 | 大龄失业人员 | 就业困难 |
| 2 | 残疾人员 | 就业困难 |
| 3 | 享受最低生活保障待遇人员 | 就业困难 |
| 4 | 城镇零就业家庭人员 | 就业困难 |
| 5 | 农村零转移就业原建档立卡贫困家庭人员 | 就业困难 |
| 6 | 失地农民 | 就业困难 |
| 7 | 连续失业1年以上人员 | 就业困难 |
| 8 | 戒毒康复人员 | 就业困难 |
| 9 | 刑满释放人员 | 就业困难 |
| 10 | 精神障碍康复人员 | 就业困难 |
| 11 | 失业6个月以上退役军人 | 就业困难 |
| 12 | 需赡养患重病直系亲属的人员 | 就业困难 |
| 13 | 省人民政府规定的其他人员 | 就业困难 |
| 14 | 脱贫不稳定户 | 脱贫人员 |
| 15 | 边缘易致贫户 | 脱贫人员 |
| 16 | 突发严重困难户 | 脱贫人员 |
| item_value | item_text | 说明 |
|---|---|---|
| 标签文本 | 标签文本 | item_value = item_text(标签本身就是值) |
已存在的SQL脚本(按顺序执行):
| 序号 | SQL脚本 | 说明 | 状态 |
|---|---|---|---|
| 1 | V20260604_1 |
初始化字典(focus_major_tag、focus_minor_tag) | 已执行 |
| 2 | V20260604_2 |
补充按钮权限 | 未执行(导致按钮不显示) |
| 3 | V20260604_3 |
重建视图(含age计算字段) | 已执行 |
| 4 | V20260604_4 |
修复旧数据字典映射 | 已执行 |
| 5 | V20260604_5 |
字典code重构 + 自定义标签字典 + 数据迁移 | 需要执行 |
重要:V20260604_5 必须在 V20260604_1~V20260604_4 之后执行。它依赖于前序脚本创建的字典ID和数据。
v-auth 指令,所有用户均可看到v-auth 指令v-auth 权限控制focus_personnel 表中的 major_tag 和 minor_tag 数据(文本→code),建议执行前备份v_focus_personnel_list 是视图,数据迁移后自动返回code值,无需重建focus_custom_tag 的 item_value = item_text,@Dict 注解翻译后和原值一致根据用户反馈:
majorTag/minorTag 显示字典翻译后的文本dictCode 客户端翻译| 序号 | 文件路径 | 修改内容 |
|---|---|---|
| 1 | FocusPersonnelMapper.xml |
重构 listCustomTags 从 sys_dict_item 表查询;新增 addCustomTagItem/deleteCustomTagItem/checkCustomTagItemExists SQL |
| 2 | FocusPersonnelMapper.java |
新增 addCustomTagItem/deleteCustomTagItem/checkCustomTagItemExists 方法 |
| 3 | IFocusPersonnelService.java |
新增 addCustomTagItem/deleteCustomTagItem 接口方法 |
| 4 | FocusPersonnelServiceImpl.java |
实现新接口方法(UUID生成器、检查重复、增删字典项);listCustomTags 改为直接查字典 |
| 5 | FocusPersonnelController.java |
新增 POST /addCustomTagItem 和 DELETE /deleteCustomTagItem 端点 |
| 6 | FocusPersonnelDetailVo.java |
majorTag 和 minorTag 添加 @Dict(dicCode = "focus_major_tag") 和 @Dict(dicCode = "focus_minor_tag") |
| 7 | FocusPersonnelPageVo.java |
majorTag 添加 @Dict(dicCode = "focus_major_tag") |
| 序号 | 文件路径 | 修改内容 |
|---|---|---|
| 1 | FocusPersonnel.api.ts |
新增 addCustomTagItem/deleteCustomTagItem API 枚举和导出函数 |
| 2 | FocusPersonnel.data.ts |
添加"序号"列(customRender: ({index}) => index + 1) |
| 3 | FocusPersonnelList.vue |
自定义标签管理弹窗改为调用后端 API 增删字典项;添加删除确认弹窗;打开管理弹窗时自动刷新标签列表 |
| 4 | FocusPersonnelDetail.vue |
去掉"服务跟进"Tab(Tab5及相关代码全部删除);自定义标签编辑改为 <a-select mode="multiple"> 多选下拉(从 listCustomTags 加载选项);标签显示改为使用 getDictText 函数(优先使用 majorTag_dictText 等后端翻译字段);移除 PlusOutlined 导入 |
管理员操作(列表页):
点击"自定义标签"按钮 → 弹窗展示 focus_custom_tag 字典所有项
→ 输入名称点击"添加" → 调用 POST /addCustomTagItem → 持久化到 sys_dict_item
→ 点击"删除" → 调用 DELETE /deleteCustomTagItem → 从字典删除
用户操作(详情页):
点击"编辑" → 显示 <a-select mode="multiple"> 多选下拉
→ 只能从已有字典项中选择 → 不能输入新标签
→ 点击"保存" → 调用 POST /focusPersonnel/edit 更新 custom_tag 字段
| 接口 | 方法 | 说明 |
|---|---|---|
/focusPersonnel/addCustomTagItem |
POST | 添加自定义标签字典项(参数:itemText) |
/focusPersonnel/deleteCustomTagItem |
DELETE | 删除自定义标签字典项(参数:itemValue) |
FocusPersonnelDetailVo 和 FocusPersonnelPageVo 的 majorTag/minorTag 都加了 @Dict 注解majorTag_dictText/minorTag_dictText/customTag_dictText 等翻译字段getDictText 函数优先使用 _dictText 后缀字段,兜底返回原始值dictCode: 'focus_major_tag' 等,由 BasicTable 客户端自动翻译@Dict 或前端 dictCode 翻译错误现象:
Required request parameter 'itemText' for method parameter type String is not present
原因:前端 defHttp.post({ url, params }) 中 params 在 POST 请求中被当作请求体(body)发送,而不是 URL 查询参数,而后端使用 @RequestParam 只能从查询参数或表单中读取。
修复:
FocusPersonnelController.java:@PostMapping 改为 @GetMappingparams 在 GET 请求中会自动作为 URL 查询参数)错误现象:字典code重构(V20260604_5)后,数据存储为数字code(如 1),但前端未正确翻译。
原因:
getDictText 函数依赖后端 _dictText 字段(DictAspect可能未拦截自定义方法)dictCode 翻译依赖字典缓存,如果用户登录后字典才创建/修改,缓存未更新修复:
initDictOptions 从 /sys/dict/getDictItems/{dictCode} 加载字典数据,构建 Map<item_value, item_text> 查询映射,getDictText 函数直接查映射onMounted 中预加载三个字典(focus_major_tag、focus_minor_tag、focus_custom_tag),确保 BasicTable 翻译生效| 序号 | 文件路径 | 修改内容 |
|---|---|---|
| 1 | FocusPersonnelController.java |
addCustomTagItem 方法改为 @GetMapping |
| 2 | FocusPersonnelDetail.vue |
导入 initDictOptions;loadDictData 加载大类小类字典构建查询映射;getDictText 函数改为查映射 |
| 3 | FocusPersonnelList.vue |
导入 initDictOptions;onMounted 中预加载三个字典 |
| 问题 | 错误原因 | 修复内容 |
|---|---|---|
API错误 Request method 'POST' is not supported |
后端改为 @GetMapping 但前端仍用 defHttp.post |
前端 API addCustomTagItem 改为 defHttp.get |
| 标签仍显示数字 | initDictOptions 返回的字典项字段是 text(不是 label)。JeecgBoot 的 DictModel 只有 {value, text} 字段 |
将 item.label 改为 item.text \|\| item.title \|\| item.label(兼容多种字段名) |
initDictOptions 首次调用会从服务器拉取并缓存。如果字典在用户登录后被修改,需要刷新页面或重新登录清除缓存api.ts 和 Detail.vue 修改后需重启前端 dev server 或硬刷新(Ctrl+F5)列表页和详情页的人员大类标签、人员小类标签、自定义标签都显示数字(如 1、2),而不是对应的字典文本(如"就业困难人员"、"脱贫人员")。
列表页:JeecgBoot-vue3 的 BasicTable 组件不识别 columns 配置中的 dictCode 属性。字典翻译完全依赖后端 DictAspect 为返回结果添加的 {fieldName}_dictText 字段(如 majorTag_dictText)。但 data.ts 中列定义的 dataIndex 配置的是原始字段名(如 majorTag),导致 BasicTable 显示的是原始数字值,而不是 majorTag_dictText 的翻译文本。
参考 JeecgBoot 其他模块(如 manage.data.ts),字典列的 dataIndex 直接使用 {field}_dictText 格式:
// manage.data.ts 示例
{ title: '发送状态', dataIndex: 'esSendStatus_dictText' }
详情页:loadDictData 使用 initDictOptions 加载字典数据。initDictOptions 内部优先检查前端缓存(userStore.getAllDictItems 或 authCache),如果用户在字典被修改(V20260604_5 SQL)之前已登录,缓存中就是旧的字典数据(value='就业困难人员' 而非 value='1'),导致翻译查找失败。
文件:FocusPersonnel.data.ts
将 majorTag、minorTag、customTag 列的 dataIndex 从原始字段名改为 {field}_dictText,移除无用的 dictCode 属性:
// 修改前
{ title: '人员大类标签', dataIndex: 'majorTag', dictCode: 'focus_major_tag' }
{ title: '人员小类标签', dataIndex: 'minorTag', dictCode: 'focus_minor_tag' }
{ title: '自定义标签', dataIndex: 'customTag', dictCode: 'focus_custom_tag' }
// 修改后
{ title: '人员大类标签', dataIndex: 'majorTag_dictText' }
{ title: '人员小类标签', dataIndex: 'minorTag_dictText' }
{ title: '自定义标签', dataIndex: 'customTag_dictText' }
文件:FocusPersonnelDetail.vue
ajaxGetDictItems(从 /@/utils/dict/index)loadDictData 中的 initDictOptions 改为 ajaxGetDictItems,绕过前端缓存直接调用后端 API// 修改前
const majorDict = await initDictOptions('focus_major_tag');
const minorDict = await initDictOptions('focus_minor_tag');
// 修改后
const majorDict = await ajaxGetDictItems('focus_major_tag');
const minorDict = await ajaxGetDictItems('focus_minor_tag');
| 序号 | 文件路径 | 修改内容 |
|---|---|---|
| 1 | FocusPersonnel.data.ts |
majorTag/minorTag/customTag 列的 dataIndex 改为 majorTag_dictText/minorTag_dictText/customTag_dictText,移除 dictCode |
| 2 | FocusPersonnelDetail.vue |
导入 ajaxGetDictItems;loadDictData 中使用 ajaxGetDictItems 替代 initDictOptions 加载大类小类字典 |
| 3 | FocusPersonnel.api.ts |
deleteCustomTagItem 增加 { joinParamsToUrl: true } 选项,修复 DELETE 请求参数未传递到 URL 查询参数的问题 |
错误现象:
Required request parameter 'itemValue' for method parameter type String is not present
原因:defHttp.delete 默认将 params 放在请求体(body)中发送,而后端 @RequestParam 只能从 URL 查询参数中读取。deleteOne 已有 { joinParamsToUrl: true } 正确处理,但 deleteCustomTagItem 未添加此选项。
修复:在 FocusPersonnel.api.ts 的 deleteCustomTagItem 函数中增加 { joinParamsToUrl: true } 选项。
需求变更:删除自定义标签时,如果有用户绑定了该标签,应禁止删除并给出提示,而不是自动清理。
原因:后端 deleteCustomTagItem 需要先检查 focus_personnel 表中是否有用户使用了该标签。
修复(4个文件修改):
FocusPersonnelMapper.java — 移除 removeCustomTagFromPersonnel,新增 countPersonnelByCustomTag 方法FocusPersonnelMapper.xml — 移除清理 UPDATE SQL,新增 countPersonnelByCustomTag SELECT SQL,使用 4 种 LIKE 条件精确匹配逗号分隔值中的标签(完全匹配、开头、结尾、中间)FocusPersonnelServiceImpl.java — deleteCustomTagItem 先检查绑定用户数,大于 0 则返回 falseFocusPersonnelController.java — 处理 Service 返回结果,若不可删除则返回 Result.error("该标签已被用户绑定,无法删除")FocusPersonnelList.vue — 错误提示改用 e.message 显示后端返回的具体错误信息修改文件清单更新:
| 序号 | 文件路径 | 修改内容 |
|---|---|---|
| 3 | FocusPersonnel.api.ts |
deleteCustomTagItem 增加 { joinParamsToUrl: true } |
| 4 | FocusPersonnelMapper.java |
移除 removeCustomTagFromPersonnel,新增 countPersonnelByCustomTag |
| 5 | FocusPersonnelMapper.xml |
移除清理 UPDATE,新增绑定检查 SELECT |
| 6 | FocusPersonnelServiceImpl.java |
deleteCustomTagItem 先检查绑定再删字典项 |
| 7 | FocusPersonnelController.java |
返回 Result.error 提示已被绑定 |
| 8 | FocusPersonnelList.vue |
错误提示显示后端消息 |
FocusPersonnelPageVo 添加 @Dict 注解a-tag 的 color 属性)文件:FocusPersonnelDetail.vue
a-tabs 组件,将四个 Tab(基本信息、教育与求职、联系与居住、标签信息)的字段全部合并到一个 a-descriptions 中,按原顺序排列,使用注释分组标明a-tag 去掉 color 属性(原来有 blue、green、orange)activeTab ref 变量及其在 loadDetail 中的赋值,移除未使用的 initDictOptions 导入:span="2" 使自定义标签编辑区域占满整行宽度| 序号 | 文件路径 | 修改内容 |
|---|---|---|
| 1 | FocusPersonnelDetail.vue |
合并所有 Tab 字段到单页 a-descriptions;标签去 color;清理 activeTab、initDictOptions |
优化内容:
详情页(FocusPersonnelDetail.vue):
type="dashed" 并添加 EditOutlined 图标,更直观.custom-tag-view、.custom-tag-edit、.custom-tag-item、.edit-tag-btn 等列表页(FocusPersonnelList.vue):
a-list 为自定义 div 列表,每项使用 flex 布局左侧标签名、右侧"删除"按钮a-empty).custom-tag-modal、.tag-list-item、.tag-delete-btn 等| 序号 | 文件路径 | 修改内容 |
|---|---|---|
| 1 | FocusPersonnelDetail.vue |
合并所有 Tab 到单页;标签去 color;自定义标签 UI 优化(flex 布局、图标按钮、scoped 样式) |
| 2 | FocusPersonnelList.vue |
标签管理弹窗 UI 优化(自定义列表、hover 显示删除、数量统计、空状态) |
所有标签字典(focus_major_tag、focus_minor_tag、focus_custom_tag)从 JeecgBoot 系统表 sys_dict + sys_dict_item 迁移到项目自定义字典表 DICTIONARY + DICTIONARY_ITEM。
V20260604_6__migrate_tags_to_dictionary_item.sqlDICTIONARY 表创建 focus_major_tag、focus_minor_tag、focus_custom_tag 三条字典记录sys_dict_item 迁移 focus_major_tag 和 focus_minor_tag 的字典项到 DICTIONARY_ITEM(Value=Integer编码,Name=标签文本)sys_dict_item 迁移 focus_custom_tag 的字典项到 DICTIONARY_ITEM(Value=自增ROWNUM编码,Name=标签文本)FROM DUAL、NEWID()、NOT EXISTS 语法FocusPersonnelServiceImpl.javaDictionaryItemMapper 替代 baseMapper 的 sys_dict 相关操作listCustomTags():从 DICTIONARY_ITEM 查询 focus_custom_tag 字典的已启用标签addCustomTagItem(itemText):按 Name 查重 → 查找当前最大 Value 自增 → 插入 DICTIONARY_ITEMdeleteCustomTagItem(itemValue):按 Name 查找字典项 → 检查绑定 → 从 DICTIONARY_ITEM 删除checkCustomTagItemExists 方法(不再需要)FocusPersonnelMapper.javalistCustomTags()、addCustomTagItem()、deleteCustomTagItem()、checkCustomTagItemExists() 方法countPersonnelByCustomTag()(仍用于检查标签是否被用户绑定)FocusPersonnelMapper.xmllistCustomTags、addCustomTagItem、deleteCustomTagItem、checkCustomTagItemExists 四个 SQLcountPersonnelByCustomTag、queryPageList、queryDetailByIdFocusPersonnelPageVo.java / FocusPersonnelDetailVo.java@Dict(dicCode = "...") 注解(因不再使用系统 sys_dict 表)import org.jeecg.common.aspect.annotation.DictFocusPersonnelList.vueuseDict from /@/hooks/dictionary/useDict,替代旧的 initDictOptions 和 JDictSelectTagconst { getDictText, getDictOptions } = useDict(['focus_major_tag', 'focus_minor_tag'])majorTagOptions = computed(() => getDictOptions('focus_major_tag')),minorTagOptions 同理<j-dict-select-tag> 替换为 <a-select :options="majorTagOptions">#bodyCell 插槽,使用 getDictText('focus_major_tag', text) 翻译字典值JDictSelectTag 组件导入、initDictOptions 函数导入、onMounted 中的预加载调用FocusPersonnelDetail.vueuseDict 替代 ajaxGetDictItemsconst { getDictText } = useDict(['focus_major_tag', 'focus_minor_tag'])dictMap 响应式变量、loadDictData() 函数、自定义 getDictText() 函数loadCustomTagOptions()(从 API 加载自定义标签选项)FocusPersonnel.data.tsmajorTag/minorTag/customTag 列的 dataIndex 从 majorTag_dictText 改回 majorTag(不再依赖后端 DictAspect)| 序号 | 文件路径 | 修改内容 |
|---|---|---|
| 1 | V20260604_6__migrate_tags_to_dictionary_item.sql |
新增:在 DICTIONARY/DICTIONARY_ITEM 创建标签字典并迁移数据 |
| 2 | FocusPersonnelServiceImpl.java |
注入 DictionaryItemMapper;CRUD 改用 DICTIONARY_ITEM 表 |
| 3 | FocusPersonnelMapper.java |
移除 sys_dict_item 相关方法,保留 countPersonnelByCustomTag |
| 4 | FocusPersonnelMapper.xml |
移除 sys_dict_item 相关 SQL |
| 5 | FocusPersonnelPageVo.java |
移除 @Dict 注解和导入 |
| 6 | FocusPersonnelDetailVo.java |
移除 @Dict 注解和导入 |
| 7 | FocusPersonnelList.vue |
引入 useDict;替换搜索框;添加 bodyCell 插槽 |
| 8 | FocusPersonnelDetail.vue |
引入 useDict;移除 loadDictData/自定义 getDictText |
| 9 | FocusPersonnel.data.ts |
dataIndex 从 _dictText 改回原始字段名 |
V20260604_6 迁移脚本focus_custom_tag 仍通过 listCustomTags/addCustomTagItem/deleteCustomTagItem API 管理,后端存储从 sys_dict_item 改为 DICTIONARY_ITEMV20260604_6 仅迁移数据到 DICTIONARY/DICTIONARY_ITEM 表,未删除 sys_dict 和 sys_dict_item 中的旧数据。新增 V20260604_7 脚本清理三个标签字典的旧记录。
| 序号 | 文件路径 | 说明 |
|---|---|---|
| 1 | V20260604_7__cleanup_old_sys_dict_tags.sql |
新增:删除 sys_dict_item 和 sys_dict 中 focus_major_tag/focus_minor_tag/focus_custom_tag 的旧数据 |
sys_dict_item 子表中三个字典的字典项(通过 dict_id 关联 sys_dict)sys_dict 主表中三个字典的字典记录自定义标签管理弹窗中,除了添加和删除,还需要支持编辑(重命名)已有标签。编辑标签时同步更新 focus_personnel 表中已绑定该标签的用户记录。
IFocusPersonnelService.java新增 updateCustomTagItem(String oldName, String newName) 接口方法。
FocusPersonnelServiceImpl.java实现 updateCustomTagItem:
DICTIONARY_ITEM 记录DICTIONARY_ITEM 的 Name 和 Code 字段baseMapper.updatePersonnelCustomTagName() 同步更新 focus_personnel 中已绑定的标签名FocusPersonnelMapper.java新增 updatePersonnelCustomTagName(@Param("oldName") String oldName, @Param("newName") String newName) 方法。
FocusPersonnelMapper.xml新增 updatePersonnelCustomTagName SQL,使用 CASE WHEN 分段处理 4 种逗号分隔位置:
使用 SUBSTR + INSTR 函数精确替换,避免误替换子串。
FocusPersonnelController.java新增 PUT /updateCustomTagItem 端点,接收 oldName 和 newName 参数。
FocusPersonnel.api.ts新增 updateCustomTagItem API 枚举和导出函数(defHttp.put)。
FocusPersonnelList.vueupdateCustomTagItem 导入editingTagOldName 和 editingTagNewName ref 变量handleStartEdit(tagName):进入编辑模式handleCancelEdit():取消编辑handleSaveEdit():保存编辑,调用后端 API,更新本地列表.tag-actions(按钮组容器)、.tag-edit-btn、.tag-edit-input、.tag-edit-actions 样式| 序号 | 文件路径 | 修改内容 |
|---|---|---|
| 1 | IFocusPersonnelService.java |
新增 updateCustomTagItem 接口方法 |
| 2 | FocusPersonnelServiceImpl.java |
实现 updateCustomTagItem(含重名校验 + 同步更新 focus_personnel) |
| 3 | FocusPersonnelMapper.java |
新增 updatePersonnelCustomTagName 方法 |
| 4 | FocusPersonnelMapper.xml |
新增 updatePersonnelCustomTagName SQL(CASE WHEN 分段精确替换) |
| 5 | FocusPersonnelController.java |
新增 PUT /updateCustomTagItem 端点 |
| 6 | FocusPersonnel.api.ts |
新增 updateCustomTagItem API |
| 7 | FocusPersonnelList.vue |
弹窗标签列表添加编辑模式(内联输入+保存/取消) |
focus_personnel 表中已绑定该标签的用户记录会同步更新(逗号分隔值中的精确替换)自定义标签管理弹窗从简单的列表改为标准的表格样式,支持分页查询。因为自定义标签数量可能较多,分页表格更便于管理。
IFocusPersonnelService.java新增 IPage<DictionaryItem> queryCustomTagPage(IPage<DictionaryItem> page) 分页查询接口方法。
FocusPersonnelServiceImpl.java实现 queryCustomTagPage:使用 DictionaryItemMapper.selectPage() 分页查询 focus_custom_tag 字典的已启用标签。
FocusPersonnelController.java新增 GET /listCustomTagsPage?pageNo=1&pageSize=10 端点,返回分页的 DictionaryItem 数据。
FocusPersonnel.api.ts新增 listCustomTagsPage API 枚举和导出函数。
FocusPersonnelList.vue<a-table> 表格+分页,包含三列:序号、标签名称、操作customTagLoading、customTagPageData、customTagPagination 表格相关状态loadCustomTagPageData():分页加载标签数据handleCustomTagTableChange(pagination):分页切换事件closeCustomTagModal():关闭弹窗(取消编辑状态)loadCustomTagPageData() 刷新表格.tag-list-wrapper、.tag-list-item 等),保留简洁的输入框间距| 序号 | 文件路径 | 修改内容 |
|---|---|---|
| 1 | IFocusPersonnelService.java |
新增 queryCustomTagPage 分页查询接口 |
| 2 | FocusPersonnelServiceImpl.java |
实现 queryCustomTagPage(DictionaryItemMapper.selectPage) |
| 3 | FocusPersonnelController.java |
新增 GET /listCustomTagsPage 端点 |
| 4 | FocusPersonnel.api.ts |
新增 listCustomTagsPage API |
| 5 | FocusPersonnelList.vue |
弹窗改为表格+分页,移除旧列表样式 |
listCustomTags 保留:非分页接口仍用于详情页下拉选择框的数据源根据需求文档 3.9 节,实现服务跟进功能:
focus_personnel_service_follow:记录服务内容、服务时间、服务人员、关联人员IDfocus_personnel:serviceFollow(已在 V20260604_2 中定义)V20260604_8__create_service_follow_table.sql| 字段名 | 类型 | 说明 |
|---|---|---|
id |
VARCHAR(32) PK | 主键(UUID) |
focus_personnel_id |
VARCHAR(32) NOT NULL | 关联重点关注人员ID |
service_content |
TEXT | 服务内容 |
service_time |
DATETIME | 服务时间 |
service_provider |
VARCHAR(100) | 服务人员 |
create_by / create_time |
VARCHAR(50) / DATETIME | 创建人/时间 |
update_by / update_time |
VARCHAR(50) / DATETIME | 更新人/时间 |
del_flag |
INT DEFAULT 0 | 删除标识 |
| 序号 | 文件路径 | 说明 |
|---|---|---|
| 1 | entity/FocusPersonnelServiceFollow.java |
新增实体类(映射 focus_personnel_service_follow 表) |
| 2 | mapper/FocusPersonnelServiceFollowMapper.java |
新增 Mapper 接口 |
| 3 | service/IFocusPersonnelServiceFollowService.java |
新增 Service 接口 |
| 4 | service/impl/FocusPersonnelServiceFollowServiceImpl.java |
新增 Service 实现 |
| 5 | controller/FocusPersonnelServiceFollowController.java |
新增 Controller(分页列表/添加/编辑/删除/批量删除/查询) |
Controller 提供 6 个端点:
| 端点 | 方法 | 说明 |
|---|---|---|
/focusPersonnelServiceFollow/list |
GET | 分页列表查询(按 focusPersonnelId 过滤,按 service_time 倒序) |
/focusPersonnelServiceFollow/add |
POST | 新增 |
/focusPersonnelServiceFollow/edit |
PUT/POST | 编辑 |
/focusPersonnelServiceFollow/delete |
DELETE | 删除 |
/focusPersonnelServiceFollow/deleteBatch |
DELETE | 批量删除 |
/focusPersonnelServiceFollow/queryById |
GET | 按 ID 查询 |
| 序号 | 文件路径 | 说明 |
|---|---|---|
| 1 | components/FocusPersonnelServiceFollowList.vue |
新增:服务跟进记录弹窗组件(表格+分页,含新增/编辑/删除表单) |
| 2 | FocusPersonnel.api.ts |
新增 4 个服务跟进 API 枚举和导出函数 |
| 3 | FocusPersonnelList.vue |
替换占位函数为实际弹窗逻辑;导入新组件;添加弹窗标签 |
列表页 → 点击"服务跟进" → 弹窗显示该人员的所有服务跟进记录(表格+分页)
├── [新增服务记录] → 小弹窗(服务内容/服务时间/服务人员)
├── 每条记录 [编辑] → 小弹窗编辑
└── 每条记录 [删除] → 确认后删除
V20260604_8 建表focus_personnel:serviceFollow 已在 V20260604_2 中定义,需要执行该 SQL 才会生效。若未执行,前端 v-auth 指令会隐藏操作按钮错误现象:
无效的表或视图名[FOCUS_PERSONNEL_SERVICE_FOLLOW]
原因:V20260604_8__create_service_follow_table.sql 未被执行,数据库中不存在 focus_personnel_service_follow 表。
解决方案:手动执行以下建表 SQL:
CREATE TABLE IF NOT EXISTS focus_personnel_service_follow (
id VARCHAR(32) NOT NULL,
focus_personnel_id VARCHAR(32) NOT NULL COMMENT '关联重点关注人员ID',
service_content TEXT COMMENT '服务内容',
service_time DATETIME COMMENT '服务时间',
service_provider VARCHAR(100) COMMENT '服务人员',
create_by VARCHAR(50) COMMENT '创建人',
create_time DATETIME COMMENT '创建时间',
update_by VARCHAR(50) COMMENT '更新人',
update_time DATETIME COMMENT '更新时间',
del_flag INT DEFAULT 0 COMMENT '删除标识(0-正常,1-已删除)',
PRIMARY KEY (id)
);
错误现象:自定义标签管理弹窗中序号列全部显示为 1。
原因:序号计算使用了 record._index,但 MyBatis Plus 返回的分页数据中,a-table 的 bodyCell 插槽不会自动设置 _index 属性,导致 record._index 恒为 undefined,回退到 0,所以所有行序号都是 1。
修复:bodyCell 插槽使用 index 参数(Ant Design Vue 提供的行索引),替代 record._index:
<template #bodyCell="{ column, text, record, index }">
UI 优化:表格添加 bordered 属性,增加边框视觉层次。
错误现象:列表页搜索条件中的"自定义标签"下拉框没有任何选项。
原因:第 15 次迭代(自定义标签管理弹窗改为表格)中,移除了 customTagOptions 的 ref 声明,但搜索条件的模板仍在使用 v-for="tag in customTagOptions"。由于 customTagOptions 未定义,下拉框始终为空。
修复:补回 const customTagOptions = ref<string[]>([]); 声明。
| 序号 | 文件路径 | 修改内容 |
|---|---|---|
| 1 | FocusPersonnelList.vue |
补回 customTagOptions ref 声明;序号改用 bodyCell 的 index 参数;表格添加 bordered |
| 2 | V20260604_8__create_service_follow_table.sql |
提供建表 SQL(需手动执行) |
V20260604_8 的建表语句DICTIONARY_ITEM 表通过 listCustomTags 接口获取,需确保 focus_custom_tag 字典项已存在于 DICTIONARY_ITEM 表错误现象:点击"新增服务记录"或"编辑"后提交,后端接收不到数据。
原因:addServiceFollow 和 editServiceFollow 使用了 params 参数发送 POST 请求,但后端 Controller 使用 @RequestBody 注解。params 在 axios 中会被转为 URL 查询参数,而不是请求体 JSON,导致后端无法解析。
修复:将 params 改为 data,使其以 Request Body 形式发送 JSON。
| 文件 | 修改内容 |
|---|---|
FocusPersonnel.api.ts |
addServiceFollow / editServiceFollow 的 params → data |
旧功能:点击"自定义标签"按钮打开标签管理弹窗(CRUD 表格,支持新增/编辑/删除标签字典项)。
新功能:先勾选表格中的数据行,点击"自定义标签"按钮打开弹窗,选择标签后批量添加到选中人员。
涉及的修改:
| 文件 | 修改内容 |
|---|---|
IFocusPersonnelService.java |
新增 batchUpdateCustomTag(List<String> ids, List<String> tags) |
FocusPersonnelServiceImpl.java |
实现:遍历人员,用 HashSet 去重合并标签,String.join(",") 写入 |
FocusPersonnelController.java |
新增 PUT /batchUpdateCustomTag,接收 { ids, tags } JSON |
| 文件 | 修改内容 |
|---|---|
FocusPersonnel.api.ts |
新增 batchUpdateCustomTag API 枚举 + 导出函数 |
FocusPersonnelList.vue |
移除旧标签管理弹窗(表格+CRUD);新增批量标签弹窗(checkbox 多选);handleCustomTag 改为检查选中行 + 打开批量弹窗 |
列表页 → 勾选 N 条数据 → 点击"自定义标签"
→ 弹窗:已选择 N 条数据,请选择标签:
☑ 就业困难人员
☐ 脱贫人员
☐ 应届毕业生
...
→ [确定] → 调用 batchUpdateCustomTag → 刷新表格
focus_personnel.custom_tag 字段不再直接存标签文本(如"就业困难人员"),改为存字典值(如 1,2,3),和大类小类标签保持一致v-auth 权限指令被隐藏,移除权限限制| 文件 | 修改内容 |
|---|---|
IFocusPersonnelService.java |
listCustomTags() 返回类型 List<String> → List<DictionaryItem> |
FocusPersonnelServiceImpl.java |
listCustomTags() 直接返回 dictionaryItemMapper.selectList();batchUpdateCustomTag() 参数 tags → tagValues,存储值而非文本 |
FocusPersonnelController.java |
listCustomTags 返回值 Result<List<String>> → Result<List<DictionaryItem>> |
| 文件 | 修改内容 |
|---|---|
FocusPersonnelList.vue |
① useDict 增加 focus_custom_tag;② customTagOptions 从 ref<string[]> 改为 computed(() => getDictOptions('focus_custom_tag'));③ 新增 #bodyCell 对 customTag 列用 getDictText 翻译每个值并显示 <a-tag>;④ 搜索框改用 :options="customTagOptions";⑤ 批量弹窗 checkbox 用 tag.value/tag.label;⑥ 移除旧的 loadCustomTagOptions |
FocusPersonnelDetail.vue |
① useDict 增加 focus_custom_tag;② loadCustomTagOptions 改为映射 { label: tag.name, value: String(tag.value) };③ 自定义标签显示使用 getDictText('focus_custom_tag', tagVal) 翻译 |
FocusPersonnelServiceFollowList.vue |
① 移除新增按钮的 v-auth 权限指令;② 序号修复:record._index → index |
| 字段 | 旧格式 | 新格式 |
|---|---|---|
custom_tag |
"就业困难人员,脱贫人员" |
"1,3" |
| 显示方式 | 直接显示文本 | getDictText('focus_custom_tag', '1') → "就业困难人员" |
| 搜索传值 | 传文本给后端 | 传字典值给后端 |
focus_custom_tag 字典项已存在于 DICTIONARY_ITEM 表,可用以下 SQL 更新旧数据:
sql
-- 示例:将旧文本标签转为字典值(按需调整)
UPDATE focus_personnel SET custom_tag = NULL WHERE custom_tag IS NOT NULL;
错误现象:
class java.lang.Integer cannot be cast to class java.lang.CharSequence
在调用批量添加标签接口时抛出。
原因:FocusPersonnelController.batchUpdateCustomTag 中直接将 request.get("tags") 强转为 List<String>。getDictOptions('focus_custom_tag') 返回的 value 是 Integer 类型(来自 DICTIONARY_ITEM.Value),JSON 序列化后为数字。Jackson 反序列化为 List<Integer>,运行时强转 List<String> 失败。
修复:
// 改为 List<?> 再通过 stream 转 String
List<?> rawTags = (List<?>) request.get("tags");
List<String> tags = rawTags.stream().map(Object::toString).collect(Collectors.toList());
优化内容:
<em> 标签突出显示选中条数,增加灰色提示文字<a-tag> 包裹展示,选中时变蓝色高亮ant-empty 上下间距可能原因:
sys_permission 表中缺少 focus_personnel:serviceFollow 权限记录(V20260604_2 第27-30行未执行)f6817f48af4fb3af11b9e8bf182f618b 不匹配| 序号 | 文件路径 | 修改内容 |
|---|---|---|
| 1 | FocusPersonnelController.java |
batchUpdateCustomTag 改用 List<?> + Object::toString 避免类型转换错误 |
| 2 | FocusPersonnelList.vue |
批量标签弹窗改用 a-tag 样式 + flex 布局美化 |
| 修改日期 | 问题 | 文件 | 修复方式 |
|---|---|---|---|
| 2026-06-04 | Integer 类型转换 | FocusPersonnelController.java |
List<?> + stream().map(Object::toString)(需重启后端) |
| 2026-06-04 | 服务跟进按钮不显示 | FocusPersonnelServiceFollowList.vue |
移除权限控制,按钮始终显示,由后端 @RequiresPermissions 保护接口 |
服务跟进新增按钮不显示的根本原因:sys_permission 表中5个按钮权限的 status 字段为 NULL。
后端 SysPermissionController.getUserPermissionByToken() 的过滤条件:
.filter(item -> item.getMenuType() == 2 && item.getStatus() == 1)
status 为 NULL 不等于 1,所以 focus_personnel:serviceFollow 等权限码从未被返回到前端 permCodeList。
前端无论使用 v-auth 指令还是 v-if + computed + usePermissionStore,都依赖 permCodeList 中包含对应权限码。permCodeList 为空,按钮自然不显示。
1. 数据库修复:将 sys_permission 表中5个按钮权限的 status 从 NULL 更新为 1
UPDATE sys_permission
SET status = 1
WHERE perms IN (
'focus_personnel:refresh',
'focus_personnel:messagePush',
'focus_personnel:jobPush',
'focus_personnel:serviceFollow',
'focus_personnel:customTag'
)
AND (status IS NULL OR status != 1);
2. 源SQL修复:V20260604_2 中5条 INSERT 语句的 status 字段从 NULL 改为 1,防止新环境部署时出现同样问题。
3. 前端权限判断:FocusPersonnelServiceFollowList.vue 使用 v-if + computed + usePermissionStore 响应式判断权限,权限数据加载后自动更新显示。
| 序号 | 文件路径 | 修改内容 |
|---|---|---|
| 1 | V20260604_2__menu_insert_FocusPersonnel_buttons.sql |
5条INSERT的status字段从NULL改为1 |
| 2 | V20260604_9__fix_permission_status.sql |
新增UPDATE修复SQL,修复已有数据 |
| 3 | FocusPersonnelServiceFollowList.vue |
按钮权限判断改为 v-if + computed + usePermissionStore |