mirror of https://gitee.com/xiaonuobase/snowy
parent
b712f1c2c4
commit
06008bf1d2
|
@ -24,12 +24,13 @@
|
|||
"@chenfengyuan/vue-qrcode": "2.0.0",
|
||||
"@highlightjs/vue-plugin": "2.1.0",
|
||||
"@kangc/v-md-editor": "2.3.18",
|
||||
"@microsoft/fetch-event-source": "2.0.1",
|
||||
"@tinymce/tinymce-vue": "5.1.1",
|
||||
"@vue-office/docx": "1.6.2",
|
||||
"@vue-office/excel": "1.7.11",
|
||||
"@vue-office/pdf": "2.0.2",
|
||||
"ant-design-vue": "4.2.6",
|
||||
"axios": "1.7.7",
|
||||
"codemirror": "5.65.19",
|
||||
"cropperjs": "1.6.2",
|
||||
"dayjs": "1.11.13",
|
||||
"echarts": "5.5.1",
|
||||
|
@ -52,6 +53,7 @@
|
|||
"vue": "3.5.13",
|
||||
"vue-cropper": "1.1.4",
|
||||
"vue-i18n": "10.0.0",
|
||||
"vue-pdf-embed": "2.1.2",
|
||||
"vue-router": "4.4.5",
|
||||
"vue3-colorpicker": "2.3.0",
|
||||
"vue3-tree-org": "4.2.2",
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
/**
|
||||
* Copyright [2022] [https://www.xiaonuo.vip]
|
||||
* Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
|
||||
* 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
* 2.请不要删除和修改Snowy源码头部的版权声明。
|
||||
* 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
|
||||
* 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
|
||||
* 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
|
||||
* 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
|
||||
*/
|
||||
import { baseRequest } from '@/utils/request'
|
||||
import tool from '@/utils/tool'
|
||||
|
||||
const request = (url, ...arg) => baseRequest(`/auth/c/` + url, ...arg)
|
||||
/**
|
||||
* 登录
|
||||
*
|
||||
* @author yubaoshan
|
||||
* @date 2025-05-31 23:55:10
|
||||
*/
|
||||
export default {
|
||||
// C端获取图片验证码
|
||||
clientGetPicCaptcha(data) {
|
||||
return request('getPicCaptcha', data, 'get')
|
||||
},
|
||||
// C端获取手机验证码
|
||||
clientGetPhoneValidCode(data) {
|
||||
return request('getPhoneValidCode', data, 'get')
|
||||
},
|
||||
// C端获取邮箱验证码
|
||||
clientGetEmailValidCode(data) {
|
||||
return request('getEmailValidCode', data, 'get')
|
||||
},
|
||||
// C端账号密码登录
|
||||
clientLogin(data) {
|
||||
return request('doLogin', data, 'post', false)
|
||||
},
|
||||
// C端手机验证码登录
|
||||
clientLoginByPhone(data) {
|
||||
return request('doLoginByPhone', data, 'post', false)
|
||||
},
|
||||
// C端邮箱验证码登录
|
||||
clientLoginByEmail(data) {
|
||||
return request('doLoginByEmail', data, 'post', false)
|
||||
},
|
||||
// 退出
|
||||
clientLogout(data) {
|
||||
return request('doLogout', data, 'get')
|
||||
},
|
||||
// 获取用户信息
|
||||
clientGetLoginUser(data) {
|
||||
return request('getLoginUser', data, 'get')
|
||||
},
|
||||
// C端注册
|
||||
clientRegister(data) {
|
||||
return request('register', data, 'post')
|
||||
}
|
||||
}
|
|
@ -26,6 +26,10 @@ export default {
|
|||
getPhoneValidCode(data) {
|
||||
return request('getPhoneValidCode', data, 'get')
|
||||
},
|
||||
// B端获取邮箱验证码
|
||||
getEmailValidCode(data) {
|
||||
return request('getEmailValidCode', data, 'get')
|
||||
},
|
||||
// B端账号密码登录
|
||||
login(data) {
|
||||
return request('doLogin', data, 'post', false)
|
||||
|
@ -34,6 +38,10 @@ export default {
|
|||
loginByPhone(data) {
|
||||
return request('doLoginByPhone', data, 'post', false)
|
||||
},
|
||||
// B端邮箱验证码登录
|
||||
loginByEmail(data) {
|
||||
return request('doLoginByEmail', data, 'post', false)
|
||||
},
|
||||
// 退出
|
||||
logout(data) {
|
||||
return request('doLogout', data, 'get')
|
||||
|
@ -41,5 +49,9 @@ export default {
|
|||
// 获取用户信息
|
||||
getLoginUser(data) {
|
||||
return request('getLoginUser', data, 'get')
|
||||
},
|
||||
// 注册用户
|
||||
register(data) {
|
||||
return request('register', data)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
/**
|
||||
* Copyright [2022] [https://www.xiaonuo.vip]
|
||||
* Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
|
||||
* 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
* 2.请不要删除和修改Snowy源码头部的版权声明。
|
||||
* 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
|
||||
* 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
|
||||
* 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
|
||||
* 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
|
||||
*/
|
||||
import { baseRequest } from '@/utils/request'
|
||||
|
||||
const request = (url, ...arg) => baseRequest(`/client/user/` + url, ...arg)
|
||||
/**
|
||||
* 前台用户接口api
|
||||
*
|
||||
* @author yubaoshan
|
||||
* @date 2025-06-01 22:26:20
|
||||
*/
|
||||
export default {
|
||||
// 获取用户分页
|
||||
userPage(data) {
|
||||
return request('page', data, 'get')
|
||||
},
|
||||
// 提交表单 edit为true时为编辑,默认为新增
|
||||
submitForm(data, edit = false) {
|
||||
return request(edit ? 'edit' : 'add', data)
|
||||
},
|
||||
// 删除用户
|
||||
userDelete(data) {
|
||||
return request('delete', data)
|
||||
},
|
||||
// 获取用户详情
|
||||
userDetail(data) {
|
||||
return request('detail', data, 'get')
|
||||
}
|
||||
}
|
|
@ -0,0 +1,113 @@
|
|||
/**
|
||||
* Copyright [2022] [https://www.xiaonuo.vip]
|
||||
* Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
|
||||
* 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
* 2.请不要删除和修改Snowy源码头部的版权声明。
|
||||
* 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
|
||||
* 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
|
||||
* 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
|
||||
* 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
|
||||
*/
|
||||
import { baseRequest } from '@/utils/request'
|
||||
|
||||
const request = (url, ...arg) => baseRequest(`/client/userCenter/` + url, ...arg)
|
||||
/**
|
||||
* C端用户个人控制器
|
||||
*
|
||||
* @author xuyuxiang
|
||||
* @date 2022-04-22 09:34:00
|
||||
*/
|
||||
export default {
|
||||
// 获取图片验证码
|
||||
clientUserGetPicCaptcha(data) {
|
||||
return request('getPicCaptcha', data, 'get')
|
||||
},
|
||||
// 找回密码获取手机验证码
|
||||
clientUserFindPasswordGetPhoneValidCode(data) {
|
||||
return request('findPasswordGetPhoneValidCode', data, 'get')
|
||||
},
|
||||
// 找回密码获取邮箱验证码
|
||||
clientUserFindPasswordGetEmailValidCode(data) {
|
||||
return request('findPasswordGetEmailValidCode', data, 'get')
|
||||
},
|
||||
// 通过手机号找回用户密码
|
||||
clientUserFindPasswordByPhone(data) {
|
||||
return request('findPasswordByPhone', data)
|
||||
},
|
||||
// 通过邮箱找回用户密码
|
||||
clientUserFindPasswordByEmail(data) {
|
||||
return request('findPasswordByEmail', data)
|
||||
},
|
||||
// 修改密码获取手机验证码
|
||||
clientUserUpdatePasswordGetPhoneValidCode(data) {
|
||||
return request('updatePasswordGetPhoneValidCode', data, 'get')
|
||||
},
|
||||
// 修改密码获取邮箱验证码
|
||||
clientUserUpdatePasswordGetEmailValidCode(data) {
|
||||
return request('updatePasswordGetEmailValidCode', data, 'get')
|
||||
},
|
||||
// 通过验证旧密码修改用户密码
|
||||
clientUserUpdatePasswordByOld(data) {
|
||||
return request('updatePasswordByOld', data)
|
||||
},
|
||||
// 通过验证手机号修改用户密码
|
||||
clientUserUpdatePasswordByPhone(data) {
|
||||
return request('updatePasswordByPhone', data)
|
||||
},
|
||||
// 通过验证邮箱修改用户密码
|
||||
clientUserUpdatePasswordByEmail(data) {
|
||||
return request('updatePasswordByEmail', data)
|
||||
},
|
||||
// 绑定手机号获取手机验证码
|
||||
clientUserBindPhoneGetPhoneValidCode(data) {
|
||||
return request('bindPhoneGetPhoneValidCode', data, 'get')
|
||||
},
|
||||
// 修改绑定手机号获取手机验证码
|
||||
clientUserUpdateBindPhoneGetPhoneValidCode(data) {
|
||||
return request('updateBindPhoneGetPhoneValidCode', data, 'get')
|
||||
},
|
||||
// 绑定手机号
|
||||
clientUserBindPhone(data) {
|
||||
return request('bindPhone', data)
|
||||
},
|
||||
// 绑定邮箱获取邮箱验证码
|
||||
clientUserBindEmailGetEmailValidCode(data) {
|
||||
return request('bindEmailGetEmailValidCode', data, 'get')
|
||||
},
|
||||
// 修改绑定邮箱获取邮箱验证码
|
||||
clientUserUpdateBindEmailGetEmailValidCode(data) {
|
||||
return request('updateBindEmailGetEmailValidCode', data, 'get')
|
||||
},
|
||||
// 绑定邮箱
|
||||
clientUserBindEmail(data) {
|
||||
return request('bindEmail', data)
|
||||
},
|
||||
// 修改用户头像
|
||||
clientUserUpdateAvatar(data) {
|
||||
return request('updateAvatar', data)
|
||||
},
|
||||
// 修改用户签名图片
|
||||
clientUserUpdateSignature(data) {
|
||||
return request('updateSignature', data)
|
||||
},
|
||||
// 编辑个人信息
|
||||
clientUserUpdateUserInfo(data) {
|
||||
return request('updateUserInfo', data)
|
||||
},
|
||||
// 根据id获取头像
|
||||
clientUserGetAvatarById(data) {
|
||||
return request('getAvatarById', data, 'get')
|
||||
},
|
||||
// 判断当前用户是否需要绑定手机号
|
||||
clientUserIsUserNeedBindPhone(data) {
|
||||
return request('isUserNeedBindPhone', data, 'get')
|
||||
},
|
||||
// 判断当前用户是否需要绑定邮箱
|
||||
clientUserIsUserNeedBindEmail(data) {
|
||||
return request('isUserNeedBindEmail', data, 'get')
|
||||
},
|
||||
// 判断当前用户密码是否过期
|
||||
clientUserIsUserPasswordExpired(data) {
|
||||
return request('isUserPasswordExpired', data, 'get')
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
import { baseRequest } from '@/utils/request'
|
||||
|
||||
const request = (url, ...arg) => baseRequest(`/dev/weakPassword/` + url, ...arg)
|
||||
|
||||
/**
|
||||
* 弱密码库Api接口管理器
|
||||
*
|
||||
* @author yubaoshan
|
||||
* @date 2025/05/31 01:45
|
||||
**/
|
||||
export default {
|
||||
// 获取弱密码库分页
|
||||
weakPasswordPage(data) {
|
||||
return request('page', data, 'get')
|
||||
},
|
||||
// 提交弱密码库表单 edit为true时为编辑,默认为新增
|
||||
weakPasswordSubmitForm(data, edit = false) {
|
||||
return request(edit ? 'edit' : 'add', data)
|
||||
},
|
||||
// 删除弱密码库
|
||||
weakPasswordDelete(data) {
|
||||
return request('delete', data)
|
||||
},
|
||||
// 获取弱密码库详情
|
||||
weakPasswordDetail(data) {
|
||||
return request('detail', data, 'get')
|
||||
}
|
||||
}
|
|
@ -59,24 +59,18 @@ export default {
|
|||
return request('updatePasswordByEmail', data)
|
||||
},
|
||||
// 绑定手机号获取手机验证码
|
||||
userBindPhoneGetPhoneValidCode(data) {
|
||||
return request('bindPhoneGetPhoneValidCode', data)
|
||||
userBindPhoneGetPhoneValidCode(data, phone) {
|
||||
// 如果有手机号,则修改获取、否则首次绑定
|
||||
return request(phone ? 'updateBindPhoneGetPhoneValidCode' : 'bindPhoneGetPhoneValidCode', data, 'get')
|
||||
},
|
||||
// 修改绑定手机号获取手机验证码
|
||||
userUpdateBindPhoneGetPhoneValidCode(data) {
|
||||
return request('updateBindPhoneGetPhoneValidCode', data)
|
||||
},
|
||||
// 修改绑定手机号获取手机验证码
|
||||
// 绑定手机号
|
||||
userBindPhone(data) {
|
||||
return request('bindPhone', data)
|
||||
},
|
||||
// 绑定邮箱获取邮箱验证码
|
||||
userBindEmailGetEmailValidCode(data) {
|
||||
return request('bindEmailGetEmailValidCode', data)
|
||||
},
|
||||
// 修改绑定邮箱获取邮箱验证码
|
||||
userUpdateBindEmailGetEmailValidCode(data) {
|
||||
return request('updateBindEmailGetEmailValidCode', data)
|
||||
userBindEmailGetEmailValidCode(data, email) {
|
||||
// 如果有邮箱号,则修改获取、否则首次绑定
|
||||
return request(email ? 'updateBindEmailGetEmailValidCode' : 'bindEmailGetEmailValidCode', data, 'get')
|
||||
},
|
||||
// 绑定邮箱
|
||||
userBindEmail(data) {
|
||||
|
|
|
@ -9,26 +9,28 @@
|
|||
返回
|
||||
</a-button>
|
||||
</a-space>
|
||||
<a-card :bordered="false" :body-style="{ padding: '0px' }">
|
||||
<a-card :bordered="false" :body-style="{ padding: 0 }">
|
||||
<a-spin :spinning="loading">
|
||||
<vue-office-docx
|
||||
v-if="fileType === 'doc' || fileType === 'docx'"
|
||||
:src="props.src"
|
||||
:src="props.src + '&token=' + tool.data.get('TOKEN')"
|
||||
class="xn-ht82"
|
||||
@rendered="renderedHandler"
|
||||
/>
|
||||
<vue-office-excel
|
||||
v-else-if="fileType === 'xls' || fileType === 'xlsx'"
|
||||
:src="props.src"
|
||||
:src="props.src + '&token=' + tool.data.get('TOKEN')"
|
||||
class="xn-ht82"
|
||||
@rendered="renderedHandler"
|
||||
@error="errorHandler"
|
||||
/>
|
||||
<vue-office-pdf
|
||||
<vue-pdf-embed
|
||||
v-else-if="fileType === 'pdf'"
|
||||
:src="props.src"
|
||||
annotation-layer
|
||||
text-layer
|
||||
:source="props.src + '&token=' + tool.data.get('TOKEN')"
|
||||
@rendered="renderedHandler"
|
||||
@error="errorHandler"
|
||||
@renderingFailed="errorHandler"
|
||||
/>
|
||||
<img
|
||||
v-else-if="
|
||||
|
@ -40,7 +42,7 @@
|
|||
fileType === 'ico' ||
|
||||
fileType === 'svg'
|
||||
"
|
||||
:src="props.src"
|
||||
:src="props.src + '&token=' + tool.data.get('TOKEN')"
|
||||
class="xn-mwh"
|
||||
/>
|
||||
<a-result v-else status="warning" title="不支持预览的文件类型" />
|
||||
|
@ -50,6 +52,7 @@
|
|||
</template>
|
||||
|
||||
<script setup>
|
||||
import tool from '@/utils/tool'
|
||||
import { message } from 'ant-design-vue'
|
||||
//引入VueOfficeDocx组件
|
||||
import VueOfficeDocx from '@vue-office/docx'
|
||||
|
@ -59,8 +62,11 @@
|
|||
import VueOfficeExcel from '@vue-office/excel'
|
||||
//引入相关样式
|
||||
import '@vue-office/excel/lib/index.css'
|
||||
//引入VueOfficePdf组件
|
||||
import VueOfficePdf from '@vue-office/pdf'
|
||||
//引Pdf组件
|
||||
// 调用组件:https://github.com/hrynko/vue-pdf-embed
|
||||
import VuePdfEmbed from 'vue-pdf-embed'
|
||||
import 'vue-pdf-embed/dist/styles/annotationLayer.css'
|
||||
import 'vue-pdf-embed/dist/styles/textLayer.css'
|
||||
|
||||
const loading = ref(false)
|
||||
const emit = defineEmits({ goBack: null })
|
||||
|
|
|
@ -17,7 +17,8 @@
|
|||
</a-col>
|
||||
<a-col :span="9">
|
||||
<div class="xn-h90wat">
|
||||
<img :src="resultImg" class="xn-bdr236 xn-h90w100" />
|
||||
<img v-if="resultImg" :src="resultImg" class="xn-bdr236 xn-h90w100" />
|
||||
<a-empty v-else />
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
|
|
@ -75,9 +75,15 @@ const DEFAULT_CONFIG = {
|
|||
// 默认整体主题
|
||||
SNOWY_THEME: 'dark',
|
||||
|
||||
// 整体表单风格
|
||||
// 整体表单风格 modal|drawer
|
||||
SNOWY_FORM_STYLE: 'drawer',
|
||||
|
||||
// 前后台登录链接是否展示
|
||||
FRONT_BACK_LOGIN_URL_SHOW: true,
|
||||
|
||||
// 三方登录是否展示
|
||||
THREE_LOGIN_SHOW: true,
|
||||
|
||||
// 系统基础配置,这些是数据库中保存起来的
|
||||
SYS_BASE_CONFIG: {
|
||||
// 默认logo
|
||||
|
@ -87,15 +93,15 @@ const DEFAULT_CONFIG = {
|
|||
// 系统名称
|
||||
SNOWY_SYS_NAME: 'Snowy',
|
||||
// 版本
|
||||
SNOWY_SYS_VERSION: '2.0',
|
||||
SNOWY_SYS_VERSION: '3.0',
|
||||
// 版权
|
||||
SNOWY_SYS_COPYRIGHT: 'Snowy ©2022 Created by xiaonuo.vip',
|
||||
// 版权跳转URL
|
||||
SNOWY_SYS_COPYRIGHT_URL: 'https://www.xiaonuo.vip',
|
||||
// 默认文件存储
|
||||
SNOWY_SYS_DEFAULT_FILE_ENGINE: 'LOCAL',
|
||||
// 是否开启验证码
|
||||
SNOWY_SYS_DEFAULT_CAPTCHA_OPEN: 'false',
|
||||
// 是否开启B端验证码
|
||||
SNOWY_SYS_DEFAULT_CAPTCHA_OPEN_FLAG_FOR_B: 'false',
|
||||
// 默认重置密码
|
||||
SNOWY_SYS_DEFAULT_PASSWORD: '123456'
|
||||
}
|
||||
|
|
|
@ -0,0 +1,178 @@
|
|||
<script setup name="bindEmail">
|
||||
import { ref, reactive } from 'vue'
|
||||
import userCenterApi from '@/api/sys/userCenterApi'
|
||||
import { message, Modal, Form, Input, Button } from 'ant-design-vue'
|
||||
import { required, rules } from '@/utils/formRules'
|
||||
|
||||
const visible = ref(false)
|
||||
const loading = ref(false)
|
||||
const captchaLoading = ref(false)
|
||||
const captchaImage = ref('')
|
||||
const captchaReqNo = ref('')
|
||||
const messageCodeReqNo = ref('')
|
||||
let state = ref({
|
||||
time: 60,
|
||||
sendBtn: false
|
||||
})
|
||||
const formRef = ref()
|
||||
const formState = reactive({
|
||||
email: '',
|
||||
validCode: '',
|
||||
emailValidCode: '',
|
||||
validCodeReqNo: ''
|
||||
})
|
||||
|
||||
// 检查是否需要绑定邮箱号
|
||||
const checkNeedBindEmail = () => {
|
||||
userCenterApi.userCenterIsUserNeedBindEmail().then((data) => {
|
||||
if (data) {
|
||||
visible.value = true
|
||||
getCaptcha()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 获取图片验证码
|
||||
const getCaptcha = () => {
|
||||
captchaLoading.value = true
|
||||
try {
|
||||
userCenterApi.userGetPicCaptcha().then((data) => {
|
||||
captchaImage.value = data.validCodeBase64
|
||||
captchaReqNo.value = data.validCodeReqNo
|
||||
})
|
||||
} finally {
|
||||
captchaLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 获取邮箱验证码
|
||||
const getEmailValidCode = async () => {
|
||||
try {
|
||||
if (!formState.email) {
|
||||
message.error('请输入邮箱号')
|
||||
return
|
||||
}
|
||||
if (!formState.validCode) {
|
||||
message.error('请输入图片验证码')
|
||||
return
|
||||
}
|
||||
const hide = message.loading('验证码发送中..', 0)
|
||||
userCenterApi
|
||||
.userBindEmailGetEmailValidCode({
|
||||
email: formState.email,
|
||||
validCode: formState.validCode,
|
||||
validCodeReqNo: captchaReqNo.value
|
||||
})
|
||||
.then((data) => {
|
||||
// 禁用发送按钮,并设置为倒计时
|
||||
state.value.sendBtn = true
|
||||
const interval = window.setInterval(() => {
|
||||
if (state.value.time-- <= 0) {
|
||||
state.value.time = 60
|
||||
state.value.sendBtn = false
|
||||
window.clearInterval(interval)
|
||||
}
|
||||
}, 1000)
|
||||
messageCodeReqNo.value = data
|
||||
message.success('验证码已发送到邮箱')
|
||||
})
|
||||
.catch(() => {
|
||||
// 清理掉已经输入的
|
||||
formState.validCode = ''
|
||||
formState.validCodeReqNo = ''
|
||||
messageCodeReqNo.value = ''
|
||||
getCaptcha()
|
||||
})
|
||||
.finally(() => {
|
||||
setTimeout(hide, 100)
|
||||
})
|
||||
} catch (error) {
|
||||
getCaptcha()
|
||||
}
|
||||
}
|
||||
|
||||
// 提交表单
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
await formRef.value.validate()
|
||||
loading.value = true
|
||||
userCenterApi
|
||||
.userBindEmail({
|
||||
email: formState.email,
|
||||
validCode: formState.emailValidCode,
|
||||
validCodeReqNo: messageCodeReqNo.value
|
||||
})
|
||||
.then(() => {
|
||||
message.success('绑定成功')
|
||||
visible.value = false
|
||||
})
|
||||
.catch(() => {
|
||||
formState.emailValidCode = ''
|
||||
messageCodeReqNo.value = ''
|
||||
})
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 表单验证规则
|
||||
const formRules = {
|
||||
email: [required('请输入邮箱号'), rules.email],
|
||||
validCode: required('请输入图片验证码'),
|
||||
emailValidCode: [{ required: true, message: '请输入邮箱验证码', trigger: 'blur' }]
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
checkNeedBindEmail
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal v-model:open="visible" title="绑定邮箱号" :maskClosable="false" :closable="false" :width="400">
|
||||
<Form ref="formRef" :model="formState" :rules="formRules" layout="vertical">
|
||||
<Form.Item name="email">
|
||||
<Input v-model:value="formState.email" placeholder="请输入邮箱号" />
|
||||
</Form.Item>
|
||||
<Form.Item name="validCode">
|
||||
<a-row :gutter="8">
|
||||
<a-col :span="16">
|
||||
<Input v-model:value="formState.validCode" placeholder="请输入图片验证码" />
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<img v-if="captchaImage" :src="captchaImage" class="captcha-image" @click="getCaptcha" />
|
||||
</a-col>
|
||||
</a-row>
|
||||
</Form.Item>
|
||||
<Form.Item name="emailValidCode">
|
||||
<a-row :gutter="8">
|
||||
<a-col :span="16">
|
||||
<Input v-model:value="formState.emailValidCode" placeholder="请输入邮箱验证码" />
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<Button :loading="captchaLoading" @click="getEmailValidCode" style="width: 100%" :disabled="state.sendBtn">
|
||||
{{ (!state.sendBtn && '获取验证码') || state.time + ' s' }}
|
||||
</Button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
<template #footer>
|
||||
<Button type="primary" :loading="loading" @click="handleSubmit">确定</Button>
|
||||
</template>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<style scoped lang="less">
|
||||
.captcha-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
.captcha-image {
|
||||
height: 32px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,178 @@
|
|||
<script setup name="bindPhone">
|
||||
import { ref, reactive } from 'vue'
|
||||
import userCenterApi from '@/api/sys/userCenterApi'
|
||||
import { message, Modal, Form, Input, Button } from 'ant-design-vue'
|
||||
import { required, rules } from '@/utils/formRules'
|
||||
|
||||
const visible = ref(false)
|
||||
const loading = ref(false)
|
||||
const captchaLoading = ref(false)
|
||||
const captchaImage = ref('')
|
||||
const captchaReqNo = ref('')
|
||||
const messageCodeReqNo = ref('')
|
||||
let state = ref({
|
||||
time: 60,
|
||||
sendBtn: false
|
||||
})
|
||||
const formRef = ref()
|
||||
const formState = reactive({
|
||||
phone: '',
|
||||
validCode: '',
|
||||
phoneValidCode: '',
|
||||
validCodeReqNo: ''
|
||||
})
|
||||
|
||||
// 检查是否需要绑定手机号
|
||||
const checkNeedBindPhone = () => {
|
||||
userCenterApi.userCenterIsUserNeedBindPhone().then((data) => {
|
||||
if (data) {
|
||||
visible.value = true
|
||||
getCaptcha()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 获取图片验证码
|
||||
const getCaptcha = () => {
|
||||
captchaLoading.value = true
|
||||
try {
|
||||
userCenterApi.userGetPicCaptcha().then((data) => {
|
||||
captchaImage.value = data.validCodeBase64
|
||||
captchaReqNo.value = data.validCodeReqNo
|
||||
})
|
||||
} finally {
|
||||
captchaLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 获取手机验证码
|
||||
const getPhoneValidCode = async () => {
|
||||
try {
|
||||
if (!formState.phone) {
|
||||
message.error('请输入手机号')
|
||||
return
|
||||
}
|
||||
if (!formState.validCode) {
|
||||
message.error('请输入图片验证码')
|
||||
return
|
||||
}
|
||||
const hide = message.loading('验证码发送中..', 0)
|
||||
userCenterApi
|
||||
.userBindPhoneGetPhoneValidCode({
|
||||
phone: formState.phone,
|
||||
validCode: formState.validCode,
|
||||
validCodeReqNo: captchaReqNo.value
|
||||
})
|
||||
.then((data) => {
|
||||
// 禁用发送按钮,并设置为倒计时
|
||||
state.value.sendBtn = true
|
||||
const interval = window.setInterval(() => {
|
||||
if (state.value.time-- <= 0) {
|
||||
state.value.time = 60
|
||||
state.value.sendBtn = false
|
||||
window.clearInterval(interval)
|
||||
}
|
||||
}, 1000)
|
||||
messageCodeReqNo.value = data
|
||||
message.success('验证码已发送到手机')
|
||||
})
|
||||
.catch(() => {
|
||||
// 清理掉已经输入的
|
||||
formState.validCode = ''
|
||||
formState.validCodeReqNo = ''
|
||||
messageCodeReqNo.value = ''
|
||||
getCaptcha()
|
||||
})
|
||||
.finally(() => {
|
||||
setTimeout(hide, 100)
|
||||
})
|
||||
} catch (error) {
|
||||
getCaptcha()
|
||||
}
|
||||
}
|
||||
|
||||
// 提交表单
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
await formRef.value.validate()
|
||||
loading.value = true
|
||||
userCenterApi
|
||||
.userBindPhone({
|
||||
phone: formState.phone,
|
||||
validCode: formState.phoneValidCode,
|
||||
validCodeReqNo: messageCodeReqNo.value
|
||||
})
|
||||
.then(() => {
|
||||
message.success('绑定成功')
|
||||
visible.value = false
|
||||
})
|
||||
.catch(() => {
|
||||
formState.phoneValidCode = ''
|
||||
messageCodeReqNo.value = ''
|
||||
})
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 表单验证规则
|
||||
const formRules = {
|
||||
phone: [required('请输入手机号'), rules.phone],
|
||||
validCode: required('请输入图片验证码'),
|
||||
phoneValidCode: [{ required: true, message: '请输入短信验证码', trigger: 'blur' }]
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
checkNeedBindPhone
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal v-model:open="visible" title="绑定手机号" :maskClosable="false" :closable="false" :width="400">
|
||||
<Form ref="formRef" :model="formState" :rules="formRules" layout="vertical">
|
||||
<Form.Item name="phone">
|
||||
<Input v-model:value="formState.phone" placeholder="请输入手机号" />
|
||||
</Form.Item>
|
||||
<Form.Item name="validCode">
|
||||
<a-row :gutter="8">
|
||||
<a-col :span="16">
|
||||
<Input v-model:value="formState.validCode" placeholder="请输入图片验证码" />
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<img v-if="captchaImage" :src="captchaImage" class="captcha-image" @click="getCaptcha" />
|
||||
</a-col>
|
||||
</a-row>
|
||||
</Form.Item>
|
||||
<Form.Item name="phoneValidCode">
|
||||
<a-row :gutter="8">
|
||||
<a-col :span="16">
|
||||
<Input v-model:value="formState.phoneValidCode" placeholder="请输入短信验证码" />
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<Button :loading="captchaLoading" @click="getPhoneValidCode" style="width: 100%" :disabled="state.sendBtn">
|
||||
{{ (!state.sendBtn && '获取验证码') || state.time + ' s' }}
|
||||
</Button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
<template #footer>
|
||||
<Button type="primary" :loading="loading" @click="handleSubmit">确定</Button>
|
||||
</template>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<style scoped lang="less">
|
||||
.captcha-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
.captcha-image {
|
||||
height: 32px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -10,7 +10,7 @@
|
|||
:width="600"
|
||||
destroyOnClose
|
||||
dialogClass="searchModal"
|
||||
:bodyStyle="{ maxHeight: '520px', overflow: 'auto', padding: '14px' }"
|
||||
:bodyStyle="{ overflow: 'auto', padding: '14px' }"
|
||||
@close="searchPanelClose"
|
||||
>
|
||||
<div
|
||||
|
@ -307,7 +307,7 @@
|
|||
}
|
||||
}
|
||||
.search-card {
|
||||
height: 380px;
|
||||
height: 580px;
|
||||
overflow: hidden;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
@onOpenChange="onOpenChange"
|
||||
@switchModule="switchModule"
|
||||
@menuIsCollapseClick="menuIsCollapseClick"
|
||||
@displayLayoutChange="exitMaximize"
|
||||
/>
|
||||
<!-- 双排菜单布局 -->
|
||||
<DoubleRowMenu
|
||||
|
@ -43,6 +44,7 @@
|
|||
@onSelect="onSelect"
|
||||
@switchModule="switchModule"
|
||||
@showMenu="showMenu"
|
||||
@displayLayoutChange="exitMaximize"
|
||||
/>
|
||||
<!-- 顶部菜单布局 -->
|
||||
<TopMenu
|
||||
|
@ -63,12 +65,15 @@
|
|||
@switchModule="switchModule"
|
||||
@onOpenChange="onOpenChange"
|
||||
@onSelect="onSelect"
|
||||
@displayLayoutChange="exitMaximize"
|
||||
/>
|
||||
|
||||
<!-- 退出最大化 -->
|
||||
<div class="main-maximize-exit" @click="exitMaximize">
|
||||
<fullscreen-exit-outlined class="xn-color-fff" />
|
||||
</div>
|
||||
<bind-phone ref="bindPhoneRef" />
|
||||
<bind-email ref="bindEmailRef" />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
@ -85,6 +90,8 @@
|
|||
import { NextLoading } from '@/utils/loading'
|
||||
import { useMenuStore } from '@/store/menu'
|
||||
import { getLocalHash, checkHash } from '@/utils/version'
|
||||
import BindPhone from '@/layout/components/bindPhone.vue'
|
||||
import BindEmail from '@/layout/components/bindEmail.vue'
|
||||
|
||||
const store = globalStore()
|
||||
const kStore = keepAliveStore()
|
||||
|
@ -101,6 +108,8 @@
|
|||
const doublerowSelectedKey = ref([])
|
||||
const layoutSiderDowbleMenu = ref(true)
|
||||
const menuList = ref([])
|
||||
const bindPhoneRef = ref()
|
||||
const bindEmailRef = ref()
|
||||
// computed计算方法 - start
|
||||
const layout = computed(() => {
|
||||
return store.layout
|
||||
|
@ -240,7 +249,7 @@
|
|||
onMounted(() => {
|
||||
// 取消loading
|
||||
NextLoading.done()
|
||||
showThis()
|
||||
// showThis()
|
||||
onLayoutResize()
|
||||
window.addEventListener('resize', onLayoutResize)
|
||||
window.addEventListener('resize', getNav)
|
||||
|
@ -250,6 +259,8 @@
|
|||
updateVersion()
|
||||
nextTick(() => {
|
||||
getNav()
|
||||
bindPhoneRef.value.checkNeedBindPhone()
|
||||
bindEmailRef.value.checkNeedBindEmail()
|
||||
})
|
||||
})
|
||||
onBeforeUnmount(() => {
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
collapsible
|
||||
:theme="sideTheme"
|
||||
width="210"
|
||||
v-show="displayLayout"
|
||||
>
|
||||
<header id="snowyHeaderLogo" class="snowy-header-logo">
|
||||
<div class="snowy-header-left">
|
||||
|
@ -19,8 +20,8 @@
|
|||
<div :class="menuIsCollapse ? 'admin-ui-side isCollapse' : 'admin-ui-side'">
|
||||
<div class="admin-ui-side-scroll">
|
||||
<a-menu
|
||||
v-bind:openKeys="openKeys"
|
||||
v-bind:selectedKeys="selectedKeys"
|
||||
:openKeys="openKeys"
|
||||
:selectedKeys="selectedKeys"
|
||||
:theme="sideTheme"
|
||||
mode="inline"
|
||||
@select="onSelect"
|
||||
|
@ -32,10 +33,10 @@
|
|||
</div>
|
||||
</a-layout-sider>
|
||||
<!-- 手机端情况下的左侧菜单 -->
|
||||
<Side-m v-if="isMobile" />
|
||||
<Side-m v-if="isMobile" v-show="displayLayout" />
|
||||
<!-- 右侧布局 -->
|
||||
<a-layout>
|
||||
<div id="snowyHeader" class="snowy-header">
|
||||
<div id="snowyHeader" class="snowy-header" v-show="displayLayout">
|
||||
<div class="snowy-header-left xn-pl0">
|
||||
<div v-if="!isMobile" class="panel-item hidden-sm-and-down" @click="menuIsCollapseClick">
|
||||
<MenuUnfoldOutlined v-if="menuIsCollapse" />
|
||||
|
@ -47,10 +48,12 @@
|
|||
<user-bar />
|
||||
</div>
|
||||
</div>
|
||||
<Breadcrumb v-if="!isMobile && breadcrumbOpen" />
|
||||
<Breadcrumb v-if="!isMobile && breadcrumbOpen" v-show="displayLayout" />
|
||||
<!-- 多标签 -->
|
||||
<Tags v-if="!isMobile && layoutTagsOpen" />
|
||||
<a-layout-content class="main-content-wrapper">
|
||||
<Tags v-if="!isMobile && layoutTagsOpen" v-show="displayLayout" />
|
||||
<a-layout-content
|
||||
:class="displayLayout ? 'main-content-wrapper' : 'main-content-wrapper main-content-wrapper-max'"
|
||||
>
|
||||
<div id="admin-ui-main" class="admin-ui-main">
|
||||
<router-view v-slot="{ Component }">
|
||||
<keep-alive :include="kStore.keepLiveRoute">
|
||||
|
@ -72,8 +75,6 @@
|
|||
<script setup>
|
||||
import { useRoute } from 'vue-router'
|
||||
import { MenuUnfoldOutlined, MenuFoldOutlined } from '@ant-design/icons-vue'
|
||||
const route = useRoute()
|
||||
|
||||
import UserBar from '@/layout/components/userbar.vue'
|
||||
import Tags from '@/layout/components/tags.vue'
|
||||
import SideM from '@/layout/components/sideM.vue'
|
||||
|
@ -97,8 +98,39 @@
|
|||
footerCopyrightOpen: { type: Boolean }, //页脚版权信息
|
||||
moduleMenuShow: { type: Boolean }
|
||||
})
|
||||
|
||||
const emit = defineEmits(['onSelect', 'onOpenChange', 'switchModule', 'menuIsCollapseClick'])
|
||||
const emit = defineEmits(['onSelect', 'onOpenChange', 'switchModule', 'menuIsCollapseClick', 'displayLayoutChange'])
|
||||
const displayLayout = ref(true)
|
||||
const route = useRoute()
|
||||
watch(route, () => {
|
||||
nextTick(() => {
|
||||
displayLayout.value = displayLayoutResult()
|
||||
})
|
||||
if (displayLayout.value) {
|
||||
emit('displayLayoutChange')
|
||||
}
|
||||
})
|
||||
onMounted(() => {
|
||||
nextTick(() => {
|
||||
displayLayout.value = displayLayoutResult()
|
||||
})
|
||||
})
|
||||
const displayLayoutResult = () => {
|
||||
// 根据route.meta.keepLive动态管理keepLiveRoute
|
||||
if (route.meta.keepLive === true) {
|
||||
props.kStore.pushKeepLive(route.name)
|
||||
} else {
|
||||
props.kStore.removeKeepLive(route.name)
|
||||
}
|
||||
if (
|
||||
route.meta.displayLayout === undefined ||
|
||||
route.meta.displayLayout === null ||
|
||||
route.meta.displayLayout === 'null'
|
||||
) {
|
||||
return true
|
||||
} else {
|
||||
return route.meta.displayLayout
|
||||
}
|
||||
}
|
||||
const onSelect = (obj) => {
|
||||
emit('onSelect', obj)
|
||||
}
|
||||
|
@ -152,4 +184,7 @@
|
|||
.xn-mg050 {
|
||||
margin: 0px 150px;
|
||||
}
|
||||
.main-content-wrapper-max {
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<a-layout>
|
||||
<a-layout-sider v-if="!isMobile" width="80" :theme="sideTheme" :trigger="null" collapsible>
|
||||
<a-layout-sider v-if="!isMobile" width="80" :theme="sideTheme" :trigger="null" collapsible v-show="displayLayout">
|
||||
<header id="snowyHeaderLogo" class="snowy-header-logo">
|
||||
<div class="snowy-header-left">
|
||||
<div class="logo-bar">
|
||||
|
@ -43,9 +43,9 @@
|
|||
</a-menu>
|
||||
</a-layout-sider>
|
||||
<!-- 手机端情况下的左侧菜单 -->
|
||||
<Side-m v-if="isMobile" />
|
||||
<Side-m v-if="isMobile" v-show="displayLayout" />
|
||||
<a-layout>
|
||||
<div id="snowyHeader" class="snowy-header">
|
||||
<div id="snowyHeader" class="snowy-header" v-show="displayLayout">
|
||||
<div class="snowy-header-left xn-pl0">
|
||||
<moduleMenu v-if="moduleMenuShow" @switchModule="switchModule" />
|
||||
</div>
|
||||
|
@ -54,9 +54,10 @@
|
|||
</div>
|
||||
</div>
|
||||
<a-layout>
|
||||
<div v-show="displayLayout"></div>
|
||||
<a-layout-sider
|
||||
v-if="!isMobile"
|
||||
v-show="layoutSiderDowbleMenu"
|
||||
v-show="displayLayout && layoutSiderDowbleMenu"
|
||||
:collapsed="menuIsCollapse"
|
||||
:trigger="null"
|
||||
width="170"
|
||||
|
@ -75,10 +76,10 @@
|
|||
</a-menu>
|
||||
</a-layout-sider>
|
||||
<a-layout-content>
|
||||
<breadcrumb v-if="!isMobile && breadcrumbOpen" />
|
||||
<breadcrumb v-if="!isMobile && breadcrumbOpen" v-show="displayLayout" />
|
||||
<!-- 多标签 -->
|
||||
<Tags v-if="!isMobile && layoutTagsOpen" />
|
||||
<div class="main-content-wrapper">
|
||||
<Tags v-if="!isMobile && layoutTagsOpen" v-show="displayLayout" />
|
||||
<div :class="displayLayout ? 'main-content-wrapper' : 'main-content-wrapper main-content-wrapper-max'">
|
||||
<div id="admin-ui-main" class="admin-ui-main">
|
||||
<router-view v-slot="{ Component }">
|
||||
<keep-alive :include="kStore.keepLiveRoute">
|
||||
|
@ -101,8 +102,6 @@
|
|||
|
||||
<script setup>
|
||||
import { useRoute } from 'vue-router'
|
||||
const route = useRoute()
|
||||
|
||||
import UserBar from '@/layout/components/userbar.vue'
|
||||
import Tags from '@/layout/components/tags.vue'
|
||||
import SideM from '@/layout/components/sideM.vue'
|
||||
|
@ -130,8 +129,39 @@
|
|||
moduleMenuShow: { type: Boolean },
|
||||
secondMenuSideTheme: {}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['onSelect', 'switchModule', 'showMenu'])
|
||||
const emit = defineEmits(['onSelect', 'switchModule', 'showMenu', 'displayLayoutChange'])
|
||||
const displayLayout = ref(true)
|
||||
const route = useRoute()
|
||||
watch(route, () => {
|
||||
nextTick(() => {
|
||||
displayLayout.value = displayLayoutResult()
|
||||
})
|
||||
if (displayLayout.value) {
|
||||
emit('displayLayoutChange')
|
||||
}
|
||||
})
|
||||
onMounted(() => {
|
||||
nextTick(() => {
|
||||
displayLayout.value = displayLayoutResult()
|
||||
})
|
||||
})
|
||||
const displayLayoutResult = () => {
|
||||
// 根据route.meta.keepLive动态管理keepLiveRoute
|
||||
if (route.meta.keepLive === true) {
|
||||
props.kStore.pushKeepLive(route.name)
|
||||
} else {
|
||||
props.kStore.removeKeepLive(route.name)
|
||||
}
|
||||
if (
|
||||
route.meta.displayLayout === undefined ||
|
||||
route.meta.displayLayout === null ||
|
||||
route.meta.displayLayout === 'null'
|
||||
) {
|
||||
return true
|
||||
} else {
|
||||
return route.meta.displayLayout
|
||||
}
|
||||
}
|
||||
const onSelect = (obj) => {
|
||||
emit('onSelect', obj)
|
||||
}
|
||||
|
@ -182,4 +212,7 @@
|
|||
.xn-mg050 {
|
||||
margin: 0px 150px;
|
||||
}
|
||||
.main-content-wrapper-max {
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<a-layout>
|
||||
<a-layout class="layout">
|
||||
<div id="snowyHeader" class="snowy-header top-snowy-header xn-pd050">
|
||||
<div id="snowyHeader" class="snowy-header top-snowy-header xn-pd050" v-show="displayLayout">
|
||||
<div class="snowy-header-left xn-pl0">
|
||||
<header id="snowyHeaderLogo" class="snowy-header-logo">
|
||||
<div class="snowy-header-left">
|
||||
|
@ -32,11 +32,13 @@
|
|||
</div>
|
||||
</div>
|
||||
<!-- 手机端情况下的左侧菜单 -->
|
||||
<Side-m v-if="isMobile" />
|
||||
<breadcrumb v-if="!isMobile && breadcrumbOpen" />
|
||||
<Side-m v-if="isMobile" v-show="displayLayout" />
|
||||
<breadcrumb v-if="!isMobile && breadcrumbOpen" v-show="displayLayout" />
|
||||
<!-- 多标签 -->
|
||||
<Tags v-if="!isMobile && layoutTagsOpen" />
|
||||
<a-layout-content class="main-content-wrapper">
|
||||
<Tags v-if="!isMobile && layoutTagsOpen" v-show="displayLayout" />
|
||||
<a-layout-content
|
||||
:class="displayLayout ? 'main-content-wrapper' : 'main-content-wrapper main-content-wrapper-max'"
|
||||
>
|
||||
<div id="admin-ui-main" class="admin-ui-main">
|
||||
<router-view v-slot="{ Component }">
|
||||
<keep-alive :include="kStore.keepLiveRoute">
|
||||
|
@ -57,7 +59,6 @@
|
|||
|
||||
<script setup>
|
||||
import { useRoute } from 'vue-router'
|
||||
const route = useRoute()
|
||||
import UserBar from '@/layout/components/userbar.vue'
|
||||
import Tags from '@/layout/components/tags.vue'
|
||||
import SideM from '@/layout/components/sideM.vue'
|
||||
|
@ -82,8 +83,39 @@
|
|||
kStore: { type: Object }, // 获取的仓库数据
|
||||
footerCopyrightOpen: { type: Boolean } //页脚版权信息
|
||||
})
|
||||
|
||||
const emit = defineEmits(['onSelect', 'switchModule', 'onOpenChange'])
|
||||
const emit = defineEmits(['onSelect', 'switchModule', 'onOpenChange', 'displayLayoutChange'])
|
||||
const displayLayout = ref(true)
|
||||
const route = useRoute()
|
||||
watch(route, () => {
|
||||
nextTick(() => {
|
||||
displayLayout.value = displayLayoutResult()
|
||||
})
|
||||
if (displayLayout.value) {
|
||||
emit('displayLayoutChange')
|
||||
}
|
||||
})
|
||||
onMounted(() => {
|
||||
nextTick(() => {
|
||||
displayLayout.value = displayLayoutResult()
|
||||
})
|
||||
})
|
||||
const displayLayoutResult = () => {
|
||||
// 根据route.meta.keepLive动态管理keepLiveRoute
|
||||
if (route.meta.keepLive === true) {
|
||||
props.kStore.pushKeepLive(route.name)
|
||||
} else {
|
||||
props.kStore.removeKeepLive(route.name)
|
||||
}
|
||||
if (
|
||||
route.meta.displayLayout === undefined ||
|
||||
route.meta.displayLayout === null ||
|
||||
route.meta.displayLayout === 'null'
|
||||
) {
|
||||
return true
|
||||
} else {
|
||||
return route.meta.displayLayout
|
||||
}
|
||||
}
|
||||
const onSelect = (obj) => {
|
||||
emit('onSelect', obj)
|
||||
}
|
||||
|
@ -135,4 +167,7 @@
|
|||
.xn-mg050 {
|
||||
margin: 0px 150px;
|
||||
}
|
||||
.main-content-wrapper-max {
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -42,18 +42,28 @@ export default {
|
|||
validError: 'Please input a valid',
|
||||
accountPassword: 'Account Password',
|
||||
phoneSms: 'Phone SMS',
|
||||
emailLogin: 'Email Login',
|
||||
phonePlaceholder: 'Please input a phone',
|
||||
phoneInputNumberPlaceholder: 'Please input a phone 11-digit',
|
||||
smsCodePlaceholder: 'Please input a SMS code',
|
||||
getSmsCode: 'SMS code',
|
||||
getEmailCode: 'EMAIL CODE',
|
||||
machineValidation: 'Machine Validation',
|
||||
sendingSmsMessage: 'Sending SMS Message',
|
||||
newPwdPlaceholder: 'Please input a new password',
|
||||
backLogin: 'Back Login',
|
||||
restPassword: 'Rest Password',
|
||||
emailPlaceholder: 'Please input a email',
|
||||
emailPlaceholder: 'Please input a correct email',
|
||||
emailCodePlaceholder: 'Please input a Email code',
|
||||
emailValidPlaceholder: 'Please input a email',
|
||||
restPhoneType: 'For phone rest',
|
||||
restEmailType: 'For email rest'
|
||||
restEmailType: 'For email rest',
|
||||
register: 'Register',
|
||||
userRegister: 'User Register',
|
||||
notAccountPleaseRegister: 'Not Account? Register!',
|
||||
haveAccountPleaseLogin: 'Have Account? Go Login!',
|
||||
enterAgainPassword: 'Please re-enter your password',
|
||||
enteredPasswordsDiffer: 'Entered passwords differ'
|
||||
},
|
||||
user: {
|
||||
userStatus: 'User Status',
|
||||
|
|
|
@ -44,9 +44,12 @@ export default {
|
|||
validError: '请输入验证码',
|
||||
accountPassword: '账号密码',
|
||||
phoneSms: '手机号登录',
|
||||
emailLogin: '邮箱号登录',
|
||||
phonePlaceholder: '请输入手机号',
|
||||
phoneInputNumberPlaceholder: '请输入11位手机号',
|
||||
smsCodePlaceholder: '请输入短信验证码',
|
||||
getSmsCode: '获取验证码',
|
||||
getEmailCode: '获取验证码',
|
||||
machineValidation: '机器验证',
|
||||
sendingSmsMessage: '短信发送中',
|
||||
newPwdPlaceholder: '请输入新密码',
|
||||
|
@ -54,8 +57,15 @@ export default {
|
|||
restPassword: '重置密码',
|
||||
emailPlaceholder: '请输入邮箱号',
|
||||
emailCodePlaceholder: '请输入邮件验证码',
|
||||
emailValidPlaceholder: '请输入正确的邮箱号',
|
||||
restPhoneType: '手机号找回',
|
||||
restEmailType: '邮箱找回'
|
||||
restEmailType: '邮箱找回',
|
||||
register: '注册',
|
||||
userRegister: '用户注册',
|
||||
notAccountPleaseRegister: '没有账号?前往注册!',
|
||||
haveAccountPleaseLogin: '已有账号?去登录!',
|
||||
enterAgainPassword: '请再次输入密码',
|
||||
enteredPasswordsDiffer: '两次输入密码不一致'
|
||||
},
|
||||
user: {
|
||||
userStatus: '用户状态',
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
/**
|
||||
* Copyright [2022] [https://www.xiaonuo.vip]
|
||||
* Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
|
||||
* 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
* 2.请不要删除和修改Snowy源码头部的版权声明。
|
||||
* 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
|
||||
* 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
|
||||
* 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
|
||||
* 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
|
||||
*/
|
||||
// 导入扩展路由
|
||||
import clientExpRouter from '@/router/clientExpRouter'
|
||||
import tool from '@/utils/tool'
|
||||
|
||||
const ClientLogin = () => import('@/views/auth/client/login/login.vue')
|
||||
const ClientFindPwd = () => import('@/views/auth/client/findPwd/index.vue')
|
||||
const ClientRegister = () => import('@/views/auth/client/register/index.vue')
|
||||
const ClientFrontIndex = () => import('@/views/front/index.vue')
|
||||
|
||||
// 前台基础路由
|
||||
const routes = [
|
||||
{
|
||||
path: '/front/client/login',
|
||||
component: ClientLogin,
|
||||
meta: {
|
||||
title: '前台登录'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/front/client/findPwd',
|
||||
component: ClientFindPwd,
|
||||
meta: {
|
||||
title: '找回密码'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/front/client/register',
|
||||
component: ClientRegister,
|
||||
meta: {
|
||||
title: '用户注册'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/front/client/index',
|
||||
component: ClientFrontIndex,
|
||||
meta: {
|
||||
title: '个人主页'
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
// 开放路由列表
|
||||
const clientOpenRouter = ['/front/client/login', '/front/client/findPwd', '/front/client/register']
|
||||
|
||||
/**
|
||||
* 验证C端路由访问权限
|
||||
* @param {string} path - 路由路径
|
||||
* @returns {Object} - 返回验证结果,包含是否通过验证和重定向路径
|
||||
*/
|
||||
export const validateClientAccess = (path) => {
|
||||
// 如果不是C端路由,直接返回true
|
||||
if (!path.includes('/front/client/')) {
|
||||
return { valid: true }
|
||||
}
|
||||
|
||||
// 如果是开放路由,直接通过
|
||||
if (clientOpenRouter.includes(path)) {
|
||||
return { valid: true }
|
||||
}
|
||||
|
||||
// 检查是否有客户端token
|
||||
const clientToken = tool.data.get('CLIENT_TOKEN')
|
||||
if (!clientToken) {
|
||||
return {
|
||||
valid: false,
|
||||
redirectPath: '/front/client/login'
|
||||
}
|
||||
}
|
||||
return { valid: true }
|
||||
}
|
||||
|
||||
const exportRoutes = [...routes, ...clientExpRouter]
|
||||
|
||||
export default exportRoutes
|
|
@ -0,0 +1,16 @@
|
|||
/**
|
||||
* Copyright [2022] [https://www.xiaonuo.vip]
|
||||
* Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
|
||||
* 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
* 2.请不要删除和修改Snowy源码头部的版权声明。
|
||||
* 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
|
||||
* 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
|
||||
* 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
|
||||
* 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
|
||||
*/
|
||||
// 前台扩展路由
|
||||
const routes = [
|
||||
// 前台其他的做到这里,但是不建议做很多用户在这里的业务,最好是分两个前端,一个B,一个C;用户的业务单独写个小程序也是比较合理。
|
||||
]
|
||||
|
||||
export default routes
|
|
@ -12,6 +12,7 @@ import { createRouter, createWebHistory } from 'vue-router'
|
|||
import NProgress from 'nprogress'
|
||||
import 'nprogress/nprogress.css'
|
||||
import systemRouter from './systemRouter'
|
||||
import clientBaseRouter, { validateClientAccess } from './clientBaseRouter'
|
||||
import { afterEach, beforeEach } from './scrollBehavior'
|
||||
import whiteListRouters from './whiteList'
|
||||
import userRoutes from '@/config/route'
|
||||
|
@ -35,7 +36,7 @@ const routes_404 = [
|
|||
}
|
||||
]
|
||||
// 系统路由
|
||||
const routes = [...systemRouter, ...whiteListRouters, ...routes_404]
|
||||
const routes = [...systemRouter, ...whiteListRouters, ...clientBaseRouter, ...routes_404]
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
|
@ -70,6 +71,11 @@ router.beforeEach(async (to, from, next) => {
|
|||
// NProgress.done()
|
||||
return false
|
||||
}
|
||||
// C端检验逻辑
|
||||
if (to.path.includes('/front/client/')) {
|
||||
return validateClientAccess(to.path).valid ? next() : next({ path: validateClientAccess(to.path).redirectPath })
|
||||
}
|
||||
|
||||
if (!isGetRouter.value) {
|
||||
// 初始化菜单加载,代码位置不能变动
|
||||
const menuStore = useMenuStore()
|
||||
|
|
|
@ -14,8 +14,9 @@ import routerUtil from '@/utils/routerUtil'
|
|||
|
||||
const Layout = () => import('@/layout/index.vue')
|
||||
const Login = () => import('@/views/auth/login/login.vue')
|
||||
const Findpwd = () => import('@/views/auth/findPwd/index.vue')
|
||||
const FindPwd = () => import('@/views/auth/findPwd/index.vue')
|
||||
const Callback = () => import('@/views/auth/login/callback.vue')
|
||||
const Register = () => import('@/views/auth/login/register.vue')
|
||||
|
||||
// 系统路由
|
||||
const routes = [
|
||||
|
@ -33,9 +34,16 @@ const routes = [
|
|||
title: '登录'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/register',
|
||||
component: Register,
|
||||
meta: {
|
||||
title: '注册'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/findpwd',
|
||||
component: Findpwd,
|
||||
component: FindPwd,
|
||||
meta: {
|
||||
title: '找回密码'
|
||||
}
|
||||
|
|
|
@ -12,6 +12,9 @@ const constRouters = [
|
|||
{
|
||||
path: '/findpwd'
|
||||
},
|
||||
{
|
||||
path: '/register'
|
||||
},
|
||||
{
|
||||
path: '/callback'
|
||||
},
|
||||
|
|
|
@ -96,14 +96,14 @@ tool.dictTypeData = (dictValue, value) => {
|
|||
}
|
||||
const tree = dictTypeTree.find((item) => item.dictValue === dictValue)
|
||||
if (!tree) {
|
||||
return '无此字典'
|
||||
return ''
|
||||
}
|
||||
const children = tree.children
|
||||
if (!tree.children) {
|
||||
return '无此字典'
|
||||
return ''
|
||||
}
|
||||
const dict = children.find((item) => item.dictValue === value)
|
||||
return dict ? dict.dictLabel : '无此字典项'
|
||||
return dict ? dict.dictLabel : ''
|
||||
}
|
||||
|
||||
// 获取某个code下字典的列表,多用于字典下拉框
|
||||
|
|
|
@ -0,0 +1,175 @@
|
|||
<template>
|
||||
<a-form ref="emailResetFormRef" :model="emailFormData" :rules="formRules">
|
||||
<a-form-item name="email">
|
||||
<a-input v-model:value="emailFormData.email" placeholder="请输入邮箱号" size="large">
|
||||
<template #prefix>
|
||||
<mail-outlined class="xn-color-00025" />
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item name="emailValidCode">
|
||||
<a-row :gutter="8">
|
||||
<a-col :span="16">
|
||||
<a-input v-model:value="emailFormData.emailValidCode" placeholder="请输入邮件验证码" size="large">
|
||||
<template #prefix>
|
||||
<mail-outlined class="xn-color-00025" />
|
||||
</template>
|
||||
</a-input>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-button size="large" class="xn-wd" @click="getEmailValidCode" :disabled="state.smsSendBtn">
|
||||
{{ (!state.smsSendBtn && '获取验证码') || state.time + ' s' }}
|
||||
</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item name="newPassword">
|
||||
<a-input-password v-model:value="emailFormData.newPassword" placeholder="请输入新密码" size="large">
|
||||
<template #prefix>
|
||||
<LockOutlined class="xn-color-00025" />
|
||||
</template>
|
||||
</a-input-password>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item>
|
||||
<a-row :gutter="8">
|
||||
<a-col :span="8">
|
||||
<a-button class="xn-wd" round size="large" href="/front/client/login">返回登录</a-button>
|
||||
</a-col>
|
||||
<a-col :span="16">
|
||||
<a-button type="primary" class="xn-wd" :loading="isFind" round size="large" @click="submitReset">
|
||||
重置密码
|
||||
</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<a-modal v-model:open="visible" :width="400" title="机器验证" @cancel="handleCancel" @ok="handleOk">
|
||||
<a-form ref="emailLoginFormModalRef" :model="emailFormModalData" :rules="formModalRules">
|
||||
<a-form-item name="validCode">
|
||||
<a-row :gutter="8">
|
||||
<a-col :span="16">
|
||||
<a-input v-model:value="emailFormModalData.validCode" placeholder="请输入验证码" size="large">
|
||||
<template #prefix>
|
||||
<verified-outlined class="xn-color-00025" />
|
||||
</template>
|
||||
</a-input>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<img :src="validCodeBase64" class="xn-findform-line" @click="getPhonePicCaptcha" />
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script setup name="emailFindForm">
|
||||
import { message } from 'ant-design-vue'
|
||||
import router from '@/router'
|
||||
import { required, rules } from '@/utils/formRules'
|
||||
import clientUserCenterApi from '@/api/client/clientUserCenterApi'
|
||||
import smCrypto from '@/utils/smCrypto'
|
||||
const emailResetFormRef = ref()
|
||||
const emailFormData = ref({})
|
||||
const isFind = ref(false)
|
||||
let state = ref({
|
||||
time: 60,
|
||||
smsSendBtn: false
|
||||
})
|
||||
let formRules = ref({})
|
||||
const emailValidCodeReqNo = ref('')
|
||||
|
||||
// 点击获取邮箱验证码
|
||||
const getEmailValidCode = () => {
|
||||
formRules.value.email = [required('请输入邮箱号'), rules.email]
|
||||
delete formRules.value.emailValidCode
|
||||
delete formRules.value.newPassword
|
||||
emailResetFormRef.value.validate().then(() => {
|
||||
// 显示弹框
|
||||
visible.value = true
|
||||
// 获取内部图片验证码
|
||||
getPhonePicCaptcha()
|
||||
})
|
||||
}
|
||||
// 点击找回按钮
|
||||
const submitReset = () => {
|
||||
formRules.value.email = [required('请输入邮箱号'), rules.email]
|
||||
formRules.value.emailValidCode = [required('请输入邮箱验证码'), rules.number]
|
||||
formRules.value.newPassword = [required('请输入新密码')]
|
||||
|
||||
emailResetFormRef.value
|
||||
.validate()
|
||||
.then(() => {
|
||||
emailFormData.value.validCode = emailFormData.value.emailValidCode
|
||||
emailFormData.value.validCodeReqNo = emailValidCodeReqNo.value
|
||||
emailFormData.value.newPassword = smCrypto.doSm2Encrypt(emailFormData.value.newPassword)
|
||||
isFind.value = true
|
||||
clientUserCenterApi
|
||||
.clientUserFindPasswordByEmail(emailFormData.value)
|
||||
.then(() => {
|
||||
router.replace({
|
||||
path: '/'
|
||||
})
|
||||
message.success('找回成功')
|
||||
})
|
||||
.finally(() => {
|
||||
isFind.value = false
|
||||
})
|
||||
})
|
||||
.catch(() => {})
|
||||
}
|
||||
|
||||
// 弹框的
|
||||
const visible = ref(false)
|
||||
const emailLoginFormModalRef = ref()
|
||||
const emailFormModalData = ref({})
|
||||
const validCodeBase64 = ref('')
|
||||
const formModalRules = {
|
||||
validCode: [required(), rules.lettersNum]
|
||||
}
|
||||
const getPhonePicCaptcha = () => {
|
||||
clientUserCenterApi.clientUserGetPicCaptcha().then((data) => {
|
||||
validCodeBase64.value = data.validCodeBase64
|
||||
emailFormModalData.value.validCodeReqNo = data.validCodeReqNo
|
||||
})
|
||||
}
|
||||
const handleCancel = () => {
|
||||
visible.value = false
|
||||
}
|
||||
const handleOk = () => {
|
||||
// 获取到里面的验证码,并发送邮箱
|
||||
emailLoginFormModalRef.value.validate().then(() => {
|
||||
visible.value = false
|
||||
// 发送邮箱,首先拿到刚刚输入的邮箱
|
||||
emailFormModalData.value.email = emailFormData.value.email
|
||||
// 禁用发送按钮,并设置为倒计时
|
||||
state.value.smsSendBtn = true
|
||||
const interval = window.setInterval(() => {
|
||||
if (state.value.time-- <= 0) {
|
||||
state.value.time = 60
|
||||
state.value.smsSendBtn = false
|
||||
window.clearInterval(interval)
|
||||
}
|
||||
}, 1000)
|
||||
const hide = message.loading('验证码发送中..', 0)
|
||||
|
||||
clientUserCenterApi
|
||||
.clientUserFindPasswordGetEmailValidCode(emailFormModalData.value)
|
||||
.then((data) => {
|
||||
emailValidCodeReqNo.value = data
|
||||
visible.value = false
|
||||
setTimeout(hide, 500)
|
||||
})
|
||||
.catch(() => {
|
||||
setTimeout(hide, 100)
|
||||
clearInterval(interval)
|
||||
state.value.smsSendBtn = false
|
||||
})
|
||||
.finally(() => {
|
||||
emailFormModalData.value.validCode = ''
|
||||
})
|
||||
})
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,37 @@
|
|||
<template>
|
||||
<div class="login-wrapper">
|
||||
<div class="login_main">
|
||||
<div class="login-form">
|
||||
<a-card>
|
||||
<div class="login-header">
|
||||
<h2>忘记密码</h2>
|
||||
</div>
|
||||
<a-tabs v-model:activeKey="activeKey">
|
||||
<a-tab-pane key="userPhone" tab="手机号找回">
|
||||
<phone-find-form />
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="userEmail" tab="邮箱找回" force-render>
|
||||
<email-find-form />
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</a-card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import PhoneFindForm from './phoneFindForm.vue'
|
||||
import EmailFindForm from './emailFindForm.vue'
|
||||
import { globalStore } from '@/store'
|
||||
|
||||
const store = globalStore()
|
||||
const activeKey = ref('userPhone')
|
||||
const sysBaseConfig = computed(() => {
|
||||
return store.sysBaseConfig
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import '../login/login';
|
||||
</style>
|
|
@ -0,0 +1,175 @@
|
|||
<template>
|
||||
<a-form ref="phoneLoginFormRef" :model="phoneFormData" :rules="formRules">
|
||||
<a-form-item name="phone">
|
||||
<a-input v-model:value="phoneFormData.phone" placeholder="请输入手机号" size="large">
|
||||
<template #prefix>
|
||||
<mobile-outlined class="xn-color-00025" />
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item name="phoneValidCode">
|
||||
<a-row :gutter="8">
|
||||
<a-col :span="16">
|
||||
<a-input v-model:value="phoneFormData.phoneValidCode" placeholder="请输入短信验证码" size="large">
|
||||
<template #prefix>
|
||||
<mail-outlined class="xn-color-00025" />
|
||||
</template>
|
||||
</a-input>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-button size="large" class="xn-wd" @click="getPhoneValidCode" :disabled="state.smsSendBtn">{{
|
||||
(!state.smsSendBtn && '获取验证码') || state.time + ' s'
|
||||
}}</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item name="newPassword">
|
||||
<a-input-password v-model:value="phoneFormData.newPassword" placeholder="请输入新密码" size="large">
|
||||
<template #prefix>
|
||||
<LockOutlined class="xn-color-00025" />
|
||||
</template>
|
||||
</a-input-password>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item>
|
||||
<a-row :gutter="8">
|
||||
<a-col :span="8">
|
||||
<a-button class="xn-wd" round size="large" href="/front/client/login">返回登录</a-button>
|
||||
</a-col>
|
||||
<a-col :span="16">
|
||||
<a-button type="primary" class="xn-wd" :loading="isFind" round size="large" @click="submitReset">
|
||||
重置密码
|
||||
</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<a-modal v-model:open="visible" :width="400" title="机器验证" @cancel="handleCancel" @ok="handleOk">
|
||||
<a-form ref="phoneLoginFormModalRef" :model="phoneFormModalData" :rules="formModalRules">
|
||||
<a-form-item name="validCode">
|
||||
<a-row :gutter="8">
|
||||
<a-col :span="17">
|
||||
<a-input v-model:value="phoneFormModalData.validCode" placeholder="机器验证" size="large">
|
||||
<template #prefix>
|
||||
<verified-outlined class="xn-color-00025" />
|
||||
</template>
|
||||
</a-input>
|
||||
</a-col>
|
||||
<a-col :span="7">
|
||||
<img :src="validCodeBase64" class="xn-findform-line" @click="getPhonePicCaptcha" />
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script setup name="phoneFindForm">
|
||||
import { message } from 'ant-design-vue'
|
||||
import router from '@/router'
|
||||
import { required, rules } from '@/utils/formRules'
|
||||
import clientUserCenterApi from '@/api/client/clientUserCenterApi'
|
||||
import smCrypto from '@/utils/smCrypto'
|
||||
const phoneLoginFormRef = ref()
|
||||
const phoneFormData = ref({})
|
||||
const isFind = ref(false)
|
||||
let state = ref({
|
||||
time: 60,
|
||||
smsSendBtn: false
|
||||
})
|
||||
let formRules = ref({})
|
||||
const phoneValidCodeReqNo = ref('')
|
||||
|
||||
// 点击获取短信验证码
|
||||
const getPhoneValidCode = () => {
|
||||
formRules.value.phone = [required('请输入手机号'), rules.phone]
|
||||
delete formRules.value.phoneValidCode
|
||||
delete formRules.value.newPassword
|
||||
phoneLoginFormRef.value.validate().then(() => {
|
||||
// 显示弹框
|
||||
visible.value = true
|
||||
// 获取内部图片验证码
|
||||
getPhonePicCaptcha()
|
||||
})
|
||||
}
|
||||
// 点击找回按钮
|
||||
const submitReset = () => {
|
||||
formRules.value.phone = [required('请输入手机号'), rules.phone]
|
||||
formRules.value.phoneValidCode = [required('请输入验证码'), rules.number]
|
||||
formRules.value.newPassword = [required('请输入新密码')]
|
||||
|
||||
phoneLoginFormRef.value
|
||||
.validate()
|
||||
.then(() => {
|
||||
phoneFormData.value.validCode = phoneFormData.value.phoneValidCode
|
||||
phoneFormData.value.validCodeReqNo = phoneValidCodeReqNo.value
|
||||
phoneFormData.value.newPassword = smCrypto.doSm2Encrypt(phoneFormData.value.newPassword)
|
||||
isFind.value = true
|
||||
clientUserCenterApi
|
||||
.clientUserFindPasswordByPhone(phoneFormData.value)
|
||||
.then(() => {
|
||||
router.replace({
|
||||
path: '/'
|
||||
})
|
||||
message.success('找回成功')
|
||||
})
|
||||
.finally(() => {
|
||||
isFind.value = false
|
||||
})
|
||||
})
|
||||
.catch(() => {})
|
||||
}
|
||||
|
||||
// 弹框的
|
||||
const visible = ref(false)
|
||||
const phoneLoginFormModalRef = ref()
|
||||
const phoneFormModalData = ref({})
|
||||
const validCodeBase64 = ref('')
|
||||
const formModalRules = {
|
||||
validCode: [required(), rules.lettersNum]
|
||||
}
|
||||
const getPhonePicCaptcha = () => {
|
||||
clientUserCenterApi.clientUserGetPicCaptcha().then((data) => {
|
||||
validCodeBase64.value = data.validCodeBase64
|
||||
phoneFormModalData.value.validCodeReqNo = data.validCodeReqNo
|
||||
})
|
||||
}
|
||||
const handleCancel = () => {
|
||||
visible.value = false
|
||||
}
|
||||
const handleOk = () => {
|
||||
// 获取到里面的验证码,并发送短信
|
||||
phoneLoginFormModalRef.value.validate().then(() => {
|
||||
visible.value = false
|
||||
// 发送短信,首先拿到刚刚输入的手机号
|
||||
phoneFormModalData.value.phone = phoneFormData.value.phone
|
||||
// 禁用发送按钮,并设置为倒计时
|
||||
state.value.smsSendBtn = true
|
||||
const interval = window.setInterval(() => {
|
||||
if (state.value.time-- <= 0) {
|
||||
state.value.time = 60
|
||||
state.value.smsSendBtn = false
|
||||
window.clearInterval(interval)
|
||||
}
|
||||
}, 1000)
|
||||
const hide = message.loading('验证码发送中..', 0)
|
||||
|
||||
clientUserCenterApi
|
||||
.clientUserFindPasswordGetPhoneValidCode(phoneFormModalData.value)
|
||||
.then((data) => {
|
||||
phoneValidCodeReqNo.value = data
|
||||
visible.value = false
|
||||
setTimeout(hide, 500)
|
||||
})
|
||||
.catch(() => {
|
||||
setTimeout(hide, 100)
|
||||
clearInterval(interval)
|
||||
state.value.smsSendBtn = false
|
||||
})
|
||||
.finally(() => {
|
||||
phoneFormModalData.value.validCode = ''
|
||||
})
|
||||
})
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,152 @@
|
|||
<template>
|
||||
<a-form ref="emailLoginFormRef" :model="emailFormData" :rules="formRules">
|
||||
<a-form-item name="email">
|
||||
<a-input v-model:value="emailFormData.email" placeholder="请输入邮箱号" size="large">
|
||||
<template #prefix>
|
||||
<mail-outlined class="text-black text-opacity-25" />
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item name="emailValidCode">
|
||||
<a-row :gutter="8">
|
||||
<a-col :span="16">
|
||||
<a-input v-model:value="emailFormData.emailValidCode" placeholder="请输入验证码" size="large">
|
||||
<template #prefix>
|
||||
<mail-outlined class="text-black text-opacity-25" />
|
||||
</template>
|
||||
</a-input>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-button size="large" class="xn-wd" @click="getEmailValidCode" :disabled="state.smsSendBtn">
|
||||
{{ (!state.smsSendBtn && '获取验证码') || state.time + ' s' }}
|
||||
</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button type="primary" class="xn-wd" :loading="loading" round size="large" @click="submitLogin">
|
||||
登录
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<a-modal v-model:open="visible" :width="400" title="机器验证" @cancel="handleCancel" @ok="handleOk">
|
||||
<a-form ref="emailLoginFormModalRef" :model="emailFormModalData" :rules="formModalRules">
|
||||
<a-form-item name="validCode">
|
||||
<a-row :gutter="8">
|
||||
<a-col :span="17">
|
||||
<a-input v-model:value="emailFormModalData.validCode" placeholder="请输入验证码" size="large">
|
||||
<template #prefix>
|
||||
<verified-outlined class="text-black text-opacity-25" />
|
||||
</template>
|
||||
</a-input>
|
||||
</a-col>
|
||||
<a-col :span="7">
|
||||
<img :src="validCodeBase64" class="xn-findform-line" @click="getEmailPicCaptcha" />
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script setup name="emailLoginForm">
|
||||
import { message } from 'ant-design-vue'
|
||||
import { required, rules } from '@/utils/formRules'
|
||||
import clientLoginApi from '@/api/auth/client/clientLoginApi'
|
||||
import { afterLogin } from './util'
|
||||
|
||||
const emailLoginFormRef = ref()
|
||||
const emailFormData = ref({})
|
||||
const loading = ref(false)
|
||||
let state = ref({
|
||||
time: 60,
|
||||
smsSendBtn: false
|
||||
})
|
||||
let formRules = ref({})
|
||||
const emailValidCodeReqNo = ref('')
|
||||
|
||||
// 点击获取邮箱验证码
|
||||
const getEmailValidCode = () => {
|
||||
formRules.value.email = [required('请输入正确的邮箱号'), rules.email]
|
||||
delete formRules.value.emailValidCode
|
||||
emailLoginFormRef.value.validate().then(() => {
|
||||
// 显示弹框
|
||||
visible.value = true
|
||||
// 获取内部图片验证码
|
||||
getEmailPicCaptcha()
|
||||
})
|
||||
}
|
||||
|
||||
// 点击登录按钮
|
||||
const submitLogin = async () => {
|
||||
formRules.value.email = [required('请输入正确的邮箱号'), rules.email]
|
||||
formRules.value.emailValidCode = [required('请输入邮箱验证码'), rules.number]
|
||||
|
||||
const validate = await emailLoginFormRef.value.validate().catch(() => {})
|
||||
if (!validate) return false
|
||||
|
||||
emailFormData.value.validCode = emailFormData.value.emailValidCode
|
||||
// delete emailFormData.value.emailValidCode
|
||||
emailFormData.value.validCodeReqNo = emailValidCodeReqNo.value
|
||||
loading.value = true
|
||||
clientLoginApi
|
||||
.clientLoginByEmail(emailFormData.value)
|
||||
.then((token) => {
|
||||
afterLogin(token)
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
// 弹框的
|
||||
const visible = ref(false)
|
||||
const emailLoginFormModalRef = ref()
|
||||
const emailFormModalData = ref({})
|
||||
const validCodeBase64 = ref('')
|
||||
const validCodeReqNo = ref('')
|
||||
const formModalRules = {
|
||||
validCode: [required('请输入图形验证码'), rules.lettersNum]
|
||||
}
|
||||
const getEmailPicCaptcha = () => {
|
||||
clientLoginApi.clientGetPicCaptcha().then((data) => {
|
||||
validCodeBase64.value = data.validCodeBase64
|
||||
emailFormModalData.value.validCodeReqNo = data.validCodeReqNo
|
||||
})
|
||||
}
|
||||
const handleCancel = () => {
|
||||
visible.value = false
|
||||
}
|
||||
const handleOk = () => {
|
||||
// 获取到里面的验证码,并发送邮箱
|
||||
emailLoginFormModalRef.value.validate().then(() => {
|
||||
visible.value = false
|
||||
// 发送邮箱,首先拿到刚刚输入的邮箱号
|
||||
emailFormModalData.value.email = emailFormData.value.email
|
||||
// 禁用发送按钮,并设置为倒计时
|
||||
state.value.smsSendBtn = true
|
||||
const interval = window.setInterval(() => {
|
||||
if (state.value.time-- <= 0) {
|
||||
state.value.time = 60
|
||||
state.value.smsSendBtn = false
|
||||
window.clearInterval(interval)
|
||||
}
|
||||
}, 1000)
|
||||
const hide = message.loading('验证码发送中..', 0)
|
||||
|
||||
clientLoginApi
|
||||
.clientGetEmailValidCode(emailFormModalData.value)
|
||||
.then((data) => {
|
||||
emailValidCodeReqNo.value = data
|
||||
visible.value = false
|
||||
setTimeout(hide, 500)
|
||||
emailFormModalData.value.validCode = ''
|
||||
})
|
||||
.catch(() => {
|
||||
setTimeout(hide, 100)
|
||||
clearInterval(interval)
|
||||
state.value.smsSendBtn = false
|
||||
})
|
||||
})
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,56 @@
|
|||
.login-wrapper {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
background: linear-gradient(135deg, #1677ff 0%, #a64fff 100%);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
.login_main {
|
||||
width: 100%;
|
||||
max-width: 450px;
|
||||
margin-top: 110px;
|
||||
}
|
||||
.login-form {
|
||||
width: 100%;
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
|
||||
overflow: hidden;
|
||||
}
|
||||
.login-header {
|
||||
text-align: center;
|
||||
}
|
||||
.login-header h2 {
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
color: #1a1a1a;
|
||||
margin: 0;
|
||||
}
|
||||
.xn-color-0d84ff {
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
.xn-color-0d84ff:hover {
|
||||
color: var(--primary-1);
|
||||
}
|
||||
:deep(.ant-tabs-tab) {
|
||||
font-size: 16px;
|
||||
padding: 12px 0;
|
||||
}
|
||||
:deep(.ant-input-affix-wrapper) {
|
||||
border-radius: 6px;
|
||||
height: 45px;
|
||||
}
|
||||
:deep(.ant-btn) {
|
||||
height: 45px;
|
||||
border-radius: 6px;
|
||||
font-weight: 500;
|
||||
}
|
||||
:deep(.ant-card) {
|
||||
background: transparent;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
:deep(.ant-card-body) {
|
||||
padding: 24px 32px;
|
||||
}
|
|
@ -0,0 +1,290 @@
|
|||
<template>
|
||||
<div class="login-wrapper">
|
||||
<div class="login_main">
|
||||
<div class="login-form">
|
||||
<a-card>
|
||||
<div class="login-header">
|
||||
<h2>登录</h2>
|
||||
</div>
|
||||
<a-tabs v-model:activeKey="activeKey">
|
||||
<a-tab-pane key="userAccount" tab="账号密码">
|
||||
<a-form ref="loginForm" :model="ruleForm" :rules="rules">
|
||||
<div v-if="tenSelectShow">
|
||||
<a-form-item name="tenCode" v-if="tenOptions.length > 1 || !ruleForm.tenCode">
|
||||
<a-select
|
||||
v-model:value="ruleForm.tenCode"
|
||||
size="large"
|
||||
placeholder="请选择租户"
|
||||
:options="tenOptions"
|
||||
@change="tenCodeChange"
|
||||
/>
|
||||
</a-form-item>
|
||||
</div>
|
||||
<a-form-item name="account">
|
||||
<a-input v-model:value="ruleForm.account" placeholder="请输入账号" size="large" @keyup.enter="login">
|
||||
<template #prefix>
|
||||
<UserOutlined class="login-icon-gray" />
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item name="password">
|
||||
<a-input-password
|
||||
v-model:value="ruleForm.password"
|
||||
placeholder="请输入密码"
|
||||
size="large"
|
||||
autocomplete="off"
|
||||
@keyup.enter="login"
|
||||
>
|
||||
<template #prefix>
|
||||
<LockOutlined class="login-icon-gray" />
|
||||
</template>
|
||||
</a-input-password>
|
||||
</a-form-item>
|
||||
<a-form-item name="validCode" v-if="captchaOpen === 'true'">
|
||||
<a-row :gutter="8">
|
||||
<a-col :span="17">
|
||||
<a-input
|
||||
v-model:value="ruleForm.validCode"
|
||||
placeholder="请输入验证码"
|
||||
size="large"
|
||||
@keyup.enter="login"
|
||||
>
|
||||
<template #prefix>
|
||||
<verified-outlined class="login-icon-gray" />
|
||||
</template>
|
||||
</a-input>
|
||||
</a-col>
|
||||
<a-col :span="7">
|
||||
<img :src="validCodeBase64" class="login-validCode-img" @click="loginCaptcha" />
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item>
|
||||
<div style="display: flex; justify-content: space-between">
|
||||
<a href="/front/client/findPwd" class="xn-color-0d84ff">忘记密码?</a>
|
||||
<a href="/front/client/register" class="xn-color-0d84ff" v-if="registerOpen === 'true'">
|
||||
没有账号?前往注册
|
||||
</a>
|
||||
</div>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button
|
||||
type="primary"
|
||||
class="w-full"
|
||||
:loading="loading"
|
||||
round
|
||||
size="large"
|
||||
@click="login"
|
||||
:disabled="loginButtonDisable"
|
||||
>
|
||||
登录
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="userSms" tab="手机号登录" force-render v-if="phoneLogin === 'true'">
|
||||
<phone-login-form />
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="userEmail" tab="邮箱号登录" force-render v-if="emailLogin === 'true'">
|
||||
<email-login-form />
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
<div v-if="configData.FRONT_BACK_LOGIN_URL_SHOW">
|
||||
<a href="/login" class="xn-color-0d84ff">后台登录</a>
|
||||
</div>
|
||||
</a-card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import clientLoginApi from '@/api/auth/client/clientLoginApi'
|
||||
import loginTenApi from '@/api/auth/loginTenApi'
|
||||
import smCrypto from '@/utils/smCrypto'
|
||||
import { required } from '@/utils/formRules'
|
||||
import { afterLogin } from './util'
|
||||
import configData from '@/config'
|
||||
import configApi from '@/api/dev/configApi'
|
||||
import tool from '@/utils/tool'
|
||||
import { globalStore } from '@/store'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { isEmpty } from 'lodash-es'
|
||||
const PhoneLoginForm = defineAsyncComponent(() => import('./phoneLoginForm.vue'))
|
||||
const EmailLoginForm = defineAsyncComponent(() => import('./emailLoginForm.vue'))
|
||||
const route = useRoute()
|
||||
const activeKey = ref('userAccount')
|
||||
const captchaOpen = ref(configData.SYS_BASE_CONFIG.SNOWY_SYS_DEFAULT_CAPTCHA_OPEN_FLAG_FOR_C)
|
||||
const registerOpen = ref('false')
|
||||
const phoneLogin = ref('false')
|
||||
const emailLogin = ref('false')
|
||||
const validCodeBase64 = ref('')
|
||||
const loading = ref(false)
|
||||
const tenSelectShow = ref(false)
|
||||
const tenOptions = ref([])
|
||||
const loginButtonDisable = ref(false)
|
||||
|
||||
const ruleForm = reactive({
|
||||
validCode: '',
|
||||
validCodeReqNo: '',
|
||||
autologin: false,
|
||||
tenCode: ''
|
||||
})
|
||||
// 如果是开发环境,填充用户名密码
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
ruleForm.account = ''
|
||||
ruleForm.password = ''
|
||||
}
|
||||
|
||||
const rules = reactive({
|
||||
account: [required('请输入账号', 'blur')],
|
||||
password: [required('请输入密码', 'blur')],
|
||||
tenCode: [required('请选择租户', 'blur')]
|
||||
})
|
||||
const config = ref({
|
||||
theme: tool.data.get('APP_THEME') || 'default'
|
||||
})
|
||||
|
||||
const store = globalStore()
|
||||
|
||||
const setSysBaseConfig = store.setSysBaseConfig
|
||||
|
||||
const sysBaseConfig = computed(() => {
|
||||
return store.sysBaseConfig
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
// 获得租户code编码
|
||||
if (!isEmpty(route.query.tenCode)) {
|
||||
ruleForm.tenCode = route.query.tenCode
|
||||
}
|
||||
loginButtonDisable.value = true
|
||||
tool.data.set('SNOWY_TEN_CODE', '')
|
||||
// 获得租户列表
|
||||
loginTenApi
|
||||
.getTenSelector()
|
||||
.then((data) => {
|
||||
if (isEmpty(data)) {
|
||||
tool.data.remove('SNOWY_TEN_CODE')
|
||||
} else {
|
||||
tenOptions.value = data.map((m) => {
|
||||
return {
|
||||
label: m.name,
|
||||
value: m.code
|
||||
}
|
||||
})
|
||||
// 如果为空,默认选中里面的第一个,如果有那么就按指定的走
|
||||
if (isEmpty(ruleForm.tenCode)) {
|
||||
ruleForm.tenCode = tenOptions.value[0].value
|
||||
tool.data.set('SNOWY_TEN_CODE', ruleForm.tenCode)
|
||||
tenSelectShow.value = true
|
||||
} else {
|
||||
// 我们来验证这个code是否是有效的
|
||||
const tenObj = tenOptions.value.find((f) => f.value === ruleForm.tenCode)
|
||||
// 如果界面上整了个假的code
|
||||
if (isEmpty(tenObj)) {
|
||||
// 将其清理掉就行
|
||||
ruleForm.tenCode = tenOptions.value[0].value
|
||||
tool.data.set('SNOWY_TEN_CODE', ruleForm.tenCode)
|
||||
tenSelectShow.value = true
|
||||
} else {
|
||||
// 如果验证成功是有的,就加进去
|
||||
tool.data.set('SNOWY_TEN_CODE', ruleForm.tenCode)
|
||||
}
|
||||
}
|
||||
}
|
||||
// 再去查配置
|
||||
getSysConfig()
|
||||
})
|
||||
.catch(() => {
|
||||
// 报错就清理掉
|
||||
tool.data.set('SNOWY_TEN_CODE', '')
|
||||
})
|
||||
})
|
||||
// 查询这个租户的配置,租户code已经被放domain里了
|
||||
const getSysConfig = () => {
|
||||
let formData = ref(configData.SYS_BASE_CONFIG)
|
||||
configApi
|
||||
.configSysBaseList()
|
||||
.then((data) => {
|
||||
loginButtonDisable.value = false
|
||||
if (data) {
|
||||
data.forEach((item) => {
|
||||
formData.value[item.configKey] = item.configValue
|
||||
})
|
||||
captchaOpen.value = formData.value.SNOWY_SYS_DEFAULT_CAPTCHA_OPEN_FLAG_FOR_C
|
||||
registerOpen.value = formData.value.SNOWY_SYS_DEFAULT_ALLOW_REGISTER_FLAG_FOR_C
|
||||
phoneLogin.value = formData.value.SNOWY_SYS_DEFAULT_ALLOW_PHONE_LOGIN_FLAG_FOR_C
|
||||
emailLogin.value = formData.value.SNOWY_SYS_DEFAULT_ALLOW_EMAIL_LOGIN_FLAG_FOR_C
|
||||
tool.data.set('SNOWY_SYS_BASE_CONFIG', formData.value)
|
||||
setSysBaseConfig(formData.value)
|
||||
refreshSwitch()
|
||||
}
|
||||
})
|
||||
.catch(() => {})
|
||||
}
|
||||
// 主题
|
||||
watch(
|
||||
() => config.value.theme,
|
||||
(newValue) => {
|
||||
document.body.setAttribute('data-theme', newValue)
|
||||
}
|
||||
)
|
||||
// 通过开关加载内容
|
||||
const refreshSwitch = () => {
|
||||
// 判断是否开启验证码
|
||||
if (captchaOpen.value === 'true') {
|
||||
// 加载验证码
|
||||
loginCaptcha()
|
||||
// 加入校验
|
||||
rules.validCode = [required('请输入验证码', 'blur')]
|
||||
}
|
||||
}
|
||||
// 获取验证码
|
||||
const loginCaptcha = () => {
|
||||
clientLoginApi.clientGetPicCaptcha().then((data) => {
|
||||
validCodeBase64.value = data.validCodeBase64
|
||||
ruleForm.validCodeReqNo = data.validCodeReqNo
|
||||
// 如果有输入的将其清空
|
||||
ruleForm.validCode = undefined
|
||||
})
|
||||
}
|
||||
//登陆
|
||||
const loginForm = ref()
|
||||
const login = async () => {
|
||||
loginForm.value
|
||||
.validate()
|
||||
.then(async () => {
|
||||
loading.value = true
|
||||
const loginData = {
|
||||
account: ruleForm.account,
|
||||
// 密码进行SM2加密,传输过程中看到的只有密文,后端存储使用hash
|
||||
password: smCrypto.doSm2Encrypt(ruleForm.password),
|
||||
validCode: ruleForm.validCode,
|
||||
validCodeReqNo: ruleForm.validCodeReqNo
|
||||
}
|
||||
// 获取token
|
||||
try {
|
||||
const loginToken = await clientLoginApi.clientLogin(loginData)
|
||||
await afterLogin(loginToken)
|
||||
} catch (err) {
|
||||
if (captchaOpen.value === 'true') {
|
||||
loginCaptcha()
|
||||
}
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
// 租户选择事件
|
||||
const tenCodeChange = (code) => {
|
||||
// 设置新的
|
||||
tool.data.set('SNOWY_TEN_CODE', code)
|
||||
ruleForm.tenCode = code
|
||||
getSysConfig()
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
@import 'login';
|
||||
</style>
|
|
@ -0,0 +1,152 @@
|
|||
<template>
|
||||
<a-form ref="phoneLoginFormRef" :model="phoneFormData" :rules="formRules">
|
||||
<a-form-item name="phone">
|
||||
<a-input v-model:value="phoneFormData.phone" placeholder="请输入手机号" size="large">
|
||||
<template #prefix>
|
||||
<mobile-outlined class="text-black text-opacity-25" />
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item name="phoneValidCode">
|
||||
<a-row :gutter="8">
|
||||
<a-col :span="16">
|
||||
<a-input v-model:value="phoneFormData.phoneValidCode" placeholder="请输入验证码" size="large">
|
||||
<template #prefix>
|
||||
<mail-outlined class="text-black text-opacity-25" />
|
||||
</template>
|
||||
</a-input>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-button size="large" class="xn-wd" @click="getPhoneValidCode" :disabled="state.smsSendBtn">
|
||||
{{ (!state.smsSendBtn && '获取验证码') || state.time + ' s' }}
|
||||
</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button type="primary" class="xn-wd" :loading="loading" round size="large" @click="submitLogin">
|
||||
登录
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<a-modal v-model:open="visible" :width="400" title="机器验证" @cancel="handleCancel" @ok="handleOk">
|
||||
<a-form ref="phoneLoginFormModalRef" :model="phoneFormModalData" :rules="formModalRules">
|
||||
<a-form-item name="validCode">
|
||||
<a-row :gutter="8">
|
||||
<a-col :span="17">
|
||||
<a-input v-model:value="phoneFormModalData.validCode" placeholder="请输入验证码" size="large">
|
||||
<template #prefix>
|
||||
<verified-outlined class="text-black text-opacity-25" />
|
||||
</template>
|
||||
</a-input>
|
||||
</a-col>
|
||||
<a-col :span="7">
|
||||
<img :src="validCodeBase64" class="xn-findform-line" @click="getPhonePicCaptcha" />
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script setup name="smsLoginForm">
|
||||
import { message } from 'ant-design-vue'
|
||||
import { required, rules } from '@/utils/formRules'
|
||||
import clientLoginApi from '@/api/auth/client/clientLoginApi'
|
||||
import { afterLogin } from './util'
|
||||
|
||||
const phoneLoginFormRef = ref()
|
||||
const phoneFormData = ref({})
|
||||
const loading = ref(false)
|
||||
let state = ref({
|
||||
time: 60,
|
||||
smsSendBtn: false
|
||||
})
|
||||
const formRules = ref({})
|
||||
const phoneValidCodeReqNo = ref('')
|
||||
|
||||
// 点击获取短信验证码
|
||||
const getPhoneValidCode = () => {
|
||||
formRules.value.phone = [required('请输入11位手机号'), rules.phone]
|
||||
delete formRules.value.phoneValidCode
|
||||
phoneLoginFormRef.value.validate().then(() => {
|
||||
// 显示弹框
|
||||
visible.value = true
|
||||
// 获取内部图片验证码
|
||||
getPhonePicCaptcha()
|
||||
})
|
||||
}
|
||||
|
||||
// 点击登录按钮
|
||||
const submitLogin = async () => {
|
||||
formRules.value.phone = [required('请输入11位手机号'), rules.phone]
|
||||
formRules.value.phoneValidCode = [required('请输入短信验证码'), rules.number]
|
||||
|
||||
const validate = await phoneLoginFormRef.value.validate().catch(() => {})
|
||||
if (!validate) return false
|
||||
|
||||
phoneFormData.value.validCode = phoneFormData.value.phoneValidCode
|
||||
// delete phoneFormData.value.phoneValidCode
|
||||
phoneFormData.value.validCodeReqNo = phoneValidCodeReqNo.value
|
||||
loading.value = true
|
||||
clientLoginApi
|
||||
.clientLoginByPhone(phoneFormData.value)
|
||||
.then((token) => {
|
||||
afterLogin(token)
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
// 弹框的
|
||||
const visible = ref(false)
|
||||
const phoneLoginFormModalRef = ref()
|
||||
const phoneFormModalData = ref({})
|
||||
const validCodeBase64 = ref('')
|
||||
const validCodeReqNo = ref('')
|
||||
const formModalRules = {
|
||||
validCode: [required('请输入图形验证码'), rules.lettersNum]
|
||||
}
|
||||
const getPhonePicCaptcha = () => {
|
||||
clientLoginApi.clientGetPicCaptcha().then((data) => {
|
||||
validCodeBase64.value = data.validCodeBase64
|
||||
phoneFormModalData.value.validCodeReqNo = data.validCodeReqNo
|
||||
})
|
||||
}
|
||||
const handleCancel = () => {
|
||||
visible.value = false
|
||||
}
|
||||
const handleOk = () => {
|
||||
// 获取到里面的验证码,并发送短信
|
||||
phoneLoginFormModalRef.value.validate().then(() => {
|
||||
visible.value = false
|
||||
// 发送短信,首先拿到刚刚输入的手机号
|
||||
phoneFormModalData.value.phone = phoneFormData.value.phone
|
||||
// 禁用发送按钮,并设置为倒计时
|
||||
state.value.smsSendBtn = true
|
||||
const interval = window.setInterval(() => {
|
||||
if (state.value.time-- <= 0) {
|
||||
state.value.time = 60
|
||||
state.value.smsSendBtn = false
|
||||
window.clearInterval(interval)
|
||||
}
|
||||
}, 1000)
|
||||
const hide = message.loading('验证码发送中..', 0)
|
||||
|
||||
clientLoginApi
|
||||
.clientGetPhoneValidCode(phoneFormModalData.value)
|
||||
.then((data) => {
|
||||
phoneValidCodeReqNo.value = data
|
||||
visible.value = false
|
||||
setTimeout(hide, 500)
|
||||
phoneFormModalData.value.validCode = ''
|
||||
})
|
||||
.catch(() => {
|
||||
setTimeout(hide, 100)
|
||||
clearInterval(interval)
|
||||
state.value.smsSendBtn = false
|
||||
})
|
||||
})
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,18 @@
|
|||
import router from '@/router'
|
||||
import tool from '@/utils/tool'
|
||||
import { message } from 'ant-design-vue'
|
||||
import clientLoginApi from '@/api/auth/client/clientLoginApi'
|
||||
|
||||
export const afterLogin = async (loginToken) => {
|
||||
tool.data.set('CLIENT_TOKEN', loginToken)
|
||||
const param = {
|
||||
token: loginToken
|
||||
}
|
||||
const clientLoginUserInfo = await clientLoginApi.clientGetLoginUser(param)
|
||||
tool.data.set('CLIENT_USER_INFO', clientLoginUserInfo)
|
||||
let indexMenu = '/front/client/index'
|
||||
message.success('登录成功')
|
||||
await router.replace({
|
||||
path: indexMenu
|
||||
})
|
||||
}
|
|
@ -0,0 +1,229 @@
|
|||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
import { cloneDeep, isEmpty } from 'lodash-es'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { required, rules } from '@/utils/formRules'
|
||||
import { globalStore } from '@/store'
|
||||
import smCrypto from '@/utils/smCrypto'
|
||||
import tool from '@/utils/tool'
|
||||
import clientLoginApi from '@/api/auth/client/clientLoginApi'
|
||||
import configApi from '@/api/dev/configApi'
|
||||
import configData from '@/config'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const isRegister = ref(false)
|
||||
const registerFormRef = ref()
|
||||
const registerButtonDisable = ref(false)
|
||||
const store = globalStore()
|
||||
const setSysBaseConfig = store.setSysBaseConfig
|
||||
const sysBaseConfig = computed(() => {
|
||||
return store.sysBaseConfig
|
||||
})
|
||||
const registerFormData = ref({
|
||||
account: '',
|
||||
password: '',
|
||||
newPassword: '',
|
||||
validCode: '',
|
||||
validCodeReqNo: ''
|
||||
})
|
||||
// 表单校验规则
|
||||
const formRules = ref({
|
||||
account: [required('请输入账号')],
|
||||
password: [
|
||||
required('请输入密码'),
|
||||
{
|
||||
validator: (rule, value) => {
|
||||
if (value && registerFormData.value.newPassword && value !== registerFormData.value.newPassword) {
|
||||
return Promise.reject('两次输入的密码不一致')
|
||||
}
|
||||
return Promise.resolve()
|
||||
},
|
||||
trigger: ['change', 'blur']
|
||||
}
|
||||
],
|
||||
newPassword: [
|
||||
required('请再次输入密码'),
|
||||
{
|
||||
validator: (rule, value) => {
|
||||
if (value && registerFormData.value.password && value !== registerFormData.value.password) {
|
||||
return Promise.reject('两次输入的密码不一致')
|
||||
}
|
||||
return Promise.resolve()
|
||||
},
|
||||
trigger: ['change', 'blur']
|
||||
}
|
||||
]
|
||||
})
|
||||
const captchaOpen = ref(configData.SYS_BASE_CONFIG.SNOWY_SYS_DEFAULT_CAPTCHA_OPEN_FLAG_FOR_C)
|
||||
const registerOpen = ref('false')
|
||||
const validCodeBase64 = ref('')
|
||||
|
||||
onMounted(() => {
|
||||
// 获得租户code编码
|
||||
if (!isEmpty(route.query.tenCode)) {
|
||||
registerFormData.value.tenCode = route.query.tenCode
|
||||
}
|
||||
registerButtonDisable.value = true
|
||||
tool.data.set('SNOWY_TEN_CODE', '')
|
||||
// 再去查配置
|
||||
getSysConfig()
|
||||
})
|
||||
const getSysConfig = () => {
|
||||
let formData = ref(configData.SYS_BASE_CONFIG)
|
||||
configApi
|
||||
.configSysBaseList()
|
||||
.then((data) => {
|
||||
registerButtonDisable.value = false
|
||||
if (data) {
|
||||
data.forEach((item) => {
|
||||
formData.value[item.configKey] = item.configValue
|
||||
})
|
||||
captchaOpen.value = formData.value.SNOWY_SYS_DEFAULT_CAPTCHA_OPEN_FLAG_FOR_C
|
||||
registerOpen.value = formData.value.SNOWY_SYS_DEFAULT_ALLOW_REGISTER_FLAG_FOR_C
|
||||
tool.data.set('SNOWY_SYS_BASE_CONFIG', formData.value)
|
||||
setSysBaseConfig(formData.value)
|
||||
refreshSwitch()
|
||||
}
|
||||
})
|
||||
.catch(() => {})
|
||||
}
|
||||
// 通过开关加载内容
|
||||
const refreshSwitch = () => {
|
||||
// 判断是否开启验证码
|
||||
if (captchaOpen.value === 'true') {
|
||||
// 加载验证码
|
||||
registerCaptcha()
|
||||
// 加入校验
|
||||
formRules.value.validCode = [required('请输入验证码', 'blur'), rules.lettersNum]
|
||||
}
|
||||
}
|
||||
|
||||
// 点击注册按钮
|
||||
const submitRegister = () => {
|
||||
formRules.value.validCode = [required('请输入验证码'), rules.lettersNum]
|
||||
registerFormRef.value
|
||||
.validate()
|
||||
.then(() => {
|
||||
registerButtonDisable.value = false
|
||||
isRegister.value = true
|
||||
const loginData = {
|
||||
account: registerFormData.value.account,
|
||||
// 密码进行SM2加密,传输过程中看到的只有密文,后端存储使用hash
|
||||
password: smCrypto.doSm2Encrypt(cloneDeep(registerFormData.value.password)),
|
||||
validCode: registerFormData.value.validCode,
|
||||
validCodeReqNo: registerFormData.value.validCodeReqNo
|
||||
}
|
||||
clientLoginApi
|
||||
.clientRegister(loginData)
|
||||
.then(() => {
|
||||
router.replace({
|
||||
path: '/front/client/login'
|
||||
})
|
||||
message.success('注册成功')
|
||||
})
|
||||
.catch(() => {
|
||||
// 异常的时候一般可能就是验证码过期,所以我们将其刷新
|
||||
if (captchaOpen.value === 'true') {
|
||||
registerCaptcha()
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
isRegister.value = false
|
||||
})
|
||||
})
|
||||
.catch(() => {})
|
||||
}
|
||||
// 获取图形验证码
|
||||
const registerCaptcha = () => {
|
||||
clientLoginApi.clientGetPicCaptcha().then((data) => {
|
||||
validCodeBase64.value = data.validCodeBase64
|
||||
registerFormData.value.validCodeReqNo = data.validCodeReqNo
|
||||
// 如果有输入的将其清空
|
||||
registerFormData.value.validCode = undefined
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="login-wrapper">
|
||||
<div class="login_main">
|
||||
<div class="login-form">
|
||||
<a-card>
|
||||
<div class="login-header" style="margin-bottom: 20px">
|
||||
<h2>用户注册</h2>
|
||||
</div>
|
||||
<a-form
|
||||
ref="registerFormRef"
|
||||
:model="registerFormData"
|
||||
:rules="formRules"
|
||||
class="user-box"
|
||||
autocomplete="off"
|
||||
>
|
||||
<a-form-item name="account">
|
||||
<a-input v-model:value="registerFormData.account" placeholder="请输入账号" size="large">
|
||||
<template #prefix>
|
||||
<user-outlined class="login-icon-gray" />
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item name="password">
|
||||
<a-input-password v-model:value="registerFormData.password" placeholder="请输入密码" size="large">
|
||||
<template #prefix>
|
||||
<lock-outlined class="login-icon-gray" />
|
||||
</template>
|
||||
</a-input-password>
|
||||
</a-form-item>
|
||||
<a-form-item name="newPassword">
|
||||
<a-input-password v-model:value="registerFormData.newPassword" placeholder="请再次输入密码" size="large">
|
||||
<template #prefix>
|
||||
<lock-outlined class="login-icon-gray" />
|
||||
</template>
|
||||
</a-input-password>
|
||||
</a-form-item>
|
||||
<a-form-item name="validCode" v-if="captchaOpen === 'true'">
|
||||
<a-row :gutter="8">
|
||||
<a-col :span="17">
|
||||
<a-input
|
||||
v-model:value="registerFormData.validCode"
|
||||
placeholder="请输入验证码"
|
||||
size="large"
|
||||
@keyup.enter="submitRegister"
|
||||
>
|
||||
<template #prefix>
|
||||
<verified-outlined class="login-icon-gray" />
|
||||
</template>
|
||||
</a-input>
|
||||
</a-col>
|
||||
<a-col :span="7">
|
||||
<img :src="validCodeBase64" class="login-validCode-img" @click="registerCaptcha" />
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button
|
||||
type="primary"
|
||||
class="w-full"
|
||||
:loading="isRegister"
|
||||
round
|
||||
size="large"
|
||||
@click="submitRegister"
|
||||
:disabled="registerButtonDisable"
|
||||
>
|
||||
注册
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
<div style="display: flex; justify-content: flex-end">
|
||||
<a href="/front/client/login" class="xn-color-0d84ff">已有账号?前往登录</a>
|
||||
</div>
|
||||
</a-form>
|
||||
</a-card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="less">
|
||||
@import '../login/login';
|
||||
</style>
|
|
@ -0,0 +1,159 @@
|
|||
<template>
|
||||
<a-form ref="emailLoginFormRef" :model="emailFormData" :rules="formRules">
|
||||
<a-form-item name="email">
|
||||
<a-input v-model:value="emailFormData.email" :placeholder="$t('login.emailPlaceholder')" size="large">
|
||||
<template #prefix>
|
||||
<mail-outlined class="text-black text-opacity-25" />
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item name="emailValidCode">
|
||||
<a-row :gutter="8">
|
||||
<a-col :span="16">
|
||||
<a-input v-model:value="emailFormData.emailValidCode" :placeholder="$t('login.validError')" size="large">
|
||||
<template #prefix>
|
||||
<mail-outlined class="text-black text-opacity-25" />
|
||||
</template>
|
||||
</a-input>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-button size="large" class="xn-wd" @click="getEmailValidCode" :disabled="state.smsSendBtn">
|
||||
{{ (!state.smsSendBtn && $t('login.getEmailCode')) || state.time + ' s' }}
|
||||
</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button type="primary" class="xn-wd" :loading="loading" round size="large" @click="submitLogin">
|
||||
{{ $t('login.signIn') }}
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<a-modal
|
||||
v-model:open="visible"
|
||||
:width="400"
|
||||
:title="$t('login.machineValidation')"
|
||||
@cancel="handleCancel"
|
||||
@ok="handleOk"
|
||||
>
|
||||
<a-form ref="emailLoginFormModalRef" :model="emailFormModalData" :rules="formModalRules">
|
||||
<a-form-item name="validCode">
|
||||
<a-row :gutter="8">
|
||||
<a-col :span="17">
|
||||
<a-input v-model:value="emailFormModalData.validCode" :placeholder="$t('login.validError')" size="large">
|
||||
<template #prefix>
|
||||
<verified-outlined class="text-black text-opacity-25" />
|
||||
</template>
|
||||
</a-input>
|
||||
</a-col>
|
||||
<a-col :span="7">
|
||||
<img :src="validCodeBase64" class="xn-findform-line" @click="getEmailPicCaptcha" />
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script setup name="emailLoginForm">
|
||||
import { message } from 'ant-design-vue'
|
||||
import { required, rules } from '@/utils/formRules'
|
||||
import loginApi from '@/api/auth/loginApi'
|
||||
import { afterLogin } from './util'
|
||||
const { proxy } = getCurrentInstance()
|
||||
|
||||
const emailLoginFormRef = ref()
|
||||
const emailFormData = ref({})
|
||||
const loading = ref(false)
|
||||
let state = ref({
|
||||
time: 60,
|
||||
smsSendBtn: false
|
||||
})
|
||||
let formRules = ref({})
|
||||
const emailValidCodeReqNo = ref('')
|
||||
|
||||
// 点击获取邮箱验证码
|
||||
const getEmailValidCode = () => {
|
||||
formRules.value.email = [required(proxy.$t('login.emailValidPlaceholder')), rules.email]
|
||||
delete formRules.value.emailValidCode
|
||||
emailLoginFormRef.value.validate().then(() => {
|
||||
// 显示弹框
|
||||
visible.value = true
|
||||
// 获取内部图片验证码
|
||||
getEmailPicCaptcha()
|
||||
})
|
||||
}
|
||||
|
||||
// 点击登录按钮
|
||||
const submitLogin = async () => {
|
||||
formRules.value.email = [required(proxy.$t('login.emailValidPlaceholder')), rules.email]
|
||||
formRules.value.emailValidCode = [required(proxy.$t('login.emailCodePlaceholder')), rules.number]
|
||||
|
||||
const validate = await emailLoginFormRef.value.validate().catch(() => {})
|
||||
if (!validate) return false
|
||||
|
||||
emailFormData.value.validCode = emailFormData.value.emailValidCode
|
||||
// delete emailFormData.value.emailValidCode
|
||||
emailFormData.value.validCodeReqNo = emailValidCodeReqNo.value
|
||||
loading.value = true
|
||||
loginApi
|
||||
.loginByEmail(emailFormData.value)
|
||||
.then((token) => {
|
||||
afterLogin(token)
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
// 弹框的
|
||||
const visible = ref(false)
|
||||
const emailLoginFormModalRef = ref()
|
||||
const emailFormModalData = ref({})
|
||||
const validCodeBase64 = ref('')
|
||||
const validCodeReqNo = ref('')
|
||||
const formModalRules = {
|
||||
validCode: [required(proxy.$t('login.validError')), rules.lettersNum]
|
||||
}
|
||||
const getEmailPicCaptcha = () => {
|
||||
loginApi.getPicCaptcha().then((data) => {
|
||||
validCodeBase64.value = data.validCodeBase64
|
||||
emailFormModalData.value.validCodeReqNo = data.validCodeReqNo
|
||||
})
|
||||
}
|
||||
const handleCancel = () => {
|
||||
visible.value = false
|
||||
}
|
||||
const handleOk = () => {
|
||||
// 获取到里面的验证码,并发送邮箱
|
||||
emailLoginFormModalRef.value.validate().then(() => {
|
||||
visible.value = false
|
||||
// 发送邮箱,首先拿到刚刚输入的邮箱号
|
||||
emailFormModalData.value.email = emailFormData.value.email
|
||||
// 禁用发送按钮,并设置为倒计时
|
||||
state.value.smsSendBtn = true
|
||||
const interval = window.setInterval(() => {
|
||||
if (state.value.time-- <= 0) {
|
||||
state.value.time = 60
|
||||
state.value.smsSendBtn = false
|
||||
window.clearInterval(interval)
|
||||
}
|
||||
}, 1000)
|
||||
const hide = message.loading('验证码发送中..', 0)
|
||||
|
||||
loginApi
|
||||
.getEmailValidCode(emailFormModalData.value)
|
||||
.then((data) => {
|
||||
emailValidCodeReqNo.value = data
|
||||
visible.value = false
|
||||
setTimeout(hide, 500)
|
||||
emailFormModalData.value.validCode = ''
|
||||
})
|
||||
.catch(() => {
|
||||
setTimeout(hide, 100)
|
||||
clearInterval(interval)
|
||||
state.value.smsSendBtn = false
|
||||
})
|
||||
})
|
||||
}
|
||||
</script>
|
|
@ -90,9 +90,12 @@
|
|||
</a-form-item>
|
||||
|
||||
<a-form-item>
|
||||
<a-button type="link" class="p-0">
|
||||
<router-link to="/findpwd">{{ $t('login.forgetPassword') }}?</router-link>
|
||||
</a-button>
|
||||
<div style="display: flex; justify-content: space-between">
|
||||
<a href="/findpwd" class="xn-color-0d84ff">{{ $t('login.forgetPassword') }}?</a>
|
||||
<a href="/register" class="xn-color-0d84ff" v-if="registerOpen === 'true'">
|
||||
{{ $t('login.notAccountPleaseRegister') }}
|
||||
</a>
|
||||
</div>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button type="primary" class="w-full" :loading="loading" round size="large" @click="login"
|
||||
|
@ -101,11 +104,17 @@
|
|||
</a-form-item>
|
||||
</a-form>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="userSms" :tab="$t('login.phoneSms')" force-render>
|
||||
<a-tab-pane key="userSms" :tab="$t('login.phoneSms')" force-render v-if="phoneLogin === 'true'">
|
||||
<phone-login-form />
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="userEmail" :tab="$t('login.emailLogin')" force-render v-if="emailLogin === 'true'">
|
||||
<email-login-form />
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
<three-login />
|
||||
<div v-if="configData.FRONT_BACK_LOGIN_URL_SHOW">
|
||||
<a href="/front/client/index" class="xn-color-0d84ff">前台登录</a>
|
||||
</div>
|
||||
<three-login v-if="configData.THREE_LOGIN_SHOW" />
|
||||
</a-card>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -114,6 +123,7 @@
|
|||
<script setup>
|
||||
import loginApi from '@/api/auth/loginApi'
|
||||
const PhoneLoginForm = defineAsyncComponent(() => import('./phoneLoginForm.vue'))
|
||||
const EmailLoginForm = defineAsyncComponent(() => import('./emailLoginForm.vue'))
|
||||
import ThreeLogin from './threeLogin.vue'
|
||||
import smCrypto from '@/utils/smCrypto'
|
||||
import { required } from '@/utils/formRules'
|
||||
|
@ -126,6 +136,9 @@
|
|||
|
||||
const activeKey = ref('userAccount')
|
||||
const captchaOpen = ref(configData.SYS_BASE_CONFIG.SNOWY_SYS_DEFAULT_CAPTCHA_OPEN)
|
||||
const registerOpen = ref('false')
|
||||
const phoneLogin = ref('false')
|
||||
const emailLogin = ref('false')
|
||||
const validCodeBase64 = ref('')
|
||||
const loading = ref(false)
|
||||
|
||||
|
@ -183,6 +196,9 @@
|
|||
formData.value[item.configKey] = item.configValue
|
||||
})
|
||||
captchaOpen.value = formData.value.SNOWY_SYS_DEFAULT_CAPTCHA_OPEN
|
||||
registerOpen.value = formData.value.SNOWY_SYS_DEFAULT_ALLOW_REGISTER_FLAG_FOR_B
|
||||
phoneLogin.value = formData.value.SNOWY_SYS_DEFAULT_ALLOW_PHONE_LOGIN_FLAG_FOR_B
|
||||
emailLogin.value = formData.value.SNOWY_SYS_DEFAULT_ALLOW_EMAIL_LOGIN_FLAG_FOR_B
|
||||
tool.data.set('SNOWY_SYS_BASE_CONFIG', formData.value)
|
||||
setSysBaseConfig(formData.value)
|
||||
refreshSwitch()
|
||||
|
|
|
@ -1,15 +1,20 @@
|
|||
<template>
|
||||
<a-form ref="phoneLoginFormRef" :model="phoneFormData" :rules="formRules">
|
||||
<a-form-item name="phone">
|
||||
<a-input v-model:value="phoneFormData.phone" :placeholder="$t('login.phonePlaceholder')" size="large">
|
||||
<a-input-number
|
||||
v-model:value="phoneFormData.phone"
|
||||
:placeholder="$t('login.phonePlaceholder')"
|
||||
size="large"
|
||||
style="width: 100%"
|
||||
>
|
||||
<template #prefix>
|
||||
<mobile-outlined class="text-black text-opacity-25" />
|
||||
</template>
|
||||
</a-input>
|
||||
</a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item name="phoneValidCode">
|
||||
<a-row :gutter="8">
|
||||
<a-col :span="17">
|
||||
<a-col :span="16">
|
||||
<a-input
|
||||
v-model:value="phoneFormData.phoneValidCode"
|
||||
:placeholder="$t('login.smsCodePlaceholder')"
|
||||
|
@ -20,7 +25,7 @@
|
|||
</template>
|
||||
</a-input>
|
||||
</a-col>
|
||||
<a-col :span="7">
|
||||
<a-col :span="8">
|
||||
<a-button size="large" class="xn-wd" @click="getPhoneValidCode" :disabled="state.smsSendBtn">
|
||||
{{ (!state.smsSendBtn && $t('login.getSmsCode')) || state.time + ' s' }}
|
||||
</a-button>
|
||||
|
@ -55,11 +60,7 @@
|
|||
</a-input>
|
||||
</a-col>
|
||||
<a-col :span="7">
|
||||
<img
|
||||
:src="validCodeBase64"
|
||||
class="xn-findform-line"
|
||||
@click="getPhonePicCaptcha"
|
||||
/>
|
||||
<img :src="validCodeBase64" class="xn-findform-line" @click="getPhonePicCaptcha" />
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form-item>
|
||||
|
@ -72,6 +73,7 @@
|
|||
import { required, rules } from '@/utils/formRules'
|
||||
import loginApi from '@/api/auth/loginApi'
|
||||
import { afterLogin } from './util'
|
||||
const { proxy } = getCurrentInstance()
|
||||
|
||||
const phoneLoginFormRef = ref()
|
||||
const phoneFormData = ref({})
|
||||
|
@ -82,10 +84,9 @@
|
|||
})
|
||||
let formRules = ref({})
|
||||
const phoneValidCodeReqNo = ref('')
|
||||
|
||||
// 点击获取短信验证码
|
||||
const getPhoneValidCode = () => {
|
||||
formRules.value.phone = [required('请输入11位手机号'), rules.phone]
|
||||
formRules.value.phone = [required(proxy.$t('login.phoneInputNumberPlaceholder')), rules.phone]
|
||||
delete formRules.value.phoneValidCode
|
||||
phoneLoginFormRef.value.validate().then(() => {
|
||||
// 显示弹框
|
||||
|
@ -94,11 +95,10 @@
|
|||
getPhonePicCaptcha()
|
||||
})
|
||||
}
|
||||
|
||||
// 点击登录按钮
|
||||
const submitLogin = async () => {
|
||||
formRules.value.phone = [required('请输入11位手机号'), rules.phone]
|
||||
formRules.value.phoneValidCode = [required('请输入短信验证码'), rules.number]
|
||||
formRules.value.phone = [required(proxy.$t('login.phoneInputNumberPlaceholder')), rules.phone]
|
||||
formRules.value.phoneValidCode = [required(proxy.$t('login.smsCodePlaceholder')), rules.number]
|
||||
|
||||
const validate = await phoneLoginFormRef.value.validate().catch(() => {})
|
||||
if (!validate) return false
|
||||
|
@ -107,9 +107,12 @@
|
|||
// delete phoneFormData.value.phoneValidCode
|
||||
phoneFormData.value.validCodeReqNo = phoneValidCodeReqNo.value
|
||||
loading.value = true
|
||||
loginApi.loginByPhone(phoneFormData.value).then((token) => {
|
||||
loginApi
|
||||
.loginByPhone(phoneFormData.value)
|
||||
.then((token) => {
|
||||
afterLogin(token)
|
||||
}).catch((err) => {
|
||||
})
|
||||
.catch((err) => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
|
@ -121,7 +124,7 @@
|
|||
const validCodeBase64 = ref('')
|
||||
const validCodeReqNo = ref('')
|
||||
const formModalRules = {
|
||||
validCode: [required('请输入图形验证码'), rules.lettersNum]
|
||||
validCode: [required(proxy.$t('login.validError')), rules.lettersNum]
|
||||
}
|
||||
const getPhonePicCaptcha = () => {
|
||||
loginApi.getPicCaptcha().then((data) => {
|
||||
|
|
|
@ -0,0 +1,265 @@
|
|||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
import { cloneDeep, isEmpty } from 'lodash-es'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { required, rules } from '@/utils/formRules'
|
||||
import { globalStore } from '@/store'
|
||||
import smCrypto from '@/utils/smCrypto'
|
||||
import tool from '@/utils/tool'
|
||||
import loginApi from '@/api/auth/loginApi'
|
||||
import configApi from '@/api/dev/configApi'
|
||||
import configData from '@/config'
|
||||
|
||||
const { proxy } = getCurrentInstance()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const isRegister = ref(false)
|
||||
const registerFormRef = ref()
|
||||
const registerButtonDisable = ref(false)
|
||||
const store = globalStore()
|
||||
const setSysBaseConfig = store.setSysBaseConfig
|
||||
const sysBaseConfig = computed(() => {
|
||||
return store.sysBaseConfig
|
||||
})
|
||||
const registerFormData = ref({
|
||||
account: '',
|
||||
password: '',
|
||||
newPassword: '',
|
||||
validCode: '',
|
||||
validCodeReqNo: ''
|
||||
})
|
||||
// 表单校验规则
|
||||
const formRules = ref({
|
||||
account: [required(proxy.$t('login.accountPlaceholder'))],
|
||||
password: [
|
||||
required(proxy.$t('login.PWPlaceholder')),
|
||||
{
|
||||
validator: (rule, value) => {
|
||||
if (value && registerFormData.value.newPassword && value !== registerFormData.value.newPassword) {
|
||||
return Promise.reject(proxy.$t('login.enteredPasswordsDiffer'))
|
||||
}
|
||||
return Promise.resolve()
|
||||
},
|
||||
trigger: ['change', 'blur']
|
||||
}
|
||||
],
|
||||
newPassword: [
|
||||
required(proxy.$t('login.enterAgainPassword')),
|
||||
{
|
||||
validator: (rule, value) => {
|
||||
if (value && registerFormData.value.password && value !== registerFormData.value.password) {
|
||||
return Promise.reject(proxy.$t('login.enteredPasswordsDiffer'))
|
||||
}
|
||||
return Promise.resolve()
|
||||
},
|
||||
trigger: ['change', 'blur']
|
||||
}
|
||||
]
|
||||
})
|
||||
const captchaOpen = ref(configData.SYS_BASE_CONFIG.SNOWY_SYS_DEFAULT_CAPTCHA_OPEN_FLAG_FOR_B)
|
||||
const registerOpen = ref('false')
|
||||
const validCodeBase64 = ref('')
|
||||
|
||||
onMounted(() => {
|
||||
// 获得租户code编码
|
||||
if (!isEmpty(route.query.tenCode)) {
|
||||
registerFormData.value.tenCode = route.query.tenCode
|
||||
}
|
||||
registerButtonDisable.value = true
|
||||
tool.data.set('SNOWY_TEN_CODE', '')
|
||||
// 再去查配置
|
||||
getSysConfig()
|
||||
})
|
||||
const getSysConfig = () => {
|
||||
let formData = ref(configData.SYS_BASE_CONFIG)
|
||||
configApi
|
||||
.configSysBaseList()
|
||||
.then((data) => {
|
||||
registerButtonDisable.value = false
|
||||
if (data) {
|
||||
data.forEach((item) => {
|
||||
formData.value[item.configKey] = item.configValue
|
||||
})
|
||||
captchaOpen.value = formData.value.SNOWY_SYS_DEFAULT_CAPTCHA_OPEN_FLAG_FOR_B
|
||||
registerOpen.value = formData.value.SNOWY_SYS_DEFAULT_ALLOW_REGISTER_FLAG_FOR_B
|
||||
tool.data.set('SNOWY_SYS_BASE_CONFIG', formData.value)
|
||||
setSysBaseConfig(formData.value)
|
||||
refreshSwitch()
|
||||
}
|
||||
})
|
||||
.catch(() => {})
|
||||
}
|
||||
// 通过开关加载内容
|
||||
const refreshSwitch = () => {
|
||||
// 判断是否开启验证码
|
||||
if (captchaOpen.value === 'true') {
|
||||
// 加载验证码
|
||||
registerCaptcha()
|
||||
// 加入校验
|
||||
formRules.value.validCode = [required(proxy.$t('login.validError'), 'blur'), rules.lettersNum]
|
||||
}
|
||||
}
|
||||
|
||||
// 点击注册按钮
|
||||
const submitRegister = () => {
|
||||
formRules.value.validCode = [required(proxy.$t('login.validError')), rules.lettersNum]
|
||||
registerFormRef.value
|
||||
.validate()
|
||||
.then(() => {
|
||||
registerButtonDisable.value = false
|
||||
isRegister.value = true
|
||||
const loginData = {
|
||||
account: registerFormData.value.account,
|
||||
// 密码进行SM2加密,传输过程中看到的只有密文,后端存储使用hash
|
||||
password: smCrypto.doSm2Encrypt(cloneDeep(registerFormData.value.password)),
|
||||
validCode: registerFormData.value.validCode,
|
||||
validCodeReqNo: registerFormData.value.validCodeReqNo
|
||||
}
|
||||
loginApi
|
||||
.register(loginData)
|
||||
.then(() => {
|
||||
router.replace({
|
||||
path: '/login'
|
||||
})
|
||||
message.success('注册成功')
|
||||
})
|
||||
.catch(() => {
|
||||
// 异常的时候一般可能就是验证码过期,所以我们将其刷新
|
||||
if (captchaOpen.value === 'true') {
|
||||
registerCaptcha()
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
isRegister.value = false
|
||||
})
|
||||
})
|
||||
.catch(() => {})
|
||||
}
|
||||
// 获取图形验证码
|
||||
const registerCaptcha = () => {
|
||||
loginApi.getPicCaptcha().then((data) => {
|
||||
validCodeBase64.value = data.validCodeBase64
|
||||
registerFormData.value.validCodeReqNo = data.validCodeReqNo
|
||||
// 如果有输入的将其清空
|
||||
registerFormData.value.validCode = undefined
|
||||
})
|
||||
}
|
||||
const handleLink = (e) => {
|
||||
if (!sysBaseConfig.value.SNOWY_SYS_COPYRIGHT_URL) {
|
||||
e?.stopPropagation()
|
||||
e?.preventDefault()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="login-wrapper">
|
||||
<div class="login_background">
|
||||
<div class="logo_background">
|
||||
<a
|
||||
:class="{ 'no-link': !sysBaseConfig.SNOWY_SYS_COPYRIGHT_URL }"
|
||||
:href="sysBaseConfig.SNOWY_SYS_COPYRIGHT_URL"
|
||||
target="_blank"
|
||||
@click="handleLink"
|
||||
>
|
||||
<img :alt="sysBaseConfig.SNOWY_SYS_NAME" :src="sysBaseConfig.SNOWY_SYS_LOGO" />
|
||||
<label>{{ sysBaseConfig.SNOWY_SYS_NAME }}</label>
|
||||
</a>
|
||||
</div>
|
||||
<div class="version">
|
||||
<p>{{ sysBaseConfig.SNOWY_SYS_DEFAULT_DESCRRIPTION }}</p>
|
||||
<p>{{ sysBaseConfig.SNOWY_SYS_COPYRIGHT }} {{ sysBaseConfig.SNOWY_SYS_VERSION }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="login_main">
|
||||
<div class="login-form">
|
||||
<a-card>
|
||||
<div class="login-header" style="margin-bottom: 20px">
|
||||
<h2>{{ $t('login.userRegister') }}</h2>
|
||||
</div>
|
||||
<a-form
|
||||
ref="registerFormRef"
|
||||
:model="registerFormData"
|
||||
:rules="formRules"
|
||||
class="user-box"
|
||||
autocomplete="off"
|
||||
>
|
||||
<a-form-item name="account">
|
||||
<a-input
|
||||
v-model:value="registerFormData.account"
|
||||
:placeholder="$t('login.accountPlaceholder')"
|
||||
size="large"
|
||||
>
|
||||
<template #prefix>
|
||||
<user-outlined class="login-icon-gray" />
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item name="password">
|
||||
<a-input-password
|
||||
v-model:value="registerFormData.password"
|
||||
:placeholder="$t('login.PWPlaceholder')"
|
||||
size="large"
|
||||
>
|
||||
<template #prefix>
|
||||
<lock-outlined class="login-icon-gray" />
|
||||
</template>
|
||||
</a-input-password>
|
||||
</a-form-item>
|
||||
<a-form-item name="newPassword">
|
||||
<a-input-password
|
||||
v-model:value="registerFormData.newPassword"
|
||||
:placeholder="$t('login.enterAgainPassword')"
|
||||
size="large"
|
||||
>
|
||||
<template #prefix>
|
||||
<lock-outlined class="login-icon-gray" />
|
||||
</template>
|
||||
</a-input-password>
|
||||
</a-form-item>
|
||||
<a-form-item name="validCode" v-if="captchaOpen === 'true'">
|
||||
<a-row :gutter="8">
|
||||
<a-col :span="17">
|
||||
<a-input
|
||||
v-model:value="registerFormData.validCode"
|
||||
:placeholder="$t('login.validError')"
|
||||
size="large"
|
||||
@keyup.enter="submitRegister"
|
||||
>
|
||||
<template #prefix>
|
||||
<verified-outlined class="login-icon-gray" />
|
||||
</template>
|
||||
</a-input>
|
||||
</a-col>
|
||||
<a-col :span="7">
|
||||
<img :src="validCodeBase64" class="login-validCode-img" @click="registerCaptcha" />
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button
|
||||
type="primary"
|
||||
class="w-full"
|
||||
:loading="isRegister"
|
||||
round
|
||||
size="large"
|
||||
@click="submitRegister"
|
||||
:disabled="registerButtonDisable"
|
||||
>
|
||||
{{ $t('login.register') }}
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
<div style="display: flex; justify-content: flex-end">
|
||||
<a href="/login" class="xn-color-0d84ff">{{ $t('login.haveAccountPleaseLogin') }}</a>
|
||||
</div>
|
||||
</a-form>
|
||||
</a-card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="less">
|
||||
@import '../login/login';
|
||||
</style>
|
|
@ -0,0 +1,273 @@
|
|||
<template>
|
||||
<xn-form-container
|
||||
:title="formData.id ? '编辑用户' : '增加用户'"
|
||||
:width="800"
|
||||
:visible="visible"
|
||||
:destroy-on-close="true"
|
||||
:body-style="{ 'padding-top': '0px' }"
|
||||
@close="onClose"
|
||||
>
|
||||
<a-form ref="formRef" :model="formData" :rules="formRules" layout="vertical">
|
||||
<a-tabs v-model:activeKey="activeTabsKey">
|
||||
<a-tab-pane key="1" tab="基础信息" force-render>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="账号:" name="account">
|
||||
<a-input v-model:value="formData.account" placeholder="请输入账号" allow-clear />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="姓名:" name="name">
|
||||
<a-input v-model:value="formData.name" placeholder="请输入姓名" allow-clear />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="性别:" name="gender">
|
||||
<a-radio-group v-model:value="formData.gender" :options="genderOptions"> </a-radio-group>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="昵称:" name="nickname">
|
||||
<a-input v-model:value="formData.nickname" placeholder="请输入昵称" allow-clear />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="手机号:" name="phone">
|
||||
<a-input v-model:value="formData.phone" placeholder="请输入手机" allow-clear />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="邮箱:" name="email">
|
||||
<a-input v-model:value="formData.email" placeholder="请输入邮箱" allow-clear />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="出生日期:" name="birthday">
|
||||
<a-date-picker v-model:value="formData.birthday" value-format="YYYY-MM-DD" style="width: 100%" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="年龄:" name="age">
|
||||
<a-input-number v-model:value="formData.age" placeholder="请输入年龄" allow-clear style="width: 100%" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="2" tab="更多信息" force-render>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="民族:" name="nation">
|
||||
<a-select v-model:value="formData.nation" placeholder="请选择民族" :options="nationOptions"> </a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="籍贯:" name="nativePlace">
|
||||
<a-input v-model:value="formData.nativePlace" placeholder="请输入籍贯" allow-clear />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="家庭住址:" name="homeAddress">
|
||||
<a-textarea
|
||||
v-model:value="formData.homeAddress"
|
||||
placeholder="请输入家庭住址"
|
||||
:auto-size="{ minRows: 2, maxRows: 5 }"
|
||||
allow-clear
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="通信地址:" name="mailingAddress">
|
||||
<a-textarea
|
||||
v-model:value="formData.mailingAddress"
|
||||
placeholder="请输入通信地址"
|
||||
:auto-size="{ minRows: 2, maxRows: 5 }"
|
||||
allow-clear
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="证件类型:" name="idCardType">
|
||||
<a-select v-model:value="formData.idCardType" placeholder="请选择证件类型" :options="idcardTypeOptions">
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="证件号码:" name="idCardNumber">
|
||||
<a-input v-model:value="formData.idCardNumber" placeholder="请输入证件号码" allow-clear />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="文化程度:" name="cultureLevel">
|
||||
<a-select
|
||||
v-model:value="formData.cultureLevel"
|
||||
placeholder="请选择文化程度"
|
||||
:options="cultureLevelOptions"
|
||||
>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="政治面貌:" name="politicalOutlook">
|
||||
<a-input v-model:value="formData.politicalOutlook" placeholder="请输入政治面貌" allow-clear />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="毕业学校:" name="college">
|
||||
<a-input v-model:value="formData.college" placeholder="请输入毕业学校" allow-clear />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="学历:" name="education">
|
||||
<a-input v-model:value="formData.education" placeholder="请输入学历" allow-clear />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="学制:" name="eduLength">
|
||||
<a-input v-model:value="formData.eduLength" placeholder="请输入学制" allow-clear />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="学位:" name="degree">
|
||||
<a-input v-model:value="formData.degree" placeholder="请输入学位" allow-clear />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="家庭电话:" name="homeTel">
|
||||
<a-input v-model:value="formData.homeTel" placeholder="请输入家庭电话" allow-clear />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="办公电话:" name="officeTel">
|
||||
<a-input v-model:value="formData.officeTel" placeholder="请输入办公电话" allow-clear />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="紧急联系人:" name="emergencyContact">
|
||||
<a-input v-model:value="formData.emergencyContact" placeholder="请输入紧急联系人" allow-clear />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="紧急联系电话:" name="emergencyPhone">
|
||||
<a-input v-model:value="formData.emergencyPhone" placeholder="请输入紧急联系电话" allow-clear />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="紧急联系人地址:" name="emergencyAddress">
|
||||
<a-textarea
|
||||
v-model:value="formData.emergencyAddress"
|
||||
placeholder="请输入紧急联系人地址"
|
||||
:auto-size="{ minRows: 2, maxRows: 5 }"
|
||||
allow-clear
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</a-form>
|
||||
<template #footer>
|
||||
<a-button style="margin-right: 8px" @click="onClose">关闭</a-button>
|
||||
<a-button type="primary" :loading="formLoading" @click="onSubmit">保存</a-button>
|
||||
</template>
|
||||
</xn-form-container>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import clientUserApi from '@/api/client/clientUserApi'
|
||||
import { required } from '@/utils/formRules'
|
||||
import tool from '@/utils/tool'
|
||||
// 默认是关闭状态
|
||||
const visible = ref(false)
|
||||
const formRef = ref()
|
||||
const activeTabsKey = ref('1')
|
||||
const emit = defineEmits({ successful: null })
|
||||
const formLoading = ref(false)
|
||||
// 表单数据
|
||||
const formData = ref({})
|
||||
// 打开抽屉
|
||||
const onOpen = (record) => {
|
||||
visible.value = true
|
||||
if (record) {
|
||||
formData.value = record
|
||||
} else {
|
||||
formData.value = {
|
||||
gender: '男'
|
||||
}
|
||||
}
|
||||
}
|
||||
// 关闭抽屉
|
||||
const onClose = () => {
|
||||
visible.value = false
|
||||
}
|
||||
// 默认要校验的
|
||||
const formRules = {
|
||||
account: [required('请输入账号')],
|
||||
name: [required('请输入姓名')],
|
||||
gender: [required('请选择性别')]
|
||||
}
|
||||
// 验证并提交数据
|
||||
const onSubmit = () => {
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(() => {
|
||||
clientUserApi
|
||||
.submitForm(formData.value, formData.value.id)
|
||||
.then(() => {
|
||||
onClose()
|
||||
emit('successful')
|
||||
})
|
||||
.finally(() => {
|
||||
formLoading.value = false
|
||||
})
|
||||
})
|
||||
.catch(() => {})
|
||||
}
|
||||
// 性别
|
||||
const genderOptions = tool.dictList('GENDER')
|
||||
// 民族
|
||||
const nationOptions = tool.dictList('NATION')
|
||||
// 身份证件
|
||||
const idcardTypeOptions = tool.dictList('IDCARD_TYPE')
|
||||
// 文化程度
|
||||
const cultureLevelOptions = tool.dictList('CULTURE_LEVEL')
|
||||
|
||||
// 调用这个函数将子组件的一些数据和方法暴露出去
|
||||
defineExpose({
|
||||
onOpen
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.form-row {
|
||||
background-color: var(--item-hover-bg);
|
||||
margin-left: 0 !important;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.form-row-con {
|
||||
padding-bottom: 5px;
|
||||
padding-top: 5px;
|
||||
padding-left: 15px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,181 @@
|
|||
<template>
|
||||
<div>
|
||||
<a-card :bordered="false" style="margin-bottom: 10px">
|
||||
<a-form ref="searchFormRef" name="advanced_search" class="ant-advanced-search-form" :model="searchFormState">
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="8">
|
||||
<a-form-item name="searchKey" label="用户关键词">
|
||||
<a-input v-model:value="searchFormState.searchKey" placeholder="请输入用户关键词" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-button type="primary" @click="tableRef.refresh(true)">
|
||||
<template #icon><SearchOutlined /></template>
|
||||
查询
|
||||
</a-button>
|
||||
<a-button class="snowy-button-left" @click="reset">
|
||||
<template #icon><redo-outlined /></template>
|
||||
重置
|
||||
</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</a-card>
|
||||
<a-card :bordered="false">
|
||||
<s-table
|
||||
ref="tableRef"
|
||||
:columns="columns"
|
||||
:data="loadData"
|
||||
:expand-row-by-click="true"
|
||||
bordered
|
||||
:alert="options.alert.show"
|
||||
:tool-config="toolConfig"
|
||||
:row-key="(record) => record.id"
|
||||
:row-selection="options.rowSelection"
|
||||
>
|
||||
<template #operator class="table-operator">
|
||||
<a-space>
|
||||
<a-button type="primary" @click="clientUserFormRef.onOpen()">
|
||||
<template #icon><plus-outlined /></template>
|
||||
<span>新增用户</span>
|
||||
</a-button>
|
||||
<xn-batch-button
|
||||
buttonName="批量删除"
|
||||
icon="DeleteOutlined"
|
||||
buttonDanger
|
||||
:selectedRowKeys="selectedRowKeys"
|
||||
@batchCallBack="deleteBatchUser"
|
||||
/>
|
||||
</a-space>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.dataIndex === 'avatar'">
|
||||
<a-avatar :src="record.avatar" style="margin-bottom: -5px; margin-top: -5px" />
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'gender'">
|
||||
{{ $TOOL.dictTypeData('GENDER', record.gender) }}
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'userStatus'">
|
||||
{{ $TOOL.dictTypeData('COMMON_STATUS', record.userStatus) }}
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'action'">
|
||||
<a @click="clientUserFormRef.onOpen(record)">编辑</a>
|
||||
<a-divider type="vertical" />
|
||||
<a-popconfirm title="确定要删除此用户吗" @confirm="removeUser(record)">
|
||||
<a-button type="link" danger size="small"> 删除 </a-button>
|
||||
</a-popconfirm>
|
||||
</template>
|
||||
</template>
|
||||
</s-table>
|
||||
</a-card>
|
||||
<client-user-form ref="clientUserFormRef" @successful="tableRef.refresh()" />
|
||||
</div>
|
||||
</template>
|
||||
<script setup name="clientUser">
|
||||
import clientUserApi from '@/api/client/clientUserApi'
|
||||
import ClientUserForm from './form.vue'
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: '头像',
|
||||
dataIndex: 'avatar',
|
||||
align: 'center',
|
||||
width: '80px'
|
||||
},
|
||||
{
|
||||
title: '账号',
|
||||
dataIndex: 'account',
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: '姓名',
|
||||
dataIndex: 'name'
|
||||
},
|
||||
{
|
||||
title: '性别',
|
||||
dataIndex: 'gender',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '手机',
|
||||
dataIndex: 'phone',
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'userStatus',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'action',
|
||||
align: 'center',
|
||||
width: '220px'
|
||||
}
|
||||
]
|
||||
const toolConfig = { refresh: true, height: true, columnSetting: true }
|
||||
const searchFormRef = ref()
|
||||
const searchFormState = ref({})
|
||||
const tableRef = ref(null)
|
||||
const selectedRowKeys = ref([])
|
||||
const clientUserFormRef = ref(null)
|
||||
// 表格查询 返回 Promise 对象
|
||||
const loadData = (parameter) => {
|
||||
return clientUserApi.userPage(Object.assign(parameter, searchFormState.value)).then((res) => {
|
||||
return res
|
||||
})
|
||||
}
|
||||
// 重置
|
||||
const reset = () => {
|
||||
searchFormRef.value.resetFields()
|
||||
tableRef.value.refresh(true)
|
||||
}
|
||||
// 列表选择配置
|
||||
const options = {
|
||||
alert: {
|
||||
show: false,
|
||||
clear: () => {
|
||||
selectedRowKeys.value = ref([])
|
||||
}
|
||||
},
|
||||
rowSelection: {
|
||||
onChange: (selectedRowKey, selectedRows) => {
|
||||
selectedRowKeys.value = selectedRowKey
|
||||
}
|
||||
}
|
||||
}
|
||||
// 删除用户
|
||||
const removeUser = (record) => {
|
||||
let params = [
|
||||
{
|
||||
id: record.id
|
||||
}
|
||||
]
|
||||
clientUserApi.userDelete(params).then(() => {
|
||||
tableRef.value.refresh()
|
||||
})
|
||||
}
|
||||
// 批量删除
|
||||
const deleteBatchUser = (params) => {
|
||||
clientUserApi.userDelete(params).then(() => {
|
||||
tableRef.value.clearRefreshSelected()
|
||||
})
|
||||
}
|
||||
// 重置用户密码
|
||||
const resetPassword = (record) => {
|
||||
clientUserApi.userResetPassword(record).then(() => {})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.ant-form-item {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
.snowy-table-avatar {
|
||||
margin-top: -10px;
|
||||
margin-bottom: -10px;
|
||||
}
|
||||
.snowy-button-left {
|
||||
margin-left: 8px;
|
||||
}
|
||||
</style>
|
|
@ -33,6 +33,9 @@
|
|||
<p v-else-if="noTitleKey === 'fileConfig'">
|
||||
<FileConfig />
|
||||
</p>
|
||||
<p v-else-if="noTitleKey === 'pushConfig'">
|
||||
<PushConfig />
|
||||
</p>
|
||||
<p v-else-if="noTitleKey === 'thirdConfig'">
|
||||
<ThirdConfig />
|
||||
</p>
|
||||
|
@ -54,6 +57,7 @@
|
|||
import FileConfig from './fileConfig/index.vue'
|
||||
import ThirdConfig from './thirdConfig/index.vue'
|
||||
import OtherConfig from './otherConfig/index.vue'
|
||||
import PushConfig from './pushConfig/index.vue'
|
||||
const key = ref('sysConfig')
|
||||
const noTitleKey = ref('sysConfig')
|
||||
|
||||
|
@ -94,6 +98,10 @@
|
|||
key: 'fileConfig',
|
||||
tab: '文件配置'
|
||||
},
|
||||
{
|
||||
key: 'pushConfig',
|
||||
tab: '推送配置'
|
||||
},
|
||||
{
|
||||
key: 'thirdConfig',
|
||||
tab: '第三方配置'
|
||||
|
|
|
@ -6,11 +6,15 @@
|
|||
<a-tab-pane key="cForm" tab="前台密码">
|
||||
<c-form />
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="weakPassword" tab="弱密码库">
|
||||
<weak-password />
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</template>
|
||||
|
||||
<script setup name="passwordConfig">
|
||||
import CForm from './cForm.vue'
|
||||
import BForm from './bForm.vue'
|
||||
import WeakPassword from './weakPassword/index.vue'
|
||||
const activeKey = ref('bForm')
|
||||
</script>
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
<template>
|
||||
<a-modal
|
||||
:title="formData.id ? '编辑弱密码' : '增加弱密码'"
|
||||
:width="500"
|
||||
v-model:open="open"
|
||||
:destroy-on-close="true"
|
||||
@cancel="onClose"
|
||||
@ok="onSubmit"
|
||||
>
|
||||
<a-form ref="formRef" :model="formData" :rules="formRules" layout="vertical">
|
||||
<a-form-item label="弱密码:" name="password">
|
||||
<a-input v-model:value="formData.password" placeholder="请输入弱密码" allow-clear />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script setup name="devWeakPasswordForm">
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
import { required } from '@/utils/formRules'
|
||||
import weakPasswordApi from '@/api/dev/weakPasswordApi'
|
||||
// 抽屉状态
|
||||
const open = ref(false)
|
||||
const emit = defineEmits({ successful: null })
|
||||
const formRef = ref()
|
||||
// 表单数据
|
||||
const formData = ref({})
|
||||
const submitLoading = ref(false)
|
||||
|
||||
// 打开抽屉
|
||||
const onOpen = (record) => {
|
||||
open.value = true
|
||||
if (record) {
|
||||
let recordData = cloneDeep(record)
|
||||
formData.value = Object.assign({}, recordData)
|
||||
}
|
||||
}
|
||||
// 关闭抽屉
|
||||
const onClose = () => {
|
||||
formRef.value.resetFields()
|
||||
formData.value = {}
|
||||
open.value = false
|
||||
}
|
||||
// 默认要校验的
|
||||
const formRules = {
|
||||
password: [required('请输入弱密码')]
|
||||
}
|
||||
// 验证并提交数据
|
||||
const onSubmit = () => {
|
||||
formRef.value.validate().then(() => {
|
||||
submitLoading.value = true
|
||||
const formDataParam = cloneDeep(formData.value)
|
||||
weakPasswordApi
|
||||
.weakPasswordSubmitForm(formDataParam, formDataParam.id)
|
||||
.then(() => {
|
||||
onClose()
|
||||
emit('successful')
|
||||
})
|
||||
.finally(() => {
|
||||
submitLoading.value = false
|
||||
})
|
||||
})
|
||||
}
|
||||
// 抛出函数
|
||||
defineExpose({
|
||||
onOpen
|
||||
})
|
||||
</script>
|
|
@ -0,0 +1,112 @@
|
|||
<template>
|
||||
<div>
|
||||
<s-table
|
||||
ref="tableRef"
|
||||
:columns="columns"
|
||||
:data="loadData"
|
||||
:alert="options.alert.show"
|
||||
bordered
|
||||
:row-key="(record) => record.id"
|
||||
:tool-config="toolConfig"
|
||||
:row-selection="options.rowSelection"
|
||||
>
|
||||
<template #operator class="table-operator">
|
||||
<a-space>
|
||||
<a-button type="primary" size="small" @click="formRef.onOpen()">
|
||||
<template #icon><plus-outlined /></template>
|
||||
新增
|
||||
</a-button>
|
||||
<xn-batch-button
|
||||
buttonName="批量删除"
|
||||
size="small"
|
||||
icon="DeleteOutlined"
|
||||
buttonDanger
|
||||
:selectedRowKeys="selectedRowKeys"
|
||||
@batchCallBack="deleteBatchDevWeakPassword"
|
||||
/>
|
||||
</a-space>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.dataIndex === 'action'">
|
||||
<a-space>
|
||||
<a @click="formRef.onOpen(record)">编辑</a>
|
||||
<a-divider type="vertical" />
|
||||
<a-popconfirm title="确定要删除吗?" @confirm="deleteDevWeakPassword(record)">
|
||||
<a-button type="link" danger size="small">删除</a-button>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</s-table>
|
||||
<Form ref="formRef" @successful="tableRef.refresh(true)" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="weakpassword">
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
import { ref } from 'vue'
|
||||
import Form from './form.vue'
|
||||
import weakPasswordApi from '@/api/dev/weakPasswordApi'
|
||||
const tableRef = ref()
|
||||
const formRef = ref()
|
||||
const toolConfig = { refresh: true, height: true, columnSetting: true, striped: false }
|
||||
const columns = [
|
||||
{
|
||||
title: '弱密码',
|
||||
dataIndex: 'password'
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime'
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'action',
|
||||
align: 'center',
|
||||
width: '150px'
|
||||
}
|
||||
]
|
||||
const selectedRowKeys = ref([])
|
||||
// 列表选择配置
|
||||
const options = {
|
||||
// columns数字类型字段加入 needTotal: true 可以勾选自动算账
|
||||
alert: {
|
||||
show: false,
|
||||
clear: () => {
|
||||
selectedRowKeys.value = ref([])
|
||||
}
|
||||
},
|
||||
rowSelection: {
|
||||
onChange: (selectedRowKey, selectedRows) => {
|
||||
selectedRowKeys.value = selectedRowKey
|
||||
}
|
||||
}
|
||||
}
|
||||
const loadData = (parameter) => {
|
||||
return weakPasswordApi.weakPasswordPage(parameter).then((data) => {
|
||||
return data
|
||||
})
|
||||
}
|
||||
// 重置
|
||||
const reset = () => {
|
||||
searchFormRef.value.resetFields()
|
||||
tableRef.value.refresh(true)
|
||||
}
|
||||
// 删除
|
||||
const deleteDevWeakPassword = (record) => {
|
||||
let params = [
|
||||
{
|
||||
id: record.id
|
||||
}
|
||||
]
|
||||
weakPasswordApi.weakPasswordDelete(params).then(() => {
|
||||
tableRef.value.refresh(true)
|
||||
})
|
||||
}
|
||||
// 批量删除
|
||||
const deleteBatchDevWeakPassword = (params) => {
|
||||
weakPasswordApi.weakPasswordDelete(params).then(() => {
|
||||
tableRef.value.clearRefreshSelected()
|
||||
})
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,86 @@
|
|||
<template>
|
||||
<a-spin :spinning="loadSpinning">
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
layout="vertical"
|
||||
:label-col="{ ...layout.labelCol, offset: 0 }"
|
||||
:wrapper-col="{ ...layout.wrapperCol, offset: 0 }"
|
||||
>
|
||||
<a-form-item label="消息推送签名:" name="SNOWY_PUSH_DINGTALK_SIGN">
|
||||
<a-input v-model:value="formData.SNOWY_PUSH_DINGTALK_SIGN" placeholder="请输入消息推送签名" />
|
||||
</a-form-item>
|
||||
<a-form-item label="消息推送TOKENID:" name="SNOWY_PUSH_DINGTALK_TOKEN_ID">
|
||||
<a-input v-model:value="formData.SNOWY_PUSH_DINGTALK_TOKEN_ID" placeholder="请输入消息推送TOKENID" />
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button type="primary" :loading="submitLoading" @click="onSubmit()">保存</a-button>
|
||||
<a-button class="xn-ml10" @click="() => formRef.resetFields()">重置</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-spin>
|
||||
</template>
|
||||
|
||||
<script setup name="dingTalkForm">
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
import { required } from '@/utils/formRules'
|
||||
import { message } from 'ant-design-vue'
|
||||
import configApi from '@/api/dev/configApi'
|
||||
|
||||
const formRef = ref()
|
||||
const formData = ref({})
|
||||
const submitLoading = ref(false)
|
||||
const loadSpinning = ref(true)
|
||||
|
||||
// 查询此界面的配置项,并转为表单
|
||||
const param = {
|
||||
category: 'PUSH_DINGTALK'
|
||||
}
|
||||
configApi.configList(param).then((data) => {
|
||||
loadSpinning.value = false
|
||||
if (data) {
|
||||
data.forEach((item) => {
|
||||
formData.value[item.configKey] = item.configValue
|
||||
})
|
||||
} else {
|
||||
message.warning('表单项不存在,请初始化数据库')
|
||||
}
|
||||
})
|
||||
|
||||
// 默认要校验的
|
||||
const formRules = {
|
||||
SNOWY_PUSH_DINGTALK_SIGN: [required('请输入钉钉消息推送签名')],
|
||||
SNOWY_PUSH_DINGTALK_TOKEN_ID: [required('请输入钉钉消息推送TOKENID')]
|
||||
}
|
||||
// 验证并提交数据
|
||||
const onSubmit = () => {
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(() => {
|
||||
submitLoading.value = true
|
||||
let submitParam = cloneDeep(formData.value)
|
||||
const param = Object.entries(submitParam).map((item) => {
|
||||
return {
|
||||
configKey: item[0],
|
||||
configValue: item[1]
|
||||
}
|
||||
})
|
||||
configApi
|
||||
.configEditForm(param)
|
||||
.then(() => {})
|
||||
.finally(() => {
|
||||
submitLoading.value = false
|
||||
})
|
||||
})
|
||||
.catch(() => {})
|
||||
}
|
||||
const layout = {
|
||||
labelCol: {
|
||||
span: 4
|
||||
},
|
||||
wrapperCol: {
|
||||
span: 12
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,82 @@
|
|||
<template>
|
||||
<a-spin :spinning="loadSpinning">
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
layout="vertical"
|
||||
:label-col="{ ...layout.labelCol, offset: 0 }"
|
||||
:wrapper-col="{ ...layout.wrapperCol, offset: 0 }"
|
||||
>
|
||||
<a-form-item label="消息推送TOKENID:" name="SNOWY_PUSH_FEISHU_TOKEN_ID">
|
||||
<a-input v-model:value="formData.SNOWY_PUSH_FEISHU_TOKEN_ID" placeholder="请输入消息推送TOKENID" />
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button type="primary" :loading="submitLoading" @click="onSubmit()">保存</a-button>
|
||||
<a-button class="xn-ml10" @click="() => formRef.resetFields()">重置</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-spin>
|
||||
</template>
|
||||
|
||||
<script setup name="feishuForm">
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
import { required } from '@/utils/formRules'
|
||||
import { message } from 'ant-design-vue'
|
||||
import configApi from '@/api/dev/configApi'
|
||||
|
||||
const formRef = ref()
|
||||
const formData = ref({})
|
||||
const submitLoading = ref(false)
|
||||
const loadSpinning = ref(true)
|
||||
|
||||
// 查询此界面的配置项,并转为表单
|
||||
const param = {
|
||||
category: 'PUSH_FEISHU'
|
||||
}
|
||||
configApi.configList(param).then((data) => {
|
||||
loadSpinning.value = false
|
||||
if (data) {
|
||||
data.forEach((item) => {
|
||||
formData.value[item.configKey] = item.configValue
|
||||
})
|
||||
} else {
|
||||
message.warning('表单项不存在,请初始化数据库')
|
||||
}
|
||||
})
|
||||
|
||||
// 默认要校验的
|
||||
const formRules = {
|
||||
SNOWY_PUSH_FEISHU_TOKEN_ID: [required('请输入飞书消息推送TOKENID')]
|
||||
}
|
||||
// 验证并提交数据
|
||||
const onSubmit = () => {
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(() => {
|
||||
submitLoading.value = true
|
||||
let submitParam = cloneDeep(formData.value)
|
||||
const param = Object.entries(submitParam).map((item) => {
|
||||
return {
|
||||
configKey: item[0],
|
||||
configValue: item[1]
|
||||
}
|
||||
})
|
||||
configApi
|
||||
.configEditForm(param)
|
||||
.then(() => {})
|
||||
.finally(() => {
|
||||
submitLoading.value = false
|
||||
})
|
||||
})
|
||||
.catch(() => {})
|
||||
}
|
||||
const layout = {
|
||||
labelCol: {
|
||||
span: 4
|
||||
},
|
||||
wrapperCol: {
|
||||
span: 12
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,20 @@
|
|||
<template>
|
||||
<a-tabs v-model:activeKey="activeKey" tab-position="left">
|
||||
<a-tab-pane key="dingTalk" tab="钉钉">
|
||||
<ding-talk-form />
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="feishu" tab="飞书">
|
||||
<feishu-form />
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="workWechat" tab="企业微信">
|
||||
<work-wechat-form />
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</template>
|
||||
|
||||
<script setup name="pushConfig">
|
||||
import WorkWechatForm from './workWechatForm.vue'
|
||||
import DingTalkForm from './dingTalkForm.vue'
|
||||
import FeishuForm from './feishuForm.vue'
|
||||
const activeKey = ref('dingTalk')
|
||||
</script>
|
|
@ -0,0 +1,82 @@
|
|||
<template>
|
||||
<a-spin :spinning="loadSpinning">
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
layout="vertical"
|
||||
:label-col="{ ...layout.labelCol, offset: 0 }"
|
||||
:wrapper-col="{ ...layout.wrapperCol, offset: 0 }"
|
||||
>
|
||||
<a-form-item label="消息推送TOKENID:" name="SNOWY_PUSH_WORKWECHAT_TOKEN_ID">
|
||||
<a-input v-model:value="formData.SNOWY_PUSH_WORKWECHAT_TOKEN_ID" placeholder="请输入消息推送TOKENID" />
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button type="primary" :loading="submitLoading" @click="onSubmit()">保存</a-button>
|
||||
<a-button class="xn-ml10" @click="() => formRef.resetFields()">重置</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-spin>
|
||||
</template>
|
||||
|
||||
<script setup name="WorkWechatForm">
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
import { required } from '@/utils/formRules'
|
||||
import { message } from 'ant-design-vue'
|
||||
import configApi from '@/api/dev/configApi'
|
||||
|
||||
const formRef = ref()
|
||||
const formData = ref({})
|
||||
const submitLoading = ref(false)
|
||||
const loadSpinning = ref(true)
|
||||
|
||||
// 查询此界面的配置项,并转为表单
|
||||
const param = {
|
||||
category: 'PUSH_WORKWECHAT'
|
||||
}
|
||||
configApi.configList(param).then((data) => {
|
||||
loadSpinning.value = false
|
||||
if (data) {
|
||||
data.forEach((item) => {
|
||||
formData.value[item.configKey] = item.configValue
|
||||
})
|
||||
} else {
|
||||
message.warning('表单项不存在,请初始化数据库')
|
||||
}
|
||||
})
|
||||
|
||||
// 默认要校验的
|
||||
const formRules = {
|
||||
SNOWY_PUSH_WORKWECHAT_TOKEN_ID: [required('请输入企业微信消息推送TOKENID')]
|
||||
}
|
||||
// 验证并提交数据
|
||||
const onSubmit = () => {
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(() => {
|
||||
submitLoading.value = true
|
||||
let submitParam = cloneDeep(formData.value)
|
||||
const param = Object.entries(submitParam).map((item) => {
|
||||
return {
|
||||
configKey: item[0],
|
||||
configValue: item[1]
|
||||
}
|
||||
})
|
||||
configApi
|
||||
.configEditForm(param)
|
||||
.then(() => {})
|
||||
.finally(() => {
|
||||
submitLoading.value = false
|
||||
})
|
||||
})
|
||||
.catch(() => {})
|
||||
}
|
||||
const layout = {
|
||||
labelCol: {
|
||||
span: 4
|
||||
},
|
||||
wrapperCol: {
|
||||
span: 12
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,137 @@
|
|||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { message } from 'ant-design-vue'
|
||||
import tool from '@/utils/tool'
|
||||
import clientLoginApi from '@/api/auth/client/clientLoginApi'
|
||||
|
||||
const router = useRouter()
|
||||
const userInfo = ref<any>({})
|
||||
|
||||
// 获取用户信息
|
||||
const getUserInfo = () => {
|
||||
const clientUserInfo = tool.data.get('CLIENT_USER_INFO')
|
||||
if (clientUserInfo) {
|
||||
userInfo.value = clientUserInfo
|
||||
}
|
||||
}
|
||||
|
||||
// 退出登录
|
||||
const handleLogout = async () => {
|
||||
try {
|
||||
const param = {
|
||||
token: tool.data.get('CLIENT_TOKEN')
|
||||
}
|
||||
await clientLoginApi.clientLogout(param)
|
||||
tool.data.remove('CLIENT_TOKEN')
|
||||
tool.data.remove('CLIENT_USER_INFO')
|
||||
message.success('退出成功')
|
||||
router.push('/front/client/login')
|
||||
} catch (error) {
|
||||
message.error('退出失败')
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getUserInfo()
|
||||
})
|
||||
const headerStyle = {
|
||||
textAlign: 'center',
|
||||
height: 64,
|
||||
paddingInline: 50,
|
||||
lineHeight: '64px',
|
||||
backgroundColor: '#ffffff'
|
||||
}
|
||||
const contentStyle = {
|
||||
minHeight: 120,
|
||||
lineHeight: '120px',
|
||||
color: '#fff'
|
||||
}
|
||||
const footerStyle = {
|
||||
textAlign: 'center',
|
||||
color: '#fff'
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-layout>
|
||||
<a-layout-header :style="headerStyle">
|
||||
<div style="height: 64px; display: flex; align-items: center; justify-content: space-around">
|
||||
<a-avatar :size="50" :src="userInfo.avatar" />
|
||||
<span>
|
||||
<span style="margin-right: 10px">{{ userInfo.name || userInfo.nickname || userInfo.account }}</span>
|
||||
<a-button type="primary" danger @click="handleLogout" size="small">退出登录</a-button>
|
||||
</span>
|
||||
</div>
|
||||
</a-layout-header>
|
||||
<a-layout-content :style="contentStyle">
|
||||
<div class="user-center">
|
||||
<a-card title="基本信息" class="info-card">
|
||||
<a-descriptions :column="{ xs: 1, sm: 2, md: 3 }">
|
||||
<a-descriptions-item label="账号">{{ userInfo.account }}</a-descriptions-item>
|
||||
<a-descriptions-item label="姓名">{{ userInfo.name }}</a-descriptions-item>
|
||||
<a-descriptions-item label="性别">{{ userInfo.gender }}</a-descriptions-item>
|
||||
<a-descriptions-item label="年龄">{{ userInfo.age || '未设置' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="生日">{{ userInfo.birthday || '未设置' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="籍贯">{{ userInfo.nativePlace || '未设置' }}</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</a-card>
|
||||
<a-card title="联系方式" class="info-card">
|
||||
<a-descriptions :column="{ xs: 1, sm: 2, md: 3 }">
|
||||
<a-descriptions-item label="手机号码">{{ userInfo.phone || '未设置' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="电子邮箱">{{ userInfo.email || '未设置' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="家庭电话">{{ userInfo.homeTel || '未设置' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="办公电话">{{ userInfo.officeTel || '未设置' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="紧急联系人">{{ userInfo.emergencyContact || '未设置' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="紧急联系电话">{{ userInfo.emergencyPhone || '未设置' }}</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</a-card>
|
||||
<a-card title="教育背景" class="info-card">
|
||||
<a-descriptions :column="{ xs: 1, sm: 2, md: 3 }">
|
||||
<a-descriptions-item label="文化程度">{{ userInfo.cultureLevel || '未设置' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="政治面貌">{{ userInfo.politicalOutlook || '未设置' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="毕业院校">{{ userInfo.college || '未设置' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="学历">{{ userInfo.education || '未设置' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="学制">{{ userInfo.eduLength || '未设置' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="学位">{{ userInfo.degree || '未设置' }}</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</a-card>
|
||||
<a-card title="登录信息" class="info-card">
|
||||
<a-descriptions :column="{ xs: 1, sm: 2, md: 3 }">
|
||||
<a-descriptions-item label="最近登录IP">{{ userInfo.latestLoginIp }}</a-descriptions-item>
|
||||
<a-descriptions-item label="最近登录地址">{{ userInfo.latestLoginAddress }}</a-descriptions-item>
|
||||
<a-descriptions-item label="最近登录时间">{{ userInfo.latestLoginTime }}</a-descriptions-item>
|
||||
<a-descriptions-item label="最近登录设备">{{ userInfo.latestLoginDevice }}</a-descriptions-item>
|
||||
<a-descriptions-item label="上次登录IP">{{ userInfo.lastLoginIp }}</a-descriptions-item>
|
||||
<a-descriptions-item label="上次登录地址">{{ userInfo.lastLoginAddress }}</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</a-card>
|
||||
</div>
|
||||
</a-layout-content>
|
||||
<a-layout-footer :style="footerStyle">Footer</a-layout-footer>
|
||||
</a-layout>
|
||||
</template>
|
||||
|
||||
<style scoped lang="less">
|
||||
.user-center {
|
||||
padding: 10px 300px;
|
||||
background: #f0f2f5;
|
||||
.header-card {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.info-card {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.user-header {
|
||||
display: flex;
|
||||
|
||||
position: relative;
|
||||
.logout-btn {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -8,6 +8,20 @@
|
|||
:showPagination="false"
|
||||
bordered
|
||||
>
|
||||
<template #headerCell="{ title, column }">
|
||||
<template v-if="column.dataIndex === 'whetherRequired'">
|
||||
<a-tooltip>
|
||||
<template #title> 非增改字段不可选择必填 </template>
|
||||
<question-circle-outlined /> {{ title }}
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'whetherUnique'">
|
||||
<a-tooltip>
|
||||
<template #title> 非必填字段不可选择唯一 </template>
|
||||
<question-circle-outlined /> {{ title }}
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.dataIndex === 'fieldRemark'">
|
||||
<a-input v-model:value="record.fieldRemark" />
|
||||
|
@ -49,14 +63,24 @@
|
|||
<a-checkbox v-model:checked="record.whetherRetract" :disabled="!record.whetherTable" />
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'whetherAddUpdate'">
|
||||
<a-checkbox v-model:checked="record.whetherAddUpdate" :disabled="toFieldEstimate(record)" />
|
||||
<a-checkbox
|
||||
v-model:checked="record.whetherAddUpdate"
|
||||
@change="whetherAddUpdateChange(record)"
|
||||
:disabled="toFieldEstimate(record)" />
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'whetherRequired'">
|
||||
<a-checkbox
|
||||
v-model:checked="record.whetherRequired"
|
||||
@change="whetherRequiredChange(record)"
|
||||
:disabled="toFieldEstimate(record) || !record.whetherAddUpdate"
|
||||
/>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'whetherUnique'">
|
||||
<a-checkbox
|
||||
v-model:checked="record.whetherUnique"
|
||||
:disabled="toFieldEstimate(record) || !record.whetherAddUpdate || !record.whetherRequired"
|
||||
/>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'queryWhether'">
|
||||
<a-switch v-model:checked="record.queryWhether" :disabled="toQueryWhetherDisabled(record)" />
|
||||
</template>
|
||||
|
@ -146,6 +170,12 @@
|
|||
dataIndex: 'whetherRequired',
|
||||
width: 80
|
||||
},
|
||||
{
|
||||
title: '唯一',
|
||||
align: 'center',
|
||||
dataIndex: 'whetherUnique',
|
||||
width: 80
|
||||
},
|
||||
{
|
||||
title: '查询',
|
||||
align: 'center',
|
||||
|
@ -389,6 +419,19 @@
|
|||
record.queryType = null
|
||||
}
|
||||
}
|
||||
// 是否增改选择触发
|
||||
const whetherAddUpdateChange = (element) => {
|
||||
if (!element.checked) {
|
||||
element.whetherRequired = false;
|
||||
element.whetherUnique = false;
|
||||
}
|
||||
}
|
||||
// 是否必填选择触发
|
||||
const whetherRequiredChange = (element) => {
|
||||
if (!element.checked) {
|
||||
element.whetherUnique = false;
|
||||
}
|
||||
}
|
||||
// 查询条件是否可用
|
||||
const toQueryWhetherDisabled = (record) => {
|
||||
// 去掉了列表显示、图片上传、文件上传是不让生成搜索的
|
||||
|
|
|
@ -101,17 +101,37 @@
|
|||
<a-button type="primary" @click="iconSelector.showIconModal(formData.icon)">选择</a-button>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="是否可见:" name="visible">
|
||||
<a-radio-group v-model:value="formData.visible" button-style="solid" :options="visibleOptions" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="排序:" name="sortCode">
|
||||
<a-input-number class="xn-wd" v-model:value="formData.sortCode" :max="100" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-collapse ghost>
|
||||
<a-collapse-panel key="def" header="展开更多">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="8">
|
||||
<a-form-item label="是否可见:" name="visible">
|
||||
<a-radio-group optionType="button" v-model:value="formData.visible" :options="visibleOptions" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8" v-if="formData.menuType !== 'CATALOG'">
|
||||
<a-form-item label="是否缓存:" name="keepLive">
|
||||
<a-radio-group optionType="button" v-model:value="formData.keepLive" :options="keepLiveOptions" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8" v-if="formData.menuType !== 'CATALOG'">
|
||||
<a-form-item label="布局可见:" name="displayLayout">
|
||||
<a-radio-group
|
||||
optionType="button"
|
||||
v-model:value="formData.displayLayout"
|
||||
:options="displayLayoutOptions"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-collapse-panel>
|
||||
</a-collapse>
|
||||
</a-form>
|
||||
<template #footer>
|
||||
<a-button class="xn-mr8" @click="onClose">关闭</a-button>
|
||||
|
@ -152,10 +172,18 @@
|
|||
if (!record.visible) {
|
||||
formData.value.visible = 'TRUE'
|
||||
}
|
||||
if (!record.keepLive) {
|
||||
formData.value.keepLive = 'YES'
|
||||
}
|
||||
if (!record.displayLayout) {
|
||||
formData.value.displayLayout = 'YES'
|
||||
}
|
||||
} else {
|
||||
formData.value = {
|
||||
menuType: 'MENU',
|
||||
visible: 'TRUE',
|
||||
keepLive: 'YES',
|
||||
displayLayout: 'YES',
|
||||
sortCode: 99
|
||||
}
|
||||
formData.value = Object.assign(formData.value, record)
|
||||
|
@ -211,11 +239,15 @@
|
|||
name: [required('请输入组件中name属性')],
|
||||
module: [required('请选择模块')],
|
||||
component: [required('请输入组件地址'), rules.initialNotBackslashChart],
|
||||
visible: [required('请选择是否可见')]
|
||||
visible: [required('请选择是否可见')],
|
||||
keepLive: [required('请选择标签页下是否缓存')],
|
||||
displayLayout: [required('请选择布局是否可见')]
|
||||
}
|
||||
|
||||
const categoryOptions = tool.dictList('MENU_TYPE')
|
||||
const visibleOptions = tool.dictList('MENU_VISIBLE')
|
||||
const keepLiveOptions = tool.dictList('COMMON_WHETHER')
|
||||
const displayLayoutOptions = tool.dictList('COMMON_WHETHER')
|
||||
// 验证并提交数据
|
||||
const onSubmit = () => {
|
||||
formRef.value
|
||||
|
@ -263,3 +295,11 @@
|
|||
onOpen
|
||||
})
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
:deep(.ant-collapse-header) {
|
||||
padding: 0 !important;
|
||||
}
|
||||
:deep(.ant-collapse-content-box) {
|
||||
padding: 15px 0 !important;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -58,6 +58,10 @@
|
|||
</template>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.dataIndex === 'title'">
|
||||
<component :is="record.icon" />
|
||||
{{ record.title }}
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'path'">
|
||||
<span v-if="record.menuType === 'MENU'">{{ record.path }}</span>
|
||||
<span v-else>-</span>
|
||||
|
@ -66,9 +70,6 @@
|
|||
<span v-if="record.menuType === 'MENU'">{{ record.component }}</span>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'icon'">
|
||||
<component :is="record.icon" />
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'menuType'">
|
||||
<a-tag v-if="record.menuType === 'CATALOG'" color="cyan">
|
||||
{{ $TOOL.dictTypeData('MENU_TYPE', record.menuType) }}
|
||||
|
@ -147,12 +148,9 @@
|
|||
const columns = [
|
||||
{
|
||||
title: '显示名称',
|
||||
dataIndex: 'title'
|
||||
},
|
||||
{
|
||||
title: '图标',
|
||||
dataIndex: 'icon',
|
||||
width: 100
|
||||
dataIndex: 'title',
|
||||
ellipsis: true,
|
||||
width: 300
|
||||
},
|
||||
{
|
||||
title: '类型',
|
||||
|
@ -161,15 +159,11 @@
|
|||
},
|
||||
{
|
||||
title: '路由地址',
|
||||
dataIndex: 'path',
|
||||
ellipsis: true,
|
||||
width: 220
|
||||
dataIndex: 'path'
|
||||
},
|
||||
{
|
||||
title: '组件',
|
||||
dataIndex: 'component',
|
||||
ellipsis: true,
|
||||
width: 220
|
||||
dataIndex: 'component'
|
||||
},
|
||||
{
|
||||
title: '是否可见',
|
||||
|
|
|
@ -13,9 +13,6 @@
|
|||
<a-form-item label="姓名:" name="name">
|
||||
<a-input v-model:value="formData.name" placeholder="请输入姓名" allow-clear />
|
||||
</a-form-item>
|
||||
<a-form-item label="手机:" name="phone">
|
||||
<a-input v-model:value="formData.phone" placeholder="请输入手机" allow-clear />
|
||||
</a-form-item>
|
||||
<a-form-item label="昵称:" name="nickname">
|
||||
<a-input v-model:value="formData.nickname" placeholder="请输入昵称" allow-clear />
|
||||
</a-form-item>
|
||||
|
@ -25,10 +22,6 @@
|
|||
<a-form-item label="生日:" name="birthday">
|
||||
<a-date-picker v-model:value="formData.birthday" value-format="YYYY-MM-DD" class="xn-wd" />
|
||||
</a-form-item>
|
||||
<a-form-item label="邮箱:" name="email">
|
||||
<a-input v-model:value="formData.email" placeholder="请输入邮箱" allow-clear />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item :wrapper-col="{ ...layout.wrapperCol, offset: 4 }">
|
||||
<a-button type="primary" :loading="submitLoading" @click="onSubmit">保存基本信息</a-button>
|
||||
</a-form-item>
|
||||
|
@ -36,7 +29,7 @@
|
|||
</template>
|
||||
|
||||
<script setup name="AccountBasic">
|
||||
import { required } from '@/utils/formRules'
|
||||
import { required, rules } from '@/utils/formRules'
|
||||
import userCenterApi from '@/api/sys/userCenterApi'
|
||||
import tool from '@/utils/tool'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
|
|
|
@ -13,6 +13,9 @@
|
|||
<qq-outlined v-if="item.type === 'qq'" class="bind-icon" :style="{ color: '#1677FF' }" />
|
||||
<wechat-outlined v-if="item.type === 'weChat'" class="bind-icon" :style="{ color: '#1AAD19' }" />
|
||||
<alipay-circle-outlined v-if="item.type === 'AliPay'" class="bind-icon" :style="{ color: '#178bf5' }" />
|
||||
<mail-outlined v-if="item.type === 'email'" class="bind-icon" :style="{ color: '#fcab43' }" />
|
||||
<mobile-outlined v-if="item.type === 'phone'" class="bind-icon" :style="{ color: '#43a0fc' }" />
|
||||
<verified-outlined v-if="item.type === 'password'" class="bind-icon" :style="{ color: '#a059e8' }" />
|
||||
<GiteeIcon v-if="item.type === 'Gitee'" class="bind-icon xn-wd40" />
|
||||
</template>
|
||||
</a-list-item-meta>
|
||||
|
@ -23,23 +26,56 @@
|
|||
</template>
|
||||
</a-list>
|
||||
<updatePassword ref="updatePasswordRef" />
|
||||
<bind-phone ref="bindPhoneRef" />
|
||||
<bind-email ref="bindEmailRef" />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { message } from 'ant-design-vue'
|
||||
import UpdatePassword from './bindForm/updatePassword.vue'
|
||||
import BindPhone from '@/views/sys/user/userTab/bindForm/bindPhone.vue'
|
||||
import BindEmail from '@/views/sys/user/userTab/bindForm/bindEmail.vue'
|
||||
// 按需导入图标组件
|
||||
import QqOutlined from '@ant-design/icons-vue/QqOutlined'
|
||||
import WechatOutlined from '@ant-design/icons-vue/WechatOutlined'
|
||||
import AlipayCircleOutlined from '@ant-design/icons-vue/AlipayCircleOutlined'
|
||||
import MailOutlined from '@ant-design/icons-vue/MailOutlined'
|
||||
import MobileOutlined from '@ant-design/icons-vue/MobileOutlined'
|
||||
import VerifiedOutlined from '@ant-design/icons-vue/VerifiedOutlined'
|
||||
import { globalStore } from '@/store'
|
||||
|
||||
const updatePasswordRef = ref()
|
||||
const bindPhoneRef = ref()
|
||||
const bindEmailRef = ref()
|
||||
const store = globalStore()
|
||||
const userInfo = computed(() => {
|
||||
if (store.userInfo) {
|
||||
return store.userInfo
|
||||
} else {
|
||||
return {
|
||||
phone: '',
|
||||
name: '',
|
||||
email: ''
|
||||
}
|
||||
}
|
||||
})
|
||||
// 获取绑定的情况
|
||||
const data = [
|
||||
{ title: '密码强度', description: '当前密码强度', value: '弱', type: 'password', bindStatus: 0 },
|
||||
/*{ title: '密保手机', description: '已绑定手机', value: '138****8293', type: 'phone', bindStatus: 1 },
|
||||
{ title: '密保邮箱', description: '未绑定邮箱', value: '', type: 'email', bindStatus: 0 },
|
||||
{ title: '实名状态', description: '未实名', value: '', type: 'userReal', bindStatus: 0 },*/
|
||||
{
|
||||
title: '邮箱',
|
||||
description: userInfo && userInfo.value.email ? '已绑定邮箱' : '未绑定邮箱',
|
||||
value: userInfo && userInfo.value.email ? userInfo.value.email : '',
|
||||
type: 'email',
|
||||
bindStatus: 0
|
||||
},
|
||||
{
|
||||
title: '手机号',
|
||||
description: userInfo && userInfo.value.phone ? '已绑定手机' : '未绑定手机',
|
||||
value: userInfo && userInfo.value.phone ? userInfo.value.phone : '',
|
||||
type: 'phone',
|
||||
bindStatus: 1
|
||||
},
|
||||
{ title: '绑定QQ', description: '未绑定', value: '', type: 'qq', bindStatus: 0 },
|
||||
{ title: '绑定微信', description: '未绑定', value: '', type: 'weChat', bindStatus: 0 },
|
||||
{ title: '绑定支付宝', description: '未绑定', value: '', type: 'AliPay', bindStatus: 0 },
|
||||
|
@ -48,10 +84,17 @@
|
|||
const bindCommon = (key) => {
|
||||
if (key === 'password') {
|
||||
updatePasswordRef.value.onOpen()
|
||||
} else if (key === 'phone') {
|
||||
bindPhoneRef.value.open(userInfo.value.phone)
|
||||
} else if (key === 'email') {
|
||||
bindEmailRef.value.open(userInfo.value.email)
|
||||
} else {
|
||||
message.info('开发中')
|
||||
}
|
||||
}
|
||||
onMounted(() => {
|
||||
// 获取绑定情况
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
|
|
@ -0,0 +1,188 @@
|
|||
<script setup name="bindEmail">
|
||||
import { ref, reactive } from 'vue'
|
||||
import userCenterApi from '@/api/sys/userCenterApi'
|
||||
import { message, Modal, Form, Input, Button } from 'ant-design-vue'
|
||||
import { required, rules } from '@/utils/formRules'
|
||||
|
||||
const visible = ref(false)
|
||||
const loading = ref(false)
|
||||
const captchaLoading = ref(false)
|
||||
const captchaImage = ref('')
|
||||
const captchaReqNo = ref('')
|
||||
const messageCodeReqNo = ref('')
|
||||
const bindEmail = ref('')
|
||||
let state = ref({
|
||||
time: 60,
|
||||
sendBtn: false
|
||||
})
|
||||
const formRef = ref()
|
||||
const formState = reactive({
|
||||
email: '',
|
||||
validCode: '',
|
||||
emailValidCode: '',
|
||||
validCodeReqNo: ''
|
||||
})
|
||||
// 打开绑定
|
||||
const open = (email) => {
|
||||
if (email) {
|
||||
formState.email = email
|
||||
bindEmail.value = email
|
||||
}
|
||||
visible.value = true
|
||||
getCaptcha()
|
||||
}
|
||||
// 获取图片验证码
|
||||
const getCaptcha = () => {
|
||||
captchaLoading.value = true
|
||||
try {
|
||||
userCenterApi.userGetPicCaptcha().then((data) => {
|
||||
captchaImage.value = data.validCodeBase64
|
||||
captchaReqNo.value = data.validCodeReqNo
|
||||
})
|
||||
} finally {
|
||||
captchaLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 获取邮箱验证码
|
||||
const getEmailValidCode = async () => {
|
||||
try {
|
||||
if (!formState.email) {
|
||||
message.error('请输入邮箱号')
|
||||
return
|
||||
}
|
||||
if (!formState.validCode) {
|
||||
message.error('请输入图片验证码')
|
||||
return
|
||||
}
|
||||
const hide = message.loading('验证码发送中..', 0)
|
||||
userCenterApi
|
||||
.userBindEmailGetEmailValidCode(
|
||||
{
|
||||
email: formState.email,
|
||||
validCode: formState.validCode,
|
||||
validCodeReqNo: captchaReqNo.value
|
||||
},
|
||||
bindEmail.value
|
||||
)
|
||||
.then((data) => {
|
||||
// 禁用发送按钮,并设置为倒计时
|
||||
state.value.sendBtn = true
|
||||
const interval = window.setInterval(() => {
|
||||
if (state.value.time-- <= 0) {
|
||||
state.value.time = 60
|
||||
state.value.sendBtn = false
|
||||
window.clearInterval(interval)
|
||||
}
|
||||
}, 1000)
|
||||
messageCodeReqNo.value = data
|
||||
message.success('验证码已发送到邮箱')
|
||||
})
|
||||
.catch(() => {
|
||||
// 清理掉已经输入的
|
||||
formState.validCode = ''
|
||||
formState.validCodeReqNo = ''
|
||||
messageCodeReqNo.value = ''
|
||||
getCaptcha()
|
||||
})
|
||||
.finally(() => {
|
||||
setTimeout(hide, 100)
|
||||
})
|
||||
} catch (error) {
|
||||
getCaptcha()
|
||||
}
|
||||
}
|
||||
|
||||
// 提交表单
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
await formRef.value.validate()
|
||||
loading.value = true
|
||||
userCenterApi
|
||||
.userBindEmail({
|
||||
email: formState.email,
|
||||
validCode: formState.emailValidCode,
|
||||
validCodeReqNo: messageCodeReqNo.value
|
||||
})
|
||||
.then(() => {
|
||||
message.success('绑定成功')
|
||||
visible.value = false
|
||||
})
|
||||
.catch(() => {
|
||||
formState.emailValidCode = ''
|
||||
messageCodeReqNo.value = ''
|
||||
})
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 表单验证规则
|
||||
const formRules = {
|
||||
email: [required('请输入邮箱号'), rules.email],
|
||||
validCode: required('请输入图片验证码'),
|
||||
emailValidCode: [{ required: true, message: '请输入邮箱验证码', trigger: 'blur' }]
|
||||
}
|
||||
const onClose = () => {
|
||||
bindEmail.value = ''
|
||||
visible.value = false
|
||||
formRef.value.resetFields()
|
||||
}
|
||||
defineExpose({
|
||||
open
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-modal
|
||||
v-model:open="visible"
|
||||
:title="bindEmail ? '修改邮箱' : '绑定邮箱'"
|
||||
:width="400"
|
||||
@ok="handleSubmit"
|
||||
:destroy-on-close="true"
|
||||
@cancel="onClose"
|
||||
>
|
||||
<Form ref="formRef" :model="formState" :rules="formRules" layout="vertical">
|
||||
<Form.Item name="email">
|
||||
<Input v-model:value="formState.email" placeholder="请输入邮箱号" />
|
||||
</Form.Item>
|
||||
<Form.Item name="validCode">
|
||||
<a-row :gutter="8">
|
||||
<a-col :span="16">
|
||||
<Input v-model:value="formState.validCode" placeholder="请输入图片验证码" />
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<img v-if="captchaImage" :src="captchaImage" class="captcha-image" @click="getCaptcha" />
|
||||
</a-col>
|
||||
</a-row>
|
||||
</Form.Item>
|
||||
<Form.Item name="emailValidCode">
|
||||
<a-row :gutter="8">
|
||||
<a-col :span="16">
|
||||
<Input v-model:value="formState.emailValidCode" placeholder="请输入邮箱验证码" />
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<Button :loading="captchaLoading" @click="getEmailValidCode" style="width: 100%" :disabled="state.sendBtn">
|
||||
{{ (!state.sendBtn && '获取验证码') || state.time + ' s' }}
|
||||
</Button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<style scoped lang="less">
|
||||
.captcha-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
.captcha-image {
|
||||
height: 32px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,190 @@
|
|||
<script setup name="bindPhone">
|
||||
import { ref, reactive } from 'vue'
|
||||
import userCenterApi from '@/api/sys/userCenterApi'
|
||||
import { message, Modal, Form, Input, Button } from 'ant-design-vue'
|
||||
import { required, rules } from '@/utils/formRules'
|
||||
|
||||
const visible = ref(false)
|
||||
const loading = ref(false)
|
||||
const captchaLoading = ref(false)
|
||||
const captchaImage = ref('')
|
||||
const captchaReqNo = ref('')
|
||||
const messageCodeReqNo = ref('')
|
||||
const bindPhone = ref('')
|
||||
let state = ref({
|
||||
time: 60,
|
||||
sendBtn: false
|
||||
})
|
||||
const formRef = ref()
|
||||
const formState = reactive({
|
||||
phone: '',
|
||||
validCode: '',
|
||||
phoneValidCode: '',
|
||||
validCodeReqNo: ''
|
||||
})
|
||||
// 打开绑定
|
||||
const open = (phone) => {
|
||||
if (phone) {
|
||||
formState.phone = phone
|
||||
bindPhone.value = phone
|
||||
}
|
||||
visible.value = true
|
||||
getCaptcha()
|
||||
}
|
||||
|
||||
// 获取图片验证码
|
||||
const getCaptcha = () => {
|
||||
captchaLoading.value = true
|
||||
try {
|
||||
userCenterApi.userGetPicCaptcha().then((data) => {
|
||||
captchaImage.value = data.validCodeBase64
|
||||
captchaReqNo.value = data.validCodeReqNo
|
||||
})
|
||||
} finally {
|
||||
captchaLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 获取手机验证码
|
||||
const getPhoneValidCode = async () => {
|
||||
try {
|
||||
if (!formState.phone) {
|
||||
message.error('请输入手机号')
|
||||
return
|
||||
}
|
||||
if (!formState.validCode) {
|
||||
message.error('请输入图片验证码')
|
||||
return
|
||||
}
|
||||
const hide = message.loading('验证码发送中..', 0)
|
||||
userCenterApi
|
||||
.userBindPhoneGetPhoneValidCode(
|
||||
{
|
||||
phone: formState.phone,
|
||||
validCode: formState.validCode,
|
||||
validCodeReqNo: captchaReqNo.value
|
||||
},
|
||||
bindPhone.value
|
||||
)
|
||||
.then((data) => {
|
||||
// 禁用发送按钮,并设置为倒计时
|
||||
state.value.sendBtn = true
|
||||
const interval = window.setInterval(() => {
|
||||
if (state.value.time-- <= 0) {
|
||||
state.value.time = 60
|
||||
state.value.sendBtn = false
|
||||
window.clearInterval(interval)
|
||||
}
|
||||
}, 1000)
|
||||
messageCodeReqNo.value = data
|
||||
message.success('验证码已发送到手机')
|
||||
})
|
||||
.catch(() => {
|
||||
// 清理掉已经输入的
|
||||
formState.validCode = ''
|
||||
formState.validCodeReqNo = ''
|
||||
messageCodeReqNo.value = ''
|
||||
getCaptcha()
|
||||
})
|
||||
.finally(() => {
|
||||
setTimeout(hide, 100)
|
||||
})
|
||||
} catch (error) {
|
||||
getCaptcha()
|
||||
}
|
||||
}
|
||||
|
||||
// 提交表单
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
await formRef.value.validate()
|
||||
loading.value = true
|
||||
userCenterApi
|
||||
.userBindPhone({
|
||||
phone: formState.phone,
|
||||
validCode: formState.phoneValidCode,
|
||||
validCodeReqNo: messageCodeReqNo.value
|
||||
})
|
||||
.then(() => {
|
||||
message.success('绑定成功')
|
||||
visible.value = false
|
||||
})
|
||||
.catch(() => {
|
||||
formState.phoneValidCode = ''
|
||||
messageCodeReqNo.value = ''
|
||||
})
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 表单验证规则
|
||||
const formRules = {
|
||||
phone: [required('请输入手机号'), rules.phone],
|
||||
validCode: required('请输入图片验证码'),
|
||||
phoneValidCode: [{ required: true, message: '请输入短信验证码', trigger: 'blur' }]
|
||||
}
|
||||
|
||||
const onClose = () => {
|
||||
visible.value = false
|
||||
formRef.value.resetFields()
|
||||
bindPhone.value = ''
|
||||
}
|
||||
defineExpose({
|
||||
open
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-modal
|
||||
v-model:open="visible"
|
||||
:title="bindPhone ? '修改手机' : '绑定手机'"
|
||||
:width="400"
|
||||
@ok="handleSubmit"
|
||||
:destroy-on-close="true"
|
||||
@cancel="onClose"
|
||||
>
|
||||
<Form ref="formRef" :model="formState" :rules="formRules" layout="vertical">
|
||||
<Form.Item name="phone">
|
||||
<Input v-model:value="formState.phone" placeholder="请输入手机号" />
|
||||
</Form.Item>
|
||||
<Form.Item name="validCode">
|
||||
<a-row :gutter="8">
|
||||
<a-col :span="16">
|
||||
<Input v-model:value="formState.validCode" placeholder="请输入图片验证码" />
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<img v-if="captchaImage" :src="captchaImage" class="captcha-image" @click="getCaptcha" />
|
||||
</a-col>
|
||||
</a-row>
|
||||
</Form.Item>
|
||||
<Form.Item name="phoneValidCode">
|
||||
<a-row :gutter="8">
|
||||
<a-col :span="16">
|
||||
<Input v-model:value="formState.phoneValidCode" placeholder="请输入短信验证码" />
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<Button :loading="captchaLoading" @click="getPhoneValidCode" style="width: 100%" :disabled="state.sendBtn">
|
||||
{{ (!state.sendBtn && '获取验证码') || state.time + ' s' }}
|
||||
</Button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<style scoped lang="less">
|
||||
.captcha-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
.captcha-image {
|
||||
height: 32px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div>
|
||||
<xn-form-container title="修改密码" :width="550" :visible="visible" :destroy-on-close="true" @close="onClose">
|
||||
<a-modal title="修改密码" :width="400" :open="visible" :destroy-on-close="true" @cancel="onClose">
|
||||
<a-skeleton active v-if="!updatePasswordConfig" />
|
||||
<a-form v-else ref="formRef" :model="formState" :rules="formRules" layout="vertical">
|
||||
<div v-if="updatePasswordConfig.SNOWY_SYS_DEFAULT_PASSWORD_UPDATE_VALID_TYPE_FOR_B === 'OLD'">
|
||||
|
@ -14,7 +14,6 @@
|
|||
</a-form-item>
|
||||
</div>
|
||||
<a-form-item
|
||||
label="手机号:"
|
||||
name="phone"
|
||||
has-feedback
|
||||
v-if="updatePasswordConfig.SNOWY_SYS_DEFAULT_PASSWORD_UPDATE_VALID_TYPE_FOR_B === 'PHONE'"
|
||||
|
@ -22,7 +21,6 @@
|
|||
<a-input v-model:value="formState.phone" placeholder="请输入手机号" allow-clear autocomplete="off" />
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="邮箱号:"
|
||||
name="email"
|
||||
has-feedback
|
||||
v-if="updatePasswordConfig.SNOWY_SYS_DEFAULT_PASSWORD_UPDATE_VALID_TYPE_FOR_B === 'EMAIL'"
|
||||
|
@ -51,7 +49,7 @@
|
|||
</a-col>
|
||||
</a-row>
|
||||
</a-form-item>
|
||||
<a-form-item label="新密码:" name="newPassword" has-feedback>
|
||||
<a-form-item name="newPassword" has-feedback>
|
||||
<a-input-password
|
||||
v-model:value="formState.newPassword"
|
||||
placeholder="请输入新密码"
|
||||
|
@ -64,7 +62,7 @@
|
|||
<a-button class="xn-mr8" @click="onClose">关闭</a-button>
|
||||
<a-button type="primary" :loading="submitLoading" @click="onSubmit">保存</a-button>
|
||||
</template>
|
||||
</xn-form-container>
|
||||
</a-modal>
|
||||
<a-modal
|
||||
v-model:open="captchaVisible"
|
||||
:width="400"
|
||||
|
|
|
@ -89,7 +89,7 @@ export default defineConfig(({ command, mode }) => {
|
|||
'ant-design-vendor': ['ant-design-vue', '@ant-design/icons-vue', 'lodash-es', 'axios', 'dayjs'],
|
||||
'echarts-vendor': ['echarts', 'echarts-stat'],
|
||||
'editor-vendor': ['@tinymce/tinymce-vue', 'tinymce'],
|
||||
'office-vendor': ['@vue-office/docx', '@vue-office/excel', '@vue-office/pdf']
|
||||
'office-vendor': ['@vue-office/docx', 'vue-pdf-embed', '@vue-office/excel']
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
Loading…
Reference in New Issue