lxf 3 months ago
parent 8855636e8a
commit 9d499cce67

@ -1,7 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AutoImportSettings">
<option name="autoReloadType" value="SELECTIVE" />
</component>
<component name="ChangeListManager">
<list default="true" id="4bee2ad7-6bf7-4a48-9f02-04204a99b147" name="Changes" comment="" />
<list default="true" id="4bee2ad7-6bf7-4a48-9f02-04204a99b147" name="Changes" comment="">
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
@ -15,10 +20,10 @@
</option>
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
</component>
<component name="ProjectColorInfo"><![CDATA[{
"customColor": "",
"associatedIndex": 1
}]]></component>
<component name="ProjectColorInfo">{
&quot;customColor&quot;: &quot;&quot;,
&quot;associatedIndex&quot;: 1
}</component>
<component name="ProjectId" id="2wPHHFebSwYCHvl09mTI9f7jYjH" />
<component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" />
@ -28,7 +33,7 @@
"keyToString": {
"RunOnceActivity.ShowReadmeOnStart": "true",
"RunOnceActivity.git.unshallow": "true",
"git-widget-placeholder": "feature/lxf",
"git-widget-placeholder": "develop",
"kotlin-language-version-configured": "true",
"last_opened_file_path": "D:/SC/Nursing-home-management-system",
"node.js.detected.package.eslint": "true",
@ -37,6 +42,7 @@
"node.js.selected.package.tslint": "(autodetect)",
"nodejs_package_manager_path": "npm",
"settings.editor.selected.configurable": "MavenSettings",
"ts.external.directory.path": "D:\\xx\\Nursing-home-management-system\\client\\node_modules\\typescript\\lib",
"vue.rearranger.settings.migration": "true"
}
}]]></component>
@ -57,6 +63,7 @@
<option name="presentableId" value="Default" />
<updated>1742217574434</updated>
<workItem from="1745936199577" duration="445000" />
<workItem from="1745995470593" duration="507000" />
</task>
<servers />
</component>

