Compare commits
128 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b42e9b36e5 | ||
|
|
f8c2d4b1ff | ||
|
|
41d5aa93d6 | ||
|
|
3135b28211 | ||
|
|
08511191f7 | ||
|
|
0246fa1ebc | ||
|
|
0e4012c623 | ||
|
|
4933180560 | ||
|
|
8710da9383 | ||
|
|
5a1f4901da | ||
|
|
3da4a3f417 | ||
|
|
84b91c6795 | ||
|
|
735ff018be | ||
|
|
0163794e3f | ||
|
|
bb63ca9541 | ||
|
|
6b28518165 | ||
|
|
4adce844d3 | ||
|
|
19b5f38e23 | ||
|
|
80fa8b74e8 | ||
|
|
5710761dbe | ||
|
|
877ba2727f | ||
|
|
70a639967c | ||
|
|
59183029b6 | ||
|
|
e81ca2c13f | ||
|
|
dcccef1c02 | ||
|
|
9a5bee4dce | ||
|
|
29665f02bf | ||
|
|
06f776d1ef | ||
|
|
40f0ba71f5 | ||
|
|
0fced45a9c | ||
|
|
2ea7da06c5 | ||
|
|
c164904a14 | ||
|
|
a0ceb45df9 | ||
|
|
c641542c71 | ||
|
|
a3d8e4bfc1 | ||
|
|
e385823d46 | ||
|
|
897220e19a | ||
|
|
b293e112c6 | ||
|
|
627e31f1b0 | ||
|
|
8020b4b743 | ||
|
|
228c5463da | ||
|
|
50ee691191 | ||
|
|
eda6ffaf1e | ||
|
|
f542db27f9 | ||
|
|
b2cf1646a4 | ||
|
|
adecddae67 | ||
|
|
a653e428f3 | ||
|
|
eb62e63a04 | ||
|
|
ccabbf0e97 | ||
|
|
5b84ac5b13 | ||
|
|
f610bd690b | ||
|
|
76f9d3d9fc | ||
|
|
1e6c39a4c6 | ||
|
|
7a1f8da68f | ||
|
|
51cae9b00c | ||
|
|
7cbeaa8390 | ||
|
|
6be3a0e204 | ||
|
|
a9b76ba2ed | ||
|
|
53ccec1d80 | ||
|
|
4af5d6152b | ||
|
|
307781f437 | ||
|
|
1326994d8e | ||
|
|
fd70a3f3e0 | ||
|
|
298930b0d7 | ||
|
|
54d95b8761 | ||
|
|
4a16040d3e | ||
|
|
ee95548340 | ||
|
|
320e687bad | ||
|
|
ad43c6817e | ||
|
|
c8747c079d | ||
|
|
224bfe7fcb | ||
|
|
f443bfbc7b | ||
|
|
195b2ea0d2 | ||
|
|
4150479549 | ||
|
|
5ebf513498 | ||
|
|
4e4ffc439c | ||
|
|
ad7ed50b52 | ||
|
|
92f8916225 | ||
|
|
7e4edd270d | ||
|
|
332ff44219 | ||
|
|
834ce3efc0 | ||
|
|
5211f5065d | ||
|
|
96d6f89732 | ||
|
|
6ab06584eb | ||
|
|
a6433c2b50 | ||
|
|
128a131797 | ||
|
|
c775d7ed80 | ||
|
|
b8b4308e1c | ||
|
|
80d6e2255f | ||
|
|
4e0968d4b7 | ||
|
|
44a5809a46 | ||
|
|
2428fb1407 | ||
|
|
bb78882f72 | ||
|
|
df88a23102 | ||
|
|
ca5f360231 | ||
|
|
147b50ec45 | ||
|
|
34439dce4e | ||
|
|
9a22027b35 | ||
|
|
282a102826 | ||
|
|
417e6c2ade | ||
|
|
9d69d7f46c | ||
|
|
87d1593a1f | ||
|
|
7fbdf3d914 | ||
|
|
65287cf4b7 | ||
|
|
6da3017dcf | ||
|
|
5c02057198 | ||
|
|
a7ca7cdb9f | ||
|
|
79408d406d | ||
|
|
e555f71bf8 | ||
|
|
4c320346c3 | ||
|
|
e5ec88169a | ||
|
|
914711ae04 | ||
|
|
4c1e3b9548 | ||
|
|
9cd3987475 | ||
|
|
47a853330d | ||
|
|
2aced2f659 | ||
|
|
cd955df02f | ||
|
|
0a819df2bf | ||
|
|
67afcadcf0 | ||
|
|
1128ef5acd | ||
|
|
ca39b8d0c9 | ||
|
|
6b3506f128 | ||
|
|
5613dcef99 | ||
|
|
53c5ccc00a | ||
|
|
1cbdf442ee | ||
|
|
f91a2702c9 | ||
|
|
a8f67ab717 | ||
|
|
e136679934 |
BIN
.gitee/image/common/iot-feature.png
Normal file
|
After Width: | Height: | Size: 58 KiB |
BIN
.gitee/image/common/iot-preview.png
Normal file
|
After Width: | Height: | Size: 163 KiB |
BIN
.gitee/image/common/mes-feature.png
Normal file
|
After Width: | Height: | Size: 79 KiB |
BIN
.gitee/image/common/mes-preview.png
Normal file
|
After Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 41 KiB |
BIN
.gitee/image/common/wms-feature.png
Normal file
|
After Width: | Height: | Size: 216 KiB |
BIN
.gitee/image/common/wms-preview.png
Normal file
|
After Width: | Height: | Size: 54 KiB |
2
.github/workflows/ci.yml
vendored
@@ -33,7 +33,7 @@ jobs:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
uses: pnpm/action-setup@v5
|
||||
with:
|
||||
run_install: false
|
||||
|
||||
|
||||
2
.github/workflows/draft.yml
vendored
@@ -20,6 +20,6 @@ jobs:
|
||||
if: github.repository == 'vbenjs/vue-vben-admin'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: release-drafter/release-drafter@v6
|
||||
- uses: release-drafter/release-drafter@v7
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
2
.github/workflows/release-tag.yml
vendored
@@ -58,7 +58,7 @@ jobs:
|
||||
echo "version=${version}" >> $GITHUB_OUTPUT
|
||||
echo "major=${major}" >> $GITHUB_OUTPUT
|
||||
|
||||
- uses: release-drafter/release-drafter@v6
|
||||
- uses: release-drafter/release-drafter@v7
|
||||
with:
|
||||
version: ${{ steps.version.outputs.version }}
|
||||
publish: true
|
||||
|
||||
2
.gitignore
vendored
@@ -22,7 +22,7 @@ yarn.lock
|
||||
package-lock.json
|
||||
.VSCodeCounter
|
||||
**/backend-mock/data
|
||||
|
||||
.omx
|
||||
# local env files
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
9
.vscode/launch.json
vendored
@@ -2,6 +2,15 @@
|
||||
"$schema": "https://json.schemastore.org/launchsettings.json",
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "chrome",
|
||||
"name": "vben admin antd dev",
|
||||
"request": "launch",
|
||||
"url": "http://localhost:5999",
|
||||
"env": { "NODE_ENV": "development" },
|
||||
"sourceMaps": true,
|
||||
"webRoot": "${workspaceFolder}/apps/web-antdv-next"
|
||||
},
|
||||
{
|
||||
"type": "chrome",
|
||||
"name": "vben admin antd dev",
|
||||
|
||||
10
.vscode/settings.json
vendored
@@ -181,9 +181,10 @@
|
||||
"stylelint.customSyntax": "postcss-html",
|
||||
"stylelint.snippet": ["css", "less", "postcss", "scss", "vue"],
|
||||
|
||||
"typescript.inlayHints.enumMemberValues.enabled": true,
|
||||
"typescript.preferences.preferTypeOnlyAutoImports": true,
|
||||
"typescript.preferences.includePackageJsonAutoImports": "on",
|
||||
"js/ts.tsdk.path": "node_modules/typescript/lib",
|
||||
"js/ts.inlayHints.enumMemberValues.enabled": true,
|
||||
"js/ts.preferences.preferTypeOnlyAutoImports": true,
|
||||
"js/ts.preferences.includePackageJsonAutoImports": "on",
|
||||
|
||||
"eslint.validate": [
|
||||
"javascript",
|
||||
@@ -236,6 +237,5 @@
|
||||
},
|
||||
"commentTranslate.hover.enabled": false,
|
||||
"commentTranslate.multiLineMerge": true,
|
||||
"vue.server.hybridMode": true,
|
||||
"typescript.tsdk": "node_modules/typescript/lib"
|
||||
"vue.server.hybridMode": true
|
||||
}
|
||||
|
||||
36
README.md
@@ -84,7 +84,7 @@
|
||||
|
||||
- 通用模块(必选):系统功能、基础设施
|
||||
- 通用模块(可选):工作流程、支付系统、数据报表、会员中心
|
||||
- 业务系统(按需):ERP 系统、CRM 系统、商城系统、微信公众号、AI 大模型
|
||||
- 业务系统(按需):Mall 电子商城、OA 办公自动化、ERP 企业资源计划系统、WMS 仓库管理系统、CRM 客户关系管理、CMS 内容管理系统、MES 执行制造系统、AI 大模型平台、IoT 物联网系统、IM 即时通讯系统、Mobile 手机移动端、Report 数据大屏
|
||||
|
||||
### 系统功能
|
||||
|
||||
@@ -219,18 +219,44 @@
|
||||
|
||||

