feat:student-vol
24
student-vol/.gitignore
vendored
Normal file
@@ -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?
|
||||
3
student-vol/.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"recommendations": ["Vue.volar"]
|
||||
}
|
||||
5
student-vol/README.md
Normal file
@@ -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).
|
||||
13
student-vol/index.html
Normal file
@@ -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>student-vol</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
18
student-vol/mock/actDetails.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"error": 0,
|
||||
"data": {
|
||||
"details": {
|
||||
"id": 1,
|
||||
"title": "运动会志愿者招募",
|
||||
"startTime": 1671494400000,
|
||||
"endTime": 1671697800000,
|
||||
"palce": "福州大学至诚学院",
|
||||
"content": "为确保运动会顺利进行,现招募志愿者协助现场秩序维护、运动员服务等工作。",
|
||||
"publisher": "福州大学至诚学院团委",
|
||||
"hour": 4,
|
||||
"total": 50,
|
||||
"canApply": true,
|
||||
"applyStatus": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
40
student-vol/mock/actList.json
Normal file
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"error": 0,
|
||||
"data": {
|
||||
"current": 1,
|
||||
"pageSize": 10,
|
||||
"pageCount": 1,
|
||||
"list": [
|
||||
{
|
||||
"id": 1,
|
||||
"title": "运动会志愿者招募",
|
||||
"startTime": 1671494400000,
|
||||
"endTime": 1671697800000,
|
||||
"palce": "福州大学至诚学院",
|
||||
"isYourSchool": true,
|
||||
"canApply": true,
|
||||
"actPic": "./imgs/actImg.jpeg"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"title": "图书馆志愿服务",
|
||||
"startTime": 1670284800000,
|
||||
"endTime": 1670371200000,
|
||||
"palce": "福州市图书馆",
|
||||
"isYourSchool": false,
|
||||
"canApply": false,
|
||||
"actPic": "./imgs/actImg.jpeg"
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"title": "社区敬老院服务",
|
||||
"startTime": 1672012800000,
|
||||
"endTime": 1672099200000,
|
||||
"palce": "福州市养老院",
|
||||
"isYourSchool": false,
|
||||
"canApply": true,
|
||||
"actPic": "./imgs/actImg.jpeg"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
23
student-vol/mock/durationList.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"error": 0,
|
||||
"data": {
|
||||
"list": [
|
||||
{
|
||||
"value": 2,
|
||||
"text": "2小时"
|
||||
},
|
||||
{
|
||||
"value": 4,
|
||||
"text": "4小时"
|
||||
},
|
||||
{
|
||||
"value": 6,
|
||||
"text": "6小时"
|
||||
},
|
||||
{
|
||||
"value": 8,
|
||||
"text": "8小时"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
32
student-vol/mock/myApplyList.json
Normal file
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"error": 0,
|
||||
"data": {
|
||||
"current": 1,
|
||||
"pageSize": 10,
|
||||
"pageCount": 1,
|
||||
"list": [
|
||||
{
|
||||
"id": 1,
|
||||
"title": "运动会志愿者招募",
|
||||
"startTime": 1671494400000,
|
||||
"endTime": 1671697800000,
|
||||
"palce": "福州大学至诚学院",
|
||||
"isYourSchool": true,
|
||||
"canApply": true,
|
||||
"actPic": "./imgs/actImg.jpeg",
|
||||
"applyStatus": 1
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"title": "图书馆志愿服务",
|
||||
"startTime": 1670284800000,
|
||||
"endTime": 1670371200000,
|
||||
"palce": "福州市图书馆",
|
||||
"isYourSchool": false,
|
||||
"canApply": false,
|
||||
"actPic": "./imgs/actImg.jpeg",
|
||||
"applyStatus": 2
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
19
student-vol/mock/publishers.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"error": 0,
|
||||
"data": {
|
||||
"list": [
|
||||
{
|
||||
"value": 1,
|
||||
"text": "福州大学至诚学院团委"
|
||||
},
|
||||
{
|
||||
"value": 2,
|
||||
"text": "福州市志愿者协会"
|
||||
},
|
||||
{
|
||||
"value": 3,
|
||||
"text": "鼓楼区社区服务中心"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
37
student-vol/mock/rank.json
Normal file
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"error": 0,
|
||||
"data": {
|
||||
"list": [
|
||||
{
|
||||
"id": 1,
|
||||
"avatar": "./imgs/avatar.png",
|
||||
"name": "张三",
|
||||
"score": 256
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"avatar": "./imgs/avatar.png",
|
||||
"name": "李四",
|
||||
"score": 238
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"avatar": "./imgs/avatar.png",
|
||||
"name": "王五",
|
||||
"score": 215
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"avatar": "./imgs/avatar.png",
|
||||
"name": "赵六",
|
||||
"score": 198
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"avatar": "./imgs/avatar.png",
|
||||
"name": "孙七",
|
||||
"score": 176
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
17
student-vol/mock/recordDetails.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"error": 0,
|
||||
"data": {
|
||||
"details": {
|
||||
"actTime": 1671494400000,
|
||||
"place": "福州大学至诚学院",
|
||||
"content": "参与运动会志愿服务,协助现场秩序维护、运动员服务等工作。",
|
||||
"imgList": [
|
||||
"./imgs/actImg.jpeg",
|
||||
"./imgs/actImg.jpeg"
|
||||
],
|
||||
"hour": 4,
|
||||
"uploadTime": 1671580800000,
|
||||
"status": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
8
student-vol/mock/scoreOverview.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"error": 0,
|
||||
"data": {
|
||||
"provinceRank": 156,
|
||||
"totalScore": 128,
|
||||
"grandeRank": 8
|
||||
}
|
||||
}
|
||||
34
student-vol/mock/serviceList.json
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"error": 0,
|
||||
"data": {
|
||||
"current": 1,
|
||||
"pageSize": 10,
|
||||
"pageCount": 1,
|
||||
"list": [
|
||||
{
|
||||
"id": 1,
|
||||
"pic": "./imgs/actImg.jpeg",
|
||||
"content": "参与运动会志愿服务,协助现场秩序维护",
|
||||
"publisher": "福州大学至诚学院",
|
||||
"time": 1671494400000,
|
||||
"status": 1
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"pic": "./imgs/actImg.jpeg",
|
||||
"content": "图书馆整理图书志愿服务",
|
||||
"publisher": "福州市图书馆",
|
||||
"time": 1670284800000,
|
||||
"status": 0
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"pic": "./imgs/actImg.jpeg",
|
||||
"content": "社区敬老院陪伴老人服务",
|
||||
"publisher": "鼓楼区养老院",
|
||||
"time": 1672012800000,
|
||||
"status": 2
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
4
student-vol/mock/success.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"error": 0,
|
||||
"msg": "success"
|
||||
}
|
||||
14
student-vol/mock/userInfo.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"error": 0,
|
||||
"data": {
|
||||
"avatar": "./imgs/avatar.png",
|
||||
"name": "李华",
|
||||
"code": 12343434,
|
||||
"gender": 0,
|
||||
"phoneNum": "18787698789",
|
||||
"school": "福州大学至诚学院",
|
||||
"profession": "软件工程",
|
||||
"userType": 0,
|
||||
"totalScore": 128
|
||||
}
|
||||
}
|
||||
8
student-vol/mock/userScore.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"error": 0,
|
||||
"data": {
|
||||
"avatar": "./imgs/avatar.png",
|
||||
"name": "李华",
|
||||
"totalScore": 128
|
||||
}
|
||||
}
|
||||
19
student-vol/mock/yearList.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"error": 0,
|
||||
"data": {
|
||||
"list": [
|
||||
{
|
||||
"value": 2023,
|
||||
"text": "2023年"
|
||||
},
|
||||
{
|
||||
"value": 2024,
|
||||
"text": "2024年"
|
||||
},
|
||||
{
|
||||
"value": 2025,
|
||||
"text": "2025年"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
8
student-vol/mock/yearScore.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"error": 0,
|
||||
"data": {
|
||||
"times": 12,
|
||||
"duration": 48,
|
||||
"score": 96
|
||||
}
|
||||
}
|
||||
1634
student-vol/package-lock.json
generated
Normal file
22
student-vol/package.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "student-vol",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.13.2",
|
||||
"moment": "^2.30.1",
|
||||
"vant": "^4.9.22",
|
||||
"vue": "^3.5.24",
|
||||
"vue-router": "^4.6.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^6.0.1",
|
||||
"vite": "^7.2.4"
|
||||
}
|
||||
}
|
||||
BIN
student-vol/public/imgs/actImg.jpeg
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
student-vol/public/imgs/avatar.png
Normal file
|
After Width: | Height: | Size: 7.4 KiB |
1
student-vol/public/vite.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
27
student-vol/src/App.vue
Normal file
@@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<router-view/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "App",
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.container {
|
||||
min-height: 100vh;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
</style>
|
||||
BIN
student-vol/src/assets/p1.png
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
BIN
student-vol/src/assets/p2.png
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
BIN
student-vol/src/assets/p3.png
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
BIN
student-vol/src/assets/p5.png
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
1
student-vol/src/assets/vue.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 496 B |
90
student-vol/src/components/Home/ActItem.vue
Normal file
@@ -0,0 +1,90 @@
|
||||
<template>
|
||||
<div class="actItem" @click="detailsClick(data.id)">
|
||||
<div class="actItem-pic">
|
||||
<img :src="data.actPic"/>
|
||||
<span class="actItemStatus" :style="getTagStyle(data)">
|
||||
{{ getTagTxt(data) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="actItem-body">
|
||||
<p class="actItem-title">{{ data.title }}</p>
|
||||
<p>{{ formatDate(data.startTime) }} 至 {{ formatDate(data.endTime) }}</p>
|
||||
<p class="actItem-tag">
|
||||
{{ data.isYourSchool ? "本人所在学校活动" : data.palce }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import moment from "moment";
|
||||
|
||||
export default {
|
||||
props: ["data"],
|
||||
methods: {
|
||||
getTagStyle(data) {
|
||||
return { backgroundColor: data.canApply ? "#07c160" : "#999" };
|
||||
},
|
||||
formatDate(value) {
|
||||
return value ? moment(value).format("YYYY-MM-DD HH:mm") : "";
|
||||
},
|
||||
detailsClick(id) {
|
||||
this.$emit("goDetails", id);
|
||||
},
|
||||
getTagTxt(data) {
|
||||
return data.canApply ? "进行中" : "已结束";
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.actItem {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
padding: 15px;
|
||||
background: white;
|
||||
margin-bottom: 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.actItem-pic {
|
||||
position: relative;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.actItem-pic img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.actItemStatus {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
right: 5px;
|
||||
padding: 2px 8px;
|
||||
color: white;
|
||||
font-size: 12px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.actItem-body {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.actItem-title {
|
||||
font-weight: bold;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.actItem-tag {
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
||||
60
student-vol/src/components/Home/Navs.vue
Normal file
@@ -0,0 +1,60 @@
|
||||
<template>
|
||||
<ul class="navs">
|
||||
<li>
|
||||
<router-link to="/loveActs">
|
||||
<img src="../../assets/p1.png" />
|
||||
<p>认领活动</p>
|
||||
</router-link>
|
||||
</li>
|
||||
<li>
|
||||
<router-link to="/loveReport">
|
||||
<img src="../../assets/p2.png" />
|
||||
<p>爱心报表</p>
|
||||
</router-link>
|
||||
</li>
|
||||
<li>
|
||||
<router-link to="/UploadService">
|
||||
<img src="../../assets/p3.png" />
|
||||
<p>服务纪实</p>
|
||||
</router-link>
|
||||
</li>
|
||||
<li>
|
||||
<router-link to="/serviceRecords">
|
||||
<img src="../../assets/p5.png" />
|
||||
<p>我的服务</p>
|
||||
</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "Navs",
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.navs {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
padding: 20px;
|
||||
background: white;
|
||||
margin: 10px 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.navs li {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.navs img {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.navs a {
|
||||
text-decoration: none;
|
||||
color: #333;
|
||||
}
|
||||
</style>
|
||||
50
student-vol/src/components/Home/User.vue
Normal file
@@ -0,0 +1,50 @@
|
||||
<template>
|
||||
<div class="header-inner">
|
||||
<div class="userInfo">
|
||||
<span class="avatar">
|
||||
<img :src="user.avatar" />
|
||||
</span>
|
||||
<span>{{ user.name }}</span>
|
||||
</div>
|
||||
<router-link to="/userCenter">
|
||||
<button class="rArrow"></button>
|
||||
</router-link>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ["user"],
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.header-inner {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 15px;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.userInfo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.avatar img {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.rArrow {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: none;
|
||||
background: url('../assets/rArrow.png') no-repeat center;
|
||||
background-size: contain;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
86
student-vol/src/components/ServiceRecords/Overview.vue
Normal file
@@ -0,0 +1,86 @@
|
||||
<template>
|
||||
<ul class="overview">
|
||||
<li>
|
||||
<p class="num">
|
||||
<span class="highColor">{{ data.times }}</span>次
|
||||
</p>
|
||||
<span class="label">服务次数</span>
|
||||
</li>
|
||||
<li>
|
||||
<p class="num">
|
||||
<span class="highColor">{{ data.duration }}</span>小时
|
||||
</p>
|
||||
<span class="label">服务时长</span>
|
||||
</li>
|
||||
<li>
|
||||
<p class="num">
|
||||
<span class="highColor">{{ data.score }}</span>分
|
||||
</p>
|
||||
<span class="label">服务积分</span>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from "axios";
|
||||
|
||||
export default {
|
||||
props: ["year"],
|
||||
data() {
|
||||
return {
|
||||
data: {},
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
fetchYearScore() {
|
||||
const that = this;
|
||||
const year = this.year;
|
||||
axios
|
||||
.get("/api/service/yearScore", {
|
||||
params: { year },
|
||||
})
|
||||
.then(function (response) {
|
||||
const { error, data = {} } = response.data;
|
||||
if (error === 0) {
|
||||
that.data = data || {};
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
year() {
|
||||
this.fetchYearScore();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.overview {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
padding: 20px;
|
||||
background: white;
|
||||
list-style: none;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.overview li {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.num {
|
||||
font-size: 20px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.highColor {
|
||||
color: #ff5d23;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.label {
|
||||
color: #666;
|
||||
font-size: 12px;
|
||||
}
|
||||
</style>
|
||||
93
student-vol/src/components/ServiceRecords/RecordItem.vue
Normal file
@@ -0,0 +1,93 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<div class="details" @click="detailsClick">
|
||||
<div class="imgBox">
|
||||
<img :src="data.pic" />
|
||||
<p class="status" :style="{ backgroundColor: bgColorMap[data.status] }">
|
||||
{{ statusMap[data.status] }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="main">
|
||||
<p class="content">{{ data.content }}</p>
|
||||
<p class="bottomTxt">
|
||||
<span style="margin-right: 10px">{{ data.publisher }}</span>
|
||||
<span>{{ formatDate(data.time) }}</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import moment from "moment";
|
||||
|
||||
export default {
|
||||
name: "recordItem",
|
||||
props: ["data"],
|
||||
data() {
|
||||
return {
|
||||
statusMap: ["审核中", "审核通过", "审核驳回"],
|
||||
bgColorMap: ["#1989fa", "#07c160", "#f6352c"],
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
formatDate(value) {
|
||||
return value ? moment(value).format("YYYY-MM-DD HH:mm") : "--";
|
||||
},
|
||||
detailsClick() {
|
||||
this.$emit("goDetails", this.data.id);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.details {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
padding: 15px;
|
||||
background: white;
|
||||
margin-bottom: 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.imgBox {
|
||||
position: relative;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.imgBox img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.status {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
right: 5px;
|
||||
padding: 2px 8px;
|
||||
color: white;
|
||||
font-size: 12px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.main {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.content {
|
||||
font-size: 15px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.bottomTxt {
|
||||
color: #999;
|
||||
font-size: 12px;
|
||||
}
|
||||
</style>
|
||||
98
student-vol/src/components/ServiceRecords/User.vue
Normal file
@@ -0,0 +1,98 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<div class="user">
|
||||
<img class="user-avatar" :src="userData.avatar" />
|
||||
<div>
|
||||
<p class="user-name">{{ userData.name }}</p>
|
||||
<span class="user-score">{{ userData.totalScore }}积分</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<van-button
|
||||
round
|
||||
hairline
|
||||
size="mini"
|
||||
icon="arrow-down"
|
||||
color="#ff5d23"
|
||||
@click="showPicker = true"
|
||||
>
|
||||
{{ date.text }}
|
||||
</van-button>
|
||||
|
||||
<van-popup v-model:show="showPicker" round position="bottom">
|
||||
<van-picker
|
||||
:columns="dateList"
|
||||
@cancel="cancel"
|
||||
@confirm="confirm"
|
||||
/>
|
||||
</van-popup>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from "axios";
|
||||
|
||||
export default {
|
||||
props: ["dateList", "date"],
|
||||
data() {
|
||||
return {
|
||||
showPicker: false,
|
||||
userData: {},
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
fetchUserData() {
|
||||
const that = this;
|
||||
axios.get("/api/service/userScore").then(function (response) {
|
||||
const { error, data = {} } = response.data;
|
||||
if (error === 0) {
|
||||
that.userData = data || {};
|
||||
}
|
||||
});
|
||||
},
|
||||
cancel() {
|
||||
this.showPicker = false;
|
||||
},
|
||||
confirm({ selectedOptions }) {
|
||||
const data = selectedOptions[0];
|
||||
this.showPicker = false;
|
||||
this.$emit("updateYear", data);
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.fetchUserData();
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.user {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.user-avatar {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.user-name {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.user-score {
|
||||
color: #ff5d23;
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
||||
32
student-vol/src/main.js
Normal file
@@ -0,0 +1,32 @@
|
||||
import { createApp } from "vue";
|
||||
import "./style.css";
|
||||
import App from "./App.vue";
|
||||
import router from "./router";
|
||||
import {
|
||||
Button,
|
||||
List,
|
||||
Tag,
|
||||
Field,
|
||||
CellGroup,
|
||||
Uploader,
|
||||
Popup,
|
||||
Picker,
|
||||
DatePicker,
|
||||
} from "vant";
|
||||
import "vant/lib/index.css";
|
||||
|
||||
const app = createApp(App);
|
||||
|
||||
app
|
||||
.use(Button)
|
||||
.use(List)
|
||||
.use(Tag)
|
||||
.use(Field)
|
||||
.use(CellGroup)
|
||||
.use(Uploader)
|
||||
.use(Popup)
|
||||
.use(Picker)
|
||||
.use(DatePicker)
|
||||
.use(router);
|
||||
|
||||
app.mount("#app");
|
||||
65
student-vol/src/router/index.js
Normal file
@@ -0,0 +1,65 @@
|
||||
import { createRouter, createWebHistory } from "vue-router";
|
||||
import HomeView from "../views/HomeView.vue";
|
||||
import UserCenter from "../views/UserCenter.vue";
|
||||
import LoveReport from "../views/LoveReport.vue";
|
||||
import LoveActs from "../views/LoveActs.vue";
|
||||
import ActDetails from "../views/ActDetails.vue";
|
||||
import MyApply from "../views/MyApply.vue";
|
||||
import UploadService from "../views/UploadService.vue";
|
||||
import ServiceRecords from "../views/ServiceRecords.vue";
|
||||
import ServiceDetail from "../views/ServiceDetail.vue";
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: "/",
|
||||
name: "home",
|
||||
component: HomeView,
|
||||
},
|
||||
{
|
||||
path: "/userCenter",
|
||||
name: "userCenter",
|
||||
component: UserCenter,
|
||||
},
|
||||
{
|
||||
path: "/loveReport",
|
||||
name: "loveReport",
|
||||
component: LoveReport,
|
||||
},
|
||||
{
|
||||
path: "/loveActs",
|
||||
name: "loveActs",
|
||||
component: LoveActs,
|
||||
},
|
||||
{
|
||||
path: "/actDetails",
|
||||
name: "actDetails",
|
||||
component: ActDetails,
|
||||
},
|
||||
{
|
||||
path: "/myApply",
|
||||
name: "myApply",
|
||||
component: MyApply,
|
||||
},
|
||||
{
|
||||
path: "/UploadService",
|
||||
name: "UploadService",
|
||||
component: UploadService,
|
||||
},
|
||||
{
|
||||
path: "/serviceRecords",
|
||||
name: "serviceRecords",
|
||||
component: ServiceRecords,
|
||||
},
|
||||
{
|
||||
path: "/serviceDetail",
|
||||
name: "serviceDetail",
|
||||
component: ServiceDetail,
|
||||
},
|
||||
];
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
routes,
|
||||
});
|
||||
|
||||
export default router;
|
||||
79
student-vol/src/style.css
Normal file
@@ -0,0 +1,79 @@
|
||||
:root {
|
||||
font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
|
||||
line-height: 1.5;
|
||||
font-weight: 400;
|
||||
|
||||
color-scheme: light dark;
|
||||
color: rgba(255, 255, 255, 0.87);
|
||||
background-color: #242424;
|
||||
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
a {
|
||||
font-weight: 500;
|
||||
color: #646cff;
|
||||
text-decoration: inherit;
|
||||
}
|
||||
a:hover {
|
||||
color: #535bf2;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
display: flex;
|
||||
place-items: center;
|
||||
min-width: 320px;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 3.2em;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
button {
|
||||
border-radius: 8px;
|
||||
border: 1px solid transparent;
|
||||
padding: 0.6em 1.2em;
|
||||
font-size: 1em;
|
||||
font-weight: 500;
|
||||
font-family: inherit;
|
||||
background-color: #1a1a1a;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.25s;
|
||||
}
|
||||
button:hover {
|
||||
border-color: #646cff;
|
||||
}
|
||||
button:focus,
|
||||
button:focus-visible {
|
||||
outline: 4px auto -webkit-focus-ring-color;
|
||||
}
|
||||
|
||||
.card {
|
||||
padding: 2em;
|
||||
}
|
||||
|
||||
#app {
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root {
|
||||
color: #213547;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
a:hover {
|
||||
color: #747bff;
|
||||
}
|
||||
button {
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
}
|
||||
165
student-vol/src/views/ActDetails.vue
Normal file
@@ -0,0 +1,165 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<div class="head">
|
||||
<p class="title">{{ data.title }}</p>
|
||||
<div class="status">
|
||||
<van-tag :color="getActStatusColor(data)" round style="margin-right: 10px">
|
||||
{{ data.canApply ? '进行中' : '已结束' }}
|
||||
</van-tag>
|
||||
<van-tag :color="getActApplyColor(data)" round>
|
||||
{{ applyStatusMap[data.applyStatus] }}
|
||||
</van-tag>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dataItem">
|
||||
<span class="label">活动时间</span>
|
||||
<p>{{ formatDate(data.startTime) }} - {{ formatDate(data.endTime) }}</p>
|
||||
</div>
|
||||
|
||||
<div class="dataItem">
|
||||
<span class="label">活动地点</span>
|
||||
<p>{{ data.palce }}</p>
|
||||
</div>
|
||||
|
||||
<div class="dataItem">
|
||||
<span class="label">活动简介</span>
|
||||
<p>{{ data.content }}</p>
|
||||
</div>
|
||||
|
||||
<div class="dataItem">
|
||||
<span class="label">活动来源</span>
|
||||
<p>{{ data.publisher }}</p>
|
||||
</div>
|
||||
|
||||
<div class="dataItem">
|
||||
<span class="label">服务时长</span>
|
||||
<p>{{ data.hour }}小时</p>
|
||||
</div>
|
||||
|
||||
<div class="dataItem">
|
||||
<span class="label">招募人数</span>
|
||||
<p>{{ data.total }}人</p>
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="fBtn"
|
||||
v-if="[2, 3].indexOf(data.applyStatus) === -1"
|
||||
@click="applyClick"
|
||||
>
|
||||
{{ data.applyStatus === 1 ? '撤销申请' : '立即报名' }}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from "axios";
|
||||
import moment from "moment";
|
||||
import { showSuccessToast, showFailToast } from "vant";
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
data: {},
|
||||
applyStatusMap: ["", "审核中", "审核通过", "审核拒绝"],
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
formatDate(value) {
|
||||
return value ? moment(value).format("YYYY-MM-DD HH:mm") : "--";
|
||||
},
|
||||
getActStatusColor(data) {
|
||||
return data.canApply ? "#07C160" : "#ff976a";
|
||||
},
|
||||
getActApplyColor(data) {
|
||||
const list = ["", "#1989fa", "#07c160", "#f6352c"];
|
||||
return list[data.applyStatus];
|
||||
},
|
||||
fetchDetails() {
|
||||
const that = this;
|
||||
const payload = { id: this.$route.query.id };
|
||||
axios
|
||||
.get("/api/actDetails/details", {
|
||||
params: payload,
|
||||
})
|
||||
.then(function (response) {
|
||||
const { error, data = {} } = response.data;
|
||||
if (error === 0) {
|
||||
that.data = data.details || {};
|
||||
}
|
||||
});
|
||||
},
|
||||
applyClick() {
|
||||
const that = this;
|
||||
const payload = {
|
||||
isApplay: this.data.applyStatus !== 1,
|
||||
id: this.$route.query.id,
|
||||
};
|
||||
|
||||
axios.post("/api/actDetails/apply", payload).then(function (response) {
|
||||
const { error, msg } = response.data;
|
||||
if (error === 0) {
|
||||
showSuccessToast("操作成功");
|
||||
that.fetchDetails();
|
||||
} else {
|
||||
showFailToast(msg || "网络错误,请稍后重试");
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.fetchDetails();
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.head {
|
||||
background: white;
|
||||
padding: 20px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.status {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.dataItem {
|
||||
background: white;
|
||||
padding: 15px 20px;
|
||||
margin-bottom: 1px;
|
||||
}
|
||||
|
||||
.label {
|
||||
display: block;
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.dataItem p {
|
||||
color: #333;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.fBtn {
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
left: 20px;
|
||||
right: 20px;
|
||||
padding: 15px;
|
||||
background: #ff5d23;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
127
student-vol/src/views/HomeView.vue
Normal file
@@ -0,0 +1,127 @@
|
||||
<template>
|
||||
<div class="home">
|
||||
<div class="header">
|
||||
<h1>大学生志愿者活动</h1>
|
||||
<User :user="userInfo" />
|
||||
</div>
|
||||
<Navs />
|
||||
<div class="list">
|
||||
<div class="list-header">
|
||||
<span>志愿活动</span>
|
||||
<div class="list-more" @click="goMoreActs">
|
||||
查看更多
|
||||
<img src="../assets/rArrow.png" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="actList">
|
||||
<ActItem
|
||||
v-for="item in list"
|
||||
:key="item.id"
|
||||
:data="item"
|
||||
@goDetails="goDetails"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from "axios";
|
||||
import User from "../components/Home/User.vue";
|
||||
import Navs from "../components/Home/Navs.vue";
|
||||
import ActItem from "../components/Home/ActItem.vue";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
User,
|
||||
Navs,
|
||||
ActItem,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
userInfo: {
|
||||
avatar: "",
|
||||
name: "",
|
||||
},
|
||||
list: [],
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
fetchUserInfo() {
|
||||
const that = this;
|
||||
axios.get("/api/userInfo").then(function (response) {
|
||||
const { error, data = {} } = response.data;
|
||||
if (error === 0) {
|
||||
that.userInfo = {
|
||||
avatar: data.avatar,
|
||||
name: data.name,
|
||||
};
|
||||
}
|
||||
});
|
||||
},
|
||||
fetchActList() {
|
||||
const that = this;
|
||||
axios.get("/api/actList").then(function (response) {
|
||||
const { error, data = {} } = response.data;
|
||||
if (error === 0) {
|
||||
that.list = data.list;
|
||||
}
|
||||
});
|
||||
},
|
||||
goDetails(id) {
|
||||
this.$router.push("/actDetails?id=" + id);
|
||||
},
|
||||
goMoreActs() {
|
||||
this.$router.push("/loveActs");
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.fetchUserInfo();
|
||||
this.fetchActList();
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.home {
|
||||
min-height: 100vh;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.header {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
text-align: center;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.list {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.list-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 15px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.list-more {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.list-more img {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
</style>
|
||||
139
student-vol/src/views/LoveActs.vue
Normal file
@@ -0,0 +1,139 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<div class="searchBox">
|
||||
<div class="search">
|
||||
<span class="search-icon"></span>
|
||||
<input placeholder="请输入地点、活动名称等关键字" v-model="keyword" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="body">
|
||||
<van-list
|
||||
v-model:loading="loading"
|
||||
:finished="finished"
|
||||
finished-text="没有更多了"
|
||||
@load="onLoad"
|
||||
>
|
||||
<ActItem
|
||||
v-for="item in actList"
|
||||
:key="item.id"
|
||||
:data="item"
|
||||
@goDetails="goDetails"
|
||||
/>
|
||||
</van-list>
|
||||
</div>
|
||||
|
||||
<button class="fBtn" @click="goMyApply">我的报名</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from "axios";
|
||||
import ActItem from "../components/Home/ActItem.vue";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ActItem,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
actList: [],
|
||||
loading: false,
|
||||
finished: false,
|
||||
currentPage: 1,
|
||||
keyword: "",
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
fetchActList(currentPage = 1) {
|
||||
const that = this;
|
||||
const payload = {
|
||||
currentPage,
|
||||
pageSize: 10,
|
||||
keyword: this.keyword,
|
||||
};
|
||||
|
||||
this.loading = true;
|
||||
axios
|
||||
.get("/api/actList", { params: payload })
|
||||
.then(function (response) {
|
||||
const { error, data = {} } = response.data;
|
||||
if (error === 0) {
|
||||
const currentPage = data.current;
|
||||
const list = data.list;
|
||||
|
||||
if (currentPage === 1) {
|
||||
that.actList = list;
|
||||
} else {
|
||||
that.actList.push(...list);
|
||||
}
|
||||
|
||||
that.currentPage = currentPage;
|
||||
that.finished = data.pageCount === currentPage;
|
||||
}
|
||||
})
|
||||
.finally(function () {
|
||||
that.loading = false;
|
||||
});
|
||||
},
|
||||
onLoad() {
|
||||
this.fetchActList(this.currentPage + 1);
|
||||
},
|
||||
goMyApply() {
|
||||
this.$router.push("/myApply");
|
||||
},
|
||||
goDetails(id) {
|
||||
this.$router.push("/actDetails?id=" + id);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
keyword() {
|
||||
this.currentPage = 1;
|
||||
this.fetchActList(1);
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.fetchActList(1);
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.searchBox {
|
||||
padding: 15px;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.search {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 10px;
|
||||
background: #f5f5f5;
|
||||
border-radius: 20px;
|
||||
}
|
||||
|
||||
.search input {
|
||||
flex: 1;
|
||||
border: none;
|
||||
background: transparent;
|
||||
outline: none;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.body {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.fBtn {
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
padding: 12px 24px;
|
||||
background: #ff5d23;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 25px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
203
student-vol/src/views/LoveReport.vue
Normal file
@@ -0,0 +1,203 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<div class="tabs">
|
||||
<div :class="{ tabItem: true, active: curTab === 0 }" @click="tabClick(0)">
|
||||
总积分
|
||||
</div>
|
||||
<div :class="{ tabItem: true, active: curTab === 1 }" @click="tabClick(1)">
|
||||
年度积分
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dataList">
|
||||
<div class="dataItem">
|
||||
<p class="dataItem-score">{{ provinceRank }}</p>
|
||||
<p class="dataItem-label">全省的排名</p>
|
||||
</div>
|
||||
<div class="dataItem">
|
||||
<p class="dataItem-score">{{ totalScore }}</p>
|
||||
<p class="dataItem-label">服务总积分</p>
|
||||
</div>
|
||||
<div class="dataItem">
|
||||
<p class="dataItem-score">{{ grandeRank }}</p>
|
||||
<p class="dataItem-label">本校本年级排名</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rank">
|
||||
<div class="rank-hd">
|
||||
<p class="rank-title">本校本年级服务标兵</p>
|
||||
<p class="rank-range">1200人参与排名</p>
|
||||
</div>
|
||||
<ul class="rank-list">
|
||||
<li v-for="(item, idx) in dataList" :key="item.id">
|
||||
<div class="rank-student">
|
||||
<span class="rank-idx">{{ idx + 1 }}</span>
|
||||
<span class="rank-avatar">
|
||||
<img :src='item.avatar' />
|
||||
</span>
|
||||
<span class="rank-name">{{ item.name }}</span>
|
||||
</div>
|
||||
<div class="rank-score">{{ item.score }}分</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from "axios";
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
curTab: 0,
|
||||
dataList: [],
|
||||
provinceRank: "",
|
||||
totalScore: "",
|
||||
grandeRank: "",
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
tabClick(value) {
|
||||
this.curTab = Number(value);
|
||||
this.fetchOverview(this.curTab);
|
||||
this.fetchRankList(this.curTab);
|
||||
},
|
||||
fetchOverview(type) {
|
||||
const that = this;
|
||||
axios
|
||||
.get("/api/report/myOverview", {
|
||||
params: { type },
|
||||
})
|
||||
.then(function (response) {
|
||||
const { error, data = {} } = response.data;
|
||||
if (error === 0) {
|
||||
that.provinceRank = data.provinceRank;
|
||||
that.totalScore = data.totalScore;
|
||||
that.grandeRank = data.grandeRank;
|
||||
}
|
||||
});
|
||||
},
|
||||
fetchRankList(type) {
|
||||
const that = this;
|
||||
const payload = {
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
type,
|
||||
};
|
||||
axios
|
||||
.get("/api/report/rankList", { params: payload })
|
||||
.then(function (response) {
|
||||
const { error, data = {} } = response.data;
|
||||
if (error === 0) {
|
||||
that.dataList = data.list || [];
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.fetchOverview(this.curTab);
|
||||
this.fetchRankList(this.curTab);
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tabs {
|
||||
display: flex;
|
||||
background: white;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.tabItem {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
padding: 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.tabItem.active {
|
||||
color: #ff5d23;
|
||||
border-bottom: 2px solid #ff5d23;
|
||||
}
|
||||
|
||||
.dataList {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
padding: 20px;
|
||||
background: white;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.dataItem {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.dataItem-score {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
color: #ff5d23;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.dataItem-label {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.rank {
|
||||
background: white;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.rank-hd {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.rank-title {
|
||||
font-weight: bold;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.rank-range {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.rank-list {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.rank-list li {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px 0;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.rank-student {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.rank-idx {
|
||||
width: 20px;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.rank-avatar img {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.rank-score {
|
||||
color: #ff5d23;
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
130
student-vol/src/views/MyApply.vue
Normal file
@@ -0,0 +1,130 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<ul class="tabList">
|
||||
<li @click="tabClick(0)">
|
||||
<span :class="curTab === 0 ? 'active' : ''">全部</span>
|
||||
</li>
|
||||
<li @click="tabClick(1)">
|
||||
<span :class="curTab === 1 ? 'active' : ''">审核中</span>
|
||||
</li>
|
||||
<li @click="tabClick(2)">
|
||||
<span :class="curTab === 2 ? 'active' : ''">审核通过</span>
|
||||
</li>
|
||||
<li @click="tabClick(3)">
|
||||
<span :class="curTab === 3 ? 'active' : ''">审核拒绝</span>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="list">
|
||||
<van-list
|
||||
v-model:loading="loading"
|
||||
:finished="finished"
|
||||
finished-text="没有更多了"
|
||||
@load="onLoad"
|
||||
>
|
||||
<ActItem
|
||||
v-for="item in actList"
|
||||
:key="item.id"
|
||||
:data="item"
|
||||
@goDetails="goDetails"
|
||||
/>
|
||||
</van-list>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from "axios";
|
||||
import ActItem from "../components/Home/ActItem.vue";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ActItem,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
curTab: 0,
|
||||
actList: [],
|
||||
loading: false,
|
||||
finished: false,
|
||||
currentPage: 1,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
tabClick(value) {
|
||||
this.curTab = value;
|
||||
this.currentPage = 1;
|
||||
this.fetchApplyList(1);
|
||||
},
|
||||
fetchApplyList(currentPage = 1) {
|
||||
const that = this;
|
||||
const payload = {
|
||||
currentPage,
|
||||
pageSize: 10,
|
||||
type: this.curTab,
|
||||
};
|
||||
|
||||
this.loading = true;
|
||||
axios
|
||||
.get("/api/myApplyList", { params: payload })
|
||||
.then(function (response) {
|
||||
const { error, data = {} } = response.data;
|
||||
if (error === 0) {
|
||||
const currentPage = data.current;
|
||||
const list = data.list;
|
||||
|
||||
if (currentPage === 1) {
|
||||
that.actList = list;
|
||||
} else {
|
||||
that.actList.push(...list);
|
||||
}
|
||||
|
||||
that.currentPage = currentPage;
|
||||
that.finished = data.pageCount === currentPage;
|
||||
}
|
||||
})
|
||||
.finally(function () {
|
||||
that.loading = false;
|
||||
});
|
||||
},
|
||||
onLoad() {
|
||||
this.fetchApplyList(this.currentPage + 1);
|
||||
},
|
||||
goDetails(id) {
|
||||
this.$router.push("/actDetails?id=" + id);
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.fetchApplyList(1);
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tabList {
|
||||
display: flex;
|
||||
background: white;
|
||||
list-style: none;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.tabList li {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
padding: 15px 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.tabList span {
|
||||
padding: 5px 10px;
|
||||
}
|
||||
|
||||
.tabList span.active {
|
||||
color: #ff5d23;
|
||||
border-bottom: 2px solid #ff5d23;
|
||||
}
|
||||
|
||||
.list {
|
||||
padding: 10px;
|
||||
}
|
||||
</style>
|
||||
152
student-vol/src/views/ServiceDetail.vue
Normal file
@@ -0,0 +1,152 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<div class="dataItem">
|
||||
<span class="label">服务时间</span>
|
||||
<span>{{ formatDay(data.actTime) }}</span>
|
||||
</div>
|
||||
|
||||
<div class="dataItem">
|
||||
<span class="label">服务地点</span>
|
||||
<span>{{ data.place }}</span>
|
||||
</div>
|
||||
|
||||
<div class="dataItem">
|
||||
<span class="label">服务内容</span>
|
||||
<span>{{ data.content }}</span>
|
||||
</div>
|
||||
|
||||
<div class="dataItem">
|
||||
<span class="label">服务照片</span>
|
||||
<div>
|
||||
<img
|
||||
class="serviceImg"
|
||||
v-for="(url) in data.imgList"
|
||||
:src="url"
|
||||
:key="url"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dataItem">
|
||||
<span class="label">服务时长</span>
|
||||
<span>{{ data.hour }}小时</span>
|
||||
</div>
|
||||
|
||||
<div class="dataItem">
|
||||
<span class="label">纪实时间</span>
|
||||
<span>{{ formatDate(data.uploadTime) }}</span>
|
||||
</div>
|
||||
|
||||
<div class="dataItem">
|
||||
<span class="label">审核状态</span>
|
||||
<span>
|
||||
<span
|
||||
class="status"
|
||||
:style="{ backgroundColor: bgColorMap[data.status] }"
|
||||
>
|
||||
{{ statusMap[data.status] }}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<p class="title">{{ titleMap[data.status] }}</p>
|
||||
<p class="score" v-if="data.status === 1">4分</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from "axios";
|
||||
import moment from "moment";
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
data: {},
|
||||
statusMap: ["审核中", "审核通过", "审核驳回"],
|
||||
bgColorMap: ["#1989fa", "#07c160", "#f6352c"],
|
||||
titleMap: [
|
||||
"正在审核中,请耐心等待",
|
||||
"感谢您的服务,请收下服务积分",
|
||||
"审核被驳回了哟",
|
||||
],
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
formatDate(value) {
|
||||
return value ? moment(value).format("YYYY-MM-DD HH:mm") : "--";
|
||||
},
|
||||
formatDay(value) {
|
||||
return value ? moment(value).format("YYYY-MM-DD") : "--";
|
||||
},
|
||||
fetchDetails() {
|
||||
const that = this;
|
||||
const payload = { id: this.$route.query.id };
|
||||
axios
|
||||
.get("/api/service/details", {
|
||||
params: payload,
|
||||
})
|
||||
.then(function (response) {
|
||||
const { error, data = {} } = response.data;
|
||||
if (error === 0) {
|
||||
that.data = data.details || {};
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.fetchDetails();
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.dataItem {
|
||||
background: white;
|
||||
padding: 15px 20px;
|
||||
margin-bottom: 1px;
|
||||
}
|
||||
|
||||
.label {
|
||||
display: block;
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.serviceImg {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
object-fit: cover;
|
||||
border-radius: 8px;
|
||||
margin-right: 10px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.status {
|
||||
padding: 4px 12px;
|
||||
color: white;
|
||||
font-size: 12px;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.footer {
|
||||
background: white;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 16px;
|
||||
color: #666;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.score {
|
||||
font-size: 32px;
|
||||
color: #ff5d23;
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
126
student-vol/src/views/ServiceRecords.vue
Normal file
@@ -0,0 +1,126 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="header">
|
||||
<User
|
||||
@updateYear="updateYear"
|
||||
:date="year"
|
||||
:dateList="dateList"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="overview">
|
||||
<Overview :year="year.value" />
|
||||
</div>
|
||||
|
||||
<van-list
|
||||
v-model:loading="loading"
|
||||
:finished="finished"
|
||||
finished-text="没有更多了"
|
||||
@load="onLoad"
|
||||
>
|
||||
<RecordItem
|
||||
v-for="item in dataList"
|
||||
:key="item.id"
|
||||
:data="item"
|
||||
@goDetails="goDetails"
|
||||
/>
|
||||
</van-list>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from "axios";
|
||||
import User from "../components/ServiceRecords/User.vue";
|
||||
import Overview from "../components/ServiceRecords/Overview.vue";
|
||||
import RecordItem from "../components/ServiceRecords/RecordItem.vue";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
User,
|
||||
Overview,
|
||||
RecordItem,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dataList: [],
|
||||
loading: false,
|
||||
finished: false,
|
||||
currentPage: 1,
|
||||
year: {},
|
||||
dateList: [],
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
fetchYearList() {
|
||||
const that = this;
|
||||
axios.get("/api/service/yearList").then(function (response) {
|
||||
const { error, data = {} } = response.data;
|
||||
if (error === 0) {
|
||||
const list = data.list || [];
|
||||
that.dateList = list;
|
||||
that.year = list[list.length - 1];
|
||||
}
|
||||
});
|
||||
},
|
||||
fetchServiceRecords(currentPage = 1) {
|
||||
const that = this;
|
||||
const payload = {
|
||||
currentPage,
|
||||
pageSize: 10,
|
||||
year: this.year.value,
|
||||
};
|
||||
|
||||
this.loading = true;
|
||||
axios
|
||||
.get("/api/service/list", { params: payload })
|
||||
.then(function (response) {
|
||||
const { error, data = {} } = response.data;
|
||||
if (error === 0) {
|
||||
const currentPage = data.current;
|
||||
const list = data.list;
|
||||
|
||||
if (currentPage === 1) {
|
||||
that.dataList = list;
|
||||
} else {
|
||||
that.dataList.push(...list);
|
||||
}
|
||||
|
||||
that.currentPage = currentPage;
|
||||
that.finished = data.pageCount === currentPage;
|
||||
}
|
||||
})
|
||||
.finally(function () {
|
||||
that.loading = false;
|
||||
});
|
||||
},
|
||||
onLoad() {
|
||||
this.fetchServiceRecords(this.currentPage + 1);
|
||||
},
|
||||
goDetails(id) {
|
||||
this.$router.push("/serviceDetail?id=" + id);
|
||||
},
|
||||
updateYear(yearValue) {
|
||||
this.year = yearValue;
|
||||
this.currentPage = 1;
|
||||
this.fetchServiceRecords(1);
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.fetchYearList();
|
||||
},
|
||||
watch: {
|
||||
"year.value"() {
|
||||
if (this.year.value) {
|
||||
this.fetchServiceRecords(1);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.header {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
}
|
||||
</style>
|
||||
239
student-vol/src/views/UploadService.vue
Normal file
@@ -0,0 +1,239 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="frm">
|
||||
<van-field
|
||||
v-model="pushlisher.text"
|
||||
is-link
|
||||
readonly
|
||||
label="活动来源"
|
||||
placeholder="请选择活动来源"
|
||||
@click="fieldCLick(0)"
|
||||
/>
|
||||
|
||||
<van-field
|
||||
v-model="date"
|
||||
is-link
|
||||
readonly
|
||||
label="服务时间"
|
||||
placeholder="请选择日期"
|
||||
@click="fieldCLick(1)"
|
||||
/>
|
||||
|
||||
<van-field
|
||||
v-model="duration.text"
|
||||
is-link
|
||||
readonly
|
||||
label="服务时长"
|
||||
placeholder="请选择时长"
|
||||
@click="fieldCLick(2)"
|
||||
/>
|
||||
|
||||
<van-field
|
||||
v-model="content"
|
||||
label="服务内容"
|
||||
placeholder="请对服务内容进行简要描述"
|
||||
label-align="top"
|
||||
type="textarea"
|
||||
maxlength="100"
|
||||
show-word-limit
|
||||
/>
|
||||
|
||||
<van-uploader
|
||||
v-model="fileList"
|
||||
multiple
|
||||
max-count="2"
|
||||
:upload-text="uploadTxt"
|
||||
image-fit="cover"
|
||||
style="padding: 10px"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<van-button
|
||||
type="primary"
|
||||
color="#ff5d23"
|
||||
block
|
||||
@click="submit"
|
||||
>
|
||||
提交
|
||||
</van-button>
|
||||
</div>
|
||||
|
||||
<van-popup v-model:show="showPicker" round position="bottom">
|
||||
<van-picker
|
||||
:columns="columns"
|
||||
@cancel="pickerCancel"
|
||||
@confirm="pickerConfirm"
|
||||
/>
|
||||
</van-popup>
|
||||
|
||||
<van-popup v-model:show="showDatePicker" round position="bottom">
|
||||
<van-date-picker
|
||||
v-model="currentDate"
|
||||
title="选择日期"
|
||||
:min-date="minDate"
|
||||
:max-date="maxDate"
|
||||
@cancel="pickerCancel"
|
||||
@confirm="pickerConfirm"
|
||||
/>
|
||||
</van-popup>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { showToast, showSuccessToast, showFailToast } from "vant";
|
||||
import axios from "axios";
|
||||
|
||||
function dateRange() {
|
||||
const curTimeStamp = new Date();
|
||||
const tYear = curTimeStamp.getFullYear();
|
||||
const tMonth = curTimeStamp.getMonth();
|
||||
const date = curTimeStamp.getDate();
|
||||
return {
|
||||
currentDate: [tYear, tMonth, date],
|
||||
minDate: new Date(tYear - 2, tMonth, date),
|
||||
maxDate: new Date(tYear, tMonth, date),
|
||||
};
|
||||
}
|
||||
|
||||
let pushlisherList = [];
|
||||
let durationList = [];
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
columns: [],
|
||||
pickerId: null,
|
||||
pushlisher: { value: null, text: "" },
|
||||
duration: { value: null, text: "" },
|
||||
date: null,
|
||||
content: "",
|
||||
fileList: [],
|
||||
currentDate: null,
|
||||
minDate: null,
|
||||
maxDate: null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
showPicker() {
|
||||
return this.pickerId === 0 || this.pickerId === 2;
|
||||
},
|
||||
showDatePicker() {
|
||||
return this.pickerId === 1;
|
||||
},
|
||||
uploadTxt() {
|
||||
const length = this.fileList.length;
|
||||
return "添加照片" + length + "/2";
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
fieldCLick(value) {
|
||||
this.pickerId = value;
|
||||
if (value === 0) {
|
||||
this.columns = pushlisherList;
|
||||
} else if (value === 2) {
|
||||
this.columns = durationList;
|
||||
}
|
||||
},
|
||||
pickerCancel() {
|
||||
this.pickerId = null;
|
||||
},
|
||||
pickerConfirm({ selectedValues, selectedOptions }) {
|
||||
const curPickerId = this.pickerId;
|
||||
this.pickerId = null;
|
||||
|
||||
if (curPickerId === 1) {
|
||||
const data = selectedValues;
|
||||
this.date = data.join(".");
|
||||
return;
|
||||
}
|
||||
|
||||
const data = selectedOptions[0];
|
||||
const payload = {
|
||||
value: data.value,
|
||||
text: data.text,
|
||||
};
|
||||
|
||||
if (curPickerId === 0) {
|
||||
this.pushlisher = payload;
|
||||
return;
|
||||
}
|
||||
|
||||
this.duration = payload;
|
||||
},
|
||||
fetchPublisherlist() {
|
||||
axios.get("/api/act/publisherList").then(function (response) {
|
||||
const { error, data = {} } = response.data;
|
||||
if (error === 0) {
|
||||
pushlisherList = data.list || [];
|
||||
}
|
||||
});
|
||||
},
|
||||
fetchDurations() {
|
||||
axios.get("/api/act/durationsList").then(function (response) {
|
||||
const { error, data = {} } = response.data;
|
||||
if (error === 0) {
|
||||
durationList = data.list || [];
|
||||
}
|
||||
});
|
||||
},
|
||||
submit() {
|
||||
if (!this.pushlisher.text) {
|
||||
showToast("未选择活动来源");
|
||||
return;
|
||||
}
|
||||
if (!this.date) {
|
||||
showToast("未选择服务时间");
|
||||
return;
|
||||
}
|
||||
if (!this.duration.text) {
|
||||
showToast("未选择服务时长");
|
||||
return;
|
||||
}
|
||||
if (!this.content) {
|
||||
showToast("未填写服务内容");
|
||||
return;
|
||||
}
|
||||
if (!this.fileList.length) {
|
||||
showToast("未上传服务照片");
|
||||
return;
|
||||
}
|
||||
|
||||
const payload = {
|
||||
pushlisher: this.pushlisher.value,
|
||||
date: +new Date(this.date),
|
||||
duration: this.duration.value,
|
||||
content: this.content,
|
||||
fileList: this.fileList,
|
||||
};
|
||||
|
||||
axios.post("/api/act/uploadService", payload).then(function (response) {
|
||||
const { error, msg } = response.data;
|
||||
if (error === 0) {
|
||||
showSuccessToast("操作成功");
|
||||
} else {
|
||||
showFailToast(msg || "网络错误,请稍后重试");
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
created() {
|
||||
const { currentDate, minDate, maxDate } = dateRange();
|
||||
this.currentDate = currentDate;
|
||||
this.minDate = minDate;
|
||||
this.maxDate = maxDate;
|
||||
this.fetchDurations();
|
||||
this.fetchPublisherlist();
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.frm {
|
||||
background: white;
|
||||
}
|
||||
|
||||
.footer {
|
||||
padding: 20px;
|
||||
}
|
||||
</style>
|
||||
202
student-vol/src/views/UserCenter.vue
Normal file
@@ -0,0 +1,202 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<div class="inner">
|
||||
<h1>个人信息</h1>
|
||||
<ul>
|
||||
<li>
|
||||
<p class="label"><span>*</span>姓名</p>
|
||||
<span class="txt">{{ name }}</span>
|
||||
</li>
|
||||
<li>
|
||||
<p class="label"><span>*</span>编号</p>
|
||||
<span class="txt">{{ code }}</span>
|
||||
</li>
|
||||
<li>
|
||||
<p class="label"><span>*</span>性别</p>
|
||||
<input type="radio" value="0" v-model="gender" />男
|
||||
<input type="radio" value="1" v-model="gender" />女
|
||||
</li>
|
||||
<li>
|
||||
<p class="label"><span>*</span>手机号</p>
|
||||
<input placeholder="请输入手机号" v-model.trim="phoneNum" />
|
||||
</li>
|
||||
<li>
|
||||
<p class="label"><span>*</span>学校</p>
|
||||
<input placeholder="请输入学校名称" v-model.trim="school" />
|
||||
</li>
|
||||
<li>
|
||||
<p class="label"><span>*</span>专业</p>
|
||||
<input placeholder="请输入专业名称" v-model.trim="profession" />
|
||||
</li>
|
||||
<li>
|
||||
<p class="label"><span>*</span>人员属性</p>
|
||||
<div class="userTypeList">
|
||||
<span :class="{ active: userType === 0 }" @click="changeUserType(0)">群众</span>
|
||||
<span :class="{ active: userType === 1 }" @click="changeUserType(1)">团员</span>
|
||||
<span :class="{ active: userType === 2 }" @click="changeUserType(2)">党员</span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<button class="subBtn" @click="submitClick">提交</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from "axios";
|
||||
import { showToast, showSuccessToast, showFailToast } from "vant";
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
name: "",
|
||||
code: null,
|
||||
gender: 0,
|
||||
phoneNum: "",
|
||||
school: "",
|
||||
profession: "",
|
||||
userType: 0,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
changeUserType(value) {
|
||||
this.userType = value;
|
||||
},
|
||||
fetchUsrInfo() {
|
||||
const that = this;
|
||||
axios.get("/api/userInfo").then(function (response) {
|
||||
const { error, data = {} } = response.data;
|
||||
if (error === 0) {
|
||||
that.name = data.name;
|
||||
that.code = data.code;
|
||||
that.gender = data.gender;
|
||||
that.phoneNum = data.phoneNum;
|
||||
that.school = data.school;
|
||||
that.profession = data.profession;
|
||||
that.userType = data.userType;
|
||||
}
|
||||
});
|
||||
},
|
||||
check() {
|
||||
var phoneNum = this.phoneNum;
|
||||
var school = this.school;
|
||||
var profession = this.profession;
|
||||
|
||||
if (phoneNum === "") {
|
||||
showToast("手机号不可为空");
|
||||
return;
|
||||
}
|
||||
if (school === "") {
|
||||
showToast("学校不可为空");
|
||||
return;
|
||||
}
|
||||
if (profession === "") {
|
||||
showToast("专业不可为空");
|
||||
return;
|
||||
}
|
||||
|
||||
const payload = {
|
||||
name: this.name,
|
||||
code: this.code,
|
||||
gender: this.gender,
|
||||
phoneNum,
|
||||
school,
|
||||
profession,
|
||||
userType: this.userType,
|
||||
};
|
||||
return payload;
|
||||
},
|
||||
submitClick() {
|
||||
var payload = this.check();
|
||||
if (!payload) {
|
||||
return;
|
||||
}
|
||||
|
||||
axios.post("/api/userInfo/submit", payload).then(function (response) {
|
||||
const { error, msg } = response.data;
|
||||
if (error === 0) {
|
||||
showSuccessToast("操作成功");
|
||||
} else {
|
||||
showFailToast(msg || "网络错误,请稍后重试");
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.fetchUsrInfo();
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.inner {
|
||||
padding: 20px;
|
||||
background: white;
|
||||
}
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
li {
|
||||
padding: 15px 0;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-weight: bold;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.label span {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.txt {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
input[type="text"], input {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.userTypeList {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.userTypeList span {
|
||||
padding: 8px 20px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.userTypeList span.active {
|
||||
background: #ff5d23;
|
||||
color: white;
|
||||
border-color: #ff5d23;
|
||||
}
|
||||
|
||||
.subBtn {
|
||||
width: 100%;
|
||||
padding: 15px;
|
||||
background: #ff5d23;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
font-size: 16px;
|
||||
margin-top: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
148
student-vol/vite.config.js
Normal file
@@ -0,0 +1,148 @@
|
||||
import { defineConfig } from "vite";
|
||||
import vue from "@vitejs/plugin-vue";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
|
||||
function mockPlugin() {
|
||||
return {
|
||||
name: "simple-mock-plugin",
|
||||
configureServer(server) {
|
||||
server.middlewares.use((req, res, next) => {
|
||||
const sendJSON = (file) => {
|
||||
const filePath = path.resolve(process.cwd(), `mock/${file}`);
|
||||
if (!fs.existsSync(filePath)) {
|
||||
res.statusCode = 404;
|
||||
res.setHeader("Content-Type", "application/json");
|
||||
return res.end(
|
||||
JSON.stringify({
|
||||
error: `Mock file not found: ${file}`,
|
||||
}),
|
||||
);
|
||||
}
|
||||
const json = fs.readFileSync(filePath, "utf-8");
|
||||
res.setHeader("Content-Type", "application/json");
|
||||
res.end(json);
|
||||
};
|
||||
|
||||
// 用户信息
|
||||
if (req.url === "/api/userInfo" && req.method === "GET") {
|
||||
return sendJSON("userInfo.json");
|
||||
}
|
||||
|
||||
// 提交用户信息
|
||||
if (req.url === "/api/userInfo/submit" && req.method === "POST") {
|
||||
return sendJSON("success.json");
|
||||
}
|
||||
|
||||
// 活动列表
|
||||
if (req.url.startsWith("/api/actList") && req.method === "GET") {
|
||||
return sendJSON("actList.json");
|
||||
}
|
||||
|
||||
// 积分概览
|
||||
if (
|
||||
req.url.startsWith("/api/report/myOverview") &&
|
||||
req.method === "GET"
|
||||
) {
|
||||
return sendJSON("scoreOverview.json");
|
||||
}
|
||||
|
||||
// 排名列表
|
||||
if (
|
||||
req.url.startsWith("/api/report/rankList") &&
|
||||
req.method === "GET"
|
||||
) {
|
||||
return sendJSON("rank.json");
|
||||
}
|
||||
|
||||
// 活动详情
|
||||
if (
|
||||
req.url.startsWith("/api/actDetails/details") &&
|
||||
req.method === "GET"
|
||||
) {
|
||||
return sendJSON("actDetails.json");
|
||||
}
|
||||
|
||||
// 申请/撤销活动
|
||||
if (
|
||||
req.url.startsWith("/api/actDetails/apply") &&
|
||||
req.method === "POST"
|
||||
) {
|
||||
return sendJSON("success.json");
|
||||
}
|
||||
|
||||
// 我的报名列表
|
||||
if (req.url.startsWith("/api/myApplyList") && req.method === "GET") {
|
||||
return sendJSON("myApplyList.json");
|
||||
}
|
||||
|
||||
// 活动来源列表
|
||||
if (
|
||||
req.url.startsWith("/api/act/publisherList") &&
|
||||
req.method === "GET"
|
||||
) {
|
||||
return sendJSON("publishers.json");
|
||||
}
|
||||
|
||||
// 服务时长列表
|
||||
if (
|
||||
req.url.startsWith("/api/act/durationsList") &&
|
||||
req.method === "GET"
|
||||
) {
|
||||
return sendJSON("durationList.json");
|
||||
}
|
||||
|
||||
// 上传服务记录
|
||||
if (
|
||||
req.url.startsWith("/api/act/uploadService") &&
|
||||
req.method === "POST"
|
||||
) {
|
||||
return sendJSON("success.json");
|
||||
}
|
||||
|
||||
// 用户积分
|
||||
if (
|
||||
req.url.startsWith("/api/service/userScore") &&
|
||||
req.method === "GET"
|
||||
) {
|
||||
return sendJSON("userScore.json");
|
||||
}
|
||||
|
||||
// 年份列表
|
||||
if (
|
||||
req.url.startsWith("/api/service/yearList") &&
|
||||
req.method === "GET"
|
||||
) {
|
||||
return sendJSON("yearList.json");
|
||||
}
|
||||
|
||||
// 年度积分
|
||||
if (
|
||||
req.url.startsWith("/api/service/yearScore") &&
|
||||
req.method === "GET"
|
||||
) {
|
||||
return sendJSON("yearScore.json");
|
||||
}
|
||||
|
||||
// 服务记录列表
|
||||
if (req.url.startsWith("/api/service/list") && req.method === "GET") {
|
||||
return sendJSON("serviceList.json");
|
||||
}
|
||||
|
||||
// 服务详情
|
||||
if (
|
||||
req.url.startsWith("/api/service/details") &&
|
||||
req.method === "GET"
|
||||
) {
|
||||
return sendJSON("recordDetails.json");
|
||||
}
|
||||
|
||||
next();
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [vue(), mockPlugin()],
|
||||
});
|
||||