parent
51b7169a18
commit
fbed7474ae
Binary file not shown.
@ -1,35 +0,0 @@
|
||||
package com.example.demo.controller;
|
||||
|
||||
|
||||
import com.example.demo.pojo.Borrow;
|
||||
import com.example.demo.pojo.Result;
|
||||
import com.example.demo.service.BorrowService;
|
||||
|
||||
import com.example.demo.service.UserService;
|
||||
import jakarta.servlet.http.HttpSession;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.CrossOrigin;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@CrossOrigin
|
||||
@RequestMapping("/borrow")
|
||||
public class BorrowController {
|
||||
@Autowired
|
||||
private BorrowService borrowService;
|
||||
|
||||
|
||||
|
||||
//租借书
|
||||
@PostMapping("/borrowbook")
|
||||
public Result borrowbook(String title, HttpSession session){
|
||||
Borrow borrow=new Borrow();
|
||||
borrow.setTitle(title);
|
||||
borrow.setBorrower((String) session.getAttribute("username"));
|
||||
borrowService.borrow(borrow);
|
||||
return Result.success(borrow);
|
||||
}
|
||||
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
package com.example.demo.mapper;
|
||||
|
||||
import com.example.demo.pojo.Borrow;
|
||||
import org.apache.ibatis.annotations.Insert;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface BorrowMapper {
|
||||
@Insert("INSERT INTO borrow (title, borrower, borrow_time)\n" +
|
||||
"VALUES \n" +
|
||||
"(#{title}, #{borrower}, now())")
|
||||
void borrowrecord(Borrow borrow);
|
||||
|
||||
|
||||
@Insert("INSERT INTO borrow (title, borrower, return_time)\n" +
|
||||
"VALUES \n" +
|
||||
"(#{title}, #{borrower}, now())")
|
||||
void returnrecord(Borrow borrow);
|
||||
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
package com.example.demo.mapper;
|
||||
|
||||
|
||||
import com.example.demo.pojo.User;
|
||||
import com.example.demo.pojo.info;
|
||||
import org.apache.ibatis.annotations.Insert;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
import org.apache.ibatis.annotations.Update;
|
||||
|
||||
@Mapper
|
||||
public interface UserMapper {
|
||||
@Select("select * from user where username=#{username}")
|
||||
User findByUserName(String username);
|
||||
|
||||
@Insert("insert into user(username,password,vip,create_time,update_time,admin,balance)" +
|
||||
" values(#{username},#{password},#{vip},now(),now(),#{admin},#{balance})")
|
||||
void add(String username,String password,int vip,int admin,float balance);
|
||||
|
||||
@Select("SELECT password FROM user WHERE username=#{username}")
|
||||
String login(String username);
|
||||
|
||||
|
||||
@Select("select username,pic from user where username=#{username}")
|
||||
info getinfo( String username);
|
||||
|
||||
|
||||
//充钱
|
||||
@Update("UPDATE user\n" +
|
||||
"SET balance=balance+#{money1}\n" +
|
||||
"WHERE username=#{username};")
|
||||
void recharge(float money1,String username);
|
||||
|
||||
@Select("select balance from user where username=#{username}")
|
||||
float findbalance(Object username);
|
||||
}
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,68 @@
|
||||
package com.example.demo.controller;
|
||||
|
||||
import com.example.demo.mapper.BorrowMapper;
|
||||
import com.example.demo.pojo.Article;
|
||||
import com.example.demo.pojo.Borrow;
|
||||
import com.example.demo.pojo.Result;
|
||||
import com.example.demo.service.ArticleService;
|
||||
import com.example.demo.service.BorrowService;
|
||||
|
||||
import com.example.demo.service.UserService;
|
||||
import jakarta.servlet.http.HttpSession;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.CrossOrigin;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@RestController
|
||||
@CrossOrigin
|
||||
@RequestMapping("/borrow")
|
||||
public class BorrowController {
|
||||
@Autowired
|
||||
private BorrowService borrowService;
|
||||
|
||||
@Autowired
|
||||
private UserService userService;
|
||||
|
||||
@Autowired
|
||||
private ArticleService articleService;
|
||||
@Autowired
|
||||
private BorrowMapper borrowMapper;
|
||||
|
||||
|
||||
//租借书
|
||||
@PostMapping("/borrowbook")
|
||||
public Result borrowbook(String title, HttpSession session) {
|
||||
Borrow borrow = new Borrow();
|
||||
borrow.setTitle(title);
|
||||
borrow.setBorrower((String) session.getAttribute("username"));
|
||||
borrow.setBorrow_time(LocalDateTime.now());
|
||||
Article article = articleService.selectonearticle(title);
|
||||
float money = article.getMoney();
|
||||
float balance = userService.findmoney(session.getAttribute("username"));
|
||||
if (balance >= money) {
|
||||
borrowService.borrow(borrow);
|
||||
userService.deduct(money,session.getAttribute("username"));
|
||||
return Result.success(borrow);
|
||||
}
|
||||
else{
|
||||
return Result.error("余额不足!");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//还书
|
||||
@PostMapping("/returnbook")
|
||||
public Result returnbook(String title, HttpSession session){
|
||||
Borrow borrow=new Borrow();
|
||||
borrow.setTitle(title);
|
||||
borrow.setBorrower((String) session.getAttribute("username"));
|
||||
borrow.setReturn_time(LocalDateTime.now());
|
||||
borrowService.returnbook(borrow);
|
||||
borrowMapper.fine();
|
||||
return Result.success(borrow);
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
package com.example.demo.controller;
|
||||
|
||||
import com.example.demo.pojo.ArticleRentRankDTO;
|
||||
import com.example.demo.service.BorrowRankService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/rank")
|
||||
public class BorrowRankController {
|
||||
|
||||
@Autowired
|
||||
private BorrowRankService borrowRankService;
|
||||
|
||||
/**
|
||||
* 本周热租榜单接口
|
||||
*/
|
||||
@GetMapping("/weekly")
|
||||
public List<ArticleRentRankDTO> weeklyRank() {
|
||||
return borrowRankService.getWeeklyRank();
|
||||
}
|
||||
|
||||
/**
|
||||
* 本月热租榜单接口
|
||||
*/
|
||||
@GetMapping("/monthly")
|
||||
public List<ArticleRentRankDTO> monthlyRank() {
|
||||
return borrowRankService.getMonthlyRank();
|
||||
}
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
package com.example.demo.mapper;
|
||||
|
||||
|
||||
import com.example.demo.pojo.User;
|
||||
import com.example.demo.pojo.info;
|
||||
import org.apache.ibatis.annotations.*;
|
||||
|
||||
@Mapper
|
||||
public interface UserMapper {
|
||||
@Select("select * from user where username=#{username}")
|
||||
User findByUserName(String username);
|
||||
|
||||
@Insert("insert into user(username,password,vip,create_time,update_time,admin,balance)" +
|
||||
" values(#{username},#{password},#{vip},now(),now(),#{admin},#{balance})")
|
||||
void add(String username,String password,int vip,int admin,float balance);
|
||||
|
||||
@Select("SELECT password FROM user WHERE username=#{username}")
|
||||
String login(String username);
|
||||
|
||||
|
||||
@Select("select username,pic from user where username=#{username}")
|
||||
info getinfo( String username);
|
||||
|
||||
|
||||
//充钱
|
||||
@Update("UPDATE user\n" +
|
||||
"SET balance=balance+#{money1}\n" +
|
||||
"WHERE username=#{username};")
|
||||
void recharge(float money1,String username);
|
||||
|
||||
|
||||
//查询余额
|
||||
@Select("select balance from user where username=#{username}")
|
||||
float findbalance(Object username);
|
||||
|
||||
//更新VIP余额
|
||||
@Update("UPDATE `user`\n" +
|
||||
"SET `vip` = CASE\n" +
|
||||
" WHEN `balance` >= 10 AND `balance` < 30 THEN '1'\n" +
|
||||
" WHEN `balance` >= 30 AND `balance` < 100 THEN '2'\n" +
|
||||
" WHEN `balance` >= 100 AND `balance` < 300 THEN '3'\n" +
|
||||
" WHEN `balance` >= 300 AND `balance` < 500 THEN '4'\n" +
|
||||
" WHEN `balance` >= 500 THEN '5'\n" +
|
||||
" ELSE `vip` -- 不满足条件的记录保持原有vip值\n" +
|
||||
"END\n" +
|
||||
"WHERE `username` = #{username};")
|
||||
void updateVIP( float balance,String username);
|
||||
|
||||
@Select("select vip from user where username=#{username}")
|
||||
int findVIP(String username);
|
||||
|
||||
//扣钱
|
||||
@Update("UPDATE user\n" +
|
||||
"SET balance=balance-#{money1}\n" +
|
||||
"WHERE username=#{username};")
|
||||
void deduct(float money1,String username);
|
||||
|
||||
//管理员删除书
|
||||
@Delete("DELETE FROM article WHERE title=#{title}")
|
||||
void deletebook(String title);
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package com.example.demo.pojo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class ArticleRentRankDTO {
|
||||
private String title; // 物品名称
|
||||
private String url; // 封面图片
|
||||
private Float money; // 租借价格
|
||||
private Integer number; // 租借次数
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
package com.example.demo.service;
|
||||
|
||||
import com.example.demo.mapper.BorrowRankMapper;
|
||||
import com.example.demo.pojo.ArticleRentRankDTO;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class BorrowRankService {
|
||||
|
||||
@Autowired // 改用Spring的@Autowired注解
|
||||
private BorrowRankMapper borrowRankMapper;
|
||||
|
||||
/**
|
||||
* 获取本周热租榜单
|
||||
*/
|
||||
public List<ArticleRentRankDTO> getWeeklyRank() {
|
||||
return borrowRankMapper.listWeeklyRank();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取本月热租榜单
|
||||
*/
|
||||
public List<ArticleRentRankDTO> getMonthlyRank() {
|
||||
return borrowRankMapper.listMonthlyRank();
|
||||
}
|
||||
}
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,24 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
@ -0,0 +1,3 @@
|
||||
{
|
||||
"recommendations": ["Vue.volar"]
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
# Vue 3 + Vite
|
||||
|
||||
This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
|
||||
|
||||
Learn more about IDE Support for Vue in the [Vue Docs Scaling up Guide](https://vuejs.org/guide/scaling-up/tooling.html#ide-support).
|
@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Library_system</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
</html>
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "library_system",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.11.0",
|
||||
"element-plus": "^2.10.4",
|
||||
"pinia": "^3.0.3",
|
||||
"vue": "^3.5.17",
|
||||
"vue-router": "^4.5.1",
|
||||
"vuex": "^4.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^24.1.0",
|
||||
"@vitejs/plugin-vue": "^6.0.0",
|
||||
"unplugin-auto-import": "^19.3.0",
|
||||
"unplugin-vue-components": "^28.8.0",
|
||||
"vite": "^7.0.4"
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 1.5 KiB |
@ -0,0 +1,71 @@
|
||||
<template>
|
||||
<div v-if="sessionInitialized">
|
||||
<el-container class="app-container">
|
||||
<el-header>
|
||||
<HeaderBar />
|
||||
</el-header>
|
||||
<el-main>
|
||||
<router-view />
|
||||
</el-main>
|
||||
<el-footer>
|
||||
<div class="footer-content">
|
||||
<p>图书馆管理系统 © {{ new Date().getFullYear() }}</p>
|
||||
</div>
|
||||
</el-footer>
|
||||
</el-container>
|
||||
</div>
|
||||
<div v-else class="loading-container">
|
||||
<el-loading-spinner />
|
||||
<p>正在初始化...</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
import { useStore } from 'vuex'
|
||||
import HeaderBar from './components/HeaderBar.vue'
|
||||
|
||||
const store = useStore()
|
||||
const sessionInitialized = computed(() => store.state.sessionInitialized)
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.app-container {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.el-main {
|
||||
flex: 1;
|
||||
padding: 0;
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
|
||||
.el-footer {
|
||||
background-color: #545c64;
|
||||
color: white;
|
||||
text-align: center;
|
||||
padding: 15px 0;
|
||||
}
|
||||
|
||||
.footer-content {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.loading-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100vh;
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
|
||||
.loading-container p {
|
||||
margin-top: 20px;
|
||||
color: #606266;
|
||||
font-size: 16px;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,62 @@
|
||||
/* Element Plus 主题定制 */
|
||||
:root {
|
||||
--el-color-primary: #165dff;
|
||||
--el-color-primary-light-3: #3c8dff;
|
||||
--el-color-primary-light-5: #6baaff;
|
||||
--el-color-primary-light-7: #a3cfff;
|
||||
--el-color-primary-light-8: #d6eaff;
|
||||
--el-color-primary-light-9: #f4faff;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', Arial, sans-serif;
|
||||
background: #f6f8fa;
|
||||
color: #222;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.el-header, .el-footer {
|
||||
background: #fff;
|
||||
box-shadow: 0 2px 8px 0 rgba(0,0,0,0.03);
|
||||
}
|
||||
|
||||
.el-menu {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.el-card {
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 12px 0 rgba(22,93,255,0.04);
|
||||
}
|
||||
|
||||
.el-main {
|
||||
padding: 32px 24px 24px 24px;
|
||||
min-height: 80vh;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--el-color-primary);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* 登录/注册页面居中 */
|
||||
.page-center {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: linear-gradient(135deg, #e3f0ff 0%, #f6f8fa 100%);
|
||||
}
|
||||
|
||||
/* 头像样式 */
|
||||
.avatar {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
box-shadow: 0 2px 8px 0 rgba(22,93,255,0.08);
|
||||
}
|
After Width: | Height: | Size: 496 B |
@ -0,0 +1,88 @@
|
||||
<template>
|
||||
<el-card class="book-card" shadow="hover" @click="$emit('click')">
|
||||
<div class="book-cover">
|
||||
<el-image :src="book.url" fit="cover" class="cover-image" :alt="book.title" />
|
||||
</div>
|
||||
<div class="book-info">
|
||||
<h3 class="book-title">{{ book.title }}</h3>
|
||||
<div class="book-meta" v-if="showMeta">
|
||||
<el-tag type="info" size="small">{{ book.content }}</el-tag>
|
||||
<el-tag type="success" size="small">¥{{ book.money }}/天</el-tag>
|
||||
</div>
|
||||
<div class="book-status"v-if="showMeta">
|
||||
<el-tag :type="book.state" size="small">
|
||||
{{ book.state }}
|
||||
</el-tag>
|
||||
<span>阅读量: {{ book.number }}次</span>
|
||||
<el-button @click="$emit('borrow')">借阅</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
defineProps({
|
||||
book: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
showMeta: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.book-card {
|
||||
cursor: pointer;
|
||||
transition: transform 0.3s;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.book-card:hover {
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
|
||||
.book-cover {
|
||||
height: 180px;
|
||||
overflow: hidden;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.cover-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.book-info {
|
||||
padding: 0 10px 10px;
|
||||
}
|
||||
|
||||
.book-title {
|
||||
margin: 0 0 10px;
|
||||
font-size: 16px;
|
||||
height: 24px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
.book-meta {
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
margin-bottom: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.book-status {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 13px;
|
||||
color: #666;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,249 @@
|
||||
<!-- src/components/HeaderBar.vue -->
|
||||
<template>
|
||||
<el-header class="header">
|
||||
<div class="logo">
|
||||
<el-icon><Reading /></el-icon>
|
||||
<span>图书馆管理系统</span>
|
||||
</div>
|
||||
<div class="nav">
|
||||
<el-menu
|
||||
:default-active="activeIndex"
|
||||
mode="horizontal"
|
||||
@select="handleSelect"
|
||||
background-color="#545c64"
|
||||
text-color="#fff"
|
||||
active-text-color="#ffd04b">
|
||||
<el-menu-item index="home">首页</el-menu-item>
|
||||
<el-menu-item index="books">图书查询</el-menu-item>
|
||||
<el-menu-item index="borrow">借阅图书</el-menu-item>
|
||||
<el-menu-item index="return">归还图书</el-menu-item>
|
||||
<el-sub-menu index="ranking">
|
||||
<template #title>排行榜</template>
|
||||
<el-menu-item index="weekly">本周热租榜</el-menu-item>
|
||||
<el-menu-item index="monthly">本月热租榜</el-menu-item>
|
||||
</el-sub-menu>
|
||||
<el-menu-item index="bookManagement" v-if="user && user.admin">
|
||||
图书管理
|
||||
</el-menu-item>
|
||||
<el-menu-item index="allBorrowRecords" v-if="user && user.admin">
|
||||
用户借阅记录
|
||||
</el-menu-item>
|
||||
</el-menu>
|
||||
</div>
|
||||
<div class="user-info">
|
||||
<el-dropdown v-if="user">
|
||||
<span class="el-dropdown-link">
|
||||
<el-avatar :src="user.pic" size="small" />
|
||||
<span class="username">{{ user.username }}</span>
|
||||
<el-icon><ArrowDown /></el-icon>
|
||||
</span>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<!-- 用户信息展示区域 -->
|
||||
<div class="user-dropdown-info">
|
||||
<div class="user-name">{{ user.username }}</div>
|
||||
<div class="user-vip">VIP{{ vip }}级</div>
|
||||
<div class="user-balance">
|
||||
<el-icon><Wallet /></el-icon>
|
||||
余额: ¥{{ balance }}
|
||||
</div>
|
||||
</div>
|
||||
<el-divider /> <!-- 分割线 -->
|
||||
|
||||
<!-- 保留的功能选项 -->
|
||||
<el-dropdown-item @click="goToRecharge">账户充值</el-dropdown-item>
|
||||
<el-dropdown-item @click="goToRecords">借阅记录</el-dropdown-item>
|
||||
<el-dropdown-item divided @click="logout">退出登录</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
<div v-else>
|
||||
<el-button type="text" @click="goToLogin">登录</el-button>
|
||||
<el-button type="text" @click="goToRegister">注册</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-header>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useStore } from 'vuex'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { Reading, ArrowDown } from '@element-plus/icons-vue'
|
||||
import { Wallet } from '@element-plus/icons-vue'
|
||||
import { computed, onMounted, watch } from 'vue'
|
||||
|
||||
|
||||
const store = useStore()
|
||||
const router = useRouter()
|
||||
|
||||
onMounted(async () => {
|
||||
// 等待会话初始化完成
|
||||
if (store.state.sessionInitialized && store.getters.isAuthenticated) {
|
||||
await fetchUserInfo()
|
||||
}
|
||||
})
|
||||
|
||||
// 监听会话状态变化
|
||||
watch(() => store.state.sessionInitialized, async (newVal) => {
|
||||
if (newVal && store.getters.isAuthenticated) {
|
||||
await fetchUserInfo()
|
||||
}
|
||||
})
|
||||
|
||||
// 添加获取用户信息的方法
|
||||
async function fetchUserInfo() {
|
||||
try {
|
||||
// 获取余额和VIP信息
|
||||
await store.dispatch('fetchBalanceAndVip')
|
||||
} catch (error) {
|
||||
console.error('获取用户信息失败:', error)
|
||||
// 避免在未登录状态下显示错误信息
|
||||
if (store.getters.isAuthenticated) {
|
||||
ElMessage.error('获取用户信息失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const vip = computed(() => store.state.vipLevel)
|
||||
const balance = computed(() => store.state.balance)
|
||||
const user = computed(() => store.state.user)
|
||||
console.log("user.pic:"+user.pic)
|
||||
|
||||
|
||||
|
||||
const activeIndex = computed(() => {
|
||||
const routeName = router.currentRoute.value.name
|
||||
if (routeName === 'Home') return 'home'
|
||||
if (routeName === 'Books' || routeName === 'BookDetail' || routeName === 'AddBook' || routeName === 'EditBook') return 'books'
|
||||
if (routeName === 'BorrowBook') return 'borrow'
|
||||
if (routeName === 'ReturnBook') return 'return'
|
||||
if (routeName === 'WeeklyRank') return 'weekly'
|
||||
if (routeName === 'MonthlyRank') return 'monthly'
|
||||
if (routeName === 'BookManagement') return 'bookManagement'
|
||||
if (routeName === 'AllBorrowRecords') return 'allBorrowRecords'
|
||||
return ''
|
||||
})
|
||||
|
||||
|
||||
|
||||
const handleSelect = (index) => {
|
||||
switch(index) {
|
||||
case 'home':
|
||||
router.push('/')
|
||||
break
|
||||
case 'books':
|
||||
router.push('/books')
|
||||
break
|
||||
case 'borrow':
|
||||
router.push('/borrow')
|
||||
break
|
||||
case 'return':
|
||||
router.push('/return')
|
||||
break
|
||||
case 'weekly':
|
||||
router.push('/ranking/weekly')
|
||||
break
|
||||
case 'monthly':
|
||||
router.push('/ranking/monthly')
|
||||
break
|
||||
case 'bookManagement':
|
||||
router.push('/admin/books')
|
||||
break
|
||||
case 'allBorrowRecords':
|
||||
router.push('/admin/borrow-records')
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
const goToLogin = () => {
|
||||
router.push('/login')
|
||||
}
|
||||
|
||||
const goToRegister = () => {
|
||||
router.push('/register')
|
||||
}
|
||||
|
||||
const goToRecharge = () => {
|
||||
router.push('/recharge')
|
||||
}
|
||||
|
||||
const goToRecords = () => {
|
||||
router.push('/borrow-records')
|
||||
}
|
||||
|
||||
|
||||
const logout = () => {
|
||||
store.dispatch('logout')
|
||||
router.push('/login')
|
||||
ElMessage.success('退出登录成功')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0 20px;
|
||||
height: 60px;
|
||||
background-color: #545c64;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.nav {
|
||||
flex: 1;
|
||||
margin: 0 20px;
|
||||
}
|
||||
|
||||
.user-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.username {
|
||||
margin: 0 5px;
|
||||
}
|
||||
|
||||
.el-dropdown-link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.user-dropdown-info {
|
||||
padding: 5px 16px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.user-name {
|
||||
font-weight: bold;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.user-vip {
|
||||
color: #ff9a2e;
|
||||
font-size: 14px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.user-balance {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 2px;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,25 @@
|
||||
import { createApp } from 'vue'
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
import store from './store'
|
||||
import ElementPlus from 'element-plus'
|
||||
import 'element-plus/dist/index.css'
|
||||
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
|
||||
|
||||
const app = createApp(App)
|
||||
|
||||
// 注册所有图标
|
||||
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
|
||||
app.component(key, component)
|
||||
}
|
||||
|
||||
app.use(ElementPlus)
|
||||
app.use(store)
|
||||
app.use(router)
|
||||
|
||||
// 初始化会话
|
||||
store.dispatch('initSession')
|
||||
|
||||
app.mount('#app')
|
||||
|
||||
window.store = store
|
@ -0,0 +1,131 @@
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
import store from '../store'
|
||||
|
||||
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: '/',
|
||||
name: 'Home',
|
||||
component: () => import('../views/Home.vue'),
|
||||
meta: { requiresAuth: false }
|
||||
},
|
||||
{
|
||||
path: '/login',
|
||||
name: 'Login',
|
||||
component: () => import('../views/Auth/Login.vue')
|
||||
},
|
||||
{
|
||||
path: '/register',
|
||||
name: 'Register',
|
||||
component: () => import('../views/Auth/Register.vue')
|
||||
},
|
||||
|
||||
{
|
||||
path: '/books',
|
||||
name: 'Books',
|
||||
component: () => import('../views/Books/BookList.vue'),
|
||||
meta: { requiresAuth: true }
|
||||
},
|
||||
{
|
||||
path: '/books/:id',
|
||||
name: 'BookDetail',
|
||||
component: () => import('../views/Books/BookDetail.vue'),
|
||||
meta: { requiresAuth: true }
|
||||
},
|
||||
{
|
||||
path: '/books/add',
|
||||
name: 'AddBook',
|
||||
component: () => import('../views/Books/AddBook.vue'),
|
||||
meta: { requiresAuth: true, requiresAdmin: true }
|
||||
},
|
||||
// {
|
||||
// path: '/books/edit/:id',
|
||||
// name: 'EditBook',
|
||||
// component: () => import('../views/Books/EditBook.vue'),
|
||||
// meta: { requiresAuth: true, requiresAdmin: true }
|
||||
// },
|
||||
{
|
||||
path: '/borrow',
|
||||
name: 'BorrowBook',
|
||||
component: () => import('../views/Borrow/BorrowBook.vue'),
|
||||
meta: { requiresAuth: true }
|
||||
},
|
||||
{
|
||||
path: '/return',
|
||||
name: 'ReturnBook',
|
||||
component: () => import('../views/Borrow/ReturnBook.vue'),
|
||||
meta: { requiresAuth: true }
|
||||
},
|
||||
{
|
||||
path: '/borrow-records',
|
||||
name: 'BorrowRecords',
|
||||
component: () => import('../views/User/BorrowRecords.vue'),
|
||||
meta: { requiresAuth: true }
|
||||
},
|
||||
{
|
||||
path: '/recharge',
|
||||
name: 'Recharge',
|
||||
component: () => import('../views/User/Recharge.vue'),
|
||||
meta: { requiresAuth: true }
|
||||
},
|
||||
{
|
||||
path: '/ranking/weekly',
|
||||
name: 'WeeklyRank',
|
||||
component: () => import('../views/Ranking/WeeklyRank.vue'),
|
||||
meta: { requiresAuth: true }
|
||||
},
|
||||
{
|
||||
path: '/ranking/monthly',
|
||||
name: 'MonthlyRank',
|
||||
component: () => import('../views/Ranking/MonthlyRank.vue'),
|
||||
meta: { requiresAuth: true }
|
||||
},
|
||||
{
|
||||
path: '/admin/borrow-records',
|
||||
name: 'AllBorrowRecords',
|
||||
component: () => import('../views/Admin/AllBorrowRecords.vue'),
|
||||
meta: { requiresAdmin: true }
|
||||
},
|
||||
{
|
||||
path: '/admin/books',
|
||||
name: 'BookManagement',
|
||||
component: () => import('../views/Admin/BookManagement.vue'),
|
||||
meta: { requiresAdmin: true }
|
||||
}
|
||||
]
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(import.meta.env.BASE_URL),
|
||||
routes
|
||||
})
|
||||
|
||||
// 路由守卫
|
||||
router.beforeEach((to, from, next) => {
|
||||
// 等待会话初始化完成
|
||||
if (!store.state.sessionInitialized) {
|
||||
return next()
|
||||
}
|
||||
|
||||
const isAuthenticated = store.getters.isAuthenticated
|
||||
const isAdmin = store.getters.isAdmin
|
||||
|
||||
// 如果路由需要认证但用户未登录,跳转到登录页
|
||||
if (to.meta.requiresAuth && !isAuthenticated) {
|
||||
return next('/login')
|
||||
}
|
||||
|
||||
// 如果路由需要管理员权限但用户不是管理员
|
||||
if (to.meta.requiresAdmin && !isAdmin) {
|
||||
return next({ name: 'Home' })
|
||||
}
|
||||
|
||||
// 如果用户已登录但访问登录/注册页,跳转到首页
|
||||
if ((to.name === 'Login' || to.name === 'Register') && isAuthenticated) {
|
||||
return next({ name: 'Home' })
|
||||
}
|
||||
|
||||
next()
|
||||
})
|
||||
|
||||
export default router
|
@ -0,0 +1,16 @@
|
||||
export function formatDate(dateString) {
|
||||
if (!dateString) return '未知时间'
|
||||
|
||||
const date = new Date(dateString)
|
||||
|
||||
// 处理无效日期
|
||||
if (isNaN(date.getTime())) return dateString
|
||||
|
||||
const year = date.getFullYear()
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
const hours = String(date.getHours()).padStart(2, '0')
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0')
|
||||
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}`
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
import axios from 'axios'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import store from '../store/index'
|
||||
import router from '../router/index'
|
||||
|
||||
// 创建axios实例
|
||||
const service = axios.create({
|
||||
baseURL: 'http://localhost:8877',
|
||||
timeout: 10000,
|
||||
withCredentials: true // 允许携带cookie
|
||||
})
|
||||
|
||||
// 请求拦截器
|
||||
service.interceptors.request.use(
|
||||
config => {
|
||||
|
||||
return config
|
||||
},
|
||||
error => {
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
|
||||
// 响应拦截器
|
||||
service.interceptors.response.use(
|
||||
response => {
|
||||
// 处理成功响应
|
||||
const res = response.data
|
||||
|
||||
// 处理业务错误 (code !== 200)
|
||||
if (res && typeof res === 'object' && res.code !== undefined && res.code !== 200) {
|
||||
// 检查是否为静默请求
|
||||
if (!response.config.silent) {
|
||||
ElMessage.error(res.message || '请求失败')
|
||||
}
|
||||
return Promise.reject(new Error(res.message || 'Error'))
|
||||
}
|
||||
|
||||
// 返回整个响应对象,确保组件可以访问响应头等信息
|
||||
return response
|
||||
},
|
||||
error => {
|
||||
// 处理HTTP错误
|
||||
if (error.response) {
|
||||
switch (error.response.status) {
|
||||
case 401:
|
||||
// 只有在非静默请求时才显示错误信息
|
||||
if (!error.config?.silent) {
|
||||
store.dispatch('logout')
|
||||
router.push('/login')
|
||||
ElMessage.error('请先登录')
|
||||
}
|
||||
break
|
||||
case 403:
|
||||
if (!error.config?.silent) {
|
||||
ElMessage.error('没有操作权限')
|
||||
}
|
||||
break
|
||||
default:
|
||||
if (!error.config?.silent) {
|
||||
ElMessage.error(error.response.data?.message || '请求失败')
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!error.config?.silent) {
|
||||
ElMessage.error('网络错误,请检查连接')
|
||||
}
|
||||
}
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
|
||||
export default service
|
@ -0,0 +1,43 @@
|
||||
// 安全地解析JSON字符串
|
||||
export function safeParseJSON(str, defaultValue = null) {
|
||||
if (!str) return defaultValue
|
||||
try {
|
||||
return JSON.parse(str)
|
||||
} catch (error) {
|
||||
console.error('JSON解析失败:', error)
|
||||
return defaultValue
|
||||
}
|
||||
}
|
||||
|
||||
// 安全地存储数据到sessionStorage
|
||||
export function safeSetItem(key, value) {
|
||||
try {
|
||||
sessionStorage.setItem(key, JSON.stringify(value))
|
||||
return true
|
||||
} catch (error) {
|
||||
console.error('存储数据失败:', error)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// 安全地从sessionStorage获取数据
|
||||
export function safeGetItem(key, defaultValue = null) {
|
||||
try {
|
||||
const item = sessionStorage.getItem(key)
|
||||
return item ? JSON.parse(item) : defaultValue
|
||||
} catch (error) {
|
||||
console.error('获取数据失败:', error)
|
||||
return defaultValue
|
||||
}
|
||||
}
|
||||
|
||||
// 安全地从sessionStorage删除数据
|
||||
export function safeRemoveItem(key) {
|
||||
try {
|
||||
sessionStorage.removeItem(key)
|
||||
return true
|
||||
} catch (error) {
|
||||
console.error('删除数据失败:', error)
|
||||
return false
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue