feat: 新增平台设备管理功能、调整设备管理、运维概览列表查询为倒序,添加产品选择组件
- 新增平台设备分配相关API接口与数据模型 - 实现设备选择弹窗组件,支持多选和查询过滤 - 添加产品选择表格组件,支持单选/多选模式切换 - 在设备详情页增加所属平台信息展示与跳转功能 - 优化设备列表页,增加所属平台字段显示 - 更新多个列表页面默认排序为按创建时间倒序 - 修复CronPickerModal组件标签名错误问题 - 移除个人设置页面中的账号绑定模块注释 - 优化产品详情页设备数量链接样式与交互 - 新增平台管理相关API接口与数据模型定义
This commit is contained in:
parent
8727d91415
commit
2ce63301fe
|
@ -0,0 +1,87 @@
|
|||
import type { AppDeviceForm, AppDeviceQuery, AppDeviceVO } from './model';
|
||||
|
||||
import type { ID, IDS, PageResult } from '#/api/common';
|
||||
|
||||
import { commonExport } from '#/api/helper';
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
/**
|
||||
* 查询平台设备分配列表
|
||||
* @param params
|
||||
* @returns 平台设备分配列表
|
||||
*/
|
||||
export function appDeviceList(params?: AppDeviceQuery) {
|
||||
return requestClient.get<PageResult<AppDeviceVO>>(
|
||||
'/application/appDevice/list',
|
||||
{ params },
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出平台设备分配列表
|
||||
* @param params
|
||||
* @returns 平台设备分配列表
|
||||
*/
|
||||
export function appDeviceExport(params?: AppDeviceQuery) {
|
||||
return commonExport('/application/appDevice/export', params ?? {});
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询平台设备分配详情
|
||||
* @param id id
|
||||
* @returns 平台设备分配详情
|
||||
*/
|
||||
export function appDeviceInfo(id: ID) {
|
||||
return requestClient.get<AppDeviceVO>(`/application/appDevice/${id}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增平台设备分配
|
||||
* @param data
|
||||
* @returns void
|
||||
*/
|
||||
export function appDeviceAdd(data: AppDeviceForm) {
|
||||
return requestClient.postWithMsg<void>('/application/appDevice', data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新平台设备分配
|
||||
* @param data
|
||||
* @returns void
|
||||
*/
|
||||
export function appDeviceUpdate(data: AppDeviceForm) {
|
||||
return requestClient.putWithMsg<void>('/application/appDevice', data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除平台设备分配
|
||||
* @param id id
|
||||
* @returns void
|
||||
*/
|
||||
export function appDeviceRemove(id: ID | IDS) {
|
||||
return requestClient.deleteWithMsg<void>(`/application/appDevice/${id}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询未绑定设备列表
|
||||
* @param params
|
||||
* @returns 未绑定设备列表
|
||||
*/
|
||||
export function queryUnBindDevice(params?: AppDeviceQuery) {
|
||||
return requestClient.get<PageResult<AppDeviceVO>>(
|
||||
'/application/appDevice/queryUnBindDevice',
|
||||
{ params },
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询已绑定设备列表
|
||||
* @param params
|
||||
* @returns 已绑定设备列表
|
||||
*/
|
||||
export function queryBindDevice(params?: AppDeviceQuery) {
|
||||
return requestClient.get<PageResult<AppDeviceVO>>(
|
||||
'/application/appDevice/queryBindDevice',
|
||||
{ params },
|
||||
);
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
import type { BaseEntity, PageQuery } from '#/api/common';
|
||||
|
||||
export interface AppDeviceVO {
|
||||
/**
|
||||
* 编号
|
||||
*/
|
||||
id: number | string;
|
||||
|
||||
/**
|
||||
* 设备唯一码
|
||||
*/
|
||||
deviceKey: string;
|
||||
|
||||
/**
|
||||
* 产品码
|
||||
*/
|
||||
productKey: string;
|
||||
|
||||
/**
|
||||
* 平台码
|
||||
*/
|
||||
applicationCode: string;
|
||||
|
||||
/**
|
||||
* 分配时间
|
||||
*/
|
||||
allotTime: string;
|
||||
}
|
||||
|
||||
export interface AppDeviceForm extends BaseEntity {
|
||||
/**
|
||||
* 编号
|
||||
*/
|
||||
id?: number | string;
|
||||
|
||||
/**
|
||||
* 设备唯一码
|
||||
*/
|
||||
deviceKey?: string;
|
||||
|
||||
/**
|
||||
* 产品码
|
||||
*/
|
||||
productKey?: string;
|
||||
|
||||
/**
|
||||
* 平台码
|
||||
*/
|
||||
applicationCode?: string;
|
||||
|
||||
/**
|
||||
* 分配时间
|
||||
*/
|
||||
allotTime?: string;
|
||||
}
|
||||
|
||||
export interface AppDeviceQuery extends PageQuery {
|
||||
/**
|
||||
* 设备唯一码
|
||||
*/
|
||||
deviceKey?: string;
|
||||
|
||||
/**
|
||||
* 产品码
|
||||
*/
|
||||
productKey?: string;
|
||||
|
||||
/**
|
||||
* 平台码
|
||||
*/
|
||||
applicationCode?: string;
|
||||
|
||||
/**
|
||||
* 分配时间
|
||||
*/
|
||||
allotTime?: string;
|
||||
|
||||
/**
|
||||
* 日期范围参数
|
||||
*/
|
||||
params?: any;
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
import type { PlatformForm, PlatformQuery, PlatformVO } from './model';
|
||||
|
||||
import type { ID, IDS, PageResult } from '#/api/common';
|
||||
|
||||
import { commonExport } from '#/api/helper';
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
/**
|
||||
* 查询平台管理列表
|
||||
* @param params
|
||||
* @returns 平台管理列表
|
||||
*/
|
||||
export function platformList(params?: PlatformQuery) {
|
||||
return requestClient.get<PageResult<PlatformVO>>(
|
||||
'/application/platform/list',
|
||||
{ params },
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出平台管理列表
|
||||
* @param params
|
||||
* @returns 平台管理列表
|
||||
*/
|
||||
export function platformExport(params?: PlatformQuery) {
|
||||
return commonExport('/application/platform/export', params ?? {});
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询平台管理详情
|
||||
* @param id id
|
||||
* @returns 平台管理详情
|
||||
*/
|
||||
export function platformInfo(id: ID) {
|
||||
return requestClient.get<PlatformVO>(`/application/platform/${id}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增平台管理
|
||||
* @param data
|
||||
* @returns void
|
||||
*/
|
||||
export function platformAdd(data: PlatformForm) {
|
||||
return requestClient.postWithMsg<void>('/application/platform', data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新平台管理
|
||||
* @param data
|
||||
* @returns void
|
||||
*/
|
||||
export function platformUpdate(data: PlatformForm) {
|
||||
return requestClient.putWithMsg<void>('/application/platform', data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除平台管理
|
||||
* @param id id
|
||||
* @returns void
|
||||
*/
|
||||
export function platformRemove(id: ID | IDS) {
|
||||
return requestClient.deleteWithMsg<void>(`/application/platform/${id}`);
|
||||
}
|
|
@ -0,0 +1,122 @@
|
|||
import type { BaseEntity, PageQuery } from '#/api/common';
|
||||
|
||||
export interface PlatformVO {
|
||||
/**
|
||||
* 编号
|
||||
*/
|
||||
id: number | string;
|
||||
|
||||
/**
|
||||
* 平台名称
|
||||
*/
|
||||
applicationName: string;
|
||||
|
||||
/**
|
||||
* 平台代码
|
||||
*/
|
||||
applicationCode: string;
|
||||
|
||||
/**
|
||||
* 联系人
|
||||
*/
|
||||
contactUser: string;
|
||||
|
||||
/**
|
||||
* 手机号
|
||||
*/
|
||||
contactPhone: string;
|
||||
|
||||
/**
|
||||
* 平台管理员
|
||||
*/
|
||||
applicationAdmin: string;
|
||||
|
||||
/**
|
||||
* 平台管理员密码
|
||||
*/
|
||||
applicationPassword: string;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
remark: string;
|
||||
}
|
||||
|
||||
export interface PlatformForm extends BaseEntity {
|
||||
/**
|
||||
* 编号
|
||||
*/
|
||||
id?: number | string;
|
||||
|
||||
/**
|
||||
* 平台名称
|
||||
*/
|
||||
applicationName?: string;
|
||||
|
||||
/**
|
||||
* 平台代码
|
||||
*/
|
||||
applicationCode?: string;
|
||||
|
||||
/**
|
||||
* 联系人
|
||||
*/
|
||||
contactUser?: string;
|
||||
|
||||
/**
|
||||
* 手机号
|
||||
*/
|
||||
contactPhone?: string;
|
||||
|
||||
/**
|
||||
* 平台管理员
|
||||
*/
|
||||
applicationAdmin?: string;
|
||||
|
||||
/**
|
||||
* 平台管理员密码
|
||||
*/
|
||||
applicationPassword?: string;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
remark?: string;
|
||||
}
|
||||
|
||||
export interface PlatformQuery extends PageQuery {
|
||||
/**
|
||||
* 平台名称
|
||||
*/
|
||||
applicationName?: string;
|
||||
|
||||
/**
|
||||
* 平台代码
|
||||
*/
|
||||
applicationCode?: string;
|
||||
|
||||
/**
|
||||
* 联系人
|
||||
*/
|
||||
contactUser?: string;
|
||||
|
||||
/**
|
||||
* 手机号
|
||||
*/
|
||||
contactPhone?: string;
|
||||
|
||||
/**
|
||||
* 平台管理员
|
||||
*/
|
||||
applicationAdmin?: string;
|
||||
|
||||
/**
|
||||
* 平台管理员密码
|
||||
*/
|
||||
applicationPassword?: string;
|
||||
|
||||
/**
|
||||
* 日期范围参数
|
||||
*/
|
||||
params?: any;
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from 'vue';
|
||||
|
||||
import { Modal } from 'ant-design-vue';
|
||||
import { EasyCronInner } from 'shiyzhangcron';
|
||||
|
||||
import 'shiyzhangcron/dist/style.css';
|
||||
|
@ -45,7 +44,7 @@ const onCronChange = (v: string) => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<Modal
|
||||
<Modalng1
|
||||
:open="isVisible"
|
||||
:title="title || 'Cron 配置'"
|
||||
width="860px"
|
||||
|
@ -61,5 +60,5 @@ const onCronChange = (v: string) => {
|
|||
<div class="mt-4 flex items-center justify-between">
|
||||
<div>当前选择: {{ innerValue }}</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</Modalng1>
|
||||
</template>
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
import type { FormSchemaGetter } from '#/adapter/form';
|
||||
import type { VxeGridProps } from '#/adapter/vxe-table';
|
||||
|
||||
import { deviceTypeOptions, enabledOptions } from '#/constants/dicts';
|
||||
|
||||
export const querySchema: FormSchemaGetter = () => [
|
||||
{
|
||||
component: 'Input',
|
||||
fieldName: 'productKey',
|
||||
label: '产品KEY',
|
||||
},
|
||||
{
|
||||
component: 'Input',
|
||||
fieldName: 'productName',
|
||||
label: '产品名称',
|
||||
},
|
||||
{
|
||||
component: 'TreeSelect',
|
||||
fieldName: 'categoryId',
|
||||
componentProps: {
|
||||
fieldNames: { children: 'children', label: 'label', value: 'id' },
|
||||
},
|
||||
label: '产品分类',
|
||||
},
|
||||
{
|
||||
component: 'Select',
|
||||
componentProps: {
|
||||
options: deviceTypeOptions,
|
||||
},
|
||||
fieldName: 'deviceType',
|
||||
label: '设备类型',
|
||||
},
|
||||
{
|
||||
component: 'Select',
|
||||
componentProps: {
|
||||
options: enabledOptions,
|
||||
},
|
||||
fieldName: 'enabled',
|
||||
label: '启用状态',
|
||||
},
|
||||
];
|
||||
|
||||
// 需要使用i18n注意这里要改成getter形式 否则切换语言不会刷新
|
||||
// export const columns: () => VxeGridProps['columns'] = () => [
|
||||
export const columns: VxeGridProps['columns'] = [
|
||||
{ type: 'radio', width: 60 },
|
||||
// {
|
||||
// title: '编号',
|
||||
// field: 'id',
|
||||
// },
|
||||
{
|
||||
title: '产品KEY',
|
||||
field: 'productKey',
|
||||
},
|
||||
{
|
||||
title: '产品名称',
|
||||
field: 'productName',
|
||||
},
|
||||
{
|
||||
title: '产品分类',
|
||||
field: 'categoryName',
|
||||
},
|
||||
{
|
||||
title: '设备类型',
|
||||
field: 'deviceType',
|
||||
slots: { default: 'deviceType' },
|
||||
},
|
||||
{
|
||||
title: '启用状态',
|
||||
field: 'enabled',
|
||||
slots: { default: 'enabled' },
|
||||
},
|
||||
{
|
||||
title: '描述',
|
||||
field: 'description',
|
||||
},
|
||||
];
|
|
@ -0,0 +1,236 @@
|
|||
<script setup lang="ts" name="ProductSelectTable">
|
||||
import type { VbenFormProps } from '@vben/common-ui';
|
||||
|
||||
import type { VxeGridProps } from '#/adapter/vxe-table';
|
||||
import type { ProductQuery, ProductVO } from '#/api/device/product/model';
|
||||
|
||||
import { computed, nextTick, onMounted, watch } from 'vue';
|
||||
|
||||
import { Tag } from 'ant-design-vue';
|
||||
|
||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import { productList } from '#/api/device/product';
|
||||
import { productCategoryTreeList } from '#/api/device/productCategory';
|
||||
import { deviceTypeOptions } from '#/constants/dicts';
|
||||
|
||||
import { columns, querySchema } from './data';
|
||||
|
||||
interface Props {
|
||||
// 外部受控选中产品列表(完整记录)。建议至少包含 `id` 字段
|
||||
modelValue?: ProductVO[];
|
||||
// 是否多选;true=checkbox,false=radio
|
||||
multiple?: boolean;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
modelValue: () => [],
|
||||
multiple: false,
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:modelValue', value: ProductVO[]): void;
|
||||
(e: 'change', value: ProductVO[]): void;
|
||||
}>();
|
||||
|
||||
// 将选中项的 key 映射出来,便于快速回显
|
||||
const selectedKeySet = computed(
|
||||
() => new Set((props.modelValue || []).map((r) => r.id)),
|
||||
);
|
||||
|
||||
// 查询表单配置
|
||||
const formOptions: VbenFormProps = {
|
||||
commonConfig: {
|
||||
labelWidth: 80,
|
||||
},
|
||||
schema: querySchema(),
|
||||
wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4',
|
||||
};
|
||||
|
||||
// 动态设置第一列为选择列
|
||||
columns[0] = props.multiple
|
||||
? { type: 'checkbox', width: 60 }
|
||||
: { type: 'radio', width: 60 };
|
||||
|
||||
const gridOptions: VxeGridProps = {
|
||||
columns,
|
||||
minHeight: 300,
|
||||
height: 660,
|
||||
keepSource: true,
|
||||
pagerConfig: {
|
||||
pageSize: 10,
|
||||
pageSizes: [10, 20, 50, 100],
|
||||
},
|
||||
rowConfig: {
|
||||
keyField: 'id',
|
||||
},
|
||||
// 多选/单选根据 props 控制
|
||||
checkboxConfig: props.multiple
|
||||
? {
|
||||
highlight: true,
|
||||
reserve: true,
|
||||
trigger: 'row',
|
||||
}
|
||||
: {},
|
||||
radioConfig: props.multiple
|
||||
? {}
|
||||
: {
|
||||
highlight: true,
|
||||
trigger: 'row',
|
||||
},
|
||||
proxyConfig: {
|
||||
ajax: {
|
||||
// 分页查询
|
||||
query: async ({ page }, formValues = {}) => {
|
||||
const params: any = {
|
||||
pageNum: page.currentPage,
|
||||
pageSize: page.pageSize,
|
||||
orderByColumn: 'createTime',
|
||||
isAsc: 'desc',
|
||||
...formValues,
|
||||
};
|
||||
return await productList(params as ProductQuery);
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const [BasicTable, tableApi] = useVbenVxeGrid({
|
||||
formOptions,
|
||||
gridOptions,
|
||||
gridEvents: {
|
||||
// 勾选变化(多选)
|
||||
checkboxChange() {
|
||||
if (!props.multiple) return;
|
||||
const curr = tableApi.grid.getCheckboxRecords();
|
||||
const reserve = tableApi.grid.getCheckboxReserveRecords();
|
||||
const all = [...curr, ...reserve];
|
||||
emit('update:modelValue', all as ProductVO[]);
|
||||
emit('change', all as ProductVO[]);
|
||||
},
|
||||
// 全选(多选)
|
||||
checkboxAll() {
|
||||
if (!props.multiple) return;
|
||||
const curr = tableApi.grid.getCheckboxRecords();
|
||||
const reserve = tableApi.grid.getCheckboxReserveRecords();
|
||||
const all = [...curr, ...reserve];
|
||||
emit('update:modelValue', all as ProductVO[]);
|
||||
emit('change', all as ProductVO[]);
|
||||
},
|
||||
// 单选变化
|
||||
radioChange() {
|
||||
if (props.multiple) return;
|
||||
const record = tableApi.grid.getRadioRecord();
|
||||
const list = record ? [record] : [];
|
||||
emit('update:modelValue', list as ProductVO[]);
|
||||
emit('change', list as ProductVO[]);
|
||||
},
|
||||
// 分页变化时回显
|
||||
pageChange() {
|
||||
// 延迟执行,确保数据加载完成
|
||||
nextTick(() => {
|
||||
setTimeout(() => {
|
||||
applySelectionToCurrentPage();
|
||||
}, 100);
|
||||
});
|
||||
},
|
||||
// 表单查询提交时回显
|
||||
formSubmit() {
|
||||
// 延迟执行,确保数据加载完成
|
||||
nextTick(() => {
|
||||
setTimeout(() => {
|
||||
applySelectionToCurrentPage();
|
||||
}, 100);
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
async function applySelectionToCurrentPage() {
|
||||
await nextTick();
|
||||
const pageRows = tableApi.grid.getData();
|
||||
|
||||
if (!pageRows || pageRows.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (props.multiple) {
|
||||
// 多选回显
|
||||
await tableApi.grid.clearCheckboxRow();
|
||||
const needCheck = pageRows.filter((r: any) =>
|
||||
selectedKeySet.value.has(r.id),
|
||||
);
|
||||
if (needCheck.length > 0) {
|
||||
await tableApi.grid.setCheckboxRow(needCheck, true);
|
||||
}
|
||||
} else {
|
||||
// 单选回显
|
||||
await tableApi.grid.clearRadioRow();
|
||||
const target = pageRows.find((r: any) => selectedKeySet.value.has(r.id));
|
||||
if (target) {
|
||||
await tableApi.grid.setRadioRow(target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 加载分类数据
|
||||
const loadFormOptions = async () => {
|
||||
try {
|
||||
const categoryOptions = await productCategoryTreeList();
|
||||
const placeholder = categoryOptions.length > 0 ? '请选择' : '暂无产品分类';
|
||||
tableApi.formApi.updateSchema([
|
||||
{
|
||||
componentProps: {
|
||||
treeData: categoryOptions || [],
|
||||
placeholder,
|
||||
},
|
||||
fieldName: 'categoryId',
|
||||
},
|
||||
]);
|
||||
} catch (error) {
|
||||
console.error('加载表单选项失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// 外部受控值变化时,回显到当前页(并保留跨页)
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
async () => {
|
||||
// 延迟执行,确保表格数据已加载
|
||||
await nextTick();
|
||||
setTimeout(() => {
|
||||
applySelectionToCurrentPage();
|
||||
}, 50);
|
||||
},
|
||||
{ deep: true },
|
||||
);
|
||||
|
||||
onMounted(async () => {
|
||||
// 加载分类数据
|
||||
await loadFormOptions();
|
||||
// 初次挂载后,延迟回显
|
||||
setTimeout(() => {
|
||||
applySelectionToCurrentPage();
|
||||
}, 200);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<BasicTable>
|
||||
<template #toolbar-actions>
|
||||
<span class="pl-[7px] text-[16px]">产品选择</span>
|
||||
</template>
|
||||
<template #deviceType="{ row }">
|
||||
<Tag color="processing">
|
||||
{{
|
||||
deviceTypeOptions.find((option) => option.value === row.deviceType)
|
||||
?.label
|
||||
}}
|
||||
</Tag>
|
||||
</template>
|
||||
<template #enabled="{ row }">
|
||||
<Tag :color="row.enabled === '1' ? 'success' : 'error'">
|
||||
{{ row.enabled === '1' ? '启用' : '禁用' }}
|
||||
</Tag>
|
||||
</template>
|
||||
</BasicTable>
|
||||
</template>
|
|
@ -1,7 +1,6 @@
|
|||
<script setup lang="ts">
|
||||
import { TabPane, Tabs } from 'ant-design-vue';
|
||||
|
||||
import AccountBind from './components/account-bind.vue';
|
||||
import BaseSetting from './components/base-setting.vue';
|
||||
import OnlineDevice from './components/online-device.vue';
|
||||
import SecureSetting from './components/secure-setting.vue';
|
||||
|
@ -17,11 +16,11 @@ const settingList = [
|
|||
key: '2',
|
||||
name: '安全设置',
|
||||
},
|
||||
{
|
||||
component: AccountBind,
|
||||
key: '3',
|
||||
name: '账号绑定',
|
||||
},
|
||||
// {
|
||||
// component: AccountBind,
|
||||
// key: '3',
|
||||
// name: '账号绑定',
|
||||
// },
|
||||
{
|
||||
component: OnlineDevice,
|
||||
key: '4',
|
||||
|
|
|
@ -0,0 +1,104 @@
|
|||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import { useVbenDrawer } from '@vben/common-ui';
|
||||
import { $t } from '@vben/locales';
|
||||
import { cloneDeep } from '@vben/utils';
|
||||
|
||||
import { useVbenForm } from '#/adapter/form';
|
||||
import {
|
||||
appDeviceAdd,
|
||||
appDeviceInfo,
|
||||
appDeviceUpdate,
|
||||
} from '#/api/application/appDevice';
|
||||
import { defaultFormValueGetter, useBeforeCloseDiff } from '#/utils/popup';
|
||||
|
||||
import { drawerSchema } from './data';
|
||||
|
||||
const emit = defineEmits<{ reload: [] }>();
|
||||
|
||||
const isUpdate = ref(false);
|
||||
const title = computed(() => {
|
||||
return isUpdate.value ? $t('pages.common.edit') : $t('pages.common.add');
|
||||
});
|
||||
|
||||
const [BasicForm, formApi] = useVbenForm({
|
||||
commonConfig: {
|
||||
// 默认占满两列
|
||||
formItemClass: 'col-span-2',
|
||||
// 默认label宽度 px
|
||||
labelWidth: 80,
|
||||
// 通用配置项 会影响到所有表单项
|
||||
componentProps: {
|
||||
class: 'w-full',
|
||||
},
|
||||
},
|
||||
schema: drawerSchema(),
|
||||
showDefaultActions: false,
|
||||
wrapperClass: 'grid-cols-2',
|
||||
});
|
||||
|
||||
const { onBeforeClose, markInitialized, resetInitialized } = useBeforeCloseDiff(
|
||||
{
|
||||
initializedGetter: defaultFormValueGetter(formApi),
|
||||
currentGetter: defaultFormValueGetter(formApi),
|
||||
},
|
||||
);
|
||||
|
||||
const [BasicDrawer, drawerApi] = useVbenDrawer({
|
||||
// 在这里更改宽度
|
||||
class: 'w-[550px]',
|
||||
fullscreenButton: false,
|
||||
onBeforeClose,
|
||||
onClosed: handleClosed,
|
||||
onConfirm: handleConfirm,
|
||||
onOpenChange: async (isOpen) => {
|
||||
if (!isOpen) {
|
||||
return null;
|
||||
}
|
||||
drawerApi.drawerLoading(true);
|
||||
|
||||
const { id } = drawerApi.getData() as { id?: number | string };
|
||||
isUpdate.value = !!id;
|
||||
|
||||
if (isUpdate.value && id) {
|
||||
const record = await appDeviceInfo(id);
|
||||
await formApi.setValues(record);
|
||||
}
|
||||
await markInitialized();
|
||||
|
||||
drawerApi.drawerLoading(false);
|
||||
},
|
||||
});
|
||||
|
||||
async function handleConfirm() {
|
||||
try {
|
||||
drawerApi.lock(true);
|
||||
const { valid } = await formApi.validate();
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
// getValues获取为一个readonly的对象 需要修改必须先深拷贝一次
|
||||
const data = cloneDeep(await formApi.getValues());
|
||||
await (isUpdate.value ? appDeviceUpdate(data) : appDeviceAdd(data));
|
||||
resetInitialized();
|
||||
emit('reload');
|
||||
drawerApi.close();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
drawerApi.lock(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleClosed() {
|
||||
await formApi.resetForm();
|
||||
resetInitialized();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<BasicDrawer :title="title">
|
||||
<BasicForm />
|
||||
</BasicDrawer>
|
||||
</template>
|
|
@ -0,0 +1,125 @@
|
|||
import type { FormSchemaGetter } from '#/adapter/form';
|
||||
import type { VxeGridProps } from '#/adapter/vxe-table';
|
||||
|
||||
export const querySchema: FormSchemaGetter = () => [
|
||||
{
|
||||
component: 'Input',
|
||||
fieldName: 'deviceKey',
|
||||
label: '设备KEY',
|
||||
},
|
||||
{
|
||||
component: 'Input',
|
||||
fieldName: 'deviceName',
|
||||
label: '设备名称',
|
||||
},
|
||||
{
|
||||
component: 'Select',
|
||||
fieldName: 'productKey',
|
||||
label: '所属产品',
|
||||
},
|
||||
// {
|
||||
// component: 'Select',
|
||||
// componentProps: {
|
||||
// options: deviceStateOptions,
|
||||
// },
|
||||
// fieldName: 'deviceState',
|
||||
// label: '设备状态',
|
||||
// },
|
||||
// {
|
||||
// component: 'Select',
|
||||
// componentProps: {
|
||||
// options: deviceTypeOptions,
|
||||
// },
|
||||
// fieldName: 'deviceType',
|
||||
// label: '设备类型',
|
||||
// },
|
||||
];
|
||||
|
||||
// 需要使用i18n注意这里要改成getter形式 否则切换语言不会刷新
|
||||
// export const columns: () => VxeGridProps['columns'] = () => [
|
||||
export const columns: VxeGridProps['columns'] = [
|
||||
{ type: 'checkbox', width: 60 },
|
||||
// {
|
||||
// title: '编号',
|
||||
// field: 'id',
|
||||
// },
|
||||
{
|
||||
title: '设备KEY',
|
||||
field: 'deviceKey',
|
||||
},
|
||||
{
|
||||
title: '设备名称',
|
||||
field: 'deviceName',
|
||||
},
|
||||
{
|
||||
title: '所属产品',
|
||||
field: 'productKey',
|
||||
slots: { default: 'productKey' },
|
||||
},
|
||||
{
|
||||
title: '设备类型',
|
||||
field: 'deviceType',
|
||||
slots: { default: 'deviceType' },
|
||||
},
|
||||
{
|
||||
title: '设备状态',
|
||||
field: 'deviceState',
|
||||
slots: { default: 'deviceState' },
|
||||
},
|
||||
{
|
||||
title: '描述',
|
||||
field: 'description',
|
||||
},
|
||||
// {
|
||||
// field: 'action',
|
||||
// fixed: 'right',
|
||||
// slots: { default: 'action' },
|
||||
// title: '操作',
|
||||
// width: 180,
|
||||
// },
|
||||
];
|
||||
|
||||
export const drawerSchema: FormSchemaGetter = () => [
|
||||
{
|
||||
label: '编号',
|
||||
fieldName: 'id',
|
||||
component: 'Input',
|
||||
dependencies: {
|
||||
show: () => false,
|
||||
triggerFields: [''],
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '设备KEY',
|
||||
fieldName: 'deviceKey',
|
||||
component: 'Input',
|
||||
rules: 'required',
|
||||
},
|
||||
{
|
||||
label: '设备名称',
|
||||
fieldName: 'deviceName',
|
||||
component: 'Input',
|
||||
rules: 'required',
|
||||
},
|
||||
{
|
||||
label: '所属产品',
|
||||
fieldName: 'productId',
|
||||
component: 'Select',
|
||||
componentProps: {},
|
||||
rules: 'selectRequired',
|
||||
},
|
||||
{
|
||||
label: '设备图片',
|
||||
fieldName: 'imgId',
|
||||
component: 'ImageUpload',
|
||||
componentProps: {
|
||||
// accept: 'image/*', // 可选拓展名或者mime类型 ,拼接
|
||||
// maxCount: 1, // 最大上传文件数 默认为1 为1会绑定为string而非string[]类型
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '描述',
|
||||
fieldName: 'description',
|
||||
component: 'Textarea',
|
||||
},
|
||||
];
|
|
@ -0,0 +1,230 @@
|
|||
<script setup lang="ts">
|
||||
import type { VbenFormProps } from '@vben/common-ui';
|
||||
|
||||
import type { VxeGridProps } from '#/adapter/vxe-table';
|
||||
import type { DeviceForm } from '#/api/device/device/model';
|
||||
|
||||
import { computed, ref, watch } from 'vue';
|
||||
|
||||
import { Modal, Tag } from 'ant-design-vue';
|
||||
|
||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import { queryUnBindDevice } from '#/api/application/appDevice';
|
||||
import { productList } from '#/api/device/product';
|
||||
import { deviceStateOptions, deviceTypeOptions } from '#/constants/dicts';
|
||||
|
||||
import { columns, querySchema } from './data';
|
||||
|
||||
interface Props {
|
||||
visible: boolean;
|
||||
title?: string;
|
||||
multiple?: boolean;
|
||||
selectedDevices?: DeviceForm[];
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
title: '选择设备',
|
||||
multiple: true,
|
||||
selectedDevices: () => [],
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
(e: 'confirm', devices: DeviceForm[]): void;
|
||||
(e: 'cancel'): void;
|
||||
}>();
|
||||
|
||||
const formOptions: VbenFormProps = {
|
||||
collapsed: true,
|
||||
commonConfig: {
|
||||
labelWidth: 80,
|
||||
componentProps: {
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
schema: querySchema(),
|
||||
wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4',
|
||||
};
|
||||
|
||||
const gridOptions: VxeGridProps = {
|
||||
checkboxConfig: {
|
||||
highlight: true,
|
||||
reserve: true,
|
||||
trigger: 'row',
|
||||
},
|
||||
columns,
|
||||
minHeight: 300,
|
||||
height: 660,
|
||||
keepSource: true,
|
||||
pagerConfig: {
|
||||
pageSize: 10,
|
||||
pageSizes: [10, 20, 50, 100],
|
||||
},
|
||||
proxyConfig: {
|
||||
ajax: {
|
||||
query: async ({ page }, formValues = {}) => {
|
||||
return await queryUnBindDevice({
|
||||
pageNum: page.currentPage,
|
||||
pageSize: page.pageSize,
|
||||
...formValues,
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
rowConfig: {
|
||||
keyField: 'id',
|
||||
},
|
||||
id: 'device-select-modal',
|
||||
};
|
||||
|
||||
const [BasicTable, tableApi] = useVbenVxeGrid({
|
||||
formOptions,
|
||||
gridOptions,
|
||||
});
|
||||
|
||||
// 产品选项
|
||||
const productOptions = ref<{ label: string; value: string }[]>([]);
|
||||
|
||||
// 加载产品选项
|
||||
const loadProductOptions = async () => {
|
||||
try {
|
||||
const res = await productList({ pageNum: 1, pageSize: 1000, enabled: '1' });
|
||||
productOptions.value = (res?.rows || []).map((item: any) => ({
|
||||
label: item.productName,
|
||||
value: item.productKey,
|
||||
}));
|
||||
|
||||
// 更新表单中的产品选项
|
||||
await tableApi.formApi.updateSchema(
|
||||
querySchema().map((field: any) => {
|
||||
if (field.fieldName === 'productKey') {
|
||||
return {
|
||||
...field,
|
||||
componentProps: {
|
||||
...field.componentProps,
|
||||
options: productOptions.value,
|
||||
},
|
||||
};
|
||||
}
|
||||
return field;
|
||||
}),
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('加载产品选项失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// 设备类型标签
|
||||
const getDeviceTypeLabel = (value?: string) => {
|
||||
const match = deviceTypeOptions.find((option) => option.value === value);
|
||||
return match ? match.label : '-';
|
||||
};
|
||||
|
||||
// 设备状态标签
|
||||
const getDeviceStateMeta = (value?: string) => {
|
||||
const match = deviceStateOptions.find((option) => option.value === value);
|
||||
return {
|
||||
label: match ? match.label : '-',
|
||||
type: match ? match.type : 'default',
|
||||
};
|
||||
};
|
||||
|
||||
// 处理确定
|
||||
const handleConfirm = () => {
|
||||
const selectedRows = tableApi.grid.getCheckboxRecords();
|
||||
emit('confirm', selectedRows);
|
||||
emit('update:visible', false);
|
||||
};
|
||||
|
||||
// 处理取消
|
||||
const handleCancel = () => {
|
||||
emit('cancel');
|
||||
emit('update:visible', false);
|
||||
};
|
||||
|
||||
// 监听弹窗显示状态
|
||||
const isVisible = computed(() => props.visible);
|
||||
watch(
|
||||
() => props.visible,
|
||||
(visible) => {
|
||||
if (visible) {
|
||||
// 弹窗显示时加载数据
|
||||
loadProductOptions();
|
||||
setTimeout(() => {
|
||||
tableApi?.reload();
|
||||
}, 100);
|
||||
} else {
|
||||
// 弹窗关闭时重置选择
|
||||
tableApi.grid.clearCheckboxRow();
|
||||
}
|
||||
},
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal
|
||||
v-model:open="isVisible"
|
||||
:title="title"
|
||||
width="1200px"
|
||||
@ok="handleConfirm"
|
||||
@cancel="handleCancel"
|
||||
>
|
||||
<div class="device-select-modal">
|
||||
<BasicTable>
|
||||
<template #deviceType="{ row }">
|
||||
<Tag color="processing">{{ getDeviceTypeLabel(row.deviceType) }}</Tag>
|
||||
</template>
|
||||
<template #deviceState="{ row }">
|
||||
<Tag :color="getDeviceStateMeta(row.deviceState).type">
|
||||
{{ getDeviceStateMeta(row.deviceState).label }}
|
||||
</Tag>
|
||||
</template>
|
||||
<template #productKey="{ row }">
|
||||
<div class="flex flex-col">
|
||||
<span class="font-medium">{{
|
||||
productOptions.find((option) => option.value === row.productKey)
|
||||
?.label
|
||||
}}</span>
|
||||
</div>
|
||||
</template>
|
||||
<!-- <template #action="{ row }">
|
||||
<Space>
|
||||
<a-button
|
||||
type="link"
|
||||
size="small"
|
||||
@click="tableApi.grid.toggleCheckboxRow(row)"
|
||||
>
|
||||
{{
|
||||
tableApi.grid.isCheckedByCheckboxRow(row) ? '取消选择' : '选择'
|
||||
}}
|
||||
</a-button>
|
||||
</Space>
|
||||
</template> -->
|
||||
</BasicTable>
|
||||
|
||||
<!-- <div class="mt-4 flex items-center justify-between">
|
||||
<div class="text-sm text-gray-500">
|
||||
已选择
|
||||
{{
|
||||
vxeCheckboxChecked(tableApi)
|
||||
? tableApi.grid.getCheckboxRecords().length
|
||||
: 0
|
||||
}}
|
||||
个设备
|
||||
</div>
|
||||
<Space>
|
||||
<a-button @click="handleCancel">取消</a-button>
|
||||
<a-button
|
||||
type="primary"
|
||||
:disabled="!vxeCheckboxChecked(tableApi)"
|
||||
@click="handleConfirm"
|
||||
>
|
||||
确定
|
||||
</a-button>
|
||||
</Space>
|
||||
</div> -->
|
||||
</div>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
|
@ -0,0 +1,99 @@
|
|||
import type { FormSchemaGetter } from '#/adapter/form';
|
||||
import type { VxeGridProps } from '#/adapter/vxe-table';
|
||||
|
||||
export const querySchema: FormSchemaGetter = () => [
|
||||
{
|
||||
component: 'Select',
|
||||
componentProps: {
|
||||
allowClear: true,
|
||||
filterOption: true,
|
||||
placeholder: '请选择所属平台',
|
||||
showSearch: true,
|
||||
},
|
||||
fieldName: 'applicationCode',
|
||||
label: '所属平台',
|
||||
},
|
||||
{
|
||||
component: 'Input',
|
||||
fieldName: 'deviceKey',
|
||||
label: '设备码',
|
||||
},
|
||||
{
|
||||
component: 'Input',
|
||||
fieldName: 'deviceName',
|
||||
label: '设备名称',
|
||||
},
|
||||
{
|
||||
component: 'Select',
|
||||
fieldName: 'productKey',
|
||||
label: '所属产品',
|
||||
},
|
||||
];
|
||||
|
||||
// 需要使用i18n注意这里要改成getter形式 否则切换语言不会刷新
|
||||
// export const columns: () => VxeGridProps['columns'] = () => [
|
||||
export const columns: VxeGridProps['columns'] = [
|
||||
{ type: 'checkbox', width: 60 },
|
||||
// {
|
||||
// title: '编号',
|
||||
// field: 'id',
|
||||
// },
|
||||
{
|
||||
title: '所属平台',
|
||||
field: 'applicationName',
|
||||
},
|
||||
{
|
||||
title: '设备码',
|
||||
field: 'deviceKey',
|
||||
},
|
||||
{
|
||||
title: '设备名称',
|
||||
field: 'deviceName',
|
||||
},
|
||||
{
|
||||
title: '所属产品',
|
||||
field: 'productKey',
|
||||
slots: { default: 'productKey' },
|
||||
},
|
||||
{
|
||||
title: '分配时间',
|
||||
field: 'allotTime',
|
||||
},
|
||||
{
|
||||
field: 'action',
|
||||
fixed: 'right',
|
||||
slots: { default: 'action' },
|
||||
title: '操作',
|
||||
width: 180,
|
||||
},
|
||||
];
|
||||
|
||||
export const drawerSchema: FormSchemaGetter = () => [
|
||||
{
|
||||
label: '编号',
|
||||
fieldName: 'id',
|
||||
component: 'Input',
|
||||
dependencies: {
|
||||
show: () => false,
|
||||
triggerFields: [''],
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '设备唯一码',
|
||||
fieldName: 'deviceKey',
|
||||
component: 'Input',
|
||||
rules: 'required',
|
||||
},
|
||||
{
|
||||
label: '产品码',
|
||||
fieldName: 'productKey',
|
||||
component: 'Input',
|
||||
rules: 'required',
|
||||
},
|
||||
{
|
||||
label: '所属平台码',
|
||||
fieldName: 'applicationCode',
|
||||
component: 'Input',
|
||||
rules: 'required',
|
||||
},
|
||||
];
|
|
@ -0,0 +1,332 @@
|
|||
<script setup lang="ts">
|
||||
import type { VbenFormProps } from '@vben/common-ui';
|
||||
|
||||
import type { VxeGridProps } from '#/adapter/vxe-table';
|
||||
import type { AppDeviceForm } from '#/api/application/appDevice/model';
|
||||
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
|
||||
import { Page, prompt, useVbenDrawer } from '@vben/common-ui';
|
||||
import { getVxePopupContainer } from '@vben/utils';
|
||||
|
||||
import { message, Modal, Popconfirm, Select, Space } from 'ant-design-vue';
|
||||
|
||||
import { useVbenVxeGrid, vxeCheckboxChecked } from '#/adapter/vxe-table';
|
||||
import {
|
||||
appDeviceAdd,
|
||||
appDeviceExport,
|
||||
appDeviceRemove,
|
||||
queryBindDevice,
|
||||
} from '#/api/application/appDevice';
|
||||
import { platformList } from '#/api/application/platform';
|
||||
import { productList } from '#/api/device/product';
|
||||
import { commonDownloadExcel } from '#/utils/file/download';
|
||||
|
||||
import appDeviceDrawer from './appDevice-drawer.vue';
|
||||
import DeviceSelectModal from './components/device-select-modal.vue';
|
||||
import { columns, querySchema } from './data';
|
||||
|
||||
const applicationOptions = ref<{ label: string; value: string }[]>([]);
|
||||
|
||||
const deviceSelectModelVisible = ref(false);
|
||||
|
||||
const route = useRoute();
|
||||
|
||||
const deviceBindObject = ref<any>({
|
||||
applicationCode: '',
|
||||
deviceKeys: [],
|
||||
});
|
||||
|
||||
const formOptions: VbenFormProps = {
|
||||
collapsed: false,
|
||||
commonConfig: {
|
||||
labelWidth: 80,
|
||||
componentProps: {
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
schema: querySchema(),
|
||||
wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4',
|
||||
};
|
||||
|
||||
const gridOptions: VxeGridProps = {
|
||||
checkboxConfig: {
|
||||
// 高亮
|
||||
highlight: true,
|
||||
// 翻页时保留选中状态
|
||||
reserve: true,
|
||||
// 点击行选中
|
||||
// trigger: 'row',
|
||||
},
|
||||
// 需要使用i18n注意这里要改成getter形式 否则切换语言不会刷新
|
||||
// columns: columns(),
|
||||
columns,
|
||||
height: 'auto',
|
||||
keepSource: true,
|
||||
pagerConfig: {},
|
||||
proxyConfig: {
|
||||
ajax: {
|
||||
query: async ({ page }, formValues = {}) => {
|
||||
return await queryBindDevice({
|
||||
pageNum: page.currentPage,
|
||||
pageSize: page.pageSize,
|
||||
...formValues,
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
rowConfig: {
|
||||
keyField: 'id',
|
||||
},
|
||||
// 表格全局唯一表示 保存列配置需要用到
|
||||
id: 'application-appDevice-index',
|
||||
};
|
||||
|
||||
const [BasicTable, tableApi] = useVbenVxeGrid({
|
||||
formOptions,
|
||||
gridOptions,
|
||||
});
|
||||
|
||||
const [AppDeviceDrawer] = useVbenDrawer({
|
||||
connectedComponent: appDeviceDrawer,
|
||||
});
|
||||
|
||||
// 产品选项
|
||||
const productOptions = ref<{ label: string; value: string }[]>([]);
|
||||
|
||||
// 加载产品选项
|
||||
const loadProductOptions = async () => {
|
||||
try {
|
||||
const res = await productList({ pageNum: 1, pageSize: 1000, enabled: '1' });
|
||||
productOptions.value = (res?.rows || []).map((item: any) => ({
|
||||
label: item.productName,
|
||||
value: item.productKey,
|
||||
}));
|
||||
|
||||
// 更新表单中的产品选项
|
||||
await tableApi.formApi.updateSchema(
|
||||
querySchema().map((field: any) => {
|
||||
if (field.fieldName === 'productKey') {
|
||||
return {
|
||||
...field,
|
||||
componentProps: {
|
||||
...field.componentProps,
|
||||
options: productOptions.value,
|
||||
},
|
||||
};
|
||||
}
|
||||
return field;
|
||||
}),
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('加载产品选项失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleAdd = () => {
|
||||
deviceBindObject.value = {
|
||||
applicationCode: '',
|
||||
deviceKeys: [],
|
||||
};
|
||||
loadapplication();
|
||||
prompt({
|
||||
component: Select,
|
||||
componentProps: {
|
||||
options: applicationOptions.value,
|
||||
placeholder: '请选择',
|
||||
popupClassName: 'pointer-events-auto',
|
||||
},
|
||||
content: '请选择要分配设备的平台',
|
||||
modelPropName: 'value',
|
||||
})
|
||||
.then((val) => {
|
||||
if (val) {
|
||||
deviceBindObject.value.applicationCode = val;
|
||||
deviceSelectModelVisible.value = true;
|
||||
} else {
|
||||
message.error('请选择一个平台');
|
||||
}
|
||||
})
|
||||
.catch(() => {});
|
||||
// drawerApi.setData({});
|
||||
// drawerApi.open();
|
||||
};
|
||||
|
||||
// async function handleEdit(row: Required<AppDeviceForm>) {
|
||||
// drawerApi.setData({ id: row.id });
|
||||
// drawerApi.open();
|
||||
// }
|
||||
|
||||
async function handleDelete(row: Required<AppDeviceForm>) {
|
||||
await appDeviceRemove(row.id);
|
||||
await tableApi.query();
|
||||
}
|
||||
|
||||
function handleMultiDelete() {
|
||||
const rows = tableApi.grid.getCheckboxRecords();
|
||||
const ids = rows.map((row: Required<AppDeviceForm>) => row.id);
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
okType: 'danger',
|
||||
content: `确认解除分配选中的${ids.length}个设备吗?`,
|
||||
onOk: async () => {
|
||||
await appDeviceRemove(ids);
|
||||
await tableApi.query();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function handleDownloadExcel() {
|
||||
commonDownloadExcel(
|
||||
appDeviceExport,
|
||||
'平台设备分配数据',
|
||||
tableApi.formApi.form.values,
|
||||
{
|
||||
fieldMappingTime: formOptions.fieldMappingTime,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// const getApplicationOptions = async () => {
|
||||
// const res = await platformList({ pageNum: 1, pageSize: 1000 } as any);
|
||||
// const option = (res?.rows || []).map((item: any) => ({
|
||||
// label: item.applicationName,
|
||||
// value: item.id,
|
||||
// }));
|
||||
// applicationOptions.value = option;
|
||||
// };
|
||||
|
||||
const confirmDeviceSelect = async (arr: any[]) => {
|
||||
console.log('确认选择设备', arr);
|
||||
if (arr.length === 0) {
|
||||
message.error('请至少选择一个设备');
|
||||
return;
|
||||
}
|
||||
deviceBindObject.value.deviceKeys = arr.map((item) => item.deviceKey);
|
||||
console.log('确认选择设备id列表', deviceBindObject.value);
|
||||
// 提交绑定请求
|
||||
await appDeviceAdd(deviceBindObject.value);
|
||||
tableApi.query();
|
||||
deviceSelectModelVisible.value = false;
|
||||
};
|
||||
|
||||
const onDeviceSelectCancel = () => {
|
||||
console.log('取消选择设备');
|
||||
deviceSelectModelVisible.value = false;
|
||||
};
|
||||
|
||||
const loadapplication = async () => {
|
||||
const res = await platformList({ pageNum: 1, pageSize: 1000 } as any);
|
||||
const option = (res?.rows || []).map((item: any) => ({
|
||||
label: item.applicationName,
|
||||
value: item.applicationCode,
|
||||
}));
|
||||
applicationOptions.value = option;
|
||||
// 更新表单字段的选项
|
||||
await tableApi.formApi.updateSchema(
|
||||
querySchema().map((field: any) => {
|
||||
if (field.fieldName === 'applicationCode') {
|
||||
return {
|
||||
...field,
|
||||
componentProps: {
|
||||
...field.componentProps,
|
||||
options: option,
|
||||
},
|
||||
};
|
||||
}
|
||||
return field;
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
loadapplication();
|
||||
loadProductOptions();
|
||||
const applicationCode = route.query.applicationCode;
|
||||
if (applicationCode && typeof applicationCode === 'string') {
|
||||
tableApi.formApi.setFieldValue('applicationCode', applicationCode);
|
||||
tableApi.query();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Page :auto-content-height="true">
|
||||
<BasicTable table-title="设备分配列表">
|
||||
<template #toolbar-tools>
|
||||
<Space>
|
||||
<a-button
|
||||
v-access:code="['application:appDevice:export']"
|
||||
@click="handleDownloadExcel"
|
||||
>
|
||||
{{ $t('pages.common.export') }}
|
||||
</a-button>
|
||||
<a-button
|
||||
:disabled="!vxeCheckboxChecked(tableApi)"
|
||||
danger
|
||||
type="primary"
|
||||
v-access:code="['application:appDevice:remove']"
|
||||
@click="handleMultiDelete"
|
||||
>
|
||||
解除分配
|
||||
</a-button>
|
||||
<a-button
|
||||
type="primary"
|
||||
v-access:code="['application:appDevice:add']"
|
||||
@click="handleAdd"
|
||||
>
|
||||
分配设备
|
||||
</a-button>
|
||||
</Space>
|
||||
</template>
|
||||
<template #applicationCode="{ row }">
|
||||
<div class="flex flex-col">
|
||||
<span class="font-medium">{{
|
||||
applicationOptions.find(
|
||||
(option) => option.value === row.applicationCode,
|
||||
)?.label
|
||||
}}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #productKey="{ row }">
|
||||
<div class="flex flex-col">
|
||||
<span class="font-medium">{{
|
||||
productOptions.find((option) => option.value === row.productKey)
|
||||
?.label
|
||||
}}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #action="{ row }">
|
||||
<Space>
|
||||
<!-- <ghost-button
|
||||
v-access:code="['application:appDevice:edit']"
|
||||
@click.stop="handleEdit(row)"
|
||||
>
|
||||
{{ $t('pages.common.edit') }}
|
||||
</ghost-button> -->
|
||||
<Popconfirm
|
||||
:get-popup-container="getVxePopupContainer"
|
||||
placement="left"
|
||||
title="确认解除分配?"
|
||||
@confirm="handleDelete(row)"
|
||||
>
|
||||
<ghost-button
|
||||
danger
|
||||
v-access:code="['application:appDevice:remove']"
|
||||
@click.stop=""
|
||||
>
|
||||
解除分配
|
||||
</ghost-button>
|
||||
</Popconfirm>
|
||||
</Space>
|
||||
</template>
|
||||
</BasicTable>
|
||||
<AppDeviceDrawer @reload="tableApi.query()" />
|
||||
<DeviceSelectModal
|
||||
:visible="deviceSelectModelVisible"
|
||||
@confirm="confirmDeviceSelect"
|
||||
@cancel="onDeviceSelectCancel"
|
||||
/>
|
||||
</Page>
|
||||
</template>
|
|
@ -0,0 +1,146 @@
|
|||
import type { FormSchemaGetter } from '#/adapter/form';
|
||||
import type { VxeGridProps } from '#/adapter/vxe-table';
|
||||
|
||||
import { z } from '#/adapter/form';
|
||||
|
||||
export const querySchema: FormSchemaGetter = () => [
|
||||
{
|
||||
component: 'Input',
|
||||
fieldName: 'applicationCode',
|
||||
label: '平台代码',
|
||||
},
|
||||
{
|
||||
component: 'Input',
|
||||
fieldName: 'applicationName',
|
||||
label: '平台名称',
|
||||
},
|
||||
{
|
||||
component: 'Input',
|
||||
fieldName: 'applicationAdmin',
|
||||
label: '平台账号',
|
||||
},
|
||||
{
|
||||
component: 'Input',
|
||||
fieldName: 'contactPhone',
|
||||
label: '手机号',
|
||||
},
|
||||
];
|
||||
|
||||
// 需要使用i18n注意这里要改成getter形式 否则切换语言不会刷新
|
||||
// export const columns: () => VxeGridProps['columns'] = () => [
|
||||
export const columns: VxeGridProps['columns'] = [
|
||||
{ type: 'checkbox', width: 60 },
|
||||
// {
|
||||
// title: '编号',
|
||||
// field: 'id',
|
||||
// },
|
||||
{
|
||||
title: '平台代码',
|
||||
field: 'applicationCode',
|
||||
},
|
||||
{
|
||||
title: '平台名称',
|
||||
field: 'applicationName',
|
||||
},
|
||||
{
|
||||
title: '联系人',
|
||||
field: 'contactUser',
|
||||
},
|
||||
{
|
||||
title: '手机号',
|
||||
field: 'contactPhone',
|
||||
},
|
||||
{
|
||||
title: '平台账号',
|
||||
field: 'applicationAdmin',
|
||||
},
|
||||
{
|
||||
title: '备注',
|
||||
field: 'remark',
|
||||
},
|
||||
{
|
||||
field: 'action',
|
||||
fixed: 'right',
|
||||
slots: { default: 'action' },
|
||||
title: '操作',
|
||||
width: 240,
|
||||
},
|
||||
];
|
||||
|
||||
export const drawerSchema: FormSchemaGetter = () => [
|
||||
{
|
||||
label: '编号',
|
||||
fieldName: 'id',
|
||||
component: 'Input',
|
||||
dependencies: {
|
||||
show: () => false,
|
||||
triggerFields: [''],
|
||||
},
|
||||
},
|
||||
{
|
||||
component: 'Divider',
|
||||
componentProps: {
|
||||
orientation: 'center',
|
||||
},
|
||||
fieldName: 'divider1',
|
||||
hideLabel: true,
|
||||
renderComponentContent: () => ({
|
||||
default: () => '平台信息',
|
||||
}),
|
||||
},
|
||||
{
|
||||
label: '平台代码',
|
||||
fieldName: 'applicationCode',
|
||||
component: 'Input',
|
||||
rules: z
|
||||
.string()
|
||||
.regex(/^[a-z]\w*$/i, '平台代码需字母开头,且仅包含字母、数字、下划线'),
|
||||
},
|
||||
{
|
||||
label: '平台名称',
|
||||
fieldName: 'applicationName',
|
||||
component: 'Input',
|
||||
rules: 'required',
|
||||
},
|
||||
{
|
||||
label: '平台账号',
|
||||
fieldName: 'applicationAdmin',
|
||||
component: 'Input',
|
||||
rules: 'required',
|
||||
},
|
||||
{
|
||||
label: '平台密码',
|
||||
fieldName: 'applicationPassword',
|
||||
component: 'InputPassword',
|
||||
rules: 'required',
|
||||
},
|
||||
{
|
||||
component: 'Divider',
|
||||
componentProps: {
|
||||
orientation: 'center',
|
||||
},
|
||||
fieldName: 'divider2',
|
||||
hideLabel: true,
|
||||
renderComponentContent: () => ({
|
||||
default: () => '平台联系人',
|
||||
}),
|
||||
},
|
||||
{
|
||||
label: '联系人',
|
||||
fieldName: 'contactUser',
|
||||
component: 'Input',
|
||||
rules: 'required',
|
||||
},
|
||||
{
|
||||
label: '手机号',
|
||||
fieldName: 'contactPhone',
|
||||
component: 'Input',
|
||||
rules: z.string().regex(/^1[3-9]\d{9}$/, '请输入正确的手机号码'),
|
||||
},
|
||||
|
||||
{
|
||||
label: '备注',
|
||||
fieldName: 'remark',
|
||||
component: 'Textarea',
|
||||
},
|
||||
];
|
|
@ -0,0 +1,218 @@
|
|||
<script setup lang="ts">
|
||||
import type { VbenFormProps } from '@vben/common-ui';
|
||||
|
||||
import type { VxeGridProps } from '#/adapter/vxe-table';
|
||||
import type { PlatformForm } from '#/api/application/platform/model';
|
||||
|
||||
import { Page, useVbenDrawer } from '@vben/common-ui';
|
||||
import { getVxePopupContainer } from '@vben/utils';
|
||||
|
||||
import { message, Modal, Popconfirm, Space } from 'ant-design-vue';
|
||||
|
||||
import { useVbenVxeGrid, vxeCheckboxChecked } from '#/adapter/vxe-table';
|
||||
import {
|
||||
platformExport,
|
||||
platformList,
|
||||
platformRemove,
|
||||
} from '#/api/application/platform';
|
||||
import { commonDownloadExcel } from '#/utils/file/download';
|
||||
|
||||
import { columns, querySchema } from './data';
|
||||
import platformDrawer from './platform-drawer.vue';
|
||||
|
||||
const formOptions: VbenFormProps = {
|
||||
collapsed: true,
|
||||
commonConfig: {
|
||||
labelWidth: 80,
|
||||
componentProps: {
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
schema: querySchema(),
|
||||
wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4',
|
||||
// 处理区间选择器RangePicker时间格式 将一个字段映射为两个字段 搜索/导出会用到
|
||||
// 不需要直接删除
|
||||
// fieldMappingTime: [
|
||||
// [
|
||||
// 'createTime',
|
||||
// ['params[beginTime]', 'params[endTime]'],
|
||||
// ['YYYY-MM-DD 00:00:00', 'YYYY-MM-DD 23:59:59'],
|
||||
// ],
|
||||
// ],
|
||||
};
|
||||
|
||||
const gridOptions: VxeGridProps = {
|
||||
checkboxConfig: {
|
||||
// 高亮
|
||||
highlight: true,
|
||||
// 翻页时保留选中状态
|
||||
reserve: true,
|
||||
// 点击行选中
|
||||
// trigger: 'row',
|
||||
},
|
||||
// 需要使用i18n注意这里要改成getter形式 否则切换语言不会刷新
|
||||
// columns: columns(),
|
||||
columns,
|
||||
height: 'auto',
|
||||
keepSource: true,
|
||||
pagerConfig: {},
|
||||
proxyConfig: {
|
||||
ajax: {
|
||||
query: async ({ page }, formValues = {}) => {
|
||||
return await platformList({
|
||||
pageNum: page.currentPage,
|
||||
pageSize: page.pageSize,
|
||||
...formValues,
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
rowConfig: {
|
||||
keyField: 'id',
|
||||
},
|
||||
// 表格全局唯一表示 保存列配置需要用到
|
||||
id: 'application-platform-index',
|
||||
};
|
||||
|
||||
const [BasicTable, tableApi] = useVbenVxeGrid({
|
||||
formOptions,
|
||||
gridOptions,
|
||||
});
|
||||
|
||||
const [PlatformDrawer, drawerApi] = useVbenDrawer({
|
||||
connectedComponent: platformDrawer,
|
||||
});
|
||||
|
||||
function handleAdd() {
|
||||
drawerApi.setData({});
|
||||
drawerApi.open();
|
||||
}
|
||||
|
||||
async function handleCopy(row: Required<PlatformForm>) {
|
||||
let text = `平台名:${row.applicationName}
|
||||
平台地址:http://192.168.1.16:5666/
|
||||
平台账号:${row.applicationAdmin}
|
||||
平台密码:${row.applicationPassword}`;
|
||||
// 添加 MQTT 密钥
|
||||
if (row.appSecrets && row.appSecrets.length > 0) {
|
||||
row.appSecrets.forEach((item) => {
|
||||
text += `\n${item.remark}:`;
|
||||
text += ` ${item.secret}`;
|
||||
});
|
||||
}
|
||||
try {
|
||||
if (navigator.clipboard) {
|
||||
await navigator.clipboard.writeText(text);
|
||||
} else {
|
||||
const textarea = document.createElement('textarea');
|
||||
textarea.value = text;
|
||||
document.body.append(textarea);
|
||||
textarea.select();
|
||||
document.execCommand('copy');
|
||||
textarea.remove();
|
||||
}
|
||||
message.success('复制成功');
|
||||
} catch {
|
||||
message.error('复制失败');
|
||||
}
|
||||
}
|
||||
|
||||
async function handleEdit(row: Required<PlatformForm>) {
|
||||
drawerApi.setData({ id: row.id });
|
||||
drawerApi.open();
|
||||
}
|
||||
|
||||
async function handleDelete(row: Required<PlatformForm>) {
|
||||
await platformRemove(row.id);
|
||||
await tableApi.query();
|
||||
}
|
||||
|
||||
function handleMultiDelete() {
|
||||
const rows = tableApi.grid.getCheckboxRecords();
|
||||
const ids = rows.map((row: Required<PlatformForm>) => row.id);
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
okType: 'danger',
|
||||
content: `确认删除选中的${ids.length}条记录吗?`,
|
||||
onOk: async () => {
|
||||
await platformRemove(ids);
|
||||
await tableApi.query();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function handleDownloadExcel() {
|
||||
commonDownloadExcel(
|
||||
platformExport,
|
||||
'平台管理数据',
|
||||
tableApi.formApi.form.values,
|
||||
{
|
||||
fieldMappingTime: formOptions.fieldMappingTime,
|
||||
},
|
||||
);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Page :auto-content-height="true">
|
||||
<BasicTable table-title="平台管理列表">
|
||||
<template #toolbar-tools>
|
||||
<Space>
|
||||
<a-button
|
||||
v-access:code="['application:platform:export']"
|
||||
@click="handleDownloadExcel"
|
||||
>
|
||||
{{ $t('pages.common.export') }}
|
||||
</a-button>
|
||||
<a-button
|
||||
:disabled="!vxeCheckboxChecked(tableApi)"
|
||||
danger
|
||||
type="primary"
|
||||
v-access:code="['application:platform:remove']"
|
||||
@click="handleMultiDelete"
|
||||
>
|
||||
{{ $t('pages.common.delete') }}
|
||||
</a-button>
|
||||
<a-button
|
||||
type="primary"
|
||||
v-access:code="['application:platform:add']"
|
||||
@click="handleAdd"
|
||||
>
|
||||
{{ $t('pages.common.add') }}
|
||||
</a-button>
|
||||
</Space>
|
||||
</template>
|
||||
<template #action="{ row }">
|
||||
<Space>
|
||||
<ghost-button
|
||||
v-access:code="['application:platform:edit']"
|
||||
@click.stop="handleCopy(row)"
|
||||
>
|
||||
复制配置
|
||||
</ghost-button>
|
||||
<ghost-button
|
||||
v-access:code="['application:platform:edit']"
|
||||
@click.stop="handleEdit(row)"
|
||||
>
|
||||
{{ $t('pages.common.edit') }}
|
||||
</ghost-button>
|
||||
<Popconfirm
|
||||
:get-popup-container="getVxePopupContainer"
|
||||
placement="left"
|
||||
title="确认删除?"
|
||||
@confirm="handleDelete(row)"
|
||||
>
|
||||
<ghost-button
|
||||
danger
|
||||
v-access:code="['application:platform:remove']"
|
||||
@click.stop=""
|
||||
>
|
||||
{{ $t('pages.common.delete') }}
|
||||
</ghost-button>
|
||||
</Popconfirm>
|
||||
</Space>
|
||||
</template>
|
||||
</BasicTable>
|
||||
<PlatformDrawer @reload="tableApi.query()" />
|
||||
</Page>
|
||||
</template>
|
|
@ -0,0 +1,221 @@
|
|||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import { useVbenDrawer } from '@vben/common-ui';
|
||||
import { $t } from '@vben/locales';
|
||||
import { cloneDeep } from '@vben/utils';
|
||||
|
||||
import { Button, message, Popconfirm } from 'ant-design-vue';
|
||||
|
||||
import { useVbenForm } from '#/adapter/form';
|
||||
import {
|
||||
platformAdd,
|
||||
platformInfo,
|
||||
platformUpdate,
|
||||
} from '#/api/application/platform';
|
||||
import { defaultFormValueGetter, useBeforeCloseDiff } from '#/utils/popup';
|
||||
|
||||
import { drawerSchema } from './data';
|
||||
|
||||
const emit = defineEmits<{ reload: [] }>();
|
||||
|
||||
const platformDetailInfo = ref({
|
||||
appSecrets: [],
|
||||
id: undefined,
|
||||
applicationCode: '',
|
||||
secret: '',
|
||||
});
|
||||
|
||||
const isUpdate = ref(false);
|
||||
const title = computed(() => {
|
||||
return isUpdate.value ? $t('pages.common.edit') : $t('pages.common.add');
|
||||
});
|
||||
|
||||
const [BasicForm, formApi] = useVbenForm({
|
||||
commonConfig: {
|
||||
// 默认占满两列
|
||||
formItemClass: 'col-span-2',
|
||||
// 默认label宽度 px
|
||||
labelWidth: 80,
|
||||
// 通用配置项 会影响到所有表单项
|
||||
componentProps: {
|
||||
class: 'w-full',
|
||||
},
|
||||
},
|
||||
schema: drawerSchema(),
|
||||
showDefaultActions: false,
|
||||
wrapperClass: 'grid-cols-2',
|
||||
});
|
||||
|
||||
// 平台代码联动:当账号/密码为空时自动填充
|
||||
formApi.updateSchema([
|
||||
{
|
||||
fieldName: 'applicationCode',
|
||||
componentProps: {
|
||||
onChange: async () => {
|
||||
const values = await formApi.getValues();
|
||||
const applicationCode = values.applicationCode;
|
||||
formApi.setFieldValue('applicationAdmin', `${applicationCode}Admin`);
|
||||
formApi.setFieldValue(
|
||||
'applicationPassword',
|
||||
`${applicationCode}123456`,
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
const { onBeforeClose, markInitialized, resetInitialized } = useBeforeCloseDiff(
|
||||
{
|
||||
initializedGetter: defaultFormValueGetter(formApi),
|
||||
currentGetter: defaultFormValueGetter(formApi),
|
||||
},
|
||||
);
|
||||
|
||||
// 生成随机密钥
|
||||
function generateRandomKey() {
|
||||
const chars =
|
||||
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
let result = '';
|
||||
for (let i = 0; i < 32; i++) {
|
||||
result += chars.charAt(Math.floor(Math.random() * chars.length));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
const upDateKey = (key: string) => {
|
||||
const newKey = generateRandomKey();
|
||||
formApi.setFieldValue(key, newKey);
|
||||
};
|
||||
|
||||
// 复制
|
||||
const handleCopy = async (text: string) => {
|
||||
try {
|
||||
if (navigator.clipboard) {
|
||||
await navigator.clipboard.writeText(text);
|
||||
} else {
|
||||
const textarea = document.createElement('textarea');
|
||||
textarea.value = text;
|
||||
document.body.append(textarea);
|
||||
textarea.select();
|
||||
document.execCommand('copy');
|
||||
textarea.remove();
|
||||
}
|
||||
message.success('复制成功');
|
||||
} catch {
|
||||
message.error('复制失败');
|
||||
}
|
||||
};
|
||||
|
||||
const [BasicDrawer, drawerApi] = useVbenDrawer({
|
||||
// 在这里更改宽度
|
||||
class: 'w-[550px]',
|
||||
fullscreenButton: false,
|
||||
onBeforeClose,
|
||||
onClosed: handleClosed,
|
||||
onConfirm: handleConfirm,
|
||||
onOpenChange: async (isOpen) => {
|
||||
if (!isOpen) {
|
||||
return null;
|
||||
}
|
||||
drawerApi.drawerLoading(true);
|
||||
|
||||
const { id } = drawerApi.getData() as { id?: number | string };
|
||||
isUpdate.value = !!id;
|
||||
|
||||
const record = await platformInfo(id);
|
||||
|
||||
if (isUpdate.value && id) {
|
||||
formApi.setState((prev) => {
|
||||
const currentSchema = prev?.schema ?? [];
|
||||
const newSchema = [];
|
||||
if (currentSchema.length < 11) {
|
||||
newSchema.push({
|
||||
component: 'Divider',
|
||||
componentProps: {
|
||||
orientation: 'center',
|
||||
},
|
||||
fieldName: 'divider3',
|
||||
hideLabel: true,
|
||||
renderComponentContent: () => ({
|
||||
default: () => '平台密钥',
|
||||
}),
|
||||
});
|
||||
}
|
||||
return {
|
||||
schema: [...currentSchema, ...newSchema],
|
||||
};
|
||||
});
|
||||
|
||||
platformDetailInfo.value = record;
|
||||
|
||||
await formApi.setValues(record);
|
||||
}
|
||||
await markInitialized();
|
||||
|
||||
drawerApi.drawerLoading(false);
|
||||
},
|
||||
});
|
||||
|
||||
async function handleConfirm() {
|
||||
try {
|
||||
drawerApi.lock(true);
|
||||
const { valid } = await formApi.validate();
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
// getValues获取为一个readonly的对象 需要修改必须先深拷贝一次
|
||||
const data = cloneDeep(await formApi.getValues());
|
||||
await (isUpdate.value ? platformUpdate(data) : platformAdd(data));
|
||||
resetInitialized();
|
||||
emit('reload');
|
||||
drawerApi.close();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
drawerApi.lock(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleClosed() {
|
||||
await formApi.resetForm();
|
||||
resetInitialized();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<BasicDrawer :title="title" :destroy-on-close="true">
|
||||
<BasicForm>
|
||||
<div class="appSecret-box">
|
||||
<div
|
||||
class="appSecret-item mb-2 flex items-center gap-2"
|
||||
v-for="item in platformDetailInfo.appSecrets"
|
||||
:key="item.id"
|
||||
>
|
||||
<div
|
||||
class="mr-2 flex flex-shrink-0 items-center justify-end text-sm font-medium leading-6 peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||
>
|
||||
<span class="w-[80px]">{{ item.remark }}: </span>
|
||||
<span>{{ item.secret }}</span>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<Button
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="handleCopy(item.secret)"
|
||||
>
|
||||
复制
|
||||
</Button>
|
||||
<Popconfirm
|
||||
placement="right"
|
||||
title="确认重置?"
|
||||
@confirm="upDateKey(item.remark)"
|
||||
>
|
||||
<Button type="primary" size="small" @click.stop=""> 重置 </Button>
|
||||
</Popconfirm>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</BasicForm>
|
||||
</BasicDrawer>
|
||||
</template>
|
|
@ -1,15 +1,27 @@
|
|||
<script setup lang="ts">
|
||||
import type { ProductVO } from '#/api/device/product/model';
|
||||
|
||||
import { defineAsyncComponent, ref } from 'vue';
|
||||
|
||||
import { Modal } from 'ant-design-vue';
|
||||
|
||||
import 'shiyzhangcron/dist/style.css';
|
||||
|
||||
const CronPickerModal = defineAsyncComponent(
|
||||
() => import('../../../components/CronPickerModal/index.vue'),
|
||||
);
|
||||
|
||||
const easyCronInnerValue = ref('* * * * * ? *');
|
||||
const ProductSelectTable = defineAsyncComponent(
|
||||
() => import('../../../components/product-select/product-select-table.vue'),
|
||||
);
|
||||
|
||||
const easyCronInnerValue = ref('* * * * * ? *');
|
||||
const cronModalVisible = ref(false);
|
||||
const productSelectModalVisible = ref(false);
|
||||
|
||||
// 产品选择相关
|
||||
const selectedProducts = ref<ProductVO[]>([]);
|
||||
const isMultiple = ref(false);
|
||||
|
||||
const openCronModal = () => {
|
||||
cronModalVisible.value = true;
|
||||
|
@ -23,28 +35,135 @@ const onCronConfirm = (val: string) => {
|
|||
const onCronCancel = () => {
|
||||
cronModalVisible.value = false;
|
||||
};
|
||||
|
||||
const onProductChange = (products: ProductVO[]) => {
|
||||
console.log('选中的产品:', products);
|
||||
};
|
||||
|
||||
const clearSelection = () => {
|
||||
selectedProducts.value = [];
|
||||
};
|
||||
|
||||
const toggleMultiple = () => {
|
||||
isMultiple.value = !isMultiple.value;
|
||||
// 切换模式时清空选择
|
||||
selectedProducts.value = [];
|
||||
};
|
||||
|
||||
const openProductSelectModal = () => {
|
||||
productSelectModalVisible.value = true;
|
||||
};
|
||||
|
||||
const okProductSelectModal = () => {
|
||||
if (selectedProducts.value.length === 0) {
|
||||
Modal.error({
|
||||
title: '请选择产品',
|
||||
});
|
||||
} else {
|
||||
console.log('已选择的产品:', selectedProducts.value);
|
||||
productSelectModalVisible.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const closeProductSelectModal = () => {
|
||||
productSelectModalVisible.value = false;
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="p-4">
|
||||
<h1 class="mb-4 text-2xl font-bold">Test Detail Page</h1>
|
||||
|
||||
<div class="flex items-center gap-2" style="max-width: 640px">
|
||||
<input
|
||||
:value="easyCronInnerValue"
|
||||
type="text"
|
||||
placeholder="请输入 CRON 表达式"
|
||||
class="flex-1 rounded border border-gray-300 px-3 py-1"
|
||||
/>
|
||||
<button
|
||||
class="rounded bg-blue-600 px-3 py-1 text-white"
|
||||
@click="openCronModal"
|
||||
>
|
||||
配置
|
||||
</button>
|
||||
<!-- CRON 配置区域 -->
|
||||
<div class="mb-8">
|
||||
<h2 class="mb-4 text-lg font-semibold">CRON 配置</h2>
|
||||
<div class="flex items-center gap-2" style="max-width: 640px">
|
||||
<input
|
||||
:value="easyCronInnerValue"
|
||||
type="text"
|
||||
placeholder="请输入 CRON 表达式"
|
||||
class="flex-1 rounded border border-gray-300 px-3 py-1"
|
||||
/>
|
||||
<button
|
||||
class="rounded bg-blue-600 px-3 py-1 text-white"
|
||||
@click="openCronModal"
|
||||
>
|
||||
配置
|
||||
</button>
|
||||
</div>
|
||||
<p class="mt-4">当前cron值为: {{ easyCronInnerValue }}</p>
|
||||
</div>
|
||||
|
||||
<p class="mt-4">当前cron值为: {{ easyCronInnerValue }}</p>
|
||||
<!-- 产品选择区域 -->
|
||||
<div class="mb-8">
|
||||
<div class="mb-4 flex items-center justify-between">
|
||||
<h2 class="text-lg font-semibold">产品选择</h2>
|
||||
<div class="flex items-center gap-2">
|
||||
<button
|
||||
class="rounded bg-green-600 px-3 py-1 text-white"
|
||||
@click="toggleMultiple"
|
||||
>
|
||||
{{ isMultiple ? '切换到单选' : '切换到多选' }}
|
||||
</button>
|
||||
<button
|
||||
class="rounded bg-red-600 px-3 py-1 text-white"
|
||||
@click="clearSelection"
|
||||
>
|
||||
清空选择
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-4 rounded border border-gray-200 p-4">
|
||||
<h3 class="mb-2 font-medium">
|
||||
已选择的产品 ({{ selectedProducts.length }} 个):
|
||||
</h3>
|
||||
<div v-if="selectedProducts.length === 0" class="text-gray-500">
|
||||
暂无选择的产品
|
||||
</div>
|
||||
<div v-else class="space-y-2">
|
||||
<div
|
||||
v-for="product in selectedProducts"
|
||||
:key="product.id"
|
||||
class="flex items-center justify-between rounded bg-gray-50 p-2"
|
||||
>
|
||||
<div>
|
||||
<span class="font-medium">{{ product.productName }}</span>
|
||||
<span class="ml-2 text-sm text-gray-500">
|
||||
({{ product.productKey }})
|
||||
</span>
|
||||
</div>
|
||||
<div class="text-sm text-gray-500">
|
||||
{{ product.categoryName }} - {{ product.deviceType }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
class="mt-2 w-full rounded bg-blue-600 px-3 py-1 text-white"
|
||||
@click="openProductSelectModal"
|
||||
>
|
||||
选择产品
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 产品选择表格 -->
|
||||
<Modal
|
||||
title="产品选择"
|
||||
:open="productSelectModalVisible"
|
||||
:width="1100"
|
||||
destroy-on-close
|
||||
@ok="okProductSelectModal"
|
||||
@cancel="closeProductSelectModal"
|
||||
>
|
||||
<div>
|
||||
<ProductSelectTable
|
||||
v-model="selectedProducts"
|
||||
:multiple="isMultiple"
|
||||
@change="onProductChange"
|
||||
/>
|
||||
</div>
|
||||
</Modal>
|
||||
</div>
|
||||
|
||||
<CronPickerModal
|
||||
v-if="cronModalVisible"
|
||||
|
|
|
@ -116,6 +116,16 @@ const handleProductClick = () => {
|
|||
router.push(`/device/product/detail/${currentDevice.value.productId}`);
|
||||
};
|
||||
|
||||
const handleApplicationClick = () => {
|
||||
if (!hasAccessByCodes(['application:platform:query'])) {
|
||||
message.warning('暂无权限');
|
||||
return;
|
||||
}
|
||||
router.push(
|
||||
`/application/appDevice?applicationCode=${currentDevice.value.applicationCode}`,
|
||||
);
|
||||
};
|
||||
|
||||
const handleDeviceClick = () => {
|
||||
router.push(`/device/device/detail/${currentDevice.value.parentId}`);
|
||||
};
|
||||
|
@ -218,6 +228,12 @@ onUnmounted(() => {
|
|||
currentDevice?.parentName || '未知设备'
|
||||
}}</a>
|
||||
</div>
|
||||
<div class="basic-item" v-if="currentDevice.applicationCode">
|
||||
<span class="basic-label">所属平台:</span>
|
||||
<a class="basic-value product-link" @click="handleApplicationClick">{{
|
||||
currentDevice?.applicationName
|
||||
}}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 标签页内容 -->
|
||||
|
|
|
@ -33,6 +33,7 @@ import {
|
|||
Tag,
|
||||
} from 'ant-design-vue';
|
||||
|
||||
// import { platformList } from '#/api/application/platform';
|
||||
import { deviceExport, deviceList, deviceRemove } from '#/api/device/device';
|
||||
import { productList } from '#/api/device/product';
|
||||
import { deviceStateOptions, deviceTypeOptions } from '#/constants/dicts';
|
||||
|
@ -50,11 +51,13 @@ const formModel = reactive({
|
|||
deviceKey: undefined as string | undefined,
|
||||
deviceName: undefined as string | undefined,
|
||||
productId: undefined as string | undefined,
|
||||
applicationCode: undefined as string | undefined,
|
||||
deviceState: undefined as string | undefined,
|
||||
deviceType: undefined as string | undefined,
|
||||
});
|
||||
|
||||
const productOptions = ref<{ label: string; value: string }[]>([]);
|
||||
// const applicationOptions = ref<{ label: string; value: string }[]>([]);
|
||||
|
||||
// 表格/卡片数据与分页
|
||||
const loading = ref(false);
|
||||
|
@ -83,6 +86,12 @@ const columns = [
|
|||
align: 'center',
|
||||
},
|
||||
{ title: '设备类型', dataIndex: 'deviceType', width: 100, align: 'center' },
|
||||
{
|
||||
title: '所属平台',
|
||||
dataIndex: 'applicationName',
|
||||
width: 100,
|
||||
align: 'center',
|
||||
},
|
||||
{ title: '设备状态', dataIndex: 'deviceState', width: 100, align: 'center' },
|
||||
{ title: '描述', dataIndex: 'description', align: 'center' },
|
||||
{ title: '操作', key: 'action', width: 200, fixed: 'right', align: 'center' },
|
||||
|
@ -107,6 +116,8 @@ const fetchData = async () => {
|
|||
const { rows = [], total = 0 } = (await deviceList({
|
||||
pageNum: pagination.current,
|
||||
pageSize: pagination.pageSize,
|
||||
orderByColumn: 'createTime',
|
||||
isAsc: 'desc',
|
||||
...formModel,
|
||||
})) as any;
|
||||
dataSource.value = rows as any;
|
||||
|
@ -125,6 +136,7 @@ const onReset = () => {
|
|||
formModel.deviceKey = undefined;
|
||||
formModel.deviceName = undefined;
|
||||
formModel.productId = undefined;
|
||||
formModel.applicationCode = undefined;
|
||||
formModel.deviceType = undefined;
|
||||
formModel.deviceState = undefined;
|
||||
pagination.current = 1;
|
||||
|
@ -182,6 +194,14 @@ const updateTableHeight = () => {
|
|||
});
|
||||
};
|
||||
|
||||
// const loadapplication = async () => {
|
||||
// const res = await platformList({ pageNum: 1, pageSize: 1000 } as any);
|
||||
// applicationOptions.value = (res?.rows || []).map((item: any) => ({
|
||||
// label: item.applicationName,
|
||||
// value: item.id,
|
||||
// }));
|
||||
// };
|
||||
|
||||
const loadProducts = async () => {
|
||||
const res = await productList({ pageNum: 1, pageSize: 1000 } as any);
|
||||
productOptions.value = (res?.rows || []).map((item: any) => ({
|
||||
|
@ -246,6 +266,21 @@ const [DeviceDrawer, drawerApi] = useVbenDrawer({
|
|||
</SelectOption>
|
||||
</Select>
|
||||
</FormItem>
|
||||
<!-- <FormItem label="所属平台">
|
||||
<Select
|
||||
v-model:value="formModel.applicationCode"
|
||||
allow-clear
|
||||
style="min-width: 200px"
|
||||
>
|
||||
<SelectOption
|
||||
v-for="opt in applicationOptions"
|
||||
:key="opt.value"
|
||||
:value="opt.value"
|
||||
>
|
||||
{{ opt.label }}
|
||||
</SelectOption>
|
||||
</Select>
|
||||
</FormItem> -->
|
||||
<FormItem label="设备类型">
|
||||
<Select
|
||||
v-model:value="formModel.deviceType"
|
||||
|
|
|
@ -199,7 +199,9 @@ onUnmounted(() => {
|
|||
</div>
|
||||
<div class="basic-item">
|
||||
<span>设备数量:</span>
|
||||
<a @click="jumpToDevices">{{ currentProduct.deviceCount }}</a>
|
||||
<a class="basic-value product-link" @click="jumpToDevices">{{
|
||||
currentProduct.deviceCount
|
||||
}}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -275,6 +277,22 @@ onUnmounted(() => {
|
|||
display: flex;
|
||||
gap: 24px;
|
||||
align-items: center;
|
||||
|
||||
.basic-item {
|
||||
.basic-value {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
|
||||
&.product-link {
|
||||
color: hsl(var(--primary));
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.detail-tabs {
|
||||
|
|
|
@ -106,6 +106,8 @@ const fetchData = async () => {
|
|||
const { rows = [], total = 0 } = (await productList({
|
||||
pageNum: pagination.current,
|
||||
pageSize: pagination.pageSize,
|
||||
orderByColumn: 'createTime',
|
||||
isAsc: 'desc',
|
||||
...formModel,
|
||||
})) as any;
|
||||
dataSource.value = rows as any;
|
||||
|
|
|
@ -60,6 +60,8 @@ const gridOptions: VxeGridProps = {
|
|||
return await gatewayList({
|
||||
pageNum: page.currentPage,
|
||||
pageSize: page.pageSize,
|
||||
orderByColumn: 'createTime',
|
||||
isAsc: 'desc',
|
||||
...formValues,
|
||||
});
|
||||
},
|
||||
|
|
|
@ -64,6 +64,8 @@ const gridOptions: VxeGridProps = {
|
|||
return await networkList({
|
||||
pageNum: page.currentPage,
|
||||
pageSize: page.pageSize,
|
||||
orderByColumn: 'createTime',
|
||||
isAsc: 'desc',
|
||||
...formValues,
|
||||
});
|
||||
},
|
||||
|
|
|
@ -63,6 +63,8 @@ const gridOptions: VxeGridProps = {
|
|||
return await protocolList({
|
||||
pageNum: page.currentPage,
|
||||
pageSize: page.pageSize,
|
||||
orderByColumn: 'createTime',
|
||||
isAsc: 'desc',
|
||||
...formValues,
|
||||
});
|
||||
},
|
||||
|
|
|
@ -29,8 +29,8 @@ export default defineConfig(async () => {
|
|||
changeOrigin: true,
|
||||
rewrite: (path) => path.replace(/^\/api/, ''),
|
||||
// mock代理目标地址
|
||||
// target: 'http://192.168.1.17:6666',
|
||||
target: 'http://192.168.1.100:6666',
|
||||
target: 'http://192.168.1.17:6666',
|
||||
// target: 'http://192.168.1.100:6666',
|
||||
ws: true,
|
||||
},
|
||||
},
|
||||
|
|
Loading…
Reference in New Issue