pull/1/head
“tang” 1 month ago
parent 5f5d3d4f22
commit 672b660b69

@ -0,0 +1 @@
registry=https://registry.npmmirror.com

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

File diff suppressed because it is too large Load Diff

@ -0,0 +1,64 @@
{
"name": "prosonal-health-web",
"version": "1.0.0",
"private": true,
"scripts": {
"dev": "vue-cli-service serve",
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
"dependencies": {
"@wangeditor/editor": "^5.1.23",
"@wangeditor/editor-for-vue": "^1.0.2",
"axios": "^0.21.1",
"core-js": "^3.6.5",
"crypto-js": "^4.2.0",
"echarts": "^4.8.0",
"element-china-area-data": "^6.1.0",
"element-ui": "^2.15.14",
"fingerprintjs2": "^2.1.4",
"js-md5": "^0.8.3",
"jwt-decode": "^3.1.2",
"sm4util": "^1.0.5",
"vue": "^2.6.11",
"vue-router": "^3.2.0",
"vue-sweetalert2": "^5.0.10",
"wangeditor": "^4.7.15"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~4.5.0",
"@vue/cli-plugin-eslint": "~4.5.0",
"@vue/cli-plugin-router": "~4.5.0",
"@vue/cli-service": "~4.5.0",
"@vue/eslint-config-prettier": "^6.0.0",
"babel-eslint": "^10.1.0",
"eslint": "^6.7.2",
"eslint-plugin-prettier": "^3.1.3",
"eslint-plugin-vue": "^6.2.2",
"prettier": "^1.19.1",
"sass": "^1.32.0",
"sass-loader": "^10.2.0",
"vue-template-compiler": "^2.6.11"
},
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/essential",
"eslint:recommended",
"@vue/prettier"
],
"parserOptions": {
"parser": "babel-eslint"
},
"rules": {}
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead"
]
}

@ -0,0 +1,20 @@
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="/logo.png">
<title>图书管理系统 - 借书 | 还书 | 订阅书 </title>
</head>
<body>
<div id="app"></div>
</body>
<style>
</style>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

@ -0,0 +1,11 @@
<template>
<div id="app">
<router-view />
</div>
</template>
<style lang="scss">
#app{
height: 100%;
}
</style>

@ -0,0 +1,23 @@
body {
margin: 0;
}
.icon-r-edit:before {
content: "" !important;
}
.icon-r-no:before {
content: "" !important;
}
.icon-r-yes:before {
content: "" !important;
}
.icon-r-delete:before {
content: "" !important;
}
.icon-r-add:before {
content: "" !important;
}

@ -0,0 +1,65 @@
//
.edit-button{
background-color: rgb(56, 183, 129);
font-size: 12px;
padding: 2px 15px;
border-radius: 3px;
height: 25px;
line-height: 25px;
display: inline-block;
user-select: none;
cursor: pointer;
color: #FFFFFF;
margin-right: 6px;
}
.edit-button:hover{
background-color: rgb(25, 136, 88);
color: #FFFFFF;
}
//
.channel-button{
background-color: rgb(245, 245, 245);
font-size: 12px;
padding: 2px 15px;
height: 25px;
line-height: 25px;
border-radius: 3px;
display: inline-block;
user-select: none;
cursor: pointer;
color: #1c1c1c;
margin: 0 10px;
}
.channel-button:hover{
color: #304a99;
}
//
.text-button{
margin-right: 10px;
color: rgb(43, 121, 203);
cursor: pointer;
}
//
.delete-button{
background-color: rgb(241, 241, 241);
font-size: 12px;
padding: 2px 15px;
height: 25px;
line-height: 25px;
border-radius: 3px;
display: inline-block;
user-select: none;
cursor: pointer;
color: #1c1c1c;
}
.delete-button:hover{
background-color: rgb(167, 83, 90);
font-size: 12px;
color: #FFFFFF;
}

@ -0,0 +1,39 @@
//
.theme-tag {
color: #1c1c1c;
background-color: rgb(237, 235, 235);
padding: 3px 10px;
border-radius: 3px;
font-size: 12px;
margin-right: 5px;
}
//
.dialog-title {
font-size: 16px;
padding: 15px 20px;
user-select: none;
color: rgb(107, 106, 106);
}
.dialog-avatar {
width: 98px;
height: 98px;
}
.dialog-input {
font-size: 30px;
font-weight: 600;
width: 100%;
border: 5px;
padding: 4px 5px;
border: none;
outline: none;
user-select: none;
}
.dialog-hover{
display: inline-block;
padding: 10px 6px;
font-size: 12px;
color: rgb(107, 106, 106);
}

@ -0,0 +1,64 @@
//
.html-content {
/* table 样式 */
table {
border-top: 1px solid #ccc;
border-left: 1px solid #ccc;
}
table td,
table th {
border-bottom: 1px solid #ccc;
border-right: 1px solid #ccc;
padding: 3px 5px;
}
table th {
border-bottom: 2px solid #ccc;
text-align: center;
}
/* blockquote 样式 */
blockquote {
display: block;
border-left: 8px solid #d0e5f2;
padding: 5px 10px;
margin: 10px 0;
line-height: 1.4;
font-size: 100%;
background-color: #f1f1f1;
}
/* code 样式 */
code {
display: inline-block;
*display: inline;
*zoom: 1;
background-color: #f1f1f1;
border-radius: 3px;
padding: 3px 5px;
margin: 0 3px;
}
pre code {
display: block;
}
/* ul ol 样式 */
ul,
ol {
margin: 10px 0 10px 20px;
}
}
.w-e-textarea-video-container {
background-image: none !important;
padding: 0 !important;
margin: 0 !important;
//
video {
width: 100% !important;
height: 300px !important;
}
}

@ -0,0 +1,174 @@
// ElementUI
.dialog-footer {
/* 使按钮水平居中 */
display: flex;
justify-content: center;
align-items: center;
}
.el-slider__bar {
height: 16px;
background-color: #c1cc57;
}
.el-slider__button-wrapper {
height: 66px;
width: 66px;
top: -25px;
}
.el-switch.is-checked .el-switch__core{
border-color: rgb(43, 121, 203) !important;
background-color: rgb(43, 121, 203) !important;
}
.avatar-uploader .el-upload {
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
}
/* 标签栏 */
.el-tabs__active-bar {
height: 2px !important;
border-radius: 3px;
}
/* 标签栏 */
.el-tabs__item {
height: 50px !important;
line-height: 50px !important;
user-select: none;
}
.avatar-uploader .el-upload:hover {
border-color: #409EFF;
}
.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 88px;
height: 88px !important;
line-height: 88px !important;
text-align: center;
}
.el-timeline {
padding: 10px;
}
.el-timeline-item__tail {
border-left: 2px solid #155599 !important;
}
.el-timeline-item__node {
background-color: #155599 !important;
}
/* 表格表头 */
.el-table thead tr th {
color: #656464 !important;
font-weight: 400;
font-family: 'Gill Sans', 'Gill Sans MT', Calibri, 'Trebuchet MS', sans-serif;
//background-color: rgb(246, 246, 246) !important;
text-align: left !important;
}
.el-dialog__body {
padding: 0 !important;
}
.el-badge__content.is-fixed {
top: 4px !important;
right: 6px !important;
}
.el-dialog__header {
padding: 0 !important;
}
/* 多选框偏移 */
.el-table th>.cell {
padding-left: 15px !important;
font-family: 'Gill Sans', 'Gill Sans MT', Calibri, 'Trebuchet MS', sans-serif;
}
/* 表格单元格 */
.el-table__row .cell {
font-size: 14px !important;
color: #1c1c1c;
padding-left: 15px !important;
}
/* 表格多选框颜色 */
.el-checkbox__input.is-checked .el-checkbox__inner,
.el-checkbox__input.is-indeterminate .el-checkbox__inner {
background-color: #a7535a !important;
border-color: #a7535a !important;
}
/* 分页显示色 */
.el-pager li.active {
color: #565555 !important;
cursor: default;
border-radius: 3px;
border: 1px solid rgb(240, 241, 242);
background-color: rgb(240, 241, 242);
}
.el-radio-button__orig-radio:checked+.el-radio-button__inner {
color: #ffffff !important;
background-color: #64d134 !important;
border-color: #64d134 !important;
box-shadow: -1px 0 0 0 #64d134 !important;
}
.el-menu {
border-right: none !important;
}
.w-e-text-container [data-slate-editor] pre>code {
text-shadow: none !important;
background-color: #2d2d2d !important;
color: aliceblue !important;
padding: 0 10px !important;
}
/* 代码高亮插入后,会出现 “=” 背景色,这里去掉 */
.token.operator,
.token.entity,
.token.url,
.language-css .token.string,
.style .token.string {
background: none !important;
}
#w-e-text-12 span {
background-color: #268611 !important;
}
/* 气泡确认框 */
.el-popconfirm__main {
padding-bottom: 21px !important;
}
/* 标签 */
.el-tag+.el-tag {
margin-left: 10px !important;
}
.el-dropdown-menu {
padding: 20px !important;
user-select: none !important;
color: #1c1c1c;
.el-dropdown-item:hover {
background-color: #ffffff !important;
}
}

@ -0,0 +1,25 @@
//
.input-title {
width: 100%;
border: none;
outline: none;
padding: 5px 10px;
font-size: 22px;
font-weight: bold;
}
.top-bar {
display: inline-block;
margin: 0 10px;
font-size: 14px;
color: #909399;
}
.input-child {
width: 100%;
border: none;
outline: none;
padding: 5px 10px;
font-size: 22px;
font-weight: bold;
}

@ -0,0 +1,122 @@
<template>
<div class="line-main">
<div>
<span class="tag">{{ tag }}</span>
</div>
<div ref="chart" :style="{ width: '100%', height: height }"></div>
</div>
</template>
<script>
//
import * as echarts from 'echarts'
export default {
name: 'BarChart',
props: {
height: {
type: String,
default: '300px'
},
tag: {
type: String,
default: '柱状图'
},
values: {
type: Array,
required: true
},
date: {
type: Array,
required: true
}
},
data() {
return {
chart: null,
}
},
mounted() {
this.init();
},
methods: {
//
init() {
this.chart = echarts.init(this.$refs.chart)
let option = {
grid: {
left: 30,
right: 5,
top: 10,
borderWidth: 5,
},
title: { text: '' },
tooltip: {},
xAxis: {
data: this.date,
axisLine: { show: false },
axisTick: { show: false },
axisLabel: { // X
color: 'rgb(102, 102, 102)',
interval: 1, //
rotate: 0, // 45
},
},
yAxis: {
axisLine: { show: false },
axisTick: { show: false },
axisLabel: {
color: 'rgb(102, 102, 102)',
fontSize: '12'
},
},
series: [{
name: '',
type: 'bar',
data: this.values,
axisLine: { show: false },
axisTick: { show: false },
axisLabel: { // y
color: 'rgb(102, 102, 102)',
},
itemStyle: {
normal: {
color: function (params) {
const colorList = [
'#e2e1e4',
'#bc84a8',
'#5e616d',
'#57c3c2',
'#87CEEB',
'#ADD8E6'
];
return colorList[params.dataIndex % colorList.length];
}
}
},
}]
}
this.chart.setOption(option)
}
},
beforeDestroy() {
if (!this.chart) {
return
}
this.chart.dispose()
}
}
</script>
<style scoped lang="scss">
.line-main {
margin-bottom: 5px;
border-radius: 3px;
.tag {
font-size: 16px;
padding: 15px 6px;
display: inline-block;
color: #333;
font-weight: bold;
}
}
</style>

@ -0,0 +1,105 @@
<template>
<div>
<Toolbar style="border-bottom: 1px solid #eae8e8;" :editor="editor" :defaultConfig="toolbarConfig"
:mode="mode" />
<Editor :style="{ height: height, overflowY: 'hidden' }" v-model="content" :defaultConfig="editorConfig"
:mode="mode" @onCreated="onCreated" />
</div>
</template>
<script>
import Vue from 'vue'
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
export default Vue.extend({
components: { Editor, Toolbar },
props: {
receiveContent: {
type: String,
default: '',
required: true
},
height: {
type: String,
default: 'calc(100vh - 100px)'
}
},
data() {
return {
editor: null,
content: '<p>创作内容</p>',
toolbarConfig: {},
editorConfig: {
placeholder: '请输入题目...',
MENU_CONF: {
uploadImage: {
server: '/api/book-manage-sys-api/v1.0/file/upload',
fieldName: 'file',
maxFileSize: 10 * 1024 * 1024,
maxNumberOfFiles: 10,
// allowedFileTypes: ['image/*'],
metaWithUrl: false,
withCredentials: true,
timeout: 10 * 1000,
headers: {
'token': sessionStorage.getItem('token')
},
customInsert(res, insertFn) {
insertFn(res.data, res.data, res.data);
},
},
uploadVideo: {
server: '/api/book-manage-sys-api/v1.0/file/upload',
fieldName: 'file',
maxFileSize: 100 * 1024 * 1024,
headers: {
'token': sessionStorage.getItem('token')
},
timeout: 60 * 1000,
customInsert(res, insertFn) {
insertFn(res.data, res.data);
},
}
}
},
mode: 'default',
}
},
methods: {
onCreated(editor) {
this.editor = Object.seal(editor);
this.toolbarConfig.excludeKeys = ['group-video','insertLink','fullScreen','emotion','insertTable'];
},
},
watch: {
receiveContent: {
handler(v1, v2) {
this.content = v1;
},
deep: true, //
immediate: true
},
content(newVal, oldVal) {
this.$emit('on-receive', newVal);
},
},
beforeDestroy() {
const editor = this.editor;
if (editor == null) return;
editor.destroy();
}
})
</script>
<style src="@wangeditor/editor/dist/css/style.css"></style>
<style scoped>
.line-number {
display: block;
margin-right: 10px;
/* 以下样式确保行号不被选中或复制 */
pointer-events: none;
user-select: none;
-webkit-user-select: none;
color: #999;
/* 行号颜色,可自定义 */
text-align: right;
/* 行号对齐方式 */
}
</style>

@ -0,0 +1,615 @@
<template>
<div style="width: 100%;">
<el-row style="margin: 5px 0;">
<h2 class="commentHeader">评论&nbsp;{{ evaluationsCount }}</h2>
<el-row style="margin: 15px 0;">
<el-col :span="2">
<el-avatar :src="userData.userAvatar"></el-avatar>
</el-col>
<el-col :span="22">
<div class="parent-comment"
:style="{ backgroundColor: bgColor, height: isFocused ? '120px' : '70px', borderColor: isFocused ? '#007bff' : 'transparent' }">
<textarea class="comment-parent-input" v-model="content" placeholder="请友好交流" @focus="onFocus"
@blur="onBlur"></textarea>
<div>
<span class="comment-input-number">{{ content.length }} / 300</span>
<el-button
:style="{ backgroundColor: isFocused ? '#007bff' : '#666', borderColor: isFocused ? '#007bff' : '#666' }"
@click="commentClick" class="comment-clike" size="mini" type="primary">评论</el-button>
</div>
</div>
</el-col>
</el-row>
</el-row>
<el-row style="margin: 5px 0px;">
<el-row v-for="(comment, index) in commentList " :key="index" style="padding: 10px 0;">
<el-row>
<el-col :span="2">
<el-avatar size="large" :src="comment.userAvatar"></el-avatar>
</el-col>
<el-col :span="22">
<span style="height: 40px;line-height: 40px;font-size: 16px;color: #515767;">{{
comment.userName }}</span>
<span v-if="comment.userId == userId" class="my-body-tag"></span>
</el-col>
</el-row>
<el-row style="padding: 8px 0;">
<el-col :span="22" :offset="2">
<span style="font-size: 16px;color: #252933;">{{ comment.content }}</span>
</el-col>
</el-row>
<el-row style="padding: 8px 0;">
<el-col :span="22" :offset="2">
<span style="font-size: 14px;color: #8A919F;">{{ comment.time }}</span>
<el-popconfirm confirm-button-text='' cancel-button-text='' icon="el-icon-info"
icon-color="red" title="删除该条评论?" v-if="comment.userId == userId"
@confirm="deleteComment(comment)">
<span slot="reference"
style="cursor: pointer;margin-left: 15px;font-size: 14px;color: #8A919F;user-select: none;">
<i class="el-icon-delete"></i>
删除
</span>
</el-popconfirm>
<span @click="toggleReplyInput(comment)"
style="cursor: pointer;margin-left: 15px;font-size: 14px;color: #8A919F;user-select: none;">
<i class="el-icon-chat-dot-round"></i>
回复<span v-if="comment.childTotal != 0">({{ comment.childTotal }})</span>
</span>
<span @click="upvote(comment)"
style="cursor: pointer;margin-left: 15px;font-size: 14px;color: #8A919F;user-select: none;">
<i class="el-icon-discount" v-if="!comment.upvoteFlag"></i>
<i class="el-icon-discount" v-else style="color: #1E80FF;">&nbsp;{{ comment.upvoteCount
}}</i>
</span>
</el-col>
</el-row>
<!-- 父级评论的回复按钮和输入框 -->
<el-row v-if="comment.showReplyInput" style="padding: 10px 0;">
<el-col :span="22" :offset="2">
<div class="parent-comment"
:style="{ backgroundColor: bgColor, height: '110px', borderColor: '#007bff' }">
<textarea class="comment-parent-input" v-model="replyContent"
:placeholder="replyText"></textarea>
<div>
<span class="comment-input-number">{{ replyContent.length }} / 300</span>
<el-button style="background-color: #007bff;user-select: none;"
@click="submitReply(comment)" class="comment-clike" size="mini"
type="primary">评论</el-button>
</div>
</div>
</el-col>
</el-row>
<!-- 子级评论 -->
<el-row v-for="(commentChild, index) in comment.commentChildVOS " :key="index"
style="padding: 10px 15px;font-size: 16px;">
<el-row>
<el-col :span="22" :offset="2">
<el-row style="display: flex; align-items: center; flex-wrap: wrap;">
<el-avatar size="small" :src="commentChild.userAvatar"
style="margin-right: 5px;"></el-avatar>
<span style="color: #515767; padding: 0 5px;">{{ commentChild.userName }}</span>
<span v-if="commentChild.userId == userId" class="my-body-tag"></span>
<span v-if="commentChild.replierName != null"
style="margin:0 15px;color: #1c1c1c;user-select: none;font-size: 12px;">
回复
</span>
<el-avatar v-if="commentChild.replierName != null" size="small"
:src="commentChild.replierAvatar" style="margin-right: 5px;"></el-avatar>
<span v-if="commentChild.replierName != null" style="color: #515767;padding: 0 5px;">{{
commentChild.replierName }}</span>
<span v-if="commentChild.replierId == userId" class="my-body-tag"></span>
<span
style="letter-spacing: 1px;font-size: 16px; color: #252933; white-space: normal; margin-left: 5px;padding: 6px 0;">
: {{ commentChild.content }}
</span>
</el-row>
<el-row style="padding: 10px 0;">
<span style="font-size: 14px;color: #8A919F;">{{ commentChild.time }}</span>
<el-popconfirm confirm-button-text='' cancel-button-text='' icon="el-icon-info"
icon-color="red" title="删除该条评论?" v-if="commentChild.userId == userId"
@confirm="deleteComment(commentChild)">
<span slot="reference"
style="cursor: pointer;margin-left: 15px;font-size: 14px;color: #8A919F;user-select: none;">
<i class="el-icon-delete"></i>
删除
</span>
</el-popconfirm>
<span @click="toggleReplyInput1(commentChild)"
style="cursor: pointer;margin-left: 15px;font-size: 14px;color: #8A919F;user-select: none;">
<i class="el-icon-chat-dot-round"></i>
回复
</span>
<span @click="upvote(commentChild)"
style="cursor: pointer;margin-left: 15px;font-size: 14px;color: #8A919F;user-select: none;">
<i class="el-icon-discount" v-if="!commentChild.upvoteFlag"></i>
<i class="el-icon-discount" v-else style="color: #1E80FF;">&nbsp;{{
commentChild.upvoteCount }}</i>
</span>
</el-row>
<!-- 子级评论的回复按钮和输入框 -->
<el-row v-if="commentChild.replyInputStatus" style="padding: 10px 0;">
<el-col :span="24">
<div class="parent-comment"
:style="{ backgroundColor: bgColor, height: '110px', borderColor: '#007bff' }">
<textarea class="comment-parent-input" v-model="replyChildContent"
:placeholder="replyText"></textarea>
<div>
<span class="comment-input-number">{{ replyChildContent.length }} /
300</span>
<el-button style="background-color: #007bff;"
@click="submitReply1(commentChild)" class="comment-clike" size="mini"
type="primary">评论</el-button>
</div>
</div>
</el-col>
</el-row>
</el-col>
</el-row>
</el-row>
</el-row>
</el-row>
<!-- 举报反馈对话框 -->
<el-dialog style="user-select: none;border-radius: 5px;" title="我要举报" :visible.sync="dialogVisibleReport"
width="30%">
<el-row v-for="(item, index) in reports" :key="index" style="margin-top: 10px;">
<el-row style="padding-bottom: 10px;user-select: none;">*{{ item.name }}</el-row>
<el-row>
<span v-for="(itemChild, indexChild) in item.list" :key="indexChild"
@click="reportItemClick(itemChild)">
<button :style="{ border: itemChild.isSelected ? '1px solid #4b87bc' : '1px solid #f4f4f4' }"
class="reportItem">
{{ itemChild.name }}
</button>
</span>
</el-row>
</el-row>
<span slot="footer" class="dialog-footer">
<button class="cannel-btn" @click="dialogVisibleReport = false">取消</button>
<button class="yes-btn" @click="operationReport"></button>
</span>
</el-dialog>
</div>
</template>
<script>
import { timeAgo } from '@/utils/data'
export default {
props: {
contentId: {
type: Number,
default: ''
},
contentType: {
type: String,
default: ''
}
},
data() {
return {
userData: {},
noteData: {},
commentContent: '',
content: '',
commentList: [],
replyContent: '',
id: null,
isFocused: false,
bgColor: 'rgb(245 245 245)',
strLength: '0/300',
replyText: '',
userId: '',
dialogVisibleReport: false,
reports: [],
selectdStatus: false,
evaluationsCount: 0,
comment: {},
replyChildContent: '',
};
},
watch: {
contentId(newVal,oldVal){
if(newVal !== oldVal){
this.loadCommentList();
}
},
content() {
if (this.content === '') {
this.isFocused = false;
return;
}
},
},
created() {
this.getUserInfo();
this.loadCommentList();
},
methods: {
getUserInfo() {
const userInfo = sessionStorage.getItem("userInfo");
this.userData = JSON.parse(userInfo);
this.userId = this.userData.id;
},
//
upvote(comment) {
let upvoteList = comment.upvoteList ? comment.upvoteList.split(',') : [];
if (upvoteList.length) {
//
if (comment.upvoteFlag) {
//
let index = upvoteList.indexOf(this.userData.id.toString());
if (index !== -1) {
upvoteList.splice(index, 1); // ID
}
} else {
//
if (!upvoteList.includes(this.userData.userId.toString())) {
upvoteList.push(this.userData.userId.toString()); // ID
}
}
}
let evalustions = {
id: comment.id,
upvoteList: upvoteList.length ? upvoteList.join(',') : this.userData.id
}
this.$axios.put(`evaluations/update`, evalustions).then(res => {
if (res.data.code == 200) {
comment.upvoteList = upvoteList.join(','); // upvoteList
comment.upvoteFlag = !comment.upvoteFlag; //
comment.upvoteCount += 1;
}
}).catch(err => {
console.error(`点赞状态设置异常 -> `, err);
})
},
//
operationReport() {
let reportItem = [];
this.reports.forEach(entity => {
let entityReport = entity.list.filter(child => child.isSelected);
if (entityReport.length != 0) {
reportItem = entityReport;
}
});
if (!reportItem.length) {
this.$message(`请选中举报项`);
return;
}
this.$axios.get(`evaluations-reports/report/${this.comment.id}/${reportItem[0].name}`).then(res => {
this.dialogVisibleReport = false;
if (res.data.code == 200) {
this.$swal.fire({
title: '举报操作',
text: '举报成功',
icon: 'success',
showConfirmButton: false,
timer: 1100
});
} else {
this.$swal.fire({
title: '举报操作',
text: res.data.msg,
icon: 'error',
showConfirmButton: false,
timer: 1100
});
}
}).catch(err => {
console.error(`评论举报异常 -> `, err);
})
},
//
reportItemClick(itemChild) {
this.reports.forEach(entity => {
entity.list.forEach(child => {
child.isSelected = false;
})
})
itemChild.isSelected = true;
},
reportList() {
this.$axios.get(`evaluations/reportList`).then(res => {
if (res.data.code == 200) {
this.reports = [];
res.data.data.forEach(entity => {
let report = { name: entity.name };
let resportList = [];
entity.list.forEach(listItem => {
let reportChild = {};
reportChild.name = listItem;
reportChild.isSelected = false;
resportList.push(reportChild);
})
report.list = resportList;
this.reports.push(report);
})
}
}).catch(err => {
console.error(`举报项加载失败 -> `, err);
})
},
reportComment(comment) {
this.reportList();
this.dialogVisibleReport = true;
this.comment = comment;
},
deleteComment(comment) { //
this.$axios.delete(`evaluations/delete/${comment.id}`).then(res => {
if (res.data.code == 200) {
this.$message.success(res.data.msg);
this.loadCommentList();
}
}).catch(err => {
console.error(`评论异常 -> `, err);
})
},
onFocus() {
this.isFocused = true;
},
//
onBlur() {
if (this.content === '') {
this.isFocused = false;
return;
}
this.isFocused = true;
},
commentClick() {
if (this.content == '') {
this.$swal.fire({
title: '内容提示',
text: '评论内容为空',
icon: 'success',
showConfirmButton: false,
timer: 800
});
return;
}
const evaluations = {
contentType: this.contentType,
content: this.content,
contentId: this.contentId,
}
this.$axios.post(`evaluations/insert`, evaluations).then(res => {
if (res.data.code == 200) {
this.content = '';
this.$swal.fire({
title: '评论操作',
text: '评论成功',
icon: 'success',
showConfirmButton: false,
timer: 1100
});
setTimeout(() => {
this.loadCommentList()
}, 1100)
}else{
this.$swal.fire({
title: '评论异常',
text: res.data.msg,
icon: 'error',
showConfirmButton: false,
timer: 1100
});
}
}).catch(err => {
console.error(`评论异常 -> `, err);
})
},
//
toggleReplyInput(comment) {
this.replyText = `回复${comment.userName}...`;
if (comment.showReplyInput == null) {
comment.showReplyInput = false;
}
comment.showReplyInput = !comment.showReplyInput;
},
//
toggleReplyInput1(comment) {
if (comment.replyInputStatus == null) {
comment.replyInputStatus = false;
}
comment.replyInputStatus = !comment.replyInputStatus;
},
//
submitReply(comment) {
if (this.replyContent == '') {
this.$message(`评论内容不能为空`);
return;
}
const evaluationsDTO = {
contentType: this.contentType,
content: this.replyContent,
contentId: this.contentId,
parentId: comment.id
}
this.$axios.post(`evaluations/insert`, evaluationsDTO).then(res => {
if (res.data.code == 200) {
this.replyContent = '';
comment.showReplyInput = false;
this.$swal.fire({
title: '回复操作',
text: '回复成功',
icon: 'success',
showConfirmButton: false,
timer: 1300
});
setTimeout(() => {
//
this.loadCommentList();
}, 1300)
}else{
this.$swal.fire({
title: '评论异常',
text: res.data.msg,
icon: 'error',
showConfirmButton: false,
timer: 1100
});
}
}).catch(err => {
console.error(`评论异常 -> `, err);
})
},
//
submitReply1(comment) {
if (this.replyChildContent == '') {
this.$message(`评论内容不能为空`);
return;
}
const evaluationsDTO = {
replierId: comment.userId,
contentType: this.contentType,
content: this.replyChildContent,
contentId: this.contentId,
parentId: comment.parentId
}
this.$axios.post(`evaluations/insert`, evaluationsDTO).then(res => {
if (res.data.code == 200) {
this.content = '';
comment.replyInputStatus = false;
this.$swal.fire({
title: '回复操作',
text: '回复成功',
icon: 'success',
showConfirmButton: false,
timer: 1300
});
setTimeout(() => {
//
this.loadCommentList();
}, 1300)
}else{
this.$swal.fire({
title: '评论异常',
text: res.data.msg,
icon: 'error',
showConfirmButton: false,
timer: 1100
});
}
}).catch(err => {
console.error(`评论异常 -> `, err);
})
},
goBack() {
//
this.$router.go(-1);
},
//
loadCommentList() {
this.$axios.get(`evaluations/list/${this.contentId}/${this.contentType}`).then(res => {
if (res.data.code == 200) {
this.commentList = res.data.data.data;
this.evaluationsCount = res.data.data.evaluationsCount;
//
this.commentList.forEach(entity => {
//
entity.time = timeAgo(entity.createTime);
//
entity.commentChildVOS.forEach(entity => entity.time = timeAgo(entity.createTime));
});
}
}).catch(err => {
console.error(`评论查询异常异常 -> `, err);
})
},
}
};
</script>
<style lang="scss">
.cannel-btn,
.yes-btn {
padding: 0px 15px 5px 15px;
font-size: 14px !important;
margin: 0 10px;
border-radius: 3px;
border: none;
}
.cannel-btn {
color: #1c1c1c;
}
.yes-btn {
background-color: #4b87bc;
color: #EAF2FF;
}
.cannel-btn:hover {
background-color: #f5f5f5;
}
.yes-btn:hover {
background-color: #66a8e1;
}
.commentHeader {
color: #252933;
font-size: 18px;
font-weight: 600;
line-height: 30px;
}
.comment-parent-input {
outline: none;
border: none;
background-color: rgb(245 245 245);
font-size: 16px;
padding: 6px;
width: 100%;
min-height: 60px;
overflow: auto;
resize: vertical;
user-select: none;
margin: 0 0 20px 0;
display: block;
}
.parent-comment {
padding: 6px 12px;
border-radius: 3px;
transition: height 0.3s ease, border-color 0.3s ease;
border: 1px solid transparent;
user-select: none;
position: relative;
}
.comment-input-number {
position: absolute;
left: 10px;
bottom: 5px;
padding: 0 6px;
font-size: 12px;
color: #666;
}
.comment-clike {
position: absolute;
right: 10px;
bottom: 5px;
}
.my-body-tag {
font-size: 12px;
padding: 3px 4px;
color: #1E80FF;
background-color: #EAF2FF;
margin-left: 5px;
}
.reportItem {
display: inline-block;
padding: 8px 22px;
background-color: #f4f4f4;
border: 1px solid #f4f4f4;
margin: 5px 3px 5px 0;
border-radius: 3px;
cursor: pointer;
user-select: none;
}
.reportItem:hover {
border: 1px solid #4b87bc;
color: #4b87bc;
}
</style>

@ -0,0 +1,83 @@
<template>
<div>
<p>倒计时: {{ formattedTime }}</p>
</div>
</template>
<script>
export default {
props: {
//
initialMinutes: {
type: Number,
required: true,
default: 0 // 0
}
},
data() {
return {
// props
totalSeconds: 0,
// setInterval便
timerId: null
};
},
computed: {
// HH:MM:SS
formattedTime() {
let hours = Math.floor(this.totalSeconds / 3600);
let minutes = Math.floor((this.totalSeconds % 3600) / 60);
let seconds = this.totalSeconds % 60;
hours = hours.toString().padStart(2, '0');
minutes = minutes.toString().padStart(2, '0');
seconds = seconds.toString().padStart(2, '0');
return `${hours}:${minutes}:${seconds}`;
}
},
watch: {
// initialMinutes
initialMinutes(newValue) {
if (this.timerId) {
//
clearInterval(this.timerId);
this.timerId = null;
}
this.totalSeconds = newValue * 60; //
//
this.startTimer();
}
},
created() {
//
this.totalSeconds = this.initialMinutes * 60;
this.startTimer();
},
methods: {
startTimer() {
this.timerId = setInterval(() => {
if (this.totalSeconds > 0) {
this.totalSeconds--;
} else {
//
clearInterval(this.timerId);
this.timerId = null;
//
this.$emit('end');
}
}, 1000);
}
},
beforeDestroy() {
//
if (this.timerId) {
clearInterval(this.timerId);
}
}
};
</script>
<style scoped>
/* 添加你的样式 */
</style>

@ -0,0 +1,124 @@
<template>
<div class="main">
<span style="margin-left: 20px;">
<span class="operation-span" @click="operation">
<i v-if="!showFlag" class="el-icon-s-fold i-folder"></i>
<i v-else class="el-icon-s-unfold i-folder"></i>
</span>
</span>
<span>
<span class="operation-span-tag">
&nbsp;&nbsp;{{ tag == '' ? '元数据' : tag }}
</span>
</span>
<span class="user-block">
<el-dropdown class="user-dropdown">
<span class="el-dropdown-link" style="display: flex; align-items: center;">
<el-avatar :size="35" :src="userInfo.url" style="margin-top: 0;"></el-avatar>
<span class="userName" style="margin-left: 5px;font-size: 16px;">{{ userInfo.name }}</span>
<i class="el-icon-arrow-down el-icon--right" style="margin-left: 5px;"></i>
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item icon="el-icon-user-solid" @click.native="userCenterPanel">个人资料</el-dropdown-item>
<el-dropdown-item icon="el-icon-s-fold" @click.native="loginOut">退出登录</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</span>
</div>
</template>
<script>
export default {
name: "LevelHeader",
data() {
return {
showFlag: sessionStorage.getItem('flag') === 'true',
};
},
props: {
tag: {
type: String,
required: true,
default: ''
},
userInfo: {
type: Object,
required: true,
default: {}
},
bag: {
type: String,
default: ''
},
},
methods: {
//
userCenterPanel() {
this.$emit('eventListener', 'center');
},
// 退
loginOut() {
this.$emit('eventListener', 'loginOut');
},
operation() {
this.showFlag = !this.showFlag;
sessionStorage.setItem('flag', this.showFlag);
this.$emit('selectOperation', this.showFlag);
},
}
};
</script>
<style scoped lang="scss">
.main {
padding: 26px 30px 26px 0;
display: flex;
align-items: center;
flex-wrap: wrap;
width: 100%;
position: relative;
background-color: rgb(255, 255, 255);
color: #666;
.operation-span-tag {
padding: 9px 10px;
border-radius: 3px;
font-size: 22px;
font-family:'Gill Sans', 'Gill Sans MT', Calibri, 'Trebuchet MS', sans-serif;
user-select: none;
margin-top: 15px;
}
.operation-span:hover {
background-color: rgb(248, 248, 248);
}
.operation-span {
margin-top: 20px;
padding: 6px;
border-radius: 3px;
user-select: none;
i {
margin: 5px;
font-size: 20px;
color: #333;
}
}
span {
color: #333;
}
.user-block {
position: absolute;
right: 50px;
.userName {
display: inline-block;
vertical-align: middle;
font-size: 14px;
cursor: pointer;
user-select: none;
}
}
}
</style>

@ -0,0 +1,319 @@
<template>
<div>
<ul class="nav">
<li>
<Logo />
</li>
<li v-if="!item.isHidden" :style="{
fontSize: selectedIndex === index ? '18px' : '14px',
color: selectedIndex === index ? '#1c1c1c' : 'rgb(102 102 102)'
}" class="funItem" v-for="(item, index) in menus" :key="index" @click="menuClick(`${item.path}`, index)">
<span>
<i :class="item.icon"></i>
<span>&nbsp; {{ item.name }}</span>
</span>
</li>
<li>
<el-row>
<el-col :span="18">
<input class="search-input" placeholder="搜索..." @keyup.enter="search" v-model="filterText" />
</el-col>
<el-col :span="6">
<span @click="search"
style="background-color: #000;color: #f1f1f1;border-radius: 5px;padding: 5px 10px;width: 100%;box-sizing: border-box;">
搜索
</span>
</el-col>
</el-row>
</li>
<li style="position: absolute;right: 330px;">
<span @click="healthDataRecord" style="margin:14px 10px;">
<i class="el-icon-edit-outline"></i>
指标记录
</span>
</li>
<li style="position: absolute;right: 300px;">
<el-badge style="margin-left: 5px;font-size: 16px;" v-if="noReadMsg !== 0" :value="noReadMsg">
<span class="message-span" @click="messageCenter">
<i class="el-icon-bell"></i>
</span>
</el-badge>
<span style="margin-left: 5px;font-size: 16px;" v-else class="message-span" @click="messageCenter">
<i class="el-icon-bell"></i>
</span>
</li>
<li>
<span class="user-block">
<el-dropdown class="user-dropdown">
<span class="el-dropdown-link" style="display: flex; align-items: center;">
<el-avatar :size="35" :src="userInfo.url" style="margin-top: 0;"></el-avatar>
<span class="userName" style="margin-left: 5px;font-size: 16px;">{{ userInfo.name }}</span>
<i class="el-icon-arrow-down el-icon--right" style="margin-left: 5px;"></i>
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item icon="el-icon-user"
@click.native="userCenterPanel">个人中心</el-dropdown-item>
<el-dropdown-item icon="el-icon-warning-outline"
@click.native="resetPwd">修改密码</el-dropdown-item>
<el-dropdown-item icon="el-icon-back" @click.native="loginOut">退出登录</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</span>
</li>
</ul>
</div>
</template>
<script>
import { clearToken } from "@/utils/storage.js";
import Logo from '@/components/Logo.vue';
export default {
name: "UserMenu",
components: {
Logo
},
data() {
return {
selectedIndex: 0,
messagePath: '/message',
loginPath: '/login',
sysName: '健康有道',
defaultPath: '/news-record',
filterText: '',
noReadMsg: 0,
}
},
props: {
//
menus: {
type: Array,
required: true
},
//
userInfo: {
type: Object,
required: true
}
},
mounted() {
this.pathToDo(this.defaultPath);
this.loadMsgCount();
},
methods: {
//
search() {
//
if (this.$route.path === '/search-detail') {
sessionStorage.setItem('keyWord', this.filterText);
return;
}
//
sessionStorage.setItem('keyWord', this.filterText);
this.$emit('eventListener', 'search-detail');
},
//
userCenterPanel() {
this.$emit('eventListener', 'center');
},
//
resetPwd() {
this.$emit('eventListener', 'resetPwd');
},
// 退
loginOut() {
this.$emit('eventListener', 'loginOut');
},
//
dietRecord() {
this.$emit('eventListener', 'dietRecord');
},
//
healthDataRecord() {
this.$emit('eventListener', 'healthDataRecord');
},
// 退
loginOut() {
this.$emit('eventListener', 'loginOut');
},
async loadMsgCount() {
const userInfo = sessionStorage.getItem('userInfo');
const userInfoEntity = JSON.parse(userInfo);
const messageQueryDto = { userId: userInfoEntity.id, isRead: false }
const response = await this.$axios.post(`/message/query`, messageQueryDto);
const { data } = response;
if(data.code === 200){
this.noReadMsg = data.data.length;
}
},
//
pathToDo(path) {
if (this.$route.path !== path) {
this.$router.push(path);
}
},
//
menuClick(path, index) {
this.selectedIndex = index;
this.pathToDo(path);
},
//
messageCenter() {
this.selectedIndex = null;
this.pathToDo(this.messagePath);
},
// 退
async out() {
const confirmed = await this.$swalConfirm({
title: '是否退出登录',
text: `退出后将重新登录,才能使用系统功能`,
icon: 'warning',
});
if (confirmed) {
this.$swal.fire({
title: '退出登录',
text: '您已成功退出登录。',
icon: 'success', // 使'success'
showConfirmButton: false, // 使
timer: 1300, // 2
});
setTimeout(() => {
clearToken();
this.$router.push('/loginPath');
}, 1300)
} else {
console.log('用户取消了退出操作');
}
},
}
}
</script>
<style scoped lang="scss">
.nav {
padding: 12px 160px;
height: 70px;
line-height: 70px;
list-style: none;
border-bottom: 1px solid #f1f1f1;
margin: 0;
li {
float: left;
height: 70px;
line-height: 70px;
font-weight: 400;
padding: 0 20px;
user-select: none;
color: rgb(102, 102, 102);
font-size: 14px;
transition: all 0.5s;
i {
color: rgb(102, 102, 102);
}
.message-span {
padding: 5px;
border-radius: 5px;
i {
font-size: 16px;
}
}
.message-span:hover {
background-color: rgb(240, 240, 240);
}
.search-input {
outline: none;
width: 100%;
font-size: 14px;
height: 35px;
line-height: 35px;
font-size: 16px;
padding: 2px 30px;
border-radius: 5px;
transition: all 0.5s;
border: 1px solid rgb(76, 77, 11);
border-radius: 5px;
}
.search-button {
background-color: #000;
font-size: 16px;
cursor: pointer;
height: 30px;
line-height: 30px;
}
.serch-input:focus {
border: 1px solid rgb(188, 229, 247);
}
}
}
.user-block {
position: absolute;
right: 200px;
.userName {
display: inline-block;
vertical-align: middle;
font-size: 14px;
cursor: pointer;
user-select: none;
}
}
.info-block {
position: fixed;
right: 10px;
float: right;
display: flex;
align-items: center;
flex-wrap: wrap;
.search {
display: flex;
align-items: center;
flex-wrap: wrap;
span {
font-size: 12px;
padding: 0 8px;
}
}
i {
padding: 6px;
border-radius: 3px;
font-size: 20px;
}
i:hover {
background-color: rgb(230, 230, 230);
}
.user-name {
padding: 0 10px;
color: #252933;
font-weight: 400;
margin: 0 10px;
font-size: 14px;
}
.login-out {
margin: 0 20px;
width: 20px;
height: 20px;
padding: 6px;
background-color: none !important;
border-radius: 3px;
}
.login-out:hover {
background-color: rgb(230, 230, 230);
}
}
</style>

@ -0,0 +1,147 @@
<template>
<div class="line-main">
<div>
<span class="tag">{{ tag }}</span>
<span class="time-show">
<span class="top-bar" style="font-size: 12px;">时间选择</span>
<el-select size="mini" style="width: 90px;" v-model="selectedValue" placeholder="期限">
<el-option v-for="item in options" :key="item.num" :label="item.name" :value="item.num">
</el-option>
</el-select>
</span>
</div>
<div ref="chart" :style="{ width: '100%', height: height }"></div>
</div>
</template>
<script>
// 线
import * as echarts from 'echarts';
export default {
name: 'DialogLine',
props: {
tag: {
type: String,
default: '折线图'
},
values: {
type: Array,
required: true
},
date: {
type: Array,
required: true
},
height: {
type: String,
default: '220px'
},
},
watch: {
selectedValue(v1, v2) {
this.$emit('on-selected', v1);
},
values(v1, v2) {
this.initChart();
}
},
data() {
return {
chart: null,
options: [{ num: 7, name: '7天内' }, { num: 30, name: '30天内' }, { num: 60, name: '60天内' }],
selectedValue: '',
}
},
methods: {
//
initChart() {
this.chart = echarts.init(this.$refs.chart);
let option = {
grid: {
left: 30,
right: 10,
top: 50,
borderWidth: 0,
},
title: {
text: '',
color: '#ffffff',
},
tooltip: {
trigger: 'axis',
formatter: '{b}{c}',
},
legend: {
data: ['']
},
xAxis: {
data: this.date,
axisLine: { show: false },
axisTick: { show: false },
axisLabel: {
color: 'rgb(102, 102, 102)',
fontSize: '12'
},
},
yAxis: {
axisLine: { show: false },
axisTick: { show: false },
axisLabel: {
color: 'rgb(102, 102, 102)',
fontSize: '12',
formatter: '{value}'
},
},
series: [{
name: '',
type: 'line',
smooth: true,
data: this.values,
areaStyle: {
color: 'rgba(144, 191, 237, 0.3)'
},
lineStyle: {
color: '#5B8FF9'
},
itemStyle: {
color: '#5B8FF9',
borderColor: '#5B8FF9',
borderWidth: 2
},
label: {
show: true,
position: 'top',
color: 'rgb(102, 102, 102)',
},
}]
};
this.chart.setOption(option);
},
},
beforeDestroy() {
if (!this.chart) {
return;
}
this.chart.dispose();
},
};
</script>
<style scoped lang="scss">
.line-main {
margin-bottom: 5px;
border-radius: 3px;
.tag {
font-size: 16px;
padding: 15px 6px;
display: inline-block;
color: #333;
font-weight: bold;
}
.time-show {
padding: 15px 6px;
float: right;
}
}
</style>

@ -0,0 +1,55 @@
<template>
<span class="logo">
<el-image style="width: 30px; height: 30px" src="/logo.png" fit="fill"></el-image>
<div v-if="!flag">
<span :style="{ color: bag, display: 'block' }">{{ name }}</span>
</div>
</span>
</template>
<script>
export default {
name: "Logo",
data() {
return {
}
},
props: {
name:{
type: String,
default: '系统'
},
flag: {
type: Boolean,
required: false
},
bag: {
type: String,
default: '#1c1c1c'
},
},
created() {
},
methods: {
}
};
</script>
<style scoped lang="scss">
.logo {
color: rgb(8, 24, 16) !important;
font-weight: bold;
font-size: 20px;
display: flex;
align-items: center;
flex-wrap: wrap;
user-select: none;
span {
margin-left: 8px;
color: #666;
font-family: Impact, Haettenschweiler, 'Arial Narrow Bold', sans-serif;
}
}
</style>

@ -0,0 +1,146 @@
<template>
<div class="line-main" :style="{ backgroundColor: bag }">
<div>
<span class="tag" :style="{ color: fontColor }">
{{ tag }}</span>
</div>
<div ref="chart" :style="{ width: width, height: height }"></div>
</div>
</template>
<script>
import * as echarts from 'echarts'
export default {
name: 'PieChart',
props: {
types: {
type: Array,
default: []
},
values: {
type: Array,
default: []
},
width: {
type: String,
default: '100%'
},
tag: {
type: String,
default: '饼状图'
},
height: {
type: String,
default: '243px'
},
bag: {
type: String,
default: '#fff'
},
fontColor:{
type: String,
default: '#333'
},
},
data() {
return {
chart: null,
}
},
watch: {
types(v1, v2) {
this.initChart();
}
},
mounted() {
this.initChart();
},
methods: {
initChart() {
this.chart = echarts.init(this.$refs.chart)
let option = {
title: {
text: '',
subtext: '',
left: 'center'
},
tooltip: {
trigger: 'item'
},
legend: {
orient: 'vertical',
left: 'left',
show: false,
},
series: [
{
name: '',
type: 'pie',
radius: '70%',
avoidLabelOverlap: false,
label: {
show: false,
position: 'center'
},
emphasis: {
label: {
show: false,
fontSize: '24',
fontWeight: '600'
}
},
labelLine: {
show: true
},
label: {
show: true,
position: 'outer',
formatter: '{d}%'
},
data: this.values.map((value, index) => ({
name: this.types[index],
value: value,
})),
itemStyle: {
color: function (params) {
const colorList = [
'#409EFF',
'#67C23A',
'#E6A23C',
'#F56C6C',
'#909399',
'#E4E7ED',
'#F2F6FC',
];
return colorList[params.dataIndex % colorList.length];
}
}
}
]
}
this.chart.setOption(option)
}
},
beforeDestroy() {
if (this.chart) {
this.chart.dispose()
}
}
}
</script>
<style scoped lang="scss">
.line-main {
padding-top: 10px;
margin-bottom: 5px;
border-radius: 3px;
background-color: #000000;
.tag {
font-size: 14px;
text-align: center;
padding: 15px 6px;
display: block;
}
}
</style>

@ -0,0 +1,48 @@
<template>
<div>
<span style="margin-right: 20px;cursor: pointer;" @click="all">
全部
</span>
<span v-for="(entity, index) in dataList" :key="index">
<span class="tag-item"
:style="{ backgroundColor: entity.isCheck ? 'rgb(56, 183, 129)' : 'rgb(252, 252, 252)', color: entity.isCheck ? '#FFFFFF' : '#1c1c1c' }"
@click="onClick(entity)">
{{ entity.name }}
</span>
</span>
</div>
</template>
<script>
//
export default {
name: "TagLine",
props: {
dataList: {
type: Array,
required: true
}
},
methods: {
onClick(tag) {
this.$emit('on-click', tag);
},
all() {
this.$emit('on-click', { id: null, name: '全部' });
},
}
};
</script>
<style scoped lang="scss">
.tag-item {
font-size: 14px;
display: inline-block;
padding: 5px 14px 5px 0;
margin: 25px 20px 25px 0;
cursor: pointer;
user-select: none;
border-radius: 3px;
transition: all 0.2s;
}
</style>

@ -0,0 +1,85 @@
<template>
<el-menu :collapse-transition="false" :collapse="flag" style="padding: 5px 20px;max-width: 253px;"
:default-active="activeIndex" :background-color="bag" text-color="#666" @select="handleSelect">
<el-menu-item v-for="(item, index) in routes" :key="index" style="width: 100%;"
v-if="!item.isHidden" :index="item.path"
:class="{ 'is-active': activeIndex === item.path }">
<i :class="item.icon" style="font-size: 20px;"></i>
<span slot="title" style="font-size: 14px;">{{ item.name }}</span>
</el-menu-item>
</el-menu>
</template>
<script>
export default {
name: 'AdminMenu',
data() {
return {
activeIndex: "1",
isCollapse: true,
selectedMenuItem: '',
}
},
props: {
routes: {
type: Array,
required: true
},
flag: {
type: Boolean,
required: true
},
bag: {
type: String,
default: '#FFFFFF'
}
},
created(){
//
const saveLastPath = sessionStorage.getItem('activeMenuItem');
if(saveLastPath === null){
//
this.handleSelect('/adminLayout');
}else{
this.handleSelect(saveLastPath);
}
},
methods: {
handleSelect(index) {
this.activeIndex = index;
this.$emit('select', this.activeIndex);
sessionStorage.setItem('activeMenuItem', this.activeIndex);
},
},
};
</script>
<style scoped>
.is-active {
background-color: rgb(235, 237, 245) !important;
color: #1c1c1c !important;
font-weight: bold;
border-radius: 6px;
}
.el-menu-item,
.el-submenu__title {
height: 45px !important;
line-height: 45px !important;
user-select: none;
color: #333;
}
.el-menu-item:focus,
.el-menu-item:hover {
box-sizing: border-box;
border-radius: 5px;
background-color: rgb(235, 237, 245) !important;
}
.el-menu-item {
height: 45px !important;
line-height: 45px !important;
margin: 3px;
}
</style>

@ -0,0 +1,86 @@
<template>
<div style="box-sizing: border-box;">
<el-tabs v-model="activeName" style="min-height: 500px;">
<el-tab-pane label="题目原题" name="first">
<Editor height="calc(100vh - 400px)" :receiveContent="operationData.askItem"
@on-receive="onReceiveContent" />
</el-tab-pane>
</el-tabs>
</div>
</template>
<script>
//
import Editor from "@/components/Editor"
export default {
components: { Editor },
props: {
data: {
type: Object,
default: function () {
return {};
}
},
},
watch: {
data: {
handler(newValue, oldValue) {
this.operationData = newValue;
this.$emit('on-listenner', newValue);
},
deep: true,
immediate: true,
},
},
data() {
return {
operationData: {},
askItem: '',
questionNumber: 1,
answerNumber: 1,
activeName: 'first',
}
},
methods: {
onReceiveContent(html) {
this.operationData.askItem = html;
},
}
}
</script>
<style lang="scss" scoped>
.options-item {
padding: 25px 0;
border-bottom: 1px solid #f1f1f1;
display: flex;
align-items: center;
flex-wrap: wrap;
.index-item {
display: inline-block;
padding: 3px 10px;
font-size: 16px;
color: rgb(113, 114, 114);
}
.check-item {
display: inline-block;
width: 20px;
height: 20px;
border-radius: 10px;
padding: 3px;
box-sizing: border-box;
border: 3px solid rgb(232, 234, 243);
margin-right: 10px;
transition: .8s;
}
input {
padding: 5px 10px;
outline: none;
border: none;
font-size: 16px;
width: 80%;
}
}
</style>

@ -0,0 +1,114 @@
<template>
<div style="box-sizing: border-box;">
<el-tabs v-model="activeName" style="min-height: 500px;">
<el-tab-pane label="1. 完善题目" name="first">
<Editor height="calc(100vh - 400px)" :receiveContent="operationData.askItem"
@on-receive="onReceiveContent" />
</el-tab-pane>
<el-tab-pane label="2. 设置选项" name="second">
<div class="options-item" v-for="(question, index) in operationData.question" :key="index">
<span class="index-item">{{ question.type }}</span>
<input v-model="question.value" placeholder="选项描述" />
</div>
</el-tab-pane>
<el-tab-pane label="3. 设置解析" name="third">
<div class="options-item" v-for="(answer, index) in operationData.answer" :key="index">
<span :style="{ backgroundColor: isCheck(answer) ? '#a61b29' : '' }" @click="selected(answer)"
class="check-item"></span>
<span :style="{ color: isCheck(answer) ? '#a61b29' : '' }" class="index-item">{{
answer.type }}</span>
<input :style="{ color: isCheck(answer) ? '#a61b29' : '' }" v-model="answer.value"
placeholder="选项答案详解" />
</div>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script>
//
import Editor from "@/components/Editor"
export default {
components: { Editor },
props: {
data: {
type: Object,
default: function () {
return {};
}
},
},
watch: {
data: {
handler(newValue, oldValue) {
this.operationData = newValue;
this.$emit('on-listenner', newValue);
},
deep: true,
immediate: true,
},
},
data() {
return {
operationData: {},
askItem: '',
questionNumber: 1,
answerNumber: 1,
activeName: 'first',
}
},
methods: {
isCheck(answer) {
return this.operationData.rightAnswer.find(type => type === answer.type) !== undefined;
},
selected(answer) {
//
const flag = this.operationData.rightAnswer.find(type => type === answer.type);
if (flag !== undefined) {
this.operationData.rightAnswer = this.operationData.rightAnswer.filter(type => type !== answer.type);
} else {
this.operationData.rightAnswer.push(answer.type);
}
},
onReceiveContent(html) {
this.operationData.askItem = html;
},
}
}
</script>
<style lang="scss" scoped>
.options-item {
padding: 25px 0;
border-bottom: 1px solid #f1f1f1;
display: flex;
align-items: center;
flex-wrap: wrap;
.index-item {
display: inline-block;
padding: 3px 10px;
font-size: 16px;
color: rgb(113, 114, 114);
}
.check-item {
display: inline-block;
width: 20px;
height: 20px;
border-radius: 10px;
padding: 3px;
box-sizing: border-box;
border: 3px solid rgb(232, 234, 243);
margin-right: 10px;
transition: .8s;
}
input {
padding: 5px 10px;
outline: none;
border: none;
font-size: 16px;
width: 80%;
}
}
</style>

