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

This commit is contained in:
leiqiaochu 2023-03-10 11:00:15 +08:00
commit ff52a91888
51 changed files with 1300 additions and 516 deletions

View File

@ -1,7 +1,7 @@
import server from '@/utils/request' import server from '@/utils/request'
// 获取记录列表 // 获取记录列表
export const getList_api = (data:object): any =>server.get(`/notifications/_query`,encodeParams(data)) export const getList_api = (data:object): any =>server.post(`/notifications/_query`,data)
// 修改记录状态 // 修改记录状态
export const changeStatus_api = (type:'_read'|'_unread',data:string[]): any =>server.post(`/notifications/${type}`,data) export const changeStatus_api = (type:'_read'|'_unread',data:string[]): any =>server.post(`/notifications/${type}`,data)

View File

@ -16,6 +16,9 @@ export default {
// 删除 // 删除
del: (id: string) => server.remove(`/media/channel/${id}`), del: (id: string) => server.remove(`/media/channel/${id}`),
// 查询树形数据
queryTree: (id: string, data?: any) => server.post(`/media/device/${id}/catalog/_query/tree`, data),
// ========== 视频播放 ========== // ========== 视频播放 ==========
// 开始直播 // 开始直播
ptzStart: (deviceId: string, channelId: string, type: string) => ptzStart: (deviceId: string, channelId: string, type: string) =>

View File

@ -77,6 +77,7 @@ const iconKeys = [
'CloudDownloadOutlined', 'CloudDownloadOutlined',
'PauseCircleOutlined',, 'PauseCircleOutlined',,
'FormOutlined', 'FormOutlined',
'EyeInvisibleOutlined',
] ]
const Icon = (props: {type: string}) => { const Icon = (props: {type: string}) => {

View File

@ -15,7 +15,7 @@
<a-table :columns="columns" :data-source="property" :pagination="false" bordered size="small"> <a-table :columns="columns" :data-source="property" :pagination="false" bordered size="small">
<template #bodyCell="{ column, record, index }"> <template #bodyCell="{ column, record, index }">
<template v-if="column.key === 'id'"> <template v-if="column.key === 'id'">
<a-input v-model:value="record.id" size="small"></a-input> <j-auto-complete :options="options" v-model:value="record.id" size="small" width="130px"/>
</template> </template>
<template v-if="column.key === 'current'"> <template v-if="column.key === 'current'">
<a-input v-model:value="record.current" size="small"></a-input> <a-input v-model:value="record.current" size="small"></a-input>
@ -58,8 +58,8 @@
</div> </div>
<div class="log"> <div class="log">
<a-descriptions> <a-descriptions>
<a-descriptions-item v-for="item in ruleEditorStore.state.log" :label="moment(item.time).format('HH:mm:ss')" :key="item.time" <a-descriptions-item v-for="item in ruleEditorStore.state.log" :label="moment(item.time).format('HH:mm:ss')"
:span="3"> :key="item.time" :span="3">
<a-tooltip placement="top" :title="item.content"> <a-tooltip placement="top" :title="item.content">
{{ item.content }} {{ item.content }}
</a-tooltip> </a-tooltip>
@ -78,6 +78,7 @@ import { message } from 'ant-design-vue';
import { useRuleEditorStore } from '@/store/ruleEditor'; import { useRuleEditorStore } from '@/store/ruleEditor';
import moment from 'moment'; import moment from 'moment';
import { getWebSocket } from '@/utils/websocket'; import { getWebSocket } from '@/utils/websocket';
import { PropertyMetadata } from '@/views/device/Product/typings';
const props = defineProps({ const props = defineProps({
@ -135,25 +136,25 @@ const runScript = () => {
}); });
if (ws.value) { if (ws.value) {
ws.value.unsubscribe(); ws.value.unsubscribe?.();
} }
if (!props.virtualRule?.script) { if (!props.virtualRule?.script) {
isBeginning.value = true; isBeginning.value = true;
message.warning('请编辑规则'); message.warning('请编辑规则');
return; return;
} }
ws.value = getWebSocket(`virtual-property-debug-${ruleEditorStore.state.property}-${new Date().getTime()}`, ws.value = getWebSocket(`virtual-property-debug-${props.id}-${new Date().getTime()}`,
'/virtual-property-debug', '/virtual-property-debug',
{ {
virtualId: `${virtualIdRef.value}-virtual-id`, virtualId: `${virtualIdRef.value}-virtual-id`,
property: ruleEditorStore.state.property, property: props.id,
virtualRule: { virtualRule: {
...props.virtualRule, ...props.virtualRule,
}, },
properties: _properties || [], properties: _properties || [],
}) })
ws.value.subscribe((data: any) => { ws.value.subscribe((data: any) => {
ruleEditorStore.state.log.push({ time: new Date().getTime(), content: JSON.stringify(data.payload) }); ruleEditorStore.state.log.push({ time: new Date().getTime(), content: JSON.stringify(data.payload) });
}) })
} }
const beginAction = () => { const beginAction = () => {
@ -163,7 +164,7 @@ const beginAction = () => {
const stopAction = () => { const stopAction = () => {
isBeginning.value = true; isBeginning.value = true;
if (ws.value) { if (ws.value) {
ws.value.unsubscribe(); ws.value.unsubscribe?.();
} }
} }
const clearAction = () => { const clearAction = () => {
@ -172,9 +173,21 @@ const clearAction = () => {
onUnmounted(() => { onUnmounted(() => {
if (ws.value) { if (ws.value) {
ws.value.unsubscribe(); ws.value.unsubscribe?.();
} }
}) })
const options = ref<{ label: string, value: string }[]>()
const getProperty = () => {
const metadata = productStore.current.metadata || '{}';
const _p: PropertyMetadata[] = JSON.parse(metadata).properties || [];
options.value = _p.filter((p) => p.id !== props.id).map((item) => ({
label: item.name,
value: item.id,
}));
console.log(options.value)
}
getProperty()
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
.debug-container { .debug-container {

View File

@ -1,6 +1,5 @@
<template> <template>
<Editor key="simple" @change="change" v-model:value="_value" :id="id" /> <Editor key="simple" @change="change" v-model:value="_value" :id="id" />
{{ _value }}
<Advance v-if="ruleEditorStore.state.model === 'advance'" v-model:value="_value" :model="ruleEditorStore.state.model" <Advance v-if="ruleEditorStore.state.model === 'advance'" v-model:value="_value" :model="ruleEditorStore.state.model"
:virtualRule="virtualRule" :id="id" @change="change" /> :virtualRule="virtualRule" :id="id" @change="change" />
</template> </template>

View File

@ -26,7 +26,7 @@ import { notification } from 'ant-design-vue';
import { changeStatus_api } from '@/api/account/notificationRecord'; import { changeStatus_api } from '@/api/account/notificationRecord';
import { useUserInfo } from '@/store/userInfo'; import { useUserInfo } from '@/store/userInfo';
const updateCount = computed(()=>useUserInfo().$state.alarmUpdateCount); const updateCount = computed(() => useUserInfo().$state.alarmUpdateCount);
const total = ref(0); const total = ref(0);
const list = ref<any[]>([]); const list = ref<any[]>([]);
@ -50,10 +50,20 @@ const subscribeNotice = () => {
const getList = () => { const getList = () => {
loading.value = true; loading.value = true;
const params = { const params = {
'terms[0].column': 'state',
'terms[0].value': 'unread',
'sorts[0].name': 'notifyTime', 'sorts[0].name': 'notifyTime',
'sorts[0].order': 'desc', 'sorts[0].order': 'desc',
terms: [
{
terms: [
{
type: 'or',
value: 'unread',
termType: 'eq',
column: 'state',
},
],
},
],
}; };
getList_api(params) getList_api(params)
.then((resp: any) => { .then((resp: any) => {

View File

@ -1,6 +1,6 @@
<template> <template>
<j-form-item :name="name.concat(['script'])"> <j-form-item :name="name.concat(['script'])">
<f-rule-editor v-model:value="value.script" :id="id"></f-rule-editor> <f-rule-editor v-model:value="value.script" :id="id" :virtualRule="value"></f-rule-editor>
</j-form-item> </j-form-item>
<template v-if="showWindow"> <template v-if="showWindow">
<j-form-item label="规则配置" :name="name.concat(['isVirtualRule'])"> <j-form-item label="规则配置" :name="name.concat(['isVirtualRule'])">

View File

@ -5,6 +5,7 @@
@ok="handleOk" @ok="handleOk"
width="770px" width="770px"
@cancel="emits('update:visible', false)" @cancel="emits('update:visible', false)"
:confirmLoading="loading"
> >
<j-form :model="form" layout="vertical" ref="formRef"> <j-form :model="form" layout="vertical" ref="formRef">
<j-row :gutter="24"> <j-row :gutter="24">
@ -12,7 +13,10 @@
<j-form-item <j-form-item
label="姓名" label="姓名"
name="name" name="name"
:rules="[{ required: true, message: '姓名必填' }]" :rules="[
{ required: true, message: '姓名必填' },
{ max: 64, message: '最多可输入64个字符' },
]"
> >
<j-input <j-input
v-model:value="form.name" v-model:value="form.name"
@ -56,7 +60,16 @@
</j-row> </j-row>
<j-row :gutter="24"> <j-row :gutter="24">
<j-col :span="12"> <j-col :span="12">
<j-form-item label="手机号"> <j-form-item
label="手机号"
name="telephone"
:rules="[
{
pattern: /^1[3456789]\d{9}$/,
message: '请输入正确手机号',
},
]"
>
<j-input <j-input
v-model:value="form.telephone" v-model:value="form.telephone"
placeholder="请输入手机号" placeholder="请输入手机号"
@ -64,7 +77,11 @@
</j-form-item> </j-form-item>
</j-col> </j-col>
<j-col :span="12"> <j-col :span="12">
<j-form-item label="邮箱"> <j-form-item
label="邮箱"
name="email"
:rules="[{ type: 'email',message:'邮箱不是一个有效的email' }]"
>
<j-input <j-input
v-model:value="form.email" v-model:value="form.email"
placeholder="请输入邮箱" placeholder="请输入邮箱"
@ -87,17 +104,19 @@ const props = defineProps<{
visible: boolean; visible: boolean;
data: userInfoType; data: userInfoType;
}>(); }>();
const loading = ref(false)
const form = ref(props.data); const form = ref(props.data);
const formRef = ref<FormInstance>(); const formRef = ref<FormInstance>();
const handleOk = () => { const handleOk = () => {
formRef.value?.validate().then(() => { formRef.value?.validate().then(() => {
loading.value = true
updateMeInfo_api(form.value).then((resp) => { updateMeInfo_api(form.value).then((resp) => {
if (resp.status === 200) { if (resp.status === 200) {
message.success('保存成功'); message.success('保存成功');
emits('ok'); emits('ok');
emits('update:visible', false); emits('update:visible', false);
} }
}); }).finally(()=>loading.value = false)
}); });
}; };
</script> </script>

View File

@ -4,6 +4,7 @@
title="重置密码" title="重置密码"
@ok="handleOk" @ok="handleOk"
width="520px" width="520px"
:confirmLoading="loading"
@cancel="emits('update:visible', false)" @cancel="emits('update:visible', false)"
> >
<j-form :model="form" layout="vertical" ref="formRef"> <j-form :model="form" layout="vertical" ref="formRef">
@ -11,7 +12,7 @@
label="旧密码" label="旧密码"
name="oldPassword" name="oldPassword"
:rules="[ :rules="[
{ required: true }, { required: true, message: '请输入密码' },
{ validator: checkMothods.old, trigger: 'blur' }, { validator: checkMothods.old, trigger: 'blur' },
]" ]"
> >
@ -24,7 +25,7 @@
label="密码" label="密码"
name="newPassword" name="newPassword"
:rules="[ :rules="[
{ required: true }, { required: true,message:'请输入密码' },
{ validator: checkMothods.new, trigger: 'blur' }, { validator: checkMothods.new, trigger: 'blur' },
]" ]"
> >
@ -37,7 +38,7 @@
label="确认密码" label="确认密码"
name="confirmPassword" name="confirmPassword"
:rules="[ :rules="[
{ required: true }, { required: true, message: '请输入确认密码' },
{ validator: checkMothods.confirm, trigger: 'blur' }, { validator: checkMothods.confirm, trigger: 'blur' },
]" ]"
> >
@ -63,6 +64,7 @@ const emits = defineEmits(['ok', 'update:visible']);
const props = defineProps<{ const props = defineProps<{
visible: boolean; visible: boolean;
}>(); }>();
const loading = ref(false)
const formRef = ref<FormInstance>(); const formRef = ref<FormInstance>();
const form = ref<formType>({ const form = ref<formType>({
oldPassword: '', oldPassword: '',
@ -72,7 +74,7 @@ const form = ref<formType>({
const checkMothods = { const checkMothods = {
old: async (_rule: Rule, value: string) => { old: async (_rule: Rule, value: string) => {
if (!value) return Promise.reject('请输入密码'); if (!value) return Promise.reject();
try { try {
const resp: any = await checkOldPassword_api(value); const resp: any = await checkOldPassword_api(value);
if (resp.status === 200 && !resp.result.passed) if (resp.status === 200 && !resp.result.passed)
@ -83,7 +85,7 @@ const checkMothods = {
} }
}, },
new: async (_rule: Rule, value: string) => { new: async (_rule: Rule, value: string) => {
if (!value) return Promise.reject('请输入密码'); if (!value) return Promise.reject();
else if ( else if (
form.value.confirmPassword && form.value.confirmPassword &&
value !== form.value.confirmPassword value !== form.value.confirmPassword
@ -99,7 +101,7 @@ const checkMothods = {
} }
}, },
confirm: async (_rule: Rule, value: string) => { confirm: async (_rule: Rule, value: string) => {
if (!value) return Promise.reject('请输入确认密码'); if (!value) return Promise.reject();
try { try {
const resp: any = await validateField_api('password', value); const resp: any = await validateField_api('password', value);
@ -114,6 +116,7 @@ const checkMothods = {
const handleOk = () => { const handleOk = () => {
formRef.value?.validate().then(() => { formRef.value?.validate().then(() => {
loading.value = true
const params = { const params = {
oldPassword: form.value.oldPassword, oldPassword: form.value.oldPassword,
newPassword: form.value.newPassword, newPassword: form.value.newPassword,
@ -124,7 +127,7 @@ const handleOk = () => {
emits('ok'); emits('ok');
emits('update:visible', false); emits('update:visible', false);
} }
}); }).finally(()=>loading.value = false)
}); });
}; };
console.clear(); console.clear();

View File

@ -8,10 +8,15 @@
style="width: 350px; justify-content: center" style="width: 350px; justify-content: center"
> >
<img <img
v-if="userInfo.avatar"
:src="userInfo.avatar" :src="userInfo.avatar"
style="width: 140px; border-radius: 70px" style="width: 140px; border-radius: 70px"
alt="" alt=""
/> />
<div class="default-avatar" v-else>
<AIcon type="UserOutlined" />
</div>
<div <div
style=" style="
width: 100%; width: 100%;
@ -29,6 +34,7 @@
}" }"
:action="`${BASE_API_PATH}/file/static`" :action="`${BASE_API_PATH}/file/static`"
@change="upload.changeBackUpload" @change="upload.changeBackUpload"
:beforeUpload="upload.beforeUpload"
> >
<j-button> <j-button>
<AIcon type="UploadOutlined" /> <AIcon type="UploadOutlined" />
@ -51,11 +57,17 @@
</div> </div>
<div class="info-card"> <div class="info-card">
<p>注册时间</p> <p>注册时间</p>
<p>{{ moment(userInfo.createTime).format('YYYY-MM-DD HH:mm:ss') }}</p> <p>
{{
moment(userInfo.createTime).format(
'YYYY-MM-DD HH:mm:ss',
)
}}
</p>
</div> </div>
<div class="info-card"> <div class="info-card">
<p>电话</p> <p>电话</p>
<p>{{ userInfo.telephone }}</p> <p>{{ userInfo.telephone || '-' }}</p>
</div> </div>
<div class="info-card"> <div class="info-card">
<p>姓名</p> <p>姓名</p>
@ -117,7 +129,7 @@
type="link" type="link"
@click="editPasswordVisible = true" @click="editPasswordVisible = true"
> >
<AIcon type="EditOutlined" style="color: #1d39c4;" /> <AIcon type="EditOutlined" style="color: #1d39c4" />
</PermissionButton> </PermissionButton>
</span> </span>
</div> </div>
@ -205,7 +217,7 @@
<EditInfoDialog <EditInfoDialog
v-if="editInfoVisible" v-if="editInfoVisible"
v-model:visible="editInfoVisible" v-model:visible="editInfoVisible"
:data="{...userInfo}" :data="{ ...userInfo }"
@ok="getUserInfo" @ok="getUserInfo"
/> />
<EditPasswordDialog <EditPasswordDialog
@ -277,6 +289,15 @@ const upload = reactive({
message.error('logo上传失败请稍后再试'); message.error('logo上传失败请稍后再试');
} }
}, },
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 isApiUser = ref<boolean>();
@ -346,7 +367,7 @@ function getViews() {
background-color: #f0f2f5; background-color: #f0f2f5;
min-height: 100vh; min-height: 100vh;
.card { .card {
margin: 24px; margin: 16px 0;
padding: 24px; padding: 24px;
background-color: #fff; background-color: #fff;
position: relative; position: relative;
@ -370,6 +391,18 @@ function getViews() {
flex-wrap: wrap; flex-wrap: wrap;
.content-item { .content-item {
margin-right: 24px; 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 { .info-card {
width: 25%; width: 25%;

View File

@ -1,14 +1,17 @@
<template> <template>
<page-container> <page-container>
<div class="notification-record-container"> <div class="notification-record-container">
<Search :columns="columns" @search="query.search" /> <j-advanced-search
:columns="columns"
@search="(params:any)=>queryParams = {...params}"
/>
<j-pro-table <j-pro-table
ref="tableRef" ref="tableRef"
:columns="columns" :columns="columns"
:request="getList_api" :request="getList_api"
model="TABLE" model="TABLE"
:params="query.params.value" :params="queryParams"
:defaultParams="{ :defaultParams="{
'sorts[0].name': 'notifyTime', 'sorts[0].name': 'notifyTime',
'sorts[0].order': 'desc', 'sorts[0].order': 'desc',
@ -52,8 +55,8 @@
? '标为未读' ? '标为未读'
: '标为已读', : '标为已读',
}" }"
>1 >
<AIcon type="ReadIconOutlined" /> <AIcon type="icon-a-PIZHU1" />
</PermissionButton> </PermissionButton>
<PermissionButton <PermissionButton
type="link" type="link"
@ -158,15 +161,10 @@ const columns = [
key: 'action', key: 'action',
ellipsis: true, ellipsis: true,
scopedSlots: true, scopedSlots: true,
width:'200px' width: '200px',
}, },
]; ];
const query = { const queryParams = ref({});
params: ref({}),
search: (params: object) => {
query.params.value = { ...params };
},
};
const tableRef = ref(); const tableRef = ref();
const table = { const table = {

View File

@ -3,6 +3,7 @@
visible visible
:title="props.data.id ? '编辑' : '新增'" :title="props.data.id ? '编辑' : '新增'"
width="865px" width="865px"
:confirmLoading="loading"
@ok="confirm" @ok="confirm"
@cancel="emits('update:visible', false)" @cancel="emits('update:visible', false)"
> >
@ -92,6 +93,7 @@ const props = defineProps<{
data: rowType; data: rowType;
}>(); }>();
const loading = ref(false);
const initForm = { const initForm = {
subscribeName: '', subscribeName: '',
topicConfig: {}, topicConfig: {},
@ -106,13 +108,16 @@ const form = ref({
const confirm = () => { const confirm = () => {
formRef.value && formRef.value &&
formRef.value.validate().then(() => { formRef.value.validate().then(() => {
save_api(form.value).then((resp) => { loading.value = true;
if (resp.status === 200) { save_api(form.value)
message.success('操作成功'); .then((resp) => {
emits('ok') if (resp.status === 200) {
emits('update:visible', false); message.success('操作成功');
} emits('ok');
}); emits('update:visible', false);
}
})
.finally(() => (loading.value = false));
}); });
}; };

View File

@ -1,13 +1,16 @@
<template> <template>
<page-container> <page-container>
<div class="notification-subscription-container"> <div class="notification-subscription-container">
<Search :columns="columns" @search="query.search" /> <j-advanced-search
:columns="columns"
@search="(params:any)=>queryParams = {...params}"
/>
<j-pro-table <j-pro-table
ref="tableRef" ref="tableRef"
:columns="columns" :columns="columns"
:request="getNoticeList_api" :request="getNoticeList_api"
model="TABLE" model="TABLE"
:params="query.params.value" :params="queryParams"
:defaultParams="{ :defaultParams="{
sorts: [{ name: 'notifyTime', order: 'desc' }], sorts: [{ name: 'notifyTime', order: 'desc' }],
}" }"
@ -105,7 +108,7 @@ import EditDialog from './components/EditDialog.vue';
import { import {
getNoticeList_api, getNoticeList_api,
changeStatus_api, changeStatus_api,
remove_api remove_api,
} from '@/api/account/notificationSubscription'; } from '@/api/account/notificationSubscription';
import { rowType } from './typing'; import { rowType } from './typing';
import { message } from 'ant-design-vue'; import { message } from 'ant-design-vue';
@ -147,19 +150,14 @@ const columns = [
key: 'action', key: 'action',
ellipsis: true, ellipsis: true,
scopedSlots: true, scopedSlots: true,
width: '200px' width: '200px',
}, },
]; ];
const query = { const queryParams = ref({});
params: ref({}),
search: (params: object) => {
query.params.value = {...params};
},
};
const dialogVisible = ref<boolean>(false); const dialogVisible = ref<boolean>(false);
const tableRef = ref(); const tableRef = ref();
const table = { const table = {
seletctRow: ref<rowType>(), seletctRow: ref<any>({}),
edit: (row?: rowType) => { edit: (row?: rowType) => {
table.seletctRow = { table.seletctRow = {
...(row || ({} as any)), ...(row || ({} as any)),
@ -176,12 +174,12 @@ const table = {
}); });
}, },
delete: (row: rowType) => { delete: (row: rowType) => {
remove_api(row.id as string).then(resp=>{ remove_api(row.id as string).then((resp) => {
if(resp.status === 200) { if (resp.status === 200) {
message.success('操作成功!') message.success('操作成功!');
table.refresh() table.refresh();
}else message.warning('操作失败!') } else message.warning('操作失败!');
}) });
}, },
refresh: () => { refresh: () => {
tableRef.value && tableRef.value.reload(); tableRef.value && tableRef.value.reload();

View File

@ -86,7 +86,7 @@ const metadataStore = useMetadataStore()
onMounted(() => { onMounted(() => {
if (props.type === 'product' || !props.value?.source) { if (props.type === 'product' || !props.value?.source) {
emit('update:value', { ...props.value, source: 'device' }) emit('update:value', { source: 'device', ...props.value })
} }
}) })

View File

@ -1,9 +1,9 @@
<template> <template>
<j-pro-table :loading="loading" :data-source="data" size="small" :columns="columns" row-key="id" model="TABLE"> <div class="table-header">
<template #headerTitle> <div>
<j-input-search v-model:value="searchValue" placeholder="请输入名称" @search="handleSearch"></j-input-search> <j-input-search v-model:value="searchValue" placeholder="请输入名称" @search="handleSearch"></j-input-search>
</template> </div>
<template #rightExtraRender> <div>
<PermissionButton type="primary" :uhas-permission="`${permission}:update`" key="add" @click="handleAddClick" <PermissionButton type="primary" :uhas-permission="`${permission}:update`" key="add" @click="handleAddClick"
:disabled="operateLimits('add', type)" :tooltip="{ :disabled="operateLimits('add', type)" :tooltip="{
title: operateLimits('add', type) ? '当前的存储方式不支持新增' : '新增', title: operateLimits('add', type) ? '当前的存储方式不支持新增' : '新增',
@ -14,45 +14,52 @@
新增 新增
</PermissionButton> </PermissionButton>
<Edit v-if="metadataStore.model.edit" :type="target" :tabs="type" @refresh="refreshMetadata"></Edit> <Edit v-if="metadataStore.model.edit" :type="target" :tabs="type" @refresh="refreshMetadata"></Edit>
</div>
</div>
<a-table :loading="loading" :data-source="data" :columns="columns" row-key="id" model="TABLE" size="small"
:pagination="pagination">
<template #bodyCell="{ column, record }">
<template v-if="column.dataIndex === 'level'">
{{ levelMap[record.expands?.level] || '-' }}
</template>
<template v-if="column.dataIndex === 'async'">
{{ record.async ? '是' : '否' }}
</template>
<template v-if="column.dataIndex === 'valueType'">
{{ record.valueType?.type }}
</template>
<template v-if="column.dataIndex === 'source'">
{{ sourceMap[record.expands?.source] }}
</template>
<template v-if="column.dataIndex === 'type'">
<j-tag v-for="item in (record.expands?.type || [])" :key="item">
{{ expandsType[item] }}
</j-tag>
</template>
<template v-if="column.dataIndex === 'action'">
<j-space>
<PermissionButton :uhas-permission="`${permission}:update`" type="link" key="edit" style="padding: 0"
:udisabled="operateLimits('updata', type)" @click="handleEditClick(record)" :tooltip="{
title: operateLimits('updata', type) ? '当前的存储方式不支持编辑' : '编辑',
}">
<AIcon type="EditOutlined" />
</PermissionButton>
<PermissionButton :uhas-permission="`${permission}:delete`" type="link" key="delete" style="padding: 0"
:pop-confirm="{
title: '确认删除?', onConfirm: async () => {
await removeItem(record);
},
}"
:tooltip="{
title: '删除',
}"
>
<AIcon type="DeleteOutlined" />
</PermissionButton>
</j-space>
</template>
</template> </template>
<template #level="slotProps"> </a-table>
{{ levelMap[slotProps.expands?.level] || '-' }}
</template>
<template #async="slotProps">
{{ slotProps.async ? '是' : '否' }}
</template>
<template #valueType="slotProps">
{{ slotProps.valueType?.type }}
</template>
<template #source="slotProps">
{{ sourceMap[slotProps.expands?.source] }}
</template>
<template #type="slotProps">
<j-tag v-for="item in (slotProps.expands?.type || [])" :key="item">
{{ expandsType[item] }}
</j-tag>
</template>
<template #action="slotProps">
<j-space>
<PermissionButton :uhas-permission="`${permission}:update`" type="link" key="edit" style="padding: 0"
:udisabled="operateLimits('updata', type)" @click="handleEditClick(slotProps)" :tooltip="{
title: operateLimits('updata', type) ? '当前的存储方式不支持编辑' : '编辑',
}">
<AIcon type="EditOutlined" />
</PermissionButton>
<PermissionButton :uhas-permission="`${permission}:delete`" type="link" key="delete" style="padding: 0"
:pop-confirm="{
title: '确认删除?', onConfirm: async () => {
await removeItem(slotProps);
},
}" :tooltip="{
title: '删除',
}">
<Aicon type="DeleteOutlined" />
</PermissionButton>
</j-space>
</template>
</j-pro-table>
</template> </template>
<script setup lang="ts" name="BaseMetadata"> <script setup lang="ts" name="BaseMetadata">
import type { MetadataItem, MetadataType } from '@/views/device/Product/typings' import type { MetadataItem, MetadataType } from '@/views/device/Product/typings'
@ -61,7 +68,7 @@ import { useInstanceStore } from '@/store/instance'
import { useProductStore } from '@/store/product' import { useProductStore } from '@/store/product'
import { useMetadataStore } from '@/store/metadata' import { useMetadataStore } from '@/store/metadata'
import PermissionButton from '@/components/PermissionButton/index.vue' import PermissionButton from '@/components/PermissionButton/index.vue'
import { message } from 'ant-design-vue/es' import { TablePaginationConfig, message } from 'ant-design-vue/es'
import { SystemConst } from '@/utils/consts' import { SystemConst } from '@/utils/consts'
import { Store } from 'jetlinks-store' import { Store } from 'jetlinks-store'
import { asyncUpdateMetadata, removeMetadata } from '../metadata' import { asyncUpdateMetadata, removeMetadata } from '../metadata'
@ -106,6 +113,15 @@ const actions = [
scopedSlots: true, scopedSlots: true,
}, },
]; ];
const pagination = {
showTotal: (num: number, range: number[]) => {
return `${range[0]} - ${range[1]} 条/总共 ${num}`;
},
showSizeChanger: true,
showQuickJumper: false,
defaultPageSize: 10,
size: 'small',
} as TablePaginationConfig
const columns = computed(() => MetadataMapping.get(type)!.concat(actions)) const columns = computed(() => MetadataMapping.get(type)!.concat(actions))
const items = computed(() => JSON.parse((target === 'product' ? productStore.current?.metadata : instanceStore.current.metadata) || '{}') as MetadataItem[]) const items = computed(() => JSON.parse((target === 'product' ? productStore.current?.metadata : instanceStore.current.metadata) || '{}') as MetadataItem[])
const searchValue = ref<string>() const searchValue = ref<string>()
@ -196,4 +212,10 @@ const removeItem = async (record: MetadataItem) => {
} }
}; };
</script> </script>
<style scoped lang="less"></style> <style scoped lang="less">
.table-header {
display: flex;
justify-content: space-between;
padding: 16px 0;
}
</style>

View File

@ -28,34 +28,19 @@
<script setup lang="ts"> <script setup lang="ts">
import { message } from 'ant-design-vue'; import { message } from 'ant-design-vue';
import { bootConfig } from "../typing"; import { bootConfig } from '../typing';
import { useMenuStore } from '@/store/menu';
const router = useRouter();
const props = defineProps({ const props = defineProps({
cardData: Array<bootConfig>, cardData: Array<bootConfig>,
cardTitle: String, cardTitle: String,
}); });
const { cardData, cardTitle } = toRefs(props); const { cardData, cardTitle } = toRefs(props);
const { jumpPage: _jumpPage } = useMenuStore();
const jumpPage = (row: bootConfig): void => { const jumpPage = (item: bootConfig) => {
if (row.auth && row.link) { if (item.auth === undefined || item.auth) _jumpPage(item.link, item.params);
router.push(`${row.link}${objToParams(row.params || {})}`); else message.warning('暂无权限,请联系管理员');
} else {
message.warning('暂无权限,请联系管理员');
}
};
const objToParams = (source: object): string => {
if (Object.prototype.toString.call(source) === '[object Object]') {
const paramsArr = <any>[];
// 使for ints
Object.entries(source).forEach(([prop, value]) => {
if (typeof value === 'object') value = JSON.stringify(value);
paramsArr.push(`${prop}=${value}`);
});
if (paramsArr.length > 0) return '?' + paramsArr.join('&');
}
return '';
}; };
</script> </script>

View File

@ -36,7 +36,7 @@
<ProductChooseDialog <ProductChooseDialog
v-if="productDialogVisible" v-if="productDialogVisible"
v-model:visible="productDialogVisible" v-model:visible="productDialogVisible"
@confirm="(id:string)=>jumpPage('device/Product/Detail', { id })" @confirm="(id:string)=>jumpPage('device/Product/Detail', { id, tab: 'Device'})"
/> />
<DeviceChooseDialog <DeviceChooseDialog
v-if="deviceDialogVisible" v-if="deviceDialogVisible"
@ -80,7 +80,7 @@ const deviceBootConfig: bootConfig[] = [
auth: productPermission('add'), auth: productPermission('add'),
image: '/images/home/guide-home1.png', image: '/images/home/guide-home1.png',
params: { params: {
type: 'add', save: true,
}, },
}, },
{ {
@ -100,7 +100,7 @@ const deviceBootConfig: bootConfig[] = [
auth: rulePermission('add'), auth: rulePermission('add'),
image: '/images/home/guide-home3.png', image: '/images/home/guide-home3.png',
params: { params: {
type: 'add', save: true,
}, },
}, },
]; ];
@ -115,7 +115,7 @@ const deviceStepDetails: recommendList[] = [
linkUrl: 'device/Product', linkUrl: 'device/Product',
auth: productPermission('add'), auth: productPermission('add'),
params: { params: {
type: 'add', save: true,
}, },
}, },
{ {
@ -182,10 +182,7 @@ const opsBootConfig: bootConfig[] = [
{ {
english: 'STEP3', english: 'STEP3',
label: '实时监控', label: '实时监控',
link: 'link/Dashboard', link: 'link/DashBoard',
params: {
save: true,
},
image: '/images/home/guide-home6.png', image: '/images/home/guide-home6.png',
}, },
]; ];
@ -230,4 +227,17 @@ const opsStepDetails: recommendList[] = [
const productDialogVisible = ref(false); const productDialogVisible = ref(false);
const deviceDialogVisible = ref(false); const deviceDialogVisible = ref(false);
// ---- {save:true}
// ----- {id: 'xxxx', tab:'xxx'}
// ---- {save: true}
// ----
// ----
// -----
// ----
</script> </script>

View File

@ -32,12 +32,12 @@ const opsBootConfig: bootConfig[] = [
{ {
english: 'STEP1', english: 'STEP1',
label: '设备接入配置', label: '设备接入配置',
link: 'link/accessConfig', link: 'link/AccessConfig',
}, },
{ {
english: 'STEP2', english: 'STEP2',
label: '日志排查', label: '日志排查',
link: 'link/Log', link: 'Log',
params: { params: {
key: 'system', key: 'system',
}, },
@ -45,7 +45,7 @@ const opsBootConfig: bootConfig[] = [
{ {
english: 'STEP3', english: 'STEP3',
label: '实时监控', label: '实时监控',
link: 'link/dashboard', link: 'link/DashBoard',
params: { params: {
type: 'add', type: 'add',
}, },

View File

@ -26,13 +26,19 @@ const { jumpPage } = useMenuStore();
const projectNum = ref(0); const projectNum = ref(0);
const deviceNum = ref(0); const deviceNum = ref(0);
const menuPermission = useMenuStore().hasPermission;
const getData = () => { const getData = () => {
getDeviceCount_api().then((resp: any) => { //
deviceNum.value = resp.result; menuPermission('device/Product') &&
}); getDeviceCount_api().then((resp: any) => {
getProductCount_api({}).then((resp: any) => { deviceNum.value = resp.result;
projectNum.value = resp.result; });
});
//
menuPermission('device/Instance') &&
getProductCount_api({}).then((resp: any) => {
projectNum.value = resp.result;
});
}; };
getData(); getData();
</script> </script>

View File

@ -23,7 +23,7 @@
<ProductChooseDialog <ProductChooseDialog
v-if="productDialogVisible" v-if="productDialogVisible"
v-model:visible="productDialogVisible" v-model:visible="productDialogVisible"
@confirm="(id:string)=>jumpPage('device/Product/Detail', { id })" @confirm="(id:string)=>jumpPage('device/Product/Detail', { id, tab: 'Device'})"
/> />
<DeviceChooseDialog <DeviceChooseDialog
v-if="deviceDialogVisible" v-if="deviceDialogVisible"
@ -67,7 +67,7 @@ const deviceBootConfig: bootConfig[] = [
link: 'device/Product', link: 'device/Product',
auth: productPermission('add'), auth: productPermission('add'),
params: { params: {
type: 'add', save: true,
}, },
}, },
{ {
@ -85,7 +85,7 @@ const deviceBootConfig: bootConfig[] = [
link: 'rule-engine/Instance', link: 'rule-engine/Instance',
auth: rulePermission('add'), auth: rulePermission('add'),
params: { params: {
type: 'add', save: true,
}, },
}, },
]; ];
@ -98,7 +98,7 @@ const deviceStepDetails: recommendList[] = [
linkUrl: 'device/Product', linkUrl: 'device/Product',
auth: productPermission('add'), auth: productPermission('add'),
params: { params: {
type: 'add', save: true,
}, },
}, },
{ {

View File

@ -17,12 +17,22 @@
<div class="card"> <div class="card">
<h3 style="margin: 0 0 24px 0">基本信息</h3> <h3 style="margin: 0 0 24px 0">基本信息</h3>
<p> <p>
<span style="font-weight: bold">clientId: </span> <span class="label">clientId: </span>
<span>{{ clientId }}</span> <span class="value">{{ clientId }}</span>
</p> </p>
<p> <p>
<span style="font-weight: bold">secureKey:</span> <span class="label">secureKey:</span>
<span>{{ secureKey }}</span> <span class="value">
{{ showKey ? secureKey : '****************' }}
</span>
<AIcon
:type="
showKey
? 'EyeOutlined'
: 'EyeInvisibleOutlined'
"
@click="showKey = !showKey"
/>
</p> </p>
</div> </div>
</template> </template>
@ -47,14 +57,15 @@ const currentView = ref<string>('');
const loading = ref<boolean>(true); const loading = ref<boolean>(true);
const clientId = useUserInfo().$state.userInfos.id; const clientId = useUserInfo().$state.userInfos.id;
const secureKey = ref<string>(''); const secureKey = ref<string>('');
const showKey = ref(false);
// //
const setCurrentView = () => { const setCurrentView = () => {
getView_api().then((resp: any) => { getView_api().then((resp: any) => {
if (resp.status === 200) { if (resp.status === 200) {
if (resp.result) currentView.value = resp.result?.content; if (resp.result) {
else if (resp.result.username === 'admin') { if (resp.result.username === 'admin')
currentView.value = 'comprehensive'; currentView.value = 'comprehensive';
else currentView.value = resp.result?.content;
} else currentView.value = 'init'; } else currentView.value = 'init';
} }
}); });
@ -90,6 +101,15 @@ if (isNoCommunity) {
p { p {
margin: 0; margin: 0;
font-size: 16px; font-size: 16px;
.label {
font-weight: bold;
margin-right: 3px;
}
.value {
margin-right: 10px;
font-size: 14px;
}
} }
} }
} }

View File

@ -0,0 +1,96 @@
<template>
<div class="channel-tree">
<div class="channel-tree-query">
<j-input @change="queryTree" placeholder="请输入目录名称">
<template #suffix>
<AIcon type="SearchOutlined" />
</template>
</j-input>
</div>
<div class="channel-tree-content">
<j-tree
:height="500"
:selectedKeys="selectedKeys"
:treeData="treeData"
:onSelect="(keys:any) => {
if (keys.length) {
selectedKeys = keys
if (props.onSelect) {
props.onSelect(keys[0]);
}
}
}"
:fieldNames="{ key: 'id', title: 'name' }"
/>
</div>
</div>
</template>
<script setup lang="ts">
import { debounce } from 'lodash';
import ChannelApi from '@/api/media/channel';
import DeviceApi from '@/api/media/device';
interface TreeProps {
deviceId: string;
onSelect: (id: string) => void;
onTreeLoad: (type: boolean) => void;
}
const props = defineProps<TreeProps>();
const treeData = ref<any[]>([]);
const selectedKeys = ref<string[]>([]);
const getTreeData = async (id: string, data?: any) => {
const { result } = await ChannelApi.queryTree(id, data);
treeData.value[0].children = result || [];
props.onTreeLoad(treeData.value[0].children.length > 10);
treeData.value = treeData.value;
};
/**
* 获取设备详情
* @param id
*/
const getDeviceDetail = async (id: string) => {
const deviceResp = await DeviceApi.detail(id);
if (deviceResp.status === 200) {
treeData.value = [
{
id,
name: deviceResp.result.name,
children: [],
},
];
selectedKeys.value = [id];
getTreeData(props.deviceId, {});
}
};
const queryTree = debounce((e: any) => {
getTreeData(props.deviceId, {
terms: [
{ column: 'name', termType: 'like', value: `%${e.target.value}%` },
],
});
}, 300);
watchEffect(() => {
getDeviceDetail(props.deviceId);
});
</script>
<style lang="less" scoped>
.channel-tree {
height: 100%;
.channel-tree-query {
margin-bottom: 16px;
}
.channel-tree-content {
min-height: calc(100% - 50px);
}
}
</style>

View File

@ -0,0 +1,48 @@
.device-channel-warp {
display: flex;
.left-warp {
position: relative;
margin-right: 16px;
padding: 20px;
background-color: #fff;
border-radius: 2px;
.left-content {
width: 0;
height: 100%;
overflow: hidden;
&.active {
width: 260px;
}
}
.left-warp--btn {
position: absolute;
top: 50%;
right: 0;
padding: 20px 4px;
color: rgba(#000, 0.3);
background-color: rgba(#f0f0f0, 6);
border-radius: ~'100% 0 0 100% / 50% 0 0 50%';
cursor: pointer;
&:hover {
color: rgba(#000, 0.5);
background-color: rgba(#f0f0f0, 8);
}
&.active {
right: 50%;
background-color: transparent;
border-radius: 0;
transform: translateX(50%) rotate(180deg);
}
}
}
.right {
flex: 1;
}
}

View File

@ -1,82 +1,102 @@
<!-- 视频设备-通道列表 --> <!-- 视频设备-通道列表 -->
<template> <template>
<page-container> <page-container>
<j-advanced-search <div class="device-channel-warp">
type="simple" <div class="left-warp" v-if="route.query.type === 'gb28181-2016'">
:columns="columns" <div class="left-content" :class="{ active: show }">
target="product" <Tree
@search="handleSearch" :deviceId="deviceId"
/> :on-tree-load="(e) => (show = e)"
:on-select="handleSelect"
<JProTable />
ref="listRef" </div>
:columns="columns" <div
:request="(e:any) => ChannelApi.list(e, route?.query.id as string)" class="left-warp--btn"
:defaultParams="{ :class="{ active: !show }"
sorts: [{ name: 'notifyTime', order: 'desc' }], @click="show = !show"
}"
:params="params"
model="table"
>
<template #headerTitle>
<j-tooltip
v-if="route?.query.type === 'gb28181-2016'"
title="接入方式为GB/T28281时不支持新增"
> >
<j-button type="primary" disabled> 新增 </j-button> <AIcon type="LeftOutlined" />
</j-tooltip> </div>
<j-button type="primary" @click="handleAdd" v-else> </div>
新增 <div class="right">
</j-button> <j-advanced-search
</template> type="simple"
<template #status="slotProps"> :columns="columns"
<j-space> target="product"
<j-badge @search="handleSearch"
:status=" />
slotProps.status.value === 'online'
? 'success' <JProTable
: 'error' ref="listRef"
" :columns="columns"
:text="slotProps.status.text" :request="(e:any) => ChannelApi.list(e, route?.query.id as string)"
></j-badge> :defaultParams="{
</j-space> sorts: [{ name: 'notifyTime', order: 'desc' }],
</template> }"
<template #action="slotProps"> :params="params"
<j-space :size="16"> model="table"
<j-tooltip >
v-for="i in getActions(slotProps, 'table')" <template #headerTitle>
:key="i.key" <j-tooltip
v-bind="i.tooltip" v-if="route?.query.type === 'gb28181-2016'"
> title="接入方式为GB/T28281时不支持新增"
<j-popconfirm
v-if="i.popConfirm"
v-bind="i.popConfirm"
:disabled="i.disabled"
> >
<j-button <j-button type="primary" disabled> 新增 </j-button>
:disabled="i.disabled" </j-tooltip>
style="padding: 0" <j-button type="primary" @click="handleAdd" v-else>
type="link" 新增
><AIcon :type="i.icon"
/></j-button>
</j-popconfirm>
<j-button
style="padding: 0"
type="link"
v-else
@click="i.onClick && i.onClick(slotProps)"
>
<j-button
:disabled="i.disabled"
style="padding: 0"
type="link"
><AIcon :type="i.icon"
/></j-button>
</j-button> </j-button>
</j-tooltip> </template>
</j-space> <template #status="slotProps">
</template> <j-space>
</JProTable> <j-badge
:status="
slotProps.status.value === 'online'
? 'success'
: 'error'
"
:text="slotProps.status.text"
></j-badge>
</j-space>
</template>
<template #action="slotProps">
<j-space :size="16">
<j-tooltip
v-for="i in getActions(slotProps, 'table')"
:key="i.key"
v-bind="i.tooltip"
>
<j-popconfirm
v-if="i.popConfirm"
v-bind="i.popConfirm"
:disabled="i.disabled"
>
<j-button
:disabled="i.disabled"
style="padding: 0"
type="link"
><AIcon :type="i.icon"
/></j-button>
</j-popconfirm>
<j-button
style="padding: 0"
type="link"
v-else
@click="i.onClick && i.onClick(slotProps)"
>
<j-button
:disabled="i.disabled"
style="padding: 0"
type="link"
><AIcon :type="i.icon"
/></j-button>
</j-button>
</j-tooltip>
</j-space>
</template>
</JProTable>
</div>
</div>
<Save <Save
v-model:visible="saveVis" v-model:visible="saveVis"
@ -94,7 +114,9 @@ import { useMenuStore } from 'store/menu';
import { message } from 'ant-design-vue'; import { message } from 'ant-design-vue';
import Save from './Save.vue'; import Save from './Save.vue';
import Live from './Live/index.vue'; import Live from './Live/index.vue';
import Tree from './Tree/index.vue';
import { cloneDeep } from 'lodash-es'; import { cloneDeep } from 'lodash-es';
import { useElementSize } from '@vueuse/core';
const menuStory = useMenuStore(); const menuStory = useMenuStore();
const route = useRoute(); const route = useRoute();
@ -253,4 +275,25 @@ const getActions = (
? actions.filter((f) => f.key !== 'delete') ? actions.filter((f) => f.key !== 'delete')
: actions; : actions;
}; };
//
const show = ref(false);
const deviceId = computed(() => route.query.id as string);
const handleSelect = (key: string) => {
if (key === deviceId.value && listRef.value?.reload) {
listRef.value?.reload();
} else {
params.value = {
terms: [
{
column: 'parentId',
value: key,
},
],
};
}
};
</script> </script>
<style lang="less" scoped>
@import './index.less';
</style>

View File

@ -84,14 +84,16 @@
?.addresses" ?.addresses"
:key="`${i.address}_address${idx}`" :key="`${i.address}_address${idx}`"
> >
<j-badge <Ellipsis>
:text="i.address" <j-badge
:color=" :text="i.address"
i.health === -1 :color="
? 'red' i.health === -1
: 'green' ? 'red'
" : 'green'
/> "
/>
</Ellipsis>
</p> </p>
</j-col> </j-col>
</j-row> </j-row>
@ -263,6 +265,7 @@ const handleCancel = () => {
text-align: center; text-align: center;
.gateway-item { .gateway-item {
padding: 16px; padding: 16px;
text-align: left;
.card-item-content-title, .card-item-content-title,
.desc, .desc,
.subtitle { .subtitle {

View File

@ -257,9 +257,7 @@ const columns = [
* @param params * @param params
*/ */
const handleSearch = (e: any) => { const handleSearch = (e: any) => {
console.log('handleSearch:', e);
params.value = e; params.value = e;
console.log('params.value: ', params.value);
}; };
/** /**

View File

@ -1,18 +1,35 @@
<template> <template>
<div> <div>
<Action :thenOptions="data.branches ? data?.branches[0].then : []" :name="0" /> <Action
:thenOptions="data.branches ? data?.branches[0].then : []"
:name="0"
@add="onActionAdd"
@update="onActionUpdate"
/>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { useSceneStore } from '@/store/scene' import { useSceneStore } from '@/store/scene';
import Action from '../action/index.vue' import Action from '../action/index.vue';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { ActionsType } from '@/components/Table';
const sceneStore = useSceneStore() const sceneStore = useSceneStore();
const { data } = storeToRefs(sceneStore) const { data } = storeToRefs(sceneStore);
const onActionAdd = (_data: ActionsType) => {
console.log(_data)
// if (data?.branches && _data) {
// const newThen = [...data?.branches?.[0].then, data];
// data.branches[0].then = newThen;
// }
};
const onActionUpdate = (_data: ActionsType, type: boolean) => {
console.log(_data, type)
};
</script> </script>
<style scoped> <style scoped>
</style> </style>

View File

@ -8,12 +8,20 @@
> >
<template #bodyCell="{ column, text, record }"> <template #bodyCell="{ column, text, record }">
<div> <div>
<template v-if="['valueType', 'name'].includes(column.dataIndex)"> <template
v-if="['valueType', 'name'].includes(column.dataIndex)"
>
<span>{{ text }}</span> <span>{{ text }}</span>
</template> </template>
<template v-else> <template v-else>
<j-input /> <ParamsDropdown
<!-- TODO --> icon="icon-canshu"
placeholder="请选择"
:options="[]"
:tabsOptions="tabOptions"
:metricOption="upperOptions(record.valueType)"
v-model:value="record.value"
/>
</template> </template>
</div> </div>
</template> </template>
@ -21,7 +29,8 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { PropType } from "vue"; import { PropType } from 'vue';
import ParamsDropdown from '../../../components/ParamsDropdown';
type Emits = { type Emits = {
(e: 'update:modelValue', data: Record<string, any>[]): void; (e: 'update:modelValue', data: Record<string, any>[]): void;
@ -31,9 +40,27 @@ const _emit = defineEmits<Emits>();
const _props = defineProps({ const _props = defineProps({
modelValue: { modelValue: {
type: Array as PropType<Record<string, any>[]>, type: Array as PropType<Record<string, any>[]>,
default: '', default: () => undefined,
} },
builtInList: {
type: Array,
default: () => [],
},
}); });
const tabOptions = [
{
label: '手动输入',
component: 'string',
key: 'fixed',
},
{
label: '内置参数',
component: 'tree',
key: 'upper',
},
];
const columns = [ const columns = [
{ {
title: '参数名称', title: '参数名称',
@ -54,11 +81,31 @@ const columns = [
const dataSource = computed({ const dataSource = computed({
get: () => { get: () => {
return _props.modelValue || [] return _props.modelValue || [];
}, },
set: (val: any) => { set: (val: any) => {
_emit('update:modelValue', val); _emit('update:modelValue', val);
} },
}) });
const filterParamsData = (type?: string, data?: any[]): any[] => {
if (type && data) {
return data.filter((item) => {
if (item.children) {
const _children = filterParamsData(type, item.children);
item.children = _children;
return _children.length ? true : false;
} else if (item.type === type) {
// optionMap.current.set(item.id, item);
return true;
}
return false;
});
}
return data || [];
};
const upperOptions = (_type: string) => {
return filterParamsData(_type, _props?.builtInList) || [];
};
</script> </script>

View File

@ -0,0 +1,140 @@
<template>
<div>
<a-form :layout="'vertical'" ref="formRef" :model="modelRef">
<j-row>
<j-col :span="11">
<j-form-item
:name="['message', 'properties']"
label="读取属性"
:rules="[{ required: true, message: '请选择读取属性' }]"
>
<j-select
showSearch
placeholder="请选择属性"
v-model:value="modelRef.properties"
>
<j-select-option
v-for="item in metadata?.properties || []"
:value="item?.id"
:key="item?.id"
>{{ item?.name }}</j-select-option
>
</j-select>
</j-form-item>
</j-col>
<j-col :span="2"></j-col>
<j-col :span="11">
<j-form-item
:name="['message', 'propertiesValue']"
label="属性值"
:rules="[{ required: true, message: '请选择' }]"
>
<ParamsDropdown
icon="icon-canshu"
placeholder="请选择"
:options="[]"
:tabsOptions="tabOptions"
:metricOption="upperOptions(getType)"
v-model:value="modelRef.propertiesValue"
/>
</j-form-item>
</j-col>
</j-row>
</a-form>
</div>
</template>
<script lang="ts" setup>
const props = defineProps({
value: {
type: Object,
default: () => {},
},
metadata: {
type: Object,
default: () => {
return {
properties: [],
};
},
},
builtInList: {
type: Array,
default: () => []
},
});
const formRef = ref();
const modelRef = reactive({
properties: '',
propertiesValue: '',
source: '',
});
const tabOptions = [
{
label: '手动输入',
component: 'string',
key: 'fixed',
},
{
label: '内置参数',
component: 'tree',
key: 'upper',
},
];
const getType = computed(() => {
return props.metadata.properties.find((item: any) => item.id === modelRef.properties)?.valueType?.type
})
const filterParamsData = (type?: string, data?: any[]): any[] => {
if (type && data) {
return data.filter((item) => {
if (item.children) {
const _children = filterParamsData(type, item.children);
item.children = _children;
return _children.length ? true : false;
} else if (item.type === type) {
// optionMap.current.set(item.id, item);
return true;
}
return false;
});
}
return data || [];
};
const upperOptions = (type: string) => {
return filterParamsData(type, props?.builtInList) || []
}
watch(
() => props.value,
(newVal) => {
const _keys = Object.keys(newVal)?.[0];
if (_keys) {
(modelRef.properties = _keys),
(modelRef.propertiesValue = newVal[_keys]?.value);
modelRef.source = newVal[_keys]?.source;
}
},
{ deep: true, immediate: true },
);
const onFormSave = () => {
return new Promise((resolve, reject) => {
formRef.value
.validate()
.then(async (_data: any) => {
//
resolve(_data);
})
.catch((err: any) => {
reject(err);
});
});
};
defineExpose({ onFormSave });
</script>

View File

@ -9,6 +9,7 @@
<TopCard <TopCard
:typeList="TypeList" :typeList="TypeList"
v-model:value="modelRef.message.messageType" v-model:value="modelRef.message.messageType"
@change="onMessageTypeChange"
/> />
</j-form-item> </j-form-item>
<template v-if="deviceMessageType === 'INVOKE_FUNCTION'"> <template v-if="deviceMessageType === 'INVOKE_FUNCTION'">
@ -21,7 +22,7 @@
showSearch showSearch
placeholder="请选择功能" placeholder="请选择功能"
v-model:value="modelRef.message.functionId" v-model:value="modelRef.message.functionId"
@change="onFunctionChange" @change="(val) => onFunctionChange(val, [])"
> >
<j-select-option <j-select-option
v-for="item in metadata?.functions || []" v-for="item in metadata?.functions || []"
@ -36,7 +37,7 @@
:name="['message', 'inputs']" :name="['message', 'inputs']"
:rules="[{ required: true, message: '请输入功能值' }]" :rules="[{ required: true, message: '请输入功能值' }]"
> >
<EditTable v-model="modelRef.message.inputs" /> <EditTable v-model:modelValue="modelRef.message.inputs" :builtInList="builtInList" />
</j-form-item> </j-form-item>
</template> </template>
<template v-else-if="deviceMessageType === 'READ_PROPERTY'"> <template v-else-if="deviceMessageType === 'READ_PROPERTY'">
@ -60,44 +61,11 @@
</j-form-item> </j-form-item>
</template> </template>
<template v-else-if="deviceMessageType === 'WRITE_PROPERTY'"> <template v-else-if="deviceMessageType === 'WRITE_PROPERTY'">
<j-row> <WriteProperty
<j-col :span="11"> v-model:value="modelRef.message.properties"
<j-form-item :metadata="metadata"
:name="['message', 'properties']" :builtInList="builtInList"
label="读取属性" />
:rules="[
{ required: true, message: '请选择读取属性' },
]"
>
<j-select
showSearch
placeholder="请选择属性"
v-model:value="modelRef.message.properties"
>
<j-select-option
v-for="item in metadata?.properties || []"
:value="item?.id"
:key="item?.id"
>{{ item?.name }}</j-select-option
>
</j-select>
</j-form-item>
</j-col>
<j-col :span="2"></j-col>
<j-col :span="11">
<j-form-item
:name="['message', 'properties']"
label="读取属性"
:rules="[
{ required: true, message: '请选择读取属性' },
]"
>
<j-select placeholder="请选择">
<!-- TODO -->
</j-select>
</j-form-item>
</j-col>
</j-row>
</template> </template>
</a-form> </a-form>
</div> </div>
@ -108,6 +76,13 @@ import { getImage } from '@/utils/comm';
import TopCard from '../device/TopCard.vue'; import TopCard from '../device/TopCard.vue';
import { detail } from '@/api/device/instance'; import { detail } from '@/api/device/instance';
import EditTable from './EditTable.vue'; import EditTable from './EditTable.vue';
import WriteProperty from './WriteProperty.vue';
import { queryBuiltInParams } from '@/api/rule-engine/scene';
import { useSceneStore } from '@/store/scene';
import { storeToRefs } from 'pinia'
const sceneStore = useSceneStore();
const { data } = storeToRefs(sceneStore);
const TypeList = [ const TypeList = [
{ {
@ -172,44 +147,93 @@ const deviceMessageType = computed(() => {
return modelRef.message.messageType; return modelRef.message.messageType;
}); });
const onFunctionChange = (val: string) => { const builtInList = ref<any[]>([]);
const onFunctionChange = (val: string, values?: any[]) => {
const _item = (metadata.value?.functions || []).find((item: any) => { const _item = (metadata.value?.functions || []).find((item: any) => {
return val === item.id; return val === item.id;
}); });
const list = (_item?.inputs || []).map((item: any) => { const list = (_item?.inputs || []).map((item: any) => {
const _a = values?.find((i) => i.name === item.id);
return { return {
id: item.id, id: item.id,
name: item.name, value: _a?.value,
value: undefined,
valueType: item?.valueType?.type, valueType: item?.valueType?.type,
..._a,
name: item.name,
}; };
}); });
modelRef.message.inputs = list; modelRef.message.inputs = list;
}; };
watchEffect(() => { const onMessageTypeChange = (val: string) => {
// console.log(props.values) if (['WRITE_PROPERTY', 'INVOKE_FUNCTION'].includes(val)) {
// console.log(metadata.value) const _params = {
// Object.assign() branch: props.thenName,
}); branchGroup: props.branchGroup,
action: props.name - 1,
};
queryBuiltInParams(unref(data), _params).then((res: any) => {
if (res.status === 200) {
builtInList.value = res.result
}
});
}
};
watch( watch(
() => [props.values?.productDetail, props.values.deviceDetail], () => [
([newVal1, newVal2]) => { props.values?.productDetail,
if (newVal1) { props.values.selectorValues,
if (props.values?.selector === 'fixed') { props.values?.selector,
detail(newVal2.id).then((resp) => { ],
if (resp.status === 200) { ([newVal1, newVal2, newVal3]) => {
metadata.value = JSON.parse( if (newVal1?.id) {
resp.result?.metadata || '{}', if (newVal3?.selector === 'fixed') {
); const id = newVal2?.[0]?.value;
} if (id) {
}); detail(id).then((resp) => {
if (resp.status === 200) {
metadata.value = JSON.parse(
resp.result?.metadata || '{}',
);
}
});
}
} else { } else {
metadata.value = JSON.parse(newVal1?.metadata || '{}'); metadata.value = JSON.parse(newVal1?.metadata || '{}');
} }
} }
}, },
{ immediate: true, deep: true },
);
watch(
() => props.values?.message,
(newVal) => {
if (newVal?.messageType) {
modelRef.message = newVal;
if (newVal.messageType === 'INVOKE_FUNCTION' && newVal.functionId) {
onFunctionChange(newVal.functionId, newVal?.inputs);
}
onMessageTypeChange(newVal.messageType)
}
},
{ deep: true, immediate: true }, { deep: true, immediate: true },
); );
const onFormSave = () => {
return new Promise((resolve, reject) => {
formRef.value
.validate()
.then(async (_data: any) => {
resolve(_data);
})
.catch((err: any) => {
reject(err);
});
});
};
defineExpose({ onFormSave });
</script> </script>

View File

@ -1,11 +1,11 @@
<template> <template>
<!-- <j-advanced-search <j-advanced-search
:columns="columns" :columns="columns"
type="simple" type="simple"
@search="handleSearch" @search="handleSearch"
class="search" class="search"
target="scene-trigger-device-device" target="scene-trigger-device-device"
/> --> />
<a-divider style="margin: 0" /> <a-divider style="margin: 0" />
<j-pro-table <j-pro-table
ref="actionRef" ref="actionRef"

View File

@ -61,6 +61,7 @@
v-model:value="modelRef.selectorValues" v-model:value="modelRef.selectorValues"
placeholder="请选择参数" placeholder="请选择参数"
@select="onVariableChange" @select="onVariableChange"
:fieldNames="{ label: 'name', value: 'id' }"
> >
<template #title="{ name, description }"> <template #title="{ name, description }">
<a-space> <a-space>
@ -108,6 +109,7 @@ const props = defineProps({
}, },
}); });
// savedeviceDetail
const emits = defineEmits(['save', 'cancel']); const emits = defineEmits(['save', 'cancel']);
const sceneStore = useSceneStore(); const sceneStore = useSceneStore();
@ -122,6 +124,7 @@ const modelRef = reactive({
source: '', source: '',
relationName: '', relationName: '',
upperKey: '', upperKey: '',
message: undefined,
}); });
const list = ref<any[]>([]); const list = ref<any[]>([]);
@ -160,7 +163,7 @@ const filterTree = (nodes: any[]) => {
if (!nodes?.length) { if (!nodes?.length) {
return nodes; return nodes;
} }
return nodes.filter((it) => { const arr = nodes.filter((it) => {
if ( if (
it.children.find( it.children.find(
(item: any) => (item: any) =>
@ -173,43 +176,16 @@ const filterTree = (nodes: any[]) => {
} }
return false; return false;
}); });
}; return arr.map((item) => {
if (item.children) {
const treeDataFilter = (arr: any[]) => { }
if (Array.isArray(arr) && arr.length) { return {
const list: any[] = []; ...item,
arr.map((item: any) => { title: item.name,
if (item.children) { value: item.id,
const children = treeDataFilter(item.children); disabled: !!item.children,
if (children.length) { };
list.push({ });
...item,
title: item.name,
value: item.id,
disabled: true,
children,
});
}
} else {
if (
item.children.find(
(item: any) =>
item.id.indexOf(
'deviceId' || 'device_id' || 'device_Id',
) > -1,
) &&
!item.children.find(
(item: any) => item.id.indexOf('bolaen') > -1,
)
) {
list.push(item);
}
}
});
return list;
} else {
return [];
}
}; };
const sourceChangeEvent = async () => { const sourceChangeEvent = async () => {
@ -220,11 +196,9 @@ const sourceChangeEvent = async () => {
}; };
const resp = await queryBuiltInParams(unref(data), _params); const resp = await queryBuiltInParams(unref(data), _params);
if (resp.status === 200) { if (resp.status === 200) {
// const array = filterTree(resp.result as any[]); const array = filterTree(resp.result as any[]);
// //
// if (props.formProductId === DeviceModel.productId)// TODO // if (props.formProductId === DeviceModel.productId)// TODO
console.log(array);
const arr = treeDataFilter(resp.result as any[]);
builtInList.value = array; builtInList.value = array;
} }
}; };
@ -293,6 +267,7 @@ const filterType = async () => {
}; };
const onSelectorChange = (val: string) => { const onSelectorChange = (val: string) => {
modelRef.selectorValues = undefined;
if (val === 'relation') { if (val === 'relation') {
queryRelationList(); queryRelationList();
} }
@ -300,7 +275,17 @@ const onSelectorChange = (val: string) => {
const onDeviceChange = (_detail: any) => { const onDeviceChange = (_detail: any) => {
if (_detail) { if (_detail) {
emits('save', modelRef, _detail); if (_detail.id) {
modelRef.deviceId = _detail.id;
modelRef.selectorValues = [
{ value: _detail.id, name: _detail.name },
] as any;
modelRef.message = {} as any;
} else {
modelRef.deviceId = '';
modelRef.selectorValues = [] as any;
}
emits('save', unref(modelRef), _detail);
} }
}; };
@ -310,7 +295,7 @@ const onRelationChange = (val: any, options: any) => {
modelRef.selectorValues = val; modelRef.selectorValues = val;
modelRef.upperKey = 'scene.deviceId'; modelRef.upperKey = 'scene.deviceId';
modelRef.relationName = options.label; modelRef.relationName = options.label;
emits('save', modelRef, {}); emits('save', unref(modelRef), {});
}; };
const onTagChange = (val: any[], arr: any[]) => { const onTagChange = (val: any[], arr: any[]) => {
@ -321,11 +306,12 @@ const onTagChange = (val: any[], arr: any[]) => {
if (arr) { if (arr) {
tagList.value = arr; tagList.value = arr;
} }
emits('save', unref(modelRef), {});
}; };
const onVariableChange = (val: any, node: any) => { const onVariableChange = (val: any, node: any) => {
modelRef.deviceId = val; modelRef.deviceId = val;
// modelRef.deviceDetail = node; emits('save', unref(modelRef), node);
modelRef.selectorValues = [{ value: val, name: node.description }] as any; modelRef.selectorValues = [{ value: val, name: node.description }] as any;
}; };
@ -348,6 +334,21 @@ watch(
deep: true, deep: true,
}, },
); );
const onFormSave = () => {
return new Promise((resolve, reject) => {
formRef.value
.validate()
.then(async (_data: any) => {
resolve(_data);
})
.catch((err: any) => {
reject(err);
});
});
};
defineExpose({ onFormSave });
</script> </script>
<style scoped lang='less'> <style scoped lang='less'>

View File

@ -33,13 +33,16 @@
:thenName="thenName" :thenName="thenName"
:values="DeviceModel" :values="DeviceModel"
@save="onDeviceSave" @save="onDeviceSave"
ref="deviceRef"
/> />
<Action v-else-if="current === 2" <Action
v-else-if="current === 2"
:name="name" :name="name"
:branchGroup="branchGroup" :branchGroup="branchGroup"
:thenName="thenName" :thenName="thenName"
:values="DeviceModel" :values="DeviceModel"
/> ref="actionRef"
/>
</div> </div>
<template #footer> <template #footer>
<div class="steps-action"> <div class="steps-action">
@ -62,7 +65,13 @@ import Product from './Product.vue';
import Device from './device/index.vue'; import Device from './device/index.vue';
import Action from './actions/index.vue'; import Action from './actions/index.vue';
import { onlyMessage } from '@/utils/comm'; import { onlyMessage } from '@/utils/comm';
import { detail } from '@/api/device/product' import { detail } from '@/api/device/product';
import { useSceneStore } from '@/store/scene';
import { storeToRefs } from 'pinia';
const sceneStore = useSceneStore();
const { data } = storeToRefs(sceneStore);
type Emit = { type Emit = {
(e: 'cancel'): void; (e: 'cancel'): void;
@ -92,6 +101,8 @@ const props = defineProps({
}); });
const current = ref<number>(0); const current = ref<number>(0);
const deviceRef = ref<any>();
const actionRef = ref<any>();
const DeviceModel = reactive<DeviceModelType>({ const DeviceModel = reactive<DeviceModelType>({
productId: '', productId: '',
@ -119,6 +130,71 @@ const onCancel = () => {
emit('cancel'); emit('cancel');
}; };
const onSave = (_data: any) => {
const item: any = {
selector: DeviceModel.selector,
source: DeviceModel.source,
selectorValues: DeviceModel.selectorValues,
productId: DeviceModel.productId,
message: _data.message,
};
//
if (DeviceModel.selector === 'variable') {
item.selector = 'fixed';
}
if (DeviceModel.selector === 'relation') {
item.upperKey = 'scene.deviceId';
}
const _options: any = {
name: '-', //
type: '', //
properties: '', //
propertiesValue: '', //
selector: DeviceModel.selector, //
productName: DeviceModel.productDetail.name,
relationName: DeviceModel.relationName,
triggerName: data.value.options?.trigger?.name || '触发设备',
taglist: [],
columns: [],
otherColumns: [],
};
_options.name = DeviceModel.deviceDetail?.name;
const _type = _data.message.messageType;
if (_type === 'INVOKE_FUNCTION') {
_options.type = '执行';
_options.properties = DeviceModel.propertiesName;
}
if (_type === 'READ_PROPERTY') {
_options.type = '读取';
_options.properties = DeviceModel.propertiesName;
}
if (_type === 'WRITE_PROPERTY') {
_options.type = '设置';
_options.properties = DeviceModel.propertiesName;
_options.propertiesValue =
typeof DeviceModel.propertiesValue === 'object'
? JSON.stringify(DeviceModel.propertiesValue)
: `${DeviceModel.propertiesValue}`;
_options.columns = DeviceModel.columns;
_options.otherColumns = DeviceModel.columns;
const cur: any = Object.values(_data.message.properties)?.[0];
if (cur?.source === 'upper') {
_options.propertiesValue = DeviceModel.actionName;
}
}
if (_options.selector === 'tag') {
_options.taglist = DeviceModel.tagList.map((it) => ({
name: it.column || it.name,
type: it.type ? (it.type === 'and' ? '并且' : '或者') : '',
value: it.value,
}));
}
if (_options.selector === 'variable') {
_options.name = DeviceModel.selectorValues?.[0]?.name;
}
emit('save', item, _options);
};
const save = async (step?: number) => { const save = async (step?: number) => {
let _step = step !== undefined ? step : current.value; let _step = step !== undefined ? step : current.value;
if (_step === 0) { if (_step === 0) {
@ -126,10 +202,15 @@ const save = async (step?: number) => {
? (current.value = 1) ? (current.value = 1)
: onlyMessage('请选择产品', 'error'); : onlyMessage('请选择产品', 'error');
} else if (_step === 1) { } else if (_step === 1) {
DeviceModel.deviceId if (deviceRef.value) {
? (current.value = 2) await deviceRef.value?.onFormSave();
: onlyMessage('请选择设备', 'error'); current.value = 2;
}
} else { } else {
if (actionRef.value) {
const _data = await actionRef.value?.onFormSave();
onSave(_data);
}
} }
}; };
@ -148,24 +229,23 @@ const prev = () => {
const saveClick = () => save(); const saveClick = () => save();
const onDeviceSave = (_data: any, _detail: any) => { const onDeviceSave = (_data: any, _detail: any) => {
Object.assign(DeviceModel, _data) Object.assign(DeviceModel, _data);
DeviceModel.deviceId = _detail.id DeviceModel.deviceDetail = _detail;
DeviceModel.deviceDetail = _detail };
}
watch( watch(
() => props.value, () => props.value,
(newValue) => { (newValue) => {
Object.assign(DeviceModel, newValue); Object.assign(DeviceModel, newValue);
if(newValue?.productId){ if (newValue?.productId) {
detail(newValue.productId).then(resp => { detail(newValue.productId).then((resp) => {
if(resp.status === 200){ if (resp.status === 200) {
DeviceModel.productDetail = resp.result DeviceModel.productDetail = resp.result;
} }
}) });
} }
}, },
{ immediate: true, deep: true }, { immediate: true },
); );
</script> </script>

View File

@ -18,12 +18,13 @@
}, },
]" ]"
> >
<j-card-select <!-- <j-card-select
v-model:value="formModel.type" v-model:value="formModel.type"
:options="options" :options="options"
type="horizontal" type="horizontal"
float="right" float="right"
/> /> -->
<a-radio-group v-model:value="formModel.type" :options="options" />
</a-form-item> </a-form-item>
<ActionTypeComponent <ActionTypeComponent
v-bind="props" v-bind="props"
@ -43,10 +44,9 @@ import Notify from '../Notify/index.vue';
import Device from '../Device/index.vue'; import Device from '../Device/index.vue';
import { PropType } from 'vue'; import { PropType } from 'vue';
import { ActionsType } from '../../../typings'; import { ActionsType } from '../../../typings';
import ActionTypeComponent from './ActionTypeComponent.vue' import ActionTypeComponent from './ActionTypeComponent.vue';
import { randomString } from '@/utils/utils'; import { randomString } from '@/utils/utils';
const props = defineProps({ const props = defineProps({
branchesName: { branchesName: {
type: Number, type: Number,
@ -63,8 +63,8 @@ const props = defineProps({
data: { data: {
type: Object as PropType<ActionsType>, type: Object as PropType<ActionsType>,
default: () => ({ default: () => ({
key: randomString() key: randomString(),
}) }),
}, },
parallel: { parallel: {
type: Boolean, type: Boolean,
@ -117,7 +117,11 @@ watch(
() => props.data, () => props.data,
(newVal) => { (newVal) => {
if (newVal?.executor) { if (newVal?.executor) {
formModel.type = (newVal?.executor === 'alarm' ? newVal?.alarm?.mode : newVal?.executor) as string formModel.type = (
newVal?.executor === 'alarm'
? newVal?.alarm?.mode
: newVal?.executor
) as string;
} }
}, },
{ {
@ -129,7 +133,15 @@ const onOk = () => {
actionForm.value.validate().then((values: any) => { actionForm.value.validate().then((values: any) => {
actionType.value = values?.type; actionType.value = values?.type;
if (values?.type === 'relieve' || values?.type === 'trigger') { if (values?.type === 'relieve' || values?.type === 'trigger') {
emit('save', { ...props.data, executor: 'alarm', alarm: { mode: values.type } }, {}); emit(
'save',
{
...props.data,
executor: 'alarm',
alarm: { mode: values.type },
},
{},
);
} }
}); });
}; };
@ -140,10 +152,10 @@ const onCancel = () => {
const onPropsOk = (data: any, options?: any) => { const onPropsOk = (data: any, options?: any) => {
emit('save', { ...data, executor: data.type }, options); emit('save', { ...data, executor: data.type }, options);
actionType.value = '' actionType.value = '';
}; };
const onPropsCancel = () => { const onPropsCancel = () => {
actionType.value = '' actionType.value = '';
} };
</script> </script>

View File

@ -1,5 +1,5 @@
<template> <template>
<Search <j-advanced-search
:columns="columns" :columns="columns"
type="simple" type="simple"
target="action-notice-config" target="action-notice-config"

View File

@ -1,5 +1,5 @@
<template> <template>
<Search <j-advanced-search
:columns="columns" :columns="columns"
type="simple" type="simple"
target="action-notice-template" target="action-notice-template"

View File

@ -1,10 +1,11 @@
<template> <template>
<a-spin :spinning="loading"> <a-spin :spinning="loading">
<j-card-select <!-- <j-card-select
v-model:value="notifyType" v-model:value="notifyType"
:options="options" :options="options"
:icon-size="106" :icon-size="106"
/> /> -->
<a-radio-group v-model:value="notifyType" :options="options" @change="onRadioChange" />
</a-spin> </a-spin>
</template> </template>
@ -34,13 +35,17 @@ const notifyType = ref('');
const options = ref<any[]>([]); const options = ref<any[]>([]);
watch( watch(
() => notifyType, () => props.value,
(newVal) => { (newVal) => {
emit('update:value', newVal) notifyType.value = newVal
}, },
{ deep: true, immediate: true }, { deep: true, immediate: true },
); );
const onRadioChange = (e: any) => {
emit('update:value', e.target.value)
}
onMounted(() => { onMounted(() => {
loading.value = true loading.value = true
notice.queryMessageType().then((resp) => { notice.queryMessageType().then((resp) => {

View File

@ -122,7 +122,6 @@ const jumpStep = async (val: number) => {
if (val === 0) { if (val === 0) {
current.value = val; current.value = val;
} else if (val === 1) { } else if (val === 1) {
console.log(formModel)
if (formModel.notifyType) { if (formModel.notifyType) {
current.value = val; current.value = val;
} else { } else {

View File

@ -23,7 +23,11 @@
type="serial" type="serial"
:branchesName="props.name" :branchesName="props.name"
:parallel="false" :parallel="false"
:actions="serialArray.length ? serialArray[0].actions : []" :actions="
serialArray.length ? serialArray[0].actions : []
"
@add="onAdd"
@delete="onDelete"
/> />
</div> </div>
</a-collapse-panel> </a-collapse-panel>
@ -41,7 +45,11 @@
type="parallel" type="parallel"
:branchesName="props.name" :branchesName="props.name"
:parallel="true" :parallel="true"
:actions="parallelArray.length ? parallelArray[0].actions : []" :actions="
parallelArray.length
? parallelArray[0].actions
: []
"
@add="onAdd" @add="onAdd"
@delete="onDelete" @delete="onDelete"
/> />
@ -57,6 +65,7 @@ import ShakeLimit from '../components/ShakeLimit/index.vue';
import { List } from './ListItem'; import { List } from './ListItem';
import { BranchesThen } from '../../typings'; import { BranchesThen } from '../../typings';
import { PropType } from 'vue'; import { PropType } from 'vue';
import { randomString } from '@/utils/utils';
interface ActionsProps { interface ActionsProps {
name: number; name: number;
@ -73,13 +82,15 @@ const props = defineProps({
openShakeLimit: Boolean, openShakeLimit: Boolean,
}); });
const emit = defineEmits(['update', 'add']);
const shakeLimit = ref({ const shakeLimit = ref({
enabled: false enabled: false,
}); });
const activeKeys = ref<string[]>(['1']); const activeKeys = ref<string[]>(['1']);
const parallelArray = ref<BranchesThen[]>([]); const parallelArray = ref<BranchesThen[]>([]);
const serialArray = ref<BranchesThen[]>([]); const serialArray = ref<BranchesThen[]>([]);
const lock = ref<boolean>(false) const lock = ref<boolean>(false);
watch( watch(
() => props.thenOptions, () => props.thenOptions,
@ -96,8 +107,8 @@ watch(
parallelArray.value.length && parallelArray.value.length &&
(!serialArray.value.length || !isSerialActions) (!serialArray.value.length || !isSerialActions)
) { ) {
activeKeys.value = ['2'] activeKeys.value = ['2'];
lock.value = true lock.value = true;
} }
//TODO //TODO
@ -109,11 +120,37 @@ watch(
); );
const onDelete = (_key: string) => { const onDelete = (_key: string) => {
console.log(_key) const aIndex = unref(serialArray)[0].actions?.findIndex(
} (aItem) => aItem.key === _key,
const onAdd = (data: any) => { );
console.log(data) if (aIndex !== -1) {
} serialArray.value[0].actions?.splice(aIndex, 1);
emit('update', serialArray[0], false);
}
};
const onAdd = (actionItem: any) => {
const newParallelArray = [...parallelArray.value];
if (newParallelArray.length) {
const indexOf = newParallelArray[0].actions?.findIndex(
(aItem) => aItem.key === actionItem.key,
);
if (indexOf !== -1) {
newParallelArray[0].actions.splice(indexOf, 1, actionItem);
} else {
newParallelArray[0].actions.push(actionItem);
}
parallelArray.value = [...newParallelArray];
console.log(parallelArray.value);
emit('update', newParallelArray[0], true);
} else {
actionItem.key = randomString();
emit('add', {
parallel: true,
key: randomString(),
actions: [actionItem],
});
}
};
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>

View File

@ -1,8 +1,17 @@
<template> <template>
<j-dropdown class='scene-select' trigger='click'> <j-dropdown
<div :class='dropdownButtonClass'> class='scene-select'
<AIcon v-if='!!icon' :type='icon' /> trigger='click'
{{ label }} v-model:visible='visible'
@visibleChange='visibleChange'
>
<div @click.prevent='visible = true'>
<slot :label='label'>
<div :class='dropdownButtonClass' >
<AIcon v-if='!!icon' :type='icon' />
{{ label }}
</div>
</slot>
</div> </div>
<template #overlay> <template #overlay>
<div class='scene-select-content'> <div class='scene-select-content'>
@ -18,7 +27,7 @@
:type='component' :type='component'
@change='timeSelect' @change='timeSelect'
/> />
<div v-else> <div style='min-width: 400px' v-else>
<j-tree <j-tree
:selectedKeys='selectValue ? [selectValue] : []' :selectedKeys='selectValue ? [selectValue] : []'
:treeData='options' :treeData='options'
@ -89,20 +98,18 @@ const props = defineProps({
component: { component: {
type: String, type: String,
default: 'select' // 'select' | 'treeSelect' default: 'select' // 'select' | 'treeSelect'
} },
}) })
const emit = defineEmits<Emit>() const emit = defineEmits<Emit>()
const label = ref<LabelType>(props.placeholder) const label = ref<LabelType>(props.placeholder)
const selectValue = ref(props.value) const selectValue = ref(props.value)
const flatMapTree = new Map() const visible = ref(false)
const LabelStyle = computed(() => { const visibleChange = (v: boolean) => {
return { visible.value = v
color: selectValue.value ? '#' : '#' }
}
})
const dropdownButtonClass = computed(() => ({ const dropdownButtonClass = computed(() => ({
'dropdown-button': true, 'dropdown-button': true,
@ -112,24 +119,33 @@ const dropdownButtonClass = computed(() => ({
'type': props.type === 'type', 'type': props.type === 'type',
})) }))
const treeSelect = (v: any) => { const treeSelect = (v: any, option: any) => {
const node = option.node
visible.value = false
label.value = node.fullname || node.name
selectValue.value = v[0]
emit('update:value', node[props.valueName])
emit('select', node)
} }
const timeSelect = (v: string) => { const timeSelect = (v: string) => {
selectValue.value = v
visible.value = false
emit('update:value', v) emit('update:value', v)
emit('select', v) emit('select', v)
} }
const menuSelect = (v: any) => { const menuSelect = (v: string, option: any) => {
const option = getOption(props.options, props.value, props.valueName) selectValue.value = v
emit('update:value', v.key) visible.value = false
emit('update:value', v)
emit('select', option) emit('select', option)
} }
watchEffect(() => { watchEffect(() => {
const option = getOption(props.options, props.value, props.valueName) const option = getOption(props.options, props.value, props.valueName)
if (option && Object.keys(option).length) { selectValue.value = props.value
if (option) {
label.value = option[props.labelName] || option.name label.value = option[props.labelName] || option.name
} else { } else {
label.value = props.value || props.placeholder label.value = props.value || props.placeholder

View File

@ -7,13 +7,13 @@
</template> </template>
<script lang='ts' setup name='DropdownMenus'> <script lang='ts' setup name='DropdownMenus'>
import { isBoolean } from 'lodash-es' import { isBoolean, isUndefined } from 'lodash-es'
import { getOption } from '../DropdownButton/util' import { getOption } from '../DropdownButton/util'
type ValueType = string| number | boolean type ValueType = string| number | boolean
type Emits = { type Emits = {
(e: 'update:value', value: ValueType): void (e: 'update:value', value: ValueType): void
(e: 'click', data: any): void (e: 'click', value: string, data: any): void
} }
const props = defineProps({ const props = defineProps({
@ -32,29 +32,39 @@ const emit = defineEmits<Emits>()
const myOptions = computed(() => { const myOptions = computed(() => {
return props.options.map((item: any) => { return props.options.map((item: any) => {
let _label = item.label || item.name let _label = item.label || item.name
if (isBoolean(item.value)) { let _value = isUndefined(item.value) ? item.id : item.value
_label = item.value === true ? '是' : '否' if (isBoolean(_value)) {
_label = _value === true ? '是' : '否'
_value = String(_value)
} }
return { return {
...item, ...item,
label: _label, label: _label,
value: item.value || item.id, value: _value
} }
}) })
}) })
const myValue = ref(props.value) const myValue = ref(props.value)
const handleBoolean = (key: string) => {
return key === 'false' ? false : true
}
const click = (e: any) => { const click = (e: any) => {
const option = getOption(myOptions.value, e.key) const _key = ['true', 'false'].includes(e.key) ? handleBoolean(e.key) : e.key
myValue.value = e.key const option = getOption(myOptions.value, _key)
emit('update:value', e.key) myValue.value = _key
emit('click', { emit('update:value', _key)
key: e.key, emit('click', _key, {
key: _key,
...option ...option
}) })
} }
watch(() => props.value, () => {
myValue.value = props.value
}, { immediate: true})
</script> </script>
<style scoped lang='less'> <style scoped lang='less'>

View File

@ -62,7 +62,7 @@ const change = (e: string) => {
} }
</script> </script>
<style lang='less'> <style lang='less' scoped>
.dropdown-time-picker { .dropdown-time-picker {
>div{ >div{
position: relative !important; position: relative !important;
@ -86,4 +86,8 @@ const change = (e: string) => {
box-shadow: unset; box-shadow: unset;
} }
} }
.wrapper{
display: none;
}
</style> </style>

View File

@ -26,8 +26,8 @@ export const getComponent = (type: string): string => {
} }
export const getOption = (data: any[], value?: string | number | boolean, key: string = 'name'): DropdownButtonOptions | any => { export const getOption = (data: any[], value?: string | number | boolean, key: string = 'name'): DropdownButtonOptions | any => {
let option = {} let option
if (!value) return option if (value === undefined && value === null) return option
for (let i = 0; i < data.length; i++) { for (let i = 0; i < data.length; i++) {
const item = data[i] const item = data[i]
if (item[key] === value) { if (item[key] === value) {
@ -35,7 +35,9 @@ export const getOption = (data: any[], value?: string | number | boolean, key: s
break break
} else if (item.children && item.children.length){ } else if (item.children && item.children.length){
option = getOption(item.children, value, key) option = getOption(item.children, value, key)
if (option) break if (option) {
break
}
} }
} }
return option return option

View File

@ -5,9 +5,13 @@
v-model:visible='visible' v-model:visible='visible'
@visibleChange='visibleChange' @visibleChange='visibleChange'
> >
<div class='dropdown-button value' @click.prevent='visible = true'> <div @click.prevent='visible = true'>
<AIcon v-if='!!icon' :type='icon' /> <slot :label='label'>
{{ label }} <div class='dropdown-button value'>
<AIcon v-if='!!icon' :type='icon' />
{{ label }}
</div>
</slot>
</div> </div>
<template #overlay> <template #overlay>
<div class='scene-select-content'> <div class='scene-select-content'>
@ -24,32 +28,36 @@
@change='timeChange' @change='timeChange'
/> />
<DropdownMenus <DropdownMenus
v-if='["metric","enum", "boolean"].includes(item.component)' v-if='["select","enum", "boolean"].includes(item.component)'
:options='options' :options='["metric", "upper"].includes(item.key) ? metricOption : options'
@change='onSelect' @click='onSelect'
/> />
<div
v-else-if='item.component === "tree"'
style='min-width: 400px'
>
<j-tree
:selectedKeys='myValue ? [myValue] : []'
:treeData='item.key === "upper" ? metricOption : options'
@select='treeSelect'
:height='450'
:virtual='true'
>
<template #title="{ name, description }">
<j-space>
{{ name }}
<span v-if='description' class='tree-title-description'>{{ description }}</span>
</j-space>
</template>
</j-tree>
</div>
<ValueItem <ValueItem
v-else-if='valueItemKey.includes(item.component)' v-else
v-model:modelValue='myValue' v-model:modelValue='myValue'
:itemType='getComponent(item.component)' :itemType='getComponent(item.component)'
:options='options' :options='item.key === "upper" ? metricOption : options'
@change='valueItemChange' @change='valueItemChange'
/> />
<j-tree
v-else
:selectedKeys='myValue ? [myValue] : []'
:treeData='options'
@select='treeSelect'
:height='450'
:virtual='true'
>
<template #title="{ name, description }">
<j-space>
{{ name }}
<span v-if='description' class='tree-title-description'>{{ description }}</span>
</j-space>
</template>
</j-tree>
</div> </div>
</j-tab-pane> </j-tab-pane>
</j-tabs> </j-tabs>
@ -101,10 +109,9 @@ const updateValue = () => {
} }
const treeSelect = (e: any) => { const treeSelect = (e: any) => {
console.log('treeSelect', e)
visible.value = false visible.value = false
label.value = e.fullname || e.name label.value = e.fullname || e.name
emit('update:value', e.id) emit('update:value', e[props.valueName])
emit('select', e) emit('select', e)
} }
@ -115,7 +122,8 @@ const valueItemChange = (e: string) => {
emit('select', e) emit('select', e)
} }
const sonSelect = (e: string, option: any) => { const onSelect = (e: string, option: any) => {
console.log(e, option)
visible.value = false visible.value = false
label.value = option.label label.value = option.label
emit('update:value', e) emit('update:value', e)
@ -133,14 +141,16 @@ const visibleChange = (v: boolean) => {
visible.value = v visible.value = v
} }
watch([props.options, props.value], () => { watchEffect(() => {
const option = getOption(props.options, props.value as string, props.valueName) // label const option = getOption(props.options, props.value as string, props.valueName) // label
if (option && Object.keys(option).length) { myValue.value = props.value
mySource.value = props.source
if (option) {
label.value = option[props.labelName] || option.name label.value = option[props.labelName] || option.name
} else { } else {
label.value = props.value || props.placeholder label.value = props.value || props.placeholder
} }
}, { immediate: true }) })
</script> </script>

View File

@ -12,8 +12,7 @@ export type DropdownButtonOptions = {
export type TabsOption = { export type TabsOption = {
label: string; label: string;
key: string; key: string;
component: string, component: string
options: DropdownButtonOptions[]
} }
type ValueArrayType = [string, number] type ValueArrayType = [string, number]
export type ValueType = string | number | undefined | ValueArrayType export type ValueType = string | number | undefined | ValueArrayType
@ -47,6 +46,10 @@ export const defaultSetting = {
type: Array as PropType<Array<DropdownButtonOptions>>, type: Array as PropType<Array<DropdownButtonOptions>>,
default: () => [] default: () => []
}, },
metricOption: {
type: Array as PropType<Array<DropdownButtonOptions>>,
default: () => []
},
metricOptions: { // 指标值 metricOptions: { // 指标值
type: Array as PropType<Array<DropdownButtonOptions>>, type: Array as PropType<Array<DropdownButtonOptions>>,
default: () => [] default: () => []

View File

@ -40,10 +40,8 @@
icon='icon-canshu' icon='icon-canshu'
placeholder='参数值' placeholder='参数值'
:options='valueOptions' :options='valueOptions'
:tabsOptions='[ :metricOption='metricOption'
{ label: "手动输入", component: "input", key: "fixed" }, :tabsOptions='tabsOptions'
{ label: "指标值", component: "time", key: "manual" }
]'
v-model:value='paramsValue.value.value' v-model:value='paramsValue.value.value'
v-model:source='paramsValue.value.source' v-model:source='paramsValue.value.source'
/> />
@ -52,10 +50,8 @@
icon='icon-canshu' icon='icon-canshu'
placeholder='参数值' placeholder='参数值'
:options='valueOptions' :options='valueOptions'
:tabsOptions='[ :metricOption='metricOption'
{ label: "手动输入", component: "time", key: "fixed" }, :tabsOptions='tabsOptions'
{ label: "指标值", component: "input", key: "manual" },
]'
v-model:value='paramsValue.value.value' v-model:value='paramsValue.value.value'
v-model:source='paramsValue.value.source' v-model:source='paramsValue.value.source'
/> />
@ -80,6 +76,16 @@ import ParamsDropdown, { DoubleParamsDropdown } from '../ParamsDropdown'
import { inject } from 'vue' import { inject } from 'vue'
import { ContextKey } from './util' import { ContextKey } from './util'
type Emit = {
(e: 'update:value', data: TermsType): void
}
type TabsOption = {
label: string;
key: string;
component: string
}
const props = defineProps({ const props = defineProps({
isFirst: { isFirst: {
type: Boolean, type: Boolean,
@ -98,7 +104,7 @@ const props = defineProps({
default: () => ({ default: () => ({
column: '', column: '',
type: '', type: '',
termType: undefined, termType: 'eq',
value: { value: {
source: 'fixed', source: 'fixed',
value: undefined value: undefined
@ -107,6 +113,8 @@ const props = defineProps({
} }
}) })
const emit = defineEmits<Emit>()
const paramsValue = reactive<TermsType>({ const paramsValue = reactive<TermsType>({
column: props.value.column, column: props.value.column,
type: props.value.type, type: props.value.type,
@ -115,21 +123,56 @@ const paramsValue = reactive<TermsType>({
}) })
const showDelete = ref(false) const showDelete = ref(false)
const columnOptions: any = inject(ContextKey) const columnOptions: any = inject(ContextKey) //
const options = ref<any>([]) const termTypeOptions = ref<Array<{ id: string, name: string}>>([]) //
const valueOptions = ref<any[]>([]) //
const metricOption = ref<any[]>([]) // termType
const tabsOptions = ref<Array<TabsOption>>([{ label: '手动输入', key: 'manual', component: 'string' }])
let metricsCacheOption: any[] = [] //
const termTypeOptions = computed(() => { const handOptionByColumn = (option: any) => {
if (option) {
termTypeOptions.value = option.termTypes || []
metricsCacheOption = option.metrics || []
tabsOptions.value.length = 1
tabsOptions.value[0].component = option.dataType
if (option.metrics && option.metrics.length) {
tabsOptions.value.push(
{ label: '指标值', key: 'metric', component: 'select' }
)
}
if (option.dataType === 'boolean') {
valueOptions.value = [
{ label: '是', value: true },
{ label: '否', value: false },
]
} else if(option.dataType === 'enum') {
valueOptions.value = option.options?.map((item: any) => ({ ...item, label: item.name, value: item.id})) || []
} else{
valueOptions.value = option.options || []
}
} else {
termTypeOptions.value = []
metricsCacheOption = []
valueOptions.value = []
}
}
watchEffect(() => {
const option = getOption(columnOptions.value, paramsValue.column, 'column') const option = getOption(columnOptions.value, paramsValue.column, 'column')
return option && Object.keys(option).length ? option.termTypes : [] handOptionByColumn(option)
})
const tabsOptions = computed(() => {
// valueoption
return []
}) })
const showDouble = computed(() => { const showDouble = computed(() => {
return paramsValue.termType ? ['nbtw', 'btw', 'in', 'nin'].includes(paramsValue.termType) : false const isRange = paramsValue.termType ? ['nbtw', 'btw', 'in', 'nin'].includes(paramsValue.termType) : false
if (metricsCacheOption.length) {
metricOption.value = metricsCacheOption.filter(item => isRange ? item.range : !item.range)
} else {
metricOption.value = []
}
return isRange
}) })
const mouseover = () => { const mouseover = () => {
@ -145,11 +188,20 @@ const mouseout = () => {
} }
const columnSelect = () => { const columnSelect = () => {
paramsValue.termType = 'eq'
paramsValue.value = {
source: tabsOptions.value[0].key,
value: undefined
}
emit('update:value', { ...paramsValue })
} }
const termsTypeSelect = () => { const termsTypeSelect = () => {
paramsValue.value = {
source: tabsOptions.value[0].key,
value: undefined
}
emit('update:value', { ...paramsValue })
} }
const termAdd = () => { const termAdd = () => {
@ -160,10 +212,6 @@ const onDelete = () => {
} }
const valueOptions = computed(() => {
return []
})
nextTick(() => { nextTick(() => {
Object.assign(paramsValue, props.value) Object.assign(paramsValue, props.value)
}) })

View File

@ -77,11 +77,22 @@ const rules = [{
} }
}] }]
const handleParamsData = (data: any[]): any[] => {
return data?.map(item => {
return {
...item,
key: item.column,
disabled: !!item.children,
children: handleParamsData(item.children)
}
}) || []
}
const queryColumn = (dataModel: FormModelType) => { const queryColumn = (dataModel: FormModelType) => {
const cloneDevice = cloneDeep(dataModel) const cloneDevice = cloneDeep(dataModel)
cloneDevice.branches = cloneDevice.branches?.filter(item => !!item) cloneDevice.branches = cloneDevice.branches?.filter(item => !!item)
getParseTerm(cloneDevice).then(res => { getParseTerm(cloneDevice).then(res => {
columnOptions.value = res.result columnOptions.value = handleParamsData(res.result as any[])
}) })
} }

View File

@ -121,7 +121,7 @@
</page-container> </page-container>
</template> </template>
<script setup lang='ts'> <script setup lang='ts' name='Scene'>
import SaveModal from './Save/save.vue'; import SaveModal from './Save/save.vue';
import type { SceneItem } from './typings'; import type { SceneItem } from './typings';
import { useMenuStore } from 'store/menu'; import { useMenuStore } from 'store/menu';

View File

@ -187,7 +187,7 @@ export type TriggerType = {
}; };
export interface TermsVale { export interface TermsVale {
source: keyof typeof Source; source: string;
/** 手动输入值,source为 manual 时不能为空 */ /** 手动输入值,source为 manual 时不能为空 */
value?: Record<string, any> | any[]; value?: Record<string, any> | any[];
/** 指标值,source为 metric 时不能为空 */ /** 指标值,source为 metric 时不能为空 */

View File

@ -3,7 +3,7 @@
<div class="top"> <div class="top">
<slot name="top" /> <slot name="top" />
</div> </div>
<j-row :gutter="24"> <j-row :gutter="24" class="content">
<j-col <j-col
:span="24" :span="24"
v-if="props.showTitle" v-if="props.showTitle"
@ -151,11 +151,16 @@ function init() {
<style lang="less" scoped> <style lang="less" scoped>
.api-page-container { .api-page-container {
.tree-content { .content {
padding-bottom: 30px; background-color: #fff;
height: calc(100vh - 230px); padding: 24px;
overflow-y: auto; margin: 0 !important;
border-right: 1px solid #e9e9e9; .tree-content {
padding-bottom: 30px;
height: calc(100vh - 230px);
overflow-y: auto;
border-right: 1px solid #e9e9e9;
}
} }
} }
</style> </style>

View File

@ -3695,8 +3695,8 @@ jetlinks-store@^0.0.3:
jetlinks-ui-components@^1.0.4: jetlinks-ui-components@^1.0.4:
version "1.0.4" version "1.0.4"
resolved "https://registry.jetlinks.cn/jetlinks-ui-components/-/jetlinks-ui-components-1.0.4.tgz#34b70f75dcc4ea679f0da6674a3f372dfc4acab4" resolved "https://registry.jetlinks.cn/jetlinks-ui-components/-/jetlinks-ui-components-1.0.4.tgz#41d52892f0f4d38adc6df02a87290a3042eb5645"
integrity sha512-ffDi9NyD51hPzu6iRBhibxlI36In5igUciMugm+Lui2LxnYdGbXGvB0i4y6xKaGg4jRDJ/lDX+b6AvuIrlb1lg== integrity sha512-aX+XiGigzxZnrG52xqipxd+WuFwBeZ6+dvLkcvOfLLBqSu8sgfvr/8NJ5hFgv5Eo2QFnUJq3Qf4HXLw9Ogv/yw==
dependencies: dependencies:
"@vueuse/core" "^9.12.0" "@vueuse/core" "^9.12.0"
ant-design-vue "^3.2.15" ant-design-vue "^3.2.15"