Merge branch 'dev' of github.com:jetlinks/jetlinks-ui-vue into dev

This commit is contained in:
JiangQiming 2023-02-24 18:01:08 +08:00
commit 15be308dc8
6 changed files with 1479 additions and 505 deletions

View File

@ -7,3 +7,8 @@ export const getApplyList_api = (data: any) => server.post(`/application/_query/
export const changeApplyStatus_api = (id:string,data: any) => server.put(`/application/${id}`, data)
// 删除应用
export const delApply_api = (id:string) => server.remove(`/application/${id}`)
// 获取组织列表
export const getDepartmentList_api = () => server.get(`/organization/_all/tree`);

File diff suppressed because it is too large Load Diff

View File

@ -1,10 +1,10 @@
<template>
<div class="form-label-container">
<span class="text">{{ props.text }}</span>
<span class="required">*</span>
<span class="required" v-show="props.required">*</span>
<a-tooltip>
<template #title>{{ props.tooltip }}</template>
<AIcon type="QuestionCircleOutlined" style="color: #00000073;cursor: inherit;" />
<AIcon type="QuestionCircleOutlined" class="icon" />
</a-tooltip>
</div>
</template>
@ -24,11 +24,15 @@ const props = defineProps<{
.required {
display: inline-block;
margin-right: 4px;
color: #ff4d4f;
font-size: 14px;
font-family: SimSun, sans-serif;
line-height: 1;
}
.icon {
color: #00000073;
cursor: inherit;
margin-left: 4px;
}
}
</style>

View File

@ -0,0 +1,121 @@
<template>
<div class="request-table-container">
<a-table
:columns="columns"
:data-source="tableData"
:pagination="false"
size="small"
bordered
>
<template #bodyCell="{ column, record, index }">
<template v-if="column.dataIndex === 'key'">
<a-input v-model:value="record.label" />
</template>
<template v-else-if="column.dataIndex === 'value'">
<a-input v-model:value="record.value" />
</template>
<template v-else-if="column.dataIndex === 'action'">
<a-button
type="link"
@click="removeRow((current - 1) * 10 + index)"
>
<AIcon type="DeleteOutlined" />
</a-button>
</template>
</template>
</a-table>
<a-pagination
v-show="props.value.length > 10"
v-model:current="current"
:page-size="10"
:total="props.value.length"
show-less-items
/>
<a-button type="dashed" @click="addRow" class="add-btn">
<AIcon type="PlusOutlined" />新增
</a-button>
</div>
</template>
<script setup lang="ts">
import type { optionsType } from '../typing';
const emits = defineEmits(['update:value']);
const props = defineProps<{
value: optionsType;
}>();
const columns = [
{
title: 'KEY',
dataIndex: 'key',
},
{
title: 'VALUE',
dataIndex: 'value',
},
{
title: ' ',
dataIndex: 'action',
},
];
const current = ref<number>(1);
const tableData = computed(() => {
return props.value.slice((current.value - 1) * 10, current.value * 10);
});
watch(
() => props.value,
(n, o) => {
if (!o || n.length === o.length) return;
//
else if (n.length > o.length) {
//
if (o.length % 10 === 0 && n.length > 10)
current.value = current.value + 1;
} else {
//
//
if (o.length % 10 === 1 && o.length > 10)
current.value = current.value - 1;
}
},
{
immediate: true,
},
);
function removeRow(index: number) {
const left = props.value.slice(0, index++);
const right = props.value.slice(index, props.value.length);
emits('update:value', [...left, ...right]);
}
function addRow() {
const newRow = {
label: '',
value: '',
};
console.log(111);
emits('update:value', [...props.value, newRow]);
}
</script>
<style lang="less" scoped>
.request-table-container {
width: 100%;
:deep(.ant-btn-link) {
color: #000000d9;
&:hover {
color: #1d39c4;
}
}
.add-btn {
width: 100%;
display: block;
margin-top: 10px;
}
}
</style>

View File

@ -3,351 +3,9 @@
<a-card class="save-container">
<a-row :gutter="24">
<a-col :span="14">
<a-form
ref="formRef"
:model="form.data"
layout="vertical"
class="form"
>
<a-form-item label="名称" name="name">
<a-input
v-model:value="form.data.name"
placeholder="请输入名称"
/>
</a-form-item>
<a-form-item label="应用" name="provider">
<a-radio-group
v-model:value="form.data.provider"
class="radio-container"
@change="form.data.integrationModes = []"
>
<a-radio-button value="internal-standalone">
<div>
<a-image
:preview="false"
:src="
getImage('/apply/provider1.png')
"
width="64px"
height="64px"
/>
<p>内部独立应用</p>
</div>
</a-radio-button>
<a-radio-button value="internal-integrated">
<div>
<a-image
:preview="false"
:src="
getImage('/apply/provider2.png')
"
/>
<p>内部集成应用</p>
</div>
</a-radio-button>
<a-radio-button value="wechat-webapp">
<div>
<a-image
:preview="false"
:src="
getImage('/apply/provider3.png')
"
/>
<p>微信网站应用</p>
</div>
</a-radio-button>
<a-radio-button value="dingtalk-ent-app">
<div>
<a-image
:preview="false"
:src="
getImage('/apply/provider4.png')
"
/>
<p>钉钉企业内部应用</p>
</div>
</a-radio-button>
<a-radio-button value="third-party">
<div>
<a-image
:preview="false"
:src="
getImage('/apply/provider5.png')
"
/>
<p>第三方应用</p>
</div>
</a-radio-button>
</a-radio-group>
</a-form-item>
<a-form-item label="接入方式" name="integrationModes">
<a-checkbox-group
v-model:value="form.data.integrationModes"
:options="joinOptions"
/>
</a-form-item>
<a-collapse
v-model:activeKey="form.integrationModesISO"
>
<a-collapse-panel
key="page"
v-show="
form.data.integrationModes.includes('page')
"
header="页面集成"
>
<a-form-item
:name="['page', 'baseUrl']"
class="resetLabel"
:rules="[
{
required: true,
},
]"
>
<template #label>
<FormLabel
text="接入地址"
required
tooltip="填写访问其它平台的地址"
/>
</template>
<a-input
v-model:value="form.data.page.baseUrl"
placeholder="请输入接入地址"
/>
</a-form-item>
<a-form-item
label="路由方式"
:name="['page', 'routeType']"
:rules="[
{
required: true,
},
]"
>
<a-select
v-model:value="form.data.page.routeType"
style="width: 120px"
>
<a-select-option value="hash"
>hash</a-select-option
>
<a-select-option value="history"
>history</a-select-option
>
</a-select>
</a-form-item>
</a-collapse-panel>
<a-collapse-panel
key="apiClient"
v-show="
form.data.integrationModes.includes(
'apiClient',
)
"
header="API客户端"
>
<a-form-item
class="resetLabel"
:name="['apiClient', 'baseUrl']"
:rules="[
{
required: true,
},
]"
>
<template #label>
<FormLabel
text="接口地址"
required
tooltip="访问Api服务的地址"
/>
</template>
<a-input
v-model:value="
form.data.apiClient.baseUrl
"
placeholder="请输入接入地址"
/>
</a-form-item>
<a-form-item
class="resetLabel"
:name="[
'apiClient',
'authConfig',
'oauth2',
'authorizationUrl',
]"
:rules="[
{
required: true,
},
]"
>
<template #label>
<FormLabel
text="授权地址"
required
tooltip="认证授权地址"
/>
</template>
<a-input
v-model:value="
form.data.apiClient.authConfig
.oauth2.authorizationUrl
"
placeholder="请输入授权地址"
/>
</a-form-item>
<a-form-item
class="resetLabel"
:name="[
'apiClient',
'authConfig',
'oauth2',
'tokenUrl',
]"
:rules="[
{
required: true,
},
]"
>
<template #label>
<FormLabel
text="token地址"
required
tooltip="设置token令牌的地址"
/>
</template>
<a-input
v-model:value="
form.data.apiClient.authConfig
.oauth2.tokenUrl
"
placeholder="请输入token地址"
/>
</a-form-item>
<a-form-item
label="回调地址"
:name="[
'apiClient',
'authConfig',
'oauth2',
'redirectUri',
]"
>
<template #label>
<FormLabel
text="回调地址"
tooltip="授权完成后跳转到具体页面的回调地址"
/>
</template>
<a-input
v-model:value="
form.data.apiClient.authConfig
.oauth2.redirectUri
"
placeholder="请输入回调地址"
/>
</a-form-item>
<a-form-item
class="resetLabel"
:name="[
'apiClient',
'authConfig',
'oauth2',
'clientId',
]"
:rules="[
{
required: true,
},
]"
>
<template #label>
<FormLabel
text="appId"
required
tooltip="第三方应用唯一标识"
/>
</template>
<a-input
v-model:value="
form.data.apiClient.authConfig
.oauth2.clientId
"
placeholder="请输入appId"
/>
</a-form-item>
<a-form-item
class="resetLabel"
:name="[
'apiClient',
'authConfig',
'oauth2',
'clientSecret',
]"
:rules="[
{
required: true,
},
]"
>
<template #label>
<FormLabel
text="appKey"
required
tooltip="第三方应用唯一标识的密钥"
/>
</template>
<a-input
v-model:value="
form.data.apiClient.authConfig
.oauth2.clientSecret
"
placeholder="请输入appKey"
/>
</a-form-item>
<a-form-item label="请求头"> </a-form-item>
<a-form-item label="参数"> </a-form-item>
</a-collapse-panel>
<a-collapse-panel
key="apiServer"
v-show="
form.data.integrationModes.includes(
'apiServer',
)
"
header="API服务"
>
</a-collapse-panel>
<a-collapse-panel
key="ssoClient"
v-show="
form.data.integrationModes.includes(
'ssoClient',
)
"
header="单点登录"
>
</a-collapse-panel>
</a-collapse>
<a-form-item label="说明" name="description">
<a-textarea
v-model:value="form.data.description"
placeholder="请输入说明"
showCount
:maxlength="200"
:rows="5"
/>
</a-form-item>
</a-form>
<a-button v-if="!routeQuery.view">保存</a-button>
<EditForm @change-apply-type="chengeType" />
</a-col>
<a-col :span="10"><Does :type="form.data.provider" /></a-col>
<a-col :span="10"><Does :type="rightType" /></a-col>
</a-row>
</a-card>
</page-container>
@ -355,151 +13,11 @@
<script setup lang="ts">
import Does from './components/Does.vue';
import FormLabel from './components/FormLabel.vue';
import { getImage } from '@/utils/comm';
import type { applyType, formType } from './typing';
const routeQuery = useRoute().query;
import EditForm from './components/EditForm.vue';
import type { applyType } from './typing';
const initForm: formType = {
name: '',
provider: 'internal-standalone',
integrationModes: [],
config: '',
description: '',
page: {
baseUrl: '',
routeType: 'hash',
},
apiClient: {
baseUrl: '',
authConfig: {
type: '',
oauth2: {
authorizationUrl: '',
tokenUrl: '',
redirectUri: '',
clientId: '',
clientSecret: '',
},
},
},
const rightType = ref<applyType>('internal-standalone');
const chengeType = (newType: applyType) => {
rightType.value = newType;
};
const form = reactive({
data: { ...initForm },
integrationModesISO: [] as string[],
});
const joinOptions = computed(() => {
if (form.data.provider === 'internal-standalone')
return [
{
label: '页面集成',
value: 'page',
},
{
label: 'API客户端',
value: 'apiClient',
},
{
label: 'API服务',
value: 'apiServer',
},
{
label: '单点登录',
value: 'ssoClient',
},
];
else if (form.data.provider === 'internal-integrated')
return [
{
label: '页面集成',
value: 'page',
},
{
label: 'API客户端',
value: 'apiClient',
},
];
else if (form.data.provider === 'wechat-webapp')
return [
{
label: '单点登录',
value: 'ssoClient',
},
];
else if (form.data.provider === 'dingtalk-ent-app')
return [
{
label: '单点登录',
value: 'ssoClient',
},
];
else if (form.data.provider === 'third-party')
return [
{
label: '页面集成',
value: 'page',
},
{
label: 'API客户端',
value: 'apiClient',
},
{
label: 'API服务',
value: 'apiServer',
},
{
label: '单点登录',
value: 'ssoClient',
},
];
});
</script>
<style lang="less" scoped>
.save-container {
.form {
.ant-form-item {
&.resetLabel {
:deep(.ant-form-item-required) {
&::before {
display: none;
}
}
}
.ant-select {
width: 100% !important;
}
}
.radio-container {
.ant-radio-button-wrapper {
height: 120px;
width: 120px;
padding: 0 15px;
box-sizing: content-box;
margin-right: 20px;
> :last-child {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
> div {
width: 100%;
text-align: center;
}
:deep(.ant-image) {
width: 64px;
height: 64px;
}
p {
margin: 0;
}
}
}
}
}
}
</style>

View File

@ -3,28 +3,89 @@ export type applyType = 'internal-standalone'
| 'internal-integrated'
| 'dingtalk-ent-app'
| 'third-party'
export type dictType = {
id: string;
name: string;
children?: dictType
}[];
export type optionsType = {
label: string,
value: string;
disabled?: boolean
}[]
export type formType = {
name: string;
provider: applyType;
integrationModes: string[];
config: string;
description: string;
page: {
baseUrl:string,
routeType:'hash' | 'history'
},
apiClient: {
page: { // 页面集成
baseUrl: string,
authConfig: {
type:string,
oauth2 :{
authorizationUrl:string,
tokenUrl: string,
redirectUri:string,
clientId:string,
clientSecret:string
routeType: 'hash' | 'history',
parameters: optionsType
},
apiClient: { // API客户端
baseUrl: string, // 接口地址
headers: optionsType, // 请求头
parameters: optionsType, // 请求参数
authConfig: { // 认证配置
type: 'none' | 'bearer' | 'oauth2' | 'basic' | 'other', // 类型, 可选值none, bearer, oauth2, basic, other
bearer: { token: string }, // 授权信息
basic: { username: string, password: string }, // 基本信息
oauth2: { // OAuth2信息
authorizationUrl: string, // 授权地址
tokenUrl: string, // token地址
redirectUri: string, // 重定向地址
clientId: string, // 客户端ID
clientSecret: string, // 客户端密钥
grantType: 'authorization_code' | 'client_credentials' | '', // 类型
accessTokenProperty: string, // token属性名
tokenRequestType: 'POST_URI' | 'POST_BODY' | '' // token请求方式, 可选值POST_URIPOST_BODY
}
}
},
apiServer: { // API服务
appId: string,
secureKey: string, // 密钥
redirectUri: string, // 重定向URL
roleIdList: string[], // 角色列表
orgIdList: string[], // 部门列表
ipWhiteList: string, // IP白名单
signature: 'MD5' | 'SHA256' | '', // 签名方式, 可选值MD5SHA256
enableOAuth2: boolean, // 是否启用OAuth2
},
sso: { // 统一单点登陆集成
configuration: { // 配置
oauth2: { // Oauth2单点登录配置
authorizationUrl: string, // 授权地址
redirectUri: string, // 重定向地址
clientId: string, // 客户端ID
clientSecret: string, // 客户端密钥
userInfoUrl: string, // 用户信息接口
scope: string, // scope
userProperty: { // 用户属性字段信息
userId: string, // 用户ID
username: string, // 用户名
name: string, // 名称
avatar: string, // 头像
email: string, // 邮箱
telephone: string, // 电话
description: string, // 说明
},
grantType: 'authorization_code' | 'client_credentials' | '', // 类型
tokenUrl: string, // token地址
accessTokenProperty: string, // token属性名
tokenRequestType: 'POST_URI' | 'POST_BODY' | '', // token请求方式
},
appId: string, // 微信单点登录配置
appKey: string, // 钉钉单点登录配置
appSecret: string, // 钉钉、微信单点登录配置
},
autoCreateUser: boolean, // 是否自动创建平台用户
usernamePrefix: string, // 用户ID前缀
roleIdList: string[], // 自动创建平台用户时角色列表
orgIdList: string[], // 自动创建平台用户时部门列表
defaultPasswd: string, // 默认密码
}
}