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 2 import {
3 3 TDeviceConfigParams,
4 4 IDeviceConfigAddOrEditModel,
  5 + ProfileRecord,
5 6 } from '/@/api/device/model/deviceConfigModel';
  7 +import { PaginationResult } from '/#/axios';
6 8
7 9 enum EDeviceConfigApi {
8 10 /**
... ... @@ -52,7 +54,7 @@ export const alarmContactGetPage = () => {
52 54 * 分页查询设备配置页面
53 55 */
54 56 export const deviceConfigGetQuery = (params?: TDeviceConfigParams) => {
55   - return defHttp.get({
  57 + return defHttp.get<PaginationResult<ProfileRecord>>({
56 58 url: EDeviceConfigApi.DEVICE_CONFIG_GET_PAGE,
57 59 params,
58 60 });
... ...
... ... @@ -199,3 +199,45 @@ export interface AlarmLogItem {
199 199 organizationId: string;
200 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 9 import { h } from 'vue';
10 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 23 export enum ModelOfMatterPermission {
13 24 CREATE = 'api:yt:things_model:post',
14 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 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 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>
... ...