@ -18,6 +18,7 @@
"cz-customizable": "^7.0.0",
"echarts": "^5.4.1",
"element-plus": "^2.2.28",
"file-saver": "^2.0.5",
"global": "^4.4.0",
"husky": "^8.0.2",
"screenfull": "^6.0.2",
@ -26,11 +27,13 @@
"unplugin-vue-components": "^0.22.12",
"vue": "^3.2.13",
"vue-i18n": "^9.2.2",
"vue-property-decorator": "^9.1.2",
"vue-router": "^4.0.3",
"vuex": "^4.0.0",
"vuex-persistedstate": "^4.1.0"
},
"devDependencies": {
"@types/file-saver": "^2.0.5",
"@typescript-eslint/eslint-plugin": "^5.4.0",
"@typescript-eslint/parser": "^5.4.0",
"@vue/cli-plugin-babel": "~5.0.0",
@ -2982,6 +2985,13 @@
"@types/range-parser": "*"
}
},
"node_modules/@types/file-saver": {
"version": "2.0.7",
"resolved": "https://registry.npmmirror.com/@types/file-saver/-/file-saver-2.0.7.tgz",
"integrity": "sha512-dNKVfHd/jk0SkR/exKGj2ggkB45MAkzvWCaqLUUgkyjITkGNzH8H+yUwr+BLJUBjZOe9w8X3wgmXhZDRg1ED6A==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/html-minifier-terser": {
"version": "6.1.0",
"resolved": "https://registry.npmmirror.com/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz",
@ -4940,18 +4950,6 @@
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
}
},
"node_modules/acorn-jsx/node_modules/acorn": {
"version": "7.4.1",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"acorn": "bin/acorn"
},
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/acorn-node": {
"version": "1.8.2",
"resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.8.2.tgz",
@ -8299,6 +8297,12 @@
"node": "^10.12.0 || >=12.0.0"
}
},
"node_modules/file-saver": {
"version": "2.0.5",
"resolved": "https://registry.npmmirror.com/file-saver/-/file-saver-2.0.5.tgz",
"integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==",
"license": "MIT"
},
"node_modules/fill-range": {
"version": "7.0.1",
"resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-7.0.1.tgz",
@ -15708,6 +15712,16 @@
"@vue/shared": "3.2.45"
}
},
"node_modules/vue-class-component": {
"version": "7.2.6",
"resolved": "https://registry.npmmirror.com/vue-class-component/-/vue-class-component-7.2.6.tgz",
"integrity": "sha512-+eaQXVrAm/LldalI272PpDe3+i4mPis0ORiMYxF6Ae4hyuCh15W8Idet7wPUEs4N4YptgFHGys4UrgNQOMyO6w==",
"license": "MIT",
"peer": true,
"peerDependencies": {
"vue": "^2.0.0"
}
},
"node_modules/vue-demi": {
"version": "0.13.11",
"resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.13.11.tgz",
@ -15861,6 +15875,16 @@
"node": ">=8.9.0"
}
},
"node_modules/vue-property-decorator": {
"version": "9.1.2",
"resolved": "https://registry.npmmirror.com/vue-property-decorator/-/vue-property-decorator-9.1.2.tgz",
"integrity": "sha512-xYA8MkZynPBGd/w5QFJ2d/NM0z/YeegMqYTphy7NJQXbZcuU6FC6AOdUAcy4SXP+YnkerC6AfH+ldg7PDk9ESQ==",
"license": "MIT",
"peerDependencies": {
"vue": "*",
"vue-class-component": "*"
}
},
"node_modules/vue-router": {
"version": "4.1.6",
"resolved": "https://registry.npmmirror.com/vue-router/-/vue-router-4.1.6.tgz",
@ -18548,6 +18572,12 @@
"@types/range-parser": "*"
}
},
"@types/file-saver": {
"version": "2.0.7",
"resolved": "https://registry.npmmirror.com/@types/file-saver/-/file-saver-2.0.7.tgz",
"integrity": "sha512-dNKVfHd/jk0SkR/exKGj2ggkB45MAkzvWCaqLUUgkyjITkGNzH8H+yUwr+BLJUBjZOe9w8X3wgmXhZDRg1ED6A==",
"dev": true
},
"@types/html-minifier-terser": {
"version": "6.1.0",
"resolved": "https://registry.npmmirror.com/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz",
@ -19967,14 +19997,7 @@
"resolved": "https://registry.npmmirror.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
"integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
"dev": true,
"requires": {},
"dependencies": {
"acorn": {
"version": "7.4.1",
"dev": true,
"peer": true
}
}
"requires": {}
},
"acorn-node": {
"version": "1.8.2",
@ -22289,6 +22312,11 @@
"flat-cache": "^3.0.4"
}
},
"file-saver": {
"version": "2.0.5",
"resolved": "https://registry.npmmirror.com/file-saver/-/file-saver-2.0.5.tgz",
"integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA=="
},
"fill-range": {
"version": "7.0.1",
"resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-7.0.1.tgz",
@ -27373,6 +27401,13 @@
}
}
},
"vue-class-component": {
"version": "7.2.6",
"resolved": "https://registry.npmmirror.com/vue-class-component/-/vue-class-component-7.2.6.tgz",
"integrity": "sha512-+eaQXVrAm/LldalI272PpDe3+i4mPis0ORiMYxF6Ae4hyuCh15W8Idet7wPUEs4N4YptgFHGys4UrgNQOMyO6w==",
"peer": true,
"requires": {}
},
"vue-demi": {
"version": "0.13.11",
"resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.13.11.tgz",
@ -27464,6 +27499,12 @@
}
}
},
"vue-property-decorator": {
"version": "9.1.2",
"resolved": "https://registry.npmmirror.com/vue-property-decorator/-/vue-property-decorator-9.1.2.tgz",
"integrity": "sha512-xYA8MkZynPBGd/w5QFJ2d/NM0z/YeegMqYTphy7NJQXbZcuU6FC6AOdUAcy4SXP+YnkerC6AfH+ldg7PDk9ESQ==",
"requires": {}
},
"vue-router": {
"version": "4.1.6",
"resolved": "https://registry.npmmirror.com/vue-router/-/vue-router-4.1.6.tgz",

@ -1,18 +1,32 @@
// 从 "@/utils" 模块导入 http 对象,该对象可能封装了 HTTP 请求方法
import { http } from "@/utils";
// 定义一个名为 IListRoomByKey 的接口,用于描述获取房间列表时请求参数的数据结构
interface IListRoomByKey {
// 楼栋 ID类型为字符串
buildingId: string;
// 楼层 ID类型为字符串
floorId: string;
// 老人姓名,类型为字符串
elderName: string;
}
// 获取楼栋列表
/**
*
* @returns Promise
*/
export async function listBuilding() {
// 发起一个 GET 请求到指定的 API 端点,用于获取楼栋列表
return http.get("/api/bedPanorama/listBuilding");
}
// 获取楼层列表
/**
* ID
* @param buildingId - ID
* @returns Promise
*/
export function listFloorByBuildingId(buildingId: string) {
// 发起一个 GET 请求到指定的 API 端点,携带楼栋 ID 作为参数,用于获取对应楼栋的楼层列表
return http.get("/api/bedPanorama/listFloorByBuildingId", {
params: {
buildingId
@ -20,8 +34,13 @@ export function listFloorByBuildingId(buildingId: string) {
});
}
// 获取房间列表
/**
*
* @param data - IListRoomByKey
* @returns Promise
*/
export function listRoomByKey(data: IListRoomByKey) {
// 发起一个 GET 请求到指定的 API 端点,将 data 对象中的属性展开作为请求参数,用于获取符合条件的房间列表
return http.get("/api/bedPanorama/listRoomByKey", {
params: {
...data

@ -1,45 +1,79 @@
// 从 '@/utils' 模块导入 http 对象,该对象可能封装了 HTTP 请求的方法,用于后续发起网络请求
import { http } from '@/utils'
// 定义一个接口 ISearchFormReserveByKey用于描述分页查询预定时请求参数的数据结构
interface ISearchFormReserveByKey {
// 当前页码,类型为数字
pageNum: number
// 每页显示的数量,类型为数字
pageSize: number
// 老人姓名,可选参数,类型为字符串
elderName?: string
// 付款人电话,可选参数,类型为字符串
payerPhone?: string
}
// 定义一个导出的接口 IPageSearchElderByKey用于描述分页搜索老人时请求参数的数据结构
export interface IPageSearchElderByKey {
// 当前页码,类型为数字
pageNum: number
// 每页显示的数量,类型为数字
pageSize: number
// 老人姓名,可选参数,类型为字符串
elderName?: string
// 老人电话,可选参数,类型为字符串
elderPhone?: string
}
// 定义一个接口 IAddReserve用于描述新增预定时请求体的数据结构
interface IAddReserve {
// 床位 ID类型为字符串
bedId: string
// 定金,类型为字符串
deposit: string
// 截止日期,类型为字符串
dueDate: string
// 老人地址,类型为字符串
elderAddress: string
// 老人年龄,类型为字符串
elderAge: string
// 老人姓名,类型为字符串
elderName: string
// 老人电话,类型为字符串
elderPhone: string
// 老人性别,类型为字符串
elderSex: string
// 身份证号码,类型为字符串
idNum: string
// 付款人姓名,类型为字符串
payerName: string
// 付款人电话,类型为字符串
payerPhone: string
// 工作人员 ID类型为字符串
staffId: string
}
// 定义一个接口 IGetReserveById用于描述根据预定编号和老人编号获取预定信息时请求参数的数据结构
interface IGetReserveById {
// 老人 ID类型为字符串
elderId: string
// 预定 ID类型为字符串
reserveId: string
}
// 定义一个接口 IRefund用于描述退款时请求体的数据结构
interface IRefund {
// 预定 ID类型为字符串
reserveId: string
}
// 分页查询预定
/**
*
* @param data - ISearchFormReserveByKey
* @returns Promise
*/
export async function pageReserveByKey(data: ISearchFormReserveByKey) {
// 发起一个 GET 请求到指定的 API 端点,将 data 对象中的属性展开作为请求参数
return http.get('/api/reserve/pageReserveByKey', {
params: {
...data
@ -47,8 +81,13 @@ export async function pageReserveByKey(data: ISearchFormReserveByKey) {
})
}
// 分页搜索老人
/**
*
* @param data - IPageSearchElderByKey
* @returns Promise
*/
export function pageSearchElderByKey(data: IPageSearchElderByKey) {
// 发起一个 GET 请求到指定的 API 端点,将 data 对象中的属性展开作为请求参数
return http.get('/api/reserve/pageSearchElderByKey', {
params: {
...data
@ -56,23 +95,41 @@ export function pageSearchElderByKey(data: IPageSearchElderByKey) {
})
}
// 获取营销人员
/**
*
* @returns Promise
*/
export function listReserveStaff() {
// 发起一个 GET 请求到指定的 API 端点,用于获取营销人员列表
return http.get('/api/reserve/listReserveStaff')
}
// 获取楼栋树
/**
*
* @returns Promise
*/
export function getBuildTree() {
// 发起一个 GET 请求到指定的 API 端点,用于获取楼栋树信息
return http.get('/api/reserve/getBuildTree')
}
// 新增预定
/**
*
* @param data - IAddReserve
* @returns Promise
*/
export function addReserve(data: IAddReserve) {
// 发起一个 POST 请求到指定的 API 端点,将 data 对象作为请求体
return http.post('/api/reserve/addReserve', data)
}
// 根据预定编号和老人编号获取预定信息
/**
*
* @param data - IGetReserveById
* @returns Promise
*/
export function getReserveById(data: IGetReserveById) {
// 发起一个 GET 请求到指定的 API 端点,将 data 对象中的属性展开作为请求参数
return http.get('/api/reserve/getReserveByReserveIdAndElderId', {
params: {
...data
@ -80,7 +137,12 @@ export function getReserveById(data: IGetReserveById) {
})
}
// 退款
/**
* 退
* @param data - 退 ID IRefund
* @returns Promise退
*/
export function refund(data: IRefund) {
return http.put('/api/reserve/refund',data)
// 发起一个 PUT 请求到指定的 API 端点,将 data 对象作为请求体
return http.put('/api/reserve/refund', data)
}

@ -1,64 +1,103 @@
// 从 "@/utils" 模块导入 http 对象,用于发起 HTTP 请求
import { http } from "@/utils";
// 定义 IPageBedByKey 接口,用于分页查询床位时的参数结构
interface IPageBedByKey {
// 楼栋 ID
buildId: string;
// 楼层 ID
floorId: string;
// 房间 ID
roomId: string;
// 床位状态标识
bedFlag: string;
}
// 定义 IAddBuilding 接口,用于新增楼栋时的请求数据结构
interface IAddBuilding {
// 楼栋 ID
id: string;
// 楼栋名称
name: string;
// 楼层数量
floorNum: string;
}
// 定义 IGetBuildingById 接口,用于根据编号获取楼栋时的参数结构
interface IGetBuildingById {
// 楼栋 ID
buildingId: string;
}
// 定义 IAddFloor 接口,用于新增楼层时的请求数据结构
interface IAddFloor {
// 楼层 ID
id: string;
// 楼层名称
name: string;
// 房间数量
roomNum: string;
// 所属楼栋 ID
buildingId: string;
// 楼层限制相关信息
floorLimit: string;
}
// 定义 IGetFloorById 接口,用于根据编号获取楼层时的参数结构
interface IGetFloorById {
// 楼层 ID
floorId: string;
}
// 定义 IAddRoom 接口,用于新增房间时的请求数据结构
interface IAddRoom {
// 房间 ID
id: string;
// 房间名称
name: string;
// 房间类型 ID
typeId: string;
// 床位数量
bedNum: string;
// 所属楼层 ID
floorId: string;
// 房间限制相关信息
roomLimit: string;
}
// 定义 IGetRoomById 接口,用于根据编号获取房间时的参数结构
interface IGetRoomById {
// 房间 ID
roomId: string;
}
// 定义 IDeleteNode 接口,用于删除节点时的参数结构
interface IDeleteNode {
// 节点 ID
id: string;
// 节点标识
mark: string;
}
// 定义 IAddBed 接口,用于新增床位时的请求数据结构
interface IAddBed {
// 床位 ID
id: string;
// 床位名称
name: string;
// 所属房间 ID
roomId: string;
// 床位限制相关信息
bedLimit: string;
}
// 定义 IGetBedById 接口,用于根据编号获取床位时的参数结构
interface IGetBedById {
// 床位 ID
bedId: string;
}
// 床位状态
// 定义床位状态列表,包含每个状态的标签和对应的值
export const IBedFlagList = [
{ label: "空闲", value: "空闲" },
{ label: "预定", value: "预定" },
@ -66,13 +105,22 @@ export const IBedFlagList = [
{ label: "退住审核", value: "退住审核" }
];
// 获取楼栋-楼层-房间树
/**
* - -
* @returns Promise - -
*/
export async function getNoBedTree() {
// 发起 GET 请求到 /api/build/getNoBedTree 接口获取数据
return http.get("/api/build/getNoBedTree");
}
// 分页查询床位
/**
*
* @param data - IPageBedByKey
* @returns Promise
*/
export async function pageBedByKey(data: IPageBedByKey) {
// 发起 GET 请求到 /api/build/pageBedByKey 接口,将 data 对象展开作为请求参数
return http.get("/api/build/pageBedByKey", {
params: {
...data
@ -80,13 +128,23 @@ export async function pageBedByKey(data: IPageBedByKey) {
});
}
// 新增楼栋
/**
*
* @param data - IAddBuilding
* @returns Promise
*/
export function addBuilding(data: IAddBuilding) {
// 发起 POST 请求到 /api/build/addBuilding 接口,将 data 作为请求体
return http.post("/api/build/addBuilding", data);
}
// 根据编号获取楼栋
/**
*
* @param data - IGetBuildingById
* @returns Promise
*/
export async function getBuildingById(data: IGetBuildingById) {
// 发起 GET 请求到 /api/build/getBuildingById 接口,将 data 对象展开作为请求参数
return http.get("/api/build/getBuildingById", {
params: {
...data
@ -94,18 +152,33 @@ export async function getBuildingById(data: IGetBuildingById) {
});
}
// 编辑楼栋
/**
*
* @param data - IAddBuilding
* @returns Promise
*/
export function editBuilding(data: IAddBuilding) {
// 发起 PUT 请求到 /api/build/editBuilding 接口,将 data 作为请求体
return http.put("/api/build/editBuilding", data);
}
// 新增楼层
/**
*
* @param data - IAddFloor
* @returns Promise
*/
export function addFloor(data: IAddFloor) {
// 发起 POST 请求到 /api/build/addFloor 接口,将 data 作为请求体
return http.post("/api/build/addFloor", data);
}
// 根据编号获取楼层
/**
*
* @param data - IGetFloorById
* @returns Promise
*/
export async function getFloorById(data: IGetFloorById) {
// 发起 GET 请求到 /api/build/getFloorById 接口,将 data 对象展开作为请求参数
return http.get("/api/build/getFloorById", {
params: {
...data
@ -113,23 +186,42 @@ export async function getFloorById(data: IGetFloorById) {
});
}
// 编辑楼层
/**
*
* @param data - IAddFloor
* @returns Promise
*/
export function editFloor(data: IAddFloor) {
// 发起 PUT 请求到 /api/build/editFloor 接口,将 data 作为请求体
return http.put("/api/build/editFloor", data);
}
// 获取房间类型列表
/**
*
* @returns Promise
*/
export async function listRoomType() {
// 发起 GET 请求到 /api/build/listRoomType 接口获取数据
return http.get("/api/build/listRoomType");
}
// 新增房间
/**
*
* @param data - IAddRoom
* @returns Promise
*/
export function addRoom(data: IAddRoom) {
// 发起 POST 请求到 /api/build/addRoom 接口,将 data 作为请求体
return http.post("/api/build/addRoom", data);
}
// 根据编号获取房间
/**
*
* @param data - IGetRoomById
* @returns Promise
*/
export async function getRoomById(data: IGetRoomById) {
// 发起 GET 请求到 /api/build/getRoomById 接口,将 data 对象展开作为请求参数
return http.get("/api/build/getRoomById", {
params: {
...data
@ -137,13 +229,23 @@ export async function getRoomById(data: IGetRoomById) {
});
}
// 编辑房间
/**
*
* @param data - IAddRoom
* @returns Promise
*/
export function editRoom(data: IAddRoom) {
// 发起 PUT 请求到 /api/build/editRoom 接口,将 data 作为请求体
return http.put("/api/build/editRoom", data);
}
// 删除节点
/**
*
* @param data - IDeleteNode
* @returns Promise
*/
export async function deleteNode(data: IDeleteNode) {
// 发起 DELETE 请求到 /api/build/deleteNode 接口,将 data 对象展开作为请求参数
return http.delete("/api/build/deleteNode", {
params: {
...data
@ -151,13 +253,23 @@ export async function deleteNode(data: IDeleteNode) {
});
}
// 新增床位
/**
*
* @param data - IAddBed
* @returns Promise
*/
export function addBed(data: IAddBed) {
// 发起 POST 请求到 /api/build/addBed 接口,将 data 作为请求体
return http.post("/api/build/addBed", data);
}
// 根据编号获取床位
/**
*
* @param data - IGetBedById
* @returns Promise
*/
export async function getBedById(data: IGetBedById) {
// 发起 GET 请求到 /api/build/getBedById 接口,将 data 对象展开作为请求参数
return http.get("/api/build/getBedById", {
params: {
...data
@ -165,13 +277,23 @@ export async function getBedById(data: IGetBedById) {
});
}
// 编辑床位
/**
*
* @param data - IAddBed
* @returns Promise
*/
export function editBed(data: IAddBed) {
// 发起 PUT 请求到 /api/build/editBed 接口,将 data 作为请求体
return http.put("/api/build/editBed", data);
}
// 删除床位
/**
*
* @param data - IGetBedById
* @returns Promise
*/
export async function deleteBed(data: IGetBedById) {
// 发起 DELETE 请求到 /api/build/deleteBed 接口,将 data 对象展开作为请求参数
return http.delete("/api/build/deleteBed", {
params: {
...data

@ -1,26 +1,50 @@
// 从 "@/utils" 模块导入 http 对象,用于发起 HTTP 请求
import { http } from "@/utils";
// 从 "@/apis/dishes" 模块导入 IPageDishesByKey 接口,用于分页查询菜品时的参数类型定义
import { IPageDishesByKey } from "@/apis/dishes";
/**
* IPageCateringSetByKey
*/
interface IPageCateringSetByKey {
name:string;
// 名称,用于筛选餐饮套餐
name: string;
// 套餐名称,由于后台与前端组件封装要求,该字段与 name 可能存在关联
setName: string;
}
/**
* IAddCateringSet
*/
interface IAddCateringSet {
// 餐饮套餐的 ID
id: number;
// 餐饮套餐的名称
name: string;
// 餐饮套餐的月价格
monthPrice: number;
dishesIdList: any ;
// 菜品 ID 列表,类型为 any需根据实际情况调整
dishesIdList: any;
}
/**
* IGetCateringSetById
*/
interface IGetCateringSetById {
setId: string
// 餐饮套餐的 ID
setId: string;
}
// 分页查询餐饮套餐
/**
*
* @param data - IPageCateringSetByKey
* @returns Promise
*/
export async function pageCateringSetByKey(data: IPageCateringSetByKey) {
// 因为后台返回的字段与前端表单数据的prop不一样但是组件封装是需要一样的所以请求前增加一些这两个字段
Reflect.has(data, 'name') ? (data.setName = data.name) : ''
// 由于后台返回的字段与前端表单数据的 prop 不一样,而组件封装要求字段一致
// 所以如果 data 对象中有 'name' 字段,则将其值赋给 'setName' 字段
Reflect.has(data, 'name') ? (data.setName = data.name) : '';
// 发起 GET 请求到 /api/cateringSet/pageCateringSetByKey 接口,将 data 对象展开作为请求参数
return http.get("/api/cateringSet/pageCateringSetByKey", {
params: {
...data
@ -28,13 +52,22 @@ export async function pageCateringSetByKey(data: IPageCateringSetByKey) {
});
}
// 获取菜品分类
/**
*
* @returns Promise
*/
export function listDishesType() {
// 发起 POST 请求到 /api/cateringSet/listDishesType 接口获取菜品分类数据
return http.post("/api/cateringSet/listDishesType");
}
// 分页查询菜品
/**
*
* @param data - IPageDishesByKey
* @returns Promise
*/
export async function pageDishesByKey(data: IPageDishesByKey) {
// 发起 GET 请求到 /api/cateringSet/pageDishesByKey 接口,将 data 对象展开作为请求参数
return http.get("/api/cateringSet/pageDishesByKey", {
params: {
...data
@ -42,13 +75,23 @@ export async function pageDishesByKey(data: IPageDishesByKey) {
});
}
// 新增餐饮套餐
/**
*
* @param data - IAddCateringSet
* @returns Promise
*/
export function addCateringSet(data: IAddCateringSet) {
// 发起 POST 请求到 /api/cateringSet/addCateringSet 接口,将 data 作为请求体
return http.post("/api/cateringSet/addCateringSet", data);
}
// 根据编号查询餐饮套餐
/**
*
* @param data - IGetCateringSetById
* @returns Promise
*/
export async function getCateringSetById(data: IGetCateringSetById) {
// 发起 GET 请求到 /api/cateringSet/getCateringSetById 接口,将 data 对象展开作为请求参数
return http.get("/api/cateringSet/getCateringSetById", {
params: {
...data
@ -56,13 +99,23 @@ export async function getCateringSetById(data: IGetCateringSetById) {
});
}
// 编辑餐饮套餐
/**
*
* @param data - IAddCateringSet
* @returns Promise
*/
export function editCateringSet(data: IAddCateringSet) {
// 发起 PUT 请求到 /api/cateringSet/editCateringSet 接口,将 data 作为请求体
return http.put("/api/cateringSet/editCateringSet", data);
}
// 删除餐饮套餐
/**
*
* @param data - IGetCateringSetById
* @returns Promise
*/
export async function deleteCateringSet(data: IGetCateringSetById) {
// 发起 DELETE 请求到 /api/cateringSet/deleteCateringSet 接口,将 data 对象展开作为请求参数
return http.delete("/api/cateringSet/deleteCateringSet", {
params: {
...data

@ -1,44 +1,87 @@
// 从 "@/utils" 模块导入 http 对象,用于发起 HTTP 请求
import { http } from "@/utils";
// 从 "@/apis/bookManage" 模块导入 IPageSearchElderByKey 接口,用于分页搜索老人时的参数类型定义
import { IPageSearchElderByKey } from "@/apis/bookManage";
/**
* IPageCheckContractByKey
*/
interface IPageCheckContractByKey {
// 姓名,用于筛选入住签约记录
name: string;
// 性别,用于筛选入住签约记录
sex: string;
// 身份证号码,用于筛选入住签约记录
idNum: string;
}
/**
* IAddCheckContract
*/
interface IAddCheckContract {
// 入住签约的 ID
id: string;
// 护理等级 ID
nursingGradeId: string;
// 餐饮套餐 ID
cateringSetId: string;
// 床位 ID
bedId: string;
// 姓名
name: string;
// 身份证号码
idNum: string;
// 年龄
age: string;
// 性别
sex: string;
// 电话
phone: string;
// 地址
address: string;
// 工作人员 ID
staffId: string;
// 签约日期
signDate: string;
// 开始日期
startDate: string;
// 结束日期
endDate: string;
// 紧急联系人查询列表,包含多个紧急联系人信息
operateEmergencyContactQueryList: IEmergencyContact[];
}
/**
* IEmergencyContact
*/
interface IEmergencyContact {
// 姓名
name: string;
// 电话
phone: string;
// 邮箱
email: string;
// 关系
relation: string;
// 接收标志
receiveFlag: string;
}
/**
* IGetCheckContractById
*/
interface IGetCheckContractById {
elderId: string
// 老人 ID
elderId: string;
}
// 分页查询入住签约
/**
*
* @param data - IPageCheckContractByKey
* @returns Promise
*/
export async function pageCheckContractByKey(data: IPageCheckContractByKey) {
// 发起 GET 请求到 /api/checkContract/pageCheckContractByKey 接口,将 data 对象展开作为请求参数
return http.get("/api/checkContract/pageCheckContractByKey", {
params: {
...data
@ -46,8 +89,13 @@ export async function pageCheckContractByKey(data: IPageCheckContractByKey) {
});
}
// 分页搜索老人
/**
*
* @param data - IPageSearchElderByKey
* @returns Promise
*/
export async function pageSearchElderByKey(data: IPageSearchElderByKey) {
// 发起 GET 请求到 /api/checkContract/pageSearchElderByKey 接口,将 data 对象展开作为请求参数
return http.get("/api/checkContract/pageSearchElderByKey", {
params: {
...data
@ -55,13 +103,22 @@ export async function pageSearchElderByKey(data: IPageSearchElderByKey) {
});
}
// 获取护理等级列表
/**
*
* @returns Promise
*/
export async function listNurseGrade() {
// 发起 GET 请求到 /api/checkContract/listNurseGrade 接口获取护理等级数据
return http.get("/api/checkContract/listNurseGrade");
}
// 根据编号查询护理等级
/**
*
* @param nurseGradeId - ID
* @returns Promise
*/
export async function getNurseGradeById(nurseGradeId: string) {
// 发起 GET 请求到 /api/checkContract/getNurseGradeById 接口,将 nurseGradeId 作为请求参数
return http.get("/api/checkContract/getNurseGradeById", {
params: {
nurseGradeId
@ -69,13 +126,22 @@ export async function getNurseGradeById(nurseGradeId: string) {
});
}
// 获取餐饮套餐列表
/**
*
* @returns Promise
*/
export async function listCateringSet() {
// 发起 GET 请求到 /api/checkContract/listCateringSet 接口获取餐饮套餐数据
return http.get("/api/checkContract/listCateringSet");
}
// 根据编号查询餐饮套餐
/**
*
* @param cateringSetId - ID
* @returns Promise
*/
export async function getCateringSetById(cateringSetId: string) {
// 发起 GET 请求到 /api/checkContract/getCateringSetById 接口,将 cateringSetId 作为请求参数
return http.get("/api/checkContract/getCateringSetById", {
params: {
cateringSetId
@ -83,13 +149,22 @@ export async function getCateringSetById(cateringSetId: string) {
});
}
// 获取楼栋树
/**
*
* @returns Promise
*/
export async function getBuildTree() {
// 发起 GET 请求到 /api/checkContract/getBuildTree 接口获取楼栋树数据
return http.get("/api/checkContract/getBuildTree");
}
// 根据编号查询床位
/**
*
* @param bedId - ID
* @returns Promise
*/
export async function getBedById(bedId: string) {
// 发起 GET 请求到 /api/checkContract/getBedById 接口,将 bedId 作为请求参数
return http.get("/api/checkContract/getBedById", {
params: {
bedId
@ -97,18 +172,32 @@ export async function getBedById(bedId: string) {
});
}
// 获取营销人员
/**
*
* @returns Promise
*/
export async function listReserveStaff() {
// 发起 GET 请求到 /api/checkContract/listReserveStaff 接口获取营销人员数据
return http.get("/api/checkContract/listReserveStaff");
}
// 新增入住签约
/**
*
* @param data - IAddCheckContract
* @returns Promise
*/
export function addCheckContract(data: IAddCheckContract) {
// 发起 POST 请求到 /api/checkContract/addCheckContract 接口,将 data 作为请求体
return http.post("/api/checkContract/addCheckContract", data);
}
// 根据老人编号查询入住签约
/**
*
* @param data - IGetCheckContractById
* @returns Promise
*/
export async function getCheckContractById(data: IGetCheckContractById) {
// 发起 GET 请求到 /api/checkContract/getCheckContractById 接口,将 data 对象展开作为请求参数
return http.get("/api/checkContract/getCheckContractById", {
params: {
...data
@ -116,13 +205,23 @@ export async function getCheckContractById(data: IGetCheckContractById) {
});
}
// 编辑入住签约
/**
*
* @param data - IAddCheckContract
* @returns Promise
*/
export function editCheckContract(data: IAddCheckContract) {
// 发起 PUT 请求到 /api/checkContract/editCheckContract 接口,将 data 作为请求体
return http.put("/api/checkContract/editCheckContract", data);
}
// 删除入住签约
/**
*
* @param data - IGetCheckContractById
* @returns Promise
*/
export async function deleteCheckContract(data: IGetCheckContractById) {
// 发起 DELETE 请求到 /api/checkContract/deleteCheckContract 接口,将 data 对象展开作为请求参数
return http.delete("/api/checkContract/deleteCheckContract", {
params: {
...data

@ -1,13 +1,25 @@
// 从 "@/utils" 模块导入名为 http 的对象,
// 推测该对象是用于发起 HTTP 请求的工具,可能是对 axios 等请求库的封装
import { http } from "@/utils";
// 定义一个接口 IPageConsumeByKey用于描述分页查询消费记录时的请求参数结构
interface IPageConsumeByKey {
// 老人姓名,用于筛选消费记录,类型为字符串
elderName: string;
// 开始时间,用于筛选消费记录的时间范围,类型为字符串
startTime: string;
// 结束时间,用于筛选消费记录的时间范围,类型为字符串
endTime: string;
}
// 分页查询消费记录
/**
* pageConsumeByKey
* @param data - IPageConsumeByKey
* @returns Promise
*/
export async function pageConsumeByKey(data: IPageConsumeByKey) {
// 使用导入的 http 对象发起 GET 请求,请求地址为 "/api/consume/pageConsumeByKey"
// 将传入的 data 对象展开作为请求的参数
return http.get("/api/consume/pageConsumeByKey", {
params: {
...data

@ -1,36 +1,60 @@
// 从 "@/utils" 模块导入名为 http 的对象,该对象可能是用于发起 HTTP 请求的工具,比如对 axios 等请求库的封装
import { http } from "@/utils";
// 定义一个接口 IListDishesType用于描述获取菜品分类列表时的请求参数结构
interface IListDishesType {
// 菜品分类名称,用于筛选菜品分类列表,类型为字符串
dishesTypeName: string;
}
// 导出一个接口 IPageDishesByKey用于描述分页查询菜品时的请求参数结构
export interface IPageDishesByKey {
// 菜品名称,用于筛选菜品,类型为字符串
dishesName: string;
// 菜品类型 ID用于筛选属于特定类型的菜品类型为数字
typeId: number;
}
// 定义一个接口 IAddDishesType用于描述新增菜品分类时的数据结构
interface IAddDishesType {
// 菜品分类的 ID类型为数字
id: number;
// 菜品分类的名称,类型为字符串
name: string;
}
// 定义一个接口 IGetDishesTypeById用于描述根据编号获取菜品分类时的请求参数结构
interface IGetDishesTypeById {
// 菜品分类 ID类型为字符串
dishesTypeId: string;
}
// 定义一个接口 IAddDishes用于描述新增菜品时的数据结构
interface IAddDishes {
// 菜品的 ID类型为数字
id: number;
// 菜品的名称,类型为字符串
name: string;
// 菜品的价格,类型为字符串
price: string;
// 菜品所属类型的 ID类型为数字
typeId: number;
}
// 导出一个接口 IGetDishesById用于描述根据编号获取菜品时的请求参数结构
export interface IGetDishesById {
// 菜品 ID类型为字符串
dishesId: string;
}
// 获取菜品分类列表
/**
* listDishesType
* @param data - IListDishesType
* @returns Promise
*/
export async function listDishesType(data: IListDishesType) {
// 使用导入的 http 对象发起 GET 请求,请求地址为 "/api/dishes/listDishesType"
// 将 data 对象展开作为请求的参数
return http.get("/api/dishes/listDishesType", {
params: {
...data
@ -38,8 +62,14 @@ export async function listDishesType(data: IListDishesType) {
});
}
// 分页查询菜品
/**
* pageDishesByKey
* @param data - IPageDishesByKey
* @returns Promise
*/
export async function pageDishesByKey(data: IPageDishesByKey) {
// 使用导入的 http 对象发起 GET 请求,请求地址为 "/api/dishes/pageDishesByKey"
// 将 data 对象展开作为请求的参数
return http.get("/api/dishes/pageDishesByKey", {
params: {
...data
@ -47,13 +77,25 @@ export async function pageDishesByKey(data: IPageDishesByKey) {
});
}
// 新增菜品分类
/**
* addDishesType
* @param data - IAddDishesType
* @returns Promise
*/
export function addDishesType(data: IAddDishesType) {
// 使用导入的 http 对象发起 POST 请求,请求地址为 "/api/dishes/addDishesType"
// 将 data 对象作为请求的主体
return http.post("/api/dishes/addDishesType", data);
}
// 根据编号获取菜品分类
/**
* getDishesTypeById
* @param data - IGetDishesTypeById
* @returns Promise
*/
export async function getDishesTypeById(data: IGetDishesTypeById) {
// 使用导入的 http 对象发起 GET 请求,请求地址为 "/api/dishes/getDishesTypeById"
// 将 data 对象展开作为请求的参数
return http.get("/api/dishes/getDishesTypeById", {
params: {
...data
@ -61,13 +103,25 @@ export async function getDishesTypeById(data: IGetDishesTypeById) {
});
}
// 编辑菜品分类
/**
* editDishesType
* @param data - IAddDishesType
* @returns Promise
*/
export function editDishesType(data: IAddDishesType) {
// 使用导入的 http 对象发起 PUT 请求,请求地址为 "/api/dishes/editDishesType"
// 将 data 对象作为请求的主体
return http.put("/api/dishes/editDishesType", data);
}
// 删除菜品分类
/**
* deleteDishesType
* @param data - IGetDishesTypeById
* @returns Promise
*/
export async function deleteDishesType(data: IGetDishesTypeById) {
// 使用导入的 http 对象发起 DELETE 请求,请求地址为 "/api/dishes/deleteDishesType"
// 将 data 对象展开作为请求的参数
return http.delete("/api/dishes/deleteDishesType", {
params: {
...data
@ -75,13 +129,25 @@ export async function deleteDishesType(data: IGetDishesTypeById) {
});
}
// 新增菜品
/**
* addDishes
* @param data - IAddDishes
* @returns Promise
*/
export function addDishes(data: IAddDishes) {
// 使用导入的 http 对象发起 POST 请求,请求地址为 "/api/dishes/addDishes"
// 将 data 对象作为请求的主体
return http.post("/api/dishes/addDishes", data);
}
// 根据编号获取菜品
/**
* getDishesById
* @param data - IGetDishesById
* @returns Promise
*/
export async function getDishesById(data: IGetDishesById) {
// 使用导入的 http 对象发起 GET 请求,请求地址为 "/api/dishes/getDishesById"
// 将 data 对象展开作为请求的参数
return http.get("/api/dishes/getDishesById", {
params: {
...data
@ -89,13 +155,25 @@ export async function getDishesById(data: IGetDishesById) {
});
}
// 编辑菜品
/**
* editDishes
* @param data - IAddDishes
* @returns Promise
*/
export function editDishes(data: IAddDishes) {
// 使用导入的 http 对象发起 PUT 请求,请求地址为 "/api/dishes/editDishes"
// 将 data 对象作为请求的主体
return http.put("/api/dishes/editDishes", data);
}
// 删除菜品
/**
* deleteDishes
* @param data - IGetDishesById
* @returns Promise
*/
export async function deleteDishes(data: IGetDishesById) {
// 使用导入的 http 对象发起 DELETE 请求,请求地址为 "/api/dishes/deleteDishes"
// 将 data 对象展开作为请求的参数
return http.delete("/api/dishes/deleteDishes", {
params: {
...data

@ -1,25 +1,56 @@
// 从 '@/utils' 模块导入名为 http 的对象,该对象可能是用于发起 HTTP 请求的工具,比如对 axios 等请求库的封装
import { http } from '@/utils'
// 可售床位
/**
* getAvailableBed
* @returns Promise
*/
export async function getAvailableBed() {
// 使用导入的 http 对象发起 GET 请求,请求地址为 '/api/home/availableBed'
return http.get('/api/home/availableBed')
}
// 业务趋势
/**
* getBusinessTrend
* @returns Promise
*/
export async function getBusinessTrend() {
// 使用导入的 http 对象发起 GET 请求,请求地址为 '/api/home/businessTrend'
return http.get('/api/home/businessTrend')
}
// 客户来源渠道
/**
* getClientSource
* @returns Promise
*/
export async function getClientSource() {
// 使用导入的 http 对象发起 GET 请求,请求地址为 '/api/home/clientSource'
return http.get('/api/home/clientSource')
}
// 本月业绩排行
/**
* getMonthPerformanceRank
* @returns Promise
*/
export async function getMonthPerformanceRank() {
// 使用导入的 http 对象发起 GET 请求,请求地址为 '/api/home/monthPerformanceRank'
return http.get('/api/home/monthPerformanceRank')
}
// 今日概览
/**
* getTodayOverview
* @returns Promise
*/
export async function getTodayOverview() {
// 使用导入的 http 对象发起 GET 请求,请求地址为 '/api/home/todayOverview'
return http.get('/api/home/todayOverview')
}
// 今日销售跟进
/**
* getTodaySaleFollow
* @returns Promise
*/
export async function getTodaySaleFollow() {
// 使用导入的 http 对象发起 GET 请求,请求地址为 '/api/home/todaySaleFollow'
return http.get('/api/home/todaySaleFollow')
}

@ -1,16 +1,29 @@
// 注释说明这是动态路由的假数据,并且提示该部分代码可以删除
// 动态路由的假数据 可删除
// 从 '@/utils' 模块导入名为 http 的对象,
// 推测该对象是用于发起 HTTP 请求的工具,可能是对 axios 等请求库的封装
import { http } from '@/utils'
// 注释说明发起请求的时机在动态设置路由的时候data => 树形结构 => 路由列表)
// 问题:何时发起请求? 在动态设置路由的时候data => 树形结构 => 路由列表)
/**
* getUserRouteList
* @param uid - ID
* @returns Promise
*/
function getUserRouteList(uid: number) {
// 使用导入的 http 对象发起 POST 请求,请求地址为 '/api/user_router_list',请求体包含用户 ID
return http
.post('/api/user_router_list', { uid })
// 处理请求成功的情况,直接返回请求得到的数据
.then((data) => data)
// 处理请求失败的情况,捕获错误并重新抛出,以便上层调用者处理
.catch((err) => {
throw err
})
}
// 导出 getUserRouteList 函数,以便在其他模块中可以导入和使用该函数
export { getUserRouteList }

@ -1,40 +1,70 @@
// 从 "@/utils" 模块导入名为 http 的对象,
// 推测该对象是用于发起 HTTP 请求的工具,可能是对 axios 等请求库的封装
import { http } from "@/utils";
// 从 "@/apis/bookManage" 模块导入 IPageSearchElderByKey 接口,
// 用于描述分页搜索老人时的请求参数结构
import { IPageSearchElderByKey } from "@/apis/bookManage";
// 从 "@/apis/dishes" 模块导入 IPageDishesByKey 接口,
// 用于描述分页查询菜品时的请求参数结构
import { IPageDishesByKey } from "@/apis/dishes";
// 定义一个接口 IPageOrderByKey用于描述分页查询点餐时的请求参数结构
interface IPageOrderByKey {
elderName:string;
// 老人姓名,用于筛选点餐记录,类型为字符串
elderName: string;
// 老人电话,用于筛选点餐记录,类型为字符串
elderPhone: string;
}
// 定义一个接口 IAddOrder用于描述新增点餐时的数据结构
interface IAddOrder {
// 老人 ID类型为字符串
elderId: string;
// 就餐方式,类型为字符串
dineType: string;
// 就餐日期,类型为字符串
dineDate: string;
// 点的菜品列表,类型为 any需根据实际情况调整
orderDishesList: any;
}
// 定义一个接口 IGetOrderById用于描述根据编号查询点餐时的请求参数结构
interface IGetOrderById {
// 菜品 ID这里可能是点餐记录中的菜品 ID用于查询特定点餐记录类型为字符串
dishesId: string;
}
// 定义一个接口 ISendOrder用于描述送餐时的数据结构
interface ISendOrder {
// 订单 ID类型为字符串
id: string;
// 送餐日期,类型为字符串
deliverDishesDate: string;
// 送餐人员 ID类型为字符串
staffId: string;
}
// 就餐方式
// 定义一个名为 IDineTypeList 的常量,是一个包含就餐方式选项的数组,每个选项有标签和对应的值
export const IDineTypeList = [
{ label: "送餐", value: "送餐" },
{ label: "堂食", value: "堂食" }
];
// 分页查询点餐
/**
* pageOrderByKey
* @param data - IPageOrderByKey
* @returns Promise
*/
export async function pageOrderByKey(data: IPageOrderByKey) {
// 因为后台返回的字段与前端表单数据的prop不一样但是组件封装是需要一样的所以请求前增加一些这两个字段
// Reflect.has(data, 'name') ? (data.gradeName = data.name) : ''
// Reflect.has(data, 'type') ? (data.nurseType = data.type) : ''
// 因为后台返回的字段与前端表单数据的 prop 不一样,
// 但是组件封装需要一样,所以原本计划请求前处理某些字段,
// 但这里代码被注释掉了,可能是暂不处理或有其他调整
// Reflect.has(data, 'name')? (data.gradeName = data.name) : ''
// Reflect.has(data, 'type')? (data.nurseType = data.type) : ''
// 使用导入的 http 对象发起 GET 请求,请求地址为 "/api/order/pageOrderByKey"
// 将 data 对象展开作为请求的参数
return http.get("/api/order/pageOrderByKey", {
params: {
...data
@ -42,31 +72,55 @@ export async function pageOrderByKey(data: IPageOrderByKey) {
});
}
// 分页搜索老人
/**
* pageSearchElderByKey
* @param data - IPageSearchElderByKey
* @returns Promise
*/
export function pageSearchElderByKey(data: IPageSearchElderByKey) {
// 使用导入的 http 对象发起 GET 请求,请求地址为 "/api/order/pageSearchElderByKey"
// 将 data 对象展开作为请求的参数
return http.get('/api/order/pageSearchElderByKey', {
params: {
...data
}
})
});
}
// 分页查询菜品
/**
* pageDishesByKey
* @param data - IPageDishesByKey
* @returns Promise
*/
export async function pageDishesByKey(data: IPageDishesByKey) {
return http.get("/api/order/pageDishesByKey",{
params:{
// 使用导入的 http 对象发起 GET 请求,请求地址为 "/api/order/pageDishesByKey"
// 将 data 对象展开作为请求的参数
return http.get("/api/order/pageDishesByKey", {
params: {
...data
}
});
}
// 新增点餐
/**
* addOrder
* @param data - IAddOrder
* @returns Promise
*/
export function addOrder(data: IAddOrder) {
// 使用导入的 http 对象发起 POST 请求,请求地址为 "/api/order/addOrder"
// 将 data 对象作为请求的主体
return http.post("/api/order/addOrder", data);
}
// 根据编号查询点餐
/**
* getOrderById
* @param data - IGetOrderById
* @returns Promise
*/
export async function getOrderById(data: IGetOrderById) {
// 使用导入的 http 对象发起 GET 请求,请求地址为 "/api/order/getOrderById"
// 将 data 对象展开作为请求的参数
return http.get("/api/order/getOrderById", {
params: {
...data
@ -74,12 +128,22 @@ export async function getOrderById(data: IGetOrderById) {
});
}
// 护理人员
/**
* listNurseStaff
* @returns Promise
*/
export async function listNurseStaff() {
// 使用导入的 http 对象发起 GET 请求,请求地址为 "/api/order/listNurseStaff"
return http.get("/api/order/listNurseStaff");
}
// 送餐
/**
* sendOrder
* @param data - ISendOrder
* @returns Promise
*/
export function sendOrder(data: ISendOrder) {
// 使用导入的 http 对象发起 PUT 请求,请求地址为 "/api/order/sendOrder"
// 将 data 对象作为请求的主体
return http.put("/api/order/sendOrder", data);
}

@ -1,29 +1,46 @@
// 从 "@/utils" 模块导入名为 http 的对象,该对象可能用于发起 HTTP 请求,例如对 axios 等请求库的封装
import { http } from "@/utils";
// 定义一个接口 IPageRetreatAuditByKey用于描述分页查询退住审核时的请求参数结构
interface IPageRetreatAuditByKey {
// 老人姓名,用于筛选退住审核记录,类型为字符串
elderName: string;
// 老人性别,用于筛选退住审核记录,类型为字符串
elderSex: string;
// 身份证号码,用于筛选退住审核记录,类型为字符串
idNum: string;
}
// 定义一个接口 IGetElderFeeById用于描述根据编号获取老人费用详情时的请求参数结构
interface IGetElderFeeById {
// 老人 ID类型为数字用于指定获取费用详情的老人
elderId: number;
}
// 定义一个接口 IAuditElderFee用于描述审核老人费用详情时的数据结构
interface IAuditElderFee {
// 申请 ID类型为数字用于标识退住申请
applyId: number;
// 老人 ID类型为数字用于关联老人
elderId: number;
// 审核结果,类型为数字,可能是枚举值(如 0 表示不通过1 表示通过等)
auditResult: number;
}
// 审核结果
// 定义一个常量 IAuditResultList包含审核结果的选项,每个选项有标签和对应的值
export const IAuditResultList = [
{ label: "通过", value: "通过" },
{ label: "不通过", value: "不通过" }
];
// 分页查询退住审核
/**
* pageRetreatAuditByKey退
* @param data - IPageRetreatAuditByKey
* @returns Promise 退
*/
export async function pageRetreatAuditByKey(data: IPageRetreatAuditByKey) {
// 使用导入的 http 对象发起 GET 请求,请求地址为 "/api/retreatAudit/pageRetreatAuditByKey"
// 将 data 对象展开作为请求的参数
return http.get("/api/retreatAudit/pageRetreatAuditByKey", {
params: {
...data
@ -31,8 +48,14 @@ export async function pageRetreatAuditByKey(data: IPageRetreatAuditByKey) {
});
}
// 根据编号获取老人费用详情
/**
* getElderFeeById
* @param data - IGetElderFeeById
* @returns Promise
*/
export async function getElderFeeById(data: IGetElderFeeById) {
// 使用导入的 http 对象发起 GET 请求,请求地址为 "/api/retreatAudit/getElderFeeById"
// 将 data 对象展开作为请求的参数
return http.get("/api/retreatAudit/getElderFeeById", {
params: {
...data
@ -40,7 +63,13 @@ export async function getElderFeeById(data: IGetElderFeeById) {
});
}
// 审核老人费用详情
/**
* auditElderFee
* @param data - IAuditElderFee
* @returns Promise
*/
export function auditElderFee(data: IAuditElderFee) {
// 使用导入的 http 对象发起 PUT 请求,请求地址为 "/api/retreatAudit/auditElderFee"
// 将 data 对象作为请求的主体
return http.put("/api/retreatAudit/auditElderFee", data);
}

@ -1,24 +1,42 @@
// 从 "@/utils" 模块导入名为 http 的对象,该对象可能是用于发起 HTTP 请求的工具,比如对 axios 等请求库的封装
import { http } from "@/utils";
// 定义一个接口 IPageRoomTypeByKey用于描述分页查询房间类型时的请求参数结构
interface IPageRoomTypeByKey {
// 名称,用于筛选房间类型,类型为字符串
name: string;
// 房间类型名称,用于筛选房间类型,类型为字符串
roomTypeName: string;
}
// 定义一个接口 IAddRoomType用于描述新增房间类型时的数据结构
interface IAddRoomType {
// 房间类型的 ID类型为字符串
id: string;
// 房间类型的名称,类型为字符串
name: string;
// 房间类型的月价格,类型为字符串
monthPrice: string;
}
// 定义一个接口 IGetRoomTypeById用于描述根据编号查询房间类型时的请求参数结构
interface IGetRoomTypeById {
// 房间类型 ID类型为字符串
roomTypeId: string;
}
// 分页查询房间类型
/**
* pageRoomTypeByKey
* @param data - IPageRoomTypeByKey
* @returns Promise
*/
export async function pageRoomTypeByKey(data: IPageRoomTypeByKey) {
// 因为后台返回的字段与前端表单数据的prop不一样但是组件封装是需要一样的所以请求前增加一些这两个字段
Reflect.has(data, "name") ? (data.roomTypeName = data.name) : "";
// 因为后台返回的字段与前端表单数据的 prop 不一样,
// 但是组件封装需要一样,所以请求前增加一些这两个字段
// 如果 data 对象中有 "name" 字段,则将其值赋给 "roomTypeName" 字段
Reflect.has(data, "name")? (data.roomTypeName = data.name) : "";
// 使用导入的 http 对象发起 GET 请求,请求地址为 "/api/roomType/pageRoomTypeByKey"
// 将处理后的 data 对象展开作为请求的参数
return http.get("/api/roomType/pageRoomTypeByKey", {
params: {
...data
@ -26,13 +44,25 @@ export async function pageRoomTypeByKey(data: IPageRoomTypeByKey) {
});
}
// 新增房间类型
/**
* addRoomType
* @param data - IAddRoomType
* @returns Promise
*/
export function addRoomType(data: IAddRoomType) {
// 使用导入的 http 对象发起 POST 请求,请求地址为 "/api/roomType/addRoomType"
// 将 data 对象作为请求的主体
return http.post("/api/roomType/addRoomType", data);
}
// 根据编号查询房间类型
/**
* getRoomTypeById
* @param data - IGetRoomTypeById
* @returns Promise
*/
export async function getRoomTypeById(data: IGetRoomTypeById) {
// 使用导入的 http 对象发起 GET 请求,请求地址为 "/api/roomType/getRoomTypeById"
// 将 data 对象展开作为请求的参数
return http.get("/api/roomType/getRoomTypeById", {
params: {
...data
@ -40,13 +70,25 @@ export async function getRoomTypeById(data: IGetRoomTypeById) {
});
}
// 编辑房间类型
/**
* editRoomType
* @param data - IAddRoomType
* @returns Promise
*/
export function editRoomType(data: IAddRoomType) {
// 使用导入的 http 对象发起 PUT 请求,请求地址为 "/api/roomType/editRoomType"
// 将 data 对象作为请求的主体
return http.put("/api/roomType/editRoomType", data);
}
// 删除房间类型
/**
* deleteRoomType
* @param data - IGetRoomTypeById
* @returns Promise
*/
export async function deleteRoomType(data: IGetRoomTypeById) {
// 使用导入的 http 对象发起 DELETE 请求,请求地址为 "/api/roomType/deleteRoomType"
// 将 data 对象展开作为请求的参数
return http.delete("/api/roomType/deleteRoomType", {
params: {
...data

@ -1,58 +1,110 @@
// 从 '@/utils' 模块导入名为 http 的对象,该对象可能是用于发起 HTTP 请求的工具,比如对 axios 等请求库的封装
import { http } from '@/utils'
// 定义一个接口 IAddConsult用于描述新增咨询时的数据结构
interface IAddConsult {
address: string
age: string
consultContent: string
consultDate: string
consultName: string
consultPhone: string
elderName: string
elderPhone: string
idNum: string
relation: string
sex: string
sourceId: string | number
staffId: string | number
consultId?: string | number
elderId?: string | number
// 地址,类型为字符串
address: string;
// 年龄,类型为字符串
age: string;
// 咨询内容,类型为字符串
consultContent: string;
// 咨询日期,类型为字符串
consultDate: string;
// 咨询人姓名,类型为字符串
consultName: string;
// 咨询人电话,类型为字符串
consultPhone: string;
// 老人姓名,类型为字符串
elderName: string;
// 老人电话,类型为字符串
elderPhone: string;
// 身份证号码,类型为字符串
idNum: string;
// 关系,类型为字符串
relation: string;
// 性别,类型为字符串
sex: string;
// 来源 ID可以是字符串或数字
sourceId: string | number;
// 接待人 ID可以是字符串或数字
staffId: string | number;
// 咨询 ID可选类型可以是字符串或数字
consultId?: string | number;
// 老人 ID可选类型可以是字符串或数字
elderId?: string | number;
}
// 定义一个接口 ISearhFormConsultByKey用于描述分页查询咨询时的请求参数结构
interface ISearhFormConsultByKey {
pageNum: number
pageSize: number
consultName?: string
consultPhone?: string
elderName?: string
elderPhone?: string
endTime?: string
sourceId?: string
staffId?: string
startTime?: string
sourceName?: string
staffName?: string
// 页码,类型为数字
pageNum: number;
// 每页数量,类型为数字
pageSize: number;
// 咨询人姓名,可选,类型为字符串
consultName?: string;
// 咨询人电话,可选,类型为字符串
consultPhone?: string;
// 老人姓名,可选,类型为字符串
elderName?: string;
// 老人电话,可选,类型为字符串
elderPhone?: string;
// 结束时间,可选,类型为字符串
endTime?: string;
// 来源 ID可选类型为字符串
sourceId?: string;
// 接待人 ID可选类型为字符串
staffId?: string;
// 开始时间,可选,类型为字符串
startTime?: string;
// 来源名称,可选,类型为字符串
sourceName?: string;
// 接待人名称,可选,类型为字符串
staffName?: string;
}
// 定义一个接口 IConsultByForm用于描述根据咨询人编号和老人编号获取咨询信息时的请求参数结构
interface IConsultByForm {
consultId: string | number
elderId: string | number
// 咨询 ID可以是字符串或数字
consultId: string | number;
// 老人 ID可以是字符串或数字
elderId: string | number;
}
// 获取咨询管理表格数据 根据咨询人编号和老人编号获取咨询信息
/**
* getConsultByForm
* @param data - IConsultByForm
* @returns Promise
*/
export async function getConsultByForm(data: IConsultByForm) {
// 使用导入的 http 对象发起 GET 请求,请求地址为 '/api/consult/getConsultByConsultIdAndElderId'
// 将 data 对象展开作为请求的参数
return http.get('/api/consult/getConsultByConsultIdAndElderId', {
params: {
...data
}
})
}
//新增资询
/**
* addConsult
* @param data - IAddConsult
* @returns Promise
*/
export function addConsult(data: IAddConsult) {
// 使用导入的 http 对象发起 POST 请求,请求地址为 '/api/consult/addConsult'
// 将 data 对象作为请求的主体
return http.post('/api/consult/addConsult', data)
}
// 删除咨询
/**
* delConsult
* @param elderId - ID
* @returns Promise
*/
export function delConsult(elderId: string | number) {
// 使用导入的 http 对象发起 DELETE 请求,请求地址为 '/api/consult/deleteConsult'
// 将 elderId 作为请求的参数
return http.delete('/api/consult/deleteConsult', {
params: {
elderId
@ -60,13 +112,25 @@ export function delConsult(elderId: string | number) {
})
}
//编辑咨询
/**
* editConsult
* @param data - IAddConsult
* @returns Promise
*/
export function editConsult(data: IAddConsult) {
// 使用导入的 http 对象发起 PUT 请求,请求地址为 '/api/consult/editConsult'
// 将 data 对象作为请求的主体
return http.put('/api/consult/editConsult', data)
}
// 转为意向客户
/**
* intentionConsult
* @param elderId - ID
* @returns Promise
*/
export function intentionConsult(elderId: string | number) {
// 使用导入的 http 对象发起 PUT 请求,请求地址为 '/api/consult/intentionConsult'
// 将包含 elderId 的对象作为请求的主体
return http.put('/api/consult/intentionConsult', {
data: {
elderId
@ -74,21 +138,37 @@ export function intentionConsult(elderId: string | number) {
})
}
// 来源渠道
/**
* listConsultSource
* @returns Promise
*/
export function listConsultSource() {
// 使用导入的 http 对象发起 GET 请求,请求地址为 '/api/consult/listConsultSource'
return http.get('/api/consult/listConsultSource')
}
// 接待人
/**
* listConsultStaff
* @returns Promise
*/
export function listConsultStaff() {
// 使用导入的 http 对象发起 GET 请求,请求地址为 '/api/consult/listConsultStaff'
return http.get('/api/consult/listConsultStaff')
}
// 分页查询咨询
/**
* pageConsultByKey
* @param data - ISearhFormConsultByKey
* @returns Promise
*/
export async function pageConsultByKey(data: ISearhFormConsultByKey) {
// 因为后台返回的字段与前端表单数据的prop不一样但是组件封装是需要一样的所以请求前增加一些这两个字段
Reflect.has(data, 'sourceName') ? (data.sourceId = data.sourceName) : ''
Reflect.has(data, 'staffName') ? (data.staffId = data.staffName) : ''
// 因为后台返回的字段与前端表单数据的 prop 不一样,但是组件封装是需要一样的,所以请求前增加一些这两个字段
// 如果 data 对象中有'sourceName' 字段,则将其值赋给'sourceId' 字段
Reflect.has(data,'sourceName')? (data.sourceId = data.sourceName) : '';
// 如果 data 对象中有'staffName' 字段,则将其值赋给'staffId' 字段
Reflect.has(data,'staffName')? (data.staffId = data.staffName) : '';
// 使用导入的 http 对象发起 GET 请求,请求地址为 '/api/consult/pageConsultByKey'
// 将处理后的 data 对象展开作为请求的参数
const res = await http.get('/api/consult/pageConsultByKey', {
params: {
...data
@ -97,17 +177,28 @@ export async function pageConsultByKey(data: ISearhFormConsultByKey) {
return res
}
//意向用户接口
// 定义一个接口 ISearhFormIntentionByKey用于描述分页查询意向客户时的请求参数结构
interface ISearhFormIntentionByKey {
pageNum: number
pageSize: number
elderName?: string
elderPhone?: string | number
labelId?: number
// 页码,类型为数字
pageNum: number;
// 每页数量,类型为数字
pageSize: number;
// 老人姓名,可选,类型为字符串
elderName?: string;
// 老人电话,可选,可以是字符串或数字
elderPhone?: string | number;
// 标签 ID可选类型为数字
labelId?: number;
}
//分页查询意向客户
/**
* pageIntentionByKey
* @param data - ISearhFormIntentionByKey
* @returns Promise
*/
export async function pageIntentionByKey(data: ISearhFormIntentionByKey) {
// 使用导入的 http 对象发起 GET 请求,请求地址为 '/api/intention/pageIntentionByKey'
// 将 data 对象展开作为请求的参数
const res = await http.get('/api/intention/pageIntentionByKey', {
params: {
...data
@ -116,23 +207,47 @@ export async function pageIntentionByKey(data: ISearhFormIntentionByKey) {
return res
}
// 新增沟通记录
/**
* addCommunicationRecord
* @param data - any
* @returns Promise
*/
export function addCommunicationRecord(data: any) {
// 使用导入的 http 对象发起 POST 请求,请求地址为 '/api/intention/addCommunicationRecord'
// 将 data 对象作为请求的主体
return http.post('/api/intention/addCommunicationRecord', data)
}
// 新增意向客户
/**
* addIntention
* @param data - any
* @returns Promise
*/
export function addIntention(data: any) {
// 使用导入的 http 对象发起 POST 请求,请求地址为 '/api/intention/addIntention'
// 将 data 对象作为请求的主体
return http.post('/api/intention/addIntention', data)
}
// 新增回访计划
/**
* addVisitPlan访
* @param data - 访 any
* @returns Promise 访
*/
export function addVisitPlan(data: any) {
// 使用导入的 http 对象发起 POST 请求,请求地址为 '/api/intention/addVisitPlan'
// 将 data 对象作为请求的主体
return http.post('/api/intention/addVisitPlan', data)
}
// 新增沟通记录
/**
* deleteCommunicationRecord
* @param communicationRecordId - ID any
* @returns Promise
*/
export function deleteCommunicationRecord(communicationRecordId: any) {
// 使用导入的 http 对象发起 DELETE 请求,请求地址为 '/api/intention/deleteCommunicationRecord'
// 将 communicationRecordId 作为请求的参数
return http.delete('/api/intention/deleteCommunicationRecord', {
params: {
communicationRecordId
@ -140,8 +255,14 @@ export function deleteCommunicationRecord(communicationRecordId: any) {
})
}
// 删除回访计划
/**
* deleteVisitPlan访
* @param visitPlanId - 访 ID any访
* @returns Promise 访
*/
export function deleteVisitPlan(visitPlanId: any) {
// 使用导入的 http 对象发起 DELETE 请求,请求地址为 '/api/intention/deleteVisitPlan'
// 将 visitPlanId 作为请求的参数
return http.delete('/api/intention/deleteVisitPlan', {
params: {
visitPlanId
@ -149,28 +270,58 @@ export function deleteVisitPlan(visitPlanId: any) {
})
}
// 编辑沟通记录
/**
* editCommunicationRecord
* @param data - any
* @returns Promise
*/
export function editCommunicationRecord(data: any) {
// 使用导入的 http 对象发起 PUT 请求,请求地址为 '/api/intention/editCommunicationRecord'
//将 data 对象作为请求的主体
return http.put('/api/intention/editCommunicationRecord', data)
}
// 编辑意向客户
/**
* editIntention
* @param data - any
* @returns Promise
*/
export function editIntention(data: any) {
// 使用导入的 http 对象发起 PUT 请求,请求地址为 '/api/intention/editIntention'
// 将 data 对象作为请求的主体
return http.put('/api/intention/editIntention', data)
}
// 编辑老人标签
/**
* editElderLabel
* @param data - any
* @returns Promise
*/
export function editElderLabel(data: any) {
// 使用导入的 http 对象发起 PUT 请求,请求地址为 '/api/intention/editElderLabel'
// 将 data 对象作为请求的主体
return http.put('/api/intention/editElderLabel', data)
}
// 执行回访计划
/**
* executeVisitPlan访
* @param data - 访 any
* @returns Promise 访
*/
export function executeVisitPlan(data: any) {
// 使用导入的 http 对象发起 PUT 请求,请求地址为 '/api/intention/executeVisitPlan'
// 将 data 对象作为请求的主体
return http.put('/api/intention/executeVisitPlan', data)
}
// 根据编号获取编辑意向客户标签
/**
* getEditElderLabelById
* @param data - any
* @returns Promise
*/
export function getEditElderLabelById(data: any) {
// 使用导入的 http 对象发起 GET 请求,请求地址为 '/api/intention/getEditElderLabelById'
// 将 data 对象展开作为请求的参数
return http.get('/api/intention/getEditElderLabelById', {
params: {
...data
@ -178,8 +329,14 @@ export function getEditElderLabelById(data: any) {
})
}
// 根据编号获取意向客户标签
/**
* getElderLabelById
* @param data - any
* @returns Promise
*/
export function getElderLabelById(data: any) {
// 使用导入的 http 对象发起 GET 请求,请求地址为 '/api/intention/getElderLabelById'
// 将 data 对象展开作为请求的参数
return http.get('/api/intention/getElderLabelById', {
params: {
...data
@ -187,8 +344,14 @@ export function getElderLabelById(data: any) {
})
}
// 根据编号获取意向客户
/**
* getIntentById
* @param data - any
* @returns Promise
*/
export function getIntentById(data: any) {
// 使用导入的 http 对象发起 GET 请求,请求地址为 '/api/intention/getIntentById'
// 将 data 对象展开作为请求的参数
return http.get('/api/intention/getIntentById', {
params: {
...data
@ -196,8 +359,14 @@ export function getIntentById(data: any) {
})
}
//客户标签
/**
* listLabel
* @param data - any
* @returns Promise
*/
export function listLabel(data: any) {
// 使用导入的 http 对象发起 GET 请求,请求地址为 '/intention/listLabel'
// 将 data 对象展开作为请求的参数
return http.get('/intention/listLabel', {
params: {
...data
@ -205,29 +374,12 @@ export function listLabel(data: any) {
})
}
// 分页查询沟通记录
/**
* pageCommunicationRecord
* @param data - any
* @returns Promise
*/
export function pageCommunicationRecord(data: any) {
return http.get('/intention/pageCommunicationRecord', {
params: {
...data
}
})
}
// 分页搜索老人
export function pageSearchElderByKey(data: any) {
return http.get('/intention/pageSearchElderByKey', {
params: {
...data
}
})
}
// 分页查询回访计划
export function pageVisitPlan(data: any) {
return http.get('/intention/pageVisitPlan', {
params: {
...data
}
})
}
// 使用导入的 http 对象发起 GET 请求,请求地址为 '/intention/pageCommunicationRecord'
// 将 data 对象展开作为请求的参数
return http.get

@ -1,23 +1,40 @@
// 从 "@/utils" 模块导入名为 http 的对象,推测该对象是用于发起 HTTP 请求的工具,可能是对 axios 等请求库的封装
import { http } from "@/utils";
// 定义一个接口 IPageSourceByKey用于描述分页查询来源渠道时的请求参数结构
interface IPageSourceByKey {
// 名称,用于筛选来源渠道,类型为字符串
name: string;
// 来源渠道名称,用于筛选来源渠道,类型为字符串
sourceName: string;
}
// 定义一个接口 IAddSource用于描述新增来源渠道时的数据结构
interface IAddSource {
// 来源渠道的 ID类型为字符串
id: string;
// 来源渠道的名称,类型为字符串
name: string;
}
// 定义一个接口 IGetSourceById用于描述根据编号查询来源渠道时的请求参数结构
interface IGetSourceById {
// 来源渠道 ID类型为字符串
sourceId: string;
}
// 分页查询来源渠道
/**
* pageSourceByKey
* @param data - IPageSourceByKey
* @returns Promise
*/
export async function pageSourceByKey(data: IPageSourceByKey) {
// 因为后台返回的字段与前端表单数据的prop不一样但是组件封装是需要一样的所以请求前增加一些这两个字段
Reflect.has(data, "name") ? (data.sourceName = data.name) : "";
// 因为后台返回的字段与前端表单数据的 prop 不一样,
// 但是组件封装需要一样,所以请求前增加一些这两个字段
// 如果 data 对象中有 "name" 字段,则将其值赋给 "sourceName" 字段
Reflect.has(data, "name")? (data.sourceName = data.name) : "";
// 使用导入的 http 对象发起 GET 请求,请求地址为 "/api/source/pageSourceByKey"
// 将处理后的 data 对象展开作为请求的参数
return http.get("/api/source/pageSourceByKey", {
params: {
...data
@ -25,13 +42,25 @@ export async function pageSourceByKey(data: IPageSourceByKey) {
});
}
// 新增来源渠道
/**
* addSource
* @param data - IAddSource
* @returns Promise
*/
export function addSource(data: IAddSource) {
// 使用导入的 http 对象发起 POST 请求,请求地址为 "/api/source/addSource"
// 将 data 对象作为请求的主体
return http.post("/api/source/addSource", data);
}
// 根据编号查询来源渠道
/**
* getSourceById
* @param data - IGetSourceById
* @returns Promise
*/
export async function getSourceById(data: IGetSourceById) {
// 使用导入的 http 对象发起 GET 请求,请求地址为 "/api/source/getSourceById"
// 将 data 对象展开作为请求的参数
return http.get("/api/source/getSourceById", {
params: {
...data
@ -39,13 +68,25 @@ export async function getSourceById(data: IGetSourceById) {
});
}
// 编辑来源渠道
/**
* editSource
* @param data - IAddSource
* @returns Promise
*/
export function editSource(data: IAddSource) {
// 使用导入的 http 对象发起 PUT 请求,请求地址为 "/api/source/editSource"
// 将 data 对象作为请求的主体
return http.put("/api/source/editSource", data);
}
// 删除来源渠道
/**
* deleteSource
* @param data - IGetSourceById
* @returns Promise
*/
export async function deleteSource(data: IGetSourceById) {
// 使用导入的 http 对象发起 DELETE 请求,请求地址为 "/api/source/deleteSource"
// 将 data 对象展开作为请求的参数
return http.delete("/api/source/deleteSource", {
params: {
...data

@ -1,60 +1,106 @@
import { http } from '@/utils'
// 从 '@/utils' 模块导入名为 http 的对象,推测该对象是用于发起 HTTP 请求的工具,可能是对 axios 等请求库的封装
import { http } from '@/utils';
// 定义一个接口 ILoginForm用于描述登录表单的数据结构
interface ILoginForm {
pass: string
phone: string
// 密码,类型为字符串
pass: string;
// 电话号码,类型为字符串
phone: string;
}
// 定义一个接口 ISendCodeForm用于描述发送验证码表单的数据结构
interface ISendCodeForm {
pass: string
account: string
// 密码,类型为字符串
pass: string;
// 账号,类型为字符串
account: string;
}
// 定义一个接口 IForgetPass用于描述忘记密码时的数据结构
interface IForgetPass {
code: string
pass: string
account: string
// 验证码,类型为字符串
code: string;
// 新密码,类型为字符串
pass: string;
// 账号,类型为字符串
account: string;
}
// 定义一个接口 IEditPass用于描述修改密码时的数据结构
interface IEditPass {
newPass: string
oldPass: string
// 新密码,类型为字符串
newPass: string;
// 旧密码,类型为字符串
oldPass: string;
}
// 定义一个类 IEditPassImpl实现了 IEditPass 接口,用于表示修改密码的数据实体
export class IEditPassImpl implements IEditPass {
newPass: string
oldPass: string
// 新密码属性
newPass: string;
// 旧密码属性
oldPass: string;
// 构造函数,接收新密码和旧密码作为参数,并赋值给对应的属性
constructor(newPass: string, oldPass: string) {
this.newPass = newPass
this.oldPass = oldPass
this.newPass = newPass;
this.oldPass = oldPass;
}
}
// 登录
/**
* getLogin
* @param data - ILoginForm
* @returns Promise
*/
export function getLogin(data: ILoginForm) {
return http.post('/api/account/login', data)
// 使用导入的 http 对象发起 POST 请求,请求地址为 '/api/account/login'
// 将 data 对象作为请求的主体
return http.post('/api/account/login', data);
}
// 发送验证码
/**
* sendCode
* @param data - ISendCodeForm
* @returns Promise
*/
export async function sendCode(data: ISendCodeForm) {
// 使用导入的 http 对象发起 GET 请求,请求地址为 '/api/account/sendCode'
// 将 data 对象展开作为请求的参数
return http.get('/api/account/sendCode', {
params: {
...data
}
})
});
}
// 忘记密码
/**
* forgetPass
* @param data - IForgetPass
* @returns Promise
*/
export async function forgetPass(data: IForgetPass) {
return http.put('/api/account/forget', data)
// 使用导入的 http 对象发起 PUT 请求,请求地址为 '/api/account/forget'
// 将 data 对象作为请求的主体
return http.put('/api/account/forget', data);
}
// 修改密码
/**
* editPass
* @param data - IEditPass
* @returns Promise
*/
export async function editPass(data: IEditPass) {
return http.put('/api/account/edit', data)
// 使用导入的 http 对象发起 PUT 请求,请求地址为 '/api/account/edit'
// 将 data 对象作为请求的主体
return http.put('/api/account/edit', data);
}
// 退出登录
/**
* getLogout退
* @returns Promise 退
*/
export async function getLogout() {
return http.delete('/api/account/logout')
// 使用导入的 http 对象发起 DELETE 请求,请求地址为 '/api/account/logout'
return http.delete('/api/account/logout');
}

@ -1,109 +0,0 @@
<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>

@ -1,207 +0,0 @@
<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>

@ -1,12 +0,0 @@
// 定义一个类型别名 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;
};

@ -1,45 +0,0 @@
<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>

@ -1,88 +0,0 @@
<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>

@ -1,47 +0,0 @@
<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>

@ -1,103 +0,0 @@
<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>

@ -1,87 +0,0 @@
## 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" | 表头的作用域插槽 |

@ -1,397 +0,0 @@
<!-- 📚📚📚 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>

@ -1,84 +0,0 @@
// 从 "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>[];
}

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

@ -1,133 +0,0 @@
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
}

@ -1,55 +0,0 @@
<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>

@ -1,117 +0,0 @@
<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>

@ -1,142 +0,0 @@
<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>

@ -1,49 +0,0 @@
<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,118 +0,0 @@
<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>

@ -1,30 +0,0 @@
<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>

@ -1,90 +0,0 @@
<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>

@ -1,189 +0,0 @@
<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>

@ -1,47 +0,0 @@
<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,27 +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 {
export type MessageType = '' | 'success' | 'warning' | 'info' | 'error'
// 在 HandleData 命名空间内,定义一个类型别名 MessageType
// MessageType 的值可以是空字符串,或者是'success'、'warning'、'info'、'error' 中的一个
export type MessageType = '' |'success' | 'warning' | 'info' | 'error'
}

@ -1,4 +1,6 @@
// 从 element-plus 库中导入 ElMessageBox 和 ElMessage 组件,分别用于显示确认框和提示消息
import { ElMessageBox, ElMessage } from 'element-plus'
// 从 './interface' 文件中导入 HandleData 命名空间,用于获取其中定义的类型
import { HandleData } from './interface'
/**
@ -9,26 +11,42 @@ import { HandleData } from './interface'
* @param {String} confirmType icon(, warning)
* @return Promise
*/
// 定义一个泛型函数 useHandleDataP 表示 api 函数参数的类型R 表示 api 函数返回值的类型
export const useHandleData = <P = any, R = any>(
// api 是一个函数,接收类型为 P 的参数并返回一个 PromisePromise 的解析值类型为 R
api: (params: P) => Promise<R>,
// params 是 api 函数的参数,类型与 api 函数的参数类型一致
params: Parameters<typeof api>[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 () => {
const res = await api(params)
if (!res) return reject(false)
ElMessage({
type: 'success',
message: `${message}成功!`
})
resolve(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)
})
})
}

@ -1,10 +1,21 @@
// 从 '@/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

@ -4,16 +4,24 @@ import { ref, computed } from 'vue'
* @description
* @param {String} rowKey id
* */
// 定义一个名为 useSelection 的函数,用于处理表格多选数据的操作
// 接受一个可选参数 rowKey默认值为 'id',表示行数据中用于唯一标识的键名
export const useSelection = (rowKey = 'id') => {
// 是否选中数据
// 创建一个响应式变量 isSelected用于表示是否选中了数据
// 初始值为 false类型为 boolean
const isSelected = ref<boolean>(false)
// 选中的数据列表
// 创建一个响应式变量 selectedList用于存储选中的数据列表
// 初始值为空数组,类型为 any[]
const selectedList = ref([])
// 当前选中的所有ids(数组)可根据项目自行配置id字段
// 创建一个计算属性 selectedListIds用于获取当前选中的所有数据的 id 数组
// 计算属性会根据依赖项(这里是 selectedList的变化而自动更新
const selectedListIds = computed((): string[] => {
// 初始化一个空数组 ids 用于存储 id
const ids: string[] = []
// 遍历 selectedList 中的每一项数据
selectedList.value.forEach(item => ids.push(item[rowKey]))
// 返回包含所有 id 的数组
return ids
})
@ -22,11 +30,18 @@ export const useSelection = (rowKey = 'id') => {
* @param {Array} rowArr
* @return void
*/
// 定义一个函数 selectionChange用于处理表格多选数据的变化事件
// 接受一个参数 rowArr类型为 any[],表示当前选中的所有数据
const selectionChange = (rowArr: any) => {
rowArr.length === 0 ? (isSelected.value = false) : (isSelected.value = true)
selectedList.value = rowArr
}
// 如果选中的数据列表为空,则将 isSelected 设置为 false
// 否则将 isSelected 设置为 true
rowArr.length === 0? (isSelected.value = false) : (isSelected.value = true)
// 更新 selectedList 为当前选中的数据列表
selectedList.value = rowArr
}
// 返回一个包含 isSelected、selectedList、selectedListIds 和 selectionChange 的对象
// 以便在其他地方使用这些变量和函数来处理表格多选数据
return {
isSelected,
selectedList,

@ -8,160 +8,196 @@ import { reactive, computed, toRefs } from 'vue'
* @param {Boolean} isPageable (true)
* @param {Function} dataCallBack ()
* */
// 定义一个名为 useTable 的函数,用于封装表格页面的操作方法
// api 是获取表格数据的 API 方法,是必传参数
// initParam 是获取数据的初始化参数,非必传,默认值为空对象
// isPageable 表示是否有分页功能,非必传,默认值为 true
// dataCallBack 是对后台返回的数据进行处理的方法,非必传
export const useTable = (
api: (params: any) => Promise<any>,
initParam: object = {},
isPageable = true,
dataCallBack?: (data: any) => any
) => {
// 使用 reactive 创建一个响应式的 state 对象,包含表格数据、分页数据、查询参数等
const state = reactive<Table.TableStateProps>({
// 表格数据
// 表格数据,初始值为空数组
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
// 获取分页查询参数
get: () => {
return {
pageNum: state.pageable.pageNum,
pageSize: state.pageable.pageSize
}
},
// 设置分页查询参数,这里暂时为空,可根据需要实现
set: (newVal: any) => { // 我是分页更新之后的值
}
},
set: (newVal: any) => { // 我是分页更新之后的值
}
})
})
/**
* @description
* @return void
* */
// 定义一个异步函数 getTableList用于获取表格数据
const getTableList = async () => {
try {
// 先把初始化参数和分页参数放到总参数里面
Object.assign(
state.totalParam,
initParam,
isPageable ? pageParam.value : {}
)
//请求前格式化数据
if (state.totalParam.consultDate) {
state.totalParam.startTime = state.totalParam.consultDate[0]
state.totalParam.endTime = state.totalParam.consultDate[1]
delete state.totalParam.consultDate
}
let { data } = await api({
...state.searchInitParam,
...state.totalParam
})
dataCallBack && (data = dataCallBack(data))
// 获取当前表格数据
state.tableData = isPageable ? data.list : data
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)
// 从返回的数据中获取分页信息
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]
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 : {}
)
}
Object.assign(
state.totalParam,
nowSearchParam,
isPageable ? pageParam.value : {}
)
}
/**
* @description
* @param {Object} resPageable
* @return void
* */
// 定义一个函数 updatePageable用于更新分页信息
const updatePageable = (resPageable: Table.Pageable) => {
Object.assign(state.pageable, resPageable)
}
// 使用 Object.assign 方法将后台返回的分页数据合并到 state.pageable 中
Object.assign(state.pageable, resPageable)
}
/**
* @description
* @return void
* */
// 定义一个函数 search用于进行表格数据查询
const search = () => {
state.pageable.pageNum = 1
updatedTotalParam()
getTableList()
}
// 将当前页数设置为 1
state.pageable.pageNum = 1
// 更新总参数
updatedTotalParam()
// 获取表格数据
getTableList()
}
/**
* @description
* @return void
* */
// 定义一个函数 reset用于重置表格数据
const reset = () => {
state.pageable.pageNum = 1
state.searchParam = {}
// 重置搜索表单的时,如果有默认搜索参数,则重置默认的搜索参数
Object.keys(state.searchInitParam).forEach(key => {
state.searchParam[key] = state.searchInitParam[key]
})
updatedTotalParam()
getTableList()
}
// 将当前页数设置为 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) => {
state.pageable.pageNum = 1
state.pageable.pageSize = val
getTableList()
}
// 将当前页数设置为 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.pageable.pageNum = val
// 获取表格数据
getTableList()
}
// 返回一个包含 state 的响应式引用、获取表格数据的函数、查询函数、重置函数、处理每页条数改变和当前页改变的函数以及更新总参数的函数
return {
...toRefs(state),
getTableList,

@ -1,15 +1,21 @@
// 从项目路径 '@/components/SvgIcon/index.vue' 导入 SvgIcon 组件
import SvgIcon from '@/components/SvgIcon/index.vue'
// 从 'vue' 中导入 App 类型,用于表示 Vue 应用实例
import { App } from 'vue'
// 获取上下文 require.context检索的目录是否检索子文件夹正则表达式
// 返回值是一个函数(传入路径可以导入文件)
// 通过静态方法keys可以检索所有文件路径
// 通过.prototype可以查看所有静态方法
// 这里指定检索当前目录下的 './svg' 文件夹,不检索子文件夹,匹配所有以.svg 结尾的文件
const svgRequired = require.context('./svg', false, /\.svg$/)
// 遍历所有匹配的 SVG 文件路径
svgRequired.keys().forEach(item => svgRequired(item))
// 导出一个默认函数,该函数接收一个 Vue 应用实例 app 作为参数
export default (app: App) => {
// 使用 app.component 方法将 SvgIcon 组件注册为全局组件,别名为'svg-icon'
// 这样在整个 Vue 应用中都可以使用 <svg-icon> 标签来使用该组件
app.component('svg-icon', SvgIcon)
}

@ -1,34 +1,49 @@
<template>
<!-- Element Plus 的下拉菜单组件触发方式为点击 -->
<el-dropdown trigger="click">
<!-- 下拉菜单的触发元素 -->
<span class="navbar-bg-hover">
<!-- 显示用户头像图片地址通过 :src 绑定 avator 变量 -->
<img :src="avator" />
<!-- 如果用户名存在显示用户名使用了暗色模式下文字颜色为黑色的样式 -->
<p v-if="username" class="dark:text-black">{{ username }}</p>
</span>
<!-- 下拉菜单内容模板 -->
<template #dropdown>
<!-- Element Plus 的下拉菜单 -->
<el-dropdown-menu class="logout">
<!-- 下拉菜单项点击触发 editPassShow 方法 -->
<el-dropdown-item @click="editPassShow">
<!-- 自定义的 SVG 图标组件显示修改密码图标 -->
<svg-icon
@click="editPassShow"
icon="verify"
size="14"
style="margin-right: 5px"
></svg-icon>
<!-- 菜单项文本 -->
修改密码
</el-dropdown-item>
<!-- 下拉菜单项点击触发 logout 方法 -->
<el-dropdown-item @click="logout">
<!-- 自定义的 SVG 图标组件显示退出登录图标 -->
<svg-icon
@click="logout"
icon="logout"
size="14"
style="margin-right: 5px"
></svg-icon>
<!-- 菜单项文本 -->
退出登录
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<!-- Element Plus 的对话框组件用于修改密码 -->
<el-dialog v-model="editPassVisible" title="修改密码">
<!-- 对话框内容模板 -->
<template #default>
<!-- Element Plus 的表单组件绑定表单数据表单规则和表单引用 -->
<el-form
:model="formData"
class="forget-pass-form"
@ -36,7 +51,9 @@
:rules="editPassRules"
size="large"
>
<!-- 表单项对应旧密码字段 -->
<el-form-item prop="oldPass">
<!-- Element Plus 的输入框组件绑定旧密码数据显示为密码输入框 -->
<el-input
v-model="formData.oldPass"
placeholder="旧密码"
@ -46,8 +63,9 @@
show-password
/>
</el-form-item>
<!-- 表单项对应新密码字段 -->
<el-form-item prop="newPass">
<!-- Element Plus 的输入框组件绑定新密码数据显示为密码输入框 -->
<el-input
v-model="formData.newPass"
placeholder="新密码"
@ -57,8 +75,9 @@
show-password
/>
</el-form-item>
<!-- 表单项对应确认密码字段 -->
<el-form-item prop="confirmPassword">
<!-- Element Plus 的输入框组件绑定确认密码数据显示为密码输入框 -->
<el-input
v-model="formData.confirmPassword"
placeholder="确认密码"
@ -70,9 +89,12 @@
</el-form-item>
</el-form>
</template>
<!-- 对话框底部模板 -->
<template #footer>
<div class="dialog-footer">
<!-- 取消按钮点击关闭对话框 -->
<el-button @click="editPassVisible = false">取消</el-button>
<!-- 确认修改按钮点击触发 handleEditPass 方法显示加载状态 -->
<el-button
class="bg-blue"
type="primary"
@ -87,43 +109,61 @@
</template>
<script setup lang="ts">
// Vuex
import store from '@/store'
//
import { useRenderIcon } from '@/hooks/useIcons'
// Vue
import { reactive, ref } from 'vue'
// API
import { editPass, forgetPass, getLogout, IEditPassImpl } from '@/apis/user'
// Element Plus
import { ElMessage, FormInstance, FormRules } from 'element-plus'
// Vuex
const avator = store.state.app.userPeofile.avator
// Vuex
const username = store.state.app.userPeofile.username
//
const editPassVisible = ref(false)
//
const ruleFormRef = ref<FormInstance | null>(null)
//
const loading = ref(false)
//
const formData = ref({
oldPass: '12345',
newPass: '123456',
confirmPassword: '123456'
})
//
//
const editPassShow = () => {
editPassVisible.value = true
}
//
//
const handleEditPass = (formRef: FormInstance | null) => {
//
loading.value = true
//
if (!formRef) return
//
formRef.validate(async (valid, fields) => {
if (valid) {
// API
// const res: any = await editPass({
// oldPass: formData.value.oldPass,
// newPass: formData.value.newPass
// })
// 使 API
const res: any = await editPass(new IEditPassImpl('1', '1'))
if (res.code === 200) {
// 退
logout()
} else {
//
loading.value = false
ElMessage({
message: res.msg,
@ -131,63 +171,84 @@ const handleEditPass = (formRef: FormInstance | null) => {
})
}
} else {
//
loading.value = false
return fields
}
})
}
// 退
// 退
const logout = async () => {
// 退 API
await getLogout()
// Vuex 退
store.dispatch('app/logout')
//
ElMessage({
message: '操作成功',
type: 'success'
})
}
/* 修改密码校验 */
/* 修改密码校验规则 */
const editPassRules = reactive<FormRules>({
oldPass: [
{
//
validator: (rule, value, callback) => {
//
let oldPass = value?.trim()
if (oldPass === '') {
//
callback(new Error('旧密码不能为空'))
} else {
//
callback()
}
},
//
trigger: 'blur'
}
],
newPass: [
{
//
validator: (rule, value, callback) => {
//
const newPass = value?.trim()
if (newPass === '') {
//
callback(new Error('新密码不能为空'))
} else {
//
callback()
}
},
//
trigger: 'blur'
}
],
confirmPassword: [
{
//
validator: (rule, value, callback) => {
//
const confirmPass = value?.trim()
//
const newPass = formData.value.newPass.trim()
if (confirmPass === '') {
//
callback(new Error('确认密码不能为空'))
} else if (confirmPass !== newPass) {
} else if (confirmPass!== newPass) {
//
callback('与新密码不一致')
} else {
//
callback()
}
},
//
trigger: 'blur'
}
]
@ -197,9 +258,13 @@ const editPassRules = reactive<FormRules>({
<style lang="scss" scoped>
.navbar-bg-hover {
img {
//
border-radius: 50%;
//
height: 22px;
//
width: 22px;
//
margin-right: 10px;
}
}

@ -1,11 +1,21 @@
<template>
<!-- Element Plus 的面包屑导航组件 el-breadcrumb -->
<!-- 添加了自定义的类名 "breadcrumb" 用于样式设置 -->
<!-- separator="/" 设置面包屑导航的分隔符为 "/" -->
<el-breadcrumb class="breadcrumb" separator="/">
<!-- 过渡组组件 transition-group用于实现面包屑导航项的过渡效果 -->
<!-- 定义了过渡的名称为 "breadcrumb" -->
<transition-group name="breadcrumb">
<!-- 循环渲染面包屑导航项 el-breadcrumb-item -->
<!-- v-for="item in breadcrumbList" 表示遍历 breadcrumbList 数组中的每一项 -->
<!-- :key="item.path" 为每个面包屑导航项设置唯一的 key -->
<!-- :to="{ path: item.path }" 为面包屑导航项设置跳转的路由路径 -->
<el-breadcrumb-item
v-for="item in breadcrumbList"
:key="item.path"
:to="{ path: item.path }"
>
<!-- 显示面包屑导航项的标题 item.meta.title 中获取如果不存在则显示空 -->
{{ item.meta?.title }}
</el-breadcrumb-item>
</transition-group>
@ -13,32 +23,47 @@
</template>
<script setup lang="ts">
// Vue watch ref
import { watch, ref } from 'vue'
// vue-router useRoute
import { useRoute } from 'vue-router'
// vue-router RouteRecordRaw
import { RouteRecordRaw } from 'vue-router'
//
const route = useRoute()
//
// breadcrumbList
const breadcrumbList = ref<RouteRecordRaw[]>([])
// initBreadcrumbList
const initBreadcrumbList = () => {
// layout
// layout layout 使 route.matched.slice(1)
breadcrumbList.value = route.matched.slice(1)
}
// route
watch(
route,
() => {
// route initBreadcrumbList
initBreadcrumbList()
},
{ deep: true, immediate: true }
{
// deep: true route
// immediate: true
deep: true,
immediate: true
}
)
</script>
<style lang="scss" scoped>
// .el-breadcrumb
.el-breadcrumb {
//
display: flex;
// 使
align-items: center;
// 10px
margin-left: 10px;
}
</style>

@ -1,32 +1,50 @@
<template>
<!-- 定义一个具有类名 "nav-container" 的顶级 div 元素作为导航栏的容器 -->
<div class="nav-container">
<!-- 导航栏左侧区域类名为 "nav-left" -->
<div class="nav-left">
<!-- 引入并使用名为 Breadcrumb 的组件该组件可能是用于显示面包屑导航 -->
<Breadcrumb />
</div>
<!-- 导航栏右侧区域类名为 "nav-right" -->
<div class="nav-right">
<!-- 引入并使用名为 Avatar 的组件该组件可能是用于显示用户头像等信息 -->
<Avatar class="nav-item" />
</div>
</div>
</template>
<script setup lang="ts">
// components Avatar Vue
import Avatar from './components/Avatar.vue'
// components Breadcrumb Vue
import Breadcrumb from './components/Breadcrumb.vue'
</script>
<style lang="scss" scoped>
// .nav-container
.nav-container {
//
display: flex;
// 使
justify-content: space-between;
// 使
align-items: center;
// 100%
width: 100%;
// 48
height: 48px;
//
overflow: hidden;
//
background-color: #fff;
// 1 线 60%
border-bottom: 1px solid #d8d8d860;
// .nav-right .nav-left
.nav-right,
.nav-left {
//
display: flex;
}
}

@ -1,6 +1,15 @@
<template>
<!-- 定义一个具有类名 "container" div 元素 -->
<div class="container">
<!-- Element Plus 的工具提示组件 el-tooltip -->
<!-- effect="light" 设置工具提示的效果为淡色 -->
<!-- :content="content" 绑定工具提示的内容内容通过计算属性 content 获取 -->
<!-- placement="right" 设置工具提示显示在目标元素的右侧 -->
<el-tooltip effect="light" :content="content" placement="right">
<!-- 自定义的 SVG 图标组件 svg-icon -->
<!-- icon="menu-fold" 设置图标为 "menu-fold" -->
<!-- @click="changeSiderType" 绑定点击事件点击时调用 changeSiderType 方法 -->
<!-- :style="style" 绑定样式样式通过计算属性 style 获取 -->
<svg-icon
icon="menu-fold"
@click="changeSiderType"
@ -11,31 +20,43 @@
</template>
<script setup lang="ts">
// '@/store' useStore 访 Vuex
import { useStore } from '@/store'
// 'vue' computed
import { computed } from 'vue'
// Vuex
const store = useStore()
// content store.state.app.siderType
const content = computed(() => {
return store.state.app.siderType ? '点击折叠' : '点击展开'
return store.state.app.siderType? '点击折叠' : '点击展开'
})
// style store.state.app.siderType
const style = computed(() => {
return store.state.app.siderType ? 'none' : 'transform: rotateY(180deg);'
return store.state.app.siderType? 'none' : 'transform: rotateY(180deg);'
})
// changeSiderType
const changeSiderType = () => {
// 'app/setSiderType' mutation Vuex
store.commit('app/setSiderType')
}
</script>
<style lang="scss" scoped>
// .svg-icon svg-icon
.svg-icon {
// Element Plus
color: var(--el-color-primary);
margin: 0 0 4px 16px !important;
//
vertical-align: middle;
//
outline: none;
// 0.36
transition-duration: 0.36s;
//
cursor: pointer;
}
</style>

@ -1,15 +1,28 @@
<template>
<!-- 使用 v-for 循环遍历 menuList 数组为每个菜单项生成对应的 DOM 结构 -->
<!-- :key="menu.id" 为每个循环项设置唯一的 key用于 Vue 的虚拟 DOM 优化 -->
<template v-for="menu in menuList" :key="menu.id">
<!-- Element Plus 的子菜单组件 el-sub-menu -->
<!-- v-if="menu.children && menu.children.length > 0" 只有当菜单有子菜单时才渲染该子菜单 -->
<!-- :index="menu.name + ''" 设置子菜单的索引 menu.name 转换为字符串类型 -->
<el-sub-menu
v-if="menu.children && menu.children.length > 0"
:index="menu.name + ''"
>
<!-- 子菜单的标题模板 #title -->
<template #title>
<!-- 如果菜单有 meta.icon 属性渲染自定义的 SVG 图标组件 svg-icon -->
<svg-icon
v-if="menu.meta.icon"
:icon="menu.meta.icon"
class="sub-menu-icon"
></svg-icon>
<!-- Element Plus 的工具提示组件 el-tooltip -->
<!-- effect="light" 设置工具提示的效果为淡色 -->
<!-- :content="menu.meta.title" 绑定工具提示的内容为菜单的标题 -->
<!-- placement="top-start" 设置工具提示显示在目标元素的顶部偏左位置 -->
<!-- :offset="-10" 设置工具提示与目标元素的偏移量为 -10 像素 -->
<!-- :disabled="!showTooltip" 根据 showTooltip 的值决定是否禁用工具提示 -->
<el-tooltip
effect="light"
:content="menu.meta.title"
@ -17,24 +30,40 @@
:offset="-10"
:disabled="!showTooltip"
>
<!-- 用于显示菜单标题的 span 元素 -->
<!-- ref="menuTextRef" 为元素设置引用方便在脚本中获取 -->
<!-- @mouseover="hoverMenu" 绑定鼠标移入事件调用 hoverMenu 方法 -->
<span ref="menuTextRef" @mouseover="hoverMenu">{{
menu.meta.title
}}</span>
menu.meta.title
}}</span>
</el-tooltip>
</template>
<!-- 递归调用 MenuItem 组件传入当前菜单的子菜单列表 -->
<MenuItem :menuList="menu.children" />
</el-sub-menu>
<!-- Element Plus 的菜单选项组件 el-menu-item -->
<!-- v-else 当菜单没有子菜单时渲染该菜单选项 -->
<!-- :index="menu.name + ''" 设置菜单选项的索引 menu.name 转换为字符串类型 -->
<!-- @click="handleMenuClick(menu.name)" 绑定点击事件调用 handleMenuClick 方法并传入菜单的 name -->
<el-menu-item
:index="menu.name + ''"
@click="handleMenuClick(menu.name)"
v-else
>
<!-- 如果菜单有 meta.icon 属性渲染自定义的 SVG 图标组件 svg-icon -->
<svg-icon
v-if="menu.meta.icon"
:icon="menu.meta.icon"
class="sub-menu-icon"
></svg-icon>
<!-- 菜单选项的标题模板 #title -->
<template #title>
<!-- Element Plus 的工具提示组件 el-tooltip -->
<!-- effect="light" 设置工具提示的效果为淡色 -->
<!-- :content="menu.meta.title" 绑定工具提示的内容为菜单的标题 -->
<!-- placement="top-start" 设置工具提示显示在目标元素的顶部偏左位置 -->
<!-- :offset="-10" 设置工具提示与目标元素的偏移量为 -10 像素 -->
<!-- :disabled="!showTooltip" 根据 showTooltip 的值决定是否禁用工具提示 -->
<el-tooltip
effect="light"
:content="menu.meta.title"
@ -42,9 +71,12 @@
:offset="-10"
:disabled="!showTooltip"
>
<!-- 用于显示菜单标题的 span 元素 -->
<!-- ref="menuTextRef" 为元素设置引用方便在脚本中获取 -->
<!-- @mouseover="hoverMenu" 绑定鼠标移入事件调用 hoverMenu 方法 -->
<span ref="menuTextRef" @mouseover="hoverMenu">{{
menu.meta.title
}}</span>
menu.meta.title
}}</span>
</el-tooltip>
</template>
</el-menu-item>
@ -52,26 +84,35 @@
</template>
<script setup lang="ts">
// Vue defineProps ref
import { defineProps, ref } from 'vue'
// IRoute
import { IRoute } from '@/router/types'
//
import router from '@/router'
// Props menuList IRoute
export interface Props {
menuList: IRoute[]
}
// Props
defineProps<Props>()
// showTooltip false
const showTooltip = ref(false)
// handleMenuClick
const handleMenuClick = (name: string) => {
// 使 router.push name
router.push({ name })
}
// tooltip
// hoverMenu
const hoverMenu = (e: Event) => {
// HTMLSpanElement
const target = e.target as HTMLSpanElement
//
//
showTooltip.value = target.scrollWidth > target.clientWidth
}
</script>

@ -1,12 +1,25 @@
<template>
<!-- 定义一个具有类名 "sidebar-container" div 元素作为侧边栏的容器 -->
<div class="sidebar-container">
<!-- 定义一个具有类名 "logo-container" div 元素用于放置 logo 和标题 -->
<div class="logo-container">
<!-- 使用 router-link 组件创建一个到根路径 "/" 的链接标题为 "敬老院管理系统" -->
<router-link title="敬老院管理系统" to="/">
<!-- 显示侧边栏 logo 的图片使用相对路径引用项目中的 logo.png 文件 -->
<img class="sidebar-logo" src="@/assets/imgs/logo.png" />
<!-- 显示侧边栏标题的 span 元素 -->
<span class="sidebar-title">敬老院管理系统</span>
</router-link>
</div>
<!-- 使用 Element Plus el-scrollbar 组件为侧边栏添加滚动条 -->
<el-scrollbar>
<!-- 使用 Element Plus el-menu 组件创建侧边栏菜单 -->
<!-- :active-text-color="variables.menuActiveText" 绑定激活菜单项的文本颜色 variables 对象中获取 -->
<!-- :background-color="variables.menuBg" 绑定菜单的背景颜色 variables 对象中获取 -->
<!-- :default-active="active" 绑定默认激活的菜单项通过响应式变量 active 控制 -->
<!-- text-color="#fefefea6" 设置菜单文本的颜色 -->
<!-- unique-opened 确保同一时间只有一个子菜单可以展开 -->
<!-- :collapse="!$store.state.app.siderType" 根据 store app.siderType 的值控制菜单是否折叠 -->
<el-menu
:active-text-color="variables.menuActiveText"
:background-color="variables.menuBg"
@ -15,76 +28,120 @@
unique-opened
:collapse="!$store.state.app.siderType"
>
<!-- 递归调用 MenuItem 组件传入 store app.routeTree 的值作为菜单列表 -->
<MenuItem :menuList="store.state.app.routeTree" />
</el-menu>
</el-scrollbar>
<!-- 定义一个具有类名 "menufold-container" div 元素用于放置菜单折叠按钮 -->
<div class="menufold-container">
<!-- 引入并使用 MenuFold 组件 -->
<MenuFold />
</div>
</div>
</template>
<script setup lang="ts">
// components MenuItem
import MenuItem from './components/MenuItem.vue'
// components MenuFold
import MenuFold from './components/MenuFold.vue'
// variables.module.scss
import variables from '@/styles/variables.module.scss'
// Vue ref watch
import { ref, watch } from 'vue'
// vue-router RouteRecordName useRoute
import { RouteRecordName, useRoute } from 'vue-router'
// Vuex
import store from '@/store'
//
const route = useRoute()
// active 'Home'
let active = ref<RouteRecordName>('Home')
//
//
watch(
//
() => route,
//
newVal => {
// name
if (newVal.name) {
// active name
active.value = newVal.name
}
},
{
// immediate: true
immediate: true,
// deep: true
deep: true
}
)
</script>
<style lang="scss">
// .logo-container logo
.logo-container {
// router-link
.router-link-active {
//
display: flex;
// 48
height: 48px;
// 100%
width: 100%;
// 使
align-items: center;
// 0 10
padding: 0 10px;
// #002140
background-color: #002140;
//
flex-wrap: nowrap;
// .sidebar-logo logo
.sidebar-logo {
// 30
height: 30px;
// 30
width: 30px;
}
// .sidebar-title
.sidebar-title {
//
overflow: hidden;
//
text-overflow: ellipsis;
//
white-space: nowrap;
// 8
margin-left: 8px;
// 18
font-size: 18px;
//
color: #fff;
}
}
}
// .menufold-container
.menufold-container {
// 0
bottom: 0;
// Element Plus 6 -2
box-shadow: 0 0 6px -2px var(--el-color-primary);
// 40
height: 40px;
// 40
line-height: 40px;
//
position: absolute;
// 100%
width: 100%;
// z-index 999使
z-index: 999;
}
</style>

@ -1,17 +1,30 @@
<template>
<!-- 定义一个具有类名 "tags" div 元素 -->
<div class="tags">
<!-- 定义一个一级标题 h1显示文本 "tags" -->
<h1>tags</h1>
</div>
</template>
<script setup lang="ts">
// useTabList
//
// import { useTabList } from '~/composables/useTabList.js'
// useTabList
// activeTab
// tabList
// changeTab
// removeTab
// handleClose
// const { activeTab, tabList, changeTab, removeTab, handleClose } = useTabList()
</script>
<style lang="scss" scoped>
// .tags "tags"
.tags {
// 38
height: 38px;
// 0 1 #888
box-shadow: 0 0 1px #888;
}
</style>

@ -1,19 +1,32 @@
<template>
<!-- Element Plus 的容器组件 el-container添加了自定义类名 "layout-container" -->
<!-- :class="{ hideSidebar: !$store.state.app.siderType }" 根据 store app.siderType 的值动态添加 "hideSidebar" 类名 -->
<el-container
class="layout-container"
:class="{ hideSidebar: !$store.state.app.siderType }"
>
<el-aside :width="$store.state.app.siderType ? '230px' : '64px'">
<!-- Element Plus 的侧边栏组件 el-aside -->
<!-- :width="$store.state.app.siderType? '230px' : '64px'" 根据 store app.siderType 的值动态设置侧边栏宽度 -->
<el-aside :width="$store.state.app.siderType? '230px' : '64px'">
<!-- 引入并使用自定义的 SideBar 组件 -->
<SideBar />
</el-aside>
<!-- Element Plus 的容器组件 el-container添加了自定义类名 "main-container" -->
<el-container class="main-container">
<!-- Element Plus 的头部组件 el-header -->
<el-header>
<!-- 引入并使用自定义的 NavBar 组件 -->
<NavBar />
</el-header>
<!-- Element Plus 的主体组件 el-main -->
<el-main>
<!-- Element Plus 的滚动条组件 el-scrollbar -->
<el-scrollbar>
<!-- 路由视图 router-view使用插槽获取当前要渲染的组件 Component -->
<router-view v-slot="{ Component }">
<!-- 过渡组件 transition设置过渡名称为 "fade-transform"过渡模式为 "out-in" -->
<transition name="fade-transform" mode="out-in">
<!-- 动态组件 component根据 Component 的值渲染对应的组件 -->
<component :is="Component" />
</transition>
</router-view>
@ -24,10 +37,14 @@
</template>
<script setup lang="ts">
// SideBar
import SideBar from './components/SideBar/index.vue'
// NavBar
import NavBar from './components/NavBar/index.vue'
// Vuex
import store from '@/store'
//
// window.onresize = () =>
// (() => {
// /** width app-wrapper
@ -36,8 +53,10 @@ import store from '@/store'
// * width > 990
// */
// // body
// let width = document.body.clientWidth
// // mutation
// if (width > 0 && width <= 760) { //
// store.commit('app/setDeviceType', 'phone')
// } else if (width > 760 && width <= 990) { //
@ -49,27 +68,41 @@ import store from '@/store'
</script>
<style lang="scss" scoped>
// .layout-container
.layout-container {
//
position: relative;
// 100%
width: 100%;
// 100%
height: 100%;
}
// .el-header Element Plus
//
.el-header {
//
position: relative;
// 0!important
padding: 0 !important;
// 48 !important
height: 48px !important;
}
// .el-main Element Plus
//
.el-main {
// 0!important
padding: 0 !important;
// #f0f2f5
background-color: #f0f2f5;
}
// .el-scrollbar Element Plus
.el-scrollbar {
// rgb(246, 246, 246)!important
background-color: rgb(246, 246, 246) !important;
// 10px
padding: 10px;
}
</style>

@ -1,11 +1,17 @@
// 从 '@element-plus/icons-vue' 模块中导入所有的图标组件
import * as ElIcons from '@element-plus/icons-vue'
// 从 'vue' 模块中导入 App 类型,用于表示 Vue 应用实例
import { App } from 'vue'
// 定义一个名为 useElementPlus 的函数,该函数接受一个 App 类型的参数 app
const useElementPlus = (app: App) => {
// 注册图标
// 遍历 ElIcons 对象的所有属性
for (const [key, component] of Object.entries(ElIcons)) {
// 使用 app.component 方法将每个图标组件注册为全局组件
// 组件的名称为属性名 key组件本身为 component
app.component(key, component)
}
}
// 导出 useElementPlus 函数,使其可以在其他模块中被导入和使用
export default useElementPlus

@ -1,3 +1,6 @@
// 从当前目录下的 element-plus 文件中导入名为 useElementPlus 的函数
import useElementPlus from './element-plus'
// 重新导出 useElementPlus 函数,这样其他模块在导入这个文件时
// 可以通过解构赋值等方式获取到 useElementPlus 函数
export { useElementPlus }

@ -1,62 +1,87 @@
// 从 vue-router 库中导入创建路由实例、路由记录类型和哈希路由历史模式的方法
import { createRouter, RouteRecordRaw, createWebHashHistory } from 'vue-router'
// 导入布局组件
import Layout from '@/layout/index.vue'
// 导入 Vuex 存储实例
import store from '@/store'
// 导入初始化动态路由的工具函数
import { initRoutes } from './utils'
// 静态路由
// 定义静态路由数组,包含应用中固定的路由配置
const routes: Array<RouteRecordRaw> = [
{
// 根路径
path: '/',
// 路由名称
name: 'Layout',
// 路由对应的组件
component: Layout,
// 重定向到 /home 路径
redirect: '/home',
// 子路由数组,初始为空
children: []
},
{
// 登录页面路径
path: '/login',
// 路由名称
name: 'Login',
// 路由元信息,包含页面标题
meta: {
title: '登录'
},
// 使用动态导入方式加载登录页面组件
component: () =>
import(/* webpackChunkName: "Login" */ '@/views/login/index.vue')
},
{
// 匹配所有未定义的路径
path: '/:pathMatch(.*)*',
// 路由名称
name: 'NotFound',
// 路由元信息,包含页面标题
meta: {
title: '404'
},
// 使用动态导入方式加载 404 错误页面组件
component: () =>
import(/* webpackChunkName: "404" */ '@/views/error/404.vue')
}
]
// 创建路由实例
const router = createRouter({
// 使用哈希路由历史模式
history: createWebHashHistory(),
// 路由配置
routes
})
// 定义路由白名单,包含无需验证即可访问的路径
const ROUTER_WHITE_LIST = ['/login']
// 全局前置守卫,在每次路由跳转前执行
router.beforeEach(async (to, from, next) => {
// 设置页面标题
document["title"] = to.meta.title + " | 敬老院管理系统"
// 设置页面标题,将目标路由的元信息中的标题与固定后缀拼接
document.title = to.meta.title + ' | 敬老院管理系统'
// .判断访问页面是否在路由白名单地址中,如果存在直接放行
// 判断访问页面路径是否在路由白名单中,如果存在直接放行
if (ROUTER_WHITE_LIST.includes(to.path)) return next()
//.判断 是否有 Token没有重定向到 login
// 判断是否有 Token如果没有则重定向到登录页面
if (!store.state.app.token) return next({ path: '/login', replace: true })
//如果没有初始化动态路由就初始化
// 如果没有初始化动态路由且存在 Token则初始化动态路由
if (!store.state.app.hasAuth && store.state.app.token) {
// 调用初始化动态路由的工具函数
await initRoutes()
// 重新跳转至目标路径
return next({ path: to.path })
}
// 7.正常访问页面
// 正常访问页面,放行路由跳转
next()
})
// 导出路由实例,供应用使用
export default router

@ -1,14 +1,25 @@
/** 路由 */
// 定义一个接口 IRoute用于规范路由对象的结构
export interface IRoute {
// 路由的唯一标识 ID类型为数字
id: number
// 父路由的 ID类型为数字用于表示路由的层级关系
pid: number
// 路由的名称,在路由配置和导航中使用,类型为字符串
name: string
// 路由的路径,用于在浏览器地址栏中显示和导航,类型为字符串
path: string
// 重定向的路径,是一个可选属性,当访问该路由时会自动重定向到指定路径,类型为字符串
redirect?: string
// 路由对应的组件路径,指定该路由应渲染的 Vue 组件的路径,类型为字符串
component: string
// 路由的元数据,是一个对象,包含与路由相关的额外信息
meta: {
// 路由的标题,可用于页面标题、菜单显示等,类型为字符串
title: string
// 路由的图标名称,是一个可选属性,可用于在菜单中显示图标,类型为字符串
icon?: string
}
// 子路由数组,是一个可选属性,用于表示该路由下的子路由列表,元素类型为 IRoute
children?: IRoute[]
}

@ -1,31 +1,53 @@
// 从项目的路由类型文件中导入 IRoute 接口,用于描述路由的结构
import { IRoute } from "@/router/types";
// 从 vue-router 库中导入 Router 和 RouteRecordRaw 类型
// Router 是 Vue Router 的实例类型RouteRecordRaw 用于定义路由记录
import { Router, RouteRecordRaw } from "vue-router";
// 导入项目的路由实例
import router from "./index";
// 导入项目的 Vuex 存储实例
import store from "@/store";
// 从项目的工具函数文件中导入数组去重的工具函数
import { arrayDeduplicationByFiled } from "@/utils/commonUtil";
/** 将后端返回的路由转换成生成树形结构 */
function formatRouteTree(data: IRoute[]) {
// 过滤出父路由,即 pid 为 0 的路由
const parents = data.filter(routeInfo => routeInfo.pid === 0);
// 过滤出子路由,即 pid 不为 0 的路由
const children = data.filter(routeInfo => routeInfo.pid !== 0);
// 调用递归函数将数据转换为树形结构
dataToTree(parents, children);
// 返回转换后的父路由数组,此时父路由数组中包含了子路由信息
return parents;
/**
*
* @param parents
* @param children
*/
function dataToTree(parents: IRoute[], children: IRoute[]) {
// 遍历父路由数组
parents.map(parent => {
// 遍历子路由数组
children.map((child, index) => {
// 如果子路由的 pid 等于父路由的 id
if (child.pid === parent.id) {
// 复制一份子路由数组,避免在原数组上进行操作
const _children: IRoute[] = JSON.parse(JSON.stringify(children));
// 从复制的子路由数组中移除当前处理的子路由
_children.splice(index, 1);
// 递归调用 dataToTree 函数,处理当前子路由的子路由
dataToTree([child], _children);
if (parent.children) {
// 添加
// 如果父路由已经有子路由数组,将当前子路由添加到该数组中
parent.children.push(child);
// 菜单去重
// 对父路由的子路由数组进行去重操作,根据 id 字段去重
parent.children = arrayDeduplicationByFiled(parent.children, "id");
} else {
// 如果父路由还没有子路由数组,创建一个包含当前子路由的数组
parent.children = [child];
}
}
@ -36,46 +58,66 @@ function formatRouteTree(data: IRoute[]) {
/** 将树形结构路由转化成真实的路由 */
function generateRouter(routeTree: IRoute[]) {
// 遍历树形结构的路由数组
const newRoutes = routeTree.map(route => {
// 创建一个新的路由记录对象
const _route: RouteRecordRaw = {
// 路由的路径
path: route.path,
// 路由的名称
name: route.name,
// 路由的元信息
meta: route.meta,
// 路由的重定向路径
redirect: route.redirect,
component: () => import("@/views/" + route.component), // 注意views目录下才能引入否则ts不识别
// 使用动态导入的方式加载路由对应的组件,组件位于 @/views 目录下
component: () => import("@/views/" + route.component),
// 子路由数组,初始为空
children: []
};
// 如果当前路由有子路由
if (route.children) {
// 递归调用 generateRouter 函数,处理子路由
_route.children = generateRouter(route.children);
}
// 返回新创建的路由记录对象
return _route;
});
// 返回转换后的路由记录数组
return newRoutes;
}
//初始化动态路由
// 初始化动态路由的函数
export async function initRoutes() {
// 调用 Vuex 的 action获取路由树形结构数据
await store.dispatch("app/getRouterTree");
// 将获取到的路由树形结构数据转换为真实的路由记录数组
const newRoutes = generateRouter(store.state.app.routeTree);
// 遍历新的路由记录数组,将每个路由添加到名为 "Layout" 的父路由下
newRoutes.forEach(route => router.addRoute("Layout", route));
}
//清除路由
// 清除路由的函数
export function clearRoutes() {
//删除之前注册的路由 默认状态下只有三个,如果超过三个删除多余的路由
// 定义默认的路由名称数组
const defaultRoutes = ["Layout", "Login", "NotFound"];
// 获取当前路由实例中的所有路由记录
const currentRoutes = router.getRoutes();
// 如果当前路由记录的数量不等于 3
if (currentRoutes.length != 3) {
//执行删除
// 遍历当前的路由记录数组
currentRoutes.forEach(item => {
// 如果当前路由的名称不在默认路由名称数组中
if (!defaultRoutes.includes(String(item.name))) {
// 从路由实例中移除该路由
router.removeRoute(String(item.name));
}
});
}
}
// 导出 formatRouteTree 和 generateRouter 函数,供其他模块使用
export { formatRouteTree, generateRouter };

@ -1,29 +1,46 @@
// 从 vue 库中导入 InjectionKey 类型,用于在 Vue 3 中为 provide 和 inject 提供类型约束
import { InjectionKey } from 'vue'
// 从 vuex 库中导入创建 store 的函数、基础的 useStore 函数和 Store 类型
import { createStore, useStore as baseUseStore, Store } from 'vuex'
// 从项目的 store 类型文件中导入根状态类型
import { IRootState } from '@/store/types'
// 从当前模块的 app 子模块中导入名为 store 的对象
import { store as app } from './modules/app'
// 从当前模块的工具文件中导入自定义的 CommonStore 类型
import { CommonStore } from './utils'
// 导入 vuex-persistedstate 插件,用于将 store 中的状态持久化到本地存储
import createPersistedState from 'vuex-persistedstate'
// 定义一个包含所有模块的对象,这里目前只有 app 模块
export const modules = {
app
}
// injectionKey约束state类型
// 定义一个注入键,用于在使用 useStore 时约束 state 的类型
// 这里使用 Symbol 创建一个唯一的键,类型为 Store<IRootState>
export const key: InjectionKey<Store<IRootState>> = Symbol()
// 创建一个 Vuex store 实例,指定根状态类型为 IRootState
const store = createStore<IRootState>({
// 注册模块
modules,
// 应用插件
plugins: [
// 使用 vuex-persistedstate 插件
createPersistedState({
// 存储在本地存储中的键名
key: 'stateData',
// 指定需要持久化的模块路径,这里只持久化 app 模块
paths: ['app']
})
]
}) as CommonStore
// 定义一个自定义的 useStore 函数,返回类型为 CommonStore
// 调用 baseUseStore 函数并传入注入键,确保类型安全
export function useStore(): CommonStore {
return baseUseStore(key)
}
// 导出创建好的 store 实例,供应用使用
export default store

@ -1,26 +1,46 @@
// 从 vuex 中导入 ActionContext 类型,用于在 action 中获取上下文
import { ActionContext } from 'vuex'
// 从项目的 store 类型文件中导入根状态类型
import { IRootState } from '@/store/types'
// 从当前模块的类型文件中导入当前模块的状态类型
import { IAppState } from './types'
// 从项目的路由类型文件中导入路由类型
import { IRoute } from '@/router/types'
// 从项目的模拟数据获取器中导入获取路由列表的函数
import { getRouterList } from '@/mock/getters'
// 从项目的路由工具文件中导入清除路由和格式化路由树的函数
import { clearRoutes, formatRouteTree } from '@/router/utils'
// 从项目的 API 模块中导入获取用户路由列表的函数(当前注释掉未使用)
// import { getUserRouteList } from '@/apis'
// 从项目的用户 API 模块中导入登录请求函数
import { getLogin } from '@/apis/user'
// 导入项目的路由实例
import router from '@/router'
// 定义默认状态对象
const defaultState = {
// 用户 ID初始为 1
uid: 1,
// 用户令牌,初始为空字符串
token: '',
// 是否有路由权限,初始为 false
hasAuth: false,
// 路由树,初始为空数组
routeTree: [],
// 侧边栏是否展开,初始为 true 表示展开
siderType: true,
// 用户信息,初始为空对象
userPeofile: {},
// 是否记住密码,初始为 false
rememberPWD: false
}
// 导出当前模块的 store 配置
export const store = {
// 开启命名空间,使模块的 action、mutation 和 getter 名称唯一
namespaced: true,
// 模块的状态
state: {
uid: 1,
token: '',
@ -30,38 +50,53 @@ export const store = {
userPeofile: {},
rememberPWD: false
},
// 模块的 mutations用于修改状态
mutations: {
// 设置用户令牌的 mutation
setToken(state: IAppState, token: string) {
state.token = token
},
// 设置路由树的 mutation
setRouteTree(state: IAppState, routeTree: IRoute[]) {
state.routeTree = routeTree
},
// 设置是否有路由权限的 mutation
setAuth(state: IAppState, auth: boolean) {
state.hasAuth = auth
},
// 切换侧边栏展开状态的 mutation
setSiderType(state: IAppState) {
state.siderType = !state.siderType
},
// 设置用户信息的 mutation
setUserProfile(state: IAppState, userPeofile: any) {
state.userPeofile = userPeofile
},
// 设置是否记住密码的 mutation
setRememberPWD(state: IAppState, rememberPWD: boolean) {
state.rememberPWD = rememberPWD
},
// 清除 store 状态的 mutation将状态重置为默认状态
clearStore(state: IAppState) {
Object.assign(state, defaultState)
}
},
// 模块的 actions用于处理异步操作和提交 mutations
actions: {
//登录
// 登录 action
async actionLogin(
// 从上下文获取 commit 和 state
{ commit, state }: ActionContext<IAppState, IRootState>,
// 登录数据
data: any
) {
// 调用登录请求函数
const res: any = await getLogin(data)
// 如果登录成功
if (res.code === 200) {
// 提交设置令牌的 mutation
commit('setToken', res?.data?.token)
// 提交设置用户信息的 mutation
commit('setUserProfile', {
username: res?.data.name,
userid: res?.data.id,
@ -69,22 +104,31 @@ export const store = {
authIdList: res?.data.authIdList
})
}
// 提交设置是否记住密码的 mutation
commit('setRememberPWD', data?.rememberPWD)
// 返回登录请求的响应
return res
},
//获取权限菜单
// 获取权限菜单 action
getRouterTree({ commit, state }: ActionContext<IAppState, IRootState>) {
// 模拟数据
// 模拟获取路由列表
const routeList = getRouterList(state.uid).data as unknown as IRoute[]
// 格式化路由列表为路由树
const routeTree = formatRouteTree(routeList)
// 提交设置路由树的 mutation
commit('setRouteTree', routeTree)
// 提交设置有路由权限的 mutation
commit('setAuth', true)
},
// 点击登出
// 点击登出 action
logout({ commit, state }: ActionContext<IAppState, IRootState>) {
// 提交清除 store 状态的 mutation
commit('clearStore')
// 清除本地存储
localStorage.clear()
// 清除动态添加的路由
clearRoutes()
// 路由跳转到登录页
router.replace('/login')
}
}

@ -1,11 +1,20 @@
// 从项目的路由类型文件中导入 IRoute 接口,用于描述路由的结构
import { IRoute } from '@/router/types'
// 定义一个接口 IAppState用于描述应用状态的结构
export interface IAppState {
// 用户 ID类型为数字
uid: number
// 用户令牌,类型为字符串
token: string
// 是否有路由权限,类型为布尔值
hasAuth: boolean
// 路由树,是一个 IRoute 类型的数组,用于存储路由信息
routeTree: IRoute[]
// 侧边栏是否展开,类型为布尔值
siderType: boolean
// 用户信息,类型为 any表示可以是任意类型的数据
userPeofile: any
// 是否记住密码,类型为布尔值
rememberPWD: boolean
}

@ -1,9 +1,16 @@
// 从 vuex 中导入 ActionContext 类型,它用于在 actions 里获取上下文,能访问 state、getters、mutations 等
import { ActionContext } from 'vuex'
// 从项目的路由配置文件导入路由实例,后续可能会在 actions 里使用路由进行页面跳转
import router from '@/router'
// 导出一个名为 store 的对象,这是一个 Vuex 模块
export const store = {
// 设置为 true 表示开启命名空间,可避免不同模块间的 action、mutation 和 getter 名称冲突
namespaced: true,
// state 是存储数据的地方,这里初始为空对象,后续可添加应用所需的状态数据
state: {},
// mutations 用于修改 state 中的数据,是同步操作,这里初始为空对象,后续可添加具体的 mutation 函数
mutations: {},
// actions 用于处理异步操作,如发起网络请求等,这里初始为空对象,后续可添加具体的 action 函数
actions: {}
}

@ -1,5 +1,11 @@
// 从项目的 app 模块的类型文件中导入 IAppState 类型,该类型描述了 app 模块的状态结构
import { IAppState } from '@/store/modules/app/types'
/**
* IRootState Vuex
* Vuex app
*/
export type IRootState = {
// app 字段的类型为 IAppState意味着根状态中的 app 模块状态遵循 IAppState 类型的定义
app: IAppState
}

@ -1,25 +1,29 @@
/** 智能提示 */
// 从当前目录的 types 文件中导入 IRootState 类型,用于表示根状态
import { IRootState } from './types'
// 从当前目录的 index 文件中导入 modules 对象,该对象包含了所有的 Vuex 模块
import { modules } from './index'
// 从 vuex 库中导入 CommitOptions、DispatchOptions 和 Store 类型
import { CommitOptions, DispatchOptions, Store as VuexStore } from 'vuex'
// 获取modules的类型
// 获取 modules 的类型
type Modules = typeof modules
// 获取所有模块下的mutations
type GetMutation<T> = T extends { mutations: infer G } ? G : never
// 获取所有模块下的 mutations
type GetMutation<T> = T extends { mutations: infer G }? G : never
type GetMutations<T> = {
[K in keyof T]: GetMutation<T[K]>
}
type mutationsObj = GetMutations<Modules>
// 获取所有模块下的actions
type GetAction<T> = T extends { actions: infer G } ? G : never
// 获取所有模块下的 actions
type GetAction<T> = T extends { actions: infer G }? G : never
type GetActions<T> = {
[K in keyof T]: GetAction<T[K]>
}
type actionsObj = GetActions<Modules>
// 获取所有模块下的getters
type GetGetter<T> = T extends { getters: infer G } ? G : never
// 获取所有模块下的 getters
type GetGetter<T> = T extends { getters: infer G }? G : never
type GetGetters<T> = {
[K in keyof T]: GetGetter<T[K]>
}

@ -1,22 +1,27 @@
/* 为.el-breadcrumb__inner 和.el-breadcrumb__inner a 元素设置字体加粗为 400!important 提高优先级 */
.el-breadcrumb__inner,
.el-breadcrumb__inner a {
font-weight: 400 !important;
}
/* 隐藏.el-upload 元素内的 input[type='file'] 元素,!important 提高优先级 */
.el-upload {
input[type='file'] {
display: none !important;
}
}
/* 隐藏.el-upload__input 元素 */
.el-upload__input {
display: none;
}
/* 为.upload-container 元素内的.el-upload 元素设置宽度为 100% */
.upload-container {
.el-upload {
width: 100%;
/* 为.upload-container 元素内的.el-upload 元素的.el-upload-dragger 子元素设置宽度为 100%,高度为 200px */
.el-upload-dragger {
width: 100%;
height: 200px;
@ -24,62 +29,70 @@
}
}
/* 为.el-dropdown-menu 元素设置内边距为 0!important 提高优先级 */
.el-dropdown-menu {
padding: 0 !important;
}
/* 为.el-range-separator 元素设置盒模型为 content-box */
.el-range-separator {
box-sizing: content-box;
}
/* 为.is-dark 元素设置 z-index 为 9999!important 提高优先级 */
.is-dark {
z-index: 9999 !important;
}
/* 重置 el-button 中 icon 的 margin */
/* 重置带有 [class*='el-icon'] 类且后面跟着 span 元素的 margin-left 为 2px!important 提高优先级 */
.reset-margin [class*='el-icon'] + span {
margin-left: 2px !important;
}
/* 自定义 popover 的类名 */
/* 为.pure-popper 元素设置内边距为 0!important 提高优先级 */
.pure-popper {
padding: 0 !important;
}
/* 自定义 tooltip 的类名 */
/* 为.pure-tooltip 元素设置 z-index 为 41000!important 提高优先级,因为右侧操作面板 right-panel 类名的 z-index 为 40000tooltip 需要大于它才能显示 */
.pure-tooltip {
// right-panelz-index40000tooltip
z-index: 41000 !important;
}
/* nprogress 适配 element-plus 的主题色 */
/* 为 #nprogress 元素及其子元素设置样式 */
#nprogress {
& .bar {
/* 为 #nprogress 元素的.bar 子元素设置背景颜色为 --el-color-primary 变量的值,!important 提高优先级 */
&.bar {
background-color: var(--el-color-primary) !important;
}
& .peg {
/* 为 #nprogress 元素的.peg 子元素设置 box-shadow颜色为 --el-color-primary 变量的值,!important 提高优先级 */
&.peg {
box-shadow: 0 0 10px var(--el-color-primary),
0 0 5px var(--el-color-primary) !important;
}
& .spinner-icon {
/* 为 #nprogress 元素的.spinner-icon 子元素设置边框颜色为 --el-color-primary 变量的值 */
&.spinner-icon {
border-top-color: var(--el-color-primary);
border-left-color: var(--el-color-primary);
}
}
/* 全局覆盖element-plus的el-dialog、el-drawer、el-message-box、el-notification组件右上角关闭图标的样式表现更鲜明 */
/* 为.el-dialog__headerbtn 和.el-message-box__headerbtn 元素的:hover 状态设置样式 */
.el-dialog__headerbtn,
.el-message-box__headerbtn {
&:hover {
/* 为.el-dialog__headerbtn 和.el-message-box__headerbtn 元素在:hover 状态下的.el-dialog__close 子元素设置颜色为 --el-color-info 变量的值,!important 提高优先级 */
.el-dialog__close {
color: var(--el-color-info) !important;
}
}
}
/* 为.el-icon 元素及其特定子元素设置样式 */
.el-icon {
/* 为.el-icon 元素的.el-dialog__close、.el-drawer__close、.el-message-box__close 和.el-notification__closeBtn 子元素设置样式 */
&.el-dialog__close,
&.el-drawer__close,
&.el-message-box__close,
@ -90,6 +103,7 @@
border-radius: 4px;
transition: background-color 0.2s, color 0.2s;
/* 为这些子元素的:hover 状态设置样式 */
&:hover {
color: rgba(0, 0, 0, 0.88) !important;
background-color: rgba(0, 0, 0, 0.06);
@ -98,7 +112,7 @@
}
}
/* 克隆并自定义 ElMessage 样式,不会影响 ElMessage 原本样式,在 src/utils/message.ts 中调用自定义样式 ElMessage 方法即可,暗黑模式在 src/style/dark.scss 文件进行了适配 */
/* 克隆并自定义 ElMessage 样式,为.pure-message 元素设置样式 */
.pure-message {
border-width: 0 !important;
background: #fff !important;
@ -106,69 +120,85 @@
box-shadow: 0 3px 6px -4px #0000001f, 0 6px 16px #00000014,
0 9px 28px 8px #0000000d !important;
&.el-message.is-closable .el-message__content {
/* 为.pure-message 元素的.is-closable 子元素的.el-message__content 子元素设置内边距 */
&.el-message.is-closable.el-message__content {
padding-right: 17px !important;
}
& .el-message__content {
/* 为.pure-message 元素的.el-message__content 子元素设置颜色、指针事件和背景图像 */
&.el-message__content {
color: #000000d9 !important;
pointer-events: all !important;
background-image: initial !important;
}
& .el-message__icon {
/* 为.pure-message 元素的.el-message__icon 子元素设置外边距 */
&.el-message__icon {
margin-right: 8px !important;
}
& .el-message__closeBtn {
/* 为.pure-message 元素的.el-message__closeBtn 子元素设置样式 */
&.el-message__closeBtn {
outline: none;
border-radius: 4px;
right: 9px !important;
transition: background-color 0.2s, color 0.2s;
/* 为.el-message__closeBtn 子元素的:hover 状态设置背景颜色 */
&:hover {
background-color: rgba(0, 0, 0, 0.06);
}
}
}
/* 为.el-popconfirm 元素的.el-popconfirm__action 子元素内的不同类型的.el-button 元素设置样式 */
.el-popconfirm {
.el-popconfirm__action {
/* 为.el-popconfirm__action 子元素内的.el-button--primary 类型的按钮设置背景颜色 */
.el-button--primary {
background-color: #409eff !important;
/* 为.el-button--primary 类型的按钮的:hover 状态设置背景颜色 */
&:hover {
background-color: #79bbff !important;
}
}
/* 为.el-popconfirm__action 子元素内的.el-button--success 类型的按钮设置背景颜色 */
.el-button--success {
background-color: #67C23A !important;
/* 为.el-button--success 类型的按钮的:hover 状态设置背景颜色 */
&:hover {
background-color: #95d475 !important;
}
}
/* 为.el-popconfirm__action 子元素内的.el-button--warning 类型的按钮设置背景颜色 */
.el-button--warning {
background-color: #E6A23C !important;
/* 为.el-button--warning 类型的按钮的:hover 状态设置背景颜色 */
&:hover {
background-color: #eebe77 !important;
}
}
/* 为.el-popconfirm__action 子元素内的.el-button--danger 类型的按钮设置背景颜色 */
.el-button--danger {
background-color: #f56c6c !important;
background-color: #F56C6C !important;
/* 为.el-button--danger 类型的按钮的:hover 状态设置背景颜色 */
&:hover {
background-color: #f89898 !important;
}
}
/* 为.el-popconfirm__action 子元素内的.el-button--info 类型的按钮设置背景颜色 */
.el-button--info {
background-color: #909399 !important;
/* 为.el-button--info 类型的按钮的:hover 状态设置背景颜色 */
&:hover {
background-color: #b1b3b8 !important;
}
@ -176,46 +206,56 @@
}
}
/* 为.el-button--primary 类型的按钮设置颜色和背景颜色 */
.el-button--primary {
color: white !important;
background-color: #409EFF !important;
/* 为.el-button--primary 类型的按钮的:hover 状态设置背景颜色 */
&:hover {
background-color: #79bbff !important;
}
}
/* 为.el-button--success 类型的按钮设置颜色和背景颜色 */
.el-button--success {
color: white !important;
background-color: #67C23A !important;
/* 为.el-button--success 类型的按钮的:hover 状态设置背景颜色 */
&:hover {
background-color: #95d475 !important;
}
}
/* 为.el-button--warning 类型的按钮设置颜色和背景颜色 */
.el-button--warning {
color: white !important;
background-color: #E6A23C !important;
/* 为.el-button--warning 类型的按钮的:hover 状态设置背景颜色 */
&:hover {
background-color: #eebe77 !important;
}
}
/* 为.el-button--danger 类型的按钮设置颜色和背景颜色 */
.el-button--danger {
color: white !important;
background-color: #F56C6C !important;
/* 为.el-button--danger 类型的按钮的:hover 状态设置背景颜色 */
&:hover {
background-color: #f89898 !important;
}
}
/* 为.el-button--info 类型的按钮设置颜色和背景颜色 */
.el-button--info {
color: white !important;
background-color: #909399 !important;
/* 为.el-button--info 类型的按钮的:hover 状态设置背景颜色 */
&:hover {
background-color: #b1b3b8 !important;
}

@ -1,9 +1,26 @@
@import './variables.module.scss'; //
@import './mixin.scss'; //
@import './base.scss'; //
@import './reset.scss'; //
//
@import './variables.module.scss';
// mixin
@import './mixin.scss';
//
@import './base.scss';
// 使
@import './reset.scss';
//
@import './login.scss';
//
@import './navbar.scss';
//
@import './sidebar.scss';
// Element Plus Element Plus
@import './element.scss';
//
@import './transition.scss';

@ -1,70 +1,116 @@
// .login-container
.login-container {
//
position: relative;
// 100%
height: 100vh;
// 100%
width: 100vw;
//
display: flex;
// 使
justify-content: space-between;
// .login-container .left-box .right-box
.left-box,
.right-box {
flex: 1;
}
// .login-container .right-box
.right-box {
//
display: flex;
// 使
align-items: center;
// 90
margin-top: -90px;
.login-form ,
// .right-box .login-form .forget-pass-form
.login-form,
.forget-pass-form {
// 360
width: 360px;
// 50
margin-left: 50px;
// .login-form .forget-pass-form .title
.title {
//
display: flex;
flex-direction: column;
// 使
align-items: center;
// .title img
img {
width: 80px;
height: 80px;
}
// .title h2
h2 {
//
color: #9193a3;
// 10 0 15 0
margin: 10px 0 15px 0;
}
}
// .login-form .forget-pass-form .password-set
.password-set {
//
display: flex;
// 100%
width: 100%;
// -10 0 10 0
margin: -10px 0 10px 0;
// 使
justify-content: right;
}
}
}
// .login-container .left-box
.left-box {
//
position: relative;
// 100%
width: 100%;
// .left-box
&::before {
content: '';
//
position: absolute;
//
top: 0;
//
left: 0;
// 100%
width: 100%;
// 100%
height: 100%;
// 使 bg.png
background-image: url('~@/assets/imgs/bg.png');
//
background-repeat: no-repeat;
// 100%
background-size: auto 100%;
// z -1使.left-box
z-index: -1;
}
// .left-box .img
.img {
// 100%
width: 100%;
// 100%
height: 100%;
//
display: flex;
// 使
align-items: center;
// 使
justify-content: center;
// .img img
img {
width: 500px;
}

@ -1,26 +1,34 @@
// clearfix mixin
@mixin clearfix {
// 使:after
&:after {
content: '';
//
display: table;
clear: both;
}
}
// scrollBar mixin
@mixin scrollBar {
//
&::-webkit-scrollbar-track-piece {
background: #d3dce6;
}
//
&::-webkit-scrollbar {
width: 6px;
}
//
&::-webkit-scrollbar-thumb {
background: #99a9bf;
border-radius: 20px;
}
}
// relative mixin
@mixin relative {
position: relative;
width: 100%;

@ -1,19 +1,31 @@
//
/* 定义一个名为.navbar-bg-hover 的类选择器,用于设置导航栏背景悬停效果的样式 */
.navbar-bg-hover {
/* 设置为弹性盒子布局 */
display: flex;
/* 使弹性盒子内的子元素在垂直方向上居中对齐 */
align-items: center;
/* 使弹性盒子内的子元素在水平方向上居中对齐 */
justify-content: center;
/* 设置高度为 48 像素 */
height: 48px;
/* 设置内边距为 12 像素 */
padding: 12px;
/* 设置所有属性的过渡效果,持续时间为 0.3 秒 */
transition: all 0.3s;
}
&:hover {
background-color: #f6f6f6;
cursor: pointer;
transition: all 0.3s;
}
/* 当.navbar-bg-hover 元素被鼠标悬停时的样式 */
&:hover {
/* 设置背景颜色为 #f6f6f6 */
background-color: #f6f6f6;
/* 设置鼠标指针样式为指针 */
cursor: pointer;
/* 再次设置所有属性的过渡效果,持续时间为 0.3 秒 */
transition: all 0.3s;
}
.svg-icon {
outline: none;
}
/* 选择.navbar-bg-hover 元素内的.svg-icon 元素,设置其样式 */
.svg-icon {
/* 去除轮廓线 */
outline: none;
}

@ -1,61 +1,97 @@
/* 对 html 和 body 元素设置样式 */
html,
body {
/* 设置高度为视口高度的 100% */
height: 100%;
/* 去除外边距 */
margin: 0;
/* 去除内边距 */
padding: 0;
/* 针对 Firefox 浏览器,设置字体抗锯齿为灰度模式,提高字体显示质量 */
-moz-osx-font-smoothing: grayscale;
/* 针对 WebKit 内核的浏览器(如 Chrome、Safari设置字体抗锯齿提高字体显示质量 */
-webkit-font-smoothing: antialiased;
/* 设置文本渲染方式为优化可读性 */
text-rendering: optimizeLegibility;
//
// font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB,
// Microsoft YaHei, Arial, sans-serif;
}
/* 对 id 为 app 的元素设置样式 */
#app {
/* 设置高度为视口高度的 100% */
height: 100%;
}
/* 对所有元素及其伪元素(:before 和 :after设置样式 */
*,
*:before,
*:after {
/* 使元素的 box-sizing 属性继承自父元素,通常用于统一盒模型的计算方式 */
box-sizing: inherit;
/* 去除外边距 */
margin: 0;
/* 去除内边距 */
padding: 0;
}
/* 对处于激活active和获得焦点focus状态的 a 元素设置样式 */
a:focus,
a:active {
/* 去除轮廓线 */
outline: none;
}
/* 对 a 元素及其处于获得焦点focus和鼠标悬停hover状态时设置样式 */
a,
a:focus,
a:hover {
/* 设置鼠标指针样式为指针 */
cursor: pointer;
/* 使链接文本颜色继承自父元素 */
color: inherit;
/* 去除链接的下划线 */
text-decoration: none;
}
/* 对获得焦点的 div 元素设置样式 */
div:focus {
/* 去除轮廓线 */
outline: none;
}
/* 定义一个名为 clearfix 的类选择器,用于清除浮动,通过伪元素:after 实现 */
.clearfix {
&:after {
/* 使伪元素不可见 */
visibility: hidden;
/* 设置伪元素的显示方式为块级元素 */
display: block;
/* 设置伪元素的字体大小为 0 */
font-size: 0;
/* 设置伪元素的内容为空字符串 */
content: ' ';
/* 清除浮动 */
clear: both;
/* 设置伪元素的高度为 0 */
height: 0;
}
}
div#driver-popover-item .driver-popover-title {
/* 选择 id 为 driver-popover-item 的 div 元素内的.driver-popover-title 元素,设置其文本颜色 */
div#driver-popover-item.driver-popover-title {
color: #1890ff;
}
div#driver-popover-item .driver-popover-footer .driver-next-btn {
/* 选择 id 为 driver-popover-item 的 div 元素内的.driver-popover-footer 元素内的.driver-next-btn 元素,设置其样式 */
div#driver-popover-item.driver-popover-footer.driver-next-btn {
/* 设置背景颜色 */
background-color: #1890ff;
/* 设置文本颜色为白色 */
color: white;
/* 去除文本阴影 */
text-shadow: none;
/* 设置元素的边框圆角为 4 像素 */
border-radius: 4px;
}

@ -1,144 +1,221 @@
//
.main-container {
//
width: calc(100% - $sideBarWidth);
//
height: 100%;
//
position: fixed;
top: 0;
right: 0;
// z-index 9
z-index: 9;
// 0.28
transition: all 0.28s;
}
//
//
.sidebar-container {
// 0.3
transition: all 0.3s;
// !important
width: $sideBarWidth !important;
//
height: 100%;
//
position: fixed;
top: 0;
bottom: 0;
left: 0;
// z-index 1001
z-index: 1001;
// 使 $menuBg
background-color: $menuBg;
//
.scrollbar-wrapper {
// !important
overflow-x: hidden !important;
}
//
.el-scrollbar__bar.is-vertical {
//
right: 0;
}
//
.el-scrollbar {
// 92px
height: calc(100% - 92px);
}
// logo
&.has-logo {
// PC
.el-scrollbar.pc {
/* logo: 48px、leftCollapse: 40px、leftCollapse-shadow: 4px */
// 92px
height: calc(100% - 92px);
}
//
.el-scrollbar.mobile {
//
height: 100%;
}
}
//
.is-horizontal {
//
display: none;
}
//
a {
//
display: inline-block;
display: flex;
// 10px
padding-left: 10px;
//
flex-wrap: wrap;
// 100%
width: 100%;
}
// Element UI
.el-menu {
//
border: none;
//
height: 100%;
// !important
background-color: transparent !important;
}
//
.el-menu-item,
.el-sub-menu__title {
// 50px
height: 50px;
// 使 $menuText
color: $menuText;
// !important
background-color: transparent !important;
// 18px!important
padding-left: 18px !important;
//
&:hover {
// 使 $menuActiveText!important
color: $menuActiveText !important;
//
.svg-icon {
// 使 $menuActiveText
color: $menuActiveText;
// 0.3
transition: color 0.3s;
}
}
//
.svg-icon {
// 使 $menuText
color: $menuText;
// 0.3
transition: color 0.3s;
}
// div span
div,
span {
// 50px
height: 50px;
// 50px
line-height: 50px;
//
overflow: hidden;
//
text-overflow: ellipsis;
}
//
.svg-icon {
// 1em
min-width: 1em;
// 5px
margin-right: 5px;
}
}
//
.submenu-title-noDropdown,
.el-sub-menu__title {
&:hover {
//
background-color: transparent;
}
}
//
//
.is-active > .el-sub-menu__title,
.is-active.submenu-title-noDropdown {
// 使 $menuActiveText!important
color: $menuActiveText !important;
//
i {
// 使 $menuActiveText!important
color: $menuActiveText !important;
}
}
//
.is-active {
// 0.3
transition: color 0.3s;
// 使 $menuActiveText!important
color: $menuActiveText !important;
//
.svg-icon {
// 使 $menuActiveText
color: $menuActiveText;
// 0.3
transition: color 0.3s;
}
}
//
.el-menu .el-sub-menu .el-menu-item {
// 12px
font-size: 12px;
// 100px!important
min-width: 100px !important;
// 使 $subMenuBg!important
background-color: $subMenuBg !important;
//
&.is-active {
// 使 $menuActiveBg!important
background: $menuActiveBg !important;
// 使 $menuActiveText!important
color: $menuActiveText !important;
}
}
//
.el-menu .el-menu-item {
//
.el-menu-tooltip__trigger {
// 18px
padding: 18px;
}
//
&.is-active {
// 使 $menuActiveBg!important
background: $menuActiveBg !important;
}
}
@ -146,153 +223,224 @@
//
.el-menu--collapse {
//
width: $hideSideBarWidth;
}
//
.hideSidebar {
//
//
.sidebar-container {
// 0.3
transition: width 0.3s;
// !important
width: $hideSideBarWidth !important;
//
.el-sub-menu {
//
&.is-active {
// 0.28
transition: all 0.28s;
// 使 $menuActiveText!important
color: $menuActiveText !important;
//
position: relative;
//
&::after {
//
content: '';
// 50px
height: 50px;
// 2px
left: 2px;
//
position: absolute;
// 0
top: 0;
// 0.28 !important
transition-delay: 0.28s !important;
// 2px
width: 2px;
// !important
background-color: var(--el-color-primary) !important;
// 1
opacity: 1;
}
//
.svg-icon {
// 使 $menuActiveText
color: $menuActiveText;
// 0.3
transition: color 0.3s;
}
}
}
//
//
.el-sub-menu__title {
// 18px
padding: 18px;
}
//
//
.el-menu-item {
//
.el-menu-tooltip__trigger {
// 18px
padding: 18px;
}
}
}
//
//
.el-menu--vertical {
// !important
width: $hideSideBarWidth !important;
// 0.3
transition: width 0.3s;
}
//
//
.main-container {
//
width: calc(100% - $hideSideBarWidth);
}
}
//
//
.el-menu--vertical {
// !important
width: $sideBarWidth !important;
// 0.3
transition: width 0.3s;
/* 子菜单中还有子菜单 */
//
.el-menu .el-sub-menu__title {
// 12px
font-size: 12px;
// 使 $subMenuBg!important
background-color: $subMenuBg !important;
}
}
//
//
.el-menu--popup-container {
//
.el-menu {
// 使 $subMenuBg!important
background-color: $subMenuBg !important;
}
//
.el-menu-item,
.el-sub-menu__title {
// 50px
height: 50px;
// 使 $menuText
color: $menuText;
// !important
background-color: transparent !important;
//
&:hover {
// 使 $menuActiveText!important
color: $menuActiveText !important;
//
.svg-icon {
// 使 $menuActiveText
color: $menuActiveText;
// 0.3
transition: color 0.3s;
}
}
//
.svg-icon {
// 使 $menuText
color: $menuText;
// 0.3
transition: color 0.3s;
}
// div span
div,
span {
// 50px
height: 50px;
// 50px
line-height: 50px;
//
overflow: hidden;
//
text-overflow: ellipsis;
}
}
//
.submenu-title-noDropdown,
.el-sub-menu__title {
&:hover {
//
background-color: transparent;
}
}
//
.is-active > .el-sub-menu__title,
.is-active.submenu-title-noDropdown {
// 使 $menuActiveText!important
color: $menuActiveText !important;
//
i {
// 使 $menuActiveText!important
color: $menuActiveText !important;
}
}
//
.el-menu .el-menu-item {
// 12px!important
font-size: 12px !important;
// 100px!important
min-width: 100px !important;
// 使 $subMenuBg!important
background-color: $subMenuBg !important;
//
&.is-active {
// 使 $menuHover!important
background: $menuHover !important;
}
}
//
.el-menu .el-menu-item {
&.is-active {
// 使 $menuHover!important
background: $menuHover !important;
}
}
}
// bug
// bug
.is-active {
//
&::after {
//
content: '';
// 1!important
opacity: 1 !important;
}
//
.menu-icon {
// !important
transition: none !important;
// !important
color: #fff !important;
}
}

@ -1,15 +1,19 @@
//
/* 定义一个名为.large-card-font 的类选择器,用于设置卡片上数字的样式 */
.large-card-font {
/* 使用 @apply 指令,应用其他样式类的样式,这里应用了 font-semibold半粗体字体和 text-2xl字体大小为 2 倍大)这两个样式类 */
@apply font-semibold text-2xl;
/* 设置文本颜色为 RGB 值为 (41, 137, 255) 的颜色 */
color: rgb(41, 137, 255);
}
//
/* 定义一个名为.text-main 的类选择器,用于设置文本的主要颜色 */
.text-main {
/* 设置文本颜色为十六进制颜色值 #cecece */
color: #cecece;
}
//
/* 定义一个名为.border-main 的类选择器,用于设置默认的边框颜色和样式 */
.border-main {
/* 设置边框为 1 像素宽的实线,颜色为十六进制颜色值 #cfcfcf */
border: #cfcfcf solid 1px;
}

@ -1,31 +1,43 @@
//
/* 定义面包屑进入和离开时的活跃状态样式,设置过渡效果 */
.breadcrumb-enter-active,
.breadcrumb-leave-active {
/* 所有属性的过渡效果,持续时间为 0.5 秒 */
transition: all 0.5s;
}
/* 定义面包屑进入前的状态和离开时的活跃状态(这里似乎重复定义了离开活跃状态,可能存在错误)的样式 */
.breadcrumb-enter-from,
.breadcrumb-leave-active {
/* 透明度设置为 0 */
opacity: 0;
/* 水平方向平移 20 像素 */
transform: translateX(20px);
}
/* 再次定义面包屑离开时的活跃状态样式,设置为绝对定位 */
.breadcrumb-leave-active {
position: absolute;
}
/* 页面切换过渡动画 */
/* 定义页面切换时的过渡动画相关类的样式,设置进入和离开时的活跃状态的过渡效果 */
.fade-transform-leave-active,
.fade-transform-enter-active {
/* 所有属性的过渡效果,持续时间为 0.28 秒 */
transition: all 0.28s;
}
/* 定义页面切换时进入前的状态样式 */
.fade-transform-enter-from {
/* 透明度设置为 0 */
opacity: 0;
/* 水平向左平移 30 像素 */
transform: translateX(-30px);
}
/* 定义页面切换时离开到的状态样式 */
.fade-transform-leave-to {
/* 透明度设置为 0 */
opacity: 0;
/* 水平向右平移 30 像素 */
transform: translateX(30px);
}

@ -1,24 +1,43 @@
// sidebar
//
// #fefefea6
$menuText: #fefefea6;
//
$menuActiveText: #fff;
// #031429
$menuBg: #031429;
// #558df6
$menuActiveBg: #558df6;
// #558df6
$menuHover: #558df6;
// #0e0303
$subMenuBg: #0e0303;
// #00061a
$subHideMenuBg: #00061a;
// 210
$sideBarWidth: 210px;
// 54
$hideSideBarWidth: 54px;
// 使 :export 便 JavaScript 使
:export {
// $menuText menuText
menuText: $menuText;
// $menuActiveText menuActiveText
menuActiveText: $menuActiveText;
// $menuBg menuBg
menuBg: $menuBg;
// $menuHover menuHover
menuHover: $menuHover;
// $subMenuBg subMenuBg
subMenuBg: $subMenuBg;
// $subHideMenuBg subHideMenuBg
subHideMenuBg: $subHideMenuBg;
// $sideBarWidth sideBarWidth
sideBarWidth: $sideBarWidth;
// $hideSideBarWidth hideSideBarWidth
hideSideBarWidth: $hideSideBarWidth;
}

@ -1,12 +1,25 @@
// 定义一个接口 IVariables用于描述包含侧边栏样式相关变量的对象结构
export interface IVariables {
// 菜单文本的颜色,类型为字符串
menuText: string
// 菜单激活时文本的颜色,类型为字符串
menuActiveText: string
// 菜单的背景颜色,类型为字符串
menuBg: string
// 菜单鼠标悬停时的背景颜色,类型为字符串
menuHover: string
// 子菜单的背景颜色,类型为字符串
subMenuBg: string
// 隐藏子菜单的背景颜色,类型为字符串
subHideMenuBg: string
// 侧边栏展开时的宽度,类型为字符串
sideBarWidth: string
// 侧边栏折叠时的宽度,类型为字符串
hideSideBarWidth: string
}
// 声明一个名为 styles 的变量,其类型为 IVariables但未初始化
export const styles: IVariables
// 默认导出 styles 变量,但由于未初始化,可能在使用时需要注意
export default styles

@ -1,11 +1,23 @@
/** 文件类型 */
// 声明一个模块,用于处理.svg 结尾的文件,这样在 TypeScript 中就可以导入.svg 文件而不会报错,
// 但这里只是简单声明,没有定义具体的类型,意味着导入该类型文件时会被视为 any 类型
declare module '*.svg'
// 声明用于处理.png 结尾的文件的模块
declare module '*.png'
// 声明用于处理.jpg 结尾的文件的模块
declare module '*.jpg'
// 声明用于处理.jpeg 结尾的文件的模块
declare module '*.jpeg'
// 声明用于处理.gif 结尾的文件的模块
declare module '*.gif'
// 声明用于处理.bmp 结尾的文件的模块
declare module '*.bmp'
// 声明用于处理.tiff 结尾的文件的模块
declare module '*.tiff'
// 声明用于处理.yaml 结尾的文件的模块
declare module '*.yaml'
// 声明用于处理.json 结尾的文件的模块
declare module '*.json'
// 声明用于处理 vue-count-to 模块,可能是为了在项目中使用 vue-count-to 这个 Vue 插件时,
// 让 TypeScript 知道它的存在,即使没有具体的类型定义,也能正常导入和使用(类型为 any
declare module 'vue-count-to'

@ -1,12 +1,17 @@
// vuex.d.ts
// 从 'vuex' 库中导入 Store 和 Module 类型
import { Store, Module } from 'vuex'
// 从 './store/types' 文件中导入 IStoreType 类型,这个类型应该定义了 Vuex 存储的状态结构
import { type IStoreType } from './store/types'
// 模块扩展
// 模板内$store强类型支持
// 模块扩展,用于在 Vue 运行时核心模块中添加自定义类型声明
// 这样做可以让 TypeScript 知道在 Vue 组件中 this.$store 的具体类型
declare module '@vue/runtime-core' {
// 为 `this.$store` 提供类型声明
// ComponentCustomProperties 是 Vue 中用于扩展组件自定义属性的接口
interface ComponentCustomProperties {
// 声明 $store 属性,其类型为 Store<IStoreType>
// Store 是 Vuex 中的存储类IStoreType 是之前导入的状态类型
// 这意味着在 Vue 组件中使用 this.$store 时TypeScript 会进行类型检查,确保操作符合 IStoreType 的定义
$store: Store<IStoreType>
}
}

@ -1,46 +1,47 @@
// TODO 初始化Array
// 导出一个函数initArray用于初始化数组
// 如果传入的array存在则返回array否则返回一个空数组
export const initArray = (array: any) => {
return array ? array : [];
return array? array : [];
};
// TODO 将一个Array的某个字段所有的值全部添加到了一个Array
// 导出一个函数arrayPushAllByFiled用于将一个数组中某个字段的所有值添加到另一个数组
export const arrayPushAllByFiled = (dataArray: any, pushArray: any, field: any) => {
// 初始化
// 初始化dataArray和pushArray
dataArray = initArray(dataArray);
pushArray = initArray(pushArray);
// 法一
// 法一使用for...of循环遍历dataArray将每个元素的指定字段值添加到pushArray中
// for (const item of dataArray) {
// pushArray.push(item[field]);
// }
// return pushArray;
// 法二
// 法二使用map方法遍历dataArray返回一个包含所有指定字段值的新数组
return dataArray.map((item: any) => item[field]);
};
// TODO 将一个Array的数据全部添加到了一个Array
// 导出一个函数arrayPushAllByObj用于将一个数组的数据全部添加到另一个数组
export const arrayPushAllByObj = (dataArray: any, pushArray: any) => {
// 初始化
// 初始化dataArray和pushArray
dataArray = initArray(dataArray);
pushArray = initArray(pushArray);
// push
// 使用展开运算符将dataArray的所有元素添加到pushArray中
pushArray.push(...dataArray);
return pushArray;
};
// TODO Array根据字段去重
// 导出一个函数arrayDeduplicationByFiled用于根据指定字段对数组进行去重
export const arrayDeduplicationByFiled = (array: any, field: string) => {
// 初始化
// 初始化array
array = initArray(array);
// 去重 法一:该方法对编号进行去重返回的是[1,3,5]的编号数组
// 去重 法一:使用Set和map方法对指定字段进行去重返回一个包含去重后字段值的新数组
// const idArray = [...new Set(array.map((item: any) => item[field]))];
// 再使用for循环依次取出id对应数据即可
// 去重 法二
// 去重 法二使用reduce方法遍历数组检查当前对象的指定字段是否已存在于新数组中不存在则添加
return array.reduce((newArray: any, thisObj: any) => {
// 检查当前对象的属性是否已经存在于新数组中
const exists = newArray.some((item: any) => thisObj[field] === item[field]);
@ -52,16 +53,16 @@ export const arrayDeduplicationByFiled = (array: any, field: string) => {
}, []);
};
// TODO Array根据对象去重(适用于非对象数组)
// 导出一个函数arrayDeduplicationByObj用于根据对象对数组进行去重适用于非对象数组
export const arrayDeduplicationByObj = (array: any) => {
// 初始化
// 初始化array
array = initArray(array);
// 去重 法一:该方法对编号进行去重返回的是[1,3,5]的编号数组
// 去重 法一:使用Set和map方法对数组元素进行去重返回一个包含去重后元素的新数组
// const idArray = [...new Set(array.map((item: any) => item))];
// 再使用for循环依次取出id对应数据即可
// 去重 法二
// 去重 法二使用reduce方法遍历数组检查当前对象是否已存在于新数组中不存在则添加
return array.reduce((newArray: any, thisObj: any) => {
// 检查当前对象的属性是否已经存在于新数组中
const exists = newArray.some((item: any) => thisObj === item);

@ -1,4 +1,4 @@
// 电话规则
// 从 @/utils/is 模块中导入多个用于验证的函数
import {
isNotEmail,
isNotIdNum,
@ -7,43 +7,58 @@ import {
isNotNumber
} from "@/utils/is";
// 电话规则
// 电话规则验证函数
export function phoneRule(rule: any, value: any, callback: any, field: string) {
// 去除输入值的首尾空格
const trim = value?.trim();
// 如果输入值为空
if (!trim) {
// 调用回调函数并传递错误信息,提示电话不能为空
callback(new Error(field + "电话不能为空"));
} else if (isNotPhone(trim)) {
// 如果输入值不是有效的电话号码格式
callback(new Error(field + "电话格式有误"));
} else {
// 如果输入值有效,调用回调函数且不传递错误信息
callback();
}
}
// 邮箱规则
// 邮箱规则验证函数
export function emailRule(rule: any, value: any, callback: any, field: string) {
// 去除输入值的首尾空格
const trim = value?.trim();
// 如果输入值为空
if (!trim) {
// 调用回调函数并传递错误信息,提示邮箱不能为空
callback(new Error(field + "邮箱不能为空"));
} else if (isNotEmail(trim)) {
// 如果输入值不是有效的邮箱格式
callback(new Error(field + "邮箱格式有误"));
} else {
// 如果输入值有效,调用回调函数且不传递错误信息
callback();
}
}
// 身份证号规则
// 身份证号规则验证函数
export function idNumRule(rule: any, value: any, callback: any, field: string) {
// 去除输入值的首尾空格
const trim = value?.trim();
// 如果输入值为空
if (!trim) {
// 调用回调函数并传递错误信息,提示身份证号不能为空
callback(new Error(field + "身份证号不能为空"));
} else if (isNotIdNum(trim)) {
// 如果输入值不是有效的身份证号格式
callback(new Error(field + "身份证号格式有误"));
} else {
// 如果输入值有效,调用回调函数且不传递错误信息
callback();
}
}
// 字符串规则
// 字符串规则验证函数
export function stringRule(
rule: any,
value: any,
@ -52,17 +67,22 @@ export function stringRule(
mixLen: number,
maxLen: number
) {
// 去除输入值的首尾空格
const trim = value?.trim();
// 如果输入值为空
if (!trim) {
// 调用回调函数并传递错误信息,提示该字段不能为空
callback(new Error(field + "不能为空"));
} else if (trim.length < mixLen || trim.length > maxLen) {
// 如果输入值的长度不在指定范围内
callback(new Error(field + "长度应为" + mixLen + "~" + maxLen));
} else {
// 如果输入值有效,调用回调函数且不传递错误信息
callback();
}
}
// 数字(整数/小数)规则
// 数字(整数/小数)规则验证函数
export function numberRule(
rule: any,
value: any,
@ -71,21 +91,27 @@ export function numberRule(
mix: number,
max: number
) {
// 如果输入值为空
if (!value) {
// 调用回调函数并传递错误信息,提示该字段不能为空
callback(new Error(field + "不能为空"));
} else {
// 将输入值转换为字符串并去除首尾空格
const trim = value.toString()?.trim();
// 如果输入值不是有效的数字格式
if (isNotNumber(trim)) {
callback(new Error(field + "格式有误"));
} else if (trim < mix || trim > max) {
// 如果输入值不在指定的数字范围内
callback(new Error(field + "应该在" + mix + "~" + max + "之间"));
} else {
// 如果输入值有效,调用回调函数且不传递错误信息
callback();
}
}
}
// 整数规则
// 整数规则验证函数
export function integerRule(
rule: any,
value: any,
@ -94,24 +120,32 @@ export function integerRule(
mix: number,
max: number
) {
// 如果输入值为空
if (!value) {
// 调用回调函数并传递错误信息,提示该字段不能为空
callback(new Error(field + "不能为空"));
} else {
// 将输入值转换为字符串并去除首尾空格
const trim = value.toString()?.trim();
// 如果输入值不是有效的整数格式
if (isNotInteger(trim)) {
callback(new Error(field + "格式有误"));
} else if (trim < mix || trim > max) {
// 如果输入值不在指定的整数范围内
callback(new Error(field + "应该在" + mix + "~" + max + "之间"));
} else {
// 如果输入值有效,调用回调函数且不传递错误信息
callback();
}
}
}
// 日期规则
// 日期规则验证函数,禁用当前时间之前的日期
export const disabledNowBeforeDate = (time: Date) => {
return time.getTime() < Date.now();
};
// 日期规则验证函数,禁用当前时间之后的日期
export const disabledNowAfterDate = (time: Date) => {
return time.getTime() > Date.now();
};

@ -1,35 +1,52 @@
// 从 '@/store' 导入 Vuex 的 store 实例
import store from "@/store";
// 导入 axios 库,并解构出 AxiosRequestConfig 和 AxiosResponse 类型
import baseAxios, { AxiosRequestConfig, AxiosResponse } from "axios";
// 从 'element-plus' 导入 ElMessage 用于显示消息提示
import { ElMessage } from "element-plus";
// 定义基础 URL
export const baseUrl = "http://127.0.0.1:9001/";
// 创建一个 axios 实例,设置基础 URL、超时时间和默认 headers
const axios = baseAxios.create({
baseURL: "",
timeout: 5000,
headers: {}
baseURL: "", // 这里可以根据实际情况设置基础 URL如果为空则使用全局的 baseUrl
timeout: 5000, // 设置请求超时时间为 5000 毫秒
headers: {} // 可以在这里设置默认的请求头
});
// 请求拦截器
// 请求拦截器,在请求发送前会被调用
axios.interceptors.request.use((config: any) => {
// 从 localStorage 中获取名为 "stateData" 的数据,并解析为 JSON 对象
// 然后尝试获取其中的 app.token 作为令牌
const token = JSON.parse(localStorage.getItem("stateData")!)?.app?.token;
// 将令牌设置到请求头的 "token" 字段中
config.headers.token = token;
// 返回配置好的请求对象
return config;
});
// 响应拦截器
// 响应拦截器,在接收到响应后会被调用
axios.interceptors.response.use(
(res: AxiosResponse) => {
// 如果响应数据中的 code 为 500 且 msg 为 "令牌无效"
if (res.data.code === 500 && res.data.msg === "令牌无效") {
// 调用 store 的 app/logout 方法进行登出操作
store.dispatch("app/logout");
// 显示错误消息提示 "登录过期,请重新登录"
ElMessage.error({ message: "登录过期,请重新登录" });
}
// 如果响应数据中的 err 为 1
if (res.data.err === 1) {
// 则返回一个被拒绝的 Promise携带响应数据
return Promise.reject(res.data);
}
// 返回响应数据
return res.data;
},
err => Promise.reject(err) // 响应出问题
// 如果响应出现错误,返回一个被拒绝的 Promise携带错误信息
err => Promise.reject(err)
);
// 默认导出创建好的 axios 实例
export default axios;

@ -1,3 +1,7 @@
// 从 './http' 文件中导入名为 http 的模块
// 这里假设 './http' 文件中导出了一个名为 http 的对象,这个对象可能是封装好的 HTTP 请求工具,例如使用 axios 封装的请求实例
import http from './http'
// 导出导入的 http 对象
// 这样其他文件就可以通过导入这个文件来使用 http 对象进行 HTTP 请求操作
export { http }

@ -1,174 +1,277 @@
const toString = Object.prototype.toString
// 保留两位小数的实数校验规则
const REGEXP_NUMBER = /^[+-]?(0|([1-9]\d*))(\.\d{1,2})?$/
// 密码校验规则
const REGEXP_EMAIL = /^[a-zA-Z0-9]+@[a-zA-Z0-9]+\.[a-zA-Z0-9]+$/
// 手机号校验规则
const REGEXP_PHONE = /^1[3456789]\d{9}$/
// 身份证号校验规则
// 获取 Object.prototype.toString 方法的引用,用于后续类型判断
const toString = Object.prototype.toString;
// 定义一个正则表达式,用于校验保留两位小数的实数
// 支持正负号,整数部分可以是 0 或者以 1 - 9 开头的数字串,小数部分可以有 1 到 2 位
const REGEXP_NUMBER = /^[+-]?(0|([1-9]\d*))(\.\d{1,2})?$/;
// 定义一个正则表达式,用于校验邮箱格式
// 要求邮箱由字母、数字组成,中间有 @ 符号,并且有正确的域名和顶级域名
const REGEXP_EMAIL = /^[a-zA-Z0-9]+@[a-zA-Z0-9]+\.[a-zA-Z0-9]+$/;
// 定义一个正则表达式,用于校验手机号格式
// 要求手机号以 1 开头,第二位是 3 - 9 中的一个数字,后面跟着 9 位数字
const REGEXP_PHONE = /^1[3456789]\d{9}$/;
// 定义一个正则表达式,用于校验身份证号格式
// 符合中国 18 位身份证号码的格式规则
const REGEXP_ID_NUM =
/^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/
/^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/;
/**
* @description: /
* @param {string} account -
* @returns {boolean} - true false
*/
export function isNotAccount(account: string) {
return !REGEXP_PHONE.test(account) && !REGEXP_EMAIL.test(account)
// 使用正则表达式检查账号是否既不是手机号也不是邮箱
return !REGEXP_PHONE.test(account) && !REGEXP_EMAIL.test(account);
}
/**
* @description:
* @param {string} phone -
* @returns {boolean} - true false
*/
export function isNotPhone(phone: string) {
return !REGEXP_PHONE.test(phone)
// 使用正则表达式检查是否不是手机号
return !REGEXP_PHONE.test(phone);
}
/**
* @description:
* @param {string} email -
* @returns {boolean} - true false
*/
export function isNotEmail(email: string) {
return !REGEXP_EMAIL.test(email)
// 使用正则表达式检查是否不是邮箱
return !REGEXP_EMAIL.test(email);
}
/**
* @description:
* @param {string} idNum -
* @returns {boolean} - true false
*/
export function isNotIdNum(idNum: string) {
return !REGEXP_ID_NUM.test(idNum)
// 使用正则表达式检查是否不是身份证号
return !REGEXP_ID_NUM.test(idNum);
}
/**
* @description:
* @param {string} num -
* @returns {boolean} - true false
*/
export function isNotNumber(num: string) {
return !REGEXP_NUMBER.test(num)
// 使用正则表达式检查是否不是符合规则的数字
return !REGEXP_NUMBER.test(num);
}
/**
* @description:
* @param {number} num -
* @returns {boolean} - true false
*/
export function isNotInteger(num: number) {
return !Number.isInteger(Number(num))
// 使用 Number.isInteger 方法检查是否不是整数
return !Number.isInteger(Number(num));
}
/**
* @description:
* @description:
* @param {unknown} val -
* @param {string} type -
* @returns {boolean} - true false
*/
export function is(val: unknown, type: string) {
return toString.call(val) === `[object ${type}]`
// 使用 toString 方法获取值的类型字符串,并与指定类型比较
return toString.call(val) === `[object ${type}]`;
}
/**
* @description:
* @description:
* @param {unknown} val -
* @returns {val is T} - true false
*/
export function isFunction<T = () => void>(val: unknown): val is T {
return is(val, 'Function')
// 调用 is 函数判断是否为 Function 类型
return is(val, 'Function');
}
/**
* @description:
* @param {T} val -
* @returns {val is T} - undefined true false
*/
export const isDef = <T = unknown>(val?: T): val is T => {
return typeof val !== 'undefined'
// 使用 typeof 检查值是否不是 undefined
return typeof val !== 'undefined';
}
/**
* @description:
* @param {T} val -
* @returns {val is T} - undefined true false
*/
export const isUnDef = <T = unknown>(val?: T): val is T => {
return !isDef(val)
// 调用 isDef 函数取反判断是否未定义
return !isDef(val);
}
/**
* @description:
* @param {any} val -
* @returns {val is Record<any, any>} - null true false
*/
export const isObject = (val: any): val is Record<any, any> => {
return val !== null && is(val, 'Object')
// 检查值不为 null 且是 Object 类型
return val !== null && is(val, 'Object');
}
/**
* @description:
* @description:
* @param {unknown} val -
* @returns {val is Date} - Date true false
*/
export function isDate(val: unknown): val is Date {
return is(val, 'Date')
// 调用 is 函数判断是否为 Date 类型
return is(val, 'Date');
}
/**
* @description:
* @description:
* @param {unknown} val -
* @returns {val is number} - Number true false
*/
export function isNumber(val: unknown): val is number {
return is(val, 'Number')
// 调用 is 函数判断是否为 Number 类型
return is(val, 'Number');
}
/**
* @description: AsyncFunction
* @description: AsyncFunction
* @param {unknown} val -
* @returns {val is Promise<T>} - AsyncFunction true false
*
*/
export function isAsyncFunction<T = any>(val: unknown): val is Promise<T> {
return is(val, 'AsyncFunction')
// 调用 is 函数判断是否为 AsyncFunction 类型
return is(val, 'AsyncFunction');
}
/**
* @description: promise
* @description: promise
* @param {unknown} val -
* @returns {val is Promise<T>} - Promise true false
*/
export function isPromise<T = any>(val: unknown): val is Promise<T> {
// 检查值是 Promise 类型,是对象,且有 then 和 catch 方法
return (
is(val, 'Promise') &&
isObject(val) &&
isFunction(val.then) &&
isFunction(val.catch)
)
);
}
/**
* @description:
* @description:
* @param {unknown} val -
* @returns {val is string} - String true false
*/
export function isString(val: unknown): val is string {
return is(val, 'String')
// 调用 is 函数判断是否为 String 类型
return is(val, 'String');
}
/**
* @description: boolean
* @description: boolean
* @param {unknown} val -
* @returns {val is boolean} - Boolean true false
*/
export function isBoolean(val: unknown): val is boolean {
return is(val, 'Boolean')
// 调用 is 函数判断是否为 Boolean 类型
return is(val, 'Boolean');
}
/**
* @description:
* @description:
* @param {any} val -
* @returns {val is Array<any>} - true false
*/
export function isArray(val: any): val is Array<any> {
return val && Array.isArray(val)
// 检查值存在且是数组
return val && Array.isArray(val);
}
/**
* @description:
* @returns {boolean} - window true false
*/
export const isClient = () => {
return typeof window !== 'undefined'
// 检查 window 对象是否已定义
return typeof window !== 'undefined';
}
/**
* @description:
* @param {any} val -
* @returns {val is Window} - window true false
*/
export const isWindow = (val: any): val is Window => {
return typeof window !== 'undefined' && is(val, 'Window')
// 检查 window 对象已定义且值是 Window 类型
return typeof window !== 'undefined' && is(val, 'Window');
}
/**
* @description:
* @param {unknown} val -
* @returns {val is Element} - DOM true false
*/
export const isElement = (val: unknown): val is Element => {
return isObject(val) && !!val.tagName
// 检查值是对象且有 tagName 属性
return isObject(val) && !!val.tagName;
}
export const isServer = typeof window === 'undefined'
/**
* @description:
* @type {boolean} - window true false
*/
export const isServer = typeof window === 'undefined';
// 是否为图片节点
/**
* @description:
* @param {Element} o - DOM
* @returns {boolean} - true false
*/
export function isImageDom(o: Element) {
return o && ['IMAGE', 'IMG'].includes(o.tagName)
// 检查元素存在且标签名是 IMAGE 或 IMG
return o && ['IMAGE', 'IMG'].includes(o.tagName);
}
/**
* @description: null
* @param {unknown} val -
* @returns {val is null} - null true false
*/
export function isNull(val: unknown): val is null {
return val === null
// 检查值是否等于 null
return val === null;
}
/**
* @description: null undefined
* @param {unknown} val -
* @returns {val is null | undefined} - null undefined true false
*/
export function isNullAndUnDef(val: unknown): val is null | undefined {
return isUnDef(val) && isNull(val)
// 调用 isUnDef 和 isNull 函数判断
return isUnDef(val) && isNull(val);
}
/**
* @description: null undefined
* @param {unknown} val -
* @returns {val is null | undefined} - null undefined true false
*/
export function isNullOrUnDef(val: unknown): val is null | undefined {
return isUnDef(val) || isNull(val)
// 调用 isUnDef 和 isNull 函数取或判断
return isUnDef(val) || isNull(val);
}

@ -1,37 +1,55 @@
// 从 './is' 文件中导入 isArray 函数,用于判断一个值是否为数组
import { isArray } from './is'
// 定义一个函数 formatValue用于格式化传入的值
export function formatValue(callValue: any) {
// 如果当前值为数组,使用 / 拼接(根据需求自定义)
if (isArray(callValue)) return callValue.length ? callValue.join(' / ') : '--'
return callValue ?? '--'
// 如果传入的值是数组,使用 '/' 拼接数组元素(如果数组有元素),否则返回 '--'
if (isArray(callValue)) return callValue.length? callValue.join(' / ') : '--'
// 如果传入的值不是数组,直接返回该值,如果值为 null 或 undefined则返回 '--'
return callValue?? '--'
}
// 定义一个函数 handleRowAccordingToProp根据属性路径处理行数据
export function handleRowAccordingToProp(
row: { [key: string]: any },
prop: string
row: { [key: string]: any }, // 表示一个对象,键为字符串类型,值为任意类型
prop: string // 表示属性路径字符串
) {
if (!prop.includes('.')) return row[prop] ?? '--'
prop.split('.').forEach(item => (row = row[item] ?? '--'))
// 如果属性路径中不包含 '.',直接返回行数据中该属性的值,如果值为 null 或 undefined则返回 '--'
if (!prop.includes('.')) return row[prop]?? '--'
// 如果属性路径中包含 '.',按 '.' 分割属性路径,依次获取嵌套对象的属性值
prop.split('.').forEach(item => (row = row[item]?? '--'))
// 返回最终获取到的值
return row
}
// 定义一个函数 handleProp处理属性路径字符串
export function handleProp(prop: string) {
// 按 '.' 分割属性路径字符串,得到一个数组
const propArr = prop.split('.')
// 如果分割后的数组长度为 1说明属性路径没有嵌套直接返回原属性路径字符串
if (propArr.length == 1) return prop
// 如果数组长度大于 1返回属性路径字符串中最后一个部分
return propArr[propArr.length - 1]
}
// 定义一个函数 filterEnum根据枚举数据过滤并格式化值
export function filterEnum(
callValue: any,
enumData: any[] | undefined,
fieldNames?: { label: string; value: string },
type?: 'tag'
callValue: any, // 要过滤的值
enumData: any[] | undefined, // 枚举数据数组或 undefined
fieldNames?: { label: string; value: string }, // 可选的字段名称配置对象,默认为 { label: 'label', value: 'value' }
type?: 'tag' // 可选的类型,默认为 undefined
): string {
const value = fieldNames?.value ?? 'value'
const label = fieldNames?.label ?? 'label'
// 获取字段名称配置中的 'value' 字段名,如果未提供则默认为 'value'
const value = fieldNames?.value?? 'value'
// 获取字段名称配置中的 'label' 字段名,如果未提供则默认为 'label'
const label = fieldNames?.label?? 'label'
// 定义一个空对象,用于存储过滤后的数据
let filterData: { [key: string]: any } = {}
// 如果枚举数据是一个数组,查找与传入值匹配的枚举项
if (Array.isArray(enumData))
filterData = enumData.find((item: any) => item[value] === callValue)
if (type == 'tag') return filterData?.tagType ? filterData.tagType : ''
return filterData ? filterData[label] : '--'
// 如果类型为 'tag',返回过滤数据中的 'tagType' 字段值,如果不存在则返回空字符串
if (type == 'tag') return filterData?.tagType? filterData.tagType : ''
// 返回过滤数据中的 'label' 字段值,如果过滤数据不存在则返回 '--'
return filterData? filterData[label] : '--'
}

@ -1,9 +1,18 @@
<template>
<!-- 定义一个顶级的 div 元素作为组件的根元素用于包裹其他内容 -->
<div>
<!-- 定义一个 h1 标题元素显示文本 "activity" -->
<h1>activity</h1>
</div>
</template>
<script setup lang="ts"></script>
<script setup lang="ts">
// 使 <script setup> Vue 3
// lang="ts" 使 TypeScript
</script>
<style lang="scss" scoped></style>
<style lang="scss" scoped>
// lang="scss" 使 SCSSSass
// scoped
//
</style>

@ -1,7 +1,22 @@
<template>
<!-- router-view Vue Router 中的一个组件它是一个占位符
在使用 Vue Router 构建单页面应用时当定义了不同的路由路径和对应的组件后
当某个路由被匹配时与之对应的组件就会被渲染到 <router-view> 所在的位置
简单来说它起到了显示当前路由所对应的组件内容的作用 -->
<router-view></router-view>
</template>
<script setup lang="ts"></script>
<script setup lang="ts">
// 使 Vue 3 <script setup> .vue使 API
// lang="ts" 使 TypeScript
//
//
</script>
<style lang="scss" scoped></style>
<style lang="scss" scoped>
// lang="scss" 使 SCSSSassy CSS
// SCSS CSS 使
// scoped Vue
//
//
</style>

@ -1,66 +1,88 @@
<template>
<div>
<!-- Element Plus 的对话框组件 -->
<el-dialog
style="width: 70%"
v-model="dialogVisible"
:title="drawerProps.title"
destroy-on-close
style="width: 70%;"
<!-- 使用 v-model 绑定对话框的显示状态dialogVisible 是一个响应式引用 -->
v-model="dialogVisible"
<!-- 动态绑定对话框的标题 drawerProps.title 获取 -->
:title="drawerProps.title"
<!-- 在对话框关闭时销毁对话框实例 -->
destroy-on-close
>
<div>
<el-form
:model="formData"
class="login-form"
ref="ruleFormRef"
:rules="rules"
label-width="120px"
>
<div class="flex justify-around flex-wrap">
<div class="w-full md:w-1/2">
<el-form-item label="床位名称:" prop="name">
<el-input v-model="formData.name" clearable />
</el-form-item>
</div>
<!-- 占位 -->
<div
class="md:w-1/2"
v-for="i in 1"
:key="i"
style="height: 0"
></div>
</div>
<div class="flex flex-row-reverse">
<div></div>
</div>
</el-form>
<div>
<!-- Element Plus 的表单组件 -->
<el-form
<!-- 绑定表单的数据模型formData 是一个响应式引用 -->
:model="formData"
class="login-form"
<!-- 为表单添加引用方便在脚本中操作ruleFormRef 是一个响应式引用 -->
ref="ruleFormRef"
<!-- 绑定表单的验证规则rules 是一个响应式对象 -->
:rules="rules"
label-width="120px"
>
<div class="flex justify-around flex-wrap">
<div class="w-full md:w-1/2">
<!-- 表单字段项标签为床位名称:绑定的属性为name -->
<el-form-item label="床位名称:" prop="name">
<!-- Element Plus 的输入框组件双向绑定 formData.name 的值可清除输入内容 -->
<el-input v-model="formData.name" clearable />
</el-form-item>
</div>
<!-- 占位 -->
<div
class="md:w-1/2"
v-for="i in 1"
:key="i"
style="height: 0"
></div>
</div>
<template v-if="!drawerProps.isView" #footer>
<span class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button
class="bg-blue"
type="primary"
@click="handleSubmit"
>
提交
</el-button>
</span>
</template>
<div class="flex flex-row-reverse">
<div></div>
</div>
</el-form>
</div>
<!-- drawerProps.isView false 显示对话框的页脚模板 -->
<template v-if="!drawerProps.isView" #footer>
<span class="dialog-footer">
<!-- 取消按钮点击时关闭对话框 -->
<el-button @click="dialogVisible = false">取消</el-button>
<!-- 提交按钮带有蓝色背景类类型为主要按钮点击时调用 handleSubmit 方法 -->
<el-button
class="bg-blue"
type="primary"
@click="handleSubmit"
>
提交
</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script lang="ts" setup>
// Vue reactiverefwatch
import { reactive, ref, watch } from "vue";
// element-plus ElMessageFormInstanceFormRules
import { ElMessage, FormInstance, FormRules } from "element-plus";
//
import { integerRule, numberRule, stringRule } from "@/utils/formRules";
// API
import { getRoomTypeById } from "@/apis/roomType";
// API
import { getBedById, getBuildingById, getFloorById } from "@/apis/build";
// null
const ruleFormRef = ref<FormInstance | null>(null);
// false
const dialogVisible = ref(false);
// dialogVisible
watch(dialogVisible, (value, oldValue, onCleanup) => {
if (!value) {
// formData
formData.value = {
id: "",
name: "",
@ -70,30 +92,35 @@ watch(dialogVisible, (value, oldValue, onCleanup) => {
}
});
//
const rules = reactive<FormRules>({
name: [
{
required: true,
// stringRule
validator(rule, value, callback) {
stringRule(rule, value, callback, "床位名称", 2, 40);
},
trigger: "blur"
trigger: "blur" //
}
]
});
//
interface DialogProps {
title: string;
isView: boolean;
rowData?: any;
api?: (params: any) => Promise<any>;
getTableList?: () => Promise<any>;
title: string; //
isView: boolean; //
rowData?: any; //
api?: (params: any) => Promise<any>; // API
getTableList?: () => Promise<any>; //
}
// { isView: false, title: "" }
const drawerProps = ref<DialogProps>({
isView: false,
title: ""
});
//
const formData = ref({
id: "",
name: "",
@ -101,35 +128,43 @@ const formData = ref({
bedLimit: ""
});
//
//
const bedAcceptParams = async (params: DialogProps) => {
drawerProps.value = params;
if (drawerProps.value.title !== "新增床位") {
if (drawerProps.value.title!== "新增床位") {
// API formData
const res: any = await getBedById({
bedId: params.rowData.id
});
formData.value = res.data;
}else {
formData.value = params.rowData
} else {
// 使
formData.value = params.rowData;
}
//
dialogVisible.value = true;
};
//
const emits = defineEmits(["operateNode"]);
//
//
const handleSubmit = () => {
//
ruleFormRef.value!.validate(async valid => {
if (!valid) return;
try {
// API
const res = await drawerProps.value.api!(formData.value);
if (res.code == 200) {
//
ElMessage.success({
message: res.msg
});
emits("operateNode");
dialogVisible.value = false;
} else {
//
ElMessage.error({
message: res.msg
});
@ -140,11 +175,13 @@ const handleSubmit = () => {
});
};
// bedAcceptParams 便
defineExpose({
bedAcceptParams
});
</script>
<style scoped lang="scss">
// 10px
.dialog-footer button:first-child {
margin-right: 10px;
}

@ -1,71 +1,95 @@
<template>
<div>
<!-- Element Plus 的对话框组件 -->
<el-dialog
style="width: 70%"
v-model="dialogVisible"
:title="drawerProps.title"
destroy-on-close
style="width: 70%;"
<!-- 使用 v-model 双向绑定对话框的显示状态 dialogVisible 控制 -->
v-model="dialogVisible"
<!-- 动态绑定对话框的标题 drawerProps.title 获取 -->
:title="drawerProps.title"
<!-- 当对话框关闭时销毁对话框实例 -->
destroy-on-close
>
<div>
<el-form
:model="formData"
class="login-form"
ref="ruleFormRef"
:rules="rules"
label-width="120px"
>
<div class="flex justify-around flex-wrap">
<div class="w-full md:w-1/2">
<el-form-item label="楼栋名称:" prop="name">
<el-input v-model="formData.name" clearable />
</el-form-item>
</div>
<div class="w-full md:w-1/2">
<el-form-item label="楼层数量:" prop="floorNum">
<el-input v-model="formData.floorNum" clearable />
</el-form-item>
</div>
<!-- 占位 -->
<div
class="md:w-1/2"
v-for="i in 1"
:key="i"
style="height: 0"
></div>
</div>
<div class="flex flex-row-reverse">
<div></div>
</div>
</el-form>
<div>
<!-- Element Plus 的表单组件 -->
<el-form
<!-- 绑定表单的数据模型数据来源于 formData -->
:model="formData"
class="login-form"
<!-- 为表单添加引用方便在 JavaScript 中获取表单实例ruleFormRef 为引用名称 -->
ref="ruleFormRef"
<!-- 绑定表单的验证规则规则定义在 rules -->
:rules="rules"
label-width="120px"
>
<div class="flex justify-around flex-wrap">
<div class="w-full md:w-1/2">
<!-- 表单字段项标签为楼栋名称:对应表单数据中的 name 属性 -->
<el-form-item label="楼栋名称:" prop="name">
<!-- Element Plus 的输入框组件双向绑定 formData.name 的值并且可清除输入内容 -->
<el-input v-model="formData.name" clearable />
</el-form-item>
</div>
<div class="w-full md:w-1/2">
<!-- 表单字段项标签为楼层数量:对应表单数据中的 floorNum 属性 -->
<el-form-item label="楼层数量:" prop="floorNum">
<!-- Element Plus 的输入框组件双向绑定 formData.floorNum 的值并且可清除输入内容 -->
<el-input v-model="formData.floorNum" clearable />
</el-form-item>
</div>
<!-- 占位 -->
<div
class="md:w-1/2"
v-for="i in 1"
:key="i"
style="height: 0"
></div>
</div>
<template v-if="!drawerProps.isView" #footer>
<span class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button
class="bg-blue"
type="primary"
@click="handleSubmit"
>
提交
</el-button>
</span>
</template>
<div class="flex flex-row-reverse">
<div></div>
</div>
</el-form>
</div>
<!-- drawerProps.isView false 显示对话框的页脚模板 -->
<template v-if="!drawerProps.isView" #footer>
<span class="dialog-footer">
<!-- 取消按钮点击时将 dialogVisible 设置为 false关闭对话框 -->
<el-button @click="dialogVisible = false">取消</el-button>
<!-- 提交按钮带有蓝色背景类类型为主要按钮点击时调用 handleSubmit 方法 -->
<el-button
class="bg-blue"
type="primary"
@click="handleSubmit"
>
提交
</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script lang="ts" setup>
// Vue reactiverefwatch
import { reactive, ref, watch } from "vue";
// element-plus ElMessageFormInstanceFormRules
import { ElMessage, FormInstance, FormRules } from "element-plus";
// integerRulenumberRulestringRule
import { integerRule, numberRule, stringRule } from "@/utils/formRules";
// API ID getRoomTypeById使
import { getRoomTypeById } from "@/apis/roomType";
// API ID getBuildingById
import { getBuildingById } from "@/apis/build";
// null
const ruleFormRef = ref<FormInstance | null>(null);
// false
const dialogVisible = ref(false);
// dialogVisible
watch(dialogVisible, (value, oldValue, onCleanup) => {
if (!value) {
// formData
formData.value = {
id: "",
name: "",
@ -74,72 +98,85 @@ watch(dialogVisible, (value, oldValue, onCleanup) => {
}
});
//
const rules = reactive<FormRules>({
name: [
{
required: true,
// stringRule 2 10
validator(rule, value, callback) {
stringRule(rule, value, callback, "楼栋名称", 2, 10);
},
trigger: "blur"
trigger: "blur" //
}
],
floorNum: [
{
required: true,
// integerRule 1 10
validator(rule, value, callback) {
integerRule(rule, value, callback, "楼层数量", 1, 10);
},
trigger: "blur"
trigger: "blur" //
}
]
});
//
interface DialogProps {
title: string;
isView: boolean;
rowData?: any;
api?: (params: any) => Promise<any>;
getTableList?: () => Promise<any>;
title: string; //
isView: boolean; //
rowData?: any; //
api?: (params: any) => Promise<any>; // API
getTableList?: () => Promise<any>; //
}
// { isView: false, title: "" }
const drawerProps = ref<DialogProps>({
isView: false,
title: ""
});
// idnamefloorNum
const formData = ref({
id: "",
name: "",
floorNum: ""
});
//
//
const buildingAcceptParams = async (params: DialogProps) => {
drawerProps.value = params;
if (drawerProps.value.title !== "新增楼栋") {
if (drawerProps.value.title!== "新增楼栋") {
// getBuildingById API formData
const res: any = await getBuildingById({
buildingId: params.rowData.id
});
formData.value = res.data;
}
//
dialogVisible.value = true;
};
// operateNode
const emits = defineEmits(["operateNode"]);
//
//
const handleSubmit = () => {
//
ruleFormRef.value!.validate(async valid => {
if (!valid) return;
try {
// drawerProps.value.api
const res = await drawerProps.value.api!(formData.value);
if (res.code == 200) {
// operateNode
ElMessage.success({
message: res.msg
});
emits("operateNode");
dialogVisible.value = false;
} else {
//
ElMessage.error({
message: res.msg
});
@ -150,11 +187,13 @@ const handleSubmit = () => {
});
};
// buildingAcceptParams 便
defineExpose({
buildingAcceptParams
});
</script>
<style scoped lang="scss">
// 10px
.dialog-footer button:first-child {
margin-right: 10px;
}

@ -1,71 +1,95 @@
<template>
<div>
<!-- Element Plus 的对话框组件 -->
<el-dialog
style="width: 70%"
v-model="dialogVisible"
:title="drawerProps.title"
destroy-on-close
style="width: 70%;"
<!-- 使用 v-model 双向绑定对话框的显示状态 dialogVisible 控制 -->
v-model="dialogVisible"
<!-- 动态绑定对话框的标题 drawerProps.title 获取 -->
:title="drawerProps.title"
<!-- 当对话框关闭时销毁对话框实例 -->
destroy-on-close
>
<div>
<el-form
:model="formData"
class="login-form"
ref="ruleFormRef"
:rules="rules"
label-width="120px"
>
<div class="flex justify-around flex-wrap">
<div class="w-full md:w-1/2">
<el-form-item label="楼层名称:" prop="name">
<el-input v-model="formData.name" clearable />
</el-form-item>
</div>
<div class="w-full md:w-1/2">
<el-form-item label="房间数量:" prop="roomNum">
<el-input v-model="formData.roomNum" clearable />
</el-form-item>
</div>
<!-- 占位 -->
<div
class="md:w-1/2"
v-for="i in 1"
:key="i"
style="height: 0"
></div>
</div>
<div class="flex flex-row-reverse">
<div></div>
</div>
</el-form>
<div>
<!-- Element Plus 的表单组件 -->
<el-form
<!-- 绑定表单的数据模型数据来源于 formData -->
:model="formData"
class="login-form"
<!-- 为表单添加引用方便在 JavaScript 中获取表单实例ruleFormRef 为引用名称 -->
ref="ruleFormRef"
<!-- 绑定表单的验证规则规则定义在 rules -->
:rules="rules"
label-width="120px"
>
<div class="flex justify-around flex-wrap">
<div class="w-full md:w-1/2">
<!-- 表单字段项标签为楼层名称:对应表单数据中的 name 属性 -->
<el-form-item label="楼层名称:" prop="name">
<!-- Element Plus 的输入框组件双向绑定 formData.name 的值并且可清除输入内容 -->
<el-input v-model="formData.name" clearable />
</el-form-item>
</div>
<div class="w-full md:w-1/2">
<!-- 表单字段项标签为房间数量:对应表单数据中的 roomNum 属性 -->
<el-form-item label="房间数量:" prop="roomNum">
<!-- Element Plus 的输入框组件双向绑定 formData.roomNum 的值并且可清除输入内容 -->
<el-input v-model="formData.roomNum" clearable />
</el-form-item>
</div>
<!-- 占位 -->
<div
class="md:w-1/2"
v-for="i in 1"
:key="i"
style="height: 0"
></div>
</div>
<template v-if="!drawerProps.isView" #footer>
<span class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button
class="bg-blue"
type="primary"
@click="handleSubmit"
>
提交
</el-button>
</span>
</template>
<div class="flex flex-row-reverse">
<div></div>
</div>
</el-form>
</div>
<!-- drawerProps.isView false 显示对话框的页脚模板 -->
<template v-if="!drawerProps.isView" #footer>
<span class="dialog-footer">
<!-- 取消按钮点击时将 dialogVisible 设置为 false关闭对话框 -->
<el-button @click="dialogVisible = false">取消</el-button>
<!-- 提交按钮带有蓝色背景类类型为主要按钮点击时调用 handleSubmit 方法 -->
<el-button
class="bg-blue"
type="primary"
@click="handleSubmit"
>
提交
</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script lang="ts" setup>
// Vue reactiverefwatch
import { reactive, ref, watch } from "vue";
// element-plus ElMessageFormInstanceFormRules
import { ElMessage, FormInstance, FormRules } from "element-plus";
// integerRulenumberRulestringRule
import { integerRule, numberRule, stringRule } from "@/utils/formRules";
// API ID getRoomTypeById使
import { getRoomTypeById } from "@/apis/roomType";
// API ID getBuildingById ID getFloorById
import { getBuildingById, getFloorById } from "@/apis/build";
// null
const ruleFormRef = ref<FormInstance | null>(null);
// false
const dialogVisible = ref(false);
// dialogVisible
watch(dialogVisible, (value, oldValue, onCleanup) => {
if (!value) {
// formData
formData.value = {
id: "",
name: "",
@ -76,39 +100,45 @@ watch(dialogVisible, (value, oldValue, onCleanup) => {
}
});
//
const rules = reactive<FormRules>({
name: [
{
required: true,
// stringRule 2 20
validator(rule, value, callback) {
stringRule(rule, value, callback, "楼层名称", 2, 20);
},
trigger: "blur"
trigger: "blur" //
}
],
roomNum: [
{
required: true,
// integerRule 1 50
validator(rule, value, callback) {
integerRule(rule, value, callback, "房间数量", 1, 50);
},
trigger: "blur"
trigger: "blur" //
}
]
});
//
interface DialogProps {
title: string;
isView: boolean;
rowData?: any;
api?: (params: any) => Promise<any>;
getTableList?: () => Promise<any>;
title: string; //
isView: boolean; //
rowData?: any; //
api?: (params: any) => Promise<any>; // API
getTableList?: () => Promise<any>; //
}
// { isView: false, title: "" }
const drawerProps = ref<DialogProps>({
isView: false,
title: ""
});
// idnameroomNumbuildingIdfloorLimit
const formData = ref({
id: "",
name: "",
@ -117,35 +147,43 @@ const formData = ref({
floorLimit: ""
});
//
//
const floorAcceptParams = async (params: DialogProps) => {
drawerProps.value = params;
if (drawerProps.value.title !== "新增楼层") {
if (drawerProps.value.title!== "新增楼层") {
// getFloorById API formData
const res: any = await getFloorById({
floorId: params.rowData.id
});
formData.value = res.data;
}else {
formData.value = params.rowData
} else {
// 使
formData.value = params.rowData;
}
//
dialogVisible.value = true;
};
// operateNode
const emits = defineEmits(["operateNode"]);
//
//
const handleSubmit = () => {
//
ruleFormRef.value!.validate(async valid => {
if (!valid) return;
try {
// drawerProps.value.api
const res = await drawerProps.value.api!(formData.value);
if (res.code == 200) {
// operateNode
ElMessage.success({
message: res.msg
});
emits("operateNode");
dialogVisible.value = false;
} else {
//
ElMessage.error({
message: res.msg
});
@ -156,11 +194,13 @@ const handleSubmit = () => {
});
};
// floorAcceptParams 便
defineExpose({
floorAcceptParams
});
</script>
<style scoped lang="scss">
// 10px
.dialog-footer button:first-child {
margin-right: 10px;
}

@ -1,89 +1,127 @@
<template>
<!-- 根元素包裹整个组件的内容 -->
<div>
<!-- Element Plus 的对话框组件 -->
<el-dialog
style="width: 70%"
v-model="dialogVisible"
:title="drawerProps.title"
destroy-on-close
<!-- 设置对话框的宽度为 70% -->
style="width: 70%"
<!-- 使用 v-model 双向绑定对话框的显示状态dialogVisible 为控制显示隐藏的响应式变量 -->
v-model="dialogVisible"
<!-- 动态绑定对话框的标题标题内容从 drawerProps.title 获取 -->
:title="drawerProps.title"
<!-- 当对话框关闭时销毁对话框实例 -->
destroy-on-close
>
<div>
<el-form
:model="formData"
class="login-form"
ref="ruleFormRef"
:rules="rules"
label-width="120px"
>
<div class="flex justify-around flex-wrap">
<div class="w-full md:w-1/2">
<el-form-item label="房间名称:" prop="name">
<el-input v-model="formData.name" clearable />
</el-form-item>
</div>
<div class="w-full md:w-1/2">
<el-form-item label="房间类型:" prop="typeId">
<el-select
v-model="formData.typeId"
placeholder="请选择"
class="w-full"
>
<el-option
clearable
:label="item.name"
:value="item.id"
v-for="item in roomTypeList"
:key="item.id"
/>
</el-select>
</el-form-item>
</div>
<div class="w-full md:w-1/2">
<el-form-item label="床位数量:" prop="bedNum">
<el-input v-model="formData.bedNum" clearable />
</el-form-item>
</div>
<!-- 占位 -->
<div
class="md:w-1/2"
v-for="i in 1"
:key="i"
style="height: 0"
></div>
</div>
<div class="flex flex-row-reverse">
<div></div>
</div>
</el-form>
<!-- 对话框的内容区域 -->
<div>
<!-- Element Plus 的表单组件 -->
<el-form
<!-- 绑定表单的数据模型数据存储在 formData -->
:model="formData"
<!-- 为表单添加一个类名方便样式控制 -->
class="login-form"
<!-- 为表单添加引用方便在 JavaScript 中访问表单实例ruleFormRef 为引用名称 -->
ref="ruleFormRef"
<!-- 绑定表单的验证规则规则定义在 rules -->
:rules="rules"
<!-- 设置表单标签的宽度为 120px -->
label-width="120px"
>
<!-- 使用 flex 布局元素均匀分布且换行 -->
<div class="flex justify-around flex-wrap">
<!-- 第一个表单字段区域在小屏幕下宽度为 100%在中等及以上屏幕宽度为 50% -->
<div class="w-full md:w-1/2">
<!-- 表单字段项标签为房间名称:对应表单数据中的 name 属性 -->
<el-form-item label="房间名称:" prop="name">
<!-- 输入框组件双向绑定 formData.name 的值且可清除输入内容 -->
<el-input v-model="formData.name" clearable />
</el-form-item>
</div>
<!-- 第二个表单字段区域在小屏幕下宽度为 100%在中等及以上屏幕宽度为 50% -->
<div class="w-full md:w-1/2">
<!-- 表单字段项标签为房间类型:对应表单数据中的 typeId 属性 -->
<el-form-item label="房间类型:" prop="typeId">
<!-- 下拉选择框组件双向绑定 formData.typeId 的值有提示文本请选择宽度占满父容器 -->
<el-select
v-model="formData.typeId"
placeholder="请选择"
class="w-full"
>
<!-- 循环渲染房间类型选项每个选项的 label item.namevalue item.idkey item.id -->
<el-option
clearable
:label="item.name"
:value="item.id"
v-for="item in roomTypeList"
:key="item.id"
/>
</el-select>
</el-form-item>
</div>
<!-- 第三个表单字段区域在小屏幕下宽度为 100%在中等及以上屏幕宽度为 50% -->
<div class="w-full md:w-1/2">
<!-- 表单字段项标签为床位数量:对应表单数据中的 bedNum 属性 -->
<el-form-item label="床位数量:" prop="bedNum">
<!-- 输入框组件双向绑定 formData.bedNum 的值且可清除输入内容 -->
<el-input v-model="formData.bedNum" clearable />
</el-form-item>
</div>
<!-- 占位区域在中等及以上屏幕宽度为 50%循环渲染一次高度为 0 -->
<div
class="md:w-1/2"
v-for="i in 1"
:key="i"
style="height: 0"
></div>
</div>
<template v-if="!drawerProps.isView" #footer>
<span class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button
class="bg-blue"
type="primary"
@click="handleSubmit"
>
提交
</el-button>
</span>
</template>
<!-- 使用 flex 布局子元素反向排列 -->
<div class="flex flex-row-reverse">
<div></div>
</div>
</el-form>
</div>
<!-- drawerProps.isView false 显示对话框的页脚模板 -->
<template v-if="!drawerProps.isView" #footer>
<span class="dialog-footer">
<!-- 取消按钮点击时将 dialogVisible 设置为 false关闭对话框 -->
<el-button @click="dialogVisible = false">取消</el-button>
<!-- 提交按钮bg-blue类样式类型为主要按钮点击时调用 handleSubmit 方法 -->
<el-button
class="bg-blue"
type="primary"
@click="handleSubmit"
>
提交
</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script lang="ts" setup>
// Vue reactiverefwatch
import { reactive, ref, watch } from "vue";
// element-plus ElMessageFormInstanceFormRules
import { ElMessage, FormInstance, FormRules } from "element-plus";
// integerRulenumberRulestringRule
import { integerRule, numberRule, stringRule } from "@/utils/formRules";
// API ID getRoomTypeById使
import { getRoomTypeById } from "@/apis/roomType";
// API ID getBuildingById ID getFloorById ID getRoomById listRoomType
import { getBuildingById, getFloorById, getRoomById, listRoomType } from "@/apis/build";
// null
const ruleFormRef = ref<FormInstance | null>(null);
// false
const dialogVisible = ref(false);
//
const roomTypeList = ref<any>([])
// dialogVisible
watch(dialogVisible, (value, oldValue, onCleanup) => {
if (!value) {
// formData
formData.value = {
id: '',
name: '',
@ -95,40 +133,55 @@ watch(dialogVisible, (value, oldValue, onCleanup) => {
}
});
//
const rules = reactive<FormRules>({
name: [
{
required: true,
// stringRule 2 30
validator(rule, value, callback) {
stringRule(rule, value, callback, "房间名称", 2, 30);
},
//
trigger: "blur"
}
],
typeId: [
{
required: true,
// typeId
message: "请选择房间类型",
trigger: "blur"
}
],
typeId: [{ required: true, message: "请选择房间类型", trigger: "blur" }],
bedNum: [
{
required: true,
// integerRule 1 8
validator(rule, value, callback) {
integerRule(rule, value, callback, "床位数量", 1, 8);
},
//
trigger: "blur"
}
]
});
//
interface DialogProps {
title: string;
isView: boolean;
rowData?: any;
api?: (params: any) => Promise<any>;
getTableList?: () => Promise<any>;
title: string; //
isView: boolean; //
rowData?: any; //
api?: (params: any) => Promise<any>; // API
getTableList?: () => Promise<any>; //
}
// { isView: false, title: "" }
const drawerProps = ref<DialogProps>({
isView: false,
title: ""
});
// idnametypeIdbedNumfloorIdroomLimit
const formData = ref({
id: '',
name: '',
@ -138,53 +191,68 @@ const formData = ref({
roomLimit: ''
});
//
//
const roomAcceptParams = async (params: DialogProps) => {
drawerProps.value = params;
if (drawerProps.value.title !== "新增房间") {
if (drawerProps.value.title!== "新增房间") {
// getRoomById API formData
const res: any = await getRoomById({
roomId: params.rowData.id
});
formData.value = res.data;
}else {
formData.value = params.rowData
} else {
// 使
formData.value = params.rowData;
}
const res:any = await listRoomType()
roomTypeList.value = res.data
//
const res: any = await listRoomType()
roomTypeList.value = res.data;
// formData
console.log(formData.value);
//
dialogVisible.value = true;
};
// operateNode
const emits = defineEmits(["operateNode"]);
//
//
const handleSubmit = () => {
//
ruleFormRef.value!.validate(async valid => {
if (!valid) return;
try {
// drawerProps.value.api
const res = await drawerProps.value.api!(formData.value);
if (res.code == 200) {
//
ElMessage.success({
message: res.msg
});
// operateNode
emits("operateNode");
//
dialogVisible.value = false;
} else {
//
ElMessage.error({
message: res.msg
});
}
} catch (error) {
//
console.log(error);
}
});
};
// roomAcceptParams 便
defineExpose({
roomAcceptParams
});
</script>
<style scoped lang="scss">
// 10px
.dialog-footer button:first-child {
margin-right: 10px;
}

@ -1,87 +1,132 @@
<template>
<!-- 根元素设置为 flex 布局用于包裹页面的主要内容区域 -->
<div style="display: flex;">
<!-- 左侧区域背景颜色为白色flex 占比为 2右边距 10px内边距 10px -->
<div class="bg-white" style="flex:2;margin-right: 10px;padding: 10px;">
<!-- 可滚动区域高度为 560px上边距 10px用于包裹树状结构 -->
<el-scrollbar height="560px" style="margin-top: 10px;">
<!-- Element Plus 的树状组件 -->
<el-tree
ref="treeRef"
:data="buildTree"
:props="defaultProps"
:default-expand-all="true"
show-checkbox
check-strictly
@check="handleCheck"
<!-- 为树状组件添加引用方便在 JavaScript 中访问树组件实例 -->
ref="treeRef"
<!-- 绑定树状结构的数据buildTree 是存储树数据的响应式变量 -->
:data="buildTree"
<!-- 定义树状结构的属性映射children 对应子节点数组属性名label 对应节点显示名称属性名 -->
:props="defaultProps"
<!-- 设置默认展开所有节点 -->
:default-expand-all="true"
<!-- 显示复选框让用户可以选择节点 -->
show-checkbox
<!-- 开启严格的父子节点选中逻辑即父子节点选中状态互不影响 -->
check-strictly
<!-- 监听复选框的点击事件当复选框状态改变时调用 handleCheck 方法 -->
@check="handleCheck"
/>
</el-scrollbar>
</div>
<!-- 右侧区域类名为 table-boxflex 占比为 8用于放置表格和相关操作元素 -->
<div class="table-box" style="flex: 8">
<!-- 自定义的表格组件 ProTable -->
<ProTable
ref="proTable"
title="用户列表"
:columns="columns"
:requestApi="getTableList"
:initParam="initParam"
:dataCallback="dataCallback"
<!-- 为表格组件添加引用方便在 JavaScript 中访问表格组件实例 -->
ref="proTable"
<!-- 设置表格的标题为用户列表 -->
title="用户列表"
<!-- 绑定表格的列配置信息columns 是一个包含列属性的数组 -->
:columns="columns"
<!-- 绑定获取表格数据的 API 函数getTableList 用于发起数据请求 -->
:requestApi="getTableList"
<!-- 绑定初始的请求参数对象initParam 是一个响应式对象 -->
:initParam="initParam"
<!-- 绑定数据处理回调函数dataCallback 用于对从 API 获取的数据进行处理 -->
:dataCallback="dataCallback"
>
<!-- 表格 header 按钮 -->
<template #tableHeader>
<el-button type="primary" :icon="Plus" @click="openDrawer('node','新增')">
<span>新增节点</span>
</el-button>
<el-button type="warning" :icon="EditPen" @click="openDrawer('node','编辑')">
<span>编辑节点</span>
</el-button>
<el-button type="danger" :icon="Delete" @click="deleteTypeData">
<span>删除节点</span>
</el-button>
</template>
<!-- 表格头部按钮的插槽用于自定义表格头部的按钮 -->
<template #tableHeader>
<!-- 新增节点按钮按钮类型为主要按钮绑定 Plus 图标点击时调用 openDrawer 方法传递类型为node和操作名称为新增 -->
<el-button type="primary" :icon="Plus" @click="openDrawer('node','新增')">
<span>新增节点</span>
</el-button>
<!-- 编辑节点按钮按钮类型为警告按钮绑定 EditPen 图标点击时调用 openDrawer 方法传递类型为node和操作名称为编辑 -->
<el-button type="warning" :icon="EditPen" @click="openDrawer('node','编辑')">
<span>编辑节点</span>
</el-button>
<!-- 删除节点按钮按钮类型为危险按钮绑定 Delete 图标点击时调用 deleteTypeData 方法 -->
<el-button type="danger" :icon="Delete" @click="deleteTypeData">
<span>删除节点</span>
</el-button>
</template>
<!-- 表格操作 -->
<template #operation="scope">
<el-button
size="small"
link
:icon="View"
@click="openDrawer('bed','查看',scope)"
>
查看
</el-button>
<el-button
size="small"
link
:icon="EditPen"
@click="openDrawer('bed','编辑',scope)"
>
编辑
</el-button>
<el-popconfirm
title="Are you sure to delete this?"
@confirm="deleteBedData(scope)"
confirm-button-type="danger"
>
<template #reference>
<el-button size="small" link :icon="Delete">删除</el-button>
</template>
</el-popconfirm>
<!-- 表格操作列的插槽scope 包含当前行的数据等信息用于自定义表格的操作按钮 -->
<template #operation="scope">
<!-- 查看按钮按钮尺寸为小为链接样式绑定 View 图标点击时调用 openDrawer 方法传递类型为bed操作名称为查看以及当前行数据 scope -->
<el-button
size="small"
link
:icon="View"
@click="openDrawer('bed','查看',scope)"
>
查看
</el-button>
<!-- 编辑按钮按钮尺寸为小为链接样式绑定 EditPen 图标点击时调用 openDrawer 方法传递类型为bed操作名称为编辑以及当前行数据 scope -->
<el-button
size="small"
link
:icon="EditPen"
@click="openDrawer('bed','编辑',scope)"
>
编辑
</el-button>
<!-- 弹出确认删除的对话框组件 -->
<el-popconfirm
<!-- 设置对话框的提示标题 -->
title="Are you sure to delete this?"
<!-- 确认删除时调用 deleteBedData 方法并传递当前行数据 scope -->
@confirm="deleteBedData(scope)"
<!-- 设置确认按钮的类型为危险按钮 -->
confirm-button-type="danger"
>
<!-- 对话框的触发元素插槽这里是一个删除按钮按钮尺寸为小为链接样式绑定 Delete 图标 -->
<template #reference>
<el-button size="small" link :icon="Delete">删除</el-button>
</template>
</el-popconfirm>
</template>
</ProTable>
<!-- 引入自定义的 buildingDialog 组件监听 operateNode 事件当事件触发时调用 operateNode 方法同时为组件添加引用 buildingDialogRef -->
<buildDialog @operateNode="operateNode" ref="buildingDialogRef" />
<!-- 引入自定义的 floorDialog 组件监听 operateNode 事件当事件触发时调用 operateNode 方法同时为组件添加引用 floorDialogRef -->
<floorDialog @operateNode="operateNode" ref="floorDialogRef" />
<!-- 引入自定义的 roomDialog 组件监听 operateNode 事件当事件触发时调用 operateNode 方法同时为组件添加引用 roomDialogRef -->
<roomDialog @operateNode="operateNode" ref="roomDialogRef" />
<!-- 引入自定义的 bedDialog 组件监听 operateNode 事件当事件触发时调用 operateNode 方法同时为组件添加引用 bedDialogRef -->
<bedDialog @operateNode="operateNode" ref="bedDialogRef" />
</div>
</div>
</template>
<script setup lang="ts" name="useProTable">
// Vue ref
// reactive
// onMounted
import { ref, reactive, onMounted } from "vue";
// element-plus ElMessage
import { ElMessage } from "element-plus";
// buildingDialog
import buildDialog from "./dialog/build.vue";
// floorDialog
import floorDialog from "./dialog/floor.vue";
// roomDialog
import roomDialog from "./dialog/room.vue";
// bedDialog
import bedDialog from "./dialog/bed.vue";
// ColumnProps
import { ColumnProps } from "@/components/ProTable/interface";
// ProTable
import ProTable from "@/components/ProTable/index.vue";
// Element Plus DeleteEditPenViewPlus
import { Delete, EditPen, View, Plus } from "@element-plus/icons-vue";
// API
import {
addBed,
addBuilding, addFloor, addRoom,
@ -93,40 +138,49 @@ import {
pageBedByKey
} from "@/apis/build";
// ProTable 便
// ProTable
const proTable = ref();
//
const initParam = reactive({});
//
const treeRef = ref<any>();
//
const ICheckedNode = ref<any>({});
//
const buildTree = ref<any>([]);
//
const defaultProps = {
children: "childrenList",
label: "name"
};
//
//
const handleCheck = (checkedNode: any, checkedNodeDetail: any) => {
if (checkedNodeDetail.checkedNodes.length > 0) {
//
ICheckedNode.value = checkedNode;
} else {
//
ICheckedNode.value = { id: -1, level: -1 };
}
// 1 10
getTableListHandle(1, 10);
//
//
treeRef.value.getCheckedNodes().forEach((node: any) => {
if (node.onlyMark !== ICheckedNode.value.onlyMark) {
if (node.onlyMark!== ICheckedNode.value.onlyMark) {
treeRef.value.setChecked(node, false);
}
});
};
//
//
onMounted(() => {
//
getBuildTreeHandle();
});
// dataCallback list && total && pageNum && pageSize
// hooks/useTable.ts
// API
const dataCallback = (data: any) => {
return {
list: data.list,
@ -136,18 +190,22 @@ const dataCallback = (data: any) => {
};
};
// params
// ProTable :requestApi="getUserList"
//
const getTableList = (params: any) => {
//
params = addSearchField(params);
//
let newParams = JSON.parse(JSON.stringify(params));
// pageBedByKey API
return pageBedByKey(newParams);
};
//
//
const getTableListHandle = (pageNum: number, pageSize: number) => {
let params: any = {};
//
params = addSearchField(params);
// ProTable getTableList
proTable.value.getTableList({
pageNum: pageNum,
pageSize: pageSize,
@ -158,31 +216,39 @@ const getTableListHandle = (pageNum: number, pageSize: number) => {
});
};
//
//
const getBuildTreeHandle = async () => {
//
ICheckedNode.value = { id: -1, level: -1 };
// getNoBedTree API
const res: any = await getNoBedTree();
//
buildTree.value = res.data;
// 1 10
getTableListHandle(1, 10);
};
//
//
const addSearchField = (params: any) => {
if (ICheckedNode.value.id !== -1) {
if (ICheckedNode.value.id!== -1) {
if (ICheckedNode.value.level === 1) {
// buildingId id floorId roomId
params.buildId = ICheckedNode.value.id;
params.floorId = null;
params.roomId = null;
} else if (ICheckedNode.value.level === 2) {
// floorId id buildingId roomId
params.buildId = null;
params.floorId = ICheckedNode.value.id;
params.roomId = null;
} else if (ICheckedNode.value.level === 3) {
// roomId id buildingId floorId
params.buildId = null;
params.floorId = null;
params.roomId = ICheckedNode.value.id;
}
} else {
// buildingIdfloorId roomId
params.buildId = null;
params.floorId = null;
params.roomId = null;
@ -190,61 +256,73 @@ const addSearchField = (params: any) => {
return params;
};
//
//
const deleteTypeData = async () => {
if ((Object.keys(ICheckedNode.value).length === 0 || ICheckedNode.value.id === -1)) {
//
ElMessage.warning("请选择节点");
return;
}
// deleteNode API id
const res: any = await deleteNode({
id: ICheckedNode.value.id,
mark: ICheckedNode.value.level === 1 ? "楼栋" : ICheckedNode.value.level === 2 ? "楼层" : ICheckedNode.value.level === 3 ? "房间" : ""
mark: ICheckedNode.value.level === 1? "楼栋" : ICheckedNode.value.level === 2? "楼层" : ICheckedNode.value.level === 3? "房间" : ""
});
if (res.code === 200) {
//
ElMessage.success(res.msg);
await getBuildTreeHandle();
} else {
//
ElMessage.error(res.msg);
}
};
//
//
const deleteBedData = async (params: any) => {
// deleteBed API id
const res: any = await deleteBed({
bedId: params.row.id
});
if (res.code === 200) {
//
ElMessage.success(res.msg);
proTable.value.getTableList();
} else {
//
ElMessage.error(res.msg);
}
};
// drawer()
// buildingDialogfloorDialogroomDialogbedDialog
const buildingDialogRef = ref();
const floorDialogRef = ref();
const roomDialogRef = ref();
const bedDialogRef = ref();
//
const openDrawer = (type: string, title: string, rowData: any = {}) => {
if (type === "node") {
//
//
if (title === "编辑" && (Object.keys(ICheckedNode.value).length === 0 || ICheckedNode.value.id === -1)) {
ElMessage.warning("请选择节点");
return;
}
//
//
if (title === "新增") {
const parentNode = treeRef.value.getCheckedNodes()[0];
if (ICheckedNode.value.level === -1) {//
if (ICheckedNode.value.level === -1) {
//
const params = {
title: title + "楼栋",
rowData: {},
isView: false,
api: addBuilding
};
// buildingDialog buildingAcceptParams
buildingDialogRef.value.buildingAcceptParams(params);
} else if (ICheckedNode.value.level === 1) {//
} else if (ICheckedNode.value.level === 1) {
//
const params = {
title: title + "楼层",
rowData: {
@ -254,8 +332,10 @@ const openDrawer = (type: string, title: string, rowData: any = {}) => {
isView: false,
api: addFloor
};
// floorDialog floorAcceptParams
floorDialogRef.value.floorAcceptParams(params);
} else if (ICheckedNode.value.level === 2) {//
} else if (ICheckedNode.value.level === 2) {
//
const params = {
title: title + "房间",
rowData: {
@ -265,99 +345,10 @@ const openDrawer = (type: string, title: string, rowData: any = {}) => {
isView: false,
api: addRoom
};
// roomDialog roomAcceptParams
roomDialogRef.value.roomAcceptParams(params);
} else if (ICheckedNode.value.level === 3) {//
} else if (ICheckedNode.value.level === 3) {
//
const params = {
title: title + "床位",
rowData: {
roomId: parentNode.roomId,
bedLimit: parentNode.bedLimit
},
isView: false,
api: addBed
};
bedDialogRef.value.bedAcceptParams(params);
}
} else if (title === "编辑") {
if (ICheckedNode.value.level === 1) {//
const params = {
title: title + "楼栋",
rowData: { ...ICheckedNode.value },
isView: false,
api: editBuilding
};
buildingDialogRef.value.buildingAcceptParams(params);
} else if (ICheckedNode.value.level === 2) {//
const params = {
title: title + "楼层",
rowData: { ...ICheckedNode.value },
isView: false,
api: editFloor
};
floorDialogRef.value.floorAcceptParams(params);
} else if (ICheckedNode.value.level === 3) {//
const params = {
title: title + "房间",
rowData: { ...ICheckedNode.value },
isView: false,
api: editRoom
};
roomDialogRef.value.roomAcceptParams(params);
}
}
} else if (type === "bed") {//
const params = {
title,
rowData: { ...rowData.row },
isView: title === "查看",
api: title === "新增" ? addBed : title === "编辑" ? editBed : "",
};
bedDialogRef.value.bedAcceptParams(params);
}
};
//
const operateNode = () => {
getBuildTreeHandle();
};
//
const columns: ColumnProps<any>[] = [
{ prop: "rank", label: "序号", width: 55 },
{ prop: "name", label: "床位名称" },
{
enum: IBedFlagList,
prop: "bedFlag",
label: "床位状态",
search: { el: "select", props: { filterable: false } }
},
{ prop: "operation", label: "操作", width: 220 }
];
</script>
<style lang="scss" scoped>
:deep(.el-radio__input) {
display: none !important;
}
:deep(.el-radio__label) {
font-size: 1em;
width: 248px;
padding: 5px 0 5px 10px;
transition: all 0.2s;
&:hover {
background-color: #eeeeee;
}
}
.el-radio-group {
display: flex;
flex-wrap: wrap;
.el-radio {
flex: 1;
margin-right: 100%;
}
}
</style>
rowData:

@ -1,7 +1,17 @@
<template>
<!-- router-view Vue Router 提供的一个组件用于显示当前路由对应的组件内容
当在路由配置中定义了不同的路径和对应的组件后匹配到的组件会渲染到这里 -->
<router-view></router-view>
</template>
<script setup lang="ts"></script>
<script setup lang="ts">
// 使 Vue 3 <script setup> API
//
//
</script>
<style lang="scss" scoped></style>
<style lang="scss" scoped>
// lang="scss" 使 SCSS SCSS CSS
// scoped
//
</style>

@ -1,118 +1,137 @@
<template>
<!-- 最外层容器使用 table-box 类名 -->
<div class="table-box">
<!-- 自定义表格组件 ProTable -->
<ProTable
ref="proTable"
title="用户列表"
:columns="columns"
:requestApi="getTableList"
:initParam="initParam"
:dataCallback="dataCallback"
ref="proTable" <!-- 引用 ProTable 组件实例 -->
title="用户列表" <!-- 表格标题 -->
:columns="columns" <!-- 表格列配置 -->
:requestApi="getTableList" <!-- 请求表格数据的 API 函数 -->
:initParam="initParam" <!-- 初始化请求参数 -->
:dataCallback="dataCallback" <!-- 处理表格数据的回调函数 -->
>
<!-- 表格 header 按钮 -->
<template #tableHeader>
<el-button @click="openDrawer('新增')">
<IconPark :icon="Plus" class="mr-1"></IconPark>
<span>新增</span></el-button
>
</template>
<!-- 表格头部自定义内容 -->
<template #tableHeader>
<!-- 新增按钮点击调用 openDrawer 方法打开抽屉 -->
<el-button @click="openDrawer('新增')">
<!-- 新增图标 -->
<IconPark :icon="Plus" class="mr-1"></IconPark>
<span>新增</span>
</el-button>
</template>
<!-- 表格操作 -->
<template #operation="scope">
<el-button
size="small"
link
:icon="View"
@click="openDrawer('查看',scope)"
>
查看
</el-button>
<el-button
size="small"
link
:icon="EditPen"
@click="openDrawer('编辑',scope)"
>
编辑
</el-button>
<el-popconfirm
title="Are you sure to delete this?"
@confirm="deleteData(scope)"
confirm-button-type="danger"
>
<template #reference>
<el-button size="small" link :icon="Delete">删除</el-button>
</template>
</el-popconfirm>
<!-- 表格操作列自定义内容 -->
<template #operation="scope">
<!-- 查看按钮点击调用 openDrawer 方法打开抽屉查看详情 -->
<el-button
size="small"
link
:icon="View"
@click="openDrawer('查看', scope)"
>
查看
</el-button>
<!-- 编辑按钮点击调用 openDrawer 方法打开抽屉进行编辑 -->
<el-button
size="small"
link
:icon="EditPen"
@click="openDrawer('编辑', scope)"
>
编辑
</el-button>
<!-- 确认删除的弹出框 -->
<el-popconfirm
title="Are you sure to delete this?" <!-- 确认提示信息 -->
@confirm="deleteData(scope)" <!-- 确认删除时调用 deleteData 方法 -->
confirm-button-type="danger" <!-- 确认按钮类型为危险 -->
>
<template #reference>
<!-- 删除按钮 -->
<el-button size="small" link :icon="Delete">删除</el-button>
</template>
</el-popconfirm>
</template>
</ProTable>
<!-- 自定义房间对话框组件 -->
<roomDialog ref="DialogRef" />
</div>
</template>
<script setup lang="ts" name="useProTable">
// Vue ref reactive
import { ref, reactive } from "vue";
// ElementPlus
import { ElMessage } from "element-plus";
//
import roomDialog from "./roomDialog/index.vue";
//
import { Plus } from "@icon-park/vue-next";
//
import { ColumnProps } from "@/components/ProTable/interface";
//
import ProTable from "@/components/ProTable/index.vue";
//
import { Delete, EditPen, View } from "@element-plus/icons-vue";
// API
import { addRoomType, deleteRoomType, editRoomType, pageRoomTypeByKey } from "@/apis/roomType";
// ProTable 便
// ProTable
const proTable = ref();
// 使
const initParam = reactive({});
// dataCallback list && total && pageNum && pageSize
// hooks/useTable.ts
//
// listtotalpageNumpageSize
const dataCallback = (data: any) => {
return {
list: data.list,
total: data.total,
pageNum: data.pageNum,
pageSize: data.pageSize
list: data.list, //
total: data.total, //
pageNum: data.pageNum, //
pageSize: data.pageSize //
};
};
// params
// ProTable :requestApi="getUserList"
// API
//
const getTableList = (params: any) => {
let newParams = JSON.parse(JSON.stringify(params));
return pageRoomTypeByKey(newParams);
let newParams = JSON.parse(JSON.stringify(params)); //
return pageRoomTypeByKey(newParams); // API
};
//
//
const deleteData = async (params: any) => {
const res: any = await deleteRoomType({
roomTypeId: params.row.id
roomTypeId: params.row.id // ID
});
if (res.code === 200) {
ElMessage.success(res.msg);
proTable.value.getTableList();
} else {
ElMessage.error(res.msg);
if (res.code === 200) { //
ElMessage.success(res.msg); //
proTable.value.getTableList(); //
} else { //
ElMessage.error(res.msg); //
}
};
// drawer()
//
const DialogRef = ref();
//
const openDrawer = (title: string, rowData: any = {}) => {
const params = {
title,
rowData: { ...rowData.row },
isView: title === "查看",
api: title === "新增" ? addRoomType : title === "编辑" ? editRoomType : "",
getTableList: proTable.value.getTableList
title, //
rowData: { ...rowData.row }, //
isView: title === "查看", //
api: title === "新增" ? addRoomType : title === "编辑" ? editRoomType : "", // API
getTableList: proTable.value.getTableList //
};
DialogRef.value.acceptParams(params);
DialogRef.value.acceptParams(params); // acceptParams
};
//
//
const columns: ColumnProps<any>[] = [
{ prop: "rank", label: "序号", width: 55 },
{ prop: "name", label: "房间类型", search: { el: "input" } },
{ prop: "monthPrice", label: "月费" },
{ prop: "operation", label: "操作", width: 220 }
{ prop: "rank", label: "序号", width: 55 }, //
{ prop: "name", label: "房间类型", search: { el: "input" } }, //
{ prop: "monthPrice", label: "月费" }, //
{ prop: "operation", label: "操作", width: 220 } //
];
</script>

@ -1,107 +1,136 @@
<template>
<!-- 最外层的div容器 -->
<div>
<!-- ElementPlus的对话框组件 -->
<el-dialog
style="width: 70%"
v-model="dialogVisible"
:title="drawerProps.title"
destroy-on-close
style="width: 70%" <!-- 设置对话框宽度为70% -->
v-model="dialogVisible" <!-- 控制对话框的显示与隐藏 -->
:title="drawerProps.title" <!-- 动态设置对话框的标题 -->
destroy-on-close <!-- 对话框关闭时销毁内容 -->
>
<div>
<el-form
:model="formData"
class="login-form"
ref="ruleFormRef"
:rules="rules"
label-width="120px"
>
<div class="flex justify-around flex-wrap">
<div class="w-full md:w-1/2">
<el-form-item label="房间类型:" prop="name">
<el-input v-model="formData.name" clearable />
</el-form-item>
</div>
<div class="w-full md:w-1/2">
<el-form-item label="月费:" prop="monthPrice">
<el-input v-model="formData.monthPrice" clearable />
</el-form-item>
</div>
<!-- 占位 -->
<div
class="md:w-1/2"
v-for="i in 1"
:key="i"
style="height: 0"
></div>
</div>
<div class="flex flex-row-reverse">
<div></div>
</div>
</el-form>
<!-- 对话框内容部分 -->
<div>
<!-- ElementPlus的表单组件 -->
<el-form
:model="formData" <!-- 绑定表单数据 -->
class="login-form" <!-- 添加类名 -->
ref="ruleFormRef" <!-- 引用表单实例 -->
:rules="rules" <!-- 绑定表单验证规则 -->
label-width="120px" <!-- 设置表单标签的宽度 -->
>
<!-- 使用flex布局内容水平环绕且两端对齐 -->
<div class="flex justify-around flex-wrap">
<!-- 在全尺寸屏幕下占满一行在中等尺寸及以上屏幕下占半行 -->
<div class="w-full md:w-1/2">
<!-- 表单的一个表单项标签为房间类型 -->
<el-form-item label="房间类型:" prop="name">
<!-- 输入框绑定表单数据中的name字段 -->
<el-input v-model="formData.name" clearable />
</el-form-item>
</div>
<!-- 在全尺寸屏幕下占满一行在中等尺寸及以上屏幕下占半行 -->
<div class="w-full md:w-1/2">
<!-- 表单的一个表单项标签为月费 -->
<el-form-item label="月费:" prop="monthPrice">
<!-- 输入框绑定表单数据中的monthPrice字段 -->
<el-input v-model="formData.monthPrice" clearable />
</el-form-item>
</div>
<!-- 占位元素在中等尺寸及以上屏幕下占半行高度为0 -->
<div
class="md:w-1/2"
v-for="i in 1"
:key="i"
style="height: 0"
></div>
</div>
<template v-if="!drawerProps.isView" #footer>
<span class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button
class="bg-blue"
type="primary"
@click="handleSubmit"
>
提交
</el-button>
</span>
</template>
<!-- 内容水平反向排列 -->
<div class="flex flex-row-reverse">
<div></div>
</div>
</el-form>
</div>
<!-- 当不是查看模式时显示对话框底部 -->
<template v-if="!drawerProps.isView" #footer>
<span class="dialog-footer">
<!-- 取消按钮点击后隐藏对话框 -->
<el-button @click="dialogVisible = false">取消</el-button>
<!-- 提交按钮点击后触发handleSubmit方法 -->
<el-button
class="bg-blue"
type="primary"
@click="handleSubmit"
>
提交
</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script lang="ts" setup>
// Vue
import { reactive, ref, watch } from "vue";
// ElementPlus
import { ElMessage, FormInstance, FormRules } from "element-plus";
//
import { numberRule, stringRule } from "@/utils/formRules";
// API
import { getRoomTypeById } from "@/apis/roomType";
// null
const ruleFormRef = ref<FormInstance | null>(null);
// false
const dialogVisible = ref(false);
//
watch(dialogVisible, (value, oldValue, onCleanup) => {
//
if (!value) {
formData.value = { id: "", name: "", monthPrice: "" };
}
});
//
const rules = reactive<FormRules>({
name: [
{
required: true,
required: true, //
validator(rule, value, callback) {
//
stringRule(rule, value, callback, "房间类型", 2, 10);
},
trigger: "blur"
trigger: "blur" //
}
],
monthPrice: [
{
required: true,
required: true, //
validator(rule, value, callback) {
//
numberRule(rule, value, callback, "月费", 1, 1000000);
},
trigger: "blur"
trigger: "blur" //
}
]
});
//
interface DialogProps {
title: string;
isView: boolean;
rowData?: any;
api?: (params: any) => Promise<any>;
getTableList?: () => Promise<any>;
title: string; //
isView: boolean; //
rowData?: any; //
api?: (params: any) => Promise<any>; //
getTableList?: () => Promise<any>; //
}
//
const drawerProps = ref<DialogProps>({
isView: false,
title: ""
});
// idnamemonthPrice
const formData = ref({
id: "",
name: "",
@ -110,22 +139,29 @@ const formData = ref({
//
const acceptParams = async (params: DialogProps) => {
//
drawerProps.value = params;
//
if (drawerProps.value.title !== "新增") {
const res: any = await getRoomTypeById({
roomTypeId: params.rowData.id
});
formData.value = res.data;
}
//
dialogVisible.value = true;
};
//
//
const handleSubmit = () => {
//
ruleFormRef.value!.validate(async valid => {
//
if (!valid) return;
try {
//
const res = await drawerProps.value.api!(formData.value);
// 200
if (res.code == 200) {
ElMessage.success({
message: res.msg
@ -133,21 +169,25 @@ const handleSubmit = () => {
drawerProps.value.getTableList!();
dialogVisible.value = false;
} else {
//
ElMessage.error({
message: res.msg
});
}
} catch (error) {
//
console.log(error);
}
});
};
// acceptParams
defineExpose({
acceptParams
});
</script>
<style scoped lang="scss">
/* 设置对话框底部取消按钮和提交按钮的间距 */
.dialog-footer button:first-child {
margin-right: 10px;
}

@ -1,7 +1,19 @@
<template>
<!-- router-view Vue Router 提供的一个组件用于显示当前路由匹配到的组件内容
Vue Router 的应用中当定义了不同的路由路径和对应的组件后当某个路由被匹配时
对应的组件就会渲染到这个 router-view 标签所在的位置 -->
<router-view></router-view>
</template>
<script setup lang="ts"></script>
<script setup lang="ts">
// 使 Vue 3 <script setup> 使 API
//
//
</script>
<style lang="scss" scoped></style>
<style lang="scss" scoped>
// lang="scss" 使 SCSSSassy CSSSCSS CSS
//
// scoped 使
//
</style>

@ -1,5 +1,9 @@
<template>
<!-- 定义一个带有 table-box 类名的容器 div用于包裹整个表格相关内容 -->
<div class="table-box">
<!-- 自定义的 ProTable 组件ref 用于获取组件实例title 设置表格标题
columns 绑定表格列配置requestApi 是获取表格数据的方法
initParam 是初始化请求参数dataCallback 用于处理返回的表格数据 -->
<ProTable
ref="proTable"
title="用户列表"
@ -8,16 +12,19 @@
:initParam="initParam"
:dataCallback="dataCallback"
>
<!-- 表格 header 按钮 -->
<!-- 表格头部自定义插槽用于放置在表格头部显示的内容 -->
<template #tableHeader>
<!-- 新增按钮点击时调用 openDrawer 方法并传入 '新增' 作为参数用于打开新增数据的抽屉 -->
<el-button @click="openDrawer('新增')">
<!-- 使用 IconPark 组件显示新增图标设置图标样式和右边距 -->
<IconPark :icon="Plus" class="mr-1"></IconPark>
<span>新增</span></el-button
>
<span>新增</span>
</el-button>
</template>
<!-- 表格操作 -->
<!-- 表格操作列的自定义插槽scope 表示当前行的上下文数据 -->
<template #operation="scope">
<!-- 查看按钮点击时调用 openDrawer 方法并传入 '查看' 和当前行数据 scope用于打开查看数据的抽屉 -->
<el-button
size="small"
link
@ -26,6 +33,7 @@
>
查看
</el-button>
<!-- 编辑按钮点击时调用 openDrawer 方法并传入 '编辑' 和当前行数据 scope用于打开编辑数据的抽屉 -->
<el-button
size="small"
link
@ -34,85 +42,99 @@
>
编辑
</el-button>
<!-- 确认删除的弹出框组件title 设置提示信息@confirm 绑定删除数据的方法 deleteData
confirm-button-type 设置确认按钮的类型为危险 -->
<el-popconfirm
title="Are you sure to delete this?"
@confirm="deleteData(scope)"
confirm-button-type="danger"
>
<!-- 定义弹出框的引用内容这里是一个删除按钮 -->
<template #reference>
<el-button size="small" link :icon="Delete">删除</el-button>
</template>
</el-popconfirm>
</template>
</ProTable>
<!-- 自定义的 originDialog 组件ref 用于获取组件实例 -->
<originDialog ref="DialogRef" />
</div>
</template>
<script setup lang="ts" name="useProTable">
// Vue ref reactive
import { ref, reactive } from "vue";
// Element Plus ElMessage
import { ElMessage } from "element-plus";
// originDialog
import originDialog from "./originDialog/index.vue";
// IconPark Plus
import { Plus } from "@icon-park/vue-next";
// ColumnProps
import { ColumnProps } from "@/components/ProTable/interface";
// ProTable
import ProTable from "@/components/ProTable/index.vue";
// Element Plus DeleteEditPenView
import { Delete, EditPen, View } from "@element-plus/icons-vue";
// API
import { addSource, deleteSource, editSource, pageSourceByKey } from "@/apis/source";
// ProTable 便
// ProTable undefined
const proTable = ref();
//
const initParam = reactive({});
// dataCallback list && total && pageNum && pageSize
// hooks/useTable.ts
//
const dataCallback = (data: any) => {
return {
list: data.list,
total: data.total,
pageNum: data.pageNum,
pageSize: data.pageSize
list: data.list, //
total: data.total, //
pageNum: data.pageNum, //
pageSize: data.pageSize //
};
};
// params
// ProTable :requestApi="getUserList"
// API
const getTableList = (params: any) => {
let newParams = JSON.parse(JSON.stringify(params));
return pageSourceByKey(newParams);
let newParams = JSON.parse(JSON.stringify(params)); //
return pageSourceByKey(newParams); // API
};
//
// sourceId API
const deleteData = async (params: any) => {
const res: any = await deleteSource({
sourceId: params.row.id
});
if (res.code === 200) {
ElMessage.success(res.msg);
proTable.value.getTableList();
if (res.code === 200) { // API 200
ElMessage.success(res.msg); //
proTable.value.getTableList(); // ProTable
} else {
ElMessage.error(res.msg);
ElMessage.error(res.msg); //
}
};
// drawer()
// originDialog undefined
const DialogRef = ref();
// title originDialog acceptParams
const openDrawer = (title: string, rowData: any = {}) => {
const params = {
title,
rowData: { ...rowData.row },
isView: title === "查看",
api: title === "新增" ? addSource : title === "编辑" ? editSource : "",
getTableList: proTable.value.getTableList
rowData: { ...rowData.row }, //
isView: title === "查看", //
api: title === "新增" ? addSource : title === "编辑" ? editSource : "", // title API
getTableList: proTable.value.getTableList //
};
DialogRef.value.acceptParams(params);
DialogRef.value.acceptParams(params); // originDialog acceptParams
};
//
//
const columns: ColumnProps<any>[] = [
{ prop: "rank", label: "序号", width: 55 },
{ prop: "name", label: "渠道名称", search: { el: "input" } },
{ prop: "operation", label: "操作", width: 220 }
{ prop: "rank", label: "序号", width: 55 }, //
{ prop: "name", label: "渠道名称", search: { el: "input" } }, //
{ prop: "operation", label: "操作", width: 220 } //
];
</script>
<style lang="scss" scoped></style>
<style lang="scss" scoped>
// scoped
</style>

@ -1,138 +1,172 @@
<template>
<div>
<!-- 一个具有固定宽度的对话框 -->
<el-dialog
style="width: 70%"
v-model="dialogVisible"
:title="drawerProps.title"
destroy-on-close
v-model="dialogVisible" <!-- 双向绑定对话框的显示与隐藏状态 -->
:title="drawerProps.title" <!-- 动态设置对话框的标题 -->
destroy-on-close <!-- 当对话框关闭时销毁其中的内容 -->
>
<div>
<el-form
:model="formData"
class="login-form"
ref="ruleFormRef"
:rules="rules"
label-width="120px"
>
<div class="flex justify-around flex-wrap">
<div class="w-full md:w-1/2">
<el-form-item label="渠道名称:" prop="name">
<el-input v-model="formData.name" clearable />
</el-form-item>
</div>
<!-- 占位 -->
<div
class="md:w-1/2"
v-for="i in 1"
:key="i"
style="height: 0"
></div>
</div>
<div class="flex flex-row-reverse">
<div></div>
</div>
</el-form>
<div>
<!-- 一个表单 -->
<el-form
:model="formData" <!-- 绑定表单数据 -->
class="login-form" <!-- 添加一个类名可能用于自定义样式 -->
ref="ruleFormRef" <!-- 引用表单实例以便在脚本中访问 -->
:rules="rules" <!-- 绑定表单验证规则 -->
label-width="120px" <!-- 设置表单标签的宽度 -->
>
<div class="flex justify-around flex-wrap">
<div class="w-full md:w-1/2">
<!-- 一个表单元素标签为渠道名称 -->
<el-form-item label="渠道名称:" prop="name">
<!-- 一个输入框绑定表单数据中的name字段 -->
<el-input v-model="formData.name" clearable />
</el-form-item>
</div>
<!-- 一个占位元素在中等屏幕及以上时占一半宽度高度为0 -->
<div
class="md:w-1/2"
v-for="i in 1"
:key="i"
style="height: 0"
></div>
</div>
<template v-if="!drawerProps.isView" #footer>
<span class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button
class="bg-blue"
type="primary"
@click="handleSubmit"
>
提交
</el-button>
</span>
</template>
<div class="flex flex-row-reverse">
<div></div>
</div>
</el-form>
</div>
<!-- 如果不是查看模式则显示对话框的底部 -->
<template v-if="!drawerProps.isView" #footer>
<span class="dialog-footer">
<!-- 取消按钮点击后隐藏对话框 -->
<el-button @click="dialogVisible = false">取消</el-button>
<!-- 提交按钮点击后触发提交操作 -->
<el-button
class="bg-blue"
type="primary"
@click="handleSubmit"
>
提交
</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script lang="ts" setup>
// Vue
import { reactive, ref, watch } from "vue";
// Element Plus
import { ElMessage, FormInstance, FormRules } from "element-plus";
//
import { stringRule } from "@/utils/formRules";
// API
import { getSourceById } from "@/apis/source";
// null
const ruleFormRef = ref<FormInstance | null>(null);
// false
const dialogVisible = ref(false);
//
watch(dialogVisible, (value, oldValue, onCleanup) => {
//
if (!value) {
formData.value = { id: "", name: "" };
}
});
//
const rules = reactive<FormRules>({
name: [
{
required: true,
required: true, //
validator(rule, value, callback) {
//
stringRule(rule, value, callback, "来源渠道", 2, 10);
},
trigger: "blur"
trigger: "blur" //
}
]
});
//
interface DialogProps {
title: string;
isView: boolean;
rowData?: any;
api?: (params: any) => Promise<any>;
getTableList?: () => Promise<any>;
title: string; //
isView: boolean; //
rowData?: any; //
api?: (params: any) => Promise<any>; // API
getTableList?: () => Promise<any>; //
}
//
const drawerProps = ref<DialogProps>({
isView: false,
title: ""
});
// idname
const formData = ref({
id: "",
name: ""
});
//
//
const acceptParams = async (params: DialogProps) => {
//
drawerProps.value = params;
//
if (drawerProps.value.title !== "新增") {
const res: any = await getSourceById({
sourceId: params.rowData.id
})
formData.value = res.data
});
formData.value = res.data;
}
//
dialogVisible.value = true;
};
//
//
const handleSubmit = () => {
//
ruleFormRef.value!.validate(async valid => {
//
if (!valid) return;
try {
// API
const res = await drawerProps.value.api!(formData.value);
// API
if (res.code == 200) {
//
ElMessage.success({
message: res.msg
});
//
drawerProps.value.getTableList!();
//
dialogVisible.value = false;
} else {
//
ElMessage.error({
message: res.msg
});
}
} catch (error) {
//
console.log(error);
}
});
};
// acceptParams便
defineExpose({
acceptParams
});
</script>
<style scoped lang="scss">
//
.dialog-footer button:first-child {
margin-right: 10px;
}

@ -1,9 +1,19 @@
<template>
<!-- 最外层的 div 元素作为容器包裹页面内容 -->
<div>
<!-- h1 HTML 中的一级标题标签这里显示文本 "tag" -->
<h1>tag</h1>
</div>
</template>
<script setup lang="ts"></script>
<script setup lang="ts">
// 使 Vue 3 <script setup> API
//
//
</script>
<style lang="scss" scoped></style>
<style lang="scss" scoped>
// lang="scss" 使 SCSSSassy CSSSCSS CSS
// scoped
// h1
</style>

@ -1,152 +1,194 @@
<template>
<div>
<!-- 一个具有固定宽度的对话框 -->
<el-dialog
style="width: 70%"
v-model="dialogVisible"
:title="drawerProps.title"
destroy-on-close
v-model="dialogVisible" <!-- 双向绑定对话框的显示与隐藏状态 -->
:title="drawerProps.title" <!-- 动态设置对话框的标题 -->
destroy-on-close <!-- 当对话框关闭时销毁其中的内容 -->
>
<div>
<el-form
:model="formData"
class="login-form"
ref="ruleFormRef"
:rules="rules"
label-width="120px"
>
<div class="flex justify-around flex-wrap">
<div class="w-full md:w-1/2">
<el-form-item label="老人姓名:" prop="">
<el-input v-model="ElderFee.elderName" clearable disabled />
</el-form-item>
<div>
<!-- 一个表单 -->
<el-form
:model="formData" <!-- 绑定表单数据 -->
class="login-form" <!-- 添加一个类名可能用于自定义样式 -->
ref="ruleFormRef" <!-- 引用表单实例以便在脚本中访问 -->
:rules="rules" <!-- 绑定表单验证规则 -->
label-width="120px" <!-- 设置表单标签的宽度 -->
>
<div class="flex justify-around flex-wrap">
<div class="w-full md:w-1/2">
<!-- 一个表单元素标签为老人姓名 -->
<el-form-item label="老人姓名:" prop="">
<!-- 一个输入框绑定 ElderFee 对象中的 elderName 属性且为禁用状态 -->
<el-input v-model="ElderFee.elderName" clearable disabled />
</el-form-item>
</div>
<div class="w-full md:w-1/2">
<!-- 一个表单元素标签为合同有效期限 -->
<el-form-item label="合同有效期限:" prop="contractPeriod">
<!-- 一个日期选择器绑定 ElderFee 对象中的 dateRange 属性为日期范围选择器且为禁用状态 -->
<el-date-picker
class="w-full"
v-model="ElderFee.dateRange"
type="daterange"
range-separator="至"
start-placeholder="开始日期"
end-placeholder="结束日期"
unlink-panels
placeholder="请选择"
disabled
/>
</el-form-item>
</div>
<div style="width: 100%">
<!-- 一个表单元素标签为月费详情 -->
<el-form-item label="月费详情:" prop="">
<div>
<!-- 一个内部表单 -->
<el-form>
<!-- 一个表格绑定 ElderFee 对象中的 feeDetailList 属性显示月费详情 -->
<el-table :data="ElderFee.feeDetailList" border>
<!-- 表格列标签为月份 -->
<el-table-column label="月份" align="center" width="220">
<!-- 表格列的内容插槽显示月份数据且为禁用状态 -->
<template v-slot="scope">
<el-input v-model="scope.row.feeDate" disabled />
</template>
</el-table-column>
<!-- 表格列标签为护理费() -->
<el-table-column label="护理费(元)" align="center">
<!-- 表格列的内容插槽显示护理费数据且为禁用状态 -->
<template v-slot="scope">
<el-input v-model="scope.row.contractInsideNurseFee" disabled />
</template>
</el-table-column>
<!-- 表格列标签为餐饮费() -->
<el-table-column label="餐饮费(元)" align="center">
<!-- 表格列的内容插槽显示餐饮费数据且为禁用状态 -->
<template v-slot="scope">
<el-input v-model="scope.row.contractInsideDishesFee" disabled />
</template>
</el-table-column>
<!-- 表格列标签为床位费() -->
<el-table-column label="床位费(元)" align="center">
<!-- 表格列的内容插槽显示床位费数据且为禁用状态 -->
<template v-slot="scope">
<el-input v-model="scope.row.contractInsideBedFee" disabled />
</template>
</el-table-column>
<!-- 表格列标签为应缴费用() -->
<el-table-column label="应缴费用(元)" align="center">
<!-- 表格列的内容插槽显示应缴费用数据且为禁用状态 -->
<template v-slot="scope">
<el-input v-model="scope.row.payableFee" disabled />
</template>
</el-table-column>
<!-- 表格列标签为应退费用() -->
<el-table-column label="应退费用(元)" align="center">
<!-- 表格列的内容插槽显示应退费用数据且为禁用状态 -->
<template v-slot="scope">
<el-input v-model="scope.row.returnableFee" disabled />
</template>
</el-table-column>
</el-table>
</el-form>
</div>
<div class="w-full md:w-1/2">
<el-form-item label="合同有效期限:" prop="contractPeriod">
<el-date-picker
class="w-full"
v-model="ElderFee.dateRange"
type="daterange"
range-separator="至"
start-placeholder="开始日期"
end-placeholder="结束日期"
unlink-panels
placeholder="请选择"
disabled
/>
</el-form-item>
</div>
<div style="width: 100%">
<el-form-item label="月费详情:" prop="">
<div>
<el-form>
<el-table :data="ElderFee.feeDetailList" border>
<el-table-column label="月份" align="center" width="220">
<template v-slot="scope">
<el-input v-model="scope.row.feeDate" disabled />
</template>
</el-table-column>
<el-table-column label="护理费(元)" align="center">
<template v-slot="scope">
<el-input v-model="scope.row.contractInsideNurseFee" disabled />
</template>
</el-table-column>
<el-table-column label="餐饮费(元)" align="center">
<template v-slot="scope">
<el-input v-model="scope.row.contractInsideDishesFee" disabled />
</template>
</el-table-column>
<el-table-column label="床位费(元)" align="center">
<template v-slot="scope">
<el-input v-model="scope.row.contractInsideBedFee" disabled />
</template>
</el-table-column>
<el-table-column label="应缴费用(元)" align="center">
<template v-slot="scope">
<el-input v-model="scope.row.payableFee" disabled />
</template>
</el-table-column>
<el-table-column label="应退费用(元)" align="center">
<template v-slot="scope">
<el-input v-model="scope.row.returnableFee" disabled />
</template>
</el-table-column>
</el-table>
</el-form>
</div>
</el-form-item>
</div>
<div class="w-full md:w-1/2">
<el-form-item label="应缴总额:" prop="">
<el-input v-model="payAmount" disabled />
</el-form-item>
</div>
<div class="w-full md:w-1/2">
<el-form-item label="应退总额:" prop="">
<el-input v-model="returnAmount" disabled />
</el-form-item>
</div>
<div class="w-full md:w-1/2">
<el-form-item label="审核结果:" prop="auditResult">
<el-select
v-model="formData.auditResult"
placeholder="请选择"
class="w-full"
>
<el-option
clearable
:label="item.name"
:value="item.value"
v-for="item in auditResultList"
:key="item.value"
/>
</el-select>
</el-form-item>
</div>
<!-- 占位 -->
<div
class="md:w-1/2"
v-for="i in 1"
:key="i"
style="height: 0"
></div>
</div>
<div class="flex flex-row-reverse">
<div></div>
</div>
</el-form>
</el-form-item>
</div>
<div class="w-full md:w-1/2">
<!-- 一个表单元素标签为应缴总额 -->
<el-form-item label="应缴总额:" prop="">
<!-- 一个输入框绑定 payAmount 变量且为禁用状态 -->
<el-input v-model="payAmount" disabled />
</el-form-item>
</div>
<div class="w-full md:w-1/2">
<!-- 一个表单元素标签为应退总额 -->
<el-form-item label="应退总额:" prop="">
<!-- 一个输入框绑定 returnAmount 变量且为禁用状态 -->
<el-input v-model="returnAmount" disabled />
</el-form-item>
</div>
<div class="w-full md:w-1/2">
<!-- 一个表单元素标签为审核结果 -->
<el-form-item label="审核结果:" prop="auditResult">
<!-- 一个选择框绑定 formData 对象中的 auditResult 属性显示审核结果选项 -->
<el-select
v-model="formData.auditResult"
placeholder="请选择"
class="w-full"
>
<!-- 选择框的选项通过遍历 auditResultList 数组生成 -->
<el-option
clearable
:label="item.name"
:value="item.value"
v-for="item in auditResultList"
:key="item.value"
/>
</el-select>
</el-form-item>
</div>
<!-- 占位 -->
<div
class="md:w-1/2"
v-for="i in 1"
:key="i"
style="height: 0"
></div>
</div>
<div class="flex flex-row-reverse">
<div></div>
</div>
<template v-if="!drawerProps.isView" #footer>
<span class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button
class="bg-blue"
type="primary"
@click="handleSubmit"
>
提交
</el-button>
</span>
</template>
</el-form>
</div>
<!-- 如果不是查看模式则显示对话框的底部 -->
<template v-if="!drawerProps.isView" #footer>
<span class="dialog-footer">
<!-- 取消按钮点击后隐藏对话框 -->
<el-button @click="dialogVisible = false">取消</el-button>
<!-- 提交按钮点击后触发提交操作 -->
<el-button
class="bg-blue"
type="primary"
@click="handleSubmit"
>
提交
</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script lang="ts" setup>
// Vue
import { reactive, ref } from "vue";
// Element Plus
import { ElMessage, FormInstance, FormRules } from "element-plus";
// API
import { getElderFeeById, IAuditResultList } from "@/apis/retreatAudit";
// null
const ruleFormRef = ref<FormInstance | null>(null);
// false
const dialogVisible = ref(false);
//
const ElderFee = ref<any>([]);
// 0
const payAmount = ref(0);
// 退 0
const returnAmount = ref(0);
//
const auditResultList = IAuditResultList;
//
const rules = reactive<FormRules>({
auditResult: [{ required: true, message: "请选择审核结果", trigger: "blur" }]
});
//
interface DialogProps {
title: string;
isView: boolean;
@ -155,11 +197,13 @@ interface DialogProps {
getTableList?: () => Promise<any>;
}
//
const drawerProps = ref<DialogProps>({
isView: false,
title: ""
});
// applyIdelderId auditResult
const formData = ref({
applyId: "",
elderId: "",
@ -168,54 +212,74 @@ const formData = ref({
//
const acceptParams = async (params: DialogProps) => {
// ID
const res: any = await getElderFeeById({
elderId: params.rowData.elderId
});
//
ElderFee.value = res.data;
//
ElderFee.value.dateRange = [new Date(ElderFee.value.contractStartTime), new Date(ElderFee.value.contractEndTime)];
payAmount.value = 0
returnAmount.value = 0
// 退
payAmount.value = 0;
returnAmount.value = 0;
// 退
ElderFee.value.feeDetailList.forEach((feeDetail: any) => {
payAmount.value += feeDetail.payableFee;
returnAmount.value += feeDetail.returnableFee;
});
//
formData.value = params.rowData;
//
drawerProps.value = params;
//
dialogVisible.value = true;
};
//
//
const handleSubmit = () => {
//
ruleFormRef.value!.validate(async valid => {
//
if (!valid) return;
try {
// API
const res = await drawerProps.value.api!(formData.value);
// API
if (res.code == 200) {
//
ElMessage.success({
message: res.msg
});
//
drawerProps.value.getTableList!();
//
dialogVisible.value = false;
} else {
//
ElMessage.error({
message: res.msg
});
}
} catch (error) {
//
console.log(error);
}
});
};
// acceptParams 便
defineExpose({
acceptParams
});
</script>
<style scoped lang="scss">
//
.dialog-footer button:first-child {
margin-right: 10px;
}
// 使 :deep
:deep(.el-form-item__content) {
display: block;
}

@ -1,90 +1,101 @@
<template>
<!-- 定义一个具有 table-box 类名的容器 div -->
<div class="table-box">
<!-- 自定义的 ProTable 组件 -->
<ProTable
ref="proTable"
title="用户列表"
:columns="columns"
:requestApi="getTableList"
:initParam="initParam"
:dataCallback="dataCallback"
ref="proTable" <!-- 引用 ProTable 组件实例 -->
title="用户列表" <!-- 表格标题 -->
:columns="columns" <!-- 表格列配置 -->
:requestApi="getTableList" <!-- 请求表格数据的 API 函数 -->
:initParam="initParam" <!-- 初始化请求参数 -->
:dataCallback="dataCallback" <!-- 处理表格数据的回调函数 -->
>
<!-- 表格 header 按钮 -->
<!-- 表格操作 -->
<template #operation="scope">
<el-button
size="small"
link
:icon="EditPen"
@click="openDrawer('退住费用审核',scope)"
v-show="scope.row.applyFlag === '待审核'"
>
审核
</el-button>
</template>
<!-- 表格操作列的自定义插槽 -->
<template #operation="scope">
<!-- 审核按钮只有当当前行的 applyFlag '待审核'时显示 -->
<el-button
size="small"
link
:icon="EditPen" <!-- 编辑图标 -->
@click="openDrawer('退住费用审核',scope)" <!-- 点击打开审核抽屉 -->
v-show="scope.row.applyFlag === '待审核'"
>
审核
</el-button>
</template>
</ProTable>
<!-- 自定义的 auditDialog 组件 -->
<auditDialog ref="DialogRef" />
</div>
</template>
<script setup lang="ts" name="useProTable">
// Vue ref reactive
import { ref, reactive } from "vue";
// auditDialog
import auditDialog from "./auditDialog/index.vue";
//
import { ColumnProps } from "@/components/ProTable/interface";
// ProTable
import ProTable from "@/components/ProTable/index.vue";
//
import { EditPen } from "@element-plus/icons-vue";
//
import { sexList } from "@/apis/elderRecord";
// 退 API
import { auditElderFee, pageRetreatAuditByKey } from "@/apis/retreatAudit";
// ProTable 便
// ProTable
const proTable = ref();
//
const initParam = reactive({});
// dataCallback list && total && pageNum && pageSize
// hooks/useTable.ts
//
const dataCallback = (data: any) => {
return {
list: data.list,
total: data.total,
pageNum: data.pageNum,
pageSize: data.pageSize
list: data.list, //
total: data.total, //
pageNum: data.pageNum, //
pageSize: data.pageSize //
};
};
// params
// ProTable :requestApi="getUserList"
// 退 API
const getTableList = (params: any) => {
let newParams = JSON.parse(JSON.stringify(params));
return pageRetreatAuditByKey(newParams);
let newParams = JSON.parse(JSON.stringify(params)); //
return pageRetreatAuditByKey(newParams); // API
};
// drawer()
// auditDialog
const DialogRef = ref();
// title auditDialog acceptParams
const openDrawer = (title: string, rowData: any = {}) => {
const params = {
title,
rowData: { ...rowData.row },
api: auditElderFee,
getTableList: proTable.value.getTableList
rowData: { ...rowData.row }, //
api: auditElderFee, // API
getTableList: proTable.value.getTableList //
};
DialogRef.value.acceptParams(params);
DialogRef.value.acceptParams(params); // auditDialog acceptParams
};
//
//
const columns: ColumnProps<any>[] = [
{ prop: "rank", label: "序号", width: 55 },
{ prop: "elderName", label: "老人姓名", search: { el: "input" } },
{ prop: "rank", label: "序号", width: 55 }, //
{ prop: "elderName", label: "老人姓名", search: { el: "input" } }, //
{
enum: sexList,
enum: sexList, //
prop: "elderSex",
label: "老人性别",
search: { el: "select", props: { filterable: true } }
search: { el: "select", props: { filterable: true } } //
},
{ prop: "bedName", label: "床位名称" },
{ prop: "idNum", label: "身份证号", search: { el: "input" } },
{ prop: "applyFlag", label: "审核结果" },
{ prop: "operation", label: "操作", width: 150 }
{ prop: "bedName", label: "床位名称" }, //
{ prop: "idNum", label: "身份证号", search: { el: "input" } }, //
{ prop: "applyFlag", label: "审核结果" }, //
{ prop: "operation", label: "操作", width: 150 } //
];
</script>
<style lang="scss" scoped></style>
<style lang="scss" scoped>
// scoped
</style>

@ -1,7 +1,27 @@
<template>
<!-- router-view Vue Router 提供的一个内置组件它作为一个占位符
在使用 Vue Router 构建单页面应用时当定义了不同的路由路径例如 /home/about
以及每个路径对应的组件 HomeComponentAboutComponent
当用户访问某个特定路由时与之对应的组件就会被渲染到 <router-view> 所在的位置
简单来说它起到了显示当前路由所对应组件内容的作用 -->
<router-view></router-view>
</template>
<script setup lang="ts"></script>
<script setup lang="ts">
// 使 Vue 3 <script setup> .vue 使 API
// lang="ts" 使 TypeScript
//
// 使 refreactive
// onMountedonUnmounted
//
</script>
<style lang="scss" scoped></style>
<style lang="scss" scoped>
// lang="scss" 使 SCSSSassy CSS
// SCSS CSS $primary-color: #007bff;
// @mixin @include使
// scoped Vue
//
//
//
</style>

@ -1,83 +1,93 @@
<template>
<!-- 定义一个具有 table-box 类名的容器 div用于包裹整个表格相关内容 -->
<div class="table-box">
<!-- 自定义的 ProTable 组件用于展示数据表格 -->
<ProTable
ref="proTable"
title="用户列表"
:columns="columns"
:requestApi="getTableList"
:initParam="initParam"
:dataCallback="dataCallback"
ref="proTable" <!-- ProTable 组件设置引用方便在脚本中获取组件实例 -->
title="用户列表" <!-- 设置表格的标题为用户列表 -->
:columns="columns" <!-- 绑定表格的列配置columns 是一个数组定义了每列的属性 -->
:requestApi="getTableList" <!-- 绑定获取表格数据的 API 函数getTableList 用于发起数据请求 -->
:initParam="initParam" <!-- 绑定初始化请求参数initParam 是一个响应式对象可包含初始的查询参数等 -->
:dataCallback="dataCallback" <!-- 绑定数据处理回调函数dataCallback 用于处理从后端返回的数据使其符合表格的展示格式 -->
>
<!-- 表格 header 按钮 -->
<!-- 表格操作 -->
<template #operation="scope">
<el-button
size="small"
link
:icon="CreditCard"
@click="openDrawer('账户充值',scope)"
>
账户充值
</el-button>
</template>
<!-- 表格操作列的自定义插槽scope 表示当前行的上下文数据 -->
<template #operation="scope">
<!-- 账户充值按钮 -->
<el-button
size="small" <!-- 设置按钮大小为小尺寸 -->
link <!-- 设置按钮为链接样式 -->
:icon="CreditCard" <!-- 绑定信用卡图标组件显示在按钮上 -->
@click="openDrawer('账户充值',scope)" <!-- 点击按钮时调用 openDrawer 函数并传入标题账户充值和当前行数据 scope -->
>
账户充值
</el-button>
</template>
</ProTable>
<!-- 自定义的 prestoreDialog 组件用于展示账户充值的对话框 -->
<prestoreDialog ref="DialogRef" />
</div>
</template>
<script setup lang="ts" name="useProTable">
// Vue ref reactive
import { ref, reactive } from "vue";
// prestoreDialog
import prestoreDialog from "./prestoreDialog/index.vue";
// ColumnProps
import { ColumnProps } from "@/components/ProTable/interface";
// ProTable
import ProTable from "@/components/ProTable/index.vue";
// Element Plus CreditCard
import { CreditCard } from "@element-plus/icons-vue";
// API pageDepositRechargeByKey recharge
import { pageDepositRechargeByKey, recharge } from "@/apis/depositRecharge";
// ProTable 便
// ProTable undefined
const proTable = ref();
//
const initParam = reactive({});
// dataCallback list && total && pageNum && pageSize
// hooks/useTable.ts
//
const dataCallback = (data: any) => {
return {
list: data.list,
total: data.total,
pageNum: data.pageNum,
pageSize: data.pageSize
list: data.list, //
total: data.total, //
pageNum: data.pageNum, //
pageSize: data.pageSize //
};
};
// params
// ProTable :requestApi="getUserList"
// API
const getTableList = (params: any) => {
let newParams = JSON.parse(JSON.stringify(params));
return pageDepositRechargeByKey(newParams);
let newParams = JSON.parse(JSON.stringify(params)); //
return pageDepositRechargeByKey(newParams); // API
};
// drawer()
// prestoreDialog undefined
const DialogRef = ref();
// title prestoreDialog acceptParams
const openDrawer = (title: string, rowData: any = {}) => {
const params = {
title,
rowData: { ...rowData.row },
api: recharge,
getTableList: proTable.value.getTableList
title, //
rowData: { ...rowData.row }, //
api: recharge, // API
getTableList: proTable.value.getTableList //
};
DialogRef.value.acceptParams(params);
DialogRef.value.acceptParams(params); // prestoreDialog acceptParams
};
//
//
const columns: ColumnProps<any>[] = [
{ prop: "rank", label: "序号", width: 55 },
{ prop: "elderName", label: "老人姓名", search: { el: "input" } },
{ prop: "bedName", label: "床位名称" },
{ prop: "idNum", label: "身份证号", search: { el: "input" } },
{ prop: "elderPhone", label: "老人电话", search: { el: "input" } },
{ prop: "balance", label: "账户余额" },
{ prop: "operation", label: "操作", width: 150 }
{ prop: "rank", label: "序号", width: 55 }, //
{ prop: "elderName", label: "老人姓名", search: { el: "input" } }, //
{ prop: "bedName", label: "床位名称" }, //
{ prop: "idNum", label: "身份证号", search: { el: "input" } }, //
{ prop: "elderPhone", label: "老人电话", search: { el: "input" } }, //
{ prop: "balance", label: "账户余额" }, //
{ prop: "operation", label: "操作", width: 150 } //
];
</script>
<style lang="scss" scoped></style>
<style lang="scss" scoped>
// scoped
</style>

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save