pull/1/head
lxf 3 months ago
parent 4397a372b4
commit f642b7c8cd

8
.idea/.gitignore vendored

@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
</profile>
</component>

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/Nursing-home-management-system.iml" filepath="$PROJECT_DIR$/.idea/Nursing-home-management-system.iml" />
</modules>
</component>
</project>

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="PrettierConfiguration">
<option name="myConfigurationMode" value="AUTOMATIC" />
</component>
</project>

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

@ -0,0 +1,4 @@
> 1%
last 2 versions
not dead
not ie 11

@ -0,0 +1,31 @@
module.exports = {
// 可选类型
types: [
{ value: 'feat', name: 'feat: 新功能' },
{ value: 'fix', name: 'fix: 修复' },
{ value: 'docs', name: 'docs: 文档变更' },
{ value: 'style', name: 'style: 代码格式(不影响代码运行的变动)' },
{
value: 'refactor',
name: 'refactor: 重构(既不是增加feature也不是修复bug)'
},
{ value: 'perf', name: 'perf: 性能优化' },
{ value: 'test', name: 'test: 增加测试' },
{ value: 'chore', name: 'chore: 构建过程或辅助工具的变动' },
{ value: 'revert', name: 'revert: 回退' },
{ value: 'build', name: 'build: 打包' }
],
// 消息步骤
messages: {
type: '请选择提交类型:',
customScope: '请输入修改范围(可选):',
subject: '请简要描述提交(必填):',
body: '请输入详细描述(可选):',
footer: '请输入要关闭的issue(可选):',
confirmCommit: '确认使用以上信息提交?(y/n/e/h)'
},
// 跳过问题
skipQuestions: ['body', 'footer'],
// subject文字长度默认是72
subjectLimit: 72
}

@ -0,0 +1,15 @@
# http://editorconfig.org
root = true
[*] # 表示所有文件适用
charset = utf-8 # 设置文件字符集为 utf-8
indent_style = space # 缩进风格tab | space
indent_size = 2 # 缩进大小
end_of_line = lf # 控制换行类型(lf | cr | crlf)
trim_trailing_whitespace = true # 去除行首的任意空白字符
insert_final_newline = true # 始终在文件末尾插入一个新行
[*.md] # 表示仅 md 文件适用以下规则
max_line_length = off
trim_trailing_whitespace = false

@ -0,0 +1,28 @@
module.exports = {
root: true,
env: {
node: true,
/** 解决defineProps没有导入的警告 */
"vue/setup-compiler-macros": true
},
extends: [
"plugin:vue/vue3-essential",
"eslint:recommended",
"@vue/typescript/recommended",
"plugin:prettier/recommended"
],
parserOptions: {
ecmaVersion: 2020
},
rules: {
"linebreak-style": [0, "error", "windows"],
"no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
"no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off",
indent: 0,
"space-before-function-paren": 0,
"vue/multi-word-component-names": "off",
"@typescript-eslint/no-explicit-any": "off",
"vue/no-mutating-props": "off", // 不允许组件 prop的改变
"prettier/prettier": "off"
}
};

23
client/.gitignore vendored

@ -0,0 +1,23 @@
.DS_Store
node_modules
/dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

@ -0,0 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npx --no-install commitlint --edit

@ -0,0 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npx lint-staged

@ -0,0 +1,9 @@
/dist/*
.local
.output.js
/node_modules/**
**/*.svg
**/*.sh
/public/*

@ -0,0 +1,53 @@
// {
// "useTabs": false,
// "tabWidth": 2,
// "printWidth": 80,
// "singleQuote": true,
// "trailingComma": "none",
// "semi": false,
// }
module.exports = {
// 超过最大值换行
printWidth: 130,
// 缩进字节数
tabWidth: 2,
// 使用制表符而不是空格缩进行
useTabs: true,
// 结尾不用分号(true有false没有)
semi: true,
// 使用单引号(true单双引号false双引号)
singleQuote: false,
// 更改引用对象属性的时间 可选值 "<as-needed|consistent|preserve>"
quoteProps: "as-needed",
// 在对象,数组括号与文字之间加空格 "{ foo: bar }"
bracketSpacing: true,
// 多行时尽可能打印尾随逗号。(例如,单行数组永远不会出现逗号结尾。) 可选值"<none|es5|all>"默认none
trailingComma: "none",
// 在JSX中使用单引号而不是双引号
jsxSingleQuote: false,
// (x) => {} 箭头函数参数只有一个时是否要有小括号。avoid省略括号 ,always不省略括号
arrowParens: "avoid",
// 如果文件顶部已经有一个 doclock这个选项将新建一行注释并打上@format标记。
insertPragma: false,
// 指定要使用的解析器,不需要写文件开头的 @prettier
requirePragma: false,
// 默认值。因为使用了一些折行敏感型的渲染器如GitHub comment而按照markdown文本样式进行折行
proseWrap: "preserve",
// 在html中空格是否是敏感的 "css" - 遵守 CSS 显示属性的默认值, "strict" - 空格被认为是敏感的 "ignore" - 空格被认为是不敏感的
htmlWhitespaceSensitivity: "css",
// 换行符使用 lf 结尾是 可选值 "<auto|lf|crlf|cr>"
endOfLine: "auto",
// 这两个选项可用于格式化以给定字符偏移量(分别包括和不包括)开始和结束的代码
rangeStart: 0,
rangeEnd: Infinity,
// Vue文件脚本和样式标签缩进
vueIndentScriptAndStyle: false,
"useTabs": false,
"tabWidth": 2,
"printWidth": 80,
"singleQuote": true,
"trailingComma": "none",
"semi": false,
};

@ -0,0 +1,29 @@
# geracomium-admin-web
## Project setup
```
yarn install
```
### Compiles and hot-reloads for development
```
yarn serve
```
### Compiles and minifies for production
```
yarn build
```
### Lints and fixes files
```
yarn lint
```
### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).

@ -0,0 +1,5 @@
// Generated by 'unplugin-auto-import'
export {}
declare global {
}

@ -0,0 +1,3 @@
module.exports = {
presets: ['@vue/cli-plugin-babel/preset']
}

@ -0,0 +1,26 @@
module.exports = {
// 继承的规则
extends: ['@commitlint/config-conventional'],
// 定义规则类型
rules: {
// type 类型定义,表示 git 提交的 type 必须在以下类型范围内
'type-enum': [
2,
'always',
[
'feat', // 新功能 feature
'fix', // 修复 bug
'docs', // 文档注释
'style', // 代码格式(不影响代码运行的变动)
'refactor', // 重构(既不增加新功能也不是修复bug)
'perf', // 性能优化
'test', // 增加测试
'chore', // 构建过程或辅助工具的变动
'revert', // 回退
'build' // 打包
]
],
// subject 大小写不做校验
'subject-case': [0]
}
}

@ -0,0 +1,67 @@
// generated by unplugin-vue-components
// We suggest you to commit this file into source control
// Read more: https://github.com/vuejs/core/pull/3399
import '@vue/runtime-core'
export {}
declare module '@vue/runtime-core' {
export interface GlobalComponents {
ColSetting: typeof import('./src/components/ProTable/components/ColSetting.vue')['default']
DynamicAdditionComponent: typeof import('./src/components/wen-test/DynamicAdditionComponent.vue')['default']
ElAside: typeof import('element-plus/es')['ElAside']
ElBreadcrumb: typeof import('element-plus/es')['ElBreadcrumb']
ElBreadcrumbItem: typeof import('element-plus/es')['ElBreadcrumbItem']
ElButton: typeof import('element-plus/es')['ElButton']
ElCol: typeof import('element-plus/es')['ElCol']
ElContainer: typeof import('element-plus/es')['ElContainer']
ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
ElderListDialog: typeof import('./src/components/elderListDialog/index.vue')['default']
ElDialog: typeof import('element-plus/es')['ElDialog']
ElDrawer: typeof import('element-plus/es')['ElDrawer']
ElDropdown: typeof import('element-plus/es')['ElDropdown']
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
ElEmpty: typeof import('element-plus/es')['ElEmpty']
ElForm: typeof import('element-plus/es')['ElForm']
ElFormItem: typeof import('element-plus/es')['ElFormItem']
ElHeader: typeof import('element-plus/es')['ElHeader']
ElIcon: typeof import('element-plus/es')['ElIcon']
ElInput: typeof import('element-plus/es')['ElInput']
ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
ElMain: typeof import('element-plus/es')['ElMain']
ElMenu: typeof import('element-plus/es')['ElMenu']
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
ElOption: typeof import('element-plus/es')['ElOption']
ElPagination: typeof import('element-plus/es')['ElPagination']
ElPopconfirm: typeof import('element-plus/es')['ElPopconfirm']
ElRadio: typeof import('element-plus/es')['ElRadio']
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
ElRow: typeof import('element-plus/es')['ElRow']
ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
ElSelect: typeof import('element-plus/es')['ElSelect']
ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
ElSwitch: typeof import('element-plus/es')['ElSwitch']
ElTable: typeof import('element-plus/es')['ElTable']
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
ElTag: typeof import('element-plus/es')['ElTag']
ElTooltip: typeof import('element-plus/es')['ElTooltip']
ElTree: typeof import('element-plus/es')['ElTree']
ElUpload: typeof import('element-plus/es')['ElUpload']
Grid: typeof import('./src/components/Grid/index.vue')['default']
GridItem: typeof import('./src/components/Grid/components/GridItem.vue')['default']
IconPark: typeof import('./src/components/IconPark/index.vue')['default']
Image: typeof import('./src/components/upload/image/index.vue')['default']
MyCard: typeof import('./src/components/my-card/my-card.vue')['default']
Pagination: typeof import('./src/components/ProTable/components/Pagination.vue')['default']
ProTable: typeof import('./src/components/ProTable/index.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
SearchForm: typeof import('./src/components/SearchForm/index.vue')['default']
SearchFormItem: typeof import('./src/components/SearchForm/components/SearchFormItem.vue')['default']
Src: typeof import('./src/components/ReImageVerify/src/index.vue')['default']
SvgIcon: typeof import('./src/components/SvgIcon/index.vue')['default']
TableColumn: typeof import('./src/components/ProTable/components/TableColumn.vue')['default']
TreeDialog: typeof import('./src/components/treeDialog/index.vue')['default']
}
}

@ -0,0 +1,2 @@
VUE_APP_BASE_URL=https://coderwhy.org/dev
VUE_APP_BASE_NAME=coderwhy

@ -0,0 +1,2 @@
VUE_APP_BASE_URL=https://coderwhy.org/prod
VUE_APP_BASE_NAME=kobe

@ -0,0 +1,2 @@
VUE_APP_BASE_URL=https://coderwhy.org/test
VUE_APP_BASE_NAME=james

27941
client/package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -0,0 +1,72 @@
{
"name": "geracomium-admin-web",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint",
"prettier": "prettier --write .",
"prepare": "husky install"
},
"dependencies": {
"@commitlint/cli": "^17.3.0",
"@commitlint/config-conventional": "^17.3.0",
"@icon-park/vue-next": "^1.4.2",
"@pureadmin/utils": "^1.8.5",
"@vueuse/motion": "^2.0.0-beta.12",
"axios": "^1.2.2",
"core-js": "^3.8.3",
"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",
"svg-sprite-loader": "^6.0.11",
"unplugin-auto-import": "^0.12.1",
"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",
"@vue/cli-plugin-eslint": "~5.0.0",
"@vue/cli-plugin-router": "~5.0.0",
"@vue/cli-plugin-typescript": "~5.0.0",
"@vue/cli-plugin-vuex": "~5.0.0",
"@vue/cli-service": "~5.0.0",
"@vue/eslint-config-typescript": "^9.1.0",
"autoprefixer": "^9.8.8",
"eslint": "^7.32.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-vue": "^8.0.3",
"postcss": "^8.4.21",
"prettier": "^2.4.1",
"sass": "^1.32.7",
"sass-loader": "^12.0.0",
"tailwindcss": "^3.2.7",
"typescript": "~4.5.5",
"unplugin-vue-macros": "^1.7.3"
},
"config": {
"commitizen": {
"path": "node_modules/cz-customizable"
}
},
"lint-staged": {
"src/**/*.{js,vue}": [
"eslint --fix",
"git add"
]
}
}

