60 changed files with 0 additions and 4402 deletions
@ -1,32 +0,0 @@ |
|||||||
import type { CommandToDeviceData, GetCloudCommandLogsParams, MessageToDeviceData } from './types' |
|
||||||
import { defHttp } from '@/utils/http/axios' |
|
||||||
|
|
||||||
export function getCloudCommandLogs(params: GetCloudCommandLogsParams) { |
|
||||||
return defHttp.get({ |
|
||||||
url: '/cloud/logPage', |
|
||||||
params, |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
export function getMessageContent(id: string) { |
|
||||||
return defHttp.get({ |
|
||||||
url: '/cloud/message', |
|
||||||
params: { |
|
||||||
id, |
|
||||||
}, |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
export function sendMessageToDevice(data: MessageToDeviceData) { |
|
||||||
return defHttp.post({ |
|
||||||
url: '/cloud/sendMessage', |
|
||||||
data, |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
export function sendCommandToDevice(data: CommandToDeviceData) { |
|
||||||
return defHttp.post({ |
|
||||||
url: '/cloud/sendCommand', |
|
||||||
data, |
|
||||||
}) |
|
||||||
} |
|
@ -1,78 +0,0 @@ |
|||||||
import type { Device, DevicePropertie, GetDeviceListParams } from './types' |
|
||||||
import type { Topic } from '@/api/product/types' |
|
||||||
import { defHttp } from '@/utils/http/axios' |
|
||||||
|
|
||||||
export function getDeviceList(params: GetDeviceListParams) { |
|
||||||
return defHttp.get<PageResult<Device>>({ |
|
||||||
url: '/device/page', |
|
||||||
params, |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
export function createDevice(data: Partial<Device>) { |
|
||||||
return defHttp.post({ |
|
||||||
url: '/device/save', |
|
||||||
data, |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
export function updateDevice(data: Partial<Device>) { |
|
||||||
return defHttp.post({ |
|
||||||
url: '/device/update', |
|
||||||
data, |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
export function deleteDevice(id: string) { |
|
||||||
return defHttp.post({ |
|
||||||
url: `/device/remove?id=${id}`, |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
export function getDeviceDetail(id: string) { |
|
||||||
return defHttp.get<Device>({ |
|
||||||
url: '/device/detail', |
|
||||||
params: { |
|
||||||
id, |
|
||||||
}, |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
export function getDeviceProperties(modelId: string, deviceSn: string) { |
|
||||||
return defHttp.get<{ |
|
||||||
properties?: DevicePropertie[] |
|
||||||
updateTime?: string |
|
||||||
}>({ |
|
||||||
url: '/device/properties', |
|
||||||
params: { |
|
||||||
deviceSn, |
|
||||||
modelId, |
|
||||||
}, |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
export function getDeviceTopicList(params: PageParam & { deviceId: string }) { |
|
||||||
return defHttp.get<PageResult<Topic>>({ |
|
||||||
url: '/device/topic/page', |
|
||||||
params, |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
export function getMqttConnectParams(deviceId: string) { |
|
||||||
return defHttp.get({ |
|
||||||
url: '/device/mqttLinkInfo', |
|
||||||
params: { |
|
||||||
deviceId, |
|
||||||
}, |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
export function getReportExample(productId: string, deviceSn: string) { |
|
||||||
return defHttp.get({ |
|
||||||
url: '/device/messageExample', |
|
||||||
params: { |
|
||||||
productId, |
|
||||||
deviceSn, |
|
||||||
}, |
|
||||||
}) |
|
||||||
} |
|
@ -1,46 +0,0 @@ |
|||||||
export interface GetDeviceListParams extends PageParam { |
|
||||||
productId?: string |
|
||||||
deviceSn?: string |
|
||||||
deviceName?: string |
|
||||||
isOnline?: BooleanFlag |
|
||||||
} |
|
||||||
|
|
||||||
export interface Device { |
|
||||||
id: string |
|
||||||
productId: string |
|
||||||
deviceSn: string |
|
||||||
deviceName: string |
|
||||||
deviceDesc: string |
|
||||||
isOnline: BooleanFlag |
|
||||||
} |
|
||||||
|
|
||||||
export interface DevicePropertie { |
|
||||||
identifier: string |
|
||||||
name: string |
|
||||||
unit: string |
|
||||||
value: string |
|
||||||
sort: number |
|
||||||
} |
|
||||||
|
|
||||||
export interface GetCloudCommandLogsParams extends PageParam { |
|
||||||
messageType?: number |
|
||||||
} |
|
||||||
|
|
||||||
export enum CloudCommandType { |
|
||||||
Attribute = 1, |
|
||||||
Command = 2, |
|
||||||
Message = 3, |
|
||||||
} |
|
||||||
|
|
||||||
export interface MessageToDeviceData { |
|
||||||
deviceId: string |
|
||||||
topic: string |
|
||||||
message: string |
|
||||||
} |
|
||||||
|
|
||||||
export interface CommandToDeviceData { |
|
||||||
deviceId: string |
|
||||||
modelId: string |
|
||||||
itemId: string |
|
||||||
value: string | number |
|
||||||
} |
|
@ -1,64 +0,0 @@ |
|||||||
import type { DeviceGroup, GetdeviceListByGroupParams } from './types' |
|
||||||
import { defHttp } from '@/utils/http/axios' |
|
||||||
|
|
||||||
export function getDeviceGroupTree() { |
|
||||||
return defHttp.get({ |
|
||||||
url: '/deviceGroup/tree', |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
export function getDevicegroupDetail(id: string) { |
|
||||||
return defHttp.get({ |
|
||||||
url: '/deviceGroup/detail', |
|
||||||
params: { |
|
||||||
id, |
|
||||||
}, |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
export function getDeviceListByGroup(params: GetdeviceListByGroupParams) { |
|
||||||
return defHttp.get({ |
|
||||||
url: '/device/pageByGroup', |
|
||||||
params, |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
export function createDeviceGroup(data: Partial<DeviceGroup>) { |
|
||||||
return defHttp.post({ |
|
||||||
url: '/deviceGroup/save', |
|
||||||
data, |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
export function updateDeviceGroup(data: Partial<DeviceGroup>) { |
|
||||||
return defHttp.post({ |
|
||||||
url: '/deviceGroup/update', |
|
||||||
data, |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
export function deleteDevicegroup(id: string) { |
|
||||||
return defHttp.post({ |
|
||||||
url: `/deviceGroup/remove?id=${id}`, |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
export function bindingDeviceToGroup(deviceGroupId: string, deviceIds: string) { |
|
||||||
return defHttp.post({ |
|
||||||
url: '/deviceGroup/bindDevice', |
|
||||||
data: { |
|
||||||
deviceGroupId, |
|
||||||
deviceIds, |
|
||||||
}, |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
export function unbindingDeviceFromGroup(deviceGroupId: string, deviceIds: string) { |
|
||||||
return defHttp.post({ |
|
||||||
url: '/deviceGroup/unbindDevice', |
|
||||||
data: { |
|
||||||
deviceGroupId, |
|
||||||
deviceIds, |
|
||||||
}, |
|
||||||
}) |
|
||||||
} |
|
@ -1,12 +0,0 @@ |
|||||||
export interface DeviceGroup { |
|
||||||
id: string |
|
||||||
parentId: string |
|
||||||
groupName: string |
|
||||||
remark: string |
|
||||||
} |
|
||||||
|
|
||||||
export interface GetdeviceListByGroupParams extends PageParam { |
|
||||||
groupId?: string |
|
||||||
productId?: string |
|
||||||
deviceSn?: string |
|
||||||
} |
|
@ -1,18 +0,0 @@ |
|||||||
import type { GetLogListParams, Log, MessageContent } from './types' |
|
||||||
import { defHttp } from '@/utils/http/axios' |
|
||||||
|
|
||||||
export function getLogList(params: GetLogListParams) { |
|
||||||
return defHttp.get<PageResult<Log>>({ |
|
||||||
url: '/device/log/page', |
|
||||||
params, |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
export function getMessageContent(id: string) { |
|
||||||
return defHttp.get<MessageContent>({ |
|
||||||
url: '/device/log/message', |
|
||||||
params: { |
|
||||||
id, |
|
||||||
}, |
|
||||||
}) |
|
||||||
} |
|
@ -1,25 +0,0 @@ |
|||||||
export interface GetLogListParams extends PageParam { |
|
||||||
productId: string |
|
||||||
deviceSn: string |
|
||||||
traceId: string |
|
||||||
bizType: string |
|
||||||
queryStartTime: string |
|
||||||
queryEndTime: string |
|
||||||
} |
|
||||||
|
|
||||||
export interface Log { |
|
||||||
productId: string |
|
||||||
deviceSn: string |
|
||||||
traceId: string |
|
||||||
messageId?: string |
|
||||||
bizType: number |
|
||||||
operation: string |
|
||||||
createTime: string |
|
||||||
code: number |
|
||||||
} |
|
||||||
|
|
||||||
export interface MessageContent { |
|
||||||
message: string |
|
||||||
topic: string |
|
||||||
createTime: string |
|
||||||
} |
|
@ -1,42 +0,0 @@ |
|||||||
import type { SubScription } from '../subscription/list/types' |
|
||||||
import type { GetProductListParmas, Product } from './types' |
|
||||||
import { defHttp } from '@/utils/http/axios' |
|
||||||
|
|
||||||
export function getProductList(params: GetProductListParmas) { |
|
||||||
return defHttp.get<PageResult<Product>>({ |
|
||||||
url: '/product/page', |
|
||||||
params, |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
export function getProductDetail(id: string) { |
|
||||||
return defHttp.get<Product & { subscribe: SubScription }>({ |
|
||||||
url: `/product/detail?id=${id}`, |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
export function createProduct(data: Partial<Product>) { |
|
||||||
return defHttp.post({ |
|
||||||
url: '/product/save', |
|
||||||
data, |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
export function updateProduct(data: Partial<Product>) { |
|
||||||
return defHttp.post({ |
|
||||||
url: '/product/update', |
|
||||||
data, |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
export function deleteProduct(id: string) { |
|
||||||
return defHttp.post({ |
|
||||||
url: `/product/remove?id=${id}`, |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
export function getAllProducts() { |
|
||||||
return defHttp.get<Pick<Product, 'id' | 'productName'>[]>({ |
|
||||||
url: '/product/select', |
|
||||||
}) |
|
||||||
} |
|
@ -1,68 +0,0 @@ |
|||||||
import type { GetModelAttributeListParams, ModelAttribute, ModelAttributeWithForm, ModelService, SimpleAttribute } from './types' |
|
||||||
import { defHttp } from '@/utils/http/axios' |
|
||||||
|
|
||||||
export function getAllModelServices(productId: string) { |
|
||||||
return defHttp.get<ModelService[]>({ |
|
||||||
url: '/thingModel/select', |
|
||||||
params: { |
|
||||||
productId, |
|
||||||
}, |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
export function createModelService(data: Partial<ModelService>) { |
|
||||||
return defHttp.post({ |
|
||||||
url: '/thingModel/save', |
|
||||||
data, |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
export function updateModelService(data: Partial<ModelService>) { |
|
||||||
return defHttp.post({ |
|
||||||
url: '/thingModel/update', |
|
||||||
data, |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
export function deleteModelService(id: string) { |
|
||||||
return defHttp.post({ |
|
||||||
url: `/thingModel/remove?id=${id}`, |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
export function getModelAttributeList(params: GetModelAttributeListParams) { |
|
||||||
return defHttp.get<PageResult<ModelAttribute>>({ |
|
||||||
url: '/thingModel/item/page', |
|
||||||
params, |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
export function createModelAttribute(data: ModelAttributeWithForm) { |
|
||||||
return defHttp.post({ |
|
||||||
url: '/thingModel/item/save', |
|
||||||
data, |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
export function updateModelAttribute(data: ModelAttributeWithForm) { |
|
||||||
return defHttp.post({ |
|
||||||
url: '/thingModel/item/update', |
|
||||||
data, |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
export function deleteModelAttribute(id: string) { |
|
||||||
return defHttp.post({ |
|
||||||
url: `/thingModel/item/remove?id=${id}`, |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
export function getAllModelAttributes(productId: string, itemType: number) { |
|
||||||
return defHttp.get<SimpleAttribute[]>({ |
|
||||||
url: '/thingModel/item/queryList', |
|
||||||
params: { |
|
||||||
productId, |
|
||||||
itemType, |
|
||||||
}, |
|
||||||
}) |
|
||||||
} |
|
@ -1,29 +0,0 @@ |
|||||||
import type { GetTopicListPrams, Topic } from './types' |
|
||||||
import { defHttp } from '@/utils/http/axios' |
|
||||||
|
|
||||||
export function getTopicList(params: GetTopicListPrams) { |
|
||||||
return defHttp.get<PageResult<Topic>>({ |
|
||||||
url: '/product/topic/page', |
|
||||||
params, |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
export function createTopic(data: Partial<Topic>) { |
|
||||||
return defHttp.post({ |
|
||||||
url: '/product/topic/save', |
|
||||||
data, |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
export function updateTopic(data: Partial<Topic>) { |
|
||||||
return defHttp.post({ |
|
||||||
url: '/product/topic/update', |
|
||||||
data, |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
export function deleteTopic(id: string) { |
|
||||||
return defHttp.post({ |
|
||||||
url: `/product/topic/remove?id=${id}`, |
|
||||||
}) |
|
||||||
} |
|
@ -1,120 +0,0 @@ |
|||||||
export interface GetProductListParmas extends PageParam { |
|
||||||
productName?: string |
|
||||||
networkType?: number |
|
||||||
networkProtocol?: number |
|
||||||
nodeType?: number |
|
||||||
securityType?: number |
|
||||||
dataType?: number |
|
||||||
} |
|
||||||
|
|
||||||
export interface Product { |
|
||||||
id: string |
|
||||||
tenantId: string |
|
||||||
uuid: string |
|
||||||
productName: string |
|
||||||
productDesc: string |
|
||||||
productKey: string |
|
||||||
productSecret: string |
|
||||||
nodeType: number |
|
||||||
networkType: number |
|
||||||
networkProtocol: number |
|
||||||
authType: number |
|
||||||
securityType: number |
|
||||||
dataType: number |
|
||||||
tsl: string |
|
||||||
isRelease: number |
|
||||||
} |
|
||||||
|
|
||||||
export enum TopicType { |
|
||||||
System = 1, |
|
||||||
Custom = 2, |
|
||||||
} |
|
||||||
|
|
||||||
export interface GetTopicListPrams extends PageParam { |
|
||||||
productId?: string |
|
||||||
topicCategory?: TopicType |
|
||||||
} |
|
||||||
|
|
||||||
export interface Topic { |
|
||||||
id: string |
|
||||||
topic: string |
|
||||||
topicType: number |
|
||||||
enableScript: BooleanFlag |
|
||||||
topicDesc: string |
|
||||||
productId: string |
|
||||||
} |
|
||||||
|
|
||||||
export interface ModelService { |
|
||||||
id: string |
|
||||||
productId: string |
|
||||||
serviceId: string |
|
||||||
tenantId: string |
|
||||||
description: string |
|
||||||
} |
|
||||||
|
|
||||||
export interface GetModelAttributeListParams extends PageParam { |
|
||||||
productId?: string |
|
||||||
modelId?: string |
|
||||||
} |
|
||||||
|
|
||||||
export enum ModelAttributeDataTypesEnum { |
|
||||||
Int32 = 'int32', |
|
||||||
Float = 'float', |
|
||||||
Bool = 'bool', |
|
||||||
Text = 'text', |
|
||||||
} |
|
||||||
|
|
||||||
export interface ModelAttribute { |
|
||||||
id: string |
|
||||||
name: string |
|
||||||
itemType: number |
|
||||||
identifier: string |
|
||||||
modelId: string |
|
||||||
modelName?: string |
|
||||||
tenantId: string |
|
||||||
method: string |
|
||||||
sort: number |
|
||||||
dataType: ModelAttributeDataTypesEnum |
|
||||||
dataSpecs?: { |
|
||||||
min?: number |
|
||||||
max?: number |
|
||||||
maxLength?: number |
|
||||||
trueDesc?: string |
|
||||||
falseDesc?: string |
|
||||||
scale?: string |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
export interface ModelAttributeWithForm { |
|
||||||
id: string |
|
||||||
name: string |
|
||||||
itemType: number |
|
||||||
identifier: string |
|
||||||
dataType: ModelAttributeDataTypesEnum |
|
||||||
min: number |
|
||||||
max: number |
|
||||||
maxLength: number |
|
||||||
scale: number |
|
||||||
unit: string |
|
||||||
trueDesc: string |
|
||||||
falseDesc: string |
|
||||||
method: string |
|
||||||
sort: number |
|
||||||
modelId: string |
|
||||||
} |
|
||||||
|
|
||||||
export interface SimpleAttribute { |
|
||||||
dataType: ModelAttributeDataTypesEnum |
|
||||||
itemType: number |
|
||||||
modelId: string |
|
||||||
modelName: string |
|
||||||
name: string |
|
||||||
dataSpecs: { |
|
||||||
min: number |
|
||||||
max: number |
|
||||||
maxLength: number |
|
||||||
trueDesc: string |
|
||||||
falseDesc: string |
|
||||||
scale: string |
|
||||||
} |
|
||||||
} |
|
@ -1,62 +0,0 @@ |
|||||||
import type { Consumer, GetConsumerListParams, Product } from './types' |
|
||||||
import { defHttp } from '@/utils/http/axios' |
|
||||||
|
|
||||||
export function getConsumerList(params: GetConsumerListParams) { |
|
||||||
return defHttp.get<PageResult<Consumer>>({ |
|
||||||
url: '/server/consumer/page', |
|
||||||
params, |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
export function createConsumer(data: Partial<Consumer>) { |
|
||||||
return defHttp.post({ |
|
||||||
url: '/server/consumer/save', |
|
||||||
data, |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
export function updateConsumer(data: Partial<Consumer>) { |
|
||||||
return defHttp.post({ |
|
||||||
url: '/server/consumer/update', |
|
||||||
data, |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
export function deleteConsumer(id: string) { |
|
||||||
return defHttp.post({ |
|
||||||
url: `/server/consumer/remove?id=${id}`, |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
export function getConsumerDetail(id: string) { |
|
||||||
return defHttp.get<Consumer>({ |
|
||||||
url: '/server/consumer/detail', |
|
||||||
params: { |
|
||||||
id, |
|
||||||
}, |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
export function getOnlineClientList(consumerToken: string) { |
|
||||||
return defHttp.get({ |
|
||||||
url: '/server/client/list', |
|
||||||
params: { |
|
||||||
consumerToken, |
|
||||||
}, |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
export function getSubscribeList(consumerId: string) { |
|
||||||
return defHttp.get<Product[]>({ |
|
||||||
url: '/server/consumer/subscribeList', |
|
||||||
params: { |
|
||||||
consumerId, |
|
||||||
}, |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
export function disSubscription(consumerId: string, serverSubscribeId: string) { |
|
||||||
return defHttp.post({ |
|
||||||
url: `/server/consumer/unsubscription?consumerId=${consumerId}&serverSubscribeId=${serverSubscribeId}`, |
|
||||||
}) |
|
||||||
} |
|
@ -1,18 +0,0 @@ |
|||||||
export interface GetConsumerListParams extends PageParam { |
|
||||||
consumerName?: string |
|
||||||
} |
|
||||||
|
|
||||||
export interface Consumer { |
|
||||||
id: string |
|
||||||
consumerName: string |
|
||||||
consumerToken: string |
|
||||||
createTime: string |
|
||||||
subscribes: string |
|
||||||
} |
|
||||||
|
|
||||||
export interface Product { |
|
||||||
id: string |
|
||||||
messageType: string |
|
||||||
productId: string |
|
||||||
tenantId: string |
|
||||||
} |
|
@ -1,44 +0,0 @@ |
|||||||
import type { GetSubscriptionListParams, SubScription } from './types' |
|
||||||
import { defHttp } from '@/utils/http/axios' |
|
||||||
|
|
||||||
export function getSubscriptionList(params: GetSubscriptionListParams) { |
|
||||||
return defHttp.get<PageResult<SubScription>>({ |
|
||||||
url: '/server/subscribe/page', |
|
||||||
params, |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
export function createSubscription(data: Pick<SubScription, 'productId' | 'messageType'>) { |
|
||||||
return defHttp.post({ |
|
||||||
url: '/server/subscribe/save', |
|
||||||
data, |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
export function updateSubscription(data: Pick<SubScription, 'productId' | 'messageType' | 'id'>) { |
|
||||||
return defHttp.post({ |
|
||||||
url: '/server/subscribe/update', |
|
||||||
data, |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
export function deleteSubscription(id: string) { |
|
||||||
return defHttp.post({ |
|
||||||
url: `/server/subscribe/remove?id=${id}`, |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
export function getAllSubscription() { |
|
||||||
return defHttp.get<SubScription[]>({ |
|
||||||
url: '/server/subscribe/select', |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
export function getSubscriptionDetail(id: string) { |
|
||||||
return defHttp.get({ |
|
||||||
url: '/server/consumer/detail', |
|
||||||
params: { |
|
||||||
id, |
|
||||||
}, |
|
||||||
}) |
|
||||||
} |
|
@ -1,11 +0,0 @@ |
|||||||
export interface GetSubscriptionListParams extends PageParam { |
|
||||||
productId?: string |
|
||||||
} |
|
||||||
|
|
||||||
export interface SubScription { |
|
||||||
id: string |
|
||||||
messageType: string |
|
||||||
productId: string |
|
||||||
tenantId: string |
|
||||||
createTime: string |
|
||||||
} |
|
@ -1,55 +0,0 @@ |
|||||||
<script lang="ts" setup> |
|
||||||
import { ref } from 'vue' |
|
||||||
import { getFormSchema } from './data' |
|
||||||
import { useMessage } from '@/hooks/web/useMessage' |
|
||||||
import { BasicForm, useForm } from '@/components/Form' |
|
||||||
import { BasicModal, useModalInner } from '@/components/Modal' |
|
||||||
import { createDevice, updateDevice } from '@/api/device-manage/device' |
|
||||||
import type { Device } from '@/api/device-manage/device/types' |
|
||||||
|
|
||||||
defineOptions({ name: 'DeviceFormModal' }) |
|
||||||
|
|
||||||
const emit = defineEmits(['success', 'register']) |
|
||||||
|
|
||||||
const isUpdate = ref(false) |
|
||||||
const [registerForm, { setFieldsValue, validate }] = useForm({ |
|
||||||
labelWidth: 100, |
|
||||||
baseColProps: { span: 24 }, |
|
||||||
schemas: getFormSchema(isUpdate), |
|
||||||
showActionButtonGroup: false, |
|
||||||
actionColOptions: { span: 23 }, |
|
||||||
}) |
|
||||||
|
|
||||||
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data: Device) => { |
|
||||||
isUpdate.value = true |
|
||||||
setFieldsValue({ ...data }) |
|
||||||
}) |
|
||||||
|
|
||||||
async function handleSubmit() { |
|
||||||
try { |
|
||||||
const values = await validate<Device>() |
|
||||||
setModalProps({ confirmLoading: true }) |
|
||||||
await (isUpdate.value ? updateDevice(values) : createDevice(values)) |
|
||||||
closeModal() |
|
||||||
emit('success') |
|
||||||
useMessage().createMessage.success('保存成功') |
|
||||||
} |
|
||||||
catch {} |
|
||||||
finally { |
|
||||||
setModalProps({ confirmLoading: false }) |
|
||||||
} |
|
||||||
} |
|
||||||
</script> |
|
||||||
|
|
||||||
<template> |
|
||||||
<BasicModal |
|
||||||
width="30%" |
|
||||||
:min-height="100" |
|
||||||
:title="isUpdate ? '编辑' : '新增'" |
|
||||||
:after-close="() => isUpdate = false" |
|
||||||
@register="registerModal" |
|
||||||
@ok="handleSubmit" |
|
||||||
> |
|
||||||
<BasicForm @register="registerForm" /> |
|
||||||
</BasicModal> |
|
||||||
</template> |
|
@ -1,104 +0,0 @@ |
|||||||
<script lang="ts" setup> |
|
||||||
import { computed, h, ref, unref } from 'vue' |
|
||||||
import { CloudSyncOutlined } from '@ant-design/icons-vue' |
|
||||||
import { Alert, Segmented } from 'ant-design-vue' |
|
||||||
import MessageModal from './MessageModal.vue' |
|
||||||
import SendCommandModal from './SendCommandModal.vue' |
|
||||||
import { BasicTable, useTable } from '@/components/Table' |
|
||||||
import { useModal } from '@/components/Modal' |
|
||||||
import { getCloudCommandLogs, getMessageContent } from '@/api/device-manage/device/cloud-command' |
|
||||||
import type { Device } from '@/api/device-manage/device/types' |
|
||||||
import { CloudCommandType } from '@/api/device-manage/device/types' |
|
||||||
import { usePermission } from '@/hooks/web/usePermission' |
|
||||||
|
|
||||||
defineProps<{ device?: Device }>() |
|
||||||
|
|
||||||
const [registerModal, { openModal }] = useModal<{ type: CloudCommandType, productId: string, deviceId: string }>() |
|
||||||
|
|
||||||
const commandTypes = [ |
|
||||||
{ |
|
||||||
label: '命令下发', |
|
||||||
value: CloudCommandType.Command, |
|
||||||
desc: '如果设备所属产品定义了命令功能,则您可以通过应用调用平台接口或者操作 “下发” 按钮下发命令,当前MQTT设备仅支持同步命令下发,NB设备仅支持异步命令下发。', |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '属性下发', |
|
||||||
value: CloudCommandType.Attribute, |
|
||||||
desc: '属性下发依赖产品模型,平台会以异步方式(属性下发后无需等待设备侧回复相应)下发消息给设备,当前仅MQTT设备支持属性下发。', |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '消息下发', |
|
||||||
value: CloudCommandType.Message, |
|
||||||
desc: '消息下发不依赖产品模型,平台会以异步方式(消息下发后无需等待设备侧回复相应)下发消息给设备,当前仅MQTT设备支持消息下发。', |
|
||||||
}, |
|
||||||
] |
|
||||||
const selectedCommonType = ref(CloudCommandType.Command) |
|
||||||
const selectedCommon = computed(() => commandTypes.find(item => item.value === selectedCommonType.value)) |
|
||||||
|
|
||||||
const [register, { reload }] = useTable({ |
|
||||||
api: params => getCloudCommandLogs({ ...params, messageType: unref(selectedCommon)?.value }), |
|
||||||
columns: [ |
|
||||||
{ |
|
||||||
title: '追踪 ID', |
|
||||||
dataIndex: 'traceId', |
|
||||||
}, |
|
||||||
{ |
|
||||||
title: '内容', |
|
||||||
width: 200, |
|
||||||
dataIndex: 'messageId', |
|
||||||
customRender({ value }) { |
|
||||||
return value && h(MessageModal, { |
|
||||||
buttonText: '查看', |
|
||||||
request: () => getMessageContent(value), |
|
||||||
}) |
|
||||||
}, |
|
||||||
}, |
|
||||||
{ |
|
||||||
title: '主题', |
|
||||||
dataIndex: 'operation', |
|
||||||
}, |
|
||||||
{ |
|
||||||
title: '创建时间', |
|
||||||
dataIndex: 'createTime', |
|
||||||
}, |
|
||||||
], |
|
||||||
bordered: true, |
|
||||||
inset: true, |
|
||||||
canResize: false, |
|
||||||
}) |
|
||||||
|
|
||||||
const { hasPermission } = usePermission() |
|
||||||
</script> |
|
||||||
|
|
||||||
<template> |
|
||||||
<div> |
|
||||||
<div flex="~ items-center gap-12px" mb="12px"> |
|
||||||
<Segmented v-model:value="selectedCommonType" :options="commandTypes" @change="() => reload()" /> |
|
||||||
<a-button |
|
||||||
v-if="hasPermission('device_cloud_command_action')" |
|
||||||
type="primary" |
|
||||||
@click="openModal(true, { |
|
||||||
deviceId: device!.id, |
|
||||||
type: selectedCommon!.value, |
|
||||||
productId: device!.productId, |
|
||||||
})" |
|
||||||
> |
|
||||||
<CloudSyncOutlined />下发 |
|
||||||
</a-button> |
|
||||||
<Alert |
|
||||||
type="info" |
|
||||||
show-icon |
|
||||||
class="py-4px text-13px" |
|
||||||
:message="selectedCommon?.desc" |
|
||||||
/> |
|
||||||
</div> |
|
||||||
|
|
||||||
<BasicTable @register="register" /> |
|
||||||
|
|
||||||
<SendCommandModal |
|
||||||
:title="selectedCommon?.label" |
|
||||||
@register="registerModal" |
|
||||||
@success="reload" |
|
||||||
/> |
|
||||||
</div> |
|
||||||
</template> |
|
@ -1,114 +0,0 @@ |
|||||||
<script lang="ts" setup> |
|
||||||
import { toRef } from 'vue' |
|
||||||
import { Card, Empty } from 'ant-design-vue' |
|
||||||
import { FieldTimeOutlined, SyncOutlined } from '@ant-design/icons-vue' |
|
||||||
import { useDeviceInfo } from './composables/useDeviceInfo' |
|
||||||
import { useDeviceProperties } from './composables/useDeviceProperties' |
|
||||||
import { Description } from '@/components/Description' |
|
||||||
import { useModelService } from '@/views/product/components/composables/useModelService' |
|
||||||
import { usePermission } from '@/hooks/web/usePermission' |
|
||||||
import { ProductTabEnums } from '@/views/product/data' |
|
||||||
import type { Device } from '@/api/device-manage/device/types' |
|
||||||
|
|
||||||
const props = defineProps<{ data: Device }>() |
|
||||||
|
|
||||||
const { scheam } = useDeviceInfo(toRef(props, 'data')) |
|
||||||
|
|
||||||
const { |
|
||||||
modelServiceList, |
|
||||||
selectedModelId, |
|
||||||
setSelectedModelId, |
|
||||||
} = useModelService(props.data.productId) |
|
||||||
|
|
||||||
const { |
|
||||||
isLoading, |
|
||||||
deviceProperties, |
|
||||||
reloadReviceProperties, |
|
||||||
} = useDeviceProperties(() => selectedModelId.value, () => props.data?.deviceSn) |
|
||||||
|
|
||||||
const { hasPermission } = usePermission() |
|
||||||
</script> |
|
||||||
|
|
||||||
<template> |
|
||||||
<div> |
|
||||||
<Description :schema="scheam" :data="data" :column="2" :label-style="{ width: '130px' }" /> |
|
||||||
|
|
||||||
<Card v-if="hasPermission('device_report_view')" mt="15px"> |
|
||||||
<div flex="~ items-center justify-between"> |
|
||||||
<div font-bold> |
|
||||||
最新上报数据 |
|
||||||
</div> |
|
||||||
<div flex="~ items-center"> |
|
||||||
<span v-if="deviceProperties?.updateTime" text="gray-400 center" mr="12px"> |
|
||||||
更新时间: {{ deviceProperties.updateTime }} |
|
||||||
</span> |
|
||||||
<a-button size="small" @click="reloadReviceProperties"> |
|
||||||
<SyncOutlined /> |
|
||||||
刷新 |
|
||||||
</a-button> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
|
|
||||||
<div v-loading="isLoading" flex="~ gap-12px" mt="12px"> |
|
||||||
<div w="20%" border="0 r-1 solid gray-50 dark:white dark:opacity-5"> |
|
||||||
<div |
|
||||||
v-for="item in modelServiceList" :key="item.id" |
|
||||||
flex="~ items-center justify-between" |
|
||||||
class="box-border h-60px cursor-pointer pl-10px hover:bg-gray-100 hover:dark:bg-white hover:dark:bg-opacity-5" |
|
||||||
border="0 b-1 solid gray-50 dark:white dark:opacity-5" |
|
||||||
:class="selectedModelId === item.id ? 'bg-gray-100 dark:bg-white dark:bg-opacity-5' : ''" |
|
||||||
@click="setSelectedModelId(item.id)" |
|
||||||
> |
|
||||||
<div> |
|
||||||
<div truncate> |
|
||||||
{{ item.serviceId }} |
|
||||||
</div> |
|
||||||
<div mt="5px" text="12px gray-500" truncate :title="item.description"> |
|
||||||
{{ item.description }} |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
|
|
||||||
<div |
|
||||||
v-if="deviceProperties?.properties" |
|
||||||
w-0 flex-1 |
|
||||||
grid="~ cols-4 rows-[160px] auto-rows-[160px] gap-12px" |
|
||||||
> |
|
||||||
<div |
|
||||||
v-for="item in deviceProperties.properties" |
|
||||||
:key="item.identifier" |
|
||||||
p="15px" box-border rounded |
|
||||||
shadow="hover:lg" transition="shadow" |
|
||||||
flex="~ col justify-between" |
|
||||||
style="background: linear-gradient(to right, rgba(243, 244, 246, 1), rgba(209, 216, 224, 1));" |
|
||||||
> |
|
||||||
<div> |
|
||||||
<div font-500 text="16px"> |
|
||||||
{{ item.name }} |
|
||||||
</div> |
|
||||||
<div text="gray-500"> |
|
||||||
{{ item.identifier }} |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
<div text="22px center"> |
|
||||||
"{{ item.unit || 'null' }}" |
|
||||||
</div> |
|
||||||
<div text="right gray-500 hover:gray-800"> |
|
||||||
<span v-if="hasPermission('device_report_history')" class="cursor-pointer"> |
|
||||||
<FieldTimeOutlined /> |
|
||||||
历史 |
|
||||||
</span> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
<div v-else text="center" flex-1> |
|
||||||
<Empty :image="Empty.PRESENTED_IMAGE_SIMPLE" /> |
|
||||||
<a-button size="small" @click="$router.push(`/product/detail/${data?.productId}/${ProductTabEnums.Model}/${selectedModelId}`)"> |
|
||||||
点击添加属性 |
|
||||||
</a-button> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
</Card> |
|
||||||
</div> |
|
||||||
</template> |
|
@ -1,59 +0,0 @@ |
|||||||
<script lang="ts" setup> |
|
||||||
import { h, ref } from 'vue' |
|
||||||
import { Modal } from 'ant-design-vue' |
|
||||||
import { EyeOutlined } from '@ant-design/icons-vue' |
|
||||||
import { useAsyncState } from '@vueuse/core' |
|
||||||
import type { DescItem } from '@/components/Description' |
|
||||||
import { Description } from '@/components/Description' |
|
||||||
import { JsonPreview } from '@/components/CodeEditor' |
|
||||||
import { noop } from '@/utils' |
|
||||||
|
|
||||||
const props = defineProps<{ |
|
||||||
buttonText?: string |
|
||||||
request: (...args: any[]) => Promise<{ topic: string, message: string }> |
|
||||||
}>() |
|
||||||
|
|
||||||
const { state, execute, isLoading } = useAsyncState(props.request, undefined, { immediate: false }) |
|
||||||
|
|
||||||
const open = ref(false) |
|
||||||
function handleOpen() { |
|
||||||
if (state.value) |
|
||||||
return open.value = true |
|
||||||
|
|
||||||
execute() |
|
||||||
.then(() => open.value = true) |
|
||||||
.catch(noop) |
|
||||||
} |
|
||||||
|
|
||||||
const schema: DescItem[] = [ |
|
||||||
{ |
|
||||||
label: 'Topic', |
|
||||||
field: 'topic', |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '内容', |
|
||||||
field: 'message', |
|
||||||
render(value) { |
|
||||||
let content = value |
|
||||||
try { |
|
||||||
content = JSON.parse(value) |
|
||||||
} |
|
||||||
catch {} |
|
||||||
return h(JsonPreview, { |
|
||||||
data: content, |
|
||||||
}) |
|
||||||
}, |
|
||||||
}, |
|
||||||
] |
|
||||||
</script> |
|
||||||
|
|
||||||
<template> |
|
||||||
<a-button size="small" :loading="isLoading" @click="handleOpen"> |
|
||||||
<EyeOutlined /> |
|
||||||
{{ buttonText || '查看参数' }} |
|
||||||
</a-button> |
|
||||||
|
|
||||||
<Modal v-model:open="open" title="上报示例" :footer="null" width="50%"> |
|
||||||
<Description :data="state" :schema="schema" :column="1" /> |
|
||||||
</Modal> |
|
||||||
</template> |
|
@ -1,77 +0,0 @@ |
|||||||
<script lang="ts" setup> |
|
||||||
import { ref } from 'vue' |
|
||||||
import { Modal } from 'ant-design-vue' |
|
||||||
import { EyeOutlined } from '@ant-design/icons-vue' |
|
||||||
import { useAsyncState } from '@vueuse/core' |
|
||||||
import { getMqttConnectParams } from '@/api/device-manage/device' |
|
||||||
import type { DescItem } from '@/components/Description' |
|
||||||
import { Description } from '@/components/Description' |
|
||||||
import { noop } from '@/utils' |
|
||||||
import { copyText } from '@/utils/copyTextToClipboard' |
|
||||||
|
|
||||||
const props = defineProps<{ deviceId: string }>() |
|
||||||
|
|
||||||
const { state, execute, isLoading } = useAsyncState(() => getMqttConnectParams(props.deviceId), undefined, { immediate: false }) |
|
||||||
|
|
||||||
const open = ref(false) |
|
||||||
function handleOpen() { |
|
||||||
if (state.value) |
|
||||||
return open.value = true |
|
||||||
|
|
||||||
execute() |
|
||||||
.then(() => { |
|
||||||
open.value = true |
|
||||||
}) |
|
||||||
.catch(noop) |
|
||||||
} |
|
||||||
|
|
||||||
const schema: DescItem[] = [ |
|
||||||
{ |
|
||||||
label: '服务器地址 (hostUrl)', |
|
||||||
field: 'hostUrl', |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '端口 (port)', |
|
||||||
field: 'port', |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: 'ClientID (clientId)', |
|
||||||
field: 'clientId', |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '用户名 (username)', |
|
||||||
field: 'username', |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '密码 (password)', |
|
||||||
field: 'password', |
|
||||||
}, |
|
||||||
] |
|
||||||
|
|
||||||
function handleCopy() { |
|
||||||
const data = schema.map((item) => { |
|
||||||
return { |
|
||||||
key: item.field, |
|
||||||
keyName: item.label, |
|
||||||
value: state.value[item.field], |
|
||||||
} |
|
||||||
}) |
|
||||||
copyText(JSON.stringify(data)) |
|
||||||
} |
|
||||||
</script> |
|
||||||
|
|
||||||
<template> |
|
||||||
<a-button size="small" :loading="isLoading" @click="handleOpen"> |
|
||||||
<EyeOutlined /> |
|
||||||
查看参数 |
|
||||||
</a-button> |
|
||||||
|
|
||||||
<Modal v-model:open="open" title="MQTT 连接参数" :footer="null"> |
|
||||||
<Description :data="state" :schema="schema" :column="1" /> |
|
||||||
<div text="center" mt="10px"> |
|
||||||
<a-button size="small" @click="handleCopy"> |
|
||||||
一键复制 |
|
||||||
</a-button> |
|
||||||
</div> |
|
||||||
</Modal> |
|
||||||
</template> |
|
@ -1,185 +0,0 @@ |
|||||||
<script lang="ts" setup> |
|
||||||
import { BasicModal, useModalInner } from '@/components/Modal' |
|
||||||
import type { FormSchema, FormSchemaInner, Rule } from '@/components/Form' |
|
||||||
import { BasicForm, useForm } from '@/components/Form' |
|
||||||
import { CloudCommandType } from '@/api/device-manage/device/types' |
|
||||||
import { sendCommandToDevice, sendMessageToDevice } from '@/api/device-manage/device/cloud-command' |
|
||||||
import CodeEditor from '@/components/CodeEditor/src/CodeEditor.vue' |
|
||||||
import { getAllModelAttributes } from '@/api/product/model' |
|
||||||
import { getDeviceTopicList } from '@/api/device-manage/device' |
|
||||||
import { ModelAttributeDataTypesEnum, type SimpleAttribute } from '@/api/product/types' |
|
||||||
import { useMessage } from '@/hooks/web/useMessage' |
|
||||||
|
|
||||||
const emit = defineEmits(['register', 'success']) |
|
||||||
|
|
||||||
const [registerForm, { updateSchema, resetSchema, validate, setFieldsValue, clearValidate }] = useForm({ |
|
||||||
schemas: [], |
|
||||||
labelWidth: 100, |
|
||||||
baseColProps: { span: 24 }, |
|
||||||
showActionButtonGroup: false, |
|
||||||
actionColOptions: { span: 23 }, |
|
||||||
}) |
|
||||||
|
|
||||||
let productId: string |
|
||||||
let deviceId: string |
|
||||||
let commandType: CloudCommandType |
|
||||||
const CommandAndAttributeSchemas: FormSchema[] = [ |
|
||||||
{ |
|
||||||
field: 'itemId', |
|
||||||
label: '选择属性', |
|
||||||
required: true, |
|
||||||
component: 'ApiSelect', |
|
||||||
componentProps: { |
|
||||||
api: () => getAllModelAttributes(productId!, commandType), |
|
||||||
valueField: 'id', |
|
||||||
labelField: 'name', |
|
||||||
allowClear: false, |
|
||||||
onChange(_, option: SimpleAttribute & { label: string }) { // label is SimpleAttribute.name |
|
||||||
if (!option) |
|
||||||
return |
|
||||||
|
|
||||||
const { dataType, label, dataSpecs, modelId } = option |
|
||||||
setFieldsValue({ modelId, value: undefined }) |
|
||||||
// avoid non-null check |
|
||||||
clearValidate() |
|
||||||
|
|
||||||
let schema: Partial<FormSchemaInner> = {} |
|
||||||
if (dataType === ModelAttributeDataTypesEnum.Bool) { |
|
||||||
schema = { |
|
||||||
rules: [], // remove rules cache |
|
||||||
component: 'RadioGroup', |
|
||||||
componentProps: { |
|
||||||
options: [ |
|
||||||
{ label: dataSpecs.falseDesc, value: 0 }, |
|
||||||
{ label: dataSpecs.trueDesc, value: 1 }, |
|
||||||
], |
|
||||||
}, |
|
||||||
} |
|
||||||
} |
|
||||||
else { |
|
||||||
let rules: Rule[] = [] |
|
||||||
switch (dataType) { |
|
||||||
case ModelAttributeDataTypesEnum.Text: |
|
||||||
rules = [{ max: dataSpecs.maxLength, message: `限制最大长度为 ${dataSpecs.maxLength}` }] |
|
||||||
break |
|
||||||
case ModelAttributeDataTypesEnum.Float: |
|
||||||
case ModelAttributeDataTypesEnum.Int32: |
|
||||||
rules = [ |
|
||||||
{ type: 'number', min: +dataSpecs.min, max: +dataSpecs.max, message: `数值范围为: ${dataSpecs.min}-${dataSpecs.max}` }, |
|
||||||
] |
|
||||||
break |
|
||||||
default: { |
|
||||||
const _exhaustiveCheck: never = dataType |
|
||||||
return _exhaustiveCheck |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
schema = { |
|
||||||
rules, |
|
||||||
// if it isn't text, then it's a float or int32 |
|
||||||
component: dataType === ModelAttributeDataTypesEnum.Text ? 'InputTextArea' : 'InputNumber', |
|
||||||
componentProps: dataType !== ModelAttributeDataTypesEnum.Text |
|
||||||
? { |
|
||||||
precision: dataType === ModelAttributeDataTypesEnum.Float ? dataSpecs.scale : 0, |
|
||||||
} |
|
||||||
: { rows: 3 }, |
|
||||||
} |
|
||||||
} |
|
||||||
updateSchema({ |
|
||||||
label, |
|
||||||
field: 'value', |
|
||||||
ifShow: true, |
|
||||||
...schema, |
|
||||||
}) |
|
||||||
}, |
|
||||||
}, |
|
||||||
}, |
|
||||||
{ |
|
||||||
field: 'value', |
|
||||||
fields: ['modelId'], |
|
||||||
required: true, |
|
||||||
component: 'Input', |
|
||||||
ifShow: false, |
|
||||||
}, |
|
||||||
] |
|
||||||
const MessageSchemas: FormSchema[] = [ |
|
||||||
{ |
|
||||||
field: 'topic', |
|
||||||
label: 'Topic', |
|
||||||
required: true, |
|
||||||
component: 'ApiSelect', |
|
||||||
componentProps: { |
|
||||||
api: async () => (await getDeviceTopicList({ deviceId, current: 1, size: 500 })).records, |
|
||||||
valueField: 'topic', |
|
||||||
labelField: 'topic', |
|
||||||
}, |
|
||||||
}, |
|
||||||
{ |
|
||||||
field: 'message', |
|
||||||
label: '指令内容', |
|
||||||
rules: [ |
|
||||||
{ |
|
||||||
async validator(_, value) { |
|
||||||
try { |
|
||||||
const code = JSON.stringify(JSON.parse(value)) |
|
||||||
if (code === '{}') |
|
||||||
// eslint-disable-next-line unicorn/error-message |
|
||||||
throw new Error() |
|
||||||
} |
|
||||||
catch { |
|
||||||
// eslint-disable-next-line prefer-promise-reject-errors |
|
||||||
return Promise.reject('指令内容必须是 JSON 格式, 且不能为空') |
|
||||||
} |
|
||||||
}, |
|
||||||
required: true, |
|
||||||
}, |
|
||||||
], |
|
||||||
slot: 'Message', |
|
||||||
helpMessage: '指令内容必须是 JSON 格式', |
|
||||||
defaultValue: {}, |
|
||||||
componentProps: { |
|
||||||
rows: 10, |
|
||||||
}, |
|
||||||
}, |
|
||||||
] |
|
||||||
const [register, { closeModal, setModalProps }] = useModalInner((params: { type: CloudCommandType, productId: string, deviceId: string }) => { |
|
||||||
productId = params.productId |
|
||||||
deviceId = params.deviceId |
|
||||||
commandType = params.type |
|
||||||
resetSchema(commandType === CloudCommandType.Message ? MessageSchemas : CommandAndAttributeSchemas) |
|
||||||
}) |
|
||||||
|
|
||||||
async function handleSubmit() { |
|
||||||
try { |
|
||||||
const values = await validate<any>() |
|
||||||
setModalProps({ confirmLoading: true }) |
|
||||||
values.deviceId = deviceId |
|
||||||
const isSendMessage = commandType === CloudCommandType.Message |
|
||||||
await (isSendMessage ? sendMessageToDevice(values) : sendCommandToDevice(values)) |
|
||||||
useMessage().createMessage.success('下发成功') |
|
||||||
emit('success') |
|
||||||
closeModal() |
|
||||||
} |
|
||||||
catch {} |
|
||||||
finally { |
|
||||||
setModalProps({ confirmLoading: false }) |
|
||||||
} |
|
||||||
} |
|
||||||
</script> |
|
||||||
|
|
||||||
<template> |
|
||||||
<BasicModal |
|
||||||
v-bind="$attrs" |
|
||||||
:min-height="100" |
|
||||||
@register="register" |
|
||||||
@ok="handleSubmit" |
|
||||||
> |
|
||||||
<BasicForm @register="registerForm"> |
|
||||||
<template #Message="{ model, field }"> |
|
||||||
<div h="200px" border="1px solid gray-200 dark:#424242" w-full overflow-hidden rounded> |
|
||||||
<CodeEditor v-model:value="model[field]" /> |
|
||||||
</div> |
|
||||||
</template> |
|
||||||
</BasicForm> |
|
||||||
</BasicModal> |
|
||||||
</template> |
|
@ -1,53 +0,0 @@ |
|||||||
<script lang="ts" setup> |
|
||||||
import { h } from 'vue' |
|
||||||
import { Alert, Tag } from 'ant-design-vue' |
|
||||||
import { SyncOutlined } from '@ant-design/icons-vue' |
|
||||||
import { useRoute } from 'vue-router' |
|
||||||
import { BasicTable, useTable } from '@/components/Table' |
|
||||||
import { getDeviceTopicList } from '@/api/device-manage/device' |
|
||||||
import { useSystemEnumStore } from '@/store/modules/systemEnum' |
|
||||||
|
|
||||||
const route = useRoute() |
|
||||||
const { getSystemEnumLabel } = useSystemEnumStore() |
|
||||||
const [register, { reload }] = useTable({ |
|
||||||
api(params) { |
|
||||||
return getDeviceTopicList({ ...params, deviceId: route.params.id as string }) |
|
||||||
}, |
|
||||||
columns: [ |
|
||||||
{ |
|
||||||
title: 'Topic', |
|
||||||
dataIndex: 'topic', |
|
||||||
}, |
|
||||||
{ |
|
||||||
title: '操作权限', |
|
||||||
dataIndex: 'topicType', |
|
||||||
customRender({ value }) { |
|
||||||
return h(Tag, () => getSystemEnumLabel('eProductTopicType', value)) |
|
||||||
}, |
|
||||||
}, |
|
||||||
{ |
|
||||||
title: '描述', |
|
||||||
dataIndex: 'topicDesc', |
|
||||||
}, |
|
||||||
], |
|
||||||
bordered: true, |
|
||||||
inset: true, |
|
||||||
canResize: false, |
|
||||||
}) |
|
||||||
</script> |
|
||||||
|
|
||||||
<template> |
|
||||||
<div flex="~ items-center justify-between"> |
|
||||||
<Alert |
|
||||||
type="info" |
|
||||||
show-icon |
|
||||||
class="py-4px text-13px" |
|
||||||
message="Topic用以将设备数据分类上报,进而分别进行处理。除了系统预置的Topic,您也可以自定义Topic,然后在设备侧开发时选择数据上报的Topic。" |
|
||||||
/> |
|
||||||
<a-button size="small" @click="reload"> |
|
||||||
<SyncOutlined /> |
|
||||||
刷新 |
|
||||||
</a-button> |
|
||||||
</div> |
|
||||||
<BasicTable mt="12px" @register="register" /> |
|
||||||
</template> |
|
@ -1,84 +0,0 @@ |
|||||||
import type { Ref } from 'vue' |
|
||||||
import { h } from 'vue' |
|
||||||
import { Tag } from 'ant-design-vue' |
|
||||||
import MqttParamsModal from '../MqttParamsModal.vue' |
|
||||||
import MessageModal from '../MessageModal.vue' |
|
||||||
import type { DescItem } from '@/components/Description' |
|
||||||
import { getReportExample } from '@/api/device-manage/device' |
|
||||||
import { usePermission } from '@/hooks/web/usePermission' |
|
||||||
import type { Device } from '@/api/device-manage/device/types' |
|
||||||
|
|
||||||
export function useDeviceInfo(data: Ref<Device | undefined>) { |
|
||||||
const { hasPermission } = usePermission() |
|
||||||
|
|
||||||
const scheam: DescItem[] = [ |
|
||||||
{ |
|
||||||
field: 'productName', |
|
||||||
label: '所属产品', |
|
||||||
}, |
|
||||||
{ |
|
||||||
field: 'deviceName', |
|
||||||
label: '设备名称', |
|
||||||
}, |
|
||||||
{ |
|
||||||
field: 'deviceSn', |
|
||||||
label: '设备序列号', |
|
||||||
}, |
|
||||||
{ |
|
||||||
field: 'deviceSecret', |
|
||||||
label: '设备密钥', |
|
||||||
}, |
|
||||||
{ |
|
||||||
field: 'isActive', |
|
||||||
label: '设备状态', |
|
||||||
render(value) { |
|
||||||
return h(Tag, { color: value ? 'green' : 'default' }, () => value ? '已激活' : '未激活') |
|
||||||
}, |
|
||||||
}, |
|
||||||
{ |
|
||||||
field: 'isOnline', |
|
||||||
label: '在线状态', |
|
||||||
render(value) { |
|
||||||
return h(Tag, { color: value ? 'green' : 'default' }, () => value ? '在线' : '离线') |
|
||||||
}, |
|
||||||
}, |
|
||||||
{ |
|
||||||
field: 'createTime', |
|
||||||
label: '创建时间', |
|
||||||
}, |
|
||||||
{ |
|
||||||
field: 'activeTime', |
|
||||||
label: '激活时间', |
|
||||||
}, |
|
||||||
{ |
|
||||||
field: 'lastOnlineTime', |
|
||||||
label: '最后上线时间', |
|
||||||
}, |
|
||||||
{ |
|
||||||
field: 'lastOfflineTime', |
|
||||||
label: '最后离线时间', |
|
||||||
}, |
|
||||||
{ |
|
||||||
field: 'mqtt', |
|
||||||
label: 'MQTT连接参数', |
|
||||||
show: () => hasPermission('device_mqtt_params'), |
|
||||||
render: () => h(MqttParamsModal, { deviceId: data.value!.id }), |
|
||||||
}, |
|
||||||
{ |
|
||||||
field: 'report', |
|
||||||
label: '上报示例', |
|
||||||
show: () => hasPermission('device_report_example'), |
|
||||||
render: () => h(MessageModal, { |
|
||||||
request: () => getReportExample(data.value!.productId, data.value!.deviceSn), |
|
||||||
}), |
|
||||||
}, |
|
||||||
{ |
|
||||||
field: 'deviceDesc', |
|
||||||
label: '设备描述', |
|
||||||
}, |
|
||||||
] |
|
||||||
|
|
||||||
return { |
|
||||||
scheam, |
|
||||||
} |
|
||||||
} |
|
@ -1,27 +0,0 @@ |
|||||||
import { useAsyncState } from '@vueuse/core' |
|
||||||
import { type MaybeRefOrGetter, toValue, watchEffect } from 'vue' |
|
||||||
import { getDeviceProperties } from '@/api/device-manage/device' |
|
||||||
|
|
||||||
export function useDeviceProperties(modelId: MaybeRefOrGetter<string | undefined>, deviceSn: MaybeRefOrGetter<string | undefined>) { |
|
||||||
const { state, execute, isLoading } = useAsyncState( |
|
||||||
() => getDeviceProperties(toValue(modelId)!, toValue(deviceSn)!), |
|
||||||
undefined, |
|
||||||
{ |
|
||||||
immediate: false, |
|
||||||
resetOnExecute: false, |
|
||||||
}, |
|
||||||
) |
|
||||||
|
|
||||||
watchEffect(() => { |
|
||||||
if (!toValue(modelId) || !toValue(deviceSn)) |
|
||||||
return |
|
||||||
|
|
||||||
execute() |
|
||||||
}) |
|
||||||
|
|
||||||
return { |
|
||||||
isLoading, |
|
||||||
deviceProperties: state, |
|
||||||
reloadReviceProperties: execute, |
|
||||||
} |
|
||||||
} |
|
@ -1,4 +0,0 @@ |
|||||||
export { default as DeviceInfo } from './DeviceInfo.vue' |
|
||||||
export { default as TopicList } from './TopicList.vue' |
|
||||||
export { default as CloudCommand } from './CloudCommand.vue' |
|
||||||
export { default as SendCommandModal } from './SendCommandModal.vue' |
|
@ -1,123 +0,0 @@ |
|||||||
import { Tag } from 'ant-design-vue' |
|
||||||
import type { Ref } from 'vue' |
|
||||||
import { h } from 'vue' |
|
||||||
import type { BasicColumn, FormSchema } from '@/components/Table' |
|
||||||
import { getAllProducts } from '@/api/product' |
|
||||||
import { getAllTenants } from '@/api/system/tenant' |
|
||||||
|
|
||||||
export const columns: BasicColumn[] = [ |
|
||||||
{ |
|
||||||
title: '所属产品', |
|
||||||
dataIndex: 'productName', |
|
||||||
}, |
|
||||||
{ |
|
||||||
title: '设备名称', |
|
||||||
dataIndex: 'deviceName', |
|
||||||
}, |
|
||||||
{ |
|
||||||
title: '设备序列号', |
|
||||||
dataIndex: 'deviceSn', |
|
||||||
}, |
|
||||||
{ |
|
||||||
title: '设备描述', |
|
||||||
dataIndex: 'deviceDesc', |
|
||||||
}, |
|
||||||
{ |
|
||||||
title: '是否在线', |
|
||||||
dataIndex: 'isOnline', |
|
||||||
customRender({ value }) { |
|
||||||
return h(Tag, { |
|
||||||
color: value ? 'green' : 'default', |
|
||||||
}, () => value ? '在线' : '离线') |
|
||||||
}, |
|
||||||
}, |
|
||||||
] |
|
||||||
|
|
||||||
export const searchFormSchemas: FormSchema[] = [ |
|
||||||
{ |
|
||||||
label: '所属租户', |
|
||||||
field: 'tenantId', |
|
||||||
component: 'ApiSelect', |
|
||||||
componentProps: { |
|
||||||
api: getAllTenants, |
|
||||||
valueField: 'tenantId', |
|
||||||
labelField: 'tenantName', |
|
||||||
}, |
|
||||||
colProps: { span: 6 }, |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '所属产品', |
|
||||||
field: 'productId', |
|
||||||
component: 'ApiSelect', |
|
||||||
componentProps: { |
|
||||||
api: getAllProducts, |
|
||||||
valueField: 'id', |
|
||||||
labelField: 'productName', |
|
||||||
}, |
|
||||||
colProps: { span: 6 }, |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '设备序列号', |
|
||||||
field: 'deviceSn', |
|
||||||
component: 'Input', |
|
||||||
colProps: { span: 6 }, |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '设备名称', |
|
||||||
field: 'deviceName', |
|
||||||
component: 'Input', |
|
||||||
colProps: { span: 6 }, |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '是否在线', |
|
||||||
field: 'isOnline', |
|
||||||
component: 'Select', |
|
||||||
componentProps: { |
|
||||||
options: [ |
|
||||||
{ label: '在线', value: 1 }, |
|
||||||
{ label: '离线', value: 0 }, |
|
||||||
], |
|
||||||
}, |
|
||||||
colProps: { span: 6 }, |
|
||||||
}, |
|
||||||
] |
|
||||||
|
|
||||||
export function getFormSchema(isUpdate: Ref<boolean>): FormSchema[] { |
|
||||||
return [ |
|
||||||
{ |
|
||||||
field: 'id', |
|
||||||
show: false, |
|
||||||
component: 'Input', |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '设备名称', |
|
||||||
field: 'deviceName', |
|
||||||
component: 'Input', |
|
||||||
required: true, |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '设备序列号', |
|
||||||
field: 'deviceSn', |
|
||||||
component: 'Input', |
|
||||||
required: true, |
|
||||||
ifShow: () => !isUpdate.value, |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '所属产品', |
|
||||||
field: 'productId', |
|
||||||
component: 'ApiSelect', |
|
||||||
required: true, |
|
||||||
ifShow: () => !isUpdate.value, |
|
||||||
componentProps: { |
|
||||||
api: getAllProducts, |
|
||||||
valueField: 'id', |
|
||||||
labelField: 'productName', |
|
||||||
}, |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '设备描述', |
|
||||||
field: 'deviceDesc', |
|
||||||
component: 'InputTextArea', |
|
||||||
}, |
|
||||||
] |
|
||||||
} |
|
@ -1,39 +0,0 @@ |
|||||||
<script lang='ts' setup> |
|
||||||
import { Card, Tabs } from 'ant-design-vue' |
|
||||||
import { useAsyncState } from '@vueuse/core' |
|
||||||
import { useRoute } from 'vue-router' |
|
||||||
import { CloudCommand, DeviceInfo, TopicList } from './components' |
|
||||||
import { usePermission } from '@/hooks/web/usePermission' |
|
||||||
import { getDeviceDetail } from '@/api/device-manage/device' |
|
||||||
|
|
||||||
defineOptions({ name: 'DeviceDetail' }) |
|
||||||
|
|
||||||
const route = useRoute() |
|
||||||
const { state: data } = useAsyncState(() => getDeviceDetail(route.params.id as string), undefined) |
|
||||||
|
|
||||||
const { hasPermission } = usePermission() |
|
||||||
</script> |
|
||||||
|
|
||||||
<template> |
|
||||||
<div p="12px"> |
|
||||||
<Card title="设备详情"> |
|
||||||
<Tabs> |
|
||||||
<Tabs.TabPane key="1" tab="设备信息"> |
|
||||||
<DeviceInfo v-if="data" :data="data" /> |
|
||||||
</Tabs.TabPane> |
|
||||||
<Tabs.TabPane v-if="hasPermission('device_topic_view')" key="2" tab="已订阅 Topic"> |
|
||||||
<TopicList /> |
|
||||||
</Tabs.TabPane> |
|
||||||
<Tabs.TabPane v-if="hasPermission('device_cloud_command_view')" key="3" tab="云端下发"> |
|
||||||
<CloudCommand :device="data" /> |
|
||||||
</Tabs.TabPane> |
|
||||||
</Tabs> |
|
||||||
</Card> |
|
||||||
</div> |
|
||||||
</template> |
|
||||||
|
|
||||||
<style scoped lang="less"> |
|
||||||
:deep(.ant-card-body) { |
|
||||||
padding-top: 10px; |
|
||||||
} |
|
||||||
</style> |
|
@ -1,109 +0,0 @@ |
|||||||
<script lang="ts" setup> |
|
||||||
import { PlusOutlined } from '@ant-design/icons-vue' |
|
||||||
import { columns, searchFormSchemas } from './data' |
|
||||||
import DeviceFormModal from './DeviceFormModal.vue' |
|
||||||
import { SendCommandModal } from './components' |
|
||||||
import { useModal } from '@/components/Modal' |
|
||||||
import { BasicTable, TableAction, useTable } from '@/components/Table' |
|
||||||
import { deleteDevice, getDeviceList } from '@/api/device-manage/device' |
|
||||||
import { CloudCommandType } from '@/api/device-manage/device/types' |
|
||||||
import type { Device } from '@/api/device-manage/device/types' |
|
||||||
import { useMessage } from '@/hooks/web/useMessage' |
|
||||||
import { usePermission } from '@/hooks/web/usePermission' |
|
||||||
|
|
||||||
defineOptions({ name: 'Device' }) |
|
||||||
|
|
||||||
const [registerModal, { openModal }] = useModal<Device>() |
|
||||||
const [ |
|
||||||
registerSendCommandModal, |
|
||||||
{ openModal: openSendCommandModal }, |
|
||||||
] = useModal<{ type: CloudCommandType, productId: string, deviceId: string }>() |
|
||||||
|
|
||||||
const { hasPermission } = usePermission() |
|
||||||
|
|
||||||
const [register, { reload }] = useTable({ |
|
||||||
api: getDeviceList, |
|
||||||
columns, |
|
||||||
formConfig: { |
|
||||||
schemas: searchFormSchemas, |
|
||||||
labelWidth: 80, |
|
||||||
}, |
|
||||||
bordered: true, |
|
||||||
canResize: false, |
|
||||||
useSearchForm: true, |
|
||||||
actionColumn: { |
|
||||||
width: 320, |
|
||||||
title: '操作', |
|
||||||
dataIndex: 'action', |
|
||||||
fixed: 'right', |
|
||||||
auth: ['view', 'edit', 'delete', 'cmd_send'].map(code => `device_${code}`), |
|
||||||
}, |
|
||||||
}) |
|
||||||
|
|
||||||
async function handleDelete(id: string) { |
|
||||||
try { |
|
||||||
await deleteDevice(id) |
|
||||||
useMessage().createMessage.success('删除成功') |
|
||||||
reload() |
|
||||||
} |
|
||||||
catch {} |
|
||||||
} |
|
||||||
</script> |
|
||||||
|
|
||||||
<template> |
|
||||||
<div> |
|
||||||
<BasicTable :api="async () => ([] as Device[])" @register="register"> |
|
||||||
<template v-if="hasPermission('device_add')" #tableTitle> |
|
||||||
<a-button type="primary" @click="openModal(true)"> |
|
||||||
<PlusOutlined /> |
|
||||||
新建 |
|
||||||
</a-button> |
|
||||||
</template> |
|
||||||
|
|
||||||
<template #bodyCell="{ column, record }"> |
|
||||||
<template v-if="column.key === 'action'"> |
|
||||||
<TableAction |
|
||||||
:actions="[ |
|
||||||
{ |
|
||||||
icon: 'i-ant-design:file-search-outlined', |
|
||||||
label: '详情', |
|
||||||
auth: 'device_view', |
|
||||||
onClick: () => $router.push(`/device-manage/device/detail/${record.id}`), |
|
||||||
}, |
|
||||||
{ |
|
||||||
icon: 'i-ant-design:edit-outlined', |
|
||||||
label: '编辑', |
|
||||||
auth: 'device_edit', |
|
||||||
onClick: () => openModal(true, record), |
|
||||||
}, |
|
||||||
{ |
|
||||||
icon: 'i-ant-design:cloud-sync-outlined', |
|
||||||
label: '指令下发', |
|
||||||
auth: 'device_cmd_send', |
|
||||||
onClick: () => openSendCommandModal(true, { |
|
||||||
deviceId: record.id, |
|
||||||
productId: record.productId, |
|
||||||
type: CloudCommandType.Message, |
|
||||||
}), |
|
||||||
}, |
|
||||||
{ |
|
||||||
icon: 'i-ant-design:delete-outlined', |
|
||||||
danger: true, |
|
||||||
label: '删除', |
|
||||||
auth: 'device_delete', |
|
||||||
popConfirm: { |
|
||||||
title: '是否要删除数据?', |
|
||||||
placement: 'left', |
|
||||||
confirm: () => handleDelete(record.id), |
|
||||||
}, |
|
||||||
}, |
|
||||||
]" |
|
||||||
/> |
|
||||||
</template> |
|
||||||
</template> |
|
||||||
</BasicTable> |
|
||||||
|
|
||||||
<DeviceFormModal @register="registerModal" @success="reload" /> |
|
||||||
<SendCommandModal title="指令下发" @register="registerSendCommandModal" /> |
|
||||||
</div> |
|
||||||
</template> |
|
@ -1,101 +0,0 @@ |
|||||||
<script lang="ts" setup> |
|
||||||
import { BasicDrawer, useDrawerInner } from '@/components/Drawer' |
|
||||||
import { BasicTable, useTable } from '@/components/Table' |
|
||||||
import { getDeviceList } from '@/api/device-manage/device' |
|
||||||
import { useMessage } from '@/hooks/web/useMessage' |
|
||||||
import { bindingDeviceToGroup } from '@/api/device-manage/group' |
|
||||||
import { useSharedProducts } from '@/views/subscription/list/data' |
|
||||||
import { noop } from '@/utils' |
|
||||||
|
|
||||||
const emit = defineEmits(['register', 'success']) |
|
||||||
|
|
||||||
let deviceGroupId: string |
|
||||||
const [register, { closeDrawer, setDrawerProps }] = useDrawerInner((id: string) => { |
|
||||||
deviceGroupId = id |
|
||||||
}) |
|
||||||
|
|
||||||
const { products } = useSharedProducts() |
|
||||||
|
|
||||||
const [registerTable, { getSelectRowKeys, clearSelectedRowKeys }] = useTable({ |
|
||||||
api: getDeviceList, |
|
||||||
rowKey: 'id', |
|
||||||
columns: [ |
|
||||||
{ |
|
||||||
title: '所属产品', |
|
||||||
dataIndex: 'productId', |
|
||||||
customRender({ value }) { |
|
||||||
return products.value.find(item => item.id === value)?.productName |
|
||||||
}, |
|
||||||
}, |
|
||||||
{ |
|
||||||
title: '产品名称', |
|
||||||
dataIndex: 'deviceName', |
|
||||||
}, |
|
||||||
{ |
|
||||||
title: '设备序列号', |
|
||||||
dataIndex: 'deviceSn', |
|
||||||
}, |
|
||||||
], |
|
||||||
formConfig: { |
|
||||||
schemas: [ |
|
||||||
{ |
|
||||||
field: 'productId', |
|
||||||
label: '所属产品', |
|
||||||
component: 'Select', |
|
||||||
componentProps: { |
|
||||||
options: products as any, |
|
||||||
showSearch: true, |
|
||||||
fieldNames: { |
|
||||||
label: 'productName', |
|
||||||
value: 'id', |
|
||||||
}, |
|
||||||
}, |
|
||||||
colProps: { span: 8 }, |
|
||||||
}, |
|
||||||
{ |
|
||||||
field: 'deviceSn', |
|
||||||
label: '设备序列号', |
|
||||||
component: 'Input', |
|
||||||
colProps: { span: 8 }, |
|
||||||
}, |
|
||||||
], |
|
||||||
labelWidth: 90, |
|
||||||
}, |
|
||||||
bordered: true, |
|
||||||
canResize: false, |
|
||||||
useSearchForm: true, |
|
||||||
inset: true, |
|
||||||
rowSelection: {}, |
|
||||||
}) |
|
||||||
|
|
||||||
function handleSubmit() { |
|
||||||
const keys = getSelectRowKeys() |
|
||||||
if (!keys.length) |
|
||||||
return useMessage().createMessage.warn('请先选择设备') |
|
||||||
|
|
||||||
setDrawerProps({ confirmLoading: true }) |
|
||||||
bindingDeviceToGroup(deviceGroupId, keys.join(',')) |
|
||||||
.then(() => { |
|
||||||
useMessage().createMessage.success('绑定成功') |
|
||||||
clearSelectedRowKeys() |
|
||||||
closeDrawer() |
|
||||||
emit('success') |
|
||||||
}) |
|
||||||
.catch(noop) |
|
||||||
.finally(() => { |
|
||||||
setDrawerProps({ confirmLoading: false }) |
|
||||||
}) |
|
||||||
} |
|
||||||
</script> |
|
||||||
|
|
||||||
<template> |
|
||||||
<BasicDrawer |
|
||||||
title="绑定设备" |
|
||||||
width="900px" |
|
||||||
show-footer |
|
||||||
@register="register" |
|
||||||
@ok="handleSubmit" |
|
||||||
> |
|
||||||
<BasicTable @register="registerTable" /> |
|
||||||
</BasicDrawer> |
|
||||||
</template> |
|
@ -1,86 +0,0 @@ |
|||||||
<script lang="ts" setup> |
|
||||||
import { ref } from 'vue' |
|
||||||
import { BasicModal, useModalInner } from '@/components/Modal' |
|
||||||
import { BasicForm, useForm } from '@/components/Form' |
|
||||||
import { createDeviceGroup, getDevicegroupDetail, updateDeviceGroup } from '@/api/device-manage/group' |
|
||||||
import type { DeviceGroup } from '@/api/device-manage/group/types' |
|
||||||
import { useMessage } from '@/hooks/web/useMessage' |
|
||||||
import { noop } from '@/utils' |
|
||||||
|
|
||||||
const emit = defineEmits(['register', 'success']) |
|
||||||
|
|
||||||
const [registerForm, { validate, setFieldsValue }] = useForm({ |
|
||||||
schemas: [ |
|
||||||
{ |
|
||||||
field: 'groupName', |
|
||||||
fields: ['parentId', 'id'], |
|
||||||
label: '群组名称', |
|
||||||
required: true, |
|
||||||
component: 'Input', |
|
||||||
componentProps: { |
|
||||||
maxlength: 30, |
|
||||||
showCount: true, |
|
||||||
}, |
|
||||||
}, |
|
||||||
{ |
|
||||||
field: 'remark', |
|
||||||
label: '群组描述', |
|
||||||
component: 'InputTextArea', |
|
||||||
componentProps: { |
|
||||||
rows: 5, |
|
||||||
}, |
|
||||||
}, |
|
||||||
], |
|
||||||
labelWidth: 80, |
|
||||||
baseColProps: { span: 24 }, |
|
||||||
showActionButtonGroup: false, |
|
||||||
actionColOptions: { span: 23 }, |
|
||||||
}) |
|
||||||
|
|
||||||
const modalTitle = ref('新增根群组') |
|
||||||
|
|
||||||
const [register, { setModalProps, closeModal }] = useModalInner((data: { parentId: string } | { id: string }) => { |
|
||||||
if ('id' in data) { |
|
||||||
modalTitle.value = '编辑群组' |
|
||||||
setModalProps({ loading: true, confirmLoading: true }) |
|
||||||
getDevicegroupDetail(data.id) |
|
||||||
.then((res) => { |
|
||||||
setFieldsValue(res) |
|
||||||
}) |
|
||||||
.catch(noop) |
|
||||||
.finally(() => { |
|
||||||
setModalProps({ loading: false, confirmLoading: false }) |
|
||||||
}) |
|
||||||
} |
|
||||||
else { |
|
||||||
modalTitle.value = '新增子群组' |
|
||||||
setFieldsValue(data) |
|
||||||
} |
|
||||||
}) |
|
||||||
|
|
||||||
async function handleSubmit() { |
|
||||||
try { |
|
||||||
const values = await validate<DeviceGroup>() |
|
||||||
setModalProps({ confirmLoading: true }) |
|
||||||
await (values.id ? updateDeviceGroup(values) : createDeviceGroup(values)) |
|
||||||
closeModal() |
|
||||||
emit('success') |
|
||||||
useMessage().createMessage.success('保存成功') |
|
||||||
} |
|
||||||
catch {} |
|
||||||
finally { |
|
||||||
setModalProps({ confirmLoading: false }) |
|
||||||
} |
|
||||||
} |
|
||||||
</script> |
|
||||||
|
|
||||||
<template> |
|
||||||
<BasicModal |
|
||||||
:title="modalTitle" |
|
||||||
:after-close="() => modalTitle = '新增根群组'" |
|
||||||
@register="register" |
|
||||||
@ok="handleSubmit" |
|
||||||
> |
|
||||||
<BasicForm @register="registerForm" /> |
|
||||||
</BasicModal> |
|
||||||
</template> |
|
@ -1,112 +0,0 @@ |
|||||||
<script lang="ts" setup> |
|
||||||
import { ref } from 'vue' |
|
||||||
import { PlusOutlined, SyncOutlined } from '@ant-design/icons-vue' |
|
||||||
import { useAsyncState } from '@vueuse/core' |
|
||||||
import { Empty, Popconfirm, Space, Tree } from 'ant-design-vue' |
|
||||||
import type { EventDataNode } from 'ant-design-vue/es/tree' |
|
||||||
import GroupFormModal from './GroupFormModal.vue' |
|
||||||
import { useModal } from '@/components/Modal' |
|
||||||
import { deleteDevicegroup, getDeviceGroupTree } from '@/api/device-manage/group' |
|
||||||
import { useMessage } from '@/hooks/web/useMessage' |
|
||||||
import { usePermission } from '@/hooks/web/usePermission' |
|
||||||
|
|
||||||
defineProps<{ selectedGroupId: string | undefined }>() |
|
||||||
const emit = defineEmits(['update:selectedGroupId', 'change']) |
|
||||||
|
|
||||||
const [registerModal, { openModal }] = useModal<{ id: string } | { parentId: string }>() |
|
||||||
|
|
||||||
const { state, execute } = useAsyncState(getDeviceGroupTree, [], { resetOnExecute: false }) |
|
||||||
|
|
||||||
async function handleDelete(id: string) { |
|
||||||
try { |
|
||||||
await deleteDevicegroup(id) |
|
||||||
useMessage().createMessage.success('删除成功') |
|
||||||
execute() |
|
||||||
} |
|
||||||
catch {} |
|
||||||
} |
|
||||||
|
|
||||||
const selectedKeys = ref<string[]>([]) |
|
||||||
|
|
||||||
function onSelectNode(_, { selected, node }: { selected: boolean, node: EventDataNode }) { |
|
||||||
// can't unselect |
|
||||||
if (!selected) |
|
||||||
return selectedKeys.value = [node.key as string] |
|
||||||
|
|
||||||
emit('update:selectedGroupId', node.key) |
|
||||||
emit('change') |
|
||||||
} |
|
||||||
|
|
||||||
const { hasPermission } = usePermission() |
|
||||||
</script> |
|
||||||
|
|
||||||
<template> |
|
||||||
<div rounded="6px" px="10px" pt="12px" pb="6px" border-box bg="white dark:[var(--component-background)]" min-w="360px"> |
|
||||||
<div> |
|
||||||
<div flex="~ items-center justify-between" h="35px"> |
|
||||||
<a-button v-if="hasPermission('device_group_add')" size="small" @click="openModal()"> |
|
||||||
<PlusOutlined /> |
|
||||||
添加根分组 |
|
||||||
</a-button> |
|
||||||
<span v-else /> <!-- ghost --> |
|
||||||
|
|
||||||
<a-button size="small" @click="execute"> |
|
||||||
<SyncOutlined /> |
|
||||||
刷新 |
|
||||||
</a-button> |
|
||||||
</div> |
|
||||||
|
|
||||||
<div mt="20px"> |
|
||||||
<Tree v-model:selected-keys="selectedKeys" :tree-data="state" :field-names="{ key: 'id' }" @select="onSelectNode"> |
|
||||||
<template #title="{ title, data }"> |
|
||||||
<div flex="~ items-center justify-between" py="8px" px="5px" box-border> |
|
||||||
<div flex="1" width="0" truncate :title="title"> |
|
||||||
{{ title }} |
|
||||||
</div> |
|
||||||
|
|
||||||
<Space> |
|
||||||
<span |
|
||||||
v-if="hasPermission('device_group_add')" |
|
||||||
class="i-ant-design:plus-outlined" |
|
||||||
title="添加子分组" |
|
||||||
@click="openModal(true, { parentId: data.id })" |
|
||||||
/> |
|
||||||
<span |
|
||||||
v-if="hasPermission('device_group_edit')" |
|
||||||
class="i-ant-design:edit-outlined" |
|
||||||
title="编辑分组" |
|
||||||
@click="openModal(true, { id: data.id })" |
|
||||||
/> |
|
||||||
<Popconfirm |
|
||||||
v-if="hasPermission('device_group_delete')" |
|
||||||
title="是否要删除数据?" |
|
||||||
:class="[data.hasChildren ? 'text-gray-300 cursor-not-allowed' : '']" |
|
||||||
:disabled="data.hasChildren" |
|
||||||
@confirm="handleDelete(data.id)" |
|
||||||
> |
|
||||||
<span class="i-ant-design:delete-outlined" :title="data.hasChildren ? '存在子分组, 无法删除' : '删除分组'" /> |
|
||||||
</Popconfirm> |
|
||||||
</Space> |
|
||||||
</div> |
|
||||||
</template> |
|
||||||
</Tree> |
|
||||||
<Empty v-if="!state.length" :image="Empty.PRESENTED_IMAGE_SIMPLE" /> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
|
|
||||||
<GroupFormModal @register="registerModal" @success="execute" /> |
|
||||||
</div> |
|
||||||
</template> |
|
||||||
|
|
||||||
<style scoped lang="less"> |
|
||||||
:deep(.ant-tree-treenode) { |
|
||||||
width: 100%; |
|
||||||
} |
|
||||||
:deep(.ant-tree-node-content-wrapper) { |
|
||||||
flex: 1; |
|
||||||
width: 0; |
|
||||||
} |
|
||||||
:deep(.ant-tree-switcher-icon) { |
|
||||||
margin-top: 15px; |
|
||||||
} |
|
||||||
</style> |
|
@ -1,2 +0,0 @@ |
|||||||
export { default as GroupList } from './GroupList.vue' |
|
||||||
export { default as BindingDeviceDrawer } from './BindingDeviceDrawer.vue' |
|
@ -1,60 +0,0 @@ |
|||||||
import { h } from 'vue' |
|
||||||
import { Tag } from 'ant-design-vue' |
|
||||||
import type { BasicColumn, FormSchema } from '@/components/Table' |
|
||||||
import { useSharedProducts } from '@/views/subscription/list/data' |
|
||||||
|
|
||||||
export function useColumns(): BasicColumn[] { |
|
||||||
const { products } = useSharedProducts() |
|
||||||
return [ |
|
||||||
{ |
|
||||||
title: '所属产品', |
|
||||||
dataIndex: 'productId', |
|
||||||
customRender({ value }) { |
|
||||||
return products.value.find(item => item.id === value)?.productName |
|
||||||
}, |
|
||||||
}, |
|
||||||
{ |
|
||||||
title: '设备序列号', |
|
||||||
dataIndex: 'deviceSn', |
|
||||||
}, |
|
||||||
{ |
|
||||||
title: '是否在线', |
|
||||||
dataIndex: 'isOnline', |
|
||||||
customRender({ value }) { |
|
||||||
return h(Tag, { |
|
||||||
color: value ? 'green' : 'default', |
|
||||||
}, () => value ? '在线' : '离线') |
|
||||||
}, |
|
||||||
}, |
|
||||||
] |
|
||||||
} |
|
||||||
|
|
||||||
export function useSearchFormSchemas(): FormSchema[] { |
|
||||||
const { products } = useSharedProducts() |
|
||||||
return [ |
|
||||||
{ |
|
||||||
label: '所属产品', |
|
||||||
field: 'productId', |
|
||||||
component: 'Select', |
|
||||||
componentProps: { |
|
||||||
options: products as any, |
|
||||||
showSearch: true, |
|
||||||
fieldNames: { |
|
||||||
label: 'productName', |
|
||||||
value: 'id', |
|
||||||
}, |
|
||||||
}, |
|
||||||
colProps: { |
|
||||||
span: 8, |
|
||||||
}, |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '设备序列号', |
|
||||||
field: 'deviceSn', |
|
||||||
component: 'Input', |
|
||||||
colProps: { |
|
||||||
span: 8, |
|
||||||
}, |
|
||||||
}, |
|
||||||
] |
|
||||||
} |
|
@ -1,126 +0,0 @@ |
|||||||
<script lang="ts" setup> |
|
||||||
import { ref } from 'vue' |
|
||||||
import { Space } from 'ant-design-vue' |
|
||||||
import { DisconnectOutlined, LinkOutlined } from '@ant-design/icons-vue' |
|
||||||
import { BindingDeviceDrawer, GroupList } from './components' |
|
||||||
import { useColumns, useSearchFormSchemas } from './data' |
|
||||||
import { BasicTable, TableAction, useTable } from '@/components/Table' |
|
||||||
import { useMessage } from '@/hooks/web/useMessage' |
|
||||||
import { getDeviceListByGroup, unbindingDeviceFromGroup } from '@/api/device-manage/group' |
|
||||||
import { useDrawer } from '@/components/Drawer' |
|
||||||
import { noop } from '@/utils' |
|
||||||
import { usePermission } from '@/hooks/web/usePermission' |
|
||||||
|
|
||||||
const { hasPermission } = usePermission() |
|
||||||
|
|
||||||
const [register, { getSelectRowKeys, reload }] = useTable({ |
|
||||||
async api(params) { |
|
||||||
if (!selectedGroupId.value) |
|
||||||
return [] |
|
||||||
|
|
||||||
return getDeviceListByGroup({ |
|
||||||
...params, |
|
||||||
groupId: selectedGroupId.value, |
|
||||||
}) |
|
||||||
}, |
|
||||||
rowKey: 'id', |
|
||||||
columns: useColumns(), |
|
||||||
formConfig: { |
|
||||||
schemas: useSearchFormSchemas(), |
|
||||||
labelWidth: 90, |
|
||||||
}, |
|
||||||
rowSelection: {}, |
|
||||||
bordered: true, |
|
||||||
canResize: false, |
|
||||||
useSearchForm: true, |
|
||||||
actionColumn: { |
|
||||||
width: 210, |
|
||||||
title: '操作', |
|
||||||
dataIndex: 'action', |
|
||||||
auth: ['device_group_unbinding', 'device_group_device_view'], |
|
||||||
}, |
|
||||||
}) |
|
||||||
|
|
||||||
const [registerBindingDeviceDrawer, { openDrawer }] = useDrawer() |
|
||||||
|
|
||||||
const selectedGroupId = ref<string | undefined>() |
|
||||||
|
|
||||||
const message = useMessage() |
|
||||||
function handleBindingDivice() { |
|
||||||
if (!selectedGroupId.value) |
|
||||||
return message.createMessage.warn('请先选择分组') |
|
||||||
|
|
||||||
openDrawer(true, selectedGroupId.value) |
|
||||||
} |
|
||||||
function handleUnbindingDivice(id?: string) { |
|
||||||
const selectionKeys = id ? [id] : getSelectRowKeys() |
|
||||||
if (!selectionKeys.length) |
|
||||||
return message.createMessage.warn('请先选择设备') |
|
||||||
|
|
||||||
function execute() { |
|
||||||
unbindingDeviceFromGroup(selectedGroupId.value!, selectionKeys.join(',')) |
|
||||||
.then(() => { |
|
||||||
message.createMessage.success('解绑成功') |
|
||||||
reload() |
|
||||||
}) |
|
||||||
.catch(noop) |
|
||||||
} |
|
||||||
|
|
||||||
if (id) |
|
||||||
return execute() |
|
||||||
|
|
||||||
// 批量绑定设备二次确认 |
|
||||||
message.createConfirm({ |
|
||||||
iconType: 'warning', |
|
||||||
title: '是否要解绑选择的设备?', |
|
||||||
onOk: execute, |
|
||||||
}) |
|
||||||
} |
|
||||||
</script> |
|
||||||
|
|
||||||
<template> |
|
||||||
<div flex="~"> |
|
||||||
<GroupList v-model:selectedGroupId="selectedGroupId" my="12px" ml="12px" @change="reload" /> |
|
||||||
<BasicTable flex="1" @register="register"> |
|
||||||
<template v-if="hasPermission(['device_group_binding', 'device_group_unbinding'])" #tableTitle> |
|
||||||
<Space> |
|
||||||
<a-button v-if="hasPermission('device_group_binding')" @click="handleBindingDivice"> |
|
||||||
<LinkOutlined /> |
|
||||||
批量绑定设备 |
|
||||||
</a-button> |
|
||||||
<a-button v-if="hasPermission('device_group_unbinding')" @click="handleUnbindingDivice()"> |
|
||||||
<DisconnectOutlined /> |
|
||||||
批量解绑设备 |
|
||||||
</a-button> |
|
||||||
</Space> |
|
||||||
</template> |
|
||||||
|
|
||||||
<template #bodyCell="{ column, record }"> |
|
||||||
<template v-if="column.key === 'action'"> |
|
||||||
<TableAction |
|
||||||
:actions="[ |
|
||||||
{ |
|
||||||
icon: 'i-ant-design:file-search-outlined', |
|
||||||
label: '设备详情', |
|
||||||
auth: 'device_group_device_view', |
|
||||||
onClick: () => $router.push(`/device-manage/device/detail/${record.id}`), |
|
||||||
}, |
|
||||||
{ |
|
||||||
icon: 'i-ant-design:edit-outlined', |
|
||||||
label: '解绑设备', |
|
||||||
auth: 'device_group_unbinding', |
|
||||||
popConfirm: { |
|
||||||
title: '是否要解绑该设备?', |
|
||||||
placement: 'left', |
|
||||||
confirm: () => handleUnbindingDivice(record.id), |
|
||||||
}, |
|
||||||
}, |
|
||||||
]" |
|
||||||
/> |
|
||||||
</template> |
|
||||||
</template> |
|
||||||
</BasicTable> |
|
||||||
|
|
||||||
<BindingDeviceDrawer @register="registerBindingDeviceDrawer" @success="reload" /> |
|
||||||
</div> |
|
||||||
</template> |
|
@ -1,62 +0,0 @@ |
|||||||
<script lang="ts" setup> |
|
||||||
import { h, ref } from 'vue' |
|
||||||
import { Button } from 'ant-design-vue' |
|
||||||
import { CopyOutlined } from '@ant-design/icons-vue' |
|
||||||
import { getMessageContent } from '@/api/monitor-ops/log' |
|
||||||
import { BasicModal, useModalInner } from '@/components/Modal' |
|
||||||
import { noop } from '@/utils' |
|
||||||
import type { DescItem } from '@/components/Description' |
|
||||||
import { Description } from '@/components/Description' |
|
||||||
import { copyText } from '@/utils/copyTextToClipboard' |
|
||||||
import type { MessageContent } from '@/api/monitor-ops/log/types' |
|
||||||
|
|
||||||
defineEmits(['register']) |
|
||||||
|
|
||||||
const data = ref<MessageContent>() |
|
||||||
const [register] = useModalInner((id: string) => { |
|
||||||
getMessageContent(id) |
|
||||||
.then((res) => { |
|
||||||
data.value = res |
|
||||||
}) |
|
||||||
.catch(noop) |
|
||||||
}) |
|
||||||
|
|
||||||
const schema: DescItem[] = [ |
|
||||||
{ |
|
||||||
field: 'topic', |
|
||||||
label: 'Topic', |
|
||||||
}, |
|
||||||
{ |
|
||||||
field: 'createTime', |
|
||||||
label: '时间', |
|
||||||
}, |
|
||||||
{ |
|
||||||
field: 'message', |
|
||||||
label: '内容', |
|
||||||
render(val) { |
|
||||||
let content = val |
|
||||||
try { |
|
||||||
content = JSON.stringify(JSON.parse(val), null, 2) |
|
||||||
} |
|
||||||
catch {} |
|
||||||
return h('div', [ |
|
||||||
h(Button, { |
|
||||||
size: 'small', |
|
||||||
onClick: () => copyText(content), |
|
||||||
}, () => [h(CopyOutlined), '复制']), |
|
||||||
h('pre', { |
|
||||||
style: { |
|
||||||
marginTop: '10px', |
|
||||||
}, |
|
||||||
}, content), |
|
||||||
]) |
|
||||||
}, |
|
||||||
}, |
|
||||||
] |
|
||||||
</script> |
|
||||||
|
|
||||||
<template> |
|
||||||
<BasicModal title="消息内容" :footer="null" @register="register"> |
|
||||||
<Description :data="data" :schema="schema" :column="1" /> |
|
||||||
</BasicModal> |
|
||||||
</template> |
|
@ -1,127 +0,0 @@ |
|||||||
import { h } from 'vue' |
|
||||||
import { Tag } from 'ant-design-vue' |
|
||||||
import dayjs from 'dayjs' |
|
||||||
import type { BasicColumn, FormSchema } from '@/components/Table' |
|
||||||
import { useSystemEnumStore } from '@/store/modules/systemEnum' |
|
||||||
import { useSharedProducts } from '@/views/subscription/list/data' |
|
||||||
|
|
||||||
export function useColumns(): BasicColumn[] { |
|
||||||
const { products } = useSharedProducts() |
|
||||||
const { getSystemEnumLabel } = useSystemEnumStore() |
|
||||||
|
|
||||||
return [ |
|
||||||
{ |
|
||||||
title: '所属产品', |
|
||||||
dataIndex: 'productId', |
|
||||||
customRender({ value }) { |
|
||||||
return products.value.find(item => item.id === value)?.productName |
|
||||||
}, |
|
||||||
}, |
|
||||||
{ |
|
||||||
title: '设备序列号', |
|
||||||
dataIndex: 'deviceSn', |
|
||||||
}, |
|
||||||
{ |
|
||||||
title: 'TraceID', |
|
||||||
dataIndex: 'traceId', |
|
||||||
}, |
|
||||||
{ |
|
||||||
title: '消息内容', |
|
||||||
dataIndex: 'messageId', |
|
||||||
width: 120, |
|
||||||
}, |
|
||||||
{ |
|
||||||
title: '业务类型', |
|
||||||
dataIndex: 'bizType', |
|
||||||
width: 150, |
|
||||||
customRender({ value }) { |
|
||||||
return h(Tag, () => getSystemEnumLabel('eDeviceLogBizType', value)) |
|
||||||
}, |
|
||||||
}, |
|
||||||
{ |
|
||||||
title: '操作', |
|
||||||
dataIndex: 'operation', |
|
||||||
}, |
|
||||||
{ |
|
||||||
title: '时间', |
|
||||||
dataIndex: 'createTime', |
|
||||||
}, |
|
||||||
{ |
|
||||||
title: '状态', |
|
||||||
dataIndex: 'code', |
|
||||||
width: 120, |
|
||||||
customRender({ value }) { |
|
||||||
return h('b', { |
|
||||||
style: { |
|
||||||
color: value === 200 ? '#16a34a' : '#dc2626', |
|
||||||
}, |
|
||||||
}, value) |
|
||||||
}, |
|
||||||
}, |
|
||||||
] |
|
||||||
} |
|
||||||
|
|
||||||
export function useSearchFormSchema(): FormSchema[] { |
|
||||||
const { products } = useSharedProducts() |
|
||||||
const { getSystemEnums } = useSystemEnumStore() |
|
||||||
|
|
||||||
return [ |
|
||||||
{ |
|
||||||
field: 'productId', |
|
||||||
label: '所属产品', |
|
||||||
component: 'Select', |
|
||||||
componentProps: { |
|
||||||
options: products as any, |
|
||||||
showSearch: true, |
|
||||||
fieldNames: { |
|
||||||
label: 'productName', |
|
||||||
value: 'id', |
|
||||||
}, |
|
||||||
}, |
|
||||||
colProps: { |
|
||||||
span: 6, |
|
||||||
}, |
|
||||||
}, |
|
||||||
{ |
|
||||||
field: 'deviceSn', |
|
||||||
label: '设备序列号', |
|
||||||
component: 'Input', |
|
||||||
colProps: { |
|
||||||
span: 6, |
|
||||||
}, |
|
||||||
}, |
|
||||||
{ |
|
||||||
field: 'traceId', |
|
||||||
label: 'TraceId', |
|
||||||
component: 'Input', |
|
||||||
colProps: { |
|
||||||
span: 6, |
|
||||||
}, |
|
||||||
}, |
|
||||||
{ |
|
||||||
field: 'bizType', |
|
||||||
label: '业务类型', |
|
||||||
component: 'Select', |
|
||||||
componentProps: { |
|
||||||
options: getSystemEnums('eDeviceLogBizType'), |
|
||||||
}, |
|
||||||
colProps: { |
|
||||||
span: 6, |
|
||||||
}, |
|
||||||
}, |
|
||||||
{ |
|
||||||
field: 'time', |
|
||||||
label: '日志时间', |
|
||||||
component: 'RangePicker', |
|
||||||
componentProps: { |
|
||||||
showTime: true, |
|
||||||
disabledDate(current) { |
|
||||||
return current && current > dayjs().endOf('day') |
|
||||||
}, |
|
||||||
}, |
|
||||||
colProps: { |
|
||||||
span: 6, |
|
||||||
}, |
|
||||||
}, |
|
||||||
] |
|
||||||
} |
|
@ -1,44 +0,0 @@ |
|||||||
<script lang="ts" setup> |
|
||||||
import { useColumns, useSearchFormSchema } from './data' |
|
||||||
import MessageContentModal from './MessageContentModal.vue' |
|
||||||
import { getLogList } from '@/api/monitor-ops/log' |
|
||||||
import { BasicTable, useTable } from '@/components/Table' |
|
||||||
import { useModal } from '@/components/Modal' |
|
||||||
import type { GetLogListParams, Log } from '@/api/monitor-ops/log/types' |
|
||||||
|
|
||||||
const [registerModal, { openModal }] = useModal<string>() |
|
||||||
|
|
||||||
const [register] = useTable({ |
|
||||||
api(params: GetLogListParams & { time: [string, string] }) { |
|
||||||
return getLogList({ |
|
||||||
...params, |
|
||||||
queryStartTime: params.time && params.time[0], |
|
||||||
queryEndTime: params.time && params.time[1], |
|
||||||
}) |
|
||||||
}, |
|
||||||
columns: useColumns(), |
|
||||||
formConfig: { |
|
||||||
labelWidth: 90, |
|
||||||
schemas: useSearchFormSchema(), |
|
||||||
}, |
|
||||||
bordered: true, |
|
||||||
canResize: false, |
|
||||||
useSearchForm: true, |
|
||||||
}) |
|
||||||
</script> |
|
||||||
|
|
||||||
<template> |
|
||||||
<div> |
|
||||||
<BasicTable :api="async () => ([] as Log[])" @register="register"> |
|
||||||
<template #bodyCell="{ column, record }"> |
|
||||||
<template v-if="column.key === 'messageId'"> |
|
||||||
<a-button v-if="record.messageId" type="link" size="small" @click="openModal(true, record.messageId)"> |
|
||||||
查看 |
|
||||||
</a-button> |
|
||||||
</template> |
|
||||||
</template> |
|
||||||
</BasicTable> |
|
||||||
|
|
||||||
<MessageContentModal @register="registerModal" /> |
|
||||||
</div> |
|
||||||
</template> |
|
@ -1,55 +0,0 @@ |
|||||||
<script lang="ts" setup> |
|
||||||
import { ref } from 'vue' |
|
||||||
import { getFormSchema } from './data' |
|
||||||
import { useMessage } from '@/hooks/web/useMessage' |
|
||||||
import { BasicForm, useForm } from '@/components/Form' |
|
||||||
import { BasicModal, useModalInner } from '@/components/Modal' |
|
||||||
import { createProduct, updateProduct } from '@/api/product' |
|
||||||
import type { Product } from '@/api/product/types' |
|
||||||
|
|
||||||
defineOptions({ name: 'ProductFormModal' }) |
|
||||||
|
|
||||||
const emit = defineEmits(['success', 'register']) |
|
||||||
|
|
||||||
const isUpdate = ref(false) |
|
||||||
const [registerForm, { setFieldsValue, validate }] = useForm({ |
|
||||||
labelWidth: 100, |
|
||||||
baseColProps: { span: 24 }, |
|
||||||
schemas: getFormSchema(isUpdate), |
|
||||||
showActionButtonGroup: false, |
|
||||||
actionColOptions: { span: 23 }, |
|
||||||
}) |
|
||||||
|
|
||||||
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data: Product) => { |
|
||||||
isUpdate.value = true |
|
||||||
setFieldsValue({ ...data }) |
|
||||||
}) |
|
||||||
|
|
||||||
async function handleSubmit() { |
|
||||||
try { |
|
||||||
const values = await validate<Product>() |
|
||||||
setModalProps({ confirmLoading: true }) |
|
||||||
await (isUpdate.value ? updateProduct(values) : createProduct(values)) |
|
||||||
closeModal() |
|
||||||
emit('success') |
|
||||||
useMessage().createMessage.success('保存成功') |
|
||||||
} |
|
||||||
catch {} |
|
||||||
finally { |
|
||||||
setModalProps({ confirmLoading: false }) |
|
||||||
} |
|
||||||
} |
|
||||||
</script> |
|
||||||
|
|
||||||
<template> |
|
||||||
<BasicModal |
|
||||||
width="30%" |
|
||||||
:min-height="100" |
|
||||||
:title="isUpdate ? '编辑' : '新增'" |
|
||||||
:after-close="() => isUpdate = false" |
|
||||||
@register="registerModal" |
|
||||||
@ok="handleSubmit" |
|
||||||
> |
|
||||||
<BasicForm @register="registerForm" /> |
|
||||||
</BasicModal> |
|
||||||
</template> |
|
@ -1,138 +0,0 @@ |
|||||||
<script setup lang='ts'> |
|
||||||
import { Alert, Popconfirm } from 'ant-design-vue' |
|
||||||
import { useRoute } from 'vue-router' |
|
||||||
import { EyeOutlined, PlusOutlined, SyncOutlined } from '@ant-design/icons-vue' |
|
||||||
import { useModelService } from './composables/useModelService' |
|
||||||
import { useModelAttribute } from './composables/useModelAttribute' |
|
||||||
import ModelServiceFormModal from './ModelServiceFormModal.vue' |
|
||||||
import ModelAttributeFormModal from './ModelAttributeFormModal.vue' |
|
||||||
import { BasicTable, TableAction } from '@/components/Table' |
|
||||||
import type { ModelAttribute } from '@/api/product/types' |
|
||||||
import JsonPreviewModal from '@/components/JsonPreviewModal' |
|
||||||
import { usePermission } from '@/hooks/web/usePermission' |
|
||||||
|
|
||||||
defineProps<{ tsl?: string }>() |
|
||||||
|
|
||||||
const route = useRoute() |
|
||||||
const { |
|
||||||
selectedModelId, |
|
||||||
setSelectedModelId, |
|
||||||
modelServiceList, |
|
||||||
handleDeleteModelService, |
|
||||||
registerModelServiceModal, |
|
||||||
openModelServiceModal, |
|
||||||
reloadModelService, |
|
||||||
} = useModelService(route.params.id as string, route.params.modelId as string) |
|
||||||
|
|
||||||
const { |
|
||||||
registerModelAttributeTable, |
|
||||||
registerModelAttributeModal, |
|
||||||
openModelAttributeModal, |
|
||||||
reloadModalAttribute, |
|
||||||
handleDeleteModelAttribute, |
|
||||||
} = useModelAttribute(route.params.id as string, selectedModelId) |
|
||||||
|
|
||||||
const { hasPermission } = usePermission() |
|
||||||
</script> |
|
||||||
|
|
||||||
<template> |
|
||||||
<div> |
|
||||||
<div mb="12px"> |
|
||||||
<JsonPreviewModal title="物模型 TSL" :data="tsl || ''"> |
|
||||||
<a-button type="primary"> |
|
||||||
<EyeOutlined /> |
|
||||||
物模型 TSL |
|
||||||
</a-button> |
|
||||||
</JsonPreviewModal> |
|
||||||
</div> |
|
||||||
<Alert type="info" show-icon class="py-4px text-13px" message="产品模型用于描述设备具备的能力和特性。通过定义产品模型,在平台构建一款设备的抽象模型,使平台理解该款设备支持的服务、属性、命令等信息,如颜色、开关等。当定义完一款产品模型后,在进行注册设备时,就可以使用在控制台上定义的产品模型。" /> |
|
||||||
|
|
||||||
<div flex="~ gap-12px" mt="12px"> |
|
||||||
<div w="20%" border="0 r-1 solid gray-50 dark:white dark:opacity-5"> |
|
||||||
<div flex="~ items-center justify-between" border="0 b-1 solid gray-200 dark:white dark:opacity-5" class="mb-12px box-border h-40px px-10px"> |
|
||||||
<div font-bold> |
|
||||||
服务列表 |
|
||||||
</div> |
|
||||||
<div v-if="hasPermission('product_model_service_add')" class="i-ant-design:plus-outlined cursor-pointer text-20px" @click="openModelServiceModal()" /> |
|
||||||
</div> |
|
||||||
|
|
||||||
<div |
|
||||||
v-for="item in modelServiceList" :key="item.id" |
|
||||||
flex="~ items-center justify-between" |
|
||||||
class="box-border h-60px cursor-pointer pl-10px hover:bg-gray-100 hover:dark:bg-white hover:dark:bg-opacity-5" |
|
||||||
border="0 b-1 solid gray-50 dark:white dark:opacity-5" |
|
||||||
:class="selectedModelId === item.id ? 'bg-gray-100 dark:bg-white dark:bg-opacity-5' : ''" |
|
||||||
@click="setSelectedModelId(item.id); reloadModalAttribute(true)" |
|
||||||
> |
|
||||||
<div w-0 flex-1> |
|
||||||
<div truncate> |
|
||||||
{{ item.serviceId }} |
|
||||||
</div> |
|
||||||
<div mt="5px" text="12px gray-500" truncate :title="item.description"> |
|
||||||
{{ item.description }} |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
<div v-if="item.serviceId !== 'DEFAULT'"> |
|
||||||
<a-button v-if="hasPermission('product_model_service_edit')" type="link" size="small" @click="openModelServiceModal(true, item)"> |
|
||||||
<span class="i-ant-design:edit-outlined" /> |
|
||||||
</a-button> |
|
||||||
<Popconfirm v-if="hasPermission('product_model_service_delete')" title="是否要删除数据?" @confirm="handleDeleteModelService(item.id)"> |
|
||||||
<a-button type="link" size="small" danger> |
|
||||||
<span class="i-ant-design:delete-outlined" /> |
|
||||||
</a-button> |
|
||||||
</Popconfirm> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
|
|
||||||
<div w-0 flex-1> |
|
||||||
<BasicTable :api="async () => ([] as ModelAttribute[])" @register="registerModelAttributeTable"> |
|
||||||
<template v-if="hasPermission('product_model_attr_add')" #tableTitle> |
|
||||||
<a-button type="primary" @click="openModelAttributeModal"> |
|
||||||
<PlusOutlined /> |
|
||||||
新增属性 |
|
||||||
</a-button> |
|
||||||
</template> |
|
||||||
|
|
||||||
<template #toolbar> |
|
||||||
<a-button size="small" @click="reloadModalAttribute()"> |
|
||||||
<template #icon> |
|
||||||
<SyncOutlined /> |
|
||||||
</template> |
|
||||||
刷新 |
|
||||||
</a-button> |
|
||||||
</template> |
|
||||||
|
|
||||||
<template #bodyCell="{ column, record }"> |
|
||||||
<template v-if="column.key === 'action'"> |
|
||||||
<TableAction |
|
||||||
:actions="[ |
|
||||||
{ |
|
||||||
icon: 'i-ant-design:edit-outlined', |
|
||||||
label: '编辑', |
|
||||||
auth: 'product_model_attr_edit', |
|
||||||
onClick: () => openModelAttributeModal(true, record), |
|
||||||
}, |
|
||||||
{ |
|
||||||
icon: 'i-ant-design:delete-outlined', |
|
||||||
danger: true, |
|
||||||
label: '删除', |
|
||||||
auth: 'product_model_attr_delete', |
|
||||||
popConfirm: { |
|
||||||
title: '是否要删除数据?', |
|
||||||
placement: 'left', |
|
||||||
confirm: () => handleDeleteModelAttribute(record.id), |
|
||||||
}, |
|
||||||
}, |
|
||||||
]" |
|
||||||
/> |
|
||||||
</template> |
|
||||||
</template> |
|
||||||
</BasicTable> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
|
|
||||||
<ModelServiceFormModal @register="registerModelServiceModal" @success="reloadModelService" /> |
|
||||||
<ModelAttributeFormModal :model-id="selectedModelId || ''" @register="registerModelAttributeModal" @success="reloadModalAttribute()" /> |
|
||||||
</div> |
|
||||||
</template> |
|
@ -1,234 +0,0 @@ |
|||||||
<script lang="ts" setup> |
|
||||||
import { ref } from 'vue' |
|
||||||
import { FormItem, Input, InputNumber } from 'ant-design-vue' |
|
||||||
import { useMessage } from '@/hooks/web/useMessage' |
|
||||||
import { BasicForm, useForm } from '@/components/Form' |
|
||||||
import { BasicModal, useModalInner } from '@/components/Modal' |
|
||||||
import { createModelAttribute, updateModelAttribute } from '@/api/product/model' |
|
||||||
import { ModelAttributeDataTypesEnum } from '@/api/product/types' |
|
||||||
import type { ModelAttribute, ModelAttributeWithForm } from '@/api/product/types' |
|
||||||
|
|
||||||
defineOptions({ name: 'ModelAttributeFormModal' }) |
|
||||||
const props = defineProps<{ modelId: string }>() |
|
||||||
|
|
||||||
const emit = defineEmits(['success', 'register']) |
|
||||||
|
|
||||||
const isUpdate = ref(false) |
|
||||||
const [registerForm, { setFieldsValue, validate }] = useForm({ |
|
||||||
labelWidth: 80, |
|
||||||
baseColProps: { span: 24 }, |
|
||||||
schemas: [ |
|
||||||
{ |
|
||||||
field: 'id', |
|
||||||
show: false, |
|
||||||
component: 'Input', |
|
||||||
}, |
|
||||||
{ |
|
||||||
field: 'itemType', |
|
||||||
label: '功能类型', |
|
||||||
required: true, |
|
||||||
component: 'Select', |
|
||||||
defaultValue: 1, |
|
||||||
componentProps: { |
|
||||||
options: [ |
|
||||||
{ label: '属性', value: 1 }, |
|
||||||
{ label: '命令', value: 2 }, |
|
||||||
], |
|
||||||
}, |
|
||||||
dynamicDisabled: () => isUpdate.value, |
|
||||||
}, |
|
||||||
{ |
|
||||||
field: 'name', |
|
||||||
label: '功能名称', |
|
||||||
required: true, |
|
||||||
component: 'Input', |
|
||||||
}, |
|
||||||
{ |
|
||||||
field: 'identifier', |
|
||||||
label: '标识符', |
|
||||||
required: true, |
|
||||||
component: 'Input', |
|
||||||
}, |
|
||||||
{ |
|
||||||
field: 'dataType', |
|
||||||
label: '数据类型', |
|
||||||
required: true, |
|
||||||
component: 'Select', |
|
||||||
componentProps: { |
|
||||||
options: Object.values(ModelAttributeDataTypesEnum).map(value => ({ label: value, value })), |
|
||||||
}, |
|
||||||
dynamicDisabled: () => isUpdate.value, |
|
||||||
}, |
|
||||||
|
|
||||||
// int32 / float schemas |
|
||||||
{ |
|
||||||
field: 'valueScope', |
|
||||||
fields: ['min', 'max'], |
|
||||||
label: '取值范围', |
|
||||||
slot: 'ValueScope', |
|
||||||
required: true, |
|
||||||
itemProps: { |
|
||||||
autoLink: false, |
|
||||||
}, |
|
||||||
defaultValue: '_', // skip the required check |
|
||||||
ifShow: ({ values }) => [ModelAttributeDataTypesEnum.Int32, ModelAttributeDataTypesEnum.Float].includes(values.dataType), |
|
||||||
}, |
|
||||||
{ |
|
||||||
field: 'scale', |
|
||||||
label: '精度', |
|
||||||
required: true, |
|
||||||
helpMessage: '小数位', |
|
||||||
component: 'InputNumber', |
|
||||||
componentProps: { |
|
||||||
precision: 0, |
|
||||||
}, |
|
||||||
ifShow: ({ values }) => values.dataType === ModelAttributeDataTypesEnum.Float, |
|
||||||
}, |
|
||||||
{ |
|
||||||
field: 'unit', |
|
||||||
label: '单位', |
|
||||||
component: 'Input', |
|
||||||
ifShow: ({ values }) => [ModelAttributeDataTypesEnum.Int32, ModelAttributeDataTypesEnum.Float].includes(values.dataType), |
|
||||||
}, |
|
||||||
|
|
||||||
// bool schemas |
|
||||||
{ |
|
||||||
field: 'bool', |
|
||||||
fields: ['trueDesc', 'falseDesc'], |
|
||||||
label: '布尔值', |
|
||||||
slot: 'Bool', |
|
||||||
required: true, |
|
||||||
itemProps: { |
|
||||||
autoLink: false, |
|
||||||
}, |
|
||||||
defaultValue: '_', // skip the required check |
|
||||||
ifShow: ({ values }) => values.dataType === ModelAttributeDataTypesEnum.Bool, |
|
||||||
}, |
|
||||||
|
|
||||||
// text schemas |
|
||||||
{ |
|
||||||
field: 'maxLength', |
|
||||||
label: '数据长度', |
|
||||||
required: true, |
|
||||||
component: 'InputNumber', |
|
||||||
componentProps: { |
|
||||||
precision: 0, |
|
||||||
}, |
|
||||||
ifShow: ({ values }) => values.dataType === ModelAttributeDataTypesEnum.Text, |
|
||||||
}, |
|
||||||
|
|
||||||
{ |
|
||||||
field: 'method', |
|
||||||
label: '读写', |
|
||||||
component: 'Select', |
|
||||||
defaultValue: 'r', |
|
||||||
componentProps: { |
|
||||||
options: [ |
|
||||||
{ label: '只读', value: 'r' }, |
|
||||||
{ label: '读写', value: 'rw' }, |
|
||||||
], |
|
||||||
}, |
|
||||||
}, |
|
||||||
{ |
|
||||||
field: 'sort', |
|
||||||
label: '排序', |
|
||||||
component: 'InputNumber', |
|
||||||
componentProps: { |
|
||||||
precision: 0, |
|
||||||
}, |
|
||||||
}, |
|
||||||
], |
|
||||||
showActionButtonGroup: false, |
|
||||||
actionColOptions: { span: 23 }, |
|
||||||
}) |
|
||||||
|
|
||||||
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data: ModelAttribute) => { |
|
||||||
isUpdate.value = true |
|
||||||
setFieldsValue({ ...data, ...(data.dataSpecs || {}) }) |
|
||||||
}) |
|
||||||
|
|
||||||
async function handleSubmit() { |
|
||||||
try { |
|
||||||
const values = await validate<ModelAttributeWithForm>() |
|
||||||
setModalProps({ confirmLoading: true }) |
|
||||||
values.modelId = props.modelId |
|
||||||
await (isUpdate.value ? updateModelAttribute(values) : createModelAttribute(values)) |
|
||||||
closeModal() |
|
||||||
emit('success') |
|
||||||
useMessage().createMessage.success('保存成功') |
|
||||||
} |
|
||||||
catch {} |
|
||||||
finally { |
|
||||||
setModalProps({ confirmLoading: false }) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
function validatorValueScope(value1: number, value2: number) { |
|
||||||
// eslint-disable-next-line prefer-promise-reject-errors |
|
||||||
return value1 > value2 ? Promise.reject() : Promise.resolve() |
|
||||||
} |
|
||||||
</script> |
|
||||||
|
|
||||||
<template> |
|
||||||
<BasicModal |
|
||||||
width="40%" |
|
||||||
:min-height="100" |
|
||||||
:title="isUpdate ? '编辑' : '新增'" |
|
||||||
:after-close="() => isUpdate = false" |
|
||||||
@register="registerModal" |
|
||||||
@ok="handleSubmit" |
|
||||||
> |
|
||||||
<BasicForm @register="registerForm"> |
|
||||||
<template #ValueScope="{ model }"> |
|
||||||
<div flex="~ justify-between gap-12px" class="custom-form-slot"> |
|
||||||
<FormItem |
|
||||||
name="min" |
|
||||||
:rules="[ |
|
||||||
{ required: true, message: '请填写最小值' }, |
|
||||||
{ validator: (_, min) => validatorValueScope(min, model.max), message: '最小值不能大于最大值' }, |
|
||||||
]" |
|
||||||
> |
|
||||||
<InputNumber v-model:value="model.min" placeholder="最小值" /> |
|
||||||
</FormItem> |
|
||||||
|
|
||||||
<span mt="5px">-</span> |
|
||||||
|
|
||||||
<FormItem |
|
||||||
name="max" |
|
||||||
:rules="[ |
|
||||||
{ required: true, message: '请填写最大值' }, |
|
||||||
{ validator: (_, max) => validatorValueScope(model.min, max), message: '最大值不能小于最小值' }, |
|
||||||
]" |
|
||||||
> |
|
||||||
<InputNumber v-model:value="model.max" placeholder="最大值" /> |
|
||||||
</FormItem> |
|
||||||
</div> |
|
||||||
</template> |
|
||||||
|
|
||||||
<template #Bool="{ model }"> |
|
||||||
<div flex="~ justify-between gap-12px" class="custom-form-slot"> |
|
||||||
<FormItem name="trueDesc" :rules="[{ required: true, message: '请填写布尔值' }]"> |
|
||||||
<Input v-model:value="model.trueDesc" addon-before="0" placeholder="如:是" /> |
|
||||||
</FormItem> |
|
||||||
|
|
||||||
<span mt="5px">-</span> |
|
||||||
|
|
||||||
<FormItem name="falseDesc" :rules="[{ required: true, message: '请填写布尔值' }]"> |
|
||||||
<Input v-model:value="model.falseDesc" addon-before="1" placeholder="如:否" /> |
|
||||||
</FormItem> |
|
||||||
</div> |
|
||||||
</template> |
|
||||||
</BasicForm> |
|
||||||
</BasicModal> |
|
||||||
</template> |
|
||||||
|
|
||||||
<style scoped lang="less"> |
|
||||||
.custom-form-slot .ant-form-item { |
|
||||||
margin-bottom: 0; |
|
||||||
flex: 1; |
|
||||||
|
|
||||||
:deep(.ant-form-item-explain-error) { |
|
||||||
position: absolute; |
|
||||||
} |
|
||||||
} |
|
||||||
</style> |
|
@ -1,79 +0,0 @@ |
|||||||
<script lang="ts" setup> |
|
||||||
import { ref } from 'vue' |
|
||||||
import { useRoute } from 'vue-router' |
|
||||||
import { useMessage } from '@/hooks/web/useMessage' |
|
||||||
import { BasicForm, useForm } from '@/components/Form' |
|
||||||
import { BasicModal, useModalInner } from '@/components/Modal' |
|
||||||
import { createModelService, updateModelService } from '@/api/product/model' |
|
||||||
import type { ModelService } from '@/api/product/types' |
|
||||||
|
|
||||||
defineOptions({ name: 'ModelServiceFormModal' }) |
|
||||||
|
|
||||||
const emit = defineEmits(['success', 'register']) |
|
||||||
|
|
||||||
const isUpdate = ref(false) |
|
||||||
const [registerForm, { setFieldsValue, validate }] = useForm({ |
|
||||||
labelWidth: 80, |
|
||||||
baseColProps: { span: 24 }, |
|
||||||
schemas: [ |
|
||||||
{ |
|
||||||
field: 'id', |
|
||||||
show: false, |
|
||||||
component: 'Input', |
|
||||||
}, |
|
||||||
{ |
|
||||||
field: 'serviceId', |
|
||||||
label: '服务 ID', |
|
||||||
required: true, |
|
||||||
component: 'Input', |
|
||||||
componentProps: { |
|
||||||
maxlength: 30, |
|
||||||
showCount: true, |
|
||||||
}, |
|
||||||
}, |
|
||||||
{ |
|
||||||
field: 'description', |
|
||||||
label: '服务描述', |
|
||||||
required: true, |
|
||||||
component: 'InputTextArea', |
|
||||||
}, |
|
||||||
], |
|
||||||
showActionButtonGroup: false, |
|
||||||
actionColOptions: { span: 23 }, |
|
||||||
}) |
|
||||||
|
|
||||||
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data: ModelService) => { |
|
||||||
isUpdate.value = true |
|
||||||
setFieldsValue({ ...data }) |
|
||||||
}) |
|
||||||
|
|
||||||
const route = useRoute() |
|
||||||
async function handleSubmit() { |
|
||||||
try { |
|
||||||
const values = await validate<ModelService>() |
|
||||||
setModalProps({ confirmLoading: true }) |
|
||||||
values.productId = route.params.id as string |
|
||||||
await (isUpdate.value ? updateModelService(values) : createModelService(values)) |
|
||||||
closeModal() |
|
||||||
emit('success') |
|
||||||
useMessage().createMessage.success('保存成功') |
|
||||||
} |
|
||||||
catch {} |
|
||||||
finally { |
|
||||||
setModalProps({ confirmLoading: false }) |
|
||||||
} |
|
||||||
} |
|
||||||
</script> |
|
||||||
|
|
||||||
<template> |
|
||||||
<BasicModal |
|
||||||
width="30%" |
|
||||||
:min-height="100" |
|
||||||
:title="isUpdate ? '编辑' : '新增'" |
|
||||||
:after-close="() => isUpdate = false" |
|
||||||
@register="registerModal" |
|
||||||
@ok="handleSubmit" |
|
||||||
> |
|
||||||
<BasicForm @register="registerForm" /> |
|
||||||
</BasicModal> |
|
||||||
</template> |
|
@ -1,56 +0,0 @@ |
|||||||
<script setup lang="ts"> |
|
||||||
import { h } from 'vue' |
|
||||||
import { Alert, Tag } from 'ant-design-vue' |
|
||||||
import { LinkOutlined } from '@ant-design/icons-vue' |
|
||||||
import type { DescItem } from '@/components/Description' |
|
||||||
import { Description } from '@/components/Description' |
|
||||||
import { useSystemEnumStore } from '@/store/modules/systemEnum' |
|
||||||
import type { SubScription } from '@/api/subscription/list/types' |
|
||||||
|
|
||||||
defineProps<{ |
|
||||||
data?: SubScription |
|
||||||
}>() |
|
||||||
|
|
||||||
const { getSystemEnums } = useSystemEnumStore() |
|
||||||
|
|
||||||
const schema: DescItem[] = [ |
|
||||||
{ |
|
||||||
label: '订阅类型', |
|
||||||
field: 'type', |
|
||||||
render: () => 'mqtt', |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '订阅消息', |
|
||||||
field: 'messageType', |
|
||||||
render: (val: string) => { |
|
||||||
const values = val.split(',') |
|
||||||
const types = getSystemEnums('eSubscribeMessageType') |
|
||||||
return h( |
|
||||||
'div', |
|
||||||
types |
|
||||||
.map(item => values.includes(item.value.toString()) ? item.label : null) |
|
||||||
.filter(Boolean) |
|
||||||
.map(name => h(Tag, () => name)), |
|
||||||
) |
|
||||||
}, |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '创建时间', |
|
||||||
field: 'createTime', |
|
||||||
}, |
|
||||||
] |
|
||||||
</script> |
|
||||||
|
|
||||||
<template> |
|
||||||
<div> |
|
||||||
<Alert |
|
||||||
type="info" show-icon class="py-4px" |
|
||||||
message="服务端订阅:服务端可以直接订阅产品下多种类型的消息,例如设备上报属性、设备上报消息、设备状态变化通知、设备生命周期变更等。配置服务端订阅后配合使用【消费组】,平台会将产品下所有设备中已订阅类型的消息进行转发。" |
|
||||||
/> |
|
||||||
<a-button type="link" size="small" my="12px" @click="$router.push({ name: 'SubscriptionList', state: { productId: data?.productId } })"> |
|
||||||
<LinkOutlined /> |
|
||||||
管理订阅 |
|
||||||
</a-button> |
|
||||||
<Description :data="data" :schema="schema" :column="1" :label-style="{ width: '100px' }" /> |
|
||||||
</div> |
|
||||||
</template> |
|
@ -1,93 +0,0 @@ |
|||||||
<script lang="ts" setup> |
|
||||||
import { h, ref } from 'vue' |
|
||||||
import { useRoute } from 'vue-router' |
|
||||||
import { useMessage } from '@/hooks/web/useMessage' |
|
||||||
import { BasicForm, useForm } from '@/components/Form' |
|
||||||
import { BasicModal, useModalInner } from '@/components/Modal' |
|
||||||
import { createTopic, updateTopic } from '@/api/product/topic' |
|
||||||
import type { Topic } from '@/api/product/types' |
|
||||||
import { useSystemEnumStore } from '@/store/modules/systemEnum' |
|
||||||
|
|
||||||
defineOptions({ name: 'CustomTopicFormModal' }) |
|
||||||
|
|
||||||
const emit = defineEmits(['success', 'register']) |
|
||||||
|
|
||||||
const isUpdate = ref(false) |
|
||||||
const { getSystemEnums } = useSystemEnumStore() |
|
||||||
const [registerForm, { setFieldsValue, validate }] = useForm({ |
|
||||||
labelWidth: 100, |
|
||||||
baseColProps: { span: 24 }, |
|
||||||
schemas: [ |
|
||||||
{ |
|
||||||
field: 'id', |
|
||||||
show: false, |
|
||||||
component: 'Input', |
|
||||||
}, |
|
||||||
{ |
|
||||||
field: 'topic', |
|
||||||
label: 'Topic', |
|
||||||
required: true, |
|
||||||
// eslint-disable-next-line no-template-curly-in-string |
|
||||||
helpMessage: 'Topic必须包含/${deviceSn}/占位符', |
|
||||||
component: 'Input', |
|
||||||
rules: [ |
|
||||||
// eslint-disable-next-line no-template-curly-in-string |
|
||||||
{ pattern: /\/\$\{deviceSn}\//, message: h('span', 'Topic必须包含/${deviceSn}/占位符') }, |
|
||||||
], |
|
||||||
dynamicDisabled: () => isUpdate.value, |
|
||||||
}, |
|
||||||
{ |
|
||||||
field: 'topicType', |
|
||||||
label: '操作权限', |
|
||||||
required: true, |
|
||||||
component: 'Select', |
|
||||||
componentProps: { |
|
||||||
options: getSystemEnums('eProductTopicType'), |
|
||||||
}, |
|
||||||
dynamicDisabled: () => isUpdate.value, |
|
||||||
}, |
|
||||||
{ |
|
||||||
field: 'topicDesc', |
|
||||||
label: '描述', |
|
||||||
component: 'InputTextArea', |
|
||||||
}, |
|
||||||
], |
|
||||||
showActionButtonGroup: false, |
|
||||||
actionColOptions: { span: 23 }, |
|
||||||
}) |
|
||||||
|
|
||||||
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data: Topic) => { |
|
||||||
isUpdate.value = true |
|
||||||
setFieldsValue({ ...data }) |
|
||||||
}) |
|
||||||
|
|
||||||
const route = useRoute() |
|
||||||
async function handleSubmit() { |
|
||||||
try { |
|
||||||
const values = await validate<Topic>() |
|
||||||
setModalProps({ confirmLoading: true }) |
|
||||||
values.productId = route.params.id as string |
|
||||||
await (isUpdate.value ? updateTopic(values) : createTopic(values)) |
|
||||||
closeModal() |
|
||||||
emit('success') |
|
||||||
useMessage().createMessage.success('保存成功') |
|
||||||
} |
|
||||||
catch {} |
|
||||||
finally { |
|
||||||
setModalProps({ confirmLoading: false }) |
|
||||||
} |
|
||||||
} |
|
||||||
</script> |
|
||||||
|
|
||||||
<template> |
|
||||||
<BasicModal |
|
||||||
width="30%" |
|
||||||
:min-height="100" |
|
||||||
:title="isUpdate ? '编辑' : '新增'" |
|
||||||
:after-close="() => isUpdate = false" |
|
||||||
@register="registerModal" |
|
||||||
@ok="handleSubmit" |
|
||||||
> |
|
||||||
<BasicForm @register="registerForm" /> |
|
||||||
</BasicModal> |
|
||||||
</template> |
|
@ -1,145 +0,0 @@ |
|||||||
<script setup lang="ts"> |
|
||||||
import { Alert, Segmented } from 'ant-design-vue' |
|
||||||
import { computed, ref } from 'vue' |
|
||||||
import { useRoute } from 'vue-router' |
|
||||||
import { PlusOutlined, SyncOutlined } from '@ant-design/icons-vue' |
|
||||||
import TopicFormModal from './TopicFormModal.vue' |
|
||||||
import type { Topic } from '@/api/product/types' |
|
||||||
import { TopicType } from '@/api/product/types' |
|
||||||
import { BasicTable, TableAction, useTable } from '@/components/Table' |
|
||||||
import { deleteTopic, getTopicList } from '@/api/product/topic' |
|
||||||
import { useSystemEnumStore } from '@/store/modules/systemEnum' |
|
||||||
import { useModal } from '@/components/Modal' |
|
||||||
import { useMessage } from '@/hooks/web/useMessage' |
|
||||||
import { usePermission } from '@/hooks/web/usePermission' |
|
||||||
|
|
||||||
const route = useRoute() |
|
||||||
const { getSystemEnumLabel } = useSystemEnumStore() |
|
||||||
|
|
||||||
const [registerModal, { openModal }] = useModal<Topic>() |
|
||||||
|
|
||||||
const topicType = ref<TopicType>(TopicType.System) |
|
||||||
const isCustomTopic = computed(() => topicType.value === TopicType.Custom) |
|
||||||
const alertMessage = computed( |
|
||||||
() => isCustomTopic.value |
|
||||||
? 'Topic用以将设备数据分类上报,进而分别进行处理。除了系统预置的Topic,您也可以自定义Topic,然后在设备侧开发时选择数据上报的Topic。' |
|
||||||
: '以下是系统Topic,设备侧通过系统预设的topic接收数据时,无需提前订阅。', |
|
||||||
) |
|
||||||
|
|
||||||
const { hasPermission } = usePermission() |
|
||||||
|
|
||||||
const [register, { reload, setTableData }] = useTable({ |
|
||||||
api(params) { |
|
||||||
return getTopicList({ |
|
||||||
...params, |
|
||||||
productId: route.params.id, |
|
||||||
topicCategory: topicType.value, |
|
||||||
}) |
|
||||||
}, |
|
||||||
columns: [ |
|
||||||
{ |
|
||||||
title: 'Topic', |
|
||||||
dataIndex: 'topic', |
|
||||||
}, |
|
||||||
{ |
|
||||||
title: '操作权限', |
|
||||||
dataIndex: 'topicType', |
|
||||||
customRender: ({ value }) => getSystemEnumLabel('eProductTopicType', value), |
|
||||||
}, |
|
||||||
{ |
|
||||||
title: '开启解析脚本', |
|
||||||
dataIndex: 'enableScript', |
|
||||||
ifShow: () => isCustomTopic.value, |
|
||||||
}, |
|
||||||
{ |
|
||||||
title: '描述', |
|
||||||
dataIndex: 'topicDesc', |
|
||||||
}, |
|
||||||
], |
|
||||||
bordered: true, |
|
||||||
inset: true, |
|
||||||
canResize: false, |
|
||||||
actionColumn: { |
|
||||||
width: 150, |
|
||||||
title: '操作', |
|
||||||
dataIndex: 'action', |
|
||||||
fixed: 'right', |
|
||||||
ifShow: () => isCustomTopic.value, |
|
||||||
auth: ['product_topic_edit', 'product_topic_delete'], |
|
||||||
}, |
|
||||||
}) |
|
||||||
|
|
||||||
function onChangeTopicType() { |
|
||||||
setTableData([]) |
|
||||||
reload() |
|
||||||
} |
|
||||||
|
|
||||||
async function handleDelete(id: string) { |
|
||||||
try { |
|
||||||
await deleteTopic(id) |
|
||||||
useMessage().createMessage.success('删除成功') |
|
||||||
reload() |
|
||||||
} |
|
||||||
catch {} |
|
||||||
} |
|
||||||
</script> |
|
||||||
|
|
||||||
<template> |
|
||||||
<div> |
|
||||||
<div flex="~ items-center justify-between gap-12px" mb="12px"> |
|
||||||
<div flex="~ wrap gap-12px"> |
|
||||||
<Segmented |
|
||||||
v-model:value="topicType" |
|
||||||
:options="[{ label: '系统 Topic', value: TopicType.System }, { label: '自定义 Topic', value: TopicType.Custom }]" |
|
||||||
@change="onChangeTopicType" |
|
||||||
/> |
|
||||||
|
|
||||||
<a-button v-if="isCustomTopic && hasPermission('product_topic_add')" type="primary" @click="openModal"> |
|
||||||
<PlusOutlined /> |
|
||||||
新增 Topic |
|
||||||
</a-button> |
|
||||||
|
|
||||||
<Alert :message="alertMessage" type="info" show-icon class="py-4px" /> |
|
||||||
</div> |
|
||||||
|
|
||||||
<div class="mr-10px flex cursor-pointer items-center"> |
|
||||||
<a-button size="small" @click="reload"> |
|
||||||
<template #icon> |
|
||||||
<SyncOutlined /> |
|
||||||
</template> |
|
||||||
刷新 |
|
||||||
</a-button> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
|
|
||||||
<BasicTable :api="async () => ([] as Topic[])" @register="register"> |
|
||||||
<template #bodyCell="{ column, record }"> |
|
||||||
<template v-if="column.key === 'action'"> |
|
||||||
<TableAction |
|
||||||
:actions="[ |
|
||||||
{ |
|
||||||
icon: 'i-ant-design:edit-outlined', |
|
||||||
label: '编辑', |
|
||||||
auth: 'product_topic_edit', |
|
||||||
onClick: () => openModal(true, record), |
|
||||||
}, |
|
||||||
{ |
|
||||||
icon: 'i-ant-design:delete-outlined', |
|
||||||
danger: true, |
|
||||||
label: '删除', |
|
||||||
auth: 'product_topic_delete', |
|
||||||
popConfirm: { |
|
||||||
title: '是否要删除数据?', |
|
||||||
placement: 'left', |
|
||||||
confirm: () => handleDelete(record.id), |
|
||||||
}, |
|
||||||
}, |
|
||||||
]" |
|
||||||
/> |
|
||||||
</template> |
|
||||||
</template> |
|
||||||
</BasicTable> |
|
||||||
|
|
||||||
<TopicFormModal @register="registerModal" @success="reload" /> |
|
||||||
</div> |
|
||||||
</template> |
|
@ -1,78 +0,0 @@ |
|||||||
import { unref } from 'vue' |
|
||||||
import type { MaybeRef, Ref } from 'vue' |
|
||||||
import { useTable } from '@/components/Table' |
|
||||||
import { deleteModelAttribute, getModelAttributeList } from '@/api/product/model' |
|
||||||
import { useModal } from '@/components/Modal' |
|
||||||
import type { ModelAttribute } from '@/api/product/types' |
|
||||||
import { useMessage } from '@/hooks/web/useMessage' |
|
||||||
|
|
||||||
export function useModelAttribute(productId: MaybeRef<string>, modelId: Ref<string | undefined>) { |
|
||||||
const [registerModelAttributeTable, { reload, setPagination }] = useTable({ |
|
||||||
async api(params) { |
|
||||||
if (!unref(modelId)) |
|
||||||
return [] |
|
||||||
|
|
||||||
return getModelAttributeList({ |
|
||||||
...params, |
|
||||||
productId: unref(productId), |
|
||||||
modelId: unref(modelId), |
|
||||||
}) |
|
||||||
}, |
|
||||||
columns: [ |
|
||||||
{ |
|
||||||
title: '功能类型', |
|
||||||
dataIndex: 'itemType', |
|
||||||
customRender: ({ value }) => ({ 1: '属性', 2: '命令' }[value]), |
|
||||||
}, |
|
||||||
{ |
|
||||||
title: '功能名称', |
|
||||||
dataIndex: 'name', |
|
||||||
}, |
|
||||||
{ |
|
||||||
title: '标识符', |
|
||||||
dataIndex: 'identifier', |
|
||||||
}, |
|
||||||
{ |
|
||||||
title: '数据类型', |
|
||||||
dataIndex: 'dataType', |
|
||||||
}, |
|
||||||
], |
|
||||||
bordered: true, |
|
||||||
inset: true, |
|
||||||
canResize: false, |
|
||||||
actionColumn: { |
|
||||||
width: 150, |
|
||||||
title: '操作', |
|
||||||
dataIndex: 'action', |
|
||||||
fixed: 'right', |
|
||||||
auth: ['product_model_attr_delete', 'product_model_attr_edit'], |
|
||||||
}, |
|
||||||
}) |
|
||||||
|
|
||||||
const [registerModelAttributeModal, { openModal: openModelAttributeModal }] = useModal<ModelAttribute>() |
|
||||||
|
|
||||||
async function handleDeleteModelAttribute(id: string) { |
|
||||||
try { |
|
||||||
await deleteModelAttribute(id) |
|
||||||
useMessage().createMessage.success('删除成功') |
|
||||||
reload() |
|
||||||
} |
|
||||||
catch {} |
|
||||||
} |
|
||||||
|
|
||||||
return { |
|
||||||
registerModelAttributeTable, |
|
||||||
registerModelAttributeModal, |
|
||||||
openModelAttributeModal, |
|
||||||
/** |
|
||||||
* @param resetPageCurrent 是否重置分页 |
|
||||||
*/ |
|
||||||
reloadModalAttribute(resetPageCurrent = false) { |
|
||||||
if (resetPageCurrent) |
|
||||||
setPagination({ current: 1 }) |
|
||||||
|
|
||||||
reload() |
|
||||||
}, |
|
||||||
handleDeleteModelAttribute, |
|
||||||
} |
|
||||||
} |
|
@ -1,41 +0,0 @@ |
|||||||
import { ref } from 'vue' |
|
||||||
import { useAsyncState, watchOnce } from '@vueuse/core' |
|
||||||
import { deleteModelService, getAllModelServices } from '@/api/product/model' |
|
||||||
import { useMessage } from '@/hooks/web/useMessage' |
|
||||||
import { useModal } from '@/components/Modal' |
|
||||||
import type { ModelService } from '@/api/product/types' |
|
||||||
|
|
||||||
export function useModelService(productId: string, defaultModelId?: string) { |
|
||||||
const selectedModelId = ref<string>() |
|
||||||
|
|
||||||
const { state, execute } = useAsyncState(() => getAllModelServices(productId), [], { |
|
||||||
resetOnExecute: false, |
|
||||||
}) |
|
||||||
|
|
||||||
// 默认选择的 ModelId, 如果没有 defaultModelId 则是第一个元素的 Id
|
|
||||||
watchOnce(state, () => { |
|
||||||
if (state.value.length > 0) |
|
||||||
selectedModelId.value = defaultModelId || state.value[0].id |
|
||||||
}) |
|
||||||
|
|
||||||
const [registerModelServiceModal, { openModal }] = useModal<ModelService>() |
|
||||||
|
|
||||||
async function handleDeleteModelService(id: string) { |
|
||||||
try { |
|
||||||
await deleteModelService(id) |
|
||||||
useMessage().createMessage.success('删除成功') |
|
||||||
execute() |
|
||||||
} |
|
||||||
catch {} |
|
||||||
} |
|
||||||
|
|
||||||
return { |
|
||||||
selectedModelId, |
|
||||||
setSelectedModelId: (id: string) => selectedModelId.value = id, |
|
||||||
reloadModelService: execute, |
|
||||||
modelServiceList: state, |
|
||||||
handleDeleteModelService, |
|
||||||
registerModelServiceModal, |
|
||||||
openModelServiceModal: openModal, |
|
||||||
} |
|
||||||
} |
|
@ -1,3 +0,0 @@ |
|||||||
export { default as TopicManage } from './TopicManage.vue' |
|
||||||
export { default as Model } from './Model.vue' |
|
||||||
export { default as Subscription } from './Subscription.vue' |
|
@ -1,188 +0,0 @@ |
|||||||
import type { Ref } from 'vue' |
|
||||||
import type { BasicColumn, FormSchema } from '@/components/Table' |
|
||||||
import { useSystemEnumStoreWithOut } from '@/store/modules/systemEnum' |
|
||||||
|
|
||||||
const { getSystemEnumLabel, getSystemEnums } = useSystemEnumStoreWithOut() |
|
||||||
|
|
||||||
export const columns: BasicColumn[] = [ |
|
||||||
{ |
|
||||||
title: '产品名称', |
|
||||||
dataIndex: 'productName', |
|
||||||
}, |
|
||||||
{ |
|
||||||
title: '产品标识', |
|
||||||
dataIndex: 'productKey', |
|
||||||
}, |
|
||||||
{ |
|
||||||
title: '节点类型', |
|
||||||
dataIndex: 'nodeType', |
|
||||||
customRender: ({ value }) => getSystemEnumLabel('eProductNodeType', value), |
|
||||||
}, |
|
||||||
{ |
|
||||||
title: '联网方式', |
|
||||||
dataIndex: 'networkType', |
|
||||||
customRender: ({ value }) => getSystemEnumLabel('eNetworkType', value), |
|
||||||
}, |
|
||||||
{ |
|
||||||
title: '鉴权方式', |
|
||||||
dataIndex: 'authType', |
|
||||||
customRender: ({ value }) => getSystemEnumLabel('eAuthType', value), |
|
||||||
}, |
|
||||||
{ |
|
||||||
title: '安全类型', |
|
||||||
dataIndex: 'securityType', |
|
||||||
customRender: ({ value }) => getSystemEnumLabel('eProductSecurityType', value), |
|
||||||
}, |
|
||||||
{ |
|
||||||
title: '通信协议', |
|
||||||
dataIndex: 'networkProtocol', |
|
||||||
customRender: ({ value }) => getSystemEnumLabel('eNetworkProtocol', value), |
|
||||||
}, |
|
||||||
{ |
|
||||||
title: '数据格式', |
|
||||||
dataIndex: 'dataType', |
|
||||||
customRender: ({ value }) => getSystemEnumLabel('eDataType', value), |
|
||||||
}, |
|
||||||
{ |
|
||||||
title: '创建时间', |
|
||||||
dataIndex: 'createTime', |
|
||||||
width: 200, |
|
||||||
}, |
|
||||||
] |
|
||||||
|
|
||||||
export const searchFormSchemas: FormSchema[] = [ |
|
||||||
{ |
|
||||||
label: '产品名称', |
|
||||||
field: 'productName', |
|
||||||
component: 'Input', |
|
||||||
colProps: { span: 6 }, |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '联网方式', |
|
||||||
field: 'networkType', |
|
||||||
component: 'Select', |
|
||||||
componentProps: { |
|
||||||
options: getSystemEnums('eNetworkType'), |
|
||||||
}, |
|
||||||
colProps: { span: 6 }, |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '通信协议', |
|
||||||
field: 'networkProtocol', |
|
||||||
component: 'Select', |
|
||||||
componentProps: { |
|
||||||
options: getSystemEnums('eNetworkProtocol'), |
|
||||||
}, |
|
||||||
colProps: { span: 6 }, |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '节点类型', |
|
||||||
field: 'nodeType', |
|
||||||
component: 'Select', |
|
||||||
componentProps: { |
|
||||||
options: getSystemEnums('eProductNodeType'), |
|
||||||
}, |
|
||||||
colProps: { span: 6 }, |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '安全类型', |
|
||||||
field: 'securityType', |
|
||||||
component: 'Select', |
|
||||||
componentProps: { |
|
||||||
options: getSystemEnums('eProductSecurityType'), |
|
||||||
}, |
|
||||||
colProps: { span: 6 }, |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '数据格式', |
|
||||||
field: 'dataType', |
|
||||||
component: 'Select', |
|
||||||
componentProps: { |
|
||||||
options: getSystemEnums('eDataType'), |
|
||||||
}, |
|
||||||
colProps: { span: 6 }, |
|
||||||
}, |
|
||||||
] |
|
||||||
|
|
||||||
export function getFormSchema(isUpdate: Ref<boolean>): FormSchema[] { |
|
||||||
return [ |
|
||||||
{ |
|
||||||
field: 'id', |
|
||||||
show: false, |
|
||||||
component: 'Input', |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '产品名称', |
|
||||||
field: 'productName', |
|
||||||
required: true, |
|
||||||
component: 'Input', |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '联网方式', |
|
||||||
field: 'networkType', |
|
||||||
component: 'Select', |
|
||||||
componentProps: { |
|
||||||
options: getSystemEnums('eNetworkType'), |
|
||||||
}, |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '通信协议', |
|
||||||
field: 'networkProtocol', |
|
||||||
component: 'Select', |
|
||||||
componentProps: { |
|
||||||
options: getSystemEnums('eNetworkProtocol'), |
|
||||||
}, |
|
||||||
ifShow: () => !isUpdate.value, |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '节点类型', |
|
||||||
field: 'nodeType', |
|
||||||
required: true, |
|
||||||
component: 'Select', |
|
||||||
componentProps: { |
|
||||||
options: getSystemEnums('eProductNodeType'), |
|
||||||
}, |
|
||||||
ifShow: () => !isUpdate.value, |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '安全类型', |
|
||||||
field: 'securityType', |
|
||||||
component: 'Select', |
|
||||||
componentProps: { |
|
||||||
options: getSystemEnums('eProductSecurityType'), |
|
||||||
}, |
|
||||||
ifShow: () => !isUpdate.value, |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '鉴权方式', |
|
||||||
field: 'authType', |
|
||||||
required: true, |
|
||||||
component: 'Select', |
|
||||||
componentProps: { |
|
||||||
options: getSystemEnums('eAuthType'), |
|
||||||
}, |
|
||||||
ifShow: () => !isUpdate.value, |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '数据格式', |
|
||||||
field: 'dataType', |
|
||||||
required: true, |
|
||||||
component: 'Select', |
|
||||||
componentProps: { |
|
||||||
options: getSystemEnums('eDataType'), |
|
||||||
}, |
|
||||||
ifShow: () => !isUpdate.value, |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '产品描述', |
|
||||||
field: 'productDesc', |
|
||||||
component: 'InputTextArea', |
|
||||||
}, |
|
||||||
] |
|
||||||
} |
|
||||||
|
|
||||||
export enum ProductTabEnums { |
|
||||||
TopicManage = '1', |
|
||||||
Model = '2', |
|
||||||
Subscription = '3', |
|
||||||
} |
|
@ -1,96 +0,0 @@ |
|||||||
<script setup lang="ts"> |
|
||||||
import { Card, Tabs } from 'ant-design-vue' |
|
||||||
import { useAsyncState } from '@vueuse/core' |
|
||||||
import { useRoute } from 'vue-router' |
|
||||||
import { ref } from 'vue' |
|
||||||
import { Model, Subscription, TopicManage } from './components' |
|
||||||
import { ProductTabEnums } from './data' |
|
||||||
import { Description } from '@/components/Description' |
|
||||||
import { getProductDetail } from '@/api/product' |
|
||||||
import type { DescItem } from '@/components/Description' |
|
||||||
import { useSystemEnumStore } from '@/store/modules/systemEnum' |
|
||||||
import { usePermission } from '@/hooks/web/usePermission' |
|
||||||
|
|
||||||
const route = useRoute() |
|
||||||
const { getSystemEnumLabel } = useSystemEnumStore() |
|
||||||
|
|
||||||
const { state } = useAsyncState(() => getProductDetail(route.params.id as string), undefined) |
|
||||||
const schema: DescItem[] = [ |
|
||||||
{ |
|
||||||
label: '产品名称', |
|
||||||
field: 'productName', |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '产品标识(ProductKey)', |
|
||||||
field: 'productKey', |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: 'ProductSecret', |
|
||||||
field: 'productSecret', |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '联网方式', |
|
||||||
field: 'networkType', |
|
||||||
render: value => getSystemEnumLabel('eNetworkType', value), |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '通信协议', |
|
||||||
field: 'networkProtocol', |
|
||||||
render: value => getSystemEnumLabel('eNetworkProtocol', value), |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '节点类型', |
|
||||||
field: 'nodeType', |
|
||||||
render: value => getSystemEnumLabel('eProductNodeType', value), |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '安全类型', |
|
||||||
field: 'securityType', |
|
||||||
render: value => getSystemEnumLabel('eProductSecurityType', value), |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '鉴权方式', |
|
||||||
field: 'authType', |
|
||||||
render: value => getSystemEnumLabel('eAuthType', value), |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '数据格式', |
|
||||||
field: 'dataType', |
|
||||||
render: value => getSystemEnumLabel('eDataType', value), |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '创建时间', |
|
||||||
field: 'createTime', |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '产品描述', |
|
||||||
field: 'productDesc', |
|
||||||
}, |
|
||||||
] |
|
||||||
|
|
||||||
const activeTab = ref<ProductTabEnums>(route.params.activeTab as unknown as ProductTabEnums || ProductTabEnums.TopicManage) |
|
||||||
|
|
||||||
const { hasPermission } = usePermission() |
|
||||||
</script> |
|
||||||
|
|
||||||
<template> |
|
||||||
<div p="12px"> |
|
||||||
<Card title="基础信息"> |
|
||||||
<Description :data="state" :schema="schema" :column="2" /> |
|
||||||
</Card> |
|
||||||
|
|
||||||
<Card mt="12px"> |
|
||||||
<Tabs v-model:active-key="activeTab"> |
|
||||||
<Tabs.TabPane v-if="hasPermission('product_topic_view')" :key="ProductTabEnums.TopicManage" tab="Topic 管理"> |
|
||||||
<TopicManage /> |
|
||||||
</Tabs.TabPane> |
|
||||||
<Tabs.TabPane v-if="hasPermission('product_model_view')" :key="ProductTabEnums.Model" tab="物模型"> |
|
||||||
<Model :tsl="state?.tsl" /> |
|
||||||
</Tabs.TabPane> |
|
||||||
<Tabs.TabPane v-if="hasPermission('product_subscription_view')" :key="ProductTabEnums.Subscription" tab="服务端订阅"> |
|
||||||
<Subscription :data="state?.subscribe" /> |
|
||||||
</Tabs.TabPane> |
|
||||||
</Tabs> |
|
||||||
</Card> |
|
||||||
</div> |
|
||||||
</template> |
|
@ -1,92 +0,0 @@ |
|||||||
<script setup lang="ts"> |
|
||||||
import { PlusOutlined } from '@ant-design/icons-vue' |
|
||||||
import { columns, searchFormSchemas } from './data' |
|
||||||
import ProductFormModal from './ProductFormModal.vue' |
|
||||||
import { BasicTable, TableAction, useTable } from '@/components/Table' |
|
||||||
import { deleteProduct, getProductList } from '@/api/product' |
|
||||||
import { useModal } from '@/components/Modal' |
|
||||||
import type { Product } from '@/api/product/types' |
|
||||||
import { useMessage } from '@/hooks/web/useMessage' |
|
||||||
import { usePermission } from '@/hooks/web/usePermission' |
|
||||||
|
|
||||||
defineOptions({ name: 'Product' }) |
|
||||||
|
|
||||||
const [registerModal, { openModal }] = useModal<Product>() |
|
||||||
|
|
||||||
const { hasPermission } = usePermission() |
|
||||||
|
|
||||||
const [register, { reload }] = useTable({ |
|
||||||
api: getProductList, |
|
||||||
columns, |
|
||||||
formConfig: { |
|
||||||
schemas: searchFormSchemas, |
|
||||||
labelWidth: 80, |
|
||||||
}, |
|
||||||
bordered: true, |
|
||||||
canResize: false, |
|
||||||
useSearchForm: true, |
|
||||||
actionColumn: { |
|
||||||
width: 220, |
|
||||||
title: '操作', |
|
||||||
dataIndex: 'action', |
|
||||||
fixed: 'right', |
|
||||||
auth: ['view', 'edit', 'delete'].map(code => `product_${code}`), |
|
||||||
}, |
|
||||||
}) |
|
||||||
|
|
||||||
async function handleDelete(id: string) { |
|
||||||
try { |
|
||||||
await deleteProduct(id) |
|
||||||
useMessage().createMessage.success('删除成功') |
|
||||||
reload() |
|
||||||
} |
|
||||||
catch {} |
|
||||||
} |
|
||||||
</script> |
|
||||||
|
|
||||||
<template> |
|
||||||
<div> |
|
||||||
<BasicTable :api="async () => ([] as Product[])" @register="register"> |
|
||||||
<template v-if="hasPermission('product_add')" #tableTitle> |
|
||||||
<a-button type="primary" @click="openModal"> |
|
||||||
<PlusOutlined /> |
|
||||||
新建 |
|
||||||
</a-button> |
|
||||||
</template> |
|
||||||
|
|
||||||
<template #bodyCell="{ column, record }"> |
|
||||||
<template v-if="column.key === 'action'"> |
|
||||||
<TableAction |
|
||||||
:actions="[ |
|
||||||
{ |
|
||||||
icon: 'i-ant-design:file-search-outlined', |
|
||||||
label: '详情', |
|
||||||
auth: 'product_view', |
|
||||||
onClick: () => $router.push(`/product/detail/${record.id}`), |
|
||||||
}, |
|
||||||
{ |
|
||||||
icon: 'i-ant-design:edit-outlined', |
|
||||||
label: '编辑', |
|
||||||
auth: 'product_edit', |
|
||||||
onClick: () => openModal(true, record), |
|
||||||
}, |
|
||||||
{ |
|
||||||
icon: 'i-ant-design:delete-outlined', |
|
||||||
danger: true, |
|
||||||
label: '删除', |
|
||||||
auth: 'product_delete', |
|
||||||
popConfirm: { |
|
||||||
title: '是否要删除数据?', |
|
||||||
placement: 'left', |
|
||||||
confirm: () => handleDelete(record.id), |
|
||||||
}, |
|
||||||
}, |
|
||||||
]" |
|
||||||
/> |
|
||||||
</template> |
|
||||||
</template> |
|
||||||
</BasicTable> |
|
||||||
|
|
||||||
<ProductFormModal @register="registerModal" @success="reload" /> |
|
||||||
</div> |
|
||||||
</template> |
|
@ -1,65 +0,0 @@ |
|||||||
<script lang="ts" setup> |
|
||||||
import { ref } from 'vue' |
|
||||||
import { formSchema } from './data' |
|
||||||
import { useMessage } from '@/hooks/web/useMessage' |
|
||||||
import { BasicForm, useForm } from '@/components/Form' |
|
||||||
import { BasicModal, useModalInner } from '@/components/Modal' |
|
||||||
import { createConsumer, updateConsumer } from '@/api/subscription/consumer' |
|
||||||
import type { Consumer } from '@/api/subscription/consumer/types' |
|
||||||
import { getSubscriptionDetail } from '@/api/subscription/list' |
|
||||||
import { noop } from '@/utils' |
|
||||||
|
|
||||||
defineOptions({ name: 'ConsumerFormModal' }) |
|
||||||
|
|
||||||
const emit = defineEmits(['success', 'register']) |
|
||||||
|
|
||||||
const isUpdate = ref(false) |
|
||||||
const [registerForm, { setFieldsValue, validate }] = useForm({ |
|
||||||
labelWidth: 100, |
|
||||||
baseColProps: { span: 24 }, |
|
||||||
schemas: formSchema, |
|
||||||
showActionButtonGroup: false, |
|
||||||
actionColOptions: { span: 23 }, |
|
||||||
}) |
|
||||||
|
|
||||||
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (id: string) => { |
|
||||||
isUpdate.value = true |
|
||||||
setModalProps({ confirmLoading: true, loading: true }) |
|
||||||
getSubscriptionDetail(id) |
|
||||||
.then((data) => { |
|
||||||
setFieldsValue({ ...data, subscribeIds: data.subscribes ? data.subscribes.split(',') : [] }) |
|
||||||
}) |
|
||||||
.catch(noop) |
|
||||||
.finally(() => { |
|
||||||
setModalProps({ confirmLoading: false, loading: false }) |
|
||||||
}) |
|
||||||
}) |
|
||||||
|
|
||||||
async function handleSubmit() { |
|
||||||
try { |
|
||||||
const values = await validate<Consumer & { subscribeIds: string[] | string }>() |
|
||||||
setModalProps({ confirmLoading: true }) |
|
||||||
await (isUpdate.value ? updateConsumer(values) : createConsumer(values)) |
|
||||||
closeModal() |
|
||||||
emit('success') |
|
||||||
useMessage().createMessage.success('保存成功') |
|
||||||
} |
|
||||||
catch {} |
|
||||||
finally { |
|
||||||
setModalProps({ confirmLoading: false }) |
|
||||||
} |
|
||||||
} |
|
||||||
</script> |
|
||||||
|
|
||||||
<template> |
|
||||||
<BasicModal |
|
||||||
width="30%" |
|
||||||
:min-height="100" |
|
||||||
:title="isUpdate ? '编辑' : '新增'" |
|
||||||
:after-close="() => isUpdate = false" |
|
||||||
@register="registerModal" |
|
||||||
@ok="handleSubmit" |
|
||||||
> |
|
||||||
<BasicForm @register="registerForm" /> |
|
||||||
</BasicModal> |
|
||||||
</template> |
|
@ -1,34 +0,0 @@ |
|||||||
<script lang="ts" setup> |
|
||||||
import { getOnlineClientList } from '@/api/subscription/consumer' |
|
||||||
import { BasicTable, useTable } from '@/components/Table' |
|
||||||
|
|
||||||
const props = defineProps<{ |
|
||||||
consumerToken: string |
|
||||||
}>() |
|
||||||
|
|
||||||
const [register] = useTable({ |
|
||||||
api: () => getOnlineClientList(props.consumerToken), |
|
||||||
columns: [ |
|
||||||
{ |
|
||||||
title: '客户端 ID', |
|
||||||
dataIndex: 'uuid', |
|
||||||
}, |
|
||||||
{ |
|
||||||
title: '客户端 IP', |
|
||||||
dataIndex: 'address', |
|
||||||
}, |
|
||||||
{ |
|
||||||
title: '最后上线时间', |
|
||||||
dataIndex: 'onlineDate', |
|
||||||
}, |
|
||||||
], |
|
||||||
bordered: true, |
|
||||||
inset: true, |
|
||||||
canResize: false, |
|
||||||
pagination: false, |
|
||||||
}) |
|
||||||
</script> |
|
||||||
|
|
||||||
<template> |
|
||||||
<BasicTable @register="register" /> |
|
||||||
</template> |
|
@ -1,92 +0,0 @@ |
|||||||
<script lang="ts" setup> |
|
||||||
import { h, ref } from 'vue' |
|
||||||
import { Popconfirm, Space, Tag } from 'ant-design-vue' |
|
||||||
import { DisconnectOutlined } from '@ant-design/icons-vue' |
|
||||||
import { useAsyncState } from '@vueuse/core' |
|
||||||
import { disSubscription, getSubscribeList } from '@/api/subscription/consumer' |
|
||||||
import { BasicTable, useTable } from '@/components/Table' |
|
||||||
import { useSystemEnumStore } from '@/store/modules/systemEnum' |
|
||||||
import { getAllProducts } from '@/api/product' |
|
||||||
import type { Product } from '@/api/subscription/consumer/types' |
|
||||||
import { noop } from '@/utils' |
|
||||||
|
|
||||||
const props = defineProps<{ consumerId: string }>() |
|
||||||
|
|
||||||
const { getSystemEnums } = useSystemEnumStore() |
|
||||||
|
|
||||||
const { state: products } = useAsyncState(getAllProducts, []) |
|
||||||
|
|
||||||
const [register, { reload }] = useTable({ |
|
||||||
api: () => getSubscribeList(props.consumerId), |
|
||||||
columns: [ |
|
||||||
{ |
|
||||||
title: '产品名称', |
|
||||||
dataIndex: 'productId', |
|
||||||
customRender({ value }) { |
|
||||||
const product = products.value.find(item => item.id === value) |
|
||||||
return product && product.productName |
|
||||||
}, |
|
||||||
}, |
|
||||||
{ |
|
||||||
title: '推送消息类型', |
|
||||||
dataIndex: 'messageType', |
|
||||||
customRender({ value }) { |
|
||||||
const values = value.split(',') |
|
||||||
const types = getSystemEnums('eSubscribeMessageType') |
|
||||||
return h( |
|
||||||
Space, |
|
||||||
() => types |
|
||||||
.map(item => values.includes(item.value.toString()) ? item.label : null) |
|
||||||
.filter(Boolean) |
|
||||||
.map(name => h(Tag, () => name)), |
|
||||||
) |
|
||||||
}, |
|
||||||
}, |
|
||||||
], |
|
||||||
bordered: true, |
|
||||||
inset: true, |
|
||||||
canResize: false, |
|
||||||
pagination: false, |
|
||||||
actionColumn: { |
|
||||||
width: 200, |
|
||||||
title: '操作', |
|
||||||
dataIndex: 'action', |
|
||||||
fixed: 'right', |
|
||||||
auth: ['consumer_disconnect'], |
|
||||||
}, |
|
||||||
}) |
|
||||||
|
|
||||||
const loading = ref(false) |
|
||||||
function handleDisconnect(serverSubscribeId: string) { |
|
||||||
loading.value = true |
|
||||||
disSubscription(props.consumerId, serverSubscribeId) |
|
||||||
.then(() => { |
|
||||||
reload() |
|
||||||
}) |
|
||||||
.catch(noop) |
|
||||||
.finally(() => { |
|
||||||
loading.value = false |
|
||||||
}) |
|
||||||
} |
|
||||||
</script> |
|
||||||
|
|
||||||
<template> |
|
||||||
<BasicTable :api="async () => ([] as Product[])" @register="register"> |
|
||||||
<template #bodyCell="{ column, record }"> |
|
||||||
<template v-if="column.key === 'action'"> |
|
||||||
<Popconfirm |
|
||||||
title="删除消费组后,该组内的消费者都将立即停止接收消息,并会清除所有相关资源,您确定要删除吗?" |
|
||||||
ok-text="确定" |
|
||||||
cancel-text="取消" |
|
||||||
placement="left" |
|
||||||
@confirm="() => handleDisconnect(record.id)" |
|
||||||
> |
|
||||||
<a-button type="link" size="small" danger :loading="loading"> |
|
||||||
<DisconnectOutlined /> |
|
||||||
解除订阅 |
|
||||||
</a-button> |
|
||||||
</Popconfirm> |
|
||||||
</template> |
|
||||||
</template> |
|
||||||
</BasicTable> |
|
||||||
</template> |
|
@ -1,2 +0,0 @@ |
|||||||
export { default as OnlineClient } from './OnlineClient.vue' |
|
||||||
export { default as Product } from './Product.vue' |
|
@ -1,50 +0,0 @@ |
|||||||
import type { BasicColumn, FormSchema } from '@/components/Table' |
|
||||||
import { getAllSubscription } from '@/api/subscription/list' |
|
||||||
|
|
||||||
export const columns: BasicColumn[] = [ |
|
||||||
{ |
|
||||||
title: '消费组名称', |
|
||||||
dataIndex: 'consumerName', |
|
||||||
}, |
|
||||||
{ |
|
||||||
title: '消费组 Token', |
|
||||||
dataIndex: 'consumerToken', |
|
||||||
}, |
|
||||||
{ |
|
||||||
title: '创建时间', |
|
||||||
dataIndex: 'createTime', |
|
||||||
}, |
|
||||||
] |
|
||||||
|
|
||||||
export const searchFormSchema: FormSchema[] = [ |
|
||||||
{ |
|
||||||
field: 'consumerName', |
|
||||||
label: '消费组名称', |
|
||||||
component: 'Input', |
|
||||||
colProps: { |
|
||||||
span: 6, |
|
||||||
}, |
|
||||||
}, |
|
||||||
] |
|
||||||
|
|
||||||
export const formSchema: FormSchema[] = [ |
|
||||||
{ |
|
||||||
field: 'consumerName', |
|
||||||
fields: ['id'], |
|
||||||
label: '消费组名称', |
|
||||||
required: true, |
|
||||||
component: 'Input', |
|
||||||
}, |
|
||||||
{ |
|
||||||
field: 'subscribeIds', |
|
||||||
label: '订阅', |
|
||||||
required: true, |
|
||||||
component: 'ApiSelect', |
|
||||||
componentProps: { |
|
||||||
api: getAllSubscription, |
|
||||||
mode: 'multiple', |
|
||||||
valueField: 'id', |
|
||||||
labelField: 'productName', |
|
||||||
}, |
|
||||||
}, |
|
||||||
] |
|
@ -1,99 +0,0 @@ |
|||||||
<script lang="ts" setup> |
|
||||||
import { useAsyncState } from '@vueuse/core' |
|
||||||
import { Button, Card, Tabs } from 'ant-design-vue' |
|
||||||
import { CopyOutlined } from '@ant-design/icons-vue' |
|
||||||
import { useRoute } from 'vue-router' |
|
||||||
import { h } from 'vue' |
|
||||||
import { OnlineClient, Product } from './components' |
|
||||||
import { getConsumerDetail } from '@/api/subscription/consumer' |
|
||||||
import type { DescItem } from '@/components/Description' |
|
||||||
import { Description } from '@/components/Description' |
|
||||||
import { copyText } from '@/utils/copyTextToClipboard' |
|
||||||
import { usePermission } from '@/hooks/web/usePermission' |
|
||||||
|
|
||||||
const route = useRoute() |
|
||||||
const { state } = useAsyncState(() => getConsumerDetail(route.params.id as string), undefined) |
|
||||||
const schema: DescItem[] = [ |
|
||||||
{ |
|
||||||
field: 'consumerToken', |
|
||||||
label: '消费组 Token', |
|
||||||
render(value) { |
|
||||||
return h('div', [ |
|
||||||
value, |
|
||||||
h(Button, { |
|
||||||
size: 'small', |
|
||||||
type: 'link', |
|
||||||
style: { |
|
||||||
marginLeft: '5px', |
|
||||||
}, |
|
||||||
onClick: () => copyText(value), |
|
||||||
}, () => [h(CopyOutlined), '复制']), |
|
||||||
]) |
|
||||||
}, |
|
||||||
}, |
|
||||||
{ |
|
||||||
field: 'consumerName', |
|
||||||
label: '消费组名称', |
|
||||||
}, |
|
||||||
{ |
|
||||||
field: 'tenantId', |
|
||||||
label: '企业编号', |
|
||||||
render(value) { |
|
||||||
return h('div', [ |
|
||||||
value, |
|
||||||
h(Button, { |
|
||||||
size: 'small', |
|
||||||
type: 'link', |
|
||||||
style: { |
|
||||||
marginLeft: '5px', |
|
||||||
}, |
|
||||||
onClick: () => copyText(value), |
|
||||||
}, () => [h(CopyOutlined), '复制']), |
|
||||||
]) |
|
||||||
}, |
|
||||||
}, |
|
||||||
{ |
|
||||||
field: 'Topic', |
|
||||||
label: 'Topic', |
|
||||||
render(_, data) { |
|
||||||
const value = `$SYS/${data.tenantId}/${data.consumerToken}/status ` |
|
||||||
return h('div', [ |
|
||||||
value, |
|
||||||
h(Button, { |
|
||||||
size: 'small', |
|
||||||
type: 'link', |
|
||||||
style: { |
|
||||||
marginLeft: '5px', |
|
||||||
}, |
|
||||||
onClick: () => copyText(value), |
|
||||||
}, () => [h(CopyOutlined), '复制']), |
|
||||||
]) |
|
||||||
}, |
|
||||||
}, |
|
||||||
{ |
|
||||||
field: 'createTime', |
|
||||||
label: '创建时间', |
|
||||||
}, |
|
||||||
] |
|
||||||
|
|
||||||
const { hasPermission } = usePermission() |
|
||||||
</script> |
|
||||||
|
|
||||||
<template> |
|
||||||
<div p="12px"> |
|
||||||
<Card title="基础信息"> |
|
||||||
<Description :data="state" :schema="schema" :column="1" :label-style="{ width: '140px' }" /> |
|
||||||
</Card> |
|
||||||
|
|
||||||
<Card v-if="hasPermission(['consumer_client_view', 'consumer_product_view'])" mt="12px"> |
|
||||||
<Tabs> |
|
||||||
<Tabs.TabPane v-if="hasPermission('consumer_client_view')" key="1" tab="在线客户端"> |
|
||||||
<OnlineClient v-if="state" :consumer-token="state.consumerToken" /> |
|
||||||
</Tabs.TabPane> |
|
||||||
<Tabs.TabPane v-if="hasPermission('consumer_product_view')" key="2" tab="订阅产品"> |
|
||||||
<Product v-if="state" :consumer-id="state.id" /> |
|
||||||
</Tabs.TabPane> |
|
||||||
</Tabs> |
|
||||||
</Card> |
|
||||||
</div> |
|
||||||
</template> |
|
@ -1,91 +0,0 @@ |
|||||||
<script lang="ts" setup> |
|
||||||
import { PlusOutlined } from '@ant-design/icons-vue' |
|
||||||
import { columns, searchFormSchema } from './data' |
|
||||||
import ConsumerFormModal from './ConsumerFormModal.vue' |
|
||||||
import { BasicTable, TableAction, useTable } from '@/components/Table' |
|
||||||
import { deleteConsumer, getConsumerList } from '@/api/subscription/consumer' |
|
||||||
import { useModal } from '@/components/Modal' |
|
||||||
import type { Consumer } from '@/api/subscription/consumer/types' |
|
||||||
import { useMessage } from '@/hooks/web/useMessage' |
|
||||||
import { usePermission } from '@/hooks/web/usePermission' |
|
||||||
|
|
||||||
defineOptions({ name: 'Consumer' }) |
|
||||||
|
|
||||||
const [registerModal, { openModal }] = useModal<string>() |
|
||||||
|
|
||||||
const { hasPermission } = usePermission() |
|
||||||
|
|
||||||
const [register, { reload }] = useTable({ |
|
||||||
api: getConsumerList, |
|
||||||
columns, |
|
||||||
formConfig: { |
|
||||||
labelWidth: 80, |
|
||||||
schemas: searchFormSchema, |
|
||||||
}, |
|
||||||
bordered: true, |
|
||||||
canResize: false, |
|
||||||
useSearchForm: true, |
|
||||||
actionColumn: { |
|
||||||
width: 220, |
|
||||||
title: '操作', |
|
||||||
dataIndex: 'action', |
|
||||||
fixed: 'right', |
|
||||||
}, |
|
||||||
}) |
|
||||||
|
|
||||||
async function handleDelete(id: string) { |
|
||||||
try { |
|
||||||
await deleteConsumer(id) |
|
||||||
useMessage().createMessage.success('删除成功') |
|
||||||
reload() |
|
||||||
} |
|
||||||
catch {} |
|
||||||
} |
|
||||||
</script> |
|
||||||
|
|
||||||
<template> |
|
||||||
<div> |
|
||||||
<BasicTable :api="async () => ([] as Consumer[])" @register="register"> |
|
||||||
<template v-if="hasPermission('consumer_add')" #tableTitle> |
|
||||||
<a-button type="primary" @click="openModal()"> |
|
||||||
<PlusOutlined /> |
|
||||||
创建 |
|
||||||
</a-button> |
|
||||||
</template> |
|
||||||
|
|
||||||
<template #bodyCell="{ column, record }"> |
|
||||||
<template v-if="column.key === 'action'"> |
|
||||||
<TableAction |
|
||||||
:actions="[ |
|
||||||
{ |
|
||||||
icon: 'i-ant-design:file-search-outlined', |
|
||||||
label: '详情', |
|
||||||
auth: 'consumer_view', |
|
||||||
onClick: () => $router.push(`/subscription/consumer/detail/${record.id}`), |
|
||||||
}, |
|
||||||
{ |
|
||||||
icon: 'i-ant-design:edit-outlined', |
|
||||||
label: '修改', |
|
||||||
auth: 'consumer_edit', |
|
||||||
onClick: () => openModal(true, record.id), |
|
||||||
}, |
|
||||||
{ |
|
||||||
icon: 'i-ant-design:delete-outlined', |
|
||||||
danger: true, |
|
||||||
label: '删除', |
|
||||||
auth: 'consumer_delete', |
|
||||||
popConfirm: { |
|
||||||
title: '确定要删除数据吗?', |
|
||||||
placement: 'left', |
|
||||||
confirm: () => handleDelete(record.id), |
|
||||||
}, |
|
||||||
}, |
|
||||||
]" |
|
||||||
/> |
|
||||||
</template> |
|
||||||
</template> |
|
||||||
</BasicTable> |
|
||||||
|
|
||||||
<ConsumerFormModal @register="registerModal" @success="reload" /> |
|
||||||
</div> |
|
||||||
</template> |
|
@ -1,59 +0,0 @@ |
|||||||
<script lang="ts" setup> |
|
||||||
import { ref } from 'vue' |
|
||||||
import { useFormSchema } from './data' |
|
||||||
import { useMessage } from '@/hooks/web/useMessage' |
|
||||||
import { BasicForm, useForm } from '@/components/Form' |
|
||||||
import { BasicModal, useModalInner } from '@/components/Modal' |
|
||||||
import { createSubscription, updateSubscription } from '@/api/subscription/list' |
|
||||||
import type { SubScription } from '@/api/subscription/list/types' |
|
||||||
|
|
||||||
defineOptions({ name: 'ProductFormModal' }) |
|
||||||
|
|
||||||
const emit = defineEmits(['success', 'register']) |
|
||||||
|
|
||||||
const isUpdate = ref(false) |
|
||||||
const [registerForm, { setFieldsValue, validate }] = useForm({ |
|
||||||
labelWidth: 100, |
|
||||||
baseColProps: { span: 24 }, |
|
||||||
schemas: useFormSchema(isUpdate), |
|
||||||
showActionButtonGroup: false, |
|
||||||
actionColOptions: { span: 23 }, |
|
||||||
}) |
|
||||||
|
|
||||||
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data: SubScription) => { |
|
||||||
isUpdate.value = true |
|
||||||
setFieldsValue({ |
|
||||||
...data, |
|
||||||
messageType: data.messageType.split(',').map(Number), |
|
||||||
}) |
|
||||||
}) |
|
||||||
|
|
||||||
async function handleSubmit() { |
|
||||||
try { |
|
||||||
const values = await validate<Pick<SubScription, 'productId' | 'id' | 'messageType'>>() |
|
||||||
setModalProps({ confirmLoading: true }) |
|
||||||
values.messageType = (values.messageType as unknown as string[]).join(',') |
|
||||||
await (isUpdate.value ? updateSubscription(values) : createSubscription(values)) |
|
||||||
closeModal() |
|
||||||
emit('success') |
|
||||||
useMessage().createMessage.success('保存成功') |
|
||||||
} |
|
||||||
catch {} |
|
||||||
finally { |
|
||||||
setModalProps({ confirmLoading: false }) |
|
||||||
} |
|
||||||
} |
|
||||||
</script> |
|
||||||
|
|
||||||
<template> |
|
||||||
<BasicModal |
|
||||||
width="30%" |
|
||||||
:min-height="100" |
|
||||||
:title="isUpdate ? '编辑' : '新增'" |
|
||||||
:after-close="() => isUpdate = false" |
|
||||||
@register="registerModal" |
|
||||||
@ok="handleSubmit" |
|
||||||
> |
|
||||||
<BasicForm @register="registerForm" /> |
|
||||||
</BasicModal> |
|
||||||
</template> |
|
@ -1,104 +0,0 @@ |
|||||||
import { h } from 'vue' |
|
||||||
import type { Ref } from 'vue' |
|
||||||
import { Space, Tag } from 'ant-design-vue' |
|
||||||
import { createSharedComposable, useAsyncState } from '@vueuse/core' |
|
||||||
import type { BasicColumn, FormSchema } from '@/components/Table' |
|
||||||
import { useSystemEnumStore } from '@/store/modules/systemEnum' |
|
||||||
import { getAllProducts } from '@/api/product' |
|
||||||
|
|
||||||
export const useSharedProducts = createSharedComposable(() => { |
|
||||||
const { state } = useAsyncState(getAllProducts, []) |
|
||||||
return { products: state } |
|
||||||
}) |
|
||||||
|
|
||||||
export function useColumns(): BasicColumn[] { |
|
||||||
const { getSystemEnums } = useSystemEnumStore() |
|
||||||
const { products } = useSharedProducts() |
|
||||||
|
|
||||||
return [ |
|
||||||
{ |
|
||||||
title: '产品名称', |
|
||||||
dataIndex: 'productId', |
|
||||||
customRender({ value }) { |
|
||||||
return products.value.find(item => item.id === value)?.productName |
|
||||||
}, |
|
||||||
}, |
|
||||||
{ |
|
||||||
title: '推送消息类型', |
|
||||||
dataIndex: 'messageType', |
|
||||||
customRender({ value }) { |
|
||||||
const values = value.split(',') |
|
||||||
const types = getSystemEnums('eSubscribeMessageType') |
|
||||||
return h( |
|
||||||
Space, |
|
||||||
() => types |
|
||||||
.map(item => values.includes(item.value.toString()) ? item.label : null) |
|
||||||
.filter(Boolean) |
|
||||||
.map(name => h(Tag, () => name)), |
|
||||||
) |
|
||||||
}, |
|
||||||
}, |
|
||||||
{ |
|
||||||
title: '创建时间', |
|
||||||
dataIndex: 'createTime', |
|
||||||
}, |
|
||||||
] |
|
||||||
} |
|
||||||
|
|
||||||
export function useSearchFormSchema(productId?: string): FormSchema[] { |
|
||||||
const { products } = useSharedProducts() |
|
||||||
|
|
||||||
return [ |
|
||||||
{ |
|
||||||
field: 'productId', |
|
||||||
label: '产品名称', |
|
||||||
component: 'Select', |
|
||||||
componentProps: { |
|
||||||
options: products as any, |
|
||||||
showSearch: true, |
|
||||||
fieldNames: { |
|
||||||
label: 'productName', |
|
||||||
value: 'id', |
|
||||||
}, |
|
||||||
}, |
|
||||||
defaultValue: productId || undefined, |
|
||||||
colProps: { |
|
||||||
span: 6, |
|
||||||
}, |
|
||||||
}, |
|
||||||
] |
|
||||||
} |
|
||||||
|
|
||||||
export function useFormSchema(isUpload: Ref<boolean>): FormSchema[] { |
|
||||||
const { products } = useSharedProducts() |
|
||||||
const { getSystemEnums } = useSystemEnumStore() |
|
||||||
|
|
||||||
return [ |
|
||||||
{ |
|
||||||
field: 'productId', |
|
||||||
fields: ['id'], |
|
||||||
label: '产品名称', |
|
||||||
required: true, |
|
||||||
component: 'ApiSelect', |
|
||||||
componentProps: { |
|
||||||
options: products as any, |
|
||||||
showSearch: true, |
|
||||||
fieldNames: { |
|
||||||
label: 'productName', |
|
||||||
value: 'id', |
|
||||||
}, |
|
||||||
}, |
|
||||||
dynamicDisabled: () => isUpload.value, |
|
||||||
}, |
|
||||||
{ |
|
||||||
field: 'messageType', |
|
||||||
label: '推送消息类型', |
|
||||||
required: true, |
|
||||||
component: 'Select', |
|
||||||
componentProps: { |
|
||||||
mode: 'multiple', |
|
||||||
options: getSystemEnums('eSubscribeMessageType'), |
|
||||||
}, |
|
||||||
}, |
|
||||||
] |
|
||||||
} |
|
@ -1,86 +0,0 @@ |
|||||||
<script lang="ts" setup> |
|
||||||
import { PlusOutlined } from '@ant-design/icons-vue' |
|
||||||
import { useColumns, useSearchFormSchema } from './data' |
|
||||||
import SubscriptionFormModal from './SubscriptionFormModal.vue' |
|
||||||
import { BasicTable, TableAction, useTable } from '@/components/Table' |
|
||||||
import { deleteSubscription, getSubscriptionList } from '@/api/subscription/list' |
|
||||||
import { useModal } from '@/components/Modal' |
|
||||||
import type { SubScription } from '@/api/subscription/list/types' |
|
||||||
import { useMessage } from '@/hooks/web/useMessage' |
|
||||||
import { usePermission } from '@/hooks/web/usePermission' |
|
||||||
|
|
||||||
defineOptions({ name: 'Subscription' }) |
|
||||||
|
|
||||||
const [registerModal, { openModal }] = useModal<SubScription>() |
|
||||||
|
|
||||||
const { hasPermission } = usePermission() |
|
||||||
|
|
||||||
const [register, { reload }] = useTable({ |
|
||||||
api: getSubscriptionList, |
|
||||||
columns: useColumns(), |
|
||||||
formConfig: { |
|
||||||
labelWidth: 80, |
|
||||||
schemas: useSearchFormSchema(history.state.productId), |
|
||||||
}, |
|
||||||
bordered: true, |
|
||||||
canResize: false, |
|
||||||
useSearchForm: true, |
|
||||||
actionColumn: { |
|
||||||
width: 160, |
|
||||||
title: '操作', |
|
||||||
dataIndex: 'action', |
|
||||||
fixed: 'right', |
|
||||||
auth: ['subscribe_edit', 'subscribe_delete'], |
|
||||||
}, |
|
||||||
}) |
|
||||||
|
|
||||||
async function handleDelete(id: string) { |
|
||||||
try { |
|
||||||
await deleteSubscription(id) |
|
||||||
useMessage().createMessage.success('删除成功') |
|
||||||
reload() |
|
||||||
} |
|
||||||
catch {} |
|
||||||
} |
|
||||||
</script> |
|
||||||
|
|
||||||
<template> |
|
||||||
<div> |
|
||||||
<BasicTable :api="async () => ([] as SubScription[])" @register="register"> |
|
||||||
<template v-if="hasPermission('subscribe_add')" #tableTitle> |
|
||||||
<a-button type="primary" @click="openModal()"> |
|
||||||
<PlusOutlined /> |
|
||||||
创建 |
|
||||||
</a-button> |
|
||||||
</template> |
|
||||||
|
|
||||||
<template #bodyCell="{ column, record }"> |
|
||||||
<template v-if="column.key === 'action'"> |
|
||||||
<TableAction |
|
||||||
:actions="[ |
|
||||||
{ |
|
||||||
icon: 'i-ant-design:edit-outlined', |
|
||||||
label: '修改', |
|
||||||
auth: 'subscribe_edit', |
|
||||||
onClick: () => openModal(true, record), |
|
||||||
}, |
|
||||||
{ |
|
||||||
icon: 'i-ant-design:delete-outlined', |
|
||||||
danger: true, |
|
||||||
label: '删除', |
|
||||||
auth: 'subscribe_delete', |
|
||||||
popConfirm: { |
|
||||||
title: '确定要删除数据吗?', |
|
||||||
placement: 'left', |
|
||||||
confirm: () => handleDelete(record.id), |
|
||||||
}, |
|
||||||
}, |
|
||||||
]" |
|
||||||
/> |
|
||||||
</template> |
|
||||||
</template> |
|
||||||
</BasicTable> |
|
||||||
|
|
||||||
<SubscriptionFormModal @register="registerModal" @success="reload" /> |
|
||||||
</div> |
|
||||||
</template> |
|
Loading…
Reference in new issue