Merge remote-tracking branch 'yudao/master'

pull/366/head
jason 2026-06-19 22:01:57 +08:00
commit d78476ed84
113 changed files with 1585 additions and 1835 deletions

View File

@ -0,0 +1,86 @@
import type { Component } from 'vue';
import type { Recordable } from '@vben/types';
import { defineAsyncComponent, defineComponent, h, ref } from 'vue';
const RawDatePicker = defineAsyncComponent(
() => import('antdv-next/dist/date-picker/index'),
);
const RawRangePicker = defineAsyncComponent(() =>
import('antdv-next/dist/date-picker/index').then(
(res) => res.DateRangePicker,
),
);
const TIMESTAMP_VALUE_FORMATS = new Set(['x', 'X']);
function isTimestampValueFormat(valueFormat: unknown) {
return (
typeof valueFormat === 'string' && TIMESTAMP_VALUE_FORMATS.has(valueFormat)
);
}
function normalizeTimestampPickerValue(value: any, valueFormat: unknown): any {
if (!isTimestampValueFormat(valueFormat)) {
return value;
}
if (Array.isArray(value)) {
return value.map((item) => normalizeTimestampPickerValue(item, valueFormat));
}
return typeof value === 'number' ? String(value) : value;
}
function withTimestampValueFormat(
component: Component,
name: 'DatePicker' | 'RangePicker',
) {
return defineComponent({
name,
inheritAttrs: false,
setup(_, { attrs, expose, slots }) {
const innerRef = ref();
expose(
new Proxy(
{},
{
get: (_target, key) => innerRef.value?.[key],
has: (_target, key) => key in (innerRef.value || {}),
},
),
);
return () => {
const pickerAttrs: Recordable<any> = { ...attrs };
if (
'value-format' in pickerAttrs &&
!Reflect.has(pickerAttrs, 'valueFormat')
) {
pickerAttrs.valueFormat = pickerAttrs['value-format'];
}
const valueFormat = pickerAttrs.valueFormat;
for (const key of [
'value',
'defaultValue',
'pickerValue',
'defaultPickerValue',
]) {
if (Reflect.has(pickerAttrs, key)) {
pickerAttrs[key] = normalizeTimestampPickerValue(
pickerAttrs[key],
valueFormat,
);
}
}
return h(component, { ...pickerAttrs, ref: innerRef }, slots);
};
},
});
}
const DatePicker = withTimestampValueFormat(RawDatePicker, 'DatePicker');
const RangePicker = withTimestampValueFormat(RawRangePicker, 'RangePicker');
export { DatePicker, RangePicker };

View File

@ -75,6 +75,9 @@ import { message, Modal, notification } from 'antdv-next';
import { uploadFile as uploadFileApi } from '#/api/infra/file'; import { uploadFile as uploadFileApi } from '#/api/infra/file';
import { Tinymce as RichTextarea } from '#/components/tinymce'; import { Tinymce as RichTextarea } from '#/components/tinymce';
import { FileUpload, ImageUpload } from '#/components/upload'; import { FileUpload, ImageUpload } from '#/components/upload';
import { DatePicker, RangePicker } from './date-picker';
type AdapterUploadProps = UploadProps & { type AdapterUploadProps = UploadProps & {
aspectRatio?: string; aspectRatio?: string;
crop?: boolean; crop?: boolean;
@ -97,9 +100,6 @@ const Checkbox = defineAsyncComponent(
const CheckboxGroup = defineAsyncComponent(() => const CheckboxGroup = defineAsyncComponent(() =>
import('antdv-next/dist/checkbox/index').then((res) => res.CheckboxGroup), import('antdv-next/dist/checkbox/index').then((res) => res.CheckboxGroup),
); );
const DatePicker = defineAsyncComponent(
() => import('antdv-next/dist/date-picker/index'),
);
const Divider = defineAsyncComponent( const Divider = defineAsyncComponent(
() => import('antdv-next/dist/divider/index'), () => import('antdv-next/dist/divider/index'),
); );
@ -117,11 +117,6 @@ const Radio = defineAsyncComponent(() => import('antdv-next/dist/radio/index'));
const RadioGroup = defineAsyncComponent(() => const RadioGroup = defineAsyncComponent(() =>
import('antdv-next/dist/radio/index').then((res) => res.RadioGroup), import('antdv-next/dist/radio/index').then((res) => res.RadioGroup),
); );
const RangePicker = defineAsyncComponent(() =>
import('antdv-next/dist/date-picker/index').then(
(res) => res.DateRangePicker,
),
);
const Rate = defineAsyncComponent(() => import('antdv-next/dist/rate/index')); const Rate = defineAsyncComponent(() => import('antdv-next/dist/rate/index'));
const Select = defineAsyncComponent( const Select = defineAsyncComponent(
() => import('antdv-next/dist/select/index'), () => import('antdv-next/dist/select/index'),
@ -794,4 +789,4 @@ async function initComponentAdapter() {
}); });
} }
export { initComponentAdapter }; export { DatePicker, initComponentAdapter, RangePicker };

View File

@ -83,6 +83,7 @@ export namespace BpmProcessInstanceApi {
reason: string; reason: string;
signPicUrl: string; signPicUrl: string;
status: number; status: number;
attachments?: string[];
} }
/** 抄送流程实例 */ /** 抄送流程实例 */

View File