@ -0,0 +1,7 @@
// postcss.config.js
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

@ -0,0 +1,21 @@
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
<link rel="icon" href="<%= BASE_URL %>logo.ico" />
<title>敬老院管理系统</title>
</head>
<body>
<noscript>
<strong
>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work
properly without JavaScript enabled. Please enable it to
continue.</strong
>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 261 KiB

@ -0,0 +1,18 @@
<template>
<el-config-provider :locale="locale">
<router-view />
</el-config-provider>
</template>
<script setup lang="ts">
import { ElConfigProvider } from 'element-plus'
import zhCn from 'element-plus/lib/locale/lang/zh-cn'
const locale = zhCn
</script>
<style lang="scss" scoped>
.app {
width: 100%;
height: 100%;
}
</style>

@ -0,0 +1,80 @@
import { http } from "@/utils";
import { IPageSearchElderByKey } from "@/apis/bookManage";
interface IPageAccidentByKey {
elderName: string;
staffName: string;
}
interface IAddAccident {
id: number;
elderId: number;
staffId: number;
occurDate: string;
description: string;
picture: string;
}
interface IGetAccidentById {
accidentId: string;
}
interface IEditAccident {
id: number;
name: string;
phone: string;
relation: string;
accidentDateStr: string;
accidentNum: number;
}
// 分页查询事故登记
export async function pageAccidentByKey(data: IPageAccidentByKey) {
return http.get("/api/accident/pageAccidentByKey", {
params: {
...data
}
});
}
// 分页搜索老人
export async function pageSearchElderByKey(data: IPageSearchElderByKey) {
return http.get("/api/accident/pageSearchElderByKey", {
params: {
...data
}
});
}
// 获取护工列表
export async function listAccidentStaff() {
return http.get("/api/accident/listAccidentStaff");
}
// 新增事故登记
export function addAccident(data: IAddAccident) {
return http.post("/api/accident/addAccident", data);
}
// 根据编号获取事故登记
export async function getAccidentById(data: IGetAccidentById) {
return http.get("/api/accident/getAccidentById", {
params: {
...data
}
});
}
// 编辑事故登记
export function editAccident(data: IEditAccident) {
return http.put("/api/accident/editAccident", data);
}
// 删除事故登记
export async function deleteAccident(data: IGetAccidentById) {
return http.delete("/api/accident/deleteAccident", {
params: {
...data
}
});
}

@ -0,0 +1,30 @@
import { http } from "@/utils";
interface IListRoomByKey {
buildingId: string;
floorId: string;
elderName: string;
}
// 获取楼栋列表
export async function listBuilding() {
return http.get("/api/bedPanorama/listBuilding");
}
// 获取楼层列表
export function listFloorByBuildingId(buildingId: string) {
return http.get("/api/bedPanorama/listFloorByBuildingId", {
params: {
buildingId
}
});
}
// 获取房间列表
export function listRoomByKey(data: IListRoomByKey) {
return http.get("/api/bedPanorama/listRoomByKey", {
params: {
...data
}
});
}

@ -0,0 +1,86 @@
import { http } from '@/utils'
interface ISearchFormReserveByKey {
pageNum: number
pageSize: number
elderName?: string
payerPhone?: string
}
export interface IPageSearchElderByKey {
pageNum: number
pageSize: number
elderName?: string
elderPhone?: string
}
interface IAddReserve {
bedId: string
deposit: string
dueDate: string
elderAddress: string
elderAge: string
elderName: string
elderPhone: string
elderSex: string
idNum: string
payerName: string
payerPhone: string
staffId: string
}
interface IGetReserveById {
elderId: string
reserveId: string
}
interface IRefund {
reserveId: string
}
// 分页查询预定
export async function pageReserveByKey(data: ISearchFormReserveByKey) {
return http.get('/api/reserve/pageReserveByKey', {
params: {
...data
}
})
}
// 分页搜索老人
export function pageSearchElderByKey(data: IPageSearchElderByKey) {
return http.get('/api/reserve/pageSearchElderByKey', {
params: {
...data
}
})
}
// 获取营销人员
export function listReserveStaff() {
return http.get('/api/reserve/listReserveStaff')
}
// 获取楼栋树
export function getBuildTree() {
return http.get('/api/reserve/getBuildTree')
}
// 新增预定
export function addReserve(data: IAddReserve) {
return http.post('/api/reserve/addReserve', data)
}
// 根据预定编号和老人编号获取预定信息
export function getReserveById(data: IGetReserveById) {
return http.get('/api/reserve/getReserveByReserveIdAndElderId', {
params: {
...data
}
})
}
// 退款
export function refund(data: IRefund) {
return http.put('/api/reserve/refund',data)
}

@ -0,0 +1,180 @@
import { http } from "@/utils";
interface IPageBedByKey {
buildId: string;
floorId: string;
roomId: string;
bedFlag: string;
}
interface IAddBuilding {
id: string;
name: string;
floorNum: string;
}
interface IGetBuildingById {
buildingId: string;
}
interface IAddFloor {
id: string;
name: string;
roomNum: string;
buildingId: string;
floorLimit: string;
}
interface IGetFloorById {
floorId: string;
}
interface IAddRoom {
id: string;
name: string;
typeId: string;
bedNum: string;
floorId: string;
roomLimit: string;
}
interface IGetRoomById {
roomId: string;
}
interface IDeleteNode {
id: string;
mark: string;
}
interface IAddBed {
id: string;
name: string;
roomId: string;
bedLimit: string;
}
interface IGetBedById {
bedId: string;
}
// 床位状态
export const IBedFlagList = [
{ label: "空闲", value: "空闲" },
{ label: "预定", value: "预定" },
{ label: "入住", value: "入住" },
{ label: "退住审核", value: "退住审核" }
];
// 获取楼栋-楼层-房间树
export async function getNoBedTree() {
return http.get("/api/build/getNoBedTree");
}
// 分页查询床位
export async function pageBedByKey(data: IPageBedByKey) {
return http.get("/api/build/pageBedByKey", {
params: {
...data
}
});
}
// 新增楼栋
export function addBuilding(data: IAddBuilding) {
return http.post("/api/build/addBuilding", data);
}
// 根据编号获取楼栋
export async function getBuildingById(data: IGetBuildingById) {
return http.get("/api/build/getBuildingById", {
params: {
...data
}
});
}
// 编辑楼栋
export function editBuilding(data: IAddBuilding) {
return http.put("/api/build/editBuilding", data);
}
// 新增楼层
export function addFloor(data: IAddFloor) {
return http.post("/api/build/addFloor", data);
}
// 根据编号获取楼层
export async function getFloorById(data: IGetFloorById) {
return http.get("/api/build/getFloorById", {
params: {
...data
}
});
}
// 编辑楼层
export function editFloor(data: IAddFloor) {
return http.put("/api/build/editFloor", data);
}
// 获取房间类型列表
export async function listRoomType() {
return http.get("/api/build/listRoomType");
}
// 新增房间
export function addRoom(data: IAddRoom) {
return http.post("/api/build/addRoom", data);
}
// 根据编号获取房间
export async function getRoomById(data: IGetRoomById) {
return http.get("/api/build/getRoomById", {
params: {
...data
}
});
}
// 编辑房间
export function editRoom(data: IAddRoom) {
return http.put("/api/build/editRoom", data);
}
// 删除节点
export async function deleteNode(data: IDeleteNode) {
return http.delete("/api/build/deleteNode", {
params: {
...data
}
});
}
// 新增床位
export function addBed(data: IAddBed) {
return http.post("/api/build/addBed", data);
}
// 根据编号获取床位
export async function getBedById(data: IGetBedById) {
return http.get("/api/build/getBedById", {
params: {
...data
}
});
}
// 编辑床位
export function editBed(data: IAddBed) {
return http.put("/api/build/editBed", data);
}
// 删除床位
export async function deleteBed(data: IGetBedById) {
return http.delete("/api/build/deleteBed", {
params: {
...data
}
});
}

@ -0,0 +1,71 @@
import { http } from "@/utils";
import { IPageDishesByKey } from "@/apis/dishes";
interface IPageCateringSetByKey {
name:string;
setName: string;
}
interface IAddCateringSet {
id: number;
name: string;
monthPrice: number;
dishesIdList: any ;
}
interface IGetCateringSetById {
setId: string
}
// 分页查询餐饮套餐
export async function pageCateringSetByKey(data: IPageCateringSetByKey) {
// 因为后台返回的字段与前端表单数据的prop不一样但是组件封装是需要一样的所以请求前增加一些这两个字段
Reflect.has(data, 'name') ? (data.setName = data.name) : ''
return http.get("/api/cateringSet/pageCateringSetByKey", {
params: {
...data
}
});
}
// 获取菜品分类
export function listDishesType() {
return http.post("/api/cateringSet/listDishesType");
}
// 分页查询菜品
export async function pageDishesByKey(data: IPageDishesByKey) {
return http.get("/api/cateringSet/pageDishesByKey", {
params: {
...data
}
});
}
// 新增餐饮套餐
export function addCateringSet(data: IAddCateringSet) {
return http.post("/api/cateringSet/addCateringSet", data);
}
// 根据编号查询餐饮套餐
export async function getCateringSetById(data: IGetCateringSetById) {
return http.get("/api/cateringSet/getCateringSetById", {
params: {
...data
}
});
}
// 编辑餐饮套餐
export function editCateringSet(data: IAddCateringSet) {
return http.put("/api/cateringSet/editCateringSet", data);
}
// 删除餐饮套餐
export async function deleteCateringSet(data: IGetCateringSetById) {
return http.delete("/api/cateringSet/deleteCateringSet", {
params: {
...data
}
});
}

@ -0,0 +1,131 @@
import { http } from "@/utils";
import { IPageSearchElderByKey } from "@/apis/bookManage";
interface IPageCheckContractByKey {
name: string;
sex: string;
idNum: string;
}
interface IAddCheckContract {
id: string;
nursingGradeId: string;
cateringSetId: string;
bedId: string;
name: string;
idNum: string;
age: string;
sex: string;
phone: string;
address: string;
staffId: string;
signDate: string;
startDate: string;
endDate: string;
operateEmergencyContactQueryList: IEmergencyContact[];
}
interface IEmergencyContact {
name: string;
phone: string;
email: string;
relation: string;
receiveFlag: string;
}
interface IGetCheckContractById {
elderId: string
}
// 分页查询入住签约
export async function pageCheckContractByKey(data: IPageCheckContractByKey) {
return http.get("/api/checkContract/pageCheckContractByKey", {
params: {
...data
}
});
}
// 分页搜索老人
export async function pageSearchElderByKey(data: IPageSearchElderByKey) {
return http.get("/api/checkContract/pageSearchElderByKey", {
params: {
...data
}
});
}
// 获取护理等级列表
export async function listNurseGrade() {
return http.get("/api/checkContract/listNurseGrade");
}
// 根据编号查询护理等级
export async function getNurseGradeById(nurseGradeId: string) {
return http.get("/api/checkContract/getNurseGradeById", {
params: {
nurseGradeId
}
});
}
// 获取餐饮套餐列表
export async function listCateringSet() {
return http.get("/api/checkContract/listCateringSet");
}
// 根据编号查询餐饮套餐
export async function getCateringSetById(cateringSetId: string) {
return http.get("/api/checkContract/getCateringSetById", {
params: {
cateringSetId
}
});
}
// 获取楼栋树
export async function getBuildTree() {
return http.get("/api/checkContract/getBuildTree");
}
// 根据编号查询床位
export async function getBedById(bedId: string) {
return http.get("/api/checkContract/getBedById", {
params: {
bedId
}
});
}
// 获取营销人员
export async function listReserveStaff() {
return http.get("/api/checkContract/listReserveStaff");
}
// 新增入住签约
export function addCheckContract(data: IAddCheckContract) {
return http.post("/api/checkContract/addCheckContract", data);
}
// 根据老人编号查询入住签约
export async function getCheckContractById(data: IGetCheckContractById) {
return http.get("/api/checkContract/getCheckContractById", {
params: {
...data
}
});
}
// 编辑入住签约
export function editCheckContract(data: IAddCheckContract) {
return http.put("/api/checkContract/editCheckContract", data);
}
// 删除入住签约
export async function deleteCheckContract(data: IGetCheckContractById) {
return http.delete("/api/checkContract/deleteCheckContract", {
params: {
...data
}
});
}