|
||||
|
||||
### 会员中心
|
||||
|
||||
| | 功能 | 描述 |
|
||||
| --- | --- | --- |
|
||||
| 🚀 | 会员管理 | 会员是 C 端的消费者,该功能用于会员的搜索与管理 |
|
||||
| 🚀 | 会员标签 | 对会员的标签进行创建、查询、修改、删除等操作 |
|
||||
| 🚀 | 会员等级 | 对会员的等级、成长值进行管理,可用于订单折扣等会员权益 |
|
||||
| 🚀 | 会员分组 | 对会员进行分组,用于用户画像、内容推送等运营手段 |
|
||||
| 🚀 | 积分签到 | 回馈给签到、消费等行为的积分,会员可订单抵现、积分兑换等途径消耗 |
|
||||
|
||||
### ERP 系统
|
||||
|
||||
演示地址:<https://doc.iocoder.cn/erp-preview/>
|
||||
|
||||

|
||||
|
||||
### WMS 系统
|
||||
|
||||
演示地址:<https://doc.iocoder.cn/wms-preview/>
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
### CRM 系统
|
||||
|
||||
演示地址:<https://doc.iocoder.cn/crm-preview/>
|
||||
|
||||

|
||||
|
||||
### MES 系统
|
||||
|
||||
演示地址:<https://doc.iocoder.cn/mes-preview/>
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
### AI 大模型
|
||||
|
||||
演示地址:<https://doc.iocoder.cn/ai-preview/>
|
||||
@@ -238,3 +264,11 @@
|
||||

|
||||
|
||||

