mirror of
https://gitee.com/yudaocode/yudao-ui-admin-vben.git
synced 2026-05-17 20:57:48 +00:00
Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b42e9b36e5 | ||
|
|
f8c2d4b1ff | ||
|
|
41d5aa93d6 | ||
|
|
3135b28211 | ||
|
|
08511191f7 | ||
|
|
0246fa1ebc | ||
|
|
0e4012c623 | ||
|
|
4933180560 | ||
|
|
8710da9383 | ||
|
|
5a1f4901da | ||
|
|
3da4a3f417 | ||
|
|
84b91c6795 | ||
|
|
735ff018be | ||
|
|
0163794e3f | ||
|
|
bb63ca9541 | ||
|
|
6b28518165 | ||
|
|
4adce844d3 | ||
|
|
19b5f38e23 | ||
|
|
80fa8b74e8 |
@@ -1,53 +1,9 @@
|
||||
import type { PageParam, PageResult } from '@vben/request';
|
||||
|
||||
import { isEmpty } from '@vben/utils';
|
||||
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
export namespace ThingModelApi {
|
||||
/** IoT 物模型数据 VO */
|
||||
export interface ThingModel {
|
||||
id?: number;
|
||||
productId?: number;
|
||||
productKey?: string;
|
||||
identifier: string;
|
||||
name: string;
|
||||
desc?: string;
|
||||
type: string;
|
||||
property?: ThingModelProperty;
|
||||
event?: ThingModelEvent;
|
||||
service?: ThingModelService;
|
||||
}
|
||||
|
||||
/** IoT 物模型属性 */
|
||||
export interface Property {
|
||||
identifier: string;
|
||||
name: string;
|
||||
accessMode: string;
|
||||
dataType: string;
|
||||
dataSpecs?: any;
|
||||
dataSpecsList?: any[];
|
||||
desc?: string;
|
||||
}
|
||||
|
||||
/** IoT 物模型服务 */
|
||||
export interface Service {
|
||||
identifier: string;
|
||||
name: string;
|
||||
callType: string;
|
||||
inputData?: any[];
|
||||
outputData?: any[];
|
||||
desc?: string;
|
||||
}
|
||||
|
||||
/** IoT 物模型事件 */
|
||||
export interface Event {
|
||||
identifier: string;
|
||||
name: string;
|
||||
type: string;
|
||||
outputData?: any[];
|
||||
desc?: string;
|
||||
}
|
||||
}
|
||||
|
||||
/** IoT 物模型数据 */
|
||||
export interface ThingModelData {
|
||||
id?: number;
|
||||
@@ -55,9 +11,9 @@ export interface ThingModelData {
|
||||
productKey?: string;
|
||||
identifier?: string;
|
||||
name?: string;
|
||||
desc?: string;
|
||||
type?: string;
|
||||
description?: string;
|
||||
dataType?: string;
|
||||
type?: number; // 参见 IoTThingModelTypeEnum 枚举类
|
||||
property?: ThingModelProperty;
|
||||
event?: ThingModelEvent;
|
||||
service?: ThingModelService;
|
||||
@@ -68,29 +24,45 @@ export interface ThingModelProperty {
|
||||
identifier?: string;
|
||||
name?: string;
|
||||
accessMode?: string;
|
||||
required?: boolean;
|
||||
dataType?: string;
|
||||
description?: string;
|
||||
dataSpecs?: any;
|
||||
dataSpecsList?: any[];
|
||||
desc?: string;
|
||||
}
|
||||
|
||||
/** IoT 物模型服务 */
|
||||
export interface ThingModelService {
|
||||
identifier?: string;
|
||||
name?: string;
|
||||
required?: boolean;
|
||||
callType?: string;
|
||||
inputData?: any[];
|
||||
outputData?: any[];
|
||||
desc?: string;
|
||||
description?: string;
|
||||
inputParams?: ThingModelParam[];
|
||||
outputParams?: ThingModelParam[];
|
||||
method?: string;
|
||||
}
|
||||
|
||||
/** IoT 物模型事件 */
|
||||
export interface ThingModelEvent {
|
||||
identifier?: string;
|
||||
name?: string;
|
||||
required?: boolean;
|
||||
type?: string;
|
||||
outputData?: any[];
|
||||
desc?: string;
|
||||
description?: string;
|
||||
outputParams?: ThingModelParam[];
|
||||
method?: string;
|
||||
}
|
||||
|
||||
/** IoT 物模型参数 */
|
||||
export interface ThingModelParam {
|
||||
identifier?: string;
|
||||
name?: string;
|
||||
direction?: string;
|
||||
paraOrder?: number;
|
||||
dataType?: string;
|
||||
dataSpecs?: any;
|
||||
dataSpecsList?: any[];
|
||||
}
|
||||
|
||||
/** IoT 数据定义(数值型) */
|
||||
@@ -108,23 +80,119 @@ export interface DataSpecsEnumOrBoolData {
|
||||
name: string;
|
||||
}
|
||||
|
||||
/** IoT 物模型表单校验规则 */
|
||||
export interface ThingModelFormRules {
|
||||
[key: string]: any;
|
||||
/** 生成「必填 + 数字」类校验器:拼到 size / length / 枚举值上 */
|
||||
function buildRequiredNumberValidator(label: string) {
|
||||
return (_rule: any, value: any, callback: any) => {
|
||||
if (isEmpty(value)) {
|
||||
callback(new Error(`${label}不能为空`));
|
||||
return;
|
||||
}
|
||||
if (Number.isNaN(Number(value))) {
|
||||
callback(new Error(`${label}必须是数字`));
|
||||
return;
|
||||
}
|
||||
callback();
|
||||
};
|
||||
}
|
||||
|
||||
/** 验证布尔型名称 */
|
||||
export function validateBoolName(_rule: any, value: any, callback: any) {
|
||||
if (value) {
|
||||
/** 生成「标识符样式」名称校验器:开头需为中文 / 英文 / 数字,整体仅允许中文、英文、数字、下划线、短划线,长度 ≤ 20 */
|
||||
export function buildIdentifierLikeNameValidator(label: string) {
|
||||
return (_rule: any, value: string, callback: any) => {
|
||||
if (isEmpty(value)) {
|
||||
callback(new Error(`${label}不能为空`));
|
||||
return;
|
||||
}
|
||||
if (!/^[一-龥A-Za-z0-9]/.test(value)) {
|
||||
callback(new Error(`${label}必须以中文、英文字母或数字开头`));
|
||||
return;
|
||||
}
|
||||
if (!/^[一-龥A-Za-z0-9][\w一-龥-]*$/.test(value)) {
|
||||
callback(
|
||||
new Error(`${label}只能包含中文、英文字母、数字、下划线和短划线`),
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (value.length > 20) {
|
||||
callback(new Error(`${label}长度不能超过 20 个字符`));
|
||||
return;
|
||||
}
|
||||
callback();
|
||||
} else {
|
||||
callback(new Error('枚举描述不能为空'));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** IoT 物模型表单校验规则 */
|
||||
export const ThingModelFormRules = {
|
||||
name: [
|
||||
{ required: true, message: '功能名称不能为空', trigger: 'blur' },
|
||||
{
|
||||
pattern: /^[一-龥A-Za-z0-9][一-龥A-Za-z0-9\-_/.]{0,29}$/,
|
||||
message:
|
||||
'支持中文、大小写字母、日文、数字、短划线、下划线、斜杠和小数点,必须以中文、英文或数字开头,不超过 30 个字符',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
type: [{ required: true, message: '功能类型不能为空', trigger: 'blur' }],
|
||||
identifier: [
|
||||
{ required: true, message: '标识符不能为空', trigger: 'blur' },
|
||||
{
|
||||
pattern: /^\w{1,50}$/,
|
||||
message: '支持大小写字母、数字和下划线,不超过 50 个字符',
|
||||
trigger: 'blur',
|
||||
},
|
||||
{
|
||||
validator: (_rule: any, value: string, callback: any) => {
|
||||
const reservedKeywords = [
|
||||
'set',
|
||||
'get',
|
||||
'post',
|
||||
'property',
|
||||
'event',
|
||||
'time',
|
||||
'value',
|
||||
];
|
||||
if (reservedKeywords.includes(value)) {
|
||||
callback(
|
||||
new Error(
|
||||
'set, get, post, property, event, time, value 是系统保留字段,不能用于标识符定义',
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (/^\d+$/.test(value)) {
|
||||
callback(new Error('标识符不能是纯数字'));
|
||||
return;
|
||||
}
|
||||
callback();
|
||||
},
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
childDataType: [{ required: true, message: '元素类型不能为空' }],
|
||||
size: [
|
||||
{
|
||||
required: true,
|
||||
validator: buildRequiredNumberValidator('元素个数'),
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
length: [
|
||||
{
|
||||
required: true,
|
||||
validator: buildRequiredNumberValidator('文本长度'),
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
accessMode: [{ required: true, message: '请选择读写类型', trigger: 'change' }],
|
||||
callType: [{ required: true, message: '请选择调用方式', trigger: 'change' }],
|
||||
eventType: [{ required: true, message: '请选择事件类型', trigger: 'change' }],
|
||||
};
|
||||
|
||||
/** 校验布尔值名称 */
|
||||
export const validateBoolName = buildIdentifierLikeNameValidator('布尔值名称');
|
||||
|
||||
/** 查询产品物模型分页 */
|
||||
export function getThingModelPage(params: PageParam) {
|
||||
return requestClient.get<PageResult<ThingModelApi.ThingModel>>(
|
||||
return requestClient.get<PageResult<ThingModelData>>(
|
||||
'/iot/thing-model/page',
|
||||
{ params },
|
||||
);
|
||||
@@ -132,17 +200,14 @@ export function getThingModelPage(params: PageParam) {
|
||||
|
||||
/** 查询产品物模型详情 */
|
||||
export function getThingModel(id: number) {
|
||||
return requestClient.get<ThingModelApi.ThingModel>(
|
||||
`/iot/thing-model/get?id=${id}`,
|
||||
);
|
||||
return requestClient.get<ThingModelData>(`/iot/thing-model/get?id=${id}`);
|
||||
}
|
||||
|
||||
/** 根据产品 ID 查询物模型列表 */
|
||||
export function getThingModelListByProductId(productId: number) {
|
||||
return requestClient.get<ThingModelApi.ThingModel[]>(
|
||||
'/iot/thing-model/list',
|
||||
{ params: { productId } },
|
||||
);
|
||||
return requestClient.get<ThingModelData[]>('/iot/thing-model/list', {
|
||||
params: { productId },
|
||||
});
|
||||
}
|
||||
|
||||
/** 新增物模型 */
|
||||
@@ -162,25 +227,7 @@ export function deleteThingModel(id: number) {
|
||||
|
||||
/** 获取物模型 TSL */
|
||||
export function getThingModelTSL(productId: number) {
|
||||
return requestClient.get<ThingModelApi.ThingModel[]>(
|
||||
'/iot/thing-model/get-tsl',
|
||||
{ params: { productId } },
|
||||
);
|
||||
}
|
||||
|
||||
/** 导入物模型 TSL
|
||||
export function importThingModelTSL(productId: number, tslData: any) {
|
||||
return requestClient.post('/iot/thing-model/import-tsl', {
|
||||
productId,
|
||||
tslData,
|
||||
});
|
||||
}
|
||||
*/
|
||||
|
||||
/** 导出物模型 TSL
|
||||
export function exportThingModelTSL(productId: number) {
|
||||
return requestClient.get<any>('/iot/thing-model/export-tsl', {
|
||||
return requestClient.get<any>('/iot/thing-model/get-tsl', {
|
||||
params: { productId },
|
||||
});
|
||||
}
|
||||
*/
|
||||
|
||||
66
apps/web-antd/src/api/wms/home/index.ts
Normal file
66
apps/web-antd/src/api/wms/home/index.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
export namespace WmsHomeStatisticsApi {
|
||||
export interface StatisticsReq {
|
||||
goodsLimit?: number;
|
||||
warehouseId?: number;
|
||||
warehouseLimit?: number;
|
||||
}
|
||||
|
||||
export interface OrderStatus {
|
||||
count: number;
|
||||
status: number;
|
||||
}
|
||||
|
||||
export interface OrderSummary {
|
||||
statuses: OrderStatus[];
|
||||
total: number;
|
||||
type: number;
|
||||
}
|
||||
|
||||
export interface OrderTrend {
|
||||
checkCount: number;
|
||||
movementCount: number;
|
||||
receiptCount: number;
|
||||
shipmentCount: number;
|
||||
time: number | string;
|
||||
}
|
||||
|
||||
export interface InventoryRankItem {
|
||||
id: number;
|
||||
name: string;
|
||||
quantity: number;
|
||||
}
|
||||
|
||||
export interface InventorySummary {
|
||||
goodsShareList: InventoryRankItem[];
|
||||
totalQuantity: number;
|
||||
warehouseDistributionList: InventoryRankItem[];
|
||||
}
|
||||
}
|
||||
|
||||
export function getOrderSummary(params?: WmsHomeStatisticsApi.StatisticsReq) {
|
||||
return requestClient.get<WmsHomeStatisticsApi.OrderSummary[]>(
|
||||
'/wms/home-statistics/order-summary',
|
||||
{ params },
|
||||
);
|
||||
}
|
||||
|
||||
export function getOrderTrend(
|
||||
days?: number,
|
||||
params?: WmsHomeStatisticsApi.StatisticsReq,
|
||||
) {
|
||||
return requestClient.get<WmsHomeStatisticsApi.OrderTrend[]>(
|
||||
'/wms/home-statistics/order-trend',
|
||||
{ params: { ...params, days } },
|
||||
);
|
||||
}
|
||||
|
||||
export function getInventorySummary(
|
||||
params?: WmsHomeStatisticsApi.StatisticsReq,
|
||||
) {
|
||||
return requestClient.get<WmsHomeStatisticsApi.InventorySummary>(
|
||||
'/wms/home-statistics/inventory-summary',
|
||||
{ params },
|
||||
);
|
||||
}
|
||||
37
apps/web-antd/src/api/wms/inventory/history/index.ts
Normal file
37
apps/web-antd/src/api/wms/inventory/history/index.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import type { PageParam, PageResult } from '@vben/request';
|
||||
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
export namespace WmsInventoryHistoryApi {
|
||||
/** WMS 库存记录 */
|
||||
export interface InventoryHistory {
|
||||
id?: number;
|
||||
itemId?: number;
|
||||
itemCode?: string;
|
||||
itemName?: string;
|
||||
unit?: string;
|
||||
skuId?: number;
|
||||
skuCode?: string;
|
||||
skuName?: string;
|
||||
warehouseId?: number;
|
||||
warehouseName?: string;
|
||||
quantity?: number;
|
||||
beforeQuantity?: number;
|
||||
afterQuantity?: number;
|
||||
price?: number;
|
||||
totalPrice?: number;
|
||||
remark?: string;
|
||||
orderId?: number;
|
||||
orderNo?: string;
|
||||
orderType?: number;
|
||||
createTime?: Date;
|
||||
}
|
||||
}
|
||||
|
||||
/** 查询库存记录分页 */
|
||||
export function getInventoryHistoryPage(params: PageParam) {
|
||||
return requestClient.get<PageResult<WmsInventoryHistoryApi.InventoryHistory>>(
|
||||
'/wms/inventory-history/page',
|
||||
{ params },
|
||||
);
|
||||
}
|
||||
42
apps/web-antd/src/api/wms/inventory/index.ts
Normal file
42
apps/web-antd/src/api/wms/inventory/index.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import type { PageParam, PageResult } from '@vben/request';
|
||||
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
export namespace WmsInventoryApi {
|
||||
/** WMS 库存统计 */
|
||||
export interface Inventory {
|
||||
id?: number;
|
||||
itemId?: number;
|
||||
itemCode?: string;
|
||||
itemName?: string;
|
||||
unit?: string;
|
||||
skuId?: number;
|
||||
skuCode?: string;
|
||||
skuName?: string;
|
||||
warehouseId?: number;
|
||||
warehouseName?: string;
|
||||
quantity?: number;
|
||||
remark?: string;
|
||||
createTime?: Date;
|
||||
}
|
||||
|
||||
/** WMS 库存统计列表请求 */
|
||||
export interface InventoryListReq {
|
||||
warehouseId: number;
|
||||
}
|
||||
}
|
||||
|
||||
/** 查询库存统计分页 */
|
||||
export function getInventoryPage(params: PageParam) {
|
||||
return requestClient.get<PageResult<WmsInventoryApi.Inventory>>(
|
||||
'/wms/inventory/page',
|
||||
{ params },
|
||||
);
|
||||
}
|
||||
|
||||
/** 查询库存统计列表 */
|
||||
export function getInventoryList(params: WmsInventoryApi.InventoryListReq) {
|
||||
return requestClient.get<WmsInventoryApi.Inventory[]>('/wms/inventory/list', {
|
||||
params,
|
||||
});
|
||||
}
|
||||
55
apps/web-antd/src/api/wms/md/item/brand/index.ts
Normal file
55
apps/web-antd/src/api/wms/md/item/brand/index.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import type { PageParam, PageResult } from '@vben/request';
|
||||
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
export namespace WmsItemBrandApi {
|
||||
/** WMS 商品品牌 */
|
||||
export interface ItemBrand {
|
||||
id?: number;
|
||||
code?: string;
|
||||
name?: string;
|
||||
createTime?: Date;
|
||||
}
|
||||
}
|
||||
|
||||
/** 查询商品品牌分页 */
|
||||
export function getItemBrandPage(params: PageParam) {
|
||||
return requestClient.get<PageResult<WmsItemBrandApi.ItemBrand>>(
|
||||
'/wms/item-brand/page',
|
||||
{ params },
|
||||
);
|
||||
}
|
||||
|
||||
/** 查询商品品牌精简列表 */
|
||||
export function getItemBrandSimpleList() {
|
||||
return requestClient.get<WmsItemBrandApi.ItemBrand[]>(
|
||||
'/wms/item-brand/simple-list',
|
||||
);
|
||||
}
|
||||
|
||||
/** 查询商品品牌详情 */
|
||||
export function getItemBrand(id: number) {
|
||||
return requestClient.get<WmsItemBrandApi.ItemBrand>(
|
||||
`/wms/item-brand/get?id=${id}`,
|
||||
);
|
||||
}
|
||||
|
||||
/** 新增商品品牌 */
|
||||
export function createItemBrand(data: WmsItemBrandApi.ItemBrand) {
|
||||
return requestClient.post('/wms/item-brand/create', data);
|
||||
}
|
||||
|
||||
/** 修改商品品牌 */
|
||||
export function updateItemBrand(data: WmsItemBrandApi.ItemBrand) {
|
||||
return requestClient.put('/wms/item-brand/update', data);
|
||||
}
|
||||
|
||||
/** 删除商品品牌 */
|
||||
export function deleteItemBrand(id: number) {
|
||||
return requestClient.delete(`/wms/item-brand/delete?id=${id}`);
|
||||
}
|
||||
|
||||
/** 导出商品品牌 */
|
||||
export function exportItemBrand(params: any) {
|
||||
return requestClient.download('/wms/item-brand/export-excel', { params });
|
||||
}
|
||||
52
apps/web-antd/src/api/wms/md/item/category/index.ts
Normal file
52
apps/web-antd/src/api/wms/md/item/category/index.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
export namespace WmsItemCategoryApi {
|
||||
/** WMS 商品分类 */
|
||||
export interface ItemCategory {
|
||||
id?: number;
|
||||
parentId?: number;
|
||||
code?: string;
|
||||
name?: string;
|
||||
sort?: number;
|
||||
status?: number;
|
||||
createTime?: Date;
|
||||
children?: ItemCategory[];
|
||||
}
|
||||
}
|
||||
|
||||
/** 查询商品分类列表 */
|
||||
export function getItemCategoryList(params?: any) {
|
||||
return requestClient.get<WmsItemCategoryApi.ItemCategory[]>(
|
||||
'/wms/item-category/list',
|
||||
{ params },
|
||||
);
|
||||
}
|
||||
|
||||
/** 查询商品分类精简列表 */
|
||||
export function getItemCategorySimpleList() {
|
||||
return requestClient.get<WmsItemCategoryApi.ItemCategory[]>(
|
||||
'/wms/item-category/simple-list',
|
||||
);
|
||||
}
|
||||
|
||||
/** 查询商品分类详情 */
|
||||
export function getItemCategory(id: number) {
|
||||
return requestClient.get<WmsItemCategoryApi.ItemCategory>(
|
||||
`/wms/item-category/get?id=${id}`,
|
||||
);
|
||||
}
|
||||
|
||||
/** 新增商品分类 */
|
||||
export function createItemCategory(data: WmsItemCategoryApi.ItemCategory) {
|
||||
return requestClient.post('/wms/item-category/create', data);
|
||||
}
|
||||
|
||||
/** 修改商品分类 */
|
||||
export function updateItemCategory(data: WmsItemCategoryApi.ItemCategory) {
|
||||
return requestClient.put('/wms/item-category/update', data);
|
||||
}
|
||||
|
||||
/** 删除商品分类 */
|
||||
export function deleteItemCategory(id: number) {
|
||||
return requestClient.delete(`/wms/item-category/delete?id=${id}`);
|
||||
}
|
||||
61
apps/web-antd/src/api/wms/md/item/index.ts
Normal file
61
apps/web-antd/src/api/wms/md/item/index.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import type { PageParam, PageResult } from '@vben/request';
|
||||
|
||||
import type { WmsItemSkuApi } from './sku';
|
||||
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
export namespace WmsItemApi {
|
||||
/** WMS 商品 */
|
||||
export interface Item {
|
||||
id?: number;
|
||||
code?: string;
|
||||
name?: string;
|
||||
categoryId?: number;
|
||||
categoryName?: string;
|
||||
unit?: string;
|
||||
brandId?: number;
|
||||
brandName?: string;
|
||||
remark?: string;
|
||||
skus?: WmsItemSkuApi.ItemSku[];
|
||||
createTime?: Date;
|
||||
}
|
||||
}
|
||||
|
||||
/** 查询商品分页 */
|
||||
export function getItemPage(params: PageParam) {
|
||||
return requestClient.get<PageResult<WmsItemApi.Item>>('/wms/item/page', {
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
/** 查询商品精简列表 */
|
||||
export function getItemSimpleList(params?: any) {
|
||||
return requestClient.get<WmsItemApi.Item[]>('/wms/item/simple-list', {
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
/** 查询商品详情 */
|
||||
export function getItem(id: number) {
|
||||
return requestClient.get<WmsItemApi.Item>(`/wms/item/get?id=${id}`);
|
||||
}
|
||||
|
||||
/** 新增商品 */
|
||||
export function createItem(data: WmsItemApi.Item) {
|
||||
return requestClient.post('/wms/item/create', data);
|
||||
}
|
||||
|
||||
/** 修改商品 */
|
||||
export function updateItem(data: WmsItemApi.Item) {
|
||||
return requestClient.put('/wms/item/update', data);
|
||||
}
|
||||
|
||||
/** 删除商品 */
|
||||
export function deleteItem(id: number) {
|
||||
return requestClient.delete(`/wms/item/delete?id=${id}`);
|
||||
}
|
||||
|
||||
/** 导出商品 */
|
||||
export function exportItem(params: any) {
|
||||
return requestClient.download('/wms/item/export-excel', { params });
|
||||
}
|
||||
37
apps/web-antd/src/api/wms/md/item/sku/index.ts
Normal file
37
apps/web-antd/src/api/wms/md/item/sku/index.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import type { PageParam, PageResult } from '@vben/request';
|
||||
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
export namespace WmsItemSkuApi {
|
||||
/** WMS 商品 SKU */
|
||||
export interface ItemSku {
|
||||
id?: number;
|
||||
name?: string;
|
||||
itemId?: number;
|
||||
itemCode?: string;
|
||||
itemName?: string;
|
||||
categoryId?: number;
|
||||
categoryName?: string;
|
||||
unit?: string;
|
||||
brandId?: number;
|
||||
brandName?: string;
|
||||
barCode?: string;
|
||||
code?: string;
|
||||
length?: number;
|
||||
width?: number;
|
||||
height?: number;
|
||||
grossWeight?: number;
|
||||
netWeight?: number;
|
||||
costPrice?: number;
|
||||
sellingPrice?: number;
|
||||
createTime?: Date;
|
||||
}
|
||||
}
|
||||
|
||||
/** 按 SKU 维度分页(支持商品 / 品牌 / 分类多表联查筛选) */
|
||||
export function getItemSkuPage(params: PageParam) {
|
||||
return requestClient.get<PageResult<WmsItemSkuApi.ItemSku>>(
|
||||
'/wms/item-sku/page',
|
||||
{ params },
|
||||
);
|
||||
}
|
||||
73
apps/web-antd/src/api/wms/md/merchant/index.ts
Normal file
73
apps/web-antd/src/api/wms/md/merchant/index.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import type { PageParam, PageResult } from '@vben/request';
|
||||
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
export namespace WmsMerchantApi {
|
||||
/** WMS 往来企业 */
|
||||
export interface Merchant {
|
||||
id?: number;
|
||||
code?: string;
|
||||
name?: string;
|
||||
type?: number;
|
||||
level?: string;
|
||||
bankName?: string;
|
||||
bankAccount?: string;
|
||||
address?: string;
|
||||
mobile?: string;
|
||||
telephone?: string;
|
||||
contact?: string;
|
||||
email?: string;
|
||||
remark?: string;
|
||||
createTime?: Date;
|
||||
}
|
||||
|
||||
/** WMS 往来企业精简列表请求 */
|
||||
export interface MerchantSimpleListReq {
|
||||
types?: number[];
|
||||
}
|
||||
}
|
||||
|
||||
/** 查询往来企业分页 */
|
||||
export function getMerchantPage(params: PageParam) {
|
||||
return requestClient.get<PageResult<WmsMerchantApi.Merchant>>(
|
||||
'/wms/merchant/page',
|
||||
{ params },
|
||||
);
|
||||
}
|
||||
|
||||
/** 查询往来企业精简列表 */
|
||||
export function getMerchantSimpleList(
|
||||
params?: WmsMerchantApi.MerchantSimpleListReq,
|
||||
) {
|
||||
return requestClient.get<WmsMerchantApi.Merchant[]>(
|
||||
'/wms/merchant/simple-list',
|
||||
{ params },
|
||||
);
|
||||
}
|
||||
|
||||
/** 查询往来企业详情 */
|
||||
export function getMerchant(id: number) {
|
||||
return requestClient.get<WmsMerchantApi.Merchant>(
|
||||
`/wms/merchant/get?id=${id}`,
|
||||
);
|
||||
}
|
||||
|
||||
/** 新增往来企业 */
|
||||
export function createMerchant(data: WmsMerchantApi.Merchant) {
|
||||
return requestClient.post('/wms/merchant/create', data);
|
||||
}
|
||||
|
||||
/** 修改往来企业 */
|
||||
export function updateMerchant(data: WmsMerchantApi.Merchant) {
|
||||
return requestClient.put('/wms/merchant/update', data);
|
||||
}
|
||||
|
||||
/** 删除往来企业 */
|
||||
export function deleteMerchant(id: number) {
|
||||
return requestClient.delete(`/wms/merchant/delete?id=${id}`);
|
||||
}
|
||||
|
||||
/** 导出往来企业 */
|
||||
export function exportMerchant(params: any) {
|
||||
return requestClient.download('/wms/merchant/export-excel', { params });
|
||||
}
|
||||
57
apps/web-antd/src/api/wms/md/warehouse/index.ts
Normal file
57
apps/web-antd/src/api/wms/md/warehouse/index.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import type { PageParam, PageResult } from '@vben/request';
|
||||
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
export namespace WmsWarehouseApi {
|
||||
/** WMS 仓库 */
|
||||
export interface Warehouse {
|
||||
id?: number;
|
||||
code?: string;
|
||||
name?: string;
|
||||
remark?: string;
|
||||
sort?: number;
|
||||
createTime?: Date;
|
||||
}
|
||||
}
|
||||
|
||||
/** 查询仓库分页 */
|
||||
export function getWarehousePage(params: PageParam) {
|
||||
return requestClient.get<PageResult<WmsWarehouseApi.Warehouse>>(
|
||||
'/wms/warehouse/page',
|
||||
{ params },
|
||||
);
|
||||
}
|
||||
|
||||
/** 查询仓库精简列表 */
|
||||
export function getWarehouseSimpleList() {
|
||||
return requestClient.get<WmsWarehouseApi.Warehouse[]>(
|
||||
'/wms/warehouse/simple-list',
|
||||
);
|
||||
}
|
||||
|
||||
/** 查询仓库详情 */
|
||||
export function getWarehouse(id: number) {
|
||||
return requestClient.get<WmsWarehouseApi.Warehouse>(
|
||||
`/wms/warehouse/get?id=${id}`,
|
||||
);
|
||||
}
|
||||
|
||||
/** 新增仓库 */
|
||||
export function createWarehouse(data: WmsWarehouseApi.Warehouse) {
|
||||
return requestClient.post('/wms/warehouse/create', data);
|
||||
}
|
||||
|
||||
/** 修改仓库 */
|
||||
export function updateWarehouse(data: WmsWarehouseApi.Warehouse) {
|
||||
return requestClient.put('/wms/warehouse/update', data);
|
||||
}
|
||||
|
||||
/** 删除仓库 */
|
||||
export function deleteWarehouse(id: number) {
|
||||
return requestClient.delete(`/wms/warehouse/delete?id=${id}`);
|
||||
}
|
||||
|
||||
/** 导出仓库 */
|
||||
export function exportWarehouse(params: any) {
|
||||
return requestClient.download('/wms/warehouse/export-excel', { params });
|
||||
}
|
||||
23
apps/web-antd/src/api/wms/order/check/detail/index.ts
Normal file
23
apps/web-antd/src/api/wms/order/check/detail/index.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
export namespace WmsCheckOrderDetailApi {
|
||||
/** WMS 盘库单明细 */
|
||||
export interface CheckOrderDetail {
|
||||
id?: number;
|
||||
orderId?: number;
|
||||
itemId?: number;
|
||||
itemCode?: string;
|
||||
itemName?: string;
|
||||
unit?: string;
|
||||
skuId?: number;
|
||||
skuCode?: string;
|
||||
skuName?: string;
|
||||
inventoryId?: number;
|
||||
warehouseId?: number;
|
||||
warehouseName?: string;
|
||||
receiptTime?: Date;
|
||||
quantity?: number;
|
||||
checkQuantity?: number;
|
||||
availableQuantity?: number;
|
||||
price?: number;
|
||||
createTime?: Date;
|
||||
}
|
||||
}
|
||||
71
apps/web-antd/src/api/wms/order/check/index.ts
Normal file
71
apps/web-antd/src/api/wms/order/check/index.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import type { PageParam, PageResult } from '@vben/request';
|
||||
|
||||
import type { WmsCheckOrderDetailApi } from './detail';
|
||||
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
export namespace WmsCheckOrderApi {
|
||||
/** WMS 盘库单 */
|
||||
export interface CheckOrder {
|
||||
id?: number;
|
||||
no?: string;
|
||||
orderTime?: string;
|
||||
status?: number;
|
||||
remark?: string;
|
||||
warehouseId?: number;
|
||||
warehouseName?: string;
|
||||
totalQuantity?: number;
|
||||
totalPrice?: number;
|
||||
actualPrice?: number;
|
||||
details?: WmsCheckOrderDetailApi.CheckOrderDetail[];
|
||||
createTime?: Date;
|
||||
creator?: string;
|
||||
creatorName?: string;
|
||||
updateTime?: Date;
|
||||
updater?: string;
|
||||
updaterName?: string;
|
||||
}
|
||||
}
|
||||
|
||||
export function getCheckOrderPage(params: PageParam) {
|
||||
return requestClient.get<PageResult<WmsCheckOrderApi.CheckOrder>>(
|
||||
'/wms/check-order/page',
|
||||
{ params },
|
||||
);
|
||||
}
|
||||
|
||||
export function getCheckOrder(id: number) {
|
||||
return requestClient.get<WmsCheckOrderApi.CheckOrder>(
|
||||
`/wms/check-order/get?id=${id}`,
|
||||
);
|
||||
}
|
||||
|
||||
export function getCheckOrderDetailListByOrderId(orderId: number) {
|
||||
return requestClient.get<WmsCheckOrderDetailApi.CheckOrderDetail[]>(
|
||||
`/wms/check-order-detail/list-by-order-id?orderId=${orderId}`,
|
||||
);
|
||||
}
|
||||
|
||||
export function createCheckOrder(data: WmsCheckOrderApi.CheckOrder) {
|
||||
return requestClient.post('/wms/check-order/create', data);
|
||||
}
|
||||
|
||||
export function updateCheckOrder(data: WmsCheckOrderApi.CheckOrder) {
|
||||
return requestClient.put('/wms/check-order/update', data);
|
||||
}
|
||||
|
||||
export function completeCheckOrder(id: number) {
|
||||
return requestClient.put(`/wms/check-order/complete?id=${id}`);
|
||||
}
|
||||
|
||||
export function cancelCheckOrder(id: number) {
|
||||
return requestClient.put(`/wms/check-order/cancel?id=${id}`);
|
||||
}
|
||||
|
||||
export function deleteCheckOrder(id: number) {
|
||||
return requestClient.delete(`/wms/check-order/delete?id=${id}`);
|
||||
}
|
||||
|
||||
export function exportCheckOrder(params: any) {
|
||||
return requestClient.download('/wms/check-order/export-excel', { params });
|
||||
}
|
||||
23
apps/web-antd/src/api/wms/order/movement/detail/index.ts
Normal file
23
apps/web-antd/src/api/wms/order/movement/detail/index.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
export namespace WmsMovementOrderDetailApi {
|
||||
/** WMS 移库单明细 */
|
||||
export interface MovementOrderDetail {
|
||||
id?: number;
|
||||
orderId?: number;
|
||||
itemId?: number;
|
||||
itemCode?: string;
|
||||
itemName?: string;
|
||||
unit?: string;
|
||||
skuId?: number;
|
||||
skuCode?: string;
|
||||
skuName?: string;
|
||||
sourceWarehouseId?: number;
|
||||
sourceWarehouseName?: string;
|
||||
targetWarehouseId?: number;
|
||||
targetWarehouseName?: string;
|
||||
quantity?: number;
|
||||
availableQuantity?: number;
|
||||
price?: number;
|
||||
totalPrice?: number;
|
||||
createTime?: Date;
|
||||
}
|
||||
}
|
||||
72
apps/web-antd/src/api/wms/order/movement/index.ts
Normal file
72
apps/web-antd/src/api/wms/order/movement/index.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import type { PageParam, PageResult } from '@vben/request';
|
||||
|
||||
import type { WmsMovementOrderDetailApi } from './detail';
|
||||
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
export namespace WmsMovementOrderApi {
|
||||
/** WMS 移库单 */
|
||||
export interface MovementOrder {
|
||||
id?: number;
|
||||
no?: string;
|
||||
orderTime?: string;
|
||||
status?: number;
|
||||
remark?: string;
|
||||
sourceWarehouseId?: number;
|
||||
sourceWarehouseName?: string;
|
||||
targetWarehouseId?: number;
|
||||
targetWarehouseName?: string;
|
||||
totalQuantity?: number;
|
||||
totalPrice?: number;
|
||||
details?: WmsMovementOrderDetailApi.MovementOrderDetail[];
|
||||
createTime?: Date;
|
||||
creator?: string;
|
||||
creatorName?: string;
|
||||
updateTime?: Date;
|
||||
updater?: string;
|
||||
updaterName?: string;
|
||||
}
|
||||
}
|
||||
|
||||
export function getMovementOrderPage(params: PageParam) {
|
||||
return requestClient.get<PageResult<WmsMovementOrderApi.MovementOrder>>(
|
||||
'/wms/movement-order/page',
|
||||
{ params },
|
||||
);
|
||||
}
|
||||
|
||||
export function getMovementOrder(id: number) {
|
||||
return requestClient.get<WmsMovementOrderApi.MovementOrder>(
|
||||
`/wms/movement-order/get?id=${id}`,
|
||||
);
|
||||
}
|
||||
|
||||
export function getMovementOrderDetailListByOrderId(orderId: number) {
|
||||
return requestClient.get<WmsMovementOrderDetailApi.MovementOrderDetail[]>(
|
||||
`/wms/movement-order-detail/list-by-order-id?orderId=${orderId}`,
|
||||
);
|
||||
}
|
||||
|
||||
export function createMovementOrder(data: WmsMovementOrderApi.MovementOrder) {
|
||||
return requestClient.post('/wms/movement-order/create', data);
|
||||
}
|
||||
|
||||
export function updateMovementOrder(data: WmsMovementOrderApi.MovementOrder) {
|
||||
return requestClient.put('/wms/movement-order/update', data);
|
||||
}
|
||||
|
||||
export function completeMovementOrder(id: number) {
|
||||
return requestClient.put(`/wms/movement-order/complete?id=${id}`);
|
||||
}
|
||||
|
||||
export function cancelMovementOrder(id: number) {
|
||||
return requestClient.put(`/wms/movement-order/cancel?id=${id}`);
|
||||
}
|
||||
|
||||
export function deleteMovementOrder(id: number) {
|
||||
return requestClient.delete(`/wms/movement-order/delete?id=${id}`);
|
||||
}
|
||||
|
||||
export function exportMovementOrder(params: any) {
|
||||
return requestClient.download('/wms/movement-order/export-excel', { params });
|
||||
}
|
||||
20
apps/web-antd/src/api/wms/order/receipt/detail/index.ts
Normal file
20
apps/web-antd/src/api/wms/order/receipt/detail/index.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
export namespace WmsReceiptOrderDetailApi {
|
||||
/** WMS 入库单明细 */
|
||||
export interface ReceiptOrderDetail {
|
||||
id?: number;
|
||||
orderId?: number;
|
||||
itemId?: number;
|
||||
itemCode?: string;
|
||||
itemName?: string;
|
||||
unit?: string;
|
||||
skuId?: number;
|
||||
skuCode?: string;
|
||||
skuName?: string;
|
||||
warehouseId?: number;
|
||||
warehouseName?: string;
|
||||
quantity?: number;
|
||||
price?: number;
|
||||
totalPrice?: number;
|
||||
createTime?: Date;
|
||||
}
|
||||
}
|
||||
74
apps/web-antd/src/api/wms/order/receipt/index.ts
Normal file
74
apps/web-antd/src/api/wms/order/receipt/index.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import type { PageParam, PageResult } from '@vben/request';
|
||||
|
||||
import type { WmsReceiptOrderDetailApi } from './detail';
|
||||
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
export namespace WmsReceiptOrderApi {
|
||||
/** WMS 入库单 */
|
||||
export interface ReceiptOrder {
|
||||
id?: number;
|
||||
no?: string;
|
||||
type?: number;
|
||||
orderTime?: string;
|
||||
status?: number;
|
||||
bizOrderNo?: string;
|
||||
merchantId?: number;
|
||||
merchantName?: string;
|
||||
remark?: string;
|
||||
warehouseId?: number;
|
||||
warehouseName?: string;
|
||||
totalQuantity?: number;
|
||||
totalPrice?: number;
|
||||
details?: WmsReceiptOrderDetailApi.ReceiptOrderDetail[];
|
||||
createTime?: Date;
|
||||
creator?: string;
|
||||
creatorName?: string;
|
||||
updateTime?: Date;
|
||||
updater?: string;
|
||||
updaterName?: string;
|
||||
}
|
||||
}
|
||||
|
||||
export function getReceiptOrderPage(params: PageParam) {
|
||||
return requestClient.get<PageResult<WmsReceiptOrderApi.ReceiptOrder>>(
|
||||
'/wms/receipt-order/page',
|
||||
{ params },
|
||||
);
|
||||
}
|
||||
|
||||
export function getReceiptOrder(id: number) {
|
||||
return requestClient.get<WmsReceiptOrderApi.ReceiptOrder>(
|
||||
`/wms/receipt-order/get?id=${id}`,
|
||||
);
|
||||
}
|
||||
|
||||
export function getReceiptOrderDetailListByOrderId(orderId: number) {
|
||||
return requestClient.get<WmsReceiptOrderDetailApi.ReceiptOrderDetail[]>(
|
||||
`/wms/receipt-order-detail/list-by-order-id?orderId=${orderId}`,
|
||||
);
|
||||
}
|
||||
|
||||
export function createReceiptOrder(data: WmsReceiptOrderApi.ReceiptOrder) {
|
||||
return requestClient.post('/wms/receipt-order/create', data);
|
||||
}
|
||||
|
||||
export function updateReceiptOrder(data: WmsReceiptOrderApi.ReceiptOrder) {
|
||||
return requestClient.put('/wms/receipt-order/update', data);
|
||||
}
|
||||
|
||||
export function completeReceiptOrder(id: number) {
|
||||
return requestClient.put(`/wms/receipt-order/complete?id=${id}`);
|
||||
}
|
||||
|
||||
export function cancelReceiptOrder(id: number) {
|
||||
return requestClient.put(`/wms/receipt-order/cancel?id=${id}`);
|
||||
}
|
||||
|
||||
export function deleteReceiptOrder(id: number) {
|
||||
return requestClient.delete(`/wms/receipt-order/delete?id=${id}`);
|
||||
}
|
||||
|
||||
export function exportReceiptOrder(params: any) {
|
||||
return requestClient.download('/wms/receipt-order/export-excel', { params });
|
||||
}
|
||||
21
apps/web-antd/src/api/wms/order/shipment/detail/index.ts
Normal file
21
apps/web-antd/src/api/wms/order/shipment/detail/index.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
export namespace WmsShipmentOrderDetailApi {
|
||||
/** WMS 出库单明细 */
|
||||
export interface ShipmentOrderDetail {
|
||||
id?: number;
|
||||
orderId?: number;
|
||||
itemId?: number;
|
||||
itemCode?: string;
|
||||
itemName?: string;
|
||||
unit?: string;
|
||||
skuId?: number;
|
||||
skuCode?: string;
|
||||
skuName?: string;
|
||||
warehouseId?: number;
|
||||
warehouseName?: string;
|
||||
quantity?: number;
|
||||
availableQuantity?: number;
|
||||
price?: number;
|
||||
totalPrice?: number;
|
||||
createTime?: Date;
|
||||
}
|
||||
}
|
||||
74
apps/web-antd/src/api/wms/order/shipment/index.ts
Normal file
74
apps/web-antd/src/api/wms/order/shipment/index.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import type { PageParam, PageResult } from '@vben/request';
|
||||
|
||||
import type { WmsShipmentOrderDetailApi } from './detail';
|
||||
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
export namespace WmsShipmentOrderApi {
|
||||
/** WMS 出库单 */
|
||||
export interface ShipmentOrder {
|
||||
id?: number;
|
||||
no?: string;
|
||||
type?: number;
|
||||
orderTime?: string;
|
||||
status?: number;
|
||||
bizOrderNo?: string;
|
||||
merchantId?: number;
|
||||
merchantName?: string;
|
||||
remark?: string;
|
||||
warehouseId?: number;
|
||||
warehouseName?: string;
|
||||
totalQuantity?: number;
|
||||
totalPrice?: number;
|
||||
details?: WmsShipmentOrderDetailApi.ShipmentOrderDetail[];
|
||||
createTime?: Date;
|
||||
creator?: string;
|
||||
creatorName?: string;
|
||||
updateTime?: Date;
|
||||
updater?: string;
|
||||
updaterName?: string;
|
||||
}
|
||||
}
|
||||
|
||||
export function getShipmentOrderPage(params: PageParam) {
|
||||
return requestClient.get<PageResult<WmsShipmentOrderApi.ShipmentOrder>>(
|
||||
'/wms/shipment-order/page',
|
||||
{ params },
|
||||
);
|
||||
}
|
||||
|
||||
export function getShipmentOrder(id: number) {
|
||||
return requestClient.get<WmsShipmentOrderApi.ShipmentOrder>(
|
||||
`/wms/shipment-order/get?id=${id}`,
|
||||
);
|
||||
}
|
||||
|
||||
export function getShipmentOrderDetailListByOrderId(orderId: number) {
|
||||
return requestClient.get<WmsShipmentOrderDetailApi.ShipmentOrderDetail[]>(
|
||||
`/wms/shipment-order-detail/list-by-order-id?orderId=${orderId}`,
|
||||
);
|
||||
}
|
||||
|
||||
export function createShipmentOrder(data: WmsShipmentOrderApi.ShipmentOrder) {
|
||||
return requestClient.post('/wms/shipment-order/create', data);
|
||||
}
|
||||
|
||||
export function updateShipmentOrder(data: WmsShipmentOrderApi.ShipmentOrder) {
|
||||
return requestClient.put('/wms/shipment-order/update', data);
|
||||
}
|
||||
|
||||
export function completeShipmentOrder(id: number) {
|
||||
return requestClient.put(`/wms/shipment-order/complete?id=${id}`);
|
||||
}
|
||||
|
||||
export function cancelShipmentOrder(id: number) {
|
||||
return requestClient.put(`/wms/shipment-order/cancel?id=${id}`);
|
||||
}
|
||||
|
||||
export function deleteShipmentOrder(id: number) {
|
||||
return requestClient.delete(`/wms/shipment-order/delete?id=${id}`);
|
||||
}
|
||||
|
||||
export function exportShipmentOrder(params: any) {
|
||||
return requestClient.download('/wms/shipment-order/export-excel', { params });
|
||||
}
|
||||
39
apps/web-antd/src/components/number-range-input/index.ts
Normal file
39
apps/web-antd/src/components/number-range-input/index.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import type { VbenFormSchema } from '#/adapter/form';
|
||||
|
||||
import { markRaw } from 'vue';
|
||||
|
||||
import NumberRangeInput from './number-range-input.vue';
|
||||
|
||||
export { default as NumberRangeInput } from './number-range-input.vue';
|
||||
|
||||
export type NumberRangeValue = [number | undefined, number | undefined];
|
||||
|
||||
function splitNumberRange(minFieldName: string, maxFieldName: string) {
|
||||
return (
|
||||
value: NumberRangeValue | undefined,
|
||||
setValue: (fieldName: string, value: number | undefined) => void,
|
||||
) => {
|
||||
setValue(minFieldName, value?.[0]);
|
||||
setValue(maxFieldName, value?.[1]);
|
||||
return undefined;
|
||||
};
|
||||
}
|
||||
|
||||
export function buildNumberRangeSchema(
|
||||
label: string,
|
||||
fieldName: string,
|
||||
minFieldName: string,
|
||||
maxFieldName: string,
|
||||
precision: number,
|
||||
): VbenFormSchema {
|
||||
return {
|
||||
component: markRaw(NumberRangeInput),
|
||||
componentProps: {
|
||||
min: 0,
|
||||
precision,
|
||||
},
|
||||
fieldName,
|
||||
label,
|
||||
valueFormat: splitNumberRange(minFieldName, maxFieldName),
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
<script lang="ts" setup>
|
||||
import { InputNumber } from 'ant-design-vue';
|
||||
|
||||
type NumberRangeValue = [number | undefined, number | undefined];
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
maxPlaceholder?: string;
|
||||
min?: number;
|
||||
minPlaceholder?: string;
|
||||
precision?: number;
|
||||
value?: NumberRangeValue;
|
||||
}>(),
|
||||
{
|
||||
maxPlaceholder: '最大值',
|
||||
min: undefined,
|
||||
minPlaceholder: '最小值',
|
||||
precision: 2,
|
||||
value: undefined,
|
||||
},
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:value': [value: NumberRangeValue | undefined];
|
||||
}>();
|
||||
|
||||
function normalizeValue(value: unknown) {
|
||||
if (typeof value === 'number') {
|
||||
return Number.isFinite(value) ? value : undefined;
|
||||
}
|
||||
if (typeof value === 'string' && value.trim() !== '') {
|
||||
const numberValue = Number(value);
|
||||
return Number.isFinite(numberValue) ? numberValue : undefined;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function updateValue(index: 0 | 1, value: unknown) {
|
||||
const next: NumberRangeValue = [
|
||||
props.value?.[0] ?? undefined,
|
||||
props.value?.[1] ?? undefined,
|
||||
];
|
||||
next[index] = normalizeValue(value);
|
||||
emit(
|
||||
'update:value',
|
||||
next[0] === undefined && next[1] === undefined ? undefined : next,
|
||||
);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex w-full items-center gap-2">
|
||||
<InputNumber
|
||||
:controls="false"
|
||||
:min="min"
|
||||
:placeholder="minPlaceholder"
|
||||
:precision="precision"
|
||||
:value="value?.[0]"
|
||||
class="min-w-0 flex-1"
|
||||
@update:value="updateValue(0, $event)"
|
||||
/>
|
||||
<span class="shrink-0 text-muted-foreground">至</span>
|
||||
<InputNumber
|
||||
:controls="false"
|
||||
:min="min"
|
||||
:placeholder="maxPlaceholder"
|
||||
:precision="precision"
|
||||
:value="value?.[1]"
|
||||
class="min-w-0 flex-1"
|
||||
@update:value="updateValue(1, $event)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -41,6 +41,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '温度参数',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
placeholder: '请输入温度参数',
|
||||
precision: 2,
|
||||
min: 0,
|
||||
@@ -53,6 +54,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '回复数 Token 数',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
placeholder: '请输入回复数 Token 数',
|
||||
min: 0,
|
||||
max: 8192,
|
||||
@@ -64,6 +66,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '上下文数量',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
placeholder: '请输入上下文数量',
|
||||
min: 0,
|
||||
max: 20,
|
||||
|
||||
@@ -52,6 +52,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '检索 topK',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
placeholder: '请输入检索 topK',
|
||||
min: 0,
|
||||
max: 10,
|
||||
@@ -63,6 +64,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '检索相似度阈值',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
placeholder: '请输入检索相似度阈值',
|
||||
min: 0,
|
||||
max: 1,
|
||||
|
||||
@@ -55,6 +55,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '检索 topK',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
placeholder: '请输入检索 topK',
|
||||
min: 0,
|
||||
max: 10,
|
||||
@@ -66,6 +67,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '检索相似度阈值',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
placeholder: '请输入检索相似度阈值',
|
||||
min: 0,
|
||||
max: 1,
|
||||
|
||||
@@ -154,6 +154,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '角色排序',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
placeholder: '请输入角色排序',
|
||||
},
|
||||
dependencies: {
|
||||
|
||||
@@ -84,6 +84,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '模型排序',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
placeholder: '请输入模型排序',
|
||||
},
|
||||
rules: 'required',
|
||||
@@ -104,6 +105,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '温度参数',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
placeholder: '请输入温度参数',
|
||||
min: 0,
|
||||
max: 2,
|
||||
@@ -121,6 +123,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '回复数 Token 数',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
min: 0,
|
||||
max: 8192,
|
||||
placeholder: '请输入回复数 Token 数',
|
||||
@@ -138,6 +141,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '上下文数量',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
min: 0,
|
||||
max: 20,
|
||||
placeholder: '请输入上下文数量',
|
||||
|
||||
@@ -60,6 +60,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '分类排序',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
min: 0,
|
||||
placeholder: '请输入分类排序',
|
||||
},
|
||||
|
||||
@@ -118,6 +118,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '产品总金额',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
min: 0,
|
||||
precision: 2,
|
||||
disabled: true,
|
||||
@@ -130,6 +131,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '整单折扣(%)',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
min: 0,
|
||||
precision: 2,
|
||||
placeholder: '请输入整单折扣',
|
||||
@@ -141,6 +143,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '折扣后金额',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
min: 0,
|
||||
precision: 2,
|
||||
disabled: true,
|
||||
|
||||
@@ -17,6 +17,7 @@ export const schema: VbenFormSchema[] = [
|
||||
component: 'InputNumber',
|
||||
fieldName: 'notifyDays',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
min: 0,
|
||||
precision: 0,
|
||||
},
|
||||
|
||||
@@ -198,6 +198,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '产品总金额',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
min: 0,
|
||||
precision: 2,
|
||||
placeholder: '请输入产品总金额',
|
||||
@@ -209,6 +210,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '整单折扣(%)',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
min: 0,
|
||||
precision: 2,
|
||||
placeholder: '请输入整单折扣',
|
||||
@@ -220,6 +222,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '折扣后金额',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
min: 0,
|
||||
precision: 2,
|
||||
disabled: true,
|
||||
|
||||
@@ -65,6 +65,7 @@ export function useFormSchema(confType: LimitConfType): VbenFormSchema[] {
|
||||
: '锁定客户数上限',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
placeholder: `请输入${
|
||||
LimitConfType.CUSTOMER_QUANTITY_LIMIT === confType
|
||||
? '拥有客户数上限'
|
||||
|
||||
@@ -16,6 +16,7 @@ export const schema: VbenFormSchema[] = [
|
||||
component: 'InputNumber',
|
||||
fieldName: 'contactExpireDays',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
min: 0,
|
||||
precision: 0,
|
||||
},
|
||||
@@ -35,6 +36,7 @@ export const schema: VbenFormSchema[] = [
|
||||
addonAfter: () => '天未成交',
|
||||
}),
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
min: 0,
|
||||
precision: 0,
|
||||
},
|
||||
@@ -63,6 +65,7 @@ export const schema: VbenFormSchema[] = [
|
||||
component: 'InputNumber',
|
||||
fieldName: 'notifyDays',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
min: 0,
|
||||
precision: 0,
|
||||
},
|
||||
|
||||
@@ -92,6 +92,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '价格(元)',
|
||||
rules: 'required',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
min: 0,
|
||||
precision: 2,
|
||||
step: 0.1,
|
||||
|
||||
@@ -141,6 +141,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
component: 'InputNumber',
|
||||
rules: 'required',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
placeholder: '请输入回款金额',
|
||||
min: 0,
|
||||
precision: 2,
|
||||
|
||||
@@ -96,6 +96,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
component: 'InputNumber',
|
||||
rules: 'required',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
placeholder: '请输入计划回款金额',
|
||||
min: 0,
|
||||
precision: 2,
|
||||
@@ -119,6 +120,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '提前几天提醒',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
placeholder: '请输入提前几天提醒',
|
||||
min: 0,
|
||||
},
|
||||
|
||||
@@ -44,6 +44,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '排序',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
placeholder: '请输入排序',
|
||||
precision: 0,
|
||||
},
|
||||
|
||||
@@ -129,6 +129,7 @@ export function useFormSchema(formType: string): VbenFormSchema[] {
|
||||
label: '合计付款',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
placeholder: '合计付款',
|
||||
precision: 2,
|
||||
formatter: erpPriceInputFormatter,
|
||||
@@ -140,6 +141,7 @@ export function useFormSchema(formType: string): VbenFormSchema[] {
|
||||
label: '优惠金额',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
disabled: formType === 'detail',
|
||||
placeholder: '请输入优惠金额',
|
||||
precision: 2,
|
||||
@@ -151,6 +153,7 @@ export function useFormSchema(formType: string): VbenFormSchema[] {
|
||||
label: '实际付款',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
placeholder: '实际付款',
|
||||
precision: 2,
|
||||
formatter: erpPriceInputFormatter,
|
||||
|
||||
@@ -129,6 +129,7 @@ export function useFormSchema(formType: string): VbenFormSchema[] {
|
||||
label: '合计收款',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
placeholder: '合计收款',
|
||||
precision: 2,
|
||||
formatter: erpPriceInputFormatter,
|
||||
@@ -140,6 +141,7 @@ export function useFormSchema(formType: string): VbenFormSchema[] {
|
||||
label: '优惠金额',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
disabled: formType === 'detail',
|
||||
placeholder: '请输入优惠金额',
|
||||
precision: 2,
|
||||
@@ -151,6 +153,7 @@ export function useFormSchema(formType: string): VbenFormSchema[] {
|
||||
label: '实际收款',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
placeholder: '实际收款',
|
||||
precision: 2,
|
||||
formatter: erpPriceInputFormatter,
|
||||
|
||||
@@ -65,6 +65,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '显示顺序',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
min: 0,
|
||||
placeholder: '请输入显示顺序',
|
||||
},
|
||||
|
||||
@@ -92,6 +92,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '保质期天数',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
placeholder: '请输入保质期天数',
|
||||
},
|
||||
},
|
||||
@@ -100,6 +101,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '重量(kg)',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
placeholder: '请输入重量(kg)',
|
||||
},
|
||||
},
|
||||
@@ -108,6 +110,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '采购价格',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
placeholder: '请输入采购价格,单位:元',
|
||||
precision: 2,
|
||||
min: 0,
|
||||
@@ -119,6 +122,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '销售价格',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
placeholder: '请输入销售价格,单位:元',
|
||||
precision: 2,
|
||||
min: 0,
|
||||
@@ -130,6 +134,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '最低价格',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
placeholder: '请输入最低价格,单位:元',
|
||||
precision: 2,
|
||||
min: 0,
|
||||
|
||||
@@ -117,6 +117,7 @@ export function useFormSchema(formType: string): VbenFormSchema[] {
|
||||
label: '优惠率(%)',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
placeholder: '请输入优惠率',
|
||||
min: 0,
|
||||
max: 100,
|
||||
@@ -129,6 +130,7 @@ export function useFormSchema(formType: string): VbenFormSchema[] {
|
||||
label: '付款优惠',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
placeholder: '付款优惠',
|
||||
precision: 2,
|
||||
formatter: erpPriceInputFormatter,
|
||||
@@ -140,6 +142,7 @@ export function useFormSchema(formType: string): VbenFormSchema[] {
|
||||
label: '优惠后金额',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
placeholder: '优惠后金额',
|
||||
precision: 2,
|
||||
formatter: erpPriceInputFormatter,
|
||||
@@ -160,6 +163,7 @@ export function useFormSchema(formType: string): VbenFormSchema[] {
|
||||
label: '其他费用',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
disabled: formType === 'detail',
|
||||
placeholder: '请输入其他费用',
|
||||
precision: 2,
|
||||
@@ -184,6 +188,7 @@ export function useFormSchema(formType: string): VbenFormSchema[] {
|
||||
label: '应付金额',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
precision: 2,
|
||||
min: 0,
|
||||
disabled: true,
|
||||
|
||||
@@ -103,6 +103,7 @@ export function useFormSchema(formType: string): VbenFormSchema[] {
|
||||
label: '优惠率(%)',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
placeholder: '请输入优惠率',
|
||||
min: 0,
|
||||
max: 100,
|
||||
@@ -115,6 +116,7 @@ export function useFormSchema(formType: string): VbenFormSchema[] {
|
||||
label: '付款优惠',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
placeholder: '付款优惠',
|
||||
precision: 2,
|
||||
formatter: erpPriceInputFormatter,
|
||||
@@ -126,6 +128,7 @@ export function useFormSchema(formType: string): VbenFormSchema[] {
|
||||
label: '优惠后金额',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
placeholder: '优惠后金额',
|
||||
precision: 2,
|
||||
formatter: erpPriceInputFormatter,
|
||||
@@ -148,6 +151,7 @@ export function useFormSchema(formType: string): VbenFormSchema[] {
|
||||
{
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
placeholder: '请输入支付订金',
|
||||
precision: 2,
|
||||
min: 0,
|
||||
|
||||
@@ -117,6 +117,7 @@ export function useFormSchema(formType: string): VbenFormSchema[] {
|
||||
label: '优惠率(%)',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
placeholder: '请输入优惠率',
|
||||
min: 0,
|
||||
max: 100,
|
||||
@@ -129,6 +130,7 @@ export function useFormSchema(formType: string): VbenFormSchema[] {
|
||||
label: '退款优惠',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
precision: 2,
|
||||
formatter: erpPriceInputFormatter,
|
||||
disabled: true,
|
||||
@@ -139,6 +141,7 @@ export function useFormSchema(formType: string): VbenFormSchema[] {
|
||||
label: '优惠后金额',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
placeholder: '优惠后金额',
|
||||
precision: 2,
|
||||
formatter: erpPriceInputFormatter,
|
||||
@@ -159,6 +162,7 @@ export function useFormSchema(formType: string): VbenFormSchema[] {
|
||||
label: '其他费用',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
disabled: formType === 'detail',
|
||||
placeholder: '请输入其他费用',
|
||||
precision: 2,
|
||||
@@ -183,6 +187,7 @@ export function useFormSchema(formType: string): VbenFormSchema[] {
|
||||
label: '应退金额',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
precision: 2,
|
||||
min: 0,
|
||||
disabled: true,
|
||||
|
||||
@@ -82,6 +82,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '排序',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
placeholder: '请输入排序',
|
||||
},
|
||||
rules: 'required',
|
||||
@@ -99,6 +100,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '税率(%)',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
placeholder: '请输入税率',
|
||||
min: 0,
|
||||
precision: 2,
|
||||
|
||||
@@ -82,6 +82,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '排序',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
placeholder: '请输入排序',
|
||||
precision: 0,
|
||||
},
|
||||
@@ -100,6 +101,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '税率(%)',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
placeholder: '请输入税率',
|
||||
precision: 2,
|
||||
},
|
||||
|
||||
@@ -116,6 +116,7 @@ export function useFormSchema(formType: string): VbenFormSchema[] {
|
||||
label: '优惠率(%)',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
placeholder: '请输入优惠率',
|
||||
min: 0,
|
||||
max: 100,
|
||||
@@ -128,6 +129,7 @@ export function useFormSchema(formType: string): VbenFormSchema[] {
|
||||
label: '付款优惠',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
placeholder: '收款优惠',
|
||||
precision: 2,
|
||||
formatter: erpPriceInputFormatter,
|
||||
@@ -139,6 +141,7 @@ export function useFormSchema(formType: string): VbenFormSchema[] {
|
||||
label: '优惠后金额',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
placeholder: '优惠后金额',
|
||||
precision: 2,
|
||||
formatter: erpPriceInputFormatter,
|
||||
@@ -161,6 +164,7 @@ export function useFormSchema(formType: string): VbenFormSchema[] {
|
||||
{
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
placeholder: '请输入收取订金',
|
||||
precision: 2,
|
||||
min: 0,
|
||||
|
||||
@@ -134,6 +134,7 @@ export function useFormSchema(formType: string): VbenFormSchema[] {
|
||||
label: '优惠率(%)',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
placeholder: '请输入优惠率',
|
||||
min: 0,
|
||||
max: 100,
|
||||
@@ -146,6 +147,7 @@ export function useFormSchema(formType: string): VbenFormSchema[] {
|
||||
label: '收款优惠',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
placeholder: '付款优惠',
|
||||
precision: 2,
|
||||
formatter: erpPriceInputFormatter,
|
||||
@@ -157,6 +159,7 @@ export function useFormSchema(formType: string): VbenFormSchema[] {
|
||||
label: '优惠后金额',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
placeholder: '优惠后金额',
|
||||
precision: 2,
|
||||
formatter: erpPriceInputFormatter,
|
||||
@@ -177,6 +180,7 @@ export function useFormSchema(formType: string): VbenFormSchema[] {
|
||||
label: '其他费用',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
disabled: formType === 'detail',
|
||||
placeholder: '请输入其他费用',
|
||||
precision: 2,
|
||||
@@ -204,6 +208,7 @@ export function useFormSchema(formType: string): VbenFormSchema[] {
|
||||
label: '应收金额',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
precision: 2,
|
||||
min: 0,
|
||||
disabled: true,
|
||||
|
||||
@@ -130,6 +130,7 @@ export function useFormSchema(formType: string): VbenFormSchema[] {
|
||||
label: '优惠率(%)',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
placeholder: '请输入优惠率',
|
||||
min: 0,
|
||||
max: 100,
|
||||
@@ -142,6 +143,7 @@ export function useFormSchema(formType: string): VbenFormSchema[] {
|
||||
label: '退款优惠',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
precision: 2,
|
||||
formatter: erpPriceInputFormatter,
|
||||
disabled: true,
|
||||
@@ -152,6 +154,7 @@ export function useFormSchema(formType: string): VbenFormSchema[] {
|
||||
label: '优惠后金额',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
placeholder: '优惠后金额',
|
||||
precision: 2,
|
||||
formatter: erpPriceInputFormatter,
|
||||
@@ -172,6 +175,7 @@ export function useFormSchema(formType: string): VbenFormSchema[] {
|
||||
label: '其他费用',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
disabled: formType === 'detail',
|
||||
placeholder: '请输入其他费用',
|
||||
precision: 2,
|
||||
@@ -197,6 +201,7 @@ export function useFormSchema(formType: string): VbenFormSchema[] {
|
||||
label: '应收金额',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
precision: 2,
|
||||
min: 0,
|
||||
disabled: true,
|
||||
|
||||
@@ -51,6 +51,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '仓储费(元)',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
placeholder: '请输入仓储费,单位:元/天/KG',
|
||||
min: 0,
|
||||
precision: 2,
|
||||
@@ -61,6 +62,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '搬运费(元)',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
placeholder: '请输入搬运费,单位:元',
|
||||
min: 0,
|
||||
precision: 2,
|
||||
@@ -79,6 +81,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '排序',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
placeholder: '请输入排序',
|
||||
precision: 0,
|
||||
},
|
||||
|
||||
@@ -82,6 +82,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '主机端口',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
min: 0,
|
||||
placeholder: '请输入主机端口',
|
||||
},
|
||||
|
||||
@@ -68,6 +68,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '重试次数',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
placeholder: '请输入重试次数。设置为 0 时,不进行重试',
|
||||
min: 0,
|
||||
},
|
||||
@@ -78,6 +79,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '重试间隔',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
placeholder: '请输入重试间隔,单位:毫秒。设置为 0 时,无需间隔',
|
||||
min: 0,
|
||||
},
|
||||
@@ -88,6 +90,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '监控超时时间',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
placeholder: '请输入监控超时时间,单位:毫秒',
|
||||
min: 0,
|
||||
},
|
||||
|
||||
@@ -122,8 +122,8 @@ export function useAdvancedFormSchema(): VbenFormSchema[] {
|
||||
label: '设备经度',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
placeholder: '请输入设备经度',
|
||||
class: 'w-full',
|
||||
min: -180,
|
||||
max: 180,
|
||||
precision: 6,
|
||||
@@ -140,8 +140,8 @@ export function useAdvancedFormSchema(): VbenFormSchema[] {
|
||||
label: '设备纬度',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
placeholder: '请输入设备纬度',
|
||||
class: 'w-full',
|
||||
min: -90,
|
||||
max: 90,
|
||||
precision: 6,
|
||||
|
||||
@@ -62,7 +62,7 @@ function openEditForm(row: IotDeviceApi.Device) {
|
||||
<div>
|
||||
<h2 class="text-xl font-bold">{{ device.deviceName }}</h2>
|
||||
</div>
|
||||
<div class="space-x-2">
|
||||
<div class="flex gap-2">
|
||||
<Button
|
||||
v-if="product.status === 0"
|
||||
v-access:code="['iot:device:update']"
|
||||
|
||||
@@ -70,6 +70,7 @@ const [Form, formApi] = useVbenForm({
|
||||
label: '端口',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
placeholder: '请输入端口',
|
||||
min: 1,
|
||||
max: 65_535,
|
||||
@@ -86,6 +87,7 @@ const [Form, formApi] = useVbenForm({
|
||||
label: '从站地址',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
placeholder: '请输入从站地址,范围 1-247',
|
||||
min: 1,
|
||||
max: 247,
|
||||
@@ -98,6 +100,7 @@ const [Form, formApi] = useVbenForm({
|
||||
label: '连接超时(ms)',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
placeholder: '请输入连接超时时间',
|
||||
min: 1000,
|
||||
step: 1000,
|
||||
@@ -114,6 +117,7 @@ const [Form, formApi] = useVbenForm({
|
||||
label: '重试间隔(ms)',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
placeholder: '请输入重试间隔',
|
||||
min: 1000,
|
||||
step: 1000,
|
||||
|
||||
@@ -111,6 +111,7 @@ function useFormSchema(): VbenFormSchema[] {
|
||||
label: '寄存器地址',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
placeholder: '请输入寄存器地址',
|
||||
min: 0,
|
||||
max: 65_535,
|
||||
@@ -133,6 +134,7 @@ function useFormSchema(): VbenFormSchema[] {
|
||||
label: '寄存器数量',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
placeholder: '请输入寄存器数量',
|
||||
min: 1,
|
||||
max: 125,
|
||||
@@ -177,6 +179,7 @@ function useFormSchema(): VbenFormSchema[] {
|
||||
label: '缩放因子',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
placeholder: '请输入缩放因子',
|
||||
precision: 6,
|
||||
step: 0.1,
|
||||
@@ -188,6 +191,7 @@ function useFormSchema(): VbenFormSchema[] {
|
||||
label: '轮询间隔(ms)',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
placeholder: '请输入轮询间隔',
|
||||
min: 100,
|
||||
step: 1000,
|
||||
|
||||
@@ -284,7 +284,7 @@ onMounted(async () => {
|
||||
<DeviceImportFormModal @success="handleRefresh" />
|
||||
|
||||
<!-- 统一搜索工具栏 -->
|
||||
<Card :body-style="{ padding: '16px' }" class="mb-4">
|
||||
<Card :body-style="{ padding: '16px' }" class="!mb-2">
|
||||
<!-- 搜索表单 -->
|
||||
<div class="mb-3 flex flex-wrap items-center gap-3">
|
||||
<Select
|
||||
|
||||
@@ -35,8 +35,8 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '分类排序',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
placeholder: '请输入分类排序',
|
||||
class: 'w-full',
|
||||
min: 0,
|
||||
precision: 0,
|
||||
},
|
||||
|
||||
@@ -147,19 +147,6 @@ export function useBasicFormSchema(
|
||||
help: 'iot-gateway-server 默认根据接入的协议类型确定数据格式,仅 MQTT、EMQX 协议支持自定义序列化类型',
|
||||
rules: 'required',
|
||||
},
|
||||
// TODO @haohao:这个貌似不需要?!
|
||||
{
|
||||
fieldName: 'status',
|
||||
label: '产品状态',
|
||||
component: 'RadioGroup',
|
||||
componentProps: {
|
||||
options: getDictOptions(DICT_TYPE.IOT_PRODUCT_STATUS, 'number'),
|
||||
buttonStyle: 'solid',
|
||||
optionType: 'button',
|
||||
},
|
||||
defaultValue: 0,
|
||||
rules: 'required',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
@@ -248,15 +235,6 @@ export function useGridColumns(): VxeTableGridOptions['columns'] {
|
||||
name: 'CellImage',
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'status',
|
||||
title: '产品状态',
|
||||
minWidth: 100,
|
||||
cellRender: {
|
||||
name: 'CellDict',
|
||||
props: { type: DICT_TYPE.IOT_PRODUCT_STATUS },
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'createTime',
|
||||
title: '创建时间',
|
||||
|
||||
@@ -11,6 +11,7 @@ import { message, Tabs } from 'ant-design-vue';
|
||||
import { getDeviceCount } from '#/api/iot/device/device';
|
||||
import { getProduct } from '#/api/iot/product/product';
|
||||
import IoTProductThingModel from '#/views/iot/thingmodel/index.vue';
|
||||
import { IOT_PROVIDE_KEY } from '#/views/iot/utils/constants';
|
||||
|
||||
import ProductDetailsHeader from './modules/header.vue';
|
||||
import ProductDetailsInfo from './modules/info.vue';
|
||||
@@ -25,7 +26,8 @@ const loading = ref(true);
|
||||
const product = ref<IotProductApi.Product>({} as IotProductApi.Product);
|
||||
const activeTab = ref('info');
|
||||
|
||||
provide('product', product); // 提供产品信息给子组件
|
||||
/** 向子组件提供产品信息 */
|
||||
provide(IOT_PROVIDE_KEY.PRODUCT, product);
|
||||
|
||||
/** 获取产品详情 */
|
||||
async function getProductData(productId: number) {
|
||||
@@ -82,10 +84,7 @@ onMounted(async () => {
|
||||
<ProductDetailsInfo v-if="activeTab === 'info'" :product="product" />
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane key="thingModel" tab="物模型(功能定义)">
|
||||
<IoTProductThingModel
|
||||
v-if="activeTab === 'thingModel'"
|
||||
:product-id="id"
|
||||
/>
|
||||
<IoTProductThingModel v-if="activeTab === 'thingModel'" />
|
||||
</Tabs.TabPane>
|
||||
</Tabs>
|
||||
</Page>
|
||||
|
||||
@@ -90,7 +90,7 @@ function handleUnpublish(product: IotProductApi.Product) {
|
||||
<div>
|
||||
<h2 class="text-xl font-bold">{{ product.name }}</h2>
|
||||
</div>
|
||||
<div class="space-x-2">
|
||||
<div class="flex gap-2">
|
||||
<Button
|
||||
:disabled="product.status === ProductStatusEnum.PUBLISHED"
|
||||
@click="openEditForm(product)"
|
||||
|
||||
@@ -175,7 +175,7 @@ onMounted(() => {
|
||||
<FormModal @success="handleRefresh" />
|
||||
|
||||
<!-- 统一搜索工具栏 -->
|
||||
<Card :body-style="{ padding: '16px' }" class="mb-4">
|
||||
<Card :body-style="{ padding: '16px' }" class="!mb-2">
|
||||
<!-- 搜索表单 -->
|
||||
<div class="mb-3 flex items-center gap-3">
|
||||
<Input
|
||||
|
||||
@@ -115,11 +115,6 @@ onMounted(() => {
|
||||
<div class="ml-3 min-w-0 flex-1">
|
||||
<div class="product-title">{{ item.name }}</div>
|
||||
</div>
|
||||
<DictTag
|
||||
:type="DICT_TYPE.IOT_PRODUCT_STATUS"
|
||||
:value="item.status"
|
||||
class="status-tag"
|
||||
/>
|
||||
</div>
|
||||
<!-- 内容区域 -->
|
||||
<div class="mb-3 flex items-start">
|
||||
@@ -269,11 +264,6 @@ onMounted(() => {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
// 状态标签
|
||||
.status-tag {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
// 信息列表
|
||||
.info-list {
|
||||
.info-item {
|
||||
|
||||
@@ -4,6 +4,8 @@ import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
import { DICT_TYPE } from '@vben/constants';
|
||||
import { getDictOptions } from '@vben/hooks';
|
||||
|
||||
import { getDataTypeOptionsLabel } from '#/views/iot/utils/constants';
|
||||
|
||||
/** 列表的搜索表单 */
|
||||
export function useGridFormSchema(): VbenFormSchema[] {
|
||||
return [
|
||||
@@ -27,7 +29,7 @@ export function useGridColumns(): VxeTableGridOptions['columns'] {
|
||||
{
|
||||
field: 'type',
|
||||
title: '功能类型',
|
||||
minWidth: 20,
|
||||
minWidth: 100,
|
||||
cellRender: {
|
||||
name: 'CellDict',
|
||||
props: { type: DICT_TYPE.IOT_THING_MODEL_TYPE },
|
||||
@@ -41,17 +43,16 @@ export function useGridColumns(): VxeTableGridOptions['columns'] {
|
||||
{
|
||||
field: 'identifier',
|
||||
title: '标识符',
|
||||
minWidth: 20,
|
||||
minWidth: 120,
|
||||
},
|
||||
{
|
||||
field: 'dataType',
|
||||
title: '数据类型',
|
||||
minWidth: 50,
|
||||
slots: { default: 'dataType' },
|
||||
minWidth: 100,
|
||||
formatter: ({ row }) =>
|
||||
getDataTypeOptionsLabel(row.property?.dataType) || '-',
|
||||
},
|
||||
{
|
||||
field: 'property',
|
||||
title: '属性',
|
||||
title: '数据定义',
|
||||
minWidth: 200,
|
||||
slots: { default: 'dataDefinition' },
|
||||
},
|
||||
|
||||
@@ -1,99 +1,90 @@
|
||||
<script setup lang="ts">
|
||||
import type { Ref } from 'vue';
|
||||
|
||||
import type { IotProductApi } from '#/api/iot/product/product';
|
||||
import type { ThingModelData } from '#/api/iot/thingmodel';
|
||||
|
||||
import { onMounted, provide, ref } from 'vue';
|
||||
import { computed, inject } from 'vue';
|
||||
|
||||
import { Page } from '@vben/common-ui';
|
||||
import { Page, useVbenModal } from '@vben/common-ui';
|
||||
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import { getProduct } from '#/api/iot/product/product';
|
||||
import { deleteThingModel, getThingModelPage } from '#/api/iot/thingmodel';
|
||||
import { $t } from '#/locales';
|
||||
import { IOT_PROVIDE_KEY } from '#/views/iot/utils/constants';
|
||||
|
||||
import { getDataTypeOptionsLabel, IOT_PROVIDE_KEY } from '../utils/constants';
|
||||
import { useGridColumns, useGridFormSchema } from './data';
|
||||
import { DataDefinition } from './modules/components';
|
||||
import ThingModelForm from './modules/thing-model-form.vue';
|
||||
import ThingModelTsl from './modules/thing-model-tsl.vue';
|
||||
import Form from './modules/form.vue';
|
||||
import Tsl from './modules/tsl.vue';
|
||||
|
||||
defineOptions({ name: 'IoTThingModel' });
|
||||
|
||||
const props = defineProps<{
|
||||
productId: number;
|
||||
}>();
|
||||
const product = inject<Ref<IotProductApi.Product>>(IOT_PROVIDE_KEY.PRODUCT);
|
||||
const productId = computed(() => product?.value?.id);
|
||||
|
||||
const product = ref<IotProductApi.Product>({} as IotProductApi.Product); // 产品信息
|
||||
const [FormModal, formModalApi] = useVbenModal({
|
||||
connectedComponent: Form,
|
||||
destroyOnClose: true,
|
||||
});
|
||||
|
||||
provide(IOT_PROVIDE_KEY.PRODUCT, product); // 提供产品信息给子组件
|
||||
const [TslModal, tslModalApi] = useVbenModal({
|
||||
connectedComponent: Tsl,
|
||||
destroyOnClose: true,
|
||||
});
|
||||
|
||||
// TODO @haohao:form 是不是用 web-antd/src/views/system/user/index.vue 里 open 的风格;
|
||||
const thingModelFormRef = ref();
|
||||
// TODO @haohao:thingModelTSLRef 应该是个 modal,也可以调整下风格;
|
||||
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();
|
||||
gridApi.query();
|
||||
}
|
||||
|
||||
// 获取产品信息
|
||||
async function getProductData() {
|
||||
/** 新增物模型 */
|
||||
function handleCreate() {
|
||||
formModalApi.setData(null).open();
|
||||
}
|
||||
|
||||
/** 编辑物模型 */
|
||||
function handleEdit(row: ThingModelData) {
|
||||
formModalApi.setData({ id: row.id }).open();
|
||||
}
|
||||
|
||||
/** 删除物模型 */
|
||||
async function handleDelete(row: ThingModelData) {
|
||||
const hideLoading = message.loading({
|
||||
content: $t('ui.actionMessage.deleting', [row.name]),
|
||||
duration: 0,
|
||||
});
|
||||
try {
|
||||
product.value = await getProduct(props.productId);
|
||||
} catch (error) {
|
||||
console.error('获取产品信息失败:', error);
|
||||
await deleteThingModel(row.id!);
|
||||
message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
|
||||
handleRefresh();
|
||||
} finally {
|
||||
hideLoading();
|
||||
}
|
||||
}
|
||||
|
||||
// TODO @haohao:字段的顺序,调整成别的模块一直;
|
||||
/** 打开 TSL 弹窗 */
|
||||
function handleOpenTsl() {
|
||||
tslModalApi.open();
|
||||
}
|
||||
|
||||
const [Grid, gridApi] = useVbenVxeGrid({
|
||||
formOptions: {
|
||||
schema: useGridFormSchema(),
|
||||
},
|
||||
gridOptions: {
|
||||
columns: useGridColumns(),
|
||||
height: 'auto',
|
||||
keepSource: true,
|
||||
proxyConfig: {
|
||||
ajax: {
|
||||
query: async ({ page }: any, formValues: any) => {
|
||||
query: async ({ page }, formValues) => {
|
||||
return await getThingModelPage({
|
||||
pageNo: page.currentPage,
|
||||
pageSize: page.pageSize,
|
||||
productId: props.productId,
|
||||
productId: productId.value,
|
||||
...formValues,
|
||||
});
|
||||
},
|
||||
@@ -108,64 +99,55 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
||||
search: true,
|
||||
},
|
||||
},
|
||||
formOptions: {
|
||||
schema: useGridFormSchema(),
|
||||
},
|
||||
});
|
||||
|
||||
// 初始化
|
||||
onMounted(async () => {
|
||||
await getProductData();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Page auto-content-height>
|
||||
<Grid>
|
||||
<FormModal @success="handleRefresh" />
|
||||
<TslModal />
|
||||
|
||||
<Grid table-title="物模型列表">
|
||||
<template #toolbar-tools>
|
||||
<TableAction
|
||||
:actions="[
|
||||
{
|
||||
label: '添加功能',
|
||||
label: $t('ui.actionTitle.create', ['物模型']),
|
||||
type: 'primary',
|
||||
icon: ACTION_ICON.ADD,
|
||||
auth: ['iot:thing-model:create'],
|
||||
onClick: handleCreate,
|
||||
},
|
||||
{
|
||||
label: 'TSL',
|
||||
type: 'default',
|
||||
color: 'success', // TODO @haohao:貌似 color 可以去掉?应该是不生效的哈。ps:另外,也给搞个 icon?
|
||||
onClick: handleOpenTSL,
|
||||
type: 'primary',
|
||||
auth: ['iot:thing-model:query'],
|
||||
onClick: handleOpenTsl,
|
||||
},
|
||||
]"
|
||||
/>
|
||||
</template>
|
||||
<!-- 数据类型列 -->
|
||||
<template #dataType="{ row }">
|
||||
<span>{{ getDataTypeLabel(row) }}</span>
|
||||
</template>
|
||||
<!-- 数据定义列 -->
|
||||
<!-- TODO @haohao:可以在 data.ts 就写掉这个逻辑; -->
|
||||
<template #dataDefinition="{ row }">
|
||||
<DataDefinition :data="row" />
|
||||
</template>
|
||||
<!-- 操作列 -->
|
||||
<template #actions="{ row }">
|
||||
<TableAction
|
||||
:actions="[
|
||||
{
|
||||
label: '编辑',
|
||||
label: $t('common.edit'),
|
||||
type: 'link',
|
||||
icon: ACTION_ICON.EDIT,
|
||||
auth: ['iot:thing-model:update'],
|
||||
onClick: handleEdit.bind(null, row),
|
||||
},
|
||||
{
|
||||
label: '删除',
|
||||
label: $t('common.delete'),
|
||||
type: 'link',
|
||||
danger: true,
|
||||
icon: ACTION_ICON.DELETE,
|
||||
auth: ['iot:thing-model:delete'],
|
||||
popConfirm: {
|
||||
title: '确认删除该功能吗?',
|
||||
title: $t('ui.actionMessage.deleteConfirm', [row.name]),
|
||||
confirm: handleDelete.bind(null, row),
|
||||
},
|
||||
},
|
||||
@@ -173,10 +155,5 @@ onMounted(async () => {
|
||||
/>
|
||||
</template>
|
||||
</Grid>
|
||||
|
||||
<!-- 物模型表单 -->
|
||||
<ThingModelForm ref="thingModelFormRef" @success="handleRefresh" />
|
||||
<!-- TSL 弹窗 -->
|
||||
<ThingModelTsl ref="thingModelTSLRef" />
|
||||
</Page>
|
||||
</template>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
<!-- TODO @haohao:如果是模块内用的,就用 modules 里。(等后面点在看,优先级:低) -->
|
||||
<script lang="ts" setup>
|
||||
import type { ThingModelData } from '#/api/iot/thingmodel';
|
||||
|
||||
@@ -13,83 +12,63 @@ import {
|
||||
IoTThingModelTypeEnum,
|
||||
} from '#/views/iot/utils/constants';
|
||||
|
||||
/** 数据定义展示组件 */
|
||||
defineOptions({ name: 'DataDefinition' });
|
||||
const NUMBER_TYPES = new Set<string>([
|
||||
IoTDataSpecsDataTypeEnum.INT,
|
||||
IoTDataSpecsDataTypeEnum.DOUBLE,
|
||||
IoTDataSpecsDataTypeEnum.FLOAT,
|
||||
]);
|
||||
const PLACEHOLDER_TYPES = new Set<string>([
|
||||
IoTDataSpecsDataTypeEnum.ARRAY,
|
||||
IoTDataSpecsDataTypeEnum.STRUCT,
|
||||
IoTDataSpecsDataTypeEnum.DATE,
|
||||
]);
|
||||
const LIST_TYPES = new Set<string>([
|
||||
IoTDataSpecsDataTypeEnum.BOOL,
|
||||
IoTDataSpecsDataTypeEnum.ENUM,
|
||||
]);
|
||||
|
||||
const props = defineProps<{ data: ThingModelData }>();
|
||||
|
||||
const formattedDataSpecsList = computed(() => {
|
||||
if (
|
||||
!props.data.property?.dataSpecsList ||
|
||||
props.data.property.dataSpecsList.length === 0
|
||||
) {
|
||||
if (!props.data.property?.dataSpecsList?.length) {
|
||||
return '';
|
||||
}
|
||||
return props.data.property.dataSpecsList
|
||||
.map((item) => `${item.value}-${item.name}`)
|
||||
.join('、');
|
||||
}); // 格式化布尔值和枚举值列表为字符串
|
||||
});
|
||||
|
||||
const shortText = computed(() => {
|
||||
if (
|
||||
!props.data.property?.dataSpecsList ||
|
||||
props.data.property.dataSpecsList.length === 0
|
||||
) {
|
||||
const list = props.data.property?.dataSpecsList;
|
||||
if (!list?.length) {
|
||||
return '-';
|
||||
}
|
||||
const first = props.data.property.dataSpecsList[0];
|
||||
const count = props.data.property.dataSpecsList.length;
|
||||
return count > 1
|
||||
? `${first.value}-${first.name} 等${count}项`
|
||||
const first = list[0];
|
||||
return list.length > 1
|
||||
? `${first.value}-${first.name} 等 ${list.length} 项`
|
||||
: `${first.value}-${first.name}`;
|
||||
}); // 显示的简短文本(第一个值)
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- 属性 -->
|
||||
<template v-if="Number(data.type) === IoTThingModelTypeEnum.PROPERTY">
|
||||
<!-- 非列表型:数值 -->
|
||||
<div
|
||||
v-if="
|
||||
[
|
||||
IoTDataSpecsDataTypeEnum.INT,
|
||||
IoTDataSpecsDataTypeEnum.DOUBLE,
|
||||
IoTDataSpecsDataTypeEnum.FLOAT,
|
||||
].includes(data.property?.dataType as any)
|
||||
"
|
||||
>
|
||||
<template v-if="data.type === IoTThingModelTypeEnum.PROPERTY">
|
||||
<div v-if="NUMBER_TYPES.has(data.property?.dataType as any)">
|
||||
取值范围:{{
|
||||
`${data.property?.dataSpecs?.min}~${data.property?.dataSpecs?.max}`
|
||||
}}
|
||||
</div>
|
||||
<!-- 非列表型:文本 -->
|
||||
<div v-if="IoTDataSpecsDataTypeEnum.TEXT === data.property?.dataType">
|
||||
<div v-if="data.property?.dataType === IoTDataSpecsDataTypeEnum.TEXT">
|
||||
数据长度:{{ data.property?.dataSpecs?.length }}
|
||||
</div>
|
||||
<!-- 列表型: 数组、结构、时间(特殊) -->
|
||||
<div
|
||||
v-if="
|
||||
[
|
||||
IoTDataSpecsDataTypeEnum.ARRAY,
|
||||
IoTDataSpecsDataTypeEnum.STRUCT,
|
||||
IoTDataSpecsDataTypeEnum.DATE,
|
||||
].includes(data.property?.dataType as any)
|
||||
"
|
||||
>
|
||||
-
|
||||
</div>
|
||||
<!-- 列表型: 布尔值、枚举 -->
|
||||
<div
|
||||
v-if="
|
||||
[IoTDataSpecsDataTypeEnum.BOOL, IoTDataSpecsDataTypeEnum.ENUM].includes(
|
||||
data.property?.dataType as any,
|
||||
)
|
||||
"
|
||||
>
|
||||
<div v-if="PLACEHOLDER_TYPES.has(data.property?.dataType as any)">-</div>
|
||||
<div v-if="LIST_TYPES.has(data.property?.dataType as any)">
|
||||
<Tooltip :title="formattedDataSpecsList" placement="topLeft">
|
||||
<span class="data-specs-text">
|
||||
<span
|
||||
class="cursor-help border-b border-dashed border-gray-300 hover:border-blue-500 hover:text-blue-500"
|
||||
>
|
||||
{{
|
||||
IoTDataSpecsDataTypeEnum.BOOL === data.property?.dataType
|
||||
data.property?.dataType === IoTDataSpecsDataTypeEnum.BOOL
|
||||
? '布尔值'
|
||||
: '枚举值'
|
||||
}}:{{ shortText }}
|
||||
@@ -98,25 +77,12 @@ const shortText = computed(() => {
|
||||
</div>
|
||||
</template>
|
||||
<!-- 服务 -->
|
||||
<div v-if="Number(data.type) === IoTThingModelTypeEnum.SERVICE">
|
||||
<div v-if="data.type === IoTThingModelTypeEnum.SERVICE">
|
||||
调用方式:
|
||||
{{ getThingModelServiceCallTypeLabel(data.service?.callType as any) }}
|
||||
</div>
|
||||
<!-- 事件 -->
|
||||
<div v-if="Number(data.type) === IoTThingModelTypeEnum.EVENT">
|
||||
<div v-if="data.type === IoTThingModelTypeEnum.EVENT">
|
||||
事件类型:{{ getEventTypeLabel(data.event?.type as any) }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
/** TODO @haohao:tindwind */
|
||||
.data-specs-text {
|
||||
cursor: help;
|
||||
border-bottom: 1px dashed #d9d9d9;
|
||||
|
||||
&:hover {
|
||||
color: #1890ff;
|
||||
border-bottom-color: #1890ff;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -5,21 +5,29 @@ import type { Ref } from 'vue';
|
||||
import { useVModel } from '@vueuse/core';
|
||||
import { Form, Input, Radio } from 'ant-design-vue';
|
||||
|
||||
import { ThingModelFormRules } from '#/api/iot/thingmodel';
|
||||
import {
|
||||
getDataTypeOptions,
|
||||
IoTDataSpecsDataTypeEnum,
|
||||
} from '#/views/iot/utils/constants';
|
||||
|
||||
import ThingModelStructDataSpecs from './thing-model-struct-data-specs.vue';
|
||||
import ThingModelStructDataSpecs from './struct.vue';
|
||||
|
||||
/** 数组型的 dataSpecs 配置组件 */
|
||||
defineOptions({ name: 'ThingModelArrayDataSpecs' });
|
||||
/** 数组元素禁止选择的类型 */
|
||||
const EXCLUDED_CHILD_TYPES = new Set<string>([
|
||||
IoTDataSpecsDataTypeEnum.ENUM,
|
||||
IoTDataSpecsDataTypeEnum.ARRAY,
|
||||
IoTDataSpecsDataTypeEnum.DATE,
|
||||
]);
|
||||
const childDataTypeOptions = getDataTypeOptions().filter(
|
||||
(item) => !EXCLUDED_CHILD_TYPES.has(item.value),
|
||||
);
|
||||
|
||||
const props = defineProps<{ modelValue: any }>();
|
||||
const emits = defineEmits(['update:modelValue']);
|
||||
const dataSpecs = useVModel(props, 'modelValue', emits) as Ref<any>;
|
||||
|
||||
/** 元素类型改变时间。当值为 struct 时,对 dataSpecs 中的 dataSpecsList 进行初始化 */
|
||||
/** 元素类型切到 struct 时,初始化 dataSpecsList 占位 */
|
||||
function handleChange(val: any) {
|
||||
if (val !== IoTDataSpecsDataTypeEnum.STRUCT) {
|
||||
return;
|
||||
@@ -31,29 +39,25 @@ function handleChange(val: any) {
|
||||
<template>
|
||||
<Form.Item
|
||||
:name="['property', 'dataSpecs', 'childDataType']"
|
||||
:rules="ThingModelFormRules.childDataType"
|
||||
label="元素类型"
|
||||
>
|
||||
<Radio.Group v-model:value="dataSpecs.childDataType" @change="handleChange">
|
||||
<template v-for="item in getDataTypeOptions()" :key="item.value">
|
||||
<Radio
|
||||
v-if="
|
||||
!(
|
||||
[
|
||||
IoTDataSpecsDataTypeEnum.ENUM,
|
||||
IoTDataSpecsDataTypeEnum.ARRAY,
|
||||
IoTDataSpecsDataTypeEnum.DATE,
|
||||
] as any[]
|
||||
).includes(item.value)
|
||||
"
|
||||
:value="item.value"
|
||||
class="w-1/3"
|
||||
>
|
||||
{{ `${item.value}(${item.label})` }}
|
||||
</Radio>
|
||||
</template>
|
||||
<Radio
|
||||
v-for="item in childDataTypeOptions"
|
||||
:key="item.value"
|
||||
:value="item.value"
|
||||
class="w-1/3"
|
||||
>
|
||||
{{ `${item.value}(${item.label})` }}
|
||||
</Radio>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
<Form.Item :name="['property', 'dataSpecs', 'size']" label="元素个数">
|
||||
<Form.Item
|
||||
:name="['property', 'dataSpecs', 'size']"
|
||||
:rules="ThingModelFormRules.size"
|
||||
label="元素个数"
|
||||
>
|
||||
<Input
|
||||
v-model:value="dataSpecs.size"
|
||||
placeholder="请输入数组中的元素个数"
|
||||
@@ -0,0 +1,131 @@
|
||||
<!-- dataType:enum 数组类型 -->
|
||||
<script lang="ts" setup>
|
||||
import type { Ref } from 'vue';
|
||||
|
||||
import { isEmpty } from '@vben/utils';
|
||||
|
||||
import { useVModel } from '@vueuse/core';
|
||||
import { Button, Form, Input, message } from 'ant-design-vue';
|
||||
|
||||
import { buildIdentifierLikeNameValidator } from '#/api/iot/thingmodel';
|
||||
|
||||
const props = defineProps<{ modelValue: any }>();
|
||||
const emits = defineEmits(['update:modelValue']);
|
||||
const dataSpecsList = useVModel(props, 'modelValue', emits) as Ref<any[]>;
|
||||
|
||||
const validateEnumName = buildIdentifierLikeNameValidator('枚举描述');
|
||||
|
||||
/** 添加枚举项 */
|
||||
function addEnum() {
|
||||
dataSpecsList.value.push({ name: '', value: '' } as any);
|
||||
}
|
||||
|
||||
/** 删除枚举项 */
|
||||
function deleteEnum(index: number) {
|
||||
if (dataSpecsList.value.length === 1) {
|
||||
message.warning('至少需要一个枚举项');
|
||||
return;
|
||||
}
|
||||
dataSpecsList.value.splice(index, 1);
|
||||
}
|
||||
|
||||
/** 校验单项枚举值:必填、数字、不重复 */
|
||||
function validateEnumValue(_rule: any, value: any, callback: any) {
|
||||
if (isEmpty(value)) {
|
||||
callback(new Error('枚举值不能为空'));
|
||||
return;
|
||||
}
|
||||
if (Number.isNaN(Number(value))) {
|
||||
callback(new Error('枚举值必须是数字'));
|
||||
return;
|
||||
}
|
||||
const sameCount = dataSpecsList.value.filter((it) => it.value === value)
|
||||
.length;
|
||||
if (sameCount > 1) {
|
||||
callback(new Error('枚举值不能重复'));
|
||||
return;
|
||||
}
|
||||
callback();
|
||||
}
|
||||
|
||||
/** 校验整个枚举列表:非空、无空项、无非法数字、无重复 */
|
||||
function validateEnumList(_rule: any, _value: any, callback: any) {
|
||||
if (isEmpty(dataSpecsList.value)) {
|
||||
callback(new Error('请至少添加一个枚举项'));
|
||||
return;
|
||||
}
|
||||
const hasEmpty = dataSpecsList.value.some(
|
||||
(item) => isEmpty(item.value) || isEmpty(item.name),
|
||||
);
|
||||
if (hasEmpty) {
|
||||
callback(new Error('存在未填写的枚举值或描述'));
|
||||
return;
|
||||
}
|
||||
const hasInvalidNumber = dataSpecsList.value.some((item) =>
|
||||
Number.isNaN(Number(item.value)),
|
||||
);
|
||||
if (hasInvalidNumber) {
|
||||
callback(new Error('存在非数字的枚举值'));
|
||||
return;
|
||||
}
|
||||
const values = dataSpecsList.value.map((item) => item.value);
|
||||
if (new Set(values).size !== values.length) {
|
||||
callback(new Error('存在重复的枚举值'));
|
||||
return;
|
||||
}
|
||||
callback();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Form.Item
|
||||
:rules="[{ validator: validateEnumList, trigger: 'change' }]"
|
||||
label="枚举项"
|
||||
>
|
||||
<div class="flex flex-col">
|
||||
<div class="flex items-center">
|
||||
<span class="flex-1"> 参数值 </span>
|
||||
<span class="flex-1"> 参数描述 </span>
|
||||
</div>
|
||||
<div
|
||||
v-for="(item, index) in dataSpecsList"
|
||||
:key="index"
|
||||
class="mb-[5px] flex items-center justify-between"
|
||||
>
|
||||
<Form.Item
|
||||
:name="['property', 'dataSpecsList', index, 'value']"
|
||||
:rules="[
|
||||
{ required: true, message: '枚举值不能为空', trigger: 'blur' },
|
||||
{ validator: validateEnumValue, trigger: 'blur' },
|
||||
]"
|
||||
class="mb-0 flex-1"
|
||||
>
|
||||
<Input v-model:value="item.value" placeholder="请输入枚举值,如「0」" />
|
||||
</Form.Item>
|
||||
<span class="mx-2">~</span>
|
||||
<Form.Item
|
||||
:name="['property', 'dataSpecsList', index, 'name']"
|
||||
:rules="[
|
||||
{ required: true, message: '枚举描述不能为空', trigger: 'blur' },
|
||||
{ validator: validateEnumName, trigger: 'blur' },
|
||||
]"
|
||||
class="mb-0 flex-1"
|
||||
>
|
||||
<Input v-model:value="item.name" placeholder="对该枚举项的描述" />
|
||||
</Form.Item>
|
||||
<Button class="ml-2.5" type="link" @click="deleteEnum(index)">
|
||||
删除
|
||||
</Button>
|
||||
</div>
|
||||
<Button type="link" @click="addEnum">+ 添加枚举项</Button>
|
||||
</div>
|
||||
</Form.Item>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep(.ant-form-item) {
|
||||
.ant-form-item {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,4 @@
|
||||
export { default as ThingModelArrayDataSpecs } from './array.vue';
|
||||
export { default as ThingModelEnumDataSpecs } from './enum.vue';
|
||||
export { default as ThingModelNumberDataSpecs } from './number.vue';
|
||||
export { default as ThingModelStructDataSpecs } from './struct.vue';
|
||||
@@ -10,9 +10,6 @@ import { getDictOptions } from '@vben/hooks';
|
||||
import { useVModel } from '@vueuse/core';
|
||||
import { Form, Input, Select } from 'ant-design-vue';
|
||||
|
||||
/** 数值型的 dataSpecs 配置组件 */
|
||||
defineOptions({ name: 'ThingModelNumberDataSpecs' });
|
||||
|
||||
const props = defineProps<{ modelValue: any }>();
|
||||
const emits = defineEmits(['update:modelValue']);
|
||||
const dataSpecs = useVModel(
|
||||
@@ -21,13 +18,15 @@ const dataSpecs = useVModel(
|
||||
emits,
|
||||
) as Ref<DataSpecsNumberData>;
|
||||
|
||||
/** 单位发生变化时触发 */
|
||||
const unitChange = (UnitSpecs: any) => {
|
||||
if (!UnitSpecs) return;
|
||||
const [unitName, unit] = String(UnitSpecs).split('-');
|
||||
/** 单位下拉变化时,拆出 unitName 与 unit 回写 */
|
||||
function unitChange(unitSpecs: any) {
|
||||
if (!unitSpecs) {
|
||||
return;
|
||||
}
|
||||
const [unitName, unit] = String(unitSpecs).split('-');
|
||||
dataSpecs.value.unitName = unitName;
|
||||
dataSpecs.value.unit = unit;
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -52,7 +51,7 @@ const unitChange = (UnitSpecs: any) => {
|
||||
"
|
||||
show-search
|
||||
placeholder="请选择单位"
|
||||
class="w-1/1"
|
||||
class="w-full"
|
||||
@change="unitChange"
|
||||
>
|
||||
<Select.Option
|
||||
@@ -0,0 +1,157 @@
|
||||
<script lang="ts" setup>
|
||||
import type { Ref } from 'vue';
|
||||
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
import { useVbenModal } from '@vben/common-ui';
|
||||
import { isEmpty } from '@vben/utils';
|
||||
|
||||
import { useVModel } from '@vueuse/core';
|
||||
import { Button, Divider, Form, Input } from 'ant-design-vue';
|
||||
|
||||
import { ThingModelFormRules } from '#/api/iot/thingmodel';
|
||||
import { IoTDataSpecsDataTypeEnum } from '#/views/iot/utils/constants';
|
||||
|
||||
import ThingModelProperty from '../property.vue';
|
||||
|
||||
const props = defineProps<{ modelValue: any }>();
|
||||
const emits = defineEmits(['update:modelValue']);
|
||||
const dataSpecsList = useVModel(props, 'modelValue', emits) as Ref<any[]>;
|
||||
|
||||
const structFormRef = ref();
|
||||
const formData = ref<any>(buildEmptyFormData());
|
||||
|
||||
const [Modal, modalApi] = useVbenModal({
|
||||
async onConfirm() {
|
||||
try {
|
||||
await structFormRef.value?.validate();
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
const data = formData.value;
|
||||
const item = {
|
||||
identifier: data.identifier,
|
||||
name: data.name,
|
||||
description: data.description,
|
||||
dataType: IoTDataSpecsDataTypeEnum.STRUCT,
|
||||
childDataType: data.property.dataType,
|
||||
dataSpecs:
|
||||
!isEmpty(data.property.dataSpecs) &&
|
||||
Object.keys(data.property.dataSpecs).length > 1
|
||||
? data.property.dataSpecs
|
||||
: undefined,
|
||||
dataSpecsList: isEmpty(data.property.dataSpecsList)
|
||||
? undefined
|
||||
: data.property.dataSpecsList,
|
||||
};
|
||||
const existingIndex = dataSpecsList.value.findIndex(
|
||||
(spec) => spec.identifier === data.identifier,
|
||||
);
|
||||
if (existingIndex === -1) {
|
||||
dataSpecsList.value.push(item);
|
||||
} else {
|
||||
dataSpecsList.value[existingIndex] = item;
|
||||
}
|
||||
await modalApi.close();
|
||||
},
|
||||
onOpenChange(isOpen: boolean) {
|
||||
if (!isOpen) {
|
||||
return;
|
||||
}
|
||||
formData.value = buildEmptyFormData();
|
||||
structFormRef.value?.clearValidate?.();
|
||||
const data = modalApi.getData<any>();
|
||||
if (isEmpty(data)) {
|
||||
return;
|
||||
}
|
||||
formData.value = {
|
||||
identifier: data.identifier ?? '',
|
||||
name: data.name ?? '',
|
||||
description: data.description ?? '',
|
||||
property: {
|
||||
dataType: data.childDataType ?? IoTDataSpecsDataTypeEnum.INT,
|
||||
dataSpecs: data.dataSpecs ?? {},
|
||||
dataSpecsList: data.dataSpecsList ?? [],
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
/** 构造空白结构体表单 */
|
||||
function buildEmptyFormData() {
|
||||
return {
|
||||
identifier: '',
|
||||
name: '',
|
||||
description: '',
|
||||
property: {
|
||||
dataType: IoTDataSpecsDataTypeEnum.INT,
|
||||
dataSpecs: { dataType: IoTDataSpecsDataTypeEnum.INT },
|
||||
dataSpecsList: [],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/** 打开结构体表单 */
|
||||
function openStructForm(val: any) {
|
||||
modalApi.setData(val).open();
|
||||
}
|
||||
|
||||
/** 删除结构体项 */
|
||||
function deleteStructItem(index: number) {
|
||||
dataSpecsList.value.splice(index, 1);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (isEmpty(dataSpecsList.value)) {
|
||||
dataSpecsList.value = [];
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Form.Item label="属性对象">
|
||||
<div
|
||||
v-for="(item, index) in dataSpecsList"
|
||||
:key="index"
|
||||
class="mb-2.5 flex w-full justify-between bg-gray-100 px-2.5 dark:bg-gray-800"
|
||||
>
|
||||
<span>参数:{{ item.name }}</span>
|
||||
<div>
|
||||
<Button type="link" @click="openStructForm(item)">编辑</Button>
|
||||
<Divider type="vertical" />
|
||||
<Button danger type="link" @click="deleteStructItem(index)">
|
||||
删除
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<Button type="link" @click="openStructForm(null)">+ 新增参数</Button>
|
||||
</Form.Item>
|
||||
|
||||
<!-- 结构体参数表单 -->
|
||||
<Modal class="w-2/5" title="结构体参数">
|
||||
<Form
|
||||
ref="structFormRef"
|
||||
:label-col="{ span: 6 }"
|
||||
:model="formData"
|
||||
:wrapper-col="{ span: 18 }"
|
||||
class="mx-4"
|
||||
>
|
||||
<Form.Item
|
||||
:rules="ThingModelFormRules.name"
|
||||
label="参数名称"
|
||||
name="name"
|
||||
>
|
||||
<Input v-model:value="formData.name" placeholder="请输入参数名称" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
:rules="ThingModelFormRules.identifier"
|
||||
label="标识符"
|
||||
name="identifier"
|
||||
>
|
||||
<Input v-model:value="formData.identifier" placeholder="请输入标识符" />
|
||||
</Form.Item>
|
||||
<!-- 属性配置 -->
|
||||
<ThingModelProperty v-model="formData.property" is-struct-data-specs />
|
||||
</Form>
|
||||
</Modal>
|
||||
</template>
|
||||
@@ -1,4 +0,0 @@
|
||||
export { default as ThingModelArrayDataSpecs } from './thing-model-array-data-specs.vue';
|
||||
export { default as ThingModelEnumDataSpecs } from './thing-model-enum-data-specs.vue';
|
||||
export { default as ThingModelNumberDataSpecs } from './thing-model-number-data-specs.vue';
|
||||
export { default as ThingModelStructDataSpecs } from './thing-model-struct-data-specs.vue';
|
||||
@@ -1,67 +0,0 @@
|
||||
<!-- dataType:enum 数组类型 -->
|
||||
<script lang="ts" setup>
|
||||
import type { Ref } from 'vue';
|
||||
|
||||
import { useVModel } from '@vueuse/core';
|
||||
import { Button, Form, Input, message } from 'ant-design-vue';
|
||||
|
||||
/** 枚举型的 dataSpecs 配置组件 */
|
||||
defineOptions({ name: 'ThingModelEnumDataSpecs' });
|
||||
|
||||
const props = defineProps<{ modelValue: any }>();
|
||||
const emits = defineEmits(['update:modelValue']);
|
||||
const dataSpecsList = useVModel(props, 'modelValue', emits) as Ref<any[]>;
|
||||
|
||||
/** 添加枚举项 */
|
||||
function addEnum() {
|
||||
dataSpecsList.value.push({
|
||||
name: '', // 枚举项的名称
|
||||
value: '', // 枚举值
|
||||
} as any);
|
||||
}
|
||||
|
||||
/** 删除枚举项 */
|
||||
function deleteEnum(index: number) {
|
||||
if (dataSpecsList.value.length === 1) {
|
||||
message.warning('至少需要一个枚举项');
|
||||
return;
|
||||
}
|
||||
dataSpecsList.value.splice(index, 1);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Form.Item label="枚举项">
|
||||
<div class="flex flex-col">
|
||||
<div class="flex items-center">
|
||||
<span class="flex-1"> 参数值 </span>
|
||||
<span class="flex-1"> 参数描述 </span>
|
||||
</div>
|
||||
<div
|
||||
v-for="(item, index) in dataSpecsList"
|
||||
:key="index"
|
||||
class="mb-5px flex items-center justify-between"
|
||||
>
|
||||
<div class="flex-1">
|
||||
<Input v-model:value="item.value" placeholder="请输入枚举值,如'0'" />
|
||||
</div>
|
||||
<span class="mx-2">~</span>
|
||||
<div class="flex-1">
|
||||
<Input v-model:value="item.name" placeholder="对该枚举项的描述" />
|
||||
</div>
|
||||
<Button class="ml-10px" type="link" @click="deleteEnum(index)">
|
||||
删除
|
||||
</Button>
|
||||
</div>
|
||||
<Button type="link" @click="addEnum">+添加枚举项</Button>
|
||||
</div>
|
||||
</Form.Item>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep(.ant-form-item) {
|
||||
.ant-form-item {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,169 +0,0 @@
|
||||
<!-- dataType:struct 数组类型 -->
|
||||
<script lang="ts" setup>
|
||||
import type { Ref } from 'vue';
|
||||
|
||||
import { nextTick, onMounted, ref, unref } from 'vue';
|
||||
|
||||
import { isEmpty } from '@vben/utils';
|
||||
|
||||
import { useVModel } from '@vueuse/core';
|
||||
import { Button, Divider, Form, Input, Modal } from 'ant-design-vue';
|
||||
|
||||
import { IoTDataSpecsDataTypeEnum } from '#/views/iot/utils/constants';
|
||||
|
||||
import ThingModelProperty from '../thing-model-property.vue';
|
||||
|
||||
/** Struct 型的 dataSpecs 配置组件 */
|
||||
defineOptions({ name: 'ThingModelStructDataSpecs' });
|
||||
|
||||
const props = defineProps<{ modelValue: any }>();
|
||||
const emits = defineEmits(['update:modelValue']);
|
||||
const dataSpecsList = useVModel(props, 'modelValue', emits) as Ref<any[]>;
|
||||
const dialogVisible = ref(false); // 弹窗的是否展示
|
||||
const dialogTitle = ref('新增参数'); // 弹窗的标题
|
||||
const formLoading = ref(false); // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const structFormRef = ref(); // 表单 ref
|
||||
const formData = ref<any>({
|
||||
property: {
|
||||
dataType: IoTDataSpecsDataTypeEnum.INT,
|
||||
dataSpecs: {
|
||||
dataType: IoTDataSpecsDataTypeEnum.INT,
|
||||
},
|
||||
dataSpecsList: [],
|
||||
},
|
||||
});
|
||||
|
||||
/** 打开 struct 表单 */
|
||||
function openStructForm(val: any) {
|
||||
dialogVisible.value = true;
|
||||
resetForm();
|
||||
if (isEmpty(val)) {
|
||||
return;
|
||||
}
|
||||
// 编辑时回显数据
|
||||
const valData = val as any;
|
||||
formData.value = {
|
||||
identifier: valData?.identifier || '',
|
||||
name: valData?.name || '',
|
||||
description: valData?.description || '',
|
||||
property: {
|
||||
dataType: valData?.childDataType || IoTDataSpecsDataTypeEnum.INT,
|
||||
dataSpecs: valData?.dataSpecs ?? {},
|
||||
dataSpecsList: valData?.dataSpecsList ?? [],
|
||||
},
|
||||
};
|
||||
|
||||
// 确保 property.dataType 有值
|
||||
if (!formData.value.property.dataType) {
|
||||
formData.value.property.dataType = IoTDataSpecsDataTypeEnum.INT;
|
||||
}
|
||||
}
|
||||
|
||||
/** 删除 struct 项 */
|
||||
function deleteStructItem(index: number) {
|
||||
dataSpecsList.value.splice(index, 1);
|
||||
}
|
||||
|
||||
/** 添加参数 */
|
||||
async function submitForm() {
|
||||
await structFormRef.value.validate();
|
||||
|
||||
try {
|
||||
const data = unref(formData);
|
||||
// 构建数据对象
|
||||
const item = {
|
||||
identifier: data.identifier,
|
||||
name: data.name,
|
||||
description: data.description,
|
||||
dataType: IoTDataSpecsDataTypeEnum.STRUCT,
|
||||
childDataType: data.property.dataType,
|
||||
dataSpecs:
|
||||
!!data.property.dataSpecs &&
|
||||
Object.keys(data.property.dataSpecs).length > 1
|
||||
? data.property.dataSpecs
|
||||
: undefined,
|
||||
dataSpecsList: isEmpty(data.property.dataSpecsList)
|
||||
? undefined
|
||||
: data.property.dataSpecsList,
|
||||
};
|
||||
|
||||
// 新增或修改同 identifier 的参数
|
||||
const existingIndex = dataSpecsList.value.findIndex(
|
||||
(spec) => spec.identifier === data.identifier,
|
||||
);
|
||||
if (existingIndex === -1) {
|
||||
dataSpecsList.value.push(item);
|
||||
} else {
|
||||
dataSpecsList.value[existingIndex] = item;
|
||||
}
|
||||
} finally {
|
||||
dialogVisible.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
function resetForm() {
|
||||
formData.value = {
|
||||
property: {
|
||||
dataType: IoTDataSpecsDataTypeEnum.INT,
|
||||
dataSpecs: {
|
||||
dataType: IoTDataSpecsDataTypeEnum.INT,
|
||||
},
|
||||
dataSpecsList: [],
|
||||
},
|
||||
};
|
||||
structFormRef.value?.resetFields();
|
||||
}
|
||||
|
||||
/** 组件初始化 */
|
||||
onMounted(async () => {
|
||||
await nextTick();
|
||||
// 预防 dataSpecsList 空指针
|
||||
isEmpty(dataSpecsList.value) && (dataSpecsList.value = []);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- struct 数据展示 -->
|
||||
<Form.Item label="属性对象">
|
||||
<div
|
||||
v-for="(item, index) in dataSpecsList"
|
||||
:key="index"
|
||||
class="px-10px mb-10px flex w-full justify-between bg-gray-100"
|
||||
>
|
||||
<span>参数:{{ item.name }}</span>
|
||||
<div class="btn">
|
||||
<Button type="link" @click="openStructForm(item)"> 编辑 </Button>
|
||||
<Divider type="vertical" />
|
||||
<Button type="link" danger @click="deleteStructItem(index)">
|
||||
删除
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<Button type="link" @click="openStructForm(null)"> +新增参数 </Button>
|
||||
</Form.Item>
|
||||
|
||||
<!-- struct 表单 -->
|
||||
<Modal
|
||||
v-model:open="dialogVisible"
|
||||
:title="dialogTitle"
|
||||
:confirm-loading="formLoading"
|
||||
@ok="submitForm"
|
||||
>
|
||||
<Form
|
||||
ref="structFormRef"
|
||||
:model="formData"
|
||||
:label-col="{ span: 6 }"
|
||||
:wrapper-col="{ span: 18 }"
|
||||
>
|
||||
<Form.Item label="参数名称" name="name">
|
||||
<Input v-model:value="formData.name" placeholder="请输入功能名称" />
|
||||
</Form.Item>
|
||||
<Form.Item label="标识符" name="identifier">
|
||||
<Input v-model:value="formData.identifier" placeholder="请输入标识符" />
|
||||
</Form.Item>
|
||||
<!-- 属性配置 -->
|
||||
<ThingModelProperty v-model="formData.property" is-struct-data-specs />
|
||||
</Form>
|
||||
</Modal>
|
||||
</template>
|
||||
@@ -9,17 +9,15 @@ import { isEmpty } from '@vben/utils';
|
||||
import { useVModel } from '@vueuse/core';
|
||||
import { Form, Radio } from 'ant-design-vue';
|
||||
|
||||
import { ThingModelFormRules } from '#/api/iot/thingmodel';
|
||||
import {
|
||||
IoTThingModelEventTypeEnum,
|
||||
IoTThingModelParamDirectionEnum,
|
||||
} from '#/views/iot/utils/constants';
|
||||
|
||||
import ThingModelInputOutputParam from './thing-model-input-output-param.vue';
|
||||
import ThingModelInputOutputParam from './input-output-param.vue';
|
||||
|
||||
/** IoT 物模型事件 */
|
||||
defineOptions({ name: 'ThingModelEvent' });
|
||||
|
||||
const props = defineProps<{ isStructDataSpecs?: boolean; modelValue: any }>();
|
||||
const props = defineProps<{ modelValue: any }>();
|
||||
const emits = defineEmits(['update:modelValue']);
|
||||
const thingModelEvent = useVModel(props, 'modelValue', emits) as Ref<any>;
|
||||
|
||||
@@ -36,7 +34,7 @@ watch(
|
||||
<template>
|
||||
<Form.Item
|
||||
:name="['event', 'type']"
|
||||
:rules="[{ required: true, message: '请选择事件类型', trigger: 'change' }]"
|
||||
:rules="ThingModelFormRules.eventType"
|
||||
label="事件类型"
|
||||
>
|
||||
<Radio.Group v-model:value="thingModelEvent.type">
|
||||
246
apps/web-antd/src/views/iot/thingmodel/modules/form.vue
Normal file
246
apps/web-antd/src/views/iot/thingmodel/modules/form.vue
Normal file
@@ -0,0 +1,246 @@
|
||||
<script lang="ts" setup>
|
||||
import type { Ref } from 'vue';
|
||||
|
||||
import type { IotProductApi } from '#/api/iot/product/product';
|
||||
import type { ThingModelData } from '#/api/iot/thingmodel';
|
||||
|
||||
import { computed, inject, ref } from 'vue';
|
||||
|
||||
import { useVbenModal } from '@vben/common-ui';
|
||||
import { DICT_TYPE } from '@vben/constants';
|
||||
import { getDictOptions } from '@vben/hooks';
|
||||
import { $t } from '@vben/locales';
|
||||
import { cloneDeep, isEmpty } from '@vben/utils';
|
||||
|
||||
import { Form, Input, message, Radio } from 'ant-design-vue';
|
||||
|
||||
import {
|
||||
createThingModel,
|
||||
getThingModel,
|
||||
ThingModelFormRules,
|
||||
updateThingModel,
|
||||
} from '#/api/iot/thingmodel';
|
||||
import {
|
||||
IOT_PROVIDE_KEY,
|
||||
IoTDataSpecsDataTypeEnum,
|
||||
IoTThingModelTypeEnum,
|
||||
} from '#/views/iot/utils/constants';
|
||||
|
||||
import ThingModelEvent from './event.vue';
|
||||
import ThingModelProperty from './property.vue';
|
||||
import ThingModelService from './service.vue';
|
||||
|
||||
const emit = defineEmits(['success']);
|
||||
|
||||
const product = inject<Ref<IotProductApi.Product>>(IOT_PROVIDE_KEY.PRODUCT);
|
||||
|
||||
const formRef = ref();
|
||||
const formData = ref<ThingModelData>(buildEmptyFormData());
|
||||
|
||||
const getTitle = computed(() =>
|
||||
formData.value.id
|
||||
? $t('ui.actionTitle.edit', ['物模型'])
|
||||
: $t('ui.actionTitle.create', ['物模型']),
|
||||
);
|
||||
|
||||
const [Modal, modalApi] = useVbenModal({
|
||||
async onConfirm() {
|
||||
try {
|
||||
await formRef.value?.validate();
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
modalApi.lock();
|
||||
try {
|
||||
const data = cloneDeep(formData.value);
|
||||
data.productId = product!.value.id;
|
||||
data.productKey = product!.value.productKey;
|
||||
fillExtraAttributes(data);
|
||||
await (data.id ? updateThingModel(data) : createThingModel(data));
|
||||
await modalApi.close();
|
||||
emit('success');
|
||||
message.success($t('ui.actionMessage.operationSuccess'));
|
||||
} finally {
|
||||
modalApi.unlock();
|
||||
}
|
||||
},
|
||||
async onOpenChange(isOpen: boolean) {
|
||||
if (!isOpen) {
|
||||
return;
|
||||
}
|
||||
// 每次打开都先重置到空白,避免上一次的状态残留
|
||||
formData.value = buildEmptyFormData();
|
||||
formRef.value?.clearValidate?.();
|
||||
const data = modalApi.getData<{ id?: number }>();
|
||||
if (!data?.id) {
|
||||
return;
|
||||
}
|
||||
modalApi.lock();
|
||||
try {
|
||||
const result = await getThingModel(data.id);
|
||||
formData.value = normalizeFormData(result);
|
||||
} finally {
|
||||
modalApi.unlock();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
/** 构造空白表单数据 */
|
||||
function buildEmptyFormData(): ThingModelData {
|
||||
return {
|
||||
type: IoTThingModelTypeEnum.PROPERTY,
|
||||
dataType: IoTDataSpecsDataTypeEnum.INT,
|
||||
property: {
|
||||
dataType: IoTDataSpecsDataTypeEnum.INT,
|
||||
dataSpecs: {
|
||||
dataType: IoTDataSpecsDataTypeEnum.INT,
|
||||
},
|
||||
},
|
||||
service: {
|
||||
inputParams: [],
|
||||
outputParams: [],
|
||||
},
|
||||
event: {
|
||||
outputParams: [],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/** 回显数据时,规整各分支字段确保子表单可绑定 */
|
||||
function normalizeFormData(result: ThingModelData): ThingModelData {
|
||||
const next: any = { ...result, type: Number(result.type) };
|
||||
if (isEmpty(next.property)) {
|
||||
next.dataType = IoTDataSpecsDataTypeEnum.INT;
|
||||
next.property = {
|
||||
dataType: IoTDataSpecsDataTypeEnum.INT,
|
||||
dataSpecs: { dataType: IoTDataSpecsDataTypeEnum.INT },
|
||||
};
|
||||
} else {
|
||||
next.property.dataSpecs ??= {};
|
||||
next.property.dataSpecsList ??= [];
|
||||
next.property.dataType ??= IoTDataSpecsDataTypeEnum.INT;
|
||||
}
|
||||
if (isEmpty(next.service)) {
|
||||
next.service = { inputParams: [], outputParams: [] };
|
||||
} else {
|
||||
next.service.inputParams ??= [];
|
||||
next.service.outputParams ??= [];
|
||||
}
|
||||
if (isEmpty(next.event)) {
|
||||
next.event = { outputParams: [] };
|
||||
} else {
|
||||
next.event.outputParams ??= [];
|
||||
}
|
||||
return next;
|
||||
}
|
||||
|
||||
/** 按功能类型将子表单数据回写到顶层,并清理无关分支 */
|
||||
function fillExtraAttributes(data: any) {
|
||||
if (data.type === IoTThingModelTypeEnum.PROPERTY) {
|
||||
removeDataSpecs(data.property);
|
||||
data.dataType = data.property.dataType;
|
||||
data.property.identifier = data.identifier;
|
||||
data.property.name = data.name;
|
||||
delete data.service;
|
||||
delete data.event;
|
||||
} else if (data.type === IoTThingModelTypeEnum.SERVICE) {
|
||||
removeDataSpecs(data.service);
|
||||
data.dataType = data.service.dataType;
|
||||
data.service.identifier = data.identifier;
|
||||
data.service.name = data.name;
|
||||
if (isEmpty(data.service.inputParams)) {
|
||||
delete data.service.inputParams;
|
||||
}
|
||||
if (isEmpty(data.service.outputParams)) {
|
||||
delete data.service.outputParams;
|
||||
}
|
||||
delete data.property;
|
||||
delete data.event;
|
||||
} else if (data.type === IoTThingModelTypeEnum.EVENT) {
|
||||
removeDataSpecs(data.event);
|
||||
data.dataType = data.event.dataType;
|
||||
data.event.identifier = data.identifier;
|
||||
data.event.name = data.name;
|
||||
if (isEmpty(data.event.outputParams)) {
|
||||
delete data.event.outputParams;
|
||||
}
|
||||
delete data.property;
|
||||
delete data.service;
|
||||
}
|
||||
}
|
||||
|
||||
/** 清理空的 dataSpecs / dataSpecsList */
|
||||
function removeDataSpecs(val: any) {
|
||||
if (isEmpty(val.dataSpecs)) {
|
||||
delete val.dataSpecs;
|
||||
}
|
||||
if (isEmpty(val.dataSpecsList)) {
|
||||
delete val.dataSpecsList;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal :title="getTitle" class="w-2/5">
|
||||
<Form
|
||||
ref="formRef"
|
||||
:label-col="{ span: 6 }"
|
||||
:model="formData"
|
||||
:wrapper-col="{ span: 18 }"
|
||||
class="mx-4"
|
||||
>
|
||||
<Form.Item
|
||||
:rules="ThingModelFormRules.type"
|
||||
label="功能类型"
|
||||
name="type"
|
||||
>
|
||||
<Radio.Group v-model:value="formData.type">
|
||||
<Radio.Button
|
||||
v-for="dict in getDictOptions(DICT_TYPE.IOT_THING_MODEL_TYPE)"
|
||||
:key="String(dict.value)"
|
||||
:value="Number(dict.value)"
|
||||
>
|
||||
{{ dict.label }}
|
||||
</Radio.Button>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
:rules="ThingModelFormRules.name"
|
||||
label="功能名称"
|
||||
name="name"
|
||||
>
|
||||
<Input v-model:value="formData.name" placeholder="请输入功能名称" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
:rules="ThingModelFormRules.identifier"
|
||||
label="标识符"
|
||||
name="identifier"
|
||||
>
|
||||
<Input v-model:value="formData.identifier" placeholder="请输入标识符" />
|
||||
</Form.Item>
|
||||
<!-- 属性配置 -->
|
||||
<ThingModelProperty
|
||||
v-if="formData.type === IoTThingModelTypeEnum.PROPERTY"
|
||||
v-model="formData.property"
|
||||
/>
|
||||
<!-- 服务配置 -->
|
||||
<ThingModelService
|
||||
v-if="formData.type === IoTThingModelTypeEnum.SERVICE"
|
||||
v-model="formData.service"
|
||||
/>
|
||||
<!-- 事件配置 -->
|
||||
<ThingModelEvent
|
||||
v-if="formData.type === IoTThingModelTypeEnum.EVENT"
|
||||
v-model="formData.event"
|
||||
/>
|
||||
<Form.Item label="描述" name="description">
|
||||
<Input.TextArea
|
||||
v-model:value="formData.description"
|
||||
:maxlength="200"
|
||||
:rows="3"
|
||||
placeholder="请输入物模型描述"
|
||||
/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
</template>
|
||||
@@ -0,0 +1,152 @@
|
||||
<script lang="ts" setup>
|
||||
import type { Ref } from 'vue';
|
||||
|
||||
import { ref } from 'vue';
|
||||
|
||||
import { useVbenModal } from '@vben/common-ui';
|
||||
import { isEmpty } from '@vben/utils';
|
||||
|
||||
import { useVModel } from '@vueuse/core';
|
||||
import { Button, Divider, Form, Input } from 'ant-design-vue';
|
||||
|
||||
import { ThingModelFormRules } from '#/api/iot/thingmodel';
|
||||
import { IoTDataSpecsDataTypeEnum } from '#/views/iot/utils/constants';
|
||||
|
||||
import ThingModelProperty from './property.vue';
|
||||
|
||||
const props = defineProps<{ direction: string; modelValue: any }>();
|
||||
const emits = defineEmits(['update:modelValue']);
|
||||
const thingModelParams = useVModel(props, 'modelValue', emits) as Ref<any[]>;
|
||||
|
||||
const paramFormRef = ref();
|
||||
const formData = ref<any>(buildEmptyFormData());
|
||||
|
||||
const [Modal, modalApi] = useVbenModal({
|
||||
async onConfirm() {
|
||||
try {
|
||||
await paramFormRef.value?.validate();
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
if (!thingModelParams.value) {
|
||||
thingModelParams.value = [];
|
||||
}
|
||||
const data = formData.value;
|
||||
const item = {
|
||||
identifier: data.identifier,
|
||||
name: data.name,
|
||||
description: data.description,
|
||||
dataType: data.property.dataType,
|
||||
paraOrder: 0,
|
||||
direction: props.direction,
|
||||
dataSpecs:
|
||||
!isEmpty(data.property.dataSpecs) &&
|
||||
Object.keys(data.property.dataSpecs).length > 1
|
||||
? data.property.dataSpecs
|
||||
: undefined,
|
||||
dataSpecsList: isEmpty(data.property.dataSpecsList)
|
||||
? undefined
|
||||
: data.property.dataSpecsList,
|
||||
};
|
||||
// 按 identifier 去重,存在则更新,否则追加
|
||||
const existingIndex = thingModelParams.value.findIndex(
|
||||
(spec) => spec.identifier === data.identifier,
|
||||
);
|
||||
if (existingIndex === -1) {
|
||||
thingModelParams.value.push(item);
|
||||
} else {
|
||||
thingModelParams.value[existingIndex] = item;
|
||||
}
|
||||
await modalApi.close();
|
||||
},
|
||||
onOpenChange(isOpen: boolean) {
|
||||
if (!isOpen) {
|
||||
return;
|
||||
}
|
||||
formData.value = buildEmptyFormData();
|
||||
paramFormRef.value?.clearValidate?.();
|
||||
const data = modalApi.getData<any>();
|
||||
if (isEmpty(data)) {
|
||||
return;
|
||||
}
|
||||
formData.value = {
|
||||
identifier: data.identifier ?? '',
|
||||
name: data.name ?? '',
|
||||
description: data.description ?? '',
|
||||
property: {
|
||||
dataType: data.dataType ?? IoTDataSpecsDataTypeEnum.INT,
|
||||
dataSpecs: data.dataSpecs ?? {},
|
||||
dataSpecsList: data.dataSpecsList ?? [],
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
/** 构造空白参数表单 */
|
||||
function buildEmptyFormData() {
|
||||
return {
|
||||
identifier: '',
|
||||
name: '',
|
||||
description: '',
|
||||
property: {
|
||||
dataType: IoTDataSpecsDataTypeEnum.INT,
|
||||
dataSpecs: { dataType: IoTDataSpecsDataTypeEnum.INT },
|
||||
dataSpecsList: [],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/** 打开参数表单(新增或编辑) */
|
||||
function openParamForm(val: any) {
|
||||
modalApi.setData(val).open();
|
||||
}
|
||||
|
||||
/** 删除参数项 */
|
||||
function deleteParamItem(index: number) {
|
||||
thingModelParams.value.splice(index, 1);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
v-for="(item, index) in thingModelParams"
|
||||
:key="index"
|
||||
class="mb-2.5 flex w-full justify-between bg-gray-100 px-2.5 dark:bg-gray-800"
|
||||
>
|
||||
<span>参数名称:{{ item.name }}</span>
|
||||
<div>
|
||||
<Button type="link" @click="openParamForm(item)">编辑</Button>
|
||||
<Divider type="vertical" />
|
||||
<Button danger type="link" @click="deleteParamItem(index)">删除</Button>
|
||||
</div>
|
||||
</div>
|
||||
<Button type="link" @click="openParamForm(null)">+ 新增参数</Button>
|
||||
|
||||
<!-- 参数表单 -->
|
||||
<Modal class="w-2/5" title="参数配置">
|
||||
<Form
|
||||
ref="paramFormRef"
|
||||
:label-col="{ span: 6 }"
|
||||
:model="formData"
|
||||
:wrapper-col="{ span: 18 }"
|
||||
class="mx-4"
|
||||
>
|
||||
<Form.Item
|
||||
:rules="ThingModelFormRules.name"
|
||||
label="参数名称"
|
||||
name="name"
|
||||
>
|
||||
<Input v-model:value="formData.name" placeholder="请输入参数名称" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
:rules="ThingModelFormRules.identifier"
|
||||
label="标识符"
|
||||
name="identifier"
|
||||
>
|
||||
<Input v-model:value="formData.identifier" placeholder="请输入标识符" />
|
||||
</Form.Item>
|
||||
<!-- 属性配置 -->
|
||||
<ThingModelProperty v-model="formData.property" is-params />
|
||||
</Form>
|
||||
</Modal>
|
||||
</template>
|
||||
@@ -11,6 +11,7 @@ import { isEmpty } from '@vben/utils';
|
||||
import { useVModel } from '@vueuse/core';
|
||||
import { Form, Input, Radio, Select } from 'ant-design-vue';
|
||||
|
||||
import { ThingModelFormRules, validateBoolName } from '#/api/iot/thingmodel';
|
||||
import {
|
||||
getDataTypeOptions,
|
||||
IoTDataSpecsDataTypeEnum,
|
||||
@@ -22,10 +23,22 @@ import {
|
||||
ThingModelEnumDataSpecs,
|
||||
ThingModelNumberDataSpecs,
|
||||
ThingModelStructDataSpecs,
|
||||
} from './dataSpecs';
|
||||
} from './data-specs';
|
||||
|
||||
/** IoT 物模型属性 */
|
||||
defineOptions({ name: 'ThingModelProperty' });
|
||||
/** 嵌套在结构体里时,禁止再选数组 / 结构体(最多支持两层嵌套) */
|
||||
const NESTED_EXCLUDED_TYPES = new Set<string>([
|
||||
IoTDataSpecsDataTypeEnum.ARRAY,
|
||||
IoTDataSpecsDataTypeEnum.STRUCT,
|
||||
]);
|
||||
const STRUCT_CHILD_OPTIONS = getDataTypeOptions().filter(
|
||||
(item) => !NESTED_EXCLUDED_TYPES.has(item.value),
|
||||
);
|
||||
/** 数值型数据类型集合 */
|
||||
const NUMERIC_TYPES = new Set<string>([
|
||||
IoTDataSpecsDataTypeEnum.INT,
|
||||
IoTDataSpecsDataTypeEnum.DOUBLE,
|
||||
IoTDataSpecsDataTypeEnum.FLOAT,
|
||||
]);
|
||||
|
||||
const props = defineProps<{
|
||||
isParams?: boolean;
|
||||
@@ -38,65 +51,54 @@ const property = useVModel(
|
||||
'modelValue',
|
||||
emits,
|
||||
) as Ref<ThingModelProperty>;
|
||||
const getDataTypeOptions2 = computed(() => {
|
||||
if (!props.isStructDataSpecs) {
|
||||
return getDataTypeOptions();
|
||||
}
|
||||
const excludedTypes = new Set([
|
||||
IoTDataSpecsDataTypeEnum.ARRAY,
|
||||
IoTDataSpecsDataTypeEnum.STRUCT,
|
||||
]);
|
||||
return getDataTypeOptions().filter(
|
||||
(item: any) => !excludedTypes.has(item.value),
|
||||
);
|
||||
}); // 获得数据类型列表
|
||||
|
||||
/** 属性值的数据类型切换时初始化相关数据 */
|
||||
const dataTypeOptions = computed(() =>
|
||||
props.isStructDataSpecs ? STRUCT_CHILD_OPTIONS : getDataTypeOptions(),
|
||||
);
|
||||
|
||||
/** 数据类型切换时,重置 dataSpecs / dataSpecsList 并按新类型初始化 */
|
||||
function handleChange(dataType: any) {
|
||||
property.value.dataSpecs = {};
|
||||
property.value.dataSpecsList = [];
|
||||
// 不是列表型数据才设置 dataSpecs.dataType
|
||||
![
|
||||
// 数值 / 文本 / 时间 / 数组型把 dataType 同步到 dataSpecs;布尔 / 枚举 / 结构走 dataSpecsList
|
||||
const listLike = [
|
||||
IoTDataSpecsDataTypeEnum.BOOL,
|
||||
IoTDataSpecsDataTypeEnum.ENUM,
|
||||
IoTDataSpecsDataTypeEnum.STRUCT,
|
||||
].includes(dataType) && (property.value.dataSpecs.dataType = dataType);
|
||||
switch (dataType) {
|
||||
case IoTDataSpecsDataTypeEnum.BOOL: {
|
||||
for (let i = 0; i < 2; i++) {
|
||||
property.value.dataSpecsList.push({
|
||||
dataType: IoTDataSpecsDataTypeEnum.BOOL,
|
||||
name: '', // 布尔值的名称
|
||||
value: i, // 布尔值
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
case IoTDataSpecsDataTypeEnum.ENUM: {
|
||||
property.value.dataSpecsList.push({
|
||||
dataType: IoTDataSpecsDataTypeEnum.ENUM,
|
||||
name: '', // 枚举项的名称
|
||||
value: undefined, // 枚举值
|
||||
});
|
||||
break;
|
||||
}
|
||||
];
|
||||
if (!listLike.includes(dataType)) {
|
||||
property.value.dataSpecs.dataType = dataType;
|
||||
}
|
||||
if (dataType === IoTDataSpecsDataTypeEnum.BOOL) {
|
||||
for (let i = 0; i < 2; i++) {
|
||||
property.value.dataSpecsList.push({
|
||||
dataType: IoTDataSpecsDataTypeEnum.BOOL,
|
||||
name: '', // 布尔描述
|
||||
value: i, // 布尔值
|
||||
});
|
||||
}
|
||||
} else if (dataType === IoTDataSpecsDataTypeEnum.ENUM) {
|
||||
property.value.dataSpecsList.push({
|
||||
dataType: IoTDataSpecsDataTypeEnum.ENUM,
|
||||
name: '', // 枚举项描述
|
||||
value: undefined, // 枚举值
|
||||
});
|
||||
}
|
||||
// useVModel 会自动同步数据到父组件,不需要手动 emit
|
||||
}
|
||||
|
||||
/** 默认选中读写 */
|
||||
watch(
|
||||
() => property.value.accessMode,
|
||||
(val: string | undefined) => {
|
||||
if (props.isStructDataSpecs || props.isParams) {
|
||||
return;
|
||||
}
|
||||
if (isEmpty(val)) {
|
||||
property.value.accessMode = IoTThingModelAccessModeEnum.READ_WRITE.value;
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
/** 顶层属性表单首次进入时,默认选中「读写」 */
|
||||
if (!props.isStructDataSpecs && !props.isParams) {
|
||||
watch(
|
||||
() => property.value.accessMode,
|
||||
(val: string | undefined) => {
|
||||
if (isEmpty(val)) {
|
||||
property.value.accessMode =
|
||||
IoTThingModelAccessModeEnum.READ_WRITE.value;
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -108,7 +110,7 @@ watch(
|
||||
>
|
||||
<!-- ARRAY 和 STRUCT 类型数据相互嵌套时,最多支持递归嵌套 2 层(父和子) -->
|
||||
<Select.Option
|
||||
v-for="option in getDataTypeOptions2"
|
||||
v-for="option in dataTypeOptions"
|
||||
:key="option.value"
|
||||
:value="option.value"
|
||||
>
|
||||
@@ -118,13 +120,7 @@ watch(
|
||||
</Form.Item>
|
||||
<!-- 数值型配置 -->
|
||||
<ThingModelNumberDataSpecs
|
||||
v-if="
|
||||
[
|
||||
IoTDataSpecsDataTypeEnum.INT,
|
||||
IoTDataSpecsDataTypeEnum.DOUBLE,
|
||||
IoTDataSpecsDataTypeEnum.FLOAT,
|
||||
].includes(property.dataType || '')
|
||||
"
|
||||
v-if="NUMERIC_TYPES.has(property.dataType || '')"
|
||||
v-model="property.dataSpecs"
|
||||
/>
|
||||
<!-- 枚举型配置 -->
|
||||
@@ -137,17 +133,27 @@ watch(
|
||||
v-if="property.dataType === IoTDataSpecsDataTypeEnum.BOOL"
|
||||
label="布尔值"
|
||||
>
|
||||
<template v-for="item in property.dataSpecsList" :key="item.value">
|
||||
<div class="w-1/1 mb-5px flex items-center justify-start">
|
||||
<template
|
||||
v-for="(item, index) in property.dataSpecsList"
|
||||
:key="item.value"
|
||||
>
|
||||
<div class="mb-[5px] flex w-full items-center justify-start">
|
||||
<span>{{ item.value }}</span>
|
||||
<span class="mx-2">-</span>
|
||||
<div class="flex-1">
|
||||
<Form.Item
|
||||
:name="['property', 'dataSpecsList', index, 'name']"
|
||||
:rules="[
|
||||
{ required: true, message: '布尔描述不能为空', trigger: 'blur' },
|
||||
{ validator: validateBoolName, trigger: 'blur' },
|
||||
]"
|
||||
class="mb-0 flex-1"
|
||||
>
|
||||
<Input
|
||||
v-model:value="item.name"
|
||||
:placeholder="`如:${item.value === 0 ? '关' : '开'}`"
|
||||
class="w-255px!"
|
||||
class="!w-[255px]"
|
||||
/>
|
||||
</div>
|
||||
</Form.Item>
|
||||
</div>
|
||||
</template>
|
||||
</Form.Item>
|
||||
@@ -155,11 +161,12 @@ watch(
|
||||
<Form.Item
|
||||
v-if="property.dataType === IoTDataSpecsDataTypeEnum.TEXT"
|
||||
:name="['property', 'dataSpecs', 'length']"
|
||||
:rules="ThingModelFormRules.length"
|
||||
label="数据长度"
|
||||
>
|
||||
<Input
|
||||
v-model:value="property.dataSpecs.length"
|
||||
class="w-255px!"
|
||||
class="!w-[255px]"
|
||||
placeholder="请输入文本字节长度"
|
||||
>
|
||||
<template #addonAfter>字节</template>
|
||||
@@ -172,7 +179,7 @@ watch(
|
||||
name="date"
|
||||
>
|
||||
<Input
|
||||
class="w-255px!"
|
||||
class="!w-[255px]"
|
||||
disabled
|
||||
placeholder="String 类型的 UTC 时间戳(毫秒)"
|
||||
/>
|
||||
@@ -190,6 +197,7 @@ watch(
|
||||
<Form.Item
|
||||
v-if="!isStructDataSpecs && !isParams"
|
||||
:name="['property', 'accessMode']"
|
||||
:rules="ThingModelFormRules.accessMode"
|
||||
label="读写类型"
|
||||
>
|
||||
<Radio.Group v-model:value="property.accessMode">
|
||||
@@ -9,17 +9,15 @@ import { isEmpty } from '@vben/utils';
|
||||
import { useVModel } from '@vueuse/core';
|
||||
import { Form, Radio } from 'ant-design-vue';
|
||||
|
||||
import { ThingModelFormRules } from '#/api/iot/thingmodel';
|
||||
import {
|
||||
IoTThingModelParamDirectionEnum,
|
||||
IoTThingModelServiceCallTypeEnum,
|
||||
} from '#/views/iot/utils/constants';
|
||||
|
||||
import ThingModelInputOutputParam from './thing-model-input-output-param.vue';
|
||||
import ThingModelInputOutputParam from './input-output-param.vue';
|
||||
|
||||
/** IoT 物模型服务 */
|
||||
defineOptions({ name: 'ThingModelService' });
|
||||
|
||||
const props = defineProps<{ isStructDataSpecs?: boolean; modelValue: any }>();
|
||||
const props = defineProps<{ modelValue: any }>();
|
||||
const emits = defineEmits(['update:modelValue']);
|
||||
const service = useVModel(props, 'modelValue', emits) as Ref<any>;
|
||||
|
||||
@@ -36,7 +34,7 @@ watch(
|
||||
<template>
|
||||
<Form.Item
|
||||
:name="['service', 'callType']"
|
||||
:rules="[{ required: true, message: '请选择调用方式', trigger: 'change' }]"
|
||||
:rules="ThingModelFormRules.callType"
|
||||
label="调用方式"
|
||||
>
|
||||
<Radio.Group v-model:value="service.callType">
|
||||
@@ -1,298 +0,0 @@
|
||||
<!-- 产品的物模型表单 -->
|
||||
<script lang="ts" setup>
|
||||
import type { Ref } from 'vue';
|
||||
|
||||
// TODO @haohao:使用 form.vue;
|
||||
import type { IotProductApi } from '#/api/iot/product/product';
|
||||
import type { ThingModelData } from '#/api/iot/thingmodel';
|
||||
|
||||
import { inject, ref } from 'vue';
|
||||
|
||||
import { DICT_TYPE } from '@vben/constants';
|
||||
import { getDictOptions } from '@vben/hooks';
|
||||
import { $t } from '@vben/locales';
|
||||
import { cloneDeep } from '@vben/utils';
|
||||
|
||||
import { Form, Input, message, Modal, Radio } from 'ant-design-vue';
|
||||
|
||||
import {
|
||||
createThingModel,
|
||||
getThingModel,
|
||||
updateThingModel,
|
||||
} from '#/api/iot/thingmodel';
|
||||
import {
|
||||
IOT_PROVIDE_KEY,
|
||||
IoTDataSpecsDataTypeEnum,
|
||||
IoTThingModelTypeEnum,
|
||||
} from '#/views/iot/utils/constants';
|
||||
|
||||
import ThingModelEvent from './thing-model-event.vue';
|
||||
import ThingModelProperty from './thing-model-property.vue';
|
||||
import ThingModelService from './thing-model-service.vue';
|
||||
|
||||
/** IoT 物模型数据表单 */
|
||||
defineOptions({ name: 'IoTThingModelForm' });
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']);
|
||||
const product = inject<Ref<IotProductApi.Product>>(IOT_PROVIDE_KEY.PRODUCT); // 注入产品信息
|
||||
|
||||
const dialogVisible = ref(false); // 弹窗的是否展示
|
||||
const dialogTitle = ref(''); // 弹窗的标题
|
||||
const formLoading = ref(false); // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formType = ref(''); // 表单的类型:create - 新增;update - 修改
|
||||
const formData = ref<any>({
|
||||
type: IoTThingModelTypeEnum.PROPERTY,
|
||||
dataType: IoTDataSpecsDataTypeEnum.INT,
|
||||
property: {
|
||||
dataType: IoTDataSpecsDataTypeEnum.INT,
|
||||
dataSpecs: {
|
||||
dataType: IoTDataSpecsDataTypeEnum.INT,
|
||||
},
|
||||
},
|
||||
service: {
|
||||
inputParams: [],
|
||||
outputParams: [],
|
||||
},
|
||||
event: {
|
||||
outputParams: [],
|
||||
},
|
||||
});
|
||||
|
||||
const formRef = ref(); // 表单 Ref
|
||||
|
||||
/** 打开弹窗 */
|
||||
// TODO @haohao:Modal 的写法。
|
||||
async function open(type: string, id?: number) {
|
||||
dialogVisible.value = true;
|
||||
// 设置标题:create -> 新增,update -> 编辑
|
||||
dialogTitle.value =
|
||||
type === 'create' ? $t('page.action.add') : $t('page.action.edit');
|
||||
formType.value = type;
|
||||
resetForm();
|
||||
if (id) {
|
||||
formLoading.value = true;
|
||||
try {
|
||||
const result = await getThingModel(id);
|
||||
// 转换类型为数字
|
||||
formData.value = {
|
||||
...result,
|
||||
type: Number(result.type),
|
||||
};
|
||||
// 情况一:属性初始化
|
||||
if (
|
||||
!formData.value.property ||
|
||||
Object.keys(formData.value.property).length === 0
|
||||
) {
|
||||
formData.value.dataType = IoTDataSpecsDataTypeEnum.INT;
|
||||
formData.value.property = {
|
||||
dataType: IoTDataSpecsDataTypeEnum.INT,
|
||||
dataSpecs: {
|
||||
dataType: IoTDataSpecsDataTypeEnum.INT,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
// 确保 dataSpecs 和 dataSpecsList 存在
|
||||
if (!formData.value.property.dataSpecs) {
|
||||
formData.value.property.dataSpecs = {};
|
||||
}
|
||||
if (!formData.value.property.dataSpecsList) {
|
||||
formData.value.property.dataSpecsList = [];
|
||||
}
|
||||
// 如果 property.dataType 不存在,设置为默认值
|
||||
if (!formData.value.property.dataType) {
|
||||
formData.value.property.dataType = IoTDataSpecsDataTypeEnum.INT;
|
||||
}
|
||||
}
|
||||
// 情况二:服务初始化
|
||||
if (
|
||||
!formData.value.service ||
|
||||
Object.keys(formData.value.service).length === 0
|
||||
) {
|
||||
formData.value.service = {
|
||||
inputParams: [],
|
||||
outputParams: [],
|
||||
};
|
||||
} else {
|
||||
// 确保参数数组存在
|
||||
if (!formData.value.service.inputParams) {
|
||||
formData.value.service.inputParams = [];
|
||||
}
|
||||
if (!formData.value.service.outputParams) {
|
||||
formData.value.service.outputParams = [];
|
||||
}
|
||||
}
|
||||
// 情况三:事件初始化
|
||||
if (
|
||||
!formData.value.event ||
|
||||
Object.keys(formData.value.event).length === 0
|
||||
) {
|
||||
formData.value.event = {
|
||||
outputParams: [],
|
||||
};
|
||||
} else {
|
||||
// 确保参数数组存在
|
||||
if (!formData.value.event.outputParams) {
|
||||
formData.value.event.outputParams = [];
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
formLoading.value = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
defineExpose({ open, close: () => (dialogVisible.value = false) });
|
||||
|
||||
async function submitForm() {
|
||||
await formRef.value.validate();
|
||||
formLoading.value = true;
|
||||
try {
|
||||
const data = cloneDeep(formData.value) as ThingModelData;
|
||||
// 信息补全
|
||||
data.productId = product!.value.id;
|
||||
data.productKey = product!.value.productKey;
|
||||
fillExtraAttributes(data);
|
||||
await (formType.value === 'create'
|
||||
? createThingModel(data)
|
||||
: updateThingModel(data));
|
||||
message.success($t('ui.actionMessage.operationSuccess'));
|
||||
// 关闭弹窗
|
||||
dialogVisible.value = false;
|
||||
emit('success');
|
||||
} finally {
|
||||
formLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
/** 填写额外的属性(处理不同类型的情况) */
|
||||
function fillExtraAttributes(data: any) {
|
||||
// 属性
|
||||
if (data.type === IoTThingModelTypeEnum.PROPERTY) {
|
||||
removeDataSpecs(data.property);
|
||||
data.dataType = data.property.dataType;
|
||||
data.property.identifier = data.identifier;
|
||||
data.property.name = data.name;
|
||||
delete data.service;
|
||||
delete data.event;
|
||||
}
|
||||
// 服务
|
||||
if (data.type === IoTThingModelTypeEnum.SERVICE) {
|
||||
removeDataSpecs(data.service);
|
||||
data.dataType = data.service.dataType;
|
||||
data.service.identifier = data.identifier;
|
||||
data.service.name = data.name;
|
||||
// 保留输入输出参数,但如果为空数组则删除
|
||||
if (!data.service.inputParams || data.service.inputParams.length === 0) {
|
||||
delete data.service.inputParams;
|
||||
}
|
||||
if (!data.service.outputParams || data.service.outputParams.length === 0) {
|
||||
delete data.service.outputParams;
|
||||
}
|
||||
delete data.property;
|
||||
delete data.event;
|
||||
}
|
||||
// 事件
|
||||
if (data.type === IoTThingModelTypeEnum.EVENT) {
|
||||
removeDataSpecs(data.event);
|
||||
data.dataType = data.event.dataType;
|
||||
data.event.identifier = data.identifier;
|
||||
data.event.name = data.name;
|
||||
// 保留输出参数,但如果为空数组则删除
|
||||
if (!data.event.outputParams || data.event.outputParams.length === 0) {
|
||||
delete data.event.outputParams;
|
||||
}
|
||||
delete data.property;
|
||||
delete data.service;
|
||||
}
|
||||
}
|
||||
|
||||
/** 处理 dataSpecs 为空的情况 */
|
||||
function removeDataSpecs(val: any) {
|
||||
if (!val.dataSpecs || Object.keys(val.dataSpecs).length === 0) {
|
||||
delete val.dataSpecs;
|
||||
}
|
||||
if (!val.dataSpecsList || val.dataSpecsList.length === 0) {
|
||||
delete val.dataSpecsList;
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
function resetForm() {
|
||||
formData.value = {
|
||||
type: IoTThingModelTypeEnum.PROPERTY,
|
||||
dataType: IoTDataSpecsDataTypeEnum.INT,
|
||||
property: {
|
||||
dataType: IoTDataSpecsDataTypeEnum.INT,
|
||||
dataSpecs: {
|
||||
dataType: IoTDataSpecsDataTypeEnum.INT,
|
||||
},
|
||||
},
|
||||
service: {
|
||||
inputParams: [],
|
||||
outputParams: [],
|
||||
},
|
||||
event: {
|
||||
outputParams: [],
|
||||
},
|
||||
};
|
||||
formRef.value?.resetFields();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal
|
||||
v-model:open="dialogVisible"
|
||||
:title="dialogTitle"
|
||||
:confirm-loading="formLoading"
|
||||
@ok="submitForm"
|
||||
>
|
||||
<!-- TODO @haohao:这个可以改造成 data.ts schema 形式么?可能是有一定成本,后续迁移 ele 版本,会容易很多。 -->
|
||||
<Form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:label-col="{ span: 6 }"
|
||||
:wrapper-col="{ span: 18 }"
|
||||
>
|
||||
<Form.Item label="功能类型" name="type">
|
||||
<Radio.Group v-model:value="formData.type">
|
||||
<Radio.Button
|
||||
v-for="dict in getDictOptions(DICT_TYPE.IOT_THING_MODEL_TYPE)"
|
||||
:key="String(dict.value)"
|
||||
:value="Number(dict.value)"
|
||||
>
|
||||
{{ dict.label }}
|
||||
</Radio.Button>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
<Form.Item label="功能名称" name="name">
|
||||
<Input v-model:value="formData.name" placeholder="请输入功能名称" />
|
||||
</Form.Item>
|
||||
<Form.Item label="标识符" name="identifier">
|
||||
<Input v-model:value="formData.identifier" placeholder="请输入标识符" />
|
||||
</Form.Item>
|
||||
<!-- 属性配置 -->
|
||||
<ThingModelProperty
|
||||
v-if="formData.type === IoTThingModelTypeEnum.PROPERTY"
|
||||
v-model="formData.property"
|
||||
/>
|
||||
<!-- 服务配置 -->
|
||||
<ThingModelService
|
||||
v-if="formData.type === IoTThingModelTypeEnum.SERVICE"
|
||||
v-model="formData.service"
|
||||
/>
|
||||
<!-- 事件配置 -->
|
||||
<ThingModelEvent
|
||||
v-if="formData.type === IoTThingModelTypeEnum.EVENT"
|
||||
v-model="formData.event"
|
||||
/>
|
||||
<Form.Item label="描述" name="desc">
|
||||
<Input.TextArea
|
||||
v-model:value="formData.desc"
|
||||
:maxlength="200"
|
||||
:rows="3"
|
||||
placeholder="请输入属性描述"
|
||||
/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
</template>
|
||||
@@ -1,163 +0,0 @@
|
||||
<!-- 产品的物模型表单(event、service 项里的参数) -->
|
||||
<script lang="ts" setup>
|
||||
import type { Ref } from 'vue';
|
||||
|
||||
import { ref, unref } from 'vue';
|
||||
|
||||
import { isEmpty } from '@vben/utils';
|
||||
|
||||
import { useVModel } from '@vueuse/core';
|
||||
import { Button, Divider, Form, Input, Modal } from 'ant-design-vue';
|
||||
|
||||
import { IoTDataSpecsDataTypeEnum } from '#/views/iot/utils/constants';
|
||||
|
||||
import ThingModelProperty from './thing-model-property.vue';
|
||||
|
||||
/** 输入输出参数配置组件 */
|
||||
defineOptions({ name: 'ThingModelInputOutputParam' });
|
||||
|
||||
const props = defineProps<{ direction: string; modelValue: any }>();
|
||||
const emits = defineEmits(['update:modelValue']);
|
||||
const thingModelParams = useVModel(props, 'modelValue', emits) as Ref<any[]>;
|
||||
const dialogVisible = ref(false); // 弹窗的是否展示
|
||||
const formLoading = ref(false); // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const paramFormRef = ref(); // 表单 ref
|
||||
const formData = ref<any>({
|
||||
dataType: IoTDataSpecsDataTypeEnum.INT,
|
||||
property: {
|
||||
dataType: IoTDataSpecsDataTypeEnum.INT,
|
||||
dataSpecs: {
|
||||
dataType: IoTDataSpecsDataTypeEnum.INT,
|
||||
},
|
||||
dataSpecsList: [],
|
||||
},
|
||||
});
|
||||
|
||||
/** 打开 param 表单 */
|
||||
function openParamForm(val: any) {
|
||||
dialogVisible.value = true;
|
||||
resetForm();
|
||||
if (isEmpty(val)) {
|
||||
return;
|
||||
}
|
||||
// 编辑时回显数据
|
||||
const valData = val as any;
|
||||
formData.value = {
|
||||
identifier: valData?.identifier || '',
|
||||
name: valData?.name || '',
|
||||
description: valData?.description || '',
|
||||
property: {
|
||||
dataType: valData?.dataType || IoTDataSpecsDataTypeEnum.INT,
|
||||
dataSpecs: valData?.dataSpecs ?? {},
|
||||
dataSpecsList: valData?.dataSpecsList ?? [],
|
||||
},
|
||||
};
|
||||
|
||||
// 确保 property.dataType 有值
|
||||
if (!formData.value.property.dataType) {
|
||||
formData.value.property.dataType = IoTDataSpecsDataTypeEnum.INT;
|
||||
}
|
||||
}
|
||||
|
||||
/** 删除 param 项 */
|
||||
function deleteParamItem(index: number) {
|
||||
thingModelParams.value.splice(index, 1);
|
||||
}
|
||||
|
||||
/** 添加参数 */
|
||||
async function submitForm() {
|
||||
// 初始化参数列表
|
||||
if (isEmpty(thingModelParams.value)) {
|
||||
thingModelParams.value = [];
|
||||
}
|
||||
// 校验参数
|
||||
await paramFormRef.value.validate();
|
||||
try {
|
||||
// 构建数据对象
|
||||
const data = unref(formData);
|
||||
const item = {
|
||||
identifier: data.identifier,
|
||||
name: data.name,
|
||||
description: data.description,
|
||||
dataType: data.property.dataType,
|
||||
paraOrder: 0, // TODO @puhui999: 先写死默认看看后续
|
||||
direction: props.direction,
|
||||
dataSpecs:
|
||||
!!data.property.dataSpecs &&
|
||||
Object.keys(data.property.dataSpecs).length > 1
|
||||
? data.property.dataSpecs
|
||||
: undefined,
|
||||
dataSpecsList: isEmpty(data.property.dataSpecsList)
|
||||
? undefined
|
||||
: data.property.dataSpecsList,
|
||||
};
|
||||
|
||||
// 新增或修改同 identifier 的参数
|
||||
const existingIndex = thingModelParams.value.findIndex(
|
||||
(spec) => spec.identifier === data.identifier,
|
||||
);
|
||||
if (existingIndex === -1) {
|
||||
thingModelParams.value.push(item);
|
||||
} else {
|
||||
thingModelParams.value[existingIndex] = item;
|
||||
}
|
||||
} finally {
|
||||
dialogVisible.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
function resetForm() {
|
||||
formData.value = {
|
||||
dataType: IoTDataSpecsDataTypeEnum.INT,
|
||||
property: {
|
||||
dataType: IoTDataSpecsDataTypeEnum.INT,
|
||||
dataSpecs: {
|
||||
dataType: IoTDataSpecsDataTypeEnum.INT,
|
||||
},
|
||||
dataSpecsList: [],
|
||||
},
|
||||
};
|
||||
paramFormRef.value?.resetFields();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
v-for="(item, index) in thingModelParams"
|
||||
:key="index"
|
||||
class="w-1/1 px-10px mb-10px flex justify-between bg-gray-100"
|
||||
>
|
||||
<span>参数名称:{{ item.name }}</span>
|
||||
<div class="btn">
|
||||
<Button type="link" @click="openParamForm(item)">编辑</Button>
|
||||
<Divider type="vertical" />
|
||||
<Button type="link" danger @click="deleteParamItem(index)">删除</Button>
|
||||
</div>
|
||||
</div>
|
||||
<Button type="link" @click="openParamForm(null)">+新增参数</Button>
|
||||
|
||||
<!-- param 表单 -->
|
||||
<Modal
|
||||
v-model:open="dialogVisible"
|
||||
title="新增参数"
|
||||
:confirm-loading="formLoading"
|
||||
@ok="submitForm"
|
||||
>
|
||||
<Form
|
||||
ref="paramFormRef"
|
||||
:model="formData"
|
||||
:label-col="{ span: 6 }"
|
||||
:wrapper-col="{ span: 18 }"
|
||||
>
|
||||
<Form.Item label="参数名称" name="name">
|
||||
<Input v-model:value="formData.name" placeholder="请输入功能名称" />
|
||||
</Form.Item>
|
||||
<Form.Item label="标识符" name="identifier">
|
||||
<Input v-model:value="formData.identifier" placeholder="请输入标识符" />
|
||||
</Form.Item>
|
||||
<!-- 属性配置 -->
|
||||
<ThingModelProperty v-model="formData.property" is-params />
|
||||
</Form>
|
||||
</Modal>
|
||||
</template>
|
||||
@@ -1,117 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import type { Ref } from 'vue';
|
||||
|
||||
import type { IotProductApi } from '#/api/iot/product/product';
|
||||
|
||||
import { computed, inject, ref, watch } from 'vue';
|
||||
|
||||
import { Modal, Radio, Textarea } from 'ant-design-vue';
|
||||
|
||||
import { getThingModelTSL } from '#/api/iot/thingmodel';
|
||||
import { IOT_PROVIDE_KEY } from '#/views/iot/utils/constants';
|
||||
|
||||
defineOptions({ name: 'ThingModelTsl' });
|
||||
|
||||
const dialogVisible = ref(false); // 弹窗的是否展示
|
||||
const dialogTitle = ref('物模型 TSL'); // 弹窗的标题
|
||||
const product = inject<Ref<IotProductApi.Product>>(IOT_PROVIDE_KEY.PRODUCT); // 注入产品信息
|
||||
const viewMode = ref('view'); // 查看模式:view-代码视图,editor-编辑器视图
|
||||
|
||||
/** 打开弹窗 */
|
||||
async function open() {
|
||||
dialogVisible.value = true;
|
||||
await getTsl();
|
||||
}
|
||||
defineExpose({ open });
|
||||
|
||||
/** 获取 TSL */
|
||||
const thingModelTSL = ref<any>({});
|
||||
const tslString = ref(''); // 用于编辑器的字符串格式
|
||||
|
||||
async function getTsl() {
|
||||
try {
|
||||
thingModelTSL.value = await getThingModelTSL(product?.value?.id || 0);
|
||||
// 将对象转换为格式化的 JSON 字符串
|
||||
tslString.value = JSON.stringify(thingModelTSL.value, null, 2);
|
||||
} catch (error) {
|
||||
console.error('获取 TSL 失败:', error);
|
||||
thingModelTSL.value = {};
|
||||
tslString.value = '{}';
|
||||
}
|
||||
}
|
||||
|
||||
/** 格式化的 TSL 用于只读展示 */
|
||||
const formattedTSL = computed(() => {
|
||||
try {
|
||||
if (typeof thingModelTSL.value === 'string') {
|
||||
return JSON.stringify(JSON.parse(thingModelTSL.value), null, 2);
|
||||
}
|
||||
return JSON.stringify(thingModelTSL.value, null, 2);
|
||||
} catch {
|
||||
return JSON.stringify(thingModelTSL.value, null, 2);
|
||||
}
|
||||
});
|
||||
|
||||
/** 监听编辑器内容变化,实时更新数据 */
|
||||
watch(tslString, (newValue) => {
|
||||
try {
|
||||
thingModelTSL.value = JSON.parse(newValue);
|
||||
} catch {
|
||||
// JSON 解析失败时保持原值
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal
|
||||
v-model:open="dialogVisible"
|
||||
:title="dialogTitle"
|
||||
:footer="null"
|
||||
width="800px"
|
||||
>
|
||||
<div class="mb-4">
|
||||
<Radio.Group v-model:value="viewMode" size="small">
|
||||
<Radio.Button value="view">代码视图</Radio.Button>
|
||||
<Radio.Button value="editor">编辑器视图</Radio.Button>
|
||||
</Radio.Group>
|
||||
</div>
|
||||
<!-- 代码视图 - 只读展示 -->
|
||||
<div v-if="viewMode === 'view'" class="json-viewer-container">
|
||||
<pre class="json-code"><code>{{ formattedTSL }}</code></pre>
|
||||
</div>
|
||||
<!-- 编辑器视图 - 可编辑 -->
|
||||
<Textarea
|
||||
v-else
|
||||
v-model:value="tslString"
|
||||
:rows="20"
|
||||
placeholder="请输入 JSON 格式的物模型 TSL"
|
||||
class="json-editor"
|
||||
/>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.json-viewer-container {
|
||||
max-height: 600px;
|
||||
padding: 12px;
|
||||
overflow-y: auto;
|
||||
background-color: #f5f5f5;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.json-code {
|
||||
margin: 0;
|
||||
font-family: Monaco, Menlo, 'Ubuntu Mono', Consolas, monospace;
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
color: #333;
|
||||
overflow-wrap: break-word;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.json-editor {
|
||||
font-family: Monaco, Menlo, 'Ubuntu Mono', Consolas, monospace;
|
||||
font-size: 13px;
|
||||
}
|
||||
</style>
|
||||
79
apps/web-antd/src/views/iot/thingmodel/modules/tsl.vue
Normal file
79
apps/web-antd/src/views/iot/thingmodel/modules/tsl.vue
Normal file
@@ -0,0 +1,79 @@
|
||||
<script setup lang="ts">
|
||||
import type { Ref } from 'vue';
|
||||
|
||||
import type { IotProductApi } from '#/api/iot/product/product';
|
||||
|
||||
import { computed, inject, ref, watch } from 'vue';
|
||||
|
||||
import { useVbenModal } from '@vben/common-ui';
|
||||
|
||||
import { Radio, Textarea } from 'ant-design-vue';
|
||||
|
||||
import { getThingModelTSL } from '#/api/iot/thingmodel';
|
||||
import { IOT_PROVIDE_KEY } from '#/views/iot/utils/constants';
|
||||
|
||||
const product = inject<Ref<IotProductApi.Product>>(IOT_PROVIDE_KEY.PRODUCT);
|
||||
|
||||
const viewMode = ref<'editor' | 'view'>('view');
|
||||
const thingModelTSL = ref<any>({});
|
||||
const tslString = ref('');
|
||||
|
||||
const [Modal, modalApi] = useVbenModal({
|
||||
async onOpenChange(isOpen: boolean) {
|
||||
if (!isOpen) {
|
||||
return;
|
||||
}
|
||||
modalApi.lock();
|
||||
try {
|
||||
thingModelTSL.value = await getThingModelTSL(product?.value?.id || 0);
|
||||
tslString.value = JSON.stringify(thingModelTSL.value, null, 2);
|
||||
} finally {
|
||||
modalApi.unlock();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
/** 只读视图下,格式化后的 TSL 字符串 */
|
||||
const formattedTSL = computed(() =>
|
||||
JSON.stringify(thingModelTSL.value, null, 2),
|
||||
);
|
||||
|
||||
/** 编辑器内容变化时,同步到数据对象 */
|
||||
watch(tslString, (newValue) => {
|
||||
try {
|
||||
thingModelTSL.value = JSON.parse(newValue);
|
||||
} catch {
|
||||
// JSON 解析失败时保持原值
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal :footer="false" class="w-3/5" title="物模型 TSL">
|
||||
<div class="mx-4">
|
||||
<div class="mb-4">
|
||||
<Radio.Group v-model:value="viewMode" size="small">
|
||||
<Radio.Button value="view">代码视图</Radio.Button>
|
||||
<Radio.Button value="editor">编辑器视图</Radio.Button>
|
||||
</Radio.Group>
|
||||
</div>
|
||||
<!-- 代码视图:只读展示 -->
|
||||
<div
|
||||
v-if="viewMode === 'view'"
|
||||
class="max-h-[600px] overflow-y-auto rounded border border-gray-200 bg-gray-50 p-3 dark:border-gray-700 dark:bg-gray-800"
|
||||
>
|
||||
<pre
|
||||
class="m-0 whitespace-pre-wrap break-words font-mono text-[13px] leading-normal"
|
||||
><code>{{ formattedTSL }}</code></pre>
|
||||
</div>
|
||||
<!-- 编辑器视图:可编辑 -->
|
||||
<Textarea
|
||||
v-else
|
||||
v-model:value="tslString"
|
||||
:rows="20"
|
||||
class="font-mono text-[13px]"
|
||||
placeholder="请输入 JSON 格式的物模型 TSL"
|
||||
/>
|
||||
</div>
|
||||
</Modal>
|
||||
</template>
|
||||
@@ -1,9 +1,6 @@
|
||||
// TODO @AI:感觉这块,放到 biz-iot-enum 里好点。
|
||||
|
||||
/** 检查值是否为空 */
|
||||
const isEmpty = (value: any): boolean => {
|
||||
return value === null || value === undefined || value === '';
|
||||
};
|
||||
import { isEmpty } from '@vben/utils';
|
||||
|
||||
/** IoT 依赖注入 KEY */
|
||||
export const IOT_PROVIDE_KEY = {
|
||||
@@ -104,19 +101,19 @@ export const IoTDataSpecsDataTypeEnum = {
|
||||
ARRAY: 'array',
|
||||
};
|
||||
|
||||
export const getDataTypeOptions = () => {
|
||||
return [
|
||||
{ value: IoTDataSpecsDataTypeEnum.INT, label: '整数型' },
|
||||
{ value: IoTDataSpecsDataTypeEnum.FLOAT, label: '单精度浮点型' },
|
||||
{ value: IoTDataSpecsDataTypeEnum.DOUBLE, label: '双精度浮点型' },
|
||||
{ value: IoTDataSpecsDataTypeEnum.ENUM, label: '枚举型' },
|
||||
{ value: IoTDataSpecsDataTypeEnum.BOOL, label: '布尔型' },
|
||||
{ value: IoTDataSpecsDataTypeEnum.TEXT, label: '文本型' },
|
||||
{ value: IoTDataSpecsDataTypeEnum.DATE, label: '时间型' },
|
||||
{ value: IoTDataSpecsDataTypeEnum.STRUCT, label: '结构体' },
|
||||
{ value: IoTDataSpecsDataTypeEnum.ARRAY, label: '数组' },
|
||||
];
|
||||
};
|
||||
const DATA_TYPE_OPTIONS = Object.freeze([
|
||||
{ value: IoTDataSpecsDataTypeEnum.INT, label: '整数型' },
|
||||
{ value: IoTDataSpecsDataTypeEnum.FLOAT, label: '单精度浮点型' },
|
||||
{ value: IoTDataSpecsDataTypeEnum.DOUBLE, label: '双精度浮点型' },
|
||||
{ value: IoTDataSpecsDataTypeEnum.ENUM, label: '枚举型' },
|
||||
{ value: IoTDataSpecsDataTypeEnum.BOOL, label: '布尔型' },
|
||||
{ value: IoTDataSpecsDataTypeEnum.TEXT, label: '文本型' },
|
||||
{ value: IoTDataSpecsDataTypeEnum.DATE, label: '时间型' },
|
||||
{ value: IoTDataSpecsDataTypeEnum.STRUCT, label: '结构体' },
|
||||
{ value: IoTDataSpecsDataTypeEnum.ARRAY, label: '数组' },
|
||||
]);
|
||||
|
||||
export const getDataTypeOptions = () => DATA_TYPE_OPTIONS;
|
||||
|
||||
/** 获得物体模型数据类型配置项名称 */
|
||||
export const getDataTypeOptionsLabel = (value: string) => {
|
||||
|
||||
@@ -41,6 +41,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '品牌排序',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
min: 0,
|
||||
placeholder: '请输入品牌排序',
|
||||
},
|
||||
|
||||
@@ -65,6 +65,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '分类排序',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
min: 0,
|
||||
placeholder: '请输入分类排序',
|
||||
},
|
||||
|
||||
@@ -281,6 +281,7 @@ export function useOtherFormSchema(): VbenFormSchema[] {
|
||||
label: '商品排序',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
min: 0,
|
||||
},
|
||||
rules: 'required',
|
||||
@@ -290,6 +291,7 @@ export function useOtherFormSchema(): VbenFormSchema[] {
|
||||
label: '赠送积分',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
min: 0,
|
||||
},
|
||||
rules: 'required',
|
||||
@@ -299,6 +301,7 @@ export function useOtherFormSchema(): VbenFormSchema[] {
|
||||
label: '虚拟销量',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
min: 0,
|
||||
},
|
||||
rules: 'required',
|
||||
|
||||
@@ -40,6 +40,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '显示顺序',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
min: 0,
|
||||
placeholder: '请输入显示顺序',
|
||||
},
|
||||
|
||||
@@ -106,6 +106,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '排序',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
min: 0,
|
||||
placeholder: '请输入排序',
|
||||
},
|
||||
|
||||
@@ -62,6 +62,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '排序',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
min: 0,
|
||||
placeholder: '请输入排序',
|
||||
},
|
||||
|
||||
@@ -55,6 +55,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '助力人数',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
min: 1,
|
||||
placeholder: '达到该人数才能砍到低价',
|
||||
},
|
||||
@@ -65,6 +66,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '砍价次数',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
min: 1,
|
||||
placeholder: '最大帮砍次数',
|
||||
},
|
||||
@@ -75,6 +77,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '购买限制',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
min: 1,
|
||||
placeholder: '最大购买次数',
|
||||
},
|
||||
@@ -85,6 +88,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '最小砍价金额(元)',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
min: 0,
|
||||
precision: 2,
|
||||
step: 0.01,
|
||||
@@ -96,6 +100,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '最大砍价金额(元)',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
min: 0,
|
||||
precision: 2,
|
||||
step: 0.01,
|
||||
|
||||
@@ -55,6 +55,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '拼团人数',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
placeholder: '达到该人数即成团',
|
||||
min: 2,
|
||||
},
|
||||
@@ -65,6 +66,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '限制时长',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
placeholder: '限制时长(小时)',
|
||||
min: 0,
|
||||
},
|
||||
@@ -75,6 +77,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '总限购数量',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
placeholder: '请输入总限购数量',
|
||||
min: 0,
|
||||
},
|
||||
@@ -84,6 +87,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '单次限购数量',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
placeholder: '请输入单次限购数量',
|
||||
min: 0,
|
||||
},
|
||||
|
||||
@@ -122,6 +122,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '优惠券面额',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
min: 0,
|
||||
precision: 2,
|
||||
placeholder: '请输入优惠金额,单位:元',
|
||||
@@ -139,6 +140,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '优惠券折扣',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
min: 1,
|
||||
max: 9.9,
|
||||
precision: 1,
|
||||
@@ -157,6 +159,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '最多优惠',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
min: 0,
|
||||
precision: 2,
|
||||
placeholder: '请输入最多优惠',
|
||||
@@ -174,6 +177,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '满多少元可以使用',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
min: 0,
|
||||
precision: 2,
|
||||
placeholder: '无门槛请设为 0',
|
||||
@@ -196,6 +200,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '发放数量',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
min: -1,
|
||||
placeholder: '发放数量,没有之后不能领取或发放,-1 为不限制',
|
||||
addonAfter: '张',
|
||||
@@ -212,6 +217,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '每人限领个数',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
min: -1,
|
||||
placeholder: '设置为 -1 时,可无限领取',
|
||||
addonAfter: '张',
|
||||
@@ -255,6 +261,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '领取日期',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
min: 0,
|
||||
placeholder: '第 0 为今天生效',
|
||||
addonBefore: '第',
|
||||
@@ -271,6 +278,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
fieldName: 'fixedEndTerm',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
min: 0,
|
||||
placeholder: '请输入结束天数',
|
||||
addonBefore: '至',
|
||||
|
||||
@@ -109,9 +109,9 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '排序',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
min: 0,
|
||||
placeholder: '请输入排序',
|
||||
class: '!w-full',
|
||||
},
|
||||
defaultValue: 0,
|
||||
rules: 'required',
|
||||
|
||||
@@ -99,9 +99,9 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '总限购数量',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
placeholder: '请输入总限购数量',
|
||||
min: 0,
|
||||
class: 'w-full',
|
||||
},
|
||||
rules: z.number().min(0).default(0),
|
||||
},
|
||||
@@ -110,9 +110,9 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '单次限购数量',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
placeholder: '请输入单次限购数量',
|
||||
min: 0,
|
||||
class: 'w-full',
|
||||
},
|
||||
rules: z.number().min(0).default(0),
|
||||
},
|
||||
@@ -121,9 +121,9 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '排序',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
placeholder: '请输入排序',
|
||||
min: 0,
|
||||
class: 'w-full',
|
||||
},
|
||||
rules: z.number().min(0).default(0),
|
||||
},
|
||||
|
||||
@@ -63,10 +63,10 @@ export const schema: VbenFormSchema[] = [
|
||||
label: '满额包邮',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
min: 0,
|
||||
precision: 2,
|
||||
placeholder: '请输入满额包邮金额',
|
||||
class: 'w-full',
|
||||
},
|
||||
rules: 'required',
|
||||
dependencies: {
|
||||
@@ -145,10 +145,10 @@ export const schema: VbenFormSchema[] = [
|
||||
label: '一级返佣比例(%)',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
min: 0,
|
||||
max: 100,
|
||||
placeholder: '请输入一级返佣比例',
|
||||
class: 'w-full',
|
||||
},
|
||||
rules: 'required',
|
||||
dependencies: {
|
||||
@@ -162,10 +162,10 @@ export const schema: VbenFormSchema[] = [
|
||||
label: '二级返佣比例(%)',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
min: 0,
|
||||
max: 100,
|
||||
placeholder: '请输入二级返佣比例',
|
||||
class: 'w-full',
|
||||
},
|
||||
rules: 'required',
|
||||
dependencies: {
|
||||
@@ -179,9 +179,9 @@ export const schema: VbenFormSchema[] = [
|
||||
label: '佣金冻结天数',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
min: 0,
|
||||
placeholder: '请输入佣金冻结天数',
|
||||
class: 'w-full',
|
||||
},
|
||||
rules: 'required',
|
||||
dependencies: {
|
||||
@@ -195,10 +195,10 @@ export const schema: VbenFormSchema[] = [
|
||||
label: '提现最低金额(元)',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
min: 0,
|
||||
precision: 2,
|
||||
placeholder: '请输入提现最低金额',
|
||||
class: 'w-full',
|
||||
},
|
||||
rules: 'required',
|
||||
dependencies: {
|
||||
@@ -212,11 +212,11 @@ export const schema: VbenFormSchema[] = [
|
||||
label: '提现手续费(%)',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
min: 0,
|
||||
max: 100,
|
||||
precision: 2,
|
||||
placeholder: '请输入提现手续费百分比',
|
||||
class: 'w-full',
|
||||
},
|
||||
rules: 'required',
|
||||
dependencies: {
|
||||
|
||||
@@ -47,6 +47,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '显示顺序',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
min: 0,
|
||||
placeholder: '请输入显示顺序',
|
||||
},
|
||||
|
||||
@@ -140,6 +140,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '显示顺序',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
placeholder: '请输入显示顺序',
|
||||
min: 0,
|
||||
},
|
||||
|
||||
@@ -306,6 +306,7 @@ export function usePriceFormSchema(): VbenFormSchema[] {
|
||||
label: '订单调价',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
placeholder: '请输入订单调价',
|
||||
step: 0.1,
|
||||
precision: 2,
|
||||
|
||||
@@ -21,9 +21,9 @@ export const schema: VbenFormSchema[] = [
|
||||
label: '积分抵扣',
|
||||
help: '积分抵用比例(1 积分抵多少金额),单位:元',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
min: 0,
|
||||
precision: 2,
|
||||
class: 'w-full',
|
||||
placeholder: '请输入积分抵扣单价',
|
||||
},
|
||||
},
|
||||
@@ -33,8 +33,8 @@ export const schema: VbenFormSchema[] = [
|
||||
label: '积分抵扣最大值',
|
||||
help: '单次下单积分使用上限,0 不限制',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
min: 0,
|
||||
class: 'w-full',
|
||||
placeholder: '请输入积分抵扣最大值',
|
||||
},
|
||||
},
|
||||
@@ -44,8 +44,8 @@ export const schema: VbenFormSchema[] = [
|
||||
label: '1 元赠送多少分',
|
||||
help: '下单支付金额按比例赠送积分(实际支付 1 元赠送多少积分)',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
min: 0,
|
||||
class: 'w-full',
|
||||
placeholder: '请输入赠送积分比例',
|
||||
},
|
||||
},
|
||||
|
||||
@@ -32,6 +32,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '等级',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
min: 0,
|
||||
precision: 0,
|
||||
placeholder: '请输入等级',
|
||||
@@ -43,6 +44,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '升级经验',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
min: 0,
|
||||
precision: 0,
|
||||
placeholder: '请输入升级经验',
|
||||
@@ -54,6 +56,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '享受折扣(%)',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
min: 0,
|
||||
max: 100,
|
||||
precision: 0,
|
||||
|
||||
@@ -23,6 +23,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '签到天数',
|
||||
help: '只允许设置 1-7,默认签到 7 天为一个周期',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
min: 1,
|
||||
max: 7,
|
||||
precision: 0,
|
||||
@@ -35,6 +36,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
fieldName: 'point',
|
||||
label: '获得积分',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
min: 0,
|
||||
precision: 0,
|
||||
placeholder: '请输入获得积分',
|
||||
@@ -46,6 +48,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
fieldName: 'experience',
|
||||
label: '奖励经验',
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
min: 0,
|
||||
precision: 0,
|
||||
placeholder: '请输入奖励经验',
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user