Merge remote-tracking branch 'yudao/dev' into dev
This commit is contained in:
@@ -3,7 +3,8 @@ import type { PageParam, PageResult } from '@vben/request';
|
|||||||
import { requestClient } from '#/api/request';
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
export namespace IotDeviceApi {
|
export namespace IotDeviceApi {
|
||||||
/** IoT 设备 VO */
|
// TODO @haohao:需要跟后端对齐,必要的 ReqVO、RespVO
|
||||||
|
/** 设备 */
|
||||||
export interface Device {
|
export interface Device {
|
||||||
id?: number; // 设备 ID,主键,自增
|
id?: number; // 设备 ID,主键,自增
|
||||||
deviceName: string; // 设备名称
|
deviceName: string; // 设备名称
|
||||||
@@ -48,7 +49,7 @@ export namespace IotDeviceApi {
|
|||||||
dataSpecsList: any[]; // 数据定义列表
|
dataSpecsList: any[]; // 数据定义列表
|
||||||
}
|
}
|
||||||
|
|
||||||
/** IoT 设备属性 VO */
|
/** 设备属性 VO */
|
||||||
export interface DeviceProperty {
|
export interface DeviceProperty {
|
||||||
identifier: string; // 属性标识符
|
identifier: string; // 属性标识符
|
||||||
value: string; // 最新值
|
value: string; // 最新值
|
||||||
@@ -62,7 +63,7 @@ export namespace IotDeviceApi {
|
|||||||
password: string; // 密码
|
password: string; // 密码
|
||||||
}
|
}
|
||||||
|
|
||||||
/** IoT 设备发送消息 Request VO */
|
/** 设备发送消息 Request VO */
|
||||||
export interface DeviceMessageSendReq {
|
export interface DeviceMessageSendReq {
|
||||||
deviceId: number; // 设备编号
|
deviceId: number; // 设备编号
|
||||||
method: string; // 请求方法
|
method: string; // 请求方法
|
||||||
@@ -77,6 +78,7 @@ export namespace IotDeviceApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** IoT 设备状态枚举 */
|
/** IoT 设备状态枚举 */
|
||||||
|
// TODO @haohao:packages/constants/src/biz-iot-enum.ts 枚举;
|
||||||
export enum DeviceStateEnum {
|
export enum DeviceStateEnum {
|
||||||
INACTIVE = 0, // 未激活
|
INACTIVE = 0, // 未激活
|
||||||
OFFLINE = 2, // 离线
|
OFFLINE = 2, // 离线
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import type { PageParam, PageResult } from '@vben/request';
|
|||||||
import { requestClient } from '#/api/request';
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
export namespace IotDeviceGroupApi {
|
export namespace IotDeviceGroupApi {
|
||||||
/** IoT 设备分组 VO */
|
/** 设备分组 */
|
||||||
export interface DeviceGroup {
|
export interface DeviceGroup {
|
||||||
id?: number; // 分组 ID
|
id?: number; // 分组 ID
|
||||||
name: string; // 分组名字
|
name: string; // 分组名字
|
||||||
|
|||||||
@@ -3,19 +3,19 @@ import type { PageParam, PageResult } from '@vben/request';
|
|||||||
import { requestClient } from '#/api/request';
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
export namespace IotProductCategoryApi {
|
export namespace IotProductCategoryApi {
|
||||||
/** IoT 產品分類 VO */
|
/** 产品分类 */
|
||||||
export interface ProductCategory {
|
export interface ProductCategory {
|
||||||
id?: number; // 分類 ID
|
id?: number; // 分类 ID
|
||||||
name: string; // 分類名稱
|
name: string; // 分类名称
|
||||||
parentId?: number; // 父级分類 ID
|
parentId?: number; // 父级分类 ID
|
||||||
sort?: number; // 分類排序
|
sort?: number; // 分类排序
|
||||||
status?: number; // 分類狀態
|
status?: number; // 分类状态
|
||||||
description?: string; // 分類描述
|
description?: string; // 分类描述
|
||||||
createTime?: string; // 創建時間
|
createTime?: string; // 创建时间
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 查詢產品分類分頁 */
|
/** 查询产品分类分页 */
|
||||||
export function getProductCategoryPage(params: PageParam) {
|
export function getProductCategoryPage(params: PageParam) {
|
||||||
return requestClient.get<PageResult<IotProductCategoryApi.ProductCategory>>(
|
return requestClient.get<PageResult<IotProductCategoryApi.ProductCategory>>(
|
||||||
'/iot/product-category/page',
|
'/iot/product-category/page',
|
||||||
@@ -23,33 +23,33 @@ export function getProductCategoryPage(params: PageParam) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 查詢產品分類詳情 */
|
/** 查询产品分类详情 */
|
||||||
export function getProductCategory(id: number) {
|
export function getProductCategory(id: number) {
|
||||||
return requestClient.get<IotProductCategoryApi.ProductCategory>(
|
return requestClient.get<IotProductCategoryApi.ProductCategory>(
|
||||||
`/iot/product-category/get?id=${id}`,
|
`/iot/product-category/get?id=${id}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 新增產品分類 */
|
/** 新增产品分类 */
|
||||||
export function createProductCategory(
|
export function createProductCategory(
|
||||||
data: IotProductCategoryApi.ProductCategory,
|
data: IotProductCategoryApi.ProductCategory,
|
||||||
) {
|
) {
|
||||||
return requestClient.post('/iot/product-category/create', data);
|
return requestClient.post('/iot/product-category/create', data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 修改產品分類 */
|
/** 修改产品分类 */
|
||||||
export function updateProductCategory(
|
export function updateProductCategory(
|
||||||
data: IotProductCategoryApi.ProductCategory,
|
data: IotProductCategoryApi.ProductCategory,
|
||||||
) {
|
) {
|
||||||
return requestClient.put('/iot/product-category/update', data);
|
return requestClient.put('/iot/product-category/update', data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 刪除產品分類 */
|
/** 刪除产品分类 */
|
||||||
export function deleteProductCategory(id: number) {
|
export function deleteProductCategory(id: number) {
|
||||||
return requestClient.delete(`/iot/product-category/delete?id=${id}`);
|
return requestClient.delete(`/iot/product-category/delete?id=${id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 獲取產品分類精簡列表 */
|
/** 获取产品分类精简列表 */
|
||||||
export function getSimpleProductCategoryList() {
|
export function getSimpleProductCategoryList() {
|
||||||
return requestClient.get<IotProductCategoryApi.ProductCategory[]>(
|
return requestClient.get<IotProductCategoryApi.ProductCategory[]>(
|
||||||
'/iot/product-category/simple-list',
|
'/iot/product-category/simple-list',
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import type { PageParam, PageResult } from '@vben/request';
|
|||||||
import { requestClient } from '#/api/request';
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
export namespace IotProductApi {
|
export namespace IotProductApi {
|
||||||
/** IoT 产品 VO */
|
/** 产品 */
|
||||||
export interface Product {
|
export interface Product {
|
||||||
id?: number; // 产品编号
|
id?: number; // 产品编号
|
||||||
name: string; // 产品名称
|
name: string; // 产品名称
|
||||||
@@ -27,6 +27,8 @@ export namespace IotProductApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO @haohao:packages/constants/src/biz-iot-enum.ts 枚举;
|
||||||
|
|
||||||
/** IOT 产品设备类型枚举类 */
|
/** IOT 产品设备类型枚举类 */
|
||||||
export enum DeviceTypeEnum {
|
export enum DeviceTypeEnum {
|
||||||
DEVICE = 0, // 直连设备
|
DEVICE = 0, // 直连设备
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import type { PageParam, PageResult } from '@vben/request';
|
|||||||
import { requestClient } from '#/api/request';
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
export namespace RuleSceneApi {
|
export namespace RuleSceneApi {
|
||||||
/** IoT 场景联动规则 VO */
|
/** 场景联动规则 */
|
||||||
export interface SceneRule {
|
export interface SceneRule {
|
||||||
id?: number;
|
id?: number;
|
||||||
name: string;
|
name: string;
|
||||||
@@ -14,7 +14,7 @@ export namespace RuleSceneApi {
|
|||||||
createTime?: Date;
|
createTime?: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** IoT 场景联动规则触发器 */
|
/** 场景联动规则的触发器 */
|
||||||
export interface Trigger {
|
export interface Trigger {
|
||||||
type?: string;
|
type?: string;
|
||||||
productId?: number;
|
productId?: number;
|
||||||
@@ -26,13 +26,13 @@ export namespace RuleSceneApi {
|
|||||||
conditionGroups?: TriggerConditionGroup[];
|
conditionGroups?: TriggerConditionGroup[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/** IoT 场景联动规则触发条件组 */
|
/** 场景联动规则的触发条件组 */
|
||||||
export interface TriggerConditionGroup {
|
export interface TriggerConditionGroup {
|
||||||
conditions?: TriggerCondition[];
|
conditions?: TriggerCondition[];
|
||||||
operator?: string;
|
operator?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** IoT 场景联动规则触发条件 */
|
/** 场景联动规则的触发条件 */
|
||||||
export interface TriggerCondition {
|
export interface TriggerCondition {
|
||||||
productId?: number;
|
productId?: number;
|
||||||
deviceId?: number;
|
deviceId?: number;
|
||||||
@@ -42,7 +42,7 @@ export namespace RuleSceneApi {
|
|||||||
type?: string;
|
type?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** IoT 场景联动规则动作 */
|
/** 场景联动规则的动作 */
|
||||||
export interface Action {
|
export interface Action {
|
||||||
type?: string;
|
type?: string;
|
||||||
productId?: number;
|
productId?: number;
|
||||||
@@ -53,6 +53,7 @@ export namespace RuleSceneApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO @haohao:貌似下面的,和 RuleSceneApi 重复了。
|
||||||
/** IoT 场景联动规则 */
|
/** IoT 场景联动规则 */
|
||||||
export interface IotSceneRule {
|
export interface IotSceneRule {
|
||||||
id?: number;
|
id?: number;
|
||||||
@@ -135,6 +136,7 @@ export function deleteSceneRule(id: number) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 批量删除场景联动规则 */
|
/** 批量删除场景联动规则 */
|
||||||
|
// TODO @haohao:貌似用上。
|
||||||
export function deleteSceneRuleList(ids: number[]) {
|
export function deleteSceneRuleList(ids: number[]) {
|
||||||
return requestClient.delete('/iot/scene-rule/delete-list', {
|
return requestClient.delete('/iot/scene-rule/delete-list', {
|
||||||
params: { ids: ids.join(',') },
|
params: { ids: ids.join(',') },
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { requestClient } from '#/api/request';
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
export namespace IotStatisticsApi {
|
export namespace IotStatisticsApi {
|
||||||
/** IoT 统计摘要数据 */
|
// TODO @haohao:需要跟后端对齐,必要的 ReqVO、RespVO
|
||||||
|
/** 统计摘要数据 */
|
||||||
export interface StatisticsSummary {
|
export interface StatisticsSummary {
|
||||||
productCategoryCount: number;
|
productCategoryCount: number;
|
||||||
productCount: number;
|
productCount: number;
|
||||||
@@ -22,7 +23,7 @@ export namespace IotStatisticsApi {
|
|||||||
[key: string]: number;
|
[key: string]: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** IoT 消息统计数据类型 */
|
/** 消息统计数据类型 */
|
||||||
export interface DeviceMessageSummary {
|
export interface DeviceMessageSummary {
|
||||||
statType: number;
|
statType: number;
|
||||||
upstreamCounts: TimeValueItem[];
|
upstreamCounts: TimeValueItem[];
|
||||||
@@ -60,6 +61,7 @@ export function getDeviceMessageSummaryByDate(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO @haohao:貌似这里,没用到?是不是后面哪里用,或者可以删除哈?
|
||||||
/** 获取设备消息统计摘要 */
|
/** 获取设备消息统计摘要 */
|
||||||
export function getDeviceMessageSummary(statType: number) {
|
export function getDeviceMessageSummary(statType: number) {
|
||||||
return requestClient.get<IotStatisticsApi.DeviceMessageSummary>(
|
return requestClient.get<IotStatisticsApi.DeviceMessageSummary>(
|
||||||
|
|||||||
@@ -140,19 +140,11 @@ export function getThingModel(id: number) {
|
|||||||
/** 根据产品 ID 查询物模型列表 */
|
/** 根据产品 ID 查询物模型列表 */
|
||||||
export function getThingModelListByProductId(productId: number) {
|
export function getThingModelListByProductId(productId: number) {
|
||||||
return requestClient.get<ThingModelApi.ThingModel[]>(
|
return requestClient.get<ThingModelApi.ThingModel[]>(
|
||||||
'/iot/thing-model/list-by-product-id',
|
'/iot/thing-model/list',
|
||||||
{ params: { productId } },
|
{ params: { productId } },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 根据产品标识查询物模型列表 */
|
|
||||||
export function getThingModelListByProductKey(productKey: string) {
|
|
||||||
return requestClient.get<ThingModelApi.ThingModel[]>(
|
|
||||||
'/iot/thing-model/list-by-product-key',
|
|
||||||
{ params: { productKey } },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 新增物模型 */
|
/** 新增物模型 */
|
||||||
export function createThingModel(data: ThingModelData) {
|
export function createThingModel(data: ThingModelData) {
|
||||||
return requestClient.post('/iot/thing-model/create', data);
|
return requestClient.post('/iot/thing-model/create', data);
|
||||||
@@ -168,13 +160,6 @@ export function deleteThingModel(id: number) {
|
|||||||
return requestClient.delete(`/iot/thing-model/delete?id=${id}`);
|
return requestClient.delete(`/iot/thing-model/delete?id=${id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 批量删除物模型 */
|
|
||||||
export function deleteThingModelList(ids: number[]) {
|
|
||||||
return requestClient.delete('/iot/thing-model/delete-list', {
|
|
||||||
params: { ids: ids.join(',') },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 获取物模型 TSL */
|
/** 获取物模型 TSL */
|
||||||
export function getThingModelTSL(productId: number) {
|
export function getThingModelTSL(productId: number) {
|
||||||
return requestClient.get<ThingModelApi.ThingModel[]>(
|
return requestClient.get<ThingModelApi.ThingModel[]>(
|
||||||
|
|||||||
@@ -24,6 +24,49 @@ export namespace MpDraftApi {
|
|||||||
articles: Article[];
|
articles: Article[];
|
||||||
createTime?: Date;
|
createTime?: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 图文项(包含预览字段) */
|
||||||
|
export interface NewsItem {
|
||||||
|
title: string;
|
||||||
|
thumbMediaId: string;
|
||||||
|
author: string;
|
||||||
|
digest: string;
|
||||||
|
showCoverPic: number;
|
||||||
|
content: string;
|
||||||
|
contentSourceUrl: string;
|
||||||
|
needOpenComment: number;
|
||||||
|
onlyFansCanComment: number;
|
||||||
|
thumbUrl: string;
|
||||||
|
picUrl?: string; // 用于预览封面
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 图文列表 */
|
||||||
|
export interface NewsItemList {
|
||||||
|
newsItem: NewsItem[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 草稿文章(用于展示) */
|
||||||
|
export interface DraftArticle {
|
||||||
|
mediaId: string;
|
||||||
|
content: NewsItemList;
|
||||||
|
updateTime: number;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 创建空的图文项 */
|
||||||
|
export function createEmptyNewsItem(): MpDraftApi.NewsItem {
|
||||||
|
return {
|
||||||
|
title: '',
|
||||||
|
thumbMediaId: '',
|
||||||
|
author: '',
|
||||||
|
digest: '',
|
||||||
|
showCoverPic: 0,
|
||||||
|
content: '',
|
||||||
|
contentSourceUrl: '',
|
||||||
|
needOpenComment: 0,
|
||||||
|
onlyFansCanComment: 0,
|
||||||
|
thumbUrl: '',
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 查询草稿列表 */
|
/** 查询草稿列表 */
|
||||||
|
|||||||
@@ -1,15 +1,8 @@
|
|||||||
import type { PageParam, PageResult } from '@vben/request';
|
import type { PageParam, PageResult } from '@vben/request';
|
||||||
|
|
||||||
import { requestClient } from '#/api/request';
|
import { MaterialType } from '@vben/constants';
|
||||||
|
|
||||||
/** 素材类型枚举 */
|
import { requestClient } from '#/api/request';
|
||||||
// TODO @hw:mp 相关的枚举,可以考虑放这里来。
|
|
||||||
export enum MaterialType {
|
|
||||||
IMAGE = 1, // 图片
|
|
||||||
THUMB = 4, // 缩略图
|
|
||||||
VIDEO = 3, // 视频
|
|
||||||
VOICE = 2, // 语音
|
|
||||||
}
|
|
||||||
|
|
||||||
export namespace MpMaterialApi {
|
export namespace MpMaterialApi {
|
||||||
/** 素材信息 */
|
/** 素材信息 */
|
||||||
|
|||||||
@@ -1,20 +1,6 @@
|
|||||||
import { requestClient } from '#/api/request';
|
import { MenuType } from '@vben/constants';
|
||||||
|
|
||||||
/** 菜单类型枚举 */
|
import { requestClient } from '#/api/request';
|
||||||
// TODO @hw:mp 相关的枚举,可以考虑放这里来。
|
|
||||||
export enum MenuType {
|
|
||||||
CLICK = 'click', // 点击推事件
|
|
||||||
LOCATION_SELECT = 'location_select', // 发送位置
|
|
||||||
MEDIA_ID = 'media_id', // 下发消息
|
|
||||||
MINIPROGRAM = 'miniprogram', // 小程序
|
|
||||||
PIC_PHOTO_OR_ALBUM = 'pic_photo_or_album', // 拍照或者相册发图
|
|
||||||
PIC_SYSPHOTO = 'pic_sysphoto', // 系统拍照发图
|
|
||||||
PIC_WEIXIN = 'pic_weixin', // 微信相册发图
|
|
||||||
SCANCODE_PUSH = 'scancode_push', // 扫码推事件
|
|
||||||
SCANCODE_WAITMSG = 'scancode_waitmsg', // 扫码带提示
|
|
||||||
VIEW = 'view', // 跳转 URL
|
|
||||||
VIEW_LIMITED = 'view_limited', // 跳转图文消息URL
|
|
||||||
}
|
|
||||||
|
|
||||||
export namespace MpMenuApi {
|
export namespace MpMenuApi {
|
||||||
/** 菜单按钮信息 */
|
/** 菜单按钮信息 */
|
||||||
|
|||||||
@@ -1,19 +1,8 @@
|
|||||||
import type { PageParam, PageResult } from '@vben/request';
|
import type { PageParam, PageResult } from '@vben/request';
|
||||||
|
|
||||||
import { requestClient } from '#/api/request';
|
import { MessageType } from '@vben/constants';
|
||||||
|
|
||||||
/** 消息类型枚举 */
|
import { requestClient } from '#/api/request';
|
||||||
// TODO @hw:mp 相关的枚举,可以考虑放这里来。
|
|
||||||
export enum MessageType {
|
|
||||||
IMAGE = 'image', // 图片消息
|
|
||||||
MPNEWS = 'mpnews', // 公众号图文消息
|
|
||||||
MUSIC = 'music', // 音乐消息
|
|
||||||
NEWS = 'news', // 图文消息
|
|
||||||
TEXT = 'text', // 文本消息
|
|
||||||
VIDEO = 'video', // 视频消息
|
|
||||||
VOICE = 'voice', // 语音消息
|
|
||||||
WXCARD = 'wxcard', // 卡券消息
|
|
||||||
}
|
|
||||||
|
|
||||||
export namespace MpMessageApi {
|
export namespace MpMessageApi {
|
||||||
/** 消息信息 */
|
/** 消息信息 */
|
||||||
|
|||||||
@@ -21,15 +21,21 @@ import {
|
|||||||
|
|
||||||
/** 编码表单 Conf */
|
/** 编码表单 Conf */
|
||||||
export function encodeConf(designerRef: any) {
|
export function encodeConf(designerRef: any) {
|
||||||
return JSON.stringify(designerRef.value.getOption());
|
// 关联案例:https://gitee.com/yudaocode/yudao-ui-admin-vue3/pulls/834/
|
||||||
|
return formCreate.toJson(designerRef.value.getOption());
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 解码表单 Conf */
|
||||||
|
export function decodeConf(conf: string) {
|
||||||
|
return formCreate.parseJson(conf);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 编码表单 Fields */
|
/** 编码表单 Fields */
|
||||||
export function encodeFields(designerRef: any) {
|
export function encodeFields(designerRef: any) {
|
||||||
const rule = JSON.parse(designerRef.value.getJson());
|
const rule = designerRef.value.getRule();
|
||||||
const fields: string[] = [];
|
const fields: string[] = [];
|
||||||
rule.forEach((item: unknown) => {
|
rule.forEach((item: any) => {
|
||||||
fields.push(JSON.stringify(item));
|
fields.push(formCreate.toJson(item));
|
||||||
});
|
});
|
||||||
return fields;
|
return fields;
|
||||||
}
|
}
|
||||||
@@ -49,7 +55,7 @@ export function setConfAndFields(
|
|||||||
conf: string,
|
conf: string,
|
||||||
fields: string | string[],
|
fields: string | string[],
|
||||||
) {
|
) {
|
||||||
designerRef.value.setOption(formCreate.parseJson(conf));
|
designerRef.value.setOption(decodeConf(conf));
|
||||||
// 处理 fields 参数类型,确保传入 decodeFields 的是 string[] 类型
|
// 处理 fields 参数类型,确保传入 decodeFields 的是 string[] 类型
|
||||||
const fieldsArray = Array.isArray(fields) ? fields : [fields];
|
const fieldsArray = Array.isArray(fields) ? fields : [fields];
|
||||||
designerRef.value.setRule(decodeFields(fieldsArray));
|
designerRef.value.setRule(decodeFields(fieldsArray));
|
||||||
@@ -65,7 +71,7 @@ export function setConfAndFields2(
|
|||||||
if (isRef(detailPreview)) {
|
if (isRef(detailPreview)) {
|
||||||
detailPreview = detailPreview.value;
|
detailPreview = detailPreview.value;
|
||||||
}
|
}
|
||||||
detailPreview.option = formCreate.parseJson(conf);
|
detailPreview.option = decodeConf(conf);
|
||||||
detailPreview.rule = decodeFields(fields);
|
detailPreview.rule = decodeFields(fields);
|
||||||
if (value) {
|
if (value) {
|
||||||
detailPreview.value = value;
|
detailPreview.value = value;
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
|
|||||||
import { deleteAlertConfig, getAlertConfigPage } from '#/api/iot/alert/config';
|
import { deleteAlertConfig, getAlertConfigPage } from '#/api/iot/alert/config';
|
||||||
import { $t } from '#/locales';
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
import AlertConfigForm from '../modules/AlertConfigForm.vue';
|
import AlertConfigForm from '../modules/alert-config-form.vue';
|
||||||
import { useGridColumns, useGridFormSchema } from './data';
|
import { useGridColumns, useGridFormSchema } from './data';
|
||||||
|
|
||||||
defineOptions({ name: 'IoTAlertConfig' });
|
defineOptions({ name: 'IoTAlertConfig' });
|
||||||
|
|||||||
@@ -32,10 +32,10 @@ import { getSimpleProductList } from '#/api/iot/product/product';
|
|||||||
import { $t } from '#/locales';
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
import { useGridColumns } from './data';
|
import { useGridColumns } from './data';
|
||||||
import DeviceCardView from './modules/DeviceCardView.vue';
|
import DeviceCardView from './modules/device-card-view.vue';
|
||||||
import DeviceForm from './modules/DeviceForm.vue';
|
import DeviceForm from './modules/device-form.vue';
|
||||||
import DeviceGroupForm from './modules/DeviceGroupForm.vue';
|
import DeviceGroupForm from './modules/device-group-form.vue';
|
||||||
import DeviceImportForm from './modules/DeviceImportForm.vue';
|
import DeviceImportForm from './modules/device-import-form.vue';
|
||||||
|
|
||||||
/** IoT 设备列表 */
|
/** IoT 设备列表 */
|
||||||
defineOptions({ name: 'IoTDevice' });
|
defineOptions({ name: 'IoTDevice' });
|
||||||
@@ -48,6 +48,7 @@ const viewMode = ref<'card' | 'list'>('card');
|
|||||||
const cardViewRef = ref();
|
const cardViewRef = ref();
|
||||||
|
|
||||||
// Modal instances
|
// Modal instances
|
||||||
|
// TODO @haohao:这个界面,等 product 改完,在一起看看怎么弄更好。
|
||||||
const [DeviceFormModal, deviceFormModalApi] = useVbenModal({
|
const [DeviceFormModal, deviceFormModalApi] = useVbenModal({
|
||||||
connectedComponent: DeviceForm,
|
connectedComponent: DeviceForm,
|
||||||
destroyOnClose: true,
|
destroyOnClose: true,
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { useRouter } from 'vue-router';
|
|||||||
|
|
||||||
import { Button, Card, Descriptions, message } from 'ant-design-vue';
|
import { Button, Card, Descriptions, message } from 'ant-design-vue';
|
||||||
|
|
||||||
import DeviceForm from '../DeviceForm.vue';
|
import DeviceForm from '../device-form.vue';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
product: IotProductApi.Product;
|
product: IotProductApi.Product;
|
||||||
@@ -22,13 +22,13 @@ import {
|
|||||||
} from 'ant-design-vue';
|
} from 'ant-design-vue';
|
||||||
|
|
||||||
import { DeviceStateEnum, sendDeviceMessage } from '#/api/iot/device/device';
|
import { DeviceStateEnum, sendDeviceMessage } from '#/api/iot/device/device';
|
||||||
import DataDefinition from '#/views/iot/thingmodel/modules/components/DataDefinition.vue';
|
import DataDefinition from '#/views/iot/thingmodel/modules/components/data-definition.vue';
|
||||||
import {
|
import {
|
||||||
IotDeviceMessageMethodEnum,
|
IotDeviceMessageMethodEnum,
|
||||||
IoTThingModelTypeEnum,
|
IoTThingModelTypeEnum,
|
||||||
} from '#/views/iot/utils/constants';
|
} from '#/views/iot/utils/constants';
|
||||||
|
|
||||||
import DeviceDetailsMessage from './DeviceDetailsMessage.vue';
|
import DeviceDetailsMessage from './device-details-message.vue';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
device: IotDeviceApi.Device;
|
device: IotDeviceApi.Device;
|
||||||
@@ -354,7 +354,7 @@ async function handleExport() {
|
|||||||
...list.value.map((item, index) => {
|
...list.value.map((item, index) => {
|
||||||
return [
|
return [
|
||||||
index + 1,
|
index + 1,
|
||||||
formatDate(new Date(item.updateTime)),
|
formatDateTime(new Date(item.updateTime)),
|
||||||
isComplexDataType.value
|
isComplexDataType.value
|
||||||
? `"${JSON.stringify(item.value)}"`
|
? `"${JSON.stringify(item.value)}"`
|
||||||
: item.value,
|
: item.value,
|
||||||
@@ -553,15 +553,17 @@ defineExpose({ open }); // 提供 open 方法,用于打开弹窗
|
|||||||
|
|
||||||
.toolbar-wrapper {
|
.toolbar-wrapper {
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
background-color: #fafafa;
|
background-color: hsl(var(--card) / 0.9);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
|
border: 1px solid hsl(var(--border) / 0.6);
|
||||||
}
|
}
|
||||||
|
|
||||||
.chart-container,
|
.chart-container,
|
||||||
.table-container {
|
.table-container {
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
background-color: #fff;
|
background-color: hsl(var(--card));
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
|
border: 1px solid hsl(var(--border) / 0.6);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -22,7 +22,8 @@ import {
|
|||||||
|
|
||||||
import { getLatestDeviceProperties } from '#/api/iot/device/device';
|
import { getLatestDeviceProperties } from '#/api/iot/device/device';
|
||||||
|
|
||||||
import DeviceDetailsThingModelPropertyHistory from './DeviceDetailsThingModelPropertyHistory.vue';
|
import DeviceDetailsThingModelPropertyHistory
|
||||||
|
from './device-details-thing-model-property-history.vue';
|
||||||
|
|
||||||
const props = defineProps<{ deviceId: number }>();
|
const props = defineProps<{ deviceId: number }>();
|
||||||
|
|
||||||
@@ -123,10 +124,12 @@ onMounted(() => {
|
|||||||
style="width: 240px"
|
style="width: 240px"
|
||||||
@press-enter="handleQuery"
|
@press-enter="handleQuery"
|
||||||
/>
|
/>
|
||||||
<div class="flex items-center" style="gap: 8px">
|
<Switch
|
||||||
<span style="font-size: 14px; color: #666">自动刷新</span>
|
v-model:checked="autoRefresh"
|
||||||
<Switch v-model:checked="autoRefresh" size="small" />
|
class="ml-20px"
|
||||||
</div>
|
checked-children="定时刷新"
|
||||||
|
un-checked-children="定时刷新"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Button.Group>
|
<Button.Group>
|
||||||
<Button
|
<Button
|
||||||
@@ -8,9 +8,9 @@ import { ContentWrap } from '@vben/common-ui';
|
|||||||
|
|
||||||
import { Tabs } from 'ant-design-vue';
|
import { Tabs } from 'ant-design-vue';
|
||||||
|
|
||||||
import DeviceDetailsThingModelEvent from './DeviceDetailsThingModelEvent.vue';
|
import DeviceDetailsThingModelEvent from './device-details-thing-model-event.vue';
|
||||||
import DeviceDetailsThingModelProperty from './DeviceDetailsThingModelProperty.vue';
|
import DeviceDetailsThingModelProperty from './device-details-thing-model-property.vue';
|
||||||
import DeviceDetailsThingModelService from './DeviceDetailsThingModelService.vue';
|
import DeviceDetailsThingModelService from './device-details-thing-model-service.vue';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
deviceId: number;
|
deviceId: number;
|
||||||
@@ -14,13 +14,13 @@ import { getDevice } from '#/api/iot/device/device';
|
|||||||
import { DeviceTypeEnum, getProduct } from '#/api/iot/product/product';
|
import { DeviceTypeEnum, getProduct } from '#/api/iot/product/product';
|
||||||
import { getThingModelListByProductId } from '#/api/iot/thingmodel';
|
import { getThingModelListByProductId } from '#/api/iot/thingmodel';
|
||||||
|
|
||||||
import DeviceDetailConfig from './DeviceDetailConfig.vue';
|
import DeviceDetailConfig from './device-detail-config.vue';
|
||||||
import DeviceDetailsHeader from './DeviceDetailsHeader.vue';
|
import DeviceDetailsHeader from './device-details-header.vue';
|
||||||
import DeviceDetailsInfo from './DeviceDetailsInfo.vue';
|
import DeviceDetailsInfo from './device-details-info.vue';
|
||||||
import DeviceDetailsMessage from './DeviceDetailsMessage.vue';
|
import DeviceDetailsMessage from './device-details-message.vue';
|
||||||
import DeviceDetailsSimulator from './DeviceDetailsSimulator.vue';
|
import DeviceDetailsSimulator from './device-details-simulator.vue';
|
||||||
import DeviceDetailsSubDevice from './DeviceDetailsSubDevice.vue';
|
import DeviceDetailsSubDevice from './device-details-sub-device.vue';
|
||||||
import DeviceDetailsThingModel from './DeviceDetailsThingModel.vue';
|
import DeviceDetailsThingModel from './device-details-thing-model.vue';
|
||||||
|
|
||||||
defineOptions({ name: 'IoTDeviceDetail' });
|
defineOptions({ name: 'IoTDeviceDetail' });
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,9 @@
|
|||||||
import { onMounted, ref } from 'vue';
|
import { onMounted, ref } from 'vue';
|
||||||
|
|
||||||
import { DICT_TYPE } from '@vben/constants';
|
import { DICT_TYPE } from '@vben/constants';
|
||||||
import { getDictLabel } from '@vben/hooks';
|
import { getDictLabel, getDictObj } from '@vben/hooks';
|
||||||
import { IconifyIcon } from '@vben/icons';
|
import { IconifyIcon } from '@vben/icons';
|
||||||
|
import { isValidColor, TinyColor } from '@vben/utils';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
@@ -52,6 +53,91 @@ const queryParams = ref({
|
|||||||
pageSize: 12,
|
pageSize: 12,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const DEFAULT_STATUS_MAP: Record<
|
||||||
|
'default' | number,
|
||||||
|
{ bgColor: string; borderColor: string; color: string; text: string }
|
||||||
|
> = {
|
||||||
|
[DeviceStateEnum.ONLINE]: {
|
||||||
|
text: '在线',
|
||||||
|
color: '#52c41a',
|
||||||
|
bgColor: '#f6ffed',
|
||||||
|
borderColor: '#b7eb8f',
|
||||||
|
},
|
||||||
|
[DeviceStateEnum.OFFLINE]: {
|
||||||
|
text: '离线',
|
||||||
|
color: '#faad14',
|
||||||
|
bgColor: '#fffbe6',
|
||||||
|
borderColor: '#ffe58f',
|
||||||
|
},
|
||||||
|
[DeviceStateEnum.INACTIVE]: {
|
||||||
|
text: '未激活',
|
||||||
|
color: '#ff4d4f',
|
||||||
|
bgColor: '#fff1f0',
|
||||||
|
borderColor: '#ffccc7',
|
||||||
|
},
|
||||||
|
default: {
|
||||||
|
text: '未知状态',
|
||||||
|
color: '#595959',
|
||||||
|
bgColor: '#fafafa',
|
||||||
|
borderColor: '#d9d9d9',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const COLOR_TYPE_PRESETS: Record<
|
||||||
|
string,
|
||||||
|
{ bgColor: string; borderColor: string; color: string }
|
||||||
|
> = {
|
||||||
|
success: {
|
||||||
|
color: '#52c41a',
|
||||||
|
bgColor: '#f6ffed',
|
||||||
|
borderColor: '#b7eb8f',
|
||||||
|
},
|
||||||
|
processing: {
|
||||||
|
color: '#1890ff',
|
||||||
|
bgColor: '#e6f7ff',
|
||||||
|
borderColor: '#91d5ff',
|
||||||
|
},
|
||||||
|
warning: {
|
||||||
|
color: '#faad14',
|
||||||
|
bgColor: '#fffbe6',
|
||||||
|
borderColor: '#ffe58f',
|
||||||
|
},
|
||||||
|
error: {
|
||||||
|
color: '#ff4d4f',
|
||||||
|
bgColor: '#fff1f0',
|
||||||
|
borderColor: '#ffccc7',
|
||||||
|
},
|
||||||
|
default: {
|
||||||
|
color: '#595959',
|
||||||
|
bgColor: '#fafafa',
|
||||||
|
borderColor: '#d9d9d9',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
function normalizeColorType(colorType?: string) {
|
||||||
|
switch (colorType) {
|
||||||
|
case 'danger': {
|
||||||
|
return 'error';
|
||||||
|
}
|
||||||
|
case 'default':
|
||||||
|
case 'error':
|
||||||
|
case 'processing':
|
||||||
|
case 'success':
|
||||||
|
case 'warning': {
|
||||||
|
return colorType;
|
||||||
|
}
|
||||||
|
case 'info': {
|
||||||
|
return 'default';
|
||||||
|
}
|
||||||
|
case 'primary': {
|
||||||
|
return 'processing';
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
return 'default';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 获取产品名称
|
// 获取产品名称
|
||||||
function getProductName(productId: number) {
|
function getProductName(productId: number) {
|
||||||
const product = props.products.find((p: any) => p.id === productId);
|
const product = props.products.find((p: any) => p.id === productId);
|
||||||
@@ -90,21 +176,41 @@ function getDeviceTypeColor(deviceType: number) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 获取设备状态信息
|
// 获取设备状态信息
|
||||||
function getStatusInfo(state: number) {
|
function getStatusInfo(state: number | string | null | undefined) {
|
||||||
if (state === DeviceStateEnum.ONLINE) {
|
const parsedState = Number(state);
|
||||||
|
const hasNumericState = Number.isFinite(parsedState);
|
||||||
|
const fallback = hasNumericState
|
||||||
|
? DEFAULT_STATUS_MAP[parsedState] || DEFAULT_STATUS_MAP.default
|
||||||
|
: DEFAULT_STATUS_MAP.default;
|
||||||
|
const dict = getDictObj(
|
||||||
|
DICT_TYPE.IOT_DEVICE_STATE,
|
||||||
|
hasNumericState ? parsedState : state,
|
||||||
|
);
|
||||||
|
if (dict) {
|
||||||
|
if (!dict.colorType && !dict.cssClass) {
|
||||||
|
return {
|
||||||
|
...fallback,
|
||||||
|
text: dict.label || fallback.text,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const presetKey = normalizeColorType(dict.colorType);
|
||||||
|
if (isValidColor(dict.cssClass)) {
|
||||||
|
const baseColor = new TinyColor(dict.cssClass);
|
||||||
|
return {
|
||||||
|
text: dict.label || fallback.text,
|
||||||
|
color: baseColor.toHexString(),
|
||||||
|
bgColor: baseColor.clone().setAlpha(0.15).toRgbString(),
|
||||||
|
borderColor: baseColor.clone().lighten(30).toHexString(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const preset = COLOR_TYPE_PRESETS[presetKey] || COLOR_TYPE_PRESETS.default;
|
||||||
return {
|
return {
|
||||||
text: '在线',
|
text: dict.label || fallback.text,
|
||||||
color: '#52c41a',
|
...preset,
|
||||||
bgColor: '#f6ffed',
|
|
||||||
borderColor: '#b7eb8f',
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return {
|
|
||||||
text: '未激活',
|
return fallback;
|
||||||
color: '#ff4d4f',
|
|
||||||
bgColor: '#fff1f0',
|
|
||||||
borderColor: '#ffccc7',
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
@@ -290,21 +396,21 @@ defineExpose({
|
|||||||
.device-card {
|
.device-card {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background: #fff;
|
background: hsl(var(--card) / 0.95);
|
||||||
border: 1px solid #f0f0f0;
|
border: 1px solid hsl(var(--border) / 0.6);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
box-shadow:
|
box-shadow:
|
||||||
0 1px 2px 0 rgb(0 0 0 / 3%),
|
0 1px 2px 0 hsl(var(--foreground) / 0.04),
|
||||||
0 1px 6px -1px rgb(0 0 0 / 2%),
|
0 1px 6px -1px hsl(var(--foreground) / 0.05),
|
||||||
0 2px 4px 0 rgb(0 0 0 / 2%);
|
0 2px 4px 0 hsl(var(--foreground) / 0.05);
|
||||||
transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
|
transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
border-color: #e6e6e6;
|
border-color: hsl(var(--border));
|
||||||
box-shadow:
|
box-shadow:
|
||||||
0 1px 2px -2px rgb(0 0 0 / 16%),
|
0 1px 2px -2px hsl(var(--foreground) / 0.12),
|
||||||
0 3px 6px 0 rgb(0 0 0 / 12%),
|
0 3px 6px 0 hsl(var(--foreground) / 0.1),
|
||||||
0 5px 12px 4px rgb(0 0 0 / 9%);
|
0 5px 12px 4px hsl(var(--foreground) / 0.08);
|
||||||
transform: translateY(-4px);
|
transform: translateY(-4px);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -367,7 +473,7 @@ defineExpose({
|
|||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
line-height: 24px;
|
line-height: 24px;
|
||||||
color: #262626;
|
color: hsl(var(--foreground) / 0.9);
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -390,7 +496,7 @@ defineExpose({
|
|||||||
.label {
|
.label {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
color: #8c8c8c;
|
color: hsl(var(--foreground) / 0.6);
|
||||||
}
|
}
|
||||||
|
|
||||||
.value {
|
.value {
|
||||||
@@ -399,17 +505,17 @@ defineExpose({
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
color: #262626;
|
color: hsl(var(--foreground) / 0.85);
|
||||||
text-align: right;
|
text-align: right;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|
||||||
&.link {
|
&.link {
|
||||||
color: #1890ff;
|
color: hsl(var(--primary));
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: color 0.2s;
|
transition: color 0.2s;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: #40a9ff;
|
color: hsl(var(--primary) / 0.85);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -418,7 +524,7 @@ defineExpose({
|
|||||||
'SF Mono', Monaco, Inconsolata, 'Fira Code', Consolas, monospace;
|
'SF Mono', Monaco, Inconsolata, 'Fira Code', Consolas, monospace;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: #595959;
|
color: hsl(var(--foreground) / 0.6);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -431,7 +537,7 @@ defineExpose({
|
|||||||
display: flex;
|
display: flex;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
padding-top: 12px;
|
padding-top: 12px;
|
||||||
border-top: 1px solid #f5f5f5;
|
border-top: 1px solid hsl(var(--border) / 0.4);
|
||||||
|
|
||||||
.action-btn {
|
.action-btn {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -445,7 +551,7 @@ defineExpose({
|
|||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border: 1px solid;
|
border: 1px solid transparent;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
transition: all 0.2s;
|
transition: all 0.2s;
|
||||||
|
|
||||||
@@ -454,52 +560,60 @@ defineExpose({
|
|||||||
}
|
}
|
||||||
|
|
||||||
&.btn-edit {
|
&.btn-edit {
|
||||||
color: #1890ff;
|
color: hsl(var(--primary));
|
||||||
background: #e6f7ff;
|
background: hsl(var(--primary) / 0.12);
|
||||||
border-color: #91d5ff;
|
border-color: hsl(var(--primary) / 0.25);
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: #fff;
|
color: hsl(var(--primary-foreground));
|
||||||
background: #1890ff;
|
background: hsl(var(--primary));
|
||||||
border-color: #1890ff;
|
border-color: hsl(var(--primary));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.btn-view {
|
&.btn-view {
|
||||||
color: #faad14;
|
color: hsl(var(--warning));
|
||||||
background: #fffbe6;
|
background: hsl(var(--warning) / 0.12);
|
||||||
border-color: #ffe58f;
|
border-color: hsl(var(--warning) / 0.25);
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
background: #faad14;
|
background: hsl(var(--warning));
|
||||||
border-color: #faad14;
|
border-color: hsl(var(--warning));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.btn-data {
|
&.btn-data {
|
||||||
color: #722ed1;
|
color: hsl(var(--accent-foreground));
|
||||||
background: #f9f0ff;
|
background: color-mix(
|
||||||
border-color: #d3adf7;
|
in srgb,
|
||||||
|
hsl(var(--accent)) 40%,
|
||||||
|
hsl(var(--card)) 60%
|
||||||
|
);
|
||||||
|
border-color: color-mix(
|
||||||
|
in srgb,
|
||||||
|
hsl(var(--accent)) 55%,
|
||||||
|
transparent
|
||||||
|
);
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: #fff;
|
color: hsl(var(--accent-foreground));
|
||||||
background: #722ed1;
|
background: hsl(var(--accent));
|
||||||
border-color: #722ed1;
|
border-color: hsl(var(--accent));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.btn-delete {
|
&.btn-delete {
|
||||||
flex: 0 0 32px;
|
flex: 0 0 32px;
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
color: #ff4d4f;
|
color: hsl(var(--destructive));
|
||||||
background: #fff1f0;
|
background: hsl(var(--destructive) / 0.12);
|
||||||
border-color: #ffa39e;
|
border-color: hsl(var(--destructive) / 0.3);
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: #fff;
|
color: hsl(var(--destructive-foreground));
|
||||||
background: #ff4d4f;
|
background: hsl(var(--destructive));
|
||||||
border-color: #ff4d4f;
|
border-color: hsl(var(--destructive));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -43,9 +43,7 @@ async function handleDelete(row: IotDeviceGroupApi.DeviceGroup) {
|
|||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
await deleteDeviceGroup(row.id as number);
|
await deleteDeviceGroup(row.id as number);
|
||||||
message.success({
|
message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
|
||||||
content: $t('ui.actionMessage.deleteSuccess', [row.name]),
|
|
||||||
});
|
|
||||||
handleRefresh();
|
handleRefresh();
|
||||||
} finally {
|
} finally {
|
||||||
hideLoading();
|
hideLoading();
|
||||||
@@ -55,7 +53,6 @@ async function handleDelete(row: IotDeviceGroupApi.DeviceGroup) {
|
|||||||
const [Grid, gridApi] = useVbenVxeGrid({
|
const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
formOptions: {
|
formOptions: {
|
||||||
schema: useGridFormSchema(),
|
schema: useGridFormSchema(),
|
||||||
showCollapseButton: true,
|
|
||||||
},
|
},
|
||||||
gridOptions: {
|
gridOptions: {
|
||||||
columns: useGridColumns(),
|
columns: useGridColumns(),
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ import { useFormSchema } from '../data';
|
|||||||
|
|
||||||
defineOptions({ name: 'IoTDeviceGroupForm' });
|
defineOptions({ name: 'IoTDeviceGroupForm' });
|
||||||
|
|
||||||
|
// TODO @haohao:web-antd/src/views/iot/product/category/modules/product-category-form.vue 类似问题
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
success: [];
|
success: [];
|
||||||
}>();
|
}>();
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
/**
|
/** 设备数量饼图配置 */
|
||||||
* 设备数量饼图配置
|
// TODO @haohao:貌似没用到??
|
||||||
*/
|
|
||||||
export function getDeviceCountChartOptions(
|
export function getDeviceCountChartOptions(
|
||||||
productCategoryDeviceCounts: Record<string, number>,
|
productCategoryDeviceCounts: Record<string, number>,
|
||||||
): any {
|
): any {
|
||||||
@@ -101,6 +101,7 @@ export function useIotHome() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO @haohao:是不是删除下哈;
|
||||||
/** 格式化数字 - 大数字显示为 K/M */
|
/** 格式化数字 - 大数字显示为 K/M */
|
||||||
export function formatNumber(num: number): string {
|
export function formatNumber(num: number): string {
|
||||||
if (num >= 1_000_000) {
|
if (num >= 1_000_000) {
|
||||||
|
|||||||
@@ -3,17 +3,15 @@ import { Page } from '@vben/common-ui';
|
|||||||
|
|
||||||
import { Col, Row } from 'ant-design-vue';
|
import { Col, Row } from 'ant-design-vue';
|
||||||
|
|
||||||
// 导入业务逻辑
|
|
||||||
import { useIotHome } from './data';
|
import { useIotHome } from './data';
|
||||||
// 导入组件
|
import ComparisonCard from './modules/comparison-card.vue';
|
||||||
import ComparisonCard from './modules/ComparisonCard.vue';
|
import DeviceCountCard from './modules/device-count-card.vue';
|
||||||
import DeviceCountCard from './modules/DeviceCountCard.vue';
|
import DeviceStateCountCard from './modules/device-state-count-card.vue';
|
||||||
import DeviceStateCountCard from './modules/DeviceStateCountCard.vue';
|
import MessageTrendCard from './modules/message-trend-card.vue';
|
||||||
import MessageTrendCard from './modules/MessageTrendCard.vue';
|
|
||||||
|
|
||||||
defineOptions({ name: 'IoTHome' });
|
defineOptions({ name: 'IoTHome' });
|
||||||
|
|
||||||
// 使用业务逻辑 Hook
|
// TODO @haohao:相关的方法,拿到 index.vue 里,data.ts 只放 schema;
|
||||||
const { loading, statsData } = useIotHome();
|
const { loading, statsData } = useIotHome();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import { createIconifyIcon } from '@vben/icons';
|
|||||||
|
|
||||||
import { Card } from 'ant-design-vue';
|
import { Card } from 'ant-design-vue';
|
||||||
|
|
||||||
|
// TODO @haohao:这个可以迁移到 packages/effects/common-ui/src/components/card/comparison-card
|
||||||
|
|
||||||
defineOptions({ name: 'ComparisonCard' });
|
defineOptions({ name: 'ComparisonCard' });
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
@@ -59,6 +61,7 @@ const IconComponent = computed(() => iconMap[props.icon] || iconMap.menu);
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
/** TODO tindwind */
|
||||||
.stat-card {
|
.stat-card {
|
||||||
height: 160px;
|
height: 160px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@@ -28,13 +28,17 @@ const hasData = computed(() => {
|
|||||||
|
|
||||||
/** 初始化图表 */
|
/** 初始化图表 */
|
||||||
function initChart() {
|
function initChart() {
|
||||||
if (!hasData.value) return;
|
if (!hasData.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO @haohao:await nextTick();
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
const data = Object.entries(
|
const data = Object.entries(
|
||||||
props.statsData.productCategoryDeviceCounts,
|
props.statsData.productCategoryDeviceCounts,
|
||||||
).map(([name, value]) => ({ name, value }));
|
).map(([name, value]) => ({ name, value }));
|
||||||
|
|
||||||
|
// TODO @haohao:看看 chart-options 怎么提取出去,类似 apps/web-antd/src/views/mall/statistics/member/modules/area-chart-options.ts 写法
|
||||||
renderEcharts({
|
renderEcharts({
|
||||||
tooltip: {
|
tooltip: {
|
||||||
trigger: 'item',
|
trigger: 'item',
|
||||||
@@ -132,6 +136,7 @@ onMounted(() => {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
/** TODO tindwind */
|
||||||
.chart-card {
|
.chart-card {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
@@ -31,6 +31,7 @@ const hasData = computed(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
/** 获取仪表盘配置 */
|
/** 获取仪表盘配置 */
|
||||||
|
// TODO @haohao:看看 chart-options 怎么提取出去,类似 apps/web-antd/src/views/mall/statistics/member/modules/area-chart-options.ts 写法
|
||||||
const getGaugeOption = (value: number, color: string, title: string): any => {
|
const getGaugeOption = (value: number, color: string, title: string): any => {
|
||||||
return {
|
return {
|
||||||
series: [
|
series: [
|
||||||
@@ -81,8 +82,11 @@ const getGaugeOption = (value: number, color: string, title: string): any => {
|
|||||||
|
|
||||||
/** 初始化图表 */
|
/** 初始化图表 */
|
||||||
function initCharts() {
|
function initCharts() {
|
||||||
if (!hasData.value) return;
|
if (!hasData.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO @haohao:await nextTick();
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
// 在线设备
|
// 在线设备
|
||||||
renderOnlineChart(
|
renderOnlineChart(
|
||||||
@@ -147,6 +151,7 @@ onMounted(() => {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
/** TODO tindwind */
|
||||||
.chart-card {
|
.chart-card {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
@@ -24,6 +24,8 @@ const messageData = ref<IotStatisticsApi.DeviceMessageSummaryByDate[]>([]);
|
|||||||
const activeTimeRange = ref('7d'); // 当前选中的时间范围
|
const activeTimeRange = ref('7d'); // 当前选中的时间范围
|
||||||
const dateRange = ref<[Dayjs, Dayjs] | undefined>(undefined);
|
const dateRange = ref<[Dayjs, Dayjs] | undefined>(undefined);
|
||||||
|
|
||||||
|
// TODO @haohao:这个貌似没迁移对。它是时间范围、事件间隔;
|
||||||
|
|
||||||
const queryParams = reactive<IotStatisticsApi.DeviceMessageReq>({
|
const queryParams = reactive<IotStatisticsApi.DeviceMessageReq>({
|
||||||
interval: 1, // 按天
|
interval: 1, // 按天
|
||||||
times: [],
|
times: [],
|
||||||
@@ -34,6 +36,7 @@ const hasData = computed(() => {
|
|||||||
return messageData.value && messageData.value.length > 0;
|
return messageData.value && messageData.value.length > 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// TODO @haohao:注释风格,应该是 /** */ 在方法上;然后变量在字段后面 // 。。。
|
||||||
// 设置时间范围
|
// 设置时间范围
|
||||||
function setTimeRange(range: string) {
|
function setTimeRange(range: string) {
|
||||||
activeTimeRange.value = range;
|
activeTimeRange.value = range;
|
||||||
@@ -64,6 +67,7 @@ function setTimeRange(range: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO @haohao:可以使用 formatDateTime;
|
||||||
queryParams.times = [
|
queryParams.times = [
|
||||||
start.format('YYYY-MM-DD HH:mm:ss'),
|
start.format('YYYY-MM-DD HH:mm:ss'),
|
||||||
end.format('YYYY-MM-DD HH:mm:ss'),
|
end.format('YYYY-MM-DD HH:mm:ss'),
|
||||||
@@ -78,6 +82,7 @@ function handleDateChange() {
|
|||||||
activeTimeRange.value = ''; // 清空快捷选择
|
activeTimeRange.value = ''; // 清空快捷选择
|
||||||
queryParams.interval = 1; // 按天
|
queryParams.interval = 1; // 按天
|
||||||
queryParams.times = [
|
queryParams.times = [
|
||||||
|
// TODO @haohao:可以使用 formatDateTime;
|
||||||
dateRange.value[0].startOf('day').format('YYYY-MM-DD HH:mm:ss'),
|
dateRange.value[0].startOf('day').format('YYYY-MM-DD HH:mm:ss'),
|
||||||
dateRange.value[1].endOf('day').format('YYYY-MM-DD HH:mm:ss'),
|
dateRange.value[1].endOf('day').format('YYYY-MM-DD HH:mm:ss'),
|
||||||
];
|
];
|
||||||
@@ -110,6 +115,7 @@ function initChart() {
|
|||||||
const upstreamData = messageData.value.map((item) => item.upstreamCount);
|
const upstreamData = messageData.value.map((item) => item.upstreamCount);
|
||||||
const downstreamData = messageData.value.map((item) => item.downstreamCount);
|
const downstreamData = messageData.value.map((item) => item.downstreamCount);
|
||||||
|
|
||||||
|
// TODO @haohao:看看 chart-options 怎么提取出去,类似 apps/web-antd/src/views/mall/statistics/member/modules/area-chart-options.ts 写法
|
||||||
renderEcharts({
|
renderEcharts({
|
||||||
tooltip: {
|
tooltip: {
|
||||||
trigger: 'axis',
|
trigger: 'axis',
|
||||||
@@ -13,7 +13,7 @@ import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
|
|||||||
import { deleteOtaFirmware, getOtaFirmwarePage } from '#/api/iot/ota/firmware';
|
import { deleteOtaFirmware, getOtaFirmwarePage } from '#/api/iot/ota/firmware';
|
||||||
import { $t } from '#/locales';
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
import Form from '../modules/OtaFirmwareForm.vue';
|
import OtaFirmwareForm from '../modules/ota-firmware-form.vue';
|
||||||
import { useGridColumns, useGridFormSchema } from './data';
|
import { useGridColumns, useGridFormSchema } from './data';
|
||||||
|
|
||||||
defineOptions({ name: 'IoTOtaFirmware' });
|
defineOptions({ name: 'IoTOtaFirmware' });
|
||||||
@@ -21,7 +21,7 @@ defineOptions({ name: 'IoTOtaFirmware' });
|
|||||||
const { push } = useRouter();
|
const { push } = useRouter();
|
||||||
|
|
||||||
const [FormModal, formModalApi] = useVbenModal({
|
const [FormModal, formModalApi] = useVbenModal({
|
||||||
connectedComponent: Form,
|
connectedComponent: OtaFirmwareForm,
|
||||||
destroyOnClose: true,
|
destroyOnClose: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { deleteOtaFirmware, getOtaFirmwarePage } from '#/api/iot/ota/firmware';
|
|||||||
import { $t } from '#/locales';
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
import { useGridColumns, useGridFormSchema } from './data';
|
import { useGridColumns, useGridFormSchema } from './data';
|
||||||
import OtaFirmwareForm from './modules/OtaFirmwareForm.vue';
|
import OtaFirmwareForm from './modules/ota-firmware-form.vue';
|
||||||
|
|
||||||
defineOptions({ name: 'IoTOtaFirmware' });
|
defineOptions({ name: 'IoTOtaFirmware' });
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { getOtaFirmware } from '#/api/iot/ota/firmware';
|
|||||||
import { getOtaTaskRecordStatusStatistics } from '#/api/iot/ota/task/record';
|
import { getOtaTaskRecordStatusStatistics } from '#/api/iot/ota/task/record';
|
||||||
import { IoTOtaTaskRecordStatusEnum } from '#/views/iot/utils/constants';
|
import { IoTOtaTaskRecordStatusEnum } from '#/views/iot/utils/constants';
|
||||||
|
|
||||||
import OtaTaskList from '../task/OtaTaskList.vue';
|
import OtaTaskList from '../task/ota-task-list.vue';
|
||||||
|
|
||||||
/** IoT OTA 固件详情 */
|
/** IoT OTA 固件详情 */
|
||||||
defineOptions({ name: 'IoTOtaFirmwareDetail' });
|
defineOptions({ name: 'IoTOtaFirmwareDetail' });
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { getOtaFirmware } from '#/api/iot/ota/firmware';
|
|||||||
import { getOtaTaskRecordStatusStatistics } from '#/api/iot/ota/task/record';
|
import { getOtaTaskRecordStatusStatistics } from '#/api/iot/ota/task/record';
|
||||||
import { IoTOtaTaskRecordStatusEnum } from '#/views/iot/utils/constants';
|
import { IoTOtaTaskRecordStatusEnum } from '#/views/iot/utils/constants';
|
||||||
|
|
||||||
import OtaTaskList from '../task/OtaTaskList.vue';
|
import OtaTaskList from '../task/ota-task-list.vue';
|
||||||
|
|
||||||
/** IoT OTA 固件详情 */
|
/** IoT OTA 固件详情 */
|
||||||
defineOptions({ name: 'IoTOtaFirmwareDetail' });
|
defineOptions({ name: 'IoTOtaFirmwareDetail' });
|
||||||
|
|||||||
@@ -22,8 +22,8 @@ import {
|
|||||||
import { getOtaTaskPage } from '#/api/iot/ota/task';
|
import { getOtaTaskPage } from '#/api/iot/ota/task';
|
||||||
import { IoTOtaTaskStatusEnum } from '#/views/iot/utils/constants';
|
import { IoTOtaTaskStatusEnum } from '#/views/iot/utils/constants';
|
||||||
|
|
||||||
import OtaTaskDetail from './OtaTaskDetail.vue';
|
import OtaTaskDetail from './ota-task-detail.vue';
|
||||||
import OtaTaskForm from './OtaTaskForm.vue';
|
import OtaTaskForm from './ota-task-form.vue';
|
||||||
|
|
||||||
/** IoT OTA 任务列表 */
|
/** IoT OTA 任务列表 */
|
||||||
defineOptions({ name: 'OtaTaskList' });
|
defineOptions({ name: 'OtaTaskList' });
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
import { Page } from '@vben/common-ui';
|
|
||||||
|
|
||||||
import { Button } from 'ant-design-vue';
|
|
||||||
|
|
||||||
defineOptions({ name: 'IotPlugin' });
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Page description="物聯網插件管理" title="插件管理">
|
|
||||||
<div class="p-4">
|
|
||||||
<Button
|
|
||||||
danger
|
|
||||||
type="link"
|
|
||||||
target="_blank"
|
|
||||||
href="https://github.com/yudaocode/yudao-ui-admin-vue3"
|
|
||||||
>
|
|
||||||
該功能支持 Vue3 + element-plus 版本!
|
|
||||||
</Button>
|
|
||||||
<div class="mt-4">
|
|
||||||
<h3>功能說明:</h3>
|
|
||||||
<p>IoT 插件管理功能</p>
|
|
||||||
<h3 class="mt-4">待實現:</h3>
|
|
||||||
<ul>
|
|
||||||
<li>⚠️ API 接口定義</li>
|
|
||||||
<li>⚠️ 頁面實現</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Page>
|
|
||||||
</template>
|
|
||||||
@@ -14,12 +14,12 @@ import {
|
|||||||
import { $t } from '#/locales';
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
import { useGridColumns, useGridFormSchema } from './data';
|
import { useGridColumns, useGridFormSchema } from './data';
|
||||||
import Form from './modules/ProductCategoryForm.vue';
|
import ProductCategoryForm from './modules/product-category-form.vue';
|
||||||
|
|
||||||
defineOptions({ name: 'IoTProductCategory' });
|
defineOptions({ name: 'IoTProductCategory' });
|
||||||
|
|
||||||
const [FormModal, formModalApi] = useVbenModal({
|
const [FormModal, formModalApi] = useVbenModal({
|
||||||
connectedComponent: Form,
|
connectedComponent: ProductCategoryForm,
|
||||||
destroyOnClose: true,
|
destroyOnClose: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -56,16 +56,11 @@ async function handleDelete(row: IotProductCategoryApi.ProductCategory) {
|
|||||||
const [Grid, gridApi] = useVbenVxeGrid({
|
const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
formOptions: {
|
formOptions: {
|
||||||
schema: useGridFormSchema(),
|
schema: useGridFormSchema(),
|
||||||
showCollapseButton: true,
|
|
||||||
collapsed: true,
|
|
||||||
},
|
},
|
||||||
gridOptions: {
|
gridOptions: {
|
||||||
columns: useGridColumns(),
|
columns: useGridColumns(),
|
||||||
height: 'auto',
|
height: 'auto',
|
||||||
keepSource: true,
|
keepSource: true,
|
||||||
pagerConfig: {
|
|
||||||
enabled: true,
|
|
||||||
},
|
|
||||||
proxyConfig: {
|
proxyConfig: {
|
||||||
ajax: {
|
ajax: {
|
||||||
query: async ({ page }, formValues) => {
|
query: async ({ page }, formValues) => {
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ import { $t } from '#/locales';
|
|||||||
|
|
||||||
import { useFormSchema } from '../data';
|
import { useFormSchema } from '../data';
|
||||||
|
|
||||||
|
// TODO @haohao:应该是 form.vue,不用前缀;
|
||||||
|
|
||||||
const emit = defineEmits(['success']);
|
const emit = defineEmits(['success']);
|
||||||
const formData = ref<IotProductCategoryApi.ProductCategory>();
|
const formData = ref<IotProductCategoryApi.ProductCategory>();
|
||||||
const getTitle = computed(() => {
|
const getTitle = computed(() => {
|
||||||
@@ -38,7 +40,7 @@ const [Form, formApi] = useVbenForm({
|
|||||||
showDefaultActions: false,
|
showDefaultActions: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO @haohao:参考别的 form;1)文件的命名可以简化;2)代码可以在简化下;
|
// TODO @haohao:参考 apps/web-antd/src/views/system/dept/modules/form.vue 简化 useVbenModal 里的代码;
|
||||||
const [Modal, modalApi] = useVbenModal({
|
const [Modal, modalApi] = useVbenModal({
|
||||||
async onConfirm() {
|
async onConfirm() {
|
||||||
const { valid } = await formApi.validate();
|
const { valid } = await formApi.validate();
|
||||||
@@ -23,7 +23,6 @@ export function useFormSchema(formApi?: any): VbenFormSchema[] {
|
|||||||
show: () => false,
|
show: () => false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// 创建时的 ProductKey 字段(带生成按钮)
|
|
||||||
{
|
{
|
||||||
fieldName: 'productKey',
|
fieldName: 'productKey',
|
||||||
label: 'ProductKey',
|
label: 'ProductKey',
|
||||||
@@ -34,7 +33,6 @@ export function useFormSchema(formApi?: any): VbenFormSchema[] {
|
|||||||
dependencies: {
|
dependencies: {
|
||||||
triggerFields: ['id'],
|
triggerFields: ['id'],
|
||||||
if(values) {
|
if(values) {
|
||||||
// 仅在创建时显示(没有 id)
|
|
||||||
return !values.id;
|
return !values.id;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -43,6 +41,7 @@ export function useFormSchema(formApi?: any): VbenFormSchema[] {
|
|||||||
.min(1, 'ProductKey 不能为空')
|
.min(1, 'ProductKey 不能为空')
|
||||||
.max(32, 'ProductKey 长度不能超过 32 个字符'),
|
.max(32, 'ProductKey 长度不能超过 32 个字符'),
|
||||||
suffix: () => {
|
suffix: () => {
|
||||||
|
// 创建时的 ProductKey 字段(带生成按钮)
|
||||||
return h(
|
return h(
|
||||||
Button,
|
Button,
|
||||||
{
|
{
|
||||||
@@ -55,19 +54,17 @@ export function useFormSchema(formApi?: any): VbenFormSchema[] {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// 编辑时的 ProductKey 字段(禁用,无按钮)
|
|
||||||
{
|
{
|
||||||
fieldName: 'productKey',
|
fieldName: 'productKey',
|
||||||
label: 'ProductKey',
|
label: 'ProductKey',
|
||||||
component: 'Input',
|
component: 'Input',
|
||||||
componentProps: {
|
componentProps: {
|
||||||
placeholder: '请输入 ProductKey',
|
placeholder: '请输入 ProductKey',
|
||||||
disabled: true,
|
disabled: true, // 编辑时的 ProductKey 字段(禁用,无按钮)
|
||||||
},
|
},
|
||||||
dependencies: {
|
dependencies: {
|
||||||
triggerFields: ['id'],
|
triggerFields: ['id'],
|
||||||
if(values) {
|
if(values) {
|
||||||
// 仅在编辑时显示(有 id)
|
|
||||||
return !!values.id;
|
return !!values.id;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -176,7 +173,6 @@ export function useBasicFormSchema(formApi?: any): VbenFormSchema[] {
|
|||||||
show: () => false,
|
show: () => false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// 创建时的 ProductKey 字段(带生成按钮)
|
|
||||||
{
|
{
|
||||||
fieldName: 'productKey',
|
fieldName: 'productKey',
|
||||||
label: 'ProductKey',
|
label: 'ProductKey',
|
||||||
@@ -187,7 +183,6 @@ export function useBasicFormSchema(formApi?: any): VbenFormSchema[] {
|
|||||||
dependencies: {
|
dependencies: {
|
||||||
triggerFields: ['id'],
|
triggerFields: ['id'],
|
||||||
if(values) {
|
if(values) {
|
||||||
// 仅在创建时显示(没有 id)
|
|
||||||
return !values.id;
|
return !values.id;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -208,7 +203,6 @@ export function useBasicFormSchema(formApi?: any): VbenFormSchema[] {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// 编辑时的 ProductKey 字段(禁用,无按钮)
|
|
||||||
{
|
{
|
||||||
fieldName: 'productKey',
|
fieldName: 'productKey',
|
||||||
label: 'ProductKey',
|
label: 'ProductKey',
|
||||||
@@ -220,7 +214,6 @@ export function useBasicFormSchema(formApi?: any): VbenFormSchema[] {
|
|||||||
dependencies: {
|
dependencies: {
|
||||||
triggerFields: ['id'],
|
triggerFields: ['id'],
|
||||||
if(values) {
|
if(values) {
|
||||||
// 仅在编辑时显示(有 id)
|
|
||||||
return !!values.id;
|
return !!values.id;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -341,6 +334,7 @@ export function useAdvancedFormSchema(): VbenFormSchema[] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 列表的搜索表单 */
|
/** 列表的搜索表单 */
|
||||||
|
// TODO @haohao:貌似用不上?
|
||||||
export function useGridFormSchema(): VbenFormSchema[] {
|
export function useGridFormSchema(): VbenFormSchema[] {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
@@ -367,7 +361,6 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
|||||||
/** 列表的字段 */
|
/** 列表的字段 */
|
||||||
export function useGridColumns(): VxeTableGridOptions['columns'] {
|
export function useGridColumns(): VxeTableGridOptions['columns'] {
|
||||||
return [
|
return [
|
||||||
{ type: 'checkbox', width: 40 },
|
|
||||||
{
|
{
|
||||||
field: 'id',
|
field: 'id',
|
||||||
title: 'ID',
|
title: 'ID',
|
||||||
@@ -413,7 +406,7 @@ export function useGridColumns(): VxeTableGridOptions['columns'] {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '操作',
|
title: '操作',
|
||||||
width: 180,
|
width: 220,
|
||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
slots: { default: 'actions' },
|
slots: { default: 'actions' },
|
||||||
},
|
},
|
||||||
@@ -421,6 +414,7 @@ export function useGridColumns(): VxeTableGridOptions['columns'] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 查询产品列表 */
|
/** 查询产品列表 */
|
||||||
|
// TODO @haohao:貌似可以删除?
|
||||||
export async function queryProductList({ page }: any, searchParams: any) {
|
export async function queryProductList({ page }: any, searchParams: any) {
|
||||||
return await getProductPage({
|
return await getProductPage({
|
||||||
pageNo: page.currentPage,
|
pageNo: page.currentPage,
|
||||||
@@ -430,6 +424,7 @@ export async function queryProductList({ page }: any, searchParams: any) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 创建图片预览状态 */
|
/** 创建图片预览状态 */
|
||||||
|
// TODO @haohao:可能不一定用的上;
|
||||||
export function useImagePreview() {
|
export function useImagePreview() {
|
||||||
const previewVisible = ref(false);
|
const previewVisible = ref(false);
|
||||||
const previewImage = ref('');
|
const previewImage = ref('');
|
||||||
@@ -446,6 +441,7 @@ export function useImagePreview() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO @haohao:放到对应的 form 里
|
||||||
/** 生成 ProductKey(包含大小写字母和数字) */
|
/** 生成 ProductKey(包含大小写字母和数字) */
|
||||||
export function generateProductKey(): string {
|
export function generateProductKey(): string {
|
||||||
const chars =
|
const chars =
|
||||||
|
|||||||
@@ -20,23 +20,20 @@ import {
|
|||||||
import { $t } from '#/locales';
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
import { useGridColumns, useImagePreview } from './data';
|
import { useGridColumns, useImagePreview } from './data';
|
||||||
import ProductCardView from './modules/ProductCardView.vue';
|
import ProductCardView from './modules/product-card-view.vue';
|
||||||
import ProductForm from './modules/ProductForm.vue';
|
import ProductForm from './modules/product-form.vue';
|
||||||
|
|
||||||
defineOptions({ name: 'IoTProduct' });
|
defineOptions({ name: 'IoTProduct' });
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const categoryList = ref<any[]>([]);
|
const categoryList = ref<any[]>([]); // TODO @haohao:category 类型
|
||||||
const viewMode = ref<'card' | 'list'>('card');
|
const viewMode = ref<'card' | 'list'>('card');
|
||||||
const cardViewRef = ref();
|
const cardViewRef = ref();
|
||||||
|
|
||||||
// 搜索参数
|
|
||||||
const searchParams = ref({
|
const searchParams = ref({
|
||||||
name: '',
|
name: '',
|
||||||
productKey: '',
|
productKey: '',
|
||||||
});
|
}); // 搜索参数
|
||||||
|
|
||||||
// 图片预览
|
|
||||||
const { previewVisible, previewImage, handlePreviewImage } = useImagePreview();
|
const { previewVisible, previewImage, handlePreviewImage } = useImagePreview();
|
||||||
|
|
||||||
const [FormModal, formModalApi] = useVbenModal({
|
const [FormModal, formModalApi] = useVbenModal({
|
||||||
@@ -44,18 +41,19 @@ const [FormModal, formModalApi] = useVbenModal({
|
|||||||
destroyOnClose: true,
|
destroyOnClose: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 加载产品分类列表
|
/** 加载产品分类列表 */
|
||||||
async function loadCategories() {
|
async function loadCategories() {
|
||||||
categoryList.value = await getSimpleProductCategoryList();
|
categoryList.value = await getSimpleProductCategoryList();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取分类名称
|
/** 获取分类名称 */
|
||||||
function getCategoryNameByValue(categoryId: number) {
|
function getCategoryNameByValue(categoryId: number) {
|
||||||
const category = categoryList.value.find((c: any) => c.id === categoryId);
|
const category = categoryList.value.find((c: any) => c.id === categoryId);
|
||||||
return category?.name || '未分类';
|
return category?.name || '未分类';
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 搜索 */
|
// TODO @haohao:要不要改成 handleRefresh,注释改成“刷新表格”,更加统一。
|
||||||
|
/** 搜索产品 */
|
||||||
function handleSearch() {
|
function handleSearch() {
|
||||||
if (viewMode.value === 'list') {
|
if (viewMode.value === 'list') {
|
||||||
gridApi.formApi.setValues(searchParams.value);
|
gridApi.formApi.setValues(searchParams.value);
|
||||||
@@ -65,14 +63,14 @@ function handleSearch() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 重置 */
|
/** 重置搜索 */
|
||||||
function handleReset() {
|
function handleReset() {
|
||||||
searchParams.value.name = '';
|
searchParams.value.name = '';
|
||||||
searchParams.value.productKey = '';
|
searchParams.value.productKey = '';
|
||||||
handleSearch();
|
handleSearch();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 刷新 */
|
/** 刷新表格 */
|
||||||
function handleRefresh() {
|
function handleRefresh() {
|
||||||
if (viewMode.value === 'list') {
|
if (viewMode.value === 'list') {
|
||||||
gridApi.query();
|
gridApi.query();
|
||||||
@@ -84,7 +82,7 @@ function handleRefresh() {
|
|||||||
/** 导出表格 */
|
/** 导出表格 */
|
||||||
async function handleExport() {
|
async function handleExport() {
|
||||||
const data = await exportProduct(searchParams.value);
|
const data = await exportProduct(searchParams.value);
|
||||||
await downloadFileFromBlobPart({ fileName: '产品列表.xls', source: data });
|
downloadFileFromBlobPart({ fileName: '产品列表.xls', source: data });
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 打开产品详情 */
|
/** 打开产品详情 */
|
||||||
@@ -117,12 +115,12 @@ function handleEdit(row: any) {
|
|||||||
/** 删除产品 */
|
/** 删除产品 */
|
||||||
async function handleDelete(row: any) {
|
async function handleDelete(row: any) {
|
||||||
const hideLoading = message.loading({
|
const hideLoading = message.loading({
|
||||||
content: `正在删除 ${row.name}...`,
|
content: $t('ui.actionMessage.deleting', [row.name]),
|
||||||
duration: 0,
|
duration: 0,
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
await deleteProduct(row.id);
|
await deleteProduct(row.id!);
|
||||||
message.success(`删除 ${row.name} 成功`);
|
message.success($t('ui.actionMessage.deleteSuccess'));
|
||||||
handleRefresh();
|
handleRefresh();
|
||||||
} finally {
|
} finally {
|
||||||
hideLoading();
|
hideLoading();
|
||||||
@@ -130,6 +128,7 @@ async function handleDelete(row: any) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const [Grid, gridApi] = useVbenVxeGrid({
|
const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
|
// TODO @haohao:这个不用,可以删除掉的
|
||||||
formOptions: {
|
formOptions: {
|
||||||
schema: [],
|
schema: [],
|
||||||
},
|
},
|
||||||
@@ -156,9 +155,10 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||||||
refresh: true,
|
refresh: true,
|
||||||
search: true,
|
search: true,
|
||||||
},
|
},
|
||||||
} as VxeTableGridOptions,
|
} as VxeTableGridOptions, // TODO @haohao:这里有个 <> 泛型
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/** 初始化 */
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
loadCategories();
|
loadCategories();
|
||||||
});
|
});
|
||||||
@@ -172,22 +172,24 @@ onMounted(() => {
|
|||||||
<Card :body-style="{ padding: '16px' }" class="mb-4">
|
<Card :body-style="{ padding: '16px' }" class="mb-4">
|
||||||
<!-- 搜索表单 -->
|
<!-- 搜索表单 -->
|
||||||
<div class="mb-3 flex items-center gap-3">
|
<div class="mb-3 flex items-center gap-3">
|
||||||
|
<!-- TODO @haohao:tindwind -->
|
||||||
<Input
|
<Input
|
||||||
v-model:value="searchParams.name"
|
v-model:value="searchParams.name"
|
||||||
placeholder="请输入产品名称"
|
placeholder="请输入产品名称"
|
||||||
allow-clear
|
allow-clear
|
||||||
style="width: 200px"
|
style="width: 220px"
|
||||||
@press-enter="handleSearch"
|
@press-enter="handleSearch"
|
||||||
>
|
>
|
||||||
<template #prefix>
|
<template #prefix>
|
||||||
<span class="text-gray-400">产品名称</span>
|
<span class="text-gray-400">产品名称</span>
|
||||||
</template>
|
</template>
|
||||||
</Input>
|
</Input>
|
||||||
|
<!-- TODO @haohao:tindwind -->
|
||||||
<Input
|
<Input
|
||||||
v-model:value="searchParams.productKey"
|
v-model:value="searchParams.productKey"
|
||||||
placeholder="请输入产品标识"
|
placeholder="请输入产品标识"
|
||||||
allow-clear
|
allow-clear
|
||||||
style="width: 200px"
|
style="width: 220px"
|
||||||
@press-enter="handleSearch"
|
@press-enter="handleSearch"
|
||||||
>
|
>
|
||||||
<template #prefix>
|
<template #prefix>
|
||||||
@@ -203,20 +205,20 @@ onMounted(() => {
|
|||||||
重置
|
重置
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 操作按钮 -->
|
<!-- 操作按钮 -->
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<Space :size="12">
|
<Space :size="12">
|
||||||
<Button type="primary" @click="handleCreate">
|
<Button type="primary" @click="handleCreate">
|
||||||
|
<!-- TODO @haohao:按钮使用中立的,ACTION_ICON.ADD -->
|
||||||
<IconifyIcon icon="ant-design:plus-outlined" class="mr-1" />
|
<IconifyIcon icon="ant-design:plus-outlined" class="mr-1" />
|
||||||
新增产品
|
新增产品
|
||||||
</Button>
|
</Button>
|
||||||
<Button type="primary" @click="handleExport">
|
<Button type="primary" @click="handleExport">
|
||||||
|
<!-- TODO @haohao:按钮使用中立的,ACTION_ICON.EXPORT -->
|
||||||
<IconifyIcon icon="ant-design:download-outlined" class="mr-1" />
|
<IconifyIcon icon="ant-design:download-outlined" class="mr-1" />
|
||||||
导出
|
导出
|
||||||
</Button>
|
</Button>
|
||||||
</Space>
|
</Space>
|
||||||
|
|
||||||
<!-- 视图切换 -->
|
<!-- 视图切换 -->
|
||||||
<Space :size="4">
|
<Space :size="4">
|
||||||
<Button
|
<Button
|
||||||
@@ -236,16 +238,17 @@ onMounted(() => {
|
|||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<Grid v-show="viewMode === 'list'">
|
<Grid v-show="viewMode === 'list'">
|
||||||
|
<!-- TODO @haohao:这里貌似可以删除掉 -->
|
||||||
<template #toolbar-tools>
|
<template #toolbar-tools>
|
||||||
<div></div>
|
<div></div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- 产品分类列 -->
|
<!-- 产品分类列 -->
|
||||||
|
<!-- TODO @haohao:这里应该可以拿到 data.ts,参考别的模块;类似 apps/web-antd/src/views/ai/image/manager/data.ts 里,里面查询 category ,和自己渲染-->
|
||||||
<template #category="{ row }">
|
<template #category="{ row }">
|
||||||
<span>{{ getCategoryNameByValue(row.categoryId) }}</span>
|
<span>{{ getCategoryNameByValue(row.categoryId) }}</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- 产品图标列 -->
|
<!-- 产品图标列 -->
|
||||||
|
<!-- TODO @haohao:直接用 Image 组件,就 ok 了呀。在 data.ts 里 -->
|
||||||
<template #icon="{ row }">
|
<template #icon="{ row }">
|
||||||
<Button
|
<Button
|
||||||
v-if="row.icon"
|
v-if="row.icon"
|
||||||
@@ -257,7 +260,7 @@ onMounted(() => {
|
|||||||
</Button>
|
</Button>
|
||||||
<span v-else class="text-gray-400">-</span>
|
<span v-else class="text-gray-400">-</span>
|
||||||
</template>
|
</template>
|
||||||
|
<!-- TODO @haohao:直接用 Image 组件,就 ok 了呀。在 data.ts 里 -->
|
||||||
<!-- 产品图片列 -->
|
<!-- 产品图片列 -->
|
||||||
<template #picUrl="{ row }">
|
<template #picUrl="{ row }">
|
||||||
<Button
|
<Button
|
||||||
@@ -270,8 +273,6 @@ onMounted(() => {
|
|||||||
</Button>
|
</Button>
|
||||||
<span v-else class="text-gray-400">-</span>
|
<span v-else class="text-gray-400">-</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- 操作列 -->
|
|
||||||
<template #actions="{ row }">
|
<template #actions="{ row }">
|
||||||
<TableAction
|
<TableAction
|
||||||
:actions="[
|
:actions="[
|
||||||
@@ -320,7 +321,9 @@ onMounted(() => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- 图片预览 -->
|
<!-- 图片预览 -->
|
||||||
|
<!-- TODO @haohao:tindwind -->
|
||||||
<div style="display: none">
|
<div style="display: none">
|
||||||
|
<!-- TODO @haohao:是不是通过 Image 直接实现预览 -->
|
||||||
<Image.PreviewGroup
|
<Image.PreviewGroup
|
||||||
:preview="{
|
:preview="{
|
||||||
visible: previewVisible,
|
visible: previewVisible,
|
||||||
@@ -333,6 +336,7 @@ onMounted(() => {
|
|||||||
</Page>
|
</Page>
|
||||||
</template>
|
</template>
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
/** TODO @haohao:貌似这 2 个 css 没啥用? */
|
||||||
:deep(.vxe-toolbar div) {
|
:deep(.vxe-toolbar div) {
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<!-- IoT 产品选择器,使用弹窗展示 -->
|
<!-- IoT 产品选择器,使用弹窗展示 -->
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
// TODO @haohao:这个貌似暂时没看到,在哪里用?
|
||||||
import type { IotProductApi } from '#/api/iot/product/product';
|
import type { IotProductApi } from '#/api/iot/product/product';
|
||||||
|
|
||||||
import { reactive, ref } from 'vue';
|
import { reactive, ref } from 'vue';
|
||||||
@@ -28,6 +29,7 @@ interface Props {
|
|||||||
|
|
||||||
const [Modal, modalApi] = useVbenModal({
|
const [Modal, modalApi] = useVbenModal({
|
||||||
title: '产品选择器',
|
title: '产品选择器',
|
||||||
|
// TODO @haohao:handleConfirm 直接放到这里,不用单独声明
|
||||||
onConfirm: handleConfirm,
|
onConfirm: handleConfirm,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -39,6 +41,7 @@ const queryParams = reactive({
|
|||||||
name: '',
|
name: '',
|
||||||
productKey: '',
|
productKey: '',
|
||||||
});
|
});
|
||||||
|
// TODO @haohao:是不是 form 应该也在 Grid 里;
|
||||||
|
|
||||||
// 配置表格
|
// 配置表格
|
||||||
const [Grid, gridApi] = useVbenVxeGrid({
|
const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { IotProductApi } from '#/api/iot/product/product';
|
import type { IotProductApi } from '#/api/iot/product/product';
|
||||||
|
|
||||||
|
// TODO @haohao:detail 挪到 yudao-ui-admin-vben-v5/apps/web-antd/src/views/iot/product/product/detail 下。独立一个,不放在 modules 里。
|
||||||
import { onMounted, provide, ref } from 'vue';
|
import { onMounted, provide, ref } from 'vue';
|
||||||
import { useRoute, useRouter } from 'vue-router';
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
|
|
||||||
@@ -12,8 +13,8 @@ import { getDeviceCount } from '#/api/iot/device/device';
|
|||||||
import { getProduct } from '#/api/iot/product/product';
|
import { getProduct } from '#/api/iot/product/product';
|
||||||
import IoTProductThingModel from '#/views/iot/thingmodel/index.vue';
|
import IoTProductThingModel from '#/views/iot/thingmodel/index.vue';
|
||||||
|
|
||||||
import ProductDetailsHeader from './ProductDetailsHeader.vue';
|
import ProductDetailsHeader from './product-details-header.vue';
|
||||||
import ProductDetailsInfo from './ProductDetailsInfo.vue';
|
import ProductDetailsInfo from './product-details-info.vue';
|
||||||
|
|
||||||
defineOptions({ name: 'IoTProductDetail' });
|
defineOptions({ name: 'IoTProductDetail' });
|
||||||
|
|
||||||
@@ -25,8 +26,7 @@ const loading = ref(true);
|
|||||||
const product = ref<IotProductApi.Product>({} as IotProductApi.Product);
|
const product = ref<IotProductApi.Product>({} as IotProductApi.Product);
|
||||||
const activeTab = ref('info');
|
const activeTab = ref('info');
|
||||||
|
|
||||||
// 提供产品信息给子组件
|
provide('product', product); // 提供产品信息给子组件
|
||||||
provide('product', product);
|
|
||||||
|
|
||||||
/** 获取产品详情 */
|
/** 获取产品详情 */
|
||||||
async function getProductData(productId: number) {
|
async function getProductData(productId: number) {
|
||||||
@@ -44,13 +44,8 @@ async function getProductData(productId: number) {
|
|||||||
async function getDeviceCountData(productId: number) {
|
async function getDeviceCountData(productId: number) {
|
||||||
try {
|
try {
|
||||||
return await getDeviceCount(productId);
|
return await getDeviceCount(productId);
|
||||||
} catch (error) {
|
} catch {
|
||||||
console.error(
|
message.error('获取设备数量失败');
|
||||||
'Error fetching device count:',
|
|
||||||
error,
|
|
||||||
'productId:',
|
|
||||||
productId,
|
|
||||||
);
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -62,7 +57,6 @@ onMounted(async () => {
|
|||||||
router.back();
|
router.back();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await getProductData(id);
|
await getProductData(id);
|
||||||
|
|
||||||
// 处理 tab 参数
|
// 处理 tab 参数
|
||||||
@@ -70,7 +64,6 @@ onMounted(async () => {
|
|||||||
if (tab) {
|
if (tab) {
|
||||||
activeTab.value = tab as string;
|
activeTab.value = tab as string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查询设备数量
|
// 查询设备数量
|
||||||
if (product.value.id) {
|
if (product.value.id) {
|
||||||
product.value.deviceCount = await getDeviceCountData(product.value.id);
|
product.value.deviceCount = await getDeviceCountData(product.value.id);
|
||||||
@@ -85,7 +78,6 @@ onMounted(async () => {
|
|||||||
:product="product"
|
:product="product"
|
||||||
@refresh="() => getProductData(id)"
|
@refresh="() => getProductData(id)"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Tabs v-model:active-key="activeTab" class="mt-4">
|
<Tabs v-model:active-key="activeTab" class="mt-4">
|
||||||
<Tabs.TabPane key="info" tab="产品信息">
|
<Tabs.TabPane key="info" tab="产品信息">
|
||||||
<ProductDetailsInfo v-if="activeTab === 'info'" :product="product" />
|
<ProductDetailsInfo v-if="activeTab === 'info'" :product="product" />
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
// TODO @haohao:放到 detail/modules 里。然后名字就是 header.vue
|
||||||
import type { IotProductApi } from '#/api/iot/product/product';
|
import type { IotProductApi } from '#/api/iot/product/product';
|
||||||
|
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
@@ -8,7 +9,7 @@ import { Button, Card, Descriptions, message } from 'ant-design-vue';
|
|||||||
|
|
||||||
import { updateProductStatus } from '#/api/iot/product/product';
|
import { updateProductStatus } from '#/api/iot/product/product';
|
||||||
|
|
||||||
import ProductForm from '../ProductForm.vue';
|
import ProductForm from '../product-form.vue';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
product: IotProductApi.Product;
|
product: IotProductApi.Product;
|
||||||
@@ -51,8 +52,9 @@ function openForm(type: string, id?: number) {
|
|||||||
|
|
||||||
/** 发布产品 */
|
/** 发布产品 */
|
||||||
async function confirmPublish(id: number) {
|
async function confirmPublish(id: number) {
|
||||||
|
// TODO @haohao:最好类似;async function handleDeleteBatch() { 的做法:1)有个 confirm;2)有个 loading
|
||||||
try {
|
try {
|
||||||
await updateProductStatus(id, 1);
|
await updateProductStatus(id, 1); // TODO @好好】:1 和 0,最好用枚举;
|
||||||
message.success('发布成功');
|
message.success('发布成功');
|
||||||
emit('refresh');
|
emit('refresh');
|
||||||
} catch {
|
} catch {
|
||||||
@@ -62,6 +64,7 @@ async function confirmPublish(id: number) {
|
|||||||
|
|
||||||
/** 撤销发布 */
|
/** 撤销发布 */
|
||||||
async function confirmUnpublish(id: number) {
|
async function confirmUnpublish(id: number) {
|
||||||
|
// TODO @haohao:最好类似;async function handleDeleteBatch() { 的做法:1)有个 confirm;2)有个 loading
|
||||||
try {
|
try {
|
||||||
await updateProductStatus(id, 0);
|
await updateProductStatus(id, 0);
|
||||||
message.success('撤销发布成功');
|
message.success('撤销发布成功');
|
||||||
@@ -126,6 +129,7 @@ async function confirmUnpublish(id: number) {
|
|||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<!-- 表单弹窗 -->
|
<!-- 表单弹窗 -->
|
||||||
|
<!-- TODO @haohao:弹不出来;另外,应该用 index.vue 里,Form 的声明方式哈。 -->
|
||||||
<ProductForm ref="formRef" @success="emit('refresh')" />
|
<ProductForm ref="formRef" @success="emit('refresh')" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
// TODO @haohao:放到 detail/modules 里。然后名字就是 info.vue
|
||||||
import type { IotProductApi } from '#/api/iot/product/product';
|
import type { IotProductApi } from '#/api/iot/product/product';
|
||||||
|
|
||||||
import { DICT_TYPE } from '@vben/constants';
|
import { DICT_TYPE } from '@vben/constants';
|
||||||
@@ -23,6 +24,7 @@ function formatDate(date?: Date | string) {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Card title="产品信息">
|
<Card title="产品信息">
|
||||||
|
<!-- TODO @haohao:看看是不是用 description 组件 -->
|
||||||
<Descriptions bordered :column="3" size="small">
|
<Descriptions bordered :column="3" size="small">
|
||||||
<Descriptions.Item label="产品名称">
|
<Descriptions.Item label="产品名称">
|
||||||
{{ product.name }}
|
{{ product.name }}
|
||||||
@@ -19,6 +19,8 @@ import {
|
|||||||
|
|
||||||
import { getProductPage } from '#/api/iot/product/product';
|
import { getProductPage } from '#/api/iot/product/product';
|
||||||
|
|
||||||
|
// TODO @haohao:应该是 card-view.vue;
|
||||||
|
|
||||||
// TODO @haohao:命名不太对;可以简化下;
|
// TODO @haohao:命名不太对;可以简化下;
|
||||||
defineOptions({ name: 'ProductCardView' });
|
defineOptions({ name: 'ProductCardView' });
|
||||||
|
|
||||||
@@ -48,6 +50,7 @@ const queryParams = ref({
|
|||||||
pageSize: 12,
|
pageSize: 12,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// TODO @haohao:注释的优化;
|
||||||
// 获取分类名称
|
// 获取分类名称
|
||||||
function getCategoryName(categoryId: number) {
|
function getCategoryName(categoryId: number) {
|
||||||
const category = props.categoryList.find((c: any) => c.id === categoryId);
|
const category = props.categoryList.find((c: any) => c.id === categoryId);
|
||||||
@@ -85,11 +88,6 @@ function getDeviceTypeColor(deviceType: number) {
|
|||||||
return colors[deviceType] || 'default';
|
return colors[deviceType] || 'default';
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
getList();
|
|
||||||
});
|
|
||||||
|
|
||||||
// 暴露方法供父组件调用
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
reload: getList,
|
reload: getList,
|
||||||
search: () => {
|
search: () => {
|
||||||
@@ -97,6 +95,11 @@ defineExpose({
|
|||||||
getList();
|
getList();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/** 初始化 */
|
||||||
|
onMounted(() => {
|
||||||
|
getList();
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -113,9 +116,11 @@ defineExpose({
|
|||||||
:lg="6"
|
:lg="6"
|
||||||
class="mb-4"
|
class="mb-4"
|
||||||
>
|
>
|
||||||
|
<!-- TODO @haohao:卡片之间的上下距离,太宽了。 -->
|
||||||
<Card :body-style="{ padding: '20px' }" class="product-card h-full">
|
<Card :body-style="{ padding: '20px' }" class="product-card h-full">
|
||||||
<!-- 顶部标题区域 -->
|
<!-- 顶部标题区域 -->
|
||||||
<div class="mb-4 flex items-start">
|
<div class="mb-4 flex items-start">
|
||||||
|
<!-- TODO @haohao:图标太大了;看看是不是参考 vue3 + element-plus 搞小点;然后标题居中。 -->
|
||||||
<div class="product-icon">
|
<div class="product-icon">
|
||||||
<IconifyIcon
|
<IconifyIcon
|
||||||
:icon="item.icon || 'ant-design:inbox-outlined'"
|
:icon="item.icon || 'ant-design:inbox-outlined'"
|
||||||
@@ -126,7 +131,6 @@ defineExpose({
|
|||||||
<div class="product-title">{{ item.name }}</div>
|
<div class="product-title">{{ item.name }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 内容区域 -->
|
<!-- 内容区域 -->
|
||||||
<div class="mb-4 flex items-start">
|
<div class="mb-4 flex items-start">
|
||||||
<div class="info-list flex-1">
|
<div class="info-list flex-1">
|
||||||
@@ -152,6 +156,7 @@ defineExpose({
|
|||||||
</div>
|
</div>
|
||||||
<div class="info-item">
|
<div class="info-item">
|
||||||
<span class="info-label">产品标识</span>
|
<span class="info-label">产品标识</span>
|
||||||
|
<!-- TODO @haohao:展示 ?有点奇怪,要不小手? -->
|
||||||
<Tooltip :title="item.productKey || item.id" placement="top">
|
<Tooltip :title="item.productKey || item.id" placement="top">
|
||||||
<span class="info-value product-key">
|
<span class="info-value product-key">
|
||||||
{{ item.productKey || item.id }}
|
{{ item.productKey || item.id }}
|
||||||
@@ -159,6 +164,8 @@ defineExpose({
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- TODO @haohao:这里是不是有 image?然后默认 icon -->
|
||||||
|
<!-- TODO @haohao:高度太高了。建议和左侧(产品分类 + 产品类型 + 产品标识)高度保持一致 -->
|
||||||
<div class="product-3d-icon">
|
<div class="product-3d-icon">
|
||||||
<IconifyIcon
|
<IconifyIcon
|
||||||
icon="ant-design:box-plot-outlined"
|
icon="ant-design:box-plot-outlined"
|
||||||
@@ -166,7 +173,6 @@ defineExpose({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 按钮组 -->
|
<!-- 按钮组 -->
|
||||||
<div class="action-buttons">
|
<div class="action-buttons">
|
||||||
<Button
|
<Button
|
||||||
@@ -174,6 +180,7 @@ defineExpose({
|
|||||||
class="action-btn action-btn-edit"
|
class="action-btn action-btn-edit"
|
||||||
@click="emit('edit', item)"
|
@click="emit('edit', item)"
|
||||||
>
|
>
|
||||||
|
<!-- TODO @haohao:按钮尽量用中立的按钮,方便迁移 ele; -->
|
||||||
<IconifyIcon icon="ant-design:edit-outlined" class="mr-1" />
|
<IconifyIcon icon="ant-design:edit-outlined" class="mr-1" />
|
||||||
编辑
|
编辑
|
||||||
</Button>
|
</Button>
|
||||||
@@ -229,13 +236,13 @@ defineExpose({
|
|||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<!-- 空状态 -->
|
<!-- 空状态 -->
|
||||||
<Empty v-else description="暂无产品数据" class="my-20" />
|
<Empty v-else description="暂无产品数据" class="my-20" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 分页 -->
|
<!-- 分页 -->
|
||||||
<div v-if="list.length > 0" class="mt-6 flex justify-center">
|
<!-- TODO @haohao:放到最右侧好点 -->
|
||||||
|
<div v-if="list.length > 0" class="flex justify-center">
|
||||||
<Pagination
|
<Pagination
|
||||||
v-model:current="queryParams.pageNo"
|
v-model:current="queryParams.pageNo"
|
||||||
v-model:page-size="queryParams.pageSize"
|
v-model:page-size="queryParams.pageSize"
|
||||||
@@ -251,6 +258,7 @@ defineExpose({
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
/** TODO @haohao:看看哪些可以 tindwind 掉 */
|
||||||
.product-card-view {
|
.product-card-view {
|
||||||
.product-card {
|
.product-card {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@@ -20,6 +20,8 @@ import {
|
|||||||
useBasicFormSchema,
|
useBasicFormSchema,
|
||||||
} from '../data';
|
} from '../data';
|
||||||
|
|
||||||
|
// TODO @haohao:应该是 form.vue;
|
||||||
|
|
||||||
defineOptions({ name: 'IoTProductForm' });
|
defineOptions({ name: 'IoTProductForm' });
|
||||||
|
|
||||||
const emit = defineEmits(['success']);
|
const emit = defineEmits(['success']);
|
||||||
@@ -30,10 +32,9 @@ const formData = ref<any>();
|
|||||||
const getTitle = computed(() => {
|
const getTitle = computed(() => {
|
||||||
return formData.value?.id ? '编辑产品' : '新增产品';
|
return formData.value?.id ? '编辑产品' : '新增产品';
|
||||||
});
|
});
|
||||||
|
const activeKey = ref<string[]>([]); // 折叠面板的激活 key,默认不展开
|
||||||
|
|
||||||
// 折叠面板的激活key,默认不展开
|
// TODO @haohao:每一行一个;
|
||||||
const activeKey = ref<string[]>([]);
|
|
||||||
|
|
||||||
const [Form, formApi] = useVbenForm({
|
const [Form, formApi] = useVbenForm({
|
||||||
commonConfig: {
|
commonConfig: {
|
||||||
componentProps: {
|
componentProps: {
|
||||||
@@ -46,7 +47,7 @@ const [Form, formApi] = useVbenForm({
|
|||||||
showDefaultActions: false,
|
showDefaultActions: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 创建高级设置表单
|
// TODO @haohao:每一行一个;
|
||||||
const [AdvancedForm, advancedFormApi] = useVbenForm({
|
const [AdvancedForm, advancedFormApi] = useVbenForm({
|
||||||
commonConfig: {
|
commonConfig: {
|
||||||
componentProps: {
|
componentProps: {
|
||||||
@@ -59,7 +60,7 @@ const [AdvancedForm, advancedFormApi] = useVbenForm({
|
|||||||
showDefaultActions: false,
|
showDefaultActions: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 在 formApi 创建后设置 schema
|
// TODO @haohao:看看是不是可以参考别的 form 模块,优化表单这块的逻辑;从 61 到 156 行。体感有点冗余、以及代码风格,不够统一;
|
||||||
formApi.setState({ schema: useBasicFormSchema(formApi) });
|
formApi.setState({ schema: useBasicFormSchema(formApi) });
|
||||||
advancedFormApi.setState({ schema: useAdvancedFormSchema() });
|
advancedFormApi.setState({ schema: useAdvancedFormSchema() });
|
||||||
|
|
||||||
@@ -67,12 +67,6 @@ export function useGridColumns(): VxeTableGridOptions['columns'] {
|
|||||||
title: '规则名称',
|
title: '规则名称',
|
||||||
minWidth: 150,
|
minWidth: 150,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
field: 'productId',
|
|
||||||
title: '所属产品',
|
|
||||||
minWidth: 150,
|
|
||||||
slots: { default: 'product' },
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
field: 'description',
|
field: 'description',
|
||||||
title: '规则描述',
|
title: '规则描述',
|
||||||
@@ -87,6 +81,7 @@ export function useGridColumns(): VxeTableGridOptions['columns'] {
|
|||||||
props: { type: DICT_TYPE.COMMON_STATUS },
|
props: { type: DICT_TYPE.COMMON_STATUS },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
// TODO @haohao:这里是【数据源】【数据目的】
|
||||||
{
|
{
|
||||||
field: 'sinkCount',
|
field: 'sinkCount',
|
||||||
title: '数据流转数',
|
title: '数据流转数',
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
// TODO @haohao:应该先有【规则】【目的】两个 tab;然后,在进行管理操作;类似,apps/web-antd/src/views/ai/chat/manager
|
||||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
|
|
||||||
import { Page, useVbenModal } from '@vben/common-ui';
|
import { Page, useVbenModal } from '@vben/common-ui';
|
||||||
@@ -10,7 +11,7 @@ import { deleteDataRule, getDataRulePage } from '#/api/iot/rule/data/rule';
|
|||||||
import { $t } from '#/locales';
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
import { useGridColumns, useGridFormSchema } from './data';
|
import { useGridColumns, useGridFormSchema } from './data';
|
||||||
import DataRuleForm from './rule/DataRuleForm.vue';
|
import DataRuleForm from './rule/data-rule-form.vue';
|
||||||
|
|
||||||
/** IoT 数据流转规则列表 */
|
/** IoT 数据流转规则列表 */
|
||||||
defineOptions({ name: 'IoTDataRule' });
|
defineOptions({ name: 'IoTDataRule' });
|
||||||
@@ -42,10 +43,8 @@ async function handleDelete(row: any) {
|
|||||||
duration: 0,
|
duration: 0,
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
await deleteDataRule(row.id);
|
await deleteDataRule(row.id!);
|
||||||
message.success({
|
message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
|
||||||
content: $t('ui.actionMessage.deleteSuccess', [row.name]),
|
|
||||||
});
|
|
||||||
handleRefresh();
|
handleRefresh();
|
||||||
} finally {
|
} finally {
|
||||||
hideLoading();
|
hideLoading();
|
||||||
|
|||||||
@@ -25,12 +25,11 @@ const formRules: any = reactive({
|
|||||||
});
|
});
|
||||||
const formRef = ref(); // 表单 Ref
|
const formRef = ref(); // 表单 Ref
|
||||||
|
|
||||||
// 获取上行消息方法列表
|
|
||||||
const upstreamMethods = computed(() => {
|
const upstreamMethods = computed(() => {
|
||||||
return Object.values(IotDeviceMessageMethodEnum).filter(
|
return Object.values(IotDeviceMessageMethodEnum).filter(
|
||||||
(item) => item.upstream,
|
(item) => item.upstream,
|
||||||
);
|
);
|
||||||
});
|
}); // 获取上行消息方法列表
|
||||||
|
|
||||||
/** 根据产品 ID 过滤设备 */
|
/** 根据产品 ID 过滤设备 */
|
||||||
function getFilteredDevices(productId: number) {
|
function getFilteredDevices(productId: number) {
|
||||||
@@ -193,7 +192,7 @@ const columns = [
|
|||||||
{
|
{
|
||||||
title: '操作',
|
title: '操作',
|
||||||
width: 80,
|
width: 80,
|
||||||
fixed: 'right' as const,
|
fixed: 'right',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -202,6 +201,8 @@ defineExpose({ validate, getData, setData });
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Form ref="formRef" :model="{ data: formData }">
|
<Form ref="formRef" :model="{ data: formData }">
|
||||||
|
<!-- TODO @haohao:貌似有告警。 -->
|
||||||
|
<!-- TODO @haohao:是不是搞成 web-antd/src/views/erp/finance/receipt/modules/item-form.vue 的做法,通过 Grid;或 apps/web-antd/src/views/infra/demo/demo03/erp/modules/demo03-grade-list.vue;目的:后续 ele 通用性更好! -->
|
||||||
<Table
|
<Table
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
:data-source="formData"
|
:data-source="formData"
|
||||||
@@ -14,13 +14,14 @@ import {
|
|||||||
import { getDataSinkSimpleList } from '#/api/iot/rule/data/sink';
|
import { getDataSinkSimpleList } from '#/api/iot/rule/data/sink';
|
||||||
import { $t } from '#/locales';
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
import SourceConfigForm from './components/SourceConfigForm.vue';
|
import SourceConfigForm from './components/source-config-form.vue';
|
||||||
import { useRuleFormSchema } from './data';
|
import { useRuleFormSchema } from './data';
|
||||||
|
|
||||||
const emit = defineEmits(['success']);
|
const emit = defineEmits(['success']);
|
||||||
const formData = ref<any>();
|
const formData = ref<any>();
|
||||||
const sourceConfigRef = ref();
|
const sourceConfigRef = ref();
|
||||||
|
|
||||||
|
// TODO @haohao:应该放到 modules
|
||||||
const getTitle = computed(() => {
|
const getTitle = computed(() => {
|
||||||
return formData.value?.id
|
return formData.value?.id
|
||||||
? $t('ui.actionTitle.edit', ['数据规则'])
|
? $t('ui.actionTitle.edit', ['数据规则'])
|
||||||
@@ -40,6 +41,7 @@ const [Form, formApi] = useVbenForm({
|
|||||||
showDefaultActions: false,
|
showDefaultActions: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// TODO @haohao:这里需要优化下,参考别的模块写法;
|
||||||
const [Modal, modalApi] = useVbenModal({
|
const [Modal, modalApi] = useVbenModal({
|
||||||
async onConfirm() {
|
async onConfirm() {
|
||||||
const { valid } = await formApi.validate();
|
const { valid } = await formApi.validate();
|
||||||
@@ -10,7 +10,9 @@ import { deleteDataRule, getDataRulePage } from '#/api/iot/rule/data/rule';
|
|||||||
import { $t } from '#/locales';
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
import { useGridColumns, useGridFormSchema } from './data';
|
import { useGridColumns, useGridFormSchema } from './data';
|
||||||
import DataRuleForm from './DataRuleForm.vue';
|
import DataRuleForm from './data-rule-form.vue';
|
||||||
|
|
||||||
|
// TODO @haohao:貌似和 apps/web-antd/src/views/iot/rule/data/index.vue 重复的。可能这个是对的。然后把 apps/web-antd/src/views/iot/rule/data/index.vue 搞成 tabs;
|
||||||
|
|
||||||
/** IoT 数据流转规则列表 */
|
/** IoT 数据流转规则列表 */
|
||||||
defineOptions({ name: 'IotDataRule' });
|
defineOptions({ name: 'IotDataRule' });
|
||||||
@@ -43,9 +45,7 @@ async function handleDelete(row: any) {
|
|||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
await deleteDataRule(row.id);
|
await deleteDataRule(row.id);
|
||||||
message.success({
|
message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
|
||||||
content: $t('ui.actionMessage.deleteSuccess', [row.name]),
|
|
||||||
});
|
|
||||||
handleRefresh();
|
handleRefresh();
|
||||||
} finally {
|
} finally {
|
||||||
hideLoading();
|
hideLoading();
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { isEmpty } from '@vben/utils';
|
|||||||
import { useVModel } from '@vueuse/core';
|
import { useVModel } from '@vueuse/core';
|
||||||
import { FormItem, Input, Select } from 'ant-design-vue';
|
import { FormItem, Input, Select } from 'ant-design-vue';
|
||||||
|
|
||||||
import KeyValueEditor from './components/KeyValueEditor.vue';
|
import KeyValueEditor from './components/key-value-editor.vue';
|
||||||
|
|
||||||
defineOptions({ name: 'HttpConfigForm' });
|
defineOptions({ name: 'HttpConfigForm' });
|
||||||
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
export { default as HttpConfigForm } from './HttpConfigForm.vue';
|
export {default as HttpConfigForm} from './http-config-form.vue';
|
||||||
export { default as KafkaMQConfigForm } from './KafkaMQConfigForm.vue';
|
export {default as KafkaMqConfigForm} from './kafka-mq-config-form.vue';
|
||||||
export { default as MqttConfigForm } from './MqttConfigForm.vue';
|
export {default as MqttConfigForm} from './mqtt-config-form.vue';
|
||||||
export { default as RabbitMQConfigForm } from './RabbitMQConfigForm.vue';
|
export {default as RabbitMqConfigForm} from './rabbit-mq-config-form.vue';
|
||||||
export { default as RedisStreamConfigForm } from './RedisStreamConfigForm.vue';
|
export {default as RedisStreamConfigForm} from './redis-stream-config-form.vue';
|
||||||
export { default as RocketMQConfigForm } from './RocketMQConfigForm.vue';
|
export {default as RocketMqConfigForm} from './rocket-mq-config-form.vue';
|
||||||
|
|||||||
@@ -15,11 +15,11 @@ import { $t } from '#/locales';
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
HttpConfigForm,
|
HttpConfigForm,
|
||||||
KafkaMQConfigForm,
|
KafkaMqConfigForm,
|
||||||
MqttConfigForm,
|
MqttConfigForm,
|
||||||
RabbitMQConfigForm,
|
RabbitMqConfigForm,
|
||||||
RedisStreamConfigForm,
|
RedisStreamConfigForm,
|
||||||
RocketMQConfigForm,
|
RocketMqConfigForm,
|
||||||
} from './config';
|
} from './config';
|
||||||
import { useSinkFormSchema } from './data';
|
import { useSinkFormSchema } from './data';
|
||||||
|
|
||||||
@@ -120,6 +120,7 @@ watch(
|
|||||||
<Form class="mx-4" />
|
<Form class="mx-4" />
|
||||||
<div v-if="formData" class="mx-4 mt-4">
|
<div v-if="formData" class="mx-4 mt-4">
|
||||||
<div class="mb-2 text-sm font-medium">配置信息</div>
|
<div class="mb-2 text-sm font-medium">配置信息</div>
|
||||||
|
<!-- TODO @haohao:下面的 form,看看有没办法,搞成 form schema 的,方便后续 ele 的迁移! -->
|
||||||
<HttpConfigForm
|
<HttpConfigForm
|
||||||
v-if="IotDataSinkTypeEnum.HTTP === formData.type"
|
v-if="IotDataSinkTypeEnum.HTTP === formData.type"
|
||||||
v-model="formData.config"
|
v-model="formData.config"
|
||||||
@@ -128,15 +129,15 @@ watch(
|
|||||||
v-if="IotDataSinkTypeEnum.MQTT === formData.type"
|
v-if="IotDataSinkTypeEnum.MQTT === formData.type"
|
||||||
v-model="formData.config"
|
v-model="formData.config"
|
||||||
/>
|
/>
|
||||||
<RocketMQConfigForm
|
<RocketMqConfigForm
|
||||||
v-if="IotDataSinkTypeEnum.ROCKETMQ === formData.type"
|
v-if="IotDataSinkTypeEnum.ROCKETMQ === formData.type"
|
||||||
v-model="formData.config"
|
v-model="formData.config"
|
||||||
/>
|
/>
|
||||||
<KafkaMQConfigForm
|
<KafkaMqConfigForm
|
||||||
v-if="IotDataSinkTypeEnum.KAFKA === formData.type"
|
v-if="IotDataSinkTypeEnum.KAFKA === formData.type"
|
||||||
v-model="formData.config"
|
v-model="formData.config"
|
||||||
/>
|
/>
|
||||||
<RabbitMQConfigForm
|
<RabbitMqConfigForm
|
||||||
v-if="IotDataSinkTypeEnum.RABBITMQ === formData.type"
|
v-if="IotDataSinkTypeEnum.RABBITMQ === formData.type"
|
||||||
v-model="formData.config"
|
v-model="formData.config"
|
||||||
/>
|
/>
|
||||||
@@ -10,9 +10,11 @@ import { deleteDataSink, getDataSinkPage } from '#/api/iot/rule/data/sink';
|
|||||||
import { $t } from '#/locales';
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
import { useGridColumns, useGridFormSchema } from './data';
|
import { useGridColumns, useGridFormSchema } from './data';
|
||||||
import DataSinkForm from './DataSinkForm.vue';
|
import DataSinkForm from './data-sink-form.vue';
|
||||||
|
|
||||||
/** IoT 数据流转目的 列表 */
|
// TODO @haohao:需要根据代码规范,在优化下这个模块。和别的模块的风格保持一致。
|
||||||
|
|
||||||
|
/** IoT 数据流转目的列表 */
|
||||||
defineOptions({ name: 'IotDataSink' });
|
defineOptions({ name: 'IotDataSink' });
|
||||||
|
|
||||||
const [FormModal, formModalApi] = useVbenModal({
|
const [FormModal, formModalApi] = useVbenModal({
|
||||||
@@ -43,9 +45,7 @@ async function handleDelete(row: any) {
|
|||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
await deleteDataSink(row.id);
|
await deleteDataSink(row.id);
|
||||||
message.success({
|
message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
|
||||||
content: $t('ui.actionMessage.deleteSuccess', [row.name]),
|
|
||||||
});
|
|
||||||
handleRefresh();
|
handleRefresh();
|
||||||
} finally {
|
} finally {
|
||||||
hideLoading();
|
hideLoading();
|
||||||
|
|||||||
@@ -1,44 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
import { Page } from '@vben/common-ui';
|
|
||||||
|
|
||||||
import { Button } from 'ant-design-vue';
|
|
||||||
|
|
||||||
defineOptions({ name: 'IotRuleDataBridge' });
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Page description="物聯網規則引擎 - 數據橋接" title="數據橋接">
|
|
||||||
<div class="p-4">
|
|
||||||
<Button
|
|
||||||
danger
|
|
||||||
type="link"
|
|
||||||
target="_blank"
|
|
||||||
href="https://github.com/yudaocode/yudao-ui-admin-vue3/tree/master/src/views/iot/rule"
|
|
||||||
>
|
|
||||||
該功能支持 Vue3 + element-plus 版本!
|
|
||||||
</Button>
|
|
||||||
<br />
|
|
||||||
<Button
|
|
||||||
type="link"
|
|
||||||
target="_blank"
|
|
||||||
href="https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/iot/rule/data/rule/index.vue"
|
|
||||||
>
|
|
||||||
可參考源代碼進行遷移!
|
|
||||||
</Button>
|
|
||||||
<div class="mt-4">
|
|
||||||
<h3>功能說明:</h3>
|
|
||||||
<p>規則引擎包括:</p>
|
|
||||||
<ul>
|
|
||||||
<li>數據規則配置</li>
|
|
||||||
<li>數據轉發配置</li>
|
|
||||||
<li>場景聯動配置</li>
|
|
||||||
</ul>
|
|
||||||
<h3 class="mt-4">待實現:</h3>
|
|
||||||
<ul>
|
|
||||||
<li>⚠️ API 接口定義</li>
|
|
||||||
<li>⚠️ 頁面實現</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Page>
|
|
||||||
</template>
|
|
||||||
@@ -14,12 +14,12 @@ import {
|
|||||||
IotRuleSceneTriggerConditionTypeEnum,
|
IotRuleSceneTriggerConditionTypeEnum,
|
||||||
} from '#/views/iot/utils/constants';
|
} from '#/views/iot/utils/constants';
|
||||||
|
|
||||||
import ValueInput from '../inputs/ValueInput.vue';
|
import ValueInput from '../inputs/value-input.vue';
|
||||||
import DeviceSelector from '../selectors/DeviceSelector.vue';
|
import DeviceSelector from '../selectors/device-selector.vue';
|
||||||
import OperatorSelector from '../selectors/OperatorSelector.vue';
|
import OperatorSelector from '../selectors/operator-selector.vue';
|
||||||
import ProductSelector from '../selectors/ProductSelector.vue';
|
import ProductSelector from '../selectors/product-selector.vue';
|
||||||
import PropertySelector from '../selectors/PropertySelector.vue';
|
import PropertySelector from '../selectors/property-selector.vue';
|
||||||
import CurrentTimeConditionConfig from './CurrentTimeConditionConfig.vue';
|
import CurrentTimeConditionConfig from './current-time-condition-config.vue';
|
||||||
|
|
||||||
/** 单个条件配置组件 */
|
/** 单个条件配置组件 */
|
||||||
defineOptions({ name: 'ConditionConfig' });
|
defineOptions({ name: 'ConditionConfig' });
|
||||||
@@ -20,9 +20,9 @@ import {
|
|||||||
IoTThingModelAccessModeEnum,
|
IoTThingModelAccessModeEnum,
|
||||||
} from '#/views/iot/utils/constants';
|
} from '#/views/iot/utils/constants';
|
||||||
|
|
||||||
import JsonParamsInput from '../inputs/JsonParamsInput.vue';
|
import JsonParamsInput from '../inputs/json-params-input.vue';
|
||||||
import DeviceSelector from '../selectors/DeviceSelector.vue';
|
import DeviceSelector from '../selectors/device-selector.vue';
|
||||||
import ProductSelector from '../selectors/ProductSelector.vue';
|
import ProductSelector from '../selectors/product-selector.vue';
|
||||||
|
|
||||||
/** 设备控制配置组件 */
|
/** 设备控制配置组件 */
|
||||||
defineOptions({ name: 'DeviceControlConfig' });
|
defineOptions({ name: 'DeviceControlConfig' });
|
||||||
@@ -9,8 +9,8 @@ import { IconifyIcon } from '@vben/icons';
|
|||||||
import { useVModel } from '@vueuse/core';
|
import { useVModel } from '@vueuse/core';
|
||||||
import { Button, Tag } from 'ant-design-vue';
|
import { Button, Tag } from 'ant-design-vue';
|
||||||
|
|
||||||
import MainConditionInnerConfig from './MainConditionInnerConfig.vue';
|
import MainConditionInnerConfig from './main-condition-inner-config.vue';
|
||||||
import SubConditionGroupConfig from './SubConditionGroupConfig.vue';
|
import SubConditionGroupConfig from './sub-condition-group-config.vue';
|
||||||
|
|
||||||
/** 设备触发配置组件 */
|
/** 设备触发配置组件 */
|
||||||
defineOptions({ name: 'DeviceTriggerConfig' });
|
defineOptions({ name: 'DeviceTriggerConfig' });
|
||||||
@@ -14,12 +14,12 @@ import {
|
|||||||
triggerTypeOptions,
|
triggerTypeOptions,
|
||||||
} from '#/views/iot/utils/constants';
|
} from '#/views/iot/utils/constants';
|
||||||
|
|
||||||
import JsonParamsInput from '../inputs/JsonParamsInput.vue';
|
import JsonParamsInput from '../inputs/json-params-input.vue';
|
||||||
import ValueInput from '../inputs/ValueInput.vue';
|
import ValueInput from '../inputs/value-input.vue';
|
||||||
import DeviceSelector from '../selectors/DeviceSelector.vue';
|
import DeviceSelector from '../selectors/device-selector.vue';
|
||||||
import OperatorSelector from '../selectors/OperatorSelector.vue';
|
import OperatorSelector from '../selectors/operator-selector.vue';
|
||||||
import ProductSelector from '../selectors/ProductSelector.vue';
|
import ProductSelector from '../selectors/product-selector.vue';
|
||||||
import PropertySelector from '../selectors/PropertySelector.vue';
|
import PropertySelector from '../selectors/property-selector.vue';
|
||||||
|
|
||||||
/** 主条件内部配置组件 */
|
/** 主条件内部配置组件 */
|
||||||
defineOptions({ name: 'MainConditionInnerConfig' });
|
defineOptions({ name: 'MainConditionInnerConfig' });
|
||||||
@@ -13,7 +13,7 @@ import {
|
|||||||
IotRuleSceneTriggerConditionTypeEnum,
|
IotRuleSceneTriggerConditionTypeEnum,
|
||||||
} from '#/views/iot/utils/constants';
|
} from '#/views/iot/utils/constants';
|
||||||
|
|
||||||
import ConditionConfig from './ConditionConfig.vue';
|
import ConditionConfig from './condition-config.vue';
|
||||||
|
|
||||||
/** 子条件组配置组件 */
|
/** 子条件组配置组件 */
|
||||||
defineOptions({ name: 'SubConditionGroupConfig' });
|
defineOptions({ name: 'SubConditionGroupConfig' });
|
||||||
@@ -16,9 +16,9 @@ import {
|
|||||||
isDeviceTrigger,
|
isDeviceTrigger,
|
||||||
} from '#/views/iot/utils/constants';
|
} from '#/views/iot/utils/constants';
|
||||||
|
|
||||||
import ActionSection from './sections/ActionSection.vue';
|
import ActionSection from './sections/action-section.vue';
|
||||||
import BasicInfoSection from './sections/BasicInfoSection.vue';
|
import BasicInfoSection from './sections/basic-info-section.vue';
|
||||||
import TriggerSection from './sections/TriggerSection.vue';
|
import TriggerSection from './sections/trigger-section.vue';
|
||||||
|
|
||||||
/** IoT 场景联动规则表单 - 主表单组件 */
|
/** IoT 场景联动规则表单 - 主表单组件 */
|
||||||
defineOptions({ name: 'RuleSceneForm' });
|
defineOptions({ name: 'RuleSceneForm' });
|
||||||
@@ -13,8 +13,8 @@ import {
|
|||||||
IotRuleSceneActionTypeEnum,
|
IotRuleSceneActionTypeEnum,
|
||||||
} from '#/views/iot/utils/constants';
|
} from '#/views/iot/utils/constants';
|
||||||
|
|
||||||
import AlertConfig from '../configs/AlertConfig.vue';
|
import AlertConfig from '../configs/alert-config.vue';
|
||||||
import DeviceControlConfig from '../configs/DeviceControlConfig.vue';
|
import DeviceControlConfig from '../configs/device-control-config.vue';
|
||||||
|
|
||||||
/** 执行器配置组件 */
|
/** 执行器配置组件 */
|
||||||
defineOptions({ name: 'ActionSection' });
|
defineOptions({ name: 'ActionSection' });
|
||||||
@@ -8,6 +8,7 @@ import { IconifyIcon } from '@vben/icons';
|
|||||||
|
|
||||||
import { useVModel } from '@vueuse/core';
|
import { useVModel } from '@vueuse/core';
|
||||||
import { Card, Col, Form, Input, Radio, Row } from 'ant-design-vue';
|
import { Card, Col, Form, Input, Radio, Row } from 'ant-design-vue';
|
||||||
|
import { DictTag } from "#/components/dict-tag";
|
||||||
|
|
||||||
/** 基础信息配置组件 */
|
/** 基础信息配置组件 */
|
||||||
defineOptions({ name: 'BasicInfoSection' });
|
defineOptions({ name: 'BasicInfoSection' });
|
||||||
@@ -15,7 +15,7 @@ import {
|
|||||||
isDeviceTrigger,
|
isDeviceTrigger,
|
||||||
} from '#/views/iot/utils/constants';
|
} from '#/views/iot/utils/constants';
|
||||||
|
|
||||||
import DeviceTriggerConfig from '../configs/DeviceTriggerConfig.vue';
|
import DeviceTriggerConfig from '../configs/device-trigger-config.vue';
|
||||||
|
|
||||||
/** 触发器配置组件 */
|
/** 触发器配置组件 */
|
||||||
defineOptions({ name: 'TriggerSection' });
|
defineOptions({ name: 'TriggerSection' });
|
||||||
@@ -7,6 +7,7 @@ import { DICT_TYPE } from '@vben/constants';
|
|||||||
import { Select } from 'ant-design-vue';
|
import { Select } from 'ant-design-vue';
|
||||||
|
|
||||||
import { getSimpleProductList } from '#/api/iot/product/product';
|
import { getSimpleProductList } from '#/api/iot/product/product';
|
||||||
|
import { DictTag } from "#/components/dict-tag";
|
||||||
|
|
||||||
/** 产品选择器组件 */
|
/** 产品选择器组件 */
|
||||||
defineOptions({ name: 'ProductSelector' });
|
defineOptions({ name: 'ProductSelector' });
|
||||||
@@ -14,8 +14,8 @@ import { deleteThingModel, getThingModelPage } from '#/api/iot/thingmodel';
|
|||||||
import { getDataTypeOptionsLabel, IOT_PROVIDE_KEY } from '../utils/constants';
|
import { getDataTypeOptionsLabel, IOT_PROVIDE_KEY } from '../utils/constants';
|
||||||
import { useGridColumns, useGridFormSchema } from './data';
|
import { useGridColumns, useGridFormSchema } from './data';
|
||||||
import { DataDefinition } from './modules/components';
|
import { DataDefinition } from './modules/components';
|
||||||
import ThingModelForm from './modules/ThingModelForm.vue';
|
import ThingModelForm from './modules/thing-model-form.vue';
|
||||||
import ThingModelTSL from './modules/ThingModelTSL.vue';
|
import ThingModelTsl from './modules/thing-model-tsl.vue';
|
||||||
|
|
||||||
defineOptions({ name: 'IoTThingModel' });
|
defineOptions({ name: 'IoTThingModel' });
|
||||||
|
|
||||||
@@ -23,16 +23,65 @@ const props = defineProps<{
|
|||||||
productId: number;
|
productId: number;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
// 产品信息
|
const product = ref<IotProductApi.Product>({} as IotProductApi.Product); // 产品信息
|
||||||
const product = ref<IotProductApi.Product>({} as IotProductApi.Product);
|
|
||||||
|
|
||||||
// 提供产品信息给子组件
|
provide(IOT_PROVIDE_KEY.PRODUCT, product); // 提供产品信息给子组件
|
||||||
provide(IOT_PROVIDE_KEY.PRODUCT, product);
|
|
||||||
|
|
||||||
// 组件引用
|
// TODO @haohao:form 是不是用 web-antd/src/views/system/user/index.vue 里 open 的风格;
|
||||||
const thingModelFormRef = ref();
|
const thingModelFormRef = ref();
|
||||||
|
// TODO @haohao:thingModelTSLRef 应该是个 modal,也可以调整下风格;
|
||||||
const thingModelTSLRef = ref();
|
const thingModelTSLRef = ref();
|
||||||
|
|
||||||
|
// TODO @haohao:方法的顺序、注释、调整的和别的模块一致。
|
||||||
|
|
||||||
|
// 新增功能
|
||||||
|
function handleCreate() {
|
||||||
|
thingModelFormRef.value?.open('create');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 编辑功能
|
||||||
|
function handleEdit(row: any) {
|
||||||
|
thingModelFormRef.value?.open('update', row.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除功能
|
||||||
|
async function handleDelete(row: any) {
|
||||||
|
// TODO @haohao:应该有个 loading,类似别的模块写法;
|
||||||
|
try {
|
||||||
|
await deleteThingModel(row.id);
|
||||||
|
message.success('删除成功');
|
||||||
|
gridApi.reload();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('删除失败:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 打开 TSL
|
||||||
|
function handleOpenTSL() {
|
||||||
|
thingModelTSLRef.value?.open();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取数据类型标签
|
||||||
|
// TODO @haohao:可以直接在 data.ts 就写掉这个逻辑;
|
||||||
|
function getDataTypeLabel(row: any) {
|
||||||
|
return getDataTypeOptionsLabel(row.property?.dataType) || '-';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 刷新表格
|
||||||
|
function handleRefresh() {
|
||||||
|
gridApi.reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取产品信息
|
||||||
|
async function getProductData() {
|
||||||
|
try {
|
||||||
|
product.value = await getProduct(props.productId);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取产品信息失败:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO @haohao:字段的顺序,调整成别的模块一直;
|
||||||
const [Grid, gridApi] = useVbenVxeGrid({
|
const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
gridOptions: {
|
gridOptions: {
|
||||||
columns: useGridColumns(),
|
columns: useGridColumns(),
|
||||||
@@ -64,51 +113,6 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// 新增功能
|
|
||||||
function handleCreate() {
|
|
||||||
thingModelFormRef.value?.open('create');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 编辑功能
|
|
||||||
function handleEdit(row: any) {
|
|
||||||
thingModelFormRef.value?.open('update', row.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 删除功能
|
|
||||||
async function handleDelete(row: any) {
|
|
||||||
try {
|
|
||||||
await deleteThingModel(row.id);
|
|
||||||
message.success('删除成功');
|
|
||||||
gridApi.reload();
|
|
||||||
} catch (error) {
|
|
||||||
console.error('删除失败:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 打开 TSL
|
|
||||||
function handleOpenTSL() {
|
|
||||||
thingModelTSLRef.value?.open();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取数据类型标签
|
|
||||||
function getDataTypeLabel(row: any) {
|
|
||||||
return getDataTypeOptionsLabel(row.property?.dataType) || '-';
|
|
||||||
}
|
|
||||||
|
|
||||||
// 刷新表格
|
|
||||||
function handleRefresh() {
|
|
||||||
gridApi.reload();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取产品信息
|
|
||||||
async function getProductData() {
|
|
||||||
try {
|
|
||||||
product.value = await getProduct(props.productId);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取产品信息失败:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 初始化
|
// 初始化
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await getProductData();
|
await getProductData();
|
||||||
@@ -116,11 +120,7 @@ onMounted(async () => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Page
|
<Page auto-content-height>
|
||||||
auto-content-height
|
|
||||||
description="管理产品的物模型定义,包括属性、服务和事件"
|
|
||||||
title="物模型管理"
|
|
||||||
>
|
|
||||||
<Grid>
|
<Grid>
|
||||||
<template #toolbar-tools>
|
<template #toolbar-tools>
|
||||||
<TableAction
|
<TableAction
|
||||||
@@ -134,23 +134,21 @@ onMounted(async () => {
|
|||||||
{
|
{
|
||||||
label: 'TSL',
|
label: 'TSL',
|
||||||
type: 'default',
|
type: 'default',
|
||||||
color: 'success',
|
color: 'success', // TODO @haohao:貌似 color 可以去掉?应该是不生效的哈。ps:另外,也给搞个 icon?
|
||||||
onClick: handleOpenTSL,
|
onClick: handleOpenTSL,
|
||||||
},
|
},
|
||||||
]"
|
]"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- 数据类型列 -->
|
<!-- 数据类型列 -->
|
||||||
<template #dataType="{ row }">
|
<template #dataType="{ row }">
|
||||||
<span>{{ getDataTypeLabel(row) }}</span>
|
<span>{{ getDataTypeLabel(row) }}</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- 数据定义列 -->
|
<!-- 数据定义列 -->
|
||||||
|
<!-- TODO @haohao:可以在 data.ts 就写掉这个逻辑; -->
|
||||||
<template #dataDefinition="{ row }">
|
<template #dataDefinition="{ row }">
|
||||||
<DataDefinition :data="row" />
|
<DataDefinition :data="row" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- 操作列 -->
|
<!-- 操作列 -->
|
||||||
<template #actions="{ row }">
|
<template #actions="{ row }">
|
||||||
<TableAction
|
<TableAction
|
||||||
@@ -178,8 +176,7 @@ onMounted(async () => {
|
|||||||
|
|
||||||
<!-- 物模型表单 -->
|
<!-- 物模型表单 -->
|
||||||
<ThingModelForm ref="thingModelFormRef" @success="handleRefresh" />
|
<ThingModelForm ref="thingModelFormRef" @success="handleRefresh" />
|
||||||
|
|
||||||
<!-- TSL 弹窗 -->
|
<!-- TSL 弹窗 -->
|
||||||
<ThingModelTSL ref="thingModelTSLRef" />
|
<ThingModelTsl ref="thingModelTSLRef" />
|
||||||
</Page>
|
</Page>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
<!-- TODO @haohao:如果是模块内用的,就用 modules 里。(等后面点在看,优先级:低) -->
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { ThingModelData } from '#/api/iot/thingmodel';
|
import type { ThingModelData } from '#/api/iot/thingmodel';
|
||||||
|
|
||||||
@@ -17,7 +18,6 @@ defineOptions({ name: 'DataDefinition' });
|
|||||||
|
|
||||||
const props = defineProps<{ data: ThingModelData }>();
|
const props = defineProps<{ data: ThingModelData }>();
|
||||||
|
|
||||||
// 格式化布尔值和枚举值列表为字符串
|
|
||||||
const formattedDataSpecsList = computed(() => {
|
const formattedDataSpecsList = computed(() => {
|
||||||
if (
|
if (
|
||||||
!props.data.property?.dataSpecsList ||
|
!props.data.property?.dataSpecsList ||
|
||||||
@@ -28,9 +28,8 @@ const formattedDataSpecsList = computed(() => {
|
|||||||
return props.data.property.dataSpecsList
|
return props.data.property.dataSpecsList
|
||||||
.map((item) => `${item.value}-${item.name}`)
|
.map((item) => `${item.value}-${item.name}`)
|
||||||
.join('、');
|
.join('、');
|
||||||
});
|
}); // 格式化布尔值和枚举值列表为字符串
|
||||||
|
|
||||||
// 显示的简短文本(第一个值)
|
|
||||||
const shortText = computed(() => {
|
const shortText = computed(() => {
|
||||||
if (
|
if (
|
||||||
!props.data.property?.dataSpecsList ||
|
!props.data.property?.dataSpecsList ||
|
||||||
@@ -43,7 +42,7 @@ const shortText = computed(() => {
|
|||||||
return count > 1
|
return count > 1
|
||||||
? `${first.value}-${first.name} 等${count}项`
|
? `${first.value}-${first.name} 等${count}项`
|
||||||
: `${first.value}-${first.name}`;
|
: `${first.value}-${first.name}`;
|
||||||
});
|
}); // 显示的简短文本(第一个值)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -100,9 +99,8 @@ const shortText = computed(() => {
|
|||||||
</template>
|
</template>
|
||||||
<!-- 服务 -->
|
<!-- 服务 -->
|
||||||
<div v-if="Number(data.type) === IoTThingModelTypeEnum.SERVICE">
|
<div v-if="Number(data.type) === IoTThingModelTypeEnum.SERVICE">
|
||||||
调用方式:{{
|
调用方式:
|
||||||
getThingModelServiceCallTypeLabel(data.service?.callType as any)
|
{{ getThingModelServiceCallTypeLabel(data.service?.callType as any) }}
|
||||||
}}
|
|
||||||
</div>
|
</div>
|
||||||
<!-- 事件 -->
|
<!-- 事件 -->
|
||||||
<div v-if="Number(data.type) === IoTThingModelTypeEnum.EVENT">
|
<div v-if="Number(data.type) === IoTThingModelTypeEnum.EVENT">
|
||||||
@@ -111,6 +109,7 @@ const shortText = computed(() => {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
/** TODO @haohao:tindwind */
|
||||||
.data-specs-text {
|
.data-specs-text {
|
||||||
cursor: help;
|
cursor: help;
|
||||||
border-bottom: 1px dashed #d9d9d9;
|
border-bottom: 1px dashed #d9d9d9;
|
||||||
@@ -1 +1 @@
|
|||||||
export { default as DataDefinition } from './DataDefinition.vue';
|
export { default as DataDefinition } from './data-definition.vue';
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
export { default as ThingModelArrayDataSpecs } from './ThingModelArrayDataSpecs.vue';
|
export {default as ThingModelArrayDataSpecs} from './thing-model-array-data-specs.vue';
|
||||||
export { default as ThingModelEnumDataSpecs } from './ThingModelEnumDataSpecs.vue';
|
export {default as ThingModelEnumDataSpecs} from './thing-model-enum-data-specs.vue';
|
||||||
export { default as ThingModelNumberDataSpecs } from './ThingModelNumberDataSpecs.vue';
|
export {default as ThingModelNumberDataSpecs} from './thing-model-number-data-specs.vue';
|
||||||
export { default as ThingModelStructDataSpecs } from './ThingModelStructDataSpecs.vue';
|
export {default as ThingModelStructDataSpecs} from './thing-model-struct-data-specs.vue';
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
IoTDataSpecsDataTypeEnum,
|
IoTDataSpecsDataTypeEnum,
|
||||||
} from '#/views/iot/utils/constants';
|
} from '#/views/iot/utils/constants';
|
||||||
|
|
||||||
import ThingModelStructDataSpecs from './ThingModelStructDataSpecs.vue';
|
import ThingModelStructDataSpecs from './thing-model-struct-data-specs.vue';
|
||||||
|
|
||||||
/** 数组型的 dataSpecs 配置组件 */
|
/** 数组型的 dataSpecs 配置组件 */
|
||||||
defineOptions({ name: 'ThingModelArrayDataSpecs' });
|
defineOptions({ name: 'ThingModelArrayDataSpecs' });
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user