Commit dbb85690e5ee7e65af6e972722a8faf760fd547f

Authored by ww
1 parent 6c0755f1

feat: device profile add card mode

@@ -2,7 +2,9 @@ import { defHttp } from '/@/utils/http/axios'; @@ -2,7 +2,9 @@ import { defHttp } from '/@/utils/http/axios';
2 import { 2 import {
3 TDeviceConfigParams, 3 TDeviceConfigParams,
4 IDeviceConfigAddOrEditModel, 4 IDeviceConfigAddOrEditModel,
  5 + ProfileRecord,
5 } from '/@/api/device/model/deviceConfigModel'; 6 } from '/@/api/device/model/deviceConfigModel';
  7 +import { PaginationResult } from '/#/axios';
6 8
7 enum EDeviceConfigApi { 9 enum EDeviceConfigApi {
8 /** 10 /**
@@ -52,7 +54,7 @@ export const alarmContactGetPage = () => { @@ -52,7 +54,7 @@ export const alarmContactGetPage = () => {
52 * 分页查询设备配置页面 54 * 分页查询设备配置页面
53 */ 55 */
54 export const deviceConfigGetQuery = (params?: TDeviceConfigParams) => { 56 export const deviceConfigGetQuery = (params?: TDeviceConfigParams) => {
55 - return defHttp.get({ 57 + return defHttp.get<PaginationResult<ProfileRecord>>({
56 url: EDeviceConfigApi.DEVICE_CONFIG_GET_PAGE, 58 url: EDeviceConfigApi.DEVICE_CONFIG_GET_PAGE,
57 params, 59 params,
58 }); 60 });
@@ -199,3 +199,45 @@ export interface AlarmLogItem { @@ -199,3 +199,45 @@ export interface AlarmLogItem {
199 organizationId: string; 199 organizationId: string;
200 organizationName: string; 200 organizationName: string;
201 } 201 }
  202 +
  203 +export interface Configuration {
  204 + type: string;
  205 +}
  206 +
  207 +export interface TransportConfiguration {
  208 + type: string;
  209 +}
  210 +
  211 +export interface ProvisionConfiguration {
  212 + type: string;
  213 + provisionDeviceSecret?: any;
  214 +}
  215 +
  216 +export interface ProfileData {
  217 + configuration: Configuration;
  218 + transportConfiguration: TransportConfiguration;
  219 + provisionConfiguration: ProvisionConfiguration;
  220 + alarms?: any;
  221 +}
  222 +
  223 +export interface ProfileRecord {
  224 + id: string;
  225 + creator: string;
  226 + createTime: string;
  227 + updater: string;
  228 + updateTime: string;
  229 + name: string;
  230 + tenantId: string;
  231 + transportType: string;
  232 + provisionType: string;
  233 + deviceType: string;
  234 + tbProfileId: string;
  235 + profileData: ProfileData;
  236 + defaultRuleChainId: string;
  237 + defaultQueueName: string;
  238 + image: string;
  239 + type: string;
  240 + default: boolean;
  241 +
  242 + checked?: boolean;
  243 +}
  1 +<script lang="ts" setup>
  2 + import { Popover, Slider, Button } from 'ant-design-vue';
  3 + import { LayoutOutlined } from '@ant-design/icons-vue';
  4 + import { computed } from 'vue';
  5 +
  6 + const props = withDefaults(
  7 + defineProps<{
  8 + min?: number;
  9 + max?: number;
  10 + value?: number;
  11 + }>(),
  12 + {
  13 + min: 4,
  14 + max: 12,
  15 + value: 4,
  16 + }
  17 + );
  18 +
  19 + const emit = defineEmits(['change', 'update:value']);
  20 +
  21 + const generateLayoutMarks = (min: number, max: number) => {
  22 + const marks = {};
  23 + Array.from({ length: max - min + 1 }).forEach((_, index) => {
  24 + const key = index + min;
  25 + marks[key] = key;
  26 + });
  27 + return marks;
  28 + };
  29 +
  30 + const getMarks = computed(() => {
  31 + const { min, max } = props;
  32 + return generateLayoutMarks(min, max);
  33 + });
  34 +
  35 + const handleChange = (value: number) => {
  36 + emit('update:value', value);
  37 + emit('change', value);
  38 + };
  39 +</script>
  40 +
  41 +<template>
  42 + <Popover :trigger="['hover']">
  43 + <template #content>
  44 + <div class="w-60">
  45 + <div>每行显示数量</div>
  46 + <Slider
  47 + :value="props.value"
  48 + :max="props.max"
  49 + :min="props.min"
  50 + :marks="getMarks"
  51 + @change="handleChange"
  52 + />
  53 + </div>
  54 + </template>
  55 + <Button type="primary">
  56 + <LayoutOutlined />
  57 + </Button>
  58 + </Popover>
  59 +</template>
  1 +<script lang="ts" setup>
  2 + import { RadioButton, RadioGroup, Tooltip } from 'ant-design-vue';
  3 + import { AppstoreOutlined, UnorderedListOutlined } from '@ant-design/icons-vue';
  4 + import { Mode } from './const';
  5 + const props = defineProps<{
  6 + value: Mode;
  7 + }>();
  8 +
  9 + const emit = defineEmits(['change']);
  10 +
  11 + const handleChange = (event: Event) => {
  12 + const value = (event.target as HTMLInputElement).value;
  13 + emit('change', value);
  14 + };
  15 +</script>
  16 +
  17 +<template>
  18 + <RadioGroup :value="props.value" button-style="solid" @change="handleChange">
  19 + <Tooltip title="卡片模式">
  20 + <RadioButton class="cursor-pointer" :value="Mode.CARD">
  21 + <AppstoreOutlined />
  22 + </RadioButton>
  23 + </Tooltip>
  24 + <Tooltip title="列表模式">
  25 + <RadioButton class="cursor-pointer" :value="Mode.TABLE">
  26 + <UnorderedListOutlined />
  27 + </RadioButton>
  28 + </Tooltip>
  29 + </RadioGroup>
  30 +</template>
  1 +export const enum Mode {
  2 + CARD = 'CARD',
  3 + TABLE = 'TABLE',
  4 +}
  1 +export { default as ModeSwitchButton } from './ModeSwitchButton.vue';
  2 +export { default as CardLayoutButton } from './CardLayoutButton.vue';
  3 +export { Mode } from './const';
  1 +<script lang="ts" setup>
  2 + import { PageWrapper } from '/@/components/Page';
  3 + import { BasicForm, useForm } from '/@/components/Form';
  4 + import { List, Button, Tooltip, Card, PaginationProps, Image } from 'ant-design-vue';
  5 + import { ReloadOutlined, EyeOutlined, FormOutlined, MoreOutlined } from '@ant-design/icons-vue';
  6 + import { computed, onMounted, reactive, ref, unref } from 'vue';
  7 + import { CardLayoutButton, Mode, ModeSwitchButton } from '/@/components/Widget';
  8 + import { Authority } from '/@/components/Authority';
  9 + import {
  10 + deviceConfigDelete,
  11 + deviceConfigGetQuery,
  12 + setDeviceProfileIsDefaultApi,
  13 + } from '/@/api/device/deviceConfigApi';
  14 + import { ProfileRecord } from '/@/api/device/model/deviceConfigModel';
  15 + import { Dropdown } from '/@/components/Dropdown';
  16 + import { defaultObj, searchFormSchema, DeviceTypeName } from './device.profile.data';
  17 + import { useMessage } from '/@/hooks/web/useMessage';
  18 + import { useSyncConfirm } from '/@/hooks/component/useSyncConfirm';
  19 + import DeviceProfileModal from './DeviceProfileModal.vue';
  20 + import DeviceProfileDrawer from './DeviceProfileDrawer.vue';
  21 + import { useModal } from '/@/components/Modal';
  22 + import { useDrawer } from '/@/components/Drawer';
  23 +
  24 + defineProps<{
  25 + mode: Mode;
  26 + }>();
  27 +
  28 + const emit = defineEmits(['changeMode']);
  29 +
  30 + enum DropMenuEvent {
  31 + SET_DEFAULT = 'setDefault',
  32 + DELETE = 'delete',
  33 + }
  34 + const IMAGE_FALLBACK =
  35 + 'data:image/png;base64,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';
  36 +
  37 + const { createMessage } = useMessage();
  38 + const { createSyncConfirm } = useSyncConfirm();
  39 +
  40 + const [register, { getFieldsValue }] = useForm({
  41 + showAdvancedButton: true,
  42 + labelWidth: 100,
  43 + compact: true,
  44 + baseColProps: { span: 8 },
  45 + schemas: searchFormSchema,
  46 + submitFunc: async () => {
  47 + getDataSource();
  48 + },
  49 + });
  50 +
  51 + const [registerModal, { openModal }] = useModal();
  52 + const [registerDrawer, { openDrawer }] = useDrawer();
  53 +
  54 + const loading = ref(false);
  55 +
  56 + const pagination = reactive<PaginationProps>({
  57 + size: 'small',
  58 + showTotal: (total: number) => `共 ${total} 条数据`,
  59 + current: 1,
  60 + onChange: (page: number) => {
  61 + pagination.current = page;
  62 + getDataSource();
  63 + },
  64 + });
  65 +
  66 + const dataSource = ref<ProfileRecord[]>([]);
  67 +
  68 + const colNumber = ref(4);
  69 +
  70 + const getSelectAllFlag = computed(() => {
  71 + return unref(dataSource).every((item) => item.checked);
  72 + });
  73 +
  74 + const getCheckedRecord = computed(() => {
  75 + return unref(dataSource)
  76 + .filter((item) => item.checked)
  77 + .map((item) => item.id);
  78 + });
  79 +
  80 + const getDataSource = async () => {
  81 + try {
  82 + loading.value = true;
  83 + const params = getFieldsValue();
  84 + const { items, total } = await deviceConfigGetQuery({
  85 + page: pagination.current,
  86 + pageSize: unref(colNumber) * 2,
  87 + ...params,
  88 + });
  89 + pagination.total = total;
  90 + dataSource.value = items.map((item) => ({ ...item, checked: false }));
  91 + } catch (error) {
  92 + } finally {
  93 + loading.value = false;
  94 + }
  95 + };
  96 +
  97 + const handleModeChange = (mode: Mode) => {
  98 + emit('changeMode', mode);
  99 + };
  100 +
  101 + const handleCheckCard = (item: ProfileRecord) => {
  102 + item.checked = !item.checked;
  103 + };
  104 +
  105 + const handleSelectAll = () => {
  106 + dataSource.value = unref(dataSource).map((item) => {
  107 + return {
  108 + ...item,
  109 + checked: !unref(getSelectAllFlag),
  110 + };
  111 + });
  112 + };
  113 +
  114 + const handleCreate = () => {
  115 + openModal(true, {
  116 + isUpdate: false,
  117 + });
  118 + };
  119 +
  120 + const handleShowDetail = (record: ProfileRecord) => {
  121 + openDrawer(true, { record });
  122 + };
  123 +
  124 + const handleUpdate = (record: ProfileRecord) => {
  125 + openModal(true, {
  126 + record,
  127 + isUpdate: true,
  128 + });
  129 + };
  130 +
  131 + const handleDelete = async (id: string[]) => {
  132 + try {
  133 + await createSyncConfirm({ iconType: 'warning', content: '是否确认删除操作?' });
  134 + await deviceConfigDelete(id);
  135 + createMessage.success('删除成功');
  136 + await getDataSource();
  137 + } catch (error) {
  138 + throw error;
  139 + }
  140 + };
  141 +
  142 + const handleSetDefault = async (record: ProfileRecord) => {
  143 + try {
  144 + const { tbProfileId } = record;
  145 + const data = await setDeviceProfileIsDefaultApi(tbProfileId, 'default', defaultObj);
  146 + if (!data) return createMessage.error('设置该产品为默认失败');
  147 + createMessage.success('设置该产品为默认成功');
  148 + await getDataSource();
  149 + } catch (error) {
  150 + throw error;
  151 + }
  152 + };
  153 +
  154 + onMounted(() => {
  155 + getDataSource();
  156 + });
  157 +</script>
  158 +
  159 +<template>
  160 + <PageWrapper dense contentFullHeight contentClass="flex">
  161 + <section class="flex-auto p-4 w-full profile-list">
  162 + <div class="flex-auto w-full bg-light-50 dark:bg-dark-900 p-4">
  163 + <BasicForm @register="register" />
  164 + </div>
  165 + <List
  166 + ref="listEl"
  167 + :loading="loading"
  168 + class="flex-auto bg-light-50 dark:bg-dark-900 !p-2 !mt-4"
  169 + position="bottom"
  170 + :pagination="pagination"
  171 + :data-source="dataSource"
  172 + :grid="{ gutter: 4, column: colNumber }"
  173 + >
  174 + <template #header>
  175 + <div class="flex gap-3 justify-end">
  176 + <Button type="primary" @click="handleCreate">新增产品</Button>
  177 + <Button type="primary" @click="handleSelectAll">
  178 + {{ getSelectAllFlag ? '反选' : '全选' }}
  179 + </Button>
  180 + <Button
  181 + type="primary"
  182 + danger
  183 + :disabled="!getCheckedRecord.length"
  184 + @click="handleDelete(getCheckedRecord)"
  185 + >
  186 + 批量删除
  187 + </Button>
  188 + <ModeSwitchButton :value="$props.mode" @change="handleModeChange" />
  189 + <CardLayoutButton v-model:value="colNumber" @change="getDataSource" />
  190 + <Tooltip title="刷新">
  191 + <Button type="primary" @click="getDataSource">
  192 + <ReloadOutlined :spin="loading" />
  193 + </Button>
  194 + </Tooltip>
  195 + </div>
  196 + </template>
  197 + <template #renderItem="{ item }">
  198 + <List.Item>
  199 + <Card
  200 + hoverable
  201 + @click="handleCheckCard(item)"
  202 + :class="item.checked ? '!border-blue-500 !border-2' : ''"
  203 + >
  204 + <template #cover>
  205 + <div class="h-full w-full !flex justify-center items-center text-center">
  206 + <Image
  207 + @click.stop
  208 + :height="144"
  209 + :src="item.image"
  210 + placeholder
  211 + :fallback="IMAGE_FALLBACK"
  212 + />
  213 + </div>
  214 + </template>
  215 + <template class="ant-card-actions" #actions>
  216 + <Authority>
  217 + <Tooltip title="详情">
  218 + <EyeOutlined key="setting" @click.stop="handleShowDetail(item)" />
  219 + </Tooltip>
  220 + </Authority>
  221 + <Authority>
  222 + <Tooltip title="编辑">
  223 + <FormOutlined key="edit" @click.stop="handleUpdate(item)" />
  224 + </Tooltip>
  225 + </Authority>
  226 + <Dropdown
  227 + :trigger="['hover']"
  228 + :drop-menu-list="[
  229 + {
  230 + text: '默认',
  231 + event: DropMenuEvent.SET_DEFAULT,
  232 + icon: 'ant-design:unordered-list-outlined',
  233 + onClick: handleSetDefault.bind(null, item),
  234 + },
  235 + {
  236 + text: '删除',
  237 + event: DropMenuEvent.DELETE,
  238 + icon: 'ant-design:delete-outlined',
  239 + onClick: handleDelete.bind(null, [item.id]),
  240 + },
  241 + ]"
  242 + >
  243 + <MoreOutlined @click.stop class="transform rotate-90" />
  244 + </Dropdown>
  245 + </template>
  246 + <Card.Meta>
  247 + <template #title>
  248 + <span class="truncate"> {{ item.name }} </span>
  249 + </template>
  250 + <template #description>
  251 + <div class="truncate h-11">
  252 + <div class="truncate">{{ DeviceTypeName[item.deviceType] }} </div>
  253 + <div class="truncate">{{ item.transportType }} </div>
  254 + </div>
  255 + </template>
  256 + </Card.Meta>
  257 + </Card>
  258 + </List.Item>
  259 + </template>
  260 + </List>
  261 + </section>
  262 + <DeviceProfileModal @register="registerModal" @success="getDataSource" />
  263 + <DeviceProfileDrawer @register="registerDrawer" />
  264 + </PageWrapper>
  265 +</template>
  266 +
  267 +<style lang="less" scoped>
  268 + .profile-list:deep(.ant-image-img) {
  269 + width: 100% !important;
  270 + height: 100% !important;
  271 + }
  272 +</style>
  1 +<template>
  2 + <div>
  3 + <BasicTable
  4 + class="devide-profiles"
  5 + @register="registerTable"
  6 + :rowSelection="{ type: 'checkbox' }"
  7 + :clickToRowSelect="false"
  8 + >
  9 + <template #toolbar>
  10 + <Authority value="api:yt:deviceProfile:post">
  11 + <a-button type="primary" @click="handleCreate"> 新增产品 </a-button>
  12 + </Authority>
  13 + <Authority value="api:yt:deviceProfile:import">
  14 + <ImpExcel @success="loadDataSuccess" dateFormat="YYYY-MM-DD">
  15 + <a-button @click="handleImport"> 导入产品 </a-button>
  16 + </ImpExcel>
  17 + </Authority>
  18 + <Authority value="api:yt:deviceProfile:delete">
  19 + <Popconfirm
  20 + title="您确定要批量删除数据"
  21 + ok-text="确定"
  22 + cancel-text="取消"
  23 + @confirm="handleDeleteOrBatchDelete(null)"
  24 + >
  25 + <a-button type="primary" color="error" :disabled="hasBatchDelete"> 批量删除 </a-button>
  26 + </Popconfirm>
  27 + </Authority>
  28 + <ModeSwitchButton :value="$props.mode" @change="handleModeChange" />
  29 + </template>
  30 + <template #img="{ record }">
  31 + <TableImg
  32 + :size="30"
  33 + :showBadge="false"
  34 + :simpleShow="true"
  35 + :imgList="
  36 + typeof record.image !== 'undefined' && record.image !== '' && record.image != null
  37 + ? [record.image]
  38 + : null
  39 + "
  40 + />
  41 + </template>
  42 + <template #action="{ record }">
  43 + <TableAction
  44 + :actions="[
  45 + {
  46 + label: '详情',
  47 + auth: 'api:yt:deviceProfile:get',
  48 + icon: 'ant-design:eye-outlined',
  49 + onClick: handleDetailView.bind(null, record),
  50 + },
  51 + {
  52 + label: '编辑',
  53 + auth: 'api:yt:deviceProfile:update',
  54 + icon: 'clarity:note-edit-line',
  55 + onClick: handleEdit.bind(null, record),
  56 + ifShow: () => {
  57 + return record.name !== 'default' ? true : false;
  58 + },
  59 + },
  60 + ]"
  61 + :drop-down-actions="[
  62 + {
  63 + label: '默认',
  64 + icon: 'ant-design:profile-outlined',
  65 + onClick: handleSetDefault.bind(null, record),
  66 + ifShow: () => {
  67 + return record.default === false;
  68 + },
  69 + },
  70 + {
  71 + label: '导出',
  72 + auth: 'api:yt:deviceProfile:export',
  73 + icon: 'ant-design:login-outlined',
  74 + onClick: handleExport.bind(null, record),
  75 + },
  76 + {
  77 + label: '删除',
  78 + auth: 'api:yt:deviceProfile:delete',
  79 + icon: 'ant-design:delete-outlined',
  80 + color: 'error',
  81 + popConfirm: {
  82 + title: '是否确认删除',
  83 + confirm: handleDeleteOrBatchDelete.bind(null, record),
  84 + },
  85 + ifShow: () => {
  86 + return record.default === false && record.name !== 'default';
  87 + },
  88 + },
  89 + ]"
  90 + />
  91 + </template>
  92 + </BasicTable>
  93 + <DeviceProfileModal @register="registerModal" @success="handleSuccess" />
  94 + <DeviceProfileDrawer @register="registerDrawer" />
  95 + <ExpExcelModal
  96 + ref="expExcelModalRef"
  97 + @register="registerExportModal"
  98 + @success="defaultHeader"
  99 + />
  100 + </div>
  101 +</template>
  102 +<script lang="ts" setup>
  103 + import { ref, nextTick, onUnmounted } from 'vue';
  104 + import { BasicTable, TableImg, useTable, TableAction, BasicColumn } from '/@/components/Table';
  105 + import { columns, searchFormSchema, defaultObj } from './device.profile.data';
  106 + import { useMessage } from '/@/hooks/web/useMessage';
  107 + import {
  108 + deviceConfigGetQuery,
  109 + deviceConfigDelete,
  110 + setDeviceProfileIsDefaultApi,
  111 + } from '/@/api/device/deviceConfigApi';
  112 + import { useModal } from '/@/components/Modal';
  113 + import { useDrawer } from '/@/components/Drawer';
  114 + import DeviceProfileModal from '/@/views/device/profiles/DeviceProfileModal.vue';
  115 + import { ImpExcel, ExcelData } from '/@/components/Excel';
  116 + import { jsonToSheetXlsx, ExpExcelModal, ExportModalResult } from '/@/components/Excel';
  117 + import { Authority } from '/@/components/Authority';
  118 + import { useBatchDelete } from '/@/hooks/web/useBatchDelete';
  119 + import { Popconfirm } from 'ant-design-vue';
  120 + import DeviceProfileDrawer from './DeviceProfileDrawer.vue';
  121 + import { Mode, ModeSwitchButton } from '/@/components/Widget';
  122 +
  123 + defineProps<{
  124 + mode: Mode;
  125 + }>();
  126 +
  127 + const emit = defineEmits(['changeMode']);
  128 +
  129 + const exportData: any = ref([]);
  130 + const expExcelModalRef: any = ref(null);
  131 + const getPathUrl = ref('');
  132 + const getPathUrlName = ref('');
  133 + const disabled = ref(true);
  134 + const onCloseVal = ref(0);
  135 + const immediateStatus = ref(false);
  136 + const { createMessage } = useMessage();
  137 + const [registerModal, { openModal }] = useModal();
  138 + const [registerExportModal, { openModal: openModalExcel }] = useModal();
  139 + const [registerTable, { setProps, reload, setTableData, getForm }] = useTable({
  140 + title: '产品列表',
  141 + clickToRowSelect: false,
  142 + api: deviceConfigGetQuery,
  143 + immediate: immediateStatus.value,
  144 + columns,
  145 + formConfig: {
  146 + labelWidth: 120,
  147 + schemas: searchFormSchema,
  148 + },
  149 + rowKey: 'id',
  150 + useSearchForm: true,
  151 + showTableSetting: true,
  152 + bordered: true,
  153 + showIndexColumn: false,
  154 + actionColumn: {
  155 + width: 200,
  156 + title: '操作',
  157 + dataIndex: 'action',
  158 + slots: { customRender: 'action' },
  159 + fixed: 'right',
  160 + },
  161 + });
  162 + const { hasBatchDelete, handleDeleteOrBatchDelete, selectionOptions } = useBatchDelete(
  163 + deviceConfigDelete,
  164 + handleSuccess,
  165 + setProps
  166 + );
  167 + selectionOptions.rowSelection.getCheckboxProps = (record: Recordable) => {
  168 + // Demo:status为1的选择框禁用
  169 + if (record.default === true) {
  170 + return { disabled: true };
  171 + } else if (record.name == 'default') {
  172 + return { disabled: true };
  173 + } else {
  174 + return { disabled: false };
  175 + }
  176 + };
  177 + nextTick(() => {
  178 + setProps(selectionOptions);
  179 + });
  180 + /**
  181 + *@param url,name
  182 + **/
  183 + function getParam(url, name) {
  184 + try {
  185 + let reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)');
  186 + let r = url.split('?')[1].match(reg);
  187 + if (r != null) {
  188 + return r[2];
  189 + }
  190 + return ''; //如果此处只写return;则返回的是undefined
  191 + } catch (e) {
  192 + return ''; //如果此处只写return;则返回的是undefined
  193 + }
  194 + }
  195 + getPathUrl.value = window.location.href;
  196 + const name = 'name';
  197 + const getName = getParam(getPathUrl.value, name);
  198 + getPathUrlName.value = decodeURIComponent(getName);
  199 +
  200 + const setRowClassName = async () => {
  201 + if (getPathUrlName.value !== '') {
  202 + const { items } = await deviceConfigGetQuery({
  203 + page: 1,
  204 + pageSize: 10,
  205 + name: getPathUrlName.value,
  206 + });
  207 + nextTick(() => {
  208 + setTableData(items);
  209 + const { setFieldsValue, resetFields } = getForm();
  210 + setFieldsValue({
  211 + name: getPathUrlName.value,
  212 + });
  213 + if (onCloseVal.value == 1) {
  214 + resetFields();
  215 + }
  216 + });
  217 + } else {
  218 + setTimeout(() => {
  219 + reload();
  220 + }, 80);
  221 + }
  222 + };
  223 + setRowClassName();
  224 + onUnmounted(() => {
  225 + getPathUrlName.value = '';
  226 + onCloseVal.value = 1;
  227 + });
  228 + const tableListRef = ref<
  229 + {
  230 + title: string;
  231 + columns?: any[];
  232 + dataSource?: any[];
  233 + }[]
  234 + >([]);
  235 +
  236 + function loadDataSuccess(excelDataList: ExcelData[]) {
  237 + tableListRef.value = [];
  238 + console.log(excelDataList);
  239 + for (const excelData of excelDataList) {
  240 + const {
  241 + header,
  242 + results,
  243 + meta: { sheetName },
  244 + } = excelData;
  245 + const columns: BasicColumn[] = [];
  246 + for (const title of header) {
  247 + columns.push({ title, dataIndex: title });
  248 + }
  249 + tableListRef.value.push({ title: sheetName, dataSource: results, columns });
  250 + }
  251 + }
  252 + //新增
  253 + function handleCreate() {
  254 + openModal(true, {
  255 + isUpdate: false,
  256 + });
  257 + }
  258 + //编辑
  259 + function handleEdit(record: Recordable) {
  260 + openModal(true, {
  261 + record,
  262 + isUpdate: true,
  263 + });
  264 + }
  265 +
  266 + const [registerDrawer, { openDrawer }] = useDrawer();
  267 + //详情
  268 + function handleDetailView(record: Recordable) {
  269 + openDrawer(true, { record });
  270 + }
  271 +
  272 + function defaultHeader({ filename, bookType }: ExportModalResult) {
  273 + // 默认Object.keys(data[0])作为header
  274 + const data = exportData.value;
  275 + jsonToSheetXlsx({
  276 + data,
  277 + filename,
  278 + write2excelOpts: {
  279 + bookType,
  280 + },
  281 + });
  282 + }
  283 + //导出
  284 + const handleExport = (record: Recordable) => {
  285 + exportData.value = [];
  286 + exportData.value.push({
  287 + createTime: record.createTime,
  288 + description: record.description,
  289 + name: record.name,
  290 + });
  291 + nextTick(() => {
  292 + openModalExcel();
  293 + expExcelModalRef.value?.clearFieldFunc();
  294 + });
  295 + };
  296 + //导入
  297 + function handleImport() {
  298 + console.log('record');
  299 + }
  300 + function handleSuccess() {
  301 + reload();
  302 + }
  303 +
  304 + const handleSetDefault = async (record: Recordable) => {
  305 + let id = record.tbProfileId;
  306 + const data = await setDeviceProfileIsDefaultApi(id, 'default', defaultObj);
  307 + if (!data) return createMessage.error('设置该产品为默认失败');
  308 + createMessage.success('设置该产品为默认成功');
  309 + reload();
  310 + disabled.value = true;
  311 + };
  312 +
  313 + const handleModeChange = (value: Mode) => {
  314 + emit('changeMode', value);
  315 + };
  316 +</script>
  317 +
  318 +<style lang="css">
  319 + .devide-profiles .rowcolor {
  320 + color: red;
  321 + }
  322 +
  323 + .devide-profiles .rowcolor2 {
  324 + background: #a2c3e6;
  325 + }
  326 +</style>
