Commit 2de737be3c77b1963c516e4adb29a45f127bcf9b

Authored by fengwotao
2 parents 08d2a04e 56943d39

chore: 合并main_dev分支导致的冲突

Showing 100 changed files with 2903 additions and 216 deletions
... ... @@ -8,8 +8,12 @@ VITE_GLOB_PUBLIC_PATH = /
8 8 # Please note that no line breaks
9 9
10 10 # 本地
  11 +<<<<<<< HEAD
11 12 <<<<<<< Updated upstream
12 13 VITE_PROXY = [["/api","http://192.168.10.146:8080/api"],["/thingskit-scada","http://localhost:3000/"],["/large-designer", "http://localhost:5555/large-designer/"]]
  14 +=======
  15 +VITE_PROXY = [["/api","http://localhost:8080/api"],["/thingskit-scada","http://localhost:5173/thingskit-scada"],["/large-designer", "http://localhost:5555/large-designer/"]]
  16 +>>>>>>> main_dev
13 17
14 18 # 实时数据的ws地址
15 19 VITE_GLOB_WEB_SOCKET = ws://192.168.10.146:8080/api/ws/plugins/telemetry?token=
... ... @@ -48,7 +52,7 @@ VITE_GLOB_ALARM_NOTIFY_POLLING_INTERVAL_TIME = 500000
48 52 VITE_GLOB_ALARM_NOTIFY_DURATION = 5
49 53
50 54 # Should Disabled Task Center Execute Interval Unit (Second)
51   -VITE_GLOB_DISABLED_TASK_CENTER_EXECUTE_INTERVAL_UNIT_SECOND = false
  55 +VITE_GLOB_DISABLED_TASK_CENTER_EXECUTE_INTERVAL_UNIT_SECOND = true
52 56
53 57 # Software version number
54   -VITE_GLOB_SOFTWARE_VERSION_NUMBER = 123
  58 +VITE_GLOB_SOFTWARE_VERSION_NUMBER = ThingsKit v1.2.0_release
... ...
... ... @@ -16,11 +16,11 @@ VITE_GLOB_BUILD_COMPRESS = 'gzip'
16 16 VITE_GLOB_BUILD_COMPRESS_DELETE_ORIGIN_FILE = false
17 17
18 18 # Basic interface address SPA
19   -VITE_GLOB_API_URL=http://localhost:8080/api
  19 +VITE_GLOB_API_URL=/api
20 20
21 21 # File upload address, optional
22 22 # It can be forwarded by nginx or write the actual address directly
23   -VITE_GLOB_UPLOAD_URL=http://localhost:8080/api/yt/oss/upload
  23 +VITE_GLOB_UPLOAD_URL=/api/yt/oss/upload
24 24
25 25 # Interface prefix
26 26 VITE_GLOB_API_URL_PREFIX=/yt
... ... @@ -56,4 +56,4 @@ VITE_GLOB_ALARM_NOTIFY_DURATION = 5
56 56 VITE_GLOB_DISABLED_TASK_CENTER_EXECUTE_INTERVAL_UNIT_SECOND = false
57 57
58 58 # software version number
59   -VITE_GLOB_SOFTWARE_VERSION_NUMBER = 123
  59 +VITE_GLOB_SOFTWARE_VERSION_NUMBER = ThingsKit v1.2.0_release
... ...
... ... @@ -9,6 +9,7 @@
9 9 "clazz",
10 10 "Cmds",
11 11 "COAP",
  12 + "drawio",
12 13 "echarts",
13 14 "edrx",
14 15 "EFENTO",
... ...
  1 +版本:v1.1.2_release
  2 +
