修改,操作其他租户时,无法修改密码和个人信息

This commit is contained in:
Wang Chen Chen
2023-06-21 21:38:54 +08:00
parent b67bc83b63
commit 9fadd17301
22 changed files with 241 additions and 111 deletions

1
.gitignore vendored
View File

@@ -1,6 +1,5 @@
HELP.md
target/
server/molly-service/src/main/resources/application-dev.yml
### STS ###
.apt_generated

31
server/.gitignore vendored
View File

@@ -1,31 +0,0 @@
HELP.md
target/
molly-service/src/main/resources/application-dev.yml
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/

View File

@@ -2,6 +2,7 @@ package com.xaaef.molly.perms.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.xaaef.molly.auth.jwt.JwtLoginUser;
import com.xaaef.molly.auth.jwt.JwtSecurityUtils;
import com.xaaef.molly.common.domain.Pagination;
import com.xaaef.molly.common.util.JsonResult;
import com.xaaef.molly.perms.entity.PmsUser;
@@ -22,6 +23,9 @@ import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Set;
import static com.xaaef.molly.auth.jwt.JwtSecurityUtils.getTenantId;
import static com.xaaef.molly.tenant.util.DelegateUtils.delegate;
/**
* <p>
@@ -89,6 +93,19 @@ public class PmsUserController {
}
@Operation(summary = "修改", description = "修改必须要id")
@PutMapping("/info")
public JsonResult<Boolean> updateInfo(@RequestBody PmsUser entity) {
try {
var flag = delegate(JwtSecurityUtils.getTenantId(),
() -> baseService.updateById(entity));
return JsonResult.success(flag);
} catch (Exception e) {
return JsonResult.fail(e.getMessage(), Boolean.FALSE);
}
}
@Operation(summary = "删除", description = "修改必须要id")
@DeleteMapping("/{id}")
public JsonResult<Boolean> delete(@PathVariable Long id) {

View File

@@ -29,7 +29,6 @@ import com.xaaef.molly.perms.service.PmsDeptService;
import com.xaaef.molly.perms.service.PmsRoleService;
import com.xaaef.molly.perms.service.PmsUserService;
import com.xaaef.molly.perms.vo.*;
import com.xaaef.molly.common.util.TenantUtils;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
@@ -286,27 +285,33 @@ public class PmsUserServiceImpl extends BaseServiceImpl<PmsUserMapper, PmsUser>
}).collect(Collectors.toSet());
}
@Transactional(rollbackFor = Exception.class)
@Override
public boolean updatePassword(UpdatePasswordVO pwd) {
var sysUser = baseMapper.selectById(pwd.getUserId());
if (!StringUtils.equals(pwd.getNewPwd(), pwd.getConfirmPwd())) {
throw new RuntimeException("新密码与确认密码不一致,请重新输入!");
}
// 判断 老密码是否正确。
if (matchesPassword(pwd.getOldPwd(), sysUser.getPassword())) {
// 判断 新密码 和 老密码是否相同
if (matchesPassword(pwd.getNewPwd(), sysUser.getPassword())) {
throw new RuntimeException("新密码与旧密码相同,请重新输入!");
} else {
// 新密码 加密
var newPassword = encryptPassword(pwd.getNewPwd());
// 修改
var pmsUser = PmsUser.builder().userId(pwd.getUserId()).password(newPassword).build();
return super.updateById(pmsUser);
// 修改密码。要切换到当前登录用户所在的租户
// 否则,当平台用户在操作其他租户时,选择修改自己的用户名密码。就查不到自己的用户信息
return delegate(getTenantId(), () -> {
var sysUser = baseMapper.selectById(pwd.getUserId());
if (!StringUtils.equals(pwd.getNewPwd(), pwd.getConfirmPwd())) {
throw new RuntimeException("新密码与确认密码不一致,请重新输入!");
}
}
throw new RuntimeException("旧密码错误,请重新输入!");
// 判断 旧的密码是否正确。
if (matchesPassword(pwd.getOldPwd(), sysUser.getPassword())) {
// 判断 新密码 和 老密码是否相同
if (matchesPassword(pwd.getNewPwd(), sysUser.getPassword())) {
throw new RuntimeException("新密码与旧密码相同,请重新输入!");
} else {
// 新密码 加密
var newPassword = encryptPassword(pwd.getNewPwd());
// 修改
var pmsUser = PmsUser.builder().userId(pwd.getUserId()).password(newPassword).build();
return super.updateById(pmsUser);
}
} else {
throw new RuntimeException("旧密码错误,请重新输入!");
}
});
}

View File

@@ -1,15 +1,15 @@
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.0.188:3306/${multi.tenant.db-name:molly_master}?useSSL=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowMultiQueries=true
url: jdbc:mysql://molly.mysql.com:3306/${multi.tenant.db-name:molly_master}?useSSL=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowMultiQueries=true
username: root
password: mht123456
password: root
type: com.zaxxer.hikari.HikariDataSource
data:
redis:
host: 192.168.0.188
host: molly.redis.com
database: 6
port: 6379
timeout: 5000
@@ -35,10 +35,10 @@ spring:
qiniu-kodo: # 七牛云 kodo
- platform: qiniu-kodo-1 # 存储平台标识
enable-storage: true # 启用存储
access-key: cyMiqN72iVBoNiY8nWW-d0JQ8Oyoz9wvRvbWzHTt
secret-key: TePPDwQarZ2rWxHMCn8iuMLOSbzh0JYXZZjCRt_N
bucket-name: 7qj4ztoh
domain: https://oss.xaaef.com/ # 访问域名,注意“/”结尾例如http://abc.hn-bkt.clouddn.com/
access-key: 123456
secret-key: 123456
bucket-name: 123456
domain: https://oss.baidu.com/ # 访问域名,注意“/”结尾例如http://abc.hn-bkt.clouddn.com/
base-path: molly/ # 基础路径

View File

@@ -5,6 +5,7 @@ import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.lang.tree.Tree;
import cn.hutool.core.lang.tree.TreeNode;
import cn.hutool.core.lang.tree.TreeUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.xaaef.molly.tenant.base.service.impl.BaseServiceImpl;
import com.xaaef.molly.system.entity.SysMenu;
@@ -42,6 +43,27 @@ public class SysMenuServiceImpl extends BaseServiceImpl<SysMenuMapper, SysMenu>
implements SysMenuService {
@Override
public boolean save(SysMenu entity) {
if (this.exist(SysMenu::getPerms, entity.getPerms())) {
throw new RuntimeException(StrUtil.format("权限标识: {} 已经存在了", entity.getPerms()));
}
return super.save(entity);
}
@Override
public boolean updateById(SysMenu entity) {
var wrapper = new LambdaQueryWrapper<SysMenu>()
.eq(SysMenu::getPerms, entity.getPerms())
.ne(SysMenu::getMenuId, entity.getMenuId());
if (this.exist(wrapper)) {
throw new RuntimeException(StrUtil.format("权限标识: {} 已经存在了", entity.getPerms()));
}
return super.updateById(entity);
}
@Transactional(rollbackFor = Exception.class)
@Override
public boolean removeById(Serializable id) {

View File

@@ -1,6 +1,7 @@
package com.xaaef.molly.tenant.base.service;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import com.baomidou.mybatisplus.extension.service.IService;
@@ -118,6 +119,15 @@ public interface BaseService<T extends BaseEntity> extends IService<T> {
boolean exist(SFunction<T, ?> column, Object value);
/**
* 根据 字段 判断,是否存在
*
* @author Wang Chen Chen
* @date 2021/8/25 9:41
*/
boolean exist(Wrapper<T> wrapper);
/**
* 根据 字段 判断,数量
*

View File

@@ -78,6 +78,12 @@ public class BaseServiceImpl<M extends BaseMapper<T>, T extends BaseEntity> exte
}
@Override
public boolean exist(Wrapper<T> wrapper) {
return super.count(wrapper) > 0;
}
@Override
public long count(SFunction<T, ?> column, Object value) {
var wrapper = new LambdaQueryWrapper<T>().eq(column, value);

View File

@@ -1,30 +0,0 @@
{
"editor.tabSize": 2,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
"[vue]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[json]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[jsonc]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[html]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[css]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[scss]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
}

View File

@@ -32,6 +32,11 @@ export const updateUserApi = (data: any) => {
return httpPut<any, IJsonResult<boolean>>("/pms/user", data)
}
/** 修改当前登录的用户信息 */
export const updateUserInfoApi = (data: any) => {
return httpPut<any, IJsonResult<boolean>>("/pms/user/info", data)
}
/** 删除 */
export const deleteUserApi = (id: number) => {
return httpDelete<number, IJsonResult<boolean>>(`/pms/user/${id}`)

View File

@@ -14,6 +14,7 @@ import Screenfull from "@/components/Screenfull/index.vue"
import Notify from "@/components/Notify/index.vue"
import { ElMessageBox } from "element-plus"
import { jumpToLogin } from "@/router"
import { computed } from "vue"
const appStore = useAppStore()
const userStore = useUserStore()
@@ -41,6 +42,8 @@ const logout = () => {
})
.catch(() => console.log("取消退出登录.. :>> "))
}
const nickname = computed(() => userInfo?.nickname)
</script>
<template>
@@ -57,7 +60,7 @@ const logout = () => {
<el-dropdown class="right-menu-item">
<div class="right-menu-avatar">
<el-avatar :src="userInfo?.avatar" :icon="UserFilled" :size="36" />
<span>{{ userInfo?.nickname }}</span>
<span>{{ nickname }}</span>
</div>
<template #dropdown>
<el-dropdown-menu>
@@ -79,6 +82,7 @@ const logout = () => {
height: var(--v3-navigationbar-height);
overflow: hidden;
background: #fff;
.hamburger {
display: flex;
align-items: center;
@@ -87,13 +91,16 @@ const logout = () => {
padding: 0 15px;
cursor: pointer;
}
.breadcrumb {
float: left;
// 参考 Bootstrap 的响应式设计将宽度设置为 576
@media screen and (max-width: 576px) {
display: none;
}
}
.right-menu {
float: right;
margin-right: 10px;
@@ -101,15 +108,19 @@ const logout = () => {
display: flex;
align-items: center;
color: #606266;
.right-menu-item {
padding: 0 10px;
cursor: pointer;
.right-menu-avatar {
display: flex;
align-items: center;
.el-avatar {
margin-right: 10px;
}
span {
font-size: 16px;
}

View File

@@ -83,7 +83,7 @@ const title = ref("切换租户")
const dialogVisible = ref(false)
const value = ref(tenantStore.getCurrentTenant().name)
const value = computed(() => tenantStore.getCurrentTenant().name)
// 获取 input 焦点
const inputFocus = () => {
@@ -99,7 +99,6 @@ const handleSwitchClick = (t: ISimpleTenant) => {
name: t.name,
linkman: t.linkman
}
value.value = tenant.name
tenantStore.setCurrentTenant(tenant)
dialogVisible.value = false
}

View File

@@ -181,14 +181,14 @@ onMounted(() => {
@contextmenu.prevent="openMenu(tag, $event)"
>
{{ tag.meta?.title }}
<el-icon v-if="!isAffix(tag)" :size="12" @click.prevent.stop="closeSelectedTag(tag)">
<el-icon v-if="!isAffix(tag)" :size="14" @click.prevent.stop="closeSelectedTag(tag)">
<Close />
</el-icon>
</router-link>
</ScrollPane>
<ul v-show="visible" class="contextmenu" :style="{ left: left + 'px', top: top + 'px' }">
<li @click="refreshSelectedTag(selectedTag)">刷新</li>
<li v-if="!isAffix(selectedTag)" @click="closeSelectedTag(selectedTag)">关闭</li>
<li @click="refreshSelectedTag(selectedTag)">刷新此页</li>
<li v-if="!isAffix(selectedTag)" @click="closeSelectedTag(selectedTag)">关闭此页</li>
<li @click="closeOthersTags">关闭其它</li>
<li @click="closeAllTags(selectedTag)">关闭所有</li>
</ul>

View File

@@ -33,8 +33,6 @@ router.beforeEach((to, _from, next) => {
// 将'有访问权限的动态路由' 添加到 Router 中
dynamicRoutes.forEach((route) => router.addRoute(route))
console.log("dynamicRoutes :>> ", dynamicRoutes)
// hack方法 确保addRoute已完成 设置 replace: true, 因此导航将不会留下历史记录
next({ path: to.path, query: to.query, replace: true })
} else {

View File

@@ -80,6 +80,11 @@ export const useNoticeStore = defineStore("notice", () => {
})
}
const stopWebSocket = () => {
// 停止
stompClient.value!.deactivate()
}
const getStompClientActive = (): boolean => {
return stompClientActive.value
}
@@ -100,6 +105,7 @@ export const useNoticeStore = defineStore("notice", () => {
getBroadcast,
getPushNotices,
startWebSocket,
stopWebSocket,
getStompClientActive
}
})

View File

@@ -26,7 +26,6 @@ export const useTagsViewStore = defineStore("tags-view", () => {
if (typeof view.name !== "string") return
if (cachedViews.value.includes(view.name)) return
if (view.meta?.keepAlive) cachedViews.value.push(view.name)
console.log("cachedViews.value :>> ", cachedViews.value)
}
//#endregion

View File

@@ -7,6 +7,7 @@ import { loginApi, getUserInfoApi, getUserPermsApi, logoutApi } from "@/api/logi
import { type IPermsButton, ILoginData, IPermsMenus, ILoginUserInfo } from "@/types/pms"
import { ElMessage } from "element-plus"
import { getAccessToken, removeAccessToken, setAccessToken } from "@/utils/cache/local-storage"
import { useNoticeStoreHook } from "./notice"
export const useUserStore = defineStore("user", () => {
// token信息
@@ -105,6 +106,8 @@ export const useUserStore = defineStore("user", () => {
userInfo.value = undefined
accessToken.value = ""
resetRouter()
// 关闭 WebSocket
useNoticeStoreHook().stopWebSocket()
buttons.value = []
menus.value = []
_resetTagsView()

View File

@@ -51,6 +51,7 @@ function createService() {
return resetTenant(service, response)
case 400446:
// 此用户不包含此租户ID
ElMessage.error(`您没有操作 ${useTenantStoreHook().getCurrentTenant().name} 租户的权限`)
useTenantStoreHook().setCurrentTenant(apiData.data as ISimpleTenant)
return resetTenant(service, response)
default:
@@ -136,7 +137,6 @@ function resetTenant(service: AxiosInstance, response: AxiosResponse) {
const config = response.config
const tenant = useTenantStoreHook().getCurrentTenant()
config.headers["x-tenant-id"] = tenant.tenantId
console.log("reset tenant id to :>> ", tenant.tenantId)
// 获取当前失败的请求,重新发起请求
return service(config)
}

View File

@@ -1,12 +0,0 @@
import { useUserStoreHook } from "@/store/modules/user"
/** 全局权限判断函数,和权限指令 v-permission 功能类似 */
export const checkPermission = (permissionRoles: string[]): boolean => {
if (Array.isArray(permissionRoles) && permissionRoles.length > 0) {
const { roles } = useUserStoreHook()
return roles.some((role) => permissionRoles.includes(role))
} else {
console.error("need roles! Like checkPermission(['admin','editor'])")
return false
}
}

View File

@@ -129,7 +129,7 @@ import { useUserStore } from "@/store/modules/user"
import UserAvatar from "@/components/UserAvatar/index.vue"
import { ref, reactive } from "vue"
import { type FormInstance, FormRules, ElMessageBox } from "element-plus"
import { updatePasswordApi, updateUserApi } from "@/api/user"
import { updatePasswordApi, updateUserInfoApi } from "@/api/user"
import { testEmail, testPassword, testPhone } from "@/utils/validate"
import { useRouter } from "vue-router"
import { ILoginUserInfo } from "@/types/pms"
@@ -194,13 +194,14 @@ const onUpdateUserInfo = () => {
loading.value = true
const params = {
userId: userInfoForm.userId,
username: userInfoForm.username,
mobile: userInfoForm.mobile,
avatar: userInfoForm.avatar,
nickname: userInfoForm.nickname,
gender: userInfoForm.gender,
email: userInfoForm.email
}
updateUserApi(params)
updateUserInfoApi(params)
.then(() => userStore.getUserInfo())
.catch((err) => {
console.log("err", err)

6
web-ui/types/api.d.ts vendored Normal file
View File

@@ -0,0 +1,6 @@
/** 所有 api 接口的响应数据都应该准守该格式 */
interface IApiResponseData<T> {
code: number
data: T
message: string
}

116
web-ui/types/element-plus.d.ts vendored Normal file
View File

@@ -0,0 +1,116 @@
/**
* Copy https://github.com/element-plus/element-plus/blob/dev/global.d.ts
* 为了解决某些依赖(比如 vxe-table 4.2.7-beta.0 ~ 4.3.11 和 vue-router 4.2.0)导致的没有 Element Plus 组件类型提示的问题
*/
declare module "vue" {
export interface GlobalComponents {
ElAffix: typeof import("element-plus")["ElAffix"]
ElAlert: typeof import("element-plus")["ElAlert"]
ElAside: typeof import("element-plus")["ElAside"]
ElAutocomplete: typeof import("element-plus")["ElAutocomplete"]
ElAvatar: typeof import("element-plus")["ElAvatar"]
ElBacktop: typeof import("element-plus")["ElBacktop"]
ElBadge: typeof import("element-plus")["ElBadge"]
ElBreadcrumb: typeof import("element-plus")["ElBreadcrumb"]
ElBreadcrumbItem: typeof import("element-plus")["ElBreadcrumbItem"]
ElButton: typeof import("element-plus")["ElButton"]
ElButtonGroup: typeof import("element-plus")["ElButtonGroup"]
ElCalendar: typeof import("element-plus")["ElCalendar"]
ElCard: typeof import("element-plus")["ElCard"]
ElCarousel: typeof import("element-plus")["ElCarousel"]
ElCarouselItem: typeof import("element-plus")["ElCarouselItem"]
ElCascader: typeof import("element-plus")["ElCascader"]
ElCascaderPanel: typeof import("element-plus")["ElCascaderPanel"]
ElCheckbox: typeof import("element-plus")["ElCheckbox"]
ElCheckboxButton: typeof import("element-plus")["ElCheckboxButton"]
ElCheckboxGroup: typeof import("element-plus")["ElCheckboxGroup"]
ElCol: typeof import("element-plus")["ElCol"]
ElCollapse: typeof import("element-plus")["ElCollapse"]
ElCollapseItem: typeof import("element-plus")["ElCollapseItem"]
ElCollapseTransition: typeof import("element-plus")["ElCollapseTransition"]
ElColorPicker: typeof import("element-plus")["ElColorPicker"]
ElContainer: typeof import("element-plus")["ElContainer"]
ElConfigProvider: typeof import("element-plus")["ElConfigProvider"]
ElDatePicker: typeof import("element-plus")["ElDatePicker"]
ElDialog: typeof import("element-plus")["ElDialog"]
ElDivider: typeof import("element-plus")["ElDivider"]
ElDrawer: typeof import("element-plus")["ElDrawer"]
ElDropdown: typeof import("element-plus")["ElDropdown"]
ElDropdownItem: typeof import("element-plus")["ElDropdownItem"]
ElDropdownMenu: typeof import("element-plus")["ElDropdownMenu"]
ElEmpty: typeof import("element-plus")["ElEmpty"]
ElFooter: typeof import("element-plus")["ElFooter"]
ElForm: typeof import("element-plus")["ElForm"]
ElFormItem: typeof import("element-plus")["ElFormItem"]
ElHeader: typeof import("element-plus")["ElHeader"]
ElIcon: typeof import("element-plus")["ElIcon"]
ElImage: typeof import("element-plus")["ElImage"]
ElImageViewer: typeof import("element-plus")["ElImageViewer"]
ElInput: typeof import("element-plus")["ElInput"]
ElInputNumber: typeof import("element-plus")["ElInputNumber"]
ElLink: typeof import("element-plus")["ElLink"]
ElMain: typeof import("element-plus")["ElMain"]
ElMenu: typeof import("element-plus")["ElMenu"]
ElMenuItem: typeof import("element-plus")["ElMenuItem"]
ElMenuItemGroup: typeof import("element-plus")["ElMenuItemGroup"]
ElOption: typeof import("element-plus")["ElOption"]
ElOptionGroup: typeof import("element-plus")["ElOptionGroup"]
ElPageHeader: typeof import("element-plus")["ElPageHeader"]
ElPagination: typeof import("element-plus")["ElPagination"]
ElPopconfirm: typeof import("element-plus")["ElPopconfirm"]
ElPopper: typeof import("element-plus")["ElPopper"]
ElPopover: typeof import("element-plus")["ElPopover"]
ElProgress: typeof import("element-plus")["ElProgress"]
ElRadio: typeof import("element-plus")["ElRadio"]
ElRadioButton: typeof import("element-plus")["ElRadioButton"]
ElRadioGroup: typeof import("element-plus")["ElRadioGroup"]
ElRate: typeof import("element-plus")["ElRate"]
ElRow: typeof import("element-plus")["ElRow"]
ElScrollbar: typeof import("element-plus")["ElScrollbar"]
ElSelect: typeof import("element-plus")["ElSelect"]
ElSlider: typeof import("element-plus")["ElSlider"]
ElStep: typeof import("element-plus")["ElStep"]
ElSteps: typeof import("element-plus")["ElSteps"]
ElSubMenu: typeof import("element-plus")["ElSubMenu"]
ElSwitch: typeof import("element-plus")["ElSwitch"]
ElTabPane: typeof import("element-plus")["ElTabPane"]
ElTable: typeof import("element-plus")["ElTable"]
ElTableColumn: typeof import("element-plus")["ElTableColumn"]
ElTabs: typeof import("element-plus")["ElTabs"]
ElTag: typeof import("element-plus")["ElTag"]
ElText: typeof import("element-plus")["ElText"]
ElTimePicker: typeof import("element-plus")["ElTimePicker"]
ElTimeSelect: typeof import("element-plus")["ElTimeSelect"]
ElTimeline: typeof import("element-plus")["ElTimeline"]
ElTimelineItem: typeof import("element-plus")["ElTimelineItem"]
ElTooltip: typeof import("element-plus")["ElTooltip"]
ElTransfer: typeof import("element-plus")["ElTransfer"]
ElTree: typeof import("element-plus")["ElTree"]
ElTreeV2: typeof import("element-plus")["ElTreeV2"]
ElTreeSelect: typeof import("element-plus")["ElTreeSelect"]
ElUpload: typeof import("element-plus")["ElUpload"]
ElSpace: typeof import("element-plus")["ElSpace"]
ElSkeleton: typeof import("element-plus")["ElSkeleton"]
ElSkeletonItem: typeof import("element-plus")["ElSkeletonItem"]
ElStatistic: typeof import("element-plus")["ElStatistic"]
ElCheckTag: typeof import("element-plus")["ElCheckTag"]
ElDescriptions: typeof import("element-plus")["ElDescriptions"]
ElDescriptionsItem: typeof import("element-plus")["ElDescriptionsItem"]
ElResult: typeof import("element-plus")["ElResult"]
ElSelectV2: typeof import("element-plus")["ElSelectV2"]
}
interface ComponentCustomProperties {
$message: typeof import("element-plus")["ElMessage"]
$notify: typeof import("element-plus")["ElNotification"]
$msgbox: typeof import("element-plus")["ElMessageBox"]
$messageBox: typeof import("element-plus")["ElMessageBox"]
$alert: typeof import("element-plus")["ElMessageBox"]["alert"]
$confirm: typeof import("element-plus")["ElMessageBox"]["confirm"]
$prompt: typeof import("element-plus")["ElMessageBox"]["prompt"]
$loading: typeof import("element-plus")["ElLoadingService"]
}
}
export {}