Pre Merge pull request !58 from chenminjie/dev-v5_cmj
commit
8eb6743d19
|
|
@ -0,0 +1,135 @@
|
||||||
|
You are an expert in TypeScript, Node.js, Vite, Vue.js, Vue Router, Pinia, VueUse, and UI frameworks (Ant Design Vue, Element Plus, Naive UI), with a deep understanding of best practices and performance optimization techniques in these technologies.
|
||||||
|
|
||||||
|
When analyzing and modifying code, you should:
|
||||||
|
|
||||||
|
Package Structure Understanding
|
||||||
|
- Core Package (@core):
|
||||||
|
- Analyze base/ for core utilities and implementations
|
||||||
|
- Review ui-kit/ for framework-agnostic components
|
||||||
|
- Examine composables/ for shared Vue hooks
|
||||||
|
- Check preferences/ for app-wide settings
|
||||||
|
- Utility Packages:
|
||||||
|
- Understand utils/ for common functions
|
||||||
|
- Review types/ for shared type definitions
|
||||||
|
- Check constants/ for shared constants
|
||||||
|
- Examine effects/ for shared animations
|
||||||
|
- Feature Packages:
|
||||||
|
- Analyze stores/ for Pinia store implementations
|
||||||
|
- Review locales/ for i18n resources
|
||||||
|
- Check icons/ for icon components
|
||||||
|
- Examine styles/ for theme implementations
|
||||||
|
|
||||||
|
Framework Adaptation Patterns
|
||||||
|
- UI Framework Handling:
|
||||||
|
- Identify the target UI framework in apps/ (Ant Design Vue, Element Plus, Naive UI)
|
||||||
|
- Identify the target UI framework in packages/ (packages/@core/ui-kit/**)
|
||||||
|
- Use framework-specific component patterns
|
||||||
|
- Maintain consistent APIs across frameworks
|
||||||
|
- Implement proper component props
|
||||||
|
- Handle framework-specific events
|
||||||
|
- Follow framework-specific styling
|
||||||
|
|
||||||
|
Component Development Rules
|
||||||
|
- Base Components:
|
||||||
|
- Use framework-agnostic design where possible
|
||||||
|
- Implement proper type definitions
|
||||||
|
- Include accessibility features
|
||||||
|
- Handle component composition
|
||||||
|
- Manage component state
|
||||||
|
- Document component APIs
|
||||||
|
- Framework Components:
|
||||||
|
- Follow framework conventions
|
||||||
|
- Use framework-specific features
|
||||||
|
- Implement proper slots
|
||||||
|
- Handle framework events
|
||||||
|
- Use framework themes
|
||||||
|
- Document framework specifics
|
||||||
|
|
||||||
|
State and Store Patterns
|
||||||
|
- Store Implementation:
|
||||||
|
- Use Pinia store patterns
|
||||||
|
- Implement proper typing
|
||||||
|
- Handle state persistence
|
||||||
|
- Manage store modules
|
||||||
|
- Handle store reset
|
||||||
|
- Document store usage
|
||||||
|
- State Management:
|
||||||
|
- Use composition store helpers
|
||||||
|
- Implement state watchers
|
||||||
|
- Handle state updates
|
||||||
|
- Manage side effects
|
||||||
|
- Document state flow
|
||||||
|
- Test store functionality
|
||||||
|
|
||||||
|
Composable Development
|
||||||
|
- Hook Patterns:
|
||||||
|
- Create reusable hooks
|
||||||
|
- Implement proper typing
|
||||||
|
- Handle lifecycle
|
||||||
|
- Manage dependencies
|
||||||
|
- Document usage
|
||||||
|
- Test hook behavior
|
||||||
|
- Utility Functions:
|
||||||
|
- Create pure functions
|
||||||
|
- Use proper typing
|
||||||
|
- Handle edge cases
|
||||||
|
- Document parameters
|
||||||
|
- Test functionality
|
||||||
|
- Consider performance
|
||||||
|
|
||||||
|
Style and Theme Handling
|
||||||
|
- Theme Implementation:
|
||||||
|
- Support dark mode
|
||||||
|
- Handle framework themes
|
||||||
|
- Use CSS variables
|
||||||
|
- Implement transitions
|
||||||
|
- Handle responsive styles
|
||||||
|
- Document theme usage
|
||||||
|
- Style Management:
|
||||||
|
- Use framework classes
|
||||||
|
- Handle style conflicts
|
||||||
|
- Implement utilities
|
||||||
|
- Manage overrides
|
||||||
|
- Document style usage
|
||||||
|
- Test theme switching
|
||||||
|
|
||||||
|
Code Quality Standards
|
||||||
|
- TypeScript Usage:
|
||||||
|
- Use strict mode
|
||||||
|
- Implement interfaces
|
||||||
|
- Handle type guards
|
||||||
|
- Document types
|
||||||
|
- Test type safety
|
||||||
|
- Maintain type files
|
||||||
|
- Testing Requirements:
|
||||||
|
- Write unit tests
|
||||||
|
- Test components
|
||||||
|
- Test utilities
|
||||||
|
- Test hooks
|
||||||
|
- Document testing
|
||||||
|
- Maintain coverage
|
||||||
|
|
||||||
|
Response Guidelines
|
||||||
|
- Code Changes:
|
||||||
|
- Explain modifications
|
||||||
|
- Show code examples
|
||||||
|
- Document impacts
|
||||||
|
- Suggest alternatives
|
||||||
|
- Consider performance
|
||||||
|
- Note security implications
|
||||||
|
- Documentation:
|
||||||
|
- Update comments
|
||||||
|
- Provide examples
|
||||||
|
- Document APIs
|
||||||
|
- Note changes
|
||||||
|
- Include references
|
||||||
|
- Maintain clarity
|
||||||
|
|
||||||
|
When making improvements:
|
||||||
|
- Follow existing patterns
|
||||||
|
- Maintain consistency
|
||||||
|
- Consider compatibility
|
||||||
|
- Think about scaling
|
||||||
|
- Document changes
|
||||||
|
- Test thoroughly
|
||||||
|
|
||||||
|
|
@ -5,8 +5,6 @@
|
||||||
|
|
||||||
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';
|
||||||
|
|
||||||
|
|
@ -38,8 +36,6 @@ 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',
|
||||||
|
|
@ -52,6 +48,7 @@ const withDefaultPlaceholder = <T extends Component>(
|
||||||
|
|
||||||
// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
|
// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
|
||||||
export type ComponentType =
|
export type ComponentType =
|
||||||
|
| 'ApiCheckbox'
|
||||||
| 'ApiSelect'
|
| 'ApiSelect'
|
||||||
| 'ApiTreeSelect'
|
| 'ApiTreeSelect'
|
||||||
| 'AutoComplete'
|
| 'AutoComplete'
|
||||||
|
|
@ -77,14 +74,26 @@ 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>> = {
|
||||||
// 如果你的组件体积比较大,可以使用异步加载
|
// 如果你的组件体积比较大,可以使用异步加载
|
||||||
// Button: () =>
|
// Button: () =>
|
||||||
// import('xxx').then((res) => res.Button),
|
// import('xxx').then((res) => res.Button),
|
||||||
|
|
||||||
|
ApiCheckbox: (props, { attrs, slots }) => {
|
||||||
|
return h(
|
||||||
|
ApiComponent,
|
||||||
|
{
|
||||||
|
...props,
|
||||||
|
...attrs,
|
||||||
|
component: CheckboxGroup,
|
||||||
|
modelPropName: 'value',
|
||||||
|
},
|
||||||
|
slots,
|
||||||
|
);
|
||||||
|
},
|
||||||
ApiSelect: (props, { attrs, slots }) => {
|
ApiSelect: (props, { attrs, slots }) => {
|
||||||
return h(
|
return h(
|
||||||
ApiComponent,
|
ApiComponent,
|
||||||
|
|
@ -94,8 +103,8 @@ async function initComponentAdapter() {
|
||||||
...attrs,
|
...attrs,
|
||||||
component: Select,
|
component: Select,
|
||||||
loadingSlot: 'suffixIcon',
|
loadingSlot: 'suffixIcon',
|
||||||
visibleEvent: 'onDropdownVisibleChange',
|
|
||||||
modelPropName: 'value',
|
modelPropName: 'value',
|
||||||
|
visibleEvent: 'onVisibleChange',
|
||||||
},
|
},
|
||||||
slots,
|
slots,
|
||||||
);
|
);
|
||||||
|
|
@ -154,9 +163,6 @@ async function initComponentAdapter() {
|
||||||
Upload,
|
Upload,
|
||||||
};
|
};
|
||||||
|
|
||||||
// 注册自定义组件
|
|
||||||
registerCustomFormComponent(components);
|
|
||||||
|
|
||||||
// 将组件注册到全局共享状态中
|
// 将组件注册到全局共享状态中
|
||||||
globalShareState.setComponents(components);
|
globalShareState.setComponents(components);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
import { type PageParam, requestClient } from '#/api/request';
|
||||||
|
|
||||||
|
export namespace FileApi {
|
||||||
|
export interface FilePageReqVO extends PageParam {
|
||||||
|
path?: string;
|
||||||
|
type?: string;
|
||||||
|
createTime?: Date[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 文件预签名地址 Response VO
|
||||||
|
export interface FilePresignedUrlRespVO {
|
||||||
|
// 文件配置编号
|
||||||
|
configId: number;
|
||||||
|
// 文件上传 URL
|
||||||
|
uploadUrl: string;
|
||||||
|
// 文件 URL
|
||||||
|
url: string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询文件列表
|
||||||
|
export function getFilePage(params: FileApi.FilePageReqVO) {
|
||||||
|
return requestClient.get('/infra/file/page', { params });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除文件
|
||||||
|
export function deleteFile(id: number) {
|
||||||
|
return requestClient.delete(`/infra/file/delete?id=${id}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取文件预签名地址
|
||||||
|
export function getFilePresignedUrl(path: string) {
|
||||||
|
return requestClient.get<FileApi.FilePresignedUrlRespVO>(
|
||||||
|
'/infra/file/presigned-url',
|
||||||
|
{ params: { path } },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createFile(data: any) {
|
||||||
|
return requestClient.post('/infra/file/create', data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上传文件
|
||||||
|
export function uploadFile(data: any) {
|
||||||
|
return requestClient.upload('/infra/file/upload', data);
|
||||||
|
}
|
||||||
|
|
@ -1,77 +0,0 @@
|
||||||
<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>
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
export { default as Description } from './description.vue';
|
|
||||||
export type * from './types';
|
|
||||||
|
|
@ -1,54 +0,0 @@
|
||||||
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];
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
import type { CustomComponentType } from './types';
|
|
||||||
|
|
||||||
import type { Component } from 'vue';
|
|
||||||
|
|
||||||
import { capitalizeFirstLetter, 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(capitalizeFirstLetter(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 };
|
|
||||||
|
|
@ -1,147 +0,0 @@
|
||||||
<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 },
|
|
||||||
);
|
|
||||||
|
|
||||||
// 监听 value 的变化,确保 mValue 的类型与 options 中的 value 类型一致
|
|
||||||
watch(
|
|
||||||
() => props.value,
|
|
||||||
() => {
|
|
||||||
if (props.numberToString && Array.isArray(mValue.value)) {
|
|
||||||
mValue.value = mValue.value.map((item) => `${item}`);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
</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>
|
|
||||||
|
|
@ -1,54 +0,0 @@
|
||||||
<script setup lang="ts">
|
|
||||||
import { computed, type PropType } from 'vue';
|
|
||||||
|
|
||||||
import { useDictStore } from '@vben/stores';
|
|
||||||
|
|
||||||
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 new Promise<OptionsItem[]>((resolve) => {
|
|
||||||
const dict = useDictStore().getDictOptions(props.code!);
|
|
||||||
const options: OptionsItem[] = dict as OptionsItem[];
|
|
||||||
resolve(options);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
<template>
|
|
||||||
<DictComponent :api="props.code ? fetch : props.api" />
|
|
||||||
</template>
|
|
||||||
|
|
@ -1,156 +0,0 @@
|
||||||
<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 },
|
|
||||||
);
|
|
||||||
|
|
||||||
// 监听 value 的变化,确保 mValue 的类型与 options 中的 value 类型一致
|
|
||||||
watch(
|
|
||||||
() => props.value,
|
|
||||||
() => {
|
|
||||||
if (props.numberToString && typeof mValue.value === 'number') {
|
|
||||||
mValue.value = `${mValue.value}`;
|
|
||||||
}
|
|
||||||
if (props.numberToString && Array.isArray(mValue.value)) {
|
|
||||||
mValue.value = mValue.value.map((item) => `${item}`);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
</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>
|
|
||||||
|
|
@ -1,163 +0,0 @@
|
||||||
<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 },
|
|
||||||
);
|
|
||||||
|
|
||||||
// 监听 value 的变化,确保 mValue 的类型与 options 中的 value 类型一致
|
|
||||||
watch(
|
|
||||||
() => props.value,
|
|
||||||
() => {
|
|
||||||
if (props.numberToString && typeof mValue.value === 'number') {
|
|
||||||
mValue.value = `${mValue.value}`;
|
|
||||||
}
|
|
||||||
if (props.numberToString && Array.isArray(mValue.value)) {
|
|
||||||
mValue.value = mValue.value.map((item) => `${item}`);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
</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>
|
|
||||||
|
|
@ -1,140 +0,0 @@
|
||||||
<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>
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
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';
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
export type CustomComponentType =
|
|
||||||
| 'ApiCheckboxGroup'
|
|
||||||
| 'ApiDict'
|
|
||||||
| 'ApiRadioGroup'
|
|
||||||
| 'ApiSelect'
|
|
||||||
| 'ApiTreeSelect';
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
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 };
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
<script setup lang="ts">
|
|
||||||
import ApiSelect from './api-select.vue';
|
|
||||||
</script>
|
|
||||||
<template>
|
|
||||||
<ApiSelect />
|
|
||||||
</template>
|
|
||||||
|
|
@ -1,64 +0,0 @@
|
||||||
<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>
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
<script setup lang="ts">
|
|
||||||
import ApiSelect from './api-select.vue';
|
|
||||||
</script>
|
|
||||||
<template>
|
|
||||||
<ApiSelect />
|
|
||||||
</template>
|
|
||||||
|
|
@ -1,154 +0,0 @@
|
||||||
<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>
|
|
||||||
|
|
@ -1,95 +0,0 @@
|
||||||
<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>
|
|
||||||
|
|
@ -2,7 +2,7 @@ import type { VxeGridProps } from '#/adapter/vxe-table';
|
||||||
import type { CodegenApi } from '#/api/infra/codegen';
|
import type { CodegenApi } from '#/api/infra/codegen';
|
||||||
|
|
||||||
import { type VbenFormProps, z } from '@vben/common-ui';
|
import { type VbenFormProps, z } from '@vben/common-ui';
|
||||||
import { useUserStore } from '@vben/stores';
|
import { useDictStore, useUserStore } from '@vben/stores';
|
||||||
|
|
||||||
import { getDataSourceConfigList } from '#/api/infra/data-source-config';
|
import { getDataSourceConfigList } from '#/api/infra/data-source-config';
|
||||||
import { $t } from '#/locales';
|
import { $t } from '#/locales';
|
||||||
|
|
@ -88,21 +88,11 @@ export namespace CodegenImportTableModalData {
|
||||||
fieldName: 'dataSourceConfigId',
|
fieldName: 'dataSourceConfigId',
|
||||||
component: 'ApiSelect',
|
component: 'ApiSelect',
|
||||||
componentProps: {
|
componentProps: {
|
||||||
defaultSelectedFirst: true,
|
|
||||||
allowClear: true,
|
|
||||||
placeholder: '请选择数据源',
|
placeholder: '请选择数据源',
|
||||||
api: getDataSourceConfigList,
|
api: getDataSourceConfigList,
|
||||||
labelField: 'name',
|
labelField: 'name',
|
||||||
valueField: 'id',
|
valueField: 'id',
|
||||||
},
|
},
|
||||||
componentEvents: (events, formApi) => {
|
|
||||||
return {
|
|
||||||
optionsChange: (value: any) => {
|
|
||||||
// 设置默认选中第一个
|
|
||||||
formApi.setFieldValue('dataSourceConfigId', value[0].id);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '表名称',
|
label: '表名称',
|
||||||
|
|
@ -169,37 +159,47 @@ export namespace CodegenOptionsModalData {
|
||||||
{
|
{
|
||||||
label: '生成模版',
|
label: '生成模版',
|
||||||
fieldName: 'template',
|
fieldName: 'template',
|
||||||
component: 'ApiDict',
|
component: 'ApiSelect',
|
||||||
componentProps: {
|
componentProps: {
|
||||||
code: DICT_TYPE.INFRA_CODEGEN_TEMPLATE_TYPE,
|
|
||||||
class: 'w-full',
|
class: 'w-full',
|
||||||
placeholder: '请选择生成模版',
|
placeholder: '请选择生成模版',
|
||||||
|
api: () => {
|
||||||
|
return useDictStore().getDictOptions(
|
||||||
|
DICT_TYPE.INFRA_CODEGEN_TEMPLATE_TYPE,
|
||||||
|
);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
rules: z.string().min(1, { message: '生成模版不能为空' }),
|
rules: 'required',
|
||||||
defaultValue: '1',
|
defaultValue: '1',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '前端类型',
|
label: '前端类型',
|
||||||
fieldName: 'frontType',
|
fieldName: 'frontType',
|
||||||
component: 'ApiDict',
|
component: 'ApiSelect',
|
||||||
componentProps: {
|
componentProps: {
|
||||||
code: DICT_TYPE.INFRA_CODEGEN_FRONT_TYPE,
|
|
||||||
class: 'w-full',
|
class: 'w-full',
|
||||||
placeholder: '请选择前端类型',
|
placeholder: '请选择前端类型',
|
||||||
|
api: () => {
|
||||||
|
return useDictStore().getDictOptions(
|
||||||
|
DICT_TYPE.INFRA_CODEGEN_FRONT_TYPE,
|
||||||
|
);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
rules: z.string().min(1, { message: '前端类型不能为空' }),
|
rules: 'required',
|
||||||
defaultValue: '31',
|
defaultValue: '31',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '生成场景',
|
label: '生成场景',
|
||||||
fieldName: 'scene',
|
fieldName: 'scene',
|
||||||
component: 'ApiDict',
|
component: 'ApiSelect',
|
||||||
componentProps: {
|
componentProps: {
|
||||||
code: DICT_TYPE.INFRA_CODEGEN_SCENE,
|
|
||||||
class: 'w-full',
|
class: 'w-full',
|
||||||
placeholder: '请选择生成场景',
|
placeholder: '请选择生成场景',
|
||||||
|
api: () => {
|
||||||
|
return useDictStore().getDictOptions(DICT_TYPE.INFRA_CODEGEN_SCENE);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
rules: z.string().min(1, { message: '生成场景不能为空' }),
|
rules: 'required',
|
||||||
defaultValue: '1',
|
defaultValue: '1',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -214,16 +214,17 @@ export namespace CodegenOptionsModalData {
|
||||||
},
|
},
|
||||||
labelField: 'name',
|
labelField: 'name',
|
||||||
valueField: 'id',
|
valueField: 'id',
|
||||||
|
childrenField: 'children',
|
||||||
placeholder: '请选择上级菜单',
|
placeholder: '请选择上级菜单',
|
||||||
},
|
},
|
||||||
rules: z.number().min(1, { message: '上级菜单不能为空' }),
|
rules: 'required',
|
||||||
defaultValue: null,
|
defaultValue: null,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '模块名',
|
label: '模块名',
|
||||||
fieldName: 'moduleName',
|
fieldName: 'moduleName',
|
||||||
component: 'Input',
|
component: 'Input',
|
||||||
rules: z.string().min(1, { message: '模块名不能为空' }),
|
rules: 'required',
|
||||||
componentProps: {
|
componentProps: {
|
||||||
placeholder: '请输入模块名',
|
placeholder: '请输入模块名',
|
||||||
},
|
},
|
||||||
|
|
@ -232,22 +233,19 @@ export namespace CodegenOptionsModalData {
|
||||||
label: '业务名',
|
label: '业务名',
|
||||||
fieldName: 'businessName',
|
fieldName: 'businessName',
|
||||||
component: 'Input',
|
component: 'Input',
|
||||||
rules: z.string().min(1, { message: '业务名不能为空' }),
|
rules: 'required',
|
||||||
componentProps: {
|
componentProps: {
|
||||||
placeholder: '请输入业务名',
|
placeholder: '请输入业务名',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
label: '类名',
|
|
||||||
fieldName: 'className',
|
|
||||||
component: 'Input',
|
|
||||||
rules: z.string().min(1, { message: '类名不能为空' }),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
label: '类描述',
|
label: '类描述',
|
||||||
fieldName: 'classComment',
|
fieldName: 'classComment',
|
||||||
component: 'Input',
|
component: 'Input',
|
||||||
rules: z.string().min(1, { message: '类描述不能为空' }),
|
rules: 'required',
|
||||||
|
componentProps: {
|
||||||
|
placeholder: '请输入类描述',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -85,9 +85,6 @@ const [Modal, modalApi] = useVbenModal({
|
||||||
confirmLoading: confirmLoading.value,
|
confirmLoading: confirmLoading.value,
|
||||||
closeOnClickModal: false,
|
closeOnClickModal: false,
|
||||||
closeOnPressEscape: false,
|
closeOnPressEscape: false,
|
||||||
onOpened: async () => {
|
|
||||||
gridApi.reload(await gridApi.formApi.getValues());
|
|
||||||
},
|
|
||||||
onConfirm: async () => {
|
onConfirm: async () => {
|
||||||
modalApi.setState({ confirmLoading: true });
|
modalApi.setState({ confirmLoading: true });
|
||||||
const formValues = await gridApi.formApi.getValues();
|
const formValues = await gridApi.formApi.getValues();
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@ import type { FileConfigApi } from '#/api/infra/file-config';
|
||||||
|
|
||||||
import { h } from 'vue';
|
import { h } from 'vue';
|
||||||
|
|
||||||
|
import { useDictStore } from '@vben/stores';
|
||||||
|
|
||||||
import { Tag } from 'ant-design-vue';
|
import { Tag } from 'ant-design-vue';
|
||||||
|
|
||||||
import { type VbenFormProps, z } from '#/adapter/form';
|
import { type VbenFormProps, z } from '#/adapter/form';
|
||||||
|
|
@ -24,11 +26,13 @@ export const formSchema: VbenFormProps['schema'] = [
|
||||||
{
|
{
|
||||||
fieldName: 'storage',
|
fieldName: 'storage',
|
||||||
label: '储存器',
|
label: '储存器',
|
||||||
component: 'ApiDict',
|
component: 'ApiSelect',
|
||||||
componentProps: {
|
componentProps: {
|
||||||
code: DICT_TYPE.INFRA_FILE_STORAGE,
|
|
||||||
class: 'w-full',
|
class: 'w-full',
|
||||||
placeholder: '请选择储存器',
|
placeholder: '请选择储存器',
|
||||||
|
api: () => {
|
||||||
|
return useDictStore().getDictOptions(DICT_TYPE.INFRA_FILE_STORAGE);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -118,14 +122,15 @@ export const editFormSchema: VbenFormProps['schema'] = [
|
||||||
{
|
{
|
||||||
fieldName: 'storage',
|
fieldName: 'storage',
|
||||||
label: '储存器',
|
label: '储存器',
|
||||||
component: 'ApiDict',
|
component: 'ApiSelect',
|
||||||
componentProps: {
|
componentProps: {
|
||||||
code: DICT_TYPE.INFRA_FILE_STORAGE,
|
|
||||||
class: 'w-full',
|
class: 'w-full',
|
||||||
placeholder: '请选择储存器',
|
placeholder: '请选择储存器',
|
||||||
numberToString: true,
|
api: () => {
|
||||||
|
return useDictStore().getDictOptions(DICT_TYPE.INFRA_FILE_STORAGE);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
rules: z.string().min(1, '请选择储存器').or(z.number()),
|
rules: 'required',
|
||||||
defaultValue: '1',
|
defaultValue: '1',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -135,7 +140,7 @@ export const editFormSchema: VbenFormProps['schema'] = [
|
||||||
componentProps: {
|
componentProps: {
|
||||||
placeholder: '请输入基础路径',
|
placeholder: '请输入基础路径',
|
||||||
},
|
},
|
||||||
rules: z.string().min(1, '请输入基础路径'),
|
rules: 'required',
|
||||||
dependencies: {
|
dependencies: {
|
||||||
triggerFields: ['storage'],
|
triggerFields: ['storage'],
|
||||||
if: (values: Record<string, any>) => {
|
if: (values: Record<string, any>) => {
|
||||||
|
|
@ -150,7 +155,7 @@ export const editFormSchema: VbenFormProps['schema'] = [
|
||||||
componentProps: {
|
componentProps: {
|
||||||
placeholder: '请输入主机地址',
|
placeholder: '请输入主机地址',
|
||||||
},
|
},
|
||||||
rules: z.string().min(1, '请输入主机地址'),
|
rules: 'required',
|
||||||
dependencies: {
|
dependencies: {
|
||||||
triggerFields: ['storage'],
|
triggerFields: ['storage'],
|
||||||
if: (values: Record<string, any>) => [11, 12].includes(values.storage),
|
if: (values: Record<string, any>) => [11, 12].includes(values.storage),
|
||||||
|
|
@ -163,7 +168,7 @@ export const editFormSchema: VbenFormProps['schema'] = [
|
||||||
componentProps: {
|
componentProps: {
|
||||||
placeholder: '请输入主机端口',
|
placeholder: '请输入主机端口',
|
||||||
},
|
},
|
||||||
rules: z.string().min(1, '请输入主机端口'),
|
rules: 'required',
|
||||||
dependencies: {
|
dependencies: {
|
||||||
triggerFields: ['storage'],
|
triggerFields: ['storage'],
|
||||||
if: (values: Record<string, any>) => [11, 12].includes(values.storage),
|
if: (values: Record<string, any>) => [11, 12].includes(values.storage),
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useVbenModal } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { useVbenForm } from '#/adapter/form';
|
||||||
|
|
||||||
|
import { uploadFormSchema } from '../file.data';
|
||||||
|
|
||||||
|
const [Modal] = useVbenModal({
|
||||||
|
title: '上传文件',
|
||||||
|
});
|
||||||
|
const [Form] = useVbenForm({
|
||||||
|
schema: uploadFormSchema,
|
||||||
|
showDefaultActions: false,
|
||||||
|
handleSubmit: async (_values) => {},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Modal>
|
||||||
|
<Form />
|
||||||
|
</Modal>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
import type { VxeGridProps } from '@vben/plugins/vxe-table';
|
||||||
|
|
||||||
|
import type { VbenFormProps } from '#/adapter/form';
|
||||||
|
|
||||||
|
import { type FileApi, uploadFile } from '#/api/infra/file';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件 表格查询表单配置
|
||||||
|
*/
|
||||||
|
export const formSchema: VbenFormProps['schema'] = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件 表格配置
|
||||||
|
*/
|
||||||
|
export const tableColumns: VxeGridProps<FileApi.FilePageReqVO>['columns'] = [
|
||||||
|
{
|
||||||
|
fixed: 'left',
|
||||||
|
type: 'checkbox',
|
||||||
|
width: 50,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fixed: 'left',
|
||||||
|
type: 'seq',
|
||||||
|
width: 50,
|
||||||
|
},
|
||||||
|
{ field: 'name', title: '文件名称' },
|
||||||
|
{ field: 'path', title: '文件路径' },
|
||||||
|
{ field: 'url', title: '文件 URL' },
|
||||||
|
{ field: 'type', title: '文件类型' },
|
||||||
|
{ field: 'size', title: '文件大小' },
|
||||||
|
{ field: 'createTime', title: '创建时间' },
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件 编辑表单配置
|
||||||
|
*/
|
||||||
|
export const editFormSchema: VbenFormProps['schema'] = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件 上传表单配置
|
||||||
|
*/
|
||||||
|
export const uploadFormSchema: VbenFormProps['schema'] = [
|
||||||
|
{
|
||||||
|
fieldName: 'file',
|
||||||
|
label: '文件',
|
||||||
|
component: 'ApiUpload',
|
||||||
|
componentProps: {
|
||||||
|
api: uploadFile,
|
||||||
|
uploadMode: 'file',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
@ -0,0 +1,107 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { defineAsyncComponent, reactive } from 'vue';
|
||||||
|
|
||||||
|
import { Page, useVbenModal } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { useVbenVxeGrid, type VxeGridProps } from '#/adapter/vxe-table';
|
||||||
|
import { getFilePage } from '#/api/infra/file';
|
||||||
|
import { ActionButtons } from '#/components/action-buttons';
|
||||||
|
|
||||||
|
import { tableColumns } from './file.data';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 表格配置
|
||||||
|
*/
|
||||||
|
const gridOptions = reactive<any>({
|
||||||
|
columns: tableColumns,
|
||||||
|
height: 'auto',
|
||||||
|
checkboxConfig: {
|
||||||
|
reserve: true,
|
||||||
|
highlight: true,
|
||||||
|
// labelField: 'id',
|
||||||
|
},
|
||||||
|
rowConfig: {
|
||||||
|
keyField: 'id',
|
||||||
|
},
|
||||||
|
proxyConfig: {
|
||||||
|
ajax: {
|
||||||
|
query: async (e, params) => {
|
||||||
|
const data = await getFilePage({
|
||||||
|
pageNo: e.page.currentPage,
|
||||||
|
pageSize: e.page.pageSize,
|
||||||
|
...params,
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as VxeGridProps);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 表格事件
|
||||||
|
*/
|
||||||
|
const gridEvents = reactive<any>({});
|
||||||
|
|
||||||
|
// 使用表格组件
|
||||||
|
const [Grid] = useVbenVxeGrid({
|
||||||
|
gridOptions,
|
||||||
|
gridEvents,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用 上传表单组件
|
||||||
|
*/
|
||||||
|
const [UploadForm, uploadFormApi] = useVbenModal({
|
||||||
|
connectedComponent: defineAsyncComponent(
|
||||||
|
() => import('./components/upload-form.vue'),
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleUpload = () => {
|
||||||
|
uploadFormApi.open();
|
||||||
|
};
|
||||||
|
const handleView = (_row: any) => {};
|
||||||
|
const handleEdit = (_row: any) => {};
|
||||||
|
const handleDelete = (_row: any) => {};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Page auto-content-height>
|
||||||
|
<Grid>
|
||||||
|
<template #toolbar-actions>
|
||||||
|
<ActionButtons
|
||||||
|
:actions="[
|
||||||
|
{
|
||||||
|
type: 'primary',
|
||||||
|
label: '上传文件',
|
||||||
|
icon: 'ant-design:plus-outlined',
|
||||||
|
onClick: handleUpload,
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template #action="{ row }">
|
||||||
|
<ActionButtons
|
||||||
|
:actions="[
|
||||||
|
{
|
||||||
|
label: '查看',
|
||||||
|
icon: 'ant-design:eye-outlined',
|
||||||
|
onClick: () => handleView(row),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '编辑',
|
||||||
|
icon: 'ant-design:edit-outlined',
|
||||||
|
onClick: () => handleEdit(row),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '删除',
|
||||||
|
icon: 'ant-design:delete-outlined',
|
||||||
|
onClick: () => handleDelete(row),
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</Grid>
|
||||||
|
<UploadForm />
|
||||||
|
</Page>
|
||||||
|
</template>
|
||||||
|
|
@ -81,6 +81,7 @@ const emit = defineEmits<{
|
||||||
const modelValue = defineModel({ default: '' });
|
const modelValue = defineModel({ default: '' });
|
||||||
|
|
||||||
const attrs = useAttrs();
|
const attrs = useAttrs();
|
||||||
|
const { class: className, style }: Record<string, any> = attrs;
|
||||||
|
|
||||||
const refOptions = ref<OptionsItem[]>([]);
|
const refOptions = ref<OptionsItem[]>([]);
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
|
@ -188,7 +189,7 @@ function emitChange() {
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div v-bind="{ ...$attrs }">
|
<div v-bind="{ class: className, style }">
|
||||||
<component
|
<component
|
||||||
:is="component"
|
:is="component"
|
||||||
v-bind="bindProps"
|
v-bind="bindProps"
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ async function calcContentHeight() {
|
||||||
footerHeight.value = footerRef.value?.offsetHeight || 0;
|
footerHeight.value = footerRef.value?.offsetHeight || 0;
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
shouldAutoHeight.value = true;
|
shouldAutoHeight.value = true;
|
||||||
}, 30);
|
}, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue