feat: 登录页,及Log页接口对接

This commit is contained in:
Xu Zhimeng 2023-05-25 19:42:39 +08:00
parent ac8acd3d38
commit 2f8104d4c9
15 changed files with 385 additions and 222 deletions

5
public/index.html vendored
View File

@ -139,7 +139,7 @@
top: 0;
width: 51%;
height: 100%;
background: #49a9ee;
background: #051314;
/* Old browsers */
z-index: 1000;
-webkit-transform: translateX(0);
@ -249,9 +249,6 @@
<div id="loader"></div>
<div class="loader-section section-left"></div>
<div class="loader-section section-right"></div>
<div class="load_title">正在加载 核素监测数据自动处理与交互分析系统,请耐心等待
</div>
</div>
</div>

View File

@ -152,8 +152,8 @@ export function downFile(url,parameter, method='get'){
* @param parameter
* @returns {*}
*/
export function downloadFile(url, fileName, parameter) {
return downFile(url, parameter).then((data) => {
export function downloadFile(url, fileName, parameter, method) {
return downFile(url, parameter, method).then((data) => {
if (!data || data.size === 0) {
Vue.prototype['$message'].warning('文件下载失败')
return

Binary file not shown.

After

Width:  |  Height:  |  Size: 572 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@ -1,6 +1,6 @@
<template>
<div class="search-form">
<a-form-model ref="form" :model="formModel" v-bind="$attrs" @keyup.enter.native="onSearch">
<a-form-model ref="form" :colon="false" :model="formModel" v-bind="$attrs" @keyup.enter.native="onSearch">
<a-row v-if="type == 'single-line'" :gutter="20" style="margin-right: 0 !important;">
<a-col
class="search-form-item"
@ -101,6 +101,13 @@ export default {
}
.ant-form-item-label {
flex-shrink: 0;
margin-right: 10px;
label {
font-size: 16px;
&::after {
display: none;
}
}
}
.ant-form-item-control-wrapper {
width: 100%;

View File

@ -1,6 +1,6 @@
<template>
<div class="tree-with-line">
<a-tree v-bind="$attrs" :defaultExpandedKeys="defaultExpandedKeys" :selected-keys="selectedKeys" @select="onSelect">
<a-tree v-bind="$attrs" :selected-keys="selectedKeys" :expandedKeys="expandedKeys" @select="onSelect" @expand="onExpand">
<template slot="switcherIcon">
<div></div>
</template>
@ -13,7 +13,7 @@ export default {
selectedKeys: {
type: Array
},
defaultExpandedKeys: {
expandedKeys: {
type: Array
}
},
@ -23,6 +23,9 @@ export default {
this.$emit('update:selectedKeys', selectedKeys)
this.$emit('select')
}
},
onExpand(expandedKeys) {
this.$emit('update:expandedKeys', expandedKeys)
}
}
}

View File

@ -680,7 +680,8 @@
margin-right: 10px;
height: 30px;
line-height: 30px;
padding: 0 15px;
padding: 0 16px;
letter-spacing: 2px;
position: relative;
> a {
color: #c9f6f6;

View File

@ -23,7 +23,7 @@ export const JeecgListMixin = {
pageSizeOptions: ['10', '20', '30'],
showTotal: (total, range) => {
const { current, pageSize } = this.ipagination
return `${total} 条记录 第 ${current} / ${Math.ceil(total / pageSize)}`
return `Total ${total} items Page ${current} / ${Math.ceil(total / pageSize)}`
},
showQuickJumper: true,
showSizeChanger: true,
@ -228,12 +228,12 @@ export const JeecgListMixin = {
},
handleEdit: function (record) {
this.$refs.modalForm.edit(record);
this.$refs.modalForm.title = "编辑";
this.$refs.modalForm.title = "Edit";
this.$refs.modalForm.disableSubmit = false;
},
handleAdd: function () {
this.$refs.modalForm.add();
this.$refs.modalForm.title = "新增";
this.$refs.modalForm.title = "Add";
this.$refs.modalForm.disableSubmit = false;
},
handleTableChange(pagination, filters, sorter) {
@ -262,7 +262,7 @@ export const JeecgListMixin = {
},
handleDetail:function(record){
this.$refs.modalForm.edit(record);
this.$refs.modalForm.title="详情";
this.$refs.modalForm.title="Detail";
this.$refs.modalForm.disableSubmit = true;
},
/* 导出 */

View File

@ -17,9 +17,18 @@
</template>
<!-- 标题结束 -->
<!-- 内容 -->
<tree-with-line :treeData="treeData" :selected-keys.sync="selectedKeys" :expanded-keys.sync="expandedKeys" @select="onSelect"> </tree-with-line>
<a-spin :spinning="isGettingTreeData" style="min-height: 200px">
<tree-with-line
:treeData="treeData"
:selected-keys.sync="selectedKeys"
:expanded-keys.sync="expandedKeys"
@select="onSelect"
>
</tree-with-line>
</a-spin>
<!-- 内容结束 -->
</a-card>
<!-- 日志列表 -->
<div class="log-list">
<custom-table
size="middle"
@ -27,10 +36,10 @@
:columns="columns"
:list="dataSource"
:pagination="false"
:loading="loading"
:loading="isGettingTreeData || loading"
:can-select="false"
@change="handleTableChange"
:scroll="{ y: 'calc(100vh - 365px)' }"
:scroll="{ y: 'calc(100vh - 175px)' }"
>
<template slot="operate" slot-scope="{ record }">
<a-space :size="20">
@ -45,37 +54,42 @@
</template>
</custom-table>
</div>
<!-- 日志列表结束 -->
<custom-modal title="Log" v-model="visible" :show-footer="false">
这里是Log内容
<a-spin :spinning="isGettingDetail" style="min-height: 100px">
{{ logInfo }}
</a-spin>
</custom-modal>
</div>
</template>
<script>
import { JeecgListMixin } from '@/mixins/JeecgListMixin'
import TreeWithLine from '@/components/TreeWithLine/index.vue'
import { downloadFile, getAction } from '../../api/manage'
import TreeJson from './tree.json'
import { downloadFile, getAction, postAction } from '../../api/manage'
const columns = [
{
title: 'Name',
title: 'NAME',
align: 'center',
dataIndex: 'name'
width: 320,
dataIndex: 'fileName'
},
{
title: 'date',
title: 'DATE',
align: 'center',
dataIndex: 'date'
width: 200,
dataIndex: 'fileDate'
},
{
title: 'size',
title: 'SIZE',
align: 'center',
dataIndex: 'size'
width: 220,
dataIndex: 'fileSize'
},
{
title: 'Operate',
title: 'OPERATE',
align: 'center',
width: 200,
scopedSlots: {
customRender: 'operate'
}
@ -92,22 +106,17 @@ export default {
return {
disableMixinCreated: true,
url: {
list: '/test'
list: '/logManage/findFiles'
},
isGettingTreeData: false, //
treeData: [],
selectedKeys: [],
expandedKeys: [],
dataSource: [
{
id: 1,
name: 'DEX33_002-20220512_0723_S_FULL_9011.9.log',
date: '2022-05-13',
size: '105KB',
fileName: 'test.txt'
}
],
visible: false
dataSource: [],
visible: false,
isGettingDetail: false,
logInfo: ''
}
},
created() {
@ -117,17 +126,15 @@ export default {
async getTreeData() {
try {
this.isGettingTreeData = true
const res = await getAction('/logManage/findFtpFolders')
console.log('%c [ res ]-159', 'font-size:13px; background:pink; color:#bf2c9f;', res)
} catch (error) {
console.error(error)
this.$message.error('Get Tree Data Failed')
this.treeData = this.buildTreeData(TreeJson)
console.log('%c [ this.treeData ]-125', 'font-size:13px; background:pink; color:#bf2c9f;', this.treeData)
const res = await getAction('/logManage/findFtpFolders', { workPath: 'log' })
this.treeData = this.buildTreeData(res)
const firstNode = this.treeData[0]
this.selectedKeys = [firstNode.key]
this.expandedKeys = [firstNode.key]
this.onSelect()
} catch (error) {
console.error(error)
this.$message.error('Get Tree Data Failed')
} finally {
this.isGettingTreeData = false
}
@ -152,15 +159,60 @@ export default {
return tree
},
onSelect() {
this.queryParam.query = this.selectedKeys[0]
this.queryParam.path = this.selectedKeys[0]
this.loadData()
},
onOperate(record) {
console.log('%c [ ]-147', 'font-size:13px; background:pink; color:#bf2c9f;', record)
this.visible = true
loadData(arg) {
if (!this.url.list) {
this.$message.error('请设置url.list属性!')
return
}
// 1
if (arg === 1) {
this.ipagination.current = 1
}
this.onClearSelected()
var params = this.getQueryParams() //
this.loading = true
getAction(this.url.list, params)
.then(res => {
this.dataSource = res
})
.finally(() => {
this.loading = false
})
},
onDownload({ fileName }) {
downloadFile('/logManage/downloadFile', fileName, { fileName })
async onOperate({ fileName, filePath, fileSize }) {
if (parseFloat(fileSize) == 0) {
this.$message.warn('This Log Is Empty')
return
}
this.visible = true
const formData = new FormData()
formData.append('fileName', fileName)
formData.append('localPath', filePath)
try {
this.isGettingDetail = true
const res = await postAction('/logManage/downloadFile', formData)
this.logInfo = res
} catch (error) {
console.error(error)
} finally {
this.isGettingDetail = false
}
},
onDownload({ fileName, filePath, fileSize }) {
if (parseFloat(fileSize) == 0) {
this.$message.warn('This Log Is Empty')
return
}
const formData = new FormData()
formData.append('fileName', fileName)
formData.append('localPath', filePath)
downloadFile('/logManage/downloadFile', fileName, formData, 'post')
}
}
}

View File

@ -1,205 +1,308 @@
<template>
<div class="main">
<a-form-model class="user-layout-login" @keyup.enter.native="handleSubmit">
<a-tabs :activeKey="customActiveKey" :tabBarStyle="{ textAlign: 'center', borderBottom: 'unset' }" @change="handleTabClick">
<a-tab-pane key="tab1" tab="账号密码登录">
<login-account ref="alogin" @validateFail="validateFail" @success="requestSuccess" @fail="requestFailed"></login-account>
</a-tab-pane>
<div class="login-page">
<div class="login-form">
<div class="logo">
<img src="@/assets/images/login/logo.png" alt="" />
</div>
<a-form-model layout="vertical" ref="formRef" :model="model" :rules="rules" @keyup.enter.native="handleSubmit">
<a-form-model-item class="username" label="Username" prop="username">
<a-input v-model="model.username"></a-input>
</a-form-model-item>
<a-form-model-item class="password" label="Password" prop="password">
<a-input type="password" v-model="model.password"></a-input>
</a-form-model-item>
<!-- 验证码 -->
<a-tab-pane key="tab2" tab="手机号登录">
<login-phone ref="plogin" @validateFail="validateFail" @success="requestSuccess" @fail="requestFailed"></login-phone>
</a-tab-pane>
</a-tabs>
<a-form-model-item class="validate-code" label="Captcha" prop="inputCode">
<a-input v-model="model.inputCode"> </a-input>
<div class="validate-image">
<img v-if="requestCodeSuccess" :src="randCodeImage" @click="handleChangeCheckCode" />
<img v-else src="@/assets/checkcode.png" @click="handleChangeCheckCode" />
</div>
</a-form-model-item>
<a-form-model-item>
<a-checkbox @change="handleRememberMeChange" default-checked>自动登录</a-checkbox>
</a-form-model-item>
<a-form-item style="margin-top:24px">
<a-button size="large" type="primary" htmlType="submit" class="login-button" :loading="loginBtn" @click.stop.prevent="handleSubmit" :disabled="loginBtn">确定
</a-button>
</a-form-item>
</a-form-model>
<login-select-tenant ref="loginSelect" @success="loginSelectOk"></login-select-tenant>
<!-- 验证码结束 -->
<!-- 记住密码 -->
<div class="remember-pwd">
<a-checkbox v-model="model.rememberPwd">Remember password</a-checkbox>
</div>
<!-- 记住密码结束 -->
<a-button class="login-button" :loading="isSubmitting" @click="handleSubmit"> SIGN IN </a-button>
</a-form-model>
</div>
</div>
</template>
<script>
import Vue from 'vue'
import { ACCESS_TOKEN, ENCRYPTED_STRING } from '@/store/mutation-types'
import ThirdLogin from './third/ThirdLogin'
import LoginSelectTenant from './LoginSelectTenant'
import TwoStepCaptcha from '@/components/tools/TwoStepCaptcha'
import { getEncryptedString } from '@/utils/encryption/aesEncrypt'
import { ACCESS_TOKEN } from '@/store/mutation-types'
import { timeFix } from '@/utils/util'
import LoginAccount from './LoginAccount'
import LoginPhone from './LoginPhone'
import { getAction } from '@/api/manage'
import { mapActions } from 'vuex'
export default {
components: {
LoginSelectTenant,
TwoStepCaptcha,
ThirdLogin,
LoginAccount,
LoginPhone
},
data () {
return {
customActiveKey: 'tab1',
rememberMe: true,
loginBtn: false,
requiredTwoStepCaptcha: false,
stepCaptchaVisible: false,
encryptedString:{
key:"",
iv:"",
},
}
},
created() {
Vue.ls.remove(ACCESS_TOKEN)
this.getRouterData();
this.rememberMe = true
},
methods:{
handleTabClick(key){
this.customActiveKey = key
data() {
return {
isSubmitting: false,
randCodeImage: '',
requestCodeSuccess: false,
model: {
username: '',
password: '',
inputCode: '',
rememberPwd: true
},
handleRememberMeChange(e){
this.rememberMe = e.target.checked
rules: {
username: [{ required: true, message: 'Username Required' }],
password: [
{
required: true,
message: 'Password Required'
},
],
inputCode: [
{
required: true,
message: 'Captchar Required',
},
],
},
/**跳转到登录页面的参数-账号获取*/
getRouterData(){
this.$nextTick(() => {
let temp = this.$route.params.username || this.$route.query.username || ''
if (temp) {
this.$refs.alogin.acceptUsername(temp)
currdatetime: '',
}
},
created() {
Vue.ls.remove(ACCESS_TOKEN)
this.getRouterData()
this.handleChangeCheckCode()
},
methods: {
...mapActions(['Login']),
/**跳转到登录页面的参数-账号获取*/
getRouterData() {
this.$nextTick(() => {
let temp = this.$route.params.username || this.$route.query.username || ''
if (temp) {
this.$refs.alogin.acceptUsername(temp)
}
})
},
/**刷新验证码*/
handleChangeCheckCode() {
this.currdatetime = new Date().getTime()
this.model.inputCode = ''
getAction(`/sys/randomImage/${this.currdatetime}`)
.then((res) => {
if (res.success) {
this.randCodeImage = res.result
this.requestCodeSuccess = true
} else {
this.$message.error(res.message)
this.requestCodeSuccess = false
}
})
},
//
handleSubmit () {
this.loginBtn = true;
if (this.customActiveKey === 'tab1') {
// 使
this.$refs.alogin.handleLogin(this.rememberMe)
} else {
//
this.$refs.plogin.handleLogin(this.rememberMe)
}
},
//
validateFail(){
this.loginBtn = false;
},
//
requestSuccess(loginResult){
this.$refs.loginSelect.show(loginResult)
},
//
requestFailed (err) {
let description = ((err.response || {}).data || {}).message || err.message || "请求出现错误,请稍后再试"
this.$notification[ 'error' ]({
message: '登录失败',
description: description,
duration: 4,
});
//
if(this.customActiveKey === 'tab1' && description.indexOf('密码错误')>0){
this.$refs.alogin.handleChangeCheckCode()
}
this.loginBtn = false;
},
loginSelectOk(){
this.loginSuccess()
},
//
loginSuccess () {
this.$router.push({ path: "/station-operation" }).catch(()=>{
console.log('登录跳转首页出错,这个错误从哪里来的')
.catch(() => {
this.requestCodeSuccess = false
})
this.$notification.success({
message: '欢迎',
description: `${timeFix()},欢迎回来`,
});
},
},
stepCaptchaSuccess () {
this.loginSuccess()
},
stepCaptchaCancel () {
this.Logout().then(() => {
this.loginBtn = false
this.stepCaptchaVisible = false
})
},
//
getEncrypte(){
var encryptedString = Vue.ls.get(ENCRYPTED_STRING);
if(encryptedString == null){
getEncryptedString().then((data) => {
this.encryptedString = data
});
}else{
this.encryptedString = encryptedString;
}
//
async handleSubmit() {
if (this.isSubmitting) {
return
}
try {
await this.$refs.formRef.validate()
try {
const loginParams = {
username: this.model.username,
password: this.model.password,
captcha: this.model.inputCode,
checkKey: this.currdatetime,
}
this.isSubmitting = true
const res = await this.Login(loginParams)
this.loginSuccess()
} catch (error) {
if (error && error.code === 412) {
this.handleChangeCheckCode()
}
this.requestFailed(error)
}
} catch (error) {
console.error(error)
}
},
}
//
requestFailed(err) {
let description = ((err.response || {}).data || {}).message || err.message || 'Request Error'
this.$notification['error']({
message: 'Login Failed',
description: description,
duration: 4,
})
//
this.handleChangeCheckCode()
this.isSubmitting = false
},
}
//
loginSuccess() {
this.$router.push({ path: '/station-operation' }).catch(() => {
console.log('登录跳转首页出错,这个错误从哪里来的')
})
this.$notification.success({
message: 'Welcome',
description: `${timeFix()}Welcome Back`,
})
},
},
}
</script>
<style lang="less" scoped>
.user-layout-login {
label {
font-size: 14px;
}
.getCaptcha {
display: block;
width: 100%;
height: 40px;
.login-page {
height: 100%;
background: url(~@/assets/images/login/bg.jpg) center no-repeat;
background-size: cover;
.login-form {
position: fixed;
top: 50%;
right: 295px;
transform: translateY(-50%);
.logo {
img {
width: 578px;
height: 233px;
}
}
.forge-password {
font-size: 14px;
}
.ant-form {
margin-right: 24px;
float: right;
button.login-button {
padding: 0 15px;
font-size: 16px;
height: 40px;
width: 100%;
}
&-item {
margin-bottom: 20px !important;
::v-deep {
.ant-form-item-label {
line-height: 17px;
label {
font-size: 22px;
color: #fff;
padding-left: 6px;
&::before {
display: none;
}
}
}
.user-login-other {
text-align: left;
margin-top: 24px;
line-height: 22px;
.ant-form-explain {
position: absolute;
right: 0;
top: -36px;
font-size: 18px;
}
.item-icon {
font-size: 24px;
color: rgba(0,0,0,.2);
margin-left: 16px;
vertical-align: middle;
cursor: pointer;
transition: color .3s;
.ant-form-item-children {
width: 444px;
height: 60px;
.ant-input {
padding-left: 70px;
padding-right: 15px;
height: 100%;
font-size: 26px;
background-color: transparent !important;
border: none;
}
}
}
&:hover {
color: #1890ff;
&.username {
::v-deep {
.ant-form-item-children {
background: url(~@/assets/images/login/username-bg.png) center no-repeat;
background-size: contain;
}
}
}
&.password {
::v-deep {
.ant-form-item-children {
background: url(~@/assets/images/login/pwd-bg.png) center no-repeat;
background-size: contain;
}
}
}
&.validate-code {
width: 444px;
::v-deep {
.ant-form-item-children {
display: flex;
justify-content: space-between;
.ant-input {
width: 294px;
background: url(~@/assets/images/login/validate-bg.png) center no-repeat;
background-size: contain;
}
.validate-image {
width: 135px;
height: 60px;
img {
width: 100%;
height: 100%;
}
}
}
}
}
}
.register {
float: right;
.remember-pwd {
user-select: none;
margin-top: -20px;
margin-left: 5px;
.ant-checkbox-wrapper {
color: #6ebad0;
}
}
.login-button {
width: 444px;
height: 76px;
margin-top: 44px;
background: url(~@/assets/images/login/login-btn.png) center no-repeat;
border: 0;
padding-bottom: 15px;
::v-deep {
.anticon-loading {
font-size: 22px;
}
span {
font-family: MicrogrammaD-MediExte;
font-size: 22px;
font-weight: bold;
color: #fff;
text-shadow: 0px 2px 4px rgba(0, 0, 0, 0.61);
}
}
&:focus,
&.ant-btn-loading {
background-image: url(~@/assets/images/login/login-btn-active.png);
}
&::before,
&::after {
display: none;
}
}
}
}
</style>
<style>
.valid-error .ant-select-selection__placeholder{
color: #f5222d;
}
}
</style>