ui库和web端产品库合并版本(还需修复细节)

This commit is contained in:
2022-11-29 18:27:14 +08:00
parent 5e4bd93238
commit 8bf6c57668
151 changed files with 28267 additions and 49 deletions

View File

@@ -0,0 +1,231 @@
<template>
<section class="ai-sign" v-if="visible">
<el-row type="flex">
<el-card :class="{'sign-in-hidden':!signInState}">
<ai-sign-in
v-if="!sassLogin"
ref="signIn"
@signIn="SignIn"
:showPhoneLogin="showPhoneLogin"
:showScanLogin="showScanLogin"
:instance="instance"
@qrlogin="onQRlogin"
@sendMessage="sendMsg"
@resetPwd="signInState=false"/>
<h2 class="scan-title sassLogin" v-else>手机扫码安全登录</h2>
<template v-if="tpLoginList.length>0">
<div class="divider" v-text="`第三方账号登录`"/>
<el-row class="tpLogin" type="flex" align="middle">
<el-tooltip v-for="(tp,i) in tpLoginList" :key="i" :content="tp.label" placement="top">
<ai-icon class="item" v-bind="tp" @click.native.stop="tp.click"/>
</el-tooltip>
</el-row>
</template>
</el-card>
<el-card class="change-password" :class="{'change-password-hidden':signInState}">
<ai-change-pwd @signUp="ChangePwd" :instance="instance" @toLogin="signInState=true"/>
</el-card>
</el-row>
</section>
</template>
<script>
import AiSignIn from "./signIn";
import AiChangePwd from "./changePwd";
export default {
name: "AiSign",
components: {AiChangePwd, AiSignIn},
props: {
/**
* 是否展示
*/
visible: Boolean,
/**
* 显示手机号登陆模块
*/
showPhoneLogin: {default: true},
/**
* 显示扫码登录
*/
showScanLogin: {default: true},
/**
* 接口工具类
*/
instance: {required: true, type: Function},
/**
* 请求接口类
* login :手机密码登录;mobile:手机验证码登录;
* qr:手机扫码登录 qrStatus:扫码状态
* changePwd:更换密码 verification:验证码验证
*/
action: Object,
/**
* 第三方登录列表: wxwork:企业微信
*/
tps: {default: () => []},
/**
* 是否是sass版本登录
*/
sassLogin: Boolean
},
provide() {
return {
actions: this.actions,
sassLogin: this.sassLogin
}
},
computed: {
actions() {
let action = {
login: "/auth/oauth/token",
mobile: "/auth/mobile/token",
qr: "/auth/qr-con/token",
qrStatus: "/admin/user/getQrStatus",
qrKey: "/admin/user/getQrKey",
code: "/auth/token/code",
failTimes: "/admin/user/getloginFailNum",
changePwd: "/admin/user/update-pwd",
verification: "/admin/user/checkPhone"
}
return {...action, ...this.action}
},
tpLoginList() {
let list = [
{
label: "企微登录", icon: "iconqiwei", key: 'wxwork', type: 'svg', click: () => {
location.href = `https://open.work.weixin.qq.com/wwopen/sso/3rd_qrConnect?appid=ww596787bb70f08288&redirect_uri=${encodeURIComponent(location.href)}&usertype=member`
}
}
]
return list.filter(e => this.tps.includes(e.key)) || []
}
},
data() {
return {
signInState: true
}
},
methods: {
ChangePwd(params) {
this.instance.post(this.actions.changePwd, null, {
params: {
phone: params.account,
code: params.verifictCode,
newPwd: params.password
}
}).then(res => {
if (res) {
this.$message.success("修改成功!")
this.signInState = true
}
})
},
SignIn(params, url) {
let password = this.$encryption(params)
this.instance.post(url, null, {
auth: {
username: 'villcloud',
password: "villcloud"
},
params: {
grant_type: 'password',
scope: 'server',
...params,
password
}
}).then(data => {
if (!data) {
this.$refs.signIn.handleInput()
} else {
this.$emit("login", data)
}
}).catch(() => {
this.$refs.signIn.handleInput()
})
},
getNowDate() {
const date = new Date()
return date.getFullYear() + '-' + (date.getMonth() + 1) + '-' + date.getDate()
},
sendMsg(phone) {
this.instance.post(this.actions.verification, null, {
auth: {},
params: {phone}
}).then(() => this.$message.success("发送成功!"))
},
onQRlogin(data) {
this.$emit('login', data)
}
}
}
</script>
<style lang="scss" scoped>
.ai-sign {
width: 400px;
display: inline-block;
transition: 2s;
.el-row {
width: 200%;
}
.sign-in-hidden {
transform: translateX(-100%);
opacity: 0;
}
.change-password {
transform: translateX(-100%);
}
.change-password-hidden {
transform: translateX(100%);
opacity: 0;
}
.el-card {
width: 400px;
border-radius: 8px;
}
.divider {
font-size: 14px;
color: #999;
text-align: center;
margin: 12px 0;
&:before, &:after {
content: "——";
margin: 0 11px;
color: #ddd;
}
}
.tpLogin {
width: 100%;
justify-content: center;
flex-wrap: wrap;
.item {
cursor: pointer;
margin: 8px;
height: 48px;
width: 48px;
font-size: 28px;
border-radius: 50%;
line-height: 48px;
text-align: center;
}
}
::v-deep.scan-title {
color: #333333;
font-size: 16px;
&.sassLogin {
margin-bottom: 150px;
}
}
}
</style>

