fix(auth): move admin session to HttpOnly cookies
This commit is contained in:
1 parent
0d2165cf80
commit
17b2ba49a8
39 files changed
+187
-215
No files matched your search
@@ -60,6 +60,12 @@
|
|||||||
<artifactId>bizlog-sdk</artifactId>
|
<artifactId>bizlog-sdk</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- 业务组件 -->
|
<!-- 业务组件 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>co.yixiang.boot</groupId>
|
<groupId>co.yixiang.boot</groupId>
|
||||||
|
|||||||
+6
@@ -48,4 +48,10 @@ public class SecurityProperties {
|
|||||||
* PasswordEncoder 加密复杂度,越高开销越大
|
* PasswordEncoder 加密复杂度,越高开销越大
|
||||||
*/
|
*/
|
||||||
private Integer passwordEncoderLength = 4;
|
private Integer passwordEncoderLength = 4;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 管理端认证 Cookie 是否仅通过 HTTPS 发送
|
||||||
|
*/
|
||||||
|
@NotNull(message = "Cookie Secure 开关不能为空")
|
||||||
|
private Boolean cookieSecure = true;
|
||||||
}
|
}
|
||||||
+8
-1
@@ -11,6 +11,7 @@ import org.springframework.security.core.context.SecurityContext;
|
|||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
|
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
|
import org.springframework.web.util.WebUtils;
|
||||||
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@@ -22,6 +23,9 @@ import java.util.Collections;
|
|||||||
*/
|
*/
|
||||||
public class SecurityFrameworkUtils {
|
public class SecurityFrameworkUtils {
|
||||||
|
|
||||||
|
public static final String ACCESS_TOKEN_COOKIE = "yshop_admin_access_token";
|
||||||
|
public static final String REFRESH_TOKEN_COOKIE = "yshop_admin_refresh_token";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HEADER 认证头 value 的前缀
|
* HEADER 认证头 value 的前缀
|
||||||
*/
|
*/
|
||||||
@@ -39,11 +43,14 @@ public class SecurityFrameworkUtils {
|
|||||||
*/
|
*/
|
||||||
public static String obtainAuthorization(HttpServletRequest request,
|
public static String obtainAuthorization(HttpServletRequest request,
|
||||||
String headerName, String parameterName) {
|
String headerName, String parameterName) {
|
||||||
// 1. 获得 Token。优先级:Header > Parameter
|
// 1. 获得 Token。优先级:Header > Parameter > HttpOnly Cookie
|
||||||
String token = request.getHeader(headerName);
|
String token = request.getHeader(headerName);
|
||||||
if (StrUtil.isEmpty(token)) {
|
if (StrUtil.isEmpty(token)) {
|
||||||
token = request.getParameter(parameterName);
|
token = request.getParameter(parameterName);
|
||||||
}
|
}
|
||||||
|
if (StrUtil.isEmpty(token) && WebUtils.getCookie(request, ACCESS_TOKEN_COOKIE) != null) {
|
||||||
|
token = WebUtils.getCookie(request, ACCESS_TOKEN_COOKIE).getValue();
|
||||||
|
}
|
||||||
if (!StringUtils.hasText(token)) {
|
if (!StringUtils.hasText(token)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
+21
@@ -0,0 +1,21 @@
|
|||||||
|
package co.yixiang.yshop.framework.security.core.util;
|
||||||
|
|
||||||
|
import jakarta.servlet.http.Cookie;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.mock.web.MockHttpServletRequest;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
class SecurityFrameworkUtilsTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void obtainAuthorizationUsesCookieWhenHeaderIsMissing() {
|
||||||
|
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||||
|
request.setCookies(new Cookie(SecurityFrameworkUtils.ACCESS_TOKEN_COOKIE, "cookie-token"));
|
||||||
|
|
||||||
|
assertEquals("cookie-token", SecurityFrameworkUtils.obtainAuthorization(request, "Authorization", "token"));
|
||||||
|
|
||||||
|
request.addHeader("Authorization", "Bearer header-token");
|
||||||
|
assertEquals("header-token", SecurityFrameworkUtils.obtainAuthorization(request, "Authorization", "token"));
|
||||||
|
}
|
||||||
|
}
|
||||||
+65
-1
@@ -24,19 +24,28 @@ import io.swagger.v3.oas.annotations.Parameter;
|
|||||||
import io.swagger.v3.oas.annotations.Parameters;
|
import io.swagger.v3.oas.annotations.Parameters;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.ResponseCookie;
|
||||||
import org.springframework.validation.annotation.Validated;
|
import org.springframework.validation.annotation.Validated;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import org.springframework.web.util.WebUtils;
|
||||||
|
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import jakarta.annotation.security.PermitAll;
|
import jakarta.annotation.security.PermitAll;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import jakarta.validation.Valid;
|
import jakarta.validation.Valid;
|
||||||
|
import java.time.Duration;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static co.yixiang.yshop.framework.common.exception.enums.GlobalErrorCodeConstants.UNAUTHORIZED;
|
||||||
|
import static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||||
import static co.yixiang.yshop.framework.common.pojo.CommonResult.success;
|
import static co.yixiang.yshop.framework.common.pojo.CommonResult.success;
|
||||||
import static co.yixiang.yshop.framework.common.util.collection.CollectionUtils.convertSet;
|
import static co.yixiang.yshop.framework.common.util.collection.CollectionUtils.convertSet;
|
||||||
|
import static co.yixiang.yshop.framework.security.core.util.SecurityFrameworkUtils.ACCESS_TOKEN_COOKIE;
|
||||||
|
import static co.yixiang.yshop.framework.security.core.util.SecurityFrameworkUtils.REFRESH_TOKEN_COOKIE;
|
||||||
import static co.yixiang.yshop.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
|
import static co.yixiang.yshop.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
|
||||||
|
|
||||||
@Tag(name = "管理后台 - 认证")
|
@Tag(name = "管理后台 - 认证")
|
||||||
@@ -69,15 +78,25 @@ public class AuthController {
|
|||||||
return success(authService.login(reqVO));
|
return success(authService.login(reqVO));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping("/login-cookie")
|
||||||
|
@PermitAll
|
||||||
|
@Operation(summary = "使用账号密码登录(HttpOnly Cookie)")
|
||||||
|
public CommonResult<Boolean> loginCookie(@RequestBody @Valid AuthLoginReqVO reqVO,
|
||||||
|
HttpServletResponse response) {
|
||||||
|
writeTokenCookies(response, authService.login(reqVO));
|
||||||
|
return success(true);
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping("/logout")
|
@PostMapping("/logout")
|
||||||
@PermitAll
|
@PermitAll
|
||||||
@Operation(summary = "登出系统")
|
@Operation(summary = "登出系统")
|
||||||
public CommonResult<Boolean> logout(HttpServletRequest request) {
|
public CommonResult<Boolean> logout(HttpServletRequest request, HttpServletResponse response) {
|
||||||
String token = SecurityFrameworkUtils.obtainAuthorization(request,
|
String token = SecurityFrameworkUtils.obtainAuthorization(request,
|
||||||
securityProperties.getTokenHeader(), securityProperties.getTokenParameter());
|
securityProperties.getTokenHeader(), securityProperties.getTokenParameter());
|
||||||
if (StrUtil.isNotBlank(token)) {
|
if (StrUtil.isNotBlank(token)) {
|
||||||
authService.logout(token, LoginLogTypeEnum.LOGOUT_SELF.getType());
|
authService.logout(token, LoginLogTypeEnum.LOGOUT_SELF.getType());
|
||||||
}
|
}
|
||||||
|
clearTokenCookies(response);
|
||||||
return success(true);
|
return success(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,6 +108,18 @@ public class AuthController {
|
|||||||
return success(authService.refreshToken(refreshToken));
|
return success(authService.refreshToken(refreshToken));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping("/refresh-token-cookie")
|
||||||
|
@PermitAll
|
||||||
|
@Operation(summary = "刷新令牌(HttpOnly Cookie)")
|
||||||
|
public CommonResult<Boolean> refreshTokenCookie(HttpServletRequest request, HttpServletResponse response) {
|
||||||
|
if (WebUtils.getCookie(request, REFRESH_TOKEN_COOKIE) == null) {
|
||||||
|
throw exception(UNAUTHORIZED);
|
||||||
|
}
|
||||||
|
writeTokenCookies(response,
|
||||||
|
authService.refreshToken(WebUtils.getCookie(request, REFRESH_TOKEN_COOKIE).getValue()));
|
||||||
|
return success(true);
|
||||||
|
}
|
||||||
|
|
||||||
@GetMapping("/get-permission-info")
|
@GetMapping("/get-permission-info")
|
||||||
@Operation(summary = "获取登录用户的权限信息")
|
@Operation(summary = "获取登录用户的权限信息")
|
||||||
public CommonResult<AuthPermissionInfoRespVO> getPermissionInfo() {
|
public CommonResult<AuthPermissionInfoRespVO> getPermissionInfo() {
|
||||||
@@ -124,6 +155,15 @@ public class AuthController {
|
|||||||
return success(authService.smsLogin(reqVO));
|
return success(authService.smsLogin(reqVO));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping("/sms-login-cookie")
|
||||||
|
@PermitAll
|
||||||
|
@Operation(summary = "使用短信验证码登录(HttpOnly Cookie)")
|
||||||
|
public CommonResult<Boolean> smsLoginCookie(@RequestBody @Valid AuthSmsLoginReqVO reqVO,
|
||||||
|
HttpServletResponse response) {
|
||||||
|
writeTokenCookies(response, authService.smsLogin(reqVO));
|
||||||
|
return success(true);
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping("/send-sms-code")
|
@PostMapping("/send-sms-code")
|
||||||
@PermitAll
|
@PermitAll
|
||||||
@Operation(summary = "发送手机验证码")
|
@Operation(summary = "发送手机验证码")
|
||||||
@@ -154,4 +194,28 @@ public class AuthController {
|
|||||||
return success(authService.socialLogin(reqVO));
|
return success(authService.socialLogin(reqVO));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping("/social-login-cookie")
|
||||||
|
@PermitAll
|
||||||
|
@Operation(summary = "社交快捷登录(HttpOnly Cookie)")
|
||||||
|
public CommonResult<Boolean> socialQuickLoginCookie(@RequestBody @Valid AuthSocialLoginReqVO reqVO,
|
||||||
|
HttpServletResponse response) {
|
||||||
|
writeTokenCookies(response, authService.socialLogin(reqVO));
|
||||||
|
return success(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeTokenCookies(HttpServletResponse response, AuthLoginRespVO token) {
|
||||||
|
response.addHeader(HttpHeaders.SET_COOKIE, tokenCookie(ACCESS_TOKEN_COOKIE, token.getAccessToken()).toString());
|
||||||
|
response.addHeader(HttpHeaders.SET_COOKIE, tokenCookie(REFRESH_TOKEN_COOKIE, token.getRefreshToken()).toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void clearTokenCookies(HttpServletResponse response) {
|
||||||
|
response.addHeader(HttpHeaders.SET_COOKIE, tokenCookie(ACCESS_TOKEN_COOKIE, "").maxAge(Duration.ZERO).toString());
|
||||||
|
response.addHeader(HttpHeaders.SET_COOKIE, tokenCookie(REFRESH_TOKEN_COOKIE, "").maxAge(Duration.ZERO).toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private ResponseCookie.ResponseCookieBuilder tokenCookie(String name, String value) {
|
||||||
|
return ResponseCookie.from(name, value).httpOnly(true).secure(securityProperties.getCookieSecure())
|
||||||
|
.sameSite("Strict").path("/");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -192,6 +192,7 @@ yshop:
|
|||||||
enable: false # 本地环境,暂时关闭图片验证码,方便登录等接口的测试;
|
enable: false # 本地环境,暂时关闭图片验证码,方便登录等接口的测试;
|
||||||
security:
|
security:
|
||||||
mock-enable: true
|
mock-enable: true
|
||||||
|
cookie-secure: false
|
||||||
xss:
|
xss:
|
||||||
enable: false
|
enable: false
|
||||||
exclude-urls: # 如下两个 url,仅仅是为了演示,去掉配置也没关系
|
exclude-urls: # 如下两个 url,仅仅是为了演示,去掉配置也没关系
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import request from '@/config/axios'
|
import request from '@/config/axios'
|
||||||
import { getRefreshToken } from '@/utils/auth'
|
|
||||||
import type { UserLoginVO } from './types'
|
import type { UserLoginVO } from './types'
|
||||||
|
|
||||||
export interface SmsCodeVO {
|
export interface SmsCodeVO {
|
||||||
@@ -14,12 +13,12 @@ export interface SmsLoginVO {
|
|||||||
|
|
||||||
// 登录
|
// 登录
|
||||||
export const login = (data: UserLoginVO) => {
|
export const login = (data: UserLoginVO) => {
|
||||||
return request.post({ url: '/system/auth/login', data })
|
return request.post({ url: '/system/auth/login-cookie', data })
|
||||||
}
|
}
|
||||||
|
|
||||||
// 刷新访问令牌
|
// 刷新访问令牌
|
||||||
export const refreshToken = () => {
|
export const refreshToken = () => {
|
||||||
return request.post({ url: '/system/auth/refresh-token?refreshToken=' + getRefreshToken() })
|
return request.post({ url: '/system/auth/refresh-token-cookie' })
|
||||||
}
|
}
|
||||||
|
|
||||||
// 使用租户名,获得租户编号
|
// 使用租户名,获得租户编号
|
||||||
@@ -49,13 +48,13 @@ export const sendSmsCode = (data: SmsCodeVO) => {
|
|||||||
|
|
||||||
// 短信验证码登录
|
// 短信验证码登录
|
||||||
export const smsLogin = (data: SmsLoginVO) => {
|
export const smsLogin = (data: SmsLoginVO) => {
|
||||||
return request.post({ url: '/system/auth/sms-login', data })
|
return request.post({ url: '/system/auth/sms-login-cookie', data })
|
||||||
}
|
}
|
||||||
|
|
||||||
// 社交快捷登录,使用 code 授权码
|
// 社交快捷登录,使用 code 授权码
|
||||||
export function socialLogin(type: string, code: string, state: string) {
|
export function socialLogin(type: string, code: string, state: string) {
|
||||||
return request.post({
|
return request.post({
|
||||||
url: '/system/auth/social-login',
|
url: '/system/auth/social-login-cookie',
|
||||||
data: {
|
data: {
|
||||||
type,
|
type,
|
||||||
code,
|
code,
|
||||||
|
|||||||
@@ -7,16 +7,6 @@ export type UserLoginVO = {
|
|||||||
socialState?: string
|
socialState?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TokenType = {
|
|
||||||
id: number // 编号
|
|
||||||
accessToken: string // 访问令牌
|
|
||||||
refreshToken: string // 刷新令牌
|
|
||||||
userId: number // 用户编号
|
|
||||||
userType: number //用户类型
|
|
||||||
clientId: string //客户端编号
|
|
||||||
expiresTime: number //过期时间
|
|
||||||
}
|
|
||||||
|
|
||||||
export type UserVO = {
|
export type UserVO = {
|
||||||
id: number
|
id: number
|
||||||
username: string
|
username: string
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { propTypes } from '@/utils/propTypes'
|
|||||||
import { isNumber } from '@/utils/is'
|
import { isNumber } from '@/utils/is'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
import { useLocaleStore } from '@/store/modules/locale'
|
import { useLocaleStore } from '@/store/modules/locale'
|
||||||
import { getAccessToken, getTenantId } from '@/utils/auth'
|
import { getTenantId } from '@/utils/auth'
|
||||||
|
|
||||||
defineOptions({ name: 'Editor' })
|
defineOptions({ name: 'Editor' })
|
||||||
|
|
||||||
@@ -104,10 +104,8 @@ const editorConfig = computed((): IEditorConfig => {
|
|||||||
// 自定义增加 http header
|
// 自定义增加 http header
|
||||||
headers: {
|
headers: {
|
||||||
Accept: '*',
|
Accept: '*',
|
||||||
Authorization: 'Bearer ' + getAccessToken(),
|
|
||||||
'tenant-id': getTenantId()
|
'tenant-id': getTenantId()
|
||||||
},
|
},
|
||||||
|
|
||||||
// 跨域是否传递 cookie ,默认为 false
|
// 跨域是否传递 cookie ,默认为 false
|
||||||
withCredentials: true,
|
withCredentials: true,
|
||||||
|
|
||||||
|
|||||||
@@ -99,7 +99,7 @@
|
|||||||
<el-col :span="12" style="text-align: right">
|
<el-col :span="12" style="text-align: right">
|
||||||
<el-upload
|
<el-upload
|
||||||
:action="uploadApi"
|
:action="uploadApi"
|
||||||
:headers="headers"
|
:with-credentials="true"
|
||||||
:file-list="[]"
|
:file-list="[]"
|
||||||
:on-progress="handleProgress"
|
:on-progress="handleProgress"
|
||||||
:before-upload="beforeUpload"
|
:before-upload="beforeUpload"
|
||||||
@@ -182,7 +182,6 @@ import {
|
|||||||
putObj as materialgroupEdit
|
putObj as materialgroupEdit
|
||||||
} from '@/api/tools/materialgroup'
|
} from '@/api/tools/materialgroup'
|
||||||
import { getPage, addObj, delObj } from '@/api/tools/material'
|
import { getPage, addObj, delObj } from '@/api/tools/material'
|
||||||
import { getAccessToken } from '@/utils/auth'
|
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
modelValue: {
|
modelValue: {
|
||||||
@@ -218,10 +217,6 @@ const props = defineProps({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const headers = ref({
|
|
||||||
// Authorization: getToken()
|
|
||||||
Authorization: 'Bearer ' + getAccessToken(),
|
|
||||||
})
|
|
||||||
//const value = ref(props.value)
|
//const value = ref(props.value)
|
||||||
const dialogVisible = ref(false)
|
const dialogVisible = ref(false)
|
||||||
const url = ref('')
|
const url = ref('')
|
||||||
|
|||||||
@@ -50,7 +50,7 @@
|
|||||||
<el-col :span="12" style="text-align: right">
|
<el-col :span="12" style="text-align: right">
|
||||||
<el-upload
|
<el-upload
|
||||||
:action="uploadApi"
|
:action="uploadApi"
|
||||||
:headers="headers"
|
:with-credentials="true"
|
||||||
:file-list="[]"
|
:file-list="[]"
|
||||||
:on-progress="handleProgress"
|
:on-progress="handleProgress"
|
||||||
:before-upload="beforeUpload"
|
:before-upload="beforeUpload"
|
||||||
@@ -133,7 +133,6 @@ import {
|
|||||||
putObj as materialgroupEdit
|
putObj as materialgroupEdit
|
||||||
} from '@/api/tools/materialgroup'
|
} from '@/api/tools/materialgroup'
|
||||||
import { getPage, addObj, delObj } from '@/api/tools/material'
|
import { getPage, addObj, delObj } from '@/api/tools/material'
|
||||||
import { getAccessToken } from '@/utils/auth'
|
|
||||||
import '../../../../public/UEditor/dialogs/internal'
|
import '../../../../public/UEditor/dialogs/internal'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@@ -170,10 +169,6 @@ const props = defineProps({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const headers = ref({
|
|
||||||
// Authorization: getToken()
|
|
||||||
Authorization: 'Bearer ' + getAccessToken(),
|
|
||||||
})
|
|
||||||
const value = ref(props.value)
|
const value = ref(props.value)
|
||||||
// const dialogVisible = ref(false)
|
// const dialogVisible = ref(false)
|
||||||
// const url = ref('')
|
// const url = ref('')
|
||||||
|
|||||||
@@ -9,11 +9,10 @@ import axios, {
|
|||||||
import { ElMessage, ElMessageBox, ElNotification } from 'element-plus'
|
import { ElMessage, ElMessageBox, ElNotification } from 'element-plus'
|
||||||
import qs from 'qs'
|
import qs from 'qs'
|
||||||
import { config } from '@/config/axios/config'
|
import { config } from '@/config/axios/config'
|
||||||
import { getAccessToken, getRefreshToken, getTenantId, removeToken, setToken } from '@/utils/auth'
|
import { getTenantId, removeAuthenticated } from '@/utils/auth'
|
||||||
import errorCode from './errorCode'
|
import errorCode from './errorCode'
|
||||||
|
|
||||||
import { resetRouter } from '@/router'
|
import { resetRouter } from '@/router'
|
||||||
import { deleteUserCache } from '@/hooks/web/useCache'
|
|
||||||
|
|
||||||
const tenantEnable = import.meta.env.VITE_APP_TENANT_ENABLE
|
const tenantEnable = import.meta.env.VITE_APP_TENANT_ENABLE
|
||||||
const { result_code, base_url, request_timeout } = config
|
const { result_code, base_url, request_timeout } = config
|
||||||
@@ -30,30 +29,16 @@ export const isRelogin = { show: false }
|
|||||||
let requestList: any[] = []
|
let requestList: any[] = []
|
||||||
// 是否正在刷新中
|
// 是否正在刷新中
|
||||||
let isRefreshToken = false
|
let isRefreshToken = false
|
||||||
// 请求白名单,无须token的接口
|
|
||||||
const whiteList: string[] = ['/login', '/refresh-token']
|
|
||||||
|
|
||||||
// 创建axios实例
|
// 创建axios实例
|
||||||
const service: AxiosInstance = axios.create({
|
const service: AxiosInstance = axios.create({
|
||||||
baseURL: base_url, // api 的 base_url
|
baseURL: base_url, // api 的 base_url
|
||||||
timeout: request_timeout, // 请求超时时间
|
timeout: request_timeout, // 请求超时时间
|
||||||
withCredentials: false // 禁用 Cookie 等信息
|
withCredentials: true
|
||||||
})
|
})
|
||||||
|
|
||||||
// request拦截器
|
// request拦截器
|
||||||
service.interceptors.request.use(
|
service.interceptors.request.use(
|
||||||
(config: InternalAxiosRequestConfig) => {
|
(config: InternalAxiosRequestConfig) => {
|
||||||
// 是否需要设置 token
|
|
||||||
let isToken = (config!.headers || {}).isToken === false
|
|
||||||
whiteList.some((v) => {
|
|
||||||
if (config.url) {
|
|
||||||
config.url.indexOf(v) > -1
|
|
||||||
return (isToken = false)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
if (getAccessToken() && !isToken) {
|
|
||||||
;(config as Recordable).headers.Authorization = 'Bearer ' + getAccessToken() // 让每个请求携带自定义token
|
|
||||||
}
|
|
||||||
// 设置租户
|
// 设置租户
|
||||||
if (tenantEnable && tenantEnable === 'true') {
|
if (tenantEnable && tenantEnable === 'true') {
|
||||||
const tenantId = getTenantId()
|
const tenantId = getTenantId()
|
||||||
@@ -117,16 +102,10 @@ service.interceptors.response.use(
|
|||||||
// 如果未认证,并且未进行刷新令牌,说明可能是访问令牌过期了
|
// 如果未认证,并且未进行刷新令牌,说明可能是访问令牌过期了
|
||||||
if (!isRefreshToken) {
|
if (!isRefreshToken) {
|
||||||
isRefreshToken = true
|
isRefreshToken = true
|
||||||
// 1. 如果获取不到刷新令牌,则只能执行登出操作
|
// 使用 HttpOnly Cookie 刷新访问令牌
|
||||||
if (!getRefreshToken()) {
|
|
||||||
return handleAuthorized()
|
|
||||||
}
|
|
||||||
// 2. 进行刷新访问令牌
|
|
||||||
try {
|
try {
|
||||||
const refreshTokenRes = await refreshToken()
|
await refreshToken()
|
||||||
// 2.1 刷新成功,则回放队列的请求 + 当前请求
|
// 2.1 刷新成功,则回放队列的请求 + 当前请求
|
||||||
setToken((await refreshTokenRes).data.data)
|
|
||||||
config.headers!.Authorization = 'Bearer ' + getAccessToken()
|
|
||||||
requestList.forEach((cb: any) => {
|
requestList.forEach((cb: any) => {
|
||||||
cb()
|
cb()
|
||||||
})
|
})
|
||||||
@@ -148,7 +127,6 @@ service.interceptors.response.use(
|
|||||||
// 添加到队列,等待刷新获取到新的令牌
|
// 添加到队列,等待刷新获取到新的令牌
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
requestList.push(() => {
|
requestList.push(() => {
|
||||||
config.headers!.Authorization = 'Bearer ' + getAccessToken() // 让每个请求携带自定义token 请根据实际情况自行修改
|
|
||||||
resolve(service(config))
|
resolve(service(config))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -200,7 +178,12 @@ service.interceptors.response.use(
|
|||||||
|
|
||||||
const refreshToken = async () => {
|
const refreshToken = async () => {
|
||||||
axios.defaults.headers.common['tenant-id'] = getTenantId()
|
axios.defaults.headers.common['tenant-id'] = getTenantId()
|
||||||
return await axios.post(base_url + '/system/auth/refresh-token?refreshToken=' + getRefreshToken())
|
const response = await axios.post(base_url + '/system/auth/refresh-token-cookie', null, {
|
||||||
|
withCredentials: true
|
||||||
|
})
|
||||||
|
if (response.data?.code !== result_code) {
|
||||||
|
throw new Error(response.data?.msg)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const handleAuthorized = () => {
|
const handleAuthorized = () => {
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
@@ -218,8 +201,7 @@ const handleAuthorized = () => {
|
|||||||
type: 'warning'
|
type: 'warning'
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
resetRouter() // 重置静态路由表
|
resetRouter() // 重置静态路由表
|
||||||
deleteUserCache() // 删除用户缓存
|
removeAuthenticated()
|
||||||
removeToken()
|
|
||||||
isRelogin.show = false
|
isRelogin.show = false
|
||||||
// 干掉token后再走一次路由让它过router.beforeEach的校验
|
// 干掉token后再走一次路由让它过router.beforeEach的校验
|
||||||
window.location.href = window.location.href
|
window.location.href = window.location.href
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
import type { App } from 'vue'
|
import type { App } from 'vue'
|
||||||
import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
|
import { useUserStoreWithOut } from '@/store/modules/user'
|
||||||
|
|
||||||
const { t } = useI18n() // 国际化
|
const { t } = useI18n() // 国际化
|
||||||
|
|
||||||
export function hasPermi(app: App<Element>) {
|
export function hasPermi(app: App<Element>) {
|
||||||
app.directive('hasPermi', (el, binding) => {
|
app.directive('hasPermi', (el, binding) => {
|
||||||
const { wsCache } = useCache()
|
|
||||||
const { value } = binding
|
const { value } = binding
|
||||||
const all_permission = '*:*:*'
|
const all_permission = '*:*:*'
|
||||||
const permissions = wsCache.get(CACHE_KEY.USER).permissions
|
const permissions = useUserStoreWithOut().getPermissions
|
||||||
|
|
||||||
if (value && value instanceof Array && value.length > 0) {
|
if (value && value instanceof Array && value.length > 0) {
|
||||||
const permissionFlag = value
|
const permissionFlag = value
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
import type { App } from 'vue'
|
import type { App } from 'vue'
|
||||||
import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
|
import { useUserStoreWithOut } from '@/store/modules/user'
|
||||||
|
|
||||||
const { t } = useI18n() // 国际化
|
const { t } = useI18n() // 国际化
|
||||||
|
|
||||||
export function hasRole(app: App<Element>) {
|
export function hasRole(app: App<Element>) {
|
||||||
app.directive('hasRole', (el, binding) => {
|
app.directive('hasRole', (el, binding) => {
|
||||||
const { wsCache } = useCache()
|
|
||||||
const { value } = binding
|
const { value } = binding
|
||||||
const super_admin = 'admin'
|
const super_admin = 'admin'
|
||||||
const roles = wsCache.get(CACHE_KEY.USER).roles
|
const roles = useUserStoreWithOut().getRoles
|
||||||
|
|
||||||
if (value && value instanceof Array && value.length > 0) {
|
if (value && value instanceof Array && value.length > 0) {
|
||||||
const roleFlag = value
|
const roleFlag = value
|
||||||
|
|||||||
@@ -7,9 +7,6 @@ import WebStorageCache from 'web-storage-cache'
|
|||||||
type CacheType = 'localStorage' | 'sessionStorage'
|
type CacheType = 'localStorage' | 'sessionStorage'
|
||||||
|
|
||||||
export const CACHE_KEY = {
|
export const CACHE_KEY = {
|
||||||
// 用户相关
|
|
||||||
ROLE_ROUTERS: 'roleRouters',
|
|
||||||
USER: 'user',
|
|
||||||
// 系统设置
|
// 系统设置
|
||||||
IS_DARK: 'isDark',
|
IS_DARK: 'isDark',
|
||||||
LANG: 'lang',
|
LANG: 'lang',
|
||||||
@@ -30,10 +27,3 @@ export const useCache = (type: CacheType = 'localStorage') => {
|
|||||||
wsCache
|
wsCache
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const deleteUserCache = () => {
|
|
||||||
const { wsCache } = useCache()
|
|
||||||
wsCache.delete(CACHE_KEY.USER)
|
|
||||||
wsCache.delete(CACHE_KEY.ROLE_ROUTERS)
|
|
||||||
// 注意,不要清理 LoginForm 登录表单
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { resetRouter } from '@/router'
|
import { resetRouter } from '@/router'
|
||||||
import { deleteUserCache } from '@/hooks/web/useCache'
|
|
||||||
import { useLockStore } from '@/store/modules/lock'
|
import { useLockStore } from '@/store/modules/lock'
|
||||||
import { useNow } from '@/hooks/web/useNow'
|
import { useNow } from '@/hooks/web/useNow'
|
||||||
import { useDesign } from '@/hooks/web/useDesign'
|
import { useDesign } from '@/hooks/web/useDesign'
|
||||||
@@ -50,7 +49,6 @@ async function unLock() {
|
|||||||
async function goLogin() {
|
async function goLogin() {
|
||||||
await userStore.loginOut().catch(() => {})
|
await userStore.loginOut().catch(() => {})
|
||||||
// 登出后清理
|
// 登出后清理
|
||||||
deleteUserCache() // 清空用户缓存
|
|
||||||
tagsViewStore.delAllViews()
|
tagsViewStore.delAllViews()
|
||||||
resetRouter() // 重置静态路由表
|
resetRouter() // 重置静态路由表
|
||||||
lockStore.resetLockInfo()
|
lockStore.resetLockInfo()
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import router from './router'
|
import router from './router'
|
||||||
import type { RouteRecordRaw } from 'vue-router'
|
import type { RouteRecordRaw } from 'vue-router'
|
||||||
import { isRelogin } from '@/config/axios/service'
|
import { isRelogin } from '@/config/axios/service'
|
||||||
import { getAccessToken } from '@/utils/auth'
|
import { isAuthenticated } from '@/utils/auth'
|
||||||
import { useTitle } from '@/hooks/web/useTitle'
|
import { useTitle } from '@/hooks/web/useTitle'
|
||||||
import { useNProgress } from '@/hooks/web/useNProgress'
|
import { useNProgress } from '@/hooks/web/useNProgress'
|
||||||
import { usePageLoading } from '@/hooks/web/usePageLoading'
|
import { usePageLoading } from '@/hooks/web/usePageLoading'
|
||||||
@@ -60,7 +60,7 @@ const whiteList = [
|
|||||||
router.beforeEach(async (to, from, next) => {
|
router.beforeEach(async (to, from, next) => {
|
||||||
start()
|
start()
|
||||||
loadStart()
|
loadStart()
|
||||||
if (getAccessToken()) {
|
if (isAuthenticated()) {
|
||||||
if (to.path === '/login') {
|
if (to.path === '/login') {
|
||||||
next({ path: '/' })
|
next({ path: '/' })
|
||||||
} else {
|
} else {
|
||||||
@@ -76,7 +76,7 @@ router.beforeEach(async (to, from, next) => {
|
|||||||
await userStore.setUserInfoAction()
|
await userStore.setUserInfoAction()
|
||||||
isRelogin.show = false
|
isRelogin.show = false
|
||||||
// 后端过滤菜单
|
// 后端过滤菜单
|
||||||
await permissionStore.generateRoutes()
|
await permissionStore.generateRoutes(userStore.getMenus)
|
||||||
permissionStore.getAddRouters.forEach((route) => {
|
permissionStore.getAddRouters.forEach((route) => {
|
||||||
router.addRoute(route as unknown as RouteRecordRaw) // 动态添加可访问路由表
|
router.addRoute(route as unknown as RouteRecordRaw) // 动态添加可访问路由表
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -3,9 +3,6 @@ import { store } from '@/store'
|
|||||||
import { cloneDeep } from 'lodash-es'
|
import { cloneDeep } from 'lodash-es'
|
||||||
import remainingRouter from '@/router/modules/remaining'
|
import remainingRouter from '@/router/modules/remaining'
|
||||||
import { flatMultiLevelRoutes, generateRoute } from '@/utils/routerHelper'
|
import { flatMultiLevelRoutes, generateRoute } from '@/utils/routerHelper'
|
||||||
import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
|
|
||||||
|
|
||||||
const { wsCache } = useCache()
|
|
||||||
|
|
||||||
export interface PermissionState {
|
export interface PermissionState {
|
||||||
routers: AppRouteRecordRaw[]
|
routers: AppRouteRecordRaw[]
|
||||||
@@ -31,13 +28,9 @@ export const usePermissionStore = defineStore('permission', {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
async generateRoutes(): Promise<unknown> {
|
async generateRoutes(res: AppCustomRouteRecordRaw[] = []): Promise<unknown> {
|
||||||
return new Promise<void>(async (resolve) => {
|
return new Promise<void>(async (resolve) => {
|
||||||
// 获得菜单列表,它在登录的时候,setUserInfoAction 方法中已经进行获取
|
// 获得菜单列表,它在登录的时候,setUserInfoAction 方法中已经进行获取
|
||||||
let res: AppCustomRouteRecordRaw[] = []
|
|
||||||
if (wsCache.get(CACHE_KEY.ROLE_ROUTERS)) {
|
|
||||||
res = wsCache.get(CACHE_KEY.ROLE_ROUTERS) as AppCustomRouteRecordRaw[]
|
|
||||||
}
|
|
||||||
const routerMap: AppRouteRecordRaw[] = generateRoute(res)
|
const routerMap: AppRouteRecordRaw[] = generateRoute(res)
|
||||||
// 动态路由,404一定要放到最后面
|
// 动态路由,404一定要放到最后面
|
||||||
this.addRouters = routerMap.concat([
|
this.addRouters = routerMap.concat([
|
||||||
|
|||||||
@@ -1,24 +1,22 @@
|
|||||||
import { store } from '@/store'
|
import { store } from '@/store'
|
||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import { getAccessToken, removeToken } from '@/utils/auth'
|
import { isAuthenticated, removeAuthenticated } from '@/utils/auth'
|
||||||
import { CACHE_KEY, useCache, deleteUserCache } from '@/hooks/web/useCache'
|
|
||||||
import { getInfo, loginOut } from '@/api/login'
|
import { getInfo, loginOut } from '@/api/login'
|
||||||
|
|
||||||
const { wsCache } = useCache()
|
|
||||||
|
|
||||||
interface UserVO {
|
interface UserVO {
|
||||||
id: number
|
id: number
|
||||||
avatar: string
|
avatar: string
|
||||||
nickname: string
|
nickname: string
|
||||||
deptId: number
|
deptId: number
|
||||||
|
shopId?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UserInfoVO {
|
interface UserInfoVO {
|
||||||
// USER 缓存
|
|
||||||
permissions: string[]
|
permissions: string[]
|
||||||
roles: string[]
|
roles: string[]
|
||||||
isSetUser: boolean
|
isSetUser: boolean
|
||||||
user: UserVO
|
user: UserVO
|
||||||
|
menus: AppCustomRouteRecordRaw[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useUserStore = defineStore('admin-user', {
|
export const useUserStore = defineStore('admin-user', {
|
||||||
@@ -31,7 +29,8 @@ export const useUserStore = defineStore('admin-user', {
|
|||||||
avatar: '',
|
avatar: '',
|
||||||
nickname: '',
|
nickname: '',
|
||||||
deptId: 0
|
deptId: 0
|
||||||
}
|
},
|
||||||
|
menus: []
|
||||||
}),
|
}),
|
||||||
getters: {
|
getters: {
|
||||||
getPermissions(): string[] {
|
getPermissions(): string[] {
|
||||||
@@ -45,49 +44,43 @@ export const useUserStore = defineStore('admin-user', {
|
|||||||
},
|
},
|
||||||
getUser(): UserVO {
|
getUser(): UserVO {
|
||||||
return this.user
|
return this.user
|
||||||
|
},
|
||||||
|
getMenus(): AppCustomRouteRecordRaw[] {
|
||||||
|
return this.menus
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
async setUserInfoAction() {
|
async setUserInfoAction() {
|
||||||
if (!getAccessToken()) {
|
if (!isAuthenticated()) {
|
||||||
this.resetState()
|
this.resetState()
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
let userInfo = wsCache.get(CACHE_KEY.USER)
|
const userInfo = await getInfo()
|
||||||
if (!userInfo) {
|
|
||||||
userInfo = await getInfo()
|
|
||||||
}
|
|
||||||
this.permissions = userInfo.permissions
|
this.permissions = userInfo.permissions
|
||||||
this.roles = userInfo.roles
|
this.roles = userInfo.roles
|
||||||
this.user = userInfo.user
|
this.user = userInfo.user
|
||||||
|
this.menus = userInfo.menus
|
||||||
this.isSetUser = true
|
this.isSetUser = true
|
||||||
wsCache.set(CACHE_KEY.USER, userInfo)
|
|
||||||
wsCache.set(CACHE_KEY.ROLE_ROUTERS, userInfo.menus)
|
|
||||||
},
|
},
|
||||||
async setUserAvatarAction(avatar: string) {
|
async setUserAvatarAction(avatar: string) {
|
||||||
const userInfo = wsCache.get(CACHE_KEY.USER)
|
|
||||||
// NOTE: 是否需要像`setUserInfoAction`一样判断`userInfo != null`
|
|
||||||
this.user.avatar = avatar
|
this.user.avatar = avatar
|
||||||
userInfo.user.avatar = avatar
|
|
||||||
wsCache.set(CACHE_KEY.USER, userInfo)
|
|
||||||
},
|
},
|
||||||
async setUserNicknameAction(nickname: string) {
|
async setUserNicknameAction(nickname: string) {
|
||||||
const userInfo = wsCache.get(CACHE_KEY.USER)
|
|
||||||
// NOTE: 是否需要像`setUserInfoAction`一样判断`userInfo != null`
|
|
||||||
this.user.nickname = nickname
|
this.user.nickname = nickname
|
||||||
userInfo.user.nickname = nickname
|
|
||||||
wsCache.set(CACHE_KEY.USER, userInfo)
|
|
||||||
},
|
},
|
||||||
async loginOut() {
|
async loginOut() {
|
||||||
|
try {
|
||||||
await loginOut()
|
await loginOut()
|
||||||
removeToken()
|
} finally {
|
||||||
deleteUserCache() // 删除用户缓存
|
removeAuthenticated()
|
||||||
this.resetState()
|
this.resetState()
|
||||||
|
}
|
||||||
},
|
},
|
||||||
resetState() {
|
resetState() {
|
||||||
this.permissions = []
|
this.permissions = []
|
||||||
this.roles = []
|
this.roles = []
|
||||||
this.isSetUser = false
|
this.isSetUser = false
|
||||||
|
this.menus = []
|
||||||
this.user = {
|
this.user = {
|
||||||
id: 0,
|
id: 0,
|
||||||
avatar: '',
|
avatar: '',
|
||||||
|
|||||||
@@ -1,38 +1,18 @@
|
|||||||
import { useCache, CACHE_KEY } from '@/hooks/web/useCache'
|
import { useCache, CACHE_KEY } from '@/hooks/web/useCache'
|
||||||
import { TokenType } from '@/api/login/types'
|
|
||||||
|
|
||||||
const { wsCache } = useCache()
|
const { wsCache } = useCache()
|
||||||
|
const { wsCache: sessionCache } = useCache('sessionStorage')
|
||||||
|
|
||||||
const AccessTokenKey = 'ACCESS_TOKEN'
|
wsCache.delete('ACCESS_TOKEN')
|
||||||
const RefreshTokenKey = 'REFRESH_TOKEN'
|
wsCache.delete('REFRESH_TOKEN')
|
||||||
|
wsCache.delete('user')
|
||||||
|
wsCache.delete('roleRouters')
|
||||||
|
|
||||||
// 获取token
|
const AuthenticatedKey = 'AUTHENTICATED'
|
||||||
export const getAccessToken = () => {
|
|
||||||
// 此处与TokenKey相同,此写法解决初始化时Cookies中不存在TokenKey报错
|
|
||||||
return wsCache.get(AccessTokenKey) ? wsCache.get(AccessTokenKey) : wsCache.get('ACCESS_TOKEN')
|
|
||||||
}
|
|
||||||
|
|
||||||
// 刷新token
|
export const isAuthenticated = () => sessionCache.get(AuthenticatedKey) === true
|
||||||
export const getRefreshToken = () => {
|
export const setAuthenticated = () => sessionCache.set(AuthenticatedKey, true)
|
||||||
return wsCache.get(RefreshTokenKey)
|
export const removeAuthenticated = () => sessionCache.delete(AuthenticatedKey)
|
||||||
}
|
|
||||||
|
|
||||||
// 设置token
|
|
||||||
export const setToken = (token: TokenType) => {
|
|
||||||
wsCache.set(RefreshTokenKey, token.refreshToken)
|
|
||||||
wsCache.set(AccessTokenKey, token.accessToken)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 删除token
|
|
||||||
export const removeToken = () => {
|
|
||||||
wsCache.delete(AccessTokenKey)
|
|
||||||
wsCache.delete(RefreshTokenKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 格式化token(jwt格式) */
|
|
||||||
export const formatToken = (token: string): string => {
|
|
||||||
return 'Bearer ' + token
|
|
||||||
}
|
|
||||||
// ========== 账号相关 ==========
|
// ========== 账号相关 ==========
|
||||||
|
|
||||||
export type LoginFormType = {
|
export type LoginFormType = {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
|
import { useUserStoreWithOut } from '@/store/modules/user'
|
||||||
|
|
||||||
const { t } = useI18n() // 国际化
|
const { t } = useI18n() // 国际化
|
||||||
|
|
||||||
@@ -9,10 +9,9 @@ const { t } = useI18n() // 国际化
|
|||||||
*/
|
*/
|
||||||
export function checkPermi(value: string[]) {
|
export function checkPermi(value: string[]) {
|
||||||
if (value && value instanceof Array && value.length > 0) {
|
if (value && value instanceof Array && value.length > 0) {
|
||||||
const { wsCache } = useCache()
|
|
||||||
const permissionDatas = value
|
const permissionDatas = value
|
||||||
const all_permission = '*:*:*'
|
const all_permission = '*:*:*'
|
||||||
const permissions = wsCache.get(CACHE_KEY.USER).permissions
|
const permissions = useUserStoreWithOut().getPermissions
|
||||||
const hasPermission = permissions.some((permission) => {
|
const hasPermission = permissions.some((permission) => {
|
||||||
return all_permission === permission || permissionDatas.includes(permission)
|
return all_permission === permission || permissionDatas.includes(permission)
|
||||||
})
|
})
|
||||||
@@ -30,10 +29,9 @@ export function checkPermi(value: string[]) {
|
|||||||
*/
|
*/
|
||||||
export function checkRole(value: string[]) {
|
export function checkRole(value: string[]) {
|
||||||
if (value && value instanceof Array && value.length > 0) {
|
if (value && value instanceof Array && value.length > 0) {
|
||||||
const { wsCache } = useCache()
|
|
||||||
const permissionRoles = value
|
const permissionRoles = value
|
||||||
const super_admin = 'admin'
|
const super_admin = 'admin'
|
||||||
const roles = wsCache.get(CACHE_KEY.USER).roles
|
const roles = useUserStoreWithOut().getRoles
|
||||||
const hasRole = roles.some((role) => {
|
const hasRole = roles.some((role) => {
|
||||||
return super_admin === role || permissionRoles.includes(role)
|
return super_admin === role || permissionRoles.includes(role)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -69,9 +69,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
|
import { useUserStore } from '@/store/modules/user'
|
||||||
|
|
||||||
const { wsCache } = useCache()
|
|
||||||
|
|
||||||
|
|
||||||
const grid =ref({
|
const grid =ref({
|
||||||
@@ -84,10 +82,10 @@ const grid =ref({
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
const user = wsCache.get(CACHE_KEY.USER)
|
const userStore = useUserStore()
|
||||||
|
|
||||||
const isShow = ref(true)
|
const isShow = ref(true)
|
||||||
if(user.user.shopId > 0) {
|
if ((userStore.user.shopId ?? 0) > 0) {
|
||||||
isShow.value = false
|
isShow.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -251,8 +251,8 @@ const tryLogin = async () => {
|
|||||||
const code = route?.query?.code as string
|
const code = route?.query?.code as string
|
||||||
const state = route?.query?.state as string
|
const state = route?.query?.state as string
|
||||||
|
|
||||||
const res = await LoginApi.socialLogin(type, code, state)
|
await LoginApi.socialLogin(type, code, state)
|
||||||
authUtil.setToken(res)
|
authUtil.setAuthenticated()
|
||||||
|
|
||||||
router.push({ path: redirect || '/' })
|
router.push({ path: redirect || '/' })
|
||||||
} catch (err) {}
|
} catch (err) {}
|
||||||
@@ -297,7 +297,7 @@ const handleLogin = async (params) => {
|
|||||||
} else {
|
} else {
|
||||||
authUtil.removeLoginForm()
|
authUtil.removeLoginForm()
|
||||||
}
|
}
|
||||||
authUtil.setToken(res)
|
authUtil.setAuthenticated()
|
||||||
if (!redirect) {
|
if (!redirect) {
|
||||||
redirect = '/'
|
redirect = '/'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,7 +79,6 @@
|
|||||||
mode="pop"
|
mode="pop"
|
||||||
@success="handleLogin"
|
@success="handleLogin"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
</el-row>
|
</el-row>
|
||||||
</el-form>
|
</el-form>
|
||||||
</template>
|
</template>
|
||||||
@@ -131,8 +130,6 @@ const loginData = reactive({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 获取验证码
|
// 获取验证码
|
||||||
const getCode = async () => {
|
const getCode = async () => {
|
||||||
// 情况一,未开启:则直接登录
|
// 情况一,未开启:则直接登录
|
||||||
@@ -189,7 +186,7 @@ const handleLogin = async (params) => {
|
|||||||
} else {
|
} else {
|
||||||
authUtil.removeLoginForm()
|
authUtil.removeLoginForm()
|
||||||
}
|
}
|
||||||
authUtil.setToken(res)
|
authUtil.setAuthenticated()
|
||||||
if (!redirect.value) {
|
if (!redirect.value) {
|
||||||
redirect.value = '/'
|
redirect.value = '/'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ import type { RouteLocationNormalizedLoaded } from 'vue-router'
|
|||||||
|
|
||||||
import { useIcon } from '@/hooks/web/useIcon'
|
import { useIcon } from '@/hooks/web/useIcon'
|
||||||
|
|
||||||
import { setTenantId, setToken } from '@/utils/auth'
|
import { setTenantId, setAuthenticated } from '@/utils/auth'
|
||||||
import { usePermissionStore } from '@/store/modules/permission'
|
import { usePermissionStore } from '@/store/modules/permission'
|
||||||
import { getTenantIdByName, sendSmsCode, smsLogin } from '@/api/login'
|
import { getTenantIdByName, sendSmsCode, smsLogin } from '@/api/login'
|
||||||
import LoginFormTitle from './LoginFormTitle.vue'
|
import LoginFormTitle from './LoginFormTitle.vue'
|
||||||
@@ -195,8 +195,8 @@ const signIn = async () => {
|
|||||||
smsVO.loginSms.mobile = loginData.loginForm.mobileNumber
|
smsVO.loginSms.mobile = loginData.loginForm.mobileNumber
|
||||||
smsVO.loginSms.code = loginData.loginForm.code
|
smsVO.loginSms.code = loginData.loginForm.code
|
||||||
await smsLogin(smsVO.loginSms)
|
await smsLogin(smsVO.loginSms)
|
||||||
.then(async (res) => {
|
.then(async () => {
|
||||||
setToken(res)
|
setAuthenticated()
|
||||||
if (!redirect.value) {
|
if (!redirect.value) {
|
||||||
redirect.value = '/'
|
redirect.value = '/'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,16 +69,13 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { formatDate } from '@/utils/formatTime'
|
import { formatDate } from '@/utils/formatTime'
|
||||||
import { useWebSocket } from '@vueuse/core'
|
import { useWebSocket } from '@vueuse/core'
|
||||||
import { getAccessToken } from '@/utils/auth'
|
|
||||||
import * as UserApi from '@/api/system/user'
|
import * as UserApi from '@/api/system/user'
|
||||||
|
|
||||||
defineOptions({ name: 'InfraWebSocket' })
|
defineOptions({ name: 'InfraWebSocket' })
|
||||||
|
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
|
|
||||||
const server = ref(
|
const server = ref((import.meta.env.VITE_BASE_URL + '/infra/ws').replace('http', 'ws')) // WebSocket 服务地址
|
||||||
(import.meta.env.VITE_BASE_URL + '/infra/ws').replace('http', 'ws') + '?token=' + getAccessToken()
|
|
||||||
) // WebSocket 服务地址
|
|
||||||
const getIsOpen = computed(() => status.value === 'OPEN') // WebSocket 连接是否打开
|
const getIsOpen = computed(() => status.value === 'OPEN') // WebSocket 连接是否打开
|
||||||
const getTagColor = computed(() => (getIsOpen.value ? 'success' : 'red')) // WebSocket 连接的展示颜色
|
const getTagColor = computed(() => (getIsOpen.value ? 'success' : 'red')) // WebSocket 连接的展示颜色
|
||||||
|
|
||||||
|
|||||||
@@ -106,12 +106,11 @@
|
|||||||
import * as ShopApi from '@/api/mall/store/shop'
|
import * as ShopApi from '@/api/mall/store/shop'
|
||||||
import * as UserApi from '@/api/system/user'
|
import * as UserApi from '@/api/system/user'
|
||||||
import MyMap from './map.vue'
|
import MyMap from './map.vue'
|
||||||
import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
|
import { useUserStore } from '@/store/modules/user'
|
||||||
|
|
||||||
const { wsCache } = useCache()
|
|
||||||
|
|
||||||
const { t } = useI18n() // 国际化
|
const { t } = useI18n() // 国际化
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
|
const userStore = useUserStore()
|
||||||
|
|
||||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||||
const dialogTitle = ref('') // 弹窗的标题
|
const dialogTitle = ref('') // 弹窗的标题
|
||||||
@@ -164,9 +163,7 @@ const isShow = ref(true)
|
|||||||
|
|
||||||
/** 打开弹窗 */
|
/** 打开弹窗 */
|
||||||
const open = async (type: string, id?: number) => {
|
const open = async (type: string, id?: number) => {
|
||||||
const user = wsCache.get(CACHE_KEY.USER)
|
if ((userStore.user.shopId ?? 0) > 0) {
|
||||||
|
|
||||||
if(user.user.shopId > 0) {
|
|
||||||
isShow.value = false
|
isShow.value = false
|
||||||
}
|
}
|
||||||
dialogVisible.value = true
|
dialogVisible.value = true
|
||||||
|
|||||||
@@ -35,7 +35,7 @@
|
|||||||
<el-col :span="12" class="col-add">
|
<el-col :span="12" class="col-add">
|
||||||
<el-upload
|
<el-upload
|
||||||
:action="UPLOAD_URL"
|
:action="UPLOAD_URL"
|
||||||
:headers="HEADERS"
|
:with-credentials="true"
|
||||||
multiple
|
multiple
|
||||||
:limit="1"
|
:limit="1"
|
||||||
:file-list="fileList"
|
:file-list="fileList"
|
||||||
@@ -59,12 +59,10 @@
|
|||||||
import WxMaterialSelect from '@/views/mp/components/wx-material-select'
|
import WxMaterialSelect from '@/views/mp/components/wx-material-select'
|
||||||
import { UploadType, useBeforeUpload } from '@/views/mp/hooks/useUpload'
|
import { UploadType, useBeforeUpload } from '@/views/mp/hooks/useUpload'
|
||||||
import type { UploadRawFile } from 'element-plus'
|
import type { UploadRawFile } from 'element-plus'
|
||||||
import { getAccessToken } from '@/utils/auth'
|
|
||||||
import { Reply } from './types'
|
import { Reply } from './types'
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
|
|
||||||
const UPLOAD_URL = import.meta.env.VITE_BASE_URL + '/admin-api/mp/material/upload-temporary'
|
const UPLOAD_URL = import.meta.env.VITE_BASE_URL + '/admin-api/mp/material/upload-temporary'
|
||||||
const HEADERS = { Authorization: 'Bearer ' + getAccessToken() } // 设置上传的请求头部
|
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
modelValue: Reply
|
modelValue: Reply
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
<div class="thumb-but">
|
<div class="thumb-but">
|
||||||
<el-upload
|
<el-upload
|
||||||
:action="UPLOAD_URL"
|
:action="UPLOAD_URL"
|
||||||
:headers="HEADERS"
|
:with-credentials="true"
|
||||||
multiple
|
multiple
|
||||||
:limit="1"
|
:limit="1"
|
||||||
:file-list="fileList"
|
:file-list="fileList"
|
||||||
@@ -62,13 +62,11 @@
|
|||||||
import WxMaterialSelect from '@/views/mp/components/wx-material-select'
|
import WxMaterialSelect from '@/views/mp/components/wx-material-select'
|
||||||
import type { UploadRawFile } from 'element-plus'
|
import type { UploadRawFile } from 'element-plus'
|
||||||
import { UploadType, useBeforeUpload } from '@/views/mp/hooks/useUpload'
|
import { UploadType, useBeforeUpload } from '@/views/mp/hooks/useUpload'
|
||||||
import { getAccessToken } from '@/utils/auth'
|
|
||||||
import { Reply } from './types'
|
import { Reply } from './types'
|
||||||
|
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
|
|
||||||
const UPLOAD_URL = import.meta.env.VITE_BASE_URL + '/admin-api/mp/material/upload-temporary'
|
const UPLOAD_URL = import.meta.env.VITE_BASE_URL + '/admin-api/mp/material/upload-temporary'
|
||||||
const HEADERS = { Authorization: 'Bearer ' + getAccessToken() } // 设置上传的请求头部
|
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
modelValue: Reply
|
modelValue: Reply
|
||||||
|
|||||||
@@ -31,7 +31,7 @@
|
|||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
<el-upload
|
<el-upload
|
||||||
:action="UPLOAD_URL"
|
:action="UPLOAD_URL"
|
||||||
:headers="HEADERS"
|
:with-credentials="true"
|
||||||
multiple
|
multiple
|
||||||
:limit="1"
|
:limit="1"
|
||||||
:file-list="fileList"
|
:file-list="fileList"
|
||||||
@@ -53,13 +53,11 @@ import WxVideoPlayer from '@/views/mp/components/wx-video-play'
|
|||||||
import WxMaterialSelect from '@/views/mp/components/wx-material-select'
|
import WxMaterialSelect from '@/views/mp/components/wx-material-select'
|
||||||
import type { UploadRawFile } from 'element-plus'
|
import type { UploadRawFile } from 'element-plus'
|
||||||
import { UploadType, useBeforeUpload } from '@/views/mp/hooks/useUpload'
|
import { UploadType, useBeforeUpload } from '@/views/mp/hooks/useUpload'
|
||||||
import { getAccessToken } from '@/utils/auth'
|
|
||||||
import { Reply } from './types'
|
import { Reply } from './types'
|
||||||
|
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
|
|
||||||
const UPLOAD_URL = import.meta.env.VITE_BASE_URL + '/admin-api/mp/material/upload-temporary'
|
const UPLOAD_URL = import.meta.env.VITE_BASE_URL + '/admin-api/mp/material/upload-temporary'
|
||||||
const HEADERS = { Authorization: 'Bearer ' + getAccessToken() }
|
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
modelValue: Reply
|
modelValue: Reply
|
||||||
|
|||||||
@@ -33,7 +33,7 @@
|
|||||||
<el-col :span="12" class="col-add">
|
<el-col :span="12" class="col-add">
|
||||||
<el-upload
|
<el-upload
|
||||||
:action="UPLOAD_URL"
|
:action="UPLOAD_URL"
|
||||||
:headers="HEADERS"
|
:with-credentials="true"
|
||||||
multiple
|
multiple
|
||||||
:limit="1"
|
:limit="1"
|
||||||
:file-list="fileList"
|
:file-list="fileList"
|
||||||
@@ -57,12 +57,10 @@ import WxMaterialSelect from '@/views/mp/components/wx-material-select'
|
|||||||
import WxVoicePlayer from '@/views/mp/components/wx-voice-play'
|
import WxVoicePlayer from '@/views/mp/components/wx-voice-play'
|
||||||
import { UploadType, useBeforeUpload } from '@/views/mp/hooks/useUpload'
|
import { UploadType, useBeforeUpload } from '@/views/mp/hooks/useUpload'
|
||||||
import type { UploadRawFile } from 'element-plus'
|
import type { UploadRawFile } from 'element-plus'
|
||||||
import { getAccessToken } from '@/utils/auth'
|
|
||||||
import { Reply } from './types'
|
import { Reply } from './types'
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
|
|
||||||
const UPLOAD_URL = import.meta.env.VITE_BASE_URL + '/admin-api/mp/material/upload-temporary'
|
const UPLOAD_URL = import.meta.env.VITE_BASE_URL + '/admin-api/mp/material/upload-temporary'
|
||||||
const HEADERS = { Authorization: 'Bearer ' + getAccessToken() } // 设置上传的请求头部
|
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
modelValue: Reply
|
modelValue: Reply
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
<div class="thumb-but">
|
<div class="thumb-but">
|
||||||
<el-upload
|
<el-upload
|
||||||
:action="UPLOAD_URL"
|
:action="UPLOAD_URL"
|
||||||
:headers="HEADERS"
|
:with-credentials="true"
|
||||||
multiple
|
multiple
|
||||||
:limit="1"
|
:limit="1"
|
||||||
:file-list="fileList"
|
:file-list="fileList"
|
||||||
@@ -61,14 +61,12 @@
|
|||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import WxMaterialSelect from '@/views/mp/components/wx-material-select'
|
import WxMaterialSelect from '@/views/mp/components/wx-material-select'
|
||||||
import { getAccessToken } from '@/utils/auth'
|
|
||||||
import type { UploadFiles, UploadProps, UploadRawFile } from 'element-plus'
|
import type { UploadFiles, UploadProps, UploadRawFile } from 'element-plus'
|
||||||
import { UploadType, useBeforeUpload } from '@/views/mp/hooks/useUpload'
|
import { UploadType, useBeforeUpload } from '@/views/mp/hooks/useUpload'
|
||||||
import { NewsItem } from './types'
|
import { NewsItem } from './types'
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
|
|
||||||
const UPLOAD_URL = import.meta.env.VITE_BASE_URL + '/admin-api/mp/material/upload-permanent' // 上传永久素材的地址
|
const UPLOAD_URL = import.meta.env.VITE_BASE_URL + '/admin-api/mp/material/upload-permanent' // 上传永久素材的地址
|
||||||
const HEADERS = { Authorization: 'Bearer ' + getAccessToken() } // 设置上传的请求头部
|
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
modelValue: NewsItem
|
modelValue: NewsItem
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { IEditorConfig } from '@wangeditor/editor'
|
import { IEditorConfig } from '@wangeditor/editor'
|
||||||
import { getAccessToken, getTenantId } from '@/utils/auth'
|
import { getTenantId } from '@/utils/auth'
|
||||||
|
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
|
|
||||||
@@ -31,10 +31,8 @@ export const createEditorConfig = (
|
|||||||
// 自定义增加 http header
|
// 自定义增加 http header
|
||||||
headers: {
|
headers: {
|
||||||
Accept: '*',
|
Accept: '*',
|
||||||
Authorization: 'Bearer ' + getAccessToken(),
|
|
||||||
'tenant-id': getTenantId()
|
'tenant-id': getTenantId()
|
||||||
},
|
},
|
||||||
|
|
||||||
// 跨域是否传递 cookie ,默认为 false
|
// 跨域是否传递 cookie ,默认为 false
|
||||||
withCredentials: true,
|
withCredentials: true,
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-upload
|
<el-upload
|
||||||
:action="UPLOAD_URL"
|
:action="UPLOAD_URL"
|
||||||
:headers="HEADERS"
|
:with-credentials="true"
|
||||||
multiple
|
multiple
|
||||||
:limit="1"
|
:limit="1"
|
||||||
:file-list="fileList"
|
:file-list="fileList"
|
||||||
@@ -20,14 +20,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { UploadProps, UploadUserFile } from 'element-plus'
|
import type { UploadProps, UploadUserFile } from 'element-plus'
|
||||||
import {
|
import { UPLOAD_URL, UploadData, UploadType, beforeImageUpload, beforeVoiceUpload } from './upload'
|
||||||
HEADERS,
|
|
||||||
UPLOAD_URL,
|
|
||||||
UploadData,
|
|
||||||
UploadType,
|
|
||||||
beforeImageUpload,
|
|
||||||
beforeVoiceUpload
|
|
||||||
} from './upload'
|
|
||||||
|
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<el-dialog title="新建视频" v-model="showDialog" width="600px">
|
<el-dialog title="新建视频" v-model="showDialog" width="600px">
|
||||||
<el-upload
|
<el-upload
|
||||||
:action="UPLOAD_URL"
|
:action="UPLOAD_URL"
|
||||||
:headers="HEADERS"
|
:with-credentials="true"
|
||||||
multiple
|
multiple
|
||||||
:limit="1"
|
:limit="1"
|
||||||
:file-list="fileList"
|
:file-list="fileList"
|
||||||
@@ -55,7 +55,7 @@ import type {
|
|||||||
UploadProps,
|
UploadProps,
|
||||||
UploadUserFile
|
UploadUserFile
|
||||||
} from 'element-plus'
|
} from 'element-plus'
|
||||||
import { HEADERS, UploadData, UPLOAD_URL, UploadType, beforeVideoUpload } from './upload'
|
import { UploadData, UPLOAD_URL, UploadType, beforeVideoUpload } from './upload'
|
||||||
|
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import type { UploadProps, UploadRawFile } from 'element-plus'
|
import type { UploadProps, UploadRawFile } from 'element-plus'
|
||||||
import { getAccessToken } from '@/utils/auth'
|
|
||||||
import { UploadType, useBeforeUpload } from '@/views/mp/hooks/useUpload'
|
import { UploadType, useBeforeUpload } from '@/views/mp/hooks/useUpload'
|
||||||
|
|
||||||
const HEADERS = { Authorization: 'Bearer ' + getAccessToken() } // 请求头
|
|
||||||
const UPLOAD_URL = import.meta.env.VITE_BASE_URL + '/admin-api/mp/material/upload-permanent' // 上传地址
|
const UPLOAD_URL = import.meta.env.VITE_BASE_URL + '/admin-api/mp/material/upload-permanent' // 上传地址
|
||||||
|
|
||||||
interface UploadData {
|
interface UploadData {
|
||||||
@@ -22,7 +20,6 @@ const beforeVideoUpload: UploadProps['beforeUpload'] = (rawFile: UploadRawFile)
|
|||||||
useBeforeUpload(UploadType.Video, 10)(rawFile)
|
useBeforeUpload(UploadType.Video, 10)(rawFile)
|
||||||
|
|
||||||
export {
|
export {
|
||||||
HEADERS,
|
|
||||||
UPLOAD_URL,
|
UPLOAD_URL,
|
||||||
UploadType,
|
UploadType,
|
||||||
UploadData,
|
UploadData,
|
||||||
|
|||||||
@@ -115,13 +115,11 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||||
import * as MenuApi from '@/api/system/menu'
|
import * as MenuApi from '@/api/system/menu'
|
||||||
import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
|
|
||||||
import { CommonStatusEnum, SystemMenuTypeEnum } from '@/utils/constants'
|
import { CommonStatusEnum, SystemMenuTypeEnum } from '@/utils/constants'
|
||||||
import { defaultProps, handleTree } from '@/utils/tree'
|
import { defaultProps, handleTree } from '@/utils/tree'
|
||||||
|
|
||||||
defineOptions({ name: 'SystemMenuForm' })
|
defineOptions({ name: 'SystemMenuForm' })
|
||||||
|
|
||||||
const { wsCache } = useCache()
|
|
||||||
const { t } = useI18n() // 国际化
|
const { t } = useI18n() // 国际化
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
|
|
||||||
@@ -213,8 +211,6 @@ const submitForm = async () => {
|
|||||||
emit('success')
|
emit('success')
|
||||||
} finally {
|
} finally {
|
||||||
formLoading.value = false
|
formLoading.value = false
|
||||||
// 清空,从而触发刷新
|
|
||||||
wsCache.delete(CACHE_KEY.ROLE_ROUTERS)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -125,11 +125,9 @@ import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
|||||||
import { handleTree } from '@/utils/tree'
|
import { handleTree } from '@/utils/tree'
|
||||||
import * as MenuApi from '@/api/system/menu'
|
import * as MenuApi from '@/api/system/menu'
|
||||||
import MenuForm from './MenuForm.vue'
|
import MenuForm from './MenuForm.vue'
|
||||||
import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
|
|
||||||
|
|
||||||
defineOptions({ name: 'SystemMenu' })
|
defineOptions({ name: 'SystemMenu' })
|
||||||
|
|
||||||
const { wsCache } = useCache()
|
|
||||||
const { t } = useI18n() // 国际化
|
const { t } = useI18n() // 国际化
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
|
|
||||||
@@ -184,9 +182,6 @@ const toggleExpandAll = () => {
|
|||||||
const refreshMenu = async () => {
|
const refreshMenu = async () => {
|
||||||
try {
|
try {
|
||||||
await message.confirm('即将更新缓存刷新浏览器!', '刷新菜单缓存')
|
await message.confirm('即将更新缓存刷新浏览器!', '刷新菜单缓存')
|
||||||
// 清空,从而触发刷新
|
|
||||||
wsCache.delete(CACHE_KEY.USER)
|
|
||||||
wsCache.delete(CACHE_KEY.ROLE_ROUTERS)
|
|
||||||
// 刷新浏览器
|
// 刷新浏览器
|
||||||
location.reload()
|
location.reload()
|
||||||
} catch {}
|
} catch {}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
:auto-upload="false"
|
:auto-upload="false"
|
||||||
:disabled="formLoading"
|
:disabled="formLoading"
|
||||||
:headers="uploadHeaders"
|
:headers="uploadHeaders"
|
||||||
|
:with-credentials="true"
|
||||||
:limit="1"
|
:limit="1"
|
||||||
:on-error="submitFormError"
|
:on-error="submitFormError"
|
||||||
:on-exceed="handleExceed"
|
:on-exceed="handleExceed"
|
||||||
@@ -42,7 +43,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import * as UserApi from '@/api/system/user'
|
import * as UserApi from '@/api/system/user'
|
||||||
import { getAccessToken, getTenantId } from '@/utils/auth'
|
import { getTenantId } from '@/utils/auth'
|
||||||
import download from '@/utils/download'
|
import download from '@/utils/download'
|
||||||
|
|
||||||
defineOptions({ name: 'SystemUserImportForm' })
|
defineOptions({ name: 'SystemUserImportForm' })
|
||||||
@@ -75,7 +76,6 @@ const submitForm = async () => {
|
|||||||
}
|
}
|
||||||
// 提交请求
|
// 提交请求
|
||||||
uploadHeaders.value = {
|
uploadHeaders.value = {
|
||||||
Authorization: 'Bearer ' + getAccessToken(),
|
|
||||||
'tenant-id': getTenantId()
|
'tenant-id': getTenantId()
|
||||||
}
|
}
|
||||||
formLoading.value = true
|
formLoading.value = true
|
||||||
|
|||||||
Reference in new issue
Block a user