|
||||
|
||||
### IoT 物联网
|
||||
|
||||
演示地址:<https://doc.iocoder.cn/iot/build>
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=0"
|
||||
/>
|
||||
<!-- 由 vite 注入 VITE_APP_TITLE 变量,在 .env 文件内配置 -->
|
||||
<title><%= VITE_APP_TITLE %></title>
|
||||
<title>%VITE_APP_TITLE%</title>
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<script>
|
||||
var HM_ID = '<%= VITE_APP_BAIDU_CODE %>';
|
||||
|
||||
@@ -6,14 +6,38 @@
|
||||
/* eslint-disable vue/one-component-per-file */
|
||||
|
||||
import type {
|
||||
AutoCompleteProps,
|
||||
ButtonProps,
|
||||
CascaderProps,
|
||||
CheckboxGroupProps,
|
||||
CheckboxProps,
|
||||
DatePickerProps,
|
||||
DividerProps,
|
||||
InputNumberProps,
|
||||
InputProps,
|
||||
MentionsProps,
|
||||
RadioGroupProps,
|
||||
RadioProps,
|
||||
RateProps,
|
||||
SelectProps,
|
||||
SpaceProps,
|
||||
SwitchProps,
|
||||
TextAreaProps,
|
||||
TimePickerProps,
|
||||
TreeSelectProps,
|
||||
UploadChangeParam,
|
||||
UploadFile,
|
||||
UploadProps,
|
||||
} from 'ant-design-vue';
|
||||
import type { RangePickerProps } from 'ant-design-vue/es/date-picker';
|
||||
|
||||
import type { Component, Ref } from 'vue';
|
||||
|
||||
import type { BaseFormComponentType } from '@vben/common-ui';
|
||||
import type {
|
||||
ApiComponentSharedProps,
|
||||
BaseFormComponentType,
|
||||
IconPickerProps,
|
||||
} from '@vben/common-ui';
|
||||
import type { Sortable } from '@vben/hooks';
|
||||
import type { Recordable } from '@vben/types';
|
||||
|
||||
@@ -46,6 +70,15 @@ import { message, Modal, notification } from 'ant-design-vue';
|
||||
|
||||
import { Tinymce as RichTextarea } from '#/components/tinymce';
|
||||
import { FileUpload, ImageUpload } from '#/components/upload';
|
||||
type AdapterUploadProps = UploadProps & {
|
||||
aspectRatio?: string;
|
||||
crop?: boolean;
|
||||
draggable?: boolean;
|
||||
handleChange?: (event: UploadChangeParam) => void;
|
||||
maxSize?: number;
|
||||
onDragSort?: (oldIndex: number, newIndex: number) => void;
|
||||
onHandleChange?: (event: UploadChangeParam) => void;
|
||||
};
|
||||
|
||||
const AutoComplete = defineAsyncComponent(
|
||||
() => import('ant-design-vue/es/auto-complete'),
|
||||
@@ -602,6 +635,39 @@ export type ComponentType =
|
||||
| 'Upload'
|
||||
| BaseFormComponentType;
|
||||
|
||||
/**
|
||||
* 与 {@link ComponentType} 中注册的组件名一一对应,便于 Schema 上 `component` + `componentProps` 联动提示
|
||||
*/
|
||||
export interface ComponentPropsMap {
|
||||
ApiCascader: ApiComponentSharedProps & CascaderProps;
|
||||
ApiSelect: ApiComponentSharedProps & SelectProps;
|
||||
ApiTreeSelect: ApiComponentSharedProps & TreeSelectProps;
|
||||
AutoComplete: AutoCompleteProps;
|
||||
Cascader: CascaderProps;
|
||||
Checkbox: CheckboxProps;
|
||||
CheckboxGroup: CheckboxGroupProps;
|
||||
DatePicker: DatePickerProps;
|
||||
DefaultButton: ButtonProps;
|
||||
Divider: DividerProps;
|
||||
IconPicker: IconPickerProps;
|
||||
Input: InputProps;
|
||||
InputNumber: InputNumberProps;
|
||||
InputPassword: InputProps;
|
||||
Mentions: MentionsProps;
|
||||
PrimaryButton: ButtonProps;
|
||||
Radio: RadioProps;
|
||||
RadioGroup: RadioGroupProps;
|
||||
RangePicker: RangePickerProps;
|
||||
Rate: RateProps;
|
||||
Select: SelectProps;
|
||||
Space: SpaceProps;
|
||||
Switch: SwitchProps;
|
||||
Textarea: TextAreaProps;
|
||||
TimePicker: TimePickerProps;
|
||||
TreeSelect: TreeSelectProps;
|
||||
Upload: AdapterUploadProps;
|
||||
}
|
||||
|
||||
async function initComponentAdapter() {
|
||||
const components: Partial<Record<ComponentType, Component>> = {
|
||||
// 如果你的组件体积比较大,可以使用异步加载
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import type {
|
||||
VbenFormProps as FormProps,
|
||||
VbenFormSchema as FormSchema,
|
||||
VbenFormProps,
|
||||
} from '@vben/common-ui';
|
||||
|
||||
import type { ComponentType } from './component';
|
||||
import type { ComponentPropsMap, ComponentType } from './component';
|
||||
|
||||
import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
|
||||
import { $t } from '@vben/locales';
|
||||
@@ -61,9 +61,9 @@ async function initSetupVbenForm() {
|
||||
});
|
||||
}
|
||||
|
||||
const useVbenForm = useForm<ComponentType>;
|
||||
const useVbenForm = useForm<ComponentType, ComponentPropsMap>;
|
||||
|
||||
export { initSetupVbenForm, useVbenForm, z };
|
||||
|
||||
export type VbenFormSchema = FormSchema<ComponentType>;
|
||||
export type { VbenFormProps };
|
||||
export type VbenFormSchema = FormSchema<ComponentType, ComponentPropsMap>;
|
||||
export type VbenFormProps = FormProps<ComponentType, ComponentPropsMap>;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import type { VxeTableGridOptions } from '@vben/plugins/vxe-table';
|
||||
import type { Recordable } from '@vben/types';
|
||||
|
||||
import type { ComponentPropsMap, ComponentType } from './component';
|
||||
|
||||
import { h } from 'vue';
|
||||
|
||||
import { IconifyIcon } from '@vben/icons';
|
||||
@@ -10,7 +12,7 @@ import {
|
||||
AsyncVxeTable,
|
||||
createRequiredValidation,
|
||||
setupVbenVxeTable,
|
||||
useVbenVxeGrid,
|
||||
useVbenVxeGrid as useGrid,
|
||||
} from '@vben/plugins/vxe-table';
|
||||
import {
|
||||
erpCountInputFormatter,
|
||||
@@ -363,10 +365,13 @@ setupVbenVxeTable({
|
||||
useVbenForm,
|
||||
});
|
||||
|
||||
export { createRequiredValidation, useVbenVxeGrid };
|
||||
export { createRequiredValidation };
|
||||
|
||||
export const [VxeTable, VxeColumn] = [AsyncVxeTable, AsyncVxeColumn];
|
||||
|
||||
export * from '#/components/table-action';
|
||||
export const useVbenVxeGrid = <T extends Record<string, any>>(
|
||||
...rest: Parameters<typeof useGrid<T, ComponentType, ComponentPropsMap>>
|
||||
) => useGrid<T, ComponentType, ComponentPropsMap>(...rest);
|
||||
|
||||
export type * from '@vben/plugins/vxe-table';
|
||||
|
||||
@@ -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 },
|
||||
});
|
||||
}
|
||||
*/
|
||||
|
||||
@@ -128,6 +128,49 @@ function createRequestClient(baseURL: string, options?: RequestClientOptions) {
|
||||
},
|
||||
});
|
||||
|
||||
// add by 芋艿:对应 https://t.zsxq.com/SHqWw 反馈
|
||||
// 处理 Blob 响应中的业务错误(如 401):后端把「账号未登录」包成 HTTP 200 + body {code: 401, msg: ...},
|
||||
// download 强制 responseType: 'blob' 后被 axios 包成 application/json 的 Blob,defaultResponseInterceptor 走
|
||||
// responseReturn === 'body' 分支直接返回,绕过了 authenticateResponseInterceptor 的 401 token 刷新;
|
||||
// 这里把这种 Blob 解析回 JSON,再以 axios 风格抛出,让后续拦截器接管
|
||||
client.addResponseInterceptor({
|
||||
fulfilled: async (response) => {
|
||||
const blob = response.data;
|
||||
if (!(blob instanceof Blob)) {
|
||||
return response;
|
||||
}
|
||||
// Blob.type 在部分环境可能为空或大小写不一,叠加 response header 一起判断更稳
|
||||
const blobType = (blob.type || '').toLowerCase();
|
||||
const headerType = String(
|
||||
response.headers?.['content-type'] ??
|
||||
response.headers?.['Content-Type'] ??
|
||||
'',
|
||||
).toLowerCase();
|
||||
if (
|
||||
!blobType.includes('application/json') &&
|
||||
!headerType.includes('application/json')
|
||||
) {
|
||||
return response;
|
||||
}
|
||||
let parsed: any;
|
||||
try {
|
||||
parsed = JSON.parse(await blob.text());
|
||||
} catch {
|
||||
return response;
|
||||
}
|
||||
if (parsed && parsed.code !== undefined && parsed.code !== 0) {
|
||||
response.data = parsed;
|
||||
throw Object.assign(new Error(parsed.msg ?? 'Request failed'), {
|
||||
config: response.config,
|
||||
response,
|
||||
data: parsed,
|
||||
isAxiosError: true,
|
||||
});
|
||||
}
|
||||
return response;
|
||||
},
|
||||
});
|
||||
|
||||
// 处理返回的响应数据格式
|
||||
client.addResponseInterceptor(
|
||||
defaultResponseInterceptor({
|
||||
|
||||
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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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 });
|
||||
}
|
||||
@@ -6,18 +6,36 @@ export function useImagesUpload() {
|
||||
return defineComponent({
|
||||
name: 'ImagesUpload',
|
||||
props: {
|
||||
multiple: {
|
||||
accept: {
|
||||
type: Array,
|
||||
default: () => ['image/jpeg', 'image/png', 'image/gif'],
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
default: false,
|
||||
},
|
||||
maxNumber: {
|
||||
type: Number,
|
||||
default: 5,
|
||||
},
|
||||
maxSize: {
|
||||
type: Number,
|
||||
default: 5,
|
||||
},
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
return () => (
|
||||
<ImageUpload maxNumber={props.maxNumber} multiple={props.multiple} />
|
||||
<ImageUpload
|
||||
accept={props.accept as string[]}
|
||||
disabled={props.disabled}
|
||||
maxNumber={props.maxNumber}
|
||||
maxSize={props.maxSize}
|
||||
multiple={props.multiple}
|
||||
/>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -26,7 +26,7 @@ export function useUploadFileRule() {
|
||||
makeRequiredRule(),
|
||||
{
|
||||
type: 'select',
|
||||
field: 'fileType',
|
||||
field: 'accept',
|
||||
title: '文件类型',
|
||||
value: ['doc', 'xls', 'ppt', 'txt', 'pdf'],
|
||||
options: [
|
||||
@@ -40,12 +40,6 @@ export function useUploadFileRule() {
|
||||
mode: 'multiple',
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'switch',
|
||||
field: 'autoUpload',
|
||||
title: '是否在选取文件后立即进行上传',
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
type: 'switch',
|
||||
field: 'drag',
|
||||
@@ -54,23 +48,23 @@ export function useUploadFileRule() {
|
||||
},
|
||||
{
|
||||
type: 'switch',
|
||||
field: 'isShowTip',
|
||||
field: 'showDescription',
|
||||
title: '是否显示提示',
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
type: 'inputNumber',
|
||||
field: 'fileSize',
|
||||
field: 'maxSize',
|
||||
title: '大小限制(MB)',
|
||||
value: 5,
|
||||
props: { min: 0 },
|
||||
},
|
||||
{
|
||||
type: 'inputNumber',
|
||||
field: 'limit',
|
||||
field: 'maxNumber',
|
||||
title: '数量限制',
|
||||
value: 5,
|
||||
props: { min: 0 },
|
||||
props: { min: 1 },
|
||||
},
|
||||
{
|
||||
type: 'switch',
|
||||
|
||||
@@ -24,15 +24,9 @@ export function useUploadImageRule() {
|
||||
props(_: any, { t }: any) {
|
||||
return localeProps(t, `${name}.props`, [
|
||||
makeRequiredRule(),
|
||||
{
|
||||
type: 'switch',
|
||||
field: 'drag',
|
||||
title: '拖拽上传',
|
||||
value: false,
|
||||
},
|
||||
{
|
||||
type: 'select',
|
||||
field: 'fileType',
|
||||
field: 'accept',
|
||||
title: '图片类型限制',
|
||||
value: ['image/jpeg', 'image/png', 'image/gif'],
|
||||
options: [
|
||||
@@ -52,40 +46,16 @@ export function useUploadImageRule() {
|
||||
},
|
||||
{
|
||||
type: 'inputNumber',
|
||||
field: 'fileSize',
|
||||
field: 'maxSize',
|
||||
title: '大小限制(MB)',
|
||||
value: 5,
|
||||
props: { min: 0 },
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
field: 'height',
|
||||
title: '组件高度',
|
||||
value: '150px',
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
field: 'width',
|
||||
title: '组件宽度',
|
||||
value: '150px',
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
field: 'borderradius',
|
||||
title: '组件边框圆角',
|
||||
value: '8px',
|
||||
},
|
||||
{
|
||||
type: 'switch',
|
||||
field: 'disabled',
|
||||
title: '是否显示删除按钮',
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
type: 'switch',
|
||||
field: 'showBtnText',
|
||||
title: '是否显示按钮文字',
|
||||
value: true,
|
||||
title: '是否禁用',
|
||||
value: false,
|
||||
},
|
||||
]);
|
||||
},
|
||||
|
||||
@@ -24,15 +24,9 @@ export function useUploadImagesRule() {
|
||||
props(_: any, { t }: any) {
|
||||
return localeProps(t, `${name}.props`, [
|
||||
makeRequiredRule(),
|
||||
{
|
||||
type: 'switch',
|
||||
field: 'drag',
|
||||
title: '拖拽上传',
|
||||
value: false,
|
||||
},
|
||||
{
|
||||
type: 'select',
|
||||
field: 'fileType',
|
||||
field: 'accept',
|
||||
title: '图片类型限制',
|
||||
value: ['image/jpeg', 'image/png', 'image/gif'],
|
||||
options: [
|
||||
@@ -48,40 +42,27 @@ export function useUploadImagesRule() {
|
||||
],
|
||||
props: {
|
||||
mode: 'multiple',
|
||||
maxNumber: 5,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'inputNumber',
|
||||
field: 'fileSize',
|
||||
field: 'maxSize',
|
||||
title: '大小限制(MB)',
|
||||
value: 5,
|
||||
props: { min: 0 },
|
||||
},
|
||||
{
|
||||
type: 'inputNumber',
|
||||
field: 'limit',
|
||||
field: 'maxNumber',
|
||||
title: '数量限制',
|
||||
value: 5,
|
||||
props: { min: 0 },
|
||||
props: { min: 1 },
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
field: 'height',
|
||||
title: '组件高度',
|
||||
value: '150px',
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
field: 'width',
|
||||
title: '组件宽度',
|
||||
value: '150px',
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
field: 'borderradius',
|
||||
title: '组件边框圆角',
|
||||
value: '8px',
|
||||
type: 'switch',
|
||||
field: 'disabled',
|
||||
title: '是否禁用',
|
||||
value: false,
|
||||
},
|
||||
]);
|
||||
},
|
||||
|
||||
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>
|
||||
@@ -1,7 +1,7 @@
|
||||
import { initPreferences } from '@vben/preferences';
|
||||
import { unmountGlobalLoading } from '@vben/utils';
|
||||
|
||||
import { overridesPreferences } from './preferences';
|
||||
import { overridesPreferences, preferencesExtension } from './preferences';
|
||||
|
||||
/**
|
||||
* 应用初始化完成之后再进行页面加载渲染
|
||||
@@ -15,6 +15,7 @@ async function initApplication() {
|
||||
|
||||
// app偏好设置初始化
|
||||
await initPreferences({
|
||||
extension: preferencesExtension,
|
||||
namespace,
|
||||
overrides: overridesPreferences,
|
||||
});
|
||||
|
||||
@@ -1,4 +1,14 @@
|
||||
import { defineOverridesPreferences } from '@vben/preferences';
|
||||
import {
|
||||
defineOverridesPreferences,
|
||||
definePreferencesExtension,
|
||||
} from '@vben/preferences';
|
||||
|
||||
interface WebAntdPreferencesExtension {
|
||||
defaultTableSize: number;
|
||||
enableFormFullscreen: boolean;
|
||||
reportTitle: string;
|
||||
tenantMode: 'multi' | 'single';
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 项目配置文件
|
||||
@@ -23,3 +33,52 @@ export const overridesPreferences = defineOverridesPreferences({
|
||||
companySiteLink: 'https://gitee.com/yudaocode/yudao-ui-admin-vben',
|
||||
},
|
||||
});
|
||||
|
||||
export const preferencesExtension =
|
||||
definePreferencesExtension<WebAntdPreferencesExtension>({
|
||||
tabLabel: 'preferences.antd.tabLabel',
|
||||
title: 'preferences.antd.title',
|
||||
fields: [
|
||||
{
|
||||
component: 'switch',
|
||||
defaultValue: true,
|
||||
key: 'enableFormFullscreen',
|
||||
label: 'preferences.antd.fields.enableFormFullscreen.label',
|
||||
tip: 'preferences.antd.fields.enableFormFullscreen.tip',
|
||||
},
|
||||
{
|
||||
component: 'select',
|
||||
defaultValue: 'single',
|
||||
key: 'tenantMode',
|
||||
label: 'preferences.antd.fields.tenantMode.label',
|
||||
options: [
|
||||
{
|
||||
label: 'preferences.antd.fields.tenantMode.options.single.label',
|
||||
value: 'single',
|
||||
},
|
||||
{
|
||||
label: 'preferences.antd.fields.tenantMode.options.multi.label',
|
||||
value: 'multi',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
component: 'number',
|
||||
componentProps: {
|
||||
max: 200,
|
||||
min: 10,
|
||||
step: 10,
|
||||
},
|
||||
defaultValue: 20,
|
||||
key: 'defaultTableSize',
|
||||
label: 'preferences.antd.fields.defaultTableSize.label',
|
||||
},
|
||||
{
|
||||
component: 'input',
|
||||
defaultValue: '',
|
||||
key: 'reportTitle',
|
||||
label: 'preferences.antd.fields.reportTitle.label',
|
||||
placeholder: 'preferences.antd.fields.reportTitle.placeholder',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
@@ -3,6 +3,9 @@ import type { Recordable } from '@vben/types';
|
||||
export * from './rangePickerProps';
|
||||
export * from './routerHelper';
|
||||
|
||||
// 从共享包导出 URL 工具函数
|
||||
export { isUrl } from '@vben/utils';
|
||||
|
||||
/**
|
||||
* 查找数组对象的某个下标
|
||||
* @param {Array} ary 查找的数组
|
||||
@@ -27,14 +30,3 @@ export const findIndex = <T = Recordable<any>>(
|
||||
});
|
||||
return index;
|
||||
};
|
||||
|
||||
/**
|
||||
* URL 验证
|
||||
* @param path URL 路径
|
||||
*/
|
||||
export const isUrl = (path: string): boolean => {
|
||||
// fix:修复hash路由无法跳转的问题
|
||||
const reg =
|
||||
/(((^https?:(?:\/\/)?)(?:[-:&=+$,\w]+@)?[A-Za-z0-9.-]+(?::\d+)?|(?:www.|[-:&=+$,\w]+@)[A-Za-z0-9.-]+)((?:\/[+~%#/.\w-]*)?\??[-+=&%@.\w]*(?:#\w*)?)?)$/;
|
||||
return reg.test(path);
|
||||
};
|
||||
|
||||
@@ -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: '请输入分类排序',
|
||||
},
|
||||
|
||||
@@ -175,7 +175,7 @@ const resetCustomConfigList = () => {
|
||||
approveType.value =
|
||||
elExtensionElements.value.values?.find(
|
||||
(ex: any) => ex.$type === `${prefix}:ApproveType`,
|
||||
)?.[0] ||
|
||||
) ||
|
||||
bpmnInstances().moddle.create(`${prefix}:ApproveType`, {
|
||||
value: ApproveType.USER,
|
||||
});
|
||||
@@ -184,7 +184,7 @@ const resetCustomConfigList = () => {
|
||||
assignStartUserHandlerTypeEl.value =
|
||||
elExtensionElements.value.values?.find(
|
||||
(ex: any) => ex.$type === `${prefix}:AssignStartUserHandlerType`,
|
||||
)?.[0] ||
|
||||
) ||
|
||||
bpmnInstances().moddle.create(`${prefix}:AssignStartUserHandlerType`, {
|
||||
value: 1,
|
||||
});
|
||||
@@ -194,13 +194,13 @@ const resetCustomConfigList = () => {
|
||||
rejectHandlerTypeEl.value =
|
||||
elExtensionElements.value.values?.find(
|
||||
(ex: any) => ex.$type === `${prefix}:RejectHandlerType`,
|
||||
)?.[0] ||
|
||||
) ||
|
||||
bpmnInstances().moddle.create(`${prefix}:RejectHandlerType`, { value: 1 });
|
||||
rejectHandlerType.value = rejectHandlerTypeEl.value.value;
|
||||
returnNodeIdEl.value =
|
||||
elExtensionElements.value.values?.find(
|
||||
(ex: any) => ex.$type === `${prefix}:RejectReturnTaskId`,
|
||||
)?.[0] ||
|
||||
) ||
|
||||
bpmnInstances().moddle.create(`${prefix}:RejectReturnTaskId`, {
|
||||
value: '',
|
||||
});
|
||||
@@ -210,7 +210,7 @@ const resetCustomConfigList = () => {
|
||||
assignEmptyHandlerTypeEl.value =
|
||||
elExtensionElements.value.values?.find(
|
||||
(ex: any) => ex.$type === `${prefix}:AssignEmptyHandlerType`,
|
||||
)?.[0] ||
|
||||
) ||
|
||||
bpmnInstances().moddle.create(`${prefix}:AssignEmptyHandlerType`, {
|
||||
value: 1,
|
||||
});
|
||||
@@ -218,7 +218,7 @@ const resetCustomConfigList = () => {
|
||||
assignEmptyUserIdsEl.value =
|
||||
elExtensionElements.value.values?.find(
|
||||
(ex: any) => ex.$type === `${prefix}:AssignEmptyUserIds`,
|
||||
)?.[0] ||
|
||||
) ||
|
||||
bpmnInstances().moddle.create(`${prefix}:AssignEmptyUserIds`, {
|
||||
value: '',
|
||||
});
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import BpmnRules from 'bpmn-js/lib/features/rules/BpmnRules';
|
||||
// eslint-disable-next-line n/no-extraneous-import
|
||||
import inherits from 'inherits';
|
||||
|
||||
function CustomRules(eventBus) {
|
||||
|
||||
@@ -767,6 +767,14 @@ export const COMPARISON_OPERATORS: DictDataType[] = [
|
||||
value: '<=',
|
||||
label: '小于等于',
|
||||
},
|
||||
{
|
||||
value: 'contain',
|
||||
label: '包含',
|
||||
},
|
||||
{
|
||||
value: '!contain',
|
||||
label: '不包含',
|
||||
},
|
||||
];
|
||||
// 审批操作按钮名称
|
||||
export const OPERATION_BUTTON_NAME = new Map<number, string>();
|
||||
|
||||
@@ -24,6 +24,7 @@ import { useUserStore } from '@vben/stores';
|
||||
import { isEmpty } from '@vben/utils';
|
||||
|
||||
import FormCreate from '@form-create/ant-design-vue';
|
||||
import { until, useDebounceFn } from '@vueuse/core';
|
||||
import {
|
||||
Alert,
|
||||
Button,
|
||||
@@ -113,6 +114,8 @@ const nextAssigneesActivityNode = ref<BpmProcessInstanceApi.ApprovalNodeInfo[]>(
|
||||
[],
|
||||
); // 下一个审批节点信息
|
||||
const nextAssigneesTimelineRef = ref(); // 下一个节点审批人时间线组件的引用
|
||||
let nextApprovalRequestId = 0; // 请求序号;onChange 高频触发时,丢弃过期请求结果
|
||||
let pendingNextNodesTask: null | Promise<unknown> = null; // 跟踪 onChange 触发的最新一轮重算,提交前需 await 等其完成
|
||||
const approveReasonForm: any = reactive({
|
||||
reason: '',
|
||||
signPicUrl: '',
|
||||
@@ -256,7 +259,6 @@ async function openPopover(type: string) {
|
||||
message.warning('表单校验不通过,请先完善表单!!');
|
||||
return;
|
||||
}
|
||||
await initNextAssigneesFormField();
|
||||
}
|
||||
if (type === 'return') {
|
||||
// 获取退回节点
|
||||
@@ -269,6 +271,18 @@ async function openPopover(type: string) {
|
||||
Object.keys(popOverVisible.value).forEach((item) => {
|
||||
if (popOverVisible.value[item]) popOverVisible.value[item] = item === type;
|
||||
});
|
||||
if (type === 'approve') {
|
||||
// 当前任务有节点表单时,等 form-create 的 fApi 就绪后再计算下一个节点;
|
||||
// 没有节点表单时,approveFormFApi 永远不会被赋值,跳过等待
|
||||
if (runningTask.value?.formId > 0) {
|
||||
// 1s 兜底超时;超时 until 会抛错,这里静默吞掉,让首次计算照常进行
|
||||
await until(() => typeof approveFormFApi.value?.validate === 'function')
|
||||
.toBeTruthy({ timeout: 1000 })
|
||||
.catch(() => {});
|
||||
}
|
||||
// 初始化下一个审批人表单字段
|
||||
await initNextAssigneesFormField();
|
||||
}
|
||||
}
|
||||
|
||||
/** 关闭气泡卡 */
|
||||
@@ -286,6 +300,8 @@ function closePopover(type: string, formRef: any | FormInstance) {
|
||||
|
||||
/** 流程通过时,根据表单变量查询新的流程节点,判断下一个节点类型是否为自选审批人 */
|
||||
async function initNextAssigneesFormField() {
|
||||
// 记录当前请求序号;如果在等待响应期间又有新请求发出,本次结果作废
|
||||
const requestId = ++nextApprovalRequestId;
|
||||
// 获取修改的流程变量, 暂时只支持流程表单
|
||||
const variables = getUpdatedProcessInstanceVariables();
|
||||
const data = await getNextApprovalNodes({
|
||||
@@ -293,6 +309,12 @@ async function initNextAssigneesFormField() {
|
||||
taskId: runningTask.value.id,
|
||||
processVariablesStr: JSON.stringify(variables),
|
||||
});
|
||||
// 已有更新的请求发出,丢弃本次过期结果,避免把旧分支节点回写到当前列表
|
||||
if (requestId !== nextApprovalRequestId) {
|
||||
return;
|
||||
}
|
||||
// 在最新结果到达时再清空,避免请求期间出现节点信息抖动
|
||||
nextAssigneesActivityNode.value = [];
|
||||
if (data && data.length > 0) {
|
||||
const customApproveUsersData: Record<string, any[]> = {}; // 用于收集需要设置到 Timeline 组件的自定义审批人数据
|
||||
data.forEach((node: BpmProcessInstanceApi.ApprovalNodeInfo) => {
|
||||
@@ -327,6 +349,12 @@ async function initNextAssigneesFormField() {
|
||||
}
|
||||
}
|
||||
|
||||
/** onChange 高频触发时合并 300ms 内的连续按键,减少网关查询请求 */
|
||||
const debouncedInitNextAssigneesFormField = useDebounceFn(
|
||||
initNextAssigneesFormField,
|
||||
300,
|
||||
);
|
||||
|
||||
/** 选择下一个节点的审批人 */
|
||||
function selectNextAssigneesConfirm(id: string, userList: any[]) {
|
||||
approveReasonForm.nextAssignees[id] = userList?.map((item: any) => item.id);
|
||||
@@ -362,6 +390,10 @@ async function handleAudit(pass: boolean, formRef: FormInstance | undefined) {
|
||||
}
|
||||
|
||||
if (pass) {
|
||||
// 等待 onChange 触发的最新一轮重算落地,避免拿旧分支节点 + 旧审批人选择 + 新表单变量的错配组合提交
|
||||
if (pendingNextNodesTask) {
|
||||
await pendingNextNodesTask;
|
||||
}
|
||||
const nextAssigneesValid = validateNextAssignees();
|
||||
if (!nextAssigneesValid) return;
|
||||
const variables = getUpdatedProcessInstanceVariables();
|
||||
@@ -376,12 +408,10 @@ async function handleAudit(pass: boolean, formRef: FormInstance | undefined) {
|
||||
if (runningTask.value.signEnable) {
|
||||
data.signPicUrl = approveReasonForm.signPicUrl;
|
||||
}
|
||||
// 多表单处理,并且有额外的 approveForm 表单,需要校验 + 拼接到 data 表单里提交
|
||||
// TODO 芋艿 任务有多表单这里要如何处理,会和可编辑的字段冲突
|
||||
// 多表单处理:节点表单需要校验;变量已经在 getUpdatedProcessInstanceVariables 中合并到 data.variables,无需再覆盖
|
||||
const formCreateApi = approveFormFApi.value;
|
||||
if (Object.keys(formCreateApi)?.length > 0) {
|
||||
await formCreateApi.validate();
|
||||
data.variables = approveForm.value.value;
|
||||
}
|
||||
await approveTask(data);
|
||||
popOverVisible.value.approve = false;
|
||||
@@ -648,18 +678,32 @@ function loadTodoTask(task: any) {
|
||||
approveForm.value = {};
|
||||
runningTask.value = task;
|
||||
approveFormFApi.value = {};
|
||||
// 切换任务时重置请求序号与 pending 重算,避免旧任务飞行中的请求/Promise 串到新任务
|
||||
nextApprovalRequestId += 1;
|
||||
pendingNextNodesTask = null;
|
||||
reasonRequire.value = task?.reasonRequire ?? false;
|
||||
nodeTypeName.value =
|
||||
task?.nodeType === BpmNodeTypeEnum.TRANSACTOR_NODE ? '办理' : '审批';
|
||||
// 处理 approve 表单
|
||||
if (task && task.formId && task.formConf) {
|
||||
const tempApproveForm = {};
|
||||
const tempApproveForm: { option?: any; rule?: any; value?: any } = {};
|
||||
setConfAndFields2(
|
||||
tempApproveForm,
|
||||
task.formConf,
|
||||
task.formFields,
|
||||
task.formVariables,
|
||||
);
|
||||
// 为表单添加 onChange 事件,当表单值变化时,重新计算下一个节点的信息;网关分支可能依赖表单字段
|
||||
tempApproveForm.option.onChange = () => {
|
||||
// 弹窗打开时,才重新计算下一个节点的信息
|
||||
if (!popOverVisible.value.approve) {
|
||||
return;
|
||||
}
|
||||
// useDebounceFn 会把前一次返回的 Promise reject 掉,需 catch 吞掉 'cancelled'
|
||||
pendingNextNodesTask = debouncedInitNextAssigneesFormField().catch(
|
||||
() => {},
|
||||
);
|
||||
};
|
||||
approveForm.value = tempApproveForm;
|
||||
} else {
|
||||
approveForm.value = {}; // 占位,避免为空
|
||||
@@ -684,9 +728,17 @@ async function validateNormalForm() {
|
||||
/** 从可以编辑的流程表单字段,获取需要修改的流程实例的变量 */
|
||||
function getUpdatedProcessInstanceVariables() {
|
||||
const variables: any = {};
|
||||
props.writableFields.forEach((field: string) => {
|
||||
variables[field] = props.normalFormApi.getValue(field);
|
||||
});
|
||||
// 从流程表单(流程定义级别)中获取变量
|
||||
if (props.writableFields?.length && props.normalFormApi) {
|
||||
props.writableFields.forEach((field: string) => {
|
||||
variables[field] = props.normalFormApi.getValue(field);
|
||||
});
|
||||
}
|
||||
// 从节点表单(节点级别)中获取变量;通过 form-create 官方的 formData() 拿当前值
|
||||
const nodeFormData = approveFormFApi.value?.formData?.();
|
||||
if (nodeFormData) {
|
||||
Object.assign(variables, nodeFormData);
|
||||
}
|
||||
return variables;
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
|
||||
@@ -18,7 +18,7 @@ import { schema } from './data';
|
||||
|
||||
const [Form, formApi] = useVbenForm({
|
||||
commonConfig: {
|
||||
labelClass: 'w-100',
|
||||
labelWidth: 120,
|
||||
},
|
||||
layout: 'horizontal',
|
||||
schema,
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
|
||||
@@ -18,7 +18,7 @@ import { schema } from './data';
|
||||
|
||||
const [Form, formApi] = useVbenForm({
|
||||
commonConfig: {
|
||||
labelClass: 'w-100',
|
||||
labelWidth: 120,
|
||||
},
|
||||
layout: 'horizontal',
|
||||
schema,
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
<!-- dataType:array 数组类型 -->
|
||||
<script lang="ts" setup>
|
||||
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 './struct.vue';
|
||||
|
||||
/** 数组元素禁止选择的类型 */
|
||||
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 时,初始化 dataSpecsList 占位 */
|
||||
function handleChange(val: any) {
|
||||
if (val !== IoTDataSpecsDataTypeEnum.STRUCT) {
|
||||
return;
|
||||
}
|
||||
dataSpecs.value.dataSpecsList = [];
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Form.Item
|
||||
:name="['property', 'dataSpecs', 'childDataType']"
|
||||
:rules="ThingModelFormRules.childDataType"
|
||||
label="元素类型"
|
||||
>
|
||||
<Radio.Group v-model:value="dataSpecs.childDataType" @change="handleChange">
|
||||
<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']"
|
||||
:rules="ThingModelFormRules.size"
|
||||
label="元素个数"
|
||||
>
|
||||
<Input
|
||||
v-model:value="dataSpecs.size"
|
||||
placeholder="请输入数组中的元素个数"
|
||||
/>
|
||||
</Form.Item>
|
||||
<!-- Struct 型配置-->
|
||||
<ThingModelStructDataSpecs
|
||||
v-if="dataSpecs.childDataType === IoTDataSpecsDataTypeEnum.STRUCT"
|
||||
v-model="dataSpecs.dataSpecsList"
|
||||
/>
|
||||
</template>
|
||||
@@ -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';
|
||||
@@ -0,0 +1,77 @@
|
||||
<!-- dataType:number 数组类型 -->
|
||||
<script lang="ts" setup>
|
||||
import type { Ref } from 'vue';
|
||||
|
||||
import type { DataSpecsNumberData } from '#/api/iot/thingmodel';
|
||||
|
||||
import { DICT_TYPE } from '@vben/constants';
|
||||
import { getDictOptions } from '@vben/hooks';
|
||||
|
||||
import { useVModel } from '@vueuse/core';
|
||||
import { Form, Input, Select } from 'ant-design-vue';
|
||||
|
||||
const props = defineProps<{ modelValue: any }>();
|
||||
const emits = defineEmits(['update:modelValue']);
|
||||
const dataSpecs = useVModel(
|
||||
props,
|
||||
'modelValue',
|
||||
emits,
|
||||
) as Ref<DataSpecsNumberData>;
|
||||
|
||||
/** 单位下拉变化时,拆出 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>
|
||||
<Form.Item label="取值范围">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex-1">
|
||||
<Input v-model:value="dataSpecs.min" placeholder="请输入最小值" />
|
||||
</div>
|
||||
<span class="mx-2">~</span>
|
||||
<div class="flex-1">
|
||||
<Input v-model:value="dataSpecs.max" placeholder="请输入最大值" />
|
||||
</div>
|
||||
</div>
|
||||
</Form.Item>
|
||||
<Form.Item label="步长">
|
||||
<Input v-model:value="dataSpecs.step" placeholder="请输入步长" />
|
||||
</Form.Item>
|
||||
<Form.Item label="单位">
|
||||
<Select
|
||||
:model-value="
|
||||
dataSpecs.unit ? `${dataSpecs.unitName}-${dataSpecs.unit}` : ''
|
||||
"
|
||||
show-search
|
||||
placeholder="请选择单位"
|
||||
class="w-full"
|
||||
@change="unitChange"
|
||||
>
|
||||
<Select.Option
|
||||
v-for="(item, index) in getDictOptions(
|
||||
DICT_TYPE.IOT_THING_MODEL_UNIT,
|
||||
'string',
|
||||
)"
|
||||
:key="index"
|
||||
:value="`${item.label}-${item.value}`"
|
||||
>
|
||||
{{ `${item.label}-${item.value}` }}
|
||||
</Select.Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep(.ant-form-item) {
|
||||
.ant-form-item {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||