feat: 应用管理修改

This commit is contained in:
100011797 2023-06-09 15:47:49 +08:00
parent b0a9a13491
commit 1dd119144a
8 changed files with 453 additions and 140 deletions

View File

@ -1,6 +1,6 @@
<template>
<div class="upload-image-warp">
<div class="upload-image-border">
<div class="upload-image-border" :style="borderStyle">
<j-upload
name="file"
list-type="picture-card"
@ -16,7 +16,7 @@
>
<div class="upload-image-content" :style="props.style">
<template v-if="imageUrl">
<img :src="imageUrl" class="upload-image" />
<img :src="imageUrl" width="100%" class="upload-image" />
<div class="upload-image-mask">点击修改</div>
</template>
<template v-else>
@ -61,7 +61,7 @@ import { FILE_UPLOAD } from '@/api/comm';
import { TOKEN_KEY } from '@/utils/variable';
import {getBase64, LocalStore} from '@/utils/comm';
import { CSSProperties } from 'vue';
import ImageCropper from './Cropper.vue'
import ImageCropper from './Cropper.vue';
type Emits = {
(e: 'update:modelValue', data: string): void;
@ -74,6 +74,7 @@ interface JUploadProps extends UploadProps {
size?: number;
style?: CSSProperties;
bgImage?: string;
borderStyle?:CSSProperties;
}
const emit = defineEmits<Emits>();
@ -94,6 +95,10 @@ const props: JUploadProps = defineProps({
accept:{
type: String,
default: undefined
},
borderStyle: {
type: Object,
default: undefined
}
});

View File

@ -0,0 +1,246 @@
<template>
<div class="upload-image-warp" @click.stop>
<div class="upload-image-border" :style="borderStyle" @click.stop>
<j-upload
name="file"
list-type="picture-card"
class="avatar-uploader"
:show-upload-list="false"
:before-upload="beforeUpload"
:action="FILE_UPLOAD"
:headers="{
'X-Access-Token': LocalStore.get(TOKEN_KEY),
}"
v-bind="props"
>
<div class="upload-image-content" :style="props.style">
<template v-if="imageUrl">
<img :src="imageUrl" width="100%" class="upload-image" />
<div v-if="imageUrl === defaultValue" class="upload-image-mask">替换图标</div>
<div v-else @click.stop="onDelete" class="upload-image-mask">删除图标</div>
</template>
<template v-else>
<AIcon
type="LoadingOutlined"
v-if="loading"
style="font-size: 20px"
/>
</template>
</div>
</j-upload>
<div class="upload-loading-mask" v-if="props.disabled"></div>
<div class="upload-loading-mask" v-if="imageUrl && loading">
<AIcon type="LoadingOutlined" style="font-size: 20px" />
</div>
</div>
</div>
<ImageCropper
v-if="cropperVisible"
:img="cropperImg"
@cancel="cropperVisible = false"
@ok="saveImage"
/>
</template>
<script lang="ts" setup name='JProUpload'>
import { UploadProps } from 'ant-design-vue';
import { message } from 'jetlinks-ui-components';
import { FILE_UPLOAD } from '@/api/comm';
import { TOKEN_KEY } from '@/utils/variable';
import {getBase64, LocalStore} from '@/utils/comm';
import { CSSProperties } from 'vue';
import ImageCropper from '@/components/Upload/Cropper.vue';
type Emits = {
(e: 'update:modelValue', data: string): void;
(e: 'change', data: string): void;
};
interface JUploadProps extends UploadProps {
modelValue: string;
disabled?: boolean;
types?: string[];
errorMessage?: string;
size?: number;
style?: CSSProperties;
// bgImage?: string;
borderStyle?:CSSProperties;
defaultValue?: string;
}
const emit = defineEmits<Emits>();
const props: JUploadProps = defineProps({
modelValue: {
type: String,
default: '',
},
disabled: {
type: Boolean,
default: false,
},
// bgImage: {
// type: String,
// default: '',
// },
accept:{
type: String,
default: undefined
},
borderStyle: {
type: Object,
default: undefined
},
defaultValue: {
type: String,
default: '',
}
});
const loading = ref<boolean>(false);
const imageUrl = ref<string>(props?.modelValue || '');
const imageTypes = props.types ? props.types : ['image/jpeg', 'image/png'];
const cropperImg = ref()
const cropperVisible = ref(false)
watch(
() => props.modelValue,
(newValue) => {
imageUrl.value = newValue;
},
{
deep: true,
immediate: true,
},
);
const onDelete = () => {
emit('update:modelValue', props.defaultValue || '');
emit('change', props.defaultValue || '');
}
const beforeUpload = (file: UploadProps['fileList'][number]) => {
const isType = imageTypes.includes(file.type);
const maxSize = props.size || 2 //
if (!isType) {
if (props.errorMessage) {
message.error(props.errorMessage);
} else {
message.error(`请上传正确格式的图片`);
}
return false;
}
const isSize = file.size / 1024 / 1024 < maxSize;
if (!isSize) {
message.error(`图片大小必须小于${maxSize}M`);
}
getBase64(file, (base64Url) => {
cropperImg.value = base64Url
cropperVisible.value = true
})
return false;
};
const saveImage = (url: string) => {
cropperVisible.value = false
imageUrl.value = url
emit('update:modelValue', url);
emit('change', url);
}
</script>
<style lang="less" scoped>
@border: 1px dashed @border-color-base;
@mask-color: rgba(#000, 0.35);
@with: 150px;
@height: 150px;
.flex-center() {
align-items: center;
justify-content: center;
}
.upload-image-warp {
display: flex;
justify-content: flex-start;
.upload-image-border {
position: relative;
width: @with;
height: @height;
overflow: hidden;
//border-radius: 50%;
// border: @border;
transition: all 0.3s;
&:hover {
border-color: @primary-color-hover;
}
:deep(.ant-upload-picture-card-wrapper) {
width: 100%;
height: 100%;
}
:deep(.ant-upload) {
width: 100%;
height: 100%;
}
.upload-image-content {
.flex-center();
position: relative;
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
background-color: rgba(#000, 0.06);
cursor: pointer;
padding: 8px;
.upload-image-mask {
.flex-center();
position: absolute;
top: 0;
left: 0;
display: none;
width: 100%;
height: 100%;
color: #fff;
font-size: 16px;
background-color: @mask-color;
}
.upload-image {
width: 100%;
height: 100%;
//border-radius: 50%;
background-repeat: no-repeat;
background-position: center;
background-size: cover;
}
&:hover .upload-image-mask {
display: flex;
}
}
}
.upload-loading-mask {
.flex-center();
position: absolute;
top: 0;
left: 0;
display: flex;
width: 100%;
height: 100%;
color: #fff;
background-color: @mask-color;
}
}
</style>

View File

@ -0,0 +1,141 @@
<template>
<j-radio-group
:value="_value"
class="radio-container"
:disabled="disabled"
@change="onChange"
>
<j-radio-button
v-for="item in list"
:value="item.value"
:key="item.value"
>
<div class="radio-container-item" @click.stop>
<div>
<MUpload
:defaultValue="item.imgUrl"
:borderStyle="{
width: '90px',
height: '90px',
border: 'none',
}"
:disabled="!disabled ? false : (item.value === _value ? false : true)"
accept="image/jpeg,image/png"
:modelValue="urlValue[item.value]"
@change="(_url) => onImgChange(_url, item.value)"
/>
</div>
<span>{{ item.text }}</span>
</div>
</j-radio-button>
</j-radio-group>
</template>
<script lang="ts" setup>
import { getImage } from '@/utils/comm';
import MUpload from './MUpload.vue';
const props = defineProps({
disabled: {
type: Boolean,
default: false,
},
value: {
type: String,
default: 'internal-standalone',
},
photoUrl: {
type: String,
default: getImage('/apply/provider1.png'),
},
});
const emit = defineEmits(['update:value', 'update:photoUrl']);
const list = [
{
value: 'internal-standalone',
text: '内部独立应用',
imgUrl: getImage('/apply/provider1.png'),
},
{
value: 'internal-integrated',
text: '内部集成应用',
imgUrl: getImage('/apply/provider2.png'),
},
{
value: 'wechat-webapp',
text: '微信网站应用',
imgUrl: getImage('/apply/provider4.png'),
},
{
value: 'dingtalk-ent-app',
text: '钉钉企业内部应用',
imgUrl: getImage('/apply/provider3.png'),
},
{
value: 'third-party',
text: '第三方应用',
imgUrl: getImage('/apply/provider5.png'),
},
{
value: 'wechat-miniapp',
text: '小程序应用',
imgUrl: getImage('/apply/provider1.png'),
},
];
const urlValue = ref({
'internal-standalone': getImage('/apply/provider1.png'),
'internal-integrated': getImage('/apply/provider2.png'),
'wechat-webapp': getImage('/apply/provider4.png'),
'dingtalk-ent-app': getImage('/apply/provider3.png'),
'third-party': getImage('/apply/provider5.png'),
'wechat-miniapp': getImage('/apply/provider1.png'),
});
const _value = ref<string>('');
watchEffect(() => {
_value.value = props.value;
});
watch(
() => props.photoUrl,
(newValue) => {
urlValue.value[props.value] = newValue
},
{
deep: true,
immediate: true,
},
);
const onChange = (e: any) => {
emit('update:value', e.target.value);
};
const onImgChange = (url: string, _key: string) => {
if (_key === _value.value) {
emit('update:photoUrl', url);
} else {
urlValue.value[_key] = url;
}
};
</script>
<style lang="less" scoped>
.radio-container {
.radio-container-item {
display: flex;
flex-direction: column;
align-items: center;
}
.ant-radio-button-wrapper {
height: auto;
width: 120px;
padding: 12px 6px;
box-sizing: content-box;
margin: 20px 20px 0 0;
color: #000;
}
}
</style>

View File

@ -156,6 +156,9 @@
<div>4单点登录</div>
<div>通过<span>第三方平台账号</span>登录到物联网平台</div>
</div>
<div v-show="props.type === 'wechat-miniapp'">
暂未开发
</div>
</div>
</template>

View File

@ -36,59 +36,7 @@
},
]"
>
<j-radio-group
v-model:value="form.data.provider"
class="radio-container"
:disabled="!!routeQuery.id"
>
<j-radio-button value="internal-standalone">
<div>
<j-image
:preview="false"
:src="getImage('/apply/provider1.png')"
width="64px"
height="64px"
/>
<p>内部独立应用</p>
</div>
</j-radio-button>
<j-radio-button value="internal-integrated">
<div>
<j-image
:preview="false"
:src="getImage('/apply/provider2.png')"
/>
<p>内部集成应用</p>
</div>
</j-radio-button>
<j-radio-button value="wechat-webapp">
<div>
<j-image
:preview="false"
:src="getImage('/apply/provider4.png')"
/>
<p>微信网站应用</p>
</div>
</j-radio-button>
<j-radio-button value="dingtalk-ent-app">
<div>
<j-image
:preview="false"
:src="getImage('/apply/provider3.png')"
/>
<p>钉钉企业内部应用</p>
</div>
</j-radio-button>
<j-radio-button value="third-party">
<div>
<j-image
:preview="false"
:src="getImage('/apply/provider5.png')"
/>
<p>第三方应用</p>
</div>
</j-radio-button>
</j-radio-group>
<ApplyList v-model:photoUrl="form.data.logoUrl" v-model:value="form.data.provider" :disabled="!!routeQuery.id" />
</j-form-item>
<j-form-item
label="接入方式"
@ -106,7 +54,7 @@
/>
</j-form-item>
<j-collapse v-model:activeKey="form.integrationModesISO">
<j-collapse>
<!-- 页面集成 -->
<j-collapse-panel
key="page"
@ -1049,7 +997,7 @@
placeholder="请输入token地址"
/>
</j-form-item>
<j-form-item label="logo">
<!-- <j-form-item label="logo">
<j-upload
v-model:file-list="form.fileList"
accept=".jpg,.png"
@ -1086,7 +1034,7 @@
</div>
</div>
</j-upload>
</j-form-item>
</j-form-item> -->
<j-form-item
label="用户信息地址"
@ -1231,7 +1179,7 @@
</j-form-item>
<!-- 非微信 -->
<j-form-item
v-if="form.data.provider !== 'wechat-webapp'"
v-if="form.data.provider !== 'wechat-webapp' && form.data.provider !== 'wechat-miniapp'"
class="resetLabel"
:name="['sso', 'configuration', 'appKey']"
:rules="[
@ -1265,6 +1213,7 @@
v-if="
form.data.provider === 'wechat-webapp' ||
form.data.provider === 'dingtalk-ent-app'
|| 'wechat-miniapp'
"
class="resetLabel"
:name="['sso', 'configuration', 'appSecret']"
@ -1466,6 +1415,7 @@ import { randomString } from '@/utils/utils';
import { cloneDeep, difference } from 'lodash';
import { useMenuStore } from '@/store/menu';
import { Rule } from 'ant-design-vue/lib/form';
import ApplyList from './ApplyList/index.vue';
const emit = defineEmits(['changeApplyType']);
const routeQuery = useRoute().query;
@ -1478,6 +1428,7 @@ const rolePermission = 'system/Role';
const initForm: formType = {
name: '',
provider: 'internal-standalone',
logoUrl: getImage('/apply/provider1.png'),
integrationModes: [],
description: '',
page: {
@ -1561,7 +1512,7 @@ const initForm: formType = {
const formRef = ref<FormInstance>();
const form = reactive({
data: { ...initForm },
integrationModesISO: [] as string[], // 使
// integrationModesISO: [] as string[], // 使
roleIdList: [] as optionsType, //
orgIdList: [] as dictType, //
@ -1622,7 +1573,7 @@ const joinOptions = computed(() => {
value: 'apiClient',
},
];
else if (form.data.provider === 'wechat-webapp')
else if (form.data.provider === 'wechat-webapp' || form.data.provider === 'wechat-miniapp')
return [
{
label: '单点登录',
@ -1697,9 +1648,9 @@ function init() {
}
emit('changeApplyType', n);
if (routeQuery.id) return;
if (n === 'wechat-webapp' || n === 'dingtalk-ent-app') {
if (['wechat-webapp', 'dingtalk-ent-app', 'wechat-miniapp'].includes(n)) {
form.data.integrationModes = ['ssoClient'];
form.integrationModesISO = ['ssoClient'];
// form.integrationModesISO = ['ssoClient'];
} else form.data.integrationModes = [];
},
{ immediate: true },
@ -1711,7 +1662,7 @@ function init() {
if (!n.includes(key)) form.errorNumInfo[key].clear();
});
form.integrationModesISO = [...n];
// form.integrationModesISO = [...n];
},
);
}
@ -1888,31 +1839,31 @@ function getErrorNum(
}
}
const imageTypes = ref(['image/jpg', 'image/png', 'image/jpeg']);
const beforeLogoUpload = (file: any) => {
const isType: any = imageTypes.value.includes(file.type);
if (!isType) {
message.error(`请上传.jpg.png格式的图片`);
return false;
}
const isSize = file.size / 1024 / 1024 < 4;
if (!isSize) {
message.error(`图片大小必须小于${4}M`);
}
return isType && isSize;
};
function changeBackUpload(info: UploadChangeParam<UploadFile<any>>) {
if (info.file.status === 'uploading') {
form.uploadLoading = true;
} else if (info.file.status === 'done') {
info.file.url = info.file.response?.result;
form.uploadLoading = false;
form.data.logoUrl = info.file.response?.result;
} else if (info.file.status === 'error') {
form.uploadLoading = false;
message.error('logo上传失败请稍后再试');
}
}
// const imageTypes = ref(['image/jpg', 'image/png', 'image/jpeg']);
// const beforeLogoUpload = (file: any) => {
// const isType: any = imageTypes.value.includes(file.type);
// if (!isType) {
// message.error(`.jpg.png`);
// return false;
// }
// const isSize = file.size / 1024 / 1024 < 4;
// if (!isSize) {
// message.error(`${4}M`);
// }
// return isType && isSize;
// };
// function changeBackUpload(info: UploadChangeParam<UploadFile<any>>) {
// if (info.file.status === 'uploading') {
// form.uploadLoading = true;
// } else if (info.file.status === 'done') {
// info.file.url = info.file.response?.result;
// form.uploadLoading = false;
// form.data.logoUrl = info.file.response?.result;
// } else if (info.file.status === 'error') {
// form.uploadLoading = false;
// message.error('logo');
// }
// }
function clearNullProp(obj: object) {
if (typeof obj !== 'object') return;
@ -1962,59 +1913,20 @@ const validateIP = (_rule: Rule, value: string) => {
:deep(.ant-form-item-control) {
.ant-form-item-control-input-content {
display: flex;
.ant-upload-select-picture-card {
width: auto;
height: auto;
max-width: 150px;
max-height: 150px;
// .ant-upload-select-picture-card {
// width: auto;
// height: auto;
// max-width: 150px;
// max-height: 150px;
> .ant-upload {
height: 150px;
}
}
// > .ant-upload {
// height: 150px;
// }
// }
}
}
}
.radio-container {
.ant-radio-button-wrapper {
height: 120px;
width: 120px;
padding: 0 15px;
box-sizing: content-box;
margin-right: 20px;
color: #000;
&.ant-radio-button-wrapper-disabled {
opacity: 0.5;
}
&.ant-radio-button-wrapper-checked {
background-color: #fff;
border: 1px solid #1d39c4;
opacity: 1;
}
> :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;
}
}
}
}
:deep(.ant-collapse-header) {
> span {
position: relative;

View File

@ -3,6 +3,7 @@ export type applyType = 'internal-standalone'
| 'internal-integrated'
| 'dingtalk-ent-app'
| 'third-party'
| 'wechat-miniapp'
export type dictType = {
id: string;
name: string;
@ -20,6 +21,7 @@ export type formType = {
id?:string,
name: string;
provider: applyType;
logoUrl: string,
integrationModes: string[];
config?: string;
description: string;

View File

@ -226,6 +226,10 @@ const typeOptions = [
label: '第三方应用',
value: 'third-party',
},
{
label: '小程序应用',
value: 'wechat-miniapp',
},
];
const columns = [
{

View File

@ -3825,8 +3825,8 @@ jetlinks-store@^0.0.3:
jetlinks-ui-components@^1.0.22:
version "1.0.22"
resolved "https://registry.jetlinks.cn/jetlinks-ui-components/-/jetlinks-ui-components-1.0.22.tgz#37e49fe435d8eccd82fc4d8e94980a98aaac1922"
integrity sha512-iI7g2ui4pSuunkyPBWkscSZeUJfJURsj5Tmbfbc04wOhQh3GA6N3gKEDIvavFWHhFk1y/6EITyOYIZT4Wf3rdg==
resolved "https://registry.jetlinks.cn/jetlinks-ui-components/-/jetlinks-ui-components-1.0.22.tgz#1b7c3d632b5c1b55a4ce59fb010c48edb402c657"
integrity sha512-U19VTSmxCgz1ybNY3QhPRaTM9CVvq1nYJOMc+Y8rc2FnzQ9B7k1Xk/fmJZvdt7lqlcDL3xO0zXkIVdAdapG3GA==
dependencies:
"@vueuse/core" "^9.12.0"
"@vueuse/router" "^9.13.0"