feat(xumu): 实现畜牧平台登录页面

- 新增登录页面组件 AppSign
- 添加用户名和密码登录功能
- 集成二维码登录
- 优化页面样式和布局
This commit is contained in:
aixianling
2024-12-17 17:55:58 +08:00
parent 536f579523
commit 0de94d76ee
8 changed files with 247 additions and 161 deletions

View File

@@ -1,4 +1,5 @@
VUE_APP_SCOPE=xumu VUE_APP_SCOPE=xumu
VUE_APP_API=http://192.168.1.87:19897 VUE_APP_API=http://192.168.1.251:19998
VUE_APP_IS_SIMPLE_SERVER=1 VUE_APP_IS_SIMPLE_SERVER=1
VUE_APP_PORT=12413 VUE_APP_PORT=12413
VUE_APP_OMS_ID=2cd70a15-a3cf-4b4d-9a22-0f3b3a888b08 # oms定制方案的ID

View File

@@ -11,7 +11,7 @@ instance.interceptors.request.use(config => {
config.url = "/ns" + config.url config.url = "/ns" + config.url
} }
if (process.env.VUE_APP_IS_SIMPLE_SERVER == 1) { if (process.env.VUE_APP_IS_SIMPLE_SERVER == 1) {
config.url = config.url.replace(/(app|auth|admin)\//, "api/") config.url = config.url.replace(/\/(app|auth|admin)\//, "/api/")
if (['xumu'].includes(process.env.VUE_APP_SCOPE)) { if (['xumu'].includes(process.env.VUE_APP_SCOPE)) {
config.url = config.url.replace("/api/", "/") config.url = config.url.replace("/api/", "/")
} }

View File

@@ -13,7 +13,8 @@
"predev": "node bin/scanApps.js", "predev": "node bin/scanApps.js",
"preoms": "dotenv -e .env.oms node bin/scanApps.js", "preoms": "dotenv -e .env.oms node bin/scanApps.js",
"prexumu": "dotenv -e .env.xumu node bin/scanApps.js", "prexumu": "dotenv -e .env.xumu node bin/scanApps.js",
"preview:xumu":"dotenv -e .env.xumu node bin/build.js&& vue-cli-service serve --mode xumu" "view:xumu": "vue-cli-service serve --mode xumu",
"preview:xumu": "dotenv -e .env.xumu node bin/build.js"
}, },
"dependencies": { "dependencies": {
"@amap/amap-jsapi-loader": "^1.0.1", "@amap/amap-jsapi-loader": "^1.0.1",

View File

@@ -0,0 +1,229 @@
<template>
<section class="AppSign">
<div class="left signLeftBg">
<el-row type="flex" align="middle">
<img class="AiIcon" v-if="/[\\\/]/.test(logo.icon)" :src="logo.icon" alt=""/>
<ai-icon v-else-if="logo.icon" type="logo" :icon="logo.icon"/>
<div v-if="logo.text" class="logoText mar-l8" v-text="logo.text"/>
</el-row>
<div class="signLeftContent">
<div class="titlePane">
<b v-text="system.name"/>
<div v-text="system.title"/>
</div>
</div>
</div>
<div class="right">
<div class="projectName mar-b48" :title="system.fullTitle">{{ system.fullTitle }}</div>
<el-card class="signBox">
<div class="choosePlatform flex column" v-if="!isAdmin&&!form.type">
<div class="font-20 mar-b40 t-center t-bold">请选择业务端后登陆</div>
<div class="selectPlatform fill">
<div class="flex center pointer" v-for="op in platforms" :key="op.dictValue"
v-text="op.dictName" @click="$set(form,'type',op.dictValue)"/>
</div>
<div class="mar-t32 font-12" style="align-self: flex-end">
未注册用户请扫码添加客服咨询
<i class="iconfont iconEwm" style="font-size: 20px"/>
</div>
</div>
<template v-else>
<div class="font-20 mar-b40 t-center t-bold"><i v-if="!isAdmin" class="el-icon-back" @click="form.type=null"/>账号登录</div>
<el-form :model="form" ref="form" :rules="rules">
<el-form-item prop="username">
<el-input v-model="form.username" placeholder="请输入您的账号"/>
</el-form-item>
<el-form-item prop="password">
<el-input type="password" v-model="form.password" placeholder="请输入您的密码" show-password/>
</el-form-item>
</el-form>
<div class="t-right font-12">忘记密码请联系客服处理</div>
<el-button type="primary" class="login-btn" @click="handleSignIn">登录</el-button>
</template>
</el-card>
<el-row type="flex" align="middle" class="bottomRecord">
<div v-if="system.recordDesc" v-text="system.recordDesc"/>
<el-link v-if="system.recordNo" v-text="system.recordNo" :href="system.recordURL"/>
<div v-if="system.ssl" v-html="system.ssl"/>
</el-row>
</div>
</section>
</template>
<script>
import {mapMutations, mapState} from 'vuex'
const rules = {
username: [{required: true, message: '请输入您的账号', trigger: 'blur'}],
password: [{required: true, message: '请输入您的密码', trigger: 'blur'}]
}
export default {
name: "AppSign",
label: "登录页",
data() {
return {
rules,
form: {}
}
},
computed: {
...mapState(['user', 'sys']),
instance: v => v.$request,
system: v => v.sys?.info || {
fullTitle: '畜牧养殖产业一体化平台'
},
logo: v => !!v.system.loginLogo ? {icon: v.system.loginLogo, text: v.system.loginLogoText} : {icon: v.system.logo, text: v.system.logoText},
isAdmin: v => v.$route.hash == "#sinoecare", //用来判断是否是管理员登录,
dict: v => v.$dict,
platforms: v => v.dict.getDict('roleType').filter(e => !['platform', 'other', 'service'].includes(e.dictValue))
},
created() {
this.dict.load("roleType")
if (this.user.token) {
this.handleGotoHome()
} else {
const {code} = this.$route.query
if (code) {
this.toLogin(code)
}
}
},
methods: {
...mapMutations(['setToken']),
login(data) {
if (data?.access_token) {
this.setToken([data.token_type, data.access_token].join(" "))
this.handleGotoHome()
}
},
handleGotoHome() {
this.$message.success("登录成功!")
if (this.$route.hash == "#dv") {
this.$router.push({name: "数据大屏入口", hash: "#dv"})
} else {
this.$router.push({name: "Home"})
}
},
handleSignIn() {
this.$refs.form.validate().then(() => {
const password = this.$encryption(this.form.password)
this.form.type = this.form.type || "platform"
this.$request.post("/oauth/token", null, {
auth: {username: 'villcloud', password: "villcloud"},
params: {grant_type: 'password', scope: 'server', ...this.form, password}
}).then(data => {
this.login(data)
})
})
}
},
}
</script>
<style lang="scss" scoped>
.AppSign {
display: flex;
box-sizing: border-box;
height: 100%;
.selectPlatform {
width: 100%;
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-gap: 10px;
& > div {
color: #fff;
background: $primaryBtnColor;
border-radius: 4px;
&:hover {
opacity: 0.8;
}
}
}
.signBox {
width: 500px;
min-height: 300px;
position: relative;
color: $primaryColor;
.choosePlatform {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
padding: inherit;
}
.el-icon-back {
position: absolute;
left: 20px;
top: 25px;
}
.login-btn {
font-size: 16px;
width: 100%;
height: 40px;
line-height: 40px;
margin: 16px auto;
padding: 0;
}
}
.AiIcon {
font-size: 40px;
height: 40px;
}
.logoText {
font-size: 20px;
}
:deep(.left) {
width: 480px;
flex-shrink: 0;
background-size: 100% 100%;
background-repeat: no-repeat;
padding-left: 64px;
padding-top: 40px;
box-sizing: border-box;
color: #fff;
font-size: 16px;
.iconcunwei1 {
font-size: 36px;
}
}
:deep(.right) {
flex: 1;
min-width: 0;
background-color: #F6F8FB;
background-repeat: no-repeat;
background-position: calc(100% - 80px) 0, calc(100% - 40px) 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.bottomRecord {
font-size: 12px;
color: #999;
gap: 16px;
position: fixed;
bottom: 20px;
.el-link {
font-size: inherit;
}
}
}
}
</style>

View File

@@ -1,155 +0,0 @@
<template>
<section class="AppSign">
<div class="left signLeftBg">
<el-row type="flex" align="middle">
<img class="AiIcon" v-if="/[\\\/]/.test(logo.icon)" :src="logo.icon" alt="" />
<ai-icon v-else type="logo" :icon="logo.icon" />
<div v-if="logo.text" class="logoText mar-l8" v-text="logo.text" />
</el-row>
<div class="signLeftContent">
<div class="titlePane">
<b v-text="system.name" />
<div v-text="system.title" />
</div>
<div class="subTitle" v-for="(t, i) in subTitles" :key="i" v-html="t" />
</div>
</div>
<div class="right">
<div class="projectName mar-b48" :title="system.fullTitle">{{ system.fullTitle }}</div>
<ai-sign isSignIn @login="login" :instance="instance" visible
:showScanLogin="system.edition == 'standard' || !system.edition" />
<el-row type="flex" align="middle" class="bottomRecord">
<div v-if="system.recordDesc" v-text="system.recordDesc" />
<el-link v-if="system.recordNo" v-text="system.recordNo" :href="system.recordURL" />
<div v-if="system.ssl" v-html="system.ssl" />
</el-row>
</div>
</section>
</template>
<script>
import { mapMutations, mapState } from 'vuex'
export default {
name: "AppSign",
computed: {
...mapState(['user', 'sys']),
instance: v => v.$request,
system: v => v.sys?.info || {
fullTitle: '武汉中卫慧通科技有限公司'
},
subTitles() {
let list = [
"构建全域数字大脑,助力政府科学决策",
"全域统一应用入口,移动办公高效协同",
"直接触达居民微信,政民互动“零距离”"
]
return (typeof this.system.desc == "object" ? this.system.desc : JSON.parse(this.system.desc || null)) || list
},
logo: v => !!v.system.loginLogo ? { icon: v.system.loginLogo, text: v.system.loginLogoText } : { icon: v.system.logo, text: v.system.logoText },
isDev: () => process.env.NODE_ENV == "development",
isAdmin: v => v.$route.hash == "#sinoecare" //用来判断是否是管理员登录
},
created() {
if (this.user.token) {
this.handleGotoHome()
} else {
const { code } = this.$route.query
if (code) {
this.toLogin(code)
}
}
},
methods: {
...mapMutations(['setToken']),
login(data) {
if (data?.access_token) {
this.setToken([data.token_type, data.access_token].join(" "))
this.handleGotoHome()
}
},
handleGotoHome() {
this.$message.success("登录成功!")
if (this.$route.hash == "#dv") {
this.$router.push({ name: "数据大屏入口", hash: "#dv" })
} else {
this.$router.push({ name: "Home" })
}
},
toLogin(code) {
this.instance.post(`/auth/wechatcp-qr/token`, {
code: code,
type: 'cpuser'
}, {
auth: {
username: 'villcloud',
password: "villcloud"
},
params: {
grant_type: 'password',
scope: 'server'
}
}).then(this.login)
},
},
}
</script>
<style lang="scss" scoped>
.AppSign {
display: flex;
box-sizing: border-box;
height: 100%;
.AiIcon {
font-size: 40px;
height: 40px;
}
.logoText {
font-size: 20px;
}
:deep(.left) {
width: 480px;
flex-shrink: 0;
background-size: 100% 100%;
background-repeat: no-repeat;
padding-left: 64px;
padding-top: 40px;
box-sizing: border-box;
color: #fff;
font-size: 16px;
.iconcunwei1 {
font-size: 36px;
}
}
:deep(.right) {
flex: 1;
min-width: 0;
background-color: #F6F8FB;
background-image: url("../assets/loginRightTop.png"), url("../assets/loginRightBottom.png");
background-repeat: no-repeat;
background-position: calc(100% - 80px) 0, calc(100% - 40px) 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.bottomRecord {
font-size: 12px;
color: #999;
gap: 16px;
position: fixed;
bottom: 20px;
.el-link {
font-size: inherit;
}
}
}
}
</style>

View File

@@ -11,7 +11,7 @@ instance.defaults.baseURL = baseURLs[process.env.NODE_ENV]
instance.interceptors.request.use(config => { instance.interceptors.request.use(config => {
config.timeout = 300000 config.timeout = 300000
if (extra?.isSingleService) { if (extra?.isSingleService) {
config.url = config.url.replace(/(app|auth|admin)\//, "api/") config.url = config.url.replace(/\/(app|auth|admin)\//, "/api/")
} }
if (config.url.startsWith("/node")) { if (config.url.startsWith("/node")) {
config.baseURL = "/ns" config.baseURL = "/ns"

View File

@@ -9,8 +9,9 @@ import CryptoJs from "crypto-js";
export const $encryption = (params, c = 0) => { export const $encryption = (params, c = 0) => {
if (CryptoJs) { if (CryptoJs) {
const key = "thanks,villcloud" const key = "thanks,villcloud"
let password = typeof params == "object" ? params.password : params
let iv = CryptoJs.enc.Latin1.parse(key) let iv = CryptoJs.enc.Latin1.parse(key)
let encrypted = CryptoJs.AES.encrypt(params.password, iv, { let encrypted = CryptoJs.AES.encrypt(password, iv, {
iv, iv,
mode: CryptoJs.mode.CBC, mode: CryptoJs.mode.CBC,
padding: CryptoJs.pad.ZeroPadding padding: CryptoJs.pad.ZeroPadding

View File

@@ -80,12 +80,21 @@ $--font-path: '~element-ui/lib/theme-chalk/fonts';
/** /**
不换行文本 不换行文本
*/ */
.nowrap-text { .nowrap-text, .t-nowrap {
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
} }
.t-center {
text-align: center;
}
.t-bold{
font-weight: bold;
}
.t-right{
text-align: right;
}
/** /**
表头式样 表头式样
*/ */