|
|
@@ -0,0 +1,554 @@
|
|
|
+<template>
|
|
|
+ <Navbar title="新建认证源" :is-go-back="true" />
|
|
|
+ <a-card
|
|
|
+ :bordered="false"
|
|
|
+ style="margin-top: 20px; box-shadow: 0 2px 4px 0 rgba(54, 58, 80, 0.32)"
|
|
|
+ >
|
|
|
+ <a-steps class="steps" :current="current" type="navigation">
|
|
|
+ <a-step v-for="item in steps" :key="item.title" :title="item.title" />
|
|
|
+ </a-steps>
|
|
|
+ <a-divider />
|
|
|
+ <div class="steps-content">
|
|
|
+ <div v-if="steps[current].contentTemplate === 'First'">
|
|
|
+ <div class="box">
|
|
|
+ <div style="display: flex">
|
|
|
+ <label class="labelStyle">选择认证源 <span style="color: red">*</span></label>
|
|
|
+ <ul class="selectUl">
|
|
|
+ <li
|
|
|
+ v-for="(item, index) in selectedItem"
|
|
|
+ :key="index"
|
|
|
+ :class="{ active: activeIndex === index }"
|
|
|
+ @click="setActiveItem(index)"
|
|
|
+ >
|
|
|
+ <img class="picture" :src="item.imgSrc" />
|
|
|
+ <div class="info">
|
|
|
+ <h3 style="font-size: 14px; font-weight: 700">
|
|
|
+ {{ item.title }}
|
|
|
+ </h3>
|
|
|
+ <p v-tooltip.bottom="item.tooltip" class="text">
|
|
|
+ {{ item.tooltip }}
|
|
|
+ </p>
|
|
|
+ </div>
|
|
|
+ </li>
|
|
|
+ </ul>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div v-else-if="steps[current].contentTemplate === 'Second'">
|
|
|
+ <a-form
|
|
|
+ :model="identityInfo"
|
|
|
+ :label-col="labelCol"
|
|
|
+ :wrapper-col="wrapperCol"
|
|
|
+ autocomplete="off"
|
|
|
+ >
|
|
|
+ <a-form-item
|
|
|
+ label="认证源名称"
|
|
|
+ name="name"
|
|
|
+ :rules="[{ required: true, message: '请输入认证源名称!' }]"
|
|
|
+ >
|
|
|
+ <a-input
|
|
|
+ v-model:value="identityInfo.name"
|
|
|
+ placeholder="必填,请输入认证源名称"
|
|
|
+ />
|
|
|
+ </a-form-item>
|
|
|
+
|
|
|
+ <a-form-item
|
|
|
+ label="认证源LOGO"
|
|
|
+ name="logo"
|
|
|
+ :rules="[{ required: true, message: '请选择LOGO!' }]"
|
|
|
+ >
|
|
|
+ <div v-tooltip.top="'点击查看大图'">
|
|
|
+ <a-image :width="48" :src="logoUrl" />
|
|
|
+ </div>
|
|
|
+ <a-upload
|
|
|
+ v-model:file-list="identityInfo.logo"
|
|
|
+ :max-count="1"
|
|
|
+ :before-upload="beforeUpload"
|
|
|
+ :show-upload-list="false"
|
|
|
+ @change="logoFileChange"
|
|
|
+ >
|
|
|
+ <a style="margin-left: 6px">去上传</a>
|
|
|
+ </a-upload>
|
|
|
+ <span>
|
|
|
+ <a style="margin-left: 6px" @click="deleteLogo">删除</a>
|
|
|
+ </span>
|
|
|
+ </a-form-item>
|
|
|
+ <a-form-item label="认证源描述" name="description" style="margin-top: 6px;">
|
|
|
+ <a-textarea
|
|
|
+ v-model:value="identityInfo.description"
|
|
|
+ :rows="4"
|
|
|
+ placeholder="非必填,请输入认证源描述"
|
|
|
+ />
|
|
|
+ </a-form-item>
|
|
|
+ </a-form>
|
|
|
+ </div>
|
|
|
+ <div v-else-if="steps[current].contentTemplate === 'Third'">
|
|
|
+ <a-form
|
|
|
+ name="basic"
|
|
|
+ :label-col="labelCol"
|
|
|
+ :wrapper-col="wrapperCol"
|
|
|
+ :model="identitySetting"
|
|
|
+ autocomplete="off"
|
|
|
+ >
|
|
|
+ <a-form-item
|
|
|
+ label="身份提供商 id"
|
|
|
+ name="entityID"
|
|
|
+ :rules="[{ required: true, message: '请输入身份提供商 id!' }]"
|
|
|
+ >
|
|
|
+ <a-input
|
|
|
+ v-model:value="identitySetting.entityID"
|
|
|
+ placeholder="必填,请输入身份提供商 id"
|
|
|
+ />
|
|
|
+ </a-form-item>
|
|
|
+ <a-form-item
|
|
|
+ label="SSO 地址"
|
|
|
+ name="ssoUrl"
|
|
|
+ :rules="[{ required: true, message: '请输入 SSO 地址' }]"
|
|
|
+ >
|
|
|
+ <a-input
|
|
|
+ v-model:value="identitySetting.ssoUrl"
|
|
|
+ placeholder="必填,请输入 SSO 地址"
|
|
|
+ />
|
|
|
+ </a-form-item>
|
|
|
+ <a-form-item
|
|
|
+ label="证书"
|
|
|
+ name="certificate"
|
|
|
+ :rules="[{ required: true, message: '请输入证书' }]"
|
|
|
+ >
|
|
|
+ <a-textarea
|
|
|
+ v-model:value="identitySetting.certificate"
|
|
|
+ :rows="4"
|
|
|
+ placeholder="必填,请输入证书"
|
|
|
+ @blur="certificateBlur"
|
|
|
+ />
|
|
|
+ </a-form-item>
|
|
|
+ <a-form-item label="退出 URL" name="loginOutUrl">
|
|
|
+ <a-input
|
|
|
+ v-model:value="identitySetting.loginOutUrl"
|
|
|
+ placeholder="非必填,请输入退出 URL"
|
|
|
+ />
|
|
|
+ </a-form-item>
|
|
|
+ </a-form>
|
|
|
+ </div>
|
|
|
+ <div v-else>
|
|
|
+ <a-form
|
|
|
+ name="basic"
|
|
|
+ :label-col="{ style: { width: '186px' } }"
|
|
|
+ :wrapper-col="wrapperCol"
|
|
|
+ :model="identitySetting"
|
|
|
+ autocomplete="off"
|
|
|
+ >
|
|
|
+ <a-form-item label="uid(用户ID)" name="uid">
|
|
|
+ <a-input
|
|
|
+ v-model:value="identitySetting.uid"
|
|
|
+ placeholder="选填,IDP 中用户ID"
|
|
|
+ />
|
|
|
+ </a-form-item>
|
|
|
+ <a-form-item label="userName(用户姓名)" name="userName">
|
|
|
+ <a-input
|
|
|
+ v-model:value="identitySetting.userName"
|
|
|
+ placeholder="选填,IDP 中用户姓名"
|
|
|
+ />
|
|
|
+ </a-form-item>
|
|
|
+ <a-form-item label="userNo(员工编号)" name="userNo">
|
|
|
+ <a-input
|
|
|
+ v-model:value="identitySetting.userNo"
|
|
|
+ placeholder="选填,IDP 中员工编号"
|
|
|
+ />
|
|
|
+ </a-form-item>
|
|
|
+ <a-form-item label="nickName(员工昵称)" name="nickName">
|
|
|
+ <a-input
|
|
|
+ v-model:value="identitySetting.nickName"
|
|
|
+ placeholder="选填,IDP 中员工昵称"
|
|
|
+ />
|
|
|
+ </a-form-item>
|
|
|
+ <a-form-item label="email(员工邮箱)" name="email">
|
|
|
+ <a-input
|
|
|
+ v-model:value="identitySetting.email"
|
|
|
+ placeholder="选填,IDP 中员工邮箱"
|
|
|
+ />
|
|
|
+ </a-form-item>
|
|
|
+ <a-form-item label="phoneNumber(员工电话)" name="phoneNumber">
|
|
|
+ <a-input
|
|
|
+ v-model:value="identitySetting.phoneNumber"
|
|
|
+ placeholder="选填,IDP 中员工电话"
|
|
|
+ />
|
|
|
+ </a-form-item>
|
|
|
+ <a-form-item label="roleTemplateNo(角色模板)" name="roleTemplateNo">
|
|
|
+ <a-input
|
|
|
+ v-model:value="identitySetting.roleTemplateNo"
|
|
|
+ placeholder="选填,如果有多个角色模板编号,使用逗号分隔"
|
|
|
+ />
|
|
|
+ </a-form-item>
|
|
|
+ </a-form>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="steps-action">
|
|
|
+ <a-button v-if="current > 0" @click="prev"> 上一步 </a-button>
|
|
|
+ <a-button v-if="current === 0" disabled @click="prev"> 上一步 </a-button>
|
|
|
+ <a-button
|
|
|
+ v-if="current === 0"
|
|
|
+ type="primary"
|
|
|
+ style="margin-left: 8px"
|
|
|
+ @click="next"
|
|
|
+ >
|
|
|
+ 下一步
|
|
|
+ </a-button>
|
|
|
+ <a-button
|
|
|
+ v-if="current === 1"
|
|
|
+ type="primary"
|
|
|
+ style="margin-left: 8px"
|
|
|
+ :disabled="
|
|
|
+ !identityInfo.name ||
|
|
|
+ logoUrl === '/static/assets/client-base-v4/image/logo.png'
|
|
|
+ ? true
|
|
|
+ : false
|
|
|
+ "
|
|
|
+ @click="next"
|
|
|
+ >
|
|
|
+ 下一步
|
|
|
+ </a-button>
|
|
|
+ <a-button
|
|
|
+ v-if="current === 2"
|
|
|
+ type="primary"
|
|
|
+ style="margin-left: 8px"
|
|
|
+ :disabled="
|
|
|
+ !identitySetting.entityID ||
|
|
|
+ !identitySetting.ssoUrl ||
|
|
|
+ !identitySetting.certificate
|
|
|
+ ? true
|
|
|
+ : false
|
|
|
+ "
|
|
|
+ @click="next"
|
|
|
+ >
|
|
|
+ 下一步
|
|
|
+ </a-button>
|
|
|
+ <a-button
|
|
|
+ v-if="current == steps.length - 1"
|
|
|
+ type="primary"
|
|
|
+ style="margin-left: 8px"
|
|
|
+ @click="createIdentity"
|
|
|
+ >
|
|
|
+ 完成
|
|
|
+ </a-button>
|
|
|
+ </div>
|
|
|
+ </a-card>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup>
|
|
|
+import Common from '../common/Common';
|
|
|
+import { message } from 'ant-design-vue';
|
|
|
+import { ref, reactive, onMounted } from 'vue';
|
|
|
+import { useRoute, useRouter } from 'vue-router';
|
|
|
+import { getImageSrc } from '../common/image-src';
|
|
|
+import {
|
|
|
+ getBase64,
|
|
|
+ imageToBase64,
|
|
|
+ base64toFile,
|
|
|
+ saveUpdateAuth,
|
|
|
+ queryById,
|
|
|
+} from './configData.js';
|
|
|
+
|
|
|
+const route = useRoute();
|
|
|
+const router = useRouter();
|
|
|
+const current = ref(0); // 当前步骤
|
|
|
+const activeIndex = ref(0); // 所选认证源
|
|
|
+const logoUrl = ref('/static/assets/client-base-v4/image/logo.png'); // logo地址
|
|
|
+// 步骤一步骤二数据
|
|
|
+const identityInfo = reactive({
|
|
|
+ id: '',
|
|
|
+ name: '',
|
|
|
+ logo: [],
|
|
|
+ file: '',
|
|
|
+ active: true,
|
|
|
+ description: '',
|
|
|
+ authType: 'OAUTH',
|
|
|
+});
|
|
|
+// 步骤三步骤四数据
|
|
|
+const identitySetting = ref({
|
|
|
+ entityID: '',
|
|
|
+ ssoUrl: '',
|
|
|
+ certificate: '',
|
|
|
+ loginOutUrl: '',
|
|
|
+ uid: '',
|
|
|
+ userName: '',
|
|
|
+ userNo: '',
|
|
|
+ nickName: '',
|
|
|
+ email: '',
|
|
|
+ phoneNumber: '',
|
|
|
+ roleTemplateNo: '',
|
|
|
+});
|
|
|
+
|
|
|
+const logoName = ref('');
|
|
|
+const logoClassName = ref('');
|
|
|
+// 设置form样式
|
|
|
+const labelCol = ref({
|
|
|
+ style: {
|
|
|
+ width: '120px',
|
|
|
+ },
|
|
|
+});
|
|
|
+const wrapperCol = ref({
|
|
|
+ span: 8,
|
|
|
+});
|
|
|
+
|
|
|
+// 获取更新Id
|
|
|
+onMounted(() => {
|
|
|
+ const { identityId } = route.query;
|
|
|
+ if (identityId) {
|
|
|
+ identityInfo.id = identityId;
|
|
|
+ queryAuthById(identityId);
|
|
|
+ }
|
|
|
+});
|
|
|
+
|
|
|
+// 根据id查询详情(更新时数据回显)
|
|
|
+const queryAuthById = id => {
|
|
|
+ const params = new FormData();
|
|
|
+ params.append('id', id);
|
|
|
+ queryById(params).then(
|
|
|
+ success => {
|
|
|
+ if (success.errorCode === 0) {
|
|
|
+ const {
|
|
|
+ active,
|
|
|
+ authType,
|
|
|
+ attribute,
|
|
|
+ className,
|
|
|
+ description,
|
|
|
+ logo,
|
|
|
+ name,
|
|
|
+ } = success.data;
|
|
|
+ logoUrl.value = getImageSrc(className, logo);
|
|
|
+ imgToBase64();
|
|
|
+ logoName.value = logo;
|
|
|
+ identityInfo.name = name;
|
|
|
+ identityInfo.active = active;
|
|
|
+ logoClassName.value = className;
|
|
|
+ identityInfo.description = description;
|
|
|
+ const datas = JSON.parse(attribute);
|
|
|
+ identitySetting.value = datas;
|
|
|
+ if (authType === 'OAuth2.0') {
|
|
|
+ activeIndex.value = 0;
|
|
|
+ identityInfo.authType = 'OAUTH';
|
|
|
+ } else {
|
|
|
+ activeIndex.value = 1;
|
|
|
+ identityInfo.authType = 'SAML';
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ message.error(success.errorMessage);
|
|
|
+ }
|
|
|
+ },
|
|
|
+ error => {
|
|
|
+ Common.processException(error);
|
|
|
+ },
|
|
|
+ );
|
|
|
+};
|
|
|
+// 图片转file
|
|
|
+const imgToBase64 = () => {
|
|
|
+ var image = new Image();
|
|
|
+ image.crossOrigin = '';
|
|
|
+ image.src = logoUrl.value;
|
|
|
+ image.onload = function () {
|
|
|
+ let base64 = imageToBase64(image); //图片转base64
|
|
|
+ identityInfo.file = base64toFile(base64, logoName.value); //base64转File
|
|
|
+ };
|
|
|
+};
|
|
|
+// 新建或更新认证源
|
|
|
+const createIdentity = () => {
|
|
|
+ const jsonStr = JSON.stringify(identitySetting.value);
|
|
|
+ const formData = new FormData();
|
|
|
+ formData.append('attribute', jsonStr);
|
|
|
+ formData.append('id', identityInfo.id);
|
|
|
+ formData.append('name', identityInfo.name);
|
|
|
+ formData.append('logo', identityInfo.file);
|
|
|
+ formData.append('active', identityInfo.active);
|
|
|
+ formData.append('authType', identityInfo.authType);
|
|
|
+ formData.append('description', identityInfo.description);
|
|
|
+ saveUpdateAuth(formData).then(
|
|
|
+ success => {
|
|
|
+ if (success.errorCode === 0) {
|
|
|
+ if (!identityInfo.id) {
|
|
|
+ message.success('新建认证源成功!');
|
|
|
+ } else {
|
|
|
+ message.success('更新认证源成功!');
|
|
|
+ }
|
|
|
+ router.push('/desktop/identityManager');
|
|
|
+ } else {
|
|
|
+ message.error(success.errorMessage);
|
|
|
+ }
|
|
|
+ },
|
|
|
+ error => {
|
|
|
+ Common.processException(error);
|
|
|
+ },
|
|
|
+ );
|
|
|
+};
|
|
|
+
|
|
|
+// 获取logo文件
|
|
|
+const logoFileChange = async e => {
|
|
|
+ identityInfo.file = e.file;
|
|
|
+ logoUrl.value = await getBase64(e.fileList[0].originFileObj);
|
|
|
+};
|
|
|
+
|
|
|
+// 删除logo文件
|
|
|
+const deleteLogo = () => {
|
|
|
+ identityInfo.file = '';
|
|
|
+ identityInfo.logo.splice(0, 1);
|
|
|
+ logoUrl.value = '/static/assets/client-base-v4/image/logo.png';
|
|
|
+ message.warning('请上传logo!');
|
|
|
+};
|
|
|
+
|
|
|
+// 证书base64格式失去焦点后清除空格换行、回车
|
|
|
+const certificateBlur = e => {
|
|
|
+ identitySetting.value.certificate = e.target.value.replace(/[\s\n\r]+/g, '');
|
|
|
+};
|
|
|
+
|
|
|
+// 禁用antd自动上传
|
|
|
+const beforeUpload = () => {
|
|
|
+ return false;
|
|
|
+};
|
|
|
+
|
|
|
+// 步骤条配置
|
|
|
+const steps = ref([
|
|
|
+ {
|
|
|
+ title: '选择认证源协议类型',
|
|
|
+ contentTemplate: 'First',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '定义认证源名称及logo',
|
|
|
+ contentTemplate: 'Second',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '认证源基础配置',
|
|
|
+ contentTemplate: 'Third',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '账号关联',
|
|
|
+ contentTemplate: 'Last',
|
|
|
+ },
|
|
|
+]);
|
|
|
+
|
|
|
+// 选择认证源数据
|
|
|
+const selectedItem = ref([
|
|
|
+ {
|
|
|
+ title: 'OAuth 2.0',
|
|
|
+ imgSrc: '/static/assets/client-base-v4/image/oAuth.png',
|
|
|
+ tooltip: 'OAuth 2.0是行业标准的授权协议。',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: 'SAML',
|
|
|
+ imgSrc: '/static/assets/client-base-v4/image/saml.png',
|
|
|
+ tooltip: 'SAML是安全断言标记语言,是一个基于XML的开源标准数据格式。',
|
|
|
+ },
|
|
|
+]);
|
|
|
+
|
|
|
+// 下一步
|
|
|
+const next = () => {
|
|
|
+ current.value++;
|
|
|
+};
|
|
|
+// 上一步
|
|
|
+const prev = () => {
|
|
|
+ current.value--;
|
|
|
+};
|
|
|
+// 设置当前点击的认证源为活动项
|
|
|
+const setActiveItem = index => {
|
|
|
+ activeIndex.value = index;
|
|
|
+ if (index === 0) {
|
|
|
+ identityInfo.authType = 'OAUTH';
|
|
|
+ } else {
|
|
|
+ identityInfo.authType = 'SAML';
|
|
|
+ }
|
|
|
+};
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+.steps {
|
|
|
+ padding: 0;
|
|
|
+}
|
|
|
+.steps-content {
|
|
|
+ margin-top: 16px;
|
|
|
+ border-radius: 6px;
|
|
|
+ min-height: 100px;
|
|
|
+}
|
|
|
+.steps-action {
|
|
|
+ margin-top: 24px;
|
|
|
+}
|
|
|
+.labelStyle {
|
|
|
+ color: rgba(0, 0, 0, 0.4);
|
|
|
+ padding-bottom: 6px;
|
|
|
+ padding-right: 20px;
|
|
|
+ padding-top: 6px;
|
|
|
+ vertical-align: baseline;
|
|
|
+ width: 100px;
|
|
|
+}
|
|
|
+.selectUl {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: row;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ font-size: 12px;
|
|
|
+ width: 100%;
|
|
|
+ list-style: none;
|
|
|
+ padding: 0;
|
|
|
+}
|
|
|
+.selectUl li {
|
|
|
+ align-items: center;
|
|
|
+ border: 1px solid #dcdcdc;
|
|
|
+ box-sizing: border-box;
|
|
|
+ cursor: pointer;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: row;
|
|
|
+ height: 80px;
|
|
|
+ margin-bottom: 20px;
|
|
|
+ margin-right: 16px;
|
|
|
+ overflow: hidden;
|
|
|
+ padding: 20px;
|
|
|
+ width: 295px;
|
|
|
+}
|
|
|
+.selectUl li:hover {
|
|
|
+ border-color: #006eff;
|
|
|
+}
|
|
|
+.selectUl li.active {
|
|
|
+ border: 1px solid #006eff;
|
|
|
+}
|
|
|
+.box {
|
|
|
+ display: table;
|
|
|
+ font-size: 12px;
|
|
|
+ line-height: 1.5;
|
|
|
+}
|
|
|
+.picture {
|
|
|
+ height: 32px;
|
|
|
+ margin-right: 16px;
|
|
|
+ width: 32px;
|
|
|
+}
|
|
|
+.info {
|
|
|
+ flex: 1;
|
|
|
+ overflow: hidden;
|
|
|
+ text-align: left;
|
|
|
+}
|
|
|
+.text {
|
|
|
+ margin-top: 5px;
|
|
|
+ display: inline-block;
|
|
|
+ max-width: 100%;
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ vertical-align: middle;
|
|
|
+ white-space: nowrap;
|
|
|
+ color: rgba(0, 0, 0, 0.4) !important;
|
|
|
+}
|
|
|
+.ant-card-body {
|
|
|
+ padding: 20px !important;
|
|
|
+}
|
|
|
+:deep(.ant-form-item-control-input-content) {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+}
|
|
|
+.ant-upload-select-picture-card i {
|
|
|
+ font-size: 32px;
|
|
|
+ color: #999;
|
|
|
+}
|
|
|
+
|
|
|
+.ant-upload-select-picture-card .ant-upload-text {
|
|
|
+ margin-top: 8px;
|
|
|
+ color: #666;
|
|
|
+}
|
|
|
+:deep(.ant-form-item-label > label){
|
|
|
+ font-size: 14px !important;
|
|
|
+}
|
|
|
+</style>
|