Merge branch 'master' of https://gitee.com/yudaocode/yudao-ui-admin-vue3 into feature/bpm
commit
89c3af5207
|
@ -38,7 +38,7 @@
|
||||||
"animate.css": "^4.1.1",
|
"animate.css": "^4.1.1",
|
||||||
"axios": "^1.6.8",
|
"axios": "^1.6.8",
|
||||||
"benz-amr-recorder": "^1.1.5",
|
"benz-amr-recorder": "^1.1.5",
|
||||||
"bpmn-js-token-simulation": "^0.10.0",
|
"bpmn-js-token-simulation": "^0.36.0",
|
||||||
"camunda-bpmn-moddle": "^7.0.1",
|
"camunda-bpmn-moddle": "^7.0.1",
|
||||||
"cropperjs": "^1.6.1",
|
"cropperjs": "^1.6.1",
|
||||||
"crypto-js": "^4.2.0",
|
"crypto-js": "^4.2.0",
|
||||||
|
@ -47,7 +47,7 @@
|
||||||
"driver.js": "^1.3.1",
|
"driver.js": "^1.3.1",
|
||||||
"echarts": "^5.5.0",
|
"echarts": "^5.5.0",
|
||||||
"echarts-wordcloud": "^2.1.0",
|
"echarts-wordcloud": "^2.1.0",
|
||||||
"element-plus": "2.8.4",
|
"element-plus": "2.9.1",
|
||||||
"fast-xml-parser": "^4.3.2",
|
"fast-xml-parser": "^4.3.2",
|
||||||
"highlight.js": "^11.9.0",
|
"highlight.js": "^11.9.0",
|
||||||
"jsencrypt": "^3.3.2",
|
"jsencrypt": "^3.3.2",
|
||||||
|
|
|
@ -72,8 +72,8 @@ dependencies:
|
||||||
specifier: ^2.1.0
|
specifier: ^2.1.0
|
||||||
version: 2.1.0(echarts@5.5.1)
|
version: 2.1.0(echarts@5.5.1)
|
||||||
element-plus:
|
element-plus:
|
||||||
specifier: 2.8.4
|
specifier: 2.9.1
|
||||||
version: 2.8.4(vue@3.5.12)
|
version: 2.9.1(vue@3.5.12)
|
||||||
fast-xml-parser:
|
fast-xml-parser:
|
||||||
specifier: ^4.3.2
|
specifier: ^4.3.2
|
||||||
version: 4.5.0
|
version: 4.5.0
|
||||||
|
@ -2122,7 +2122,7 @@ packages:
|
||||||
'@form-create/element-ui': 3.2.14(vue@3.5.12)
|
'@form-create/element-ui': 3.2.14(vue@3.5.12)
|
||||||
'@form-create/utils': 3.2.14
|
'@form-create/utils': 3.2.14
|
||||||
codemirror: 6.65.7
|
codemirror: 6.65.7
|
||||||
element-plus: 2.8.4(vue@3.5.12)
|
element-plus: 2.9.1(vue@3.5.12)
|
||||||
vue: 3.5.12(typescript@5.3.3)
|
vue: 3.5.12(typescript@5.3.3)
|
||||||
vuedraggable: 4.1.0(vue@3.5.12)
|
vuedraggable: 4.1.0(vue@3.5.12)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
|
@ -5795,8 +5795,8 @@ packages:
|
||||||
resolution: {integrity: sha512-nz88NNBsD7kQSAGGJyp8hS6xSPtWwqNogA0mjtc2nUYeEf3nURK9qpV18TuBdDmEDgVWotS8Wkzf+V52dSQ/LQ==, tarball: https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.5.67.tgz}
|
resolution: {integrity: sha512-nz88NNBsD7kQSAGGJyp8hS6xSPtWwqNogA0mjtc2nUYeEf3nURK9qpV18TuBdDmEDgVWotS8Wkzf+V52dSQ/LQ==, tarball: https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.5.67.tgz}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/element-plus@2.8.4(vue@3.5.12):
|
/element-plus@2.9.1(vue@3.5.12):
|
||||||
resolution: {integrity: sha512-ZlVAdUOoJliv4kW3ntWnnSHMT+u/Os7mXJjk2xzOlqNeHaI2/ozlF+R58ZCEak8ZnDi6+5A2viWEYRsq64IuiA==, tarball: https://registry.npmmirror.com/element-plus/-/element-plus-2.8.4.tgz}
|
resolution: {integrity: sha512-9Agqf/jt4Ugk7EZ6C5LME71sgkvauPCsnvJN12Xid2XVobjufxMGpRE4L7pS4luJMOmFAH3J0NgYEGZT5r+NDg==, tarball: https://registry.npmmirror.com/element-plus/-/element-plus-2.9.1.tgz}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
vue: ^3.2.0
|
vue: ^3.2.0
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|
|
@ -22,11 +22,6 @@ export const register = (data: RegisterVO) => {
|
||||||
return request.post({ url: '/system/auth/register', data })
|
return request.post({ url: '/system/auth/register', data })
|
||||||
}
|
}
|
||||||
|
|
||||||
// 刷新访问令牌
|
|
||||||
export const refreshToken = () => {
|
|
||||||
return request.post({ url: '/system/auth/refresh-token?refreshToken=' + getRefreshToken() })
|
|
||||||
}
|
|
||||||
|
|
||||||
// 使用租户名,获得租户编号
|
// 使用租户名,获得租户编号
|
||||||
export const getTenantIdByName = (name: string) => {
|
export const getTenantIdByName = (name: string) => {
|
||||||
return request.get({ url: '/system/tenant/get-id-by-name?name=' + name })
|
return request.get({ url: '/system/tenant/get-id-by-name?name=' + name })
|
||||||
|
@ -76,12 +71,17 @@ export const socialAuthRedirect = (type: number, redirectUri: string) => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
// 获取验证图片以及 token
|
// 获取验证图片以及 token
|
||||||
export const getCode = (data) => {
|
export const getCode = (data: any) => {
|
||||||
debugger
|
debugger
|
||||||
return request.postOriginal({ url: 'system/captcha/get', data })
|
return request.postOriginal({ url: 'system/captcha/get', data })
|
||||||
}
|
}
|
||||||
|
|
||||||
// 滑动或者点选验证
|
// 滑动或者点选验证
|
||||||
export const reqCheck = (data) => {
|
export const reqCheck = (data: any) => {
|
||||||
return request.postOriginal({ url: 'system/captcha/check', data })
|
return request.postOriginal({ url: 'system/captcha/check', data })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 通过短信重置密码
|
||||||
|
export const smsResetPassword = (data: any) => {
|
||||||
|
return request.post({ url: '/system/auth/sms-reset-password', data })
|
||||||
|
}
|
||||||
|
|
|
@ -9,6 +9,10 @@ import { useAppStore } from '@/store/modules/app'
|
||||||
import { isString } from '@/utils/is'
|
import { isString } from '@/utils/is'
|
||||||
import { useDesign } from '@/hooks/web/useDesign'
|
import { useDesign } from '@/hooks/web/useDesign'
|
||||||
|
|
||||||
|
import 'echarts/lib/component/markPoint'
|
||||||
|
import 'echarts/lib/component/markLine'
|
||||||
|
import 'echarts/lib/component/markArea'
|
||||||
|
|
||||||
defineOptions({ name: 'EChart' })
|
defineOptions({ name: 'EChart' })
|
||||||
|
|
||||||
const { getPrefixCls, variables } = useDesign()
|
const { getPrefixCls, variables } = useDesign()
|
||||||
|
|
|
@ -79,9 +79,14 @@ function remoteMethod(data) {
|
||||||
|
|
||||||
function handleChange(path) {
|
function handleChange(path) {
|
||||||
router.push({ path })
|
router.push({ path })
|
||||||
|
hiddenSearch()
|
||||||
hiddenTopSearch()
|
hiddenTopSearch()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function hiddenSearch() {
|
||||||
|
showSearch.value = false
|
||||||
|
}
|
||||||
|
|
||||||
function hiddenTopSearch() {
|
function hiddenTopSearch() {
|
||||||
showTopSearch.value = false
|
showTopSearch.value = false
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
@use 'bpmn-js-token-simulation/assets/css/bpmn-js-token-simulation.css';
|
@use 'bpmn-js-token-simulation/assets/css/bpmn-js-token-simulation.css';
|
||||||
@use 'bpmn-js-token-simulation/assets/css/font-awesome.min.css';
|
|
||||||
@use 'bpmn-js-token-simulation/assets/css/normalize.css';
|
|
||||||
|
|
||||||
// 边框被 token-simulation 样式覆盖了
|
// 边框被 token-simulation 样式覆盖了
|
||||||
.djs-palette {
|
.djs-palette {
|
||||||
|
@ -97,12 +95,12 @@
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
svg {
|
// svg {
|
||||||
width: 100%;
|
// width: 100%;
|
||||||
height: 100%;
|
// height: 100%;
|
||||||
min-height: 100%;
|
// min-height: 100%;
|
||||||
overflow: hidden;
|
// overflow: hidden;
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,18 +5,10 @@ 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 userInfo = wsCache.get(CACHE_KEY.USER)
|
|
||||||
const permissions = userInfo?.permissions || []
|
|
||||||
|
|
||||||
if (value && value instanceof Array && value.length > 0) {
|
if (value && value instanceof Array && value.length > 0) {
|
||||||
const permissionFlag = value
|
const hasPermissions = hasPermission(value)
|
||||||
|
|
||||||
const hasPermissions = permissions.some((permission: string) => {
|
|
||||||
return all_permission === permission || permissionFlag.includes(permission)
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!hasPermissions) {
|
if (!hasPermissions) {
|
||||||
el.parentNode && el.parentNode.removeChild(el)
|
el.parentNode && el.parentNode.removeChild(el)
|
||||||
|
@ -26,3 +18,14 @@ export function hasPermi(app: App<Element>) {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const hasPermission = (permission: string[]) => {
|
||||||
|
const { wsCache } = useCache()
|
||||||
|
const all_permission = '*:*:*'
|
||||||
|
const userInfo = wsCache.get(CACHE_KEY.USER)
|
||||||
|
const permissions = userInfo?.permissions || []
|
||||||
|
|
||||||
|
return permissions.some((p: string) => {
|
||||||
|
return all_permission === p || permission.includes(p)
|
||||||
|
})
|
||||||
|
}
|
|
@ -12,6 +12,9 @@ const prefixCls = getPrefixCls('footer')
|
||||||
const appStore = useAppStore()
|
const appStore = useAppStore()
|
||||||
|
|
||||||
const title = computed(() => appStore.getTitle)
|
const title = computed(() => appStore.getTitle)
|
||||||
|
|
||||||
|
// 添加当前年份计算属性
|
||||||
|
const currentYear = computed(() => new Date().getFullYear())
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -19,6 +22,6 @@ const title = computed(() => appStore.getTitle)
|
||||||
:class="prefixCls"
|
:class="prefixCls"
|
||||||
class="h-[var(--app-footer-height)] bg-[var(--app-content-bg-color)] text-center leading-[var(--app-footer-height)] text-[var(--el-text-color-placeholder)] dark:bg-[var(--el-bg-color)] overflow-hidden"
|
class="h-[var(--app-footer-height)] bg-[var(--app-content-bg-color)] text-center leading-[var(--app-footer-height)] text-[var(--el-text-color-placeholder)] dark:bg-[var(--el-bg-color)] overflow-hidden"
|
||||||
>
|
>
|
||||||
<span class="text-14px">Copyright ©2022-{{ title }}</span>
|
<span class="text-14px">Copyright ©{{ currentYear }} {{ title }}</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -140,7 +140,10 @@ export default {
|
||||||
btnQRCode: 'QR code sign in',
|
btnQRCode: 'QR code sign in',
|
||||||
qrcode: 'Scan the QR code to log in',
|
qrcode: 'Scan the QR code to log in',
|
||||||
btnRegister: 'Sign up',
|
btnRegister: 'Sign up',
|
||||||
SmsSendMsg: 'code has been sent'
|
SmsSendMsg: 'code has been sent',
|
||||||
|
resetPassword: "Reset Password",
|
||||||
|
resetPasswordSuccess: "Reset Password Success",
|
||||||
|
invalidTenantName:"Invalid Tenant Name"
|
||||||
},
|
},
|
||||||
captcha: {
|
captcha: {
|
||||||
verification: 'Please complete security verification',
|
verification: 'Please complete security verification',
|
||||||
|
|
|
@ -141,7 +141,10 @@ export default {
|
||||||
btnQRCode: '二维码登录',
|
btnQRCode: '二维码登录',
|
||||||
qrcode: '扫描二维码登录',
|
qrcode: '扫描二维码登录',
|
||||||
btnRegister: '注册',
|
btnRegister: '注册',
|
||||||
SmsSendMsg: '验证码已发送'
|
SmsSendMsg: '验证码已发送',
|
||||||
|
resetPassword: "重置密码",
|
||||||
|
resetPasswordSuccess: "重置密码成功",
|
||||||
|
invalidTenantName: "无效的租户名称"
|
||||||
},
|
},
|
||||||
captcha: {
|
captcha: {
|
||||||
verification: '请完成安全验证',
|
verification: '请完成安全验证',
|
||||||
|
|
|
@ -73,7 +73,7 @@ export const generateRoute = (routes: AppCustomRouteRecordRaw[]): AppRouteRecord
|
||||||
noCache: !route.keepAlive,
|
noCache: !route.keepAlive,
|
||||||
alwaysShow:
|
alwaysShow:
|
||||||
route.children &&
|
route.children &&
|
||||||
route.children.length === 1 &&
|
route.children.length > 0 &&
|
||||||
(route.alwaysShow !== undefined ? route.alwaysShow : true)
|
(route.alwaysShow !== undefined ? route.alwaysShow : true)
|
||||||
} as any
|
} as any
|
||||||
// 特殊逻辑:如果后端配置的 MenuDO.component 包含 ?,则表示需要传递参数
|
// 特殊逻辑:如果后端配置的 MenuDO.component 包含 ?,则表示需要传递参数
|
||||||
|
@ -100,7 +100,6 @@ export const generateRoute = (routes: AppCustomRouteRecordRaw[]): AppRouteRecord
|
||||||
//处理顶级非目录路由
|
//处理顶级非目录路由
|
||||||
if (!route.children && route.parentId == 0 && route.component) {
|
if (!route.children && route.parentId == 0 && route.component) {
|
||||||
data.component = Layout
|
data.component = Layout
|
||||||
data.meta = {}
|
|
||||||
data.name = toCamelCase(route.path, true) + 'Parent'
|
data.name = toCamelCase(route.path, true) + 'Parent'
|
||||||
data.redirect = ''
|
data.redirect = ''
|
||||||
meta.alwaysShow = true
|
meta.alwaysShow = true
|
||||||
|
|
|
@ -59,6 +59,8 @@
|
||||||
<RegisterForm class="m-auto h-auto p-20px lt-xl:(rounded-3xl light:bg-white)" />
|
<RegisterForm class="m-auto h-auto p-20px lt-xl:(rounded-3xl light:bg-white)" />
|
||||||
<!-- 三方登录 -->
|
<!-- 三方登录 -->
|
||||||
<SSOLoginVue class="m-auto h-auto p-20px lt-xl:(rounded-3xl light:bg-white)" />
|
<SSOLoginVue class="m-auto h-auto p-20px lt-xl:(rounded-3xl light:bg-white)" />
|
||||||
|
<!-- 忘记密码 -->
|
||||||
|
<ForgetPasswordForm class="m-auto h-auto p-20px lt-xl:(rounded-3xl light:bg-white)" />
|
||||||
</div>
|
</div>
|
||||||
</Transition>
|
</Transition>
|
||||||
</div>
|
</div>
|
||||||
|
@ -73,7 +75,7 @@ import { useAppStore } from '@/store/modules/app'
|
||||||
import { ThemeSwitch } from '@/layout/components/ThemeSwitch'
|
import { ThemeSwitch } from '@/layout/components/ThemeSwitch'
|
||||||
import { LocaleDropdown } from '@/layout/components/LocaleDropdown'
|
import { LocaleDropdown } from '@/layout/components/LocaleDropdown'
|
||||||
|
|
||||||
import { LoginForm, MobileForm, QrCodeForm, RegisterForm, SSOLoginVue } from './components'
|
import { LoginForm, MobileForm, QrCodeForm, RegisterForm, SSOLoginVue, ForgetPasswordForm } from './components'
|
||||||
|
|
||||||
defineOptions({ name: 'Login' })
|
defineOptions({ name: 'Login' })
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,278 @@
|
||||||
|
<template>
|
||||||
|
<el-form
|
||||||
|
v-show="getShow"
|
||||||
|
ref="formSmsResetPassword"
|
||||||
|
:model="resetPasswordData"
|
||||||
|
:rules="rules"
|
||||||
|
class="login-form"
|
||||||
|
label-position="top"
|
||||||
|
label-width="120px"
|
||||||
|
size="large"
|
||||||
|
>
|
||||||
|
<el-row style="margin-right: -10px; margin-left: -10px">
|
||||||
|
<!-- 租户名 -->
|
||||||
|
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
||||||
|
<el-form-item>
|
||||||
|
<LoginFormTitle style="width: 100%" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
||||||
|
<el-form-item v-if="resetPasswordData.tenantEnable === 'true'" prop="tenantName">
|
||||||
|
<el-input
|
||||||
|
v-model="resetPasswordData.tenantName"
|
||||||
|
:placeholder="t('login.tenantNamePlaceholder')"
|
||||||
|
:prefix-icon="iconHouse"
|
||||||
|
type="primary"
|
||||||
|
link
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<!-- 手机号 -->
|
||||||
|
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
||||||
|
<el-form-item prop="mobile">
|
||||||
|
<el-input
|
||||||
|
v-model="resetPasswordData.mobile"
|
||||||
|
:placeholder="t('login.mobileNumberPlaceholder')"
|
||||||
|
:prefix-icon="iconCellphone"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<Verify
|
||||||
|
ref="verify"
|
||||||
|
:captchaType="captchaType"
|
||||||
|
:imgSize="{ width: '400px', height: '200px' }"
|
||||||
|
mode="pop"
|
||||||
|
@success="getSmsCode"
|
||||||
|
/>
|
||||||
|
<!-- 验证码 -->
|
||||||
|
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
||||||
|
<el-form-item prop="code">
|
||||||
|
<el-row :gutter="5" justify="space-between" style="width: 100%">
|
||||||
|
<el-col :span="24">
|
||||||
|
<el-input
|
||||||
|
v-model="resetPasswordData.code"
|
||||||
|
:placeholder="t('login.codePlaceholder')"
|
||||||
|
:prefix-icon="iconCircleCheck"
|
||||||
|
>
|
||||||
|
<template #append>
|
||||||
|
<span
|
||||||
|
v-if="mobileCodeTimer <= 0"
|
||||||
|
class="getMobileCode"
|
||||||
|
style="cursor: pointer"
|
||||||
|
@click="getCode"
|
||||||
|
>
|
||||||
|
{{ t('login.getSmsCode') }}
|
||||||
|
</span>
|
||||||
|
<span v-if="mobileCodeTimer > 0" class="getMobileCode" style="cursor: pointer">
|
||||||
|
{{ mobileCodeTimer }}秒后可重新获取
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
<!-- </el-button> -->
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
||||||
|
<el-form-item prop="password">
|
||||||
|
<InputPassword
|
||||||
|
v-model="resetPasswordData.password"
|
||||||
|
:placeholder="t('login.passwordPlaceholder')"
|
||||||
|
style="width: 100%"
|
||||||
|
strength="true"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
||||||
|
<el-form-item prop="check_password">
|
||||||
|
<InputPassword
|
||||||
|
v-model="resetPasswordData.check_password"
|
||||||
|
:placeholder="t('login.checkPassword')"
|
||||||
|
style="width: 100%"
|
||||||
|
strength="true"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<!-- 登录按钮 / 返回按钮 -->
|
||||||
|
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
||||||
|
<el-form-item>
|
||||||
|
<XButton
|
||||||
|
:loading="loginLoading"
|
||||||
|
:title="t('login.resetPassword')"
|
||||||
|
class="w-[100%]"
|
||||||
|
type="primary"
|
||||||
|
@click="resetPassword()"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
||||||
|
<el-form-item>
|
||||||
|
<XButton
|
||||||
|
:loading="loginLoading"
|
||||||
|
:title="t('login.backLogin')"
|
||||||
|
class="w-[100%]"
|
||||||
|
@click="handleBackLogin()"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-form>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { RouteLocationNormalizedLoaded } from 'vue-router'
|
||||||
|
|
||||||
|
import { useIcon } from '@/hooks/web/useIcon'
|
||||||
|
|
||||||
|
import { sendSmsCode, smsResetPassword } from '@/api/login'
|
||||||
|
import LoginFormTitle from './LoginFormTitle.vue'
|
||||||
|
import { LoginStateEnum, useFormValid, useLoginState } from './useLogin'
|
||||||
|
import { ElLoading } from 'element-plus'
|
||||||
|
import * as authUtil from '@/utils/auth'
|
||||||
|
import * as LoginApi from '@/api/login'
|
||||||
|
defineOptions({ name: 'ForgetPasswordForm' })
|
||||||
|
const verify = ref()
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
const message = useMessage()
|
||||||
|
const { currentRoute, push } = useRouter()
|
||||||
|
const formSmsResetPassword = ref()
|
||||||
|
const loginLoading = ref(false)
|
||||||
|
const iconHouse = useIcon({ icon: 'ep:house' })
|
||||||
|
const iconCellphone = useIcon({ icon: 'ep:cellphone' })
|
||||||
|
const iconCircleCheck = useIcon({ icon: 'ep:circle-check' })
|
||||||
|
const { validForm } = useFormValid(formSmsResetPassword)
|
||||||
|
const { handleBackLogin, getLoginState, setLoginState } = useLoginState()
|
||||||
|
const getShow = computed(() => unref(getLoginState) === LoginStateEnum.RESET_PASSWORD)
|
||||||
|
const captchaType = ref('blockPuzzle') // blockPuzzle 滑块 clickWord 点击文字
|
||||||
|
|
||||||
|
const validatePass2 = (rule, value, callback) => {
|
||||||
|
if (value === '') {
|
||||||
|
callback(new Error('请再次输入密码'))
|
||||||
|
} else if (value !== resetPasswordData.password) {
|
||||||
|
callback(new Error('两次输入密码不一致!'))
|
||||||
|
} else {
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const rules = {
|
||||||
|
tenantName: [{ required: true, min: 2, max: 20, trigger: 'blur', message: '长度为4到16位' }],
|
||||||
|
mobile: [{ required: true, min: 11, max: 11, trigger: 'blur', message: '手机号长度为11位' }],
|
||||||
|
password: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
min: 4,
|
||||||
|
max: 16,
|
||||||
|
validator: validatePass2,
|
||||||
|
trigger: 'blur',
|
||||||
|
message: '密码长度为4到16位'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
check_password: [{ required: true, validator: validatePass2, trigger: 'blur' }],
|
||||||
|
code: [required]
|
||||||
|
}
|
||||||
|
|
||||||
|
const resetPasswordData = reactive({
|
||||||
|
captchaEnable: import.meta.env.VITE_APP_CAPTCHA_ENABLE,
|
||||||
|
tenantEnable: import.meta.env.VITE_APP_TENANT_ENABLE,
|
||||||
|
tenantName: '',
|
||||||
|
username: '',
|
||||||
|
password: '',
|
||||||
|
check_password: '',
|
||||||
|
mobile: '',
|
||||||
|
code: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
const smsVO = reactive({
|
||||||
|
tenantName: '',
|
||||||
|
mobile: '',
|
||||||
|
captchaVerification: '',
|
||||||
|
scene: 23
|
||||||
|
})
|
||||||
|
const mobileCodeTimer = ref(0)
|
||||||
|
const redirect = ref<string>('')
|
||||||
|
|
||||||
|
// 获取验证码
|
||||||
|
const getCode = async () => {
|
||||||
|
// 情况一,未开启:则直接发送验证码
|
||||||
|
if (resetPasswordData.captchaEnable === 'false') {
|
||||||
|
await getSmsCode({})
|
||||||
|
} else {
|
||||||
|
// 情况二,已开启:则展示验证码;只有完成验证码的情况,才进行发送验证码
|
||||||
|
// 弹出验证码
|
||||||
|
verify.value.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getSmsCode = async (params) => {
|
||||||
|
if (resetPasswordData.tenantEnable === 'true') {
|
||||||
|
await getTenantId()
|
||||||
|
}
|
||||||
|
smsVO.captchaVerification = params.captchaVerification
|
||||||
|
smsVO.mobile = resetPasswordData.mobile
|
||||||
|
await sendSmsCode(smsVO).then(async () => {
|
||||||
|
message.success(t('login.SmsSendMsg'))
|
||||||
|
// 设置倒计时
|
||||||
|
mobileCodeTimer.value = 60
|
||||||
|
let msgTimer = setInterval(() => {
|
||||||
|
mobileCodeTimer.value = mobileCodeTimer.value - 1
|
||||||
|
if (mobileCodeTimer.value <= 0) {
|
||||||
|
clearInterval(msgTimer)
|
||||||
|
}
|
||||||
|
}, 1000)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
watch(
|
||||||
|
() => currentRoute.value,
|
||||||
|
(route: RouteLocationNormalizedLoaded) => {
|
||||||
|
redirect.value = route?.query?.redirect as string
|
||||||
|
},
|
||||||
|
{
|
||||||
|
immediate: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const getTenantId = async () => {
|
||||||
|
if (resetPasswordData.tenantEnable === 'true') {
|
||||||
|
const res = await LoginApi.getTenantIdByName(resetPasswordData.tenantName)
|
||||||
|
if (res == null) {
|
||||||
|
message.error(t('login.invalidTenantName'))
|
||||||
|
throw t('login.invalidTenantName')
|
||||||
|
}
|
||||||
|
authUtil.setTenantId(res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置密码
|
||||||
|
const resetPassword = async () => {
|
||||||
|
const data = await validForm()
|
||||||
|
if (!data) return
|
||||||
|
await getTenantId()
|
||||||
|
loginLoading.value = true
|
||||||
|
await smsResetPassword(resetPasswordData)
|
||||||
|
.then(async () => {
|
||||||
|
message.success(t('login.resetPasswordSuccess'))
|
||||||
|
setLoginState(LoginStateEnum.LOGIN)
|
||||||
|
})
|
||||||
|
.catch(() => {})
|
||||||
|
.finally(() => {
|
||||||
|
loginLoading.value = false
|
||||||
|
setTimeout(() => {
|
||||||
|
const loadingInstance = ElLoading.service()
|
||||||
|
loadingInstance.close()
|
||||||
|
}, 400)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
:deep(.anticon) {
|
||||||
|
&:hover {
|
||||||
|
color: var(--el-color-primary) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.smsbtn {
|
||||||
|
margin-top: 33px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -59,7 +59,13 @@
|
||||||
</el-checkbox>
|
</el-checkbox>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :offset="6" :span="12">
|
<el-col :offset="6" :span="12">
|
||||||
<el-link style="float: right" type="primary">{{ t('login.forgetPassword') }}</el-link>
|
<el-link
|
||||||
|
style="float: right"
|
||||||
|
type="primary"
|
||||||
|
@click="setLoginState(LoginStateEnum.RESET_PASSWORD)"
|
||||||
|
>
|
||||||
|
{{ t('login.forgetPassword') }}
|
||||||
|
</el-link>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
|
@ -4,5 +4,6 @@ import LoginFormTitle from './LoginFormTitle.vue'
|
||||||
import RegisterForm from './RegisterForm.vue'
|
import RegisterForm from './RegisterForm.vue'
|
||||||
import QrCodeForm from './QrCodeForm.vue'
|
import QrCodeForm from './QrCodeForm.vue'
|
||||||
import SSOLoginVue from './SSOLogin.vue'
|
import SSOLoginVue from './SSOLogin.vue'
|
||||||
|
import ForgetPasswordForm from './ForgetPasswordForm.vue'
|
||||||
|
|
||||||
export { LoginForm, MobileForm, LoginFormTitle, RegisterForm, QrCodeForm, SSOLoginVue }
|
export { LoginForm, MobileForm, LoginFormTitle, RegisterForm, QrCodeForm, SSOLoginVue, ForgetPasswordForm }
|
||||||
|
|
|
@ -95,6 +95,9 @@
|
||||||
/>
|
/>
|
||||||
<el-table-column label="操作" align="center">
|
<el-table-column label="操作" align="center">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
|
<el-button link type="primary" @click="copyToClipboard(scope.row.url)">
|
||||||
|
复制链接
|
||||||
|
</el-button>
|
||||||
<el-button
|
<el-button
|
||||||
link
|
link
|
||||||
type="danger"
|
type="danger"
|
||||||
|
@ -172,6 +175,13 @@ const openForm = () => {
|
||||||
formRef.value.open()
|
formRef.value.open()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 复制到剪贴板方法 */
|
||||||
|
const copyToClipboard = (text: string) => {
|
||||||
|
navigator.clipboard.writeText(text).then(() => {
|
||||||
|
message.success('复制成功')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/** 删除按钮操作 */
|
/** 删除按钮操作 */
|
||||||
const handleDelete = async (id: number) => {
|
const handleDelete = async (id: number) => {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
<template #default="{ height, width }">
|
<template #default="{ height, width }">
|
||||||
<!-- Virtualized Table 虚拟化表格:高性能,解决表格在大数据量下的卡顿问题 -->
|
<!-- Virtualized Table 虚拟化表格:高性能,解决表格在大数据量下的卡顿问题 -->
|
||||||
<el-table-v2
|
<el-table-v2
|
||||||
|
v-loading="loading"
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
:data="list"
|
:data="list"
|
||||||
:width="width"
|
:width="width"
|
||||||
|
@ -31,7 +32,7 @@
|
||||||
<AreaForm ref="formRef" />
|
<AreaForm ref="formRef" />
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="tsx">
|
<script setup lang="tsx">
|
||||||
import type { Column } from 'element-plus'
|
import { Column } from 'element-plus'
|
||||||
import AreaForm from './AreaForm.vue'
|
import AreaForm from './AreaForm.vue'
|
||||||
import * as AreaApi from '@/api/system/area'
|
import * as AreaApi from '@/api/system/area'
|
||||||
|
|
||||||
|
@ -40,7 +41,7 @@ defineOptions({ name: 'SystemArea' })
|
||||||
// 表格的 column 字段
|
// 表格的 column 字段
|
||||||
const columns: Column[] = [
|
const columns: Column[] = [
|
||||||
{
|
{
|
||||||
dataKey: 'id', // 需要渲染当前列的数据字段。例如说:{id:9527, name:'Mike'},则填 id
|
dataKey: 'id', // 需要渲染当前列的数据字段
|
||||||
title: '编号', // 显示在单元格表头的文本
|
title: '编号', // 显示在单元格表头的文本
|
||||||
width: 400, // 当前列的宽度,必须设置
|
width: 400, // 当前列的宽度,必须设置
|
||||||
fixed: true, // 是否固定列
|
fixed: true, // 是否固定列
|
||||||
|
@ -52,14 +53,17 @@ const columns: Column[] = [
|
||||||
width: 200
|
width: 200
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
// 表格的数据
|
const loading = ref(true) // 列表的加载中
|
||||||
const list = ref([])
|
const list = ref([]) // 表格的数据
|
||||||
|
|
||||||
/**
|
/** 获得数据列表 */
|
||||||
* 获得数据列表
|
|
||||||
*/
|
|
||||||
const getList = async () => {
|
const getList = async () => {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
list.value = await AreaApi.getAreaTree()
|
list.value = await AreaApi.getAreaTree()
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 添加/修改操作 */
|
/** 添加/修改操作 */
|
||||||
|
|
|
@ -53,10 +53,6 @@
|
||||||
<Icon class="mr-5px" icon="ep:plus" />
|
<Icon class="mr-5px" icon="ep:plus" />
|
||||||
新增
|
新增
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button plain type="danger" @click="toggleExpandAll">
|
|
||||||
<Icon class="mr-5px" icon="ep:sort" />
|
|
||||||
展开/折叠
|
|
||||||
</el-button>
|
|
||||||
<el-button plain @click="refreshMenu">
|
<el-button plain @click="refreshMenu">
|
||||||
<Icon class="mr-5px" icon="ep:refresh" />
|
<Icon class="mr-5px" icon="ep:refresh" />
|
||||||
刷新菜单缓存
|
刷新菜单缓存
|
||||||
|
@ -67,65 +63,22 @@
|
||||||
|
|
||||||
<!-- 列表 -->
|
<!-- 列表 -->
|
||||||
<ContentWrap>
|
<ContentWrap>
|
||||||
<el-table
|
<div style="height: 700px">
|
||||||
v-if="refreshTable"
|
<!-- AutoResizer 自动调节大小 -->
|
||||||
|
<el-auto-resizer>
|
||||||
|
<template #default="{ height, width }">
|
||||||
|
<!-- Virtualized Table 虚拟化表格:高性能,解决表格在大数据量下的卡顿问题 -->
|
||||||
|
<el-table-v2
|
||||||
v-loading="loading"
|
v-loading="loading"
|
||||||
|
:columns="columns"
|
||||||
:data="list"
|
:data="list"
|
||||||
:default-expand-all="isExpandAll"
|
:width="width"
|
||||||
row-key="id"
|
:height="height"
|
||||||
>
|
expand-column-key="name"
|
||||||
<el-table-column :show-overflow-tooltip="true" label="菜单名称" prop="name" width="250" />
|
|
||||||
<el-table-column align="center" label="图标" prop="icon" width="100">
|
|
||||||
<template #default="scope">
|
|
||||||
<Icon :icon="scope.row.icon" />
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="排序" prop="sort" width="60" />
|
|
||||||
<el-table-column :show-overflow-tooltip="true" label="权限标识" prop="permission" />
|
|
||||||
<el-table-column :show-overflow-tooltip="true" label="组件路径" prop="component" />
|
|
||||||
<el-table-column :show-overflow-tooltip="true" label="组件名称" prop="componentName" />
|
|
||||||
<el-table-column label="状态" prop="status">
|
|
||||||
<template #default="scope">
|
|
||||||
<el-switch
|
|
||||||
class="ml-4px"
|
|
||||||
v-model="scope.row.status"
|
|
||||||
v-hasPermi="['system:menu:update']"
|
|
||||||
:active-value="CommonStatusEnum.ENABLE"
|
|
||||||
:inactive-value="CommonStatusEnum.DISABLE"
|
|
||||||
:loading="menuStatusUpdating[scope.row.id]"
|
|
||||||
@change="(val) => handleStatusChanged(scope.row, val as number)"
|
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-auto-resizer>
|
||||||
<el-table-column align="center" label="操作">
|
</div>
|
||||||
<template #default="scope">
|
|
||||||
<el-button
|
|
||||||
v-hasPermi="['system:menu:update']"
|
|
||||||
link
|
|
||||||
type="primary"
|
|
||||||
@click="openForm('update', scope.row.id)"
|
|
||||||
>
|
|
||||||
修改
|
|
||||||
</el-button>
|
|
||||||
<el-button
|
|
||||||
v-hasPermi="['system:menu:create']"
|
|
||||||
link
|
|
||||||
type="primary"
|
|
||||||
@click="openForm('create', undefined, scope.row.id)"
|
|
||||||
>
|
|
||||||
新增
|
|
||||||
</el-button>
|
|
||||||
<el-button
|
|
||||||
v-hasPermi="['system:menu:delete']"
|
|
||||||
link
|
|
||||||
type="danger"
|
|
||||||
@click="handleDelete(scope.row.id)"
|
|
||||||
>
|
|
||||||
删除
|
|
||||||
</el-button>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
</el-table>
|
|
||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
|
|
||||||
<!-- 表单弹窗:添加/修改 -->
|
<!-- 表单弹窗:添加/修改 -->
|
||||||
|
@ -138,6 +91,10 @@ import * as MenuApi from '@/api/system/menu'
|
||||||
import { MenuVO } from '@/api/system/menu'
|
import { MenuVO } from '@/api/system/menu'
|
||||||
import MenuForm from './MenuForm.vue'
|
import MenuForm from './MenuForm.vue'
|
||||||
import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
|
import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
|
||||||
|
import { h } from 'vue'
|
||||||
|
import { Column, ElButton } from 'element-plus'
|
||||||
|
import { Icon } from '@/components/Icon'
|
||||||
|
import { hasPermission } from '@/directives/permission/hasPermi'
|
||||||
import { CommonStatusEnum } from '@/utils/constants'
|
import { CommonStatusEnum } from '@/utils/constants'
|
||||||
|
|
||||||
defineOptions({ name: 'SystemMenu' })
|
defineOptions({ name: 'SystemMenu' })
|
||||||
|
@ -146,6 +103,101 @@ const { wsCache } = useCache()
|
||||||
const { t } = useI18n() // 国际化
|
const { t } = useI18n() // 国际化
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
|
|
||||||
|
// 表格的 column 字段
|
||||||
|
const columns: Column[] = [
|
||||||
|
{
|
||||||
|
dataKey: 'name',
|
||||||
|
title: '菜单名称',
|
||||||
|
width: 250
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataKey: 'icon',
|
||||||
|
title: '图标',
|
||||||
|
width: 150,
|
||||||
|
cellRenderer: ({ rowData }) => {
|
||||||
|
return h(Icon, {
|
||||||
|
icon: rowData.icon
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataKey: 'sort',
|
||||||
|
title: '排序',
|
||||||
|
width: 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataKey: 'permission',
|
||||||
|
title: '权限标识',
|
||||||
|
width: 240
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataKey: 'component',
|
||||||
|
title: '组件路径',
|
||||||
|
width: 240
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataKey: 'componentName',
|
||||||
|
title: '组件名称',
|
||||||
|
width: 240
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataKey: 'status',
|
||||||
|
title: '状态',
|
||||||
|
width: 160,
|
||||||
|
cellRenderer: ({ rowData }) => {
|
||||||
|
return h(ElSwitch, {
|
||||||
|
modelValue: rowData.status,
|
||||||
|
activeValue: CommonStatusEnum.ENABLE,
|
||||||
|
inactiveValue: CommonStatusEnum.DISABLE,
|
||||||
|
loading: menuStatusUpdating.value[rowData.id],
|
||||||
|
disabled: !hasPermission(['system:menu:update']),
|
||||||
|
onChange: (val) => handleStatusChanged(rowData, val as number)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataKey: 'operation',
|
||||||
|
title: '操作',
|
||||||
|
width: 200,
|
||||||
|
cellRenderer: ({ rowData }) => {
|
||||||
|
return h(
|
||||||
|
'div',
|
||||||
|
[
|
||||||
|
hasPermission(['system:menu:update']) &&
|
||||||
|
h(
|
||||||
|
ElButton,
|
||||||
|
{
|
||||||
|
link: true,
|
||||||
|
type: 'primary',
|
||||||
|
onClick: () => openForm('update', rowData.id)
|
||||||
|
},
|
||||||
|
'修改'
|
||||||
|
),
|
||||||
|
hasPermission(['system:menu:create']) &&
|
||||||
|
h(
|
||||||
|
ElButton,
|
||||||
|
{
|
||||||
|
link: true,
|
||||||
|
type: 'primary',
|
||||||
|
onClick: () => openForm('create', undefined, rowData.id)
|
||||||
|
},
|
||||||
|
'新增'
|
||||||
|
),
|
||||||
|
hasPermission(['system:menu:delete']) &&
|
||||||
|
h(
|
||||||
|
ElButton,
|
||||||
|
{
|
||||||
|
link: true,
|
||||||
|
type: 'danger',
|
||||||
|
onClick: () => handleDelete(rowData.id)
|
||||||
|
},
|
||||||
|
'删除'
|
||||||
|
)
|
||||||
|
].filter(Boolean)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
const loading = ref(true) // 列表的加载中
|
const loading = ref(true) // 列表的加载中
|
||||||
const list = ref<any>([]) // 列表的数据
|
const list = ref<any>([]) // 列表的数据
|
||||||
const queryParams = reactive({
|
const queryParams = reactive({
|
||||||
|
@ -153,8 +205,6 @@ const queryParams = reactive({
|
||||||
status: undefined
|
status: undefined
|
||||||
})
|
})
|
||||||
const queryFormRef = ref() // 搜索的表单
|
const queryFormRef = ref() // 搜索的表单
|
||||||
const isExpandAll = ref(false) // 是否展开,默认全部折叠
|
|
||||||
const refreshTable = ref(true) // 重新渲染表格状态
|
|
||||||
|
|
||||||
/** 查询列表 */
|
/** 查询列表 */
|
||||||
const getList = async () => {
|
const getList = async () => {
|
||||||
|
@ -184,15 +234,6 @@ const openForm = (type: string, id?: number, parentId?: number) => {
|
||||||
formRef.value.open(type, id, parentId)
|
formRef.value.open(type, id, parentId)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 展开/折叠操作 */
|
|
||||||
const toggleExpandAll = () => {
|
|
||||||
refreshTable.value = false
|
|
||||||
isExpandAll.value = !isExpandAll.value
|
|
||||||
nextTick(() => {
|
|
||||||
refreshTable.value = true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 刷新菜单缓存按钮操作 */
|
/** 刷新菜单缓存按钮操作 */
|
||||||
const refreshMenu = async () => {
|
const refreshMenu = async () => {
|
||||||
try {
|
try {
|
||||||
|
|
Loading…
Reference in New Issue