feat-0330: init object

This commit is contained in:
牛哥 2025-02-27 21:54:11 +08:00
commit 7097d0777c
73 changed files with 30573 additions and 0 deletions

3
.browserslistrc Normal file
View File

@ -0,0 +1,3 @@
> 1%
last 2 versions
not dead

5
.env.development Normal file
View File

@ -0,0 +1,5 @@
# 变量必须以 VITE_ 为前缀才能暴露给外部读取
NODE_ENV = 'development'
VUE_APP_TITLE = '悦读馆'
# 开发环境
VUE_APP_API_BASE_URL=http://localhost:8101/api

4
.env.production Normal file
View File

@ -0,0 +1,4 @@
NODE_ENV = 'production'
VUE_APP_TITLE = '悦读馆'
#线上环境
VUE_APP_API_BASE_URL=https://backend.xiaobaitiao.icu:443/api

23
.gitignore vendored Normal file
View File

@ -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?

405
LICENSE Normal file
View File

@ -0,0 +1,405 @@
<<<<<<< HEAD
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
=======
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
>>>>>>> remotes/github/main

827
README.md Normal file
View File

@ -0,0 +1,827 @@
# 智能图书馆开源文档
>作者:[程序员小白条](https://luoye6.github.io/)
>
>[Gitee 主页](https://gitee.com/falle22222n-leaves)
>
>[GitHub 主页](https://github.com/luoye6)
Language**[English](README_en.md)**| **[中文](README.md).**
## ☀️新手必读
+ 本项目拥有完整的API后台接口文档文尾)(重点⭐)
+ 项目部署视频正在录制
+ 如果项目对您有所帮助可以Star⭐一下受到鼓励的我会继续加油。
+ [项目在线演示地址](https://www.xiaobaitiao.top)
+ [项目前端地址](https://gitee.com/falle22222n-leaves/vue_-book-manage-system)
+ [项目后端地址](https://gitee.com/falle22222n-leaves/vue_-book-manage-system_backend)
+ [项目部署视频](https://www.bilibili.com/video/BV1Zh4y1z7QE/?spm_id_from=333.999.0.0)
[![star](https://gitee.com/falle22222n-leaves/vue_-book-manage-system/badge/star.svg?theme=dark)](https://gitee.com/falle22222n-leaves/vue_-book-manage-system) [![gitee](https://badgen.net/badge/gitee/falle22222n-leaves/red)](https://gitee.com/falle22222n-leaves) [![github](https://badgen.net/badge/github/github?icon)](https://github.com/luoye6)
## ☀️个人介绍
![](https://pic.yupi.icu/5563/202403021406388.png)
![](https://pic.yupi.icu/5563/202403021406360.png)
## ☀️项目介绍
**AI 智能图书馆**AI Intelligent Library是一个利用 AI 模型和数据分析对用户所喜欢的图书进行精准推荐的系统,并且提供了 AIGC 的在线生成借阅量分析的 BI 图表功能,能够起到一个数据分析师的作用。其主要有三大使用者:用户(借阅人)、图书管理员、系统管理员。
> Ps如果你想要简易和新颖那么这个项目将会是不错的选择~
![](https://pic.yupi.icu/5563/202403041924533.png)
![](https://pic.yupi.icu/5563/202403041924237.png)
## ☀️功能和特性
### 用户功能
1图书查询功能分页构造器缓解数据过大压力后端可设置请求数防止爬虫请求数过大服务器负载过大。模糊查询进行字段搜索。表格均**可导出 PDF 和 EXCEL**。
2读者规则功能查询现有的借阅规则借阅规则包括借阅编号可借阅图书数量可借阅天数可借阅图书馆过期扣费/天。
3查看公告: 可以查询图书管理员发布的公告列表,**文字滑动效果**。
4个人信息: 可以查看个人的借阅证编号,借阅证姓名,规则编号,状态,可以修改个人账户的密码。
5借阅信息: 可以查看自身借阅过的图书记录和归还情况。
6违章信息: 可以查询自身归还的图书是否有违章信息。
7读者留言: 实现留言功能并以**弹幕形式**显示。
8**智能推荐**用户输入自己的偏好AI 根据数据库书籍列表和用户偏好,给用户推荐书籍。
### 图书管理员功能
1借阅图书: 图书管理员输入借阅证号(用户)和要借的图书编号和当前的时间,点击借阅。
2归还图书: 输入图书编号查看图书是否逾期,并且可以设置违规信息,然后选择是否归还图书。
3借书报表: 用于查询已经借阅并归还的书籍列表,同样使用分页构造器和模糊查询字段,显示借阅证编号,图书编号,借阅日期,截止日期,归还日期,违章信息,处理人。
4还书报表: 用于查询已经借阅但是还未归还的书籍列表,显示借阅证编号,图书编号,借阅日期,截止日期。
5发布公告: 可以查询当前发布的公告列表,并进行删除,修改,增加功能,分页构造器用于缓解数据量大的情况。
### 系统管理员功能
1书籍管理: 可以查询当前的所有图书,显示图书编号,图书昵称,作者,图书馆,分类,位置,状态,描述。可以进行添加,修改,删除图书。利用分页构造器实现批量查询。利用模糊查询实现图书搜索功能。**利用插件实现 PDF 和 EXCEL 导出**。
2书籍类型: 显示查询当前的所有图书类型,可以进行添加,修改,删除图书类型,利用分页构造器实现批量查询,缓解数据压力。
3借阅证管理: 可以查询当前的所有借阅证列表,也就是用户数量,可以进行添加,修改,删除操作。同样实现分页。
4借阅信息查询: 可以查询当前已经完成借阅和归还的记录显示借阅证号书籍编号借阅日期截止日期归还日期违章信息处理人。分页功能PDF 和 EXCEL 导出。
5借阅规则管理: 可以查询当前所有的借阅规则,显示限制借阅天数,限制本数,限制图书馆,逾期费用,可以进行添加、删除、修改操作。
6图书管理员管理: 显示当前的图书管理员列表,显示账号,姓名,邮箱,可以进行添加、删除、修改操作。
7系统管理: 可以查询一个月内的借阅量,以一周为时间间隔,计算借阅量,**用 Echarts 实现各种图表的展示**。
8系统分析可以上传某个时间段的借阅量和日期并且输入分析目标和想要生成的图表类型等待一段时间后**AI 将会给出分析结论和可视化图表**。
### 特性(亮点)
1本项目采用前后端分离的模式前端构建页面后端作数据接口前端调用后端数据接口得到数据重新渲染页面。
2前端在 Authorization 字段提供 Token 令牌API 认证使用 Token 认证,使用 HTTP Status Code 表示状态,数据返回格式使用 JSON。
3后端已开启 CORS 跨域支持,采用权限拦截器进行权限校验,并检查登录情况。
4添加全局异常处理机制捕获异常增强系统健壮性。
5前端用 Echarts 可视化库实现了图书借阅的分析图标(折线图、饼图),并通过 Loading 配置提高加载体验。
6留言组件采用弹幕形式贴合用户的喜好。
7引入 knife4j 依赖,使用 Swagger + Knife4j 自动生成 OpenAPI 规范的接口文档,前端可以在此基础上使用插件自动生成接口请求代码,降低前后端协作成本
8使用 ElementUI 组件库进行前端界面搭建,快速实现页面生成,并实现了前后端统一权限管理,多环境切换等能力。
9基于 MyBatis Plus 框架的 QueryWrapper 实现对 MySQL 数据库的灵活查询,并配合 MyBatisX 插件自动生成后端 CRUD 基础代码,减少重复工作。
10前端路由懒加载、CDN 静态资源缓存优化、图片懒加载效果。
## ☀️运行方式
### 2 分钟快速上手使用项目
1找到 SpringBoot 启动类,点击运行
![](https://pic.yupi.icu/5563/202403041925113.png)
2打开 Knife4J 注册用户,或者可以直接找我拿数据库模拟数据(简易)。
![](https://pic.yupi.icu/5563/202403041925196.png)
![](https://pic.yupi.icu/5563/202403041925244.png)
3前端输入表单内容后点击登录即可成功开始愉快使用功能~
![](https://pic.yupi.icu/5563/202403041925792.png)
![](https://pic.yupi.icu/5563/202403041925648.png)
## ☀️部署方式
### 前置条件
**前端**
软件Vscode 或者 Webstorm推荐
环境Node 版本 16 或者 18推荐 **注:千万别选 18 以上的版本!**
**后端**
软件Eclipse 或者 IDEA推荐
环境MySQL 5.7 或者 8.0推荐Redis可选
### 前端部署
1点击克隆/下载项目,会使用 Git 进行版本控制的,推荐 Git Clone不会的小伙伴可以选择下载一个 Zip 压缩包,然后解压到自己电脑的 D 盘,推荐直接 Star后续直接向我拿数据库模拟文件和 API 接口文档。
![](https://pic.yupi.icu/5563/202403041926975.png)
2利用 Vscode 或者 Webstorm 打开前端页面,配置 Configuration。配置 Node 环境和包管理工具即可,我这边选择的包管理工具是 Npm其他包管理工具如Yarn、Cnpm、Pnpm 皆可。 **注:注意更改 Npm 的镜像地址为淘宝的新镜像地址,否则会出现 Npm Install 一直卡进度条的情况。**
3直接点击 dev 的运行,或者打开控制台,输入 npm run serve 即可成功启动前端项目。
```shell
npm config set registry https://registry.npmmirror.com/
```
![](https://pic.yupi.icu/5563/202403041926892.png)
![](https://pic.yupi.icu/5563/202403041926931.png)
![](https://pic.yupi.icu/5563/202403041926639.png)
4将图片链接进行自定义切换可以切换为你自己的图床的图片链接比如七牛云、GitHub 等,也可以寻找在线图片,复制百度文库图片链接(多试几次,有些图片有防盗链)。**更换背景后,可以看到右下角的权限切换小图标。**
![](https://pic.yupi.icu/5563/202403041926849.png)
![](https://pic.yupi.icu/5563/202403041926100.png)
### 后端部署
1点击克隆/下载项目,会使用 Git 进行版本控制的,推荐 Git Clone不会的小伙伴可以选择下载一个 Zip 压缩包,然后解压到自己电脑的 D 盘,推荐直接 Star后续直接向我拿数据库模拟文件和 API 接口文档。
![](https://pic.yupi.icu/5563/202403041926093.png)
2领取数据库模拟文件后利用 Navicat 或者 SQLYog 等软件导入数据库文件,记得先建立一个名为 bms_boot 的数据库,然后右键点击运行 SQL 文件即可,运行成功,无报错后,重新打开数据库,检查是否有数据,如果有数据,表明导入成功。
![](https://pic.yupi.icu/5563/202403041926256.png)
![](https://pic.yupi.icu/5563/202403041926292.png)
3用 IDEA 打开后端项目,找到 application-dev.yml 文件,修改其中的 MySQL 配置,保证用户名和密码正确,注:密码不能以数字 0 开头。
![](https://pic.yupi.icu/5563/202403041926672.png)
4导入 Maven 依赖,注意看自己的 Maven 版本是否正确建议选择跟我一样的3.8以上的版本,发现依赖导入很慢,是因为没有配置国内镜像,默认连接的是国外服务器,因此阿里云镜像配置可以看这篇博客。[CSDN Maven 配置教程](https://blog.csdn.net/lianghecai52171314/article/details/102625184?ops_request_misc=&request_id=&biz_id=102&utm_term=Maven)
![](https://pic.yupi.icu/5563/202403041926747.png)
5找到 SpringBoot 启动类,我建议用 Debug 模式启动项目,更好排查错误。
![](https://pic.yupi.icu/5563/202403041926037.png)
6如果遇到错误大概率可能是 JDK 版本问题,我项目用的是 JDK 8建议选择与我相同版本。
![](https://pic.yupi.icu/5563/202403041926752.png)
![](https://pic.yupi.icu/5563/202403041926887.png)
7成功启动项目效果展示如下
![](https://pic.yupi.icu/5563/202403041926993.png)
### 前后端联调
1如果需要修改端口和前缀比如/api需要同时修改前端和后端。
![](https://pic.yupi.icu/5563/202403041926975.png)
![](https://pic.yupi.icu/5563/202403041926787.png)
## ☀️技术选型
### 前端
| **技术** | **作用** | **版本** |
| ---------------------------- | ------------------------------------------------------------ | ---------------------------------------------------- |
| Vue | 提供前端交互 | 2.6.14 |
| Vue-Router | 路由式编程导航 | 3.5.1 |
| Element-UI | 模块组件库,绘制界面 | 2.4.5 |
| Axios | 发送ajax请求给后端请求数据 | 1.2.1 |
| core-js | 兼容性更强,浏览器适配 | 3.8.3 |
| swiper | 轮播图插件(快速实现) | 3.4.2 |
| vue-baberrage | vue弹幕插件(实现留言功能) | 3.2.4 |
| vue-json-excel | 表格导出Excel | 0.3.0 |
| html2canvas+jspdf | 表格导出PDF | 1.4.1 2.5.1 |
| node-polyfill-webpack-plugin | webpack5中移除了nodejs核心模块的polyfill自动引入 | 2.0.1 |
| default-passive-events | **Chrome** 增加了新的事件捕获机制 **Passive Event Listeners**(被动事件侦听器) | 让页面滑动更加流畅,主要用于提升移动端滑动行为的性能 |
| nprogress | 发送请求显示进度条(人机交互友好) | 0.2.0 |
| echarts | 数据转图标的好工具(功能强大) | 5.4.1 |
| less lessloader | 方便样式开发 | 4.1.3 11.1.0 |
### 后端
| **技术及版本** | **作用** | **版本** |
| ------------------------------------ | ------------------------------------------------------------ | --------------------------------- |
| SpringBoot | 应用开发框架 | 2.7.8 |
| JDK | Java 开发包 | 1.8 |
| MySQL | 提供后端数据库 | 8.0.23 |
| MyBatisPlus | 提供连接数据库和快捷的增删改查 | 3.5.1 |
| SpringBoot-Configuration-processor | 配置处理器 定义的类和配置文件绑定一般没有提示,因此可以添加配置处理器,产生相对应的提示. | |
| SpringBoot-Starter-Web | 后端集成Tomcat MVC | 用于和前端连接 |
| SpringBoot-starter-test | Junit4单元测试前端在调用接口前后端先调用单元测试进行增删改查注意Junit4和5的问题注解@RunWith是否添加 | |
| Lombok | 实体类方法的快速生成 简化代码 | |
| mybatis-plus-generator | 代码生成器 | 3.5.1 |
| MyBatisX | MyBatisPlus插件直接生成mapper,实体类,service | |
| jjwt | token工具包 | 0.9.0 |
| fastjson | 阿里巴巴的 JSON 工具类 | 1.2.83 |
| hutool | hutool工具包(简化开发工具类) | [文档](https://hutool.cn/docs/#/) |
| knife4j-openapi2-spring-boot-starter | Knife4j 在线接口文档测试工具 | 4.0.0 |
| gson | 谷歌的 JSON 工具类 | 2.8.5 |
| Java-WebSocket | 讯飞星火 AI 配置 | 1.3.8 |
| okhttp | 讯飞星火 AI 配置 | 4.10.0 |
| okio | 讯飞星火 AI 配置 | 2.10.0 |
| jsoup | 简易爬虫工具 | 1.15.3 |
| guava | 谷歌工具类 | 30.1-jre |
| spring-boot-starter-data-redis | Redis 的 Starter | |
| broadscope-bailian-sdk-java | 阿里云 AI 模型 | 1.1.7 |
| spring-boot-starter-websocket | WebSocket 的 Starter | |
## ☀️架构
![](https://pic.yupi.icu/5563/202403061541028.png)
## ☀️核心设计
### 智能推荐功能
1用户输入自己的图书偏爱信息。
2前端发送 Axios 请求。
3后端先判断文本是否违法为空或者文本字数过长
4查看接口是否存在。
5查看 AI 接口调用次数是否充足。
6GuavaRateLimiter 进行单体限流,判断请求次数是否超出正常业务频次。
7给 AI 模型人工预设,并且查询数据库中的书籍列表进行拼接。
8查询 AI 模型与该用户最近的五条历史记录,用于上下文关联。
9FutureTask 同步调用获取 AI 结果,并设置超时时间(超时抛出异常)
10获取 AI 模型推荐信息后进行持久化,并且减少接口调用次数(判断是否成功)
11返回处理好的 AI 推荐信息给前端,并设置响应状态码为 200 即可。
### 智能分析功能
1用户输入分析目标、图标名称、选择图标类型、上传 Excel 文件,点击提交,发送 Axios 请求至后端。
2校验文件是否为空、名称是否过长、文件大小检验、文件后缀校验
3获取管理员 ID从接口信息表查询管理员 ID 拥有的接口,接口判空。
4判断 AI 接口调用次数是否足够
5GuavaRateLimiter 进行单体限流,判断请求次数是否超出正常业务频次。
6构造 AI 模型的提示词和角色
7构造用户输入拼接用户输入信息并用工具类将 Excel 转为 CSV 字符串数据。
8利用讯飞星火 AI 模型,传入调用者 ID 和输入参数,利用 FutureTask 同步获取,并设置超时时间(超时抛出异常)
9对 AI 生成结果进行判断,格式错误就返回前端错误信息,并提示重新调用(后续考虑 RabbitMQ 进行重试和补偿机制)
10将 AI 生成结果持久化到数据库,并更新接口调用次数(判断是否成功),动态给前端返回图标和数据结论。
## ☀️学完这个项目你能得到什么
1简单地调用 AI 模型(讯飞星火 | 阿里百炼)获取自定义文本内容。
2简单的 JWT 权限校验 ,利用后端拦截器进行登录校验。
3上传 Excel 文件Excel 文件转换为 CSV 数据AIGC 在线生成可视化图表。
4Jousp 批量爬取图书列表,可结合 SpringSchedule 定时任务执行。
5简单的增删改查系统前后端是如何联调协作的。
6前端路由懒加载、CDN 静态资源缓存优化、图片懒加载是如何实现的
7利用 Lodash 进行节流控制,尽量降低无效的恶意刷留言情况。
8利用自定义线程池和 FutureTask 进行超时请求处理。
9利用Google 的 GuavaRateLimiter 进行单体限流控制。
10定时任务结合 Redis 做一个缓存预热,加快查询效率,提高用户体验。
## ☀️项目简介
+ 主要使用Vue2和SpringBoot2实现
+ 项目权限控制分别为:用户借阅,图书管理员,系统管理员
+ 开发工具IDEA2022.1.3(真不推荐用eclipse开发IDEA项目可以导出为eclipse项目二者不影响但需要自己学教程)
+ [IDEA->Eclipse](https://blog.csdn.net/HD202202/article/details/128076400)
+ [Eclipse->IDEA](https://blog.csdn.net/q20010619/article/details/125096051)
+ 学校老师硬性要求软件的话,还是按要求来。可以先问一下是否可以选择其他软件开发。
+ 用户账号密码: 相思断红肠 123456
+ 图书管理员账号密码: admin 123456
+ 系统管理员账号密码: root 123456
+ [前端样式参考](https://gitee.com/mingyuefusu/tushuguanlixitong) 感谢原作者**明月复苏**
+ 遇到交互功能错误或者页面无法打开请用开发者工具F12查看请求和响应状态码情况当然可能小白不懂那也没关系可以加我**QQ909088445**。白天上课,晚上有空才能回答,感谢体谅!⭐⭐⭐
## ☀️项目详细介绍(亮点)
+ 本项目采用前后端分离的模式,前端构建页面,后端作数据接口,前端调用后端数据接口得到数据,重新渲染页面。
+ 后端已开启 CORS 跨域支持
+ API 认证使用 Token 认证
+ 前端在 Authorization 字段提供 Token 令牌
+ 使用 HTTP Status Code 表示状态
+ 数据返回格式使用 JSON
+ 后端采用权限拦截器进行权限校验,并检查登录情况
+ 添加全局异常处理机制,捕获异常,增强系统健壮性
+ 前端用 Echarts 可视化库实现了图书借阅的分析图标(折线图、饼图),并通过 Loading 配置提高加载体验。
+ 留言组件采用弹幕形式,贴合用户的喜好。
+ 引入 knife4j 依赖,使用 Swagger + Knife4j 自动生成 OpenAPI 规范的接口文档,前端可以在此基础上使用插件自动生成接口请求代码,降低前后端协作成本
+ 使用 ElementUI 组件库进行前端界面搭建,快速实现页面生成,并实现了前后端统一权限管理,多环境切换等能力。
+ 基于 MyBatis Plus 框架的 QueryWrapper 实现对 MySQL 数据库的灵活查询,并配合 MyBatisX 插件自动生成后端 CRUD 基础代码,减少重复工作。
+ 前端路由懒加载、CDN 静态资源缓存优化、图片懒加载效果。
### ⭐用户模块功能介绍
![](https://pic.yupi.icu/5563/202403021406815.png)
+ 图书查询功能分页构造器缓解数据过大压力后端可设置请求数防止爬虫请求数过大服务器负载过大。模糊查询进行字段搜索。表格均可导出PDF和EXCEL。
+ 读者规则功能:查询现有的借阅规则,借阅规则包括:借阅编号,可借阅图书数量,可借阅天数,可借阅图书馆,过期扣费/天。
+ 查看公告: 可以查询图书管理员发布的公告列表,文字滑动效果。
+ 个人信息: 可以查看个人的借阅证编号,借阅证姓名,规则编号,状态,可以修改个人账户的密码。
+ 借阅信息: 可以查看自身借阅过的图书记录和归还情况。
+ 违章信息: 可以查询自身归还的图书是否有违章信息。
+ 读者留言: 实现留言功能并以弹幕形式显示。
### ⭐图书管理员模块功能介绍
![](https://pic.yupi.icu/5563/202403021406227.png)
+ 借阅图书: 图书管理员输入借阅证号(用户)和要借的图书编号和当前的时间,点击借阅。
+ 归还图书: 输入图书编号查看图书是否逾期,并且可以设置违规信息,然后选择是否归还图书
+ 借书报表: 用于查询已经借阅并归还的书籍列表,同样使用分页构造器和模糊查询字段,显示借阅证编号,图书编号,借阅日期,截止日期,归还日期,违章信息,处理人。
+ 还书报表: 用于查询已经借阅但是还未归还的书籍列表,显示借阅证编号,图书编号,借阅日期,截止日期。
+ 发布公告: 可以查询当前发布的公告列表,并进行删除,修改,增加功能,分页构造器用于缓解数据量大的情况。
### ⭐系统管理员模块功能介绍
![](https://pic.yupi.icu/5563/202403021406443.png)
+ 书籍管理: 可以查询当前的所有图书显示图书编号图书昵称作者图书馆分类位置状态描述。可以进行添加修改删除图书。利用分页构造器实现批量查询。利用模糊查询实现图书搜索功能。利用插件实现PDF和EXCEL导出。
+ 书籍类型: 显示查询当前的所有图书类型,可以进行添加,修改,删除图书类型,利用分页构造器实现批量查询,缓解数据压力。
+ 借阅证管理: 可以查询当前的所有借阅证列表,也就是用户数量,可以进行添加,修改,删除操作。同样实现分页。
+ 借阅信息查询: 可以查询当前已经完成借阅和归还的记录显示借阅证号书籍编号借阅日期截止日期归还日期违章信息处理人。分页功能PDF和EXCEL导出。
+ 借阅规则管理: 可以查询当前所有的借阅规则,显示限制借阅天数,限制本数,限制图书馆,逾期费用,可以进行添加、删除、修改操作。
+ 图书管理员管理: 显示当前的图书管理员列表,显示账号,姓名,邮箱,可以进行添加、删除、修改操作。
+ 系统管理: 可以查询一个月内的借阅量以一周为时间间隔计算借阅量用Echarts实现折线图的展示。
## ☀️数据库表设计
### t_users表
| 列名 | 数据类型以及长度 | 备注 |
| ----------- | ---------------- | ------------------------------------------------- |
| user_id | int(11) | 主键 非空 自增 用户表的唯一标识 |
| username | varchar(32) | 用户名 非空 |
| password | varchar(32) | 密码(MD5加密) 非空 |
| card_name | varchar(10) | 真实姓名 非空 |
| card_number | Bigint(11) | 借阅证编号 固定 11位随机生成 非空(后文都改BigInt) |
| rule_number | int(11) | 规则编号 可以自定义 也就是权限功能 |
| status | int(1) | 1表示可用 0表示禁用 |
| create_time | datetime | 创建时间 Java注解 JsonFormatter |
| update_time | datetime | 更新时间 Java注解 JsonFormatter |
### t_admins表
| 列名 | 数据类型以及长度 | 备注 |
| ----------- | ---------------- | --------------------------------- |
| admin_id | int(11) | 主键 非空 自增 管理员表的唯一标识 |
| username | varchar(32) | 用户名 非空 |
| password | varchar(32) | 密码(MD5加密) 非空 |
| admin_name | varchar(10) | 管理员真实姓名 非空 |
| status | int(1) | 1表示可用 0表示禁用 |
| create_time | datetime | 创建时间 Java注解 JsonFormatter |
| update_time | datetime | 更新时间 Java注解 JsonFormatter |
### t_book_admins表
| 列名 | 数据类型以及长度 | 备注 |
| --------------- | ---------------- | ------------------------------- |
| book_admin_id | int(11) | 主键 非空 自增 管理表的唯一标识 |
| username | varchar(32) | 用户名 非空 |
| password | varchar(32) | 密码(MD5加密)非空 |
| book_admin_name | varchar(10) | 图书管理员真实姓名 非空 |
| status | int(1) | 1表示可用 0表示禁用 |
| email | varchar(255) | 电子邮箱 |
| create_time | datetime | 创建时间 Java注解 JsonFormatter |
| update_time | datetime | 更新时间 Java注解 JsonFormatter |
### t_books表
| 列名 | 数据类型以及长度 | 备注 |
| ---------------- | ---------------- | ------------------------------- |
| book_id | int(11) | 主键 自增 非空 图书表的唯一标识 |
| book_number | int(11) | 图书编号 非空 图书的唯一标识 |
| book_name | varchar(32) | 图书名称 非空 |
| book_author | varchar(32) | 图书作者 非空 |
| book_library | varchar(32) | 图书所在图书馆的名称 非空 |
| book_type | varchar(32) | 图书类别 非空 |
| book_location | varchar(32) | 图书位置 非空 |
| book_status | varchar(32) | 图书状态(未借出/已借出) |
| book_description | varchar(100) | 图书描述 |
| create_time | datetime | 创建时间 Java注解 JsonFormatter |
| update_time | datetime | 更新时间 Java注解 JsonFormatter |
### t_books_borrow表
| 列名 | 数据类型以及长度 | 备注 |
| ----------- | ---------------- | ------------------------------------------------------------ |
| borrow_id | int(11) | 主键 自增 非空 借阅表的唯一标识 |
| card_number | int(11) | 借阅证编号 固定 11位随机生成 非空 用户与图书关联的的唯一标识 |
| book_number | int(11) | 图书编号 非空 图书的唯一标识 |
| borrow_date | datetime | 借阅日期 Java注解 JsonFormatter |
| close_date | datetime | 截止日期 Java注解 JsonFormatter |
| return_date | datetime | 归还日期 Java注解 JsonFormatter |
| create_time | datetime | 创建时间 Java注解 JsonFormatter |
| update_time | datetime | 更新时间 Java注解 JsonFormatter |
### t_notice表
| 列名 | 数据类型以及长度 | 备注 |
| --------------- | ---------------- | ----------------------------------- |
| notice_id | int(11) | 主键 非空 自增 公告表记录的唯一标识 |
| notice_title | varchar(32) | 公告的题目 非空 |
| notice_content | varchar(255) | 公告的内容 非空 |
| notice_admin_id | int(11) | 发布公告的管理员的id |
| create_time | datetime | 创建时间 Java注解 JsonFormatter |
| update_time | datetime | 更新时间 Java注解 JsonFormatter |
### t_violation表
| 列名 | 数据类型以及长度 | 备注 |
| ------------------ | ---------------- | ----------------------------------- |
| violation_id | int(11) | 主键 非空 自增 违章表记录的唯一标识 |
| card_number | int(11) | 借阅证编号 固定 11位随机生成 非空 |
| book_number | int(11) | 图书编号 非空 图书的唯一标识 |
| borrow_date | datetime | 借阅日期 Java注解 JsonFormatter |
| close_date | datetime | 截止日期 Java注解 JsonFormatter |
| return_date | datetime | 归还日期 Java注解 JsonFormatter |
| violation_message | varchar(100) | 违章信息 非空 |
| violation_admin_id | int(11) | 违章信息管理员的id |
| create_time | datetime | 创建时间 Java注解 JsonFormatter |
| update_time | datetime | 更新时间 Java注解 JsonFormatter |
### t_comment表
| 列名 | 数据类型以及长度 | 备注 |
| --------------------- | ---------------- | ----------------------------------- |
| comment_id | int(11) | 主键 非空 自增 留言表记录的唯一标识 |
| comment_avatar | varchar(255) | 留言的头像 |
| comment_barrage_style | varchar(32) | 弹幕的高度 |
| comment_message | varchar(255) | 留言的内容 |
| comment_time | int(11) | 留言的时间(控制速度) |
| create_time | datetime | 创建时间 Java注解 JsonFormatter |
| update_time | datetime | 更新时间 Java注解 JsonFormatter |
### t_book_rule表
| 列名 | 数据类型以及长度 | 备注 |
| ------------------ | ---------------- | ------------------------------------- |
| rule_id | int(11) | 主键 非空 自增 借阅规则记录的唯一标识 |
| book_rule_id | int(11) | 借阅规则编号 非空 |
| book_days | int(11) | 借阅天数 非空 |
| book_limit_number | int(11) | 限制借阅的本数 非空 |
| book_limit_library | varchar(255) | 限制的图书馆 非空 |
| book_overdue_fee | double | 图书借阅逾期后每天费用 非空 |
| create_time | datetime | 创建时间 Java注解 JsonFormatter |
| update_time | datetime | 更新时间 Java注解 JsonFormatter |
### t_book_type表
| 列名 | 数据类型以及长度 | 备注 |
| ------------ | ---------------- | ------------------------------------- |
| type_id | int(11) | 主键 非空 自增 图书类别记录的唯一标识 |
| type_name | varchar(32) | 借阅类别的昵称 非空 |
| type_content | varchar(255) | 借阅类别的描述 非空 |
| create_time | datetime | 创建时间 Java注解 JsonFormatter |
| update_time | datetime | 更新时间 Java注解 JsonFormatter |
## 🐼功能演示图
### 用户模块功能图
**首页轮播图演示**
![](https://pic.yupi.icu/5563/202403021406581.png)
**图书查询演示**
![](https://pic.yupi.icu/5563/202403021406053.png)
**读者规则演示**
![](https://pic.yupi.icu/5563/202403021406571.png)
**查看公告演示**
![](https://pic.yupi.icu/5563/202403021406776.png)
**个人信息演示**
![](https://pic.yupi.icu/5563/202403021406779.png)
**借阅信息演示**
![](https://pic.yupi.icu/5563/202403021406890.png)
**违章信息演示**
![](https://pic.yupi.icu/5563/202403021406091.png)
**读者留言演示**
![](https://pic.yupi.icu/5563/202403021406261.png)
**智能推荐演示**
![](https://pic.yupi.icu/5563/202403021406234.png)
### 图书管理员功能图
**借阅图书演示**
![](https://pic.yupi.icu/5563/202403021406213.png)
**归还图书演示**
![](https://pic.yupi.icu/5563/202403021406604.png)
**借书报表演示**
![](https://pic.yupi.icu/5563/202403021406590.png)
**还书报表演示**
![](https://pic.yupi.icu/5563/202403021406562.png)
**发布公告演示**
![](https://pic.yupi.icu/5563/202403021406616.png)
### 系统管理员功能图
+ 由于篇幅受限,系统功能展示主要功能。
**系统管理演示**
![](https://pic.yupi.icu/5563/202403021406081.png)
![](https://pic.yupi.icu/5563/202403021406169.png)
**智能分析演示**
![](https://pic.yupi.icu/5563/202403021406245.png)
## 🐼部署项目
![](https://pic.yupi.icu/5563/202403021406282.png)
+ 可以下载ZIP压缩包或者使用克隆(Git clone)
+ 复制http或者ssh的链接github建议ssh,gittee都可以)
+ 在D盘新建一个文件夹点击进入该文件夹右键Git Bash Here
![](https://pic.yupi.icu/5563/202403021406715.png)
+ 还没有下载Git或者不会Git的建议先看基础教程30分钟左右)
+ 输入git init 初始化git项目 然后出现一个.git文件夹
+ 输入git remote add origin xxxxxx(xxx为刚刚复制的http或者ssh链接)
+ 输入git pull origin master 从远程代码托管仓库拉取代码
+ 成功拉取项目(前端后端都是如此)
+ 前端项目注意依赖下载使用npm install 或者 yarn install Vscode或者Webstorm)
+ 后端项目注意maven依赖下载IDEA(推荐)或者Ecplise)
+ 前端npm 镜像源建议淘宝镜像源后端maven镜像源推荐阿里云镜像源非必选但更换后下载快速)
## 🐼部署项目问题
+ 乱码问题 项目采用的UFT-8
+ 一般出现乱码就是UTF-8和GBK二者相反
+ 请百度IDEA乱码和Eclipse乱码问题(描述清楚即可)
+ 点击交互按钮,没有发生反应。
+ 很明显请求失败浏览器打开开发者工具Edge浏览器直接ctrl+shift+i,其他浏览器按F12
+ 查看红色的请求和响应状态码问题
+ 先阅读文档再进行问题的查询或者提问
+ 提问有技巧模糊的发言让高级架构师找BUG也无从下手
+ **QQ909088445**
+ 一般晚上在线,建议先自己寻找问题!!!
+ 开源免费, 定制化和调试项目付费。
## 🐼需求分析和设计
需求分析和设计文档,有(**付费**)需求的可以加 QQ909088445适合走毕设和课设的小伙伴图省事的可以找我。
![](https://pic.yupi.icu/5563/202403061545778.png)
## 🐼项目API接口文档
+ 接口文档篇幅过大
+ 本来想完全采用RESTFUL风格做到一半忘记了
+ 看清楚文档的基准地址
+ 要API后端接口文档详细内容和数据库结构+内容一起的,将前后端**star**⭐的截图加我QQ**909088445**发我即可领取~感谢支持
#### **数据库领取截图示例(Gitee&GitHub)**
![](https://pic.yupi.icu/5563/202403021406801.png)
![](https://pic.yupi.icu/5563/202403021406821.png)
![](https://pic.yupi.icu/5563/202403092029471.png)
![](https://pic.yupi.icu/5563/202403092029399.png)
## 🐷其他
+ 个人博客地址: https://luoye6.github.io/
+ 个人博客采用Hexo+Github托管
+ 采用butterfly主题可以实现定制化
+ 推荐有空闲时间的可以花1-2天搭建个人博客用于记录笔记。
## ☕请我喝咖啡
如果本项目对您有所帮助,不妨请作者我喝杯咖啡
<div><img src="https://pic.yupi.icu/5563/202312191854931.png" style="height:300px;width:300px"></img> <img src="https://pic.yupi.icu/5563/202312191859536.png" style="height:300px;width:300px"></img></div>
## **版本迭代**
### 2023-3-19
1.引入knife4j依赖使用 Swagger + Knife4j 自动生成 OpenAPI 规范的接口文档,前端可以在此基础上使用插件自动生成接口请求代码,降低前后端协作成本。
2.引入jsoup依赖可以自定义添加爬虫功能,可以批量添加图书并且是比较真实的数据。
3.添加事务管理器,可以进行用@Transactional指定异常类型回滚和事务传播行为
### 2023-4-13
1.手动在增加和删除逻辑较为复杂的数据库操作上,添加了@Transactional注解,遇到运行时异常直接回滚数据库,防止借书和还书出现逻辑错误。
2.修复11位图书编号无法借书的Bug,其原因是因为11位超出了Integer的2147483647(10位)。解决方法数据库改为BigInt,Java改为Long。
3.**注意**:不要随便删除用户和公告!!!会导致其他人体验的时候出现逻辑错误!!!请明白了项目逻辑再去做删除操作!!!感谢配合!!!
4.下一期准备优化图表的展示逾期图书后告警通知之类的功能感谢大家的支持我会继续维护和优化功能有Bug可以加我QQ或者提出issue,勿要恶意利用bug再次鸣谢。
5.劳动节准备录一期部署项目的视频会发到b站到时候会将部署讲清楚方便大家课设或者毕设的完成此项目有数据库表设计、API接口文档、内容功能介绍、亮点介绍唯一缺少的可能是数据流图、ER图之类的star的人多了我会添加上去。
### 2023-5-1
1.添加“系统管理员”权限的系统管理功能,**添加借书类型分析统计图(饼图)**采用Echarts。
2.优化请求在没有收到数据时的显示卡顿的情况,添加“加载中”状态,**使用v-loading**(ElementUI组件库)**优化用户人机交互体验**,在服务器调用接口缓慢的情况下,给予**良好的交互**。
3.轮播图优化:**压缩图片体积**另外使用Swiper的**懒加载**,实现图片加载中状态,然后图片完全加载完成后才显示图片,**优化用户体验过程**。
4.后端**新增自定义错误码枚举类**,可以自定义状态码进行返回,保留原有枚举类。
5.前端优化部分表格内容展示,当纵向内容过长,**设置了表格最大高度**,超出就会显示滑动窗口。优化表格列宽度,**提高表格美观度**。
6.**添加**书籍管理组件的**批量删除图书**功能,优化管理员体验,不用单个删除图书,**提高效率**。
7.Jmeter进行压力测试服务器接口在**100个用户并发**发送请求的情况下,**QPS达到50**以上。
### 2023-5-20
**后端更新情况**
1.~~防止前端抓包被获取明文密码前端输入密码进行md5加密混合盐值防止碰撞),后端直接与数据库加密后的密码比较,相等代表登录成功。提高系统**安全性**!~~。
2.整改Controller层**将业务代码全部放入Service层**由Controller调用Service服务并修改了@Transactional注解位置到业务层减少耦合度让Controller减少臃肿。做到对扩展开放对修改关闭。后续考虑运用**设计模式**进行优化代码和**多线程**知识提高在**高并发**下接口响应的速度。
3.对照阿里巴巴手册进行代码修改,将警告进行减少,代码更加**优雅、规范**。
4.**修复BUG** 借阅时间为空,造成服务器被击穿。归还日期为空,仍然显示借书成功。(解决方法:时间参数进行校验,判断是否为空)
5.**工具类增加情况**:SQLUtils(防止SQL注入)NetUtils(网络工具类)
**前端更新情况**
1.将路由加载方式,改为懒加载,利用懒加载可以有效分担首页加载压力,**减少首页加载用时**。
2.添加404页面当用户访问请求地址不存在的页面直接跳转到404页面**提高用户体验度**。
3.添加按钮的加载中状态,**优化人机交互**,提升用户体验度。修改按钮:登录按钮,其他按钮如果有需要可以自定义去修改,增加:loading="loading"即可。
**Bug修复情况**
1.11位图书编号可以借,但却**无法进行逾期检查**发现方法参数还是Integer,上次把借书和还书的改成Long了逾期查看还没改成Long,因此出现问题,现在已经修复。
### 2023-6-10
**前端更新情况**
1.在某些页面添加全屏功能按钮,**方便用户放大查看表格数据**。
2.增加了GitHub和Gitee的地址图标**方便进行项目拉取和克隆**。
3.读者留言组件,留言功能进行强化,防止无意义的数字、字母、空格出现在数据,后续考虑
4.读者留言组件,**利用lodash进行节流**5秒内只可发送一次网络请求防止恶意刷无效留言。
**后端更新情况**
1.后端添加利用EasyExcel进行图书的**批量导入功能**实现与实际生活中利用Excel存储一些图书数据的交互功能**提高导入效率**和爬虫功能效果相同都可以实现大数据量情况下的导入推荐利用EasyExcel进行批量导入时间会比爬虫会更快。
**Bug修复情况**
1.修改用户页面的修改密码功能,因为上次更新已经加了盐值,但是后端代码逻辑没有进行更改,本次修复"在修改密码后无法登录的情况",原因是因为后端没有加盐值,已修复。
2.修复系统管理员修改借阅证的密码然后就登录不上了原因跟第一条Bug是一样的因为后端的盐值没有进行添加已修复。
3.修复系统管理员在书籍管理功能时候,直接点击修改书籍,发现书籍的分类是错误的,因为前端只在添加书籍的对话框发了获取分类的请求,修改对话框的时候忘记添加了获取分类的请求,已修复。
### 2023-9
**前端更新情况**
1.增加**智能推荐页面**能够与AI进行交流**用户输入自己喜欢xxx类的书籍AI能够在现有数据库中进行分析**然后给用户作出推荐调用的是国内AI模型底层是OpenAI。
2.增加**智能分析页面**输入分析目标和图标类型和Excel文件AI生成分析结论和可视化图标大大提高效率**减少人力分析成本**。
3.增加系统管理员可以利用在前端**利用Excel文件批量上传图书**的功能(测试中),仅供参考。
**后端更新情况**
1.增加智能分析的接口和获取最近5条聊天记录的接口利用**线程池**和**Future**进行**超时请求处理**如果接口调用超过40秒直接返回错误信息。
2.利用Google的Guava中的RateLimiter进行限流控制**每秒钟只允许一个请求通过**,防止刷量行为。
### 2023-11
**后端更新情况**
1.将用户聊天的AI模型切换为阿里的通义千问Plus模型并且**支持多轮会话的历史记录****不再使用讯飞星火的AI模型**,但仍保留工具类。主要是为了可以更快的得到响应,而且阿里的**文档更加详细**,可以**定制话术**,在用户输入无关图书推荐的内容时候,直接**拒绝回答**。
2.添加一个 IncSyncDeleteAIMessage **定时任务**,每天将会**删除由于系统错误等原因AI回复失败**,导致内容为空的记录,并且会为这些用户**恢复接口的次数****后续可能会选择 RabbitMQ**,将失败的消息放入消息队列,然后**确保失败的消息被消费**。
3.登录加密由前端改到后端,由于前端可以被撞库,因此加密依然放到后端。**方案:**前端传输,用 HTTPS 进行密文加密,后端采用盐值+算法进行加密,数据库存密文。
4.将留言页面存放在 Redis 中,**减少数据库的 IO 查询**QPS 是原来的数百倍!
**前端更新情况**
1.将三个登录页面的背景图和头像改为存储在 assets 文件夹的 images 中,**主要是为使用项目的人考虑**,很多人不懂图床技术,我这边暂时将登录页面改成静态图。
2.权限切换的提示优化,**在图标上面现在有登录权限切换的文字样式**,提示用户有多个登录页面可以切换。
3.登录加密由前端改到后端,由于前端可以被撞库,因此加密依然放到后端。**方案**:前端传输,用 HTTPS 进行密文加密,后端采用盐值+算法进行加密,数据库存密文。
### 2024-3
**后端更新情况**
1为 Knife4J 添加 @ ApiOperation 注解,标注每个接口的作用,**方便开发者阅读和测试接口**。

14
babel.config.js Normal file
View File

@ -0,0 +1,14 @@
module.exports = {
"presets": [
"@vue/cli-plugin-babel/preset"
],
"plugins": [
[
"component",
{
"libraryName": "element-ui",
"styleLibraryName": "theme-chalk"
}
]
]
}

19
jsconfig.json Normal file
View File

@ -0,0 +1,19 @@
{
"compilerOptions": {
"target": "es5",
"module": "esnext",
"baseUrl": "./",
"moduleResolution": "node",
"paths": {
"@/*": [
"src/*"
]
},
"lib": [
"esnext",
"dom",
"dom.iterable",
"scripthost"
]
}
}

20149
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

40
package.json Normal file
View File

@ -0,0 +1,40 @@
{
"name": "book_manage_system",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"dev": "npm run serve",
"npm-build": "npm run build"
},
"dependencies": {
"axios": "^1.2.1",
"core-js": "^3.8.3",
"default-passive-events": "^2.0.0",
"echarts": "^5.4.1",
"element-ui": "^2.4.5",
"html2canvas": "^1.4.1",
"jspdf": "^2.5.1",
"lodash": "^4.17.21",
"nanoid": "^4.0.0",
"node-polyfill-webpack-plugin": "^2.0.1",
"nprogress": "^0.2.0",
"swiper": "^3.4.2",
"vue": "^2.6.14",
"vue-baberrage": "^3.2.4",
"vue-json-excel": "^0.3.0",
"vue-router": "^3.5.1"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~5.0.0",
"@vue/cli-plugin-router": "~5.0.0",
"@vue/cli-service": "~5.0.0",
"babel-plugin-component": "^1.1.1",
"less": "^4.1.3",
"less-loader": "^11.1.0",
"vue-cli-plugin-element": "^1.0.1",
"vue-particles": "^1.0.9",
"vue-template-compiler": "^2.6.14"
}
}

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

25
public/index.html Normal file
View File

@ -0,0 +1,25 @@
<!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">
<meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<!-- <link rel="stylesheet" href="../node_modules/swiper/dist/css/swiper.min.css">
<script src="../node_modules/swiper/dist/js/swiper.min.js"></script> -->
<title>基于ollama的图书推荐系统</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>

20
src/App.vue Normal file
View File

@ -0,0 +1,20 @@
<template>
<div id="app">
<!-- 路由占位符 -->
<router-view></router-view>
</div>
</template>
<script>
export default {
name: 'app',
}
</script>
<style>
</style>

111
src/assets/css/global.css Normal file
View File

@ -0,0 +1,111 @@
/* 全局样式表 */
* {
margin: 0px;
padding: 0px;
}
a {
text-decoration: none;
}
html,
body,
#app {
height: 100%;
margin: 0;
padding: 0;
min-width: 1366px;
}
/* body {
cursor:url(../images/konglong.ico),auto;
} */
ul {
list-style: none;
margin: 0px;
padding: 0px;
}
.el-breadcrumb {
margin-bottom: 15px;
font-size: 12px;
}
.el-card {
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.15) !important;
}
.el-table {
margin-top: 15px;
font-size: 12px;
}
.el-pagination {
margin-top: 15px;
}
.el-cascader-menu {
height: 400px;
}
.el-steps {
margin: 15px 0;
}
.el-step__title {
font-size: 13px;
}
.ql-editor {
min-height: 300px;
}
body {
position: relative;
/* cursor: url(https://cdn.jsdelivr.net/gh/sviptzk/HexoStaticFile@latest/Hexo/img/default.cur), default; */
}
.el-main {
margin: 0px;
padding: 0px;
}
.yibai {
margin-top: 100px;
}
.erbai {
margin-top: 120px;
}
.sanbai {
margin-top: 140px;
}
.sibai {
margin-top: 160px;
}
.wubai {
margin-top: 180px;
}
.liubai {
margin-top: 200px;
}
.qibai {
margin-top: 220px;
}
.babai {
margin-top: 240px;
}
.jiubai {
margin-top: 260px;
}
.yiqian {
margin-top: 300px;
}

539
src/assets/fonts/demo.css Normal file
View File

@ -0,0 +1,539 @@
/* Logo 字体 */
@font-face {
font-family: "iconfont logo";
src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834');
src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834#iefix') format('embedded-opentype'),
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.woff?t=1545807318834') format('woff'),
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.ttf?t=1545807318834') format('truetype'),
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.svg?t=1545807318834#iconfont') format('svg');
}
.logo {
font-family: "iconfont logo";
font-size: 160px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/* tabs */
.nav-tabs {
position: relative;
}
.nav-tabs .nav-more {
position: absolute;
right: 0;
bottom: 0;
height: 42px;
line-height: 42px;
color: #666;
}
#tabs {
border-bottom: 1px solid #eee;
}
#tabs li {
cursor: pointer;
width: 100px;
height: 40px;
line-height: 40px;
text-align: center;
font-size: 16px;
border-bottom: 2px solid transparent;
position: relative;
z-index: 1;
margin-bottom: -1px;
color: #666;
}
#tabs .active {
border-bottom-color: #f00;
color: #222;
}
.tab-container .content {
display: none;
}
/* 页面布局 */
.main {
padding: 30px 100px;
width: 960px;
margin: 0 auto;
}
.main .logo {
color: #333;
text-align: left;
margin-bottom: 30px;
line-height: 1;
height: 110px;
margin-top: -50px;
overflow: hidden;
*zoom: 1;
}
.main .logo a {
font-size: 160px;
color: #333;
}
.helps {
margin-top: 40px;
}
.helps pre {
padding: 20px;
margin: 10px 0;
border: solid 1px #e7e1cd;
background-color: #fffdef;
overflow: auto;
}
.icon_lists {
width: 100% !important;
overflow: hidden;
*zoom: 1;
}
.icon_lists li {
width: 100px;
margin-bottom: 10px;
margin-right: 20px;
text-align: center;
list-style: none !important;
cursor: default;
}
.icon_lists li .code-name {
line-height: 1.2;
}
.icon_lists .icon {
display: block;
height: 100px;
line-height: 100px;
font-size: 42px;
margin: 10px auto;
color: #333;
-webkit-transition: font-size 0.25s linear, width 0.25s linear;
-moz-transition: font-size 0.25s linear, width 0.25s linear;
transition: font-size 0.25s linear, width 0.25s linear;
}
.icon_lists .icon:hover {
font-size: 100px;
}
.icon_lists .svg-icon {
/* 通过设置 font-size 来改变图标大小 */
width: 1em;
/* 图标和文字相邻时,垂直对齐 */
vertical-align: -0.15em;
/* 通过设置 color 来改变 SVG 的颜色/fill */
fill: currentColor;
/* path stroke 溢出 viewBox 部分在 IE 下会显示
normalize.css 中也包含这行 */
overflow: hidden;
}
.icon_lists li .name,
.icon_lists li .code-name {
color: #666;
}
/* markdown 样式 */
.markdown {
color: #666;
font-size: 14px;
line-height: 1.8;
}
.highlight {
line-height: 1.5;
}
.markdown img {
vertical-align: middle;
max-width: 100%;
}
.markdown h1 {
color: #404040;
font-weight: 500;
line-height: 40px;
margin-bottom: 24px;
}
.markdown h2,
.markdown h3,
.markdown h4,
.markdown h5,
.markdown h6 {
color: #404040;
margin: 1.6em 0 0.6em 0;
font-weight: 500;
clear: both;
}
.markdown h1 {
font-size: 28px;
}
.markdown h2 {
font-size: 22px;
}
.markdown h3 {
font-size: 16px;
}
.markdown h4 {
font-size: 14px;
}
.markdown h5 {
font-size: 12px;
}
.markdown h6 {
font-size: 12px;
}
.markdown hr {
height: 1px;
border: 0;
background: #e9e9e9;
margin: 16px 0;
clear: both;
}
.markdown p {
margin: 1em 0;
}
.markdown>p,
.markdown>blockquote,
.markdown>.highlight,
.markdown>ol,
.markdown>ul {
width: 80%;
}
.markdown ul>li {
list-style: circle;
}
.markdown>ul li,
.markdown blockquote ul>li {
margin-left: 20px;
padding-left: 4px;
}
.markdown>ul li p,
.markdown>ol li p {
margin: 0.6em 0;
}
.markdown ol>li {
list-style: decimal;
}
.markdown>ol li,
.markdown blockquote ol>li {
margin-left: 20px;
padding-left: 4px;
}
.markdown code {
margin: 0 3px;
padding: 0 5px;
background: #eee;
border-radius: 3px;
}
.markdown strong,
.markdown b {
font-weight: 600;
}
.markdown>table {
border-collapse: collapse;
border-spacing: 0px;
empty-cells: show;
border: 1px solid #e9e9e9;
width: 95%;
margin-bottom: 24px;
}
.markdown>table th {
white-space: nowrap;
color: #333;
font-weight: 600;
}
.markdown>table th,
.markdown>table td {
border: 1px solid #e9e9e9;
padding: 8px 16px;
text-align: left;
}
.markdown>table th {
background: #F7F7F7;
}
.markdown blockquote {
font-size: 90%;
color: #999;
border-left: 4px solid #e9e9e9;
padding-left: 0.8em;
margin: 1em 0;
}
.markdown blockquote p {
margin: 0;
}
.markdown .anchor {
opacity: 0;
transition: opacity 0.3s ease;
margin-left: 8px;
}
.markdown .waiting {
color: #ccc;
}
.markdown h1:hover .anchor,
.markdown h2:hover .anchor,
.markdown h3:hover .anchor,
.markdown h4:hover .anchor,
.markdown h5:hover .anchor,
.markdown h6:hover .anchor {
opacity: 1;
display: inline-block;
}
.markdown>br,
.markdown>p>br {
clear: both;
}
.hljs {
display: block;
background: white;
padding: 0.5em;
color: #333333;
overflow-x: auto;
}
.hljs-comment,
.hljs-meta {
color: #969896;
}
.hljs-string,
.hljs-variable,
.hljs-template-variable,
.hljs-strong,
.hljs-emphasis,
.hljs-quote {
color: #df5000;
}
.hljs-keyword,
.hljs-selector-tag,
.hljs-type {
color: #a71d5d;
}
.hljs-literal,
.hljs-symbol,
.hljs-bullet,
.hljs-attribute {
color: #0086b3;
}
.hljs-section,
.hljs-name {
color: #63a35c;
}
.hljs-tag {
color: #333333;
}
.hljs-title,
.hljs-attr,
.hljs-selector-id,
.hljs-selector-class,
.hljs-selector-attr,
.hljs-selector-pseudo {
color: #795da3;
}
.hljs-addition {
color: #55a532;
background-color: #eaffea;
}
.hljs-deletion {
color: #bd2c00;
background-color: #ffecec;
}
.hljs-link {
text-decoration: underline;
}
/* 代码高亮 */
/* PrismJS 1.15.0
https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript */
/**
* prism.js default theme for JavaScript, CSS and HTML
* Based on dabblet (http://dabblet.com)
* @author Lea Verou
*/
code[class*="language-"],
pre[class*="language-"] {
color: black;
background: none;
text-shadow: 0 1px white;
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
word-wrap: normal;
line-height: 1.5;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
}
pre[class*="language-"]::-moz-selection,
pre[class*="language-"] ::-moz-selection,
code[class*="language-"]::-moz-selection,
code[class*="language-"] ::-moz-selection {
text-shadow: none;
background: #b3d4fc;
}
pre[class*="language-"]::selection,
pre[class*="language-"] ::selection,
code[class*="language-"]::selection,
code[class*="language-"] ::selection {
text-shadow: none;
background: #b3d4fc;
}
@media print {
code[class*="language-"],
pre[class*="language-"] {
text-shadow: none;
}
}
/* Code blocks */
pre[class*="language-"] {
padding: 1em;
margin: .5em 0;
overflow: auto;
}
:not(pre)>code[class*="language-"],
pre[class*="language-"] {
background: #f5f2f0;
}
/* Inline code */
:not(pre)>code[class*="language-"] {
padding: .1em;
border-radius: .3em;
white-space: normal;
}
.token.comment,
.token.prolog,
.token.doctype,
.token.cdata {
color: slategray;
}
.token.punctuation {
color: #999;
}
.namespace {
opacity: .7;
}
.token.property,
.token.tag,
.token.boolean,
.token.number,
.token.constant,
.token.symbol,
.token.deleted {
color: #905;
}
.token.selector,
.token.attr-name,
.token.string,
.token.char,
.token.builtin,
.token.inserted {
color: #690;
}
.token.operator,
.token.entity,
.token.url,
.language-css .token.string,
.style .token.string {
color: #9a6e3a;
background: hsla(0, 0%, 100%, .5);
}
.token.atrule,
.token.attr-value,
.token.keyword {
color: #07a;
}
.token.function,
.token.class-name {
color: #DD4A68;
}
.token.regex,
.token.important,
.token.variable {
color: #e90;
}
.token.important,
.token.bold {
font-weight: bold;
}
.token.italic {
font-style: italic;
}
.token.entity {
cursor: help;
}

View File

@ -0,0 +1,510 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>iconfont Demo</title>
<link rel="shortcut icon" href="//img.alicdn.com/imgextra/i4/O1CN01Z5paLz1O0zuCC7osS_!!6000000001644-55-tps-83-82.svg" type="image/x-icon"/>
<link rel="icon" type="image/svg+xml" href="//img.alicdn.com/imgextra/i4/O1CN01Z5paLz1O0zuCC7osS_!!6000000001644-55-tps-83-82.svg"/>
<link rel="stylesheet" href="https://g.alicdn.com/thx/cube/1.3.2/cube.min.css">
<link rel="stylesheet" href="demo.css">
<link rel="stylesheet" href="iconfont.css">
<script src="iconfont.js"></script>
<!-- jQuery -->
<script src="https://a1.alicdn.com/oss/uploads/2018/12/26/7bfddb60-08e8-11e9-9b04-53e73bb6408b.js"></script>
<!-- 代码高亮 -->
<script src="https://a1.alicdn.com/oss/uploads/2018/12/26/a3f714d0-08e6-11e9-8a15-ebf944d7534c.js"></script>
<style>
.main .logo {
margin-top: 0;
height: auto;
}
.main .logo a {
display: flex;
align-items: center;
}
.main .logo .sub-title {
margin-left: 0.5em;
font-size: 22px;
color: #fff;
background: linear-gradient(-45deg, #3967FF, #B500FE);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
</style>
</head>
<body>
<div class="main">
<h1 class="logo"><a href="https://www.iconfont.cn/" title="iconfont 首页" target="_blank">
<img width="200" src="https://img.alicdn.com/imgextra/i3/O1CN01Mn65HV1FfSEzR6DKv_!!6000000000514-55-tps-228-59.svg">
</a></h1>
<div class="nav-tabs">
<ul id="tabs" class="dib-box">
<li class="dib active"><span>Unicode</span></li>
<li class="dib"><span>Font class</span></li>
<li class="dib"><span>Symbol</span></li>
</ul>
<a href="https://www.iconfont.cn/manage/index?manage_type=myprojects&projectId=3820793" target="_blank" class="nav-more">查看项目</a>
</div>
<div class="tab-container">
<div class="content unicode" style="display: block;">
<ul class="icon_lists dib-box">
<li class="dib">
<span class="icon iconfont">&#xeacc;</span>
<div class="name">管理员认证</div>
<div class="code-name">&amp;#xeacc;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe602;</span>
<div class="name">好友</div>
<div class="code-name">&amp;#xe602;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe681;</span>
<div class="name">管理员</div>
<div class="code-name">&amp;#xe681;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe638;</span>
<div class="name">密码(关)</div>
<div class="code-name">&amp;#xe638;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe600;</span>
<div class="name">天猫公告</div>
<div class="code-name">&amp;#xe600;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe6ca;</span>
<div class="name">首页_fill</div>
<div class="code-name">&amp;#xe6ca;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe8d6;</span>
<div class="name">搜索小</div>
<div class="code-name">&amp;#xe8d6;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe618;</span>
<div class="name">图书期刊</div>
<div class="code-name">&amp;#xe618;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe614;</span>
<div class="name">留言</div>
<div class="code-name">&amp;#xe614;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe606;</span>
<div class="name">退出</div>
<div class="code-name">&amp;#xe606;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe601;</span>
<div class="name">个人信息</div>
<div class="code-name">&amp;#xe601;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe8c4;</span>
<div class="name">公告</div>
<div class="code-name">&amp;#xe8c4;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe6dc;</span>
<div class="name">规则设置</div>
<div class="code-name">&amp;#xe6dc;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe607;</span>
<div class="name">违章查询</div>
<div class="code-name">&amp;#xe607;</div>
</li>
</ul>
<div class="article markdown">
<h2 id="unicode-">Unicode 引用</h2>
<hr>
<p>Unicode 是字体在网页端最原始的应用方式,特点是:</p>
<ul>
<li>支持按字体的方式去动态调整图标大小,颜色等等。</li>
<li>默认情况下不支持多色,直接添加多色图标会自动去色。</li>
</ul>
<blockquote>
<p>注意:新版 iconfont 支持两种方式引用多色图标SVG symbol 引用方式和彩色字体图标模式。(使用彩色字体图标需要在「编辑项目」中开启「彩色」选项后并重新生成。)</p>
</blockquote>
<p>Unicode 使用步骤如下:</p>
<h3 id="-font-face">第一步:拷贝项目下面生成的 <code>@font-face</code></h3>
<pre><code class="language-css"
>@font-face {
font-family: 'iconfont';
src: url('iconfont.woff2?t=1672483284386') format('woff2'),
url('iconfont.woff?t=1672483284386') format('woff'),
url('iconfont.ttf?t=1672483284386') format('truetype');
}
</code></pre>
<h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3>
<pre><code class="language-css"
>.iconfont {
font-family: "iconfont" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
</code></pre>
<h3 id="-">第三步:挑选相应图标并获取字体编码,应用于页面</h3>
<pre>
<code class="language-html"
>&lt;span class="iconfont"&gt;&amp;#x33;&lt;/span&gt;
</code></pre>
<blockquote>
<p>"iconfont" 是你项目下的 font-family。可以通过编辑项目查看默认是 "iconfont"。</p>
</blockquote>
</div>
</div>
<div class="content font-class">
<ul class="icon_lists dib-box">
<li class="dib">
<span class="icon iconfont icon-guanliyuanrenzheng"></span>
<div class="name">
管理员认证
</div>
<div class="code-name">.icon-guanliyuanrenzheng
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-haoyou"></span>
<div class="name">
好友
</div>
<div class="code-name">.icon-haoyou
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-guanliyuan"></span>
<div class="name">
管理员
</div>
<div class="code-name">.icon-guanliyuan
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-tianchongxing-"></span>
<div class="name">
密码(关)
</div>
<div class="code-name">.icon-tianchongxing-
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-gonggao"></span>
<div class="name">
天猫公告
</div>
<div class="code-name">.icon-gonggao
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-shouyefill"></span>
<div class="name">
首页_fill
</div>
<div class="code-name">.icon-shouyefill
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-sousuoxiao"></span>
<div class="name">
搜索小
</div>
<div class="code-name">.icon-sousuoxiao
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-tushuqikan"></span>
<div class="name">
图书期刊
</div>
<div class="code-name">.icon-tushuqikan
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-liuyan"></span>
<div class="name">
留言
</div>
<div class="code-name">.icon-liuyan
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-tuichu"></span>
<div class="name">
退出
</div>
<div class="code-name">.icon-tuichu
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-gerenxinxi"></span>
<div class="name">
个人信息
</div>
<div class="code-name">.icon-gerenxinxi
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-gonggao1"></span>
<div class="name">
公告
</div>
<div class="code-name">.icon-gonggao1
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-guizeshezhi"></span>
<div class="name">
规则设置
</div>
<div class="code-name">.icon-guizeshezhi
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-weizhangchaxun"></span>
<div class="name">
违章查询
</div>
<div class="code-name">.icon-weizhangchaxun
</div>
</li>
</ul>
<div class="article markdown">
<h2 id="font-class-">font-class 引用</h2>
<hr>
<p>font-class 是 Unicode 使用方式的一种变种,主要是解决 Unicode 书写不直观,语意不明确的问题。</p>
<p>与 Unicode 使用方式相比,具有如下特点:</p>
<ul>
<li>相比于 Unicode 语意明确,书写更直观。可以很容易分辨这个 icon 是什么。</li>
<li>因为使用 class 来定义图标,所以当要替换图标时,只需要修改 class 里面的 Unicode 引用。</li>
</ul>
<p>使用步骤如下:</p>
<h3 id="-fontclass-">第一步:引入项目下面生成的 fontclass 代码:</h3>
<pre><code class="language-html">&lt;link rel="stylesheet" href="./iconfont.css"&gt;
</code></pre>
<h3 id="-">第二步:挑选相应图标并获取类名,应用于页面:</h3>
<pre><code class="language-html">&lt;span class="iconfont icon-xxx"&gt;&lt;/span&gt;
</code></pre>
<blockquote>
<p>"
iconfont" 是你项目下的 font-family。可以通过编辑项目查看默认是 "iconfont"。</p>
</blockquote>
</div>
</div>
<div class="content symbol">
<ul class="icon_lists dib-box">
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-guanliyuanrenzheng"></use>
</svg>
<div class="name">管理员认证</div>
<div class="code-name">#icon-guanliyuanrenzheng</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-haoyou"></use>
</svg>
<div class="name">好友</div>
<div class="code-name">#icon-haoyou</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-guanliyuan"></use>
</svg>
<div class="name">管理员</div>
<div class="code-name">#icon-guanliyuan</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-tianchongxing-"></use>
</svg>
<div class="name">密码(关)</div>
<div class="code-name">#icon-tianchongxing-</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-gonggao"></use>
</svg>
<div class="name">天猫公告</div>
<div class="code-name">#icon-gonggao</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-shouyefill"></use>
</svg>
<div class="name">首页_fill</div>
<div class="code-name">#icon-shouyefill</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-sousuoxiao"></use>
</svg>
<div class="name">搜索小</div>
<div class="code-name">#icon-sousuoxiao</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-tushuqikan"></use>
</svg>
<div class="name">图书期刊</div>
<div class="code-name">#icon-tushuqikan</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-liuyan"></use>
</svg>
<div class="name">留言</div>
<div class="code-name">#icon-liuyan</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-tuichu"></use>
</svg>
<div class="name">退出</div>
<div class="code-name">#icon-tuichu</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-gerenxinxi"></use>
</svg>
<div class="name">个人信息</div>
<div class="code-name">#icon-gerenxinxi</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-gonggao1"></use>
</svg>
<div class="name">公告</div>
<div class="code-name">#icon-gonggao1</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-guizeshezhi"></use>
</svg>
<div class="name">规则设置</div>
<div class="code-name">#icon-guizeshezhi</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-weizhangchaxun"></use>
</svg>
<div class="name">违章查询</div>
<div class="code-name">#icon-weizhangchaxun</div>
</li>
</ul>
<div class="article markdown">
<h2 id="symbol-">Symbol 引用</h2>
<hr>
<p>这是一种全新的使用方式,应该说这才是未来的主流,也是平台目前推荐的用法。相关介绍可以参考这篇<a href="">文章</a>
这种用法其实是做了一个 SVG 的集合,与另外两种相比具有如下特点:</p>
<ul>
<li>支持多色图标了,不再受单色限制。</li>
<li>通过一些技巧,支持像字体那样,通过 <code>font-size</code>, <code>color</code> 来调整样式。</li>
<li>兼容性较差,支持 IE9+,及现代浏览器。</li>
<li>浏览器渲染 SVG 的性能一般,还不如 png。</li>
</ul>
<p>使用步骤如下:</p>
<h3 id="-symbol-">第一步:引入项目下面生成的 symbol 代码:</h3>
<pre><code class="language-html">&lt;script src="./iconfont.js"&gt;&lt;/script&gt;
</code></pre>
<h3 id="-css-">第二步:加入通用 CSS 代码(引入一次就行):</h3>
<pre><code class="language-html">&lt;style&gt;
.icon {
width: 1em;
height: 1em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
&lt;/style&gt;
</code></pre>
<h3 id="-">第三步:挑选相应图标并获取类名,应用于页面:</h3>
<pre><code class="language-html">&lt;svg class="icon" aria-hidden="true"&gt;
&lt;use xlink:href="#icon-xxx"&gt;&lt;/use&gt;
&lt;/svg&gt;
</code></pre>
</div>
</div>
</div>
</div>
<script>
$(document).ready(function () {
$('.tab-container .content:first').show()
$('#tabs li').click(function (e) {
var tabContent = $('.tab-container .content')
var index = $(this).index()
if ($(this).hasClass('active')) {
return
} else {
$('#tabs li').removeClass('active')
$(this).addClass('active')
tabContent.hide().eq(index).fadeIn()
}
})
})
</script>
</body>
</html>

View File

@ -0,0 +1,80 @@
@font-face {
font-family: "iconfont"; /* Project id 3820793 */
src: url('iconfont.woff2?t=1672483284386') format('woff2'),
url('iconfont.woff?t=1672483284386') format('woff'),
url('iconfont.ttf?t=1672483284386') format('truetype');
}
.iconfont {
font-family: "iconfont" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.icon-guanliyuanrenzheng:before {
content: "\eacc";
font-size: 48px !important;
color: white !important;
}
.icon-haoyou:before {
content: "\e602";
font-size: 48px !important;
color: white !important;
}
.icon-guanliyuan:before {
content: "\e681";
font-size: 48px !important;
color: white !important;
}
.icon-tianchongxing-:before {
content: "\e638";
}
.icon-gonggao:before {
content: "\e600";
}
.icon-shouyefill:before {
content: "\e6ca";
}
.icon-sousuoxiao:before {
content: "\e8d6";
}
.icon-tushuqikan:before {
content: "\e618";
}
.icon-liuyan:before {
content: "\e614";
}
.icon-tuichu:before {
content: "\e606";
}
.icon-gerenxinxi:before {
content: "\e601";
}
.icon-gonggao1:before {
content: "\e8c4";
}
.icon-guizeshezhi:before {
content: "\e6dc";
}
.icon-weizhangchaxun:before {
content: "\e607";
}
.icon-zhinengfenxi:before {
content: "\e699";
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,108 @@
{
"id": "3820793",
"name": "BookManageIcons",
"font_family": "iconfont",
"css_prefix_text": "icon-",
"description": "图书管理系统的图标库",
"glyphs": [
{
"icon_id": "5387410",
"name": "管理员认证",
"font_class": "guanliyuanrenzheng",
"unicode": "eacc",
"unicode_decimal": 60108
},
{
"icon_id": "1260",
"name": "好友",
"font_class": "haoyou",
"unicode": "e602",
"unicode_decimal": 58882
},
{
"icon_id": "11810477",
"name": "管理员",
"font_class": "guanliyuan",
"unicode": "e681",
"unicode_decimal": 59009
},
{
"icon_id": "6446244",
"name": "密码(关)",
"font_class": "tianchongxing-",
"unicode": "e638",
"unicode_decimal": 58936
},
{
"icon_id": "54854",
"name": "天猫公告",
"font_class": "gonggao",
"unicode": "e600",
"unicode_decimal": 58880
},
{
"icon_id": "673798",
"name": "首页_fill",
"font_class": "shouyefill",
"unicode": "e6ca",
"unicode_decimal": 59082
},
{
"icon_id": "2076426",
"name": "搜索小",
"font_class": "sousuoxiao",
"unicode": "e8d6",
"unicode_decimal": 59606
},
{
"icon_id": "2129721",
"name": "图书期刊",
"font_class": "tushuqikan",
"unicode": "e618",
"unicode_decimal": 58904
},
{
"icon_id": "3267400",
"name": "留言",
"font_class": "liuyan",
"unicode": "e614",
"unicode_decimal": 58900
},
{
"icon_id": "7738011",
"name": "退出",
"font_class": "tuichu",
"unicode": "e606",
"unicode_decimal": 58886
},
{
"icon_id": "10359979",
"name": "个人信息",
"font_class": "gerenxinxi",
"unicode": "e601",
"unicode_decimal": 58881
},
{
"icon_id": "11372764",
"name": "公告",
"font_class": "gonggao1",
"unicode": "e8c4",
"unicode_decimal": 59588
},
{
"icon_id": "13584358",
"name": "规则设置",
"font_class": "guizeshezhi",
"unicode": "e6dc",
"unicode_decimal": 59100
},
{
"icon_id": "16102269",
"name": "违章查询",
"font_class": "weizhangchaxun",
"unicode": "e607",
"unicode_decimal": 58887
},
]
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
src/assets/images/404.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 372 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 448 KiB

BIN
src/assets/images/book1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 418 KiB

BIN
src/assets/images/book2.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 KiB

BIN
src/assets/images/book3.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 373 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 458 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 637 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -0,0 +1,16 @@
<template>
<div class="container">
</div>
</template>
<script>
</script>
<style>
.container {
width: 100%;
height: 100vh;
background: url(https://xxx.xiaobaitiao.icu/img/icu/202312211243626.png) no-repeat;
background-size: cover;
}
</style>

View File

@ -0,0 +1,84 @@
<template>
<div>
<!-- 面包屑导航区域 -->
<el-breadcrumb separator-class="el-icon-arrow-right">
<el-breadcrumb-item>系统管理</el-breadcrumb-item>
<el-breadcrumb-item>借阅量查询</el-breadcrumb-item>
</el-breadcrumb>
<div style="width: 100%; height: 500px">
<ChartLine ref="chart_line_one" />
</div>
<div style="width: 100%; height: 500px; margin-top: 50px">
<PieChart ref="chart_pie_one"></PieChart>
</div>
</div>
</template>
<script>
import ChartLine from "../Chart/ChartLint.vue";
import PieChart from "../Chart/PieChart.vue";
export default {
data() {
return {
name: "借阅量",
xData: ["", "2020-03", "2020-04", "2020-05"],
yData: [30, 132, 80, 134],
allData: {
xData: ["", "2020-03", "2020-04", "2020-05"],
yData: [30, 132, 80, 134],
},
pieData: [],
};
},
methods: {
// async getBorrowData() {
// console.log(res);
// },
},
// created() {
// this.getBorrowData();
// },
async mounted() {
// console.log(this.$refs);
const { data: res } = await this.$http.get("admin/get_borrowdata");
if (res.status !== 200) {
return this.$message.error(res.msg);
}
this.$message.success({
message: res.msg,
duration: 800,
});
const { data: res2 } = await this.$http.get(
"admin/get_borrowtype_statistics"
);
if (res2.status !== 200) {
return this.$message.error(res2.msg);
}
this.$message.success({
message: res2.msg,
duration: 800,
});
console.log(res2);
for (var element of res2.data) {
this.pieData.push({
name: element.bookTypes,
value: element.borrowNumbers,
});
}
this.allData.xData = res.data.borrowDates;
this.allData.yData = res.data.borrowNumber;
this.$refs.chart_line_one.initChart(
this.name,
this.allData.xData,
this.allData.yData
);
this.$refs.chart_pie_one.initChart(this.pieData);
},
components: {
ChartLine,
PieChart,
},
};
</script>
<style>
</style>

View File

@ -0,0 +1,427 @@
<template>
<div class="search_container">
<!-- 面包屑导航区域 -->
<el-breadcrumb separator-class="el-icon-arrow-right">
<el-breadcrumb-item>首页</el-breadcrumb-item>
<el-breadcrumb-item>图书管理员管理</el-breadcrumb-item>
</el-breadcrumb>
<el-card shadow="always">
<!-- 搜索内容和导出区域 -->
<el-row>
<el-col :span="4">
<el-button type="primary" @click="showAddDialog()">
<i class="el-icon-plus"></i> 添加管理员</el-button
>
</el-col>
<el-col :span="2" style="float: right">
<download-excel
class="export-excel-wrapper"
:data="tableData"
:fields="json_fields"
:header="title"
name="图书管理员.xls"
>
<!-- 上面可以自定义自己的样式还可以引用其他组件button -->
<el-button type="primary" class="el-icon-printer" size="mini"
>导出Excel</el-button
>
</download-excel>
</el-col>
<el-col :span="2" style="float: right">
<el-button
type="primary"
class="el-icon-printer"
size="mini"
@click="downLoad"
>导出PDF</el-button
>
</el-col>
<el-col :span="2" style="float: right">
<el-button type="success" class="el-icon-full-screen" size="mini" @click="fullScreen"
>全屏</el-button
>
</el-col>
</el-row>
<!-- 表格区域 -->
<el-table
:data="tableData"
border
style="width: 100%"
stripe
id="pdfDom"
:default-sort="{ prop: 'bookAdminId', order: 'ascending' }"
v-loading="loading"
element-loading-text="拼命加载中"
element-loading-spinner="el-icon-loading"
element-loading-background="rgba(0, 0, 0, 0.8)"
>
<el-table-column prop="bookAdminId" label="ID" sortable>
</el-table-column>
<el-table-column prop="username" label="账号"> </el-table-column>
<el-table-column prop="bookAdminName" label="姓名"> </el-table-column>
<el-table-column prop="email" label="邮箱"> </el-table-column>
<el-table-column label="操作">
<template slot-scope="scope">
<!-- 修改按钮 -->
<el-tooltip
effect="dark"
content="修改"
placement="top"
:enterable="false"
>
<el-button
type="primary"
icon="el-icon-edit"
size="mini"
@click="showEditDialog(scope.row.bookAdminId)"
></el-button
></el-tooltip>
<!-- 删除按钮 -->
<el-tooltip
effect="dark"
content="删除"
placement="top"
:enterable="false"
>
<el-button
type="danger"
icon="el-icon-delete"
size="mini"
@click="removeUserById(scope.row.bookAdminId)"
></el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<!-- 分页查询区域 -->
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="queryInfo.pageNum"
:page-sizes="[1, 2, 3, 4, 5]"
:page-size="queryInfo.pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="this.total"
>
</el-pagination>
<!-- 修改公告的对话框 -->
<el-dialog
title="修改管理员"
:visible.sync="editDialogVisible"
width="50%"
@close="editDialogClosed"
>
<el-form
:model="editForm"
ref="editFormRef"
:rules="editFormRules"
label-width="100px"
>
<el-form-item label="姓名" prop="bookAdminName">
<el-input v-model="editForm.bookAdminName"></el-input>
</el-form-item>
<el-form-item label="账号" prop="username">
<el-input v-model="editForm.username"></el-input>
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input
v-model="editForm.password"
type="password"
show-password
></el-input>
</el-form-item>
<el-form-item label="邮箱" prop="email">
<el-input v-model="editForm.email"></el-input>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="editDialogVisible = false"> </el-button>
<el-button type="primary" @click="updateBookAdmin"> </el-button>
</span>
</el-dialog>
<!-- 添加管理员的对话框 -->
<el-dialog
title="添加管理员"
:visible.sync="addDialogVisible"
width="50%"
@close="addDialogClosed"
>
<el-form
:model="addForm"
ref="addFormRef"
:rules="addFormRules"
label-width="100px"
>
<el-form-item label="姓名" prop="bookAdminName">
<el-input v-model="addForm.bookAdminName"></el-input>
</el-form-item>
<el-form-item label="账号" prop="username">
<el-input v-model="addForm.username"></el-input>
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input v-model="addForm.password"></el-input>
</el-form-item>
<el-form-item label="邮箱" prop="email">
<el-input v-model="addForm.email"></el-input>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="addDialogVisible = false"> </el-button>
<el-button type="primary" @click="addBookAdmin">添加管理员</el-button>
</span>
</el-dialog>
</el-card>
</div>
</template>
<script>
export default {
data() {
return {
tableData: [],
editDialogVisible: false,
editForm: {
username: "",
password: "",
email: "",
bookAdminName: "",
},
editFormRules: {
username: [
{ required: true, message: "请输入账号", trigger: "blur" },
{
min: 5,
max: 30,
message: "长度在5到30个字符",
trigger: "blur",
},
],
password: [
{ required: true, message: "请输入密码", trigger: "blur" },
{
min: 6,
max: 50,
message: "长度在6到50个字符",
trigger: "blur",
},
],
email: [
{
required: true,
message: "请输入正确的邮箱",
trigger: "blur",
type: "email",
},
{
min: 6,
max: 50,
message: "长度在6到50个字符",
trigger: "blur",
},
],
bookAdminName: [
{ required: true, message: "请输入姓名", trigger: "blur" },
],
},
addDialogVisible: false,
addForm: {
username: "",
password: "",
email: "",
bookAdminName: "",
},
addFormRules: {
username: [
{ required: true, message: "请输入账号", trigger: "blur" },
{
min: 5,
max: 30,
message: "长度在5到30个字符",
trigger: "blur",
},
],
password: [
{ required: true, message: "请输入密码", trigger: "blur" },
{
min: 6,
max: 50,
message: "长度在6到50个字符",
trigger: "blur",
},
],
email: [
{
required: true,
message: "请输入正确的邮箱",
trigger: "blur",
type: "email",
},
{
min: 6,
max: 50,
message: "长度在6到50个字符",
trigger: "blur",
},
],
bookAdminName: [
{ required: true, message: "请输入姓名", trigger: "blur" },
],
},
queryInfo: {
pageNum: 1,
pageSize: 5,
},
total: 0,
title: "图书管理员",
json_fields: {
图书管理员编号: "bookAdminId",
用户名: "username",
姓名: "bookAdminName",
邮箱: "email",
},
loading:true
};
},
methods: {
handleSizeChange(val) {
this.queryInfo.pageSize = val;
this.getBookAdminList();
},
handleCurrentChange(val) {
this.queryInfo.pageNum = val;
this.getBookAdminList();
},
//,
async showEditDialog(id) {
const { data: res } = await this.$http.get("admin/get_bookadmin/" + id);
console.log(res);
if (res.status !== 200) {
return this.$message.error(res.msg);
}
this.editForm = res.data;
//
this.editDialogVisible = true;
},
//
editDialogClosed() {
this.$refs.editFormRef.resetFields();
},
//
async removeUserById(id) {
//
const confirmResult = await this.$confirm(
"此操作将永久删除该公告, 是否继续?",
"提示",
{
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}
).catch((error) => {
return error;
});
//confirm
//cancel
// console.log(confirmResult);
if (confirmResult !== "confirm") {
return this.$message.info("已经取消删除");
}
//axios,
const { data: res } = await this.$http.delete(
"admin/delete_bookadmin/" + id
);
if (res.status !== 200) {
return this.$message.error(res.msg);
}
this.$message.success({
message: res.msg,
duration: 1000,
});
this.getBookAdminList();
},
//
addDialogClosed() {
this.$refs.addFormRef.resetFields();
},
//visibletrue
showAddDialog() {
this.addDialogVisible = true;
},
//
async getBookAdminList() {
this.loading = true;
const { data: res } = await this.$http.post(
"admin/get_bookadminlist",
this.queryInfo
);
// console.log(res);
if (res.status !== 200) {
this.loading = false;
return this.$message.error(res.msg);
}
this.$message.success({
message: res.msg,
duration: 1000,
});
this.tableData = res.data.records;
this.total = parseInt(res.data.total);
this.loading = false;
},
async addBookAdmin() {
const { data: res } = await this.$http.post(
"admin/add_bookadmin",
this.addForm
);
// console.log(res);
if (res.status !== 200) {
return this.$message.error(res.msg);
}
this.$message.success({
message: res.msg,
duration: 1500,
});
this.getBookAdminList();
this.addDialogVisible = false;
},
async updateBookAdmin() {
const { data: res } = await this.$http.put(
"admin/update_bookadmin",
this.editForm
);
// console.log(res);
if (res.status !== 200) {
return this.$message.error(res.msg);
}
this.$message.success({
message: res.msg,
duration: 1500,
});
this.getBookAdminList();
this.editDialogVisible = false;
},
downLoad() {
this.getPdf(this.title); //pdf
},
fullScreen(){
// Dom: (trueORfalse)
let full = document.fullscreenElement;
//
if(!full){
// requestFullscreen
document.documentElement.requestFullscreen();
}else{
// 退
document.exitFullscreen();
}
}
},
created() {
this.getBookAdminList();
},
};
</script>
<style>
</style>

View File

@ -0,0 +1,537 @@
<template>
<div class="search_container">
<!-- 面包屑导航区域 -->
<el-breadcrumb separator-class="el-icon-arrow-right">
<el-breadcrumb-item>首页</el-breadcrumb-item>
<el-breadcrumb-item>书籍管理</el-breadcrumb-item>
</el-breadcrumb>
<el-card shadow="always">
<!-- 搜索内容和导出区域 -->
<el-row :gutter="10">
<el-col :span="6">条件搜索:<el-select v-model="queryInfo.condition" filterable placeholder="请选择"
style="margin-left: 15px">
<el-option v-for="item in searchs" :key="item.value" :label="item.label" :value="item.value">
</el-option>
</el-select>
</el-col>
<el-col :span="4">
<el-input placeholder="请输入内容" v-model="queryInfo.query" class="input-with-select"
@keyup.enter.native="getBookList">
<el-button slot="append" icon="el-icon-search" @click="getBookList"></el-button> </el-input></el-col>
<el-col :span="2">
<el-button type="primary" @click="showAddDialog()" size="default">
<i class="el-icon-plus"></i> 添加书籍</el-button>
</el-col>
<el-col :span="3" style="float:right">
<el-upload class="upload-demo" ref="upload" name="files" action="http://localhost:8889/api/admin/updown"
:on-preview="handlePreview" :on-remove="handleRemove" :headers="headers"
:file-list="fileList"
:on-success="onSuccess"
:auto-upload="false">
<el-button slot="trigger" size="mini" type="primary" title="从Excel批量导入图书">选取文件</el-button>
<el-button size="mini" type="success" @click="submitUpload" style="margin-left: 5px;">上传</el-button>
</el-upload>
</el-col>
<el-col :span="2" style="float: right">
<download-excel class="export-excel-wrapper" :data="tableData" :fields="json_fields" :header="title"
name="书籍管理.xls">
<!-- 上面可以自定义自己的样式还可以引用其他组件button -->
<el-button type="primary" class="el-icon-printer" size="mini">导出Excel</el-button>
</download-excel>
</el-col>
<el-col :span="2" style="float: right">
<el-button type="primary" class="el-icon-printer" size="mini" @click="downLoad">导出PDF</el-button>
</el-col>
<el-col :span="2" style="float: right">
<el-button type="warning" @click="removeBatch()" size="mini">
<i class="el-icon-delete"></i>批量删除</el-button>
</el-col>
<el-col :span="2" style="float: right">
<el-button type="success" class="el-icon-full-screen" size="mini" @click="fullScreen">全屏</el-button>
</el-col>
</el-row>
<!-- 表格区域 -->
<el-table :data="tableData" border height="520" style="width: 100%" stripe id="pdfDom"
:default-sort="{ prop: 'bookNumber', order: 'ascending' }" v-loading="loading" element-loading-text="拼命加载中"
element-loading-spinner="el-icon-loading" element-loading-background="rgba(0, 0, 0, 0.8)"
@selection-change="handleSelectionChange">
<el-table-column type="selection" width="55"> </el-table-column>
<el-table-column type="index"></el-table-column>
<el-table-column prop="bookNumber" label="图书编号" sortable>
</el-table-column>
<el-table-column prop="bookName" label="书名" width="80px">
</el-table-column>
<el-table-column prop="bookAuthor" label="作者" width="80px">
</el-table-column>
<el-table-column prop="bookLibrary" label="图书馆" width="80px">
</el-table-column>
<el-table-column prop="bookType" label="分类" width="80px">
</el-table-column>
<el-table-column prop="bookLocation" label="位置" sortable width="80px">
</el-table-column>
<el-table-column prop="bookStatus" label="状态" sortable width="80px">
</el-table-column>
<el-table-column prop="bookDescription" label="描述" width="400px">
</el-table-column>
<el-table-column label="操作">
<template slot-scope="scope">
<!-- 修改按钮 -->
<el-tooltip effect="dark" content="修改" placement="top" :enterable="false">
<el-button type="primary" icon="el-icon-edit" size="mini"
@click="showEditDialog(scope.row.bookId)"></el-button></el-tooltip>
<!-- 删除按钮 -->
<el-tooltip effect="dark" content="删除" placement="top" :enterable="false">
<el-button type="danger" icon="el-icon-delete" size="mini"
@click="removeUserById(scope.row.bookId)"></el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<!-- 分页查询区域 -->
<el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange"
:current-page="queryInfo.pageNum" :page-sizes="[1, 2, 3, 4, 5]" :page-size="queryInfo.pageSize"
layout="total, sizes, prev, pager, next, jumper" :total="this.total">
</el-pagination>
<!-- 修改规则的对话框 -->
<el-dialog title="修改书籍" :visible.sync="editDialogVisible" width="50%" @close="editDialogClosed">
<el-form :model="editForm" ref="editFormRef" :rules="editFormRules" label-width="120px">
<el-form-item label="书名" prop="bookName">
<el-input v-model="editForm.bookName"></el-input>
</el-form-item>
<el-form-item label="作者" prop="bookAuthor">
<el-input v-model="editForm.bookAuthor"></el-input>
</el-form-item>
<el-form-item label="图书馆">
<el-radio-group v-model="editForm.bookLibrary">
<el-radio-button label="南图"></el-radio-button>
<el-radio-button label="北图"></el-radio-button>
<el-radio-button label="教师之家"></el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item label="分类">
<el-select v-model="editForm.bookType" placeholder="请选择">
<el-option v-for="item in types" :key="item.typeId" :label="item.typeName" :value="item.typeId">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="位置" prop="bookLocation">
<el-input v-model="editForm.bookLocation"></el-input>
</el-form-item>
<el-form-item label="状态" prop="bookStatus">
<el-radio-group v-model="editForm.bookStatus">
<el-radio label="已借出">已借出</el-radio>
<el-radio label="未借出">未借出</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="书籍简介" prop="bookDescription">
<el-input type="textarea" v-model="editForm.bookDescription"></el-input>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="editDialogVisible = false"> </el-button>
<el-button type="primary" @click="updateBook"> </el-button>
</span>
</el-dialog>
<!-- 添加书籍的对话框 -->
<el-dialog title="添加书籍" :visible.sync="addDialogVisible" width="50%" @close="addDialogClosed">
<el-form :model="addForm" ref="addFormRef" :rules="addFormRules" label-width="120px">
<el-form-item label="书名" prop="bookName">
<el-input v-model="addForm.bookName"></el-input>
</el-form-item>
<el-form-item label="作者" prop="bookAuthor">
<el-input v-model="addForm.bookAuthor"></el-input>
</el-form-item>
<el-form-item label="图书馆">
<el-radio-group v-model="addForm.bookLibrary">
<el-radio-button label="南图"></el-radio-button>
<el-radio-button label="北图"></el-radio-button>
<el-radio-button label="教师之家"></el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item label="分类">
<el-select v-model="addForm.bookTypeNumber" placeholder="请选择">
<el-option v-for="item in types" :key="item.typeId" :label="item.typeName" :value="item.typeId">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="位置" prop="bookLocation">
<el-input v-model="addForm.bookLocation"></el-input>
</el-form-item>
<el-form-item label="状态" prop="bookStatus">
<el-radio-group v-model="addForm.bookStatus">
<el-radio label="已借出">已借出</el-radio>
<el-radio label="未借出">未借出</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="书籍简介" prop="bookDescription">
<el-input type="textarea" v-model="addForm.bookDescription"></el-input>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="addDialogVisible = false"> </el-button>
<el-button type="primary" @click="addBook">添加书籍</el-button>
</span>
</el-dialog>
</el-card>
</div>
</template>
<script>
export default {
data() {
return {
tableData: [],
editDialogVisible: false,
editForm: {
bookName: "",
bookAuthor: "",
bookLibrary: "南图",
bookType: "",
bookLocation: "",
bookStatus: "",
bookDescription: "",
},
editFormRules: {
bookName: [
{ required: true, message: "图书名称不能为空", trigger: "blur" },
],
bookAuthor: [
{ required: true, message: "图书作者不能为空", trigger: "blur" },
],
bookLocation: [
{ required: true, message: "图书位置不能为空", trigger: "blur" },
],
bookDescription: [
{ required: true, message: "图书介绍不能为空", trigger: "blur" },
],
},
addDialogVisible: false,
addForm: {
bookName: "",
bookAuthor: "",
bookLibrary: "南图",
bookTypeNumber: "",
bookLocation: "",
bookStatus: "",
bookDescription: "",
},
addFormRules: {
bookName: [
{ required: true, message: "图书名称不能为空", trigger: "blur" },
],
bookAuthor: [
{ required: true, message: "图书作者不能为空", trigger: "blur" },
],
bookLocation: [
{ required: true, message: "图书位置不能为空", trigger: "blur" },
],
bookDescription: [
{ required: true, message: "图书介绍不能为空", trigger: "blur" },
],
},
searchs: [
{
value: "book_number",
label: "图书编号",
},
{
value: "book_name",
label: "书名",
},
{
value: "book_author",
label: "作者",
},
{
value: "book_library",
label: "图书馆",
},
{
value: "book_location",
label: "位置",
},
{
value: "book_status",
label: "状态",
},
{
value: "book_description",
label: "描述",
},
],
queryInfo: {
pageNum: 1,
pageSize: 5,
condition: "",
query: "",
},
total: 0,
types: [
{
typeId: "童话",
typeName: "童话",
},
{
typeId: "文学",
typeName: "文学",
},
{
typeId: "散文",
typeName: "散文",
},
],
title: "书籍管理",
json_fields: {
图书编号: "bookNumber",
图书名称: "bookName",
作者: "bookAuthor",
图书馆: "bookLibrary",
分类: "bookType",
位置: "bookLocation",
状态: "bookStatus",
描述: "bookDescription",
},
loading: true,
multipleSelection: [],
fileList: []
};
},
methods: {
handleSizeChange(val) {
this.queryInfo.pageSize = val;
this.getBookList();
},
handleCurrentChange(val) {
this.queryInfo.pageNum = val;
this.getBookList();
},
handleSelectionChange(val) {
this.multipleSelection = val;
},
//,
async showEditDialog(id) {
// axios
const { data: res1 } = await this.$http.get("admin/get_type");
if (res1.status !== 200) {
return this.$message.error(res1.msg);
}
this.types = res1.data;
//
this.editDialogVisible = true;
const { data: res } = await this.$http.get(
"admin/get_bookinformation/" + id
);
// console.log(res);
if (res.status !== 200) {
return this.$message.error(res.msg);
}
this.editForm = res.data;
},
//
editDialogClosed() {
this.$refs.editFormRef.resetFields();
},
//
async removeUserById(id) {
//
const confirmResult = await this.$confirm(
"此操作将永久删除该书籍, 是否继续?",
"提示",
{
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}
).catch((error) => {
return error;
});
//confirm
//cancel
// console.log(confirmResult);
if (confirmResult !== "confirm") {
return this.$message.info("已经取消删除");
}
//axios,
const { data: res } = await this.$http.get("admin/delete_book/" + id);
// console.log(res);
if (res.status !== 200) {
return this.$message.error(res.msg);
}
this.$message.success(res.msg);
//
this.queryInfo.pageNum = 1;
this.queryInfo.pageSize = 5;
this.getBookList();
},
//
addDialogClosed() {
this.$refs.addFormRef.resetFields();
},
//visibletrue
async showAddDialog() {
// axios
const { data: res } = await this.$http.get("admin/get_type");
console.log(res);
if (res.status !== 200) {
return this.$message.error(res.msg);
}
this.types = res.data;
this.addDialogVisible = true;
},
async getBookList() {
this.loading = true;
const { data: res } = await this.$http.post(
"admin/get_booklist",
this.queryInfo
);
this.tableData = [];
// console.log(res);
if (res.status !== 200) {
this.total = 0;
this.loading = false;
return this.$message.error(res.msg);
}
this.$message.success({
message: res.msg,
duration: 1000,
});
this.tableData = res.data.records;
this.total = parseInt(res.data.total);
this.loading = false;
},
async addBook() {
const { data: res } = await this.$http.post(
"admin/add_book",
this.addForm
);
// console.log(res);
if (res.status !== 200) {
return this.$message.error(res.msg);
}
this.$message.success(res.msg);
this.getBookList();
this.addDialogVisible = false;
},
async updateBook() {
this.$refs.editFormRef.validate(async (valid) => {
// console.log(valid);
//
if (!valid) {
return;
}
// axios
const { data: res } = await this.$http.post(
"admin/update_book",
this.editForm
);
// console.log(res);
if (res.status !== 200) {
return this.$message.error(res.msg);
}
this.$message.success(res.msg);
this.getBookList();
this.editDialogVisible = false;
});
},
downLoad() {
this.getPdf(this.title); //pdf
},
fullScreen() {
// Dom: (trueORfalse)
let full = document.fullscreenElement;
//
if (!full) {
// requestFullscreen
document.documentElement.requestFullscreen();
} else {
// 退
document.exitFullscreen();
}
},
//
async removeBatch() {
//
const confirmResult = await this.$confirm(
"此操作将永久删除这些书籍, 是否继续?",
"提示",
{
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}
).catch((error) => {
return error;
});
//confirm
//cancel
// console.log(confirmResult);
if (confirmResult !== "confirm") {
return this.$message.info("已经取消删除");
}
// multipleSelection
if (this.multipleSelection.length == 0) {
return this.$message.error({
message: "选中项为空,无法进行批量删除",
duration: 1000
});
}
//axios,
// const { data: res } = await this.$http.get("admin/delete_book/" + id);
const { data: res } = await this.$http.delete("admin/delete_book_batch", {
data: this.multipleSelection
});
// console.log(res);
if (res.status !== 200) {
return this.$message.error({
message: res.msg,
duration: 1000
});
}
this.$message.success({
message: res.msg,
duration: 1000
});
//
this.queryInfo.pageNum = 1;
this.queryInfo.pageSize = 5;
this.getBookList();
},
submitUpload() {
console.log(this.$refs.upload.data);
this.$refs.upload.submit();
this.$message.success({
duration: 1500,
message: "Excel批量导入图书成功"
})
},
handleRemove(file, fileList) {
console.log(file,fileList);
},
handlePreview(file) {
console.log(file);
},
onSuccess(response, file, fileList){
// console.log(response);
// console.log(file);
// console.log(fileList);
}
},
created() {
this.getBookList();
},
computed: {
headers() {
return {
"Authorization": "Bearer " + window.sessionStorage.getItem('token')
};
}
}
};
</script>
<style></style>

View File

@ -0,0 +1,354 @@
<template>
<div class="search_container">
<!-- 面包屑导航区域 -->
<el-breadcrumb separator-class="el-icon-arrow-right">
<el-breadcrumb-item>首页</el-breadcrumb-item>
<el-breadcrumb-item>书籍类型</el-breadcrumb-item>
</el-breadcrumb>
<el-card shadow="always">
<!-- 搜索内容和导出区域 -->
<el-row>
<el-col :span="4">
<el-button type="primary" @click="showAddDialog()">
<i class="el-icon-plus"></i>添加分类</el-button
>
</el-col>
<el-col :span="2" style="float: right">
<download-excel
class="export-excel-wrapper"
:data="tableData"
:fields="json_fields"
:header="title"
name="书籍类型.xls"
>
<!-- 上面可以自定义自己的样式还可以引用其他组件button -->
<el-button type="primary" class="el-icon-printer" size="mini"
>导出Excel</el-button
>
</download-excel>
</el-col>
<el-col :span="2" style="float: right">
<el-button
type="primary"
class="el-icon-printer"
size="mini"
@click="downLoad"
>导出PDF</el-button
>
</el-col>
<el-col :span="2" style="float: right">
<el-button type="success" class="el-icon-full-screen" size="mini" @click="fullScreen"
>全屏</el-button
>
</el-col>
</el-row>
<!-- 表格区域 -->
<el-table :data="tableData" border style="width: 100%" stripe id="pdfDom" :default-sort = "{prop: 'typeId', order: 'ascending'}"
v-loading="loading"
element-loading-text="拼命加载中"
element-loading-spinner="el-icon-loading"
element-loading-background="rgba(0, 0, 0, 0.8)">
<el-table-column prop="typeId" label="ID" sortable> </el-table-column>
<el-table-column prop="typeName" label="分类名"> </el-table-column>
<el-table-column prop="typeContent" label="描述"> </el-table-column>
<el-table-column label="操作">
<template slot-scope="scope">
<!-- 修改按钮 -->
<el-tooltip
effect="dark"
content="修改"
placement="top"
:enterable="false"
>
<el-button
type="primary"
icon="el-icon-edit"
size="mini"
@click="showEditDialog(scope.row.typeId)"
></el-button
></el-tooltip>
<!-- 删除按钮 -->
<el-tooltip
effect="dark"
content="删除"
placement="top"
:enterable="false"
>
<el-button
type="danger"
icon="el-icon-delete"
size="mini"
@click="removeUserById(scope.row.typeId)"
></el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<!-- 分页查询区域 -->
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="queryInfo.pageNum"
:page-sizes="[1, 2, 3, 4, 5]"
:page-size="queryInfo.pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="this.total"
>
</el-pagination>
<!-- 修改规则的对话框 -->
<el-dialog
title="修改分类"
:visible.sync="editDialogVisible"
width="50%"
@close="editDialogClosed"
>
<el-form
:model="editForm"
ref="editFormRef"
:rules="editFormRules"
label-width="120px"
>
<el-form-item label="分类名" prop="typeName">
<el-input v-model="editForm.typeName"></el-input>
</el-form-item>
<el-form-item label="分类描述" prop="typeContent">
<el-input v-model="editForm.typeContent" type="textarea"></el-input>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="editDialogVisible = false"> </el-button>
<el-button type="primary" @click="updateBookType"
> </el-button
>
</span>
</el-dialog>
<!-- 添加规则的对话框 -->
<el-dialog
title="添加分类"
:visible.sync="addDialogVisible"
width="50%"
@close="addDialogClosed"
>
<el-form
:model="addForm"
ref="addFormRef"
:rules="addFormRules"
label-width="120px"
>
<el-form-item label="分类名" prop="typeName">
<el-input v-model="addForm.typeName"></el-input>
</el-form-item>
<el-form-item label="分类描述" prop="typeContent">
<el-input v-model="addForm.typeContent" type="textarea"></el-input>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="addDialogVisible = false"> </el-button>
<el-button type="primary" @click="addBookType">添加分类</el-button>
</span>
</el-dialog>
</el-card>
</div>
</template>
<script>
export default {
data() {
return {
tableData: [
{
typeId: 183,
typeName: "童话",
typeContent: "孩子的世界",
},
],
editDialogVisible: false,
editForm: {
typeName: "",
typeContent: "",
},
editFormRules: {
typeName: [
{ required: true, message: "请输入分类名", trigger: "blur" },
],
typeContent: [
{ required: true, message: "请输入分类描述", trigger: "blur" },
],
},
addDialogVisible: false,
addForm: {
typeName: "",
typeContent: "",
},
addFormRules: {
typeName: [
{ required: true, message: "请输入分类名", trigger: "blur" },
],
typeContent: [
{ required: true, message: "请输入分类描述", trigger: "blur" },
],
},
queryInfo: {
pageNum: 1,
pageSize: 5,
},
total: 0,
title: "书籍类型",
json_fields: {
类别编号: "typeId",
类别昵称: "typeName",
类别概述: "typeContent",
},
loading:true
};
},
methods: {
handleSizeChange(val) {
this.queryInfo.pageSize = val;
this.getBookTypeList();
},
handleCurrentChange(val) {
this.queryInfo.pageNum = val;
this.getBookTypeList();
},
//,
async showEditDialog(id) {
//
this.editDialogVisible = true;
const { data: res } = await this.$http.get("admin/get_booktype/" + id);
console.log(res);
if (res.status !== 200) {
return this.$message.error(res.msg);
}
this.editForm = res.data;
},
//
editDialogClosed() {
this.$refs.editFormRef.resetFields();
this.editForm.checkList = ["南图", "北图", "教师之家"];
},
//
async removeUserById(id) {
//
const confirmResult = await this.$confirm(
"此操作将永久删除该公告, 是否继续?",
"提示",
{
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}
).catch((error) => {
return error;
});
//confirm
//cancel
// console.log(confirmResult);
if (confirmResult !== "confirm") {
return this.$message.info("已经取消删除");
}
//axios,
const {data:res } = await this.$http.get('admin/delete_booktype/'+id)
console.log(res);
if (res.status !== 200) {
return this.$message.error(res.msg);
}
this.$message.success(res.msg)
//
this.queryInfo.pageNum= 1;
this.queryInfo.pageSize= 5;
this.getBookTypeList();
},
//
addDialogClosed() {
this.$refs.addFormRef.resetFields();
},
//visibletrue
showAddDialog() {
this.addDialogVisible = true;
},
async getBookTypeList() {
this.loading = true;
const { data: res } = await this.$http.post(
"admin/get_booktype_page",
this.queryInfo
);
// console.log(res);
if (res.status !== 200) {
this.loading = false;
return this.$message.error(res.msg);
}
this.$message.success(
{
message:res.msg,
duration:1000
}
)
this.tableData = res.data.records;
this.total = parseInt(res.data.total);
this.loading = false;
},
async addBookType() {
this.$refs.addFormRef.validate(async (valid) => {
// console.log(valid);
//
if (!valid) {
return;
}
// axios
const { data: res } = await this.$http.post(
"admin/add_booktype",
this.addForm
);
if (res.status !== 200) {
return this.$message.error(res.msg);
}
this.$message.success({
message: res.msg,
duration: 1500,
});
this.getBookTypeList();
this.addDialogVisible = false;
});
},
async updateBookType(){
const {data:res} = await this.$http.post('admin/update_booktype',this.editForm)
console.log(res);
if (res.status !== 200) {
return this.$message.error(res.msg);
}
this.$message.success({
message:res.msg,
duration:1500
})
this.getBookTypeList();
this.editDialogVisible = false;
},
downLoad() {
this.getPdf(this.title); //pdf
},
fullScreen(){
// Dom: (trueORfalse)
let full = document.fullscreenElement;
//
if(!full){
// requestFullscreen
document.documentElement.requestFullscreen();
}else{
// 退
document.exitFullscreen();
}
}
},
created() {
this.getBookTypeList();
},
};
</script>
<style>
</style>

View File

@ -0,0 +1,231 @@
<template>
<div class="intelligent_analysis">
<el-row :gutter="24">
<el-col :span="12">
<el-card header="智能分析">
<el-form
ref="form"
:model="form"
:rules="rules"
label-width="90px"
enctype="multipart/form-data"
>
<el-form-item label="分析目标: " prop="goal">
<el-input type="textarea" v-model="form.goal"></el-input>
</el-form-item>
<el-form-item label="图表名称: " prop="name">
<el-input v-model="form.name"></el-input>
</el-form-item>
<el-form-item label="图表类型: " prop="chartType">
<el-select v-model="form.chartType" placeholder="请选择图表类型">
<el-option label="折线图" value="折线图"></el-option>
<el-option label="柱状图" value="柱状图"></el-option>
<el-option label="堆叠图" value="堆叠图"></el-option>
<el-option label="饼图" value="饼图"></el-option>
<el-option label="雷达图" value="雷达图"></el-option>
</el-select>
</el-form-item>
<el-form-item label="原始数据: ">
<el-upload
class="upload-demo"
action="https://jsonplaceholder.typicode.com/posts/"
:on-remove="handleRemove"
:on-change="handleChange"
multiple
:limit="1"
:on-exceed="handleExceed"
name="file"
:file-list="fileList"
ref="upload"
:auto-upload="false"
>
<el-button size="small" type="success" icon="el-icon-upload2"
>上传Excel文件</el-button
>
<div slot="tip" class="el-upload__tip">
只能上传xls/xlsx文件且不超过1MB
</div>
</el-upload>
</el-form-item>
<el-form-item>
<el-button
type="primary"
@click="onSubmit"
v-loading="submitting"
:disabled="submitting"
>提交</el-button
>
<el-button @click="resetForm" :disabled="submitting"
>重置</el-button
>
</el-form-item>
</el-form>
</el-card>
</el-col>
<el-col :span="12">
<el-card header="分析结论" v-loading="submitting">
<div v-if="chart.genResult" style="white-space: pre-wrap">
{{ chart.genResult }}
<!-- 根据具体的需求来显示生成结果 -->
</div>
<div v-else>
<div v-if="!submitting">请先在左侧进行提交</div>
</div>
</el-card>
<el-divider></el-divider>
<el-card header="可视化图表" v-loading="submitting">
<div v-show="chart.genChart">
<div
ref="chartContainer"
style="width: 100%; height: 50vh"
class="chartContainer"
></div>
</div>
<div v-show="!chart.genChart">
<div v-if="!submitting" @click="createChart()">
请先在左侧进行提交
</div>
</div>
</el-card>
</el-col>
</el-row>
</div>
</template>
<script>
import * as echarts from "echarts";
export default {
data() {
return {
form: {
goal: "",
name: "",
chartType: "",
adminId: undefined,
},
file: {},
rules: {
goal: [
{ required: true, message: "请输入分析目标", trigger: "blur" },
{
min: 5,
max: 100,
message: "长度在 5 到 100 个字符",
trigger: "blur",
},
],
},
chart: {
genChart: "",
genResult: "",
chartId: "",
},
resultChart: {},
fileList: [],
submitting: false,
};
},
methods: {
//
handleExceed(files, fileList) {
this.$message.info({
message: "最多只能上传一个文件",
duration: 1500,
});
},
// ()
handleChange(file, fileList) {
// 1. xlsxlsx\
// xls xlsx
const acceptTypeList = [
"application/vnd.ms-excel",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
];
//
if (acceptTypeList.indexOf(file.raw.type) === -1) {
this.$message.info({
message: "只允许xls或者xlsx格式的文件上传",
duration: 1500,
});
this.handleRemove(file);
return;
}
// 2.1MB
const acceptSize = 1 * 1024 * 1024;
if (file.raw.size > acceptSize) {
this.$message.info({
message: "只允许文件大小在1MB之内的Excel文件",
duration: 1500,
});
this.handleRemove(file);
return;
}
this.file = file.raw;
},
async onSubmit() {
if (this.submitting) {
return;
}
this.submitting = true;
const formData = new FormData();
if (this.file) {
formData.append("file", this.file);
}
this.form.adminId = parseInt(window.sessionStorage.getItem("adminId"));
for (const key in this.form) {
formData.append(key, this.form[key]);
}
this.$message.info({
message: "接口调用花费时间在30秒左右AI模型暂时不稳定",
duration: 2500,
});
const { data: res } = await this.$http.post("/admin/gen", formData, {
headers: {
"Content-Type": "multipart/form-data",
},
});
if (res.status !== 200) {
this.submitting = false;
return this.$message.error({
message: res.msg,
duration: 1500,
});
}
this.chart = res.data;
this.resultChart = res.map.genChart;
this.createChart();
this.$message.success({
message: res.msg,
duration: 1500,
});
this.submitting = false;
},
createChart() {
const chartOption = this.resultChart;
if (!chartOption) {
return this.$message.error({
message: "图表代码解析错误",
duration: 1500,
});
}
this.$nextTick(() => {
const chart = echarts.init(this.$refs.chartContainer);
chart.setOption(chartOption);
});
},
resetForm() {
this.$refs.form.resetFields();
this.handleRemove();
},
handleRemove(file, fileList) {
this.fileList = fileList;
this.fileList = [];
this.file = {};
},
},
mounted() {},
};
</script>
<style scoped lang="less"></style>

View File

@ -0,0 +1,237 @@
<template>
<div class="login_container">
<div class="login_title">系统管理员登录界面</div>
<div class="login_box">
<!-- 头像区域 -->
<div class="avatar_box">
<img src="https://xxx.xiaobaitiao.icu/img/icu/202312211243634.jpg" alt=""/>
</div>
<!-- 登录表单区域 -->
<el-form
ref="loginFormRef"
:model="loginForm"
:rules="loginFormRules"
label-width="0px"
class="login_form"
>
<!-- 用户名 -->
<el-form-item prop="username">
<el-input
v-model.trim="loginForm.username"
prefix-icon="iconfont icon-gerenxinxi"
></el-input>
</el-form-item>
<!-- 密码 -->
<el-form-item prop="password">
<el-input
v-model="loginForm.password"
prefix-icon="iconfont icon-tianchongxing-"
type="password"
@keyup.enter.native="login"
:show-password="true"
></el-input>
</el-form-item>
<!-- 按钮区域 -->
<el-form-item class="btns">
<el-button type="primary" @click="login" :loading="loginLoading">登录</el-button>
<el-button type="info" @click="resetLoginForm">重置</el-button>
</el-form-item>
</el-form>
</div>
<vue-particles
class="login-bg"
color="#39AFFD"
:particleOpacity="0.7"
:particlesNumber="100"
shapeType="circle"
:particleSize="4"
linesColor="#8DD1FE"
:linesWidth="1"
:lineLinked="true"
:lineOpacity="0.4"
:linesDistance="150"
:moveSpeed="3"
:hoverEffect="true"
hoverMode="grab"
:clickEffect="true"
clickMode="push"
>
</vue-particles>
<div class="footer">
<span style="font-weight: bold;color:white;margin-bottom: 10px">
登录页面切换
</span>
<span><i class="iconfont icon-haoyou" @click="goUser"></i></span>
</div>
<div class="footer2">
</div>
</div>
</template>
<script>
export default {
data() {
return {
//
loginForm: {
username: "root",
password: "123456",
},
//
loginFormRules: {
username: [
{required: true, message: "用户名不能为空", trigger: "blur"},
{
min: 3,
max: 20,
message: "长度在 3 到 20 个字符",
trigger: "blur",
},
],
password: [
{required: true, message: "密码不能为空", trigger: "blur"},
{
min: 6,
max: 15,
message: "长度在 6 到 15 个字符",
trigger: "blur",
},
],
},
loginLoading: false
};
},
methods: {
resetLoginForm() {
this.$refs.loginFormRef.resetFields();
},
login() {
this.$refs.loginFormRef.validate(async (valid) => {
// console.log(valid);
//
if (!valid) {
return;
}
this.loginLoading = true;
const username = this.loginForm.username;
const password = this.loginForm.password;
//axios
const {data: res} = await this.$http.post(
"admin/login",
{
username,
password
}
);
if (res.status !== 200) {
this.loginLoading = false;
return this.$message.error(res.msg);
}
this.$message.success("登录成功");
this.loginLoading = false;
// console.log(res);
window.sessionStorage.setItem("token", res.map.token);
window.sessionStorage.setItem("adminId", res.map.id);
this.$router.push("/homeadmin");
});
},
goUser() {
this.$router.push("/login");
},
},
};
</script>
<style lang="less" scoped>
.footer2 {
position: absolute;
bottom: 0px;
left: 35%;
color: #ccc;
a {
color: #ccc;
}
}
.login_container {
// background-color: #2b4b6b;
background: url(https://xxx.xiaobaitiao.icu/img/icu/202312211236280.jpg) no-repeat 0px 0px;
background-size: cover;
height: 100%;
}
.login_box {
height: 300px;
width: 450px;
background-color: #fff;
border-radius: 3px;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
.avatar_box {
width: 130px;
height: 130px;
border: 1px solid #eee;
border-radius: 50%;
padding: 10px;
box-shadow: 0 0 10px #ddd;
position: absolute;
left: 50%;
transform: translate(-50%, -50%);
background-color: #fff;
img {
width: 100%;
height: 100%;
border-radius: 50%;
background-color: #eee;
}
}
}
.login_form {
position: absolute;
bottom: 0;
width: 100%;
padding: 0 20px;
box-sizing: border-box;
}
.btns {
display: flex;
justify-content: flex-end;
}
.login_title {
position: relative;
top: 5%;
font-size: 36px;
color: white;
text-align: center;
font-weight: 700;
//
letter-spacing: 10px;
}
.footer {
display: flex;
position: absolute;
flex-direction: column;
bottom: 0;
right: 0;
width: 100px;
height: 120px;
// background-color: pink;
span {
// width: 100%;
// background-color: red;
text-align: center;
}
}
</style>

View File

@ -0,0 +1,429 @@
<template>
<div class="search_container">
<!-- 面包屑导航区域 -->
<el-breadcrumb separator-class="el-icon-arrow-right">
<el-breadcrumb-item>首页</el-breadcrumb-item>
<el-breadcrumb-item>借阅证管理</el-breadcrumb-item>
</el-breadcrumb>
<el-card shadow="always">
<!-- 搜索内容和导出区域 -->
<el-row :gutter="10">
<el-col :span="6"
>条件搜索:<el-select
v-model="queryInfo.condition"
filterable
placeholder="请选择"
style="margin-left: 15px"
>
<el-option
v-for="item in searchs"
:key="item.value"
:label="item.label"
:value="item.value"
>
</el-option>
</el-select>
</el-col>
<el-col :span="4">
<el-input
placeholder="请输入内容"
v-model="queryInfo.query"
class="input-with-select"
@keyup.enter.native="getStatementList"
>
<el-button
slot="append"
icon="el-icon-search"
@click="getStatementList"
></el-button> </el-input
></el-col>
<el-col :span="4">
<el-button type="primary" @click="showAddDialog()">
<i class="el-icon-plus"></i> 添加借阅证</el-button
>
</el-col>
<el-col :span="2" style="float: right">
<download-excel
class="export-excel-wrapper"
:data="tableData"
:fields="json_fields"
:header="title"
name="借阅证管理.xls"
>
<!-- 上面可以自定义自己的样式还可以引用其他组件button -->
<el-button type="primary" class="el-icon-printer" size="mini"
>导出Excel</el-button
>
</download-excel>
</el-col>
<el-col :span="2" style="float: right">
<el-button
type="primary"
class="el-icon-printer"
size="mini"
@click="downLoad"
>导出PDF</el-button
>
</el-col>
<el-col :span="2" style="float: right">
<el-button type="success" class="el-icon-full-screen" size="mini" @click="fullScreen"
>全屏</el-button
>
</el-col>
</el-row>
<!-- 表格区域 -->
<el-table :data="tableData" border style="width: 100%" stripe id="pdfDom" :default-sort = "{prop: 'cardNumber', order: 'ascending'}"
v-loading="loading"
element-loading-text="拼命加载中"
element-loading-spinner="el-icon-loading"
element-loading-background="rgba(0, 0, 0, 0.8)">
<el-table-column prop="cardNumber" label="借阅证编号" sortable>
</el-table-column>
<el-table-column prop="username" label="用户名"> </el-table-column>
<el-table-column prop="ruleNumber" label="借阅规则" sortable> </el-table-column>
<el-table-column prop="status" label="状态" sortable> </el-table-column>
<el-table-column label="操作">
<template slot-scope="scope">
<!-- 修改按钮 -->
<el-tooltip
effect="dark"
content="修改"
placement="top"
:enterable="false"
>
<el-button
type="primary"
icon="el-icon-edit"
size="mini"
@click="showEditDialog(scope.row.userId)"
></el-button
></el-tooltip>
<!-- 删除按钮 -->
<el-tooltip
effect="dark"
content="删除"
placement="top"
:enterable="false"
>
<el-button
type="danger"
icon="el-icon-delete"
size="mini"
@click="removeUserById(scope.row.userId)"
></el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<!-- 分页查询区域 -->
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="queryInfo.pageNum"
:page-sizes="[1, 2, 3, 4, 5]"
:page-size="queryInfo.pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="this.total"
>
</el-pagination>
<!-- 修改规则的对话框 -->
<el-dialog
title="修改书籍"
:visible.sync="editDialogVisible"
width="50%"
@close="editDialogClosed"
>
<el-form
:model="editForm"
ref="editFormRef"
:rules="editFormRules"
label-width="120px"
>
<el-form-item label="账号" prop="username">
<el-input v-model="editForm.username"></el-input>
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input v-model="editForm.password" type="password"></el-input>
</el-form-item>
<el-form-item label="规则" prop="ruleNumber">
<el-select v-model="editForm.ruleNumber" placeholder="请选择">
<el-option
v-for="item in bookRuleIdLists"
:key="item.ruleId"
:label="item.bookRuleId"
:value="item.bookRuleId"
>
</el-option>
</el-select>
</el-form-item>
<el-form-item label="状态" prop="userStatus">
<el-radio-group v-model="editForm.userStatus">
<el-radio label="可用">可用</el-radio>
<el-radio label="挂失">挂失</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="editDialogVisible = false"> </el-button>
<el-button type="primary" @click="updateStatement"> </el-button>
</span>
</el-dialog>
<!-- 添加书籍的对话框 -->
<el-dialog
title="添加借书证"
:visible.sync="addDialogVisible"
width="50%"
@close="addDialogClosed"
>
<el-form
:model="addForm"
ref="addFormRef"
:rules="addFormRules"
label-width="120px"
>
<el-form-item label="账号" prop="username">
<el-input v-model="addForm.username"></el-input>
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input v-model="addForm.password"></el-input>
</el-form-item>
<el-form-item label="规则" prop="ruleNumber">
<el-select v-model="addForm.ruleNumber" placeholder="请选择">
<el-option
v-for="item in bookRuleIdLists"
:key="item.ruleId"
:label="item.bookRuleId"
:value="item.bookRuleId"
>
</el-option>
</el-select>
</el-form-item>
<el-form-item label="状态" prop="userStatus">
<el-radio-group v-model="addForm.userStatus">
<el-radio label="可用">可用</el-radio>
<el-radio label="禁用">禁用</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="addDialogVisible = false"> </el-button>
<el-button type="primary" @click="addStatement">添加借阅证</el-button>
</span>
</el-dialog>
</el-card>
</div>
</template>
<script>
export default {
data() {
return {
tableData: [],
editDialogVisible: false,
editForm: {
username: "",
password: "",
ruleNumber: "",
status: "",
userStatus: "",
},
editFormRules: {},
addDialogVisible: false,
addForm: {
username: "",
password: "",
ruleNumber: "",
userStatus: "",
},
addFormRules: {},
searchs: [
{
value: "card_number",
label: "借阅证编号",
},
{
value: "username",
label: "用户名",
},
{
value: "rule_number",
label: "借阅规则",
},
{
value: "status",
label: "状态",
},
],
bookRuleIdLists: [
{
ruleId: "18",
bookRuleId: "18",
},
{
ruleId: "357",
bookRuleId: "357",
},
],
queryInfo: {
pageNum: 1,
pageSize: 5,
condition: "",
query: "",
},
total: 0,
title: "借阅证管理",
json_fields: {
借阅证遍号: "cardNumber",
用户名: "username",
借阅规则: "ruleNumber",
状态: "status",
},
loading:true
};
},
methods: {
handleSizeChange(val) {
this.queryInfo.pageSize = val;
this.getStatementList();
},
handleCurrentChange(val) {
this.queryInfo.pageNum = val;
this.getStatementList();
},
//,
async showEditDialog(id) {
//
const { data: res } = await this.$http.get("admin/get_statement/" + id);
// console.log(res);
if (res.status !== 200) {
return this.$message.error(res.msg);
}
// axios
const { data: res2 } = await this.$http.get("user/get_rulelist");
this.bookRuleIdLists = res2.data;
this.editForm = res.data;
this.editDialogVisible = true;
},
//
editDialogClosed() {
this.$refs.editFormRef.resetFields();
this.editForm.checkList = ["南图", "北图", "教师之家"];
},
//
async removeUserById(id) {
//
const confirmResult = await this.$confirm(
"此操作将永久删除该书籍, 是否继续?",
"提示",
{
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}
).catch((error) => {
return error;
});
//confirm
//cancel
// console.log(confirmResult);
if (confirmResult !== "confirm") {
return this.$message.info("已经取消删除");
}
//axios,
const { data: res } = await this.$http.delete(
"admin/delete_statement/" + id
);
console.log(res);
if (res.status !== 200) {
return this.$message.error(res.msg);
}
this.$message.success(res.msg);
this.getStatementList();
},
//
addDialogClosed() {
this.$refs.addFormRef.resetFields();
},
//visibletrue
async showAddDialog() {
// axios
const { data: res } = await this.$http.get("user/get_rulelist");
this.bookRuleIdLists = res.data;
this.addDialogVisible = true;
},
async getStatementList() {
this.loading = true;
const { data: res } = await this.$http.post(
"admin/get_statementlist",
this.queryInfo
);
// console.log(res);
this.tableData = [];
if (res.status !== 200) {
this.total = 0;
this.loading = false;
return this.$message.error(res.msg);
}
this.$message.success({
message: res.msg,
duration: 1000,
});
this.tableData = res.data.records;
this.total = parseInt(res.data.total);
this.loading = false;
},
async addStatement() {
const { data: res } = await this.$http.post(
"admin/add_statement",
this.addForm
);
// console.log(res);
if (res.status !== 200) {
return this.$message.error(res.msg);
}
this.$message.success(res.msg);
this.addDialogVisible = false;
this.getStatementList();
},
async updateStatement() {
const { data: res } = await this.$http.post(
"admin/update_statement",
this.editForm
);
// console.log(res);
if (res.status !== 200) {
return this.$message.error(res.msg);
}
this.$message.success(res.msg);
this.getStatementList();
this.editDialogVisible = false;
},
downLoad() {
this.getPdf(this.title); //pdf
},
fullScreen(){
// Dom: (trueORfalse)
let full = document.fullscreenElement;
//
if(!full){
// requestFullscreen
document.documentElement.requestFullscreen();
}else{
// 退
document.exitFullscreen();
}
}
},
created() {
this.getStatementList();
},
};
</script>
<style>
</style>

View File

@ -0,0 +1,415 @@
<template>
<div class="search_container">
<!-- 面包屑导航区域 -->
<el-breadcrumb separator-class="el-icon-arrow-right">
<el-breadcrumb-item>首页</el-breadcrumb-item>
<el-breadcrumb-item>借阅规则管理</el-breadcrumb-item>
</el-breadcrumb>
<el-card shadow="always">
<!-- 搜索内容和导出区域 -->
<el-row>
<el-col :span="4">
<el-button type="primary" @click="showAddDialog()">
<i class="el-icon-plus"></i> 添加</el-button
>
</el-col>
<el-col :span="2" style="float: right">
<download-excel
class="export-excel-wrapper"
:data="tableData"
:fields="json_fields"
:header="title"
name="借阅规则.xls"
>
<!-- 上面可以自定义自己的样式还可以引用其他组件button -->
<el-button type="primary" class="el-icon-printer" size="mini"
>导出Excel</el-button
>
</download-excel>
</el-col>
<el-col :span="2" style="float: right">
<el-button
type="primary"
class="el-icon-printer"
size="mini"
@click="downLoad"
>导出PDF</el-button
>
</el-col>
<el-col :span="2" style="float: right">
<el-button type="success" class="el-icon-full-screen" size="mini" @click="fullScreen"
>全屏</el-button
>
</el-col>
</el-row>
<!-- 表格区域 -->
<el-table :data="tableData" border style="width: 100%" stripe id="pdfDom" :default-sort = "{prop: 'bookRuleId', order: 'ascending'}"
v-loading="loading"
element-loading-text="拼命加载中"
element-loading-spinner="el-icon-loading"
element-loading-background="rgba(0, 0, 0, 0.8)">
<el-table-column prop="bookRuleId" label="ID" sortable> </el-table-column>
<el-table-column prop="bookDays" label="限制天数" sortable> </el-table-column>
<el-table-column prop="bookLimitNumber" label="限制本数" sortable>
</el-table-column>
<el-table-column prop="bookLimitLibrary" label="限制图书馆">
</el-table-column>
<el-table-column prop="bookOverdueFee" label="逾期费用" sortable>
</el-table-column>
<el-table-column label="操作">
<template slot-scope="scope">
<!-- 修改按钮 -->
<el-tooltip
effect="dark"
content="修改"
placement="top"
:enterable="false"
>
<el-button
type="primary"
icon="el-icon-edit"
size="mini"
@click="showEditDialog(scope.row.ruleId)"
></el-button
></el-tooltip>
<!-- 删除按钮 -->
<el-tooltip
effect="dark"
content="删除"
placement="top"
:enterable="false"
>
<el-button
type="danger"
icon="el-icon-delete"
size="mini"
@click="removeUserById(scope.row.ruleId)"
></el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<!-- 分页查询区域 -->
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="queryInfo.pageNum"
:page-sizes="[1, 2, 3, 4, 5]"
:page-size="queryInfo.pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="this.total"
>
</el-pagination>
<!-- 修改规则的对话框 -->
<el-dialog
title="修改规则"
:visible.sync="editDialogVisible"
width="50%"
@close="editDialogClosed"
>
<el-form
:model="editForm"
ref="editFormRef"
:rules="editFormRules"
label-width="120px"
>
<el-form-item label="限制天数" prop="bookDays">
<el-input v-model="editForm.bookDays"></el-input>
</el-form-item>
<el-form-item label="限制数量" prop="bookLimitNumber">
<el-input v-model="editForm.bookLimitNumber"></el-input>
</el-form-item>
<el-form-item label="限制图书馆">
<el-checkbox-group v-model="editForm.checkList">
<el-checkbox label="南图"></el-checkbox>
<el-checkbox label="北图"></el-checkbox>
<el-checkbox label="教师之家"></el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item label="逾期每天费用" prop="bookOverdueFee">
<el-input v-model="editForm.bookOverdueFee"></el-input>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="editDialogVisible = false"> </el-button>
<el-button type="primary" @click="updateRule"
> </el-button
>
</span>
</el-dialog>
<!-- 添加规则的对话框 -->
<el-dialog
title="添加规则"
:visible.sync="addDialogVisible"
width="50%"
@close="addDialogClosed"
>
<el-form
:model="addForm"
ref="addFormRef"
:rules="addFormRules"
label-width="120px"
>
<el-form-item label="限制天数" prop="bookDays">
<el-input v-model="addForm.bookDays"></el-input>
</el-form-item>
<el-form-item label="限制数量" prop="bookLimitNumber">
<el-input v-model="addForm.bookLimitNumber"></el-input>
</el-form-item>
<el-form-item label="限制图书馆">
<el-checkbox-group v-model="addForm.checkList">
<el-checkbox label="南图"></el-checkbox>
<el-checkbox label="北图"></el-checkbox>
<el-checkbox label="教师之家"></el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item label="逾期每天费用" prop="bookOverdueFee">
<el-input v-model="addForm.bookOverdueFee"></el-input>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="addDialogVisible = false"> </el-button>
<el-button type="primary" @click="addRule">添加规则</el-button>
</span>
</el-dialog>
</el-card>
</div>
</template>
<script>
export default {
data() {
return {
tableData: [
{
bookRuleId: 183,
bookDays: 60,
bookLimitNumber: 1000,
bookLimitLibrary: "1、2、3",
bookOverdueFee: 3.0,
},
{
bookRuleId: 183,
bookDays: 60,
bookLimitNumber: 1000,
bookLimitLibrary: "1、2、3",
bookOverdueFee: 3.0,
},
{
bookRuleId: 183,
bookDays: 60,
bookLimitNumber: 1000,
bookLimitLibrary: "1、2、3",
bookOverdueFee: 3.0,
},
{
bookRuleId: 183,
bookDays: 60,
bookLimitNumber: 1000,
bookLimitLibrary: "1、2、3",
bookOverdueFee: 3.0,
},
],
editDialogVisible: false,
editForm: {
bookDays: "",
bookLimitNumber: "",
checkList: ["南图", "北图", "教师之家"],
bookOverdueFee: 0,
bookLimitLibrary: "",
},
editFormRules: {
bookLimitDays: [
{ required: true, message: "请输入限制天数", trigger: "blur" },
],
bookLimitNumber: [
{ required: true, message: "请输入限制数量", trigger: "blur" },
],
bookOverdueFee: [
{
required: true,
message: "请输入正确的逾期每天费用",
trigger: "blur",
},
],
},
addDialogVisible: false,
addForm: {
bookDays: "",
bookLimitNumber: "",
checkList: ["南图", "北图", "教师之家"],
bookOverdueFee: 0,
bookLimitLibrary: "",
},
addFormRules: {
bookLimitDays: [
{ required: true, message: "请输入限制天数", trigger: "blur" },
],
bookLimitNumber: [
{ required: true, message: "请输入限制数量", trigger: "blur" },
],
bookOverdueFee: [
{
required: true,
message: "请输入正确的逾期每天费用",
trigger: "blur",
},
],
},
queryInfo: {
pageNum: 1,
pageSize: 5,
},
total: 0,
title: "借阅规则",
json_fields: {
规则编号: "bookRuleId",
限制天数: "bookDays",
限制本数: "bookLimitNumber",
限制图书馆: "bookLimitLibrary",
逾期费用: "bookOverdueFee",
},
loading:true
};
},
methods: {
handleSizeChange(val) {
this.queryInfo.pageSize = val;
this.getRuleList();
},
handleCurrentChange(val) {
this.queryInfo.pageNum = val;
this.getRuleList();
},
//,
async showEditDialog(id) {
//
const {data:res} = await this.$http.get('admin/get_rule_ruleid/'+id)
if (res.status !== 200) {
return this.$message.error(res.msg);
}
this.editForm = res.data
this.editDialogVisible = true;
},
//
editDialogClosed() {
this.$refs.editFormRef.resetFields();
this.editForm.checkList = ["南图", "北图", "教师之家"];
},
//
async removeUserById(id) {
//
const confirmResult = await this.$confirm(
"此操作将永久删除该公告, 是否继续?",
"提示",
{
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}
).catch((error) => {
return error;
});
//confirm
//cancel
// console.log(confirmResult);
if (confirmResult !== "confirm") {
return this.$message.info("已经取消删除");
}
//axios,
const {data:res} = await this.$http.delete('admin/delete_rule/'+id);
if (res.status !== 200) {
return this.$message.error(res.msg);
}
this.$message.success({
message: res.msg,
duration: 1500,
});
this.getRuleList();
},
//
addDialogClosed() {
this.$refs.addFormRef.resetFields();
this.addForm.checkList = ["南图", "北图", "教师之家"];
},
//visibletrue
showAddDialog() {
this.addDialogVisible = true;
},
async getRuleList() {
this.loading = true;
const { data: res } = await this.$http.post(
"admin/get_rulelist_page",
this.queryInfo
);
if (res.status !== 200) {
this.loading = false;
return this.$message.error(res.msg);
}
this.$message.success({
message: res.msg,
duration: 1000,
});
this.tableData = res.data.records;
this.total = parseInt(res.data.total);
this.loading = false;
},
async addRule() {
const libraryList = this.addForm.checkList.join(',')
this.addForm.bookLimitLibrary = libraryList;
const { data: res } = await this.$http.post(
"admin/add_rule",
this.addForm
);
// console.log(res);
if (res.status !== 200) {
return this.$message.error(res.msg);
}
this.$message.success({
message: res.msg,
duration: 1500,
});
this.addDialogVisible = false;
this.getRuleList();
},
async updateRule(){
const {data:res} = await this.$http.put('admin/update_rule',this.editForm)
// console.log(res);
if (res.status !== 200) {
return this.$message.error(res.msg);
}
this.$message.success({
message: res.msg,
duration: 1500,
});
this.getRuleList();
this.editDialogVisible = false;
},
downLoad() {
this.getPdf(this.title); //pdf
},
fullScreen(){
// Dom: (trueORfalse)
let full = document.fullscreenElement;
//
if(!full){
// requestFullscreen
document.documentElement.requestFullscreen();
}else{
// 退
document.exitFullscreen();
}
}
},
created() {
this.getRuleList();
},
};
</script>
<style>
</style>

View File

@ -0,0 +1,203 @@
<template>
<div class="search_container">
<!-- 面包屑导航区域 -->
<el-breadcrumb separator-class="el-icon-arrow-right">
<el-breadcrumb-item>首页</el-breadcrumb-item>
<el-breadcrumb-item>借阅信息查询</el-breadcrumb-item>
</el-breadcrumb>
<el-card shadow="always">
<!-- 搜索内容和导出区域 -->
<el-row>
<el-col :span="6"
>条件搜索:<el-select
v-model="queryInfo.condition"
filterable
placeholder="请选择"
style="margin-left: 15px"
>
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value"
>
</el-option>
</el-select>
</el-col>
<el-col :span="4">
<el-input
placeholder="请输入内容"
v-model="queryInfo.query"
class="input-with-select"
@keyup.enter.native = "getBorrowStatement"
>
<el-button
slot="append"
icon="el-icon-search"
@click="getBorrowStatement"
></el-button> </el-input
></el-col>
<el-col :span="2" style="float: right">
<download-excel
class="export-excel-wrapper"
:data="tableData"
:fields="json_fields"
:header="title"
name="借阅信息查询.xls"
>
<!-- 上面可以自定义自己的样式还可以引用其他组件button -->
<el-button type="primary" class="el-icon-printer" size="mini"
>导出Excel</el-button
>
</download-excel>
</el-col>
<el-col :span="2" style="float: right">
<el-button
type="primary"
class="el-icon-printer"
size="mini"
@click="downLoad"
>导出PDF</el-button
>
</el-col>
<el-col :span="2" style="float: right">
<el-button type="success" class="el-icon-full-screen" size="mini" @click="fullScreen"
>全屏</el-button
>
</el-col>
</el-row>
<!-- 表格区域 -->
<el-table :data="tableData" border style="width: 100%" stripe id="pdfDom" :default-sort = "{prop: 'violationId', order: 'ascending'}"
v-loading="loading"
element-loading-text="拼命加载中"
element-loading-spinner="el-icon-loading"
element-loading-background="rgba(0, 0, 0, 0.8)">
<el-table-column prop="violationId" label="ID" sortable>
</el-table-column>
<el-table-column prop="cardNumber" label="借阅证号" sortable>
</el-table-column>
<el-table-column prop="bookNumber" label="书籍ID" sortable> </el-table-column>
<el-table-column prop="borrowDate" label="借阅时间" sortable> </el-table-column>
<el-table-column prop="closeDate" label="截止时间" sortable> </el-table-column>
<el-table-column prop="returnDate" label="归还时间" sortable> </el-table-column>
<el-table-column prop="violationMessage" label="违章信息"> </el-table-column>
<el-table-column prop="violationAdmin" label="处理人"> </el-table-column>
</el-table>
<!-- 分页查询区域 -->
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="queryInfo.pageNum"
:page-sizes="[1, 2, 3, 4,5]"
:page-size="queryInfo.pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="this.total">
</el-pagination>
</el-card>
</div>
</template>
<script>
export default {
data() {
return {
options: [
{
value: "card_number",
label: "借阅证号",
},
{
value: "book_number",
label: "书籍ID",
},
{
value: "borrow_date",
label: "借阅时间",
},
{
value: "close_date",
label: "截止时间",
},
{
value: "return_date",
label: "归还时间",
},
{
value: "violation_message",
label: "违章信息",
},
],
tableData: [],
queryInfo: {
pageNum: 1,
pageSize: 5,
condition: "",
query: "",
},
total: 0,
title: "借阅信息查询",
json_fields: {
ID: "violationId",
借阅证编号: "cardNumber",
书籍编号: "bookNumber",
借阅日期: "borrowDate",
截止日期: "closeDate",
归还日期:"returnDate",
违章信息:"violationMessage",
处理人:"violationAdmin"
},
loading:true
};
},
methods: {
handleSizeChange(val) {
this.queryInfo.pageSize = val;
this.getBorrowStatement();
},
handleCurrentChange(val) {
this.queryInfo.pageNum = val;
this.getBorrowStatement();
},
async getBorrowStatement(){
this.loading = true;
const {data:res} = await this.$http.post('bookadmin/get_borrow_statement',this.queryInfo)
// console.log(res);
this.tableData = [];
if (res.status !== 200) {
this.total = 0;
this.loading = false;
return this.$message.error(res.msg);
}
this.$message.success({
message: res.msg,
duration: 1000,
});
this.tableData = res.data.records;
this.total = parseInt(res.data.total);
this.loading = false;
},
downLoad() {
this.getPdf(this.title); //pdf
},
fullScreen(){
// Dom: (trueORfalse)
let full = document.fullscreenElement;
//
if(!full){
// requestFullscreen
document.documentElement.requestFullscreen();
}else{
// 退
document.exitFullscreen();
}
}
},
created(){
this.getBorrowStatement();
}
};
</script>
<style>
</style>

View File

@ -0,0 +1,163 @@
<template>
<div class="expire_container">
<div class="header">
<p>归还图书</p>
</div>
<div class="banner">
<div class="expireDays">
<p>还剩{{this.expireInfo.expireDays}}</p>
</div>
<div class="bookNumber">
<el-input v-model="expireInfo.bookNumber" :readonly="true">
<template slot="prepend">图书编号</template>
<el-button slot="prepend" icon="el-icon-collection"> </el-button>
</el-input>
</div>
<div class="closeDate">
<el-input v-model="expireInfo.closeDate" :readonly="true">
<template slot="prepend">截止日期</template>
<el-button slot="prepend" icon="el-icon-date"></el-button
></el-input>
</div>
<div class="violationMsg">
<el-input
v-model="returnInfo.violationMessage"
placeholder="若无违章信息,则不填"
>
<template slot="prepend">违章信息</template>
<el-button
slot="prepend"
icon="iconfont icon-weizhangchaxun"
></el-button
></el-input>
</div>
<div class="adminId">
<el-input v-model="expireInfo.bookAdminId" :readonly="true">
<template slot="prepend">管理员编号</template>
<el-button slot="prepend" icon="el-icon-s-custom"></el-button
></el-input>
</div>
<div class="returnDate">
<el-date-picker
v-model="returnInfo.returnDate"
type="datetime"
placeholder="选择归还日期时间"
default-time="12:00:00"
value-format="yyyy-MM-dd HH:mm:ss"
prefix-icon="el-icon-date"
>
</el-date-picker>
</div>
<div class="return_button">
<el-button type="warning" @click="returnBook">归还</el-button>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
returnInfo: {
returnDate: "",
violationMessage: "",
bookNumber:0,
violationAdminId:0
},
expireInfo:{
expireDays:47,
bookNumber: 192,
closeDate: "",
bookAdminId:0
}
};
},
methods: {
async getExpireInfo(){
//
const bookNumber = this.$route.query.bookNumber;
// axios
const {data:res} = await this.$http.get('bookadmin/query_expire/'+bookNumber);
// console.log(res);
if(res.status !== 200){
return this.$message.error(res.msg)
}
this.$message.success(
{
message:res.msg,
duration:1000
}
)
this.expireInfo = res.data;
const id = window.sessionStorage.getItem('bookAdminId')
this.expireInfo.bookAdminId = parseInt(id);
this.returnInfo.bookNumber = this.expireInfo.bookNumber;
},
async returnBook(){
//
if(this.returnInfo.returnDate===""){
this.$message.error({
message:"归还日期不能为空",
duration:1000
})
return;
}
this.returnInfo.violationAdminId = parseInt(window.sessionStorage.getItem('bookAdminId'))
const {data:res} = await this.$http.post('bookadmin/return_book',this.returnInfo)
// console.log(res);
if(res.status !== 200){
return this.$message.error(res.msg)
}
this.$message.success(
{
message:res.msg,
duration:1000
}
)
this.$router.push('/returnbook');
}
},
created(){
this.getExpireInfo();
}
};
</script>
<style lang="less" scoped>
.header {
text-align: center;
height: 100px;
// background-color: pink;
p {
font-size: 30px;
line-height: 100px;
}
}
.banner {
height: 680px;
// background-color:brown;
display: flex;
flex-direction: column;
align-items: center;
.expireDays {
margin-top: 20px;
width: 700px;
height: 100px;
border: 2px solid #ccc;
text-align: center;
p {
line-height: 100px;
font-size: 24px;
}
}
div:nth-child(n + 2) {
margin-top: 25px;
}
}
</style>

View File

@ -0,0 +1,104 @@
<template>
<div class="borrowbook_container">
<div class="header"><p>借阅图书</p></div>
<div class="banner">
<div class="card_number">
<el-input placeholder="请输入借阅证号" v-model.number="borrowInfo.cardNumber">
<el-button slot="prepend" icon="el-icon-notebook-2"></el-button>
</el-input>
</div>
<div class="book_number">
<el-input placeholder="请输入图书编号" v-model.number="borrowInfo.bookNumber">
<el-button slot="prepend" icon="el-icon-collection"></el-button
></el-input>
</div>
<div class="borrow_date">
<el-date-picker
v-model="borrowInfo.borrowDate"
type="datetime"
placeholder="选择日期时间"
default-time="12:00:00"
value-format="yyyy-MM-dd HH:mm:ss"
>
</el-date-picker>
</div>
<div class="borrow_button">
<el-button type="primary" @click="borrowBook">借阅</el-button>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
borrowInfo: {
cardNumber: "",
bookNumber: "",
borrowDate: "",
bookAdminId:0
},
};
},
methods: {
async borrowBook(){
// console.log(this.borrowInfo.borrowDate);
if(this.borrowInfo.borrowDate=== ""){
return this.$message.error({
message:"借阅时间不能为空",
duration:1000
})
}
this.borrowInfo.bookAdminId = parseInt(window.sessionStorage.getItem('bookAdminId'))
const {data:res} = await this.$http.post('bookadmin/borrow_book',this.borrowInfo)
if(res.status !== 200){
return this.$message.error(res.msg)
}
this.$message.success(res.msg)
//
this.borrowInfo.bookNumber = "";
this.borrowInfo.cardNumber = "";
this.borrowInfo.borrowDate = "";
}
},
};
</script>
<style lang="less" scoped>
.borrowbook_container {
display: flex;
flex-direction: column;
align-items: center;
.header {
width: 100%;
height: 100px;
// background-color: pink;
p {
color: black;
font-size: 30px;
text-align: center;
line-height: 60px;
}
}
.banner {
display: flex;
flex-direction: column;
align-items: center;
width: 60%;
height: 400px;
// background-color: brown;
div {
margin-top: 15px;
// margin-left: ;
}
.borrow_date {
margin-left: 8px;
}
.borrow_button {
margin-top: 30px;
}
}
}
</style>

View File

@ -0,0 +1,219 @@
<template>
<div class="search_container">
<!-- 面包屑导航区域 -->
<el-breadcrumb separator-class="el-icon-arrow-right">
<el-breadcrumb-item>首页</el-breadcrumb-item>
<el-breadcrumb-item>借书报表</el-breadcrumb-item>
</el-breadcrumb>
<el-card shadow="always">
<!-- 搜索内容和导出区域 -->
<el-row>
<el-col :span="6"
>条件搜索:<el-select
v-model="queryInfo.condition"
filterable
placeholder="请选择"
style="margin-left: 15px"
>
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value"
>
</el-option>
</el-select>
</el-col>
<el-col :span="4">
<el-input
placeholder="请输入内容"
v-model="queryInfo.query"
class="input-with-select"
@keyup.enter.native="getBorrowStatement"
>
<el-button
slot="append"
icon="el-icon-search"
@click="getBorrowStatement"
></el-button> </el-input
></el-col>
<el-col :span="2" style="float: right">
<download-excel
class="export-excel-wrapper"
:data="tableData"
:fields="json_fields"
:header="title"
name="借书报表.xls"
>
<!-- 上面可以自定义自己的样式还可以引用其他组件button -->
<el-button type="primary" class="el-icon-printer" size="mini"
>导出Excel</el-button
>
</download-excel>
</el-col>
<el-col :span="2" style="float: right">
<el-button
type="primary"
class="el-icon-printer"
size="mini"
@click="downLoad"
>导出PDF</el-button
>
</el-col>
<el-col :span="2" style="float: right">
<el-button type="success" class="el-icon-full-screen" size="mini" @click="fullScreen"
>全屏</el-button
>
</el-col>
</el-row>
<!-- 表格区域 -->
<el-table
:data="tableData"
border
style="width: 100%"
stripe
id="pdfDom"
:default-sort="{ prop: 'cardNumber', order: 'ascending' }"
v-loading="loading"
element-loading-text="拼命加载中"
element-loading-spinner="el-icon-loading"
element-loading-background="rgba(0, 0, 0, 0.8)"
>
<el-table-column prop="cardNumber" label="借阅证编号" sortable>
</el-table-column>
<el-table-column prop="bookNumber" label="图书编号" sortable>
</el-table-column>
<el-table-column prop="borrowDate" label="借阅日期" sortable>
</el-table-column>
<el-table-column prop="closeDate" label="截止日期" sortable>
</el-table-column>
<el-table-column prop="returnDate" label="归还日期" sortable>
</el-table-column>
<el-table-column prop="violationMessage" label="违章信息">
</el-table-column>
<el-table-column prop="violationAdmin" label="处理人">
</el-table-column>
</el-table>
<!-- 分页查询区域 -->
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="queryInfo.pageNum"
:page-sizes="[1, 2, 3, 4, 5]"
:page-size="queryInfo.pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="this.total"
>
</el-pagination>
</el-card>
</div>
</template>
<script>
export default {
data() {
return {
options: [
{
value: "card_number",
label: "借阅证号",
},
{
value: "book_number",
label: "图书编号",
},
{
value: "borrow_date",
label: "借阅日期",
},
{
value: "close_date",
label: "截止日期",
},
{
value: "return_date",
label: "归还日期",
},
{
value: "violation_message",
label: "违章信息",
},
],
tableData: [],
queryInfo: {
pageNum: 1,
pageSize: 5,
condition: "",
query: "",
},
total: 0,
title: "借书报表",
json_fields: {
借阅证编号: "cardNumber",
图书编号: "bookNumber",
借阅日期: "borrowDate",
截止日期: "closeDate",
归还日期: "returnDate",
违章信息: "violationMessage",
处理人: "violationAdmin",
},
loading:true
};
},
methods: {
handleSizeChange(val) {
this.queryInfo.pageSize = val;
this.getBorrowStatement();
},
handleCurrentChange(val) {
this.queryInfo.pageNum = val;
this.getBorrowStatement();
},
async getBorrowStatement() {
this.loading = true;
const { data: res } = await this.$http.post(
"bookadmin/get_borrow_statement",
this.queryInfo
);
// console.log(res);
this.tableData = [];
if (res.status !== 200) {
this.total = 0;
this.loading = false;
return this.$message.error(res.msg);
}
this.$message.success({
message: res.msg,
duration: 1000,
});
this.tableData = res.data.records;
this.total = parseInt(res.data.total);
this.loading = false;
},
downLoad() {
this.getPdf(this.title); //pdf
},
fullScreen(){
// Dom: (trueORfalse)
let full = document.fullscreenElement;
//
if(!full){
// requestFullscreen
document.documentElement.requestFullscreen();
}else{
// 退
document.exitFullscreen();
}
}
},
created() {
this.getBorrowStatement();
},
};
</script>
<style>
</style>

View File

@ -0,0 +1,248 @@
<template>
<div class="login_container">
<div class="login_title">图书管理员登录界面</div>
<div class="login_box">
<!-- 头像区域 -->
<div class="avatar_box">
<img
src="https://xxx.xiaobaitiao.icu/img/icu/202312211243634.jpg"
alt=""
/>
</div>
<!-- 登录表单区域 -->
<el-form
ref="loginFormRef"
:model="loginForm"
:rules="loginFormRules"
label-width="0px"
class="login_form"
>
<!-- 用户名 -->
<el-form-item prop="username">
<el-input
v-model.trim="loginForm.username"
prefix-icon="iconfont icon-gerenxinxi"
></el-input>
</el-form-item>
<!-- 密码 -->
<el-form-item prop="password">
<el-input
v-model="loginForm.password"
prefix-icon="iconfont icon-tianchongxing-"
type="password"
@keyup.enter.native="login"
:show-password="true"
></el-input>
</el-form-item>
<!-- 按钮区域 -->
<el-form-item class="btns">
<el-button type="primary" @click="login" :loading="loginLoading"
>登录
</el-button>
<el-button type="info" @click="resetLoginForm">重置</el-button>
</el-form-item>
</el-form>
</div>
<vue-particles
class="login-bg"
color="#39AFFD"
:particleOpacity="0.7"
:particlesNumber="100"
shapeType="circle"
:particleSize="4"
linesColor="#8DD1FE"
:linesWidth="1"
:lineLinked="true"
:lineOpacity="0.4"
:linesDistance="150"
:moveSpeed="3"
:hoverEffect="true"
hoverMode="grab"
:clickEffect="true"
clickMode="push"
>
</vue-particles>
<div class="footer">
<span style="font-weight: bold;color:white;">
登录页面切换
</span>
<span><i class="iconfont icon-haoyou" @click="goUser"></i></span>
<span>
<i class="iconfont icon-guanliyuanrenzheng" @click="goAdmin"></i>
</span>
</div>
<div class="footer2">
</div>
</div>
</template>
<script>
export default {
data() {
return {
//
loginForm: {
username: "admin",
password: "123456",
},
//
loginFormRules: {
username: [
{ required: true, message: "用户名不能为空", trigger: "blur" },
{
min: 3,
max: 20,
message: "长度在 3 到 20 个字符",
trigger: "blur",
},
],
password: [
{ required: true, message: "密码不能为空", trigger: "blur" },
{
min: 6,
max: 15,
message: "长度在 6 到 15 个字符",
trigger: "blur",
},
],
},
loginLoading: false,
};
},
methods: {
resetLoginForm() {
this.$refs.loginFormRef.resetFields();
},
login() {
this.$refs.loginFormRef.validate(async (valid) => {
// console.log(valid);
//
if (!valid) {
return;
}
this.loginLoading = true;
// md5
const username = this.loginForm.username;
const password = this.loginForm.password;
//axios
const { data: res } = await this.$http.post("bookadmin/login", {
username,
password,
});
// console.log(res);
if (res.status !== 200) {
this.loginLoading = false;
return this.$message.error(res.msg);
}
this.$message.success("登录成功");
this.loginLoading = false;
window.sessionStorage.setItem("token", res.map.token);
window.sessionStorage.setItem("bookAdminId", res.map.id);
this.$router.push("/homemange");
// window.sessionStorage.setItem("token", token);
// this.$router.push("/home"); //home
});
},
goUser() {
this.$router.push("/login");
},
goAdmin() {
this.$router.push("/loginadmin");
},
},
};
</script>
<style lang="less" scoped>
.footer2 {
position: absolute;
bottom: 0px;
left: 35%;
color: #ccc;
a {
color: #ccc;
}
}
.login_container {
// background-color: #2b4b6b;
background: url(https://xxx.xiaobaitiao.icu/img/icu/202312211236280.jpg) no-repeat
0px 0px;
background-size: cover;
height: 100%;
}
.login_box {
height: 300px;
width: 450px;
background-color: #fff;
border-radius: 3px;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
.avatar_box {
width: 130px;
height: 130px;
border: 1px solid #eee;
border-radius: 50%;
padding: 10px;
box-shadow: 0 0 10px #ddd;
position: absolute;
left: 50%;
transform: translate(-50%, -50%);
background-color: #fff;
img {
width: 100%;
height: 100%;
border-radius: 50%;
background-color: #eee;
}
}
}
.login_form {
position: absolute;
bottom: 0;
width: 100%;
padding: 0 20px;
box-sizing: border-box;
}
.btns {
display: flex;
justify-content: flex-end;
}
.login_title {
position: relative;
top: 5%;
font-size: 36px;
color: white;
text-align: center;
font-weight: 700;
//
letter-spacing: 10px;
}
.footer {
display: flex;
position: absolute;
flex-direction: column;
bottom: 0;
right: 0;
width: 100px;
height: 120px;
// background-color: pink;
span {
// width: 100%;
// background-color: red;
text-align: center;
}
}
</style>

View File

@ -0,0 +1,317 @@
<template>
<div class="search_container">
<!-- 面包屑导航区域 -->
<el-breadcrumb separator-class="el-icon-arrow-right">
<el-breadcrumb-item>首页</el-breadcrumb-item>
<el-breadcrumb-item>发布公告</el-breadcrumb-item>
</el-breadcrumb>
<el-card shadow="always">
<!-- 搜索内容和导出区域 -->
<el-row>
<el-col :span="4">
<el-button type="primary" @click="showAddDialog()">
<i class="el-icon-s-promotion"></i> 发布新公告</el-button
>
</el-col>
</el-row>
<!-- 表格区域 -->
<el-table :data="tableData" border style="width: 100%" stripe
v-loading="loading"
element-loading-text="拼命加载中"
element-loading-spinner="el-icon-loading"
element-loading-background="rgba(0, 0, 0, 0.8)">
<el-table-column prop="noticeId" label="ID"> </el-table-column>
<el-table-column prop="noticeTitle" label="标题"> </el-table-column>
<el-table-column prop="noticeContent" label="公告"> </el-table-column>
<el-table-column prop="createTime" label="发布日期"> </el-table-column>
<el-table-column label="操作">
<template slot-scope="scope">
<!-- 修改按钮 -->
<el-tooltip
effect="dark"
content="修改"
placement="top"
:enterable="false"
>
<el-button
type="primary"
icon="el-icon-edit"
size="mini"
@click="showEditDialog(scope.row.noticeId)"
></el-button
></el-tooltip>
<!-- 删除按钮 -->
<el-tooltip
effect="dark"
content="删除"
placement="top"
:enterable="false"
>
<el-button
type="danger"
icon="el-icon-delete"
size="mini"
@click="removeUserById(scope.row.noticeId)"
></el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<!-- 分页查询区域 -->
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="queryInfo.pageNum"
:page-sizes="[1, 2, 3, 4,5]"
:page-size="queryInfo.pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="this.total"
>
</el-pagination>
<!-- 修改公告的对话框 -->
<el-dialog
title="修改公告"
:visible.sync="editDialogVisible"
width="50%"
@close="editDialogClosed"
>
<el-form
:model="editForm"
ref="editFormRef"
:rules="editFormRules"
label-width="100px"
>
<el-form-item label="公告标题" prop="noticeTitle">
<el-input v-model="editForm.noticeTitle"></el-input>
</el-form-item>
<el-form-item label="公告内容" prop="noticeContent">
<el-input
type="textarea"
v-model="editForm.noticeContent"
></el-input>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="editDialogVisible = false"> </el-button>
<el-button type="primary" @click="editNoticeById"
> </el-button
>
</span>
</el-dialog>
<!-- 添加公告的对话框 -->
<el-dialog
title="添加公告"
:visible.sync="addDialogVisible"
width="50%"
@close="addDialogClosed"
>
<el-form
:model="addForm"
ref="addFormRef"
:rules="addFormRules"
label-width="100px"
>
<el-form-item label="公告标题" prop="noticeTitle">
<el-input v-model="addForm.noticeTitle"></el-input>
</el-form-item>
<el-form-item label="公告内容" prop="noticeContent">
<el-input
type="textarea"
v-model="addForm.noticeContent"
></el-input>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="addDialogVisible = false"> </el-button>
<el-button type="primary" @click="addNotice"
>添加公告</el-button
>
</span>
</el-dialog>
</el-card>
</div>
</template>
<script>
export default {
data() {
return {
tableData: [],
editDialogVisible: false,
editForm: {
noticeTitle: "",
noticeContent: "",
},
editFormRules: {
noticeTitle: [
{ required: true, message: "请输入公告标题", trigger: "blur" },
{
min: 6,
max: 30,
message: "长度在6到30个字符",
trigger: "blur",
},
],
noticeContent: [
{ required: true, message: "请输入公告内容", trigger: "blur" },
],
},
addDialogVisible:false,
addForm:{
noticeTitle:"",
noticeContent:"",
noticeAdminId:0
},
addFormRules:{
noticeTitle: [
{ required: true, message: "请输入公告标题", trigger: "blur" },
{
min: 6,
max: 30,
message: "长度在6到30个字符",
trigger: "blur",
},
],
noticeContent: [
{ required: true, message: "请输入公告内容", trigger: "blur" },
],
},
queryInfo: {
pageNum: 1,
pageSize: 5,
},
total: 0,
loading:true
};
},
methods: {
handleSizeChange(val) {
this.queryInfo.pageSize = val;
this.getNoticeList();
},
handleCurrentChange(val) {
this.queryInfo.pageNum = val;
this.getNoticeList();
},
//,
async showEditDialog(id) {
this.loading = true;
const {data:res} = await this.$http.get('bookadmin/get_notice/'+id);
if(res.status !== 200){
return this.$message.error(res.msg)
}
// console.log(res);
this.editForm = res.data;
//
this.editDialogVisible = true;
},
//
editDialogClosed() {
this.$refs.editFormRef.resetFields();
},
// id
async editNoticeById(){
const {data:res} = await this.$http.put('bookadmin/update_notice/'+this.editForm.noticeId,{
noticeTitle:this.editForm.noticeTitle,
noticeContent:this.editForm.noticeContent
})
if(res.status !== 200){
return this.$message.error(res.msg)
}
//
this.editDialogVisible = false;
this.getNoticeList();
this.$message.success(res.msg)
},
//
async removeUserById(id) {
//
const confirmResult = await this.$confirm(
"此操作将永久删除该公告, 是否继续?",
"提示",
{
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}
).catch((error) => {
return error;
});
//confirm
//cancel
// console.log(confirmResult);
if (confirmResult !== "confirm") {
return this.$message.info("已经取消删除");
}
//axios,
const {data:res} = await this.$http.get('bookadmin/delete_notice/'+id)
if (res.status !== 200) {
return this.$message.error(res.msg);
}
this.$message.success({
message: res.msg,
duration: 1500,
});
this.getNoticeList();
},
//
addDialogClosed(){
this.$refs.addFormRef.resetFields();
},
//visibletrue
showAddDialog(){
this.addDialogVisible = true;
},
async getNoticeList(){
this.loading = true;
const {data:res} = await this.$http.post('bookadmin/get_noticelist',this.queryInfo)
// console.log(res);
this.tableData = [];
if (res.status !== 200) {
this.total = 0;
this.loading = false;
return this.$message.error(res.msg);
}
this.$message.success({
message: res.msg,
duration: 1000,
});
this.tableData = res.data.records;
this.total = parseInt(res.data.total);
this.loading = false;
},
async addNotice(){
//
this.addDialogVisible = false;
// id
this.addForm.noticeAdminId = parseInt(window.sessionStorage.getItem('bookAdminId'))
// axios
const {data:res} = await this.$http.post('bookadmin/add_notice',this.addForm)
console.log(res);
if(res.status!== 200){
return this.$message.error(res.msg)
}
this.$message.success({
message:res.msg,
duration:1000
})
this.getNoticeList();
}
},
created(){
this.getNoticeList();
}
};
</script>
<style>
</style>

View File

@ -0,0 +1,86 @@
<template>
<div class="borrowbook_container">
<div class="header"><p>查看图书是否逾期</p></div>
<div class="banner">
<div class="book_number">
<el-input placeholder="请输入图书编号" v-model="bookNumber">
<el-button slot="prepend" icon="el-icon-collection"></el-button
></el-input>
</div>
<div class="query_button">
<el-button type="warning" @click="goExpire">查询</el-button>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
bookNumber: "",
};
},
methods: {
async goExpire() {
// axios
const { data: res } = await this.$http.get(
"bookadmin/query_book/" + this.bookNumber
);
if (res.status !== 200) {
return this.$message.error(
{
message:res.msg,
duration:1000
}
);
}
// console.log(res);
// this.$message.success(res.msg);
this.$router.push({
name:"bookexpire",
query:{
bookNumber:this.bookNumber
}
});
},
},
};
</script>
<style lang="less" scoped>
.borrowbook_container {
display: flex;
flex-direction: column;
align-items: center;
.header {
width: 100%;
height: 100px;
// background-color: pink;
p {
color: black;
font-size: 30px;
text-align: center;
line-height: 60px;
}
}
.banner {
display: flex;
flex-direction: column;
align-items: center;
width: 60%;
height: 400px;
// background-color: brown;
div {
margin-top: 15px;
// margin-left: ;
}
.borrow_date {
margin-left: 8px;
}
.query_button {
margin-top: 30px;
}
}
}
</style>

View File

@ -0,0 +1,208 @@
<template>
<div class="search_container">
<!-- 面包屑导航区域 -->
<el-breadcrumb separator-class="el-icon-arrow-right">
<el-breadcrumb-item>首页</el-breadcrumb-item>
<el-breadcrumb-item>还书报表</el-breadcrumb-item>
</el-breadcrumb>
<el-card shadow="always">
<!-- 搜索内容和导出区域 -->
<el-row>
<el-col :span="6"
>条件搜索:<el-select
v-model="queryInfo.condition"
filterable
placeholder="请选择"
style="margin-left: 15px"
>
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value"
>
</el-option>
</el-select>
</el-col>
<el-col :span="4">
<el-input
placeholder="请输入内容"
v-model="queryInfo.query"
class="input-with-select"
@keyup.enter.native="getRuturnStatement"
>
<el-button
slot="append"
icon="el-icon-search"
@click="getRuturnStatement"
></el-button> </el-input
></el-col>
<el-col :span="2" style="float: right">
<download-excel
class="export-excel-wrapper"
:data="tableData"
:fields="json_fields"
:header="title"
name="还书报表.xls"
>
<!-- 上面可以自定义自己的样式还可以引用其他组件button -->
<el-button type="primary" class="el-icon-printer" size="mini"
>导出Excel</el-button
>
</download-excel>
</el-col>
<el-col :span="2" style="float: right">
<el-button
type="primary"
class="el-icon-printer"
size="mini"
@click="downLoad"
>导出PDF</el-button
>
</el-col>
<el-col :span="2" style="float: right">
<el-button type="success" class="el-icon-full-screen" size="mini" @click="fullScreen"
>全屏</el-button
>
</el-col>
</el-row>
<!-- 表格区域 -->
<el-table
:data="tableData"
border
style="width: 100%"
stripe
id="pdfDom"
:default-sort="{ prop: 'cardNumber', order: 'ascending' }"
v-loading="loading"
element-loading-text="拼命加载中"
element-loading-spinner="el-icon-loading"
element-loading-background="rgba(0, 0, 0, 0.8)"
>
<el-table-column prop="cardNumber" label="借阅证编号" sortable>
</el-table-column>
<el-table-column prop="bookNumber" label="图书编号" sortable> </el-table-column>
<el-table-column prop="borrowDate" label="借阅日期" sortable> </el-table-column>
<el-table-column prop="closeDate" label="截止日期" sortable> </el-table-column>
</el-table>
<!-- 分页查询区域 -->
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="queryInfo.pageNum"
:page-sizes="[1, 2, 3, 4, 5]"
:page-size="queryInfo.pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="this.total"
>
</el-pagination>
</el-card>
</div>
</template>
<script>
export default {
data() {
return {
options: [
{
value: "card_number",
label: "借阅证号",
},
{
value: "book_number",
label: "图书编号",
},
{
value: "borrow_date",
label: "借阅日期",
},
{
value: "close_date",
label: "截止日期",
},
],
tableData: [
{
cardNumber: Number,
bookNumber: Number,
borrowDate: "",
closeDate: "",
},
],
queryInfo: {
pageNum: 1,
pageSize: 5,
condition: "",
query: "",
},
total: 0,
title: "借书报表",
json_fields: {
借阅证号: "cardNumber",
图书编号: "bookNumber",
借阅日期: "borrowDate",
截止日期: "closeDate",
},
loading:true
};
},
methods: {
handleSizeChange(val) {
this.queryInfo.pageSize = val;
this.getRuturnStatement();
},
handleCurrentChange(val) {
this.queryInfo.pageNum = val;
this.getRuturnStatement();
},
async getRuturnStatement() {
this.loading = true;
const { data: res } = await this.$http.post(
"bookadmin/get_return_statement",
this.queryInfo
);
// console.log(res);
this.tableData = [];
if (res.status !== 200) {
this.total = 0;
this.loading = false;
return this.$message.error(res.msg);
}
this.$message.success({
message: res.msg,
duration: 1000,
});
this.tableData = res.data.records;
this.total = parseInt(res.data.total);
this.loading = false;
},
downLoad() {
this.getPdf(this.title); //pdf
},
fullScreen(){
// Dom: (trueORfalse)
let full = document.fullscreenElement;
//
if(!full){
// requestFullscreen
document.documentElement.requestFullscreen();
}else{
// 退
document.exitFullscreen();
}
}
},
created() {
this.getRuturnStatement();
},
};
</script>
<style>
</style>

View File

@ -0,0 +1,62 @@
<template>
<div id="echart-line" :style="{ width: '100%', height: '100%' }"></div>
</template>
<script>
import * as echarts from "echarts";
export default {
methods: {
initChart(name, xData, yData) {
// echarts.init('','')
let getchart = echarts.init(document.getElementById("echart-line"));
var option = {
tooltip: {
trigger: "axis",
axisPointer: { type: "cross" },
},
legend: {
data: [name],
bottom: "0%",
},
grid: {
//
top: "10%",
left: "10%",
right: "10%",
bottom: "10%",
containLabel: true,
},
xAxis: {
type: "category",
boundaryGap: false,
data: xData,
axisLabel: {
formatter: "{value}",
align: "center",
// ...
},
},
yAxis: {
type: "value",
},
series: [
{
name: name,
type: "line",
data: yData,
barWidth: "20%",
},
],
};
getchart.setOption(option);
//
window.addEventListener("resize", () => {
getchart.resize();
});
},
},
};
</script>

View File

@ -0,0 +1,49 @@
<template>
<div id="echart-pie" :style="{ width: '100%', height: '100%' }"></div>
</template>
<script>
import * as echarts from "echarts";
export default {
methods: {
initChart(pieData) {
var chartDom = document.getElementById("echart-pie");
var myChart = echarts.init(chartDom);
var option = {
title: {
text: "借书类型分析统计图",
left: "center",
},
tooltip: {
trigger: "item",
},
legend: {
orient: "vertical",
left: "left",
},
series: [
{
name: "借书类型分析统计图",
type: "pie",
radius: "50%",
data:pieData,
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: "rgba(0, 0, 0, 0.5)",
},
},
},
],
};
option && myChart.setOption(option);
},
},
};
</script>
<style>
</style>

View File

@ -0,0 +1,230 @@
<template>
<el-container class="home-container">
<!-- 头部区域 -->
<el-header>
<div>
<!-- <img src="../assets/heima.png" alt="" /> -->
<span>欢迎登录图书管理系统</span>
<span style="color: #ccc; font-size: 16px">借阅者界面</span>
</div>
<div>
<div>
<el-avatar src="https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png" :size="35"
style="margin-right: 10px"></el-avatar>
</div>
<div class="user">用户:{{ this.user.cardName }}</div>
<el-button type="info" @click="logout">退出</el-button>
</div>
</el-header>
<!-- 页面主体区域 -->
<el-container>
<!-- 侧边栏 -->
<el-aside :width="isCollapse ? '64px' : '200px'">
<div class="toggle-button" @click="toggleCollapse">|||</div>
<!-- 侧边栏菜单区域 -->
<el-menu :default-active="activePath" class="el-menu-vertical-demo" background-color="#fff" text-color="black"
active-text-color="#ffd04b" :router="true" :collapse="isCollapse" :collapse-transition="false">
<el-menu-item index="index" @click="saveNavState('index')">
<i class="iconfont icon-shouyefill"></i>
<span slo="title">首页</span>
</el-menu-item>
<el-menu-item index="search" @click="saveNavState('search')">
<i class="iconfont icon-sousuoxiao"></i>
<span slot="title">图书查询</span>
</el-menu-item>
<el-menu-item index="rule" @click="saveNavState('rule')">
<i class="iconfont icon-guizeshezhi"></i>
<span slot="title">读者规则</span>
</el-menu-item>
<el-menu-item index="notice" @click="saveNavState('notice')">
<i class="iconfont icon-gonggao1"></i>
<span slot="title">查看公告</span>
</el-menu-item>
<el-menu-item index="information" @click="saveNavState('information')">
<i class="iconfont icon-gerenxinxi"></i>
<span slot="title">个人信息</span>
</el-menu-item>
<el-menu-item index="borrow" @click="saveNavState('borrow')">
<i class="iconfont icon-tushuqikan"></i>
<span slot="title">借阅信息</span>
</el-menu-item>
<el-menu-item index="violation" @click="saveNavState('violation')">
<i class="iconfont icon-weizhangchaxun"></i>
<span slot="title">违章信息</span>
</el-menu-item>
<el-menu-item index="comment" @click="saveNavState('comment')">
<i class="iconfont icon-liuyan"></i>
<span slot="title">读者留言</span>
</el-menu-item>
<el-menu-item index="intelligent" @click="saveNavState('intelligent')">
<i class="el-icon-monitor"></i>
<span slot="title">智能推荐</span>
</el-menu-item>
<!-- <el-menu-item index="chat" @click="saveNavState('chat')">-->
<!-- <i class="el-icon-monitor"></i>-->
<!-- <span slot="title">图书反馈</span>-->
<!-- </el-menu-item>-->
</el-menu>
</el-aside>
<!-- 右侧内容主体 -->
<el-main>
<!-- 路由占位符 -->
<router-view></router-view>
<div class="footer">
</div>
</el-main>
</el-container>
</el-container>
</template>
<script>
export default {
data() {
return {
//
menulist: [],
iconsObj: {
125: "iconfont icon-user",
103: "iconfont icon-tijikongjian",
101: "iconfont icon-shangpin",
102: "iconfont icon-danju",
145: "iconfont icon-baobiao",
},
//
isCollapse: false,
//
activePath: "",
user: {
userId: Number,
cardNumber: Number,
ruleNumber: Number,
status: Number,
cardName: "",
username: "",
password: "",
createTime: "",
updateTime: "",
},
};
},
async created() {
// this.getMenuList();
this.activePath = window.sessionStorage.getItem("activePath");
// console.log(this.activePath)
// sessionStorageid
const stringId = window.sessionStorage.getItem("userId");
const id = parseInt(stringId);
this.user.userId = id;
const { data: res } = await this.$http.post("user/getData", this.user);
console.log(res);
window.sessionStorage.setItem('cardNumber', res.data.cardNumber)
this.user = res.data;
},
async mounted() { },
methods: {
logout() {
window.sessionStorage.clear();
this.$router.push("/login");
},
//
toggleCollapse() {
this.isCollapse = !this.isCollapse;
},
//
saveNavState(activePath) {
// console.log("first")
window.sessionStorage.setItem("activePath", activePath);
this.activePath = activePath;
// console.log(this.activePath);
},
toGitee() {
console.log(1123);
}
},
};
</script>
<style lang="less" scoped>
.footer {
position: fixed;
bottom: 0px;
left: 40%;
color: #ccc;
a {
color: #ccc;
}
}
.home-container {
height: 100%;
}
.el-header {
background-color: rgb(34, 34, 34);
display: flex;
justify-content: space-between;
padding-left: 0px;
align-items: center;
color: #fff;
font-size: 20px;
border-radius: 10px;
>div {
display: flex;
align-items: center;
span {
margin-left: 15px;
}
}
}
.el-aside {
background-color: #fff;
.el-menu {
border-right: none;
}
}
.el-main {
background-color: #eaedf1;
padding: 20px;
}
.iconfont {
margin-right: 10px;
}
.toggle-button {
background-color: #4a5064;
font-size: 10px;
line-height: 24px;
color: #fff;
text-align: center;
//
letter-spacing: 0.2em;
cursor: pointer;
}
.user {
margin-right: 15px;
color: #ccc;
font-size: 16px;
}
.el-menu-item:hover {
background-color: rgb(51, 122, 183) !important;
}
// .el-menu-item{
// color:rgb(135, 206, 235) !important;
// }</style>

View File

@ -0,0 +1,227 @@
<template>
<el-container class="home-container">
<!-- 头部区域 -->
<el-header>
<div>
<!-- <img src="../assets/heima.png" alt="" /> -->
<span>欢迎登录图书管理系统</span>
<span style="color: #ccc; font-size: 16px">系统管理人员页面</span>
</div>
<div>
<div>
<el-avatar
src="https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png"
:size="35"
style="margin-right: 10px"
></el-avatar>
</div>
<div class="user">管理员:{{ this.admin.adminName }}</div>
<el-button type="info" @click="logout">退出</el-button>
</div>
</el-header>
<!-- 页面主体区域 -->
<el-container>
<!-- 侧边栏 -->
<el-aside :width="isCollapse ? '64px' : '200px'">
<div class="toggle-button" @click="toggleCollapse">|||</div>
<!-- 侧边栏菜单区域 -->
<el-menu
:default-active="activePath"
class="el-menu-vertical-demo"
background-color="#fff"
text-color="black"
active-text-color="#ffd04b"
:router="true"
:collapse="isCollapse"
:collapse-transition="false"
>
<el-menu-item index="bookmanage" @click="saveNavState('bookmanage')">
<i class="el-icon-notebook-1"></i>
<span slo="title">书籍管理</span>
</el-menu-item>
<el-menu-item index="booktype" @click="saveNavState('booktype')">
<i class="el-icon-notebook-2"></i>
<span slot="title">书籍类型</span>
</el-menu-item>
<el-menu-item
index="statementmanage"
@click="saveNavState('statementmanage')"
>
<i class="el-icon-bank-card"></i>
<span slot="title">借阅证管理</span>
</el-menu-item>
<el-menu-item
index="statementsearch"
@click="saveNavState('statementsearch')"
>
<i class="el-icon-search"></i>
<span slot="title">借阅信息查询</span>
</el-menu-item>
<el-menu-item
index="statementrulemanage"
@click="saveNavState('statementrulemanage')"
>
<i class="el-icon-key"></i>
<span slot="title">借阅规则管理</span>
</el-menu-item>
<el-menu-item
index="bookadminmanage"
@click="saveNavState('bookadminmanage')"
>
<i class="el-icon-user-solid"></i>
<span slot="title">图书管理员管理</span>
</el-menu-item>
<el-menu-item
index="adminmanage"
@click="saveNavState('adminmanage')"
>
<i class="el-icon-s-tools"></i>
<span slot="title">系统管理</span>
</el-menu-item>
<el-menu-item
index="intelligent_analysis"
@click="saveNavState('intelligent_analysis')"
>
<i class="el-icon-data-line"></i>
<span slot="title">智能分析</span>
</el-menu-item>
</el-menu>
</el-aside>
<!-- 右侧内容主体 -->
<el-main>
<!-- 路由占位符 -->
<router-view></router-view>
<div class="footer">
</div>
</el-main>
</el-container>
</el-container>
</template>
<script>
export default {
data() {
return {
//
menulist: [],
iconsObj: {
125: "iconfont icon-user",
103: "iconfont icon-tijikongjian",
101: "iconfont icon-shangpin",
102: "iconfont icon-danju",
145: "iconfont icon-baobiao",
},
//
isCollapse: false,
//
activePath: "",
admin: {
adminId: Number,
status: Number,
adminName: "",
username: "",
password: "",
createTime: "",
updateTime: "",
},
};
},
async created() {
// this.getMenuList();
this.activePath = window.sessionStorage.getItem("activePath");
// console.log(this.activePath)
// sessionStorageid
const stringId = window.sessionStorage.getItem("adminId");
const id = parseInt(stringId);
this.admin.adminId = id;
const { data: res } = await this.$http.post("admin/getData", this.admin);
this.admin = res.data;
},
methods: {
logout() {
window.sessionStorage.clear();
this.$router.push("/loginadmin");
},
//
toggleCollapse() {
this.isCollapse = !this.isCollapse;
},
//
saveNavState(activePath) {
// console.log("first")
window.sessionStorage.setItem("activePath", activePath);
this.activePath = activePath;
// console.log(this.activePath);
},
},
};
</script>
<style lang="less" scoped>
.footer {
position: fixed;
bottom: 0px;
left: 40%;
color:#ccc;
a {
color:#ccc;
}
}
.home-container {
height: 100%;
}
.el-header {
background-color: rgb(34, 34, 34);
display: flex;
justify-content: space-between;
padding-left: 0px;
align-items: center;
color: #fff;
font-size: 20px;
border-radius: 10px;
> div {
display: flex;
align-items: center;
span {
margin-left: 15px;
}
}
}
.el-aside {
background-color: #fff;
.el-menu {
border-right: none;
}
}
.el-main {
background-color: #eaedf1;
padding: 20px;
}
.iconfont {
margin-right: 10px;
}
.toggle-button {
background-color: #4a5064;
font-size: 10px;
line-height: 24px;
color: #fff;
text-align: center;
//
letter-spacing: 0.2em;
cursor: pointer;
}
.user {
margin-right: 15px;
color: #ccc;
font-size: 16px;
}
.el-menu-item:hover {
background-color: rgb(51, 122, 183) !important;
}
// .el-menu-item{
// color:rgb(135, 206, 235) !important;
// }
</style>

View File

@ -0,0 +1,209 @@
<template>
<el-container class="home-container">
<!-- 头部区域 -->
<el-header>
<div>
<!-- <img src="../assets/heima.png" alt="" /> -->
<span>欢迎登录图书管理系统</span>
<span style="color: #ccc; font-size: 16px">图书馆管理人员页面</span>
</div>
<div>
<div>
<el-avatar
src="https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png"
:size="35"
style="margin-right: 10px"
></el-avatar>
</div>
<div class="user">管理员:{{ this.bookAdmin.bookAdminName }}</div>
<el-button type="info" @click="logout">退出</el-button>
</div>
</el-header>
<!-- 页面主体区域 -->
<el-container>
<!-- 侧边栏 -->
<el-aside :width="isCollapse ? '64px' : '200px'">
<div class="toggle-button" @click="toggleCollapse">|||</div>
<!-- 侧边栏菜单区域 -->
<el-menu
:default-active="activePath"
class="el-menu-vertical-demo"
background-color="#fff"
text-color="black"
active-text-color="#ffd04b"
:router="true"
:collapse="isCollapse"
:collapse-transition="false"
>
<el-menu-item index="borrowbook" @click="saveNavState('borrowbook')">
<i class="el-icon-collection"></i>
<span slo="title">借阅图书</span>
</el-menu-item>
<el-menu-item index="returnbook" @click="saveNavState('returnbook')">
<i class="el-icon-collection"></i>
<span slot="title">归还图书</span>
</el-menu-item>
<el-menu-item
index="borrowstatement"
@click="saveNavState('borrowstatement')"
>
<i class="el-icon-folder-checked"></i>
<span slot="title">借书报表</span>
</el-menu-item>
<el-menu-item
index="returnstatement"
@click="saveNavState('returnstatement')"
>
<i class="el-icon-folder-checked"></i>
<span slot="title">还书报表</span>
</el-menu-item>
<el-menu-item
index="noticemanage"
@click="saveNavState('noticemanage')"
>
<i class="el-icon-s-promotion"></i>
<span slot="title">发布公告</span>
</el-menu-item>
</el-menu>
</el-aside>
<!-- 右侧内容主体 -->
<el-main>
<!-- 路由占位符 -->
<router-view></router-view>
<div class="footer">
</div>
</el-main>
</el-container>
</el-container>
</template>
<script>
export default {
data() {
return {
//
menulist: [],
iconsObj: {
125: "iconfont icon-user",
103: "iconfont icon-tijikongjian",
101: "iconfont icon-shangpin",
102: "iconfont icon-danju",
145: "iconfont icon-baobiao",
},
//
isCollapse: false,
//
activePath: "",
bookAdmin:{
bookAdminId:Number,
status:Number,
username:"",
password:"",
bookAdminName:"",
email:"",
createTime:"",
updateTime:""
}
};
},
async created() {
// this.getMenuList();
this.activePath = window.sessionStorage.getItem("activePath");
// console.log(this.activePath)
// sessionStorageid
const stringId = window.sessionStorage.getItem("bookAdminId");
const id = parseInt(stringId);
this.bookAdmin.bookAdminId = id;
const { data: res } = await this.$http.post("bookadmin/getData", this.bookAdmin);
// console.log(res);
this.bookAdmin = res.data;
},
methods: {
logout() {
window.sessionStorage.clear();
this.$router.push("/loginmanage");
},
//
toggleCollapse() {
this.isCollapse = !this.isCollapse;
},
//
saveNavState(activePath) {
// console.log("first")
window.sessionStorage.setItem("activePath", activePath);
this.activePath = activePath;
// console.log(this.activePath);
},
},
};
</script>
<style lang="less" scoped>
.footer {
position: fixed;
bottom: 0px;
left: 40%;
color:#ccc;
a {
color:#ccc;
}
}
.home-container {
height: 100%;
}
.el-header {
background-color: rgb(34, 34, 34);
display: flex;
justify-content: space-between;
padding-left: 0px;
align-items: center;
color: #fff;
font-size: 20px;
border-radius: 10px;
> div {
display: flex;
align-items: center;
span {
margin-left: 15px;
}
}
}
.el-aside {
background-color: #fff;
.el-menu {
border-right: none;
}
}
.el-main {
background-color: #eaedf1;
padding: 20px;
}
.iconfont {
margin-right: 10px;
}
.toggle-button {
background-color: #4a5064;
font-size: 10px;
line-height: 24px;
color: #fff;
text-align: center;
//
letter-spacing: 0.2em;
cursor: pointer;
}
.user {
margin-right: 15px;
color: #ccc;
font-size: 16px;
}
.el-menu-item:hover {
background-color: rgb(51, 122, 183) !important;
}
// .el-menu-item{
// color:rgb(135, 206, 235) !important;
// }
</style>

View File

@ -0,0 +1,20 @@
<template>
<div class="index">
<swiperVue />
</div>
</template>
<script>
import swiperVue from "../../components/Swiper/swiper.vue"
export default {
name: "index",
components:{
swiperVue
},
created() {
// const {data:res} = await this.$http.get('/user/test');
// console.log(res);
},
};
</script>

215
src/components/Login.vue Normal file
View File

@ -0,0 +1,215 @@
<template>
<div class="login_container">
<div class="login_title">用户登录界面</div>
<div class="login_box">
<!-- 头像区域 -->
<div class="avatar_box">
<img src="../assets/images/dinosaur.jpg" alt="" />
</div>
<!-- 登录表单区域 -->
<el-form ref="loginFormRef" :model="loginForm" :rules="loginFormRules" label-width="0px" class="login_form">
<!-- 用户名 -->
<el-form-item prop="username">
<el-input v-model.trim="loginForm.username" prefix-icon="iconfont icon-gerenxinxi"></el-input>
</el-form-item>
<!-- 密码 -->
<el-form-item prop="password">
<el-input v-model="loginForm.password" prefix-icon="iconfont icon-tianchongxing-" type="password"
@keyup.enter.native="login" :show-password="true"></el-input>
</el-form-item>
<!-- 按钮区域 -->
<el-form-item class="btns">
<el-button type="primary" @click="login" :loading="loginLoading">登录</el-button>
<el-button type="info" @click="resetLoginForm">重置</el-button>
</el-form-item>
</el-form>
</div>
<!-- 粒子插件特效-->
<vue-particles class="login-bg" color="#39AFFD" :particleOpacity="0.7" :particlesNumber="100" shapeType="circle"
:particleSize="4" linesColor="#8DD1FE" :linesWidth="1" :lineLinked="true" :lineOpacity="0.4" :linesDistance="150"
:moveSpeed="3" :hoverEffect="true" hoverMode="grab" :clickEffect="true" clickMode="push">
</vue-particles>
<div class="footer">
<span style="font-weight: bold;color:white;margin-bottom: 10px">
登录页面切换
<!-- <i class="iconfont icon-haoyou " @click="goUser"></i> -->
</span>
<span>
<i class="iconfont icon-guanliyuan" @click="goManage"></i>
</span>
</div>
</div>
</template>
<script>
export default {
data() {
return {
//
loginForm: {
username: "yangzhen",
password: "123456",
},
//
loginFormRules: {
username: [
{ required: true, message: "用户名不能为空", trigger: "blur" },
{
min: 3,
max: 20,
message: "长度在 3 到 20 个字符",
trigger: "blur",
},
],
password: [
{ required: true, message: "密码不能为空", trigger: "blur" },
{
min: 6,
max: 15,
message: "长度在 6 到 15 个字符",
trigger: "blur",
},
],
},
loginLoading: false
};
},
methods: {
resetLoginForm() {
this.$refs.loginFormRef.resetFields();
},
login() {
this.$refs.loginFormRef.validate(async (valid) => {
// console.log(valid);
//
if (!valid) {
return;
}
this.loginLoading = true;
// md5
const salt = "xiaobaitiao";
const username = this.loginForm.username;
const password = this.loginForm.password;
//axios
const { data: res } = await this.$http.post(
"user/login",
{
username,
password
}
);
if (res.status !== 200) {
this.loginLoading = false;
return this.$message.error(res.msg);
}
// console.log(res);
this.$message.success("登录成功");
this.loginLoading = false;
window.sessionStorage.setItem("token", res.map.token);
window.sessionStorage.setItem("userId", res.map.id);
this.$router.push("/home"); //home
});
},
goUser() {
this.$router.push("/login");
},
goManage() {
this.$router.push("/loginmanage");
},
},
};
</script>
<style lang="less" scoped>
.footer2 {
position: absolute;
bottom: 0px;
left: 35%;
color: #ccc;
a {
color: #ccc;
}
}
.login_container {
// background-color: #2b4b6b;
background: url(https://xxx.xiaobaitiao.icu/img/icu/202312211236280.jpg) no-repeat 0px 0px;
background-size: cover;
height: 100%;
}
.login_box {
height: 300px;
width: 450px;
background-color: #fff;
border-radius: 3px;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
.avatar_box {
width: 130px;
height: 130px;
border: 1px solid #eee;
border-radius: 50%;
padding: 10px;
box-shadow: 0 0 10px #ddd;
position: absolute;
left: 50%;
transform: translate(-50%, -50%);
background-color: #fff;
img {
width: 100%;
height: 100%;
border-radius: 50%;
background-color: #eee;
}
}
}
.login_form {
position: absolute;
bottom: 0;
width: 100%;
padding: 0 20px;
box-sizing: border-box;
}
.btns {
display: flex;
justify-content: flex-end;
}
.login_title {
position: relative;
top: 5%;
font-size: 36px;
color: white;
text-align: center;
font-weight: 700;
//
letter-spacing: 10px;
}
.footer {
display: flex;
position: absolute;
flex-direction: column;
bottom: 0;
right: 0;
width: 100px;
height: 120px;
// background-color: pink;
span {
// width: 100%;
// background-color: red;
text-align: center;
}
}
</style>

View File

@ -0,0 +1,98 @@
<template>
<!--基础存放容器-->
<div class="swiper-container">
<!-- 需要进行轮播的部分 -->
<div class="swiper-wrapper">
<!-- 每个节点 -->
<div class="swiper-slide">
<img
data-src="https://pic.yupi.icu/5563/202311091857319.jpg"
src="https://pic.yupi.icu/5563/202311091902481.gif"
alt="书籍1"
class="swiper-lazy"
/>
</div>
<div class="swiper-slide">
<img
data-src="https://pic.yupi.icu/5563/202311091857306.jpg"
src="https://pic.yupi.icu/5563/202311091902481.gif"
alt="书籍2"
class="swiper-lazy"
/>
<div class="swiper-lazy-preloader"></div>
</div>
<div class="swiper-slide">
<img
data-src="https://pic.yupi.icu/5563/202311091857330.jpg"
src="https://pic.yupi.icu/5563/202311091902481.gif"
alt="书籍3"
class="swiper-lazy"
/>
<div class="swiper-lazy-preloader"></div>
</div>
</div>
<!--如果需要使用分页器-->
<div class="swiper-pagination swiper-pagination-white"></div>
<!-- 如果需要使用前进后退按钮 -->
<div class="swiper-button-prev swiper-button-white"></div>
<div class="swiper-button-next swiper-button-white"></div>
</div>
</template>
<script>
import swiper from "swiper";
export default {
name: "",
data() {
return {};
},
mounted() {
this.initSwiper();
},
methods: {
initSwiper() {
new Swiper(".swiper-container", {
//
loop: true,
//
autoplay: 2000,
// ,
effect: "slide",
//
pagination: ".swiper-pagination",
//退
prevButton: ".swiper-button-prev",
nextButton: ".swiper-button-next",
//
autoplayDisableOnInteraction: false,
//
lazyLoading: true,
});
},
},
};
</script>
<style scoped>
@import "../../../node_modules/swiper/dist/css/swiper.css";
.swiper-container {
width: 1000px;
height: 650px;
margin-top: 40px;
}
.swiper-slide img {
width: 100%;
height: 100%;
object-fit: cover;
}
</style>

View File

@ -0,0 +1,218 @@
<template>
<div class="search_container">
<!-- 面包屑导航区域 -->
<el-breadcrumb separator-class="el-icon-arrow-right">
<el-breadcrumb-item>首页</el-breadcrumb-item>
<el-breadcrumb-item>借阅信息</el-breadcrumb-item>
</el-breadcrumb>
<el-card shadow="always">
<!-- 搜索内容和导出区域 -->
<el-row>
<el-col :span="6"
>条件搜索:<el-select
v-model="queryInfo.condition"
filterable
placeholder="请选择"
style="margin-left: 15px"
>
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value"
>
</el-option>
</el-select>
</el-col>
<el-col :span="4">
<el-input
placeholder="请输入内容"
v-model="queryInfo.query"
class="input-with-select"
@keyup.enter.native="searchBookBorrowByPage"
>
<el-button
slot="append"
icon="el-icon-search"
@click="searchBookBorrowByPage"
></el-button> </el-input
></el-col>
<el-col :span="2" style="float: right">
<download-excel
class="export-excel-wrapper"
:data="tableData"
:fields="json_fields"
:header="title"
name="图书借阅表格.xls"
>
<!-- 上面可以自定义自己的样式还可以引用其他组件button -->
<el-button type="primary" class="el-icon-printer" size="mini"
>导出Excel</el-button
>
</download-excel>
</el-col>
<el-col :span="2" style="float: right">
<el-button
type="primary"
class="el-icon-printer"
size="mini"
@click="downLoad"
>导出PDF</el-button
>
</el-col>
<el-col :span="2" style="float: right">
<el-button type="success" class="el-icon-full-screen" size="mini" @click="fullScreen"
>全屏</el-button
>
</el-col>
</el-row>
<!-- 表格区域 -->
<el-table
:data="tableData"
border
style="width: 100%"
stripe
id="pdfDom"
:default-sort="{ prop: 'cardNumber', order: 'ascending' }"
v-loading="loading"
element-loading-text="拼命加载中"
element-loading-spinner="el-icon-loading"
element-loading-background="rgba(0, 0, 0, 0.8)"
>
<el-table-column prop="cardNumber" label="借阅编号" sortable>
</el-table-column>
<el-table-column prop="bookNumber" label="图书编号" sortable>
</el-table-column>
<el-table-column prop="borrowDate" label="借阅日期" sortable>
</el-table-column>
<el-table-column prop="closeDate" label="截止日期" sortable>
</el-table-column>
<el-table-column prop="returnDate" label="归还日期" sortable>
</el-table-column>
</el-table>
<!-- 分页查询区域 -->
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="this.queryInfo.pageNum"
:page-sizes="[1, 2, 3, 4, 5]"
:page-size="this.queryInfo.pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="this.total"
>
</el-pagination>
</el-card>
</div>
</template>
<script>
export default {
data() {
return {
options: [
{
value: "book_number",
label: "图书编号",
},
{
value: "borrow_date",
label: "借阅日期",
},
{
value: "close_date",
label: "截止日期",
},
{
value: "return_date",
label: "归还日期",
},
],
tableData: [
{
cardNumber: Number,
bookNumber: Number,
borrowId: Number,
borrowDate: "",
closeDate: "",
returnDate: "",
createTime: "",
updateTime: "",
},
],
queryInfo: {
pageNum: 1,
pageSize: 5,
condition: "",
query: "",
cardNumber: window.sessionStorage.getItem("cardNumber"),
},
total: 0,
title: "图书借阅表格",
json_fields: {
借阅编号: "cardNumber",
图书编号: "bookNumber",
借阅日期: "borrowDate",
截止日期: "closeDate",
归还日期: "returnDate",
},
loading: true,
};
},
methods: {
handleSizeChange(val) {
this.queryInfo.pageSize = val;
this.searchBookBorrowByPage();
},
handleCurrentChange(val) {
this.queryInfo.pageNum = val;
this.searchBookBorrowByPage();
},
async searchBookBorrowByPage() {
this.loading = true;
const { data: res } = await this.$http.post(
"user/get_bookborrow",
this.queryInfo
);
this.tableData = [];
// console.log(res);
if (res.status !== 200) {
this.total = 0;
this.loading = false;
return this.$message.error(res.msg);
}
this.$message.success({
message: res.msg,
duration: 1000,
});
this.tableData = res.data.records;
this.total = parseInt(res.data.total);
this.loading = false;
},
downLoad() {
this.getPdf(this.title); //pdf
},
fullScreen(){
// Dom: (trueORfalse)
let full = document.fullscreenElement;
//
if(!full){
// requestFullscreen
document.documentElement.requestFullscreen();
}else{
// 退
document.exitFullscreen();
}
}
},
created() {
this.searchBookBorrowByPage();
},
};
</script>
<style>
</style>

View File

@ -0,0 +1,229 @@
<template>
<div class="chat_container" style="height: 100%">
<div
style="
height: 100%;
width: 100%;
background-color: red;
"
>
<el-row style="height: 100%">
<el-col :span="5" style="background-color: grey; height: inherit">
<div>
<!-- <div style="position: relative; top: 10px; left: 20px; width: 90%">-->
<!-- <el-input class="search_input" placeholder="搜索"></el-input>-->
<!-- </div>-->
<el-row style="height: 60px;background-color:rgb(240, 197, 168);border: 1px solid #eee"
v-for="item in friendList" key="item.username">
<el-col :span="6">
<el-avatar src="https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png" :size="35"
style="margin: 10px 30px">
</el-avatar>
</el-col>
<el-col :span="18">
<el-row>
<el-col :span="14">
<div style="margin:10px 0px 5px 0px">{{ item.username }}</div>
<div class="ellipsis">{{ item.text }}</div>
</el-col>
<el-col :span="10">
<div style="font-size: 10px;margin:10px 0px">
{{ item.date }}
</div>
</el-col>
</el-row>
</el-col>
</el-row>
</div>
</el-col>
<el-col :span="19" style="background-color: blue; height: inherit">
<div style="background-color: yellowgreen; height: 600px;">
<el-row style="height: 60px;background-color:red;display: flex;align-items: center;">
<el-row style="margin-left: 20px;color:rgb(88, 88, 88);font-size:24px">
王二离
</el-row>
</el-row>
<el-row>
<el-row
v-for="message in messages"
:key="message.id"
:class="{'left-message': message.role === 'user', 'right-message': message.role === 'me'}"
>
<!-- 用户或者对方的单行消息包括头像和消息-->
<el-row style="float:left;">
<div style="display: flex; align-items: center;">
<el-avatar v-if="message.role === 'user'"
src="https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png"
:size="35"></el-avatar>
<p :class="{'message-backgroudcolor':true,'left-message-content':message.role==='user','right-message-content':message.role ==='me'}" v-if="message.role === 'user'"> {{ message.content }}</p>
</div>
</el-row>
<el-row style="float: right;margin-top:10px">
<div style="display: flex; align-items: center;">
<span :class="{'message-backgroudcolor':true,'left-message-content':message.role==='user','right-message-content':message.role ==='me'}" v-if="message.role === 'me'"> {{ message.content }}</span>
<el-avatar v-if="message.role === 'me'"
src="https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png"
:size="35"></el-avatar>
</div>
</el-row>
</el-row>
</el-row>
</div>
<!-- 聊天框区域-->
<el-row style="background-color:purple; height: 150px;box-sizing: border-box">
<el-row style="height: 30px;background-color:red;display: flex; align-items: center;">
<div style="margin-left: 20px">
<span>表情</span>
<span style="margin-left: 20px">文件</span>
</div>
</el-row>
<el-row style="height: 200px">
<el-input
type="textarea"
:autosize="{ minRows: 5, maxRows: 5}"
>
</el-input>
</el-row>
</el-row>
</el-col>
</el-row>
</div>
</div>
</template>
<script>
//socket
import {socket} from "@/components/Utils/websocket";
export default {
data() {
return {
loading: true,
websocketCount: -1,
//
queryCondition: {
"toId": 2,
"text": "你好",
"chatType": 1,
"isAdmin": false
},
messages: [
{
id: 1, content: "你好23222222222222222222222222222哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈222222222222222222222222222222222222222222222222222222222222222222", role: "user",
},
{
id: 2, content: "你好红红火火恍恍惚惚哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈红红火火恍恍惚惚哈哈哈哈哈哈哈哈哈哈哈", role: "me",
},
{
id: 3, content: "你好23222222222222222222222222222哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈222222222222222222222222222222222222222222222222222222222222222222", role: "user",
},
{
id: 4, content: "你好", role: "me",
},
],
friendList: [
{
username: "王二离",
text: "最后一条聊天记录:你好",
date: "2024年3月1日"
},
{
username: "礼喆",
text: "最后一条聊天记录:你好",
date: "2024年12月24日"
},
],
};
},
created() {
//websocket
//window.location.hostip
//process.env.VUE_APP_WEBSOCKET_BASE_API
// socket.initWebSocket(`ws://localhost:8889/api/websocket/` + 1923);
//
// socket.websocket.onmessage = this.websocketOnMessage;
},
methods: {
backgroundColor() {
return backgroundColor
},
init() {
this.queryCondition.type = "message";
socket.sendMsg(JSON.stringify(this.queryCondition));
},
websocketOnMessage(event) {
//
this.websocketCount += 1;
if (this.websocketCount === 0) {
this.init();
}
let info = event.data;
this.messages.push(info);
switch (info.type) {
case "PONG":
socket.websocketState = true;
break;
case "message":
this.loading = true;
this.$nextTick(() => {
this.consumeMessage(info);
});
break;
case "error":
console.log("错误");
this.loading = false;
break;
}
},
consumeMessage(info) {
//
console.log(info);
},
},
};
</script>
<style lang="less" scoped>
.chat_container {
}
.ellipsis {
font-size: 12px;
color: rgb(127, 76, 69);
white-space: nowrap; /* 不换行 */
overflow: hidden; /* 超出部分隐藏 */
text-overflow: ellipsis; /* 显示省略号 */
width: 100px; /* 设置元素宽度 */
}
.left-message {
text-align: left;
margin-left: 10px;
margin-right:10px;
margin-top: 10px;
}
.right-message {
text-align: right;
margin-right: 10px;
margin-left: 10px;
}
.message-backgroudcolor {
background-color: #e7f7ff;
border-radius: 5px;
}
.left-message-content {
margin-left: 10px;
}
.right-message-content {
margin-right:10px;
}
</style>

View File

@ -0,0 +1,143 @@
<template>
<div class="comment_container">
<div class="backgroundImg">
<img
src="https://xxx.xiaobaitiao.icu/img/icu/202312211243628.jpg"
alt=""
/>
</div>
<div class="barrages-drop">
<vue-baberrage
:isShow="barrageIsShow"
:barrageList="barrageList"
:maxWordCount="maxWordCount"
:throttleGap="throttleGap"
:loop="barrageLoop"
:boxHeight="boxHeight"
:boxWidth="boxWidth"
:messageHeight="messageHeight"
:lanesCount="lanesCount"
style="width: 1330px; height: 750px"
>
</vue-baberrage>
<div class="addMain">
<el-input
placeholder="请输入内容"
v-model.trim="input"
class="input-with-select"
@keyup.enter.native="addContent"
>
<el-button
slot="append"
icon="el-icon-edit"
@click="addContent"
></el-button>
</el-input>
</div>
</div>
</div>
</template>
<script>
import { MESSAGE_TYPE } from "vue-baberrage";
import { nanoid, random } from "nanoid";
export default {
data() {
return {
barrageIsShow: true, //
messageHeight: 3, //()
barrageLoop: false, //
maxWordCount: 2, //()
lanesCount: 1, //()
boxWidth: 1600, //
boxHeight: 200, //()
throttleGap: 3000, //
input: "",
//
barrageList: [],
barrage: {
id: "",
avatar:
"https://img0.baidu.com/it/u=825023390,3429989944&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500",
msg: "",
time: "",
type: MESSAGE_TYPE.NORMAL,
barrageStyle: "",
},
};
},
methods: {
addContent:_.throttle(async function(){
//
if(this.input.trim()===''||/^\d+$/.test(this.input)||/^[a-zA-Z]+$/.test(this.input)){
this.$message.info({
message: "请不要输入无意义的内容",
duration:1000
})
return;
}
// barrage
this.barrage.msg = this.input;
// addComment
const {data:res} = await this.$http.post('user/add_comment',this.barrage);
if(res.status !== 200){
return this.$message.error(res.msg)
}
//
this.getCommentList();
//
this.input = "";
this.$message.success(res.msg);
},5000,{ trailing: false }),
async getCommentList() {
// axios
const { data: res } = await this.$http.get("user/get_commentlist");
// console.log(res);
if (res.status !== 200) {
return this.$message.error(res.msg);
}
this.$message.success(res.msg);
this.barrageList = res.data
//
this.barrageList.push({})
},
},
mounted(){
this.getCommentList();
}
};
</script>
<style lang="less" scoped>
.comment_container {
position: relative;
height: 100%;
width: 100%;
}
.backgroundImg {
position: absolute;
height: 100%;
width: 100%;
img {
height: 100%;
width: 100%;
opacity: 0.5;
}
}
.barrages-drop {
position: relative;
}
.addMain {
position: absolute;
width: 300px;
height: 100%;
background-color: pink;
top: 450px;
left: 50%;
transform: translate(-50%, -50%);
}
</style>

View File

@ -0,0 +1,231 @@
<template>
<div class="information_container">
<div class="backgroundImg">
<img
src="https://xxx.xiaobaitiao.icu/img/icu/202312211243635.jpg"
alt="背景图片"
/>
</div>
<div class="information_header">
<p>个人信息</p>
<p>
<i class="el-icon-s-flag"></i> By reading we enrich the mind, by
conversation we polish it.
</p>
</div>
<div class="information_banner"
v-loading="loading"
element-loading-text="拼命加载中"
element-loading-spinner="el-icon-loading"
element-loading-background="rgba(0, 0, 0, 0.8)">
<div class="information_banner_left" >
<div class="banner_left_main" v-if="show">
<div class="number">
<i class="el-icon-collection-tag"></i> 借阅证编号:
{{ this.user.cardNumber }}
</div>
<div class="name">
<i class="iconfont icon-gerenxinxi"></i> 借阅证姓名:
{{ this.user.cardName }}
</div>
<div class="rule">
<i class="iconfont icon-guizeshezhi"></i> 规则编号:
{{ this.user.ruleNumber }}
</div>
<div class="status">
<i class="el-icon-refresh"></i> 状态:
{{ this.user.status === 1 ? "可用" : "禁用" }}
</div>
</div>
</div>
<div class="information_banner_right">
<el-button type="primary" class="changePWD" @click="showEditDialog" v-if="show"
>修改密码</el-button
>
</div>
<el-dialog
title="修改密码"
:visible.sync="editDialogVisible"
width="50%"
@close="editDialogClosed"
>
<el-form
:model="editForm"
ref="editFormRef"
:rules="editFormRules"
label-width="120px"
>
<el-form-item label="新密码" prop="password">
<el-input v-model="editForm.password" type="password" placeholder="请输入新密码"></el-input>
</el-form-item>
<el-form-item label="新密码" prop="confirmPassword">
<el-input v-model="editForm.confirmPassword" type="password" placeholder="请再次输入新密码"></el-input>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="editDialogVisible = false"> </el-button>
<el-button type="primary" @click="changePassword"
> </el-button
>
</span>
</el-dialog>
</div>
</div>
</template>
<script>
export default {
data() {
var validatePass2 = (rule, value, callback) => {
if (value === '') {
callback(new Error('请再次输入密码'));
} else if (value !== this.editForm.password) {
callback(new Error('两次输入密码不一致!'));
} else {
callback();
}
};
return {
user: {
ruleNumber: Number,
cardNumber: Number,
status: Number,
userId: Number,
cardName: "",
username: "",
password: "",
createTime: "",
updateTime: "",
},
editForm: {
password: "",
confirmPassword: "",
},
editFormRules:{
password:[
{required:true,message:"请输入新密码",trigger:"blur"},
{min:6,max:15,message:"新密码长度在6-15个字符",trigger:"blur"}
],
confirmPassword:[
{validator:validatePass2,trigger:"blur"}
]
},
editDialogVisible: false,
show:false,
loading:true
};
},
methods: {
//,
showEditDialog() {
//
this.editDialogVisible = true;
},
//
editDialogClosed() {
this.$refs.editFormRef.resetFields();
},
async getUserInformaton() {
// sessionStorageid
const userId = window.sessionStorage.getItem("userId");
// axiosid
this.loading = true;
const { data: res } = await this.$http.get(
"user/get_information/" + userId
);
if (res.status !== 200) {
return this.$message.error(res.msg);
}
this.$message.success({
message: res.msg,
duration: 1000,
});
this.user = res.data;
this.show = true;
this.loading = false;
},
async changePassword(){
const {data:res} = await this.$http.post('user/update_password',{
password:this.editForm.password,
userId:window.sessionStorage.getItem('userId')
})
if(res.status !== 200){
return this.$message.error(res.msg);
}
this.$message.success(res.msg)
this.editDialogVisible = false;
this.$refs.editFormRef.resetFields();
window.sessionStorage.clear();
this.$router.push("/login");
}
},
created() {
this.getUserInformaton();
},
};
</script>
<style lang="less" scoped>
.information_container {
position: relative;
height: 100%;
}
.backgroundImg {
position: absolute;
width: 100%;
height: 100%;
img {
width: 100%;
height: 100%;
opacity: 0.3;
}
}
.information_header {
height: 200px;
// background-color: pink;
text-align: center;
p:nth-child(1) {
line-height: 140px;
color: black;
font-size: 36px;
}
p:nth-child(2) {
color: black;
font-size: 24px;
}
}
.information_banner {
display: flex;
flex-direction: row;
height: 400px;
// background-color: pink;
.information_banner_left {
flex: 0.5;
// background-color: brown;
text-align: center;
}
.information_banner_right {
flex: 0.5;
// background-color: skyblue;
text-align: left;
line-height: 400px;
}
}
.banner_left_main {
margin-top: 120px;
color: black;
font-size: 20px;
div {
margin-top: 15px;
}
}
.changePWD {
position: absolute;
top: 50%;
left: 50%;
}
</style>

View File

@ -0,0 +1,170 @@
<template>
<div class="chat-container">
<h2 style="text-align: center;color:deepskyblue;">llama3 智能推荐 </h2>
<el-card class="chat" style="margin-top:10px;height: 80vh;border-radius: 15px;border-color: #ccc;">
<el-card class="main_chat">
<div v-for="item in messages" :key="message.id" class="message">
<div v-if="item.inputMessage!=null&&item.inputMessage!==''" class="user-message">
{{ item.inputMessage }}
</div>
<div v-if="item.aiResult!=null&&item.aiResult!==''" class="bot-message">{{ item.aiResult }}</div>
</div>
</el-card>
<el-input
placeholder="请输入内容,例如:我喜欢动物类的书籍,请给我推荐"
v-model.trim="inputMessage"
:disabled="loading"
@keyup.enter.native="sendMessage"
>
<el-button
slot="append"
icon="el-icon-s-promotion"
v-loading="loading"
@click="sendMessage"
:disabled="loading"
></el-button>
</el-input
>
</el-card>
</div>
</template>
<script>
export default {
data() {
return {
messages: [],
inputMessage: "",
message: {
inputMessage: "",
aiResult: "",
userId: null
},
loading: false
}
},
methods: {
/**
* 发送消息接受AI结果
* @returns {Promise<ElMessageComponent>}
*/
async sendMessage() {
//
if (this.inputMessage.trim() === "") {
return this.$message.info({
message: "文本不能为空",
duration: 1500
})
}
//
if (/[\da-zA-Z]/.test(this.inputMessage)) {
this.$message.info({
message: "请不要输入无意义的内容",
duration: 1000
});
return;
}
if (this.inputMessage.trim() !== "") {
var userId = window.sessionStorage.getItem("userId");
this.messages.push({
id: Date.now(),
inputMessage: this.inputMessage,
userId: parseInt(userId)
});
// GPT API
this.message.inputMessage = this.inputMessage.trim();
this.message.userId = userId;
//
this.inputMessage = "";
this.loading = true;
this.$message.info({
message:"文本输入越长接口调用时间越长请耐心等待10秒左右",
duration:2500
})
const {data: res} = await this.$http.post("user/ai_intelligent", this.message)
if (res.status !== 200) {
this.loading = false;
return this.$message.error(res.msg);
}
this.$message.success({
message: res.msg,
duration: 1500
})
this.messages.push({
id: Date.now(),
aiResult: res.data,
userId: userId
});
this.loading = false;
}
},
/**
* 获取最近五条该用户和AI交流的信息
* @returns {Promise<void>}
*/
async getHistoryRecords() {
var userId = window.sessionStorage.getItem("userId");
const {data: res} = await this.$http.get("user/ai_list_information/" + userId)
if (res.status !== 200) {
return this.$message.error(res.msg)
}
this.$message.success({
message: res.msg,
duration: 1500
})
res.data.forEach((item) => {
this.messages.push(item);
})
}
},
created() {
this.getHistoryRecords();
}
}
</script>
<style scoped lang='less'>
//.chat-container {
// display: flex;
// flex-direction: column;
// height: 100vh;
//}
.main_chat {
margin-bottom: 10px;
height: 70vh;
background-size: cover;
background: url(https://xxx.xiaobaitiao.icu/img/icu/202312211243632.jpg) no-repeat 0px 0px;
border-radius: 10px;
overflow-y: scroll;
}
.message {
display: flex;
flex-direction: column;
}
.user-message {
display: flex;
flex-direction: row;
background-color: #e7f7ff;
padding: 10px;
border-radius: 10px;
align-self: flex-end;
text-align: right;
margin-top: 10px;
}
.bot-message {
background-color: #f4f6f8;
padding: 10px;
border-radius: 10px;
text-align: left;
align-self: flex-start;
margin-top: 10px;
white-space: pre-wrap;
}
</style>

View File

@ -0,0 +1,173 @@
<template>
<div class="notice_container">
<div class="header">
<div class="scroll-text" ref="scrollText">
<i class="el-icon-s-opportunity"></i> {{ text }}
<i class="el-icon-s-opportunity"></i>
</div>
</div>
<div class="banner">
<div class="banner_header"><p>近期公告</p></div>
<div class="banner_main"
v-loading="loading"
element-loading-text="拼命加载中"
element-loading-spinner="el-icon-loading"
element-loading-background="rgba(0, 0, 0, 0.8)">
<div class="banner_main_item" v-for="item in noticeList" :key="item.noticeId">
<div class="banner_main_item_header"> <p> {{ item.noticeTitle }} {{ item.createTime }}</p></div>
<div class="banner_main_item_main">
<p>{{ item.noticeContent }}</p>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
text: "图书馆公告栏,记得查收公告呀!小项目请勿恶意攻击,谢谢",
noticeList:[
{
noticeId:0,
noticeAdminId:Number,
noticeTitle:"",
noticeContent:"",
createTime:"",
updateTime:""
}
],
loading:true
};
},
methods: {
async getRuleList(){
this.loading = true;
const {data:res} = await this.$http.get('user/get_noticelist')
if(res.status!== 200){
this.loading = false;
return this.$message.error(res.msg)
}
this.$message.success({
message:res.msg,
duration:1000
})
this.noticeList = res.data;
this.loading = false;
}
},
mounted() {
const containerWidth = this.$refs.scrollText.offsetWidth;
const textWidth = this.$refs.scrollText.scrollWidth;
// If the text is longer than the container, start the animation
if (textWidth > containerWidth) {
this.$refs.scrollText.style.animation = "scroll 10s linear infinite";
}
},
created(){
this.getRuleList();
}
};
</script>
<style lang="less" scoped>
.notice_container {
overflow: hidden;
}
.header {
width: 100%;
height: 50px;
background-color: rgb(70, 130, 180);
border-radius: 15px;
// box-shadow: 0px 0px 5px 5px rgb(66, 142, 5);
color: white;
text-align: center;
line-height: 50px;
font-size: 24px;
}
.scroll-text {
white-space: nowrap;
animation: scroll 10s linear infinite;
}
@keyframes scroll {
from {
transform: translateX(100%);
}
to {
transform: translateX(-100%);
}
}
.banner {
width: 100%;
height: 100%;
// background-color: pink;
margin-top:30px;
}
.banner_header {
width: 100%;
height: 80px;
// background-color: brown;
p {
color:black;
font-size: 30px;
text-align: center;
line-height: 80px;
}
}
.banner_main{
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
color:skyblue;
}
.banner_main_item:nth-child(n+2) {
margin-top: 30px;
}
.banner_main_item:nth-child(n+2){
background-color: #D1EEEE;
}
.banner_main_item:nth-child(1){
background-color: pink;
}
.banner_main_item {
width: 80%;
height: 120px;
// background-color: aqua;
.banner_main_item_header {
width: 100%;
height: 50px;
// background-color: pink;
border:1px solid skyblue;
box-sizing: border-box;
p {
color:rgb(175, 129, 143);
text-align: center;
line-height: 50px;
font-size: 16px;
}
}
.banner_main_item_main{
width: 100%;
height: 70px;
background-color: white;
border:1px solid skyblue;
box-sizing: border-box;
text-align: center;
p {
line-height: 70px;
}
}
}
</style>

View File

@ -0,0 +1,114 @@
<template>
<div class="rule_container">
<div class="header">
<p>读者规则信息查看</p>
</div>
<div class="banner">
<el-tooltip
v-for="item in ruleList"
v-loading="loading"
element-loading-text="拼命加载中"
element-loading-spinner="el-icon-loading"
element-loading-background="rgba(0, 0, 0, 0.8)"
:key="item.ruleId"
effect="dark"
placement="right"
>
<div slot="content" class="content_tip">可借阅数量: {{ item.bookLimitNumber }}<br/>可借阅天数: {{ item.bookDays }}
<br/>可借阅图书馆: {{ item.bookLimitLibrary }}<br/>过期扣费/: {{ item.bookOverdueFee }}</div>
<el-button style="font-size:16px">借阅证规则编号: {{ item.bookRuleId }}</el-button>
</el-tooltip>
</div>
</div>
</template>
<script>
import { Loading } from 'element-ui';
export default {
data(){
return {
ruleList:[
{
ruleId:0,
bookRuleId:Number,
bookDays:Number,
bookLimitNumber:Number,
bookOverdueFee:Number,
bookLimitLibrary:"",
createTime:"",
updateTime:"",
}
],
loading:true
}
},
methods:{
async getRuleList(){
const {data:res} = await this.$http.get("user/get_rulelist")
if(res.status !== 200){
this.loading = false;
return this.$message.error(res.msg);
}
this.$message.success({
message:res.msg,
duration:1000
})
this.loading = false;
this.ruleList = res.data
}
},
created(){
this.getRuleList()
}
};
</script>
<style lang="less" scoped>
.rule_container {
position: relative;
}
.header {
position: absolute;
left: 50%;
transform: translate(-50%);
p {
font-size: 36px;
color: rgb(70, 130, 180);
}
}
.banner {
display: flex;
flex-direction: column;
position:absolute;
left:50%;
transform: translate(-50%);
}
.el-tooltip{
width: 500px;
font-size: 16px;
color:white;
}
.el-tooltip:hover{
color:black;
}
.el-tooltip:nth-child(1){
margin-top:100px;
}
.el-tooltip:nth-child(n+2){
margin-top:50px;
margin-left:0px;
}
.el-tooltip:nth-child(odd){
background-color: pink;
}
.el-tooltip:nth-child(even){
background-color: rgb(49, 176, 213)
}
.content_tip {
font-size: 16px;
}
</style>

View File

@ -0,0 +1,228 @@
<template>
<div class="search_container">
<!-- 面包屑导航区域 -->
<el-breadcrumb separator-class="el-icon-arrow-right">
<el-breadcrumb-item>首页</el-breadcrumb-item>
<el-breadcrumb-item>图书查询</el-breadcrumb-item>
</el-breadcrumb>
<el-card shadow="always">
<!-- 搜索内容和导出区域 -->
<el-row>
<el-col :span="6"
>条件搜索:
<el-select
v-model="queryInfo.condition"
filterable
placeholder="请选择"
style="margin-left: 15px"
>
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value"
>
</el-option>
</el-select>
</el-col>
<el-col :span="4">
<el-input
placeholder="请输入内容"
v-model="queryInfo.query"
class="input-with-select"
@keyup.enter.native="searchBookByPage"
>
<el-button
slot="append"
icon="el-icon-search"
@click="searchBookByPage"
></el-button>
</el-input>
</el-col>
<el-col :span="2" style="float: right">
<download-excel
class="export-excel-wrapper"
:data="tableData"
:fields="json_fields"
:header="title"
name="图书查询表格.xls"
>
<!-- 上面可以自定义自己的样式还可以引用其他组件button -->
<el-button type="primary" class="el-icon-printer" size="mini"
>导出Excel
</el-button>
</download-excel>
</el-col>
<el-col :span="2" style="float: right">
<el-button
type="primary"
class="el-icon-printer"
size="mini"
@click="downLoad"
>导出PDF
</el-button>
</el-col>
<el-col :span="2" style="float: right">
<el-button
type="success"
class="el-icon-full-screen"
size="mini"
@click="fullScreen"
>全屏
</el-button>
</el-col>
</el-row>
<!-- 表格区域 -->
<el-table
:data="tableData"
height="520"
border
style="width: 100%; font-size: 14px"
v-loading="loading"
element-loading-text="拼命加载中"
element-loading-spinner="el-icon-loading"
element-loading-background="rgba(0, 0, 0, 0.8)"
id="pdfDom"
:default-sort="{ prop: 'bookNumber', order: 'ascending' }"
stripe
>
<el-table-column
prop="bookNumber"
label="图书编号"
sortable
></el-table-column>
<el-table-column prop="bookName" label="图书名称"></el-table-column>
<el-table-column prop="bookAuthor" label="作者"></el-table-column>
<el-table-column prop="bookLibrary" label="图书馆"></el-table-column>
<el-table-column prop="bookType" label="分类"></el-table-column>
<el-table-column
prop="bookLocation"
label="位置"
sortable
></el-table-column>
<el-table-column
prop="bookStatus"
label="状态"
sortable
></el-table-column>
<el-table-column prop="bookDescription" label="描述" width="600px">
</el-table-column>
</el-table>
<!-- 分页查询区域 -->
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="this.queryInfo.pageNum"
:page-sizes="[1, 2, 3, 4, 5]"
:page-size="this.queryInfo.pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="this.total"
>
</el-pagination>
</el-card>
</div>
</template>
<script>
export default {
data() {
return {
options: [
{
value: "book_number",
label: "图书编号",
},
{
value: "book_name",
label: "图书名称",
},
{
value: "book_author",
label: "作者",
},
{
value: "book_location",
label: "位置",
},
{
value: "book_description",
label: "描述",
},
],
tableData: [],
queryInfo: {
pageNum: 1,
pageSize: 5,
condition: "",
query: "",
},
total: 0,
title: "图书查询表格",
json_fields: {
图书编号: "bookNumber",
图书名称: "bookName",
作者: "bookAuthor",
图书馆: "bookLibrary",
分类: "bookType",
位置: "bookLocation",
状态: "bookStatus",
描述: "bookDescription",
},
loading: true,
};
},
created() {
this.searchBookByPage();
},
methods: {
handleSizeChange(val) {
this.queryInfo.pageSize = val;
this.searchBookByPage();
},
handleCurrentChange(val) {
this.queryInfo.pageNum = val;
this.searchBookByPage();
},
async searchBookByPage() {
this.loading = true;
const { data: res } = await this.$http.post(
"user/search_book_page",
this.queryInfo
);
this.tableData = [];
if (res.status !== 200) {
this.total = 0;
this.loading = false;
return this.$message.error(res.msg);
}
this.$message.success({
message: res.msg,
duration: 1000,
});
this.loading = false;
this.tableData = res.data.records;
this.total = parseInt(res.data.total);
},
downLoad() {
this.getPdf(this.title); //pdf
},
fullScreen() {
// Dom: (trueORfalse)
let full = document.fullscreenElement;
//
if (!full) {
// requestFullscreen
document.documentElement.requestFullscreen();
} else {
// 退
document.exitFullscreen();
}
},
},
};
</script>
<style lang="css"></style>

View File

@ -0,0 +1,215 @@
<template>
<div class="search_container">
<!-- 面包屑导航区域 -->
<el-breadcrumb separator-class="el-icon-arrow-right">
<el-breadcrumb-item>首页</el-breadcrumb-item>
<el-breadcrumb-item>违章信息</el-breadcrumb-item>
</el-breadcrumb>
<el-card shadow="always">
<!-- 搜索内容和导出区域 -->
<el-row>
<el-col :span="6"
>条件搜索:<el-select
v-model="queryInfo.condition"
filterable
placeholder="请选择"
style="margin-left: 15px"
>
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value"
>
</el-option>
</el-select>
</el-col>
<el-col :span="4">
<el-input
placeholder="请输入内容"
v-model="queryInfo.query"
class="input-with-select"
@keyup.enter.native="searchViolationByPage"
>
<el-button
slot="append"
icon="el-icon-search"
@click="searchViolationByPage"
></el-button> </el-input
></el-col>
<el-col :span="2" style="float: right">
<download-excel
class="export-excel-wrapper"
:data="tableData"
:fields="json_fields"
:header="title"
name="图书违章表格.xls"
>
<!-- 上面可以自定义自己的样式还可以引用其他组件button -->
<el-button type="primary" class="el-icon-printer" size="mini"
>导出Excel</el-button
>
</download-excel>
</el-col>
<el-col :span="2" style="float: right">
<el-button
type="primary"
class="el-icon-printer"
size="mini"
@click="downLoad"
>导出PDF</el-button
>
</el-col>
<el-col :span="2" style="float: right">
<el-button type="success" class="el-icon-full-screen" size="mini" @click="fullScreen"
>全屏</el-button
>
</el-col>
</el-row>
<!-- 表格区域 -->
<el-table :data="tableData" border style="width: 100%" stripe id="pdfDom" :default-sort = "{prop: 'cardNumber', order: 'ascending'}"
v-loading="loading"
element-loading-text="拼命加载中"
element-loading-spinner="el-icon-loading"
element-loading-background="rgba(0, 0, 0, 0.8)">
<el-table-column prop="cardNumber" label="借阅证号" sortable> </el-table-column>
<el-table-column prop="bookNumber" label="图书编号" sortable> </el-table-column>
<el-table-column prop="borrowDate" label="借阅日期" sortable> </el-table-column>
<el-table-column prop="closeDate" label="截止日期" sortable> </el-table-column>
<el-table-column prop="returnDate" label="归还日期" sortable> </el-table-column>
<el-table-column prop="violationMessage" label="违章信息">
</el-table-column>
<el-table-column prop="violationAdmin" label="处理人">
</el-table-column>
</el-table>
<!-- 分页查询区域 -->
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="this.queryInfo.pageNum"
:page-sizes="[1, 2, 3, 4, 5]"
:page-size="this.queryInfo.pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="this.total"
>
</el-pagination>
</el-card>
</div>
</template>
<script>
export default {
data() {
return {
options: [
{
value: "book_number",
label: "图书编号",
},
{
value: "borrow_date",
label: "借阅日期",
},
{
value: "close_date",
label: "截止日期",
},
{
value: "return_date",
label: "归还日期",
},
{
value: "violation_message",
label: "违章信息",
},
],
tableData: [
{
cardNumber: Number,
bookNumber: Number,
borrowDate: "",
closeDate: "",
returnDate: "",
violationMessage: "",
violationAdmin: "",
},
],
queryInfo: {
pageNum: 1,
pageSize: 5,
condition: "",
query: "",
cardNumber: window.sessionStorage.getItem("cardNumber"),
},
total: 0,
title: "图书违章表格",
json_fields: {
借阅证号: "cardNumber",
图书编号: "bookNumber",
借阅日期: "borrowDate",
截止日期: "closeDate",
归还日期: "returnDate",
违章信息:"violationMessage",
处理人:"violationAdmin"
},
loading:true
};
},
methods: {
handleSizeChange(val) {
this.queryInfo.pageSize = val;
this.searchViolationByPage();
},
handleCurrentChange(val) {
this.queryInfo.pageNum = val;
this.searchViolationByPage();
},
async searchViolationByPage() {
this.loading = true;
const { data: res } = await this.$http.post(
"user/get_violation",
this.queryInfo
);
this.tableData = [];
// console.log(res);
if (res.status !== 200) {
this.total = 0;
this.loading = false;
return this.$message.error(res.msg);
}
this.$message.success({
message: res.msg,
duration: 1000,
});
this.tableData = res.data.records;
this.total = parseInt(res.data.total);
this.loading = false;
},
downLoad() {
this.getPdf(this.title); //pdf
},
fullScreen(){
// Dom: (trueORfalse)
let full = document.fullscreenElement;
//
if(!full){
// requestFullscreen
document.documentElement.requestFullscreen();
}else{
// 退
document.exitFullscreen();
}
}
},
created() {
this.searchViolationByPage();
},
};
</script>
<style>
</style>

View File

@ -0,0 +1,34 @@
import html2canvas from 'html2canvas'
import jsPDF from 'jspdf'
export default{
install (Vue, options) {
Vue.prototype.getPdf = function (htmlTitle,currentTime) {
var element = document.getElementById("pdfDom");
html2canvas(element, {
logging:false
}).then(function(canvas) {
var pdf = new jsPDF('p', 'mm', 'a4'); //A4纸纵向
var ctx = canvas.getContext('2d'),
a4w = 190, a4h = 277, //A4大小210mm x 297mm四边各保留10mm的边距显示区域190x277
imgHeight = Math.floor(a4h * canvas.width / a4w), //按A4显示比例换算一页图像的像素高度
renderedHeight = 0;
while(renderedHeight < canvas.height) {
var page = document.createElement("canvas");
page.width = canvas.width;
page.height = Math.min(imgHeight, canvas.height - renderedHeight);//可能内容不足一页
//用getImageData剪裁指定区域并画到前面创建的canvas对象中
page.getContext('2d').putImageData(ctx.getImageData(0, renderedHeight, canvas.width, Math.min(imgHeight, canvas.height - renderedHeight)), 0, 0);
pdf.addImage(page.toDataURL('image/jpeg', 1.0), 'JPEG', 10, 10, a4w, Math.min(a4h, a4w * page.height / page.width)); //添加图像到页面保留10mm边距
renderedHeight += imgHeight;
if(renderedHeight < canvas.height)
pdf.addPage();//如果后面还有内容,添加一个空页
}
pdf.save(htmlTitle);
});
}
}
}

View File

@ -0,0 +1,83 @@
//暴露自定义websocket对象
export const socket = {
//后台请求路径
url: "",
//websocket对象
websocket: null,
//websocket状态
websocketState: false,
//重新连接次数
reconnectNum: 0,
//重连锁状态,保证重连按顺序执行
lockReconnect: false,
//定时器信息
timeout: null,
clientTimeout: null,
serverTimeout: null,
//初始化方法根据url创建websocket对象封装基本连接方法并重置心跳检测
initWebSocket(newUrl) {
socket.url = newUrl;
socket.websocket = new WebSocket(socket.url);
socket.websocket.onopen = socket.websocketOnOpen;
socket.websocket.onerror = socket.websocketOnError;
socket.websocket.onclose = socket.websocketOnClose;
this.resetHeartbeat()
},
reconnect() {
//判断连接状态
if (socket.lockReconnect) return;
socket.reconnectNum += 1;
//重新连接三次还未成功调用连接关闭方法
if (socket.reconnectNum === 3) {
socket.reconnectNum = 0;
socket.websocket.onclose()
return;
}
//等待本次重连完成后再进行下一次
socket.lockReconnect = true;
//5s后进行重新连接
socket.timeout = setTimeout(() => {
socket.initWebSocket(socket.url);
socket.lockReconnect = false;
}, 5000);
},
//重置心跳检测
resetHeartbeat() {
socket.heartbeat();
},
//心跳检测
heartbeat() {
socket.clientTimeout = setTimeout(() => {
if (socket.websocket) {
//向后台发送消息进行心跳检测
socket.websocketState = false;
//一分钟内服务器不响应则关闭连接
socket.serverTimeout = setTimeout(() => {
if (!socket.websocketState) {
console.log("无响应,我要关闭")
socket.websocket.onclose()
} else {
this.resetHeartbeat()
}
}, 10 * 1000);
}
}, 3 * 1000);
},
//发送消息
sendMsg(message) {
socket.websocket.send(message);
},
websocketOnOpen(event) {
//连接开启后向后台发送消息进行一次心跳检测
console.log("WebSocket 启动")
socket.sendMsg("PING");
},
websocketOnError(error) {
console.log(error);
socket.reconnect();
},
websocketOnClose() {
socket.websocket.close();
},
};

81
src/main-dev.js Normal file
View File

@ -0,0 +1,81 @@
import Vue from 'vue'
import App from './App.vue'
//引入路由组件
import router from './router'
//引入element ui 组件库
import './plugins/element.js'
//引入粒子插件
import VueParticles from 'vue-particles'
//引入全局样式表
import './assets/css/global.css'
//引入图书管理系统的图标和字体库
import './assets/fonts/iconfont.css'
// 引入swiper的样式
// import 'swiper/css/swiper.css'
//单独引入Message axios拦截器需要
import {Message} from 'element-ui'
// 引入表格导出excel插件
import JsonExcel from 'vue-json-excel'
Vue.component('downloadExcel', JsonExcel)
// 导入pdf插件
import htmlToPdf from './components/Utils/htmlToPdf.js'
Vue.use(htmlToPdf)
// 引入echarts
import * as echarts from 'echarts'
// 引入Chrome PassiveEventListeners优化页面性能
import 'default-passive-events'
// 引入lodash
import _ from 'lodash'
Vue.prototype._ = _ //全局导入的挂载方式
// 设置全局变量
Vue.prototype.$echarts = echarts
//注册粒子插件
Vue.use(VueParticles)
//main.js:
import { vueBaberrage } from 'vue-baberrage'
//使用弹幕插件
Vue.use(vueBaberrage)
//导入axios
import axios from 'axios'
//配置请求的根路径
axios.defaults.baseURL = 'http://localhost:8889/api/'
Vue.prototype.$http = axios
// const CancelToken = axios.CancelToken;
// const source = CancelToken.source();
// export {source}
//导入NProgress包对应的js和CSS
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
//在request拦截器中展示进度条NProgress.start()
axios.interceptors.request.use(config => {
NProgress.start()
// console.log(config);
// 为请求头对象添加Token验证的Authorization字段
config.headers.Authorization = "Bearer "+window.sessionStorage.getItem('token');
return config;
})
//在response拦截器中隐藏进度条NProgress.done()
axios.interceptors.response.use(response => {
NProgress.done()
// 登录校验 响应状态码为401时拦截
if(response.data.status === 401) {
Message.error("未登录或登录过期,请重新登录");
// 清除过期的token和原来保存的用户id
window.sessionStorage.clear();
// 跳转到登录页面
router.replace('/login')
}
// }else if (response.data.status === 404){
// Message.error('访问的资源或地址不存在');
// router.replace('/404');
// }
return response
})
Vue.config.productionTip = false
new Vue({
router,
render: h => h(App)
}).$mount('#app')

76
src/main-prod.js Normal file
View File

@ -0,0 +1,76 @@
import Vue from 'vue'
import App from './App.vue'
//引入路由组件
import router from './router'
//引入element ui 组件库
import './plugins/element.js'
//引入粒子插件
import VueParticles from 'vue-particles'
//引入全局样式表
import './assets/css/global.css'
//引入图书管理系统的图标和字体库
import './assets/fonts/iconfont.css'
// 引入swiper的样式
// import 'swiper/css/swiper.css'
//单独引入Message axios拦截器需要
import {Message} from 'element-ui'
// 引入表格导出excel插件
import JsonExcel from 'vue-json-excel'
Vue.component('downloadExcel', JsonExcel)
// 导入pdf插件
import htmlToPdf from './components/Utils/htmlToPdf.js'
Vue.use(htmlToPdf)
// 引入echarts
import * as echarts from 'echarts'
// 引入Chrome PassiveEventListeners优化页面性能
import 'default-passive-events'
// 设置全局变量
Vue.prototype.$echarts = echarts
//注册粒子插件
Vue.use(VueParticles)
//main.js:
import { vueBaberrage } from 'vue-baberrage'
//使用弹幕插件
Vue.use(vueBaberrage)
//导入axios
import axios from 'axios'
//配置请求的根路径
axios.defaults.baseURL = 'http://abc.xiaobaitiao.top:443/api/'
Vue.prototype.$http = axios
// const CancelToken = axios.CancelToken;
// const source = CancelToken.source();
// export {source}
//导入NProgress包对应的js和CSS
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
// 引入lodash
import _ from 'lodash'
Vue.prototype._ = _ //全局导入的挂载方式
//在request拦截器中展示进度条NProgress.start()
axios.interceptors.request.use(config => {
NProgress.start()
// console.log(config);
// 为请求头对象添加Token验证的Authorization字段
config.headers.Authorization = "Bearer "+window.sessionStorage.getItem('token');
return config;
})
//在response拦截器中隐藏进度条NProgress.done()
axios.interceptors.response.use(response => {
NProgress.done()
// 登录校验 响应状态码为401时拦截
if(response.data.status === 401){
Message.error("未登录或登录过期,请重新登录");
// 清除过期的token和原来保存的用户id
window.sessionStorage.clear();
// 跳转到登录页面
router.replace('/login')
}
return response
})
Vue.config.productionTip = false
new Vue({
router,
render: h => h(App)
}).$mount('#app')

100
src/plugins/element.js Normal file
View File

@ -0,0 +1,100 @@
import Vue from 'vue'
// import { Button } from 'element-ui'
// import {Form,FormItem} from 'element-ui'
// import {Input} from 'element-ui'
// //导入弹框提示组件
// import {Message} from 'element-ui'
import {
Button,
Form,
FormItem,
Input,
Message,
Container,
Header,
Aside,
Main,
Menu,
Submenu,
MenuItem,
Breadcrumb,
BreadcrumbItem,
Card,
Row,
Col,
Table,
TableColumn,
Switch,
Tooltip,
Pagination,
Dialog,
MessageBox,
Tag,
Tree,
Select,
Option,
Cascader,
Alert,
Tabs,
TabPane,
Steps,
Step,
CheckboxGroup,
Checkbox,
Upload,
Avatar,
Loading,
Divider,
DatePicker,
RadioGroup,
RadioButton,
Radio,
Popover
} from 'element-ui'
Vue.use(Button)
Vue.use(Form)
Vue.use(FormItem)
Vue.use(Input)
Vue.use(Container)
Vue.use(Header)
Vue.use(Aside)
Vue.use(Main)
Vue.use(Menu)
Vue.use(Main)
Vue.use(Submenu)
Vue.use(MenuItem)
Vue.use(Breadcrumb)
Vue.use(BreadcrumbItem)
Vue.use(Card)
Vue.use(Row)
Vue.use(Col)
Vue.use(Table)
Vue.use(TableColumn)
Vue.use(Switch)
Vue.use(Tooltip)
Vue.use(Pagination)
Vue.use(Dialog)
Vue.use(Tag)
Vue.use(Tree)
Vue.use(Select)
Vue.use(Option)
Vue.use(Cascader)
Vue.use(Alert)
Vue.use(TabPane)
Vue.use(Tabs)
Vue.use(Step)
Vue.use(Steps)
Vue.use(CheckboxGroup)
Vue.use(Checkbox)
Vue.use(Upload)
Vue.use(Avatar)
Vue.use(Loading)
Vue.use(Divider)
Vue.use(DatePicker)
Vue.use(RadioGroup)
Vue.use(RadioButton)
Vue.use(Radio)
Vue.use(Popover)
//把Message挂载到vue的原型对象上可以通过this.$message获取
Vue.prototype.$message = Message
Vue.prototype.$confirm = MessageBox.confirm

164
src/router/index.js Normal file
View File

@ -0,0 +1,164 @@
import Vue from "vue";
import VueRouter from "vue-router";
Vue.use(VueRouter);
const routes = [
{ path: "/", redirect: "/login" },
{ path: "/login", component: () => import("@/components/Login.vue") },
{
path: "/loginmanage",
component: () => import("@/components/BookManage/LoginBookManage.vue"),
},
{
path: "/home",
component: () => import("@/components/Home/Home.vue"),
redirect: "/index",
children: [
{
path: "/index",
component: () => import("@/components/Index/Index.vue"),
meta: {
title: "首页",
},
},
{
path: "/search",
component: () => import("@/components/User/Search.vue"),
},
{ path: "/rule", component: () => import("@/components/User/Rule.vue") },
{
path: "/notice",
component: () => import("@/components/User/Notice.vue"),
},
{
path: "/information",
component: () => import("@/components/User/Information.vue"),
},
{
path: "/borrow",
component: () => import("@/components/User/Borrow.vue"),
},
{
path: "/violation",
component: () => import("@/components/User/Violation.vue"),
},
{
path: "/comment",
component: () => import("@/components/User/Comment.vue"),
},
{
path: "/intelligent",
component: () => import("@/components/User/Intelligent.vue"),
},
// { path: "/chat", component: () => import("@/components/User/Chat") },
],
},
{
path: "/homemange",
component: () => import("@/components/Home/HomeManage.vue"),
children: [
{
path: "/borrowbook",
component: () => import("@/components/BookManage/BorrowBook.vue"),
},
{
path: "/returnbook",
component: () => import("@/components/BookManage/ReturnBook.vue"),
},
{
path: "/borrowstatement",
component: () => import("@/components/BookManage/BorrowStatement.vue"),
},
{
path: "/returnstatement",
component: () => import("@/components/BookManage/ReturnStatement.vue"),
},
{
path: "/noticemanage",
component: () => import("@/components/BookManage/NoticeManage.vue"),
},
{
path: "/bookexpire",
name: "bookexpire",
component: () => import("@/components/BookManage/BookExpire.vue"),
},
],
},
{
path: "/loginadmin",
component: () => import("@/components/Admin/LoginAdmin.vue"),
},
{
path: "/homeadmin",
component: () => import("@/components/Home/HomeAdmin.vue"),
children: [
{
path: "/bookmanage",
component: () => import("@/components/Admin/BookManage.vue"),
},
{
path: "/booktype",
component: () => import("@/components/Admin/BookType.vue"),
},
{
path: "/statementmanage",
component: () => import("@/components/Admin/StatementManage.vue"),
},
{
path: "/statementsearch",
component: () => import("@/components/Admin/StatementSearch.vue"),
},
{
path: "/statementrulemanage",
component: () => import("@/components/Admin/StatementRuleManage.vue"),
},
{
path: "/bookadminmanage",
component: () => import("@/components/Admin/BookAdminManage.vue"),
},
{
path: "/adminmanage",
component: () => import("@/components/Admin/AdminManage.vue"),
},
{
path: "/intelligent_analysis",
component: () => import("@/components/Admin/IntelligentAnalysis.vue"),
},
],
},
{
path: "/404",
component: () => import("@/components/404/404.vue"),
},
{
path: "/:pathMatch(.*)*",
redirect: "/404",
},
];
const router = new VueRouter({
routes,
});
// //挂载路由导航守卫
router.beforeEach((to, from, next) => {
//to将要访问的路径
//from代表从哪个路径跳转而来
//next是一个函数表示放行
const tokenStr = window.sessionStorage.getItem("token");
if (tokenStr) {
next();
} else {
// 用户未登录判断
if (
to.path === "/login" ||
to.path === "/loginmanage" ||
to.path === "/loginadmin"
) {
next();
} else {
next({ path: "/login" });
}
}
});
export default router

43
vue.config.js Normal file
View File

@ -0,0 +1,43 @@
const { defineConfig } = require('@vue/cli-service')
const NodePolyfillPlugin = require('node-polyfill-webpack-plugin')
const path = require('path')
module.exports = defineConfig({
configureWebpack: {
plugins: [new NodePolyfillPlugin()],
resolve: {
alias: {
"@": path.resolve("./src") // 相对路径别名配置,使用 @ 代替 src
}
},
},
transpileDependencies: true,
lintOnSave: false,
devServer: {
host: '0.0.0.0',
port: 8081,
client: {
webSocketURL: 'ws://0.0.0.0/ws',
},
},
chainWebpack:config =>{
//发布模式
config.when(process.env.NODE_ENV === 'production',config =>{
config.entry('app').clear().add('./src/main-prod.js')
config.plugin('html').tap(args => {
args[0].isProd = true
return args
})
})
//开发模式
config.when(process.env.NODE_ENV === 'development',config =>{
config.entry('app').clear().add('./src/main-dev.js')
config.plugin('html').tap(args => {
args[0].isProd = false
return args
})
})
}
})