View File

@@ -0,0 +1,185 @@
<template>
<el-form class="changePwd" ref="form" :model="signUpForm" :rules="rules">
<div class="backLogin" type="text" @click="$emit('toLogin')"><i class="el-icon-caret-left"/> 重置密码</div>
<el-form-item prop="account" style="white-space: nowrap">
<el-input placeholder="请输入您的手机号" v-model="signUpForm.account" clearable>
<i slot="prefix" class="iconfont iconPhone"></i>
</el-input>
</el-form-item>
<el-form-item prop="verifictCode">
<el-input class="code" placeholder="请输入短信验证码" maxlength="6" v-model="signUpForm.verifictCode" clearable>
<el-button slot="suffix" style="padding-right: 14px" type="text" @click="getVerifictCode"
:disabled="verify.disable">{{ verify.btnLabel }}
</el-button>
<i slot="prefix" class="iconfont iconMessage"></i>
</el-input>
</el-form-item>
<el-form-item prop="password">
<el-input class="form-item-width" type="password" show-password placeholder="密码8-16个字符区分大小写"
v-model="signUpForm.password" clearable>
<i slot="prefix" class="iconfont iconPassword"></i>
</el-input>
</el-form-item>
<el-form-item prop="confirmPassword">
<el-input class="form-item-width" type="password" show-password placeholder="再次输入密码"
v-model="signUpForm.confirmPassword" clearable>
<i slot="prefix" class="iconfont iconPassword"></i>
</el-input>
</el-form-item>
<el-form-item>
<el-button class="form-item-width" type="primary" @click.native="submitChangePwd">重置密码</el-button>
</el-form-item>
</el-form>
</template>
<script>
export default {
name: "changePwd",
inject: ["actions"],
props: {
instance: Function
},
computed: {
rules() {
const _ = this
return {
account: [
{required: true, message: "请输入您的手机号", trigger: 'blur'},
{pattern: /^1[345789]\d{9}$/, message: "手机号格式有误", trigger: 'blur'},
],
verifictCode: [{
trigger: 'blur', validator(r, v, cb) {
if (/^\d{6}$/.test(v)) {
cb()
} else {
cb(new Error("请输入6位验证码"))
}
}
}],
password: [
{required: true, message: "请输入新密码", trigger: 'blur'},
{
pattern: /^(?=.*\d)(?=.*[a-zA-Z])(?=.*[~!@#$%^&*,.?_-])[\da-zA-Z~!@#$%^&*,.?_-]{8,16}$/,
message: "数字和字母及特殊字符(~!@#$%^&*,.?_-)组合,长度8到16位"
}
],
confirmPassword: [
{required: true, message: "请再次输入新密码", trigger: 'blur'},
{
trigger: 'blur', validator(r, v, cb) {
if (v == _.signUpForm.password) {
cb()
} else {
cb(new Error("两次填写的密码不一致"))
}
}
}
],
}
}
},
data() {
return {
signUpForm: {
account: "",
password: "",
confirmPassword: "",
verifictCode: "",
},
verify: {
disable: false,
timer: 0,
btnLabel: "获取验证码",
},
}
},
methods: {
getVerifictCode() {
this.$refs.form.validateField("account", v => {
if (!v) {
this.verify.disable = true
this.verify.timer = 60
this.verify.btnLabel = this.verify.timer + "秒"
const timer = setInterval(() => {
if (this.verify.timer === 0) {
this.verify.btnLabel = "获取验证码"
this.verify.disable = false
clearInterval(timer)
} else {
this.verify.timer--
this.verify.btnLabel = this.verify.timer + "秒"
}
}, 1000)
this.instance.post(this.actions.verification, null, {
auth: {},
params: {
phone: this.signUpForm.account
}
}).then(() => this.$message.success("短信发送成功!"))
}
})
},
submitChangePwd() {
this.$refs.form.validate(v => {
if (v) {
this.$emit("signUp", this.signUpForm)
}
})
}
}
}
</script>
<style lang="scss" scoped>
.changePwd {
// margin: 0 20px;
::v-deep .code {
.el-button {
width: auto !important;
}
}
::v-deep.backLogin {
display: flex;
align-items: center;
font-weight: bold;
font-size: 16px;
height: 40px;
color: $primaryColor;
cursor: pointer;
width: 100px;
margin-bottom: 46px;
&:hover {
opacity: 0.8;
}
.el-icon-caret-left {
padding: 0;
font-size: 22px;
}
}
.el-button {
width: 100%;
}
::v-deep.el-form-item {
margin-bottom: 24px;
i {
padding-left: 7px;
padding-right: 7px;
}
}
.form-item-width {
font-size: 16px;
width: 100%;
height: 48px;
line-height: 48px;
padding: 0;
}
}
</style>

View File

@@ -0,0 +1,132 @@
<template>
<div class="s-canvas">
<canvas id="s-canvas" :width="contentWidth" :height="contentHeight"></canvas>
</div>
</template>
<script>
export default {
name: 'SIdentify',
props: {
identifyCode: {
type: String,
default: '1234'
},
fontSizeMin: {
type: Number,
default: 16
},
fontSizeMax: {
type: Number,
default: 40
},
backgroundColorMin: {
type: Number,
default: 180
},
backgroundColorMax: {
type: Number,
default: 240
},
colorMin: {
type: Number,
default: 50
},
colorMax: {
type: Number,
default: 160
},
lineColorMin: {
type: Number,
default: 40
},
lineColorMax: {
type: Number,
default: 180
},
dotColorMin: {
type: Number,
default: 0
},
dotColorMax: {
type: Number,
default: 255
},
contentWidth: {
type: Number,
default: 160
},
contentHeight: {
type: Number,
default: 40
}
},
methods: {
// 生成一个随机数
randomNum (min, max) {
return Math.floor(Math.random() * (max - min) + min)
},
// 生成一个随机的颜色
randomColor (min, max) {
let r = this.randomNum(min, max)
let g = this.randomNum(min, max)
let b = this.randomNum(min, max)
return 'rgb(' + r + ',' + g + ',' + b + ')'
},
drawPic () {
let canvas = document.getElementById('s-canvas')
let ctx = canvas.getContext('2d')
ctx.textBaseline = 'bottom'
// 绘制背景
ctx.fillStyle = this.randomColor(this.backgroundColorMin, this.backgroundColorMax)
ctx.fillRect(0, 0, this.contentWidth, this.contentHeight)
// 绘制文字
for (let i = 0; i < this.identifyCode.length; i++) {
this.drawText(ctx, this.identifyCode[i], i)
}
this.drawLine(ctx)
this.drawDot(ctx)
},
drawText (ctx, txt, i) {
ctx.fillStyle = this.randomColor(this.colorMin, this.colorMax)
ctx.font = this.randomNum(this.fontSizeMin, this.fontSizeMax) + 'px SimHei'
let x = (i + 1) * (this.contentWidth / (this.identifyCode.length + 1))
let y = this.randomNum(this.fontSizeMax, this.contentHeight - 5)
var deg = this.randomNum(-45, 45)
// 修改坐标原点和旋转角度
ctx.translate(x, y)
ctx.rotate(deg * Math.PI / 180)
ctx.fillText(txt, 0, 0)
// 恢复坐标原点和旋转角度
ctx.rotate(-deg * Math.PI / 180)
ctx.translate(-x, -y)
},
drawLine (ctx) {
// 绘制干扰线
// for (let i = 0; i < 8; i++) {
// ctx.strokeStyle = this.randomColor(this.lineColorMin, this.lineColorMax)
// ctx.beginPath()
// ctx.moveTo(this.randomNum(0, this.contentWidth), this.randomNum(0, this.contentHeight))
// ctx.lineTo(this.randomNum(0, this.contentWidth), this.randomNum(0, this.contentHeight))
// ctx.stroke()
// }
},
drawDot (ctx) {
// 绘制干扰点
// for (let i = 0; i < 100; i++) {
// ctx.fillStyle = this.randomColor(0, 255)
// ctx.beginPath()
// ctx.arc(this.randomNum(0, this.contentWidth), this.randomNum(0, this.contentHeight), 1, 0, 2 * Math.PI)
// ctx.fill()
// }
}
},
watch: {
identifyCode () {
this.drawPic()
}
},
mounted () {
this.drawPic()
}
}
</script>

View File

@@ -0,0 +1,333 @@
<template>
<el-form ref="loginForm" :model="sign" class="signIn">
<el-form-item class="ai-sign__header">
<el-tabs v-model="currentWay" v-if="!isScan">
<el-tab-pane label="账号登录"></el-tab-pane>
<el-tab-pane label="短信登录" v-if="showPhoneLogin"></el-tab-pane>
</el-tabs>
<h2 class="scan-title" v-if="isScan">手机扫码安全登录</h2>
<div class="ai-scan" @click="changeLoginType" v-if="showScanLogin">
<div class="poptip-arrow">
<span>{{ tipContent }}</span>
<a/>
<em/>
</div>
<i class="iconfont" :class="[isScan ? 'iconAccount_Login' : 'iconQR_code']"></i>
</div>
</el-form-item>
<div class="qrcode" id="qrcode" v-show="isScan"/>
<template v-if="!isScan">
<el-form-item v-if="isAccountSignIn" prop="username"
:rules="[{required: true, message: '请输入您的手机号', trigger: 'change'}]">
<el-input v-model="sign.username" placeholder="请输入您的手机号" clearable
@keyup.enter.native="$refs.validInput.focus()">
<i slot="prefix" class="iconfont iconProlife"></i>
</el-input>
</el-form-item>
<el-form-item v-else prop="mobile" :rules="[{required: true, message: '请输入您的手机号', trigger: 'change'}]">
<el-input v-model="sign.mobile" placeholder="请输入您的手机号" clearable
@keyup.enter.native="$refs.validInput.focus()">
<i slot="prefix" class="iconfont iconPhone"></i>
</el-input>
</el-form-item>
<el-form-item v-if="isAccountSignIn" prop="password"
:rules="[{required: true, message: '请输入您的密码', trigger: 'change'}]">
<el-input ref="validInput" type="password" placeholder="请输入您的密码" show-password
v-model="sign.password" @keyup.enter.native="handleClick" clearable>
<i slot="prefix" class="iconfont iconPassword"></i>
</el-input>
</el-form-item>
<el-form-item v-else prop="code" :rules="[{required: true, message: '请输入短信验证码', trigger: 'change'}]">
<el-input ref="validInput" placeholder="请输入短信验证码" v-model="sign.code" clearable
@keyup.enter.native="handleClick">
<i slot="prefix" class="iconfont iconMessage"></i>
<el-button slot="suffix" style="padding-right: 14px" type="text" :disabled="verCodeTimer>0"
@click="sendMessage">
{{ verCodeTimer ? verCodeTimer + "" : "获取验证码" }}
</el-button>
</el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" class="login-btn" @click="handleClick">登录</el-button>
<div class="reset-password-row">
<el-button type="text" class="reset-password" @click="$emit('resetPwd')">忘记密码?</el-button>
</div>
</el-form-item>
</template>
</el-form>
</template>
<script>
import VueQr from 'vue-qr'
import identify from './identify'
export default {
name: "signIn",
inject: ["actions"],
props: {
instance: Function,
showPhoneLogin: {
type: Boolean,
default: true
},
showScanLogin: {
type: Boolean,
default: true
},
},
data() {
return {
currentWay: 0,
verCodeTimer: 0,
sign: {
username: "",
password: "",
mobile: "",
code: "",
randomStr: ""
},
code: {
src: "/code",
value: "",
len: 4,
type: "image"
},
isScan: true,
validFocus: false,
QRkey: '',
timer: null,
isInvalid: false,
isLoginSuccess: false,
identifyCodes: '1234567890',
identifyCode: '',
errorNum: 0,
appid: '',
agentid: ''
}
},
components: {
VueQr,
identify
},
computed: {
isAccountSignIn() {
return this.currentWay == 0
},
tipContent() {
return this.isScan ? '返回账号登录' : '扫码登录更安全'
}
},
watch: {
currentWay() {
this.sign = {
username: "",
password: "",
mobile: "",
code: "",
randomStr: ""
}
}
},
created() {
localStorage.removeItem('ui-token')
localStorage.removeItem('token')
localStorage.removeItem('vuex')
if (this.showScanLogin) {
this.getLoginInfo()
this.isScan = true
} else {
this.isScan = false
}
},
methods: {
changeLoginType() {
if (!this.isScan) {
this.isScan = true
this.getQRkey()
} else {
this.isScan = false
clearInterval(this.timer)
}
},
getLoginInfo() {
this.instance.post(`/app/wxcp/portal/getCpParams`).then(res => {
if (res && res.data) {
this.agentid = res.data.agentid
this.appid = res.data.corpid
this.getQRkey()
}
})
},
getErrorNum(phone) {
return new Promise((resolve, reject) => {
this.instance.post(this.actions.failTimes, null, {
params: {phone}
}).then(res => {
resolve(Number(res.data.status) || 0)
}).catch(() => {
reject('error')
})
})
},
handleInput() {
},
handleClick() {
this.$refs.loginForm.validate((valid) => {
if (valid) {
if (this.currentWay == 0) {
this.$emit("signIn", this.sign, this.actions.login)
} else {
this.$emit("signIn", this.sign, this.actions.mobile)
}
}
});
},
sendMessage() {
if (this.currentWay == 1) {
if (this.sign.mobile) {
this.$emit("sendMessage", this.sign.mobile)
this.verCodeTimer = 60
const timer = setInterval(() => {
this.verCodeTimer--
if (this.verCodeTimer == 0) {
clearInterval(timer)
}
}, 1000)
} else {
this.$message.error("请输入手机号!")
}
}
},
getQRkey() {
this.$injectLib('https://rescdn.qqmail.com/node/ww/wwopenmng/js/sso/wwLogin-1.0.0.js', () => {
window.WwLogin({
id: 'qrcode',
appid: this.appid,
agentid: this.agentid,
redirect_uri: `${location.origin}/login`,
state: '',
href: 'https://cdn.cunwuyun.cn/dvcp/wechat-login.css',
})
})
}
}
}
</script>
<style lang="scss" scoped>
.signIn {
.imgcode {
display: flex;
justify-content: space-between;
.el-input {
margin-right: 8px;
}
.imgcode-img {
border-radius: 4px;
}
}
.scan-success {
padding-top: 64px;
text-align: center;
i {
font-size: 64px;
color: #2EA222;
}
h2 {
margin: 16px 0;
color: #333;
font-size: 16px;
}
p {
text-align: center;
color: #666666;
font-size: 12px;
}
}
.qrcode {
}
.qrlogin-bottom {
margin-top: 16px;
text-align: center;
font-size: 12px;
span {
color: #666;
}
a {
color: $primaryColor;
}
p {
margin-top: 8px;
text-align: center;
}
}
.ai-sign__header {
position: relative;
}
::v-deep.el-tabs__nav-wrap::after {
display: none;
}
::v-deep.el-tabs__header {
padding: 0;
}
::v-deep.el-tabs__item {
padding-right: 0;
}
::v-deep.el-form-item {
margin-bottom: 32px;
.el-tabs__item {
font-size: 16px;
}
.login-btn {
font-size: 16px;
width: 100%;
height: 48px;
line-height: 48px;
margin: 16px auto;
padding: 0;
}
i {
padding-left: 7px;
padding-right: 7px;
}
}
.reset-password-row {
text-align: right;
}
.qrcode {
height: 296px;
text-align: center;
overflow: hidden;
}
}
</style>