diff --git a/fastbee-admin/src/main/java/com/fastbee/web/controller/system/SysUserSettingController.java b/fastbee-admin/src/main/java/com/fastbee/web/controller/system/SysUserSettingController.java index f2dd1edf..e4f4b7eb 100644 --- a/fastbee-admin/src/main/java/com/fastbee/web/controller/system/SysUserSettingController.java +++ b/fastbee-admin/src/main/java/com/fastbee/web/controller/system/SysUserSettingController.java @@ -1,12 +1,10 @@ package com.fastbee.web.controller.system; import com.fastbee.common.annotation.Log; -import com.fastbee.common.constant.UserSettingConstants; import com.fastbee.common.core.controller.BaseController; import com.fastbee.common.core.domain.AjaxResult; import com.fastbee.common.enums.BusinessType; import com.fastbee.common.utils.SecurityUtils; -import com.fastbee.common.utils.StringUtils; import com.fastbee.system.domain.LoginRedirectOption; import com.fastbee.system.domain.SysUserSetting; import com.fastbee.system.service.ISysUserSettingService; @@ -15,8 +13,8 @@ import io.swagger.annotations.ApiOperation; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; @@ -80,25 +78,30 @@ public class SysUserSettingController extends BaseController { return AjaxResult.success(); } - @ApiOperation("保存当前用户的设置") + /** + * 部分更新:只提交、也只写「用户改动过的那几项」 + * + *

刻意不是整体覆盖写(PUT):设置页有多个互相独立的配置项, + * 整体覆盖会让「保存 A 配置」把 B 配置冲回默认值。 + * 这里按入参中非 null 的字段逐列更新,未传的字段在 SQL 层面就碰不到。 + *

用 PATCH 而非 PUT:PUT 的语义是整体替换,与部分更新名实不符。 + */ + @ApiOperation("部分更新当前用户的设置") @Log(title = "个人设置", businessType = BusinessType.UPDATE) - @PutMapping() - public AjaxResult save(@RequestBody SysUserSetting setting) { - Long userId = SecurityUtils.getUserId(); - String redirect = setting.getLoginRedirect(); - if (StringUtils.isEmpty(redirect)) { - // 空值不覆盖,交由默认值兜底,避免把用户显式配置冲掉 - redirect = UserSettingConstants.DEFAULT_LOGIN_REDIRECT; + @PatchMapping() + public AjaxResult update(@RequestBody SysUserSetting setting) { + if (setting.getLoginRedirect() == null + && setting.getQueryGatewayChild() == null + && setting.getShowChild() == null) { + return AjaxResult.error("没有需要保存的配置项"); } - if (!isValidLoginRedirect(redirect)) { + // 只有在本次确实要改「登录后显示页面」时才做白名单校验; + // 未传(null)表示本次不改它,不能顺手用默认值把它覆盖掉 + if (setting.getLoginRedirect() != null && !isValidLoginRedirect(setting.getLoginRedirect())) { return AjaxResult.error("登录后跳转页面不合法,请从系统菜单中选择"); } - setting.setLoginRedirect(redirect); // 审计字段由服务端填充,不接受前端传入,避免越权写入他人操作名 - String username = SecurityUtils.getUsername(); - setting.setCreateBy(username); - setting.setUpdateBy(username); - userSettingService.save(userId, setting); + userSettingService.updatePartial(SecurityUtils.getUserId(), setting, SecurityUtils.getUsername()); return AjaxResult.success(); } diff --git a/fastbee-common/src/main/java/com/fastbee/common/constant/UserSettingConstants.java b/fastbee-common/src/main/java/com/fastbee/common/constant/UserSettingConstants.java index d7675767..5bc2c663 100644 --- a/fastbee-common/src/main/java/com/fastbee/common/constant/UserSettingConstants.java +++ b/fastbee-common/src/main/java/com/fastbee/common/constant/UserSettingConstants.java @@ -69,6 +69,17 @@ public class UserSettingConstants { */ public static final boolean DEFAULT_QUERY_GATEWAY_CHILD = false; + /** + * 设备管理列表是否包含下级机构数据的默认值 + * + *

语义对应 {@code Device.showChild}:仅在机构用户({@code deptId} 非空)时参与 SQL, + * 开启时 {@code tenant_id} 落在「本机构及全部下级机构用户」集合内,关闭时仅本机构。 + * 原实现是设备管理页的勾选框 + 浏览器 localStorage(key {@code device-showChild}), + * 换浏览器/换电脑就丢,现改为落库、由「系统管理 → 系统设置」页维护。 + *

默认 false = 与引入本开关前完全一致(默认列表不含下级机构数据)。 + */ + public static final boolean DEFAULT_SHOW_CHILD = false; + private UserSettingConstants() { } diff --git a/fastbee-service/fastbee-system-service/src/main/java/com/fastbee/system/domain/SysUserSetting.java b/fastbee-service/fastbee-system-service/src/main/java/com/fastbee/system/domain/SysUserSetting.java index 09843d7b..0bad61af 100644 --- a/fastbee-service/fastbee-system-service/src/main/java/com/fastbee/system/domain/SysUserSetting.java +++ b/fastbee-service/fastbee-system-service/src/main/java/com/fastbee/system/domain/SysUserSetting.java @@ -60,6 +60,17 @@ public class SysUserSetting extends BaseEntity { @ApiModelProperty(value = "是否查询网关子设备(按编号查时换算网关并放开子设备类产品过滤)") private Boolean queryGatewayChild; + /** + * 设备管理列表是否包含下级机构数据 + * + *

对应 {@code Device.showChild}:仅在机构用户(deptId 非空)时参与 SQL, + * 开启时 {@code tenant_id} 落在「本机构及全部下级机构用户」集合内,关闭时仅本机构。 + * 原为设备管理页勾选框 + 浏览器 localStorage,现改为落库、由系统设置页维护; + * 设备管理页只读取该值作为查询参数下发,页面内不再可就地修改。 + */ + @ApiModelProperty(value = "设备管理列表是否包含下级机构数据") + private Boolean showChild; + public SysUserSetting() { } @@ -75,6 +86,7 @@ public class SysUserSetting extends BaseEntity { setting.setLoginRedirect(UserSettingConstants.DEFAULT_LOGIN_REDIRECT); setting.setPinnedTags(UserSettingConstants.EMPTY_PINNED_TAGS); setting.setQueryGatewayChild(UserSettingConstants.DEFAULT_QUERY_GATEWAY_CHILD); + setting.setShowChild(UserSettingConstants.DEFAULT_SHOW_CHILD); return setting; } @@ -118,6 +130,14 @@ public class SysUserSetting extends BaseEntity { this.queryGatewayChild = queryGatewayChild; } + public Boolean getShowChild() { + return showChild; + } + + public void setShowChild(Boolean showChild) { + this.showChild = showChild; + } + @Override public String toString() { return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE) @@ -126,6 +146,7 @@ public class SysUserSetting extends BaseEntity { .append("loginRedirect", getLoginRedirect()) .append("pinnedTags", getPinnedTags()) .append("queryGatewayChild", getQueryGatewayChild()) + .append("showChild", getShowChild()) .append("createBy", getCreateBy()) .append("createTime", getCreateTime()) .append("updateBy", getUpdateBy()) diff --git a/fastbee-service/fastbee-system-service/src/main/java/com/fastbee/system/mapper/SysUserSettingMapper.java b/fastbee-service/fastbee-system-service/src/main/java/com/fastbee/system/mapper/SysUserSettingMapper.java index 4bc6ef2b..6af3d64c 100644 --- a/fastbee-service/fastbee-system-service/src/main/java/com/fastbee/system/mapper/SysUserSettingMapper.java +++ b/fastbee-service/fastbee-system-service/src/main/java/com/fastbee/system/mapper/SysUserSettingMapper.java @@ -23,12 +23,16 @@ public interface SysUserSettingMapper { SysUserSetting selectByUserId(Long userId); /** - * 保存设置(存在则更新,不存在则插入) + * 部分更新设置:只更新入参中**非 null** 的列,未传的列保持库中原值 * - * @param setting 设置 + *

调用方先 {@link #insertDefaultIfAbsent} 保证行存在,否则 UPDATE 0 行会静默丢改动。 + * 刻意不提供「整体覆盖写」的入口:设置页每次只提交用户改动过的那几项, + * 整体覆盖会让「保存 A 配置」把 B 配置冲回默认值。 + * + * @param setting 设置,userId 必填,其余字段只填本次要改的 * @return 影响行数 */ - int upsert(SysUserSetting setting); + int updateSetting(SysUserSetting setting); /** * 仅更新固定标签列(整列覆盖) @@ -46,12 +50,13 @@ public interface SysUserSettingMapper { * 确保用户设置行存在:不存在则插入默认行,已存在则不做任何修改 * *

固定标签的增量写入要在一行上做「锁定读 → 改 → 写」,行必须先存在。 - * 刻意不用 {@link #upsert}:upsert 的 ON DUPLICATE 子句会覆盖 - * login_redirect / query_gateway_child,把用户已保存的设置冲回默认值。 + * 刻意不用 ON DUPLICATE 写其它列:那会覆盖 + * login_redirect / query_gateway_child / show_child,把用户已保存的设置冲回默认值。 * * @param userId 用户ID * @param loginRedirect 新行使用的默认登录跳转 * @param queryGatewayChild 新行使用的默认开关值 + * @param showChild 新行使用的默认「包含下级机构数据」值 * @param operator 操作人用户名 * @param now 当前时间 * @return 影响行数(插入 1 / 已存在 0) @@ -59,6 +64,7 @@ public interface SysUserSettingMapper { int insertDefaultIfAbsent(@Param("userId") Long userId, @Param("loginRedirect") String loginRedirect, @Param("queryGatewayChild") Boolean queryGatewayChild, + @Param("showChild") Boolean showChild, @Param("operator") String operator, @Param("now") Date now); diff --git a/fastbee-service/fastbee-system-service/src/main/java/com/fastbee/system/service/ISysUserSettingService.java b/fastbee-service/fastbee-system-service/src/main/java/com/fastbee/system/service/ISysUserSettingService.java index b03426ec..a676e9c1 100644 --- a/fastbee-service/fastbee-system-service/src/main/java/com/fastbee/system/service/ISysUserSettingService.java +++ b/fastbee-service/fastbee-system-service/src/main/java/com/fastbee/system/service/ISysUserSettingService.java @@ -25,13 +25,17 @@ public interface ISysUserSettingService { SysUserSetting selectByUserId(Long userId); /** - * 保存用户设置(存在则更新,不存在则插入) + * 部分更新用户设置:只写 {@code patch} 中非 null 的字段,未传的字段保持库中原值 * - * @param userId 用户ID - * @param setting 设置内容,userId 字段忽略,以入参 userId 为准 + *

调用方每次只提交「用户真正改动过」的配置项,因此保存某一项不会把其它项冲成默认值。 + * 用户还没有设置行时先落一行默认值再更新,避免 UPDATE 0 行静默丢改动。 + * + * @param userId 用户ID + * @param patch 只填本次要改的字段,userId 字段忽略,以入参 userId 为准 + * @param operator 操作人用户名,写入 update_by * @return 影响行数 */ - int save(Long userId, SysUserSetting setting); + int updatePartial(Long userId, SysUserSetting patch, String operator); /** * 查询登录落地页候选项 diff --git a/fastbee-service/fastbee-system-service/src/main/java/com/fastbee/system/service/impl/SysUserSettingServiceImpl.java b/fastbee-service/fastbee-system-service/src/main/java/com/fastbee/system/service/impl/SysUserSettingServiceImpl.java index 35b519cd..c11f370a 100644 --- a/fastbee-service/fastbee-system-service/src/main/java/com/fastbee/system/service/impl/SysUserSettingServiceImpl.java +++ b/fastbee-service/fastbee-system-service/src/main/java/com/fastbee/system/service/impl/SysUserSettingServiceImpl.java @@ -37,7 +37,9 @@ public class SysUserSettingServiceImpl implements ISysUserSettingService { * *

用 \n 而不是常见的逗号/竖线:loginRedirect 是路由 path 不含换行, * pinnedTags 是 JSON 数组(元素为带引号的字符串)也不含裸换行, - * queryGatewayChild 是布尔字面量,三段互不冲突。 + * showChild 与 queryGatewayChild 是布尔字面量,四段互不冲突。 + *

字段顺序固定为:loginRedirect | pinnedTags | showChild | queryGatewayChild, + * 与 {@link #parseCached} 一一对应,增删字段必须同步改这两处。 */ private static final String CACHE_SEPARATOR = "\n"; @@ -80,15 +82,17 @@ public class SysUserSettingServiceImpl implements ISysUserSettingService { redisCache.setStrCacheObject(cacheKey, setting.getLoginRedirect() + CACHE_SEPARATOR + setting.getPinnedTags() + CACHE_SEPARATOR + + setting.getShowChild() + CACHE_SEPARATOR + setting.getQueryGatewayChild()); } /** * 解析缓存值,格式非法时返回 null 走查库 * - *

段数必须严格等于 3:旧版本只缓存了 2 段(loginRedirect + pinnedTags), - * 若按缺省值补齐会读到错误的 queryGatewayChild,故旧格式一律判为未命中, - * 由查库回填新格式。缓存无 TTL,但每个用户最多回填一次。 + *

段数必须严格等于 4:历史版本只缓存了 2 段(loginRedirect + pinnedTags)、 + * 后又扩到 3 段(追加 queryGatewayChild)。若按缺省值补齐,会把旧格式里 + * 那个位置的字段当成 showChild 读出来,故段数不等一律判为未命中,由查库回填新格式。 + * 缓存无 TTL,但每个用户最多回填一次。 * * @param cached 缓存字符串 * @param userId 用户ID @@ -96,38 +100,33 @@ public class SysUserSettingServiceImpl implements ISysUserSettingService { */ private SysUserSetting parseCached(String cached, Long userId) { String[] parts = cached.split(CACHE_SEPARATOR, -1); - if (parts.length != 3) { + if (parts.length != 4) { return null; } SysUserSetting setting = SysUserSetting.defaultOf(userId); setting.setLoginRedirect(parts[0]); setting.setPinnedTags(parts[1]); - setting.setQueryGatewayChild(Boolean.parseBoolean(parts[2])); + setting.setShowChild(Boolean.parseBoolean(parts[2])); + setting.setQueryGatewayChild(Boolean.parseBoolean(parts[3])); return setting; } @Override @DS("master") - public int save(Long userId, SysUserSetting setting) { - SysUserSetting target = SysUserSetting.defaultOf(userId); - target.setLoginRedirect(setting.getLoginRedirect()); - // 未传时落到默认值:列约束为 NOT NULL,直接写 null 会在 STRICT 模式下报错 - target.setQueryGatewayChild(setting.getQueryGatewayChild() == null - ? UserSettingConstants.DEFAULT_QUERY_GATEWAY_CHILD - : setting.getQueryGatewayChild()); - target.setRemark(setting.getRemark()); + @Transactional(rollbackFor = Exception.class) + public int updatePartial(Long userId, SysUserSetting patch, String operator) { Date now = new Date(); - target.setUpdateBy(setting.getUpdateBy()); - target.setUpdateTime(now); - // create_by/create_time 仅首次写入,由 ON DUPLICATE KEY UPDATE 的列清单保证不被覆盖 - target.setCreateBy(setting.getUpdateBy()); - target.setCreateTime(now); - - int rows = userSettingMapper.upsert(target); - // 写库成功后立即刷缓存。必须重查库再回填:upsert 的 ON DUPLICATE 子句不更新 - // pinned_tags,而内存对象 target 的 pinnedTags 是默认空数组, - // 直接回填会把用户的固定标签"洗"成空。 - refreshCache(userId, target); + // 行必须先存在,否则后面的部分更新会 UPDATE 0 行、静默丢掉本次改动 + userSettingMapper.insertDefaultIfAbsent(userId, UserSettingConstants.DEFAULT_LOGIN_REDIRECT, + UserSettingConstants.DEFAULT_QUERY_GATEWAY_CHILD, UserSettingConstants.DEFAULT_SHOW_CHILD, + operator, now); + patch.setUserId(userId); + patch.setUpdateBy(operator); + patch.setUpdateTime(now); + int rows = userSettingMapper.updateSetting(patch); + // 写库成功后重查库再回填缓存:patch 里只有本次改动的字段, + // 直接拿它刷缓存会把未改动字段写成 null。 + refreshCache(userId, patch); return rows; } @@ -141,7 +140,8 @@ public class SysUserSettingServiceImpl implements ISysUserSettingService { Date now = new Date(); // 行必须先存在,后面的锁定读才有东西可锁 userSettingMapper.insertDefaultIfAbsent(userId, UserSettingConstants.DEFAULT_LOGIN_REDIRECT, - UserSettingConstants.DEFAULT_QUERY_GATEWAY_CHILD, operator, now); + UserSettingConstants.DEFAULT_QUERY_GATEWAY_CHILD, UserSettingConstants.DEFAULT_SHOW_CHILD, + operator, now); List paths = parsePinnedTags(userSettingMapper.selectPinnedTagsForUpdate(userId)); if (paths.contains(path)) { // 已固定:幂等返回。不重复追加,也不把它挪到末尾,避免重复点击改变固定区顺序 diff --git a/fastbee-service/fastbee-system-service/src/main/resources/mapper/system/SysUserSettingMapper.xml b/fastbee-service/fastbee-system-service/src/main/resources/mapper/system/SysUserSettingMapper.xml index f49d7a83..24d04ec4 100644 --- a/fastbee-service/fastbee-system-service/src/main/resources/mapper/system/SysUserSettingMapper.xml +++ b/fastbee-service/fastbee-system-service/src/main/resources/mapper/system/SysUserSettingMapper.xml @@ -10,6 +10,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + @@ -23,7 +24,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" - select setting_id, user_id, login_redirect, pinned_tags, query_gateway_child, + select setting_id, user_id, login_redirect, pinned_tags, query_gateway_child, show_child, create_by, create_time, update_by, update_time, remark from sys_user_setting @@ -34,37 +35,38 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" - - insert into sys_user_setting - (user_id, login_redirect, pinned_tags, query_gateway_child, create_by, create_time, update_by, update_time, remark) - values - (#{userId}, #{loginRedirect}, #{pinnedTags}, #{queryGatewayChild}, #{createBy}, #{createTime}, #{updateBy}, #{updateTime}, #{remark}) - on duplicate key update - login_redirect = values(login_redirect), - query_gateway_child = values(query_gateway_child), - update_by = values(update_by), - update_time = values(update_time), - remark = values(remark) - + + update sys_user_setting + + login_redirect = #{loginRedirect}, + query_gateway_child = #{queryGatewayChild}, + show_child = #{showChild}, + update_by = #{updateBy}, + update_time = #{updateTime} + + where user_id = #{userId} + insert into sys_user_setting - (user_id, login_redirect, pinned_tags, query_gateway_child, create_by, create_time, update_by, update_time) + (user_id, login_redirect, pinned_tags, query_gateway_child, show_child, create_by, create_time, update_by, update_time) values - (#{userId}, #{loginRedirect}, '[]', #{queryGatewayChild}, #{operator}, #{now}, #{operator}, #{now}) + (#{userId}, #{loginRedirect}, '[]', #{queryGatewayChild}, #{showChild}, #{operator}, #{now}, #{operator}, #{now}) on duplicate key update user_id = user_id @@ -82,8 +84,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" diff --git a/sql/sys_user_setting.sql b/sql/sys_user_setting.sql index a8bfca7c..4918599f 100644 --- a/sql/sys_user_setting.sql +++ b/sql/sys_user_setting.sql @@ -36,6 +36,10 @@ -- ADD COLUMN `query_gateway_child` tinyint(1) NOT NULL DEFAULT 0 -- COMMENT '按编号查询时是否查网关子设备(0否1是)' -- AFTER `pinned_tags`; +-- ALTER TABLE `sys_user_setting` +-- ADD COLUMN `show_child` tinyint(1) NOT NULL DEFAULT 0 +-- COMMENT '设备管理列表是否包含下级机构数据(0否1是)' +-- AFTER `query_gateway_child`; -- -- query_gateway_child:开启后 /iot/device/shortList **按设备编号查询时**(serialNumber 非空): -- ① 命中的若是网关子设备编号,返回其所属的网关设备(子设备 gw_dev_code 存的就是网关 serial_number, @@ -46,6 +50,16 @@ -- 所以短列表始终只出顶层设备(网关 + 直连),既不出现挂在网关下的真子设备, -- 默认列表也不会多出 device_type=4 的设备。 -- 默认 0。读该值的入口是 DeviceController#shortList,有 SecurityContext,故为 per-user 设置。 +-- +-- show_child:设备管理列表是否包含下级机构数据(原为设备管理页勾选框,值存在浏览器 +-- localStorage 的 device-showChild,换浏览器/换电脑就丢,现改为落库)。 +-- 生效条件同 Device.showChild:仅在 deptId 非空(机构用户)时参与 SQL,语义见 +-- DeviceMapper.xml#selectDeviceShortListJoinWhere —— 开启时 tenant_id 落在「本机构 +-- 及全部下级机构用户」集合内,关闭时仅本机构。 +-- 该值现在由「系统管理 → 系统设置」页维护,设备管理页只读不写(页面内的勾选框已移除)。 +-- 设备管理页进入时读一次该设置并作为查询参数下发,故本列是「前端读取、后端不感知」的偏好, +-- DeviceController 在未收到 showChild 时依然按 false 处理。 +-- 默认 0 = 与引入本开关前完全一致。 -- ------------------------------------------------------------ CREATE TABLE IF NOT EXISTS `sys_user_setting` ( `setting_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '设置主键', @@ -53,6 +67,7 @@ CREATE TABLE IF NOT EXISTS `sys_user_setting` ( `login_redirect` varchar(128) NOT NULL DEFAULT '/bigscreen' COMMENT '登录后跳转路由path', `pinned_tags` text NOT NULL DEFAULT ('[]') COMMENT '任务栏固定标签JSON数组,按序存 path;空数组表示未固定', `query_gateway_child` tinyint(1) NOT NULL DEFAULT 0 COMMENT '按编号查询时是否查网关子设备(0否1是)', + `show_child` tinyint(1) NOT NULL DEFAULT 0 COMMENT '设备管理列表是否包含下级机构数据(0否1是)', `create_by` varchar(64) DEFAULT NULL COMMENT '创建者', `create_time` datetime DEFAULT NULL COMMENT '创建时间', `update_by` varchar(64) DEFAULT NULL COMMENT '更新者', diff --git a/yack-web/src/api/system/userSetting.js b/yack-web/src/api/system/userSetting.js index 37b705ef..eacbf27a 100644 --- a/yack-web/src/api/system/userSetting.js +++ b/yack-web/src/api/system/userSetting.js @@ -8,11 +8,13 @@ export function getUserSetting() { }) } -// 保存当前用户的设置 +// 部分更新当前用户的设置:data 里只放**本次改动过**的字段,未改动的一律不放 +// 用 PATCH 而非 PUT:服务端只按非空字段逐列更新,未传的字段保持库中原值, +// 这样保存某一项配置不会把其它配置冲回默认值 export function updateUserSetting(data) { return request({ url: '/system/userSetting', - method: 'put', + method: 'patch', data: data }) } diff --git a/yack-web/src/views/iot/device/index.vue b/yack-web/src/views/iot/device/index.vue index 54f0d6b3..9bbdb588 100644 --- a/yack-web/src/views/iot/device/index.vue +++ b/yack-web/src/views/iot/device/index.vue @@ -120,13 +120,6 @@ - - - {{ $t('device.index.105953-18') }} - - - - @@ -37,10 +41,11 @@ import { import SettingRow from './SettingRow'; // 默认值与后端 UserSettingConstants 保持一致: -// 未指定的账号保持原有的数据大屏落地行为;子设备开关默认关闭 +// 未指定的账号保持原有的数据大屏落地行为;子设备开关与「查下级机构数据」默认关闭 const DEFAULT_FORM = { loginRedirect: '/bigscreen', - queryGatewayChild: false + queryGatewayChild: false, + showChild: false }; export default { @@ -51,7 +56,9 @@ export default { saving: false, optionsLoading: false, options: [], - form: { ...DEFAULT_FORM } + form: { ...DEFAULT_FORM }, + // 服务端当前值的快照:保存时只提交与它不同的字段,未改动的不进 payload + original: { ...DEFAULT_FORM } }; }, created() { @@ -73,31 +80,52 @@ export default { getUserSetting().then((res) => { const data = res.data || {}; // 后端对未配置过的用户返回默认值,此处仅在值缺失时兜底。 - // queryGatewayChild 不能用 ||:布尔 false 会被吞掉,导致开关回显错乱。 - this.form = { + // 两个布尔开关不能用 ||:布尔 false 会被吞掉,导致开关回显错乱。 + const form = { loginRedirect: data.loginRedirect || DEFAULT_FORM.loginRedirect, queryGatewayChild: data.queryGatewayChild === null || data.queryGatewayChild === undefined ? DEFAULT_FORM.queryGatewayChild - : data.queryGatewayChild + : data.queryGatewayChild, + showChild: data.showChild === null || data.showChild === undefined + ? DEFAULT_FORM.showChild + : data.showChild }; + this.form = { ...form }; + // 快照必须与回显值同源(含兜底后的值),否则未改动也会被当成脏数据提交 + this.original = { ...form }; }); }, + /** 只取本次真正改动过的字段,未改动的不进 payload */ + buildPatch() { + const patch = {}; + Object.keys(this.form).forEach((key) => { + if (this.form[key] !== this.original[key]) { + patch[key] = this.form[key]; + } + }); + return patch; + }, submitForm() { + const patch = this.buildPatch(); + const keys = Object.keys(patch); + if (keys.length === 0) { + // 不发明知无改动的请求;服务端也会拒绝空 payload + this.$modal.msgWarning('没有需要保存的改动'); + return; + } this.saving = true; - updateUserSetting({ - loginRedirect: this.form.loginRedirect, - queryGatewayChild: this.form.queryGatewayChild - }) + updateUserSetting(patch) .then(() => { - this.$modal.msgSuccess('保存成功,下次登录生效'); + // 提交成功后推进快照,保证下一次保存只带新的改动 + this.original = { ...this.original, ...patch }; + // 「登录后显示页面」下次登录才生效,其余开关下次进入对应页面即生效 + this.$modal.msgSuccess(keys.includes('loginRedirect') + ? '保存成功,登录后显示页面将于下次登录生效' + : '保存成功'); }) .finally(() => { this.saving = false; }); - }, - handleReset() { - this.form = { ...DEFAULT_FORM }; - this.getSetting(); } } }; @@ -112,5 +140,12 @@ export default { // 172px = SettingRow 的标签列 160px + 列间距 12px,见 SettingRow.vue .setting-actions { padding: 16px 0 0 172px; + + &__hint { + margin-left: 10px; + vertical-align: middle; + color: #909399; + font-size: 12px; + } }