Commit ca118a71af2ff60c5dfe1762725b8741656b2d66

Authored by ww
1 parent 356ba0a0

feat: ota page add permission control

... ... @@ -81,7 +81,13 @@
81 81 </script>
82 82
83 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 91 <BasicForm @register="registerForm" class="package-manage-form" />
86 92 </BasicModal>
87 93 </template>
... ...
... ... @@ -15,6 +15,8 @@
15 15 } from '/@/api/ota';
16 16 import { useCopyToClipboard } from '/@/hooks/web/useCopyToClipboard';
17 17 import { useDownload } from '../hook/useDownload';
  18 + import { Authority } from '/@/components/Authority';
  19 + import { OtaPermissionKey } from '../config/config';
18 20 // import DeviceDetailDrawer from '/@/views/device/list/cpns/modal/DeviceDetailDrawer.vue';
19 21
20 22 const emit = defineEmits(['register', 'update:list']);
... ... @@ -143,7 +145,9 @@
143 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 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 151 </div>
148 152 </template>
149 153 <!-- <DeviceDetailDrawer @register="registerTBDrawer" /> -->
... ...
... ... @@ -8,6 +8,13 @@ export interface ModalPassRecord {
8 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 18 export const columns: BasicColumn[] = [
12 19 {
13 20 title: '创建时间',
... ...
... ... @@ -45,14 +45,29 @@ export enum ALG {
45 45 MURMUR3_128 = 'MURMUR3128',
46 46 }
47 47
  48 +const getVersionTag = (title: string, version: string) => {
  49 + return `${title ?? ''} ${version ?? ''}`;
  50 +};
  51 +
48 52 export const formSchema: FormSchema[] = [
49 53 {
50 54 field: PackageField.TITLE,
51 55 label: '标题',
52 56 component: 'Input',
53 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 75 label: '版本',
61 76 component: 'Input',
62 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 1 <script lang="ts" setup>
2 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 4 import { PageWrapper } from '/@/components/Page';
5 5 import { BasicTable, useTable, TableAction } from '/@/components/Table';
6 6 import PackageDetailModal from './components/PackageDetailModal.vue';
... ... @@ -13,6 +13,7 @@
13 13 import { useDownload } from './hook/useDownload';
14 14 import { computed } from 'vue';
15 15 import { useSyncConfirm } from '/@/hooks/component/useSyncConfirm';
  16 + import { Authority } from '/@/components/Authority';
16 17
17 18 const [register, { reload, getSelectRowKeys, getRowSelection, setSelectedRowKeys }] = useTable({
18 19 columns,
... ... @@ -102,10 +103,14 @@
102 103 <PageWrapper dense contentFullHeight contentClass="flex flex-col">
103 104 <BasicTable @register="register" @row-click="handleOpenDetailDrawer" class="ota-list">
104 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 114 </template>
110 115 <template #action="{ record }">
111 116 <TableAction
... ... @@ -114,12 +119,14 @@
114 119 {
115 120 label: '下载',
116 121 icon: 'ant-design:download-outlined',
  122 + auth: OtaPermissionKey.DOWNLOAD,
117 123 onClick: downloadFile.bind(null, record),
118 124 },
119 125 {
120 126 label: '删除',
121 127 icon: 'ant-design:delete-outlined',
122 128 color: 'error',
  129 + auth: OtaPermissionKey.DELETE,
123 130 popConfirm: {
124 131 title: '是否确认删除',
125 132 confirm: deletePackage.bind(null, record),
... ...
... ... @@ -17,7 +17,7 @@
17 17 :row-props="{
18 18 gutter: 10,
19 19 }"
20   - layout="inline"
  20 + layout="horizontal"
21 21 :label-col="{ span: 0 }"
22 22 />
23 23 </template>
... ...
... ... @@ -39,7 +39,7 @@
39 39 :row-props="{
40 40 gutter: 10,
41 41 }"
42   - layout="inline"
  42 + layout="horizontal"
43 43 :label-col="{ span: 0 }"
44 44 />
45 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 2 import { FrontComponent } from '../../../const/const';
3 3 import BasicDataSourceForm from './BasicDataSourceForm.vue';
4 4 import ControlDataSourceForm from './ControlDataSourceForm.vue';
  5 +import MapDataSourceForm from './MapDataSourceForm.vue';
5 6
6 7 const dataSourceComponentMap = new Map<FrontComponent, Component>();
7 8
8 9 dataSourceComponentMap.set(FrontComponent.CONTROL_COMPONENT_TOGGLE_SWITCH, ControlDataSourceForm);
  10 +dataSourceComponentMap.set(FrontComponent.MAP_COMPONENT_TRACK, MapDataSourceForm);
9 11
10 12 export const getDataSourceComponent = (frontId: FrontComponent) => {
11 13 if (dataSourceComponentMap.has(frontId)) return dataSourceComponentMap.get(frontId)!;
... ...
... ... @@ -25,6 +25,8 @@ export enum DataSourceField {
25 25 ATTRIBUTE_RENAME = 'attributeRename',
26 26 DEVICE_NAME = 'deviceName',
27 27 DEVICE_RENAME = 'deviceRename',
  28 + LONGITUDE_ATTRIBUTE = 'longitudeAttribute',
  29 + LATITUDE_ATTRIBUTE = 'latitudeAttribute',
28 30 }
29 31
30 32 export const basicSchema: 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 +];
... ...