微调-调整日期组件
This commit is contained in:
@@ -1,11 +1,10 @@
|
||||
<template>
|
||||
<div v-show="isShow" :style="style">
|
||||
<div v-show='isShow' :style='style'>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts" name="GridItem">
|
||||
import { computed, inject, Ref, ref, useAttrs, watch } from "vue";
|
||||
import { BreakPoint, Responsive } from "../interface/index";
|
||||
<script setup lang='ts' name='GridItem'>
|
||||
import { BreakPoint, Responsive } from '../interface/index'
|
||||
|
||||
type Props = {
|
||||
offset?: number;
|
||||
@@ -26,43 +25,43 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
sm: undefined,
|
||||
md: undefined,
|
||||
lg: undefined,
|
||||
xl: undefined
|
||||
});
|
||||
xl: undefined,
|
||||
})
|
||||
|
||||
const attrs = useAttrs() as { index: string };
|
||||
const isShow = ref(true);
|
||||
const attrs = useAttrs() as { index: string }
|
||||
const isShow = ref(true)
|
||||
|
||||
// 注入断点
|
||||
const breakPoint = inject<Ref<BreakPoint>>("breakPoint", ref("xl"));
|
||||
const shouldHiddenIndex = inject<Ref<number>>("shouldHiddenIndex", ref(-1));
|
||||
const breakPoint = inject<Ref<BreakPoint>>('breakPoint', ref('xl'))
|
||||
const shouldHiddenIndex = inject<Ref<number>>('shouldHiddenIndex', ref(-1))
|
||||
watch(
|
||||
() => [shouldHiddenIndex.value, breakPoint.value],
|
||||
n => {
|
||||
if (!!attrs.index) {
|
||||
isShow.value = !(n[0] !== -1 && parseInt(attrs.index) >= Number(n[0]));
|
||||
isShow.value = !(n[0] !== -1 && parseInt(attrs.index) >= Number(n[0]))
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
const gap = inject("gap", 0);
|
||||
const cols = inject("cols", ref(4));
|
||||
const gap = inject('gap', 0)
|
||||
const cols = inject('cols', ref(4))
|
||||
const style = computed(() => {
|
||||
let span = props[breakPoint.value]?.span ?? props.span;
|
||||
let offset = props[breakPoint.value]?.offset ?? props.offset;
|
||||
let span = props[breakPoint.value]?.span ?? props.span
|
||||
let offset = props[breakPoint.value]?.offset ?? props.offset
|
||||
if (props.suffix) {
|
||||
return {
|
||||
gridColumnStart: cols.value - span - offset + 2,
|
||||
gridColumnStart: cols.value - span - offset + 1,
|
||||
gridColumnEnd: `span ${span + offset}`,
|
||||
marginLeft: offset !== 0 ? `calc(((100% + ${gap}px) / ${span + offset}) * ${offset})` : "unset"
|
||||
};
|
||||
marginLeft: offset !== 0 ? `calc(((100% + ${gap}px) / ${span + offset}) * ${offset})` : 'unset',
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
gridColumn: `span ${span + offset > cols.value ? cols.value : span + offset}/span ${
|
||||
span + offset > cols.value ? cols.value : span + offset
|
||||
}`,
|
||||
marginLeft: offset !== 0 ? `calc(((100% + ${gap}px) / ${span + offset}) * ${offset})` : "unset"
|
||||
};
|
||||
marginLeft: offset !== 0 ? `calc(((100% + ${gap}px) / ${span + offset}) * ${offset})` : 'unset',
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -1,25 +1,11 @@
|
||||
<template>
|
||||
<div :style="style">
|
||||
<div :style='style'>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="Grid">
|
||||
import {
|
||||
ref,
|
||||
watch,
|
||||
useSlots,
|
||||
computed,
|
||||
provide,
|
||||
onBeforeMount,
|
||||
onMounted,
|
||||
onUnmounted,
|
||||
onDeactivated,
|
||||
onActivated,
|
||||
VNodeArrayChildren,
|
||||
VNode
|
||||
} from "vue";
|
||||
import type { BreakPoint } from "./interface/index";
|
||||
<script setup lang='ts' name='Grid'>
|
||||
import type { BreakPoint } from './interface/index'
|
||||
|
||||
type Props = {
|
||||
cols?: number | Record<BreakPoint, number>;
|
||||
@@ -32,136 +18,136 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
cols: () => ({ xs: 1, sm: 2, md: 2, lg: 3, xl: 4 }),
|
||||
collapsed: false,
|
||||
collapsedRows: 1,
|
||||
gap: 0
|
||||
});
|
||||
gap: 0,
|
||||
})
|
||||
|
||||
onBeforeMount(() => props.collapsed && findIndex());
|
||||
onBeforeMount(() => props.collapsed && findIndex())
|
||||
onMounted(() => {
|
||||
resize({ target: { innerWidth: window.innerWidth } } as unknown as UIEvent);
|
||||
window.addEventListener("resize", resize);
|
||||
});
|
||||
resize({ target: { innerWidth: window.innerWidth } } as unknown as UIEvent)
|
||||
window.addEventListener('resize', resize)
|
||||
})
|
||||
onActivated(() => {
|
||||
resize({ target: { innerWidth: window.innerWidth } } as unknown as UIEvent);
|
||||
window.addEventListener("resize", resize);
|
||||
});
|
||||
resize({ target: { innerWidth: window.innerWidth } } as unknown as UIEvent)
|
||||
window.addEventListener('resize', resize)
|
||||
})
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener("resize", resize);
|
||||
});
|
||||
window.removeEventListener('resize', resize)
|
||||
})
|
||||
onDeactivated(() => {
|
||||
window.removeEventListener("resize", resize);
|
||||
});
|
||||
window.removeEventListener('resize', resize)
|
||||
})
|
||||
|
||||
// 监听屏幕变化
|
||||
const resize = (e: UIEvent) => {
|
||||
let width = (e.target as Window).innerWidth;
|
||||
let width = (e.target as Window).innerWidth
|
||||
switch (!!width) {
|
||||
case width < 768:
|
||||
breakPoint.value = "xs";
|
||||
break;
|
||||
breakPoint.value = 'xs'
|
||||
break
|
||||
case width >= 768 && width < 992:
|
||||
breakPoint.value = "sm";
|
||||
break;
|
||||
breakPoint.value = 'sm'
|
||||
break
|
||||
case width >= 992 && width < 1200:
|
||||
breakPoint.value = "md";
|
||||
break;
|
||||
breakPoint.value = 'md'
|
||||
break
|
||||
case width >= 1200 && width < 1920:
|
||||
breakPoint.value = "lg";
|
||||
break;
|
||||
breakPoint.value = 'lg'
|
||||
break
|
||||
case width >= 1920:
|
||||
breakPoint.value = "xl";
|
||||
break;
|
||||
breakPoint.value = 'xl'
|
||||
break
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 注入 gap 间距
|
||||
provide("gap", Array.isArray(props.gap) ? props.gap[0] : props.gap);
|
||||
provide('gap', Array.isArray(props.gap) ? props.gap[0] : props.gap)
|
||||
|
||||
// 注入响应式断点
|
||||
let breakPoint = ref<BreakPoint>("xl");
|
||||
provide("breakPoint", breakPoint);
|
||||
let breakPoint = ref<BreakPoint>('xl')
|
||||
provide('breakPoint', breakPoint)
|
||||
|
||||
// 注入要开始折叠的 index
|
||||
const hiddenIndex = ref(-1);
|
||||
provide("shouldHiddenIndex", hiddenIndex);
|
||||
const hiddenIndex = ref(-1)
|
||||
provide('shouldHiddenIndex', hiddenIndex)
|
||||
|
||||
// 注入 cols
|
||||
const gridCols = computed(() => {
|
||||
if (typeof props.cols === "object") return props.cols[breakPoint.value] ?? props.cols;
|
||||
return props.cols;
|
||||
});
|
||||
provide("cols", gridCols);
|
||||
if (typeof props.cols === 'object') return props.cols[breakPoint.value] ?? props.cols
|
||||
return props.cols
|
||||
})
|
||||
provide('cols', gridCols)
|
||||
|
||||
// 寻找需要开始折叠的字段 index
|
||||
const slots = useSlots().default!();
|
||||
const slots = useSlots().default!()
|
||||
|
||||
const findIndex = () => {
|
||||
let fields: VNodeArrayChildren = [];
|
||||
let suffix: VNode | null = null;
|
||||
let fields: VNodeArrayChildren = []
|
||||
let suffix: VNode | null = null
|
||||
slots.forEach((slot: any) => {
|
||||
// suffix
|
||||
if (typeof slot.type === "object" && slot.type.name === "GridItem" && slot.props?.suffix !== undefined) suffix = slot;
|
||||
if (typeof slot.type === 'object' && slot.type.name === 'GridItem' && slot.props?.suffix !== undefined) suffix = slot
|
||||
// slot children
|
||||
if (typeof slot.type === "symbol" && Array.isArray(slot.children)) fields.push(...slot.children);
|
||||
});
|
||||
if (typeof slot.type === 'symbol' && Array.isArray(slot.children)) fields.push(...slot.children)
|
||||
})
|
||||
|
||||
// 计算 suffix 所占用的列
|
||||
let suffixCols = 0;
|
||||
let suffixCols = 0
|
||||
if (suffix) {
|
||||
suffixCols =
|
||||
((suffix as VNode).props![breakPoint.value]?.span ?? (suffix as VNode).props?.span ?? 1) +
|
||||
((suffix as VNode).props![breakPoint.value]?.offset ?? (suffix as VNode).props?.offset ?? 0);
|
||||
((suffix as VNode).props![breakPoint.value]?.offset ?? (suffix as VNode).props?.offset ?? 0)
|
||||
}
|
||||
try {
|
||||
let find = false;
|
||||
let find = false
|
||||
fields.reduce((prev = 0, current, index) => {
|
||||
prev +=
|
||||
((current as VNode)!.props![breakPoint.value]?.span ?? (current as VNode)!.props?.span ?? 1) +
|
||||
((current as VNode)!.props![breakPoint.value]?.offset ?? (current as VNode)!.props?.offset ?? 0);
|
||||
((current as VNode)!.props![breakPoint.value]?.offset ?? (current as VNode)!.props?.offset ?? 0)
|
||||
if (Number(prev) > props.collapsedRows * gridCols.value - suffixCols) {
|
||||
hiddenIndex.value = index;
|
||||
find = true;
|
||||
throw "find it";
|
||||
hiddenIndex.value = index
|
||||
find = true
|
||||
throw 'find it'
|
||||
}
|
||||
return prev;
|
||||
}, 0);
|
||||
if (!find) hiddenIndex.value = -1;
|
||||
return prev
|
||||
}, 0)
|
||||
if (!find) hiddenIndex.value = -1
|
||||
} catch (e) {
|
||||
// console.warn(e);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 断点变化时执行 findIndex
|
||||
watch(
|
||||
() => breakPoint.value,
|
||||
() => {
|
||||
if (props.collapsed) findIndex();
|
||||
}
|
||||
);
|
||||
if (props.collapsed) findIndex()
|
||||
},
|
||||
)
|
||||
|
||||
// 监听 collapsed
|
||||
watch(
|
||||
() => props.collapsed,
|
||||
value => {
|
||||
if (value) return findIndex();
|
||||
hiddenIndex.value = -1;
|
||||
}
|
||||
);
|
||||
if (value) return findIndex()
|
||||
hiddenIndex.value = -1
|
||||
},
|
||||
)
|
||||
|
||||
// 设置间距
|
||||
const gridGap = computed(() => {
|
||||
if (typeof props.gap === "number") return `${props.gap}px`;
|
||||
if (Array.isArray(props.gap)) return `${props.gap[1]}px ${props.gap[0]}px`;
|
||||
return "unset";
|
||||
});
|
||||
if (typeof props.gap === 'number') return `${props.gap}px`
|
||||
if (Array.isArray(props.gap)) return `${props.gap[1]}px ${props.gap[0]}px`
|
||||
return 'unset'
|
||||
})
|
||||
|
||||
// 设置 style
|
||||
const style = computed(() => {
|
||||
return {
|
||||
display: "grid",
|
||||
display: 'grid',
|
||||
gridGap: gridGap.value,
|
||||
gridTemplateColumns: `repeat(${gridCols.value}, minmax(0, 1fr))`
|
||||
};
|
||||
});
|
||||
gridTemplateColumns: `repeat(${gridCols.value}, minmax(0, 1fr))`,
|
||||
}
|
||||
})
|
||||
|
||||
defineExpose({ breakPoint });
|
||||
defineExpose({ breakPoint })
|
||||
</script>
|
||||
|
||||
@@ -1,119 +1,122 @@
|
||||
<template>
|
||||
<!-- 查询表单 -->
|
||||
<SearchForm
|
||||
v-show="isShowSearch"
|
||||
:search="_search"
|
||||
:reset="_reset"
|
||||
:columns="searchColumns"
|
||||
:search-param="searchParam"
|
||||
:search-col="searchCol"
|
||||
v-show='isShowSearch'
|
||||
:search='_search'
|
||||
:reset='_reset'
|
||||
:columns='searchColumns'
|
||||
:search-param='searchParam'
|
||||
:search-col='searchCol'
|
||||
/>
|
||||
|
||||
<!-- 表格主体 -->
|
||||
<div class="card table-main">
|
||||
<div class='card table-main'>
|
||||
<!-- 表格头部 操作按钮 -->
|
||||
<div class="table-header">
|
||||
<div class="header-button-lf">
|
||||
<slot name="tableHeader" :selected-list="selectedList" :selected-list-ids="selectedListIds" :is-selected="isSelected" />
|
||||
<div class='table-header'>
|
||||
<div class='header-button-lf'>
|
||||
<slot name='tableHeader' :selected-list='selectedList' :selected-list-ids='selectedListIds'
|
||||
:is-selected='isSelected' />
|
||||
</div>
|
||||
<div v-if="toolButton" class="header-button-ri">
|
||||
<slot name="toolButton">
|
||||
<el-button v-if="showToolButton('refresh')" :icon="Refresh" circle @click="getTableList" />
|
||||
<el-button v-if="showToolButton('setting') && columns.length" :icon="Operation" circle @click="openColSetting" />
|
||||
<div v-if='toolButton' class='header-button-ri'>
|
||||
<slot name='toolButton'>
|
||||
<el-button v-if="showToolButton('refresh')" :icon='Refresh' circle @click='getTableList' />
|
||||
<el-button v-if="showToolButton('setting') && columns.length" :icon='Operation' circle
|
||||
@click='openColSetting' />
|
||||
<el-button
|
||||
v-if="showToolButton('search') && searchColumns?.length"
|
||||
:icon="Search"
|
||||
:icon='Search'
|
||||
circle
|
||||
@click="isShowSearch = !isShowSearch"
|
||||
@click='isShowSearch = !isShowSearch'
|
||||
/>
|
||||
</slot>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 表格主体 -->
|
||||
<el-table
|
||||
ref="tableRef"
|
||||
v-bind="$attrs"
|
||||
:id="uuid"
|
||||
:data="processTableData"
|
||||
:border="border"
|
||||
:row-key="rowKey"
|
||||
@selection-change="selectionChange"
|
||||
ref='tableRef'
|
||||
v-bind='$attrs'
|
||||
:id='uuid'
|
||||
:data='processTableData'
|
||||
:border='border'
|
||||
:row-key='rowKey'
|
||||
@selection-change='selectionChange'
|
||||
>
|
||||
<!-- 默认插槽 -->
|
||||
<slot />
|
||||
<template v-for="item in tableColumns" :key="item">
|
||||
<template v-for='item in tableColumns' :key='item'>
|
||||
<!-- selection || radio || index || expand || sort -->
|
||||
<el-table-column
|
||||
v-if="item.type && columnTypes.includes(item.type)"
|
||||
v-bind="item"
|
||||
v-if='item.type && columnTypes.includes(item.type)'
|
||||
v-bind='item'
|
||||
:align="item.align ?? 'center'"
|
||||
:reserve-selection="item.type == 'selection'"
|
||||
>
|
||||
<template #default="scope">
|
||||
<template #default='scope'>
|
||||
<!-- expand -->
|
||||
<template v-if="item.type == 'expand'">
|
||||
<component :is="item.render" v-bind="scope" v-if="item.render" />
|
||||
<slot v-else :name="item.type" v-bind="scope" />
|
||||
<component :is='item.render' v-bind='scope' v-if='item.render' />
|
||||
<slot v-else :name='item.type' v-bind='scope' />
|
||||
</template>
|
||||
<!-- radio -->
|
||||
<el-radio v-if="item.type == 'radio'" v-model="radio" :label="scope.row[rowKey]">
|
||||
<el-radio v-if="item.type == 'radio'" v-model='radio' :label='scope.row[rowKey]'>
|
||||
<i></i>
|
||||
</el-radio>
|
||||
<!-- sort -->
|
||||
<el-tag v-if="item.type == 'sort'" class="move">
|
||||
<el-icon> <DCaret /></el-icon>
|
||||
<el-tag v-if="item.type == 'sort'" class='move'>
|
||||
<el-icon>
|
||||
<DCaret />
|
||||
</el-icon>
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<!-- other -->
|
||||
<TableColumn v-else :column="item">
|
||||
<template v-for="slot in Object.keys($slots)" #[slot]="scope">
|
||||
<slot :name="slot" v-bind="scope" />
|
||||
<TableColumn v-else :column='item'>
|
||||
<template v-for='slot in Object.keys($slots)' #[slot]='scope'>
|
||||
<slot :name='slot' v-bind='scope' />
|
||||
</template>
|
||||
</TableColumn>
|
||||
</template>
|
||||
<!-- 插入表格最后一行之后的插槽 -->
|
||||
<template #append>
|
||||
<slot name="append" />
|
||||
<slot name='append' />
|
||||
</template>
|
||||
<!-- 无数据 -->
|
||||
<template #empty>
|
||||
<div class="table-empty">
|
||||
<slot name="empty">
|
||||
<img src="@/assets/images/notData.png" alt="notData" />
|
||||
<div class='table-empty'>
|
||||
<slot name='empty'>
|
||||
<img src='@/assets/images/notData.png' alt='notData' />
|
||||
<div>暂无数据</div>
|
||||
</slot>
|
||||
</div>
|
||||
</template>
|
||||
</el-table>
|
||||
<!-- 分页组件 -->
|
||||
<slot name="pagination">
|
||||
<slot name='pagination'>
|
||||
<Pagination
|
||||
v-if="pagination"
|
||||
:pageable="pageable"
|
||||
:handle-size-change="handleSizeChange"
|
||||
:handle-current-change="handleCurrentChange"
|
||||
v-if='pagination'
|
||||
:pageable='pageable'
|
||||
:handle-size-change='handleSizeChange'
|
||||
:handle-current-change='handleCurrentChange'
|
||||
/>
|
||||
</slot>
|
||||
</div>
|
||||
<!-- 列设置 -->
|
||||
<ColSetting v-if="toolButton" ref="colRef" v-model:col-setting="colSetting" />
|
||||
<ColSetting v-if='toolButton' ref='colRef' v-model:col-setting='colSetting' />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="ProTable">
|
||||
import { ref, watch, provide, onMounted, unref, computed, reactive } from "vue";
|
||||
import { ElTable } from "element-plus";
|
||||
import { useTable } from "@/hooks/useTable";
|
||||
import { useSelection } from "@/hooks/useSelection";
|
||||
import { BreakPoint } from "@/components/Grid/interface";
|
||||
import { ColumnProps, TypeProps } from "@/components/ProTable/interface";
|
||||
import { Refresh, Operation, Search } from "@element-plus/icons-vue";
|
||||
import { generateUUID, handleProp } from "@/utils";
|
||||
import SearchForm from "@/components/SearchForm/index.vue";
|
||||
import Pagination from "./components/Pagination.vue";
|
||||
import ColSetting from "./components/ColSetting.vue";
|
||||
import TableColumn from "./components/TableColumn.vue";
|
||||
import Sortable from "sortablejs";
|
||||
<script setup lang='ts' name='ProTable'>
|
||||
import { ElTable } from 'element-plus'
|
||||
import { useTable } from '@/hooks/useTable'
|
||||
import { useSelection } from '@/hooks/useSelection'
|
||||
import { BreakPoint } from '@/components/Grid/interface'
|
||||
import { ColumnProps, TypeProps } from '@/components/ProTable/interface'
|
||||
import { Refresh, Operation, Search } from '@element-plus/icons-vue'
|
||||
import { generateUUID, handleProp } from '@/utils'
|
||||
import SearchForm from '@/components/SearchForm/index.vue'
|
||||
import Pagination from './components/Pagination.vue'
|
||||
import ColSetting from './components/ColSetting.vue'
|
||||
import TableColumn from './components/TableColumn.vue'
|
||||
import Sortable from 'sortablejs'
|
||||
|
||||
export interface ProTableProps {
|
||||
columns: ColumnProps[]; // 列配置项 ==> 必传
|
||||
@@ -126,7 +129,7 @@ export interface ProTableProps {
|
||||
pagination?: boolean; // 是否需要分页组件 ==> 非必传(默认为true)
|
||||
initParam?: any; // 初始化请求参数 ==> 非必传(默认为{})
|
||||
border?: boolean; // 是否带有纵向边框 ==> 非必传(默认为true)
|
||||
toolButton?: ("refresh" | "setting" | "search")[] | boolean; // 是否显示表格功能按钮 ==> 非必传(默认为true)
|
||||
toolButton?: ('refresh' | 'setting' | 'search')[] | boolean; // 是否显示表格功能按钮 ==> 非必传(默认为true)
|
||||
rowKey?: string; // 行数据的 Key,用来优化 Table 的渲染,当表格数据多选时,所指定的 id ==> 非必传(默认为 id)
|
||||
searchCol?: number | Record<BreakPoint, number>; // 表格搜索项 每列占比配置 ==> 非必传 { xs: 1, sm: 2, md: 2, lg: 3, xl: 4 }
|
||||
}
|
||||
@@ -139,161 +142,171 @@ const props = withDefaults(defineProps<ProTableProps>(), {
|
||||
initParam: {},
|
||||
border: true,
|
||||
toolButton: true,
|
||||
rowKey: "id",
|
||||
searchCol: () => ({ xs: 1, sm: 2, md: 2, lg: 3, xl: 4 })
|
||||
});
|
||||
rowKey: 'id',
|
||||
searchCol: () => ({ xs: 1, sm: 2, md: 2, lg: 3, xl: 4 }),
|
||||
})
|
||||
|
||||
// table 实例
|
||||
const tableRef = ref<InstanceType<typeof ElTable>>();
|
||||
const tableRef = ref<InstanceType<typeof ElTable>>()
|
||||
|
||||
// 生成组件唯一id
|
||||
const uuid = ref("id-" + generateUUID());
|
||||
const uuid = ref('id-' + generateUUID())
|
||||
|
||||
// column 列类型
|
||||
const columnTypes: TypeProps[] = ["selection", "radio", "index", "expand", "sort"];
|
||||
const columnTypes: TypeProps[] = ['selection', 'radio', 'index', 'expand', 'sort']
|
||||
|
||||
// 是否显示搜索模块
|
||||
const isShowSearch = ref(true);
|
||||
const isShowSearch = ref(true)
|
||||
|
||||
// 控制 ToolButton 显示
|
||||
const showToolButton = (key: "refresh" | "setting" | "search") => {
|
||||
return Array.isArray(props.toolButton) ? props.toolButton.includes(key) : props.toolButton;
|
||||
};
|
||||
const showToolButton = (key: 'refresh' | 'setting' | 'search') => {
|
||||
return Array.isArray(props.toolButton) ? props.toolButton.includes(key) : props.toolButton
|
||||
}
|
||||
|
||||
// 单选值
|
||||
const radio = ref("");
|
||||
const radio = ref('')
|
||||
|
||||
// 表格多选 Hooks
|
||||
const { selectionChange, selectedList, selectedListIds, isSelected } = useSelection(props.rowKey);
|
||||
const { selectionChange, selectedList, selectedListIds, isSelected } = useSelection(props.rowKey)
|
||||
|
||||
// 表格操作 Hooks
|
||||
const { tableData, pageable, searchParam, searchInitParam, getTableList, search, reset, handleSizeChange, handleCurrentChange } =
|
||||
useTable(props.requestApi, props.initParam, props.pagination, props.dataCallback, props.requestError);
|
||||
const {
|
||||
tableData,
|
||||
pageable,
|
||||
searchParam,
|
||||
searchInitParam,
|
||||
getTableList,
|
||||
search,
|
||||
reset,
|
||||
handleSizeChange,
|
||||
handleCurrentChange,
|
||||
} =
|
||||
useTable(props.requestApi, props.initParam, props.pagination, props.dataCallback, props.requestError)
|
||||
|
||||
// 清空选中数据列表
|
||||
const clearSelection = () => tableRef.value!.clearSelection();
|
||||
const clearSelection = () => tableRef.value!.clearSelection()
|
||||
|
||||
// 初始化表格数据 && 拖拽排序
|
||||
onMounted(() => {
|
||||
dragSort();
|
||||
props.requestAuto && getTableList();
|
||||
props.data && (pageable.value.total = props.data.length);
|
||||
});
|
||||
dragSort()
|
||||
props.requestAuto && getTableList()
|
||||
props.data && (pageable.value.total = props.data.length)
|
||||
})
|
||||
|
||||
// 处理表格数据
|
||||
const processTableData = computed(() => {
|
||||
if (!props.data) return tableData.value;
|
||||
if (!props.pagination) return props.data;
|
||||
if (!props.data) return tableData.value
|
||||
if (!props.pagination) return props.data
|
||||
return props.data.slice(
|
||||
(pageable.value.pageNum - 1) * pageable.value.pageSize,
|
||||
pageable.value.pageSize * pageable.value.pageNum
|
||||
);
|
||||
});
|
||||
pageable.value.pageSize * pageable.value.pageNum,
|
||||
)
|
||||
})
|
||||
|
||||
// 监听页面 initParam 改化,重新获取表格数据
|
||||
watch(() => props.initParam, getTableList, { deep: true });
|
||||
watch(() => props.initParam, getTableList, { deep: true })
|
||||
|
||||
// 接收 columns 并设置为响应式
|
||||
const tableColumns = reactive<ColumnProps[]>(props.columns);
|
||||
const tableColumns = reactive<ColumnProps[]>(props.columns)
|
||||
|
||||
// 扁平化 columns
|
||||
const flatColumns = computed(() => flatColumnsFunc(tableColumns));
|
||||
const flatColumns = computed(() => flatColumnsFunc(tableColumns))
|
||||
|
||||
// 定义 enumMap 存储 enum 值(避免异步请求无法格式化单元格内容 || 无法填充搜索下拉选择)
|
||||
const enumMap = ref(new Map<string, { [key: string]: any }[]>());
|
||||
const enumMap = ref(new Map<string, { [key: string]: any }[]>())
|
||||
const setEnumMap = async ({ prop, enum: enumValue }: ColumnProps) => {
|
||||
if (!enumValue) return;
|
||||
if (!enumValue) return
|
||||
|
||||
// 如果当前 enumMap 存在相同的值 return
|
||||
if (enumMap.value.has(prop!) && (typeof enumValue === "function" || enumMap.value.get(prop!) === enumValue)) return;
|
||||
if (enumMap.value.has(prop!) && (typeof enumValue === 'function' || enumMap.value.get(prop!) === enumValue)) return
|
||||
|
||||
// 当前 enum 为静态数据,则直接存储到 enumMap
|
||||
if (typeof enumValue !== "function") return enumMap.value.set(prop!, unref(enumValue!));
|
||||
if (typeof enumValue !== 'function') return enumMap.value.set(prop!, unref(enumValue!))
|
||||
|
||||
// 为了防止接口执行慢,而存储慢,导致重复请求,所以预先存储为[],接口返回后再二次存储
|
||||
enumMap.value.set(prop!, []);
|
||||
enumMap.value.set(prop!, [])
|
||||
|
||||
// 当前 enum 为后台数据需要请求数据,则调用该请求接口,并存储到 enumMap
|
||||
const { data } = await enumValue();
|
||||
enumMap.value.set(prop!, data);
|
||||
};
|
||||
const { data } = await enumValue()
|
||||
enumMap.value.set(prop!, data)
|
||||
}
|
||||
|
||||
// 注入 enumMap
|
||||
provide("enumMap", enumMap);
|
||||
provide('enumMap', enumMap)
|
||||
|
||||
// 扁平化 columns 的方法
|
||||
const flatColumnsFunc = (columns: ColumnProps[], flatArr: ColumnProps[] = []) => {
|
||||
columns.forEach(async col => {
|
||||
if (col._children?.length) flatArr.push(...flatColumnsFunc(col._children));
|
||||
flatArr.push(col);
|
||||
if (col._children?.length) flatArr.push(...flatColumnsFunc(col._children))
|
||||
flatArr.push(col)
|
||||
|
||||
// column 添加默认 isShow && isSetting && isFilterEnum 属性值
|
||||
col.isShow = col.isShow ?? true;
|
||||
col.isSetting = col.isSetting ?? true;
|
||||
col.isFilterEnum = col.isFilterEnum ?? true;
|
||||
col.isShow = col.isShow ?? true
|
||||
col.isSetting = col.isSetting ?? true
|
||||
col.isFilterEnum = col.isFilterEnum ?? true
|
||||
|
||||
// 设置 enumMap
|
||||
await setEnumMap(col);
|
||||
});
|
||||
return flatArr.filter(item => !item._children?.length);
|
||||
};
|
||||
await setEnumMap(col)
|
||||
})
|
||||
return flatArr.filter(item => !item._children?.length)
|
||||
}
|
||||
|
||||
// 过滤需要搜索的配置项 && 排序
|
||||
const searchColumns = computed(() => {
|
||||
return flatColumns.value
|
||||
?.filter(item => item.search?.el || item.search?.render)
|
||||
.sort((a, b) => a.search!.order! - b.search!.order!);
|
||||
});
|
||||
.sort((a, b) => a.search!.order! - b.search!.order!)
|
||||
})
|
||||
|
||||
// 设置 搜索表单默认排序 && 搜索表单项的默认值
|
||||
searchColumns.value?.forEach((column, index) => {
|
||||
column.search!.order = column.search?.order ?? index + 2;
|
||||
const key = column.search?.key ?? handleProp(column.prop!);
|
||||
const defaultValue = column.search?.defaultValue;
|
||||
column.search!.order = column.search?.order ?? index + 2
|
||||
const key = column.search?.key ?? handleProp(column.prop!)
|
||||
const defaultValue = column.search?.defaultValue
|
||||
if (defaultValue !== undefined && defaultValue !== null) {
|
||||
searchParam.value[key] = defaultValue;
|
||||
searchInitParam.value[key] = defaultValue;
|
||||
searchParam.value[key] = defaultValue
|
||||
searchInitParam.value[key] = defaultValue
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
// 列设置 ==> 需要过滤掉不需要设置的列
|
||||
const colRef = ref();
|
||||
const colRef = ref()
|
||||
const colSetting = tableColumns!.filter(item => {
|
||||
const { type, prop, isSetting } = item;
|
||||
return !columnTypes.includes(type!) && prop !== "operation" && isSetting;
|
||||
});
|
||||
const openColSetting = () => colRef.value.openColSetting();
|
||||
const { type, prop, isSetting } = item
|
||||
return !columnTypes.includes(type!) && prop !== 'operation' && isSetting
|
||||
})
|
||||
const openColSetting = () => colRef.value.openColSetting()
|
||||
|
||||
// 定义 emit 事件
|
||||
const emit = defineEmits<{
|
||||
search: [];
|
||||
reset: [];
|
||||
dragSort: [{ newIndex?: number; oldIndex?: number }];
|
||||
}>();
|
||||
}>()
|
||||
|
||||
const _search = () => {
|
||||
search();
|
||||
emit("search");
|
||||
};
|
||||
search()
|
||||
emit('search')
|
||||
}
|
||||
|
||||
const _reset = () => {
|
||||
reset();
|
||||
emit("reset");
|
||||
};
|
||||
reset()
|
||||
emit('reset')
|
||||
}
|
||||
|
||||
// 表格拖拽排序
|
||||
const dragSort = () => {
|
||||
const tbody = document.querySelector(`#${uuid.value} tbody`) as HTMLElement;
|
||||
const tbody = document.querySelector(`#${uuid.value} tbody`) as HTMLElement
|
||||
Sortable.create(tbody, {
|
||||
handle: ".move",
|
||||
handle: '.move',
|
||||
animation: 300,
|
||||
onEnd({ newIndex, oldIndex }) {
|
||||
const [removedItem] = processTableData.value.splice(oldIndex!, 1);
|
||||
processTableData.value.splice(newIndex!, 0, removedItem);
|
||||
emit("dragSort", { newIndex, oldIndex });
|
||||
const [removedItem] = processTableData.value.splice(oldIndex!, 1)
|
||||
processTableData.value.splice(newIndex!, 0, removedItem)
|
||||
emit('dragSort', { newIndex, oldIndex })
|
||||
},
|
||||
})
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 暴露给父组件的参数和方法 (外部需要什么,都可以从这里暴露出去)
|
||||
defineExpose({
|
||||
@@ -314,6 +327,6 @@ defineExpose({
|
||||
handleSizeChange,
|
||||
handleCurrentChange,
|
||||
clearSelection,
|
||||
enumMap
|
||||
});
|
||||
enumMap,
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -1,29 +1,29 @@
|
||||
<template>
|
||||
<div v-if="columns.length" class="card table-search">
|
||||
<el-form ref="formRef" :model="searchParam">
|
||||
<Grid ref="gridRef" :collapsed="collapsed" :gap="[20, 0]" :cols="searchCol">
|
||||
<GridItem v-for="(item, index) in columns" :key="item.prop" v-bind="getResponsive(item)" :index="index">
|
||||
<div v-if='columns.length' class='card table-search'>
|
||||
<el-form ref='formRef' :model='searchParam'>
|
||||
<Grid ref='gridRef' :collapsed='collapsed' :gap='[20, 0]' :cols='searchCol'>
|
||||
<GridItem v-for='(item, index) in columns' :key='item.prop' v-bind='getResponsive(item)' :index='index + 1'>
|
||||
<el-form-item>
|
||||
<template #label>
|
||||
<el-space :size="4">
|
||||
<el-space :size='4'>
|
||||
<span>{{ `${item.search?.label ?? item.label}` }}</span>
|
||||
<el-tooltip v-if="item.search?.tooltip" effect="dark" :content="item.search?.tooltip" placement="top">
|
||||
<el-tooltip v-if='item.search?.tooltip' effect='dark' :content='item.search?.tooltip' placement='top'>
|
||||
<i :class="'iconfont icon-yiwen'"></i>
|
||||
</el-tooltip>
|
||||
</el-space>
|
||||
<span> :</span>
|
||||
</template>
|
||||
<SearchFormItem :column="item" :search-param="searchParam" />
|
||||
<SearchFormItem :column='item' :search-param='searchParam' />
|
||||
</el-form-item>
|
||||
</GridItem>
|
||||
<GridItem suffix>
|
||||
<div class="operation">
|
||||
<el-button type="primary" :icon="Search" @click="search"> 搜索 </el-button>
|
||||
<el-button :icon="Delete" @click="reset"> 重置 </el-button>
|
||||
<el-button v-if="showCollapse" type="primary" link class="search-isOpen" @click="collapsed = !collapsed">
|
||||
{{ collapsed ? "展开" : "合并" }}
|
||||
<el-icon class="el-icon--right">
|
||||
<component :is="collapsed ? ArrowDown : ArrowUp"></component>
|
||||
<div class='operation'>
|
||||
<el-button type='primary' :icon='Search' @click='search'> 搜索</el-button>
|
||||
<el-button :icon='Delete' @click='reset'> 重置</el-button>
|
||||
<el-button v-if='showCollapse' type='primary' link class='search-isOpen' @click='collapsed = !collapsed'>
|
||||
{{ collapsed ? '展开' : '合并' }}
|
||||
<el-icon class='el-icon--right'>
|
||||
<component :is='collapsed ? ArrowDown : ArrowUp'></component>
|
||||
</el-icon>
|
||||
</el-button>
|
||||
</div>
|
||||
@@ -32,14 +32,15 @@
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts" name="SearchForm">
|
||||
import { computed, ref } from "vue";
|
||||
import { ColumnProps } from "@/components/ProTable/interface";
|
||||
import { BreakPoint } from "@/components/Grid/interface";
|
||||
import { Delete, Search, ArrowDown, ArrowUp } from "@element-plus/icons-vue";
|
||||
import SearchFormItem from "./components/SearchFormItem.vue";
|
||||
import Grid from "@/components/Grid/index.vue";
|
||||
import GridItem from "@/components/Grid/components/GridItem.vue";
|
||||
<script setup lang='ts' name='SearchForm'>
|
||||
import { ColumnProps } from '@/components/ProTable/interface'
|
||||
import { BreakPoint } from '@/components/Grid/interface'
|
||||
import { Delete, Search, ArrowDown, ArrowUp } from '@element-plus/icons-vue'
|
||||
import SearchFormItem from './components/SearchFormItem.vue'
|
||||
import Grid from '@/components/Grid/index.vue'
|
||||
import GridItem from '@/components/Grid/components/GridItem.vue'
|
||||
|
||||
|
||||
|
||||
interface ProTableProps {
|
||||
columns?: ColumnProps[]; // 搜索配置列
|
||||
@@ -52,8 +53,12 @@ interface ProTableProps {
|
||||
// 默认值
|
||||
const props = withDefaults(defineProps<ProTableProps>(), {
|
||||
columns: () => [],
|
||||
searchParam: () => ({})
|
||||
});
|
||||
searchParam: () => ({}),
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
console.log(props.columns)
|
||||
})
|
||||
|
||||
// 获取响应式设置
|
||||
const getResponsive = (item: ColumnProps) => {
|
||||
@@ -64,31 +69,31 @@ const getResponsive = (item: ColumnProps) => {
|
||||
sm: item.search?.sm,
|
||||
md: item.search?.md,
|
||||
lg: item.search?.lg,
|
||||
xl: item.search?.xl
|
||||
};
|
||||
};
|
||||
xl: item.search?.xl,
|
||||
}
|
||||
}
|
||||
|
||||
// 是否默认折叠搜索项
|
||||
const collapsed = ref(true);
|
||||
const collapsed = ref(true)
|
||||
|
||||
// 获取响应式断点
|
||||
const gridRef = ref();
|
||||
const breakPoint = computed<BreakPoint>(() => gridRef.value?.breakPoint);
|
||||
const gridRef = ref()
|
||||
const breakPoint = computed<BreakPoint>(() => gridRef.value?.breakPoint)
|
||||
|
||||
// 判断是否显示 展开/合并 按钮
|
||||
const showCollapse = computed(() => {
|
||||
let show = false;
|
||||
let show = false
|
||||
props.columns.reduce((prev, current) => {
|
||||
prev +=
|
||||
(current.search![breakPoint.value]?.span ?? current.search?.span ?? 1) +
|
||||
(current.search![breakPoint.value]?.offset ?? current.search?.offset ?? 0);
|
||||
if (typeof props.searchCol !== "number") {
|
||||
if (prev >= props.searchCol[breakPoint.value]) show = true;
|
||||
(current.search![breakPoint.value]?.offset ?? current.search?.offset ?? 0)
|
||||
if (typeof props.searchCol !== 'number') {
|
||||
if (prev >= props.searchCol[breakPoint.value]) show = true
|
||||
} else {
|
||||
if (prev >= props.searchCol) show = true;
|
||||
if (prev >= props.searchCol) show = true
|
||||
}
|
||||
return prev;
|
||||
}, 0);
|
||||
return show;
|
||||
});
|
||||
return prev
|
||||
}, 0)
|
||||
return show
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -2,24 +2,29 @@
|
||||
.time-control {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin:0 0 0 300px ; /* 使整体居中 */
|
||||
}
|
||||
|
||||
.select {
|
||||
margin-right: 10px; /* 下拉框右侧间距 */
|
||||
width: 90px; /* 下拉框宽度 */
|
||||
}
|
||||
|
||||
.date-display {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-right: 10px; /* 日期选择器右侧间距 */
|
||||
width: 250px;
|
||||
width: 320px;
|
||||
}
|
||||
|
||||
.date-picker {
|
||||
margin-right: 10px; /* 日期选择器之间的间距 */
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.triangle-button {
|
||||
margin: 0 2px; /* 设置左右间距 */
|
||||
}
|
||||
|
||||
.left_triangle {
|
||||
width: 0;
|
||||
height: 0;
|
||||
@@ -27,6 +32,7 @@
|
||||
border-bottom: 10px solid transparent; /* 下边透明 */
|
||||
border-right: 15px solid white; /* 左边为白色 */
|
||||
}
|
||||
|
||||
.right_triangle {
|
||||
width: 0;
|
||||
height: 0;
|
||||
|
||||
@@ -1,56 +1,59 @@
|
||||
<template>
|
||||
<div class="time-control" >
|
||||
<div class='time-control'>
|
||||
<el-select
|
||||
class="select"
|
||||
v-model="timeUnit"
|
||||
placeholder="选择时间单位"
|
||||
@change="handleChange"
|
||||
class='select'
|
||||
v-model='timeUnit'
|
||||
placeholder='选择时间单位'
|
||||
@change='handleChange'
|
||||
>
|
||||
<el-option label="按日" value="日"></el-option>
|
||||
<el-option label="按周" value="周"></el-option>
|
||||
<el-option label="按月" value="月"></el-option>
|
||||
<el-option label="按季度" value="季度"></el-option>
|
||||
<el-option label="按年" value="年"></el-option>
|
||||
<el-option label="自定义" value="自定义"></el-option>
|
||||
<!--采用 v-for 去动态渲染-->
|
||||
<el-option label='按日' value='日'></el-option>
|
||||
<el-option label='按周' value='周'></el-option>
|
||||
<el-option label='按月' value='月'></el-option>
|
||||
<el-option label='按季度' value='季度'></el-option>
|
||||
<el-option label='按年' value='年'></el-option>
|
||||
<el-option label='自定义' value='自定义'></el-option>
|
||||
|
||||
</el-select>
|
||||
|
||||
<div class="date-display"><!-- 禁用时间选择器 -->
|
||||
<!-- 禁用时间选择器 -->
|
||||
<div class='date-display'>
|
||||
<el-date-picker
|
||||
class="date-picker"
|
||||
v-model="startDate"
|
||||
type="date"
|
||||
placeholder="起始时间"
|
||||
class='date-picker'
|
||||
v-model='startDate'
|
||||
type='date'
|
||||
placeholder='起始时间'
|
||||
:readonly="timeUnit != '自定义'"
|
||||
></el-date-picker>
|
||||
<el-text>~</el-text>
|
||||
<el-date-picker
|
||||
class="date-picker"
|
||||
v-model="endDate"
|
||||
type="date"
|
||||
placeholder="结束时间"
|
||||
class='date-picker'
|
||||
v-model='endDate'
|
||||
type='date'
|
||||
placeholder='结束时间'
|
||||
:readonly="timeUnit !== '自定义'"
|
||||
></el-date-picker>
|
||||
</div>
|
||||
<div class="date-display" v-if="timeUnit !== '自定义'">
|
||||
<div class='date-display' v-if="timeUnit !== '自定义'">
|
||||
<el-button
|
||||
style="width: 10px;"
|
||||
class="triangle-button"
|
||||
type="primary"
|
||||
@click="prevPeriod"
|
||||
style='width: 10px;'
|
||||
class='triangle-button'
|
||||
type='primary'
|
||||
@click='prevPeriod'
|
||||
>
|
||||
<div class="left_triangle"></div>
|
||||
<div class='left_triangle'></div>
|
||||
</el-button>
|
||||
<el-button class="triangle-button" type="primary" @click="goToCurrent">
|
||||
<el-button class='triangle-button' type='primary' @click='goToCurrent'>
|
||||
本{{ timeUnit }}
|
||||
</el-button>
|
||||
<el-button
|
||||
style="width: 10px;"
|
||||
class="triangle-button"
|
||||
type="primary"
|
||||
@click="nextPeriod"
|
||||
:disabled="isNextDisabled"
|
||||
style='width: 10px;'
|
||||
class='triangle-button'
|
||||
type='primary'
|
||||
@click='nextPeriod'
|
||||
:disabled='isNextDisabled'
|
||||
>
|
||||
<div class="right_triangle"></div>
|
||||
<div class='right_triangle'></div>
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -64,125 +67,127 @@ export default {
|
||||
startDate: null, // 起始日期
|
||||
endDate: null, // 结束日期
|
||||
isNextDisabled: false, // 控制下一周期按钮的禁用状态
|
||||
today: new Date() // 当前日期
|
||||
};
|
||||
today: new Date(), // 当前日期
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 组件创建时更新日期范围
|
||||
this.updateDateRange();
|
||||
this.updateDateRange()
|
||||
},
|
||||
methods: {
|
||||
handleChange(value) {
|
||||
// 根据选择的时间单位处理日期变化
|
||||
if (value !== '自定义') {
|
||||
this.updateDateRange();
|
||||
this.updateDateRange()
|
||||
} else {
|
||||
// 自定义选项逻辑
|
||||
this.startDate = new Date();
|
||||
this.endDate = new Date();
|
||||
this.startDate = new Date()
|
||||
this.endDate = new Date()
|
||||
}
|
||||
this.updateNextButtonStatus();
|
||||
this.updateNextButtonStatus()
|
||||
},
|
||||
updateDateRange() {
|
||||
const today = this.today;
|
||||
const today = this.today
|
||||
// 根据选择的时间单位计算起始和结束日期
|
||||
if (this.timeUnit === '日') {
|
||||
this.startDate = today;
|
||||
this.endDate = today;
|
||||
this.startDate = today
|
||||
this.endDate = today
|
||||
} else if (this.timeUnit === '周') {
|
||||
this.startDate = this.getStartOfWeek(today);
|
||||
this.endDate = this.getEndOfWeek(today);
|
||||
this.startDate = this.getStartOfWeek(today)
|
||||
this.endDate = this.getEndOfWeek(today)
|
||||
} else if (this.timeUnit === '月') {
|
||||
this.startDate = new Date(today.getFullYear(), today.getMonth(), 1);
|
||||
this.endDate = new Date(today.getFullYear(), today.getMonth() + 1, 0);
|
||||
this.startDate = new Date(today.getFullYear(), today.getMonth(), 1)
|
||||
this.endDate = new Date(today.getFullYear(), today.getMonth() + 1, 0)
|
||||
} else if (this.timeUnit === '季度') {
|
||||
const quarter = Math.floor(today.getMonth() / 3);
|
||||
this.startDate = new Date(today.getFullYear(), quarter * 3, 1);
|
||||
this.endDate = new Date(today.getFullYear(), quarter * 3 + 3, 0);
|
||||
const quarter = Math.floor(today.getMonth() / 3)
|
||||
this.startDate = new Date(today.getFullYear(), quarter * 3, 1)
|
||||
this.endDate = new Date(today.getFullYear(), quarter * 3 + 3, 0)
|
||||
} else if (this.timeUnit === '年') {
|
||||
this.startDate = new Date(today.getFullYear(), 0, 1);
|
||||
this.endDate = new Date(today.getFullYear(), 11, 31);
|
||||
this.startDate = new Date(today.getFullYear(), 0, 1)
|
||||
this.endDate = new Date(today.getFullYear(), 11, 31)
|
||||
}
|
||||
this.updateNextButtonStatus();
|
||||
this.updateNextButtonStatus()
|
||||
},
|
||||
getStartOfWeek(date) {
|
||||
const startOfWeek = new Date(date);
|
||||
const day = startOfWeek.getDay();
|
||||
const diff = day === 0 ? -6 : 1 - day; // 星期天的情况
|
||||
startOfWeek.setDate(startOfWeek.getDate() + diff);
|
||||
return startOfWeek;
|
||||
const startOfWeek = new Date(date)
|
||||
const day = startOfWeek.getDay()
|
||||
const diff = day === 0 ? -6 : 1 - day // 星期天的情况
|
||||
startOfWeek.setDate(startOfWeek.getDate() + diff)
|
||||
return startOfWeek
|
||||
},
|
||||
getEndOfWeek(date) {
|
||||
const endOfWeek = new Date(date);
|
||||
const day = endOfWeek.getDay();
|
||||
const diff = day === 0 ? 0 : 7 - day; // 星期天的情况
|
||||
endOfWeek.setDate(endOfWeek.getDate() + diff);
|
||||
return endOfWeek;
|
||||
const endOfWeek = new Date(date)
|
||||
const day = endOfWeek.getDay()
|
||||
const diff = day === 0 ? 0 : 7 - day // 星期天的情况
|
||||
endOfWeek.setDate(endOfWeek.getDate() + diff)
|
||||
return endOfWeek
|
||||
},
|
||||
prevPeriod() {
|
||||
const prevStartDate = new Date(this.startDate);
|
||||
const prevEndDate = new Date(this.endDate);
|
||||
const prevStartDate = new Date(this.startDate)
|
||||
const prevEndDate = new Date(this.endDate)
|
||||
|
||||
if (this.timeUnit === '日') {
|
||||
prevStartDate.setDate(prevStartDate.getDate() - 1);
|
||||
prevEndDate.setDate(prevEndDate.getDate() - 1);
|
||||
prevStartDate.setDate(prevStartDate.getDate() - 1)
|
||||
prevEndDate.setDate(prevEndDate.getDate() - 1)
|
||||
} else if (this.timeUnit === '周') {
|
||||
prevStartDate.setDate(prevStartDate.getDate() - 7);
|
||||
prevEndDate.setDate(prevEndDate.getDate() - 7);
|
||||
prevStartDate.setDate(prevStartDate.getDate() - 7)
|
||||
prevEndDate.setDate(prevEndDate.getDate() - 7)
|
||||
} else if (this.timeUnit === '月') {
|
||||
prevStartDate.setMonth(prevStartDate.getMonth() - 1);
|
||||
prevEndDate.setMonth(prevEndDate.getMonth() - 1);
|
||||
prevStartDate.setMonth(prevStartDate.getMonth() - 1)
|
||||
prevEndDate.setMonth(prevEndDate.getMonth() - 1)
|
||||
} else if (this.timeUnit === '季度') {
|
||||
prevStartDate.setMonth(prevStartDate.getMonth() - 3);
|
||||
prevEndDate.setMonth(prevEndDate.getMonth() - 3);
|
||||
prevStartDate.setMonth(prevStartDate.getMonth() - 3)
|
||||
prevEndDate.setMonth(prevEndDate.getMonth() - 3)
|
||||
} else if (this.timeUnit === '年') {
|
||||
prevStartDate.setFullYear(prevStartDate.getFullYear() - 1);
|
||||
prevEndDate.setFullYear(prevEndDate.getFullYear() - 1);
|
||||
prevStartDate.setFullYear(prevStartDate.getFullYear() - 1)
|
||||
prevEndDate.setFullYear(prevEndDate.getFullYear() - 1)
|
||||
}
|
||||
|
||||
this.startDate = prevStartDate;
|
||||
this.endDate = prevEndDate;
|
||||
this.updateNextButtonStatus();
|
||||
this.startDate = prevStartDate
|
||||
this.endDate = prevEndDate
|
||||
this.updateNextButtonStatus()
|
||||
},
|
||||
goToCurrent() {
|
||||
if (this.timeUnit !== '自定义') {
|
||||
this.updateDateRange(); // 更新为当前选择时间单位的时间范围
|
||||
this.updateDateRange() // 更新为当前选择时间单位的时间范围
|
||||
}
|
||||
},
|
||||
nextPeriod() {
|
||||
const nextStartDate = new Date(this.startDate);
|
||||
const nextEndDate = new Date(this.endDate);
|
||||
const nextStartDate = new Date(this.startDate)
|
||||
const nextEndDate = new Date(this.endDate)
|
||||
|
||||
if (this.timeUnit === '日') {
|
||||
nextStartDate.setDate(nextStartDate.getDate() + 1);
|
||||
nextEndDate.setDate(nextEndDate.getDate() + 1);
|
||||
nextStartDate.setDate(nextStartDate.getDate() + 1)
|
||||
nextEndDate.setDate(nextEndDate.getDate() + 1)
|
||||
} else if (this.timeUnit === '周') {
|
||||
nextStartDate.setDate(nextStartDate.getDate() + 7);
|
||||
nextEndDate.setDate(nextEndDate.getDate() + 7);
|
||||
nextStartDate.setDate(nextStartDate.getDate() + 7)
|
||||
nextEndDate.setDate(nextEndDate.getDate() + 7)
|
||||
} else if (this.timeUnit === '月') {
|
||||
nextStartDate.setMonth(nextStartDate.getMonth() + 1);
|
||||
nextEndDate.setMonth(nextEndDate.getMonth() + 1);
|
||||
nextStartDate.setMonth(nextStartDate.getMonth() + 1)
|
||||
nextEndDate.setMonth(nextEndDate.getMonth() + 1)
|
||||
} else if (this.timeUnit === '季度') {
|
||||
nextStartDate.setMonth(nextStartDate.getMonth() + 3);
|
||||
nextEndDate.setMonth(nextStartDate.getMonth() + 3);
|
||||
nextStartDate.setMonth(nextStartDate.getMonth() + 3)
|
||||
nextEndDate.setMonth(nextStartDate.getMonth() + 3)
|
||||
} else if (this.timeUnit === '年') {
|
||||
nextStartDate.setFullYear(nextStartDate.getFullYear() + 1);
|
||||
nextEndDate.setFullYear(nextEndDate.getFullYear() + 1);
|
||||
nextStartDate.setFullYear(nextStartDate.getFullYear() + 1)
|
||||
nextEndDate.setFullYear(nextEndDate.getFullYear() + 1)
|
||||
}
|
||||
|
||||
this.startDate = nextStartDate;
|
||||
this.endDate = nextEndDate;
|
||||
this.updateNextButtonStatus();
|
||||
this.startDate = nextStartDate
|
||||
this.endDate = nextEndDate
|
||||
this.updateNextButtonStatus()
|
||||
},
|
||||
updateNextButtonStatus() {
|
||||
// 更新下一个按钮的禁用状态
|
||||
const maxDate = new Date(); // 假设最新日期为今天
|
||||
this.isNextDisabled = this.endDate > maxDate;
|
||||
const maxDate = new Date() // 假设最新日期为今天
|
||||
this.isNextDisabled = this.endDate > maxDate
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// defineProps('include','exclude','default')
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
<style scoped lang='scss'>
|
||||
@import "./index.scss";
|
||||
</style>
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
<template>
|
||||
<div class="tabs-box">
|
||||
<div class="tabs-menu">
|
||||
<el-tabs v-model="tabsMenuValue" type="card" @tab-click="tabClick" @tab-remove="tabRemove">
|
||||
<el-tab-pane v-for="item in tabsMenuList" :key="item.path" :label="item.title" :name="item.path" :closable="item.close">
|
||||
<div class='tabs-box'>
|
||||
<div class='tabs-menu'>
|
||||
<el-tabs v-model='tabsMenuValue' type='card' @tab-click='tabClick' @tab-remove='tabRemove'>
|
||||
<el-tab-pane v-for='item in tabsMenuList' :key='item.path' :label='item.title' :name='item.path'
|
||||
:closable='item.close'>
|
||||
<template #label>
|
||||
<el-icon v-show="item.icon && tabsIcon" class="tabs-icon">
|
||||
<component :is="item.icon"></component>
|
||||
<el-icon v-show='item.icon && tabsIcon' class='tabs-icon'>
|
||||
<component :is='item.icon'></component>
|
||||
</el-icon>
|
||||
{{ item.title }}
|
||||
</template>
|
||||
@@ -16,49 +17,54 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Sortable from "sortablejs";
|
||||
import { ref, computed, watch, onMounted } from "vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { useGlobalStore } from "@/stores/modules/global";
|
||||
import { useTabsStore } from "@/stores/modules/tabs";
|
||||
import { useAuthStore } from "@/stores/modules/auth";
|
||||
import { TabsPaneContext, TabPaneName } from "element-plus";
|
||||
import MoreButton from "./components/MoreButton.vue";
|
||||
<script setup lang='ts'>
|
||||
import Sortable from 'sortablejs'
|
||||
import { ref, computed, watch, onMounted } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { useGlobalStore } from '@/stores/modules/global'
|
||||
import { useTabsStore } from '@/stores/modules/tabs'
|
||||
import { useAuthStore } from '@/stores/modules/auth'
|
||||
import { TabsPaneContext, TabPaneName } from 'element-plus'
|
||||
import MoreButton from './components/MoreButton.vue'
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const tabStore = useTabsStore();
|
||||
const authStore = useAuthStore();
|
||||
const globalStore = useGlobalStore();
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const tabStore = useTabsStore()
|
||||
const authStore = useAuthStore()
|
||||
const globalStore = useGlobalStore()
|
||||
|
||||
const tabsMenuValue = ref(route.fullPath);
|
||||
const tabsMenuList = computed(() => tabStore.tabsMenuList);
|
||||
const tabsIcon = computed(() => globalStore.tabsIcon);
|
||||
const tabsMenuValue = ref(route.fullPath)
|
||||
const tabsMenuList = computed(() => tabStore.tabsMenuList)
|
||||
const tabsIcon = computed(() => globalStore.tabsIcon)
|
||||
|
||||
onMounted(() => {
|
||||
tabsDrop();
|
||||
initTabs();
|
||||
});
|
||||
tabsDrop()
|
||||
initTabs()
|
||||
})
|
||||
|
||||
|
||||
// 监听路由的变化(防止浏览器后退/前进不变化 tabsMenuValue)
|
||||
watch(
|
||||
() => route.fullPath,
|
||||
() => {
|
||||
if (route.meta.isFull) return;
|
||||
tabsMenuValue.value = route.fullPath;
|
||||
if (route.meta.isFull) return
|
||||
if (route.meta.hideTab){
|
||||
tabsMenuValue.value = route.meta.parentPath as string
|
||||
}else{
|
||||
tabsMenuValue.value = route.fullPath
|
||||
const tabsParams = {
|
||||
icon: route.meta.icon as string,
|
||||
title: route.meta.title as string,
|
||||
path: route.fullPath,
|
||||
name: route.name as string,
|
||||
close: !route.meta.isAffix,
|
||||
isKeepAlive: route.meta.isKeepAlive as boolean
|
||||
};
|
||||
tabStore.addTabs(tabsParams);
|
||||
isKeepAlive: route.meta.isKeepAlive as boolean,
|
||||
}
|
||||
tabStore.addTabs(tabsParams)
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
// 初始化需要固定的 tabs
|
||||
const initTabs = () => {
|
||||
@@ -70,39 +76,39 @@ const initTabs = () => {
|
||||
path: item.path,
|
||||
name: item.name,
|
||||
close: !item.meta.isAffix,
|
||||
isKeepAlive: item.meta.isKeepAlive
|
||||
};
|
||||
tabStore.addTabs(tabsParams);
|
||||
isKeepAlive: item.meta.isKeepAlive,
|
||||
}
|
||||
tabStore.addTabs(tabsParams)
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// tabs 拖拽排序
|
||||
const tabsDrop = () => {
|
||||
Sortable.create(document.querySelector(".el-tabs__nav") as HTMLElement, {
|
||||
draggable: ".el-tabs__item",
|
||||
Sortable.create(document.querySelector('.el-tabs__nav') as HTMLElement, {
|
||||
draggable: '.el-tabs__item',
|
||||
animation: 300,
|
||||
onEnd({ newIndex, oldIndex }) {
|
||||
const tabsList = [...tabStore.tabsMenuList];
|
||||
const currRow = tabsList.splice(oldIndex as number, 1)[0];
|
||||
tabsList.splice(newIndex as number, 0, currRow);
|
||||
tabStore.setTabs(tabsList);
|
||||
const tabsList = [...tabStore.tabsMenuList]
|
||||
const currRow = tabsList.splice(oldIndex as number, 1)[0]
|
||||
tabsList.splice(newIndex as number, 0, currRow)
|
||||
tabStore.setTabs(tabsList)
|
||||
},
|
||||
})
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Tab Click
|
||||
const tabClick = (tabItem: TabsPaneContext) => {
|
||||
const fullPath = tabItem.props.name as string;
|
||||
router.push(fullPath);
|
||||
};
|
||||
const fullPath = tabItem.props.name as string
|
||||
router.push(fullPath)
|
||||
}
|
||||
|
||||
// Remove Tab
|
||||
const tabRemove = (fullPath: TabPaneName) => {
|
||||
tabStore.removeTabs(fullPath as string, fullPath == route.fullPath);
|
||||
};
|
||||
tabStore.removeTabs(fullPath as string, fullPath == route.fullPath)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
<style scoped lang='scss'>
|
||||
@import "./index.scss";
|
||||
</style>
|
||||
|
||||
@@ -77,6 +77,8 @@ export const staticRouter: RouteRecordRaw[] = [
|
||||
title: "自动检测",
|
||||
icon: "List",
|
||||
isLink: "",
|
||||
hideTab:true,
|
||||
parentPath:'/system/proTable',
|
||||
isHide: false,
|
||||
isFull: false,
|
||||
isAffix: false,
|
||||
|
||||
@@ -4,6 +4,11 @@
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.flx-flex-start {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
.flx-justify-between {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -43,6 +43,7 @@ import userDataList from '@/api/user/userData'
|
||||
import { useDictStore } from '@/stores/modules/dict'
|
||||
import SingleColumn from '@/views/demo/proTable/singleColumn.vue'
|
||||
import DoubleColumn from '@/views/demo/proTable/doubleColumn.vue'
|
||||
|
||||
const dictStore = useDictStore()
|
||||
import {
|
||||
getUserList,
|
||||
@@ -54,6 +55,8 @@ import {
|
||||
getUserStatus,
|
||||
} from '@/api/user/user'
|
||||
import { ElMessageBox } from 'element-plus'
|
||||
import router from '@/routers'
|
||||
|
||||
const userData = userDataList
|
||||
const singleColumn = ref()
|
||||
const doubleColumn = ref()
|
||||
@@ -156,8 +159,9 @@ const columns = reactive<ColumnProps<User.ResUserList>[]>([
|
||||
])
|
||||
// 删除用户信息
|
||||
const deleteAccount = async (params: User.ResUserList) => {
|
||||
await useHandleData(deleteUser, { id: [params.id] }, `删除【${params.username}】用户`)
|
||||
proTable.value?.getTableList()
|
||||
// await useHandleData(deleteUser, { id: [params.id] }, `删除【${params.username}】用户`)
|
||||
// proTable.value?.getTableList()
|
||||
router.push('/plan/autoTest')
|
||||
}
|
||||
// 批量删除用户信息
|
||||
const batchDelete = async (id: string[]) => {
|
||||
@@ -169,7 +173,7 @@ const batchDelete = async (id: string[]) => {
|
||||
const resetPass = async (params: User.ResUserList) => {
|
||||
// await useHandleData(resetUserPassWord, { id: params.id }, `重置【${params.username}】用户密码`)
|
||||
// proTable.value?.getTableList()
|
||||
doubleColumn.value.open("双列弹出框")
|
||||
doubleColumn.value.open('双列弹出框')
|
||||
}
|
||||
// 切换用户状态
|
||||
const changeStatus = async (row: User.ResUserList) => {
|
||||
@@ -198,6 +202,6 @@ const batchAdd = () => {
|
||||
}
|
||||
// 打开 drawer(新增、查看、编辑)
|
||||
const openDrawer = (title: string, row: Partial<User.ResUserList> = {}) => {
|
||||
singleColumn.value.open("单列弹出框")
|
||||
singleColumn.value.open('单列弹出框')
|
||||
}
|
||||
</script>
|
||||
@@ -4,8 +4,8 @@
|
||||
ref='proTable'
|
||||
:columns='columns'
|
||||
:data='logData'
|
||||
@selection-change="handleSelectionChange"
|
||||
type="selection"
|
||||
@selection-change='handleSelectionChange'
|
||||
type='selection'
|
||||
>
|
||||
<!-- 表格 header 按钮 -->
|
||||
<template #tableHeader>
|
||||
@@ -16,14 +16,15 @@
|
||||
|
||||
</template>
|
||||
<script setup lang='tsx' name='useProTable'>
|
||||
import TimeControl from '@/components/TimeControl/index.vue'; // 根据实际路径调整
|
||||
import { defineComponent,ref ,reactive} from 'vue'
|
||||
// 根据实际路径调整
|
||||
import TimeControl from '@/components/TimeControl/index.vue'
|
||||
import { type Log } from '@/api/log/interface'
|
||||
import ProTable from '@/components/ProTable/index.vue'
|
||||
import { Upload } from '@element-plus/icons-vue'
|
||||
import logDataList from '@/api/log/logData'
|
||||
import type { ColumnProps, ProTableInstance } from '@/components/ProTable/interface'
|
||||
let multipleSelection = ref<string[]>([]);
|
||||
|
||||
let multipleSelection = ref<string[]>([])
|
||||
const logData = logDataList
|
||||
|
||||
// ProTable 实例
|
||||
@@ -40,16 +41,6 @@ const columns = reactive<ColumnProps<Log.LogList>[]>([
|
||||
prop: 'record_Time',
|
||||
label: '记录时间',
|
||||
width: 180,
|
||||
search: {
|
||||
// 自定义 search 显示内容
|
||||
render: ({ searchParam }) => {
|
||||
return (
|
||||
<div class='flx-center'>
|
||||
<TimeControl/>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
prop: 'user',
|
||||
@@ -67,14 +58,29 @@ const columns = reactive<ColumnProps<Log.LogList>[]>([
|
||||
label: '日志等级',
|
||||
search: { el: 'select', props: { filterable: true } },
|
||||
},
|
||||
{
|
||||
prop: 'record_Time',
|
||||
label: '记录时间',
|
||||
isShow: false,
|
||||
search: {
|
||||
span: 2,
|
||||
render: ({ searchParam }) => {
|
||||
return (
|
||||
<div class='flx-flex-start'>
|
||||
<TimeControl />
|
||||
</div>
|
||||
)
|
||||
},
|
||||
},
|
||||
},
|
||||
])
|
||||
|
||||
//选中
|
||||
// 处理选择变化
|
||||
const handleSelectionChange = (selection: Log.LogList[]) => {
|
||||
multipleSelection.value = selection.map(row => row.id); // 更新选中的行
|
||||
multipleSelection.value = selection.map(row => row.id) // 更新选中的行
|
||||
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -51,8 +51,6 @@ const getTreeData = (val: any) => {
|
||||
defaultChecked.value = [];
|
||||
data.value = val;
|
||||
defaultChecked.value.push(data.value[0].children[0].children[0].id);
|
||||
console.log(defaultChecked.value, "+++++++++++++");
|
||||
console.log(treeList.value);
|
||||
treeRef.value.setCurrentKey(defaultChecked.value);
|
||||
};
|
||||
const filterText = ref("");
|
||||
|
||||
@@ -271,7 +271,6 @@ const dataCallback = (data: any) => {
|
||||
total: data.length || data.total, //total
|
||||
};
|
||||
};
|
||||
console.log(proTable.value, "proTable.value?proTable.value?proTable.value?");
|
||||
// 如果你想在请求之前对当前请求参数做一些操作,可以自定义如下函数:params 为当前所有的请求参数(包括分页),最后返回请求列表接口
|
||||
// 默认不做操作就直接在 ProTable 组件上绑定 :requestApi="getUserList"
|
||||
const getTableList = (params: any) => {
|
||||
|
||||
@@ -240,7 +240,6 @@ const dataCallback = (data: any) => {
|
||||
total: data.length || data.total,
|
||||
};
|
||||
};
|
||||
console.log(proTable.value, "proTable.value?proTable.value?proTable.value?");
|
||||
// 如果你想在请求之前对当前请求参数做一些操作,可以自定义如下函数:params 为当前所有的请求参数(包括分页),最后返回请求列表接口
|
||||
// 默认不做操作就直接在 ProTable 组件上绑定 :requestApi="getUserList"
|
||||
const getTableList = (params: any) => {
|
||||
|
||||
@@ -240,7 +240,6 @@ const dataCallback = (data: any) => {
|
||||
total: data.length || data.total,
|
||||
};
|
||||
};
|
||||
console.log(proTable.value, "proTable.value?proTable.value?proTable.value?");
|
||||
// 如果你想在请求之前对当前请求参数做一些操作,可以自定义如下函数:params 为当前所有的请求参数(包括分页),最后返回请求列表接口
|
||||
// 默认不做操作就直接在 ProTable 组件上绑定 :requestApi="getUserList"
|
||||
const getTableList = (params: any) => {
|
||||
|
||||
Reference in New Issue
Block a user