@ -0,0 +1,16 @@
import { http } from "@/utils";
interface IPageConsumeByKey {
elderName: string;
startTime: string;
endTime: string;
}
// 分页查询消费记录
export async function pageConsumeByKey(data: IPageConsumeByKey) {
return http.get("/api/consume/pageConsumeByKey", {
params: {
...data
}
});
}

@ -0,0 +1,41 @@
import { http } from "@/utils";
import { IPageSearchElderByKey } from "@/apis/bookManage";
interface IPageDepositRechargeByKey {
idNum:string;
name:string;
elderName:string;
phone: string;
elderPhone: string;
}
interface IRecharge {
elderId: string;
amount: string;
}
// 分页查询预存充值
export async function pageDepositRechargeByKey(data: IPageDepositRechargeByKey) {
// 因为后台返回的字段与前端表单数据的prop不一样但是组件封装是需要一样的所以请求前增加一些这两个字段
Reflect.has(data, 'elderName') ? (data.name = data.elderName) : ''
Reflect.has(data, 'elderPhone') ? (data.phone = data.elderPhone) : ''
return http.get("/api/depositRecharge/pageDepositRechargeByKey", {
params: {
...data
}
});
}
// 分页搜索老人
export function pageSearchElderByKey(data: IPageSearchElderByKey) {
return http.get('/api/depositRecharge/pageSearchElderByKey', {
params: {
...data
}
})
}
// 入住老人账户充值
export function recharge(data: IRecharge) {
return http.put("/api/depositRecharge/recharge", data);
}

@ -0,0 +1,104 @@
import { http } from "@/utils";
interface IListDishesType {
dishesTypeName: string;
}
export interface IPageDishesByKey {
dishesName: string;
typeId: number;
}
interface IAddDishesType {
id: number;
name: string;
}
interface IGetDishesTypeById {
dishesTypeId: string;
}
interface IAddDishes {
id: number;
name: string;
price: string;
typeId: number;
}
export interface IGetDishesById {
dishesId: string;
}
// 获取菜品分类列表
export async function listDishesType(data: IListDishesType) {
return http.get("/api/dishes/listDishesType", {
params: {
...data
}
});
}
// 分页查询菜品
export async function pageDishesByKey(data: IPageDishesByKey) {
return http.get("/api/dishes/pageDishesByKey", {
params: {
...data
}
});
}
// 新增菜品分类
export function addDishesType(data: IAddDishesType) {
return http.post("/api/dishes/addDishesType", data);
}
// 根据编号获取菜品分类
export async function getDishesTypeById(data: IGetDishesTypeById) {
return http.get("/api/dishes/getDishesTypeById", {
params: {
...data
}
});
}
// 编辑菜品分类
export function editDishesType(data: IAddDishesType) {
return http.put("/api/dishes/editDishesType", data);
}
// 删除菜品分类
export async function deleteDishesType(data: IGetDishesTypeById) {
return http.delete("/api/dishes/deleteDishesType", {
params: {
...data
}
});
}
// 新增菜品
export function addDishes(data: IAddDishes) {
return http.post("/api/dishes/addDishes", data);
}
// 根据编号获取菜品
export async function getDishesById(data: IGetDishesById) {
return http.get("/api/dishes/getDishesById", {
params: {
...data
}
});
}
// 编辑菜品
export function editDishes(data: IAddDishes) {
return http.put("/api/dishes/editDishes", data);
}
// 删除菜品
export async function deleteDishes(data: IGetDishesById) {
return http.delete("/api/dishes/deleteDishes", {
params: {
...data
}
});
}

@ -0,0 +1,78 @@
import { http } from "@/utils";
interface IPageElderByKey {
name: string;
elderName: string;
idNum: string;
sex: string;
elderSex: string;
}
interface IGetElderById {
elderId: string;
}
interface IEditElder {
id: number;
name: string;
idNum: string;
age: number;
sex: string;
phone: string;
address: string;
}
// 性别
export const sexList = [
{ label: "男", value: "男" },
{ label: "女", value: "女" }
];
// 导出excel
export function exportExcel() {
return http.get("/api/elderRecord/exportExcel");
}
// 分页查询员工
export async function pageElderByKey(data: IPageElderByKey) {
// 因为后台返回的字段与前端表单数据的prop不一样但是组件封装是需要一样的所以请求前增加一些这两个字段
Reflect.has(data, "sex") ? (data.elderSex = data.sex) : "";
Reflect.has(data, "name") ? (data.elderName = data.name) : "";
return http.get("/api/elderRecord/pageElderByKey", {
params: {
...data
}
});
}
// 根据编号获取长者信息
export async function getElderById(data: IGetElderById) {
return http.get("/api/elderRecord/getElderById", {
params: {
...data
}
});
}
// 根据编号获取长者档案
export async function getElderRecordById(data: IGetElderById) {
return http.get("/api/elderRecord/getElderRecordById", {
params: {
...data
}
});
}
// 编辑长者
export function editElder(data: IEditElder) {
return http.put("/api/elderRecord/editElder", data);
}
// 删除长者
export async function deleteElder(data: IGetElderById) {
return http.delete("/api/elderRecord/deleteElder", {
params: {
...data
}
});
}

@ -0,0 +1,25 @@
import { http } from '@/utils'
// 可售床位
export async function getAvailableBed() {
return http.get('/api/home/availableBed')
}
// 业务趋势
export async function getBusinessTrend() {
return http.get('/api/home/businessTrend')
}
// 客户来源渠道
export async function getClientSource() {
return http.get('/api/home/clientSource')
}
// 本月业绩排行
export async function getMonthPerformanceRank() {
return http.get('/api/home/monthPerformanceRank')
}
// 今日概览
export async function getTodayOverview() {
return http.get('/api/home/todayOverview')
}
// 今日销售跟进
export async function getTodaySaleFollow() {
return http.get('/api/home/todaySaleFollow')
}

@ -0,0 +1,16 @@
// 动态路由的假数据 可删除
import { http } from '@/utils'
// 问题:何时发起请求? 在动态设置路由的时候data => 树形结构 => 路由列表)
function getUserRouteList(uid: number) {
return http
.post('/api/user_router_list', { uid })
.then((data) => data)
.catch((err) => {
throw err
})
}
export { getUserRouteList }

@ -0,0 +1,82 @@
import { http } from "@/utils";
import { IPageServiceByKey } from "@/apis/service";
interface IPageNurseGradeByKey {
name:string;
gradeName: string;
type:string;
nurseType: string;
}
interface IAddNurseGrade {
id: number;
name: string;
type: string;
monthPrice: number;
serviceIdList: any ;
}
interface IGetNurseGradeById {
nurseGradeId: string
}
// 护理类型
export const INurseTypeList = [
{ label: "自理", value: "自理" },
{ label: "介护", value: "介护" },
{ label: "全护", value: "全护" }
];
// 分页查询护理等级
export async function pageNurseGradeByKey(data: IPageNurseGradeByKey) {
// 因为后台返回的字段与前端表单数据的prop不一样但是组件封装是需要一样的所以请求前增加一些这两个字段
Reflect.has(data, 'name') ? (data.gradeName = data.name) : ''
Reflect.has(data, 'type') ? (data.nurseType = data.type) : ''
return http.get("/api/nurseGrade/pageNurseGradeByKey", {
params: {
...data
}
});
}
// 获取服务类型
export function listServiceType() {
return http.post("/api/nurseGrade/listServiceType");
}
// 分页查询服务
export async function pageServiceByKey(data: IPageServiceByKey) {
return http.get("/api/nurseGrade/pageServiceByKey", {
params: {
...data
}
});
}
// 新增护理等级
export function addNurseGrade(data: IAddNurseGrade) {
return http.post("/api/nurseGrade/addNurseGrade", data);
}
// 根据编号查询护理等级
export async function getNurseGradeById(data: IGetNurseGradeById) {
return http.get("/api/nurseGrade/getNurseGradeById", {
params: {
...data
}
});
}
// 编辑护理等级
export function editNurseGrade(data: IAddNurseGrade) {
return http.put("/api/nurseGrade/editNurseGrade", data);
}
// 删除护理等级
export async function deleteNurseGrade(data: IGetNurseGradeById) {
return http.delete("/api/nurseGrade/deleteNurseGrade", {
params: {
...data
}
});
}

@ -0,0 +1,75 @@
import { http } from "@/utils";
import { IPageSearchElderByKey } from "@/apis/bookManage";
import { IGetServiceById } from "@/apis/service";
interface IPageNurseReserveByKey {
bedName: string;
elderName:string;
serviceName: string;
}
interface IAddNurseReserve {
elderId: number;
serviceName: string;
needDate: number;
servicePrice: number;
chargeMethod: string;
frequency: number;
payAmount: number;
}
interface IExecuteNurseReserve {
id: number;
nurseDate: string;
staffId: string;
}
// 分页查询护理预定
export async function pageNurseReserveByKey(data: IPageNurseReserveByKey) {
// 因为后台返回的字段与前端表单数据的prop不一样但是组件封装是需要一样的所以请求前增加一些这两个字段
// Reflect.has(data, 'name') ? (data.gradeName = data.name) : ''
// Reflect.has(data, 'type') ? (data.nurseType = data.type) : ''
return http.get("/api/nurseReserve/pageNurseReserveByKey", {
params: {
...data
}
});
}
// 分页搜索老人
export function pageSearchElderByKey(data: IPageSearchElderByKey) {
return http.get('/api/nurseReserve/pageSearchElderByKey', {
params: {
...data
}
})
}
// 获取服务项目
export async function listService() {
return http.get("/api/nurseReserve/listService");
}
// 新增护理预定
export function addNurseReserve(data: IAddNurseReserve) {
return http.post("/api/nurseReserve/addNurseReserve", data);
}
// 根据编号查询护理预定
export async function getServiceById(data: IGetServiceById) {
return http.get("/api/nurseReserve/getServiceById", {
params: {
...data
}
});
}
// 护理人员
export async function listNurseStaff() {
return http.get("/api/nurseReserve/listNurseStaff");
}
// 执行护理预定
export function executeNurseReserve(data: IExecuteNurseReserve) {
return http.put("/api/nurseReserve/executeNurseReserve", data);
}

