JeecgBoot 3.5.0 版本发布,开源的企业级低代码平台

pull/340/merge v3.5.0
zhangdaiscott 2023-03-05 11:41:15 +08:00
parent 95ca88b47c
commit f147d8fbe5
141 changed files with 12309 additions and 15232 deletions

15
LICENSE
View File

@ -19,3 +19,18 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
<developers>
<developer>
<name>北京敲敲云科技有限公司</name>
<email>jeecgos@163.com</email>
</developer>
</developers>
<scm>
<connection>http://www.jeecg.com</connection>
<developerConnection>https://qiaoqiaoyun.com</developerConnection>
<url>http://www.jeecg.com/vip</url>
</scm>

View File

@ -1,11 +1,11 @@
JEECG BOOT 低代码开发平台Vue3前端
===============
当前最新版本: 3.4.4发布时间2022-11-21
当前最新版本: 3.5.0发布时间2023-03-08
[![AUR](https://img.shields.io/badge/license-Apache%20License%202.0-blue.svg)](https://github.com/zhangdaiscott/jeecg-boot/blob/master/LICENSE)
[![](https://img.shields.io/badge/Author-北京敲敲云科技-orange.svg)](http://www.jeecg.com)
[![](https://img.shields.io/badge/Blog-官方博客-blue.svg)](https://jeecg.blog.csdn.net)
[![](https://img.shields.io/badge/version-3.4.4-brightgreen.svg)](https://github.com/zhangdaiscott/jeecg-boot)
[![](https://img.shields.io/badge/version-3.5.0-brightgreen.svg)](https://github.com/zhangdaiscott/jeecg-boot)
[![GitHub stars](https://img.shields.io/github/stars/zhangdaiscott/jeecg-boot.svg?style=social&label=Stars)](https://github.com/zhangdaiscott/jeecg-boot)
[![GitHub forks](https://img.shields.io/github/forks/zhangdaiscott/jeecg-boot.svg?style=social&label=Fork)](https://github.com/zhangdaiscott/jeecg-boot)

View File

@ -1,6 +1,6 @@
{
"name": "jeecgboot-vue3",
"version": "3.4.4",
"version": "3.5.0",
"author": {
"name": "jeecg",
"email": "jeecgos@163.com",
@ -67,6 +67,7 @@
"path-to-regexp": "^6.2.0",
"pinia": "2.0.12",
"print-js": "^1.6.0",
"pinyin-pro": "^3.11.0",
"qs": "^6.10.3",
"qrcode": "^1.5.0",
"qrcodejs2": "0.0.2",
@ -107,6 +108,7 @@
"@types/nprogress": "^0.2.0",
"@types/qrcode": "^1.4.2",
"@types/qs": "^6.9.7",
"@types/pinyin": "^2.10.0",
"@types/showdown": "^1.9.4",
"@types/sortablejs": "^1.10.7",
"@typescript-eslint/eslint-plugin": "^5.20.0",
@ -194,7 +196,107 @@
"vite": {
"optimizeDeps": {
"include": [
"@ant-design/colors",
"@ant-design/icons-vue",
"@vueuse/core",
"@vueuse/shared",
"@zxcvbn-ts/core",
"ant-design-vue",
"axios",
"china-area-data",
"clipboard",
"codemirror",
"codemirror/addon/fold/brace-fold.js",
"codemirror/addon/fold/comment-fold.js",
"codemirror/addon/fold/foldcode.js",
"codemirror/addon/fold/foldgutter.js",
"codemirror/addon/fold/indent-fold.js",
"codemirror/addon/hint/anyword-hint.js",
"codemirror/addon/hint/show-hint.js",
"codemirror/addon/selection/active-line.js",
"codemirror/mode/clike/clike.js",
"codemirror/mode/css/css.js",
"codemirror/mode/javascript/javascript.js",
"codemirror/mode/markdown/markdown.js",
"codemirror/mode/python/python.js",
"codemirror/mode/r/r.js",
"codemirror/mode/shell/shell.js",
"codemirror/mode/sql/sql.js",
"codemirror/mode/swift/swift.js",
"codemirror/mode/vue/vue.js",
"codemirror/mode/xml/xml.js",
"cron-parser",
"cropperjs",
"crypto-js/aes",
"crypto-js/enc-base64",
"crypto-js/enc-utf8",
"crypto-js/md5",
"crypto-js/mode-ecb",
"crypto-js/pad-pkcs7",
"dom-align",
"echarts",
"echarts/charts",
"echarts/components",
"echarts/core",
"echarts/renderers",
"emoji-mart-vue-fast/src",
"intro.js",
"lodash-es",
"md5",
"nprogress",
"path-to-regexp",
"pinia",
"print-js",
"qrcode",
"qs",
"resize-observer-polyfill",
"showdown",
"sortablejs",
"tinymce/icons/default/icons",
"tinymce/plugins/advlist",
"tinymce/plugins/anchor",
"tinymce/plugins/autolink",
"tinymce/plugins/autosave",
"tinymce/plugins/code",
"tinymce/plugins/codesample",
"tinymce/plugins/contextmenu",
"tinymce/plugins/directionality",
"tinymce/plugins/fullscreen",
"tinymce/plugins/hr",
"tinymce/plugins/image",
"tinymce/plugins/insertdatetime",
"tinymce/plugins/link",
"tinymce/plugins/lists",
"tinymce/plugins/media",
"tinymce/plugins/nonbreaking",
"tinymce/plugins/noneditable",
"tinymce/plugins/pagebreak",
"tinymce/plugins/paste",
"tinymce/plugins/preview",
"tinymce/plugins/print",
"tinymce/plugins/save",
"tinymce/plugins/searchreplace",
"tinymce/plugins/spellchecker",
"tinymce/plugins/tabfocus",
"tinymce/plugins/table",
"tinymce/plugins/template",
"tinymce/plugins/textcolor",
"tinymce/plugins/textpattern",
"tinymce/plugins/visualblocks",
"tinymce/plugins/visualchars",
"tinymce/plugins/wordcount",
"tinymce/themes/silver",
"tinymce/tinymce",
"vditor",
"vue",
"vue-i18n",
"vue-print-nb-jeecg/src/printarea",
"vue-router",
"vue-types",
"vxe-table",
"vxe-table-plugin-antd",
"xe-utils",
"xss"
]
}
}

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 894 B

After

Width:  |  Height:  |  Size: 430 B

File diff suppressed because one or more lines are too long

View File

@ -23,6 +23,7 @@ export interface LoginResultModel {
userId: string | number;
token: string;
role: RoleInfo;
userInfo?: any
}
/**

View File

@ -78,7 +78,7 @@ export function phoneLoginApi(params: LoginParams, mode: ErrorMessageMode = 'mod
* @description: getUserInfo
*/
export function getUserInfo() {
return defHttp.get<GetUserInfoModel>({ url: Api.GetUserInfo }, { errorMessageMode: 'none' }).catch((e) => {
return defHttp.get<GetUserInfoModel>({ url: Api.GetUserInfo }, {}).catch((e) => {
// update-begin--author:zyf---date:20220425---for:【VUEN-76】捕获接口超时异常,跳转到登录界面
if (e && (e.message.includes('timeout') || e.message.includes('401'))) {
//接口不通时跳转到登录界面

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
<?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="1671872759315" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="29438" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128"><path d="M136.005284 399.993557h751.9874c35.198978 0 63.999619 28.79861 63.99962 63.999619v495.992986c0 35.198978-28.79861 63.999619-63.99962 63.999619H136.005284c-35.198978 0-63.999619-28.79861-63.999619-63.999619V463.993176c0-35.198978 28.800641-63.999619 63.999619-63.999619z" fill="#ECC45C" p-id="29439"></path><path d="M136.005284 367.993748h751.9874c35.198978 0 63.999619 28.79861 63.99962 63.999619v31.999809c0 35.198978-28.79861 63.999619-63.99962 63.99962H136.005284c-35.198978 0-63.999619-28.79861-63.999619-63.99962v-31.999809c0-35.198978 28.800641-63.999619 63.999619-63.999619z" fill="#F8D578" p-id="29440"></path><path d="M232.002682 415.993462c-39.998746 0-71.998556 14.399305-71.998556 31.99981s31.99981 31.99981 71.998556 31.999809 71.998556-14.399305 71.998556-31.999809c0.002031-17.600505-31.997778-31.99981-71.998556-31.99981z m559.992605 0c-39.998746 0-71.998556 14.399305-71.998556 31.99981s31.99981 31.99981 71.998556 31.999809 71.998556-14.399305 71.998556-31.999809S831.994033 415.993462 791.995287 415.993462zM512 655.990003c66.398488 0 119.99827 53.599783 119.99827 119.99827s-53.599783 119.99827-119.99827 119.99827-119.99827-53.599783-119.99827-119.99827 53.597751-119.99827 119.99827-119.99827z" fill="#D4B053" p-id="29441"></path><path d="M512 687.989812c48.800014 0 87.998461 39.198446 87.998461 87.998461s-39.198446 87.998461-87.998461 87.998461-87.998461-39.198446-87.998461-87.998461a87.742527 87.742527 0 0 1 87.998461-87.998461z" fill="#E4E7E7" p-id="29442"></path><path d="M512 735.987495c8.799237 0 15.999905 7.200668 15.999905 15.999905v47.999714c0 8.799237-7.200668 15.999905-15.999905 15.999905s-15.999905-7.200668-15.999905-15.999905v-47.999714c0-8.799237 7.198637-15.999905 15.999905-15.999905z" fill="#324D5B" p-id="29443"></path><path d="M512 0C326.401511 0 176.004031 150.39748 176.004031 335.993938v95.999429c0 17.600505 24.799141 31.99981 55.998651 31.999809s55.998651-14.399305 55.998651-31.999809v-95.999429c0-123.997739 99.998897-223.996636 223.996636-223.996636s223.996636 99.998897 223.996636 223.996636v95.999429c0 17.600505 24.799141 31.99981 55.998651 31.999809s55.998651-14.399305 55.998651-31.999809v-95.999429C847.993938 150.39748 697.596458 0 512 0z" fill="#E4E7E7" p-id="29444"></path><path d="M512 63.999619c-150.39748 0-271.99635 121.59887-271.99635 271.99635v127.997207c27.200041-2.4009 47.999714-15.199605 47.999714-31.999809v-95.999429c0-123.997739 99.998897-223.996636 223.996636-223.996636s223.996636 99.998897 223.996636 223.996636v95.999429c0 15.999905 20.799673 29.59891 47.999714 31.999809v-127.997207c-0.002031-150.399511-121.59887-271.99635-271.99635-271.99635z" fill="#CDCFCF" p-id="29445"></path></svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 536 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@ -0,0 +1,365 @@
::-webkit-input-placeholder {
/* WebKit browsers */
color: #868686;
font-size: 15px;
}
::-moz-placeholder {
/* Mozilla Firefox 19+ */
color: #868686;
font-size: 15px;
}
:-ms-input-placeholder {
/* Internet Explorer 10+ */
color: #868686;
font-size: 15px;
}
input:-webkit-autofill {
transition: background-color 5000s ease-in-out 0s;
}
html {
scroll-behavior: smooth;
}
html,
body {
color: #333;
margin: 0;
height: 100%;
font-family: 'Myriad Set Pro', 'Helvetica Neue', Helvetica, Arial, Verdana, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
font-weight: normal;
}
* {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
a {
text-decoration: none;
color: #000;
}
a,
label,
button,
input,
select {
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
img {
max-width: 100%;
height: auto;
display: block;
border: 0;
}
body {
background: #e3f0ff;
color: #666;
}
html,
body,
div,
dl,
dt,
dd,
ol,
ul,
li,
h1,
h2,
h3,
h4,
h5,
h6,
p,
blockquote,
pre,
button,
fieldset,
form,
input,
legend,
textarea,
th,
td {
margin: 0;
padding: 0;
}
a {
text-decoration: none;
color: #08acee;
}
button {
outline: 0;
}
button,
input,
optgroup,
select,
textarea {
margin: 0;
font: inherit;
color: inherit;
outline: none;
}
li {
list-style: none;
}
a {
color: #666;
}
.clearfix::after {
clear: both;
content: '.';
display: block;
height: 0;
visibility: hidden;
}
.clearfix {
}
.divHeight {
width: 100%;
height: 10px;
background: #f5f5f5;
position: relative;
overflow: hidden;
}
.r-line {
position: relative;
}
.r-line:after {
content: '';
position: absolute;
z-index: 0;
top: 0;
right: 0;
height: 100%;
border-right: 1px solid #d9d9d9;
-webkit-transform: scaleX(0.5);
transform: scaleX(0.5);
-webkit-transform-origin: 100% 0;
transform-origin: 100% 0;
}
.b-line {
position: relative;
}
.b-line:after {
content: '';
position: absolute;
z-index: 2;
bottom: 0;
left: 0;
width: 100%;
height: 1px;
border-bottom: 1px solid #dedede;
-webkit-transform: scaleY(0.5);
transform: scaleY(0.5);
-webkit-transform-origin: 0 100%;
transform-origin: 0 100%;
}
.aui-arrow {
position: relative;
padding-right: 0.8rem;
}
.aui-arrow span {
font-size: 0.8rem;
color: #9b9b9b;
}
.aui-arrow:after {
content: ' ';
display: inline-block;
height: 6px;
width: 6px;
border-width: 2px 2px 0 0;
border-color: #848484;
border-style: solid;
-webkit-transform: matrix(0.71, 0.71, -0.71, 0.71, 0, 0);
transform: matrix(0.71, 0.71, -0.71, 0.71, 0, 0);
position: relative;
position: absolute;
top: 50%;
margin-top: -4px;
right: 2px;
border-radius: 1px;
}
.aui-flex {
display: -webkit-box;
display: -webkit-flex;
display: flex;
-webkit-box-align: center;
-webkit-align-items: center;
align-items: center;
position: relative;
}
.aui-flex-box {
-webkit-box-flex: 1;
-webkit-flex: 1;
flex: 1;
min-width: 0;
font-size: 14px;
color: #333;
}
/* 必要布局样式css */
.aui-flexView {
width: 100%;
height: 100%;
margin: 0 auto;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
-webkit-flex-direction: column;
-ms-flex-direction: column;
flex-direction: column;
}
.aui-scrollView {
width: 100%;
height: 100%;
-webkit-box-flex: 1;
-webkit-flex: 1;
-ms-flex: 1;
flex: 1;
overflow-y: auto;
overflow-x: hidden;
-webkit-overflow-scrolling: touch;
position: relative;
padding-bottom: 53px;
}
.aui-navBar {
height: 44px;
position: relative;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
z-index: 102;
background-color: #5064eb;
}
.aui-navBar-item {
height: 44px;
min-width: 15%;
-webkit-box-flex: 0;
-webkit-flex: 0 0 15%;
-ms-flex: 0 0 15%;
flex: 0 0 15%;
padding: 0 0.9rem;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-box-align: center;
-webkit-align-items: center;
-ms-flex-align: center;
align-items: center;
font-size: 0.7rem;
white-space: nowrap;
overflow: hidden;
color: #808080;
position: relative;
}
.aui-navBar-item:first-child {
-webkit-box-ordinal-group: 2;
-webkit-order: 1;
-ms-flex-order: 1;
order: 1;
margin-right: -25%;
font-size: 0.9rem;
font-weight: bold;
}
.aui-navBar-item:last-child {
-webkit-box-ordinal-group: 4;
-webkit-order: 3;
-ms-flex-order: 3;
order: 3;
-webkit-box-pack: end;
-webkit-justify-content: flex-end;
-ms-flex-pack: end;
justify-content: flex-end;
}
.aui-center {
-webkit-box-ordinal-group: 3;
-webkit-order: 2;
-ms-flex-order: 2;
order: 2;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-box-pack: center;
-webkit-justify-content: center;
-ms-flex-pack: center;
justify-content: center;
-webkit-box-align: center;
-webkit-align-items: center;
-ms-flex-align: center;
align-items: center;
height: 44px;
width: 80%;
margin-left: 22%;
}
.aui-center-title {
text-align: center;
width: 100%;
white-space: nowrap;
overflow: hidden;
display: block;
text-overflow: ellipsis;
font-size: 0.95rem;
color: #fff;
font-weight: 500;
}
.icon {
width: 20px;
height: 20px;
display: block;
border: none;
float: left;
background-size: 20px;
background-repeat: no-repeat;
position: relative;
}
.login-background-img {
background-image: url(../icon/jeecg_bg.png);
background-size: cover;
background-position: top center;
background-repeat: no-repeat;
}

View File

@ -0,0 +1,612 @@
.aui-content {
padding: 40px 60px;
min-height: 100vh;
}
.aui-container {
max-width: 1000px;
margin: 0 auto;
box-shadow: 0 4px 8px 1px rgba(0, 0, 0, 0.2);
position: fixed;
top: 50%;
left: 50%;
width: 92%;
height: auto;
-webkit-transform: translateX(-50%) translateY(-50%);
-moz-transform: translateX(-50%) translateY(-50%);
-ms-transform: translateX(-50%) translateY(-50%);
transform: translateX(-50%) translateY(-50%);
-webkit-transform: translateX(-50%) translateY(-50%);
}
.aui-form {
width: 100%;
background: #eee;
display: -webkit-box;
display: -moz-box;
display: -ms-flexbox;
display: -webkit-flex;
display: flex;
}
.aui-image {
padding: 180px 80px;
flex-basis: 60%;
-webkit-flex-basis: 60%;
background-color: #0198cd;
background-image: url(../icon/jeecg_ad.png);
background-size: cover;
}
.aui-image-text {
top: 50%;
left: 50%;
width: 100%;
}
.aui-formBox {
flex-basis: 40%;
-webkit-flex-basis: 40%;
box-sizing: border-box;
padding: 30px 20px;
background: #fff;
box-shadow: 2px 9px 49px -17px rgba(0, 0, 0, 0.1);
}
.aui-logo {
width: 180px;
height: 80px;
position: absolute;
top: 2%;
left: 8%;
z-index: 4;
}
.aui-account-line {
padding-top: 20px;
padding-bottom: 40px;
}
.aui-code-line {
position: absolute;
right: 0;
top: 0;
border-left: 3px solid #fff;
height: 42px;
padding: 0 15px;
line-height: 40px;
font-size: 14px;
cursor: pointer;
}
.aui-eye {
position: absolute;
right: 20px;
top: 10px;
width: 20px;
cursor: pointer;
}
.aui-input-line {
background: #f5f5f9;
border-radius: 2px;
position: relative;
margin: 12px 0;
}
.aui-input-line input {
width: 100%;
padding: 12px 10px;
border: none;
color: #333333;
font-size: 14px;
background: unset;
padding-left: 40px;
}
.aui-input-line .icon {
position: absolute;
top: 10px;
left: 10px;
}
.icon-line-user {
background-image: url(../icon/icon-line-user.png);
}
.icon-line-tel {
background-image: url(../icon/icon-line-tel.png);
}
.icon-line-msg {
background-image: url(../icon/icon-line-msg.png);
}
.icon-line-pad {
background-image: url(../icon/icon-line-pad.png);
}
.aui-forgot .aui-input-line input {
padding-left: 20px;
}
.aui-forgot .aui-input-line {
background: none;
border: 1px solid #dbdbdb;
border-radius: 2px;
}
.aui-forgot .aui-input-line:focus {
border-color: #1b90ff;
}
.aui-forgot .aui-input-line:hover {
border-color: #1b90ff;
}
.aui-forgot .aui-input-line .aui-code-line {
border-left: 1px solid #dbdbdb;
height: 40px;
color: #1b90ff;
}
.aui-step-box {
width: 100%;
height: auto;
position: relative;
overflow: hidden;
margin-top: 50px;
margin-bottom: 20px;
}
.aui-step-box::after {
position: absolute;
top: 20px;
left: 50%;
width: 76%;
margin-left: -38%;
height: 1px;
background: #bcbcbc;
content: '';
}
.aui-step-item {
width: 33.333%;
float: left;
text-align: center;
position: relative;
z-index: 2;
}
.aui-step-tags em {
width: 40px;
height: 40px;
border: 8px solid #fff;
line-height: 1.3;
border-radius: 100px;
background: #bcbcbc;
display: block;
margin: 0 auto;
font-style: normal;
color: #fff;
font-size: 19px;
font-weight: 500;
}
.aui-step-tags p {
font-size: 14px;
color: #bcbcbc;
}
.activeStep .aui-step-tags em {
background: #1b90ff;
}
.activeStep .aui-step-tags p {
color: #1b90ff;
}
.aui-success {
position: absolute;
top: 50%;
left: 50%;
height: 80px;
width: 100%;
margin-top: -40px;
margin-left: -50%;
}
.aui-success-icon {
width: 40px;
margin: 0 auto;
}
.aui-success h3 {
width: 100%;
text-align: center;
color: #515151;
font-size: 18px;
padding-top: 20px;
}
.aui-form-nav {
text-align: center;
padding-bottom: 20px;
}
.aui-form-nav .aui-flex-box {
color: #040404;
font-size: 18px;
font-weight: 500;
cursor: pointer;
}
.aui-clear-left {
text-align: left;
}
.aui-clear-left .activeNav::after {
left: 18px;
}
.activeNav {
position: relative;
}
.activeNav::after {
content: '';
position: absolute;
z-index: 0;
bottom: -10px;
left: 50%;
margin-left: -15px;
width: 30px;
height: 4px;
background: #1b90ff;
border-radius: 100px;
}
.phone .aui-inputClear {
padding-left: 0;
}
.phone .aui-inputClear input {
//padding-left: 1px;
}
.phone .aui-inputClear .aui-code {
text-align: right;
width: auto;
bottom: 10px;
}
.phone .aui-inputClear .aui-code a {
color: #1b90ff;
font-size: 14px;
}
.phoneChina {
position: absolute;
bottom: 10px;
left: 0;
font-size: 14px;
color: #040404;
}
.phoneChina::after {
position: absolute;
right: -25px;
bottom: 0;
content: '';
background-image: url(../icon/icon_dow.png);
background-size: 18px;
width: 18px;
height: 18px;
}
.phoneChina:before {
position: absolute;
right: -42px;
bottom: -15px;
content: ' ';
background: #fff;
width: 18px;
height: 18px;
}
.aui-ewm {
width: 280px;
margin: 0 auto;
}
.aui-formEwm {
padding: 50px 40px 55px 40px;
}
.aui-inputClear {
width: 100%;
border-bottom: 1px solid #cccccc;
position: relative;
padding-left: 20px;
background: #fff;
margin-bottom: 8px;
margin-top: 20px;
}
.aui-inputClear .icon {
position: absolute;
top: 10px;
left: 0;
}
.aui-inputClear input {
width: 100%;
padding: 10px;
border: none;
color: #333333;
font-size: 14px;
background: none;
}
.aui-code {
position: absolute;
right: 8px;
bottom: 0;
width: 115px;
cursor: pointer;
}
.icon-code {
background-image: url(../icon/icon-user.png);
}
.icon-password {
background-image: url(../icon/icon-password.png);
}
.icon-code {
background-image: url(../icon/icon-code.png);
}
.aui-inputClear:focus {
border-bottom: 1px solid #1b90ff;
}
.aui-inputClear:hover {
border-bottom: 1px solid #1b90ff;
}
.aui-choice {
position: relative;
font-size: 12px;
display: -webkit-box;
display: -webkit-flex;
display: flex;
-webkit-box-align: center;
-webkit-align-items: center;
align-items: center;
position: relative;
color: #040404;
}
.aui-choice input {
width: 14px;
height: 14px;
cursor: pointer;
}
.aui-forget a {
color: #1b90ff;
font-size: 12px;
}
.aui-forget a:hover {
text-decoration: underline;
}
.aui-formButton {
padding-top: 10px;
}
.aui-formButton a {
height: 42px;
padding: 10px 15px;
font-size: 14px;
border-radius: 8px;
border-color: #67b5ff;
background: #1b90ff;
width: 100%;
cursor: pointer;
border: none;
color: #fff;
margin: 8px 0;
display: block;
text-align: center;
}
.aui-formButton a:focus {
opacity: 0.9;
}
.aui-formButton a:hover {
opacity: 0.9;
}
.aui-formButton .aui-linek-code {
background: #fff;
color: #3c3c3c;
border: 1px solid #dbdbdb;
}
.aui-formButton .aui-linek-code:hover {
color: #1b90ff;
border: 1px solid #1b90ff;
}
.aui-third-text {
font-size: 12px;
color: #3c3c3c;
margin-top: 25px;
margin-bottom: 25px;
}
.aui-third-text span {
color: #afafaf;
display: block;
width: 38%;
margin: 0 auto;
text-align: center;
position: relative;
background: #fff;
z-index: 100;
font-size: 12px;
}
.aui-third-border {
position: relative;
}
.aui-third-border::after {
content: '';
position: absolute;
z-index: 0;
top: 8px;
left: 0;
width: 100%;
height: 1px;
border-top: 1px solid #d9d9d9;
-webkit-transform: scaleY(0.5);
transform: scaleY(0.5);
-webkit-transform-origin: 0 100%;
transform-origin: 0 100%;
}
.aui-third-login {
width: 30px;
height: 30px;
margin: 0 auto;
border-radius: 100px;
}
.aui-third-login a {
font-size: 22px;
margin: 0 auto;
border-radius: 100px;
display: inline-block;
color: #888;
}
.aui-third-login a:hover {
color: #1b90ff;
cursor: pointer;
}
.aui-third-login:hover {
cursor: pointer;
}
@media (max-width: 320px) {
.aui-form {
flex-direction: column;
}
.aui-image {
order: 2;
display: none;
}
.aui-container {
width: 100%;
max-width: 550px;
margin-top: 10px;
}
.aui-content {
justify-content: initial;
width: 100%;
padding: 20px;
}
}
@media (min-width: 321px) and (max-width: 375px) {
.aui-form {
flex-direction: column;
}
.aui-image {
order: 2;
display: none;
}
.aui-container {
width: 90%;
max-width: 550px;
}
.aui-content {
justify-content: initial;
width: 100%;
padding: 20px;
}
}
@media (min-width: 375px) and (max-width: 425px) {
.aui-form {
flex-direction: column;
}
.aui-image {
order: 2;
display: none;
}
.aui-container {
width: 90%;
max-width: 550px;
}
.aui-content {
justify-content: initial;
width: 100%;
padding: 40px;
}
}
@media (min-width: 425px) and (max-width: 768px) {
.aui-form {
flex-direction: column;
}
.aui-image {
order: 2;
display: none;
}
.aui-container {
width: 90%;
max-width: 550px;
}
.aui-content {
justify-content: initial;
width: 100%;
padding: 40px;
}
.aui-step-box::after {
width: 70%;
margin-left: -35%;
}
}
@media only screen and (max-width: 767px) {
.aui-logo {
top: 3%;
}
}
@media screen and (max-width: 300px) {
.aui-logo {
top: 3%;
}
}

View File

@ -14,15 +14,18 @@
>
<a-popconfirm v-if="popconfirm && item.popConfirm" v-bind="getPopConfirmAttrs(item.popConfirm)">
<template #icon v-if="item.popConfirm.icon">
<Icon :icon="item.popConfirm.icon" />
<Icon v-if="item.iconColor" :icon="item.popConfirm.icon" :color="item.iconColor"/>
<Icon v-else :icon="item.popConfirm.icon"/>
</template>
<div class="dropdown-event-area">
<Icon :icon="item.icon" v-if="item.icon" />
<Icon :icon="item.icon" v-if="item.icon && item.iconColor" :color="item.iconColor"/>
<Icon :icon="item.icon" v-else-if="item.icon"/>
<span class="ml-1">{{ item.text }}</span>
</div>
</a-popconfirm>
<template v-else>
<Icon :icon="item.icon" v-if="item.icon" />
<Icon :icon="item.icon" v-if="item.icon && item.iconColor" :color="item.iconColor"/>
<Icon :icon="item.icon" v-else-if="item.icon"/>
<span class="ml-1">{{ item.text }}</span>
</template>
</a-menu-item>

View File

@ -53,6 +53,7 @@
import { basicProps } from './props';
import { useDesign } from '/@/hooks/web/useDesign';
import dayjs from 'dayjs';
import { useDebounceFn } from '@vueuse/core';
export default defineComponent({
name: 'BasicForm',
@ -239,13 +240,19 @@
propsRef.value = deepMerge(unref(propsRef) || {}, formProps);
}
//update-begin-author:taoyan date:2022-11-28 for: QQYUN-3121 #scott 8
const onFormSubmitWhenChange = useDebounceFn(handleSubmit, 300);
function setFormModel(key: string, value: any) {
formModel[key] = value;
const { validateTrigger } = unref(getBindValue);
if (!validateTrigger || validateTrigger === 'change') {
validateFields([key]).catch((_) => {});
}
if(props.autoSearch === true){
onFormSubmitWhenChange();
}
}
//update-end-author:taoyan date:2022-11-28 for: QQYUN-3121 #scott 8
function handleEnterPress(e: KeyboardEvent) {
const { autoSubmitOnEnter } = unref(getProps);

View File

@ -58,7 +58,9 @@ import JSearchSelect from './jeecg/components/JSearchSelect.vue';
import JAddInput from './jeecg/components/JAddInput.vue';
import { Time } from '/@/components/Time';
import JRangeNumber from './jeecg/components/JRangeNumber.vue';
import UserSelect from './jeecg/components/userSelect/index.vue';
import JRangeDate from './jeecg/components/JRangeDate.vue'
import JRangeTime from './jeecg/components/JRangeTime.vue'
const componentMap = new Map<ComponentType, Component>();
@ -126,7 +128,9 @@ componentMap.set('JUpload', JUpload);
componentMap.set('JSearchSelect', JSearchSelect);
componentMap.set('JAddInput', JAddInput);
componentMap.set('JRangeNumber', JRangeNumber);
componentMap.set('UserSelect', UserSelect);
componentMap.set('RangeDate', JRangeDate);
componentMap.set('RangeTime', JRangeTime);
export function add(compName: ComponentType, component: Component) {
componentMap.set(compName, component);

View File

@ -5,8 +5,7 @@
<RadioGroup v-bind="attrs" v-model:value="state" button-style="solid">
<template v-for="item in getOptions" :key="`${item.value}`">
<RadioButton :value="item.value" :disabled="item.disabled">
<Icon v-if="item.icon" :icon="item.icon" />
{{ item.label ? item.label : '' }}
{{ item.label }}
</RadioButton>
</template>
</RadioGroup>
@ -17,13 +16,8 @@
import { isString } from '/@/utils/is';
import { useRuleFormItem } from '/@/hooks/component/useFormItem';
import { useAttrs } from '/@/hooks/core/useAttrs';
import { Icon } from '/@/components/Icon';
type OptionsItem = {
icon?: string;
label?: string;
value: string | number | boolean;
disabled?: boolean;
};
type OptionsItem = { label: string; value: string | number | boolean; disabled?: boolean };
type RadioItem = string | OptionsItem;
export default defineComponent({
@ -31,7 +25,6 @@
components: {
RadioGroup: Radio.Group,
RadioButton: Radio.Button,
Icon,
},
props: {
value: {

View File

@ -146,24 +146,26 @@
function handleChange(e) {
const { mode } = unref<Recordable>(getBindValue);
let changeValue:any;
//
//update-begin---author:wangshuai ---date:20230216 for[QQYUN-4290],change------------
//statechange
if (mode === 'multiple') {
state.value = e?.target?.value ?? e;
} else {
state.value = [e?.target?.value ?? e];
}
changeValue = e?.target?.value ?? e;
//
if (state.value == null || state.value === '') {
state.value = [];
if (changeValue == null || changeValue === '') {
changeValue = [];
}
if (Array.isArray(state.value)) {
state.value = state.value.filter((item) => item != null && item !== '');
if (Array.isArray(changeValue)) {
changeValue = changeValue.filter((item) => item != null && item !== '');
}
//update-begin---author:wangshuai ---date:20221123 for------------
if(mode !== 'multiple' && state.value && state.value.length>0){
state.value = state.value[0];
} else {
changeValue = e?.target?.value ?? e;
}
//update-end---author:wangshuai ---date:20221123 for--------------
state.value = changeValue;
//update-end---author:wangshuai ---date:20230216 for[QQYUN-4290],change------------
// nextTick(() => formItemContext.onFieldChange());
}

View File

@ -7,7 +7,7 @@
import { propTypes } from '/@/utils/propTypes';
import { Form } from 'ant-design-vue';
const placeholder = ['最小值', '最大值']
const placeholder = ['开始日期', '结束日期']
/**
* 用于范围查询
*/

View File

@ -0,0 +1,53 @@
<template>
<a-time-range-picker v-model:value="rangeValue" @change="handleChange" :placeholder="placeholder" :valueFormat="format" :format="format"/>
</template>
<script>
import { defineComponent, ref, watch } from 'vue';
import { propTypes } from '/@/utils/propTypes';
import { Form } from 'ant-design-vue';
const placeholder = ['开始时间', '结束时间']
/**
* 用于时间-time组件的范围查询
*/
export default defineComponent({
name: "JRangeTime",
props:{
value: propTypes.string.def(''),
format: propTypes.string.def('HH:mm:ss'),
placeholder: propTypes.string.def(''),
},
emits:['change', 'update:value'],
setup(props, {emit}){
const rangeValue = ref([])
const formItemContext = Form.useInjectFormItemContext();
watch(()=>props.value, (val)=>{
if(val){
rangeValue.value = val.split(',')
}else{
rangeValue.value = []
}
}, {immediate: true});
function handleChange(arr){
let str = ''
if(arr && arr.length>0){
if(arr[1] && arr[0]){
str = arr.join(',')
}
}
emit('change', str);
emit('update:value', str);
formItemContext.onFieldChange();
}
return {
rangeValue,
placeholder,
handleChange
}
}
});
</script>

View File

@ -13,6 +13,7 @@
v-bind="attrs"
@change="onChange"
@search="onSearch"
:tree-checkable="treeCheckAble"
>
</a-tree-select>
</template>
@ -50,8 +51,12 @@
reload: propTypes.number.def(1),
//update-begin-author:taoyan date:2022-11-8 for: issues/4173 Online JTreeSelectchangeOptions
url: propTypes.string.def(''),
params: propTypes.object.def({})
params: propTypes.object.def({}),
//update-end-author:taoyan date:2022-11-8 for: issues/4173 Online JTreeSelectchangeOptions
//update-begin---author:wangshuai date: 20230202 for:
//
treeCheckAble: propTypes.bool.def(false),
//update-end---author:wangshuai date: 20230202 for:
});
const attrs = useAttrs();
const emit = defineEmits(['change', 'update:value']);

View File

@ -0,0 +1,282 @@
<template>
<BasicModal
@register="register"
:getContainer="getContainer"
:canFullscreen="false"
:title="title"
:width="500"
destroyOnClose
@ok="handleOk"
wrapClassName="j-user-select-modal2" >
<div style="position: relative; min-height: 350px">
<div style="width: 100%">
<a-input v-model:value="searchText" allowClear style="width: 100%" placeholder="搜索">
<template #prefix>
<SearchOutlined style="color: #c0c0c0" />
</template>
</a-input>
</div>
<!-- tabs -->
<div class="modal-select-list-container">
<div class="scroll">
<div class="content" style="right: -10px">
<label class="item" v-for="item in showDataList" @click="(e)=>onSelect(e, item)">
<a-checkbox v-model:checked="item.checked">
<span>{{ item.name }}</span>
</a-checkbox>
</label>
</div>
</div>
</div>
<!-- 选中用户 -->
<div class="selected-users" style="width: 100%; overflow-x: hidden">
<SelectedUserItem v-for="item in selectedList" :info="item" @unSelect="unSelect" />
</div>
</div>
</BasicModal>
</template>
<script lang="ts">
import { BasicModal, useModalInner } from '/@/components/Modal';
import { SearchOutlined, CloseOutlined } from '@ant-design/icons-vue';
import SelectedUserItem from '../userSelect/SelectedUserItem.vue';
import { defHttp } from '/@/utils/http/axios';
import { computed, ref, toRaw, watch } from 'vue';
export default {
name: 'PositionSelectModal',
components: {
BasicModal,
SearchOutlined,
CloseOutlined,
SelectedUserItem,
},
props: {
multi: {
type: Boolean,
default: true,
},
getContainer: {
type: Function,
default: null,
},
title:{
type: String,
default: '',
},
type: {
type: String,
default: 'sys_position',
},
appId: {
type: String,
default: '',
}
},
emits: ['selected', 'register'],
setup(props, { emit }) {
const searchText = ref('');
const selectedIdList = computed(() => {
let arr = selectedList.value;
if (!arr || arr.length == 0) {
return [];
} else {
return arr.map((k) => k.id);
}
});
watch(()=>props.appId, async (val)=>{
if(val){
await loadDataList();
}
}, {immediate: true});
//
const [register] = useModalInner(() => {
let list = dataList.value;
if(!list || list.length ==0 ){
}
for(let item of list){
item.checked = false
}
});
//
function handleOk() {
let arr = toRaw(selectedIdList.value);
emit('selected', arr);
}
const dataList = ref<any[]>([]);
const showDataList = computed(()=>{
let list = dataList.value;
if(!list || list.length ==0 ){
return []
}
let text = searchText.value;
if(!text){
return list
}
return list.filter(item=>item.name.indexOf(text)>=0)
});
const selectedList = computed(()=>{
let list = dataList.value;
if(!list || list.length ==0 ){
return []
}
return list.filter(item=>item.checked)
});
function unSelect(id) {
let list = dataList.value;
if(!list || list.length ==0 ){
return;
}
let arr = list.filter(item=>item.id == id);
arr[0].checked = false;
}
async function loadDataList() {
let params = {
pageNo: 1,
pageSize: 200,
column: 'createTime',
order: 'desc'
};
const url = '/sys/position/list'
const data = await defHttp.get({ url, params }, { isTransformResponse: false });
if (data.success) {
const { records } = data.result;
let arr:any[] = [];
if(records && records.length>0){
for(let item of records){
arr.push({
id: item.id,
name: item.name || item.roleName,
selectType: props.type,
checked: false
})
}
}
dataList.value = arr;
} else {
console.error(data.message);
}
console.log('loadDataList', data);
}
function onSelect(e, item) {
prevent(e);
console.log('onselect');
item.checked = !item.checked;
}
function prevent(e) {
e.preventDefault();
e.stopPropagation();
}
return {
register,
showDataList,
searchText,
handleOk,
selectedList,
selectedIdList,
unSelect,
onSelect
};
},
};
</script>
<style scoped lang="less">
.modal-select-list-container{
height: 352px;
margin-top: 12px;
overflow: auto;
.scroll{
height: 100%;
position: relative;
width: 100%;
overflow: hidden;
.content{
bottom: 0;
left: 0;
overflow: scroll;
overflow-x: hidden;
position: absolute;
right: 0;
top: 0;
.item{
padding: 7px 5px;
cursor: pointer;
display: block;
&:hover{
background-color: #f5f5f5;
}
}
}
}
}
</style>
<style lang="less">
.j-user-select-modal2 {
.depart-select {
.ant-select-selector {
color: #fff !important;
background-color: #409eff !important;
border-radius: 5px !important;
}
.ant-select-selection-item,
.ant-select-arrow {
color: #fff !important;
}
}
.my-search {
position: absolute;
top: 14px;
z-index: 1;
&.all-width {
width: 100%;
}
.anticon {
cursor: pointer;
&:hover {
color: #0a8fe9 !important;
}
}
.hidden {
display: none;
}
}
.my-tabs {
}
.selected-users {
display: flex;
flex-wrap: wrap;
flex-direction: row;
padding-top: 15px;
}
.scroll-container {
padding-bottom: 0 !important;
}
}
</style>

View File

@ -0,0 +1,282 @@
<template>
<BasicModal
@register="register"
:getContainer="getContainer"
:canFullscreen="false"
:title="title"
:width="500"
destroyOnClose
@ok="handleOk"
wrapClassName="j-user-select-modal2" >
<div style="position: relative; min-height: 350px">
<div style="width: 100%">
<a-input v-model:value="searchText" allowClear style="width: 100%" placeholder="搜索">
<template #prefix>
<SearchOutlined style="color: #c0c0c0" />
</template>
</a-input>
</div>
<!-- tabs -->
<div class="modal-select-list-container">
<div class="scroll">
<div class="content" style="right: -10px">
<label class="item" v-for="item in showDataList" @click="(e)=>onSelect(e, item)">
<a-checkbox v-model:checked="item.checked">
<span class="text">{{ item.name }}</span>
</a-checkbox>
</label>
</div>
</div>
</div>
<!-- 选中用户 -->
<div class="selected-users" style="width: 100%; overflow-x: hidden">
<SelectedUserItem v-for="item in selectedList" :info="item" @unSelect="unSelect" />
</div>
</div>
</BasicModal>
</template>
<script lang="ts">
import { BasicModal, useModalInner } from '/@/components/Modal';
import { SearchOutlined, CloseOutlined } from '@ant-design/icons-vue';
import SelectedUserItem from '../userSelect/SelectedUserItem.vue';
import { defHttp } from '/@/utils/http/axios';
import { computed, ref, toRaw, watch } from 'vue';
export default {
name: 'RoleSelectModal',
components: {
BasicModal,
SearchOutlined,
CloseOutlined,
SelectedUserItem,
},
props: {
multi: {
type: Boolean,
default: true,
},
getContainer: {
type: Function,
default: null,
},
title:{
type: String,
default: '',
},
type: {
type: String,
default: 'sys_role',
},
appId: {
type: String,
default: '',
}
},
emits: ['selected', 'register'],
setup(props, { emit }) {
const searchText = ref('');
const selectedIdList = computed(() => {
let arr = selectedList.value;
if (!arr || arr.length == 0) {
return [];
} else {
return arr.map((k) => k.id);
}
});
watch(()=>props.appId, async (val)=>{
if(val){
await loadDataList();
}
}, {immediate: true});
//
const [register] = useModalInner(() => {
let list = dataList.value;
if(!list || list.length ==0 ){
}
for(let item of list){
item.checked = false
}
});
//
function handleOk() {
let arr = toRaw(selectedIdList.value);
emit('selected', arr);
}
const dataList = ref<any[]>([]);
const showDataList = computed(()=>{
let list = dataList.value;
if(!list || list.length ==0 ){
return []
}
let text = searchText.value;
if(!text){
return list
}
return list.filter(item=>item.name.indexOf(text)>=0)
});
const selectedList = computed(()=>{
let list = dataList.value;
if(!list || list.length ==0 ){
return []
}
return list.filter(item=>item.checked)
});
function unSelect(id) {
let list = dataList.value;
if(!list || list.length ==0 ){
return;
}
let arr = list.filter(item=>item.id == id);
arr[0].checked = false;
}
async function loadDataList() {
let params = {
pageNo: 1,
pageSize: 200,
column: 'createTime',
order: 'desc'
};
const url = '/sys/role/listByTenant';
const data = await defHttp.get({ url, params }, { isTransformResponse: false });
if (data.success) {
const { records } = data.result;
let arr:any[] = [];
if(records && records.length>0){
for(let item of records){
arr.push({
id: item.id,
name: item.name || item.roleName,
selectType: props.type,
checked: false
})
}
}
dataList.value = arr;
} else {
console.error(data.message);
}
console.log('loadDataList', data);
}
function onSelect(e, item) {
prevent(e);
console.log('onselect');
item.checked = !item.checked;
}
function prevent(e) {
e.preventDefault();
e.stopPropagation();
}
return {
register,
showDataList,
searchText,
handleOk,
selectedList,
selectedIdList,
unSelect,
onSelect
};
},
};
</script>
<style scoped lang="less">
.modal-select-list-container{
height: 352px;
margin-top: 12px;
overflow: auto;
.scroll{
height: 100%;
position: relative;
width: 100%;
overflow: hidden;
.content{
bottom: 0;
left: 0;
overflow: scroll;
overflow-x: hidden;
position: absolute;
right: 0;
top: 0;
.item{
padding: 7px 5px;
cursor: pointer;
display: block;
&:hover{
background-color: #f5f5f5;
}
}
}
}
}
</style>
<style lang="less">
.j-user-select-modal2 {
.depart-select {
.ant-select-selector {
color: #fff !important;
background-color: #409eff !important;
border-radius: 5px !important;
}
.ant-select-selection-item,
.ant-select-arrow {
color: #fff !important;
}
}
.my-search {
position: absolute;
top: 14px;
z-index: 1;
&.all-width {
width: 100%;
}
.anticon {
cursor: pointer;
&:hover {
color: #0a8fe9 !important;
}
}
.hidden {
display: none;
}
}
.my-tabs {
}
.selected-users {
display: flex;
flex-wrap: wrap;
flex-direction: row;
padding-top: 15px;
}
.scroll-container {
padding-bottom: 0 !important;
}
}
</style>

View File

@ -0,0 +1,144 @@
<template>
<div class="user-selected-item">
<div
style="
display: flex;
flex-direction: row;
height: 24px;
border-radius: 12px;
padding-right: 10px;
vertical-align: middle;
background-color: #f5f5f5;
"
>
<span style="width: 24px; height: 24px; line-height: 20px; margin-right: 3px; display: inline-block">
<a-avatar v-if="info.avatar" :src="getFileAccessHttpUrl(info.avatar)" :size="24"></a-avatar>
<a-avatar v-else-if="info.selectType == 'sys_role'" :size="24" style="background-color: rgb(255, 173, 0);">
<template #icon>
<team-outlined style="font-size: 16px"/>
</template>
</a-avatar>
<a-avatar v-else-if="info.selectType == 'sys_position'" :size="24" style="background-color: rgb(245, 34, 45);">
<template #icon>
<TagsOutlined style="font-size: 16px"/>
</template>
</a-avatar>
<a-avatar :size="24" v-else>
<template #icon><UserOutlined /></template>
</a-avatar>
</span>
<div style="height: 24px; line-height: 24px" class="ellipsis">
{{ info.realname || info.name }}
</div>
<div v-if="showClose" class="icon-close">
<CloseOutlined @click="removeSelect"/>
</div>
</div>
<div v-if="!showClose" class="icon-remove">
<MinusCircleFilled @click="removeSelect" />
</div>
</div>
</template>
<script>
import { UserOutlined, CloseOutlined, MinusCircleFilled, TagsOutlined, TeamOutlined } from '@ant-design/icons-vue';
import {computed} from 'vue'
import { getFileAccessHttpUrl } from '/@/utils/common/compUtils';
export default {
name: 'SelectedUserItem',
components: {
UserOutlined,
MinusCircleFilled,
CloseOutlined,
TagsOutlined,
TeamOutlined
},
props: {
info: {
type: Object,
default: () => {},
},
//
query:{
type: Boolean,
default: false,
}
},
emits: ['unSelect'],
setup(props, { emit }) {
function removeSelect(e) {
e.preventDefault();
e.stopPropagation();
emit('unSelect', props.info.id);
}
const showClose = computed(()=>{
if(props.query===true){
return true;
}else{
return false;
}
});
return {
showClose,
removeSelect,
getFileAccessHttpUrl
};
},
};
</script>
<style lang="less">
.user-selected-item {
position: relative;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
margin-right: 8px;
height: 30px;
border-radius: 12px;
line-height: 30px;
vertical-align: middle;
.ellipsis {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.icon-remove {
position: absolute;
top: -10px;
right: -4px;
font-size: 18px;
width: 15px;
height: 15px;
cursor: pointer;
display: none;
}
.icon-close{
height: 22px;
line-height: 24px;
font-size: 10px;
font-weight: bold;
margin-left: 7px;
&:hover{
color: #0a8fe9;
}
}
&:hover {
.icon-remove {
display: block;
}
}
}
</style>

View File

@ -0,0 +1,175 @@
<template>
<a-list item-layout="horizontal" :data-source="showDataList">
<template #renderItem="{ item }">
<a-list-item style="padding: 3px 0">
<div class="user-select-user-info" @click="(e) => onClickUser(e, item)">
<div>
<a-checkbox v-model:checked="checkStatus[item.id]" />
</div>
<div>
<a-avatar v-if="item.avatar" :src="getFileAccessHttpUrl(item.avatar)"></a-avatar>
<a-avatar v-else>
<template #icon><UserOutlined /></template>
</a-avatar>
</div>
<div :style="nameStyle">
{{ item.realname }}
</div>
<div :style="departStyle">
{{ item.orgCodeTxt }}
</div>
</div>
</a-list-item>
</template>
</a-list>
</template>
<script lang="ts">
import { UserOutlined } from '@ant-design/icons-vue';
import { computed, toRaw, reactive, watchEffect, ref } from 'vue';
import { getFileAccessHttpUrl } from '/@/utils/common/compUtils';
export default {
name: 'UserList',
props: {
dataList: {
type: Array,
default: () => [],
},
//
depart: {
type: Boolean,
default: false,
},
selectedIdList: {
type: Array,
default: () => [],
},
excludeUserIdList:{
type: Array,
default: () => [],
}
},
components: {
UserOutlined,
},
emits: ['selected', 'unSelect'],
setup(props, { emit }) {
function onClickUser(e, user) {
e && prevent(e);
let status = checkStatus[user.id];
if (status === true) {
emit('unSelect', user.id);
} else {
emit('selected', toRaw(user));
}
}
function getTwoText(text) {
if (!text) {
return '';
} else {
return text.substr(0, 2);
}
}
const departStyle = computed(() => {
if (props.depart === true) {
//
return {
flex: 1,
};
} else {
return {
display: 'none',
};
}
});
const nameStyle = computed(() => {
if (props.depart === true) {
//
return {
width: '200px',
};
} else {
return {
flex: 1,
};
}
});
function onChangeChecked(e) {
console.error('onChangeChecked', e);
}
// const showDataList = ref<any[]>([])
const checkStatus = reactive<any>({});
watchEffect(() => {
let arr1 = props.dataList;
if (!arr1 || arr1.length === 0) {
return;
}
let idList = props.selectedIdList;
for (let item of arr1) {
if (idList.indexOf(item.id) >= 0) {
checkStatus[item.id] = true;
} else {
checkStatus[item.id] = false;
}
}
});
function prevent(e) {
e.preventDefault();
e.stopPropagation();
}
function records2DataList() {
let arr:any[] = [];
let excludeList = props.excludeUserIdList;
let records = props.dataList;
if(records && records.length>0){
for(let item of records){
if(excludeList.indexOf(item.id)<0){
arr.push({...item})
}
}
}
return arr;
}
const showDataList = computed(()=>{
let excludeList = props.excludeUserIdList;
if(excludeList && excludeList.length>0){
return records2DataList();
}
return props.dataList;
});
return {
onClickUser,
getTwoText,
departStyle,
nameStyle,
onChangeChecked,
checkStatus,
showDataList,
getFileAccessHttpUrl
};
},
};
</script>
<style lang="less">
.user-select-user-info {
display: flex;
> div {
height: 36px;
line-height: 36px;
margin-right: 10px;
}
}
</style>

View File

@ -0,0 +1,181 @@
<template>
<a-row>
<a-col :span="12">
<div :style="containerStyle">
<a-tree
v-if="treeData.length > 0"
:load-data="loadChildren"
showIcon
autoExpandParent
:treeData="treeData"
:selectedKeys="selectedKeys"
v-model:expandedKeys="expandedKeys"
@select="onSelect"
>
</a-tree>
</div>
</a-col>
<a-col :span="12" style="padding-left: 10px">
<div :style="containerStyle">
<user-list :excludeUserIdList="excludeUserIdList" :dataList="userDataList" :selectedIdList="selectedIdList" @selected="onSelectUser" @unSelect="unSelectUser" />
</div>
</a-col>
</a-row>
</template>
<script lang="ts">
import { defHttp } from '/@/utils/http/axios';
import { computed, ref, watch } from 'vue';
import UserList from './UserList.vue';
export default {
name: 'DepartUserList',
components: {
UserList,
},
props: {
searchText: {
type: String,
default: '',
},
selectedIdList: {
type: Array,
default: () => [],
},
excludeUserIdList:{
type: Array,
default: () => [],
}
},
emits: ['loaded', 'selected', 'unSelect'],
setup(props, { emit }) {
//export const queryById = (params) => defHttp.get({ url: Api.queryById, params }, { isTransformResponse: false });
async function loadDepartTree(pid?) {
const url = '/sys/sysDepart/queryDepartTreeSync';
let params = {};
if (pid) {
params['pid'] = pid;
}
const data = await defHttp.get({ url, params }, { isTransformResponse: false });
console.log('loadDepartTree', data);
return data;
}
async function initRoot() {
console.log('initRoot');
const data = await loadDepartTree();
if (data.success) {
let arr = data.result;
treeData.value = arr;
emitDepartOptions(arr);
} else {
console.error(data.message);
}
clear();
}
function emitDepartOptions(arr) {
let options = [];
if (arr && arr.length > 0) {
options = arr.map((k) => {
return {
value: k.id,
label: k.departName,
};
});
}
emit('loaded', options);
}
initRoot();
const treeData = ref<any[]>([]);
const selectedKeys = ref<string[]>([]);
const expandedKeys = ref<string[]>([]);
const selectedDepartId = ref('');
function onSelect(ids, e) {
let record = e.node.dataRef;
selectedKeys.value = [record.key];
let id = ids[0];
selectedDepartId.value = id;
loadUserList();
}
function clear() {
selectedDepartId.value = '';
}
async function loadChildren(treeNode) {
console.log('loadChildren', treeNode);
const data = await loadDepartTree(treeNode.eventKey);
if (data.success) {
let arr = data.result;
treeNode.dataRef.children = [...arr];
treeData.value = [...treeData.value];
} else {
console.error(data.message);
}
}
const maxHeight = ref(300);
maxHeight.value = window.innerHeight - 300;
const containerStyle = computed(() => {
return {
'overflow-y': 'auto',
'max-height': maxHeight.value + 'px',
};
});
const userDataList = ref<any[]>([]);
async function loadUserList() {
const url = '/sys/user/selectUserList';
let params = {
pageNo: 1,
pageSize: 10,
};
if (props.searchText) {
params['keyword'] = props.searchText;
}
if (selectedDepartId.value) {
params['departId'] = selectedDepartId.value;
}
const data = await defHttp.get({ url, params }, { isTransformResponse: false });
if (data.success) {
const { records } = data.result;
userDataList.value = records;
} else {
console.error(data.message);
}
console.log('depart-loadUserList', data);
}
watch(
() => props.searchText,
() => {
loadUserList();
}
);
function onSelectUser(info) {
emit('selected', info);
}
function unSelectUser(id) {
emit('unSelect', id);
}
return {
containerStyle,
treeData,
selectedKeys,
expandedKeys,
onSelect,
loadChildren,
onSelectUser,
unSelectUser,
userDataList,
};
},
};
</script>
<style scoped></style>

View File

@ -0,0 +1,136 @@
<template>
<a-row>
<a-col :span="12">
<div :style="containerStyle">
<a-tree v-if="treeData.length > 0" showIcon :treeData="treeData" :selectedKeys="selectedKeys" @select="onSelect"> </a-tree>
</div>
</a-col>
<a-col :span="12" style="padding-left: 10px">
<div :style="containerStyle">
<user-list :excludeUserIdList="excludeUserIdList" :dataList="userDataList" :selectedIdList="selectedIdList" @selected="onSelectUser" @unSelect="unSelectUser" />
</div>
</a-col>
</a-row>
</template>
<script lang="ts">
import { computed, ref, watch } from 'vue';
import { defHttp } from '/@/utils/http/axios';
import UserList from './UserList.vue';
export default {
name: 'RoleUserList',
components: {
UserList,
},
props: {
searchText: {
type: String,
default: '',
},
selectedIdList: {
type: Array,
default: () => [],
},
excludeUserIdList:{
type: Array,
default: () => [],
}
},
emits: ['selected', 'unSelect'],
setup(props, { emit }) {
const treeData = ref<any[]>([]);
async function loadRoleList() {
const url = '/sys/role/listByTenant';
let params = {
order: 'desc',
column: 'createTime',
pageSize: 200,
};
let arr = [];
const data = await defHttp.get({ url, params }, { isTransformResponse: false });
if (data.success) {
const { records } = data.result;
arr = records.map((k) => {
return {
title: k.roleName,
id: k.id,
key: k.id,
};
});
}
console.log('loadRoleList', data);
treeData.value = arr;
}
loadRoleList();
const selectedKeys = ref<any[]>([]);
const selectedRoleId = ref('');
function onSelect(ids, e) {
let record = e.node.dataRef;
selectedKeys.value = [record.key];
let id = ids[0];
selectedRoleId.value = id;
loadUserList();
}
const userDataList = ref<any[]>([]);
async function loadUserList() {
const url = '/sys/user/selectUserList';
let params = {
pageNo: 1,
pageSize: 10,
};
if (props.searchText) {
params['keyword'] = props.searchText;
}
if (selectedRoleId.value) {
params['roleId'] = selectedRoleId.value;
}
const data = await defHttp.get({ url, params }, { isTransformResponse: false });
if (data.success) {
const { records } = data.result;
userDataList.value = records;
} else {
console.error(data.message);
}
console.log('role-loadUserList', data);
}
watch(
() => props.searchText,
() => {
loadUserList();
}
);
function onSelectUser(info) {
emit('selected', info);
}
function unSelectUser(id) {
emit('unSelect', id);
}
const maxHeight = ref(300);
maxHeight.value = window.innerHeight - 300;
const containerStyle = computed(() => {
return {
'overflow-y': 'auto',
'max-height': maxHeight.value + 'px',
};
});
return {
containerStyle,
treeData,
selectedKeys,
onSelect,
onSelectUser,
unSelectUser,
userDataList,
};
},
};
</script>
<style scoped></style>

View File

@ -0,0 +1,347 @@
<template>
<BasicModal
@register="register"
:getContainer="getContainer"
:canFullscreen="false"
title="选择用户"
:width="600"
wrapClassName="j-user-select-modal2"
>
<!-- 部门下拉框 -->
<a-select v-model:value="selectedDepart" style="width: 100%" class="depart-select" @change="onDepartChange">
<a-select-option v-for="item in departOptions" :value="item.value">{{ item.label }}</a-select-option>
</a-select>
<div style="position: relative; min-height: 350px">
<!-- 用户搜索框 -->
<div :class="searchInputStatus ? 'my-search all-width' : 'my-search'">
<span :class="searchInputStatus ? 'hidden' : ''" style="margin-left: 10px"
><SearchOutlined style="color: #c0c0c0" @click="showSearchInput"
/></span>
<div style="width: 100%" :class="searchInputStatus ? '' : 'hidden'">
<a-input v-model:value="searchText" @pressEnter="onSearchUser" style="width: 100%" placeholder="请输入用户名按回车搜索">
<template #prefix>
<SearchOutlined style="color: #c0c0c0" />
</template>
<template #suffix>
<CloseOutlined title="退出搜索" @click="clearSearch" />
</template>
</a-input>
</div>
</div>
<!-- tabs -->
<div class="my-tabs">
<a-tabs v-model:activeKey="myActiveKey" :centered="true" @change="onChangeTab">
<!-- 所有用户 -->
<a-tab-pane key="1" tab="全部" forceRender>
<user-list :excludeUserIdList="excludeUserIdList" :dataList="userDataList" :selectedIdList="selectedIdList" depart @selected="onSelectUser" @unSelect="unSelectUser" />
</a-tab-pane>
<!-- 部门用户 -->
<a-tab-pane key="2" tab="按部门" forceRender>
<depart-user-list
:searchText="searchText"
:selectedIdList="selectedIdList"
:excludeUserIdList="excludeUserIdList"
@loaded="initDepartOptions"
@selected="onSelectUser"
@unSelect="unSelectUser"
/>
</a-tab-pane>
<!-- 角色用户 -->
<a-tab-pane key="3" tab="按角色" forceRender>
<role-user-list :excludeUserIdList="excludeUserIdList" :searchText="searchText" :selectedIdList="selectedIdList" @selected="onSelectUser" @unSelect="unSelectUser" />
</a-tab-pane>
</a-tabs>
</div>
<!-- 选中用户 -->
<div class="selected-users" style="width: 100%; overflow-x: hidden">
<SelectedUserItem v-for="item in selectedUserList" :info="item" @unSelect="unSelectUser" />
</div>
</div>
<template #footer>
<div style="display: flex; justify-content: space-between; width: 100%">
<div class="select-user-page-info">
<a-pagination
v-if="myActiveKey == '1'"
v-model:current="pageNo"
size="small"
:total="totalRecord"
show-quick-jumper
@change="onPageChange"
/>
</div>
<a-button type="primary" @click="handleOk"> </a-button>
</div>
</template>
</BasicModal>
</template>
<script lang="ts">
import { BasicModal, useModalInner } from '/@/components/Modal';
import { SearchOutlined, CloseOutlined } from '@ant-design/icons-vue';
import UserList from './UserList.vue';
import SelectedUserItem from './SelectedUserItem.vue';
import DepartUserList from './UserListAndDepart.vue';
import RoleUserList from './UserListAndRole.vue';
import { Pagination } from 'ant-design-vue';
const APagination = Pagination;
import { defHttp } from '/@/utils/http/axios';
import { computed, ref, toRaw } from 'vue';
import { useUserStore } from '/@/store/modules/user';
export default {
name: 'UserSelectModal',
components: {
BasicModal,
SearchOutlined,
CloseOutlined,
SelectedUserItem,
UserList,
DepartUserList,
RoleUserList,
APagination,
},
props: {
multi: {
type: Boolean,
default: false,
},
getContainer: {
type: Function,
default: null,
},
//
izExcludeMy: {
type: Boolean,
default: false,
},
},
emits: ['selected', 'register'],
setup(props, { emit }) {
const myActiveKey = ref('1');
const selectedUserList = ref<any[]>([]);
const userStore = useUserStore();
const selectedIdList = computed(() => {
let arr = selectedUserList.value;
if (!arr || arr.length == 0) {
return [];
} else {
return arr.map((k) => k.id);
}
});
// QQYUN-4152
const excludeUserIdList = ref<any[]>([]);
//
const [register] = useModalInner((data) => {
let list = data.list;
if (list && list.length > 0) {
selectedUserList.value = [...list];
} else {
selectedUserList.value = [];
}
if(data.excludeUserIdList){
excludeUserIdList.value = data.excludeUserIdList;
}else{
excludeUserIdList.value = [];
}
//excludeUserIdList.push
if (props.izExcludeMy) {
excludeUserIdList.value.push(userStore.getUserInfo.id);
}
});
//
function handleOk() {
let arr = toRaw(selectedUserList.value);
emit('selected', arr);
}
/*--------------部门下拉框,用于筛选用户---------------*/
const selectedDepart = ref('');
const departOptions = ref<any[]>([]);
function initDepartOptions(options) {
departOptions.value = [{ value: '', label: '全部用户' }, ...options];
selectedDepart.value = '';
}
function onDepartChange() {
loadUserList();
}
/*--------------部门下拉框,用于筛选用户---------------*/
/*--------------第一页 搜索框---------------*/
const searchInputStatus = ref(false);
const searchText = ref('');
function showSearchInput(e) {
e && prevent(e);
searchInputStatus.value = true;
}
function onSearchUser() {
console.log('onSearchUser');
loadUserList();
}
function clearSearch(e) {
e && prevent(e);
searchText.value = '';
searchInputStatus.value = false;
loadUserList();
}
/*--------------第一页 搜索框---------------*/
/*--------------加载数据---------------*/
const pageNo = ref(1);
const totalRecord = ref(0);
const userDataList = ref<any[]>([]);
async function onPageChange() {
console.log('onPageChange', pageNo.value);
await loadUserList();
}
async function loadUserList() {
const url = '/sys/user/selectUserList';
let params = {
pageNo: pageNo.value,
pageSize: 10,
};
if (searchText.value) {
params['keyword'] = searchText.value;
}
if (selectedDepart.value) {
params['departId'] = selectedDepart.value;
}
const data = await defHttp.get({ url, params }, { isTransformResponse: false });
if (data.success) {
const { records, total } = data.result;
totalRecord.value = total;
userDataList.value = records;
} else {
console.error(data.message);
}
console.log('loadUserList', data);
}
/*--------------加载数据---------------*/
/*--------------选中/取消选中---------------*/
function onSelectUser(info) {
if (props.multi === true) {
let arr = selectedUserList.value;
let idList = selectedIdList.value;
if (idList.indexOf(info.id) < 0) {
arr.push({ ...info });
selectedUserList.value = arr;
}
} else {
selectedUserList.value = [{ ...info }];
}
}
function unSelectUser(id) {
let arr = selectedUserList.value;
let index = -1;
for (let i = 0; i < arr.length; i++) {
if (arr[i].id === id) {
index = i;
break;
}
}
if (index >= 0) {
arr.splice(index, 1);
selectedUserList.value = arr;
}
}
/*--------------选中/取消选中---------------*/
function onChangeTab(tab) {
myActiveKey.value = tab;
}
function prevent(e) {
e.preventDefault();
e.stopPropagation();
}
//
loadUserList();
return {
selectedDepart,
departOptions,
initDepartOptions,
onDepartChange,
register,
handleOk,
searchText,
searchInputStatus,
showSearchInput,
onSearchUser,
clearSearch,
myActiveKey,
onChangeTab,
pageNo,
totalRecord,
onPageChange,
userDataList,
selectedUserList,
selectedIdList,
onSelectUser,
unSelectUser,
excludeUserIdList
};
},
};
</script>
<style lang="less">
.j-user-select-modal2 {
.depart-select {
.ant-select-selector {
color: #fff !important;
background-color: #409eff !important;
border-radius: 5px !important;
}
.ant-select-selection-item,
.ant-select-arrow {
color: #fff !important;
}
}
.my-search {
position: absolute;
top: 14px;
z-index: 1;
&.all-width {
width: 100%;
}
.anticon {
cursor: pointer;
&:hover {
color: #0a8fe9 !important;
}
}
.hidden {
display: none;
}
}
.my-tabs {
}
.selected-users {
display: flex;
flex-wrap: wrap;
flex-direction: row;
padding-top: 15px;
}
.scroll-container {
padding-bottom: 0 !important;
}
}
</style>

View File

@ -0,0 +1,245 @@
<template>
<div>
<div v-if="isSearchFormComp" @click="click2Add" :class="disabled?'disabled-user-select':''" style="padding:0 5px;background-color: #fff;border: 1px solid #ccc;border-radius: 3px;box-sizing: border-box;display:flex;color: #9e9e9e;font-size: 14px;flex-wrap: wrap;min-height: 32px;">
<template v-if="selectedUserList.length > 0">
<SelectedUserItem v-for="item in showUserList" :info="item" @unSelect="unSelectUser" query />
</template>
<span v-else style="height: 30px;line-height: 30px;display: inline-block;margin-left: 7px;color: #bfbfbf;">请选择用户</span>
<div v-if="ellipsisInfo.status" class="user-selected-item">
<div class="user-select-ellipsis">
<span style="color: red">+{{ellipsisInfo.count}}...</span>
</div>
</div>
</div>
<div v-else style="display: flex; flex-wrap: wrap; flex-direction: row" >
<template v-if="selectedUserList.length > 0">
<SelectedUserItem v-for="item in selectedUserList" :info="item" @unSelect="unSelectUser" />
</template>
<a-button v-if="showAddButton" shape="circle" @click="onShowModal"><PlusOutlined /></a-button>
</div>
<user-select-modal :multi="multi" :getContainer="getContainer" @register="registerModal" @selected="onSelected" :izExcludeMy="izExcludeMy"></user-select-modal>
</div>
</template>
<script lang="ts">
import { defineComponent, watch, ref, computed, toRaw } from 'vue';
import { Form } from 'ant-design-vue';
import { PlusOutlined } from '@ant-design/icons-vue';
import { useModal } from '/@/components/Modal';
import UserSelectModal from './UserSelectModal.vue';
import { defHttp } from '/@/utils/http/axios';
import SelectedUserItem from './SelectedUserItem.vue';
export default defineComponent({
name: 'UserSelect',
components: {
PlusOutlined,
UserSelectModal,
SelectedUserItem,
},
props: {
store: {
type: String,
default: 'id',
},
value: {
type: String,
default: '',
},
multi: {
type: Boolean,
default: false,
},
getContainer: {
type: Function,
default: null,
},
//
query:{
type: Boolean,
default: false,
},
//-querytrue
maxCount:{
type: Number,
default: 2
},
disabled:{
type: Boolean,
default: false,
},
//
izExcludeMy:{
type: Boolean,
default: false,
}
},
emits: ['update:value', 'change'],
setup(props, { emit }) {
const formItemContext = Form.useInjectFormItemContext();
const loading = ref(true);
const selectedUserList = ref<any[]>([]);
const showUserList = computed(()=>{
let list = selectedUserList.value
let max = props.maxCount;
if(list.length<=max){
return list;
}
return list.filter((_item, index)=>index<max);
});
const ellipsisInfo = computed(()=>{
let max = props.maxCount;
let len = selectedUserList.value.length
if(len > max){
return {status: true, count: len-max};
}else{
return {status: false}
}
});
//
const [registerModal, { openModal, closeModal }] = useModal();
function onShowModal() {
if(props.disabled===true){
return ;
}
let list = toRaw(selectedUserList.value);
openModal(true, {
list,
});
}
function onSelected(arr) {
console.log('onSelected', arr);
selectedUserList.value = arr;
onSelectedChange();
closeModal();
}
function onSelectedChange() {
loading.value = false;
let temp: any[] = [];
let arr = selectedUserList.value;
if (arr && arr.length > 0) {
temp = arr.map((k) => {
return k[props.store];
});
}
let str = temp.join(',');
emit('update:value', str);
emit('change', str);
formItemContext.onFieldChange();
console.log('选中数据', str);
}
watch(
() => props.value,
async (val) => {
if (val) {
if (loading.value === true) {
await getUserList(val);
}
} else {
selectedUserList.value = [];
}
loading.value = true;
},
{ immediate: true }
);
async function getUserList(ids) {
const url = '/sys/user/list';
let params = {
[props.store]: ids,
};
selectedUserList.value = [];
const data = await defHttp.get({ url, params }, { isTransformResponse: false });
if (data.success) {
const { records } = data.result;
selectedUserList.value = records;
} else {
console.error(data.message);
}
console.log('getUserList', data);
}
const showAddButton = computed(() => {
if(props.disabled === true){
return false;
}
if (props.multi === true) {
return true;
} else {
if (selectedUserList.value.length > 0) {
return false;
} else {
return true;
}
}
});
function unSelectUser(id) {
console.log('unSelectUser', id);
let arr = selectedUserList.value;
let index = -1;
for (let i = 0; i < arr.length; i++) {
if (arr[i].id == id) {
index = i;
break;
}
}
if (index >= 0) {
arr.splice(index, 1);
selectedUserList.value = arr;
onSelectedChange();
}
}
function click2Add(e) {
e.preventDefault();
e.stopPropagation();
onShowModal();
}
const isSearchFormComp = computed(()=>{
if(props.query===true){
return true;
}else{
return false
}
});
return {
registerModal,
onShowModal,
isSearchFormComp,
onSelected,
showAddButton,
unSelectUser,
selectedUserList,
showUserList,
ellipsisInfo,
click2Add
};
},
});
</script>
<style lang="less" scoped>
.user-select-ellipsis{
width: 40px;
height: 24px;
text-align: center;
line-height: 22px;
border-radius: 8px;
background: #f5f5f5;
border: 1px solid #f0f0f0;
}
.disabled-user-select{
cursor: not-allowed;
background-color: #f5f5f5 !important;
}
</style>

View File

@ -29,6 +29,9 @@ export function useTreeBiz(treeRef, getList, props) {
watch(
selectValues,
({ value: values }: Recordable) => {
if(!values){
return;
}
if (openModal.value == false && values.length > 0) {
loadingEcho.value = isFirstLoadEcho;
isFirstLoadEcho = false;

View File

@ -115,4 +115,7 @@ export const basicProps = {
labelAlign: propTypes.string,
rowProps: Object as PropType<RowProps>,
// 当表单是查询条件的时候 当表单改变后自动查询,不需要点击查询按钮
autoSearch: propTypes.bool.def(false),
};

View File

@ -141,6 +141,14 @@ export type ComponentType =
| 'JSearchSelect'
| 'JAddInput'
| 'Time'
| 'OnlineSelectCascade'
| 'LinkTableCard'
| 'LinkTableSelect'
| 'LinkTableForQuery'
| 'CascaderPcaForQuery'
| 'UserSelect'
| 'RangeDate'
| 'RangeNumber'
| 'linkRecordSelect'
| 'RangeTime'
| 'JRangeNumber';

View File

@ -89,6 +89,7 @@
copy: propTypes.bool.def(false),
mode: propTypes.oneOf<('svg' | 'iconify')[]>(['svg', 'iconify']).def('iconify'),
disabled: propTypes.bool.def(true),
clearSelect: propTypes.bool.def(false)
});
const emit = defineEmits(['change', 'update:value']);
@ -126,6 +127,13 @@
}
function handleClick(icon: string) {
if(props.clearSelect === true){
if(currentSelect.value===icon){
currentSelect.value = ''
}else{
currentSelect.value = icon;
}
}else{
currentSelect.value = icon;
if (props.copy) {
clipboardRef.value = icon;
@ -135,6 +143,8 @@
}
}
}
function handleSearchChange(e: ChangeEvent) {
const value = e.target.value;
if (!value) {

View File

@ -142,10 +142,13 @@
});
watchEffect(() => {
visibleRef.value = !!props.visible;
fullScreenRef.value = !!props.defaultFullscreen;
});
watchEffect(() => {
visibleRef.value = !!props.visible;
});
watch(
() => unref(visibleRef),
(v) => {

View File

@ -11,7 +11,7 @@
<!-- 是否开启评论区域 -->
<template v-if="enableComment">
<Tooltip title="关闭" placement="bottom" v-if="commentSpan>0">
<Tooltip title="收起" placement="bottom" v-if="commentSpan>0">
<RightSquareOutlined @click="handleCloseComment" style="font-size: 16px"/>
</Tooltip>
<Tooltip title="展开" placement="bottom" v-else>
@ -50,7 +50,7 @@
prefixCls,
`${prefixCls}--custom`,
{
[`${prefixCls}--can-full`]: props.canFullscreen,
[`${prefixCls}--can-full`]: props.canFullscreen || props.enableComment,
},
];
});

View File

@ -205,9 +205,13 @@
footer: unref(getFooterProps),
...unref(getExpandOption),
};
if (slots.expandedRowRender) {
//update-begin---author:wangshuai ---date:20230214 for[QQYUN-4237] ------------
//,
/*if (slots.expandedRowRender) {
propsData = omit(propsData, 'scroll');
}
}*/
//update-end---author:wangshuai ---date:20230214 for[QQYUN-4237] ------------
propsData = omit(propsData, ['class', 'onChange']);
return propsData;

View File

@ -65,7 +65,9 @@ export function useTableScroll(
if (!tableEl) return;
if (!bodyEl) {
bodyEl = tableEl.querySelector('.ant-table-body');
//update-begin-author:taoyan date:2023-2-11 for: issues/355 前端-jeecgboot-vue3 3.4.4版本,BasicTable高度自适应功能失效,设置BasicTable组件maxHeight失效; 原因已找到,请看详情
bodyEl = tableEl.querySelector('.ant-table-tbody');
//update-end-author:taoyan date:2023-2-11 for: issues/355 前端-jeecgboot-vue3 3.4.4版本,BasicTable高度自适应功能失效,设置BasicTable组件maxHeight失效; 原因已找到,请看详情
if (!bodyEl) return;
}

View File

@ -16,6 +16,8 @@ export interface ActionItem extends ButtonProps {
tooltip?: string | TooltipProps;
// 自定义类名
class?: string | Record<string, boolean> | any[];
// 自定义图标颜色
iconColor?: string;
}
export interface PopConfirm {

View File

@ -89,6 +89,9 @@
let props = originColumn.value.props || {};
props['mode'] = 'multiple';
props['maxTagCount'] = 1;
//update-begin-author:taoyan date:2022-12-5 for: issues/271 Online
originColumn.value.allowSearch = true;
//update-end-author:taoyan date:2022-12-5 for: issues/271 Online
originColumn.value.props = props;
} else if (searchTypes.includes(props.type)) {
//

View File

@ -353,6 +353,7 @@ export function useMethods(props: JVxeTableProps, { emit }, data: JVxeDataProps,
insertIndex: index,
$table: xTable,
target: instanceRef.value,
isModalData: options?.isModalData
});
}
}
@ -368,6 +369,8 @@ export function useMethods(props: JVxeTableProps, { emit }, data: JVxeDataProps,
setActive?: boolean;
//是否需要触发change事件
emitChange?:boolean
// 是否是modal弹窗添加的数据
isModalData?:boolean
}
/**

View File

@ -123,7 +123,7 @@
queryParam,
dictOptions,
},
] = usePopBiz(getBindValue);
] = usePopBiz(getBindValue, tableRef);
const showSearchFlag = computed(() => unref(queryInfo) && unref(queryInfo).length > 0);
/**

View File

@ -559,6 +559,11 @@ export function usePopBiz(props, tableRef?) {
}
}
dataSource.value = data.records;
//update-begin-author:taoyan date:2023-2-11 for:issues/356 在线报表分页有问题
tableRef.value && tableRef.value.setPagination({
total: Number(data.total)
})
//update-end-author:taoyan date:2023-2-11 for:issues/356 在线报表分页有问题
} else {
pagination.total = 0;
dataSource.value = [];

View File

@ -0,0 +1,142 @@
<template>
<div class="user-avatar-info">
<a-popover title="" :overlayStyle="{width: '250px'}">
<template #content>
<div style="display: flex;flex-direction: row;align-items: center">
<div style="width: 60px;text-align: center">
<a-avatar v-if="userAvatar" :src="userAvatar" :size="47"/>
<a-avatar v-else :size="47">{{ getAvatarText() }}</a-avatar>
</div>
<div style="flex: 1;display: flex;flex-direction: column;margin-left: 12px">
<div style="color: #000;display: inline-block;font-size: 15px;font-weight: 700;margin-top: 3px;vertical-align: top;width: 170px;">
{{ userLabel }}
</div>
<div style="color: #757575;display: block;margin-top: 4px;">
{{ phone }}
</div>
</div>
</div>
</template>
<span style="cursor: pointer">
<a-avatar v-if="userAvatar" :src="userAvatar" :loadError="loadError"/>
<a-avatar v-else>{{ getAvatarText() }}</a-avatar>
</span>
</a-popover>
<span class="realname-ellipsis">
{{ userLabel }}
</span>
</div>
</template>
<script lang="ts">
import { ref, watchEffect, defineComponent } from 'vue';
import { defHttp } from '/@/utils/http/axios';
import { getFileAccessHttpUrl } from '/@/utils/common/compUtils';
export default defineComponent({
name: 'UserAvatar',
props: {
username: {
type: String,
default: '',
},
detail:{
type: Object,
default: ()=>{},
}
},
setup(props) {
const userAvatar = ref('');
const userLabel = ref('');
const phone = ref('');
watchEffect(async ()=>{
userAvatar.value = '';
userLabel.value = '';
phone.value = '';
let username = props.username;
if(username){
await initUserInfo(username);
}
let userInfo = props.detail;
if(userInfo){
if(userInfo.avatar){
userAvatar.value = getFileAccessHttpUrl(userInfo.avatar);
}
if(userInfo.realname){
userLabel.value = userInfo.realname;
}
if(userInfo.phone){
phone.value = userInfo.phone;
}
}
});
async function initUserInfo(val) {
const params = {
username: val,
};
const url = '/sys/user/getMultiUser';
const data = await defHttp.get({ url, params }, {isTransformResponse: false});
if(data && data.length > 0){
let temp = data[0].avatar;
if (temp) {
userAvatar.value = getFileAccessHttpUrl(temp)
}
userLabel.value = data[0].realname;
phone.value = data[0].phone;
}else{
console.log(data)
}
}
function getAvatarText() {
let text = userLabel.value;
if (!text) {
text = props.username;
}
if (text) {
if (text.length > 2) {
return text.substr(0, 2);
} else {
return text;
}
}
return '';
}
function loadError() {
userAvatar.value = '';
return true;
}
return {
userAvatar,
userLabel,
getAvatarText,
phone,
loadError
};
},
});
</script>
<style scoped lang="less">
.user-avatar-info{
.ant-avatar-image{
cursor: pointer;
}
.realname-ellipsis {
overflow: hidden;
text-overflow: ellipsis;
vertical-align: top;
white-space: nowrap;
height: 32px;
line-height: 32px;
display: inline-block;
margin-left: 10px;
}
}
</style>

View File

@ -1,13 +1,13 @@
<template>
<div class="comment-tabs-warp" v-if="showStatus">
<a-tabs @change="handleChange" :animated="false">
<a-tab-pane tab="评论" key="comment" class="comment-list-tab">
<a-tab-pane v-if="showComment" tab="评论" key="comment" class="comment-list-tab">
<comment-list :tableName="tableName" :dataId="dataId" :datetime="datetime1"></comment-list>
</a-tab-pane>
<a-tab-pane tab="文件" key="file">
<a-tab-pane v-if="showFiles" tab="文件" key="file">
<comment-files :tableName="tableName" :dataId="dataId" :datetime="datetime2"></comment-files>
</a-tab-pane>
<a-tab-pane tab="日志" key="log">
<a-tab-pane v-if="showDataLog" tab="日志" key="log">
<data-log-list :tableName="tableName" :dataId="dataId" :datetime="datetime3"></data-log-list>
</a-tab-pane>
</a-tabs>
@ -35,6 +35,12 @@
props: {
tableName: propTypes.string.def(''),
dataId: propTypes.string.def(''),
//
showComment: propTypes.bool.def(true),
//
showFiles: propTypes.bool.def(true),
//
showDataLog: propTypes.bool.def(true),
},
setup(props) {
const showStatus = computed(() => {

View File

@ -4,7 +4,7 @@
<template #overlay>
<a-menu @click="handleMenuClick">
<a-menu-item v-if="syncToApp" key="to-app">{{ name }}</a-menu-item>
<a-menu-item v-if="syncToLocal" key="to-local"></a-menu-item>
<a-menu-item v-if="getSyncToLocal" key="to-local"></a-menu-item>
</a-menu>
</template>
</a-dropdown>
@ -16,6 +16,7 @@
<script lang="ts" setup>
/* JThirdAppButton 的子组件,不可单独使用 */
import { computed } from 'vue';
const props = defineProps({
type: String,
@ -26,6 +27,14 @@
// Emits
const emit = defineEmits(['to-app', 'to-local']);
const getSyncToLocal = computed(() => {
//
if (props.type === 'wechatEnterprise') {
return false;
}
return props.syncToLocal;
});
function handleMenuClick(event) {
emit(event.key, { type: props.type });
}

View File

@ -32,6 +32,9 @@ export const TENANT_ID = 'TENANT_ID';
// login info key
export const LOGIN_INFO_KEY = 'LOGIN__INFO__';
// 聊天UID key
export const JEECG_CHAT_UID = 'JEECG_CHAT_UID';
export enum CacheTypeEnum {
SESSION,
LOCAL,

View File

@ -42,7 +42,7 @@ export enum ConfigEnum {
// Sign
Sign = 'X-Sign',
// 租户id
TENANT_ID = 'tenant-id',
TENANT_ID = 'X-Tenant-Id',
// 版本
VERSION = 'X-Version',
// 低代码应用ID

View File

@ -9,4 +9,6 @@ export enum PageEnum {
ERROR_LOG_PAGE = '/error-log/list',
// auth2登录路由路径
OAUTH2_LOGIN_PAGE_PATH = '/oauth2-app/login',
//文件路由
SYS_FILES_PATH = '/file/share',
}

View File

@ -73,8 +73,10 @@ export function useMethods() {
<span><a href = ${href} download = ${fileName}> </a> </span>
</div>`,
});
} else if (fileInfo.code === 500) {
//update-begin---author:wangshuai ---date:20221121 for[VUEN-2827]导入无权限,提示图标错误------------
} else if (fileInfo.code === 500 || fileInfo.code === 510) {
createMessage.error(fileInfo.message || `${data.file.name} 导入失败`);
//update-end---author:wangshuai ---date:20221121 for[VUEN-2827]导入无权限,提示图标错误------------
} else {
createMessage.success(fileInfo.message || `${data.file.name} 文件上传成功`);
}

View File

@ -88,11 +88,19 @@ const getBaseOptions = () => {
};
function createModalOptions(options: ModalOptionsPartial, icon: string): ModalOptionsPartial {
//update-begin-author:taoyan date:2023-1-10 for: 可以自定义图标
let titleIcon:any = ''
if(options.icon){
titleIcon = options.icon;
}else{
titleIcon = getIcon(icon)
}
//update-end-author:taoyan date:2023-1-10 for: 可以自定义图标
return {
...getBaseOptions(),
...options,
content: renderContent(options),
icon: getIcon(icon),
icon: titleIcon
};
}

View File

@ -18,10 +18,15 @@ import { isArray } from '/@/utils/is';
import { useMultipleTabStore } from '/@/store/modules/multipleTab';
// User permissions related operations
export function usePermission(formData?) {
export function usePermission() {
const userStore = useUserStore();
const appStore = useAppStore();
const permissionStore = usePermissionStore();
//动态加载流程节点表单权限
let formData: any = {};
function initBpmFormData(_bpmFormData) {
formData = _bpmFormData;
}
const { closeAll } = useTabs(router);
//==================================工作流权限判断-begin=========================================
@ -165,5 +170,5 @@ export function usePermission(formData?) {
}
//update-end-author:taoyan date:2022-6-17 for: VUEN-1342【流程】编码方式 节点权限配置好后,未生效
return { changeRole, hasPermission, togglePermissionMode, refreshMenu, isDisabledAuth };
return { changeRole, hasPermission, togglePermissionMode, refreshMenu, isDisabledAuth, initBpmFormData };
}

View File

@ -72,7 +72,6 @@
initWebSocket();
});
const messageCount = ref<number>(0)
function mapAnnouncement(item) {
return {
...item,
@ -92,12 +91,6 @@
listData.value[1].list = sysMsgList.map(mapAnnouncement);
listData.value[0].count = anntMsgTotal;
listData.value[1].count = sysMsgTotal;
//update-begin-author:taoyan date:2022-8-30 for: chat
let msgCount = anntMsgTotal+sysMsgTotal;
//update-begin-author:wangshuai date:2022-09-02 for: 0
messageCount.value = msgCount
//update-end-author:wangshuai date:2022-09-02 for: 0
//update-end-author:taoyan date:2022-8-30 for: chat
} catch (e) {
console.warn('系统消息通知异常:', e);
}

View File

@ -151,7 +151,7 @@
* 提交数据
*/
async function handleSubmit() {
if (unref(isMultiTenant) && !unref(tenantSelected)) {
if (unref(isMultiTenant) && unref(tenantSelected)==null) {
validate_status.value = 'error';
return false;
}
@ -165,6 +165,9 @@
userStore.setTenant(unref(tenantSelected));
}
createMessage.success('切换成功');
//
window.location.reload();
})
.catch((e) => {
console.log('登录选择出现问题', e);
@ -187,6 +190,7 @@
const result = await selectDepart({
username: userStore.getUserInfo.username,
orgCode: unref(departSelected),
loginTenantId: unref(tenantSelected),
});
if (result.userInfo) {
const userInfo = result.userInfo;

View File

@ -156,7 +156,9 @@
updatePassword();
break;
case 'account':
go(`/page-demo/account/setting`);
//update-begin---author:wangshuai ---date:20221125 for------------
go(`/system/usersetting`);
//update-end---author:wangshuai ---date:20221125 for--------------
break;
}
}

View File

@ -11,7 +11,7 @@
/>
<LayoutBreadcrumb v-if="getShowContent && getShowBread" :theme="getHeaderTheme" />
<!-- 欢迎语 -->
<span v-if="getShowContent && getShowBreadTitle" :class="[prefixCls, `${prefixCls}--${getHeaderTheme}`,'headerIntroductionClass']"> {{ title }} </span>
<span v-if="getShowContent && getShowBreadTitle && !getIsMobile" :class="[prefixCls, `${prefixCls}--${getHeaderTheme}`,'headerIntroductionClass']"> {{ title }} </span>
</div>
<!-- left end -->

View File

@ -4,8 +4,10 @@ export default {
// user dropdown
dropdownItemDoc: 'Document',
dropdownItemLoginOut: 'Login Out',
dropdownItemSwitchPassword: 'Password Change',
dropdownItemSwitchDepart: 'Switch Department',
dropdownItemRefreshCache: 'Clean cache',
dropdownItemSwitchAccount: 'Account Setting',
tooltipErrorLog: 'Error log',
tooltipLock: 'Lock screen',

View File

@ -94,9 +94,17 @@ export default {
userName: 'Username',
password: 'Password',
inputCode: 'Verification code',
confirmPassword: 'Confirm Password',
email: 'Email',
smsCode: 'SMS code',
mobile: 'Mobile',
//重置密码页面英文
authentication:'authentication',
resetLoginPassword:'reset login password',
resetSuccess:'reset succeeded',
nextStep:'next step',
goToLogin:'go to login'
},
};

View File

@ -69,7 +69,7 @@ export default {
signInTitle: 'Jeecg Boot',
signInDesc: '是中国最具影响力的 企业级低代码平台在线开发可视化拖拽设计零代码实现80%的基础功能~',
policy: '我同意xxx隐私政策',
policy: '我同意敲敲云隐私政策',
scanSign: `扫码后,即可完成登录`,
scanSuccess: `扫码成功,登录中`,
@ -101,5 +101,12 @@ export default {
mobile: '手机号码',
subTitleText: '{0}秒后返回登录页面',
//重置密码页面中文
authentication:'验证身份',
resetLoginPassword:'重置登录密码',
resetSuccess:'重置成功',
nextStep:'下一步',
goToLogin:'去登录'
},
};

View File

@ -18,6 +18,7 @@ import { setupI18n } from '/@/locales/setupI18n';
import { registerGlobComp } from '/@/components/registerGlobComp';
import { registerThirdComp } from '/@/settings/registerThirdComp';
import { useSso } from '/@/hooks/web/useSso';
// 注册online模块lib
import { registerPackages } from '/@/utils/monorepo/registerPackages';
// 在本地开发中引入的,以提高浏览器响应速度
@ -37,7 +38,7 @@ async function bootstrap() {
// 初始化内部系统配置
initAppConfigStore();
// 注册外部模块路由
// 注册外部模块路由(注册online模块lib)
registerPackages(app);
// 注册全局组件

View File

@ -15,10 +15,15 @@ const LOGIN_PATH = PageEnum.BASE_LOGIN;
//auth2登录路由
const OAUTH2_LOGIN_PAGE_PATH = PageEnum.OAUTH2_LOGIN_PAGE_PATH;
//分享免登录路由
const SYS_FILES_PATH = PageEnum.SYS_FILES_PATH;
const ROOT_PATH = RootRoute.path;
//update-begin---author:wangshuai ---date:20220629 for[issues/I5BG1I]vue3不支持auth2登录------------
const whitePathList: PageEnum[] = [LOGIN_PATH, OAUTH2_LOGIN_PAGE_PATH];
//update-begin---author:wangshuai ---date:20221111 for: [VUEN-2472]分享免登录------------
const whitePathList: PageEnum[] = [LOGIN_PATH, OAUTH2_LOGIN_PAGE_PATH,SYS_FILES_PATH];
//update-end---author:wangshuai ---date:20221111 for: [VUEN-2472]分享免登录------------
//update-end---author:wangshuai ---date:20220629 for[issues/I5BG1I]vue3不支持auth2登录------------
export function createPermissionGuard(router: Router) {

View File

@ -5,8 +5,9 @@ import { getParentLayout, LAYOUT, EXCEPTION_COMPONENT } from '/@/router/constant
import { cloneDeep, omit } from 'lodash-es';
import { warn } from '/@/utils/log';
import { createRouter, createWebHashHistory } from 'vue-router';
import { getTenantId, getToken } from '/@/utils/auth';
import { getTenantId, getToken } from "/@/utils/auth";
import { URL_HASH_TAB } from '/@/utils';
//引入online lib路由
import { packageViews } from '/@/utils/monorepo/dynamicRouter';
import {useI18n} from "/@/hooks/web/useI18n";
@ -27,7 +28,7 @@ let dynamicViewsModules: Record<string, () => Promise<Recordable>>;
function asyncImportRoute(routes: AppRouteRecordRaw[] | undefined) {
if (!dynamicViewsModules) {
dynamicViewsModules = import.meta.glob('../../views/**/*.{vue,tsx}');
// 跟模块views合并
//合并online lib路由
dynamicViewsModules = Object.assign({}, dynamicViewsModules, packageViews);
}
if (!routes) return;

View File

@ -31,7 +31,9 @@ export const RootRoute: AppRouteRecordRaw = {
export const LoginRoute: AppRouteRecordRaw = {
path: '/login',
name: 'Login',
component: () => import('/@/views/sys/login/Login.vue'),
//新版后台登录,如果想要使用旧版登录放开即可
// component: () => import('/@/views/sys/login/Login.vue'),
component: () => import('/@/views/system/loginmini/MiniLogin.vue'),
meta: {
title: t('routes.basic.login'),
},
@ -41,7 +43,9 @@ export const LoginRoute: AppRouteRecordRaw = {
export const Oauth2LoginRoute: AppRouteRecordRaw = {
path: '/oauth2-app/login',
name: 'oauth2-app-login',
component: () => import('/@/views/sys/login/OAuth2Login.vue'),
//新版钉钉免登录,如果想要使用旧版放开即可
// component: () => import('/@/views/sys/login/OAuth2Login.vue'),
component: () => import('/@/views/system/loginmini/OAuth2Login.vue'),
meta: {
title: t('routes.oauth2.login'),
},

View File

@ -14,6 +14,10 @@ const lsLocaleSetting = (ls.get(LOCALE_KEY) || localeSetting) as LocaleSetting;
interface LocaleState {
localInfo: LocaleSetting;
pathTitleMap: object;
// myapps主题色低代码应用列表首页
appIndexTheme: string
// myapps - 跳转前路由地址
appMainPth: string
}
export const useLocaleStore = defineStore({
@ -21,6 +25,8 @@ export const useLocaleStore = defineStore({
state: (): LocaleState => ({
localInfo: lsLocaleSetting,
pathTitleMap: {},
appIndexTheme: '',
appMainPth: ''
}),
getters: {
getShowPicker(): boolean {
@ -34,6 +40,12 @@ export const useLocaleStore = defineStore({
return (path) => state.pathTitleMap[path];
},
//update-end-author:taoyan date:2022-6-1 for: VUEN-1144 online 配置成菜单后,打开菜单,显示名称未展示为菜单名称
getAppIndexTheme(): string {
return this.appIndexTheme;
},
getAppMainPth(): string {
return this.appMainPth;
},
},
actions: {
/**
@ -58,6 +70,12 @@ export const useLocaleStore = defineStore({
this.pathTitleMap[path] = title;
},
//update-end-author:taoyan date:2022-6-1 for: VUEN-1144 online 配置成菜单后,打开菜单,显示名称未展示为菜单名称
setAppIndexTheme(theme) {
this.appIndexTheme = theme;
},
setAppMainPth(path) {
this.appMainPth = path;
},
},
});

View File

@ -123,9 +123,10 @@ export const useUserStore = defineStore({
try {
const { goHome = true, mode, ...loginParams } = params;
const data = await loginApi(loginParams, mode);
const { token } = data;
const { token, userInfo } = data;
// save token
this.setToken(token);
this.setTenant(userInfo.loginTenantId);
return this.afterLoginAction(goHome, data);
} catch (error) {
return Promise.reject(error);
@ -246,6 +247,7 @@ export const useUserStore = defineStore({
this.setSessionTimeout(false);
this.setUserInfo(null);
this.setLoginInfo(null);
this.setTenant(null);
//update-begin-author:liusq date:2022-5-5 for:退出登录后清除拖拽模块的接口前缀
localStorage.removeItem(JDragConfigEnum.DRAG_BASE_URL);
//update-end-author:liusq date:2022-5-5 for: 退出登录后清除拖拽模块的接口前缀

View File

@ -40,7 +40,7 @@ export const rules = {
if (required && !value) {
return Promise.reject('请输入手机号码1!');
}
if (!new RegExp(/^1[3|4|5|7|8|9][0-9]\d{8}$/).test(value)) {
if (!/^1[3456789]\d{9}$/.test(value)) {
return Promise.reject('手机号码格式有误');
}
return Promise.resolve();

View File

@ -102,7 +102,7 @@
span: 12,
},
dynamicRules: ({ values }) => {
return values.field8 ? [{ required: true, message: '字段4必填' }] : [];
return values.field8 ? [{ required: true, message: '字段必填' }] : [];
},
componentProps: {
options: [

View File

@ -102,6 +102,7 @@
onSearch: useDebounceFn(onSearch, 300),
searchParams,
superQueryConfig,
handleSuperQuery,
handleReset: () => {
keyword.value = '';
},

View File

@ -32,6 +32,7 @@
<script lang="ts" setup>
//ts
import type { ComputedRef } from 'vue';
import { ref, computed, unref, watch, inject } from 'vue';
import { BasicTable, TableAction } from '/@/components/Table';
import JeecgOrderCustomerModal from './components/JeecgOrderCustomerModal.vue';
@ -42,7 +43,10 @@
import { isEmpty } from '/@/utils/is';
import { useMessage } from '/@/hooks/web/useMessage';
//id
const orderId = inject('orderId') || '';
const orderId = inject<ComputedRef<string>>(
'orderId',
computed(() => '')
);
//
const $message = useMessage();
//model
@ -51,7 +55,7 @@
//
const { prefixCls, tableContext } = useListPage({
tableProps: {
api: customList,
api: getCustomList,
tableSetting:{
cacheKey:'customer'
},
@ -70,13 +74,24 @@
});
//table
const [registerTable, { reload }, { rowSelection, selectedRowKeys }] = tableContext;
const [registerTable, { reload, setSelectedRowKeys }, { rowSelection, selectedRowKeys }] = tableContext;
watch(orderId, () => {
searchInfo['orderId'] = unref(orderId);
reload();
// id
setSelectedRowKeys([]);
});
async function getCustomList(params) {
let { orderId } = params;
// Id
if (orderId == null || isEmpty(orderId)) {
return [];
}
return await customList(params);
}
/**
* 新增事件
*/

View File

@ -32,6 +32,7 @@
<script lang="ts" setup>
//ts
import type { ComputedRef } from 'vue';
import { ref, computed, unref, watch, inject } from 'vue';
import { BasicTable, TableAction } from '/@/components/Table';
import JeecgOrderTicketModal from './components/JeecgOrderTicketModal.vue';
@ -42,7 +43,10 @@
import { isEmpty } from '/@/utils/is';
import { useMessage } from '/@/hooks/web/useMessage';
//id
const orderId = inject('orderId');
const orderId = inject<ComputedRef<string>>(
'orderId',
computed(() => '')
);
//
const $message = useMessage();
//model
@ -51,7 +55,7 @@
//
const { prefixCls, tableContext } = useListPage({
tableProps: {
api: ticketList,
api: getTicketList,
tableSetting:{
cacheKey:'ticket'
},
@ -70,13 +74,24 @@
});
//table
const [registerTable, { reload }, { rowSelection, selectedRowKeys }] = tableContext;
const [registerTable, { reload, setSelectedRowKeys }, { rowSelection, selectedRowKeys }] = tableContext;
watch(orderId, () => {
searchInfo['orderId'] = unref(orderId);
reload();
// id
setSelectedRowKeys([]);
});
async function getTicketList(params) {
let { orderId } = params;
// Id
if (orderId == null || isEmpty(orderId)) {
return [];
}
return await ticketList(params);
}
/**
* 新增事件
*/

View File

@ -179,7 +179,7 @@ export const customerFormSchema: FormSchema[] = [
label: '联系方式',
field: 'telphone',
component: 'Input',
rules: [{ required: false, pattern: /^1[3|4|5|7|8|9][0-9]\d{8}$/, message: '手机号码格式有误' }],
rules: [{ required: false, pattern: /^1[3456789]\d{9}$/, message: '手机号码格式有误' }],
},
{
label: 'orderId',

View File

@ -138,6 +138,21 @@ export const schemas: FormSchema[] = [
label: '选择值',
colProps: { span: 12 },
},
{
field: 'xldx2',
component: 'JSelectMultiple',
label: '字典下拉多选2',
colProps: { span: 12 },
componentProps: {
dictCode: 'sex',
},
},
{
field: 'xldx2',
component: 'JEllipsis',
label: '选择值',
colProps: { span: 12 },
},
{
field: 'dxxlk',
component: 'JDictSelectTag',
@ -645,26 +660,21 @@ export const schemas: FormSchema[] = [
label: '选中值',
colProps: { span: 12 },
},
{
field: 'radioButtonGroup',
component: 'RadioButtonGroup',
label: 'RadioButtonGroup',
field: 'userSelect2',
component: 'UserSelect',
label: '高级用户选择',
helpMessage: ['component模式'],
colProps: { span: 12 },
defaultValue: '0',
componentProps: {
options: [
{ value: '0',icon: 'ant-design:setting'},
{ label: '停用', value: '1',icon: 'mdi:home' },
],
},
},
{
field: 'radioButtonGroup',
field: 'userSelect2',
component: 'JEllipsis',
label: '选中值',
colProps: { span: 12 },
},
{
field: 'superQuery',
component: 'Input',

View File

@ -78,7 +78,7 @@ export const baseSetschemas: FormSchema[] = [
dynamicRules: ({ model, schema }) => {
return [
{ ...rules.duplicateCheckRule('sys_user', 'phone', model, schema, false)[0] },
{ pattern: /^1[3|4|5|7|8|9][0-9]\d{8}$/, message: '手机号码格式有误' },
{ pattern: /^1[3456789]\d{9}$/, message: '手机号码格式有误' },
];
},
colProps: { span: 18 },

View File

@ -1,6 +1,7 @@
import { BasicColumn, FormSchema } from '/@/components/Table';
import { usePermission } from '/@/hooks/web/usePermission';
import { JVxeColumn, JVxeTypes } from '/@/components/jeecg/JVxeTable/types';
const { isDisabledAuth, hasPermission, initBpmFormData} = usePermission();
export const columns: BasicColumn[] = [
{
@ -47,7 +48,9 @@ export const columns: BasicColumn[] = [
];
export function getBpmFormSchema(formData) {
const { isDisabledAuth, hasPermission } = usePermission(formData);
//注入流程节点表单权限
initBpmFormData(formData);
const formSchema2: FormSchema[] = [
{
label: '订单号',
@ -94,7 +97,9 @@ export function getBpmFormSchema(formData) {
}
export function getOrderCustomerFormSchema(formData) {
const { isDisabledAuth, hasPermission } = usePermission(formData);
//注入流程节点表单权限
initBpmFormData(formData);
const formSchema2: FormSchema[] = [
{
label: '客户名',

View File

@ -46,7 +46,7 @@ export const columns: JVxeColumn[] = [
placeholder: '请输入${title}',
validateRules: [
{
pattern: '^1(3|4|5|7|8)\\d{9}$',
pattern: '^1[3456789]\\d{9}$',
message: '${title}格式不正确',
},
],

View File

@ -122,7 +122,7 @@
const rememberMe = ref(false);
const formData = reactive({
account: 'admin',
account: 'jeecg',
password: '123456',
inputCode: '',
});

View File

@ -137,6 +137,12 @@
* 处理部门情况
*/
function bizDepart(loginResult) {
//ID
if(loginResult.userInfo?.orgCode && loginResult.userInfo?.orgCode!==''){
isMultiDepart.value = false;
return;
}
let multi_depart = loginResult.multi_depart;
//0: 1: 2:
if (multi_depart == 0) {
@ -158,6 +164,12 @@
* 处理租户情况
*/
function bizTenantList(loginResult) {
//ID
if(loginResult.userInfo?.loginTenantId && loginResult.userInfo?.loginTenantId!==0){
isMultiTenant.value = false;
return;
}
let tenantArr = loginResult.tenantList;
if (Array.isArray(tenantArr)) {
if (tenantArr.length === 0) {
@ -210,10 +222,10 @@
*/
function departResolve() {
return new Promise((resolve, reject) => {
if (!unref(isMultiDepart)) {
if (!unref(isMultiDepart) && !unref(isMultiTenant)) {
resolve();
} else {
let params = { orgCode: formState.orgCode, username: unref(username) };
let params = { orgCode: formState.orgCode,loginTenantId: formState.tenantId, username: unref(username) };
defHttp.put({ url: '/sys/selectDepart', params }).then((res) => {
if (res.userInfo) {
userStore.setUserInfo(res.userInfo);

View File

@ -21,6 +21,9 @@ export enum Api {
getCurrentUserDeparts = '/sys/user/getCurrentUserDeparts',
selectDepart = '/sys/selectDepart',
getUpdateDepartInfo = '/sys/user/getUpdateDepartInfo',
doUpdateDepartInfo = '/sys/user/doUpdateDepartInfo',
changeDepartChargePerson = '/sys/user/changeDepartChargePerson',
}
/**
@ -93,3 +96,27 @@ export const getUserDeparts = (params?) => defHttp.get({ url: Api.getCurrentUser
*
*/
export const selectDepart = (params?) => defHttp.put({ url: Api.selectDepart, params });
/**
*
* @param id
*/
export const getUpdateDepartInfo = (id) => defHttp.get({ url: Api.getUpdateDepartInfo, params: {id} });
/**
*
* @param params
*/
export const doUpdateDepartInfo = (params) => defHttp.put({ url: Api.doUpdateDepartInfo, params });
/**
*
* @param id
*/
export const deleteDepart = (id) => defHttp.delete({ url: Api.delete, params:{ id } }, { joinParamsToUrl: true });
/**
*
* @param params
*/
export const changeDepartChargePerson = (params) => defHttp.put({ url: Api.changeDepartChargePerson, params });

View File

@ -9,7 +9,7 @@ export const columns: BasicColumn[] = [
width: 240,
},
{
title: '字典编',
title: '字典编',
dataIndex: 'dictCode',
width: 240,
},
@ -27,7 +27,7 @@ export const recycleBincolumns: BasicColumn[] = [
width: 120,
},
{
title: '字典编',
title: '字典编',
dataIndex: 'dictCode',
width: 120,
},

View File

@ -211,4 +211,10 @@ export const formSchema: FormSchema[] = [
placeholder: '请输入个人简介',
},
},
{
field: 'updateCount',
label: '乐观锁',
show: false,
component: 'Input',
},
];

View File

@ -53,12 +53,15 @@
<a-button preIcon="ant-design:import-outlined" type="primary">导入</a-button>
</a-upload>
<a-button preIcon="ant-design:export-outlined" type="primary" @click="handleExportXls('单表示例', getExportUrl,exportParams)">导出</a-button>
<a-button preIcon="ant-design:filter" type="primary" @click="">高级查询</a-button>
<a-button preIcon="ant-design:plus-outlined" type="primary" @click="openTab">Tab</a-button>
<a-button preIcon="ant-design:retweet-outlined" type="primary" @click="customSearch = !customSearch">{{
customSearch ? '表单配置查询' : '自定义查询'
}}</a-button>
<a-button preIcon="ant-design:import-outlined" type="primary" @click="handleImport"></a-button>
<super-query :config="superQueryConfig" @search="handleSuperQuery"/>
<a-dropdown v-if="checkedKeys.length > 0">
<template #overlay>
<a-menu>
@ -122,7 +125,7 @@
},
//
defSort: {
column: 'sex,salaryMoney',
column: 'createTime,sex',
order: 'desc',
},
striped: true,
@ -285,6 +288,17 @@
}
//----end---------
const superQueryConfig = reactive({
name:{ title: "名称", view: "text", type: "string", order: 1 },
sex:{ title: "性别", view: "list", type: "string", dictCode:'sex', order: 2 },
});
function handleSuperQuery(params) {
Object.keys(params).map(k=>{
queryParam[k] = params[k]
});
searchQuery();
}
</script>
<style lang="less" scoped>
.jeecg-basic-table-form-container {

View File

@ -0,0 +1,165 @@
<template>
<div class="aui-content">
<div class="aui-container">
<div class="aui-form">
<div class="aui-image">
<div class="aui-image-text">
<img :src="adTextImg" alt="" />
</div>
</div>
<div class="aui-formBox aui-formEwm">
<div class="aui-formWell">
<form>
<div class="aui-flex aui-form-nav investment_title" style="padding-bottom: 19px">
<div class="aui-flex-box activeNav">{{t('sys.login.qrSignInFormTitle')}}</div>
</div>
<div class="aui-form-box">
<div class="aui-account" style="padding: 30px 0">
<div class="aui-ewm">
<QrCode :value="qrCodeUrl" class="enter-x flex justify-center xl:justify-start" :width="280" />
</div>
</div>
</div>
<div class="aui-formButton">
<a class="aui-linek-code aui-link-register" @click="goBackHandleClick">{{t('sys.login.backSignIn')}}</a>
</div>
</form>
</div>
<div class="aui-flex aui-third-text">
<div class="aui-flex-box aui-third-border">
<span>{{ t('sys.login.otherSignIn') }}</span>
</div>
</div>
<div class="aui-flex" :class="`${prefixCls}-sign-in-way`">
<div class="aui-flex-box">
<div class="aui-third-login">
<a href="" title="github" @click="onThirdLogin('github')"><GithubFilled /></a>
</div>
</div>
<div class="aui-flex-box">
<div class="aui-third-login">
<a href="" title="企业微信" @click="onThirdLogin('wechat_enterprise')"><icon-font class="item-icon" type="icon-qiyeweixin3" /></a>
</div>
</div>
<div class="aui-flex-box">
<div class="aui-third-login">
<a href="" title="钉钉" @click="onThirdLogin('dingtalk')"><DingtalkCircleFilled /></a>
</div>
</div>
<div class="aui-flex-box">
<div class="aui-third-login">
<a href="" title="微信" @click="onThirdLogin('wechat_open')"><WechatFilled /></a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 第三方登录相关弹框 -->
<ThirdModal ref="thirdModalRef"></ThirdModal>
</template>
<script lang="ts" setup name="mini-code-login">
import { ref, onUnmounted } from 'vue';
import { getLoginQrcode, getQrcodeToken } from '/@/api/sys/user';
import { useUserStore } from '/@/store/modules/user';
import { QrCode } from '/@/components/Qrcode/index';
import ThirdModal from '/@/views/sys/login/ThirdModal.vue';
import logoImg from '/@/assets/loginmini/icon/jeecg_logo.png';
import adTextImg from '/@/assets/loginmini/icon/jeecg_ad_text.png';
import { useI18n } from '/@/hooks/web/useI18n';
import { useDesign } from "/@/hooks/web/useDesign";
import { GithubFilled, WechatFilled, DingtalkCircleFilled, createFromIconfontCN } from '@ant-design/icons-vue';
const IconFont = createFromIconfontCN({
scriptUrl: '//at.alicdn.com/t/font_2316098_umqusozousr.js',
});
const { prefixCls } = useDesign('minilogin');
const { t } = useI18n();
const qrCodeUrl = ref<string>('');
let timer: IntervalHandle;
const state = ref('0');
const thirdModalRef = ref();
const userStore = useUserStore();
const emit = defineEmits(['go-back', 'success', 'register']);
//
function loadQrCode() {
state.value = '0';
getLoginQrcode().then((res) => {
qrCodeUrl.value = res.qrcodeId;
if (res.qrcodeId) {
openTimer(res.qrcodeId);
}
});
}
//
function watchQrcodeToken(qrcodeId) {
getQrcodeToken({ qrcodeId: qrcodeId }).then((res) => {
let token = res.token;
if (token == '-2') {
//
loadQrCode();
clearInterval(timer);
}
//
if (res.success) {
state.value = '2';
clearInterval(timer);
setTimeout(() => {
userStore.qrCodeLogin(token);
}, 500);
}
});
}
/** 开启定时器 */
function openTimer(qrcodeId) {
watchQrcodeToken(qrcodeId);
closeTimer();
timer = setInterval(() => {
watchQrcodeToken(qrcodeId);
}, 1500);
}
/** 关闭定时器 */
function closeTimer() {
if (timer) clearInterval(timer);
}
/**
* 第三方登录
* @param type
*/
function onThirdLogin(type) {
thirdModalRef.value.onThirdLogin(type);
}
/**
* 初始化表单
*/
function initFrom() {
loadQrCode();
}
/**
* 返回
*/
function goBackHandleClick() {
emit('go-back');
closeTimer();
}
onUnmounted(() => {
closeTimer();
});
defineExpose({
initFrom,
});
</script>
<style lang="less" scoped>
@import '/@/assets/loginmini/style/home.less';
@import '/@/assets/loginmini/style/base.less';
</style>

View File

@ -0,0 +1,282 @@
<template>
<div class="aui-content">
<div class="aui-container">
<div class="aui-form">
<div class="aui-image">
<div class="aui-image-text">
<img :src="adTextImg" alt="" />
</div>
</div>
<div class="aui-formBox">
<div class="aui-formWell">
<div class="aui-step-box">
<div class="aui-step-item" :class="activeKey === 1 ? 'activeStep' : ''">
<div class="aui-step-tags">
<em>1</em>
<p>{{t('sys.login.authentication')}}</p>
</div>
</div>
<div class="aui-step-item" :class="activeKey === 2 ? 'activeStep' : ''">
<div class="aui-step-tags">
<em>2</em>
<p>{{t('sys.login.resetLoginPassword')}}</p>
</div>
</div>
<div class="aui-step-item" :class="activeKey === 3 ? 'activeStep' : ''">
<div class="aui-step-tags">
<em>3</em>
<p>{{t('sys.login.resetSuccess')}}</p>
</div>
</div>
</div>
<div class="" style="height: 230px; position: relative">
<a-form ref="formRef" :model="formData" v-if="activeKey === 1">
<!-- 身份验证 begin -->
<div class="aui-account aui-account-line aui-forgot">
<a-form-item>
<div class="aui-input-line">
<a-input type="text" :placeholder="t('sys.login.mobile')" v-model:value="formData.mobile" />
</div>
</a-form-item>
<div class="aui-input-line">
<a-form-item>
<a-input type="text" :placeholder="t('sys.login.smsCode')" v-model:value="formData.smscode" />
</a-form-item>
<div v-if="showInterval" class="aui-code-line" @click="getLoginCode">{{t('component.countdown.normalText')}}</div>
<div v-else class="aui-code-line">{{t('component.countdown.sendText',[unref(timeRuning)])}}</div>
</div>
</div>
<!-- 身份验证 end -->
</a-form>
<a-form ref="pwdFormRef" :model="pwdFormData" v-else-if="activeKey === 2">
<!-- 重置密码 begin -->
<div class="aui-account aui-account-line aui-forgot">
<a-form-item>
<div class="aui-input-line">
<a-input type="password" :placeholder="t('sys.login.passwordPlaceholder')" v-model:value="pwdFormData.password" />
</div>
</a-form-item>
<a-form-item>
<div class="aui-input-line">
<a-input type="password" :placeholder="t('sys.login.confirmPassword')" v-model:value="pwdFormData.confirmPassword" />
</div>
</a-form-item>
</div>
<!-- 重置密码 end -->
</a-form>
<!-- 重置成功 begin -->
<div class="aui-success" v-else>
<div class="aui-success-icon">
<img :src="successImg"/>
</div>
<h3>恭喜您重置密码成功</h3>
</div>
<!-- 重置成功 end -->
</div>
<div class="aui-formButton" style="padding-bottom: 40px">
<div class="aui-flex" v-if="activeKey === 1 || activeKey === 2">
<a class="aui-link-login aui-flex-box" @click="nextStepClick">{{t('sys.login.nextStep')}}</a>
</div>
<div class="aui-flex" v-else>
<a class="aui-linek-code aui-flex-box" @click="toLogin">{{t('sys.login.goToLogin')}}</a>
</div>
<div class="aui-flex">
<a class="aui-linek-code aui-flex-box" @click="goBack"> {{ t('sys.login.backSignIn') }}</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script lang="ts" name="mini-forgotpad" setup>
import { reactive, ref, toRaw, unref } from 'vue';
import { useI18n } from '/@/hooks/web/useI18n';
import { SmsEnum, useFormRules, useFormValid, useLoginState } from '/@/views/sys/login/useLogin';
import { useMessage } from '/@/hooks/web/useMessage';
import { getCaptcha, passwordChange, phoneVerify } from '/@/api/sys/user';
import logoImg from '/@/assets/loginmini/icon/jeecg_logo.png'
import adTextImg from '/@/assets/loginmini/icon/jeecg_ad_text.png'
import successImg from '/@/assets/loginmini/icon/icon-success.png'
//
const activeKey = ref<number>(1);
const { t } = useI18n();
const { handleBackLogin } = useLoginState();
const { notification, createMessage, createErrorModal } = useMessage();
//
const showInterval = ref<boolean>(true);
//60s
const timeRuning = ref<number>(60);
//
const timer = ref<any>(null);
const formRef = ref();
const pwdFormRef = ref();
//
const accountInfo = reactive<any>({});
//
const formData = reactive({
mobile: '',
smscode: '',
});
//
const pwdFormData = reactive<any>({
password: '',
confirmPassword: '',
});
const emit = defineEmits(['go-back', 'success', 'register']);
/**
* 下一步
*/
async function handleNext() {
if (!formData.mobile) {
createMessage.warn(t('sys.login.mobilePlaceholder'));
return;
}
if (!formData.smscode) {
createMessage.warn(t('sys.login.smsPlaceholder'));
return;
}
const resultInfo = await phoneVerify(
toRaw({
phone: formData.mobile,
smscode: formData.smscode,
})
);
if (resultInfo.success) {
Object.assign(accountInfo, {
username: resultInfo.result.username,
phone: formData.mobile,
smscode: formData.smscode,
});
activeKey.value = 2;
setTimeout(()=>{
pwdFormRef.value.resetFields();
},300)
} else {
notification.error({
message: '错误提示',
description: resultInfo.message || t('sys.api.networkExceptionMsg'),
duration: 3,
});
}
}
/**
* 完成修改密码
*/
async function finishedPwd() {
if (!pwdFormData.password) {
createMessage.warn(t('sys.login.passwordPlaceholder'));
return;
}
if (!pwdFormData.confirmPassword) {
createMessage.warn(t('sys.login.confirmPassword'));
return;
}
if (pwdFormData.password !== pwdFormData.confirmPassword) {
createMessage.warn(t('sys.login.diffPwd'));
return;
}
const resultInfo = await passwordChange(
toRaw({
username: accountInfo.username,
password: pwdFormData.password,
smscode: accountInfo.smscode,
phone: accountInfo.phone,
})
);
if (resultInfo.success) {
accountInfo.password = pwdFormData.password;
//
activeKey.value = 3;
} else {
//
createErrorModal({
title: t('sys.api.errorTip'),
content: resultInfo.message || t('sys.api.networkExceptionMsg'),
});
}
}
/**
* 下一步
*/
function nextStepClick() {
if (unref(activeKey) == 1) {
handleNext();
} else if (unref(activeKey) == 2) {
finishedPwd();
}
}
/**
* 去登录
*/
function toLogin() {
emit('success', { username: accountInfo.username, password: accountInfo.password });
initForm();
}
/**
* 返回
*/
function goBack() {
emit('go-back');
initForm();
}
/**
* 获取手机验证码
*/
async function getLoginCode() {
if (!formData.mobile) {
createMessage.warn(t('sys.login.mobilePlaceholder'));
return;
}
const result = await getCaptcha({ mobile: formData.mobile, smsmode: SmsEnum.FORGET_PASSWORD });
if (result) {
const TIME_COUNT = 60;
if (!unref(timer)) {
timeRuning.value = TIME_COUNT;
showInterval.value = false;
timer.value = setInterval(() => {
if (unref(timeRuning) > 0 && unref(timeRuning) <= TIME_COUNT) {
timeRuning.value = timeRuning.value - 1;
} else {
showInterval.value = true;
clearInterval(unref(timer));
timer.value = null;
}
}, 1000);
}
}
}
/**
* 初始化表单
*/
function initForm() {
activeKey.value = 1;
Object.assign(formData, { phone: '', smscode: '' });
Object.assign(pwdFormData, { password: '', confirmPassword: '' });
Object.assign(accountInfo, {});
if(unref(timer)){
clearInterval(unref(timer));
timer.value = null;
showInterval.value = true;
}
setTimeout(()=>{
formRef.value.resetFields();
},300)
}
defineExpose({
initForm,
});
</script>
<style lang="less" scoped>
@import '/@/assets/loginmini/style/home.less';
@import '/@/assets/loginmini/style/base.less';
</style>

View File

@ -0,0 +1,557 @@
<template>
<div :class="prefixCls" class="login-background-img">
<AppLocalePicker class="absolute top-4 right-4 enter-x xl:text-gray-600" :showText="false"/>
<AppDarkModeToggle class="absolute top-3 right-7 enter-x" />
<div class="aui-logo" v-if="!getIsMobile">
<div>
<h3>
<img :src="logoImg" alt="jeecg" />
</h3>
</div>
</div>
<div v-else class="aui-phone-logo">
<img :src="logoImg" alt="jeecg" />
</div>
<div v-show="type === 'login'">
<div class="aui-content">
<div class="aui-container">
<div class="aui-form">
<div class="aui-image">
<div class="aui-image-text">
<img :src="adTextImg" />
</div>
</div>
<div class="aui-formBox">
<div class="aui-formWell">
<div class="aui-flex aui-form-nav investment_title">
<div class="aui-flex-box" :class="activeIndex === 'accountLogin' ? 'activeNav on' : ''" @click="loginClick('accountLogin')"
>{{ t('sys.login.signInFormTitle') }}
</div>
<div class="aui-flex-box" :class="activeIndex === 'phoneLogin' ? 'activeNav on' : ''" @click="loginClick('phoneLogin')"
>{{ t('sys.login.mobileSignInFormTitle') }}
</div>
</div>
<div class="aui-form-box" style="height: 180px">
<a-form ref="loginRef" :model="formData" v-if="activeIndex === 'accountLogin'" @keyup.enter.native="loginHandleClick">
<div class="aui-account">
<div class="aui-inputClear">
<i class="icon icon-code"></i>
<a-form-item>
<a-input class="fix-auto-fill" :placeholder="t('sys.login.userName')" v-model:value="formData.username" />
</a-form-item>
</div>
<div class="aui-inputClear">
<i class="icon icon-password"></i>
<a-form-item>
<a-input class="fix-auto-fill" type="password" :placeholder="t('sys.login.password')" v-model:value="formData.password" />
</a-form-item>
</div>
<div class="aui-inputClear">
<i class="icon icon-code"></i>
<a-form-item>
<a-input class="fix-auto-fill" type="text" :placeholder="t('sys.login.inputCode')" v-model:value="formData.inputCode" />
</a-form-item>
<div class="aui-code">
<img v-if="randCodeData.requestCodeSuccess" :src="randCodeData.randCodeImage" @click="handleChangeCheckCode" />
<img v-else style="margin-top: 2px; max-width: initial" :src="codeImg" @click="handleChangeCheckCode" />
</div>
</div>
<div class="aui-flex">
<div class="aui-flex-box">
<div class="aui-choice">
<a-input class="fix-auto-fill" type="checkbox" v-model:value="rememberMe" />
<span style="margin-left: 5px">{{ t('sys.login.rememberMe') }}</span>
</div>
</div>
<div class="aui-forget">
<a @click="forgetHandelClick"> {{ t('sys.login.forgetPassword') }}</a>
</div>
</div>
</div>
</a-form>
<a-form v-else ref="phoneFormRef" :model="phoneFormData" @keyup.enter.native="loginHandleClick">
<div class="aui-account phone">
<div class="aui-inputClear phoneClear">
<a-input class="fix-auto-fill" :placeholder="t('sys.login.mobile')" v-model:value="phoneFormData.mobile" />
</div>
<div class="aui-inputClear">
<a-input class="fix-auto-fill" :maxlength="6" :placeholder="t('sys.login.smsCode')" v-model:value="phoneFormData.smscode" />
<div v-if="showInterval" class="aui-code" @click="getLoginCode">
<a>{{ t('component.countdown.normalText') }}</a>
</div>
<div v-else class="aui-code">
<span class="aui-get-code code-shape">{{ t('component.countdown.sendText', [unref(timeRuning)]) }}</span>
</div>
</div>
</div>
</a-form>
</div>
<div class="aui-formButton">
<div class="aui-flex">
<a-button :loading="loginLoading" class="aui-link-login aui-flex-box" type="primary" @click="loginHandleClick">
{{ t('sys.login.loginButton') }}</a-button>
</div>
<div class="aui-flex">
<a class="aui-linek-code aui-flex-box" @click="codeHandleClick">{{ t('sys.login.qrSignInFormTitle') }}</a>
</div>
<div class="aui-flex">
<a class="aui-linek-code aui-flex-box" @click="registerHandleClick">{{ t('sys.login.registerButton') }}</a>
</div>
</div>
</div>
<a-form @keyup.enter.native="loginHandleClick">
<div class="aui-flex aui-third-text">
<div class="aui-flex-box aui-third-border">
<span>{{ t('sys.login.otherSignIn') }}</span>
</div>
</div>
<div class="aui-flex" :class="`${prefixCls}-sign-in-way`">
<div class="aui-flex-box">
<div class="aui-third-login">
<a title="github" @click="onThirdLogin('github')"><GithubFilled /></a>
</div>
</div>
<div class="aui-flex-box">
<div class="aui-third-login">
<a title="企业微信" @click="onThirdLogin('wechat_enterprise')"><icon-font class="item-icon" type="icon-qiyeweixin3" /></a>
</div>
</div>
<div class="aui-flex-box">
<div class="aui-third-login">
<a title="钉钉" @click="onThirdLogin('dingtalk')"><DingtalkCircleFilled /></a>
</div>
</div>
<div class="aui-flex-box">
<div class="aui-third-login">
<a title="微信" @click="onThirdLogin('wechat_open')"><WechatFilled /></a>
</div>
</div>
</div>
</a-form>
</div>
</div>
</div>
</div>
</div>
<div v-show="type === 'forgot'" :class="`${prefixCls}-form`">
<MiniForgotpad ref="forgotRef" @go-back="goBack" @success="handleSuccess" />
</div>
<div v-show="type === 'register'" :class="`${prefixCls}-form`">
<MiniRegister ref="registerRef" @go-back="goBack" @success="handleSuccess" />
</div>
<div v-show="type === 'codeLogin'" :class="`${prefixCls}-form`">
<MiniCodelogin ref="codeRef" @go-back="goBack" @success="handleSuccess" />
</div>
<!-- 第三方登录相关弹框 -->
<ThirdModal ref="thirdModalRef"></ThirdModal>
</div>
</template>
<script lang="ts" setup name="login-mini">
import { getCaptcha, getCodeInfo } from '/@/api/sys/user';
import { computed, onMounted, reactive, ref, toRaw, unref } from 'vue';
import codeImg from '/@/assets/images/checkcode.png';
import { Rule } from '/@/components/Form';
import { useUserStore } from '/@/store/modules/user';
import { useMessage } from '/@/hooks/web/useMessage';
import { useI18n } from '/@/hooks/web/useI18n';
import { SmsEnum } from '/@/views/sys/login/useLogin';
import ThirdModal from '/@/views/sys/login/ThirdModal.vue';
import MiniForgotpad from './MiniForgotpad.vue';
import MiniRegister from './MiniRegister.vue';
import MiniCodelogin from './MiniCodelogin.vue';
import logoImg from '/@/assets/loginmini/icon/jeecg_logo.png';
import adTextImg from '/@/assets/loginmini/icon/jeecg_ad_text.png';
import { AppLocalePicker, AppDarkModeToggle } from '/@/components/Application';
import { useLocaleStore } from '/@/store/modules/locale';
import { useDesign } from "/@/hooks/web/useDesign";
import { useAppInject } from "/@/hooks/web/useAppInject";
import { GithubFilled, WechatFilled, DingtalkCircleFilled, createFromIconfontCN } from '@ant-design/icons-vue';
const IconFont = createFromIconfontCN({
scriptUrl: '//at.alicdn.com/t/font_2316098_umqusozousr.js',
});
const { prefixCls } = useDesign('mini-login');
const { notification, createMessage } = useMessage();
const userStore = useUserStore();
const { t } = useI18n();
const localeStore = useLocaleStore();
const showLocale = localeStore.getShowPicker;
const randCodeData = reactive<any>({
randCodeImage: '',
requestCodeSuccess: false,
checkKey: null,
});
const rememberMe = ref<string>('0');
//
const activeIndex = ref<string>('accountLogin');
const type = ref<string>('login');
//
const formData = reactive<any>({
inputCode: '',
username: 'admin',
password: '123456',
});
//
const phoneFormData = reactive<any>({
mobile: '',
smscode: '',
});
const loginRef = ref();
//
const thirdModalRef = ref();
//
const codeRef = ref();
//
const showInterval = ref<boolean>(true);
//60s
const timeRuning = ref<number>(60);
//
const timer = ref<any>(null);
//
const forgotRef = ref();
//
const registerRef = ref();
const loginLoading = ref<boolean>(false);
const { getIsMobile } = useAppInject();
defineProps({
sessionTimeout: {
type: Boolean,
},
});
/**
* 获取验证码
*/
function handleChangeCheckCode() {
formData.inputCode = '';
randCodeData.checkKey = 1629428467008;
getCodeInfo(randCodeData.checkKey).then((res) => {
randCodeData.randCodeImage = res;
randCodeData.requestCodeSuccess = true;
});
}
/**
* 切换登录方式
*/
function loginClick(type) {
activeIndex.value = type;
}
/**
* 账号或者手机登录
*/
async function loginHandleClick() {
if (unref(activeIndex) === 'accountLogin') {
accountLogin();
} else {
//
phoneLogin();
}
}
async function accountLogin() {
if (!formData.username) {
createMessage.warn(t('sys.login.accountPlaceholder'));
return;
}
if (!formData.password) {
createMessage.warn(t('sys.login.passwordPlaceholder'));
return;
}
try {
loginLoading.value = true;
const { userInfo } = await userStore.login(
toRaw({
password: formData.password,
username: formData.username,
captcha: formData.inputCode,
checkKey: randCodeData.checkKey,
mode: 'none', //
})
);
if (userInfo) {
notification.success({
message: t('sys.login.loginSuccessTitle'),
description: `${t('sys.login.loginSuccessDesc')}: ${userInfo.realname}`,
duration: 3,
});
}
} catch (error) {
notification.error({
message: t('sys.api.errorTip'),
description: error.message || t('sys.login.networkExceptionMsg'),
duration: 3,
});
handleChangeCheckCode();
} finally {
loginLoading.value = false;
}
}
/**
* 手机号登录
*/
async function phoneLogin() {
if (!phoneFormData.mobile) {
createMessage.warn(t('sys.login.mobilePlaceholder'));
return;
}
if (!phoneFormData.smscode) {
createMessage.warn(t('sys.login.smsPlaceholder'));
return;
}
try {
loginLoading.value = true;
const { userInfo }: any = await userStore.phoneLogin({
mobile: phoneFormData.mobile,
captcha: phoneFormData.smscode,
mode: 'none', //
});
if (userInfo) {
notification.success({
message: t('sys.login.loginSuccessTitle'),
description: `${t('sys.login.loginSuccessDesc')}: ${userInfo.realname}`,
duration: 3,
});
}
} catch (error) {
notification.error({
message: t('sys.api.errorTip'),
description: error.message || t('sys.login.networkExceptionMsg'),
duration: 3,
});
} finally {
loginLoading.value = false;
}
}
/**
* 获取手机验证码
*/
async function getLoginCode() {
if (!phoneFormData.mobile) {
createMessage.warn(t('sys.login.mobilePlaceholder'));
return;
}
const result = await getCaptcha({ mobile: phoneFormData.mobile, smsmode: SmsEnum.FORGET_PASSWORD });
if (result) {
const TIME_COUNT = 60;
if (!unref(timer)) {
timeRuning.value = TIME_COUNT;
showInterval.value = false;
timer.value = setInterval(() => {
if (unref(timeRuning) > 0 && unref(timeRuning) <= TIME_COUNT) {
timeRuning.value = timeRuning.value - 1;
} else {
showInterval.value = true;
clearInterval(unref(timer));
timer.value = null;
}
}, 1000);
}
}
}
/**
* 第三方登录
* @param type
*/
function onThirdLogin(type) {
thirdModalRef.value.onThirdLogin(type);
}
/**
* 忘记密码
*/
function forgetHandelClick() {
type.value = 'forgot';
setTimeout(() => {
forgotRef.value.initForm();
}, 300);
}
/**
* 返回登录页面
*/
function goBack() {
activeIndex.value = 'accountLogin';
type.value = 'login';
}
/**
* 忘记密码/注册账号回调事件
* @param value
*/
function handleSuccess(value) {
Object.assign(formData, value);
Object.assign(phoneFormData, { mobile: "", smscode: "" });
type.value = 'login';
activeIndex.value = 'accountLogin';
handleChangeCheckCode();
}
/**
* 注册
*/
function registerHandleClick() {
type.value = 'register';
setTimeout(() => {
registerRef.value.initForm();
}, 300);
}
/**
* 注册
*/
function codeHandleClick() {
type.value = 'codeLogin';
setTimeout(() => {
codeRef.value.initFrom();
}, 300);
}
onMounted(() => {
//
handleChangeCheckCode();
});
</script>
<style lang="less" scoped>
@import '/@/assets/loginmini/style/home.less';
@import '/@/assets/loginmini/style/base.less';
:deep(.ant-input:focus) {
box-shadow: none;
}
.aui-get-code {
float: right;
position: relative;
z-index: 3;
background: #ffffff;
color: #1573e9;
border-radius: 100px;
padding: 5px 16px;
margin: 7px;
border: 1px solid #1573e9;
top: 12px;
}
.aui-get-code:hover {
color: #1573e9;
}
.code-shape {
border-color: #dadada !important;
color: #aaa !important;
}
:deep(.jeecg-dark-switch){
position:absolute;
margin-right: 10px;
}
.aui-link-login{
height: 42px;
padding: 10px 15px;
font-size: 14px;
border-radius: 8px;
margin-top: 15px;
margin-bottom: 8px;
}
.aui-phone-logo{
position: absolute;
margin-left: 10px;
width: 60px;
top:2px;
z-index: 4;
}
.top-3{
top: 0.45rem;
}
</style>
<style lang="less">
@prefix-cls: ~'@{namespace}-mini-login';
@dark-bg: #293146;
html[data-theme='dark'] {
.@{prefix-cls} {
background-color: @dark-bg !important;
background-image: none;
&::before {
background-image: url(/@/assets/svg/login-bg-dark.svg);
}
.aui-inputClear{
background-color: #232a3b !important;
}
.ant-input,
.ant-input-password {
background-color: #232a3b !important;
}
.ant-btn:not(.ant-btn-link):not(.ant-btn-primary) {
border: 1px solid #4a5569 !important;
}
&-form {
background: @dark-bg !important;
}
.app-iconify {
color: #fff !important;
}
.aui-inputClear input,.aui-input-line input,.aui-choice{
color: #c9d1d9 !important;
}
.aui-formBox{
background-color: @dark-bg !important;
}
.aui-third-text span{
background-color: @dark-bg !important;
}
.aui-form-nav .aui-flex-box{
color: #c9d1d9 !important;
}
.aui-formButton .aui-linek-code{
background: @dark-bg !important;
color: white !important;
}
.aui-code-line{
border-left: none !important;
}
.ant-checkbox-inner,.aui-success h3{
border-color: #c9d1d9;
}
}
input.fix-auto-fill,
.fix-auto-fill input {
-webkit-text-fill-color: #c9d1d9 !important;
box-shadow: inherit !important;
}
&-sign-in-way {
.anticon {
font-size: 22px !important;
color: #888 !important;
cursor: pointer !important;
&:hover {
color: @primary-color !important;
}
}
}
.ant-divider-inner-text {
font-size: 12px !important;
color: @text-color-secondary !important;
}
.aui-third-login a{
background: transparent;
}
}
</style>

View File

@ -0,0 +1,266 @@
<template>
<div class="aui-content">
<div class="aui-container">
<div class="aui-form">
<div class="aui-image">
<div class="aui-image-text">
<img :src="jeecgAdTextImg" alt="" />
</div>
</div>
<div class="aui-formBox">
<div class="aui-formWell">
<a-form ref="formRef" :model="formData">
<div class="aui-flex aui-form-nav aui-clear-left" style="padding-bottom: 21px">
<div class="aui-flex-box activeNav on">{{t('sys.login.signUpFormTitle')}}</div>
</div>
<div class="aui-form-box">
<div class="aui-account aui-account-line">
<a-form-item>
<div class="aui-input-line">
<Icon class="aui-icon" icon="ant-design:user-outlined"/>
<a-input class="fix-auto-fill" type="text" :placeholder="t('sys.login.userName')" v-model:value="formData.username" />
</div>
</a-form-item>
<a-form-item>
<div class="aui-input-line">
<Icon class="aui-icon" icon="ant-design:mobile-outlined"/>
<a-input class="fix-auto-fill" type="text" :placeholder="t('sys.login.mobile')" v-model:value="formData.mobile" />
</div>
</a-form-item>
<a-form-item>
<div class="aui-input-line">
<Icon class="aui-icon" icon="ant-design:mail-outlined"/>
<a-input class="fix-auto-fill" type="text" :placeholder="t('sys.login.smsCode')" v-model:value="formData.smscode" />
<div v-if="showInterval" class="aui-code-line" @click="getLoginCode">{{t('component.countdown.normalText')}}</div>
<div v-else class="aui-code-line">{{t('component.countdown.sendText',[unref(timeRuning)])}}</div>
</div>
</a-form-item>
<a-form-item>
<div class="aui-input-line">
<Icon class="aui-icon" icon="ant-design:lock-outlined"/>
<a-input class="fix-auto-fill" :type="pwdIndex==='close'?'password':'text'" :placeholder="t('sys.login.password')" v-model:value="formData.password" />
<div class="aui-eye">
<img :src="eyeKImg" alt="开启" v-if="pwdIndex==='open'" @click="pwdClick('close')" />
<img :src="eyeGImg" alt="关闭" v-else-if="pwdIndex==='close'" @click="pwdClick('open')" />
</div>
</div>
</a-form-item>
<a-form-item>
<div class="aui-input-line">
<Icon class="aui-icon" icon="ant-design:lock-outlined"/>
<a-input class="fix-auto-fill" :type="confirmPwdIndex==='close'?'password':'text'" :placeholder="t('sys.login.confirmPassword')" v-model:value="formData.confirmPassword" />
<div class="aui-eye">
<img :src="eyeKImg" alt="开启" v-if="confirmPwdIndex==='open'" @click="confirmPwdClick('close')" />
<img :src="eyeGImg" alt="关闭" v-else-if="confirmPwdIndex==='close'" @click="confirmPwdClick('open')" />
</div>
</div>
</a-form-item>
<a-form-item name="policy">
<div class="aui-flex">
<div class="aui-flex-box">
<div class="aui-choice">
<a-checkbox v-model:checked="formData.policy" />
<span style="color: #1b90ff;margin-left: 4px">{{ t('sys.login.policy') }}</span>
</div>
</div>
</div>
</a-form-item>
</div>
</div>
<div class="aui-formButton">
<div class="aui-flex">
<a class="aui-link-login aui-flex-box" @click="registerHandleClick"> {{ t('sys.login.registerButton') }}</a>
</div>
<div class="aui-flex">
<a class="aui-linek-code aui-flex-box" @click="goBackHandleClick">{{ t('sys.login.backSignIn') }}</a>
</div>
</div>
</a-form>
</div>
</div>
</div>
</div>
</div>
</template>
<script lang="ts" setup name="mini-register">
import { ref, reactive, unref, toRaw } from 'vue';
import { getCaptcha, register } from '/@/api/sys/user';
import { SmsEnum } from '/@/views/sys/login/useLogin';
import { useMessage } from '/@/hooks/web/useMessage';
import logoImg from '/@/assets/loginmini/icon/jeecg_logo.png';
import jeecgAdTextImg from '/@/assets/loginmini/icon/jeecg_ad_text.png';
import eyeKImg from '/@/assets/loginmini/icon/icon-eye-k.png';
import eyeGImg from '/@/assets/loginmini/icon/icon-eye-g.png';
import { useI18n } from "/@/hooks/web/useI18n";
const { t } = useI18n();
const { notification, createErrorModal, createMessage } = useMessage();
const emit = defineEmits(['go-back', 'success', 'register']);
const formRef = ref();
const formData = reactive<any>({
username: '',
mobile: '',
smscode: '',
password: '',
confirmPassword: '',
policy: false,
});
//
const showInterval = ref<boolean>(true);
//60s
const timeRuning = ref<number>(60);
//
const timer = ref<any>(null);
//
const pwdIndex = ref<string>('close');
//
const confirmPwdIndex = ref<string>('close');
/**
* 返回
*/
function goBackHandleClick() {
emit('go-back');
initForm();
}
/**
* 获取手机验证码
*/
async function getLoginCode() {
if (!formData.mobile) {
createMessage.warn(t('sys.login.mobilePlaceholder'));
return;
}
const result = await getCaptcha({ mobile: formData.mobile, smsmode: SmsEnum.REGISTER });
if (result) {
const TIME_COUNT = 60;
if (!unref(timer)) {
timeRuning.value = TIME_COUNT;
showInterval.value = false;
timer.value = setInterval(() => {
if (unref(timeRuning) > 0 && unref(timeRuning) <= TIME_COUNT) {
timeRuning.value = timeRuning.value - 1;
} else {
showInterval.value = true;
clearInterval(unref(timer));
timer.value = null;
}
}, 1000);
}
}
}
function registerHandleClick() {
if (!formData.username) {
createMessage.warn(t('sys.login.accountPlaceholder'));
return;
}
if (!formData.mobile) {
createMessage.warn(t('sys.login.mobilePlaceholder'));
return;
}
if (!formData.smscode) {
createMessage.warn(t('sys.login.smsPlaceholder'));
return;
}
if (!formData.password) {
createMessage.warn(t('sys.login.passwordPlaceholder'));
return;
}
if (!formData.confirmPassword) {
createMessage.warn(t('sys.login.confirmPassword'));
return;
}
if (formData.password !== formData.confirmPassword) {
createMessage.warn(t('sys.login.diffPwd'));
return;
}
if(!formData.policy){
createMessage.warn(t('sys.login.policyPlaceholder'));
return;
}
registerAccount();
}
/**
* 注册账号
*/
async function registerAccount() {
try {
const resultInfo = await register(
toRaw({
username: formData.username,
password: formData.password,
phone: formData.mobile,
smscode: formData.smscode,
})
);
if (resultInfo && resultInfo.data.success) {
notification.success({
description: resultInfo.data.message || t('sys.api.registerMsg'),
duration: 3,
});
emit('success', { username: formData.username, password: formData.password });
initForm();
} else {
notification.warning({
message: t('sys.api.errorTip'),
description: resultInfo.data.message || t('sys.api.networkExceptionMsg'),
duration: 3,
});
}
} catch (error) {
notification.error({
message: t('sys.api.errorTip'),
description: error.message || t('sys.api.networkExceptionMsg'),
duration: 3,
});
}
}
/**
* 初始化表单
*/
function initForm() {
Object.assign(formData,{username:'',mobile: '', smscode: '', password: '', confirmPassword: '', policy: false})
if(!unref(timer)){
showInterval.value = true;
clearInterval(unref(timer));
timer.value = null;
}
formRef.value.resetFields();
}
/**
* 密码打开或关闭
* @param value
*/
function pwdClick(value) {
pwdIndex.value = value;
}
/**
* 确认密码打开或关闭
* @param value
*/
function confirmPwdClick(value) {
confirmPwdIndex.value = value;
}
defineExpose({
initForm
})
</script>
<style lang="less" scoped>
@import '/@/assets/loginmini/style/home.less';
@import '/@/assets/loginmini/style/base.less';
.aui-input-line .aui-icon{
position: absolute;
z-index: 2;
top: 10px;
left: 10px;
font-size: 20px !important;
}
</style>

View File

@ -0,0 +1,86 @@
<template>
<div> </div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { isOAuth2AppEnv, sysOAuth2Login } from '/@/views/sys/login/useLogin';
import { useRouter } from 'vue-router';
import { PageEnum } from '/@/enums/pageEnum';
import { router } from '/@/router';
import { useUserStore } from '/@/store/modules/user';
import { useMessage } from '/@/hooks/web/useMessage';
import { useI18n } from '/@/hooks/web/useI18n';
const isOAuth = ref<boolean>(isOAuth2AppEnv());
const env = ref<any>({ thirdApp: false, wxWork: false, dingtalk: false });
const { currentRoute } = useRouter();
const route = currentRoute.value;
if (!isOAuth2AppEnv()) {
router.replace({ path: PageEnum.BASE_LOGIN, query: route.query });
}
if (isOAuth.value) {
checkEnv();
}
/**
* 检测当前的环境
*/
function checkEnv() {
//
if (/wxwork/i.test(navigator.userAgent)) {
env.value.thirdApp = true;
env.value.wxWork = true;
}
//
if (/dingtalk/i.test(navigator.userAgent)) {
env.value.thirdApp = true;
env.value.dingtalk = true;
}
doOAuth2Login();
}
/**
* 进行OAuth2登录操作
*/
function doOAuth2Login() {
if (env.value.thirdApp) {
// Token
if (route.query.oauth2LoginToken) {
let token = route.query.oauth2LoginToken;
//
thirdLogin({ token, thirdType: route.query.thirdType });
} else if (env.value.wxWork) {
sysOAuth2Login('wechat_enterprise');
} else if (env.value.dingtalk) {
sysOAuth2Login('dingtalk');
}
}
}
/**
* 第三方登录
* @param params
*/
function thirdLogin(params) {
const userStore = useUserStore();
const { notification } = useMessage();
const { t } = useI18n();
userStore.ThirdLogin(params).then((res) => {
if (res && res.userInfo) {
notification.success({
message: t('sys.login.loginSuccessTitle'),
description: `${t('sys.login.loginSuccessDesc')}: ${res.userInfo.realname}`,
duration: 3,
});
} else {
notification.error({
message: t('sys.login.errorTip'),
description: ((res.response || {}).data || {}).message || res.message || t('sys.login.networkExceptionMsg'),
duration: 4,
});
}
});
}
</script>

Some files were not shown because too many files have changed in this diff Show More