Merge remote-tracking branch 'origin/dev' into dev

This commit is contained in:
XieYongHong 2023-06-29 17:09:07 +08:00
commit 08efcf29eb
45 changed files with 552 additions and 573 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 776 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 710 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 697 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 544 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 656 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 897 B

View File

@ -99,7 +99,7 @@ export const templateDownload = (productId: string, type: string) => server.get(
* @param type
* @returns
*/
export const deviceImport = (productId: string, fileUrl: string, autoDeploy: boolean) => `${BASE_API_PATH}/device-instance/${productId}/import?fileUrl=${fileUrl}&autoDeploy=${autoDeploy}&:X_Access_Token=${LocalStore.get(TOKEN_KEY)}`
export const deviceImport = (productId: string, fileUrl: string, autoDeploy: boolean) => `${BASE_API_PATH}/device-instance/${productId}/import/_withlog?fileUrl=${fileUrl}&autoDeploy=${autoDeploy}&:X_Access_Token=${LocalStore.get(TOKEN_KEY)}`
/**
*

View File

@ -0,0 +1,9 @@
<template>
123
</template>
<script lang="ts" setup>
const props = defineProps({
// options:
})
</script>

View File

@ -1,14 +1,11 @@
<template>
<div class="box">
<div class="box-item" v-if="data.length > showLength">
<div class="box-item" v-if="pageIndex > 0">
<j-button
@click="onLeft"
type="primary"
:disabled="!(pageIndex > 0)"
shape="circle"
class="box-item-action"
><AIcon type="LeftOutlined"
/></j-button>
>+{{ pageIndex * showLength }}</j-button>
</div>
<div class="box-item" v-for="item in getData" :key="item.id">
<slot name="card" v-bind="item"></slot>
@ -16,15 +13,12 @@
<div class="box-item">
<slot name="add"></slot>
</div>
<div class="box-item" v-if="data.length > showLength">
<div class="box-item" v-if="(pageIndex + 1) * showLength < data.length">
<j-button
:disabled="!(pageIndex + showLength < data.length)"
type="primary"
shape="circle"
class="box-item-action"
@click="onRight"
><AIcon type="RightOutlined"
/></j-button>
>+{{ data.length - (pageIndex + 1) * showLength }}</j-button>
</div>
</div>
</template>
@ -43,15 +37,13 @@ const props = defineProps({
},
});
// const emit = defineEmits(['add']);
const pageIndex = ref<number>(0);
const getData = computed(() => {
const start = pageIndex.value >= 0 ? pageIndex.value : 0;
const start = pageIndex.value >= 0 ? pageIndex.value * props.showLength : 0;
const end =
props.showLength + pageIndex.value < props.data.length
? props.showLength + pageIndex.value
(pageIndex.value + 1) * props.showLength < props.data.length
? props.showLength * (pageIndex.value + 1)
: props.data.length;
return props.data.slice(start, end);
});
@ -69,10 +61,6 @@ const onLeft = () => {
pageIndex.value -= 1;
}
};
// const onAdd = () => {
// emit('add');
// };
</script>
<style scoped lang="less">

View File

@ -37,7 +37,7 @@
:src="
iconMap.get(
bindUser?.applicationProvider,
) || getImage('/apply/provider1.png')
) || getImage('/apply/internal-standalone.png')
"
/>
<p>账号{{ bindUser?.result?.userId || '-' }}</p>
@ -153,8 +153,8 @@ interface formData {
const iconMap = new Map()
iconMap.set('dingtalk-ent-app', getImage('/notice/dingtalk.png'))
iconMap.set('wechat-webapp', getImage('/notice/wechat.png'))
iconMap.set('internal-standalone', getImage('/apply/provider1.png'))
iconMap.set('third-party', getImage('/apply/provider5.png'))
iconMap.set('internal-standalone', getImage('/apply/internal-standalone.png'))
iconMap.set('third-party', getImage('/apply/third-party.png'))
const token = computed(() => LocalStore.get(TOKEN_KEY))

View File

@ -2,27 +2,32 @@
<div class="box">
<div class="content">
<template v-if="bindList.length">
<div class="content-item" v-for="item in bindList" :key="item.id">
<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"
:src="
item.logoUrl ||
getImage(bindIcon[item.provider])
"
style="height: 24px; width: 24px"
alt=""
/>
<Ellipsis style="max-width: 200px; font-size: 22px">{{
<Ellipsis style="max-width: 200px; color: #333; margin: 0 8px 0 6px">{{
item?.name
}}</Ellipsis>
<div>
<j-tag v-if="item.bound">已绑定</j-tag>
<j-tag v-else>未绑定</j-tag>
<span v-if="item.bound" style="color: #2BA245">已绑定</span>
<span v-else style="color: #999">未绑定</span>
</div>
<div v-if="item.others?.name">
绑定名{{ item.others?.name }}
<div v-if="item.others?.name" style="color: #666666">
{{ item.others?.name }}已绑定的用户名
</div>
</div>
<div>
<div class="content-item-right">
<j-popconfirm
v-if="item.bound"
title="确认解除绑定嘛?"
@ -30,13 +35,17 @@
>
<j-button>解除绑定</j-button>
</j-popconfirm>
<j-button v-else type="primary" @click="clickBind(item.id)"
<j-button
v-else
ghost
type="primary"
@click="clickBind(item.id)"
>立即绑定</j-button
>
</div>
</div>
</template>
<j-empty v-else style="margin: 200px 0;" />
<j-empty v-else style="margin: 200px 0" />
</div>
</div>
</template>
@ -51,8 +60,8 @@ 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',
'internal-standalone': '/apply/internal-standalone.png',
'third-party': '/apply/third-party.png',
};
const unBind = (id: string) => {
unBind_api(id).then((resp) => {
@ -93,18 +102,19 @@ onMounted(() => {
.box {
display: flex;
justify-content: center;
width: 100%;
.content {
margin-top: 24px;
width: 80%;
width: 100%;
.content-item {
width: 100%;
margin: 10px 0;
margin-bottom: 16px;
padding: 15px;
border: 1px solid #f0f0f0;
display: flex;
justify-content: space-between;
align-items: center;
height: 60px;
border-radius: 6px;
background: #f7f8fa;
.content-item-left {
display: flex;
@ -112,6 +122,13 @@ onMounted(() => {
align-items: center;
}
}
.content-item-right {
button:hover {
background-color: @primary-color;
color: #fff;
}
}
}
}
</style>

View File

@ -1,34 +1,31 @@
<template>
<div class="choose-view">
<j-row class="view-content" :gutter="24">
<j-col
<div class="view-content">
<div
:span="8"
class="select-item"
:class="{ selected: currentView === 'device' }"
@click="currentView = 'device'"
>
<img :src="getImage('/home/device.png')" alt="" />
</j-col>
<j-col
<img :src="getImage(`/home/home-view/device${currentView === 'device' ? '-active' : ''}.png`)" alt="" />
</div>
<div
:span="8"
class="select-item"
:class="{ selected: currentView === 'ops' }"
@click="currentView = 'ops'"
>
<img :src="getImage('/home/ops.png')" alt="" />
</j-col>
<j-col
<img :src="getImage(`/home/home-view/ops${currentView === 'ops' ? '-active' : ''}.png`)" alt="" />
</div>
<div
: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>
<img :src="getImage(`/home/home-view/comprehensive${currentView === 'comprehensive' ? '-active' : ''}.png`)" alt="" />
</div>
</div>
<div class="btn">
<j-button type="primary" @click="confirm">保存修改</j-button>
</div>
</div>
</template>
@ -76,28 +73,26 @@ onMounted(() => {
<style lang="less" scoped>
.choose-view {
width: 100%;
margin-top: 30px;
padding: 48px 150px;
padding: 4% 9% 0 9%;
box-sizing: border-box;
.view-content {
display: flex;
flex-flow: row wrap;
justify-content: space-between;
.select-item {
border: 2px solid transparent;
cursor: pointer;
width: 30%;
img {
width: 100%;
background-size: cover;
}
&.selected {
border-color: #10239e;
}
}
}
.btn {
display: block;
margin: 48px auto;
display: flex;
justify-content: flex-end;
margin-top: 68px;
}
}
</style>

View File

@ -1,88 +1,81 @@
<template>
<page-container>
<div class="notification-record-container">
<pro-search
:columns="columns"
target="category"
style="padding: 0"
@search="queryParams"
/>
<j-pro-table
ref="tableRef"
:columns="columns"
:request="getList_api"
model="TABLE"
:params="queryParams"
:bodyStyle="{ padding: 0 }"
:defaultParams="defaultParams"
>
<template #headerTitle>
<j-popconfirm title="确认全部已读?" @confirm="onAllRead">
<j-button type="primary">全部已读</j-button>
</j-popconfirm>
</template>
<template #topicProvider="slotProps">
{{ slotProps.topicName }}
</template>
<template #notifyTime="slotProps">
{{
dayjs(slotProps.notifyTime).format(
'YYYY-MM-DD HH:mm:ss',
)
}}
</template>
<template #state="slotProps">
<BadgeStatus
:status="slotProps.state.value"
:text="slotProps.state.text"
:statusNames="{
read: 'success',
unread: 'error',
<div class="notification-record-container">
<pro-search
:columns="columns"
:target="type"
style="padding: 0"
@search="(e) => (queryParams = e)"
/>
<j-pro-table
ref="tableRef"
:columns="columns"
:request="getList_api"
model="TABLE"
:params="queryParams"
:bodyStyle="{ padding: 0 }"
:defaultParams="defaultParams"
>
<template #rightExtraRender>
<j-popconfirm title="确认全部已读?" @confirm="onAllRead">
<j-button type="primary">全部已读</j-button>
</j-popconfirm>
</template>
<template #topicProvider="slotProps">
{{ slotProps.topicName }}
</template>
<template #notifyTime="slotProps">
{{ dayjs(slotProps.notifyTime).format('YYYY-MM-DD HH:mm:ss') }}
</template>
<template #state="slotProps">
<BadgeStatus
:status="slotProps.state.value"
:text="slotProps.state.text"
:statusNames="{
read: 'success',
unread: 'error',
}"
></BadgeStatus>
</template>
<template #action="slotProps">
<j-space :size="16">
<PermissionButton
type="link"
:popConfirm="{
title: `确认标为${
slotProps.state.value === 'read'
? '未读'
: '已读'
}`,
onConfirm: () => changeStatus(slotProps),
}"
></BadgeStatus>
</template>
<template #action="slotProps">
<j-space :size="16">
<PermissionButton
type="link"
:popConfirm="{
title: `确认标为${
slotProps.state.value === 'read'
? '未读'
: '已读'
}`,
onConfirm: () => changeStatus(slotProps),
}"
:tooltip="{
title:
slotProps.state.value === 'read'
? '标为未读'
: '标为已读',
}"
>
<AIcon type="icon-a-PIZHU1" />
</PermissionButton>
<PermissionButton
type="link"
:tooltip="{
title: '查看',
}"
@click="view(slotProps)"
>
<AIcon type="SearchOutlined" />
</PermissionButton>
</j-space>
</template>
</j-pro-table>
<ViewDialog
v-if="viewVisible"
v-model:visible="viewVisible"
:data="viewItem"
:type="type"
/>
</div>
</page-container>
:tooltip="{
title:
slotProps.state.value === 'read'
? '标为未读'
: '标为已读',
}"
>
<AIcon type="icon-a-PIZHU1" />
</PermissionButton>
<PermissionButton
type="link"
:tooltip="{
title: '查看',
}"
@click="view(slotProps)"
>
<AIcon type="SearchOutlined" />
</PermissionButton>
</j-space>
</template>
</j-pro-table>
<ViewDialog
v-if="viewVisible"
v-model:visible="viewVisible"
:data="viewItem"
:type="type"
/>
</div>
</template>
<script setup lang="ts" name="NotificationRecord">
@ -91,7 +84,7 @@ import PermissionButton from '@/components/PermissionButton/index.vue';
import {
getList_api,
changeStatus_api,
changeAllStatus
changeAllStatus,
} from '@/api/account/notificationRecord';
import dayjs from 'dayjs';
import { useUserInfo } from '@/store/userInfo';
@ -115,11 +108,11 @@ const getType = computed(() => {
return ['system-event'];
} else {
return [
'alarm',
'alarm-product',
'alarm-device',
'alarm-other',
'alarm-org',
'alarm',
];
}
});
@ -132,16 +125,18 @@ const columns = [
search: {
type: 'select',
options: () =>
getTypeList_api().then((resp: any) =>
resp.result
getTypeList_api().then((resp: any) => {
return resp.result
.map((item: any) => ({
label: item.name,
value: item.id,
}))
.filter((item: any) =>
[...getType.value].includes(item?.value),
),
),
.filter((item: any) => {
return [...getType.value].includes(item?.value)
}).sort((a: any, b: any) => {
return b?.value?.length - a?.value?.length
});
}),
},
scopedSlots: true,
ellipsis: true,
@ -240,18 +235,18 @@ const changeStatus = (row: any) => {
};
watchEffect(() => {
if(user.messageInfo?.id) {
view(user.messageInfo)
if (user.messageInfo?.id) {
view(user.messageInfo);
}
})
});
const onAllRead = async () => {
const resp = await changeAllStatus('_read', getType.value)
if(resp.status === 200){
onlyMessage('操作成功!')
refresh()
const resp = await changeAllStatus('_read', getType.value);
if (resp.status === 200) {
onlyMessage('操作成功!');
refresh();
}
}
};
onMounted(() => {
if (routerParams.params?.value.row) {

View File

@ -1,5 +1,5 @@
<template>
<div style="margin-top: 24px">
<div>
<j-tabs
tab-position="left"
v-if="tabs.length"

View File

@ -1,13 +1,13 @@
<template>
<div class="child-item">
<div class="child-item-left">
<div style="font-weight: 600">
<div style="color: #333333">
{{ data?.name }}
</div>
<div class="child-item-left-auth" v-if="data?.description">
<j-tooltip :title="data.description">
<AIcon
style="font-size: 20px"
style="font-size: 16px"
type="ExclamationCircleOutlined"
/>
</j-tooltip>
@ -19,31 +19,22 @@
<div class="box-item">
<div class="box-item-img">
<j-dropdown placement="top" :trigger="['click']">
<!-- :visible="show?.[slotProps?.id]"
@visibleChange="onVisibleChange(slotProps)" -->
<div>
<div
:class="{
disabled: !notifyChannels?.includes(
slotProps?.id,
),
}"
>
<img
:src="
iconMap.get(
slotProps?.channelProvider,
)
"
style="width: 32px"
/>
<div
:class="{
disabled: !notifyChannels?.includes(
slotProps?.id,
),
}"
></div>
</div>
<!-- v-if="
notifyChannels?.includes(
slotProps?.id,
) &&
slotProps?.channelProvider !==
'inside-mail'
" -->
<template #overlay>
<j-menu>
<j-menu-item
@ -97,13 +88,6 @@
</j-menu>
</template>
</j-dropdown>
<div class="box-item-checked">
<j-checkbox
:checked="
notifyChannels?.includes(slotProps?.id)
"
></j-checkbox>
</div>
</div>
<div class="box-item-text">
{{ slotProps?.name }}
@ -140,12 +124,12 @@ import { useUserInfo } from '@/store/userInfo';
import EditInfo from '../../EditInfo/index.vue';
const iconMap = new Map();
iconMap.set('notifier-dingTalk', getImage('/notice/dingtalk.png'));
iconMap.set('notifier-weixin', getImage('/notice/wechat.png'));
iconMap.set('notifier-email', getImage('/notice/email.png'));
iconMap.set('notifier-voice', getImage('/notice/voice.png'));
iconMap.set('notifier-sms', getImage('/notice/sms.png'));
iconMap.set('inside-mail', getImage('/notice/inside-mail.png'));
iconMap.set('notifier-dingTalk', getImage('/notice-rule/dingtalk.png'));
iconMap.set('notifier-weixin', getImage('/notice-rule/wechat.png'));
iconMap.set('notifier-email', getImage('/notice-rule/email.png'));
iconMap.set('notifier-voice', getImage('/notice-rule/voice.png'));
iconMap.set('notifier-sms', getImage('/notice-rule/sms.png'));
iconMap.set('inside-mail', getImage('/notice-rule/inside-mail.png'));
const current = ref<any>({});
const visible = ref<boolean>(false);
@ -272,17 +256,15 @@ const onSave = () => {
<style lang="less" scoped>
.child-item {
padding: 10px 20px;
margin: 5px;
background: #f7f7f7;
padding: 0 20px;
display: flex;
justify-content: space-between;
align-items: center;
box-shadow: 0px 1px 0px 0px #E2E2E2;
.child-item-left {
display: flex;
align-items: center;
height: 80px;
div {
display: flex;
@ -303,40 +285,29 @@ const onSave = () => {
.box-item {
margin-left: 10px;
.box-item-img {
width: 48px;
height: 48px;
width: 60px;
display: flex;
justify-content: center;
align-items: center;
position: relative;
img {
width: 100%;
z-index: 1;
}
.box-item-checked {
position: absolute;
top: -10px;
right: -10px;
z-index: 3;
}
.disabled {
background-color: rgba(#000, 0.38);
position: absolute;
width: 48px;
height: 48px;
z-index: 2;
top: 0;
left: 0;
filter: grayscale(100%);
// filter: brightness(0);
// opacity: 50%;
}
}
.box-item-text {
width: 100%;
text-align: center;
height: 20px;
color: #666666;
font-size: 12px;
}
}
}

View File

@ -1,13 +1,13 @@
<template>
<j-spin :spinning="loading">
<div style="margin-top: 24px">
<div>
<div class="alert">
<AIcon type="InfoCircleOutlined" />
你可以在该页面选择需要订阅的主题及接收通知的方式
</div>
<div style="margin-top: 20px">
<div class="content-collapse">
<template v-if="dataSource.length">
<j-collapse :bordered="false" v-model:activeKey="activeKey">
<j-collapse :bordered="false" v-model:activeKey="activeKey" expand-icon-position="right">
<template #expandIcon="{ isActive }">
<AIcon
type="CaretRightOutlined"
@ -22,6 +22,7 @@
v-for="item in dataSource"
:key="item.provider"
class="custom"
:header="item.name"
>
<template #header
><h3>{{ item.name }}</h3></template
@ -120,13 +121,19 @@ onMounted(() => {
background-color: #f6f6f6;
}
.custom {
background: #f7f7f7;
border-radius: 4px;
background: #F7F8FA;
border: 0;
overflow: hidden;
color: #333333;
}
.child {
background-color: white;
padding: 10px;
padding-bottom: 24px;
}
.content-collapse {
:deep(.ant-collapse-content > .ant-collapse-content-box) {
padding: 0;
}
}
</style>

View File

@ -154,8 +154,8 @@ const saveImage = (url: string) => {
<style lang="less" scoped>
@border: 1px dashed @border-color-base;
@mask-color: rgba(#000, 0.35);
@with: 66px;
@height: 66px;
@with: 96px;
@height: 96px;
.flex-center() {
align-items: center;

View File

@ -16,73 +16,58 @@
</div>
<div class="person-header-item-info-right">
<div class="person-header-item-info-right-top">
<span>{{ _org }}部门 · {{ _role }}角色</span>
Hi, {{ user.userInfos?.name }}
</div>
<div class="person-header-item-info-right-info">
<div>用户名 {{ user.userInfos?.username }}</div>
<div>账号ID {{ user.userInfos?.id }}</div>
<div>
<j-tag
v-for="i in user.userInfos?.orgList || []"
:key="i?.id"
>{{ i?.name }}</j-tag
>
</div>
<div>
<j-tag
v-for="i in user.userInfos?.roleList || []"
:key="i?.id"
>{{ i?.name }}</j-tag
>
</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="
user.tabKey === item.key
? 'primary'
: 'default'
"
:key="item.key"
>{{ item.title }}</j-button
>
</j-space>
</div>
<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>
<PermissionButton
shape="circle"
v-if="permission"
:tooltip="{
title: '修改密码'
}"
@click="editPasswordVisible = true"
>
<AIcon
style="font-size: 24px"
type="LockOutlined"
/>
</PermissionButton>
</j-space>
</div>
<j-space :size="24">
<j-button class="btn" @click="visible = true"
>查看详情</j-button
>
<j-button @click="editInfoVisible = true"
>编辑资料</j-button
>
<j-button
v-if="permission"
@click="editPasswordVisible = true"
>
修改密码
</j-button>
</j-space>
</div>
</div>
</div>
<div class="person-content">
<div class="person-content-item">
<FullPage>
<div class="person-content-item-content">
<div class="person-content-item-content">
<j-tabs v-model:activeKey="user.tabKey" type="card">
<j-tab-pane
v-for="item in list"
:key="item.key"
:tab="item.title"
/>
</j-tabs>
<j-scrollbar :height="`calc(100% - 51px)`">
<component :is="tabs[user.tabKey]" />
</div>
</FullPage>
</j-scrollbar>
</div>
</div>
</div>
<Detail v-if="visible" @close="visible = false" />
@ -114,9 +99,9 @@ import { updateMeInfo_api } from '@/api/account/center';
import { onlyMessage } from '@/utils/comm';
import { useRouterParams } from '@/utils/hooks/useParams';
import {
USER_CENTER_MENU_BUTTON_CODE,
USER_CENTER_MENU_CODE
} from '@/utils/consts'
USER_CENTER_MENU_BUTTON_CODE,
USER_CENTER_MENU_CODE,
} from '@/utils/consts';
import { usePermissionStore } from '@/store/permission';
const imageTypes = reactive([
@ -164,28 +149,9 @@ const visible = ref<boolean>(false);
const editInfoVisible = ref<boolean>(false);
const editPasswordVisible = ref<boolean>(false);
const hasPermission = usePermissionStore().hasPermission;
const permission = () => hasPermission(`${USER_CENTER_MENU_CODE}:${USER_CENTER_MENU_BUTTON_CODE}`);
const onActivated = (_key: KeyType) => {
user.tabKey = _key;
};
const _org = computed(() => {
return user.userInfos?.orgList
?.map((item: any) => {
return item?.name;
})
.join(',');
});
const _role = computed(() => {
return user.userInfos?.roleList
?.map((item: any) => {
return item?.name;
})
.join(',');
});
const permission = usePermissionStore().hasPermission(
`${USER_CENTER_MENU_CODE}:${USER_CENTER_MENU_BUTTON_CODE}`,
);
const onSave = () => {
user.getUserInfo();
@ -213,21 +179,27 @@ watchEffect(() => {
user.tabKey = router.params.value?.tabKey;
}
});
onUnmounted(() => {
user.tabKey = 'HomeView'
})
</script>
<style lang="less" scoped>
@padding: 14%;
.person {
.person-header {
width: 100%;
height: 150px;
padding: 0 150px;
background-color: rgba(2, 125, 180, 0.368);
height: 156px;
padding: 0 @padding;
background-color: #fff;
.person-header-item {
position: relative;
display: flex;
justify-content: space-between;
align-items: center;
height: 100%;
.person-header-item-info {
padding-top: 30px;
display: flex;
.person-header-item-info-left {
margin-right: 30px;
@ -236,64 +208,52 @@ watchEffect(() => {
.person-header-item-info-right {
display: flex;
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;
}
display: flex;
font-size: 26px;
color: #1d2129;
font-weight: 500;
}
.person-header-item-info-right-info {
color: #fff;
display: flex;
font-size: 16px;
> :not(:last-child) {
margin-right: 20px;
div {
margin: 5px 0;
}
}
}
}
.person-header-item-action {
position: absolute;
width: 100%;
height: 50px;
z-index: 2;
left: 0;
bottom: -25px;
padding: 0 50px;
display: flex;
justify-content: space-between;
align-items: center;
.person-header-item-action-left {
button {
height: 35px;
padding: 0 40px;
}
button {
width: 110px;
background-color: #ebeef4;
color: #333333;
border: none;
}
.person-header-item-action-right {
:deep(button) {
height: 50px;
width: 50px;
}
.btn {
background-color: @primary-color;
color: #fff;
}
}
}
}
.person-content-item {
padding: 10px 20px;
background-color: #fff;
overflow: hidden;
}
.person-content {
width: 100%;
padding: 0 150px;
.person-content-item-content {
padding: 20px;
}
padding: 0 @padding;
margin-top: 15px;
}
.person-content-item-content {
height: calc(100vh - 251px);
width: 100%;
padding: 10px 0;
}
}
</style>

View File

@ -50,7 +50,7 @@
功能说明
</div>
<p>
该功能用于将{{'协议包'}}中的
该功能用于将协议包中的
<b>物模型属性标识</b>
<b>平台物模型属性标识</b>进行映射,当两方属性标识不一致时可在当前页面直接修改映射管理系统将以映射后的物模型属性进行数据处理
</p>

View File

@ -83,6 +83,10 @@ watch(
{ immediate: true, deep: true },
);
const productName = computed(() => {
return productList.value.find(item => item.id === modelRef.product)?.name || ''
})
const handleOk = async () => {
const params = encodeQuery(props.data);
// downloadFile(
@ -97,7 +101,7 @@ const handleOk = async () => {
if (res) {
const blob = new Blob([res], { type: modelRef.fileType });
const url = URL.createObjectURL(blob);
downloadFileByUrl(url, `设备实例`, modelRef.fileType);
downloadFileByUrl(url, `${productName.value ? (productName.value + '下设备') : '设备实例'}`, modelRef.fileType);
emit('close');
}
};

View File

@ -1,70 +1,68 @@
<template>
<div class='file'>
<j-form layout='vertical'>
<!-- <j-form-item label='文件格式' >
<div class='file-type-label'>
<a-radio-group class='file-type-radio' v-model:value="modelRef.file.fileType" >
<a-radio-button value="xlsx">xlsx</a-radio-button>
<a-radio-button value="csv">csv</a-radio-button>
</a-radio-group>
<a-checkbox v-model:checked="modelRef.file.autoDeploy">自动启用</a-checkbox>
<div class="file">
<j-form layout="vertical">
<j-form-item label="下载模板">
<div class="file-download">
<j-button @click="downFile('xlsx')">模板格式.xlsx</j-button>
<j-button @click="downFile('csv')">模板格式.csv</j-button>
</div>
</j-form-item>
<j-form-item label="文件上传">
<!-- 导入系统已存在的设备数据不会更改已存在设备的所属产品信息 -->
<a-upload-dragger
v-model:fileList="modelRef.upload"
name="file"
:action="FILE_UPLOAD"
:headers="{
'X-Access-Token': LocalStore.get(TOKEN_KEY),
}"
:maxCount="1"
:showUploadList="false"
@change="uploadChange"
:accept="
modelRef?.file?.fileType
? `.${modelRef?.file?.fileType}`
: '.xlsx'
"
:before-upload="beforeUpload"
:disabled="disabled"
@drop="handleDrop"
>
<div
style="
height: 115px;
line-height: 115px;
color: #666666;
"
>
<!-- <AIcon style="font-size: 20px;" type="PlusCircleOutlined" /> -->
点击或拖拽上传文件
</div>
</a-upload-dragger>
</j-form-item>
<div style="margin-bottom: 16px">
<j-checkbox v-model:checked="modelRef.file.autoDeploy"
>导入并启用</j-checkbox
>
</div>
</j-form>
<div v-if="importLoading" class="result">
<div v-if="flag">
<j-spin size="small" style="margin-right: 10px" />正在导入
</div>
<div v-else>
<AIcon
style="color: #08e21e; margin-right: 10px; font-size: 16px"
type="CheckCircleOutlined"
/>
</div>
<div>导入成功{{ count }} </div>
<div>
导入失败<span style="color: #ff595e">{{ errCount }}</span>
<a v-if="errMessage && !flag && errCount > 0" style="margin-left: 20px" @click="downError">下载</a>
</div>
</div>
</j-form-item> -->
<j-form-item label='下载模板'>
<div class='file-download'>
<j-button @click="downFile('xlsx')">模板格式.xlsx</j-button>
<j-button @click="downFile('csv')">模板格式.csv</j-button>
</div>
</j-form-item>
<j-row type="flex">
<j-col :flex="600">
<j-form-item label="文件上传">
<a-upload-dragger
v-model:fileList="modelRef.upload"
name="file"
:action="FILE_UPLOAD"
:headers="{
'X-Access-Token': LocalStore.get(TOKEN_KEY),
}"
:maxCount="1"
:showUploadList="false"
@change="uploadChange"
:accept="
modelRef?.file?.fileType ? `.${modelRef?.file?.fileType}` : '.xlsx'
"
:before-upload="beforeUpload"
:disabled='disabled'
@drop="handleDrop"
>
<div style="height: 115px; line-height: 115px;">
<template v-if="!modelRef.upload.length">
点击或拖拽上传文件
</template>
<template v-else>
重传
</template>
</div>
</a-upload-dragger>
</j-form-item>
</j-col>
<j-col flex="auto">
<j-form-item>
<a-checkbox style="margin: 30px 0 0 30px;" v-model:checked="modelRef.file.autoDeploy">导入并启用</a-checkbox>
</j-form-item>
</j-col>
</j-row>
</j-form>
<div v-if="importLoading">
<!-- <j-badge v-if="flag" status="processing" text="正在导入" />
<j-badge v-else status="success" text="导入完成" /> -->
<div v-if="flag">正在导入</div>
<div v-else>导入完成</div>
<!-- <span>总数量{{ count }}</span>
<p style="color: red">{{ errMessage }}</p> -->
<div>导入成功{{ count }} </div>
<div>导入失败{{ count }} <a style="margin-left: 20px;">下载</a></div>
</div>
</div>
</template>
<script setup lang='ts' name='DeviceImportFile'>
@ -72,129 +70,143 @@ import { FILE_UPLOAD } from '@/api/comm';
import { TOKEN_KEY } from '@/utils/variable';
import { LocalStore, onlyMessage } from '@/utils/comm';
import { downloadFileByUrl } from '@/utils/utils';
import {
deviceImport,
templateDownload,
} from '@/api/device/instance';
import { deviceImport, templateDownload } from '@/api/device/instance';
import { EventSourcePolyfill } from 'event-source-polyfill';
import { message } from 'jetlinks-ui-components'
import { message } from 'jetlinks-ui-components';
const props = defineProps({
product: {
type: String,
default: undefined
}
})
product: {
type: String,
default: undefined,
},
});
const modelRef = reactive({
product: props.product,
upload: [],
file: {
fileType: 'xlsx',
autoDeploy: false,
},
product: props.product,
upload: [],
file: {
fileType: 'xlsx',
autoDeploy: false,
},
});
const importLoading = ref<boolean>(false);
const flag = ref<boolean>(false);
const count = ref<number>(0);
const errCount = ref<number>(0);
const errMessage = ref<string>('');
const disabled = ref(false)
const disabled = ref(false);
const downFile = async (type: string) => {
const res: any = await templateDownload(props.product!, type);
if (res) {
const blob = new Blob([res], { type: type });
const url = URL.createObjectURL(blob);
downloadFileByUrl(url, `设备导入模板`, type);
}
const res: any = await templateDownload(props.product!, type);
if (res) {
const blob = new Blob([res], { type: type });
const url = URL.createObjectURL(blob);
downloadFileByUrl(url, `设备导入模板`, type);
}
};
const beforeUpload = (_file: any) => {
const fileType = modelRef.file?.fileType === 'csv' ? 'csv' : 'xlsx';
const isCsv = _file.type === 'text/csv';
const isXlsx = _file.type === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
if (!isCsv && fileType !== 'xlsx') {
onlyMessage('请上传.csv格式文件', 'warning');
}
if (!isXlsx && fileType !== 'csv') {
onlyMessage('请上传.xlsx格式文件', 'warning');
}
return (isCsv && fileType !== 'xlsx') || (isXlsx && fileType !== 'csv');
const fileType = modelRef.file?.fileType === 'csv' ? 'csv' : 'xlsx';
const isCsv = _file.type === 'text/csv';
const isXlsx =
_file.type ===
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
if (!isCsv && fileType !== 'xlsx') {
onlyMessage('请上传.csv格式文件', 'warning');
}
if (!isXlsx && fileType !== 'csv') {
onlyMessage('请上传.xlsx格式文件', 'warning');
}
return (isCsv && fileType !== 'xlsx') || (isXlsx && fileType !== 'csv');
};
const handleDrop = () => {
const handleDrop = () => {};
const downError = () => {
window.open(errMessage.value)
}
const submitData = async (fileUrl: string) => {
if (!!fileUrl) {
count.value = 0;
errMessage.value = '';
const autoDeploy = !!modelRef?.file?.autoDeploy || false;
importLoading.value = true;
let dt = 0;
const source = new EventSourcePolyfill(
deviceImport(props.product!, fileUrl, autoDeploy),
);
source.onmessage = (e: any) => {
const res = JSON.parse(e.data);
if (res.success) {
const temp = res.result.total;
dt += temp;
count.value = dt;
} else {
errMessage.value = res.message || '失败';
}
disabled.value = false
};
source.onerror = (e: { status: number }) => {
if (e.status === 403) errMessage.value = '暂无权限,请联系管理员';
flag.value = false;
disabled.value = false
source.close();
};
source.onopen = () => {};
} else {
message.error('请先上传文件');
}
if (!!fileUrl) {
count.value = 0;
errCount.value = 0;
const autoDeploy = !!modelRef?.file?.autoDeploy || false;
importLoading.value = true;
let dt = 0;
let et = 0;
const source = new EventSourcePolyfill(
deviceImport(props.product!, fileUrl, autoDeploy),
);
source.onmessage = (e: any) => {
const res = JSON.parse(e.data);
if (res.success) {
const temp = res.result.total;
dt += temp;
count.value = dt;
} else {
if (res.detailFile) {
errMessage.value = res.detailFile
} else {
et += 1;
errCount.value = et;
}
}
disabled.value = false;
};
source.onerror = (e: { status: number }) => {
if (e.status === 403) errMessage.value = '暂无权限,请联系管理员';
flag.value = false;
disabled.value = false;
source.close();
};
source.onopen = () => {};
} else {
message.error('请先上传文件');
}
};
const uploadChange = async (info: Record<string, any>) => {
disabled.value = true
// console.log(info.file)
if (info.file.status === 'done') {
const resp: any = info.file.response || { result: '' };
await submitData(resp?.result || '');
}
disabled.value = true;
if (info.file.status === 'done') {
const resp: any = info.file.response || { result: '' };
await submitData(resp?.result || '');
}
};
</script>
<style scoped lang='less'>
.file {
.file-type-label {
display: flex;
gap: 16px;
align-items: center;
.file-type-label {
display: flex;
gap: 16px;
align-items: center;
.file-type-radio {
display: flex;
flex-grow: 1;
.file-type-radio {
display: flex;
flex-grow: 1;
:deep(.ant-radio-button-wrapper) {
width: 50%;
}
:deep(.ant-radio-button-wrapper) {
width: 50%;
}
}
}
}
.file-download {
display: flex;
gap: 16px;
>button {
flex: 1;
min-width: 0;
.file-download {
display: flex;
gap: 16px;
> button {
flex: 1;
min-width: 0;
}
}
.result {
div {
margin: 5px 0;
color: #605e5c;
}
}
}
}
</style>

View File

@ -172,7 +172,7 @@ const saveImage = (url: string) => {
width: @with;
height: @height;
overflow: hidden;
//border-radius: 50%;
border-radius: 5px;
// border: @border;
transition: all 0.3s;
@ -197,9 +197,9 @@ const saveImage = (url: string) => {
flex-direction: column;
width: 100%;
height: 100%;
background-color: rgba(#000, 0.06);
// background-color: rgba(#000, 0.06);
cursor: pointer;
padding: 8px;
// padding: 8px;
.upload-image-mask {
.flex-center();
@ -211,7 +211,7 @@ const saveImage = (url: string) => {
width: 100%;
height: 100%;
color: #fff;
font-size: 16px;
font-size: 14px;
background-color: @mask-color;
}

View File

@ -47,7 +47,7 @@ const props = defineProps({
},
photoUrl: {
type: String,
default: getImage('/apply/provider1.png'),
default: getImage('/apply/internal-standalone.png'),
},
options: {
type: Array as PropType<any[]>,
@ -58,12 +58,12 @@ const props = defineProps({
const emit = defineEmits(['update:value', 'update:photoUrl']);
const defaultImg = {
'internal-standalone': getImage('/apply/provider1.png'),
'internal-integrated': getImage('/apply/provider2.png'),
'wechat-webapp': getImage('/apply/provider4.png'),
'dingtalk-ent-app': getImage('/apply/provider3.png'),
'third-party': getImage('/apply/provider5.png'),
'wechat-miniapp': getImage('/apply/provider1.png'),
'internal-standalone': getImage('/apply/internal-standalone.png'),
'internal-integrated': getImage('/apply/internal-integrated.png'),
'wechat-webapp': getImage('/apply/wechat-webapp.png'),
'dingtalk-ent-app': getImage('/apply/dingtalk-ent-app.png'),
'third-party': getImage('/apply/third-party.png'),
'wechat-miniapp': getImage('/apply/wechat-miniapp.png'),
}
const urlValue = ref<any>({...defaultImg});

View File

@ -1427,7 +1427,7 @@ const loading = ref<boolean>(false);
const initForm: formType = {
name: '',
provider: 'internal-standalone',
logoUrl: getImage('/apply/provider1.png'),
logoUrl: getImage('/apply/internal-standalone.png'),
integrationModes: [],
description: '',
page: {

View File

@ -1,7 +1,7 @@
<template>
<div class="child-item">
<div class="child-item" :class="{ border: !isLast }">
<div class="child-item-left">
<div style="font-weight: 600">
<div style="color: #333333">
{{ data?.name }}
</div>
<div>
@ -14,7 +14,7 @@
</j-tooltip>
</div>
</div>
<div class="child-item-right" v-if="checked">
<div class="child-item-right" :class="{ 'disabled': !checked }">
<MCarousel :data="data?.channels">
<template #card="slotProps">
<div class="box-item">
@ -74,29 +74,22 @@
</div>
</div>
</template>
<template #add>
<div class="box-item">
<div class="box-item-img">
<j-tooltip
:title="!add ? '暂无权限,请联系管理员' : ''"
>
<j-button
:disabled="!add"
type="text"
@click="onAdd"
>
<AIcon
style="font-size: 20px"
type="PlusOutlined"
/>
</j-button>
</j-tooltip>
</div>
<div class="box-item-text"></div>
</div>
</template>
</MCarousel>
<div class="box-item-add">
<div class="box-item-img">
<j-tooltip :title="!add ? '暂无权限,请联系管理员' : ''">
<j-button :disabled="!add" type="text" @click="onAdd">
<AIcon
style="font-size: 20px"
type="PlusOutlined"
/>
</j-button>
</j-tooltip>
</div>
<div class="box-item-text"></div>
</div>
<div class="child-item-right-auth">
<j-tooltip :title="!update ? '暂无权限,请联系管理员' : ''">
<j-button :disabled="!update" type="text" @click="onAuth">
@ -156,6 +149,10 @@ const props = defineProps({
type: Object,
default: () => {},
},
isLast: {
type: Boolean,
default: false,
},
});
const emits = defineEmits(['refresh']);
@ -345,9 +342,7 @@ const onSave = (_data: any) => {
<style lang="less" scoped>
.child-item {
padding: 10px 20px;
margin: 5px;
background: #f7f7f7;
padding: 0 20px;
display: flex;
justify-content: space-between;
align-items: center;
@ -368,14 +363,15 @@ const onSave = (_data: any) => {
.child-item-right {
display: flex;
align-items: center;
.box-item {
margin-left: 10px;
cursor: pointer;
.box-item-img {
background-color: #fff;
width: 48px;
height: 48px;
width: 32px;
height: 32px;
display: flex;
justify-content: center;
align-items: center;
@ -384,10 +380,21 @@ const onSave = (_data: any) => {
.box-item-text {
width: 100%;
text-align: center;
height: 20px;
color: #666666;
font-size: 12px;
}
}
.box-item-add {
cursor: pointer;
margin-left: 16px;
background-color: #F8F9FC;
width: 54px;
height: 54px;
display: flex;
align-items: center;
}
.child-item-right-auth {
margin-left: 20px;
@ -409,6 +416,14 @@ const onSave = (_data: any) => {
}
}
}
&.disabled {
filter: grayscale(100%);
}
}
&.border {
box-shadow: 0px 1px 0px 0px #e2e2e2;
}
}
</style>

View File

@ -1,12 +1,12 @@
import { getImage } from "@/utils/comm";
const iconMap = new Map();
iconMap.set('notifier-dingTalk', getImage('/notice/dingtalk.png'));
iconMap.set('notifier-weixin', getImage('/notice/wechat.png'));
iconMap.set('notifier-email', getImage('/notice/email.png'));
iconMap.set('notifier-voice', getImage('/notice/voice.png'));
iconMap.set('notifier-sms', getImage('/notice/sms.png'));
iconMap.set('inside-mail', getImage('/notice/inside-mail.png'));
iconMap.set('notifier-dingTalk', getImage('/notice-rule/dingtalk.png'));
iconMap.set('notifier-weixin', getImage('/notice-rule/wechat.png'));
iconMap.set('notifier-email', getImage('/notice-rule/email.png'));
iconMap.set('notifier-voice', getImage('/notice-rule/voice.png'));
iconMap.set('notifier-sms', getImage('/notice-rule/sms.png'));
iconMap.set('inside-mail', getImage('/notice-rule/inside-mail.png'));
const noticeType = new Map();
noticeType.set('notifier-dingTalk', 'dingTalk');

View File

@ -9,8 +9,8 @@
</div>
</div>
<div class="content-collapse">
<j-collapse :bordered="false" v-model:activeKey="activeKey">
<template #expandIcon="{ isActive }">
<j-collapse :bordered="false" v-model:activeKey="activeKey" expand-icon-position="right">
<!-- <template #expandIcon="{ isActive }">
<AIcon
type="CaretRightOutlined"
:style="{
@ -19,23 +19,22 @@
}deg)`,
}"
/>
</template>
</template> -->
<j-collapse-panel
v-for="item in dataSource"
:key="item.provider"
class="custom"
:header="item.name"
>
<template #header
><h3>{{ item.name }}</h3></template
>
<div class="child">
<template
v-for="child in item.children"
v-for="(child, index) in item.children"
:key="child.provider"
>
<Item
:data="data.find(i => i?.provider === child?.provider)"
@refresh="onRefresh"
:isLast="index === item.children?.length"
/>
</template>
</div>
@ -156,13 +155,19 @@ onMounted(() => {
background-color: #f6f6f6;
}
.custom {
background: #f7f7f7;
border-radius: 4px;
background: #F7F8FA;
border: 0;
overflow: hidden;
color: #333333;
}
.child {
background-color: white;
padding: 10px;
padding-bottom: 24px;
}
.content-collapse {
:deep(.ant-collapse-content > .ant-collapse-content-box) {
padding: 0;
}
}
</style>

View File

@ -239,12 +239,13 @@ const loading = ref(false);
const bindings = ref<any[]>();
// const basis = ref<any>({});
const defaultImg = getImage('/apply/provider1.png');
const defaultImg = getImage('/apply/internal-standalone.png');
const iconMap = new Map();
iconMap.set('dingtalk-ent-app', getImage('/bind/dingtalk.png'));
iconMap.set('wechat-webapp', getImage('/bind/wechat-webapp.png'));
iconMap.set('internal-standalone', getImage('/apply/provider1.png'));
iconMap.set('third-party', getImage('/apply/provider5.png'));
iconMap.set('internal-standalone', getImage('/apply/internal-standalone.png'));
iconMap.set('third-party', getImage('/apply/third-party.png'));
iconMap.set('wechat-miniapp', getImage('/apply/wechat-miniapp.png'));
const onFinish = async () => {
try {