@ -0,0 +1,85 @@
import { http } from "@/utils";
import { IPageSearchElderByKey } from "@/apis/bookManage";
import { IPageDishesByKey } from "@/apis/dishes";
interface IPageOrderByKey {
elderName:string;
elderPhone: string;
}
interface IAddOrder {
elderId: string;
dineType: string;
dineDate: string;
orderDishesList: any;
}
interface IGetOrderById {
dishesId: string;
}
interface ISendOrder {
id: string;
deliverDishesDate: string;
staffId: string;
}
// 就餐方式
export const IDineTypeList = [
{ label: "送餐", value: "送餐" },
{ label: "堂食", value: "堂食" }
];
// 分页查询点餐
export async function pageOrderByKey(data: IPageOrderByKey) {
// 因为后台返回的字段与前端表单数据的prop不一样但是组件封装是需要一样的所以请求前增加一些这两个字段
// Reflect.has(data, 'name') ? (data.gradeName = data.name) : ''
// Reflect.has(data, 'type') ? (data.nurseType = data.type) : ''
return http.get("/api/order/pageOrderByKey", {
params: {
...data
}
});
}
// 分页搜索老人
export function pageSearchElderByKey(data: IPageSearchElderByKey) {
return http.get('/api/order/pageSearchElderByKey', {
params: {
...data
}
})
}
// 分页查询菜品
export async function pageDishesByKey(data: IPageDishesByKey) {
return http.get("/api/order/pageDishesByKey",{
params:{
...data
}
});
}
// 新增点餐
export function addOrder(data: IAddOrder) {
return http.post("/api/order/addOrder", data);
}
// 根据编号查询点餐
export async function getOrderById(data: IGetOrderById) {
return http.get("/api/order/getOrderById", {
params: {
...data
}
});
}
// 护理人员
export async function listNurseStaff() {
return http.get("/api/order/listNurseStaff");
}
// 送餐
export function sendOrder(data: ISendOrder) {
return http.put("/api/order/sendOrder", data);
}

@ -0,0 +1,107 @@
import { http } from "@/utils";
import { IPageSearchElderByKey } from "@/apis/bookManage";
interface IPageOutwardByKey {
elderName: string;
chaperoneType: string;
startTime: string;
endTime: string;
}
interface IListContactByElderId {
elderId: string;
}
interface IAddOutward {
elderId: number;
chaperoneName: string;
chaperonePhone: string;
chaperoneType: string;
outwardDate: string;
planReturnDate: string;
}
interface IGetOutwardById {
outwardId: string;
}
interface IDelayReturn {
id: string;
planReturnDate: string;
}
interface IRecordReturn {
id: string;
realReturnDate: any;
}
// 陪同人类型
export const typeList = [
{ label: "护工", value: "护工" },
{ label: "家属", value: "家属" }
];
// 分页查询外出登记
export async function pageOutwardByKey(data: IPageOutwardByKey) {
return http.get("/api/outward/pageOutwardByKey", {
params: {
...data
}
});
}
// 分页搜索老人
export async function pageSearchElderByKey(data: IPageSearchElderByKey) {
return http.get("/api/outward/pageSearchElderByKey", {
params: {
...data
}
});
}
// 获取护工列表
export async function listOutwardStaff() {
return http.get("/api/outward/listOutwardStaff");
}
// 获取紧急联系人列表
export async function listContactByElderId(data: IListContactByElderId) {
return http.get("/api/outward/listContactByElderId", {
params: {
...data
}
});
}
// 新增外出登记
export function addOutward(data: IAddOutward) {
return http.post("/api/outward/addOutward", data);
}
// 根据编号获取外出登记
export async function getOutwardById(data: IGetOutwardById) {
return http.get("/api/outward/getOutwardById", {
params: {
...data
}
});
}
// 延期返回
export function delayReturn(data: IDelayReturn) {
return http.put("/api/outward/delayReturn", data);
}
// 登记返回
export function recordReturn(data: IRecordReturn) {
return http.put("/api/outward/recordReturn", data);
}
// 删除外出登记
export async function deleteOutward(data: IGetOutwardById) {
return http.delete("/api/outward/deleteOutward", {
params: {
...data
}
});
}

@ -0,0 +1,36 @@
import { http } from "@/utils";
import { IPageSearchElderByKey } from "@/apis/bookManage";
interface IPageRetreatApplyByKey {
bedName: string;
elderName: string;
elderSex: string;
idNum: string;
}
interface IAddRetreatApply {
elderId: number;
}
// 分页查询退住申请
export async function pageRetreatApplyByKey(data: IPageRetreatApplyByKey) {
return http.get("/api/retreatApply/pageRetreatApplyByKey", {
params: {
...data
}
});
}
// 分页搜索老人
export async function pageSearchElderByKey(data: IPageSearchElderByKey) {
return http.get("/api/retreatApply/pageSearchElderByKey", {
params: {
...data
}
});
}
// 新增退住申请
export function addRetreatApply(data: IAddRetreatApply) {
return http.post("/api/retreatApply/addRetreatApply", data);
}

@ -0,0 +1,46 @@
import { http } from "@/utils";
interface IPageRetreatAuditByKey {
elderName: string;
elderSex: string;
idNum: string;
}
interface IGetElderFeeById {
elderId: number;
}
interface IAuditElderFee {
applyId: number;
elderId: number;
auditResult: number;
}
// 审核结果
export const IAuditResultList = [
{ label: "通过", value: "通过" },
{ label: "不通过", value: "不通过" }
];
// 分页查询退住审核
export async function pageRetreatAuditByKey(data: IPageRetreatAuditByKey) {
return http.get("/api/retreatAudit/pageRetreatAuditByKey", {
params: {
...data
}
});
}
// 根据编号获取老人费用详情
export async function getElderFeeById(data: IGetElderFeeById) {
return http.get("/api/retreatAudit/getElderFeeById", {
params: {
...data
}
});
}
// 审核老人费用详情
export function auditElderFee(data: IAuditElderFee) {
return http.put("/api/retreatAudit/auditElderFee", data);
}

@ -0,0 +1,55 @@
import { http } from "@/utils";
interface IPageRoomTypeByKey {
name: string;
roomTypeName: string;
}
interface IAddRoomType {
id: string;
name: string;
monthPrice: string;
}
interface IGetRoomTypeById {
roomTypeId: string;
}
// 分页查询房间类型
export async function pageRoomTypeByKey(data: IPageRoomTypeByKey) {
// 因为后台返回的字段与前端表单数据的prop不一样但是组件封装是需要一样的所以请求前增加一些这两个字段
Reflect.has(data, "name") ? (data.roomTypeName = data.name) : "";
return http.get("/api/roomType/pageRoomTypeByKey", {
params: {
...data
}
});
}
// 新增房间类型
export function addRoomType(data: IAddRoomType) {
return http.post("/api/roomType/addRoomType", data);
}
// 根据编号查询房间类型
export async function getRoomTypeById(data: IGetRoomTypeById) {
return http.get("/api/roomType/getRoomTypeById", {
params: {
...data
}
});
}
// 编辑房间类型
export function editRoomType(data: IAddRoomType) {
return http.put("/api/roomType/editRoomType", data);
}
// 删除房间类型
export async function deleteRoomType(data: IGetRoomTypeById) {
return http.delete("/api/roomType/deleteRoomType", {
params: {
...data
}
});
}

@ -0,0 +1,114 @@
import { http } from "@/utils";
interface IGetServiceType {
serviceTypeName: string;
}
export interface IPageServiceByKey {
name: string;
typeId: number;
}
interface IAddServiceType {
id: number;
name: string;
}
interface IGetServiceTypeById {
serviceTypeId: string;
}
interface IAddService {
id: number;
name: string;
needDate: string;
price: string;
typeId: number;
chargeMethod: string;
}
export interface IGetServiceById {
serviceId: string;
}
// 收费方式
export const IChargeMethodList = [
{ label: "按次", value: "按次" },
{ label: "按月", value: "按月" }
];
// 获取服务类型列表
export async function getServiceType(data: IGetServiceType) {
return http.get("/api/service/getServiceType", {
params: {
...data
}
});
}
// 分页查询服务
export async function pageServiceByKey(data: IPageServiceByKey) {
// 因为后台返回的字段与前端表单数据的prop不一样但是组件封装是需要一样的所以请求前增加一些这两个字段
// Reflect.has(data, 'roleName') ? (data.roleId = data.roleName) : ''
return http.get("/api/service/pageServiceByKey", {
params: {
...data
}
});
}
// 新增服务类型
export function addServiceType(data: IAddServiceType) {
return http.post("/api/service/addServiceType", data);
}
// 根据编号获取服务类型
export async function getServiceTypeById(data: IGetServiceTypeById) {
return http.get("/api/service/getServiceTypeById", {
params: {
...data
}
});
}
// 编辑服务类型
export function editServiceType(data: IAddServiceType) {
return http.put("/api/service/editServiceType", data);
}
// 删除服务类型
export async function deleteServiceType(data: IGetServiceTypeById) {
return http.delete("/api/service/deleteServiceType", {
params: {
...data
}
});
}
// 新增服务
export function addService(data: IAddService) {
return http.post("/api/service/addService", data);
}
// 根据编号获取服务
export async function getServiceById(data: IGetServiceById) {
return http.get("/api/service/getServiceById", {
params: {
...data
}
});
}
// 编辑服务
export function editService(data: IAddService) {
return http.put("/api/service/editService", data);
}
// 删除服务
export async function deleteService(data: IGetServiceById) {
return http.delete("/api/service/deleteService", {
params: {
...data
}
});
}

@ -0,0 +1,233 @@
import { http } from '@/utils'
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
}
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
}
interface IConsultByForm {
consultId: string | number
elderId: string | number
}
// 获取咨询管理表格数据 根据咨询人编号和老人编号获取咨询信息
export async function getConsultByForm(data: IConsultByForm) {
return http.get('/api/consult/getConsultByConsultIdAndElderId', {
params: {
...data
}
})
}
//新增资询
export function addConsult(data: IAddConsult) {
return http.post('/api/consult/addConsult', data)
}
// 删除咨询
export function delConsult(elderId: string | number) {
return http.delete('/api/consult/deleteConsult', {
params: {
elderId
}
})
}
//编辑咨询
export function editConsult(data: IAddConsult) {
return http.put('/api/consult/editConsult', data)
}
// 转为意向客户
export function intentionConsult(elderId: string | number) {
return http.put('/api/consult/intentionConsult', {
data: {
elderId
}
})
}
// 来源渠道
export function listConsultSource() {
return http.get('/api/consult/listConsultSource')
}
// 接待人
export function listConsultStaff() {
return http.get('/api/consult/listConsultStaff')
}
// 分页查询咨询
export async function pageConsultByKey(data: ISearhFormConsultByKey) {
// 因为后台返回的字段与前端表单数据的prop不一样但是组件封装是需要一样的所以请求前增加一些这两个字段
Reflect.has(data, 'sourceName') ? (data.sourceId = data.sourceName) : ''
Reflect.has(data, 'staffName') ? (data.staffId = data.staffName) : ''
const res = await http.get('/api/consult/pageConsultByKey', {
params: {
...data
}
})
return res
}
//意向用户接口
interface ISearhFormIntentionByKey {
pageNum: number
pageSize: number
elderName?: string
elderPhone?: string | number
labelId?: number
}
//分页查询意向客户
export async function pageIntentionByKey(data: ISearhFormIntentionByKey) {
const res = await http.get('/api/intention/pageIntentionByKey', {
params: {
...data
}
})
return res
}
// 新增沟通记录
export function addCommunicationRecord(data: any) {
return http.post('/api/intention/addCommunicationRecord', data)
}
// 新增意向客户
export function addIntention(data: any) {
return http.post('/api/intention/addIntention', data)
}
// 新增回访计划
export function addVisitPlan(data: any) {
return http.post('/api/intention/addVisitPlan', data)
}
// 新增沟通记录
export function deleteCommunicationRecord(communicationRecordId: any) {
return http.delete('/api/intention/deleteCommunicationRecord', {
params: {
communicationRecordId
}
})
}
// 删除回访计划
export function deleteVisitPlan(visitPlanId: any) {
return http.delete('/api/intention/deleteVisitPlan', {
params: {
visitPlanId
}
})
}
// 编辑沟通记录
export function editCommunicationRecord(data: any) {
return http.put('/api/intention/editCommunicationRecord', data)
}
// 编辑意向客户
export function editIntention(data: any) {
return http.put('/api/intention/editIntention', data)
}
// 编辑老人标签
export function editElderLabel(data: any) {
return http.put('/api/intention/editElderLabel', data)
}
// 执行回访计划
export function executeVisitPlan(data: any) {
return http.put('/api/intention/executeVisitPlan', data)
}
// 根据编号获取编辑意向客户标签
export function getEditElderLabelById(data: any) {
return http.get('/api/intention/getEditElderLabelById', {
params: {
...data
}
})
}
// 根据编号获取意向客户标签
export function getElderLabelById(data: any) {
return http.get('/api/intention/getElderLabelById', {
params: {
...data
}
})
}
// 根据编号获取意向客户
export function getIntentById(data: any) {
return http.get('/api/intention/getIntentById', {
params: {
...data
}
})
}
//客户标签
export function listLabel(data: any) {
return http.get('/intention/listLabel', {
params: {
...data
}
})
}
// 分页查询沟通记录
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
}
})
}