@ -0,0 +1,113 @@
<template>
<div style="box-sizing: border-box;">
<el-tabs v-model="activeName" style="min-height: 500px;">
<el-tab-pane label="1. 完善题目" name="first">
<Editor height="calc(100vh - 400px)" :receiveContent="operationData.askItem"
@on-receive="onReceiveContent" />
</el-tab-pane>
<el-tab-pane label="2. 设置答案" name="second">
<div class="options-item" v-for="(question, index) in operationData.question" :key="index">
<span :style="{ backgroundColor: isCheck(question) ? '#a61b29' : '' }" @click="selected(question)"
class="check-item"></span>
<span :style="{ color: isCheck(question) ? '#a61b29' : '' }" class="index-item">{{
question.type }}</span>
<input :style="{ color: isCheck(question) ? '#a61b29' : '' }" v-model="question.value"
placeholder="选项答案详解" />
</div>
</el-tab-pane>
<el-tab-pane label="3. 设置解析答案" name="third">
<div style="padding: 10px 20px 0 0;">
<textarea style="width: 100%;min-height: 300px;max-height: 500px;border: none;outline: none;" v-model="data.parseAnswer" placeholder="解析过程" ></textarea>
</div>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script>
//
import Editor from "@/components/Editor"
export default {
components: { Editor },
props: {
data: {
type: Object,
default: function () {
return {};
}
},
},
watch: {
data: {
handler(newValue, oldValue) {
this.operationData = newValue;
this.$emit('on-listenner', newValue);
},
deep: true,
immediate: true,
},
},
data() {
return {
operationData: {},
askItem: '',
questionNumber: 1,
answerNumber: 1,
activeName: 'first',
}
},
methods: {
isCheck(answer) {
return this.operationData.rightAnswer.find(type => type === answer.type) !== undefined;
},
selected(answer) {
//
const flag = this.operationData.rightAnswer.find(type => type === answer.type);
if (flag !== undefined) {
this.operationData.rightAnswer = this.operationData.rightAnswer.filter(type => type !== answer.type);
} else {
this.operationData.rightAnswer.push(answer.type);
}
},
onReceiveContent(html) {
this.operationData.askItem = html;
},
}
}
</script>
<style lang="scss" scoped>
.options-item {
padding: 25px 0;
border-bottom: 1px solid #f1f1f1;
display: flex;
align-items: center;
flex-wrap: wrap;
.index-item {
display: inline-block;
padding: 3px 10px;
font-size: 16px;
color: rgb(113, 114, 114);
}
.check-item {
display: inline-block;
width: 20px;
height: 20px;
border-radius: 10px;
padding: 3px;
box-sizing: border-box;
border: 3px solid rgb(232, 234, 243);
margin-right: 10px;
transition: .8s;
}
input {
padding: 5px 10px;
outline: none;
border: none;
font-size: 16px;
width: 80%;
}
}
</style>

@ -0,0 +1,114 @@
<template>
<div style="box-sizing: border-box;">
<el-tabs v-model="activeName" style="min-height: 500px;">
<el-tab-pane label="1. 完善题目" name="first">
<Editor height="calc(100vh - 400px)" :receiveContent="operationData.askItem"
@on-receive="onReceiveContent" />
</el-tab-pane>
<el-tab-pane label="2. 设置选项" name="second">
<div class="options-item" v-for="(question, index) in operationData.question" :key="index">
<span class="index-item">{{ question.type }}</span>
<input v-model="question.value" placeholder="选项描述" />
</div>
</el-tab-pane>
<el-tab-pane label="3. 设置解析" name="third">
<div class="options-item" v-for="(answer, index) in operationData.answer" :key="index">
<span :style="{ backgroundColor: isCheck(answer) ? '#a61b29' : '' }" @click="selected(answer)"
class="check-item"></span>
<span :style="{ color: isCheck(answer) ? '#a61b29' : '' }" class="index-item">{{
answer.type }}</span>
<input :style="{ color: isCheck(answer) ? '#a61b29' : '' }" v-model="answer.value"
placeholder="选项答案详解" />
</div>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script>
//
import Editor from "@/components/Editor"
export default {
components: { Editor },
props: {
data: {
type: Object,
default: function () {
return {};
}
},
},
watch: {
data: {
handler(newValue, oldValue) {
this.operationData = newValue;
this.$emit('on-listenner', newValue);
},
deep: true,
immediate: true,
},
},
data() {
return {
operationData: {},
askItem: '',
questionNumber: 1,
answerNumber: 1,
activeName: 'first',
}
},
methods: {
isCheck(answer) {
return this.operationData.rightAnswer.find(type => type === answer.type) !== undefined;
},
selected(answer) {
//
const flag = this.operationData.rightAnswer.find(type => type === answer.type);
if (flag !== undefined) {
this.operationData.rightAnswer = this.operationData.rightAnswer.filter(type => type !== answer.type);
} else {
this.operationData.rightAnswer.push(answer.type);
}
},
onReceiveContent(html) {
this.operationData.askItem = html;
},
}
}
</script>
<style lang="scss" scoped>
.options-item {
padding: 25px 0;
border-bottom: 1px solid #f1f1f1;
display: flex;
align-items: center;
flex-wrap: wrap;
.index-item {
display: inline-block;
padding: 3px 10px;
font-size: 16px;
color: rgb(113, 114, 114);
}
.check-item {
display: inline-block;
width: 20px;
height: 20px;
border-radius: 10px;
padding: 3px;
box-sizing: border-box;
border: 3px solid rgb(232, 234, 243);
margin-right: 10px;
transition: .8s;
}
input {
padding: 5px 10px;
outline: none;
border: none;
font-size: 16px;
width: 80%;
}
}
</style>

@ -0,0 +1,104 @@
<template>
<div style="box-sizing: border-box;">
<el-tabs v-model="activeName" style="min-height: 500px;">
<el-tab-pane label="1. 完善题目" name="first">
<Editor height="calc(100vh - 400px)" :receiveContent="operationData.askItem"
@on-receive="onReceiveContent" />
</el-tab-pane>
<el-tab-pane label="2. 设置答案" name="second">
<div style="padding: 10px 20px 0 0;">
<textarea style="width: 100%;min-height: 300px;max-height: 500px;border: none;outline: none;" v-model="data.rightAnswer" placeholder="选项描述" ></textarea>
</div>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script>
//
import Editor from "@/components/Editor"
export default {
components: { Editor },
props: {
data: {
type: Object,
default: function () {
return {};
}
},
},
watch: {
data: {
handler(newValue, oldValue) {
this.operationData = newValue;
this.$emit('on-listenner', newValue);
},
deep: true,
immediate: true,
},
},
data() {
return {
operationData: {},
askItem: '',
questionNumber: 1,
answerNumber: 1,
activeName: 'first',
}
},
methods: {
isCheck(answer) {
return this.operationData.rightAnswer.find(type => type === answer.type) !== undefined;
},
selected(answer) {
//
const flag = this.operationData.rightAnswer.find(type => type === answer.type);
if (flag !== undefined) {
this.operationData.rightAnswer = this.operationData.rightAnswer.filter(type => type !== answer.type);
} else {
this.operationData.rightAnswer.push(answer.type);
}
},
onReceiveContent(html) {
this.operationData.askItem = html;
},
}
}
</script>
<style lang="scss" scoped>
.options-item {
padding: 25px 0;
border-bottom: 1px solid #f1f1f1;
display: flex;
align-items: center;
flex-wrap: wrap;
.index-item {
display: inline-block;
padding: 3px 10px;
font-size: 16px;
color: rgb(113, 114, 114);
}
.check-item {
display: inline-block;
width: 20px;
height: 20px;
border-radius: 10px;
padding: 3px;
box-sizing: border-box;
border: 3px solid rgb(232, 234, 243);
margin-right: 10px;
transition: .8s;
}
input {
padding: 5px 10px;
outline: none;
border: none;
font-size: 16px;
width: 80%;
}
}
</style>

@ -0,0 +1,30 @@
import Vue from "vue";
import App from "./App.vue";
import router from "./router";
import 'element-ui/lib/theme-chalk/index.css';
import { provinceAndCityData, regionData } from 'element-china-area-data';
import VueSweetalert2 from 'vue-sweetalert2';
import 'sweetalert2/dist/sweetalert2.min.css';
import './assets/css/editor.scss'
import './assets/css/button.scss'
import './assets/css/elementui-cover.scss'
import './assets/css/basic.scss'
import './assets/css/dialog.scss'
import './assets/css/input.scss'
import request from '@/utils/request'
import md5 from 'js-md5';
Vue.config.productionTip = false;
Vue.use(VueSweetalert2);
Vue.prototype.$md5 = md5;
Vue.prototype.$axios = request;
import swalPlugin from '@/utils/swalPlugin';
Vue.use(swalPlugin);
new Vue({
router,
regionData,
provinceAndCityData,
VueSweetalert2,
render: h => h(App)
}).$mount("#app");

@ -0,0 +1,84 @@
import Vue from "vue";
import VueRouter from "vue-router";
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import { getToken } from "@/utils/storage.js";
import echarts from 'echarts';
Vue.prototype.$echarts = echarts;
Vue.use(ElementUI);
Vue.use(VueRouter);
const routes = [
{ path: "/", component: () => import(`@/views/login/Login.vue`) },
{ path: "/login", component: () => import(`@/views/login/Login.vue`) },
{ path: "/register", component: () => import(`@/views/register/Register.vue`) },
{ path: "/createNotice", component: () => import(`@/views/admin/CreateNotice.vue`), meta: { requireAuth: true } },
{
path: "/admin",
component: () => import(`@/views/admin/Home.vue`),
meta: { requireAuth: true },
children: [
{ path: "/adminLayout", name: '数据总览', icon: 'el-icon-data-analysis', component: () => import(`@/views/admin/Main.vue`), meta: { requireAuth: true } },
{ path: "/userManage", name: '用户管理', icon: 'el-icon-user-solid', component: () => import(`@/views/admin/UserManage.vue`), meta: { requireAuth: true } },
{ path: "/noticeManage", name: '公告管理', icon: 'el-icon-edit-outline', component: () => import(`@/views/admin/NoticeManage.vue`), meta: { requireAuth: true } }
]
},
{
path: "/user",
component: () => import(`@/views/user/Home.vue`),
meta: { requireAuth: true },
children: [
{ name: '留言板', path: "/main", icon: 'el-icon-question', component: () => import(`@/views/user/Main.vue`), meta: { requireAuth: true } },
]
}
];
const router = new VueRouter({
routes,
mode: 'history'
});
router.beforeEach((to, from, next) => {
// 放行登录页和注册页
if (to.path === '/login' || to.path === '/register') {
return next();
}
// 检查需要认证的路由
if (to.matched.some(record => record.meta.requireAuth)) {
const token = getToken();
// 未登录情况处理
if (!token) {
return next({
path: '/login',
query: { redirect: to.fullPath } // 记录目标路由
});
}
// 已登录时的权限检查
try {
const role = parseInt(sessionStorage.getItem('role'));
// 管理员路径检查
if (to.matched[0].path === '/admin' && role !== 1) {
clearToken();
return next("/login"); //返回登录页
}
// 用户路径检查
if (to.matched[0].path === '/user' && role !== 2) {
clearToken();
return next("/login"); //返回登录页
}
return next();
} catch (error) {
console.error('权限检查失败:', error);
return next('/login');
}
}
// 普通页面直接放行
next();
});
export default router;

@ -0,0 +1,19 @@
export function timeAgo(dateString) {
const now = new Date();
const date = new Date(dateString);
const secondsPast = (now.getTime() - date.getTime()) / 1000;
if (secondsPast < 60) {
return `${Math.floor(secondsPast)} 秒前`;
} else if (secondsPast < 3600) {
return `${Math.floor(secondsPast / 60)} 分钟前`;
} else if (secondsPast <= 86400) {
return `${Math.floor(secondsPast / 3600)} 小时前`;
} else {
const daysPast = Math.floor(secondsPast / 86400);
if (daysPast === 1) {
return '1 天前';
} else {
return `${daysPast} 天前`;
}
}
}

@ -0,0 +1,17 @@
import axios from "axios"
import { getToken } from "@/utils/storage.js";
const URL_API = 'http://localhost:21090/api/book-manage-sys-api/v1.0'
const request = axios.create({
baseURL: URL_API,
timeout: 8000
});
request.interceptors.request.use(config => {
const token = getToken();
if (token !== null) {
config.headers["token"] = token;
}
return config;
}, error => {
return Promise.reject(error);
});
export default request;

@ -0,0 +1,29 @@
const TOKEN_KEY="token"
const INFO = "health-info";
const ACTIVE_PATH="active_key"
export function getToken(){
return sessionStorage.getItem(TOKEN_KEY);
}
export function setToken(token){
sessionStorage.setItem(TOKEN_KEY,token);
}
export function getHealthInfo(){
return sessionStorage.getItem(INFO);
}
export function setHealthInfo(obj){
sessionStorage.setItem(INFO,obj);
}
export function clearToken(){
sessionStorage.clear();
}
export function getActivePath(){
return sessionStorage.getItem(ACTIVE_PATH);
}
export function setActivePath(path){
sessionStorage.setItem(ACTIVE_PATH, path);
}

@ -0,0 +1,40 @@
// swalPlugin.js
import Swal from 'sweetalert2';
// 五种图标: success\error\info\warning\question
// 提示框:
// this.$swal.fire({
// title: '退出登录',
// text: '您已成功退出登录。',
// icon: 'success', // 使用'success'图标表示操作成功
// showConfirmButton: false, // 隐藏确认按钮,使得弹窗只展示信息后自动关闭
// timer: 2000, // 自动关闭弹窗的延迟时间这里是2秒
// });
const swalPlugin = {
install(Vue) {
Vue.prototype.$swalConfirm = async function(options = {}) {
const defaultOptions = {
title: '提示',
text: '',
icon: 'info',
reverseButtons: true,
showCancelButton: true,
confirmButtonText: '确认',
cancelButtonText: '取消',
customClass: {
confirmButton: 'sweet-btn-primary',
},
...options,
};
try {
const result = await Swal.fire(defaultOptions);
return result.isConfirmed;
} catch (error) {
console.error('Swal Error:', error);
return false;
}
};
},
};
export default swalPlugin;

@ -0,0 +1,112 @@
<template>
<div style="background-color: rgb(248, 249, 250);">
<div style="width: 800px;margin: 0 auto;padding: 30px 0;box-sizing: border-box;min-height: 100vh;">
<p style="font-size: 22px;padding: 20px 0;">{{ noticeOperation === 'save' ? '公告新增' : '公告修改' }}</p>
<div>
<div style="margin: 20px 0;">
<input type="text" placeholder="标题" v-model="notice.name">
</div>
<div>
<Editor height="calc(100vh - 500px)" :receiveContent="notice.content" @on-receive="receiveData" />
</div>
</div>
<div style="margin: 20px 0;text-align: center;">
<span class="operation-btn" @click="operation">
{{ noticeOperation === 'save' ? '立即新增' : '立即修改' }}
<i class="el-icon-right"></i>
</span>
</div>
</div>
</div>
</template>
<script>
import Editor from "@/components/Editor"
export default {
components: { Editor },
data() {
return {
notice: {},
saveApi: '/notice/save',
updateApi: '/notice/update',
noticeOperation: ''
}
},
created() {
this.loadOperation();
},
methods: {
operation() {
if (this.noticeOperation === 'save') {
this.save();
return;
}
this.update();
},
loadOperation() {
const operation = sessionStorage.getItem('noticeOperation');
console.log(operation);
if (operation === 'update') {
const notice = sessionStorage.getItem('noticeInfo');
this.notice = JSON.parse(notice);
}
this.noticeOperation = sessionStorage.getItem('noticeOperation');
},
receiveData(html) {
this.notice.content = html;
},
update() {
this.$axios.put(this.updateApi, this.notice).then(response => {
if (response.data.code === 200) {
this.$swal.fire({
title: '公告修改',
text: '修改成功',
icon: 'success',
showConfirmButton: false,
timer: 1000,
});
//
this.$router.go(-1);
}
});
},
save() {
this.$axios.post(this.saveApi, this.notice).then(response => {
if (response.data.code === 200) {
this.$swal.fire({
title: '公告新增',
text: '新增成功',
icon: 'success',
showConfirmButton: false,
timer: 1000,
});
//
this.$router.go(-1);
}
});
},
}
}
</script>
<style lang="scss" scoped>
input {
outline: none;
border: none;
width: 100%;
padding: 18px 12px;
font-size: 24px;
box-sizing: border-box;
border-radius: 5px;
font-weight: bold;
}
.operation-btn {
padding: 14px 40px;
background-color: rgb(235, 237, 245);
color: rgb(43, 121, 203);
border: none;
font-size: 12px;
border-radius: 5px;
cursor: pointer;
}
</style>

@ -0,0 +1,254 @@
<template>
<div class="menu-container">
<div class="menu-side" :class="{ 'menu-side-narrow': flag }">
<div style="display: flex;align-items: center;">
<Logo name="图书管理" style="padding: 0 40px;margin: 15px 0;" :flag="flag" :bag="colorLogo" />
</div>
<div style="margin-top: 12px;">
<AdminMenu :flag="flag" :routes="adminRoutes" :bag="bagMenu" @select="handleRouteSelect" />
</div>
</div>
<div class="main">
<div class="header-section">
<LevelHeader @eventListener="eventListener" @selectOperation="selectOperation" :tag="tag"
:userInfo="userInfo" />
</div>
<div class="content-section">
<router-view></router-view>
</div>
</div>
<!-- 个人中心 -->
<el-dialog :show-close="false" :visible.sync="dialogOperaion" width="26%">
<div slot="title" style="padding: 25px 0 0 20px;">
<span style="font-size: 18px;font-weight: 800;">个人中心</span>
</div>
<el-row style="padding: 10px 20px 20px 20px;">
<el-row>
<p style="font-size: 12px;padding: 3px 0;margin-bottom: 10px;">
<span class="modelName">*头像</span>
</p>
<el-upload class="avatar-uploader" action="/api/book-manage-sys-api/v1.0/file/upload"
:show-file-list="false" :on-success="handleAvatarSuccess">
<img v-if="userInfo.url" :src="userInfo.url" style="width: 80px;height: 80px;">
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
</el-upload>
</el-row>
<el-row>
<p style="font-size: 12px;padding: 3px 0;">
<span class="modelName">*用户名</span>
</p>
<input class="input-title" v-model="userInfo.name" placeholder="用户名">
</el-row>
<el-row>
<p style="font-size: 12px;padding: 3px 0;">
<span class="modelName">*用户邮箱</span>
</p>
<input class="input-title" v-model="userInfo.email" placeholder="用户邮箱">
</el-row>
</el-row>
<span slot="footer" class="dialog-footer">
<el-button class="customer" size="small" style="background-color: rgb(241, 241, 241);border: none;"
@click="dialogOperaion = false"> </el-button>
<el-button size="small" style="background-color: #15559a;border: none;" class="customer" type="info"
@click="updateUserInfo">修改</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import request from "@/utils/request.js";
import router from "@/router/index";
import { clearToken } from "@/utils/storage"
import AdminMenu from '@/components/VerticalMenu.vue';
import Logo from '@/components/Logo.vue';
import LevelHeader from '@/components/LevelHeader.vue';
export default {
name: "Admin",
components: {
Logo,
LevelHeader,
AdminMenu
},
data() {
return {
adminRoutes: [],
activeIndex: '',
userInfo: {
id: null,
url: '',
name: '',
role: null,
email: ''
},
flag: false,
tag: '可视化',
bag: 'rgb(250, 250, 250)',
colorLogo: '#1c1c1c',
bagMenu: 'rgb(250, 250, 250)',
dialogOperaion: false
};
},
created() {
let menus = router.options.routes.filter(route => route.path == '/admin')[0];
this.adminRoutes = menus.children;
this.tokenCheckLoad();
this.menuOperationHistory();
},
methods: {
async updateUserInfo() {
try {
const userUpdateDTO = {
userAvatar: this.userInfo.url,
userName: this.userInfo.name,
userEmail: this.userInfo.email
}
const resposne = await this.$axios.put(`/user/update`, userUpdateDTO);
const { data } = resposne;
if (data.code === 200) {
this.dialogOperaion = false;
this.tokenCheckLoad();
this.$swal.fire({
title: '修改个人信息',
text: data.msg,
icon: 'success',
showConfirmButton: false,
timer: 1000,
});
}
} catch (e) {
this.dialogOperaion = false;
this.$swal.fire({
title: '修改个人信息异常',
text: e,
icon: 'error',
showConfirmButton: false,
timer: 2000,
});
console.error(`修改个人信息异常:${e}`);
}
},
handleAvatarSuccess(res, file) {
if (res.code !== 200) {
this.$message.error(`头像上传异常`);
return;
}
this.$message.success(`头像上传成功`);
this.userInfo.url = res.data;
},
eventListener(event) {
//
if (event === 'center') {
this.dialogOperaion = true;
}
// 退
if (event === 'loginOut') {
this.loginOut();
}
},
async loginOut() {
const confirmed = await this.$swalConfirm({
title: '退出登录?',
text: `推出后需重新登录?`,
icon: 'warning',
});
if (confirmed) {
this.$swal.fire({
title: '退出登录成功',
text: '1s 后返回登录页面',
icon: 'success',
showConfirmButton: false,
timer: 1000,
});
setTimeout(() => {
clearToken();
this.$router.push("/login");
}, 1000)
}
},
menuOperationHistory() {
this.flag = sessionStorage.getItem('flag') === 'true';
},
selectOperation(flag) {
this.flag = flag;
},
handleRouteSelect(index) {
let ary = this.adminRoutes.filter(entity => entity.path == index);
this.tag = ary[0].name;
if (this.$router.currentRoute.fullPath == index) {
return;
}
this.$router.push(index);
},
// Token
async tokenCheckLoad() {
try {
const res = await request.get('user/auth');
//
if (res.data.code === 400) {
this.$message.error(res.data.msg);
this.$router.push('/login');
return;
}
//
const { id, userAvatar: url, userName: name, userRole: role, userEmail: email } = res.data.data;
this.userInfo = { id, url, name, role, email };
//
const rolePath = role === 1 ? '/admin' : '/user';
const targetMenu = router.options.routes.find(route => route.path === rolePath);
if (targetMenu) {
this.routers = targetMenu.children;
} else {
console.warn(`未找到与角色对应的路由:${rolePath}`);
}
} catch (error) {
console.error('获取用户认证信息时发生错误:', error);
this.$message.error('认证信息加载失败,请重试!');
}
},
}
};
</script>
<style scoped lang="scss">
.menu-container {
display: flex;
height: 100vh;
width: 100%;
.menu-side {
width: 253px;
min-width: 95px;
height: 100vh;
padding-top: 10px;
box-sizing: border-box;
transition: width 0.3s ease;
background-color: rgb(250, 250, 250);
}
.menu-side-narrow {
width: 115px;
}
.main {
flex-grow: 1;
overflow-x: hidden;
.header-section {
max-width: 100%;
padding: 0 15px 0 0;
}
.content-section {
overflow-x: hidden;
flex-grow: 1;
padding: 0 15px;
box-sizing: border-box;
overflow-y: auto;
}
}
}
</style>

