Merge remote-tracking branch 'yudao/dev' into dev
This commit is contained in:
38
packages/effects/common-ui/src/components/iframe/iframe.vue
Normal file
38
packages/effects/common-ui/src/components/iframe/iframe.vue
Normal file
@@ -0,0 +1,38 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
interface IFrameProps {
|
||||
/** iframe 的源地址 */
|
||||
src: string;
|
||||
}
|
||||
|
||||
const props = defineProps<IFrameProps>();
|
||||
|
||||
const loading = ref(true);
|
||||
const height = ref('');
|
||||
const frameRef = ref<HTMLElement | null>(null);
|
||||
|
||||
function init() {
|
||||
height.value = `${document.documentElement.clientHeight - 94.5}px`;
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
setTimeout(() => {
|
||||
init();
|
||||
}, 300);
|
||||
});
|
||||
// TODO @芋艿:优化:未来使用 vben 自带的内链实现
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-loading="loading" :style="`height:${height}`">
|
||||
<iframe
|
||||
ref="frameRef"
|
||||
:src="props.src"
|
||||
style="width: 100%; height: 100%"
|
||||
frameborder="no"
|
||||
scrolling="auto"
|
||||
></iframe>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1 @@
|
||||
export { default as IFrame } from './iframe.vue';
|
||||
@@ -5,6 +5,7 @@ export * from './count-to';
|
||||
export * from './doc-alert';
|
||||
export * from './ellipsis-text';
|
||||
export * from './icon-picker';
|
||||
export * from './iframe';
|
||||
export * from './json-viewer';
|
||||
export * from './loading';
|
||||
export * from './page';
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
export * from './use-app-config';
|
||||
export * from './use-content-maximize';
|
||||
export * from './use-design-tokens';
|
||||
export * from './use-dict';
|
||||
export * from './use-hover-toggle';
|
||||
export * from './use-pagination';
|
||||
export * from './use-refresh';
|
||||
|
||||
87
packages/effects/hooks/src/use-dict.ts
Normal file
87
packages/effects/hooks/src/use-dict.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import { useDictStore } from '@vben/stores';
|
||||
import { isObject } from '@vben/utils';
|
||||
|
||||
type ColorType = 'error' | 'info' | 'success' | 'warning';
|
||||
|
||||
export interface DictDataType {
|
||||
dictType?: string;
|
||||
label: string;
|
||||
value: boolean | number | string;
|
||||
colorType?: ColorType;
|
||||
cssClass?: string;
|
||||
}
|
||||
|
||||
export interface NumberDictDataType extends DictDataType {
|
||||
value: number;
|
||||
}
|
||||
|
||||
export interface StringDictDataType extends DictDataType {
|
||||
value: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取字典标签
|
||||
*
|
||||
* @param dictType 字典类型
|
||||
* @param value 字典值
|
||||
* @returns 字典标签
|
||||
*/
|
||||
export function getDictLabel(dictType: string, value: any) {
|
||||
const dictStore = useDictStore();
|
||||
const dictObj = dictStore.getDictData(dictType, value);
|
||||
return isObject(dictObj) ? dictObj.label : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取字典对象
|
||||
*
|
||||
* @param dictType 字典类型
|
||||
* @param value 字典值
|
||||
* @returns 字典对象
|
||||
*/
|
||||
export function getDictObj(dictType: string, value: any) {
|
||||
const dictStore = useDictStore();
|
||||
const dictObj = dictStore.getDictData(dictType, value);
|
||||
return isObject(dictObj) ? dictObj : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取字典数组 用于select radio 等
|
||||
*
|
||||
* @param dictType 字典类型
|
||||
* @param valueType 字典值类型,默认 string 类型
|
||||
* @returns 字典数组
|
||||
*/
|
||||
export function getDictOptions(
|
||||
dictType: string,
|
||||
valueType: 'boolean' | 'number' | 'string' = 'string',
|
||||
): DictDataType[] {
|
||||
const dictStore = useDictStore();
|
||||
const dictOpts = dictStore.getDictOptions(dictType);
|
||||
const dictOptions: DictDataType[] = [];
|
||||
if (dictOpts.length > 0) {
|
||||
let dictValue: boolean | number | string = '';
|
||||
dictOpts.forEach((d) => {
|
||||
switch (valueType) {
|
||||
case 'boolean': {
|
||||
dictValue = `${d.value}` === 'true';
|
||||
break;
|
||||
}
|
||||
case 'number': {
|
||||
dictValue = Number.parseInt(`${d.value}`);
|
||||
break;
|
||||
}
|
||||
case 'string': {
|
||||
dictValue = `${d.value}`;
|
||||
break;
|
||||
}
|
||||
// No default
|
||||
}
|
||||
dictOptions.push({
|
||||
value: dictValue,
|
||||
label: d.label,
|
||||
});
|
||||
});
|
||||
}
|
||||
return dictOptions.length > 0 ? dictOptions : [];
|
||||
}
|
||||
@@ -1,13 +1,27 @@
|
||||
export { AsyncComponents, setupVbenVxeTable } from './init';
|
||||
import { defineAsyncComponent } from 'vue';
|
||||
|
||||
export { setupVbenVxeTable } from './init';
|
||||
export { default as VbenVxeTableToolbar } from './table-toolbar.vue';
|
||||
export type { VxeTableGridOptions } from './types';
|
||||
export * from './use-vxe-grid';
|
||||
export { default as VbenVxeGrid } from './use-vxe-grid.vue';
|
||||
|
||||
export { useTableToolbar } from './use-vxe-toolbar';
|
||||
export * from './validation';
|
||||
|
||||
export type {
|
||||
VxeGridListeners,
|
||||
VxeGridProps,
|
||||
VxeGridPropTypes,
|
||||
VxeTableInstance,
|
||||
VxeToolbarInstance,
|
||||
} from 'vxe-table';
|
||||
|
||||
// 异步导出 vxe-table 相关组件提供给需要单独使用 vxe-table 的场景
|
||||
export const AsyncVxeTable = defineAsyncComponent(() =>
|
||||
import('vxe-table').then((mod) => mod.VxeTable),
|
||||
);
|
||||
export const AsyncVxeColumn = defineAsyncComponent(() =>
|
||||
import('vxe-table').then((mod) => mod.VxeColumn),
|
||||
);
|
||||
export const AsyncVxeToolbar = defineAsyncComponent(() =>
|
||||
import('vxe-table').then((mod) => mod.VxeToolbar),
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { SetupVxeTable } from './types';
|
||||
|
||||
import { defineAsyncComponent, defineComponent, watch } from 'vue';
|
||||
import { defineComponent, watch } from 'vue';
|
||||
|
||||
import { usePreferences } from '@vben/preferences';
|
||||
|
||||
@@ -100,18 +100,6 @@ export function initVxeTable() {
|
||||
isInit = true;
|
||||
}
|
||||
|
||||
// 异步导出 vxe-table 相关组件提供给需要单独使用 vxe-table 的场景
|
||||
const AsyncVxeTable = defineAsyncComponent(() =>
|
||||
import('vxe-table').then((mod) => mod.VxeTable),
|
||||
);
|
||||
const AsyncVxeColumn = defineAsyncComponent(() =>
|
||||
import('vxe-table').then((mod) => mod.VxeColumn),
|
||||
);
|
||||
const AsyncVxeToolbar = defineAsyncComponent(() =>
|
||||
import('vxe-table').then((mod) => mod.VxeToolbar),
|
||||
);
|
||||
export const AsyncComponents = [AsyncVxeTable, AsyncVxeColumn, AsyncVxeToolbar];
|
||||
|
||||
export function setupVbenVxeTable(setupOptions: SetupVxeTable) {
|
||||
const { configVxeTable, useVbenForm } = setupOptions;
|
||||
|
||||
|
||||
74
packages/effects/plugins/src/vxe-table/table-toolbar.vue
Normal file
74
packages/effects/plugins/src/vxe-table/table-toolbar.vue
Normal file
@@ -0,0 +1,74 @@
|
||||
<!-- add by puhui999:vxe table 工具栏二次封装,提供给 vxe 原生列表使用 -->
|
||||
<script setup lang="ts">
|
||||
import type { VxeToolbarInstance } from 'vxe-table';
|
||||
|
||||
import { ref } from 'vue';
|
||||
|
||||
import { useContentMaximize, useRefresh } from '@vben/hooks';
|
||||
import { IconifyIcon } from '@vben/icons';
|
||||
|
||||
import { VxeButton, VxeTooltip } from 'vxe-pc-ui';
|
||||
import { VxeToolbar } from 'vxe-table';
|
||||
|
||||
/** 列表工具栏封装 */
|
||||
defineOptions({ name: 'TableToolbar' });
|
||||
|
||||
const props = defineProps<{
|
||||
hiddenSearch: boolean;
|
||||
}>();
|
||||
|
||||
const emits = defineEmits(['update:hiddenSearch']);
|
||||
|
||||
const toolbarRef = ref<VxeToolbarInstance>();
|
||||
const { toggleMaximizeAndTabbarHidden, contentIsMaximize } =
|
||||
useContentMaximize();
|
||||
const { refresh } = useRefresh();
|
||||
|
||||
/** 隐藏搜索栏 */
|
||||
function onHiddenSearchBar() {
|
||||
emits('update:hiddenSearch', !props.hiddenSearch);
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
getToolbarRef: () => toolbarRef.value,
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VxeToolbar ref="toolbarRef" custom>
|
||||
<template #toolPrefix>
|
||||
<slot></slot>
|
||||
<VxeTooltip placement="bottom" content="搜索">
|
||||
<template #default>
|
||||
<VxeButton class="ml-2 font-normal" circle @click="onHiddenSearchBar">
|
||||
<IconifyIcon icon="lucide:search" :size="15" />
|
||||
</VxeButton>
|
||||
</template>
|
||||
</VxeTooltip>
|
||||
<VxeTooltip
|
||||
placement="bottom"
|
||||
:content="contentIsMaximize ? '还原' : '全屏'"
|
||||
>
|
||||
<template #default>
|
||||
<VxeButton class="ml-2 font-medium" circle @click="refresh">
|
||||
<IconifyIcon icon="lucide:refresh-cw" :size="15" />
|
||||
</VxeButton>
|
||||
</template>
|
||||
</VxeTooltip>
|
||||
<VxeTooltip placement="bottom" content="全屏">
|
||||
<template #default>
|
||||
<VxeButton
|
||||
class="ml-2 font-medium"
|
||||
circle
|
||||
@click="toggleMaximizeAndTabbarHidden"
|
||||
>
|
||||
<IconifyIcon
|
||||
:icon="contentIsMaximize ? 'lucide:minimize' : 'lucide:maximize'"
|
||||
:size="15"
|
||||
/>
|
||||
</VxeButton>
|
||||
</template>
|
||||
</VxeTooltip>
|
||||
</template>
|
||||
</VxeToolbar>
|
||||
</template>
|
||||
48
packages/effects/plugins/src/vxe-table/use-vxe-toolbar.ts
Normal file
48
packages/effects/plugins/src/vxe-table/use-vxe-toolbar.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import type { VxeTableInstance, VxeToolbarInstance } from 'vxe-table';
|
||||
|
||||
import { ref, watch } from 'vue';
|
||||
|
||||
import VbenVxeTableToolbar from './table-toolbar.vue';
|
||||
|
||||
/**
|
||||
* vxe 原生工具栏挂载封装
|
||||
* 解决每个组件使用 vxe-table 组件时都需要写一遍的问题
|
||||
*/
|
||||
export function useTableToolbar() {
|
||||
const hiddenSearchBar = ref(false); // 隐藏搜索栏
|
||||
const tableToolbarRef = ref<InstanceType<typeof VbenVxeTableToolbar>>();
|
||||
const tableRef = ref<VxeTableInstance>();
|
||||
const isBound = ref<boolean>(false);
|
||||
|
||||
/** 挂载 toolbar 工具栏 */
|
||||
async function bindTableToolbar() {
|
||||
const table = tableRef.value;
|
||||
const tableToolbar = tableToolbarRef.value;
|
||||
if (table && tableToolbar) {
|
||||
// 延迟 1 秒,确保 toolbar 组件已经挂载
|
||||
setTimeout(async () => {
|
||||
const toolbar = tableToolbar.getToolbarRef();
|
||||
if (!toolbar) {
|
||||
console.error('[toolbar 挂载失败] Table toolbar not found');
|
||||
}
|
||||
await table.connectToolbar(toolbar as VxeToolbarInstance);
|
||||
isBound.value = true;
|
||||
}, 1000); // 延迟挂载确保 toolbar 正确挂载
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => tableRef.value,
|
||||
async (val) => {
|
||||
if (!val || isBound.value) return;
|
||||
await bindTableToolbar();
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
return {
|
||||
hiddenSearchBar,
|
||||
tableToolbarRef,
|
||||
tableRef,
|
||||
};
|
||||
}
|
||||
73
packages/stores/src/modules/dict.ts
Normal file
73
packages/stores/src/modules/dict.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import { acceptHMRUpdate, defineStore } from 'pinia';
|
||||
|
||||
export interface DictItem {
|
||||
colorType?: string;
|
||||
cssClass?: string;
|
||||
label: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export type Dict = Record<string, DictItem[]>;
|
||||
|
||||
interface DictState {
|
||||
dictCache: Dict;
|
||||
}
|
||||
|
||||
export const useDictStore = defineStore('core-dict', {
|
||||
actions: {
|
||||
getDictData(dictType: string, value: any) {
|
||||
const dict = this.dictCache[dictType];
|
||||
if (!dict) {
|
||||
return undefined;
|
||||
}
|
||||
return (
|
||||
dict.find((d) => d.value === value || d.value === value.toString()) ??
|
||||
undefined
|
||||
);
|
||||
},
|
||||
getDictOptions(dictType: string) {
|
||||
const dictOptions = this.dictCache[dictType];
|
||||
if (!dictOptions) {
|
||||
return [];
|
||||
}
|
||||
return dictOptions;
|
||||
},
|
||||
setDictCache(dicts: Dict) {
|
||||
this.dictCache = dicts;
|
||||
},
|
||||
setDictCacheByApi(
|
||||
api: (params: Record<string, any>) => Promise<Record<string, any>[]>,
|
||||
params: Record<string, any> = {},
|
||||
labelField: string = 'label',
|
||||
valueField: string = 'value',
|
||||
) {
|
||||
api(params).then((dicts) => {
|
||||
const dictCacheData: Dict = {};
|
||||
dicts.forEach((dict) => {
|
||||
dictCacheData[dict.dictType] = dicts
|
||||
.filter((d) => d.dictType === dict.dictType)
|
||||
.map((d) => ({
|
||||
colorType: d.colorType,
|
||||
cssClass: d.cssClass,
|
||||
label: d[labelField],
|
||||
value: d[valueField],
|
||||
}));
|
||||
});
|
||||
this.setDictCache(dictCacheData);
|
||||
});
|
||||
},
|
||||
},
|
||||
persist: {
|
||||
// 持久化
|
||||
pick: ['dictCache'],
|
||||
},
|
||||
state: (): DictState => ({
|
||||
dictCache: {},
|
||||
}),
|
||||
});
|
||||
|
||||
// 解决热更新问题
|
||||
const hot = import.meta.hot;
|
||||
if (hot) {
|
||||
hot.accept(acceptHMRUpdate(useDictStore, hot));
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from './access';
|
||||
export * from './dict';
|
||||
export * from './tabbar';
|
||||
export * from './user';
|
||||
|
||||
Reference in New Issue
Block a user