@@ -9,6 +9,17 @@ import { FormField, FunctionType } from './step/cpns/physical/cpns/config'; @@ -9,6 +9,17 @@ import { FormField, FunctionType } from './step/cpns/physical/cpns/config';
9 import { h } from 'vue'; 9 import { h } from 'vue';
10 import { Tag } from 'ant-design-vue'; 10 import { Tag } from 'ant-design-vue';
11 11
  12 +export enum Mode {
  13 + CARD = 'card',
  14 + TABLE = 'table',
  15 +}
  16 +
  17 +export enum DeviceTypeName {
  18 + DIRECT_CONNECTION = '直连设备',
  19 + GATEWAY = '网关设备',
  20 + SENSOR = '网关子设备',
  21 +}
  22 +
12 export enum ModelOfMatterPermission { 23 export enum ModelOfMatterPermission {
13 CREATE = 'api:yt:things_model:post', 24 CREATE = 'api:yt:things_model:post',
14 UPDATE = 'api:yt:things_model:put', 25 UPDATE = 'api:yt:things_model:put',
1 -<template>  
2 - <div>  
3 - <BasicTable  
4 - class="devide-profiles"  
5 - @register="registerTable"  
6 - :rowSelection="{ type: 'checkbox' }"  
7 - :clickToRowSelect="false"  
8 - >  
9 - <template #toolbar>  
10 - <Authority value="api:yt:deviceProfile:post">  
11 - <a-button type="primary" @click="handleCreate"> 新增产品 </a-button>  
12 - </Authority>  
13 - <Authority value="api:yt:deviceProfile:import">  
14 - <ImpExcel @success="loadDataSuccess" dateFormat="YYYY-MM-DD">  
15 - <a-button @click="handleImport"> 导入产品 </a-button>  
16 - </ImpExcel>  
17 - </Authority>  
18 - <Authority value="api:yt:deviceProfile:delete">  
19 - <Popconfirm  
20 - title="您确定要批量删除数据"  
21 - ok-text="确定"  
22 - cancel-text="取消"  
23 - @confirm="handleDeleteOrBatchDelete(null)"  
24 - >  
25 - <a-button type="primary" color="error" :disabled="hasBatchDelete"> 批量删除 </a-button>  
26 - </Popconfirm>  
27 - </Authority>  
28 - </template>  
29 - <template #img="{ record }">  
30 - <TableImg  
31 - :size="30"  
32 - :showBadge="false"  
33 - :simpleShow="true"  
34 - :imgList="  
35 - typeof record.image !== 'undefined' && record.image !== '' && record.image != null  
36 - ? [record.image]  
37 - : null  
38 - "  
39 - />  
40 - </template>  
41 - <template #action="{ record }">  
42 - <TableAction  
43 - :actions="[  
44 - {  
45 - label: '详情',  
46 - auth: 'api:yt:deviceProfile:get',  
47 - icon: 'ant-design:eye-outlined',  
48 - onClick: handleDetailView.bind(null, record),  
49 - },  
50 - {  
51 - label: '编辑',  
52 - auth: 'api:yt:deviceProfile:update',  
53 - icon: 'clarity:note-edit-line',  
54 - onClick: handleEdit.bind(null, record),  
55 - ifShow: () => {  
56 - return record.name !== 'default' ? true : false;  
57 - },  
58 - },  
59 - ]"  
60 - :drop-down-actions="[  
61 - {  
62 - label: '默认',  
63 - icon: 'ant-design:profile-outlined',  
64 - onClick: handleSetDefault.bind(null, record),  
65 - ifShow: () => {  
66 - return record.default === false;  
67 - },  
68 - },  
69 - {  
70 - label: '导出',  
71 - auth: 'api:yt:deviceProfile:export',  
72 - icon: 'ant-design:login-outlined',  
73 - onClick: handleExport.bind(null, record),  
74 - },  
75 - {  
76 - label: '删除',  
77 - auth: 'api:yt:deviceProfile:delete',  
78 - icon: 'ant-design:delete-outlined',  
79 - color: 'error',  
80 - popConfirm: {  
81 - title: '是否确认删除',  
82 - confirm: handleDeleteOrBatchDelete.bind(null, record),  
83 - },  
84 - ifShow: () => {  
85 - return record.default === false && record.name !== 'default';  
86 - },  
87 - },  
88 - ]"  
89 - />  
90 - </template>  
91 - </BasicTable>  
92 - <DeviceProfileModal @register="registerModal" @success="handleSuccess" />  
93 - <DeviceProfileDrawer @register="registerDrawer" />  
94 - <ExpExcelModal  
95 - ref="expExcelModalRef"  
96 - @register="registerExportModal"  
97 - @success="defaultHeader"  
98 - />  
99 - </div>  
100 -</template>  
101 <script lang="ts" setup> 1 <script lang="ts" setup>
102 - import { ref, nextTick, onUnmounted } from 'vue';  
103 - import { BasicTable, TableImg, useTable, TableAction, BasicColumn } from '/@/components/Table';  
104 - import { columns, searchFormSchema, defaultObj } from './device.profile.data';  
105 - import { useMessage } from '/@/hooks/web/useMessage';  
106 - import {  
107 - deviceConfigGetQuery,  
108 - deviceConfigDelete,  
109 - setDeviceProfileIsDefaultApi,  
110 - } from '/@/api/device/deviceConfigApi';  
111 - import { useModal } from '/@/components/Modal';  
112 - import { useDrawer } from '/@/components/Drawer';  
113 - import DeviceProfileModal from '/@/views/device/profiles/DeviceProfileModal.vue';  
114 - import { ImpExcel, ExcelData } from '/@/components/Excel';  
115 - import { jsonToSheetXlsx, ExpExcelModal, ExportModalResult } from '/@/components/Excel';  
116 - import { Authority } from '/@/components/Authority';  
117 - import { useBatchDelete } from '/@/hooks/web/useBatchDelete';  
118 - import { Popconfirm } from 'ant-design-vue';  
119 - import DeviceProfileDrawer from './DeviceProfileDrawer.vue';  
120 -  
121 - const exportData: any = ref([]);  
122 - const expExcelModalRef: any = ref(null);  
123 - const getPathUrl = ref('');  
124 - const getPathUrlName = ref('');  
125 - const disabled = ref(true);  
126 - const onCloseVal = ref(0);  
127 - const immediateStatus = ref(false);  
128 - const { createMessage } = useMessage();  
129 - const [registerModal, { openModal }] = useModal();  
130 - const [registerExportModal, { openModal: openModalExcel }] = useModal();  
131 - const [registerTable, { setProps, reload, setTableData, getForm }] = useTable({  
132 - title: '产品列表',  
133 - clickToRowSelect: false,  
134 - api: deviceConfigGetQuery,  
135 - immediate: immediateStatus.value,  
136 - columns,  
137 - formConfig: {  
138 - labelWidth: 120,  
139 - schemas: searchFormSchema,  
140 - },  
141 - rowKey: 'id',  
142 - useSearchForm: true,  
143 - showTableSetting: true,  
144 - bordered: true,  
145 - showIndexColumn: false,  
146 - actionColumn: {  
147 - width: 200,  
148 - title: '操作',  
149 - dataIndex: 'action',  
150 - slots: { customRender: 'action' },  
151 - fixed: 'right',  
152 - },  
153 - });  
154 - const { hasBatchDelete, handleDeleteOrBatchDelete, selectionOptions } = useBatchDelete(  
155 - deviceConfigDelete,  
156 - handleSuccess,  
157 - setProps  
158 - );  
159 - selectionOptions.rowSelection.getCheckboxProps = (record: Recordable) => {  
160 - // Demo:status为1的选择框禁用  
161 - if (record.default === true) {  
162 - return { disabled: true };  
163 - } else if (record.name == 'default') {  
164 - return { disabled: true };  
165 - } else {  
166 - return { disabled: false };  
167 - }  
168 - };  
169 - nextTick(() => {  
170 - setProps(selectionOptions);  
171 - });  
172 - /**  
173 - *@param url,name  
174 - **/  
175 - function getParam(url, name) {  
176 - try {  
177 - let reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)');  
178 - let r = url.split('?')[1].match(reg);  
179 - if (r != null) {  
180 - return r[2];  
181 - }  
182 - return ''; //如果此处只写return;则返回的是undefined  
183 - } catch (e) {  
184 - return ''; //如果此处只写return;则返回的是undefined  
185 - }  
186 - }  
187 - getPathUrl.value = window.location.href;  
188 - const name = 'name';  
189 - const getName = getParam(getPathUrl.value, name);  
190 - getPathUrlName.value = decodeURIComponent(getName);  
191 -  
192 - const setRowClassName = async () => {  
193 - if (getPathUrlName.value !== '') {  
194 - const { items } = await deviceConfigGetQuery({  
195 - page: 1,  
196 - pageSize: 10,  
197 - name: getPathUrlName.value,  
198 - });  
199 - nextTick(() => {  
200 - setTableData(items);  
201 - const { setFieldsValue, resetFields } = getForm();  
202 - setFieldsValue({  
203 - name: getPathUrlName.value,  
204 - });  
205 - if (onCloseVal.value == 1) {  
206 - resetFields();  
207 - }  
208 - });  
209 - } else {  
210 - setTimeout(() => {  
211 - reload();  
212 - }, 80);  
213 - }  
214 - };  
215 - setRowClassName();  
216 - onUnmounted(() => {  
217 - getPathUrlName.value = '';  
218 - onCloseVal.value = 1;  
219 - });  
220 - const tableListRef = ref<  
221 - {  
222 - title: string;  
223 - columns?: any[];  
224 - dataSource?: any[];  
225 - }[]  
226 - >([]);  
227 -  
228 - function loadDataSuccess(excelDataList: ExcelData[]) {  
229 - tableListRef.value = [];  
230 - console.log(excelDataList);  
231 - for (const excelData of excelDataList) {  
232 - const {  
233 - header,  
234 - results,  
235 - meta: { sheetName },  
236 - } = excelData;  
237 - const columns: BasicColumn[] = [];  
238 - for (const title of header) {  
239 - columns.push({ title, dataIndex: title });  
240 - }  
241 - tableListRef.value.push({ title: sheetName, dataSource: results, columns });  
242 - }  
243 - }  
244 - //新增  
245 - function handleCreate() {  
246 - openModal(true, {  
247 - isUpdate: false,  
248 - });  
249 - }  
250 - //编辑  
251 - function handleEdit(record: Recordable) {  
252 - openModal(true, {  
253 - record,  
254 - isUpdate: true,  
255 - });  
256 - }  
257 -  
258 - const [registerDrawer, { openDrawer }] = useDrawer();  
259 - //详情  
260 - function handleDetailView(record: Recordable) {  
261 - openDrawer(true, { record });  
262 - }  
263 -  
264 - function defaultHeader({ filename, bookType }: ExportModalResult) {  
265 - // 默认Object.keys(data[0])作为header  
266 - const data = exportData.value;  
267 - jsonToSheetXlsx({  
268 - data,  
269 - filename,  
270 - write2excelOpts: {  
271 - bookType,  
272 - },  
273 - });  
274 - }  
275 - //导出  
276 - const handleExport = (record: Recordable) => {  
277 - exportData.value = [];  
278 - exportData.value.push({  
279 - createTime: record.createTime,  
280 - description: record.description,  
281 - name: record.name,  
282 - });  
283 - nextTick(() => {  
284 - openModalExcel();  
285 - expExcelModalRef.value?.clearFieldFunc();  
286 - });  
287 - };  
288 - //导入  
289 - function handleImport() {  
290 - console.log('record');  
291 - }  
292 - function handleSuccess() {  
293 - reload();  
294 - }  
295 -  
296 - const handleSetDefault = async (record: Recordable) => {  
297 - let id = record.tbProfileId;  
298 - const data = await setDeviceProfileIsDefaultApi(id, 'default', defaultObj);  
299 - if (!data) return createMessage.error('设置该产品为默认失败');  
300 - createMessage.success('设置该产品为默认成功');  
301 - reload();  
302 - disabled.value = true; 2 + import { ref } from 'vue';
  3 + import TableMode from './TableMode.vue';
  4 + import CardMode from './CardMode.vue';
  5 + import { Mode } from '/@/components/Widget';
  6 + const mode = ref(Mode.CARD);
  7 +
  8 + const handleChangeMode = (flag: Mode) => {
  9 + mode.value = flag;
303 }; 10 };
304 </script> 11 </script>
305 12
306 -<style lang="css">  
307 - .devide-profiles .rowcolor {  
308 - color: red;  
309 - }  
310 -  
311 - .devide-profiles .rowcolor2 {  
312 - background: #a2c3e6;  
313 - }  
314 -</style> 13 +<template>
  14 + <section>
  15 + <CardMode v-if="mode === Mode.CARD" :mode="mode" @change-mode="handleChangeMode" />
  16 + <TableMode v-if="mode === Mode.TABLE" :mode="mode" @change-mode="handleChangeMode" />
  17 + </section>
  18 +</template>