Merge branch 'dev' of github.com:jetlinks/jetlinks-ui-vue into dev
This commit is contained in:
commit
519042d019
|
@ -175,3 +175,45 @@ export const delTags = (deviceId: string, id: string) => server.remove(`/device/
|
|||
* @returns
|
||||
*/
|
||||
export const configurationReset = (deviceId: string) => server.put(`/device-instance/${deviceId}/configuration/_reset`)
|
||||
|
||||
/**
|
||||
* 查询事件详情列表
|
||||
* @param deviceId 设备id
|
||||
* @param eventId 事件id
|
||||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
export const getEventList = (deviceId: string, eventId: string, data: Record<string, any>) => server.post(`/device-instance/${deviceId}/event/${eventId}?format=true`, data)
|
||||
|
||||
/**
|
||||
* 设置属性至设备
|
||||
* @param deviceId 设备id
|
||||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
export const setProperty = (deviceId: string, data: Record<string, any>) => server.put(`/device-instance/${deviceId}/property`, data)
|
||||
|
||||
/**
|
||||
* 获取最新属性值
|
||||
* @param deviceId 设备id
|
||||
* @param type 属性id
|
||||
* @returns
|
||||
*/
|
||||
export const getProperty = (deviceId: string, type: string) => server.get(`/device/standard/${deviceId}/property/${type}`)
|
||||
|
||||
/**
|
||||
* 查询设备的物模型指标
|
||||
* @param deviceId 设备id
|
||||
* @param propertyId 属性id
|
||||
* @returns
|
||||
*/
|
||||
export const queryMetric = (deviceId: string, propertyId: string) => server.get(`/device-instance/${deviceId}/metric/property/${propertyId}`)
|
||||
|
||||
/**
|
||||
* 保存设备的物模型指标
|
||||
* @param deviceId 设备id
|
||||
* @param propertyId 属性id
|
||||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
export const saveMetric = (deviceId: string, propertyId: string, data: Record<string, any>) => server.patch(`/device-instance/${deviceId}/metric/property/${propertyId}`, data)
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
import server from '@/utils/request'
|
||||
|
||||
/**
|
||||
* 分页查询平台接入列表
|
||||
* @param data
|
||||
*/
|
||||
export const queryList = (data: any) => server.post(`/network/card/platform/_query`, data)
|
||||
|
||||
/**
|
||||
* 根据id查询详情
|
||||
* @param id
|
||||
*/
|
||||
export const queryById = (id: any) => server.get(`/network/card/platform/${id}`)
|
||||
|
||||
/**
|
||||
* 新增
|
||||
* @param data
|
||||
*/
|
||||
export const save = (data: any) => server.post(`/network/card/platform`, data)
|
||||
|
||||
/**
|
||||
* 编辑
|
||||
* @param data
|
||||
*/
|
||||
export const update = (data: any) => server.patch(`/network/card/platform`, data)
|
||||
|
||||
/**
|
||||
* 删除
|
||||
* @param id
|
||||
*/
|
||||
export const del = (id: string) => server.remove(`/network/card/platform/${id}`)
|
|
@ -0,0 +1,7 @@
|
|||
import server from '@/utils/request'
|
||||
|
||||
/**
|
||||
* 分页查询操作记录列表
|
||||
* @param data
|
||||
*/
|
||||
export const queryList = (data: any) => server.post(`/network/card/stateOperate/_log`, data)
|
|
@ -6,5 +6,6 @@ export default {
|
|||
agg: () => server.get<Agg>(`/media/record/file/agg`),
|
||||
// 播放中数量
|
||||
aggPlaying: () => server.get<AggPlaying>(`/media/channel/playing/agg`),
|
||||
|
||||
// 获取播放数量(人次)
|
||||
getPlayCount: (data: any) => server.post<any>(`/dashboard/_multi`, data),
|
||||
}
|
|
@ -2,8 +2,8 @@ import server from '@/utils/request'
|
|||
|
||||
export default {
|
||||
// 设备数量
|
||||
deviceCount: () => server.get<number>(`/media/device/_count`),
|
||||
deviceCount: (params: any) => server.get<number>(`/media/device/_count`, params),
|
||||
// 通道数量
|
||||
channelCount: () => server.post<number>(`/media/channel/_count`),
|
||||
channelCount: (data: any) => server.post<number>(`/media/channel/_count`, data),
|
||||
|
||||
}
|
|
@ -4,14 +4,14 @@
|
|||
<a-popconfirm v-bind="popConfirm" :disabled="!isPermission || props.disabled">
|
||||
<a-tooltip v-if="tooltip" v-bind="tooltip">
|
||||
<slot v-if="noButton"></slot>
|
||||
<a-button v-else v-bind="buttonProps" :disabled="_isPermission" @click="handleClick">
|
||||
<a-button v-else v-bind="_buttonProps" :disabled="_isPermission" @click="handleClick">
|
||||
<slot></slot>
|
||||
<template #icon>
|
||||
<slot name="icon"></slot>
|
||||
</template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-button v-else v-bind="buttonProps" :disabled="_isPermission" @click="handleClick">
|
||||
<a-button v-else v-bind="_buttonProps" :disabled="_isPermission" @click="handleClick">
|
||||
<slot></slot>
|
||||
<template #icon>
|
||||
<slot name="icon"></slot>
|
||||
|
@ -22,7 +22,7 @@
|
|||
<template v-else-if="tooltip">
|
||||
<a-tooltip v-bind="tooltip">
|
||||
<slot v-if="noButton"></slot>
|
||||
<a-button v-else v-bind="buttonProps" :disabled="_isPermission" @click="handleClick">
|
||||
<a-button v-else v-bind="_buttonProps" :disabled="_isPermission" @click="handleClick">
|
||||
<slot></slot>
|
||||
<template #icon>
|
||||
<slot name="icon"></slot>
|
||||
|
@ -32,7 +32,7 @@
|
|||
</template>
|
||||
<template v-else>
|
||||
<slot v-if="noButton"></slot>
|
||||
<a-button v-else v-bind="buttonProps" :disabled="_isPermission" @click="handleClick">
|
||||
<a-button v-else v-bind="_buttonProps" :disabled="_isPermission" @click="handleClick">
|
||||
<slot></slot>
|
||||
<template #icon>
|
||||
<slot name="icon"></slot>
|
||||
|
@ -42,7 +42,7 @@
|
|||
</template>
|
||||
<a-tooltip v-else title="没有权限">
|
||||
<slot v-if="noButton"></slot>
|
||||
<a-button v-else v-bind="buttonProps" :disabled="_isPermission" @click="handleClick">
|
||||
<a-button v-else v-bind="_buttonProps" :disabled="_isPermission" @click="handleClick">
|
||||
<slot></slot>
|
||||
<template #icon>
|
||||
<slot name="icon"></slot>
|
||||
|
@ -51,7 +51,9 @@
|
|||
</a-tooltip>
|
||||
</template>
|
||||
<script setup lang="ts" name="PermissionButton">
|
||||
import type { ButtonProps, TooltipProps, PopconfirmProps } from 'ant-design-vue'
|
||||
import { PropType } from 'vue'
|
||||
import { TooltipProps, PopconfirmProps } from 'ant-design-vue/es'
|
||||
import { buttonProps } from 'ant-design-vue/es/button/button'
|
||||
import { usePermissionStore } from '@/store/permission';
|
||||
|
||||
interface PermissionButtonEmits {
|
||||
|
@ -60,16 +62,33 @@ interface PermissionButtonEmits {
|
|||
|
||||
const emits = defineEmits<PermissionButtonEmits>()
|
||||
|
||||
interface PermissionButtonProps extends ButtonProps {
|
||||
tooltip?: TooltipProps;
|
||||
popConfirm?: PopconfirmProps;
|
||||
hasPermission?: string | Array<string>;
|
||||
noButton?: boolean;
|
||||
}
|
||||
const props = withDefaults(defineProps<PermissionButtonProps>(), {
|
||||
noButton: false
|
||||
// interface PermissionButtonProps extends ButtonProps {
|
||||
// tooltip?: TooltipProps;
|
||||
// popConfirm?: PopconfirmProps;
|
||||
// hasPermission?: string | Array<string>;
|
||||
// noButton?: boolean;
|
||||
// }
|
||||
// const props = withDefaults(defineProps<PermissionButtonProps>(), {
|
||||
// noButton: false,
|
||||
// })
|
||||
const props = defineProps({
|
||||
noButton: {
|
||||
type: Boolean,
|
||||
default: () => false
|
||||
},
|
||||
tooltip: {
|
||||
type: Object as PropType<TooltipProps>,
|
||||
},
|
||||
popConfirm: {
|
||||
type: Object as PropType<PopconfirmProps>,
|
||||
},
|
||||
hasPermission: {
|
||||
type: String || Array,
|
||||
},
|
||||
...buttonProps()
|
||||
})
|
||||
const { tooltip, popConfirm, hasPermission, noButton, ...buttonProps } = props;
|
||||
|
||||
const { tooltip, popConfirm, hasPermission, noButton, ..._buttonProps } = props;
|
||||
|
||||
const permissionStore = usePermissionStore()
|
||||
|
||||
|
@ -82,7 +101,7 @@ const isPermission = computed(() => {
|
|||
const _isPermission = computed(() =>
|
||||
'hasPermission' in props && isPermission.value
|
||||
? 'disabled' in buttonProps
|
||||
? buttonProps.disabled
|
||||
? buttonProps.disabled as boolean
|
||||
: false
|
||||
: true
|
||||
)
|
||||
|
|
|
@ -74,7 +74,8 @@ const JTable = defineComponent<JTableProps>({
|
|||
slots: [
|
||||
'headerTitle', // 顶部左边插槽
|
||||
'card', // 卡片内容
|
||||
'rightExtraRender'
|
||||
'rightExtraRender',
|
||||
'paginationRender' // 分页
|
||||
],
|
||||
emits: [
|
||||
'modelChange', // 切换卡片和表格
|
||||
|
@ -354,6 +355,9 @@ const JTable = defineComponent<JTableProps>({
|
|||
{
|
||||
(!!_dataSource.value.length) && !props.noPagination && props.type === 'PAGE' &&
|
||||
<div class={styles['jtable-pagination']}>
|
||||
{
|
||||
slots?.paginationRender ?
|
||||
slots.paginationRender() :
|
||||
<Pagination
|
||||
size="small"
|
||||
total={total.value}
|
||||
|
@ -375,6 +379,7 @@ const JTable = defineComponent<JTableProps>({
|
|||
})
|
||||
}}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
|
|
@ -84,3 +84,34 @@ export const randomString = (length?: number) => {
|
|||
}
|
||||
return pwd;
|
||||
};
|
||||
|
||||
/**
|
||||
* 时间戳转时分秒文本
|
||||
* @param time
|
||||
* @returns
|
||||
*/
|
||||
export const timestampFormat = (time: number) => {
|
||||
let hour = 0;
|
||||
let minute = 0;
|
||||
let second = 0;
|
||||
const timeStr = 'hh小时mm分钟ss秒';
|
||||
|
||||
if (time) {
|
||||
if (time >= 60 * 60 * 1000) {
|
||||
hour = Math.trunc(time / (60 * 60 * 1000));
|
||||
}
|
||||
|
||||
if (time >= 60 * 1000) {
|
||||
minute = Math.trunc((time - hour * 60 * 60 * 1000) / (60 * 1000));
|
||||
}
|
||||
|
||||
second = Math.trunc(
|
||||
(time - hour * (60 * 60 * 1000) - minute * 60 * 1000) / 1000,
|
||||
);
|
||||
}
|
||||
|
||||
return timeStr
|
||||
.replace('hh', hour.toString())
|
||||
.replace('mm', minute.toString())
|
||||
.replace('ss', second.toString());
|
||||
};
|
|
@ -3,7 +3,7 @@
|
|||
<JTable
|
||||
ref="eventsRef"
|
||||
:columns="columns"
|
||||
:dataSource="dataSource"
|
||||
:request="_getEventList"
|
||||
model="TABLE"
|
||||
:bodyStyle="{padding: '0 24px'}"
|
||||
>
|
||||
|
@ -16,16 +16,24 @@
|
|||
</a-button>
|
||||
</template>
|
||||
</JTable>
|
||||
<a-button type="link" @click="detail(slotProps)">
|
||||
<AIcon type="SearchOutlined" />
|
||||
</a-button>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import moment from 'moment'
|
||||
import { getEventList } from '@/api/device/instance'
|
||||
import { useInstanceStore } from '@/store/instance'
|
||||
import { Modal } from 'ant-design-vue'
|
||||
|
||||
const events = defineProps({
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
}
|
||||
})
|
||||
const instanceStore = useInstanceStore()
|
||||
|
||||
const columns = ref<Record<string, any>>([
|
||||
{
|
||||
|
@ -41,8 +49,9 @@ const columns = ref<Record<string, any>>([
|
|||
scopedSlots: true,
|
||||
}
|
||||
])
|
||||
const params = ref<Record<string, any>>({})
|
||||
|
||||
const dataSource = ref<Record<string, any>[]>([])
|
||||
const _getEventList = () => getEventList(instanceStore.current.id || '', events.data.id || '', params.value)
|
||||
|
||||
watchEffect(() => {
|
||||
if(events.data?.valueType?.type === 'object'){
|
||||
|
@ -50,8 +59,7 @@ watchEffect(() => {
|
|||
columns.value.splice(0, 0, {
|
||||
key: i.id,
|
||||
title: i.name,
|
||||
dataIndex: `${i.id}_format`,
|
||||
// renderText: (text) => (typeof text === 'object' ? JSON.stringify(text) : text),
|
||||
dataIndex: `${i.id}_format`
|
||||
})
|
||||
})
|
||||
} else {
|
||||
|
@ -63,6 +71,13 @@ watchEffect(() => {
|
|||
})
|
||||
|
||||
const detail = () => {
|
||||
|
||||
Modal.info({
|
||||
title: () => '详情',
|
||||
width: 850,
|
||||
content: () => h('div', {}, [
|
||||
h('p', '暂未开发'),
|
||||
]),
|
||||
okText: '关闭'
|
||||
});
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,156 @@
|
|||
<template>
|
||||
<a-modal
|
||||
:maskClosable="false"
|
||||
:visible="true"
|
||||
title="编辑指标"
|
||||
@ok="handleSave"
|
||||
@cancel="handleCancel"
|
||||
:confirmLoading="loading"
|
||||
>
|
||||
<a-alert message="场景联动页面可引用指标配置触发条件" type="warning" showIcon />
|
||||
<a-form layout="vertical" ref="formRef" :model="modelRef" style="margin-top: 20px">
|
||||
<template v-for="(item, index) in modelRef.metrics" :key="index">
|
||||
<a-row type="flex" justify="space-between" align="bottom">
|
||||
<a-col :span="11">
|
||||
<a-form-item
|
||||
:rules="{
|
||||
required: true,
|
||||
message: `请${['date', 'boolean'].includes(data?.valueType?.type)? '选择': '输入'}指标值`,
|
||||
}"
|
||||
:name="['metrics', index, 'value', 0]"
|
||||
:label="item?.name || '指标值'"
|
||||
>
|
||||
<ValueItem
|
||||
v-model:modelValue="item.value[0]"
|
||||
:itemType="data.valueType?.type"
|
||||
:options="
|
||||
data.valueType?.type === 'boolean'
|
||||
? [
|
||||
{
|
||||
label: data.valueType?.trueText,
|
||||
value: String(data.valueType?.trueValue),
|
||||
},
|
||||
{
|
||||
label: data.valueType?.falseText,
|
||||
value: String(data.valueType?.falseValue),
|
||||
},
|
||||
]
|
||||
: undefined
|
||||
"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<template v-if="item.range">
|
||||
<a-col><div class="center-icon">~</div></a-col>
|
||||
<a-col :span="11">
|
||||
<a-form-item
|
||||
:name="['metrics', index, 'value', 1]"
|
||||
:rules="{
|
||||
required: true,
|
||||
message: `请${['date', 'boolean'].includes(data?.valueType?.type)? '选择': '输入'}指标值`,
|
||||
}"
|
||||
>
|
||||
<ValueItem
|
||||
v-model:modelValue="item.value[1]"
|
||||
:itemType="data.valueType?.type"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</template>
|
||||
</a-row>
|
||||
</template>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { queryMetric, saveMetric } from '@/api/device/instance'
|
||||
const emit = defineEmits(['close']);
|
||||
import { useInstanceStore } from "@/store/instance"
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
}
|
||||
})
|
||||
|
||||
const loading = ref<boolean>(false)
|
||||
const instanceStore = useInstanceStore()
|
||||
const formRef = ref();
|
||||
|
||||
const modelRef = reactive({
|
||||
metrics: []
|
||||
});
|
||||
|
||||
const handleCancel = () => {
|
||||
emit('close')
|
||||
}
|
||||
|
||||
watch(() => props.data.id, (newVal) => {
|
||||
if(newVal && instanceStore.current.id){
|
||||
queryMetric(instanceStore.current.id, props.data.id).then(resp => {
|
||||
if (resp.status === 200) {
|
||||
if (Array.isArray(resp?.result) && resp?.result.length) {
|
||||
const list = resp?.result.map((item: any) => {
|
||||
const val = Array.isArray(item?.value) ? [item?.value] : item?.value?.split(',')
|
||||
return {
|
||||
...item,
|
||||
value: val
|
||||
};
|
||||
});
|
||||
modelRef.metrics = list as any
|
||||
} else {
|
||||
const type = props.data.valueType?.type;
|
||||
if (type === 'boolean') {
|
||||
const list = props.data.expands?.metrics.map((item: any) => {
|
||||
const value = (item?.value || {}).map((i: any) => String(i)) || {};
|
||||
return {
|
||||
...item,
|
||||
value,
|
||||
};
|
||||
});
|
||||
modelRef.metrics = list || []
|
||||
} else {
|
||||
modelRef.metrics = props.data.expands?.metrics || []
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}, {immediate: true, deep: true})
|
||||
|
||||
const handleSave = () => {
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(async () => {
|
||||
loading.value = true;
|
||||
const list = (toRaw(modelRef)?.metrics || []).map((item: any) => {
|
||||
return {
|
||||
...item,
|
||||
value: item.value.join(','),
|
||||
};
|
||||
});
|
||||
const resp = await saveMetric(instanceStore.current.id || '', props.data.id || '', list).finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
if (resp.status === 200) {
|
||||
message.success('操作成功!');
|
||||
emit('close')
|
||||
formRef.value.resetFields();
|
||||
}
|
||||
})
|
||||
.catch((err: any) => {
|
||||
console.log('error', err);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.center-icon {
|
||||
height: 86px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
|
@ -5,33 +5,20 @@
|
|||
<div class="header">
|
||||
<div class="title">{{ _props.data.name }}</div>
|
||||
<div class="extra">
|
||||
<a-space>
|
||||
<a-tooltip title="设置属性至设备" v-if="data.expands?.type?.includes('write')">
|
||||
<AIcon
|
||||
type="EditOutlined"
|
||||
style="font-size: 12px"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-tooltip title="指标" v-if="(data.expands?.metrics || []).length > 0 &&
|
||||
['int', 'long', 'float', 'double', 'string', 'boolean', 'date'].includes(
|
||||
data.valueType?.type || '',
|
||||
)">
|
||||
<AIcon
|
||||
type="ClockCircleOutlined"
|
||||
style="font-size: 12px"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-tooltip title="获取最新属性值" v-if="data.expands?.type?.includes('read')">
|
||||
<AIcon
|
||||
type="SyncOutlined"
|
||||
style="font-size: 12px"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-tooltip title="详情">
|
||||
<AIcon
|
||||
type="BarsOutlined"
|
||||
style="font-size: 12px"
|
||||
/>
|
||||
<a-space :size="16">
|
||||
<a-tooltip
|
||||
v-for="i in actions"
|
||||
:key="i.key"
|
||||
v-bind="i.tooltip"
|
||||
>
|
||||
<a-button
|
||||
style="padding: 0; margin: 0"
|
||||
type="link"
|
||||
:disabled="i.disabled"
|
||||
@click="i.onClick && i.onClick(data)"
|
||||
>
|
||||
<AIcon :type="i.icon" style="color: #323130; font-size: 12px" />
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</div>
|
||||
|
@ -55,6 +42,10 @@ const _props = defineProps({
|
|||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
actions: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
});
|
||||
const loading = ref<boolean>(true);
|
||||
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
<template>
|
||||
<a-modal
|
||||
:maskClosable="false"
|
||||
:visible="true"
|
||||
title="编辑"
|
||||
@ok="handleSave"
|
||||
@cancel="handleCancel"
|
||||
:confirmLoading="loading"
|
||||
>
|
||||
<a-alert message="当数据来源为设备时,填写的值将下发到设备" type="warning" showIcon />
|
||||
<a-form :rules="rules" layout="vertical" ref="formRef" :model="modelRef" style="margin-top: 20px">
|
||||
<a-form-item name="propertyValue" :label="data?.name || '自定义属性'">
|
||||
<ValueItem
|
||||
v-model:modelValue="modelRef.propertyValue"
|
||||
:itemType="data?.valueType?.type || data?.dataType"
|
||||
:options="
|
||||
(data?.valueType?.type || data?.dataType) === 'enum'
|
||||
? (data?.valueType?.elements || []).map((item) => {
|
||||
return {
|
||||
label: item?.text,
|
||||
value: item?.value
|
||||
};
|
||||
})
|
||||
: undefined
|
||||
"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { setProperty } from '@/api/device/instance'
|
||||
const emit = defineEmits(['close']);
|
||||
import { useInstanceStore } from "@/store/instance"
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
}
|
||||
})
|
||||
|
||||
const loading = ref<boolean>(false)
|
||||
const instanceStore = useInstanceStore()
|
||||
|
||||
const formRef = ref();
|
||||
|
||||
const modelRef = reactive({
|
||||
propertyValue: undefined
|
||||
});
|
||||
|
||||
const handleCancel = () => {
|
||||
emit('close')
|
||||
}
|
||||
|
||||
const rules = {
|
||||
propertyValue: [
|
||||
{
|
||||
required: true,
|
||||
message: '该字段是必填字段',
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
const handleSave = () => {
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(async () => {
|
||||
loading.value = true;
|
||||
const resp = await setProperty(instanceStore.current?.id || '', {[props.data?.id]: toRaw(modelRef)?.propertyValue}).finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
if (resp.status === 200) {
|
||||
message.success('操作成功!');
|
||||
emit('close')
|
||||
formRef.value.resetFields();
|
||||
}
|
||||
})
|
||||
.catch((err: any) => {
|
||||
console.log('error', err);
|
||||
});
|
||||
}
|
||||
</script>
|
|
@ -1,14 +1,74 @@
|
|||
<template>
|
||||
<div>
|
||||
{{data.value || '--'}}
|
||||
<div class="value">
|
||||
{{value}}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { getImage } from "@/utils/comm";
|
||||
|
||||
const _data = defineProps({
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
value: {
|
||||
type: [Object, String, Number],
|
||||
default: '--'
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: 'card'
|
||||
}
|
||||
});
|
||||
|
||||
const imgMap = new Map<any, any>();
|
||||
imgMap.set('txt', getImage('/running/txt.png'));
|
||||
imgMap.set('doc', getImage('/running/doc.png'));
|
||||
imgMap.set('xls', getImage('/running/xls.png'));
|
||||
imgMap.set('ppt', getImage('/running/ppt.png'));
|
||||
imgMap.set('docx', getImage('/running/docx.png'));
|
||||
imgMap.set('xlsx', getImage('/running/xlsx.png'));
|
||||
imgMap.set('pptx', getImage('/running/pptx.png'));
|
||||
imgMap.set('pdf', getImage('/running/pdf.png'));
|
||||
imgMap.set('img', getImage('/running/img.png'));
|
||||
imgMap.set('error', getImage('/running/error.png'));
|
||||
imgMap.set('video', getImage('/running/video.png'));
|
||||
imgMap.set('other', getImage('/running/other.png'));
|
||||
imgMap.set('obj', getImage('/running/obj.png'));
|
||||
|
||||
const imgList = ['.jpg', '.png', '.swf', '.tiff'];
|
||||
const videoList = ['.m3u8', '.flv', '.mp4', '.rmvb', '.mvb'];
|
||||
const fileList = ['.txt', '.doc', '.xls', '.pdf', '.ppt', '.docx', '.xlsx', '.pptx'];
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.value {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
|
||||
.cardValue {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 60px;
|
||||
overflow: hidden;
|
||||
color: #323130;
|
||||
font-weight: 700;
|
||||
font-size: 24px;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
img {
|
||||
width: 60px;
|
||||
}
|
||||
}
|
||||
|
||||
.otherValue {
|
||||
img {
|
||||
width: 40px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,19 +1,20 @@
|
|||
<template>
|
||||
<JTable
|
||||
ref="metadataRef"
|
||||
:columns="columns"
|
||||
:dataSource="dataSource"
|
||||
:bodyStyle="{padding: 0}"
|
||||
:bodyStyle="{padding: '0 0 0 20px'}"
|
||||
>
|
||||
<template #headerTitle>
|
||||
<a-input-search
|
||||
placeholder="请输入名称"
|
||||
style="width: 300px; margin-bottom: 10px"
|
||||
@search="onSearch"
|
||||
v-model:value="value"
|
||||
:allowClear="true"
|
||||
/>
|
||||
</template>
|
||||
<template #card="slotProps">
|
||||
<PropertyCard :data="slotProps" />
|
||||
<PropertyCard :data="slotProps" :actions="getActions(slotProps)" />
|
||||
</template>
|
||||
<template #value="slotProps">
|
||||
<ValueRender :data="slotProps" />
|
||||
|
@ -28,41 +29,45 @@
|
|||
:key="i.key"
|
||||
v-bind="i.tooltip"
|
||||
>
|
||||
<a-popconfirm
|
||||
v-if="i.popConfirm"
|
||||
v-bind="i.popConfirm"
|
||||
:disabled="i.disabled"
|
||||
>
|
||||
<a-button
|
||||
:disabled="i.disabled"
|
||||
style="padding: 0"
|
||||
type="link"
|
||||
><AIcon :type="i.icon"
|
||||
/></a-button>
|
||||
</a-popconfirm>
|
||||
<a-button
|
||||
style="padding: 0"
|
||||
type="link"
|
||||
v-else
|
||||
:disabled="i.disabled"
|
||||
@click="i.onClick && i.onClick(slotProps)"
|
||||
>
|
||||
<a-button
|
||||
:disabled="i.disabled"
|
||||
style="padding: 0"
|
||||
type="link"
|
||||
><AIcon :type="i.icon"
|
||||
/></a-button>
|
||||
<AIcon :type="i.icon" />
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
<template #paginationRender>
|
||||
<a-pagination
|
||||
size="small"
|
||||
:total="total"
|
||||
:showQuickJumper="false"
|
||||
:showSizeChanger="true"
|
||||
:current="pageIndex + 1"
|
||||
:pageSize="pageSize"
|
||||
:pageSizeOptions="['8', '12', '24', '60', '100']"
|
||||
:show-total="(num) => `第 ${pageIndex * pageSize + 1} - ${(pageIndex + 1) * pageSize > num ? num : (pageIndex + 1) * pageSize} 条/总共 ${num} 条`"
|
||||
@change="pageChange"
|
||||
/>
|
||||
</template>
|
||||
</JTable>
|
||||
<Save v-if="editVisible" @close="editVisible = false" :data="currentInfo" />
|
||||
<Indicators v-if="indicatorVisible" @close="indicatorVisible = false" :data="currentInfo" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import _ from "lodash"
|
||||
import { PropertyData } from "../../../typings"
|
||||
import PropertyCard from './PropertyCard.vue'
|
||||
import ValueRender from './ValueRender.vue'
|
||||
import Save from './Save.vue'
|
||||
import Indicators from './Indicators.vue'
|
||||
import { getProperty } from '@/api/device/instance'
|
||||
import { useInstanceStore } from "@/store/instance"
|
||||
import { message } from "ant-design-vue"
|
||||
|
||||
const columns = [
|
||||
{
|
||||
|
@ -96,8 +101,17 @@ const _data = defineProps({
|
|||
default: () => []
|
||||
}
|
||||
})
|
||||
|
||||
const value = ref<string>('')
|
||||
const dataSource = ref<PropertyData[]>([])
|
||||
const _dataSource = ref<PropertyData[]>([])
|
||||
const pageIndex = ref<number>(0)
|
||||
const pageSize = ref<number>(8)
|
||||
const total = ref<number>(0)
|
||||
const editVisible = ref<boolean>(false) // 编辑
|
||||
const detailVisible = ref<boolean>(false) // 详情
|
||||
const currentInfo = ref<Record<string, any>>({})
|
||||
const instanceStore = useInstanceStore()
|
||||
const indicatorVisible = ref<boolean>(false) // 指标
|
||||
|
||||
const getActions = (data: Partial<Record<string, any>>) => {
|
||||
const arr = []
|
||||
|
@ -109,7 +123,8 @@ const getActions = (data: Partial<Record<string, any>>) => {
|
|||
},
|
||||
icon: 'EditOutlined',
|
||||
onClick: () => {
|
||||
|
||||
editVisible.value = true
|
||||
currentInfo.value = data
|
||||
},
|
||||
})
|
||||
}
|
||||
|
@ -123,7 +138,8 @@ const getActions = (data: Partial<Record<string, any>>) => {
|
|||
},
|
||||
icon: 'ClockCircleOutlined',
|
||||
onClick: () => {
|
||||
|
||||
indicatorVisible.value = true
|
||||
currentInfo.value = data
|
||||
},
|
||||
})
|
||||
}
|
||||
|
@ -134,8 +150,13 @@ const getActions = (data: Partial<Record<string, any>>) => {
|
|||
title: '获取最新属性值',
|
||||
},
|
||||
icon: 'SyncOutlined',
|
||||
onClick: () => {
|
||||
|
||||
onClick: async () => {
|
||||
if(instanceStore.current.id && data.id){
|
||||
const resp = await getProperty(instanceStore.current.id, data.id)
|
||||
if(resp.status === 200){
|
||||
message.success('操作成功!')
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
@ -147,17 +168,53 @@ const getActions = (data: Partial<Record<string, any>>) => {
|
|||
},
|
||||
icon: 'BarsOutlined',
|
||||
onClick: () => {
|
||||
|
||||
detailVisible.value = true
|
||||
currentInfo.value = data
|
||||
},
|
||||
})
|
||||
return arr
|
||||
}
|
||||
|
||||
watchEffect(() => {
|
||||
dataSource.value = _data.data as PropertyData[]
|
||||
const query = (page: number, size: number, value: string) => {
|
||||
pageIndex.value = page || 0
|
||||
pageSize.value = size || 8
|
||||
const _from = pageIndex.value * pageSize.value
|
||||
const _to = (pageIndex.value + 1) * pageSize.value
|
||||
const arr = _.cloneDeep(_dataSource.value)
|
||||
if(value){
|
||||
const li = arr.filter((i: any) => {
|
||||
return i?.name.indexOf(value) !== -1;
|
||||
})
|
||||
dataSource.value = li.slice(_from, _to)
|
||||
total.value = li.length
|
||||
} else {
|
||||
dataSource.value = arr.slice(_from, _to)
|
||||
total.value = arr.length
|
||||
}
|
||||
}
|
||||
|
||||
const pageChange = (page: number, size: number) => {
|
||||
if(size === pageSize.value) {
|
||||
query(page - 1, size, value.value)
|
||||
} else {
|
||||
query(0, size, value.value)
|
||||
}
|
||||
}
|
||||
|
||||
watch(() => _data.data,
|
||||
(newVal) => {
|
||||
if(newVal.length) {
|
||||
_dataSource.value = newVal as PropertyData[]
|
||||
query(0, 8, value.value)
|
||||
}
|
||||
}, {
|
||||
deep: true,
|
||||
immediate: true
|
||||
})
|
||||
|
||||
const onSearch = () => {};
|
||||
const onSearch = () => {
|
||||
query(0, 8, value.value)
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
|
|
|
@ -111,7 +111,6 @@ const tabChange = (key: string) => {
|
|||
display: flex;
|
||||
.property-box-left {
|
||||
width: 200px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
.property-box-right {
|
||||
flex: 1;
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
width="1100px"
|
||||
:visible="true"
|
||||
title="选择设备"
|
||||
okText="确定"
|
||||
cancelText="取消"
|
||||
@ok="handleOk"
|
||||
@cancel="handleCancel"
|
||||
:confirmLoading="btnLoading"
|
||||
|
@ -12,8 +14,9 @@
|
|||
<div style="margin-top: 10px">
|
||||
<Search
|
||||
:columns="columns"
|
||||
target="iot-card-management-search"
|
||||
target="iot-card-bind-device"
|
||||
@search="handleSearch"
|
||||
type="simple"
|
||||
/>
|
||||
<JTable
|
||||
ref="bindDeviceRef"
|
||||
|
@ -121,9 +124,8 @@ const columns = [
|
|||
},
|
||||
];
|
||||
|
||||
const handleSearch = (params: any) => {
|
||||
console.log(params);
|
||||
params.value = params;
|
||||
const handleSearch = (e: any) => {
|
||||
params.value = e;
|
||||
};
|
||||
|
||||
const onSelectChange = (record: any) => {
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
:maskClosable="false"
|
||||
:visible="true"
|
||||
title="导出"
|
||||
okText="确定"
|
||||
cancelText="取消"
|
||||
@ok="handleOk"
|
||||
@cancel="handleCancel"
|
||||
>
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
:maskClosable="false"
|
||||
:visible="true"
|
||||
title="导入"
|
||||
okText="确定"
|
||||
cancelText="取消"
|
||||
@ok="handleCancel"
|
||||
@cancel="handleCancel"
|
||||
>
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
width="600px"
|
||||
:visible="true"
|
||||
:title="type === 'add' ? '新增' : '编辑'"
|
||||
okText="确定"
|
||||
cancelText="取消"
|
||||
@ok="handleOk"
|
||||
@cancel="handleCancel"
|
||||
:confirmLoading="btnLoading"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<!-- 物联卡管理 -->
|
||||
<template>
|
||||
<page-container class="container">
|
||||
<page-container>
|
||||
<Search
|
||||
:columns="columns"
|
||||
target="iot-card-management-search"
|
||||
|
@ -124,10 +124,7 @@
|
|||
</slot>
|
||||
</template>
|
||||
<template #content>
|
||||
<h3
|
||||
class="card-item-content-title"
|
||||
@click.stop="handleView(slotProps.id)"
|
||||
>
|
||||
<h3 class="card-item-content-title">
|
||||
{{ slotProps.id }}
|
||||
</h3>
|
||||
<a-row>
|
||||
|
@ -553,6 +550,8 @@ const getActions = (
|
|||
popConfirm: data.deviceId
|
||||
? {
|
||||
title: '确认解绑设备?',
|
||||
okText: '确定',
|
||||
cancelText: '取消',
|
||||
onConfirm: async () => {
|
||||
unbind(data.id).then((resp: any) => {
|
||||
if (resp.status === 200) {
|
||||
|
@ -599,6 +598,8 @@ const getActions = (
|
|||
: data.cardStateType?.value === 'deactivate'
|
||||
? '确认复机?'
|
||||
: '确认停用?',
|
||||
okText: '确定',
|
||||
cancelText: '取消',
|
||||
onConfirm: async () => {
|
||||
if (data.cardStateType?.value === 'toBeActivated') {
|
||||
changeDeploy(data.id).then((resp) => {
|
||||
|
@ -633,6 +634,8 @@ const getActions = (
|
|||
},
|
||||
popConfirm: {
|
||||
title: '确认删除?',
|
||||
okText: '确定',
|
||||
cancelText: '取消',
|
||||
onConfirm: async () => {
|
||||
const resp: any = await del(data.id);
|
||||
if (resp.status === 200) {
|
||||
|
@ -648,9 +651,8 @@ const getActions = (
|
|||
];
|
||||
};
|
||||
|
||||
const handleSearch = (params: any) => {
|
||||
console.log(params);
|
||||
params.value = params;
|
||||
const handleSearch = (e: any) => {
|
||||
params.value = e;
|
||||
};
|
||||
|
||||
const onSelectChange = (keys: string[], rows: []) => {
|
||||
|
@ -671,13 +673,6 @@ const handleClick = (dt: any) => {
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 查看
|
||||
*/
|
||||
const handleView = (id: string) => {
|
||||
message.warn(id + '暂未开发');
|
||||
};
|
||||
|
||||
/**
|
||||
* 新增
|
||||
*/
|
||||
|
@ -792,25 +787,19 @@ const handelRemove = async () => {
|
|||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.container {
|
||||
.search {
|
||||
width: calc(100% - 330px);
|
||||
}
|
||||
.flow-text {
|
||||
.flow-text {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.progress-text {
|
||||
}
|
||||
.progress-text {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
:deep(.ant-progress-inner) {
|
||||
}
|
||||
:deep(.ant-progress-inner) {
|
||||
border-radius: 0px;
|
||||
}
|
||||
:deep(.ant-progress-bg) {
|
||||
}
|
||||
:deep(.ant-progress-bg) {
|
||||
border-radius: 0px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -0,0 +1,249 @@
|
|||
<template>
|
||||
<page-container>
|
||||
<a-card>
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="14">
|
||||
<TitleComponent data="详情" />
|
||||
<a-form
|
||||
:layout="'vertical'"
|
||||
ref="formRef"
|
||||
:rules="rules"
|
||||
:model="form"
|
||||
>
|
||||
<a-form-item
|
||||
label="平台类型"
|
||||
name="operatorName"
|
||||
required
|
||||
>
|
||||
<PlatformType
|
||||
:disabled="false"
|
||||
:model="'singular'"
|
||||
:itemStyle="{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'space-around',
|
||||
minWidth: '130px',
|
||||
}"
|
||||
:options="platformTypeList"
|
||||
v-model:value="form.operatorName"
|
||||
@change="typeChange"
|
||||
></PlatformType
|
||||
></a-form-item>
|
||||
<a-form-item label="名称" name="name">
|
||||
<a-input
|
||||
v-model:value="form.name"
|
||||
placeholder="请输入名称"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<!-- onelink -->
|
||||
<div v-if="form.operatorName === 'onelink'">
|
||||
<a-form-item label="App ID" name="appId">
|
||||
<a-input
|
||||
v-model:value="form.appId"
|
||||
placeholder="请输入App ID"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="Password" name="passWord">
|
||||
<a-input-password
|
||||
v-model:value="form.passWord"
|
||||
placeholder="请输入密码"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="接口地址" name="apiAddr">
|
||||
<a-input
|
||||
v-model:value="form.apiAddr"
|
||||
placeholder="请输入接口地址"
|
||||
/>
|
||||
</a-form-item>
|
||||
</div>
|
||||
<!-- ctwing -->
|
||||
<div v-if="form.operatorName === 'ctwing'">
|
||||
<a-form-item label="用户id" name="userId">
|
||||
<a-input
|
||||
v-model:value="form.userId"
|
||||
placeholder="请输入用户id"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="密码" name="passWord">
|
||||
<a-input-password
|
||||
v-model:value="form.passWord"
|
||||
placeholder="请输入密码"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="secretKey" name="secretKey">
|
||||
<a-input
|
||||
v-model:value="form.secretKey"
|
||||
placeholder="请输入secretKey"
|
||||
/>
|
||||
</a-form-item>
|
||||
</div>
|
||||
<!-- unicom -->
|
||||
<div v-if="form.operatorName === 'unicom'">
|
||||
<a-form-item label="App ID" name="appId">
|
||||
<a-input
|
||||
v-model:value="form.appId"
|
||||
placeholder="请输入App ID"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="App Secret" name="appSecret">
|
||||
<a-input
|
||||
v-model:value="form.appSecret"
|
||||
placeholder="请输入App Secret"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="创建者ID" name="openId">
|
||||
<a-input
|
||||
v-model:value="form.openId"
|
||||
placeholder="请输入创建者ID"
|
||||
/>
|
||||
</a-form-item>
|
||||
</div>
|
||||
|
||||
<a-form-item label="说明" name="explain">
|
||||
<a-textarea
|
||||
v-model:value="form.explain"
|
||||
placeholder="请输入说明"
|
||||
showCount
|
||||
:rows="3"
|
||||
:maxlength="200"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-divider />
|
||||
<a-button
|
||||
:loading="saveBtnLoading"
|
||||
type="primary"
|
||||
@click="handleSave"
|
||||
>
|
||||
保存
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-col>
|
||||
<a-col :span="10">
|
||||
<Doc :type="form.operatorName" />
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
</page-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { getImage } from '@/utils/comm';
|
||||
import PlatformType from '@/views/iot-card/components/PlatformType.vue';
|
||||
import { queryById, save, update } from '@/api/iot-card/platform';
|
||||
import { message } from 'ant-design-vue';
|
||||
import Doc from '../doc/index.vue';
|
||||
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
|
||||
const formRef = ref();
|
||||
const saveBtnLoading = ref<boolean>(false);
|
||||
|
||||
const form = reactive({
|
||||
operatorName: 'onelink',
|
||||
name: undefined,
|
||||
// onelink
|
||||
appId: undefined,
|
||||
passWord: undefined,
|
||||
apiAddr: undefined,
|
||||
// ctwing
|
||||
userId: undefined,
|
||||
secretKey: undefined,
|
||||
// unicom
|
||||
appSecret: undefined,
|
||||
openId: undefined,
|
||||
explain: undefined,
|
||||
});
|
||||
|
||||
const platformTypeList = [
|
||||
{
|
||||
label: '移动OneLink',
|
||||
value: 'onelink',
|
||||
imgUrl: getImage('/iot-card/onelink.png'),
|
||||
imgSize: ['78px', '20px'],
|
||||
},
|
||||
{
|
||||
label: '电信Ctwing',
|
||||
value: 'ctwing',
|
||||
imgUrl: getImage('/iot-card/ctwingcmp.png'),
|
||||
imgSize: ['52px', '25px'],
|
||||
},
|
||||
{
|
||||
label: '联通Unicom',
|
||||
value: 'unicom',
|
||||
imgUrl: getImage('/iot-card/unicom.png'),
|
||||
imgSize: ['56px', '41px'],
|
||||
},
|
||||
];
|
||||
|
||||
const rules = {
|
||||
name: [
|
||||
{ required: true, message: '请输入名称' },
|
||||
{ max: 64, message: '最多可输入64个字符' },
|
||||
],
|
||||
appId: [
|
||||
{ required: true, message: '请输入App ID' },
|
||||
{ max: 64, message: '最多可输入64个字符' },
|
||||
],
|
||||
passWord: [
|
||||
{ required: true, message: '请输入密码' },
|
||||
{ max: 64, message: '最多可输入64个字符' },
|
||||
],
|
||||
apiAddr: [{ required: true, message: '请输入接口地址' }],
|
||||
userId: [
|
||||
{ required: true, message: '请输入用户 ID' },
|
||||
{ max: 64, message: '最多可输入64个字符' },
|
||||
],
|
||||
secretKey: [{ required: true, message: '请输入secretKey' }],
|
||||
appSecret: [{ required: true, message: '请输入App Secret' }],
|
||||
openId: [{ required: true, message: '请输入创建者ID' }],
|
||||
explain: [{ required: false, max: 200, message: '最多可输入200个字符' }],
|
||||
};
|
||||
|
||||
const getDetail = async () => {
|
||||
if (route.params.id === ':id') return;
|
||||
const resp: any = await queryById(route.params.id);
|
||||
if (resp.status === 200) {
|
||||
Object.assign(form, resp.result, { ...resp.result.config });
|
||||
}
|
||||
};
|
||||
|
||||
const typeChange = (val: any) => {
|
||||
formRef.value.resetFields();
|
||||
form.operatorName = val;
|
||||
};
|
||||
|
||||
const handleSave = async () => {
|
||||
const data: any = await formRef.value.validate();
|
||||
const formData = {
|
||||
operatorName: data.operatorName,
|
||||
name: data.name,
|
||||
config: {
|
||||
appId: data.appId,
|
||||
passWord: data.passWord,
|
||||
apiAddr: data.apiAddr,
|
||||
userId: data.userId,
|
||||
secretKey: data.secretKey,
|
||||
appSecret: data.appSecret,
|
||||
openId: data.openId,
|
||||
},
|
||||
explain: data.explain,
|
||||
};
|
||||
saveBtnLoading.value = true;
|
||||
|
||||
const res: any =
|
||||
route.params.id === ':id'
|
||||
? await save(formData)
|
||||
: await update({ id: route.params.id, ...formData });
|
||||
if (res.status === 200) {
|
||||
message.success('保存成功!');
|
||||
router.back();
|
||||
}
|
||||
saveBtnLoading.value = false;
|
||||
};
|
||||
|
||||
getDetail();
|
||||
</script>
|
|
@ -0,0 +1,220 @@
|
|||
<template>
|
||||
<div v-if="type === 'onelink'" class="doc">
|
||||
<div class="url">
|
||||
中国移动物联卡能力开放平台:
|
||||
<a
|
||||
style="word-break: break-all"
|
||||
href="https://api.iot.10086.cn/api/index.html#/login"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
https://api.iot.10086.cn/api/index.html#/login
|
||||
</a>
|
||||
</div>
|
||||
<h1>1.概述</h1>
|
||||
<p>
|
||||
平台对接通过API的方式与三方系统进行数据对接,为物联卡的管理提供数据交互支持。
|
||||
</p>
|
||||
<h1>2.配置说明</h1>
|
||||
<h2>1、APP ID</h2>
|
||||
<p>
|
||||
第三方应用唯一标识,中国移动物联网全网管理员在 OneLink
|
||||
能力开放平台上分配并展示给集团客户。
|
||||
<br />
|
||||
获取路径:“中移物联卡能力开放平台”--“个人中心”--“客户信息”--“接入信息”
|
||||
</p>
|
||||
<div class="image">
|
||||
<a-image
|
||||
width="100%"
|
||||
:src="getImage('/iot-card/onelink-appid.png')"
|
||||
/>
|
||||
</div>
|
||||
<h2>2、Password</h2>
|
||||
<p>
|
||||
API 接入秘钥,由中国移动物联网提供,集团客户从“OneLink
|
||||
能力开放平台”获取。
|
||||
<br />
|
||||
获取路径:“中移物联卡能力开放平台”--“个人中心”--“客户信息”--“接入信息”
|
||||
</p>
|
||||
<div class="image">
|
||||
<a-image
|
||||
width="100%"
|
||||
:src="getImage('/iot-card/onelink-pass.png')"
|
||||
/>
|
||||
</div>
|
||||
<h2>3、接口地址</h2>
|
||||
<p>
|
||||
https://api.iot.10086.cn/v5/ec/get/token
|
||||
<br />
|
||||
token后缀请根据实际情况填写
|
||||
<br />
|
||||
示例:https://api.iot.10086.cn/v5/authService?appid=xxx&password=xxx&transid=xxx
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div v-if="type === 'ctwing'" class="doc">
|
||||
<div class="url">
|
||||
5G连接管理平台:
|
||||
<a
|
||||
style="word-break: break-all"
|
||||
href="https://cmp.ctwing.cn:4821/login"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
https://cmp.ctwing.cn:4821/login
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
<h1>1.概述</h1>
|
||||
<p>
|
||||
平台对接通过API的方式与三方系统进行数据对接,为物联卡的管理提供数据交互支持。
|
||||
</p>
|
||||
<h1>2.配置说明</h1>
|
||||
<h2>1、用户 id</h2>
|
||||
<p>
|
||||
5G连接管理平台用户的唯一标识,用于身份识别。
|
||||
<br />
|
||||
获取路径:“5G连接管理平台”--“能力开放”--“API网关账号管理”
|
||||
</p>
|
||||
<div class="image">
|
||||
<a-image
|
||||
width="100%"
|
||||
:src="getImage('/iot-card/ctwing-id.png')"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<h2>2、密码</h2>
|
||||
<p>
|
||||
用户id经加密之后的密码。
|
||||
<br />
|
||||
获取路径:“5G连接管理平台”--“能力开放”--“API网关账号管理”
|
||||
</p>
|
||||
<div class="image">
|
||||
<a-image
|
||||
width="100%"
|
||||
:src="getImage('/iot-card/ctwing-pass.png')"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<h2>3、secretKey</h2>
|
||||
<p>
|
||||
APP secret唯一秘钥。
|
||||
<br />
|
||||
获取路径:“5G连接管理平台”--“能力开放”--“API网关账号管理”
|
||||
</p>
|
||||
<div class="image">
|
||||
<a-image
|
||||
width="100%"
|
||||
:src="getImage('/iot-card/ctwing-secret.png')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="type === 'unicom'" class="doc">
|
||||
<div class="url">
|
||||
雁飞智连CMP平台:
|
||||
<a
|
||||
style="word-break: break-all"
|
||||
href=" https://cmp.10646.cn/webframe/login"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
https://cmp.10646.cn/webframe/login
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h1>1.概述</h1>
|
||||
<p>
|
||||
平台对接通过API的方式与三方系统进行数据对接,为物联卡的管理提供数据交互支持。
|
||||
</p>
|
||||
<h1>2.配置说明</h1>
|
||||
<h2>1、APP ID</h2>
|
||||
<p>
|
||||
第三方应用唯一标识。
|
||||
<br />
|
||||
获取路径:“雁飞智连CMP平台”--“我的应用”--“应用列表”
|
||||
</p>
|
||||
<div class="image">
|
||||
<a-image
|
||||
width="100%"
|
||||
:src="getImage('/iot-card/unicom-id.png')"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<h2>2、App Secret</h2>
|
||||
<p>
|
||||
API 接入秘钥。
|
||||
<br />
|
||||
获取路径:“雁飞智连CMP平台”--“我的应用”--“应用列表”
|
||||
</p>
|
||||
<div class="image">
|
||||
<a-image
|
||||
width="100%"
|
||||
:src="getImage('/iot-card/unicom-secret.png')"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<h2>3、创建者ID</h2>
|
||||
<p>
|
||||
接口参数中的 OpenId。
|
||||
<br />
|
||||
获取路径:“雁飞智连CMP平台”--“我的应用”--“应用列表”
|
||||
<br />
|
||||
</p>
|
||||
<div class="image">
|
||||
<a-image
|
||||
width="100%"
|
||||
:src="getImage('/iot-card/unicom-openid.png')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { getImage } from '@/utils/comm';
|
||||
|
||||
const props = defineProps({
|
||||
type: { type: String, default: 'onelink' },
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.doc {
|
||||
height: 800px;
|
||||
padding: 24px;
|
||||
overflow-y: auto;
|
||||
color: rgba(#000, 0.8);
|
||||
font-size: 14px;
|
||||
background-color: #fafafa;
|
||||
|
||||
.url {
|
||||
padding: 8px 16px;
|
||||
color: #2f54eb;
|
||||
background-color: rgba(#a7bdf7, 0.2);
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: 16px 0;
|
||||
color: rgba(#000, 0.85);
|
||||
font-weight: bold;
|
||||
font-size: 14px;
|
||||
|
||||
// &:first-child {
|
||||
// margin-top: 0;
|
||||
// }
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin: 6px 0;
|
||||
color: rgba(0, 0, 0, 0.8);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.image {
|
||||
margin: 16px 0;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,304 @@
|
|||
<!-- 平台对接 -->
|
||||
<template>
|
||||
<page-container>
|
||||
<Search
|
||||
:columns="columns"
|
||||
target="platform-search"
|
||||
@search="handleSearch"
|
||||
/>
|
||||
<JTable
|
||||
ref="platformRef"
|
||||
:columns="columns"
|
||||
:request="queryList"
|
||||
:defaultParams="{ sorts: [{ name: 'createTime', order: 'desc' }] }"
|
||||
:params="params"
|
||||
>
|
||||
<template #headerTitle>
|
||||
<a-space>
|
||||
<a-button type="primary" @click="handleAdd">
|
||||
<AIcon type="PlusOutlined" />新增
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
<template #card="slotProps">
|
||||
<CardBox
|
||||
:value="slotProps"
|
||||
:actions="getActions(slotProps, 'card')"
|
||||
v-bind="slotProps"
|
||||
:status="slotProps.state.value"
|
||||
:statusText="slotProps.state.text"
|
||||
:statusNames="{
|
||||
enabled: 'success',
|
||||
disabled: 'error',
|
||||
}"
|
||||
>
|
||||
<template #img>
|
||||
<slot name="img">
|
||||
<img :src="getImage('/iot-card/iot-card-bg.png')" />
|
||||
</slot>
|
||||
</template>
|
||||
<template #content>
|
||||
<h3 class="card-item-content-title">
|
||||
{{ slotProps.name }}
|
||||
</h3>
|
||||
<a-row>
|
||||
<a-col :span="12">
|
||||
<div class="card-item-content-text">
|
||||
平台类型
|
||||
</div>
|
||||
<div>{{ slotProps.operatorName }}</div>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<div class="card-item-content-text">说明</div>
|
||||
<div>{{ slotProps.explain }}</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
<template #actions="item">
|
||||
<a-tooltip
|
||||
v-bind="item.tooltip"
|
||||
:title="item.disabled && item.tooltip.title"
|
||||
>
|
||||
<a-popconfirm
|
||||
v-if="item.popConfirm"
|
||||
v-bind="item.popConfirm"
|
||||
:disabled="item.disabled"
|
||||
>
|
||||
<a-button :disabled="item.disabled">
|
||||
<AIcon
|
||||
type="DeleteOutlined"
|
||||
v-if="item.key === 'delete'"
|
||||
/>
|
||||
<template v-else>
|
||||
<AIcon :type="item.icon" />
|
||||
<span>{{ item.text }}</span>
|
||||
</template>
|
||||
</a-button>
|
||||
</a-popconfirm>
|
||||
<template v-else>
|
||||
<a-button
|
||||
:disabled="item.disabled"
|
||||
@click="item.onClick"
|
||||
>
|
||||
<AIcon
|
||||
type="DeleteOutlined"
|
||||
v-if="item.key === 'delete'"
|
||||
/>
|
||||
<template v-else>
|
||||
<AIcon :type="item.icon" />
|
||||
<span>{{ item.text }}</span>
|
||||
</template>
|
||||
</a-button>
|
||||
</template>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</CardBox>
|
||||
</template>
|
||||
<template #state="slotProps">
|
||||
<a-badge
|
||||
:text="slotProps.state.text"
|
||||
:status="
|
||||
slotProps.state.value === 'disabled'
|
||||
? 'error'
|
||||
: 'success'
|
||||
"
|
||||
/>
|
||||
</template>
|
||||
<template #action="slotProps">
|
||||
<a-space :size="16">
|
||||
<a-tooltip
|
||||
v-for="i in getActions(slotProps, 'table')"
|
||||
:key="i.key"
|
||||
v-bind="i.tooltip"
|
||||
>
|
||||
<a-popconfirm v-if="i.popConfirm" v-bind="i.popConfirm">
|
||||
<a-button
|
||||
:disabled="i.disabled"
|
||||
style="padding: 0"
|
||||
type="link"
|
||||
><AIcon :type="i.icon"
|
||||
/></a-button>
|
||||
</a-popconfirm>
|
||||
<a-button
|
||||
style="padding: 0"
|
||||
type="link"
|
||||
v-else
|
||||
@click="i.onClick && i.onClick(slotProps)"
|
||||
>
|
||||
<a-button
|
||||
:disabled="i.disabled"
|
||||
style="padding: 0"
|
||||
type="link"
|
||||
><AIcon :type="i.icon"
|
||||
/></a-button>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
</JTable>
|
||||
</page-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { getImage } from '@/utils/comm';
|
||||
import type { ActionsType } from '@/components/Table';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { queryList, update, del } from '@/api/iot-card/platform';
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const platformRef = ref<Record<string, any>>({});
|
||||
const params = ref<Record<string, any>>({});
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: '名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
ellipsis: true,
|
||||
search: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '平台类型',
|
||||
dataIndex: 'operatorName',
|
||||
key: 'operatorName',
|
||||
search: {
|
||||
type: 'select',
|
||||
options: [
|
||||
{ label: '移动OneLink', value: 'onelink' },
|
||||
{ label: '电信Ctwing', value: 'ctwing' },
|
||||
{ label: '联通Unicom', value: 'unicom' },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'state',
|
||||
key: 'state',
|
||||
scopedSlots: true,
|
||||
width: 120,
|
||||
search: {
|
||||
type: 'select',
|
||||
options: [
|
||||
{ label: '启用', value: 'enabled' },
|
||||
{ label: '禁用', value: 'disabled' },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '说明',
|
||||
dataIndex: 'explain',
|
||||
key: 'explain',
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
fixed: 'right',
|
||||
width: 250,
|
||||
scopedSlots: true,
|
||||
},
|
||||
];
|
||||
|
||||
const statusUpdate = async (data: any) => {
|
||||
const res = await update(data);
|
||||
if (res.status === 200) {
|
||||
message.success('操作成功');
|
||||
platformRef.value?.reload();
|
||||
}
|
||||
};
|
||||
|
||||
const getActions = (
|
||||
data: Partial<Record<string, any>>,
|
||||
type: 'card' | 'table',
|
||||
): ActionsType[] => {
|
||||
if (!data) return [];
|
||||
return [
|
||||
{
|
||||
key: 'edit',
|
||||
text: '编辑',
|
||||
tooltip: {
|
||||
title: '编辑',
|
||||
},
|
||||
icon: 'EditOutlined',
|
||||
onClick: () => {
|
||||
router.push(`/iot-card/Platform/detail/${data.id}`);
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'action',
|
||||
text: data.state.value === 'enabled' ? '禁用' : '启用',
|
||||
tooltip: {
|
||||
title: data.state.value === 'enabled' ? '禁用' : '启用',
|
||||
},
|
||||
icon:
|
||||
data.state.value === 'enabled'
|
||||
? 'StopOutlined'
|
||||
: 'PlayCircleOutlined',
|
||||
popConfirm: {
|
||||
title: `确认${
|
||||
data.state.value === 'enabled' ? '禁用' : '启用'
|
||||
}?`,
|
||||
okText: ' 确定',
|
||||
cancelText: '取消',
|
||||
onConfirm: () => {
|
||||
if (data.state.value === 'enabled') {
|
||||
statusUpdate({
|
||||
id: data.id,
|
||||
config: { ...data.config },
|
||||
state: 'disabled',
|
||||
operatorName: data.operatorName,
|
||||
});
|
||||
} else {
|
||||
statusUpdate({
|
||||
id: data.id,
|
||||
config: { ...data.config },
|
||||
state: 'enabled',
|
||||
operatorName: data.operatorName,
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'delete',
|
||||
text: '删除',
|
||||
tooltip: {
|
||||
title:
|
||||
data.state.value !== 'enabled' ? '删除' : '请先禁用再删除',
|
||||
},
|
||||
disabled: data.state.value === 'enabled',
|
||||
popConfirm: {
|
||||
title: '确认删除?',
|
||||
okText: ' 确定',
|
||||
cancelText: '取消',
|
||||
onConfirm: async () => {
|
||||
const resp: any = await del(data.id);
|
||||
if (resp.status === 200) {
|
||||
message.success('操作成功!');
|
||||
platformRef.value?.reload();
|
||||
} else {
|
||||
message.error('操作失败!');
|
||||
}
|
||||
},
|
||||
},
|
||||
icon: 'DeleteOutlined',
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
const handleSearch = (e: any) => {
|
||||
params.value = e;
|
||||
};
|
||||
|
||||
/**
|
||||
* 新增
|
||||
*/
|
||||
const handleAdd = () => {
|
||||
router.push(`/iot-card/Platform/detail/:id`)
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="less"></style>
|
|
@ -4,6 +4,8 @@
|
|||
width="600px"
|
||||
:visible="true"
|
||||
title="充值"
|
||||
okText="确定"
|
||||
cancelText="取消"
|
||||
@ok="handleOk"
|
||||
@cancel="handleCancel"
|
||||
:confirmLoading="btnLoading"
|
||||
|
|
|
@ -151,9 +151,8 @@ const getActions = (data: Partial<Record<string, any>>): ActionsType[] => {
|
|||
];
|
||||
};
|
||||
|
||||
const handleSearch = (params: any) => {
|
||||
console.log(params);
|
||||
params.value = params;
|
||||
const handleSearch = (e: any) => {
|
||||
params.value = e;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
<!-- 操作记录 -->
|
||||
<template>
|
||||
<page-container>
|
||||
<Search
|
||||
:columns="columns"
|
||||
target="record-search"
|
||||
@search="handleSearch"
|
||||
/>
|
||||
<JTable
|
||||
ref="RecordRef"
|
||||
:columns="columns"
|
||||
:request="queryList"
|
||||
:defaultParams="{ sorts: [{ name: 'time', order: 'desc' }] }"
|
||||
:params="params"
|
||||
:model="'TABLE'"
|
||||
>
|
||||
<template #time="slotProps">
|
||||
{{
|
||||
slotProps.time
|
||||
? moment(slotProps.time).format('YYYY-MM-DD HH:mm:ss')
|
||||
: ''
|
||||
}}
|
||||
</template>
|
||||
</JTable>
|
||||
</page-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { queryList } from '@/api/iot-card/record';
|
||||
import moment from 'moment';
|
||||
|
||||
const params = ref<Record<string, any>>({});
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: '卡号',
|
||||
dataIndex: 'cardId',
|
||||
key: 'cardId',
|
||||
ellipsis: true,
|
||||
search: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作类型',
|
||||
dataIndex: 'type',
|
||||
key: 'type',
|
||||
search: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作时间',
|
||||
dataIndex: 'time',
|
||||
key: 'time',
|
||||
scopedSlots: true,
|
||||
search: {
|
||||
type: 'date',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作人',
|
||||
dataIndex: 'operator',
|
||||
key: 'operator',
|
||||
ellipsis: true,
|
||||
search: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const handleSearch = (e: any) => {
|
||||
params.value = e;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="less"></style>
|
|
@ -0,0 +1,205 @@
|
|||
<!-- 平台类型 -->
|
||||
<template>
|
||||
<div :class="['radio-card-items', className, disabled ? 'disabled' : '']">
|
||||
<div
|
||||
v-for="item in options"
|
||||
:key="item.value"
|
||||
:style="itemStyle"
|
||||
:class="[
|
||||
'radio-card-item',
|
||||
keys.includes(item.value) ? 'checked' : '',
|
||||
]"
|
||||
@click="toggleOption(item.value)"
|
||||
>
|
||||
<div class="card-list">
|
||||
<div>
|
||||
<img
|
||||
:style="`width: ${item.imgSize?.[0]}; height: ${item.imgSize?.[1]}`"
|
||||
v-if="item.imgUrl"
|
||||
:src="item.imgUrl"
|
||||
alt=""
|
||||
/>
|
||||
</div>
|
||||
<div>{{ item.label }}</div>
|
||||
</div>
|
||||
<div class="checked-icon">
|
||||
<div><AIcon type="CheckOutlined" /></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const emit = defineEmits(['update:value', 'change'], );
|
||||
|
||||
const props = defineProps({
|
||||
options: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
required: true,
|
||||
},
|
||||
model: {
|
||||
validator: function (value) {
|
||||
return ['multiple', 'singular'].includes(value);
|
||||
},
|
||||
default: () => 'singular',
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
default: () => '',
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: () => true,
|
||||
},
|
||||
className: {
|
||||
type: String,
|
||||
},
|
||||
itemStyle: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
});
|
||||
|
||||
const keys = ref(
|
||||
!(props.model && props.model === 'singular') ? props.value : [props.value],
|
||||
);
|
||||
|
||||
const toggleOption = (key) => {
|
||||
if (props.disabled) {
|
||||
return;
|
||||
} else {
|
||||
const optionIndex = keys.value.includes(key);
|
||||
const newKeys = [...keys.value];
|
||||
const singular = props.model && props.model === 'singular';
|
||||
if (!optionIndex) {
|
||||
if (!(props.model && props.model === 'singular')) {
|
||||
newKeys.push(key);
|
||||
} else {
|
||||
newKeys[0] = key;
|
||||
}
|
||||
} else {
|
||||
newKeys.splice(optionIndex, 1);
|
||||
}
|
||||
emit('update:value', singular ? newKeys[0] : newKeys);
|
||||
emit('change', singular ? newKeys[0] : newKeys);
|
||||
}
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.value,
|
||||
(newVal) => {
|
||||
keys.value = !(props.model && props.model === 'singular')
|
||||
? newVal
|
||||
: [newVal];
|
||||
},
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@border: 1px solid @border-color-base;
|
||||
|
||||
.radio-card-items {
|
||||
display: flex;
|
||||
|
||||
.radio-card-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-width: 180px;
|
||||
padding: 22px 28px;
|
||||
overflow: hidden;
|
||||
font-size: 14px;
|
||||
border: @border;
|
||||
border-radius: @border-radius-base;
|
||||
|
||||
.card-list {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
div {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
> img {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
margin-right: 24px;
|
||||
}
|
||||
|
||||
> span {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
&:not(:last-child) {
|
||||
margin-right: 24px;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
color: @primary-color-hover;
|
||||
border-color: @primary-color-hover;
|
||||
}
|
||||
|
||||
.checked-icon {
|
||||
position: absolute;
|
||||
right: -22px;
|
||||
bottom: -22px;
|
||||
z-index: 2;
|
||||
display: none;
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
color: #fff;
|
||||
background-color: @primary-color-active;
|
||||
transform: rotate(-45deg);
|
||||
|
||||
> div {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
transform: rotate(45deg);
|
||||
|
||||
> span {
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
left: 6px;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.checked {
|
||||
position: relative;
|
||||
color: @primary-color-active;
|
||||
border-color: @primary-color-active;
|
||||
|
||||
> .checked-icon {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
.radio-card-item {
|
||||
color: @disabled-color;
|
||||
border-color: @disabled-bg;
|
||||
cursor: not-allowed;
|
||||
|
||||
.checked-icon {
|
||||
background-color: @disabled-active-bg;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
color: @disabled-color;
|
||||
border-color: @disabled-active-bg;
|
||||
}
|
||||
|
||||
&.checked {
|
||||
color: @disabled-color;
|
||||
border-color: @disabled-active-bg;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,7 +1,124 @@
|
|||
<template>
|
||||
<div class="page-container"></div>
|
||||
<div class="page-container">
|
||||
<div class="card-header">
|
||||
<div class="title">{{ title }}</div>
|
||||
<div class="tools">
|
||||
<a-space>
|
||||
<a-radio-group
|
||||
v-model:value="dimension"
|
||||
button-style="solid"
|
||||
>
|
||||
<a-radio-button value="today">今日</a-radio-button>
|
||||
<a-radio-button value="week">近一周</a-radio-button>
|
||||
<a-radio-button value="month">近一月</a-radio-button>
|
||||
<a-radio-button value="year">近一年</a-radio-button>
|
||||
</a-radio-group>
|
||||
<a-range-picker v-model:value="dateRange" />
|
||||
</a-space>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chart" ref="chartRef"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts"></script>
|
||||
<script setup lang="ts">
|
||||
import * as echarts from 'echarts';
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
// const { proxy } = <any>getCurrentInstance();
|
||||
|
||||
const props = defineProps({
|
||||
title: { type: String, default: '' },
|
||||
// 图表数据
|
||||
chartData: { type: Array, default: () => [] },
|
||||
});
|
||||
|
||||
// 统计时间维度
|
||||
const dimension = ref('week');
|
||||
const dateRange = ref<any>([]);
|
||||
|
||||
/**
|
||||
* 绘制图表
|
||||
*/
|
||||
const chartRef = ref();
|
||||
const createChart = () => {
|
||||
nextTick(() => {
|
||||
const myChart = echarts.init(chartRef.value as HTMLElement);
|
||||
|
||||
const options = {
|
||||
grid: {
|
||||
left: '7%',
|
||||
right: '5%',
|
||||
top: '5%',
|
||||
bottom: '5%',
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
// formatter: '{a}<br>{b}: {c}',
|
||||
axisPointer: {
|
||||
type: 'shadow',
|
||||
},
|
||||
},
|
||||
xAxis: [
|
||||
{
|
||||
data: props.chartData.map((m: any) => m.x),
|
||||
},
|
||||
],
|
||||
yAxis: [
|
||||
{
|
||||
show: false,
|
||||
axisTick: {
|
||||
show: false,
|
||||
},
|
||||
axisLine: {
|
||||
show: false,
|
||||
},
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
type: 'solid',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
series: [
|
||||
{
|
||||
name: '播放数量(人次)',
|
||||
type: 'line',
|
||||
symbol: 'circle',
|
||||
showSymbol: false,
|
||||
smooth: true,
|
||||
data: props.chartData.map(
|
||||
(m: any) => m.value && m.value.toFixed(2),
|
||||
),
|
||||
},
|
||||
],
|
||||
};
|
||||
myChart.setOption(options);
|
||||
window.addEventListener('resize', function () {
|
||||
myChart.resize();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.chartData,
|
||||
() => createChart(),
|
||||
{ immediate: true, deep: true },
|
||||
);
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.page-container {
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
.title {
|
||||
font-weight: 700;
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
.chart {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -19,7 +19,8 @@
|
|||
</div>
|
||||
<div class="top-card-footer">
|
||||
<template v-for="(item, index) in footer" :key="index">
|
||||
<a-badge :text="item.title" :status="item.status" />
|
||||
<span v-if="!item.status">{{ item.title }}</span>
|
||||
<a-badge v-else :text="item.title" :status="item.status" />
|
||||
<div class="footer-item-value">{{ item.value }}</div>
|
||||
</template>
|
||||
</div>
|
||||
|
|
|
@ -34,24 +34,49 @@
|
|||
:value="aggPlayingTotal"
|
||||
/>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
<Card title="播放数量(人次)" :chartData="chartData" />
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import TopCard from '@/views/media/DashBoard/components/TopCard.vue'
|
||||
import TopCard from '@/views/media/DashBoard/components/TopCard.vue';
|
||||
import Card from '@/views/media/DashBoard/components/Card.vue';
|
||||
import { getImage } from '@/utils/comm';
|
||||
import homeApi from '@/api/media/home';
|
||||
import dashboardApi from '@/api/media/dashboard';
|
||||
import type { Footer } from '@/views/media/DashBoard/typings';
|
||||
import encodeQuery from '@/utils/encodeQuery';
|
||||
import { timestampFormat } from '@/utils/utils';
|
||||
|
||||
// 设备
|
||||
const deviceFooter = ref<Footer[]>([]);
|
||||
const deviceTotal = ref(0);
|
||||
const getDeviceData = () => {
|
||||
homeApi.deviceCount().then((res) => {
|
||||
homeApi.deviceCount({}).then((res) => {
|
||||
deviceTotal.value = res.result;
|
||||
});
|
||||
|
||||
homeApi
|
||||
.deviceCount(encodeQuery({ terms: { state: 'online' } }))
|
||||
.then((res) => {
|
||||
deviceFooter.value[0] = {
|
||||
title: '在线',
|
||||
value: res.result,
|
||||
status: 'success',
|
||||
};
|
||||
});
|
||||
homeApi
|
||||
.deviceCount(encodeQuery({ terms: { state: 'offline' } }))
|
||||
.then((res) => {
|
||||
deviceFooter.value[1] = {
|
||||
title: '离线',
|
||||
value: res.result,
|
||||
status: 'error',
|
||||
};
|
||||
});
|
||||
};
|
||||
getDeviceData();
|
||||
|
||||
|
@ -59,9 +84,27 @@ getDeviceData();
|
|||
const channelFooter = ref<Footer[]>([]);
|
||||
const channelTotal = ref(0);
|
||||
const getChannelData = () => {
|
||||
homeApi.channelCount().then((res) => {
|
||||
homeApi.channelCount({}).then((res) => {
|
||||
channelTotal.value = res.result;
|
||||
});
|
||||
homeApi
|
||||
.channelCount({ terms: [{ column: 'status', value: 'online' }] })
|
||||
.then((res) => {
|
||||
channelFooter.value[0] = {
|
||||
title: '在线',
|
||||
value: res.result,
|
||||
status: 'success',
|
||||
};
|
||||
});
|
||||
homeApi
|
||||
.channelCount({ terms: [{ column: 'status$not', value: 'online' }] })
|
||||
.then((res) => {
|
||||
channelFooter.value[1] = {
|
||||
title: '离线',
|
||||
value: res.result,
|
||||
status: 'error',
|
||||
};
|
||||
});
|
||||
};
|
||||
getChannelData();
|
||||
|
||||
|
@ -71,6 +114,10 @@ const aggTotal = ref(0);
|
|||
const getAggData = () => {
|
||||
dashboardApi.agg().then((res) => {
|
||||
aggTotal.value = res.result.total;
|
||||
aggFooter.value.push({
|
||||
title: '总时长',
|
||||
value: timestampFormat(res.result.duration),
|
||||
});
|
||||
});
|
||||
};
|
||||
getAggData();
|
||||
|
@ -81,9 +128,32 @@ const aggPlayingTotal = ref(0);
|
|||
const getAggPlayingData = () => {
|
||||
dashboardApi.aggPlaying().then((res) => {
|
||||
aggTotal.value = res.result.playingTotal;
|
||||
aggPlayingFooter.value.push({
|
||||
title: '播放人数',
|
||||
value: res.result.playerTotal,
|
||||
});
|
||||
});
|
||||
};
|
||||
getAggPlayingData();
|
||||
|
||||
/**
|
||||
* 获取播放数量(人次)
|
||||
*/
|
||||
const chartData = ref([]);
|
||||
const getPlayCount = async () => {
|
||||
const params = {};
|
||||
dashboardApi.getPlayCount(params).then((res) => {
|
||||
let result: any = [];
|
||||
res.result.forEach((item: any) => {
|
||||
result = [...result, ...item.data];
|
||||
});
|
||||
chartData.value = result.map((m: any) => ({
|
||||
x: m.timeString,
|
||||
value: m.value,
|
||||
}));
|
||||
});
|
||||
};
|
||||
getPlayCount();
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
|
|
@ -10,7 +10,7 @@ export type AggPlaying = {
|
|||
|
||||
export type Footer = {
|
||||
title: string;
|
||||
value: number;
|
||||
value: number | string;
|
||||
status?: "default" | "error" | "success" | "warning" | "processing"
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue