refactor(@vben/web-antdv-next): migrate SelectOption usages to options and optionRender

Replace remaining SelectOption children with Select options across form-create, BPM, IoT, MES, WMS, Mall, and infra pages.

Use optionRender for custom option content and update the sync skill with Select slot migration guidance.
pull/364/head
XuZhiqiang 2026-06-18 23:17:32 +08:00
parent 456a91dfc2
commit c20eb8e1f4
19 changed files with 304 additions and 255 deletions

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

@ -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>
@ -236,23 +248,18 @@ defineExpose({ validate });
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>
@ -292,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

@ -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

@ -19,7 +19,6 @@ import {
Input, Input,
message, message,
Select, Select,
SelectOption,
Tag, Tag,
TextArea, TextArea,
} from 'antdv-next'; } from 'antdv-next';
@ -101,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('消息内容不能为空');
@ -229,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

@ -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

@ -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

@ -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

@ -3,7 +3,7 @@ import type { MesMdUnitMeasureApi } from '#/api/mes/md/unitmeasure';
import { computed, onMounted, ref, watch } from 'vue'; import { computed, onMounted, ref, watch } from 'vue';
import { Select, SelectOption, Tag, Tooltip } from 'antdv-next'; import { Select, Tag, Tooltip } from 'antdv-next';
import { getUnitMeasureSimpleList } from '#/api/mes/md/unitmeasure'; import { getUnitMeasureSimpleList } from '#/api/mes/md/unitmeasure';
@ -32,6 +32,13 @@ const emit = defineEmits<{
const allList = ref<MesMdUnitMeasureApi.UnitMeasure[]>([]); // const allList = ref<MesMdUnitMeasureApi.UnitMeasure[]>([]); //
const filteredList = ref<MesMdUnitMeasureApi.UnitMeasure[]>([]); // const filteredList = ref<MesMdUnitMeasureApi.UnitMeasure[]>([]); //
const selectedItem = ref<MesMdUnitMeasureApi.UnitMeasure>(); // const selectedItem = ref<MesMdUnitMeasureApi.UnitMeasure>(); //
const selectOptions = computed(() =>
filteredList.value.map((item) => ({
label: item.name,
value: item.id,
raw: item,
})),
);
const selectValue = computed({ const selectValue = computed({
// //
@ -44,7 +51,7 @@ const selectValue = computed({
/** 前端按名称和编码过滤计量单位 */ /** 前端按名称和编码过滤计量单位 */
function handleFilter(input: string, option: any) { function handleFilter(input: string, option: any) {
const keyword = input.toLowerCase(); const keyword = input.toLowerCase();
const item = option?.item as MesMdUnitMeasureApi.UnitMeasure | undefined; const item = option?.raw as MesMdUnitMeasureApi.UnitMeasure | undefined;
return Boolean( return Boolean(
item?.name?.toLowerCase().includes(keyword) || item?.name?.toLowerCase().includes(keyword) ||
item?.code?.toLowerCase().includes(keyword), item?.code?.toLowerCase().includes(keyword),
@ -104,19 +111,17 @@ onMounted(async () => {
:placeholder="placeholder" :placeholder="placeholder"
class="w-full" class="w-full"
show-search show-search
:options="selectOptions"
@change="handleChange" @change="handleChange"
> >
<SelectOption <template #optionRender="{ option }">
v-for="item in filteredList"
:key="item.id"
:item="item"
:value="item.id"
>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<span>{{ item.name }}</span> <span>{{ option.data.raw.name }}</span>
<Tag v-if="item.code" color="default">: {{ item.code }}</Tag> <Tag v-if="option.data.raw.code" color="default">
编号: {{ option.data.raw.code }}
</Tag>
</div> </div>
</SelectOption> </template>
</Select> </Select>
</Tooltip> </Tooltip>
</template> </template>

View File

@ -3,7 +3,7 @@ import type { MesMdWorkshopApi } from '#/api/mes/md/workstation/workshop';
import { computed, onMounted, ref, watch } from 'vue'; import { computed, onMounted, ref, watch } from 'vue';
import { Select, SelectOption, Tag, Tooltip } from 'antdv-next'; import { Select, Tag, Tooltip } from 'antdv-next';
import { getWorkshopSimpleList } from '#/api/mes/md/workstation/workshop'; import { getWorkshopSimpleList } from '#/api/mes/md/workstation/workshop';
@ -31,6 +31,13 @@ const emit = defineEmits<{
const allList = ref<MesMdWorkshopApi.Workshop[]>([]); const allList = ref<MesMdWorkshopApi.Workshop[]>([]);
const selectedItem = ref<MesMdWorkshopApi.Workshop>(); const selectedItem = ref<MesMdWorkshopApi.Workshop>();
const selectOptions = computed(() =>
allList.value.map((item) => ({
label: item.name,
value: item.id,
raw: item,
})),
);
const selectValue = computed({ const selectValue = computed({
get: () => props.modelValue, get: () => props.modelValue,
@ -41,7 +48,7 @@ const selectValue = computed({
function handleFilter(input: string, option: any) { function handleFilter(input: string, option: any) {
const keyword = input.toLowerCase(); const keyword = input.toLowerCase();
const item = option?.item as MesMdWorkshopApi.Workshop | undefined; const item = option?.raw as MesMdWorkshopApi.Workshop | undefined;
return Boolean( return Boolean(
item?.name?.toLowerCase().includes(keyword) || item?.name?.toLowerCase().includes(keyword) ||
item?.code?.toLowerCase().includes(keyword), item?.code?.toLowerCase().includes(keyword),
@ -99,19 +106,17 @@ onMounted(async () => {
:placeholder="placeholder" :placeholder="placeholder"
class="w-full" class="w-full"
show-search show-search
:options="selectOptions"
@change="handleChange" @change="handleChange"
> >
<SelectOption <template #optionRender="{ option }">
v-for="item in allList"
:key="item.id"
:item="item"
:value="item.id"
>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<span>{{ item.name }}</span> <span>{{ option.data.raw.name }}</span>
<Tag v-if="item.code" color="default">{{ item.code }}</Tag> <Tag v-if="option.data.raw.code" color="default">
{{ option.data.raw.code }}
</Tag>
</div> </div>
</SelectOption> </template>
</Select> </Select>
</Tooltip> </Tooltip>
</template> </template>

View File

@ -5,7 +5,7 @@ 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 { getAndonConfigList } from '#/api/mes/pro/andon/config'; import { getAndonConfigList } from '#/api/mes/pro/andon/config';
import DictTag from '#/components/dict-tag/dict-tag.vue'; import DictTag from '#/components/dict-tag/dict-tag.vue';
@ -34,6 +34,13 @@ const emit = defineEmits<{
}>(); }>();
const allList = ref<MesProAndonConfigApi.AndonConfig[]>([]); const allList = ref<MesProAndonConfigApi.AndonConfig[]>([]);
const selectOptions = computed(() =>
allList.value.map((item) => ({
label: item.reason,
value: item.id,
raw: item,
})),
);
const selectValue = computed({ const selectValue = computed({
get: () => props.modelValue, get: () => props.modelValue,
@ -45,7 +52,7 @@ const selectValue = computed({
/** 前端过滤:按 reason 模糊匹配 */ /** 前端过滤:按 reason 模糊匹配 */
function handleFilter(input: string, option: any) { function handleFilter(input: string, option: any) {
const keyword = input.toLowerCase(); const keyword = input.toLowerCase();
const item = option?.item as MesProAndonConfigApi.AndonConfig | undefined; const item = option?.raw as MesProAndonConfigApi.AndonConfig | undefined;
return Boolean(item?.reason?.toLowerCase().includes(keyword)); return Boolean(item?.reason?.toLowerCase().includes(keyword));
} }
@ -71,18 +78,17 @@ onMounted(async () => {
:placeholder="placeholder" :placeholder="placeholder"
class="w-full" class="w-full"
show-search show-search
:options="selectOptions"
@change="handleChange" @change="handleChange"
> >
<SelectOption <template #optionRender="{ option }">
v-for="item in allList"
:key="item.id"
:item="item"
:value="item.id"
>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<span>{{ item.reason }}</span> <span>{{ option.data.raw.reason }}</span>
<DictTag :type="DICT_TYPE.MES_PRO_ANDON_LEVEL" :value="item.level" /> <DictTag
:type="DICT_TYPE.MES_PRO_ANDON_LEVEL"
:value="option.data.raw.level"
/>
</div> </div>
</SelectOption> </template>
</Select> </Select>
</template> </template>

View File

@ -3,7 +3,7 @@ import type { MesProProcessApi } from '#/api/mes/pro/process';
import { computed, onMounted, ref, watch } from 'vue'; import { computed, onMounted, ref, watch } from 'vue';
import { Select, SelectOption, Tag, Tooltip } from 'antdv-next'; import { Select, Tag, Tooltip } from 'antdv-next';
import { getProcessSimpleList } from '#/api/mes/pro/process'; import { getProcessSimpleList } from '#/api/mes/pro/process';
@ -31,6 +31,13 @@ const emit = defineEmits<{
const allList = ref<MesProProcessApi.Process[]>([]); const allList = ref<MesProProcessApi.Process[]>([]);
const selectedItem = ref<MesProProcessApi.Process>(); const selectedItem = ref<MesProProcessApi.Process>();
const selectOptions = computed(() =>
allList.value.map((item) => ({
label: item.name,
value: item.id,
raw: item,
})),
);
const selectValue = computed({ const selectValue = computed({
get: () => props.modelValue, get: () => props.modelValue,
@ -42,7 +49,7 @@ const selectValue = computed({
/** 前端过滤:按工序名称或编码模糊匹配 */ /** 前端过滤:按工序名称或编码模糊匹配 */
function handleFilter(input: string, option: any) { function handleFilter(input: string, option: any) {
const keyword = input.toLowerCase(); const keyword = input.toLowerCase();
const item = option?.item as MesProProcessApi.Process | undefined; const item = option?.raw as MesProProcessApi.Process | undefined;
return Boolean( return Boolean(
item?.name?.toLowerCase().includes(keyword) || item?.name?.toLowerCase().includes(keyword) ||
item?.code?.toLowerCase().includes(keyword), item?.code?.toLowerCase().includes(keyword),
@ -96,19 +103,17 @@ onMounted(async () => {
:placeholder="placeholder" :placeholder="placeholder"
class="w-full" class="w-full"
show-search show-search
:options="selectOptions"
@change="handleChange" @change="handleChange"
> >
<SelectOption <template #optionRender="{ option }">
v-for="item in allList"
:key="item.id"
:item="item"
:value="item.id"
>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<span>{{ item.name }}</span> <span>{{ option.data.raw.name }}</span>
<Tag v-if="item.code" color="default">{{ item.code }}</Tag> <Tag v-if="option.data.raw.code" color="default">
{{ option.data.raw.code }}
</Tag>
</div> </div>
</SelectOption> </template>
</Select> </Select>
</Tooltip> </Tooltip>
</template> </template>

View File

@ -3,7 +3,7 @@ import type { MesWmWarehouseAreaApi } from '#/api/mes/wm/warehouse/area';
import { computed, ref, useAttrs, watch, watchEffect } from 'vue'; import { computed, ref, useAttrs, watch, watchEffect } from 'vue';
import { Select, SelectOption, Tag, Tooltip } from 'antdv-next'; import { Select, Tag, Tooltip } from 'antdv-next';
import { getWarehouseAreaSimpleList } from '#/api/mes/wm/warehouse/area'; import { getWarehouseAreaSimpleList } from '#/api/mes/wm/warehouse/area';
@ -34,6 +34,13 @@ const emit = defineEmits<{
const attrs = useAttrs(); const attrs = useAttrs();
const allList = ref<MesWmWarehouseAreaApi.WarehouseArea[]>([]); const allList = ref<MesWmWarehouseAreaApi.WarehouseArea[]>([]);
const selectedItem = ref<MesWmWarehouseAreaApi.WarehouseArea>(); const selectedItem = ref<MesWmWarehouseAreaApi.WarehouseArea>();
const selectOptions = computed(() =>
allList.value.map((item) => ({
label: item.name,
value: item.id,
raw: item,
})),
);
const selectValue = computed({ const selectValue = computed({
get: () => props.modelValue, get: () => props.modelValue,
@ -49,7 +56,7 @@ function handleChange(val: any) {
/** 前端过滤name + code */ /** 前端过滤name + code */
function filterOption(input: string, option: any) { function filterOption(input: string, option: any) {
const item = allList.value.find((o) => o.id === option.value); const item = option?.raw as MesWmWarehouseAreaApi.WarehouseArea | undefined;
if (!item) { if (!item) {
return false; return false;
} }
@ -101,14 +108,17 @@ watchEffect(async () => {
:filter-option="filterOption" :filter-option="filterOption"
:placeholder="placeholder" :placeholder="placeholder"
show-search show-search
:options="selectOptions"
@change="handleChange" @change="handleChange"
> >
<SelectOption v-for="item in allList" :key="item.id" :value="item.id"> <template #optionRender="{ option }">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<span>{{ item.name }}</span> <span>{{ option.data.raw.name }}</span>
<Tag v-if="item.code" color="blue">: {{ item.code }}</Tag> <Tag v-if="option.data.raw.code" color="blue">
编号: {{ option.data.raw.code }}
</Tag>
</div> </div>
</SelectOption> </template>
</Select> </Select>
</Tooltip> </Tooltip>
</template> </template>

View File

@ -3,7 +3,7 @@ import type { MesWmWarehouseLocationApi } from '#/api/mes/wm/warehouse/location'
import { computed, ref, useAttrs, watch, watchEffect } from 'vue'; import { computed, ref, useAttrs, watch, watchEffect } from 'vue';
import { Select, SelectOption, Tag, Tooltip } from 'antdv-next'; import { Select, Tag, Tooltip } from 'antdv-next';
import { getWarehouseLocationSimpleList } from '#/api/mes/wm/warehouse/location'; import { getWarehouseLocationSimpleList } from '#/api/mes/wm/warehouse/location';
@ -34,6 +34,13 @@ const emit = defineEmits<{
const attrs = useAttrs(); const attrs = useAttrs();
const allList = ref<MesWmWarehouseLocationApi.WarehouseLocation[]>([]); const allList = ref<MesWmWarehouseLocationApi.WarehouseLocation[]>([]);
const selectedItem = ref<MesWmWarehouseLocationApi.WarehouseLocation>(); const selectedItem = ref<MesWmWarehouseLocationApi.WarehouseLocation>();
const selectOptions = computed(() =>
allList.value.map((item) => ({
label: item.name,
value: item.id,
raw: item,
})),
);
const selectValue = computed({ const selectValue = computed({
get: () => props.modelValue, get: () => props.modelValue,
@ -49,7 +56,9 @@ function handleChange(val: any) {
/** 前端过滤name + code */ /** 前端过滤name + code */
function filterOption(input: string, option: any) { function filterOption(input: string, option: any) {
const item = allList.value.find((o) => o.id === option.value); const item = option?.raw as
| MesWmWarehouseLocationApi.WarehouseLocation
| undefined;
if (!item) { if (!item) {
return false; return false;
} }
@ -100,14 +109,17 @@ watchEffect(async () => {
:filter-option="filterOption" :filter-option="filterOption"
:placeholder="placeholder" :placeholder="placeholder"
show-search show-search
:options="selectOptions"
@change="handleChange" @change="handleChange"
> >
<SelectOption v-for="item in allList" :key="item.id" :value="item.id"> <template #optionRender="{ option }">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<span>{{ item.name }}</span> <span>{{ option.data.raw.name }}</span>
<Tag v-if="item.code" color="blue">: {{ item.code }}</Tag> <Tag v-if="option.data.raw.code" color="blue">
编号: {{ option.data.raw.code }}
</Tag>
</div> </div>
</SelectOption> </template>
</Select> </Select>
</Tooltip> </Tooltip>
</template> </template>

View File

@ -3,7 +3,7 @@ import type { MesWmWarehouseApi } from '#/api/mes/wm/warehouse';
import { computed, onMounted, ref, useAttrs, watch } from 'vue'; import { computed, onMounted, ref, useAttrs, watch } from 'vue';
import { Select, SelectOption, Tag, Tooltip } from 'antdv-next'; import { Select, Tag, Tooltip } from 'antdv-next';
import { getWarehouseSimpleList } from '#/api/mes/wm/warehouse'; import { getWarehouseSimpleList } from '#/api/mes/wm/warehouse';
@ -32,6 +32,13 @@ const emit = defineEmits<{
const attrs = useAttrs(); const attrs = useAttrs();
const allList = ref<MesWmWarehouseApi.Warehouse[]>([]); const allList = ref<MesWmWarehouseApi.Warehouse[]>([]);
const selectedItem = ref<MesWmWarehouseApi.Warehouse>(); const selectedItem = ref<MesWmWarehouseApi.Warehouse>();
const selectOptions = computed(() =>
allList.value.map((item) => ({
label: item.name,
value: item.id,
raw: item,
})),
);
const selectValue = computed({ const selectValue = computed({
get: () => props.modelValue, get: () => props.modelValue,
@ -47,7 +54,7 @@ function handleChange(val: any) {
/** 前端过滤name + code */ /** 前端过滤name + code */
function filterOption(input: string, option: any) { function filterOption(input: string, option: any) {
const item = allList.value.find((o) => o.id === option.value); const item = option?.raw as MesWmWarehouseApi.Warehouse | undefined;
if (!item) { if (!item) {
return false; return false;
} }
@ -97,14 +104,17 @@ onMounted(async () => {
:filter-option="filterOption" :filter-option="filterOption"
:placeholder="placeholder" :placeholder="placeholder"
show-search show-search
:options="selectOptions"
@change="handleChange" @change="handleChange"
> >
<SelectOption v-for="item in allList" :key="item.id" :value="item.id"> <template #optionRender="{ option }">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<span>{{ item.name }}</span> <span>{{ option.data.raw.name }}</span>
<Tag v-if="item.code" color="blue">: {{ item.code }}</Tag> <Tag v-if="option.data.raw.code" color="blue">
编号: {{ option.data.raw.code }}
</Tag>
</div> </div>
</SelectOption> </template>
</Select> </Select>
</Tooltip> </Tooltip>
</template> </template>