Browse Source

feat: 产品详情 - Topic 管理

main
刘凯 1 year ago
parent
commit
61d0ea2fbe
  1. 29
      src/api/product/topic.ts
  2. 93
      src/views/product/components/CustomTopicFormModal.vue
  3. 137
      src/views/product/components/TopicManage.vue
  4. 1
      src/views/product/components/index.ts
  5. 92
      src/views/product/detail.vue

29
src/api/product/topic.ts

@ -0,0 +1,29 @@
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}`,
})
}

93
src/views/product/components/CustomTopicFormModal.vue

@ -0,0 +1,93 @@
<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: /\/\$\{.+?\}\//, 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('保存成功')
}
finally {
setModalProps({ confirmLoading: false })
}
}
</script>
<template>
<BasicModal
v-bind="$attrs"
width="30%"
:min-height="100"
:title="isUpdate ? '编辑' : '新增'"
:after-close="() => isUpdate = false"
@register="registerModal"
@ok="handleSubmit"
>
<BasicForm @register="registerForm" />
</BasicModal>
</template>

137
src/views/product/components/TopicManage.vue

@ -0,0 +1,137 @@
<script setup lang="ts">
import { Alert, Radio } from 'ant-design-vue'
import { computed, ref, watch } from 'vue'
import { useRoute } from 'vue-router'
import { PlusOutlined } from '@ant-design/icons-vue'
import CustomTopicFormModal from './CustomTopicFormModal.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'
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 [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,
},
})
watch(topicType, () => {
setTableData([])
reload()
})
async function handleDelete(id: string) {
try {
await deleteTopic(id)
useMessage().createMessage.success('删除成功')
reload()
}
catch {}
}
</script>
<template>
<div>
<div flex="! items-center wrap gap-12px" mb="12px">
<Radio.Group v-model:value="topicType" button-style="solid">
<Radio.Button :value="TopicType.System">
系统 Topic
</Radio.Button>
<Radio.Button :value="TopicType.Custom">
自定义 Topic
</Radio.Button>
</Radio.Group>
<Alert :message="alertMessage" type="info" show-icon class="py-4px" />
</div>
<div class="mb-12px flex items-center justify-between">
<a-button v-if="isCustomTopic" type="primary" @click="openModal">
<PlusOutlined />
新建
</a-button>
<div v-else />
<div class="mr-10px flex cursor-pointer items-center" @click="() => reload()">
<span class="i-ant-design:sync-outlined mr-5px text-20px" />
刷新
</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: '编辑',
onClick: () => openModal(true, record),
},
{
icon: 'i-ant-design:delete-outlined',
danger: true,
label: '删除',
popConfirm: {
title: '是否要删除数据?',
placement: 'left',
confirm: () => handleDelete(record.id),
},
},
]"
/>
</template>
</template>
</BasicTable>
<CustomTopicFormModal @register="registerModal" @success="reload" />
</div>
</template>

1
src/views/product/components/index.ts

@ -0,0 +1 @@
export { default as TopicManage } from './TopicManage.vue'

92
src/views/product/detail.vue

@ -0,0 +1,92 @@
<script setup lang="ts">
import { Card, Tabs } from 'ant-design-vue'
import { useAsyncState } from '@vueuse/core'
import { useRoute } from 'vue-router'
import { TopicManage } from './components'
import { Description } from '@/components/Description'
import { getProductDetail } from '@/api/product'
import type { DescItem } from '@/components/Description'
import { useSystemEnumStore } from '@/store/modules/systemEnum'
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',
},
]
</script>
<template>
<div p="12px">
<Card title="基础信息">
<Description :data="state" :schema="schema" :column="2" />
</Card>
<Card mt="12px">
<Tabs>
<Tabs.TabPane key="1" tab="Topic 管理">
<TopicManage />
</Tabs.TabPane>
<Tabs.TabPane key="2" tab="物模型">
物模型
</Tabs.TabPane>
<Tabs.TabPane key="3" tab="消息解析">
消息解析
</Tabs.TabPane>
<Tabs.TabPane key="3" tab="服务端订阅">
服务端订阅
</Tabs.TabPane>
</Tabs>
</Card>
</div>
</template>
Loading…
Cancel
Save