pull/4/head
zgj 3 months ago
parent d82acbbe55
commit ea50457f51

@ -1,23 +1,38 @@
<template>
<!-- 根元素 div通过 style 绑定动态样式v-show 根据 isShow 的值控制元素的显示与隐藏 -->
<div :style="style" v-show="isShow">
<!-- 插槽用于在父组件中插入内容 -->
<slot></slot>
</div>
</template>
<script setup lang="ts" name="GridItem">
// vue
import { computed, inject, Ref, ref, useAttrs, watch } from 'vue'
// '../interface/index' BreakPoint Responsive
import { BreakPoint, Responsive } from '../interface/index'
// Props
type Props = {
// 0
offset?: number
// 1
span?: number
// false
suffix?: boolean
// xs undefined
xs?: Responsive
// sm undefined
sm?: Responsive
// md undefined
md?: Responsive
// lg undefined
lg?: Responsive
// xl undefined
xl?: Responsive
}
// 使 withDefaults defineProps
const props = withDefaults(defineProps<Props>(), {
offset: 0,
span: 1,
@ -29,43 +44,63 @@ const props = withDefaults(defineProps<Props>(), {
xl: undefined
})
// props
const attrs = useAttrs() as any
// isShow true
const isShow = ref(true)
//
// breakPoint Ref<BreakPoint> ref('xl')
const breakPoint = inject<Ref<BreakPoint>>('breakPoint', ref('xl'))
// shouldHiddenIndex Ref<number> ref(-1)
const shouldHiddenIndex = inject<Ref<number>>('shouldHiddenIndex', ref(-1))
// shouldHiddenIndex breakPoint
watch(
// shouldHiddenIndex.value breakPoint.value
() => [shouldHiddenIndex.value, breakPoint.value],
// n shouldHiddenIndex.value breakPoint.value
n => {
// attrs index
if (attrs.index) {
isShow.value = !(n[0] !== -1 && parseInt(attrs.index) >= n[0])
//
isShow.value =!(n[0]!== -1 && parseInt(attrs.index) >= n[0])
}
},
// immediate: true
{ immediate: true }
)
// gap 0
const gap = inject('gap', 0)
// cols Ref<number> ref(4)
const cols = inject<Ref<number>>('cols', ref(4))
// style props
const style = computed(() => {
let span = props[breakPoint.value]?.span ?? props.span
let offset = props[breakPoint.value]?.offset ?? props.offset
// span 使 props.span
let span = props[breakPoint.value]?.span?? props.span
// offset 使 props.offset
let offset = props[breakPoint.value]?.offset?? props.offset
//
if (props.suffix) {
return {
// gridColumnStart
gridColumnStart: cols.value - span - offset + 1,
// gridColumnEnd
gridColumnEnd: `span ${span + offset}`,
// marginLeft
marginLeft:
offset !== 0
offset!== 0
? `calc(((100% + ${gap}px) / ${span + offset}) * ${offset})`
: 'unset'
}
} else {
return {
// gridColumn
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
}/span ${span + offset > cols.value? cols.value : span + offset}`,
// marginLeft
marginLeft:
offset !== 0
offset!== 0
? `calc(((100% + ${gap}px) / ${span + offset}) * ${offset})`
: 'unset'
}

@ -1,10 +1,13 @@
<template>
<!-- 根元素 div通过 style 绑定动态样式用于包裹插槽内容 -->
<div :style="style">
<!-- 插槽用于父组件插入内容 -->
<slot></slot>
</div>
</template>
<script setup lang="ts" name="Grid">
// 'vue' Vue
import {
ref,
watch,
@ -19,50 +22,68 @@ import {
VNodeArrayChildren,
VNode
} from 'vue'
// BreakPoint
import type { BreakPoint } from './interface/index'
// Props
type Props = {
//
cols?: number | Record<BreakPoint, number>
// false
collapsed?: boolean
// 1
collapsedRows?: number
// [, ]
gap?: [number, number] | number
}
// 使 withDefaults
const props = withDefaults(defineProps<Props>(), {
//
cols: () => ({ xs: 1, sm: 2, md: 2, lg: 3, xl: 4 }),
//
collapsed: false,
// 1
collapsedRows: 1,
// 0
gap: 0
})
// collapsed true findIndex
onBeforeMount(() => props.collapsed && findIndex())
// resize window resize
onMounted(() => {
resize({ target: { innerWidth: window.innerWidth } } as any)
window.addEventListener('resize', resize)
})
// resize window resize
onActivated(() => {
resize({ target: { innerWidth: window.innerWidth } } as any)
window.addEventListener('resize', resize)
})
// window resize
onUnmounted(() => {
window.removeEventListener('resize', resize)
})
// window resize
onDeactivated(() => {
window.removeEventListener('resize', resize)
})
//
// resize breakPoint
const resize = (e: UIEvent) => {
//
let width = (e.target as Window).innerWidth
// breakPoint
switch (!!width) {
case width < 768:
breakPoint.value = 'xs'
break
case width >= 768 && width < 992:
breakPoint.value = 'sm'
breakPoint.value ='sm'
break
case width >= 992 && width < 1200:
breakPoint.value = 'md'
breakPoint.value ='md'
break
case width >= 1200 && width < 1920:
breakPoint.value = 'lg'
@ -73,58 +94,66 @@ const resize = (e: UIEvent) => {
}
}
// gap
provide('gap', Array.isArray(props.gap) ? props.gap[0] : props.gap)
// gap gap 使 gap
provide('gap', Array.isArray(props.gap)? props.gap[0] : props.gap)
//
// breakPoint
let breakPoint = ref<BreakPoint>('xl')
provide('breakPoint', breakPoint)
// index
// index
const hiddenIndex = ref(-1)
provide('shouldHiddenIndex', hiddenIndex)
// cols
// cols cols breakPoint 使 cols
const cols = computed(() => {
if (typeof props.cols === 'object')
return props.cols[breakPoint.value] ?? props.cols
return props.cols[breakPoint.value]?? props.cols
return props.cols
})
provide('cols', cols)
//
const slots = useSlots().default!()
// index
// findIndex index
const findIndex = () => {
//
let fields: VNodeArrayChildren = []
//
let suffix: any = null
//
slots.forEach((slot: any) => {
// 'GridItem' suffix
if (
typeof slot.type === 'object' &&
slot.type.name === 'GridItem' &&
slot.props?.suffix !== undefined
slot.props?.suffix!== undefined
)
suffix = slot
if (typeof slot.type === 'symbol' && Array.isArray(slot.children))
// symbol
if (typeof slot.type ==='symbol' && Array.isArray(slot.children))
slot.children.forEach((child: any) => fields.push(child))
})
// suffix
// suffix
let suffixCols = 0
if (suffix) {
suffixCols =
(suffix.props![breakPoint.value]?.span ?? suffix.props?.span ?? 1) +
(suffix.props![breakPoint.value]?.offset ?? suffix.props?.offset ?? 0)
(suffix.props![breakPoint.value]?.span?? suffix.props?.span?? 1) +
(suffix.props![breakPoint.value]?.offset?? suffix.props?.offset?? 0)
}
try {
//
let find = false
//
fields.reduce((prev = 0, current, index) => {
prev +=
((current as VNode)!.props![breakPoint.value]?.span ??
(current as VNode)!.props?.span ??
((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 ??
((current as VNode)!.props![breakPoint.value]?.offset??
(current as VNode)!.props?.offset??
0)
if ((prev as number) > props.collapsedRows * cols.value - suffixCols) {
hiddenIndex.value = index
@ -133,13 +162,14 @@ const findIndex = () => {
}
return prev
}, 0)
// hiddenIndex -1
if (!find) hiddenIndex.value = -1
} catch (e) {
// console.warn(e);
}
}
// findIndex
// breakPoint collapsed true findIndex
watch(
() => breakPoint.value,
() => {
@ -147,7 +177,7 @@ watch(
}
)
// collapsed
// collapsed true findIndex hiddenIndex -1
watch(
() => props.collapsed,
value => {
@ -156,14 +186,14 @@ watch(
}
)
//
//
const gap = 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'
})
// style
//
const style = computed(() => {
return {
display: 'grid',
@ -172,5 +202,6 @@ const style = computed(() => {
}
})
// breakPoint便访
defineExpose({ breakPoint })
</script>

@ -1,6 +1,12 @@
export type BreakPoint = 'xs' | 'sm' | 'md' | 'lg' | 'xl'
// 定义一个类型别名 BreakPoint它的值只能是 'xs'、'sm'、'md'、'lg' 或 'xl' 中的一个,
// 通常用于表示响应式布局中的不同屏幕断点
export type BreakPoint = 'xs' |'sm' |'md' | 'lg' | 'xl';
// 定义一个类型别名 Responsive它是一个对象类型包含两个可选属性 span 和 offset。
// span 用于表示在响应式布局中元素所占的列数,类型为 number 类型的可选值。
// offset 用于表示在响应式布局中元素的偏移量,类型为 number 类型的可选值。
// 这个类型通常用于描述在不同屏幕断点下元素的布局属性
export type Responsive = {
span?: number
offset?: number
}
span?: number;
offset?: number;
};

@ -1,4 +1,12 @@
<template>
<!-- Component Vue 中的动态组件:is="icon" 表示根据 icon 的值来动态渲染对应的图标组件 -->
<!-- :theme="theme" 绑定图标主题如轮廓outline填充filled -->
<!-- :size="size" 绑定图标大小可以是数字或字符串类型 -->
<!-- :spin="spin" 绑定图标是否旋转的状态布尔类型 -->
<!-- :fill="fill" 绑定图标的填充颜色可以是字符串或字符串数组 -->
<!-- :strokeLinecap="strokeLinecap" 绑定图标描边端点的样式 -->
<!-- :strokeLinejoin="strokeLinejoin" 绑定图标描边连接处的样式 -->
<!-- :strokeWidth="strokeWidth" 绑定图标描边的宽度 -->
<Component
:is="icon"
:theme="theme"
@ -12,16 +20,26 @@
</template>
<script setup lang="ts">
// '@icon-park/vue-next/lib/runtime' Icon
import type { Icon } from '@icon-park/vue-next/lib/runtime'
// 使 defineProps
defineProps<{
// icon Icon
icon: Icon
theme?: 'outline' | 'filled' | 'two-tone' | 'multi-color'
// theme 'outline' | 'filled' | 'two-tone' |'multi-color'
theme?: 'outline' | 'filled' | 'two-tone' |'multi-color'
// size number | string
size?: number | string
// spin boolean
spin?: boolean
// fill string | string[]
fill?: string | string[]
strokeLinecap?: 'butt' | 'round' | 'square'
// strokeLinecap 'butt' | 'round' |'square'
strokeLinecap?: 'butt' | 'round' |'square'
// strokeLinejoin 'miter' | 'round' | 'bevel'
strokeLinejoin?: 'miter' | 'round' | 'bevel'
// strokeWidth number
strokeWidth?: number
}>()
</script>

@ -0,0 +1,109 @@
<template>
<!-- 根元素 div通过 style 绑定动态样式v-show 根据 isShow 的值控制元素的显示与隐藏 -->
<div :style="style" v-show="isShow">
<!-- 插槽用于在父组件中插入内容 -->
<slot></slot>
</div>
</template>
<script setup lang="ts" name="GridItem">
// vue
import { computed, inject, Ref, ref, useAttrs, watch } from 'vue'
// '../interface/index' BreakPoint Responsive
import { BreakPoint, Responsive } from '../interface/index'
// Props
type Props = {
// 0
offset?: number
// 1
span?: number
// false
suffix?: boolean
// xs undefined
xs?: Responsive
// sm undefined
sm?: Responsive
// md undefined
md?: Responsive
// lg undefined
lg?: Responsive
// xl undefined
xl?: Responsive
}
// 使 withDefaults defineProps
const props = withDefaults(defineProps<Props>(), {
offset: 0,
span: 1,
suffix: false,
xs: undefined,
sm: undefined,
md: undefined,
lg: undefined,
xl: undefined
})
// props
const attrs = useAttrs() as any
// isShow true
const isShow = ref(true)
// breakPoint Ref<BreakPoint> ref('xl')
const breakPoint = inject<Ref<BreakPoint>>('breakPoint', ref('xl'))
// shouldHiddenIndex Ref<number> ref(-1)
const shouldHiddenIndex = inject<Ref<number>>('shouldHiddenIndex', ref(-1))
// shouldHiddenIndex breakPoint
watch(
// shouldHiddenIndex.value breakPoint.value
() => [shouldHiddenIndex.value, breakPoint.value],
// n shouldHiddenIndex.value breakPoint.value
n => {
// attrs index
if (attrs.index) {
//
isShow.value =!(n[0]!== -1 && parseInt(attrs.index) >= n[0])
}
},
// immediate: true
{ immediate: true }
)
// gap 0
const gap = inject('gap', 0)
// cols Ref<number> ref(4)
const cols = inject<Ref<number>>('cols', ref(4))
// style props
const style = computed(() => {
// span 使 props.span
let span = props[breakPoint.value]?.span?? props.span
// offset 使 props.offset
let offset = props[breakPoint.value]?.offset?? props.offset
//
if (props.suffix) {
return {
// gridColumnStart
gridColumnStart: cols.value - span - offset + 1,
// gridColumnEnd
gridColumnEnd: `span ${span + offset}`,
// marginLeft
marginLeft:
offset!== 0
? `calc(((100% + ${gap}px) / ${span + offset}) * ${offset})`
: 'unset'
}
} else {
return {
// gridColumn
gridColumn: `span ${
span + offset > cols.value? cols.value : span + offset
}/span ${span + offset > cols.value? cols.value : span + offset}`,
// marginLeft
marginLeft:
offset!== 0
? `calc(((100% + ${gap}px) / ${span + offset}) * ${offset})`
: 'unset'
}
}
})
</script>

@ -0,0 +1,207 @@
<template>
<!-- 根元素 div通过 style 绑定动态样式用于包裹插槽内容 -->
<div :style="style">
<!-- 插槽用于父组件插入内容 -->
<slot></slot>
</div>
</template>
<script setup lang="ts" name="Grid">
// 'vue' Vue
import {
ref,
watch,
useSlots,
computed,
provide,
onBeforeMount,
onMounted,
onUnmounted,
onDeactivated,
onActivated,
VNodeArrayChildren,
VNode
} from 'vue'
// BreakPoint
import type { BreakPoint } from './interface/index'
// Props
type Props = {
//
cols?: number | Record<BreakPoint, number>
// false
collapsed?: boolean
// 1
collapsedRows?: number
// [, ]
gap?: [number, number] | number
}
// 使 withDefaults
const props = withDefaults(defineProps<Props>(), {
//
cols: () => ({ xs: 1, sm: 2, md: 2, lg: 3, xl: 4 }),
//
collapsed: false,
// 1
collapsedRows: 1,
// 0
gap: 0
})
// collapsed true findIndex
onBeforeMount(() => props.collapsed && findIndex())
// resize window resize
onMounted(() => {
resize({ target: { innerWidth: window.innerWidth } } as any)
window.addEventListener('resize', resize)
})
// resize window resize
onActivated(() => {
resize({ target: { innerWidth: window.innerWidth } } as any)
window.addEventListener('resize', resize)
})
// window resize
onUnmounted(() => {
window.removeEventListener('resize', resize)
})
// window resize
onDeactivated(() => {
window.removeEventListener('resize', resize)
})
// resize breakPoint
const resize = (e: UIEvent) => {
//
let width = (e.target as Window).innerWidth
// breakPoint
switch (!!width) {
case width < 768:
breakPoint.value = 'xs'
break
case width >= 768 && width < 992:
breakPoint.value ='sm'
break
case width >= 992 && width < 1200:
breakPoint.value ='md'
break
case width >= 1200 && width < 1920:
breakPoint.value = 'lg'
break
case width >= 1920:
breakPoint.value = 'xl'
break
}
}
// gap gap 使 gap
provide('gap', Array.isArray(props.gap)? props.gap[0] : props.gap)
// breakPoint
let breakPoint = ref<BreakPoint>('xl')
provide('breakPoint', breakPoint)
// index
const hiddenIndex = ref(-1)
provide('shouldHiddenIndex', hiddenIndex)
// cols cols breakPoint 使 cols
const cols = computed(() => {
if (typeof props.cols === 'object')
return props.cols[breakPoint.value]?? props.cols
return props.cols
})
provide('cols', cols)
//
const slots = useSlots().default!()
// findIndex index
const findIndex = () => {
//
let fields: VNodeArrayChildren = []
//
let suffix: any = null
//
slots.forEach((slot: any) => {
// 'GridItem' suffix
if (
typeof slot.type === 'object' &&
slot.type.name === 'GridItem' &&
slot.props?.suffix!== undefined
)
suffix = slot
// symbol
if (typeof slot.type ==='symbol' && Array.isArray(slot.children))
slot.children.forEach((child: any) => fields.push(child))
})
// suffix
let suffixCols = 0
if (suffix) {
suffixCols =
(suffix.props![breakPoint.value]?.span?? suffix.props?.span?? 1) +
(suffix.props![breakPoint.value]?.offset?? suffix.props?.offset?? 0)
}
try {
//
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)
if ((prev as number) > props.collapsedRows * cols.value - suffixCols) {
hiddenIndex.value = index
find = true
throw 'find it'
}
return prev
}, 0)
// hiddenIndex -1
if (!find) hiddenIndex.value = -1
} catch (e) {
// console.warn(e);
}
}
// breakPoint collapsed true findIndex
watch(
() => breakPoint.value,
() => {
if (props.collapsed) findIndex()
}
)
// collapsed true findIndex hiddenIndex -1
watch(
() => props.collapsed,
value => {
if (value) return findIndex()
hiddenIndex.value = -1
}
)
//
const gap = 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'
})
//
const style = computed(() => {
return {
display: 'grid',
gridGap: gap.value,
gridTemplateColumns: `repeat(${cols.value}, minmax(0, 1fr))`
}
})
// breakPoint便访
defineExpose({ breakPoint })
</script>

@ -0,0 +1,12 @@
// 定义一个类型别名 BreakPoint它的值只能是 'xs'、'sm'、'md'、'lg' 或 'xl' 中的一个,
// 通常用于表示响应式布局中的不同屏幕断点
export type BreakPoint = 'xs' |'sm' |'md' | 'lg' | 'xl';
// 定义一个类型别名 Responsive它是一个对象类型包含两个可选属性 span 和 offset。
// span 用于表示在响应式布局中元素所占的列数,类型为 number 类型的可选值。
// offset 用于表示在响应式布局中元素的偏移量,类型为 number 类型的可选值。
// 这个类型通常用于描述在不同屏幕断点下元素的布局属性
export type Responsive = {
span?: number;
offset?: number;
};

@ -0,0 +1,45 @@
<template>
<!-- Component Vue 中的动态组件:is="icon" 表示根据 icon 的值来动态渲染对应的图标组件 -->
<!-- :theme="theme" 绑定图标主题如轮廓outline填充filled -->
<!-- :size="size" 绑定图标大小可以是数字或字符串类型 -->
<!-- :spin="spin" 绑定图标是否旋转的状态布尔类型 -->
<!-- :fill="fill" 绑定图标的填充颜色可以是字符串或字符串数组 -->
<!-- :strokeLinecap="strokeLinecap" 绑定图标描边端点的样式 -->
<!-- :strokeLinejoin="strokeLinejoin" 绑定图标描边连接处的样式 -->
<!-- :strokeWidth="strokeWidth" 绑定图标描边的宽度 -->
<Component
:is="icon"
:theme="theme"
:size="size"
:spin="spin"
:fill="fill"
:strokeLinecap="strokeLinecap"
:strokeLinejoin="strokeLinejoin"
:strokeWidth="strokeWidth"
/>
</template>
<script setup lang="ts">
// '@icon-park/vue-next/lib/runtime' Icon
import type { Icon } from '@icon-park/vue-next/lib/runtime'
// 使 defineProps
defineProps<{
// icon Icon
icon: Icon
// theme 'outline' | 'filled' | 'two-tone' |'multi-color'
theme?: 'outline' | 'filled' | 'two-tone' |'multi-color'
// size number | string
size?: number | string
// spin boolean
spin?: boolean
// fill string | string[]
fill?: string | string[]
// strokeLinecap 'butt' | 'round' |'square'
strokeLinecap?: 'butt' | 'round' |'square'
// strokeLinejoin 'miter' | 'round' | 'bevel'
strokeLinejoin?: 'miter' | 'round' | 'bevel'
// strokeWidth number
strokeWidth?: number
}>()
</script>

@ -0,0 +1,88 @@
<template>
<!-- 注释说明该部分是列设置 -->
<!-- el-drawer Element Plus 中的抽屉组件用于展示列设置的内容 -->
<!-- title="列设置" 设置抽屉的标题 -->
<!-- v-model="drawerVisible" 双向绑定抽屉的显示隐藏状态drawerVisible 是一个响应式变量 -->
<!-- size="450px" 设置抽屉的宽度为 450px -->
<el-drawer title="列设置" v-model="drawerVisible" size="450px">
<!-- 定义一个包含表格的 div 容器 -->
<div class="table-main">
<!-- el-table Element Plus 中的表格组件用于展示列设置的数据 -->
<!-- :data="colSetting" 绑定表格的数据colSetting 是一个数组包含列的设置信息 -->
<!-- :border="true" 设置表格有边框 -->
<!-- row-key="prop" 为表格的每一行设置唯一的标识这里使用 prop 作为标识 -->
<!-- default-expand-all 表示默认展开所有行对于树形表格 -->
<!-- :tree-props="{ children: '_children' }" 配置表格为树形表格指定子节点的字段为 _children -->
<el-table
:data="colSetting"
:border="true"
row-key="prop"
default-expand-all
:tree-props="{ children: '_children' }"
>
<!-- el-table-column 是表格列组件用于定义表格的列 -->
<!-- prop="label" 绑定列的数据字段为 label -->
<!-- align="center" 设置列内容居中对齐 -->
<!-- label="列名" 设置列的标题为列名 -->
<el-table-column prop="label" align="center" label="列名" />
<!-- 定义显示列prop="isShow" 绑定数据字段为 isShow -->
<!-- v-slot="scope" 定义作用域插槽scope 包含当前行的数据 -->
<el-table-column
prop="isShow"
align="center"
label="显示"
v-slot="scope"
>
<!-- el-switch Element Plus 中的开关组件v-model="scope.row.isShow" 双向绑定当前行的 isShow 字段 -->
<el-switch v-model="scope.row.isShow"></el-switch>
</el-table-column>
<!-- 定义排序列prop="sortable" 绑定数据字段为 sortable -->
<!-- v-slot="scope" 定义作用域插槽scope 包含当前行的数据 -->
<el-table-column
prop="sortable"
align="center"
label="排序"
v-slot="scope"
>
<!-- el-switch Element Plus 中的开关组件v-model="scope.row.sortable" 双向绑定当前行的 sortable 字段 -->
<el-switch v-model="scope.row.sortable"></el-switch>
</el-table-column>
<!-- 定义表格为空时显示的内容#empty 是表格的 empty 插槽 -->
<template #empty>
<div class="table-empty">
<div>暂无可配置列</div>
</div>
</template>
</el-table>
</div>
</el-drawer>
</template>
<script setup lang="ts" name="ColSetting">
// Vue ref
import { ref } from 'vue'
// ProTable ColumnProps
import { ColumnProps } from '@/components/ProTable/interface'
// 使 defineProps colSetting ColumnProps
defineProps<{ colSetting: ColumnProps[] }>()
// drawerVisible false
const drawerVisible = ref<boolean>(false)
// openColSetting drawerVisible true
const openColSetting = () => {
drawerVisible.value = true
}
// openColSetting 便
defineExpose({
openColSetting
})
</script>
<style scoped lang="scss">
// cursor-move move
.cursor-move {
cursor: move;
}
</style>

@ -0,0 +1,47 @@
<template>
<!-- 注释说明该部分是分页组件 -->
<!-- el-pagination Element Plus 中的分页组件 -->
<!-- :current-page="pageable.pageNum" 双向绑定当前页码pageable.pageNum 是一个响应式变量表示当前页码 -->
<!-- :page-size="pageable.pageSize" 双向绑定每页显示的数量pageable.pageSize 是一个响应式变量表示每页显示的数量 -->
<!-- :page-sizes="[10, 25, 50, 100]" 设置每页显示数量的可选值 -->
<!-- :background="true" 设置分页组件的背景颜色 -->
<!-- layout="total, sizes, prev, pager, next, jumper" 设置分页组件的布局包括总数量每页显示数量选择器上一页页码下一页跳转到指定页 -->
<!-- :total="pageable.total" 绑定总数量pageable.total 是一个响应式变量表示数据的总数量 -->
<!-- @size-change="handleSizeChange" 监听每页显示数量改变的事件当用户选择不同的每页显示数量时会调用 handleSizeChange 函数 -->
<!-- @current-change="handleCurrentChange" 监听当前页码改变的事件当用户点击上一页下一页或跳转到指定页时会调用 handleCurrentChange 函数 -->
<el-pagination
:current-page="pageable.pageNum"
:page-size="pageable.pageSize"
:page-sizes="[10, 25, 50, 100]"
:background="true"
layout="total, sizes, prev, pager, next, jumper"
:total="pageable.total"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
></el-pagination>
</template>
<script setup lang="ts" name="Pagination">
// Pageable
// pageNum number
// pageSize number
// total number
interface Pageable {
pageNum: number
pageSize: number
total: number
}
// PaginationProps
// pageable Pageable
// handleSizeChange number size
// handleCurrentChange number currentPage
interface PaginationProps {
pageable: Pageable
handleSizeChange: (size: number) => void
handleCurrentChange: (currentPage: number) => void
}
// 使 defineProps PaginationProps
defineProps<PaginationProps>()
</script>

@ -0,0 +1,103 @@
<template>
<!-- 使用动态组件根据 renderLoop 函数的返回值来渲染不同的表格列 -->
<component :is="renderLoop(column)"></component>
</template>
<script lang="tsx" setup name="TableColumn">
// vue
import { inject, ref, useSlots } from 'vue'
// ProTable ColumnProps
import { ColumnProps } from '@/components/ProTable/interface'
//
import { filterEnum, formatValue, handleRowAccordingToProp } from '@/utils/util'
// 使 defineProps column ColumnProps
defineProps<{ column: ColumnProps }>()
//
const slots = useSlots()
// enumMap Ref<Map<any, any>> Map
const enumMap = inject('enumMap', ref(new Map()))
// renderCellData
const renderCellData = (item: ColumnProps, scope: { [key: string]: any }) => {
// prop enumMap
return enumMap.value.get(item.prop) && item.isFilterEnum
// filterEnum
? filterEnum(
handleRowAccordingToProp(scope.row, item.prop!),
enumMap.value.get(item.prop)!,
item.fieldNames
)
//
: formatValue(handleRowAccordingToProp(scope.row, item.prop!))
}
// getTagType
const getTagType = (item: ColumnProps, scope: { [key: string]: any }) => {
// filterEnum
return filterEnum(
handleRowAccordingToProp(scope.row, item.prop!),
enumMap.value.get(item.prop),
item.fieldNames,
'tag'
) as any
}
// renderLoop
const renderLoop = (item: ColumnProps) => {
return (
<>
//
{item.isShow && (
// el-table-column
<el-table-column
//
{...item}
//
align={item.align?? 'center'}
// prop 'operation'
showOverflowTooltip={
item.showOverflowTooltip?? item.prop!== 'operation'
}
>
{{
//
default: (scope: any) => {
//
if (item._children)
//
return item._children.map(child => renderLoop(child))
//
if (item.render) return item.render(scope)
// prop
if (slots[item.prop!]) return slots[item.prop!]!(scope)
//
if (item.tag)
return (
// el-tag
<el-tag type={getTagType(item, scope)}>
{renderCellData(item, scope)}
</el-tag>
)
//
return renderCellData(item, scope)
},
//
header: () => {
//
if (item.headerRender) return item.headerRender(item)
// propHeader
if (slots[`${item.prop}Header`])
return slots[`${item.prop}Header`]!({ row: item })
// label
return item.label
}
}}
</el-table-column>
)}
</>
)
}
</script>

@ -0,0 +1,87 @@
## ProTable 文档 📚
### 1、ProTable 属性ProTableProps
> 使用 `v-bind="$attrs"` 通过属性透传将 **ProTable** 组件属性全部透传到 **el-table** 上,所以我们支持 **el-table** 的所有 **Props** 属性。在此基础上,还扩展了以下 **Props**
| 属性名 | 类型 | 是否必传 | 默认值 | 属性描述 |
| :----------: | :---------: | :------: | :-----------------------------------: | :--------------------------------------------------------------------------------------------------: |
| columns | ColumnProps | ✅ | — | ProTable 组件会根据此字段渲染搜索表单与表格列,详情见 ColumnProps |
| requestApi | Function | ✅ | — | 获取表格数据的请求 API |
| requestAuto | Boolean | ❌ | true | 表格初始化是否自动执行请求 API |
| dataCallback | Function | ❌ | — | 后台返回数据的回调函数,可对后台返回数据进行处理 |
| title | String | ❌ | — | 表格标题,目前只在打印的时候用到 |
| pagination | Boolean | ❌ | true | 是否显示分页组件pagination 为 false 后台返回数据应该没有分页信息 和 list 字段data 就是 list 数据 |
| initParam | Object | ❌ | {} | 表格请求的初始化参数,该值变化会自动请求表格数据 |
| toolButton | Boolean | ❌ | true | 是否显示表格功能按钮 |
| rowKey | String | ❌ | 'id' | 当表格数据多选时,所指定的 id |
| searchCol | Object | ❌ | { xs: 1, sm: 2, md: 2, lg: 3, xl: 4 } | 表格搜索项每列占比配置 |
### 2、Column 配置ColumnProps
> 使用 `v-bind="column"` 通过属性透传将每一项 **column** 属性全部透传到 **el-table-column** 上,所以我们支持 **el-table-column** 的所有 **Props** 属性。在此基础上,还扩展了以下 **Props**
| 属性名 | 类型 | 是否必传 | 默认值 | 属性描述 |
| :----------: | :----------------: | :------: | :----: | :---------------------------------------------------------------------------------------------: |
| tag | Boolean | ❌ | false | 当前单元格值是否为标签展示,可通过 enum 数据中 tagType 字段指定 tag 类型 |
| isShow | Boolean | ❌ | true | 当前列是否显示在表格内(只对 prop 列生效) |
| search | SearchProps | ❌ | — | 搜索项配置,详情见 SearchProps |
| enum | Object \| Function | ❌ | — | 字典,可格式化单元格内容,还可以作为搜索框的下拉选项(字典可以为 API 请求函数,内部会自动执行) |
| isFilterEnum | Boolean | ❌ | true | 当前单元格值是否根据 enum 格式化(例如 enum 只作为搜索项数据,不参与内容格式化) |
| fieldNames | Object | ❌ | — | 指定字典 label && value 的 key 值 |
| headerRender | Function | ❌ | — | 自定义表头内容渲染tsx 语法、h 语法) |
| render | Function | ❌ | — | 自定义单元格内容渲染tsx 语法、h 语法) |
| \_children | ColumnProps | ❌ | — | 多级表头 |
### 3、搜索项 配置SearchProps
> 使用 `v-bind="column.search.props“` 通过属性透传将 **search.props** 属性全部透传到每一项搜索组件上,所以我们支持 **input、select、tree-select、date-packer、time-picker、time-select、switch** 大部分属性,并在其基础上还扩展了以下 **Props**
| 属性名 | 类型 | 是否必传 | 默认值 | 属性描述 |
| :----------: | :----: | :------: | :----: | :--------------------------------------------------------------------------------------------------------------------------------------------: |
| el | String | ✅ | — | 当前项搜索框的类型支持input、input-number、select、select-v2、tree-select、cascader、date-packer、time-picker、time-select、switch、slider |
| props | Object | ❌ | — | 根据 element plus 官方文档来传递,该属性所有值会透传到组件 |
| defaultValue | Any | ❌ | — | 搜索项默认值 |
| key | String | ❌ | — | 当搜索项 key 不为 prop 属性时,可通过 key 指定 |
| order | Number | ❌ | — | 搜索项排序(从小到大) |
| span | Number | ❌ | 1 | 搜索项所占用的列数,默认为 1 列 |
| offset | Number | ❌ | — | 搜索字段左侧偏移列数 |
### 4、ProTable 事件:
> 根据 **ElementPlus Table** 文档在 **ProTable** 组件上绑定事件即可,组件会通过 **$attrs** 透传给 **el-table**
>
> [el-table 事件文档链接](https://element-plus.org/zh-CN/component/table.html#table-%E4%BA%8B%E4%BB%B6)
### 5、ProTable 方法:
> **ProTable** 组件暴露了 **el-table** 实例和一些组件内部的参数和方法:
>
> [el-table 方法文档链接](https://element-plus.org/zh-CN/component/table.html#table-%E6%96%B9%E6%B3%95)
| 方法名 | 描述 |
| :-------------: | :-------------------------------------------------------------------: |
| element | `el-table` 实例,可以通过`element.方法名`来调用 `el-table` 的所有方法 |
| tableData | 当前页面所展示的数据 |
| searchParam | 所有的搜索参数,不包含分页 |
| pageable | 当前表格的分页数据 |
| getTableList | 获取、刷新表格数据的方法(携带所有参数) |
| reset | 重置表格查询参数,相当于点击重置搜索按钮 |
| clearSelection | 清空表格所选择的数据,除此方法之外还可使用 `element.clearSelection()` |
| enumMap | 当前表格使用的所有字典数据Map 数据结构) |
| isSelected | 表格是否选中数据 |
| selectedList | 表格选中的数据列表 |
| selectedListIds | 表格选中的数据列表的 id |
### 6、ProTable 插槽:
| 插槽名 | 描述 |
| :----------------------: | :-------------------------------------------------------------------------------------------------------------------------------------: |
| — | 默认插槽,支持直接在 ProTable 中写 el-table-column 标签 |
| tableHeader | 自定义表格头部左侧区域的插槽,一般情况该区域放操作按钮 |
| toolButton | 自定义表格头部左右侧侧功能区域的插槽 |
| append | 插入至表格最后一行之后的内容, 如果需要对表格的内容进行无限滚动操作,可能需要用到这个 slot。 若表格有合计行,该 slot 会位于合计行之上。 |
| empty | 当表格数据为空时自定义的内容 |
| pagination | 分页组件插槽 |
| `column.prop` | 单元格的作用域插槽 |
| `column.prop` + "Header" | 表头的作用域插槽 |

@ -0,0 +1,397 @@
<!-- 📚📚📚 Pro-Table 文档: https://juejin.cn/post/7166068828202336263 -->
<template>
<!-- 查询表单 card -->
<!-- SearchForm 是自定义的搜索表单组件 -->
<!-- :search="search" 绑定搜索方法 -->
<!-- :reset="reset" 绑定重置方法 -->
<!-- :searchParam="searchParam" 绑定搜索参数 -->
<!-- :columns="searchColumns" 绑定搜索列配置 -->
<!-- :searchCol="searchCol" 绑定搜索项每列占比配置 -->
<!-- v-show="isShowSearch" 根据 isShowSearch 的值显示或隐藏搜索表单 -->
<!-- ref="searchForm" 为搜索表单组件添加引用 -->
<SearchForm
:search="search"
:reset="reset"
:searchParam="searchParam"
:columns="searchColumns"
:searchCol="searchCol"
v-show="isShowSearch"
ref="searchForm"
/>
<!-- 表格内容 card -->
<MyCard class="mt-2">
<div class="card table-main">
<div class="table-header">
<div class="flex justify-between header-button-lf mb-2">
<div>
<!-- 具名插槽 tableHeader传递了 selectedListIdsselectedList isSelected 数据 -->
<slot
name="tableHeader"
:selectedListIds="selectedListIds"
:selectedList="selectedList"
:isSelected="isSelected"
/>
</div>
<div class="header-button-ri">
<!-- 具名插槽 toolButton包含了一些功能按钮 -->
<slot name="toolButton">
<!-- 刷新按钮点击时调用 getTableList 方法获取表格数据 -->
<el-button :icon="Refresh" circle @click="getTableList" />
<!-- <el-button :icon="Printer" circle v-if="columns.length" @click="handlePrint" /> -->
<!-- 列设置按钮 columns 存在时显示点击时调用 openColSetting 方法打开列设置 -->
<el-button
:icon="Operation"
circle
v-if="columns.length"
@click="openColSetting"
/>
<!-- 搜索按钮 searchColumns 存在时显示点击时切换 isShowSearch 的值显示或隐藏搜索表单 -->
<el-button
:icon="Search"
circle
v-if="searchColumns.length"
@click="isShowSearch = !isShowSearch"
/>
</slot>
</div>
</div>
</div>
<!-- 表格主体 -->
<!-- el-table Element Plus 的表格组件 -->
<!-- ref="tableRef" 为表格组件添加引用 -->
<!-- v-bind="$attrs" 绑定父组件传递的所有属性 -->
<!-- :data="tableData" 绑定表格数据 -->
<!-- :border="border" 绑定表格是否有纵向边框 -->
<!-- :row-key="rowKey" 绑定行数据的唯一标识 -->
<!-- @selection-change="selectionChange" 监听表格行选择变化事件 -->
<el-table
ref="tableRef"
v-bind="$attrs"
:data="tableData"
:border="border"
:row-key="rowKey"
@selection-change="selectionChange"
>
<!-- 默认插槽 -->
<slot></slot>
<!-- 循环渲染表格列 -->
<template v-for="item in tableColumns" :key="item">
<!-- selection || index 类型的列 -->
<el-table-column
v-bind="item"
:align="item.align?? 'center'"
:reserve-selection="item.type =='selection'"
v-if="item.type =='selection' || item.type == 'index'"
>
</el-table-column>
<!-- expand 类型的列支持 tsx 语法和作用域插槽 -->
<el-table-column
v-bind="item"
:align="item.align?? 'center'"
v-if="item.type == 'expand'"
v-slot="scope"
>
<!-- 如果有自定义渲染函数则使用该函数渲染 -->
<component :is="item.render" :row="scope.row" v-if="item.render">
</component>
<!-- 否则使用插槽渲染 -->
<slot :name="item.type" :row="scope.row" v-else></slot>
</el-table-column>
<!-- 其他类型的列通过 TableColumn 组件递归渲染 -->
<TableColumn
v-if="!item.type && item.prop && item.isShow"
:column="item"
>
<!-- 循环渲染插槽内容 -->
<template v-for="slot in Object.keys($slots)" #[slot]="scope">
<slot :name="slot" :row="scope.row"></slot>
</template>
</TableColumn>
</template>
<!-- 插入表格最后一行之后的插槽 -->
<template #append>
<slot name="append"> </slot>
</template>
<!-- 表格无数据情况 -->
<template #empty>
<div class="table-empty">
<slot name="empty">
<div>暂无数据</div>
</slot>
</div>
</template>
</el-table>
<!-- 分页组件插槽 -->
<slot name="pagination">
<div class="mt-2 flex flex-row-reverse">
<!-- Pagination 是自定义的分页组件 pagination true 时显示 -->
<Pagination
v-if="pagination"
:pageable="pageable"
:handleSizeChange="handleSizeChange"
:handleCurrentChange="handleCurrentChange"
/>
</div>
</slot>
</div>
</MyCard>
<!-- 列设置 -->
<!-- ColSetting 是自定义的列设置组件 toolButton true 时显示 -->
<!-- v-model:colSetting="colSetting" 双向绑定列设置数据 -->
<ColSetting v-if="toolButton" ref="colRef" v-model:colSetting="colSetting" />
</template>
<script setup lang="ts" name="ProTable">
// Vue
import { ref, watch, computed, provide, onMounted } from 'vue'
// Hooks
import { useTable } from '@/hooks/useTable'
//
import { BreakPoint } from '@/components/Grid/interface'
//
import { ColumnProps } from '@/components/ProTable/interface'
// Element Plus
import { ElTable, TableProps } from 'element-plus'
// Element Plus
import { Refresh, Printer, Operation, Search } from '@element-plus/icons-vue'
//
import {
filterEnum,
formatValue,
handleProp,
handleRowAccordingToProp
} from '@/utils/util'
//
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'
// Hooks
import { useSelection } from '@/hooks/useSelection'
// import printJS from "print-js";
//
const searchForm = ref()
// ProTable TableProps data
interface ProTableProps extends Partial<Omit<TableProps<any>, 'data'>> {
//
columns: ColumnProps[]
// API
requestApi: (params: any) => Promise<any>
//
requestAuto?: boolean
//
dataCallback?: (data: any) => any
//
title?: string
// true
pagination?: boolean
// {}
initParam?: any
// true
border?: boolean
// true
toolButton?: boolean
// Key Table id id
rowKey?: string
// { xs: 1, sm: 2, md: 2, lg: 3, xl: 4 }
searchCol?: number | Record<BreakPoint, number>
}
//
const props = withDefaults(defineProps<ProTableProps>(), {
requestAuto: true,
columns: () => [],
pagination: true,
initParam: {},
border: true,
toolButton: true,
rowKey: 'id',
searchCol: () => ({ xs: 1, sm: 2, md: 2, lg: 3, xl: 4 })
})
//
const isShowSearch = ref(true)
// DOM
const tableRef = ref<InstanceType<typeof ElTable>>()
// 使 Hooks
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
)
//
const clearSelection = () => tableRef.value!.clearSelection()
// requestAuto true getTableList
onMounted(() => props.requestAuto && getTableList())
// initParam
watch(() => props.initParam, getTableList, { deep: true })
//
const tableColumns = ref<ColumnProps[]>(props.columns)
// enumMap provide
const enumMap = ref(new Map<string, { [key: string]: any }[]>())
provide('enumMap', enumMap)
// enumMap
const setEnumMap = async (col: ColumnProps) => {
if (!col.enum) return
// enum enumMap
if (typeof col.enum!== 'function')
return enumMap.value.set(col.prop!, col.enum!)
// enum enumMap
const { data } = await col.enum()
enumMap.value.set(col.prop!, data)
}
// columns
const flatColumnsFunc = (
columns: ColumnProps[],
flatArr: ColumnProps[] = []
) => {
columns.forEach(async col => {
//
if (col._children?.length) flatArr.push(...flatColumnsFunc(col._children))
flatArr.push(col)
// column isShow isFilterEnum
col.isShow = col.isShow?? true
col.isFilterEnum = col.isFilterEnum?? true
// enumMap
setEnumMap(col)
})
//
return flatArr.filter(item =>!item._children?.length)
}
// columns
const flatColumns = ref<ColumnProps[]>()
flatColumns.value = flatColumnsFunc(tableColumns.value)
//
const searchColumns = flatColumns.value.filter(item => item.search?.el)
//
searchColumns.forEach((column, index) => {
column.search!.order = column.search!.order?? index + 2
if (
column.search?.defaultValue!== undefined &&
column.search?.defaultValue!== null
) {
searchInitParam.value[column.search.key?? handleProp(column.prop!)] =
column.search?.defaultValue
searchParam.value[column.search.key?? handleProp(column.prop!)] =
column.search?.defaultValue
}
})
//
searchColumns.sort((a, b) => a.search!.order! - b.search!.order!)
//
const colRef = ref()
//
const colSetting = tableColumns.value!.filter(
item =>
![
'selection',
'index',
'expand'
].includes(item.type!) && item.prop!== 'operation'
)
//
const openColSetting = () => colRef.value.openColSetting()
// 201-238
// enum
const printData = computed(() => {
const printDataList = JSON.parse(
JSON.stringify(
selectedList.value.length? selectedList.value : tableData.value
)
)
//
const needTransformCol = flatColumns.value!.filter(
item =>
(item.enum || (item.prop && item.prop.split('.').length > 1)) &&
item.isFilterEnum
)
needTransformCol.forEach(colItem => {
printDataList.forEach((tableItem: { [key: string]: any }) => {
tableItem[handleProp(colItem.prop!)] =
colItem.prop!.split('.').length > 1 &&!colItem.enum
? formatValue(handleRowAccordingToProp(tableItem, colItem.prop!))
: filterEnum(
handleRowAccordingToProp(tableItem, colItem.prop!),
enumMap.value.get(colItem.prop!),
colItem.fieldNames
)
for (const key in tableItem) {
if (tableItem[key] === null)
tableItem[key] = formatValue(tableItem[key])
}
})
})
return printDataList
})
// 💥 printJs
// const handlePrint = () => {
// const header = `<div style="text-align: center"><h2>${props.title}</h2></div>`;
// const gridHeaderStyle = "border: 1px solid #ebeef5;height: 45px;color: #232425;text-align: center;background-color: #fafafa;";
// const gridStyle = "border: 1px solid #ebeef5;height: 40px;color: #494b4e;text-align: center";
// printJS({
// printable: printData.value,
// header: props.title && header,
// properties: flatColumns
// .value!.filter(item => !["selection", "index", "expand"].includes(item.type!) && item.isShow && item.prop !== "operation")
// .map((item: ColumnProps) => ({ field: handleProp(item.prop!), displayName: item.label })),
// type: "json",
// gridHeaderStyle,
// gridStyle
// });
// };
//
defineExpose({
element: tableRef,
tableData,
searchParam,
pageable,
getTableList,
reset,
clearSelection,
enumMap,
isSelected,
selectedList,
selectedListIds
})
</script>

@ -0,0 +1,84 @@
// 从 "element-plus/es/components/table/src/table-column/defaults" 导入 TableColumnCtx 类型
// TableColumnCtx 可能是 Element Plus 中表格列的上下文类型,包含了表格列的一些默认属性和方法
import { TableColumnCtx } from "element-plus/es/components/table/src/table-column/defaults";
// 从 "@/components/Grid/interface" 导入 BreakPoint 和 Responsive 类型
// BreakPoint 可能表示响应式布局中的不同断点(如 xs, sm, md, lg, xl 等)
// Responsive 可能用于描述在不同断点下的响应式配置
import { BreakPoint, Responsive } from "@/components/Grid/interface";
// 定义一个接口 EnumProps用于描述枚举属性
export interface EnumProps {
// 选项框显示的文字,类型为字符串
label: string;
// 选项框值,可以是任意类型
value: any;
// 是否禁用此选项,可选,默认为 undefined类型为布尔值
disabled?: boolean;
// 当 tag 为 true 时,此选择会指定 tag 显示类型,可选,默认为 undefined类型为字符串
tagType?: string;
// 为树形选择时,可以通过 children 属性指定子选项,可选,默认为 undefined类型为 EnumProps 数组
children?: EnumProps[];
// 允许添加任意其他属性,属性名和值的类型都为任意类型
[key: string]: any;
}
// 定义一个类型别名 TypeProp其值只能是 "index"、"selection" 或 "expand" 中的一个
export type TypeProp = "index" | "selection" | "expand";
// 定义一个类型别名 SearchType用于表示搜索框的类型
// 可以是 "input"、"input-number" 等多种 Element Plus 组件类型
export type SearchType =
| "input"
| "input-number"
| "select"
| "select-v2"
| "tree-select"
| "cascader"
| "date-picker"
| "time-picker"
| "time-select"
| "switch"
| "slider";
// 定义一个接口 SearchProps用于描述搜索项的属性
export type SearchProps = {
// 当前项搜索框的类型,类型为 SearchType
el: SearchType;
// 搜索项参数,根据 element plus 官方文档来传递,该属性所有值会透传到组件,可选,默认为 undefined
props?: any;
// 当搜索项 key 不为 prop 属性时,可通过 key 指定,可选,默认为 undefined类型为字符串
key?: string;
// 搜索项排序(从大到小),可选,默认为 undefined类型为数字
order?: number;
// 搜索项所占用的列数,默认为 1 列,可选,默认为 undefined类型为数字
span?: number;
// 搜索字段左侧偏移列数,可选,默认为 undefined类型为数字
offset?: number;
// 搜索项默认值,可以是字符串、数字、布尔值或任意类型的数组,可选,默认为 undefined
defaultValue?: string | number | boolean | any[];
// 扩展为包含 BreakPoint 类型的键和 Responsive 类型的值的部分记录,用于实现响应式配置
} & Partial<Record<BreakPoint, Responsive>>;
// 定义一个接口 ColumnProps用于描述表格列的属性
// 扩展自 Partial<Omit<TableColumnCtx<T>, "children" | "renderHeader" | "renderCell">>,表示部分继承 TableColumnCtx 类型并去除 "children"、"renderHeader" 和 "renderCell" 属性
export interface ColumnProps<T = any>
extends Partial<Omit<TableColumnCtx<T>, "children" | "renderHeader" | "renderCell">> {
// 是否是标签展示,可选,默认为 undefined类型为布尔值
tag?: boolean;
// 是否显示在表格当中,可选,默认为 undefined类型为布尔值
isShow?: boolean;
// 搜索项配置,可选,默认为 undefined类型为 SearchProps 或 undefined
search?: SearchProps | undefined;
// 枚举类型(渲染值的字典),可以是 EnumProps 数组或一个返回 Promise<any> 的函数,可选,默认为 undefined
enum?: EnumProps[] | ((params?: any) => Promise<any>);
// 当前单元格值是否根据 enum 格式化示例enum 只作为搜索项数据),可选,默认为 undefined类型为布尔值
isFilterEnum?: boolean;
// 指定 label && value 的 key 值,可选,默认为 undefined类型为包含 label 和 value 键的对象
fieldNames?: { label: string; value: string };
// 自定义表头内容渲染tsx语法可选默认为 undefined类型为一个接受 ColumnProps 类型参数并返回任意类型的函数
headerRender?: (row: ColumnProps) => any;
// 自定义单元格内容渲染tsx语法可选默认为 undefined类型为一个接受包含 row 属性且类型为 T 的对象并返回任意类型的函数
render?: (scope: { row: T }) => any;
// 多级表头,可选,默认为 undefined类型为 ColumnProps<T> 数组
_children?: ColumnProps<T>[];
}

@ -0,0 +1,12 @@
// 从当前目录下的 src 文件夹中的 index.vue 文件导入组件
// 这里将导入的组件命名为 reImageVerify它应该是一个 Vue 组件
import reImageVerify from './src/index.vue'
/** 图形验证码组件 */
// 使用具名导出的方式,将导入的组件以 ReImageVerify 这个名称导出
// 具名导出允许在导入时指定具体要导入的内容,方便在其他文件中使用
export const ReImageVerify = reImageVerify
// 使用默认导出的方式,将导入的组件作为默认导出
// 默认导出允许在导入时可以使用任意名称来接收该组件,通常一个文件只能有一个默认导出
export default ReImageVerify

@ -0,0 +1,133 @@
import { ref, onMounted } from 'vue'
/**
*
* @param width -
* @param height -
*/
// 定义一个名为 useImageVerify 的函数,用于生成和管理图形验证码,接受图形宽度和高度作为参数,有默认值
export const useImageVerify = (width = 120, height = 40) => {
// 创建一个响应式引用 domRef用于存储 <canvas> 元素的引用
const domRef = ref<HTMLCanvasElement>()
// 创建一个响应式变量 imgCode用于存储生成的图形验证码字符串
const imgCode = ref('')
// 定义一个函数 setImgCode用于设置 imgCode 的值
function setImgCode(code: string) {
imgCode.value = code
}
// 定义一个函数 getImgCode用于在 <canvas> 上绘制验证码并获取生成的验证码字符串
function getImgCode() {
// 如果 domRef 没有值(即 <canvas> 元素未挂载),则直接返回
if (!domRef.value) return
// 调用 draw 函数绘制验证码,并将返回的验证码字符串赋值给 imgCode
imgCode.value = draw(domRef.value, width, height)
}
// 在组件挂载后执行的钩子函数,调用 getImgCode 函数生成验证码
onMounted(() => {
getImgCode()
})
// 返回一个包含 domRef、imgCode、setImgCode 和 getImgCode 的对象,以便在其他地方使用这些变量和函数
return {
domRef,
imgCode,
setImgCode,
getImgCode
}
}
// 定义一个函数 randomNum用于生成指定范围内的随机整数
function randomNum(min: number, max: number) {
// 使用 Math.random() 生成一个 0 到 1 之间的随机小数,然后通过计算得到指定范围内的整数
const num = Math.floor(Math.random() * (max - min) + min)
return num
}
// 定义一个函数 randomColor用于生成指定范围内的随机颜色RGB 格式)
function randomColor(min: number, max: number) {
// 分别生成随机的红r、绿g、蓝b分量值
const r = randomNum(min, max)
const g = randomNum(min, max)
const b = randomNum(min, max)
// 返回 RGB 格式的颜色字符串
return `rgb(${r},${g},${b})`
}
// 定义一个函数 draw用于在 <canvas> 元素上绘制图形验证码
function draw(dom: HTMLCanvasElement, width: number, height: number) {
// 初始化一个空字符串 imgCode用于存储生成的验证码
let imgCode = ''
// 定义一个字符串 NUMBER_STRING包含数字 0 到 9用于生成验证码字符
const NUMBER_STRING = '0123456789'
// 获取 <canvas> 元素的 2D 绘图上下文 ctx
const ctx = dom.getContext('2d')
// 如果获取上下文失败ctx 为 null则直接返回空的验证码字符串
if (!ctx) return imgCode
// 设置绘图上下文的填充样式为随机颜色(背景色)
ctx.fillStyle = randomColor(180, 230)
// 使用填充样式绘制一个矩形,覆盖整个 <canvas> 区域,作为验证码的背景
ctx.fillRect(0, 0, width, height)
// 循环 4 次,生成 4 个验证码字符
for (let i = 0; i < 4; i += 1) {
// 从 NUMBER_STRING 中随机选取一个字符作为验证码字符
const text = NUMBER_STRING[randomNum(0, NUMBER_STRING.length)]
// 将选取的字符添加到 imgCode 字符串中
imgCode += text
// 随机生成字体大小
const fontSize = randomNum(18, 41)
// 随机生成字符的旋转角度
const deg = randomNum(-30, 30)
// 设置绘图上下文的字体样式
ctx.font = `${fontSize}px Simhei`
// 设置文本基线为顶部
ctx.textBaseline = 'top'
// 设置绘图上下文的填充样式为随机颜色(字符颜色)
ctx.fillStyle = randomColor(80, 150)
// 保存当前绘图上下文的状态
ctx.save()
// 平移绘图上下文的原点到指定位置(每个字符的起始位置)
ctx.translate(30 * i + 15, 15)
// 旋转绘图上下文指定的角度
ctx.rotate((deg * Math.PI) / 180)
// 在指定位置绘制字符
ctx.fillText(text, -15 + 5, -15)
// 恢复之前保存的绘图上下文状态
ctx.restore()
}
// 循环 5 次,绘制 5 条随机直线
for (let i = 0; i < 5; i += 1) {
// 开始绘制路径
ctx.beginPath()
// 设置路径的起始点为随机位置
ctx.moveTo(randomNum(0, width), randomNum(0, height))
// 设置路径的终点为随机位置
ctx.lineTo(randomNum(0, width), randomNum(0, height))
// 设置绘图上下文的描边样式为随机颜色
ctx.strokeStyle = randomColor(180, 230)
// 关闭路径
ctx.closePath()
// 绘制路径(直线)
ctx.stroke()
}
// 循环 41 次,绘制 41 个随机圆形
for (let i = 0; i < 41; i += 1) {
// 开始绘制路径
ctx.beginPath()
// 绘制一个圆形,圆心为随机位置,半径为 1
ctx.arc(randomNum(0, width), randomNum(0, height), 1, 0, 2 * Math.PI)
// 关闭路径
ctx.closePath()
// 设置绘图上下文的填充样式为随机颜色
ctx.fillStyle = randomColor(150, 200)
// 填充圆形
ctx.fill()
}
// 返回生成的验证码字符串
return imgCode
}

@ -0,0 +1,55 @@
<script setup lang="ts">
// Vue watch
import { watch } from 'vue'
// useImageVerify
import { useImageVerify } from './hooks'
// Props
interface Props {
code?: string // code
}
// Emits
interface Emits {
(e: 'update:code', code: string): void // 'update:code' code
}
// 使 withDefaults props
const props = withDefaults(defineProps<Props>(), {
code: '' // code
})
// emit
const emit = defineEmits<Emits>()
// useImageVerify domRefimgCodesetImgCode getImgCode
const { domRef, imgCode, setImgCode, getImgCode } = useImageVerify()
// props.code props.code setImgCode
watch(
() => props.code,
newValue => {
setImgCode(newValue)
}
)
// imgCode imgCode 'update:code'
watch(imgCode, newValue => {
emit('update:code', newValue)
})
// getImgCode 便
defineExpose({ getImgCode })
</script>
<template>
<!-- 定义一个 canvas 元素设置其宽度为 120高度为 40添加类名 cursor-pointer 使其具有指针样式
绑定 ref domRef以便在脚本中访问该元素绑定 click 事件为 getImgCode 函数 -->
<canvas
ref="domRef"
width="120"
height="40"
class="cursor-pointer"
@click="getImgCode"
/>
</template>

@ -0,0 +1,117 @@
<template>
<!-- 根据条件渲染不同的组件 -->
<component
v-if="column.search?.el" <!-- 如果 column.search.el 存在则渲染该组件 -->
:is="`el-${column.search.el}`" <!-- 根据 column.search.el 的值动态渲染对应的 Element Plus 组件例如 'el-input''el-select' -->
v-bind="handleSearchProps" <!-- 绑定处理后的搜索属性 -->
v-model.trim="searchParam[column.search.key?? handleProp(column.prop!)]" <!-- 双向绑定搜索参数使用 trim 修饰符去除首尾空格 -->
:data="column.search?.el === 'tree-select'? columnEnum : []" <!-- 如果是 tree-select 组件绑定 columnEnum 数据否则绑定空数组 -->
:options="
['cascader','select-v2'].includes(column.search?.el)? columnEnum : []
" <!-- 如果是 cascader select-v2 组件绑定 columnEnum 数据作为选项否则绑定空数组 -->
:placeholder="placeholder" <!-- 绑定占位符文本 -->
:clearable="clearable" <!-- 绑定是否可清除的属性 -->
range-separator="至" <!-- 日期范围选择器的分隔符 -->
start-placeholder="开始时间" <!-- 日期范围选择器开始时间的占位符 -->
end-placeholder="结束时间" <!-- 日期范围选择器结束时间的占位符 -->
>
<!-- column.search.el 'cascader' 渲染默认插槽内容 -->
<template #default="{ data }" v-if="column.search.el === 'cascader'">
<span>{{ data[fieldNames.label] }}</span> <!-- 显示数据的 label 字段 -->
</template>
<!-- column.search.el 'select' 渲染子选项 -->
<template v-if="column.search.el ==='select'">
<component
:is="`el-option`" <!-- 渲染 Element Plus el-option 组件 -->
v-for="(col, index) in columnEnum" <!-- 遍历 columnEnum 数据 -->
:key="index" <!-- 设置唯一的 key -->
:label="col[fieldNames.label]" <!-- 绑定选项的 label -->
:value="col[fieldNames.value]" <!-- 绑定选项的值 -->
></component>
</template>
<!-- 如果以上条件都不满足渲染插槽内容 -->
<slot v-else></slot>
</component>
</template>
<script setup lang="ts" name="SearchFormItem">
// Vue
import { computed, inject, onMounted, ref } from 'vue'
// handleProp
import { handleProp } from '@/utils/util'
//
import { ColumnProps } from '@/components/ProTable/interface'
// SearchFormItem
interface SearchFormItem {
column: ColumnProps //
searchParam: { [key: string]: any } //
}
// props
const props = defineProps<SearchFormItem>()
// fieldNames label && value key
const fieldNames = computed(() => {
return {
label: props.column.fieldNames?.label?? 'label', // column.fieldNames.label 使使 'label'
value: props.column.fieldNames?.value?? 'value' // column.fieldNames.value 使使 'value'
}
})
// enumMap
const enumMap = inject('enumMap', ref(new Map()))
// columnEnum props.column.prop enumMap
const columnEnum = computed(() => {
let enumData = enumMap.value.get(props.column.prop)
if (!enumData) return [] //
if (props.column.search?.el ==='select-v2' && props.column.fieldNames) {
enumData = enumData.map((item: { [key: string]: any }) => {
return {
...item,
label: item[fieldNames.value.label], // label
value: item[fieldNames.value.value] // value
}
})
}
return enumData
})
// searchProps(el tree-selectcascader label value)
const handleSearchProps = computed(() => {
const label = fieldNames.value.label
const value = fieldNames.value.value
const searchEl = props.column.search?.el
const searchProps = props.column.search?.props?? {}
let handleProps = searchProps
if (searchEl === 'tree-select')
handleProps = {
...searchProps,
props: { label,...searchProps.props }, // tree-select props
nodeKey: value
}
if (searchEl === 'cascader')
handleProps = {
...searchProps,
props: { label, value,...searchProps.props } // cascader props
}
return handleProps
})
// placeholder
const placeholder = computed(() => {
const search = props.column.search
return (
search?.props?.placeholder?? (search?.el === 'input'? '请输入' : '请选择') //
)
})
// ()
const clearable = computed(() => {
const search = props.column.search
return (
search?.props?.clearable??
(search?.defaultValue == null || search?.defaultValue == undefined) // props
)
})
</script>

@ -0,0 +1,142 @@
<template>
<!-- MyCard 组件可能是一个自定义的卡片组件 -->
<MyCard>
<!-- columns 数组长度大于 0 时显示搜索表单 -->
<div class="card table-search" v-if="columns.length">
<!-- el-form 组件用于创建表单 -->
<el-form ref="formRef" :model="searchParam">
<!-- Grid 组件可能是一个用于布局的网格组件 -->
<Grid
ref="gridRef"
:collapsed="collapsed"
:gap="[20, 0]"
:cols="searchCol"
>
<!-- 循环渲染 GridItem 组件每个 GridItem 对应一个搜索表单字段 -->
<GridItem
v-for="(item, index) in columns"
:key="item.prop"
v-bind="getResponsive(item)"
:index="index"
>
<!-- el-form-item 组件用于创建表单字段 -->
<el-form-item :label="`${item.label} :`">
<!-- SearchFormItem 组件用于渲染具体的搜索表单字段 -->
<SearchFormItem :column="item" :searchParam="searchParam" />
</el-form-item>
</GridItem>
<!-- 带有 suffix 标志的 GridItem 组件用于放置操作按钮 -->
<GridItem suffix>
<div class="operation">
<!-- 搜索按钮点击时调用 search 方法 -->
<el-button
class="bg-blue clickSearchBtn"
type="primary"
:icon="Search"
@click="search"
>
搜索
</el-button>
<!-- 重置按钮点击时调用 reset 方法 -->
<el-button :icon="Delete" @click="reset"></el-button>
<!-- 展开/合并按钮根据 collapsed 的值显示不同的文本和图标 -->
<el-button
v-if="showCollapse"
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>
</GridItem>
</Grid>
</el-form>
</div>
</MyCard>
</template>
<script setup lang="ts" name="SearchForm">
// Vue
import { computed, onMounted, ref } from 'vue'
// ProTable
import { ColumnProps } from '@/components/ProTable/interface'
// Grid
import { BreakPoint } from '@/components/Grid/interface'
// Element Plus
import { Delete, Search, ArrowDown, ArrowUp } from '@element-plus/icons-vue'
// SearchFormItem
import SearchFormItem from './components/SearchFormItem.vue'
// Grid
import Grid from '@/components/Grid/index.vue'
// GridItem
import GridItem from '@/components/Grid/components/GridItem.vue'
// MyCard
import MyCard from '../my-card/my-card.vue'
// ProTableProps
interface ProTableProps {
//
columns?: ColumnProps[]
//
searchParam?: { [key: string]: any }
//
searchCol: number | Record<BreakPoint, number>
//
search: (params: any) => void
//
reset: (params: any) => void
}
// 使 withDefaults
const props = withDefaults(defineProps<ProTableProps>(), {
columns: () => [],
searchParam: () => ({})
})
//
const getResponsive = (item: ColumnProps) => {
return {
// item.search.span
span: item.search?.span,
// item.search.offset 0
offset: item.search?.offset?? 0,
// item.search
xs: item.search?.xs,
sm: item.search?.sm,
md: item.search?.md,
lg: item.search?.lg,
xl: item.search?.xl
}
}
//
const collapsed = ref(true)
// ref Grid
const gridRef = ref()
//
const breakPoint = computed<BreakPoint>(() => gridRef.value?.breakPoint)
// /
const showCollapse = computed(() => {
let show = false
// columns
props.columns.reduce((prev, current) => {
prev +=
(current.search![breakPoint.value]?.span?? current.search?.span?? 1) +
(current.search![breakPoint.value]?.offset?? current.search?.offset?? 0)
// searchCol /
if (typeof props.searchCol!== 'number') {
if (prev >= props.searchCol[breakPoint.value]) show = true
} else {
if (prev > props.searchCol) show = true
}
return prev
}, 0)
return show
})
</script>

@ -0,0 +1,49 @@
<template>
<!-- SVG 图标容器添加了 svg-icon 类用于样式设置area-hidden="true" 用于隐藏无障碍区域 -->
<svg class="svg-icon" area-hidden="true">
<!-- use 元素用于引用外部 SVG 符号:xlink:href 绑定了动态的图标名称 -->
<use :xlink:href="iconName"></use>
</svg>
</template>
<script setup lang="ts">
// Vue defineProps computed
import { defineProps, computed } from 'vue'
//
const props = defineProps({
// icon
icon: {
type: String,
required: true
},
// size 16
size: {
type: [Number, String],
default: 16
}
})
// icon #icon-
const iconName = computed(() => {
return `#icon-${props.icon}`
})
// size
const iconSize = computed(() => {
return props.size + 'px'
})
</script>
<style lang="scss" scoped>
// .svg-icon SVG
.svg-icon {
// 1em
width: 1em;
// 1em
height: 1em;
//
fill: currentColor;
// iconSize
font-size: v-bind(iconSize);
}
</style>

@ -1,7 +1,18 @@
<template>
<!-- 列设置 -->
<!-- 注释说明该部分是列设置 -->
<!-- el-drawer Element Plus 中的抽屉组件用于展示列设置的内容 -->
<!-- title="列设置" 设置抽屉的标题 -->
<!-- v-model="drawerVisible" 双向绑定抽屉的显示隐藏状态drawerVisible 是一个响应式变量 -->
<!-- size="450px" 设置抽屉的宽度为 450px -->
<el-drawer title="列设置" v-model="drawerVisible" size="450px">
<!-- 定义一个包含表格的 div 容器 -->
<div class="table-main">
<!-- el-table Element Plus 中的表格组件用于展示列设置的数据 -->
<!-- :data="colSetting" 绑定表格的数据colSetting 是一个数组包含列的设置信息 -->
<!-- :border="true" 设置表格有边框 -->
<!-- row-key="prop" 为表格的每一行设置唯一的标识这里使用 prop 作为标识 -->
<!-- default-expand-all 表示默认展开所有行对于树形表格 -->
<!-- :tree-props="{ children: '_children' }" 配置表格为树形表格指定子节点的字段为 _children -->
<el-table
:data="colSetting"
:border="true"
@ -9,23 +20,34 @@
default-expand-all
:tree-props="{ children: '_children' }"
>
<!-- el-table-column 是表格列组件用于定义表格的列 -->
<!-- prop="label" 绑定列的数据字段为 label -->
<!-- align="center" 设置列内容居中对齐 -->
<!-- label="列名" 设置列的标题为列名 -->
<el-table-column prop="label" align="center" label="列名" />
<!-- 定义显示列prop="isShow" 绑定数据字段为 isShow -->
<!-- v-slot="scope" 定义作用域插槽scope 包含当前行的数据 -->
<el-table-column
prop="isShow"
align="center"
label="显示"
v-slot="scope"
>
<!-- el-switch Element Plus 中的开关组件v-model="scope.row.isShow" 双向绑定当前行的 isShow 字段 -->
<el-switch v-model="scope.row.isShow"></el-switch>
</el-table-column>
<!-- 定义排序列prop="sortable" 绑定数据字段为 sortable -->
<!-- v-slot="scope" 定义作用域插槽scope 包含当前行的数据 -->
<el-table-column
prop="sortable"
align="center"
label="排序"
v-slot="scope"
>
<!-- el-switch Element Plus 中的开关组件v-model="scope.row.sortable" 双向绑定当前行的 sortable 字段 -->
<el-switch v-model="scope.row.sortable"></el-switch>
</el-table-column>
<!-- 定义表格为空时显示的内容#empty 是表格的 empty 插槽 -->
<template #empty>
<div class="table-empty">
<div>暂无可配置列</div>
@ -37,23 +59,29 @@
</template>
<script setup lang="ts" name="ColSetting">
// Vue ref
import { ref } from 'vue'
// ProTable ColumnProps
import { ColumnProps } from '@/components/ProTable/interface'
// 使 defineProps colSetting ColumnProps
defineProps<{ colSetting: ColumnProps[] }>()
// drawerVisible false
const drawerVisible = ref<boolean>(false)
//
// openColSetting drawerVisible true
const openColSetting = () => {
drawerVisible.value = true
}
// openColSetting 便
defineExpose({
openColSetting
})
</script>
<style scoped lang="scss">
// cursor-move move
.cursor-move {
cursor: move;
}

@ -1,5 +1,14 @@
<template>
<!-- 分页组件 -->
<!-- 注释说明该部分是分页组件 -->
<!-- el-pagination Element Plus 中的分页组件 -->
<!-- :current-page="pageable.pageNum" 双向绑定当前页码pageable.pageNum 是一个响应式变量表示当前页码 -->
<!-- :page-size="pageable.pageSize" 双向绑定每页显示的数量pageable.pageSize 是一个响应式变量表示每页显示的数量 -->
<!-- :page-sizes="[10, 25, 50, 100]" 设置每页显示数量的可选值 -->
<!-- :background="true" 设置分页组件的背景颜色 -->
<!-- layout="total, sizes, prev, pager, next, jumper" 设置分页组件的布局包括总数量每页显示数量选择器上一页页码下一页跳转到指定页 -->
<!-- :total="pageable.total" 绑定总数量pageable.total 是一个响应式变量表示数据的总数量 -->
<!-- @size-change="handleSizeChange" 监听每页显示数量改变的事件当用户选择不同的每页显示数量时会调用 handleSizeChange 函数 -->
<!-- @current-change="handleCurrentChange" 监听当前页码改变的事件当用户点击上一页下一页或跳转到指定页时会调用 handleCurrentChange 函数 -->
<el-pagination
:current-page="pageable.pageNum"
:page-size="pageable.pageSize"
@ -13,17 +22,26 @@
</template>
<script setup lang="ts" name="Pagination">
// Pageable
// pageNum number
// pageSize number
// total number
interface Pageable {
pageNum: number
pageSize: number
total: number
}
// PaginationProps
// pageable Pageable
// handleSizeChange number size
// handleCurrentChange number currentPage
interface PaginationProps {
pageable: Pageable
handleSizeChange: (size: number) => void
handleCurrentChange: (currentPage: number) => void
}
// 使 defineProps PaginationProps
defineProps<PaginationProps>()
</script>

@ -1,31 +1,42 @@
<template>
<!-- 使用动态组件根据 renderLoop 函数的返回值来渲染不同的表格列 -->
<component :is="renderLoop(column)"></component>
</template>
<script lang="tsx" setup name="TableColumn">
// vue
import { inject, ref, useSlots } from 'vue'
// ProTable ColumnProps
import { ColumnProps } from '@/components/ProTable/interface'
//
import { filterEnum, formatValue, handleRowAccordingToProp } from '@/utils/util'
// 使 defineProps column ColumnProps
defineProps<{ column: ColumnProps }>()
//
const slots = useSlots()
// enumMap Ref<Map<any, any>> Map
const enumMap = inject('enumMap', ref(new Map()))
//
// renderCellData
const renderCellData = (item: ColumnProps, scope: { [key: string]: any }) => {
// prop enumMap
return enumMap.value.get(item.prop) && item.isFilterEnum
// filterEnum
? filterEnum(
handleRowAccordingToProp(scope.row, item.prop!),
enumMap.value.get(item.prop)!,
item.fieldNames
)
handleRowAccordingToProp(scope.row, item.prop!),
enumMap.value.get(item.prop)!,
item.fieldNames
)
//
: formatValue(handleRowAccordingToProp(scope.row, item.prop!))
}
// tag
// getTagType
const getTagType = (item: ColumnProps, scope: { [key: string]: any }) => {
// filterEnum
return filterEnum(
handleRowAccordingToProp(scope.row, item.prop!),
enumMap.value.get(item.prop),
@ -34,35 +45,53 @@ const getTagType = (item: ColumnProps, scope: { [key: string]: any }) => {
) as any
}
// renderLoop
const renderLoop = (item: ColumnProps) => {
return (
<>
//
{item.isShow && (
// el-table-column
<el-table-column
//
{...item}
align={item.align ?? 'center'}
//
align={item.align?? 'center'}
// prop 'operation'
showOverflowTooltip={
item.showOverflowTooltip ?? item.prop !== 'operation'
item.showOverflowTooltip?? item.prop!== 'operation'
}
>
{{
//
default: (scope: any) => {
//
if (item._children)
//
return item._children.map(child => renderLoop(child))
//
if (item.render) return item.render(scope)
// prop
if (slots[item.prop!]) return slots[item.prop!]!(scope)
//
if (item.tag)
return (
// el-tag
<el-tag type={getTagType(item, scope)}>
{renderCellData(item, scope)}
</el-tag>
)
//
return renderCellData(item, scope)
},
//
header: () => {
//
if (item.headerRender) return item.headerRender(item)
// propHeader
if (slots[`${item.prop}Header`])
return slots[`${item.prop}Header`]!({ row: item })
// label
return item.label
}
}}

@ -0,0 +1,118 @@
<template>
<!-- el-dialog Element Plus 中的对话框组件用于展示选择老人的界面 -->
<!-- style="width: 70%" 设置对话框的宽度为父容器的 70% -->
<!-- v-model="dialogVisible" 双向绑定对话框的显示隐藏状态dialogVisible 是一个响应式变量 -->
<!-- title="选择老人" 设置对话框的标题 -->
<!-- destroy-on-close 表示在对话框关闭时销毁其内容 -->
<el-dialog
style="width: 70%"
v-model="dialogVisible"
title="选择老人"
destroy-on-close
>
<!-- 定义一个包含表格的 div 容器 -->
<div class="table-box">
<!-- ProTable 是自定义的表格组件用于展示用户列表 -->
<!-- ref="proTable" 为表格组件添加引用方便在脚本中访问 -->
<!-- title="用户列表" 设置表格的标题 -->
<!-- :columns="columns" 绑定表格的列配置项columns 是一个数组包含每列的信息 -->
<!-- :requestApi="getTableList" 绑定数据请求方法getTableList 用于获取表格数据 -->
<ProTable
ref="proTable"
title="用户列表"
:columns="columns"
:requestApi="getTableList"
>
<!-- 定义表格操作列的插槽内容 -->
<template #operation="scope">
<!-- el-popconfirm Element Plus 中的弹出确认框组件 -->
<!-- title="Are you sure to choose this?" 设置确认框的提示信息 -->
<!-- @confirm="checkElder(scope.row)" 当用户确认时调用 checkElder 方法并传递当前行的数据 -->
<!-- confirm-button-type="warning" 设置确认按钮的类型为警告样式 -->
<el-popconfirm
title="Are you sure to choose this?"
@confirm="checkElder(scope.row)"
confirm-button-type="warning"
>
<!-- 定义确认框的触发元素 -->
<template #reference>
<!-- el-button Element Plus 中的按钮组件 -->
<!-- size="small" 设置按钮大小为小 -->
<!-- link 设置按钮为链接样式 -->
<!-- :icon="View" 绑定图标View 是从 @element-plus/icons-vue 导入的图标组件 -->
<!-- 选择 是按钮的文本内容 -->
<el-button size="small" link :icon="View">选择</el-button>
</template>
</el-popconfirm>
</template>
</ProTable>
</div>
</el-dialog>
</template>
<script setup lang="ts" name="useProTable">
// Vue ref
import { ref } from "vue";
// ProTable ColumnProps
import { ColumnProps } from "@/components/ProTable/interface";
// ProTable
import ProTable from "@/components/ProTable/index.vue";
// Element Plus View
import { View } from "@element-plus/icons-vue";
// dialogVisible
const dialogVisible = ref(false);
// proTable ProTable
const proTable = ref();
// drawerProps DialogProps
const drawerProps = ref<DialogProps>();
// DialogProps elderApi
interface DialogProps {
elderApi: (params: any) => Promise<any>;
}
// getTableList params
//
// elderApi
let getTableList = async (params: any) => {
let newParams = JSON.parse(JSON.stringify(params));
return drawerProps.value?.elderApi(newParams);
};
// columns ColumnProps
const columns: ColumnProps<any>[] = [
{ prop: "rank", label: "序号", width: 55 },
{ prop: "name", label: "姓名", search: { el: "input" } },
{ prop: "idNum", label: "身份证号" },
{ prop: "sex", label: "性别" },
{ prop: "age", label: "年龄" },
{ prop: "phone", label: "电话", search: { el: "input" } },
{ prop: "address", label: "地址" },
{ prop: "operation", label: "操作", width: 70 }
];
// elderAcceptParams DialogProps params
// drawerProps
const elderAcceptParams = (params: DialogProps) => {
drawerProps.value = params;
dialogVisible.value = true;
};
// elderAcceptParams 便
defineExpose({
elderAcceptParams
});
// emit
const emit = defineEmits<{
(event: "getCheckElderInfo", val: any): void
}>();
// checkElder row
// "getCheckElderInfo"
const checkElder = (row: any) => {
emit("getCheckElderInfo", row);
dialogVisible.value = false;
};
</script>
<style lang="scss" scoped></style>

@ -2,6 +2,14 @@
<template>
<!-- 查询表单 card -->
<!-- SearchForm 是自定义的搜索表单组件 -->
<!-- :search="search" 绑定搜索方法 -->
<!-- :reset="reset" 绑定重置方法 -->
<!-- :searchParam="searchParam" 绑定搜索参数 -->
<!-- :columns="searchColumns" 绑定搜索列配置 -->
<!-- :searchCol="searchCol" 绑定搜索项每列占比配置 -->
<!-- v-show="isShowSearch" 根据 isShowSearch 的值显示或隐藏搜索表单 -->
<!-- ref="searchForm" 为搜索表单组件添加引用 -->
<SearchForm
:search="search"
:reset="reset"
@ -13,11 +21,12 @@
/>
<!-- 表格内容 card -->
<MyCard class="mt-2"
><div class="card table-main">
<MyCard class="mt-2">
<div class="card table-main">
<div class="table-header">
<div class="flex justify-between header-button-lf mb-2">
<div>
<!-- 具名插槽 tableHeader传递了 selectedListIdsselectedList isSelected 数据 -->
<slot
name="tableHeader"
:selectedListIds="selectedListIds"
@ -27,15 +36,19 @@
</div>
<div class="header-button-ri">
<!-- 具名插槽 toolButton包含了一些功能按钮 -->
<slot name="toolButton">
<!-- 刷新按钮点击时调用 getTableList 方法获取表格数据 -->
<el-button :icon="Refresh" circle @click="getTableList" />
<!-- <el-button :icon="Printer" circle v-if="columns.length" @click="handlePrint" /> -->
<!-- 列设置按钮 columns 存在时显示点击时调用 openColSetting 方法打开列设置 -->
<el-button
:icon="Operation"
circle
v-if="columns.length"
@click="openColSetting"
/>
<!-- 搜索按钮 searchColumns 存在时显示点击时切换 isShowSearch 的值显示或隐藏搜索表单 -->
<el-button
:icon="Search"
circle
@ -47,6 +60,13 @@
</div>
</div>
<!-- 表格主体 -->
<!-- el-table Element Plus 的表格组件 -->
<!-- ref="tableRef" 为表格组件添加引用 -->
<!-- v-bind="$attrs" 绑定父组件传递的所有属性 -->
<!-- :data="tableData" 绑定表格数据 -->
<!-- :border="border" 绑定表格是否有纵向边框 -->
<!-- :row-key="rowKey" 绑定行数据的唯一标识 -->
<!-- @selection-change="selectionChange" 监听表格行选择变化事件 -->
<el-table
ref="tableRef"
v-bind="$attrs"
@ -57,31 +77,35 @@
>
<!-- 默认插槽 -->
<slot></slot>
<!-- 循环渲染表格列 -->
<template v-for="item in tableColumns" :key="item">
<!-- selection || index -->
<!-- selection || index 类型的列 -->
<el-table-column
v-bind="item"
:align="item.align ?? 'center'"
:reserve-selection="item.type == 'selection'"
v-if="item.type == 'selection' || item.type == 'index'"
:align="item.align?? 'center'"
:reserve-selection="item.type =='selection'"
v-if="item.type =='selection' || item.type == 'index'"
>
</el-table-column>
<!-- expand 支持 tsx 语法 && 作用域插槽 (tsx > slot) -->
<!-- expand 类型的列支持 tsx 语法和作用域插槽 -->
<el-table-column
v-bind="item"
:align="item.align ?? 'center'"
:align="item.align?? 'center'"
v-if="item.type == 'expand'"
v-slot="scope"
>
<!-- 如果有自定义渲染函数则使用该函数渲染 -->
<component :is="item.render" :row="scope.row" v-if="item.render">
</component>
<!-- 否则使用插槽渲染 -->
<slot :name="item.type" :row="scope.row" v-else></slot>
</el-table-column>
<!-- other 循环递归 -->
<!-- 其他类型的列通过 TableColumn 组件递归渲染 -->
<TableColumn
v-if="!item.type && item.prop && item.isShow"
:column="item"
>
<!-- 循环渲染插槽内容 -->
<template v-for="slot in Object.keys($slots)" #[slot]="scope">
<slot :name="slot" :row="scope.row"></slot>
</template>
@ -100,9 +124,10 @@
</div>
</template>
</el-table>
<!-- 分页组件 -->
<!-- 分页组件插槽 -->
<slot name="pagination">
<div class="mt-2 flex flex-row-reverse">
<!-- Pagination 是自定义的分页组件 pagination true 时显示 -->
<Pagination
v-if="pagination"
:pageable="pageable"
@ -110,50 +135,78 @@
:handleCurrentChange="handleCurrentChange"
/>
</div>
</slot></div
></MyCard>
</slot>
</div>
</MyCard>
<!-- 列设置 -->
<!-- ColSetting 是自定义的列设置组件 toolButton true 时显示 -->
<!-- v-model:colSetting="colSetting" 双向绑定列设置数据 -->
<ColSetting v-if="toolButton" ref="colRef" v-model:colSetting="colSetting" />
</template>
<script setup lang="ts" name="ProTable">
// Vue
import { ref, watch, computed, provide, onMounted } from 'vue'
// Hooks
import { useTable } from '@/hooks/useTable'
//
import { BreakPoint } from '@/components/Grid/interface'
//
import { ColumnProps } from '@/components/ProTable/interface'
// Element Plus
import { ElTable, TableProps } from 'element-plus'
// Element Plus
import { Refresh, Printer, Operation, Search } from '@element-plus/icons-vue'
//
import {
filterEnum,
formatValue,
handleProp,
handleRowAccordingToProp
} from '@/utils/util'
//
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'
// Hooks
import { useSelection } from '@/hooks/useSelection'
// import printJS from "print-js";
//
const searchForm = ref()
// ProTable TableProps data
interface ProTableProps extends Partial<Omit<TableProps<any>, 'data'>> {
columns: ColumnProps[] //
requestApi: (params: any) => Promise<any> // api ==>
//
columns: ColumnProps[]
// API
requestApi: (params: any) => Promise<any>
//
requestAuto?: boolean
dataCallback?: (data: any) => any // ==>
title?: string // ==>
pagination?: boolean // ==> true
initParam?: any // ==> {}
border?: boolean // ==> true
toolButton?: boolean // ==> true
rowKey?: string // Key Table id ==> id
searchCol?: number | Record<BreakPoint, number> // ==> { xs: 1, sm: 2, md: 2, lg: 3, xl: 4 }
//
dataCallback?: (data: any) => any
//
title?: string
// true
pagination?: boolean
// {}
initParam?: any
// true
border?: boolean
// true
toolButton?: boolean
// Key Table id id
rowKey?: string
// { xs: 1, sm: 2, md: 2, lg: 3, xl: 4 }
searchCol?: number | Record<BreakPoint, number>
}
//
//
const props = withDefaults(defineProps<ProTableProps>(), {
requestAuto: true,
columns: () => [],
@ -165,17 +218,17 @@ const props = withDefaults(defineProps<ProTableProps>(), {
searchCol: () => ({ xs: 1, sm: 2, md: 2, lg: 3, xl: 4 })
})
//
//
const isShowSearch = ref(true)
// DOM
// DOM
const tableRef = ref<InstanceType<typeof ElTable>>()
// Hooks
// 使 Hooks
const { selectionChange, selectedList, selectedListIds, isSelected } =
useSelection(props.rowKey)
// Hooks
// 使 Hooks
const {
tableData,
pageable,
@ -193,90 +246,99 @@ const {
props.dataCallback
)
//
//
const clearSelection = () => tableRef.value!.clearSelection()
//
// requestAuto true getTableList
onMounted(() => props.requestAuto && getTableList())
// initParam
// initParam
watch(() => props.initParam, getTableList, { deep: true })
// columns
//
const tableColumns = ref<ColumnProps[]>(props.columns)
// enumMap enum ||
// enumMap provide
const enumMap = ref(new Map<string, { [key: string]: any }[]>())
provide('enumMap', enumMap)
// enumMap
const setEnumMap = async (col: ColumnProps) => {
if (!col.enum) return
// enum enumMap
if (typeof col.enum !== 'function')
// enum enumMap
if (typeof col.enum!== 'function')
return enumMap.value.set(col.prop!, col.enum!)
// enum enumMap
const { data } = await col.enum()
enumMap.value.set(col.prop!, data)
}
// columns
// columns
const flatColumnsFunc = (
columns: ColumnProps[],
flatArr: ColumnProps[] = []
) => {
columns.forEach(async col => {
//
if (col._children?.length) flatArr.push(...flatColumnsFunc(col._children))
flatArr.push(col)
// column isShow && isFilterEnum
col.isShow = col.isShow ?? true
col.isFilterEnum = col.isFilterEnum ?? true
// column isShow isFilterEnum
col.isShow = col.isShow?? true
col.isFilterEnum = col.isFilterEnum?? true
// enumMap
setEnumMap(col)
})
return flatArr.filter(item => !item._children?.length)
//
return flatArr.filter(item =>!item._children?.length)
}
// flatColumns
// columns
const flatColumns = ref<ColumnProps[]>()
flatColumns.value = flatColumnsFunc(tableColumns.value)
//
//
const searchColumns = flatColumns.value.filter(item => item.search?.el)
// &&
//
searchColumns.forEach((column, index) => {
column.search!.order = column.search!.order ?? index + 2
column.search!.order = column.search!.order?? index + 2
if (
column.search?.defaultValue !== undefined &&
column.search?.defaultValue !== null
column.search?.defaultValue!== undefined &&
column.search?.defaultValue!== null
) {
searchInitParam.value[column.search.key ?? handleProp(column.prop!)] =
searchInitParam.value[column.search.key?? handleProp(column.prop!)] =
column.search?.defaultValue
searchParam.value[column.search.key ?? handleProp(column.prop!)] =
searchParam.value[column.search.key?? handleProp(column.prop!)] =
column.search?.defaultValue
}
})
//
//
searchColumns.sort((a, b) => a.search!.order! - b.search!.order!)
// ==>
//
const colRef = ref()
//
const colSetting = tableColumns.value!.filter(
item =>
!['selection', 'index', 'expand'].includes(item.type!) &&
item.prop !== 'operation'
![
'selection',
'index',
'expand'
].includes(item.type!) && item.prop!== 'operation'
)
//
const openColSetting = () => colRef.value.openColSetting()
// 201-238
// enum
// enum
const printData = computed(() => {
const printDataList = JSON.parse(
JSON.stringify(
selectedList.value.length ? selectedList.value : tableData.value
selectedList.value.length? selectedList.value : tableData.value
)
)
// enum || prop && enum
//
const needTransformCol = flatColumns.value!.filter(
item =>
(item.enum || (item.prop && item.prop.split('.').length > 1)) &&
@ -285,13 +347,13 @@ const printData = computed(() => {
needTransformCol.forEach(colItem => {
printDataList.forEach((tableItem: { [key: string]: any }) => {
tableItem[handleProp(colItem.prop!)] =
colItem.prop!.split('.').length > 1 && !colItem.enum
colItem.prop!.split('.').length > 1 &&!colItem.enum
? formatValue(handleRowAccordingToProp(tableItem, colItem.prop!))
: filterEnum(
handleRowAccordingToProp(tableItem, colItem.prop!),
enumMap.value.get(colItem.prop!),
colItem.fieldNames
)
handleRowAccordingToProp(tableItem, colItem.prop!),
enumMap.value.get(colItem.prop!),
colItem.fieldNames
)
for (const key in tableItem) {
if (tableItem[key] === null)
tableItem[key] = formatValue(tableItem[key])
@ -318,7 +380,7 @@ const printData = computed(() => {
// });
// };
// ()
//
defineExpose({
element: tableRef,
tableData,

@ -1,17 +1,32 @@
// 从 "element-plus/es/components/table/src/table-column/defaults" 导入 TableColumnCtx 类型
// TableColumnCtx 可能是 Element Plus 中表格列的上下文类型,包含了表格列的一些默认属性和方法
import { TableColumnCtx } from "element-plus/es/components/table/src/table-column/defaults";
// 从 "@/components/Grid/interface" 导入 BreakPoint 和 Responsive 类型
// BreakPoint 可能表示响应式布局中的不同断点(如 xs, sm, md, lg, xl 等)
// Responsive 可能用于描述在不同断点下的响应式配置
import { BreakPoint, Responsive } from "@/components/Grid/interface";
// 定义一个接口 EnumProps用于描述枚举属性
export interface EnumProps {
label: string; // 选项框显示的文字
value: any; // 选项框值
disabled?: boolean; // 是否禁用此选项
tagType?: string; // 当 tag 为 true 时,此选择会指定 tag 显示类型
children?: EnumProps[]; // 为树形选择时,可以通过 children 属性指定子选项
// 选项框显示的文字,类型为字符串
label: string;
// 选项框值,可以是任意类型
value: any;
// 是否禁用此选项,可选,默认为 undefined类型为布尔值
disabled?: boolean;
// 当 tag 为 true 时,此选择会指定 tag 显示类型,可选,默认为 undefined类型为字符串
tagType?: string;
// 为树形选择时,可以通过 children 属性指定子选项,可选,默认为 undefined类型为 EnumProps 数组
children?: EnumProps[];
// 允许添加任意其他属性,属性名和值的类型都为任意类型
[key: string]: any;
}
export type TypeProp = "index" | "selection" | "expand"
// 定义一个类型别名 TypeProp其值只能是 "index"、"selection" 或 "expand" 中的一个
export type TypeProp = "index" | "selection" | "expand";
// 定义一个类型别名 SearchType用于表示搜索框的类型
// 可以是 "input"、"input-number" 等多种 Element Plus 组件类型
export type SearchType =
| "input"
| "input-number"
@ -23,27 +38,47 @@ export type SearchType =
| "time-picker"
| "time-select"
| "switch"
| "slider"
| "slider";
// 定义一个接口 SearchProps用于描述搜索项的属性
export type SearchProps = {
el: SearchType // 当前项搜索框的类型
props?: any // 搜索项参数,根据 element plus 官方文档来传递,该属性所有值会透传到组件
key?: string // 当搜索项 key 不为 prop 属性时,可通过 key 指定
order?: number // 搜索项排序(从大到小)
span?: number // 搜索项所占用的列数默认为1列
offset?: number // 搜索字段左侧偏移列数
defaultValue?: string | number | boolean | any[] // 搜索项默认值
} & Partial<Record<BreakPoint, Responsive>>
// 当前项搜索框的类型,类型为 SearchType
el: SearchType;
// 搜索项参数,根据 element plus 官方文档来传递,该属性所有值会透传到组件,可选,默认为 undefined
props?: any;
// 当搜索项 key 不为 prop 属性时,可通过 key 指定,可选,默认为 undefined类型为字符串
key?: string;
// 搜索项排序(从大到小),可选,默认为 undefined类型为数字
order?: number;
// 搜索项所占用的列数,默认为 1 列,可选,默认为 undefined类型为数字
span?: number;
// 搜索字段左侧偏移列数,可选,默认为 undefined类型为数字
offset?: number;
// 搜索项默认值,可以是字符串、数字、布尔值或任意类型的数组,可选,默认为 undefined
defaultValue?: string | number | boolean | any[];
// 扩展为包含 BreakPoint 类型的键和 Responsive 类型的值的部分记录,用于实现响应式配置
} & Partial<Record<BreakPoint, Responsive>>;
// 定义一个接口 ColumnProps用于描述表格列的属性
// 扩展自 Partial<Omit<TableColumnCtx<T>, "children" | "renderHeader" | "renderCell">>,表示部分继承 TableColumnCtx 类型并去除 "children"、"renderHeader" 和 "renderCell" 属性
export interface ColumnProps<T = any>
extends Partial<Omit<TableColumnCtx<T>, "children" | "renderHeader" | "renderCell">> {
tag?: boolean; // 是否是标签展示
isShow?: boolean; // 是否显示在表格当中
search?: SearchProps | undefined; // 搜索项配置
enum?: EnumProps[] | ((params?: any) => Promise<any>); // 枚举类型(渲染值的字典)
isFilterEnum?: boolean; // 当前单元格值是否根据 enum 格式化示例enum 只作为搜索项数据)
fieldNames?: { label: string; value: string }; // 指定 label && value 的 key 值
headerRender?: (row: ColumnProps) => any; // 自定义表头内容渲染tsx语法
render?: (scope: { row: T }) => any; // 自定义单元格内容渲染tsx语法
_children?: ColumnProps<T>[]; // 多级表头
// 是否是标签展示,可选,默认为 undefined类型为布尔值
tag?: boolean;
// 是否显示在表格当中,可选,默认为 undefined类型为布尔值
isShow?: boolean;
// 搜索项配置,可选,默认为 undefined类型为 SearchProps 或 undefined
search?: SearchProps | undefined;
// 枚举类型(渲染值的字典),可以是 EnumProps 数组或一个返回 Promise<any> 的函数,可选,默认为 undefined
enum?: EnumProps[] | ((params?: any) => Promise<any>);
// 当前单元格值是否根据 enum 格式化示例enum 只作为搜索项数据),可选,默认为 undefined类型为布尔值
isFilterEnum?: boolean;
// 指定 label && value 的 key 值,可选,默认为 undefined类型为包含 label 和 value 键的对象
fieldNames?: { label: string; value: string };
// 自定义表头内容渲染tsx语法可选默认为 undefined类型为一个接受 ColumnProps 类型参数并返回任意类型的函数
headerRender?: (row: ColumnProps) => any;
// 自定义单元格内容渲染tsx语法可选默认为 undefined类型为一个接受包含 row 属性且类型为 T 的对象并返回任意类型的函数
render?: (scope: { row: T }) => any;
// 多级表头,可选,默认为 undefined类型为 ColumnProps<T> 数组
_children?: ColumnProps<T>[];
}

@ -0,0 +1,30 @@
<template>
<!-- 根元素 div应用了多个类名用于设置样式和布局 -->
<!-- class="flex flex-col rounded card-wrap p-3" 表示使用 flex 布局方向为列边框圆角应用 card-wrap 类的样式内边距为 3 -->
<div class="flex flex-col rounded card-wrap p-3">
<!-- 条件渲染的 div如果 title 存在即不是 undefined 或空字符串等假值则显示该 div -->
<!-- class="pb-2" 表示该元素底部外边距为 2 -->
<!-- v-if="title" Vue 的条件渲染指令 -->
<div class="pb-2" v-if="title">{{ title }}</div>
<!-- 插槽用于父组件插入内容会被父组件的内容替换 -->
<slot></slot>
</div>
</template>
<script lang="ts" setup>
// 使 defineProps
// title ?
defineProps<{
title?: string
}>()
</script>
<style lang="scss" scoped>
// .card-wrap
// 1px 线 #eee 18px
.card-wrap {
background-color: #ffffff;
border: 1px solid #eee;
font-size: 18px;
}
</style>

@ -0,0 +1,90 @@
<template>
<!-- Element Plus 的对话框组件 el-dialog -->
<!-- style="width: 70%" 设置对话框的宽度为父容器的 70% -->
<!-- v-model="dialogVisible" 双向绑定对话框的显示隐藏状态dialogVisible 为一个响应式变量 -->
<!-- title="选择床位" 设置对话框的标题为选择床位 -->
<!-- destroy-on-close 表示在对话框关闭时销毁其中的内容 -->
<el-dialog
style="width: 70%"
v-model="dialogVisible"
title="选择床位"
destroy-on-close
>
<!-- Element Plus 的树状组件 el-tree -->
<!-- :data="data" 绑定树状结构的数据data 是一个响应式变量 -->
<!-- :props="defaultProps" 配置树状结构的属性如节点的标识显示文本子节点的字段等 -->
<!-- accordion 使树状结构以手风琴模式显示即同一时间只有一个节点可以展开 -->
<!-- @node-click="checkBed" 监听节点点击事件当点击节点时调用 checkBed 函数 -->
<el-tree
:data="data"
:props="defaultProps"
accordion
@node-click="checkBed"
/>
</el-dialog>
</template>
<script setup lang="ts" name="useProTable">
// Vue ref
import { ref } from "vue";
// API getBuildTree
import { getBuildTree } from "@/apis/bookManage";
// data undefined
const data: any = ref();
// dialogVisible false
const dialogVisible = ref(false);
// drawerProps DialogProps undefined
const drawerProps = ref<DialogProps>();
// DialogProps
// treeApi PromisePromise
interface DialogProps {
treeApi: (params: any) => Promise<any>;
}
// defaultProps el-tree
// id: "id" "id"
// label: "name" "name"
// children: "childrenList" "childrenList"
const defaultProps = {
id: "id",
label: "name",
children: "childrenList"
};
// treeAcceptParams
const treeAcceptParams = async (params: DialogProps) => {
// drawerProps
drawerProps.value = params;
//
dialogVisible.value = true;
// getBuildTree
const res: any = await getBuildTree();
// data
data.value = res.data;
};
// treeAcceptParams 便
defineExpose({
treeAcceptParams
});
// "getCheckBedInfo"
//
const emit = defineEmits<{
(event: "getCheckBedInfo", val: any): void
}>();
// checkBed
const checkBed = (bed: any) => {
// level 4
if (bed.level === 4) {
// "getCheckBedInfo"
emit("getCheckBedInfo", bed);
//
dialogVisible.value = false;
}
};
</script>
<style lang="scss" scoped></style>

@ -0,0 +1,189 @@
<template>
<!-- Element Plus 的上传组件 el-upload -->
<!-- v-model:file-list="imageList" 双向绑定上传文件列表imageList 是一个响应式变量 -->
<!-- :action="requestUrl" 设置上传文件的接口地址requestUrl 是一个变量 -->
<!-- :headers="{ token: token }" 设置上传请求的头部信息包含 token -->
<!-- list-type="picture-card" 设置上传文件列表的展示类型为图片卡片形式 -->
<!-- :before-upload="uploadBefore" 在文件上传前调用 uploadBefore 函数进行处理 -->
<!-- :on-error="handleError" 当文件上传失败时调用 handleError 函数 -->
<!-- :on-success="handleSuccess" 当文件上传成功时调用 handleSuccess 函数 -->
<!-- :on-preview="handlePreview" 当点击预览图片时调用 handlePreview 函数 -->
<!-- :on-remove="handleRemove" 当移除上传文件时调用 handleRemove 函数 -->
<el-upload
v-model:file-list="imageList"
:action="requestUrl"
:headers="{ token: token }"
list-type="picture-card"
:before-upload="uploadBefore"
:on-error="handleError"
:on-success="handleSuccess"
:on-preview="handlePreview"
:on-remove="handleRemove"
>
<!-- Element Plus 的图标组件 el-icon展示一个加号图标 -->
<el-icon>
<Plus />
</el-icon>
</el-upload>
<!-- Element Plus 的对话框组件 el-dialog用于展示预览图片 -->
<!-- v-model="dialogVisible" 双向绑定对话框的显示隐藏状态dialogVisible 是一个响应式变量 -->
<el-dialog v-model="dialogVisible">
<!-- img 标签用于显示预览图片w-full 可能是一个自定义的类名表示宽度占满:src="dialogImageUrl" 绑定图片的源地址dialogImageUrl 是一个响应式变量 -->
<img w-full :src="dialogImageUrl" alt="Preview Image" />
</el-dialog>
</template>
<!--
TODO 上传图片 / 使用方法
// 1.
import elderListDialog from "@/components/elderListDialog/index.vue";
// 2.使
<uploadImage :uploadParams="uploadParams" @setImageData='setImageData' />
// 3. single / multiple
ref({
uploadType: "single",
imageList: ref<UploadUserFile[]>([])
});
const setImageData = (url: string) => {
formData.value.picture = url;
};
// 4.()
//
uploadParams.value.imageList = []
//
uploadParams.value.imageList.push({
uid: 0,
name: "0.jpeg",
url: formData.value.image
});
-->
<script lang="ts" setup>
// Vuex store
import store from "@/store";
// URL
import { baseUrl } from "@/utils/http";
// Element Plus ElMessage
import { ElMessage } from "element-plus";
// Element Plus Plus
import { Plus } from "@element-plus/icons-vue";
// Vue
import { defineEmits, onMounted, ref, watch } from "vue";
// Element Plus
import type { UploadUserFile, ElUpload } from "element-plus";
// dialogImageUrl URL
const dialogImageUrl = ref("");
// dialogVisible false
const dialogVisible = ref(false);
// imageList
const imageList = ref<UploadUserFile[]>([]);
// props uploadParams
const props = defineProps({
uploadParams: Object
});
//
onMounted(() => {
// uploadParams imageList imageList
props.uploadParams?.["imageList"].forEach((image: any) => {
imageList.value.push(image);
});
});
// URL
const requestUrl = baseUrl + "file/uploadImg";
// Vuex store token
const token = store.state.app.token;
// imageUrlList URL
const imageUrlList = ref<any[]>([]);
// "setImageData"
const emits = defineEmits(["setImageData"]);
// updateData
const updateData = () => {
// imageUrlList
imageUrlList.value = [];
// imageList URL imageUrlList
imageList.value.forEach(image => imageUrlList.value.push(image.url));
// data
const data = ref<any>();
//
if (props.uploadParams?.["uploadType"] === "single") {
// URL data
data.value = imageUrlList.value[0];
} else {
// imageUrlList data
data.value = imageUrlList;
}
// "setImageData"
emits("setImageData", data);
};
// imageList
watch(imageList, (value, oldValue, onCleanup) => {
// imageList 1
if (props.uploadParams?.["uploadType"] === "single" && imageList.value.length > 1) {
//
imageList.value.splice(0, 1);
//
updateData();
}
});
// uploadBefore
const uploadBefore = (file: any) => {
// jpeg png
const isJPG = file.type === "image/jpeg" || file.type === "image/png";
// 2MB
const isLt2M = file.size / 1024 / 1024 < 2;
// jpeg png
if (!isJPG) {
ElMessage.error("只能上传jpg/png文件");
}
// 2MB
if (!isLt2M) {
ElMessage.error("文件大小不能超过2MB");
}
//
return isJPG && isLt2M;
};
// handleError
const handleError = (response: any) => {
//
ElMessage.error(response.data.msg);
};
// handleSuccess
const handleSuccess = (response: any, uploadFile: any) => {
// URL url
uploadFile.url = response.data.url;
//
updateData();
};
// handleRemove
const handleRemove = async (uploadFile: any) => {
// imageList
imageList.value.filter((image) => image.uid!== uploadFile.uid);
//
updateData();
// TODO imageId : uploadFile.uid
};
// handlePreview
const handlePreview = (uploadFile: any) => {
// URL URL
dialogImageUrl.value = uploadFile.url!;
//
dialogVisible.value = true;
};
</script>

@ -0,0 +1,47 @@
<template>
<div>
<!-- 定义一个 Element Plus 的按钮点击该按钮会触发 addNew 方法 -->
<el-button @click="addNew"></el-button>
<!-- 使用 v-for 指令遍历 componentList 数组将数组中的每个元素渲染为一个组件 -->
<div v-for="(item, index) in componentList" :key="index">
<!-- 使用 el-component 动态添加组件 -->
<!-- :is="item.type" 指定要渲染的组件类型该类型从 componentList 数组元素的 type 属性获取 -->
<!-- :props="item.props" 传递组件所需的 props 数据这些数据从 componentList 数组元素的 props 属性获取 -->
<el-component :is="item.type" :props="item.props"></el-component>
</div>
</div>
</template>
<script>
// HelloWorld
import HelloWorld from './components/HelloWorld.vue'
export default {
// data
data() {
return {
// componentList
componentList: []
}
},
// methods
methods: {
// addNew componentList
addNew() {
// componentList
// type HelloWorld
// props HelloWorld msg 'Hello World!'
this.componentList.push({
type: HelloWorld,
props: {
msg: 'Hello World!'
}
})
}
},
// components HelloWorld
components: {
HelloWorld
}
}
</script>

@ -1,6 +1,12 @@
// 从当前目录下的 src 文件夹中的 index.vue 文件导入组件
// 这里将导入的组件命名为 reImageVerify它应该是一个 Vue 组件
import reImageVerify from './src/index.vue'
/** 图形验证码组件 */
// 使用具名导出的方式,将导入的组件以 ReImageVerify 这个名称导出
// 具名导出允许在导入时指定具体要导入的内容,方便在其他文件中使用
export const ReImageVerify = reImageVerify
// 使用默认导出的方式,将导入的组件作为默认导出
// 默认导出允许在导入时可以使用任意名称来接收该组件,通常一个文件只能有一个默认导出
export default ReImageVerify

@ -5,23 +5,32 @@ import { ref, onMounted } from 'vue'
* @param width -
* @param height -
*/
// 定义一个名为 useImageVerify 的函数,用于生成和管理图形验证码,接受图形宽度和高度作为参数,有默认值
export const useImageVerify = (width = 120, height = 40) => {
// 创建一个响应式引用 domRef用于存储 <canvas> 元素的引用
const domRef = ref<HTMLCanvasElement>()
// 创建一个响应式变量 imgCode用于存储生成的图形验证码字符串
const imgCode = ref('')
// 定义一个函数 setImgCode用于设置 imgCode 的值
function setImgCode(code: string) {
imgCode.value = code
}
// 定义一个函数 getImgCode用于在 <canvas> 上绘制验证码并获取生成的验证码字符串
function getImgCode() {
// 如果 domRef 没有值(即 <canvas> 元素未挂载),则直接返回
if (!domRef.value) return
// 调用 draw 函数绘制验证码,并将返回的验证码字符串赋值给 imgCode
imgCode.value = draw(domRef.value, width, height)
}
// 在组件挂载后执行的钩子函数,调用 getImgCode 函数生成验证码
onMounted(() => {
getImgCode()
})
// 返回一个包含 domRef、imgCode、setImgCode 和 getImgCode 的对象,以便在其他地方使用这些变量和函数
return {
domRef,
imgCode,
@ -30,56 +39,95 @@ export const useImageVerify = (width = 120, height = 40) => {
}
}
// 定义一个函数 randomNum用于生成指定范围内的随机整数
function randomNum(min: number, max: number) {
// 使用 Math.random() 生成一个 0 到 1 之间的随机小数,然后通过计算得到指定范围内的整数
const num = Math.floor(Math.random() * (max - min) + min)
return num
}
// 定义一个函数 randomColor用于生成指定范围内的随机颜色RGB 格式)
function randomColor(min: number, max: number) {
// 分别生成随机的红r、绿g、蓝b分量值
const r = randomNum(min, max)
const g = randomNum(min, max)
const b = randomNum(min, max)
// 返回 RGB 格式的颜色字符串
return `rgb(${r},${g},${b})`
}
// 定义一个函数 draw用于在 <canvas> 元素上绘制图形验证码
function draw(dom: HTMLCanvasElement, width: number, height: number) {
// 初始化一个空字符串 imgCode用于存储生成的验证码
let imgCode = ''
// 定义一个字符串 NUMBER_STRING包含数字 0 到 9用于生成验证码字符
const NUMBER_STRING = '0123456789'
// 获取 <canvas> 元素的 2D 绘图上下文 ctx
const ctx = dom.getContext('2d')
// 如果获取上下文失败ctx 为 null则直接返回空的验证码字符串
if (!ctx) return imgCode
// 设置绘图上下文的填充样式为随机颜色(背景色)
ctx.fillStyle = randomColor(180, 230)
// 使用填充样式绘制一个矩形,覆盖整个 <canvas> 区域,作为验证码的背景
ctx.fillRect(0, 0, width, height)
// 循环 4 次,生成 4 个验证码字符
for (let i = 0; i < 4; i += 1) {
// 从 NUMBER_STRING 中随机选取一个字符作为验证码字符
const text = NUMBER_STRING[randomNum(0, NUMBER_STRING.length)]
// 将选取的字符添加到 imgCode 字符串中
imgCode += text
// 随机生成字体大小
const fontSize = randomNum(18, 41)
// 随机生成字符的旋转角度
const deg = randomNum(-30, 30)
// 设置绘图上下文的字体样式
ctx.font = `${fontSize}px Simhei`
// 设置文本基线为顶部
ctx.textBaseline = 'top'
// 设置绘图上下文的填充样式为随机颜色(字符颜色)
ctx.fillStyle = randomColor(80, 150)
// 保存当前绘图上下文的状态
ctx.save()
// 平移绘图上下文的原点到指定位置(每个字符的起始位置)
ctx.translate(30 * i + 15, 15)
// 旋转绘图上下文指定的角度
ctx.rotate((deg * Math.PI) / 180)
// 在指定位置绘制字符
ctx.fillText(text, -15 + 5, -15)
// 恢复之前保存的绘图上下文状态
ctx.restore()
}
// 循环 5 次,绘制 5 条随机直线
for (let i = 0; i < 5; i += 1) {
// 开始绘制路径
ctx.beginPath()
// 设置路径的起始点为随机位置
ctx.moveTo(randomNum(0, width), randomNum(0, height))
// 设置路径的终点为随机位置
ctx.lineTo(randomNum(0, width), randomNum(0, height))
// 设置绘图上下文的描边样式为随机颜色
ctx.strokeStyle = randomColor(180, 230)
// 关闭路径
ctx.closePath()
// 绘制路径(直线)
ctx.stroke()
}
// 循环 41 次,绘制 41 个随机圆形
for (let i = 0; i < 41; i += 1) {
// 开始绘制路径
ctx.beginPath()
// 绘制一个圆形,圆心为随机位置,半径为 1
ctx.arc(randomNum(0, width), randomNum(0, height), 1, 0, 2 * Math.PI)
// 关闭路径
ctx.closePath()
// 设置绘图上下文的填充样式为随机颜色
ctx.fillStyle = randomColor(150, 200)
// 填充圆形
ctx.fill()
}
// 返回生成的验证码字符串
return imgCode
}

@ -1,37 +1,50 @@
<script setup lang="ts">
// Vue watch
import { watch } from 'vue'
// useImageVerify
import { useImageVerify } from './hooks'
// Props
interface Props {
code?: string
code?: string // code
}
// Emits
interface Emits {
(e: 'update:code', code: string): void
(e: 'update:code', code: string): void // 'update:code' code
}
// 使 withDefaults props
const props = withDefaults(defineProps<Props>(), {
code: ''
code: '' // code
})
// emit
const emit = defineEmits<Emits>()
// useImageVerify domRefimgCodesetImgCode getImgCode
const { domRef, imgCode, setImgCode, getImgCode } = useImageVerify()
// props.code props.code setImgCode
watch(
() => props.code,
newValue => {
setImgCode(newValue)
}
)
// imgCode imgCode 'update:code'
watch(imgCode, newValue => {
emit('update:code', newValue)
})
// getImgCode 便
defineExpose({ getImgCode })
</script>
<template>
<!-- 定义一个 canvas 元素设置其宽度为 120高度为 40添加类名 cursor-pointer 使其具有指针样式
绑定 ref domRef以便在脚本中访问该元素绑定 click 事件为 getImgCode 函数 -->
<canvas
ref="domRef"
width="120"

@ -1,66 +1,76 @@
<template>
<!-- 根据条件渲染不同的组件 -->
<component
v-if="column.search?.el"
:is="`el-${column.search.el}`"
v-bind="handleSearchProps"
v-model.trim="searchParam[column.search.key ?? handleProp(column.prop!)]"
:data="column.search?.el === 'tree-select' ? columnEnum : []"
:options="
['cascader', 'select-v2'].includes(column.search?.el) ? columnEnum : []
"
:placeholder="placeholder"
:clearable="clearable"
range-separator="至"
start-placeholder="开始时间"
end-placeholder="结束时间"
v-if="column.search?.el" <!-- 如果 column.search.el 存在则渲染该组件 -->
:is="`el-${column.search.el}`" <!-- 根据 column.search.el 的值动态渲染对应的 Element Plus 组件例如 'el-input''el-select' -->
v-bind="handleSearchProps" <!-- 绑定处理后的搜索属性 -->
v-model.trim="searchParam[column.search.key?? handleProp(column.prop!)]" <!-- 双向绑定搜索参数使用 trim 修饰符去除首尾空格 -->
:data="column.search?.el === 'tree-select'? columnEnum : []" <!-- 如果是 tree-select 组件绑定 columnEnum 数据否则绑定空数组 -->
:options="
['cascader','select-v2'].includes(column.search?.el)? columnEnum : []
" <!-- 如果是 cascader select-v2 组件绑定 columnEnum 数据作为选项否则绑定空数组 -->
:placeholder="placeholder" <!-- 绑定占位符文本 -->
:clearable="clearable" <!-- 绑定是否可清除的属性 -->
range-separator="至" <!-- 日期范围选择器的分隔符 -->
start-placeholder="开始时间" <!-- 日期范围选择器开始时间的占位符 -->
end-placeholder="结束时间" <!-- 日期范围选择器结束时间的占位符 -->
>
<template #default="{ data }" v-if="column.search.el === 'cascader'">
<span>{{ data[fieldNames.label] }}</span>
</template>
<template v-if="column.search.el === 'select'">
<component
:is="`el-option`"
v-for="(col, index) in columnEnum"
:key="index"
:label="col[fieldNames.label]"
:value="col[fieldNames.value]"
></component>
</template>
<slot v-else></slot>
<!-- column.search.el 'cascader' 渲染默认插槽内容 -->
<template #default="{ data }" v-if="column.search.el === 'cascader'">
<span>{{ data[fieldNames.label] }}</span> <!-- 显示数据的 label 字段 -->
</template>
<!-- column.search.el 'select' 渲染子选项 -->
<template v-if="column.search.el ==='select'">
<component
:is="`el-option`" <!-- 渲染 Element Plus el-option 组件 -->
v-for="(col, index) in columnEnum" <!-- 遍历 columnEnum 数据 -->
:key="index" <!-- 设置唯一的 key -->
:label="col[fieldNames.label]" <!-- 绑定选项的 label -->
:value="col[fieldNames.value]" <!-- 绑定选项的值 -->
></component>
</template>
<!-- 如果以上条件都不满足渲染插槽内容 -->
<slot v-else></slot>
</component>
</template>
<script setup lang="ts" name="SearchFormItem">
// Vue
import { computed, inject, onMounted, ref } from 'vue'
// handleProp
import { handleProp } from '@/utils/util'
//
import { ColumnProps } from '@/components/ProTable/interface'
// SearchFormItem
interface SearchFormItem {
column: ColumnProps
searchParam: { [key: string]: any }
column: ColumnProps //
searchParam: { [key: string]: any } //
}
// props
const props = defineProps<SearchFormItem>()
// fieldNames label && value key
const fieldNames = computed(() => {
return {
label: props.column.fieldNames?.label ?? 'label',
value: props.column.fieldNames?.value ?? 'value'
label: props.column.fieldNames?.label?? 'label', // column.fieldNames.label 使使 'label'
value: props.column.fieldNames?.value?? 'value' // column.fieldNames.value 使使 'value'
}
})
// enumMap
// enumMap
const enumMap = inject('enumMap', ref(new Map()))
// columnEnum props.column.prop enumMap
const columnEnum = computed(() => {
let enumData = enumMap.value.get(props.column.prop)
if (!enumData) return []
if (props.column.search?.el === 'select-v2' && props.column.fieldNames) {
if (!enumData) return [] //
if (props.column.search?.el ==='select-v2' && props.column.fieldNames) {
enumData = enumData.map((item: { [key: string]: any }) => {
return {
...item,
label: item[fieldNames.value.label],
value: item[fieldNames.value.value]
label: item[fieldNames.value.label], // label
value: item[fieldNames.value.value] // value
}
})
}
@ -72,18 +82,18 @@ const handleSearchProps = computed(() => {
const label = fieldNames.value.label
const value = fieldNames.value.value
const searchEl = props.column.search?.el
const searchProps = props.column.search?.props ?? {}
const searchProps = props.column.search?.props?? {}
let handleProps = searchProps
if (searchEl === 'tree-select')
handleProps = {
...searchProps,
props: { label, ...searchProps.props },
props: { label,...searchProps.props }, // tree-select props
nodeKey: value
}
if (searchEl === 'cascader')
handleProps = {
...searchProps,
props: { label, value, ...searchProps.props }
props: { label, value,...searchProps.props } // cascader props
}
return handleProps
})
@ -92,7 +102,7 @@ const handleSearchProps = computed(() => {
const placeholder = computed(() => {
const search = props.column.search
return (
search?.props?.placeholder ?? (search?.el === 'input' ? '请输入' : '请选择')
search?.props?.placeholder?? (search?.el === 'input'? '请输入' : '请选择') //
)
})
@ -100,8 +110,8 @@ const placeholder = computed(() => {
const clearable = computed(() => {
const search = props.column.search
return (
search?.props?.clearable ??
(search?.defaultValue == null || search?.defaultValue == undefined)
search?.props?.clearable??
(search?.defaultValue == null || search?.defaultValue == undefined) // props
)
})
</script>

@ -1,25 +1,34 @@
<template>
<MyCard
><div class="card table-search" v-if="columns.length">
<!-- MyCard 组件可能是一个自定义的卡片组件 -->
<MyCard>
<!-- columns 数组长度大于 0 时显示搜索表单 -->
<div class="card table-search" v-if="columns.length">
<!-- el-form 组件用于创建表单 -->
<el-form ref="formRef" :model="searchParam">
<!-- Grid 组件可能是一个用于布局的网格组件 -->
<Grid
ref="gridRef"
:collapsed="collapsed"
:gap="[20, 0]"
:cols="searchCol"
>
<!-- 循环渲染 GridItem 组件每个 GridItem 对应一个搜索表单字段 -->
<GridItem
v-for="(item, index) in columns"
:key="item.prop"
v-bind="getResponsive(item)"
:index="index"
>
<!-- el-form-item 组件用于创建表单字段 -->
<el-form-item :label="`${item.label} :`">
<!-- SearchFormItem 组件用于渲染具体的搜索表单字段 -->
<SearchFormItem :column="item" :searchParam="searchParam" />
</el-form-item>
</GridItem>
<!-- 带有 suffix 标志的 GridItem 组件用于放置操作按钮 -->
<GridItem suffix>
<div class="operation">
<!-- 搜索按钮点击时调用 search 方法 -->
<el-button
class="bg-blue clickSearchBtn"
type="primary"
@ -28,53 +37,74 @@
>
搜索
</el-button>
<!-- 重置按钮点击时调用 reset 方法 -->
<el-button :icon="Delete" @click="reset"></el-button>
<!-- 展开/合并按钮根据 collapsed 的值显示不同的文本和图标 -->
<el-button
v-if="showCollapse"
link
class="search-isOpen"
@click="collapsed = !collapsed"
@click="collapsed =!collapsed"
>
{{ collapsed ? '展开' : '合并' }}
{{ collapsed? '展开' : '合并' }}
<el-icon class="el-icon--right">
<component :is="collapsed ? ArrowDown : ArrowUp"></component>
<component :is="collapsed? ArrowDown : ArrowUp"></component>
</el-icon>
</el-button>
</div>
</GridItem>
</Grid>
</el-form></div
></MyCard>
</el-form>
</div>
</MyCard>
</template>
<script setup lang="ts" name="SearchForm">
// Vue
import { computed, onMounted, ref } from 'vue'
// ProTable
import { ColumnProps } from '@/components/ProTable/interface'
// Grid
import { BreakPoint } from '@/components/Grid/interface'
// Element Plus
import { Delete, Search, ArrowDown, ArrowUp } from '@element-plus/icons-vue'
// SearchFormItem
import SearchFormItem from './components/SearchFormItem.vue'
// Grid
import Grid from '@/components/Grid/index.vue'
// GridItem
import GridItem from '@/components/Grid/components/GridItem.vue'
// MyCard
import MyCard from '../my-card/my-card.vue'
// ProTableProps
interface ProTableProps {
columns?: ColumnProps[] //
searchParam?: { [key: string]: any } //
//
columns?: ColumnProps[]
//
searchParam?: { [key: string]: any }
//
searchCol: number | Record<BreakPoint, number>
search: (params: any) => void //
reset: (params: any) => void //
//
search: (params: any) => void
//
reset: (params: any) => void
}
//
// 使 withDefaults
const props = withDefaults(defineProps<ProTableProps>(), {
columns: () => [],
searchParam: () => ({})
})
//
//
const getResponsive = (item: ColumnProps) => {
return {
// item.search.span
span: item.search?.span,
offset: item.search?.offset ?? 0,
// item.search.offset 0
offset: item.search?.offset?? 0,
// item.search
xs: item.search?.xs,
sm: item.search?.sm,
md: item.search?.md,
@ -83,21 +113,24 @@ const getResponsive = (item: ColumnProps) => {
}
}
//
//
const collapsed = ref(true)
//
// ref Grid
const gridRef = ref()
//
const breakPoint = computed<BreakPoint>(() => gridRef.value?.breakPoint)
// /
// /
const showCollapse = computed(() => {
let show = false
// columns
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') {
(current.search![breakPoint.value]?.span?? current.search?.span?? 1) +
(current.search![breakPoint.value]?.offset?? current.search?.offset?? 0)
// searchCol /
if (typeof props.searchCol!== 'number') {
if (prev >= props.searchCol[breakPoint.value]) show = true
} else {
if (prev > props.searchCol) show = true

@ -1,36 +1,49 @@
<template>
<!-- SVG 图标容器添加了 svg-icon 类用于样式设置area-hidden="true" 用于隐藏无障碍区域 -->
<svg class="svg-icon" area-hidden="true">
<!-- use 元素用于引用外部 SVG 符号:xlink:href 绑定了动态的图标名称 -->
<use :xlink:href="iconName"></use>
</svg>
</template>
<script setup lang="ts">
// Vue defineProps computed
import { defineProps, computed } from 'vue'
//
const props = defineProps({
// icon
icon: {
type: String,
required: true
},
// size 16
size: {
type: [Number, String],
default: 16
}
})
// icon #icon-
const iconName = computed(() => {
return `#icon-${props.icon}`
})
// size
const iconSize = computed(() => {
return props.size + 'px'
})
</script>
<style lang="scss" scoped>
// .svg-icon SVG
.svg-icon {
// 1em
width: 1em;
// 1em
height: 1em;
//
fill: currentColor;
// iconSize
font-size: v-bind(iconSize);
}
</style>

@ -1,26 +1,47 @@
<template>
<!-- el-dialog Element Plus 中的对话框组件用于展示选择老人的界面 -->
<!-- style="width: 70%" 设置对话框的宽度为父容器的 70% -->
<!-- v-model="dialogVisible" 双向绑定对话框的显示隐藏状态dialogVisible 是一个响应式变量 -->
<!-- title="选择老人" 设置对话框的标题 -->
<!-- destroy-on-close 表示在对话框关闭时销毁其内容 -->
<el-dialog
style="width: 70%"
v-model="dialogVisible"
title="选择老人"
destroy-on-close
>
<!-- 定义一个包含表格的 div 容器 -->
<div class="table-box">
<!-- ProTable 是自定义的表格组件用于展示用户列表 -->
<!-- ref="proTable" 为表格组件添加引用方便在脚本中访问 -->
<!-- title="用户列表" 设置表格的标题 -->
<!-- :columns="columns" 绑定表格的列配置项columns 是一个数组包含每列的信息 -->
<!-- :requestApi="getTableList" 绑定数据请求方法getTableList 用于获取表格数据 -->
<ProTable
ref="proTable"
title="用户列表"
:columns="columns"
:requestApi="getTableList"
>
<!-- 表格操作 -->
<!-- 定义表格操作列的插槽内容 -->
<template #operation="scope">
<!-- el-popconfirm Element Plus 中的弹出确认框组件 -->
<!-- title="Are you sure to choose this?" 设置确认框的提示信息 -->
<!-- @confirm="checkElder(scope.row)" 当用户确认时调用 checkElder 方法并传递当前行的数据 -->
<!-- confirm-button-type="warning" 设置确认按钮的类型为警告样式 -->
<el-popconfirm
title="Are you sure to choose this?"
@confirm="checkElder(scope.row)"
confirm-button-type="warning"
>
<!-- 定义确认框的触发元素 -->
<template #reference>
<el-button size="small" link :icon="View" > 选择 </el-button>
<!-- el-button Element Plus 中的按钮组件 -->
<!-- size="small" 设置按钮大小为小 -->
<!-- link 设置按钮为链接样式 -->
<!-- :icon="View" 绑定图标View 是从 @element-plus/icons-vue 导入的图标组件 -->
<!-- 选择 是按钮的文本内容 -->
<el-button size="small" link :icon="View">选择</el-button>
</template>
</el-popconfirm>
</template>
@ -30,27 +51,36 @@
</template>
<script setup lang="ts" name="useProTable">
// Vue ref
import { ref } from "vue";
// ProTable ColumnProps
import { ColumnProps } from "@/components/ProTable/interface";
// ProTable
import ProTable from "@/components/ProTable/index.vue";
// Element Plus View
import { View } from "@element-plus/icons-vue";
// dialogVisible
const dialogVisible = ref(false);
// proTable ProTable
const proTable = ref();
// drawerProps DialogProps
const drawerProps = ref<DialogProps>();
// DialogProps elderApi
interface DialogProps {
elderApi: (params: any) => Promise<any>;
}
// params
// ProTable :requestApi="getUserList"
// getTableList params
//
// elderApi
let getTableList = async (params: any) => {
let newParams = JSON.parse(JSON.stringify(params));
return drawerProps.value?.elderApi(newParams);
};
//
// columns ColumnProps
const columns: ColumnProps<any>[] = [
{ prop: "rank", label: "序号", width: 55 },
{ prop: "name", label: "姓名", search: { el: "input" } },
@ -62,21 +92,23 @@ const columns: ColumnProps<any>[] = [
{ prop: "operation", label: "操作", width: 70 }
];
//
// elderAcceptParams DialogProps params
// drawerProps
const elderAcceptParams = (params: DialogProps) => {
drawerProps.value = params;
dialogVisible.value = true;
};
//
// elderAcceptParams 便
defineExpose({
elderAcceptParams
});
// const emit = defineEmits(['getCheckElderInfo'])
// emit
const emit = defineEmits<{
(event: "getCheckElderInfo", val: any): void
}>();
//
// checkElder row
// "getCheckElderInfo"
const checkElder = (row: any) => {
emit("getCheckElderInfo", row);
dialogVisible.value = false;

@ -1,17 +1,27 @@
<template>
<!-- 根元素 div应用了多个类名用于设置样式和布局 -->
<!-- class="flex flex-col rounded card-wrap p-3" 表示使用 flex 布局方向为列边框圆角应用 card-wrap 类的样式内边距为 3 -->
<div class="flex flex-col rounded card-wrap p-3">
<!-- 条件渲染的 div如果 title 存在即不是 undefined 或空字符串等假值则显示该 div -->
<!-- class="pb-2" 表示该元素底部外边距为 2 -->
<!-- v-if="title" Vue 的条件渲染指令 -->
<div class="pb-2" v-if="title">{{ title }}</div>
<!-- 插槽用于父组件插入内容会被父组件的内容替换 -->
<slot></slot>
</div>
</template>
<script lang="ts" setup>
// 使 defineProps
// title ?
defineProps<{
title?: string
}>()
</script>
<style lang="scss" scoped>
// .card-wrap
// 1px 线 #eee 18px
.card-wrap {
background-color: #ffffff;
border: 1px solid #eee;

@ -1,10 +1,20 @@
<template>
<!-- Element Plus 的对话框组件 el-dialog -->
<!-- style="width: 70%" 设置对话框的宽度为父容器的 70% -->
<!-- v-model="dialogVisible" 双向绑定对话框的显示隐藏状态dialogVisible 为一个响应式变量 -->
<!-- title="选择床位" 设置对话框的标题为选择床位 -->
<!-- destroy-on-close 表示在对话框关闭时销毁其中的内容 -->
<el-dialog
style="width: 70%"
v-model="dialogVisible"
title="选择床位"
destroy-on-close
>
<!-- Element Plus 的树状组件 el-tree -->
<!-- :data="data" 绑定树状结构的数据data 是一个响应式变量 -->
<!-- :props="defaultProps" 配置树状结构的属性如节点的标识显示文本子节点的字段等 -->
<!-- accordion 使树状结构以手风琴模式显示即同一时间只有一个节点可以展开 -->
<!-- @node-click="checkBed" 监听节点点击事件当点击节点时调用 checkBed 函数 -->
<el-tree
:data="data"
:props="defaultProps"
@ -15,43 +25,63 @@
</template>
<script setup lang="ts" name="useProTable">
// Vue ref
import { ref } from "vue";
// API getBuildTree
import { getBuildTree } from "@/apis/bookManage";
// data undefined
const data: any = ref();
// dialogVisible false
const dialogVisible = ref(false);
// drawerProps DialogProps undefined
const drawerProps = ref<DialogProps>();
// DialogProps
// treeApi PromisePromise
interface DialogProps {
treeApi: (params: any) => Promise<any>;
}
// defaultProps el-tree
// id: "id" "id"
// label: "name" "name"
// children: "childrenList" "childrenList"
const defaultProps = {
id: "id",
label: "name",
children: "childrenList"
};
//
// treeAcceptParams
const treeAcceptParams = async (params: DialogProps) => {
// drawerProps
drawerProps.value = params;
//
dialogVisible.value = true;
// getBuildTree
const res: any = await getBuildTree();
// data
data.value = res.data;
};
//
// treeAcceptParams 便
defineExpose({
treeAcceptParams
});
// const emit = defineEmits(['getCheckElderInfo'])
// "getCheckBedInfo"
//
const emit = defineEmits<{
(event: "getCheckBedInfo", val: any): void
}>();
//
// checkBed
const checkBed = (bed: any) => {
// level 4
if (bed.level === 4) {
// "getCheckBedInfo"
emit("getCheckBedInfo", bed);
//
dialogVisible.value = false;
}
};

@ -1,4 +1,14 @@
<template>
<!-- Element Plus 的上传组件 el-upload -->
<!-- v-model:file-list="imageList" 双向绑定上传文件列表imageList 是一个响应式变量 -->
<!-- :action="requestUrl" 设置上传文件的接口地址requestUrl 是一个变量 -->
<!-- :headers="{ token: token }" 设置上传请求的头部信息包含 token -->
<!-- list-type="picture-card" 设置上传文件列表的展示类型为图片卡片形式 -->
<!-- :before-upload="uploadBefore" 在文件上传前调用 uploadBefore 函数进行处理 -->
<!-- :on-error="handleError" 当文件上传失败时调用 handleError 函数 -->
<!-- :on-success="handleSuccess" 当文件上传成功时调用 handleSuccess 函数 -->
<!-- :on-preview="handlePreview" 当点击预览图片时调用 handlePreview 函数 -->
<!-- :on-remove="handleRemove" 当移除上传文件时调用 handleRemove 函数 -->
<el-upload
v-model:file-list="imageList"
:action="requestUrl"
@ -10,12 +20,16 @@
:on-preview="handlePreview"
:on-remove="handleRemove"
>
<!-- Element Plus 的图标组件 el-icon展示一个加号图标 -->
<el-icon>
<Plus />
</el-icon>
</el-upload>
<!-- Element Plus 的对话框组件 el-dialog用于展示预览图片 -->
<!-- v-model="dialogVisible" 双向绑定对话框的显示隐藏状态dialogVisible 是一个响应式变量 -->
<el-dialog v-model="dialogVisible">
<!-- img 标签用于显示预览图片w-full 可能是一个自定义的类名表示宽度占满:src="dialogImageUrl" 绑定图片的源地址dialogImageUrl 是一个响应式变量 -->
<img w-full :src="dialogImageUrl" alt="Preview Image" />
</el-dialog>
</template>
@ -50,97 +64,126 @@ uploadParams.value.imageList.push({
-->
<script lang="ts" setup>
// Vuex store
import store from "@/store";
// URL
import { baseUrl } from "@/utils/http";
// Element Plus ElMessage
import { ElMessage } from "element-plus";
// Element Plus Plus
import { Plus } from "@element-plus/icons-vue";
// Vue
import { defineEmits, onMounted, ref, watch } from "vue";
// Element Plus
import type { UploadUserFile, ElUpload } from "element-plus";
// page data
// dialogImageUrl URL
const dialogImageUrl = ref("");
// dialogVisible false
const dialogVisible = ref(false);
// imageList
const imageList = ref<UploadUserFile[]>([]);
// father component transfer to this date
// props uploadParams
const props = defineProps({
uploadParams: Object
});
//
//
onMounted(() => {
// uploadParams imageList imageList
props.uploadParams?.["imageList"].forEach((image: any) => {
imageList.value.push(image);
});
});
// http request data
// URL
const requestUrl = baseUrl + "file/uploadImg";
// Vuex store token
const token = store.state.app.token;
// return data
// imageUrlList URL
const imageUrlList = ref<any[]>([]);
//
// "setImageData"
const emits = defineEmits(["setImageData"]);
//
// updateData
const updateData = () => {
//
// imageUrlList
imageUrlList.value = [];
// imageList URL imageUrlList
imageList.value.forEach(image => imageUrlList.value.push(image.url));
//
// data
const data = ref<any>();
//
if (props.uploadParams?.["uploadType"] === "single") {
// URL data
data.value = imageUrlList.value[0];
} else {
// imageUrlList data
data.value = imageUrlList;
}
//
// "setImageData"
emits("setImageData", data);
};
//
// imageList
watch(imageList, (value, oldValue, onCleanup) => {
// imageList 1
if (props.uploadParams?.["uploadType"] === "single" && imageList.value.length > 1) {
//
imageList.value.splice(0, 1);
//
updateData();
}
});
//
// uploadBefore
const uploadBefore = (file: any) => {
// jpeg png
const isJPG = file.type === "image/jpeg" || file.type === "image/png";
// 2MB
const isLt2M = file.size / 1024 / 1024 < 2;
// jpeg png
if (!isJPG) {
ElMessage.error("只能上传jpg/png文件");
}
// 2MB
if (!isLt2M) {
ElMessage.error("文件大小不能超过2MB");
}
//
return isJPG && isLt2M;
};
//
// handleError
const handleError = (response: any) => {
//
ElMessage.error(response.data.msg);
};
//
// handleSuccess
const handleSuccess = (response: any, uploadFile: any) => {
// URL url
uploadFile.url = response.data.url;
//
updateData();
};
//
// handleRemove
const handleRemove = async (uploadFile: any) => {
imageList.value.filter((image) => image.uid !== uploadFile.uid);
// imageList
imageList.value.filter((image) => image.uid!== uploadFile.uid);
//
updateData();
// TODO imageId : uploadFile.uid
};
//
// handlePreview
const handlePreview = (uploadFile: any) => {
// URL URL
dialogImageUrl.value = uploadFile.url!;
//
dialogVisible.value = true;
};
</script>

@ -1,28 +1,36 @@
<!-- Element Plus 中动态添加组件可以使用 el-component 组件该组件可以将任何一个 Vue 组件动态添加到页面中下面是一个示例代码-->
<!--在上面的示例中我们使用了 el-component 组件来动态添加组件通过 :is 属性指定要添加的组件类型通过 :props 属性传递组件所需的 props 数据-->
<!-- addNew 方法中我们向 componentList 数组中添加一个组件对象其中 type 属性指定要添加的组件类型props 属性指定要传递给组件的数据-->
<!--最后在模板中使用 v-for 指令遍历 componentList 数组并通过 :is :props 属性将组件动态添加到页面中-->
<!--需要注意的是在使用 el-component 组件动态添加组件时需要将要添加的组件在 components 中先进行注册-->
<template>
<div>
<!-- 定义一个 Element Plus 的按钮点击该按钮会触发 addNew 方法 -->
<el-button @click="addNew"></el-button>
<!-- 使用 v-for 指令遍历 componentList 数组将数组中的每个元素渲染为一个组件 -->
<div v-for="(item, index) in componentList" :key="index">
<!-- 使用 el-component 动态添加组件 -->
<!-- :is="item.type" 指定要渲染的组件类型该类型从 componentList 数组元素的 type 属性获取 -->
<!-- :props="item.props" 传递组件所需的 props 数据这些数据从 componentList 数组元素的 props 属性获取 -->
<el-component :is="item.type" :props="item.props"></el-component>
</div>
</div>
</template>
<script>
// HelloWorld
import HelloWorld from './components/HelloWorld.vue'
export default {
// data
data() {
return {
// componentList
componentList: []
}
},
// methods
methods: {
// addNew componentList
addNew() {
// componentList
// type HelloWorld
// props HelloWorld msg 'Hello World!'
this.componentList.push({
type: HelloWorld,
props: {
@ -31,6 +39,7 @@ export default {
})
}
},
// components HelloWorld
components: {
HelloWorld
}

@ -1,15 +1,23 @@
// 从当前目录的 index 文件中导入 routes、users、IRoute 和 IUser
import { routes, users, IRoute, IUser } from './index'
// 导入项目的 Vuex 存储实例
import store from '@/store'
// 获取路由列表
// 获取路由列表的函数,接受一个用户 IDuid作为参数
export const getRouterList = (uid: number) => {
// 从 Vuex 存储中获取当前用户的权限 ID 列表
const authIdList: number[] = store.state.app.userPeofile.authIdList
// 过滤出当前用户有权限访问的路由列表
const routeList: any[] = routes.filter(route => authIdList.includes(route.id))
// 如果传入了用户 IDuid
if (uid) {
// userInfo 有可能是 undefined
// 查找具有指定 ID 的用户信息,userInfo 有可能是 undefined
const userInfo: IUser | undefined = users.find(user => user.id === uid)
// 如果找到了用户信息
if (userInfo) {
// 以下是一段被注释掉的代码,功能可能是进一步筛选用户的授权路由列表
// const authRouteList: IRoute[] = []
// rid = router id
// userInfo.auth.map(rid => {
@ -19,12 +27,14 @@ export const getRouterList = (uid: number) => {
// }
// })
// })
// 返回成功响应,包含状态码、消息和筛选后的路由列表
return {
code: 0,
msg: 'ok',
data: routeList
}
} else {
// 如果没有找到用户信息,返回错误响应
return {
code: 1001,
msg: 'No userInfo for this UID',
@ -32,6 +42,7 @@ export const getRouterList = (uid: number) => {
}
}
} else {
// 如果没有传入用户 ID返回错误响应
return {
code: 1002,
msg: 'No UID received',

@ -1,5 +1,9 @@
// 从当前目录下的 routes 文件中导入 routes 变量和 IRoute 类型
import routes, { IRoute } from './routes'
// 从当前目录下的 users 文件中导入 users 变量和 IUser 类型
import users, { IUser } from './users'
// 导出 routes 变量,这样在其他文件中可以导入使用该变量
export { routes, users }
// 导出 IRoute 和 IUser 类型,方便其他文件导入使用这些类型定义
export type { IRoute, IUser }

@ -1,17 +1,29 @@
// 定义一个接口 IRoute用于描述路由的结构
export interface IRoute {
// 路由的唯一标识 ID类型为数字
id: number
// 父路由的 ID类型为数字
pid: number
// 路由的名称,类型为字符串
name: string
// 路由的路径,类型为字符串
path: string
// 重定向的路径,是一个可选属性,类型为字符串
redirect?: string
// 路由对应的组件路径,类型为字符串
component: string
// 路由的元数据,是一个对象
meta: {
// 路由的标题,类型为字符串
title: string
// 路由的图标名称,是一个可选属性,类型为字符串
icon?: string
}
}
// 定义一个名为 routes 的数组,元素类型为 IRoute存储路由信息
const routes: IRoute[] = [
// 第一个路由对象
{
id: 1,
pid: 0,
@ -23,6 +35,7 @@ const routes: IRoute[] = [
icon: 'home'
}
},
// 第二个路由对象
{
id: 2,
pid: 0,
@ -35,39 +48,43 @@ const routes: IRoute[] = [
icon: 'sale'
}
},
// 第三个路由对象
{
id: 3,
pid: 2,
path: 'counsel',
name: 'CounselSale',
component: 'sale/counsel/index.vue',
component:'sale/counsel/index.vue',
meta: {
title: '咨询管理',
icon: ''
}
},
// 以下是一些被注释掉的路由对象,可根据需求启用
// {
// id: 4,
// pid: 2,
// path: 'intention',
// name: 'IntentionSale',
// component: 'sale/intention/index.vue',
// component:'sale/intention/index.vue',
// meta: {
// title: '意向客户',
// icon: ''
// }
// },
// 第五个路由对象
{
id: 5,
pid: 2,
path: 'book',
name: 'BookSale',
component: 'sale/book/index.vue',
component:'sale/book/index.vue',
meta: {
title: '预定管理',
icon: ''
}
},
// 第六个路由对象
{
id: 6,
pid: 0,
@ -80,6 +97,7 @@ const routes: IRoute[] = [
icon: 'live'
}
},
// 第七个路由对象
{
id: 7,
pid: 6,
@ -91,6 +109,7 @@ const routes: IRoute[] = [
icon: ''
}
},
// 第八个路由对象
{
id: 8,
pid: 6,
@ -102,6 +121,7 @@ const routes: IRoute[] = [
icon: ''
}
},
// 第九个路由对象
{
id: 9,
pid: 6,
@ -113,6 +133,7 @@ const routes: IRoute[] = [
icon: ''
}
},
// 第十个路由对象
{
id: 10,
pid: 6,
@ -124,6 +145,7 @@ const routes: IRoute[] = [
icon: ''
}
},
// 第十一个路由对象
{
id: 11,
pid: 6,
@ -135,6 +157,7 @@ const routes: IRoute[] = [
icon: ''
}
},
// 第十二个路由对象
{
id: 12,
pid: 6,
@ -146,6 +169,7 @@ const routes: IRoute[] = [
icon: ''
}
},
// 第十三个路由对象
{
id: 13,
pid: 0,
@ -158,6 +182,7 @@ const routes: IRoute[] = [
icon: 'people'
}
},
// 第十四个路由对象
{
id: 14,
pid: 13,
@ -169,10 +194,11 @@ const routes: IRoute[] = [
icon: ''
}
},
// 第十五个路由对象
{
id: 15,
pid: 13,
path: 'staff',
path:'staff',
name: 'StaffPeople',
component: 'people/staff/index.vue',
meta: {
@ -180,6 +206,7 @@ const routes: IRoute[] = [
icon: ''
}
},
// 以下是一些被注释掉的路由对象,可根据需求启用
// {
// id: 16,
// pid: 13,
@ -191,61 +218,66 @@ const routes: IRoute[] = [
// icon: ''
// }
// },
// 第十七个路由对象
{
id: 17,
pid: 0,
path: '/serve',
name: 'Serve',
redirect: '/serve/project',
component: 'serve/index.vue',
component:'serve/index.vue',
meta: {
title: '服务管理',
icon: 'serve'
icon:'serve'
}
},
// 第十八个路由对象
{
id: 18,
pid: 17,
path: 'project',
name: 'ProjectServe',
component: 'serve/project/index.vue',
component:'serve/project/index.vue',
meta: {
title: '服务项目',
icon: ''
}
},
// 第十九个路由对象
{
id: 19,
pid: 17,
path: 'level',
name: 'LevelServe',
component: 'serve/level/index.vue',
component:'serve/level/index.vue',
meta: {
title: '护理等级',
icon: ''
}
},
// 第二十个路由对象
{
id: 20,
pid: 17,
path: 'book',
name: 'BookServe',
component: 'serve/book/index.vue',
component:'serve/book/index.vue',
meta: {
title: '服务预定',
icon: ''
}
},
// 以下是一些被注释掉的路由对象,可根据需求启用
// {
// id: 21,
// pid: 0,
// path: '/resource',
// name: 'Resource',
// redirect: '/resource/info',
// component: 'resource/index.vue',
// component:'resource/index.vue',
// meta: {
// title: '物资管理',
// icon: 'resource'
// icon:'resource'
// }
// },
// {
@ -253,7 +285,7 @@ const routes: IRoute[] = [
// pid: 21,
// path: 'info',
// name: 'InfoResource',
// component: 'resource/info/index.vue',
// component:'resource/info/index.vue',
// meta: {
// title: '物资信息',
// icon: ''
@ -264,7 +296,7 @@ const routes: IRoute[] = [
// pid: 21,
// path: 'Storage',
// name: 'StorageResource',
// component: 'resource/storage/index.vue',
// component:'resource/storage/index.vue',
// meta: {
// title: '仓库设置',
// icon: ''
@ -275,7 +307,7 @@ const routes: IRoute[] = [
// pid: 21,
// path: 'enter',
// name: 'EnterResource',
// component: 'resource/enter/index.vue',
// component:'resource/enter/index.vue',
// meta: {
// title: '入库管理',
// icon: ''
@ -286,7 +318,7 @@ const routes: IRoute[] = [
// pid: 21,
// path: 'leave',
// name: 'LeaveResource',
// component: 'resource/leave/index.vue',
// component:'resource/leave/index.vue',
// meta: {
// title: '出库管理',
// icon: ''
@ -295,14 +327,15 @@ const routes: IRoute[] = [
// {
// id: 26,
// pid: 21,
// path: 'search',
// path:'search',
// name: 'SearchResource',
// component: 'resource/search/index.vue',
// component:'resource/search/index.vue',
// meta: {
// title: '库存查询',
// icon: ''
// }
// },
// 第二十七个路由对象
{
id: 27,
pid: 0,
@ -315,6 +348,7 @@ const routes: IRoute[] = [
icon: 'diet'
}
},
// 第二十八个路由对象
{
id: 28,
pid: 27,
@ -326,6 +360,7 @@ const routes: IRoute[] = [
icon: ''
}
},
// 第二十九个路由对象
{
id: 29,
pid: 27,
@ -337,6 +372,7 @@ const routes: IRoute[] = [
icon: ''
}
},
// 第三十个路由对象
{
id: 30,
pid: 27,
@ -348,6 +384,7 @@ const routes: IRoute[] = [
icon: ''
}
},
// 第三十一个路由对象
{
id: 31,
pid: 0,
@ -360,6 +397,7 @@ const routes: IRoute[] = [
icon: 'charge'
}
},
// 第三十二个路由对象
{
id: 32,
pid: 31,
@ -371,10 +409,11 @@ const routes: IRoute[] = [
icon: ''
}
},
// 第三十三个路由对象
{
id: 33,
pid: 31,
path: 'record',
path:'record',
name: 'RecordCharge',
component: 'charge/record/index.vue',
meta: {
@ -382,6 +421,7 @@ const routes: IRoute[] = [
icon: ''
}
},
// 第三十四个路由对象
{
id: 34,
pid: 31,
@ -393,6 +433,7 @@ const routes: IRoute[] = [
icon: ''
}
},
// 第三十五个路由对象
{
id: 35,
pid: 0,
@ -405,10 +446,11 @@ const routes: IRoute[] = [
icon: 'base'
}
},
// 第三十六个路由对象
{
id: 36,
pid: 35,
path: 'sale',
path:'sale',
name: 'SaleBase',
redirect: '/base/sale/origin',
component: 'base/sale/index.vue',
@ -417,6 +459,7 @@ const routes: IRoute[] = [
icon: ''
}
},
// 第三十七个路由对象
{
id: 37,
pid: 36,
@ -428,6 +471,7 @@ const routes: IRoute[] = [
icon: ''
}
},
// 以下是一些被注释掉的路由对象,可根据需求启用
// {
// id: 38,
// pid: 36,
@ -439,6 +483,7 @@ const routes: IRoute[] = [
// icon: ''
// }
// },
// 第三十九个路由对象
{
id: 39,
pid: 35,
@ -451,6 +496,7 @@ const routes: IRoute[] = [
icon: ''
}
},
// 第四十个路由对象
{
id: 40,
pid: 39,
@ -462,6 +508,7 @@ const routes: IRoute[] = [
icon: ''
}
},
// 第四十一个路由对象
{
id: 41,
pid: 39,
@ -473,6 +520,7 @@ const routes: IRoute[] = [
icon: ''
}
},
// 以下是一些被注释掉的路由对象,可根据需求启用
// {
// id: 42,
// pid: 35,
@ -483,4 +531,5 @@ const routes: IRoute[] = [
// }
]
// 导出 routes 数组,使其可以在其他模块中被导入和使用
export default routes

@ -1,13 +1,20 @@
// 定义一个接口 IUser用于描述用户对象的结构
export interface IUser {
// 用户的唯一标识 ID类型为数字
id: number
// 用户的用户名,类型为字符串
username: string
// 用户拥有的权限 ID 数组,数组中的元素类型为数字
auth: number[]
}
// 导出一个默认的用户数组,类型为 IUser 数组
// 数组中包含一个用户对象
export default <IUser[]>[
{
id: 1,
username: 'zhangsan',
// 该用户拥有的权限 ID 数组,包含多个权限 ID
auth: [
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39,

@ -1,76 +1,216 @@
// 声明该类所在的包路径
package com.ew.gerocomium.controller;
// 导入通用常量类,可能包含开发者信息、常用标识等常量
import com.ew.gerocomium.common.constant.Constant;
// 导入自定义的数据结果封装类,用于封装接口返回的结果信息
import com.ew.gerocomium.dao.base.Result;
// 导入操作餐饮套餐的查询类,用于封装新增、编辑餐饮套餐时的请求参数
import com.ew.gerocomium.dao.query.OperateCateringSetQuery;
// 导入按关键字分页查询餐饮套餐的查询类,封装查询条件和分页信息
import com.ew.gerocomium.dao.query.PageCateringSetByKeyQuery;
// 导入按关键字分页查询菜品的查询类,封装查询条件和分页信息
import com.ew.gerocomium.dao.query.PageDishesByKeyQuery;
// 导入餐饮套餐服务类,控制器会调用该类的方法处理餐饮套餐相关业务
import com.ew.gerocomium.service.CateringSetService;
// 导入菜品服务类,控制器会调用该类的方法处理菜品相关业务
import com.ew.gerocomium.service.DishesService;
// 导入 Swagger 注解,用于生成 API 文档
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
// 导入 Spring Security 权限控制注解,用于控制接口的访问权限
import org.springframework.security.access.prepost.PreAuthorize;
// 导入 Spring MVC 相关注解,用于处理 HTTP 请求
import org.springframework.web.bind.annotation.*;
// 导入资源注入注解,用于将 Spring 容器中的 Bean 注入到当前类
import javax.annotation.Resource;
/**
*
*/
// 使用 Swagger 注解,为该控制器添加标签,方便在 API 文档中分类展示
@Api(tags = "餐饮套餐")
// 标记该类为 RESTful 风格的控制器,会自动将返回值转换为 JSON 格式
@RestController
// 定义该控制器处理的请求的基础路径,所有请求路径都会以 /cateringSet 开头
@RequestMapping("/cateringSet")
// 权限控制注解,只有拥有 /food/list 权限的用户才能访问该控制器的接口
@PreAuthorize("@AuthorityAssert.hasAuthority('/food/list')")
public class CateringSetController {
// 注入餐饮套餐服务类的实例,通过该实例调用餐饮套餐相关的业务方法
@Resource
private CateringSetService cateringSetService;
// 注入菜品服务类的实例,通过该实例调用菜品相关的业务方法
@Resource
private DishesService dishesService;
/**
*
*
* @param pageCateringSetByKeyQuery
* @param token 访访
* @return Result
*/
// 处理 HTTP GET 请求,请求路径为 /cateringSet/pageCateringSetByKey
@GetMapping("/pageCateringSetByKey")
// 使用 Swagger 注解,描述该接口的功能和开发者信息
@ApiOperation(value = "分页查询餐饮套餐", notes = Constant.DEVELOPER + Constant.EMPEROR_WEN)
public Result pageCateringSetByKey(@ApiParam(value = "分页查询餐饮套餐请求实体", required = true) PageCateringSetByKeyQuery pageCateringSetByKeyQuery,
@ApiParam(value = "接口访问请求头", required = true) @RequestHeader String token) {
public Result pageCateringSetByKey(
// 使用 Swagger 注解,描述该参数的作用和是否必填
@ApiParam(value = "分页查询餐饮套餐请求实体", required = true)
// 接收分页查询餐饮套餐的请求实体
PageCateringSetByKeyQuery pageCateringSetByKeyQuery,
// 使用 Swagger 注解,描述该参数的作用和是否必填
@ApiParam(value = "接口访问请求头", required = true)
// 从请求头中获取令牌,用于身份验证
@RequestHeader String token) {
// 调用餐饮套餐服务类的分页查询方法,并返回结果
return cateringSetService.pageCateringSetByKey(pageCateringSetByKeyQuery);
}
/**
*
*
* @param dishesTypeName
* @param token 访访
* @return Result
*/
// 处理 HTTP GET 请求,请求路径为 /cateringSet/listDishesType
@GetMapping("/listDishesType")
// 使用 Swagger 注解,描述该接口的功能和开发者信息
@ApiOperation(value = "获取菜品分类", notes = Constant.DEVELOPER + Constant.EMPEROR_WEN)
public Result listDishesType(@ApiParam(value = "获取菜品分类请求参数", required = false) String dishesTypeName,
@ApiParam(value = "接口访问请求头", required = true) @RequestHeader String token) {
public Result listDishesType(
// 使用 Swagger 注解,描述该参数的作用和是否必填
@ApiParam(value = "获取菜品分类请求参数", required = false)
// 接收可选的菜品分类名称参数
String dishesTypeName,
// 使用 Swagger 注解,描述该参数的作用和是否必填
@ApiParam(value = "接口访问请求头", required = true)
// 从请求头中获取令牌,用于身份验证
@RequestHeader String token) {
// 调用菜品服务类的获取菜品分类方法,并返回结果
return dishesService.listDishesType(dishesTypeName);
}
/**
*
*
* @param pageDishesByKeyQuery
* @param token 访访
* @return Result
*/
// 处理 HTTP GET 请求,请求路径为 /cateringSet/pageDishesByKey
@GetMapping("/pageDishesByKey")
// 使用 Swagger 注解,描述该接口的功能和开发者信息
@ApiOperation(value = "分页查询菜品", notes = Constant.DEVELOPER + Constant.EMPEROR_WEN)
public Result pageDishesByKey(@ApiParam(value = "分页查询菜品请求实体", required = true) PageDishesByKeyQuery pageDishesByKeyQuery,
@ApiParam(value = "接口访问请求头", required = true) @RequestHeader String token) {
public Result pageDishesByKey(
// 使用 Swagger 注解,描述该参数的作用和是否必填
@ApiParam(value = "分页查询菜品请求实体", required = true)
// 接收分页查询菜品的请求实体
PageDishesByKeyQuery pageDishesByKeyQuery,
// 使用 Swagger 注解,描述该参数的作用和是否必填
@ApiParam(value = "接口访问请求头", required = true)
// 从请求头中获取令牌,用于身份验证
@RequestHeader String token) {
// 调用菜品服务类的分页查询菜品方法,并返回结果
return dishesService.pageDishesByKey(pageDishesByKeyQuery);
}
/**
*
*
* @param operateCateringSetQuery
* @param token 访访
* @return Result
*/
// 处理 HTTP POST 请求,请求路径为 /cateringSet/addCateringSet
@PostMapping("/addCateringSet")
// 使用 Swagger 注解,描述该接口的功能和开发者信息
@ApiOperation(value = "新增餐饮套餐", notes = Constant.DEVELOPER + Constant.EMPEROR_WEN)
public Result addCateringSet(@ApiParam(value = "新增餐饮套餐请求实体", required = true) @RequestBody OperateCateringSetQuery operateCateringSetQuery,
@ApiParam(value = "接口访问请求头", required = true) @RequestHeader String token) {
public Result addCateringSet(
// 使用 Swagger 注解,描述该参数的作用和是否必填
@ApiParam(value = "新增餐饮套餐请求实体", required = true)
// 接收新增餐饮套餐的请求实体,从请求体中获取数据
@RequestBody OperateCateringSetQuery operateCateringSetQuery,
// 使用 Swagger 注解,描述该参数的作用和是否必填
@ApiParam(value = "接口访问请求头", required = true)
// 从请求头中获取令牌,用于身份验证
@RequestHeader String token) {
// 调用餐饮套餐服务类的新增餐饮套餐方法,并返回结果
return cateringSetService.addCateringSet(operateCateringSetQuery);
}
/**
*
*
* @param setId
* @param token 访访
* @return Result
*/
// 处理 HTTP GET 请求,请求路径为 /cateringSet/getCateringSetById
@GetMapping("/getCateringSetById")
// 使用 Swagger 注解,描述该接口的功能和开发者信息
@ApiOperation(value = "根据编号查询餐饮套餐", notes = Constant.DEVELOPER + Constant.EMPEROR_WEN)
public Result getCateringSetById(@ApiParam(value = "根据编号查询餐饮套餐请求参数", required = true) @RequestParam Long setId,
@ApiParam(value = "接口访问请求头", required = true) @RequestHeader String token) {
public Result getCateringSetById(
// 使用 Swagger 注解,描述该参数的作用和是否必填
@ApiParam(value = "根据编号查询餐饮套餐请求参数", required = true)
// 接收要查询的餐饮套餐编号
@RequestParam Long setId,
// 使用 Swagger 注解,描述该参数的作用和是否必填
@ApiParam(value = "接口访问请求头", required = true)
// 从请求头中获取令牌,用于身份验证
@RequestHeader String token) {
// 调用餐饮套餐服务类的根据编号查询餐饮套餐方法,并返回结果
return cateringSetService.getCateringSetById(setId);
}
/**
*
*
* @param operateCateringSetQuery
* @param token 访访
* @return Result
*/
// 处理 HTTP PUT 请求,请求路径为 /cateringSet/editCateringSet
@PutMapping("/editCateringSet")
// 使用 Swagger 注解,描述该接口的功能和开发者信息
@ApiOperation(value = "编辑餐饮套餐", notes = Constant.DEVELOPER + Constant.EMPEROR_WEN)
public Result editCateringSet(@ApiParam(value = "编辑餐饮套餐请求实体", required = true) @RequestBody OperateCateringSetQuery operateCateringSetQuery,
@ApiParam(value = "接口访问请求头", required = true) @RequestHeader String token) {
public Result editCateringSet(
// 使用 Swagger 注解,描述该参数的作用和是否必填
@ApiParam(value = "编辑餐饮套餐请求实体", required = true)
// 接收编辑餐饮套餐的请求实体,从请求体中获取数据
@RequestBody OperateCateringSetQuery operateCateringSetQuery,
// 使用 Swagger 注解,描述该参数的作用和是否必填
@ApiParam(value = "接口访问请求头", required = true)
// 从请求头中获取令牌,用于身份验证
@RequestHeader String token) {
// 调用餐饮套餐服务类的编辑餐饮套餐方法,并返回结果
return cateringSetService.editCateringSet(operateCateringSetQuery);
}
/**
*
*
* @param setId
* @param token 访访
* @return Result
*/
// 处理 HTTP DELETE 请求,请求路径为 /cateringSet/deleteCateringSet
@DeleteMapping("/deleteCateringSet")
// 使用 Swagger 注解,描述该接口的功能和开发者信息
@ApiOperation(value = "删除餐饮套餐", notes = Constant.DEVELOPER + Constant.EMPEROR_WEN)
public Result deleteCateringSet(@ApiParam(value = "删除餐饮套餐请求参数", required = true) @RequestParam Long setId,
@ApiParam(value = "接口访问请求头", required = true) @RequestHeader String token) {
public Result deleteCateringSet(
// 使用 Swagger 注解,描述该参数的作用和是否必填
@ApiParam(value = "删除餐饮套餐请求参数", required = true)
// 接收要删除的餐饮套餐编号
@RequestParam Long setId,
// 使用 Swagger 注解,描述该参数的作用和是否必填
@ApiParam(value = "接口访问请求头", required = true)
// 从请求头中获取令牌,用于身份验证
@RequestHeader String token) {
// 调用餐饮套餐服务类的删除餐饮套餐方法,并返回结果
return cateringSetService.deleteCateringSet(setId);
}
}
}

@ -1,33 +1,67 @@
// 声明该类所属的包,用于组织代码结构,标识该类在项目中的位置
package com.ew.gerocomium.controller;
// 导入自定义的常量类,该类可能包含项目中通用的常量信息,如开发者标识等
import com.ew.gerocomium.common.constant.Constant;
// 导入自定义的基础结果类,用于封装接口返回的结果,通常包含操作状态、消息和数据等
import com.ew.gerocomium.dao.base.Result;
// 导入分页查询消费记录的请求实体类,用于封装查询条件和分页信息
import com.ew.gerocomium.dao.query.PageConsumeByKeyQuery;
// 导入分页查询预存充值的请求实体类(此处未使用,可能是多余导入)
import com.ew.gerocomium.dao.query.PageDepositRechargeByKeyQuery;
// 导入消费业务逻辑的服务类,负责处理与消费相关的具体操作,如查询消费记录等
import com.ew.gerocomium.service.ConsumeService;
// 导入 Swagger 的注解,用于生成 API 文档时对控制器进行分类和描述
import io.swagger.annotations.Api;
// 导入 Swagger 的注解,用于描述 API 操作的详细信息,如操作名称和备注
import io.swagger.annotations.ApiOperation;
// 导入 Swagger 的注解,用于描述 API 参数的详细信息,如参数名称、是否必填等
import io.swagger.annotations.ApiParam;
// 导入 Spring Security 的注解,用于在方法执行前进行权限验证
import org.springframework.security.access.prepost.PreAuthorize;
// 导入 Spring MVC 的注解,用于处理 HTTP 请求,定义请求映射和请求方法等
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
// 导入 Spring 的注解,用于实现依赖注入,将所需的服务类实例注入到当前类中
import javax.annotation.Resource;
/**
* HTTP
*/
// 使用 Swagger 的 @Api 注解,标记该控制器的功能模块为“消费记录”,方便在生成 API 文档时进行分类展示
@Api(tags = "消费记录")
// 使用 Spring MVC 的 @RestController 注解,将该类标记为 RESTful 风格的控制器,用于处理 HTTP 请求并返回 JSON 等格式的响应
@RestController
// 使用 Spring MVC 的 @RequestMapping 注解,定义该控制器处理的请求的基础路径为 /consume该控制器下的所有接口路径都会基于此路径
@RequestMapping("/consume")
// 使用 Spring Security 的 @PreAuthorize 注解,进行权限验证,只有拥有 /fee/record 权限的用户才能访问该控制器的接口,确保系统的安全性
@PreAuthorize("@AuthorityAssert.hasAuthority('/fee/record')")
public class ConsumeController {
// 使用 @Resource 注解,将 ConsumeService 类型的实例注入到当前类中,以便调用其方法处理消费相关业务逻辑
@Resource
private ConsumeService consumeService;
/**
*
*
* @param pageConsumeByKeyQuery
* @param token 访
* @return
*/
// 使用 Spring MVC 的 @GetMapping 注解,处理 HTTP GET 请求,路径为 /consume/pageConsumeByKey
@GetMapping("/pageConsumeByKey")
// 使用 Swagger 的 @ApiOperation 注解,描述该 API 操作的名称为“分页查询消费记录”,并添加备注信息(包含开发者信息)
@ApiOperation(value = "分页查询消费记录", notes = Constant.DEVELOPER + Constant.EMPEROR_WEN)
public Result pageConsumeByKey(@ApiParam(value = "分页查询消费记录请求实体", required = true) PageConsumeByKeyQuery pageConsumeByKeyQuery,
@ApiParam(value = "接口访问请求头", required = true) @RequestHeader String token) {
public Result pageConsumeByKey(
// 使用 Swagger 的 @ApiParam 注解,描述该参数的作用为“分页查询消费记录请求实体”,并标记为必需参数
@ApiParam(value = "分页查询消费记录请求实体", required = true)
PageConsumeByKeyQuery pageConsumeByKeyQuery,
// 使用 Swagger 的 @ApiParam 注解,描述该参数的作用为“接口访问请求头”,并标记为必需参数,同时使用 @RequestHeader 注解从请求头中获取该参数值
@ApiParam(value = "接口访问请求头", required = true) @RequestHeader String token) {
// 调用 ConsumeService 的 pageConsumeByKey 方法,传入分页查询请求实体,执行分页查询消费记录的操作并返回结果
return consumeService.pageConsumeByKey(pageConsumeByKeyQuery);
}
}
}

@ -1,46 +1,110 @@
// 声明该类所属的包,用于组织代码结构,体现代码的模块划分
package com.ew.gerocomium.controller;
// 导入自定义的常量类,该类可能包含项目中的通用常量,如开发者信息等
import com.ew.gerocomium.common.constant.Constant;
// 导入自定义的基础结果类,用于封装接口返回的结果,包含操作状态、消息和数据等
import com.ew.gerocomium.dao.base.Result;
// 导入分页查询预存充值记录的请求实体类,用于封装查询条件和分页信息
import com.ew.gerocomium.dao.query.PageCheckContractByKeyQuery;
// 导入分页查询预存充值的请求实体类,用于封装查询条件和分页信息
import com.ew.gerocomium.dao.query.PageDepositRechargeByKeyQuery;
// 导入分页搜索老人的请求实体类,用于封装搜索条件和分页信息
import com.ew.gerocomium.dao.query.PageSearchElderByKeyQuery;
// 导入入住老人账户充值的请求实体类,用于封装充值相关信息
import com.ew.gerocomium.dao.query.RechargeQuery;
// 导入预存充值业务逻辑的服务类,负责处理预存充值相关的具体业务操作
import com.ew.gerocomium.service.DepositRechargeService;
// 导入 Swagger 的注解,用于生成 API 文档时对控制器进行分类和描述
import io.swagger.annotations.Api;
// 导入 Swagger 的注解,用于描述 API 操作的详细信息,如操作名称和备注
import io.swagger.annotations.ApiOperation;
// 导入 Swagger 的注解,用于描述 API 参数的详细信息,如参数名称、是否必填等
import io.swagger.annotations.ApiParam;
// 导入 Spring Security 的注解,用于在方法执行前进行权限验证
import org.springframework.security.access.prepost.PreAuthorize;
// 导入 Spring MVC 的注解,用于处理 HTTP 请求,定义请求映射和请求方法等
import org.springframework.web.bind.annotation.*;
// 导入 Spring 的注解,用于实现依赖注入,将所需的服务类实例注入到当前类中
import javax.annotation.Resource;
/**
*
*/
// 使用 Swagger 的 @Api 注解,标记该控制器的标签为“预存充值”,方便在生成 API 文档时进行分类展示
@Api(tags = "预存充值")
// 使用 Spring MVC 的 @RestController 注解,将该类标记为 RESTful 风格的控制器,用于处理 HTTP 请求并返回 JSON 等格式的响应
@RestController
// 使用 Spring MVC 的 @RequestMapping 注解,定义该控制器的基础请求路径为 /depositRecharge该控制器下的所有请求都以此为前缀
@RequestMapping("/depositRecharge")
// 使用 Spring Security 的 @PreAuthorize 注解,进行权限控制,只有拥有指定权限(/fee/pay的用户才能访问该控制器的方法
@PreAuthorize("@AuthorityAssert.hasAuthority('/fee/pay')")
public class DepositRechargeController {
// 使用 @Resource 注解,将 DepositRechargeService 类型的实例注入到当前类中,以便调用其方法处理预存充值相关业务逻辑
@Resource
private DepositRechargeService depositRechargeService;
/**
*
*
* @param pageDepositRechargeByKeyQuery
* @param token 访访
* @return Result
*/
// 使用 Spring MVC 的 @GetMapping 注解,处理 HTTP GET 请求,路径为 /depositRecharge/pageDepositRechargeByKey
@GetMapping("/pageDepositRechargeByKey")
// 使用 Swagger 的 @ApiOperation 注解,描述该 API 操作的名称为“分页查询预存充值”,并添加备注信息(包含开发者信息)
@ApiOperation(value = "分页查询预存充值", notes = Constant.DEVELOPER + Constant.EMPEROR_WEN)
public Result pageDepositRechargeByKey(@ApiParam(value = "分页查询预存充值请求实体", required = true) PageDepositRechargeByKeyQuery pageDepositRechargeByKeyQuery,
@ApiParam(value = "接口访问请求头", required = true) @RequestHeader String token) {
public Result pageDepositRechargeByKey(
// 使用 Swagger 的 @ApiParam 注解,描述该参数的作用为“分页查询预存充值请求实体”,并标记为必需参数
@ApiParam(value = "分页查询预存充值请求实体", required = true)
PageDepositRechargeByKeyQuery pageDepositRechargeByKeyQuery,
// 使用 Swagger 的 @ApiParam 注解,描述该参数的作用为“接口访问请求头”,并标记为必需参数,同时使用 @RequestHeader 注解从请求头中获取该参数值
@ApiParam(value = "接口访问请求头", required = true) @RequestHeader String token) {
// 调用 DepositRechargeService 的 pageDepositRechargeByKey 方法,传入分页查询请求实体,获取分页查询的预存充值记录信息并返回结果
return depositRechargeService.pageDepositRechargeByKey(pageDepositRechargeByKeyQuery);
}
/**
*
*
* @param pageSearchElderByKeyQuery
* @param token 访访
* @return Result
*/
// 使用 Spring MVC 的 @GetMapping 注解,处理 HTTP GET 请求,路径为 /depositRecharge/pageSearchElderByKey
@GetMapping("/pageSearchElderByKey")
// 使用 Swagger 的 @ApiOperation 注解,描述该 API 操作的名称为“分页搜索老人”,并添加备注信息(包含开发者信息)
@ApiOperation(value = "分页搜索老人", notes = Constant.DEVELOPER + Constant.EMPEROR_WEN)
public Result pageSearchElderByKey(@ApiParam(value = "分页搜索老人请求实体", required = true) PageSearchElderByKeyQuery pageSearchElderByKeyQuery,
@ApiParam(value = "接口访问请求头", required = true) @RequestHeader String token) {
public Result pageSearchElderByKey(
// 使用 Swagger 的 @ApiParam 注解,描述该参数的作用为“分页搜索老人请求实体”,并标记为必需参数
@ApiParam(value = "分页搜索老人请求实体", required = true)
PageSearchElderByKeyQuery pageSearchElderByKeyQuery,
// 使用 Swagger 的 @ApiParam 注解,描述该参数的作用为“接口访问请求头”,并标记为必需参数,同时使用 @RequestHeader 注解从请求头中获取该参数值
@ApiParam(value = "接口访问请求头", required = true) @RequestHeader String token) {
// 调用 DepositRechargeService 的 pageSearchElderByKey 方法,传入分页搜索请求实体,获取分页搜索的老人信息并返回结果
return depositRechargeService.pageSearchElderByKey(pageSearchElderByKeyQuery);
}
/**
*
*
* @param rechargeQuery
* @param token 访访
* @return Result
*/
// 使用 Spring MVC 的 @PutMapping 注解,处理 HTTP PUT 请求,路径为 /depositRecharge/recharge
@PutMapping("/recharge")
// 使用 Swagger 的 @ApiOperation 注解,描述该 API 操作的名称为“入住老人账户充值”,并添加备注信息(包含开发者信息)
@ApiOperation(value = "入住老人账户充值", notes = Constant.DEVELOPER + Constant.EMPEROR_WEN)
public Result recharge(@ApiParam(value = "入住老人账户充值请求实体", required = true) @RequestBody RechargeQuery rechargeQuery,
@ApiParam(value = "接口访问请求头", required = true) @RequestHeader String token) {
public Result recharge(
// 使用 Swagger 的 @ApiParam 注解,描述该参数的作用为“入住老人账户充值请求实体”,并标记为必需参数,同时使用 @RequestBody 注解从请求体中获取该参数值
@ApiParam(value = "入住老人账户充值请求实体", required = true) @RequestBody
RechargeQuery rechargeQuery,
// 使用 Swagger 的 @ApiParam 注解,描述该参数的作用为“接口访问请求头”,并标记为必需参数,同时使用 @RequestHeader 注解从请求头中获取该参数值
@ApiParam(value = "接口访问请求头", required = true) @RequestHeader String token) {
// 调用 DepositRechargeService 的 recharge 方法,传入入住老人账户充值请求实体,执行充值操作并返回结果
return depositRechargeService.recharge(rechargeQuery);
}
}
}

@ -1,94 +1,282 @@
// 声明该类所在的包,此包为 com.ew.gerocomium.controller
package com.ew.gerocomium.controller;
// 导入自定义的常量类,一般包含项目里固定的常量信息,像开发者名字等
import com.ew.gerocomium.common.constant.Constant;
// 导入自定义的基础结果类,用于封装接口返回的结果,包含操作是否成功、数据等
import com.ew.gerocomium.dao.base.Result;
// 导入操作菜品的查询实体类,用于封装新增、编辑菜品时的请求数据
import com.ew.gerocomium.dao.query.OperateDishesQuery;
// 导入操作菜品类型的查询实体类,用于封装新增、编辑菜品类型时的请求数据
import com.ew.gerocomium.dao.query.OperateDishesTypeQuery;
// 导入分页查询菜品的查询实体类,用于封装分页查询菜品时的请求数据,含查询条件和分页信息
import com.ew.gerocomium.dao.query.PageDishesByKeyQuery;
// 导入菜品业务逻辑服务类,用于处理菜品相关的具体业务逻辑
import com.ew.gerocomium.service.DishesService;
// 导入 Swagger 相关注解,用于生成 API 文档,方便接口的管理和展示
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
// 导入 Spring Security 的权限控制注解,用于控制对接口的访问权限
import org.springframework.security.access.prepost.PreAuthorize;
// 导入 Spring MVC 相关注解,用于处理 HTTP 请求和定义接口
import org.springframework.web.bind.annotation.*;
// 导入 Spring 的资源注入注解,用于实现依赖注入
import javax.annotation.Resource;
/**
* HTTP
*/
// 使用 Swagger 注解,标记该控制器的标签为“菜品管理”,方便在生成 API 文档时进行分类展示
@Api(tags = "菜品管理")
// 标记该类为 RESTful 风格的控制器,用于处理 HTTP 请求并返回 JSON 等格式的响应
@RestController
// 定义该控制器的基础请求路径为 /dishes该控制器下的所有请求都以此为前缀
@RequestMapping("/dishes")
// 权限控制注解,只有拥有指定权限('/food/dish')的用户才能访问该控制器的方法
@PreAuthorize("@AuthorityAssert.hasAuthority('/food/dish')")
public class DishesController {
// 通过依赖注入的方式获取 DishesService 的实例,用于调用该服务类的方法处理业务逻辑
@Resource
private DishesService dishesService;
/**
*
*
* @param dishesTypeName
* @param token 访
* @return
*/
// 处理 HTTP GET 请求,请求路径为 /dishes/listDishesType用于获取菜品分类列表
@GetMapping("/listDishesType")
// 使用 Swagger 注解描述该接口的功能和开发者信息
@ApiOperation(value = "获取菜品分类", notes = Constant.DEVELOPER + Constant.EMPEROR_WEN)
public Result listDishesType(@ApiParam(value = "获取菜品分类请求参数", required = false) String dishesTypeName,
@ApiParam(value = "接口访问请求头", required = true) @RequestHeader String token) {
public Result listDishesType(
// 使用 Swagger 注解描述该参数的作用,该参数为可选的菜品分类名称,用于筛选特定名称的分类
@ApiParam(value = "获取菜品分类请求参数", required = false)
// 接收菜品分类名称参数
String dishesTypeName,
// 使用 Swagger 注解描述该参数的作用,并标记为必需参数,该参数为接口访问请求头中的令牌
@ApiParam(value = "接口访问请求头", required = true)
// 从请求头中获取令牌
@RequestHeader String token) {
// 调用菜品服务类的获取菜品分类列表方法,并返回操作结果
return dishesService.listDishesType(dishesTypeName);
}
/**
*
*
* @param pageDishesByKeyQuery
* @param token 访
* @return
*/
// 处理 HTTP GET 请求,请求路径为 /dishes/pageDishesByKey用于分页查询菜品信息
@GetMapping("/pageDishesByKey")
// 使用 Swagger 注解描述该接口的功能和开发者信息
@ApiOperation(value = "分页查询菜品", notes = Constant.DEVELOPER + Constant.EMPEROR_WEN)
public Result pageDishesByKey(@ApiParam(value = "分页查询菜品请求实体", required = true) PageDishesByKeyQuery pageDishesByKeyQuery,
@ApiParam(value = "接口访问请求头", required = true) @RequestHeader String token) {
public Result pageDishesByKey(
// 使用 Swagger 注解描述该参数的作用,并标记为必需参数,该参数为分页查询菜品的请求实体
@ApiParam(value = "分页查询菜品请求实体", required = true)
// 接收分页查询菜品的请求实体
PageDishesByKeyQuery pageDishesByKeyQuery,
// 使用 Swagger 注解描述该参数的作用,并标记为必需参数,该参数为接口访问请求头中的令牌
@ApiParam(value = "接口访问请求头", required = true)
// 从请求头中获取令牌
@RequestHeader String token) {
// 调用菜品服务类的分页查询菜品方法,并返回操作结果
return dishesService.pageDishesByKey(pageDishesByKeyQuery);
}
/**
*
*
* @param operateDishesTypeQuery
* @param token 访
* @return
*/
// 处理 HTTP POST 请求,请求路径为 /dishes/addDishesType用于新增菜品分类
@PostMapping("/addDishesType")
// 使用 Swagger 注解描述该接口的功能和开发者信息
@ApiOperation(value = "新增菜品分类", notes = Constant.DEVELOPER + Constant.EMPEROR_WEN)
public Result addDishesType(@ApiParam(value = "新增菜品分类请求实体", required = true) @RequestBody OperateDishesTypeQuery operateDishesTypeQuery,
@ApiParam(value = "接口访问请求头", required = true) @RequestHeader String token) {
public Result addDishesType(
// 使用 Swagger 注解描述该参数的作用,并标记为必需参数,该参数为新增菜品分类的请求实体
@ApiParam(value = "新增菜品分类请求实体", required = true)
// 从请求体中接收新增菜品分类的请求实体
@RequestBody OperateDishesTypeQuery operateDishesTypeQuery,
// 使用 Swagger 注解描述该参数的作用,并标记为必需参数,该参数为接口访问请求头中的令牌
@ApiParam(value = "接口访问请求头", required = true)
// 从请求头中获取令牌
@RequestHeader String token) {
// 调用菜品服务类的新增菜品分类方法,并返回操作结果
return dishesService.addDishesType(operateDishesTypeQuery);
}
/**
*
*
* @param dishesTypeId
* @param token 访
* @return
*/
// 处理 HTTP GET 请求,请求路径为 /dishes/getDishesTypeById用于根据编号查询菜品分类信息
@GetMapping("/getDishesTypeById")
// 使用 Swagger 注解描述该接口的功能和开发者信息
@ApiOperation(value = "根据编号查询菜品分类", notes = Constant.DEVELOPER + Constant.EMPEROR_WEN)
public Result getDishesTypeById(@ApiParam(value = "根据编号查询菜品分类请求参数", required = true) @RequestParam Long dishesTypeId,
@ApiParam(value = "接口访问请求头", required = true) @RequestHeader String token) {
public Result getDishesTypeById(
// 使用 Swagger 注解描述该参数的作用,并标记为必需参数,该参数为要查询的菜品分类的编号
@ApiParam(value = "根据编号查询菜品分类请求参数", required = true)
// 从请求参数中获取菜品分类编号
@RequestParam Long dishesTypeId,
// 使用 Swagger 注解描述该参数的作用,并标记为必需参数,该参数为接口访问请求头中的令牌
@ApiParam(value = "接口访问请求头", required = true)
// 从请求头中获取令牌
@RequestHeader String token) {
// 调用菜品服务类的根据编号查询菜品分类方法,并返回操作结果
return dishesService.getDishesTypeById(dishesTypeId);
}
/**
*
*
* @param operateDishesTypeQuery
* @param token 访
* @return
*/
// 处理 HTTP PUT 请求,请求路径为 /dishes/editDishesType用于编辑菜品分类信息
@PutMapping("/editDishesType")
// 使用 Swagger 注解描述该接口的功能和开发者信息
@ApiOperation(value = "编辑菜品分类", notes = Constant.DEVELOPER + Constant.EMPEROR_WEN)
public Result editDishesType(@ApiParam(value = "编辑菜品分类请求实体", required = true) @RequestBody OperateDishesTypeQuery operateDishesTypeQuery,
@ApiParam(value = "接口访问请求头", required = true) @RequestHeader String token) {
public Result editDishesType(
// 使用 Swagger 注解描述该参数的作用,并标记为必需参数,该参数为编辑菜品分类的请求实体
@ApiParam(value = "编辑菜品分类请求实体", required = true)
// 从请求体中接收编辑菜品分类的请求实体
@RequestBody OperateDishesTypeQuery operateDishesTypeQuery,
// 使用 Swagger 注解描述该参数的作用,并标记为必需参数,该参数为接口访问请求头中的令牌
@ApiParam(value = "接口访问请求头", required = true)
// 从请求头中获取令牌
@RequestHeader String token) {
// 调用菜品服务类的编辑菜品分类方法,并返回操作结果
return dishesService.editDishesType(operateDishesTypeQuery);
}
/**
*
*
* @param dishesTypeId
* @param token 访
* @return
*/
// 处理 HTTP DELETE 请求,请求路径为 /dishes/deleteDishesType用于删除菜品分类信息
@DeleteMapping("/deleteDishesType")
// 使用 Swagger 注解描述该接口的功能和开发者信息
@ApiOperation(value = "删除菜品分类", notes = Constant.DEVELOPER + Constant.EMPEROR_WEN)
public Result deleteDishesType(@ApiParam(value = "删除菜品分类请求参数", required = true) @RequestParam Long dishesTypeId,
@ApiParam(value = "接口访问请求头", required = true) @RequestHeader String token) {
public Result deleteDishesType(
// 使用 Swagger 注解描述该参数的作用,并标记为必需参数,该参数为要删除的菜品分类的编号
@ApiParam(value = "删除菜品分类请求参数", required = true)
// 从请求参数中获取菜品分类编号
@RequestParam Long dishesTypeId,
// 使用 Swagger 注解描述该参数的作用,并标记为必需参数,该参数为接口访问请求头中的令牌
@ApiParam(value = "接口访问请求头", required = true)
// 从请求头中获取令牌
@RequestHeader String token) {
// 调用菜品服务类的删除菜品分类方法,并返回操作结果
return dishesService.deleteDishesType(dishesTypeId);
}
/**
*
*
* @param operateDishesQuery
* @param token 访
* @return
*/
// 处理 HTTP POST 请求,请求路径为 /dishes/addDishes用于新增菜品信息
@PostMapping("/addDishes")
// 使用 Swagger 注解描述该接口的功能和开发者信息
@ApiOperation(value = "新增菜品", notes = Constant.DEVELOPER + Constant.EMPEROR_WEN)
public Result addDishes(@ApiParam(value = "新增菜品请求实体", required = true) @RequestBody OperateDishesQuery operateDishesQuery,
@ApiParam(value = "接口访问请求头", required = true) @RequestHeader String token) {
public Result addDishes(
// 使用 Swagger 注解描述该参数的作用,并标记为必需参数,该参数为新增菜品的请求实体
@ApiParam(value = "新增菜品请求实体", required = true)
// 从请求体中接收新增菜品的请求实体
@RequestBody OperateDishesQuery operateDishesQuery,
// 使用 Swagger 注解描述该参数的作用,并标记为必需参数,该参数为接口访问请求头中的令牌
@ApiParam(value = "接口访问请求头", required = true)
// 从请求头中获取令牌
@RequestHeader String token) {
// 调用菜品服务类的新增菜品方法,并返回操作结果
return dishesService.addDishes(operateDishesQuery);
}
/**
*
*
* @param dishesId
* @param token 访
* @return
*/
// 处理 HTTP GET 请求,请求路径为 /dishes/getDishesById用于根据编号查询菜品信息
@GetMapping("/getDishesById")
// 使用 Swagger 注解描述该接口的功能和开发者信息
@ApiOperation(value = "根据编号查询菜品", notes = Constant.DEVELOPER + Constant.EMPEROR_WEN)
public Result getDishesById(@ApiParam(value = "根据编号查询菜品请求参数", required = true) @RequestParam Long dishesId,
@ApiParam(value = "接口访问请求头", required = true) @RequestHeader String token) {
public Result getDishesById(
// 使用 Swagger 注解描述该参数的作用,并标记为必需参数,该参数为要查询的菜品的编号
@ApiParam(value = "根据编号查询菜品请求参数", required = true)
// 从请求参数中获取菜品编号
@RequestParam Long dishesId,
// 使用 Swagger 注解描述该参数的作用,并标记为必需参数,该参数为接口访问请求头中的令牌
@ApiParam(value = "接口访问请求头", required = true)
// 从请求头中获取令牌
@RequestHeader String token) {
// 调用菜品服务类的根据编号查询菜品方法,并返回操作结果
return dishesService.getDishesById(dishesId);
}
/**
*
*
* @param operateDishesQuery
* @param token 访
* @return
*/
// 处理 HTTP PUT 请求,请求路径为 /dishes/editDishes用于编辑菜品信息
@PutMapping("/editDishes")
// 使用 Swagger 注解描述该接口的功能和开发者信息
@ApiOperation(value = "编辑菜品", notes = Constant.DEVELOPER + Constant.EMPEROR_WEN)
public Result editDishes(@ApiParam(value = "编辑菜品请求实体", required = true) @RequestBody OperateDishesQuery operateDishesQuery,
@ApiParam(value = "接口访问请求头", required = true) @RequestHeader String token) {
public Result editDishes(
// 使用 Swagger 注解描述该参数的作用,并标记为必需参数,该参数为编辑菜品的请求实体
@ApiParam(value = "编辑菜品请求实体", required = true)
// 从请求体中接收编辑菜品的请求实体
@RequestBody OperateDishesQuery operateDishesQuery,
// 使用 Swagger 注解描述该参数的作用,并标记为必需参数,该参数为接口访问请求头中的令牌
@ApiParam(value = "接口访问请求头", required = true)
// 从请求头中获取令牌
@RequestHeader String token) {
// 调用菜品服务类的编辑菜品方法,并返回操作结果
return dishesService.editDishes(operateDishesQuery);
}
/**
*
*
* @param dishesId
* @param token 访
* @return
*/
// 处理 HTTP DELETE 请求,请求路径为 /dishes/deleteDishes用于删除菜品信息
@DeleteMapping("/deleteDishes")
// 使用 Swagger 注解描述该接口的功能和开发者信息
@ApiOperation(value = "删除菜品", notes = Constant.DEVELOPER + Constant.EMPEROR_WEN)
public Result deleteDishes(@ApiParam(value = "删除菜品请求参数", required = true) @RequestParam Long dishesId,
@ApiParam(value = "接口访问请求头", required = true) @RequestHeader String token) {
public Result deleteDishes(
// 使用 Swagger 注解描述该参数的作用,并标记为必需参数,该参数为要删除的菜品的编号
@ApiParam(value = "删除菜品请求参数", required = true)
// 从请求参数中获取菜品编号
@RequestParam Long dishesId,
// 使用 Swagger 注解描述该参数的作用,并标记为必需参数,该参数为接口访问请求头中的令牌
@ApiParam(value = "接口访问请求头", required = true)
// 从请求头中获取令牌
@RequestHeader String token) {
// 调用菜品服务类的删除菜品方法,并返回操作结果
return dishesService.deleteDishes(dishesId);
}
}
}

@ -1,86 +1,225 @@
// 声明该类所属的包,方便代码的组织和管理
package com.ew.gerocomium.controller;
// 导入自定义的常量类,通常用于存放一些通用的常量信息
import com.ew.gerocomium.common.constant.Constant;
// 导入自定义的基础结果类,用于封装接口返回的结果,一般包含状态码、消息和数据等信息
import com.ew.gerocomium.dao.base.Result;
// 导入多个查询类,这些类用于封装不同业务操作的查询条件和参数
import com.ew.gerocomium.dao.query.*;
// 导入押金充值服务类,用于处理与押金充值相关的业务逻辑
import com.ew.gerocomium.service.DepositRechargeService;
// 导入菜品服务类,用于处理与菜品相关的业务逻辑
import com.ew.gerocomium.service.DishesService;
// 导入护理预定服务类,用于处理与护理预定相关的业务逻辑
import com.ew.gerocomium.service.NurseReserveService;
// 导入订单服务类,用于处理与订单相关的业务逻辑
import com.ew.gerocomium.service.OrderService;
// 导入 Swagger 注解,用于在生成 API 文档时对控制器进行分类和说明
import io.swagger.annotations.Api;
// 导入 Swagger 注解,用于描述 API 操作的详细信息,如操作名称和备注
import io.swagger.annotations.ApiOperation;
// 导入 Swagger 注解,用于描述 API 参数的详细信息,如参数名称、是否必填等
import io.swagger.annotations.ApiParam;
// 导入 Spring Security 注解,用于在方法执行前进行权限验证
import org.springframework.security.access.prepost.PreAuthorize;
// 导入 Spring MVC 注解,用于处理 HTTP 请求
import org.springframework.web.bind.annotation.*;
// 导入 Spring 注解,用于实现依赖注入
import javax.annotation.Resource;
/**
*
*
*
*/
// 使用 Swagger 的 @Api 注解,标记该控制器的标签为“点餐”,方便在 API 文档中分类展示
@Api(tags = "点餐")
// 使用 Spring MVC 的 @RestController 注解,将该类标记为 RESTful 风格的控制器,处理 HTTP 请求并返回 JSON 格式的响应
@RestController
// 使用 Spring MVC 的 @RequestMapping 注解,定义该控制器的基础请求路径为 /order
@RequestMapping("/order")
// 使用 Spring Security 的 @PreAuthorize 注解,进行权限控制,只有拥有 '/food/order' 权限的用户才能访问该控制器的方法
@PreAuthorize("@AuthorityAssert.hasAuthority('/food/order')")
public class OrderController {
// 使用 @Resource 注解,将 OrderService 实例注入到当前类中,用于处理订单相关的业务逻辑
@Resource
private OrderService orderService;
// 使用 @Resource 注解,将 DepositRechargeService 实例注入到当前类中,用于处理押金充值相关的业务逻辑
@Resource
private DepositRechargeService depositRechargeService;
// 使用 @Resource 注解,将 DishesService 实例注入到当前类中,用于处理菜品相关的业务逻辑
@Resource
private DishesService dishesService;
// 使用 @Resource 注解,将 NurseReserveService 实例注入到当前类中,用于处理护理预定相关的业务逻辑
@Resource
private NurseReserveService nurseReserveService;
/**
*
*
* @param pageOrderByKeyQuery
* @param token 访访
* @return Result
*/
// 使用 Spring MVC 的 @GetMapping 注解,处理 HTTP GET 请求,路径为 /order/pageOrderByKey
@GetMapping("/pageOrderByKey")
// 使用 Swagger 的 @ApiOperation 注解,描述该 API 操作的名称和备注信息
@ApiOperation(value = "分页查询点餐", notes = Constant.DEVELOPER + Constant.EMPEROR_WEN)
public Result pageOrderByKey(@ApiParam(value = "分页查询点餐请求实体", required = true) PageOrderByKeyQuery pageOrderByKeyQuery,
@ApiParam(value = "接口访问请求头", required = true) @RequestHeader String token) {
public Result pageOrderByKey(
// 使用 Swagger 的 @ApiParam 注解,描述参数的作用和是否必填
@ApiParam(value = "分页查询点餐请求实体", required = true)
PageOrderByKeyQuery pageOrderByKeyQuery,
// 使用 Swagger 的 @ApiParam 注解,描述参数的作用和是否必填,并使用 @RequestHeader 注解从请求头中获取参数值
@ApiParam(value = "接口访问请求头", required = true) @RequestHeader String token) {
// 调用 OrderService 的 pageOrderByKey 方法进行分页查询,并返回结果
return orderService.pageOrderByKey(pageOrderByKeyQuery);
}
/**
*
*
* @param pageSearchElderByKeyQuery
* @param token 访访
* @return Result
*/
// 使用 Spring MVC 的 @GetMapping 注解,处理 HTTP GET 请求,路径为 /order/pageSearchElderByKey
@GetMapping("/pageSearchElderByKey")
// 使用 Swagger 的 @ApiOperation 注解,描述该 API 操作的名称和备注信息
@ApiOperation(value = "分页搜索老人", notes = Constant.DEVELOPER + Constant.EMPEROR_WEN)
public Result pageSearchElderByKey(@ApiParam(value = "分页搜索老人请求实体", required = true) PageSearchElderByKeyQuery pageSearchElderByKeyQuery,
@ApiParam(value = "接口访问请求头", required = true) @RequestHeader String token) {
public Result pageSearchElderByKey(
// 使用 Swagger 的 @ApiParam 注解,描述参数的作用和是否必填
@ApiParam(value = "分页搜索老人请求实体", required = true)
PageSearchElderByKeyQuery pageSearchElderByKeyQuery,
// 使用 Swagger 的 @ApiParam 注解,描述参数的作用和是否必填,并使用 @RequestHeader 注解从请求头中获取参数值
@ApiParam(value = "接口访问请求头", required = true) @RequestHeader String token) {
// 调用 DepositRechargeService 的 pageSearchElderByKey 方法进行分页搜索,并返回结果
return depositRechargeService.pageSearchElderByKey(pageSearchElderByKeyQuery);
}
/**
*
*
* @param dishesTypeName null
* @param token 访访
* @return Result
*/
// 使用 Spring MVC 的 @GetMapping 注解,处理 HTTP GET 请求,路径为 /order/listDishesType
@GetMapping("/listDishesType")
// 使用 Swagger 的 @ApiOperation 注解,描述该 API 操作的名称和备注信息
@ApiOperation(value = "获取菜品分类", notes = Constant.DEVELOPER + Constant.EMPEROR_WEN)
public Result listDishesType(@ApiParam(value = "获取菜品分类请求参数", required = false) String dishesTypeName,
@ApiParam(value = "接口访问请求头", required = true) @RequestHeader String token) {
public Result listDishesType(
// 使用 Swagger 的 @ApiParam 注解,描述参数的作用和是否必填
@ApiParam(value = "获取菜品分类请求参数", required = false)
String dishesTypeName,
// 使用 Swagger 的 @ApiParam 注解,描述参数的作用和是否必填,并使用 @RequestHeader 注解从请求头中获取参数值
@ApiParam(value = "接口访问请求头", required = true) @RequestHeader String token) {
// 调用 DishesService 的 listDishesType 方法获取菜品分类列表,并返回结果
return dishesService.listDishesType(dishesTypeName);
}
/**
*
*
* @param pageDishesByKeyQuery
* @param token 访访
* @return Result
*/
// 使用 Spring MVC 的 @GetMapping 注解,处理 HTTP GET 请求,路径为 /order/pageDishesByKey
@GetMapping("/pageDishesByKey")
// 使用 Swagger 的 @ApiOperation 注解,描述该 API 操作的名称和备注信息
@ApiOperation(value = "分页查询菜品", notes = Constant.DEVELOPER + Constant.EMPEROR_WEN)
public Result pageDishesByKey(@ApiParam(value = "分页查询菜品请求实体", required = true) PageDishesByKeyQuery pageDishesByKeyQuery,
@ApiParam(value = "接口访问请求头", required = true) @RequestHeader String token) {
public Result pageDishesByKey(
// 使用 Swagger 的 @ApiParam 注解,描述参数的作用和是否必填
@ApiParam(value = "分页查询菜品请求实体", required = true)
PageDishesByKeyQuery pageDishesByKeyQuery,
// 使用 Swagger 的 @ApiParam 注解,描述参数的作用和是否必填,并使用 @RequestHeader 注解从请求头中获取参数值
@ApiParam(value = "接口访问请求头", required = true) @RequestHeader String token) {
// 调用 DishesService 的 pageDishesByKey 方法进行分页查询,并返回结果
return dishesService.pageDishesByKey(pageDishesByKeyQuery);
}
/**
*
*
* @param addOrderQuery
* @param token 访访
* @return Result
*/
// 使用 Spring MVC 的 @PostMapping 注解,处理 HTTP POST 请求,路径为 /order/addOrder
@PostMapping("/addOrder")
// 使用 Swagger 的 @ApiOperation 注解,描述该 API 操作的名称和备注信息
@ApiOperation(value = "新增点餐", notes = Constant.DEVELOPER + Constant.EMPEROR_WEN)
public Result addOrder(@ApiParam(value = "新增点餐请求实体", required = true) @RequestBody AddOrderQuery addOrderQuery,
@ApiParam(value = "接口访问请求头", required = true) @RequestHeader String token) {
public Result addOrder(
// 使用 Swagger 的 @ApiParam 注解,描述参数的作用和是否必填,并使用 @RequestBody 注解从请求体中获取参数值
@ApiParam(value = "新增点餐请求实体", required = true) @RequestBody
AddOrderQuery addOrderQuery,
// 使用 Swagger 的 @ApiParam 注解,描述参数的作用和是否必填,并使用 @RequestHeader 注解从请求头中获取参数值
@ApiParam(value = "接口访问请求头", required = true) @RequestHeader String token) {
// 调用 OrderService 的 addOrder 方法新增点餐记录,并返回结果
return orderService.addOrder(addOrderQuery);
}
/**
*
*
* @param orderId
* @param token 访访
* @return Result
*/
// 使用 Spring MVC 的 @GetMapping 注解,处理 HTTP GET 请求,路径为 /order/getOrderById
@GetMapping("/getOrderById")
// 使用 Swagger 的 @ApiOperation 注解,描述该 API 操作的名称和备注信息
@ApiOperation(value = "根据编号获取点餐", notes = Constant.DEVELOPER + Constant.EMPEROR_WEN)
public Result getOrderById(@ApiParam(value = "根据编号获取点餐请求参数", required = true) @RequestParam Long orderId,
@ApiParam(value = "接口访问请求头", required = true) @RequestHeader String token) {
public Result getOrderById(
// 使用 Swagger 的 @ApiParam 注解,描述参数的作用和是否必填,并使用 @RequestParam 注解从请求参数中获取参数值
@ApiParam(value = "根据编号获取点餐请求参数", required = true) @RequestParam
Long orderId,
// 使用 Swagger 的 @ApiParam 注解,描述参数的作用和是否必填,并使用 @RequestHeader 注解从请求头中获取参数值
@ApiParam(value = "接口访问请求头", required = true) @RequestHeader String token) {
// 调用 OrderService 的 getOrderById 方法获取点餐记录信息,并返回结果
return orderService.getOrderById(orderId);
}
/**
*
*
* @param token 访访
* @return Result
*/
// 使用 Spring MVC 的 @GetMapping 注解,处理 HTTP GET 请求,路径为 /order/listNurseStaff
@GetMapping("/listNurseStaff")
// 使用 Swagger 的 @ApiOperation 注解,描述该 API 操作的名称和备注信息
@ApiOperation(value = "护理人员", notes = Constant.DEVELOPER + Constant.EMPEROR_WEN)
public Result listNurseStaff(@ApiParam(value = "接口访问请求头", required = true) @RequestHeader String token) {
public Result listNurseStaff(
// 使用 Swagger 的 @ApiParam 注解,描述参数的作用和是否必填,并使用 @RequestHeader 注解从请求头中获取参数值
@ApiParam(value = "接口访问请求头", required = true) @RequestHeader String token) {
// 调用 NurseReserveService 的 listNurseStaff 方法获取护理人员列表,并返回结果
return nurseReserveService.listNurseStaff();
}
/**
*
*
* @param sendOrderQuery
* @param token 访访
* @return Result
*/
// 使用 Spring MVC 的 @PutMapping 注解,处理 HTTP PUT 请求,路径为 /order/sendOrder
@PutMapping("/sendOrder")
// 使用 Swagger 的 @ApiOperation 注解,描述该 API 操作的名称和备注信息
@ApiOperation(value = "送餐", notes = Constant.DEVELOPER + Constant.EMPEROR_WEN)
public Result sendOrder(@ApiParam(value = "送餐请求实体", required = true) @RequestBody SendOrderQuery sendOrderQuery,
@ApiParam(value = "接口访问请求头", required = true) @RequestHeader String token) {
public Result sendOrder(
// 使用 Swagger 的 @ApiParam 注解,描述参数的作用和是否必填,并使用 @RequestBody 注解从请求体中获取参数值
@ApiParam(value = "送餐请求实体", required = true) @RequestBody
SendOrderQuery sendOrderQuery,
// 使用 Swagger 的 @ApiParam 注解,描述参数的作用和是否必填,并使用 @RequestHeader 注解从请求头中获取参数值
@ApiParam(value = "接口访问请求头", required = true) @RequestHeader String token) {
// 调用 OrderService 的 sendOrder 方法进行送餐操作,并返回结果
return orderService.sendOrder(sendOrderQuery);
}
}
}

@ -1,44 +1,103 @@
// 声明该类所在的包,用于组织和管理项目中的类,这里表明是与退住费用审核相关的控制器类所在的包
package com.ew.gerocomium.controller;
// 导入项目自定义的常量类,通常包含一些通用的项目常量,如开发者信息等
import com.ew.gerocomium.common.constant.Constant;
// 导入项目自定义的基础结果类,用于封装接口返回的数据,一般包含状态码、消息和具体的数据内容
import com.ew.gerocomium.dao.base.Result;
// 导入审核老人费用的查询实体类,用于封装审核老人费用相关操作的参数
import com.ew.gerocomium.dao.query.AuditElderFeeQuery;
// 导入分页查询退住审核的查询实体类,用于封装分页查询退住审核信息时的条件和分页信息
import com.ew.gerocomium.dao.query.PageRetreatAuditQuery;
// 导入退住审核服务类,该类包含处理退住费用审核相关业务逻辑的方法
import com.ew.gerocomium.service.RetreatAuditService;
// 导入 Swagger 注解,用于标记 API 分组,方便在生成 API 文档时对接口进行分类展示
import io.swagger.annotations.Api;
// 导入 Swagger 注解,用于描述 API 操作的详细信息,如操作名称和备注
import io.swagger.annotations.ApiOperation;
// 导入 Swagger 注解,用于描述 API 参数的详细信息,如参数名称、是否必填等
import io.swagger.annotations.ApiParam;
// 导入 Spring Security 注解,用于在方法执行前进行权限验证,确保只有具有指定权限的用户才能访问接口
import org.springframework.security.access.prepost.PreAuthorize;
// 导入 Spring MVC 注解,用于处理 HTTP 请求,将类标记为 RESTful 风格的控制器
import org.springframework.web.bind.annotation.*;
// 导入 Spring 注解,用于实现依赖注入,将所需的服务类实例注入到当前类中
import javax.annotation.Resource;
/**
* 退退 HTTP
*/
// 使用 Swagger 的 @Api 注解,标记该控制器的 API 分组为“退住费用审核”,方便在 API 文档中展示
@Api(tags = "退住费用审核")
// 使用 Spring MVC 的 @RestController 注解,将该类标记为 RESTful 风格的控制器,用于处理 HTTP 请求并返回 JSON 格式的数据
@RestController
// 使用 Spring MVC 的 @RequestMapping 注解,指定该控制器处理的请求的基础路径为 "/retreatAudit"
@RequestMapping("/retreatAudit")
// 使用 Spring Security 的 @PreAuthorize 注解,进行权限验证,只有拥有 '/fee/audit' 权限的用户才能访问该控制器的方法
@PreAuthorize("@AuthorityAssert.hasAuthority('/fee/audit')")
public class RetreatAuditController {
// 使用 @Resource 注解,将 RetreatAuditService 类型的实例注入到当前类中,以便调用其方法处理业务逻辑
@Resource
private RetreatAuditService retreatAuditService;
/**
* 退
*
* @param pageRetreatAuditQuery 退
* @param token 访
* @return Result
*/
// 使用 Spring MVC 的 @GetMapping 注解,指定该方法处理 HTTP GET 请求,路径为 "/retreatAudit/pageRetreatAuditByKey"
@GetMapping("/pageRetreatAuditByKey")
// 使用 Swagger 的 @ApiOperation 注解,描述该 API 操作的名称为“分页查询退住审核”,并添加备注信息(包含开发者信息)
@ApiOperation(value = "分页查询退住审核", notes = Constant.DEVELOPER + Constant.EMPEROR_WEN)
public Result pageRetreatAuditByKey(@ApiParam(value = "分页查询退住审核请求实体", required = true) PageRetreatAuditQuery pageRetreatAuditQuery,
@ApiParam(value = "接口访问请求头", required = true) @RequestHeader String token) {
public Result pageRetreatAuditByKey(
// 使用 Swagger 的 @ApiParam 注解,描述该参数的作用为“分页查询退住审核请求实体”,并标记为必需参数
@ApiParam(value = "分页查询退住审核请求实体", required = true)
PageRetreatAuditQuery pageRetreatAuditQuery,
// 使用 Swagger 的 @ApiParam 注解,描述该参数的作用为“接口访问请求头”,并标记为必需参数,同时使用 @RequestHeader 注解从请求头中获取该参数值
@ApiParam(value = "接口访问请求头", required = true) @RequestHeader String token) {
// 调用退住审核服务类的 pageRetreatAuditByKey 方法,传入分页查询请求实体,获取分页查询结果并以 Result 对象形式返回
return retreatAuditService.pageRetreatAuditByKey(pageRetreatAuditQuery);
}
/**
*
*
* @param elderId
* @param token 访
* @return Result
*/
// 使用 Spring MVC 的 @GetMapping 注解,指定该方法处理 HTTP GET 请求,路径为 "/retreatAudit/getElderFeeById"
@GetMapping("/getElderFeeById")
// 使用 Swagger 的 @ApiOperation 注解,描述该 API 操作的名称为“根据编号获取老人费用详情”,并添加备注信息(包含开发者信息)
@ApiOperation(value = "根据编号获取老人费用详情", notes = Constant.DEVELOPER + Constant.EMPEROR_WEN)
public Result getElderFeeById(@ApiParam(value = "根据获取老人费用详情请求参数", required = true) @RequestParam Long elderId,
@ApiParam(value = "接口访问请求头", required = true) @RequestHeader String token) {
public Result getElderFeeById(
// 使用 Swagger 的 @ApiParam 注解,描述该参数的作用为“根据获取老人费用详情请求参数”,并标记为必需参数,同时使用 @RequestParam 注解从请求参数中获取该参数值
@ApiParam(value = "根据获取老人费用详情请求参数", required = true) @RequestParam Long elderId,
// 使用 Swagger 的 @ApiParam 注解,描述该参数的作用为“接口访问请求头”,并标记为必需参数,同时使用 @RequestHeader 注解从请求头中获取该参数值
@ApiParam(value = "接口访问请求头", required = true) @RequestHeader String token) {
// 调用退住审核服务类的 getElderFeeById 方法,传入老人编号,获取老人费用详情并以 Result 对象形式返回
return retreatAuditService.getElderFeeById(elderId);
}
/**
*
*
* @param auditElderFeeQuery
* @param token 访
* @return Result
*/
// 使用 Spring MVC 的 @PutMapping 注解,指定该方法处理 HTTP PUT 请求,路径为 "/retreatAudit/auditElderFee"
@PutMapping("/auditElderFee")
// 使用 Swagger 的 @ApiOperation 注解,描述该 API 操作的名称为“审核老人费用详情”,并添加备注信息(包含开发者信息)
@ApiOperation(value = "审核老人费用详情", notes = Constant.DEVELOPER + Constant.EMPEROR_WEN)
public Result auditElderFee(@ApiParam(value = "审核老人费用详情请求实体", required = true) @RequestBody AuditElderFeeQuery auditElderFeeQuery,
@ApiParam(value = "接口访问请求头", required = true) @RequestHeader String token) {
public Result auditElderFee(
// 使用 Swagger 的 @ApiParam 注解,描述该参数的作用为“审核老人费用详情请求实体”,并标记为必需参数,同时使用 @RequestBody 注解从请求体中获取该参数值
@ApiParam(value = "审核老人费用详情请求实体", required = true) @RequestBody AuditElderFeeQuery auditElderFeeQuery,
// 使用 Swagger 的 @ApiParam 注解,描述该参数的作用为“接口访问请求头”,并标记为必需参数,同时使用 @RequestHeader 注解从请求头中获取该参数值
@ApiParam(value = "接口访问请求头", required = true) @RequestHeader String token) {
// 调用退住审核服务类的 auditElderFee 方法,传入审核请求实体,执行审核操作并将结果以 Result 对象形式返回
return retreatAuditService.auditElderFee(auditElderFeeQuery);
}
}
}
Loading…
Cancel
Save