refactor: 移除验证码组件并添加自定义表单组件支持
- 移除未使用的 Verify 验证码组件及其相关文件 - 在组件适配器中添加自定义表单组件的注册支持 - 扩展 ComponentType 类型以支持自定义组件类型pull/48/head
parent
0fed947230
commit
23bcf4f61b
|
@ -5,6 +5,8 @@
|
||||||
|
|
||||||
import type { BaseFormComponentType } from '@vben/common-ui';
|
import type { BaseFormComponentType } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import type { CustomComponentType } from '#/components/form/types';
|
||||||
|
|
||||||
import type { Component, SetupContext } from 'vue';
|
import type { Component, SetupContext } from 'vue';
|
||||||
import { h } from 'vue';
|
import { h } from 'vue';
|
||||||
|
|
||||||
|
@ -36,6 +38,8 @@ import {
|
||||||
Upload,
|
Upload,
|
||||||
} from 'ant-design-vue';
|
} from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { registerComponent as registerCustomFormComponent } from '#/components/form/component-map';
|
||||||
|
|
||||||
const withDefaultPlaceholder = <T extends Component>(
|
const withDefaultPlaceholder = <T extends Component>(
|
||||||
component: T,
|
component: T,
|
||||||
type: 'input' | 'select',
|
type: 'input' | 'select',
|
||||||
|
@ -70,7 +74,8 @@ export type ComponentType =
|
||||||
| 'TimePicker'
|
| 'TimePicker'
|
||||||
| 'TreeSelect'
|
| 'TreeSelect'
|
||||||
| 'Upload'
|
| 'Upload'
|
||||||
| BaseFormComponentType;
|
| BaseFormComponentType
|
||||||
|
| CustomComponentType;
|
||||||
|
|
||||||
async function initComponentAdapter() {
|
async function initComponentAdapter() {
|
||||||
const components: Partial<Record<ComponentType, Component>> = {
|
const components: Partial<Record<ComponentType, Component>> = {
|
||||||
|
@ -108,6 +113,9 @@ async function initComponentAdapter() {
|
||||||
Upload,
|
Upload,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 注册自定义组件
|
||||||
|
registerCustomFormComponent(components);
|
||||||
|
|
||||||
// 将组件注册到全局共享状态中
|
// 将组件注册到全局共享状态中
|
||||||
globalShareState.setComponents(components);
|
globalShareState.setComponents(components);
|
||||||
|
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
export { default as Verify } from './src/Verify.vue';
|
|
|
@ -1,162 +0,0 @@
|
||||||
<script type="text/babel">
|
|
||||||
/**
|
|
||||||
* Verify 验证码组件
|
|
||||||
* @description 分发验证码使用
|
|
||||||
*/
|
|
||||||
import { computed, ref, toRefs, watchEffect } from 'vue';
|
|
||||||
|
|
||||||
// import { $t } from '@vben/locales';
|
|
||||||
|
|
||||||
import VerifyPoints from './Verify/VerifyPoints.vue';
|
|
||||||
import VerifySlide from './Verify/VerifySlide.vue';
|
|
||||||
|
|
||||||
import './style/verify.css';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'Vue3Verify',
|
|
||||||
components: {
|
|
||||||
VerifyPoints,
|
|
||||||
VerifySlide,
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
arith: {
|
|
||||||
default: 0,
|
|
||||||
type: Number,
|
|
||||||
},
|
|
||||||
barSize: {
|
|
||||||
default: () => {
|
|
||||||
return {
|
|
||||||
height: '40px',
|
|
||||||
width: '310px',
|
|
||||||
};
|
|
||||||
},
|
|
||||||
type: Object,
|
|
||||||
},
|
|
||||||
blockSize: {
|
|
||||||
default() {
|
|
||||||
return {
|
|
||||||
height: '50px',
|
|
||||||
width: '50px',
|
|
||||||
};
|
|
||||||
},
|
|
||||||
type: Object,
|
|
||||||
},
|
|
||||||
captchaType: {
|
|
||||||
required: true,
|
|
||||||
type: String,
|
|
||||||
},
|
|
||||||
explain: {
|
|
||||||
default: '',
|
|
||||||
type: String,
|
|
||||||
},
|
|
||||||
figure: {
|
|
||||||
default: 0,
|
|
||||||
type: Number,
|
|
||||||
},
|
|
||||||
imgSize: {
|
|
||||||
default() {
|
|
||||||
return {
|
|
||||||
height: '155px',
|
|
||||||
width: '310px',
|
|
||||||
};
|
|
||||||
},
|
|
||||||
type: Object,
|
|
||||||
},
|
|
||||||
mode: {
|
|
||||||
default: 'pop',
|
|
||||||
type: String,
|
|
||||||
},
|
|
||||||
vSpace: {
|
|
||||||
default: 5,
|
|
||||||
type: Number,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
setup(props) {
|
|
||||||
const { captchaType, mode } = toRefs(props);
|
|
||||||
const clickShow = ref(false);
|
|
||||||
const verifyType = ref(undefined);
|
|
||||||
const componentType = ref(undefined);
|
|
||||||
|
|
||||||
const instance = ref({});
|
|
||||||
|
|
||||||
const showBox = computed(() => {
|
|
||||||
return mode.value === 'pop' ? clickShow.value : true;
|
|
||||||
});
|
|
||||||
/**
|
|
||||||
* refresh
|
|
||||||
* @description 刷新
|
|
||||||
*/
|
|
||||||
const refresh = () => {
|
|
||||||
if (instance.value.refresh) instance.value.refresh();
|
|
||||||
};
|
|
||||||
const closeBox = () => {
|
|
||||||
clickShow.value = false;
|
|
||||||
refresh();
|
|
||||||
};
|
|
||||||
const show = () => {
|
|
||||||
if (mode.value === 'pop') clickShow.value = true;
|
|
||||||
};
|
|
||||||
watchEffect(() => {
|
|
||||||
switch (captchaType.value) {
|
|
||||||
case 'blockPuzzle': {
|
|
||||||
verifyType.value = '2';
|
|
||||||
componentType.value = 'VerifySlide';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'clickWord': {
|
|
||||||
verifyType.value = '';
|
|
||||||
componentType.value = 'VerifyPoints';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
clickShow,
|
|
||||||
closeBox,
|
|
||||||
componentType,
|
|
||||||
instance,
|
|
||||||
show,
|
|
||||||
showBox,
|
|
||||||
verifyType,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div v-show="showBox" :class="mode === 'pop' ? 'mask' : ''">
|
|
||||||
<div
|
|
||||||
:class="mode === 'pop' ? 'verifybox' : ''"
|
|
||||||
:style="{ 'max-width': `${parseInt(imgSize.width) + 20}px` }"
|
|
||||||
>
|
|
||||||
<div v-if="mode === 'pop'" class="verifybox-top">
|
|
||||||
请完成安全验证
|
|
||||||
<span class="verifybox-close" @click="closeBox">
|
|
||||||
<i class="iconfont icon-close"></i>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
:style="{ padding: mode === 'pop' ? '10px' : '0' }"
|
|
||||||
class="verifybox-bottom"
|
|
||||||
>
|
|
||||||
<!-- 验证码容器 -->
|
|
||||||
<component
|
|
||||||
:is="componentType"
|
|
||||||
v-if="componentType"
|
|
||||||
ref="instance"
|
|
||||||
:arith="arith"
|
|
||||||
:bar-size="barSize"
|
|
||||||
:block-size="blockSize"
|
|
||||||
:captcha-type="captchaType"
|
|
||||||
:explain="explain"
|
|
||||||
:figure="figure"
|
|
||||||
:img-size="imgSize"
|
|
||||||
:mode="mode"
|
|
||||||
:type="verifyType"
|
|
||||||
:v-space="vSpace"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { DescItem } from './types';
|
||||||
|
|
||||||
|
import type { PropType } from 'vue';
|
||||||
|
|
||||||
|
import { Descriptions, DescriptionsItem } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { componentMap } from '#/components/view/component-map';
|
||||||
|
|
||||||
|
defineProps({
|
||||||
|
title: { type: String, default: '' },
|
||||||
|
bordered: { type: Boolean, default: true },
|
||||||
|
size: {
|
||||||
|
type: String as PropType<'default' | 'middle' | 'small'>,
|
||||||
|
default: undefined,
|
||||||
|
},
|
||||||
|
column: {
|
||||||
|
type: [Number, Object],
|
||||||
|
default: () => {
|
||||||
|
// return { xxl: 4, xl: 3, lg: 3, md: 3, sm: 2, xs: 1 };
|
||||||
|
return 12;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
labelStyle: {
|
||||||
|
type: Object,
|
||||||
|
default() {
|
||||||
|
return {
|
||||||
|
width: '120px',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
contentStyle: {
|
||||||
|
type: Object,
|
||||||
|
default() {
|
||||||
|
return {
|
||||||
|
width: '0px',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
type: Array as PropType<DescItem[]>,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
data: { type: Object, default: undefined },
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<Descriptions
|
||||||
|
:bordered="bordered"
|
||||||
|
:column="column"
|
||||||
|
:content-style="contentStyle"
|
||||||
|
:label-style="labelStyle"
|
||||||
|
:size="size"
|
||||||
|
:title="title ? title : undefined"
|
||||||
|
>
|
||||||
|
<template v-for="item in schema" :key="item.field">
|
||||||
|
<DescriptionsItem :label="item.label" :span="item.span">
|
||||||
|
<component
|
||||||
|
:is="(componentMap as Map<String, any>).get(item.component)"
|
||||||
|
v-if="(componentMap as Map<String, any>).has(item.component)"
|
||||||
|
:value="data?.[item.field]"
|
||||||
|
v-bind="{ ...item.componentProps }"
|
||||||
|
/>
|
||||||
|
<component
|
||||||
|
:is="item.render(data?.[item.field], data)"
|
||||||
|
v-else-if="
|
||||||
|
!(componentMap as Map<String, any>).has(item.component) &&
|
||||||
|
item.render
|
||||||
|
"
|
||||||
|
:value="data?.[item.field]"
|
||||||
|
v-bind="{ ...item.componentProps }"
|
||||||
|
/>
|
||||||
|
<template v-else>{{ data?.[item.field] }}</template>
|
||||||
|
</DescriptionsItem>
|
||||||
|
</template>
|
||||||
|
</Descriptions>
|
||||||
|
</template>
|
|
@ -0,0 +1,2 @@
|
||||||
|
export { default as Description } from './description.vue';
|
||||||
|
export type * from './types';
|
|
@ -0,0 +1,54 @@
|
||||||
|
import type { CollapseContainerOptions } from '@/components/Container';
|
||||||
|
import type { DescriptionsProps } from 'ant-design-vue/es/descriptions';
|
||||||
|
|
||||||
|
import type { CSSProperties, VNode } from 'vue';
|
||||||
|
|
||||||
|
export interface DescItem {
|
||||||
|
labelMinWidth?: number;
|
||||||
|
contentMinWidth?: number;
|
||||||
|
labelStyle?: CSSProperties;
|
||||||
|
field: string;
|
||||||
|
label: JSX.Element | string | VNode;
|
||||||
|
// Merge column
|
||||||
|
span?: number;
|
||||||
|
show?: (...arg: any) => boolean;
|
||||||
|
// render
|
||||||
|
render?: (
|
||||||
|
val: any,
|
||||||
|
data: Recordable,
|
||||||
|
) => Element | JSX.Element | number | string | undefined | VNode;
|
||||||
|
component: string;
|
||||||
|
componentProps?: any;
|
||||||
|
children?: DescItem[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DescriptionProps extends DescriptionsProps {
|
||||||
|
// Whether to include the collapse component
|
||||||
|
useCollapse?: boolean;
|
||||||
|
/**
|
||||||
|
* item configuration
|
||||||
|
* @type DescItem
|
||||||
|
*/
|
||||||
|
schema: DescItem[];
|
||||||
|
/**
|
||||||
|
* 数据
|
||||||
|
* @type object
|
||||||
|
*/
|
||||||
|
data: Recordable;
|
||||||
|
/**
|
||||||
|
* Built-in CollapseContainer component configuration
|
||||||
|
* @type CollapseContainerOptions
|
||||||
|
*/
|
||||||
|
collapseOptions?: CollapseContainerOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DescInstance {
|
||||||
|
setDescProps(descProps: Partial<DescriptionProps>): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Register = (descInstance: DescInstance) => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description:
|
||||||
|
*/
|
||||||
|
export type UseDescReturnType = [Register, DescInstance];
|
|
@ -0,0 +1,37 @@
|
||||||
|
import type { CustomComponentType } from './types';
|
||||||
|
|
||||||
|
import type { Component } from 'vue';
|
||||||
|
|
||||||
|
import { kebabToCamelCase } from '@vben/utils';
|
||||||
|
|
||||||
|
const componentMap = new Map<CustomComponentType | string, Component>();
|
||||||
|
// import.meta.glob() 直接引入所有的模块 Vite 独有的功能
|
||||||
|
const modules = import.meta.glob('./components/**/*.vue', { eager: true });
|
||||||
|
// 加入到路由集合中
|
||||||
|
Object.keys(modules).forEach((key) => {
|
||||||
|
if (!key.includes('-ignore')) {
|
||||||
|
const mod = (modules as any)[key].default || {};
|
||||||
|
// ./components/ApiDict.vue
|
||||||
|
// 获取ApiDict
|
||||||
|
const compName = key.replace('./components/', '').replace('.vue', '');
|
||||||
|
componentMap.set(kebabToCamelCase(compName), mod);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export function add(compName: string, component: Component) {
|
||||||
|
componentMap.set(compName, component);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function del(compName: string) {
|
||||||
|
componentMap.delete(compName);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 注册组件
|
||||||
|
* @param components
|
||||||
|
*/
|
||||||
|
export const registerComponent = (components: any) => {
|
||||||
|
componentMap.forEach((value, key) => {
|
||||||
|
components[key] = value as Component;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
export { componentMap };
|
|
@ -0,0 +1,137 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { CheckboxValueType } from 'ant-design-vue/es/checkbox/interface';
|
||||||
|
|
||||||
|
import { computed, type PropType, ref, watch, watchEffect } from 'vue';
|
||||||
|
|
||||||
|
import { getNestedValue, isFunction } from '@vben/utils';
|
||||||
|
|
||||||
|
import { objectOmit, useVModel } from '@vueuse/core';
|
||||||
|
import { CheckboxGroup, Spin } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
|
type OptionsItem = { disabled?: boolean; label: string; value: string };
|
||||||
|
const props = defineProps({
|
||||||
|
value: {
|
||||||
|
type: [Array] as PropType<CheckboxValueType[]>,
|
||||||
|
default: undefined,
|
||||||
|
},
|
||||||
|
numberToString: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
api: {
|
||||||
|
type: [Function, String] as PropType<
|
||||||
|
(arg?: any) => Promise<OptionsItem[]> | String
|
||||||
|
>,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
// api params
|
||||||
|
params: {
|
||||||
|
type: Object as PropType<any>,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
requestMethod: {
|
||||||
|
// 请求方法
|
||||||
|
type: String,
|
||||||
|
default: 'post',
|
||||||
|
},
|
||||||
|
// support xxx.xxx.xx
|
||||||
|
resultField: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
labelField: {
|
||||||
|
type: String,
|
||||||
|
default: 'label',
|
||||||
|
},
|
||||||
|
valueField: {
|
||||||
|
type: String,
|
||||||
|
default: 'value',
|
||||||
|
},
|
||||||
|
immediate: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const emits = defineEmits(['update:value', 'optionsChange']);
|
||||||
|
const mValue = useVModel(props, 'value', emits, {
|
||||||
|
defaultValue: props.value,
|
||||||
|
passive: true,
|
||||||
|
});
|
||||||
|
const options = ref<OptionsItem[]>([]);
|
||||||
|
const loading = ref(false);
|
||||||
|
const isFirstLoad = ref(true);
|
||||||
|
const getOptions = computed(() => {
|
||||||
|
const { labelField, valueField, numberToString } = props;
|
||||||
|
const res: OptionsItem[] = [];
|
||||||
|
options.value.forEach((item: any) => {
|
||||||
|
const value = item[valueField];
|
||||||
|
res.push({
|
||||||
|
...objectOmit(item, [labelField, valueField]),
|
||||||
|
label: item[labelField],
|
||||||
|
value: numberToString ? `${value}` : value,
|
||||||
|
disabled: item.disabled || false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return res;
|
||||||
|
});
|
||||||
|
|
||||||
|
const fetch = async () => {
|
||||||
|
const api: any =
|
||||||
|
typeof props.api === 'string' && props.api
|
||||||
|
? (params: any) => {
|
||||||
|
return (requestClient as any)[props.requestMethod](
|
||||||
|
props.api as any,
|
||||||
|
params,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
: props.api;
|
||||||
|
if (!api || !isFunction(api)) return;
|
||||||
|
try {
|
||||||
|
loading.value = true;
|
||||||
|
const params =
|
||||||
|
props.requestMethod === 'get' ? { params: props.params } : props.params;
|
||||||
|
const res = await api(params);
|
||||||
|
if (Array.isArray(res)) {
|
||||||
|
options.value = res;
|
||||||
|
emits('optionsChange', options.value);
|
||||||
|
} else {
|
||||||
|
options.value = props.resultField
|
||||||
|
? getNestedValue(res, props.resultField)
|
||||||
|
: [];
|
||||||
|
emits('optionsChange', options.value);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(error);
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
watchEffect(() => {
|
||||||
|
props.immediate && fetch();
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.params,
|
||||||
|
() => {
|
||||||
|
!isFirstLoad.value && fetch();
|
||||||
|
},
|
||||||
|
{ deep: true },
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Spin :spinning="loading" style="margin-left: 20px">
|
||||||
|
<CheckboxGroup
|
||||||
|
v-bind="$attrs"
|
||||||
|
v-model:value="mValue"
|
||||||
|
:options="getOptions"
|
||||||
|
class="w-full"
|
||||||
|
>
|
||||||
|
<template v-for="item in Object.keys($slots)" #[item]="data">
|
||||||
|
<slot :name="item" v-bind="data || {}"></slot>
|
||||||
|
</template>
|
||||||
|
</CheckboxGroup>
|
||||||
|
</Spin>
|
||||||
|
</template>
|
|
@ -0,0 +1,53 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, type PropType } from 'vue';
|
||||||
|
|
||||||
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
|
import { ApiCheckboxGroup, ApiRadioGroup, ApiSelect } from '..';
|
||||||
|
|
||||||
|
type OptionsItem = { disabled?: boolean; label: string; value: string };
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
renderType: {
|
||||||
|
type: String as PropType<'CheckboxGroup' | 'RadioGroup' | 'Select'>,
|
||||||
|
default: 'Select',
|
||||||
|
},
|
||||||
|
api: {
|
||||||
|
type: [Function, String] as PropType<
|
||||||
|
(arg?: any) => Promise<OptionsItem[]> | String
|
||||||
|
>,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
requestMethod: {
|
||||||
|
// 请求方法
|
||||||
|
type: String,
|
||||||
|
default: 'post',
|
||||||
|
},
|
||||||
|
// api params
|
||||||
|
params: {
|
||||||
|
type: Object as PropType<any>,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
code: {
|
||||||
|
type: String,
|
||||||
|
default: undefined,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const DictComponent = computed(() => {
|
||||||
|
if (props.renderType === 'RadioGroup') {
|
||||||
|
return ApiRadioGroup;
|
||||||
|
} else if (props.renderType === 'CheckboxGroup') {
|
||||||
|
return ApiCheckboxGroup;
|
||||||
|
}
|
||||||
|
return ApiSelect;
|
||||||
|
});
|
||||||
|
const fetch = () => {
|
||||||
|
return requestClient.post('/sys/dict/getByDictType', {
|
||||||
|
dictType: props.code,
|
||||||
|
...props.params,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<DictComponent :api="props.code ? fetch : props.api" />
|
||||||
|
</template>
|
|
@ -0,0 +1,143 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { SelectValue } from 'ant-design-vue/es/select';
|
||||||
|
|
||||||
|
import { computed, type PropType, ref, watch, watchEffect } from 'vue';
|
||||||
|
|
||||||
|
import { getNestedValue, isFunction } from '@vben/utils';
|
||||||
|
|
||||||
|
import { objectOmit, useVModel } from '@vueuse/core';
|
||||||
|
import { RadioGroup, Spin } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
|
type OptionsItem = { disabled?: boolean; label: string; value: string };
|
||||||
|
const props = defineProps({
|
||||||
|
value: {
|
||||||
|
type: [String, Number, Array] as PropType<SelectValue>,
|
||||||
|
default: undefined,
|
||||||
|
},
|
||||||
|
numberToString: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
api: {
|
||||||
|
type: [Function, String] as PropType<
|
||||||
|
(arg?: any) => Promise<OptionsItem[]> | String
|
||||||
|
>,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
requestMethod: {
|
||||||
|
// 请求方法
|
||||||
|
type: String,
|
||||||
|
default: 'post',
|
||||||
|
},
|
||||||
|
// api params
|
||||||
|
params: {
|
||||||
|
type: Object as PropType<any>,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
// support xxx.xxx.xx
|
||||||
|
resultField: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
labelField: {
|
||||||
|
type: String,
|
||||||
|
default: 'label',
|
||||||
|
},
|
||||||
|
valueField: {
|
||||||
|
type: String,
|
||||||
|
default: 'value',
|
||||||
|
},
|
||||||
|
immediate: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
isBtn: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const emits = defineEmits(['update:value', 'optionsChange']);
|
||||||
|
const mValue = useVModel(props, 'value', emits, {
|
||||||
|
defaultValue: props.value,
|
||||||
|
passive: true,
|
||||||
|
});
|
||||||
|
const options = ref<OptionsItem[]>([]);
|
||||||
|
const loading = ref(false);
|
||||||
|
const isFirstLoad = ref(true);
|
||||||
|
const getOptions = computed(() => {
|
||||||
|
const { labelField, valueField, numberToString } = props;
|
||||||
|
const res: OptionsItem[] = [];
|
||||||
|
options.value.forEach((item: any) => {
|
||||||
|
const value = item[valueField];
|
||||||
|
res.push({
|
||||||
|
...objectOmit(item, [labelField, valueField]),
|
||||||
|
label: item[labelField],
|
||||||
|
value: numberToString ? `${value}` : value,
|
||||||
|
disabled: item.disabled || false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return res;
|
||||||
|
});
|
||||||
|
|
||||||
|
const fetch = async () => {
|
||||||
|
const api: any =
|
||||||
|
typeof props.api === 'string' && props.api
|
||||||
|
? (params: any) => {
|
||||||
|
return (requestClient as any)[props.requestMethod](
|
||||||
|
props.api as any,
|
||||||
|
params,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
: props.api;
|
||||||
|
if (!api || !isFunction(api)) return;
|
||||||
|
try {
|
||||||
|
loading.value = true;
|
||||||
|
const params =
|
||||||
|
props.requestMethod === 'get' ? { params: props.params } : props.params;
|
||||||
|
const res = await api(params);
|
||||||
|
if (Array.isArray(res)) {
|
||||||
|
options.value = res;
|
||||||
|
emits('optionsChange', options.value);
|
||||||
|
} else {
|
||||||
|
options.value = props.resultField
|
||||||
|
? getNestedValue(res, props.resultField)
|
||||||
|
: [];
|
||||||
|
emits('optionsChange', options.value);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(error);
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
watchEffect(() => {
|
||||||
|
props.immediate && fetch();
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.params,
|
||||||
|
() => {
|
||||||
|
!isFirstLoad.value && fetch();
|
||||||
|
},
|
||||||
|
{ deep: true },
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Spin :spinning="loading" style="margin-left: 20px">
|
||||||
|
<RadioGroup
|
||||||
|
v-bind="$attrs"
|
||||||
|
v-model:value="mValue"
|
||||||
|
:button-style="isBtn ? 'solid' : 'outline'"
|
||||||
|
:option-type="isBtn ? 'button' : 'default'"
|
||||||
|
:options="getOptions"
|
||||||
|
class="w-full"
|
||||||
|
>
|
||||||
|
<template v-for="item in Object.keys($slots)" #[item]="data">
|
||||||
|
<slot :name="item" v-bind="data || {}"></slot>
|
||||||
|
</template>
|
||||||
|
</RadioGroup>
|
||||||
|
</Spin>
|
||||||
|
</template>
|
|
@ -0,0 +1,150 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { SelectValue } from 'ant-design-vue/es/select';
|
||||||
|
|
||||||
|
import { computed, type PropType, ref, watch, watchEffect } from 'vue';
|
||||||
|
|
||||||
|
import { IconifyIcon } from '@vben/icons';
|
||||||
|
import { getNestedValue, isFunction } from '@vben/utils';
|
||||||
|
|
||||||
|
import { objectOmit, useVModel } from '@vueuse/core';
|
||||||
|
import { Select } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
|
type OptionsItem = { disabled?: boolean; label: string; value: string };
|
||||||
|
const props = defineProps({
|
||||||
|
value: {
|
||||||
|
type: [String, Number, Array] as PropType<SelectValue>,
|
||||||
|
default: undefined,
|
||||||
|
},
|
||||||
|
numberToString: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
api: {
|
||||||
|
type: [Function, String] as PropType<
|
||||||
|
(arg?: any) => Promise<OptionsItem[]> | String
|
||||||
|
>,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
requestMethod: {
|
||||||
|
type: String,
|
||||||
|
default: 'post',
|
||||||
|
},
|
||||||
|
// api params
|
||||||
|
params: {
|
||||||
|
type: Object as PropType<any>,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
// support xxx.xxx.xx
|
||||||
|
resultField: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
labelField: {
|
||||||
|
type: String,
|
||||||
|
default: 'label',
|
||||||
|
},
|
||||||
|
valueField: {
|
||||||
|
type: String,
|
||||||
|
default: 'value',
|
||||||
|
},
|
||||||
|
immediate: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const emits = defineEmits(['update:value', 'optionsChange']);
|
||||||
|
const mValue = useVModel(props, 'value', emits, {
|
||||||
|
defaultValue: props.value,
|
||||||
|
passive: true,
|
||||||
|
});
|
||||||
|
const options = ref<OptionsItem[]>([]);
|
||||||
|
const loading = ref(false);
|
||||||
|
const isFirstLoad = ref(true);
|
||||||
|
const getOptions = computed(() => {
|
||||||
|
const { labelField, valueField, numberToString } = props;
|
||||||
|
const res: OptionsItem[] = [];
|
||||||
|
options.value.forEach((item: any) => {
|
||||||
|
const value = item[valueField];
|
||||||
|
res.push({
|
||||||
|
...objectOmit(item, [labelField, valueField]),
|
||||||
|
label: item[labelField],
|
||||||
|
value: numberToString ? `${value}` : value,
|
||||||
|
disabled: item.disabled || false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return res;
|
||||||
|
});
|
||||||
|
|
||||||
|
const fetch = async () => {
|
||||||
|
const api: any =
|
||||||
|
typeof props.api === 'string' && props.api
|
||||||
|
? (params: any) => {
|
||||||
|
return (requestClient as any)[props.requestMethod](
|
||||||
|
props.api as any,
|
||||||
|
params,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
: props.api;
|
||||||
|
if (!api || !isFunction(api)) return;
|
||||||
|
try {
|
||||||
|
loading.value = true;
|
||||||
|
const params =
|
||||||
|
props.requestMethod === 'get' ? { params: props.params } : props.params;
|
||||||
|
const res = await api(params);
|
||||||
|
if (Array.isArray(res)) {
|
||||||
|
options.value = res;
|
||||||
|
emits('optionsChange', options.value);
|
||||||
|
} else {
|
||||||
|
options.value = props.resultField
|
||||||
|
? getNestedValue(res, props.resultField)
|
||||||
|
: [];
|
||||||
|
emits('optionsChange', options.value);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(error);
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
async function handleFetch() {
|
||||||
|
if (!props.immediate && isFirstLoad.value) {
|
||||||
|
await fetch();
|
||||||
|
isFirstLoad.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
watchEffect(() => {
|
||||||
|
props.immediate && fetch();
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.params,
|
||||||
|
() => {
|
||||||
|
!isFirstLoad.value && fetch();
|
||||||
|
},
|
||||||
|
{ deep: true },
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Select
|
||||||
|
v-model:value="mValue"
|
||||||
|
:options="getOptions"
|
||||||
|
class="w-full"
|
||||||
|
@dropdown-visible-change="handleFetch"
|
||||||
|
>
|
||||||
|
<template v-for="item in Object.keys($slots)" #[item]="data">
|
||||||
|
<slot :name="item" v-bind="data || {}"></slot>
|
||||||
|
</template>
|
||||||
|
<template v-if="loading" #suffixIcon>
|
||||||
|
<IconifyIcon icon="ant-design:loading-outlined" spin />
|
||||||
|
</template>
|
||||||
|
<template v-if="loading" #notFoundContent>
|
||||||
|
<span>
|
||||||
|
<IconifyIcon icon="ant-design:loading-outlined" spin />
|
||||||
|
请等待数据加载完成
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</Select>
|
||||||
|
</template>
|
|
@ -0,0 +1,140 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { SelectValue } from 'ant-design-vue/es/select';
|
||||||
|
|
||||||
|
import { computed, type PropType, ref, watch, watchEffect } from 'vue';
|
||||||
|
|
||||||
|
import { IconifyIcon } from '@vben/icons';
|
||||||
|
import { getNestedValue, isFunction } from '@vben/utils';
|
||||||
|
|
||||||
|
import { useVModel } from '@vueuse/core';
|
||||||
|
import { TreeSelect } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
value: {
|
||||||
|
type: [String, Number, Array] as PropType<SelectValue>,
|
||||||
|
default: undefined,
|
||||||
|
},
|
||||||
|
api: {
|
||||||
|
type: [Function, String] as PropType<(arg?: any) => Promise<any> | String>,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
requestMethod: {
|
||||||
|
// 请求方法
|
||||||
|
type: String,
|
||||||
|
default: 'post',
|
||||||
|
},
|
||||||
|
// api params
|
||||||
|
params: {
|
||||||
|
type: Object as PropType<any>,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
// support xxx.xxx.xx
|
||||||
|
resultField: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
labelField: {
|
||||||
|
type: String,
|
||||||
|
default: 'title',
|
||||||
|
},
|
||||||
|
valueField: {
|
||||||
|
type: String,
|
||||||
|
default: 'value',
|
||||||
|
},
|
||||||
|
childrenField: {
|
||||||
|
type: String,
|
||||||
|
default: 'children',
|
||||||
|
},
|
||||||
|
immediate: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const emits = defineEmits(['update:value', 'treeDataChange']);
|
||||||
|
const mValue = useVModel(props, 'value', emits, {
|
||||||
|
defaultValue: props.value,
|
||||||
|
passive: true,
|
||||||
|
});
|
||||||
|
const treeData = ref<any>([]);
|
||||||
|
const loading = ref(false);
|
||||||
|
const isFirstLoad = ref(true);
|
||||||
|
const fieldNames = computed(() => {
|
||||||
|
return {
|
||||||
|
label: props.labelField,
|
||||||
|
value: props.valueField,
|
||||||
|
children: props.childrenField,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
const getTreeData = computed(() => {
|
||||||
|
return treeData.value;
|
||||||
|
});
|
||||||
|
|
||||||
|
const fetch = async () => {
|
||||||
|
const api: any =
|
||||||
|
typeof props.api === 'string' && props.api
|
||||||
|
? (params: any) => {
|
||||||
|
return (requestClient as any)[props.requestMethod](
|
||||||
|
props.api as any,
|
||||||
|
params,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
: props.api;
|
||||||
|
if (!api || !isFunction(api)) return;
|
||||||
|
try {
|
||||||
|
loading.value = true;
|
||||||
|
const params =
|
||||||
|
props.requestMethod === 'get' ? { params: props.params } : props.params;
|
||||||
|
const res = await api(params);
|
||||||
|
if (Array.isArray(res)) {
|
||||||
|
treeData.value = res;
|
||||||
|
emits('treeDataChange', treeData.value);
|
||||||
|
} else {
|
||||||
|
treeData.value = props.resultField
|
||||||
|
? getNestedValue(res, props.resultField)
|
||||||
|
: [];
|
||||||
|
emits('treeDataChange', treeData.value);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(error);
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
async function handleFetch() {
|
||||||
|
if (!props.immediate && isFirstLoad.value) {
|
||||||
|
await fetch();
|
||||||
|
isFirstLoad.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
watchEffect(() => {
|
||||||
|
props.immediate && fetch();
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.params,
|
||||||
|
() => {
|
||||||
|
!isFirstLoad.value && fetch();
|
||||||
|
},
|
||||||
|
{ deep: true },
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<TreeSelect
|
||||||
|
v-model:value="mValue"
|
||||||
|
:field-names="fieldNames"
|
||||||
|
:tree-data="getTreeData"
|
||||||
|
:tree-node-filter-prop="labelField"
|
||||||
|
class="w-full"
|
||||||
|
@dropdown-visible-change="handleFetch"
|
||||||
|
>
|
||||||
|
<template v-for="item in Object.keys($slots)" #[item]="data">
|
||||||
|
<slot :name="item" v-bind="data || {}"></slot>
|
||||||
|
</template>
|
||||||
|
<template v-if="loading" #suffixIcon>
|
||||||
|
<IconifyIcon icon="ant-design:loading-outlined" spin />
|
||||||
|
</template>
|
||||||
|
</TreeSelect>
|
||||||
|
</template>
|
|
@ -0,0 +1,5 @@
|
||||||
|
export { default as ApiCheckboxGroup } from './components/api-checkbox-group.vue';
|
||||||
|
export { default as ApiDict } from './components/api-dict.vue';
|
||||||
|
export { default as ApiRadioGroup } from './components/api-radio-group.vue';
|
||||||
|
export { default as ApiSelect } from './components/api-select.vue';
|
||||||
|
export { default as ApiTreeSelect } from './components/api-tree-select.vue';
|
|
@ -0,0 +1,6 @@
|
||||||
|
export type CustomComponentType =
|
||||||
|
| 'ApiCheckboxGroup'
|
||||||
|
| 'ApiDict'
|
||||||
|
| 'ApiRadioGroup'
|
||||||
|
| 'ApiSelect'
|
||||||
|
| 'ApiTreeSelect';
|
|
@ -0,0 +1,27 @@
|
||||||
|
import type { Component } from 'vue';
|
||||||
|
|
||||||
|
import { toPascalCase } from '#/util/tool';
|
||||||
|
|
||||||
|
const componentMap = new Map<string, Component>();
|
||||||
|
// import.meta.glob() 直接引入所有的模块 Vite 独有的功能
|
||||||
|
const modules = import.meta.glob('./components/**/*.vue', { eager: true });
|
||||||
|
// 加入到路由集合中
|
||||||
|
Object.keys(modules).forEach((key) => {
|
||||||
|
if (!key.includes('-ignore')) {
|
||||||
|
const mod = (modules as any)[key].default || {};
|
||||||
|
// ./components/ApiDict.vue
|
||||||
|
// 获取ApiDict
|
||||||
|
const compName = key.replace('./components/', '').replace('.vue', '');
|
||||||
|
componentMap.set(toPascalCase(compName), mod);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export function add(compName: string, component: Component) {
|
||||||
|
componentMap.set(compName, component);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function del(compName: string) {
|
||||||
|
componentMap.delete(compName);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { componentMap };
|
|
@ -0,0 +1,6 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import ApiSelect from './api-select.vue';
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<ApiSelect />
|
||||||
|
</template>
|
|
@ -0,0 +1,64 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue';
|
||||||
|
|
||||||
|
import { type DictItem, useDictStore } from '@vben/stores';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
code: {
|
||||||
|
type: String,
|
||||||
|
default: undefined,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
type: Object,
|
||||||
|
default() {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
// 值
|
||||||
|
type: [String, Number, Array],
|
||||||
|
default: undefined,
|
||||||
|
},
|
||||||
|
split: {
|
||||||
|
// 分割符
|
||||||
|
type: String,
|
||||||
|
default: ',',
|
||||||
|
},
|
||||||
|
join: {
|
||||||
|
// 连接符
|
||||||
|
type: String,
|
||||||
|
default: ',',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const dictStore = useDictStore();
|
||||||
|
const cValue = computed(() => {
|
||||||
|
if (!props.value && props.value !== 0) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
const arr: Array<any> = [];
|
||||||
|
if (Array.isArray(props.value)) {
|
||||||
|
arr.push(...props.value);
|
||||||
|
} else {
|
||||||
|
arr.push(...props.value.toString().split(props.split));
|
||||||
|
}
|
||||||
|
const dictData = dictStore.getDictData(props.code as string) as DictItem[];
|
||||||
|
const res: Array<any> = [];
|
||||||
|
arr.forEach((item) => {
|
||||||
|
for (let i = 0; i < dictData.length; i++) {
|
||||||
|
if (dictData[i]?.value?.toString() === item?.toString()) {
|
||||||
|
res.push(dictData[i]?.label);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (i === dictData.length - 1) {
|
||||||
|
res.push(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.join(props.join);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div>{{ cValue }}</div>
|
||||||
|
</template>
|
||||||
|
<style lang="less" scoped></style>
|
|
@ -0,0 +1,6 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import ApiSelect from './api-select.vue';
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<ApiSelect />
|
||||||
|
</template>
|
|
@ -0,0 +1,154 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, onMounted, type PropType, watch } from 'vue';
|
||||||
|
|
||||||
|
import { type DictItem, useDictStore } from '@vben/stores';
|
||||||
|
|
||||||
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
code: {
|
||||||
|
type: String,
|
||||||
|
default: undefined,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
type: Object,
|
||||||
|
default() {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
// 值
|
||||||
|
type: [String, Number, Array],
|
||||||
|
default: undefined,
|
||||||
|
},
|
||||||
|
split: {
|
||||||
|
// 分割符
|
||||||
|
type: String,
|
||||||
|
default: ',',
|
||||||
|
},
|
||||||
|
join: {
|
||||||
|
// 连接符
|
||||||
|
type: String,
|
||||||
|
default: ',',
|
||||||
|
},
|
||||||
|
api: {
|
||||||
|
// 接口请求对象
|
||||||
|
type: [Function, String] as PropType<
|
||||||
|
((...arg: any) => Promise<any>) | String
|
||||||
|
>,
|
||||||
|
default() {
|
||||||
|
return () => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
resolve([]);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
params: {
|
||||||
|
type: Object,
|
||||||
|
default() {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cacheKey: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
requestMethod: {
|
||||||
|
type: String,
|
||||||
|
default: 'post',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const dictStore = useDictStore();
|
||||||
|
/**
|
||||||
|
* 获取包含的id
|
||||||
|
*/
|
||||||
|
const getIncludeIds = () => {
|
||||||
|
if (!props.value && props.value !== 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const arr: Array<any> = [];
|
||||||
|
if (Array.isArray(props.value)) {
|
||||||
|
arr.push(...props.value);
|
||||||
|
} else {
|
||||||
|
arr.push(...props.value.toString().split(props.split));
|
||||||
|
}
|
||||||
|
return arr;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* 获取缓存key
|
||||||
|
*/
|
||||||
|
const getCacheKey = () => {
|
||||||
|
let cacheKey = props.cacheKey;
|
||||||
|
if (typeof props.api === 'string' && !cacheKey) {
|
||||||
|
cacheKey = props.api as string;
|
||||||
|
}
|
||||||
|
return cacheKey;
|
||||||
|
};
|
||||||
|
const cValue = computed(() => {
|
||||||
|
if (!props.value && props.value !== 0) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
const arr: Array<any> = getIncludeIds();
|
||||||
|
const cacheKey = getCacheKey();
|
||||||
|
const dictData = dictStore.getDictData(cacheKey) as DictItem[];
|
||||||
|
const res: Array<any> = [];
|
||||||
|
arr.forEach((item) => {
|
||||||
|
for (let i = 0; i < dictData.length; i++) {
|
||||||
|
if (dictData[i]?.value?.toString() === item?.toString()) {
|
||||||
|
res.push(dictData[i]?.label);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (i === dictData.length - 1) {
|
||||||
|
res.push(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.join(props.join);
|
||||||
|
});
|
||||||
|
const requestData = () => {
|
||||||
|
const api: (...arg: any) => Promise<any> =
|
||||||
|
typeof props.api === 'string'
|
||||||
|
? (params: any) => {
|
||||||
|
return (requestClient as any)[props.requestMethod](
|
||||||
|
props.api as any,
|
||||||
|
params,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
: (props.api as (...arg: any) => Promise<any>);
|
||||||
|
const cacheKey = getCacheKey();
|
||||||
|
const params =
|
||||||
|
props.requestMethod === 'get'
|
||||||
|
? {
|
||||||
|
params: {
|
||||||
|
...props.params,
|
||||||
|
dictType: cacheKey,
|
||||||
|
includeType: 2,
|
||||||
|
includeIds: getIncludeIds(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
data: {
|
||||||
|
...props.params,
|
||||||
|
dictType: cacheKey,
|
||||||
|
includeType: 2,
|
||||||
|
includeIds: getIncludeIds(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
dictStore.setDictCacheByApi(api, params);
|
||||||
|
};
|
||||||
|
onMounted(() => {
|
||||||
|
requestData();
|
||||||
|
});
|
||||||
|
watch(
|
||||||
|
() => props.value,
|
||||||
|
() => {
|
||||||
|
requestData();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div>{{ cValue }}</div>
|
||||||
|
</template>
|
||||||
|
<style lang="less" scoped></style>
|
|
@ -0,0 +1,95 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, onMounted, type PropType, ref } from 'vue';
|
||||||
|
|
||||||
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
data: {
|
||||||
|
type: Object,
|
||||||
|
default() {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
// 值
|
||||||
|
type: [String, Number, Array],
|
||||||
|
default: undefined,
|
||||||
|
},
|
||||||
|
api: {
|
||||||
|
// 接口请求对象
|
||||||
|
type: [Function, String] as PropType<
|
||||||
|
((...arg: any) => Promise<any>) | String
|
||||||
|
>,
|
||||||
|
default() {
|
||||||
|
return () => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
resolve([]);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
params: {
|
||||||
|
type: Object,
|
||||||
|
default() {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cacheKey: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
requestMethod: {
|
||||||
|
type: String,
|
||||||
|
default: 'post',
|
||||||
|
},
|
||||||
|
valueField: {
|
||||||
|
type: String,
|
||||||
|
default: 'id',
|
||||||
|
},
|
||||||
|
labelField: {
|
||||||
|
type: String,
|
||||||
|
default: 'name',
|
||||||
|
},
|
||||||
|
multiple: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const currentData = ref({});
|
||||||
|
const cValue = computed(() => {
|
||||||
|
return (currentData.value as any)[props.labelField] || props.value;
|
||||||
|
});
|
||||||
|
onMounted(() => {
|
||||||
|
const api: (...arg: any) => Promise<any> =
|
||||||
|
typeof props.api === 'string'
|
||||||
|
? (params: any) => {
|
||||||
|
return (requestClient as any)[props.requestMethod](
|
||||||
|
props.api as any,
|
||||||
|
params,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
: (props.api as (...arg: any) => Promise<any>);
|
||||||
|
const searchType = props.multiple ? 'IN' : 'EQ';
|
||||||
|
const params =
|
||||||
|
props.requestMethod === 'get'
|
||||||
|
? {
|
||||||
|
params: {
|
||||||
|
...props.params,
|
||||||
|
[`m_${searchType}_${props.valueField}`]: props.value,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
...props.params,
|
||||||
|
[`m_${searchType}_${props.valueField}`]: props.value,
|
||||||
|
};
|
||||||
|
api(params).then((res) => {
|
||||||
|
if (res.length > 0) {
|
||||||
|
currentData.value = res[0];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div>{{ cValue }}</div>
|
||||||
|
</template>
|
||||||
|
<style lang="less" scoped></style>
|
Loading…
Reference in New Issue