feat: 订阅管理+个人中心
This commit is contained in:
parent
bb62983d6e
commit
4aa8a2dbe6
|
@ -0,0 +1,4 @@
|
|||
import server from '@/utils/request';
|
||||
|
||||
// 获取角色列表
|
||||
export const queryRoleList = (data: any): Promise<any> => server.post(`/role/_query/`, data);
|
|
@ -0,0 +1,87 @@
|
|||
<template>
|
||||
<div class="box">
|
||||
<div class="box-item" v-if="data.length > showLength">
|
||||
<j-button
|
||||
@click="onLeft"
|
||||
type="primary"
|
||||
:disabled="!(pageIndex > 0)"
|
||||
shape="circle"
|
||||
class="box-item-action"
|
||||
><AIcon type="LeftOutlined"
|
||||
/></j-button>
|
||||
</div>
|
||||
<div class="box-item" v-for="item in getData" :key="item.id">
|
||||
<slot name="card" v-bind="item"></slot>
|
||||
</div>
|
||||
<div class="box-item">
|
||||
<slot name="add"></slot>
|
||||
</div>
|
||||
<div class="box-item" v-if="data.length > showLength">
|
||||
<j-button
|
||||
:disabled="!(pageIndex + showLength < data.length)"
|
||||
type="primary"
|
||||
shape="circle"
|
||||
class="box-item-action"
|
||||
@click="onRight"
|
||||
><AIcon type="RightOutlined"
|
||||
/></j-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { PropType } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Array as PropType<any[]>,
|
||||
default: () => [],
|
||||
},
|
||||
showLength: {
|
||||
type: Number,
|
||||
default: 8,
|
||||
},
|
||||
});
|
||||
|
||||
// const emit = defineEmits(['add']);
|
||||
|
||||
const pageIndex = ref<number>(0);
|
||||
|
||||
const getData = computed(() => {
|
||||
const start = pageIndex.value >= 0 ? pageIndex.value : 0;
|
||||
const end =
|
||||
props.showLength + pageIndex.value < props.data.length
|
||||
? props.showLength + pageIndex.value
|
||||
: props.data.length;
|
||||
return props.data.slice(start, end);
|
||||
});
|
||||
|
||||
const onRight = () => {
|
||||
const flag = pageIndex.value + 1;
|
||||
if (flag < props.data.length) {
|
||||
pageIndex.value = flag;
|
||||
}
|
||||
};
|
||||
|
||||
const onLeft = () => {
|
||||
const flag = pageIndex.value - 1;
|
||||
if (flag >= 0) {
|
||||
pageIndex.value -= 1;
|
||||
}
|
||||
};
|
||||
|
||||
// const onAdd = () => {
|
||||
// emit('add');
|
||||
// };
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 5px;
|
||||
.box-item {
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -23,34 +23,34 @@ export const AccountMenu = {
|
|||
name: 'account/center',
|
||||
code: 'account/center',
|
||||
meta: {
|
||||
title: '基本设置',
|
||||
title: '个人中心',
|
||||
icon: '',
|
||||
hideInMenu: false
|
||||
hideInMenu: true
|
||||
},
|
||||
component: () => import('@/views/account/Center/index.vue')
|
||||
},
|
||||
{
|
||||
path: '/account/NotificationSubscription',
|
||||
name: 'account/NotificationSubscription',
|
||||
code: 'account/NotificationSubscription',
|
||||
meta: {
|
||||
title: '通知订阅',
|
||||
icon: '',
|
||||
hideInMenu: false
|
||||
},
|
||||
component: () => import('@/views/account/NotificationSubscription/index.vue')
|
||||
},
|
||||
{
|
||||
path: '/account/NotificationRecord',
|
||||
name: 'account/NotificationRecord',
|
||||
code: 'account/NotificationRecord',
|
||||
meta: {
|
||||
title: '通知记录',
|
||||
icon: '',
|
||||
hideInMenu: false
|
||||
},
|
||||
component: () => import('@/views/account/NotificationRecord/index.vue')
|
||||
},
|
||||
// {
|
||||
// path: '/account/NotificationSubscription',
|
||||
// name: 'account/NotificationSubscription',
|
||||
// code: 'account/NotificationSubscription',
|
||||
// meta: {
|
||||
// title: '通知订阅',
|
||||
// icon: '',
|
||||
// hideInMenu: false
|
||||
// },
|
||||
// component: () => import('@/views/account/NotificationSubscription/index.vue')
|
||||
// },
|
||||
// {
|
||||
// path: '/account/NotificationRecord',
|
||||
// name: 'account/NotificationRecord',
|
||||
// code: 'account/NotificationRecord',
|
||||
// meta: {
|
||||
// title: '通知记录',
|
||||
// icon: '',
|
||||
// hideInMenu: false
|
||||
// },
|
||||
// component: () => import('@/views/account/NotificationRecord/index.vue')
|
||||
// },
|
||||
]
|
||||
}
|
||||
|
||||
|
@ -78,6 +78,6 @@ export default [
|
|||
title: '授权页'
|
||||
},
|
||||
component: () => import('@/views/oauth/index.vue')
|
||||
}
|
||||
|
||||
},
|
||||
AccountMenu
|
||||
]
|
|
@ -5,7 +5,7 @@ import { cloneDeep, isArray } from 'lodash-es'
|
|||
import { usePermissionStore } from './permission'
|
||||
import router from '@/router'
|
||||
import { onlyMessage } from '@/utils/comm'
|
||||
import { AccountMenu, NotificationRecordCode, NotificationSubscriptionCode } from '@/router/menu'
|
||||
// import { AccountMenu, NotificationRecordCode, NotificationSubscriptionCode } from '@/router/menu'
|
||||
import { MESSAGE_SUBSCRIBE_MENU_CODE, USER_CENTER_MENU_CODE } from '@/utils/consts'
|
||||
import {isNoCommunity} from "@/utils/utils";
|
||||
|
||||
|
@ -104,11 +104,11 @@ export const useMenuStore = defineStore({
|
|||
const { menusData, silderMenus } = filterAsyncRouter(resultData)
|
||||
|
||||
// 是否存在通知订阅
|
||||
const hasMessageSub = resultData.some((item: { code: string }) => item.code === MESSAGE_SUBSCRIBE_MENU_CODE)
|
||||
if (!hasMessageSub) {
|
||||
AccountMenu.children = AccountMenu.children.filter((item: { code: string }) => ![NotificationSubscriptionCode, NotificationRecordCode].includes(item.code) )
|
||||
}
|
||||
this.menus = findCodeRoute([...resultData, AccountMenu])
|
||||
// const hasMessageSub = resultData.some((item: { code: string }) => item.code === MESSAGE_SUBSCRIBE_MENU_CODE)
|
||||
// if (!hasMessageSub) {
|
||||
// AccountMenu.children = AccountMenu.children.filter((item: { code: string }) => ![NotificationSubscriptionCode, NotificationRecordCode].includes(item.code) )
|
||||
// }
|
||||
this.menus = findCodeRoute([...resultData]) // AccountMenu
|
||||
Object.keys(this.menus).forEach((item) => {
|
||||
const _item = this.menus[item]
|
||||
if (_item.buttons?.length) {
|
||||
|
@ -123,7 +123,7 @@ export const useMenuStore = defineStore({
|
|||
hideInMenu: true
|
||||
}
|
||||
})
|
||||
menusData.push(AccountMenu)
|
||||
// menusData.push(AccountMenu)
|
||||
this.siderMenus = silderMenus.filter((item: { name: string }) => ![USER_CENTER_MENU_CODE, MESSAGE_SUBSCRIBE_MENU_CODE].includes(item.name))
|
||||
res(menusData)
|
||||
}
|
||||
|
|
|
@ -19,6 +19,11 @@ export const useUserInfo = defineStore('userInfo', {
|
|||
roles: [],
|
||||
token: '',
|
||||
user: {},
|
||||
name: '',
|
||||
orgList: [],
|
||||
roleList: [],
|
||||
telephone: '',
|
||||
email: ''
|
||||
},
|
||||
alarmUpdateCount: 0
|
||||
}),
|
||||
|
|
|
@ -0,0 +1,114 @@
|
|||
<template>
|
||||
<div class="box">
|
||||
<div class="content">
|
||||
<div class="content-item" v-for="item in bindList" :key="item.id">
|
||||
<div class="content-item-left">
|
||||
<img
|
||||
:src="item.logoUrl || getImage(bindIcon[item.provider])"
|
||||
style="height: 50px; width: 50px"
|
||||
width="50px"
|
||||
height="50px"
|
||||
alt=""
|
||||
/>
|
||||
<Ellipsis style="max-width: 200px; font-size: 22px">{{
|
||||
item?.name
|
||||
}}</Ellipsis>
|
||||
<div>
|
||||
<j-tag v-if="item.bound">已绑定</j-tag>
|
||||
<j-tag v-else>未绑定</j-tag>
|
||||
</div>
|
||||
<div v-if="item.others?.name">
|
||||
绑定名:{{ item.others?.name }}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<j-popconfirm
|
||||
v-if="item.bound"
|
||||
title="确认解除绑定嘛?"
|
||||
@confirm="() => unBind(item.id)"
|
||||
>
|
||||
<j-button>解除绑定</j-button>
|
||||
</j-popconfirm>
|
||||
<j-button v-else type="primary" @click="clickBind(item.id)"
|
||||
>立即绑定</j-button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { BASE_API_PATH } from '@/utils/variable';
|
||||
import { getSsoBinds_api } from '@/api/account/center';
|
||||
import { unBind_api } from '@/api/account/center';
|
||||
import { onlyMessage, getImage } from '@/utils/comm';
|
||||
|
||||
const bindList = ref<any[]>([]);
|
||||
const bindIcon = {
|
||||
'dingtalk-ent-app': '/notice/dingtalk.png',
|
||||
'wechat-webapp': '/notice/wechat.png',
|
||||
'internal-standalone': '/apply/provider1.png',
|
||||
'third-party': '/apply/provider5.png',
|
||||
};
|
||||
const unBind = (id: string) => {
|
||||
unBind_api(id).then((resp) => {
|
||||
if (resp.status === 200) {
|
||||
onlyMessage('解绑成功', 'success');
|
||||
getSsoBinds();
|
||||
}
|
||||
});
|
||||
};
|
||||
const clickBind = (id: string) => {
|
||||
window.open(
|
||||
`${BASE_API_PATH}/application/sso/${id}/login?autoCreateUser=false`,
|
||||
);
|
||||
localStorage.setItem('onBind', 'false');
|
||||
localStorage.setItem('onLogin', 'yes');
|
||||
window.onstorage = (e) => {
|
||||
if (e.newValue) {
|
||||
getSsoBinds();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取绑定第三方账号
|
||||
*/
|
||||
function getSsoBinds() {
|
||||
getSsoBinds_api().then((resp: any) => {
|
||||
if (resp.status === 200) bindList.value = resp.result;
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getSsoBinds();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.box {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
.content {
|
||||
margin-top: 24px;
|
||||
width: 80%;
|
||||
|
||||
.content-item {
|
||||
width: 100%;
|
||||
margin: 10px 0;
|
||||
padding: 15px;
|
||||
border: 1px solid #f0f0f0;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.content-item-left {
|
||||
display: flex;
|
||||
gap: 24px;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,33 @@
|
|||
<template>
|
||||
<j-modal visible title="查看详情" @cancel="emit('close')">
|
||||
<j-descriptions :column="1">
|
||||
<j-descriptions-item label="用户名">{{ userInfos?.username }}</j-descriptions-item>
|
||||
<j-descriptions-item label="账号ID">{{ userInfos?.id }}</j-descriptions-item>
|
||||
<j-descriptions-item label="姓名">{{ userInfos.name }}</j-descriptions-item>
|
||||
<j-descriptions-item label="角色">{{ role }}</j-descriptions-item>
|
||||
<j-descriptions-item label="组织">{{ org }}</j-descriptions-item>
|
||||
<j-descriptions-item label="手机号">{{ userInfos?.telephone || '--' }}</j-descriptions-item>
|
||||
<j-descriptions-item label="邮箱">{{ userInfos?.email || '--' }}</j-descriptions-item>
|
||||
</j-descriptions>
|
||||
<template #footer>
|
||||
<j-button type="primary" @click="emit('close')">关闭</j-button>
|
||||
</template>
|
||||
</j-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useUserInfo } from '@/store/userInfo';
|
||||
|
||||
const { userInfos } = useUserInfo();
|
||||
const emit = defineEmits(['close', 'save']);
|
||||
|
||||
const role = computed(() => {
|
||||
const _role = userInfos?.roleList.map((item: any) => item?.name).join(',')
|
||||
return _role || '暂无角色'
|
||||
})
|
||||
|
||||
const org = computed(() => {
|
||||
const _role = userInfos?.orgList.map((item: any) => item?.name).join(',')
|
||||
return _role || '暂无组织'
|
||||
})
|
||||
</script>
|
|
@ -4,7 +4,7 @@
|
|||
title="编辑"
|
||||
@ok="handleOk"
|
||||
width="770px"
|
||||
@cancel="emits('update:visible', false)"
|
||||
@cancel="emits('close')"
|
||||
:confirmLoading="loading"
|
||||
>
|
||||
<j-form :model="form" layout="vertical" ref="formRef">
|
||||
|
@ -80,7 +80,12 @@
|
|||
<j-form-item
|
||||
label="邮箱"
|
||||
name="email"
|
||||
:rules="[{ type: 'email',message:'邮箱不是一个有效的email' }]"
|
||||
:rules="[
|
||||
{
|
||||
type: 'email',
|
||||
message: '邮箱不是一个有效的email',
|
||||
},
|
||||
]"
|
||||
>
|
||||
<j-input
|
||||
v-model:value="form.email"
|
||||
|
@ -95,28 +100,30 @@
|
|||
|
||||
<script setup lang="ts">
|
||||
import { updateMeInfo_api } from '@/api/account/center';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { FormInstance } from 'ant-design-vue/es';
|
||||
import { userInfoType } from '../typing';
|
||||
import { onlyMessage } from '@/utils/comm';
|
||||
|
||||
const emits = defineEmits(['ok', 'update:visible']);
|
||||
const props = defineProps<{
|
||||
visible: boolean;
|
||||
data: userInfoType;
|
||||
}>();
|
||||
const loading = ref(false)
|
||||
const emits = defineEmits(['save', 'close']);
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
});
|
||||
const loading = ref(false);
|
||||
const form = ref(props.data);
|
||||
const formRef = ref<FormInstance>();
|
||||
const formRef = ref<any>();
|
||||
|
||||
const handleOk = () => {
|
||||
formRef.value?.validate().then(() => {
|
||||
loading.value = true
|
||||
updateMeInfo_api(form.value).then((resp) => {
|
||||
if (resp.status === 200) {
|
||||
message.success('保存成功');
|
||||
emits('ok');
|
||||
emits('update:visible', false);
|
||||
}
|
||||
}).finally(()=>loading.value = false)
|
||||
loading.value = true;
|
||||
updateMeInfo_api(form.value)
|
||||
.then((resp) => {
|
||||
if (resp.status === 200) {
|
||||
onlyMessage('保存成功', 'success');
|
||||
emits('save');
|
||||
}
|
||||
})
|
||||
.finally(() => (loading.value = false));
|
||||
});
|
||||
};
|
||||
</script>
|
|
@ -5,7 +5,7 @@
|
|||
@ok="handleOk"
|
||||
width="520px"
|
||||
:confirmLoading="loading"
|
||||
@cancel="emits('update:visible', false)"
|
||||
@cancel="emits('close')"
|
||||
>
|
||||
<j-form :model="form" layout="vertical" ref="formRef">
|
||||
<j-form-item
|
||||
|
@ -24,7 +24,6 @@
|
|||
<j-form-item
|
||||
label="密码"
|
||||
name="newPassword"
|
||||
|
||||
:rules="[
|
||||
{ required: true, message: '请输入密码' },
|
||||
{ validator: checkMethods.new, trigger: 'blur' },
|
||||
|
@ -58,15 +57,18 @@ import {
|
|||
checkOldPassword_api,
|
||||
validateField_api,
|
||||
} from '@/api/account/center';
|
||||
import { FormInstance, message } from 'ant-design-vue';
|
||||
import { Rule } from 'ant-design-vue/lib/form';
|
||||
import { onlyMessage } from '@/utils/comm';
|
||||
|
||||
type formType = {
|
||||
oldPassword: string;
|
||||
newPassword: string;
|
||||
confirmPassword: string;
|
||||
};
|
||||
|
||||
const emits = defineEmits(['save', 'close']);
|
||||
|
||||
const emits = defineEmits(['ok', 'update:visible']);
|
||||
const props = defineProps<{
|
||||
visible: boolean;
|
||||
}>();
|
||||
const loading = ref(false);
|
||||
const formRef = ref<FormInstance>();
|
||||
const formRef = ref<any>();
|
||||
const form = ref<formType>({
|
||||
oldPassword: '',
|
||||
newPassword: '',
|
||||
|
@ -74,7 +76,7 @@ const form = ref<formType>({
|
|||
});
|
||||
|
||||
const checkMethods = {
|
||||
old: async (_rule: Rule, value: string) => {
|
||||
old: async (_rule: any, value: string) => {
|
||||
if (!value) return Promise.reject('请输入密码');
|
||||
try {
|
||||
const resp: any = await checkOldPassword_api(value);
|
||||
|
@ -85,7 +87,7 @@ const checkMethods = {
|
|||
return Promise.reject('验证失败');
|
||||
}
|
||||
},
|
||||
new: async (_rule: Rule, value: string) => {
|
||||
new: async (_rule: any, value: string) => {
|
||||
if (!value) return Promise.reject('请输入密码');
|
||||
else if (
|
||||
form.value.confirmPassword &&
|
||||
|
@ -101,7 +103,7 @@ const checkMethods = {
|
|||
return Promise.reject('验证失败');
|
||||
}
|
||||
},
|
||||
confirm: async (_rule: Rule, value: string) => {
|
||||
confirm: async (_rule: any, value: string) => {
|
||||
if (!value) return Promise.reject();
|
||||
else if (form.value.newPassword && value !== form.value.newPassword) {
|
||||
formRef.value?.validate('newPassword');
|
||||
|
@ -127,20 +129,13 @@ const handleOk = () => {
|
|||
updateMepsd_api(params)
|
||||
.then((resp) => {
|
||||
if (resp.status === 200) {
|
||||
message.success('保存成功');
|
||||
emits('ok');
|
||||
emits('update:visible', false);
|
||||
onlyMessage('保存成功', 'success');
|
||||
emits('save');
|
||||
}
|
||||
})
|
||||
.finally(() => (loading.value = false));
|
||||
});
|
||||
};
|
||||
|
||||
type formType = {
|
||||
oldPassword: string;
|
||||
newPassword: string;
|
||||
confirmPassword: string;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
|
@ -0,0 +1,184 @@
|
|||
<template>
|
||||
<j-modal
|
||||
visible
|
||||
title="重置密码"
|
||||
width="520px"
|
||||
:confirmLoading="loading"
|
||||
@cancel="emits('close')"
|
||||
>
|
||||
<j-steps :current="current" size="small" progress-dot @change="onChange">
|
||||
<j-step title="验证密码" />
|
||||
<j-step title="设置密码" />
|
||||
<j-step title="二次确认" />
|
||||
</j-steps>
|
||||
<div class="content">
|
||||
<j-form :model="form" layout="vertical" ref="formRef">
|
||||
<j-form-item
|
||||
label="请输入当前密码"
|
||||
name="oldPassword"
|
||||
v-show="current === 0"
|
||||
:rules="[
|
||||
{ required: true, message: '请输入当前密码' },
|
||||
{ validator: checkMethods.old, trigger: 'blur' },
|
||||
]"
|
||||
>
|
||||
<j-input
|
||||
v-model:value="form.oldPassword"
|
||||
placeholder="请输入当前密码"
|
||||
/>
|
||||
</j-form-item>
|
||||
<j-form-item
|
||||
label="请输入新密码"
|
||||
name="newPassword"
|
||||
v-show="current === 1"
|
||||
:rules="[
|
||||
{ required: true, message: '请输入新密码' },
|
||||
{ validator: checkMethods.new, trigger: 'blur' },
|
||||
]"
|
||||
>
|
||||
<j-input-password
|
||||
v-model:value="form.newPassword"
|
||||
placeholder="请输入新密码"
|
||||
/>
|
||||
</j-form-item>
|
||||
<j-form-item
|
||||
label="请确认新密码"
|
||||
v-show="current === 2"
|
||||
name="confirmPassword"
|
||||
:rules="[
|
||||
{ required: true, message: '请确认新密码' },
|
||||
{ validator: checkMethods.confirm, trigger: 'blur' },
|
||||
]"
|
||||
>
|
||||
<j-input-password
|
||||
v-model:value="form.confirmPassword"
|
||||
placeholder="请确认新密码"
|
||||
/>
|
||||
</j-form-item>
|
||||
</j-form>
|
||||
</div>
|
||||
<template #footer>
|
||||
<j-button v-if="current === 0" @click="emits('close')">取消</j-button>
|
||||
<j-button v-if="current === 2" @click="onPrev">上一步</j-button>
|
||||
<j-button type="primary" v-else @click="onNext">下一步</j-button>
|
||||
<j-button v-if="current === 2" type="primary" @click="handleOk">完成</j-button>
|
||||
</template>
|
||||
</j-modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
updateMepsd_api,
|
||||
checkOldPassword_api,
|
||||
validateField_api,
|
||||
} from '@/api/account/center';
|
||||
import { onlyMessage } from '@/utils/comm';
|
||||
|
||||
type formType = {
|
||||
oldPassword: string;
|
||||
newPassword: string;
|
||||
confirmPassword: string;
|
||||
};
|
||||
|
||||
const emits = defineEmits(['save', 'close']);
|
||||
|
||||
const loading = ref(false);
|
||||
const formRef = ref<any>();
|
||||
|
||||
const form = ref<formType>({
|
||||
oldPassword: '',
|
||||
newPassword: '',
|
||||
confirmPassword: '',
|
||||
});
|
||||
const current = ref<number>(0);
|
||||
|
||||
const onPrev = () => {
|
||||
current.value -= 1
|
||||
}
|
||||
|
||||
const jumpStep = (val: number) => {
|
||||
if(val === 1) {
|
||||
formRef.value?.validate('oldPassword').then(() => {
|
||||
current.value += 1
|
||||
})
|
||||
} else if(val === 2) {
|
||||
formRef.value?.validate('newPassword').then(() => {
|
||||
current.value += 1
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const onNext = () => {
|
||||
jumpStep(current.value + 1)
|
||||
}
|
||||
|
||||
const onChange = (cur: number) => {
|
||||
jumpStep(cur)
|
||||
}
|
||||
|
||||
const checkMethods = {
|
||||
old: async (_rule: any, value: string) => {
|
||||
if (!value) return Promise.resolve();
|
||||
try {
|
||||
const resp: any = await checkOldPassword_api(value);
|
||||
if (resp.status === 200 && !resp.result.passed)
|
||||
return Promise.reject(resp.result.reason);
|
||||
else return Promise.resolve();
|
||||
} catch (error) {
|
||||
return Promise.reject('验证失败');
|
||||
}
|
||||
},
|
||||
new: async (_rule: any, value: string) => {
|
||||
if (!value) return Promise.resolve();
|
||||
try {
|
||||
const resp: any = await validateField_api('password', value);
|
||||
if (resp.status === 200 && !resp.result.passed)
|
||||
return Promise.reject(resp.result.reason);
|
||||
else return Promise.resolve();
|
||||
} catch (error) {
|
||||
return Promise.reject('验证失败');
|
||||
}
|
||||
},
|
||||
confirm: async (_rule: any, value: string) => {
|
||||
if (!value) return Promise.resolve();
|
||||
else if (
|
||||
form.value.newPassword &&
|
||||
value !== form.value.newPassword
|
||||
) {
|
||||
return Promise.reject('两次密码输入不一致');
|
||||
}
|
||||
try {
|
||||
const resp: any = await validateField_api('password', value);
|
||||
if (resp.status === 200 && !resp.result.passed)
|
||||
return Promise.reject(resp.result.reason);
|
||||
else return Promise.resolve();
|
||||
} catch (error) {
|
||||
return Promise.reject('验证失败');
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const handleOk = () => {
|
||||
formRef.value?.validate().then(() => {
|
||||
loading.value = true;
|
||||
const params = {
|
||||
oldPassword: form.value.oldPassword,
|
||||
newPassword: form.value.newPassword,
|
||||
};
|
||||
updateMepsd_api(params)
|
||||
.then((resp) => {
|
||||
if (resp.status === 200) {
|
||||
onlyMessage('保存成功', 'success');
|
||||
emits('save');
|
||||
}
|
||||
})
|
||||
.finally(() => (loading.value = false));
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.content {
|
||||
padding: 20px 50px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,103 @@
|
|||
<template>
|
||||
<div class="choose-view">
|
||||
<j-row class="view-content" :gutter="24">
|
||||
<j-col
|
||||
:span="8"
|
||||
class="select-item"
|
||||
:class="{ selected: currentView === 'device' }"
|
||||
@click="currentView = 'device'"
|
||||
>
|
||||
<img :src="getImage('/home/device.png')" alt="" />
|
||||
</j-col>
|
||||
<j-col
|
||||
:span="8"
|
||||
class="select-item"
|
||||
:class="{ selected: currentView === 'ops' }"
|
||||
@click="currentView = 'ops'"
|
||||
>
|
||||
<img :src="getImage('/home/ops.png')" alt="" />
|
||||
</j-col>
|
||||
<j-col
|
||||
:span="8"
|
||||
class="select-item"
|
||||
:class="{
|
||||
selected: currentView === 'comprehensive',
|
||||
}"
|
||||
@click="currentView = 'comprehensive'"
|
||||
>
|
||||
<img :src="getImage('/home/comprehensive.png')" alt="" />
|
||||
</j-col>
|
||||
</j-row>
|
||||
<j-button type="primary" class="btn" @click="confirm">确定</j-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { getMe_api, getView_api, setView_api } from '@/api/home';
|
||||
import { getImage, onlyMessage } from '@/utils/comm';
|
||||
const currentView = ref<string>('');
|
||||
const isApiUser = ref<boolean>();
|
||||
|
||||
function getViews() {
|
||||
// 判断是否是api用户 不是则获取选中的视图
|
||||
getMe_api()
|
||||
.then((resp: any) => {
|
||||
if (resp && resp.status === 200) {
|
||||
isApiUser.value = resp.result.dimensions.find(
|
||||
(item: any) =>
|
||||
item.type === 'api-client' ||
|
||||
item.type.id === 'api-client',
|
||||
);
|
||||
if (!isApiUser.value) return getView_api();
|
||||
}
|
||||
})
|
||||
.then((resp: any) => {
|
||||
if (resp?.status === 200) {
|
||||
if (resp.result) currentView.value = resp.result?.content;
|
||||
else if (resp.result?.username === 'admin') {
|
||||
currentView.value = 'comprehensive';
|
||||
} else currentView.value = 'init';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const confirm = () => {
|
||||
setView_api({
|
||||
name: 'view',
|
||||
content: currentView.value,
|
||||
}).then(() => onlyMessage('保存成功', 'success'));
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
getViews();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.choose-view {
|
||||
width: 100%;
|
||||
margin-top: 30px;
|
||||
padding: 48px 150px;
|
||||
box-sizing: border-box;
|
||||
.view-content {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
.select-item {
|
||||
border: 2px solid transparent;
|
||||
img {
|
||||
width: 100%;
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
&.selected {
|
||||
border-color: #10239e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.btn {
|
||||
display: block;
|
||||
margin: 48px auto;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -4,6 +4,7 @@
|
|||
<pro-search
|
||||
:columns="columns"
|
||||
target="category"
|
||||
style="padding: 0"
|
||||
@search="(params:any)=>queryParams = {...params}"
|
||||
/>
|
||||
|
||||
|
@ -13,12 +14,16 @@
|
|||
:request="getList_api"
|
||||
model="TABLE"
|
||||
:params="queryParams"
|
||||
:bodyStyle="{padding: 0}"
|
||||
:defaultParams="{
|
||||
sorts: [{
|
||||
name: 'notifyTime', order: 'desc'
|
||||
}]
|
||||
}"
|
||||
>
|
||||
<template #headerTitle>
|
||||
<j-button type="primary">全部已读</j-button>
|
||||
</template>
|
||||
<template #topicProvider="slotProps">
|
||||
{{ slotProps.topicName }}
|
||||
</template>
|
|
@ -0,0 +1,26 @@
|
|||
<template>
|
||||
<div style="margin-top: 24px;">
|
||||
<j-tabs tab-position="left">
|
||||
<j-tab-pane v-for="item in tabs" :key="item.key" :tab="item.tab"><NotificationRecord /></j-tab-pane>
|
||||
</j-tabs>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import NotificationRecord from './components/NotificationRecord/index.vue'
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
key: '1',
|
||||
tab: '告警'
|
||||
},
|
||||
{
|
||||
key: '2',
|
||||
tab: '系统运维'
|
||||
},
|
||||
{
|
||||
key: '3',
|
||||
tab: '业务监控'
|
||||
}
|
||||
]
|
||||
</script>
|
|
@ -0,0 +1,23 @@
|
|||
<template>
|
||||
<div>
|
||||
<div><j-button type="link">取消订阅</j-button></div>
|
||||
<template v-if="['dingTalk', 'weixin'].includes(type)">
|
||||
<div><j-button type="link">更换接收账号</j-button></div>
|
||||
<p>当前绑定的账号名称</p>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div><j-button type="link">更换接收账号</j-button></div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { PropType } from "vue";
|
||||
type Type = 'dingTalk' | 'weixin' | 'email' | 'voice' | 'sms';
|
||||
const props = defineProps({
|
||||
type: {
|
||||
type: String as PropType<Type>,
|
||||
default: 'dingTalk',
|
||||
},
|
||||
});
|
||||
</script>
|
|
@ -0,0 +1,39 @@
|
|||
<template>
|
||||
<j-modal visible @cancel="emit('close')">
|
||||
<template v-if="type === 'dingTalk'">
|
||||
<p>请先绑定钉钉账号</p>
|
||||
</template>
|
||||
<template v-else-if="type === 'weixin'">
|
||||
<p>请先绑定企业微信账号</p>
|
||||
</template>
|
||||
<template v-else-if="type === 'email'">
|
||||
<p>请先绑定邮箱</p>
|
||||
</template>
|
||||
<template v-else>
|
||||
<p>请先绑定手机号</p>
|
||||
</template>
|
||||
<template #footer>
|
||||
<j-button @click="emit('close')">确定</j-button>
|
||||
<j-button @click="onBind" type="primary" v-if="['voice', 'sms'].includes(type)">立即绑定</j-button>
|
||||
</template>
|
||||
</j-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onlyMessage } from '@/utils/comm';
|
||||
import { PropType } from 'vue';
|
||||
|
||||
type Type = 'dingTalk' | 'weixin' | 'email' | 'voice' | 'sms';
|
||||
|
||||
const emit = defineEmits(['close']);
|
||||
const props = defineProps({
|
||||
type: {
|
||||
type: String as PropType<Type>,
|
||||
default: 'dingTalk',
|
||||
},
|
||||
});
|
||||
|
||||
const onBind = () => {
|
||||
onlyMessage('打开详情编辑框')
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,300 @@
|
|||
<template>
|
||||
<div style="margin-top: 24px">
|
||||
<div class="alert">
|
||||
<AIcon type="InfoCircleOutlined" />
|
||||
你可以在该页面选择需要订阅的主题及接收通知的方式。
|
||||
</div>
|
||||
<div style="margin-top: 20px">
|
||||
<j-collapse :bordered="false" v-model:activeKey="activeKey">
|
||||
<template #expandIcon="{ isActive }">
|
||||
<AIcon
|
||||
type="CaretRightOutlined"
|
||||
:rotate="isActive ? 90 : 0"
|
||||
/>
|
||||
</template>
|
||||
<j-collapse-panel
|
||||
v-for="item in dataSource"
|
||||
:key="item.id"
|
||||
class="custom"
|
||||
>
|
||||
<template #header
|
||||
><h3>{{ item.name }}</h3></template
|
||||
>
|
||||
<div class="child">
|
||||
<template
|
||||
v-for="child in item.children"
|
||||
:key="child.id"
|
||||
>
|
||||
<div class="child-item">
|
||||
<div class="child-item-left">
|
||||
<div style="font-weight: 600">
|
||||
{{ child.name }}
|
||||
</div>
|
||||
<div class="child-item-left-auth">
|
||||
<j-tooltip
|
||||
title="当产品类型的告警被触发时,你将在已订阅的方式中收到通知"
|
||||
>
|
||||
<AIcon
|
||||
type="ExclamationCircleOutlined"
|
||||
/>
|
||||
</j-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
<div class="child-item-right">
|
||||
<MCarousel :data="child.children">
|
||||
<template #card="slotProps">
|
||||
<div class="box-item">
|
||||
<j-popover>
|
||||
<div class="box-item-img">
|
||||
<img
|
||||
style="width: 100%"
|
||||
:src="
|
||||
getImage(
|
||||
`/notice/${slotProps?.type}.png`,
|
||||
)
|
||||
"
|
||||
/>
|
||||
<div
|
||||
class="box-item-checked"
|
||||
>
|
||||
<j-checkbox
|
||||
:checked="
|
||||
!slotProps?.type
|
||||
"
|
||||
></j-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
<template #content>
|
||||
<Detail />
|
||||
<!-- <Error v-else /> -->
|
||||
</template>
|
||||
</j-popover>
|
||||
<div class="box-item-text">
|
||||
{{ item.name }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</MCarousel>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</j-collapse-panel>
|
||||
</j-collapse>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { getImage } from '@/utils/comm';
|
||||
import MCarousel from '@/components/MCarousel/index.vue';
|
||||
import Detail from './components/Detail.vue';
|
||||
import Error from './components/Error.vue'
|
||||
|
||||
const dataSource = ref([
|
||||
{
|
||||
id: 'a',
|
||||
name: '告警',
|
||||
children: [
|
||||
{
|
||||
id: 'product',
|
||||
name: '产品告警',
|
||||
active: true,
|
||||
children: [
|
||||
{
|
||||
id: 'sms9',
|
||||
name: '站内信',
|
||||
type: 'sms',
|
||||
},
|
||||
{
|
||||
id: 'dingtalk8',
|
||||
name: '钉钉',
|
||||
type: 'dingtalk',
|
||||
},
|
||||
{
|
||||
id: 'wechat7',
|
||||
name: '微信',
|
||||
type: 'wechat',
|
||||
},
|
||||
{
|
||||
id: 'email6',
|
||||
name: '邮箱',
|
||||
type: 'email',
|
||||
},
|
||||
{
|
||||
id: 'dingtalk5',
|
||||
name: '钉钉',
|
||||
type: 'dingtalk',
|
||||
},
|
||||
{
|
||||
id: 'wechat4',
|
||||
name: '微信',
|
||||
type: 'wechat',
|
||||
},
|
||||
{
|
||||
id: 'email3',
|
||||
name: '邮箱',
|
||||
type: 'email',
|
||||
},
|
||||
{
|
||||
id: 'email2',
|
||||
name: '邮箱',
|
||||
type: 'email',
|
||||
},
|
||||
{
|
||||
id: 'email1',
|
||||
name: '邮箱',
|
||||
type: 'email',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'device',
|
||||
name: '设备告警',
|
||||
active: false,
|
||||
children: [
|
||||
{
|
||||
id: 'sms11',
|
||||
name: '站内信',
|
||||
type: 'sms',
|
||||
},
|
||||
{
|
||||
id: 'wechat11',
|
||||
name: '微信',
|
||||
type: 'wechat',
|
||||
},
|
||||
{
|
||||
id: 'voice11',
|
||||
name: '语音',
|
||||
type: 'voice',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'b',
|
||||
name: '系统监控',
|
||||
children: [
|
||||
{
|
||||
id: 'cache',
|
||||
name: '缓冲区数据丢弃',
|
||||
active: false,
|
||||
children: [
|
||||
{
|
||||
id: 'message111',
|
||||
name: '站内信',
|
||||
type: 'sms',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'mqtt',
|
||||
name: 'MQTT并发限制',
|
||||
active: false,
|
||||
children: [
|
||||
{
|
||||
id: 'message22',
|
||||
name: '站内信',
|
||||
type: 'sms',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'c',
|
||||
name: '业务监控',
|
||||
children: [
|
||||
{
|
||||
id: 'error',
|
||||
name: '透传消息解析异常',
|
||||
active: false,
|
||||
children: [
|
||||
{
|
||||
id: 'message333',
|
||||
name: '站内信',
|
||||
type: 'sms',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
const activeKey = ['a', 'b', 'c'];
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.alert {
|
||||
height: 40px;
|
||||
padding-left: 10px;
|
||||
margin-bottom: 10px;
|
||||
color: rgba(0, 0, 0, 0.55);
|
||||
line-height: 40px;
|
||||
background-color: #f6f6f6;
|
||||
}
|
||||
.custom {
|
||||
background: #f7f7f7;
|
||||
border-radius: 4px;
|
||||
border: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
.child {
|
||||
background-color: white;
|
||||
padding: 10px;
|
||||
.child-item {
|
||||
padding: 10px 20px;
|
||||
margin: 5px;
|
||||
background: #f7f7f7;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.child-item-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
div {
|
||||
display: flex;
|
||||
margin-right: 30px;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.child-item-left-auth {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.child-item-right {
|
||||
display: flex;
|
||||
|
||||
.box-item {
|
||||
margin-left: 10px;
|
||||
.box-item-img {
|
||||
background-color: #fff;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
|
||||
.box-item-checked {
|
||||
position: absolute;
|
||||
top: -10px;
|
||||
right: -10px;
|
||||
}
|
||||
}
|
||||
|
||||
.box-item-text {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
height: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,482 @@
|
|||
<template>
|
||||
<page-container>
|
||||
<div class="center-container">
|
||||
<div class="card">
|
||||
<div class="content" style="margin-top: 0">
|
||||
<div
|
||||
class="content-item flex-item"
|
||||
style="width: 350px; justify-content: center"
|
||||
>
|
||||
<img
|
||||
v-if="userInfo.avatar"
|
||||
:src="userInfo.avatar"
|
||||
style="width: 140px; border-radius: 70px"
|
||||
alt=""
|
||||
/>
|
||||
<div class="default-avatar" v-else>
|
||||
<AIcon type="UserOutlined" />
|
||||
</div>
|
||||
|
||||
<div
|
||||
style="
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
margin-top: 20px;
|
||||
"
|
||||
>
|
||||
<j-upload
|
||||
v-model:file-list="upload.fileList"
|
||||
accept=".jpg,.png,.jfif,.pjp,.pjpeg,.jpeg"
|
||||
:maxCount="1"
|
||||
:show-upload-list="false"
|
||||
:headers="{
|
||||
[TOKEN_KEY]: LocalStore.get(TOKEN_KEY),
|
||||
}"
|
||||
:action="`${BASE_API_PATH}/file/static`"
|
||||
@change="upload.changeBackUpload"
|
||||
:beforeUpload="upload.beforeUpload"
|
||||
>
|
||||
<j-button>
|
||||
<AIcon type="UploadOutlined" />
|
||||
更换头像
|
||||
</j-button>
|
||||
</j-upload>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="content-item flex-item"
|
||||
style="flex: 1; padding: 15px 0"
|
||||
>
|
||||
<div class="info-card">
|
||||
<p>用户名</p>
|
||||
<p>{{ userInfo.username }}</p>
|
||||
</div>
|
||||
<div class="info-card">
|
||||
<p>账号ID</p>
|
||||
<p>{{ userInfo.id }}</p>
|
||||
</div>
|
||||
<div class="info-card">
|
||||
<p>注册时间</p>
|
||||
<p>
|
||||
{{
|
||||
userInfo.createTime ? moment(userInfo.createTime).format(
|
||||
'YYYY-MM-DD HH:mm:ss',
|
||||
) : '-'
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
<div class="info-card">
|
||||
<p>电话</p>
|
||||
<p>{{ userInfo.telephone || '-' }}</p>
|
||||
</div>
|
||||
<div class="info-card">
|
||||
<p>姓名</p>
|
||||
<p>{{ userInfo.name }}</p>
|
||||
</div>
|
||||
<div class="info-card">
|
||||
<p>角色</p>
|
||||
<p>
|
||||
{{
|
||||
(userInfo.roleList &&
|
||||
userInfo.roleList
|
||||
.map((item) => item.name)
|
||||
.join(',')) ||
|
||||
'-'
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
<div class="info-card">
|
||||
<p>组织</p>
|
||||
<p>
|
||||
{{
|
||||
(userInfo.orgList &&
|
||||
userInfo.orgList
|
||||
.map((item) => item.name)
|
||||
.join(',')) ||
|
||||
'-'
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
<div class="info-card">
|
||||
<p>邮箱</p>
|
||||
<p>{{ userInfo.email || '-' }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<AIcon
|
||||
type="EditOutlined"
|
||||
class="edit"
|
||||
style="right: 40px"
|
||||
@click="editInfoVisible = true"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card" v-if='updatePassword'>
|
||||
<h3>修改密码</h3>
|
||||
<div class="content">
|
||||
<div class="content" style="align-items: flex-end">
|
||||
<AIcon
|
||||
type="LockOutlined"
|
||||
style="color: #1d39c4; font-size: 70px"
|
||||
/>
|
||||
<span
|
||||
style="margin-left: 5px; color: rgba(0, 0, 0, 0.55)"
|
||||
>安全性高的密码可以使帐号更安全。建议您定期更换密码,设置一个包含字母,符号或数字中至少两项且长度超过8位的密码</span
|
||||
>
|
||||
</div>
|
||||
<span class="edit">
|
||||
<PermissionButton
|
||||
:uhasPermission="`${permission}:update`"
|
||||
type="link"
|
||||
@click="editPasswordVisible = true"
|
||||
>
|
||||
<AIcon type="EditOutlined" style="color: #1d39c4" />
|
||||
</PermissionButton>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 社区版不显示 -->
|
||||
<div class="card" v-if="isNoCommunity">
|
||||
<h3>绑定三方账号</h3>
|
||||
<div class="content">
|
||||
<div class="account-card" v-for="item in bindList">
|
||||
<img
|
||||
:src="item.logoUrl || getImage(bindIcon[item.provider])"
|
||||
style="height: 50px;width: 50px"
|
||||
width='50px'
|
||||
height='50px'
|
||||
alt=""
|
||||
/>
|
||||
<Ellipsis style="width: 150px; font-size: 22px">
|
||||
<div v-if="item.bound">
|
||||
<div>绑定名:{{ item.others.name }}</div>
|
||||
<div>
|
||||
绑定时间:{{
|
||||
moment(item.bindTime).format(
|
||||
'YYYY-MM-DD HH:mm:ss',
|
||||
)
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>{{ item.name }}未绑定</div>
|
||||
</Ellipsis>
|
||||
<j-popconfirm
|
||||
v-if="item.bound"
|
||||
title="确认解除绑定嘛?"
|
||||
@confirm="() => unBind(item.id)"
|
||||
>
|
||||
<j-button>解除绑定</j-button>
|
||||
</j-popconfirm>
|
||||
<j-button
|
||||
v-else
|
||||
type="primary"
|
||||
@click="clickBind(item.id)"
|
||||
>立即绑定</j-button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 第三方用户不显示 -->
|
||||
<div class="card" v-if="!isApiUser">
|
||||
<h3>首页视图</h3>
|
||||
<div class="choose-view">
|
||||
<j-row class="view-content" :gutter="24">
|
||||
<j-col
|
||||
:span="6"
|
||||
class="select-item"
|
||||
:class="{ selected: currentView === 'device' }"
|
||||
@click="currentView = 'device'"
|
||||
>
|
||||
<img :src="getImage('/home/device.png')" alt="" />
|
||||
</j-col>
|
||||
<j-col
|
||||
:span="6"
|
||||
class="select-item"
|
||||
:class="{ selected: currentView === 'ops' }"
|
||||
@click="currentView = 'ops'"
|
||||
>
|
||||
<img :src="getImage('/home/ops.png')" alt="" />
|
||||
</j-col>
|
||||
<j-col
|
||||
:span="6"
|
||||
class="select-item"
|
||||
:class="{
|
||||
selected: currentView === 'comprehensive',
|
||||
}"
|
||||
@click="currentView = 'comprehensive'"
|
||||
>
|
||||
<img
|
||||
:src="getImage('/home/comprehensive.png')"
|
||||
alt=""
|
||||
/>
|
||||
</j-col>
|
||||
</j-row>
|
||||
<j-button type="primary" class="btn" @click="confirm"
|
||||
>确定</j-button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<EditInfoDialog
|
||||
v-if="editInfoVisible"
|
||||
v-model:visible="editInfoVisible"
|
||||
:data="{ ...userInfo }"
|
||||
@ok="getUserInfo"
|
||||
/>
|
||||
<EditPasswordDialog
|
||||
v-if="editPasswordVisible"
|
||||
v-model:visible="editPasswordVisible"
|
||||
/>
|
||||
</div>
|
||||
</page-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="Center">
|
||||
import PermissionButton from '@/components/PermissionButton/index.vue';
|
||||
import EditInfoDialog from './components/EditInfoDialog.vue';
|
||||
import EditPasswordDialog from './components/EditPasswordDialog.vue';
|
||||
import { BASE_API_PATH, TOKEN_KEY } from '@/utils/variable';
|
||||
import { LocalStore, getImage, onlyMessage } from '@/utils/comm'
|
||||
import { message, UploadChangeParam, UploadFile } from 'ant-design-vue';
|
||||
import {
|
||||
getMeInfo_api,
|
||||
getSsoBinds_api,
|
||||
unBind_api,
|
||||
updateMeInfo_api
|
||||
} from '@/api/account/center';
|
||||
import moment from 'moment';
|
||||
import { getMe_api, getView_api, setView_api } from '@/api/home';
|
||||
import { isNoCommunity } from '@/utils/utils';
|
||||
import { userInfoType } from './typing';
|
||||
import { usePermissionStore } from 'store/permission'
|
||||
|
||||
const btnHasPermission = usePermissionStore().hasPermission;
|
||||
const updatePassword = btnHasPermission('account-center:user-center-passwd-update')
|
||||
const permission = 'system/User';
|
||||
const userInfo = ref<userInfoType>({} as any);
|
||||
// 第三方账号
|
||||
const bindList = ref<any[]>([]);
|
||||
const bindIcon = {
|
||||
'dingtalk-ent-app': '/notice/dingtalk.png',
|
||||
'wechat-webapp': '/notice/wechat.png',
|
||||
'internal-standalone': '/apply/provider1.png',
|
||||
'third-party': '/apply/provider5.png',
|
||||
};
|
||||
const unBind = (id: string) => {
|
||||
unBind_api(id).then((resp) => {
|
||||
if (resp.status === 200) {
|
||||
message.success('解绑成功');
|
||||
getSsoBinds();
|
||||
}
|
||||
});
|
||||
};
|
||||
const clickBind = (id: string) => {
|
||||
window.open(
|
||||
`${BASE_API_PATH}/application/sso/${id}/login?autoCreateUser=false`,
|
||||
);
|
||||
localStorage.setItem('onBind', 'false');
|
||||
localStorage.setItem('onLogin', 'yes');
|
||||
window.onstorage = (e) => {
|
||||
if (e.newValue) {
|
||||
getSsoBinds();
|
||||
}
|
||||
};
|
||||
};
|
||||
const upload = reactive({
|
||||
fileList: [] as any[],
|
||||
uploadLoading: false,
|
||||
changeBackUpload: (info: UploadChangeParam<UploadFile<any>>) => {
|
||||
if (info.file.status === 'uploading') {
|
||||
upload.uploadLoading = true;
|
||||
} else if (info.file.status === 'done') {
|
||||
info.file.url = info.file.response?.result;
|
||||
upload.uploadLoading = false;
|
||||
userInfo.value.avatar = info.file.response?.result;
|
||||
updateMeInfo_api(userInfo.value).then(res => {
|
||||
if(res.success) {
|
||||
onlyMessage('上传成功')
|
||||
}
|
||||
})
|
||||
} else if (info.file.status === 'error') {
|
||||
upload.uploadLoading = false;
|
||||
onlyMessage('logo上传失败,请稍后再试', 'error');
|
||||
}
|
||||
},
|
||||
beforeUpload: ({ size, type }: File) => {
|
||||
const imageTypes = ['jpg', 'png', 'jfif', 'pjp', 'pjpeg', 'jpeg'];
|
||||
const typeBool =
|
||||
imageTypes.filter((typeStr) => type.includes(typeStr)).length > 0;
|
||||
const sizeBool = size < 4 * 1024 * 1024;
|
||||
|
||||
(typeBool && sizeBool) || message.error('请上传正确格式的图片');
|
||||
return typeBool && sizeBool;
|
||||
},
|
||||
});
|
||||
// 首页视图
|
||||
const isApiUser = ref<boolean>();
|
||||
const currentView = ref<string>('');
|
||||
const confirm = () => {
|
||||
setView_api({
|
||||
name: 'view',
|
||||
content: currentView.value,
|
||||
}).then(() => message.success('保存成功'));
|
||||
};
|
||||
|
||||
const editInfoVisible = ref<boolean>(false);
|
||||
const editPasswordVisible = ref<boolean>(false);
|
||||
init();
|
||||
|
||||
function init() {
|
||||
getUserInfo();
|
||||
isNoCommunity && getSsoBinds();
|
||||
getViews();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户信息
|
||||
*/
|
||||
function getUserInfo() {
|
||||
getMeInfo_api().then((resp) => {
|
||||
userInfo.value = resp.result as userInfoType;
|
||||
});
|
||||
}
|
||||
/**
|
||||
* 获取绑定第三方账号
|
||||
*/
|
||||
function getSsoBinds() {
|
||||
getSsoBinds_api().then((resp: any) => {
|
||||
if (resp.status === 200) bindList.value = resp.result;
|
||||
});
|
||||
}
|
||||
/**
|
||||
* 获取首页视图
|
||||
*/
|
||||
function getViews() {
|
||||
// 判断是否是api用户 不是则获取选中的视图
|
||||
getMe_api()
|
||||
.then((resp: any) => {
|
||||
if (resp && resp.status === 200) {
|
||||
isApiUser.value = resp.result.dimensions.find(
|
||||
(item: any) =>
|
||||
item.type === 'api-client' ||
|
||||
item.type.id === 'api-client',
|
||||
);
|
||||
if (!isApiUser.value) return getView_api();
|
||||
}
|
||||
})
|
||||
.then((resp: any) => {
|
||||
if (resp?.status === 200) {
|
||||
if (resp.result) currentView.value = resp.result?.content;
|
||||
else if (resp.result?.username === 'admin') {
|
||||
currentView.value = 'comprehensive';
|
||||
} else currentView.value = 'init';
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.center-container {
|
||||
background-color: #f0f2f5;
|
||||
min-height: 100vh;
|
||||
.card {
|
||||
margin: 16px 0;
|
||||
padding: 24px;
|
||||
background-color: #fff;
|
||||
position: relative;
|
||||
|
||||
h3 {
|
||||
font-size: 22px;
|
||||
|
||||
&::before {
|
||||
display: inline-block;
|
||||
width: 3px;
|
||||
height: 0.7em;
|
||||
content: '';
|
||||
background-color: #2f54eb;
|
||||
margin: 0 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
margin-top: 24px;
|
||||
flex-wrap: wrap;
|
||||
gap: 24px;
|
||||
|
||||
.content-item {
|
||||
margin-right: 24px;
|
||||
|
||||
.default-avatar {
|
||||
background-color: #ccc;
|
||||
color: #fff;
|
||||
border-radius: 50%;
|
||||
font-size: 70px;
|
||||
width: 140px;
|
||||
height: 140px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.info-card {
|
||||
width: 25%;
|
||||
|
||||
:first-child {
|
||||
font-weight: bold;
|
||||
}
|
||||
:last-child {
|
||||
color: #666363d9;
|
||||
}
|
||||
}
|
||||
&.flex-item {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
|
||||
.edit {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
top: 30px;
|
||||
right: 24px;
|
||||
color: #1d39c4;
|
||||
}
|
||||
|
||||
.account-card {
|
||||
width: 415px;
|
||||
background-image: url(/images/notice/dingtalk-background.png);
|
||||
border-right: 1px solid #f0f0f0;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.choose-view {
|
||||
width: 100%;
|
||||
margin-top: 48px;
|
||||
.view-content {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
.select-item {
|
||||
border: 2px solid transparent;
|
||||
img {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&.selected {
|
||||
border-color: #10239e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.btn {
|
||||
display: block;
|
||||
margin: 48px auto;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,481 +1,219 @@
|
|||
<template>
|
||||
<page-container>
|
||||
<div class="center-container">
|
||||
<div class="card">
|
||||
<div class="content" style="margin-top: 0">
|
||||
<div
|
||||
class="content-item flex-item"
|
||||
style="width: 350px; justify-content: center"
|
||||
>
|
||||
<img
|
||||
v-if="userInfo.avatar"
|
||||
:src="userInfo.avatar"
|
||||
style="width: 140px; border-radius: 70px"
|
||||
alt=""
|
||||
/>
|
||||
<div class="default-avatar" v-else>
|
||||
<AIcon type="UserOutlined" />
|
||||
<div class="person">
|
||||
<div class="person-header">
|
||||
<div class="person-header-item">
|
||||
<div class="person-header-item-info">
|
||||
<div class="person-header-item-info-left">
|
||||
<j-avatar :size="64">
|
||||
<template #icon
|
||||
><AIcon type="UserOutlined"
|
||||
/></template>
|
||||
</j-avatar>
|
||||
</div>
|
||||
<div class="person-header-item-info-right">
|
||||
<div class="person-header-item-info-right-top">
|
||||
<span>xx部门 · xx角色</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
style="
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
margin-top: 20px;
|
||||
"
|
||||
>
|
||||
<j-upload
|
||||
v-model:file-list="upload.fileList"
|
||||
accept=".jpg,.png,.jfif,.pjp,.pjpeg,.jpeg"
|
||||
:maxCount="1"
|
||||
:show-upload-list="false"
|
||||
:headers="{
|
||||
[TOKEN_KEY]: LocalStore.get(TOKEN_KEY),
|
||||
}"
|
||||
:action="`${BASE_API_PATH}/file/static`"
|
||||
@change="upload.changeBackUpload"
|
||||
:beforeUpload="upload.beforeUpload"
|
||||
<div class="person-header-item-info-right-info">
|
||||
<div>用户名 {{user.userInfos?.username}}</div>
|
||||
<div>账号ID {{user.userInfos?.id}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="person-header-item-action">
|
||||
<div class="person-header-item-action-left">
|
||||
<j-space>
|
||||
<j-button
|
||||
@click="onActivated(item.key)"
|
||||
v-for="item in list"
|
||||
:type="
|
||||
activeKey === item.key
|
||||
? 'primary'
|
||||
: 'default'
|
||||
"
|
||||
:key="item.key"
|
||||
>{{ item.title }}</j-button
|
||||
>
|
||||
<j-button>
|
||||
<AIcon type="UploadOutlined" />
|
||||
更换头像
|
||||
</j-button>
|
||||
</j-upload>
|
||||
</div>
|
||||
</j-space>
|
||||
</div>
|
||||
<div
|
||||
class="content-item flex-item"
|
||||
style="flex: 1; padding: 15px 0"
|
||||
>
|
||||
<div class="info-card">
|
||||
<p>用户名</p>
|
||||
<p>{{ userInfo.username }}</p>
|
||||
</div>
|
||||
<div class="info-card">
|
||||
<p>账号ID</p>
|
||||
<p>{{ userInfo.id }}</p>
|
||||
</div>
|
||||
<div class="info-card">
|
||||
<p>注册时间</p>
|
||||
<p>
|
||||
{{
|
||||
userInfo.createTime ? moment(userInfo.createTime).format(
|
||||
'YYYY-MM-DD HH:mm:ss',
|
||||
) : '-'
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
<div class="info-card">
|
||||
<p>电话</p>
|
||||
<p>{{ userInfo.telephone || '-' }}</p>
|
||||
</div>
|
||||
<div class="info-card">
|
||||
<p>姓名</p>
|
||||
<p>{{ userInfo.name }}</p>
|
||||
</div>
|
||||
<div class="info-card">
|
||||
<p>角色</p>
|
||||
<p>
|
||||
{{
|
||||
(userInfo.roleList &&
|
||||
userInfo.roleList
|
||||
.map((item) => item.name)
|
||||
.join(',')) ||
|
||||
'-'
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
<div class="info-card">
|
||||
<p>组织</p>
|
||||
<p>
|
||||
{{
|
||||
(userInfo.orgList &&
|
||||
userInfo.orgList
|
||||
.map((item) => item.name)
|
||||
.join(',')) ||
|
||||
'-'
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
<div class="info-card">
|
||||
<p>邮箱</p>
|
||||
<p>{{ userInfo.email || '-' }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<AIcon
|
||||
type="EditOutlined"
|
||||
class="edit"
|
||||
style="right: 40px"
|
||||
@click="editInfoVisible = true"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card" v-if='updatePassword'>
|
||||
<h3>修改密码</h3>
|
||||
<div class="content">
|
||||
<div class="content" style="align-items: flex-end">
|
||||
<AIcon
|
||||
type="LockOutlined"
|
||||
style="color: #1d39c4; font-size: 70px"
|
||||
/>
|
||||
<span
|
||||
style="margin-left: 5px; color: rgba(0, 0, 0, 0.55)"
|
||||
>安全性高的密码可以使帐号更安全。建议您定期更换密码,设置一个包含字母,符号或数字中至少两项且长度超过8位的密码</span
|
||||
>
|
||||
</div>
|
||||
<span class="edit">
|
||||
<PermissionButton
|
||||
:uhasPermission="`${permission}:update`"
|
||||
type="link"
|
||||
@click="editPasswordVisible = true"
|
||||
>
|
||||
<AIcon type="EditOutlined" style="color: #1d39c4" />
|
||||
</PermissionButton>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 社区版不显示 -->
|
||||
<div class="card" v-if="isNoCommunity">
|
||||
<h3>绑定三方账号</h3>
|
||||
<div class="content">
|
||||
<div class="account-card" v-for="item in bindList">
|
||||
<img
|
||||
:src="item.logoUrl || getImage(bindIcon[item.provider])"
|
||||
style="height: 50px;width: 50px"
|
||||
width='50px'
|
||||
height='50px'
|
||||
alt=""
|
||||
/>
|
||||
<Ellipsis style="width: 150px; font-size: 22px">
|
||||
<div v-if="item.bound">
|
||||
<div>绑定名:{{ item.others.name }}</div>
|
||||
<div>
|
||||
绑定时间:{{
|
||||
moment(item.bindTime).format(
|
||||
'YYYY-MM-DD HH:mm:ss',
|
||||
)
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>{{ item.name }}未绑定</div>
|
||||
</Ellipsis>
|
||||
<j-popconfirm
|
||||
v-if="item.bound"
|
||||
title="确认解除绑定嘛?"
|
||||
@confirm="() => unBind(item.id)"
|
||||
>
|
||||
<j-button>解除绑定</j-button>
|
||||
</j-popconfirm>
|
||||
<j-button
|
||||
v-else
|
||||
type="primary"
|
||||
@click="clickBind(item.id)"
|
||||
>立即绑定</j-button
|
||||
>
|
||||
<div class="person-header-item-action-right">
|
||||
<j-space :size="24">
|
||||
<j-tooltip title="查看详情"
|
||||
><j-button @click="visible = true" shape="circle"
|
||||
><AIcon
|
||||
style="font-size: 24px"
|
||||
type="FileSearchOutlined" /></j-button
|
||||
></j-tooltip>
|
||||
<j-tooltip title="编辑资料"
|
||||
><j-button shape="circle"
|
||||
@click="editInfoVisible = true"
|
||||
><AIcon
|
||||
style="font-size: 24px"
|
||||
type="FormOutlined" /></j-button
|
||||
></j-tooltip>
|
||||
<j-tooltip title="修改密码"
|
||||
><j-button shape="circle"
|
||||
@click="editPasswordVisible = true"
|
||||
><AIcon
|
||||
style="font-size: 24px"
|
||||
type="LockOutlined" /></j-button
|
||||
></j-tooltip>
|
||||
</j-space>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 第三方用户不显示 -->
|
||||
<div class="card" v-if="!isApiUser">
|
||||
<h3>首页视图</h3>
|
||||
<div class="choose-view">
|
||||
<j-row class="view-content" :gutter="24">
|
||||
<j-col
|
||||
:span="6"
|
||||
class="select-item"
|
||||
:class="{ selected: currentView === 'device' }"
|
||||
@click="currentView = 'device'"
|
||||
>
|
||||
<img :src="getImage('/home/device.png')" alt="" />
|
||||
</j-col>
|
||||
<j-col
|
||||
:span="6"
|
||||
class="select-item"
|
||||
:class="{ selected: currentView === 'ops' }"
|
||||
@click="currentView = 'ops'"
|
||||
>
|
||||
<img :src="getImage('/home/ops.png')" alt="" />
|
||||
</j-col>
|
||||
<j-col
|
||||
:span="6"
|
||||
class="select-item"
|
||||
:class="{
|
||||
selected: currentView === 'comprehensive',
|
||||
}"
|
||||
@click="currentView = 'comprehensive'"
|
||||
>
|
||||
<img
|
||||
:src="getImage('/home/comprehensive.png')"
|
||||
alt=""
|
||||
/>
|
||||
</j-col>
|
||||
</j-row>
|
||||
<j-button type="primary" class="btn" @click="confirm"
|
||||
>确定</j-button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<EditInfoDialog
|
||||
v-if="editInfoVisible"
|
||||
v-model:visible="editInfoVisible"
|
||||
:data="{ ...userInfo }"
|
||||
@ok="getUserInfo"
|
||||
/>
|
||||
<EditPasswordDialog
|
||||
v-if="editPasswordVisible"
|
||||
v-model:visible="editPasswordVisible"
|
||||
/>
|
||||
</div>
|
||||
</page-container>
|
||||
<div class="person-content">
|
||||
<div class="person-content-item">
|
||||
<FullPage>
|
||||
<div class="person-content-item-content">
|
||||
<component :is="tabs[activeKey]" />
|
||||
</div>
|
||||
</FullPage>
|
||||
</div>
|
||||
</div>
|
||||
<Detail v-if="visible" @close="visible = false"/>
|
||||
<EditInfo v-if="editInfoVisible" :data="user.userInfos" @close="editInfoVisible = false" @save="onSave" />
|
||||
<EditPassword v-if="editPasswordVisible" @close="editPasswordVisible = false" @save="onPasswordSave" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="Center">
|
||||
import PermissionButton from '@/components/PermissionButton/index.vue';
|
||||
import EditInfoDialog from './components/EditInfoDialog.vue';
|
||||
import EditPasswordDialog from './components/EditPasswordDialog.vue';
|
||||
import { BASE_API_PATH, TOKEN_KEY } from '@/utils/variable';
|
||||
import { LocalStore, getImage, onlyMessage } from '@/utils/comm'
|
||||
import { message, UploadChangeParam, UploadFile } from 'ant-design-vue';
|
||||
import {
|
||||
getMeInfo_api,
|
||||
getSsoBinds_api,
|
||||
unBind_api,
|
||||
updateMeInfo_api
|
||||
} from '@/api/account/center';
|
||||
import moment from 'moment';
|
||||
import { getMe_api, getView_api, setView_api } from '@/api/home';
|
||||
import { isNoCommunity } from '@/utils/utils';
|
||||
import { userInfoType } from './typing';
|
||||
import { usePermissionStore } from 'store/permission'
|
||||
import HomeView from './components/HomeView/index.vue';
|
||||
import BindThirdAccount from './components/BindThirdAccount/index.vue';
|
||||
import Subscribe from './components/Subscribe/index.vue';
|
||||
import StationMessage from './components/StationMessage/index.vue';
|
||||
import Detail from './components/Detail/index.vue';
|
||||
import EditInfo from './components/EditInfo/index.vue';
|
||||
import EditPassword from './components/EditPassword/index.vue';
|
||||
import { useUserInfo } from '@/store/userInfo';
|
||||
|
||||
const btnHasPermission = usePermissionStore().hasPermission;
|
||||
const updatePassword = btnHasPermission('account-center:user-center-passwd-update')
|
||||
const permission = 'system/User';
|
||||
const userInfo = ref<userInfoType>({} as any);
|
||||
// 第三方账号
|
||||
const bindList = ref<any[]>([]);
|
||||
const bindIcon = {
|
||||
'dingtalk-ent-app': '/notice/dingtalk.png',
|
||||
'wechat-webapp': '/notice/wechat.png',
|
||||
'internal-standalone': '/apply/provider1.png',
|
||||
'third-party': '/apply/provider5.png',
|
||||
};
|
||||
const unBind = (id: string) => {
|
||||
unBind_api(id).then((resp) => {
|
||||
if (resp.status === 200) {
|
||||
message.success('解绑成功');
|
||||
getSsoBinds();
|
||||
}
|
||||
});
|
||||
};
|
||||
const clickBind = (id: string) => {
|
||||
window.open(
|
||||
`${BASE_API_PATH}/application/sso/${id}/login?autoCreateUser=false`,
|
||||
);
|
||||
localStorage.setItem('onBind', 'false');
|
||||
localStorage.setItem('onLogin', 'yes');
|
||||
window.onstorage = (e) => {
|
||||
if (e.newValue) {
|
||||
getSsoBinds();
|
||||
}
|
||||
};
|
||||
};
|
||||
const upload = reactive({
|
||||
fileList: [] as any[],
|
||||
uploadLoading: false,
|
||||
changeBackUpload: (info: UploadChangeParam<UploadFile<any>>) => {
|
||||
if (info.file.status === 'uploading') {
|
||||
upload.uploadLoading = true;
|
||||
} else if (info.file.status === 'done') {
|
||||
info.file.url = info.file.response?.result;
|
||||
upload.uploadLoading = false;
|
||||
userInfo.value.avatar = info.file.response?.result;
|
||||
updateMeInfo_api(userInfo.value).then(res => {
|
||||
if(res.success) {
|
||||
onlyMessage('上传成功')
|
||||
}
|
||||
})
|
||||
} else if (info.file.status === 'error') {
|
||||
upload.uploadLoading = false;
|
||||
onlyMessage('logo上传失败,请稍后再试', 'error');
|
||||
}
|
||||
const user = useUserInfo();
|
||||
|
||||
type KeyType = 'HomeView' | 'BindThirdAccount' | 'Subscribe' | 'StationMessage';
|
||||
const list: { key: KeyType; title: string }[] = [
|
||||
{
|
||||
key: 'HomeView',
|
||||
title: '首页视图',
|
||||
},
|
||||
beforeUpload: ({ size, type }: File) => {
|
||||
const imageTypes = ['jpg', 'png', 'jfif', 'pjp', 'pjpeg', 'jpeg'];
|
||||
const typeBool =
|
||||
imageTypes.filter((typeStr) => type.includes(typeStr)).length > 0;
|
||||
const sizeBool = size < 4 * 1024 * 1024;
|
||||
|
||||
(typeBool && sizeBool) || message.error('请上传正确格式的图片');
|
||||
return typeBool && sizeBool;
|
||||
{
|
||||
key: 'BindThirdAccount',
|
||||
title: '绑定第三方账号',
|
||||
},
|
||||
});
|
||||
// 首页视图
|
||||
const isApiUser = ref<boolean>();
|
||||
const currentView = ref<string>('');
|
||||
const confirm = () => {
|
||||
setView_api({
|
||||
name: 'view',
|
||||
content: currentView.value,
|
||||
}).then(() => message.success('保存成功'));
|
||||
{
|
||||
key: 'Subscribe',
|
||||
title: '我的订阅',
|
||||
},
|
||||
{
|
||||
key: 'StationMessage',
|
||||
title: '站内信',
|
||||
},
|
||||
];
|
||||
|
||||
const tabs = {
|
||||
HomeView,
|
||||
BindThirdAccount,
|
||||
Subscribe,
|
||||
StationMessage,
|
||||
};
|
||||
|
||||
const activeKey = ref<KeyType>('HomeView');
|
||||
const visible = ref<boolean>(false);
|
||||
const editInfoVisible = ref<boolean>(false);
|
||||
const editPasswordVisible = ref<boolean>(false);
|
||||
init();
|
||||
|
||||
function init() {
|
||||
getUserInfo();
|
||||
isNoCommunity && getSsoBinds();
|
||||
getViews();
|
||||
const onActivated = (_key: KeyType) => {
|
||||
activeKey.value = _key;
|
||||
};
|
||||
|
||||
const onSave = () => {
|
||||
user.getUserInfo()
|
||||
editInfoVisible.value = false
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户信息
|
||||
*/
|
||||
function getUserInfo() {
|
||||
getMeInfo_api().then((resp) => {
|
||||
userInfo.value = resp.result as userInfoType;
|
||||
});
|
||||
}
|
||||
/**
|
||||
* 获取绑定第三方账号
|
||||
*/
|
||||
function getSsoBinds() {
|
||||
getSsoBinds_api().then((resp: any) => {
|
||||
if (resp.status === 200) bindList.value = resp.result;
|
||||
});
|
||||
}
|
||||
/**
|
||||
* 获取首页视图
|
||||
*/
|
||||
function getViews() {
|
||||
// 判断是否是api用户 不是则获取选中的视图
|
||||
getMe_api()
|
||||
.then((resp: any) => {
|
||||
if (resp && resp.status === 200) {
|
||||
isApiUser.value = resp.result.dimensions.find(
|
||||
(item: any) =>
|
||||
item.type === 'api-client' ||
|
||||
item.type.id === 'api-client',
|
||||
);
|
||||
if (!isApiUser.value) return getView_api();
|
||||
}
|
||||
})
|
||||
.then((resp: any) => {
|
||||
if (resp?.status === 200) {
|
||||
if (resp.result) currentView.value = resp.result?.content;
|
||||
else if (resp.result?.username === 'admin') {
|
||||
currentView.value = 'comprehensive';
|
||||
} else currentView.value = 'init';
|
||||
}
|
||||
});
|
||||
const onPasswordSave = () => {
|
||||
editPasswordVisible.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.center-container {
|
||||
background-color: #f0f2f5;
|
||||
min-height: 100vh;
|
||||
.card {
|
||||
margin: 16px 0;
|
||||
padding: 24px;
|
||||
background-color: #fff;
|
||||
position: relative;
|
||||
.person {
|
||||
.person-header {
|
||||
width: 100%;
|
||||
height: 150px;
|
||||
padding: 0 150px;
|
||||
background-color: rgba(2, 125, 180, 0.368);
|
||||
|
||||
h3 {
|
||||
font-size: 22px;
|
||||
|
||||
&::before {
|
||||
display: inline-block;
|
||||
width: 3px;
|
||||
height: 0.7em;
|
||||
content: '';
|
||||
background-color: #2f54eb;
|
||||
margin: 0 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
margin-top: 24px;
|
||||
flex-wrap: wrap;
|
||||
gap: 24px;
|
||||
|
||||
.content-item {
|
||||
margin-right: 24px;
|
||||
|
||||
.default-avatar {
|
||||
background-color: #ccc;
|
||||
color: #fff;
|
||||
border-radius: 50%;
|
||||
font-size: 70px;
|
||||
width: 140px;
|
||||
height: 140px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
.person-header-item {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
.person-header-item-info {
|
||||
padding-top: 30px;
|
||||
display: flex;
|
||||
.person-header-item-info-left {
|
||||
margin-right: 30px;
|
||||
}
|
||||
.info-card {
|
||||
width: 25%;
|
||||
|
||||
:first-child {
|
||||
font-weight: bold;
|
||||
}
|
||||
:last-child {
|
||||
color: #666363d9;
|
||||
}
|
||||
}
|
||||
&.flex-item {
|
||||
.person-header-item-info-right {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
.person-header-item-info-right-top {
|
||||
span {
|
||||
background-color: rgba(
|
||||
255,
|
||||
255,
|
||||
128,
|
||||
0.43137254901960786
|
||||
);
|
||||
border-radius: 5px;
|
||||
padding: 0 10px;
|
||||
}
|
||||
}
|
||||
.person-header-item-info-right-info {
|
||||
color: #fff;
|
||||
display: flex;
|
||||
font-size: 16px;
|
||||
> :not(:last-child) {
|
||||
margin-right: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.edit {
|
||||
.person-header-item-action {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
top: 30px;
|
||||
right: 24px;
|
||||
color: #1d39c4;
|
||||
}
|
||||
|
||||
.account-card {
|
||||
width: 415px;
|
||||
background-image: url(/images/notice/dingtalk-background.png);
|
||||
border-right: 1px solid #f0f0f0;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
z-index: 2;
|
||||
left: 0;
|
||||
bottom: -25px;
|
||||
padding: 0 50px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.choose-view {
|
||||
width: 100%;
|
||||
margin-top: 48px;
|
||||
.view-content {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
.select-item {
|
||||
border: 2px solid transparent;
|
||||
img {
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
.person-header-item-action-left {
|
||||
button {
|
||||
height: 35px;
|
||||
padding: 0 40px;
|
||||
}
|
||||
}
|
||||
|
||||
&.selected {
|
||||
border-color: #10239e;
|
||||
.person-header-item-action-right {
|
||||
button {
|
||||
height: 50px;
|
||||
width: 50px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.btn {
|
||||
display: block;
|
||||
margin: 48px auto;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.person-content {
|
||||
width: 100%;
|
||||
padding: 0 150px;
|
||||
.person-content-item-content {
|
||||
padding: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
|
||||
import { dictType } from '@/views/system/Department/typing';
|
||||
|
||||
export type userInfoType = {
|
||||
avatar: string;
|
||||
createTime: number;
|
||||
email: string;
|
||||
id: string;
|
||||
name: string;
|
||||
orgList: dictType;
|
||||
roleList: dictType;
|
||||
status: number;
|
||||
telephone: string;
|
||||
tenantDisabled: boolean;
|
||||
type: { name: string; id: string };
|
||||
username: string;
|
||||
};
|
|
@ -3922,6 +3922,99 @@ export default [
|
|||
accessSupport: { text: "不支持", value: "unsupported" },
|
||||
supportDataAccess: false
|
||||
},
|
||||
{
|
||||
code: 'system/NoticeRule',
|
||||
name: '通知规则',
|
||||
owner: 'iot',
|
||||
id: 'system/NoticeRule',
|
||||
sortIndex: 11,
|
||||
url: '/system/NoticeRule',
|
||||
icon: 'icon-yingyongguanli',
|
||||
showPage: ['application'],
|
||||
permissions: [],
|
||||
buttons: [
|
||||
{
|
||||
id: 'delete',
|
||||
name: '删除',
|
||||
permissions: [
|
||||
// {
|
||||
// permission: 'application',
|
||||
// actions: ['query', 'delete'],
|
||||
// },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'add',
|
||||
name: '新增',
|
||||
permissions: [
|
||||
// {
|
||||
// permission: 'role',
|
||||
// actions: ['query'],
|
||||
// },
|
||||
// {
|
||||
// permission: 'menu',
|
||||
// actions: ['query'],
|
||||
// },
|
||||
// {
|
||||
// permission: 'application',
|
||||
// actions: ['query', 'save'],
|
||||
// },
|
||||
// {
|
||||
// permission: 'open-api',
|
||||
// actions: ['query', 'save', 'delete'],
|
||||
// },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'update',
|
||||
name: '编辑',
|
||||
permissions: [
|
||||
// {
|
||||
// permission: 'role',
|
||||
// actions: ['query'],
|
||||
// },
|
||||
// {
|
||||
// permission: 'menu',
|
||||
// actions: ['query'],
|
||||
// },
|
||||
// {
|
||||
// permission: 'application',
|
||||
// actions: ['query', 'save'],
|
||||
// },
|
||||
// {
|
||||
// permission: 'open-api',
|
||||
// actions: ['query', 'save', 'delete'],
|
||||
// },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'view',
|
||||
name: '查看',
|
||||
permissions: [
|
||||
// {
|
||||
// permission: 'application',
|
||||
// actions: ['query'],
|
||||
// },
|
||||
// {
|
||||
// permission: 'role',
|
||||
// actions: ['query'],
|
||||
// },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'action',
|
||||
name: '启/禁用',
|
||||
permissions: [
|
||||
// {
|
||||
// permission: 'application',
|
||||
// actions: ['save'],
|
||||
// },
|
||||
],
|
||||
},
|
||||
],
|
||||
accessSupport: { text: "不支持", value: "unsupported" },
|
||||
supportDataAccess: false
|
||||
},
|
||||
// {
|
||||
// code: 'system/License',
|
||||
// name: 'License管理',
|
||||
|
|
|
@ -203,6 +203,7 @@ const next = async () => {
|
|||
const onCancel = () => {
|
||||
emit('cancel');
|
||||
};
|
||||
|
||||
const onOk = async () => {
|
||||
let _data = null
|
||||
if(variable.value.length){
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
<template>
|
||||
<j-modal
|
||||
:width="900"
|
||||
visible
|
||||
title="权限控制"
|
||||
@cancel="emit('close')"
|
||||
@ok="onSave"
|
||||
>
|
||||
<Role v-model="_selectedRowKeys" />
|
||||
</j-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onlyMessage } from '@/utils/comm';
|
||||
import Role from '../Role/index.vue'
|
||||
|
||||
const emit = defineEmits(['close', 'save']);
|
||||
|
||||
const _selectedRowKeys = ref<string[]>([]);
|
||||
const checked = ref<boolean>(false);
|
||||
|
||||
const onSave = () => {
|
||||
if(_selectedRowKeys.value.length) {
|
||||
emit('save', _selectedRowKeys.value);
|
||||
} else {
|
||||
onlyMessage('请配置角色权限', 'error')
|
||||
}
|
||||
};
|
||||
|
||||
</script>
|
|
@ -0,0 +1,25 @@
|
|||
<template>
|
||||
<j-modal :width="700" visible title="配置详情" @cancel="emit('close')">
|
||||
<j-descriptions bordered :column="2">
|
||||
<j-descriptions-item label="通知方式">Cloud Database</j-descriptions-item>
|
||||
<j-descriptions-item label="通知配置">Cloud Database</j-descriptions-item>
|
||||
<j-descriptions-item label="通知模板">Cloud Database</j-descriptions-item>
|
||||
<j-descriptions-item label="通知内容">Cloud Database</j-descriptions-item>
|
||||
<j-descriptions-item label="模板变量">Cloud Database</j-descriptions-item>
|
||||
<j-descriptions-item label="用户权限">Cloud Database</j-descriptions-item>
|
||||
</j-descriptions>
|
||||
<template #footer>
|
||||
<j-button type="primary" @click="emit('close')">确定</j-button>
|
||||
</template>
|
||||
</j-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
});
|
||||
const emit = defineEmits(['close', 'save']);
|
||||
</script>
|
|
@ -0,0 +1,115 @@
|
|||
<template>
|
||||
<pro-search
|
||||
style="padding: 0"
|
||||
type="simple"
|
||||
:columns="columns"
|
||||
target="category"
|
||||
@search="onSearch"
|
||||
/>
|
||||
<j-pro-table
|
||||
ref="tableRef"
|
||||
:columns="columns"
|
||||
:request="queryRoleList"
|
||||
model="TABLE"
|
||||
:params="params"
|
||||
:bodyStyle="{ padding: 0 }"
|
||||
:scroll="{ y: 500 }"
|
||||
:defaultParams="{
|
||||
// pageSize: 10,
|
||||
sorts: [
|
||||
{ name: 'createTime', order: 'desc' },
|
||||
{ name: 'id', order: 'desc' },
|
||||
],
|
||||
}"
|
||||
:rowSelection="{
|
||||
selectedRowKeys: _selectedRowKeys,
|
||||
onSelect: onSelect,
|
||||
onSelectAll: onSelectAll,
|
||||
onSelectNone: cancelSelect,
|
||||
}"
|
||||
>
|
||||
<!-- <template #headerTitle>
|
||||
<j-checkbox v-model:checked="checked">全选</j-checkbox>
|
||||
</template> -->
|
||||
</j-pro-table>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { queryRoleList } from '@/api/system/noticeRule';
|
||||
import { PropType } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: Array as PropType<string[]>,
|
||||
default: () => [],
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:modelValue']);
|
||||
|
||||
const params = ref<any>();
|
||||
const _selectedRowKeys = ref<string[]>([]);
|
||||
|
||||
watchEffect(() => {
|
||||
_selectedRowKeys.value = props?.modelValue || [];
|
||||
});
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: '标识',
|
||||
dataIndex: 'id',
|
||||
key: 'id',
|
||||
ellipsis: true,
|
||||
fixed: 'left',
|
||||
},
|
||||
{
|
||||
title: '名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
ellipsis: true,
|
||||
search: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '说明',
|
||||
key: 'description',
|
||||
ellipsis: true,
|
||||
dataIndex: 'description',
|
||||
},
|
||||
];
|
||||
|
||||
const onSearch = (e: any) => {
|
||||
params.value = e;
|
||||
};
|
||||
|
||||
// 取消全选
|
||||
const cancelSelect = () => {
|
||||
_selectedRowKeys.value = [];
|
||||
};
|
||||
|
||||
const onSelect = (record: any, selected: boolean) => {
|
||||
const _set = new Set(_selectedRowKeys.value);
|
||||
if (selected) {
|
||||
_set.add(record.id);
|
||||
} else {
|
||||
_set.delete(record.id);
|
||||
}
|
||||
emit('update:modelValue', [..._set.values()]);
|
||||
};
|
||||
|
||||
const onSelectAll = (selected: boolean, _: any[], _keys: any[]) => {
|
||||
const _set = new Set(_selectedRowKeys.value);
|
||||
const arr = _keys.map((item: any) => item?.id);
|
||||
if (selected) {
|
||||
arr.map((i: any) => {
|
||||
_set.add(i);
|
||||
});
|
||||
} else {
|
||||
arr.map((i: any) => {
|
||||
_set.delete(i);
|
||||
});
|
||||
}
|
||||
emit('update:modelValue', [..._set.values()]);
|
||||
};
|
||||
</script>
|
|
@ -0,0 +1,194 @@
|
|||
<template>
|
||||
<pro-search
|
||||
:columns="columns"
|
||||
type="simple"
|
||||
target="action-notice-config"
|
||||
@search="handleSearch"
|
||||
class="action-search"
|
||||
/>
|
||||
<div style="height: 400px; overflow-y: auto">
|
||||
<JProTable
|
||||
:columns="columns"
|
||||
:request="query"
|
||||
model="CARD"
|
||||
:bodyStyle="{
|
||||
padding: 0,
|
||||
}"
|
||||
|
||||
:params="params"
|
||||
:gridColumn="2"
|
||||
:rowSelection="{
|
||||
selectedRowKeys: _selectedRowKeys,
|
||||
onChange: onSelectChange,
|
||||
}"
|
||||
>
|
||||
<template #card="slotProps">
|
||||
<CardBox
|
||||
:showStatus="false"
|
||||
:value="slotProps"
|
||||
:showTool="false"
|
||||
:actions="[]"
|
||||
v-bind="slotProps"
|
||||
@click="handleClick"
|
||||
:active="_selectedRowKeys.includes(slotProps.id)"
|
||||
>
|
||||
<template #img>
|
||||
<slot name="img">
|
||||
<img
|
||||
:src="
|
||||
getLogo(slotProps.type, slotProps.provider)
|
||||
"
|
||||
class="notify-logo"
|
||||
/>
|
||||
</slot>
|
||||
</template>
|
||||
<template #content>
|
||||
<Ellipsis style="width: calc(100% - 100px)">
|
||||
<span style="font-size: 16px; font-weight: 600">
|
||||
{{ slotProps.name }}
|
||||
</span>
|
||||
</Ellipsis>
|
||||
<j-row>
|
||||
<j-col :span="12">
|
||||
<div class="card-item-content-text">
|
||||
通知方式
|
||||
</div>
|
||||
<div>
|
||||
{{ getMethodTxt(slotProps.type) }}
|
||||
</div>
|
||||
</j-col>
|
||||
<j-col :span="12">
|
||||
<div class="card-item-content-text">说明</div>
|
||||
<Ellipsis>
|
||||
{{ slotProps.description }}
|
||||
</Ellipsis>
|
||||
</j-col>
|
||||
</j-row>
|
||||
</template>
|
||||
</CardBox>
|
||||
</template>
|
||||
</JProTable>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import ConfigApi from '@/api/notice/config';
|
||||
import { MSG_TYPE, NOTICE_METHOD } from '@/views/notice/const';
|
||||
const props = defineProps({
|
||||
notifyType: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:value', 'change']);
|
||||
|
||||
const getLogo = (type: string, provider: string) => {
|
||||
return MSG_TYPE[type].find((f: any) => f.value === provider)?.logo;
|
||||
};
|
||||
|
||||
const getMethodTxt = (type: string) => {
|
||||
return NOTICE_METHOD.find((f) => f.value === type)?.label;
|
||||
};
|
||||
|
||||
const params = ref<Record<string, any>>({});
|
||||
const _selectedRowKeys = ref<string[]>([]);
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: '名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
search: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'ID',
|
||||
dataIndex: 'id',
|
||||
key: 'id',
|
||||
search: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '说明',
|
||||
dataIndex: 'description',
|
||||
key: 'description',
|
||||
search: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const query = (e: Record<string, any>) =>
|
||||
ConfigApi.list({
|
||||
...e,
|
||||
terms: [
|
||||
...e?.terms,
|
||||
{
|
||||
terms: [
|
||||
{
|
||||
termType: 'eq',
|
||||
column: 'type',
|
||||
value: props.notifyType,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
sorts: [
|
||||
{ name: 'id', value: props.value },
|
||||
{ name: 'createTime', order: 'desc' },
|
||||
],
|
||||
});
|
||||
|
||||
const handleSearch = (_params: any) => {
|
||||
params.value = _params;
|
||||
};
|
||||
|
||||
const onSelectChange = (keys: string[]) => {
|
||||
_selectedRowKeys.value = [...keys];
|
||||
};
|
||||
|
||||
const handleClick = (dt: any) => {
|
||||
if (_selectedRowKeys.value.includes(dt.id)) {
|
||||
_selectedRowKeys.value = [];
|
||||
emit('update:value', undefined);
|
||||
emit('change', { provider: undefined });
|
||||
} else {
|
||||
_selectedRowKeys.value = [dt.id];
|
||||
emit('update:value', dt.id);
|
||||
emit('change', { provider: dt?.provider });
|
||||
}
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.value,
|
||||
(newValue) => {
|
||||
if (newValue) {
|
||||
_selectedRowKeys.value = [newValue];
|
||||
} else {
|
||||
_selectedRowKeys.value = [];
|
||||
}
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
immediate: true,
|
||||
},
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.action-search {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.notify-logo {
|
||||
width: 88px;
|
||||
height: 88px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,196 @@
|
|||
<template>
|
||||
<pro-search
|
||||
:columns="columns"
|
||||
type="simple"
|
||||
target="action-notice-template"
|
||||
@search="handleSearch"
|
||||
class="action-search"
|
||||
/>
|
||||
<div style="height: 400px; overflow-y: auto">
|
||||
<JProTable
|
||||
:columns="columns"
|
||||
:request="(e) => handleData(e)"
|
||||
model="CARD"
|
||||
:bodyStyle="{
|
||||
padding: 0
|
||||
}"
|
||||
:params="params"
|
||||
:gridColumn="2"
|
||||
:noPagination="true"
|
||||
:rowSelection="{
|
||||
selectedRowKeys: _selectedRowKeys,
|
||||
onChange: onSelectChange,
|
||||
}"
|
||||
>
|
||||
<template #card="slotProps">
|
||||
<CardBox
|
||||
:showStatus="false"
|
||||
:value="slotProps"
|
||||
:showTool="false"
|
||||
:actions="[]"
|
||||
v-bind="slotProps"
|
||||
@click="handleClick"
|
||||
:active="_selectedRowKeys.includes(slotProps.id)"
|
||||
>
|
||||
<template #img>
|
||||
<slot name="img">
|
||||
<img
|
||||
:src="
|
||||
getLogo(slotProps.type, slotProps.provider)
|
||||
"
|
||||
class="notify-logo"
|
||||
/>
|
||||
</slot>
|
||||
</template>
|
||||
<template #content>
|
||||
<Ellipsis style="width: calc(100% - 100px)">
|
||||
<span style="font-size: 16px; font-weight: 600">
|
||||
{{ slotProps.name }}
|
||||
</span>
|
||||
</Ellipsis>
|
||||
<j-row>
|
||||
<j-col :span="12">
|
||||
<div class="card-item-content-text">
|
||||
通知方式
|
||||
</div>
|
||||
<div>
|
||||
{{ getMethodTxt(slotProps.type) }}
|
||||
</div>
|
||||
</j-col>
|
||||
<j-col :span="12">
|
||||
<div class="card-item-content-text">说明</div>
|
||||
<Ellipsis>
|
||||
{{ slotProps.description }}
|
||||
</Ellipsis>
|
||||
</j-col>
|
||||
</j-row>
|
||||
</template>
|
||||
</CardBox>
|
||||
</template>
|
||||
</JProTable>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import TemplateApi from '@/api/notice/template';
|
||||
import { MSG_TYPE, NOTICE_METHOD } from '@/views/notice/const';
|
||||
const props = defineProps({
|
||||
notifierId: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:value', 'change', 'update:detail']);
|
||||
|
||||
const getLogo = (type: string, provider: string) => {
|
||||
return MSG_TYPE[type].find((f: any) => f.value === provider)?.logo;
|
||||
};
|
||||
|
||||
const getMethodTxt = (type: string) => {
|
||||
return NOTICE_METHOD.find((f) => f.value === type)?.label;
|
||||
};
|
||||
|
||||
const params = ref<Record<string, any>>({});
|
||||
const _selectedRowKeys = ref<string[]>([]);
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: '名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
search: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'ID',
|
||||
dataIndex: 'id',
|
||||
key: 'id',
|
||||
search: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '说明',
|
||||
dataIndex: 'description',
|
||||
key: 'description',
|
||||
search: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const handleSearch = (_params: any) => {
|
||||
params.value = _params;
|
||||
};
|
||||
|
||||
const handleClick = (dt: any) => {
|
||||
if (_selectedRowKeys.value.includes(dt.id)) {
|
||||
_selectedRowKeys.value = [];
|
||||
emit('update:value', undefined);
|
||||
emit('change', { templateName: undefined });
|
||||
emit('update:detail', undefined);
|
||||
} else {
|
||||
_selectedRowKeys.value = [dt.id];
|
||||
emit('update:value', dt.id);
|
||||
emit('change', { templateName: dt?.name });
|
||||
emit('update:detail', dt);
|
||||
}
|
||||
};
|
||||
|
||||
const onSelectChange = (keys: string[]) => {
|
||||
_selectedRowKeys.value = [...keys];
|
||||
};
|
||||
|
||||
const handleData = async (e: any) => {
|
||||
const sorts = [
|
||||
{ name: 'id', value: props.value },
|
||||
{ name: 'createTime', order: 'desc' },
|
||||
];
|
||||
const resp = await TemplateApi.getListByConfigId(props.notifierId, {
|
||||
...e,
|
||||
sorts: sorts,
|
||||
});
|
||||
return {
|
||||
code: resp.message,
|
||||
result: {
|
||||
data: resp.result ? resp.result : [],
|
||||
pageIndex: 0,
|
||||
pageSize: resp.result.length,
|
||||
total: resp.result.length,
|
||||
},
|
||||
status: resp.status,
|
||||
};
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.value,
|
||||
(newValue) => {
|
||||
if (newValue) {
|
||||
_selectedRowKeys.value = [newValue];
|
||||
} else {
|
||||
_selectedRowKeys.value = [];
|
||||
}
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
immediate: true,
|
||||
},
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.action-search {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.notify-logo {
|
||||
width: 88px;
|
||||
height: 88px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,137 @@
|
|||
<template>
|
||||
<j-spin :spinning="loading">
|
||||
<div class="notify-type-warp" :class="{ disabled: disabled }">
|
||||
<div
|
||||
:key="item.id"
|
||||
v-for="item in options"
|
||||
class="notify-type-item"
|
||||
:class="{ active: notifyType === item.value }"
|
||||
@click="onSelect(item.value)"
|
||||
>
|
||||
<div class="notify-type-item-image">
|
||||
<img :width="106" :src="item.iconUrl" />
|
||||
</div>
|
||||
<div class="notify-type-item-title">{{item.label}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</j-spin>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { getImage } from '@/utils/comm';
|
||||
import notice from '@/api/notice/config';
|
||||
|
||||
const iconMap = new Map();
|
||||
iconMap.set('dingTalk', getImage('/notice/dingtalk.png'));
|
||||
iconMap.set('weixin', getImage('/notice/wechat.png'));
|
||||
iconMap.set('email', getImage('/notice/email.png'));
|
||||
iconMap.set('voice', getImage('/notice/voice.png'));
|
||||
iconMap.set('sms', getImage('/notice/sms.png'));
|
||||
iconMap.set('webhook', getImage('/notice/webhook.png'));
|
||||
|
||||
const props = defineProps({
|
||||
value: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:value', 'change']);
|
||||
|
||||
const loading = ref<boolean>(false);
|
||||
const notifyType = ref('');
|
||||
const options = ref<any[]>([]);
|
||||
|
||||
watch(
|
||||
() => props.value,
|
||||
(newVal) => {
|
||||
notifyType.value = newVal;
|
||||
},
|
||||
{ deep: true, immediate: true },
|
||||
);
|
||||
|
||||
const onSelect = (val: string) => {
|
||||
if (!props.disabled) {
|
||||
emit('update:value', val);
|
||||
emit('change', val);
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
loading.value = true;
|
||||
notice.queryMessageType().then((resp) => {
|
||||
if (resp.status === 200) {
|
||||
options.value = (resp.result as any[]).filter(i => i.id !== 'webhook').map((item) => {
|
||||
return {
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
iconUrl: iconMap.get(item.id),
|
||||
};
|
||||
});
|
||||
}
|
||||
loading.value = false;
|
||||
});
|
||||
notifyType.value = props.value;
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.notify-type-warp {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16px 24px;
|
||||
width: 100%;
|
||||
|
||||
.notify-type-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 172px;
|
||||
border: 1px solid #e0e4e8;
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
|
||||
.notify-type-item-title {
|
||||
margin-bottom: 8px;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.notify-type-item-image {
|
||||
width: 106px;
|
||||
margin: 16px 33px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: @primary-color-hover;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
&.active {
|
||||
border-color: @primary-color-active;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
.notify-type-item {
|
||||
cursor: not-allowed;
|
||||
|
||||
&:hover {
|
||||
color: initial;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
&.active {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
|
@ -0,0 +1,138 @@
|
|||
<template>
|
||||
<j-form
|
||||
v-if="variableDefinitions.length"
|
||||
:layout="'vertical'"
|
||||
ref="formRef"
|
||||
:model="modelRef"
|
||||
>
|
||||
<j-form-item
|
||||
:name="`${item?.id}`"
|
||||
:label="item?.name"
|
||||
v-for="(item) in variableDefinitions"
|
||||
:key="item.id"
|
||||
:required="getType(item) !== 'file' ? true : false"
|
||||
:rules="[
|
||||
{
|
||||
validator: (_rule, value) => checkValue(_rule, value, item),
|
||||
trigger: ['blur', 'change'],
|
||||
},
|
||||
]"
|
||||
>
|
||||
<Tag
|
||||
:notify="notify"
|
||||
v-if="getType(item) === 'tag'"
|
||||
v-model:value="modelRef[item.id]"
|
||||
/>
|
||||
<InputFile
|
||||
v-else-if="getType(item) === 'file'"
|
||||
v-model:value="modelRef[item.id]"
|
||||
/>
|
||||
<j-input
|
||||
v-else-if="getType(item) === 'link'"
|
||||
v-model:value="modelRef[item.id]"
|
||||
/>
|
||||
<BuildIn
|
||||
v-else
|
||||
:item="item"
|
||||
v-model:value="modelRef[item.id]"
|
||||
/>
|
||||
</j-form-item>
|
||||
</j-form>
|
||||
<j-empty v-else style="margin: 20px 0" description="当前模版暂无变量" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import BuildIn from './variableItem/BuildIn.vue';
|
||||
import Tag from './variableItem/Tag.vue';
|
||||
import InputFile from './variableItem/InputFile.vue';
|
||||
import { PropType } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
variableDefinitions: {
|
||||
type: Array as PropType<any>,
|
||||
default: () => [],
|
||||
},
|
||||
value: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
notify: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
template: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
});
|
||||
|
||||
const formRef = ref();
|
||||
|
||||
const modelRef = reactive({});
|
||||
|
||||
watchEffect(() => {
|
||||
Object.assign(modelRef, props?.value);
|
||||
});
|
||||
|
||||
const getType = (item: any) => {
|
||||
return item.expands?.businessType || item.type;
|
||||
};
|
||||
|
||||
const checkValue = (_rule: any, value: any, item: any) => {
|
||||
if(!value){
|
||||
return Promise.resolve();
|
||||
}
|
||||
const type = item.expands?.businessType || item?.type;
|
||||
if (type === 'file') {
|
||||
return Promise.resolve();
|
||||
} else if (type === 'link') {
|
||||
if (!value) {
|
||||
return Promise.reject(new Error('请输入' + item.name));
|
||||
} else if (value.length > 64) {
|
||||
return Promise.reject(new Error('最多64个字符'));
|
||||
}
|
||||
} else if (type === 'tag' && !value) {
|
||||
return Promise.reject(new Error('请选择' + item.name));
|
||||
} else if (['date'].includes(type)) {
|
||||
if (!value) {
|
||||
return Promise.reject(new Error('请选择' + item.name));
|
||||
} else {
|
||||
if (value?.source === 'upper') {
|
||||
if (!value?.upperKey) {
|
||||
return Promise.reject(new Error('请选择' + item.name));
|
||||
} else {
|
||||
return Promise.resolve();
|
||||
}
|
||||
} else {
|
||||
if (!value?.value) {
|
||||
return Promise.reject(new Error('请选择' + item.name));
|
||||
} else {
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (value?.source === 'fixed' && !value?.value) {
|
||||
return Promise.reject(new Error('请输入' + item.name));
|
||||
} else if (
|
||||
value?.source === 'relation' &&
|
||||
!value?.value &&
|
||||
!value?.relation
|
||||
) {
|
||||
return Promise.reject(new Error('请选择' + item.name));
|
||||
} else if (value?.source === 'upper' && !value.upperKey) {
|
||||
return Promise.reject(new Error('请选择' + item.name));
|
||||
}
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
const onSave = () =>
|
||||
new Promise((resolve, reject) => {
|
||||
formRef.value?.validate().then((_data: any) => {
|
||||
resolve(_data);
|
||||
}).catch(() => {
|
||||
reject(false)
|
||||
})
|
||||
});
|
||||
|
||||
defineExpose({ onSave });
|
||||
</script>
|
|
@ -0,0 +1,187 @@
|
|||
<template>
|
||||
<j-input-group compact>
|
||||
<j-select
|
||||
:options="[
|
||||
{ label: '手动输入', value: 'fixed' },
|
||||
{ label: '内置参数', value: 'upper' },
|
||||
]"
|
||||
style="width: 120px"
|
||||
:value="value?.source"
|
||||
@change="sourceChange"
|
||||
/>
|
||||
<template v-if="source === 'upper'">
|
||||
<j-tree-select
|
||||
v-model:value="upperKey"
|
||||
:treeData="builtInList"
|
||||
placeholder="请选择参数"
|
||||
style="width: calc(100% - 120px)"
|
||||
:fieldNames="{ label: 'name', value: 'id' }"
|
||||
@change="(val, label, extra) => itemOnChange(undefined, val, label, extra)"
|
||||
>
|
||||
<template #title="{ fullName, description }">
|
||||
<j-space>
|
||||
{{ fullName }}
|
||||
<span style="color: grey; margin-left: 5px">{{
|
||||
description
|
||||
}}</span>
|
||||
</j-space>
|
||||
</template>
|
||||
</j-tree-select>
|
||||
</template>
|
||||
<template v-else>
|
||||
<j-date-picker
|
||||
:value="value.value"
|
||||
allowClear
|
||||
valueFormat='YYYY-MM-DD HH:mm:ss'
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
style="width: calc(100% - 120px)"
|
||||
v-if="item.type === 'date'"
|
||||
@change="(_, dateString) => itemOnChange(dateString)"
|
||||
/>
|
||||
<j-input-number
|
||||
:value="value.value"
|
||||
allowClear
|
||||
style="width: calc(100% - 120px)"
|
||||
v-else-if="item.type === 'number'"
|
||||
:placeholder="`请输入${item.name}`"
|
||||
@change="itemOnChange"
|
||||
/>
|
||||
<j-input
|
||||
:value="value.value"
|
||||
allowClear
|
||||
style="width: calc(100% - 120px)"
|
||||
v-else
|
||||
:placeholder="`请输入${item.name}`"
|
||||
@change="(e) => itemOnChange(e.target.value)"
|
||||
/>
|
||||
</template>
|
||||
</j-input-group>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup name='NotifyBuildIn'>
|
||||
import { queryBuiltInParams } from '@/api/rule-engine/scene';
|
||||
import { useSceneStore } from '@/store/scene';
|
||||
import { storeToRefs } from 'pinia';
|
||||
|
||||
const sceneStore = useSceneStore();
|
||||
const { data } = storeToRefs(sceneStore);
|
||||
|
||||
const props = defineProps({
|
||||
value: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {
|
||||
source: 'fixed',
|
||||
value: undefined,
|
||||
upperKey: undefined,
|
||||
};
|
||||
},
|
||||
},
|
||||
item: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
name: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:value', 'change']);
|
||||
|
||||
const source = computed(() => {
|
||||
return props.value?.source || 'fixed';
|
||||
});
|
||||
|
||||
const builtInList = ref<any[]>([]);
|
||||
const upperKey = ref(props.value?.upperKey);
|
||||
|
||||
const sourceChange = (val: any) => {
|
||||
emit('update:value', {
|
||||
...props.value,
|
||||
source: val,
|
||||
value: undefined,
|
||||
});
|
||||
};
|
||||
|
||||
const itemOnChange = (val: any, _upperKey?: string, label?: any, extra?: any) => {
|
||||
const item = extra?.triggerNode?.props
|
||||
let othersColumns = ''
|
||||
if (item && item.metadata) {
|
||||
othersColumns = item.column
|
||||
}
|
||||
|
||||
emit('update:value', {
|
||||
...props.value,
|
||||
value: val,
|
||||
upperKey: _upperKey,
|
||||
});
|
||||
|
||||
emit('change', {
|
||||
sendTo: label?.[0] || val,
|
||||
}, othersColumns);
|
||||
};
|
||||
|
||||
const treeDataFilter = (arr: any[], type: string) => {
|
||||
if (Array.isArray(arr) && arr.length) {
|
||||
const list: any[] = [];
|
||||
arr.map((item: any) => {
|
||||
if (item.children) {
|
||||
const children = treeDataFilter(item.children, type);
|
||||
if (children.length) {
|
||||
list.push({
|
||||
...item,
|
||||
title: item.name,
|
||||
value: item.id,
|
||||
disabled: true,
|
||||
children,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (
|
||||
item.type === type ||
|
||||
(type === 'double' &&
|
||||
['int', 'float', 'double', 'long'].includes(item.type))
|
||||
) {
|
||||
list.push(item);
|
||||
}
|
||||
}
|
||||
});
|
||||
return list;
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
watch(
|
||||
() => source.value,
|
||||
(newVal) => {
|
||||
const v = newVal;
|
||||
if (v === 'upper') {
|
||||
const params =
|
||||
props.name - 1 >= 0 ? { action: props.name - 1 } : undefined;
|
||||
queryBuiltInParams(unref(data), params).then((resp) => {
|
||||
if (resp.status === 200) {
|
||||
const arr = treeDataFilter(
|
||||
resp.result as any[],
|
||||
props.item.expands?.businessType || props.item?.type,
|
||||
);
|
||||
builtInList.value = arr;
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
{ deep: true, immediate: true },
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.value.upperKey,
|
||||
(newVal) => {
|
||||
upperKey.value = newVal;
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
</style>
|
|
@ -0,0 +1,77 @@
|
|||
<template>
|
||||
<j-input
|
||||
allowClear
|
||||
placeholder="请上传文件"
|
||||
v-model:value="url"
|
||||
@change="onChange"
|
||||
>
|
||||
<template #addonAfter>
|
||||
<j-upload
|
||||
name="file"
|
||||
:showUploadList="false"
|
||||
:accept="'image/jpeg,image/png'"
|
||||
:disabled="loading"
|
||||
:headers="{
|
||||
[TOKEN_KEY]: LocalStore.get(TOKEN_KEY),
|
||||
}"
|
||||
:action="`${BASE_API_PATH}/file/static`"
|
||||
@change="handleChange"
|
||||
@beforeUpload="handleBeforeUpload"
|
||||
>
|
||||
<j-button type="link" style="height: 30px">
|
||||
<AIcon type="LoadingOutlined" v-if="loading" />
|
||||
<AIcon type="PlusOutlined" v-else />
|
||||
上传附件
|
||||
</j-button>
|
||||
</j-upload>
|
||||
</template>
|
||||
</j-input>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { BASE_API_PATH, TOKEN_KEY } from '@/utils/variable';
|
||||
import { LocalStore, onlyMessage } from '@/utils/comm';
|
||||
|
||||
const props = defineProps({
|
||||
id: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:value']);
|
||||
const url = ref(props.value || undefined);
|
||||
const loading = ref<boolean>(false);
|
||||
|
||||
const handleChange = (info: any) => {
|
||||
if (info.file.status === 'uploading') {
|
||||
loading.value = true;
|
||||
}
|
||||
if (info.file.status === 'done') {
|
||||
info.file.url = info.file.response?.result;
|
||||
loading.value = false;
|
||||
const result = info.file.response?.result;
|
||||
emit('update:value', result);
|
||||
}
|
||||
};
|
||||
|
||||
const handleBeforeUpload = (file: any) => {
|
||||
const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png';
|
||||
if (!isJpgOrPng) {
|
||||
onlyMessage('请上传正确格式图片', 'error');
|
||||
}
|
||||
const isSize = file.size / 1024 / 1024 < 4;
|
||||
if (!isSize) {
|
||||
onlyMessage(`图片大小必须小于4M`, 'error');
|
||||
}
|
||||
return isJpgOrPng && isSize;
|
||||
};
|
||||
|
||||
const onChange = (e: any) => {
|
||||
emit('update:value', e.target.value);
|
||||
};
|
||||
</script>
|
|
@ -0,0 +1,65 @@
|
|||
<template>
|
||||
<j-select
|
||||
style="width: 100%"
|
||||
v-model:value="keys"
|
||||
placeholder="请选择标签"
|
||||
:options="tagsList"
|
||||
@change="onChange"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import TemplateApi from '@/api/notice/template';
|
||||
|
||||
const props = defineProps({
|
||||
notify: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:value', 'change']);
|
||||
|
||||
const tagsList = ref<any[]>([]);
|
||||
const keys = ref<string | undefined>(undefined);
|
||||
|
||||
const getDepartment = async (id: string) => {
|
||||
const resp = await TemplateApi.getTags(id);
|
||||
if (resp.status === 200) {
|
||||
tagsList.value = resp.result.map((item: any) => ({value: item.id, label: item.name}))
|
||||
}
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.value,
|
||||
(newVal) => {
|
||||
keys.value = newVal || undefined
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.notify.notifierId,
|
||||
(newVal) => {
|
||||
if (newVal) {
|
||||
getDepartment(newVal);
|
||||
}
|
||||
},
|
||||
{ deep: true, immediate: true },
|
||||
);
|
||||
|
||||
const onChange = (key: string, option: any) => {
|
||||
emit('update:value', {
|
||||
source: 'fixed',
|
||||
value: key,
|
||||
});
|
||||
emit('change', option ? option?.label : '')
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
</style>
|
|
@ -0,0 +1,151 @@
|
|||
<template>
|
||||
<j-modal
|
||||
:width="900"
|
||||
visible
|
||||
title="配置通知方式"
|
||||
@cancel="emit('close')"
|
||||
@ok="onSave"
|
||||
>
|
||||
<j-steps :current="current" size="small" @change="onChange">
|
||||
<j-step v-for="item in stepList" :title="item" :key="item" />
|
||||
</j-steps>
|
||||
<div style="margin: 20px">
|
||||
<template v-if="current === 0">
|
||||
<NotifyWay v-model:value="formModel.notifyType" />
|
||||
</template>
|
||||
<template v-if="current === 1">
|
||||
<NotifyConfig
|
||||
v-model:value="formModel.notifierId"
|
||||
:notifyType="formModel.notifyType"
|
||||
/>
|
||||
</template>
|
||||
<template v-if="current === 2">
|
||||
<NotifyTemplate
|
||||
v-model:value="formModel.templateId"
|
||||
:notifierId="formModel.notifierId"
|
||||
/>
|
||||
</template>
|
||||
<template v-if="current === 3">
|
||||
<VariableDefinitions
|
||||
:variableDefinitions="_variableDefinitions"
|
||||
:value="formModel.variables"
|
||||
:notify="formModel"
|
||||
ref="variableRef"
|
||||
/>
|
||||
</template>
|
||||
<template v-if="current === 4">
|
||||
<Role v-model="formModel.role" />
|
||||
</template>
|
||||
</div>
|
||||
<template #footer>
|
||||
<j-space>
|
||||
<j-button v-if="current === 0" @click="emit('close')"
|
||||
>取消</j-button
|
||||
>
|
||||
<j-button v-else @click="onPrev">上一步</j-button>
|
||||
<j-button
|
||||
type="primary"
|
||||
@click="onNext"
|
||||
v-if="current !== stepList.length - 1"
|
||||
>下一步</j-button
|
||||
>
|
||||
<j-button type="primary" @click="onSave" v-else>确认</j-button>
|
||||
</j-space>
|
||||
</template>
|
||||
</j-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import NotifyWay from './components/NotifyWay.vue';
|
||||
import NotifyConfig from './components/NotifyConfig.vue';
|
||||
import NotifyTemplate from './components/NotifyTemplate.vue';
|
||||
import VariableDefinitions from './components/VariableDefinitions.vue';
|
||||
import Role from '../Role/index.vue';
|
||||
import { onlyMessage } from '@/utils/comm';
|
||||
import Template from '@/api/notice/template';
|
||||
|
||||
const emit = defineEmits(['close', 'save']);
|
||||
|
||||
const stepList = [
|
||||
'选择通知方式',
|
||||
'选择通知配置',
|
||||
'选择通知模板',
|
||||
'配置模板变量',
|
||||
'配置用户权限',
|
||||
];
|
||||
const current = ref<number>(0);
|
||||
const variable = ref([]);
|
||||
const formModel = reactive({
|
||||
notifyType: '',
|
||||
notifierId: '',
|
||||
templateId: '',
|
||||
variables: undefined,
|
||||
role: [],
|
||||
});
|
||||
const variableRef = ref();
|
||||
|
||||
const _variableDefinitions = computed(() => {
|
||||
const arr = ['user', 'org']
|
||||
return variable.value.filter((item: any) => {
|
||||
const _type = item.expands?.businessType || item.type || ''
|
||||
return !arr.includes(_type)
|
||||
})
|
||||
})
|
||||
|
||||
const jumpStep = async (val: number) => {
|
||||
if (val === 1) {
|
||||
if (formModel.notifyType) {
|
||||
current.value = val;
|
||||
} else {
|
||||
onlyMessage('请选择通知方式', 'error');
|
||||
}
|
||||
} else if (val === 2) {
|
||||
if (formModel.notifierId) {
|
||||
current.value = val;
|
||||
} else {
|
||||
onlyMessage('请选择通知配置', 'error');
|
||||
}
|
||||
} else if (val === 3) {
|
||||
if (formModel.templateId) {
|
||||
const resp = await Template.getTemplateDetail(formModel.templateId);
|
||||
if (resp.status === 200) {
|
||||
variable.value = resp.result?.variableDefinitions || [];
|
||||
current.value = val;
|
||||
}
|
||||
} else {
|
||||
onlyMessage('请选择通知模板', 'error');
|
||||
}
|
||||
} else if (val === 4) {
|
||||
if (_variableDefinitions.value.length) {
|
||||
formModel.variables = await variableRef.value.onSave();
|
||||
if (formModel.variables) {
|
||||
current.value = val;
|
||||
} else {
|
||||
onlyMessage('请配置模版变量', 'error');
|
||||
}
|
||||
} else {
|
||||
current.value = val;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const onPrev = () => {
|
||||
current.value -= 1;
|
||||
};
|
||||
|
||||
const onNext = () => {
|
||||
jumpStep(current.value + 1);
|
||||
};
|
||||
|
||||
const onChange = (cur: number) => {
|
||||
jumpStep(cur);
|
||||
};
|
||||
|
||||
const onSave = () => {
|
||||
if (formModel.role.length) {
|
||||
emit('save');
|
||||
} else {
|
||||
onlyMessage('请配置角色权限', 'error');
|
||||
}
|
||||
};
|
||||
</script>
|
|
@ -0,0 +1,424 @@
|
|||
<template>
|
||||
<page-container>
|
||||
<FullPage>
|
||||
<div style="padding: 24px">
|
||||
<div class="alert">
|
||||
<AIcon type="InfoCircleOutlined" />
|
||||
你可以为每种通知类型配置不同的通知方式与通知模版;
|
||||
</div>
|
||||
<div class="alert">
|
||||
<AIcon type="InfoCircleOutlined" />
|
||||
默认平台中所有用户都能接收到通知,如需限制接收权限可以在配置通知方式时完成,或在通知类型后方的“权限控制”处配置外层权限。
|
||||
</div>
|
||||
<div style="margin-top: 20px">
|
||||
<j-collapse :bordered="false" v-model:activeKey="activeKey">
|
||||
<template #expandIcon="{ isActive }">
|
||||
<AIcon
|
||||
type="CaretRightOutlined"
|
||||
:rotate="isActive ? 90 : 0"
|
||||
/>
|
||||
</template>
|
||||
<j-collapse-panel
|
||||
v-for="item in dataSource"
|
||||
:key="item.id"
|
||||
class="custom"
|
||||
>
|
||||
<template #header
|
||||
><h3>{{ item.name }}</h3></template
|
||||
>
|
||||
<div class="child">
|
||||
<template
|
||||
v-for="child in item.children"
|
||||
:key="child.id"
|
||||
>
|
||||
<div class="child-item">
|
||||
<div class="child-item-left">
|
||||
<div style="font-weight: 600">
|
||||
{{ child.name }}
|
||||
</div>
|
||||
<div
|
||||
class="child-item-left-auth"
|
||||
:class="{
|
||||
active: child.active,
|
||||
}"
|
||||
@click="onAuth(item, child)"
|
||||
>
|
||||
<AIcon type="UserOutlined" />
|
||||
<span>权限控制</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="child-item-right">
|
||||
<MCarousel :data="child.children">
|
||||
<template #card="slotProps">
|
||||
<div class="box-item">
|
||||
<j-dropdown>
|
||||
<div
|
||||
class="box-item-img"
|
||||
>
|
||||
<img
|
||||
style="
|
||||
width: 100%;
|
||||
"
|
||||
:src="
|
||||
getImage(
|
||||
`/notice/${slotProps?.type}.png`,
|
||||
)
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
<template #overlay>
|
||||
<j-menu mode="">
|
||||
<j-menu-item>
|
||||
<PermissionButton
|
||||
@click="
|
||||
onView(
|
||||
item,
|
||||
child,
|
||||
)
|
||||
"
|
||||
type="link"
|
||||
:hasPermission="
|
||||
true
|
||||
"
|
||||
>
|
||||
查看
|
||||
</PermissionButton>
|
||||
</j-menu-item>
|
||||
<j-menu-item>
|
||||
<PermissionButton
|
||||
@click="
|
||||
onEdit(
|
||||
item,
|
||||
child,
|
||||
)
|
||||
"
|
||||
type="link"
|
||||
:hasPermission="
|
||||
true
|
||||
"
|
||||
>
|
||||
编辑
|
||||
</PermissionButton>
|
||||
</j-menu-item>
|
||||
<j-menu-item>
|
||||
<PermissionButton
|
||||
@click="
|
||||
onDelete(
|
||||
item,
|
||||
child,
|
||||
)
|
||||
"
|
||||
danger
|
||||
type="link"
|
||||
:hasPermission="
|
||||
true
|
||||
"
|
||||
>
|
||||
删除
|
||||
</PermissionButton>
|
||||
</j-menu-item>
|
||||
</j-menu>
|
||||
</template>
|
||||
</j-dropdown>
|
||||
<div
|
||||
class="box-item-text"
|
||||
>
|
||||
{{ item.name }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #add>
|
||||
<div class="box-item">
|
||||
<div
|
||||
@click="
|
||||
onAdd(
|
||||
item,
|
||||
child,
|
||||
)
|
||||
"
|
||||
class="box-item-img"
|
||||
>
|
||||
<AIcon
|
||||
style="
|
||||
font-size: 20px;
|
||||
"
|
||||
type="PlusOutlined"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="box-item-text"
|
||||
></div>
|
||||
</div>
|
||||
</template>
|
||||
</MCarousel>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</j-collapse-panel>
|
||||
</j-collapse>
|
||||
</div>
|
||||
</div>
|
||||
</FullPage>
|
||||
<Save v-if="visible" @close="visible = false" @save="onSave" />
|
||||
<Detail v-if="detailVisible" @close="detailVisible = false" />
|
||||
<Auth
|
||||
v-if="authVisible"
|
||||
@close="authVisible = false"
|
||||
@save="onAuthSave"
|
||||
/>
|
||||
</page-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import MCarousel from '@/components/MCarousel/index.vue';
|
||||
import { getImage } from '@/utils/comm';
|
||||
import Save from './components/Save/index.vue';
|
||||
import Detail from './components/Detail/index.vue';
|
||||
import Auth from './components/Auth/index.vue';
|
||||
|
||||
const dataSource = ref([
|
||||
{
|
||||
id: 'a',
|
||||
name: '告警',
|
||||
children: [
|
||||
{
|
||||
id: 'product',
|
||||
name: '产品告警',
|
||||
active: true,
|
||||
children: [
|
||||
{
|
||||
id: 'sms9',
|
||||
name: '站内信',
|
||||
type: 'sms',
|
||||
},
|
||||
{
|
||||
id: 'dingtalk8',
|
||||
name: '钉钉',
|
||||
type: 'dingtalk',
|
||||
},
|
||||
{
|
||||
id: 'wechat7',
|
||||
name: '微信',
|
||||
type: 'wechat',
|
||||
},
|
||||
{
|
||||
id: 'email6',
|
||||
name: '邮箱',
|
||||
type: 'email',
|
||||
},
|
||||
{
|
||||
id: 'dingtalk5',
|
||||
name: '钉钉',
|
||||
type: 'dingtalk',
|
||||
},
|
||||
{
|
||||
id: 'wechat4',
|
||||
name: '微信',
|
||||
type: 'wechat',
|
||||
},
|
||||
{
|
||||
id: 'email3',
|
||||
name: '邮箱',
|
||||
type: 'email',
|
||||
},
|
||||
{
|
||||
id: 'email2',
|
||||
name: '邮箱',
|
||||
type: 'email',
|
||||
},
|
||||
{
|
||||
id: 'email1',
|
||||
name: '邮箱',
|
||||
type: 'email',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'device',
|
||||
name: '设备告警',
|
||||
active: false,
|
||||
children: [
|
||||
{
|
||||
id: 'sms11',
|
||||
name: '站内信',
|
||||
type: 'sms',
|
||||
},
|
||||
{
|
||||
id: 'wechat11',
|
||||
name: '微信',
|
||||
type: 'wechat',
|
||||
},
|
||||
{
|
||||
id: 'voice11',
|
||||
name: '语音',
|
||||
type: 'voice',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'b',
|
||||
name: '系统监控',
|
||||
children: [
|
||||
{
|
||||
id: 'cache',
|
||||
name: '缓冲区数据丢弃',
|
||||
active: false,
|
||||
children: [
|
||||
{
|
||||
id: 'message111',
|
||||
name: '站内信',
|
||||
type: 'sms',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'mqtt',
|
||||
name: 'MQTT并发限制',
|
||||
active: false,
|
||||
children: [
|
||||
{
|
||||
id: 'message22',
|
||||
name: '站内信',
|
||||
type: 'sms',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'c',
|
||||
name: '业务监控',
|
||||
children: [
|
||||
{
|
||||
id: 'error',
|
||||
name: '透传消息解析异常',
|
||||
active: false,
|
||||
children: [
|
||||
{
|
||||
id: 'message333',
|
||||
name: '站内信',
|
||||
type: 'sms',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
const activeKey = ['a', 'b', 'c'];
|
||||
const visible = ref<boolean>(false);
|
||||
const detailVisible = ref<boolean>(false);
|
||||
const authVisible = ref<boolean>(false);
|
||||
const current = reactive({
|
||||
item: {},
|
||||
child: {},
|
||||
});
|
||||
|
||||
const onAdd = (_item: any, _child: any) => {
|
||||
(current.child = _child), (current.item = _item);
|
||||
visible.value = true;
|
||||
};
|
||||
|
||||
const onView = (_item: any, _child: any) => {
|
||||
(current.child = _child), (current.item = _item);
|
||||
detailVisible.value = true;
|
||||
};
|
||||
|
||||
const onEdit = (_item: any, _child: any) => {
|
||||
(current.child = _child), (current.item = _item);
|
||||
visible.value = true;
|
||||
};
|
||||
|
||||
const onDelete = (_item: any, _child: any) => {};
|
||||
|
||||
const onSave = () => {
|
||||
visible.value = false;
|
||||
};
|
||||
|
||||
const onAuth = (_item: any, _child: any) => {
|
||||
(current.child = _child), (current.item = _item);
|
||||
authVisible.value = true;
|
||||
};
|
||||
|
||||
const onAuthSave = (_data: string[]) => {
|
||||
console.log(_data);
|
||||
authVisible.value = false;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.alert {
|
||||
height: 40px;
|
||||
padding-left: 10px;
|
||||
margin-bottom: 10px;
|
||||
color: rgba(0, 0, 0, 0.55);
|
||||
line-height: 40px;
|
||||
background-color: #f6f6f6;
|
||||
}
|
||||
.custom {
|
||||
background: #f7f7f7;
|
||||
border-radius: 4px;
|
||||
border: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
.child {
|
||||
background-color: white;
|
||||
padding: 10px;
|
||||
.child-item {
|
||||
padding: 10px 20px;
|
||||
margin: 5px;
|
||||
background: #f7f7f7;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.child-item-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
div {
|
||||
display: flex;
|
||||
margin-right: 30px;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.child-item-left-auth {
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
color: @primary-color-hover;
|
||||
}
|
||||
&.active {
|
||||
color: @primary-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.child-item-right {
|
||||
display: flex;
|
||||
|
||||
.box-item {
|
||||
margin-left: 10px;
|
||||
.box-item-img {
|
||||
background-color: #fff;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.box-item-text {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
height: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
Loading…
Reference in New Issue