1 3 ## 准备
2 4
3 5 - [node](http://nodejs.org/) 和 [git](https://git-scm.com/) -项目开发环境
... ...
... ... @@ -2,6 +2,7 @@ import { DeviceProfileModel } from '../../device/model/deviceModel';
2 2 import { HistoryData } from './model';
3 3 import { defHttp } from '/@/utils/http/axios';
4 4 import { isString } from '/@/utils/is';
  5 +import { OrderByEnum } from '/@/views/device/localtion/cpns/TimePeriodForm/config';
5 6
6 7 // 获取设备配置
7 8 export const getDeviceProfile = (deviceType?: string) => {
... ... @@ -12,11 +13,11 @@ export const getDeviceProfile = (deviceType?: string) => {
12 13 };
13 14
14 15 // 获取历史数据
15   -export const getDeviceHistoryInfo = (params: Recordable, orderBy?: string) => {
  16 +export const getDeviceHistoryInfo = (params: Recordable, orderBy = OrderByEnum.DESC) => {
16 17 return defHttp.get<HistoryData>(
17 18 {
18 19 url: `/plugins/telemetry/DEVICE/${params.entityId}/values/timeseries`,
19   - params: { ...params, entityId: null, orderBy: orderBy || 'DESC' },
  20 + params: { orderBy, ...params, entityId: null },
20 21 },
21 22 {
22 23 joinPrefix: false,
... ...
... ... @@ -119,3 +119,11 @@ export const closeFlvPlay = (url: string, browserId: string) => {
119 119 url: `/rtsp/closeFlv?url=${encodeURIComponent(url)}&browserId=${browserId}`,
120 120 });
121 121 };
  122 +
  123 +//云台控制
  124 +export const controlling = (params: any) => {
  125 + return defHttp.get({
  126 + url: `/video/controlling`,
  127 + params,
  128 + });
  129 +};
... ...
... ... @@ -8,10 +8,13 @@ export interface ConfigurationCenterItemsModal {
8 8 remark: string;
9 9 publicId?: string;
10 10 organizationId?: string;
  11 + platform?: string;
  12 + productIds?: string;
11 13 }
12 14 export type queryPageParams = BasicPageParams & {
13 15 name?: Nullable<string>;
14 16 organizationId?: Nullable<number>;
  17 + isTemplate?: number;
15 18 };
16 19
17 20 export interface ConfigurationModal {
... ...
... ... @@ -45,7 +45,7 @@ enum DeviceManagerApi {
45 45 DEVICE_PUBLIC = '/customer/public/device',
46 46
47 47 DEVICE_PRIVATE = '/customer/device',
48   - DEVICE_COLLECT = '/device/collect ', //收藏
  48 + DEVICE_COLLECT = '/collect/DEVICE_COLLECT', //收藏
49 49
50 50 /**
51 51 * @description 通过设备列表获取设备信息
... ... @@ -62,7 +62,7 @@ export const devicePage = (params: DeviceQueryParam) => {
62 62 });
63 63 };
64 64
65   -export const deviceCollect = (params: Array<any>) => {
  65 +export const deviceCollect = (params: string[]) => {
66 66 return defHttp.post({
67 67 url: `${DeviceManagerApi.DEVICE_COLLECT}`,
68 68 params,
... ...
1 1 import { StructJSON } from './modelOfMatterModel';
2 2 import { BasicPageParams } from '/@/api/model/baseModel';
3   -import { AlarmStatus } from '/@/views/alarm/log/config/detail.config';
  3 +import { AlarmStatus } from '/@/enums/alarmEnum';
4 4 import { DeviceStatusEnum } from '/@/views/rule/dataFlow/cpns/config';
5 5 export enum DeviceState {
6 6 INACTIVE = 'INACTIVE',
... ...
... ... @@ -151,6 +151,7 @@
151 151 defineExpose({
152 152 get,
153 153 set,
  154 + handleFormat,
154 155 });
155 156 </script>
156 157
... ...
... ... @@ -41,6 +41,7 @@ import ApiSelectScrollLoad from './components/ApiSelectScrollLoad.vue';
41 41 import InputGroup from './components/InputGroup.vue';
42 42 import RegisterAddressInput from '/@/views/task/center/components/PollCommandInput/RegisterAddressInput.vue';
43 43 import ExtendDesc from '/@/components/Form/src/externalCompns/components/ExtendDesc/index.vue';
  44 +import DeviceProfileForm from '/@/components/Form/src/externalCompns/components/DeviceProfileForm/index.vue';
44 45
45 46 const componentMap = new Map<ComponentType, Component>();
46 47
... ... @@ -89,6 +90,7 @@ componentMap.set('ApiSelectScrollLoad', ApiSelectScrollLoad);
89 90 componentMap.set('InputGroup', InputGroup);
90 91 componentMap.set('RegisterAddressInput', RegisterAddressInput);
91 92 componentMap.set('ExtendDesc', ExtendDesc);
  93 +componentMap.set('DeviceProfileForm', DeviceProfileForm);
92 94
93 95 export function add(compName: ComponentType, component: Component) {
94 96 componentMap.set(compName, component);
... ...
... ... @@ -22,11 +22,13 @@
22 22 value?: string;
23 23 disabled?: boolean;
24 24 validateStatus?: boolean;
  25 + scriptType?: string;
25 26 }>(),
26 27 {
27 28 functionName: 'method',
28 29 paramsName: () => [],
29 30 height: 200,
  31 + scriptType: 'filter',
30 32 value: '',
31 33 }
32 34 );
... ...
  1 +<template>
  2 + <div v-for="(param, index) in dynamicInput.params" :key="index" style="display: flex">
  3 + <a-input placeholder="产品" v-model:value="param.label" :disabled="true" />
  4 + <Select
  5 + placeholder="请选择设备"
  6 + v-model:value="param.value"
  7 + style="width: 100%"
  8 + v-bind="createPickerSearch()"
  9 + mode="multiple"
  10 + labelInValue
  11 + />
  12 + <MinusCircleOutlined
  13 + v-if="dynamicInput.params.length > min && !disabled"
  14 + class="dynamic-delete-button"
  15 + @click="remove(param)"
  16 + style="width: 50px"
  17 + />
  18 + </div>
  19 +</template>
  20 +<script lang="ts" name="DeviceProfileForm">
  21 + import { MinusCircleOutlined } from '@ant-design/icons-vue';
  22 + import { defineComponent, reactive, UnwrapRef, watchEffect } from 'vue';
  23 + import { propTypes } from '/@/utils/propTypes';
  24 + import { isEmpty } from '/@/utils/is';
  25 + import { Select } from 'ant-design-vue';
  26 + import { createPickerSearch } from '/@/utils/pickerSearch';
  27 +
  28 + interface Params {
  29 + label: string;
  30 + value: string;
  31 + }
  32 +
  33 + export default defineComponent({
  34 + name: 'DeviceProfileForm',
  35 + components: {
  36 + MinusCircleOutlined,
  37 + Select,
  38 + },
  39 + //--------------不继承Antd Design Vue Input的所有属性 否则控制台报大片警告--------------
  40 + inheritAttrs: false,
  41 + props: {
  42 + value: propTypes.object.def({}),
  43 + //自定义删除按钮多少才会显示
  44 + min: propTypes.integer.def(0),
  45 + disabled: {
  46 + type: Boolean,
  47 + default: false,
  48 + },
  49 + },
  50 + emits: ['change', 'update:value'],
  51 + setup(props, { emit }) {
  52 + //input动态数据
  53 + const dynamicInput: UnwrapRef<{ params: Params[] }> = reactive({ params: [] });
  54 +
  55 + //删除Input
  56 + const remove = (item: Params) => {
  57 + let index = dynamicInput.params.indexOf(item);
  58 + if (index !== -1) {
  59 + dynamicInput.params.splice(index, 1);
  60 + }
  61 + emitChange();
  62 + };
  63 +
  64 + //监听传入数据value
  65 + watchEffect(() => {
  66 + initVal();
  67 + });
  68 +
  69 + /**
  70 + * 初始化数值
  71 + */
  72 + function initVal() {
  73 + dynamicInput.params = [];
  74 + if (props.value) {
  75 + let jsonObj = props.value;
  76 + Object.keys(jsonObj).forEach((key) => {
  77 + dynamicInput.params.push({ label: key, value: jsonObj[key] });
  78 + });
  79 + }
  80 + }
  81 +
  82 + /**
  83 + * 数值改变
  84 + */
  85 + function emitChange() {
  86 + let obj = {};
  87 + if (dynamicInput.params.length > 0) {
  88 + dynamicInput.params.forEach((item) => {
  89 + obj[item.label] = item.value;
  90 + });
  91 + }
  92 + emit('change', isEmpty(obj) ? '' : obj);
  93 + emit('update:value', isEmpty(obj) ? '' : obj);
  94 + }
  95 +
  96 + return {
  97 + dynamicInput,
  98 + emitChange,
  99 + remove,
  100 + createPickerSearch,
  101 + };
  102 + },
  103 + });
  104 +</script>
  105 +<style scoped>
  106 + .dynamic-delete-button {
  107 + cursor: pointer;
  108 + position: relative;
  109 + top: 4px;
  110 + font-size: 24px;
  111 + color: #999;
  112 + transition: all 0.3s;
  113 + }
  114 +
  115 + .dynamic-delete-button:hover {
  116 + color: #777;
  117 + }
  118 +
  119 + .dynamic-delete-button[disabled] {
  120 + cursor: not-allowed;
  121 + opacity: 0.5;
  122 + }
  123 +</style>
... ...
... ... @@ -22,9 +22,11 @@
22 22 value: ModelOfMatterParams[];
23 23 disabled: boolean;
24 24 hasStructForm?: boolean;
  25 + hiddenAccessMode?: boolean;
25 26 }>(),
26 27 {
27 28 value: () => [],
  29 + hiddenAccessMode: false,
28 30 hasStructForm: false,
29 31 }
30 32 );
... ... @@ -106,11 +108,12 @@
106 108 <span class="mr-2">
107 109 <PlusOutlined />
108 110 </span>
109   - <span @click="!$props.disabled && handleCreateParams()">增加参数</span>
  111 + <span @click="!disabled && handleCreateParams()">增加参数</span>
110 112 </div>
111 113 </div>
112 114 <StructFormModel
113   - :has-struct-form="$props.hasStructForm!"
  115 + :has-struct-form="hasStructForm!"
  116 + :hidden-access-mode="hiddenAccessMode"
114 117 :disabled="$props.disabled"
115 118 :value-list="getValue"
116 119 @register="registerModal"
... ...
... ... @@ -23,6 +23,7 @@
23 23 disabled: boolean;
24 24 hasStructForm: boolean;
25 25 valueList: StructRecord[];
  26 + hiddenAccessMode: boolean;
26 27 }>();
27 28
28 29 const emit = defineEmits(['register', 'submit']);
... ... @@ -31,7 +32,7 @@
31 32
32 33 const [register, { validate, setFieldsValue, setProps }] = useForm({
33 34 labelWidth: 100,
34   - schemas: formSchemas(props.hasStructForm),
  35 + schemas: formSchemas(props.hasStructForm, props.hiddenAccessMode),
35 36 actionColOptions: {
36 37 span: 14,
37 38 },
... ...
... ... @@ -28,7 +28,11 @@ export const validateJSON = (_rule, value = [] as ModelOfMatterParams[], _callba
28 28 return Promise.reject('JSON对象不能为空');
29 29 };
30 30
31   -export const formSchemas = (hasStructForm: boolean, isTcp = false): FormSchema[] => {
  31 +export const formSchemas = (
  32 + hasStructForm: boolean,
  33 + hiddenAccessMode: boolean,
  34 + isTcp = false
  35 +): FormSchema[] => {
32 36 return [
33 37 {
34 38 field: FormField.FUNCTION_NAME,
... ... @@ -299,7 +303,7 @@ export const formSchemas = (hasStructForm: boolean, isTcp = false): FormSchema[]
299 303 colProps: {
300 304 span: 24,
301 305 },
302   - ifShow: () => !hasStructForm,
  306 + ifShow: () => !hiddenAccessMode && !hasStructForm,
303 307 defaultValue: 'r',
304 308 componentProps: {
305 309 placeholder: '请选择读写类型',
... ...
... ... @@ -137,4 +137,5 @@ export type ComponentType =
137 137 | 'CorrelationFilters'
138 138 | 'RelationsQuery'
139 139 | 'CredentialsCard'
140   - | 'ApiComplete';
  140 + | 'ApiComplete'
  141 + | 'DeviceProfileForm';
... ...
1 1 <script lang="ts" setup>
2 2 import { QuestionCircleOutlined } from '@ant-design/icons-vue';
3 3 import { Tooltip } from 'ant-design-vue';
  4 + import { usePermission } from '/@/hooks/web/usePermission';
  5 + import { UserDropDownItemEnum } from './user-dropdown/config';
4 6
5 7 const handleJump = () => {
6 8 open('https://docs.thingskit.com');
7 9 };
  10 +
  11 + const { hasPermission } = usePermission();
8 12 </script>
9 13
10 14 <template>
11 15 <Tooltip title="帮助文档">
12   - <QuestionCircleOutlined class="text-base cursor-pointer" @click="handleJump" />
  16 + <QuestionCircleOutlined
  17 + v-if="hasPermission(UserDropDownItemEnum.ABOUT_SOFTWARE)"
  18 + class="text-base cursor-pointer"
  19 + @click="handleJump"
  20 + />
13 21 </Tooltip>
14 22 </template>
... ...
  1 +/**
  2 + * 系统右上角下拉选择项权限标识key枚举值
  3 + */
  4 +
  5 +export const enum UserDropDownItemEnum {
  6 + FORGOT_PASSWORD = 'system:password:view', //忘记密码权限标识key
  7 + ABOUT_SOFTWARE = 'system:about_software:view', //关于软件权限标识key
  8 +}
... ...
... ... @@ -17,11 +17,13 @@
17 17 icon="ion:document-text-outline"
18 18 />
19 19 <MenuItem
  20 + v-if="hasPermission(UserDropDownItemEnum.FORGOT_PASSWORD)"
20 21 key="changePassword"
21 22 :text="t('layout.header.dropdownItemChangePassword')"
22 23 icon="ant-design:unlock-twotone"
23 24 />
24 25 <MenuItem
  26 + v-if="hasPermission(UserDropDownItemEnum.ABOUT_SOFTWARE)"
25 27 key="aboutSoftware"
26 28 :text="handleDecode(t('routes.aboutSoftware.aboutSoftware'))"
27 29 icon="ant-design:message-outline"
... ... @@ -67,6 +69,7 @@
67 69 import AboutSoftwareModal from '../AboutSoftwareModal.vue';
68 70 import { AesEncryption } from '/@/utils/cipher';
69 71 import { cacheCipher } from '/@/settings/encryptionSetting';
  72 + import { UserDropDownItemEnum } from './config';
70 73
71 74 type MenuEvent = 'logout' | 'doc' | 'lock' | 'personal' | 'changePassword' | 'aboutSoftware';
72 75
... ... @@ -187,6 +190,7 @@
187 190 getUseLockPage,
188 191 hasPermission,
189 192 registerModal,
  193 + UserDropDownItemEnum,
190 194 };
191 195 },
192 196 });
... ...
... ... @@ -78,6 +78,8 @@
78 78 </PageWrapper>
79 79 <CameraDrawer @register="registerDrawer" @success="handleSuccess" />
80 80 <VideoPreviewModal @register="registerModal" />
  81 +
  82 + <VideoModal @register="registerModal1" />
81 83 </div>
82 84 </template>
83 85
... ... @@ -96,6 +98,7 @@
96 98 import { useBatchDelete } from '/@/hooks/web/useBatchDelete';
97 99 import { Popconfirm } from 'ant-design-vue';
98 100 import { Tag } from 'ant-design-vue';
  101 + import VideoModal from '/@/views/device/list/cpns/tabs/VideoChannel/videoModal.vue';
99 102
100 103 export default defineComponent({
101 104 components: {
... ... @@ -105,6 +108,7 @@
105 108 TableAction,
106 109 CameraDrawer,
107 110 VideoPreviewModal,
  111 + VideoModal,
108 112 TableImg,
109 113 Authority,
110 114 Popconfirm,
... ... @@ -114,7 +118,9 @@
114 118 setup(_, { emit }) {
115 119 const searchInfo = reactive<Recordable>({});
116 120 const { organizationIdTreeRef, resetFn } = useResetOrganizationTree(searchInfo);
117   - const [registerModal, { openModal }] = useModal();
  121 + const [registerModal, { openModal }] = useModal(); //手动输入
  122 +
  123 + const [registerModal1, { openModal: openModal1 }] = useModal(); //流媒体获取
118 124 // 表格hooks
119 125 const [registerTable, { reload, setProps, clearSelectedRowKeys }] = useTable({
120 126 title: '视频列表',
... ... @@ -176,7 +182,14 @@
176 182 handleSuccess();
177 183 };
178 184 const handleViewVideo = (record) => {
179   - openModal(true, {
  185 + if (record.accessMode === AccessMode.ManuallyEnter) {
  186 + openModal(true, {
  187 + isUpdate: true,
  188 + record,
  189 + });
  190 + return;
  191 + }
  192 + openModal1(true, {
180 193 isUpdate: true,
181 194 record,
182 195 });
... ... @@ -198,6 +211,7 @@
198 211 organizationIdTreeRef,
199 212 handleViewVideo,
200 213 registerModal,
  214 + registerModal1,
201 215 AccessMode,
202 216 handleSwitchMode,
203 217 CameraPermission,
... ...
... ... @@ -7,36 +7,97 @@
7 7 width="30%"
8 8 @ok="handleSubmit"
9 9 >
10   - <BasicForm @register="registerForm" />
  10 + <BasicForm @register="registerForm">
  11 + <!-- 模板选择 -->
  12 + <template #templateId="{ model }">
  13 + <Select
  14 + v-model:value="model['templateId']"
  15 + placeholder="请选择模板"
  16 + style="width: 100%"
  17 + :options="selectTemplateOptions"
  18 + @change="handleTemplateChange"
  19 + v-bind="createPickerSearch()"
  20 + :disabled="templateDisabled"
  21 + />
  22 + </template>
  23 + <!-- 产品选择 -->
  24 + <template #productIds="{ model }">
  25 + <SelectDeviceProfile
  26 + v-if="model['templateId']"
  27 + ref="selectDeviceProfileRef"
  28 + :selectOptions="selectOptions"
  29 + :organizationId="model?.['organizationId']"
  30 + />
  31 + </template>
  32 + </BasicForm>
11 33 </BasicDrawer>
12 34 </template>
13 35 <script lang="ts">
14   - import { defineComponent, ref, computed, unref } from 'vue';
  36 + import { defineComponent, ref, computed, unref, Ref, onMounted, nextTick } from 'vue';
15 37 import { BasicForm, useForm } from '/@/components/Form';
  38 + import { Select } from 'ant-design-vue';
16 39 import { formSchema, PC_DEFAULT_CONTENT, PHONE_DEFAULT_CONTENT, Platform } from './center.data';
17 40 import { BasicDrawer, useDrawerInner } from '/@/components/Drawer';
18 41 import { useMessage } from '/@/hooks/web/useMessage';
19   - import { saveOrUpdateConfigurationCenter } from '/@/api/configuration/center/configurationCenter';
  42 + import {
  43 + saveOrUpdateConfigurationCenter,
  44 + getPage,
  45 + } from '/@/api/configuration/center/configurationCenter';
20 46 import { FileItem } from '/@/components/Form/src/components/ApiUpload.vue';
21 47 import { buildUUID } from '/@/utils/uuid';
  48 + import SelectDeviceProfile from './components/SelectDeviceProfile.vue';
  49 + import { createPickerSearch } from '/@/utils/pickerSearch';
  50 + import type { queryPageParams } from '/@/api/configuration/center/model/configurationCenterModal';
  51 + import { getDeviceProfile } from '/@/api/alarm/position';
22 52
23 53 export default defineComponent({
24 54 name: 'ConfigurationDrawer',
25   - components: { BasicDrawer, BasicForm },
  55 + components: { BasicDrawer, BasicForm, SelectDeviceProfile, Select },
26 56 emits: ['success', 'register'],
27 57 setup(_, { emit }) {
28 58 const isUpdate = ref(true);
29 59
30   - const [registerForm, { validate, setFieldsValue, resetFields }] = useForm({
  60 + const selectDeviceProfileRef = ref<InstanceType<typeof SelectDeviceProfile>>();
  61 +
  62 + const [registerForm, { validate, setFieldsValue, resetFields, updateSchema }] = useForm({
31 63 labelWidth: 120,
32 64 schemas: formSchema,
33 65 showActionButtonGroup: false,
34 66 });
35 67
  68 + const updateEnableTemplate = async (enable: number, disabled: boolean) => {
  69 + await setFieldsValue({
  70 + enableTemplate: enable,
  71 + });
  72 + await updateSchema({
  73 + field: 'enableTemplate',
  74 + componentProps: ({ formActionType }) => {
  75 + return {
  76 + disabled,
  77 + checkedValue: 1,
  78 + unCheckedValue: 0,
  79 + checkedChildren: '开',
  80 + unCheckedChildren: '关',
  81 + onChange: () => {
  82 + formActionType.setFieldsValue({
  83 + productIds: [],
  84 + templateId: null,
  85 + productId: undefined,
  86 + });
  87 + },
  88 + };
  89 + },
  90 + });
  91 + };
  92 +
36 93 const [registerDrawer, { setDrawerProps, closeDrawer }] = useDrawerInner(async (data) => {
  94 + await nextTick();
37 95 await resetFields();
38 96 setDrawerProps({ confirmLoading: false });
39 97 isUpdate.value = !!data?.isUpdate;
  98 + selectDeviceProfileRef.value?.retValue();
  99 + await updateEnableTemplate(0, false);
  100 + templateDisabled.value = false;
40 101 if (unref(isUpdate)) {
41 102 if (data.record.thumbnail) {
42 103 data.record.thumbnail = [
... ... @@ -49,9 +110,66 @@
49 110 Reflect.deleteProperty(data.record, 'organizationId');
50 111 await setFieldsValue(data.record);
51 112 }
  113 + // 业务 编辑如果有templateId 则启用模板开关禁用,模板不能选择,产品和设备可以选择
  114 + if (Reflect.get(data.record, 'templateId')) {
  115 + await updateEnableTemplate(1, true);
  116 + templateDisabled.value = true;
  117 + //回显产品和设备
  118 + const { productAndDevice } = data.record;
  119 + selectOptions.value = productAndDevice?.map((item) => ({
  120 + label: item.name,
  121 + value: item.profileId,
  122 + }));
  123 + selectDeviceProfileRef.value?.setValue(productAndDevice);
  124 + } else {
  125 + const productAndDevice = data.record['productAndDevice'];
  126 + setFieldsValue({
  127 + productId: productAndDevice?.map((item) => item.profileId),
  128 + });
  129 + }
52 130 }
53 131 });
54 132
  133 + //新增修改
  134 + const templateDisabled = ref(false);
  135 + onMounted(() => {
  136 + getTemplate({
  137 + page: 1,
  138 + pageSize: 30,
  139 + });
  140 + });
  141 +
  142 + const selectTemplateOptions: Ref<any[]> = ref([]);
  143 + const getTemplate = async (params: queryPageParams) => {
  144 + const { items } = await getPage({ ...params, isTemplate: 1 });
  145 + selectTemplateOptions.value = items.map((item) => ({
  146 + ...item,
  147 + label: item.name,
  148 + value: item.id,
  149 + }));
  150 + };
  151 + const selectOptions: Ref<any[]> = ref([]);
  152 +
  153 + const handleTemplateChange = async (_, option) => {
  154 + const { productAndDevice } = option;
  155 + // if (!productAndDevice) return;
  156 + // selectOptions.value = productAndDevice?.map((item) => ({
  157 + // label: item.profileName || item.name,
  158 + // value: item.profileId,
  159 + // }));
  160 + await nextTick();
  161 + // 赋值
  162 + selectDeviceProfileRef.value?.setFieldsValue(
  163 + productAndDevice?.map((item) => ({
  164 + label: item.profileName || item.name,
  165 + value: item.profileId,
  166 + transportType: item?.transportType,
  167 + deviceType: item?.deviceType,
  168 + }))
  169 + );
  170 + };
  171 + //
  172 +
55 173 const getTitle = computed(() => (!unref(isUpdate) ? '新增组态中心' : '编辑组态中心'));
56 174
57 175 const getDefaultContent = (platform: Platform) => {
... ... @@ -61,10 +179,49 @@
61 179 return PHONE_DEFAULT_CONTENT;
62 180 };
63 181
  182 + // 获取产品
  183 + const getCurrentAllProduct = async (products: string[]) => {
  184 + const resp = (await getDeviceProfile()) as any;
  185 + if (!resp) return;
  186 + const values = resp?.map((item) => ({
  187 + name: item.name,
  188 + profileId: item.id,
  189 + deviceType: item.deviceType,
  190 + transportType: item.transportType,
  191 + }));
  192 + return values.filter((item) => products?.includes(item.profileId));
  193 + };
  194 +
64 195 async function handleSubmit() {
65 196 try {
66 197 const { createMessage } = useMessage();
67 198 const values = await validate();
  199 + if (!values) return;
  200 + const selectDevice = selectDeviceProfileRef.value?.getSelectDevice();
  201 + if (values['enableTemplate'] === 1) {
  202 + if (!values['templateId']) return createMessage.error('未选择模板');
  203 + // 业务 启用模板,产品和设备必填
  204 + if (values['templateId']) {
  205 + values.templateId = values['templateId'];
  206 + if (Array.isArray(selectDevice) && selectDevice.length == 0)
  207 + return createMessage.error('您已经选择了模板,但产品或者设备未选择');
  208 + if (
  209 + selectDevice?.some(
  210 + (item) =>
  211 + !item.name ||
  212 + item.deviceList.includes('' || undefined) ||
  213 + item.deviceList.length == 0
  214 + )
  215 + ) {
  216 + return createMessage.error('您已经选择了模板,但产品或者设备未选择');
  217 + }
  218 + values.productAndDevice = selectDevice;
  219 + }
  220 + } else {
  221 + const { productId } = values;
  222 + values.productAndDevice = await getCurrentAllProduct(productId);
  223 + Reflect.deleteProperty(values, 'productId');
  224 + }
68 225 if (Reflect.has(values, 'thumbnail')) {
69 226 const file = (values.thumbnail || []).at(0) || {};
70 227 values.thumbnail = file.url || null;
... ... @@ -87,6 +244,12 @@
87 244 registerDrawer,
88 245 registerForm,
89 246 handleSubmit,
  247 + selectOptions,
  248 + selectTemplateOptions,
  249 + handleTemplateChange,
  250 + createPickerSearch,
  251 + selectDeviceProfileRef,
  252 + templateDisabled,
90 253 };
91 254 },
92 255 });
... ...
... ... @@ -4,6 +4,8 @@ import { createImgPreview } from '/@/components/Preview';
4 4 import { uploadThumbnail } from '/@/api/configuration/center/configurationCenter';
5 5 import { useComponentRegister } from '/@/components/Form';
6 6 import { OrgTreeSelect } from '../../common/OrgTreeSelect';
  7 +import { getDeviceProfile } from '/@/api/alarm/position';
  8 +import { buildUUID } from '/@/utils/uuid';
7 9
8 10 useComponentRegister('OrgTreeSelect', OrgTreeSelect);
9 11 export enum Platform {
... ... @@ -21,11 +23,9 @@ export enum ConfigurationPermission {
21 23 UN_SHARE = 'api:yt:configuration:center:monopoly',
22 24 }
23 25
24   -export const PC_DEFAULT_CONTENT =
25   - '<mxfile><diagram>dZHBDsIgDIafhvuEzOh5Tr142sEzGXWQsHVhmKFP7xbAidMT5fv/UtoSVrTuZHgvLyhAE5oJR9iBUMrybT4dM3l4stnTzJPGKBHYAir1hACj7a4EDInRImqr+hTW2HVQ24RxY3BMbTfUadWeN7ACVc31ml6VsPK7jVk4g2pkLJ3tgtLy6A5gkFzg+IFYSVhhEK2PWleAnscXB+Pzjn/U988MdPZHwhQsb0+XZEesfAE=</diagram></mxfile>';
  26 +export const PC_DEFAULT_CONTENT = `<mxfile><diagram id="${buildUUID()}">dZHBDsIgDIafhvuEzOh5Tr142sEzGXWQsHVhmKFP7xbAidMT5fv/UtoSVrTuZHgvLyhAE5oJR9iBUMrybT4dM3l4stnTzJPGKBHYAir1hACj7a4EDInRImqr+hTW2HVQ24RxY3BMbTfUadWeN7ACVc31ml6VsPK7jVk4g2pkLJ3tgtLy6A5gkFzg+IFYSVhhEK2PWleAnscXB+Pzjn/U988MdPZHwhQsb0+XZEesfAE=</diagram></mxfile>`;
26 27
27   -export const PHONE_DEFAULT_CONTENT =
28   - '<mxfile><diagram>dZHBEoIgEEC/hru6lXU2q0snD50Z2YQZdB2k0fr6dMCMsU4sb9+ysDDI6uFseCuvJFCzJBIDgyNLkjjdw7hM5OnIYRc5UBklvLSAQr3Qw1l7KIFdIFoibVUbwpKaBksbMG4M9aF2Jx12bXmFK1CUXK/pTQkrHd3E24VfUFXSd04hdYmaz65/SCe5oP4LQc4gM0TWRfWQoZ5mN4/F1Z3+ZD/3MtjYHwVjsJw9boIPgvwN</diagram></mxfile>';
  28 +export const PHONE_DEFAULT_CONTENT = `<mxfile><diagram id="${buildUUID()}">dZHBEoIgEEC/hru6lXU2q0snD50Z2YQZdB2k0fr6dMCMsU4sb9+ysDDI6uFseCuvJFCzJBIDgyNLkjjdw7hM5OnIYRc5UBklvLSAQr3Qw1l7KIFdIFoibVUbwpKaBksbMG4M9aF2Jx12bXmFK1CUXK/pTQkrHd3E24VfUFXSd04hdYmaz65/SCe5oP4LQc4gM0TWRfWQoZ5mN4/F1Z3+ZD/3MtjYHwVjsJw9boIPgvwN</diagram></mxfile>`;
29 29 // 表格列数据
30 30 export const columns: BasicColumn[] = [
31 31 {
... ... @@ -82,6 +82,7 @@ export const searchFormSchema: FormSchema[] = [
82 82 },
83 83 ];
84 84
  85 +// 表单
85 86 export const formSchema: FormSchema[] = [
86 87 {
87 88 field: 'thumbnail',
... ... @@ -111,14 +112,9 @@ export const formSchema: FormSchema[] = [
111 112 onPreview: (fileList: FileItem) => {
112 113 createImgPreview({ imageList: [fileList.url!] });
113 114 },
114   - // showUploadList: {
115   - // showDownloadIcon: true,
116   - // showRemoveIcon: true,
117   - // },
118 115 };
119 116 },
120 117 },
121   -
122 118 {
123 119 field: 'name',
124 120 label: '组态名称',
... ... @@ -150,6 +146,79 @@ export const formSchema: FormSchema[] = [
150 146 },
151 147 },
152 148 {
  149 + field: 'enableTemplate', //前端控制
  150 + label: '启用模版',
  151 + component: 'Switch',
  152 + defaultValue: 0,
  153 + componentProps: ({ formActionType }) => {
  154 + const { setFieldsValue } = formActionType;
  155 +
  156 + return {
  157 + checkedValue: 1,
  158 + unCheckedValue: 0,
  159 + checkedChildren: '开',
  160 + unCheckedChildren: '关',
  161 + onChange: () => {
  162 + setFieldsValue({
  163 + productIds: [],
  164 + templateId: null,
  165 + productId: undefined,
  166 + });
  167 + },
  168 + };
  169 + },
  170 + },
  171 + {
  172 + field: 'isTemplate',
  173 + label: '默认启用模版',
  174 + component: 'Switch',
  175 + defaultValue: 0,
  176 + componentProps: {
  177 + checkedValue: 1,
  178 + unCheckedValue: 0,
  179 + },
  180 + show: false,
  181 + },
  182 + {
  183 + field: 'templateId', //暂且使用插槽形式
  184 + label: '模板',
  185 + component: 'Input',
  186 + required: true,
  187 + slot: 'templateId',
  188 + colProps: { span: 24 },
  189 + ifShow: ({ values }) => values['enableTemplate'] === 1,
  190 + },
  191 + {
  192 + field: 'productIds', //暂且使用插槽形式
  193 + label: '产品',
  194 + component: 'Input',
  195 + slot: 'productIds',
  196 + colProps: { span: 24 },
  197 + ifShow: ({ values }) => values['enableTemplate'] === 1 && values['templateId'],
  198 + },
  199 + {
  200 + field: 'productId',
  201 + label: '产品',
  202 + component: 'ApiSelect',
  203 + required: true,
  204 + componentProps: {
  205 + api: getDeviceProfile,
  206 + mode: 'multiple',
  207 + labelField: 'name',
  208 + valueField: 'id',
  209 + placeholder: '请选择产品',
  210 + getPopupContainer: (triggerNode) => triggerNode.parentNode,
  211 + filterOption: (inputValue: string, option: Record<'label' | 'value', string>) => {
  212 + let { label, value } = option;
  213 + label = label.toLowerCase();
  214 + value = value.toLowerCase();
  215 + inputValue = inputValue.toLowerCase();
  216 + return label.includes(inputValue) || value.includes(inputValue);
  217 + },
  218 + },
  219 + ifShow: ({ values }) => values['enableTemplate'] === 0,
  220 + },
  221 + {
153 222 field: 'remark',
154 223 label: '备注',
155 224 component: 'InputTextArea',
... ...
  1 +<template>
  2 + <div v-for="param in dynamicInput.params" :key="param.name" class="mt-4 flex gap-2">
  3 + <a-input :disabled="true" v-model:value="param.name" class="w-1/2 flex-1" />
  4 + <Select
  5 + placeholder="请选择设备"
  6 + v-model:value="param.deviceList"
  7 + class="!w-1/2"
  8 + :options="selectOptions"
  9 + v-bind="createPickerSearch()"
  10 + @change="emitChange"
  11 + mode="multiple"
  12 + allowClear
  13 + />
  14 + </div>
  15 +</template>
  16 +<script lang="ts">
  17 + export default {
  18 + inheritAttrs: false,
  19 + };
  20 +</script>
  21 +<script lang="ts" setup name="SelectAttributes">
  22 + import { reactive, UnwrapRef, watchEffect, ref } from 'vue';
  23 + import { propTypes } from '/@/utils/propTypes';
  24 + import { Select } from 'ant-design-vue';
  25 + import { createPickerSearch } from '/@/utils/pickerSearch';
  26 + import { byOrganizationIdGetMasterDevice } from '/@/api/ruleengine/ruleengineApi';
  27 +
  28 + const props = defineProps({
  29 + value: propTypes.object.def({}),
  30 + organizationId: {
  31 + type: String,
  32 + required: true,
  33 + },
  34 + });
  35 +
  36 + const selectOptions: any = ref([]);
  37 +
  38 + //动态数据
  39 + const dynamicInput: UnwrapRef<{ params: any[] }> = reactive({ params: [] });
  40 +
  41 + const initVal = async () => {
  42 + if (props.value) {
  43 + if (props.organizationId) {
  44 + const resp = await byOrganizationIdGetMasterDevice({
  45 + organizationId: props.organizationId,
  46 + deviceProfileId: props.value.value,
  47 + });
  48 + selectOptions.value = resp.map((item) => ({
  49 + ...item,
  50 + label: item.alias || item.name,
  51 + value: item.tbDeviceId,
  52 + }));
  53 + }
  54 + dynamicInput.params.push({
  55 + name: props.value.label,
  56 + profileId: props.value.value,
  57 + deviceType: props.value?.deviceType,
  58 + transportType: props.value?.transportType,
  59 + deviceList: props.value.deviceList?.filter(Boolean)?.map((item) => item.deviceId),
  60 + });
  61 + }
  62 + };
  63 +
  64 + //数值改变
  65 + const valEffect = watchEffect(() => {
  66 + initVal();
  67 + });
  68 +
  69 + valEffect();
  70 +
  71 + //chang改变
  72 + const emitChange = () => {
  73 + const tempDeviceList: Recordable[] = []; // fix: 修改选择设备顺序问题
  74 + dynamicInput.params[0].deviceList?.forEach((item) => {
  75 + selectOptions.value?.forEach((newItem) => {
  76 + if (item === newItem.value) {
  77 + tempDeviceList.push({
  78 + name: newItem.label,
  79 + deviceId: newItem.value,
  80 + codeType: newItem.codeType,
  81 + });
  82 + }
  83 + });
  84 + });
  85 + return {
  86 + ...dynamicInput.params[0],
  87 + deviceList: tempDeviceList.filter(Boolean), // 过滤假值
  88 + };
  89 + };
  90 + defineExpose({
  91 + emitChange,
  92 + });
  93 +</script>
  94 +<style scoped lang="css">
  95 + .dynamic-delete-button {
  96 + cursor: pointer;
  97 + position: relative;
  98 + top: 4px;
  99 + font-size: 24px;
  100 + color: #999;
  101 + transition: all 0.3s;
  102 + }
  103 +
  104 + .dynamic-delete-button:hover {
  105 + color: #777;
  106 + }
  107 +
  108 + .dynamic-delete-button[disabled] {
  109 + cursor: not-allowed;
  110 + opacity: 0.5;
  111 + }
  112 +</style>
... ...
  1 +<template>
  2 + <Select
  3 + placeholder="请选择产品"
  4 + v-model:value="selectValue"
  5 + style="width: 100%"
  6 + :options="selectOptions"
  7 + v-bind="createPickerSearch()"
  8 + @change="handleDeviceProfileChange"
  9 + mode="multiple"
  10 + labelInValue
  11 + />
  12 + <template v-for="(item, index) in profileList" :key="item.value">
  13 + <SelectDevice
  14 + :ref="bindDeviceRef.deviceAttrRef"
  15 + :value="item"
  16 + :index="index"
  17 + :organizationId="organizationId"
  18 + />
  19 + </template>
  20 +</template>
  21 +<script lang="ts" setup name="SelectDevice">
  22 + import { ref, Ref, PropType, unref, nextTick, onMounted } from 'vue';
  23 + import { Select } from 'ant-design-vue';
  24 + import SelectDevice from './SelectDevice.vue';
  25 + import { createPickerSearch } from '/@/utils/pickerSearch';
  26 +
  27 + import { getDeviceProfile } from '/@/api/alarm/position';
  28 +
  29 + defineProps({
  30 + selectOptions: {
  31 + type: Array as PropType<any[]>,
  32 + required: true,
  33 + },
  34 + organizationId: {
  35 + type: String,
  36 + required: true,
  37 + },
  38 + });
  39 +
  40 + const selectValue = ref([]);
  41 + const selectOptions = ref<any>([]);
  42 +
  43 + const bindDeviceRef = {
  44 + deviceAttrRef: ref([]),
  45 + };
  46 +
  47 + const profileList: Ref<any[]> = ref([]);
  48 +
  49 + const handleDeviceProfileChange = (_, options) => {
  50 + profileList.value = options;
  51 + };
  52 +
  53 + const getSelectDevice = () => {
  54 + return unref(bindDeviceRef.deviceAttrRef)?.map((item: any) => item.emitChange());
  55 + };
  56 +
  57 + const setFieldsValue = async (productIds) => {
  58 + await nextTick();
  59 + selectValue.value = productIds || [];
  60 + profileList.value = productIds || [];
  61 + };
  62 +
  63 + const setValue = (value: any) => {
  64 + selectValue.value = value.map((item) => ({
  65 + label: item.name,
  66 + key: item.profileId,
  67 + }));
  68 + profileList.value = value.map((item) => {
  69 + return {
  70 + label: item.name,
  71 + value: item.profileId,
  72 + deviceList: item.deviceList,
  73 + };
  74 + });
  75 + };
  76 + const retValue = () => {
  77 + selectValue.value = [];
  78 + profileList.value = [];
  79 + };
  80 +
  81 + onMounted(async () => {
  82 + const values = await getDeviceProfile();
  83 + selectOptions.value = values.map((item) => ({
  84 + label: item.name,
  85 + value: item.id,
  86 + }));
  87 + });
  88 +
  89 + defineExpose({
  90 + getSelectDevice,
  91 + setValue,
  92 + retValue,
  93 + setFieldsValue,
  94 + });
  95 +</script>
  96 +<style scoped lang="css"></style>
... ...
  1 +import { Platform } from './center.data';
  2 +import { ConfigurationCenterItemsModal } from '/@/api/configuration/center/model/configurationCenterModal';
  3 +import { useGlobSetting } from '/@/hooks/setting';
  4 +
  5 +export enum ScadaModeEnum {
  6 + LIGHTBOX = 'lightbox',
  7 + DESIGN = 'design',
  8 + SHARE = 'share',
  9 +}
  10 +
  11 +interface ScadaLinkParamsType {
  12 + configurationId: string;
  13 + organizationId: string;
  14 + mode: ScadaModeEnum;
  15 + platform: Platform;
  16 + publicId?: string;
  17 +}
  18 +
  19 +const getRandomString = () => Number(Math.random().toString().substring(2)).toString(36);
  20 +
  21 +export const encode = (record: Recordable) => {
  22 + let hash = JSON.stringify(record);
  23 + const mixinString = getRandomString()
  24 + .slice(0, 10)
  25 + .padEnd(10, getRandomString())
  26 + .split('')
  27 + .map((item) => (Math.random() > 0.5 ? item.toUpperCase() : item))
  28 + .join('');
  29 + hash = window.btoa(hash);
  30 + hash = hash.substring(0, 6) + mixinString + hash.substring(6);
  31 + hash = window.btoa(hash);
  32 + return hash;
  33 +};
  34 +
  35 +export const createScadaPageLink = (
  36 + record: ConfigurationCenterItemsModal,
  37 + mode: ScadaModeEnum = ScadaModeEnum.DESIGN,
  38 + open = true
  39 +) => {
  40 + const { configurationPrefix } = useGlobSetting();
  41 + const params: ScadaLinkParamsType = {
  42 + configurationId: record.id,
  43 + organizationId: record.organizationId!,
  44 + mode: mode,
  45 + platform: record.platform as Platform,
  46 + };
  47 +
  48 + if (mode === ScadaModeEnum.SHARE) {
  49 + params.publicId = record.publicId;
  50 + }
  51 +
  52 + const href = new URL(location.origin);
  53 + href.pathname = configurationPrefix;
  54 + href.hash = encode(params);
  55 + open && window.open(href.href);
  56 + return href.href;
  57 +};
... ...
... ... @@ -14,14 +14,12 @@
14 14 import { ConfigurationPermission, Platform, searchFormSchema } from './center.data';
15 15 import { useMessage } from '/@/hooks/web/useMessage';
16 16 import { Authority } from '/@/components/Authority';
17   - import { isDevMode } from '/@/utils/env';
18 17 import ConfigurationCenterDrawer from './ConfigurationCenterDrawer.vue';
19 18 import { useDrawer } from '/@/components/Drawer';
20 19 import { getBoundingClientRect } from '/@/utils/domUtils';
21 20 import configurationSrc from '/@/assets/icons/configuration.svg';
22 21 import { cloneDeep } from 'lodash';
23 22 import { usePermission } from '/@/hooks/web/usePermission';
24   - import { useGlobSetting } from '/@/hooks/setting';
25 23 import { AuthIcon, CardLayoutButton } from '/@/components/Widget';
26 24 import AuthDropDown from '/@/components/Widget/AuthDropDown.vue';
27 25 import { ShareModal } from '/@/views/common/ShareModal';
... ... @@ -31,6 +29,7 @@
31 29 import { useRole } from '/@/hooks/business/useRole';
32 30 import { useClipboard } from '@vueuse/core';
33 31 import { Icon } from '/@/components/Icon';
  32 + import { createScadaPageLink, ScadaModeEnum } from './help';
34 33
35 34 const listColumn = ref(5);
36 35
... ... @@ -78,6 +77,7 @@
78 77 const { items, total } = await getPage({
79 78 organizationId: unref(organizationId),
80 79 ...value,
  80 + isTemplate: 0,
81 81 page: pagination.current!,
82 82 pageSize,
83 83 });
... ... @@ -130,25 +130,15 @@
130 130 }
131 131 };
132 132
133   - const { configurationPrefix } = useGlobSetting();
134   - const isDev = isDevMode();
135   -
136 133 const handlePreview = (record: ConfigurationCenterItemsModal) => {
137 134 if (!unref(getPreviewFlag)) return;
138   - window.open(
139   - `${configurationPrefix}/${isDev ? '?dev=1&' : '?'}configurationId=${
140   - record!.id
141   - }&lightbox=1&organizationId=${record.organizationId}`
142   - );
  135 + createScadaPageLink(record, ScadaModeEnum.LIGHTBOX);
143 136 };
144 137
145 138 const handleDesign = (record: ConfigurationCenterItemsModal) => {
146 139 if (!unref(getDesignFlag)) return;
147   - window.open(
148   - `${configurationPrefix}/${isDev ? '?dev=1&' : '?'}configurationId=${
149   - record!.id
150   - }&organizationId=${record.organizationId}`
151   - );
  140 +
  141 + createScadaPageLink(record, ScadaModeEnum.DESIGN);
152 142 };
153 143
154 144 const handleDelete = async (record: ConfigurationCenterItemsModal) => {
... ... @@ -165,14 +155,7 @@
165 155 };
166 156
167 157 const createShareUrl = (record: ConfigurationCenterItemsModal) => {
168   - const searchParams = new URLSearchParams();
169   - isDev && searchParams.set('dev', '1');
170   - searchParams.set('share', 'SCADA');
171   - searchParams.set('configurationId', record.id);
172   - searchParams.set('publicId', record.publicId || '');
173   - searchParams.set('lightbox', '1');
174   - searchParams.set('organizationId', record!.organizationId || '');
175   - return `${origin}${configurationPrefix}/?${searchParams.toString()}`;
  158 + return createScadaPageLink(record, ScadaModeEnum.SHARE, false);
176 159 };
177 160
178 161 const { copied, copy } = useClipboard({ legacy: true });
... ...
  1 +<template>
  2 + <BasicDrawer
  3 + v-bind="$attrs"
  4 + @register="registerDrawer"
  5 + showFooter
  6 + :title="getTitle"
  7 + width="30%"
  8 + @ok="handleSubmit"
  9 + >
  10 + <BasicForm @register="registerForm" />
  11 + </BasicDrawer>
  12 +</template>
  13 +<script lang="ts">
  14 + import { defineComponent, ref, computed, unref } from 'vue';
  15 + import { BasicForm, useForm } from '/@/components/Form';
  16 + import { formSchema } from './center.data';
  17 + import { BasicDrawer, useDrawerInner } from '/@/components/Drawer';
  18 + import { useMessage } from '/@/hooks/web/useMessage';
  19 + import { saveOrUpdateConfigurationCenter } from '/@/api/configuration/center/configurationCenter';
  20 + import { FileItem } from '/@/components/Form/src/components/ApiUpload.vue';
  21 + import { buildUUID } from '/@/utils/uuid';
  22 + import { getDeviceProfile } from '/@/api/alarm/position';
  23 + import { PC_DEFAULT_CONTENT, PHONE_DEFAULT_CONTENT, Platform } from '../center/center.data';
  24 +
  25 + export default defineComponent({
  26 + name: 'ConfigurationDrawer',
  27 + components: { BasicDrawer, BasicForm },
  28 + emits: ['success', 'register'],
  29 + setup(_, { emit }) {
  30 + const isUpdate = ref(true);
  31 +
  32 + const [registerForm, { validate, setFieldsValue, resetFields }] = useForm({
  33 + labelWidth: 120,
  34 + schemas: formSchema,
  35 + showActionButtonGroup: false,
  36 + });
  37 +
  38 + const [registerDrawer, { setDrawerProps, closeDrawer }] = useDrawerInner(async (data) => {
  39 + await resetFields();
  40 + setDrawerProps({ confirmLoading: false });
  41 + isUpdate.value = !!data?.isUpdate;
  42 + if (unref(isUpdate)) {
  43 + if (data.record.thumbnail) {
  44 + data.record.thumbnail = [
  45 + { uid: buildUUID(), name: 'name', url: data.record.thumbnail } as FileItem,
  46 + ];
  47 + }
  48 + if (data.record.organizationDTO) {
  49 + await setFieldsValue(data.record);
  50 + } else {
  51 + Reflect.deleteProperty(data.record, 'organizationId');
  52 + await setFieldsValue(data.record);
  53 + }
  54 + if (!data.record?.productAndDevice) return;
  55 + const productAndDevice = data.record['productAndDevice'];
  56 + setFieldsValue({
  57 + productIds: productAndDevice.map((item) => item.profileId),
  58 + });
  59 + }
  60 + });
  61 +
  62 + const getTitle = computed(() => (!unref(isUpdate) ? '新增模板' : '编辑模板'));
  63 +
  64 + const getDefaultContent = (platform: Platform) => {
  65 + if (platform === Platform.PC) {
  66 + return PC_DEFAULT_CONTENT;
  67 + }
  68 + return PHONE_DEFAULT_CONTENT;
  69 + };
  70 +
  71 + // 获取产品
  72 + const getCurrentAllProduct = async (products: string[]) => {
  73 + const resp = (await getDeviceProfile()) as any;
  74 + if (!resp) return;
  75 + const values = resp.map((item) => ({
  76 + name: item.name,
  77 + profileId: item.id,
  78 + deviceType: item.deviceType,
  79 + transportType: item.transportType,
  80 + }));
  81 + return values.filter((item) => products.includes(item.profileId));
  82 + };
  83 +
  84 + async function handleSubmit() {
  85 + try {
  86 + const { createMessage } = useMessage();
  87 + const values = await validate();
  88 + if (!values) return;
  89 + const reflectProduct = await getCurrentAllProduct(values['productIds']);
  90 + if (Reflect.has(values, 'thumbnail')) {
  91 + const file = (values.thumbnail || []).at(0) || {};
  92 + values.thumbnail = file.url || null;
  93 + }
  94 + setDrawerProps({ confirmLoading: true });
  95 + let saveMessage = '添加成功';
  96 + let updateMessage = '修改成功';
  97 + values.defaultContent = getDefaultContent(values.platform);
  98 + values.productAndDevice = reflectProduct;
  99 + Reflect.deleteProperty(values, 'productIds');
  100 + await saveOrUpdateConfigurationCenter(values, unref(isUpdate));
  101 + closeDrawer();
  102 + emit('success');
  103 + createMessage.success(unref(isUpdate) ? updateMessage : saveMessage);
  104 + } finally {
  105 + setDrawerProps({ confirmLoading: false });
  106 + }
  107 + }
  108 +
  109 + return {
  110 + getTitle,
  111 + registerDrawer,
  112 + registerForm,
  113 + handleSubmit,
  114 + };
  115 + },
  116 + });
  117 +</script>
... ...
  1 +<template>
  2 + <div>
  3 + <PageWrapper dense contentFullHeight contentClass="flex">
  4 + <OrganizationIdTree @select="handleSelect" ref="organizationIdTreeRef" />
  5 + <BasicTable
  6 + style="flex: auto"
  7 + :clickToRowSelect="false"
  8 + @register="registerTable"
  9 + :searchInfo="searchInfo"
  10 + class="w-3/4 xl:w-4/5"
  11 + >
  12 + <template #platform="{ record }">
  13 + <Tag :color="record.platform === Platform.PHONE ? 'cyan' : 'blue'">
  14 + {{ record.platform === Platform.PHONE ? '移动端' : 'PC端' }}
  15 + </Tag>
  16 + </template>
  17 + <template #toolbar>
  18 + <Authority value="api:yt:configuration:center:post">
  19 + <a-button type="primary" @click="handleCreateOrEdit(null)"> 新增组态 </a-button>
  20 + </Authority>
  21 + <Authority value="api:yt:configuration:center:delete">
  22 + <Popconfirm
  23 + title="您确定要批量删除数据"
  24 + ok-text="确定"
  25 + cancel-text="取消"
  26 + @confirm="handleDeleteOrBatchDelete(null)"
  27 + >
  28 + <a-button type="primary" color="error" :disabled="hasBatchDelete">
  29 + 批量删除
  30 + </a-button>
  31 + </Popconfirm>
  32 + </Authority>
  33 + </template>
  34 + <template #action="{ record }">
  35 + <TableAction
  36 + :actions="[
  37 + {
  38 + label: '设计',
  39 + auth: 'api:yt:configuration:center:get_configuration_info:get',
  40 + icon: 'clarity:note-edit-line',
  41 + onClick: handleDesign.bind(null, record),
  42 + },
  43 + {
  44 + label: '预览',
  45 + auth: 'api:yt:configuration:center:get_configuration_info:get',
  46 + icon: 'ant-design:eye-outlined',
  47 + onClick: handlePreview.bind(null, record),
  48 + },
  49 + {
  50 + label: '编辑',
  51 + auth: 'api:yt:configuration:center:update',
  52 + icon: 'clarity:note-edit-line',
  53 + onClick: handleCreateOrEdit.bind(null, record),
  54 + },
  55 + {
  56 + label: '删除',
  57 + auth: 'api:yt:configuration:center:delete',
  58 + icon: 'ant-design:delete-outlined',
  59 + color: 'error',
  60 + popConfirm: {
  61 + title: '是否确认删除',
  62 + confirm: handleDeleteOrBatchDelete.bind(null, record),
  63 + },
  64 + },
  65 + ]"
  66 + />
  67 + </template>
  68 + </BasicTable>
  69 + </PageWrapper>
  70 + <ContactDrawer @register="registerDrawer" @success="handleSuccess" />
  71 + </div>
  72 +</template>
  73 +
  74 +<script lang="ts">
  75 + import { defineComponent, reactive, nextTick } from 'vue';
  76 + import { BasicTable, useTable, TableAction } from '/@/components/Table';
  77 + import { PageWrapper } from '/@/components/Page';
  78 + import { useDrawer } from '/@/components/Drawer';
  79 + import ContactDrawer from './ConfigurationCenterDrawer.vue';
  80 + import { useResetOrganizationTree, OrganizationIdTree } from '/@/views/common/organizationIdTree';
  81 + import { searchFormSchema, columns } from './center.data';
  82 + import { Platform } from '../center/center.data';
  83 + import {
  84 + getPage,
  85 + deleteConfigurationCenter,
  86 + } from '/@/api/configuration/center/configurationCenter';
  87 + import { useBatchDelete } from '/@/hooks/web/useBatchDelete';
  88 + import { isDevMode } from '/@/utils/env';
  89 + import { Authority } from '/@/components/Authority';
  90 + import { Popconfirm } from 'ant-design-vue';
  91 + import { Tag } from 'ant-design-vue';
  92 + import { useGlobSetting } from '/@/hooks/setting';
  93 + export default defineComponent({
  94 + components: {
  95 + PageWrapper,
  96 + OrganizationIdTree,
  97 + BasicTable,
  98 + TableAction,
  99 + ContactDrawer,
  100 + Authority,
  101 + Popconfirm,
  102 + Tag,
  103 + },
  104 + setup() {
  105 + const { configurationPrefix } = useGlobSetting();
  106 + const isDev = isDevMode();
  107 + const searchInfo = reactive<Recordable>({});
  108 + const { organizationIdTreeRef, resetFn } = useResetOrganizationTree(searchInfo);
  109 + // 表格hooks
  110 + const [registerTable, { reload, setProps }] = useTable({
  111 + title: '组态中心列表',
  112 + api: getPage,
  113 + columns,
  114 + clickToRowSelect: false,
  115 + formConfig: {
  116 + labelWidth: 120,
  117 + schemas: searchFormSchema,
  118 + resetFunc: resetFn,
  119 + },
  120 + showIndexColumn: false,
  121 + useSearchForm: true,
  122 + showTableSetting: true,
  123 + bordered: true,
  124 + rowKey: 'id',
  125 + actionColumn: {
  126 + width: 200,
  127 + title: '操作',
  128 + dataIndex: 'action',
  129 + slots: { customRender: 'action' },
  130 + fixed: 'right',
  131 + },
  132 + });
  133 + const { hasBatchDelete, handleDeleteOrBatchDelete, selectionOptions } = useBatchDelete(
  134 + deleteConfigurationCenter,
  135 + handleSuccess,
  136 + setProps
  137 + );
  138 + nextTick(() => {
  139 + setProps(selectionOptions);
  140 + });
  141 +
  142 + // 弹框
  143 + const [registerDrawer, { openDrawer }] = useDrawer();
  144 +
  145 + // 刷新
  146 + function handleSuccess() {
  147 + reload();
  148 + }
  149 + // 新增或编辑
  150 + const handleCreateOrEdit = (record: Recordable | null) => {
  151 + if (record) {
  152 + openDrawer(true, {
  153 + isUpdate: true,
  154 + record,
  155 + });
  156 + } else {
  157 + openDrawer(true, {
  158 + isUpdate: false,
  159 + });
  160 + }
  161 + };
  162 + // 树形选择器
  163 + const handleSelect = (organizationId: string) => {
  164 + searchInfo.organizationId = organizationId;
  165 + handleSuccess();
  166 + };
  167 +
  168 + const handlePreview = (record: Recordable | null) => {
  169 + window.open(
  170 + `${configurationPrefix}/${isDev ? '?dev=1&' : '?'}configurationId=${
  171 + record!.id
  172 + }&lightbox=1`
  173 + );
  174 + };
  175 + const handleDesign = (record: Recordable | null) => {
  176 + window.open(
  177 + `${configurationPrefix}/${isDev ? '?dev=1&' : '?'}configurationId=${record!.id}`
  178 + );
  179 + };
  180 +
  181 + return {
  182 + Platform,
  183 + searchInfo,
  184 + hasBatchDelete,
  185 + handleCreateOrEdit,
  186 + handleDeleteOrBatchDelete,
  187 + handleSelect,
  188 + handleSuccess,
  189 + handlePreview,
  190 + handleDesign,
  191 + registerTable,
  192 + registerDrawer,
  193 + organizationIdTreeRef,
  194 + };
  195 + },
  196 + });
  197 +</script>
... ...
  1 +import { BasicColumn, FormSchema } from '/@/components/Table';
  2 +import { FileItem } from '/@/components/Form/src/components/ApiUpload.vue';
  3 +import { createImgPreview } from '/@/components/Preview';
  4 +import { uploadThumbnail } from '/@/api/configuration/center/configurationCenter';
  5 +import { useComponentRegister } from '/@/components/Form';
  6 +import { OrgTreeSelect } from '../../common/OrgTreeSelect';
  7 +import { getDeviceProfile } from '/@/api/alarm/position';
  8 +import { Platform } from '../center/center.data';
  9 +
  10 +useComponentRegister('OrgTreeSelect', OrgTreeSelect);
  11 +
  12 +export enum ConfigurationTemplatePermission {
  13 + CREATE = 'api:yt:configuration:template:center:post',
  14 + UPDATE = 'api:yt:configuration:template:center:update',
  15 + DELETE = 'api:yt:configuration:template:center:delete',
  16 + DESIGN = 'api:yt:configuration:template:center:get_configuration_info:design',
  17 + PREVIEW = 'api:yt:configuration:template:center:get_configuration_info:preview',
  18 +}
  19 +// 表格列数据
  20 +export const columns: BasicColumn[] = [
  21 + {
  22 + title: '模板名称',
  23 + dataIndex: 'name',
  24 + width: 120,
  25 + },
  26 + {
  27 + title: '所属组织',
  28 + dataIndex: 'organizationDTO.name',
  29 + width: 160,
  30 + },
  31 + {
  32 + title: '平台',
  33 + dataIndex: 'platform',
  34 + width: 100,
  35 + slots: { customRender: 'platform' },
  36 + },
  37 + {
  38 + title: '备注',
  39 + dataIndex: 'remark',
  40 + width: 200,
  41 + },
  42 + {
  43 + title: '创建时间',
  44 + dataIndex: 'createTime',
  45 + width: 120,
  46 + },
  47 + {
  48 + title: '更新时间',
  49 + dataIndex: 'updateTime',
  50 + width: 120,
  51 + },
  52 + {
  53 + title: '操作',
  54 + dataIndex: 'action',
  55 + flag: 'ACTION',
  56 + width: 260,
  57 + slots: { customRender: 'action' },
  58 + },
  59 +];
  60 +
  61 +// 查询字段
  62 +export const searchFormSchema: FormSchema[] = [
  63 + {
  64 + field: 'name',
  65 + label: '模板名称',
  66 + component: 'Input',
  67 + colProps: { span: 8 },
  68 + componentProps: {
  69 + maxLength: 36,
  70 + placeholder: '请输入模板名称',
  71 + },
  72 + },
  73 +];
  74 +
  75 +export const formSchema: FormSchema[] = [
  76 + {
  77 + field: 'thumbnail',
  78 + label: '缩略图',
  79 + component: 'ApiUpload',
  80 + changeEvent: 'update:fileList',
  81 + valueField: 'fileList',
  82 + componentProps: () => {
  83 + return {
  84 + listType: 'picture-card',
  85 + maxFileLimit: 1,
  86 + accept: '.png,.jpg,.jpeg,.gif',
  87 + api: async (file: File) => {
  88 + try {
  89 + const formData = new FormData();
  90 + formData.set('file', file);
  91 + const { fileStaticUri, fileName } = await uploadThumbnail(formData);
  92 + return {
  93 + uid: fileStaticUri,
  94 + name: fileName,
  95 + url: fileStaticUri,
  96 + } as FileItem;
  97 + } catch (error) {
  98 + return {};
  99 + }
  100 + },
  101 + onPreview: (fileList: FileItem) => {
  102 + createImgPreview({ imageList: [fileList.url!] });
  103 + },
  104 + // showUploadList: {
  105 + // showDownloadIcon: true,
  106 + // showRemoveIcon: true,
  107 + // },
  108 + };
  109 + },
  110 + },
  111 +
  112 + {
  113 + field: 'name',
  114 + label: '模板名称',
  115 + required: true,
  116 + component: 'Input',
  117 + componentProps: {
  118 + placeholder: '请输入模板名称',
  119 + maxLength: 36,
  120 + },
  121 + },
  122 + {
  123 + field: 'organizationId',
  124 + label: '所属组织',
  125 + required: true,
  126 + component: 'OrgTreeSelect',
  127 + },
  128 + {
  129 + field: 'productIds',
  130 + label: '产品',
  131 + component: 'ApiSelect',
  132 + required: true,
  133 + componentProps: {
  134 + api: getDeviceProfile,
  135 + mode: 'multiple',
  136 + labelField: 'name',
  137 + valueField: 'id',
  138 + },
  139 + },
  140 + {
  141 + field: 'isTemplate',
  142 + label: '',
  143 + component: 'Switch',
  144 + required: true,
  145 + defaultValue: 1,
  146 + componentProps: {
  147 + disabled: true,
  148 + checkedValue: 1,
  149 + unCheckedValue: 0,
  150 + },
  151 + show: false,
  152 + },
  153 + {
  154 + field: 'platform',
  155 + label: '平台',
  156 + required: true,
  157 + component: 'RadioGroup',
  158 + defaultValue: Platform.PC,
  159 + componentProps: {
  160 + defaultValue: Platform.PC,
  161 + options: [
  162 + { label: 'PC端', value: Platform.PC },
  163 + { label: '移动端', value: Platform.PHONE },
  164 + ],
  165 + },
  166 + },
  167 + {
  168 + field: 'remark',
  169 + label: '备注',
  170 + component: 'InputTextArea',
  171 + componentProps: {
  172 + placeholder: '请输入备注',
  173 + maxLength: 255,
  174 + },
  175 + },
  176 + {
  177 + field: 'id',
  178 + label: '',
  179 + component: 'Input',
  180 + show: false,
  181 + componentProps: {
  182 + maxLength: 36,
  183 + placeholder: 'id',
  184 + },
  185 + },
  186 +];
... ...
  1 +import { Platform } from './center.data';
  2 +import { ConfigurationCenterItemsModal } from '/@/api/configuration/center/model/configurationCenterModal';
  3 +import { useGlobSetting } from '/@/hooks/setting';
  4 +
  5 +export enum ScadaModeEnum {
  6 + LIGHTBOX = 'lightbox',
  7 + DESIGN = 'design',
  8 + SHARE = 'share',
  9 +}
  10 +
  11 +interface ScadaLinkParamsType {
  12 + configurationId: string;
  13 + organizationId: string;
  14 + mode: ScadaModeEnum;
  15 + platform: Platform;
  16 + publicId?: string;
  17 +}
  18 +
  19 +const getRandomString = () => Number(Math.random().toString().substring(2)).toString(36);
  20 +
  21 +export const encode = (record: Recordable) => {
  22 + let hash = JSON.stringify(record);
  23 + const mixinString = getRandomString()
  24 + .slice(0, 10)
  25 + .padEnd(10, getRandomString())
  26 + .split('')
  27 + .map((item) => (Math.random() > 0.5 ? item.toUpperCase() : item))
  28 + .join('');
  29 + hash = window.btoa(hash);
  30 + hash = hash.substring(0, 6) + mixinString + hash.substring(6);
  31 + hash = window.btoa(hash);
  32 + return hash;
  33 +};
  34 +
  35 +export const createScadaPageLink = (
  36 + record: ConfigurationCenterItemsModal,
  37 + mode: ScadaModeEnum = ScadaModeEnum.DESIGN,
  38 + open = true
  39 +) => {
  40 + const { configurationPrefix } = useGlobSetting();
  41 + const params: ScadaLinkParamsType = {
  42 + configurationId: record.id,
  43 + organizationId: record.organizationId!,
  44 + mode: mode,
  45 + platform: record.platform as Platform,
  46 + };
  47 +
  48 + if (mode === ScadaModeEnum.SHARE) {
  49 + params.publicId = record.publicId;
  50 + }
  51 +
  52 + const href = new URL(location.origin);
  53 + href.pathname = configurationPrefix;
  54 + href.hash = encode(params);
  55 + open && window.open(href.href);
  56 + return href.href;
  57 +};
... ...
  1 +<script setup lang="ts">
  2 + import { List, Card, Button, PaginationProps, Tooltip } from 'ant-design-vue';
  3 + import { ReloadOutlined } from '@ant-design/icons-vue';
  4 + import { computed, onMounted, reactive, ref, unref } from 'vue';
  5 + import { OrganizationIdTree, useResetOrganizationTree } from '../../common/organizationIdTree';
  6 + import {
  7 + deleteConfigurationCenter,
  8 + getPage,
  9 + } from '/@/api/configuration/center/configurationCenter';
  10 + import { ConfigurationCenterItemsModal } from '/@/api/configuration/center/model/configurationCenterModal';
  11 + import { PageWrapper } from '/@/components/Page';
  12 + import { BasicForm, useForm } from '/@/components/Form';
  13 + import { searchFormSchema, ConfigurationTemplatePermission } from './center.data';
  14 + import { useMessage } from '/@/hooks/web/useMessage';
  15 + import { Authority } from '/@/components/Authority';
  16 + import ConfigurationCenterDrawer from './ConfigurationCenterDrawer.vue';
  17 + import { useDrawer } from '/@/components/Drawer';
  18 + import { getBoundingClientRect } from '/@/utils/domUtils';
  19 + import configurationSrc from '/@/assets/icons/configuration.svg';
  20 + import { cloneDeep } from 'lodash';
  21 + import { usePermission } from '/@/hooks/web/usePermission';
  22 + import { AuthIcon, CardLayoutButton } from '/@/components/Widget';
  23 + import AuthDropDown from '/@/components/Widget/AuthDropDown.vue';
  24 + import { useRole } from '/@/hooks/business/useRole';
  25 + import { Icon } from '/@/components/Icon';
  26 + import { createScadaPageLink, ScadaModeEnum } from './help';
  27 + import { Platform } from '../center/center.data';
  28 +
  29 + const listColumn = ref(5);
  30 +
  31 + const { createMessage } = useMessage();
  32 +
  33 + const { isCustomerUser } = useRole();
  34 +
  35 + const organizationId = ref<Nullable<number>>(null);
  36 +
  37 + const pagination = reactive<PaginationProps>({
  38 + size: 'small',
  39 + showTotal: (total: number) => `共 ${total} 条数据`,
  40 + current: 1,
  41 + pageSize: unref(listColumn) * 2,
  42 + onChange: (page: number) => {
  43 + pagination.current = page;
  44 + getListData();
  45 + },
  46 + });
  47 +
  48 + const loading = ref(false);
  49 +
  50 + const dataSource = ref<ConfigurationCenterItemsModal[]>([]);
  51 +
  52 + const [registerForm, { getFieldsValue }] = useForm({
  53 + schemas: searchFormSchema,
  54 + showAdvancedButton: true,
  55 + labelWidth: 100,
  56 + compact: true,
  57 + resetFunc: () => {
  58 + resetFn();
  59 + organizationId.value = null;
  60 + return getListData();
  61 + },
  62 + submitFunc: async () => {
  63 + const value = getFieldsValue();
  64 + getListData(value);
  65 + },
  66 + });
  67 +
  68 + async function getListData(value: Recordable = {}) {
  69 + try {
  70 + loading.value = true;
  71 + const pageSize = unref(listColumn) * 2;
  72 + const { items, total } = await getPage({
  73 + organizationId: unref(organizationId),
  74 + ...value,
  75 + page: pagination.current!,
  76 + pageSize,
  77 + isTemplate: 1,
  78 + });
  79 +
  80 + dataSource.value = items;
  81 + Object.assign(pagination, { total, pageSize });
  82 + } catch (error) {
  83 + } finally {
  84 + loading.value = false;
  85 + }
  86 + }
  87 +
  88 + onMounted(() => {
  89 + getListData();
  90 + });
  91 +
  92 + const searchInfo = reactive<Recordable>({});
  93 + const { organizationIdTreeRef, resetFn } = useResetOrganizationTree(searchInfo);
  94 + const handleSelect = (orgId: number) => {
  95 + organizationId.value = orgId;
  96 + getListData();
  97 + };
  98 +
  99 + const [registerDrawer, { openDrawer }] = useDrawer();
  100 +
  101 + const { hasPermission } = usePermission();
  102 +
  103 + const getPreviewFlag = computed(() => {
  104 + return hasPermission(ConfigurationTemplatePermission.PREVIEW);
  105 + });
  106 +
  107 + const getDesignFlag = computed(() => {
  108 + return hasPermission(ConfigurationTemplatePermission.DESIGN);
  109 + });
  110 +
  111 + const handleCreateOrUpdate = (record?: ConfigurationCenterItemsModal) => {
  112 + if (record) {
  113 + openDrawer(true, {
  114 + isUpdate: true,
  115 + record: cloneDeep(record),
  116 + });
  117 + } else {
  118 + openDrawer(true, {
  119 + isUpdate: false,
  120 + });
  121 + }
  122 + };
  123 +
  124 + const handlePreview = (record: ConfigurationCenterItemsModal) => {
  125 + if (!unref(getPreviewFlag)) return;
  126 + createScadaPageLink(record, ScadaModeEnum.LIGHTBOX);
  127 + };
  128 +
  129 + const handleDesign = (record: ConfigurationCenterItemsModal) => {
  130 + if (!unref(getDesignFlag)) return;
  131 +
  132 + createScadaPageLink(record, ScadaModeEnum.DESIGN);
  133 + };
  134 +
  135 + const handleDelete = async (record: ConfigurationCenterItemsModal) => {
  136 + try {
  137 + await deleteConfigurationCenter([record.id]);
  138 + createMessage.success('删除成功');
  139 + await getListData();
  140 + } catch (error) {}
  141 + };
  142 +
  143 + const handleCardLayoutChange = () => {
  144 + pagination.current = 1;
  145 + getListData();
  146 + };
  147 +
  148 + const listEl = ref<Nullable<ComponentElRef>>(null);
  149 +
  150 + onMounted(() => {
  151 + const clientHeight = document.documentElement.clientHeight;
  152 + const rect = getBoundingClientRect(unref(listEl)!.$el! as HTMLElement) as DOMRect;
  153 + // margin-top 24 height 24
  154 + const paginationHeight = 24 + 24 + 8;
  155 + // list pading top 8 maring-top 8 extra slot 56
  156 + const listContainerMarginBottom = 8 + 8 + 56;
  157 + const listContainerHeight =
  158 + clientHeight - rect.top - paginationHeight - listContainerMarginBottom;
  159 + const listContainerEl = (unref(listEl)!.$el as HTMLElement).querySelector(
  160 + '.ant-spin-container'
  161 + ) as HTMLElement;
  162 + listContainerEl &&
  163 + (listContainerEl.style.height = listContainerHeight + 'px') &&
  164 + (listContainerEl.style.overflowY = 'auto') &&
  165 + (listContainerEl.style.overflowX = 'hidden');
  166 + });
  167 +</script>
  168 +
  169 +<template>
  170 + <PageWrapper dense contentFullHeight contentClass="flex">
  171 + <OrganizationIdTree @select="handleSelect" ref="organizationIdTreeRef" />
  172 + <section class="flex-auto p-4 w-3/4 xl:w-4/5 w-full configuration-list">
  173 + <div class="flex-auto w-full bg-light-50 dark:bg-dark-900 p-4">
  174 + <BasicForm @register="registerForm" />
  175 + </div>
  176 + <List
  177 + ref="listEl"
  178 + :loading="loading"
  179 + class="flex-auto bg-light-50 dark:bg-dark-900 !p-2 !mt-4"
  180 + position="bottom"
  181 + :pagination="pagination"
  182 + :data-source="dataSource"
  183 + :grid="{ gutter: 4, column: listColumn }"
  184 + >
  185 + <template #header>
  186 + <div class="flex gap-3 justify-end">
  187 + <Authority v-if="!isCustomerUser" :value="ConfigurationTemplatePermission.CREATE">
  188 + <Button type="primary" @click="handleCreateOrUpdate()">新增模板</Button>
  189 + </Authority>
  190 + <CardLayoutButton v-model:value="listColumn" @change="handleCardLayoutChange" />
  191 + <Tooltip title="刷新">
  192 + <Button type="primary" @click="getListData">
  193 + <ReloadOutlined />
  194 + </Button>
  195 + </Tooltip>
  196 + </div>
  197 + </template>
  198 + <template #renderItem="{ item }">
  199 + <List.Item>
  200 + <Card
  201 + :style="{
  202 + '--viewType': '#1890ff',
  203 + }"
  204 + hoverable
  205 + class="card-container"
  206 + >
  207 + <template #cover>
  208 + <div
  209 + class="img-container h-full w-full !flex justify-center items-center text-center p-1 relative"
  210 + >
  211 + <img
  212 + class="w-full h-36"
  213 + alt="example"
  214 + :src="item.thumbnail || configurationSrc"
  215 + @click="handlePreview(item)"
  216 + />
  217 + <span
  218 + class="absolute top-0 left-0 text-light-50 transform -rotate-45 translate-y-1"
  219 + >
  220 + 母版
  221 + </span>
  222 + </div>
  223 + </template>
  224 + <template class="ant-card-actions" #actions>
  225 + <Tooltip v-if="!isCustomerUser" title="设计">
  226 + <AuthIcon
  227 + :auth="ConfigurationTemplatePermission.DESIGN"
  228 + class="!text-lg"
  229 + icon="ant-design:edit-outlined"
  230 + @click="handleDesign(item)"
  231 + />
  232 + </Tooltip>
  233 + <AuthDropDown
  234 + v-if="!isCustomerUser"
  235 + :dropMenuList="[
  236 + {
  237 + text: '编辑',
  238 + auth: ConfigurationTemplatePermission.UPDATE,
  239 + icon: 'clarity:note-edit-line',
  240 + event: '',
  241 + onClick: handleCreateOrUpdate.bind(null, item),
  242 + },
  243 + {
  244 + text: '删除',
  245 + auth: ConfigurationTemplatePermission.DELETE,
  246 + icon: 'ant-design:delete-outlined',
  247 + event: '',
  248 + popconfirm: {
  249 + title: '是否确认删除操作?',
  250 + onConfirm: handleDelete.bind(null, item),
  251 + },
  252 + },
  253 + ]"
  254 + :trigger="['hover']"
  255 + />
  256 + </template>
  257 + <Card.Meta>
  258 + <template #title>
  259 + <span class="truncate">{{ item.name }}</span>
  260 + </template>
  261 + <template #description>
  262 + <div class="truncate h-11">
  263 + <div class="truncate flex justify-between items-center">
  264 + <div>{{ item.organizationDTO?.name }}</div>
  265 + <Icon
  266 + :icon="
  267 + item.platform === Platform.PC
  268 + ? 'ri:computer-line'
  269 + : 'clarity:mobile-phone-solid'
  270 + "
  271 + />
  272 + </div>
  273 + <div class="truncate">{{ item.remark || '' }} </div>
  274 + </div>
  275 + </template>
  276 + </Card.Meta>
  277 + </Card>
  278 + </List.Item>
  279 + </template>
  280 + </List>
  281 + </section>
  282 + <ConfigurationCenterDrawer @register="registerDrawer" @success="getListData" />
  283 + </PageWrapper>
  284 +</template>
  285 +
  286 +<style lang="less" scoped>
  287 + .configuration-list:deep(.ant-list-header) {
  288 + border-bottom: none !important;
  289 + }
  290 +
  291 + .configuration-list:deep(.ant-list-pagination) {
  292 + height: 24px;
  293 + }
  294 +
  295 + .configuration-list:deep(.ant-card-body) {
  296 + padding: 16px !important;
  297 + }
  298 +
  299 + .configuration-list:deep(.ant-list-empty-text) {
  300 + @apply w-full h-full flex justify-center items-center;
  301 + }
  302 +
  303 + .card-container {
  304 + // background-color: red;
  305 + .img-container {
  306 + border-top-left-radius: 80px;
  307 + background-color: #fff;
  308 +
  309 + img {
  310 + border-top-left-radius: 80px;
  311 + }
  312 + }
  313 + }
  314 +
  315 + .card-container:deep(.ant-card-cover) {
  316 + background-color: var(--viewType);
  317 + }
  318 +</style>
... ...
... ... @@ -16,54 +16,33 @@
16 16 </div>
17 17 </div>
18 18 <div class="mt-8">
19   - <a-row type="flex" align="top">
20   - <a-col
21   - :span="
22   - method === RequestMethodTypeEnum.WEBSOCKET
23   - ? 11
24   - : httpType === RequestHttpTypeEnum.POST
25   - ? 6
26   - : 6
27   - "
28   - >
  19 + <div class="flex">
  20 + <div>
29 21 <p>过滤器函数编写:</p>
30   - <AceTypeIsJsEditor
31   - :restData="getRestData"
32   - @changeAceContent="onHandleAceContent"
33   - ref="aceTypeIsJsEditorRef"
34   - />
35   - </a-col>
36   - <a-col :span="1">
37   - <a-divider type="vertical" class="divider-color" />
38   - </a-col>
39   - <a-col
40   - :span="
41   - method === RequestMethodTypeEnum.WEBSOCKET
42   - ? 12
43   - : httpType === RequestHttpTypeEnum.POST
44   - ? 17
45   - : 17
46   - "
47   - style="position: relative"
48   - :style="{
49   - left:
50   - (method === RequestMethodTypeEnum.WEBSOCKET
51   - ? 1.5
52   - : httpType === RequestHttpTypeEnum.POST
53   - ? -0.25
54   - : 0) + 'vw',
55   - }"
56   - >
57   - <a-col>
  22 + <div class="w-90">
  23 + <AceTypeIsJsEditor
  24 + :restData="getRestData"
  25 + @changeAceContent="onHandleAceContent"
  26 + ref="aceTypeIsJsEditorRef"
  27 + />
  28 + </div>
  29 + </div>
  30 + <div><a-divider type="vertical" class="divider-color" /></div>
  31 + <div class="flex flex-col ml-8">
  32 + <div>
58 33 <p>接口返回数据(res):</p>
59   - <JsonEditor style="height: 35vh" :showBtn="true" ref="jsonEditorRef" />
60   - </a-col>
61   - <a-col class="mt-3">
  34 + <div>
  35 + <JsonEditor class="w-100 h-100" :showBtn="true" ref="jsonEditorRef" />
  36 + </div>
  37 + </div>
  38 + <div class="mt-4">
62 39 <p>过滤器结果:</p>
63   - <JsonFilterEditor style="height: 35vh" :showBtn="true" ref="jsonEditorFilterRef" />
64   - </a-col>
65   - </a-col>
66   - </a-row>
  40 + <div>
  41 + <JsonFilterEditor class="w-100 h-100" :showBtn="true" ref="jsonEditorFilterRef" />
  42 + </div>
  43 + </div>
  44 + </div>
  45 + </div>
67 46 </div>
68 47 </div>
69 48 </template>
... ... @@ -78,7 +57,7 @@
78 57 import AceTypeIsJsEditor from '../../SimpleRequest/components/aceEditor.vue';
79 58 import { Tag } from 'ant-design-vue';
80 59 import { useThrottleFn } from '@vueuse/shared';
81   - import { RequestMethodTypeEnum, RequestHttpTypeEnum } from '../../../config/enum';
  60 + import { RequestMethodTypeEnum } from '../../../config/enum';
82 61
83 62 const emits = defineEmits(['emitExcute']);
84 63
... ... @@ -334,9 +313,9 @@
334 313 }
335 314
336 315 .divider-color {
337   - height: 78.5vh;
  316 + height: 54rem;
338 317 background-color: #e5e7eb;
339 318 position: relative;
340   - left: 1.16vw;
  319 + left: 1rem;
341 320 }
342 321 </style>
... ...
... ... @@ -99,7 +99,7 @@ export const descSchema = (emit: EmitType): DescItem[] => {
99 99 export const realTimeDataColumns: BasicColumn[] = [
100 100 {
101 101 title: '键',
102   - dataIndex: 'key',
  102 + dataIndex: 'name',
103 103 width: 100,
104 104 },
105 105 {
... ...
... ... @@ -54,6 +54,9 @@
54 54 <TabPane key="task" tab="任务">
55 55 <Task :tbDeviceId="deviceDetail.tbDeviceId" />
56 56 </TabPane>
  57 + <!-- <TabPane v-if="false" key="videoChannel" tab="视频通道">
  58 + <VideoChannel :deviceDetail="deviceDetail" :fromId="deviceDetail?.tbDeviceId" />
  59 + </TabPane> -->
57 60 </Tabs>
58 61 </BasicDrawer>
59 62 </template>
... ... @@ -74,6 +77,7 @@
74 77 import EventManage from '../tabs/EventManage/index.vue';
75 78 import { DeviceRecord } from '/@/api/device/model/deviceModel';
76 79 import Task from '../tabs/Task.vue';
  80 + // import { VideoChannel } from '../tabs/VideoChannel/index';
77 81
78 82 export default defineComponent({
79 83 name: 'DeviceModal',
... ... @@ -91,6 +95,7 @@
91 95 CommandRecord,
92 96 EventManage,
93 97 Task,
  98 + // VideoChannel,
94 99 },
95 100 emits: ['reload', 'register', 'openTbDeviceDetail', 'openGatewayDeviceDetail'],
96 101 setup(_props, { emit }) {
... ... @@ -99,9 +104,13 @@
99 104 const deviceDetailRef = ref();
100 105 const deviceDetail = ref<DeviceRecord>({} as unknown as DeviceRecord);
101 106 const tbDeviceId = ref('');
  107 +
  108 + const isTransportType = ref<Boolean>(false); //获取产品是不是GB/T 28181
102 109 // 详情回显
103 110 const [register] = useDrawerInner(async (data) => {
104   - const { id } = data;
  111 + const { id, transportType, deviceType } = data || {};
  112 + isTransportType.value =
  113 + transportType == 'GB/T28181' && deviceType == 'DIRECT_CONNECTION' ? true : false;
105 114 // 设备详情
106 115 const res = await getDeviceDetail(id);
107 116 deviceDetail.value = res;
... ... @@ -136,6 +145,7 @@
136 145 tbDeviceId,
137 146 handleOpenTbDeviceDetail,
138 147 handleOpenGatewayDevice,
  148 + isTransportType,
139 149 drawerTitle,
140 150 };
141 151 },
... ...
... ... @@ -34,7 +34,7 @@ export const columnSchema: BasicColumn[] = [
34 34 {
35 35 title: '标识符',
36 36 dataIndex: 'eventIdentifier',
37   - helpMessage: ['标识符格式为模块标识符: 功能定义标识符'],
  37 + helpMessage: ['标识符:物模型事件功能定义标识符'],
38 38 },
39 39 {
40 40 title: '事件名称',
... ...
... ... @@ -5,7 +5,10 @@
5 5 import { useECharts } from '/@/hooks/web/useECharts';
6 6 import { AggregateDataEnum, selectDeviceAttrSchema } from '/@/views/device/localtion/config.data';
7 7 import { useTimePeriodForm } from '/@/views/device/localtion/cpns/TimePeriodForm';
8   - import { defaultSchemas } from '/@/views/device/localtion/cpns/TimePeriodForm/config';
  8 + import {
  9 + defaultSchemas,
  10 + OrderByEnum,
  11 + } from '/@/views/device/localtion/cpns/TimePeriodForm/config';
9 12 import TimePeriodForm from '/@/views/device/localtion/cpns/TimePeriodForm/TimePeriodForm.vue';
10 13 import { useGridLayout } from '/@/hooks/component/useGridLayout';
11 14 import { ColEx } from '/@/components/Form/src/types';
... ... @@ -96,7 +99,7 @@
96 99 size: 'small',
97 100 });
98 101
99   - const getTableList = async (orderBy?: string) => {
  102 + const getTableList = async (orderBy?: OrderByEnum) => {
100 103 // 表单验证
101 104 await method.validate();
102 105 const value = method.getFieldsValue();
... ... @@ -121,9 +124,9 @@
121 124 await setColumns(unref(columns));
122 125 if (sorter.field == 'ts') {
123 126 if (sorter.order == 'descend') {
124   - getTableList('DESC');
  127 + getTableList(OrderByEnum.DESC);
125 128 } else {
126   - getTableList('ASC');
  129 + getTableList(OrderByEnum.ASC);
127 130 }
128 131 }
129 132 };
... ... @@ -184,7 +187,6 @@
184 187 if (props.attr) {
185 188 method.setFieldsValue({ keys: props.attr });
186 189 const attrInfo = unref(deviceAttrs).find((item) => item.identifier === props.attr);
187   - console.log({ attrInfo });
188 190 if (
189 191 [DataTypeEnum.IS_STRING, DataTypeEnum.IS_STRUCT].includes(
190 192 attrInfo?.detail.dataType.type as unknown as DataTypeEnum
... ... @@ -198,7 +200,7 @@
198 200 } catch (error) {}
199 201 };
200 202
201   - const openHistoryPanel = async (orderBy?: string) => {
  203 + const openHistoryPanel = async (orderBy = OrderByEnum.ASC) => {
202 204 await nextTick();
203 205 method.updateSchema({
204 206 field: 'keys',
... ...
  1 +import { h } from 'vue';
  2 +import { BasicColumn, FormSchema } from '/@/components/Table';
  3 +import { Tag } from 'ant-design-vue';
  4 +import { withInstall } from '/@/utils/index';
  5 +import { DeviceTypeEnum } from '/@/api/device/model/deviceModel';
  6 +
  7 +import VideoPlay from './video.vue';
  8 +export const Video = withInstall(VideoPlay);
  9 +
  10 +export const configColumns: BasicColumn[] = [
  11 + {
  12 + title: '通道编号',
  13 + dataIndex: 'channellNumber',
  14 + },
  15 + {
  16 + title: '设备名称',
  17 + dataIndex: 'deviceName',
  18 + },
  19 + {
  20 + title: '通道名称',
  21 + dataIndex: 'channelName',
  22 + },
  23 + {
  24 + title: '厂家',
  25 + dataIndex: 'manufacturer',
  26 + },
  27 + {
  28 + title: '开启音频',
  29 + dataIndex: 'turnOnAudio',
  30 + slots: { customRender: 'turnOnAudio' },
  31 + },
  32 + {
  33 + title: '状态',
  34 + dataIndex: 'state',
  35 + format: (text) => {
  36 + return h(
  37 + Tag,
  38 + {
  39 + color: Number(text) === 1 ? 'green' : 'blue',
  40 + },
  41 + () => (Number(text) === 1 ? '在线' : '离线')
  42 + );
  43 + },
  44 + },
  45 + {
  46 + title: '操作',
  47 + dataIndex: 'action',
  48 + slots: { customRender: 'action' },
  49 + },
  50 +];
  51 +
  52 +export const searchFormSchema: FormSchema[] | any = [
  53 + {
  54 + field: 'name',
  55 + label: '设备名称',
  56 + component: 'Input',
  57 + colProps: { span: 6 },
  58 + componentProps: {
  59 + maxLength: 255,
  60 + placeholder: '请输入设备名称',
  61 + },
  62 + },
  63 + {
  64 + field: 'deviceType',
  65 + label: '设备类型',
  66 + component: 'Select',
  67 + colProps: { span: 6 },
  68 + componentProps: {
  69 + options: [
  70 + { label: '网关设备', value: DeviceTypeEnum.GATEWAY },
  71 + { label: '直连设备', value: DeviceTypeEnum.DIRECT_CONNECTION },
  72 + { label: '网关子设备', value: DeviceTypeEnum.SENSOR },
  73 + ],
  74 + placeholder: '请选择设备类型',
  75 + },
  76 + },
  77 + // {
  78 + // field: 'channelName',
  79 + // label: '通道名称',
  80 + // component: 'Input',
  81 + // colProps: { span: 6 },
  82 + // componentProps: {
  83 + // maxLength: 255,
  84 + // placeholder: '请输入通道名称',
  85 + // },
  86 + // },
  87 + {
  88 + field: 'manufacturer',
  89 + label: '厂家',
  90 + component: 'Select',
  91 + colProps: { span: 6 },
  92 + componentProps: {
  93 + options: [{}],
  94 + placeholder: '请选择厂家',
  95 + },
  96 + },
  97 +];
... ...
  1 +import VideoChannel from './index.vue';
  2 +export { VideoChannel };
... ...
  1 +<template>
  2 + <BasicTable
  3 + class="bg-neutral-100 dark:text-gray-300 dark:bg-dark-700 p-4"
  4 + @register="registerTable"
  5 + >
  6 + <template #turnOnAudio="{ record }">
  7 + <Switch
  8 + :checked="record.status === 1"
  9 + :loading="record.pendingStatus"
  10 + checkedChildren="开启"
  11 + unCheckedChildren="关闭"
  12 + @change="(checked:boolean)=>handleTurnVideo(checked,record)"
  13 + />
  14 + </template>
  15 + <template #action="{ record }">
  16 + <TableAction
  17 + :actions="[
  18 + {
  19 + label: '播放',
  20 + auth: 'api:yt:sceneLinkage:get',
  21 + icon: 'ant-design:play-circle-outlined',
  22 + onClick: handlePlay.bind(null, record),
  23 + },
  24 + ]"
  25 + /></template>
  26 + </BasicTable>
  27 + <VideoModal @register="registerModal" />
  28 +</template>
  29 +
  30 +<script lang="ts" setup>
  31 + import { configColumns, searchFormSchema } from './config';
  32 + import { BasicTable, useTable, TableAction } from '/@/components/Table';
  33 + import { Switch } from 'ant-design-vue';
  34 + import { DeviceRecord } from '/@/api/device/model/deviceModel';
  35 + import { watch } from 'vue';
  36 + import VideoModal from './videoModal.vue';
  37 + import { useModal } from '/@/components/Modal';
  38 + import { onMounted } from 'vue';
  39 + import { useMessage } from '/@/hooks/web/useMessage';
  40 +
  41 + const props = defineProps({
  42 + fromId: {
  43 + type: String,
  44 + default: '',
  45 + },
  46 + deviceDetail: {
  47 + type: Object as PropType<DeviceRecord>,
  48 + required: true,
  49 + },
  50 + });
  51 +
  52 + watch(
  53 + () => props,
  54 + () => {
  55 + console.log(props, 'props');
  56 + }
  57 + );
  58 +
  59 + const [registerModal, { openModal }] = useModal();
  60 +
  61 + const [registerTable, { setTableData, setProps, setSelectedRowKeys, reload }] = useTable({
  62 + // api: deviceCommandRecordGetQuery,
  63 + columns: configColumns,
  64 + showTableSetting: true,
  65 + bordered: true,
  66 + showIndexColumn: false,
  67 + formConfig: {
  68 + labelWidth: 120,
  69 + schemas: searchFormSchema,
  70 + },
  71 + beforeFetch: (params) => {
  72 + console.log(params);
  73 + },
  74 + useSearchForm: true,
  75 + });
  76 +
  77 + const handleTurnVideo = async (checked: Boolean, record: Recordable) => {
  78 + console.log(checked, record, 'record');
  79 + setProps({
  80 + loading: true,
  81 + });
  82 + setSelectedRowKeys([]);
  83 + const newStatus = checked ? 1 : 0;
  84 + const { createMessage } = useMessage();
  85 + try {
  86 + if (newStatus) createMessage.success('开启成功');
  87 + else createMessage.success('关闭成功');
  88 + } finally {
  89 + setProps({
  90 + loading: false,
  91 + });
  92 + reload();
  93 + }
  94 + };
  95 +
  96 + const tableList = [
  97 + {
  98 + id: '8b66f4fa-88e0-42b2-be33-60652cd2bda1',
  99 + creator: '694a8f7f-f7ed-4a9c-af89-a3d18fc3663e',
  100 + createTime: '2023-03-10 17:16:54',
  101 + updater: '694a8f7f-f7ed-4a9c-af89-a3d18fc3663e',
  102 + updateTime: '2023-04-11 10:25:49',
  103 + name: 'dasd',
  104 + enabled: false,
  105 + tenantId: '0277ca80-693d-11ed-9e12-e5edad4f7148',
  106 + videoUrl: 'ws://192.168.10.134:28080/rtp/62020000492000000002_34020000001320000001.live.flv',
  107 + sn: 's',
  108 + organizationId: '27ef2a83-6f1f-4e33-824b-80afac684699',
  109 + organizationName: '车车组织',
  110 + status: false,
  111 + accessMode: 0,
  112 + playProtocol: 0,
  113 + channellNumber: 1,
  114 + },
  115 + {
  116 + id: '9ff408ab-980f-470c-a55c-e4e284ddd34d',
  117 + creator: '694a8f7f-f7ed-4a9c-af89-a3d18fc3663e',
  118 + createTime: '2023-02-22 14:50:13',
  119 + updater: '694a8f7f-f7ed-4a9c-af89-a3d18fc3663e',
  120 + updateTime: '2023-04-11 10:36:25',
  121 + name: '2',
  122 + enabled: false,
  123 + tenantId: '0277ca80-693d-11ed-9e12-e5edad4f7148',
  124 + videoUrl:
  125 + 'https://vcsplay.scjtonline.cn:8099/live/HD_d80a740b-2672-4ad1-90e4-52eb71d8c2ef.m3u8?auth_key=1681180571-0-0-dfdb334e5f83838ade5f01f61f910107',
  126 + sn: '212',
  127 + organizationId: '27ef2a83-6f1f-4e33-824b-80afac684699',
  128 + organizationName: '车车组织',
  129 + status: false,
  130 + accessMode: 0,
  131 + playProtocol: 0,
  132 + channellNumber: 1,
  133 + },
  134 + {
  135 + id: 'a6edd8fb-a91a-4a1b-8124-65959206dac4',
  136 + creator: '694a8f7f-f7ed-4a9c-af89-a3d18fc3663e',
  137 + createTime: '2023-02-21 16:39:51',
  138 + updater: '694a8f7f-f7ed-4a9c-af89-a3d18fc3663e',
  139 + updateTime: '2023-04-11 10:33:27',
  140 + name: 'dasda',
  141 + enabled: false,
  142 + tenantId: '0277ca80-693d-11ed-9e12-e5edad4f7148',
  143 + videoUrl:
  144 + 'https://vcsplay.scjtonline.cn:8200/live/HD_1b361aa9-5230-48ba-b8be-d1e2c4e9b178.m3u8?auth_key=1681180365-0-0-e77f40e88550091053139f5562d8afa2',
  145 + brand: 'dasdad',
  146 + sn: 'adsad',
  147 + organizationId: '27ef2a83-6f1f-4e33-824b-80afac684699',
  148 + organizationName: '车车组织',
  149 + status: false,
  150 + accessMode: 0,
  151 + playProtocol: 0,
  152 + channellNumber: 1,
  153 + },
  154 + {
  155 + id: '51b4c0bc-8050-4b37-bdb3-9fe08b14cf86',
  156 + creator: '694a8f7f-f7ed-4a9c-af89-a3d18fc3663e',
  157 + createTime: '2023-02-21 16:37:44',
  158 + updater: '694a8f7f-f7ed-4a9c-af89-a3d18fc3663e',
  159 + updateTime: '2023-04-11 10:56:59',
  160 + name: '视频',
  161 + enabled: false,
  162 + tenantId: '0277ca80-693d-11ed-9e12-e5edad4f7148',
  163 + videoUrl:
  164 + 'https://vcsplay.scjtonline.cn:8093/live/HD_c54034ca-4a6d-11eb-8edc-3cd2e55e088c.m3u8?auth_key=1681181808-0-0-d756d9b0426b71482e45b9377958e4dc',
  165 + brand: '视频厂家',
  166 + sn: 'DART2143SAD12RE',
  167 + organizationId: '27ef2a83-6f1f-4e33-824b-80afac684699',
  168 + organizationName: '车车组织',
  169 + status: false,
  170 + accessMode: 0,
  171 + playProtocol: 0,
  172 + channellNumber: 1,
  173 + },
  174 + {
  175 + id: '8c0e6ed0-3176-4e76-bd8e-92f722f61e79',
  176 + creator: '694a8f7f-f7ed-4a9c-af89-a3d18fc3663e',
  177 + createTime: '2022-12-01 15:55:54',
  178 + updater: '694a8f7f-f7ed-4a9c-af89-a3d18fc3663e',
  179 + updateTime: '2023-04-11 10:59:22',
  180 + name: '视频',
  181 + enabled: false,
  182 + tenantId: '0277ca80-693d-11ed-9e12-e5edad4f7148',
  183 + avatar: 'https://demo.thingskit.com:9000/yunteng/itbbqOBXIUzWvjq.jpeg',
  184 + videoUrl: 'http://113.204.115.250:83/openUrl/iFzoWME/live.m3u8',
  185 + brand: '厂家',
  186 + sn: 'JDKSA11321',
  187 + organizationId: '9196fd9a-624a-4891-a8b3-ce588baedf9f',
  188 + organizationName: '丰田',
  189 + status: false,
  190 + channellNumber: 1,
  191 + accessMode: 0,
  192 + playProtocol: 0,
  193 + },
  194 + {
  195 + id: 'c30422a7-9498-49a5-96d5-e77f3a2823e3',
  196 + creator: '694a8f7f-f7ed-4a9c-af89-a3d18fc3663e',
  197 + createTime: '2022-12-01 14:42:52',
  198 + updater: '694a8f7f-f7ed-4a9c-af89-a3d18fc3663e',
  199 + updateTime: '2023-03-02 17:12:53',
  200 + name: '湖南卫视',
  201 + enabled: false,
  202 + tenantId: '0277ca80-693d-11ed-9e12-e5edad4f7148',
  203 + videoUrl:
  204 + 'http://219.151.31.38/liveplay-kk.rtxapp.com/live/program/live/hnwshd/4000000/mnf.m3u8',
  205 + brand: 'BUZHIDAO',
  206 + sn: 'QKDK123456',
  207 + organizationId: '27ef2a83-6f1f-4e33-824b-80afac684699',
  208 + organizationName: '车车组织',
  209 + status: false,
  210 + channellNumber: 1,
  211 + accessMode: 0,
  212 + playProtocol: 0,
  213 + },
  214 + {
  215 + id: 'e3253015-b8df-4e8f-ad0c-f634d3fd927e',
  216 + creator: '694a8f7f-f7ed-4a9c-af89-a3d18fc3663e',
  217 + createTime: '2022-11-29 11:19:00',
  218 + name: '玩具视频播放',
  219 + enabled: false,
  220 + tenantId: '0277ca80-693d-11ed-9e12-e5edad4f7148',
  221 + avatar: 'http://nc.tianzow.com:29000/yunteng/tgygMQmYaaPDPdG.jpg',
  222 + videoUrl: 'https://ask.dcloud.net.cn/topic.m3u8',
  223 + brand: '成都厂家',
  224 + sn: 'TCL1528GOP',
  225 + organizationId: '27ef2a83-6f1f-4e33-824b-80afac684699',
  226 + organizationName: '车车组织',
  227 + status: false,
  228 + accessMode: 0,
  229 + channellNumber: 1,
  230 + playProtocol: 0,
  231 + },
  232 + {
  233 + id: '9edc9f73-3a9a-4e4b-9b4c-b955329b99b3',
  234 + creator: '694a8f7f-f7ed-4a9c-af89-a3d18fc3663e',
  235 + createTime: '2022-11-29 11:13:44',
  236 + updater: '694a8f7f-f7ed-4a9c-af89-a3d18fc3663e',
  237 + updateTime: '2023-01-11 10:03:18',
  238 + name: '视频',
  239 + enabled: false,
  240 + tenantId: '0277ca80-693d-11ed-9e12-e5edad4f7148',
  241 + sn: '7aeb4061f3cf4ba384ed8e1a3027d2a8',
  242 + organizationId: '27ef2a83-6f1f-4e33-824b-80afac684699',
  243 + organizationName: '车车组织',
  244 + status: false,
  245 + accessMode: 1,
  246 + videoPlatformId: '8cc7c20e-693b-48ea-9124-994e0908c83e',
  247 + streamType: 0,
  248 + playProtocol: 0,
  249 + channellNumber: 1,
  250 + videoPlatformDTO: {
  251 + enabled: false,
  252 + host: '113.204.115.250:7120',
  253 + appKey: '28238690',
  254 + appSecret: 'F3e3Ffvo9Wyg9jkl8BUS',
  255 + ssl: 1,
  256 + },
  257 + },
  258 + {
  259 + id: '9d6675ad-07ed-45ae-bb4a-457000e164f8',
  260 + creator: '694a8f7f-f7ed-4a9c-af89-a3d18fc3663e',
  261 + createTime: '2022-11-21 11:49:59',
  262 + updater: '694a8f7f-f7ed-4a9c-af89-a3d18fc3663e',
  263 + updateTime: '2023-04-11 10:53:27',
  264 + name: '新视频',
  265 + enabled: false,
  266 + tenantId: '0277ca80-693d-11ed-9e12-e5edad4f7148',
  267 + videoUrl: 'http://www.w3school.com.cn/example/html5/mov_bbb.mp4',
  268 + brand: '1111',
  269 + sn: 'XYZ',
  270 + organizationId: '27ef2a83-6f1f-4e33-824b-80afac684699',
  271 + organizationName: '车车组织',
  272 + status: false,
  273 + accessMode: 0,
  274 + channellNumber: 1,
  275 + playProtocol: 0,
  276 + },
  277 + ];
  278 +
  279 + onMounted(() => {
  280 + setTableData(tableList);
  281 + });
  282 +
  283 + const handlePlay = (record: Recordable) => {
  284 + console.log(record);
  285 + openModal(true, {
  286 + record,
  287 + });
  288 + };
  289 +</script>
... ...
  1 +<script lang="ts" setup>
  2 + import { isNumber } from 'lodash';
  3 + import videoJs, { VideoJsPlayer, VideoJsPlayerOptions } from 'video.js';
  4 + import 'video.js/dist/video-js.css';
  5 + import { Tooltip } from 'ant-design-vue';
  6 + import { computed, CSSProperties, onMounted, onUnmounted, ref, unref } from 'vue';
  7 + import { useDesign } from '/@/hooks/web/useDesign';
  8 + import { getJwtToken, getShareJwtToken } from '/@/utils/auth';
  9 + import { isShareMode } from '/@/views/sys/share/hook';
  10 + import 'videojs-flvjs-es6';
  11 + import { QuestionCircleOutlined } from '@ant-design/icons-vue';
  12 + import {
  13 + CaretUpOutlined,
  14 + CaretRightOutlined,
  15 + PauseOutlined,
  16 + CaretDownOutlined,
  17 + CaretLeftOutlined,
  18 + ZoomInOutlined,
  19 + ZoomOutOutlined,
  20 + } from '@ant-design/icons-vue';
  21 + import { Button } from 'ant-design-vue';
  22 + import { nextTick } from 'vue';
  23 + import { controlling } from '/@/api/camera/cameraManager';
  24 +
  25 + const { prefixCls } = useDesign('basic-video-play');
  26 +
  27 + const props = defineProps<{
  28 + options?: VideoJsPlayerOptions;
  29 + withToken?: boolean;
  30 + }>();
  31 +
  32 + const emit = defineEmits<{
  33 + (event: 'ready', instance?: Nullable<VideoJsPlayer>): void;
  34 + (event: 'onUnmounted'): void;
  35 + }>();
  36 +
  37 + const videoPlayEl = ref<HTMLVideoElement>();
  38 +
  39 + const videoPlayInstance = ref<Nullable<VideoJsPlayer>>();
  40 +
  41 + const getOptions = computed(() => {
  42 + const { options, withToken } = props;
  43 +
  44 + const defaultOptions: VideoJsPlayerOptions & Recordable = {
  45 + language: 'zh',
  46 + muted: true,
  47 + liveui: true,
  48 + controls: true,
  49 + techOrder: ['html5', 'flvjs'],
  50 + flvjs: {
  51 + mediaDataSource: {
  52 + isLive: true,
  53 + cors: true,
  54 + hasAudio: false,
  55 + withCredentials: false,
  56 + },
  57 + config: {
  58 + headers: {
  59 + ...(withToken
  60 + ? {
  61 + 'X-Authorization': `Bearer ${isShareMode() ? getShareJwtToken() : getJwtToken()}`,
  62 + }
  63 + : {}),
  64 + },
  65 + autoCleanupSourceBuffer: true,
  66 + },
  67 + },
  68 + };
  69 + return videoJs.mergeOptions(defaultOptions, options);
  70 + });
  71 +
  72 + const getWidthHeight = computed(() => {
  73 + let { width = 300, height = 150 } = unref(getOptions);
  74 + width = isNumber(width) ? (`${width}px` as unknown as number) : width;
  75 + height = isNumber(height) ? (`${height}px` as unknown as number) : height;
  76 + return { width, height } as CSSProperties;
  77 + });
  78 +
  79 + const init = () => {
  80 + if (unref(videoPlayInstance)) unref(videoPlayInstance)?.dispose();
  81 + videoPlayInstance.value = videoJs(unref(videoPlayEl)!, unref(getOptions), () => {
  82 + emit('ready', unref(videoPlayInstance));
  83 + });
  84 + };
  85 +
  86 + //播放/暂停
  87 + const handleClick = () => {
  88 + unref(isPlay) && unref(videoPlayInstance)?.pause();
  89 + !unref(isPlay) && unref(videoPlayInstance)?.play();
  90 + };
  91 +
  92 + const getId = () => {
  93 + const { options } = props || {};
  94 + const { sources }: any = options || {};
  95 + return sources?.[0]?.id;
  96 + };
  97 +
  98 + const handleControl = (action: number, direction: string) => {
  99 + const organizationId = getId();
  100 + controlling({ cameralndexCode: organizationId, action, command: direction });
  101 + };
  102 +
  103 + const isPlay = ref<Boolean | null | undefined>(false);
  104 +
  105 + onMounted(async () => {
  106 + init();
  107 + await nextTick();
  108 + // isPlay.value = unref(videoPlayInstance)?.paused();
  109 + videoPlayInstance.value?.on('loadedmetadata', () => {});
  110 + videoPlayInstance.value?.on('waiting', () => {
  111 + isPlay.value = false;
  112 + });
  113 + videoPlayInstance.value?.on('play', () => {
  114 + isPlay.value = true;
  115 + });
  116 + videoPlayInstance.value?.on('playing', () => {
  117 + isPlay.value = true;
  118 + });
  119 + videoPlayInstance.value?.on('pause', () => {
  120 + isPlay.value = false;
  121 + });
  122 + videoPlayInstance.value?.on('ended', () => {
  123 + isPlay.value = false;
  124 + });
  125 + });
  126 +
  127 + //长按开始
  128 + const moveStart = (action) => {
  129 + handleControl(0, action);
  130 + };
  131 + // 长按结束
  132 + const moveStop = (action) => {
  133 + handleControl(1, action);
  134 + };
  135 +
  136 + onUnmounted(() => {
  137 + unref(videoPlayInstance)?.dispose();
  138 + videoPlayInstance.value = null;
  139 + emit('onUnmounted');
  140 + });
  141 +
  142 + defineExpose({
  143 + reloadPlayer: init,
  144 + getInstance: () => unref(videoPlayInstance),
  145 + });
  146 +</script>
  147 +
  148 +<template>
  149 + <div :class="prefixCls" class="!w-full h-full flex" :style="getWidthHeight">
  150 + <video
  151 + ref="videoPlayEl"
  152 + class="video-js vjs-big-play-centered vjs-show-big-play-button-on-pause !w-8/10 !h-full"
  153 + muted
  154 + >
  155 + </video>
  156 +
  157 + <div class="!w-2/10 bg-white flex items-center flex-col">
  158 + <Tooltip>
  159 + <template #title
  160 + >长按按钮调用API,由于视频流媒体提供的流,有一定时延,请耐心等待。</template
  161 + >
  162 + <label class="validate-dot">云台控制</label>
  163 + <QuestionCircleOutlined :style="{ fontSize: '14px', marginLeft: '5px' }" />
  164 + </Tooltip>
  165 +
  166 + <div class="home mt-5">
  167 + <Button class="front-sty-center child center" shape="circle" @click="handleClick()">
  168 + <PauseOutlined v-if="isPlay" class="child-icon" style="color: #fffbfb" />
  169 + <CaretRightOutlined v-else class="child-icon" style="color: #fffbfb" />
  170 + </Button>
  171 +
  172 + <div class="box">
  173 + <div>
  174 + <Button
  175 + class="left-top in-block"
  176 + @mousedown="moveStart('up')"
  177 + @mouseup="moveStop('up')"
  178 + >
  179 + <CaretUpOutlined class="icon-rotate child-icon" />
  180 + </Button>
  181 + <Button
  182 + class="right-top in-block"
  183 + @mousedown="moveStart('RIGHT')"
  184 + @mouseup="moveStop('RIGHT')"
  185 + >
  186 + <CaretRightOutlined class="icon-rotate child-icon" />
  187 + </Button>
  188 + </div>
  189 + <div>
  190 + <Button
  191 + class="left-bottom in-block"
  192 + @mousedown="moveStart('LEFT')"
  193 + @mouseup="moveStop('LEFT')"
  194 + >
  195 + <CaretLeftOutlined class="icon-rotate child-icon" />
  196 + </Button>
  197 + <Button
  198 + class="right-bottom in-block"
  199 + @mousedown="moveStart('DOWN')"
  200 + @mouseup="moveStop('DOWN')"
  201 + >
  202 + <CaretDownOutlined class="icon-rotate child-icon" />
  203 + </Button>
  204 + </div>
  205 +
  206 + <Button class="circle" @click="handleClick" />
  207 + </div>
  208 + </div>
  209 + <div class="flex justify-center mt-8">
  210 + <Button
  211 + class="button-icon"
  212 + @mousedown="moveStart('ZOOM_IN')"
  213 + @mouseup="moveStop('ZOOM_IN')"
  214 + style="border-radius: :50%;"
  215 + >
  216 + <ZoomInOutlined style="color: #315a9c; font-size: 1.5rem" />
  217 + </Button>
  218 + <Button
  219 + class="ml-10 button-icon"
  220 + @mousedown="moveStart('ZOOM_OUT')"
  221 + @mouseup="moveStop('ZOOM_OUT')"
  222 + style="border-radius: :50%;"
  223 + >
  224 + <ZoomOutOutlined style="color: #315a9c; font-size: 1.5rem" />
  225 + </Button>
  226 + </div>
  227 + </div>
  228 + </div>
  229 +</template>
  230 +
  231 +<style lang="less" scoped>
  232 + @prefix-cls: ~'@{namespace}-basic-video-play';
  233 +
  234 + .@{prefix-cls} {
  235 + .vjs-error-display {
  236 + .vjs-modal-dialog-content::after {
  237 + content: '无法加载视频,原因可能是服务器或网络故障,也可能是格式不支持.';
  238 + }
  239 + }
  240 + }
  241 +
  242 + .child {
  243 + position: absolute;
  244 + width: 3rem;
  245 + height: 3rem;
  246 + display: flex;
  247 + justify-content: center;
  248 + background: #e2dede;
  249 + align-items: center;
  250 + border: none;
  251 + }
  252 +
  253 + .child-icon {
  254 + font-size: 1.5rem;
  255 + color: #fffbfb;
  256 + }
  257 +
  258 + .button-icon {
  259 + width: 3rem;
  260 + height: 3rem;
  261 + background: #f5f5f5;
  262 + border: none;
  263 + border-radius: 50% !important;
  264 + }
  265 +
  266 + .center {
  267 + top: 50%;
  268 + left: 50%;
  269 + width: 4rem;
  270 + height: 4rem;
  271 + transform: translate(-50%, -50%);
  272 + border-radius: 50%;
  273 + background: #5586d4;
  274 + }
  275 +
  276 + .home {
  277 + position: relative;
  278 + width: 10rem;
  279 + height: 10rem;
  280 + }
  281 +
  282 + .box {
  283 + transform: rotateZ(45deg);
  284 + width: 10rem;
  285 + height: 10rem;
  286 + }
  287 +
  288 + .icon-rotate {
  289 + transform: rotate(315deg);
  290 + }
  291 +
  292 + .front-sty-center {
  293 + position: absolute;
  294 + top: 50%;
  295 + z-index: 9999;
  296 + left: 50%;
  297 + transform: translate(-50%, -50%);
  298 + }
  299 +
  300 + .circle {
  301 + display: inline-block;
  302 + border-radius: 50%;
  303 + background-color: #5586d4;
  304 + width: 4rem;
  305 + height: 4rem;
  306 + position: absolute;
  307 + top: 50%;
  308 + left: 50%;
  309 + transform: translate(-50%, -50%);
  310 + }
  311 +
  312 + .in-block {
  313 + display: inline-block;
  314 + position: relative;
  315 + }
  316 +
  317 + .left-top {
  318 + width: 5rem;
  319 + height: 5rem;
  320 + border-radius: 5rem 0 0 0;
  321 + background-color: #e2dede;
  322 + }
  323 +
  324 + .right-top {
  325 + width: 5rem;
  326 + height: 5rem;
  327 + border-radius: 0 5rem 0 0;
  328 + background-color: #e2dede;
  329 + }
  330 +
  331 + .left-bottom {
  332 + width: 5rem;
  333 + height: 5rem;
  334 + border-radius: 0 0 0 5rem;
  335 + background-color: #e2dede;
  336 + }
  337 +
  338 + .right-bottom {
  339 + width: 5rem;
  340 + height: 5rem;
  341 + border-radius: 0 0 5rem 0;
  342 + background-color: #e2dede;
  343 + }
  344 +</style>
... ...
  1 +<template>
  2 + <div>
  3 + <BasicModal
  4 + v-bind="$attrs"
  5 + width="60rem"
  6 + destroyOnClose
  7 + :height="heightNum"
  8 + @register="register"
  9 + title="视频预览"
  10 + :showOkBtn="false"
  11 + @cancel="handleCancel"
  12 + >
  13 + <div class="flex items-center justify-center w-full h-full min-h-96 video-container">
  14 + <Video
  15 + v-if="showVideo"
  16 + :options="(options as any)"
  17 + :withToken="withToken"
  18 + @on-unmounted="handleCloseFlvPlayUrl"
  19 + />
  20 + </div>
  21 + </BasicModal>
  22 + </div>
  23 +</template>
  24 +<script setup lang="ts">
  25 + import { ref, reactive, unref } from 'vue';
  26 + import { BasicModal, useModalInner } from '/@/components/Modal';
  27 + import type { StreamingManageRecord, CameraModel } from '/@/api/camera/model/cameraModel';
  28 + import { getVideoTypeByUrl } from '/@/components/Video';
  29 + import { closeFlvPlay, getFlvPlayUrl, getStreamingPlayUrl } from '/@/api/camera/cameraManager';
  30 + import { isRtspProtocol } from '/@/components/Video/src/utils';
  31 + import { VideoJsPlayerOptions } from 'video.js';
  32 + import { useFingerprint } from '/@/utils/useFingerprint';
  33 + import { GetResult } from '@fingerprintjs/fingerprintjs';
  34 + import { AccessMode } from '/@/views/camera/manage/config.data';
  35 + import { Video } from './config';
  36 +
  37 + const heightNum = ref(800);
  38 + const showVideo = ref(false);
  39 +
  40 + const playUrl = ref('');
  41 +
  42 + const withToken = ref(false);
  43 +
  44 + const fingerprintResult = ref<Nullable<GetResult>>(null);
  45 +
  46 + const options = reactive<VideoJsPlayerOptions>({
  47 + width: '100%' as unknown as number,
  48 + height: 384 as unknown as number,
  49 + autoplay: true,
  50 + });
  51 +
  52 + const setSources = (url: string, fingerprintResult: GetResult, id) => {
  53 + const flag = isRtspProtocol(url);
  54 + options.sources = [
  55 + {
  56 + src: flag ? getFlvPlayUrl(url, fingerprintResult.visitorId) : url,
  57 + type: getVideoTypeByUrl(url),
  58 + id,
  59 + },
  60 + ];
  61 + };
  62 +
  63 + const { getResult } = useFingerprint();
  64 + const [register] = useModalInner(
  65 + async (data: { record: CameraModel | StreamingManageRecord }) => {
  66 + const { record } = data;
  67 + const result = await getResult();
  68 + fingerprintResult.value = result;
  69 + if (record.accessMode === AccessMode.ManuallyEnter) {
  70 + if ((record as CameraModel).videoUrl) {
  71 + if (isRtspProtocol((record as CameraModel).videoUrl)) {
  72 + playUrl.value = (record as CameraModel).videoUrl;
  73 + closeFlvPlay(unref(playUrl), result.visitorId);
  74 + withToken.value = true;
  75 + }
  76 + setSources((record as CameraModel).videoUrl, result, record.id);
  77 + }
  78 + } else {
  79 + try {
  80 + const { data: { url } = { url: '' } } = await getStreamingPlayUrl(record.id!);
  81 + setSources(url, result, record.id);
  82 + } catch (error) {}
  83 + }
  84 + showVideo.value = true;
  85 + }
  86 + );
  87 +
  88 + const handleCloseFlvPlayUrl = () => {
  89 + if (isRtspProtocol(unref(playUrl))) {
  90 + closeFlvPlay(unref(playUrl)!, unref(fingerprintResult)!.visitorId!);
  91 + }
  92 + };
  93 +
  94 + const handleCancel = () => {
  95 + showVideo.value = false;
  96 + withToken.value = false;
  97 + };
  98 +</script>
  99 +
  100 +<style lang="less" scoped>
  101 + .video-container:deep(.vben-basic-video-play) {
  102 + min-height: 13rem;
  103 + }
  104 +
  105 + .video-container:deep(.video-js) {
  106 + min-height: 13rem;
  107 + }
  108 +</style>
... ...
... ... @@ -32,13 +32,14 @@ export function useHistoryData() {
32 32 };
33 33
34 34 function getSearchParams(value: Partial<Record<SchemaFiled, string>>) {
35   - const { startTs, endTs, interval, agg, limit, keys, way, deviceId } = value;
  35 + const { startTs, endTs, interval, agg, limit, keys, way, deviceId, orderBy } = value;
36 36 const basicRecord = {
37 37 entityId: deviceId,
38 38 keys: keys ? keys : unref(getDeviceKeys).join(),
39 39 interval,
40 40 agg,
41 41 limit,
  42 + orderBy,
42 43 };
43 44 if (way === QueryWay.LATEST) {
44 45 return Object.assign(basicRecord, {
... ...
... ... @@ -175,7 +175,10 @@
175 175 label: '取消收藏',
176 176 auth: 'api:yt:device:online:record',
177 177 icon: 'ant-design:heart-outlined',
178   - onClick: handelCollect.bind(null, record),
  178 + popConfirm: {
  179 + title: '是否取消收藏',
  180 + confirm: handelCollect.bind(null, record),
  181 + },
179 182 },
180 183 {
181 184 label: '删除',
... ... @@ -305,6 +308,7 @@
305 308 columns,
306 309 beforeFetch: (params) => {
307 310 const { deviceProfileId } = params;
  311 + if (!deviceProfileId) return;
308 312 const obj = {
309 313 ...params,
310 314 ...{
... ... @@ -368,10 +372,13 @@
368 372 }
369 373
370 374 function handleDetail(record: Recordable) {
371   - const { id, tbDeviceId } = record;
  375 + const { id, tbDeviceId, deviceProfile, deviceType } = record;
  376 + const { transportType } = deviceProfile || {};
372 377 openDrawer(true, {
373 378 id,
374 379 tbDeviceId,
  380 + transportType,
  381 + deviceType,
375 382 });
376 383 }
377 384
... ...
... ... @@ -5,6 +5,7 @@ import { copyTransFun } from '/@/utils/fnUtils';
5 5 import { getDeviceDataKeys } from '/@/api/alarm/position';
6 6 import { deviceProfile } from '/@/api/device/deviceManager';
7 7 import { EChartsOption } from 'echarts';
  8 +import { OrderByEnum, OrderByNameEnum, SchemaFiled } from './cpns/TimePeriodForm/config';
8 9
9 10 export enum AggregateDataEnum {
10 11 MIN = 'MIN',
... ... @@ -487,6 +488,18 @@ export const selectDeviceAttrSchema: FormSchema[] = [
487 488 getPopupContainer: () => document.body,
488 489 },
489 490 },
  491 + {
  492 + field: SchemaFiled.ORDER_BY,
  493 + label: '数据排序',
  494 + component: 'Select',
  495 + defaultValue: OrderByEnum.ASC,
  496 + componentProps: {
  497 + options: Object.values(OrderByEnum).map((value) => ({
  498 + value,
  499 + label: OrderByNameEnum[value],
  500 + })),
  501 + },
  502 + },
490 503 ];
491 504
492 505 export const eChartOptions = (series: EChartsOption['series'], keys: string[]): EChartsOption => {
... ...
... ... @@ -21,6 +21,16 @@ export enum SchemaFiled {
21 21 ORDER_BY = 'orderBy',
22 22 }
23 23
  24 +export enum OrderByEnum {
  25 + DESC = 'DESC',
  26 + ASC = 'ASC',
  27 +}
  28 +
  29 +export enum OrderByNameEnum {
  30 + DESC = '降序',
  31 + ASC = '升序',
  32 +}
  33 +
24 34 export enum AggregateDataEnum {
25 35 MIN = 'MIN',
26 36 MAX = 'MAX',
... ... @@ -106,7 +116,7 @@ export const defaultSchemas: FormSchema[] = [
106 116 getPopupContainer: () => document.body,
107 117 };
108 118 },
109   - colProps: useGridLayout(2, 2, 2, 2, 2, 2) as unknown as ColEx,
  119 + colProps: useGridLayout(1.5, 1.5, 1.5, 1.5, 1.5, 1.5) as unknown as ColEx,
110 120 },
111 121 {
112 122 field: SchemaFiled.AGG,
... ...
... ... @@ -260,6 +260,7 @@
260 260 },
261 261 beforeFetch: (params) => {
262 262 const { deviceProfileId } = params;
  263 + if (!deviceProfileId) return;
263 264 const deviceProfileIds = [deviceProfileId];
264 265 const obj = {
265 266 ...params,
... ...
... ... @@ -145,6 +145,7 @@
145 145 }
146 146 };
147 147 const handleStepNext = (e, data) => {
  148 + // const { deviceType } = unref(DevConStRef)?.getFieldsValue() || {};
148 149 if (e) {
149 150 current.value++;
150 151 unref(isUpdate)
... ...
... ... @@ -15,19 +15,20 @@
15 15 ifShowBtn: { type: Boolean, default: true },
16 16 });
17 17
18   - const [register, { validate, setFieldsValue, resetFields, updateSchema }] = useForm({
19   - labelWidth: 100,
20   - schemas: step1Schemas,
21   - actionColOptions: {
22   - span: 14,
23   - },
24   - showResetButton: false,
25   - showActionButtonGroup: props.ifShowBtn ? true : false,
26   - submitButtonOptions: {
27   - text: '下一步',
28   - },
29   - submitFunc: customSubmitFunc,
30   - });
  18 + const [register, { validate, setFieldsValue, resetFields, updateSchema, getFieldsValue }] =
  19 + useForm({
  20 + labelWidth: 100,
  21 + schemas: step1Schemas,
  22 + actionColOptions: {
  23 + span: 14,
  24 + },
  25 + showResetButton: false,
  26 + showActionButtonGroup: props.ifShowBtn ? true : false,
  27 + submitButtonOptions: {
  28 + text: '下一步',
  29 + },
  30 + submitFunc: customSubmitFunc,
  31 + });
31 32 const editOrAddNameStatus = (nameStatus) =>
32 33 updateSchema({
33 34 field: 'name',
... ... @@ -87,6 +88,7 @@
87 88 resetFormData,
88 89 getFormData,
89 90 editOrAddDeviceTypeStatus,
  91 + getFieldsValue,
90 92 setFieldsdefaultRuleChainId,
91 93 });
92 94 </script>
... ...
1 1 <template>
2 2 <div
3 3 class="step2-style"
4   - :style="[isMqttType == 'DEFAULT' ? { minHeight: 0 + 'px' } : { minHeight: 800 + 'px' }]"
  4 + :style="[
  5 + isMqttType == 'DEFAULT' || isMqttType == 'GB/T28181'
  6 + ? { minHeight: 0 + 'px' }
  7 + : { minHeight: 800 + 'px' },
  8 + ]"
5 9 >
6 10 <div
7 11 :style="[
... ... @@ -138,6 +142,7 @@
138 142 const getSnmpVal = await snmpRef.value?.getFormData();
139 143 const getTcpVal = await tcpRef.value?.getFormData();
140 144 step2Data.transportConfiguration = {
  145 + // type: isMqttType.value,
141 146 ...getMqttVal,
142 147 ...getCoapVal,
143 148 ...getLwm2mVal,
... ... @@ -149,18 +154,25 @@
149 154 };
150 155
151 156 const editOrAddTransportTypeStatus = (status: boolean) => {
  157 + const options = [
  158 + { label: '默认', value: 'DEFAULT' },
  159 + { label: 'MQTT', value: 'MQTT' },
  160 + { label: 'CoAP', value: 'COAP' },
  161 + // { label: 'LWM2M', value: 'LWM2M' },
  162 + // { label: 'SNMP', value: 'SNMP' },
  163 + { label: 'TCP/UDP', value: 'TCP' },
  164 + ];
  165 + // if (deviceType == 'DIRECT_CONNECTION') {
  166 + // options.push({ label: 'GB/T 28181', value: 'GB/T28181' });//暂时隐藏 GBT 28181写完放出来
  167 + // }
  168 + // if (deviceType != 'DIRECT_CONNECTION' && isMqttType.value == 'GB/T28181') {
  169 + // setFieldsValue({ transportType: null });
  170 + // }
152 171 updateSchema({
153 172 field: 'transportType',
154 173 componentProps: {
155 174 disabled: status,
156   - options: [
157   - { label: '默认', value: 'DEFAULT' },
158   - { label: 'MQTT', value: 'MQTT' },
159   - { label: 'CoAP', value: 'COAP' },
160   - // { label: 'LWM2M', value: 'LWM2M' },
161   - // { label: 'SNMP', value: 'SNMP' },
162   - { label: 'TCP/UDP', value: 'TCP' },
163   - ],
  175 + options,
164 176 onChange(e) {
165 177 isMqttType.value = e;
166 178 },
... ...
... ... @@ -19,7 +19,7 @@
19 19
20 20 const [register, { validate, resetFields, setFieldsValue, setProps }] = useForm({
21 21 labelWidth: 100,
22   - schemas: formSchemas(false, props.transportType === TransportTypeEnum.TCP),
  22 + schemas: formSchemas(false, false, props.transportType === TransportTypeEnum.TCP),
23 23 actionColOptions: {
24 24 span: 14,
25 25 },
... ...
... ... @@ -143,6 +143,9 @@ export const serviceSchemas = (tcpDeviceFlag: boolean): FormSchema[] => {
143 143 rules: [{ message: '输入参数为必填项', required: true, type: 'array' }],
144 144 ifShow: !tcpDeviceFlag,
145 145 colProps: { span: 24 },
  146 + componentProps: {
  147 + hiddenAccessMode: true,
  148 + },
146 149 },
147 150 {
148 151 field: FormField.OUTPUT_PARAM,
... ... @@ -151,6 +154,9 @@ export const serviceSchemas = (tcpDeviceFlag: boolean): FormSchema[] => {
151 154 valueField: 'value',
152 155 changeEvent: 'update:value',
153 156 colProps: { span: 24 },
  157 + componentProps: {
  158 + hiddenAccessMode: true,
  159 + },
154 160 },
155 161 {
156 162 field: FormField.REFARK,
... ... @@ -218,6 +224,9 @@ export const eventSchemas: FormSchema[] = [
218 224 valueField: 'value',
219 225 changeEvent: 'update:value',
220 226 colProps: { span: 24 },
  227 + componentProps: {
  228 + hiddenAccessMode: true,
  229 + },
221 230 },
222 231 {
223 232 field: FormField.REFARK,
... ...
... ... @@ -12,6 +12,8 @@
12 12 import { sendSms } from '/@/api/message/template';
13 13 import { useMessage } from '/@/hooks/web/useMessage';
14 14 import { phoneRule } from '/@/utils/rules';
  15 + import { PlateFormTypeEnum } from './template.data';
  16 +
15 17 const schemas: FormSchema[] = [
16 18 {
17 19 field: 'id',
... ... @@ -142,7 +144,53 @@
142 144 field: 'params',
143 145 componentProps: {
144 146 placeholder:
145   - platformType.value !== 'TENCENT_CLOUD' ? '示例:{"code":"1234"}' : '示例:["123456"]',
  147 + platformType.value !== PlateFormTypeEnum.tencent_cound
  148 + ? '示例:{"code":"1234"}'
  149 + : '示例:["123456"]',
  150 + },
  151 + dynamicRules: () => {
  152 + return [
  153 + {
  154 + required: true,
  155 + validator: (_, value) => {
  156 + try {
  157 + if (typeof value == 'object') {
  158 + return Promise.resolve();
  159 + } else {
  160 + if (platformType.value !== PlateFormTypeEnum.tencent_cound) {
  161 + // 阿里云不能是以[]开头
  162 + if (
  163 + Object.prototype.toString.call(JSON.parse(value)) === '[object Array]'
  164 + ) {
  165 + return Promise.reject('请输入JSON格式例如{"code":"1234"}');
  166 + }
  167 + } else {
  168 + // 腾讯云只能是[]开头
  169 + if (
  170 + Object.prototype.toString.call(JSON.parse(value)) !== '[object Array]'
  171 + ) {
  172 + return Promise.reject('请输入这种格式["123456"]');
  173 + }
  174 + }
  175 + if (typeof JSON.parse(value) == 'object') {
  176 + return Promise.resolve();
  177 + }
  178 + return Promise.reject(
  179 + platformType.value !== PlateFormTypeEnum.tencent_cound
  180 + ? '请输入JSON格式例如{"code":"1234"}'
  181 + : '请输入这种格式["123456"]'
  182 + );
  183 + }
  184 + } catch {
  185 + return Promise.reject(
  186 + platformType.value !== PlateFormTypeEnum.tencent_cound
  187 + ? '请输入JSON格式例如{"code":"1234"}'
  188 + : '请输入这种格式["123456"]'
  189 + );
  190 + }
  191 + },
  192 + },
  193 + ];
146 194 },
147 195 });
148 196 await resetFields();
... ... @@ -154,7 +202,7 @@
154 202 async function handleOK() {
155 203 let smsParams: any = null;
156 204 const values = await validate();
157   - if (platformType.value === 'TENCENT_CLOUD') {
  205 + if (platformType.value === PlateFormTypeEnum.tencent_cound) {
158 206 //腾讯云发送格式 将字符串转为数组
159 207 smsParams = {
160 208 tencent_param: JSON.parse(Reflect.get(values, 'params')),
... ...
... ... @@ -4,6 +4,11 @@ import { findDictItemByCode } from '/@/api/system/dict';
4 4 import { findMessageConfig } from '/@/api/message/config';
5 5 import { isMessage } from '/@/views/message/config/config.data';
6 6
  7 +export enum PlateFormTypeEnum {
  8 + ali_cound = 'ALI_CLOUD',
  9 + tencent_cound = 'TENCENT_CLOUD',
  10 +}
  11 +
7 12 export const columns: BasicColumn[] = [
8 13 {
9 14 title: '模板名称',
... ...
... ... @@ -32,13 +32,13 @@
32 32 setFieldsValue(value);
33 33 };
34 34
35   - // 禁用表单
  35 + // ½ûÓÃ±íµ¥
36 36 const setDisabledProps = (value) => {
37 37 setProps(value);
38 38 updateSchema({ field: 'otherProperties', componentProps: { disabled: true } });
39 39 };
40 40
41   - // 取消禁用
  41 + // È¡Ïû½ûÓÃ
42 42 const setCancelDisabled = () => {
43 43 updateSchema({ field: 'otherProperties', componentProps: { disabled: false } });
44 44 };
... ...
... ... @@ -86,12 +86,12 @@ export enum OriginatorTelemetryFieldsEnum {
86 86 }
87 87
88 88 export enum OriginatorTelemetryFieldsNameEnum {
89   - LATEST_TS_KEY_NAMES = 'Latest timeseries',
  89 + LATEST_TS_KEY_NAMES = 'Timeseries key',
90 90 AGGREGATION = '数据聚合功能',
91 91 FETCH_MODE = 'Fetch Mode',
92 92 ORDER_BY = 'Order by',
93 93 LIMIT = 'Limit',
94   - USE_METADATA_INTERVAL_PATTERNS = 'useMetadataIntervalPatterns',
  94 + USE_METADATA_INTERVAL_PATTERNS = 'Use interval patterns',
95 95 START_INTERVAL = 'Start Interval',
96 96 START_INTERVAL_TIME_UNIT = 'Start Interval Time Unit',
97 97 END_INTERVAL = 'End Interval',
... ...
... ... @@ -13,6 +13,7 @@ export const formSchemas: FormSchema[] = [
13 13 javaScriptEditorProps: {
14 14 functionName: 'Details',
15 15 paramsName: ['msg', 'metadata', 'msgType'],
  16 + scriptType: 'json',
16 17 },
17 18 buttonName: 'Test details function',
18 19 },
... ...
... ... @@ -36,6 +36,7 @@ export const formSchemas: FormSchema[] = [
36 36 componentProps: {
37 37 javaScriptEditorProps: {
38 38 functionName: 'Details',
  39 + scriptType: 'json',
39 40 paramsName: ['msg', 'metadata', 'msgType'],
40 41 },
41 42 buttonName: 'Test details function',
... ...
... ... @@ -60,6 +60,7 @@ export const formSchemas: FormSchema[] = [
60 60 componentProps: {
61 61 javaScriptEditorProps: {
62 62 functionName: 'Generate',
  63 + scriptType: 'generate',
63 64 paramsName: ['prevMsg', 'prevMetadata', 'prevMsgType'],
64 65 },
65 66 buttonName: 'Test generator function',
... ...
... ... @@ -14,6 +14,7 @@ export const formSchemas: FormSchema[] = [
14 14 componentProps: {
15 15 javaScriptEditorProps: {
16 16 functionName: 'ToString',
  17 + scriptType: 'string',
17 18 paramsName: ['msg', 'metadata', 'msgType'],
18 19 },
19 20 buttonName: 'Test to string function',
... ...
... ... @@ -73,6 +73,10 @@ export const formSchemas: FormSchema[] = [
73 73 getPopupContainer: () => document.body,
74 74 placeholder: `请选择${RestApiCallFieldsEnum.PROXY_SCHEME}`,
75 75 },
  76 + ifShow: ({ model }) => {
  77 + const ifShowField = model[RestApiCallFieldsEnum.ENABLE_PROXY];
  78 + return ifShowField && !model[RestApiCallFieldsEnum.USE_SYSTEM_PROXY_PROPERTIES];
  79 + },
76 80 },
77 81 {
78 82 field: RestApiCallFieldsEnum.PROXY_HOST,
... ... @@ -83,6 +87,10 @@ export const formSchemas: FormSchema[] = [
83 87 componentProps: {
84 88 placeholder: `请输入${RestApiCallFieldsNameEnum.PROXY_HOST}`,
85 89 },
  90 + ifShow: ({ model }) => {
  91 + const ifShowField = model[RestApiCallFieldsEnum.ENABLE_PROXY];
  92 + return ifShowField && !model[RestApiCallFieldsEnum.USE_SYSTEM_PROXY_PROPERTIES];
  93 + },
86 94 },
87 95 {
88 96 field: RestApiCallFieldsEnum.PROXY_PORT,
... ... @@ -93,6 +101,10 @@ export const formSchemas: FormSchema[] = [
93 101 componentProps: {
94 102 placeholder: `请输入${RestApiCallFieldsNameEnum.PROXY_HOST}`,
95 103 },
  104 + ifShow: ({ model }) => {
  105 + const ifShowField = model[RestApiCallFieldsEnum.ENABLE_PROXY];
  106 + return ifShowField && !model[RestApiCallFieldsEnum.USE_SYSTEM_PROXY_PROPERTIES];
  107 + },
96 108 },
97 109 {
98 110 field: RestApiCallFieldsEnum.PROXY_USER,
... ... @@ -101,6 +113,10 @@ export const formSchemas: FormSchema[] = [
101 113 componentProps: {
102 114 placeholder: `请输入${RestApiCallFieldsNameEnum.PROXY_USER}`,
103 115 },
  116 + ifShow: ({ model }) => {
  117 + const ifShowField = model[RestApiCallFieldsEnum.ENABLE_PROXY];
  118 + return ifShowField && !model[RestApiCallFieldsEnum.USE_SYSTEM_PROXY_PROPERTIES];
  119 + },
104 120 },
105 121 {
106 122 field: RestApiCallFieldsEnum.PROXY_PASSWORD,
... ... @@ -109,6 +125,10 @@ export const formSchemas: FormSchema[] = [
109 125 componentProps: {
110 126 placeholder: `请输入${RestApiCallFieldsNameEnum.PROXY_PASSWORD}`,
111 127 },
  128 + ifShow: ({ model }) => {
  129 + const ifShowField = model[RestApiCallFieldsEnum.ENABLE_PROXY];
  130 + return ifShowField && !model[RestApiCallFieldsEnum.USE_SYSTEM_PROXY_PROPERTIES];
  131 + },
112 132 },
113 133 {
114 134 field: RestApiCallFieldsEnum.READ_TIMEOUT_MS,
... ...
... ... @@ -15,6 +15,7 @@ export const formSchemas: FormSchema[] = [
15 15 javaScriptEditorProps: {
16 16 functionName: 'Transform',
17 17 paramsName: ['msg', 'metadata', 'msgType'],
  18 + scriptType: 'update',
18 19 },
19 20 buttonName: 'Test transformer function',
20 21 },
... ...
1 1 <script lang="ts" setup>
2 2 import { Card, Select, Tag, Form, Button } from 'ant-design-vue';
3   - import { ref, toRaw, unref, watch } from 'vue';
  3 + import { nextTick, ref, toRaw, unref, watch } from 'vue';
4 4 import { MessageTypesEnum, MessageTypesNameEnum } from '../../../enum/form';
5 5 import { JSONEditor } from '/@/components/CodeEditor';
6 6 import { Icon } from '/@/components/Icon';
... ... @@ -8,6 +8,8 @@
8 8 import { useJsonParse } from '/@/hooks/business/useJsonParse';
9 9 import { useMessage } from '/@/hooks/web/useMessage';
10 10 import { AttributeConfiguration } from '../AttributeConfiguration';
  11 + import { testScript } from '/@/api/ruleChainDesigner';
  12 + import { isString } from '/@/utils/is';
11 13
12 14 interface Value {
13 15 msg: Recordable;
... ... @@ -24,6 +26,7 @@
24 26 {
25 27 javaScriptEditorProps: () => ({
26 28 functionName: 'Filter',
  29 + scriptType: 'filter',
27 30 paramsName: ['msg', 'metadata', 'msgType'],
28 31 }),
29 32 }
... ... @@ -35,6 +38,8 @@
35 38 (eventName: 'save', value: Value): void;
36 39 }>();
37 40
  41 + const jsonEditor = ref<InstanceType<typeof JSONEditor>>();
  42 +
38 43 const messageType = ref(MessageTypesEnum.POST_TELEMETRY_REQUEST);
39 44 const messageTypeOptions = Object.keys(MessageTypesEnum).map((value) => ({
40 45 label: MessageTypesNameEnum[value],
... ... @@ -74,18 +79,34 @@
74 79 const msgType = unref(messageType);
75 80 const javascriptFunction = unref(scriptContent);
76 81
77   - return { msg, msgType, metadata: toRaw(unref(metadata)), javascriptFunction };
  82 + return {
  83 + msg,
  84 + msgType,
  85 + metadata: toRaw(unref(metadata)),
  86 + javascriptFunction,
  87 + };
78 88 };
79 89
80   - function executeTestScript() {
  90 + async function executeTestScript() {
81 91 try {
82 92 const { javaScriptEditorProps } = props;
83   - const { paramsName } = javaScriptEditorProps;
84   - const fn = new Function(...(paramsName || [])!, unref(scriptContent));
85   - const value = getValue();
86   - const executeParams = paramsName!.map((key) => value[key]);
  93 + const { paramsName, scriptType } = javaScriptEditorProps;
  94 + const { msg, msgType, metadata } = getValue();
  95 +
  96 + const { output, error } = await testScript({
  97 + argNames: paramsName,
  98 + scriptType,
  99 + script: unref(scriptContent),
  100 + msg: isString(msg) ? msg : JSON.stringify(msg),
  101 + metadata,
  102 + msgType,
  103 + });
  104 +
  105 + if (error) {
  106 + createMessage.error(error);
  107 + }
87 108
88   - return fn(...executeParams);
  109 + return output;
89 110 } catch (error) {
90 111 return error;
91 112 }
... ... @@ -96,9 +117,11 @@
96 117
97 118 flag && emit('test', getValue());
98 119
99   - const result = executeTestScript();
  120 + const result = await executeTestScript();
100 121
101 122 outputContent.value = result;
  123 + await nextTick();
  124 + unref(jsonEditor)?.handleFormat();
102 125 };
103 126
104 127 const handleSave = async () => {
... ... @@ -177,6 +200,7 @@
177 200 <Card class="w-full h-full">
178 201 <JSONEditor
179 202 v-model:value="outputContent"
  203 + ref="jsonEditor"
180 204 title="输出"
181 205 class="flex-auto"
182 206 height="100%"
... ...
... ... @@ -27,6 +27,7 @@
27 27
28 28 const handleSaveScript = (value: SaveValueType) => {
29 29 emit('save', value);
  30 +
30 31 closeModal();
31 32 };
32 33 </script>
... ...
... ... @@ -11,11 +11,13 @@
11 11 value?: string;
12 12 buttonName?: string;
13 13 javaScriptEditorProps?: InstanceType<typeof JavaScriptFunctionEditor>['$props'];
  14 + scriptType?: string;
14 15 }>(),
15 16 {
16 17 buttonName: 'Test Filter Function',
17 18 javaScriptEditorProps: () => ({
18 19 functionName: 'Filter',
  20 + scriptType: 'filter',
19 21 paramsName: ['msg', 'metadata', 'msgType'],
20 22 }),
21 23 }
... ...
... ... @@ -47,3 +47,9 @@
47 47 <div ref="javaEditorElRef" class="w-full h-full min-h-96"></div>
48 48 </BasicModal>
49 49 </template>
  50 +
  51 +<style>
  52 + .ace-github .ace_print-margin {
  53 + width: 0;
  54 + }
  55 +</style>
... ...
... ... @@ -229,7 +229,7 @@ export const trigger_condition_schema: FormSchema[] = [
229 229 placeholder: '请选择类型',
230 230 labelField: 'itemText',
231 231 valueField: 'itemValue',
232   - getPopupContainer: () => document.body,
  232 + getPopupContainer: (triggerNode) => triggerNode.parentNode,
233 233 onChange(e) {
234 234 if (e) {
235 235 setFieldsValue({ deviceProfileId: '' });
... ... @@ -256,7 +256,7 @@ export const trigger_condition_schema: FormSchema[] = [
256 256 placeholder: '请选择产品',
257 257 labelField: 'name',
258 258 valueField: 'id',
259   - getPopupContainer: () => document.body,
  259 + getPopupContainer: (triggerNode) => triggerNode.parentNode,
260 260 onChange: async (e) => {
261 261 if (e) {
262 262 setFieldsValue({ type2: '', entityId: [] });
... ... @@ -315,7 +315,7 @@ export const trigger_condition_schema: FormSchema[] = [
315 315 return [];
316 316 },
317 317 placeholder: '请选择设备',
318   - getPopupContainer: () => document.body,
  318 + getPopupContainer: (triggerNode) => triggerNode.parentNode,
319 319 filterOption: (inputValue: string, option: Record<'label' | 'value', string>) => {
320 320 let { label, value } = option;
321 321 label = label.toLowerCase();
... ... @@ -450,7 +450,7 @@ export const actionSchema: FormSchema[] = [
450 450 placeholder: '请选择类型',
451 451 labelField: 'itemText',
452 452 valueField: 'itemValue',
453   - getPopupContainer: () => document.body,
  453 + getPopupContainer: (triggerNode) => triggerNode.parentNode,
454 454 onChange(e) {
455 455 if (e) {
456 456 setFieldsValue({ deviceProfileId: '' });
... ... @@ -482,7 +482,7 @@ export const actionSchema: FormSchema[] = [
482 482 placeholder: '请选择产品',
483 483 labelField: 'name',
484 484 valueField: 'id',
485   - getPopupContainer: () => document.body,
  485 + getPopupContainer: (triggerNode) => triggerNode.parentNode,
486 486 onChange: (_value: string, options = {} as DeviceProfileModel) => {
487 487 const oldType = formModel['transportType'];
488 488
... ... @@ -556,7 +556,7 @@ export const actionSchema: FormSchema[] = [
556 556 return [];
557 557 },
558 558 placeholder: '请选择设备',
559   - getPopupContainer: () => document.body,
  559 + getPopupContainer: (triggerNode) => triggerNode.parentNode,
560 560 };
561 561 },
562 562 ifShow: ({ values }) => isPart(values.device) && isDeviceOut(values.outTarget),
... ... @@ -593,7 +593,7 @@ export const actionSchema: FormSchema[] = [
593 593 dictCode: 'custom_define',
594 594 },
595 595 numberToString: true,
596   - getPopupContainer: () => document.body,
  596 + getPopupContainer: (triggerNode) => triggerNode.parentNode,
597 597 onChange: () => {
598 598 setFieldsValue({ doContext: null, thingsModelId: null });
599 599 },
... ... @@ -619,7 +619,7 @@ export const actionSchema: FormSchema[] = [
619 619 },
620 620 labelField: 'itemText',
621 621 valueField: 'itemValue',
622   - getPopupContainer: () => document.body,
  622 + getPopupContainer: (triggerNode) => triggerNode.parentNode,
623 623 },
624 624 ifShow: ({ values }) =>
625 625 isDeviceOut(values.outTarget) && isPartOrAll(values.device) && isDefine(values.commandType),
... ... @@ -688,7 +688,7 @@ export const actionSchema: FormSchema[] = [
688 688 },
689 689 labelField: 'functionName',
690 690 valueField: 'id',
691   - getPopupContainer: () => document.body,
  691 + getPopupContainer: (triggerNode) => triggerNode.parentNode,
692 692 onChange: (_, options: ModelOfMatterParams) => {
693 693 if (options) {
694 694 // setFieldsValue({ doContext: { ...options.functionJson, callType: options.callType } });
... ...
... ... @@ -250,6 +250,7 @@ export function isType(operationType) {
250 250 field: 'operation',
251 251 label: '执行操作',
252 252 component: 'Select',
  253 + getPopupContainer: (triggerNode) => triggerNode.parentNode,
253 254 required: true,
254 255 componentProps: {
255 256 options: operationNumber_OR_TIME,
... ... @@ -286,6 +287,7 @@ export function isType(operationType) {
286 287 field: 'operation',
287 288 label: '执行操作',
288 289 component: 'Select',
  290 + getPopupContainer: (triggerNode) => triggerNode.parentNode,
289 291 required: true,
290 292 componentProps: {
291 293 options: operationString,
... ... @@ -310,6 +312,7 @@ export function isType(operationType) {
310 312 field: 'operation',
311 313 label: '执行操作',
312 314 component: 'Select',
  315 + getPopupContainer: (triggerNode) => triggerNode.parentNode,
313 316 required: true,
314 317 componentProps: {
315 318 options: operationBoolean,
... ... @@ -322,6 +325,7 @@ export function isType(operationType) {
322 325 field: 'value',
323 326 label: '操作值',
324 327 component: 'Select',
  328 + getPopupContainer: (triggerNode) => triggerNode.parentNode,
325 329 required: true,
326 330 componentProps: {
327 331 options: [
... ... @@ -347,6 +351,7 @@ export function isType(operationType) {
347 351 label: '执行操作',
348 352 required: true,
349 353 component: 'Select',
  354 + getPopupContainer: (triggerNode) => triggerNode.parentNode,
350 355 componentProps: {
351 356 options: operationNumber_OR_TIME,
352 357 },
... ...
... ... @@ -31,6 +31,7 @@
31 31 :options="options"
32 32 :disabled="disabled"
33 33 v-model:value="model[field]"
  34 + :getPopupContainer="(triggerNode) => triggerNode.parentNode"
34 35 @change="operationType = model[field]"
35 36 placeholder="请选择比较类型"
36 37 allowClear
... ...
... ... @@ -2,6 +2,14 @@ import { BasicColumn, FormSchema } from '/@/components/Table';
2 2 import { h } from 'vue';
3 3 import { Tag } from 'ant-design-vue';
4 4 import moment from 'moment';
  5 +// import {
  6 +// ClearAlarmFieldsEnum,
  7 +// ClearAlarmFieldsNameEnum,
  8 +// } from '../../designer/enum/formField/action';
  9 +import { useComponentRegister } from '/@/components/Form';
  10 +import { JavascriptEditorWithTestModal } from '../../designer/src/components/JavaScriptFilterModal';
  11 +
  12 +useComponentRegister('JavascriptEditorWithTestModal', JavascriptEditorWithTestModal);
5 13
6 14 export enum StatusEnum {
7 15 ENABLE = 1,
... ... @@ -129,11 +137,25 @@ export const formSchema: FormSchema[] = [
129 137 placeholder: '请输入说明',
130 138 },
131 139 },
  140 + // {
  141 + // field: 'function',
  142 + // label: '',
  143 + // component: 'Input',
  144 + // slot: 'function',
  145 + // },
132 146 {
133 147 field: 'function',
134   - label: '',
135   - component: 'Input',
136   - slot: 'function',
  148 + component: 'JavascriptEditorWithTestModal',
  149 + label: '转换函数',
  150 + changeEvent: 'update:value',
  151 + componentProps: {
  152 + javaScriptEditorProps: {
  153 + functionName: 'Details',
  154 + paramsName: ['msg', 'metadata', 'msgType'],
  155 + scriptType: 'json',
  156 + },
  157 + buttonName: '测试转换功能',
  158 + },
137 159 },
138 160 {
139 161 field: 'id',
... ...
... ... @@ -111,6 +111,7 @@
111 111 aceEditor.value.setValue(
112 112 jsScript ?? 'return {msg: msg, metadata: metadata, msgType: msgType};'
113 113 );
  114 + setFieldsValue({ function: 'return {msg: msg, metadata: metadata, msgType: msgType};' });
114 115 beautify(aceEditor.value.session);
115 116 };
116 117
... ...
... ... @@ -148,6 +148,7 @@
148 148 { value: 'DEFAULT', label: '默认' },
149 149 { value: 'SYSTEM', label: '系统' },
150 150 { value: 'REPORT', label: '报表' },
  151 + { value: 'TASK_CENTER', label: '任务中心' },
151 152 ]);
152 153 const optionStatus: any = ref([
153 154 { value: '1 ', label: '成功' },
... ...
... ... @@ -45,7 +45,7 @@
45 45 min: 0,
46 46 max: 100,
47 47 formatter: (e) => {
48   - const value = e.replace(/^0/g, '');
  48 + const value = e?.toString().replace(/^0/g, '');
49 49 if (value) {
50 50 return value.replace(/^0/g, '');
51 51 } else {
... ...
... ... @@ -24,6 +24,18 @@
24 24 label: '文本字体大小',
25 25 component: 'InputNumber',
26 26 defaultValue: 14,
  27 + componentProps: {
  28 + min: 0,
  29 + max: 100,
  30 + formatter: (e) => {
  31 + const value = e?.toString().replace(/^0/g, '');
  32 + if (value) {
  33 + return value.replace(/^0/g, '');
  34 + } else {
  35 + return 0;
  36 + }
  37 + },
  38 + },
27 39 },
28 40 {
29 41 field: ComponentConfigFieldEnum.SHOW_DEVICE_NAME,
... ...
... ... @@ -36,7 +36,19 @@
36 36 field: ComponentConfigFieldEnum.FONT_SIZE,
37 37 label: '文本字体大小',
38 38 component: 'InputNumber',
39   - defaultValue: option.fontSize,
  39 + defaultValue: 14,
  40 + componentProps: {
  41 + min: 0,
  42 + max: 100,
  43 + formatter: (e) => {
  44 + const value = e?.toString().replace(/^0/g, '');
  45 + if (value) {
  46 + return value.replace(/^0/g, '');
  47 + } else {
  48 + return 0;
  49 + }
  50 + },
  51 + },
40 52 },
41 53 {
42 54 field: ComponentConfigFieldEnum.PASS_WORD,
... ...
... ... @@ -22,7 +22,19 @@
22 22 field: ComponentConfigFieldEnum.FONT_SIZE,
23 23 label: '文本字体大小',
24 24 component: 'InputNumber',
25   - defaultValue: option.fontSize,
  25 + defaultValue: 14,
  26 + componentProps: {
  27 + min: 0,
  28 + max: 100,
  29 + formatter: (e) => {
  30 + const value = e?.toString().replace(/^0/g, '');
  31 + if (value) {
  32 + return value.replace(/^0/g, '');
  33 + } else {
  34 + return 0;
  35 + }
  36 + },
  37 + },
26 38 },
27 39 {
28 40 field: ComponentConfigFieldEnum.PASS_WORD,
... ...
... ... @@ -18,7 +18,19 @@
18 18 field: ComponentConfigFieldEnum.VALUE_SIZE,
19 19 label: '数值字体大小',
20 20 component: 'InputNumber',
21   - defaultValue: option.fontSize,
  21 + defaultValue: 14,
  22 + componentProps: {
  23 + min: 0,
  24 + max: 100,
  25 + formatter: (e) => {
  26 + const value = e?.toString().replace(/^0/g, '');
  27 + if (value) {
  28 + return value.replace(/^0/g, '');
  29 + } else {
  30 + return 0;
  31 + }
  32 + },
  33 + },
22 34 },
23 35 {
24 36 field: ComponentConfigFieldEnum.TEXT_COLOR,
... ...
... ... @@ -30,7 +30,19 @@
30 30 field: ComponentConfigFieldEnum.FONT_SIZE,
31 31 label: '文本字体大小',
32 32 component: 'InputNumber',
33   - defaultValue: option.fontSize,
  33 + defaultValue: 14,
  34 + componentProps: {
  35 + min: 0,
  36 + max: 100,
  37 + formatter: (e) => {
  38 + const value = e?.toString().replace(/^0/g, '');
  39 + if (value) {
  40 + return value.replace(/^0/g, '');
  41 + } else {
  42 + return 0;
  43 + }
  44 + },
  45 + },
34 46 },
35 47 {
36 48 field: ComponentConfigFieldEnum.SHOW_DEVICE_NAME,
... ...
... ... @@ -78,6 +78,6 @@ export const getSendValues = async (option, getDesign, checked) => {
78 78
79 79 export const CommandTypeEnumLIst = {
80 80 '0': { CUSTOM: 0, name: '自定义' },
81   - '1': { CUSTOM: 0, name: '服务' },
82   - '2': { CUSTOM: 0, name: '属性' },
  81 + '1': { CUSTOM: 1, name: '服务' },
  82 + '2': { CUSTOM: 2, name: '属性' },
83 83 };
... ...
... ... @@ -58,7 +58,7 @@
58 58 min: 0,
59 59 max: 100,
60 60 formatter: (e) => {
61   - const value = e.replace(/^0/g, '');
  61 + const value = e?.toString().replace(/^0/g, '');
62 62 if (value) {
63 63 return value.replace(/^0/g, '');
64 64 } else {
... ... @@ -76,7 +76,7 @@
76 76 min: 0,
77 77 max: 100,
78 78 formatter: (e) => {
79   - const value = e.replace(/^0/g, '');
  79 + const value = e?.toString().replace(/^0/g, '');
80 80 if (value) {
81 81 return value.replace(/^0/g, '');
82 82 } else {
... ...
... ... @@ -64,7 +64,7 @@
64 64 min: 0,
65 65 max: 100,
66 66 formatter: (e) => {
67   - const value = e.replace(/^0/g, '');
  67 + const value = e?.toString().replace(/^0/g, '');
68 68 if (value) {
69 69 return value.replace(/^0/g, '');
70 70 } else {
... ... @@ -82,7 +82,7 @@
82 82 min: 0,
83 83 max: 100,
84 84 formatter: (e) => {
85   - const value = e.replace(/^0/g, '');
  85 + const value = e?.toString().replace(/^0/g, '');
86 86 if (value) {
87 87 return value.replace(/^0/g, '');
88 88 } else {
... ...
... ... @@ -15,6 +15,7 @@ export const option: PublicPresetOptions = {
15 15 [ComponentConfigFieldEnum.SHOW_TIME]: false,
16 16 [ComponentConfigFieldEnum.FONT_SIZE]: 14,
17 17 [ComponentConfigFieldEnum.VALUE_SIZE]: 20,
  18 + [ComponentConfigFieldEnum.MAX_NUMBER]: 120,
18 19 };
19 20
20 21 export default class Config extends PublicConfigClass implements CreateComponentType {
... ...
... ... @@ -2,6 +2,7 @@
2 2 import { ComponentConfigFieldEnum } from '/@/views/visual/packages/enum';
3 3 import { useForm, BasicForm } from '/@/components/Form';
4 4 import { PublicFormInstaceType } from '/@/views/visual/dataSourceBindPanel/index.type';
  5 + import { nextTick } from 'vue';
5 6
6 7 const [register, { getFieldsValue, setFieldsValue, resetFields }] = useForm({
7 8 schemas: [
... ... @@ -28,7 +29,7 @@
28 29 min: 0,
29 30 max: 100,
30 31 formatter: (e) => {
31   - const value = e.replace(/^0/g, '');
  32 + const value = e?.toString().replace(/^0/g, '');
32 33 if (value) {
33 34 return value.replace(/^0/g, '');
34 35 } else {
... ...
... ... @@ -18,16 +18,38 @@
18 18
19 19 const time = ref<Nullable<number>>(null);
20 20
  21 + const POSITIVE_NUMBER = 6;
  22 +
  23 + const ALL_PART = 7;
  24 +
  25 + const DEFAULT_PART_VALUE = 20;
  26 +
  27 + const partValue = computed(() => {
  28 + const { config } = props;
  29 + const { option } = config;
  30 + const { componentInfo } = option;
  31 + const { maxNumber = 120 } = componentInfo || {};
  32 + let value = maxNumber / POSITIVE_NUMBER;
  33 + return value % 10 > 0 ? Math.floor(value / 10) * 10 + 10 : value + 10;
  34 + });
  35 +
  36 + const maxValueScale = computed(() => unref(partValue) / DEFAULT_PART_VALUE);
  37 +
21 38 const getValue = computed(() => {
22 39 const maxHeight = 190;
23 40 const minHeight = 15;
  41 +
24 42 const height = maxHeight - minHeight;
25   - const rangeNumber = 7;
26   - const itemRange = 20;
27   - const itemHeight = height / (rangeNumber * itemRange);
  43 +
  44 + const itemHeight = height / (ALL_PART * (DEFAULT_PART_VALUE * unref(maxValueScale)));
  45 +
28 46 const value = unref(currentValue);
29   - const transformValue =
30   - maxHeight - (value >= 0 ? value + 20 : itemRange - Math.abs(value)) * itemHeight;
  47 + let transformValue =
  48 + maxHeight -
  49 + (value >= 0
  50 + ? value + unref(DEFAULT_PART_VALUE * unref(maxValueScale))
  51 + : DEFAULT_PART_VALUE * unref(maxValueScale) - Math.abs(value)) *
  52 + itemHeight;
31 53
32 54 return transformValue >= maxHeight
33 55 ? maxHeight
... ... @@ -184,7 +206,9 @@
184 206 style="stroke: rgb(136, 136, 136); shape-rendering: crispEdges; stroke-width: 1px"
185 207 />
186 208 <foreignObject xmlns="http://www.w3.org/2000/svg" x="-55" y="-10" width="45" height="20">
187   - <div class="tick-label" xmlns="http://www.w3.org/1999/xhtml">-20</div>
  209 + <div class="tick-label" xmlns="http://www.w3.org/1999/xhtml">
  210 + {{ partValue * -1 }}
  211 + </div>
188 212 </foreignObject>
189 213 </g>
190 214 <g class="tick" opacity="1" transform="translate(0,165)">
... ... @@ -204,7 +228,9 @@
204 228 style="stroke: rgb(136, 136, 136); shape-rendering: crispEdges; stroke-width: 1px"
205 229 />
206 230 <foreignObject xmlns="http://www.w3.org/2000/svg" x="-55" y="-10" width="45" height="20">
207   - <div class="tick-label" xmlns="http://www.w3.org/1999/xhtml">20</div>
  231 + <div class="tick-label" xmlns="http://www.w3.org/1999/xhtml">
  232 + {{ partValue }}
  233 + </div>
208 234 </foreignObject>
209 235 </g>
210 236 <g class="tick" opacity="1" transform="translate(0,115)">
... ... @@ -214,7 +240,9 @@
214 240 style="stroke: rgb(136, 136, 136); shape-rendering: crispEdges; stroke-width: 1px"
215 241 />
216 242 <foreignObject xmlns="http://www.w3.org/2000/svg" x="-55" y="-10" width="45" height="20">
217   - <div class="tick-label" xmlns="http://www.w3.org/1999/xhtml">40</div>
  243 + <div class="tick-label" xmlns="http://www.w3.org/1999/xhtml">
  244 + {{ partValue * 2 }}
  245 + </div>
218 246 </foreignObject>
219 247 </g>
220 248 <g class="tick" opacity="1" transform="translate(0,90)">
... ... @@ -224,7 +252,9 @@
224 252 style="stroke: rgb(136, 136, 136); shape-rendering: crispEdges; stroke-width: 1px"
225 253 />
226 254 <foreignObject xmlns="http://www.w3.org/2000/svg" x="-55" y="-10" width="45" height="20">
227   - <div class="tick-label" xmlns="http://www.w3.org/1999/xhtml">60</div>
  255 + <div class="tick-label" xmlns="http://www.w3.org/1999/xhtml">
  256 + {{ partValue * 3 }}
  257 + </div>
228 258 </foreignObject>
229 259 </g>
230 260 <g class="tick" opacity="1" transform="translate(0,65)">
... ... @@ -234,7 +264,9 @@
234 264 style="stroke: rgb(136, 136, 136); shape-rendering: crispEdges; stroke-width: 1px"
235 265 />
236 266 <foreignObject xmlns="http://www.w3.org/2000/svg" x="-55" y="-10" width="45" height="20">
237   - <div class="tick-label" xmlns="http://www.w3.org/1999/xhtml">80</div>
  267 + <div class="tick-label" xmlns="http://www.w3.org/1999/xhtml">
  268 + {{ partValue * 4 }}
  269 + </div>
238 270 </foreignObject>
239 271 </g>
240 272 <g class="tick" opacity="1" transform="translate(0,40)">
... ... @@ -244,7 +276,9 @@
244 276 style="stroke: rgb(136, 136, 136); shape-rendering: crispEdges; stroke-width: 1px"
245 277 />
246 278 <foreignObject xmlns="http://www.w3.org/2000/svg" x="-55" y="-10" width="45" height="20">
247   - <div class="tick-label" xmlns="http://www.w3.org/1999/xhtml">100</div>
  279 + <div class="tick-label" xmlns="http://www.w3.org/1999/xhtml">
  280 + {{ partValue * 5 }}
  281 + </div>
248 282 </foreignObject>
249 283 </g>
250 284 <g class="tick" opacity="1" transform="translate(0,15)">
... ... @@ -254,7 +288,9 @@
254 288 style="stroke: rgb(136, 136, 136); shape-rendering: crispEdges; stroke-width: 1px"
255 289 />
256 290 <foreignObject xmlns="http://www.w3.org/2000/svg" x="-55" y="-10" width="45" height="20">
257   - <div class="tick-label" xmlns="http://www.w3.org/1999/xhtml">120</div>
  291 + <div class="tick-label" xmlns="http://www.w3.org/1999/xhtml">
  292 + {{ partValue * 6 }}
  293 + </div>
258 294 </foreignObject>
259 295 </g>
260 296 </g>
... ...
... ... @@ -58,7 +58,7 @@
58 58 min: 0,
59 59 max: 100,
60 60 formatter: (e) => {
61   - const value = e.replace(/^0/g, '');
  61 + const value = e?.toString().replace(/^0/g, '');
62 62 if (value) {
63 63 return value.replace(/^0/g, '');
64 64 } else {
... ... @@ -76,7 +76,7 @@
76 76 min: 0,
77 77 max: 100,
78 78 formatter: (e) => {
79   - const value = e.replace(/^0/g, '');
  79 + const value = e?.toString().replace(/^0/g, '');
80 80 if (value) {
81 81 return value.replace(/^0/g, '');
82 82 } else {
... ...
... ... @@ -72,7 +72,7 @@
72 72 min: 0,
73 73 max: 100,
74 74 formatter: (e) => {
75   - const value = e.replace(/^0/g, '');
  75 + const value = e?.toString().replace(/^0/g, '');
76 76 if (value) {
77 77 return value.replace(/^0/g, '');
78 78 } else {
... ... @@ -90,7 +90,7 @@
90 90 min: 0,
91 91 max: 100,
92 92 formatter: (e) => {
93   - const value = e.replace(/^0/g, '');
  93 + const value = e?.toString().replace(/^0/g, '');
94 94 if (value) {
95 95 return value.replace(/^0/g, '');
96 96 } else {
... ...
... ... @@ -51,7 +51,7 @@
51 51 min: 0,
52 52 max: 100,
53 53 formatter: (e) => {
54   - const value = e.replace(/^0/g, '');
  54 + const value = e?.toString().replace(/^0/g, '');
55 55 if (value) {
56 56 return value.replace(/^0/g, '');
57 57 } else {
... ... @@ -69,7 +69,7 @@
69 69 min: 0,
70 70 max: 100,
71 71 formatter: (e) => {
72   - const value = e.replace(/^0/g, '');
  72 + const value = e?.toString().replace(/^0/g, '');
73 73 if (value) {
74 74 return value.replace(/^0/g, '');
75 75 } else {
... ...
... ... @@ -62,7 +62,7 @@
62 62 min: 0,
63 63 max: 100,
64 64 formatter: (e) => {
65   - const value = e.replace(/^0/g, '');
  65 + const value = e?.toString().replace(/^0/g, '');
66 66 if (value) {
67 67 return value.replace(/^0/g, '');
68 68 } else {
... ... @@ -80,7 +80,7 @@
80 80 min: 0,
81 81 max: 100,
82 82 formatter: (e) => {
83   - const value = e.replace(/^0/g, '');
  83 + const value = e?.toString().replace(/^0/g, '');
84 84 if (value) {
85 85 return value.replace(/^0/g, '');
86 86 } else {
... ...
... ... @@ -85,7 +85,7 @@
85 85 min: 0,
86 86 max: 100,
87 87 formatter: (e) => {
88   - const value = e.replace(/^0/g, '');
  88 + const value = e?.toString().replace(/^0/g, '');
89 89 if (value) {
90 90 return value.replace(/^0/g, '');
91 91 } else {
... ... @@ -103,7 +103,7 @@
103 103 min: 0,
104 104 max: 100,
105 105 formatter: (e) => {
106   - const value = e.replace(/^0/g, '');
  106 + const value = e?.toString().replace(/^0/g, '');
107 107 if (value) {
108 108 return value.replace(/^0/g, '');
109 109 } else {
... ...
... ... @@ -33,7 +33,7 @@
33 33 min: 0,
34 34 max: 100,
35 35 formatter: (e) => {
36   - const value = e.replace(/^0/g, '');
  36 + const value = e?.toString().replace(/^0/g, '');
37 37 if (value) {
38 38 return value.replace(/^0/g, '');
39 39 } else {
... ...
... ... @@ -55,7 +55,7 @@
55 55 min: 0,
56 56 max: 100,
57 57 formatter: (e) => {
58   - const value = e.replace(/^0/g, '');
  58 + const value = e?.toString().replace(/^0/g, '');
59 59 if (value) {
60 60 return value.replace(/^0/g, '');
61 61 } else {
... ...
... ... @@ -28,7 +28,7 @@
28 28 min: 0,
29 29 max: 100,
30 30 formatter: (e) => {
31   - const value = e.replace(/^0/g, '');
  31 + const value = e?.toString().replace(/^0/g, '');
32 32 if (value) {
33 33 return value.replace(/^0/g, '');
34 34 } else {
... ... @@ -45,7 +45,7 @@
45 45 componentProps: {
46 46 min: 0,
47 47 formatter: (e) => {
48   - const value = e.replace(/^0/g, '');
  48 + const value = e?.toString().replace(/^0/g, '');
49 49 if (value) {
50 50 return value.replace(/^0/g, '');
51 51 } else {
... ...
... ... @@ -28,7 +28,7 @@
28 28 min: 0,
29 29 max: 100,
30 30 formatter: (e) => {
31   - const value = e.replace(/^0/g, '');
  31 + const value = e?.toString().replace(/^0/g, '');
32 32 if (value) {
33 33 return value.replace(/^0/g, '');
34 34 } else {
... ... @@ -46,7 +46,7 @@
46 46 min: 0,
47 47 max: 100,
48 48 formatter: (e) => {
49   - const value = e.replace(/^0/g, '');
  49 + const value = e?.toString().replace(/^0/g, '');
50 50 if (value) {
51 51 return value.replace(/^0/g, '');
52 52 } else {
... ...
... ... @@ -33,7 +33,7 @@
33 33 min: 0,
34 34 max: 100,
35 35 formatter: (e) => {
36   - const value = e.replace(/^0/g, '');
  36 + const value = e?.toString().replace(/^0/g, '');
37 37 if (value) {
38 38 return value.replace(/^0/g, '');
39 39 } else {
... ... @@ -51,7 +51,7 @@
51 51 min: 0,
52 52 max: 100,
53 53 formatter: (e) => {
54   - const value = e.replace(/^0/g, '');
  54 + const value = e?.toString().replace(/^0/g, '');
55 55 if (value) {
56 56 return value.replace(/^0/g, '');
57 57 } else {
... ...
... ... @@ -32,7 +32,7 @@
32 32 min: 0,
33 33 max: 100,
34 34 formatter: (e) => {
35   - const value = e.replace(/^0/g, '');
  35 + const value = e?.toString().replace(/^0/g, '');
36 36 if (value) {
37 37 return value.replace(/^0/g, '');
38 38 } else {
... ... @@ -50,7 +50,7 @@
50 50 min: 0,
51 51 max: 100,
52 52 formatter: (e) => {
53   - const value = e.replace(/^0/g, '');
  53 + const value = e?.toString().replace(/^0/g, '');
54 54 if (value) {
55 55 return value.replace(/^0/g, '');
56 56 } else {
... ...
... ... @@ -34,7 +34,7 @@
34 34 min: 0,
35 35 max: 100,
36 36 formatter: (e) => {
37   - const value = e.replace(/^0/g, '');
  37 + const value = e?.toString().replace(/^0/g, '');
38 38 if (value) {
39 39 return value.replace(/^0/g, '');
40 40 } else {
... ... @@ -52,7 +52,7 @@
52 52 min: 0,
53 53 max: 100,
54 54 formatter: (e) => {
55   - const value = e.replace(/^0/g, '');
  55 + const value = e?.toString().replace(/^0/g, '');
56 56 if (value) {
57 57 return value.replace(/^0/g, '');
58 58 } else {
... ...
... ... @@ -32,7 +32,7 @@
32 32 componentProps: {
33 33 min: 0,
34 34 formatter: (e) => {
35   - const value = e.replace(/^0/g, '');
  35 + const value = e?.toString().replace(/^0/g, '');
36 36 if (value) {
37 37 return value.replace(/^0/g, '');
38 38 } else {
... ... @@ -50,7 +50,7 @@
50 50 min: 0,
51 51 max: 100,
52 52 formatter: (e) => {
53   - const value = e.replace(/^0/g, '');
  53 + const value = e?.toString().replace(/^0/g, '');
54 54 if (value) {
55 55 return value.replace(/^0/g, '');
56 56 } else {
... ...
... ... @@ -10,7 +10,7 @@ export function useSendCommand() {
10 10 const loading = ref(false);
11 11
12 12 const error = () => {
13   - createMessage.error('下发指令失败');
  13 + // createMessage.error('下发指令失败');
14 14 return false;
15 15 };
16 16
... ... @@ -52,6 +52,7 @@ export function useSendCommand() {
52 52 createMessage.success('命令下发成功');
53 53 return true;
54 54 } catch (msg) {
  55 + console.error(msg);
55 56 return error();
56 57 } finally {
57 58 loading.value = false;
... ...
... ... @@ -8,6 +8,7 @@ import {
8 8 getPacketIntervalByValue,
9 9 intervalOption,
10 10 } from '/@/views/device/localtion/cpns/TimePeriodForm/helper';
  11 +import { OrderByEnum, OrderByNameEnum } from '/@/views/device/localtion/cpns/TimePeriodForm/config';
11 12 export enum QueryWay {
12 13 LATEST = 'latest',
13 14 TIME_PERIOD = 'timePeriod',
... ... @@ -200,6 +201,18 @@ export const formSchema = (): FormSchema[] => {
200 201 getPopupContainer: () => document.body,
201 202 },
202 203 },
  204 + {
  205 + field: SchemaFiled.ORDER_BY,
  206 + label: '数据排序',
  207 + component: 'Select',
  208 + defaultValue: OrderByEnum.ASC,
  209 + componentProps: {
  210 + options: Object.values(OrderByEnum).map((value) => ({
  211 + value,
  212 + label: OrderByNameEnum[value],
  213 + })),
  214 + },
  215 + },
203 216 ];
204 217 };
205 218
... ...
... ... @@ -22,6 +22,7 @@
22 22 import { ModalParamsType } from '/#/utils';
23 23 import { WidgetDataType } from '../../hooks/useDataSource';
24 24 import { ExtraDataSource } from '../../types';
  25 + import { OrderByEnum } from '/@/views/device/localtion/cpns/TimePeriodForm/config';
25 26
26 27 type DeviceOption = Record<'label' | 'value' | 'organizationId', string>;
27 28
... ... @@ -211,6 +212,7 @@
211 212 endTs: Date.now(),
212 213 agg: AggregateDataEnum.NONE,
213 214 limit: 7,
  215 + orderBy: OrderByEnum.ASC,
214 216 });
215 217 historyData.value = getTableHistoryData(res);
216 218 // 判断对象是否为空
... ...