系统设置:设备管理查下级机构
This commit is contained in:
parent
fe8d204d11
commit
7fc8b14781
@ -1,12 +1,10 @@
|
|||||||
package com.fastbee.web.controller.system;
|
package com.fastbee.web.controller.system;
|
||||||
|
|
||||||
import com.fastbee.common.annotation.Log;
|
import com.fastbee.common.annotation.Log;
|
||||||
import com.fastbee.common.constant.UserSettingConstants;
|
|
||||||
import com.fastbee.common.core.controller.BaseController;
|
import com.fastbee.common.core.controller.BaseController;
|
||||||
import com.fastbee.common.core.domain.AjaxResult;
|
import com.fastbee.common.core.domain.AjaxResult;
|
||||||
import com.fastbee.common.enums.BusinessType;
|
import com.fastbee.common.enums.BusinessType;
|
||||||
import com.fastbee.common.utils.SecurityUtils;
|
import com.fastbee.common.utils.SecurityUtils;
|
||||||
import com.fastbee.common.utils.StringUtils;
|
|
||||||
import com.fastbee.system.domain.LoginRedirectOption;
|
import com.fastbee.system.domain.LoginRedirectOption;
|
||||||
import com.fastbee.system.domain.SysUserSetting;
|
import com.fastbee.system.domain.SysUserSetting;
|
||||||
import com.fastbee.system.service.ISysUserSettingService;
|
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.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
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.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.PutMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
@ -80,25 +78,30 @@ public class SysUserSettingController extends BaseController {
|
|||||||
return AjaxResult.success();
|
return AjaxResult.success();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ApiOperation("保存当前用户的设置")
|
/**
|
||||||
|
* 部分更新:只提交、也只写「用户改动过的那几项」
|
||||||
|
*
|
||||||
|
* <p>刻意不是整体覆盖写(PUT):设置页有多个互相独立的配置项,
|
||||||
|
* 整体覆盖会让「保存 A 配置」把 B 配置冲回默认值。
|
||||||
|
* 这里按入参中非 null 的字段逐列更新,未传的字段在 SQL 层面就碰不到。
|
||||||
|
* <p>用 PATCH 而非 PUT:PUT 的语义是整体替换,与部分更新名实不符。
|
||||||
|
*/
|
||||||
|
@ApiOperation("部分更新当前用户的设置")
|
||||||
@Log(title = "个人设置", businessType = BusinessType.UPDATE)
|
@Log(title = "个人设置", businessType = BusinessType.UPDATE)
|
||||||
@PutMapping()
|
@PatchMapping()
|
||||||
public AjaxResult save(@RequestBody SysUserSetting setting) {
|
public AjaxResult update(@RequestBody SysUserSetting setting) {
|
||||||
Long userId = SecurityUtils.getUserId();
|
if (setting.getLoginRedirect() == null
|
||||||
String redirect = setting.getLoginRedirect();
|
&& setting.getQueryGatewayChild() == null
|
||||||
if (StringUtils.isEmpty(redirect)) {
|
&& setting.getShowChild() == null) {
|
||||||
// 空值不覆盖,交由默认值兜底,避免把用户显式配置冲掉
|
return AjaxResult.error("没有需要保存的配置项");
|
||||||
redirect = UserSettingConstants.DEFAULT_LOGIN_REDIRECT;
|
|
||||||
}
|
}
|
||||||
if (!isValidLoginRedirect(redirect)) {
|
// 只有在本次确实要改「登录后显示页面」时才做白名单校验;
|
||||||
|
// 未传(null)表示本次不改它,不能顺手用默认值把它覆盖掉
|
||||||
|
if (setting.getLoginRedirect() != null && !isValidLoginRedirect(setting.getLoginRedirect())) {
|
||||||
return AjaxResult.error("登录后跳转页面不合法,请从系统菜单中选择");
|
return AjaxResult.error("登录后跳转页面不合法,请从系统菜单中选择");
|
||||||
}
|
}
|
||||||
setting.setLoginRedirect(redirect);
|
|
||||||
// 审计字段由服务端填充,不接受前端传入,避免越权写入他人操作名
|
// 审计字段由服务端填充,不接受前端传入,避免越权写入他人操作名
|
||||||
String username = SecurityUtils.getUsername();
|
userSettingService.updatePartial(SecurityUtils.getUserId(), setting, SecurityUtils.getUsername());
|
||||||
setting.setCreateBy(username);
|
|
||||||
setting.setUpdateBy(username);
|
|
||||||
userSettingService.save(userId, setting);
|
|
||||||
return AjaxResult.success();
|
return AjaxResult.success();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -69,6 +69,17 @@ public class UserSettingConstants {
|
|||||||
*/
|
*/
|
||||||
public static final boolean DEFAULT_QUERY_GATEWAY_CHILD = false;
|
public static final boolean DEFAULT_QUERY_GATEWAY_CHILD = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设备管理列表是否包含下级机构数据的默认值
|
||||||
|
*
|
||||||
|
* <p>语义对应 {@code Device.showChild}:仅在机构用户({@code deptId} 非空)时参与 SQL,
|
||||||
|
* 开启时 {@code tenant_id} 落在「本机构及全部下级机构用户」集合内,关闭时仅本机构。
|
||||||
|
* 原实现是设备管理页的勾选框 + 浏览器 localStorage(key {@code device-showChild}),
|
||||||
|
* 换浏览器/换电脑就丢,现改为落库、由「系统管理 → 系统设置」页维护。
|
||||||
|
* <p>默认 false = 与引入本开关前完全一致(默认列表不含下级机构数据)。
|
||||||
|
*/
|
||||||
|
public static final boolean DEFAULT_SHOW_CHILD = false;
|
||||||
|
|
||||||
private UserSettingConstants() {
|
private UserSettingConstants() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -60,6 +60,17 @@ public class SysUserSetting extends BaseEntity {
|
|||||||
@ApiModelProperty(value = "是否查询网关子设备(按编号查时换算网关并放开子设备类产品过滤)")
|
@ApiModelProperty(value = "是否查询网关子设备(按编号查时换算网关并放开子设备类产品过滤)")
|
||||||
private Boolean queryGatewayChild;
|
private Boolean queryGatewayChild;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设备管理列表是否包含下级机构数据
|
||||||
|
*
|
||||||
|
* <p>对应 {@code Device.showChild}:仅在机构用户(deptId 非空)时参与 SQL,
|
||||||
|
* 开启时 {@code tenant_id} 落在「本机构及全部下级机构用户」集合内,关闭时仅本机构。
|
||||||
|
* 原为设备管理页勾选框 + 浏览器 localStorage,现改为落库、由系统设置页维护;
|
||||||
|
* 设备管理页只读取该值作为查询参数下发,页面内不再可就地修改。
|
||||||
|
*/
|
||||||
|
@ApiModelProperty(value = "设备管理列表是否包含下级机构数据")
|
||||||
|
private Boolean showChild;
|
||||||
|
|
||||||
public SysUserSetting() {
|
public SysUserSetting() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,6 +86,7 @@ public class SysUserSetting extends BaseEntity {
|
|||||||
setting.setLoginRedirect(UserSettingConstants.DEFAULT_LOGIN_REDIRECT);
|
setting.setLoginRedirect(UserSettingConstants.DEFAULT_LOGIN_REDIRECT);
|
||||||
setting.setPinnedTags(UserSettingConstants.EMPTY_PINNED_TAGS);
|
setting.setPinnedTags(UserSettingConstants.EMPTY_PINNED_TAGS);
|
||||||
setting.setQueryGatewayChild(UserSettingConstants.DEFAULT_QUERY_GATEWAY_CHILD);
|
setting.setQueryGatewayChild(UserSettingConstants.DEFAULT_QUERY_GATEWAY_CHILD);
|
||||||
|
setting.setShowChild(UserSettingConstants.DEFAULT_SHOW_CHILD);
|
||||||
return setting;
|
return setting;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -118,6 +130,14 @@ public class SysUserSetting extends BaseEntity {
|
|||||||
this.queryGatewayChild = queryGatewayChild;
|
this.queryGatewayChild = queryGatewayChild;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Boolean getShowChild() {
|
||||||
|
return showChild;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setShowChild(Boolean showChild) {
|
||||||
|
this.showChild = showChild;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
|
return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
|
||||||
@ -126,6 +146,7 @@ public class SysUserSetting extends BaseEntity {
|
|||||||
.append("loginRedirect", getLoginRedirect())
|
.append("loginRedirect", getLoginRedirect())
|
||||||
.append("pinnedTags", getPinnedTags())
|
.append("pinnedTags", getPinnedTags())
|
||||||
.append("queryGatewayChild", getQueryGatewayChild())
|
.append("queryGatewayChild", getQueryGatewayChild())
|
||||||
|
.append("showChild", getShowChild())
|
||||||
.append("createBy", getCreateBy())
|
.append("createBy", getCreateBy())
|
||||||
.append("createTime", getCreateTime())
|
.append("createTime", getCreateTime())
|
||||||
.append("updateBy", getUpdateBy())
|
.append("updateBy", getUpdateBy())
|
||||||
|
|||||||
@ -23,12 +23,16 @@ public interface SysUserSettingMapper {
|
|||||||
SysUserSetting selectByUserId(Long userId);
|
SysUserSetting selectByUserId(Long userId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 保存设置(存在则更新,不存在则插入)
|
* 部分更新设置:只更新入参中**非 null** 的列,未传的列保持库中原值
|
||||||
*
|
*
|
||||||
* @param setting 设置
|
* <p>调用方先 {@link #insertDefaultIfAbsent} 保证行存在,否则 UPDATE 0 行会静默丢改动。
|
||||||
|
* 刻意不提供「整体覆盖写」的入口:设置页每次只提交用户改动过的那几项,
|
||||||
|
* 整体覆盖会让「保存 A 配置」把 B 配置冲回默认值。
|
||||||
|
*
|
||||||
|
* @param setting 设置,userId 必填,其余字段只填本次要改的
|
||||||
* @return 影响行数
|
* @return 影响行数
|
||||||
*/
|
*/
|
||||||
int upsert(SysUserSetting setting);
|
int updateSetting(SysUserSetting setting);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 仅更新固定标签列(整列覆盖)
|
* 仅更新固定标签列(整列覆盖)
|
||||||
@ -46,12 +50,13 @@ public interface SysUserSettingMapper {
|
|||||||
* 确保用户设置行存在:不存在则插入默认行,已存在则不做任何修改
|
* 确保用户设置行存在:不存在则插入默认行,已存在则不做任何修改
|
||||||
*
|
*
|
||||||
* <p>固定标签的增量写入要在一行上做「锁定读 → 改 → 写」,行必须先存在。
|
* <p>固定标签的增量写入要在一行上做「锁定读 → 改 → 写」,行必须先存在。
|
||||||
* 刻意不用 {@link #upsert}:upsert 的 ON DUPLICATE 子句会覆盖
|
* 刻意不用 ON DUPLICATE 写其它列:那会覆盖
|
||||||
* login_redirect / query_gateway_child,把用户已保存的设置冲回默认值。
|
* login_redirect / query_gateway_child / show_child,把用户已保存的设置冲回默认值。
|
||||||
*
|
*
|
||||||
* @param userId 用户ID
|
* @param userId 用户ID
|
||||||
* @param loginRedirect 新行使用的默认登录跳转
|
* @param loginRedirect 新行使用的默认登录跳转
|
||||||
* @param queryGatewayChild 新行使用的默认开关值
|
* @param queryGatewayChild 新行使用的默认开关值
|
||||||
|
* @param showChild 新行使用的默认「包含下级机构数据」值
|
||||||
* @param operator 操作人用户名
|
* @param operator 操作人用户名
|
||||||
* @param now 当前时间
|
* @param now 当前时间
|
||||||
* @return 影响行数(插入 1 / 已存在 0)
|
* @return 影响行数(插入 1 / 已存在 0)
|
||||||
@ -59,6 +64,7 @@ public interface SysUserSettingMapper {
|
|||||||
int insertDefaultIfAbsent(@Param("userId") Long userId,
|
int insertDefaultIfAbsent(@Param("userId") Long userId,
|
||||||
@Param("loginRedirect") String loginRedirect,
|
@Param("loginRedirect") String loginRedirect,
|
||||||
@Param("queryGatewayChild") Boolean queryGatewayChild,
|
@Param("queryGatewayChild") Boolean queryGatewayChild,
|
||||||
|
@Param("showChild") Boolean showChild,
|
||||||
@Param("operator") String operator,
|
@Param("operator") String operator,
|
||||||
@Param("now") Date now);
|
@Param("now") Date now);
|
||||||
|
|
||||||
|
|||||||
@ -25,13 +25,17 @@ public interface ISysUserSettingService {
|
|||||||
SysUserSetting selectByUserId(Long userId);
|
SysUserSetting selectByUserId(Long userId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 保存用户设置(存在则更新,不存在则插入)
|
* 部分更新用户设置:只写 {@code patch} 中非 null 的字段,未传的字段保持库中原值
|
||||||
|
*
|
||||||
|
* <p>调用方每次只提交「用户真正改动过」的配置项,因此保存某一项不会把其它项冲成默认值。
|
||||||
|
* 用户还没有设置行时先落一行默认值再更新,避免 UPDATE 0 行静默丢改动。
|
||||||
*
|
*
|
||||||
* @param userId 用户ID
|
* @param userId 用户ID
|
||||||
* @param setting 设置内容,userId 字段忽略,以入参 userId 为准
|
* @param patch 只填本次要改的字段,userId 字段忽略,以入参 userId 为准
|
||||||
|
* @param operator 操作人用户名,写入 update_by
|
||||||
* @return 影响行数
|
* @return 影响行数
|
||||||
*/
|
*/
|
||||||
int save(Long userId, SysUserSetting setting);
|
int updatePartial(Long userId, SysUserSetting patch, String operator);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询登录落地页候选项
|
* 查询登录落地页候选项
|
||||||
|
|||||||
@ -37,7 +37,9 @@ public class SysUserSettingServiceImpl implements ISysUserSettingService {
|
|||||||
*
|
*
|
||||||
* <p>用 \n 而不是常见的逗号/竖线:loginRedirect 是路由 path 不含换行,
|
* <p>用 \n 而不是常见的逗号/竖线:loginRedirect 是路由 path 不含换行,
|
||||||
* pinnedTags 是 JSON 数组(元素为带引号的字符串)也不含裸换行,
|
* pinnedTags 是 JSON 数组(元素为带引号的字符串)也不含裸换行,
|
||||||
* queryGatewayChild 是布尔字面量,三段互不冲突。
|
* showChild 与 queryGatewayChild 是布尔字面量,四段互不冲突。
|
||||||
|
* <p>字段顺序固定为:loginRedirect | pinnedTags | showChild | queryGatewayChild,
|
||||||
|
* 与 {@link #parseCached} 一一对应,增删字段必须同步改这两处。
|
||||||
*/
|
*/
|
||||||
private static final String CACHE_SEPARATOR = "\n";
|
private static final String CACHE_SEPARATOR = "\n";
|
||||||
|
|
||||||
@ -80,15 +82,17 @@ public class SysUserSettingServiceImpl implements ISysUserSettingService {
|
|||||||
redisCache.setStrCacheObject(cacheKey,
|
redisCache.setStrCacheObject(cacheKey,
|
||||||
setting.getLoginRedirect() + CACHE_SEPARATOR
|
setting.getLoginRedirect() + CACHE_SEPARATOR
|
||||||
+ setting.getPinnedTags() + CACHE_SEPARATOR
|
+ setting.getPinnedTags() + CACHE_SEPARATOR
|
||||||
|
+ setting.getShowChild() + CACHE_SEPARATOR
|
||||||
+ setting.getQueryGatewayChild());
|
+ setting.getQueryGatewayChild());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 解析缓存值,格式非法时返回 null 走查库
|
* 解析缓存值,格式非法时返回 null 走查库
|
||||||
*
|
*
|
||||||
* <p>段数必须严格等于 3:旧版本只缓存了 2 段(loginRedirect + pinnedTags),
|
* <p>段数必须严格等于 4:历史版本只缓存了 2 段(loginRedirect + pinnedTags)、
|
||||||
* 若按缺省值补齐会读到错误的 queryGatewayChild,故旧格式一律判为未命中,
|
* 后又扩到 3 段(追加 queryGatewayChild)。若按缺省值补齐,会把旧格式里
|
||||||
* 由查库回填新格式。缓存无 TTL,但每个用户最多回填一次。
|
* 那个位置的字段当成 showChild 读出来,故段数不等一律判为未命中,由查库回填新格式。
|
||||||
|
* 缓存无 TTL,但每个用户最多回填一次。
|
||||||
*
|
*
|
||||||
* @param cached 缓存字符串
|
* @param cached 缓存字符串
|
||||||
* @param userId 用户ID
|
* @param userId 用户ID
|
||||||
@ -96,38 +100,33 @@ public class SysUserSettingServiceImpl implements ISysUserSettingService {
|
|||||||
*/
|
*/
|
||||||
private SysUserSetting parseCached(String cached, Long userId) {
|
private SysUserSetting parseCached(String cached, Long userId) {
|
||||||
String[] parts = cached.split(CACHE_SEPARATOR, -1);
|
String[] parts = cached.split(CACHE_SEPARATOR, -1);
|
||||||
if (parts.length != 3) {
|
if (parts.length != 4) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
SysUserSetting setting = SysUserSetting.defaultOf(userId);
|
SysUserSetting setting = SysUserSetting.defaultOf(userId);
|
||||||
setting.setLoginRedirect(parts[0]);
|
setting.setLoginRedirect(parts[0]);
|
||||||
setting.setPinnedTags(parts[1]);
|
setting.setPinnedTags(parts[1]);
|
||||||
setting.setQueryGatewayChild(Boolean.parseBoolean(parts[2]));
|
setting.setShowChild(Boolean.parseBoolean(parts[2]));
|
||||||
|
setting.setQueryGatewayChild(Boolean.parseBoolean(parts[3]));
|
||||||
return setting;
|
return setting;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@DS("master")
|
@DS("master")
|
||||||
public int save(Long userId, SysUserSetting setting) {
|
@Transactional(rollbackFor = Exception.class)
|
||||||
SysUserSetting target = SysUserSetting.defaultOf(userId);
|
public int updatePartial(Long userId, SysUserSetting patch, String operator) {
|
||||||
target.setLoginRedirect(setting.getLoginRedirect());
|
|
||||||
// 未传时落到默认值:列约束为 NOT NULL,直接写 null 会在 STRICT 模式下报错
|
|
||||||
target.setQueryGatewayChild(setting.getQueryGatewayChild() == null
|
|
||||||
? UserSettingConstants.DEFAULT_QUERY_GATEWAY_CHILD
|
|
||||||
: setting.getQueryGatewayChild());
|
|
||||||
target.setRemark(setting.getRemark());
|
|
||||||
Date now = new Date();
|
Date now = new Date();
|
||||||
target.setUpdateBy(setting.getUpdateBy());
|
// 行必须先存在,否则后面的部分更新会 UPDATE 0 行、静默丢掉本次改动
|
||||||
target.setUpdateTime(now);
|
userSettingMapper.insertDefaultIfAbsent(userId, UserSettingConstants.DEFAULT_LOGIN_REDIRECT,
|
||||||
// create_by/create_time 仅首次写入,由 ON DUPLICATE KEY UPDATE 的列清单保证不被覆盖
|
UserSettingConstants.DEFAULT_QUERY_GATEWAY_CHILD, UserSettingConstants.DEFAULT_SHOW_CHILD,
|
||||||
target.setCreateBy(setting.getUpdateBy());
|
operator, now);
|
||||||
target.setCreateTime(now);
|
patch.setUserId(userId);
|
||||||
|
patch.setUpdateBy(operator);
|
||||||
int rows = userSettingMapper.upsert(target);
|
patch.setUpdateTime(now);
|
||||||
// 写库成功后立即刷缓存。必须重查库再回填:upsert 的 ON DUPLICATE 子句不更新
|
int rows = userSettingMapper.updateSetting(patch);
|
||||||
// pinned_tags,而内存对象 target 的 pinnedTags 是默认空数组,
|
// 写库成功后重查库再回填缓存:patch 里只有本次改动的字段,
|
||||||
// 直接回填会把用户的固定标签"洗"成空。
|
// 直接拿它刷缓存会把未改动字段写成 null。
|
||||||
refreshCache(userId, target);
|
refreshCache(userId, patch);
|
||||||
return rows;
|
return rows;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -141,7 +140,8 @@ public class SysUserSettingServiceImpl implements ISysUserSettingService {
|
|||||||
Date now = new Date();
|
Date now = new Date();
|
||||||
// 行必须先存在,后面的锁定读才有东西可锁
|
// 行必须先存在,后面的锁定读才有东西可锁
|
||||||
userSettingMapper.insertDefaultIfAbsent(userId, UserSettingConstants.DEFAULT_LOGIN_REDIRECT,
|
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<String> paths = parsePinnedTags(userSettingMapper.selectPinnedTagsForUpdate(userId));
|
List<String> paths = parsePinnedTags(userSettingMapper.selectPinnedTagsForUpdate(userId));
|
||||||
if (paths.contains(path)) {
|
if (paths.contains(path)) {
|
||||||
// 已固定:幂等返回。不重复追加,也不把它挪到末尾,避免重复点击改变固定区顺序
|
// 已固定:幂等返回。不重复追加,也不把它挪到末尾,避免重复点击改变固定区顺序
|
||||||
|
|||||||
@ -10,6 +10,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
<result property="loginRedirect" column="login_redirect" />
|
<result property="loginRedirect" column="login_redirect" />
|
||||||
<result property="pinnedTags" column="pinned_tags" />
|
<result property="pinnedTags" column="pinned_tags" />
|
||||||
<result property="queryGatewayChild" column="query_gateway_child" />
|
<result property="queryGatewayChild" column="query_gateway_child" />
|
||||||
|
<result property="showChild" column="show_child" />
|
||||||
<result property="createBy" column="create_by" />
|
<result property="createBy" column="create_by" />
|
||||||
<result property="createTime" column="create_time" />
|
<result property="createTime" column="create_time" />
|
||||||
<result property="updateBy" column="update_by" />
|
<result property="updateBy" column="update_by" />
|
||||||
@ -23,7 +24,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
</resultMap>
|
</resultMap>
|
||||||
|
|
||||||
<sql id="selectUserSettingVo">
|
<sql id="selectUserSettingVo">
|
||||||
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
|
create_by, create_time, update_by, update_time, remark
|
||||||
from sys_user_setting
|
from sys_user_setting
|
||||||
</sql>
|
</sql>
|
||||||
@ -34,37 +35,38 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
</select>
|
</select>
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
user_id 上有唯一索引 uk_sys_user_setting_user,
|
部分更新:只更新**显式传入**(非 null)的列。
|
||||||
用 INSERT ... ON DUPLICATE KEY UPDATE 收敛「先查后插」的并发竞态:
|
设置页每次只提交用户真正改动过的那几项,因此保存 A 配置不可能把 B 配置冲成默认值——
|
||||||
并发首次保存不会因唯一键冲突抛 DuplicateKey,也不需要 SELECT FOR UPDATE。
|
这是把写入从「整体覆盖」拆成「单量提交」的关键:
|
||||||
create_time 保留首次写入时间,重复插入时不覆盖。
|
· Boolean 为 null = 本次不改这一项(显式传 false 与不传是两回事,包装类型能区分)
|
||||||
pinned_tags 故意不在此更新:它由 tags-view 的独立写入路径维护,
|
· String 为 null = 本次不改这一项
|
||||||
并入 upsert 会导致保存其它设置时把用户的固定标签冲成默认值。
|
刻意不碰 pinned_tags:它由 tags-view 的独立写入路径维护,混进来会互相覆盖。
|
||||||
|
调用方必须先 insertDefaultIfAbsent 保证行存在,否则 UPDATE 0 行会静默丢改动。
|
||||||
-->
|
-->
|
||||||
<insert id="upsert" parameterType="com.fastbee.system.domain.SysUserSetting">
|
<update id="updateSetting" parameterType="com.fastbee.system.domain.SysUserSetting">
|
||||||
insert into sys_user_setting
|
update sys_user_setting
|
||||||
(user_id, login_redirect, pinned_tags, query_gateway_child, create_by, create_time, update_by, update_time, remark)
|
<set>
|
||||||
values
|
<if test="loginRedirect != null">login_redirect = #{loginRedirect},</if>
|
||||||
(#{userId}, #{loginRedirect}, #{pinnedTags}, #{queryGatewayChild}, #{createBy}, #{createTime}, #{updateBy}, #{updateTime}, #{remark})
|
<if test="queryGatewayChild != null">query_gateway_child = #{queryGatewayChild},</if>
|
||||||
on duplicate key update
|
<if test="showChild != null">show_child = #{showChild},</if>
|
||||||
login_redirect = values(login_redirect),
|
update_by = #{updateBy},
|
||||||
query_gateway_child = values(query_gateway_child),
|
update_time = #{updateTime}
|
||||||
update_by = values(update_by),
|
</set>
|
||||||
update_time = values(update_time),
|
where user_id = #{userId}
|
||||||
remark = values(remark)
|
</update>
|
||||||
</insert>
|
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
确保设置行存在:不存在则插入默认行,已存在则**什么都不改**。
|
确保设置行存在:不存在则插入默认行,已存在则**什么都不改**。
|
||||||
固定标签的增量写入要在一行上做「锁定读 → 改 → 写」,行必须先存在。
|
两处调用:① 固定标签的增量写入要在一行上做「锁定读 → 改 → 写」,行必须先存在;
|
||||||
刻意不用 upsert:它的 ON DUPLICATE 子句会覆盖 login_redirect / query_gateway_child,
|
② 设置页的部分更新前先兜一行,否则 UPDATE 0 行会静默丢改动。
|
||||||
|
刻意不用 ON DUPLICATE 写其它列:那会覆盖 login_redirect / query_gateway_child / show_child,
|
||||||
把用户已保存的设置冲回默认值;这里用 user_id = user_id 作为 no-op 更新。
|
把用户已保存的设置冲回默认值;这里用 user_id = user_id 作为 no-op 更新。
|
||||||
-->
|
-->
|
||||||
<insert id="insertDefaultIfAbsent">
|
<insert id="insertDefaultIfAbsent">
|
||||||
insert into sys_user_setting
|
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
|
values
|
||||||
(#{userId}, #{loginRedirect}, '[]', #{queryGatewayChild}, #{operator}, #{now}, #{operator}, #{now})
|
(#{userId}, #{loginRedirect}, '[]', #{queryGatewayChild}, #{showChild}, #{operator}, #{now}, #{operator}, #{now})
|
||||||
on duplicate key update
|
on duplicate key update
|
||||||
user_id = user_id
|
user_id = user_id
|
||||||
</insert>
|
</insert>
|
||||||
@ -82,8 +84,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
</select>
|
</select>
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
整列覆盖写固定标签。与登录跳转是两条独立写入路径:若合并进 upsert,
|
整列覆盖写固定标签。与设置页的部分更新是两条独立写入路径:
|
||||||
保存登录跳转时会把固定标签一并覆盖成默认值。
|
若把 pinned_tags 并进 updateSetting,保存登录跳转等配置时会把固定标签一并覆盖掉。
|
||||||
**只在 addPinnedTag / removePinnedTag 的事务内调用**,且在 SELECT ... FOR UPDATE
|
**只在 addPinnedTag / removePinnedTag 的事务内调用**,且在 SELECT ... FOR UPDATE
|
||||||
之后落下,入参是锁内读出并改好的完整数组;对外不再有全量覆盖写的入口。
|
之后落下,入参是锁内读出并改好的完整数组;对外不再有全量覆盖写的入口。
|
||||||
-->
|
-->
|
||||||
|
|||||||
@ -36,6 +36,10 @@
|
|||||||
-- ADD COLUMN `query_gateway_child` tinyint(1) NOT NULL DEFAULT 0
|
-- ADD COLUMN `query_gateway_child` tinyint(1) NOT NULL DEFAULT 0
|
||||||
-- COMMENT '按编号查询时是否查网关子设备(0否1是)'
|
-- COMMENT '按编号查询时是否查网关子设备(0否1是)'
|
||||||
-- AFTER `pinned_tags`;
|
-- 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 非空):
|
-- query_gateway_child:开启后 /iot/device/shortList **按设备编号查询时**(serialNumber 非空):
|
||||||
-- ① 命中的若是网关子设备编号,返回其所属的网关设备(子设备 gw_dev_code 存的就是网关 serial_number,
|
-- ① 命中的若是网关子设备编号,返回其所属的网关设备(子设备 gw_dev_code 存的就是网关 serial_number,
|
||||||
@ -46,6 +50,16 @@
|
|||||||
-- 所以短列表始终只出顶层设备(网关 + 直连),既不出现挂在网关下的真子设备,
|
-- 所以短列表始终只出顶层设备(网关 + 直连),既不出现挂在网关下的真子设备,
|
||||||
-- 默认列表也不会多出 device_type=4 的设备。
|
-- 默认列表也不会多出 device_type=4 的设备。
|
||||||
-- 默认 0。读该值的入口是 DeviceController#shortList,有 SecurityContext,故为 per-user 设置。
|
-- 默认 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` (
|
CREATE TABLE IF NOT EXISTS `sys_user_setting` (
|
||||||
`setting_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '设置主键',
|
`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',
|
`login_redirect` varchar(128) NOT NULL DEFAULT '/bigscreen' COMMENT '登录后跳转路由path',
|
||||||
`pinned_tags` text NOT NULL DEFAULT ('[]') COMMENT '任务栏固定标签JSON数组,按序存 path;空数组表示未固定',
|
`pinned_tags` text NOT NULL DEFAULT ('[]') COMMENT '任务栏固定标签JSON数组,按序存 path;空数组表示未固定',
|
||||||
`query_gateway_child` tinyint(1) NOT NULL DEFAULT 0 COMMENT '按编号查询时是否查网关子设备(0否1是)',
|
`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_by` varchar(64) DEFAULT NULL COMMENT '创建者',
|
||||||
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
|
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
|
||||||
`update_by` varchar(64) DEFAULT NULL COMMENT '更新者',
|
`update_by` varchar(64) DEFAULT NULL COMMENT '更新者',
|
||||||
|
|||||||
@ -8,11 +8,13 @@ export function getUserSetting() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 保存当前用户的设置
|
// 部分更新当前用户的设置:data 里只放**本次改动过**的字段,未改动的一律不放
|
||||||
|
// 用 PATCH 而非 PUT:服务端只按非空字段逐列更新,未传的字段保持库中原值,
|
||||||
|
// 这样保存某一项配置不会把其它配置冲回默认值
|
||||||
export function updateUserSetting(data) {
|
export function updateUserSetting(data) {
|
||||||
return request({
|
return request({
|
||||||
url: '/system/userSetting',
|
url: '/system/userSetting',
|
||||||
method: 'put',
|
method: 'patch',
|
||||||
data: data
|
data: data
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -120,13 +120,6 @@
|
|||||||
</el-dropdown-menu>
|
</el-dropdown-menu>
|
||||||
</el-dropdown>
|
</el-dropdown>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="1.5" style="line-height: 28px">
|
|
||||||
<el-checkbox v-model="queryParams.showChild" style="margin: 0px 10px" @change="handleQuery">
|
|
||||||
{{ $t('device.index.105953-18') }}
|
|
||||||
</el-checkbox>
|
|
||||||
<el-tooltip :content="$t('device.index.105953-19')" placement="top"><i class="el-icon-question"></i>
|
|
||||||
</el-tooltip>
|
|
||||||
</el-col>
|
|
||||||
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList">
|
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList">
|
||||||
<template>
|
<template>
|
||||||
<el-tooltip effect="dark" content="切换卡片/列表" placement="top">
|
<el-tooltip effect="dark" content="切换卡片/列表" placement="top">
|
||||||
@ -401,6 +394,7 @@ import vueQr from 'vue-qr';
|
|||||||
import {listDeviceShort, delDevice} from '@/api/iot/device';
|
import {listDeviceShort, delDevice} from '@/api/iot/device';
|
||||||
import {listGroup} from '@/api/iot/group';
|
import {listGroup} from '@/api/iot/group';
|
||||||
import {deptsTreeSelect} from '@/api/system/user';
|
import {deptsTreeSelect} from '@/api/system/user';
|
||||||
|
import {getUserSetting} from '@/api/system/userSetting';
|
||||||
import {delSipDeviceBySipId} from '@/api/iot/sipdevice';
|
import {delSipDeviceBySipId} from '@/api/iot/sipdevice';
|
||||||
import auth from '@/plugins/auth';
|
import auth from '@/plugins/auth';
|
||||||
import Treeselect from '@riophae/vue-treeselect';
|
import Treeselect from '@riophae/vue-treeselect';
|
||||||
@ -447,12 +441,16 @@ export default {
|
|||||||
myGroupList: [],
|
myGroupList: [],
|
||||||
// 机构树
|
// 机构树
|
||||||
deptOptions: [],
|
deptOptions: [],
|
||||||
|
// 「是否包含下级机构数据」是否已从个人设置同步完成(首次同步由 created 负责)
|
||||||
|
showChildLoaded: false,
|
||||||
// 根路径
|
// 根路径
|
||||||
baseUrl: process.env.VUE_APP_BASE_API,
|
baseUrl: process.env.VUE_APP_BASE_API,
|
||||||
// 查询参数
|
// 查询参数
|
||||||
queryParams: {
|
queryParams: {
|
||||||
pageNum: 1,
|
pageNum: 1,
|
||||||
pageSize: 12,
|
pageSize: 12,
|
||||||
|
// 「是否包含下级机构数据」由个人设置(系统管理 → 系统设置)决定,
|
||||||
|
// 进入页面时由 syncShowChild() 覆盖,页面内不再就地修改
|
||||||
showChild: false,
|
showChild: false,
|
||||||
entrance: 1,
|
entrance: 1,
|
||||||
deviceName: null,
|
deviceName: null,
|
||||||
@ -526,13 +524,13 @@ export default {
|
|||||||
this.queryParams.productId = null;
|
this.queryParams.productId = null;
|
||||||
this.queryParams.groupId = null;
|
this.queryParams.groupId = null;
|
||||||
}
|
}
|
||||||
// 恢复"显示下级机构数据"用户选择
|
|
||||||
const cachedShowChild = this.$cache.local.get('device-showChild');
|
|
||||||
if (cachedShowChild === true || cachedShowChild === 'true') {
|
|
||||||
this.queryParams.showChild = true;
|
|
||||||
}
|
|
||||||
this.getDeptTree();
|
this.getDeptTree();
|
||||||
|
// 先把「是否包含下级机构数据」从个人设置同步过来再拉列表,
|
||||||
|
// 避免先用默认值查一次、拿到设置后又重查一次的闪动
|
||||||
|
this.syncShowChild().then(() => {
|
||||||
|
this.showChildLoaded = true;
|
||||||
this.connectMqtt();
|
this.connectMqtt();
|
||||||
|
});
|
||||||
},
|
},
|
||||||
activated() {
|
activated() {
|
||||||
// 页码筛选(优先恢复页码,无论是否有其他参数变化)
|
// 页码筛选(优先恢复页码,无论是否有其他参数变化)
|
||||||
@ -580,8 +578,46 @@ export default {
|
|||||||
// 浏览器回退时,即使没有 t 参数也要刷新列表
|
// 浏览器回退时,即使没有 t 参数也要刷新列表
|
||||||
this.getList();
|
this.getList();
|
||||||
}
|
}
|
||||||
|
this.resyncShowChild();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
/**
|
||||||
|
* 同步「设备管理是否包含下级机构数据」
|
||||||
|
*
|
||||||
|
* 该偏好由系统设置页维护并落 sys_user_setting,本页只读不写。
|
||||||
|
* 拉取失败时保持默认 false(不写坏值),并返回是否发生了变化,
|
||||||
|
* 供 activated 判断是否需要重查列表。
|
||||||
|
*
|
||||||
|
* @returns {Promise<boolean>} 值是否发生变化
|
||||||
|
*/
|
||||||
|
syncShowChild() {
|
||||||
|
return getUserSetting()
|
||||||
|
.then((response) => {
|
||||||
|
const next = !!(response.data && response.data.showChild);
|
||||||
|
const changed = this.queryParams.showChild !== next;
|
||||||
|
this.queryParams.showChild = next;
|
||||||
|
return changed;
|
||||||
|
})
|
||||||
|
.catch(() => false);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 页面被重新激活时同步偏好
|
||||||
|
*
|
||||||
|
* 本页被 keep-alive 常驻时切回来不会重跑 created,若不在激活时重新同步,
|
||||||
|
* 在系统设置页改了开关再切回本页将看不到变化。
|
||||||
|
* 首次进入由 created 负责(它同步完才拉列表),此处跳过,
|
||||||
|
* 否则会和 created 抢跑、让列表在 MQTT 连上之前就查一次。
|
||||||
|
*/
|
||||||
|
resyncShowChild() {
|
||||||
|
if (!this.showChildLoaded) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.syncShowChild().then((changed) => {
|
||||||
|
if (changed) {
|
||||||
|
this.getList();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
/* 连接Mqtt消息服务器 */
|
/* 连接Mqtt消息服务器 */
|
||||||
async connectMqtt() {
|
async connectMqtt() {
|
||||||
if (this.$mqttTool.client == null) {
|
if (this.$mqttTool.client == null) {
|
||||||
@ -795,7 +831,6 @@ export default {
|
|||||||
/** 搜索按钮操作 */
|
/** 搜索按钮操作 */
|
||||||
handleQuery() {
|
handleQuery() {
|
||||||
this.queryParams.pageNum = 1;
|
this.queryParams.pageNum = 1;
|
||||||
this.$cache.local.set('device-showChild', this.queryParams.showChild);
|
|
||||||
this.getList();
|
this.getList();
|
||||||
},
|
},
|
||||||
/** 重置按钮操作 */
|
/** 重置按钮操作 */
|
||||||
@ -804,8 +839,8 @@ export default {
|
|||||||
this.queryParams.groupId = null;
|
this.queryParams.groupId = null;
|
||||||
this.queryParams.serialNumber = null;
|
this.queryParams.serialNumber = null;
|
||||||
this.queryParams.deptId = null;
|
this.queryParams.deptId = null;
|
||||||
this.queryParams.showChild = false;
|
// 刻意不重置 showChild:「是否包含下级机构数据」是个人设置里的持久偏好,
|
||||||
this.$cache.local.remove('device-showChild');
|
// 重置按钮只管本次查询条件,改了偏好下次进页面又会被覆盖回来,反而不一致
|
||||||
this.resetForm('queryForm');
|
this.resetForm('queryForm');
|
||||||
this.handleQuery();
|
this.handleQuery();
|
||||||
},
|
},
|
||||||
|
|||||||
@ -21,9 +21,13 @@
|
|||||||
<el-switch v-model="form.queryGatewayChild" />
|
<el-switch v-model="form.queryGatewayChild" />
|
||||||
</setting-row>
|
</setting-row>
|
||||||
|
|
||||||
|
<setting-row label="设备管理查下级机构" desc="开启后设备管理列表返回本机构及全部下级机构的设备,关闭时仅返回本机构设备。仅对归属了机构的账号生效;该值即设备管理页原先的「显示下级机构数据」勾选项,页面内不再就地切换。">
|
||||||
|
<el-switch v-model="form.showChild" />
|
||||||
|
</setting-row>
|
||||||
|
|
||||||
<div class="setting-actions">
|
<div class="setting-actions">
|
||||||
<el-button type="primary" icon="el-icon-check" :loading="saving" @click="submitForm">保存</el-button>
|
<el-button type="primary" icon="el-icon-check" :loading="saving" @click="submitForm">保存</el-button>
|
||||||
<el-button icon="el-icon-refresh-left" @click="handleReset">重置</el-button>
|
<span class="setting-actions__hint">仅提交改动过的配置项</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -37,10 +41,11 @@ import {
|
|||||||
import SettingRow from './SettingRow';
|
import SettingRow from './SettingRow';
|
||||||
|
|
||||||
// 默认值与后端 UserSettingConstants 保持一致:
|
// 默认值与后端 UserSettingConstants 保持一致:
|
||||||
// 未指定的账号保持原有的数据大屏落地行为;子设备开关默认关闭
|
// 未指定的账号保持原有的数据大屏落地行为;子设备开关与「查下级机构数据」默认关闭
|
||||||
const DEFAULT_FORM = {
|
const DEFAULT_FORM = {
|
||||||
loginRedirect: '/bigscreen',
|
loginRedirect: '/bigscreen',
|
||||||
queryGatewayChild: false
|
queryGatewayChild: false,
|
||||||
|
showChild: false
|
||||||
};
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@ -51,7 +56,9 @@ export default {
|
|||||||
saving: false,
|
saving: false,
|
||||||
optionsLoading: false,
|
optionsLoading: false,
|
||||||
options: [],
|
options: [],
|
||||||
form: { ...DEFAULT_FORM }
|
form: { ...DEFAULT_FORM },
|
||||||
|
// 服务端当前值的快照:保存时只提交与它不同的字段,未改动的不进 payload
|
||||||
|
original: { ...DEFAULT_FORM }
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
@ -73,31 +80,52 @@ export default {
|
|||||||
getUserSetting().then((res) => {
|
getUserSetting().then((res) => {
|
||||||
const data = res.data || {};
|
const data = res.data || {};
|
||||||
// 后端对未配置过的用户返回默认值,此处仅在值缺失时兜底。
|
// 后端对未配置过的用户返回默认值,此处仅在值缺失时兜底。
|
||||||
// queryGatewayChild 不能用 ||:布尔 false 会被吞掉,导致开关回显错乱。
|
// 两个布尔开关不能用 ||:布尔 false 会被吞掉,导致开关回显错乱。
|
||||||
this.form = {
|
const form = {
|
||||||
loginRedirect: data.loginRedirect || DEFAULT_FORM.loginRedirect,
|
loginRedirect: data.loginRedirect || DEFAULT_FORM.loginRedirect,
|
||||||
queryGatewayChild: data.queryGatewayChild === null || data.queryGatewayChild === undefined
|
queryGatewayChild: data.queryGatewayChild === null || data.queryGatewayChild === undefined
|
||||||
? DEFAULT_FORM.queryGatewayChild
|
? 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() {
|
submitForm() {
|
||||||
|
const patch = this.buildPatch();
|
||||||
|
const keys = Object.keys(patch);
|
||||||
|
if (keys.length === 0) {
|
||||||
|
// 不发明知无改动的请求;服务端也会拒绝空 payload
|
||||||
|
this.$modal.msgWarning('没有需要保存的改动');
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.saving = true;
|
this.saving = true;
|
||||||
updateUserSetting({
|
updateUserSetting(patch)
|
||||||
loginRedirect: this.form.loginRedirect,
|
|
||||||
queryGatewayChild: this.form.queryGatewayChild
|
|
||||||
})
|
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.$modal.msgSuccess('保存成功,下次登录生效');
|
// 提交成功后推进快照,保证下一次保存只带新的改动
|
||||||
|
this.original = { ...this.original, ...patch };
|
||||||
|
// 「登录后显示页面」下次登录才生效,其余开关下次进入对应页面即生效
|
||||||
|
this.$modal.msgSuccess(keys.includes('loginRedirect')
|
||||||
|
? '保存成功,登录后显示页面将于下次登录生效'
|
||||||
|
: '保存成功');
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
this.saving = false;
|
this.saving = false;
|
||||||
});
|
});
|
||||||
},
|
|
||||||
handleReset() {
|
|
||||||
this.form = { ...DEFAULT_FORM };
|
|
||||||
this.getSetting();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -112,5 +140,12 @@ export default {
|
|||||||
// 172px = SettingRow 的标签列 160px + 列间距 12px,见 SettingRow.vue
|
// 172px = SettingRow 的标签列 160px + 列间距 12px,见 SettingRow.vue
|
||||||
.setting-actions {
|
.setting-actions {
|
||||||
padding: 16px 0 0 172px;
|
padding: 16px 0 0 172px;
|
||||||
|
|
||||||
|
&__hint {
|
||||||
|
margin-left: 10px;
|
||||||
|
vertical-align: middle;
|
||||||
|
color: #909399;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user