@ -0,0 +1,123 @@
<template>
<div style="padding: 0 10px;overflow-y: hidden;overflow-x: hidden;">
<el-row>
<el-col :span="8">
<div style="padding: 10px;box-sizing: border-box;">
<PieChart fontColor="#000" bag="rgb(236, 245, 255)" tag="基础数据" :values="pieValues"
:types="pieTypes" />
</div>
<div style="padding: 10px 20px;box-sizing: border-box;">
<h3>最新公告</h3>
<div style="background-color: rgb(236, 245, 255);border-radius: 5px;padding: 5px 10px;">
<div style="margin-bottom: 20px;margin-top: 5px;" v-for="(notice, index) in noticeList" :key="index">
<div style="margin: 5px 0;cursor: pointer;">
<span style="font-size: 14px;">{{ notice.name }}</span>
</div>
<div>
<span style="font-size: 14px;">时间{{ notice.createTime }}</span>
</div>
</div>
</div>
</div>
</el-col>
<el-col :span="16">
<div style="padding: 8px;box-sizing: border-box;">
<LineChart height="310px" tag="用户数" @on-selected="userDatesSelected" :values="userValues"
:date="userDates" />
</div>
<div style="padding: 8px;box-sizing: border-box;">
<LineChart height="310px" tag="收录问卷" @on-selected="modelDatesSelected" :values="modelValues"
:date="modelDates" />
</div>
</el-col>
</el-row>
</div>
</template>
<script>
import LineChart from "@/components/LineChart"
import PieChart from "@/components/PieChart"
export default {
components: { LineChart, PieChart },
data() {
return {
userValues: [],
userDates: [],
modelDates: [],
modelValues: [],
pieValues: [],
pieTypes: [],
noticeList: []
}
},
created() {
// 7
this.userDatesSelected(365);
// 7
this.modelDatesSelected(365);
this.loadPieCharts();
this.loadMessage();
},
methods: {
//
loadMessage() {
const messageQueryDto = {
current: 1,
size: 4
}
this.$axios.post(`/notice/query`, messageQueryDto).then(response => {
const { data } = response;
if (data.code === 200) {
this.noticeList = data.data;
}
})
},
loadPieCharts() {
this.$axios.get(`/views/staticControls`).then(response => {
const { data } = response;
if (data.code === 200) {
this.pieValues = data.data.map(entity => entity.count);
this.pieTypes = data.data.map(entity => entity.name);
}
})
},
modelDatesSelected(time) {
this.$axios.get(`/paper/daysQuery/${time}`).then(response => {
const { data } = response;
if (data.code === 200) {
this.modelValues = data.data.map(entity => entity.count);
this.modelDates = data.data.map(entity => entity.name);
}
})
},
userDatesSelected(time) {
this.$axios.get(`/user/daysQuery/${time}`).then(response => {
const { data } = response;
if (data.code === 200) {
this.userValues = data.data.map(entity => entity.count);
this.userDates = data.data.map(entity => entity.name);
}
})
},
},
};
</script>
<style scoped lang="scss">
.status-success {
display: inline-block;
padding: 1px 5px;
border-radius: 2px;
background-color: rgb(201, 237, 249);
color: rgb(111, 106, 196);
font-size: 12px;
}
.status-error {
display: inline-block;
padding: 1px 5px;
border-radius: 2px;
background-color: rgb(233, 226, 134);
color: rgb(131, 138, 142);
color: rgb(111, 106, 196);
font-size: 12px;
}
</style>

@ -0,0 +1,176 @@
<template>
<el-row style="background-color: #FFFFFF;padding: 10px 0;border-radius: 5px;">
<el-row style="padding: 10px;">
<el-row>
<span class="top-bar">公告标题</span>
<el-input size="small" style="width: 188px;" v-model="noticeQueryDto.name" placeholder="输入处" clearable
@clear="handleFilterClear">
</el-input>
<span class="top-bar">发布时间</span>
<el-date-picker size="small" style="margin-left: 10px;width: 220px;" v-model="searchTime"
type="daterange" range-separator="至" start-placeholder="开始时间" end-placeholder="结束时间">
</el-date-picker>
<el-button size="small" class="customer"
style="margin-left: 10px;background-color: rgb(235, 237, 245);color: rgb(43, 121, 203);border: none;" type="primary"
@click="handleFilter">立即查询</el-button>
<el-button size="small"
style="background-color: rgb(235, 237, 245);color: rgb(43, 121, 203);border: none;" class="customer"
type="info" @click="addNotice">新增公告</el-button>
<el-button size="small" class="customer reset"
style="background-color: #f1f1f1;border: none;color: #909399;border: 1px solid #f1f1f1;" type="info"
@click="resetQueryCondition">条件重置</el-button>
<el-button size="small" class="customer"
:style="{ marginLeft: '10px', backgroundColor: selectedRows.length ? '#a7535a' : '#F1F1F1', border: 'none', color: selectedRows.length ? '#FFFFFF' : '#909399' }"
type="danger" @click="batchDelete()">批量删除</el-button>
</el-row>
</el-row>
<el-row style="margin: 10px;">
<el-table row-key="id" @selection-change="handleSelectionChange" :data="tableData" style="width: 100%">
<el-table-column type="selection" width="55"></el-table-column>
<el-table-column prop="name" width="508" label="公告"></el-table-column>
<el-table-column prop="createTime" width="188" label="发布时间"></el-table-column>
<el-table-column label="操作">
<template slot-scope="scope">
<span class="text-button" @click="handleEdit(scope.row)"></span>
<span class="text-button" @click="handleDelete(scope.row)"></span>
</template>
</el-table-column>
</el-table>
<el-pagination style="margin: 20px 0;float: right;" @size-change="handleSizeChange"
@current-change="handleCurrentChange" :current-page="currentPage" :page-sizes="[8, 20]"
:page-size="pageSize" layout="total, sizes, prev, pager, next, jumper"
:total="totalItems"></el-pagination>
</el-row>
</el-row>
</template>
<script>
export default {
data() {
return {
data: {},
filterText: '',
currentPage: 1,
pageSize: 8,
totalItems: 0,
tableData: [],
searchTime: [],
selectedRows: [],
noticeQueryDto: {},
};
},
created() {
this.fetchFreshData();
},
methods: {
//
addNotice() {
sessionStorage.setItem('noticeOperation', 'save');
this.$router.push('/createNotice');
},
//
handleSelectionChange(selection) {
this.selectedRows = selection;
},
//
async batchDelete() {
if (!this.selectedRows.length) {
this.$message(`未选中任何数据`);
return;
}
const confirmed = await this.$swalConfirm({
title: '删除公告数据',
text: `删除后不可恢复,是否继续?`,
icon: 'warning',
});
if (confirmed) {
try {
let ids = this.selectedRows.map(entity => entity.id);
const response = await this.$axios.post(`notice/batchDelete`, ids);
if (response.data.code === 200) {
this.$swal.fire({
title: '删除提示',
text: response.data.msg,
icon: 'success',
showConfirmButton: false,
timer: 2000,
});
this.fetchFreshData();
return;
}
} catch (e) {
this.$swal.fire({
title: '错误提示',
text: e,
icon: 'error',
showConfirmButton: false,
timer: 2000,
});
console.error(`公告信息删除异常:`, e);
}
}
},
resetQueryCondition() {
this.noticeQueryDto = {};
this.searchTime = [];
this.fetchFreshData();
},
clearFormData() {
this.data = {};
},
async fetchFreshData() {
try {
this.tableData = [];
let startTime = null;
let endTime = null;
if (this.searchTime != null && this.searchTime.length === 2) {
const [startDate, endDate] = await Promise.all(this.searchTime.map(date => date.toISOString()));
startTime = `${startDate.split('T')[0]}T00:00:00`;
endTime = `${endDate.split('T')[0]}T23:59:59`;
}
//
const params = {
current: this.currentPage,
size: this.pageSize,
startTime: startTime,
endTime: endTime,
...this.noticeQueryDto
};
const response = await this.$axios.post('notice/query', params);
const { data } = response;
this.tableData = data.data;
this.totalItems = data.total;
} catch (error) {
console.error('查询公告信息异常:', error);
}
},
handleFilter() {
this.currentPage = 1;
this.fetchFreshData();
},
handleFilterClear() {
this.filterText = '';
this.handleFilter();
},
handleSizeChange(val) {
this.pageSize = val;
this.currentPage = 1;
this.fetchFreshData();
},
handleCurrentChange(val) {
this.currentPage = val;
this.fetchFreshData();
},
handleEdit(row) {
sessionStorage.setItem('noticeInfo', JSON.stringify(row));
sessionStorage.setItem('noticeOperation', 'update');
this.$router.push('/createNotice');
},
handleDelete(row) {
this.selectedRows.push(row);
this.batchDelete();
}
},
};
</script>
<style scoped lang="scss"></style>

@ -0,0 +1,405 @@
<template>
<el-row style="background-color: #FFFFFF;padding: 20px 0;border-radius: 5px;">
<el-row style="padding: 10px;margin: 0 10px;">
<el-row>
<span class="top-bar">用户名</span>
<el-input size="small" style="width: 188px;margin-right: 10px;" v-model="userQueryDto.userName" placeholder="用户名" clearable
@clear="handleFilterClear">
</el-input>
<span class="top-bar">注册时间</span>
<el-date-picker size="small" style="width: 220px;" v-model="searchTime"
type="daterange" range-separator="至" start-placeholder="起始时间" end-placeholder="结束时间">
</el-date-picker>
<el-button size="small" class="customer" style="background-color: rgb(235, 237, 245);color: rgb(43, 121, 203);border: none;"
type="primary" @click="handleFilter">立即查询</el-button>
<el-button size="small" style="background-color: rgb(235, 237, 245);color: rgb(43, 121, 203);border: none;" class="customer"
type="info" @click="add()">新增用户</el-button>
<el-button size="small" class="customer"
:style="{ marginLeft: '10px', backgroundColor: selectedRows.length ? '#a7535a' : '#F1F1F1', border: 'none', color: selectedRows.length ? '#FFFFFF' : '#909399' }"
type="danger" @click="batchDelete()">批量删除</el-button>
<el-button size="small" class="customer reset"
style="background-color: #f1f1f1;border: none;color: #909399;border: 1px solid #f1f1f1;" type="info"
@click="resetQueryCondition">条件重置</el-button>
</el-row>
</el-row>
<el-row style="margin: 10px 20px;">
<el-table row-key="id" @selection-change="handleSelectionChange" :data="tableData" style="width: 100%">
<el-table-column type="selection" width="55"></el-table-column>
<el-table-column prop="userAvatar" width="68" label="头像">
<template slot-scope="scope">
<el-avatar :size="30" :src="scope.row.userAvatar" style="margin-top: 10px;"></el-avatar>
</template>
</el-table-column>
<el-table-column prop="userName" width="148" label="名称"></el-table-column>
<el-table-column prop="userAccount" width="128" label="账号"></el-table-column>
<el-table-column prop="userEmail" width="168" label="用户邮箱"></el-table-column>
<el-table-column prop="userRole" width="68" label="角色">
<template slot-scope="scope">
<span>{{ scope.row.userRole === 1 ? '管理员' : '用户' }}</span>
</template>
</el-table-column>
<el-table-column prop="isLogin" width="108" label="封号">
<template slot-scope="scope">
<el-switch @change="handleSwitchChange(scope.row.id, scope.row.isLogin, true)"
style="user-select: none;" v-model="scope.row.isLogin" active-color="#13ce66"
inactive-color="rgb(226, 226, 226)">
</el-switch>
</template>
</el-table-column>
<el-table-column :sortable="true" prop="createTime" width="168" label="注册于"></el-table-column>
<el-table-column label="操作">
<template slot-scope="scope">
<span class="text-button" @click="handleEdit(scope.row)"></span>
<span class="text-button" @click="handleDelete(scope.row)"></span>
</template>
</el-table-column>
</el-table>
<el-pagination style="margin: 20px 0;float: right;" @size-change="handleSizeChange"
@current-change="handleCurrentChange" :current-page="currentPage" :page-sizes="[5, 7]"
:page-size="pageSize" layout="total, sizes, prev, pager, next, jumper"
:total="totalItems"></el-pagination>
</el-row>
<!-- 操作面板 -->
<el-dialog :show-close="false" :visible.sync="dialogUserOperaion" width="28%">
<div slot="title">
<p class="dialog-title">{{ !isOperation ? '新增新用户' : '编辑用户信息' }}</p>
</div>
<div style="padding:0 20px;">
<el-row>
<el-upload class="avatar-uploader" action="/api/book-manage-sys-api/v1.0/file/upload"
:show-file-list="false" :on-success="handleAvatarSuccess">
<img v-if="data.userAvatar" :src="data.userAvatar" class="dialog-avatar">
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
</el-upload>
</el-row>
<el-row>
<span class="dialog-hover">用户名</span>
<input class="dialog-input" v-model="data.userName" placeholder="用户名" />
<span class="dialog-hover">账号</span>
<input class="dialog-input" v-model="data.userAccount" placeholder="账号" />
<span class="dialog-hover">邮箱</span>
<input class="dialog-input" v-model="data.userEmail" placeholder="邮箱" />
<span class="dialog-hover">密码</span>
<input class="dialog-input" v-model="userPwd" type="password" placeholder="密码" />
</el-row>
</div>
<span slot="footer" class="dialog-footer">
<el-button size="small" v-if="!isOperation" style="background-color: rgb(43, 121, 203);border: none;"
class="customer" type="info" @click="addOperation">新增</el-button>
<el-button size="small" v-else style="background-color: rgb(43, 121, 203);border: none;"
class="customer" type="info" @click="updateOperation">修改</el-button>
<el-button class="customer" size="small" style="background-color: rgb(241, 241, 241);border: none;"
@click="dialogUserOperaion = false">取消</el-button>
</span>
</el-dialog>
<!-- 消息推送 -->
<el-dialog :show-title="false" :show-close="false" :visible.sync="dialogMessageOperation" width="24%">
<p style="padding: 20px 0 0 20px;">消息推送</p>
<div style="padding:0 20px;">
<el-row>
<span class="dialog-hover">消息内容</span>
<el-input type="textarea" :autosize="{ minRows: 2, maxRows: 4 }" placeholder="消息内容"
v-model="data.content">
</el-input>
</el-row>
</div>
<span slot="footer" class="dialog-footer">
<el-button size="small" style="background-color: rgb(43, 121, 203);border: none;" class="customer"
type="info" @click="messagePushOperation">确定推送</el-button>
<el-button class="customer" size="small" style="background-color: rgb(241, 241, 241);border: none;"
@click="dialogMessageOperation = false">取消</el-button>
</span>
</el-dialog>
</el-row>
</template>
<script>
export default {
data() {
return {
userPwd: '',
data: { userAvatar: '' },
filterText: '',
currentPage: 1,
pageSize: 7,
totalItems: 0,
dialogMessageOperation: false,
dialogUserOperaion: false, //
isOperation: false, // -
tableData: [],
searchTime: [],
selectedRows: [],
status: null,
userQueryDto: {}, //
messsageContent: ''
};
},
watch: {
dialogUserOperaion(v1, v2) {
if (!v1) {
this.isOperation = !this.isOperation;
}
if (!v1 && v2) {
this.data = {};
}
},
},
created() {
this.fetchFreshData();
},
methods: {
//
messagePushOperation() {
const messages = []
const message = {
receiverId: this.data.id,
content: this.data.content
}
messages.push(message);
this.$axios.post('/message/systemInfoSave', messages).then(response => {
const { data } = response;
if (data.code === 200) {
this.$swal.fire({
title: '消息推送',
text: '推送成功',
icon: 'success',
showConfirmButton: false,
timer: 1000,
});
this.dialogMessageOperation = false;
this.data = {};
}
})
},
handleAvatarSuccess(res, file) {
if (res.code !== 200) {
this.$message.error(`用户头像上传异常`);
return;
}
this.$message.success(`用户头像上传成功`);
this.data.userAvatar = res.data;
console.log(this.data);
},
switchChange() {
this.fetchFreshData();
},
async handleSwitchChange(id, status, operation) {
try {
let param = { id: id }
//
if (operation) {
param.isLogin = status;
} else { //
param.isWord = status;
}
const response = await this.$axios.put(`/user/backUpdate`, param);
if (response.data.code === 200) {
this.$swal.fire({
title: operation ? '登录状态' : '评论状态',
text: operation ? '登录状态操作成功' : '评论状态操作成功',
icon: 'success',
showConfirmButton: false,
timer: 1000,
});
}
} catch (e) {
console.error(`更新用户状态异常:${e}`);
}
},
//
handleSelectionChange(selection) {
this.selectedRows = selection;
},
//
async batchDelete() {
if (!this.selectedRows.length) {
this.$message(`未选中任何数据`);
return;
}
const confirmed = await this.$swalConfirm({
title: '删除用户数据',
text: `删除后不可恢复,是否继续?`,
icon: 'warning',
});
if (confirmed) {
try {
let ids = this.selectedRows.map(entity => entity.id);
const response = await this.$axios.post(`/user/batchDelete`, ids);
if (response.data.code === 200) {
this.$swal.fire({
title: '删除提示',
text: response.data.msg,
icon: 'success',
showConfirmButton: false,
timer: 2000,
});
this.fetchFreshData();
return;
}
} catch (e) {
this.$swal.fire({
title: '错误提示',
text: e,
icon: 'error',
showConfirmButton: false,
timer: 2000,
});
console.error(`用户信息删除异常:`, e);
}
}
},
resetQueryCondition() {
this.userQueryDto = {};
this.searchTime = [];
this.fetchFreshData();
},
//
async updateOperation() {
if (this.userPwd !== '') {
const pwd = this.$md5(this.$md5(this.userPwd));
this.data.userPwd = pwd;
} else {
this.data.userPwd = null;
}
try {
const response = await this.$axios.put('/user/backUpdate', this.data);
this.$swal.fire({
title: '用户信息修改',
text: response.data.msg,
icon: response.data.code === 200 ? 'success' : 'error',
showConfirmButton: false,
timer: 1000,
});
if (response.data.code === 200) {
this.closeDialog();
this.fetchFreshData();
this.clearFormData();
}
} catch (error) {
console.error('提交表单时出错:', error);
this.$message.error('提交失败,请稍后再试!');
}
},
//
async addOperation() {
if (this.userPwd !== '') {
this.data.userPwd = this.$md5(this.$md5(this.userPwd));
} else {
this.data.userPwd = null;
}
try {
const response = await this.$axios.post('/user/insert', this.data);
this.$message[response.data.code === 200 ? 'success' : 'error'](response.data.msg);
if (response.data.code === 200) {
this.closeDialog();
this.fetchFreshData();
this.clearFormData();
}
} catch (error) {
console.error('提交表单时出错:', error);
this.$message.error('提交失败,请稍后再试!');
}
},
closeDialog() {
this.dialogUserOperaion = false;
},
clearFormData() {
this.data = {};
},
async fetchFreshData() {
try {
this.tableData = [];
let startTime = null;
let endTime = null;
if (this.searchTime != null && this.searchTime.length === 2) {
const [startDate, endDate] = await Promise.all(this.searchTime.map(date => date.toISOString()));
startTime = `${startDate.split('T')[0]}T00:00:00`;
endTime = `${endDate.split('T')[0]}T23:59:59`;
}
//
const params = {
current: this.currentPage,
size: this.pageSize,
key: this.filterText,
startTime: startTime,
endTime: endTime,
...this.userQueryDto
};
const response = await this.$axios.post('/user/query', params);
const { data } = response;
this.tableData = data.data;
this.totalItems = data.total;
} catch (error) {
console.error('查询用户信息异常:', error);
}
},
add() {
this.dialogUserOperaion = true;
},
handleFilter() {
this.currentPage = 1;
this.fetchFreshData();
},
handleFilterClear() {
this.filterText = '';
this.handleFilter();
},
handleSizeChange(val) {
this.pageSize = val;
this.currentPage = 1;
this.fetchFreshData();
},
handleCurrentChange(val) {
this.currentPage = val;
this.fetchFreshData();
},
messagePush(row) {
this.dialogMessageOperation = true;
this.data = { ...row };
},
handleEdit(row) {
this.dialogUserOperaion = true;
this.isOperation = true;
row.userPwd = null;
this.data = { ...row }
},
handleDelete(row) {
this.selectedRows.push(row);
this.batchDelete();
}
},
};
</script>
<style scoped lang="scss">
.tag-tip {
display: inline-block;
padding: 5px 10px;
border-radius: 5px;
background-color: rgb(245, 245, 245);
color: rgb(104, 118, 130);
}
.input-def {
height: 40px;
line-height: 40px;
outline: none;
border: none;
font-size: 20px;
color: rgb(102, 102, 102);
font-weight: 900;
width: 100%;
}
.dialog-footer {
/* 使按钮水平居中 */
display: flex;
justify-content: center;
align-items: center;
}
/* 如果需要调整按钮之间的间距 */
.customer {
margin: 0 8px;
/* 根据需要调整间距 */
}
</style>

