Commit ca118a71af2ff60c5dfe1762725b8741656b2d66

Authored by ww
1 parent 356ba0a0

feat: ota page add permission control

@@ -81,7 +81,13 @@ @@ -81,7 +81,13 @@
81 </script> 81 </script>
82 82
83 <template> 83 <template>
84 - <BasicModal title="包管理" destroy-on-close @register="registerModal" @ok="handleSubmit"> 84 + <BasicModal
  85 + title="包管理"
  86 + destroy-on-close
  87 + :ok-button-props="{ loading }"
  88 + @register="registerModal"
  89 + @ok="handleSubmit"
  90 + >
85 <BasicForm @register="registerForm" class="package-manage-form" /> 91 <BasicForm @register="registerForm" class="package-manage-form" />
86 </BasicModal> 92 </BasicModal>
87 </template> 93 </template>
@@ -15,6 +15,8 @@ @@ -15,6 +15,8 @@
15 } from '/@/api/ota'; 15 } from '/@/api/ota';
16 import { useCopyToClipboard } from '/@/hooks/web/useCopyToClipboard'; 16 import { useCopyToClipboard } from '/@/hooks/web/useCopyToClipboard';
17 import { useDownload } from '../hook/useDownload'; 17 import { useDownload } from '../hook/useDownload';
  18 + import { Authority } from '/@/components/Authority';
  19 + import { OtaPermissionKey } from '../config/config';
18 // import DeviceDetailDrawer from '/@/views/device/list/cpns/modal/DeviceDetailDrawer.vue'; 20 // import DeviceDetailDrawer from '/@/views/device/list/cpns/modal/DeviceDetailDrawer.vue';
19 21
20 const emit = defineEmits(['register', 'update:list']); 22 const emit = defineEmits(['register', 'update:list']);
@@ -143,7 +145,9 @@ @@ -143,7 +145,9 @@
143 class="absolute right-0 bottom-0 w-full border-t bg-light-50 border-t-gray-100 py-2 px-4 text-right" 145 class="absolute right-0 bottom-0 w-full border-t bg-light-50 border-t-gray-100 py-2 px-4 text-right"
144 > 146 >
145 <Button class="mr-2" @click="closeDrawer">取消</Button> 147 <Button class="mr-2" @click="closeDrawer">取消</Button>
146 - <Button type="primary" :loading="loading" @click="handleSubmit">保存</Button> 148 + <Authority :value="OtaPermissionKey.UPDATE">
  149 + <Button type="primary" :loading="loading" @click="handleSubmit">保存</Button>
  150 + </Authority>
147 </div> 151 </div>
148 </template> 152 </template>
149 <!-- <DeviceDetailDrawer @register="registerTBDrawer" /> --> 153 <!-- <DeviceDetailDrawer @register="registerTBDrawer" /> -->
@@ -8,6 +8,13 @@ export interface ModalPassRecord { @@ -8,6 +8,13 @@ export interface ModalPassRecord {
8 record?: Recordable; 8 record?: Recordable;
9 } 9 }
10 10
  11 +export enum OtaPermissionKey {
  12 + CREATE = 'api:operation:ota:post',
  13 + UPDATE = 'api:operation:ota:update',
  14 + DELETE = 'api:operation:ota:delete',
  15 + DOWNLOAD = 'api:operation:ota:download',
  16 +}
  17 +