@ -1,4 +1,4 @@
import type { Rule } from 'antdv-next/es/form'; import type { FormItemProps } from 'antdv-next';
import type { PageParam, PageResult } from '@vben/request'; import type { PageParam, PageResult } from '@vben/request';
@ -6,6 +6,8 @@ import { isEmpty } from '@vben/utils';
import { requestClient } from '#/api/request'; import { requestClient } from '#/api/request';
type FormItemRule = NonNullable<FormItemProps['rules']>[number];
export namespace ThingModelApi { export namespace ThingModelApi {
/** IoT 物模型数据 */ /** IoT 物模型数据 */
export interface ThingModel { export interface ThingModel {
@ -95,46 +97,42 @@ export namespace ThingModelApi {
/** 生成「必填 + 数字」类校验器:拼到 size / length / 枚举值上 */ /** 生成「必填 + 数字」类校验器:拼到 size / length / 枚举值上 */
function buildRequiredNumberValidator(label: string) { function buildRequiredNumberValidator(label: string) {
return (_rule: any, value: any, callback: any) => { return (_rule: any, value: any) => {
if (isEmpty(value)) { if (isEmpty(value)) {
callback(new Error(`${label}不能为空`)); return Promise.reject(new Error(`${label}不能为空`));
return;
} }
if (Number.isNaN(Number(value))) { if (Number.isNaN(Number(value))) {
callback(new Error(`${label}必须是数字`)); return Promise.reject(new Error(`${label}必须是数字`));
return;
} }
callback(); return Promise.resolve();
}; };
} }
/** 生成「标识符样式」名称校验器:开头需为中文 / 英文 / 数字,整体仅允许中文、英文、数字、下划线、短划线,长度 ≤ 20 */ /** 生成「标识符样式」名称校验器:开头需为中文 / 英文 / 数字,整体仅允许中文、英文、数字、下划线、短划线,长度 ≤ 20 */
export function buildIdentifierLikeNameValidator(label: string) { export function buildIdentifierLikeNameValidator(label: string) {
return (_rule: any, value: string, callback: any) => { return (_rule: any, value: string) => {
if (isEmpty(value)) { if (isEmpty(value)) {
callback(new Error(`${label}不能为空`)); return Promise.reject(new Error(`${label}不能为空`));
return;
} }
if (!/^[一-龥A-Za-z0-9]/.test(value)) { if (!/^[一-龥A-Za-z0-9]/.test(value)) {
callback(new Error(`${label}必须以中文、英文字母或数字开头`)); return Promise.reject(
return; new Error(`${label}必须以中文、英文字母或数字开头`),
);
} }
if (!/^[一-龥A-Za-z0-9][\w一-龥-]*$/.test(value)) { if (!/^[一-龥A-Za-z0-9][\w一-龥-]*$/.test(value)) {
callback( return Promise.reject(
new Error(`${label}只能包含中文、英文字母、数字、下划线和短划线`), new Error(`${label}只能包含中文、英文字母、数字、下划线和短划线`),
); );
return;
} }
if (value.length > 20) { if (value.length > 20) {
callback(new Error(`${label}长度不能超过 20 个字符`)); return Promise.reject(new Error(`${label}长度不能超过 20 个字符`));
return;
} }
callback(); return Promise.resolve();
}; };
} }
/** IoT 物模型表单校验规则 */ /** IoT 物模型表单校验规则 */
export const ThingModelFormRules: Record<string, Rule[]> = { export const ThingModelFormRules: Record<string, FormItemRule[]> = {
name: [ name: [
{ required: true, message: '功能名称不能为空', trigger: 'blur' }, { required: true, message: '功能名称不能为空', trigger: 'blur' },
{ {
@ -153,7 +151,7 @@ export const ThingModelFormRules: Record<string, Rule[]> = {
trigger: 'blur', trigger: 'blur',
}, },
{ {
validator: (_rule: any, value: string, callback: any) => { validator: (_rule: any, value: string) => {
const reservedKeywords = [ const reservedKeywords = [
'set', 'set',
'get', 'get',
@ -164,18 +162,16 @@ export const ThingModelFormRules: Record<string, Rule[]> = {
'value', 'value',
]; ];
if (reservedKeywords.includes(value)) { if (reservedKeywords.includes(value)) {
callback( return Promise.reject(
new Error( new Error(
'set, get, post, property, event, time, value 是系统保留字段,不能用于标识符定义', 'set, get, post, property, event, time, value 是系统保留字段,不能用于标识符定义',
), ),
); );
return;
} }
if (/^\d+$/.test(value)) { if (/^\d+$/.test(value)) {
callback(new Error('标识符不能是纯数字')); return Promise.reject(new Error('标识符不能是纯数字'));
return;
} }
callback(); return Promise.resolve();
}, },
trigger: 'blur', trigger: 'blur',
}, },

View File

@ -8,6 +8,7 @@ import { computed, onMounted, reactive, ref, watch } from 'vue';
import { import {
Button, Button,
Form, Form,
FormItem,
Input, Input,
InputNumber, InputNumber,
message, message,
@ -417,22 +418,21 @@ function inputChange() {
@input="inputChange" @input="inputChange"
> >
<template #addonAfter> <template #addonAfter>
<Select v-model:value="select" placeholder="生成器" class="w-36"> <Select
<SelectOption value="0 * * * * ?">每分钟</SelectOption> v-model:value="select"
<SelectOption value="0 0 * * * ?">每小时</SelectOption> placeholder="生成器"
<SelectOption value="0 0 0 * * ?">每天零点</SelectOption> class="w-36"
<SelectOption value="0 0 0 1 * ?">每月一号零点</SelectOption> :options="[
<SelectOption value="0 0 0 L * ?">每月最后一天零点</SelectOption> { label: '每分钟', value: '0 * * * * ?' },
<SelectOption value="0 0 0 ? * 1">每周星期日零点</SelectOption> { label: '每小时', value: '0 0 * * * ?' },
<SelectOption { label: '每天零点', value: '0 0 0 * * ?' },
v-for="(item, index) in shortcuts" { label: '每月一号零点', value: '0 0 0 1 * ?' },
:key="index" { label: '每月最后一天零点', value: '0 0 0 L * ?' },
:value="item.value" { label: '每周星期日零点', value: '0 0 0 ? * 1' },
> ...shortcuts.map((item) => ({ label: item.text, value: item.value })),
{{ item.text }} { label: '自定义', value: 'custom' },
</SelectOption> ]"
<SelectOption value="custom">自定义</SelectOption> />
</Select>
</template> </template>
</Input> </Input>
@ -496,14 +496,10 @@ function inputChange() {
v-model:value="cronValue.second.appoint" v-model:value="cronValue.second.appoint"
mode="multiple" mode="multiple"
style="width: 100%" style="width: 100%"
> :options="
<SelectOption data.second.map((item) => ({ label: item, value: item }))
v-for="(item, index) in data.second" "
:key="index" />
:label="item"
:value="item"
/>
</Select>
</FormItem> </FormItem>
</Form> </Form>
</TabPane> </TabPane>
@ -560,14 +556,10 @@ function inputChange() {
v-model:value="cronValue.minute.appoint" v-model:value="cronValue.minute.appoint"
mode="multiple" mode="multiple"
style="width: 100%" style="width: 100%"
> :options="
<SelectOption data.minute.map((item) => ({ label: item, value: item }))
v-for="(item, index) in data.minute" "
:key="index" />
:label="item"
:value="item"
/>
</Select>
</FormItem> </FormItem>
</Form> </Form>
</TabPane> </TabPane>
@ -624,14 +616,10 @@ function inputChange() {
v-model:value="cronValue.hour.appoint" v-model:value="cronValue.hour.appoint"
mode="multiple" mode="multiple"
style="width: 100%" style="width: 100%"
> :options="
<SelectOption data.hour.map((item) => ({ label: item, value: item }))
v-for="(item, index) in data.hour" "
:key="index" />
:label="item"
:value="item"
/>
</Select>
</FormItem> </FormItem>
</Form> </Form>
</TabPane> </TabPane>
@ -690,14 +678,10 @@ function inputChange() {
v-model:value="cronValue.day.appoint" v-model:value="cronValue.day.appoint"
mode="multiple" mode="multiple"
style="width: 100%" style="width: 100%"
> :options="
<SelectOption data.day.map((item) => ({ label: item, value: item }))
v-for="(item, index) in data.day" "
:key="index" />
:label="item"
:value="item"
/>
</Select>
</FormItem> </FormItem>
</Form> </Form>
</TabPane> </TabPane>
@ -754,14 +738,10 @@ function inputChange() {
v-model:value="cronValue.month.appoint" v-model:value="cronValue.month.appoint"
mode="multiple" mode="multiple"
style="width: 100%" style="width: 100%"
> :options="
<SelectOption data.month.map((item) => ({ label: item, value: item }))
v-for="(item, index) in data.month" "
:key="index" />
:label="item"
:value="item"
/>
</Select>
</FormItem> </FormItem>
</Form> </Form>
</TabPane> </TabPane>
@ -785,23 +765,15 @@ function inputChange() {
</RadioGroup> </RadioGroup>
</FormItem> </FormItem>
<FormItem v-if="cronValue.week.type === '1'" label="范围"> <FormItem v-if="cronValue.week.type === '1'" label="范围">
<Select v-model:value="cronValue.week.range.start"> <Select
<SelectOption v-model:value="cronValue.week.range.start"
v-for="(item, index) in data.week" :options="data.week"
:key="index" />
:label="item.label"
:value="item.value"
/>
</Select>
<span style="padding: 0 15px">-</span> <span style="padding: 0 15px">-</span>
<Select v-model:value="cronValue.week.range.end"> <Select
<SelectOption v-model:value="cronValue.week.range.end"
v-for="(item, index) in data.week" :options="data.week"
:key="index" />
:label="item.label"
:value="item.value"
/>
</Select>
</FormItem> </FormItem>
<FormItem v-if="cronValue.week.type === '2'" label="间隔"> <FormItem v-if="cronValue.week.type === '2'" label="间隔">
@ -812,14 +784,10 @@ function inputChange() {
controls-position="right" controls-position="right"
/> />
周的星期 周的星期
<Select v-model:value="cronValue.week.loop.end"> <Select
<SelectOption v-model:value="cronValue.week.loop.end"
v-for="(item, index) in data.week" :options="data.week"
:key="index" />
:label="item.label"
:value="item.value"
/>
</Select>
执行一次 执行一次
</FormItem> </FormItem>
<FormItem v-if="cronValue.week.type === '3'" label="指定"> <FormItem v-if="cronValue.week.type === '3'" label="指定">
@ -827,24 +795,14 @@ function inputChange() {
v-model:value="cronValue.week.appoint" v-model:value="cronValue.week.appoint"
mode="multiple" mode="multiple"
style="width: 100%" style="width: 100%"
> :options="data.week"
<SelectOption />
v-for="(item, index) in data.week"
:key="index"
:label="item.label"
:value="item.value"
/>
</Select>
</FormItem> </FormItem>
<FormItem v-if="cronValue.week.type === '4'" label="最后一周"> <FormItem v-if="cronValue.week.type === '4'" label="最后一周">
<Select v-model:value="cronValue.week.last"> <Select
<SelectOption v-model:value="cronValue.week.last"
v-for="(item, index) in data.week" :options="data.week"
:key="index" />
:label="item.label"
:value="item.value"
/>
</Select>
</FormItem> </FormItem>
</Form> </Form>
</TabPane> </TabPane>
@ -895,14 +853,10 @@ function inputChange() {
v-model:value="cronValue.year.appoint" v-model:value="cronValue.year.appoint"
mode="multiple" mode="multiple"
style="width: 100%" style="width: 100%"
> :options="
<SelectOption data.year.map((item) => ({ label: item, value: item }))
v-for="(item, index) in data.year" "
:key="index" />
:label="item"
:value="item"
/>
</Select>
</FormItem> </FormItem>
</Form> </Form>
</TabPane> </TabPane>

View File

@ -9,7 +9,7 @@ import { computed, defineComponent, ref, unref, useAttrs } from 'vue';
import { get, getNestedValue, isFunction } from '@vben/utils'; import { get, getNestedValue, isFunction } from '@vben/utils';
import { Card, Descriptions } from 'antdv-next'; import { Card, Descriptions, DescriptionsItem } from 'antdv-next';
const props = { const props = {
bordered: { default: true, type: Boolean }, bordered: { default: true, type: Boolean },

View File

@ -6,14 +6,7 @@ import { computed, useAttrs } from 'vue';
import { getDictOptions } from '@vben/hooks'; import { getDictOptions } from '@vben/hooks';
import { import { Checkbox, CheckboxGroup, Radio, RadioGroup, Select } from 'antdv-next';
Checkbox,
CheckboxGroup,
Radio,
RadioGroup,
Select,
SelectOption,
} from 'antdv-next';
defineOptions({ name: 'DictSelect' }); defineOptions({ name: 'DictSelect' });
@ -41,18 +34,22 @@ const getDictOption = computed(() => {
} }
} }
}); });
const selectOptions = computed(() =>
getDictOption.value.map((dict) => ({
label: dict.label,
value: dict.value as any,
})),
);
</script> </script>
<template> <template>
<Select v-if="selectType === 'select'" class="w-full" v-bind="attrs"> <Select
<SelectOption v-if="selectType === 'select'"
v-for="(dict, index) in getDictOption" class="w-full"
:key="index" v-bind="attrs"
:value="dict.value" :options="selectOptions"
> />
{{ dict.label }}
</SelectOption>
</Select>
<RadioGroup v-if="selectType === 'radio'" class="w-full" v-bind="attrs"> <RadioGroup v-if="selectType === 'radio'" class="w-full" v-bind="attrs">
<Radio <Radio
v-for="(dict, index) in getDictOption" v-for="(dict, index) in getDictOption"

View File

@ -5,14 +5,7 @@ import { defineComponent, onMounted, ref, useAttrs } from 'vue';
import { useUserStore } from '@vben/stores'; import { useUserStore } from '@vben/stores';
import { isEmpty } from '@vben/utils'; import { isEmpty } from '@vben/utils';
import { import { Checkbox, CheckboxGroup, Radio, RadioGroup, Select } from 'antdv-next';
Checkbox,
CheckboxGroup,
Radio,
RadioGroup,
Select,
SelectOption,
} from 'antdv-next';
import { requestClient } from '#/api/request'; import { requestClient } from '#/api/request';
@ -256,18 +249,11 @@ export function useApiSelect(option: ApiSelectProps) {
onUpdate:value={onUpdateModelValue as any} onUpdate:value={onUpdateModelValue as any}
value={modelValue as any} value={modelValue as any}
{...restAttrs} {...restAttrs}
options={options.value}
// TODO @xingyu remote 对等实现, 还是说没作用 // TODO @xingyu remote 对等实现, 还是说没作用
// remote={props.remote} // remote={props.remote}
{...(props.remote && { remoteMethod })} {...(props.remote && { remoteMethod })}
> />
{options.value.map(
(item: { label: any; value: any }, index: any) => (
<SelectOption key={index} value={item.value}>
{item.label}
</SelectOption>
),
)}
</Select>
); );
} }
return ( return (
@ -277,18 +263,11 @@ export function useApiSelect(option: ApiSelectProps) {
onUpdate:value={onUpdateModelValue as any} onUpdate:value={onUpdateModelValue as any}
value={modelValue as any} value={modelValue as any}
{...restAttrs} {...restAttrs}
options={options.value}
// TODO: @xingyu remote 对等实现, 还是说没作用 // TODO: @xingyu remote 对等实现, 还是说没作用
// remote={props.remote} // remote={props.remote}
{...(props.remote && { remoteMethod })} {...(props.remote && { remoteMethod })}
> />
{options.value.map(
(item: { label: any; value: any }, index: any) => (
<SelectOption key={index} value={item.value}>
{item.label}
</SelectOption>
),
)}
</Select>
); );
}; };
const buildCheckbox = () => { const buildCheckbox = () => {

View File

@ -4,7 +4,7 @@ import { nextTick, reactive, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui'; import { useVbenModal } from '@vben/common-ui';
import { Button, Form, Input, Select, Space } from 'antdv-next'; import { Button, Form, FormItem, Input, Select, Space } from 'antdv-next';
import { loadBaiduMapSdk } from './utils'; import { loadBaiduMapSdk } from './utils';

View File

@ -91,7 +91,6 @@ onMounted(() => {
<DateRangePicker <DateRangePicker
v-model:value="times" v-model:value="times"
:format="rangePickerProps.format" :format="rangePickerProps.format"
:value-format="rangePickerProps.valueFormat"
:placeholder="rangePickerProps.placeholder" :placeholder="rangePickerProps.placeholder"
:presets="rangePickerProps.presets" :presets="rangePickerProps.presets"
class="!w-full !max-w-96" class="!w-full !max-w-96"

View File

@ -188,8 +188,8 @@ watch(
<Dropdown v-if="getDropdownList.length > 0" :trigger="['hover']"> <Dropdown v-if="getDropdownList.length > 0" :trigger="['hover']">
<slot name="more"> <slot name="more">
<Button type="link"> <Button type="link">
{{ $t('page.action.more') }}
<template #icon> <template #icon>
{{ $t('page.action.more') }}
<IconifyIcon icon="lucide:ellipsis-vertical" /> <IconifyIcon icon="lucide:ellipsis-vertical" />
</template> </template>
</Button> </Button>

View File

@ -1,6 +1,5 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { UploadFile, UploadProps } from 'antdv-next'; import type { UploadFile, UploadProps } from 'antdv-next';
import type { UploadRequestOption } from 'antdv-next/lib/vc-upload/interface';
import type { FileUploadProps } from './typing'; import type { FileUploadProps } from './typing';
@ -17,6 +16,8 @@ import { Button, message, Upload } from 'antdv-next';
import { UploadResultStatus } from './typing'; import { UploadResultStatus } from './typing';
import { useUpload, useUploadType } from './use-upload'; import { useUpload, useUploadType } from './use-upload';
type UploadRequestOption = any;
defineOptions({ name: 'FileUpload', inheritAttrs: false }); defineOptions({ name: 'FileUpload', inheritAttrs: false });
const props = withDefaults(defineProps<FileUploadProps>(), { const props = withDefaults(defineProps<FileUploadProps>(), {
@ -34,6 +35,7 @@ const props = withDefaults(defineProps<FileUploadProps>(), {
resultField: '', resultField: '',
returnText: false, returnText: false,
showDescription: false, showDescription: false,
showDownloadIcon: true,
}); });
const emit = defineEmits([ const emit = defineEmits([
'change', 'change',
@ -295,7 +297,7 @@ function getValue() {
:show-upload-list="{ :show-upload-list="{
showPreviewIcon: true, showPreviewIcon: true,
showRemoveIcon: true, showRemoveIcon: true,
showDownloadIcon: true, showDownloadIcon,
}" }"
@remove="handleRemove" @remove="handleRemove"
@preview="handlePreview" @preview="handlePreview"

View File

@ -29,5 +29,6 @@ export interface FileUploadProps {
resultField?: string; // support xxx.xxx.xx resultField?: string; // support xxx.xxx.xx
returnText?: boolean; // 是否返回文件文本内容 returnText?: boolean; // 是否返回文件文本内容
showDescription?: boolean; // 是否显示下面的描述 showDescription?: boolean; // 是否显示下面的描述
showDownloadIcon?: boolean; // 是否显示下载按钮
value?: string | string[]; value?: string | string[];
} }

View File

@ -1,7 +1,16 @@
import type { Dayjs } from 'dayjs';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { $t } from '#/locales'; import { $t } from '#/locales';
type DateRangeTuple = [Dayjs, Dayjs];
type StringRangeTuple = [string, string];
function dateRange(start: Dayjs, end: Dayjs): DateRangeTuple {
return [start, end];
}
/** 时间段选择器拓展 */ /** 时间段选择器拓展 */
export function getRangePickerDefaultProps() { export function getRangePickerDefaultProps() {
return { return {
@ -13,55 +22,55 @@ export function getRangePickerDefaultProps() {
placeholder: [ placeholder: [
$t('utils.rangePicker.beginTime'), $t('utils.rangePicker.beginTime'),
$t('utils.rangePicker.endTime'), $t('utils.rangePicker.endTime'),
], ] as StringRangeTuple,
// 快捷时间范围 // 快捷时间范围
presets: [ presets: [
{ {
label: $t('utils.rangePicker.today'), label: $t('utils.rangePicker.today'),
value: [dayjs().startOf('day'), dayjs().endOf('day')], value: dateRange(dayjs().startOf('day'), dayjs().endOf('day')),
}, },
{ {
label: $t('utils.rangePicker.last7Days'), label: $t('utils.rangePicker.last7Days'),
value: [ value: dateRange(
dayjs().subtract(7, 'day').startOf('day'), dayjs().subtract(7, 'day').startOf('day'),
dayjs().endOf('day'), dayjs().endOf('day'),
], ),
}, },
{ {
label: $t('utils.rangePicker.last30Days'), label: $t('utils.rangePicker.last30Days'),
value: [ value: dateRange(
dayjs().subtract(30, 'day').startOf('day'), dayjs().subtract(30, 'day').startOf('day'),
dayjs().endOf('day'), dayjs().endOf('day'),
], ),
}, },
{ {
label: $t('utils.rangePicker.yesterday'), label: $t('utils.rangePicker.yesterday'),
value: [ value: dateRange(
dayjs().subtract(1, 'day').startOf('day'), dayjs().subtract(1, 'day').startOf('day'),
dayjs().subtract(1, 'day').endOf('day'), dayjs().subtract(1, 'day').endOf('day'),
], ),
}, },
{ {
label: $t('utils.rangePicker.thisWeek'), label: $t('utils.rangePicker.thisWeek'),
value: [dayjs().startOf('week'), dayjs().endOf('day')], value: dateRange(dayjs().startOf('week'), dayjs().endOf('day')),
}, },
{ {
label: $t('utils.rangePicker.thisMonth'), label: $t('utils.rangePicker.thisMonth'),
value: [dayjs().startOf('month'), dayjs().endOf('day')], value: dateRange(dayjs().startOf('month'), dayjs().endOf('day')),
}, },
{ {
label: $t('utils.rangePicker.lastWeek'), label: $t('utils.rangePicker.lastWeek'),
value: [ value: dateRange(
dayjs().subtract(1, 'week').startOf('day'), dayjs().subtract(1, 'week').startOf('day'),
dayjs().endOf('day'), dayjs().endOf('day'),
], ),
}, },
], ],
showTime: { showTime: {
defaultValue: [ defaultValue: dateRange(
dayjs('00:00:00', 'HH:mm:ss'), dayjs('00:00:00', 'HH:mm:ss'),
dayjs('23:59:59', 'HH:mm:ss'), dayjs('23:59:59', 'HH:mm:ss'),
], ),
format: 'HH:mm:ss', format: 'HH:mm:ss',
}, },
}; };

View File

@ -147,15 +147,9 @@ defineExpose({ settingValues });
size="large" size="large"
class="!w-80" class="!w-80"
@change="handlerPlatformChange" @change="handlerPlatformChange"
> :options="OtherPlatformEnum"
<SelectOption :field-names="{ label: 'name', value: 'key' }"
v-for="item in OtherPlatformEnum" />
:key="item.key"
:value="item.key"
>
{{ item.name }}
</SelectOption>
</Select>
</Space> </Space>
</div> </div>
@ -169,15 +163,9 @@ defineExpose({ settingValues });
placeholder="Select" placeholder="Select"
size="large" size="large"
class="!w-80" class="!w-80"
> :options="platformModels"
<SelectOption :field-names="{ label: 'name', value: 'id' }"
v-for="item in platformModels" />
:key="item.id"
:value="item.id"
>
{{ item.name }}
</SelectOption>
</Select>
</Space> </Space>
</div> </div>

View File

@ -214,15 +214,8 @@ defineExpose({ settingValues });
class="!w-80" class="!w-80"
allow-clear allow-clear
placeholder="请选择版本" placeholder="请选择版本"
> :options="versionList"
<SelectOption />
v-for="item in versionList"
:key="item.value"
:value="item.value"
>
{{ item.label }}
</SelectOption>
</Select>
</Space> </Space>
</div> </div>

View File

@ -168,15 +168,9 @@ defineExpose({ settingValues });
placeholder="Select" placeholder="Select"
size="large" size="large"
class="!w-80" class="!w-80"
> :options="StableDiffusionSamplers"
<SelectOption :field-names="{ label: 'name', value: 'key' }"
v-for="item in StableDiffusionSamplers" />
:key="item.key"
:value="item.key"
>
{{ item.name }}
</SelectOption>
</Select>
</Space> </Space>
</div> </div>
@ -189,15 +183,9 @@ defineExpose({ settingValues });
placeholder="Select" placeholder="Select"
size="large" size="large"
class="!w-80" class="!w-80"
> :options="StableDiffusionClipGuidancePresets"
<SelectOption :field-names="{ label: 'name', value: 'key' }"
v-for="item in StableDiffusionClipGuidancePresets" />
:key="item.key"
:value="item.key"
>
{{ item.name }}
</SelectOption>
</Select>
</Space> </Space>
</div> </div>
@ -210,16 +198,9 @@ defineExpose({ settingValues });
placeholder="Select" placeholder="Select"
size="large" size="large"
class="!w-80" class="!w-80"
> :options="StableDiffusionStylePresets"
<SelectOption :field-names="{ label: 'name', value: 'key' }"
v-for="item in StableDiffusionStylePresets" />
:key="item.key"
:label="item.name"
:value="item.key"
>
{{ item.name }}
</SelectOption>
</Select>
</Space> </Space>
</div> </div>

View File

@ -10,6 +10,7 @@ import {
Dropdown, Dropdown,
Empty, Empty,
Form, Form,
FormItem,
InputNumber, InputNumber,
Menu, Menu,
MenuItem, MenuItem,

View File

@ -10,7 +10,7 @@ import { computed, getCurrentInstance, inject, onMounted, ref } from 'vue';
import { IconifyIcon } from '@vben/icons'; import { IconifyIcon } from '@vben/icons';
import { generateAcceptedFileTypes } from '@vben/utils'; import { generateAcceptedFileTypes } from '@vben/utils';
import { Button, Form, message, UploadDragger } from 'antdv-next'; import { Button, Form, FormItem, message, UploadDragger } from 'antdv-next';
import { useUpload } from '#/components/upload/use-upload'; import { useUpload } from '#/components/upload/use-upload';
type UploadRequestOption = any; type UploadRequestOption = any;

View File

@ -47,9 +47,8 @@ defineExpose({
v-model:value="formData.version" v-model:value="formData.version"
class="w-full" class="w-full"
placeholder="请选择" placeholder="请选择"
> :options="[
<SelectOption ...[
v-for="item in [
{ {
value: '3', value: '3',
label: 'V3', label: 'V3',
@ -58,13 +57,9 @@ defineExpose({
value: '2', value: '2',
label: 'V2', label: 'V2',
}, },
]" ].map((item) => ({ label: item.label, value: item.value })),
:key="item.value" ]"
:value="item.value" />
>
{{ item.label }}
</SelectOption>
</Select>
</Title> </Title>
</div> </div>
</template> </template>

View File

@ -3,7 +3,7 @@ import type { Nullable, Recordable } from '@vben/types';
import { ref, unref } from 'vue'; import { ref, unref } from 'vue';
import { Button, Card } from 'antdv-next'; import { Button, Card, RadioButton, RadioGroup } from 'antdv-next';
import desc from './desc.vue'; import desc from './desc.vue';
import lyric from './lyric.vue'; import lyric from './lyric.vue';

View File

@ -80,9 +80,8 @@ defineExpose({
v-model:value="formData.version" v-model:value="formData.version"
class="w-full" class="w-full"
placeholder="请选择" placeholder="请选择"
> :options="[
<SelectOption ...[
v-for="item in [
{ {
value: '3', value: '3',
label: 'V3', label: 'V3',
@ -91,13 +90,9 @@ defineExpose({
value: '2', value: '2',
label: 'V2', label: 'V2',
}, },
]" ].map((item) => ({ label: item.label, value: item.value })),
:key="item.value" ]"
:value="item.value" />
>
{{ item.label }}
</SelectOption>
</Select>
</Title> </Title>
</div> </div>
</template> </template>

View File

@ -4,7 +4,7 @@ import { ref } from 'vue';
import { DICT_TYPE } from '@vben/constants'; import { DICT_TYPE } from '@vben/constants';
import { getDictOptions } from '@vben/hooks'; import { getDictOptions } from '@vben/hooks';
import { Form, Input, Select, TextArea } from 'antdv-next'; import { Form, FormItem, Input, Select, TextArea } from 'antdv-next';
type Rule = any; type Rule = any;
const modelData = defineModel<any>(); // const modelData = defineModel<any>(); //
@ -53,15 +53,13 @@ defineExpose({ validate });
v-model:value="modelData.status" v-model:value="modelData.status"
allow-clear allow-clear
placeholder="请选择状态" placeholder="请选择状态"
> :options="
<SelectOption getDictOptions(DICT_TYPE.COMMON_STATUS, 'number').map((dict) => ({
v-for="dict in getDictOptions(DICT_TYPE.COMMON_STATUS, 'number')" label: dict.label,
:key="dict.value" value: dict.value as any,
:value="dict.value" }))
> "
{{ dict.label }} />
</SelectOption>
</Select>
</FormItem> </FormItem>
<FormItem label="流程描述" name="description" class="mb-5"> <FormItem label="流程描述" name="description" class="mb-5">
<TextArea v-model:value="modelData.description" allow-clear /> <TextArea v-model:value="modelData.description" allow-clear />

View File

@ -1,7 +1,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { Ref } from 'vue'; import type { Ref } from 'vue';
import { inject, ref } from 'vue'; import { computed, inject, ref } from 'vue';
import { useVbenDrawer } from '@vben/common-ui'; import { useVbenDrawer } from '@vben/common-ui';
import { IconifyIcon } from '@vben/icons'; import { IconifyIcon } from '@vben/icons';
@ -20,6 +20,15 @@ const tinyflowRef = ref<InstanceType<typeof Tinyflow> | null>(null);
const workflowData = inject('workflowData') as Ref; const workflowData = inject('workflowData') as Ref;
const params4Test = ref<any[]>([]); const params4Test = ref<any[]>([]);
const paramsOfStartNode = ref<any>({}); const paramsOfStartNode = ref<any>({});
const startNodeParamOptions = computed(() =>
Object.entries(paramsOfStartNode.value).map(
([key, value]: [string, any]) => ({
label: value?.description || key,
value: key,
disabled: !!value?.disabled,
}),
),
);
const testResult = ref(null); const testResult = ref(null);
const loading = ref(false); const loading = ref(false);
const error = ref(null); const error = ref(null);
@ -231,16 +240,12 @@ defineExpose({ validate });
v-for="(param, index) in params4Test" v-for="(param, index) in params4Test"
:key="index" :key="index"
> >
<Select class="w-48" v-model="param.key" placeholder="参数名"> <Select
<SelectOption v-model="param.key"
v-for="(value, key) in paramsOfStartNode" :options="startNodeParamOptions"
:key="key" class="w-48"
:value="key" placeholder="参数名"
:disabled="!!value?.disabled" />
>
{{ value?.description || key }}
</SelectOption>
</Select>
<Input <Input
class="mx-2 w-48" class="mx-2 w-48"
v-model:value="param.value" v-model:value="param.value"

View File

@ -4,7 +4,7 @@ import { nextTick, onBeforeUnmount, onMounted, provide, ref, watch } from 'vue';
import { IconifyIcon } from '@vben/icons'; import { IconifyIcon } from '@vben/icons';
import { cloneDeep } from '@vben/utils'; import { cloneDeep } from '@vben/utils';
import { Collapse } from 'antdv-next'; import { Collapse, CollapsePanel } from 'antdv-next';
import ElementCustomConfig from '#/views/bpm/components/bpmn-process-designer/package/penal/custom-config/ElementCustomConfig.vue'; import ElementCustomConfig from '#/views/bpm/components/bpmn-process-designer/package/penal/custom-config/ElementCustomConfig.vue';
import ElementForm from '#/views/bpm/components/bpmn-process-designer/package/penal/form/ElementForm.vue'; import ElementForm from '#/views/bpm/components/bpmn-process-designer/package/penal/form/ElementForm.vue';
@ -57,8 +57,6 @@ const props = defineProps({
}, // }, //
}); });
const CollapsePanel = CollapsePanel;
const activeTab = ref('base'); const activeTab = ref('base');
const elementId = ref(''); const elementId = ref('');
const elementType = ref<any>(''); const elementType = ref<any>('');

View File

@ -10,7 +10,6 @@ import {
RadioGroup, RadioGroup,
Row, Row,
Select, Select,
SelectOption,
Switch, Switch,
TypographyText, TypographyText,
} from 'antdv-next'; } from 'antdv-next';
@ -277,18 +276,10 @@ watch(
<Col> <Col>
<Select <Select
v-model:value="timeUnit" v-model:value="timeUnit"
:options="TIME_UNIT_TYPES"
class="mr-2 !w-24" class="mr-2 !w-24"
@change="onTimeUnitChange" @change="onTimeUnitChange"
> />
<SelectOption
v-for="item in TIME_UNIT_TYPES"
:key="item.value"
:label="item.label"
:value="item.value"
>
{{ item.label }}
</SelectOption>
</Select>
<TypographyText class="mr-2 mt-2 inline-flex text-sm"> <TypographyText class="mr-2 mt-2 inline-flex text-sm">
未处理 未处理
</TypographyText> </TypographyText>

View File

@ -20,11 +20,11 @@ import { IconifyIcon } from '@vben/icons';
import { import {
Button, Button,
Divider, Divider,
FormItem,
Input, Input,
Radio, Radio,
RadioGroup, RadioGroup,
Select, Select,
SelectOption,
Switch, Switch,
} from 'antdv-next'; } from 'antdv-next';
@ -487,15 +487,9 @@ onMounted(async () => {
style="width: 100%" style="width: 100%"
@change="updateReturnNodeId" @change="updateReturnNodeId"
placeholder="请选择驳回节点" placeholder="请选择驳回节点"
> :options="returnTaskList"
<SelectOption :field-names="{ label: 'name', value: 'id' }"
v-for="item in returnTaskList" />
:key="item.id"
:value="item.id"
>
{{ item.name }}
</SelectOption>
</Select>
</FormItem> </FormItem>
<Divider orientation="left">审批人为空时</Divider> <Divider orientation="left">审批人为空时</Divider>
@ -524,15 +518,9 @@ onMounted(async () => {
mode="multiple" mode="multiple"
style="width: 100%" style="width: 100%"
@change="updateAssignEmptyUserIds" @change="updateAssignEmptyUserIds"
> :options="userOptions"
<SelectOption :field-names="{ label: 'nickname', value: 'id' }"
v-for="item in userOptions" />
:key="item.id"
:value="item.id"
>
{{ item.nickname }}
</SelectOption>
</Select>
</FormItem> </FormItem>
<Divider orientation="left">审批人与提交人为同一人时</Divider> <Divider orientation="left">审批人与提交人为同一人时</Divider>

View File

@ -157,21 +157,28 @@ watch(
:wrapper-col="{ span: 18 }" :wrapper-col="{ span: 18 }"
> >
<FormItem label="流转类型"> <FormItem label="流转类型">
<Select v-model:value="flowConditionForm.type" @change="updateFlowType"> <Select
<SelectOption value="normal">普通流转路径</SelectOption> v-model:value="flowConditionForm.type"
<SelectOption value="default">默认流转路径</SelectOption> @change="updateFlowType"
<SelectOption value="condition">条件流转路径</SelectOption> :options="[
</Select> { label: '普通流转路径', value: 'normal' },
{ label: '默认流转路径', value: 'default' },
{ label: '条件流转路径', value: 'condition' },
]"
/>
</FormItem> </FormItem>
<FormItem <FormItem
label="条件格式" label="条件格式"
v-if="flowConditionForm.type === 'condition'" v-if="flowConditionForm.type === 'condition'"
key="condition" key="condition"
> >
<Select v-model:value="flowConditionForm.conditionType"> <Select
<SelectOption value="expression">表达式</SelectOption> v-model:value="flowConditionForm.conditionType"
<SelectOption value="script">脚本</SelectOption> :options="[
</Select> { label: '表达式', value: 'expression' },
{ label: '脚本', value: 'script' },
]"
/>
</FormItem> </FormItem>
<FormItem <FormItem
label="表达式" label="表达式"
@ -202,10 +209,13 @@ watch(
/> />
</FormItem> </FormItem>
<FormItem label="脚本类型" key="scriptType"> <FormItem label="脚本类型" key="scriptType">
<Select v-model:value="flowConditionForm.scriptType"> <Select
<SelectOption value="inlineScript">内联脚本</SelectOption> v-model:value="flowConditionForm.scriptType"
<SelectOption value="externalScript">外部脚本</SelectOption> :options="[
</Select> { label: '内联脚本', value: 'inlineScript' },
{ label: '外部脚本', value: 'externalScript' },
]"
/>
</FormItem> </FormItem>
<FormItem <FormItem
label="脚本" label="脚本"

View File

@ -319,14 +319,13 @@ watch(
<FormItem label="业务标识"> <FormItem label="业务标识">
<Select <Select
v-model:value="businessKey" v-model:value="businessKey"
:options="[
...fieldList.map((i) => ({ label: i.label, value: i.id })),
{ label: '无', value: '' },
]"
@change="_updateElementBusinessKey" @change="_updateElementBusinessKey"
allow-clear allow-clear
> />
<SelectOption v-for="i in fieldList" :key="i.id" :value="i.id">
{{ i.label }}
</SelectOption>
<SelectOption value=""></SelectOption>
</Select>
</FormItem> </FormItem>
</Form> </Form>
@ -388,7 +387,6 @@ watch(
<!-- allowClear--> <!-- allowClear-->
<!-- @change="changeFieldTypeType"--> <!-- @change="changeFieldTypeType"-->
<!-- >--> <!-- >-->
<!-- <SelectOption v-for="(value, key) of fieldType" :key="key" :value="key">{{ value }}</SelectOption>-->
<!-- </Select>--> <!-- </Select>-->
<!-- </FormItem>--> <!-- </FormItem>-->
<!-- <FormItem label="类型名称" v-if="formFieldForm.typeType === 'custom'">--> <!-- <FormItem label="类型名称" v-if="formFieldForm.typeType === 'custom'">-->

View File

@ -5,15 +5,7 @@ import { confirm, useVbenDrawer, useVbenModal } from '@vben/common-ui';
import { IconifyIcon } from '@vben/icons'; import { IconifyIcon } from '@vben/icons';
import { cloneDeep } from '@vben/utils'; import { cloneDeep } from '@vben/utils';
import { import { Button, Divider, Form, FormItem, Input, Select } from 'antdv-next';
Button,
Divider,
Form,
FormItem,
Input,
Select,
SelectOption,
} from 'antdv-next';
import { useVbenVxeGrid } from '#/adapter/vxe-table'; import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { ProcessListenerSelectModal } from '#/views/bpm/processListener/components'; import { ProcessListenerSelectModal } from '#/views/bpm/processListener/components';
@ -392,10 +384,13 @@ watch(
}, },
]" ]"
> >
<Select v-model:value="listenerForm.event"> <Select
<SelectOption value="start">start</SelectOption> v-model:value="listenerForm.event"
<SelectOption value="end">end</SelectOption> :options="[
</Select> { label: 'start', value: 'start' },
{ label: 'end', value: 'end' },
]"
/>
</FormItem> </FormItem>
<FormItem <FormItem
label="监听器类型" label="监听器类型"
@ -408,15 +403,15 @@ watch(
}, },
]" ]"
> >
<Select v-model:value="listenerForm.listenerType"> <Select
<SelectOption v-model:value="listenerForm.listenerType"
v-for="i in Object.keys(listenerTypeObject)" :options="
:key="i" Object.keys(listenerTypeObject).map((i) => ({
:value="i" label: listenerTypeObject[i as keyof typeof listenerType],
> value: i,
{{ listenerTypeObject[i as keyof typeof listenerType] }} }))
</SelectOption> "
</Select> />
</FormItem> </FormItem>
<FormItem <FormItem
v-if="listenerForm.listenerType === 'classListener'" v-if="listenerForm.listenerType === 'classListener'"
@ -490,10 +485,13 @@ watch(
}, },
]" ]"
> >
<Select v-model:value="listenerForm.scriptType"> <Select
<SelectOption value="inlineScript">内联脚本</SelectOption> v-model:value="listenerForm.scriptType"
<SelectOption value="externalScript">外部脚本</SelectOption> :options="[
</Select> { label: '内联脚本', value: 'inlineScript' },
{ label: '外部脚本', value: 'externalScript' },
]"
/>
</FormItem> </FormItem>
<FormItem <FormItem
v-if="listenerForm.scriptType === 'inlineScript'" v-if="listenerForm.scriptType === 'inlineScript'"

View File

@ -3,7 +3,7 @@ import { ref } from 'vue';
import { useVbenModal } from '@vben/common-ui'; import { useVbenModal } from '@vben/common-ui';
import { Form, FormItem, Input, Select, SelectOption } from 'antdv-next'; import { Form, FormItem, Input, Select } from 'antdv-next';
import { fieldType } from './utilSelf'; import { fieldType } from './utilSelf';
@ -72,15 +72,15 @@ const [Modal, modalApi] = useVbenModal({
}, },
]" ]"
> >
<Select v-model:value="form.fieldType"> <Select
<SelectOption v-model:value="form.fieldType"
v-for="i in Object.keys(fieldTypeObject)" :options="[
:key="i" ...Object.keys(fieldTypeObject).map((i) => ({
:value="i" label: fieldTypeObject[i as keyof typeof fieldType],
> value: i,
{{ fieldTypeObject[i as keyof typeof fieldType] }} })),
</SelectOption> ]"
</Select> />
</FormItem> </FormItem>
<FormItem <FormItem
v-if="form.fieldType === 'string'" v-if="form.fieldType === 'string'"

View File

@ -5,15 +5,7 @@ import { confirm, useVbenDrawer, useVbenModal } from '@vben/common-ui';
import { IconifyIcon } from '@vben/icons'; import { IconifyIcon } from '@vben/icons';
import { cloneDeep } from '@vben/utils'; import { cloneDeep } from '@vben/utils';
import { import { Button, Divider, Form, FormItem, Input, Select } from 'antdv-next';
Button,
Divider,
Form,
FormItem,
Input,
Select,
SelectOption,
} from 'antdv-next';
import { useVbenVxeGrid } from '#/adapter/vxe-table'; import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { ProcessListenerSelectModal } from '#/views/bpm/processListener/components'; import { ProcessListenerSelectModal } from '#/views/bpm/processListener/components';
@ -379,15 +371,15 @@ watch(
name="event" name="event"
:rules="[{ required: true, message: '请选择事件类型' }]" :rules="[{ required: true, message: '请选择事件类型' }]"
> >
<Select v-model:value="listenerForm.event"> <Select
<SelectOption v-model:value="listenerForm.event"
v-for="i in Object.keys(listenerEventTypeObject)" :options="[
:key="i" ...Object.keys(listenerEventTypeObject).map((i) => ({
:value="i" label: listenerEventTypeObject[i as keyof typeof eventType],
> value: i,
{{ listenerEventTypeObject[i as keyof typeof eventType] }} })),
</SelectOption> ]"
</Select> />
</FormItem> </FormItem>
<FormItem <FormItem
label="监听器ID" label="监听器ID"
@ -401,15 +393,15 @@ watch(
name="listenerType" name="listenerType"
:rules="[{ required: true, message: '请选择监听器类型' }]" :rules="[{ required: true, message: '请选择监听器类型' }]"
> >
<Select v-model:value="listenerForm.listenerType"> <Select
<SelectOption v-model:value="listenerForm.listenerType"
v-for="i in Object.keys(listenerTypeObject)" :options="[
:key="i" ...Object.keys(listenerTypeObject).map((i) => ({
:value="i" label: listenerTypeObject[i as keyof typeof listenerType],
> value: i,
{{ listenerTypeObject[i as keyof typeof listenerType] }} })),
</SelectOption> ]"
</Select> />
</FormItem> </FormItem>
<FormItem <FormItem
v-if="listenerForm.listenerType === 'classListener'" v-if="listenerForm.listenerType === 'classListener'"
@ -456,10 +448,13 @@ watch(
key="listener-script-type" key="listener-script-type"
:rules="[{ required: true, message: '请选择脚本类型' }]" :rules="[{ required: true, message: '请选择脚本类型' }]"
> >
<Select v-model:value="listenerForm.scriptType"> <Select
<SelectOption value="inlineScript">内联脚本</SelectOption> v-model:value="listenerForm.scriptType"
<SelectOption value="externalScript">外部脚本</SelectOption> :options="[
</Select> { label: '内联脚本', value: 'inlineScript' },
{ label: '外部脚本', value: 'externalScript' },
]"
/>
</FormItem> </FormItem>
<FormItem <FormItem
v-if="listenerForm.scriptType === 'inlineScript'" v-if="listenerForm.scriptType === 'inlineScript'"
@ -487,12 +482,15 @@ watch(
name="eventDefinitionType" name="eventDefinitionType"
key="eventDefinitionType" key="eventDefinitionType"
> >
<Select v-model:value="listenerForm.eventDefinitionType"> <Select
<SelectOption value="date">日期</SelectOption> v-model:value="listenerForm.eventDefinitionType"
<SelectOption value="duration">持续时长</SelectOption> :options="[
<SelectOption value="cycle">循环</SelectOption> { label: '日期', value: 'date' },
<SelectOption value="null"></SelectOption> { label: '持续时长', value: 'duration' },
</Select> { label: '循环', value: 'cycle' },
{ label: '无', value: 'null' },
]"
/>
</FormItem> </FormItem>
<FormItem <FormItem
v-if=" v-if="

View File

@ -447,15 +447,12 @@ watch(
<Select <Select
v-model:value="loopCharacteristics" v-model:value="loopCharacteristics"
@change="changeLoopCharacteristicsType" @change="changeLoopCharacteristicsType"
> :options="[
<SelectOption value="ParallelMultiInstance"> { label: '并行多重事件', value: 'ParallelMultiInstance' },
并行多重事件 { label: '时序多重事件', value: 'SequentialMultiInstance' },
</SelectOption> { label: '无', value: 'Null' },
<SelectOption value="SequentialMultiInstance"> ]"
时序多重事件 />
</SelectOption>
<SelectOption value="Null"></SelectOption>
</Select>
</FormItem> </FormItem>
<template <template
v-if=" v-if="

View File

@ -11,7 +11,6 @@ import {
FormItem, FormItem,
Input, Input,
Select, Select,
SelectOption,
Switch, Switch,
} from 'antdv-next'; } from 'antdv-next';
@ -269,16 +268,9 @@ onMounted(async () => {
placeholder="请选择子流程" placeholder="请选择子流程"
allow-clear allow-clear
@change="handleChildProcessChange" @change="handleChildProcessChange"
> :options="childProcessOptions"
<SelectOption :field-names="{ label: 'name', value: 'key' }"
v-for="item in childProcessOptions" />
:key="item.key"
:value="item.key"
:label="item.name"
>
{{ item.name }}
</SelectOption>
</Select>
</FormItem> </FormItem>
<FormItem label="继承变量"> <FormItem label="继承变量">

View File

@ -4,7 +4,7 @@ import { nextTick, onBeforeUnmount, onMounted, ref, toRaw, watch } from 'vue';
import { useVbenModal } from '@vben/common-ui'; import { useVbenModal } from '@vben/common-ui';
import { IconifyIcon } from '@vben/icons'; import { IconifyIcon } from '@vben/icons';
import { Button, message, Select, SelectOption } from 'antdv-next'; import { Button, message, Select } from 'antdv-next';
import SignalMessageModal from '../../signal-message/SignalMessageModal.vue'; import SignalMessageModal from '../../signal-message/SignalMessageModal.vue';
@ -118,17 +118,15 @@ watch(
<span class="w-20 text-foreground">消息实例:</span> <span class="w-20 text-foreground">消息实例:</span>
<Select <Select
v-model:value="bindMessageId" v-model:value="bindMessageId"
:options="
Object.keys(messageMap).map((key) => ({
label: messageMap[key],
value: key,
}))
"
class="w-full" class="w-full"
@change="(value: any) => updateTaskMessage(value)" @change="(value: any) => updateTaskMessage(value)"
> />
<SelectOption
v-for="key in Object.keys(messageMap)"
:key="key"
:value="key"
>
{{ messageMap[key] }}
</SelectOption>
</Select>
</div> </div>
<Modal @confirm="handleConfirm" /> <Modal @confirm="handleConfirm" />
</div> </div>

View File

@ -1,14 +1,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { nextTick, onBeforeUnmount, ref, toRaw, watch } from 'vue'; import { nextTick, onBeforeUnmount, ref, toRaw, watch } from 'vue';
import { import { Form, FormItem, Input, Select, TextArea } from 'antdv-next';
Form,
FormItem,
Input,
Select,
SelectOption,
TextArea,
} from 'antdv-next';
defineOptions({ name: 'ScriptTask' }); defineOptions({ name: 'ScriptTask' });
const props = defineProps({ const props = defineProps({
@ -86,10 +79,13 @@ watch(
</FormItem> </FormItem>
<!-- TODO scriptType 外部资源 内联脚本 flowable 文档 https://www.flowable.com/open-source/docs/bpmn/ch07b-BPMN-Constructs#script-task --> <!-- TODO scriptType 外部资源 内联脚本 flowable 文档 https://www.flowable.com/open-source/docs/bpmn/ch07b-BPMN-Constructs#script-task -->
<FormItem label="脚本类型"> <FormItem label="脚本类型">
<Select v-model:value="scriptTaskForm.scriptType"> <Select
<SelectOption value="inline">内联脚本</SelectOption> v-model:value="scriptTaskForm.scriptType"
<SelectOption value="external">外部资源</SelectOption> :options="[
</Select> { label: '内联脚本', value: 'inline' },
{ label: '外部资源', value: 'external' },
]"
/>
</FormItem> </FormItem>
<FormItem label="脚本" v-show="scriptTaskForm.scriptType === 'inline'"> <FormItem label="脚本" v-show="scriptTaskForm.scriptType === 'inline'">
<TextArea <TextArea

View File

@ -26,7 +26,6 @@ import {
Form, Form,
FormItem, FormItem,
Select, Select,
SelectOption,
TextArea, TextArea,
TreeSelect, TreeSelect,
} from 'antdv-next'; } from 'antdv-next';
@ -356,15 +355,8 @@ onBeforeUnmount(() => {
allow-clear allow-clear
style="width: 100%" style="width: 100%"
@change="changeCandidateStrategy" @change="changeCandidateStrategy"
> :options="CANDIDATE_STRATEGY"
<SelectOption />
v-for="(dict, index) in CANDIDATE_STRATEGY"
:key="index"
:value="dict.value"
>
{{ dict.label }}
</SelectOption>
</Select>
</FormItem> </FormItem>
<FormItem <FormItem
v-if="userTaskForm.candidateStrategy === CandidateStrategy.ROLE" v-if="userTaskForm.candidateStrategy === CandidateStrategy.ROLE"
@ -377,15 +369,9 @@ onBeforeUnmount(() => {
mode="multiple" mode="multiple"
style="width: 100%" style="width: 100%"
@change="updateElementTask" @change="updateElementTask"
> :options="roleOptions"
<SelectOption :field-names="{ label: 'name', value: 'id' }"
v-for="item in roleOptions" />
:key="item.id"
:value="item.id"
>
{{ item.name }}
</SelectOption>
</Select>
</FormItem> </FormItem>
<FormItem <FormItem
v-if=" v-if="
@ -419,15 +405,9 @@ onBeforeUnmount(() => {
mode="multiple" mode="multiple"
style="width: 100%" style="width: 100%"
@change="updateElementTask" @change="updateElementTask"
> :options="postOptions"
<SelectOption :field-names="{ label: 'name', value: 'id' }"
v-for="item in postOptions" />
:key="item.id"
:value="item.id"
>
{{ item.name }}
</SelectOption>
</Select>
</FormItem> </FormItem>
<FormItem <FormItem
v-if="userTaskForm.candidateStrategy === CandidateStrategy.USER" v-if="userTaskForm.candidateStrategy === CandidateStrategy.USER"
@ -440,15 +420,9 @@ onBeforeUnmount(() => {
mode="multiple" mode="multiple"
style="width: 100%" style="width: 100%"
@change="updateElementTask" @change="updateElementTask"
> :options="userOptions"
<SelectOption :field-names="{ label: 'nickname', value: 'id' }"
v-for="item in userOptions" />
:key="item.id"
:value="item.id"
>
{{ item.nickname }}
</SelectOption>
</Select>
</FormItem> </FormItem>
<FormItem <FormItem
v-if="userTaskForm.candidateStrategy === CandidateStrategy.USER_GROUP" v-if="userTaskForm.candidateStrategy === CandidateStrategy.USER_GROUP"
@ -461,15 +435,9 @@ onBeforeUnmount(() => {
mode="multiple" mode="multiple"
style="width: 100%" style="width: 100%"
@change="updateElementTask" @change="updateElementTask"
> :options="userGroupOptions"
<SelectOption :field-names="{ label: 'name', value: 'id' }"
v-for="item in userGroupOptions" />
:key="item.id"
:value="item.id"
>
{{ item.name }}
</SelectOption>
</Select>
</FormItem> </FormItem>
<FormItem <FormItem
v-if="userTaskForm.candidateStrategy === CandidateStrategy.FORM_USER" v-if="userTaskForm.candidateStrategy === CandidateStrategy.FORM_USER"
@ -478,19 +446,17 @@ onBeforeUnmount(() => {
> >
<Select <Select
v-model:value="userTaskForm.candidateParam" v-model:value="userTaskForm.candidateParam"
:options="
userFieldOnFormOptions.map((item) => ({
label: item.title,
value: item.field,
disabled: !item.required,
}))
"
allow-clear allow-clear
style="width: 100%" style="width: 100%"
@change="handleFormUserChange" @change="handleFormUserChange"
> />
<SelectOption
v-for="(item, idx) in userFieldOnFormOptions"
:key="idx"
:value="item.field"
:disabled="!item.required"
>
{{ item.title }}
</SelectOption>
</Select>
</FormItem> </FormItem>
<FormItem <FormItem
v-if=" v-if="
@ -504,16 +470,14 @@ onBeforeUnmount(() => {
allow-clear allow-clear
style="width: 100%" style="width: 100%"
@change="updateElementTask" @change="updateElementTask"
> :options="[
<SelectOption ...deptFieldOnFormOptions.map((item) => ({
v-for="(item, idx) in deptFieldOnFormOptions" label: item.title,
:key="idx" value: item.field,
:value="item.field" disabled: !item.required,
:disabled="!item.required" })),
> ]"
{{ item.title }} />
</SelectOption>
</Select>
</FormItem> </FormItem>
<FormItem <FormItem
v-if=" v-if="
@ -528,15 +492,12 @@ onBeforeUnmount(() => {
:label="deptLevelLabel!" :label="deptLevelLabel!"
name="deptLevel" name="deptLevel"
> >
<Select v-model:value="deptLevel" allow-clear @change="updateElementTask"> <Select
<SelectOption v-model:value="deptLevel"
v-for="(item, index) in MULTI_LEVEL_DEPT" allow-clear
:key="index" @change="updateElementTask"
:value="item.value" :options="MULTI_LEVEL_DEPT"
> />
{{ item.label }}
</SelectOption>
</Select>
</FormItem> </FormItem>
<FormItem <FormItem
v-if="userTaskForm.candidateStrategy === CandidateStrategy.EXPRESSION" v-if="userTaskForm.candidateStrategy === CandidateStrategy.EXPRESSION"

View File

@ -9,6 +9,7 @@ import {
Input, Input,
InputNumber, InputNumber,
Radio, Radio,
RadioGroup,
TabPane, TabPane,
Tabs, Tabs,
} from 'antdv-next'; } from 'antdv-next';

View File

@ -21,7 +21,6 @@ import {
RadioGroup, RadioGroup,
Row, Row,
Select, Select,
SelectOption,
Switch, Switch,
} from 'antdv-next'; } from 'antdv-next';
@ -432,15 +431,9 @@ onMounted(async () => {
v-model:value="configForm.calledProcessDefinitionKey" v-model:value="configForm.calledProcessDefinitionKey"
allow-clear allow-clear
@change="handleCalledElementChange" @change="handleCalledElementChange"
> :options="childProcessOptions"
<SelectOption :field-names="{ label: 'name', value: 'key' }"
v-for="(item, index) in childProcessOptions" />
:key="index"
:value="item.key"
>
{{ item.name }}
</SelectOption>
</Select>
</FormItem> </FormItem>
<FormItem <FormItem
label="是否自动跳过子流程发起节点" label="是否自动跳过子流程发起节点"
@ -464,41 +457,39 @@ onMounted(async () => {
<div class="mr-2"> <div class="mr-2">
<FormItem <FormItem
:name="['inVariables', index, 'source']" :name="['inVariables', index, 'source']"
:rules="{ :rules="[
required: true, {
message: '变量不能为空', required: true,
trigger: 'blur', message: '变量不能为空',
}" trigger: 'blur',
},
]"
> >
<Select class="!w-40" v-model:value="item.source"> <Select
<SelectOption class="!w-40"
v-for="(field, fIdx) in formFieldOptions" v-model:value="item.source"
:key="fIdx" :options="formFieldOptions"
:value="field.field" :field-names="{ label: 'title', value: 'field' }"
> />
{{ field.title }}
</SelectOption>
</Select>
</FormItem> </FormItem>
</div> </div>
<div class="mr-2"> <div class="mr-2">
<FormItem <FormItem
:name="['inVariables', index, 'target']" :name="['inVariables', index, 'target']"
:rules="{ :rules="[
required: true, {
message: '变量不能为空', required: true,
trigger: 'blur', message: '变量不能为空',
}" trigger: 'blur',
},
]"
> >
<Select class="!w-40" v-model:value="item.target"> <Select
<SelectOption class="!w-40"
v-for="(field, fIdx) in childFormFieldOptions" v-model:value="item.target"
:key="fIdx" :options="childFormFieldOptions"
:value="field.field" :field-names="{ label: 'title', value: 'field' }"
> />
{{ field.title }}
</SelectOption>
</Select>
</FormItem> </FormItem>
</div> </div>
<div class="mr-1 flex h-8 items-center"> <div class="mr-1 flex h-8 items-center">
@ -534,41 +525,39 @@ onMounted(async () => {
<div class="mr-2"> <div class="mr-2">
<FormItem <FormItem
:name="['outVariables', index, 'source']" :name="['outVariables', index, 'source']"
:rules="{ :rules="[
required: true, {
message: '变量不能为空', required: true,
trigger: 'blur', message: '变量不能为空',
}" trigger: 'blur',
},
]"
> >
<Select class="!w-40" v-model:value="item.source"> <Select
<SelectOption class="!w-40"
v-for="(field, fIdx) in childFormFieldOptions" v-model:value="item.source"
:key="fIdx" :options="childFormFieldOptions"
:value="field.field" :field-names="{ label: 'title', value: 'field' }"
> />
{{ field.title }}
</SelectOption>
</Select>
</FormItem> </FormItem>
</div> </div>
<div class="mr-2"> <div class="mr-2">
<FormItem <FormItem
:name="['outVariables', index, 'target']" :name="['outVariables', index, 'target']"
:rules="{ :rules="[
required: true, {
message: '变量不能为空', required: true,
trigger: 'blur', message: '变量不能为空',
}" trigger: 'blur',
},
]"
> >
<Select class="!w-40" v-model:value="item.target"> <Select
<SelectOption class="!w-40"
v-for="(field, fIdx) in formFieldOptions" v-model:value="item.target"
:key="fIdx" :options="formFieldOptions"
:value="field.field" :field-names="{ label: 'title', value: 'field' }"
> />
{{ field.title }}
</SelectOption>
</Select>
</FormItem> </FormItem>
</div> </div>
<div class="mr-1 flex h-8 items-center"> <div class="mr-1 flex h-8 items-center">
@ -609,16 +598,12 @@ onMounted(async () => {
label="子流程发起人字段" label="子流程发起人字段"
name="startUserFormField" name="startUserFormField"
> >
<Select v-model:value="configForm.startUserFormField" allow-clear> <Select
<SelectOption v-model:value="configForm.startUserFormField"
v-for="(field, fIdx) in startUserFormFieldOptions" allow-clear
:key="fIdx" :options="startUserFormFieldOptions"
:label="field.title" :field-names="{ label: 'title', value: 'field' }"
:value="field.field" />
>
{{ field.title }}
</SelectOption>
</Select>
</FormItem> </FormItem>
<FormItem <FormItem
v-if=" v-if="
@ -682,16 +667,11 @@ onMounted(async () => {
</FormItem> </FormItem>
</Col> </Col>
<Col> <Col>
<Select v-model:value="configForm.timeUnit" class="w-24"> <Select
<SelectOption v-model:value="configForm.timeUnit"
v-for="item in TIME_UNIT_TYPES" class="w-24"
:key="item.value" :options="TIME_UNIT_TYPES"
:label="item.label" />
:value="item.value"
>
{{ item.label }}
</SelectOption>
</Select>
</Col> </Col>
<Col> <Col>
<span class="inline-flex h-8 items-center">后进入下一节点</span> <span class="inline-flex h-8 items-center">后进入下一节点</span>
@ -774,16 +754,8 @@ onMounted(async () => {
<Select <Select
v-model:value="configForm.multiInstanceSourceType" v-model:value="configForm.multiInstanceSourceType"
@change="handleMultiInstanceSourceTypeChange" @change="handleMultiInstanceSourceTypeChange"
> :options="CHILD_PROCESS_MULTI_INSTANCE_SOURCE_TYPE"
<SelectOption />
v-for="item in CHILD_PROCESS_MULTI_INSTANCE_SOURCE_TYPE"
:key="item.value"
:label="item.label"
:value="item.value"
>
{{ item.label }}
</SelectOption>
</Select>
</FormItem> </FormItem>
<FormItem <FormItem
v-if=" v-if="
@ -795,11 +767,13 @@ onMounted(async () => {
label-align="left" label-align="left"
:label-col="{ span: 6 }" :label-col="{ span: 6 }"
:wrapper-col="{ span: 12 }" :wrapper-col="{ span: 12 }"
:rules="{ :rules="[
required: true, {
message: '固定数量不能为空', required: true,
trigger: 'change', message: '固定数量不能为空',
}" trigger: 'change',
},
]"
> >
<InputNumber <InputNumber
v-model:value="configForm.multiInstanceSource" v-model:value="configForm.multiInstanceSource"
@ -816,22 +790,19 @@ onMounted(async () => {
label-align="left" label-align="left"
:label-col="{ span: 6 }" :label-col="{ span: 6 }"
:wrapper-col="{ span: 12 }" :wrapper-col="{ span: 12 }"
:rules="{ :rules="[
required: true, {
message: '数字表单字段不能为空', required: true,
trigger: 'change', message: '数字表单字段不能为空',
}" trigger: 'change',
},
]"
> >
<Select v-model:value="configForm.multiInstanceSource"> <Select
<SelectOption v-model:value="configForm.multiInstanceSource"
v-for="(field, fIdx) in digitalFormFieldOptions" :options="digitalFormFieldOptions"
:key="fIdx" :field-names="{ label: 'title', value: 'field' }"
:label="field.title" />
:value="field.field"
>
{{ field.title }}
</SelectOption>
</Select>
</FormItem> </FormItem>
<FormItem <FormItem
v-if=" v-if="
@ -843,22 +814,19 @@ onMounted(async () => {
label-align="left" label-align="left"
:label-col="{ span: 6 }" :label-col="{ span: 6 }"
:wrapper-col="{ span: 12 }" :wrapper-col="{ span: 12 }"
:rules="{ :rules="[
required: true, {
message: '多选表单字段不能为空', required: true,
trigger: 'change', message: '多选表单字段不能为空',
}" trigger: 'change',
},
]"
> >
<Select v-model:value="configForm.multiInstanceSource"> <Select
<SelectOption v-model:value="configForm.multiInstanceSource"
v-for="(field, fIdx) in multiFormFieldOptions" :options="multiFormFieldOptions"
:key="fIdx" :field-names="{ label: 'title', value: 'field' }"
:label="field.title" />
:value="field.field"
>
{{ field.title }}
</SelectOption>
</Select>
</FormItem> </FormItem>
</div> </div>
</Form> </Form>

View File

@ -19,7 +19,6 @@ import {
RadioGroup, RadioGroup,
Row, Row,
Select, Select,
SelectOption,
TabPane, TabPane,
Tabs, Tabs,
TextArea, TextArea,
@ -259,18 +258,11 @@ defineExpose({ showCopyTaskNodeConfig }); // 暴露方法给父组件
> >
<Select <Select
v-model:value="configForm.roleIds" v-model:value="configForm.roleIds"
clearable allow-clear
mode="multiple" mode="multiple"
> :options="roleOptions"
<SelectOption :field-names="{ label: 'name', value: 'id' }"
v-for="item in roleOptions" />
:key="item.id"
:label="item.name"
:value="item.id"
>
{{ item.name }}
</SelectOption>
</Select>
</FormItem> </FormItem>
<FormItem <FormItem
v-if=" v-if="
@ -306,18 +298,11 @@ defineExpose({ showCopyTaskNodeConfig }); // 暴露方法给父组件
> >
<Select <Select
v-model:value="configForm.postIds" v-model:value="configForm.postIds"
clearable allow-clear
mode="multiple" mode="multiple"
> :options="postOptions"
<SelectOption :field-names="{ label: 'name', value: 'id' }"
v-for="item in postOptions" />
:key="item.id"
:label="item.name"
:value="item.id!"
>
{{ item.name }}
</SelectOption>
</Select>
</FormItem> </FormItem>
<FormItem <FormItem
v-if="configForm.candidateStrategy === CandidateStrategy.USER" v-if="configForm.candidateStrategy === CandidateStrategy.USER"
@ -326,18 +311,11 @@ defineExpose({ showCopyTaskNodeConfig }); // 暴露方法给父组件
> >
<Select <Select
v-model:value="configForm.userIds" v-model:value="configForm.userIds"
clearable allow-clear
mode="multiple" mode="multiple"
> :options="userOptions"
<SelectOption :field-names="{ label: 'nickname', value: 'id' }"
v-for="item in userOptions" />
:key="item.id"
:label="item.nickname"
:value="item.id"
>
{{ item.nickname }}
</SelectOption>
</Select>
</FormItem> </FormItem>
<FormItem <FormItem
v-if=" v-if="
@ -348,18 +326,11 @@ defineExpose({ showCopyTaskNodeConfig }); // 暴露方法给父组件
> >
<Select <Select
v-model:value="configForm.userGroups" v-model:value="configForm.userGroups"
clearable allow-clear
mode="multiple" mode="multiple"
> :options="userGroupOptions"
<SelectOption :field-names="{ label: 'name', value: 'id' }"
v-for="item in userGroupOptions" />
:key="item.id"
:label="item.name"
:value="item.id"
>
{{ item.name }}
</SelectOption>
</Select>
</FormItem> </FormItem>
<FormItem <FormItem
v-if=" v-if="
@ -368,17 +339,17 @@ defineExpose({ showCopyTaskNodeConfig }); // 暴露方法给父组件
label="表单内用户字段" label="表单内用户字段"
name="formUser" name="formUser"
> >
<Select v-model:value="configForm.formUser" clearable> <Select
<SelectOption v-model:value="configForm.formUser"
v-for="(item, idx) in userFieldOnFormOptions" :options="
:key="idx" userFieldOnFormOptions.map((item) => ({
:label="item.title" label: item.title,
:value="item.field" value: item.field,
:disabled="!item.required" disabled: !item.required,
> }))
{{ item.title }} "
</SelectOption> allow-clear
</Select> />
</FormItem> </FormItem>
<FormItem <FormItem
v-if=" v-if="
@ -388,17 +359,17 @@ defineExpose({ showCopyTaskNodeConfig }); // 暴露方法给父组件
label="表单内部门字段" label="表单内部门字段"
name="formDept" name="formDept"
> >
<Select v-model:value="configForm.formDept" clearable> <Select
<SelectOption v-model:value="configForm.formDept"
v-for="(item, idx) in deptFieldOnFormOptions" allow-clear
:key="idx" :options="[
:label="item.title" ...deptFieldOnFormOptions.map((item) => ({
:value="item.field" label: item.title,
:disabled="!item.required" value: item.field,
> disabled: !item.required,
{{ item.title }} })),
</SelectOption> ]"
</Select> />
</FormItem> </FormItem>
<FormItem <FormItem
v-if=" v-if="
@ -414,16 +385,11 @@ defineExpose({ showCopyTaskNodeConfig }); // 暴露方法给父组件
:label="deptLevelLabel!" :label="deptLevelLabel!"
name="deptLevel" name="deptLevel"
> >
<Select v-model:value="configForm.deptLevel" clearable> <Select
<SelectOption v-model:value="configForm.deptLevel"
v-for="(item, index) in MULTI_LEVEL_DEPT" allow-clear
:key="index" :options="MULTI_LEVEL_DEPT"
:label="item.label" />
:value="item.value"
>
{{ item.label }}
</SelectOption>
</Select>
</FormItem> </FormItem>
<FormItem <FormItem
v-if=" v-if="

View File

@ -18,7 +18,6 @@ import {
RadioGroup, RadioGroup,
Row, Row,
Select, Select,
SelectOption,
} from 'antdv-next'; } from 'antdv-next';
import { import {
@ -207,15 +206,11 @@ defineExpose({ openDrawer }); // 暴露方法给父组件
</FormItem> </FormItem>
</Col> </Col>
<Col> <Col>
<Select v-model:value="configForm.timeUnit" class="w-28"> <Select
<SelectOption v-model:value="configForm.timeUnit"
v-for="item in TIME_UNIT_TYPES" class="w-28"
:key="item.value" :options="TIME_UNIT_TYPES"
:value="item.value" />
>
{{ item.label }}
</SelectOption>
</Select>
</Col> </Col>
<Col> <Col>
<span class="inline-flex h-8 items-center">后进入下一节点</span> <span class="inline-flex h-8 items-center">后进入下一节点</span>

View File

@ -17,7 +17,6 @@ import {
RadioGroup, RadioGroup,
Row, Row,
Select, Select,
SelectOption,
Space, Space,
Switch, Switch,
TextArea, TextArea,
@ -69,6 +68,14 @@ const conditionConfigTypes = computed(() => {
/** 条件规则可选择的表单字段 */ /** 条件规则可选择的表单字段 */
const fieldOptions = useFormFieldsAndStartUser(); const fieldOptions = useFormFieldsAndStartUser();
const fieldSelectOptions = computed(() =>
fieldOptions.map((field) => ({
label: field.title,
value: field.field,
disabled: !field.required,
raw: field,
})),
);
// //
const formRules: Record<string, Rule[]> = reactive({ const formRules: Record<string, Rule[]> = reactive({
@ -172,10 +179,12 @@ defineExpose({ validate });
/> />
</div> </div>
</div> </div>
<Space orientation="vertical" size="small" class="w-11/12 pl-1"> <Space
<template #split> orientation="vertical"
{{ condition.conditionGroups.and ? '且' : '或' }} size="small"
</template> class="w-11/12 pl-1"
:separator="condition.conditionGroups.and ? '且' : '或'"
>
<Card <Card
class="group relative w-full hover:border-blue-500" class="group relative w-full hover:border-blue-500"
v-for="(equation, cIdx) in condition.conditionGroups.conditions" v-for="(equation, cIdx) in condition.conditionGroups.conditions"
@ -190,7 +199,10 @@ defineExpose({ validate });
icon="lucide:circle-x" icon="lucide:circle-x"
class="size-4" class="size-4"
@click=" @click="
deleteConditionGroup(condition.conditionGroups.conditions, cIdx) deleteConditionGroup(
condition.conditionGroups.conditions,
Number(cIdx),
)
" "
/> />
</div> </div>
@ -224,47 +236,39 @@ defineExpose({ validate });
rIdx, rIdx,
'leftSide', 'leftSide',
]" ]"
:rules="{ :rules="[
required: true, {
message: '左值不能为空', required: true,
trigger: 'change', message: '左值不能为空',
}" trigger: 'change',
},
]"
> >
<Select <Select
v-model:value="rule.leftSide" v-model:value="rule.leftSide"
allow-clear allow-clear
placeholder="请选择表单字段" placeholder="请选择表单字段"
:options="fieldSelectOptions"
> >
<SelectOption <template #optionRender="{ option }">
v-for="(field, fIdx) in fieldOptions"
:key="fIdx"
:label="field.title"
:value="field.field"
:disabled="!field.required"
>
<Tooltip <Tooltip
title="表单字段非必填时不能作为流程分支条件" title="表单字段非必填时不能作为流程分支条件"
placement="right" placement="right"
v-if="!field.required" v-if="!option.data.raw.required"
> >
<span>{{ field.title }}</span> <span>{{ option.data.raw.title }}</span>
</Tooltip> </Tooltip>
<template v-else>{{ field.title }}</template> <template v-else>{{ option.data.raw.title }}</template>
</SelectOption> </template>
</Select> </Select>
</FormItem> </FormItem>
</Col> </Col>
<Col :span="6"> <Col :span="6">
<Select v-model:value="rule.opCode" placeholder="请选择操作符"> <Select
<SelectOption v-model:value="rule.opCode"
v-for="operator in COMPARISON_OPERATORS" placeholder="请选择操作符"
:key="operator.value" :options="COMPARISON_OPERATORS"
:label="operator.label" />
:value="operator.value"
>
{{ operator.label }}
</SelectOption>
</Select>
</Col> </Col>
<Col :span="7"> <Col :span="7">
<FormItem <FormItem
@ -276,11 +280,13 @@ defineExpose({ validate });
rIdx, rIdx,
'rightSide', 'rightSide',
]" ]"
:rules="{ :rules="[
required: true, {
message: '右值不能为空', required: true,
trigger: ['blur', 'change'], message: '右值不能为空',
}" trigger: ['blur', 'change'],
},
]"
> >
<Input <Input
v-model:value="rule.rightSide" v-model:value="rule.rightSide"
@ -293,11 +299,11 @@ defineExpose({ validate });
<Trash2 <Trash2
v-if="equation.rules.length > 1" v-if="equation.rules.length > 1"
class="mr-2 size-4 cursor-pointer text-red-500" class="mr-2 size-4 cursor-pointer text-red-500"
@click="deleteConditionRule(equation, rIdx)" @click="deleteConditionRule(equation, Number(rIdx))"
/> />
<Plus <Plus
class="size-4 cursor-pointer text-blue-500" class="size-4 cursor-pointer text-blue-500"
@click="addConditionRule(equation, rIdx)" @click="addConditionRule(equation, Number(rIdx))"
/> />
</div> </div>
</Col> </Col>

View File

@ -3,15 +3,7 @@ import type { HttpRequestParam } from '../../../consts';
import { IconifyIcon } from '@vben/icons'; import { IconifyIcon } from '@vben/icons';
import { import { Button, Col, FormItem, Input, Row, Select } from 'antdv-next';
Button,
Col,
FormItem,
Input,
Row,
Select,
SelectOption,
} from 'antdv-next';
import { import {
BPM_HTTP_REQUEST_PARAM_TYPES, BPM_HTTP_REQUEST_PARAM_TYPES,
@ -65,59 +57,59 @@ function deleteHttpRequestParam(arr: HttpRequestParam[], index: number) {
<Col :span="7"> <Col :span="7">
<FormItem <FormItem
:name="[bind, 'header', index, 'key']" :name="[bind, 'header', index, 'key']"
:rules="{ :rules="[
required: true, {
message: '参数名不能为空', required: true,
trigger: ['blur', 'change'], message: '参数名不能为空',
}" trigger: ['blur', 'change'],
},
]"
> >
<Input placeholder="参数名不能为空" v-model:value="item.key" /> <Input placeholder="参数名不能为空" v-model:value="item.key" />
</FormItem> </FormItem>
</Col> </Col>
<Col :span="5"> <Col :span="5">
<Select v-model:value="item.type"> <Select
<SelectOption v-model:value="item.type"
v-for="types in BPM_HTTP_REQUEST_PARAM_TYPES" :options="BPM_HTTP_REQUEST_PARAM_TYPES"
:key="types.value" />
:label="types.label"
:value="types.value"
>
{{ types.label }}
</SelectOption>
</Select>
</Col> </Col>
<Col :span="10"> <Col :span="10">
<FormItem <FormItem
:name="[bind, 'header', index, 'value']" :name="[bind, 'header', index, 'value']"
:rules="{ :rules="[
required: true, {
message: '参数值不能为空', required: true,
trigger: ['blur', 'change'], message: '参数值不能为空',
}" trigger: ['blur', 'change'],
},
]"
v-if="item.type === BpmHttpRequestParamTypeEnum.FIXED_VALUE" v-if="item.type === BpmHttpRequestParamTypeEnum.FIXED_VALUE"
> >
<Input placeholder="请求头" v-model:value="item.value" /> <Input placeholder="请求头" v-model:value="item.value" />
</FormItem> </FormItem>
<FormItem <FormItem
:name="[bind, 'header', index, 'value']" :name="[bind, 'header', index, 'value']"
:rules="{ :rules="[
required: true, {
message: '参数值不能为空', required: true,
trigger: 'change', message: '参数值不能为空',
}" trigger: 'change',
},
]"
v-if="item.type === BpmHttpRequestParamTypeEnum.FROM_FORM" v-if="item.type === BpmHttpRequestParamTypeEnum.FROM_FORM"
> >
<Select v-model:value="item.value" placeholder="请选择表单字段"> <Select
<SelectOption v-model:value="item.value"
v-for="(field, fIdx) in formFieldOptions" :options="
:key="fIdx" formFieldOptions.map((field) => ({
:label="field.title" label: field.title,
:value="field.field" value: field.field,
:disabled="!field.required" disabled: !field.required,
> }))
{{ field.title }} "
</SelectOption> placeholder="请选择表单字段"
</Select> />
</FormItem> </FormItem>
</Col> </Col>
<Col :span="2"> <Col :span="2">
@ -150,59 +142,59 @@ function deleteHttpRequestParam(arr: HttpRequestParam[], index: number) {
<Col :span="7"> <Col :span="7">
<FormItem <FormItem
:name="[bind, 'body', index, 'key']" :name="[bind, 'body', index, 'key']"
:rules="{ :rules="[
required: true, {
message: '参数名不能为空', required: true,
trigger: ['blur', 'change'], message: '参数名不能为空',
}" trigger: ['blur', 'change'],
},
]"
> >
<Input placeholder="参数名" v-model:value="item.key" /> <Input placeholder="参数名" v-model:value="item.key" />
</FormItem> </FormItem>
</Col> </Col>
<Col :span="5"> <Col :span="5">
<Select v-model:value="item.type"> <Select
<SelectOption v-model:value="item.type"
v-for="types in BPM_HTTP_REQUEST_PARAM_TYPES" :options="BPM_HTTP_REQUEST_PARAM_TYPES"
:key="types.value" />
:label="types.label"
:value="types.value"
>
{{ types.label }}
</SelectOption>
</Select>
</Col> </Col>
<Col :span="10"> <Col :span="10">
<FormItem <FormItem
:name="[bind, 'body', index, 'value']" :name="[bind, 'body', index, 'value']"
:rules="{ :rules="[
required: true, {
message: '参数值不能为空', required: true,
trigger: ['blur', 'change'], message: '参数值不能为空',
}" trigger: ['blur', 'change'],
},
]"
v-if="item.type === BpmHttpRequestParamTypeEnum.FIXED_VALUE" v-if="item.type === BpmHttpRequestParamTypeEnum.FIXED_VALUE"
> >
<Input placeholder="参数值" v-model:value="item.value" /> <Input placeholder="参数值" v-model:value="item.value" />
</FormItem> </FormItem>
<FormItem <FormItem
:name="[bind, 'body', index, 'value']" :name="[bind, 'body', index, 'value']"
:rules="{ :rules="[
required: true, {
message: '参数值不能为空', required: true,
trigger: 'change', message: '参数值不能为空',
}" trigger: 'change',
},
]"
v-if="item.type === BpmHttpRequestParamTypeEnum.FROM_FORM" v-if="item.type === BpmHttpRequestParamTypeEnum.FROM_FORM"
> >
<Select v-model:value="item.value" placeholder="请选择表单字段"> <Select
<SelectOption v-model:value="item.value"
v-for="(field, fIdx) in formFieldOptions" :options="
:key="fIdx" formFieldOptions.map((field) => ({
:label="field.title" label: field.title,
:value="field.field" value: field.field,
:disabled="!field.required" disabled: !field.required,
> }))
{{ field.title }} "
</SelectOption> placeholder="请选择表单字段"
</Select> />
</FormItem> </FormItem>
</Col> </Col>
<Col :span="2"> <Col :span="2">

View File

@ -3,16 +3,7 @@ import { toRefs, watch } from 'vue';
import { IconifyIcon } from '@vben/icons'; import { IconifyIcon } from '@vben/icons';
import { import { Alert, Button, Col, FormItem, Input, Row, Select } from 'antdv-next';
Alert,
Button,
Col,
FormItem,
Input,
Row,
Select,
SelectOption,
} from 'antdv-next';
import { useFormFields } from '../../../helpers'; import { useFormFields } from '../../../helpers';
import HttpRequestParamSetting from './http-request-param-setting.vue'; import HttpRequestParamSetting from './http-request-param-setting.vue';
@ -79,11 +70,13 @@ function deleteHttpResponseSetting(
:label-col="{ span: 24 }" :label-col="{ span: 24 }"
:wrapper-col="{ span: 24 }" :wrapper-col="{ span: 24 }"
:name="[formItemPrefix, 'url']" :name="[formItemPrefix, 'url']"
:rules="{ :rules="[
required: true, {
message: '请求地址不能为空', required: true,
trigger: ['blur', 'change'], message: '请求地址不能为空',
}" trigger: ['blur', 'change'],
},
]"
> >
<Input v-model:value="setting.url" placeholder="请输入请求地址" /> <Input v-model:value="setting.url" placeholder="请输入请求地址" />
</FormItem> </FormItem>
@ -117,37 +110,38 @@ function deleteHttpResponseSetting(
<Col :span="10"> <Col :span="10">
<FormItem <FormItem
:name="[formItemPrefix, 'response', index, 'key']" :name="[formItemPrefix, 'response', index, 'key']"
:rules="{ :rules="[
required: true, {
message: '表单字段不能为空', required: true,
trigger: ['blur', 'change'], message: '表单字段不能为空',
}" trigger: ['blur', 'change'],
},
]"
> >
<Select <Select
v-model:value="item.key" v-model:value="item.key"
placeholder="请选择表单字段" placeholder="请选择表单字段"
allow-clear allow-clear
> :options="[
<SelectOption ...formFields.map((field) => ({
v-for="(field, fIdx) in formFields" label: field.title,
:key="fIdx" value: field.field,
:label="field.title" disabled: !field.required,
:value="field.field" })),
:disabled="!field.required" ]"
> />
{{ field.title }}
</SelectOption>
</Select>
</FormItem> </FormItem>
</Col> </Col>
<Col :span="12"> <Col :span="12">
<FormItem <FormItem
:name="[formItemPrefix, 'response', index, 'value']" :name="[formItemPrefix, 'response', index, 'value']"
:rules="{ :rules="[
required: true, {
message: '请求返回字段不能为空', required: true,
trigger: ['blur', 'change'], message: '请求返回字段不能为空',
}" trigger: ['blur', 'change'],
},
]"
> >
<Input v-model:value="item.value" placeholder="请求返回字段" /> <Input v-model:value="item.value" placeholder="请求返回字段" />
</FormItem> </FormItem>
@ -157,7 +151,9 @@ function deleteHttpResponseSetting(
<IconifyIcon <IconifyIcon
class="size-4 cursor-pointer text-red-500" class="size-4 cursor-pointer text-red-500"
icon="lucide:trash-2" icon="lucide:trash-2"
@click="deleteHttpResponseSetting(setting.response!, index)" @click="
deleteHttpResponseSetting(setting.response!, Number(index))
"
/> />
</div> </div>
</Col> </Col>

View File

@ -90,11 +90,13 @@ defineExpose({ validate });
<FormItem <FormItem
label="请求地址" label="请求地址"
:name="`task${listener.type}ListenerPath`" :name="`task${listener.type}ListenerPath`"
:rules="{ :rules="[
required: true, {
message: '请求地址不能为空', required: true,
trigger: ['blur', 'change'], message: '请求地址不能为空',
}" trigger: ['blur', 'change'],
},
]"
> >
<Input <Input
v-model:value="configForm[`task${listener.type}ListenerPath`]" v-model:value="configForm[`task${listener.type}ListenerPath`]"

View File

@ -19,7 +19,6 @@ import {
message, message,
Row, Row,
Select, Select,
SelectOption,
} from 'antdv-next'; } from 'antdv-next';
import { ConditionType } from '../../consts'; import { ConditionType } from '../../consts';
@ -237,25 +236,20 @@ defineExpose({ openDrawer }); // 暴露方法给父组件
<FormItem <FormItem
class="mb-0 ml-4 inline-block w-48" class="mb-0 ml-4 inline-block w-48"
:name="['routerGroups', index, 'nodeId']" :name="['routerGroups', index, 'nodeId']"
:rules="{ :rules="[
required: true, {
message: '路由目标节点不能为空', required: true,
trigger: 'change', message: '路由目标节点不能为空',
}" trigger: 'change',
},
]"
> >
<Select <Select
v-model:value="item.nodeId" v-model:value="item.nodeId"
placeholder="请选择路由目标节点" placeholder="请选择路由目标节点"
allow-clear allow-clear
> :options="nodeOptions"
<SelectOption />
v-for="node in nodeOptions"
:key="node.value"
:value="node.value"
>
{{ node.label }}
</SelectOption>
</Select>
</FormItem> </FormItem>
</div> </div>
<Button <Button

View File

@ -25,7 +25,6 @@ import {
message, message,
Row, Row,
Select, Select,
SelectOption,
Tag, Tag,
} from 'antdv-next'; } from 'antdv-next';
@ -412,16 +411,11 @@ onMounted(() => {
:rules="formRules" :rules="formRules"
> >
<FormItem label="触发器类型" name="type"> <FormItem label="触发器类型" name="type">
<Select v-model:value="configForm.type" @change="changeTriggerType"> <Select
<SelectOption v-model:value="configForm.type"
v-for="(item, index) in TRIGGER_TYPES" @change="changeTriggerType"
:key="index" :options="TRIGGER_TYPES"
:value="item.value" />
:label="item.label"
>
{{ item.label }}
</SelectOption>
</Select>
</FormItem> </FormItem>
<!-- HTTP 请求触发器 --> <!-- HTTP 请求触发器 -->
<div <div
@ -509,17 +503,9 @@ onMounted(() => {
placeholder="请选择表单字段" placeholder="请选择表单字段"
:disabled="key !== ''" :disabled="key !== ''"
allow-clear allow-clear
> :options="optionalUpdateFormFields"
<SelectOption :field-names="{ label: 'title', value: 'field' }"
v-for="(field, fIdx) in optionalUpdateFormFields" />
:key="fIdx"
:label="field.title"
:value="field.field"
:disabled="field.disabled"
>
{{ field.title }}
</SelectOption>
</Select>
</FormItem> </FormItem>
</Col> </Col>
<Col :span="4"> <Col :span="4">
@ -528,11 +514,13 @@ onMounted(() => {
<Col :span="10"> <Col :span="10">
<FormItem <FormItem
:name="['formSettings', index, 'updateFormFields', key]" :name="['formSettings', index, 'updateFormFields', key]"
:rules="{ :rules="[
required: true, {
message: '值不能为空', required: true,
trigger: 'blur', message: '值不能为空',
}" trigger: 'blur',
},
]"
> >
<Input <Input
v-model:value="formSetting.updateFormFields![key]" v-model:value="formSetting.updateFormFields![key]"
@ -651,16 +639,9 @@ onMounted(() => {
mode="multiple" mode="multiple"
placeholder="请选择要删除的字段" placeholder="请选择要删除的字段"
class="w-full" class="w-full"
> :options="formFields"
<SelectOption :field-names="{ label: 'title', value: 'field' }"
v-for="field in formFields" />
:key="field.field"
:label="field.title"
:value="field.field"
>
{{ field.title }}
</SelectOption>
</Select>
</div> </div>
</Card> </Card>
</div> </div>

View File

@ -28,7 +28,6 @@ import {
RadioGroup, RadioGroup,
Row, Row,
Select, Select,
SelectOption,
Switch, Switch,
TabPane, TabPane,
Tabs, Tabs,
@ -148,6 +147,20 @@ const deptFieldOnFormOptions = computed(() => {
return formFieldOptions.filter((item) => item.type === 'DeptSelect'); return formFieldOptions.filter((item) => item.type === 'DeptSelect');
}); });
const userFieldSelectOptions = computed(() =>
userFieldOnFormOptions.value.map((item) => ({
...item,
disabled: !item.required,
})),
);
const deptFieldSelectOptions = computed(() =>
deptFieldOnFormOptions.value.map((item) => ({
...item,
disabled: !item.required,
})),
);
// //
const { const {
buttonsSetting, buttonsSetting,
@ -697,18 +710,11 @@ onMounted(() => {
> >
<Select <Select
v-model:value="configForm.roleIds" v-model:value="configForm.roleIds"
clearable :options="roleOptions"
:field-names="{ label: 'name', value: 'id' }"
allow-clear
mode="multiple" mode="multiple"
> />
<SelectOption
v-for="item in roleOptions"
:key="item.id"
:label="item.name"
:value="item.id"
>
{{ item.name }}
</SelectOption>
</Select>
</FormItem> </FormItem>
<FormItem <FormItem
v-if=" v-if="
@ -744,18 +750,11 @@ onMounted(() => {
> >
<Select <Select
v-model:value="configForm.postIds" v-model:value="configForm.postIds"
clearable :options="postOptions"
:field-names="{ label: 'name', value: 'id' }"
allow-clear
mode="multiple" mode="multiple"
> />
<SelectOption
v-for="item in postOptions"
:key="item.id"
:label="item.name"
:value="item.id!"
>
{{ item.name }}
</SelectOption>
</Select>
</FormItem> </FormItem>
<FormItem <FormItem
v-if="configForm.candidateStrategy === CandidateStrategy.USER" v-if="configForm.candidateStrategy === CandidateStrategy.USER"
@ -764,18 +763,11 @@ onMounted(() => {
> >
<Select <Select
v-model:value="configForm.userIds" v-model:value="configForm.userIds"
clearable :options="userOptions"
:field-names="{ label: 'nickname', value: 'id' }"
allow-clear
mode="multiple" mode="multiple"
> />
<SelectOption
v-for="item in userOptions"
:key="item.id"
:label="item.nickname"
:value="item.id"
>
{{ item.nickname }}
</SelectOption>
</Select>
</FormItem> </FormItem>
<FormItem <FormItem
v-if=" v-if="
@ -786,18 +778,11 @@ onMounted(() => {
> >
<Select <Select
v-model:value="configForm.userGroups" v-model:value="configForm.userGroups"
clearable :options="userGroupOptions"
:field-names="{ label: 'name', value: 'id' }"
allow-clear
mode="multiple" mode="multiple"
> />
<SelectOption
v-for="item in userGroupOptions"
:key="item.id"
:label="item.name"
:value="item.id"
>
{{ item.name }}
</SelectOption>
</Select>
</FormItem> </FormItem>
<FormItem <FormItem
v-if=" v-if="
@ -806,17 +791,12 @@ onMounted(() => {
label="表单内用户字段" label="表单内用户字段"
name="formUser" name="formUser"
> >
<Select v-model:value="configForm.formUser" clearable> <Select
<SelectOption v-model:value="configForm.formUser"
v-for="(item, idx) in userFieldOnFormOptions" :options="userFieldSelectOptions"
:key="idx" :field-names="{ label: 'title', value: 'field' }"
:label="item.title" allow-clear
:value="item.field" />
:disabled="!item.required"
>
{{ item.title }}
</SelectOption>
</Select>
</FormItem> </FormItem>
<FormItem <FormItem
v-if=" v-if="
@ -826,17 +806,12 @@ onMounted(() => {
label="表单内部门字段" label="表单内部门字段"
name="formDept" name="formDept"
> >
<Select v-model:value="configForm.formDept" clearable> <Select
<SelectOption v-model:value="configForm.formDept"
v-for="(item, idx) in deptFieldOnFormOptions" :options="deptFieldSelectOptions"
:key="idx" :field-names="{ label: 'title', value: 'field' }"
:label="item.title" allow-clear
:value="item.field" />
:disabled="!item.required"
>
{{ item.title }}
</SelectOption>
</Select>
</FormItem> </FormItem>
<FormItem <FormItem
v-if=" v-if="
@ -852,16 +827,11 @@ onMounted(() => {
:label="deptLevelLabel!" :label="deptLevelLabel!"
name="deptLevel" name="deptLevel"
> >
<Select v-model:value="configForm.deptLevel" clearable> <Select
<SelectOption v-model:value="configForm.deptLevel"
v-for="(item, index) in MULTI_LEVEL_DEPT" :options="MULTI_LEVEL_DEPT"
:key="index" allow-clear
:label="item.label" />
:value="item.value"
>
{{ item.label }}
</SelectOption>
</Select>
</FormItem> </FormItem>
<FormItem <FormItem
v-if=" v-if="
@ -943,16 +913,12 @@ onMounted(() => {
label="驳回节点" label="驳回节点"
name="returnNodeId" name="returnNodeId"
> >
<Select v-model:value="configForm.returnNodeId" clearable> <Select
<SelectOption v-model:value="configForm.returnNodeId"
v-for="item in returnTaskList" :options="returnTaskList"
:key="item.id" :field-names="{ label: 'name', value: 'id' }"
:label="item.name" allow-clear
:value="item.id" />
>
{{ item.name }}
</SelectOption>
</Select>
</FormItem> </FormItem>
</div> </div>
@ -1021,19 +987,11 @@ onMounted(() => {
<Col> <Col>
<Select <Select
v-model:value="timeUnit" v-model:value="timeUnit"
:options="TIME_UNIT_TYPES"
class="mr-2" class="mr-2"
:style="{ width: '100px' }" :style="{ width: '100px' }"
@change="timeUnitChange" @change="timeUnitChange"
> />
<SelectOption
v-for="item in TIME_UNIT_TYPES"
:key="item.value"
:label="item.label"
:value="item.value"
>
{{ item.label }}
</SelectOption>
</Select>
<TypographyText class="mr-2 mt-2 inline-flex text-sm"> <TypographyText class="mr-2 mt-2 inline-flex text-sm">
未处理 未处理
</TypographyText> </TypographyText>
@ -1087,18 +1045,11 @@ onMounted(() => {
> >
<Select <Select
v-model:value="configForm.assignEmptyHandlerUserIds" v-model:value="configForm.assignEmptyHandlerUserIds"
clearable :options="userOptions"
:field-names="{ label: 'nickname', value: 'id' }"
allow-clear
mode="multiple" mode="multiple"
> />
<SelectOption
v-for="item in userOptions"
:key="item.id"
:label="item.nickname"
:value="item.id"
>
{{ item.nickname }}
</SelectOption>
</Select>
</FormItem> </FormItem>
<div v-if="currentNode.type === BpmNodeTypeEnum.USER_TASK_NODE"> <div v-if="currentNode.type === BpmNodeTypeEnum.USER_TASK_NODE">

View File

@ -18,8 +18,10 @@ import {
Avatar, Avatar,
Button, Button,
Form, Form,
FormItem,
Input, Input,
Radio, Radio,
RadioGroup,
Select, Select,
TextArea, TextArea,
Tooltip, Tooltip,
@ -68,20 +70,18 @@ const rules: Record<string, Rule[]> = {
key: [ key: [
{ required: true, message: '流程标识不能为空', trigger: 'blur' }, { required: true, message: '流程标识不能为空', trigger: 'blur' },
{ {
validator: (_rule: any, value: string, callback: any) => { validator: (_rule: any, value: string) => {
if (!value) { if (!value) {
callback(); return Promise.resolve();
return;
} }
if (!/^[a-z_][-\w.$]*$/i.test(value)) { if (!/^[a-z_][-\w.$]*$/i.test(value)) {
callback( return Promise.reject(
new Error( new Error(
'只能包含字母、数字、下划线、连字符和点号,且必须以字母或下划线开头', '只能包含字母、数字、下划线、连字符和点号,且必须以字母或下划线开头',
), ),
); );
return;
} }
callback(); return Promise.resolve();
}, },
trigger: 'blur', trigger: 'blur',
}, },
@ -281,16 +281,10 @@ defineExpose({ validate });
class="w-full" class="w-full"
v-model:value="modelData.category" v-model:value="modelData.category"
allow-clear allow-clear
:field-names="{ label: 'name', value: 'code' }"
:options="categoryList"
placeholder="请选择流程分类" placeholder="请选择流程分类"
> />
<SelectOption
v-for="category in categoryList"
:key="category.code"
:value="category.code"
>
{{ category.name }}
</SelectOption>
</Select>
</FormItem> </FormItem>
<FormItem label="流程图标"> <FormItem label="流程图标">
<ImageUpload v-model:value="modelData.icon" /> <ImageUpload v-model:value="modelData.icon" />
@ -326,13 +320,14 @@ defineExpose({ validate });
<FormItem label="谁可以发起" name="startUserType"> <FormItem label="谁可以发起" name="startUserType">
<Select <Select
v-model:value="modelData.startUserType" v-model:value="modelData.startUserType"
:options="[
{ label: '全员', value: 0 },
{ label: '指定人员', value: 1 },
{ label: '指定部门', value: 2 },
]"
placeholder="请选择谁可以发起" placeholder="请选择谁可以发起"
@change="handleStartUserTypeChange" @change="handleStartUserTypeChange"
> />
<SelectOption :value="0">全员</SelectOption>
<SelectOption :value="1">指定人员</SelectOption>
<SelectOption :value="2">指定部门</SelectOption>
</Select>
<div <div
v-if="modelData.startUserType === 1" v-if="modelData.startUserType === 1"
class="mt-2 flex flex-wrap gap-1" class="mt-2 flex flex-wrap gap-1"

View File

@ -15,7 +15,6 @@ import {
Radio, Radio,
RadioGroup, RadioGroup,
Select, Select,
SelectOption,
Tooltip, Tooltip,
} from 'antdv-next'; } from 'antdv-next';
@ -107,16 +106,12 @@ defineExpose({ validate });
name="formId" name="formId"
class="mb-5" class="mb-5"
> >
<Select v-model:value="modelData.formId" allow-clear> <Select
<SelectOption v-model:value="modelData.formId"
v-for="form in props.formList" allow-clear
:key="form.id" :options="props.formList"
:value="form.id" :field-names="{ label: 'name', value: 'id' }"
> />
{{ form.name }}
</SelectOption>
>
</Select>
</FormItem> </FormItem>
<FormItem <FormItem
v-if="modelData.formType === BpmModelFormType.CUSTOM" v-if="modelData.formType === BpmModelFormType.CUSTOM"

View File

@ -18,6 +18,7 @@ import {
Button, Button,
Card, Card,
Collapse, Collapse,
CollapsePanel,
Dropdown, Dropdown,
Menu, Menu,
MenuItem, MenuItem,

View File

@ -34,7 +34,6 @@ import {
message, message,
Popover, Popover,
Select, Select,
SelectOption,
Space, Space,
TextArea, TextArea,
} from 'antdv-next'; } from 'antdv-next';
@ -55,6 +54,7 @@ import {
transferTask, transferTask,
} from '#/api/bpm/task'; } from '#/api/bpm/task';
import { setConfAndFields2 } from '#/components/form-create'; import { setConfAndFields2 } from '#/components/form-create';
import { FileUpload } from '#/components/upload';
import { $t } from '#/locales'; import { $t } from '#/locales';
import Signature from './signature.vue'; import Signature from './signature.vue';
@ -120,6 +120,7 @@ const approveReasonForm: any = reactive({
reason: '', reason: '',
signPicUrl: '', signPicUrl: '',
nextAssignees: {}, nextAssignees: {},
attachments: [],
}); });
const approveReasonRule: Record<string, any> = computed(() => { const approveReasonRule: Record<string, any> = computed(() => {
return { return {
@ -140,7 +141,8 @@ const approveReasonRule: Record<string, any> = computed(() => {
}); });
const rejectFormRef = ref<FormInstance>(); const rejectFormRef = ref<FormInstance>();
const rejectReasonForm = reactive({ const rejectReasonForm = reactive<{ attachments: string[]; reason: string }>({
attachments: [],
reason: '', reason: '',
}); // }); //
const rejectReasonRule: any = computed(() => { const rejectReasonRule: any = computed(() => {
@ -290,6 +292,14 @@ function closePopover(type: string, formRef: any | FormInstance) {
if (formRef) { if (formRef) {
formRef.resetFields(); formRef.resetFields();
} }
if (type === 'approve') {
approveReasonForm.reason = '';
approveReasonForm.attachments = [];
approveReasonForm.signPicUrl = '';
} else if (type === 'reject') {
rejectReasonForm.reason = '';
rejectReasonForm.attachments = [];
}
if (popOverVisible.value[type]) popOverVisible.value[type] = false; if (popOverVisible.value[type]) popOverVisible.value[type] = false;
nextAssigneesActivityNode.value = []; nextAssigneesActivityNode.value = [];
// Timeline // Timeline
@ -401,6 +411,7 @@ async function handleAudit(pass: boolean, formRef: FormInstance | undefined) {
const data = { const data = {
id: runningTask.value.id, id: runningTask.value.id,
reason: approveReasonForm.reason, reason: approveReasonForm.reason,
attachments: approveReasonForm.attachments,
variables, // , variables, // ,
nextAssignees: approveReasonForm.nextAssignees, // nextAssignees: approveReasonForm.nextAssignees, //
} as any; } as any;
@ -414,6 +425,9 @@ async function handleAudit(pass: boolean, formRef: FormInstance | undefined) {
await formCreateApi.validate(); await formCreateApi.validate();
} }
await approveTask(data); await approveTask(data);
approveReasonForm.reason = '';
approveReasonForm.attachments = [];
approveReasonForm.signPicUrl = '';
popOverVisible.value.approve = false; popOverVisible.value.approve = false;
nextAssigneesActivityNode.value = []; nextAssigneesActivityNode.value = [];
// Timeline // Timeline
@ -425,9 +439,12 @@ async function handleAudit(pass: boolean, formRef: FormInstance | undefined) {
// //
const data = { const data = {
id: runningTask.value.id, id: runningTask.value.id,
attachments: rejectReasonForm.attachments,
reason: rejectReasonForm.reason, reason: rejectReasonForm.reason,
}; };
await rejectTask(data); await rejectTask(data);
rejectReasonForm.reason = '';
rejectReasonForm.attachments = [];
popOverVisible.value.reject = false; popOverVisible.value.reject = false;
message.success('审批不通过成功'); message.success('审批不通过成功');
} }
@ -748,6 +765,38 @@ function handleSignFinish(url: string) {
approveFormRef.value?.validateFields(['signPicUrl']); approveFormRef.value?.validateFields(['signPicUrl']);
} }
/** 附件图片预览 */
const imagePreviewOpen = ref(false);
const imagePreviewUrl = ref('');
/** 判断文件是否为图片类型 */
function isImageUrl(url: string) {
return /\.(bmp|gif|jpe?g|png|svg|webp)$/i.test(url);
}
/** 处理文件预览 */
function handleFilePreview(file: any) {
if (!file?.url && !file?.response) {
message.warning('文件地址不存在,无法预览');
return;
}
const url = file.url || file?.response?.url || file?.response;
if (!url) {
message.warning('文件地址不存在,无法预览');
return;
}
if (isImageUrl(url)) {
imagePreviewUrl.value = url;
imagePreviewOpen.value = true;
} else {
window.open(url, '_blank');
}
}
function handleImagePreviewOpenChange(open: boolean) {
imagePreviewOpen.value = open;
}
/** 处理弹窗可见性 */ /** 处理弹窗可见性 */
function handlePopoverVisible(visible: boolean) { function handlePopoverVisible(visible: boolean) {
if (!visible) { if (!visible) {
@ -843,6 +892,16 @@ defineExpose({ loadTodoTask });
:rows="4" :rows="4"
/> />
</FormItem> </FormItem>
<FormItem label="上传附件/图片" name="attachments">
<FileUpload
v-model:value="approveReasonForm.attachments"
:max-number="10"
:multiple="true"
:show-download-icon="false"
help-text="支持多文件/图片上传"
@preview="handleFilePreview"
/>
</FormItem>
<FormItem> <FormItem>
<Space> <Space>
<Button <Button
@ -900,6 +959,15 @@ defineExpose({ loadTodoTask });
:rows="4" :rows="4"
/> />
</FormItem> </FormItem>
<FormItem label="上传附件/图片" name="attachments">
<FileUpload
v-model:value="rejectReasonForm.attachments"
:max-number="10"
:multiple="true"
help-text="支持多文件/图片上传"
@preview="handleFilePreview"
/>
</FormItem>
<FormItem> <FormItem>
<Button <Button
:disabled="formLoading" :disabled="formLoading"
@ -956,16 +1024,9 @@ defineExpose({ loadTodoTask });
mode="multiple" mode="multiple"
placeholder="请选择抄送人" placeholder="请选择抄送人"
class="w-full" class="w-full"
> :options="userOptions"
<SelectOption :field-names="{ label: 'nickname', value: 'id' }"
v-for="item in userOptions" />
:key="item.id"
:label="item.nickname"
:value="item.id"
>
{{ item.nickname }}
</SelectOption>
</Select>
</FormItem> </FormItem>
<FormItem label="抄送意见" name="copyReason"> <FormItem label="抄送意见" name="copyReason">
<TextArea <TextArea
@ -1026,16 +1087,9 @@ defineExpose({ loadTodoTask });
v-model:value="transferForm.assigneeUserId" v-model:value="transferForm.assigneeUserId"
:allow-clear="true" :allow-clear="true"
style="width: 100%" style="width: 100%"
> :options="userOptions"
<SelectOption :field-names="{ label: 'nickname', value: 'id' }"
v-for="item in userOptions" />
:key="item.id"
:label="item.nickname"
:value="item.id"
>
{{ item.nickname }}
</SelectOption>
</Select>
</FormItem> </FormItem>
<FormItem label="审批意见" name="reason"> <FormItem label="审批意见" name="reason">
<TextArea <TextArea
@ -1099,16 +1153,9 @@ defineExpose({ loadTodoTask });
v-model:value="delegateForm.delegateUserId" v-model:value="delegateForm.delegateUserId"
:allow-clear="true" :allow-clear="true"
style="width: 100%" style="width: 100%"
> :options="userOptions"
<SelectOption :field-names="{ label: 'nickname', value: 'id' }"
v-for="item in userOptions" />
:key="item.id"
:label="item.nickname"
:value="item.id"
>
{{ item.nickname }}
</SelectOption>
</Select>
</FormItem> </FormItem>
<FormItem label="审批意见" name="reason"> <FormItem label="审批意见" name="reason">
<TextArea <TextArea
@ -1173,16 +1220,9 @@ defineExpose({ loadTodoTask });
:allow-clear="true" :allow-clear="true"
mode="multiple" mode="multiple"
style="width: 100%" style="width: 100%"
> :options="userOptions"
<SelectOption :field-names="{ label: 'nickname', value: 'id' }"
v-for="item in userOptions" />
:key="item.id"
:label="item.nickname"
:value="item.id"
>
{{ item.nickname }}
</SelectOption>
</Select>
</FormItem> </FormItem>
<FormItem label="审批意见" name="reason"> <FormItem label="审批意见" name="reason">
<TextArea <TextArea
@ -1250,18 +1290,15 @@ defineExpose({ loadTodoTask });
<FormItem label="减签人员" name="deleteSignTaskId"> <FormItem label="减签人员" name="deleteSignTaskId">
<Select <Select
v-model:value="deleteSignForm.deleteSignTaskId" v-model:value="deleteSignForm.deleteSignTaskId"
:options="
(runningTask.children as any[]).map((item) => ({
label: getDeleteSignUserLabel(item),
value: item.id,
}))
"
:allow-clear="true" :allow-clear="true"
style="width: 100%" style="width: 100%"
> />
<SelectOption
v-for="item in runningTask.children"
:key="item.id"
:label="getDeleteSignUserLabel(item)"
:value="item.id"
>
{{ getDeleteSignUserLabel(item) }}
</SelectOption>
</Select>
</FormItem> </FormItem>
<FormItem label="审批意见" name="reason"> <FormItem label="审批意见" name="reason">
<TextArea <TextArea
@ -1323,16 +1360,9 @@ defineExpose({ loadTodoTask });
v-model:value="returnForm.targetTaskDefinitionKey" v-model:value="returnForm.targetTaskDefinitionKey"
:allow-clear="true" :allow-clear="true"
style="width: 100%" style="width: 100%"
> :options="returnList"
<SelectOption :field-names="{ label: 'name', value: 'taskDefinitionKey' }"
v-for="item in returnList" />
:key="item.taskDefinitionKey"
:label="item.name"
:value="item.taskDefinitionKey"
>
{{ item.name }}
</SelectOption>
</Select>
</FormItem> </FormItem>
<FormItem label="退回理由" name="returnReason"> <FormItem label="退回理由" name="returnReason">
<TextArea <TextArea
@ -1444,4 +1474,14 @@ defineExpose({ loadTodoTask });
<!-- 签名弹窗 --> <!-- 签名弹窗 -->
<SignatureModal @success="handleSignFinish" /> <SignatureModal @success="handleSignFinish" />
<!-- 图片预览隐藏的 Image 组件仅用于附件预览弹窗 -->
<Image
:preview="{
open: imagePreviewOpen,
onOpenChange: handleImagePreviewOpenChange,
}"
:src="imagePreviewUrl"
style="display: none"
/>
</template> </template>

View File

@ -201,16 +201,25 @@ function shouldShowCustomUserSelect(
); );
} }
/** 判断是否需要显示审批意见 */ /** 判断是否需要显示审批意见和附件 */
function shouldShowApprovalReason(task: any, nodeType: BpmNodeTypeEnum) { function shouldShowReasonAndAttachment(task: any, nodeType: BpmNodeTypeEnum) {
return ( return (
task.reason && (task.reason || task.attachments?.length > 0) &&
[BpmNodeTypeEnum.START_USER_NODE, BpmNodeTypeEnum.USER_TASK_NODE].includes( [BpmNodeTypeEnum.START_USER_NODE, BpmNodeTypeEnum.USER_TASK_NODE].includes(
nodeType, nodeType,
) )
); );
} }
function getAttachmentName(url: string) {
return decodeURIComponent(url.slice(url.lastIndexOf('/') + 1));
}
function isImageAttachment(url: string) {
const ext = url.split('.').pop()?.toLowerCase();
return ['bmp', 'gif', 'jpeg', 'jpg', 'png', 'webp'].includes(ext || '');
}
/** 用户选择弹窗关闭 */ /** 用户选择弹窗关闭 */
function handleUserSelectClosed() { function handleUserSelectClosed() {
selectedUsers.value = []; selectedUsers.value = [];
@ -410,29 +419,74 @@ defineExpose({ setCustomApproveUsers, batchSetCustomApproveUsers });
</div> </div>
</div> </div>
<!-- 审批意见和签名 --> <!-- 审批意见附件和签名 -->
<div <div
v-if="shouldShowApprovalReason(task, activity.nodeType)" v-if="shouldShowReasonAndAttachment(task, activity.nodeType)"
class="mt-1 w-full rounded-md bg-gray-100 p-2 text-sm text-gray-500" class="mt-1 w-full rounded-md bg-gray-100 p-2 text-sm text-gray-500"
> >
审批意见{{ task.reason }} <div v-if="task.reason">{{ task.reason }}</div>
</div>
<div <div
v-if=" v-if="(task.attachments?.length || 0) > 0"
task.signPicUrl && :class="{
activity.nodeType === BpmNodeTypeEnum.USER_TASK_NODE 'mt-2 border-t border-dashed border-gray-300 pt-2':
" task.reason,
class="mt-1 flex w-full items-center rounded-md bg-gray-100 p-2 text-sm text-gray-500" }"
> >
签名 <div class="mb-1 text-xs font-semibold text-gray-400">
<Image 附件列表
class="ml-2" </div>
:width="180" <div class="flex flex-col gap-1.5">
:height="60" <template
:src="task.signPicUrl" v-for="(attachment, attachmentIndex) in task.attachments"
:preview="{ src: task.signPicUrl }" :key="attachmentIndex"
/> >
<div class="flex items-center gap-2">
<IconifyIcon
:icon="
isImageAttachment(attachment)
? 'lucide:image'
: 'lucide:file-text'
"
class="text-gray-400"
/>
<Image
v-if="isImageAttachment(attachment)"
:width="32"
:height="32"
class="rounded border border-solid border-gray-200 object-cover"
:src="attachment"
:preview="{ src: attachment }"
/>
<a
v-else
:href="attachment"
target="_blank"
class="max-w-[240px] truncate text-blue-500 hover:text-blue-600 hover:underline"
:title="getAttachmentName(attachment)"
>
{{ getAttachmentName(attachment) }}
</a>
</div>
</template>
</div>
</div> </div>
</div>
<div
v-if="
task.signPicUrl &&
activity.nodeType === BpmNodeTypeEnum.USER_TASK_NODE
"
class="mt-1 flex w-full items-center rounded-md bg-gray-100 p-2 text-sm text-gray-500"
>
签名
<Image
class="ml-2"
:width="180"
:height="60"
:src="task.signPicUrl"
:preview="{ src: task.signPicUrl }"
/>
</div>
</div> </div>
<!-- 情况二遍历每个审批节点下的候选的task 任务 --> <!-- 情况二遍历每个审批节点下的候选的task 任务 -->

View File

@ -90,15 +90,11 @@ function filterDictTypeOption(input: string, option: any) {
<!-- Java 类型 --> <!-- Java 类型 -->
<template #javaType="{ row, column }"> <template #javaType="{ row, column }">
<Select v-model:value="row.javaType" style="width: 100%"> <Select
<SelectOption v-model:value="row.javaType"
v-for="option in column.params.options" style="width: 100%"
:key="option.value" :options="column.params.options"
:value="option.value" />
>
{{ option.label }}
</SelectOption>
</Select>
</template> </template>
<!-- Java 属性 --> <!-- Java 属性 -->
<template #javaField="{ row }"> <template #javaField="{ row }">
@ -124,15 +120,11 @@ function filterDictTypeOption(input: string, option: any) {
<!-- 查询方式 --> <!-- 查询方式 -->
<template #listOperationCondition="{ row, column }"> <template #listOperationCondition="{ row, column }">
<Select v-model:value="row.listOperationCondition" class="w-full"> <Select
<SelectOption v-model:value="row.listOperationCondition"
v-for="option in column.params.options" class="w-full"
:key="option.value" :options="column.params.options"
:value="option.value" />
>
{{ option.label }}
</SelectOption>
</Select>
</template> </template>
<!-- 允许空 --> <!-- 允许空 -->
@ -142,15 +134,11 @@ function filterDictTypeOption(input: string, option: any) {
<!-- 显示类型 --> <!-- 显示类型 -->
<template #htmlType="{ row, column }"> <template #htmlType="{ row, column }">
<Select v-model:value="row.htmlType" class="w-full"> <Select
<SelectOption v-model:value="row.htmlType"
v-for="option in column.params.options" class="w-full"
:key="option.value" :options="column.params.options"
:value="option.value" />
>
{{ option.label }}
</SelectOption>
</Select>
</template> </template>
<!-- 字典类型 --> <!-- 字典类型 -->
@ -161,15 +149,9 @@ function filterDictTypeOption(input: string, option: any) {
allow-clear allow-clear
show-search show-search
:filter-option="filterDictTypeOption" :filter-option="filterDictTypeOption"
> :options="dictTypeOptions"
<SelectOption :field-names="{ label: 'name', value: 'type' }"
v-for="option in dictTypeOptions" />
:key="option.type"
:value="option.type"
>
{{ option.name }}
</SelectOption>
</Select>
</template> </template>
<!-- 示例 --> <!-- 示例 -->

View File

@ -26,6 +26,7 @@ const loading = ref(false);
const fileTree = ref<FileNode[]>([]); const fileTree = ref<FileNode[]>([]);
const previewFiles = ref<InfraCodegenApi.CodegenPreview[]>([]); const previewFiles = ref<InfraCodegenApi.CodegenPreview[]>([]);
const activeKey = ref<string>(''); const activeKey = ref<string>('');
const selectedKeys = ref<string[]>([]);
/** 代码地图 */ /** 代码地图 */
const codeMap = ref<Map<string, string>>(new Map<string, string>()); const codeMap = ref<Map<string, string>>(new Map<string, string>());
@ -53,31 +54,35 @@ function removeCodeMapKey(targetKey: any) {
/** 复制代码 */ /** 复制代码 */
async function copyCode() { async function copyCode() {
const { copy } = useClipboard(); const { copy } = useClipboard();
const file = previewFiles.value.find( const file = findPreviewFile(activeKey.value);
(item) => item.filePath === activeKey.value,
);
if (file) { if (file) {
await copy(file.code); await copy(file.code);
message.success('复制成功'); message.success('复制成功');
} }
} }
/** 文件节点点击事件 */ function findPreviewFile(fileKey: string) {
function handleNodeClick(_: any[], e: any) { return previewFiles.value.find((item) => {
if (!e.node.isLeaf) { const list = fileKey.split('.');
return;
}
activeKey.value = e.node.key;
const file = previewFiles.value.find((item) => {
const list = activeKey.value.split('.');
// - // -
if (list.length > 2) { if (list.length > 2) {
const lang = list.pop(); const lang = list.pop();
return item.filePath === `${list.join('/')}.${lang}`; return item.filePath === `${list.join('/')}.${lang}`;
} }
return item.filePath === activeKey.value; return item.filePath === fileKey;
}); });
}
/** 文件节点点击事件 */
function handleNodeClick(_: any[], e: any) {
if (!e.node.isLeaf) {
selectedKeys.value = activeKey.value ? [activeKey.value] : [];
return;
}
activeKey.value = String(e.node.key);
selectedKeys.value = [activeKey.value];
const file = findPreviewFile(activeKey.value);
if (!file) { if (!file) {
return; return;
} }
@ -179,6 +184,7 @@ const [Modal, modalApi] = useVbenModal({
if (!isOpen) { if (!isOpen) {
// //
codeMap.value.clear(); codeMap.value.clear();
selectedKeys.value = [];
return; return;
} }
@ -197,6 +203,7 @@ const [Modal, modalApi] = useVbenModal({
fileTree.value = handleFiles(data); fileTree.value = handleFiles(data);
if (data.length > 0) { if (data.length > 0) {
activeKey.value = data[0]?.filePath || ''; activeKey.value = data[0]?.filePath || '';
selectedKeys.value = activeKey.value ? [activeKey.value] : [];
const code = data[0]?.code || ''; const code = data[0]?.code || '';
setCodeMap(activeKey.value, code); setCodeMap(activeKey.value, code);
} }
@ -217,7 +224,7 @@ const [Modal, modalApi] = useVbenModal({
<DirectoryTree <DirectoryTree
v-if="fileTree.length > 0" v-if="fileTree.length > 0"
default-expand-all default-expand-all
v-model:active-key="activeKey" v-model:selected-keys="selectedKeys"
@select="handleNodeClick" @select="handleNodeClick"
:tree-data="fileTree" :tree-data="fileTree"
/> />
@ -240,7 +247,7 @@ const [Modal, modalApi] = useVbenModal({
> >
<CodeEditor <CodeEditor
class="max-h-200" class="max-h-200"
:value="codeMap.get(activeKey)" :value="codeMap.get(key)"
mode="application/json" mode="application/json"
:readonly="true" :readonly="true"
:bordered="true" :bordered="true"

View File

@ -18,6 +18,7 @@ import {
import { import {
Button, Button,
Form, Form,
FormItem,
Input, Input,
message, message,
Pagination, Pagination,
@ -180,18 +181,12 @@ onMounted(() => {
placeholder="请选择性别" placeholder="请选择性别"
allow-clear allow-clear
class="w-full" class="w-full"
> :options="[
<SelectOption ...getDictOptions(DICT_TYPE.SYSTEM_USER_SEX, 'number').map(
v-for="dict in getDictOptions( (dict) => ({ label: dict.label, value: dict.value as any }),
DICT_TYPE.SYSTEM_USER_SEX, ),
'number', ]"
)" />
:key="dict.value"
:value="dict.value"
>
{{ dict.label }}
</SelectOption>
</Select>
</FormItem> </FormItem>
<FormItem label="创建时间" name="createTime"> <FormItem label="创建时间" name="createTime">
<RangePicker <RangePicker

View File

@ -7,15 +7,9 @@ import { useVbenModal } from '@vben/common-ui';
import { DICT_TYPE } from '@vben/constants'; import { DICT_TYPE } from '@vben/constants';
import { getDictOptions } from '@vben/hooks'; import { getDictOptions } from '@vben/hooks';
import { import { Form, FormItem, Input, message, Radio, RadioGroup } from 'antdv-next';
DatePicker,
Form,
Input,
message,
Radio,
RadioGroup,
} from 'antdv-next';
import { DatePicker } from '#/adapter/component/date-picker';
import { import {
createDemo01Contact, createDemo01Contact,
getDemo01Contact, getDemo01Contact,

View File

@ -13,7 +13,14 @@ import {
isEmpty, isEmpty,
} from '@vben/utils'; } from '@vben/utils';
import { Button, DateRangePicker, Form, Input, message } from 'antdv-next'; import {
Button,
DateRangePicker,
Form,
FormItem,
Input,
message,
} from 'antdv-next';
import { VxeColumn, VxeTable } from '#/adapter/vxe-table'; import { VxeColumn, VxeTable } from '#/adapter/vxe-table';
import { import {

View File

@ -6,7 +6,7 @@ import { computed, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui'; import { useVbenModal } from '@vben/common-ui';
import { handleTree } from '@vben/utils'; import { handleTree } from '@vben/utils';
import { Form, Input, message, TreeSelect } from 'antdv-next'; import { Form, FormItem, Input, message, TreeSelect } from 'antdv-next';
import { import {
createDemo02Category, createDemo02Category,

View File

@ -18,6 +18,7 @@ import {
import { import {
Button, Button,
Form, Form,
FormItem,
Input, Input,
message, message,
Pagination, Pagination,
@ -191,18 +192,12 @@ onMounted(() => {
placeholder="请选择性别" placeholder="请选择性别"
allow-clear allow-clear
class="w-full" class="w-full"
> :options="[
<SelectOption ...getDictOptions(DICT_TYPE.SYSTEM_USER_SEX, 'number').map(
v-for="dict in getDictOptions( (dict) => ({ label: dict.label, value: dict.value as any }),
DICT_TYPE.SYSTEM_USER_SEX, ),
'number', ]"
)" />
:key="dict.value"
:value="dict.value"
>
{{ dict.label }}
</SelectOption>
</Select>
</FormItem> </FormItem>
<FormItem label="创建时间" name="createTime"> <FormItem label="创建时间" name="createTime">
<RangePicker <RangePicker

View File

@ -5,7 +5,7 @@ import { computed, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui'; import { useVbenModal } from '@vben/common-ui';
import { Form, Input, message } from 'antdv-next'; import { Form, FormItem, Input, message } from 'antdv-next';
import { import {
createDemo03Course, createDemo03Course,

View File

@ -11,6 +11,7 @@ import { cloneDeep, formatDateTime, isEmpty } from '@vben/utils';
import { import {
Button, Button,
Form, Form,
FormItem,
Input, Input,
message, message,
Pagination, Pagination,

View File

@ -5,7 +5,7 @@ import { computed, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui'; import { useVbenModal } from '@vben/common-ui';
import { Form, Input, message } from 'antdv-next'; import { Form, FormItem, Input, message } from 'antdv-next';
import { import {
createDemo03Grade, createDemo03Grade,

View File

@ -11,6 +11,7 @@ import { cloneDeep, formatDateTime, isEmpty } from '@vben/utils';
import { import {
Button, Button,
Form, Form,
FormItem,
Input, Input,
message, message,
Pagination, Pagination,

View File

@ -7,15 +7,9 @@ import { useVbenModal } from '@vben/common-ui';
import { DICT_TYPE } from '@vben/constants'; import { DICT_TYPE } from '@vben/constants';
import { getDictOptions } from '@vben/hooks'; import { getDictOptions } from '@vben/hooks';
import { import { Form, FormItem, Input, message, Radio, RadioGroup } from 'antdv-next';
DatePicker,
Form,
Input,
message,
Radio,
RadioGroup,
} from 'antdv-next';
import { DatePicker } from '#/adapter/component/date-picker';
import { import {
createDemo03Student, createDemo03Student,
getDemo03Student, getDemo03Student,

View File

@ -18,6 +18,7 @@ import {
import { import {
Button, Button,
Form, Form,
FormItem,
Input, Input,
message, message,
Pagination, Pagination,
@ -184,18 +185,12 @@ onMounted(() => {
placeholder="请选择性别" placeholder="请选择性别"
allow-clear allow-clear
class="w-full" class="w-full"
> :options="[
<SelectOption ...getDictOptions(DICT_TYPE.SYSTEM_USER_SEX, 'number').map(
v-for="dict in getDictOptions( (dict) => ({ label: dict.label, value: dict.value as any }),
DICT_TYPE.SYSTEM_USER_SEX, ),
'number', ]"
)" />
:key="dict.value"
:value="dict.value"
>
{{ dict.label }}
</SelectOption>
</Select>
</FormItem> </FormItem>
<FormItem label="创建时间" name="createTime"> <FormItem label="创建时间" name="createTime">
<RangePicker <RangePicker

View File

@ -3,7 +3,7 @@ import type { Demo03StudentApi } from '#/api/infra/demo/demo03/normal';
import { nextTick, ref, watch } from 'vue'; import { nextTick, ref, watch } from 'vue';
import { Form, Input } from 'antdv-next'; import { Form, FormItem, Input } from 'antdv-next';
import { getDemo03GradeByStudentId } from '#/api/infra/demo/demo03/normal'; import { getDemo03GradeByStudentId } from '#/api/infra/demo/demo03/normal';

View File

@ -8,8 +8,8 @@ import { DICT_TYPE } from '@vben/constants';
import { getDictOptions } from '@vben/hooks'; import { getDictOptions } from '@vben/hooks';
import { import {
DatePicker,
Form, Form,
FormItem,
Input, Input,
message, message,
Radio, Radio,
@ -17,6 +17,7 @@ import {
Tabs, Tabs,
} from 'antdv-next'; } from 'antdv-next';
import { DatePicker } from '#/adapter/component/date-picker';
import { import {
createDemo03Student, createDemo03Student,
getDemo03Student, getDemo03Student,

View File

@ -18,6 +18,7 @@ import {
import { import {
Button, Button,
Form, Form,
FormItem,
Input, Input,
message, message,
Pagination, Pagination,
@ -181,18 +182,12 @@ onMounted(() => {
placeholder="请选择性别" placeholder="请选择性别"
allow-clear allow-clear
class="w-full" class="w-full"
> :options="[
<SelectOption ...getDictOptions(DICT_TYPE.SYSTEM_USER_SEX, 'number').map(
v-for="dict in getDictOptions( (dict) => ({ label: dict.label, value: dict.value as any }),
DICT_TYPE.SYSTEM_USER_SEX, ),
'number', ]"
)" />
:key="dict.value"
:value="dict.value"
>
{{ dict.label }}
</SelectOption>
</Select>
</FormItem> </FormItem>
<FormItem label="创建时间" name="createTime"> <FormItem label="创建时间" name="createTime">
<RangePicker <RangePicker

View File

@ -3,7 +3,7 @@ import type { Demo03StudentApi } from '#/api/infra/demo/demo03/normal';
import { nextTick, ref, watch } from 'vue'; import { nextTick, ref, watch } from 'vue';
import { Form, Input } from 'antdv-next'; import { Form, FormItem, Input } from 'antdv-next';
import { getDemo03GradeByStudentId } from '#/api/infra/demo/demo03/normal'; import { getDemo03GradeByStudentId } from '#/api/infra/demo/demo03/normal';

View File

@ -8,8 +8,8 @@ import { DICT_TYPE } from '@vben/constants';
import { getDictOptions } from '@vben/hooks'; import { getDictOptions } from '@vben/hooks';
import { import {
DatePicker,
Form, Form,
FormItem,
Input, Input,
message, message,
Radio, Radio,
@ -17,6 +17,7 @@ import {
Tabs, Tabs,
} from 'antdv-next'; } from 'antdv-next';
import { DatePicker } from '#/adapter/component/date-picker';
import { import {
createDemo03Student, createDemo03Student,
getDemo03Student, getDemo03Student,

View File

@ -100,7 +100,15 @@ watchEffect(() => {
/** 发送消息 */ /** 发送消息 */
const sendText = ref(''); // const sendText = ref(''); //
const sendUserId = ref('all'); // const sendUserId = ref<'all' | number>('all'); //
const sendUserOptions = computed(() => [
{ label: '所有人', value: 'all', raw: null },
...userList.value.map((user) => ({
label: user.nickname,
value: user.id,
raw: user,
})),
]);
function handlerSend() { function handlerSend() {
if (!sendText.value.trim()) { if (!sendText.value.trim()) {
message.warning('消息内容不能为空'); message.warning('消息内容不能为空');
@ -228,26 +236,20 @@ onMounted(async () => {
size="large" size="large"
placeholder="请选择接收人" placeholder="请选择接收人"
:disabled="!getIsOpen" :disabled="!getIsOpen"
:options="sendUserOptions"
> >
<SelectOption key="" value="" label="所有人"> <template #optionRender="{ option }">
<div class="flex items-center"> <div class="flex items-center" v-if="!option.data.raw">
<Avatar size="small" class="mr-2"></Avatar> <Avatar size="small" class="mr-2"></Avatar>
<span>所有人</span> <span>所有人</span>
</div> </div>
</SelectOption> <div class="flex items-center" v-else>
<SelectOption
v-for="user in userList"
:key="user.id"
:value="user.id"
:label="user.nickname"
>
<div class="flex items-center">
<Avatar size="small" class="mr-2"> <Avatar size="small" class="mr-2">
{{ user.nickname.slice(0, 1) }} {{ option.data.raw.nickname?.slice(0, 1) }}
</Avatar> </Avatar>
<span>{{ user.nickname }}</span> <span>{{ option.data.raw.nickname }}</span>
</div> </div>
</SelectOption> </template>
</Select> </Select>
<TextArea <TextArea

View File

@ -15,7 +15,7 @@ import { DICT_TYPE, IotDeviceMessageMethodEnum } from '@vben/constants';
import { IconifyIcon } from '@vben/icons'; import { IconifyIcon } from '@vben/icons';
import { formatDateTime } from '@vben/utils'; import { formatDateTime } from '@vben/utils';
import { Button, Select, SelectOption, Space, Switch, Tag } from 'antdv-next'; import { Button, Select, Space, Switch, Tag } from 'antdv-next';
import { useVbenVxeGrid } from '#/adapter/vxe-table'; import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { getDeviceMessagePage } from '#/api/iot/device/device'; import { getDeviceMessagePage } from '#/api/iot/device/device';
@ -195,25 +195,18 @@ defineExpose({
allow-clear allow-clear
placeholder="所有方法" placeholder="所有方法"
style="width: 160px" style="width: 160px"
> :options="methodOptions"
<SelectOption />
v-for="item in methodOptions"
:key="item.value"
:label="item.label"
:value="item.value"
>
{{ item.label }}
</SelectOption>
</Select>
<Select <Select
v-model:value="queryParams.upstream" v-model:value="queryParams.upstream"
allow-clear allow-clear
placeholder="上行/下行" placeholder="上行/下行"
style="width: 160px" style="width: 160px"
> :options="[
<SelectOption label="上行" value="true">上行</SelectOption> { label: '上行', value: 'true' },
<SelectOption label="下行" value="false">下行</SelectOption> { label: '下行', value: 'false' },
</Select> ]"
/>
<Space> <Space>
<Button type="primary" @click="handleQuery"> <Button type="primary" @click="handleQuery">
<IconifyIcon icon="ep:search" class="mr-[5px]" /> 搜索 <IconifyIcon icon="ep:search" class="mr-[5px]" /> 搜索

View File

@ -14,14 +14,7 @@ import {
import { IconifyIcon } from '@vben/icons'; import { IconifyIcon } from '@vben/icons';
import { formatDateTime } from '@vben/utils'; import { formatDateTime } from '@vben/utils';
import { import { Button, DateRangePicker, Select, Space, Tag } from 'antdv-next';
Button,
DateRangePicker,
Select,
SelectOption,
Space,
Tag,
} from 'antdv-next';
import { useVbenVxeGrid } from '#/adapter/vxe-table'; import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { getDeviceMessagePairPage } from '#/api/iot/device/device'; import { getDeviceMessagePairPage } from '#/api/iot/device/device';
@ -217,15 +210,13 @@ defineExpose({
allow-clear allow-clear
placeholder="请选择事件标识符" placeholder="请选择事件标识符"
style="width: 240px" style="width: 240px"
> :options="[
<SelectOption ...eventThingModels.map((event) => ({
v-for="event in eventThingModels" label: `${event.name}(${event.identifier})`,
:key="event.identifier" value: event.identifier!,
:value="event.identifier!" })),
> ]"
{{ event.name }}({{ event.identifier }}) />
</SelectOption>
</Select>
</div> </div>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<span>时间范围</span> <span>时间范围</span>

View File

@ -14,14 +14,7 @@ import {
import { IconifyIcon } from '@vben/icons'; import { IconifyIcon } from '@vben/icons';
import { formatDateTime } from '@vben/utils'; import { formatDateTime } from '@vben/utils';
import { import { Button, DateRangePicker, Select, Space, Tag } from 'antdv-next';
Button,
DateRangePicker,
Select,
SelectOption,
Space,
Tag,
} from 'antdv-next';
import { useVbenVxeGrid } from '#/adapter/vxe-table'; import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { getDeviceMessagePairPage } from '#/api/iot/device/device'; import { getDeviceMessagePairPage } from '#/api/iot/device/device';
@ -231,15 +224,13 @@ defineExpose({
allow-clear allow-clear
placeholder="请选择服务标识符" placeholder="请选择服务标识符"
style="width: 240px" style="width: 240px"
> :options="[
<SelectOption ...serviceThingModels.map((service) => ({
v-for="service in serviceThingModels" label: `${service.name}(${service.identifier})`,
:key="service.identifier" value: service.identifier!,
:value="service.identifier!" })),
> ]"
{{ service.name }}({{ service.identifier }}) />
</SelectOption>
</Select>
</div> </div>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<span>时间范围</span> <span>时间范围</span>

View File

@ -15,16 +15,7 @@ import { getDictOptions } from '@vben/hooks';
import { IconifyIcon } from '@vben/icons'; import { IconifyIcon } from '@vben/icons';
import { downloadFileFromBlobPart, isEmpty } from '@vben/utils'; import { downloadFileFromBlobPart, isEmpty } from '@vben/utils';
import { import { Button, Card, Input, message, Select, Space, Tag } from 'antdv-next';
Button,
Card,
Input,
message,
Select,
SelectOption,
Space,
Tag,
} from 'antdv-next';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { import {
@ -292,15 +283,9 @@ onMounted(async () => {
placeholder="请选择产品" placeholder="请选择产品"
allow-clear allow-clear
style="width: 200px" style="width: 200px"
> :options="products"
<SelectOption :field-names="{ label: 'name', value: 'id' }"
v-for="product in products" />
:key="product.id"
:value="product.id"
>
{{ product.name }}
</SelectOption>
</Select>
<Input <Input
v-model:value="queryParams.deviceName" v-model:value="queryParams.deviceName"
placeholder="请输入 DeviceName" placeholder="请输入 DeviceName"
@ -317,49 +302,34 @@ onMounted(async () => {
/> />
<Select <Select
v-model:value="queryParams.deviceType" v-model:value="queryParams.deviceType"
:options="
getDictOptions(DICT_TYPE.IOT_PRODUCT_DEVICE_TYPE, 'number').map(
(dict) => ({ label: dict.label, value: dict.value as any }),
)
"
placeholder="请选择设备类型" placeholder="请选择设备类型"
allow-clear allow-clear
style="width: 200px" style="width: 200px"
> />
<SelectOption
v-for="dict in getDictOptions(
DICT_TYPE.IOT_PRODUCT_DEVICE_TYPE,
'number',
)"
:key="dict.value"
:value="dict.value"
>
{{ dict.label }}
</SelectOption>
</Select>
<Select <Select
v-model:value="queryParams.status" v-model:value="queryParams.status"
placeholder="请选择设备状态" placeholder="请选择设备状态"
allow-clear allow-clear
style="width: 200px" style="width: 200px"
> :options="
<SelectOption getDictOptions(DICT_TYPE.IOT_DEVICE_STATE, 'number').map(
v-for="dict in getDictOptions(DICT_TYPE.IOT_DEVICE_STATE, 'number')" (dict) => ({ label: dict.label, value: dict.value as any }),
:key="dict.value" )
:value="dict.value" "
> />
{{ dict.label }}
</SelectOption>
</Select>
<Select <Select
v-model:value="queryParams.groupId" v-model:value="queryParams.groupId"
placeholder="请选择设备分组" placeholder="请选择设备分组"
allow-clear allow-clear
style="width: 200px" style="width: 200px"
> :options="deviceGroups"
<SelectOption :field-names="{ label: 'name', value: 'id' }"
v-for="group in deviceGroups" />
:key="group.id"
:value="group.id"
>
{{ group.name }}
</SelectOption>
</Select>
<Button type="primary" @click="handleSearch"> <Button type="primary" @click="handleSearch">
<IconifyIcon icon="ant-design:search-outlined" class="mr-1" /> <IconifyIcon icon="ant-design:search-outlined" class="mr-1" />
{{ $t('common.search') }} {{ $t('common.search') }}

View File

@ -5,7 +5,7 @@ import { computed, onMounted, ref, watch } from 'vue';
import { isEmpty } from '@vben/utils'; import { isEmpty } from '@vben/utils';
import { useVModel } from '@vueuse/core'; import { useVModel } from '@vueuse/core';
import { FormItem, Input, Select, SelectOption, TextArea } from 'antdv-next'; import { FormItem, Input, Select, TextArea } from 'antdv-next';
import { IotDataSinkTypeEnum } from '#/api/iot/rule/data/sink'; import { IotDataSinkTypeEnum } from '#/api/iot/rule/data/sink';
@ -67,10 +67,14 @@ onMounted(() => {
> >
<Input v-model:value="urlPath" placeholder="请输入请求地址"> <Input v-model:value="urlPath" placeholder="请输入请求地址">
<template #addonBefore> <template #addonBefore>
<Select v-model:value="urlPrefix" class="w-[100px]"> <Select
<SelectOption value="http://">http://</SelectOption> v-model:value="urlPrefix"
<SelectOption value="https://">https://</SelectOption> class="w-[100px]"
</Select> :options="[
{ label: 'http://', value: 'http://' },
{ label: 'https://', value: 'https://' },
]"
/>
</template> </template>
</Input> </Input>
</FormItem> </FormItem>
@ -81,12 +85,16 @@ onMounted(() => {
]" ]"
label="请求方法" label="请求方法"
> >
<Select v-model:value="config.method" placeholder="请选择请求方法"> <Select
<SelectOption value="GET">GET</SelectOption> v-model:value="config.method"
<SelectOption value="POST">POST</SelectOption> placeholder="请选择请求方法"
<SelectOption value="PUT">PUT</SelectOption> :options="[
<SelectOption value="DELETE">DELETE</SelectOption> { label: 'GET', value: 'GET' },
</Select> { label: 'POST', value: 'POST' },
{ label: 'PUT', value: 'PUT' },
{ label: 'DELETE', value: 'DELETE' },
]"
/>
</FormItem> </FormItem>
<FormItem label="请求头"> <FormItem label="请求头">
<KeyValueEditor v-model="config.headers" add-button-text="" /> <KeyValueEditor v-model="config.headers" add-button-text="" />

View File

@ -4,14 +4,7 @@ import { onMounted } from 'vue';
import { isEmpty } from '@vben/utils'; import { isEmpty } from '@vben/utils';
import { useVModel } from '@vueuse/core'; import { useVModel } from '@vueuse/core';
import { import { FormItem, Input, InputNumber, Select, Switch } from 'antdv-next';
FormItem,
Input,
InputNumber,
Select,
SelectOption,
Switch,
} from 'antdv-next';
import { IotDataSinkTypeEnum } from '#/api/iot/rule/data/sink'; import { IotDataSinkTypeEnum } from '#/api/iot/rule/data/sink';
@ -120,10 +113,14 @@ onMounted(() => {
]" ]"
label="数据格式" label="数据格式"
> >
<Select v-model:value="config.dataFormat" placeholder="请选择数据格式"> <Select
<SelectOption value="JSON">JSON</SelectOption> v-model:value="config.dataFormat"
<SelectOption value="BINARY">BINARY</SelectOption> placeholder="请选择数据格式"
</Select> :options="[
{ label: 'JSON', value: 'JSON' },
{ label: 'BINARY', value: 'BINARY' },
]"
/>
</FormItem> </FormItem>
<FormItem :name="['config', 'heartbeatIntervalMs']" label="心跳间隔(ms)"> <FormItem :name="['config', 'heartbeatIntervalMs']" label="心跳间隔(ms)">
<InputNumber <InputNumber

View File

@ -9,7 +9,6 @@ import {
Input, Input,
InputNumber, InputNumber,
Select, Select,
SelectOption,
Switch, Switch,
TextArea, TextArea,
} from 'antdv-next'; } from 'antdv-next';
@ -127,10 +126,14 @@ onMounted(() => {
]" ]"
label="数据格式" label="数据格式"
> >
<Select v-model:value="config.dataFormat" placeholder="请选择数据格式"> <Select
<SelectOption value="JSON">JSON</SelectOption> v-model:value="config.dataFormat"
<SelectOption value="TEXT">TEXT</SelectOption> placeholder="请选择数据格式"
</Select> :options="[
{ label: 'JSON', value: 'JSON' },
{ label: 'TEXT', value: 'TEXT' },
]"
/>
</FormItem> </FormItem>
<FormItem :name="['config', 'reconnectIntervalMs']" label="重连间隔(ms)"> <FormItem :name="['config', 'reconnectIntervalMs']" label="重连间隔(ms)">
<InputNumber <InputNumber

View File

@ -3,7 +3,7 @@
import { onMounted, ref } from 'vue'; import { onMounted, ref } from 'vue';
import { useVModel } from '@vueuse/core'; import { useVModel } from '@vueuse/core';
import { FormItem, Select, SelectOption } from 'antdv-next'; import { FormItem, Select } from 'antdv-next';
import { getSimpleAlertConfigList } from '#/api/iot/alert/config'; import { getSimpleAlertConfigList } from '#/api/iot/alert/config';
@ -49,20 +49,15 @@ onMounted(() => {
<FormItem label="告警配置" required> <FormItem label="告警配置" required>
<Select <Select
v-model:value="localValue" v-model:value="localValue"
:options="alertConfigs"
:field-names="{ label: 'name', value: 'id' }"
placeholder="请选择告警配置" placeholder="请选择告警配置"
show-search show-search
allow-clear allow-clear
@change="handleChange" @change="handleChange"
class="w-full" class="w-full"
:loading="loading" :loading="loading"
> />
<SelectOption
v-for="config in alertConfigs"
:key="config.id"
:label="config.name"
:value="config.id"
/>
</Select>
</FormItem> </FormItem>
</div> </div>
</template> </template>

View File

@ -12,7 +12,7 @@ import {
} from '@vben/constants'; } from '@vben/constants';
import { useVModel } from '@vueuse/core'; import { useVModel } from '@vueuse/core';
import { Col, FormItem, Row, Select, SelectOption } from 'antdv-next'; import { Col, FormItem, Row, Select } from 'antdv-next';
import ValueInput from '../inputs/value-input.vue'; import ValueInput from '../inputs/value-input.vue';
import DeviceSelector from '../selectors/device-selector.vue'; import DeviceSelector from '../selectors/device-selector.vue';
@ -163,6 +163,7 @@ function handleOperatorChange() {
<FormItem label="条件类型" required> <FormItem label="条件类型" required>
<Select <Select
:value="condition.type" :value="condition.type"
:options="getConditionTypeOptions()"
@change=" @change="
(value: any) => { (value: any) => {
updateConditionField('type', value); updateConditionField('type', value);
@ -171,15 +172,7 @@ function handleOperatorChange() {
" "
placeholder="请选择条件类型" placeholder="请选择条件类型"
class="w-full" class="w-full"
> />
<SelectOption
v-for="option in getConditionTypeOptions()"
:key="option.value"
:value="option.value"
>
{{ option.label }}
</SelectOption>
</Select>
</FormItem> </FormItem>
</Col> </Col>
</Row> </Row>
@ -225,18 +218,11 @@ function handleOperatorChange() {
<FormItem label="操作符" required> <FormItem label="操作符" required>
<Select <Select
:value="condition.operator" :value="condition.operator"
:options="statusOperatorOptions"
@change="(value: any) => updateConditionField('operator', value)" @change="(value: any) => updateConditionField('operator', value)"
placeholder="请选择操作符" placeholder="请选择操作符"
class="w-full" class="w-full"
> />
<SelectOption
v-for="option in statusOperatorOptions"
:key="option.value"
:value="option.value"
>
{{ option.label }}
</SelectOption>
</Select>
</FormItem> </FormItem>
</Col> </Col>
@ -245,18 +231,11 @@ function handleOperatorChange() {
<FormItem label="设备状态" required> <FormItem label="设备状态" required>
<Select <Select
:value="condition.param" :value="condition.param"
:options="deviceStatusOptions"
@change="(value: any) => updateConditionField('param', value)" @change="(value: any) => updateConditionField('param', value)"
placeholder="请选择设备状态" placeholder="请选择设备状态"
class="w-full" class="w-full"
> />
<SelectOption
v-for="option in deviceStatusOptions"
:key="option.value"
:value="option.value"
>
{{ option.label }}
</SelectOption>
</Select>
</FormItem> </FormItem>
</Col> </Col>
</Row> </Row>

View File

@ -1,7 +1,5 @@
<!-- 当前时间条件配置组件 --> <!-- 当前时间条件配置组件 -->
<script setup lang="ts"> <script setup lang="ts">
import type { Dayjs } from 'dayjs';
import type { RuleSceneApi } from '#/api/iot/rule/scene'; import type { RuleSceneApi } from '#/api/iot/rule/scene';
import { computed, watch } from 'vue'; import { computed, watch } from 'vue';
@ -17,7 +15,6 @@ import {
FormItem, FormItem,
Row, Row,
Select, Select,
SelectOption,
Tag, Tag,
TimePicker, TimePicker,
} from 'antdv-next'; } from 'antdv-next';
@ -130,7 +127,7 @@ function updateConditionField(field: any, value: any) {
* 处理第一个时间值变化 * 处理第一个时间值变化
* @param value 时间值 * @param value 时间值
*/ */
function handleTimeValueChange(value: Dayjs | null | string) { function handleTimeValueChange(value: any) {
const normalized = formatDayjs(value); const normalized = formatDayjs(value);
const currentParams = condition.value.param const currentParams = condition.value.param
? condition.value.param.split(',') ? condition.value.param.split(',')
@ -147,7 +144,7 @@ function handleTimeValueChange(value: Dayjs | null | string) {
* 处理第二个时间值变化 * 处理第二个时间值变化
* @param value 时间值 * @param value 时间值
*/ */
function handleTimeValue2Change(value: Dayjs | null | string) { function handleTimeValue2Change(value: any) {
const normalized = formatDayjs(value); const normalized = formatDayjs(value);
const currentParams = condition.value.param const currentParams = condition.value.param
? condition.value.param.split(',') ? condition.value.param.split(',')
@ -187,23 +184,22 @@ watch(
" "
placeholder="请选择时间条件" placeholder="请选择时间条件"
class="w-full" class="w-full"
:options="timeOperatorOptions"
> >
<SelectOption <template #optionRender="{ option }">
v-for="option in timeOperatorOptions"
:key="option.value"
:label="option.label"
:value="option.value"
>
<div class="flex w-full items-center justify-between"> <div class="flex w-full items-center justify-between">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<IconifyIcon :icon="option.icon" :class="option.iconClass" /> <IconifyIcon
<span>{{ option.label }}</span> :icon="option.data.icon"
:class="option.data.iconClass"
/>
<span>{{ option.data.label }}</span>
</div> </div>
<Tag :color="option.tag"> <Tag :color="option.data.tag">
{{ option.category }} {{ option.data.category }}
</Tag> </Tag>
</div> </div>
</SelectOption> </template>
</Select> </Select>
</FormItem> </FormItem>
</Col> </Col>

View File

@ -13,7 +13,7 @@ import {
import { isObject } from '@vben/utils'; import { isObject } from '@vben/utils';
import { useVModel } from '@vueuse/core'; import { useVModel } from '@vueuse/core';
import { Col, FormItem, Row, Select, SelectOption, Tag } from 'antdv-next'; import { Col, FormItem, Row, Select, Tag } from 'antdv-next';
import { getThingModelTSLByProductId } from '#/api/iot/thingmodel'; import { getThingModelTSLByProductId } from '#/api/iot/thingmodel';
@ -39,6 +39,13 @@ const loadingThingModel = ref(false); // 物模型加载状态
const selectedService = ref<null | ThingModelApi.Service>(null); // const selectedService = ref<null | ThingModelApi.Service>(null); //
const serviceList = ref<ThingModelApi.Service[]>([]); // const serviceList = ref<ThingModelApi.Service[]>([]); //
const loadingServices = ref(false); // const loadingServices = ref(false); //
const serviceOptions = computed(() =>
serviceList.value.map((service) => ({
label: service.name,
value: service.identifier,
raw: service,
})),
);
// //
const paramsValue = computed({ const paramsValue = computed({
@ -346,23 +353,22 @@ watch(
allow-clear allow-clear
class="w-full" class="w-full"
:loading="loadingServices" :loading="loadingServices"
:options="serviceOptions"
option-filter-prop="label"
@change="handleServiceChange" @change="handleServiceChange"
> >
<SelectOption <template #optionRender="{ option }">
v-for="service in serviceList"
:key="service.identifier"
:label="service.name"
:value="service.identifier"
>
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<span>{{ service.name }}</span> <span>{{ option.data.raw.name }}</span>
<Tag <Tag
:color="service.callType === 'sync' ? 'processing' : 'success'" :color="
option.data.raw.callType === 'sync' ? 'processing' : 'success'
"
> >
{{ service.callType === 'sync' ? '同步' : '异步' }} {{ option.data.raw.callType === 'sync' ? '同步' : '异步' }}
</Tag> </Tag>
</div> </div>
</SelectOption> </template>
</Select> </Select>
</FormItem> </FormItem>

View File

@ -12,7 +12,7 @@ import {
} from '@vben/constants'; } from '@vben/constants';
import { useVModel } from '@vueuse/core'; import { useVModel } from '@vueuse/core';
import { Col, FormItem, Input, Row, Select, SelectOption } from 'antdv-next'; import { Col, FormItem, Input, Row, Select } from 'antdv-next';
import JsonParamsInput from '../inputs/json-params-input.vue'; import JsonParamsInput from '../inputs/json-params-input.vue';
import ValueInput from '../inputs/value-input.vue'; import ValueInput from '../inputs/value-input.vue';
@ -171,18 +171,11 @@ function handlePropertyChange(propertyInfo: any) {
<FormItem label="触发事件类型" required> <FormItem label="触发事件类型" required>
<Select <Select
:value="triggerType" :value="triggerType"
:options="triggerTypeOptions"
@change="(value: any) => handleTriggerTypeChange(value)" @change="(value: any) => handleTriggerTypeChange(value)"
placeholder="请选择触发事件类型" placeholder="请选择触发事件类型"
class="w-full" class="w-full"
> />
<SelectOption
v-for="option in triggerTypeOptions"
:key="option.value"
:value="option.value"
>
{{ option.label }}
</SelectOption>
</Select>
</FormItem> </FormItem>
<!-- 设备属性条件配置 --> <!-- 设备属性条件配置 -->
@ -317,38 +310,31 @@ function handlePropertyChange(propertyInfo: any) {
<FormItem label="操作符" required> <FormItem label="操作符" required>
<Select <Select
:value="condition.operator" :value="condition.operator"
:options="[
{
label:
IotRuleSceneTriggerConditionParameterOperatorEnum.EQUALS
.name,
value:
IotRuleSceneTriggerConditionParameterOperatorEnum.EQUALS
.value,
},
]"
@change="(value: any) => updateConditionField('operator', value)" @change="(value: any) => updateConditionField('operator', value)"
placeholder="请选择操作符" placeholder="请选择操作符"
class="w-full" class="w-full"
> />
<SelectOption
:value="
IotRuleSceneTriggerConditionParameterOperatorEnum.EQUALS.value
"
>
{{
IotRuleSceneTriggerConditionParameterOperatorEnum.EQUALS.name
}}
</SelectOption>
</Select>
</FormItem> </FormItem>
</Col> </Col>
<Col :span="6"> <Col :span="6">
<FormItem label="参数" required> <FormItem label="参数" required>
<Select <Select
:value="condition.value" :value="condition.value"
:options="deviceStatusChangeOptions"
@change="(value: any) => updateConditionField('value', value)" @change="(value: any) => updateConditionField('value', value)"
placeholder="请选择操作符" placeholder="请选择操作符"
class="w-full" class="w-full"
> />
<SelectOption
v-for="option in deviceStatusChangeOptions"
:key="option.value"
:value="option.value"
>
{{ option.label }}
</SelectOption>
</Select>
</FormItem> </FormItem>
</Col> </Col>
</Row> </Row>

View File

@ -14,7 +14,6 @@ import {
Input, Input,
InputNumber, InputNumber,
Select, Select,
SelectOption,
Tag, Tag,
Tooltip, Tooltip,
} from 'antdv-next'; } from 'antdv-next';
@ -209,10 +208,11 @@ watch(
v-model:value="localValue" v-model:value="localValue"
placeholder="请选择布尔值" placeholder="请选择布尔值"
class="w-full!" class="w-full!"
> :options="[
<SelectOption value="true"> (true)</SelectOption> { label: '真 (true)', value: 'true' },
<SelectOption value="false"> (false)</SelectOption> { label: '假 (false)', value: 'false' },
</Select> ]"
/>
<!-- 枚举值选择 --> <!-- 枚举值选择 -->
<Select <Select
@ -220,17 +220,10 @@ watch(
propertyType === IoTDataSpecsDataTypeEnum.ENUM && enumOptions.length > 0 propertyType === IoTDataSpecsDataTypeEnum.ENUM && enumOptions.length > 0
" "
v-model:value="localValue" v-model:value="localValue"
:options="enumOptions"
placeholder="请选择枚举值" placeholder="请选择枚举值"
class="w-full!" class="w-full!"
> />
<SelectOption
v-for="option in enumOptions"
:key="option.value"
:value="option.value"
>
{{ option.label }}
</SelectOption>
</Select>
<!-- 范围输入 (between 操作符) --> <!-- 范围输入 (between 操作符) -->
<div <div

View File

@ -11,15 +11,7 @@ import { IconifyIcon } from '@vben/icons';
import { getStableObjectKey } from '@vben/utils'; import { getStableObjectKey } from '@vben/utils';
import { useVModel } from '@vueuse/core'; import { useVModel } from '@vueuse/core';
import { import { Button, Card, Empty, FormItem, Select, Tag } from 'antdv-next';
Button,
Card,
Empty,
FormItem,
Select,
SelectOption,
Tag,
} from 'antdv-next';
import AlertConfig from '../configs/alert-config.vue'; import AlertConfig from '../configs/alert-config.vue';
import DeviceControlConfig from '../configs/device-control-config.vue'; import DeviceControlConfig from '../configs/device-control-config.vue';
@ -239,18 +231,11 @@ function onActionTypeChange(action: RuleSceneApi.Action, type: number) {
<FormItem label="执行类型" required> <FormItem label="执行类型" required>
<Select <Select
:value="action.type" :value="action.type"
:options="getActionTypeOptions()"
@change="(value) => updateActionType(index, value as number)" @change="(value) => updateActionType(index, value as number)"
placeholder="请选择执行类型" placeholder="请选择执行类型"
class="w-full" class="w-full"
> />
<SelectOption
v-for="option in getActionTypeOptions()"
:key="option.value"
:value="option.value"
>
{{ option.label }}
</SelectOption>
</Select>
</FormItem> </FormItem>
</div> </div>

View File

@ -1,10 +1,10 @@
<!-- 设备选择器组件 --> <!-- 设备选择器组件 -->
<script setup lang="ts"> <script setup lang="ts">
import { ref, watch } from 'vue'; import { computed, ref, watch } from 'vue';
import { DEVICE_SELECTOR_OPTIONS, DICT_TYPE } from '@vben/constants'; import { DEVICE_SELECTOR_OPTIONS, DICT_TYPE } from '@vben/constants';
import { Select, SelectOption } from 'antdv-next'; import { Select } from 'antdv-next';
import { getDeviceListByProductId } from '#/api/iot/device/device'; import { getDeviceListByProductId } from '#/api/iot/device/device';
import { DictTag } from '#/components/dict-tag'; import { DictTag } from '#/components/dict-tag';
@ -24,6 +24,13 @@ const emit = defineEmits<{
const deviceLoading = ref(false); // const deviceLoading = ref(false); //
const deviceList = ref<any[]>([]); // const deviceList = ref<any[]>([]); //
const deviceOptions = computed(() =>
deviceList.value.map((device) => ({
label: device.deviceName,
value: device.id,
raw: device,
})),
);
/** 处理选择变化事件 */ /** 处理选择变化事件 */
function handleChange(value: any) { function handleChange(value: any) {
@ -90,28 +97,28 @@ watch(
allow-clear allow-clear
class="w-full" class="w-full"
option-label-prop="label" option-label-prop="label"
option-filter-prop="label"
:loading="deviceLoading" :loading="deviceLoading"
:disabled="!productId" :disabled="!productId"
:options="deviceOptions"
> >
<SelectOption <template #optionRender="{ option }">
v-for="device in deviceList"
:key="device.id"
:label="device.deviceName"
:value="device.id"
>
<div class="py-[4px] flex w-full items-center justify-between"> <div class="py-[4px] flex w-full items-center justify-between">
<div class="flex-1"> <div class="flex-1">
<div class="text-[14px] font-medium mb-[2px] text-foreground"> <div class="text-[14px] font-medium mb-[2px] text-foreground">
{{ device.deviceName }} {{ option.data.raw.deviceName }}
</div> </div>
<div class="text-[12px] text-muted-foreground"> <div class="text-[12px] text-muted-foreground">
{{ device.deviceKey }} {{ option.data.raw.deviceKey }}
</div> </div>
</div> </div>
<div class="gap-[4px] flex items-center" v-if="device.id > 0"> <div class="gap-[4px] flex items-center" v-if="option.data.raw.id > 0">
<DictTag :type="DICT_TYPE.IOT_DEVICE_STATE" :value="device.state" /> <DictTag
:type="DICT_TYPE.IOT_DEVICE_STATE"
:value="option.data.raw.state"
/>
</div> </div>
</div> </div>
</SelectOption> </template>
</Select> </Select>
</template> </template>

View File

@ -8,7 +8,7 @@ import {
} from '@vben/constants'; } from '@vben/constants';
import { useVModel } from '@vueuse/core'; import { useVModel } from '@vueuse/core';
import { Select, SelectOption } from 'antdv-next'; import { Select } from 'antdv-next';
/** 操作符选择器组件 */ /** 操作符选择器组件 */
defineOptions({ name: 'OperatorSelector' }); defineOptions({ name: 'OperatorSelector' });
@ -204,6 +204,13 @@ const availableOperators = computed(() => {
(op.supportedTypes as any[]).includes(props.propertyType || ''), (op.supportedTypes as any[]).includes(props.propertyType || ''),
); );
}); });
const availableOperatorOptions = computed(() =>
availableOperators.value.map((operator) => ({
label: operator.label,
value: operator.value,
raw: operator,
})),
);
// //
const selectedOperator = computed(() => { const selectedOperator = computed(() => {
@ -244,29 +251,25 @@ watch(
@change="handleChange" @change="handleChange"
class="w-full" class="w-full"
option-label-prop="label" option-label-prop="label"
:options="availableOperatorOptions"
> >
<SelectOption <template #optionRender="{ option }">
v-for="operator in availableOperators"
:key="operator.value"
:label="operator.label"
:value="operator.value"
>
<div class="py-[4px] flex w-full items-center justify-between"> <div class="py-[4px] flex w-full items-center justify-between">
<div class="gap-[8px] flex items-center"> <div class="gap-[8px] flex items-center">
<div class="text-[14px] font-medium text-foreground"> <div class="text-[14px] font-medium text-foreground">
{{ operator.label }} {{ option.data.raw.label }}
</div> </div>
<div <div
class="text-[12px] px-[6px] py-[2px] rounded-[4px] bg-primary-light-9 font-mono text-primary" class="text-[12px] px-[6px] py-[2px] rounded-[4px] bg-primary-light-9 font-mono text-primary"
> >
{{ operator.symbol }} {{ option.data.raw.symbol }}
</div> </div>
</div> </div>
<div class="text-[12px] text-muted-foreground"> <div class="text-[12px] text-muted-foreground">
{{ operator.description }} {{ option.data.raw.description }}
</div> </div>
</div> </div>
</SelectOption> </template>
</Select> </Select>
</div> </div>
</template> </template>

View File

@ -1,10 +1,10 @@
<!-- 产品选择器组件 --> <!-- 产品选择器组件 -->
<script setup lang="ts"> <script setup lang="ts">
import { onMounted, ref } from 'vue'; import { computed, onMounted, ref } from 'vue';
import { DICT_TYPE } from '@vben/constants'; import { DICT_TYPE } from '@vben/constants';
import { Select, SelectOption } from 'antdv-next'; import { Select } from 'antdv-next';
import { getSimpleProductList } from '#/api/iot/product/product'; import { getSimpleProductList } from '#/api/iot/product/product';
import { DictTag } from '#/components/dict-tag'; import { DictTag } from '#/components/dict-tag';
@ -23,6 +23,13 @@ const emit = defineEmits<{
const productLoading = ref(false); // const productLoading = ref(false); //
const productList = ref<any[]>([]); // const productList = ref<any[]>([]); //
const productOptions = computed(() =>
productList.value.map((product) => ({
label: product.name,
value: product.id,
raw: product,
})),
);
/** /**
* 处理选择变化事件 * 处理选择变化事件
@ -62,25 +69,25 @@ onMounted(() => {
allow-clear allow-clear
class="w-full" class="w-full"
option-label-prop="label" option-label-prop="label"
option-filter-prop="label"
:loading="productLoading" :loading="productLoading"
:options="productOptions"
> >
<SelectOption <template #optionRender="{ option }">
v-for="product in productList"
:key="product.id"
:label="product.name"
:value="product.id"
>
<div class="py-[4px] flex w-full items-center justify-between"> <div class="py-[4px] flex w-full items-center justify-between">
<div class="flex-1"> <div class="flex-1">
<div class="text-[14px] font-medium mb-[2px] text-foreground"> <div class="text-[14px] font-medium mb-[2px] text-foreground">
{{ product.name }} {{ option.data.raw.name }}
</div> </div>
<div class="text-[12px] text-muted-foreground"> <div class="text-[12px] text-muted-foreground">
{{ product.productKey }} {{ option.data.raw.productKey }}
</div> </div>
</div> </div>
<DictTag :type="DICT_TYPE.IOT_PRODUCT_STATUS" :value="product.status" /> <DictTag
:type="DICT_TYPE.IOT_PRODUCT_STATUS"
:value="option.data.raw.status"
/>
</div> </div>
</SelectOption> </template>
</Select> </Select>
</template> </template>

View File

@ -16,14 +16,7 @@ import {
import { IconifyIcon } from '@vben/icons'; import { IconifyIcon } from '@vben/icons';
import { useVModel } from '@vueuse/core'; import { useVModel } from '@vueuse/core';
import { import { Button, Popover, Select, Tag } from 'antdv-next';
Button,
Popover,
Select,
SelectOptGroup,
SelectOption,
Tag,
} from 'antdv-next';
import { getThingModelTSLByProductId } from '#/api/iot/thingmodel'; import { getThingModelTSLByProductId } from '#/api/iot/thingmodel';
@ -99,6 +92,16 @@ const propertyGroups = computed(() => {
); );
return options.length > 0 ? [{ label: config.label, options }] : []; return options.length > 0 ? [{ label: config.label, options }] : [];
}); });
const propertySelectOptions = computed(() =>
propertyGroups.value.map((group) => ({
label: group.label,
options: group.options.map((property) => ({
label: property.name,
value: property.identifier,
raw: property,
})),
})),
);
/** 当前选中的属性 */ /** 当前选中的属性 */
const selectedProperty = computed(() => const selectedProperty = computed(() =>
@ -224,31 +227,20 @@ watch(
@change="handleChange" @change="handleChange"
class="!w-[150px]" class="!w-[150px]"
option-label-prop="label" option-label-prop="label"
option-filter-prop="label"
:loading="loading" :loading="loading"
:options="propertySelectOptions"
> >
<SelectOptGroup <template #optionRender="{ option }">
v-for="group in propertyGroups" <div class="py-[2px] flex w-full items-center justify-between">
:key="group.label" <span class="text-[14px] font-medium flex-1 truncate text-foreground">
:label="group.label" {{ option.data.raw.name }}
> </span>
<SelectOption <Tag class="ml-[8px] flex-shrink-0">
v-for="property in group.options" {{ option.data.raw.identifier }}
:key="property.identifier" </Tag>
:label="property.name" </div>
:value="property.identifier" </template>
>
<div class="py-[2px] flex w-full items-center justify-between">
<span
class="text-[14px] font-medium flex-1 truncate text-foreground"
>
{{ property.name }}
</span>
<Tag class="ml-[8px] flex-shrink-0">
{{ property.identifier }}
</Tag>
</div>
</SelectOption>
</SelectOptGroup>
</Select> </Select>
<!-- 属性详情弹出层 --> <!-- 属性详情弹出层 -->

View File

@ -122,23 +122,21 @@ function normalizeFormData(result: any): RuleSceneApi.SceneRule {
} }
/** 触发器校验 */ /** 触发器校验 */
function validateTriggers(_rule: any, value: any, callback: any) { function validateTriggers(_rule: any, value: any) {
const error = validateSceneRuleTriggers(value); const error = validateSceneRuleTriggers(value);
if (error) { if (error) {
callback(new Error(error)); return Promise.reject(new Error(error));
return;
} }
callback(); return Promise.resolve();
} }
/** 执行器校验 */ /** 执行器校验 */
function validateActions(_rule: any, value: any, callback: any) { function validateActions(_rule: any, value: any) {
const error = validateSceneRuleActions(value); const error = validateSceneRuleActions(value);
if (error) { if (error) {
callback(new Error(error)); return Promise.reject(new Error(error));
return;
} }
callback(); return Promise.resolve();
} }
const formRules = reactive({ const formRules = reactive({

View File

@ -35,51 +35,44 @@ function deleteEnum(index: number) {
} }
/** 校验单项枚举值:必填、数字、不重复 */ /** 校验单项枚举值:必填、数字、不重复 */
function validateEnumValue(_rule: any, value: any, callback: any) { function validateEnumValue(_rule: any, value: any) {
if (isEmpty(value)) { if (isEmpty(value)) {
callback(new Error('枚举值不能为空')); return Promise.reject(new Error('枚举值不能为空'));
return;
} }
if (Number.isNaN(Number(value))) { if (Number.isNaN(Number(value))) {
callback(new Error('枚举值必须是数字')); return Promise.reject(new Error('枚举值必须是数字'));
return;
} }
const sameCount = dataSpecsList.value.filter( const sameCount = dataSpecsList.value.filter(
(it) => it.value === value, (it) => it.value === value,
).length; ).length;
if (sameCount > 1) { if (sameCount > 1) {
callback(new Error('枚举值不能重复')); return Promise.reject(new Error('枚举值不能重复'));
return;
} }
callback(); return Promise.resolve();
} }
/** 校验整个枚举列表:非空、无空项、无非法数字、无重复 */ /** 校验整个枚举列表:非空、无空项、无非法数字、无重复 */
function validateEnumList(_rule: any, _value: any, callback: any) { function validateEnumList(_rule: any, _value: any) {
if (isEmpty(dataSpecsList.value)) { if (isEmpty(dataSpecsList.value)) {
callback(new Error('请至少添加一个枚举项')); return Promise.reject(new Error('请至少添加一个枚举项'));
return;
} }
const hasEmpty = dataSpecsList.value.some( const hasEmpty = dataSpecsList.value.some(
(item) => isEmpty(item.value) || isEmpty(item.name), (item) => isEmpty(item.value) || isEmpty(item.name),
); );
if (hasEmpty) { if (hasEmpty) {
callback(new Error('存在未填写的枚举值或描述')); return Promise.reject(new Error('存在未填写的枚举值或描述'));
return;
} }
const hasInvalidNumber = dataSpecsList.value.some((item) => const hasInvalidNumber = dataSpecsList.value.some((item) =>
Number.isNaN(Number(item.value)), Number.isNaN(Number(item.value)),
); );
if (hasInvalidNumber) { if (hasInvalidNumber) {
callback(new Error('存在非数字的枚举值')); return Promise.reject(new Error('存在非数字的枚举值'));
return;
} }
const values = dataSpecsList.value.map((item) => item.value); const values = dataSpecsList.value.map((item) => item.value);
if (new Set(values).size !== values.length) { if (new Set(values).size !== values.length) {
callback(new Error('存在重复的枚举值')); return Promise.reject(new Error('存在重复的枚举值'));
return;
} }
callback(); return Promise.resolve();
} }
</script> </script>

View File

@ -10,7 +10,7 @@ import { DICT_TYPE } from '@vben/constants';
import { getDictOptions } from '@vben/hooks'; import { getDictOptions } from '@vben/hooks';
import { useVModel } from '@vueuse/core'; import { useVModel } from '@vueuse/core';
import { FormItem, Input, Select, SelectOption } from 'antdv-next'; import { FormItem, Input, Select } from 'antdv-next';
const props = defineProps<{ modelValue: any }>(); const props = defineProps<{ modelValue: any }>();
const emits = defineEmits(['update:modelValue']); const emits = defineEmits(['update:modelValue']);
@ -36,53 +36,46 @@ function unitChange(unitSpecs: any) {
} }
/** 校验最小值 */ /** 校验最小值 */
function validateMin(_rule: any, _value: any, callback: any) { function validateMin(_rule: any, _value: any) {
const min = Number(dataSpecs.value.min); const min = Number(dataSpecs.value.min);
const max = Number(dataSpecs.value.max); const max = Number(dataSpecs.value.max);
if (Number.isNaN(min)) { if (Number.isNaN(min)) {
callback(new Error('请输入有效的数值')); return Promise.reject(new Error('请输入有效的数值'));
return;
} }
if (!Number.isNaN(max) && min >= max) { if (!Number.isNaN(max) && min >= max) {
callback(new Error('最小值必须小于最大值')); return Promise.reject(new Error('最小值必须小于最大值'));
return;
} }
callback(); return Promise.resolve();
} }
/** 校验最大值 */ /** 校验最大值 */
function validateMax(_rule: any, _value: any, callback: any) { function validateMax(_rule: any, _value: any) {
const min = Number(dataSpecs.value.min); const min = Number(dataSpecs.value.min);
const max = Number(dataSpecs.value.max); const max = Number(dataSpecs.value.max);
if (Number.isNaN(max)) { if (Number.isNaN(max)) {
callback(new Error('请输入有效的数值')); return Promise.reject(new Error('请输入有效的数值'));
return;
} }
if (!Number.isNaN(min) && max <= min) { if (!Number.isNaN(min) && max <= min) {
callback(new Error('最大值必须大于最小值')); return Promise.reject(new Error('最大值必须大于最小值'));
return;
} }
callback(); return Promise.resolve();
} }
/** 校验步长 */ /** 校验步长 */
function validateStep(_rule: any, _value: any, callback: any) { function validateStep(_rule: any, _value: any) {
const step = Number(dataSpecs.value.step); const step = Number(dataSpecs.value.step);
if (Number.isNaN(step)) { if (Number.isNaN(step)) {
callback(new Error('请输入有效的数值')); return Promise.reject(new Error('请输入有效的数值'));
return;
} }
if (step <= 0) { if (step <= 0) {
callback(new Error('步长必须大于 0')); return Promise.reject(new Error('步长必须大于 0'));
return;
} }
const min = Number(dataSpecs.value.min); const min = Number(dataSpecs.value.min);
const max = Number(dataSpecs.value.max); const max = Number(dataSpecs.value.max);
if (!Number.isNaN(min) && !Number.isNaN(max) && step > max - min) { if (!Number.isNaN(min) && !Number.isNaN(max) && step > max - min) {
callback(new Error('步长不能大于最大值与最小值的差值')); return Promise.reject(new Error('步长不能大于最大值与最小值的差值'));
return;
} }
callback(); return Promise.resolve();
} }
</script> </script>
@ -133,15 +126,13 @@ function validateStep(_rule: any, _value: any, callback: any) {
placeholder="请选择单位" placeholder="请选择单位"
class="w-full" class="w-full"
@change="unitChange" @change="unitChange"
> :options="[
<SelectOption ...unitOptions.map((item) => ({
v-for="(item, index) in unitOptions" label: `{{ \`${item.label}-${item.value}\` }}`,
:key="index" value: `${item.label}-${item.value}`,
:value="`${item.label}-${item.value}`" })),
> ]"
{{ `${item.label}-${item.value}` }} />
</SelectOption>
</Select>
</FormItem> </FormItem>
</template> </template>

View File

@ -33,12 +33,11 @@ const structFormRef = ref();
const formData = ref<any>(buildEmptyFormData()); const formData = ref<any>(buildEmptyFormData());
/** 校验结构体属性对象非空 */ /** 校验结构体属性对象非空 */
function validateStructSpecsList(_rule: any, _value: any, callback: any) { function validateStructSpecsList(_rule: any, _value: any) {
if (isEmpty(dataSpecsList.value)) { if (isEmpty(dataSpecsList.value)) {
callback(new Error('请至少添加一个结构体属性对象')); return Promise.reject(new Error('请至少添加一个结构体属性对象'));
return;
} }
callback(); return Promise.resolve();
} }
const [Modal, modalApi] = useVbenModal({ const [Modal, modalApi] = useVbenModal({

View File

@ -14,14 +14,7 @@ import {
import { isEmpty } from '@vben/utils'; import { isEmpty } from '@vben/utils';
import { useVModel } from '@vueuse/core'; import { useVModel } from '@vueuse/core';
import { import { FormItem, Input, Radio, RadioGroup, Select } from 'antdv-next';
FormItem,
Input,
Radio,
RadioGroup,
Select,
SelectOption,
} from 'antdv-next';
import { ThingModelFormRules, validateBoolName } from '#/api/iot/thingmodel'; import { ThingModelFormRules, validateBoolName } from '#/api/iot/thingmodel';
@ -118,16 +111,13 @@ if (!props.isStructDataSpecs && !props.isParams) {
v-model:value="property.dataType" v-model:value="property.dataType"
placeholder="请选择数据类型" placeholder="请选择数据类型"
@change="handleChange" @change="handleChange"
> :options="[
<!-- ARRAY STRUCT 类型数据相互嵌套时最多支持递归嵌套 2 父和子 --> ...dataTypeOptions.map((option) => ({
<SelectOption label: `{{ \`${option.value}(${option.label})\` }}`,
v-for="option in dataTypeOptions" value: option.value,
:key="option.value" })),
:value="option.value" ]"
> />
{{ `${option.value}(${option.label})` }}
</SelectOption>
</Select>
</FormItem> </FormItem>
<!-- 数值型配置 --> <!-- 数值型配置 -->
<ThingModelNumberDataSpecs <ThingModelNumberDataSpecs

View File

@ -204,15 +204,9 @@ async function getAttributeOptions(propertyId: number) {
@blur="handleInputConfirm(index, attribute.id)" @blur="handleInputConfirm(index, attribute.id)"
@change="handleInputConfirm(index, attribute.id)" @change="handleInputConfirm(index, attribute.id)"
@keyup.enter="handleInputConfirm(index, attribute.id)" @keyup.enter="handleInputConfirm(index, attribute.id)"
> :options="attributeOptions"
<SelectOption :field-names="{ label: 'name', value: 'name' }"
v-for="item2 in attributeOptions" />
:key="item2.id"
:value="item2.name"
>
{{ item2.name }}
</SelectOption>
</Select>
<Tag <Tag
v-show="!inputVisible(index)" v-show="!inputVisible(index)"
@click="showInput(index)" @click="showInput(index)"

View File

@ -6,7 +6,7 @@ import { ref, watch } from 'vue';
import { IconifyIcon } from '@vben/icons'; import { IconifyIcon } from '@vben/icons';
import { cloneDeep } from '@vben/utils'; import { cloneDeep } from '@vben/utils';
import { Collapse } from 'antdv-next'; import { Collapse, CollapsePanel } from 'antdv-next';
import draggable from 'vuedraggable'; import draggable from 'vuedraggable';
import { componentConfigs } from './mobile/index'; import { componentConfigs } from './mobile/index';

View File

@ -11,7 +11,6 @@ import {
RadioButton, RadioButton,
RadioGroup, RadioGroup,
Select, Select,
SelectOption,
} from 'antdv-next'; } from 'antdv-next';
import UploadImg from '#/components/upload/image-upload.vue'; import UploadImg from '#/components/upload/image-upload.vue';
@ -29,6 +28,11 @@ defineOptions({ name: 'TabBarProperty' });
const props = defineProps<{ modelValue: TabBarProperty }>(); const props = defineProps<{ modelValue: TabBarProperty }>();
const emit = defineEmits(['update:modelValue']); const emit = defineEmits(['update:modelValue']);
const formData = useVModel(props, 'modelValue', emit); const formData = useVModel(props, 'modelValue', emit);
const themeOptions = THEME_LIST.map((theme) => ({
label: theme.name,
value: theme.id,
raw: theme,
}));
// //
component.property.items = formData.value.items; component.property.items = formData.value.items;
@ -51,18 +55,20 @@ const handleThemeChange = () => {
:wrapper-col="{ span: 18 }" :wrapper-col="{ span: 18 }"
> >
<FormItem label="主题" name="theme"> <FormItem label="主题" name="theme">
<Select v-model:value="formData!.theme" @change="handleThemeChange"> <Select
<SelectOption v-model:value="formData!.theme"
v-for="(theme, index) in THEME_LIST" :options="themeOptions"
:key="index" @change="handleThemeChange"
:label="theme.name" >
:value="theme.id" <template #optionRender="{ option }">
>
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<IconifyIcon :icon="theme.icon" :color="theme.color" /> <IconifyIcon
<span>{{ theme.name }}</span> :icon="option.data.raw.icon"
:color="option.data.raw.color"
/>
<span>{{ option.data.raw.name }}</span>
</div> </div>
</SelectOption> </template>
</Select> </Select>
</FormItem> </FormItem>
<FormItem label="默认颜色"> <FormItem label="默认颜色">

View File

@ -11,7 +11,7 @@ import { IconifyIcon } from '@vben/icons';
import { useAccessStore } from '@vben/stores'; import { useAccessStore } from '@vben/stores';
import { isEmpty } from '@vben/utils'; import { isEmpty } from '@vben/utils';
import { message, RadioGroup } from 'antdv-next'; import { message, RadioButton, RadioGroup } from 'antdv-next';
import { updateDiyPageProperty } from '#/api/mall/promotion/diy/page'; import { updateDiyPageProperty } from '#/api/mall/promotion/diy/page';
import { import {

View File

@ -54,8 +54,17 @@ function handleSearch(value: string) {
} }
/** 处理节点点击:支持点击同一节点取消选中 */ /** 处理节点点击:支持点击同一节点取消选中 */
function handleSelect(_selectedKeys: any[], info: any) { function handleSelect(selectedNodeKeys: any[], info: any) {
const row = info.node.dataRef as MesMdItemTypeApi.ItemType; const selectedKey = selectedNodeKeys[0] ?? info.node?.id ?? info.node?.key;
const row = itemTypeList.value.find(
(item) => String(item.id) === String(selectedKey),
);
if (!row) {
currentNodeId.value = undefined;
selectedKeys.value = [];
emit('nodeClick', undefined);
return;
}
if (currentNodeId.value === row.id) { if (currentNodeId.value === row.id) {
currentNodeId.value = undefined; currentNodeId.value = undefined;
selectedKeys.value = []; selectedKeys.value = [];

Some files were not shown because too many files have changed in this diff Show More