@ -0,0 +1,229 @@
<template>
<div class="login-container">
<div class="login-panel">
<div class="logo">
<Logo :bag="colorLogo" name="图书管理系统" />
</div>
<div class="text">
<input v-model="act" class="act" placeholder="输入账号" />
</div>
<div class="text">
<input v-model="pwd" class="pwd" type="password" placeholder="输入密码" />
</div>
<div>
<span class="login-btn" @click="login"></span>
</div>
<div class="tip">
<p>没有账号<span class="no-act" @click="toDoRegister"></span></p>
</div>
</div>
</div>
</template>
<script>
const ADMIN_ROLE = 1;
const USER_ROLE = 2;
const DELAY_TIME = 1300;
import request from "@/utils/request.js";
import { setToken } from "@/utils/storage.js";
import md5 from 'js-md5';
import Logo from '@/components/Logo.vue';
export default {
name: "Login",
components: { Logo },
data() {
return {
act: '',
pwd: '',
colorLogo: 'rgb(38,38,38)',
}
},
created() {
this.defaultLoad();
},
methods: {
defaultLoad() {
const token = sessionStorage.getItem('token');
if (token === undefined || token === null || token === '') {
return;
}
this.$axios.get('user/auth').then(response => {
const { data } = response;
if (data.code === 400) {
return;
}
if (data.data.userRole === 1) {
this.$router.push('/admin');
} else {
this.$router.push('/user');
}
})
},
//
toDoRegister() {
this.$router.push('/register');
},
async login() {
if (!this.act || !this.pwd) {
this.$swal.fire({
title: '填写校验',
text: '账号或密码不能为空',
icon: 'error',
showConfirmButton: false,
timer: DELAY_TIME,
});
return;
}
const hashedPwd = md5(md5(this.pwd));
const paramDTO = { userAccount: this.act, userPwd: hashedPwd };
try {
const { data } = await request.post(`user/login`, paramDTO);
if (data.code !== 200) {
this.$swal.fire({
title: '登录失败',
text: data.msg,
icon: 'error',
showConfirmButton: false,
timer: DELAY_TIME,
});
return;
}
setToken(data.data.token);
// 使Swal
this.$swal.fire({
title: '登录成功',
text: '即将进入系统...',
icon: 'success',
showConfirmButton: false,
timer: DELAY_TIME,
});
//
setTimeout(() => {
const { role } = data.data;
sessionStorage.setItem('role', role);
this.navigateToRole(role);
}, DELAY_TIME);
} catch (error) {
console.error('登录请求错误:', error);
this.$message.error('登录请求出错,请重试!');
}
},
navigateToRole(role) {
switch (role) {
case ADMIN_ROLE:
this.$router.push('/admin');
break;
case USER_ROLE:
this.$router.push('/user');
break;
default:
console.warn('未知的角色类型:', role);
break;
}
},
}
};
</script>
<style lang="scss" scoped>
* {
user-select: none;
}
.login-container {
width: 100%;
min-height: 100vh;
background-color: rgb(255, 255, 255);
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
.login-panel {
width: 313px;
height: auto;
padding: 40px 30px 16px 30px;
border-radius: 3px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1), 0 1px 3px rgba(0, 0, 0, 0.06);
.logo {
margin: 10px 0 30px 0;
}
.act,
.pwd {
margin: 8px 0;
height: 53px;
line-height: 53px;
width: 100%;
font-size: 16px;
padding: 0 8px;
box-sizing: border-box;
border: 2px solid #c8d4e7;
border-radius: 6px;
padding: 0 15px;
margin-top: 13px;
}
.act:focus,
.pwd:focus {
outline: none;
border: 2px solid rgb(255, 126, 0);
transition: 1.2s;
}
.role {
display: inline-block;
color: rgb(30, 102, 147);
font-size: 14px;
padding-right: 10px;
}
}
.login-btn {
display: inline-block;
text-align: center;
border-radius: 3px;
margin-top: 20px;
height: 43px;
line-height: 43px;
width: 100%;
background-color: rgb(255, 126, 0);
font-size: 14px !important;
border: none;
color: white;
padding: 0 !important;
cursor: pointer;
user-select: none;
}
.tip {
margin: 20px 0;
p {
padding: 3px 0;
margin: 0;
font-size: 14px;
color: #647897;
i {
margin-right: 3px;
}
span {
color: #3b3c3e;
border-radius: 2px;
margin: 0 6px;
}
.no-act:hover {
color: #3e77c2;
cursor: pointer;
}
}
}
}
</style>

@ -0,0 +1,211 @@
<template>
<div class="login-container">
<div class="login-panel">
<div class="logo">
<Logo :bag="colorLogo" name="读者注册" />
</div>
<div class="text">
<input v-model="act" class="act" placeholder="注册账号" />
</div>
<div class="text">
<input v-model="name" class="act" placeholder="用户名" />
</div>
<div class="text">
<input v-model="pwd" class="pwd" type="password" placeholder="输入密码" />
</div>
<div class="text">
<input v-model="pwdConfirm" class="pwd" type="password" placeholder="确认密码" />
</div>
<div>
<span class="login-btn" @click="registerFunc"></span>
</div>
<div class="tip">
<p>已有账户<span class="no-act" @click="toDoLogin"></span></p>
</div>
</div>
</div>
</template>
<script>
const DELAY_TIME = 1300;
import request from "@/utils/request.js";
import md5 from 'js-md5';
import Logo from '@/components/Logo.vue';
export default {
name: "Register",
components: { Logo },
data() {
return {
act: '', //
pwd: '', //
pwdConfirm: '', //
name: '' //
}
},
methods: {
//
toDoLogin() {
this.$router.push('/login');
},
async registerFunc() {
if (!this.act || !this.pwd || !this.pwdConfirm || !this.name) {
this.$swal.fire({
title: '填写校验',
text: '账号或密码或用户名不能为空',
icon: 'error',
showConfirmButton: false,
timer: DELAY_TIME,
});
return;
}
if (this.pwd !== this.pwdConfirm) {
this.$swal.fire({
title: '填写校验',
text: '前后密码输入不一致',
icon: 'error',
showConfirmButton: false,
timer: DELAY_TIME,
});
return;
}
const hashedPwd = md5(md5(this.pwd));
const paramDTO = { userAccount: this.act, userPwd: hashedPwd, userName: this.name };
try {
const { data } = await request.post(`user/register`, paramDTO);
if (data.code !== 200) {
this.$swal.fire({
title: '注册失败',
text: data.msg,
icon: 'error',
showConfirmButton: false,
timer: DELAY_TIME,
});
return;
}
// 使Swal
this.$swal.fire({
title: '注册成功',
text: '即将返回登录页...',
icon: 'success',
showConfirmButton: false,
timer: DELAY_TIME,
});
//
setTimeout(() => {
this.$router.push('/login');
}, DELAY_TIME);
} catch (error) {
console.error('注册请求错误:', error);
}
}
}
};
</script>
<style lang="scss" scoped>
* {
user-select: none;
}
.login-container {
width: 100%;
height: 100vh;
display: flex;
/* 启用Flexbox布局 */
justify-content: center;
/* 水平居中 */
align-items: center;
/* 垂直居中 */
flex-direction: column;
/* 如果需要垂直居中,确保子元素也是这样排列 */
.login-panel {
margin: 0 auto;
width: 333px;
height: auto;
padding: 40px 30px 16px 30px;
border-radius: 3px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1), 0 1px 3px rgba(0, 0, 0, 0.06);
.logo {
margin: 10px 0 30px 0;
}
.act,
.pwd {
margin: 8px 0;
height: 53px;
line-height: 53px;
font-size: 16px;
width: 100%;
padding: 0 8px;
box-sizing: border-box;
border: 2px solid #c8d4e7;
border-radius: 6px;
padding: 0 15px;
margin-top: 13px;
}
.act:focus,
.pwd:focus {
outline: none;
border: 2px solid rgb(255, 126, 0);
transition: 1.2s;
}
.role {
display: inline-block;
color: rgb(30, 102, 147);
font-size: 14px;
padding-right: 10px;
}
}
.login-btn {
display: inline-block;
text-align: center;
border-radius: 6px;
margin-top: 20px;
height: 43px;
line-height: 43px;
width: 100%;
background-color: rgb(255, 126, 0);
font-size: 14px !important;
border: none;
color: white;
padding: 0 !important;
cursor: pointer;
user-select: none;
}
.tip {
margin: 20px 0;
p {
padding: 3px 0;
font-size: 14px;
margin: 0;
color: #647897;
i {
margin-right: 3px;
}
span {
color: #3b3c3e;
border-radius: 2px;
margin: 0 6px;
}
.no-act:hover {
color: #568ed7;
cursor: pointer;
}
}
}
}
</style>

@ -0,0 +1,254 @@
<template>
<div class="menu-container">
<div class="menu-side" :class="{ 'menu-side-narrow': flag }">
<div style="display: flex;align-items: center;">
<Logo name="图书管理" style="padding: 0 40px;margin: 15px 0;" :flag="flag" :bag="colorLogo" />
</div>
<div style="margin-top: 12px;">
<AdminMenu :flag="flag" :routes="adminRoutes" :bag="bagMenu" @select="handleRouteSelect" />
</div>
</div>
<div class="main">
<div class="header-section">
<LevelHeader @eventListener="eventListener" @selectOperation="selectOperation" :tag="tag"
:userInfo="userInfo" />
</div>
<div class="content-section">
<router-view></router-view>
</div>
</div>
<!-- 个人中心 -->
<el-dialog :show-close="false" :visible.sync="dialogOperaion" width="26%">
<div slot="title" style="padding: 25px 0 0 20px;">
<span style="font-size: 18px;font-weight: 800;">个人中心</span>
</div>
<el-row style="padding: 10px 20px 20px 20px;">
<el-row>
<p style="font-size: 12px;padding: 3px 0;margin-bottom: 10px;">
<span class="modelName">*头像</span>
</p>
<el-upload class="avatar-uploader" action="/api/book-manage-sys-api/v1.0/file/upload"
:show-file-list="false" :on-success="handleAvatarSuccess">
<img v-if="userInfo.url" :src="userInfo.url" style="width: 80px;height: 80px;">
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
</el-upload>
</el-row>
<el-row>
<p style="font-size: 12px;padding: 3px 0;">
<span class="modelName">*用户名</span>
</p>
<input class="input-title" v-model="userInfo.name" placeholder="用户名">
</el-row>
<el-row>
<p style="font-size: 12px;padding: 3px 0;">
<span class="modelName">*用户邮箱</span>
</p>
<input class="input-title" v-model="userInfo.email" placeholder="用户邮箱">
</el-row>
</el-row>
<span slot="footer" class="dialog-footer">
<el-button class="customer" size="small" style="background-color: rgb(241, 241, 241);border: none;"
@click="dialogOperaion = false"> </el-button>
<el-button size="small" style="background-color: #15559a;border: none;" class="customer" type="info"
@click="updateUserInfo">修改</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import request from "@/utils/request.js";
import router from "@/router/index";
import { clearToken } from "@/utils/storage"
import AdminMenu from '@/components/VerticalMenu.vue';
import Logo from '@/components/Logo.vue';
import LevelHeader from '@/components/LevelHeader.vue';
export default {
name: "Admin",
components: {
Logo,
LevelHeader,
AdminMenu
},
data() {
return {
adminRoutes: [],
activeIndex: '',
userInfo: {
id: null,
url: '',
name: '',
role: null,
email: ''
},
flag: false,
tag: '可视化',
bag: 'rgb(250, 250, 250)',
colorLogo: '#1c1c1c',
bagMenu: 'rgb(250, 250, 250)',
dialogOperaion: false
};
},
created() {
let menus = router.options.routes.filter(route => route.path == '/user')[0];
this.adminRoutes = menus.children;
this.tokenCheckLoad();
this.menuOperationHistory();
},
methods: {
async updateUserInfo() {
try {
const userUpdateDTO = {
userAvatar: this.userInfo.url,
userName: this.userInfo.name,
userEmail: this.userInfo.email
}
const resposne = await this.$axios.put(`/user/update`, userUpdateDTO);
const { data } = resposne;
if (data.code === 200) {
this.dialogOperaion = false;
this.tokenCheckLoad();
this.$swal.fire({
title: '修改个人信息',
text: data.msg,
icon: 'success',
showConfirmButton: false,
timer: 1000,
});
}
} catch (e) {
this.dialogOperaion = false;
this.$swal.fire({
title: '修改个人信息异常',
text: e,
icon: 'error',
showConfirmButton: false,
timer: 2000,
});
console.error(`修改个人信息异常:${e}`);
}
},
handleAvatarSuccess(res, file) {
if (res.code !== 200) {
this.$message.error(`头像上传异常`);
return;
}
this.$message.success(`头像上传成功`);
this.userInfo.url = res.data;
},
eventListener(event) {
//
if (event === 'center') {
this.dialogOperaion = true;
}
// 退
if (event === 'loginOut') {
this.loginOut();
}
},
async loginOut() {
const confirmed = await this.$swalConfirm({
title: '退出登录?',
text: `推出后需重新登录?`,
icon: 'warning',
});
if (confirmed) {
this.$swal.fire({
title: '退出登录成功',
text: '1s 后返回登录页面',
icon: 'success',
showConfirmButton: false,
timer: 1000,
});
setTimeout(() => {
clearToken();
this.$router.push("/login");
}, 1000)
}
},
menuOperationHistory() {
this.flag = sessionStorage.getItem('flag') === 'true';
},
selectOperation(flag) {
this.flag = flag;
},
handleRouteSelect(index) {
let ary = this.adminRoutes.filter(entity => entity.path == index);
this.tag = ary[0].name;
if (this.$router.currentRoute.fullPath == index) {
return;
}
this.$router.push(index);
},
// Token
async tokenCheckLoad() {
try {
const res = await request.get('user/auth');
//
if (res.data.code === 400) {
this.$message.error(res.data.msg);
this.$router.push('/login');
return;
}
//
const { id, userAvatar: url, userName: name, userRole: role, userEmail: email } = res.data.data;
this.userInfo = { id, url, name, role, email };
//
const rolePath = role === 1 ? '/admin' : '/user';
const targetMenu = router.options.routes.find(route => route.path === rolePath);
if (targetMenu) {
this.routers = targetMenu.children;
} else {
console.warn(`未找到与角色对应的路由:${rolePath}`);
}
} catch (error) {
console.error('获取用户认证信息时发生错误:', error);
this.$message.error('认证信息加载失败,请重试!');
}
},
}
};
</script>
<style scoped lang="scss">
.menu-container {
display: flex;
height: 100vh;
width: 100%;
.menu-side {
width: 253px;
min-width: 95px;
height: 100vh;
padding-top: 10px;
box-sizing: border-box;
transition: width 0.3s ease;
background-color: rgb(250, 250, 250);
}
.menu-side-narrow {
width: 115px;
}
.main {
flex-grow: 1;
overflow-x: hidden;
.header-section {
max-width: 100%;
padding: 0 15px 0 0;
}
.content-section {
overflow-x: hidden;
flex-grow: 1;
padding: 0 15px;
box-sizing: border-box;
overflow-y: auto;
}
}
}
</style>

@ -0,0 +1,20 @@
<template>
<div>首页</div>
</template>
<script>
export default {
data() {
return {
};
},
created() {
},
methods: {
},
};
</script>
<style scoped lang="scss"></style>

@ -0,0 +1,13 @@
module.exports = {
lintOnSave: false,
devServer: {
host: "localhost",
port: 21091,
https: false,
proxy: "http://localhost:21090",
overlay: {
warning: false,
errors: false
},
}
}

@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Datasource local storage ignored files
/../../../../../../:\Desktop\project\hospital_manager\HospitalManagerApi - idea\.idea/dataSources/
/dataSources.local.xml
# Editor-based HTTP Client requests
/httpRequests/

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<annotationProcessing>
<profile name="Maven default annotation processors profile" enabled="true">
<sourceOutputDir name="target/generated-sources/annotations" />
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
<outputRelativeToContentRoot value="true" />
<module name="seniors-nursing" />
<module name="book-manage-sys-api" />
<module name="health-api" />
</profile>
</annotationProcessing>
<bytecodeTargetLevel>
<module name="class-net" target="1.8" />
</bytecodeTargetLevel>
</component>
<component name="JavacSettings">
<option name="ADDITIONAL_OPTIONS_OVERRIDE">
<module name="book-manage-sys-api" options="-parameters" />
<module name="class-net" options="-parameters" />
<module name="health-api" options="-parameters" />
<module name="seniors-nursing" options="-parameters" />
</option>
</component>
</project>

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding">
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
</component>
</project>

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

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RemoteRepositoriesConfiguration">
<remote-repository>
<option name="id" value="central" />
<option name="name" value="Central Repository" />
<option name="url" value="http://maven.aliyun.com/nexus/content/groups/public/" />
</remote-repository>
<remote-repository>
<option name="id" value="public" />
<option name="name" value="aliyun nexus" />
<option name="url" value="https://maven.aliyun.com/repository/public/" />
</remote-repository>
<remote-repository>
<option name="id" value="central" />
<option name="name" value="Maven Central repository" />
<option name="url" value="https://repo1.maven.org/maven2" />
</remote-repository>
<remote-repository>
<option name="id" value="jboss.community" />
<option name="name" value="JBoss Community repository" />
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
</remote-repository>
</component>
</project>

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="MavenProjectsManager">
<option name="originalFiles">
<list>
<option value="$PROJECT_DIR$/pom.xml" />
</list>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="1.8" project-jdk-type="JavaSDK" />
</project>

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/book-manage-sys-api.iml" filepath="$PROJECT_DIR$/book-manage-sys-api.iml" />
</modules>
</component>
</project>

@ -0,0 +1,124 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Palette2">
<group name="Swing">
<item class="com.intellij.uiDesigner.HSpacer" tooltip-text="Horizontal Spacer" icon="/com/intellij/uiDesigner/icons/hspacer.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="1" hsize-policy="6" anchor="0" fill="1" />
</item>
<item class="com.intellij.uiDesigner.VSpacer" tooltip-text="Vertical Spacer" icon="/com/intellij/uiDesigner/icons/vspacer.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="1" anchor="0" fill="2" />
</item>
<item class="javax.swing.JPanel" icon="/com/intellij/uiDesigner/icons/panel.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3" />
</item>
<item class="javax.swing.JScrollPane" icon="/com/intellij/uiDesigner/icons/scrollPane.png" removable="false" auto-create-binding="false" can-attach-label="true">
<default-constraints vsize-policy="7" hsize-policy="7" anchor="0" fill="3" />
</item>
<item class="javax.swing.JButton" icon="/com/intellij/uiDesigner/icons/button.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="0" fill="1" />
<initial-values>
<property name="text" value="Button" />
</initial-values>
</item>
<item class="javax.swing.JRadioButton" icon="/com/intellij/uiDesigner/icons/radioButton.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
<initial-values>
<property name="text" value="RadioButton" />
</initial-values>
</item>
<item class="javax.swing.JCheckBox" icon="/com/intellij/uiDesigner/icons/checkBox.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
<initial-values>
<property name="text" value="CheckBox" />
</initial-values>
</item>
<item class="javax.swing.JLabel" icon="/com/intellij/uiDesigner/icons/label.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="0" anchor="8" fill="0" />
<initial-values>
<property name="text" value="Label" />
</initial-values>
</item>
<item class="javax.swing.JTextField" icon="/com/intellij/uiDesigner/icons/textField.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JPasswordField" icon="/com/intellij/uiDesigner/icons/passwordField.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JFormattedTextField" icon="/com/intellij/uiDesigner/icons/formattedTextField.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JTextArea" icon="/com/intellij/uiDesigner/icons/textArea.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTextPane" icon="/com/intellij/uiDesigner/icons/textPane.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JEditorPane" icon="/com/intellij/uiDesigner/icons/editorPane.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JComboBox" icon="/com/intellij/uiDesigner/icons/comboBox.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="2" anchor="8" fill="1" />
</item>
<item class="javax.swing.JTable" icon="/com/intellij/uiDesigner/icons/table.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JList" icon="/com/intellij/uiDesigner/icons/list.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="2" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTree" icon="/com/intellij/uiDesigner/icons/tree.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTabbedPane" icon="/com/intellij/uiDesigner/icons/tabbedPane.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
<preferred-size width="200" height="200" />
</default-constraints>
</item>
<item class="javax.swing.JSplitPane" icon="/com/intellij/uiDesigner/icons/splitPane.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
<preferred-size width="200" height="200" />
</default-constraints>
</item>
<item class="javax.swing.JSpinner" icon="/com/intellij/uiDesigner/icons/spinner.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
</item>
<item class="javax.swing.JSlider" icon="/com/intellij/uiDesigner/icons/slider.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
</item>
<item class="javax.swing.JSeparator" icon="/com/intellij/uiDesigner/icons/separator.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3" />
</item>
<item class="javax.swing.JProgressBar" icon="/com/intellij/uiDesigner/icons/progressbar.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1" />
</item>
<item class="javax.swing.JToolBar" icon="/com/intellij/uiDesigner/icons/toolbar.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1">
<preferred-size width="-1" height="20" />
</default-constraints>
</item>
<item class="javax.swing.JToolBar$Separator" icon="/com/intellij/uiDesigner/icons/toolbarSeparator.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="0" anchor="0" fill="1" />
</item>
<item class="javax.swing.JScrollBar" icon="/com/intellij/uiDesigner/icons/scrollbar.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="0" anchor="0" fill="2" />
</item>
</group>
</component>
</project>

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<module version="4">
<component name="FacetManager">
<facet type="Spring" name="Spring">
<configuration />
</facet>
</component>
</module>

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

@ -0,0 +1,149 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.kmbeast</groupId>
<artifactId>book-manage-sys-api</artifactId>
<version>1.0-SNAPSHOT</version>
<name>book-manage-sys-api</name>
<!--编译配置-->
<properties>
<news.build.sourceEncoding>UTF-8</news.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<!--springboot父亲依赖-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.4.RELEASE</version>
<relativePath/>
</parent>
<!--依赖-->
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
<!--SpringBoot 启动包-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!--SpringBoot Web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--SpringBoot Aop-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!--Lombok 简化实体类开发-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.14.3</version> <!-- 请检查是否有更新的版本 -->
</dependency>
<!--数据库链接-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- mybatis依赖-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.2</version>
</dependency>
<!--excel处理-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.2.1</version>
</dependency>
<!--excel处理-->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>4.1.2</version>
</dependency>
<!-- JWT相关 -->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.0</version>
</dependency>
<!-- JWT相关 -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
<!-- JWT相关 -->
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itext-asian</artifactId>
<version>5.2.0</version>
</dependency>
<!-- JSON解析 -->
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.33</version>
</dependency>
</dependencies>
<build>
<defaultGoal>compile</defaultGoal>
<!--定义资源路径-->
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>*/*.*</include>
<include>*.*</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
<repositories>
<repository>
<id>public</id>
<name>aliyun nexus</name>
<url>https://maven.aliyun.com/repository/public/</url>
<releases>
<enabled>true</enabled>
</releases>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>public</id>
<name>aliyun nexus</name>
<url>https://maven.aliyun.com/repository/public/</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
</project>

@ -0,0 +1,16 @@
package cn.kmbeast;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
*
*/
@MapperScan("cn.kmbeast.mapper")
@SpringBootApplication
public class BookManageSysApplication {
public static void main(String[] args) {
SpringApplication.run(BookManageSysApplication.class, args);
}
}

@ -0,0 +1,65 @@
package cn.kmbeast.Interceptor;
import cn.kmbeast.context.LocalThreadHolder;
import cn.kmbeast.pojo.api.ApiResult;
import cn.kmbeast.pojo.api.Result;
import cn.kmbeast.utils.JwtUtil;
import com.alibaba.fastjson2.JSONObject;
import io.jsonwebtoken.Claims;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.Writer;
/**
* token
* tokentoken
* token
* token
*
* @author B
*/
public class JwtInterceptor implements HandlerInterceptor {
/**
*
*
* @param request current HTTP request
* @param response current HTTP response
* @param handler chosen handler to execute, for type and/or instance evaluation
* @return boolean true false
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String requestMethod = request.getMethod();
// 放行预检请求
if ("OPTIONS".equals(requestMethod)) {
return true;
}
String requestURI = request.getRequestURI();
// 登录及错误等请求不做拦截
if (requestURI.contains("/login") || requestURI.contains("/error") || requestURI.contains("/file") || requestURI.contains("/register")) {
return true;
}
String token = request.getHeader("token");
Claims claims = JwtUtil.fromToken(token);
// 解析不成功,直接退回!访问后续资源的可能性都没有!
if (claims == null) {
Result<String> error = ApiResult.error("身份认证异常,请先登录");
response.setContentType("application/json;charset=UTF-8");
Writer stream = response.getWriter();
// 将失败信息输出
stream.write(JSONObject.toJSONString(error));
stream.flush();
stream.close();
return false;
}
Integer userId = claims.get("id", Integer.class);
Integer roleId = claims.get("role", Integer.class);
// 将解析出来的用户ID、用户角色放置于LocalThread中当前线程可用
LocalThreadHolder.setUserId(userId, roleId);
return true;
}
}

@ -0,0 +1,16 @@
package cn.kmbeast.aop;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
*
*
*/
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Pager {
}

@ -0,0 +1,43 @@
package cn.kmbeast.aop;
import cn.kmbeast.pojo.dto.query.base.QueryDto;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class PagerAspect {
/**
* @Pager
*
* @param joinPoint
* @param pager
* @return
* @throws Throwable
*/
@Around("@annotation(pager)")
public Object handlePageableParams(ProceedingJoinPoint joinPoint, Pager pager) throws Throwable {
Object[] args = joinPoint.getArgs();
for (Object arg : args) {
if (arg instanceof QueryDto) {
QueryDto queryDTO = (QueryDto) arg;
configPager(queryDTO);
}
}
return joinPoint.proceed(args);
}
/**
*
*
* @param queryDTO DTO
*/
private void configPager(QueryDto queryDTO) {
if (queryDTO.getCurrent() != null && queryDTO.getSize() != null) {
queryDTO.setCurrent((queryDTO.getCurrent() - 1) * queryDTO.getSize());
}
}
}

@ -0,0 +1,22 @@
package cn.kmbeast.aop;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
*
*
* 使
*/
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Protector {
/**
*
*
* @return String
*/
String role() default "";
}

@ -0,0 +1,74 @@
package cn.kmbeast.aop;
import cn.kmbeast.context.LocalThreadHolder;
import cn.kmbeast.pojo.api.ApiResult;
import cn.kmbeast.pojo.em.RoleEnum;
import cn.kmbeast.service.UserService;
import cn.kmbeast.utils.JwtUtil;
import io.jsonwebtoken.Claims;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Objects;
/**
*
*/
@Aspect
@Component
public class ProtectorAspect {
/**
*
* --- ---
*
*
* @param proceedingJoinPoint
* @return Object
* @author B
*/
@Around("@annotation(cn.kmbeast.aop.Protector)")
public Object auth(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
String token = request.getHeader("token");
if (token == null) {
return ApiResult.error("身份认证失败,请先登录");
}
Claims claims = JwtUtil.fromToken(token);
if (claims == null) {
return ApiResult.error("身份认证失败,请先登录");
}
Integer userId = claims.get("id", Integer.class);
Integer roleId = claims.get("role", Integer.class);
// 获取被拦截方法的签名
MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();
// 获取方法上的@Protector注解实例
Protector protectorAnnotation = signature.getMethod().getAnnotation(Protector.class);
if (protectorAnnotation == null) {
return ApiResult.error("身份认证失败,请先登录");
}
String role = protectorAnnotation.role();
// 验证用户角色
if (!"".equals(role)) {
if (!Objects.equals(RoleEnum.ROLE(Math.toIntExact(roleId)), role)) {
return ApiResult.error("无操作权限");
}
}
// 放在 ThreadLocal里面当前线程都可用
LocalThreadHolder.setUserId(userId, roleId);
Object result = proceedingJoinPoint.proceed();
// 请求结束,释放资源
LocalThreadHolder.clear();
return result;
}
}

@ -0,0 +1,31 @@
package cn.kmbeast.config;
import cn.kmbeast.Interceptor.JwtInterceptor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* API
*/
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Value("${my-server.api-context-path}")
private String API;
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 拦截器注册
registry.addInterceptor(new JwtInterceptor())
.addPathPatterns("/**")
// 放行登录、注册请求
.excludePathPatterns(
API + "/user/login",
API + "/user/register",
API + "/file/upload",
API + "/file/getFile"
);
}
}

@ -0,0 +1,20 @@
package cn.kmbeast.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
*
*/
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedHeaders("*")
.allowedMethods("*")
.maxAge(5000);
}
}

@ -0,0 +1,58 @@
package cn.kmbeast.context;
import java.util.HashMap;
import java.util.Map;
/**
*
*
* @author B
*/
public class LocalThreadHolder {
private static final ThreadLocal<Map<String, Integer>> USER_HOLDER = new ThreadLocal<>();
/**
*
*
* @param userId ID
* @param userRole
* @author B
*/
public static void setUserId(Integer userId, Integer userRole) {
Map<String, Integer> map = new HashMap<>();
map.put("userId", userId);
map.put("userRole", userRole);
USER_HOLDER.set(map);
}
/**
* ID
*
* @return Integer
* @author B
*/
public static Integer getUserId() {
return USER_HOLDER.get().get("userId");
}
/**
*
*
* @return Integer
* @author B
*/
public static Integer getRoleId() {
return USER_HOLDER.get().get("userRole");
}
/**
* 线
*
* @author B
*/
public static void clear() {
USER_HOLDER.remove();
}
}

@ -0,0 +1,141 @@
package cn.kmbeast.controller;
import cn.kmbeast.utils.IdFactoryUtil;
import cn.kmbeast.utils.PathUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;
/**
*
*
* @since 2024-03-22
*/
@RestController
@RequestMapping("/file")
public class FileController {
@Value("${my-server.api-context-path}")
private String API;
/**
*
*
* @param multipartFile
* @return
*/
@PostMapping("/upload")
public Map<String, Object> uploadFile(@RequestParam("file") MultipartFile multipartFile) {
String uuid = IdFactoryUtil.getFileId();
String fileName = uuid + multipartFile.getOriginalFilename();
Map<String, Object> rep = new HashMap<>();
try {
if (uploadFile(multipartFile, fileName)) {
rep.put("code", 200);
rep.put("data", API+ "/file/getFile?fileName=" + fileName);
return rep;
}
} catch (IOException e) {
rep.put("code", 400);
rep.put("msg", "文件上传异常");
return rep;
}
rep.put("code", 400);
rep.put("msg", "文件上传异常");
return rep;
}
/**
*
*
* @param multipartFile
* @return
*/
@PostMapping("/video/upload")
public Map<String, Object> videoUpload(@RequestParam("file") MultipartFile multipartFile) {
String uuid = IdFactoryUtil.getFileId();
String fileName = uuid + multipartFile.getOriginalFilename();
Map<String, Object> rep = new HashMap<>();
try {
if (uploadFile(multipartFile, fileName)) {
rep.put("code", 200);
rep.put("data", API+ "/file/getFile?fileName=" + fileName);
return rep;
}
} catch (IOException e) {
rep.put("code", 400);
rep.put("msg", "文件上传异常");
return rep;
}
rep.put("code", 400);
rep.put("msg", "文件上传异常");
return rep;
}
/**
*
*
* @param multipartFile
* @param fileName
* @return boolean
* @throws IOException
*/
public boolean uploadFile(MultipartFile multipartFile, String fileName) throws IOException {
return fileName(multipartFile, fileName);
}
public static boolean fileName(MultipartFile multipartFile, String fileName) throws IOException {
File fileDir = new File(PathUtils.getClassLoadRootPath() + "/pic");
if (!fileDir.exists()) {
if (!fileDir.mkdirs()) {
return false;
}
}
File file = new File(fileDir.getAbsolutePath() + "/" + fileName);
if (file.exists()) {
if (!file.delete()) {
return false;
}
}
if (file.createNewFile()) {
multipartFile.transferTo(file);
return true;
}
return false;
}
/**
*
*
* @param imageName
* @param response
* @throws IOException
*/
@GetMapping("/getFile")
public void getImage(@RequestParam("fileName") String imageName,
HttpServletResponse response) throws IOException {
File fileDir = new File(PathUtils.getClassLoadRootPath() + "/pic");
File image = new File(fileDir.getAbsolutePath() + "/" + imageName);
if (image.exists()) {
FileInputStream fileInputStream = new FileInputStream(image);
byte[] bytes = new byte[fileInputStream.available()];
if (fileInputStream.read(bytes) > 0) {
OutputStream outputStream = response.getOutputStream();
outputStream.write(bytes);
outputStream.close();
}
fileInputStream.close();
}
}
}