11 export const columns: BasicColumn[] = [ 18 export const columns: BasicColumn[] = [
12 { 19 {
13 title: '创建时间', 20 title: '创建时间',
@@ -45,14 +45,29 @@ export enum ALG { @@ -45,14 +45,29 @@ export enum ALG {
45 MURMUR3_128 = 'MURMUR3128', 45 MURMUR3_128 = 'MURMUR3128',
46 } 46 }
47 47
  48 +const getVersionTag = (title: string, version: string) => {
  49 + return `${title ?? ''} ${version ?? ''}`;
  50 +};
  51 +
48 export const formSchema: FormSchema[] = [ 52 export const formSchema: FormSchema[] = [
49 { 53 {
50 field: PackageField.TITLE, 54 field: PackageField.TITLE,
51 label: '标题', 55 label: '标题',
52 component: 'Input', 56 component: 'Input',
53 rules: [{ required: true, message: '标题为必填项' }], 57 rules: [{ required: true, message: '标题为必填项' }],
54 - componentProps: {  
55 - placeholder: '请输入标题', 58 + componentProps: ({ formActionType, formModel }) => {
  59 + const { setFieldsValue } = formActionType;
  60 + return {
  61 + placeholder: '请输入标题',
  62 + onChange: (value: Event) => {
  63 + setFieldsValue({
  64 + [PackageField.VERSION_TAG]: getVersionTag(
  65 + (value.target as HTMLInputElement).value,
  66 + formModel[PackageField.VERSION]
  67 + ),
  68 + });
  69 + },
  70 + };
56 }, 71 },
57 }, 72 },
58 { 73 {
@@ -60,8 +75,19 @@ export const formSchema: FormSchema[] = [ @@ -60,8 +75,19 @@ export const formSchema: FormSchema[] = [
60 label: '版本', 75 label: '版本',
61 component: 'Input', 76 component: 'Input',
62 rules: [{ required: true, message: '版本为必填项' }], 77 rules: [{ required: true, message: '版本为必填项' }],
63 - componentProps: {  
64 - placeholder: '请输入版本', 78 + componentProps: ({ formActionType, formModel }) => {
  79 + const { setFieldsValue } = formActionType;
  80 + return {
  81 + placeholder: '请输入版本',
  82 + onChange: (value: Event) => {
  83 + setFieldsValue({
  84 + [PackageField.VERSION_TAG]: getVersionTag(
  85 + formModel[PackageField.TITLE],
  86 + (value.target as HTMLInputElement).value
  87 + ),
  88 + });
  89 + },
  90 + };
65 }, 91 },
66 }, 92 },
67 { 93 {
1 <script lang="ts" setup> 1 <script lang="ts" setup>
2 import { Button } from 'ant-design-vue'; 2 import { Button } from 'ant-design-vue';
3 - import { columns, ModalPassRecord, searchFormSchema } from './config/config'; 3 + import { columns, ModalPassRecord, OtaPermissionKey, searchFormSchema } from './config/config';
4 import { PageWrapper } from '/@/components/Page'; 4 import { PageWrapper } from '/@/components/Page';
5 import { BasicTable, useTable, TableAction } from '/@/components/Table'; 5 import { BasicTable, useTable, TableAction } from '/@/components/Table';
6 import PackageDetailModal from './components/PackageDetailModal.vue'; 6 import PackageDetailModal from './components/PackageDetailModal.vue';
@@ -13,6 +13,7 @@ @@ -13,6 +13,7 @@
13 import { useDownload } from './hook/useDownload'; 13 import { useDownload } from './hook/useDownload';
14 import { computed } from 'vue'; 14 import { computed } from 'vue';
15 import { useSyncConfirm } from '/@/hooks/component/useSyncConfirm'; 15 import { useSyncConfirm } from '/@/hooks/component/useSyncConfirm';
  16 + import { Authority } from '/@/components/Authority';
16 17
17 const [register, { reload, getSelectRowKeys, getRowSelection, setSelectedRowKeys }] = useTable({ 18 const [register, { reload, getSelectRowKeys, getRowSelection, setSelectedRowKeys }] = useTable({
18 columns, 19 columns,
@@ -102,10 +103,14 @@ @@ -102,10 +103,14 @@
102 <PageWrapper dense contentFullHeight contentClass="flex flex-col"> 103 <PageWrapper dense contentFullHeight contentClass="flex flex-col">
103 <BasicTable @register="register" @row-click="handleOpenDetailDrawer" class="ota-list"> 104 <BasicTable @register="register" @row-click="handleOpenDetailDrawer" class="ota-list">
104 <template #toolbar> 105 <template #toolbar>
105 - <Button @click="handleCreatePackage" type="primary">新增包</Button>  
106 - <Button @click="handleBatchDelete" :disabled="canDelete" type="primary" danger>  
107 - 批量删除  
108 - </Button> 106 + <Authority :value="OtaPermissionKey.CREATE">
  107 + <Button @click="handleCreatePackage" type="primary">新增包</Button>
  108 + </Authority>
  109 + <Authority :value="OtaPermissionKey.DELETE">
  110 + <Button @click="handleBatchDelete" :disabled="canDelete" type="primary" danger>
  111 + 批量删除
  112 + </Button>
  113 + </Authority>
109 </template> 114 </template>
110 <template #action="{ record }"> 115 <template #action="{ record }">
111 <TableAction 116 <TableAction
@@ -114,12 +119,14 @@ @@ -114,12 +119,14 @@
114 { 119 {
115 label: '下载', 120 label: '下载',
116 icon: 'ant-design:download-outlined', 121 icon: 'ant-design:download-outlined',
  122 + auth: OtaPermissionKey.DOWNLOAD,
117 onClick: downloadFile.bind(null, record), 123 onClick: downloadFile.bind(null, record),
118 }, 124 },
119 { 125 {
120 label: '删除', 126 label: '删除',
121 icon: 'ant-design:delete-outlined', 127 icon: 'ant-design:delete-outlined',
122 color: 'error', 128 color: 'error',
  129 + auth: OtaPermissionKey.DELETE,
123 popConfirm: { 130 popConfirm: {
124 title: '是否确认删除', 131 title: '是否确认删除',
125 confirm: deletePackage.bind(null, record), 132 confirm: deletePackage.bind(null, record),
@@ -17,7 +17,7 @@ @@ -17,7 +17,7 @@
17 :row-props="{ 17 :row-props="{
18 gutter: 10, 18 gutter: 10,
19 }" 19 }"
20 - layout="inline" 20 + layout="horizontal"
21 :label-col="{ span: 0 }" 21 :label-col="{ span: 0 }"
22 /> 22 />
23 </template> 23 </template>
@@ -39,7 +39,7 @@ @@ -39,7 +39,7 @@
39 :row-props="{ 39 :row-props="{
40 gutter: 10, 40 gutter: 10,
41 }" 41 }"
42 - layout="inline" 42 + layout="horizontal"
43 :label-col="{ span: 0 }" 43 :label-col="{ span: 0 }"
44 /> 44 />
45 </div> 45 </div>
  1 +<script lang="ts" setup>
  2 + import { ref, unref } from 'vue';
  3 + import { BasicForm, FormActionType } from '/@/components/Form';
  4 + import { mapFormSchema } from '../../config/basicConfiguration';
  5 +
  6 + const formEl = ref<Nullable<FormActionType>>();
  7 +
  8 + const setFormEl = (el: any) => {
  9 + formEl.value = el;
  10 + };
  11 +
  12 + const getFieldsValue = () => {
  13 + return unref(formEl)!.getFieldsValue();
  14 + };
  15 +
  16 + const validate = async () => {
  17 + await unref(formEl)!.validate();
  18 + };
  19 +
  20 + const setFieldsValue = async (record: Recordable) => {
  21 + await unref(formEl)!.setFieldsValue(record);
  22 + };
  23 +
  24 + const clearValidate = async (name?: string | string[]) => {
  25 + await unref(formEl)!.clearValidate(name);
  26 + };
  27 + defineExpose({
  28 + formActionType: { getFieldsValue, validate, setFieldsValue, clearValidate },
  29 + });
  30 +</script>
  31 +
  32 +<template>
  33 + <div class="w-full flex-1">
  34 + <BasicForm
  35 + :ref="(el) => setFormEl(el)"
  36 + :schemas="mapFormSchema"
  37 + class="w-full flex-1 data-source-form"
  38 + :show-action-button-group="false"
  39 + :row-props="{
  40 + gutter: 10,
  41 + }"
  42 + layout="horizontal"
  43 + :label-col="{ span: 0 }"
  44 + />
  45 + </div>
  46 +</template>
@@ -2,10 +2,12 @@ import { Component } from 'vue'; @@ -2,10 +2,12 @@ import { Component } from 'vue';
2 import { FrontComponent } from '../../../const/const'; 2 import { FrontComponent } from '../../../const/const';
3 import BasicDataSourceForm from './BasicDataSourceForm.vue'; 3 import BasicDataSourceForm from './BasicDataSourceForm.vue';
4 import ControlDataSourceForm from './ControlDataSourceForm.vue'; 4 import ControlDataSourceForm from './ControlDataSourceForm.vue';
  5 +import MapDataSourceForm from './MapDataSourceForm.vue';
5 6
6 const dataSourceComponentMap = new Map<FrontComponent, Component>(); 7 const dataSourceComponentMap = new Map<FrontComponent, Component>();
7 8
8 dataSourceComponentMap.set(FrontComponent.CONTROL_COMPONENT_TOGGLE_SWITCH, ControlDataSourceForm); 9 dataSourceComponentMap.set(FrontComponent.CONTROL_COMPONENT_TOGGLE_SWITCH, ControlDataSourceForm);
  10 +dataSourceComponentMap.set(FrontComponent.MAP_COMPONENT_TRACK, MapDataSourceForm);
9 11
10 export const getDataSourceComponent = (frontId: FrontComponent) => { 12 export const getDataSourceComponent = (frontId: FrontComponent) => {
11 if (dataSourceComponentMap.has(frontId)) return dataSourceComponentMap.get(frontId)!; 13 if (dataSourceComponentMap.has(frontId)) return dataSourceComponentMap.get(frontId)!;
@@ -25,6 +25,8 @@ export enum DataSourceField { @@ -25,6 +25,8 @@ export enum DataSourceField {
25 ATTRIBUTE_RENAME = 'attributeRename', 25 ATTRIBUTE_RENAME = 'attributeRename',
26 DEVICE_NAME = 'deviceName', 26 DEVICE_NAME = 'deviceName',
27 DEVICE_RENAME = 'deviceRename', 27 DEVICE_RENAME = 'deviceRename',
  28 + LONGITUDE_ATTRIBUTE = 'longitudeAttribute',
  29 + LATITUDE_ATTRIBUTE = 'latitudeAttribute',
28 } 30 }
29 31
30 export const basicSchema: FormSchema[] = [ 32 export const basicSchema: FormSchema[] = [
@@ -238,3 +240,214 @@ export const controlFormSchema: FormSchema[] = [ @@ -238,3 +240,214 @@ export const controlFormSchema: FormSchema[] = [
238 }, 240 },
239 }, 241 },
240 ]; 242 ];
  243 +
  244 +export const mapFormSchema: FormSchema[] = [
  245 + {
  246 + field: DataSourceField.IS_GATEWAY_DEVICE,
  247 + component: 'Switch',
  248 + label: '是否是网关设备',
  249 + show: false,
  250 + },
  251 + {
  252 + field: DataSourceField.DEVICE_NAME,
  253 + component: 'Input',
  254 + label: '设备名',
  255 + show: false,
  256 + },
  257 + {
  258 + field: DataSourceField.ORIGINATION_ID,
  259 + component: 'ApiTreeSelect',
  260 + label: '组织',
  261 + colProps: { span: 8 },
  262 + rules: [{ required: true, message: '组织为必填项' }],
  263 + componentProps({ formActionType }) {
  264 + const { setFieldsValue } = formActionType;
  265 + return {
  266 + placeholder: '请选择组织',
  267 + api: async () => {
  268 + const data = await getOrganizationList();
  269 + copyTransFun(data as any as any[]);
  270 + return data;
  271 + },
  272 + onChange() {
  273 + setFieldsValue({
  274 + [DataSourceField.DEVICE_ID]: null,
  275 + [DataSourceField.LATITUDE_ATTRIBUTE]: null,
  276 + [DataSourceField.LONGITUDE_ATTRIBUTE]: null,
  277 + [DataSourceField.SLAVE_DEVICE_ID]: null,
  278 + [DataSourceField.IS_GATEWAY_DEVICE]: false,
  279 + });
  280 + },
  281 + getPopupContainer: () => document.body,
  282 + };
  283 + },
  284 + },
  285 + {
  286 + field: DataSourceField.DEVICE_ID,
  287 + component: 'ApiSelect',
  288 + label: '设备',
  289 + colProps: { span: 8 },
  290 + rules: [{ required: true, message: '设备名称为必填项' }],
  291 + componentProps({ formModel, formActionType }) {
  292 + const { setFieldsValue } = formActionType;
  293 + const organizationId = formModel[DataSourceField.ORIGINATION_ID];
  294 + return {
  295 + api: async () => {
  296 + if (organizationId) {
  297 + try {
  298 + const data = await getAllDeviceByOrg(organizationId);
  299 + if (data)
  300 + return data.map((item) => ({
  301 + label: item.name,
  302 + value: item.id,
  303 + deviceType: item.deviceType,
  304 + }));
  305 + } catch (error) {}
  306 + }
  307 + return [];
  308 + },
  309 + onChange(_value, record: Record<'value' | 'label' | 'deviceType', string>) {
  310 + setFieldsValue({
  311 + [DataSourceField.LONGITUDE_ATTRIBUTE]: null,
  312 + [DataSourceField.LATITUDE_ATTRIBUTE]: null,
  313 + [DataSourceField.IS_GATEWAY_DEVICE]: record?.deviceType === 'GATEWAY',
  314 + [DataSourceField.SLAVE_DEVICE_ID]: null,
  315 + [DataSourceField.DEVICE_NAME]: record?.label,
  316 + });
  317 + },
  318 + placeholder: '请选择设备',
  319 + getPopupContainer: () => document.body,
  320 + };
  321 + },
  322 + },
  323 + {
  324 + field: DataSourceField.SLAVE_DEVICE_ID,
  325 + label: '网关子设备',
  326 + component: 'ApiSelect',
  327 + colProps: { span: 8 },
  328 + rules: [{ required: true, message: '网关子设备为必填项' }],
  329 + ifShow({ model }) {
  330 + return model[DataSourceField.IS_GATEWAY_DEVICE];
  331 + },
  332 + dynamicRules({ model }) {
  333 + return [{ required: model[DataSourceField.IS_GATEWAY_DEVICE], message: '请选择网关子设备' }];
  334 + },
  335 + componentProps({ formModel, formActionType }) {
  336 + const { setFieldsValue } = formActionType;
  337 + const organizationId = formModel[DataSourceField.ORIGINATION_ID];
  338 + const isGatewayDevice = formModel[DataSourceField.IS_GATEWAY_DEVICE];
  339 + const deviceId = formModel[DataSourceField.DEVICE_ID];
  340 + return {
  341 + api: async () => {
  342 + if (organizationId && isGatewayDevice) {
  343 + try {
  344 + const data = await getGatewaySlaveDevice({ organizationId, masterId: deviceId });
  345 + if (data)
  346 + return data.map((item) => ({
  347 + label: item.name,
  348 + value: item.id,
  349 + deviceType: item.deviceType,
  350 + }));
  351 + } catch (error) {}
  352 + }
  353 + return [];
  354 + },
  355 + onChange(_value, record: Record<'value' | 'label' | 'deviceType', string>) {
  356 + setFieldsValue({
  357 + [DataSourceField.LATITUDE_ATTRIBUTE]: null,
  358 + [DataSourceField.LONGITUDE_ATTRIBUTE]: null,
  359 + [DataSourceField.DEVICE_NAME]: record?.label,
  360 + });
  361 + },
  362 + placeholder: '请选择网关子设备',
  363 + getPopupContainer: () => document.body,
  364 + };
  365 + },
  366 + },
  367 + {
  368 + field: DataSourceField.LONGITUDE_ATTRIBUTE,
  369 + component: 'ApiSelect',
  370 + label: '经度属性',
  371 + colProps: { span: 8 },
  372 + rules: [{ required: true, message: '属性为必填项' }],
  373 + componentProps({ formModel, formActionType }) {
  374 + const { updateSchema, setFieldsValue } = formActionType;
  375 + const organizationId = formModel[DataSourceField.ORIGINATION_ID];
  376 + const isGatewayDevice = formModel[DataSourceField.IS_GATEWAY_DEVICE];
  377 + const deviceId = formModel[DataSourceField.DEVICE_ID];
  378 + const slaveDeviceId = formModel[DataSourceField.SLAVE_DEVICE_ID];
  379 +
  380 + let attrs: Record<'label' | 'value', string>[] = [];
  381 + return {
  382 + api: async () => {
  383 + if (organizationId && deviceId) {
  384 + try {
  385 + if (isGatewayDevice && slaveDeviceId) {
  386 + return (attrs = await getDeviceAttribute(slaveDeviceId));
  387 + }
  388 + if (!isGatewayDevice) {
  389 + return (attrs = await getDeviceAttribute(deviceId));
  390 + }
  391 + } catch (error) {}
  392 + }
  393 + return [];
  394 + },
  395 + placeholder: '请选择经度属性',
  396 + getPopupContainer: () => document.body,
  397 + onChange: (value: string) => {
  398 + if (!value) return;
  399 + setFieldsValue({ [DataSourceField.LATITUDE_ATTRIBUTE]: null });
  400 + updateSchema({
  401 + field: DataSourceField.LATITUDE_ATTRIBUTE,
  402 + componentProps: {
  403 + options: attrs.filter((item) => item.value !== value),
  404 + },
  405 + });
  406 + },
  407 + };
  408 + },
  409 + },
  410 + {
  411 + field: DataSourceField.LATITUDE_ATTRIBUTE,
  412 + component: 'ApiSelect',
  413 + label: '纬度属性',
  414 + colProps: { span: 8 },
  415 + rules: [{ required: true, message: '属性为必填项' }],
  416 + componentProps({ formModel, formActionType }) {
  417 + const { updateSchema, setFieldsValue } = formActionType;
  418 + const organizationId = formModel[DataSourceField.ORIGINATION_ID];
  419 + const isGatewayDevice = formModel[DataSourceField.IS_GATEWAY_DEVICE];
  420 + const deviceId = formModel[DataSourceField.DEVICE_ID];
  421 + const slaveDeviceId = formModel[DataSourceField.SLAVE_DEVICE_ID];
  422 + let attrs: Record<'label' | 'value', string>[] = [];
  423 +
  424 + return {
  425 + api: async () => {
  426 + if (organizationId && deviceId) {
  427 + try {
  428 + if (isGatewayDevice && slaveDeviceId) {
  429 + return (attrs = await getDeviceAttribute(slaveDeviceId));
  430 + }
  431 + if (!isGatewayDevice) {
  432 + return (attrs = await getDeviceAttribute(deviceId));
  433 + }
  434 + } catch (error) {}
  435 + }
  436 + return [];
  437 + },
  438 + onChange: (value: string) => {
  439 + if (!value) return;
  440 + setFieldsValue({ [DataSourceField.LONGITUDE_ATTRIBUTE]: null });
  441 + updateSchema({
  442 + field: DataSourceField.LATITUDE_ATTRIBUTE,
  443 + componentProps: {
  444 + options: attrs.filter((item) => item.value !== value),
  445 + },
  446 + });
  447 + },
  448 + placeholder: '请输入纬度属性',
  449 + getPopupContainer: () => document.body,
  450 + };
  451 + },
  452 + },
  453 +];