提交 0dc2f7ca 编写于 作者: ibizdev's avatar ibizdev

ShineKOT 发布系统代码 [后台服务,演示应用]

上级 30265302
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1616652640756" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="651" width="48" height="48" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><style type="text/css"></style></defs><path d="M512 2C230.2 2 2 230.2 2 512s228.2 510 510 510 510-228.2 510-510S793.3 2 512 2z m235.9 442c-1 4.6-3.6 10.8-7.2 19.1l-0.5 0.5c-21.6 45.8-77.3 135.5-77.3 135.5l-0.5-0.5-16.5 28.3h78.8L574.3 826.8l34-136h-61.8l21.6-90.2c-17.5 4.1-38.1 9.8-62.3 18 0 0-33 19.1-94.8-37.1 0 0-41.7-37.1-17.5-45.8 10.3-4.1 50-8.8 81.4-12.9 42.2-5.7 68.5-8.8 68.5-8.8s-130.3 2.1-161.2-3.1c-30.9-4.6-70.1-56.7-78.3-102 0 0-12.9-24.7 27.8-12.9 40.2 11.8 209.2 45.8 209.2 45.8S321.4 375 307 358.5c-14.4-16.5-42.8-89.6-39.2-134.5 0 0 1.5-11.3 12.9-8.2 0 0 161.8 74.2 272.5 114.4C664.5 371.4 760.8 392 747.9 444z" fill="#3296FA" p-id="652"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1616652700601" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="973" xmlns:xlink="http://www.w3.org/1999/xlink" width="48" height="48"><defs><style type="text/css"></style></defs><path d="M512 2c281.7 0 510 228.3 510 510s-228.3 510-510 510S2 793.7 2 512 230.3 2 512 2z m159.8 680.3c-4 3.9-4 10.2-0.2 14.1 0.4 0.5 0.9 0.9 1.5 1.2 22.1 20.4 36.4 47.9 40.4 77.7 6.2 22.4 29.7 35.7 52.4 29.5 22.5-5.9 35.9-28.9 30-51.3 0-0.1-0.1-0.2-0.1-0.4-4.7-16.8-19.3-29.1-36.7-30.8-28-5.1-53.5-19.2-72.8-40.1-4.1-3.8-10.5-3.8-14.5 0.1z m-225.7-483c-76.4 8.3-145.8 40.6-195.6 91-19.4 19.4-35.5 41.8-47.8 66.3-37.7 74.9-31.4 164.4 16.5 233.2 13.5 20.2 35.8 45.4 56.1 63.3l-9.2 71.3-1 3c-0.3 0.9-0.3 1.9-0.4 2.8l-0.2 2.3 0.2 2.3c1.2 12.7 12.5 22 25.2 20.9 3.5-0.3 6.8-1.4 9.8-3.1h0.4l1.4-1 22-10.8 65.5-32.5c31.1 8.8 63.4 13.2 95.8 13 40 0.1 79.8-6.7 117.5-20.2-18.8-6-30.9-24.3-29-44-39 12.4-80.2 16.4-120.8 11.9l-6.5-0.9c-14.7-1.9-29.2-4.9-43.4-8.9-7.8-2.4-16.1-1.5-23.3 2.4l-1.8 0.9-53.9 31.3-2.3 1.4c-1.3 0.7-1.9 1-2.6 1-2-0.1-3.5-1.8-3.4-3.8l2-8.2 2.4-8.9 3.9-14.7 4.5-16.4c3-9.2-0.3-19.2-8.2-24.8-21.1-15.5-39.5-34.4-54.4-56-37.9-54.2-43-124.8-13.3-183.8 9.9-19.5 22.9-37.4 38.4-52.9 40.9-41.6 98.3-68.1 161.9-74.9 22-2.4 44.2-2.4 66.2 0 63.2 7.2 120.4 34 161.1 75.4 15.4 15.7 28.2 33.6 37.9 53.2 12.5 24.8 19 52.3 19.1 80.1 0 2.9-0.3 5.8-0.4 8.6 16.8-10.2 38.4-7.7 52.4 6.1l1.9 2.3c3.3-41.2-4.8-82.5-23.3-119.5-12.1-24.5-28.1-46.8-47.3-66.3-52.5-52-121.4-84.3-194.9-91.7-26.4-3.4-52.9-3.5-79.1-0.7z m418.2 405.4c-7.2 1.9-13.8 5.7-19.2 11h-0.1c-6.9 6.8-11.2 15.7-12.2 25.3-5.2 27.8-19.5 53.1-40.5 72-4 3.8-4.1 10.1-0.3 14.1l0.1 0.1c4 4 10.5 4.1 14.6 0.1 0.5-0.5 0.9-0.9 1.2-1.5 20.9-21.9 48.7-36 78.7-39.8 22.8-6.1 36.2-29.2 30.1-51.6-6.2-22.5-29.6-35.8-52.4-29.7z m-160.4-42l-0.7 0.7c-20.9 22.7-49.2 37.3-79.9 41.2-22.6 5.9-36.2 28.7-30.2 51.1 1.9 7.3 5.9 14 11.3 19.3 16.7 16.4 43.7 16.4 60.4-0.1 6.8-6.8 11.1-15.7 12.2-25.2 5.3-27.8 19.6-53.1 40.7-72 4.1-3.7 4.5-10 0.9-14.1l-0.1-0.1c-4-4.2-10.4-4.5-14.6-0.8z m39.6-76.6c-7.1 1.9-13.6 5.7-18.7 10.8-16.4 16.2-16.6 42.6-0.4 59l0.5 0.5c6.9 6.8 15.9 11 25.5 12.1 28 5.1 53.6 19.1 72.9 39.9 4 4 10.4 4 14.4 0.1 4-3.8 4.1-10.1 0.3-14.1-0.5-0.5-1.1-1-1.7-1.4-22.1-20.4-36.4-47.8-40.4-77.6-6.1-22.4-29.6-35.5-52.4-29.3z" fill="#0082EF" p-id="974"></path></svg>
\ No newline at end of file
......@@ -115,6 +115,16 @@ import AppStandardContainer from './components/layout-element/container/app-stan
import AppTabPanel from './components/layout-element/container/app-tab-panel/app-tab-panel.vue';
import AppTabPage from './components/layout-element/container/app-tab-page/app-tab-page.vue';
import AppNavPos from './components/layout-element/container/app-nav-pos/app-nav-pos.vue';
import AppPreSetText from './components/layout-element/text/app-preset-text/app-preset-text.vue';
import AppPreSetCaption from './components/layout-element/text/app-preset-caption/app-preset-caption.vue';
import AppPreSetTitle from './components/layout-element/text/app-preset-title/app-preset-title.vue';
import AppLoginInput from './components/layout-element/login/app-login-input/app-login-input.vue';
import AppLoginButton from './components/layout-element/login/app-login-button/app-login-button.vue';
import AppLoginOrg from './components/layout-element/login/app-login-org/app-login-org.vue';
import AppLoginMessage from './components/layout-element/login/app-login-message/app-login-message.vue';
import AppLoginThird from './components/layout-element/login/app-login-third/app-login-third.vue';
import AppLoginCaptcha from './components/layout-element/login/app-login-captcha/app-login-captcha.vue';
import AppLoginNoteVerify from './components/layout-element/login/app-login-note-verify/app-login-note-verify.vue';
// 全局挂载UI实体服务注册中心
window['uiServiceRegister'] = uiServiceRegister;
// 全局挂载实体权限服务注册中心
......@@ -244,5 +254,15 @@ export const AppComponents = {
v.component('app-tab-panel', AppTabPanel);
v.component('app-tab-page', AppTabPage);
v.component('app-nav-pos', AppNavPos);
v.component('app-preset-text',AppPreSetText);
v.component('app-preset-caption',AppPreSetCaption);
v.component('app-preset-title',AppPreSetTitle);
v.component('app-login-input',AppLoginInput);
v.component('app-login-button',AppLoginButton);
v.component('app-login-org',AppLoginOrg);
v.component('app-login-message',AppLoginMessage);
v.component('app-login-third',AppLoginThird);
v.component('app-login-captcha',AppLoginCaptcha);
v.component('app-login-note-verify',AppLoginNoteVerify);
},
};
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1616652640756" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="651" width="48" height="48" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><style type="text/css"></style></defs><path d="M512 2C230.2 2 2 230.2 2 512s228.2 510 510 510 510-228.2 510-510S793.3 2 512 2z m235.9 442c-1 4.6-3.6 10.8-7.2 19.1l-0.5 0.5c-21.6 45.8-77.3 135.5-77.3 135.5l-0.5-0.5-16.5 28.3h78.8L574.3 826.8l34-136h-61.8l21.6-90.2c-17.5 4.1-38.1 9.8-62.3 18 0 0-33 19.1-94.8-37.1 0 0-41.7-37.1-17.5-45.8 10.3-4.1 50-8.8 81.4-12.9 42.2-5.7 68.5-8.8 68.5-8.8s-130.3 2.1-161.2-3.1c-30.9-4.6-70.1-56.7-78.3-102 0 0-12.9-24.7 27.8-12.9 40.2 11.8 209.2 45.8 209.2 45.8S321.4 375 307 358.5c-14.4-16.5-42.8-89.6-39.2-134.5 0 0 1.5-11.3 12.9-8.2 0 0 161.8 74.2 272.5 114.4C664.5 371.4 760.8 392 747.9 444z" fill="#3296FA" p-id="652"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1616652700601" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="973" xmlns:xlink="http://www.w3.org/1999/xlink" width="48" height="48"><defs><style type="text/css"></style></defs><path d="M512 2c281.7 0 510 228.3 510 510s-228.3 510-510 510S2 793.7 2 512 230.3 2 512 2z m159.8 680.3c-4 3.9-4 10.2-0.2 14.1 0.4 0.5 0.9 0.9 1.5 1.2 22.1 20.4 36.4 47.9 40.4 77.7 6.2 22.4 29.7 35.7 52.4 29.5 22.5-5.9 35.9-28.9 30-51.3 0-0.1-0.1-0.2-0.1-0.4-4.7-16.8-19.3-29.1-36.7-30.8-28-5.1-53.5-19.2-72.8-40.1-4.1-3.8-10.5-3.8-14.5 0.1z m-225.7-483c-76.4 8.3-145.8 40.6-195.6 91-19.4 19.4-35.5 41.8-47.8 66.3-37.7 74.9-31.4 164.4 16.5 233.2 13.5 20.2 35.8 45.4 56.1 63.3l-9.2 71.3-1 3c-0.3 0.9-0.3 1.9-0.4 2.8l-0.2 2.3 0.2 2.3c1.2 12.7 12.5 22 25.2 20.9 3.5-0.3 6.8-1.4 9.8-3.1h0.4l1.4-1 22-10.8 65.5-32.5c31.1 8.8 63.4 13.2 95.8 13 40 0.1 79.8-6.7 117.5-20.2-18.8-6-30.9-24.3-29-44-39 12.4-80.2 16.4-120.8 11.9l-6.5-0.9c-14.7-1.9-29.2-4.9-43.4-8.9-7.8-2.4-16.1-1.5-23.3 2.4l-1.8 0.9-53.9 31.3-2.3 1.4c-1.3 0.7-1.9 1-2.6 1-2-0.1-3.5-1.8-3.4-3.8l2-8.2 2.4-8.9 3.9-14.7 4.5-16.4c3-9.2-0.3-19.2-8.2-24.8-21.1-15.5-39.5-34.4-54.4-56-37.9-54.2-43-124.8-13.3-183.8 9.9-19.5 22.9-37.4 38.4-52.9 40.9-41.6 98.3-68.1 161.9-74.9 22-2.4 44.2-2.4 66.2 0 63.2 7.2 120.4 34 161.1 75.4 15.4 15.7 28.2 33.6 37.9 53.2 12.5 24.8 19 52.3 19.1 80.1 0 2.9-0.3 5.8-0.4 8.6 16.8-10.2 38.4-7.7 52.4 6.1l1.9 2.3c3.3-41.2-4.8-82.5-23.3-119.5-12.1-24.5-28.1-46.8-47.3-66.3-52.5-52-121.4-84.3-194.9-91.7-26.4-3.4-52.9-3.5-79.1-0.7z m418.2 405.4c-7.2 1.9-13.8 5.7-19.2 11h-0.1c-6.9 6.8-11.2 15.7-12.2 25.3-5.2 27.8-19.5 53.1-40.5 72-4 3.8-4.1 10.1-0.3 14.1l0.1 0.1c4 4 10.5 4.1 14.6 0.1 0.5-0.5 0.9-0.9 1.2-1.5 20.9-21.9 48.7-36 78.7-39.8 22.8-6.1 36.2-29.2 30.1-51.6-6.2-22.5-29.6-35.8-52.4-29.7z m-160.4-42l-0.7 0.7c-20.9 22.7-49.2 37.3-79.9 41.2-22.6 5.9-36.2 28.7-30.2 51.1 1.9 7.3 5.9 14 11.3 19.3 16.7 16.4 43.7 16.4 60.4-0.1 6.8-6.8 11.1-15.7 12.2-25.2 5.3-27.8 19.6-53.1 40.7-72 4.1-3.7 4.5-10 0.9-14.1l-0.1-0.1c-4-4.2-10.4-4.5-14.6-0.8z m39.6-76.6c-7.1 1.9-13.6 5.7-18.7 10.8-16.4 16.2-16.6 42.6-0.4 59l0.5 0.5c6.9 6.8 15.9 11 25.5 12.1 28 5.1 53.6 19.1 72.9 39.9 4 4 10.4 4 14.4 0.1 4-3.8 4.1-10.1 0.3-14.1-0.5-0.5-1.1-1-1.7-1.4-22.1-20.4-36.4-47.8-40.4-77.6-6.1-22.4-29.6-35.5-52.4-29.3z" fill="#0082EF" p-id="974"></path></svg>
\ No newline at end of file
.app-user-button {
width: 100%;
}
\ No newline at end of file
<template>
<div class="app-user-button">
<i-button @click="handleSubmit" :type="type">{{ caption }} </i-button>
</div>
</template>
<script lang="ts">
import { Vue, Component, Watch, Prop, Model } from "vue-property-decorator";
@Component({})
export default class AppUserId extends Vue {
@Prop() public predefinedType!: string;
@Prop() public type?: string;
@Prop() public caption!: string;
public handleSubmit() {
this.$emit('itemClick', this.predefinedType);
}
}
</script>
<style lang="less">
@import './app-login-button.less';
</style>
\ No newline at end of file
<template>
<div class="app-preset-auth">
<auth-puzzle-vcode :show="show" @success="onSuccess" @fail="onFail" />
<i-button ghost @click="executeOpen" long class="app-preset-auth-button"
>验证</i-button
>
</div>
</template>
<script lang="ts">
import { Vue, Component, Prop, Model } from "vue-property-decorator";
import AuthPuzzleVcode from "./vue-puzzle-code/vue-puzzle-code.vue";
@Component({
components: {
"auth-puzzle-vcode": AuthPuzzleVcode,
},
})
export default class AppPreSetAuth extends Vue {
/**
* 是否显示
*/
public show: boolean = false;
/**
* 打开
*/
public executeOpen() {
this.show = true;
}
/**
* 成功
*/
public onSuccess() {
this.show = false;
this.$emit("success");
}
/**
* 失败
*/
public onFail() {
this.$emit("fail");
}
}
</script>
<style lang='less'>
.app-preset-auth {
.app-preset-auth-button {
border: 1px solid #dcdee2;
border-radius: 4px;
color: #515a6e;
&:hover{
border: 1px solid #dcdee2;
color: #515a6e;
}
}
}
</style>
\ No newline at end of file
.vue-puzzle-vcode {
position: fixed;
top: 0;
left: 0;
bottom: 0;
right: 0;
background-color: rgba(0, 0, 0, 0.3);
z-index: 999;
opacity: 0;
pointer-events: none;
transition: opacity 200ms;
&.show_ {
opacity: 1;
pointer-events: auto;
}
}
.vue-auth-box_ {
position: absolute;
top: 40%;
left: 50%;
transform: translate(-50%, -50%);
padding: 20px;
background: #fff;
user-select: none;
border-radius: 3px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
.auth-body_ {
position: relative;
overflow: hidden;
border-radius: 3px;
.loading-box_ {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
background-color: rgba(0, 0, 0, 0.8);
z-index: 20;
opacity: 1;
transition: opacity 200ms;
display: flex;
align-items: center;
justify-content: center;
&.hide_ {
opacity: 0;
pointer-events: none;
.loading-gif_ {
span {
animation-play-state: paused;
}
}
}
.loading-gif_ {
flex: none;
height: 5px;
line-height: 0;
@keyframes load {
0% {
opacity: 1;
transform: scale(1.3);
}
100% {
opacity: 0.2;
transform: scale(0.3);
}
}
span {
display: inline-block;
width: 5px;
height: 100%;
margin-left: 2px;
border-radius: 50%;
background-color: #888;
animation: load 1.04s ease infinite;
&:nth-child(1) {
margin-left: 0;
}
&:nth-child(2) {
animation-delay: 0.13s;
}
&:nth-child(3) {
animation-delay: 0.26s;
}
&:nth-child(4) {
animation-delay: 0.39s;
}
&:nth-child(5) {
animation-delay: 0.52s;
}
}
}
}
.info-box_ {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 24px;
line-height: 24px;
text-align: center;
overflow: hidden;
font-size: 13px;
background-color: #83ce3f;
opacity: 0;
transform: translateY(24px);
transition: all 200ms;
color: #fff;
z-index: 10;
&.show {
opacity: 0.95;
transform: translateY(0);
}
&.fail {
background-color: #ce594b;
}
}
.auth-canvas2_ {
position: absolute;
top: 0;
left: 0;
width: 60px;
height: 100%;
z-index: 2;
}
.auth-canvas3_ {
position: absolute;
top: 0;
left: 0;
opacity: 0;
transition: opacity 600ms;
z-index: 3;
&.show {
opacity: 1;
}
}
.flash_ {
position: absolute;
top: 0;
left: 0;
width: 30px;
height: 100%;
background-color: rgba(255, 255, 255, 0.1);
z-index: 3;
&.show {
transition: transform 600ms;
}
}
.reset_ {
position: absolute;
top: 2px;
right: 2px;
width: 35px;
height: auto;
z-index: 12;
cursor: pointer;
transition: transform 200ms;
transform: rotate(0deg);
&:hover {
transform: rotate(-90deg);
}
}
}
.auth-control_ {
.range-box {
position: relative;
width: 100%;
background-color: #eef1f8;
margin-top: 20px;
border-radius: 3px;
box-shadow: 0 0 8px rgba(240, 240, 240, 0.6) inset;
.range-text {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 14px;
color: #b7bcd1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
text-align: center;
width: 100%;
}
.range-slider {
position: absolute;
height: 100%;
width: 50px;
background-color: rgba(106, 160, 255, 0.8);
border-radius: 3px;
.range-btn {
position: absolute;
display: flex;
align-items: center;
justify-content: center;
right: 0;
width: 50px;
height: 100%;
background-color: #fff;
border-radius: 3px;
box-shadow: 0 0 4px #ccc;
cursor: pointer;
& > div {
width: 0;
height: 40%;
transition: all 200ms;
&:nth-child(2) {
margin: 0 4px;
}
border: solid 1px #6aa0ff;
}
&:hover,
&.isDown {
& > div:first-child {
border: solid 4px transparent;
height: 0;
border-right-color: #6aa0ff;
}
& > div:nth-child(2) {
border-width: 3px;
height: 0;
border-radius: 3px;
margin: 0 6px;
border-right-color: #6aa0ff;
}
& > div:nth-child(3) {
border: solid 4px transparent;
height: 0;
border-left-color: #6aa0ff;
}
}
}
}
}
}
}
.vue-puzzle-overflow {
overflow: hidden !important;
}
\ No newline at end of file
<template>
<!-- 本体部分 -->
<div :id="id"
:class="['vue-puzzle-vcode', { show_: show }]"
@mousedown="onCloseMouseDown"
@mouseup="onCloseMouseUp"
@touchstart="onCloseMouseDown"
@touchend="onCloseMouseUp">
<div class="vue-auth-box_"
@mousedown.stop
@touchstart.stop>
<div class="auth-body_"
:style="`height: ${canvasHeight}px`">
<!-- 主图,有缺口 -->
<canvas ref="canvas1"
:width="canvasWidth"
:height="canvasHeight"
:style="`width:${canvasWidth}px;height:${canvasHeight}px`" />
<!-- 成功后显示的完整图 -->
<canvas ref="canvas3"
:class="['auth-canvas3_', { show: isSuccess }]"
:width="canvasWidth"
:height="canvasHeight"
:style="`width:${canvasWidth}px;height:${canvasHeight}px`" />
<!-- 小图 -->
<canvas :width="puzzleBaseSize"
class="auth-canvas2_"
:height="canvasHeight"
ref="canvas2"
:style="
`width:${puzzleBaseSize}px;height:${canvasHeight}px;transform:translateX(${styleWidth -
sliderBaseSize -
(puzzleBaseSize - sliderBaseSize) *
((styleWidth - sliderBaseSize) /
(canvasWidth - sliderBaseSize))}px)`
" />
<div :class="['loading-box_', { hide_: !loading }]">
<div class="loading-gif_">
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
</div>
</div>
<div :class="['info-box_', { show: infoBoxShow }, { fail: infoBoxFail }]">
{{ infoText }}
</div>
<div :class="['flash_', { show: isSuccess }]"
:style="
`transform: translateX(${
isSuccess
? `${canvasWidth + canvasHeight * 0.578}px`
: `-${canvasHeight * 0.578}px`
}) skew(-30deg, 0);`
"></div>
<img class="reset_"
@click="reset"
:src="resetSvg" />
</div>
<div class="auth-control_">
<div class="range-box"
:style="`height:${sliderBaseSize}px`">
<div class="range-text">{{ sliderText }}</div>
<div class="range-slider"
ref="range-slider"
:style="`width:${styleWidth}px`">
<div :class="['range-btn', { isDown: mouseDown }]"
:style="`width:${sliderBaseSize}px`"
@mousedown="onRangeMouseDown($event)"
@touchstart="onRangeMouseDown($event)">
<div></div>
<div></div>
<div></div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import resetSvg from "./reset.png";
export default {
/** 私有数据 **/
data() {
return {
mouseDown: false, // 鼠标是否在按钮上按下
startWidth: 50, // 鼠标点下去时父级的width
startX: 0, // 鼠标按下时的X
newX: 0, // 鼠标当前的偏移X
pinX: 0, // 拼图的起始X
pinY: 0, // 拼图的起始Y
loading: true, // 是否正在加在中,主要是等图片onload
isCanSlide: false, // 是否可以拉动滑动条
error: false, // 图片加在失败会出现这个,提示用户手动刷新
infoBoxShow: false, // 提示信息是否出现
infoText: "", // 提示等信息
infoBoxFail: false, // 是否验证失败
timer1: null, // setTimout1
closeDown: false, // 为了解决Mac上的click BUG
isSuccess: false, // 验证成功
resetSvg,
imgIndex: -1 // 用于自定义图片时不会随机到重复的图片
};
},
/** 父级参数 **/
props: {
id: { type: String },
canvasWidth: { type: Number, default: 310 }, // 主canvas的宽
canvasHeight: { type: Number, default: 160 }, // 主canvas的高
// 是否出现,由父级控制
show: { type: Boolean, default: false },
puzzleScale: { type: Number, default: 1 }, // 拼图块的大小缩放比例
sliderSize: { type: Number, default: 50 }, // 滑块的大小
range: { type: Number, default: 10 }, // 允许的偏差值
// 所有的背景图片
imgs: {
type: Array
},
successText: {
type: String,
default: "验证通过!"
},
failText: {
type: String,
default: "验证失败,请重试"
},
sliderText: {
type: String,
default: "拖动滑块完成拼图"
}
},
/** 生命周期 **/
mounted() {
document.body.appendChild(this.$el);
document.addEventListener("mousemove", this.onRangeMouseMove, false);
document.addEventListener("mouseup", this.onRangeMouseUp, false);
document.addEventListener("touchmove", this.onRangeMouseMove, {
passive: false
});
document.addEventListener("touchend", this.onRangeMouseUp, false);
if (this.show) {
document.body.classList.add("vue-puzzle-overflow");
}
this.reset();
},
beforeDestroy() {
clearTimeout(this.timer1);
document.body.removeChild(this.$el);
document.removeEventListener("mousemove", this.onRangeMouseMove, false);
document.removeEventListener("mouseup", this.onRangeMouseUp, false);
document.removeEventListener("touchmove", this.onRangeMouseMove, {
passive: false
});
document.removeEventListener("touchend", this.onRangeMouseUp, false);
},
/** 监听 **/
watch: {
show(newV) {
// 每次出现都应该重新初始化
if (newV) {
document.body.classList.add("vue-puzzle-overflow");
this.reset();
} else {
document.body.classList.remove("vue-puzzle-overflow");
}
}
},
/** 计算属性 **/
computed: {
// styleWidth是底部用户操作的滑块的父级,就是轨道在鼠标的作用下应该具有的宽度
styleWidth() {
const w = this.startWidth + this.newX - this.startX;
return w < this.sliderBaseSize
? this.sliderBaseSize
: w > this.canvasWidth
? this.canvasWidth
: w;
},
// 图中拼图块的60 * 用户设定的缩放比例计算之后的值 0.2~2
puzzleBaseSize() {
return Math.round(
Math.max(Math.min(this.puzzleScale, 2), 0.2) * 52.5 + 6
);
},
// 处理一下sliderSize,弄成整数,以免计算有偏差
sliderBaseSize() {
return Math.max(
Math.min(
Math.round(this.sliderSize),
Math.round(this.canvasWidth * 0.5)
),
10
);
}
},
/** 方法 **/
methods: {
// 关闭
onClose() {
if (!this.mouseDown) {
clearTimeout(this.timer1);
this.$emit("close");
}
},
onCloseMouseDown() {
this.closeDown = true;
},
onCloseMouseUp() {
if (this.closeDown) {
this.onClose();
}
this.closeDown = false;
},
// 鼠标按下准备拖动
onRangeMouseDown(e) {
if (this.isCanSlide) {
this.mouseDown = true;
this.startWidth = this.$refs["range-slider"].clientWidth;
this.newX = e.clientX || e.changedTouches[0].clientX;
this.startX = e.clientX || e.changedTouches[0].clientX;
}
},
// 鼠标移动
onRangeMouseMove(e) {
if (this.mouseDown) {
e.preventDefault();
this.newX = e.clientX || e.changedTouches[0].clientX;
}
},
// 鼠标抬起
onRangeMouseUp() {
if (this.mouseDown) {
this.mouseDown = false;
this.submit();
}
},
/**
* 开始进行
* @param withCanvas 是否强制使用canvas随机作图
*/
init(withCanvas) {
this.loading = true;
this.isCanSlide = false;
const c = this.$refs.canvas1;
const c2 = this.$refs.canvas2;
const c3 = this.$refs.canvas3;
const ctx = c.getContext("2d");
const ctx2 = c2.getContext("2d");
const ctx3 = c3.getContext("2d");
const img = document.createElement("img");
ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
ctx2.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
// 取一个随机坐标,作为拼图块的位置
this.pinX = this.getRandom(this.puzzleBaseSize,this.canvasWidth - this.puzzleBaseSize - 20); // 留20的边距
this.pinY = this.getRandom(20,this.canvasHeight - this.puzzleBaseSize - 20); // 主图高度 - 拼图块自身高度 - 20边距
img.crossOrigin = "anonymous"; // 匿名,想要获取跨域的图片
img.onload = () => {
const [x, y, w, h] = this.makeImgSize(img);
ctx.save();
// 先画小图
this.paintBrick(ctx);
ctx.closePath();
if (
!(
navigator.userAgent.indexOf("Firefox") >= 0 &&
navigator.userAgent.indexOf("Windows") >= 0
)
) {
// 非火狐,在此画外阴影
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 0;
ctx.shadowColor = "#000";
ctx.shadowBlur = 3;
ctx.fill();
}
ctx.clip(); // 按照外阴影区域切割
ctx.save();
// 小图外阴影
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 0;
ctx.shadowColor = "#000";
ctx.shadowBlur = 2;
ctx.fill();
ctx.restore();
ctx.drawImage(img, x, y, w, h);
ctx3.drawImage(img, x, y, w, h);
// 设置小图的内阴影
ctx.globalCompositeOperation = "source-atop";
this.paintBrick(ctx);
ctx.arc(
this.pinX + Math.ceil(this.puzzleBaseSize / 2),
this.pinY + Math.ceil(this.puzzleBaseSize / 2),
this.puzzleBaseSize * 1.2,
0,
Math.PI * 2,
true
);
ctx.closePath();
ctx.shadowColor = "rgba(255, 255, 255, .8)";
ctx.shadowOffsetX = -1;
ctx.shadowOffsetY = -1;
ctx.shadowBlur = Math.min(Math.ceil(8 * this.puzzleScale), 12);
ctx.fillStyle = "#ffffaa";
ctx.fill();
// 将小图赋值给ctx2
const imgData = ctx.getImageData(
this.pinX - 3, // 为了阴影 是从-3px开始截取,判定的时候要+3px
this.pinY - 20,
this.pinX + this.puzzleBaseSize + 5,
this.pinY + this.puzzleBaseSize + 5
);
ctx2.putImageData(imgData, 0, this.pinY - 20);
// 清理
ctx.restore();
ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
// 画缺口
ctx.save();
this.paintBrick(ctx);
ctx.globalAlpha = 0.8;
ctx.fillStyle = "#ffffff";
ctx.fill();
ctx.restore();
// 画缺口的内阴影
ctx.save();
ctx.globalCompositeOperation = "source-atop";
this.paintBrick(ctx);
ctx.arc(
this.pinX + Math.ceil(this.puzzleBaseSize / 2),
this.pinY + Math.ceil(this.puzzleBaseSize / 2),
this.puzzleBaseSize * 1.2,
0,
Math.PI * 2,
true
);
ctx.shadowColor = "#000";
ctx.shadowOffsetX = 2;
ctx.shadowOffsetY = 2;
ctx.shadowBlur = 16;
ctx.fill();
ctx.restore();
// 画整体背景图
ctx.save();
ctx.globalCompositeOperation = "destination-over";
ctx.drawImage(img, x, y, w, h);
ctx.restore();
this.loading = false;
this.isCanSlide = true;
};
img.onerror = () => {
this.init(true); // 如果图片加载错误就重新来,并强制用canvas随机作图
};
if (!withCanvas && this.imgs && this.imgs.length) {
let randomNum = this.getRandom(0, this.imgs.length - 1);
if (randomNum === this.imgIndex) {
if (randomNum === this.imgs.length - 1) {
randomNum = 0;
} else {
randomNum++;
}
}
this.imgIndex = randomNum;
img.src = this.imgs[randomNum];
} else {
img.src = this.makeImgWithCanvas();
}
},
// 工具 - 范围随机数
getRandom(min, max) {
return Math.ceil(Math.random() * (max - min) + min);
},
// 工具 - 设置图片尺寸cover方式贴合canvas尺寸 w/h
makeImgSize(img) {
const imgScale = img.width / img.height;
const canvasScale = this.canvasWidth / this.canvasHeight;
let x = 0,
y = 0,
w = 0,
h = 0;
if (imgScale > canvasScale) {
h = this.canvasHeight;
w = imgScale * h;
y = 0;
x = (this.canvasWidth - w) / 2;
} else {
w = this.canvasWidth;
h = w / imgScale;
x = 0;
y = (this.canvasHeight - h) / 2;
}
return [x, y, w, h];
},
// 绘制拼图块的路径
paintBrick(ctx) {
const moveL = Math.ceil(15 * this.puzzleScale); // 直线移动的基础距离
ctx.beginPath();
ctx.moveTo(this.pinX, this.pinY);
ctx.lineTo(this.pinX + moveL, this.pinY);
ctx.arcTo(
this.pinX + moveL,
this.pinY - moveL / 2,
this.pinX + moveL + moveL / 2,
this.pinY - moveL / 2,
moveL / 2
);
ctx.arcTo(
this.pinX + moveL + moveL,
this.pinY - moveL / 2,
this.pinX + moveL + moveL,
this.pinY,
moveL / 2
);
ctx.lineTo(this.pinX + moveL + moveL + moveL, this.pinY);
ctx.lineTo(this.pinX + moveL + moveL + moveL, this.pinY + moveL);
ctx.arcTo(
this.pinX + moveL + moveL + moveL + moveL / 2,
this.pinY + moveL,
this.pinX + moveL + moveL + moveL + moveL / 2,
this.pinY + moveL + moveL / 2,
moveL / 2
);
ctx.arcTo(
this.pinX + moveL + moveL + moveL + moveL / 2,
this.pinY + moveL + moveL,
this.pinX + moveL + moveL + moveL,
this.pinY + moveL + moveL,
moveL / 2
);
ctx.lineTo(
this.pinX + moveL + moveL + moveL,
this.pinY + moveL + moveL + moveL
);
ctx.lineTo(this.pinX, this.pinY + moveL + moveL + moveL);
ctx.lineTo(this.pinX, this.pinY + moveL + moveL);
ctx.arcTo(
this.pinX + moveL / 2,
this.pinY + moveL + moveL,
this.pinX + moveL / 2,
this.pinY + moveL + moveL / 2,
moveL / 2
);
ctx.arcTo(
this.pinX + moveL / 2,
this.pinY + moveL,
this.pinX,
this.pinY + moveL,
moveL / 2
);
ctx.lineTo(this.pinX, this.pinY);
},
// 用canvas随机生成图片
makeImgWithCanvas() {
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
canvas.width = this.canvasWidth;
canvas.height = this.canvasHeight;
ctx.fillStyle = `rgb(${this.getRandom(100, 255)},${this.getRandom(
100,
255
)},${this.getRandom(100, 255)})`;
ctx.fillRect(0, 0, this.canvasWidth, this.canvasHeight);
// 随机画10个图形
for (let i = 0; i < 12; i++) {
ctx.fillStyle = `rgb(${this.getRandom(100, 255)},${this.getRandom(
100,
255
)},${this.getRandom(100, 255)})`;
ctx.strokeStyle = `rgb(${this.getRandom(100, 255)},${this.getRandom(
100,
255
)},${this.getRandom(100, 255)})`;
if (this.getRandom(0, 2) > 1) {
// 矩形
ctx.save();
ctx.rotate((this.getRandom(-90, 90) * Math.PI) / 180);
ctx.fillRect(
this.getRandom(-20, canvas.width - 20),
this.getRandom(-20, canvas.height - 20),
this.getRandom(10, canvas.width / 2 + 10),
this.getRandom(10, canvas.height / 2 + 10)
);
ctx.restore();
} else {
// 圆
ctx.beginPath();
const ran = this.getRandom(-Math.PI, Math.PI);
ctx.arc(
this.getRandom(0, canvas.width),
this.getRandom(0, canvas.height),
this.getRandom(10, canvas.height / 2 + 10),
ran,
ran + Math.PI * 1.5
);
ctx.closePath();
ctx.fill();
}
}
return canvas.toDataURL("image/png");
},
// 开始判定
submit() {
// 偏差 x = puzzle的起始X - (用户真滑动的距离) + (puzzle的宽度 - 滑块的宽度) * (用户真滑动的距离/canvas总宽度)
// 最后+ 的是补上slider和滑块宽度不一致造成的缝隙
const x = Math.abs(
this.pinX -
(this.styleWidth - this.sliderBaseSize) +
(this.puzzleBaseSize - this.sliderBaseSize) *
((this.styleWidth - this.sliderBaseSize) /
(this.canvasWidth - this.sliderBaseSize)) -
3
);
if (x < this.range) {
// 成功
this.infoText = this.successText;
this.infoBoxFail = false;
this.infoBoxShow = true;
this.isCanSlide = false;
this.isSuccess = true;
// 成功后准备关闭
clearTimeout(this.timer1);
this.timer1 = setTimeout(() => {
// 成功的回调
this.$emit("success", x);
}, 800);
} else {
// 失败
this.infoText = this.failText;
this.infoBoxFail = true;
this.infoBoxShow = true;
this.isCanSlide = false;
// 失败的回调
this.$emit("fail", x);
// 800ms后重置
clearTimeout(this.timer1);
this.timer1 = setTimeout(() => {
this.reset();
}, 800);
}
},
// 重置
reset() {
this.infoBoxFail = false;
this.infoBoxShow = false;
this.isCanSlide = true;
this.isSuccess = false;
this.startWidth = this.sliderBaseSize; // 鼠标点下去时父级的width
this.startX = 0; // 鼠标按下时的X
this.newX = 0; // 鼠标当前的偏移X
this.init();
}
}
};
</script>
<style lang="less">
@import './vue-puzzle-code.less';
</style>
<template>
<div class="app-user-input">
<i-input
size='large'
:prefix='icon'
v-model="value"
:placeholder="placeholder">
</i-input>
</div>
</template>
<script lang="ts">
import { Vue, Component, Watch, Prop, Model } from 'vue-property-decorator';
@Component({
})
export default class AppUserId extends Vue {
/**
* 登录名称
*
* @type {string}
* @memberof AppUserId
*/
@Prop({default: ''}) public value!: string;
@Prop() public icon?: string;
@Prop() public placeholder?: string;
}
</script>
.app-login-message{
.ivu-alert{
display: flex;
align-items: center;
background-color: rgb(255, 225, 225);
.ivu-alert-icon{
position: unset;
margin-right: 5px;
}
}
.ivu-alert-error,
.ivu-alert-with-icon{
padding-left: 10px;
}
}
\ No newline at end of file
<template>
<div class="app-login-message">
<alert v-show="value" type="error" show-icon>{{ value }}</alert>
</div>
</template>
<script lang='ts'>
import { Component, Vue, Prop } from "vue-property-decorator";
@Component({})
export default class AppPreSetLoginMessage extends Vue {
/**
* 内容
*
* @type {string}
* @memberof AppPreSetLoginMessage
*/
@Prop() public value?: string;
}
</script>
<style lang='less'>
@import "./app-login-message.less";
</style>
\ No newline at end of file
.app-preset-smsverification{
.content {
display: flex;
margin: 5px 0;
.el-button {
border: 1px solid #dcdee2;
margin-left: 10px;
color: #606266;
background-color: #fff;
&:hover {
border: 1px solid #dcdee2;
}
}
}
.code{
padding-top: 8px;
}
.error {
color: red;
}
}
\ No newline at end of file
<template>
<div class="app-preset-smsverification">
<div class="content">
<i-input
size="default"
type="text"
v-model="phoneNumber"
:placeholder="$t('components.login.phoneplaceholder')"
@on-blur="veriPhoneNumber"
></i-input>
<el-button
:disabled="disabled"
size="mini"
type="primary"
@click="getVeriCode()"
>{{ disabled ? `${countDown}s ${$t('components.login.getcodeafter')}` : `${$t('components.login.getcode')}` }}</el-button
>
</div>
<div class="code">
<i-input
size="default"
type="text"
:value="currentValue"
@input="codeChange"
:placeholder="$t('components.login.codeplaceholder')"
></i-input>
</div>
</div>
</template>
<script lang='ts'>
import { Vue, Component, Prop } from "vue-property-decorator";
@Component({})
export default class AppPresetSmsVerification extends Vue {
/**
*验证码值
*
* @type {string}
* @memberof AppPresetSmsVerification
*/
@Prop() public value!: string;
/**
*验证码当前值
*
* @type {string}
* @memberof AppPresetSmsVerification
*/
get currentValue(): string {
return this.value;
}
/**
* 手机号
*
* @type {*}
* @memberof AppPresetSmsVerification
*/
public phoneNumber: string = '';
/**
* 倒计时
*
* @type {*}
* @memberof AppPresetSmsVerification
*/
public countDown: number = 60;
/**
* 错误提示
* @type {*}
* @memberof AppPresetSmsVerification
*/
public phoneError = false;
/**
* 是否禁用获取验证码按钮
*
* @type {*}
* @memberof AppPresetSmsVerification
*/
public disabled: boolean = false;
/**
* 定时器
*
* @type {*}
* @memberof AppPresetSmsVerification
*/
public timer: any;
/**
* @description 设置倒计时
* @memberof AppPresetSmsVerification
*/
public setCountDown() {
if (this.countDown > 0) {
this.countDown--;
} else {
this.countDown = 60;
this.disabled = false;
clearInterval(this.timer);
}
this.$forceUpdate();
}
/**
* @description 验证码输入变化
* @memberof AppPresetSmsVerification
*/
public codeChange(value: string) {
this.$emit("change", value);
}
/**
* @description 校验手机号
* @memberof AppPresetSmsVerification
*/
public veriPhoneNumber(): boolean {
this.phoneError = !/^1[3-9]\d{9}$/.test(this.phoneNumber);
if(this.phoneError) {
this.$emit("error", this.$t('components.login.phonefailed'));
} else {
this.$emit("error", '');
}
return this.phoneError;
}
/**
* @description 获取验证码
* @memberof AppPresetSmsVerification
*/
public getVeriCode() {
if(this.phoneError) return;
// todo
this.$http.post(``, {}).then((response: any) => {
if (response.status == 200) {
this.disabled = true;
this.timer = setInterval(() => {
this.setCountDown();
}, 1000);
}
});
}
}
</script>
<style lang='less'>
@import "./app-login-note-verify.less";
</style>
\ No newline at end of file
.app-auth-org-picker {
height: 36px;
width: 100%;
.app-org-sector-remote {
width: 100%;
.org-sector-choice{
background-color: #e6f7ff;
}
}
.ivu-dropdown{
width: inherit;
position: relative;
.ivu-select-dropdown{
width: 100%;
}
}
.auth-org-picker-dropdown{
width: inherit;
position: relative;
border-radius: 5px;
border: 1px solid gray;
.ivu-select-dropdown{
width: 100%;
}
}
.menu-item-no-data{
width: 100%;
font-size: 16px;
text-align: center;
color: #dbdbdb;
padding: 5px 0;
}
}
\ No newline at end of file
<template>
<div class="app-org-picker">
<div class="app-org-sector-remote">
<dropdown @on-click="orgSelect" @on-visible-change="visibleChange">
<div class="org-sector">
<Input :value="getSelectedOrgName" placeholder="请选择部门" readonly :icon="iconClass" />
</div>
<dropdown-menu slot="list">
<dropdown-item
:class="{ 'org-sector-choice': Object.is(item.srforgsectorid, getSelectedOrgName) }"
:name="item.srforgsectorid"
v-for="(item, index) in selectedOrgArray"
:key="index"
>
{{ item.srforgsectorname }}
</dropdown-item>
<div class="menu-item-no-data" v-show="!selectedOrgArray.length">暂无数据</div>
</dropdown-menu>
</dropdown>
</div>
</div>
</template>
<script lang='ts'>
import { Vue, Component, Inject, Prop } from 'vue-property-decorator';
@Component({})
export default class AppAuthOrgPicker extends Vue {
/**
* 输入值
*
* @type {*}
* @memberof AppAuthOrgPicker
*/
@Prop() public value!: string;
/**
* 默认数据来源模式
*
* @type {string}
* @memberof AppAuthOrgPicker
*
*/
public selectMode: 'REMOTE' | 'LOCAL' = 'LOCAL';
/**
* 选中组织部门id
*
* @type {string}
* @memberof AppAuthOrgPicker
*/
public selectedOrgId: string = '';
/**
* 图标名称
*
* @type {boolean}
* @memberof AppAuthOrgPicker
*/
public iconClass: string = 'ios-arrow-down';
/**
* 选中组织部门名称
*
* @type {string}
* @memberof AppAuthOrgPicker
*/
get getSelectedOrgName() {
if (this.value) {
const selectedValue = this.selectedOrgArray.find((item: any) => {
return item.srforgsectorid == this.value;
});
return selectedValue?.srforgsectorname;
}
}
/**
* 组织部门名称数组
*
* @type {Array<any>}
* @memberof AppAuthOrgPicker
*/
public selectedOrgArray: Array<any> = [];
/**
* 组件初始化数据,vue生命周期
*
* @memberof AppAuthOrgPicker
*/
public created() {
this.getOrgData();
}
/**
* 选择组织部门回调
*
* @memberof AppAuthOrgPicker
*/
public orgSelect(data: string) {
if (Object.is(data, this.selectedOrgId)) {
return;
}
let item: any = this.selectedOrgArray.find((_item: any) => Object.is(_item.srforgsectorid, data));
this.$emit('valueChange', { value: item.srforgsectorid });
}
/**
* 下拉框打开或收起回调
*
* @memberof AppAuthOrgPicker
*/
visibleChange(data: boolean) {
this.iconClass = data ? 'ios-arrow-up' : 'ios-arrow-down';
}
/**
* 获取数据
*
* @memberof AppAuthOrgPicker
*/
public async getOrgData() {
// todo
console.log('获取数据暂未实现');
}
}
</script>
<style lang="less">
@import './app-login-org.less';
</style>
\ No newline at end of file
.app-login-third {
text-align: center;
.app-login-third-imgrwap {
.sign-btn {
.third-svg-container {
margin: 0px;
padding: 0px;
}
}
}
}
\ No newline at end of file
<template>
<div class="app-login-third">
<span class="title">{{ $t("components.login.other") }}</span>
<div class="app-login-third-imgrwap">
<div class="sign-btn" @click="handleThirdLogin('DINGDING')">
<img class="third-svg-container" src="@/assets/img/dingding.svg" />
</div>
<div class="sign-btn" @click="handleThirdLogin('WXWORK')">
<img class="third-svg-container" src="@/assets/img/qiyeweixin.svg" />
</div>
</div>
</div>
</template>
<script lang="ts">
import { Component, Vue } from "vue-property-decorator";
import qs from "qs";
import * as dd from "dingtalk-jsapi";
import axios from "axios";
import { Util } from "@/utils";
@Component({})
export default class AppThirdLogin extends Vue {
/**
* 处理第三方登录
*
* @param type 登录类型
* @memberof AppThirdLogin
*/
handleThirdLogin(type: string) {
switch (type) {
case "DINGDING":
this.dingTalkHandleClick();
break;
case "WXWORK":
this.wxWorkHandleClick();
break;
default:
console.warn(`暂不支持${type}登录`);
break;
}
}
/**
* 钉钉登录
*
* @memberof AppThirdLogin
*/
async dingTalkHandleClick() {
let result: any = await this.dingtalkLogin();
if (result) {
if (result.state && Object.is(result.state, "SUCCESS")) {
const data = result.data;
// 截取地址,拼接需要部分组成新地址
const baseUrl = this.getNeedLocation();
// 1.钉钉开放平台提供的appId
const appId = data.appid;
// 2.钉钉扫码后回调地址,需要UrlEncode转码
const redirect_uri = baseUrl + "assets/third/dingdingRedirect.html?id=" + data.appid;
const redirect_uri_encode = encodeURIComponent(redirect_uri);
// 3.钉钉扫码url
const url =
"https://oapi.dingtalk.com/connect/qrconnect?response_type=code" +
"&appid=" +
appId +
"&redirect_uri=" +
redirect_uri_encode +
"&scope=snsapi_login" +
"&state=STATE";
// 4.跳转钉钉扫码
window.location.href = url;
} else if (result.message) {
throw new Error(result.message);
}
}
}
/**
* 微信登录
*
* @memberof AppThirdLogin
*/
async wxWorkHandleClick() {
let result: any = await this.wxWorkLogin();
if (result) {
if (result.state && Object.is(result.state, "SUCCESS")) {
const data = result.data;
// 截取地址,拼接需要部分组成新地址
const baseUrl = this.getNeedLocation();
// 1.微信平台提供的appId
const appId = data.corp_id;
const agentId = data.agentid;
// 2.微信扫码后回调地址,需要UrlEncode转码
const redirect_uri = baseUrl + "assets/third/wxWorkRedirect.html?id=" + data.appid;
const redirect_uri_encode = encodeURIComponent(redirect_uri);
// 3.微信扫码url
const url =
"https://open.work.weixin.qq.com/wwopen/sso/qrConnect?state=STATE" +
"&appid=" +
appId +
"&agentid=" +
agentId +
"&redirect_uri=" +
redirect_uri_encode;
// 4.跳转微信扫码
window.location.href = url;
} else if (result.message) {
throw new Error(result.message);
}
}
}
/**
* 截取地址
*
* @memberof AppThirdLogin
*/
private getNeedLocation() {
// 截取地址,拼接需要部分组成新地址
const scheme = window.location.protocol;
const host = window.location.host;
let baseUrl: any = scheme + "//" + host;
const port = window.location.port;
if (port) {
if (port == "80" || port == "443") {
baseUrl += "/";
} else {
baseUrl += ":" + port + "/";
}
} else {
baseUrl += "/";
}
return baseUrl;
}
/**
* 钉钉授权登录
*
* @memberof AppThirdService
*/
public dingtalkLogin(): Promise<any> {
return new Promise((resolve) => {
// 请求头
const headers = {};
const tempViewParam = Util.getDcSystemIdViewParam();
if (tempViewParam && tempViewParam.srfdcsystem) {
Object.assign(headers, { srfdcsystem: tempViewParam.srfdcsystem });
}
const get: Promise<any> = this.getData("/uaa/open/dingtalk/appid", {}, false, headers);
get.then((response: any) => {
if (response && response.status === 200) {
const data = response.data;
if (data && data.appid) {
resolve({ state: "SUCCESS", data: data });
} else {
resolve({
state: "ERROR",
message: `获取网站应用appid失败,${data.detail}`,
});
}
}
}).catch((error: any) => {
const data = error.data;
if (data && data.detail) {
resolve({
state: "ERROR",
message: `获取网站应用appid失败,${data.detail}`,
});
} else {
resolve({ state: "ERROR", message: `获取网站应用appid失败` });
}
});
});
}
/**
* 企业微信授权登录
*
* @memberof AppThirdService
*/
public wxWorkLogin(): Promise<any> {
return new Promise((resolve) => {
// 请求头
const headers = {};
const tempViewParam = Util.getDcSystemIdViewParam();
if (tempViewParam && tempViewParam.srfdcsystem) {
Object.assign(headers, { srfdcsystem: tempViewParam.srfdcsystem });
}
const get: Promise<any> = this.getData("/uaa/open/wxwork/appid", {}, false, headers);
get.then((response: any) => {
if (response && response.status === 200) {
const data = response.data;
if (data && (data.corp_id || data.appid)) {
resolve({ state: "SUCCESS", data: data });
} else {
resolve({
state: "ERROR",
message: `获取网站应用appid失败,${data.detail}`,
});
}
}
}).catch((error: any) => {
const data = error.data;
if (data && data.detail) {
resolve({
state: "ERROR",
message: `获取网站应用appid失败,${data.detail}`,
});
} else {
resolve({ state: "ERROR", message: `获取网站应用appid失败` });
}
});
});
}
/**
* 钉钉内部免登
*
* @memberof AppThirdService
*/
public embedDingTalkLogin(Environment: any): Promise<any> {
return new Promise((resolve) => {
// 请求头
const headers = {};
const tempViewParam = Util.getDcSystemIdViewParam();
if (tempViewParam && tempViewParam.srfdcsystem) {
Object.assign(headers, { srfdcsystem: tempViewParam.srfdcsystem });
}
const param = Util.handleViewParam(window.location.href);
if (param.corpId) {
let corpId: string = param.corpId;
if (corpId.indexOf("#") > -1) {
corpId = corpId.split("#")[0];
}
dd.runtime.permission
.requestAuthCode({ corpId })
.then((res: any) => {
if (res && res.code) {
this.getData(`/uaa/open/dingtalk/auth/${res.code}`, {}, false, headers).then((res: any) => {
if (res.status == 200 && (res.data.token || res.data.user)) {
resolve({ state: "SUCCESS", data: res.data });
} else {
resolve({ state: "ERROR", message: `${res.data.message}` });
}
});
} else {
resolve({ state: "ERROR", message: `钉钉用户信息获取失败` });
}
})
.catch((error: any) => {
resolve({ state: "ERROR", message: `钉钉用户信息获取失败` });
});
} else {
resolve({ state: "ERROR", message: `获取企业ID失败` });
}
});
}
/**
* 获取数据
*
* @memberof AppThirdService
*/
public getData(url: string, params: any = {}, isloading?: boolean, headers: any = {}) {
params = this.handleRequestData(params);
if (params.srfparentdata) {
Object.assign(params, params.srfparentdata);
delete params.srfparentdata;
}
if (Object.keys(params).length > 0) {
let tempParam: any = {};
Object.keys(params).forEach((item: any) => {
if (params[item] || Object.is(params[item], 0)) {
tempParam[item] = params[item];
}
});
if (Object.keys(tempParam).length > 0) {
url += `?${qs.stringify(tempParam)}`;
}
}
return new Promise((resolve: any, reject: any) => {
axios.get(url, { headers: headers }).then((response: any) => {
resolve(response);
});
});
}
/**
* 处理请求数据
*
* @memberof AppThirdService
*/
public handleRequestData(data: any) {
if (data?.srfsessionkey) {
delete data.srfsessionkey;
}
if (data?.srfsessionid) {
delete data.srfsessionid;
}
if (data?.srfparentdemapname) {
delete data.srfparentdemapname;
}
return data;
}
}
</script>
<style lang="less">
@import "./app-login-third.less";
</style>
.app-preset-caption {
padding: 8px;
.caption-info {
font-weight: 600;
font-family: "Microsoft YaHei";
font-size: 14px;
}
}
\ No newline at end of file
<template>
<div class="app-preset-caption">
<slot></slot>
</div>
</template>
<script lang="ts">
import { Vue, Component } from "vue-property-decorator";
@Component({})
export default class AppPreSetCaption extends Vue {}
</script>
<style lang='less'>
@import "./app-preset-caption.less";
</style>
\ No newline at end of file
.app-preset-text {
width: 100%;
height: 100%;
}
\ No newline at end of file
<template>
<div :class="['app-preset-text', `app-preset-text--${contentType.toLowerCase()}`]">
<!-- 直接内容类型 -->
<template v-if="Object.is(contentType, 'RAW')">
<template v-if="Object.is(renderMode, 'TEXT')">
<span :style="cssStyle">{{ value }}</span>
</template>
<template v-else-if="Object.is(renderMode, 'HEADING1')">
<h1 :style="cssStyle">{{ value }}</h1>
</template>
<template v-else-if="Object.is(renderMode, 'HEADING2')">
<h2 :style="cssStyle">{{ value }}</h2>
</template>
<template v-else-if="Object.is(renderMode, 'HEADING3')">
<h3 :style="cssStyle">{{ value }}</h3>
</template>
<template v-else-if="Object.is(renderMode, 'HEADING4')">
<h4 :style="cssStyle">{{ value }}</h4>
</template>
<template v-else-if="Object.is(renderMode, 'HEADING5')">
<h5 :style="cssStyle">{{ value }}</h5>
</template>
<template v-else-if="Object.is(renderMode, 'HEADING6')">
<h6 :style="cssStyle">{{ value }}</h6>
</template>
<template v-else-if="Object.is(renderMode, 'PARAGRAPH')">
<p :style="cssStyle">{{ value }}</p>
</template>
</template>
<!-- 图片类型 -->
<template v-else-if="Object.is(contentType, 'IMAGE')">
<img :style="cssStyle" v-if="imgUrl" :src="imgUrl" />
<i :style="cssStyle" v-else :class="imageClass ? imageClass : ''"></i>
</template>
<!-- HTML类型 -->
<template v-else-if="Object.is(contentType, 'HTML')">
<div :style="cssStyle" v-html="value" />
</template>
<!-- MARKDOWN类型 -->
<template v-else-if="Object.is(contentType, 'MARKDOWN')">
MARKDOWN暂未支持
<!-- <app-markdown-editor :style="cssStyle" mode="PREVIEWONLY" :itemValue="value"></app-markdown-editor> -->
</template>
</div>
</template>
<script lang="ts">
import { Vue, Component, Prop } from 'vue-property-decorator';
@Component({})
export default class AppPreSetText extends Vue {
/**
* 输入值
*
* @type {*}
* @memberof AppPreSetText
*/
@Prop() public value!: any;
/**
* 内容类型
*
* @type {string}
* @memberof AppPreSetText
*/
@Prop({ default: 'RAW' }) public contentType!: 'RAW' | 'HTML' | 'IMAGE' | 'MARKDOWN';
/**
* 绘制模式
*
* @type {string}
* @memberof AppPreSetText
*/
@Prop({ default: 'TEXT' }) public renderMode!: 'TEXT' | 'HEADING1' | 'HEADING2' | 'HEADING3' | 'HEADING4' | 'HEADING5' | 'HEADING6' | 'PARAGRAPH';
/**
* 内容样式
*
* @type {string}
* @memberof AppPreSetText
*/
@Prop() public contentStyle?: string;
/**
* 预置类型
*
* @type {string}
* @memberof AppPreSetText
*/
@Prop({ default: 'STATIC_TEXT' }) public predefinedType!: 'FIELD_TEXT_DYNAMIC' | 'STATIC_LABEL' | 'STATIC_TEXT';
/**
* 图标
*
* @memberof AppPreSetText
*/
@Prop() public imageClass?: string;
/**
* 动态图片路径
*
* @memberof AppPreSetText
*/
protected dynaImgUrl: string = '';
/**
* 样式
*
* @memberof AppPreSetText
*/
protected cssStyle: string = '';
/**
* 图片路径
*
* @memberof AppPreSetText
*/
get imgUrl(): string {
return this.dynaImgUrl;
}
/**
* Vue生命周期 --- Created
*
* @memberof AppPreSetText
*/
created() {
this.handleText();
this.handleDynaImg();
}
/**
* 处理文本
*
* @memberof AppPreSetText
*/
protected handleText() {
if (this.predefinedType === 'STATIC_LABEL') {
this.cssStyle += "white-space: nowrap;overflow: hidden;text-overflow: ellipsis;";
}
}
/**
* 处理动态图片
*
* @memberof AppPreSetText
*/
protected handleDynaImg() {
// TODO 动态图片
// if (this.value && typeof this.value == 'string') {
// // 默认识别文件对象形式,识别失败则为全路径模式
// try {
// const _files = JSON.parse(this.value);
// const file = _files instanceof Array ? _files[0] : null;
// const url = file && file.id ? `${this.downloadUrl}/${file.id}` : '';
// ImgurlBase64.getInstance()
// .getImgURLOfBase64(url)
// .then((res: any) => {
// this.dynaImgUrl = res;
// });
// } catch (error) {
// this.dynaImgUrl = this.value;
// }
// }
}
}
</script>
<style lang="less">
@import './app-preset-text.less';
</style>
\ No newline at end of file
.app-preset-title {
padding: 16px;
font-weight: 600;
font-size: 24px;
}
\ No newline at end of file
<template>
<h1 class="app-preset-title">{{ curValue }}</h1>
</template>
<script lang="ts">
import { Environment } from "@/environments/environment";
import { Vue, Component } from 'vue-property-decorator';
@Component({})
export default class AppPreSetTitle extends Vue {
/**
* 当前值
*
* @memberof AppPreSetTitle
*/
public curValue: string = '';
/**
* 初始化
*
* @memberof AppPreSetTitle
*/
public created() {
this.curValue = Environment.AppTitle;
}
}
</script>
<style lang='less'>
@import './app-preset-title.less';
</style>
\ No newline at end of file
......@@ -158,6 +158,8 @@ function getLocaleResourceBase(){
placeholder2:'Password',
name: 'Login',
reset:'Reset',
register: 'Register',
logout: 'Logout',
other:'Other login methods',
tip: 'Enter username and password',
warning1:'QQ authorization login not supported',
......@@ -171,6 +173,13 @@ function getLocaleResourceBase(){
message: 'The password cannot be empty',
},
loginfailed: 'Login failed',
authfailed: 'Authentication failed',
phoneplaceholder: 'Please enter your mobile phone number',
phonefailed: 'The phone number is incorrect',
codeplaceholder: 'Please enter the verification code',
getcode: 'Get verification code',
getcodeafter: 'Get verification code after',
entervercode: 'Please enter the verification code',
},
appUser: {
name: 'System',
......
......@@ -159,6 +159,8 @@ function getLocaleResourceBase(){
placeholder2:'密码',
name: '登录',
reset:'重置',
register: '注册',
logout: '登出',
other:'其他登录方式',
tip: '输入用户名和密码',
warning1:'qq授权登录暂未支持',
......@@ -172,6 +174,13 @@ function getLocaleResourceBase(){
message: '密码不能为空',
},
loginfailed: '登录失败',
authfailed: '验证失败',
phoneplaceholder: '请输入手机号',
phonefailed: '手机号不正确',
codeplaceholder: '请输入验证码',
getcode: '获取验证码',
getcodeafter: '后获取验证码',
entervercode: '请输入验证码',
},
appUser: {
name: '系统管理员',
......
......@@ -663,7 +663,7 @@ export default class DefaultBase extends Vue implements ControlInterface {
*/
public load(opt: any = {}): void {
if(!this.loadAction){
this.$Notice.error({ title: (this.$t('app.commonWords.wrong') as string), desc: 'IBIZBOOKUsr3GridView' + (this.$t('app.searchForm.notConfig.loadAction') as string) });
this.$Notice.error({ title: (this.$t('app.commonWords.wrong') as string), desc: 'IBIZBOOKUsr9GridView' + (this.$t('app.searchForm.notConfig.loadAction') as string) });
return;
}
const arg: any = { ...opt };
......@@ -699,7 +699,7 @@ export default class DefaultBase extends Vue implements ControlInterface {
*/
public loadDraft(opt: any = {},mode?:string): void {
if(!this.loaddraftAction){
this.$Notice.error({ title: (this.$t('app.commonWords.wrong') as string), desc: 'IBIZBOOKUsr3GridView' + (this.$t('app.searchForm.notConfig.loaddraftAction') as string) });
this.$Notice.error({ title: (this.$t('app.commonWords.wrong') as string), desc: 'IBIZBOOKUsr9GridView' + (this.$t('app.searchForm.notConfig.loaddraftAction') as string) });
return;
}
const arg: any = { ...opt } ;
......
......@@ -621,7 +621,7 @@ export default class QUICKSEARCHFORMBase extends Vue implements ControlInterface
*/
public load(opt: any = {}): void {
if(!this.loadAction){
this.$Notice.error({ title: (this.$t('app.commonWords.wrong') as string), desc: 'IBIZBOOKListView' + (this.$t('app.searchForm.notConfig.loadAction') as string) });
this.$Notice.error({ title: (this.$t('app.commonWords.wrong') as string), desc: 'IBIZBOOKCalendarView' + (this.$t('app.searchForm.notConfig.loadAction') as string) });
return;
}
const arg: any = { ...opt };
......@@ -657,7 +657,7 @@ export default class QUICKSEARCHFORMBase extends Vue implements ControlInterface
*/
public loadDraft(opt: any = {},mode?:string): void {
if(!this.loaddraftAction){
this.$Notice.error({ title: (this.$t('app.commonWords.wrong') as string), desc: 'IBIZBOOKListView' + (this.$t('app.searchForm.notConfig.loaddraftAction') as string) });
this.$Notice.error({ title: (this.$t('app.commonWords.wrong') as string), desc: 'IBIZBOOKCalendarView' + (this.$t('app.searchForm.notConfig.loaddraftAction') as string) });
return;
}
const arg: any = { ...opt } ;
......
Markdown 格式
0% or
您添加了 0 到此讨论。请谨慎行事。
先完成此消息的编辑!
想要评论请 注册