微调-调整日期组件

This commit is contained in:
2024-10-22 14:47:25 +08:00
parent 97d6329d82
commit 0d25e477d7
15 changed files with 586 additions and 554 deletions

View File

@@ -1,11 +1,10 @@
<template> <template>
<div v-show="isShow" :style="style"> <div v-show='isShow' :style='style'>
<slot></slot> <slot></slot>
</div> </div>
</template> </template>
<script setup lang="ts" name="GridItem"> <script setup lang='ts' name='GridItem'>
import { computed, inject, Ref, ref, useAttrs, watch } from "vue"; import { BreakPoint, Responsive } from '../interface/index'
import { BreakPoint, Responsive } from "../interface/index";
type Props = { type Props = {
offset?: number; offset?: number;
@@ -26,43 +25,43 @@ const props = withDefaults(defineProps<Props>(), {
sm: undefined, sm: undefined,
md: undefined, md: undefined,
lg: undefined, lg: undefined,
xl: undefined xl: undefined,
}); })
const attrs = useAttrs() as { index: string }; const attrs = useAttrs() as { index: string }
const isShow = ref(true); const isShow = ref(true)
// 注入断点 // 注入断点
const breakPoint = inject<Ref<BreakPoint>>("breakPoint", ref("xl")); const breakPoint = inject<Ref<BreakPoint>>('breakPoint', ref('xl'))
const shouldHiddenIndex = inject<Ref<number>>("shouldHiddenIndex", ref(-1)); const shouldHiddenIndex = inject<Ref<number>>('shouldHiddenIndex', ref(-1))
watch( watch(
() => [shouldHiddenIndex.value, breakPoint.value], () => [shouldHiddenIndex.value, breakPoint.value],
n => { n => {
if (!!attrs.index) { 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 gap = inject('gap', 0)
const cols = inject("cols", ref(4)); const cols = inject('cols', ref(4))
const style = computed(() => { const style = computed(() => {
let span = props[breakPoint.value]?.span ?? props.span; let span = props[breakPoint.value]?.span ?? props.span
let offset = props[breakPoint.value]?.offset ?? props.offset; let offset = props[breakPoint.value]?.offset ?? props.offset
if (props.suffix) { if (props.suffix) {
return { return {
gridColumnStart: cols.value - span - offset + 2, gridColumnStart: cols.value - span - offset + 1,
gridColumnEnd: `span ${span + offset}`, 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 { } else {
return { return {
gridColumn: `span ${span + offset > cols.value ? cols.value : span + offset }/span ${ gridColumn: `span ${span + offset > cols.value ? cols.value : span + offset}/span ${
span + offset > cols.value ? cols.value : span + offset 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> </script>

View File

@@ -1,25 +1,11 @@
<template> <template>
<div :style="style"> <div :style='style'>
<slot></slot> <slot></slot>
</div> </div>
</template> </template>
<script setup lang="ts" name="Grid"> <script setup lang='ts' name='Grid'>
import { import type { BreakPoint } from './interface/index'
ref,
watch,
useSlots,
computed,
provide,
onBeforeMount,
onMounted,
onUnmounted,
onDeactivated,
onActivated,
VNodeArrayChildren,
VNode
} from "vue";
import type { BreakPoint } from "./interface/index";
type Props = { type Props = {
cols?: number | Record<BreakPoint, number>; 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 }), cols: () => ({ xs: 1, sm: 2, md: 2, lg: 3, xl: 4 }),
collapsed: false, collapsed: false,
collapsedRows: 1, collapsedRows: 1,
gap: 0 gap: 0,
}); })
onBeforeMount(() => props.collapsed && findIndex()); onBeforeMount(() => props.collapsed && findIndex())
onMounted(() => { onMounted(() => {
resize({ target: { innerWidth: window.innerWidth } } as unknown as UIEvent); resize({ target: { innerWidth: window.innerWidth } } as unknown as UIEvent)
window.addEventListener("resize", resize); window.addEventListener('resize', resize)
}); })
onActivated(() => { onActivated(() => {
resize({ target: { innerWidth: window.innerWidth } } as unknown as UIEvent); resize({ target: { innerWidth: window.innerWidth } } as unknown as UIEvent)
window.addEventListener("resize", resize); window.addEventListener('resize', resize)
}); })
onUnmounted(() => { onUnmounted(() => {
window.removeEventListener("resize", resize); window.removeEventListener('resize', resize)
}); })
onDeactivated(() => { onDeactivated(() => {
window.removeEventListener("resize", resize); window.removeEventListener('resize', resize)
}); })
// 监听屏幕变化 // 监听屏幕变化
const resize = (e: UIEvent) => { const resize = (e: UIEvent) => {
let width = (e.target as Window).innerWidth; let width = (e.target as Window).innerWidth
switch (!!width) { switch (!!width) {
case width < 768: case width < 768:
breakPoint.value = "xs"; breakPoint.value = 'xs'
break; break
case width >= 768 && width < 992: case width >= 768 && width < 992:
breakPoint.value = "sm"; breakPoint.value = 'sm'
break; break
case width >= 992 && width < 1200: case width >= 992 && width < 1200:
breakPoint.value = "md"; breakPoint.value = 'md'
break; break
case width >= 1200 && width < 1920: case width >= 1200 && width < 1920:
breakPoint.value = "lg"; breakPoint.value = 'lg'
break; break
case width >= 1920: case width >= 1920:
breakPoint.value = "xl"; breakPoint.value = 'xl'
break; break
} }
}; }
// 注入 gap 间距 // 注入 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"); let breakPoint = ref<BreakPoint>('xl')
provide("breakPoint", breakPoint); provide('breakPoint', breakPoint)
// 注入要开始折叠的 index // 注入要开始折叠的 index
const hiddenIndex = ref(-1); const hiddenIndex = ref(-1)
provide("shouldHiddenIndex", hiddenIndex); provide('shouldHiddenIndex', hiddenIndex)
// 注入 cols // 注入 cols
const gridCols = computed(() => { const gridCols = computed(() => {
if (typeof props.cols === "object") return props.cols[breakPoint.value] ?? props.cols; if (typeof props.cols === 'object') return props.cols[breakPoint.value] ?? props.cols
return props.cols; return props.cols
}); })
provide("cols", gridCols); provide('cols', gridCols)
// 寻找需要开始折叠的字段 index // 寻找需要开始折叠的字段 index
const slots = useSlots().default!(); const slots = useSlots().default!()
const findIndex = () => { const findIndex = () => {
let fields: VNodeArrayChildren = []; let fields: VNodeArrayChildren = []
let suffix: VNode | null = null; let suffix: VNode | null = null
slots.forEach((slot: any) => { slots.forEach((slot: any) => {
// suffix // 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 // 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 所占用的列 // 计算 suffix 所占用的列
let suffixCols = 0; let suffixCols = 0
if (suffix) { if (suffix) {
suffixCols = suffixCols =
((suffix as VNode).props![breakPoint.value]?.span ?? (suffix as VNode).props?.span ?? 1) + ((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 { try {
let find = false; let find = false
fields.reduce((prev = 0, current, index) => { fields.reduce((prev = 0, current, index) => {
prev += prev +=
((current as VNode)!.props![breakPoint.value]?.span ?? (current as VNode)!.props?.span ?? 1) + ((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) { if (Number(prev) > props.collapsedRows * gridCols.value - suffixCols) {
hiddenIndex.value = index; hiddenIndex.value = index
find = true; find = true
throw "find it"; throw 'find it'
} }
return prev; return prev
}, 0); }, 0)
if (!find) hiddenIndex.value = -1; if (!find) hiddenIndex.value = -1
} catch (e) { } catch (e) {
// console.warn(e); // console.warn(e);
} }
}; }
// 断点变化时执行 findIndex // 断点变化时执行 findIndex
watch( watch(
() => breakPoint.value, () => breakPoint.value,
() => { () => {
if (props.collapsed) findIndex(); if (props.collapsed) findIndex()
} },
); )
// 监听 collapsed // 监听 collapsed
watch( watch(
() => props.collapsed, () => props.collapsed,
value => { value => {
if (value) return findIndex(); if (value) return findIndex()
hiddenIndex.value = -1; hiddenIndex.value = -1
} },
); )
// 设置间距 // 设置间距
const gridGap = computed(() => { const gridGap = computed(() => {
if (typeof props.gap === "number") return `${props.gap}px`; if (typeof props.gap === 'number') return `${props.gap}px`
if (Array.isArray(props.gap)) return `${props.gap[1]}px ${props.gap[0]}px`; if (Array.isArray(props.gap)) return `${props.gap[1]}px ${props.gap[0]}px`
return "unset"; return 'unset'
}); })
// 设置 style // 设置 style
const style = computed(() => { const style = computed(() => {
return { return {
display: "grid", display: 'grid',
gridGap: gridGap.value, gridGap: gridGap.value,
gridTemplateColumns: `repeat(${gridCols.value}, minmax(0, 1fr))` gridTemplateColumns: `repeat(${gridCols.value}, minmax(0, 1fr))`,
}; }
}); })
defineExpose({ breakPoint }); defineExpose({ breakPoint })
</script> </script>

View File

@@ -1,119 +1,122 @@
<template> <template>
<!-- 查询表单 --> <!-- 查询表单 -->
<SearchForm <SearchForm
v-show="isShowSearch" v-show='isShowSearch'
:search="_search" :search='_search'
:reset="_reset" :reset='_reset'
:columns="searchColumns" :columns='searchColumns'
:search-param="searchParam" :search-param='searchParam'
:search-col="searchCol" :search-col='searchCol'
/> />
<!-- 表格主体 --> <!-- 表格主体 -->
<div class="card table-main"> <div class='card table-main'>
<!-- 表格头部 操作按钮 --> <!-- 表格头部 操作按钮 -->
<div class="table-header"> <div class='table-header'>
<div class="header-button-lf"> <div class='header-button-lf'>
<slot name="tableHeader" :selected-list="selectedList" :selected-list-ids="selectedListIds" :is-selected="isSelected" /> <slot name='tableHeader' :selected-list='selectedList' :selected-list-ids='selectedListIds'
:is-selected='isSelected' />
</div> </div>
<div v-if="toolButton" class="header-button-ri"> <div v-if='toolButton' class='header-button-ri'>
<slot name="toolButton"> <slot name='toolButton'>
<el-button v-if="showToolButton('refresh')" :icon="Refresh" circle @click="getTableList" /> <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('setting') && columns.length" :icon='Operation' circle
@click='openColSetting' />
<el-button <el-button
v-if="showToolButton('search') && searchColumns?.length" v-if="showToolButton('search') && searchColumns?.length"
:icon="Search" :icon='Search'
circle circle
@click="isShowSearch = !isShowSearch" @click='isShowSearch = !isShowSearch'
/> />
</slot> </slot>
</div> </div>
</div> </div>
<!-- 表格主体 --> <!-- 表格主体 -->
<el-table <el-table
ref="tableRef" ref='tableRef'
v-bind="$attrs" v-bind='$attrs'
:id="uuid" :id='uuid'
:data="processTableData" :data='processTableData'
:border="border" :border='border'
:row-key="rowKey" :row-key='rowKey'
@selection-change="selectionChange" @selection-change='selectionChange'
> >
<!-- 默认插槽 --> <!-- 默认插槽 -->
<slot /> <slot />
<template v-for="item in tableColumns" :key="item"> <template v-for='item in tableColumns' :key='item'>
<!-- selection || radio || index || expand || sort --> <!-- selection || radio || index || expand || sort -->
<el-table-column <el-table-column
v-if="item.type && columnTypes.includes(item.type)" v-if='item.type && columnTypes.includes(item.type)'
v-bind="item" v-bind='item'
:align="item.align ?? 'center'" :align="item.align ?? 'center'"
:reserve-selection="item.type == 'selection'" :reserve-selection="item.type == 'selection'"
> >
<template #default="scope"> <template #default='scope'>
<!-- expand --> <!-- expand -->
<template v-if="item.type == 'expand'"> <template v-if="item.type == 'expand'">
<component :is="item.render" v-bind="scope" v-if="item.render" /> <component :is='item.render' v-bind='scope' v-if='item.render' />
<slot v-else :name="item.type" v-bind="scope" /> <slot v-else :name='item.type' v-bind='scope' />
</template> </template>
<!-- radio --> <!-- 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> <i></i>
</el-radio> </el-radio>
<!-- sort --> <!-- sort -->
<el-tag v-if="item.type == 'sort'" class="move"> <el-tag v-if="item.type == 'sort'" class='move'>
<el-icon> <DCaret /></el-icon> <el-icon>
<DCaret />
</el-icon>
</el-tag> </el-tag>
</template> </template>
</el-table-column> </el-table-column>
<!-- other --> <!-- other -->
<TableColumn v-else :column="item"> <TableColumn v-else :column='item'>
<template v-for="slot in Object.keys($slots)" #[slot]="scope"> <template v-for='slot in Object.keys($slots)' #[slot]='scope'>
<slot :name="slot" v-bind="scope" /> <slot :name='slot' v-bind='scope' />
</template> </template>
</TableColumn> </TableColumn>
</template> </template>
<!-- 插入表格最后一行之后的插槽 --> <!-- 插入表格最后一行之后的插槽 -->
<template #append> <template #append>
<slot name="append" /> <slot name='append' />
</template> </template>
<!-- 无数据 --> <!-- 无数据 -->
<template #empty> <template #empty>
<div class="table-empty"> <div class='table-empty'>
<slot name="empty"> <slot name='empty'>
<img src="@/assets/images/notData.png" alt="notData" /> <img src='@/assets/images/notData.png' alt='notData' />
<div>暂无数据</div> <div>暂无数据</div>
</slot> </slot>
</div> </div>
</template> </template>
</el-table> </el-table>
<!-- 分页组件 --> <!-- 分页组件 -->
<slot name="pagination"> <slot name='pagination'>
<Pagination <Pagination
v-if="pagination" v-if='pagination'
:pageable="pageable" :pageable='pageable'
:handle-size-change="handleSizeChange" :handle-size-change='handleSizeChange'
:handle-current-change="handleCurrentChange" :handle-current-change='handleCurrentChange'
/> />
</slot> </slot>
</div> </div>
<!-- 列设置 --> <!-- 列设置 -->
<ColSetting v-if="toolButton" ref="colRef" v-model:col-setting="colSetting" /> <ColSetting v-if='toolButton' ref='colRef' v-model:col-setting='colSetting' />
</template> </template>
<script setup lang="ts" name="ProTable"> <script setup lang='ts' name='ProTable'>
import { ref, watch, provide, onMounted, unref, computed, reactive } from "vue"; import { ElTable } from 'element-plus'
import { ElTable } from "element-plus"; import { useTable } from '@/hooks/useTable'
import { useTable } from "@/hooks/useTable"; import { useSelection } from '@/hooks/useSelection'
import { useSelection } from "@/hooks/useSelection"; import { BreakPoint } from '@/components/Grid/interface'
import { BreakPoint } from "@/components/Grid/interface"; import { ColumnProps, TypeProps } from '@/components/ProTable/interface'
import { ColumnProps, TypeProps } from "@/components/ProTable/interface"; import { Refresh, Operation, Search } from '@element-plus/icons-vue'
import { Refresh, Operation, Search } from "@element-plus/icons-vue"; import { generateUUID, handleProp } from '@/utils'
import { generateUUID, handleProp } from "@/utils"; import SearchForm from '@/components/SearchForm/index.vue'
import SearchForm from "@/components/SearchForm/index.vue"; import Pagination from './components/Pagination.vue'
import Pagination from "./components/Pagination.vue"; import ColSetting from './components/ColSetting.vue'
import ColSetting from "./components/ColSetting.vue"; import TableColumn from './components/TableColumn.vue'
import TableColumn from "./components/TableColumn.vue"; import Sortable from 'sortablejs'
import Sortable from "sortablejs";
export interface ProTableProps { export interface ProTableProps {
columns: ColumnProps[]; // 列配置项 ==> 必传 columns: ColumnProps[]; // 列配置项 ==> 必传
@@ -126,7 +129,7 @@ export interface ProTableProps {
pagination?: boolean; // 是否需要分页组件 ==> 非必传默认为true pagination?: boolean; // 是否需要分页组件 ==> 非必传默认为true
initParam?: any; // 初始化请求参数 ==> 非必传(默认为{} initParam?: any; // 初始化请求参数 ==> 非必传(默认为{}
border?: boolean; // 是否带有纵向边框 ==> 非必传默认为true border?: boolean; // 是否带有纵向边框 ==> 非必传默认为true
toolButton?: ("refresh" | "setting" | "search")[] | boolean; // 是否显示表格功能按钮 ==> 非必传默认为true toolButton?: ('refresh' | 'setting' | 'search')[] | boolean; // 是否显示表格功能按钮 ==> 非必传默认为true
rowKey?: string; // 行数据的 Key用来优化 Table 的渲染,当表格数据多选时,所指定的 id ==> 非必传(默认为 id rowKey?: string; // 行数据的 Key用来优化 Table 的渲染,当表格数据多选时,所指定的 id ==> 非必传(默认为 id
searchCol?: number | Record<BreakPoint, number>; // 表格搜索项 每列占比配置 ==> 非必传 { xs: 1, sm: 2, md: 2, lg: 3, xl: 4 } 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: {}, initParam: {},
border: true, border: true,
toolButton: true, toolButton: true,
rowKey: "id", rowKey: 'id',
searchCol: () => ({ xs: 1, sm: 2, md: 2, lg: 3, xl: 4 }) searchCol: () => ({ xs: 1, sm: 2, md: 2, lg: 3, xl: 4 }),
}); })
// table 实例 // table 实例
const tableRef = ref<InstanceType<typeof ElTable>>(); const tableRef = ref<InstanceType<typeof ElTable>>()
// 生成组件唯一id // 生成组件唯一id
const uuid = ref("id-" + generateUUID()); const uuid = ref('id-' + generateUUID())
// column 列类型 // 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 显示 // 控制 ToolButton 显示
const showToolButton = (key: "refresh" | "setting" | "search") => { const showToolButton = (key: 'refresh' | 'setting' | 'search') => {
return Array.isArray(props.toolButton) ? props.toolButton.includes(key) : props.toolButton; return Array.isArray(props.toolButton) ? props.toolButton.includes(key) : props.toolButton
}; }
// 单选值 // 单选值
const radio = ref(""); const radio = ref('')
// 表格多选 Hooks // 表格多选 Hooks
const { selectionChange, selectedList, selectedListIds, isSelected } = useSelection(props.rowKey); const { selectionChange, selectedList, selectedListIds, isSelected } = useSelection(props.rowKey)
// 表格操作 Hooks // 表格操作 Hooks
const { tableData, pageable, searchParam, searchInitParam, getTableList, search, reset, handleSizeChange, handleCurrentChange } = const {
useTable(props.requestApi, props.initParam, props.pagination, props.dataCallback, props.requestError); 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(() => { onMounted(() => {
dragSort(); dragSort()
props.requestAuto && getTableList(); props.requestAuto && getTableList()
props.data && (pageable.value.total = props.data.length); props.data && (pageable.value.total = props.data.length)
}); })
// 处理表格数据 // 处理表格数据
const processTableData = computed(() => { const processTableData = computed(() => {
if (!props.data) return tableData.value; if (!props.data) return tableData.value
if (!props.pagination) return props.data; if (!props.pagination) return props.data
return props.data.slice( return props.data.slice(
(pageable.value.pageNum - 1) * pageable.value.pageSize, (pageable.value.pageNum - 1) * pageable.value.pageSize,
pageable.value.pageSize * pageable.value.pageNum pageable.value.pageSize * pageable.value.pageNum,
); )
}); })
// 监听页面 initParam 改化,重新获取表格数据 // 监听页面 initParam 改化,重新获取表格数据
watch(() => props.initParam, getTableList, { deep: true }); watch(() => props.initParam, getTableList, { deep: true })
// 接收 columns 并设置为响应式 // 接收 columns 并设置为响应式
const tableColumns = reactive<ColumnProps[]>(props.columns); const tableColumns = reactive<ColumnProps[]>(props.columns)
// 扁平化 columns // 扁平化 columns
const flatColumns = computed(() => flatColumnsFunc(tableColumns)); const flatColumns = computed(() => flatColumnsFunc(tableColumns))
// 定义 enumMap 存储 enum 值(避免异步请求无法格式化单元格内容 || 无法填充搜索下拉选择) // 定义 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) => { const setEnumMap = async ({ prop, enum: enumValue }: ColumnProps) => {
if (!enumValue) return; if (!enumValue) return
// 如果当前 enumMap 存在相同的值 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 // 当前 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 // 当前 enum 为后台数据需要请求数据,则调用该请求接口,并存储到 enumMap
const { data } = await enumValue(); const { data } = await enumValue()
enumMap.value.set(prop!, data); enumMap.value.set(prop!, data)
}; }
// 注入 enumMap // 注入 enumMap
provide("enumMap", enumMap); provide('enumMap', enumMap)
// 扁平化 columns 的方法 // 扁平化 columns 的方法
const flatColumnsFunc = (columns: ColumnProps[], flatArr: ColumnProps[] = []) => { const flatColumnsFunc = (columns: ColumnProps[], flatArr: ColumnProps[] = []) => {
columns.forEach(async col => { columns.forEach(async col => {
if (col._children?.length) flatArr.push(...flatColumnsFunc(col._children)); if (col._children?.length) flatArr.push(...flatColumnsFunc(col._children))
flatArr.push(col); flatArr.push(col)
// column 添加默认 isShow && isSetting && isFilterEnum 属性值 // column 添加默认 isShow && isSetting && isFilterEnum 属性值
col.isShow = col.isShow ?? true; col.isShow = col.isShow ?? true
col.isSetting = col.isSetting ?? true; col.isSetting = col.isSetting ?? true
col.isFilterEnum = col.isFilterEnum ?? true; col.isFilterEnum = col.isFilterEnum ?? true
// 设置 enumMap // 设置 enumMap
await setEnumMap(col); await setEnumMap(col)
}); })
return flatArr.filter(item => !item._children?.length); return flatArr.filter(item => !item._children?.length)
}; }
// 过滤需要搜索的配置项 && 排序 // 过滤需要搜索的配置项 && 排序
const searchColumns = computed(() => { const searchColumns = computed(() => {
return flatColumns.value return flatColumns.value
?.filter(item => item.search?.el || item.search?.render) ?.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) => { searchColumns.value?.forEach((column, index) => {
column.search!.order = column.search?.order ?? index + 2; column.search!.order = column.search?.order ?? index + 2
const key = column.search?.key ?? handleProp(column.prop!); const key = column.search?.key ?? handleProp(column.prop!)
const defaultValue = column.search?.defaultValue; const defaultValue = column.search?.defaultValue
if (defaultValue !== undefined && defaultValue !== null) { if (defaultValue !== undefined && defaultValue !== null) {
searchParam.value[key] = defaultValue; searchParam.value[key] = defaultValue
searchInitParam.value[key] = defaultValue; searchInitParam.value[key] = defaultValue
} }
}); })
// 列设置 ==> 需要过滤掉不需要设置的列 // 列设置 ==> 需要过滤掉不需要设置的列
const colRef = ref(); const colRef = ref()
const colSetting = tableColumns!.filter(item => { const colSetting = tableColumns!.filter(item => {
const { type, prop, isSetting } = item; const { type, prop, isSetting } = item
return !columnTypes.includes(type!) && prop !== "operation" && isSetting; return !columnTypes.includes(type!) && prop !== 'operation' && isSetting
}); })
const openColSetting = () => colRef.value.openColSetting(); const openColSetting = () => colRef.value.openColSetting()
// 定义 emit 事件 // 定义 emit 事件
const emit = defineEmits<{ const emit = defineEmits<{
search: []; search: [];
reset: []; reset: [];
dragSort: [{ newIndex?: number; oldIndex?: number }]; dragSort: [{ newIndex?: number; oldIndex?: number }];
}>(); }>()
const _search = () => { const _search = () => {
search(); search()
emit("search"); emit('search')
}; }
const _reset = () => { const _reset = () => {
reset(); reset()
emit("reset"); emit('reset')
}; }
// 表格拖拽排序 // 表格拖拽排序
const dragSort = () => { const dragSort = () => {
const tbody = document.querySelector(`#${uuid.value} tbody`) as HTMLElement; const tbody = document.querySelector(`#${uuid.value} tbody`) as HTMLElement
Sortable.create(tbody, { Sortable.create(tbody, {
handle: ".move", handle: '.move',
animation: 300, animation: 300,
onEnd({ newIndex, oldIndex }) { onEnd({ newIndex, oldIndex }) {
const [removedItem] = processTableData.value.splice(oldIndex!, 1); const [removedItem] = processTableData.value.splice(oldIndex!, 1)
processTableData.value.splice(newIndex!, 0, removedItem); processTableData.value.splice(newIndex!, 0, removedItem)
emit("dragSort", { newIndex, oldIndex }); emit('dragSort', { newIndex, oldIndex })
} },
}); })
}; }
// 暴露给父组件的参数和方法 (外部需要什么,都可以从这里暴露出去) // 暴露给父组件的参数和方法 (外部需要什么,都可以从这里暴露出去)
defineExpose({ defineExpose({
@@ -314,6 +327,6 @@ defineExpose({
handleSizeChange, handleSizeChange,
handleCurrentChange, handleCurrentChange,
clearSelection, clearSelection,
enumMap enumMap,
}); })
</script> </script>

View File

@@ -1,29 +1,29 @@
<template> <template>
<div v-if="columns.length" class="card table-search"> <div v-if='columns.length' class='card table-search'>
<el-form ref="formRef" :model="searchParam"> <el-form ref='formRef' :model='searchParam'>
<Grid ref="gridRef" :collapsed="collapsed" :gap="[20, 0]" :cols="searchCol"> <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"> <GridItem v-for='(item, index) in columns' :key='item.prop' v-bind='getResponsive(item)' :index='index + 1'>
<el-form-item> <el-form-item>
<template #label> <template #label>
<el-space :size="4"> <el-space :size='4'>
<span>{{ `${item.search?.label ?? item.label}` }}</span> <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> <i :class="'iconfont icon-yiwen'"></i>
</el-tooltip> </el-tooltip>
</el-space> </el-space>
<span>&nbsp;:</span> <span>&nbsp;:</span>
</template> </template>
<SearchFormItem :column="item" :search-param="searchParam" /> <SearchFormItem :column='item' :search-param='searchParam' />
</el-form-item> </el-form-item>
</GridItem> </GridItem>
<GridItem suffix> <GridItem suffix>
<div class="operation"> <div class='operation'>
<el-button type="primary" :icon="Search" @click="search"> 搜索 </el-button> <el-button type='primary' :icon='Search' @click='search'> 搜索</el-button>
<el-button :icon="Delete" @click="reset"> 重置 </el-button> <el-button :icon='Delete' @click='reset'> 重置</el-button>
<el-button v-if="showCollapse" type="primary" link class="search-isOpen" @click="collapsed = !collapsed"> <el-button v-if='showCollapse' type='primary' link class='search-isOpen' @click='collapsed = !collapsed'>
{{ collapsed ? "展开" : "合并" }} {{ collapsed ? '展开' : '合并' }}
<el-icon class="el-icon--right"> <el-icon class='el-icon--right'>
<component :is="collapsed ? ArrowDown : ArrowUp"></component> <component :is='collapsed ? ArrowDown : ArrowUp'></component>
</el-icon> </el-icon>
</el-button> </el-button>
</div> </div>
@@ -32,14 +32,15 @@
</el-form> </el-form>
</div> </div>
</template> </template>
<script setup lang="ts" name="SearchForm"> <script setup lang='ts' name='SearchForm'>
import { computed, ref } from "vue"; import { ColumnProps } from '@/components/ProTable/interface'
import { ColumnProps } from "@/components/ProTable/interface"; import { BreakPoint } from '@/components/Grid/interface'
import { BreakPoint } from "@/components/Grid/interface"; import { Delete, Search, ArrowDown, ArrowUp } from '@element-plus/icons-vue'
import { Delete, Search, ArrowDown, ArrowUp } from "@element-plus/icons-vue"; import SearchFormItem from './components/SearchFormItem.vue'
import SearchFormItem from "./components/SearchFormItem.vue"; import Grid from '@/components/Grid/index.vue'
import Grid from "@/components/Grid/index.vue"; import GridItem from '@/components/Grid/components/GridItem.vue'
import GridItem from "@/components/Grid/components/GridItem.vue";
interface ProTableProps { interface ProTableProps {
columns?: ColumnProps[]; // 搜索配置列 columns?: ColumnProps[]; // 搜索配置列
@@ -52,8 +53,12 @@ interface ProTableProps {
// 默认值 // 默认值
const props = withDefaults(defineProps<ProTableProps>(), { const props = withDefaults(defineProps<ProTableProps>(), {
columns: () => [], columns: () => [],
searchParam: () => ({}) searchParam: () => ({}),
}); })
onMounted(() => {
console.log(props.columns)
})
// 获取响应式设置 // 获取响应式设置
const getResponsive = (item: ColumnProps) => { const getResponsive = (item: ColumnProps) => {
@@ -64,31 +69,31 @@ const getResponsive = (item: ColumnProps) => {
sm: item.search?.sm, sm: item.search?.sm,
md: item.search?.md, md: item.search?.md,
lg: item.search?.lg, lg: item.search?.lg,
xl: item.search?.xl xl: item.search?.xl,
}; }
}; }
// 是否默认折叠搜索项 // 是否默认折叠搜索项
const collapsed = ref(true); const collapsed = ref(true)
// 获取响应式断点 // 获取响应式断点
const gridRef = ref(); const gridRef = ref()
const breakPoint = computed<BreakPoint>(() => gridRef.value?.breakPoint); const breakPoint = computed<BreakPoint>(() => gridRef.value?.breakPoint)
// 判断是否显示 展开/合并 按钮 // 判断是否显示 展开/合并 按钮
const showCollapse = computed(() => { const showCollapse = computed(() => {
let show = false; let show = false
props.columns.reduce((prev, current) => { props.columns.reduce((prev, current) => {
prev += prev +=
(current.search![breakPoint.value]?.span ?? current.search?.span ?? 1) + (current.search![breakPoint.value]?.span ?? current.search?.span ?? 1) +
(current.search![breakPoint.value]?.offset ?? current.search?.offset ?? 0); (current.search![breakPoint.value]?.offset ?? current.search?.offset ?? 0)
if (typeof props.searchCol !== "number") { if (typeof props.searchCol !== 'number') {
if (prev >= props.searchCol[breakPoint.value]) show = true; if (prev >= props.searchCol[breakPoint.value]) show = true
} else { } else {
if (prev >= props.searchCol) show = true; if (prev >= props.searchCol) show = true
} }
return prev; return prev
}, 0); }, 0)
return show; return show
}); })
</script> </script>

View File

@@ -1,36 +1,42 @@
/* 添加样式 */ /* 添加样式 */
.time-control { .time-control {
display: flex; display: flex;
align-items: center; align-items: center;
margin:0 0 0 300px ; /* 使整体居中 */ }
}
.select { .select {
margin-right: 10px; /* 下拉框右侧间距 */ margin-right: 10px; /* 下拉框右侧间距 */
width: 90px; /* 下拉框宽度 */ width: 90px; /* 下拉框宽度 */
} }
.date-display {
display: flex; .date-display {
align-items: center; display: flex;
margin-right: 10px; /* 日期选择器右侧间距 */ align-items: center;
width: 250px; margin-right: 10px; /* 日期选择器右侧间距 */
} width: 320px;
.date-picker { }
margin-right: 10px; /* 日期选择器之间的间距 */
} .date-picker {
.triangle-button { margin-right: 10px; /* 日期选择器之间的间距 */
margin:0 2px; /* 设置左右间距 */ width: 100%;
} }
.left_triangle {
width: 0; .triangle-button {
height: 0; margin: 0 2px; /* 设置左右间距 */
border-top: 10px solid transparent; /* 上边透明 */ }
border-bottom: 10px solid transparent; /* 下边透明 */
border-right: 15px solid white; /* 左边为白色 */ .left_triangle {
} width: 0;
.right_triangle { height: 0;
width: 0; border-top: 10px solid transparent; /* 上边透明 */
height: 0; border-bottom: 10px solid transparent; /* 下边透明 */
border-top: 10px solid transparent; /* 上边透明 */ border-right: 15px solid white; /* 左边为白色 */
border-bottom: 10px solid transparent; /* 下边透明 */ }
border-left: 15px solid white; /* 左边为白色 */
} .right_triangle {
width: 0;
height: 0;
border-top: 10px solid transparent; /* 上边透明 */
border-bottom: 10px solid transparent; /* 下边透明 */
border-left: 15px solid white; /* 左边为白色 */
}

View File

@@ -1,56 +1,59 @@
<template> <template>
<div class="time-control" > <div class='time-control'>
<el-select <el-select
class="select" class='select'
v-model="timeUnit" v-model='timeUnit'
placeholder="选择时间单位" placeholder='选择时间单位'
@change="handleChange" @change='handleChange'
> >
<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-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> </el-select>
<div class="date-display"><!-- 禁用时间选择器 --> <!-- 禁用时间选择器 -->
<div class='date-display'>
<el-date-picker <el-date-picker
class="date-picker" class='date-picker'
v-model="startDate" v-model='startDate'
type="date" type='date'
placeholder="起始时间" placeholder='起始时间'
:readonly="timeUnit != '自定义'" :readonly="timeUnit != '自定义'"
></el-date-picker> ></el-date-picker>
<el-text>~</el-text> <el-text>~</el-text>
<el-date-picker <el-date-picker
class="date-picker" class='date-picker'
v-model="endDate" v-model='endDate'
type="date" type='date'
placeholder="结束时间" placeholder='结束时间'
:readonly="timeUnit !== '自定义'" :readonly="timeUnit !== '自定义'"
></el-date-picker> ></el-date-picker>
</div> </div>
<div class="date-display" v-if="timeUnit !== '自定义'"> <div class='date-display' v-if="timeUnit !== '自定义'">
<el-button <el-button
style="width: 10px;" style='width: 10px;'
class="triangle-button" class='triangle-button'
type="primary" type='primary'
@click="prevPeriod" @click='prevPeriod'
> >
<div class="left_triangle"></div> <div class='left_triangle'></div>
</el-button> </el-button>
<el-button class="triangle-button" type="primary" @click="goToCurrent"> <el-button class='triangle-button' type='primary' @click='goToCurrent'>
{{ timeUnit }} {{ timeUnit }}
</el-button> </el-button>
<el-button <el-button
style="width: 10px;" style='width: 10px;'
class="triangle-button" class='triangle-button'
type="primary" type='primary'
@click="nextPeriod" @click='nextPeriod'
:disabled="isNextDisabled" :disabled='isNextDisabled'
> >
<div class="right_triangle"></div> <div class='right_triangle'></div>
</el-button> </el-button>
</div> </div>
</div> </div>
@@ -64,125 +67,127 @@ export default {
startDate: null, // 起始日期 startDate: null, // 起始日期
endDate: null, // 结束日期 endDate: null, // 结束日期
isNextDisabled: false, // 控制下一周期按钮的禁用状态 isNextDisabled: false, // 控制下一周期按钮的禁用状态
today: new Date() // 当前日期 today: new Date(), // 当前日期
}; }
}, },
created() { created() {
// 组件创建时更新日期范围 // 组件创建时更新日期范围
this.updateDateRange(); this.updateDateRange()
}, },
methods: { methods: {
handleChange(value) { handleChange(value) {
// 根据选择的时间单位处理日期变化 // 根据选择的时间单位处理日期变化
if (value !== '自定义') { if (value !== '自定义') {
this.updateDateRange(); this.updateDateRange()
} else { } else {
// 自定义选项逻辑 // 自定义选项逻辑
this.startDate = new Date(); this.startDate = new Date()
this.endDate = new Date(); this.endDate = new Date()
} }
this.updateNextButtonStatus(); this.updateNextButtonStatus()
}, },
updateDateRange() { updateDateRange() {
const today = this.today; const today = this.today
// 根据选择的时间单位计算起始和结束日期 // 根据选择的时间单位计算起始和结束日期
if (this.timeUnit === '日') { if (this.timeUnit === '日') {
this.startDate = today; this.startDate = today
this.endDate = today; this.endDate = today
} else if (this.timeUnit === '周') { } else if (this.timeUnit === '周') {
this.startDate = this.getStartOfWeek(today); this.startDate = this.getStartOfWeek(today)
this.endDate = this.getEndOfWeek(today); this.endDate = this.getEndOfWeek(today)
} else if (this.timeUnit === '月') { } else if (this.timeUnit === '月') {
this.startDate = new Date(today.getFullYear(), today.getMonth(), 1); this.startDate = new Date(today.getFullYear(), today.getMonth(), 1)
this.endDate = new Date(today.getFullYear(), today.getMonth() + 1, 0); this.endDate = new Date(today.getFullYear(), today.getMonth() + 1, 0)
} else if (this.timeUnit === '季度') { } else if (this.timeUnit === '季度') {
const quarter = Math.floor(today.getMonth() / 3); const quarter = Math.floor(today.getMonth() / 3)
this.startDate = new Date(today.getFullYear(), quarter * 3, 1); this.startDate = new Date(today.getFullYear(), quarter * 3, 1)
this.endDate = new Date(today.getFullYear(), quarter * 3 + 3, 0); this.endDate = new Date(today.getFullYear(), quarter * 3 + 3, 0)
} else if (this.timeUnit === '年') { } else if (this.timeUnit === '年') {
this.startDate = new Date(today.getFullYear(), 0, 1); this.startDate = new Date(today.getFullYear(), 0, 1)
this.endDate = new Date(today.getFullYear(), 11, 31); this.endDate = new Date(today.getFullYear(), 11, 31)
} }
this.updateNextButtonStatus(); this.updateNextButtonStatus()
}, },
getStartOfWeek(date) { getStartOfWeek(date) {
const startOfWeek = new Date(date); const startOfWeek = new Date(date)
const day = startOfWeek.getDay(); const day = startOfWeek.getDay()
const diff = day === 0 ? -6 : 1 - day; // 星期天的情况 const diff = day === 0 ? -6 : 1 - day // 星期天的情况
startOfWeek.setDate(startOfWeek.getDate() + diff); startOfWeek.setDate(startOfWeek.getDate() + diff)
return startOfWeek; return startOfWeek
}, },
getEndOfWeek(date) { getEndOfWeek(date) {
const endOfWeek = new Date(date); const endOfWeek = new Date(date)
const day = endOfWeek.getDay(); const day = endOfWeek.getDay()
const diff = day === 0 ? 0 : 7 - day; // 星期天的情况 const diff = day === 0 ? 0 : 7 - day // 星期天的情况
endOfWeek.setDate(endOfWeek.getDate() + diff); endOfWeek.setDate(endOfWeek.getDate() + diff)
return endOfWeek; return endOfWeek
}, },
prevPeriod() { prevPeriod() {
const prevStartDate = new Date(this.startDate); const prevStartDate = new Date(this.startDate)
const prevEndDate = new Date(this.endDate); const prevEndDate = new Date(this.endDate)
if (this.timeUnit === '日') { if (this.timeUnit === '日') {
prevStartDate.setDate(prevStartDate.getDate() - 1); prevStartDate.setDate(prevStartDate.getDate() - 1)
prevEndDate.setDate(prevEndDate.getDate() - 1); prevEndDate.setDate(prevEndDate.getDate() - 1)
} else if (this.timeUnit === '周') { } else if (this.timeUnit === '周') {
prevStartDate.setDate(prevStartDate.getDate() - 7); prevStartDate.setDate(prevStartDate.getDate() - 7)
prevEndDate.setDate(prevEndDate.getDate() - 7); prevEndDate.setDate(prevEndDate.getDate() - 7)
} else if (this.timeUnit === '月') { } else if (this.timeUnit === '月') {
prevStartDate.setMonth(prevStartDate.getMonth() - 1); prevStartDate.setMonth(prevStartDate.getMonth() - 1)
prevEndDate.setMonth(prevEndDate.getMonth() - 1); prevEndDate.setMonth(prevEndDate.getMonth() - 1)
} else if (this.timeUnit === '季度') { } else if (this.timeUnit === '季度') {
prevStartDate.setMonth(prevStartDate.getMonth() - 3); prevStartDate.setMonth(prevStartDate.getMonth() - 3)
prevEndDate.setMonth(prevEndDate.getMonth() - 3); prevEndDate.setMonth(prevEndDate.getMonth() - 3)
} else if (this.timeUnit === '年') { } else if (this.timeUnit === '年') {
prevStartDate.setFullYear(prevStartDate.getFullYear() - 1); prevStartDate.setFullYear(prevStartDate.getFullYear() - 1)
prevEndDate.setFullYear(prevEndDate.getFullYear() - 1); prevEndDate.setFullYear(prevEndDate.getFullYear() - 1)
} }
this.startDate = prevStartDate; this.startDate = prevStartDate
this.endDate = prevEndDate; this.endDate = prevEndDate
this.updateNextButtonStatus(); this.updateNextButtonStatus()
}, },
goToCurrent() { goToCurrent() {
if (this.timeUnit !== '自定义') { if (this.timeUnit !== '自定义') {
this.updateDateRange(); // 更新为当前选择时间单位的时间范围 this.updateDateRange() // 更新为当前选择时间单位的时间范围
} }
}, },
nextPeriod() { nextPeriod() {
const nextStartDate = new Date(this.startDate); const nextStartDate = new Date(this.startDate)
const nextEndDate = new Date(this.endDate); const nextEndDate = new Date(this.endDate)
if (this.timeUnit === '日') { if (this.timeUnit === '日') {
nextStartDate.setDate(nextStartDate.getDate() + 1); nextStartDate.setDate(nextStartDate.getDate() + 1)
nextEndDate.setDate(nextEndDate.getDate() + 1); nextEndDate.setDate(nextEndDate.getDate() + 1)
} else if (this.timeUnit === '周') { } else if (this.timeUnit === '周') {
nextStartDate.setDate(nextStartDate.getDate() + 7); nextStartDate.setDate(nextStartDate.getDate() + 7)
nextEndDate.setDate(nextEndDate.getDate() + 7); nextEndDate.setDate(nextEndDate.getDate() + 7)
} else if (this.timeUnit === '月') { } else if (this.timeUnit === '月') {
nextStartDate.setMonth(nextStartDate.getMonth() + 1); nextStartDate.setMonth(nextStartDate.getMonth() + 1)
nextEndDate.setMonth(nextEndDate.getMonth() + 1); nextEndDate.setMonth(nextEndDate.getMonth() + 1)
} else if (this.timeUnit === '季度') { } else if (this.timeUnit === '季度') {
nextStartDate.setMonth(nextStartDate.getMonth() + 3); nextStartDate.setMonth(nextStartDate.getMonth() + 3)
nextEndDate.setMonth(nextStartDate.getMonth() + 3); nextEndDate.setMonth(nextStartDate.getMonth() + 3)
} else if (this.timeUnit === '年') { } else if (this.timeUnit === '年') {
nextStartDate.setFullYear(nextStartDate.getFullYear() + 1); nextStartDate.setFullYear(nextStartDate.getFullYear() + 1)
nextEndDate.setFullYear(nextEndDate.getFullYear() + 1); nextEndDate.setFullYear(nextEndDate.getFullYear() + 1)
} }
this.startDate = nextStartDate; this.startDate = nextStartDate
this.endDate = nextEndDate; this.endDate = nextEndDate
this.updateNextButtonStatus(); this.updateNextButtonStatus()
}, },
updateNextButtonStatus() { updateNextButtonStatus() {
// 更新下一个按钮的禁用状态 // 更新下一个按钮的禁用状态
const maxDate = new Date(); // 假设最新日期为今天 const maxDate = new Date() // 假设最新日期为今天
this.isNextDisabled = this.endDate > maxDate; this.isNextDisabled = this.endDate > maxDate
} },
} },
}; }
// defineProps('include','exclude','default')
</script> </script>
<style scoped lang="scss"> <style scoped lang='scss'>
@import "./index.scss"; @import "./index.scss";
</style> </style>

View File

@@ -1,11 +1,12 @@
<template> <template>
<div class="tabs-box"> <div class='tabs-box'>
<div class="tabs-menu"> <div class='tabs-menu'>
<el-tabs v-model="tabsMenuValue" type="card" @tab-click="tabClick" @tab-remove="tabRemove"> <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"> <el-tab-pane v-for='item in tabsMenuList' :key='item.path' :label='item.title' :name='item.path'
:closable='item.close'>
<template #label> <template #label>
<el-icon v-show="item.icon && tabsIcon" class="tabs-icon"> <el-icon v-show='item.icon && tabsIcon' class='tabs-icon'>
<component :is="item.icon"></component> <component :is='item.icon'></component>
</el-icon> </el-icon>
{{ item.title }} {{ item.title }}
</template> </template>
@@ -16,49 +17,54 @@
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang='ts'>
import Sortable from "sortablejs"; import Sortable from 'sortablejs'
import { ref, computed, watch, onMounted } from "vue"; import { ref, computed, watch, onMounted } from 'vue'
import { useRoute, useRouter } from "vue-router"; import { useRoute, useRouter } from 'vue-router'
import { useGlobalStore } from "@/stores/modules/global"; import { useGlobalStore } from '@/stores/modules/global'
import { useTabsStore } from "@/stores/modules/tabs"; import { useTabsStore } from '@/stores/modules/tabs'
import { useAuthStore } from "@/stores/modules/auth"; import { useAuthStore } from '@/stores/modules/auth'
import { TabsPaneContext, TabPaneName } from "element-plus"; import { TabsPaneContext, TabPaneName } from 'element-plus'
import MoreButton from "./components/MoreButton.vue"; import MoreButton from './components/MoreButton.vue'
const route = useRoute(); const route = useRoute()
const router = useRouter(); const router = useRouter()
const tabStore = useTabsStore(); const tabStore = useTabsStore()
const authStore = useAuthStore(); const authStore = useAuthStore()
const globalStore = useGlobalStore(); const globalStore = useGlobalStore()
const tabsMenuValue = ref(route.fullPath); const tabsMenuValue = ref(route.fullPath)
const tabsMenuList = computed(() => tabStore.tabsMenuList); const tabsMenuList = computed(() => tabStore.tabsMenuList)
const tabsIcon = computed(() => globalStore.tabsIcon); const tabsIcon = computed(() => globalStore.tabsIcon)
onMounted(() => { onMounted(() => {
tabsDrop(); tabsDrop()
initTabs(); initTabs()
}); })
// 监听路由的变化(防止浏览器后退/前进不变化 tabsMenuValue // 监听路由的变化(防止浏览器后退/前进不变化 tabsMenuValue
watch( watch(
() => route.fullPath, () => route.fullPath,
() => { () => {
if (route.meta.isFull) return; if (route.meta.isFull) return
tabsMenuValue.value = route.fullPath; if (route.meta.hideTab){
const tabsParams = { tabsMenuValue.value = route.meta.parentPath as string
icon: route.meta.icon as string, }else{
title: route.meta.title as string, tabsMenuValue.value = route.fullPath
path: route.fullPath, const tabsParams = {
name: route.name as string, icon: route.meta.icon as string,
close: !route.meta.isAffix, title: route.meta.title as string,
isKeepAlive: route.meta.isKeepAlive as boolean path: route.fullPath,
}; name: route.name as string,
tabStore.addTabs(tabsParams); close: !route.meta.isAffix,
isKeepAlive: route.meta.isKeepAlive as boolean,
}
tabStore.addTabs(tabsParams)
}
}, },
{ immediate: true } { immediate: true },
); )
// 初始化需要固定的 tabs // 初始化需要固定的 tabs
const initTabs = () => { const initTabs = () => {
@@ -70,39 +76,39 @@ const initTabs = () => {
path: item.path, path: item.path,
name: item.name, name: item.name,
close: !item.meta.isAffix, close: !item.meta.isAffix,
isKeepAlive: item.meta.isKeepAlive isKeepAlive: item.meta.isKeepAlive,
}; }
tabStore.addTabs(tabsParams); tabStore.addTabs(tabsParams)
} }
}); })
}; }
// tabs 拖拽排序 // tabs 拖拽排序
const tabsDrop = () => { const tabsDrop = () => {
Sortable.create(document.querySelector(".el-tabs__nav") as HTMLElement, { Sortable.create(document.querySelector('.el-tabs__nav') as HTMLElement, {
draggable: ".el-tabs__item", draggable: '.el-tabs__item',
animation: 300, animation: 300,
onEnd({ newIndex, oldIndex }) { onEnd({ newIndex, oldIndex }) {
const tabsList = [...tabStore.tabsMenuList]; const tabsList = [...tabStore.tabsMenuList]
const currRow = tabsList.splice(oldIndex as number, 1)[0]; const currRow = tabsList.splice(oldIndex as number, 1)[0]
tabsList.splice(newIndex as number, 0, currRow); tabsList.splice(newIndex as number, 0, currRow)
tabStore.setTabs(tabsList); tabStore.setTabs(tabsList)
} },
}); })
}; }
// Tab Click // Tab Click
const tabClick = (tabItem: TabsPaneContext) => { const tabClick = (tabItem: TabsPaneContext) => {
const fullPath = tabItem.props.name as string; const fullPath = tabItem.props.name as string
router.push(fullPath); router.push(fullPath)
}; }
// Remove Tab // Remove Tab
const tabRemove = (fullPath: TabPaneName) => { const tabRemove = (fullPath: TabPaneName) => {
tabStore.removeTabs(fullPath as string, fullPath == route.fullPath); tabStore.removeTabs(fullPath as string, fullPath == route.fullPath)
}; }
</script> </script>
<style scoped lang="scss"> <style scoped lang='scss'>
@import "./index.scss"; @import "./index.scss";
</style> </style>

View File

@@ -77,6 +77,8 @@ export const staticRouter: RouteRecordRaw[] = [
title: "自动检测", title: "自动检测",
icon: "List", icon: "List",
isLink: "", isLink: "",
hideTab:true,
parentPath:'/system/proTable',
isHide: false, isHide: false,
isFull: false, isFull: false,
isAffix: false, isAffix: false,

View File

@@ -4,6 +4,11 @@
align-items: center; align-items: center;
justify-content: center; justify-content: center;
} }
.flx-flex-start {
display: flex;
align-items: center;
justify-content: flex-start;
}
.flx-justify-between { .flx-justify-between {
display: flex; display: flex;
align-items: center; align-items: center;

View File

@@ -1,9 +1,9 @@
<template> <template>
<div class='table-box'> <div class='table-box'>
<ProTable <ProTable
ref='proTable' ref='proTable'
:columns='columns' :columns='columns'
:data='userData' :data='userData'
> >
<!-- 表格 header 按钮 --> <!-- 表格 header 按钮 -->
<template #tableHeader='scope'> <template #tableHeader='scope'>
@@ -26,11 +26,11 @@
</template> </template>
</ProTable> </ProTable>
</div> </div>
<single-column ref='singleColumn' /> <single-column ref='singleColumn' />
<double-column ref='doubleColumn' /> <double-column ref='doubleColumn' />
</template> </template>
<script setup lang='tsx' name='useProTable'> <script setup lang='tsx' name='useProTable'>
import { ref ,reactive} from 'vue' import { ref, reactive } from 'vue'
import { User } from '@/api/user/interface' import { User } from '@/api/user/interface'
import { useHandleData } from '@/hooks/useHandleData' import { useHandleData } from '@/hooks/useHandleData'
import { useDownload } from '@/hooks/useDownload' import { useDownload } from '@/hooks/useDownload'
@@ -43,6 +43,7 @@ import userDataList from '@/api/user/userData'
import { useDictStore } from '@/stores/modules/dict' import { useDictStore } from '@/stores/modules/dict'
import SingleColumn from '@/views/demo/proTable/singleColumn.vue' import SingleColumn from '@/views/demo/proTable/singleColumn.vue'
import DoubleColumn from '@/views/demo/proTable/doubleColumn.vue' import DoubleColumn from '@/views/demo/proTable/doubleColumn.vue'
const dictStore = useDictStore() const dictStore = useDictStore()
import { import {
getUserList, getUserList,
@@ -54,6 +55,8 @@ import {
getUserStatus, getUserStatus,
} from '@/api/user/user' } from '@/api/user/user'
import { ElMessageBox } from 'element-plus' import { ElMessageBox } from 'element-plus'
import router from '@/routers'
const userData = userDataList const userData = userDataList
const singleColumn = ref() const singleColumn = ref()
const doubleColumn = ref() const doubleColumn = ref()
@@ -88,7 +91,7 @@ const columns = reactive<ColumnProps<User.ResUserList>[]>([
prop: 'username', prop: 'username',
label: '姓名', label: '姓名',
width: 120, width: 120,
search: { el: 'input'}, search: { el: 'input' },
}, },
{ {
prop: 'gender', prop: 'gender',
@@ -105,11 +108,11 @@ const columns = reactive<ColumnProps<User.ResUserList>[]>([
// 自定义 search 显示内容 // 自定义 search 显示内容
render: ({ searchParam }) => { render: ({ searchParam }) => {
return ( return (
<div class='flx-center'> <div class='flx-center'>
<el-input vModel_trim={searchParam.minAge} placeholder='最小年龄' /> <el-input vModel_trim={searchParam.minAge} placeholder='最小年龄' />
<span class='mr10 ml10'>-</span> <span class='mr10 ml10'>-</span>
<el-input vModel_trim={searchParam.maxAge} placeholder='最大年龄' /> <el-input vModel_trim={searchParam.maxAge} placeholder='最大年龄' />
</div> </div>
) )
}, },
}, },
@@ -125,19 +128,19 @@ const columns = reactive<ColumnProps<User.ResUserList>[]>([
fieldNames: { label: 'userLabel', value: 'userStatus' }, fieldNames: { label: 'userLabel', value: 'userStatus' },
render: scope => { render: scope => {
return ( return (
<> <>
{BUTTONS.value.status ? ( {BUTTONS.value.status ? (
<el-switch <el-switch
model-value={scope.row.status} model-value={scope.row.status}
active-text={scope.row.status ? '启用' : '禁用'} active-text={scope.row.status ? '启用' : '禁用'}
active-value={1} active-value={1}
inactive-value={0} inactive-value={0}
onClick={() => changeStatus(scope.row)} onClick={() => changeStatus(scope.row)}
/> />
) : ( ) : (
<el-tag type={scope.row.status ? 'success' : 'danger'}>{scope.row.status ? '启用' : '禁用'}</el-tag> <el-tag type={scope.row.status ? 'success' : 'danger'}>{scope.row.status ? '启用' : '禁用'}</el-tag>
)} )}
</> </>
) )
}, },
}, },
@@ -156,8 +159,9 @@ const columns = reactive<ColumnProps<User.ResUserList>[]>([
]) ])
// 删除用户信息 // 删除用户信息
const deleteAccount = async (params: User.ResUserList) => { const deleteAccount = async (params: User.ResUserList) => {
await useHandleData(deleteUser, { id: [params.id] }, `删除【${params.username}】用户`) // await useHandleData(deleteUser, { id: [params.id] }, `删除【${params.username}】用户`)
proTable.value?.getTableList() // proTable.value?.getTableList()
router.push('/plan/autoTest')
} }
// 批量删除用户信息 // 批量删除用户信息
const batchDelete = async (id: string[]) => { const batchDelete = async (id: string[]) => {
@@ -169,7 +173,7 @@ const batchDelete = async (id: string[]) => {
const resetPass = async (params: User.ResUserList) => { const resetPass = async (params: User.ResUserList) => {
// await useHandleData(resetUserPassWord, { id: params.id }, `重置【${params.username}】用户密码`) // await useHandleData(resetUserPassWord, { id: params.id }, `重置【${params.username}】用户密码`)
// proTable.value?.getTableList() // proTable.value?.getTableList()
doubleColumn.value.open("双列弹出框") doubleColumn.value.open('双列弹出框')
} }
// 切换用户状态 // 切换用户状态
const changeStatus = async (row: User.ResUserList) => { const changeStatus = async (row: User.ResUserList) => {
@@ -182,7 +186,7 @@ const changeStatus = async (row: User.ResUserList) => {
// 导出用户列表 // 导出用户列表
const downloadFile = async () => { const downloadFile = async () => {
ElMessageBox.confirm('确认导出用户数据?', '温馨提示', { type: 'warning' }).then(() => ElMessageBox.confirm('确认导出用户数据?', '温馨提示', { type: 'warning' }).then(() =>
useDownload(exportUserInfo, '用户列表', proTable.value?.searchParam), useDownload(exportUserInfo, '用户列表', proTable.value?.searchParam),
) )
} }
// 批量添加用户 // 批量添加用户
@@ -198,6 +202,6 @@ const batchAdd = () => {
} }
// 打开 drawer(新增、查看、编辑) // 打开 drawer(新增、查看、编辑)
const openDrawer = (title: string, row: Partial<User.ResUserList> = {}) => { const openDrawer = (title: string, row: Partial<User.ResUserList> = {}) => {
singleColumn.value.open("单列弹出框") singleColumn.value.open('单列弹出框')
} }
</script> </script>

View File

@@ -4,26 +4,27 @@
ref='proTable' ref='proTable'
:columns='columns' :columns='columns'
:data='logData' :data='logData'
@selection-change="handleSelectionChange" @selection-change='handleSelectionChange'
type="selection" type='selection'
> >
<!-- 表格 header 按钮 --> <!-- 表格 header 按钮 -->
<template #tableHeader> <template #tableHeader>
<el-button type='primary' :icon='Upload'>导出csv</el-button> <el-button type='primary' :icon='Upload'>导出csv</el-button>
</template> </template>
</ProTable> </ProTable>
</div> </div>
</template> </template>
<script setup lang='tsx' name='useProTable'> <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 { type Log } from '@/api/log/interface'
import ProTable from '@/components/ProTable/index.vue' import ProTable from '@/components/ProTable/index.vue'
import {Upload} from '@element-plus/icons-vue' import { Upload } from '@element-plus/icons-vue'
import logDataList from '@/api/log/logData' import logDataList from '@/api/log/logData'
import type { ColumnProps, ProTableInstance } from '@/components/ProTable/interface' import type { ColumnProps, ProTableInstance } from '@/components/ProTable/interface'
let multipleSelection = ref<string[]>([]);
let multipleSelection = ref<string[]>([])
const logData = logDataList const logData = logDataList
// ProTable 实例 // ProTable 实例
@@ -40,23 +41,13 @@ const columns = reactive<ColumnProps<Log.LogList>[]>([
prop: 'record_Time', prop: 'record_Time',
label: '记录时间', label: '记录时间',
width: 180, width: 180,
search: {
// 自定义 search 显示内容
render: ({ searchParam }) => {
return (
<div class='flx-center'>
<TimeControl/>
</div>
)
},
},
}, },
{ {
prop: 'user', prop: 'user',
label: '操作用户', label: '操作用户',
search: { el: 'select', props: { filterable: true } }, search: { el: 'select', props: { filterable: true } },
}, },
{ {
prop: 'type', prop: 'type',
label: '日志类型', label: '日志类型',
@@ -67,14 +58,29 @@ const columns = reactive<ColumnProps<Log.LogList>[]>([
label: '日志等级', label: '日志等级',
search: { el: 'select', props: { filterable: true } }, 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[]) => { const handleSelectionChange = (selection: Log.LogList[]) => {
multipleSelection.value = selection.map(row => row.id); // 更新选中的行 multipleSelection.value = selection.map(row => row.id) // 更新选中的行
}; }
</script> </script>
<style scoped> <style scoped>

View File

@@ -51,8 +51,6 @@ const getTreeData = (val: any) => {
defaultChecked.value = []; defaultChecked.value = [];
data.value = val; data.value = val;
defaultChecked.value.push(data.value[0].children[0].children[0].id); defaultChecked.value.push(data.value[0].children[0].children[0].id);
console.log(defaultChecked.value, "+++++++++++++");
console.log(treeList.value);
treeRef.value.setCurrentKey(defaultChecked.value); treeRef.value.setCurrentKey(defaultChecked.value);
}; };
const filterText = ref(""); const filterText = ref("");

View File

@@ -271,7 +271,6 @@ const dataCallback = (data: any) => {
total: data.length || data.total, //total total: data.length || data.total, //total
}; };
}; };
console.log(proTable.value, "proTable.value?proTable.value?proTable.value?");
// 如果你想在请求之前对当前请求参数做一些操作可以自定义如下函数params 为当前所有的请求参数(包括分页),最后返回请求列表接口 // 如果你想在请求之前对当前请求参数做一些操作可以自定义如下函数params 为当前所有的请求参数(包括分页),最后返回请求列表接口
// 默认不做操作就直接在 ProTable 组件上绑定 :requestApi="getUserList" // 默认不做操作就直接在 ProTable 组件上绑定 :requestApi="getUserList"
const getTableList = (params: any) => { const getTableList = (params: any) => {

View File

@@ -240,7 +240,6 @@ const dataCallback = (data: any) => {
total: data.length || data.total, total: data.length || data.total,
}; };
}; };
console.log(proTable.value, "proTable.value?proTable.value?proTable.value?");
// 如果你想在请求之前对当前请求参数做一些操作可以自定义如下函数params 为当前所有的请求参数(包括分页),最后返回请求列表接口 // 如果你想在请求之前对当前请求参数做一些操作可以自定义如下函数params 为当前所有的请求参数(包括分页),最后返回请求列表接口
// 默认不做操作就直接在 ProTable 组件上绑定 :requestApi="getUserList" // 默认不做操作就直接在 ProTable 组件上绑定 :requestApi="getUserList"
const getTableList = (params: any) => { const getTableList = (params: any) => {

View File

@@ -240,7 +240,6 @@ const dataCallback = (data: any) => {
total: data.length || data.total, total: data.length || data.total,
}; };
}; };
console.log(proTable.value, "proTable.value?proTable.value?proTable.value?");
// 如果你想在请求之前对当前请求参数做一些操作可以自定义如下函数params 为当前所有的请求参数(包括分页),最后返回请求列表接口 // 如果你想在请求之前对当前请求参数做一些操作可以自定义如下函数params 为当前所有的请求参数(包括分页),最后返回请求列表接口
// 默认不做操作就直接在 ProTable 组件上绑定 :requestApi="getUserList" // 默认不做操作就直接在 ProTable 组件上绑定 :requestApi="getUserList"
const getTableList = (params: any) => { const getTableList = (params: any) => {