diff --git a/.idea/workspace.xml b/.idea/workspace.xml index c3a830d..481517b 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -1,7 +1,12 @@ + + - + + + - + { + "customColor": "", + "associatedIndex": 1 +} @@ -57,6 +63,7 @@ diff --git a/client/src/layout/assets/imgs/bg.png b/client/src/layout/assets/imgs/bg.png new file mode 100644 index 0000000..8cdd300 Binary files /dev/null and b/client/src/layout/assets/imgs/bg.png differ diff --git a/client/src/layout/assets/imgs/computer.svg b/client/src/layout/assets/imgs/computer.svg new file mode 100644 index 0000000..a03b08a --- /dev/null +++ b/client/src/layout/assets/imgs/computer.svg @@ -0,0 +1 @@ + diff --git a/client/src/layout/assets/imgs/img.png b/client/src/layout/assets/imgs/img.png new file mode 100644 index 0000000..61d17f8 Binary files /dev/null and b/client/src/layout/assets/imgs/img.png differ diff --git a/client/src/layout/assets/imgs/img_1.png b/client/src/layout/assets/imgs/img_1.png new file mode 100644 index 0000000..e90504d Binary files /dev/null and b/client/src/layout/assets/imgs/img_1.png differ diff --git a/client/src/layout/assets/imgs/img_2.png b/client/src/layout/assets/imgs/img_2.png new file mode 100644 index 0000000..cab43ff Binary files /dev/null and b/client/src/layout/assets/imgs/img_2.png differ diff --git a/client/src/layout/assets/imgs/logo.png b/client/src/layout/assets/imgs/logo.png new file mode 100644 index 0000000..05634bd Binary files /dev/null and b/client/src/layout/assets/imgs/logo.png differ diff --git a/client/src/layout/assets/imgs/man.png b/client/src/layout/assets/imgs/man.png new file mode 100644 index 0000000..12336c9 Binary files /dev/null and b/client/src/layout/assets/imgs/man.png differ diff --git a/client/src/layout/assets/imgs/women.png b/client/src/layout/assets/imgs/women.png new file mode 100644 index 0000000..0af6e75 Binary files /dev/null and b/client/src/layout/assets/imgs/women.png differ diff --git a/client/src/layout/components/Grid/components/GridItem.vue b/client/src/layout/components/Grid/components/GridItem.vue new file mode 100644 index 0000000..7e5389e --- /dev/null +++ b/client/src/layout/components/Grid/components/GridItem.vue @@ -0,0 +1,109 @@ + + + diff --git a/client/src/layout/components/Grid/index.vue b/client/src/layout/components/Grid/index.vue new file mode 100644 index 0000000..fa2858b --- /dev/null +++ b/client/src/layout/components/Grid/index.vue @@ -0,0 +1,207 @@ + + + diff --git a/client/src/layout/components/Grid/interface/index.ts b/client/src/layout/components/Grid/interface/index.ts new file mode 100644 index 0000000..a3ff77d --- /dev/null +++ b/client/src/layout/components/Grid/interface/index.ts @@ -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; +}; diff --git a/client/src/layout/components/IconPark/index.vue b/client/src/layout/components/IconPark/index.vue new file mode 100644 index 0000000..9ca92a5 --- /dev/null +++ b/client/src/layout/components/IconPark/index.vue @@ -0,0 +1,45 @@ + + + diff --git a/client/src/layout/components/ProTable/components/ColSetting.vue b/client/src/layout/components/ProTable/components/ColSetting.vue new file mode 100644 index 0000000..12323c8 --- /dev/null +++ b/client/src/layout/components/ProTable/components/ColSetting.vue @@ -0,0 +1,88 @@ + + + + + diff --git a/client/src/layout/components/ProTable/components/Pagination.vue b/client/src/layout/components/ProTable/components/Pagination.vue new file mode 100644 index 0000000..7109b8f --- /dev/null +++ b/client/src/layout/components/ProTable/components/Pagination.vue @@ -0,0 +1,47 @@ + + + diff --git a/client/src/layout/components/ProTable/components/TableColumn.vue b/client/src/layout/components/ProTable/components/TableColumn.vue new file mode 100644 index 0000000..233dbf7 --- /dev/null +++ b/client/src/layout/components/ProTable/components/TableColumn.vue @@ -0,0 +1,103 @@ + + + diff --git a/client/src/layout/components/ProTable/index.md b/client/src/layout/components/ProTable/index.md new file mode 100644 index 0000000..1a70ec4 --- /dev/null +++ b/client/src/layout/components/ProTable/index.md @@ -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" | 表头的作用域插槽 | diff --git a/client/src/layout/components/ProTable/index.vue b/client/src/layout/components/ProTable/index.vue new file mode 100644 index 0000000..912cb40 --- /dev/null +++ b/client/src/layout/components/ProTable/index.vue @@ -0,0 +1,397 @@ + + + + + diff --git a/client/src/layout/components/ProTable/interface/index.ts b/client/src/layout/components/ProTable/interface/index.ts new file mode 100644 index 0000000..8d9e597 --- /dev/null +++ b/client/src/layout/components/ProTable/interface/index.ts @@ -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>; + +// 定义一个接口 ColumnProps,用于描述表格列的属性 +// 扩展自 Partial, "children" | "renderHeader" | "renderCell">>,表示部分继承 TableColumnCtx 类型并去除 "children"、"renderHeader" 和 "renderCell" 属性 +export interface ColumnProps + extends Partial, "children" | "renderHeader" | "renderCell">> { + // 是否是标签展示,可选,默认为 undefined,类型为布尔值 + tag?: boolean; + // 是否显示在表格当中,可选,默认为 undefined,类型为布尔值 + isShow?: boolean; + // 搜索项配置,可选,默认为 undefined,类型为 SearchProps 或 undefined + search?: SearchProps | undefined; + // 枚举类型(渲染值的字典),可以是 EnumProps 数组或一个返回 Promise 的函数,可选,默认为 undefined + enum?: EnumProps[] | ((params?: any) => Promise); + // 当前单元格值是否根据 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 数组 + _children?: ColumnProps[]; +} diff --git a/client/src/layout/components/ReImageVerify/index.ts b/client/src/layout/components/ReImageVerify/index.ts new file mode 100644 index 0000000..6b4eca3 --- /dev/null +++ b/client/src/layout/components/ReImageVerify/index.ts @@ -0,0 +1,12 @@ +// 从当前目录下的 src 文件夹中的 index.vue 文件导入组件 +// 这里将导入的组件命名为 reImageVerify,它应该是一个 Vue 组件 +import reImageVerify from './src/index.vue' + +/** 图形验证码组件 */ +// 使用具名导出的方式,将导入的组件以 ReImageVerify 这个名称导出 +// 具名导出允许在导入时指定具体要导入的内容,方便在其他文件中使用 +export const ReImageVerify = reImageVerify + +// 使用默认导出的方式,将导入的组件作为默认导出 +// 默认导出允许在导入时可以使用任意名称来接收该组件,通常一个文件只能有一个默认导出 +export default ReImageVerify diff --git a/client/src/layout/components/ReImageVerify/src/hooks.ts b/client/src/layout/components/ReImageVerify/src/hooks.ts new file mode 100644 index 0000000..898eb05 --- /dev/null +++ b/client/src/layout/components/ReImageVerify/src/hooks.ts @@ -0,0 +1,133 @@ +import { ref, onMounted } from 'vue' + +/** + * 绘制图形验证码 + * @param width - 图形宽度 + * @param height - 图形高度 + */ +// 定义一个名为 useImageVerify 的函数,用于生成和管理图形验证码,接受图形宽度和高度作为参数,有默认值 +export const useImageVerify = (width = 120, height = 40) => { + // 创建一个响应式引用 domRef,用于存储 元素的引用 + const domRef = ref() + // 创建一个响应式变量 imgCode,用于存储生成的图形验证码字符串 + const imgCode = ref('') + + // 定义一个函数 setImgCode,用于设置 imgCode 的值 + function setImgCode(code: string) { + imgCode.value = code + } + + // 定义一个函数 getImgCode,用于在 上绘制验证码并获取生成的验证码字符串 + function getImgCode() { + // 如果 domRef 没有值(即 元素未挂载),则直接返回 + 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,用于在 元素上绘制图形验证码 +function draw(dom: HTMLCanvasElement, width: number, height: number) { + // 初始化一个空字符串 imgCode,用于存储生成的验证码 + let imgCode = '' + + // 定义一个字符串 NUMBER_STRING,包含数字 0 到 9,用于生成验证码字符 + const NUMBER_STRING = '0123456789' + + // 获取 元素的 2D 绘图上下文 ctx + const ctx = dom.getContext('2d') + // 如果获取上下文失败(ctx 为 null),则直接返回空的验证码字符串 + if (!ctx) return imgCode + + // 设置绘图上下文的填充样式为随机颜色(背景色) + ctx.fillStyle = randomColor(180, 230) + // 使用填充样式绘制一个矩形,覆盖整个 区域,作为验证码的背景 + 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 +} diff --git a/client/src/layout/components/ReImageVerify/src/index.vue b/client/src/layout/components/ReImageVerify/src/index.vue new file mode 100644 index 0000000..b8548ec --- /dev/null +++ b/client/src/layout/components/ReImageVerify/src/index.vue @@ -0,0 +1,55 @@ + + + diff --git a/client/src/layout/components/SearchForm/components/SearchFormItem.vue b/client/src/layout/components/SearchForm/components/SearchFormItem.vue new file mode 100644 index 0000000..7e966ba --- /dev/null +++ b/client/src/layout/components/SearchForm/components/SearchFormItem.vue @@ -0,0 +1,117 @@ + + + diff --git a/client/src/layout/components/SearchForm/index.vue b/client/src/layout/components/SearchForm/index.vue new file mode 100644 index 0000000..e587851 --- /dev/null +++ b/client/src/layout/components/SearchForm/index.vue @@ -0,0 +1,142 @@ + + + diff --git a/client/src/layout/components/SvgIcon/index.vue b/client/src/layout/components/SvgIcon/index.vue new file mode 100644 index 0000000..e25234e --- /dev/null +++ b/client/src/layout/components/SvgIcon/index.vue @@ -0,0 +1,49 @@ + + + + + diff --git a/client/src/layout/components/elderListDialog/index.vue b/client/src/layout/components/elderListDialog/index.vue new file mode 100644 index 0000000..172f22c --- /dev/null +++ b/client/src/layout/components/elderListDialog/index.vue @@ -0,0 +1,118 @@ + + + + + diff --git a/client/src/layout/components/my-card/my-card.vue b/client/src/layout/components/my-card/my-card.vue new file mode 100644 index 0000000..d050f98 --- /dev/null +++ b/client/src/layout/components/my-card/my-card.vue @@ -0,0 +1,30 @@ + + + + + diff --git a/client/src/layout/components/treeDialog/index.vue b/client/src/layout/components/treeDialog/index.vue new file mode 100644 index 0000000..c4d327e --- /dev/null +++ b/client/src/layout/components/treeDialog/index.vue @@ -0,0 +1,90 @@ + + + + + diff --git a/client/src/layout/components/upload/image/index.vue b/client/src/layout/components/upload/image/index.vue new file mode 100644 index 0000000..e739333 --- /dev/null +++ b/client/src/layout/components/upload/image/index.vue @@ -0,0 +1,189 @@ + + + diff --git a/client/src/layout/components/wen-test/DynamicAdditionComponent.vue b/client/src/layout/components/wen-test/DynamicAdditionComponent.vue new file mode 100644 index 0000000..c13ef16 --- /dev/null +++ b/client/src/layout/components/wen-test/DynamicAdditionComponent.vue @@ -0,0 +1,47 @@ + + + diff --git a/client/src/layout/hooks/interface/index.ts b/client/src/layout/hooks/interface/index.ts new file mode 100644 index 0000000..05cfd4d --- /dev/null +++ b/client/src/layout/hooks/interface/index.ts @@ -0,0 +1,42 @@ +// 定义一个名为 Table 的命名空间 +export namespace Table { + // 在 Table 命名空间内,定义一个接口 Pageable,用于描述分页相关的属性 + export interface Pageable { + // 当前页码,类型为数字 + pageNum: number + // 每页显示的数量,类型为数字 + pageSize: number + // 数据的总数量,类型为数字 + total: number + } + // 在 Table 命名空间内,定义一个接口 TableStateProps,用于描述表格相关的状态属性 + export interface TableStateProps { + // 表格数据,是一个任意类型的数组 + tableData: any[] + // 分页相关的属性,类型为 Pageable 接口 + pageable: Pageable + // 搜索参数,是一个键值对对象,键为字符串,值为任意类型 + searchParam: { + [key: string]: any + } + // 初始化的搜索参数,是一个键值对对象,键为字符串,值为任意类型 + searchInitParam: { + [key: string]: any + } + // 用于获取总数的参数,是一个键值对对象,键为字符串,值为任意类型 + totalParam: { + [key: string]: any + } + // 可选的图标相关属性,是一个键值对对象,键为字符串,值为任意类型,默认为 undefined + icon?: { + [key: string]: any + } + } +} + +// 定义一个名为 HandleData 的命名空间 +export namespace HandleData { + // 在 HandleData 命名空间内,定义一个类型别名 MessageType + // MessageType 的值可以是空字符串,或者是'success'、'warning'、'info'、'error' 中的一个 + export type MessageType = '' |'success' | 'warning' | 'info' | 'error' +} diff --git a/client/src/layout/hooks/useHandleData.ts b/client/src/layout/hooks/useHandleData.ts new file mode 100644 index 0000000..f4a917a --- /dev/null +++ b/client/src/layout/hooks/useHandleData.ts @@ -0,0 +1,52 @@ +// 从 element-plus 库中导入 ElMessageBox 和 ElMessage 组件,分别用于显示确认框和提示消息 +import { ElMessageBox, ElMessage } from 'element-plus' +// 从 './interface' 文件中导入 HandleData 命名空间,用于获取其中定义的类型 +import { HandleData } from './interface' + +/** + * @description 操作单条数据信息(二次确认【删除、禁用、启用、重置密码】) + * @param {Function} api 操作数据接口的api方法(必传) + * @param {Object} params 携带的操作数据参数 {id,params}(必传) + * @param {String} message 提示信息(必传) + * @param {String} confirmType icon类型(不必传,默认为 warning) + * @return Promise + */ +// 定义一个泛型函数 useHandleData,P 表示 api 函数参数的类型,R 表示 api 函数返回值的类型 +export const useHandleData =