@ -0,0 +1,54 @@
import { http } from "@/utils";
interface IPageSourceByKey {
name: string;
sourceName: string;
}
interface IAddSource {
id: string;
name: string;
}
interface IGetSourceById {
sourceId: string;
}
// 分页查询来源渠道
export async function pageSourceByKey(data: IPageSourceByKey) {
// 因为后台返回的字段与前端表单数据的prop不一样但是组件封装是需要一样的所以请求前增加一些这两个字段
Reflect.has(data, "name") ? (data.sourceName = data.name) : "";
return http.get("/api/source/pageSourceByKey", {
params: {
...data
}
});
}
// 新增来源渠道
export function addSource(data: IAddSource) {
return http.post("/api/source/addSource", data);
}
// 根据编号查询来源渠道
export async function getSourceById(data: IGetSourceById) {
return http.get("/api/source/getSourceById", {
params: {
...data
}
});
}
// 编辑来源渠道
export function editSource(data: IAddSource) {
return http.put("/api/source/editSource", data);
}
// 删除来源渠道
export async function deleteSource(data: IGetSourceById) {
return http.delete("/api/source/deleteSource", {
params: {
...data
}
});
}

@ -0,0 +1,69 @@
import { http } from "@/utils";
interface IPageStaffByKey {
roleId: number;
roleName:number;
name: string;
phone: string;
}
interface IAddStaff {
id: number;
roleId: number;
name: string;
idNum: string;
age: number;
sex: string;
phone: string;
email: string;
address: string;
avator: string;
}
interface IGetStaffById {
staffId: string
}
// 获取角色列表
export async function getRole() {
return http.get("/api/staff/getRole");
}
// 分页查询员工
export async function pageStaffByKey(data: IPageStaffByKey) {
// 因为后台返回的字段与前端表单数据的prop不一样但是组件封装是需要一样的所以请求前增加一些这两个字段
Reflect.has(data, 'roleName') ? (data.roleId = data.roleName) : ''
return http.get("/api/staff/pageStaffByKey", {
params: {
...data
}
});
}
// 新增员工
export function addStaff(data: IAddStaff) {
return http.post("/api/staff/addStaff", data);
}
// 根据老人编号查询员工
export async function getStaffById(data: IGetStaffById) {
return http.get("/api/staff/getStaffById", {
params: {
...data
}
});
}
// 编辑员工
export function editStaff(data: IAddStaff) {
return http.put("/api/staff/editStaff", data);
}
// 删除员工
export async function leaveStaff(data: IGetStaffById) {
return http.delete("/api/staff/leaveStaff", {
params: {
...data
}
});
}

@ -0,0 +1,60 @@
import { http } from '@/utils'
interface ILoginForm {
pass: string
phone: string
}
interface ISendCodeForm {
pass: string
account: string
}
interface IForgetPass {
code: string
pass: string
account: string
}
interface IEditPass {
newPass: string
oldPass: string
}
export class IEditPassImpl implements IEditPass {
newPass: string
oldPass: string
constructor(newPass: string, oldPass: string) {
this.newPass = newPass
this.oldPass = oldPass
}
}
// 登录
export function getLogin(data: ILoginForm) {
return http.post('/api/account/login', data)
}
// 发送验证码
export async function sendCode(data: ISendCodeForm) {
return http.get('/api/account/sendCode', {
params: {
...data
}
})
}
// 忘记密码
export async function forgetPass(data: IForgetPass) {
return http.put('/api/account/forget', data)
}
// 修改密码
export async function editPass(data: IEditPass) {
return http.put('/api/account/edit', data)
}
// 退出登录
export async function getLogout() {
return http.delete('/api/account/logout')
}

@ -0,0 +1,94 @@
import { http } from "@/utils";
import { IPageSearchElderByKey } from "@/apis/bookManage";
interface IPageVisitByKey {
elderName: string;
visitName: string;
visitPhone: string;
visitFlag: string;
}
interface IAddVisit {
id: number;
elderId: number;
name: string;
phone: string;
relation: string;
visitDate: string;
visitNum: number;
}
interface IGetVisitById {
visitId: string;
}
interface IEditVisit {
id: number;
name: string;
phone: string;
relation: string;
visitDateStr: string;
visitNum: number;
}
interface IRecordLeave {
id: string;
leaveDate: any;
}
// 来访状态
export const typeList = [
{ label: "待离开", value: "待离开" },
{ label: "已离开", value: "已离开" }
];
// 分页查询来访登记
export async function pageVisitByKey(data: IPageVisitByKey) {
return http.get("/api/visit/pageVisitByKey", {
params: {
...data
}
});
}
// 分页搜索老人
export async function pageSearchElderByKey(data: IPageSearchElderByKey) {
return http.get("/api/visit/pageSearchElderByKey", {
params: {
...data
}
});
}
// 新增来访登记
export function addVisit(data: IAddVisit) {
return http.post("/api/visit/addVisit", data);
}
// 根据编号获取来访登记
export async function getVisitById(data: IGetVisitById) {
return http.get("/api/visit/getVisitById", {
params: {
...data
}
});
}
// 编辑来访登记
export function editVisit(data: IEditVisit) {
return http.put("/api/visit/editVisit", data);
}
// 登记离开
export function recordLeave(data: IRecordLeave) {
return http.put("/api/visit/recordLeave", data);
}
// 删除来访登记
export async function deleteVisit(data: IGetVisitById) {
return http.delete("/api/visit/deleteVisit", {
params: {
...data
}
});
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 253 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 223 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

@ -0,0 +1,74 @@
<template>
<div :style="style" v-show="isShow">
<slot></slot>
</div>
</template>
<script setup lang="ts" name="GridItem">
import { computed, inject, Ref, ref, useAttrs, watch } from 'vue'
import { BreakPoint, Responsive } from '../interface/index'
type Props = {
offset?: number
span?: number
suffix?: boolean
xs?: Responsive
sm?: Responsive
md?: Responsive
lg?: Responsive
xl?: Responsive
}
const props = withDefaults(defineProps<Props>(), {
offset: 0,
span: 1,
suffix: false,
xs: undefined,
sm: undefined,
md: undefined,
lg: undefined,
xl: undefined
})
const attrs = useAttrs() as any
const isShow = ref(true)
//
const breakPoint = inject<Ref<BreakPoint>>('breakPoint', ref('xl'))
const shouldHiddenIndex = inject<Ref<number>>('shouldHiddenIndex', ref(-1))
watch(
() => [shouldHiddenIndex.value, breakPoint.value],
n => {
if (attrs.index) {
isShow.value = !(n[0] !== -1 && parseInt(attrs.index) >= n[0])
}
},
{ immediate: true }
)
const gap = inject('gap', 0)
const cols = inject<Ref<number>>('cols', ref(4))
const style = computed(() => {
let span = props[breakPoint.value]?.span ?? props.span
let offset = props[breakPoint.value]?.offset ?? props.offset
if (props.suffix) {
return {
gridColumnStart: cols.value - span - offset + 1,
gridColumnEnd: `span ${span + offset}`,
marginLeft:
offset !== 0
? `calc(((100% + ${gap}px) / ${span + offset}) * ${offset})`
: 'unset'
}
} else {
return {
gridColumn: `span ${
span + offset > cols.value ? cols.value : span + offset
}/span ${span + offset > cols.value ? cols.value : span + offset}`,
marginLeft:
offset !== 0
? `calc(((100% + ${gap}px) / ${span + offset}) * ${offset})`
: 'unset'
}
}
})
</script>

@ -0,0 +1,176 @@
<template>
<div :style="style">
<slot></slot>
</div>
</template>
<script setup lang="ts" name="Grid">
import {
ref,
watch,
useSlots,
computed,
provide,
onBeforeMount,
onMounted,
onUnmounted,
onDeactivated,
onActivated,
VNodeArrayChildren,
VNode
} from 'vue'
import type { BreakPoint } from './interface/index'
type Props = {
cols?: number | Record<BreakPoint, number>
collapsed?: boolean
collapsedRows?: number
gap?: [number, number] | number
}
const props = withDefaults(defineProps<Props>(), {
cols: () => ({ xs: 1, sm: 2, md: 2, lg: 3, xl: 4 }),
collapsed: false,
collapsedRows: 1,
gap: 0
})
onBeforeMount(() => props.collapsed && findIndex())
onMounted(() => {
resize({ target: { innerWidth: window.innerWidth } } as any)
window.addEventListener('resize', resize)
})
onActivated(() => {
resize({ target: { innerWidth: window.innerWidth } } as any)
window.addEventListener('resize', resize)
})
onUnmounted(() => {
window.removeEventListener('resize', resize)
})
onDeactivated(() => {
window.removeEventListener('resize', resize)
})
//
const resize = (e: UIEvent) => {
let width = (e.target as Window).innerWidth
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
provide('gap', Array.isArray(props.gap) ? props.gap[0] : props.gap)
//
let breakPoint = ref<BreakPoint>('xl')
provide('breakPoint', breakPoint)
// index
const hiddenIndex = ref(-1)
provide('shouldHiddenIndex', hiddenIndex)
// 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!()
// index
const findIndex = () => {
let fields: VNodeArrayChildren = []
let suffix: any = null
slots.forEach((slot: any) => {
if (
typeof slot.type === 'object' &&
slot.type.name === 'GridItem' &&
slot.props?.suffix !== undefined
)
suffix = slot
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)
if (!find) hiddenIndex.value = -1
} catch (e) {
// console.warn(e);
}
}
// findIndex
watch(
() => breakPoint.value,
() => {
if (props.collapsed) findIndex()
}
)
// collapsed
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'
})
// style
const style = computed(() => {
return {
display: 'grid',
gridGap: gap.value,
gridTemplateColumns: `repeat(${cols.value}, minmax(0, 1fr))`
}
})
defineExpose({ breakPoint })
</script>

@ -0,0 +1,6 @@
export type BreakPoint = 'xs' | 'sm' | 'md' | 'lg' | 'xl'
export type Responsive = {
span?: number
offset?: number
}

@ -0,0 +1,27 @@
<template>
<Component
:is="icon"
:theme="theme"
:size="size"
:spin="spin"
:fill="fill"
:strokeLinecap="strokeLinecap"
:strokeLinejoin="strokeLinejoin"
:strokeWidth="strokeWidth"
/>
</template>
<script setup lang="ts">
import type { Icon } from '@icon-park/vue-next/lib/runtime'
defineProps<{
icon: Icon
theme?: 'outline' | 'filled' | 'two-tone' | 'multi-color'
size?: number | string
spin?: boolean
fill?: string | string[]
strokeLinecap?: 'butt' | 'round' | 'square'
strokeLinejoin?: 'miter' | 'round' | 'bevel'
strokeWidth?: number
}>()
</script>