@ -0,0 +1,68 @@
package cn.kmbeast.controller;
import cn.kmbeast.aop.Pager;
import cn.kmbeast.pojo.api.Result;
import cn.kmbeast.pojo.dto.query.extend.NoticeQueryDto;
import cn.kmbeast.pojo.entity.Notice;
import cn.kmbeast.service.NoticeService;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
/**
* Controller
*/
@RestController
@RequestMapping(value = "/notice")
public class NoticeController {
@Resource
private NoticeService noticeService;
/**
*
*
* @param notice
* @return Result<Void>
*/
@PostMapping(value = "/save")
public Result<Void> save(@RequestBody Notice notice) {
return noticeService.save(notice);
}
/**
*
*
* @param ids ID
* @return Result<Void>
*/
@PostMapping(value = "/batchDelete")
public Result<Void> batchDelete(@RequestBody List<Integer> ids) {
return noticeService.batchDelete(ids);
}
/**
*
*
* @param notice
* @return Result<Void>
*/
@PutMapping(value = "/update")
public Result<Void> update(@RequestBody Notice notice) {
return noticeService.update(notice);
}
/**
*
*
* @param noticeQueryDto
* @return Result<List < Notice>>
*/
@Pager
@PostMapping(value = "/query")
public Result<List<Notice>> query(@RequestBody NoticeQueryDto noticeQueryDto) {
return noticeService.query(noticeQueryDto);
}
}

@ -0,0 +1,164 @@
package cn.kmbeast.controller;
import cn.kmbeast.aop.Pager;
import cn.kmbeast.aop.Protector;
import cn.kmbeast.pojo.api.Result;
import cn.kmbeast.pojo.dto.query.extend.UserQueryDto;
import cn.kmbeast.pojo.dto.update.UserLoginDTO;
import cn.kmbeast.pojo.dto.update.UserRegisterDTO;
import cn.kmbeast.pojo.dto.update.UserUpdateDTO;
import cn.kmbeast.pojo.entity.User;
import cn.kmbeast.pojo.vo.ChartVO;
import cn.kmbeast.pojo.vo.UserVO;
import cn.kmbeast.service.UserService;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/user")
public class UserController {
@Resource
private UserService userService;
/**
*
*
* @param userLoginDTO
* @return Result<String>
*/
@PostMapping(value = "/login")
@ResponseBody
public Result<Object> login(@RequestBody UserLoginDTO userLoginDTO) {
return userService.login(userLoginDTO);
}
/**
* token
*/
@Protector
@GetMapping(value = "/auth")
@ResponseBody
public Result<UserVO> auth() {
return userService.auth();
}
/**
* ID
*
* @param id ID
* @return Result<UserVO>
*/
@Protector
@GetMapping(value = "/getById/{id}")
@ResponseBody
public Result<UserVO> getById(@PathVariable Integer id) {
return userService.getById(id);
}
/**
*
*
* @param userRegisterDTO
* @return Result<String>
*/
@PostMapping(value = "/register")
@ResponseBody
public Result<String> register(@RequestBody UserRegisterDTO userRegisterDTO) {
return userService.register(userRegisterDTO);
}
/**
*
*
* @param userRegisterDTO
* @return Result<String>
*/
@Protector(role = "管理员")
@PostMapping(value = "/insert")
@ResponseBody
public Result<String> insert(@RequestBody UserRegisterDTO userRegisterDTO) {
return userService.insert(userRegisterDTO);
}
/**
*
*
* @param userUpdateDTO
* @return Result<String>
*/
@Protector
@PutMapping(value = "/update")
@ResponseBody
public Result<String> update(@RequestBody UserUpdateDTO userUpdateDTO) {
return userService.update(userUpdateDTO);
}
/**
*
*
* @param user
* @return Result<String>
*/
@Protector(role = "管理员")
@PutMapping(value = "/backUpdate")
@ResponseBody
public Result<String> backUpdate(@RequestBody User user) {
return userService.backUpdate(user);
}
/**
*
*
* @param map
* @return Result<String>
*/
@PutMapping(value = "/updatePwd")
@ResponseBody
public Result<String> updatePwd(@RequestBody Map<String, String> map) {
return userService.updatePwd(map);
}
/**
*
*/
@Protector(role = "管理员")
@PostMapping(value = "/batchDelete")
@ResponseBody
public Result<String> batchDelete(@RequestBody List<Integer> ids) {
return userService.batchDelete(ids);
}
/**
*
*
* @param userQueryDto
* @return Result<List < User>>
*/
@Pager
@Protector(role = "管理员")
@PostMapping(value = "/query")
@ResponseBody
public Result<List<User>> query(@RequestBody UserQueryDto userQueryDto) {
return userService.query(userQueryDto);
}
/**
*
*
* @return Result<List < ChartVO>>
*/
@GetMapping(value = "/daysQuery/{day}")
@ResponseBody
public Result<List<ChartVO>> query(@PathVariable Integer day) {
return userService.daysQuery(day);
}
}

@ -0,0 +1,33 @@
package cn.kmbeast.controller;
import cn.kmbeast.pojo.api.Result;
import cn.kmbeast.pojo.vo.ChartVO;
import cn.kmbeast.service.ViewsService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.List;
/**
*
*/
@RestController
@RequestMapping(value = "/views")
public class ViewsController {
@Resource
private ViewsService viewsService;
/**
*
*
* @return Result<List < ChartVO>>
*/
@GetMapping("/staticControls")
public Result<List<ChartVO>> staticControls() {
return viewsService.staticControls();
}
}

@ -0,0 +1,29 @@
package cn.kmbeast.mapper;
import cn.kmbeast.pojo.dto.query.extend.NoticeQueryDto;
import cn.kmbeast.pojo.dto.query.extend.TestHistoryQueryDto;
import cn.kmbeast.pojo.entity.Notice;
import cn.kmbeast.pojo.entity.TestHistory;
import cn.kmbeast.pojo.vo.TestHistoryVO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
*
*/
@Mapper
public interface NoticeMapper {
void save(Notice notice);
void update(Notice notice);
void batchDelete(@Param(value = "ids") List<Integer> ids);
List<Notice> query(NoticeQueryDto noticeQueryDto);
Integer queryCount(NoticeQueryDto noticeQueryDto);
}

@ -0,0 +1,62 @@
package cn.kmbeast.mapper;
import cn.kmbeast.pojo.dto.query.extend.UserQueryDto;
import cn.kmbeast.pojo.entity.User;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
*
*/
public interface UserMapper {
/**
*
*
* @param userInsert
* @return int
*/
int insert(User userInsert);
/**
*
*
* @param userQueryDto
* @return List<User>
*/
List<User> query(UserQueryDto userQueryDto);
/**
*
*
* @param userQueryDto
* @return int
*/
int queryCount(UserQueryDto userQueryDto);
/**
*
*
* @param user
* @return int
*/
int update(User user);
/**
*
*
* @param ids ID
*/
void batchDelete(@Param(value = "ids") List<Integer> ids);
/**
*
*
* @param user
* @return User
*/
User getByActive(User user);
}

@ -0,0 +1,88 @@
package cn.kmbeast.pojo.api;
import lombok.Getter;
import lombok.Setter;
/**
*
*
* @author B
* @param <T>
*/
@Setter
@Getter
public class ApiResult<T> extends Result<T> {
/**
*
*/
private T data;
/**
* 使
*/
private Integer total;
public ApiResult(Integer code) {
super(code, "操作成功");
}
public ApiResult(Integer code, String msg) {
super(code, msg);
}
public ApiResult(Integer code, String msg, T data) {
super(code, msg);
this.data = data;
}
public static <T> Result<T> success() {
ApiResult<T> result = new ApiResult<>(ResultCode.REQUEST_SUCCESS.getCode());
result.setData(null);
return result;
}
public static <T> Result<T> success(T data) {
ApiResult<T> result = new ApiResult<>(ResultCode.REQUEST_SUCCESS.getCode());
result.setData(data);
return result;
}
/**
*
*
* @param data
* @param total
* @param <T>
*/
public static <T> Result<T> success(T data, Integer total) {
ApiResult<T> result = new ApiResult<>(ResultCode.REQUEST_SUCCESS.getCode());
result.setData(data);
result.setTotal(total);
return result;
}
public static <T> Result<T> success(String msg) {
return new Result<>(ResultCode.REQUEST_SUCCESS.getCode(), msg);
}
public static <T> Result<T> success(String msg, T data) {
return new ApiResult<T>(ResultCode.REQUEST_SUCCESS.getCode(), msg, data);
}
public static <T> Result<T> error(String msg) {
return new Result<T>(ResultCode.REQUEST_ERROR.getCode(), msg);
}
public ApiResult(T data, Integer total) {
this.data = data;
this.total = total;
}
public ApiResult(Integer code, String msg, T data, Integer total) {
super(code, msg);
this.data = data;
this.total = total;
}
}

@ -0,0 +1,55 @@
package cn.kmbeast.pojo.api;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
/**
* -
*
* @param <T>
* @author B
*/
@EqualsAndHashCode(callSuper = true)
@AllArgsConstructor
@NoArgsConstructor
@Data
public class PageResult<T> extends Result<T> {
/**
*
*/
private T data;
/**
*
*/
private Integer total;
/**
*
*
* @param code
* @author B
*/
public PageResult(Integer code) {
super(code, "查询成功");
}
/**
*
*
* @param data
* @param total
* @param <T>
* @return <T>
* @author B
*/
public static <T> Result<T> success(T data, Integer total) {
PageResult<T> result = new PageResult<>(ResultCode.REQUEST_SUCCESS.getCode());
result.setData(data);
result.setTotal(total);
return result;
}
}

@ -0,0 +1,49 @@
package cn.kmbeast.pojo.api;
/**
*
*
* @param <T>
*/
public class Result<T> {
/**
*
*/
private Integer code;
/**
*
*/
private String msg;
@Override
public String toString() {
return "Result{" +
"code=" + code +
", msg='" + msg + '\'' +
'}';
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public Result() {
}
public Result(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
}

@ -0,0 +1,36 @@
package cn.kmbeast.pojo.api;
/**
*
*/
public enum ResultCode {
/**
*
*/
REQUEST_SUCCESS(200),
/**
*
*/
REQUEST_ERROR(400);
private Integer code;
@Override
public String toString() {
return "ResultCode{" +
"code=" + code +
'}';
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
ResultCode(Integer code) {
this.code = code;
}
}

@ -0,0 +1,33 @@
package cn.kmbeast.pojo.dto.query.base;
import lombok.*;
import java.time.LocalDateTime;
/**
* 使
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
@Builder
public class QueryDto {
/**
*
*/
private Integer current;
/**
*
*/
private Integer size;
/**
*
*/
private LocalDateTime startTime;
/**
*
*/
private LocalDateTime endTime;
}

@ -0,0 +1,34 @@
package cn.kmbeast.pojo.dto.query.extend;
import lombok.Data;
/**
*
*/
@Data
public class AutoCreatePaper {
/**
*
*/
private Integer oneSelected;
/**
*
*/
private Integer doubleSelected;
/**
*
*/
private Integer putWord;
/**
*
*/
private Integer judgeSelected;
/**
* ID
*/
private Integer projectId;
/**
* ID
*/
private Integer paperId;
}

@ -0,0 +1,23 @@
package cn.kmbeast.pojo.dto.query.extend;
import cn.kmbeast.pojo.dto.query.base.QueryDto;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* Dto
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class EvaluationsQueryDto extends QueryDto {
/**
*
*/
private String contentType;
/**
*
*/
private String content;
}

@ -0,0 +1,12 @@
package cn.kmbeast.pojo.dto.query.extend;
import cn.kmbeast.pojo.dto.query.base.QueryDto;
import lombok.Data;
import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = true)
@Data
public class NoticeQueryDto extends QueryDto {
private String name;
private String content;
}

@ -0,0 +1,13 @@
package cn.kmbeast.pojo.dto.query.extend;
import cn.kmbeast.pojo.dto.query.base.QueryDto;
import lombok.Data;
import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = true)
@Data
public class PaperPracticeQueryDto extends QueryDto {
private Integer id;
private Integer paperId;
private Integer practiceId;
}

@ -0,0 +1,19 @@
package cn.kmbeast.pojo.dto.query.extend;
import cn.kmbeast.pojo.dto.query.base.QueryDto;
import lombok.Data;
import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = true)
@Data
public class PaperQueryDto extends QueryDto {
private Integer id;
private String name;
private Integer projectId;
private Integer userId;
private String detail;
private Long limitTime;
private Integer totalScore;
private Boolean isShow;
}

@ -0,0 +1,17 @@
package cn.kmbeast.pojo.dto.query.extend;
import cn.kmbeast.pojo.dto.query.base.QueryDto;
import lombok.Data;
import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = true)
@Data
public class PracticeQueryDto extends QueryDto {
private Integer id;
private String askItem;
private Integer projectId;
private Integer practiceType;
private Integer userId;
private String detail;
private Integer score;
}

@ -0,0 +1,11 @@
package cn.kmbeast.pojo.dto.query.extend;
import cn.kmbeast.pojo.dto.query.base.QueryDto;
import lombok.Data;
import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = true)
@Data
public class ProjectTypeQueryDto extends QueryDto {
private String name;
}

@ -0,0 +1,15 @@
package cn.kmbeast.pojo.dto.query.extend;
import cn.kmbeast.pojo.dto.query.base.QueryDto;
import lombok.Data;
import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = true)
@Data
public class TestHistoryQueryDto extends QueryDto {
private Integer id;
private String answer;
private Integer practiceId;
private Integer paperId;
private Integer userId;
}

@ -0,0 +1,37 @@
package cn.kmbeast.pojo.dto.query.extend;
import cn.kmbeast.pojo.dto.query.base.QueryDto;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* DTO
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class UserQueryDto extends QueryDto {
/**
*
*/
private String userAccount;
/**
*
*/
private String userName;
/**
*
*/
private String userEmail;
/**
*
*/
private Boolean role;
/**
*
*/
private Boolean isLogin;
/**
*
*/
private Boolean isWord;
}

@ -0,0 +1,24 @@
package cn.kmbeast.pojo.dto.save;
import cn.kmbeast.pojo.entity.TestHistory;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
*
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class TestHistoryDto extends TestHistory {
/**
*
*/
private Long startTime;
/**
*
*/
private Long endTime;
}

@ -0,0 +1,19 @@
package cn.kmbeast.pojo.dto.update;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserLoginDTO {
/**
*
*/
private String userAccount;
/**
*
*/
private String userPwd;
}

@ -0,0 +1,31 @@
package cn.kmbeast.pojo.dto.update;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserRegisterDTO {
/**
*
*/
private String userName;
/**
*
*/
private String userAccount;
/**
*
*/
private String userPwd;
/**
*
*/
private String userEmail;
/**
*
*/
private String userAvatar;
}

@ -0,0 +1,35 @@
package cn.kmbeast.pojo.dto.update;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserUpdateDTO {
/**
*
*/
private String userAccount;
/**
*
*/
private String userName;
/**
*
*/
private String userPwd;
/**
*
*/
private String userAvatar;
/**
*
*/
private String userEmail;
}

@ -0,0 +1,23 @@
package cn.kmbeast.pojo.em;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public enum AuditStatusEnum {
NO_AUDIT(1, "未审核"),
HAVE_AUDIT(2, "已经审核");
/**
*
*/
private final Integer status;
/**
*
*/
private final String detail;
}

@ -0,0 +1,22 @@
package cn.kmbeast.pojo.em;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public enum IsReadEnum {
READ_OK(true, "已读"),
READ_NO(false, "未读");
/**
*
*/
private final Boolean status;
/**
*
*/
private final String detail;
}

@ -0,0 +1,25 @@
package cn.kmbeast.pojo.em;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
*
*/
@Getter
@AllArgsConstructor
public enum LoginStatusEnum {
USE(false, "可登录"),
BANK_USE(true, "登录状态异常");
/**
*
*/
private final Boolean flag;
/**
*
*/
private final String name;
}

@ -0,0 +1,26 @@
package cn.kmbeast.pojo.em;
import lombok.AllArgsConstructor;
import lombok.Getter;
@AllArgsConstructor
@Getter
public enum MessageType {
// 互动消息(评论被被人回复、评论被别人点赞)、指标消息、系统通知类
EVALUATIONS_BY_REPLY(1,"评论"),
EVALUATIONS_BY_UPVOTE(2,"点赞"),
DATA_MESSAGE(3,"指标提醒"),
SYSTEM_INFO(4,"系统通知");
/**
*
*/
private final Integer type;
/**
*
*/
private final String detail;
}

@ -0,0 +1,27 @@
package cn.kmbeast.pojo.em;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
*
*/
@Getter
@AllArgsConstructor
public enum PracticeTypeEnum {
ONE_SELECTED(1, "单选题"),
DOUBLE_SELECTED(2, "多选题"),
WORD_PUT(3, "填空题"),
JUDGEMENT(4, "判断题");
/**
*
*/
private final Integer type;
/**
*
*/
private final String detail;
}

@ -0,0 +1,40 @@
package cn.kmbeast.pojo.em;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
*
*/
@Getter
@AllArgsConstructor
public enum RoleEnum {
ADMIN(1, "管理员"),
USER(2, "用户");
/**
*
*/
private final Integer role;
/**
*
*/
private final String name;
/**
*
*
* @param role
* @return String
*/
public static String ROLE(Integer role) {
for (RoleEnum value : RoleEnum.values()) {
if (value.getRole().equals(role)) {
return value.name;
}
}
return null;
}
}

@ -0,0 +1,25 @@
package cn.kmbeast.pojo.em;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
*
*/
@Getter
@AllArgsConstructor
public enum WordStatusEnum {
USE(false, "可用"),
BANK_USE(true, "禁言状态");
/**
*
*/
private final Boolean flag;
/**
*
*/
private final String name;
}

@ -0,0 +1,61 @@
package cn.kmbeast.pojo.entity;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.time.LocalDateTime;
/**
*
*/
@Data
@EqualsAndHashCode(callSuper = false)
public class Evaluations {
/**
*
*/
private Integer id;
/**
* ID
*/
private Integer parentId;
/**
* ID
*/
private Integer commenterId;
/**
* ID
*/
private Integer replierId;
/**
*
*/
private String contentType;
/**
*
*/
private String content;
/**
* ID
*/
private Integer contentId;
/**
* (",")
*/
private String upvoteList;
/**
*
*/
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTime;
}

@ -0,0 +1,24 @@
package cn.kmbeast.pojo.entity;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
/**
*
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Notice {
private Integer id;
private String name;
private String content;
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTime;
}

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

Loading…
Cancel
Save