( + // api 是一个函数,接收类型为 P 的参数并返回一个 Promise,Promise 的解析值类型为 R + api: (params: P) => Promise, + // params 是 api 函数的参数,类型与 api 函数的参数类型一致 + params: Parameters[0], + // message 是一个字符串,用于显示确认框中的提示信息 + message: string, + // confirmType 是一个字符串,类型为 HandleData.MessageType,默认值为 'warning' + confirmType: HandleData.MessageType = 'warning' +) => { + // 返回一个 Promise + return new Promise((resolve, reject) => { + // 显示一个确认框 + ElMessageBox.confirm(`是否${message}?`, '温馨提示', { + // 确认按钮的文本 + confirmButtonText: '确定', + // 取消按钮的文本 + cancelButtonText: '取消', + // 确认框的图标类型 + type: confirmType, + // 是否可拖动 + draggable: true + }) + .then(async () => { + // 调用 api 函数并等待其返回结果 + const res = await api(params) + // 如果结果为 falsy 值,则 reject 该 Promise + if (!res) return reject(false) + // 显示一个成功提示消息 + ElMessage({ + type:'success', + message: `${message}成功!` + }) + // resolve 该 Promise,表示操作成功 + resolve(true) + }) + }) +} diff --git a/client/src/layout/hooks/useIcons.ts b/client/src/layout/hooks/useIcons.ts new file mode 100644 index 0000000..a99fbba --- /dev/null +++ b/client/src/layout/hooks/useIcons.ts @@ -0,0 +1,25 @@ +// 从 '@/components/SvgIcon/index.vue' 导入 SvgIcon 组件 +import SvgIcon from '@/components/SvgIcon/index.vue' +// 从 'vue' 导入 h 函数和 defineComponent 函数 +import { h, defineComponent } from 'vue' + +// 定义一个名为 useRenderIcon 的函数,它接受两个参数:iconName(字符串类型)和 attrs(可选的任意类型) +export function useRenderIcon(iconName: string, attrs?: any) { + // 使用 defineComponent 函数定义一个新的组件 + return defineComponent({ + // 新组件的名称为 'SvgIcon' + name: 'SvgIcon', + // 定义组件的渲染函数 + render() { + // 使用 h 函数创建一个 SvgIcon 组件的虚拟节点 + // 第一个参数是要创建的组件(这里是 SvgIcon) + // 第二个参数是一个对象,包含要传递给 SvgIcon 组件的属性 + // 'icon' 属性的值是传入的 iconName + // '...attrs' 表示将 attrs 对象中的所有属性也传递给 SvgIcon 组件 + return h(SvgIcon, { + icon: iconName, + ...attrs + }) + } + }) +} diff --git a/client/src/layout/hooks/useSelection.ts b/client/src/layout/hooks/useSelection.ts new file mode 100644 index 0000000..b3df20a --- /dev/null +++ b/client/src/layout/hooks/useSelection.ts @@ -0,0 +1,51 @@ +import { ref, computed } from 'vue' + +/** + * @description 表格多选数据操作 + * @param {String} rowKey 当表格可以多选时,所指定的 id + * */ +// 定义一个名为 useSelection 的函数,用于处理表格多选数据的操作 +// 接受一个可选参数 rowKey,默认值为 'id',表示行数据中用于唯一标识的键名 +export const useSelection = (rowKey = 'id') => { + // 创建一个响应式变量 isSelected,用于表示是否选中了数据 + // 初始值为 false,类型为 boolean + const isSelected = ref(false) + // 创建一个响应式变量 selectedList,用于存储选中的数据列表 + // 初始值为空数组,类型为 any[] + const selectedList = ref([]) + + // 创建一个计算属性 selectedListIds,用于获取当前选中的所有数据的 id 数组 + // 计算属性会根据依赖项(这里是 selectedList)的变化而自动更新 + const selectedListIds = computed((): string[] => { + // 初始化一个空数组 ids 用于存储 id + const ids: string[] = [] + // 遍历 selectedList 中的每一项数据 + selectedList.value.forEach(item => ids.push(item[rowKey])) + // 返回包含所有 id 的数组 + return ids + }) + + /** + * @description 多选操作 + * @param {Array} rowArr 当前选择的所有数据 + * @return void + */ + // 定义一个函数 selectionChange,用于处理表格多选数据的变化事件 + // 接受一个参数 rowArr,类型为 any[],表示当前选中的所有数据 + const selectionChange = (rowArr: any) => { + // 如果选中的数据列表为空,则将 isSelected 设置为 false + // 否则将 isSelected 设置为 true + rowArr.length === 0? (isSelected.value = false) : (isSelected.value = true) + // 更新 selectedList 为当前选中的数据列表 + selectedList.value = rowArr + } + + // 返回一个包含 isSelected、selectedList、selectedListIds 和 selectionChange 的对象 + // 以便在其他地方使用这些变量和函数来处理表格多选数据 + return { + isSelected, + selectedList, + selectedListIds, + selectionChange + } +} diff --git a/client/src/layout/hooks/useTabList.ts b/client/src/layout/hooks/useTabList.ts new file mode 100644 index 0000000..f9a7847 --- /dev/null +++ b/client/src/layout/hooks/useTabList.ts @@ -0,0 +1,98 @@ +// import { ref } from 'vue' +// import { useRoute, onBeforeRouteUpdate } from 'vue-router' +// import { useCookies } from '@vueuse/integrations/useCookies' +// import { router } from '~/router' + +// export function useTabList() { +// const route = useRoute() +// const cookie = useCookies() + +// const activeTab = ref(route.path) +// const tabList = ref([ +// { +// title: '后台首页', +// path: '/' +// } +// ]) + +// // 添加标签导航 +// function addTab(tab) { +// let noTab = tabList.value.findIndex((t) => t.path == tab.path) == -1 +// if (noTab) { +// tabList.value.push(tab) +// } + +// cookie.set('tabList', tabList.value) +// } + +// // 初始化标签导航列表 +// function initTabList() { +// let tbs = cookie.get('tabList') +// if (tbs) { +// tabList.value = tbs +// } +// } + +// initTabList() + +// onBeforeRouteUpdate((to, from) => { +// activeTab.value = to.path +// addTab({ +// title: to.meta.title, +// path: to.path +// }) +// }) + +// const changeTab = (t) => { +// activeTab.value = t +// router.push(t) +// } + +// const removeTab = (t) => { +// let tabs = tabList.value +// let a = activeTab.value +// if (a == t) { +// tabs.forEach((tab, index) => { +// if (tab.path == t) { +// const nextTab = tabs[index + 1] || tabs[index - 1] +// if (nextTab) { +// a = nextTab.path +// } +// } +// }) +// } + +// activeTab.value = a +// tabList.value = tabList.value.filter((tab) => tab.path != t) + +// cookie.set('tabList', tabList.value) +// } + +// const handleClose = (c) => { +// if (c == 'clearAll') { +// // 切换回首页 +// activeTab.value = '/' +// // 过滤只剩下首页 +// tabList.value = [ +// { +// title: '后台首页', +// path: '/' +// } +// ] +// } else if (c == 'clearOther') { +// // 过滤只剩下首页和当前激活 +// tabList.value = tabList.value.filter( +// (tab) => tab.path == '/' || tab.path == activeTab.value +// ) +// } +// cookie.set('tabList', tabList.value) +// } + +// return { +// activeTab, +// tabList, +// changeTab, +// removeTab, +// handleClose +// } +// } diff --git a/client/src/layout/hooks/useTable.ts b/client/src/layout/hooks/useTable.ts new file mode 100644 index 0000000..d9d5e6e --- /dev/null +++ b/client/src/layout/hooks/useTable.ts @@ -0,0 +1,210 @@ +import { Table } from './interface' +import { reactive, computed, toRefs } from 'vue' + +/** + * @description table 页面操作方法封装 + * @param {Function} api 获取表格数据 api 方法(必传) + * @param {Object} initParam 获取数据初始化参数(非必传,默认为{}) + * @param {Boolean} isPageable 是否有分页(非必传,默认为true) + * @param {Function} dataCallBack 对后台返回的数据进行处理的方法(非必传) + * */ +// 定义一个名为 useTable 的函数,用于封装表格页面的操作方法 +// api 是获取表格数据的 API 方法,是必传参数 +// initParam 是获取数据的初始化参数,非必传,默认值为空对象 +// isPageable 表示是否有分页功能,非必传,默认值为 true +// dataCallBack 是对后台返回的数据进行处理的方法,非必传 +export const useTable = ( + api: (params: any) => Promise, + initParam: object = {}, + isPageable = true, + dataCallBack?: (data: any) => any +) => { + // 使用 reactive 创建一个响应式的 state 对象,包含表格数据、分页数据、查询参数等 + const state = reactive({ + // 表格数据,初始值为空数组 + tableData: [], + // 分页数据,包含当前页数、每页显示条数和总条数 + pageable: { + // 当前页数,初始值为 1 + pageNum: 1, + // 每页显示条数,初始值为 10 + pageSize: 10, + // 总条数,初始值为 0 + total: 0 + }, + // 查询参数,只包括查询条件,初始值为空对象 + searchParam: {}, + // 初始化默认的查询参数,初始值为空对象 + searchInitParam: {}, + // 总参数,包含分页和查询参数,初始值为空对象 + totalParam: {} + }) + + /** + * @description 分页查询参数(只包括分页和表格字段排序,其他排序方式可自行配置) + * */ + // 定义一个计算属性 pageParam,用于获取和设置分页查询参数 + const pageParam = computed({ + // 获取分页查询参数 + get: () => { + return { + pageNum: state.pageable.pageNum, + pageSize: state.pageable.pageSize + } + }, + // 设置分页查询参数,这里暂时为空,可根据需要实现 + set: (newVal: any) => { // 我是分页更新之后的值 + } + }) + + /** + * @description 获取表格数据 + * @return void + * */ + // 定义一个异步函数 getTableList,用于获取表格数据 + const getTableList = async () => { + try { + // 先把初始化参数和分页参数放到总参数里面 + Object.assign( + state.totalParam, + initParam, + isPageable? pageParam.value : {} + ) + // 请求前格式化数据,如果总参数中有 consultDate 字段,则进行处理 + if (state.totalParam.consultDate) { + state.totalParam.startTime = state.totalParam.consultDate[0] + state.totalParam.endTime = state.totalParam.consultDate[1] + delete state.totalParam.consultDate + } + // 调用 API 方法获取数据 + let { data } = await api({ + ...state.searchInitParam, + ...state.totalParam + }) + // 如果有数据处理回调函数,则对数据进行处理 + dataCallBack && (data = dataCallBack(data)) + // 获取当前表格数据,如果有分页,则取 data.list,否则取 data + state.tableData = isPageable? data.list : data + + // 从返回的数据中获取分页信息 + const { pageNum, pageSize, total } = data + // 如果有分页,则更新分页信息 + isPageable && updatePageable({ pageNum, pageSize, total }) + } catch (error) { + // 如果发生错误,打印错误信息 + console.log(error) + } + } + + /** + * @description 更新查询参数 + * @return void + * */ + // 定义一个函数 updatedTotalParam,用于更新总参数 + const updatedTotalParam = () => { + state.totalParam = {} + // 处理查询参数,可以给查询参数加自定义前缀操作 + const nowSearchParam: { [key: string]: any } = {} + // 防止手动清空输入框携带参数(这里可以自定义查询参数前缀) + for (const key in state.searchParam) { + // * 某些情况下参数为 false/0 也应该携带参数 + if ( + state.searchParam[key] || + state.searchParam[key] === false || + state.searchParam[key] === 0 + ) { + nowSearchParam[key] = state.searchParam[key] + } + } + // 将处理后的查询参数和分页参数合并到总参数中 + Object.assign( + state.totalParam, + nowSearchParam, + isPageable? pageParam.value : {} + ) + } + + /** + * @description 更新分页信息 + * @param {Object} resPageable 后台返回的分页数据 + * @return void + * */ + // 定义一个函数 updatePageable,用于更新分页信息 + const updatePageable = (resPageable: Table.Pageable) => { + // 使用 Object.assign 方法将后台返回的分页数据合并到 state.pageable 中 + Object.assign(state.pageable, resPageable) + } + + /** + * @description 表格数据查询 + * @return void + * */ + // 定义一个函数 search,用于进行表格数据查询 + const search = () => { + // 将当前页数设置为 1 + state.pageable.pageNum = 1 + // 更新总参数 + updatedTotalParam() + // 获取表格数据 + getTableList() + } + + /** + * @description 表格数据重置 + * @return void + * */ + // 定义一个函数 reset,用于重置表格数据 + const reset = () => { + // 将当前页数设置为 1 + state.pageable.pageNum = 1 + // 清空搜索参数 + state.searchParam = {} + // 重置搜索表单时,如果有默认搜索参数,则重置为默认值 + Object.keys(state.searchInitParam).forEach(key => { + state.searchParam[key] = state.searchInitParam[key] + }) + // 更新总参数 + updatedTotalParam() + // 获取表格数据 + getTableList() + } + + /** + * @description 每页条数改变 + * @param {Number} val 当前条数 + * @return void + * */ + // 定义一个函数 handleSizeChange,用于处理每页条数改变的事件 + const handleSizeChange = (val: number) => { + // 将当前页数设置为 1 + state.pageable.pageNum = 1 + // 更新每页显示条数 + state.pageable.pageSize = val + // 获取表格数据 + getTableList() + } + + /** + * @description 当前页改变 + * @param {Number} val 当前页 + * @return void + * */ + // 定义一个函数 handleCurrentChange,用于处理当前页改变的事件 + const handleCurrentChange = (val: number) => { + // 更新当前页数 + state.pageable.pageNum = val + // 获取表格数据 + getTableList() + } + + // 返回一个包含 state 的响应式引用、获取表格数据的函数、查询函数、重置函数、处理每页条数改变和当前页改变的函数以及更新总参数的函数 + return { + ...toRefs(state), + getTableList, + search, + reset, + handleSizeChange, + handleCurrentChange, + updatedTotalParam + } +}