@ -0,0 +1,60 @@
<template>
<!-- 列设置 -->
<el-drawer title="列设置" v-model="drawerVisible" size="450px">
<div class="table-main">
<el-table
:data="colSetting"
:border="true"
row-key="prop"
default-expand-all
:tree-props="{ children: '_children' }"
>
<el-table-column prop="label" align="center" label="列名" />
<el-table-column
prop="isShow"
align="center"
label="显示"
v-slot="scope"
>
<el-switch v-model="scope.row.isShow"></el-switch>
</el-table-column>
<el-table-column
prop="sortable"
align="center"
label="排序"
v-slot="scope"
>
<el-switch v-model="scope.row.sortable"></el-switch>
</el-table-column>
<template #empty>
<div class="table-empty">
<div>暂无可配置列</div>
</div>
</template>
</el-table>
</div>
</el-drawer>
</template>
<script setup lang="ts" name="ColSetting">
import { ref } from 'vue'
import { ColumnProps } from '@/components/ProTable/interface'
defineProps<{ colSetting: ColumnProps[] }>()
const drawerVisible = ref<boolean>(false)
//
const openColSetting = () => {
drawerVisible.value = true
}
defineExpose({
openColSetting
})
</script>
<style scoped lang="scss">
.cursor-move {
cursor: move;
}
</style>

@ -0,0 +1,29 @@
<template>
<!-- 分页组件 -->
<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">
interface Pageable {
pageNum: number
pageSize: number
total: number
}
interface PaginationProps {
pageable: Pageable
handleSizeChange: (size: number) => void
handleCurrentChange: (currentPage: number) => void
}
defineProps<PaginationProps>()
</script>

@ -0,0 +1,74 @@
<template>
<component :is="renderLoop(column)"></component>
</template>
<script lang="tsx" setup name="TableColumn">
import { inject, ref, useSlots } from 'vue'
import { ColumnProps } from '@/components/ProTable/interface'
import { filterEnum, formatValue, handleRowAccordingToProp } from '@/utils/util'
defineProps<{ column: ColumnProps }>()
const slots = useSlots()
const enumMap = inject('enumMap', ref(new Map()))
//
const renderCellData = (item: ColumnProps, scope: { [key: string]: any }) => {
return enumMap.value.get(item.prop) && item.isFilterEnum
? filterEnum(
handleRowAccordingToProp(scope.row, item.prop!),
enumMap.value.get(item.prop)!,
item.fieldNames
)
: formatValue(handleRowAccordingToProp(scope.row, item.prop!))
}
// tag
const getTagType = (item: ColumnProps, scope: { [key: string]: any }) => {
return filterEnum(
handleRowAccordingToProp(scope.row, item.prop!),
enumMap.value.get(item.prop),
item.fieldNames,
'tag'
) as any
}
const renderLoop = (item: ColumnProps) => {
return (
<>
{item.isShow && (
<el-table-column
{...item}
align={item.align ?? 'center'}
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)
if (slots[item.prop!]) return slots[item.prop!]!(scope)
if (item.tag)
return (
<el-tag type={getTagType(item, scope)}>
{renderCellData(item, scope)}
</el-tag>
)
return renderCellData(item, scope)
},
header: () => {
if (item.headerRender) return item.headerRender(item)
if (slots[`${item.prop}Header`])
return slots[`${item.prop}Header`]!({ row: item })
return item.label
}
}}
</el-table-column>
)}
</>
)
}
</script>

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

@ -0,0 +1,335 @@
<!-- 📚📚📚 Pro-Table 文档: https://juejin.cn/post/7166068828202336263 -->
<template>
<!-- 查询表单 card -->
<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>
<slot
name="tableHeader"
:selectedListIds="selectedListIds"
:selectedList="selectedList"
:isSelected="isSelected"
/>
</div>
<div class="header-button-ri">
<slot name="toolButton">
<el-button :icon="Refresh" circle @click="getTableList" />
<!-- <el-button :icon="Printer" circle v-if="columns.length" @click="handlePrint" /> -->
<el-button
:icon="Operation"
circle
v-if="columns.length"
@click="openColSetting"
/>
<el-button
:icon="Search"
circle
v-if="searchColumns.length"
@click="isShowSearch = !isShowSearch"
/>
</slot>
</div>
</div>
</div>
<!-- 表格主体 -->
<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 语法 && 作用域插槽 (tsx > slot) -->
<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>
<!-- other 循环递归 -->
<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
v-if="pagination"
:pageable="pageable"
:handleSizeChange="handleSizeChange"
:handleCurrentChange="handleCurrentChange"
/>
</div>
</slot></div
></MyCard>
<!-- 列设置 -->
<ColSetting v-if="toolButton" ref="colRef" v-model:colSetting="colSetting" />
</template>
<script setup lang="ts" name="ProTable">
import { ref, watch, computed, provide, onMounted } from 'vue'
import { useTable } from '@/hooks/useTable'
import { BreakPoint } from '@/components/Grid/interface'
import { ColumnProps } from '@/components/ProTable/interface'
import { ElTable, TableProps } from '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'
import { useSelection } from '@/hooks/useSelection'
// import printJS from "print-js";
const searchForm = ref()
interface ProTableProps extends Partial<Omit<TableProps<any>, 'data'>> {
columns: ColumnProps[] //
requestApi: (params: any) => Promise<any> // api ==>
requestAuto?: boolean
dataCallback?: (data: any) => any // ==>
title?: string // ==>
pagination?: boolean // ==> true
initParam?: any // ==> {}
border?: boolean // ==> true
toolButton?: boolean // ==> true
rowKey?: string // Key Table id ==> id
searchCol?: number | Record<BreakPoint, number> // ==> { xs: 1, sm: 2, md: 2, lg: 3, xl: 4 }
}
//
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()
//
onMounted(() => props.requestAuto && getTableList())
// initParam
watch(() => props.initParam, getTableList, { deep: true })
// columns
const tableColumns = ref<ColumnProps[]>(props.columns)
// enumMap enum ||
const enumMap = ref(new Map<string, { [key: string]: any }[]>())
provide('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!)
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)
}
// flatColumns
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
)
)
// enum || prop && enum
const needTransformCol = flatColumns.value!.filter(
item =>
(item.enum || (item.prop && item.prop.split('.').length > 1)) &&
item.isFilterEnum
)
needTransformCol.forEach(colItem => {
printDataList.forEach((tableItem: { [key: string]: any }) => {
tableItem[handleProp(colItem.prop!)] =
colItem.prop!.split('.').length > 1 && !colItem.enum
? formatValue(handleRowAccordingToProp(tableItem, colItem.prop!))
: filterEnum(
handleRowAccordingToProp(tableItem, colItem.prop!),
enumMap.value.get(colItem.prop!),
colItem.fieldNames
)
for (const key in tableItem) {
if (tableItem[key] === null)
tableItem[key] = formatValue(tableItem[key])
}
})
})
return printDataList
})
// 💥 printJs
// const handlePrint = () => {
// const header = `<div style="text-align: center"><h2>${props.title}</h2></div>`;
// const gridHeaderStyle = "border: 1px solid #ebeef5;height: 45px;color: #232425;text-align: center;background-color: #fafafa;";
// const gridStyle = "border: 1px solid #ebeef5;height: 40px;color: #494b4e;text-align: center";
// printJS({
// printable: printData.value,
// header: props.title && header,
// properties: flatColumns
// .value!.filter(item => !["selection", "index", "expand"].includes(item.type!) && item.isShow && item.prop !== "operation")
// .map((item: ColumnProps) => ({ field: handleProp(item.prop!), displayName: item.label })),
// type: "json",
// gridHeaderStyle,
// gridStyle
// });
// };
// ()
defineExpose({
element: tableRef,
tableData,
searchParam,
pageable,
getTableList,
reset,
clearSelection,
enumMap,
isSelected,
selectedList,
selectedListIds
})
</script>

@ -0,0 +1,49 @@
import { TableColumnCtx } from "element-plus/es/components/table/src/table-column/defaults";
import { BreakPoint, Responsive } from "@/components/Grid/interface";
export interface EnumProps {
label: string; // 选项框显示的文字
value: any; // 选项框值
disabled?: boolean; // 是否禁用此选项
tagType?: string; // 当 tag 为 true 时,此选择会指定 tag 显示类型
children?: EnumProps[]; // 为树形选择时,可以通过 children 属性指定子选项
[key: string]: any;
}
export type TypeProp = "index" | "selection" | "expand"
export type SearchType =
| "input"
| "input-number"
| "select"
| "select-v2"
| "tree-select"
| "cascader"
| "date-picker"
| "time-picker"
| "time-select"
| "switch"
| "slider"
export type SearchProps = {
el: SearchType // 当前项搜索框的类型
props?: any // 搜索项参数,根据 element plus 官方文档来传递,该属性所有值会透传到组件
key?: string // 当搜索项 key 不为 prop 属性时,可通过 key 指定
order?: number // 搜索项排序(从大到小)
span?: number // 搜索项所占用的列数默认为1列
offset?: number // 搜索字段左侧偏移列数
defaultValue?: string | number | boolean | any[] // 搜索项默认值
} & Partial<Record<BreakPoint, Responsive>>
export interface ColumnProps<T = any>
extends Partial<Omit<TableColumnCtx<T>, "children" | "renderHeader" | "renderCell">> {
tag?: boolean; // 是否是标签展示
isShow?: boolean; // 是否显示在表格当中
search?: SearchProps | undefined; // 搜索项配置
enum?: EnumProps[] | ((params?: any) => Promise<any>); // 枚举类型(渲染值的字典)
isFilterEnum?: boolean; // 当前单元格值是否根据 enum 格式化示例enum 只作为搜索项数据)
fieldNames?: { label: string; value: string }; // 指定 label && value 的 key 值
headerRender?: (row: ColumnProps) => any; // 自定义表头内容渲染tsx语法
render?: (scope: { row: T }) => any; // 自定义单元格内容渲染tsx语法
_children?: ColumnProps<T>[]; // 多级表头
}

@ -0,0 +1,6 @@
import reImageVerify from './src/index.vue'
/** 图形验证码组件 */
export const ReImageVerify = reImageVerify
export default ReImageVerify

@ -0,0 +1,85 @@
import { ref, onMounted } from 'vue'
/**
*
* @param width -
* @param height -
*/
export const useImageVerify = (width = 120, height = 40) => {
const domRef = ref<HTMLCanvasElement>()
const imgCode = ref('')
function setImgCode(code: string) {
imgCode.value = code
}
function getImgCode() {
if (!domRef.value) return
imgCode.value = draw(domRef.value, width, height)
}
onMounted(() => {
getImgCode()
})
return {
domRef,
imgCode,
setImgCode,
getImgCode
}
}
function randomNum(min: number, max: number) {
const num = Math.floor(Math.random() * (max - min) + min)
return num
}
function randomColor(min: number, max: number) {
const r = randomNum(min, max)
const g = randomNum(min, max)
const b = randomNum(min, max)
return `rgb(${r},${g},${b})`
}
function draw(dom: HTMLCanvasElement, width: number, height: number) {
let imgCode = ''
const NUMBER_STRING = '0123456789'
const ctx = dom.getContext('2d')
if (!ctx) return imgCode
ctx.fillStyle = randomColor(180, 230)
ctx.fillRect(0, 0, width, height)
for (let i = 0; i < 4; i += 1) {
const text = NUMBER_STRING[randomNum(0, NUMBER_STRING.length)]
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()
}
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()
}
for (let i = 0; i < 41; i += 1) {
ctx.beginPath()
ctx.arc(randomNum(0, width), randomNum(0, height), 1, 0, 2 * Math.PI)
ctx.closePath()
ctx.fillStyle = randomColor(150, 200)
ctx.fill()
}
return imgCode
}

@ -0,0 +1,42 @@
<script setup lang="ts">
import { watch } from 'vue'
import { useImageVerify } from './hooks'
interface Props {
code?: string
}
interface Emits {
(e: 'update:code', code: string): void
}
const props = withDefaults(defineProps<Props>(), {
code: ''
})
const emit = defineEmits<Emits>()
const { domRef, imgCode, setImgCode, getImgCode } = useImageVerify()
watch(
() => props.code,
newValue => {
setImgCode(newValue)
}
)
watch(imgCode, newValue => {
emit('update:code', newValue)
})
defineExpose({ getImgCode })
</script>
<template>
<canvas
ref="domRef"
width="120"
height="40"
class="cursor-pointer"
@click="getImgCode"
/>
</template>

