feat(财务): 新增入账管理模块
新增账单导入和账单核对功能 - 添加账单导入页面,支持微信、支付宝、银行账单文件上传 - 实现账单核对功能,包括自动核对和人工核对 - 添加多种类型订单展示组件(VIP、课程、实物商品等) - 实现订单选择和提交功能 - 添加CardList组件用于展示可选项和已选项
This commit is contained in:
197
apps/finance/src/components/card-list/api.ts
Normal file
197
apps/finance/src/components/card-list/api.ts
Normal file
@@ -0,0 +1,197 @@
|
||||
import type { CardListApi, CardListPagination, CardListProps } from './types';
|
||||
|
||||
import { bindMethods, isFunction, mergeWithArrayOverride, StateHandler } from '@vben/utils';
|
||||
|
||||
import { Store } from '@vben-core/shared/store';
|
||||
|
||||
function getDefaultState(): CardListProps {
|
||||
return {
|
||||
class: '',
|
||||
gridClass: '',
|
||||
gridOptions: {
|
||||
columns: [],
|
||||
data: [],
|
||||
showTitle: true,
|
||||
gridColumns: 3,
|
||||
pagerConfig: {
|
||||
enabled: true,
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
showTotal: (total: number) => `共 ${total} 条`,
|
||||
},
|
||||
cardHeight: 'auto',
|
||||
cardWidth: '100%',
|
||||
cardGap: '10px',
|
||||
loading: false,
|
||||
emptyText: '暂无数据',
|
||||
},
|
||||
gridEvents: {},
|
||||
};
|
||||
}
|
||||
|
||||
export class CardListApiInstance<T extends Record<string, any> = any> implements CardListApi<T> {
|
||||
public state: CardListProps<T> | null = null;
|
||||
public store: Store<CardListProps<T>>;
|
||||
|
||||
private isMounted = false;
|
||||
private stateHandler: StateHandler;
|
||||
|
||||
constructor(options: CardListProps = {}) {
|
||||
const storeState = { ...options };
|
||||
|
||||
const defaultState = getDefaultState();
|
||||
this.store = new Store<CardListProps>(mergeWithArrayOverride(storeState, defaultState), {
|
||||
onUpdate: () => {
|
||||
this.state = this.store.state;
|
||||
},
|
||||
});
|
||||
|
||||
this.state = this.store.state;
|
||||
this.stateHandler = new StateHandler();
|
||||
bindMethods(this);
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.setData([]);
|
||||
}
|
||||
|
||||
getData() {
|
||||
return (this.state?.gridOptions?.data || []).map((item) => ({ ...item })) as T[];
|
||||
}
|
||||
|
||||
/** 检查组件是否已挂载 */
|
||||
getIsMounted() {
|
||||
return this.isMounted;
|
||||
}
|
||||
|
||||
getState() {
|
||||
return this.state;
|
||||
}
|
||||
|
||||
/** 添加项目 */
|
||||
insertAt(items: T[], index?: number) {
|
||||
const data = this.getData();
|
||||
const newData = [...data];
|
||||
|
||||
if (typeof index === 'number' && index >= 0 && index <= newData.length) {
|
||||
newData.splice(index, 0, ...items);
|
||||
} else {
|
||||
// 需判断如果原数据已经存在,则不添加
|
||||
newData.push(...items);
|
||||
}
|
||||
|
||||
this.setData(newData);
|
||||
}
|
||||
|
||||
mount() {
|
||||
this.isMounted = true;
|
||||
}
|
||||
|
||||
refresh() {
|
||||
// 触发数据刷新
|
||||
const currentData = this.state?.gridOptions?.data || [];
|
||||
this.setData([...currentData] as any);
|
||||
}
|
||||
|
||||
remove(predicate: ((row: T, index: number) => boolean) | number) {
|
||||
const data = this.getData();
|
||||
|
||||
const newData =
|
||||
typeof predicate === 'number'
|
||||
? data.filter((_, index) => index !== predicate)
|
||||
: data.filter((row, index) => !predicate(row, index));
|
||||
|
||||
this.setData(newData);
|
||||
}
|
||||
|
||||
reset() {
|
||||
const defaultState = getDefaultState();
|
||||
this.setState({
|
||||
gridOptions: defaultState.gridOptions,
|
||||
});
|
||||
}
|
||||
|
||||
setData(data: T[]) {
|
||||
this.setState({
|
||||
gridOptions: {
|
||||
...this.state?.gridOptions,
|
||||
data: data as any,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
setErrorItems(errorItems: any[]) {
|
||||
// 遍历data,将errorItems中的id匹配到的item添加error属性
|
||||
const newData = this.getData().map((item) => {
|
||||
const errorData = errorItems.find((error) => error.id === item.id);
|
||||
return errorData ? { ...item, error: true } : { ...item, error: false };
|
||||
});
|
||||
this.setState({
|
||||
gridOptions: {
|
||||
...this.state?.gridOptions,
|
||||
data: newData as any,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
setLoading(loading: boolean) {
|
||||
this.setState({
|
||||
gridOptions: {
|
||||
...this.state?.gridOptions,
|
||||
loading,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
setPagination(pagination: any) {
|
||||
// 确保分页对象符合CardListPagination类型要求
|
||||
const validPagination: CardListPagination = {
|
||||
current: pagination?.current || 1,
|
||||
pageSize: pagination?.pageSize || 10,
|
||||
total: pagination?.total,
|
||||
showSizeChanger: pagination?.showSizeChanger,
|
||||
showQuickJumper: pagination?.showQuickJumper,
|
||||
showTotal: pagination?.showTotal,
|
||||
};
|
||||
|
||||
this.setState({
|
||||
gridOptions: {
|
||||
...this.state?.gridOptions,
|
||||
pagerConfig: validPagination,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
setState(
|
||||
state: ((prev: CardListProps<T>) => Partial<CardListProps<T>>) | Partial<CardListProps<T>>,
|
||||
) {
|
||||
if (isFunction(state)) {
|
||||
const stateFn = state as (prev: CardListProps<T>) => Partial<CardListProps<T>>;
|
||||
this.store.setState((prev: any) => {
|
||||
return mergeWithArrayOverride(stateFn(prev), prev);
|
||||
});
|
||||
} else {
|
||||
this.store.setState((prev: any) => mergeWithArrayOverride(state, prev));
|
||||
}
|
||||
}
|
||||
|
||||
unmount() {
|
||||
this.isMounted = false;
|
||||
this.stateHandler.reset();
|
||||
}
|
||||
|
||||
updateItem(predicate: ((row: T, index: number) => boolean) | number, newItem: Partial<T>) {
|
||||
const data = this.getData();
|
||||
|
||||
const newData = data.map((row, index) => {
|
||||
const matched = typeof predicate === 'number' ? index === predicate : predicate(row, index);
|
||||
|
||||
return matched ? { ...row, ...newItem } : row;
|
||||
});
|
||||
|
||||
this.setData(newData);
|
||||
}
|
||||
}
|
||||
393
apps/finance/src/components/card-list/card-list.vue
Normal file
393
apps/finance/src/components/card-list/card-list.vue
Normal file
@@ -0,0 +1,393 @@
|
||||
<script lang="ts" setup>
|
||||
import type { CardListProps, ExtendedCardListApi } from './types';
|
||||
|
||||
import { computed, h, nextTick, onMounted, onUnmounted, ref } from 'vue';
|
||||
|
||||
import { usePriorityValues } from '@vben/hooks';
|
||||
import { cn } from '@vben/utils';
|
||||
|
||||
import { VbenLoading } from '@vben-core/shadcn-ui';
|
||||
|
||||
import { Card, DatePicker, Empty, Input, Select } from 'ant-design-vue';
|
||||
|
||||
import SelectDropdownRender from '#/components/SelectDropdownRender/index.vue';
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {});
|
||||
|
||||
// 创建一个全局缓存对象
|
||||
const selectOptionsCache: Record<string, any[]> = {};
|
||||
|
||||
interface Props extends CardListProps {
|
||||
api: ExtendedCardListApi;
|
||||
}
|
||||
|
||||
const listData = computed(() => {
|
||||
return gridOptions.value?.data || [];
|
||||
});
|
||||
|
||||
const state = props.api?.useStore?.();
|
||||
|
||||
const { class: className, gridClass, gridOptions, gridEvents } = usePriorityValues(props, state);
|
||||
|
||||
// 计算标题字段
|
||||
const titleFieldName = computed(() => {
|
||||
const titleField = gridOptions.value?.titleField;
|
||||
if (titleField) return titleField;
|
||||
const columns = gridOptions.value?.columns || [];
|
||||
if (columns.length > 0) return columns[0]?.field || '';
|
||||
return '';
|
||||
});
|
||||
|
||||
// 卡片网格样式
|
||||
const gridStyle = computed(() => {
|
||||
const gridHeight = gridOptions.value?.gridHeight || 'auto';
|
||||
const gridColumns = gridOptions.value?.gridColumns || 3;
|
||||
const cardGap = gridOptions.value?.cardGap || '10px';
|
||||
|
||||
return {
|
||||
display: 'grid',
|
||||
gridTemplateColumns: `repeat(${gridColumns}, 1fr)`,
|
||||
gap: cardGap,
|
||||
height: gridHeight,
|
||||
overflow: 'auto',
|
||||
padding: '5px',
|
||||
};
|
||||
});
|
||||
|
||||
// 卡片样式
|
||||
const cardStyle = (item: any) => {
|
||||
const cardHeight = gridOptions.value?.cardHeight;
|
||||
const cardWidth = gridOptions.value?.cardWidth || '100%';
|
||||
|
||||
const style: any = {
|
||||
width: cardWidth,
|
||||
minWidth: 0, // 添加最小宽度为0,防止内容撑开
|
||||
overflow: 'hidden', // 添加溢出隐藏
|
||||
borderColor: item.error ? 'red' : 'inherit',
|
||||
};
|
||||
|
||||
if (cardHeight && cardHeight !== 'auto') {
|
||||
style.height = cardHeight;
|
||||
}
|
||||
|
||||
return style;
|
||||
};
|
||||
|
||||
// const errorCardStyle = (item: any) => {
|
||||
// const errorData = gridOptions.value?.errorData || [];
|
||||
// return errorData.some((errorItem) => errorItem.id === item.id)
|
||||
// ? {
|
||||
// ...cardStyle.value,
|
||||
// borderColor: 'red',
|
||||
// }
|
||||
// : false;
|
||||
// };
|
||||
|
||||
// 格式化字段值
|
||||
const formatFieldValue = (column: any, row: any) => {
|
||||
if (!column?.field) return '';
|
||||
const value = row[column.field];
|
||||
if (column?.formatter) {
|
||||
return column.formatter(value, row);
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
// 渲染编辑组件
|
||||
const renderEditComponent = (column: any, row: any) => {
|
||||
const fieldName = column.field;
|
||||
const value = row[fieldName];
|
||||
const editRender = column.editRender;
|
||||
const editProps = editRender?.props || {};
|
||||
|
||||
// 创建更新函数,避免直接修改只读对象
|
||||
const updateValue = (val: any) => {
|
||||
// 获取当前数据数组
|
||||
const currentData = gridOptions.value?.data || [];
|
||||
// 找到要修改的项的索引
|
||||
const index = currentData.findIndex((item: any) => item.id === row.id);
|
||||
if (index !== -1) {
|
||||
// 创建新的数据数组,避免直接修改原数组
|
||||
const newData = [...currentData];
|
||||
// 创建新的对象,避免直接修改原对象
|
||||
newData[index] = { ...newData[index], [fieldName]: val };
|
||||
// 更新数据
|
||||
props.api.setData(newData);
|
||||
}
|
||||
};
|
||||
|
||||
switch (editRender.name) {
|
||||
case 'date-picker':
|
||||
case 'DatePicker': {
|
||||
return h(DatePicker, {
|
||||
value,
|
||||
valueFormat: 'YYYY-MM-DD',
|
||||
...editProps,
|
||||
'onUpdate:value': updateValue,
|
||||
});
|
||||
}
|
||||
case 'Input':
|
||||
case 'input': {
|
||||
return h(Input, {
|
||||
value,
|
||||
...editProps,
|
||||
'onUpdate:value': updateValue,
|
||||
});
|
||||
}
|
||||
case 'input-number':
|
||||
case 'InputNumber': {
|
||||
return h(Input, {
|
||||
value,
|
||||
type: 'number',
|
||||
...editProps,
|
||||
'onUpdate:value': updateValue,
|
||||
});
|
||||
}
|
||||
case 'Select':
|
||||
case 'select': {
|
||||
return h(Select, {
|
||||
value,
|
||||
...editProps,
|
||||
'onUpdate:value': updateValue,
|
||||
});
|
||||
}
|
||||
case 'SelectDropdownRender':
|
||||
case 'selectDropdownRender': {
|
||||
// 创建一个响应式的选项数组
|
||||
const options = ref<any[]>([]);
|
||||
|
||||
// 创建一个唯一的缓存键,基于 row 的关键属性
|
||||
const cacheKey = `${row.paymentId}_${row.come}_${row.orderType}_${row.courseId}`;
|
||||
|
||||
// 检查缓存中是否已有数据
|
||||
if (selectOptionsCache[cacheKey]) {
|
||||
options.value = selectOptionsCache[cacheKey];
|
||||
} else if (editProps?.fetchOptions) {
|
||||
// 如果缓存中没有,则获取数据并存储到缓存
|
||||
editProps.fetchOptions(row).then((res: any) => {
|
||||
options.value = res;
|
||||
// 存储到缓存
|
||||
selectOptionsCache[cacheKey] = res;
|
||||
});
|
||||
}
|
||||
|
||||
return h(SelectDropdownRender, {
|
||||
value,
|
||||
...editProps,
|
||||
options,
|
||||
'onUpdate:value': updateValue,
|
||||
});
|
||||
}
|
||||
default: {
|
||||
return formatFieldValue(column, row);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 处理卡片点击
|
||||
const handleCardClick = (row: any) => {
|
||||
gridEvents.value?.cardClick?.(row);
|
||||
};
|
||||
|
||||
// 初始化
|
||||
async function init() {
|
||||
await nextTick();
|
||||
if (props.api?.mount) {
|
||||
props.api.mount();
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
init();
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (props.api?.unmount) {
|
||||
props.api.unmount();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="cn('h-full overflow-auto rounded-md', className)">
|
||||
<div :class="cn('rounded-md bg-card', gridClass)">
|
||||
<!-- 加载状态 -->
|
||||
<VbenLoading v-if="gridOptions?.loading" :spinning="true" />
|
||||
|
||||
<!-- 卡片列表 -->
|
||||
<div v-else-if="listData.length > 0" :style="gridStyle">
|
||||
<Card
|
||||
v-for="(item, index) in listData"
|
||||
:key="item[titleFieldName]"
|
||||
:class="gridOptions?.cardClass"
|
||||
:style="cardStyle(item)"
|
||||
@click="handleCardClick(item)"
|
||||
>
|
||||
<!-- 自定义标题 -->
|
||||
<template #title v-if="gridOptions?.showTitle">
|
||||
<div class="custom-card-title" :title="item[titleFieldName]">
|
||||
{{ item[titleFieldName] }}
|
||||
</div>
|
||||
</template>
|
||||
<!-- 卡片右上角额外内容 -->
|
||||
<template #extra v-if="$slots['card-extra']">
|
||||
<slot name="card-extra" :row="item" :index="index"></slot>
|
||||
</template>
|
||||
|
||||
<div class="card-content">
|
||||
<div
|
||||
v-for="column in gridOptions?.columns"
|
||||
:key="column?.field || ''"
|
||||
class="card-field"
|
||||
v-show="
|
||||
!column?.show ||
|
||||
(typeof column.show === 'function' ? column.show(item) : column.show)
|
||||
"
|
||||
>
|
||||
<!-- 自定义插槽 -->
|
||||
<div v-if="column?.slots?.default" class="field-item">
|
||||
<span class="field-title">{{ column?.title }}:</span>
|
||||
<span class="field-value field-edit">
|
||||
<slot
|
||||
:name="column.slots.default"
|
||||
:row="item"
|
||||
:field="column?.field || ''"
|
||||
:value="item[column?.field || '']"
|
||||
:index="index"
|
||||
></slot>
|
||||
</span>
|
||||
</div>
|
||||
<!-- 编辑渲染组件 -->
|
||||
<div v-else-if="column?.editRender" class="field-item">
|
||||
<span class="field-title">{{ column?.title }}:</span>
|
||||
<span class="field-value field-edit">
|
||||
<component :is="renderEditComponent(column, item)" />
|
||||
</span>
|
||||
</div>
|
||||
<!-- 默认显示 -->
|
||||
<div v-else class="field-item">
|
||||
<span class="field-title">{{ column?.title }}:</span>
|
||||
<span class="field-value">{{ formatFieldValue(column, item) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 卡片底部操作区域 -->
|
||||
<template #actions v-if="$slots['card-actions']">
|
||||
<slot name="card-actions" :row="item"></slot>
|
||||
</template>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<div v-else class="flex flex-col items-center justify-center py-10">
|
||||
<Empty :description="gridOptions?.emptyText || '暂无数据'" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.card-content {
|
||||
.card-field {
|
||||
margin-bottom: 8px;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.field-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.field-title {
|
||||
flex-shrink: 0;
|
||||
margin-right: 8px;
|
||||
font-weight: 500;
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
}
|
||||
|
||||
.field-value {
|
||||
flex: 1;
|
||||
word-break: break-all;
|
||||
color: rgba(0, 0, 0, 0.65);
|
||||
|
||||
&.field-edit {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
:deep(.ant-input),
|
||||
:deep(.ant-picker),
|
||||
:deep(.ant-select) {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.pagination-container {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
:deep(.ant-card) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%; // 确保卡片不超出容器宽度
|
||||
min-width: 0; // 防止内容撑开
|
||||
|
||||
.ant-card-body {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
padding: 12px !important;
|
||||
min-width: 0; // 防止内容撑开
|
||||
}
|
||||
|
||||
.card-content {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
min-width: 0; // 防止内容撑开
|
||||
}
|
||||
|
||||
.ant-card-head {
|
||||
min-height: auto;
|
||||
padding: 0 12px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
min-width: 0; // 防止内容撑开
|
||||
height: auto; // 允许头部高度自适应
|
||||
display: flex; // 使用flex布局
|
||||
|
||||
.ant-card-head-wrapper {
|
||||
align-items: flex-start; // 顶部对齐
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ant-card-head-title {
|
||||
padding: 8px 0;
|
||||
width: 100%;
|
||||
height: auto; // 允许标题高度自适应
|
||||
white-space: normal; // 允许换行
|
||||
word-wrap: break-word;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.ant-card-extra {
|
||||
padding: 8px 0; // 给extra区域添加与标题相同的内边距
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.custom-card-title {
|
||||
word-wrap: break-word;
|
||||
word-break: break-all;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
white-space: normal; // 允许换行
|
||||
line-height: 1.5;
|
||||
}
|
||||
</style>
|
||||
11
apps/finance/src/components/card-list/index.ts
Normal file
11
apps/finance/src/components/card-list/index.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
export { CardListApiInstance } from './api';
|
||||
export { default as CardList } from './card-list.vue';
|
||||
export type {
|
||||
CardListApi,
|
||||
CardListColumn,
|
||||
CardListOptions,
|
||||
CardListPagination,
|
||||
CardListProps,
|
||||
ExtendedCardListApi,
|
||||
} from './types';
|
||||
export { useCardList } from './use-card-list';
|
||||
134
apps/finance/src/components/card-list/types.ts
Normal file
134
apps/finance/src/components/card-list/types.ts
Normal file
@@ -0,0 +1,134 @@
|
||||
import type { Ref } from 'vue';
|
||||
|
||||
import type { ClassType, DeepPartial } from '@vben/types';
|
||||
|
||||
export interface CardListColumn<T = any> {
|
||||
/** 字段名 */
|
||||
field: string;
|
||||
/** 显示标题 */
|
||||
title: string;
|
||||
/** 格式化函数 */
|
||||
formatter?: (value: any, row: T) => string;
|
||||
/** 插槽配置 */
|
||||
slots?: {
|
||||
default?: string;
|
||||
};
|
||||
/** 显示条件 */
|
||||
show?: ((row: T) => boolean) | boolean;
|
||||
/** 编辑渲染组件 */
|
||||
editRender?: { name: string; props?: Record<string, any> };
|
||||
}
|
||||
|
||||
export interface CardListPagination {
|
||||
/** 是否启用分页 */
|
||||
enabled?: boolean;
|
||||
/** 当前页码 */
|
||||
current?: number;
|
||||
/** 每页条数 */
|
||||
pageSize?: number;
|
||||
/** 总条数 */
|
||||
total?: number;
|
||||
/** 显示分页大小选择器 */
|
||||
showSizeChanger?: boolean;
|
||||
/** 显示快速跳转 */
|
||||
showQuickJumper?: boolean;
|
||||
/** 显示总条数 */
|
||||
showTotal?: ((total: number, range: [number, number]) => string) | boolean;
|
||||
}
|
||||
|
||||
export interface CardListOptions<T = any> {
|
||||
/** 列配置 */
|
||||
columns?: CardListColumn<T>[];
|
||||
/** 数据源 */
|
||||
data?: T[];
|
||||
/** 是否显示卡片标题 */
|
||||
showTitle?: boolean;
|
||||
/** 标题字段名,如果不指定则使用第一个字段 */
|
||||
titleField?: string;
|
||||
/** 卡片类名 */
|
||||
cardClass?: string;
|
||||
/** 网格列数 */
|
||||
gridColumns?: number;
|
||||
/** 网格高度 */
|
||||
gridHeight?: string;
|
||||
/** 分页配置 */
|
||||
pagerConfig?: CardListPagination;
|
||||
/** 卡片高度 */
|
||||
cardHeight?: string;
|
||||
/** 卡片宽度 */
|
||||
cardWidth?: string;
|
||||
/** 卡片间距 */
|
||||
cardGap?: string;
|
||||
/** 加载状态 */
|
||||
loading?: boolean;
|
||||
/** 空状态文本 */
|
||||
emptyText?: string;
|
||||
/** 错误数据 */
|
||||
errorData?: T[];
|
||||
}
|
||||
|
||||
export interface CardListProps<T = any> {
|
||||
/** 标题 */
|
||||
title?: string;
|
||||
/** 标题帮助 */
|
||||
titleHelp?: string;
|
||||
/** 组件class */
|
||||
class?: ClassType;
|
||||
/** 网格class */
|
||||
gridClass?: ClassType;
|
||||
/** 网格配置 */
|
||||
gridOptions?: DeepPartial<CardListOptions<T>>;
|
||||
/** 网格事件 */
|
||||
gridEvents?: {
|
||||
/** 卡片点击 */
|
||||
cardClick?: (row: T) => void;
|
||||
/** 页码变化 */
|
||||
pageChange?: (page: number, pageSize: number) => void;
|
||||
/** 每页条数变化 */
|
||||
pageSizeChange?: (pageSize: number) => void;
|
||||
};
|
||||
}
|
||||
|
||||
export type CardListApi<T = any> = {
|
||||
/** 清空数据 */
|
||||
clear: () => void;
|
||||
/** 获取数据 */
|
||||
getData: () => T[];
|
||||
/** 获取状态 */
|
||||
getState: () => CardListProps<T> | null;
|
||||
/** 添加项目 */
|
||||
insertAt: (items: T[], index?: number) => void;
|
||||
/** 挂载组件 */
|
||||
mount: () => void;
|
||||
/** 刷新数据 */
|
||||
refresh: () => void;
|
||||
/** 删除项目 */
|
||||
remove: (predicate: ((row: T, index: number) => boolean) | number) => void;
|
||||
/** 重置 */
|
||||
reset: () => void;
|
||||
/** 设置数据 */
|
||||
setData: (data: T[]) => void;
|
||||
/** 设置错误数据 */
|
||||
setErrorItems: (errorItems: { id: number | string }[]) => void;
|
||||
/** 设置加载状态 */
|
||||
setLoading: (loading: boolean) => void;
|
||||
/** 设置分页 */
|
||||
setPagination: (pagination: CardListPagination) => void;
|
||||
/** 设置状态 */
|
||||
setState: (state: Partial<CardListProps<T>>) => void;
|
||||
/** 卸载组件 */
|
||||
unmount: () => void;
|
||||
/** 更新项目 */
|
||||
updateItem: (
|
||||
predicate: ((row: T, index: number) => boolean) | number,
|
||||
newItem: Partial<T>,
|
||||
) => void;
|
||||
};
|
||||
|
||||
export type ExtendedCardListApi<T = any> = CardListApi<T> & {
|
||||
useStore: <R = CardListProps<T>>(selector?: (state: CardListProps<T>) => R) => Readonly<Ref<R>>;
|
||||
};
|
||||
|
||||
export type UseVbenCardList = <T extends Record<string, any> = any>(
|
||||
options: CardListProps<T>,
|
||||
) => readonly [any, ExtendedCardListApi<T>];
|
||||
41
apps/finance/src/components/card-list/use-card-list.ts
Normal file
41
apps/finance/src/components/card-list/use-card-list.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import type { SlotsType } from 'vue';
|
||||
|
||||
import type { CardListProps, ExtendedCardListApi } from './types';
|
||||
|
||||
import { defineComponent, h, onBeforeUnmount } from 'vue';
|
||||
|
||||
import { useStore } from '@vben-core/shared/store';
|
||||
|
||||
import { CardListApiInstance } from './api';
|
||||
import CardList from './card-list.vue';
|
||||
|
||||
export function useCardList<T extends Record<string, any> = any>(options: CardListProps<T>) {
|
||||
const api = new CardListApiInstance<T>(options);
|
||||
const extendedApi = api as any as ExtendedCardListApi<T>;
|
||||
|
||||
extendedApi.useStore = (selector) => {
|
||||
return useStore(api.store, selector);
|
||||
};
|
||||
|
||||
const CardListComponent = defineComponent(
|
||||
(props: CardListProps<T>, { attrs, slots }) => {
|
||||
onBeforeUnmount(() => {
|
||||
api.unmount();
|
||||
});
|
||||
|
||||
api.setState({ ...props, ...attrs });
|
||||
return () => h(CardList, { ...props, ...attrs, api: extendedApi }, slots);
|
||||
},
|
||||
{
|
||||
name: 'CardList',
|
||||
inheritAttrs: false,
|
||||
slots: Object as SlotsType<{
|
||||
[key: string]: { field: string; index: number; row: T; value: any };
|
||||
}>,
|
||||
},
|
||||
);
|
||||
|
||||
return [CardListComponent, extendedApi] as const;
|
||||
}
|
||||
|
||||
export type useCardList = typeof useCardList;
|
||||
Reference in New Issue
Block a user