Pre Merge pull request !359 from XuZhiqiang/feat-antdv-next
commit
e12b19a90e
|
|
@ -3,7 +3,7 @@ import type { Dayjs } from 'dayjs';
|
|||
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
import { DatePicker, Radio, RadioGroup } from 'antdv-next';
|
||||
import { DateRangePicker, Radio, RadioGroup } from 'antdv-next';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
import { getRangePickerDefaultProps } from '#/utils/rangePickerProps';
|
||||
|
|
@ -88,7 +88,7 @@ onMounted(() => {
|
|||
{{ option.label }}
|
||||
</Radio>
|
||||
</RadioGroup>
|
||||
<DatePicker.RangePicker
|
||||
<DateRangePicker
|
||||
v-model:value="times"
|
||||
:format="rangePickerProps.format"
|
||||
:value-format="rangePickerProps.valueFormat"
|
||||
|
|
|
|||
|
|
@ -11,7 +11,15 @@ import { IconifyIcon } from '@vben/icons';
|
|||
import { $t } from '@vben/locales';
|
||||
import { isBoolean, isFunction } from '@vben/utils';
|
||||
|
||||
import { Button, Dropdown, Menu, Popconfirm, Space, Tooltip } from 'antdv-next';
|
||||
import {
|
||||
Button,
|
||||
Dropdown,
|
||||
Menu,
|
||||
MenuItem,
|
||||
Popconfirm,
|
||||
Space,
|
||||
Tooltip,
|
||||
} from 'antdv-next';
|
||||
|
||||
const props = defineProps({
|
||||
actions: {
|
||||
|
|
@ -188,7 +196,7 @@ watch(
|
|||
</slot>
|
||||
<template #popupRender>
|
||||
<Menu>
|
||||
<Menu.Item
|
||||
<MenuItem
|
||||
v-for="(action, index) in getDropdownList"
|
||||
:key="index"
|
||||
:disabled="action.disabled"
|
||||
|
|
@ -225,7 +233,7 @@ watch(
|
|||
{{ action.label }}
|
||||
</div>
|
||||
</template>
|
||||
</Menu.Item>
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
</template>
|
||||
</Dropdown>
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ function handleClick(doc: any) {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Tooltip placement="topLeft" trigger="click">
|
||||
<Tooltip placement="topLeft" :trigger="['click']">
|
||||
<div ref="documentRef"></div>
|
||||
<template #title>
|
||||
<div class="mb-3 text-base font-bold">{{ document?.title }}</div>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { ref } from 'vue';
|
|||
|
||||
import { IconifyIcon } from '@vben/icons';
|
||||
|
||||
import { Avatar, Button, Card, Dropdown, Menu } from 'antdv-next';
|
||||
import { Avatar, Button, Card, Dropdown, Menu, MenuItem } from 'antdv-next';
|
||||
|
||||
const props = defineProps({
|
||||
loading: {
|
||||
|
|
@ -103,18 +103,18 @@ async function handleTabsScroll() {
|
|||
</Button>
|
||||
<template #popupRender>
|
||||
<Menu>
|
||||
<Menu.Item @click="handleMoreClick(['delete', role])">
|
||||
<MenuItem @click="handleMoreClick(['delete', role])">
|
||||
<div class="flex items-center">
|
||||
<IconifyIcon icon="lucide:trash" color="red" />
|
||||
<span class="ml-2 text-red-500">删除</span>
|
||||
</div>
|
||||
</Menu.Item>
|
||||
<Menu.Item @click="handleMoreClick(['edit', role])">
|
||||
</MenuItem>
|
||||
<MenuItem @click="handleMoreClick(['edit', role])">
|
||||
<div class="flex items-center">
|
||||
<IconifyIcon icon="lucide:edit" color="#787878" />
|
||||
<span class="ml-2 text-primary">编辑</span>
|
||||
</div>
|
||||
</Menu.Item>
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
</template>
|
||||
</Dropdown>
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import {
|
|||
Form,
|
||||
InputNumber,
|
||||
Menu,
|
||||
MenuItem,
|
||||
message,
|
||||
Tooltip,
|
||||
} from 'antdv-next';
|
||||
|
|
@ -207,7 +208,7 @@ onMounted(async () => {
|
|||
<div class="mb-2.5">
|
||||
<Dropdown
|
||||
v-if="modelData.list && modelData.list.length > 0"
|
||||
trigger="click"
|
||||
:trigger="['click']"
|
||||
>
|
||||
<div class="flex cursor-pointer items-center">
|
||||
<IconifyIcon icon="lucide:file-text" class="mr-1" />
|
||||
|
|
@ -222,7 +223,7 @@ onMounted(async () => {
|
|||
</div>
|
||||
<template #popupRender>
|
||||
<Menu>
|
||||
<Menu.Item
|
||||
<MenuItem
|
||||
v-for="(file, index) in modelData.list"
|
||||
:key="index"
|
||||
@click="selectFile(index)"
|
||||
|
|
@ -231,7 +232,7 @@ onMounted(async () => {
|
|||
<span v-if="file.segments" class="ml-1 text-sm text-gray-500">
|
||||
({{ file.segments.length }} 个分片)
|
||||
</span>
|
||||
</Menu.Item>
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
</template>
|
||||
</Dropdown>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { ref } from 'vue';
|
|||
import { DICT_TYPE } from '@vben/constants';
|
||||
import { getDictOptions } from '@vben/hooks';
|
||||
|
||||
import { Form, Input, Select } from 'antdv-next';
|
||||
import { Form, Input, Select, TextArea } from 'antdv-next';
|
||||
|
||||
type Rule = any;
|
||||
const modelData = defineModel<any>(); // 创建本地数据副本
|
||||
|
|
@ -64,7 +64,7 @@ defineExpose({ validate });
|
|||
</Select>
|
||||
</FormItem>
|
||||
<FormItem label="流程描述" name="description" class="mb-5">
|
||||
<Input.TextArea v-model:value="modelData.description" allow-clear />
|
||||
<TextArea v-model:value="modelData.description" allow-clear />
|
||||
</FormItem>
|
||||
</Form>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,21 @@
|
|||
<script lang="ts" setup>
|
||||
import { h, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue';
|
||||
import type { TableColumnType } from 'antdv-next';
|
||||
|
||||
import {
|
||||
computed,
|
||||
h,
|
||||
nextTick,
|
||||
onBeforeUnmount,
|
||||
onMounted,
|
||||
ref,
|
||||
watch,
|
||||
} from 'vue';
|
||||
|
||||
import { BpmProcessInstanceStatus, DICT_TYPE } from '@vben/constants';
|
||||
import { UndoOutlined, ZoomInOutlined, ZoomOutOutlined } from '@vben/icons';
|
||||
import { formatDate, formatPast2 } from '@vben/utils';
|
||||
|
||||
import { Button, Modal, Row, Table } from 'antdv-next';
|
||||
import { Button, Modal, Row, SpaceCompact, Table } from 'antdv-next';
|
||||
import BpmnViewer from 'bpmn-js/lib/Viewer';
|
||||
import MoveCanvasModule from 'diagram-js/lib/navigation/movecanvas';
|
||||
|
||||
|
|
@ -13,8 +23,6 @@ import { DictTag } from '#/components/dict-tag';
|
|||
|
||||
import '../theme/index.scss';
|
||||
|
||||
const ButtonGroup = Button.Group;
|
||||
|
||||
const props = defineProps({
|
||||
xml: {
|
||||
type: String,
|
||||
|
|
@ -40,6 +48,80 @@ const dialogVisible = ref(false); // 弹窗可见性
|
|||
const dialogTitle = ref<string | undefined>(undefined); // 弹窗标题
|
||||
const selectActivityType = ref<string | undefined>(undefined); // 选中 Task 的活动编号
|
||||
const selectTasks = ref<any[]>([]); // 选中的任务数组
|
||||
const approvalColumns = computed<TableColumnType[]>(() => {
|
||||
const userColumn: TableColumnType =
|
||||
selectActivityType.value === 'bpmn:UserTask'
|
||||
? {
|
||||
align: 'center',
|
||||
key: 'approver',
|
||||
title: '审批人',
|
||||
width: 100,
|
||||
}
|
||||
: {
|
||||
align: 'center',
|
||||
dataIndex: ['assigneeUser', 'nickname'],
|
||||
key: 'starter',
|
||||
title: '发起人',
|
||||
width: 100,
|
||||
};
|
||||
|
||||
return [
|
||||
{
|
||||
align: 'center',
|
||||
key: 'index',
|
||||
title: '序号',
|
||||
width: 50,
|
||||
},
|
||||
userColumn,
|
||||
{
|
||||
align: 'center',
|
||||
key: 'dept',
|
||||
title: '部门',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
align: 'center',
|
||||
dataIndex: 'createTime',
|
||||
key: 'createTime',
|
||||
render: (val) => formatDate(val),
|
||||
title: '开始时间',
|
||||
width: 140,
|
||||
},
|
||||
{
|
||||
align: 'center',
|
||||
dataIndex: 'endTime',
|
||||
key: 'endTime',
|
||||
render: (val) => formatDate(val),
|
||||
title: '结束时间',
|
||||
width: 140,
|
||||
},
|
||||
{
|
||||
align: 'center',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
title: '审批状态',
|
||||
width: 90,
|
||||
},
|
||||
...(selectActivityType.value === 'bpmn:UserTask'
|
||||
? [
|
||||
{
|
||||
align: 'center',
|
||||
dataIndex: 'reason',
|
||||
key: 'reason',
|
||||
title: '审批建议',
|
||||
width: 120,
|
||||
} as TableColumnType,
|
||||
]
|
||||
: []),
|
||||
{
|
||||
align: 'center',
|
||||
dataIndex: 'durationInMillis',
|
||||
key: 'durationInMillis',
|
||||
title: '耗时',
|
||||
width: 100,
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
/** Zoom:恢复 */
|
||||
const processReZoom = () => {
|
||||
|
|
@ -360,78 +442,32 @@ onBeforeUnmount(() => {
|
|||
:width="1000"
|
||||
>
|
||||
<Row>
|
||||
<Table :data-source="selectTasks" size="small" :bordered="true">
|
||||
<Table.Column title="序号" align="center" width="50">
|
||||
<template #default="{ index }">
|
||||
<Table
|
||||
:columns="approvalColumns"
|
||||
:data-source="selectTasks"
|
||||
size="small"
|
||||
:bordered="true"
|
||||
>
|
||||
<template #bodyCell="{ column, index, record }">
|
||||
<template v-if="column.key === 'index'">
|
||||
{{ index + 1 }}
|
||||
</template>
|
||||
</Table.Column>
|
||||
<Table.Column
|
||||
title="审批人"
|
||||
width="100"
|
||||
align="center"
|
||||
v-if="selectActivityType === 'bpmn:UserTask'"
|
||||
>
|
||||
<template #default="{ record }">
|
||||
<template v-else-if="column.key === 'approver'">
|
||||
{{ record.assigneeUser?.nickname || record.ownerUser?.nickname }}
|
||||
</template>
|
||||
</Table.Column>
|
||||
<Table.Column
|
||||
title="发起人"
|
||||
data-index="assigneeUser.nickname"
|
||||
width="100"
|
||||
align="center"
|
||||
v-else
|
||||
/>
|
||||
<Table.Column title="部门" width="100" align="center">
|
||||
<template #default="{ record }">
|
||||
<template v-else-if="column.key === 'dept'">
|
||||
{{ record.assigneeUser?.deptName || record.ownerUser?.deptName }}
|
||||
</template>
|
||||
</Table.Column>
|
||||
<Table.Column
|
||||
:custom-render="({ text }) => formatDate(text)"
|
||||
align="center"
|
||||
title="开始时间"
|
||||
data-index="createTime"
|
||||
width="140"
|
||||
/>
|
||||
<Table.Column
|
||||
:custom-render="({ text }) => formatDate(text)"
|
||||
align="center"
|
||||
title="结束时间"
|
||||
data-index="endTime"
|
||||
width="140"
|
||||
/>
|
||||
<Table.Column
|
||||
align="center"
|
||||
title="审批状态"
|
||||
data-index="status"
|
||||
width="90"
|
||||
>
|
||||
<template #default="{ record }">
|
||||
<template v-else-if="column.key === 'status'">
|
||||
<DictTag
|
||||
:type="DICT_TYPE.BPM_TASK_STATUS"
|
||||
:value="record.status"
|
||||
/>
|
||||
</template>
|
||||
</Table.Column>
|
||||
<Table.Column
|
||||
align="center"
|
||||
title="审批建议"
|
||||
data-index="reason"
|
||||
width="120"
|
||||
v-if="selectActivityType === 'bpmn:UserTask'"
|
||||
/>
|
||||
<Table.Column
|
||||
align="center"
|
||||
title="耗时"
|
||||
data-index="durationInMillis"
|
||||
width="100"
|
||||
>
|
||||
<template #default="{ record }">
|
||||
<template v-else-if="column.key === 'durationInMillis'">
|
||||
{{ formatPast2(record.durationInMillis) }}
|
||||
</template>
|
||||
</Table.Column>
|
||||
</template>
|
||||
</Table>
|
||||
</Row>
|
||||
</Modal>
|
||||
|
|
@ -439,7 +475,7 @@ onBeforeUnmount(() => {
|
|||
<!-- Zoom:放大、缩小 -->
|
||||
<div style="position: absolute; top: 0; left: 0; width: 100%">
|
||||
<Row justify="end">
|
||||
<ButtonGroup key="scale-control">
|
||||
<SpaceCompact key="scale-control">
|
||||
<Button
|
||||
:disabled="defaultZoom <= 0.3"
|
||||
:icon="h(ZoomOutOutlined)"
|
||||
|
|
@ -454,7 +490,7 @@ onBeforeUnmount(() => {
|
|||
@click="processZoomIn()"
|
||||
/>
|
||||
<Button :icon="h(UndoOutlined)" @click="processReZoom()" />
|
||||
</ButtonGroup>
|
||||
</SpaceCompact>
|
||||
</Row>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { ref, watch } from 'vue';
|
|||
import {
|
||||
Button,
|
||||
Checkbox,
|
||||
CheckboxGroup,
|
||||
DatePicker,
|
||||
Input,
|
||||
InputNumber,
|
||||
|
|
@ -309,7 +310,7 @@ watch(
|
|||
</RadioGroup>
|
||||
</div>
|
||||
<div v-if="cronMode[f.key] === 'appoint'">
|
||||
<Checkbox.Group
|
||||
<CheckboxGroup
|
||||
v-model:value="cronAppoint[f.key]"
|
||||
:key="`group-${f.key}`"
|
||||
>
|
||||
|
|
@ -320,7 +321,7 @@ watch(
|
|||
>
|
||||
{{ pad(n - 1) }}
|
||||
</Checkbox>
|
||||
</Checkbox.Group>
|
||||
</CheckboxGroup>
|
||||
</div>
|
||||
</TabPane>
|
||||
</Tabs>
|
||||
|
|
|
|||
|
|
@ -248,7 +248,7 @@ function addNode(type: number) {
|
|||
<template>
|
||||
<div class="node-handler-wrapper">
|
||||
<div class="node-handler">
|
||||
<Popover trigger="hover" placement="right" width="auto" v-if="!readonly">
|
||||
<Popover :trigger="['hover']" placement="right" width="auto" v-if="!readonly">
|
||||
<template #content>
|
||||
<div class="handler-item-wrapper">
|
||||
<div
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import {
|
|||
Input,
|
||||
Radio,
|
||||
Select,
|
||||
TextArea,
|
||||
Tooltip,
|
||||
} from 'antdv-next';
|
||||
|
||||
|
|
@ -295,7 +296,7 @@ defineExpose({ validate });
|
|||
<ImageUpload v-model:value="modelData.icon" />
|
||||
</FormItem>
|
||||
<FormItem label="流程描述" name="description">
|
||||
<Input.TextArea v-model:value="modelData.description" allow-clear />
|
||||
<TextArea v-model:value="modelData.description" allow-clear />
|
||||
</FormItem>
|
||||
<FormItem label="流程类型" name="type">
|
||||
<RadioGroup v-model:value="modelData.type">
|
||||
|
|
|
|||
|
|
@ -8,7 +8,15 @@ import { IconifyIcon } from '@vben/icons';
|
|||
import { cloneDeep } from '@vben/utils';
|
||||
|
||||
import { useSortable } from '@vueuse/integrations/useSortable';
|
||||
import { Button, Card, Dropdown, Input, Menu, message } from 'antdv-next';
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
Dropdown,
|
||||
Input,
|
||||
Menu,
|
||||
MenuItem,
|
||||
message,
|
||||
} from 'antdv-next';
|
||||
|
||||
import {
|
||||
getCategorySimpleList,
|
||||
|
|
@ -171,18 +179,18 @@ async function handleCategorySortSubmit() {
|
|||
</Button>
|
||||
<template #popupRender>
|
||||
<Menu @click="(e) => handleCommand(e.key as string)">
|
||||
<Menu.Item key="handleCategoryAdd">
|
||||
<MenuItem key="handleCategoryAdd">
|
||||
<div class="flex items-center gap-1">
|
||||
<IconifyIcon icon="lucide:plus" />
|
||||
新建分类
|
||||
</div>
|
||||
</Menu.Item>
|
||||
<Menu.Item key="handleCategorySort">
|
||||
</MenuItem>
|
||||
<MenuItem key="handleCategorySort">
|
||||
<div class="flex items-center gap-1">
|
||||
<IconifyIcon icon="lucide:align-start-vertical" />
|
||||
分类排序
|
||||
</div>
|
||||
</Menu.Item>
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
</template>
|
||||
</Dropdown>
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import {
|
|||
Collapse,
|
||||
Dropdown,
|
||||
Menu,
|
||||
MenuItem,
|
||||
message,
|
||||
Tag,
|
||||
Tooltip,
|
||||
|
|
@ -524,8 +525,8 @@ function handleRenameSuccess() {
|
|||
</Button>
|
||||
<template #popupRender>
|
||||
<Menu @click="(e) => handleCommand(e.key as string)">
|
||||
<Menu.Item key="renameCategory"> 重命名 </Menu.Item>
|
||||
<Menu.Item key="deleteCategory"> 删除分类 </Menu.Item>
|
||||
<MenuItem key="renameCategory"> 重命名 </MenuItem>
|
||||
<MenuItem key="deleteCategory"> 删除分类 </MenuItem>
|
||||
</Menu>
|
||||
</template>
|
||||
</Dropdown>
|
||||
|
|
@ -687,16 +688,16 @@ function handleRenameSuccess() {
|
|||
<Menu
|
||||
@click="(e) => handleModelCommand(e.key as string, row)"
|
||||
>
|
||||
<Menu.Item key="handleCopy"> 复制 </Menu.Item>
|
||||
<Menu.Item key="handleDefinitionList"> 历史 </Menu.Item>
|
||||
<MenuItem key="handleCopy"> 复制 </MenuItem>
|
||||
<MenuItem key="handleDefinitionList"> 历史 </MenuItem>
|
||||
|
||||
<Menu.Item
|
||||
<MenuItem
|
||||
key="handleReport"
|
||||
:disabled="!isManagerUser(row)"
|
||||
>
|
||||
报表
|
||||
</Menu.Item>
|
||||
<Menu.Item
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
key="handleChangeState"
|
||||
v-if="row.processDefinition"
|
||||
:disabled="!isManagerUser(row)"
|
||||
|
|
@ -706,21 +707,21 @@ function handleRenameSuccess() {
|
|||
? '停用'
|
||||
: '启用'
|
||||
}}
|
||||
</Menu.Item>
|
||||
<Menu.Item
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
danger
|
||||
key="handleClean"
|
||||
:disabled="!isManagerUser(row)"
|
||||
>
|
||||
清理
|
||||
</Menu.Item>
|
||||
<Menu.Item
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
danger
|
||||
key="handleDelete"
|
||||
:disabled="!isManagerUser(row) && !hasPermiDelete"
|
||||
>
|
||||
删除
|
||||
</Menu.Item>
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
</template>
|
||||
</Dropdown>
|
||||
|
|
|
|||
|
|
@ -767,7 +767,7 @@ defineExpose({ loadTodoTask });
|
|||
v-model:open="popOverVisible.approve"
|
||||
placement="top"
|
||||
:styles="{ root: { minWidth: '400px', zIndex: 300 } }"
|
||||
trigger="click"
|
||||
:trigger="['click']"
|
||||
@open-change="handlePopoverVisible"
|
||||
v-if="
|
||||
runningTask &&
|
||||
|
|
@ -871,7 +871,7 @@ defineExpose({ loadTodoTask });
|
|||
v-model:open="popOverVisible.reject"
|
||||
placement="top"
|
||||
:styles="{ root: { minWidth: '400px' } }"
|
||||
trigger="click"
|
||||
:trigger="['click']"
|
||||
v-if="
|
||||
runningTask &&
|
||||
isHandleTaskStatus() &&
|
||||
|
|
@ -928,7 +928,7 @@ defineExpose({ loadTodoTask });
|
|||
v-model:open="popOverVisible.copy"
|
||||
placement="top"
|
||||
:styles="{ root: { width: '400px' } }"
|
||||
trigger="click"
|
||||
:trigger="['click']"
|
||||
v-if="
|
||||
runningTask &&
|
||||
isHandleTaskStatus() &&
|
||||
|
|
@ -1000,7 +1000,7 @@ defineExpose({ loadTodoTask });
|
|||
v-model:open="popOverVisible.transfer"
|
||||
placement="top"
|
||||
:styles="{ root: { width: '400px' } }"
|
||||
trigger="click"
|
||||
:trigger="['click']"
|
||||
v-if="
|
||||
runningTask &&
|
||||
isHandleTaskStatus() &&
|
||||
|
|
@ -1073,7 +1073,7 @@ defineExpose({ loadTodoTask });
|
|||
v-model:open="popOverVisible.delegate"
|
||||
placement="top"
|
||||
:styles="{ root: { width: '400px' } }"
|
||||
trigger="click"
|
||||
:trigger="['click']"
|
||||
v-if="
|
||||
runningTask &&
|
||||
isHandleTaskStatus() &&
|
||||
|
|
@ -1146,7 +1146,7 @@ defineExpose({ loadTodoTask });
|
|||
v-model:open="popOverVisible.addSign"
|
||||
placement="top"
|
||||
:styles="{ root: { width: '400px' } }"
|
||||
trigger="click"
|
||||
:trigger="['click']"
|
||||
v-if="
|
||||
runningTask &&
|
||||
isHandleTaskStatus() &&
|
||||
|
|
@ -1231,7 +1231,7 @@ defineExpose({ loadTodoTask });
|
|||
v-model:open="popOverVisible.deleteSign"
|
||||
placement="top"
|
||||
:styles="{ root: { width: '400px' } }"
|
||||
trigger="click"
|
||||
:trigger="['click']"
|
||||
v-if="runningTask?.children.length > 0"
|
||||
>
|
||||
<Button type="dashed" @click="openPopover('deleteSign')">
|
||||
|
|
@ -1297,7 +1297,7 @@ defineExpose({ loadTodoTask });
|
|||
v-model:open="popOverVisible.return"
|
||||
placement="top"
|
||||
:styles="{ root: { width: '400px' } }"
|
||||
trigger="click"
|
||||
:trigger="['click']"
|
||||
v-if="
|
||||
runningTask &&
|
||||
isHandleTaskStatus() &&
|
||||
|
|
@ -1370,7 +1370,7 @@ defineExpose({ loadTodoTask });
|
|||
v-model:open="popOverVisible.cancel"
|
||||
placement="top"
|
||||
:width="500"
|
||||
trigger="click"
|
||||
:trigger="['click']"
|
||||
v-if="
|
||||
userId === processInstance?.startUser?.id &&
|
||||
!isEndProcessStatus(processInstance?.status)
|
||||
|
|
|
|||
|
|
@ -32,6 +32,12 @@ const clueId = ref(0); // 线索编号
|
|||
const clue = ref<CrmClueApi.Clue>({} as CrmClueApi.Clue); // 线索详情
|
||||
const logList = ref<SystemOperateLogApi.OperateLog[]>([]); // 操作日志
|
||||
const permissionListRef = ref<InstanceType<typeof PermissionList>>(); // 团队成员列表 Ref
|
||||
const clueTabItems = [
|
||||
{ key: '1', label: '跟进记录', forceRender: true },
|
||||
{ key: '2', label: '基本信息', forceRender: true },
|
||||
{ key: '3', label: '团队成员', forceRender: true },
|
||||
{ key: '4', label: '操作日志', forceRender: true },
|
||||
];
|
||||
|
||||
const [Descriptions] = useDescription({
|
||||
bordered: false,
|
||||
|
|
@ -143,25 +149,24 @@ onMounted(() => {
|
|||
<Descriptions :data="clue" />
|
||||
</Card>
|
||||
<Card class="mt-4 min-h-[60%]">
|
||||
<Tabs :tab-bar-gutter="16">
|
||||
<Tabs.TabPane tab="跟进记录" key="1" :force-render="true">
|
||||
<FollowUp :biz-id="clueId" :biz-type="BizTypeEnum.CRM_CLUE" />
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane tab="基本信息" key="2" :force-render="true">
|
||||
<Info :clue="clue" />
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane tab="团队成员" key="3" :force-render="true">
|
||||
<Tabs :items="clueTabItems" :tab-bar-gutter="16">
|
||||
<template #contentRender="{ item }">
|
||||
<FollowUp
|
||||
v-if="item.key === '1'"
|
||||
:biz-id="clueId"
|
||||
:biz-type="BizTypeEnum.CRM_CLUE"
|
||||
/>
|
||||
<Info v-else-if="item.key === '2'" :clue="clue" />
|
||||
<PermissionList
|
||||
v-else-if="item.key === '3'"
|
||||
ref="permissionListRef"
|
||||
:biz-id="clueId"
|
||||
:biz-type="BizTypeEnum.CRM_CLUE"
|
||||
:show-action="true"
|
||||
@quit-team="handleBack"
|
||||
/>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane tab="操作日志" key="4" :force-render="true">
|
||||
<OperateLog :log-list="logList" />
|
||||
</Tabs.TabPane>
|
||||
<OperateLog v-else-if="item.key === '4'" :log-list="logList" />
|
||||
</template>
|
||||
</Tabs>
|
||||
</Card>
|
||||
</Page>
|
||||
|
|
|
|||
|
|
@ -45,6 +45,16 @@ const customerId = ref(0); // 客户编号
|
|||
const customer = ref<CrmCustomerApi.Customer>({} as CrmCustomerApi.Customer); // 客户详情
|
||||
const logList = ref<SystemOperateLogApi.OperateLog[]>([]); // 操作日志
|
||||
const permissionListRef = ref<InstanceType<typeof PermissionList>>(); // 团队成员列表 Ref
|
||||
const customerTabItems = [
|
||||
{ key: '1', label: '跟进记录', forceRender: true },
|
||||
{ key: '2', label: '基本信息', forceRender: true },
|
||||
{ key: '3', label: '联系人', forceRender: true },
|
||||
{ key: '4', label: '团队成员', forceRender: true },
|
||||
{ key: '5', label: '商机', forceRender: true },
|
||||
{ key: '6', label: '合同', forceRender: true },
|
||||
{ key: '7', label: '回款', forceRender: true },
|
||||
{ key: '8', label: '操作日志', forceRender: true },
|
||||
];
|
||||
|
||||
const [Descriptions] = useDescription({
|
||||
bordered: false,
|
||||
|
|
@ -241,49 +251,45 @@ onMounted(() => {
|
|||
<Descriptions :data="customer" />
|
||||
</Card>
|
||||
<Card class="mt-4 min-h-[60%]">
|
||||
<Tabs>
|
||||
<Tabs.TabPane tab="跟进记录" key="1" :force-render="true">
|
||||
<FollowUp :biz-id="customerId" :biz-type="BizTypeEnum.CRM_CUSTOMER" />
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane tab="基本信息" key="2" :force-render="true">
|
||||
<Info :customer="customer" />
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane tab="联系人" key="3" :force-render="true">
|
||||
<Tabs :items="customerTabItems">
|
||||
<template #contentRender="{ item }">
|
||||
<FollowUp
|
||||
v-if="item.key === '1'"
|
||||
:biz-id="customerId"
|
||||
:biz-type="BizTypeEnum.CRM_CUSTOMER"
|
||||
/>
|
||||
<Info v-else-if="item.key === '2'" :customer="customer" />
|
||||
<ContactDetailsList
|
||||
v-else-if="item.key === '3'"
|
||||
:biz-id="customerId"
|
||||
:biz-type="BizTypeEnum.CRM_CUSTOMER"
|
||||
:customer-id="customerId"
|
||||
/>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane tab="团队成员" key="4" :force-render="true">
|
||||
<PermissionList
|
||||
v-else-if="item.key === '4'"
|
||||
ref="permissionListRef"
|
||||
:biz-id="customerId"
|
||||
:biz-type="BizTypeEnum.CRM_CUSTOMER"
|
||||
:show-action="true"
|
||||
@quit-team="handleBack"
|
||||
/>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane tab="商机" key="5" :force-render="true">
|
||||
<BusinessDetailsList
|
||||
v-else-if="item.key === '5'"
|
||||
:biz-id="customerId"
|
||||
:biz-type="BizTypeEnum.CRM_CUSTOMER"
|
||||
:customer-id="customerId"
|
||||
/>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane tab="合同" key="6" :force-render="true">
|
||||
<ContractDetailsList
|
||||
v-else-if="item.key === '6'"
|
||||
:biz-id="customerId"
|
||||
:biz-type="BizTypeEnum.CRM_CUSTOMER"
|
||||
/>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane tab="回款" key="7" :force-render="true">
|
||||
<ReceivablePlanDetailsList :customer-id="customerId" />
|
||||
<ReceivableDetailsList :customer-id="customerId" />
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane tab="操作日志" key="8" :force-render="true">
|
||||
<OperateLog :log-list="logList" />
|
||||
</Tabs.TabPane>
|
||||
<template v-else-if="item.key === '7'">
|
||||
<ReceivablePlanDetailsList :customer-id="customerId" />
|
||||
<ReceivableDetailsList :customer-id="customerId" />
|
||||
</template>
|
||||
<OperateLog v-else-if="item.key === '8'" :log-list="logList" />
|
||||
</template>
|
||||
</Tabs>
|
||||
</Card>
|
||||
</Page>
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import { DICT_TYPE } from '@vben/constants';
|
|||
import { getDictOptions } from '@vben/hooks';
|
||||
import { formatDateTime } from '@vben/utils';
|
||||
|
||||
import { Timeline } from 'antdv-next';
|
||||
import { Timeline, TimelineItem } from 'antdv-next';
|
||||
|
||||
import { CronTab } from '#/components/cron-tab';
|
||||
import { DictTag } from '#/components/dict-tag';
|
||||
|
|
@ -239,7 +239,7 @@ export function useDetailSchema(): DescriptionItemSchema[] {
|
|||
}
|
||||
return h(Timeline, {}, () =>
|
||||
val?.map((time: Date) =>
|
||||
h(Timeline.Item, {}, () => formatDateTime(time)),
|
||||
h(TimelineItem, {}, () => formatDateTime(time)),
|
||||
),
|
||||
);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import {
|
|||
message,
|
||||
Select,
|
||||
Tag,
|
||||
TextArea,
|
||||
} from 'antdv-next';
|
||||
|
||||
import { getSimpleUserList } from '#/api/system/user';
|
||||
|
|
@ -249,7 +250,7 @@ onMounted(async () => {
|
|||
</SelectOption>
|
||||
</Select>
|
||||
|
||||
<Input.TextArea
|
||||
<TextArea
|
||||
v-model:value="sendText"
|
||||
:auto-size="{ minRows: 3, maxRows: 6 }"
|
||||
:disabled="!getIsOpen"
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { h, ref } from 'vue';
|
|||
import { Page } from '@vben/common-ui';
|
||||
import { IconifyIcon } from '@vben/icons';
|
||||
|
||||
import { Button, Input, message, Modal, Popover } from 'antdv-next';
|
||||
import { Button, message, Modal, Popover, TextArea } from 'antdv-next';
|
||||
|
||||
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import { getAlertRecordPage, processAlertRecord } from '#/api/iot/alert/record';
|
||||
|
|
@ -37,7 +37,7 @@ function handleProcess(row: AlertRecordApi.AlertRecord) {
|
|||
content: () =>
|
||||
h('div', { class: 'space-y-2' }, [
|
||||
h('p', '请输入处理原因:'),
|
||||
h(Input.TextArea, {
|
||||
h(TextArea, {
|
||||
value: processRemark.value,
|
||||
'onUpdate:value': (val: string) => (processRemark.value = val),
|
||||
rows: 3,
|
||||
|
|
@ -98,7 +98,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||
<Popover
|
||||
v-if="row.deviceMessage"
|
||||
placement="topLeft"
|
||||
trigger="hover"
|
||||
:trigger="['hover']"
|
||||
:overlay-style="{ maxWidth: '600px' }"
|
||||
>
|
||||
<template #content>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import type { IotDeviceApi } from '#/api/iot/device/device';
|
|||
import type { IotProductApi } from '#/api/iot/product/product';
|
||||
import type { ThingModelApi } from '#/api/iot/thingmodel';
|
||||
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
|
||||
import { Page } from '@vben/common-ui';
|
||||
|
|
@ -33,6 +33,22 @@ const product = ref<IotProductApi.Product>({} as IotProductApi.Product);
|
|||
const device = ref<IotDeviceApi.Device>({} as IotDeviceApi.Device);
|
||||
const activeTab = ref('info');
|
||||
const thingModelList = ref<ThingModelApi.ThingModel[]>([]);
|
||||
const deviceTabItems = computed(() => [
|
||||
{ key: 'info', label: '设备信息' },
|
||||
{ key: 'model', label: '物模型数据' },
|
||||
...(product.value.deviceType === DeviceTypeEnum.GATEWAY
|
||||
? [{ key: 'subDevice', label: '子设备管理' }]
|
||||
: []),
|
||||
{ key: 'log', label: '设备消息' },
|
||||
{ key: 'simulator', label: '模拟设备' },
|
||||
{ key: 'config', label: '设备配置' },
|
||||
...([
|
||||
ProtocolTypeEnum.MODBUS_TCP_CLIENT,
|
||||
ProtocolTypeEnum.MODBUS_TCP_SERVER,
|
||||
].includes(product.value.protocolType as ProtocolTypeEnum)
|
||||
? [{ key: 'modbus', label: 'Modbus 配置' }]
|
||||
: []),
|
||||
]);
|
||||
|
||||
/** 获取设备详情 */
|
||||
async function getDeviceData(deviceId: number) {
|
||||
|
|
@ -94,69 +110,52 @@ onMounted(async () => {
|
|||
@refresh="() => getDeviceData(id)"
|
||||
/>
|
||||
|
||||
<Tabs v-model:active-key="activeTab" class="mt-4">
|
||||
<Tabs.TabPane key="info" tab="设备信息">
|
||||
<Tabs v-model:active-key="activeTab" :items="deviceTabItems" class="mt-4">
|
||||
<template #contentRender="{ item }">
|
||||
<DeviceDetailsInfo
|
||||
v-if="activeTab === 'info' && device.id"
|
||||
v-if="item.key === 'info' && activeTab === 'info' && device.id"
|
||||
:device="device"
|
||||
:product="product"
|
||||
/>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane key="model" tab="物模型数据">
|
||||
<DeviceDetailsThingModel
|
||||
v-if="activeTab === 'model' && device.id"
|
||||
v-else-if="item.key === 'model' && activeTab === 'model' && device.id"
|
||||
:device-id="device.id"
|
||||
:thing-model-list="thingModelList"
|
||||
/>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane
|
||||
v-if="product.deviceType === DeviceTypeEnum.GATEWAY"
|
||||
key="subDevice"
|
||||
tab="子设备管理"
|
||||
>
|
||||
<DeviceDetailsSubDevice
|
||||
v-if="activeTab === 'subDevice' && device.id"
|
||||
v-else-if="
|
||||
item.key === 'subDevice' && activeTab === 'subDevice' && device.id
|
||||
"
|
||||
:device-id="device.id"
|
||||
/>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane key="log" tab="设备消息">
|
||||
<DeviceDetailsMessage
|
||||
v-if="activeTab === 'log' && device.id"
|
||||
v-else-if="item.key === 'log' && activeTab === 'log' && device.id"
|
||||
:device-id="device.id"
|
||||
/>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane key="simulator" tab="模拟设备">
|
||||
<DeviceDetailsSimulator
|
||||
v-if="activeTab === 'simulator' && device.id"
|
||||
v-else-if="
|
||||
item.key === 'simulator' && activeTab === 'simulator' && device.id
|
||||
"
|
||||
:device="device"
|
||||
:product="product"
|
||||
:thing-model-list="thingModelList"
|
||||
/>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane key="config" tab="设备配置">
|
||||
<DeviceDetailConfig
|
||||
v-if="activeTab === 'config' && device.id"
|
||||
v-else-if="
|
||||
item.key === 'config' && activeTab === 'config' && device.id
|
||||
"
|
||||
:device="device"
|
||||
@success="() => getDeviceData(id)"
|
||||
/>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane
|
||||
v-if="
|
||||
[
|
||||
ProtocolTypeEnum.MODBUS_TCP_CLIENT,
|
||||
ProtocolTypeEnum.MODBUS_TCP_SERVER,
|
||||
].includes(product.protocolType as ProtocolTypeEnum)
|
||||
"
|
||||
key="modbus"
|
||||
tab="Modbus 配置"
|
||||
>
|
||||
<DeviceModbusConfig
|
||||
v-if="activeTab === 'modbus' && device.id"
|
||||
v-else-if="
|
||||
item.key === 'modbus' && activeTab === 'modbus' && device.id
|
||||
"
|
||||
:device="device"
|
||||
:product="product"
|
||||
:thing-model-list="thingModelList"
|
||||
/>
|
||||
</Tabs.TabPane>
|
||||
</template>
|
||||
</Tabs>
|
||||
</Page>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { useRouter } from 'vue-router';
|
|||
|
||||
import { useVbenModal } from '@vben/common-ui';
|
||||
|
||||
import { Button, Card, Descriptions, message } from 'antdv-next';
|
||||
import { Button, Card, Descriptions, DescriptionsItem, message } from 'antdv-next';
|
||||
|
||||
import DeviceForm from '../../modules/form.vue';
|
||||
|
||||
|
|
@ -75,15 +75,15 @@ function openEditForm(row: IotDeviceApi.Device) {
|
|||
|
||||
<Card class="mt-4">
|
||||
<Descriptions :column="2">
|
||||
<Descriptions.Item label="产品">
|
||||
<DescriptionsItem label="产品">
|
||||
<a
|
||||
class="cursor-pointer text-blue-600"
|
||||
@click="goToProductDetail(product.id)"
|
||||
>
|
||||
{{ product.name }}
|
||||
</a>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="ProductKey">
|
||||
</DescriptionsItem>
|
||||
<DescriptionsItem label="ProductKey">
|
||||
{{ product.productKey }}
|
||||
<Button
|
||||
class="ml-2"
|
||||
|
|
@ -92,7 +92,7 @@ function openEditForm(row: IotDeviceApi.Device) {
|
|||
>
|
||||
复制
|
||||
</Button>
|
||||
</Descriptions.Item>
|
||||
</DescriptionsItem>
|
||||
</Descriptions>
|
||||
</Card>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -12,10 +12,13 @@ import {
|
|||
Button,
|
||||
Card,
|
||||
Descriptions,
|
||||
DescriptionsItem,
|
||||
Form,
|
||||
FormItem,
|
||||
Input,
|
||||
message,
|
||||
Modal,
|
||||
SpaceCompact,
|
||||
} from 'antdv-next';
|
||||
|
||||
import { getDeviceAuthInfo } from '#/api/iot/device/device';
|
||||
|
|
@ -38,7 +41,7 @@ const mapDialogRef = ref<InstanceType<typeof MapDialog>>();
|
|||
|
||||
/** 是否有位置信息(合法经纬度 0 不应视为空) */
|
||||
const hasLocation = computed(() => {
|
||||
return props.device.longitude != null && props.device.latitude != null;
|
||||
return props.device.longitude !== null && props.device.latitude !== null;
|
||||
});
|
||||
|
||||
/** 打开地图弹窗 */
|
||||
|
|
@ -77,40 +80,40 @@ function handleAuthInfoDialogClose() {
|
|||
<div>
|
||||
<Card title="设备信息">
|
||||
<Descriptions :column="3" bordered size="small">
|
||||
<Descriptions.Item label="产品名称">
|
||||
<DescriptionsItem label="产品名称">
|
||||
{{ product.name }}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="ProductKey">
|
||||
</DescriptionsItem>
|
||||
<DescriptionsItem label="ProductKey">
|
||||
{{ product.productKey }}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="设备类型">
|
||||
</DescriptionsItem>
|
||||
<DescriptionsItem label="设备类型">
|
||||
<DictTag
|
||||
:type="DICT_TYPE.IOT_PRODUCT_DEVICE_TYPE"
|
||||
:value="product.deviceType"
|
||||
/>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="DeviceName">
|
||||
</DescriptionsItem>
|
||||
<DescriptionsItem label="DeviceName">
|
||||
{{ device.deviceName }}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="备注名称">
|
||||
</DescriptionsItem>
|
||||
<DescriptionsItem label="备注名称">
|
||||
{{ device.nickname || '--' }}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="当前状态">
|
||||
</DescriptionsItem>
|
||||
<DescriptionsItem label="当前状态">
|
||||
<DictTag :type="DICT_TYPE.IOT_DEVICE_STATE" :value="device.state" />
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="创建时间">
|
||||
</DescriptionsItem>
|
||||
<DescriptionsItem label="创建时间">
|
||||
{{ formatDateTime(device.createTime) }}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="激活时间">
|
||||
</DescriptionsItem>
|
||||
<DescriptionsItem label="激活时间">
|
||||
{{ formatDateTime(device.activeTime) }}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="最后上线时间">
|
||||
</DescriptionsItem>
|
||||
<DescriptionsItem label="最后上线时间">
|
||||
{{ formatDateTime(device.onlineTime) }}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="最后离线时间">
|
||||
</DescriptionsItem>
|
||||
<DescriptionsItem label="最后离线时间">
|
||||
{{ formatDateTime(device.offlineTime) }}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="设备位置">
|
||||
</DescriptionsItem>
|
||||
<DescriptionsItem label="设备位置">
|
||||
<template v-if="hasLocation">
|
||||
<span class="mr-2">
|
||||
{{ device.longitude }}, {{ device.latitude }}
|
||||
|
|
@ -121,12 +124,12 @@ function handleAuthInfoDialogClose() {
|
|||
</Button>
|
||||
</template>
|
||||
<span v-else class="text-gray-400">暂无位置信息</span>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="MQTT 连接参数">
|
||||
</DescriptionsItem>
|
||||
<DescriptionsItem label="MQTT 连接参数">
|
||||
<Button size="small" type="link" @click="handleAuthInfoDialogOpen">
|
||||
查看
|
||||
</Button>
|
||||
</Descriptions.Item>
|
||||
</DescriptionsItem>
|
||||
</Descriptions>
|
||||
</Card>
|
||||
|
||||
|
|
@ -138,8 +141,8 @@ function handleAuthInfoDialogClose() {
|
|||
width="640px"
|
||||
>
|
||||
<Form :label-col="{ span: 6 }">
|
||||
<Form.Item label="clientId">
|
||||
<Input.Group compact>
|
||||
<FormItem label="clientId">
|
||||
<SpaceCompact>
|
||||
<Input
|
||||
v-model:value="authInfo.clientId"
|
||||
readonly
|
||||
|
|
@ -148,10 +151,10 @@ function handleAuthInfoDialogClose() {
|
|||
<Button type="primary" @click="copyToClipboard(authInfo.clientId)">
|
||||
<IconifyIcon icon="lucide:copy" />
|
||||
</Button>
|
||||
</Input.Group>
|
||||
</Form.Item>
|
||||
<Form.Item label="username">
|
||||
<Input.Group compact>
|
||||
</SpaceCompact>
|
||||
</FormItem>
|
||||
<FormItem label="username">
|
||||
<SpaceCompact>
|
||||
<Input
|
||||
v-model:value="authInfo.username"
|
||||
readonly
|
||||
|
|
@ -160,10 +163,10 @@ function handleAuthInfoDialogClose() {
|
|||
<Button type="primary" @click="copyToClipboard(authInfo.username)">
|
||||
<IconifyIcon icon="lucide:copy" />
|
||||
</Button>
|
||||
</Input.Group>
|
||||
</Form.Item>
|
||||
<Form.Item label="password">
|
||||
<Input.Group compact>
|
||||
</SpaceCompact>
|
||||
</FormItem>
|
||||
<FormItem label="password">
|
||||
<SpaceCompact>
|
||||
<Input
|
||||
v-model:value="authInfo.password"
|
||||
:type="authPasswordVisible ? 'text' : 'password'"
|
||||
|
|
@ -181,8 +184,8 @@ function handleAuthInfoDialogClose() {
|
|||
<Button type="primary" @click="copyToClipboard(authInfo.password)">
|
||||
<IconifyIcon icon="lucide:copy" />
|
||||
</Button>
|
||||
</Input.Group>
|
||||
</Form.Item>
|
||||
</SpaceCompact>
|
||||
</FormItem>
|
||||
</Form>
|
||||
<div class="mt-4 text-right">
|
||||
<Button @click="handleAuthInfoDialogClose">关闭</Button>
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import { DICT_TYPE, IotDeviceMessageMethodEnum } from '@vben/constants';
|
|||
import { IconifyIcon } from '@vben/icons';
|
||||
import { formatDateTime } from '@vben/utils';
|
||||
|
||||
import { Button, Select, Space, Switch, Tag } from 'antdv-next';
|
||||
import { Button, Select, SelectOption, Space, Switch, Tag } from 'antdv-next';
|
||||
|
||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import { getDeviceMessagePage } from '#/api/iot/device/device';
|
||||
|
|
@ -196,14 +196,14 @@ defineExpose({
|
|||
placeholder="所有方法"
|
||||
style="width: 160px"
|
||||
>
|
||||
<Select.Option
|
||||
<SelectOption
|
||||
v-for="item in methodOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
>
|
||||
{{ item.label }}
|
||||
</Select.Option>
|
||||
</SelectOption>
|
||||
</Select>
|
||||
<Select
|
||||
v-model:value="queryParams.upstream"
|
||||
|
|
@ -211,8 +211,8 @@ defineExpose({
|
|||
placeholder="上行/下行"
|
||||
style="width: 160px"
|
||||
>
|
||||
<Select.Option label="上行" value="true">上行</Select.Option>
|
||||
<Select.Option label="下行" value="false">下行</Select.Option>
|
||||
<SelectOption label="上行" value="true">上行</SelectOption>
|
||||
<SelectOption label="下行" value="false">下行</SelectOption>
|
||||
</Select>
|
||||
<Space>
|
||||
<Button type="primary" @click="handleQuery">
|
||||
|
|
|
|||
|
|
@ -48,6 +48,34 @@ const upstreamTab = ref(IotDeviceMessageMethodEnum.PROPERTY_POST.method); // 上
|
|||
const downstreamTab = ref(IotDeviceMessageMethodEnum.PROPERTY_SET.method); // 下行子标签
|
||||
const deviceMessageRef = ref(); // 设备消息组件引用
|
||||
const deviceMessageRefreshDelay = 2000; // 延迟 N 秒,保证模拟上行的消息被处理
|
||||
const simulatorTabItems = [
|
||||
{ key: 'upstream', label: '上行指令调试' },
|
||||
{ key: 'downstream', label: '下行指令调试' },
|
||||
];
|
||||
const upstreamTabItems = [
|
||||
{
|
||||
key: IotDeviceMessageMethodEnum.PROPERTY_POST.method,
|
||||
label: '属性上报',
|
||||
},
|
||||
{
|
||||
key: IotDeviceMessageMethodEnum.EVENT_POST.method,
|
||||
label: '事件上报',
|
||||
},
|
||||
{
|
||||
key: IotDeviceMessageMethodEnum.STATE_UPDATE.method,
|
||||
label: '状态变更',
|
||||
},
|
||||
];
|
||||
const downstreamTabItems = [
|
||||
{
|
||||
key: IotDeviceMessageMethodEnum.PROPERTY_SET.method,
|
||||
label: '属性设置',
|
||||
},
|
||||
{
|
||||
key: IotDeviceMessageMethodEnum.SERVICE_INVOKE.method,
|
||||
label: '设备服务调用',
|
||||
},
|
||||
];
|
||||
|
||||
// 折叠状态
|
||||
const debugCollapsed = ref(false); // 指令调试区域折叠状态
|
||||
|
|
@ -465,87 +493,91 @@ watch([activeTab, upstreamTab, downstreamTab], () => {
|
|||
</div>
|
||||
</template>
|
||||
<div v-show="!debugCollapsed">
|
||||
<Tabs v-model:active-key="activeTab" size="small">
|
||||
<!-- 上行指令调试 -->
|
||||
<Tabs.TabPane key="upstream" tab="上行指令调试">
|
||||
<Tabs
|
||||
v-model:active-key="activeTab"
|
||||
:items="simulatorTabItems"
|
||||
size="small"
|
||||
>
|
||||
<template #contentRender="{ item }">
|
||||
<Tabs
|
||||
v-if="activeTab === 'upstream'"
|
||||
v-if="item.key === 'upstream' && activeTab === 'upstream'"
|
||||
v-model:active-key="upstreamTab"
|
||||
:items="upstreamTabItems"
|
||||
size="small"
|
||||
>
|
||||
<!-- 属性上报 -->
|
||||
<Tabs.TabPane
|
||||
:key="IotDeviceMessageMethodEnum.PROPERTY_POST.method"
|
||||
tab="属性上报"
|
||||
>
|
||||
<template #contentRender="{ item: upstreamItem }">
|
||||
<ContentWrap>
|
||||
<Table
|
||||
:columns="propertyColumns"
|
||||
:data-source="propertyList"
|
||||
:pagination="false"
|
||||
:scroll="{ x: 'max-content', y: 300 }"
|
||||
align="center"
|
||||
size="small"
|
||||
<template
|
||||
v-if="
|
||||
upstreamItem.key ===
|
||||
IotDeviceMessageMethodEnum.PROPERTY_POST.method
|
||||
"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'dataType'">
|
||||
{{ record.property?.dataType ?? '-' }}
|
||||
<Table
|
||||
:columns="propertyColumns"
|
||||
:data-source="propertyList"
|
||||
:pagination="false"
|
||||
:scroll="{ x: 'max-content', y: 300 }"
|
||||
align="center"
|
||||
size="small"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'dataType'">
|
||||
{{ record.property?.dataType ?? '-' }}
|
||||
</template>
|
||||
<template
|
||||
v-else-if="column.key === 'dataDefinition'"
|
||||
>
|
||||
<DataDefinition :data="record" />
|
||||
</template>
|
||||
<template v-else-if="column.key === 'value'">
|
||||
<InputNumber
|
||||
v-if="isNumberProperty(record)"
|
||||
:value="getFormValue(record.identifier)"
|
||||
placeholder="输入值"
|
||||
size="small"
|
||||
class="w-full"
|
||||
@update:value="
|
||||
setFormValue(record.identifier, $event)
|
||||
"
|
||||
/>
|
||||
<Select
|
||||
v-else-if="isSelectProperty(record)"
|
||||
:value="getFormValue(record.identifier)"
|
||||
:options="getPropertyOptions(record)"
|
||||
placeholder="请选择值"
|
||||
size="small"
|
||||
class="w-full"
|
||||
@update:value="
|
||||
setFormValue(record.identifier, $event)
|
||||
"
|
||||
/>
|
||||
<Input
|
||||
v-else
|
||||
:value="getFormValue(record.identifier)"
|
||||
placeholder="输入值"
|
||||
size="small"
|
||||
@update:value="
|
||||
setFormValue(record.identifier, $event)
|
||||
"
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'dataDefinition'">
|
||||
<DataDefinition :data="record" />
|
||||
</template>
|
||||
<template v-else-if="column.key === 'value'">
|
||||
<InputNumber
|
||||
v-if="isNumberProperty(record)"
|
||||
:value="getFormValue(record.identifier)"
|
||||
placeholder="输入值"
|
||||
size="small"
|
||||
class="w-full"
|
||||
@update:value="
|
||||
setFormValue(record.identifier, $event)
|
||||
"
|
||||
/>
|
||||
<Select
|
||||
v-else-if="isSelectProperty(record)"
|
||||
:value="getFormValue(record.identifier)"
|
||||
:options="getPropertyOptions(record)"
|
||||
placeholder="请选择值"
|
||||
size="small"
|
||||
class="w-full"
|
||||
@update:value="
|
||||
setFormValue(record.identifier, $event)
|
||||
"
|
||||
/>
|
||||
<Input
|
||||
v-else
|
||||
:value="getFormValue(record.identifier)"
|
||||
placeholder="输入值"
|
||||
size="small"
|
||||
@update:value="
|
||||
setFormValue(record.identifier, $event)
|
||||
"
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
</Table>
|
||||
<div class="mt-4 flex items-center justify-between">
|
||||
<span class="text-sm text-gray-600">
|
||||
设置属性值后,点击「发送属性上报」按钮
|
||||
</span>
|
||||
<Button type="primary" @click="handlePropertyPost">
|
||||
发送属性上报
|
||||
</Button>
|
||||
</div>
|
||||
</ContentWrap>
|
||||
</Tabs.TabPane>
|
||||
|
||||
<!-- 事件上报 -->
|
||||
<Tabs.TabPane
|
||||
:key="IotDeviceMessageMethodEnum.EVENT_POST.method"
|
||||
tab="事件上报"
|
||||
>
|
||||
<ContentWrap>
|
||||
</Table>
|
||||
<div class="mt-4 flex items-center justify-between">
|
||||
<span class="text-sm text-gray-600">
|
||||
设置属性值后,点击「发送属性上报」按钮
|
||||
</span>
|
||||
<Button type="primary" @click="handlePropertyPost">
|
||||
发送属性上报
|
||||
</Button>
|
||||
</div>
|
||||
</template>
|
||||
<Table
|
||||
v-else-if="
|
||||
upstreamItem.key ===
|
||||
IotDeviceMessageMethodEnum.EVENT_POST.method
|
||||
"
|
||||
:columns="eventColumns"
|
||||
:data-source="eventList"
|
||||
:pagination="false"
|
||||
|
|
@ -555,7 +587,7 @@ watch([activeTab, upstreamTab, downstreamTab], () => {
|
|||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'dataType'">
|
||||
{{ record.event?.dataType ?? '-' }}
|
||||
{{ record.dataType ?? '-' }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'dataDefinition'">
|
||||
<DataDefinition :data="record" />
|
||||
|
|
@ -563,11 +595,11 @@ watch([activeTab, upstreamTab, downstreamTab], () => {
|
|||
<template v-else-if="column.key === 'value'">
|
||||
<TextArea
|
||||
:rows="3"
|
||||
:value="getFormValue(record.identifier)"
|
||||
:value="getFormValue(record.identifier!)"
|
||||
placeholder="输入事件参数(JSON格式)"
|
||||
size="small"
|
||||
@update:value="
|
||||
setFormValue(record.identifier, $event)
|
||||
setFormValue(record.identifier!, $event)
|
||||
"
|
||||
/>
|
||||
</template>
|
||||
|
|
@ -582,16 +614,13 @@ watch([activeTab, upstreamTab, downstreamTab], () => {
|
|||
</template>
|
||||
</template>
|
||||
</Table>
|
||||
</ContentWrap>
|
||||
</Tabs.TabPane>
|
||||
|
||||
<!-- 状态变更 -->
|
||||
<Tabs.TabPane
|
||||
:key="IotDeviceMessageMethodEnum.STATE_UPDATE.method"
|
||||
tab="状态变更"
|
||||
>
|
||||
<ContentWrap>
|
||||
<div class="flex gap-4">
|
||||
<div
|
||||
v-else-if="
|
||||
upstreamItem.key ===
|
||||
IotDeviceMessageMethodEnum.STATE_UPDATE.method
|
||||
"
|
||||
class="flex gap-4"
|
||||
>
|
||||
<Button
|
||||
type="primary"
|
||||
@click="handleDeviceState(DeviceStateEnum.ONLINE)"
|
||||
|
|
@ -606,90 +635,89 @@ watch([activeTab, upstreamTab, downstreamTab], () => {
|
|||
</Button>
|
||||
</div>
|
||||
</ContentWrap>
|
||||
</Tabs.TabPane>
|
||||
</template>
|
||||
</Tabs>
|
||||
</Tabs.TabPane>
|
||||
|
||||
<!-- 下行指令调试 -->
|
||||
<Tabs.TabPane key="downstream" tab="下行指令调试">
|
||||
<Tabs
|
||||
v-if="activeTab === 'downstream'"
|
||||
v-else-if="
|
||||
item.key === 'downstream' && activeTab === 'downstream'
|
||||
"
|
||||
v-model:active-key="downstreamTab"
|
||||
:items="downstreamTabItems"
|
||||
size="small"
|
||||
>
|
||||
<!-- 属性调试 -->
|
||||
<Tabs.TabPane
|
||||
:key="IotDeviceMessageMethodEnum.PROPERTY_SET.method"
|
||||
tab="属性设置"
|
||||
>
|
||||
<template #contentRender="{ item: downstreamItem }">
|
||||
<ContentWrap>
|
||||
<Table
|
||||
:columns="propertyColumns"
|
||||
:data-source="propertyList"
|
||||
:pagination="false"
|
||||
:scroll="{ x: 'max-content', y: 300 }"
|
||||
align="center"
|
||||
size="small"
|
||||
<template
|
||||
v-if="
|
||||
downstreamItem.key ===
|
||||
IotDeviceMessageMethodEnum.PROPERTY_SET.method
|
||||
"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'dataType'">
|
||||
{{ record.property?.dataType ?? '-' }}
|
||||
<Table
|
||||
:columns="propertyColumns"
|
||||
:data-source="propertyList"
|
||||
:pagination="false"
|
||||
:scroll="{ x: 'max-content', y: 300 }"
|
||||
align="center"
|
||||
size="small"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'dataType'">
|
||||
{{ record.property?.dataType ?? '-' }}
|
||||
</template>
|
||||
<template
|
||||
v-else-if="column.key === 'dataDefinition'"
|
||||
>
|
||||
<DataDefinition :data="record" />
|
||||
</template>
|
||||
<template v-else-if="column.key === 'value'">
|
||||
<InputNumber
|
||||
v-if="isNumberProperty(record)"
|
||||
:value="getFormValue(record.identifier)"
|
||||
placeholder="输入值"
|
||||
size="small"
|
||||
class="w-full"
|
||||
@update:value="
|
||||
setFormValue(record.identifier, $event)
|
||||
"
|
||||
/>
|
||||
<Select
|
||||
v-else-if="isSelectProperty(record)"
|
||||
:value="getFormValue(record.identifier)"
|
||||
:options="getPropertyOptions(record)"
|
||||
placeholder="请选择值"
|
||||
size="small"
|
||||
class="w-full"
|
||||
@update:value="
|
||||
setFormValue(record.identifier, $event)
|
||||
"
|
||||
/>
|
||||
<Input
|
||||
v-else
|
||||
:value="getFormValue(record.identifier)"
|
||||
placeholder="输入值"
|
||||
size="small"
|
||||
@update:value="
|
||||
setFormValue(record.identifier, $event)
|
||||
"
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'dataDefinition'">
|
||||
<DataDefinition :data="record" />
|
||||
</template>
|
||||
<template v-else-if="column.key === 'value'">
|
||||
<InputNumber
|
||||
v-if="isNumberProperty(record)"
|
||||
:value="getFormValue(record.identifier)"
|
||||
placeholder="输入值"
|
||||
size="small"
|
||||
class="w-full"
|
||||
@update:value="
|
||||
setFormValue(record.identifier, $event)
|
||||
"
|
||||
/>
|
||||
<Select
|
||||
v-else-if="isSelectProperty(record)"
|
||||
:value="getFormValue(record.identifier)"
|
||||
:options="getPropertyOptions(record)"
|
||||
placeholder="请选择值"
|
||||
size="small"
|
||||
class="w-full"
|
||||
@update:value="
|
||||
setFormValue(record.identifier, $event)
|
||||
"
|
||||
/>
|
||||
<Input
|
||||
v-else
|
||||
:value="getFormValue(record.identifier)"
|
||||
placeholder="输入值"
|
||||
size="small"
|
||||
@update:value="
|
||||
setFormValue(record.identifier, $event)
|
||||
"
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
</Table>
|
||||
<div class="mt-4 flex items-center justify-between">
|
||||
<span class="text-sm text-gray-600">
|
||||
设置属性值后,点击「发送属性设置」按钮
|
||||
</span>
|
||||
<Button type="primary" @click="handlePropertySet">
|
||||
发送属性设置
|
||||
</Button>
|
||||
</div>
|
||||
</ContentWrap>
|
||||
</Tabs.TabPane>
|
||||
|
||||
<!-- 服务调用 -->
|
||||
<Tabs.TabPane
|
||||
:key="IotDeviceMessageMethodEnum.SERVICE_INVOKE.method"
|
||||
tab="设备服务调用"
|
||||
>
|
||||
<ContentWrap>
|
||||
</Table>
|
||||
<div class="mt-4 flex items-center justify-between">
|
||||
<span class="text-sm text-gray-600">
|
||||
设置属性值后,点击「发送属性设置」按钮
|
||||
</span>
|
||||
<Button type="primary" @click="handlePropertySet">
|
||||
发送属性设置
|
||||
</Button>
|
||||
</div>
|
||||
</template>
|
||||
<Table
|
||||
v-else-if="
|
||||
downstreamItem.key ===
|
||||
IotDeviceMessageMethodEnum.SERVICE_INVOKE.method
|
||||
"
|
||||
:columns="serviceColumns"
|
||||
:data-source="serviceList"
|
||||
:pagination="false"
|
||||
|
|
@ -704,11 +732,11 @@ watch([activeTab, upstreamTab, downstreamTab], () => {
|
|||
<template v-else-if="column.key === 'value'">
|
||||
<TextArea
|
||||
:rows="3"
|
||||
:value="getFormValue(record.identifier)"
|
||||
:value="getFormValue(record.identifier!)"
|
||||
placeholder="输入服务参数(JSON格式)"
|
||||
size="small"
|
||||
@update:value="
|
||||
setFormValue(record.identifier, $event)
|
||||
setFormValue(record.identifier!, $event)
|
||||
"
|
||||
/>
|
||||
</template>
|
||||
|
|
@ -724,9 +752,9 @@ watch([activeTab, upstreamTab, downstreamTab], () => {
|
|||
</template>
|
||||
</Table>
|
||||
</ContentWrap>
|
||||
</Tabs.TabPane>
|
||||
</template>
|
||||
</Tabs>
|
||||
</Tabs.TabPane>
|
||||
</template>
|
||||
</Tabs>
|
||||
</div>
|
||||
</Card>
|
||||
|
|
|
|||
|
|
@ -14,7 +14,14 @@ import {
|
|||
import { IconifyIcon } from '@vben/icons';
|
||||
import { formatDateTime } from '@vben/utils';
|
||||
|
||||
import { Button, DatePicker, Select, Space, Tag } from 'antdv-next';
|
||||
import {
|
||||
Button,
|
||||
DateRangePicker,
|
||||
Select,
|
||||
SelectOption,
|
||||
Space,
|
||||
Tag,
|
||||
} from 'antdv-next';
|
||||
|
||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import { getDeviceMessagePairPage } from '#/api/iot/device/device';
|
||||
|
|
@ -24,8 +31,6 @@ const props = defineProps<{
|
|||
thingModelList: ThingModelApi.ThingModel[];
|
||||
}>();
|
||||
|
||||
const RangePicker = DatePicker.RangePicker;
|
||||
|
||||
/** 查询参数 */
|
||||
const queryParams = reactive({
|
||||
identifier: '',
|
||||
|
|
@ -213,18 +218,18 @@ defineExpose({
|
|||
placeholder="请选择事件标识符"
|
||||
style="width: 240px"
|
||||
>
|
||||
<Select.Option
|
||||
<SelectOption
|
||||
v-for="event in eventThingModels"
|
||||
:key="event.identifier"
|
||||
:value="event.identifier!"
|
||||
>
|
||||
{{ event.name }}({{ event.identifier }})
|
||||
</Select.Option>
|
||||
</SelectOption>
|
||||
</Select>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<span>时间范围:</span>
|
||||
<RangePicker
|
||||
<DateRangePicker
|
||||
v-model:value="queryParams.times"
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
show-time
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import {
|
|||
message,
|
||||
Modal,
|
||||
Space,
|
||||
SpaceCompact,
|
||||
Spin,
|
||||
Table,
|
||||
Tag,
|
||||
|
|
@ -120,7 +121,7 @@ const tableColumns = computed(() => [
|
|||
key: 'index',
|
||||
width: 80,
|
||||
align: 'center' as const,
|
||||
customRender: ({ index }: { index: number }) => index + 1,
|
||||
render: (_value: unknown, _record: unknown, index: number) => index + 1,
|
||||
},
|
||||
{
|
||||
title: '时间',
|
||||
|
|
@ -373,7 +374,7 @@ defineExpose({ open }); // 提供 open 方法,用于打开弹窗
|
|||
</Button>
|
||||
|
||||
<!-- 视图切换 -->
|
||||
<Button.Group class="ml-auto">
|
||||
<SpaceCompact class="ml-auto">
|
||||
<Button
|
||||
:disabled="!canShowChart"
|
||||
:type="viewMode === 'chart' ? 'primary' : 'default'"
|
||||
|
|
@ -393,7 +394,7 @@ defineExpose({ open }); // 提供 open 方法,用于打开弹窗
|
|||
</template>
|
||||
列表
|
||||
</Button>
|
||||
</Button.Group>
|
||||
</SpaceCompact>
|
||||
</Space>
|
||||
|
||||
<!-- 数据统计信息 -->
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import {
|
|||
Divider,
|
||||
Input,
|
||||
Row,
|
||||
SpaceCompact,
|
||||
Switch,
|
||||
Tag,
|
||||
} from 'antdv-next';
|
||||
|
|
@ -298,7 +299,7 @@ onBeforeUnmount(() => {
|
|||
un-checked-children="定时刷新"
|
||||
/>
|
||||
</div>
|
||||
<Button.Group>
|
||||
<SpaceCompact>
|
||||
<Button
|
||||
:type="viewMode === 'card' ? 'primary' : 'default'"
|
||||
@click="handleViewModeChange('card')"
|
||||
|
|
@ -311,7 +312,7 @@ onBeforeUnmount(() => {
|
|||
>
|
||||
<IconifyIcon icon="ep:list" />
|
||||
</Button>
|
||||
</Button.Group>
|
||||
</SpaceCompact>
|
||||
</div>
|
||||
|
||||
<!-- 分隔线 -->
|
||||
|
|
|
|||
|
|
@ -14,7 +14,14 @@ import {
|
|||
import { IconifyIcon } from '@vben/icons';
|
||||
import { formatDateTime } from '@vben/utils';
|
||||
|
||||
import { Button, DatePicker, Select, Space, Tag } from 'antdv-next';
|
||||
import {
|
||||
Button,
|
||||
DateRangePicker,
|
||||
Select,
|
||||
SelectOption,
|
||||
Space,
|
||||
Tag,
|
||||
} from 'antdv-next';
|
||||
|
||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import { getDeviceMessagePairPage } from '#/api/iot/device/device';
|
||||
|
|
@ -24,8 +31,6 @@ const props = defineProps<{
|
|||
thingModelList: ThingModelApi.ThingModel[];
|
||||
}>();
|
||||
|
||||
const RangePicker = DatePicker.RangePicker;
|
||||
|
||||
/** 查询参数 */
|
||||
const queryParams = reactive({
|
||||
identifier: '',
|
||||
|
|
@ -227,18 +232,18 @@ defineExpose({
|
|||
placeholder="请选择服务标识符"
|
||||
style="width: 240px"
|
||||
>
|
||||
<Select.Option
|
||||
<SelectOption
|
||||
v-for="service in serviceThingModels"
|
||||
:key="service.identifier"
|
||||
:value="service.identifier!"
|
||||
>
|
||||
{{ service.name }}({{ service.identifier }})
|
||||
</Select.Option>
|
||||
</SelectOption>
|
||||
</Select>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<span>时间范围:</span>
|
||||
<RangePicker
|
||||
<DateRangePicker
|
||||
v-model:value="queryParams.times"
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
show-time
|
||||
|
|
|
|||
|
|
@ -18,30 +18,35 @@ const props = defineProps<{
|
|||
}>();
|
||||
|
||||
const activeTab = ref('property'); // 默认选中设备属性
|
||||
const thingModelTabItems = [
|
||||
{ key: 'property', label: '设备属性(运行状态)' },
|
||||
{ key: 'event', label: '设备事件上报' },
|
||||
{ key: 'service', label: '设备服务调用' },
|
||||
];
|
||||
</script>
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<Tabs v-model:active-key="activeTab" class="!h-auto !p-0">
|
||||
<Tabs.TabPane key="property" tab="设备属性(运行状态)">
|
||||
<Tabs
|
||||
v-model:active-key="activeTab"
|
||||
:items="thingModelTabItems"
|
||||
class="!h-auto !p-0"
|
||||
>
|
||||
<template #contentRender="{ item }">
|
||||
<DeviceDetailsThingModelProperty
|
||||
v-if="activeTab === 'property'"
|
||||
v-if="item.key === 'property' && activeTab === 'property'"
|
||||
:device-id="deviceId"
|
||||
/>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane key="event" tab="设备事件上报">
|
||||
<DeviceDetailsThingModelEvent
|
||||
v-if="activeTab === 'event'"
|
||||
v-else-if="item.key === 'event' && activeTab === 'event'"
|
||||
:device-id="props.deviceId"
|
||||
:thing-model-list="props.thingModelList"
|
||||
/>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane key="service" tab="设备服务调用">
|
||||
<DeviceDetailsThingModelService
|
||||
v-if="activeTab === 'service'"
|
||||
v-else-if="item.key === 'service' && activeTab === 'service'"
|
||||
:device-id="deviceId"
|
||||
:thing-model-list="props.thingModelList"
|
||||
/>
|
||||
</Tabs.TabPane>
|
||||
</template>
|
||||
</Tabs>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import {
|
|||
Input,
|
||||
message,
|
||||
Select,
|
||||
SelectOption,
|
||||
Space,
|
||||
Tag,
|
||||
} from 'antdv-next';
|
||||
|
|
@ -292,13 +293,13 @@ onMounted(async () => {
|
|||
allow-clear
|
||||
style="width: 200px"
|
||||
>
|
||||
<Select.Option
|
||||
<SelectOption
|
||||
v-for="product in products"
|
||||
:key="product.id"
|
||||
:value="product.id"
|
||||
>
|
||||
{{ product.name }}
|
||||
</Select.Option>
|
||||
</SelectOption>
|
||||
</Select>
|
||||
<Input
|
||||
v-model:value="queryParams.deviceName"
|
||||
|
|
@ -320,7 +321,7 @@ onMounted(async () => {
|
|||
allow-clear
|
||||
style="width: 200px"
|
||||
>
|
||||
<Select.Option
|
||||
<SelectOption
|
||||
v-for="dict in getDictOptions(
|
||||
DICT_TYPE.IOT_PRODUCT_DEVICE_TYPE,
|
||||
'number',
|
||||
|
|
@ -329,7 +330,7 @@ onMounted(async () => {
|
|||
:value="dict.value"
|
||||
>
|
||||
{{ dict.label }}
|
||||
</Select.Option>
|
||||
</SelectOption>
|
||||
</Select>
|
||||
<Select
|
||||
v-model:value="queryParams.status"
|
||||
|
|
@ -337,13 +338,13 @@ onMounted(async () => {
|
|||
allow-clear
|
||||
style="width: 200px"
|
||||
>
|
||||
<Select.Option
|
||||
<SelectOption
|
||||
v-for="dict in getDictOptions(DICT_TYPE.IOT_DEVICE_STATE, 'number')"
|
||||
:key="dict.value"
|
||||
:value="dict.value"
|
||||
>
|
||||
{{ dict.label }}
|
||||
</Select.Option>
|
||||
</SelectOption>
|
||||
</Select>
|
||||
<Select
|
||||
v-model:value="queryParams.groupId"
|
||||
|
|
@ -351,13 +352,13 @@ onMounted(async () => {
|
|||
allow-clear
|
||||
style="width: 200px"
|
||||
>
|
||||
<Select.Option
|
||||
<SelectOption
|
||||
v-for="group in deviceGroups"
|
||||
:key="group.id"
|
||||
:value="group.id"
|
||||
>
|
||||
{{ group.name }}
|
||||
</Select.Option>
|
||||
</SelectOption>
|
||||
</Select>
|
||||
<Button type="primary" @click="handleSearch">
|
||||
<IconifyIcon icon="ant-design:search-outlined" class="mr-1" />
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { computed, nextTick, onMounted, ref, watch } from 'vue';
|
|||
|
||||
import { useVbenModal } from '@vben/common-ui';
|
||||
|
||||
import { Button, Collapse, message, Space } from 'antdv-next';
|
||||
import { Button, Collapse, CollapsePanel, message, Space } from 'antdv-next';
|
||||
|
||||
import { useVbenForm } from '#/adapter/form';
|
||||
import { createDevice, getDevice, updateDevice } from '#/api/iot/device/device';
|
||||
|
|
@ -208,12 +208,12 @@ onMounted(async () => {
|
|||
<div class="mx-4">
|
||||
<Form />
|
||||
<Collapse v-model:active-key="activeKey" class="mt-4">
|
||||
<Collapse.Panel key="advanced" header="更多设置">
|
||||
<CollapsePanel key="advanced" header="更多设置">
|
||||
<AdvancedForm />
|
||||
<Space class="mt-2">
|
||||
<Button type="primary" @click="openMapDialog">坐标拾取</Button>
|
||||
</Space>
|
||||
</Collapse.Panel>
|
||||
</CollapsePanel>
|
||||
</Collapse>
|
||||
</div>
|
||||
</Modal>
|
||||
|
|
|
|||
|
|
@ -98,9 +98,12 @@ watch(
|
|||
|
||||
<template>
|
||||
<Card title="升级设备记录">
|
||||
<Tabs v-model:active-key="activeTab" @change="handleTabChange" class="mb-4">
|
||||
<Tabs.TabPane v-for="tab in statusTabs" :key="tab.key" :tab="tab.label" />
|
||||
</Tabs>
|
||||
<Tabs
|
||||
v-model:active-key="activeTab"
|
||||
:items="statusTabs"
|
||||
@change="handleTabChange"
|
||||
class="mb-4"
|
||||
/>
|
||||
<Grid>
|
||||
<template #actions="{ row }">
|
||||
<TableAction
|
||||
|
|
|
|||
|
|
@ -23,6 +23,10 @@ const id = Number(route.params.id);
|
|||
const loading = ref(true);
|
||||
const product = ref<IotProductApi.Product>({} as IotProductApi.Product);
|
||||
const activeTab = ref('info');
|
||||
const productTabItems = [
|
||||
{ key: 'info', label: '产品信息' },
|
||||
{ key: 'thingModel', label: '物模型(功能定义)' },
|
||||
];
|
||||
|
||||
/** 向子组件提供产品信息 */
|
||||
provide(IOT_PROVIDE_KEY.PRODUCT, product);
|
||||
|
|
@ -77,13 +81,16 @@ onMounted(async () => {
|
|||
:product="product"
|
||||
@refresh="() => getProductData(id)"
|
||||
/>
|
||||
<Tabs v-model:active-key="activeTab" class="mt-4">
|
||||
<Tabs.TabPane key="info" tab="产品信息">
|
||||
<ProductDetailsInfo v-if="activeTab === 'info'" :product="product" />
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane key="thingModel" tab="物模型(功能定义)">
|
||||
<IoTProductThingModel v-if="activeTab === 'thingModel'" />
|
||||
</Tabs.TabPane>
|
||||
<Tabs v-model:active-key="activeTab" :items="productTabItems" class="mt-4">
|
||||
<template #contentRender="{ item }">
|
||||
<ProductDetailsInfo
|
||||
v-if="item.key === 'info' && activeTab === 'info'"
|
||||
:product="product"
|
||||
/>
|
||||
<IoTProductThingModel
|
||||
v-else-if="item.key === 'thingModel' && activeTab === 'thingModel'"
|
||||
/>
|
||||
</template>
|
||||
</Tabs>
|
||||
</Page>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import {
|
|||
Button,
|
||||
Card,
|
||||
Descriptions,
|
||||
DescriptionsItem,
|
||||
message,
|
||||
Popconfirm,
|
||||
} from 'antdv-next';
|
||||
|
|
@ -135,7 +136,7 @@ async function handleSyncPropertyTable(product: IotProductApi.Product) {
|
|||
|
||||
<Card class="mt-4">
|
||||
<Descriptions :column="1">
|
||||
<Descriptions.Item label="ProductKey">
|
||||
<DescriptionsItem label="ProductKey">
|
||||
{{ product.productKey }}
|
||||
<Button
|
||||
class="ml-2"
|
||||
|
|
@ -144,15 +145,15 @@ async function handleSyncPropertyTable(product: IotProductApi.Product) {
|
|||
>
|
||||
复制
|
||||
</Button>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="设备总数">
|
||||
</DescriptionsItem>
|
||||
<DescriptionsItem label="设备总数">
|
||||
<span class="ml-5 mr-2">
|
||||
{{ product.deviceCount ?? '加载中...' }}
|
||||
</span>
|
||||
<Button size="small" @click="goToDeviceList(product.id!)">
|
||||
前往管理
|
||||
</Button>
|
||||
</Descriptions.Item>
|
||||
</DescriptionsItem>
|
||||
</Descriptions>
|
||||
</Card>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { ref } from 'vue';
|
|||
|
||||
import { DeviceTypeEnum, DICT_TYPE } from '@vben/constants';
|
||||
|
||||
import { Button, Card, Descriptions, message } from 'antdv-next';
|
||||
import { Button, Card, Descriptions, DescriptionsItem, message } from 'antdv-next';
|
||||
|
||||
import { DictTag } from '#/components/dict-tag';
|
||||
|
||||
|
|
@ -44,37 +44,37 @@ async function copyToClipboard(text: string) {
|
|||
<template>
|
||||
<Card title="产品信息">
|
||||
<Descriptions :column="3" bordered size="small">
|
||||
<Descriptions.Item label="产品名称">
|
||||
<DescriptionsItem label="产品名称">
|
||||
{{ product.name }}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="所属分类">
|
||||
</DescriptionsItem>
|
||||
<DescriptionsItem label="所属分类">
|
||||
{{ product.categoryName || '-' }}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="设备类型">
|
||||
</DescriptionsItem>
|
||||
<DescriptionsItem label="设备类型">
|
||||
<DictTag
|
||||
:type="DICT_TYPE.IOT_PRODUCT_DEVICE_TYPE"
|
||||
:value="product.deviceType"
|
||||
/>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="创建时间">
|
||||
</DescriptionsItem>
|
||||
<DescriptionsItem label="创建时间">
|
||||
{{ formatDate(product.createTime) }}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="协议类型">
|
||||
</DescriptionsItem>
|
||||
<DescriptionsItem label="协议类型">
|
||||
<DictTag
|
||||
:type="DICT_TYPE.IOT_PROTOCOL_TYPE"
|
||||
:value="product.protocolType"
|
||||
/>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="序列化类型">
|
||||
</DescriptionsItem>
|
||||
<DescriptionsItem label="序列化类型">
|
||||
<DictTag
|
||||
:type="DICT_TYPE.IOT_SERIALIZE_TYPE"
|
||||
:value="product.serializeType"
|
||||
/>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="产品状态">
|
||||
</DescriptionsItem>
|
||||
<DescriptionsItem label="产品状态">
|
||||
<DictTag :type="DICT_TYPE.IOT_PRODUCT_STATUS" :value="product.status" />
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item
|
||||
</DescriptionsItem>
|
||||
<DescriptionsItem
|
||||
v-if="
|
||||
(
|
||||
[DeviceTypeEnum.DEVICE, DeviceTypeEnum.GATEWAY] as number[]
|
||||
|
|
@ -83,8 +83,8 @@ async function copyToClipboard(text: string) {
|
|||
label="联网方式"
|
||||
>
|
||||
<DictTag :type="DICT_TYPE.IOT_NET_TYPE" :value="product.netType" />
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="产品密钥">
|
||||
</DescriptionsItem>
|
||||
<DescriptionsItem label="产品密钥">
|
||||
<span v-if="showProductSecret">{{ product.productSecret }}</span>
|
||||
<span v-else>********</span>
|
||||
<Button class="ml-2" size="small" @click="toggleProductSecretVisible">
|
||||
|
|
@ -98,13 +98,13 @@ async function copyToClipboard(text: string) {
|
|||
>
|
||||
复制
|
||||
</Button>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="动态注册">
|
||||
</DescriptionsItem>
|
||||
<DescriptionsItem label="动态注册">
|
||||
{{ product.registerEnabled ? '已开启' : '未开启' }}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item :span="3" label="产品描述">
|
||||
</DescriptionsItem>
|
||||
<DescriptionsItem :span="3" label="产品描述">
|
||||
{{ product.description || '-' }}
|
||||
</Descriptions.Item>
|
||||
</DescriptionsItem>
|
||||
</Descriptions>
|
||||
</Card>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { computed, nextTick, ref } from 'vue';
|
|||
|
||||
import { useVbenModal } from '@vben/common-ui';
|
||||
|
||||
import { Collapse, message } from 'antdv-next';
|
||||
import { Collapse, CollapsePanel, message } from 'antdv-next';
|
||||
|
||||
import { useVbenForm } from '#/adapter/form';
|
||||
import {
|
||||
|
|
@ -148,9 +148,9 @@ const [Modal, modalApi] = useVbenModal({
|
|||
<div class="mx-4">
|
||||
<Form />
|
||||
<Collapse v-model:active-key="activeKey" class="mt-4">
|
||||
<Collapse.Panel key="advanced" header="更多设置">
|
||||
<CollapsePanel key="advanced" header="更多设置">
|
||||
<AdvancedForm />
|
||||
</Collapse.Panel>
|
||||
</CollapsePanel>
|
||||
</Collapse>
|
||||
</div>
|
||||
</Modal>
|
||||
|
|
|
|||
|
|
@ -9,17 +9,19 @@ import DataRuleList from './rule/index.vue';
|
|||
import DataSinkList from './sink/index.vue';
|
||||
|
||||
const activeTabName = ref('rule');
|
||||
const ruleDataTabItems = [
|
||||
{ key: 'rule', label: '规则' },
|
||||
{ key: 'sink', label: '目的' },
|
||||
];
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Page auto-content-height>
|
||||
<Tabs v-model:active-key="activeTabName">
|
||||
<Tabs.TabPane key="rule" tab="规则">
|
||||
<DataRuleList />
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane key="sink" tab="目的">
|
||||
<DataSinkList />
|
||||
</Tabs.TabPane>
|
||||
<Tabs v-model:active-key="activeTabName" :items="ruleDataTabItems">
|
||||
<template #contentRender="{ item }">
|
||||
<DataRuleList v-if="item.key === 'rule'" />
|
||||
<DataSinkList v-else-if="item.key === 'sink'" />
|
||||
</template>
|
||||
</Tabs>
|
||||
</Page>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { IconifyIcon } from '@vben/icons';
|
|||
import { isEmpty } from '@vben/utils';
|
||||
|
||||
import { useClipboard, useVModel } from '@vueuse/core';
|
||||
import { Button, Form, Input, message } from 'antdv-next';
|
||||
import { Button, FormItem, Input, InputPassword, message } from 'antdv-next';
|
||||
|
||||
import { IotDataSinkTypeEnum } from '#/api/iot/rule/data/sink';
|
||||
|
||||
|
|
@ -67,7 +67,7 @@ onMounted(() => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<Form.Item
|
||||
<FormItem
|
||||
:name="['config', 'jdbcUrl']"
|
||||
:rules="[
|
||||
{ required: true, message: 'JDBC 连接地址不能为空', trigger: 'blur' },
|
||||
|
|
@ -78,25 +78,25 @@ onMounted(() => {
|
|||
v-model:value="config.jdbcUrl"
|
||||
placeholder="请输入 JDBC 连接地址,如:jdbc:mysql://localhost:3306/iot_data"
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
</FormItem>
|
||||
<FormItem
|
||||
:name="['config', 'username']"
|
||||
:rules="[{ required: true, message: '用户名不能为空', trigger: 'blur' }]"
|
||||
label="用户名"
|
||||
>
|
||||
<Input v-model:value="config.username" placeholder="请输入数据库用户名" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
</FormItem>
|
||||
<FormItem
|
||||
:name="['config', 'password']"
|
||||
:rules="[{ required: true, message: '密码不能为空', trigger: 'blur' }]"
|
||||
label="密码"
|
||||
>
|
||||
<Input.Password
|
||||
<InputPassword
|
||||
v-model:value="config.password"
|
||||
placeholder="请输入数据库密码"
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
</FormItem>
|
||||
<FormItem
|
||||
:name="['config', 'tableName']"
|
||||
:rules="[{ required: true, message: '目标表名不能为空', trigger: 'blur' }]"
|
||||
label="目标表名"
|
||||
|
|
@ -115,7 +115,7 @@ onMounted(() => {
|
|||
{{ showSqlTip ? '收起表结构提示' : '查看表结构提示' }}
|
||||
</Button>
|
||||
</div>
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
<div
|
||||
v-if="showSqlTip"
|
||||
class="mt-2 overflow-hidden rounded border border-gray-200 dark:border-gray-700"
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { computed, onMounted, ref, watch } from 'vue';
|
|||
import { isEmpty } from '@vben/utils';
|
||||
|
||||
import { useVModel } from '@vueuse/core';
|
||||
import { Form, Input, Select } from 'antdv-next';
|
||||
import { FormItem, Input, Select, SelectOption, TextArea } from 'antdv-next';
|
||||
|
||||
import { IotDataSinkTypeEnum } from '#/api/iot/rule/data/sink';
|
||||
|
||||
|
|
@ -60,7 +60,7 @@ onMounted(() => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<Form.Item
|
||||
<FormItem
|
||||
:name="['config', 'url']"
|
||||
:rules="[{ required: true, message: '请求地址不能为空', trigger: 'blur' }]"
|
||||
label="请求地址"
|
||||
|
|
@ -68,13 +68,13 @@ onMounted(() => {
|
|||
<Input v-model:value="urlPath" placeholder="请输入请求地址">
|
||||
<template #addonBefore>
|
||||
<Select v-model:value="urlPrefix" class="w-[100px]">
|
||||
<Select.Option value="http://">http://</Select.Option>
|
||||
<Select.Option value="https://">https://</Select.Option>
|
||||
<SelectOption value="http://">http://</SelectOption>
|
||||
<SelectOption value="https://">https://</SelectOption>
|
||||
</Select>
|
||||
</template>
|
||||
</Input>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
</FormItem>
|
||||
<FormItem
|
||||
:name="['config', 'method']"
|
||||
:rules="[
|
||||
{ required: true, message: '请求方法不能为空', trigger: 'change' },
|
||||
|
|
@ -82,23 +82,23 @@ onMounted(() => {
|
|||
label="请求方法"
|
||||
>
|
||||
<Select v-model:value="config.method" placeholder="请选择请求方法">
|
||||
<Select.Option value="GET">GET</Select.Option>
|
||||
<Select.Option value="POST">POST</Select.Option>
|
||||
<Select.Option value="PUT">PUT</Select.Option>
|
||||
<Select.Option value="DELETE">DELETE</Select.Option>
|
||||
<SelectOption value="GET">GET</SelectOption>
|
||||
<SelectOption value="POST">POST</SelectOption>
|
||||
<SelectOption value="PUT">PUT</SelectOption>
|
||||
<SelectOption value="DELETE">DELETE</SelectOption>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item label="请求头">
|
||||
</FormItem>
|
||||
<FormItem label="请求头">
|
||||
<KeyValueEditor v-model="config.headers" add-button-text="添加请求头" />
|
||||
</Form.Item>
|
||||
<Form.Item label="请求参数">
|
||||
</FormItem>
|
||||
<FormItem label="请求参数">
|
||||
<KeyValueEditor v-model="config.query" add-button-text="添加参数" />
|
||||
</Form.Item>
|
||||
<Form.Item label="请求体">
|
||||
<Input.TextArea
|
||||
</FormItem>
|
||||
<FormItem label="请求体">
|
||||
<TextArea
|
||||
v-model:value="config.body"
|
||||
placeholder="请输入内容"
|
||||
:rows="4"
|
||||
/>
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { onMounted } from 'vue';
|
|||
import { isEmpty } from '@vben/utils';
|
||||
|
||||
import { useVModel } from '@vueuse/core';
|
||||
import { Form, Input, Switch } from 'antdv-next';
|
||||
import { FormItem, Input, InputPassword, Switch } from 'antdv-next';
|
||||
|
||||
import { IotDataSinkTypeEnum } from '#/api/iot/rule/data/sink';
|
||||
|
||||
|
|
@ -28,7 +28,7 @@ onMounted(() => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<Form.Item
|
||||
<FormItem
|
||||
:name="['config', 'bootstrapServers']"
|
||||
:rules="[{ required: true, message: '服务地址不能为空', trigger: 'blur' }]"
|
||||
label="服务地址"
|
||||
|
|
@ -37,29 +37,29 @@ onMounted(() => {
|
|||
v-model:value="config.bootstrapServers"
|
||||
placeholder="请输入服务地址,如:localhost:9092"
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
</FormItem>
|
||||
<FormItem
|
||||
:name="['config', 'username']"
|
||||
:rules="[{ required: true, message: '用户名不能为空', trigger: 'blur' }]"
|
||||
label="用户名"
|
||||
>
|
||||
<Input v-model:value="config.username" placeholder="请输入用户名" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
</FormItem>
|
||||
<FormItem
|
||||
:name="['config', 'password']"
|
||||
:rules="[{ required: true, message: '密码不能为空', trigger: 'blur' }]"
|
||||
label="密码"
|
||||
>
|
||||
<Input.Password v-model:value="config.password" placeholder="请输入密码" />
|
||||
</Form.Item>
|
||||
<Form.Item :name="['config', 'ssl']" label="启用 SSL">
|
||||
<InputPassword v-model:value="config.password" placeholder="请输入密码" />
|
||||
</FormItem>
|
||||
<FormItem :name="['config', 'ssl']" label="启用 SSL">
|
||||
<Switch v-model:checked="config.ssl" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
</FormItem>
|
||||
<FormItem
|
||||
:name="['config', 'topic']"
|
||||
:rules="[{ required: true, message: '主题不能为空', trigger: 'blur' }]"
|
||||
label="主题"
|
||||
>
|
||||
<Input v-model:value="config.topic" placeholder="请输入主题" />
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { onMounted } from 'vue';
|
|||
import { isEmpty } from '@vben/utils';
|
||||
|
||||
import { useVModel } from '@vueuse/core';
|
||||
import { Form, Input } from 'antdv-next';
|
||||
import { FormItem, Input, InputPassword } from 'antdv-next';
|
||||
|
||||
import { IotDataSinkTypeEnum } from '#/api/iot/rule/data/sink';
|
||||
|
||||
|
|
@ -28,7 +28,7 @@ onMounted(() => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<Form.Item
|
||||
<FormItem
|
||||
:name="['config', 'url']"
|
||||
:rules="[{ required: true, message: '服务地址不能为空', trigger: 'blur' }]"
|
||||
label="服务地址"
|
||||
|
|
@ -37,22 +37,22 @@ onMounted(() => {
|
|||
v-model:value="config.url"
|
||||
placeholder="请输入 MQTT 服务地址,如:mqtt://localhost:1883"
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
</FormItem>
|
||||
<FormItem
|
||||
:name="['config', 'username']"
|
||||
:rules="[{ required: true, message: '用户名不能为空', trigger: 'blur' }]"
|
||||
label="用户名"
|
||||
>
|
||||
<Input v-model:value="config.username" placeholder="请输入用户名" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
</FormItem>
|
||||
<FormItem
|
||||
:name="['config', 'password']"
|
||||
:rules="[{ required: true, message: '密码不能为空', trigger: 'blur' }]"
|
||||
label="密码"
|
||||
>
|
||||
<Input.Password v-model:value="config.password" placeholder="请输入密码" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
<InputPassword v-model:value="config.password" placeholder="请输入密码" />
|
||||
</FormItem>
|
||||
<FormItem
|
||||
:name="['config', 'clientId']"
|
||||
:rules="[
|
||||
{ required: true, message: '客户端 ID 不能为空', trigger: 'blur' },
|
||||
|
|
@ -60,12 +60,12 @@ onMounted(() => {
|
|||
label="客户端 ID"
|
||||
>
|
||||
<Input v-model:value="config.clientId" placeholder="请输入客户端 ID" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
</FormItem>
|
||||
<FormItem
|
||||
:name="['config', 'topic']"
|
||||
:rules="[{ required: true, message: '主题不能为空', trigger: 'blur' }]"
|
||||
label="主题"
|
||||
>
|
||||
<Input v-model:value="config.topic" placeholder="请输入主题" />
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { onMounted } from 'vue';
|
|||
import { isEmpty } from '@vben/utils';
|
||||
|
||||
import { useVModel } from '@vueuse/core';
|
||||
import { Form, Input, InputNumber } from 'antdv-next';
|
||||
import { FormItem, Input, InputNumber, InputPassword } from 'antdv-next';
|
||||
|
||||
import { IotDataSinkTypeEnum } from '#/api/iot/rule/data/sink';
|
||||
|
||||
|
|
@ -31,7 +31,7 @@ onMounted(() => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<Form.Item
|
||||
<FormItem
|
||||
:name="['config', 'host']"
|
||||
:rules="[{ required: true, message: '主机地址不能为空', trigger: 'blur' }]"
|
||||
label="主机地址"
|
||||
|
|
@ -40,8 +40,8 @@ onMounted(() => {
|
|||
v-model:value="config.host"
|
||||
placeholder="请输入主机地址,如:localhost"
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
</FormItem>
|
||||
<FormItem
|
||||
:name="['config', 'port']"
|
||||
:rules="[
|
||||
{ required: true, message: '端口不能为空', trigger: 'blur' },
|
||||
|
|
@ -62,47 +62,47 @@ onMounted(() => {
|
|||
placeholder="请输入端口"
|
||||
class="w-full"
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
</FormItem>
|
||||
<FormItem
|
||||
:name="['config', 'virtualHost']"
|
||||
:rules="[{ required: true, message: '虚拟主机不能为空', trigger: 'blur' }]"
|
||||
label="虚拟主机"
|
||||
>
|
||||
<Input v-model:value="config.virtualHost" placeholder="请输入虚拟主机" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
</FormItem>
|
||||
<FormItem
|
||||
:name="['config', 'username']"
|
||||
:rules="[{ required: true, message: '用户名不能为空', trigger: 'blur' }]"
|
||||
label="用户名"
|
||||
>
|
||||
<Input v-model:value="config.username" placeholder="请输入用户名" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
</FormItem>
|
||||
<FormItem
|
||||
:name="['config', 'password']"
|
||||
:rules="[{ required: true, message: '密码不能为空', trigger: 'blur' }]"
|
||||
label="密码"
|
||||
>
|
||||
<Input.Password v-model:value="config.password" placeholder="请输入密码" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
<InputPassword v-model:value="config.password" placeholder="请输入密码" />
|
||||
</FormItem>
|
||||
<FormItem
|
||||
:name="['config', 'exchange']"
|
||||
:rules="[{ required: true, message: '交换机不能为空', trigger: 'blur' }]"
|
||||
label="交换机"
|
||||
>
|
||||
<Input v-model:value="config.exchange" placeholder="请输入交换机" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
</FormItem>
|
||||
<FormItem
|
||||
:name="['config', 'routingKey']"
|
||||
:rules="[{ required: true, message: '路由键不能为空', trigger: 'blur' }]"
|
||||
label="路由键"
|
||||
>
|
||||
<Input v-model:value="config.routingKey" placeholder="请输入路由键" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
</FormItem>
|
||||
<FormItem
|
||||
:name="['config', 'queue']"
|
||||
:rules="[{ required: true, message: '队列不能为空', trigger: 'blur' }]"
|
||||
label="队列"
|
||||
>
|
||||
<Input v-model:value="config.queue" placeholder="请输入队列" />
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { onMounted } from 'vue';
|
|||
import { isEmpty } from '@vben/utils';
|
||||
|
||||
import { useVModel } from '@vueuse/core';
|
||||
import { Form, Input, InputNumber } from 'antdv-next';
|
||||
import { FormItem, Input, InputNumber, InputPassword } from 'antdv-next';
|
||||
|
||||
import { IotDataSinkTypeEnum } from '#/api/iot/rule/data/sink';
|
||||
|
||||
|
|
@ -36,7 +36,7 @@ onMounted(() => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<Form.Item
|
||||
<FormItem
|
||||
:name="['config', 'host']"
|
||||
:rules="[{ required: true, message: '主机地址不能为空', trigger: 'blur' }]"
|
||||
label="主机地址"
|
||||
|
|
@ -45,8 +45,8 @@ onMounted(() => {
|
|||
v-model:value="config.host"
|
||||
placeholder="请输入主机地址,如:localhost"
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
</FormItem>
|
||||
<FormItem
|
||||
:name="['config', 'port']"
|
||||
:rules="[
|
||||
{ required: true, message: '端口不能为空', trigger: 'blur' },
|
||||
|
|
@ -67,15 +67,15 @@ onMounted(() => {
|
|||
placeholder="请输入端口"
|
||||
class="w-full"
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
</FormItem>
|
||||
<FormItem
|
||||
:name="['config', 'password']"
|
||||
:rules="[{ required: true, message: '密码不能为空', trigger: 'blur' }]"
|
||||
label="密码"
|
||||
>
|
||||
<Input.Password v-model:value="config.password" placeholder="请输入密码" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
<InputPassword v-model:value="config.password" placeholder="请输入密码" />
|
||||
</FormItem>
|
||||
<FormItem
|
||||
:name="['config', 'database']"
|
||||
:rules="[
|
||||
{ required: true, message: '数据库索引不能为空', trigger: 'blur' },
|
||||
|
|
@ -95,12 +95,12 @@ onMounted(() => {
|
|||
placeholder="请输入数据库索引"
|
||||
class="w-full"
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
</FormItem>
|
||||
<FormItem
|
||||
:name="['config', 'topic']"
|
||||
:rules="[{ required: true, message: '主题不能为空', trigger: 'blur' }]"
|
||||
label="主题"
|
||||
>
|
||||
<Input v-model:value="config.topic" placeholder="请输入主题" />
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { onMounted } from 'vue';
|
|||
import { isEmpty } from '@vben/utils';
|
||||
|
||||
import { useVModel } from '@vueuse/core';
|
||||
import { Form, Input } from 'antdv-next';
|
||||
import { FormItem, Input, InputPassword } from 'antdv-next';
|
||||
|
||||
import { IotDataSinkTypeEnum } from '#/api/iot/rule/data/sink';
|
||||
|
||||
|
|
@ -29,7 +29,7 @@ onMounted(() => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<Form.Item
|
||||
<FormItem
|
||||
:name="['config', 'nameServer']"
|
||||
:rules="[
|
||||
{ required: true, message: 'NameServer 地址不能为空', trigger: 'blur' },
|
||||
|
|
@ -40,8 +40,8 @@ onMounted(() => {
|
|||
v-model:value="config.nameServer"
|
||||
placeholder="请输入 NameServer 地址,如:127.0.0.1:9876"
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
</FormItem>
|
||||
<FormItem
|
||||
:name="['config', 'accessKey']"
|
||||
:rules="[
|
||||
{ required: true, message: 'AccessKey 不能为空', trigger: 'blur' },
|
||||
|
|
@ -49,34 +49,34 @@ onMounted(() => {
|
|||
label="AccessKey"
|
||||
>
|
||||
<Input v-model:value="config.accessKey" placeholder="请输入 AccessKey" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
</FormItem>
|
||||
<FormItem
|
||||
:name="['config', 'secretKey']"
|
||||
:rules="[
|
||||
{ required: true, message: 'SecretKey 不能为空', trigger: 'blur' },
|
||||
]"
|
||||
label="SecretKey"
|
||||
>
|
||||
<Input.Password
|
||||
<InputPassword
|
||||
v-model:value="config.secretKey"
|
||||
placeholder="请输入 SecretKey"
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
</FormItem>
|
||||
<FormItem
|
||||
:name="['config', 'group']"
|
||||
:rules="[{ required: true, message: '消费组不能为空', trigger: 'blur' }]"
|
||||
label="消费组"
|
||||
>
|
||||
<Input v-model:value="config.group" placeholder="请输入消费组" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
</FormItem>
|
||||
<FormItem
|
||||
:name="['config', 'topic']"
|
||||
:rules="[{ required: true, message: '主题不能为空', trigger: 'blur' }]"
|
||||
label="主题"
|
||||
>
|
||||
<Input v-model:value="config.topic" placeholder="请输入主题" />
|
||||
</Form.Item>
|
||||
<Form.Item :name="['config', 'tags']" label="标签">
|
||||
</FormItem>
|
||||
<FormItem :name="['config', 'tags']" label="标签">
|
||||
<Input v-model:value="config.tags" placeholder="请输入标签" />
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { onMounted } from 'vue';
|
|||
import { isEmpty } from '@vben/utils';
|
||||
|
||||
import { useVModel } from '@vueuse/core';
|
||||
import { Form, Input, InputNumber, Select, Switch } from 'antdv-next';
|
||||
import { FormItem, Input, InputNumber, Select, SelectOption, Switch } from 'antdv-next';
|
||||
|
||||
import { IotDataSinkTypeEnum } from '#/api/iot/rule/data/sink';
|
||||
|
||||
|
|
@ -33,7 +33,7 @@ onMounted(() => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<Form.Item
|
||||
<FormItem
|
||||
:name="['config', 'host']"
|
||||
:rules="[{ required: true, message: '主机地址不能为空', trigger: 'blur' }]"
|
||||
label="服务器地址"
|
||||
|
|
@ -42,8 +42,8 @@ onMounted(() => {
|
|||
v-model:value="config.host"
|
||||
placeholder="请输入 TCP 服务器地址,如:localhost"
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
</FormItem>
|
||||
<FormItem
|
||||
:name="['config', 'port']"
|
||||
:rules="[
|
||||
{ required: true, message: '端口不能为空', trigger: 'blur' },
|
||||
|
|
@ -64,8 +64,8 @@ onMounted(() => {
|
|||
placeholder="请输入端口"
|
||||
class="w-full"
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
</FormItem>
|
||||
<FormItem
|
||||
:name="['config', 'connectTimeoutMs']"
|
||||
:rules="[
|
||||
{ required: true, message: '连接超时时间不能为空', trigger: 'blur' },
|
||||
|
|
@ -78,8 +78,8 @@ onMounted(() => {
|
|||
:step="1000"
|
||||
class="w-full"
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
</FormItem>
|
||||
<FormItem
|
||||
:name="['config', 'readTimeoutMs']"
|
||||
:rules="[
|
||||
{ required: true, message: '读取超时时间不能为空', trigger: 'blur' },
|
||||
|
|
@ -92,11 +92,11 @@ onMounted(() => {
|
|||
:step="1000"
|
||||
class="w-full"
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item :name="['config', 'ssl']" label="启用 SSL">
|
||||
</FormItem>
|
||||
<FormItem :name="['config', 'ssl']" label="启用 SSL">
|
||||
<Switch v-model:checked="config.ssl" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
</FormItem>
|
||||
<FormItem
|
||||
v-if="config.ssl"
|
||||
:name="['config', 'sslCertPath']"
|
||||
label="SSL 证书路径"
|
||||
|
|
@ -105,8 +105,8 @@ onMounted(() => {
|
|||
v-model:value="config.sslCertPath"
|
||||
placeholder="请输入 SSL 证书路径"
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
</FormItem>
|
||||
<FormItem
|
||||
:name="['config', 'dataFormat']"
|
||||
:rules="[
|
||||
{ required: true, message: '数据格式不能为空', trigger: 'change' },
|
||||
|
|
@ -114,11 +114,11 @@ onMounted(() => {
|
|||
label="数据格式"
|
||||
>
|
||||
<Select v-model:value="config.dataFormat" placeholder="请选择数据格式">
|
||||
<Select.Option value="JSON">JSON</Select.Option>
|
||||
<Select.Option value="BINARY">BINARY</Select.Option>
|
||||
<SelectOption value="JSON">JSON</SelectOption>
|
||||
<SelectOption value="BINARY">BINARY</SelectOption>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item :name="['config', 'heartbeatIntervalMs']" label="心跳间隔(ms)">
|
||||
</FormItem>
|
||||
<FormItem :name="['config', 'heartbeatIntervalMs']" label="心跳间隔(ms)">
|
||||
<InputNumber
|
||||
v-model:value="config.heartbeatIntervalMs"
|
||||
:min="0"
|
||||
|
|
@ -126,20 +126,20 @@ onMounted(() => {
|
|||
placeholder="0 表示不启用心跳"
|
||||
class="w-full"
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item :name="['config', 'reconnectIntervalMs']" label="重连间隔(ms)">
|
||||
</FormItem>
|
||||
<FormItem :name="['config', 'reconnectIntervalMs']" label="重连间隔(ms)">
|
||||
<InputNumber
|
||||
v-model:value="config.reconnectIntervalMs"
|
||||
:min="1000"
|
||||
:step="1000"
|
||||
class="w-full"
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item :name="['config', 'maxReconnectAttempts']" label="最大重连次数">
|
||||
</FormItem>
|
||||
<FormItem :name="['config', 'maxReconnectAttempts']" label="最大重连次数">
|
||||
<InputNumber
|
||||
v-model:value="config.maxReconnectAttempts"
|
||||
:min="0"
|
||||
class="w-full"
|
||||
/>
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { onMounted } from 'vue';
|
|||
import { isEmpty } from '@vben/utils';
|
||||
|
||||
import { useVModel } from '@vueuse/core';
|
||||
import { Form, Input, InputNumber, Select, Switch } from 'antdv-next';
|
||||
import { FormItem, Input, InputNumber, Select, SelectOption, Switch, TextArea } from 'antdv-next';
|
||||
|
||||
import { IotDataSinkTypeEnum } from '#/api/iot/rule/data/sink';
|
||||
|
||||
|
|
@ -37,7 +37,7 @@ onMounted(() => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<Form.Item
|
||||
<FormItem
|
||||
:name="['config', 'serverUrl']"
|
||||
:rules="[
|
||||
{
|
||||
|
|
@ -52,8 +52,8 @@ onMounted(() => {
|
|||
v-model:value="config.serverUrl"
|
||||
placeholder="请输入 WebSocket 地址,如:ws://localhost:8080/ws"
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
</FormItem>
|
||||
<FormItem
|
||||
:name="['config', 'connectTimeoutMs']"
|
||||
:rules="[
|
||||
{ required: true, message: '连接超时时间不能为空', trigger: 'blur' },
|
||||
|
|
@ -66,8 +66,8 @@ onMounted(() => {
|
|||
:step="1000"
|
||||
class="w-full"
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
</FormItem>
|
||||
<FormItem
|
||||
:name="['config', 'sendTimeoutMs']"
|
||||
:rules="[
|
||||
{ required: true, message: '发送超时时间不能为空', trigger: 'blur' },
|
||||
|
|
@ -80,8 +80,8 @@ onMounted(() => {
|
|||
:step="1000"
|
||||
class="w-full"
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item :name="['config', 'heartbeatIntervalMs']" label="心跳间隔(ms)">
|
||||
</FormItem>
|
||||
<FormItem :name="['config', 'heartbeatIntervalMs']" label="心跳间隔(ms)">
|
||||
<InputNumber
|
||||
v-model:value="config.heartbeatIntervalMs"
|
||||
:min="0"
|
||||
|
|
@ -89,30 +89,30 @@ onMounted(() => {
|
|||
placeholder="0 表示不启用心跳"
|
||||
class="w-full"
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item :name="['config', 'heartbeatMessage']" label="心跳消息">
|
||||
</FormItem>
|
||||
<FormItem :name="['config', 'heartbeatMessage']" label="心跳消息">
|
||||
<Input
|
||||
v-model:value="config.heartbeatMessage"
|
||||
placeholder="请输入心跳消息内容(JSON 格式)"
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item :name="['config', 'subprotocols']" label="子协议">
|
||||
</FormItem>
|
||||
<FormItem :name="['config', 'subprotocols']" label="子协议">
|
||||
<Input
|
||||
v-model:value="config.subprotocols"
|
||||
placeholder="请输入子协议列表,多个用逗号分隔"
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item :name="['config', 'customHeaders']" label="自定义请求头">
|
||||
<Input.TextArea
|
||||
</FormItem>
|
||||
<FormItem :name="['config', 'customHeaders']" label="自定义请求头">
|
||||
<TextArea
|
||||
v-model:value="config.customHeaders"
|
||||
placeholder="请输入自定义请求头(JSON 格式)"
|
||||
:rows="3"
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item :name="['config', 'verifySslCert']" label="验证 SSL 证书">
|
||||
</FormItem>
|
||||
<FormItem :name="['config', 'verifySslCert']" label="验证 SSL 证书">
|
||||
<Switch v-model:checked="config.verifySslCert" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
</FormItem>
|
||||
<FormItem
|
||||
:name="['config', 'dataFormat']"
|
||||
:rules="[
|
||||
{ required: true, message: '数据格式不能为空', trigger: 'change' },
|
||||
|
|
@ -120,41 +120,41 @@ onMounted(() => {
|
|||
label="数据格式"
|
||||
>
|
||||
<Select v-model:value="config.dataFormat" placeholder="请选择数据格式">
|
||||
<Select.Option value="JSON">JSON</Select.Option>
|
||||
<Select.Option value="TEXT">TEXT</Select.Option>
|
||||
<SelectOption value="JSON">JSON</SelectOption>
|
||||
<SelectOption value="TEXT">TEXT</SelectOption>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item :name="['config', 'reconnectIntervalMs']" label="重连间隔(ms)">
|
||||
</FormItem>
|
||||
<FormItem :name="['config', 'reconnectIntervalMs']" label="重连间隔(ms)">
|
||||
<InputNumber
|
||||
v-model:value="config.reconnectIntervalMs"
|
||||
:min="1000"
|
||||
:step="1000"
|
||||
class="w-full"
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item :name="['config', 'maxReconnectAttempts']" label="最大重连次数">
|
||||
</FormItem>
|
||||
<FormItem :name="['config', 'maxReconnectAttempts']" label="最大重连次数">
|
||||
<InputNumber
|
||||
v-model:value="config.maxReconnectAttempts"
|
||||
:min="0"
|
||||
class="w-full"
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item :name="['config', 'enableCompression']" label="启用压缩">
|
||||
</FormItem>
|
||||
<FormItem :name="['config', 'enableCompression']" label="启用压缩">
|
||||
<Switch v-model:checked="config.enableCompression" />
|
||||
</Form.Item>
|
||||
<Form.Item :name="['config', 'sendRetryCount']" label="发送重试次数">
|
||||
</FormItem>
|
||||
<FormItem :name="['config', 'sendRetryCount']" label="发送重试次数">
|
||||
<InputNumber
|
||||
v-model:value="config.sendRetryCount"
|
||||
:min="0"
|
||||
class="w-full"
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item :name="['config', 'sendRetryIntervalMs']" label="重试间隔(ms)">
|
||||
</FormItem>
|
||||
<FormItem :name="['config', 'sendRetryIntervalMs']" label="重试间隔(ms)">
|
||||
<InputNumber
|
||||
v-model:value="config.sendRetryIntervalMs"
|
||||
:min="100"
|
||||
:step="500"
|
||||
class="w-full"
|
||||
/>
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { useVbenModal } from '@vben/common-ui';
|
|||
import { CommonStatusEnum, DICT_TYPE } from '@vben/constants';
|
||||
import { getDictOptions } from '@vben/hooks';
|
||||
|
||||
import { Form, Input, message, Radio, Select } from 'antdv-next';
|
||||
import { Form, FormItem, Input, message, Radio, RadioGroup, Select, TextArea } from 'antdv-next';
|
||||
|
||||
import {
|
||||
createDataSink,
|
||||
|
|
@ -102,7 +102,7 @@ function handleTypeChange(type: number) {
|
|||
:wrapper-col="{ span: 18 }"
|
||||
class="mx-4"
|
||||
>
|
||||
<Form.Item
|
||||
<FormItem
|
||||
:rules="[
|
||||
{ required: true, message: '目的名称不能为空', trigger: 'blur' },
|
||||
]"
|
||||
|
|
@ -110,15 +110,15 @@ function handleTypeChange(type: number) {
|
|||
name="name"
|
||||
>
|
||||
<Input v-model:value="formData.name" placeholder="请输入目的名称" />
|
||||
</Form.Item>
|
||||
<Form.Item label="目的描述" name="description">
|
||||
<Input.TextArea
|
||||
</FormItem>
|
||||
<FormItem label="目的描述" name="description">
|
||||
<TextArea
|
||||
v-model:value="formData.description"
|
||||
placeholder="请输入目的描述"
|
||||
:rows="3"
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
</FormItem>
|
||||
<FormItem
|
||||
:rules="[
|
||||
{ required: true, message: '目的类型不能为空', trigger: 'change' },
|
||||
]"
|
||||
|
|
@ -133,7 +133,7 @@ function handleTypeChange(type: number) {
|
|||
placeholder="请选择目的类型"
|
||||
@change="(value: any) => handleTypeChange(value as number)"
|
||||
/>
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
<!-- 配置项:按目的类型分支 -->
|
||||
<HttpConfigForm
|
||||
v-if="formData.type === IotDataSinkTypeEnum.HTTP"
|
||||
|
|
@ -171,14 +171,14 @@ function handleTypeChange(type: number) {
|
|||
v-if="formData.type === IotDataSinkTypeEnum.REDIS_STREAM"
|
||||
v-model="formData.config"
|
||||
/>
|
||||
<Form.Item
|
||||
<FormItem
|
||||
:rules="[
|
||||
{ required: true, message: '目的状态不能为空', trigger: 'change' },
|
||||
]"
|
||||
label="目的状态"
|
||||
name="status"
|
||||
>
|
||||
<Radio.Group v-model:value="formData.status">
|
||||
<RadioGroup v-model:value="formData.status">
|
||||
<Radio
|
||||
v-for="dict in getDictOptions(DICT_TYPE.COMMON_STATUS, 'number')"
|
||||
:key="String(dict.value)"
|
||||
|
|
@ -186,8 +186,8 @@ function handleTypeChange(type: number) {
|
|||
>
|
||||
{{ dict.label }}
|
||||
</Radio>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
</RadioGroup>
|
||||
</FormItem>
|
||||
</Form>
|
||||
</Modal>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
import { onMounted, ref } from 'vue';
|
||||
|
||||
import { useVModel } from '@vueuse/core';
|
||||
import { Form, Select } from 'antdv-next';
|
||||
import { FormItem, Select, SelectOption } from 'antdv-next';
|
||||
|
||||
import { getSimpleAlertConfigList } from '#/api/iot/alert/config';
|
||||
|
||||
|
|
@ -46,7 +46,7 @@ onMounted(() => {
|
|||
|
||||
<template>
|
||||
<div class="w-full">
|
||||
<Form.Item label="告警配置" required>
|
||||
<FormItem label="告警配置" required>
|
||||
<Select
|
||||
v-model:value="localValue"
|
||||
placeholder="请选择告警配置"
|
||||
|
|
@ -56,13 +56,13 @@ onMounted(() => {
|
|||
class="w-full"
|
||||
:loading="loading"
|
||||
>
|
||||
<Select.Option
|
||||
<SelectOption
|
||||
v-for="config in alertConfigs"
|
||||
:key="config.id"
|
||||
:label="config.name"
|
||||
:value="config.id"
|
||||
/>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import {
|
|||
} from '@vben/constants';
|
||||
|
||||
import { useVModel } from '@vueuse/core';
|
||||
import { Col, Form, Row, Select } from 'antdv-next';
|
||||
import { Col, FormItem, Row, Select, SelectOption } from 'antdv-next';
|
||||
|
||||
import ValueInput from '../inputs/value-input.vue';
|
||||
import DeviceSelector from '../selectors/device-selector.vue';
|
||||
|
|
@ -160,7 +160,7 @@ function handleOperatorChange() {
|
|||
<!-- 条件类型选择 -->
|
||||
<Row :gutter="16">
|
||||
<Col :span="8">
|
||||
<Form.Item label="条件类型" required>
|
||||
<FormItem label="条件类型" required>
|
||||
<Select
|
||||
:value="condition.type"
|
||||
@change="
|
||||
|
|
@ -172,22 +172,22 @@ function handleOperatorChange() {
|
|||
placeholder="请选择条件类型"
|
||||
class="w-full"
|
||||
>
|
||||
<Select.Option
|
||||
<SelectOption
|
||||
v-for="option in getConditionTypeOptions()"
|
||||
:key="option.value"
|
||||
:value="option.value"
|
||||
>
|
||||
{{ option.label }}
|
||||
</Select.Option>
|
||||
</SelectOption>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<!-- 产品设备选择 - 设备相关条件的公共部分 -->
|
||||
<Row v-if="isDeviceCondition" :gutter="16">
|
||||
<Col :span="12">
|
||||
<Form.Item label="产品" required>
|
||||
<FormItem label="产品" required>
|
||||
<ProductSelector
|
||||
:model-value="condition.productId"
|
||||
@update:model-value="
|
||||
|
|
@ -195,10 +195,10 @@ function handleOperatorChange() {
|
|||
"
|
||||
@change="handleProductChange"
|
||||
/>
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
</Col>
|
||||
<Col :span="12">
|
||||
<Form.Item label="设备" required>
|
||||
<FormItem label="设备" required>
|
||||
<DeviceSelector
|
||||
:model-value="condition.deviceId"
|
||||
@update:model-value="
|
||||
|
|
@ -207,7 +207,7 @@ function handleOperatorChange() {
|
|||
:product-id="condition.productId"
|
||||
@change="handleDeviceChange"
|
||||
/>
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
|
|
@ -222,42 +222,42 @@ function handleOperatorChange() {
|
|||
<Row :gutter="16">
|
||||
<!-- 操作符选择 -->
|
||||
<Col :span="12">
|
||||
<Form.Item label="操作符" required>
|
||||
<FormItem label="操作符" required>
|
||||
<Select
|
||||
:value="condition.operator"
|
||||
@change="(value: any) => updateConditionField('operator', value)"
|
||||
placeholder="请选择操作符"
|
||||
class="w-full"
|
||||
>
|
||||
<Select.Option
|
||||
<SelectOption
|
||||
v-for="option in statusOperatorOptions"
|
||||
:key="option.value"
|
||||
:value="option.value"
|
||||
>
|
||||
{{ option.label }}
|
||||
</Select.Option>
|
||||
</SelectOption>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
</Col>
|
||||
|
||||
<!-- 状态选择 -->
|
||||
<Col :span="12">
|
||||
<Form.Item label="设备状态" required>
|
||||
<FormItem label="设备状态" required>
|
||||
<Select
|
||||
:value="condition.param"
|
||||
@change="(value: any) => updateConditionField('param', value)"
|
||||
placeholder="请选择设备状态"
|
||||
class="w-full"
|
||||
>
|
||||
<Select.Option
|
||||
<SelectOption
|
||||
v-for="option in deviceStatusOptions"
|
||||
:key="option.value"
|
||||
:value="option.value"
|
||||
>
|
||||
{{ option.label }}
|
||||
</Select.Option>
|
||||
</SelectOption>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
|
|
@ -273,7 +273,7 @@ function handleOperatorChange() {
|
|||
<Row :gutter="16">
|
||||
<!-- 属性/事件/服务选择 -->
|
||||
<Col :span="6">
|
||||
<Form.Item label="监控项" required>
|
||||
<FormItem label="监控项" required>
|
||||
<PropertySelector
|
||||
:model-value="condition.identifier"
|
||||
@update:model-value="
|
||||
|
|
@ -284,12 +284,12 @@ function handleOperatorChange() {
|
|||
:device-id="condition.deviceId"
|
||||
@change="handlePropertyChange"
|
||||
/>
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
</Col>
|
||||
|
||||
<!-- 操作符选择 -->
|
||||
<Col :span="6">
|
||||
<Form.Item label="操作符" required>
|
||||
<FormItem label="操作符" required>
|
||||
<OperatorSelector
|
||||
:model-value="condition.operator"
|
||||
@update:model-value="
|
||||
|
|
@ -298,12 +298,12 @@ function handleOperatorChange() {
|
|||
:property-type="propertyType"
|
||||
@change="handleOperatorChange"
|
||||
/>
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
</Col>
|
||||
|
||||
<!-- 值输入 -->
|
||||
<Col :span="12">
|
||||
<Form.Item label="比较值" required>
|
||||
<FormItem label="比较值" required>
|
||||
<ValueInput
|
||||
:model-value="condition.param"
|
||||
@update:model-value="
|
||||
|
|
@ -313,7 +313,7 @@ function handleOperatorChange() {
|
|||
:operator="condition.operator"
|
||||
:property-config="propertyConfig"
|
||||
/>
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -14,9 +14,10 @@ import { useVModel } from '@vueuse/core';
|
|||
import {
|
||||
Col,
|
||||
DatePicker,
|
||||
Form,
|
||||
FormItem,
|
||||
Row,
|
||||
Select,
|
||||
SelectOption,
|
||||
Tag,
|
||||
TimePicker,
|
||||
} from 'antdv-next';
|
||||
|
|
@ -178,7 +179,7 @@ watch(
|
|||
<Row :gutter="16">
|
||||
<!-- 时间操作符选择 -->
|
||||
<Col :span="8">
|
||||
<Form.Item label="时间条件" required>
|
||||
<FormItem label="时间条件" required>
|
||||
<Select
|
||||
:value="condition.operator"
|
||||
@update:value="
|
||||
|
|
@ -187,7 +188,7 @@ watch(
|
|||
placeholder="请选择时间条件"
|
||||
class="w-full"
|
||||
>
|
||||
<Select.Option
|
||||
<SelectOption
|
||||
v-for="option in timeOperatorOptions"
|
||||
:key="option.value"
|
||||
:label="option.label"
|
||||
|
|
@ -202,14 +203,14 @@ watch(
|
|||
{{ option.category }}
|
||||
</Tag>
|
||||
</div>
|
||||
</Select.Option>
|
||||
</SelectOption>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
</Col>
|
||||
|
||||
<!-- 时间值输入 -->
|
||||
<Col :span="8">
|
||||
<Form.Item label="时间值" required>
|
||||
<FormItem label="时间值" required>
|
||||
<TimePicker
|
||||
v-if="needsTimeInput"
|
||||
:value="timeValue"
|
||||
|
|
@ -230,12 +231,12 @@ watch(
|
|||
class="w-full"
|
||||
/>
|
||||
<div v-else class="text-sm text-muted-foreground">无需设置时间值</div>
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
</Col>
|
||||
|
||||
<!-- 第二个时间值(范围条件) -->
|
||||
<Col :span="8" v-if="needsSecondTimeInput">
|
||||
<Form.Item label="结束时间" required>
|
||||
<FormItem label="结束时间" required>
|
||||
<TimePicker
|
||||
v-if="needsTimeInput"
|
||||
:value="timeValue2"
|
||||
|
|
@ -255,7 +256,7 @@ watch(
|
|||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
class="w-full"
|
||||
/>
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import {
|
|||
import { isObject } from '@vben/utils';
|
||||
|
||||
import { useVModel } from '@vueuse/core';
|
||||
import { Col, Form, Row, Select, Tag } from 'antdv-next';
|
||||
import { Col, FormItem, Row, Select, SelectOption, Tag } from 'antdv-next';
|
||||
|
||||
import { getThingModelTSLByProductId } from '#/api/iot/thingmodel';
|
||||
|
||||
|
|
@ -315,21 +315,21 @@ watch(
|
|||
<!-- 产品和设备选择 - 与触发器保持一致的分离式选择器 -->
|
||||
<Row :gutter="16">
|
||||
<Col :span="12">
|
||||
<Form.Item label="产品" required>
|
||||
<FormItem label="产品" required>
|
||||
<ProductSelector
|
||||
v-model="action.productId"
|
||||
@change="handleProductChange"
|
||||
/>
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
</Col>
|
||||
<Col :span="12">
|
||||
<Form.Item label="设备" required>
|
||||
<FormItem label="设备" required>
|
||||
<DeviceSelector
|
||||
v-model="action.deviceId"
|
||||
:product-id="action.productId"
|
||||
@change="handleDeviceChange"
|
||||
/>
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
|
|
@ -338,7 +338,7 @@ watch(
|
|||
v-if="action.productId && isServiceInvokeAction"
|
||||
class="space-y-[16px]"
|
||||
>
|
||||
<Form.Item label="服务" required>
|
||||
<FormItem label="服务" required>
|
||||
<Select
|
||||
v-model:value="action.identifier"
|
||||
placeholder="请选择服务"
|
||||
|
|
@ -348,7 +348,7 @@ watch(
|
|||
:loading="loadingServices"
|
||||
@change="handleServiceChange"
|
||||
>
|
||||
<Select.Option
|
||||
<SelectOption
|
||||
v-for="service in serviceList"
|
||||
:key="service.identifier"
|
||||
:label="service.name"
|
||||
|
|
@ -362,34 +362,34 @@ watch(
|
|||
{{ service.callType === 'sync' ? '同步' : '异步' }}
|
||||
</Tag>
|
||||
</div>
|
||||
</Select.Option>
|
||||
</SelectOption>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
|
||||
<!-- 服务参数配置 -->
|
||||
<div v-if="action.identifier" class="space-y-[16px]">
|
||||
<Form.Item label="服务参数" required>
|
||||
<FormItem label="服务参数" required>
|
||||
<JsonParamsInput
|
||||
v-model="paramsValue"
|
||||
type="service"
|
||||
:config="{ service: selectedService } as any"
|
||||
placeholder="请输入 JSON 格式的服务参数"
|
||||
/>
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 控制参数配置 - 属性设置类型时显示 -->
|
||||
<div v-if="action.productId && isPropertySetAction" class="space-y-[16px]">
|
||||
<!-- 参数配置 -->
|
||||
<Form.Item label="参数" required>
|
||||
<FormItem label="参数" required>
|
||||
<JsonParamsInput
|
||||
v-model="paramsValue"
|
||||
type="property"
|
||||
:config="{ properties: thingModelProperties }"
|
||||
placeholder="请输入 JSON 格式的控制参数"
|
||||
/>
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import {
|
|||
} from '@vben/constants';
|
||||
|
||||
import { useVModel } from '@vueuse/core';
|
||||
import { Col, Form, Input, Row, Select } from 'antdv-next';
|
||||
import { Col, FormItem, Input, Row, Select, SelectOption } from 'antdv-next';
|
||||
|
||||
import JsonParamsInput from '../inputs/json-params-input.vue';
|
||||
import ValueInput from '../inputs/value-input.vue';
|
||||
|
|
@ -168,29 +168,29 @@ function handlePropertyChange(propertyInfo: any) {
|
|||
<template>
|
||||
<div class="space-y-4">
|
||||
<!-- 触发事件类型选择 -->
|
||||
<Form.Item label="触发事件类型" required>
|
||||
<FormItem label="触发事件类型" required>
|
||||
<Select
|
||||
:value="triggerType"
|
||||
@change="(value: any) => handleTriggerTypeChange(value)"
|
||||
placeholder="请选择触发事件类型"
|
||||
class="w-full"
|
||||
>
|
||||
<Select.Option
|
||||
<SelectOption
|
||||
v-for="option in triggerTypeOptions"
|
||||
:key="option.value"
|
||||
:value="option.value"
|
||||
>
|
||||
{{ option.label }}
|
||||
</Select.Option>
|
||||
</SelectOption>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
|
||||
<!-- 设备属性条件配置 -->
|
||||
<div v-if="isDevicePropertyTrigger" class="space-y-4">
|
||||
<!-- 产品设备选择 -->
|
||||
<Row :gutter="16">
|
||||
<Col :span="12">
|
||||
<Form.Item label="产品" required>
|
||||
<FormItem label="产品" required>
|
||||
<ProductSelector
|
||||
:model-value="condition.productId"
|
||||
@update:model-value="
|
||||
|
|
@ -198,10 +198,10 @@ function handlePropertyChange(propertyInfo: any) {
|
|||
"
|
||||
@change="handleProductChange"
|
||||
/>
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
</Col>
|
||||
<Col :span="12">
|
||||
<Form.Item label="设备" required>
|
||||
<FormItem label="设备" required>
|
||||
<DeviceSelector
|
||||
:model-value="condition.deviceId"
|
||||
@update:model-value="
|
||||
|
|
@ -210,7 +210,7 @@ function handlePropertyChange(propertyInfo: any) {
|
|||
:product-id="condition.productId"
|
||||
@change="handleDeviceChange"
|
||||
/>
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
|
|
@ -218,7 +218,7 @@ function handlePropertyChange(propertyInfo: any) {
|
|||
<Row :gutter="16">
|
||||
<!-- 属性/事件/服务选择 -->
|
||||
<Col :span="6">
|
||||
<Form.Item label="监控项" required>
|
||||
<FormItem label="监控项" required>
|
||||
<PropertySelector
|
||||
:model-value="condition.identifier"
|
||||
@update:model-value="
|
||||
|
|
@ -229,12 +229,12 @@ function handlePropertyChange(propertyInfo: any) {
|
|||
:device-id="condition.deviceId"
|
||||
@change="handlePropertyChange"
|
||||
/>
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
</Col>
|
||||
|
||||
<!-- 操作符选择 - 服务调用和事件上报不需要操作符 -->
|
||||
<Col v-if="needsOperatorSelector" :span="6">
|
||||
<Form.Item label="操作符" required>
|
||||
<FormItem label="操作符" required>
|
||||
<OperatorSelector
|
||||
:model-value="condition.operator"
|
||||
@update:model-value="
|
||||
|
|
@ -242,12 +242,12 @@ function handlePropertyChange(propertyInfo: any) {
|
|||
"
|
||||
:property-type="propertyType"
|
||||
/>
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
</Col>
|
||||
|
||||
<!-- 值输入 -->
|
||||
<Col :span="isWideValueColumn ? 18 : 12">
|
||||
<Form.Item :label="valueInputLabel" required>
|
||||
<FormItem :label="valueInputLabel" required>
|
||||
<!-- 服务调用参数配置 -->
|
||||
<JsonParamsInput
|
||||
v-if="
|
||||
|
|
@ -265,9 +265,7 @@ function handlePropertyChange(propertyInfo: any) {
|
|||
triggerType === IotRuleSceneTriggerTypeEnum.DEVICE_EVENT_POST
|
||||
"
|
||||
:value="condition.value"
|
||||
@update:value="
|
||||
(value) => updateConditionField('value', value)
|
||||
"
|
||||
@update:value="(value) => updateConditionField('value', value)"
|
||||
placeholder="留空则事件发生即匹配"
|
||||
/>
|
||||
<!-- 普通值输入 -->
|
||||
|
|
@ -281,7 +279,7 @@ function handlePropertyChange(propertyInfo: any) {
|
|||
:operator="condition.operator"
|
||||
:property-config="propertyConfig"
|
||||
/>
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
|
|
@ -291,7 +289,7 @@ function handlePropertyChange(propertyInfo: any) {
|
|||
<!-- 设备状态触发器使用简化的配置 -->
|
||||
<Row :gutter="16">
|
||||
<Col :span="12">
|
||||
<Form.Item label="产品" required>
|
||||
<FormItem label="产品" required>
|
||||
<ProductSelector
|
||||
:model-value="condition.productId"
|
||||
@update:model-value="
|
||||
|
|
@ -299,10 +297,10 @@ function handlePropertyChange(propertyInfo: any) {
|
|||
"
|
||||
@change="handleProductChange"
|
||||
/>
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
</Col>
|
||||
<Col :span="12">
|
||||
<Form.Item label="设备" required>
|
||||
<FormItem label="设备" required>
|
||||
<DeviceSelector
|
||||
:model-value="condition.deviceId"
|
||||
@update:model-value="
|
||||
|
|
@ -311,19 +309,19 @@ function handlePropertyChange(propertyInfo: any) {
|
|||
:product-id="condition.productId"
|
||||
@change="handleDeviceChange"
|
||||
/>
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row :gutter="16">
|
||||
<Col :span="6">
|
||||
<Form.Item label="操作符" required>
|
||||
<FormItem label="操作符" required>
|
||||
<Select
|
||||
:value="condition.operator"
|
||||
@change="(value: any) => updateConditionField('operator', value)"
|
||||
placeholder="请选择操作符"
|
||||
class="w-full"
|
||||
>
|
||||
<Select.Option
|
||||
<SelectOption
|
||||
:value="
|
||||
IotRuleSceneTriggerConditionParameterOperatorEnum.EQUALS.value
|
||||
"
|
||||
|
|
@ -331,27 +329,27 @@ function handlePropertyChange(propertyInfo: any) {
|
|||
{{
|
||||
IotRuleSceneTriggerConditionParameterOperatorEnum.EQUALS.name
|
||||
}}
|
||||
</Select.Option>
|
||||
</SelectOption>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
</Col>
|
||||
<Col :span="6">
|
||||
<Form.Item label="参数" required>
|
||||
<FormItem label="参数" required>
|
||||
<Select
|
||||
:value="condition.value"
|
||||
@change="(value: any) => updateConditionField('value', value)"
|
||||
placeholder="请选择操作符"
|
||||
class="w-full"
|
||||
>
|
||||
<Select.Option
|
||||
<SelectOption
|
||||
v-for="option in deviceStatusChangeOptions"
|
||||
:key="option.value"
|
||||
:value="option.value"
|
||||
>
|
||||
{{ option.label }}
|
||||
</Select.Option>
|
||||
</SelectOption>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import { IconifyIcon } from '@vben/icons';
|
|||
import { isEmptyVal } from '@vben/utils';
|
||||
|
||||
import { useVModel } from '@vueuse/core';
|
||||
import { Button, Input, Popover, Tag } from 'antdv-next';
|
||||
import { Button, Popover, Tag, TextArea } from 'antdv-next';
|
||||
|
||||
/** JSON参数输入组件 - 通用版本 */
|
||||
defineOptions({ name: 'JsonParamsInput' });
|
||||
|
|
@ -422,7 +422,7 @@ watch(
|
|||
<div class="w-full space-y-3">
|
||||
<!-- JSON 输入框 -->
|
||||
<div class="relative">
|
||||
<Input.TextArea
|
||||
<TextArea
|
||||
v-model:value="paramsJson"
|
||||
:rows="4"
|
||||
:placeholder="placeholder"
|
||||
|
|
@ -434,7 +434,7 @@ watch(
|
|||
<Popover
|
||||
placement="leftTop"
|
||||
:overlay-style="{ width: '450px' }"
|
||||
trigger="click"
|
||||
:trigger="['click']"
|
||||
:arrow="true"
|
||||
overlay-class-name="json-params-detail-popover"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import {
|
|||
Input,
|
||||
InputNumber,
|
||||
Select,
|
||||
SelectOption,
|
||||
Tag,
|
||||
Tooltip,
|
||||
} from 'antdv-next';
|
||||
|
|
@ -209,8 +210,8 @@ watch(
|
|||
placeholder="请选择布尔值"
|
||||
class="w-full!"
|
||||
>
|
||||
<Select.Option value="true">真 (true)</Select.Option>
|
||||
<Select.Option value="false">假 (false)</Select.Option>
|
||||
<SelectOption value="true">真 (true)</SelectOption>
|
||||
<SelectOption value="false">假 (false)</SelectOption>
|
||||
</Select>
|
||||
|
||||
<!-- 枚举值选择 -->
|
||||
|
|
@ -222,13 +223,13 @@ watch(
|
|||
placeholder="请选择枚举值"
|
||||
class="w-full!"
|
||||
>
|
||||
<Select.Option
|
||||
<SelectOption
|
||||
v-for="option in enumOptions"
|
||||
:key="option.value"
|
||||
:value="option.value"
|
||||
>
|
||||
{{ option.label }}
|
||||
</Select.Option>
|
||||
</SelectOption>
|
||||
</Select>
|
||||
|
||||
<!-- 范围输入 (between 操作符) -->
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import { IconifyIcon } from '@vben/icons';
|
|||
import { getStableObjectKey } from '@vben/utils';
|
||||
|
||||
import { useVModel } from '@vueuse/core';
|
||||
import { Button, Card, Empty, Form, Select, Tag } from 'antdv-next';
|
||||
import { Button, Card, Empty, FormItem, Select, SelectOption, Tag } from 'antdv-next';
|
||||
|
||||
import AlertConfig from '../configs/alert-config.vue';
|
||||
import DeviceControlConfig from '../configs/device-control-config.vue';
|
||||
|
|
@ -228,22 +228,22 @@ function onActionTypeChange(action: RuleSceneApi.Action, type: number) {
|
|||
<div class="p-[16px] space-y-[16px]">
|
||||
<!-- 执行类型选择 -->
|
||||
<div class="w-full">
|
||||
<Form.Item label="执行类型" required>
|
||||
<FormItem label="执行类型" required>
|
||||
<Select
|
||||
:value="action.type"
|
||||
@change="(value) => updateActionType(index, value as number)"
|
||||
placeholder="请选择执行类型"
|
||||
class="w-full"
|
||||
>
|
||||
<Select.Option
|
||||
<SelectOption
|
||||
v-for="option in getActionTypeOptions()"
|
||||
:key="option.value"
|
||||
:value="option.value"
|
||||
>
|
||||
{{ option.label }}
|
||||
</Select.Option>
|
||||
</SelectOption>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
</div>
|
||||
|
||||
<!-- 设备控制配置 -->
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { getDictOptions } from '@vben/hooks';
|
|||
import { IconifyIcon } from '@vben/icons';
|
||||
|
||||
import { useVModel } from '@vueuse/core';
|
||||
import { Card, Col, Form, Input, Radio, Row } from 'antdv-next';
|
||||
import { Card, Col, FormItem, Input, Radio, RadioGroup, Row, TextArea } from 'antdv-next';
|
||||
|
||||
import { DictTag } from '#/components/dict-tag';
|
||||
|
||||
|
|
@ -44,7 +44,7 @@ const formData = useVModel(props, 'modelValue', emit); // 表单数据
|
|||
<div class="p-0">
|
||||
<Row :gutter="24" class="mb-[24px]">
|
||||
<Col :span="12">
|
||||
<Form.Item label="场景名称" name="name" required>
|
||||
<FormItem label="场景名称" name="name" required>
|
||||
<Input
|
||||
v-model:value="formData.name"
|
||||
placeholder="请输入场景名称"
|
||||
|
|
@ -52,11 +52,11 @@ const formData = useVModel(props, 'modelValue', emit); // 表单数据
|
|||
show-word-limit
|
||||
allow-clear
|
||||
/>
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
</Col>
|
||||
<Col :span="12">
|
||||
<Form.Item label="场景状态" name="status" required>
|
||||
<Radio.Group v-model:value="formData.status">
|
||||
<FormItem label="场景状态" name="status" required>
|
||||
<RadioGroup v-model:value="formData.status">
|
||||
<Radio
|
||||
v-for="(dict, index) in getDictOptions(
|
||||
DICT_TYPE.COMMON_STATUS,
|
||||
|
|
@ -67,12 +67,12 @@ const formData = useVModel(props, 'modelValue', emit); // 表单数据
|
|||
>
|
||||
{{ dict.label }}
|
||||
</Radio>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
</RadioGroup>
|
||||
</FormItem>
|
||||
</Col>
|
||||
</Row>
|
||||
<Form.Item label="场景描述" name="description">
|
||||
<Input.TextArea
|
||||
<FormItem label="场景描述" name="description">
|
||||
<TextArea
|
||||
v-model:value="formData.description"
|
||||
placeholder="请输入场景描述(可选)"
|
||||
:rows="3"
|
||||
|
|
@ -80,7 +80,7 @@ const formData = useVModel(props, 'modelValue', emit); // 表单数据
|
|||
show-word-limit
|
||||
resize="none"
|
||||
/>
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
</div>
|
||||
</Card>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import { IconifyIcon } from '@vben/icons';
|
|||
import { getStableObjectKey } from '@vben/utils';
|
||||
|
||||
import { useVModel } from '@vueuse/core';
|
||||
import { Button, Card, Empty, Form, Tag } from 'antdv-next';
|
||||
import { Button, Card, Empty, FormItem, Tag } from 'antdv-next';
|
||||
|
||||
import { CronTab } from '#/components/cron-tab';
|
||||
|
||||
|
|
@ -208,14 +208,14 @@ onMounted(() => {
|
|||
<div
|
||||
class="p-[16px] rounded-[6px] border border-primary bg-background"
|
||||
>
|
||||
<Form.Item label="CRON 表达式" required>
|
||||
<FormItem label="CRON 表达式" required>
|
||||
<CronTab
|
||||
:model-value="triggerItem.cronExpression || '0 0 12 * * ?'"
|
||||
@update:model-value="
|
||||
(value) => updateTriggerCronConfig(index, value)
|
||||
"
|
||||
/>
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
</div>
|
||||
|
||||
<!-- 附加条件组配置 -->
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { ref, watch } from 'vue';
|
|||
|
||||
import { DEVICE_SELECTOR_OPTIONS, DICT_TYPE } from '@vben/constants';
|
||||
|
||||
import { Select } from 'antdv-next';
|
||||
import { Select, SelectOption } from 'antdv-next';
|
||||
|
||||
import { getDeviceListByProductId } from '#/api/iot/device/device';
|
||||
import { DictTag } from '#/components/dict-tag';
|
||||
|
|
@ -93,7 +93,7 @@ watch(
|
|||
:loading="deviceLoading"
|
||||
:disabled="!productId"
|
||||
>
|
||||
<Select.Option
|
||||
<SelectOption
|
||||
v-for="device in deviceList"
|
||||
:key="device.id"
|
||||
:label="device.deviceName"
|
||||
|
|
@ -112,6 +112,6 @@ watch(
|
|||
<DictTag :type="DICT_TYPE.IOT_DEVICE_STATE" :value="device.state" />
|
||||
</div>
|
||||
</div>
|
||||
</Select.Option>
|
||||
</SelectOption>
|
||||
</Select>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import {
|
|||
} from '@vben/constants';
|
||||
|
||||
import { useVModel } from '@vueuse/core';
|
||||
import { Select } from 'antdv-next';
|
||||
import { Select, SelectOption } from 'antdv-next';
|
||||
|
||||
/** 操作符选择器组件 */
|
||||
defineOptions({ name: 'OperatorSelector' });
|
||||
|
|
@ -245,7 +245,7 @@ watch(
|
|||
class="w-full"
|
||||
option-label-prop="label"
|
||||
>
|
||||
<Select.Option
|
||||
<SelectOption
|
||||
v-for="operator in availableOperators"
|
||||
:key="operator.value"
|
||||
:label="operator.label"
|
||||
|
|
@ -266,7 +266,7 @@ watch(
|
|||
{{ operator.description }}
|
||||
</div>
|
||||
</div>
|
||||
</Select.Option>
|
||||
</SelectOption>
|
||||
</Select>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { onMounted, ref } from 'vue';
|
|||
|
||||
import { DICT_TYPE } from '@vben/constants';
|
||||
|
||||
import { Select } from 'antdv-next';
|
||||
import { Select, SelectOption } from 'antdv-next';
|
||||
|
||||
import { getSimpleProductList } from '#/api/iot/product/product';
|
||||
import { DictTag } from '#/components/dict-tag';
|
||||
|
|
@ -64,7 +64,7 @@ onMounted(() => {
|
|||
option-label-prop="label"
|
||||
:loading="productLoading"
|
||||
>
|
||||
<Select.Option
|
||||
<SelectOption
|
||||
v-for="product in productList"
|
||||
:key="product.id"
|
||||
:label="product.name"
|
||||
|
|
@ -81,6 +81,6 @@ onMounted(() => {
|
|||
</div>
|
||||
<DictTag :type="DICT_TYPE.IOT_PRODUCT_STATUS" :value="product.status" />
|
||||
</div>
|
||||
</Select.Option>
|
||||
</SelectOption>
|
||||
</Select>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import {
|
|||
import { IconifyIcon } from '@vben/icons';
|
||||
|
||||
import { useVModel } from '@vueuse/core';
|
||||
import { Button, Popover, Select, Tag } from 'antdv-next';
|
||||
import { Button, Popover, Select, SelectOptGroup, SelectOption, Tag } from 'antdv-next';
|
||||
|
||||
import { getThingModelTSLByProductId } from '#/api/iot/thingmodel';
|
||||
|
||||
|
|
@ -219,12 +219,12 @@ watch(
|
|||
option-label-prop="label"
|
||||
:loading="loading"
|
||||
>
|
||||
<Select.OptGroup
|
||||
<SelectOptGroup
|
||||
v-for="group in propertyGroups"
|
||||
:key="group.label"
|
||||
:label="group.label"
|
||||
>
|
||||
<Select.Option
|
||||
<SelectOption
|
||||
v-for="property in group.options"
|
||||
:key="property.identifier"
|
||||
:label="property.name"
|
||||
|
|
@ -240,8 +240,8 @@ watch(
|
|||
{{ property.identifier }}
|
||||
</Tag>
|
||||
</div>
|
||||
</Select.Option>
|
||||
</Select.OptGroup>
|
||||
</SelectOption>
|
||||
</SelectOptGroup>
|
||||
</Select>
|
||||
|
||||
<!-- 属性详情弹出层 -->
|
||||
|
|
@ -249,7 +249,7 @@ watch(
|
|||
v-if="selectedProperty"
|
||||
placement="rightTop"
|
||||
:overlay-style="{ width: '350px' }"
|
||||
trigger="click"
|
||||
:trigger="['click']"
|
||||
:arrow="true"
|
||||
overlay-class-name="property-detail-popover"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import type { Ref } from 'vue';
|
|||
import { getDataTypeOptions, IoTDataSpecsDataTypeEnum } from '@vben/constants';
|
||||
|
||||
import { useVModel } from '@vueuse/core';
|
||||
import { Form, Input, Radio } from 'antdv-next';
|
||||
import { FormItem, Input, Radio, RadioGroup } from 'antdv-next';
|
||||
|
||||
import { ThingModelFormRules } from '#/api/iot/thingmodel';
|
||||
|
||||
|
|
@ -34,12 +34,12 @@ function handleChange(e: any) {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<Form.Item
|
||||
<FormItem
|
||||
:name="['property', 'dataSpecs', 'childDataType']"
|
||||
:rules="ThingModelFormRules.childDataType"
|
||||
label="元素类型"
|
||||
>
|
||||
<Radio.Group v-model:value="dataSpecs.childDataType" @change="handleChange">
|
||||
<RadioGroup v-model:value="dataSpecs.childDataType" @change="handleChange">
|
||||
<Radio
|
||||
v-for="item in childDataTypeOptions"
|
||||
:key="item.value"
|
||||
|
|
@ -48,9 +48,9 @@ function handleChange(e: any) {
|
|||
>
|
||||
{{ `${item.value}(${item.label})` }}
|
||||
</Radio>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
</RadioGroup>
|
||||
</FormItem>
|
||||
<FormItem
|
||||
:name="['property', 'dataSpecs', 'size']"
|
||||
:rules="ThingModelFormRules.size"
|
||||
label="元素个数"
|
||||
|
|
@ -59,7 +59,7 @@ function handleChange(e: any) {
|
|||
v-model:value="dataSpecs.size"
|
||||
placeholder="请输入数组中的元素个数"
|
||||
/>
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
<!-- Struct 型配置-->
|
||||
<ThingModelStructDataSpecs
|
||||
v-if="dataSpecs.childDataType === IoTDataSpecsDataTypeEnum.STRUCT"
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { IoTDataSpecsDataTypeEnum } from '@vben/constants';
|
|||
import { isEmpty } from '@vben/utils';
|
||||
|
||||
import { useVModel } from '@vueuse/core';
|
||||
import { Button, Form, Input, message } from 'antdv-next';
|
||||
import { Button, FormItem, Input, message } from 'antdv-next';
|
||||
|
||||
import { buildIdentifierLikeNameValidator } from '#/api/iot/thingmodel';
|
||||
|
||||
|
|
@ -84,7 +84,7 @@ function validateEnumList(_rule: any, _value: any, callback: any) {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<Form.Item
|
||||
<FormItem
|
||||
:rules="[{ validator: validateEnumList, trigger: 'change' }]"
|
||||
label="枚举项"
|
||||
>
|
||||
|
|
@ -98,7 +98,7 @@ function validateEnumList(_rule: any, _value: any, callback: any) {
|
|||
:key="index"
|
||||
class="mb-[5px] flex items-center justify-between"
|
||||
>
|
||||
<Form.Item
|
||||
<FormItem
|
||||
:name="['property', 'dataSpecsList', index, 'value']"
|
||||
:rules="[
|
||||
{ required: true, message: '枚举值不能为空', trigger: 'blur' },
|
||||
|
|
@ -110,9 +110,9 @@ function validateEnumList(_rule: any, _value: any, callback: any) {
|
|||
v-model:value="item.value"
|
||||
placeholder="请输入枚举值,如「0」"
|
||||
/>
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
<span class="mx-2">~</span>
|
||||
<Form.Item
|
||||
<FormItem
|
||||
:name="['property', 'dataSpecsList', index, 'name']"
|
||||
:rules="[
|
||||
{ required: true, message: '枚举描述不能为空', trigger: 'blur' },
|
||||
|
|
@ -121,14 +121,14 @@ function validateEnumList(_rule: any, _value: any, callback: any) {
|
|||
class="mb-0 flex-1"
|
||||
>
|
||||
<Input v-model:value="item.name" placeholder="对该枚举项的描述" />
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
<Button class="ml-2.5" type="link" @click="deleteEnum(index)">
|
||||
删除
|
||||
</Button>
|
||||
</div>
|
||||
<Button type="link" @click="addEnum">+ 添加枚举项</Button>
|
||||
</div>
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import { DICT_TYPE } from '@vben/constants';
|
|||
import { getDictOptions } from '@vben/hooks';
|
||||
|
||||
import { useVModel } from '@vueuse/core';
|
||||
import { Form, Input, Select } from 'antdv-next';
|
||||
import { FormItem, Input, Select, SelectOption } from 'antdv-next';
|
||||
|
||||
const props = defineProps<{ modelValue: any }>();
|
||||
const emits = defineEmits(['update:modelValue']);
|
||||
|
|
@ -87,9 +87,9 @@ function validateStep(_rule: any, _value: any, callback: any) {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<Form.Item label="取值范围">
|
||||
<FormItem label="取值范围">
|
||||
<div class="flex items-center justify-between">
|
||||
<Form.Item
|
||||
<FormItem
|
||||
:name="['property', 'dataSpecs', 'min']"
|
||||
:rules="[
|
||||
{ required: true, message: '最小值不能为空', trigger: 'blur' },
|
||||
|
|
@ -98,9 +98,9 @@ function validateStep(_rule: any, _value: any, callback: any) {
|
|||
class="mb-0 flex-1"
|
||||
>
|
||||
<Input v-model:value="dataSpecs.min" placeholder="请输入最小值" />
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
<span class="mx-2">~</span>
|
||||
<Form.Item
|
||||
<FormItem
|
||||
:name="['property', 'dataSpecs', 'max']"
|
||||
:rules="[
|
||||
{ required: true, message: '最大值不能为空', trigger: 'blur' },
|
||||
|
|
@ -109,10 +109,10 @@ function validateStep(_rule: any, _value: any, callback: any) {
|
|||
class="mb-0 flex-1"
|
||||
>
|
||||
<Input v-model:value="dataSpecs.max" placeholder="请输入最大值" />
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
</div>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
</FormItem>
|
||||
<FormItem
|
||||
:name="['property', 'dataSpecs', 'step']"
|
||||
:rules="[
|
||||
{ required: true, message: '步长不能为空', trigger: 'blur' },
|
||||
|
|
@ -121,8 +121,8 @@ function validateStep(_rule: any, _value: any, callback: any) {
|
|||
label="步长"
|
||||
>
|
||||
<Input v-model:value="dataSpecs.step" placeholder="请输入步长" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
</FormItem>
|
||||
<FormItem
|
||||
:name="['property', 'dataSpecs', 'unit']"
|
||||
:rules="[{ required: true, message: '请选择单位', trigger: 'change' }]"
|
||||
label="单位"
|
||||
|
|
@ -134,15 +134,15 @@ function validateStep(_rule: any, _value: any, callback: any) {
|
|||
class="w-full"
|
||||
@change="unitChange"
|
||||
>
|
||||
<Select.Option
|
||||
<SelectOption
|
||||
v-for="(item, index) in unitOptions"
|
||||
:key="index"
|
||||
:value="`${item.label}-${item.value}`"
|
||||
>
|
||||
{{ `${item.label}-${item.value}` }}
|
||||
</Select.Option>
|
||||
</SelectOption>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import { IoTDataSpecsDataTypeEnum } from '@vben/constants';
|
|||
import { cloneDeep, isEmpty } from '@vben/utils';
|
||||
|
||||
import { useVModel } from '@vueuse/core';
|
||||
import { Button, Divider, Form, Input } from 'antdv-next';
|
||||
import { Button, Divider, Form, FormItem, Input } from 'antdv-next';
|
||||
|
||||
import { ThingModelFormRules } from '#/api/iot/thingmodel';
|
||||
|
||||
|
|
@ -130,7 +130,7 @@ onMounted(() => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<Form.Item
|
||||
<FormItem
|
||||
:name="fieldPath"
|
||||
:rules="[{ validator: validateStructSpecsList, trigger: 'change' }]"
|
||||
label="属性对象"
|
||||
|
|
@ -150,7 +150,7 @@ onMounted(() => {
|
|||
</div>
|
||||
</div>
|
||||
<Button type="link" @click="openStructForm(null)">+ 新增参数</Button>
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
|
||||
<!-- 结构体参数表单 -->
|
||||
<Modal class="w-2/5" title="结构体参数">
|
||||
|
|
@ -161,16 +161,16 @@ onMounted(() => {
|
|||
:wrapper-col="{ span: 18 }"
|
||||
class="mx-4"
|
||||
>
|
||||
<Form.Item :rules="ThingModelFormRules.name" label="参数名称" name="name">
|
||||
<FormItem :rules="ThingModelFormRules.name" label="参数名称" name="name">
|
||||
<Input v-model:value="formData.name" placeholder="请输入参数名称" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
</FormItem>
|
||||
<FormItem
|
||||
:rules="ThingModelFormRules.identifier"
|
||||
label="标识符"
|
||||
name="identifier"
|
||||
>
|
||||
<Input v-model:value="formData.identifier" placeholder="请输入标识符" />
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
<!-- 属性配置 -->
|
||||
<ThingModelProperty v-model="formData.property" is-struct-data-specs />
|
||||
</Form>
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import {
|
|||
import { isEmpty } from '@vben/utils';
|
||||
|
||||
import { useVModel } from '@vueuse/core';
|
||||
import { Form, Radio } from 'antdv-next';
|
||||
import { FormItem, Radio, RadioGroup } from 'antdv-next';
|
||||
|
||||
import { ThingModelFormRules } from '#/api/iot/thingmodel';
|
||||
|
||||
|
|
@ -32,12 +32,12 @@ watch(
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<Form.Item
|
||||
<FormItem
|
||||
:name="['event', 'type']"
|
||||
:rules="ThingModelFormRules.eventType"
|
||||
label="事件类型"
|
||||
>
|
||||
<Radio.Group v-model:value="thingModelEvent.type">
|
||||
<RadioGroup v-model:value="thingModelEvent.type">
|
||||
<Radio
|
||||
v-for="eventType in Object.values(IoTThingModelEventTypeEnum)"
|
||||
:key="eventType.value"
|
||||
|
|
@ -45,14 +45,14 @@ watch(
|
|||
>
|
||||
{{ eventType.label }}
|
||||
</Radio>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
<Form.Item label="输出参数">
|
||||
</RadioGroup>
|
||||
</FormItem>
|
||||
<FormItem label="输出参数">
|
||||
<ThingModelInputOutputParam
|
||||
v-model="thingModelEvent.outputParams"
|
||||
:direction="IoTThingModelParamDirectionEnum.OUTPUT"
|
||||
/>
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
|
|
|||
|
|
@ -17,7 +17,15 @@ import { getDictOptions } from '@vben/hooks';
|
|||
import { $t } from '@vben/locales';
|
||||
import { cloneDeep, isEmpty } from '@vben/utils';
|
||||
|
||||
import { Form, Input, message, Radio } from 'antdv-next';
|
||||
import {
|
||||
Form,
|
||||
FormItem,
|
||||
Input,
|
||||
message,
|
||||
RadioButton,
|
||||
RadioGroup,
|
||||
TextArea,
|
||||
} from 'antdv-next';
|
||||
|
||||
import {
|
||||
createThingModel,
|
||||
|
|
@ -197,30 +205,27 @@ function removeDataSpecs(val: any) {
|
|||
:wrapper-col="{ span: 18 }"
|
||||
class="mx-4"
|
||||
>
|
||||
<Form.Item :rules="ThingModelFormRules.type" label="功能类型" name="type">
|
||||
<Radio.Group v-model:value="formData.type">
|
||||
<Radio.Button
|
||||
<FormItem :rules="ThingModelFormRules.type" label="功能类型" name="type">
|
||||
<RadioGroup v-model:value="formData.type">
|
||||
<RadioButton
|
||||
v-for="dict in getDictOptions(DICT_TYPE.IOT_THING_MODEL_TYPE)"
|
||||
:key="String(dict.value)"
|
||||
:value="Number(dict.value)"
|
||||
>
|
||||
{{ dict.label }}
|
||||
</Radio.Button>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
<Form.Item :rules="ThingModelFormRules.name" label="功能名称" name="name">
|
||||
</RadioButton>
|
||||
</RadioGroup>
|
||||
</FormItem>
|
||||
<FormItem :rules="ThingModelFormRules.name" label="功能名称" name="name">
|
||||
<Input v-model:value="formData.name" placeholder="请输入功能名称" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
</FormItem>
|
||||
<FormItem
|
||||
:rules="ThingModelFormRules.identifier"
|
||||
label="标识符"
|
||||
name="identifier"
|
||||
>
|
||||
<Input
|
||||
v-model:value="formData.identifier"
|
||||
placeholder="请输入标识符"
|
||||
/>
|
||||
</Form.Item>
|
||||
<Input v-model:value="formData.identifier" placeholder="请输入标识符" />
|
||||
</FormItem>
|
||||
<!-- 属性配置 -->
|
||||
<ThingModelProperty
|
||||
v-if="formData.type === IoTThingModelTypeEnum.PROPERTY"
|
||||
|
|
@ -236,14 +241,14 @@ function removeDataSpecs(val: any) {
|
|||
v-if="formData.type === IoTThingModelTypeEnum.EVENT"
|
||||
v-model="formData.event"
|
||||
/>
|
||||
<Form.Item label="描述" name="description">
|
||||
<Input.TextArea
|
||||
<FormItem label="描述" name="description">
|
||||
<TextArea
|
||||
v-model:value="formData.description"
|
||||
:maxlength="200"
|
||||
:rows="3"
|
||||
placeholder="请输入物模型描述"
|
||||
/>
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
</Form>
|
||||
</Modal>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import { IoTDataSpecsDataTypeEnum } from '@vben/constants';
|
|||
import { cloneDeep, isEmpty } from '@vben/utils';
|
||||
|
||||
import { useVModel } from '@vueuse/core';
|
||||
import { Button, Divider, Form, Input, message } from 'antdv-next';
|
||||
import { Button, Divider, Form, FormItem, Input, message } from 'antdv-next';
|
||||
|
||||
import { ThingModelFormRules } from '#/api/iot/thingmodel';
|
||||
|
||||
|
|
@ -146,16 +146,16 @@ function deleteParamItem(index: number) {
|
|||
:wrapper-col="{ span: 18 }"
|
||||
class="mx-4"
|
||||
>
|
||||
<Form.Item :rules="ThingModelFormRules.name" label="参数名称" name="name">
|
||||
<FormItem :rules="ThingModelFormRules.name" label="参数名称" name="name">
|
||||
<Input v-model:value="formData.name" placeholder="请输入参数名称" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
</FormItem>
|
||||
<FormItem
|
||||
:rules="ThingModelFormRules.identifier"
|
||||
label="标识符"
|
||||
name="identifier"
|
||||
>
|
||||
<Input v-model:value="formData.identifier" placeholder="请输入标识符" />
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
<!-- 属性配置 -->
|
||||
<ThingModelProperty v-model="formData.property" is-params />
|
||||
</Form>
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import {
|
|||
import { isEmpty } from '@vben/utils';
|
||||
|
||||
import { useVModel } from '@vueuse/core';
|
||||
import { Form, Input, Radio, Select } from 'antdv-next';
|
||||
import { FormItem, Input, Radio, RadioGroup, Select, SelectOption } from 'antdv-next';
|
||||
|
||||
import { ThingModelFormRules, validateBoolName } from '#/api/iot/thingmodel';
|
||||
|
||||
|
|
@ -102,7 +102,7 @@ if (!props.isStructDataSpecs && !props.isParams) {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<Form.Item
|
||||
<FormItem
|
||||
:name="['property', 'dataType']"
|
||||
:rules="[{ required: true, message: '请选择数据类型', trigger: 'change' }]"
|
||||
label="数据类型"
|
||||
|
|
@ -113,15 +113,15 @@ if (!props.isStructDataSpecs && !props.isParams) {
|
|||
@change="handleChange"
|
||||
>
|
||||
<!-- ARRAY 和 STRUCT 类型数据相互嵌套时,最多支持递归嵌套 2 层(父和子) -->
|
||||
<Select.Option
|
||||
<SelectOption
|
||||
v-for="option in dataTypeOptions"
|
||||
:key="option.value"
|
||||
:value="option.value"
|
||||
>
|
||||
{{ `${option.value}(${option.label})` }}
|
||||
</Select.Option>
|
||||
</SelectOption>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
<!-- 数值型配置 -->
|
||||
<ThingModelNumberDataSpecs
|
||||
v-if="NUMERIC_TYPES.has(property.dataType || '')"
|
||||
|
|
@ -133,7 +133,7 @@ if (!props.isStructDataSpecs && !props.isParams) {
|
|||
v-model="property.dataSpecsList"
|
||||
/>
|
||||
<!-- 布尔型配置 -->
|
||||
<Form.Item
|
||||
<FormItem
|
||||
v-if="property.dataType === IoTDataSpecsDataTypeEnum.BOOL"
|
||||
label="布尔值"
|
||||
>
|
||||
|
|
@ -141,7 +141,7 @@ if (!props.isStructDataSpecs && !props.isParams) {
|
|||
<div class="mb-[5px] flex w-full items-center justify-start">
|
||||
<span>{{ item.value }}</span>
|
||||
<span class="mx-2">-</span>
|
||||
<Form.Item
|
||||
<FormItem
|
||||
:name="['property', 'dataSpecsList', index, 'name']"
|
||||
:rules="[
|
||||
{ required: true, message: '布尔描述不能为空', trigger: 'blur' },
|
||||
|
|
@ -154,12 +154,12 @@ if (!props.isStructDataSpecs && !props.isParams) {
|
|||
:placeholder="`如:${item.value === 0 ? '关' : '开'}`"
|
||||
class="!w-[255px]"
|
||||
/>
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
</div>
|
||||
</template>
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
<!-- 文本型配置 -->
|
||||
<Form.Item
|
||||
<FormItem
|
||||
v-if="property.dataType === IoTDataSpecsDataTypeEnum.TEXT"
|
||||
:name="['property', 'dataSpecs', 'length']"
|
||||
:rules="ThingModelFormRules.length"
|
||||
|
|
@ -172,9 +172,9 @@ if (!props.isStructDataSpecs && !props.isParams) {
|
|||
>
|
||||
<template #addonAfter>字节</template>
|
||||
</Input>
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
<!-- 时间型配置 -->
|
||||
<Form.Item
|
||||
<FormItem
|
||||
v-if="property.dataType === IoTDataSpecsDataTypeEnum.DATE"
|
||||
label="时间格式"
|
||||
name="date"
|
||||
|
|
@ -184,7 +184,7 @@ if (!props.isStructDataSpecs && !props.isParams) {
|
|||
disabled
|
||||
placeholder="String 类型的 UTC 时间戳(毫秒)"
|
||||
/>
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
<!-- 数组型配置-->
|
||||
<ThingModelArrayDataSpecs
|
||||
v-if="property.dataType === IoTDataSpecsDataTypeEnum.ARRAY"
|
||||
|
|
@ -195,13 +195,13 @@ if (!props.isStructDataSpecs && !props.isParams) {
|
|||
v-if="property.dataType === IoTDataSpecsDataTypeEnum.STRUCT"
|
||||
v-model="property.dataSpecsList"
|
||||
/>
|
||||
<Form.Item
|
||||
<FormItem
|
||||
v-if="!isStructDataSpecs && !isParams"
|
||||
:name="['property', 'accessMode']"
|
||||
:rules="ThingModelFormRules.accessMode"
|
||||
label="读写类型"
|
||||
>
|
||||
<Radio.Group v-model:value="property.accessMode">
|
||||
<RadioGroup v-model:value="property.accessMode">
|
||||
<Radio
|
||||
v-for="accessMode in Object.values(IoTThingModelAccessModeEnum)"
|
||||
:key="accessMode.value"
|
||||
|
|
@ -209,8 +209,8 @@ if (!props.isStructDataSpecs && !props.isParams) {
|
|||
>
|
||||
{{ accessMode.label }}
|
||||
</Radio>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
</RadioGroup>
|
||||
</FormItem>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import {
|
|||
import { isEmpty } from '@vben/utils';
|
||||
|
||||
import { useVModel } from '@vueuse/core';
|
||||
import { Form, Radio } from 'antdv-next';
|
||||
import { FormItem, Radio, RadioGroup } from 'antdv-next';
|
||||
|
||||
import { ThingModelFormRules } from '#/api/iot/thingmodel';
|
||||
|
||||
|
|
@ -43,12 +43,12 @@ function getParamIdentifiers(params?: any[]) {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<Form.Item
|
||||
<FormItem
|
||||
:name="['service', 'callType']"
|
||||
:rules="ThingModelFormRules.callType"
|
||||
label="调用方式"
|
||||
>
|
||||
<Radio.Group v-model:value="service.callType">
|
||||
<RadioGroup v-model:value="service.callType">
|
||||
<Radio
|
||||
v-for="callType in Object.values(IoTThingModelServiceCallTypeEnum)"
|
||||
:key="callType.value"
|
||||
|
|
@ -56,22 +56,22 @@ function getParamIdentifiers(params?: any[]) {
|
|||
>
|
||||
{{ callType.label }}
|
||||
</Radio>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
<Form.Item label="输入参数">
|
||||
</RadioGroup>
|
||||
</FormItem>
|
||||
<FormItem label="输入参数">
|
||||
<ThingModelInputOutputParam
|
||||
v-model="service.inputParams"
|
||||
:direction="IoTThingModelParamDirectionEnum.INPUT"
|
||||
:existing-identifiers="getParamIdentifiers(service.outputParams)"
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item label="输出参数">
|
||||
</FormItem>
|
||||
<FormItem label="输出参数">
|
||||
<ThingModelInputOutputParam
|
||||
v-model="service.outputParams"
|
||||
:direction="IoTThingModelParamDirectionEnum.OUTPUT"
|
||||
:existing-identifiers="getParamIdentifiers(service.inputParams)"
|
||||
/>
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import { computed, inject, ref, watch } from 'vue';
|
|||
import { useVbenModal } from '@vben/common-ui';
|
||||
import { IOT_PROVIDE_KEY } from '@vben/constants';
|
||||
|
||||
import { Radio, TextArea } from 'antdv-next';
|
||||
import { Radio, RadioButton, RadioGroup, TextArea } from 'antdv-next';
|
||||
|
||||
import { getThingModelTSLByProductId } from '#/api/iot/thingmodel';
|
||||
|
||||
|
|
@ -56,10 +56,10 @@ watch(tslString, (newValue) => {
|
|||
<Modal :footer="false" class="w-3/5" title="物模型 TSL">
|
||||
<div class="mx-4">
|
||||
<div class="mb-4">
|
||||
<Radio.Group v-model:value="viewMode" size="small">
|
||||
<Radio.Button value="view">代码视图</Radio.Button>
|
||||
<Radio.Button value="editor">编辑器视图</Radio.Button>
|
||||
</Radio.Group>
|
||||
<RadioGroup v-model:value="viewMode" size="small">
|
||||
<RadioButton value="view">代码视图</RadioButton>
|
||||
<RadioButton value="editor">编辑器视图</RadioButton>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
<!-- 代码视图:只读展示(pre / code 必须紧贴,避免显示出空白) -->
|
||||
<div
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
import type { MallSpuApi } from '#/api/mall/product/spu';
|
||||
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
|
||||
import { confirm, DocAlert, Page } from '@vben/common-ui';
|
||||
|
|
@ -53,6 +53,12 @@ const tabsData = ref([
|
|||
count: 0,
|
||||
},
|
||||
]);
|
||||
const spuTabItems = computed(() =>
|
||||
tabsData.value.map((item) => ({
|
||||
key: String(item.type),
|
||||
label: `${item.name} (${item.count})`,
|
||||
})),
|
||||
);
|
||||
|
||||
/** 刷新表格 */
|
||||
async function handleRefresh() {
|
||||
|
|
@ -208,13 +214,7 @@ onMounted(async () => {
|
|||
|
||||
<Grid>
|
||||
<template #toolbar-actions>
|
||||
<Tabs @change="onChangeTab" class="w-full">
|
||||
<Tabs.TabPane
|
||||
v-for="item in tabsData"
|
||||
:key="item.type"
|
||||
:tab="`${item.name} (${item.count})`"
|
||||
/>
|
||||
</Tabs>
|
||||
<Tabs :items="spuTabItems" @change="onChangeTab" class="w-full" />
|
||||
</template>
|
||||
<template #toolbar-tools>
|
||||
<TableAction
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
import { Space } from 'antdv-next';
|
||||
import { SpaceCompact } from 'antdv-next';
|
||||
|
||||
/**
|
||||
* 垂直按钮组
|
||||
|
|
@ -10,8 +10,8 @@ defineOptions({ name: 'VerticalSpace' });
|
|||
|
||||
<template>
|
||||
<Space v-bind="$attrs">
|
||||
<Space.Compact orientation="vertical">
|
||||
<SpaceCompact orientation="vertical">
|
||||
<slot></slot>
|
||||
</Space.Compact>
|
||||
</SpaceCompact>
|
||||
</Space>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ function handleSelect(item: Emoji) {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<Popover placement="top" trigger="click">
|
||||
<Popover placement="top" :trigger="['click']">
|
||||
<template #content>
|
||||
<!-- TODO: antdv-next 暂不支持 List 组件,后续组件库会新增 Listy 组件替代 List -->
|
||||
<div class="h-[300px] w-[500px] overflow-auto">
|
||||
|
|
|
|||
|
|
@ -15,6 +15,11 @@ import 'dayjs/locale/zh-cn';
|
|||
dayjs.locale('zh-cn');
|
||||
|
||||
const activeTab = ref<string>('type');
|
||||
const calendarTabItems = [
|
||||
{ key: 'type', label: '按分类', forceRender: true },
|
||||
{ key: 'team', label: '按班组', forceRender: true },
|
||||
{ key: 'user', label: '按个人', forceRender: true },
|
||||
];
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
@ -26,16 +31,12 @@ const activeTab = ref<string>('type');
|
|||
/>
|
||||
</template>
|
||||
<div class="bg-card rounded-md p-3">
|
||||
<Tabs v-model:active-key="activeTab" type="card">
|
||||
<Tabs.TabPane key="type" tab="按分类" force-render>
|
||||
<TypeView />
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane key="team" tab="按班组" force-render>
|
||||
<TeamView />
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane key="user" tab="按个人" force-render>
|
||||
<UserView />
|
||||
</Tabs.TabPane>
|
||||
<Tabs v-model:active-key="activeTab" :items="calendarTabItems" type="card">
|
||||
<template #contentRender="{ item }">
|
||||
<TypeView v-if="item.key === 'type'" />
|
||||
<TeamView v-else-if="item.key === 'team'" />
|
||||
<UserView v-else-if="item.key === 'user'" />
|
||||
</template>
|
||||
</Tabs>
|
||||
</div>
|
||||
</Page>
|
||||
|
|
|
|||
|
|
@ -21,6 +21,10 @@ import PlanTeamList from './team-list.vue';
|
|||
const emit = defineEmits(['success']);
|
||||
const formType = ref<FormType>('create'); // 表单模式
|
||||
const subTabsName = ref('shift'); // 当前资源页签
|
||||
const planTabItems = [
|
||||
{ key: 'shift', label: '班次' },
|
||||
{ key: 'team', label: '班组' },
|
||||
];
|
||||
const formData = ref<MesCalPlanApi.Plan>();
|
||||
const isDetail = computed(() => formType.value === 'detail'); // 是否查看模式
|
||||
const canConfirm = computed(
|
||||
|
|
@ -131,14 +135,21 @@ const [Modal, modalApi] = useVbenModal({
|
|||
<Tabs
|
||||
v-if="formType !== 'create' && formData?.id"
|
||||
v-model:active-key="subTabsName"
|
||||
:items="planTabItems"
|
||||
class="mx-4 mt-4"
|
||||
>
|
||||
<Tabs.TabPane key="shift" tab="班次">
|
||||
<ShiftList :form-type="formType" :plan-id="formData.id" />
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane key="team" tab="班组">
|
||||
<PlanTeamList :form-type="formType" :plan-id="formData.id" />
|
||||
</Tabs.TabPane>
|
||||
<template #contentRender="{ item }">
|
||||
<ShiftList
|
||||
v-if="item.key === 'shift'"
|
||||
:form-type="formType"
|
||||
:plan-id="formData.id"
|
||||
/>
|
||||
<PlanTeamList
|
||||
v-else-if="item.key === 'team'"
|
||||
:form-type="formType"
|
||||
:plan-id="formData.id"
|
||||
/>
|
||||
</template>
|
||||
</Tabs>
|
||||
<template #prepend-footer>
|
||||
<div class="flex flex-auto items-center">
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import MemberList from './member-list.vue';
|
|||
const emit = defineEmits(['success']);
|
||||
const formType = ref<FormType>('create'); // 表单模式
|
||||
const subTabsName = ref('member'); // 当前资源页签
|
||||
const teamTabItems = [{ key: 'member', label: '班组成员' }];
|
||||
const formData = ref<MesCalTeamApi.Team>();
|
||||
const isDetail = computed(() => formType.value === 'detail'); // 是否查看模式
|
||||
const getTitle = computed(() => {
|
||||
|
|
@ -107,11 +108,16 @@ const [Modal, modalApi] = useVbenModal({
|
|||
<Tabs
|
||||
v-if="formType !== 'create' && formData?.id"
|
||||
v-model:active-key="subTabsName"
|
||||
:items="teamTabItems"
|
||||
class="mx-4 mt-4"
|
||||
>
|
||||
<Tabs.TabPane key="member" tab="班组成员">
|
||||
<MemberList :form-type="formType" :team-id="formData.id" />
|
||||
</Tabs.TabPane>
|
||||
<template #contentRender="{ item }">
|
||||
<MemberList
|
||||
v-if="item.key === 'member'"
|
||||
:form-type="formType"
|
||||
:team-id="formData.id"
|
||||
/>
|
||||
</template>
|
||||
</Tabs>
|
||||
</Modal>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -21,6 +21,10 @@ import SubjectList from './subject-list.vue';
|
|||
const emit = defineEmits(['success']);
|
||||
const formType = ref<FormType>('create');
|
||||
const subTabsName = ref('machinery');
|
||||
const checkPlanTabItems = [
|
||||
{ key: 'machinery', label: '设备' },
|
||||
{ key: 'subject', label: '项目' },
|
||||
];
|
||||
const formData = ref<MesDvCheckPlanApi.CheckPlan>();
|
||||
const isDetail = computed(() => formType.value === 'detail');
|
||||
const getTitle = computed(() => {
|
||||
|
|
@ -105,14 +109,22 @@ const [Modal, modalApi] = useVbenModal({
|
|||
<Tabs
|
||||
v-if="formType !== 'create' && formData?.id"
|
||||
v-model:active-key="subTabsName"
|
||||
:items="checkPlanTabItems"
|
||||
class="mx-4 mt-4"
|
||||
>
|
||||
<Tabs.TabPane key="machinery" tab="设备">
|
||||
<MachineryList :form-type="formType" :plan-id="formData.id" />
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane key="subject" tab="项目">
|
||||
<SubjectList :form-type="formType" :plan-id="formData.id" :plan-type="formData.type" />
|
||||
</Tabs.TabPane>
|
||||
<template #contentRender="{ item }">
|
||||
<MachineryList
|
||||
v-if="item.key === 'machinery'"
|
||||
:form-type="formType"
|
||||
:plan-id="formData.id"
|
||||
/>
|
||||
<SubjectList
|
||||
v-else-if="item.key === 'subject'"
|
||||
:form-type="formType"
|
||||
:plan-id="formData.id"
|
||||
:plan-type="formData.type"
|
||||
/>
|
||||
</template>
|
||||
</Tabs>
|
||||
</Modal>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -23,6 +23,11 @@ import RepairList from './repair-list.vue';
|
|||
const emit = defineEmits(['success']);
|
||||
const formType = ref<FormType>('create'); // 表单模式
|
||||
const subTabsName = ref('check'); // 当前资源页签
|
||||
const machineryTabItems = [
|
||||
{ key: 'check', label: '点检记录' },
|
||||
{ key: 'mainten', label: '保养记录' },
|
||||
{ key: 'repair', label: '维修记录' },
|
||||
];
|
||||
const formData = ref<MesDvMachineryApi.Machinery>();
|
||||
const barcodeDetailRef = ref<InstanceType<typeof BarcodeDetail>>(); // 条码详情弹窗
|
||||
const isDetail = computed(() => formType.value === 'detail'); // 是否查看模式
|
||||
|
|
@ -116,17 +121,23 @@ const [Modal, modalApi] = useVbenModal({
|
|||
<Tabs
|
||||
v-if="formType !== 'create' && formData?.id"
|
||||
v-model:active-key="subTabsName"
|
||||
:items="machineryTabItems"
|
||||
class="mx-4 mt-4"
|
||||
>
|
||||
<Tabs.TabPane key="check" tab="点检记录">
|
||||
<CheckRecordList :machinery-id="formData.id" />
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane key="mainten" tab="保养记录">
|
||||
<MaintenRecordList :machinery-id="formData.id" />
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane key="repair" tab="维修记录">
|
||||
<RepairList :machinery-id="formData.id" />
|
||||
</Tabs.TabPane>
|
||||
<template #contentRender="{ item }">
|
||||
<CheckRecordList
|
||||
v-if="item.key === 'check'"
|
||||
:machinery-id="formData.id"
|
||||
/>
|
||||
<MaintenRecordList
|
||||
v-else-if="item.key === 'mainten'"
|
||||
:machinery-id="formData.id"
|
||||
/>
|
||||
<RepairList
|
||||
v-else-if="item.key === 'repair'"
|
||||
:machinery-id="formData.id"
|
||||
/>
|
||||
</template>
|
||||
</Tabs>
|
||||
<template #prepend-footer>
|
||||
<div class="flex flex-auto items-center">
|
||||
|
|
|
|||
|
|
@ -20,6 +20,10 @@ import ClientProductSalesList from './product-sales-list.vue';
|
|||
const emit = defineEmits(['success']);
|
||||
const formType = ref<FormType>('create'); // 表单模式
|
||||
const subTabsName = ref('productSalesLine'); // 当前子表页签
|
||||
const clientTabItems = [
|
||||
{ key: 'productSalesLine', label: '产品清单' },
|
||||
{ key: 'productSales', label: '销售记录' },
|
||||
];
|
||||
const formData = ref<MesMdClientApi.Client>();
|
||||
|
||||
const isDetail = computed(() => formType.value === 'detail'); // 是否查看模式
|
||||
|
|
@ -100,14 +104,19 @@ const [Modal, modalApi] = useVbenModal({
|
|||
<Tabs
|
||||
v-if="formType !== 'create' && formData?.id"
|
||||
v-model:active-key="subTabsName"
|
||||
:items="clientTabItems"
|
||||
class="mx-4 mt-4"
|
||||
>
|
||||
<Tabs.TabPane key="productSalesLine" tab="产品清单">
|
||||
<ClientProductSalesLineList :client-id="formData.id" />
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane key="productSales" tab="销售记录">
|
||||
<ClientProductSalesList :client-id="formData.id" />
|
||||
</Tabs.TabPane>
|
||||
<template #contentRender="{ item }">
|
||||
<ClientProductSalesLineList
|
||||
v-if="item.key === 'productSalesLine'"
|
||||
:client-id="formData.id"
|
||||
/>
|
||||
<ClientProductSalesList
|
||||
v-else-if="item.key === 'productSales'"
|
||||
:client-id="formData.id"
|
||||
/>
|
||||
</template>
|
||||
</Tabs>
|
||||
</Modal>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -37,6 +37,13 @@ const getTitle = computed(() => {
|
|||
const currentItemOrProduct = computed(
|
||||
() => formData.value?.itemOrProduct || '',
|
||||
); // 当前物料/产品标识
|
||||
const itemTabItems = computed(() => [
|
||||
{ key: 'bom', label: 'BOM 组成' },
|
||||
...(formData.value?.batchFlag ? [{ key: 'batch', label: '批次属性' }] : []),
|
||||
{ key: 'substitute', label: '替代品' },
|
||||
{ key: 'sip', label: 'SIP' },
|
||||
{ key: 'sop', label: 'SOP' },
|
||||
]);
|
||||
|
||||
const [Form, formApi] = useVbenForm({
|
||||
commonConfig: {
|
||||
|
|
@ -128,27 +135,36 @@ const [Modal, modalApi] = useVbenModal({
|
|||
<Tabs
|
||||
v-if="formType !== 'create' && formData?.id"
|
||||
v-model:active-key="subTabsName"
|
||||
:items="itemTabItems"
|
||||
class="mx-4 mt-4"
|
||||
>
|
||||
<Tabs.TabPane key="bom" tab="BOM 组成">
|
||||
<ProductBomForm :form-type="formType" :item-id="formData.id" />
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane v-if="formData.batchFlag" key="batch" tab="批次属性">
|
||||
<template #contentRender="{ item }">
|
||||
<ProductBomForm
|
||||
v-if="item.key === 'bom'"
|
||||
:form-type="formType"
|
||||
:item-id="formData.id"
|
||||
/>
|
||||
<ItemBatchConfigForm
|
||||
v-else-if="item.key === 'batch'"
|
||||
:form-type="formType"
|
||||
:item-id="formData.id"
|
||||
:item-or-product="currentItemOrProduct"
|
||||
/>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane key="substitute" tab="替代品">
|
||||
<Empty description="替代品(待实现)" />
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane key="sip" tab="SIP">
|
||||
<ProductSipForm :form-type="formType" :item-id="formData.id" />
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane key="sop" tab="SOP">
|
||||
<ProductSopForm :form-type="formType" :item-id="formData.id" />
|
||||
</Tabs.TabPane>
|
||||
<Empty
|
||||
v-else-if="item.key === 'substitute'"
|
||||
description="替代品(待实现)"
|
||||
/>
|
||||
<ProductSipForm
|
||||
v-else-if="item.key === 'sip'"
|
||||
:form-type="formType"
|
||||
:item-id="formData.id"
|
||||
/>
|
||||
<ProductSopForm
|
||||
v-else-if="item.key === 'sop'"
|
||||
:form-type="formType"
|
||||
:item-id="formData.id"
|
||||
/>
|
||||
</template>
|
||||
</Tabs>
|
||||
<template #prepend-footer>
|
||||
<div class="flex flex-auto items-center">
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import type { MesMdUnitMeasureApi } from '#/api/mes/md/unitmeasure';
|
|||
|
||||
import { computed, onMounted, ref, watch } from 'vue';
|
||||
|
||||
import { Select, Tag, Tooltip } from 'antdv-next';
|
||||
import { Select, SelectOption, Tag, Tooltip } from 'antdv-next';
|
||||
|
||||
import { getUnitMeasureSimpleList } from '#/api/mes/md/unitmeasure';
|
||||
|
||||
|
|
@ -106,7 +106,7 @@ onMounted(async () => {
|
|||
show-search
|
||||
@change="handleChange"
|
||||
>
|
||||
<Select.Option
|
||||
<SelectOption
|
||||
v-for="item in filteredList"
|
||||
:key="item.id"
|
||||
:item="item"
|
||||
|
|
@ -116,7 +116,7 @@ onMounted(async () => {
|
|||
<span>{{ item.name }}</span>
|
||||
<Tag v-if="item.code" color="default">编号: {{ item.code }}</Tag>
|
||||
</div>
|
||||
</Select.Option>
|
||||
</SelectOption>
|
||||
</Select>
|
||||
</Tooltip>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -20,6 +20,10 @@ import VendorItemReceiptList from './item-receipt-list.vue';
|
|||
const emit = defineEmits(['success']);
|
||||
const formType = ref<FormType>('create'); // 表单模式
|
||||
const subTabsName = ref('itemReceiptLine'); // 当前子表页签
|
||||
const vendorTabItems = [
|
||||
{ key: 'itemReceiptLine', label: '物料清单' },
|
||||
{ key: 'itemReceipt', label: '采购记录' },
|
||||
];
|
||||
const formData = ref<MesMdVendorApi.Vendor>();
|
||||
|
||||
const isDetail = computed(() => formType.value === 'detail'); // 是否查看模式
|
||||
|
|
@ -102,14 +106,19 @@ const [Modal, modalApi] = useVbenModal({
|
|||
<Tabs
|
||||
v-if="formType !== 'create' && formData?.id"
|
||||
v-model:active-key="subTabsName"
|
||||
:items="vendorTabItems"
|
||||
class="mx-4 mt-4"
|
||||
>
|
||||
<Tabs.TabPane key="itemReceiptLine" tab="物料清单">
|
||||
<VendorItemReceiptLineList :vendor-id="formData.id" />
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane key="itemReceipt" tab="采购记录">
|
||||
<VendorItemReceiptList :vendor-id="formData.id" />
|
||||
</Tabs.TabPane>
|
||||
<template #contentRender="{ item }">
|
||||
<VendorItemReceiptLineList
|
||||
v-if="item.key === 'itemReceiptLine'"
|
||||
:vendor-id="formData.id"
|
||||
/>
|
||||
<VendorItemReceiptList
|
||||
v-else-if="item.key === 'itemReceipt'"
|
||||
:vendor-id="formData.id"
|
||||
/>
|
||||
</template>
|
||||
</Tabs>
|
||||
</Modal>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import { nextTick, ref } from 'vue';
|
|||
import { useVbenModal } from '@vben/common-ui';
|
||||
import { formatDateTime } from '@vben/utils';
|
||||
|
||||
import { Descriptions, Spin } from 'antdv-next';
|
||||
import { Descriptions, DescriptionsItem, Spin } from 'antdv-next';
|
||||
|
||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import { getItemReceipt } from '#/api/mes/wm/itemreceipt';
|
||||
|
|
@ -123,36 +123,36 @@ const [Modal, modalApi] = useVbenModal({
|
|||
>
|
||||
<Spin :spinning="loading">
|
||||
<Descriptions bordered size="small" :column="3">
|
||||
<Descriptions.Item label="入库单编号">
|
||||
<DescriptionsItem label="入库单编号">
|
||||
{{ formatEmpty(receipt?.code) }}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="入库单名称">
|
||||
</DescriptionsItem>
|
||||
<DescriptionsItem label="入库单名称">
|
||||
{{ formatEmpty(receipt?.name) }}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="入库日期">
|
||||
</DescriptionsItem>
|
||||
<DescriptionsItem label="入库日期">
|
||||
{{ formatDate(receipt?.receiptDate) }}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="到货通知单">
|
||||
</DescriptionsItem>
|
||||
<DescriptionsItem label="到货通知单">
|
||||
{{ formatEmpty(receipt?.noticeCode) }}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="供应商">
|
||||
</DescriptionsItem>
|
||||
<DescriptionsItem label="供应商">
|
||||
{{ formatEmpty(receipt?.vendorName) }}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="采购订单号">
|
||||
</DescriptionsItem>
|
||||
<DescriptionsItem label="采购订单号">
|
||||
{{ formatEmpty(receipt?.purchaseOrderCode) }}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="仓库">
|
||||
</DescriptionsItem>
|
||||
<DescriptionsItem label="仓库">
|
||||
{{ formatEmpty(receipt?.warehouseName) }}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="库区">
|
||||
</DescriptionsItem>
|
||||
<DescriptionsItem label="库区">
|
||||
{{ formatEmpty(receipt?.locationName) }}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="库位">
|
||||
</DescriptionsItem>
|
||||
<DescriptionsItem label="库位">
|
||||
{{ formatEmpty(receipt?.areaName) }}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="备注" :span="3">
|
||||
</DescriptionsItem>
|
||||
<DescriptionsItem label="备注" :span="3">
|
||||
{{ formatEmpty(receipt?.remark) }}
|
||||
</Descriptions.Item>
|
||||
</DescriptionsItem>
|
||||
</Descriptions>
|
||||
<div class="mt-4">
|
||||
<Grid table-title="入库物料" />
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import type { MesMdWorkshopApi } from '#/api/mes/md/workstation/workshop';
|
|||
|
||||
import { computed, onMounted, ref, watch } from 'vue';
|
||||
|
||||
import { Select, Tag, Tooltip } from 'antdv-next';
|
||||
import { Select, SelectOption, Tag, Tooltip } from 'antdv-next';
|
||||
|
||||
import { getWorkshopSimpleList } from '#/api/mes/md/workstation/workshop';
|
||||
|
||||
|
|
@ -101,7 +101,7 @@ onMounted(async () => {
|
|||
show-search
|
||||
@change="handleChange"
|
||||
>
|
||||
<Select.Option
|
||||
<SelectOption
|
||||
v-for="item in allList"
|
||||
:key="item.id"
|
||||
:item="item"
|
||||
|
|
@ -111,7 +111,7 @@ onMounted(async () => {
|
|||
<span>{{ item.name }}</span>
|
||||
<Tag v-if="item.code" color="default">{{ item.code }}</Tag>
|
||||
</div>
|
||||
</Select.Option>
|
||||
</SelectOption>
|
||||
</Select>
|
||||
</Tooltip>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -27,6 +27,11 @@ import WorkerList from './worker-list.vue';
|
|||
const emit = defineEmits(['success']);
|
||||
const formType = ref<FormType>('create'); // 表单模式
|
||||
const subTabsName = ref('machine'); // 当前资源页签
|
||||
const workstationTabItems = [
|
||||
{ key: 'machine', label: '设备资源' },
|
||||
{ key: 'tool', label: '工装夹具' },
|
||||
{ key: 'worker', label: '人力资源' },
|
||||
];
|
||||
const formData = ref<MesMdWorkstationApi.Workstation>();
|
||||
const barcodeDetailRef = ref<InstanceType<typeof BarcodeDetail>>(); // 条码详情弹窗
|
||||
|
||||
|
|
@ -127,17 +132,26 @@ const [Modal, modalApi] = useVbenModal({
|
|||
<Tabs
|
||||
v-if="formType !== 'create' && formData?.id"
|
||||
v-model:active-key="subTabsName"
|
||||
:items="workstationTabItems"
|
||||
class="mx-4 mt-4"
|
||||
>
|
||||
<Tabs.TabPane key="machine" tab="设备资源">
|
||||
<MachineList :form-type="formType" :workstation-id="formData.id" />
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane key="tool" tab="工装夹具">
|
||||
<ToolList :form-type="formType" :workstation-id="formData.id" />
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane key="worker" tab="人力资源">
|
||||
<WorkerList :form-type="formType" :workstation-id="formData.id" />
|
||||
</Tabs.TabPane>
|
||||
<template #contentRender="{ item }">
|
||||
<MachineList
|
||||
v-if="item.key === 'machine'"
|
||||
:form-type="formType"
|
||||
:workstation-id="formData.id"
|
||||
/>
|
||||
<ToolList
|
||||
v-else-if="item.key === 'tool'"
|
||||
:form-type="formType"
|
||||
:workstation-id="formData.id"
|
||||
/>
|
||||
<WorkerList
|
||||
v-else-if="item.key === 'worker'"
|
||||
:form-type="formType"
|
||||
:workstation-id="formData.id"
|
||||
/>
|
||||
</template>
|
||||
</Tabs>
|
||||
<template #prepend-footer>
|
||||
<div class="flex flex-auto items-center">
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { computed, onMounted, ref } from 'vue';
|
|||
|
||||
import { DICT_TYPE } from '@vben/constants';
|
||||
|
||||
import { Select } from 'antdv-next';
|
||||
import { Select, SelectOption } from 'antdv-next';
|
||||
|
||||
import { getAndonConfigList } from '#/api/mes/pro/andon/config';
|
||||
import DictTag from '#/components/dict-tag/dict-tag.vue';
|
||||
|
|
@ -73,7 +73,7 @@ onMounted(async () => {
|
|||
show-search
|
||||
@change="handleChange"
|
||||
>
|
||||
<Select.Option
|
||||
<SelectOption
|
||||
v-for="item in allList"
|
||||
:key="item.id"
|
||||
:item="item"
|
||||
|
|
@ -83,6 +83,6 @@ onMounted(async () => {
|
|||
<span>{{ item.reason }}</span>
|
||||
<DictTag :type="DICT_TYPE.MES_PRO_ANDON_LEVEL" :value="item.level" />
|
||||
</div>
|
||||
</Select.Option>
|
||||
</SelectOption>
|
||||
</Select>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -53,6 +53,10 @@ const showSubTabs = computed(
|
|||
formData.value?.status !== MesProFeedbackStatusEnum.PREPARE &&
|
||||
formData.value?.status !== MesProFeedbackStatusEnum.APPROVING,
|
||||
);
|
||||
const feedbackTabItems = [
|
||||
{ key: 'itemConsume', label: 'BOM 物资消耗' },
|
||||
{ key: 'productProduce', label: '产品产出' },
|
||||
];
|
||||
const getTitle = computed(() => {
|
||||
if (formType.value === 'detail') {
|
||||
return $t('ui.actionTitle.view', ['生产报工']);
|
||||
|
|
@ -274,15 +278,20 @@ const [Modal, modalApi] = useVbenModal({
|
|||
<Tabs
|
||||
v-if="showSubTabs"
|
||||
v-model:active-key="subTabsName"
|
||||
:items="feedbackTabItems"
|
||||
type="card"
|
||||
class="mx-4 mt-2"
|
||||
>
|
||||
<Tabs.TabPane key="itemConsume" tab="BOM 物资消耗">
|
||||
<ItemConsumeList :feedback-id="formData!.id!" />
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane key="productProduce" tab="产品产出">
|
||||
<ProductProduceList :feedback-id="formData!.id!" />
|
||||
</Tabs.TabPane>
|
||||
<template #contentRender="{ item }">
|
||||
<ItemConsumeList
|
||||
v-if="item.key === 'itemConsume'"
|
||||
:feedback-id="formData!.id!"
|
||||
/>
|
||||
<ProductProduceList
|
||||
v-else-if="item.key === 'productProduce'"
|
||||
:feedback-id="formData!.id!"
|
||||
/>
|
||||
</template>
|
||||
</Tabs>
|
||||
<template #prepend-footer>
|
||||
<div class="flex flex-auto items-center justify-end gap-2">
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import type { MesProProcessApi } from '#/api/mes/pro/process';
|
|||
|
||||
import { computed, onMounted, ref, watch } from 'vue';
|
||||
|
||||
import { Select, Tag, Tooltip } from 'antdv-next';
|
||||
import { Select, SelectOption, Tag, Tooltip } from 'antdv-next';
|
||||
|
||||
import { getProcessSimpleList } from '#/api/mes/pro/process';
|
||||
|
||||
|
|
@ -98,7 +98,7 @@ onMounted(async () => {
|
|||
show-search
|
||||
@change="handleChange"
|
||||
>
|
||||
<Select.Option
|
||||
<SelectOption
|
||||
v-for="item in allList"
|
||||
:key="item.id"
|
||||
:item="item"
|
||||
|
|
@ -108,7 +108,7 @@ onMounted(async () => {
|
|||
<span>{{ item.name }}</span>
|
||||
<Tag v-if="item.code" color="default">{{ item.code }}</Tag>
|
||||
</div>
|
||||
</Select.Option>
|
||||
</SelectOption>
|
||||
</Select>
|
||||
</Tooltip>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -34,6 +34,10 @@ const formType = ref<FormType>('create');
|
|||
const formData = ref<MesProWorkOrderApi.WorkOrder>();
|
||||
const originalSnapshot = ref(''); // 表单原始数据快照,用于确认时跳过未变更的保存请求
|
||||
const subTabsName = ref('bom'); // 当前选中的子表 Tab
|
||||
const workOrderTabItems = [
|
||||
{ key: 'bom', label: '工单 BOM' },
|
||||
{ key: 'item', label: '物料需求' },
|
||||
];
|
||||
const barcodeDetailRef = ref<InstanceType<typeof BarcodeDetail>>(); // 条码详情弹窗
|
||||
|
||||
const isEditable = computed(() => // 是否为编辑模式(可保存)
|
||||
|
|
@ -252,18 +256,24 @@ const [Modal, modalApi] = useVbenModal({
|
|||
<Form class="mx-4" />
|
||||
<!-- BOM / 物料需求 Tab:非新建态展示 -->
|
||||
<template v-if="formData?.id">
|
||||
<Tabs v-model:active-key="subTabsName" class="mx-4">
|
||||
<Tabs.TabPane key="bom" tab="工单 BOM">
|
||||
<Tabs
|
||||
v-model:active-key="subTabsName"
|
||||
:items="workOrderTabItems"
|
||||
class="mx-4"
|
||||
>
|
||||
<template #contentRender="{ item }">
|
||||
<BomList
|
||||
v-if="item.key === 'bom'"
|
||||
:form-type="formType"
|
||||
:work-order="formData"
|
||||
:work-order-id="formData.id"
|
||||
@generate-work-order="handleGenerateWorkOrder"
|
||||
/>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane key="item" tab="物料需求">
|
||||
<ItemList :work-order-id="formData.id" />
|
||||
</Tabs.TabPane>
|
||||
<ItemList
|
||||
v-else-if="item.key === 'item'"
|
||||
:work-order-id="formData.id"
|
||||
/>
|
||||
</template>
|
||||
</Tabs>
|
||||
</template>
|
||||
<template #prepend-footer>
|
||||
|
|
|
|||
|
|
@ -14,6 +14,10 @@ import TraceList from './trace-list.vue';
|
|||
|
||||
const detailData = ref<MesWmBatchApi.Batch>(); // 当前批次详情
|
||||
const subTabsName = ref<'backward' | 'forward'>('forward'); // 当前激活的追溯方向
|
||||
const traceTabItems = [
|
||||
{ key: 'forward', label: '向前追溯' },
|
||||
{ key: 'backward', label: '向后追溯' },
|
||||
];
|
||||
|
||||
const [Descriptions] = useDescription({
|
||||
bordered: true,
|
||||
|
|
@ -44,15 +48,22 @@ const [Modal, modalApi] = useVbenModal({
|
|||
<Tabs
|
||||
v-if="detailData?.code"
|
||||
v-model:active-key="subTabsName"
|
||||
:items="traceTabItems"
|
||||
class="mx-4 mt-4"
|
||||
type="card"
|
||||
>
|
||||
<Tabs.TabPane key="forward" tab="向前追溯">
|
||||
<TraceList :batch-code="detailData.code" direction="forward" />
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane key="backward" tab="向后追溯">
|
||||
<TraceList :batch-code="detailData.code" direction="backward" />
|
||||
</Tabs.TabPane>
|
||||
<template #contentRender="{ item }">
|
||||
<TraceList
|
||||
v-if="item.key === 'forward'"
|
||||
:batch-code="detailData.code"
|
||||
direction="forward"
|
||||
/>
|
||||
<TraceList
|
||||
v-else-if="item.key === 'backward'"
|
||||
:batch-code="detailData.code"
|
||||
direction="backward"
|
||||
/>
|
||||
</template>
|
||||
</Tabs>
|
||||
</Modal>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import { getDictOptions } from '@vben/hooks';
|
|||
import {
|
||||
Form as AForm,
|
||||
Divider,
|
||||
FormItem,
|
||||
Input,
|
||||
InputNumber,
|
||||
message,
|
||||
|
|
@ -97,7 +98,7 @@ const [Modal, modalApi] = useVbenModal({
|
|||
item.valueType === MesQcResultValueType.INTEGER
|
||||
) {
|
||||
submit.value =
|
||||
item.valueNumber == null ? undefined : String(item.valueNumber);
|
||||
item.valueNumber === null ? undefined : String(item.valueNumber);
|
||||
} else {
|
||||
submit.value = item.value;
|
||||
}
|
||||
|
|
@ -148,7 +149,7 @@ const [Modal, modalApi] = useVbenModal({
|
|||
valueNumber:
|
||||
(item.valueType === MesQcResultValueType.FLOAT ||
|
||||
item.valueType === MesQcResultValueType.INTEGER) &&
|
||||
item.value != null
|
||||
item.value !== null
|
||||
? Number(item.value)
|
||||
: undefined,
|
||||
}));
|
||||
|
|
@ -178,9 +179,7 @@ const [Modal, modalApi] = useVbenModal({
|
|||
:key="item.indicatorId ?? index"
|
||||
class="mb-2"
|
||||
>
|
||||
<AForm.Item
|
||||
:label="`检测项${index + 1}:${item.indicatorName ?? ''}`"
|
||||
>
|
||||
<FormItem :label="`检测项${index + 1}:${item.indicatorName ?? ''}`">
|
||||
<InputNumber
|
||||
v-if="
|
||||
item.valueType === MesQcResultValueType.FLOAT ||
|
||||
|
|
@ -210,7 +209,7 @@ const [Modal, modalApi] = useVbenModal({
|
|||
placeholder="请输入文件地址"
|
||||
/>
|
||||
<Input v-else v-model:value="item.value" placeholder="请输入" />
|
||||
</AForm.Item>
|
||||
</FormItem>
|
||||
</div>
|
||||
</AForm>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import { computed, ref } from 'vue';
|
|||
import { confirm, useVbenModal } from '@vben/common-ui';
|
||||
import { MesQcStatusEnum, MesQcTypeEnum } from '@vben/constants';
|
||||
|
||||
import { Button, Descriptions, message, Tabs } from 'antdv-next';
|
||||
import { Button, Descriptions, DescriptionsItem, message, Tabs } from 'antdv-next';
|
||||
|
||||
import { useVbenForm } from '#/adapter/form';
|
||||
import {
|
||||
|
|
@ -27,6 +27,10 @@ const emit = defineEmits(['success']);
|
|||
const formType = ref<FormType>('create');
|
||||
const formData = ref<MesQcIpqcApi.Ipqc>();
|
||||
const subTabsName = ref('line');
|
||||
const qcTabItems = [
|
||||
{ key: 'line', label: '检验项' },
|
||||
{ key: 'result', label: '检测结果' },
|
||||
];
|
||||
const originalSnapshot = ref(''); // 表单原始数据快照,用于 finish 时跳过未变更的保存请求
|
||||
const isDetail = computed(() => formType.value === 'detail');
|
||||
const canFinish = computed(
|
||||
|
|
@ -188,45 +192,46 @@ const [Modal, modalApi] = useVbenModal({
|
|||
<!-- 缺陷统计(只读) -->
|
||||
<div v-if="formData?.id" class="mx-4 mt-4">
|
||||
<Descriptions title="缺陷情况" :column="3" bordered size="small">
|
||||
<Descriptions.Item label="致命缺陷数">
|
||||
<DescriptionsItem label="致命缺陷数">
|
||||
{{ formData.criticalQuantity ?? 0 }}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="严重缺陷数">
|
||||
</DescriptionsItem>
|
||||
<DescriptionsItem label="严重缺陷数">
|
||||
{{ formData.majorQuantity ?? 0 }}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="轻微缺陷数">
|
||||
</DescriptionsItem>
|
||||
<DescriptionsItem label="轻微缺陷数">
|
||||
{{ formData.minorQuantity ?? 0 }}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="致命缺陷率">
|
||||
</DescriptionsItem>
|
||||
<DescriptionsItem label="致命缺陷率">
|
||||
{{ formData.criticalRate ?? 0 }}%
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="严重缺陷率">
|
||||
</DescriptionsItem>
|
||||
<DescriptionsItem label="严重缺陷率">
|
||||
{{ formData.majorRate ?? 0 }}%
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="轻微缺陷率">
|
||||
</DescriptionsItem>
|
||||
<DescriptionsItem label="轻微缺陷率">
|
||||
{{ formData.minorRate ?? 0 }}%
|
||||
</Descriptions.Item>
|
||||
</DescriptionsItem>
|
||||
</Descriptions>
|
||||
</div>
|
||||
<Tabs
|
||||
v-if="formData?.id"
|
||||
v-model:active-key="subTabsName"
|
||||
:items="qcTabItems"
|
||||
class="mx-4 mt-4"
|
||||
>
|
||||
<Tabs.TabPane key="line" tab="检验项">
|
||||
<template #contentRender="{ item }">
|
||||
<LineList
|
||||
v-if="item.key === 'line'"
|
||||
:form-type="formType"
|
||||
:ipqc-id="formData.id"
|
||||
@refresh="handleRefresh"
|
||||
/>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane key="result" tab="检测结果">
|
||||
<QcIndicatorResultList
|
||||
v-else-if="item.key === 'result'"
|
||||
:qc-id="formData.id"
|
||||
:qc-type="MesQcTypeEnum.IPQC"
|
||||
:readonly="isDetail"
|
||||
/>
|
||||
</Tabs.TabPane>
|
||||
</template>
|
||||
</Tabs>
|
||||
<template #prepend-footer>
|
||||
<div class="flex flex-auto items-center gap-2">
|
||||
|
|
|
|||
|
|
@ -11,17 +11,13 @@ import { MesQcStatusEnum, MesQcTypeEnum } from '@vben/constants';
|
|||
import {
|
||||
Button,
|
||||
Descriptions,
|
||||
DescriptionsItem,
|
||||
message,
|
||||
Tabs,
|
||||
} from 'antdv-next';
|
||||
|
||||
import { useVbenForm } from '#/adapter/form';
|
||||
import {
|
||||
createIqc,
|
||||
finishIqc,
|
||||
getIqc,
|
||||
updateIqc,
|
||||
} from '#/api/mes/qc/iqc';
|
||||
import { createIqc, finishIqc, getIqc, updateIqc } from '#/api/mes/qc/iqc';
|
||||
import { $t } from '#/locales';
|
||||
|
||||
import { QcIndicatorResultList } from '../../indicatorresult/components';
|
||||
|
|
@ -32,6 +28,10 @@ const emit = defineEmits(['success']);
|
|||
const formType = ref<FormType>('create');
|
||||
const formData = ref<MesQcIqcApi.Iqc>();
|
||||
const subTabsName = ref('line');
|
||||
const qcTabItems = [
|
||||
{ key: 'line', label: '检验项' },
|
||||
{ key: 'result', label: '检测结果' },
|
||||
];
|
||||
const originalSnapshot = ref(''); // 表单原始数据快照,用于 finish 时跳过未变更的保存请求
|
||||
const isDetail = computed(() => formType.value === 'detail');
|
||||
const canFinish = computed(
|
||||
|
|
@ -194,45 +194,46 @@ const [Modal, modalApi] = useVbenModal({
|
|||
<!-- 缺陷统计(只读) -->
|
||||
<div v-if="formData?.id" class="mx-4 mt-4">
|
||||
<Descriptions title="缺陷情况" :column="3" bordered size="small">
|
||||
<Descriptions.Item label="致命缺陷数">
|
||||
<DescriptionsItem label="致命缺陷数">
|
||||
{{ formData.criticalQuantity ?? 0 }}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="严重缺陷数">
|
||||
</DescriptionsItem>
|
||||
<DescriptionsItem label="严重缺陷数">
|
||||
{{ formData.majorQuantity ?? 0 }}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="轻微缺陷数">
|
||||
</DescriptionsItem>
|
||||
<DescriptionsItem label="轻微缺陷数">
|
||||
{{ formData.minorQuantity ?? 0 }}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="致命缺陷率">
|
||||
</DescriptionsItem>
|
||||
<DescriptionsItem label="致命缺陷率">
|
||||
{{ formData.criticalRate ?? 0 }}%
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="严重缺陷率">
|
||||
</DescriptionsItem>
|
||||
<DescriptionsItem label="严重缺陷率">
|
||||
{{ formData.majorRate ?? 0 }}%
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="轻微缺陷率">
|
||||
</DescriptionsItem>
|
||||
<DescriptionsItem label="轻微缺陷率">
|
||||
{{ formData.minorRate ?? 0 }}%
|
||||
</Descriptions.Item>
|
||||
</DescriptionsItem>
|
||||
</Descriptions>
|
||||
</div>
|
||||
<Tabs
|
||||
v-if="formData?.id"
|
||||
v-model:active-key="subTabsName"
|
||||
:items="qcTabItems"
|
||||
class="mx-4 mt-4"
|
||||
>
|
||||
<Tabs.TabPane key="line" tab="检验项">
|
||||
<template #contentRender="{ item }">
|
||||
<LineList
|
||||
v-if="item.key === 'line'"
|
||||
:form-type="formType"
|
||||
:iqc-id="formData.id"
|
||||
@refresh="handleRefresh"
|
||||
/>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane key="result" tab="检测结果">
|
||||
<QcIndicatorResultList
|
||||
v-else-if="item.key === 'result'"
|
||||
:qc-id="formData.id"
|
||||
:qc-type="MesQcTypeEnum.IQC"
|
||||
:readonly="isDetail"
|
||||
/>
|
||||
</Tabs.TabPane>
|
||||
</template>
|
||||
</Tabs>
|
||||
<template #prepend-footer>
|
||||
<div class="flex flex-auto items-center gap-2">
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import { computed, ref } from 'vue';
|
|||
import { confirm, useVbenModal } from '@vben/common-ui';
|
||||
import { MesQcStatusEnum, MesQcTypeEnum } from '@vben/constants';
|
||||
|
||||
import { Button, Descriptions, message, Tabs } from 'antdv-next';
|
||||
import { Button, Descriptions, DescriptionsItem, message, Tabs } from 'antdv-next';
|
||||
|
||||
import { useVbenForm } from '#/adapter/form';
|
||||
import {
|
||||
|
|
@ -27,6 +27,10 @@ const emit = defineEmits(['success']);
|
|||
const formType = ref<FormType>('create');
|
||||
const formData = ref<MesQcOqcApi.Oqc>();
|
||||
const subTabsName = ref('line');
|
||||
const qcTabItems = [
|
||||
{ key: 'line', label: '检验项' },
|
||||
{ key: 'result', label: '检测结果' },
|
||||
];
|
||||
const originalSnapshot = ref(''); // 表单原始数据快照,用于 finish 时跳过未变更的保存请求
|
||||
const isDetail = computed(() => formType.value === 'detail');
|
||||
const canFinish = computed(
|
||||
|
|
@ -192,45 +196,46 @@ const [Modal, modalApi] = useVbenModal({
|
|||
<!-- 缺陷统计(只读) -->
|
||||
<div v-if="formData?.id" class="mx-4 mt-4">
|
||||
<Descriptions title="缺陷情况" :column="3" bordered size="small">
|
||||
<Descriptions.Item label="致命缺陷数">
|
||||
<DescriptionsItem label="致命缺陷数">
|
||||
{{ formData.criticalQuantity ?? 0 }}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="严重缺陷数">
|
||||
</DescriptionsItem>
|
||||
<DescriptionsItem label="严重缺陷数">
|
||||
{{ formData.majorQuantity ?? 0 }}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="轻微缺陷数">
|
||||
</DescriptionsItem>
|
||||
<DescriptionsItem label="轻微缺陷数">
|
||||
{{ formData.minorQuantity ?? 0 }}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="致命缺陷率">
|
||||
</DescriptionsItem>
|
||||
<DescriptionsItem label="致命缺陷率">
|
||||
{{ formData.criticalRate ?? 0 }}%
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="严重缺陷率">
|
||||
</DescriptionsItem>
|
||||
<DescriptionsItem label="严重缺陷率">
|
||||
{{ formData.majorRate ?? 0 }}%
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="轻微缺陷率">
|
||||
</DescriptionsItem>
|
||||
<DescriptionsItem label="轻微缺陷率">
|
||||
{{ formData.minorRate ?? 0 }}%
|
||||
</Descriptions.Item>
|
||||
</DescriptionsItem>
|
||||
</Descriptions>
|
||||
</div>
|
||||
<Tabs
|
||||
v-if="formData?.id"
|
||||
v-model:active-key="subTabsName"
|
||||
:items="qcTabItems"
|
||||
class="mx-4 mt-4"
|
||||
>
|
||||
<Tabs.TabPane key="line" tab="检验项">
|
||||
<template #contentRender="{ item }">
|
||||
<LineList
|
||||
v-if="item.key === 'line'"
|
||||
:form-type="formType"
|
||||
:oqc-id="formData.id"
|
||||
@refresh="handleRefresh"
|
||||
/>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane key="result" tab="检测结果">
|
||||
<QcIndicatorResultList
|
||||
v-else-if="item.key === 'result'"
|
||||
:qc-id="formData.id"
|
||||
:qc-type="MesQcTypeEnum.OQC"
|
||||
:readonly="isDetail"
|
||||
/>
|
||||
</Tabs.TabPane>
|
||||
</template>
|
||||
</Tabs>
|
||||
<template #prepend-footer>
|
||||
<div class="flex flex-auto items-center gap-2">
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import { computed, ref } from 'vue';
|
|||
import { confirm, useVbenModal } from '@vben/common-ui';
|
||||
import { MesQcStatusEnum, MesQcTypeEnum } from '@vben/constants';
|
||||
|
||||
import { Button, Descriptions, message, Tabs } from 'antdv-next';
|
||||
import { Button, Descriptions, DescriptionsItem, message, Tabs } from 'antdv-next';
|
||||
|
||||
import { useVbenForm } from '#/adapter/form';
|
||||
import {
|
||||
|
|
@ -27,6 +27,10 @@ const emit = defineEmits(['success']);
|
|||
const formType = ref<FormType>('create');
|
||||
const formData = ref<MesQcRqcApi.Rqc>();
|
||||
const subTabsName = ref('line');
|
||||
const qcTabItems = [
|
||||
{ key: 'line', label: '检验项' },
|
||||
{ key: 'result', label: '检测结果' },
|
||||
];
|
||||
const originalSnapshot = ref(''); // 表单原始数据快照,用于 finish 时跳过未变更的保存请求
|
||||
const isDetail = computed(() => formType.value === 'detail');
|
||||
const canFinish = computed(
|
||||
|
|
@ -188,45 +192,46 @@ const [Modal, modalApi] = useVbenModal({
|
|||
<!-- 缺陷统计(只读) -->
|
||||
<div v-if="formData?.id" class="mx-4 mt-4">
|
||||
<Descriptions title="缺陷情况" :column="3" bordered size="small">
|
||||
<Descriptions.Item label="致命缺陷数">
|
||||
<DescriptionsItem label="致命缺陷数">
|
||||
{{ formData.criticalQuantity ?? 0 }}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="严重缺陷数">
|
||||
</DescriptionsItem>
|
||||
<DescriptionsItem label="严重缺陷数">
|
||||
{{ formData.majorQuantity ?? 0 }}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="轻微缺陷数">
|
||||
</DescriptionsItem>
|
||||
<DescriptionsItem label="轻微缺陷数">
|
||||
{{ formData.minorQuantity ?? 0 }}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="致命缺陷率">
|
||||
</DescriptionsItem>
|
||||
<DescriptionsItem label="致命缺陷率">
|
||||
{{ formData.criticalRate ?? 0 }}%
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="严重缺陷率">
|
||||
</DescriptionsItem>
|
||||
<DescriptionsItem label="严重缺陷率">
|
||||
{{ formData.majorRate ?? 0 }}%
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="轻微缺陷率">
|
||||
</DescriptionsItem>
|
||||
<DescriptionsItem label="轻微缺陷率">
|
||||
{{ formData.minorRate ?? 0 }}%
|
||||
</Descriptions.Item>
|
||||
</DescriptionsItem>
|
||||
</Descriptions>
|
||||
</div>
|
||||
<Tabs
|
||||
v-if="formData?.id"
|
||||
v-model:active-key="subTabsName"
|
||||
:items="qcTabItems"
|
||||
class="mx-4 mt-4"
|
||||
>
|
||||
<Tabs.TabPane key="line" tab="检验项">
|
||||
<template #contentRender="{ item }">
|
||||
<LineList
|
||||
v-if="item.key === 'line'"
|
||||
:form-type="formType"
|
||||
:rqc-id="formData.id"
|
||||
@refresh="handleRefresh"
|
||||
/>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane key="result" tab="检测结果">
|
||||
<QcIndicatorResultList
|
||||
v-else-if="item.key === 'result'"
|
||||
:qc-id="formData.id"
|
||||
:qc-type="MesQcTypeEnum.RQC"
|
||||
:readonly="isDetail"
|
||||
/>
|
||||
</Tabs.TabPane>
|
||||
</template>
|
||||
</Tabs>
|
||||
<template #prepend-footer>
|
||||
<div class="flex flex-auto items-center gap-2">
|
||||
|
|
|
|||
|
|
@ -24,6 +24,10 @@ import ItemList from './item-list.vue';
|
|||
const emit = defineEmits(['success']);
|
||||
const formType = ref<FormType>('create'); // 弹窗形态:新增/编辑/详情
|
||||
const subTabsName = ref('indicator'); // 当前激活的子表 tab
|
||||
const templateTabItems = [
|
||||
{ key: 'indicator', label: '检测指标项' },
|
||||
{ key: 'item', label: '产品关联' },
|
||||
];
|
||||
const formData = ref<MesQcTemplateApi.Template>();
|
||||
|
||||
const getTitle = computed(() => {
|
||||
|
|
@ -105,20 +109,21 @@ const [Modal, modalApi] = useVbenModal({
|
|||
<Tabs
|
||||
v-if="formType !== 'create' && formData?.id"
|
||||
v-model:active-key="subTabsName"
|
||||
:items="templateTabItems"
|
||||
class="mx-4 mt-4"
|
||||
>
|
||||
<Tabs.TabPane key="indicator" tab="检测指标项">
|
||||
<template #contentRender="{ item }">
|
||||
<IndicatorList
|
||||
v-if="item.key === 'indicator'"
|
||||
:readonly="formType === 'detail'"
|
||||
:template-id="formData.id"
|
||||
/>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane key="item" tab="产品关联">
|
||||
<ItemList
|
||||
v-else-if="item.key === 'item'"
|
||||
:readonly="formType === 'detail'"
|
||||
:template-id="formData.id"
|
||||
/>
|
||||
</Tabs.TabPane>
|
||||
</template>
|
||||
</Tabs>
|
||||
</Modal>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -27,6 +27,10 @@ const emit = defineEmits(['success']);
|
|||
const formType = ref<FormType>('create');
|
||||
const formData = ref<MesWmPackageApi.Package>();
|
||||
const subTabsName = ref('subPackage'); // 子表当前 tab
|
||||
const packageTabItems = [
|
||||
{ key: 'subPackage', label: '子箱' },
|
||||
{ key: 'packageLine', label: '装箱清单' },
|
||||
];
|
||||
const originalSnapshot = ref(''); // 表单原始数据快照,用于 finish 时跳过未变更的保存请求
|
||||
|
||||
const isEditable = computed(() =>
|
||||
|
|
@ -173,14 +177,21 @@ const [Modal, modalApi] = useVbenModal({
|
|||
<Tabs
|
||||
v-if="formData?.id"
|
||||
v-model:active-key="subTabsName"
|
||||
:items="packageTabItems"
|
||||
class="mx-4 mt-4"
|
||||
>
|
||||
<Tabs.TabPane key="subPackage" tab="子箱">
|
||||
<SubPackageList :editable="isEditable" :package-id="formData.id" />
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane key="packageLine" tab="装箱清单">
|
||||
<PackageLineList :editable="isEditable" :package-id="formData.id" />
|
||||
</Tabs.TabPane>
|
||||
<template #contentRender="{ item }">
|
||||
<SubPackageList
|
||||
v-if="item.key === 'subPackage'"
|
||||
:editable="isEditable"
|
||||
:package-id="formData.id"
|
||||
/>
|
||||
<PackageLineList
|
||||
v-else-if="item.key === 'packageLine'"
|
||||
:editable="isEditable"
|
||||
:package-id="formData.id"
|
||||
/>
|
||||
</template>
|
||||
</Tabs>
|
||||
<template #prepend-footer>
|
||||
<div class="flex flex-auto items-center gap-2">
|
||||
|
|
|
|||
|
|
@ -49,6 +49,10 @@ const showResultTab = computed(
|
|||
(!!formData.value?.status &&
|
||||
formData.value.status !== MesWmStockTakingTaskStatusEnum.PREPARE),
|
||||
);
|
||||
const stockTakingTabItems = computed(() => [
|
||||
...(showLineTab.value ? [{ key: 'lines', label: '盘点清单' }] : []),
|
||||
...(showResultTab.value ? [{ key: 'results', label: '盘点结果' }] : []),
|
||||
]);
|
||||
const getTitle = computed(() => {
|
||||
if (formType.value === 'detail') {
|
||||
return $t('ui.actionTitle.view', ['盘点任务']);
|
||||
|
|
@ -191,18 +195,22 @@ const [Modal, modalApi] = useVbenModal({
|
|||
<Tabs
|
||||
v-if="formData?.id"
|
||||
v-model:active-key="subTabsName"
|
||||
:items="stockTakingTabItems"
|
||||
class="mx-4 mt-4"
|
||||
type="card"
|
||||
>
|
||||
<Tabs.TabPane v-if="showLineTab" key="lines" tab="盘点清单">
|
||||
<LineList :form-type="formType" :task-id="formData.id" />
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane v-if="showResultTab" key="results" tab="盘点结果">
|
||||
<template #contentRender="{ item }">
|
||||
<LineList
|
||||
v-if="item.key === 'lines'"
|
||||
:form-type="formType"
|
||||
:task-id="formData.id"
|
||||
/>
|
||||
<ResultList
|
||||
v-else-if="item.key === 'results'"
|
||||
:form-type="isExecute ? 'execute' : 'detail'"
|
||||
:task-id="formData.id"
|
||||
/>
|
||||
</Tabs.TabPane>
|
||||
</template>
|
||||
</Tabs>
|
||||
<template #prepend-footer>
|
||||
<div class="flex flex-auto items-center gap-2">
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import {
|
|||
Input,
|
||||
message,
|
||||
Modal,
|
||||
TextArea,
|
||||
Upload,
|
||||
} from 'antdv-next';
|
||||
|
||||
|
|
@ -149,7 +150,7 @@ const customRequest: UploadProps['customRequest'] = async function (options) {
|
|||
/>
|
||||
</FormItem>
|
||||
<FormItem label="描述" name="introduction">
|
||||
<Input.TextArea
|
||||
<TextArea
|
||||
v-model:value="uploadData.introduction"
|
||||
:rows="3"
|
||||
placeholder="介绍语将展示在相关播放页面,建议填写简洁明确、有信息量的内容"
|
||||
|
|
|
|||
Loading…
Reference in New Issue