@ -0,0 +1,107 @@
<template>
<component
v-if="column.search?.el"
:is="`el-${column.search.el}`"
v-bind="handleSearchProps"
v-model.trim="searchParam[column.search.key ?? handleProp(column.prop!)]"
:data="column.search?.el === 'tree-select' ? columnEnum : []"
:options="
['cascader', 'select-v2'].includes(column.search?.el) ? columnEnum : []
"
:placeholder="placeholder"
:clearable="clearable"
range-separator="至"
start-placeholder="开始时间"
end-placeholder="结束时间"
>
<template #default="{ data }" v-if="column.search.el === 'cascader'">
<span>{{ data[fieldNames.label] }}</span>
</template>
<template v-if="column.search.el === 'select'">
<component
:is="`el-option`"
v-for="(col, index) in columnEnum"
:key="index"
:label="col[fieldNames.label]"
:value="col[fieldNames.value]"
></component>
</template>
<slot v-else></slot>
</component>
</template>
<script setup lang="ts" name="SearchFormItem">
import { computed, inject, onMounted, ref } from 'vue'
import { handleProp } from '@/utils/util'
import { ColumnProps } from '@/components/ProTable/interface'
interface SearchFormItem {
column: ColumnProps
searchParam: { [key: string]: any }
}
const props = defineProps<SearchFormItem>()
// fieldNames label && value key
const fieldNames = computed(() => {
return {
label: props.column.fieldNames?.label ?? 'label',
value: props.column.fieldNames?.value ?? 'value'
}
})
// enumMap
const enumMap = inject('enumMap', ref(new Map()))
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],
value: item[fieldNames.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 },
nodeKey: value
}
if (searchEl === 'cascader')
handleProps = {
...searchProps,
props: { label, value, ...searchProps.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)
)
})
</script>

@ -0,0 +1,109 @@
<template>
<MyCard
><div class="card table-search" v-if="columns.length">
<el-form ref="formRef" :model="searchParam">
<Grid
ref="gridRef"
:collapsed="collapsed"
:gap="[20, 0]"
:cols="searchCol"
>
<GridItem
v-for="(item, index) in columns"
:key="item.prop"
v-bind="getResponsive(item)"
:index="index"
>
<el-form-item :label="`${item.label} :`">
<SearchFormItem :column="item" :searchParam="searchParam" />
</el-form-item>
</GridItem>
<GridItem suffix>
<div class="operation">
<el-button
class="bg-blue clickSearchBtn"
type="primary"
:icon="Search"
@click="search"
>
搜索
</el-button>
<el-button :icon="Delete" @click="reset"></el-button>
<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">
import { computed, onMounted, ref } from 'vue'
import { ColumnProps } from '@/components/ProTable/interface'
import { BreakPoint } from '@/components/Grid/interface'
import { Delete, Search, ArrowDown, ArrowUp } from '@element-plus/icons-vue'
import SearchFormItem from './components/SearchFormItem.vue'
import Grid from '@/components/Grid/index.vue'
import GridItem from '@/components/Grid/components/GridItem.vue'
import MyCard from '../my-card/my-card.vue'
interface ProTableProps {
columns?: ColumnProps[] //
searchParam?: { [key: string]: any } //
searchCol: number | Record<BreakPoint, number>
search: (params: any) => void //
reset: (params: any) => void //
}
//
const props = withDefaults(defineProps<ProTableProps>(), {
columns: () => [],
searchParam: () => ({})
})
//
const getResponsive = (item: ColumnProps) => {
return {
span: item.search?.span,
offset: item.search?.offset ?? 0,
xs: item.search?.xs,
sm: item.search?.sm,
md: item.search?.md,
lg: item.search?.lg,
xl: item.search?.xl
}
}
//
const collapsed = ref(true)
//
const gridRef = ref()
const breakPoint = computed<BreakPoint>(() => gridRef.value?.breakPoint)
// /
const showCollapse = computed(() => {
let show = false
props.columns.reduce((prev, current) => {
prev +=
(current.search![breakPoint.value]?.span ?? current.search?.span ?? 1) +
(current.search![breakPoint.value]?.offset ?? current.search?.offset ?? 0)
if (typeof props.searchCol !== 'number') {
if (prev >= props.searchCol[breakPoint.value]) show = true
} else {
if (prev > props.searchCol) show = true
}
return prev
}, 0)
return show
})
</script>

@ -0,0 +1,36 @@
<template>
<svg class="svg-icon" area-hidden="true">
<use :xlink:href="iconName"></use>
</svg>
</template>
<script setup lang="ts">
import { defineProps, computed } from 'vue'
const props = defineProps({
icon: {
type: String,
required: true
},
size: {
type: [Number, String],
default: 16
}
})
const iconName = computed(() => {
return `#icon-${props.icon}`
})
const iconSize = computed(() => {
return props.size + 'px'
})
</script>
<style lang="scss" scoped>
.svg-icon {
width: 1em;
height: 1em;
fill: currentColor;
font-size: v-bind(iconSize);
}
</style>

@ -0,0 +1,86 @@
<template>
<el-dialog
style="width: 70%"
v-model="dialogVisible"
title="选择老人"
destroy-on-close
>
<div class="table-box">
<ProTable
ref="proTable"
title="用户列表"
:columns="columns"
:requestApi="getTableList"
>
<!-- 表格操作 -->
<template #operation="scope">
<el-popconfirm
title="Are you sure to choose this?"
@confirm="checkElder(scope.row)"
confirm-button-type="warning"
>
<template #reference>
<el-button size="small" link :icon="View" > 选择 </el-button>
</template>
</el-popconfirm>
</template>
</ProTable>
</div>
</el-dialog>
</template>
<script setup lang="ts" name="useProTable">
import { ref } from "vue";
import { ColumnProps } from "@/components/ProTable/interface";
import ProTable from "@/components/ProTable/index.vue";
import { View } from "@element-plus/icons-vue";
const dialogVisible = ref(false);
const proTable = ref();
const drawerProps = ref<DialogProps>();
interface DialogProps {
elderApi: (params: any) => Promise<any>;
}
// params
// ProTable :requestApi="getUserList"
let getTableList = async (params: any) => {
let newParams = JSON.parse(JSON.stringify(params));
return drawerProps.value?.elderApi(newParams);
};
//
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 }
];
//
const elderAcceptParams = (params: DialogProps) => {
drawerProps.value = params;
dialogVisible.value = true;
};
//
defineExpose({
elderAcceptParams
});
// const emit = defineEmits(['getCheckElderInfo'])
const emit = defineEmits<{
(event: "getCheckElderInfo", val: any): void
}>();
//
const checkElder = (row: any) => {
emit("getCheckElderInfo", row);
dialogVisible.value = false;
};
</script>
<style lang="scss" scoped></style>

@ -0,0 +1,20 @@
<template>
<div class="flex flex-col rounded card-wrap p-3">
<div class="pb-2" v-if="title">{{ title }}</div>
<slot></slot>
</div>
</template>
<script lang="ts" setup>
defineProps<{
title?: string
}>()
</script>
<style lang="scss" scoped>
.card-wrap {
background-color: #ffffff;
border: 1px solid #eee;
font-size: 18px;
}
</style>

@ -0,0 +1,60 @@
<template>
<el-dialog
style="width: 70%"
v-model="dialogVisible"
title="选择床位"
destroy-on-close
>
<el-tree
:data="data"
:props="defaultProps"
accordion
@node-click="checkBed"
/>
</el-dialog>
</template>
<script setup lang="ts" name="useProTable">
import { ref } from "vue";
import { getBuildTree } from "@/apis/bookManage";
const data: any = ref();
const dialogVisible = ref(false);
const drawerProps = ref<DialogProps>();
interface DialogProps {
treeApi: (params: any) => Promise<any>;
}
const defaultProps = {
id: "id",
label: "name",
children: "childrenList"
};
//
const treeAcceptParams = async (params: DialogProps) => {
drawerProps.value = params;
dialogVisible.value = true;
const res: any = await getBuildTree();
data.value = res.data;
};
//
defineExpose({
treeAcceptParams
});
// const emit = defineEmits(['getCheckElderInfo'])
const emit = defineEmits<{
(event: "getCheckBedInfo", val: any): void
}>();
//
const checkBed = (bed: any) => {
if (bed.level === 4) {
emit("getCheckBedInfo", bed);
dialogVisible.value = false;
}
};
</script>
<style lang="scss" scoped></style>

@ -0,0 +1,146 @@
<template>
<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"
>
<el-icon>
<Plus />
</el-icon>
</el-upload>
<el-dialog v-model="dialogVisible">
<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>
import store from "@/store";
import { baseUrl } from "@/utils/http";
import { ElMessage } from "element-plus";
import { Plus } from "@element-plus/icons-vue";
import { defineEmits, onMounted, ref, watch } from "vue";
import type { UploadUserFile, ElUpload } from "element-plus";
// page data
const dialogImageUrl = ref("");
const dialogVisible = ref(false);
const imageList = ref<UploadUserFile[]>([]);
// father component transfer to this date
const props = defineProps({
uploadParams: Object
});
//
onMounted(() => {
props.uploadParams?.["imageList"].forEach((image: any) => {
imageList.value.push(image);
});
});
// http request data
const requestUrl = baseUrl + "file/uploadImg";
const token = store.state.app.token;
// return data
const imageUrlList = ref<any[]>([]);
//
const emits = defineEmits(["setImageData"]);
//
const updateData = () => {
//
imageUrlList.value = [];
imageList.value.forEach(image => imageUrlList.value.push(image.url));
//
const data = ref<any>();
if (props.uploadParams?.["uploadType"] === "single") {
data.value = imageUrlList.value[0];
} else {
data.value = imageUrlList;
}
//
emits("setImageData", data);
};
//
watch(imageList, (value, oldValue, onCleanup) => {
if (props.uploadParams?.["uploadType"] === "single" && imageList.value.length > 1) {
imageList.value.splice(0, 1);
updateData();
}
});
//
const uploadBefore = (file: any) => {
const isJPG = file.type === "image/jpeg" || file.type === "image/png";
const isLt2M = file.size / 1024 / 1024 < 2;
if (!isJPG) {
ElMessage.error("只能上传jpg/png文件");
}
if (!isLt2M) {
ElMessage.error("文件大小不能超过2MB");
}
return isJPG && isLt2M;
};
//
const handleError = (response: any) => {
ElMessage.error(response.data.msg);
};
//
const handleSuccess = (response: any, uploadFile: any) => {
uploadFile.url = response.data.url;
updateData();
};
//
const handleRemove = async (uploadFile: any) => {
imageList.value.filter((image) => image.uid !== uploadFile.uid);
updateData();
// TODO imageId : uploadFile.uid
};
//
const handlePreview = (uploadFile: any) => {
dialogImageUrl.value = uploadFile.url!;
dialogVisible.value = true;
};
</script>

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

@ -0,0 +1,27 @@
export namespace Table {
export interface Pageable {
pageNum: number
pageSize: number
total: number
}
export interface TableStateProps {
tableData: any[]
pageable: Pageable
searchParam: {
[key: string]: any
}
searchInitParam: {
[key: string]: any
}
totalParam: {
[key: string]: any
}
icon?: {
[key: string]: any
}
}
}
export namespace HandleData {
export type MessageType = '' | 'success' | 'warning' | 'info' | 'error'
}

@ -0,0 +1,34 @@
import { ElMessageBox, ElMessage } from 'element-plus'
import { HandleData } from './interface'
/**
* @description ()
* @param {Function} api api()
* @param {Object} params {id,params}()
* @param {String} message ()
* @param {String} confirmType icon(, warning)
* @return Promise
*/
export const useHandleData = <P = any, R = any>(
api: (params: P) => Promise<R>,
params: Parameters<typeof api>[0],
message: string,
confirmType: HandleData.MessageType = 'warning'
) => {
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)
})
})
}

@ -0,0 +1,14 @@
import SvgIcon from '@/components/SvgIcon/index.vue'
import { h, defineComponent } from 'vue'
export function useRenderIcon(iconName: string, attrs?: any) {
return defineComponent({
name: 'SvgIcon',
render() {
return h(SvgIcon, {
icon: iconName,
...attrs
})
}
})
}

@ -0,0 +1,36 @@
import { ref, computed } from 'vue'
/**
* @description
* @param {String} rowKey id
* */
export const useSelection = (rowKey = 'id') => {
// 是否选中数据
const isSelected = ref<boolean>(false)
// 选中的数据列表
const selectedList = ref([])
// 当前选中的所有ids(数组)可根据项目自行配置id字段
const selectedListIds = computed((): string[] => {
const ids: string[] = []
selectedList.value.forEach(item => ids.push(item[rowKey]))
return ids
})
/**
* @description
* @param {Array} rowArr
* @return void
*/
const selectionChange = (rowArr: any) => {
rowArr.length === 0 ? (isSelected.value = false) : (isSelected.value = true)
selectedList.value = rowArr
}
return {
isSelected,
selectedList,
selectedListIds,
selectionChange
}
}

@ -0,0 +1,98 @@
// import { ref } from 'vue'
// import { useRoute, onBeforeRouteUpdate } from 'vue-router'
// import { useCookies } from '@vueuse/integrations/useCookies'
// import { router } from '~/router'
// export function useTabList() {
// const route = useRoute()
// const cookie = useCookies()
// const activeTab = ref(route.path)
// const tabList = ref([
// {
// title: '后台首页',
// path: '/'
// }
// ])
// // 添加标签导航
// function addTab(tab) {
// let noTab = tabList.value.findIndex((t) => t.path == tab.path) == -1
// if (noTab) {
// tabList.value.push(tab)
// }
// cookie.set('tabList', tabList.value)
// }
// // 初始化标签导航列表
// function initTabList() {
// let tbs = cookie.get('tabList')
// if (tbs) {
// tabList.value = tbs
// }
// }
// initTabList()
// onBeforeRouteUpdate((to, from) => {
// activeTab.value = to.path
// addTab({
// title: to.meta.title,
// path: to.path
// })
// })
// const changeTab = (t) => {
// activeTab.value = t
// router.push(t)
// }
// const removeTab = (t) => {
// let tabs = tabList.value
// let a = activeTab.value
// if (a == t) {
// tabs.forEach((tab, index) => {
// if (tab.path == t) {
// const nextTab = tabs[index + 1] || tabs[index - 1]
// if (nextTab) {
// a = nextTab.path
// }
// }
// })
// }
// activeTab.value = a
// tabList.value = tabList.value.filter((tab) => tab.path != t)
// cookie.set('tabList', tabList.value)
// }
// const handleClose = (c) => {
// if (c == 'clearAll') {
// // 切换回首页
// activeTab.value = '/'
// // 过滤只剩下首页
// tabList.value = [
// {
// title: '后台首页',
// path: '/'
// }
// ]
// } else if (c == 'clearOther') {
// // 过滤只剩下首页和当前激活
// tabList.value = tabList.value.filter(
// (tab) => tab.path == '/' || tab.path == activeTab.value
// )
// }
// cookie.set('tabList', tabList.value)
// }
// return {
// activeTab,
// tabList,
// changeTab,
// removeTab,
// handleClose
// }
// }

@ -0,0 +1,174 @@
import { Table } from './interface'
import { reactive, computed, toRefs } from 'vue'
/**
* @description table
* @param {Function} api api ()
* @param {Object} initParam ({})
* @param {Boolean} isPageable (true)
* @param {Function} dataCallBack ()
* */
export const useTable = (
api: (params: any) => Promise<any>,
initParam: object = {},
isPageable = true,
dataCallBack?: (data: any) => any
) => {
const state = reactive<Table.TableStateProps>({
// 表格数据
tableData: [],
// 分页数据
pageable: {
// 当前页数
pageNum: 1,
// 每页显示条数
pageSize: 10,
// 总条数
total: 0
},
// 查询参数(只包括查询)
searchParam: {},
// 初始化默认的查询参数
searchInitParam: {},
// 总参数(包含分页和查询参数)
totalParam: {}
})
/**
* @description (,)
* */
const pageParam = computed({
get: () => {
return {
pageNum: state.pageable.pageNum,
pageSize: state.pageable.pageSize
}
},
set: (newVal: any) => { // 我是分页更新之后的值
}
})
/**
* @description
* @return void
* */
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
const { pageNum, pageSize, total } = data
isPageable && updatePageable({ pageNum, pageSize, total })
} catch (error) {
console.log(error)
}
}
/**
* @description
* @return void
* */
const updatedTotalParam = () => {
state.totalParam = {}
// 处理查询参数,可以给查询参数加自定义前缀操作
const nowSearchParam: { [key: string]: any } = {}
// 防止手动清空输入框携带参数(这里可以自定义查询参数前缀)
for (const key in state.searchParam) {
// * 某些情况下参数为 false/0 也应该携带参数
if (
state.searchParam[key] ||
state.searchParam[key] === false ||
state.searchParam[key] === 0
) {
nowSearchParam[key] = state.searchParam[key]
}
}
Object.assign(
state.totalParam,
nowSearchParam,
isPageable ? pageParam.value : {}
)
}
/**
* @description
* @param {Object} resPageable
* @return void
* */
const updatePageable = (resPageable: Table.Pageable) => {
Object.assign(state.pageable, resPageable)
}
/**
* @description
* @return void
* */
const search = () => {
state.pageable.pageNum = 1
updatedTotalParam()
getTableList()
}
/**
* @description
* @return void
* */
const reset = () => {
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
* */
const handleSizeChange = (val: number) => {
state.pageable.pageNum = 1
state.pageable.pageSize = val
getTableList()
}
/**
* @description
* @param {Number} val
* @return void
* */
const handleCurrentChange = (val: number) => {
state.pageable.pageNum = val
getTableList()
}
return {
...toRefs(state),
getTableList,
search,
reset,
handleSizeChange,
handleCurrentChange,
updatedTotalParam
}
}

@ -0,0 +1,15 @@
import SvgIcon from '@/components/SvgIcon/index.vue'
import { App } from 'vue'
// 获取上下文 require.context检索的目录是否检索子文件夹正则表达式
// 返回值是一个函数(传入路径可以导入文件)
// 通过静态方法keys可以检索所有文件路径
// 通过.prototype可以查看所有静态方法
const svgRequired = require.context('./svg', false, /\.svg$/)
svgRequired.keys().forEach(item => svgRequired(item))
export default (app: App) => {
app.component('svg-icon', SvgIcon)
}

@ -0,0 +1 @@
<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' width='24' height='24'><path d="m2.344 15.271 2 3.46a1 1 0 0 0 1.366.365l1.396-.806c.58.457 1.221.832 1.895 1.112V21a1 1 0 0 0 1 1h4a1 1 0 0 0 1-1v-1.598a8.094 8.094 0 0 0 1.895-1.112l1.396.806c.477.275 1.091.11 1.366-.365l2-3.46a1.004 1.004 0 0 0-.365-1.366l-1.372-.793a7.683 7.683 0 0 0-.002-2.224l1.372-.793c.476-.275.641-.89.365-1.366l-2-3.46a1 1 0 0 0-1.366-.365l-1.396.806A8.034 8.034 0 0 0 15 4.598V3a1 1 0 0 0-1-1h-4a1 1 0 0 0-1 1v1.598A8.094 8.094 0 0 0 7.105 5.71L5.71 4.904a.999.999 0 0 0-1.366.365l-2 3.46a1.004 1.004 0 0 0 .365 1.366l1.372.793a7.683 7.683 0 0 0 0 2.224l-1.372.793c-.476.275-.641.89-.365 1.366zM12 8c2.206 0 4 1.794 4 4s-1.794 4-4 4-4-1.794-4-4 1.794-4 4-4z"></path></svg>

After

Width:  |  Height:  |  Size: 764 B

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" style="fill: rgba(0, 0, 0, 1);transform: ;msFilter:;"><path d="M20 4H4c-1.103 0-2 .897-2 2v2h20V6c0-1.103-.897-2-2-2zM2 18c0 1.103.897 2 2 2h16c1.103 0 2-.897 2-2v-6H2v6zm3-3h6v2H5v-2z"></path></svg>

After

Width:  |  Height:  |  Size: 283 B

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="check-zh" width="1em" height="1em" viewBox="0 0 1024 1024" data-v-c265709e="" style="outline: none;"><path fill="currentColor" d="M406.656 706.944L195.84 496.256a32 32 0 1 0-45.248 45.248l256 256l512-512a32 32 0 0 0-45.248-45.248L406.592 706.944z"></path></svg>

After

Width:  |  Height:  |  Size: 382 B

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" style="fill: rgba(0, 0, 0, 1);transform: ;msFilter:;"><path d="M21 10a3.58 3.58 0 0 0-1.8-3 3.66 3.66 0 0 0-3.63-3.13 3.86 3.86 0 0 0-1 .13 3.7 3.7 0 0 0-5.11 0 3.86 3.86 0 0 0-1-.13A3.66 3.66 0 0 0 4.81 7 3.58 3.58 0 0 0 3 10a1 1 0 0 0-1 1 10 10 0 0 0 5 8.66V21a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1v-1.34A10 10 0 0 0 22 11a1 1 0 0 0-1-1zM5 10a1.59 1.59 0 0 1 1.11-1.39l.83-.26-.16-.85a1.64 1.64 0 0 1 1.66-1.62 1.78 1.78 0 0 1 .83.2l.81.45.5-.77a1.71 1.71 0 0 1 2.84 0l.5.77.81-.45a1.78 1.78 0 0 1 .83-.2 1.65 1.65 0 0 1 1.67 1.6l-.16.85.82.28A1.59 1.59 0 0 1 19 10z"></path></svg>

After

Width:  |  Height:  |  Size: 659 B

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg width="24" height="24" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M33 6V15H42" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/><path d="M15 6V15H6" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/><path d="M15 42V33H6" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/><path d="M33 42V33H41.8995" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/></svg>

After

Width:  |  Height:  |  Size: 549 B

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg width="24" height="24" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M6 6L16 15.8995" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/><path d="M6 41.8995L16 32" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/><path d="M42.0001 41.8995L32.1006 32" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/><path d="M41.8995 6L32 15.8995" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/><path d="M33 6H42V15" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/><path d="M42 33V42H33" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/><path d="M15 42H6V33" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/><path d="M6 15V6H15" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/></svg>

After

Width:  |  Height:  |  Size: 983 B

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg width="24" height="24" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M43 5L29.7 43L22.1 25.9L5 18.3L43 5Z" stroke="#333" stroke-width="4" stroke-linejoin="round"/><path d="M43.0001 5L22.1001 25.9" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/></svg>

After

Width:  |  Height:  |  Size: 356 B

@ -0,0 +1 @@
<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' width='24' height='24'><path d="m21.743 12.331-9-10c-.379-.422-1.107-.422-1.486 0l-9 10a.998.998 0 0 0-.17 1.076c.16.361.518.593.913.593h2v7a1 1 0 0 0 1 1h3a1 1 0 0 0 1-1v-4h4v4a1 1 0 0 0 1 1h3a1 1 0 0 0 1-1v-7h2a.998.998 0 0 0 .743-1.669z"></path></svg>

After

Width:  |  Height:  |  Size: 316 B

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

Loading…
Cancel
Save