diff --git a/src/api/device-manage/device/cloud-command.ts b/src/api/device-manage/device/cloud-command.ts deleted file mode 100644 index 47d2f2b4..00000000 --- a/src/api/device-manage/device/cloud-command.ts +++ /dev/null @@ -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, - }) -} diff --git a/src/api/device-manage/device/index.ts b/src/api/device-manage/device/index.ts deleted file mode 100644 index 5682289e..00000000 --- a/src/api/device-manage/device/index.ts +++ /dev/null @@ -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, - }, - }) -} diff --git a/src/api/device-manage/device/types.ts b/src/api/device-manage/device/types.ts deleted file mode 100644 index 91667704..00000000 --- a/src/api/device-manage/device/types.ts +++ /dev/null @@ -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 -} diff --git a/src/api/device-manage/group/index.ts b/src/api/device-manage/group/index.ts deleted file mode 100644 index a13f6c55..00000000 --- a/src/api/device-manage/group/index.ts +++ /dev/null @@ -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, - }, - }) -} diff --git a/src/api/device-manage/group/types.ts b/src/api/device-manage/group/types.ts deleted file mode 100644 index efec1276..00000000 --- a/src/api/device-manage/group/types.ts +++ /dev/null @@ -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 -} diff --git a/src/api/monitor-ops/log/index.ts b/src/api/monitor-ops/log/index.ts deleted file mode 100644 index 3feadc3b..00000000 --- a/src/api/monitor-ops/log/index.ts +++ /dev/null @@ -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, - }, - }) -} diff --git a/src/api/monitor-ops/log/types.ts b/src/api/monitor-ops/log/types.ts deleted file mode 100644 index cbf18dda..00000000 --- a/src/api/monitor-ops/log/types.ts +++ /dev/null @@ -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 -} diff --git a/src/api/product/index.ts b/src/api/product/index.ts deleted file mode 100644 index d5de8d4b..00000000 --- a/src/api/product/index.ts +++ /dev/null @@ -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', - }) -} diff --git a/src/api/product/model.ts b/src/api/product/model.ts deleted file mode 100644 index 63642e36..00000000 --- a/src/api/product/model.ts +++ /dev/null @@ -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, - }, - }) -} diff --git a/src/api/product/topic.ts b/src/api/product/topic.ts deleted file mode 100644 index 893fc562..00000000 --- a/src/api/product/topic.ts +++ /dev/null @@ -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}`, - }) -} diff --git a/src/api/product/types.ts b/src/api/product/types.ts deleted file mode 100644 index 48cd6cd9..00000000 --- a/src/api/product/types.ts +++ /dev/null @@ -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 - } -} diff --git a/src/api/subscription/consumer/index.ts b/src/api/subscription/consumer/index.ts deleted file mode 100644 index f10df17a..00000000 --- a/src/api/subscription/consumer/index.ts +++ /dev/null @@ -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}`, - }) -} diff --git a/src/api/subscription/consumer/types.ts b/src/api/subscription/consumer/types.ts deleted file mode 100644 index 35742dc8..00000000 --- a/src/api/subscription/consumer/types.ts +++ /dev/null @@ -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 -} diff --git a/src/api/subscription/list/index.ts b/src/api/subscription/list/index.ts deleted file mode 100644 index f8ee4511..00000000 --- a/src/api/subscription/list/index.ts +++ /dev/null @@ -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, - }, - }) -} diff --git a/src/api/subscription/list/types.ts b/src/api/subscription/list/types.ts deleted file mode 100644 index c1a66711..00000000 --- a/src/api/subscription/list/types.ts +++ /dev/null @@ -1,11 +0,0 @@ -export interface GetSubscriptionListParams extends PageParam { - productId?: string -} - -export interface SubScription { - id: string - messageType: string - productId: string - tenantId: string - createTime: string -} diff --git a/src/views/device-manage/device/DeviceFormModal.vue b/src/views/device-manage/device/DeviceFormModal.vue deleted file mode 100644 index 7afc2b8f..00000000 --- a/src/views/device-manage/device/DeviceFormModal.vue +++ /dev/null @@ -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> diff --git a/src/views/device-manage/device/components/CloudCommand.vue b/src/views/device-manage/device/components/CloudCommand.vue deleted file mode 100644 index f901c310..00000000 --- a/src/views/device-manage/device/components/CloudCommand.vue +++ /dev/null @@ -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> diff --git a/src/views/device-manage/device/components/DeviceInfo.vue b/src/views/device-manage/device/components/DeviceInfo.vue deleted file mode 100644 index a9600e4b..00000000 --- a/src/views/device-manage/device/components/DeviceInfo.vue +++ /dev/null @@ -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> diff --git a/src/views/device-manage/device/components/MessageModal.vue b/src/views/device-manage/device/components/MessageModal.vue deleted file mode 100644 index 80e595a3..00000000 --- a/src/views/device-manage/device/components/MessageModal.vue +++ /dev/null @@ -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> diff --git a/src/views/device-manage/device/components/MqttParamsModal.vue b/src/views/device-manage/device/components/MqttParamsModal.vue deleted file mode 100644 index d6aa0f43..00000000 --- a/src/views/device-manage/device/components/MqttParamsModal.vue +++ /dev/null @@ -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> diff --git a/src/views/device-manage/device/components/SendCommandModal.vue b/src/views/device-manage/device/components/SendCommandModal.vue deleted file mode 100644 index 7c0dc6eb..00000000 --- a/src/views/device-manage/device/components/SendCommandModal.vue +++ /dev/null @@ -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> diff --git a/src/views/device-manage/device/components/TopicList.vue b/src/views/device-manage/device/components/TopicList.vue deleted file mode 100644 index 745fed33..00000000 --- a/src/views/device-manage/device/components/TopicList.vue +++ /dev/null @@ -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> diff --git a/src/views/device-manage/device/components/composables/useDeviceInfo.ts b/src/views/device-manage/device/components/composables/useDeviceInfo.ts deleted file mode 100644 index 0f021d51..00000000 --- a/src/views/device-manage/device/components/composables/useDeviceInfo.ts +++ /dev/null @@ -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, - } -} diff --git a/src/views/device-manage/device/components/composables/useDeviceProperties.ts b/src/views/device-manage/device/components/composables/useDeviceProperties.ts deleted file mode 100644 index e19c55b9..00000000 --- a/src/views/device-manage/device/components/composables/useDeviceProperties.ts +++ /dev/null @@ -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, - } -} diff --git a/src/views/device-manage/device/components/index.ts b/src/views/device-manage/device/components/index.ts deleted file mode 100644 index 66066a05..00000000 --- a/src/views/device-manage/device/components/index.ts +++ /dev/null @@ -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' diff --git a/src/views/device-manage/device/data.ts b/src/views/device-manage/device/data.ts deleted file mode 100644 index bd49e40e..00000000 --- a/src/views/device-manage/device/data.ts +++ /dev/null @@ -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', - }, - ] -} diff --git a/src/views/device-manage/device/detail.vue b/src/views/device-manage/device/detail.vue deleted file mode 100644 index 1c674b9e..00000000 --- a/src/views/device-manage/device/detail.vue +++ /dev/null @@ -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> diff --git a/src/views/device-manage/device/index.vue b/src/views/device-manage/device/index.vue deleted file mode 100644 index 9da1c6c3..00000000 --- a/src/views/device-manage/device/index.vue +++ /dev/null @@ -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> diff --git a/src/views/device-manage/group/components/BindingDeviceDrawer.vue b/src/views/device-manage/group/components/BindingDeviceDrawer.vue deleted file mode 100644 index fecdfddf..00000000 --- a/src/views/device-manage/group/components/BindingDeviceDrawer.vue +++ /dev/null @@ -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> diff --git a/src/views/device-manage/group/components/GroupFormModal.vue b/src/views/device-manage/group/components/GroupFormModal.vue deleted file mode 100644 index 996a0daa..00000000 --- a/src/views/device-manage/group/components/GroupFormModal.vue +++ /dev/null @@ -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> diff --git a/src/views/device-manage/group/components/GroupList.vue b/src/views/device-manage/group/components/GroupList.vue deleted file mode 100644 index d0c375fd..00000000 --- a/src/views/device-manage/group/components/GroupList.vue +++ /dev/null @@ -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> diff --git a/src/views/device-manage/group/components/index.ts b/src/views/device-manage/group/components/index.ts deleted file mode 100644 index 39614a1d..00000000 --- a/src/views/device-manage/group/components/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { default as GroupList } from './GroupList.vue' -export { default as BindingDeviceDrawer } from './BindingDeviceDrawer.vue' diff --git a/src/views/device-manage/group/data.ts b/src/views/device-manage/group/data.ts deleted file mode 100644 index 9f255d17..00000000 --- a/src/views/device-manage/group/data.ts +++ /dev/null @@ -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, - }, - }, - ] -} diff --git a/src/views/device-manage/group/index.vue b/src/views/device-manage/group/index.vue deleted file mode 100644 index 6e4097d6..00000000 --- a/src/views/device-manage/group/index.vue +++ /dev/null @@ -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> diff --git a/src/views/monitor-ops/log/MessageContentModal.vue b/src/views/monitor-ops/log/MessageContentModal.vue deleted file mode 100644 index ffb1e466..00000000 --- a/src/views/monitor-ops/log/MessageContentModal.vue +++ /dev/null @@ -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> diff --git a/src/views/monitor-ops/log/data.ts b/src/views/monitor-ops/log/data.ts deleted file mode 100644 index 14e6f2f6..00000000 --- a/src/views/monitor-ops/log/data.ts +++ /dev/null @@ -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, - }, - }, - ] -} diff --git a/src/views/monitor-ops/log/index.vue b/src/views/monitor-ops/log/index.vue deleted file mode 100644 index a759a86c..00000000 --- a/src/views/monitor-ops/log/index.vue +++ /dev/null @@ -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> diff --git a/src/views/product/ProductFormModal.vue b/src/views/product/ProductFormModal.vue deleted file mode 100644 index 2469206c..00000000 --- a/src/views/product/ProductFormModal.vue +++ /dev/null @@ -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> diff --git a/src/views/product/components/Model.vue b/src/views/product/components/Model.vue deleted file mode 100644 index 19e2799a..00000000 --- a/src/views/product/components/Model.vue +++ /dev/null @@ -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> diff --git a/src/views/product/components/ModelAttributeFormModal.vue b/src/views/product/components/ModelAttributeFormModal.vue deleted file mode 100644 index 768a27b5..00000000 --- a/src/views/product/components/ModelAttributeFormModal.vue +++ /dev/null @@ -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> diff --git a/src/views/product/components/ModelServiceFormModal.vue b/src/views/product/components/ModelServiceFormModal.vue deleted file mode 100644 index 0eb833b9..00000000 --- a/src/views/product/components/ModelServiceFormModal.vue +++ /dev/null @@ -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> diff --git a/src/views/product/components/Subscription.vue b/src/views/product/components/Subscription.vue deleted file mode 100644 index 6de0e2b4..00000000 --- a/src/views/product/components/Subscription.vue +++ /dev/null @@ -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> diff --git a/src/views/product/components/TopicFormModal.vue b/src/views/product/components/TopicFormModal.vue deleted file mode 100644 index a15fc823..00000000 --- a/src/views/product/components/TopicFormModal.vue +++ /dev/null @@ -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> diff --git a/src/views/product/components/TopicManage.vue b/src/views/product/components/TopicManage.vue deleted file mode 100644 index e95376a3..00000000 --- a/src/views/product/components/TopicManage.vue +++ /dev/null @@ -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> diff --git a/src/views/product/components/composables/useModelAttribute.ts b/src/views/product/components/composables/useModelAttribute.ts deleted file mode 100644 index a5db29be..00000000 --- a/src/views/product/components/composables/useModelAttribute.ts +++ /dev/null @@ -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, - } -} diff --git a/src/views/product/components/composables/useModelService.ts b/src/views/product/components/composables/useModelService.ts deleted file mode 100644 index 11f2cc72..00000000 --- a/src/views/product/components/composables/useModelService.ts +++ /dev/null @@ -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, - } -} diff --git a/src/views/product/components/index.ts b/src/views/product/components/index.ts deleted file mode 100644 index cf20d6d4..00000000 --- a/src/views/product/components/index.ts +++ /dev/null @@ -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' diff --git a/src/views/product/data.ts b/src/views/product/data.ts deleted file mode 100644 index 327de5e5..00000000 --- a/src/views/product/data.ts +++ /dev/null @@ -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', -} diff --git a/src/views/product/detail.vue b/src/views/product/detail.vue deleted file mode 100644 index ead6b75c..00000000 --- a/src/views/product/detail.vue +++ /dev/null @@ -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> diff --git a/src/views/product/index.vue b/src/views/product/index.vue deleted file mode 100644 index 4cc56301..00000000 --- a/src/views/product/index.vue +++ /dev/null @@ -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> diff --git a/src/views/subscription/consumer/ConsumerFormModal.vue b/src/views/subscription/consumer/ConsumerFormModal.vue deleted file mode 100644 index c7374e1b..00000000 --- a/src/views/subscription/consumer/ConsumerFormModal.vue +++ /dev/null @@ -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> diff --git a/src/views/subscription/consumer/components/OnlineClient.vue b/src/views/subscription/consumer/components/OnlineClient.vue deleted file mode 100644 index 4578b3c6..00000000 --- a/src/views/subscription/consumer/components/OnlineClient.vue +++ /dev/null @@ -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> diff --git a/src/views/subscription/consumer/components/Product.vue b/src/views/subscription/consumer/components/Product.vue deleted file mode 100644 index f2c98696..00000000 --- a/src/views/subscription/consumer/components/Product.vue +++ /dev/null @@ -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> diff --git a/src/views/subscription/consumer/components/index.ts b/src/views/subscription/consumer/components/index.ts deleted file mode 100644 index eda7e295..00000000 --- a/src/views/subscription/consumer/components/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { default as OnlineClient } from './OnlineClient.vue' -export { default as Product } from './Product.vue' diff --git a/src/views/subscription/consumer/data.ts b/src/views/subscription/consumer/data.ts deleted file mode 100644 index 0d9020c3..00000000 --- a/src/views/subscription/consumer/data.ts +++ /dev/null @@ -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', - }, - }, -] diff --git a/src/views/subscription/consumer/detail.vue b/src/views/subscription/consumer/detail.vue deleted file mode 100644 index 17c6f469..00000000 --- a/src/views/subscription/consumer/detail.vue +++ /dev/null @@ -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> diff --git a/src/views/subscription/consumer/index.vue b/src/views/subscription/consumer/index.vue deleted file mode 100644 index 32112427..00000000 --- a/src/views/subscription/consumer/index.vue +++ /dev/null @@ -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> diff --git a/src/views/subscription/list/SubscriptionFormModal.vue b/src/views/subscription/list/SubscriptionFormModal.vue deleted file mode 100644 index abee291a..00000000 --- a/src/views/subscription/list/SubscriptionFormModal.vue +++ /dev/null @@ -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> diff --git a/src/views/subscription/list/data.ts b/src/views/subscription/list/data.ts deleted file mode 100644 index 21b14d14..00000000 --- a/src/views/subscription/list/data.ts +++ /dev/null @@ -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'), - }, - }, - ] -} diff --git a/src/views/subscription/list/index.vue b/src/views/subscription/list/index.vue deleted file mode 100644 index 38f7c23f..00000000 --- a/src/views/subscription/list/index.vue +++ /dev/null @@ -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>