init
15
.editorconfig
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
end_of_line = lf
|
||||||
|
insert_final_newline = true
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 4
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
trim_trailing_whitespace = false
|
||||||
|
|
||||||
|
[*.{yml,yaml}]
|
||||||
|
indent_size = 2
|
||||||
51
.env.example
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
APP_NAME=Laravel
|
||||||
|
APP_ENV=local
|
||||||
|
APP_KEY=base64:umLkXIEpRxIKX78YG1gu3kr1OcMJ7+kFq2G87/paE2E=
|
||||||
|
APP_DEBUG=false
|
||||||
|
APP_URL=http://
|
||||||
|
|
||||||
|
LOG_CHANNEL=stack
|
||||||
|
|
||||||
|
FILESYSTEM_DRIVER=public
|
||||||
|
|
||||||
|
DB_CONNECTION=mysql
|
||||||
|
DB_HOST=127.0.0.1
|
||||||
|
DB_PORT=3306
|
||||||
|
DB_DATABASE=
|
||||||
|
DB_USERNAME=
|
||||||
|
DB_PASSWORD=
|
||||||
|
|
||||||
|
BROADCAST_DRIVER=log
|
||||||
|
CACHE_DRIVER=redis
|
||||||
|
QUEUE_CONNECTION=sync
|
||||||
|
SESSION_DRIVER=redis
|
||||||
|
SESSION_LIFETIME=120
|
||||||
|
|
||||||
|
REDIS_HOST=127.0.0.1
|
||||||
|
REDIS_PASSWORD=null
|
||||||
|
REDIS_PORT=6379
|
||||||
|
|
||||||
|
MAIL_MAILER=smtp
|
||||||
|
MAIL_HOST=smtp.mailtrap.io
|
||||||
|
MAIL_PORT=2525
|
||||||
|
MAIL_USERNAME=null
|
||||||
|
MAIL_PASSWORD=null
|
||||||
|
MAIL_ENCRYPTION=null
|
||||||
|
MAIL_FROM_ADDRESS=null
|
||||||
|
MAIL_FROM_NAME="${APP_NAME}"
|
||||||
|
|
||||||
|
AWS_ACCESS_KEY_ID=
|
||||||
|
AWS_SECRET_ACCESS_KEY=
|
||||||
|
AWS_DEFAULT_REGION=us-east-1
|
||||||
|
AWS_BUCKET=
|
||||||
|
|
||||||
|
PUSHER_APP_ID=
|
||||||
|
PUSHER_APP_KEY=
|
||||||
|
PUSHER_APP_SECRET=
|
||||||
|
PUSHER_APP_CLUSTER=mt1
|
||||||
|
|
||||||
|
MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
|
||||||
|
MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
|
||||||
|
|
||||||
|
JWT_TTL=102400
|
||||||
|
JWT_SECRET=92mEtw8IgQ9yARXPs0Doy7L8DRvPHclm4YZB4tSq3uCYtNURxy3X3Q6YRh8MUEcG
|
||||||
5
.gitattributes
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
* text=auto
|
||||||
|
*.css linguist-vendored
|
||||||
|
*.scss linguist-vendored
|
||||||
|
*.js linguist-vendored
|
||||||
|
CHANGELOG.md export-ignore
|
||||||
14
.gitignore
vendored
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
/node_modules
|
||||||
|
/public/hot
|
||||||
|
/public/storage
|
||||||
|
/storage/*.key
|
||||||
|
/vendor
|
||||||
|
.env
|
||||||
|
.env.backup
|
||||||
|
.phpunit.result.cache
|
||||||
|
Homestead.json
|
||||||
|
Homestead.yaml
|
||||||
|
npm-debug.log
|
||||||
|
yarn-error.log
|
||||||
|
/tests/
|
||||||
|
.idea
|
||||||
13
.styleci.yml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
php:
|
||||||
|
preset: laravel
|
||||||
|
disabled:
|
||||||
|
- unused_use
|
||||||
|
finder:
|
||||||
|
not-name:
|
||||||
|
- index.php
|
||||||
|
- server.php
|
||||||
|
js:
|
||||||
|
finder:
|
||||||
|
not-name:
|
||||||
|
- webpack.mix.js
|
||||||
|
css: true
|
||||||
3
VUE-WEB/lions-agent/.browserslistrc
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
> 1%
|
||||||
|
last 2 versions
|
||||||
|
not dead
|
||||||
17
VUE-WEB/lions-agent/.eslintrc.js
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
env: {
|
||||||
|
node: true
|
||||||
|
},
|
||||||
|
'extends': [
|
||||||
|
'plugin:vue/essential',
|
||||||
|
'eslint:recommended'
|
||||||
|
],
|
||||||
|
parserOptions: {
|
||||||
|
parser: 'babel-eslint'
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
|
||||||
|
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off'
|
||||||
|
}
|
||||||
|
}
|
||||||
21
VUE-WEB/lions-agent/.gitignore
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
.DS_Store
|
||||||
|
node_modules
|
||||||
|
/dist
|
||||||
|
|
||||||
|
# local env files
|
||||||
|
.env.local
|
||||||
|
.env.*.local
|
||||||
|
|
||||||
|
# Log files
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.idea
|
||||||
|
.vscode
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
29
VUE-WEB/lions-agent/README.md
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# lions
|
||||||
|
|
||||||
|
## Project setup
|
||||||
|
```
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
### Compiles and hot-reloads for development
|
||||||
|
```
|
||||||
|
npm run serve
|
||||||
|
```
|
||||||
|
|
||||||
|
### Compiles and minifies for production
|
||||||
|
```
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
### Run your tests
|
||||||
|
```
|
||||||
|
npm run test
|
||||||
|
```
|
||||||
|
|
||||||
|
### Lints and fixes files
|
||||||
|
```
|
||||||
|
npm run lint
|
||||||
|
```
|
||||||
|
|
||||||
|
### Customize configuration
|
||||||
|
See [Configuration Reference](https://cli.vuejs.org/config/).
|
||||||
5
VUE-WEB/lions-agent/babel.config.js
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
module.exports = {
|
||||||
|
presets: [
|
||||||
|
'@vue/cli-plugin-babel/preset'
|
||||||
|
]
|
||||||
|
}
|
||||||
11975
VUE-WEB/lions-agent/package-lock.json
generated
Normal file
30
VUE-WEB/lions-agent/package.json
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"name": "lions-agent",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"serve": "vue-cli-service serve",
|
||||||
|
"build": "vue-cli-service build",
|
||||||
|
"lint": "vue-cli-service lint"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"axios": "^0.19.2",
|
||||||
|
"core-js": "^3.6.4",
|
||||||
|
"vue": "^2.6.11",
|
||||||
|
"vue-router": "^3.1.6",
|
||||||
|
"vuelidate": "^0.7.5",
|
||||||
|
"vuetify": "^2.2.26",
|
||||||
|
"vuex": "^3.1.3"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@mdi/font": "^5.1.45",
|
||||||
|
"@vue/cli-plugin-babel": "^4.3.0",
|
||||||
|
"@vue/cli-plugin-eslint": "^4.3.0",
|
||||||
|
"@vue/cli-service": "^4.3.0",
|
||||||
|
"babel-eslint": "^10.1.0",
|
||||||
|
"eslint": "^6.7.2",
|
||||||
|
"eslint-plugin-vue": "^6.2.2",
|
||||||
|
"material-design-icons-iconfont": "^5.0.1",
|
||||||
|
"vue-template-compiler": "^2.6.11"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
VUE-WEB/lions-agent/public/favicon.ico
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
17
VUE-WEB/lions-agent/public/index.html
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, minimum-scale=1.0, maximum-scale=1.0, initial-scale=1.0, user-scalable=no">
|
||||||
|
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||||
|
<title><%= htmlWebpackPlugin.options.title %></title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<noscript>
|
||||||
|
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||||
|
</noscript>
|
||||||
|
<div id="app"></div>
|
||||||
|
<!-- built files will be auto injected -->
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
23
VUE-WEB/lions-agent/src/App.vue
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<template>
|
||||||
|
<v-app>
|
||||||
|
<router-view></router-view>
|
||||||
|
</v-app>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'app',
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
#app {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: #fdfdfd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hasFooter {
|
||||||
|
padding-bottom: 50px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
56
VUE-WEB/lions-agent/src/api/axios.js
vendored
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import axios from 'axios'
|
||||||
|
import store from "@/store"
|
||||||
|
import router from "@/router";
|
||||||
|
// axios 配置
|
||||||
|
axios.defaults.timeout = 3000;
|
||||||
|
axios.defaults.baseURL = 'http://lions-vote.funnyzhibo.com/api/';
|
||||||
|
|
||||||
|
const axiosConf = (config) => {
|
||||||
|
// config.headers.Authorization = store.state.accessToken;
|
||||||
|
config.headers.Authorization = localStorage.getItem('accessToken')
|
||||||
|
return config;
|
||||||
|
};
|
||||||
|
|
||||||
|
axios.interceptors.request.use(axiosConf, err => {
|
||||||
|
return Promise.reject(err);
|
||||||
|
});
|
||||||
|
|
||||||
|
axios.interceptors.response.use(async (response) => {
|
||||||
|
let data = {};
|
||||||
|
let code = Number(response.data.status_code);
|
||||||
|
if (code === 401 || code == 0) {
|
||||||
|
if (response.data.status == 'EXCEPTION') {
|
||||||
|
return Promise.reject(response.data)
|
||||||
|
}
|
||||||
|
if (response.headers.authorization) {
|
||||||
|
await store.commit('login', response.headers.authorization);
|
||||||
|
|
||||||
|
response.config.headers.Authorization = response.headers.authorization;
|
||||||
|
const result = await axios.request(axiosConf(response.config));
|
||||||
|
if (result) {
|
||||||
|
data = result;
|
||||||
|
}
|
||||||
|
} else if (router.currentRoute.name != 'AuthLogin') {
|
||||||
|
setTimeout(() => {
|
||||||
|
router.replace({name: 'AuthLogin', query: {redirect: router.currentRoute.fullPath}});
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
} else if (code === 200) {
|
||||||
|
data = response.data.data;
|
||||||
|
} else if (code === 403) {
|
||||||
|
await router.replace({path: '/403', query: {redirect: router.currentRoute.fullPath}});
|
||||||
|
} else if (code === 410) {
|
||||||
|
setTimeout(() => {
|
||||||
|
router.replace({name: 'AuthLogin', query: {redirect: router.currentRoute.fullPath}});
|
||||||
|
}, 1200);
|
||||||
|
} else if (code == 404) {
|
||||||
|
return Promise.reject(response.data);
|
||||||
|
} else {
|
||||||
|
return Promise.reject(response.data);
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}, (error) => {
|
||||||
|
return Promise.reject(error.response.data);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default axios;
|
||||||
7
VUE-WEB/lions-agent/src/api/index.js
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import auth from './interfaces/auth'
|
||||||
|
import index from './interfaces/index'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
index,
|
||||||
|
auth
|
||||||
|
}
|
||||||
13
VUE-WEB/lions-agent/src/api/interfaces/auth.js
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import axios from '../axios'
|
||||||
|
|
||||||
|
const license = () => axios.get('auth/license')
|
||||||
|
|
||||||
|
const code = (data) => axios.post('auth/code/login2', data)
|
||||||
|
|
||||||
|
const loginByCode = (data) => axios.post('auth/loginByCode', data)
|
||||||
|
|
||||||
|
export default {
|
||||||
|
license,
|
||||||
|
code,
|
||||||
|
loginByCode
|
||||||
|
}
|
||||||
7
VUE-WEB/lions-agent/src/api/interfaces/index.js
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import axios from '../axios'
|
||||||
|
|
||||||
|
const index = () => axios.get('agent')
|
||||||
|
|
||||||
|
export default {
|
||||||
|
index
|
||||||
|
}
|
||||||
BIN
VUE-WEB/lions-agent/src/assets/logo.png
Normal file
|
After Width: | Height: | Size: 59 KiB |
27
VUE-WEB/lions-agent/src/main.js
vendored
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import Vue from 'vue'
|
||||||
|
import App from './App.vue'
|
||||||
|
import VueRouter from 'vue-router'
|
||||||
|
import router from './router'
|
||||||
|
import store from './store'
|
||||||
|
import vuetify from '@/plugins/vuetify'
|
||||||
|
import api from './api'
|
||||||
|
import toast from './plugins/toast'
|
||||||
|
|
||||||
|
Vue.use(VueRouter)
|
||||||
|
|
||||||
|
Vue.config.productionTip = false
|
||||||
|
Vue.prototype.$api = api
|
||||||
|
Vue.prototype.toast = toast;
|
||||||
|
Vue.prototype.jump = (name, params, query) => {
|
||||||
|
router.push({
|
||||||
|
name: name,
|
||||||
|
params: params,
|
||||||
|
query: query
|
||||||
|
})
|
||||||
|
}
|
||||||
|
new Vue({
|
||||||
|
vuetify,
|
||||||
|
router,
|
||||||
|
store,
|
||||||
|
render: h => h(App)
|
||||||
|
}).$mount('#app')
|
||||||
174
VUE-WEB/lions-agent/src/pages/auth/login.vue
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
<template>
|
||||||
|
<v-container class="login">
|
||||||
|
<div class="body-2 grey--text">
|
||||||
|
中国狮子联会哈尔滨代表处
|
||||||
|
</div>
|
||||||
|
<div class="display-1 pt-3 pb-10">
|
||||||
|
选举监督系统
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<v-form
|
||||||
|
@submit="formSubmit"
|
||||||
|
v-model="valid"
|
||||||
|
>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" md="12">
|
||||||
|
<v-text-field
|
||||||
|
:counter="11"
|
||||||
|
:rules="usernameRules"
|
||||||
|
clearable
|
||||||
|
label="手机号"
|
||||||
|
prefix="+86"
|
||||||
|
required
|
||||||
|
type="tel"
|
||||||
|
v-model="username"
|
||||||
|
></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col
|
||||||
|
class="caption grey--text"
|
||||||
|
cols="12"
|
||||||
|
md="12"
|
||||||
|
>
|
||||||
|
点击"下一步"即表示已阅读并同意并自愿遵守
|
||||||
|
<span
|
||||||
|
@click="ishow(1)"
|
||||||
|
class="blue--text text--darken-4"
|
||||||
|
>《服务使用协议》</span>以及
|
||||||
|
<span
|
||||||
|
@click="ishow(2)"
|
||||||
|
class="blue--text text--darken-4"
|
||||||
|
>《隐私声明》</span>
|
||||||
|
</v-col>
|
||||||
|
<v-col>
|
||||||
|
<v-btn
|
||||||
|
:disabled="!valid"
|
||||||
|
block
|
||||||
|
color="#1f3c84"
|
||||||
|
dark
|
||||||
|
large
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
|
下一步
|
||||||
|
</v-btn>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-form>
|
||||||
|
|
||||||
|
<v-dialog
|
||||||
|
v-model="dialog"
|
||||||
|
width="500"
|
||||||
|
>
|
||||||
|
<v-card>
|
||||||
|
<v-card-title
|
||||||
|
class="grey lighten-2"
|
||||||
|
>
|
||||||
|
{{dia.title}}
|
||||||
|
</v-card-title>
|
||||||
|
|
||||||
|
<v-card-text class="pt-4" v-html="dia.content">
|
||||||
|
</v-card-text>
|
||||||
|
|
||||||
|
<v-divider></v-divider>
|
||||||
|
|
||||||
|
<v-card-actions>
|
||||||
|
<div class="flex-grow-1"></div>
|
||||||
|
<v-btn
|
||||||
|
@click="dialog = false"
|
||||||
|
color="#1f3c84"
|
||||||
|
text
|
||||||
|
>
|
||||||
|
已阅读
|
||||||
|
</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
|
|
||||||
|
</v-container>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import router from '@/router'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "login",
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
valid: false,
|
||||||
|
username: '',
|
||||||
|
usernameRules: [
|
||||||
|
v => !!v || '手机号必须填写',
|
||||||
|
v => (v && v.length == 11) || '手机号码必须是11位',
|
||||||
|
v => /^1[3-9][0-9]{9}$/.test(v) || '手机号码格式不正确'
|
||||||
|
],
|
||||||
|
dialog: false,
|
||||||
|
dia: {
|
||||||
|
title: '',
|
||||||
|
content: ''
|
||||||
|
},
|
||||||
|
agreement: '',
|
||||||
|
privacy: '',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$api.auth.license().then(result => {
|
||||||
|
this.agreement = result.agreement;
|
||||||
|
this.privacy = result.privacy
|
||||||
|
});
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
formSubmit(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
this.$api.auth.code({
|
||||||
|
username: this.username
|
||||||
|
}).then(() => {
|
||||||
|
router.push({
|
||||||
|
name: 'AuthVerify',
|
||||||
|
params: {
|
||||||
|
username: this.username
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}).catch(err => {
|
||||||
|
this.toast(err.message, 'warning')
|
||||||
|
})
|
||||||
|
},
|
||||||
|
ishow(n) {
|
||||||
|
this.dialog = !this.dialog;
|
||||||
|
if (n == 1) {
|
||||||
|
this.dia = {
|
||||||
|
title: '用户协议',
|
||||||
|
content: this.agreement
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.dia = {
|
||||||
|
title: '隐私声明',
|
||||||
|
content: this.privacy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
wechatLogin() {
|
||||||
|
let uri = window.location.origin + '/auth/wechat';
|
||||||
|
this.$api.auth.wechat_url({
|
||||||
|
uri: uri,
|
||||||
|
scope: 'snsapi_base'
|
||||||
|
}).then(res => {
|
||||||
|
window.location.href = res.url
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.login {
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-image: url("../../assets/logo.png");
|
||||||
|
background-size: 200px;
|
||||||
|
background-position: center;
|
||||||
|
|
||||||
|
padding: 15%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.caption span {
|
||||||
|
color: #1f3c84;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
186
VUE-WEB/lions-agent/src/pages/auth/verify.vue
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
<template>
|
||||||
|
<v-content>
|
||||||
|
<v-container>
|
||||||
|
<input
|
||||||
|
:disabled="disable"
|
||||||
|
@input="getVal"
|
||||||
|
autofocus="autofocus"
|
||||||
|
class="hidden-input"
|
||||||
|
type="number"
|
||||||
|
v-model="verify"
|
||||||
|
>
|
||||||
|
<div class="code-box">
|
||||||
|
<div class="title">
|
||||||
|
输入短信验证码
|
||||||
|
</div>
|
||||||
|
<div class="body-2">
|
||||||
|
验证码已发送至{{username}},请在下方输入框内输入4位数字验证码
|
||||||
|
</div>
|
||||||
|
<div class="d-flex justify-space-around">
|
||||||
|
<template v-for="(item, index) in ranges">
|
||||||
|
<div :class="{active: codeIndex === item}" :key="index" class="item">
|
||||||
|
<div v-if="codeIndex == item">
|
||||||
|
<span class="dot-line"></span>
|
||||||
|
</div>
|
||||||
|
{{ codeArr[index] ? codeArr[index] : ''}}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</v-container>
|
||||||
|
</v-content>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import router from "@/router";
|
||||||
|
import {mapActions} from 'vuex'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
maxlength: 4,
|
||||||
|
codeIndex: 1,
|
||||||
|
codeArr: [],
|
||||||
|
ranges: [1, 2, 3, 4],
|
||||||
|
disable: false,
|
||||||
|
username: '',
|
||||||
|
verify: '',
|
||||||
|
next_path: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
beforeRouteEnter(to, from, next) {
|
||||||
|
if (from.name != 'AuthLogin' || to.params.username == undefined) {
|
||||||
|
router.push({
|
||||||
|
name: 'AuthLogin'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
next()
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.username = this.$route.params.username;
|
||||||
|
this.parent_id = this.$route.params.parent_id;
|
||||||
|
this.next_path = this.$route.params.next_path
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
...mapActions(['doLogin']),
|
||||||
|
getVal(e) {
|
||||||
|
let value = e.target.value;
|
||||||
|
let arr = value.split('');
|
||||||
|
this.codeIndex = arr.length + 1;
|
||||||
|
this.codeArr = arr;
|
||||||
|
if (this.codeIndex > Number(this.maxlength)) {
|
||||||
|
this.disable = true;
|
||||||
|
this.sendVerifyToServer()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
sendVerifyToServer() {
|
||||||
|
this.$api.auth.loginByCode({
|
||||||
|
username: this.username,
|
||||||
|
parent_id: this.parent_id,
|
||||||
|
verify: this.verify
|
||||||
|
}).then(result => {
|
||||||
|
this.doLogin(result.access_token);
|
||||||
|
this.toast('登录成功');
|
||||||
|
setTimeout(() => {
|
||||||
|
if (this.next_path) {
|
||||||
|
router.push({
|
||||||
|
path: this.next_path
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
router.push({
|
||||||
|
name: 'Index'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, 1000)
|
||||||
|
}).catch(err => {
|
||||||
|
this.toast(err.message, 'warning');
|
||||||
|
this.disable = false;
|
||||||
|
this.codeIndex = 1;
|
||||||
|
this.codeArr = [];
|
||||||
|
this.verify = ''
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
@-webkit-keyframes twinkling {
|
||||||
|
0% {
|
||||||
|
opacity: 0.1;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes twinkling {
|
||||||
|
0% {
|
||||||
|
opacity: 0.1;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@-moz-keyframes twinkling {
|
||||||
|
0% {
|
||||||
|
opacity: 0.1;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.v-content {
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-image: url("../../assets/logo.png");
|
||||||
|
background-size: 200px;
|
||||||
|
background-position: bottom right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-box {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.d-flex {
|
||||||
|
margin-top: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hidden-input {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: -100%;
|
||||||
|
width: 300%;
|
||||||
|
color: #fff;
|
||||||
|
height: 100%;
|
||||||
|
text-align: left;
|
||||||
|
z-index: 9;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item {
|
||||||
|
border-bottom: 5px solid #BDBDBD;
|
||||||
|
height: 50px;
|
||||||
|
width: 50px;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 50px;
|
||||||
|
font-size: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dot-line {
|
||||||
|
display: block;
|
||||||
|
height: 20px;
|
||||||
|
width: 2px;
|
||||||
|
background: #BDBDBD;
|
||||||
|
margin-top: 15px;
|
||||||
|
margin-left: 24px;
|
||||||
|
-webkit-animation: twinkling 1s infinite;
|
||||||
|
-moz-animation: twinkling 1s infinite;
|
||||||
|
animation: twinkling 1s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item.active {
|
||||||
|
border-bottom-color: #1f3c84;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
140
VUE-WEB/lions-agent/src/pages/index/index.vue
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="pa-4 mbody"
|
||||||
|
>
|
||||||
|
<v-list
|
||||||
|
three-line
|
||||||
|
>
|
||||||
|
<v-list-item>
|
||||||
|
<v-list-item-content>
|
||||||
|
<v-list-item-title>尊敬的 {{ user.name }} 狮兄/狮姐:</v-list-item-title>
|
||||||
|
<v-list-item-subtitle>欢迎您参与中国狮子联会哈尔滨代表处第六次联席会议</v-list-item-subtitle>
|
||||||
|
</v-list-item-content>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item v-if="now_vote">
|
||||||
|
<v-list-item-content>
|
||||||
|
<v-list-item-title>当前投票轮次</v-list-item-title>
|
||||||
|
<v-list-item-subtitle>{{ now_vote }}</v-list-item-subtitle>
|
||||||
|
</v-list-item-content>
|
||||||
|
</v-list-item>
|
||||||
|
</v-list>
|
||||||
|
<v-simple-table>
|
||||||
|
<template v-slot:default>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="text-left">姓名</th>
|
||||||
|
<th class="text-left">手机号</th>
|
||||||
|
<th class="text-left">签到</th>
|
||||||
|
<th class="text-left">投票</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="item in desserts" :key="item.name">
|
||||||
|
<td>{{ item.name }}</td>
|
||||||
|
<td><a :href="`tel:` + item.mobile">{{ item.mobile }}</a></td>
|
||||||
|
<td>
|
||||||
|
<v-icon
|
||||||
|
v-if="item.sign"
|
||||||
|
color="green"
|
||||||
|
>
|
||||||
|
mdi-check
|
||||||
|
</v-icon>
|
||||||
|
<v-icon
|
||||||
|
v-else
|
||||||
|
color="red"
|
||||||
|
>
|
||||||
|
mdi-close
|
||||||
|
</v-icon>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<v-icon
|
||||||
|
v-if="item.vote"
|
||||||
|
color="green"
|
||||||
|
>
|
||||||
|
mdi-check
|
||||||
|
</v-icon>
|
||||||
|
<v-icon
|
||||||
|
v-else
|
||||||
|
color="red"
|
||||||
|
>
|
||||||
|
mdi-close
|
||||||
|
</v-icon>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</template>
|
||||||
|
</v-simple-table>
|
||||||
|
<v-btn
|
||||||
|
absolute
|
||||||
|
dark
|
||||||
|
fab
|
||||||
|
right
|
||||||
|
color="#1f3c84"
|
||||||
|
style="bottom: 16px"
|
||||||
|
@click="reload"
|
||||||
|
>
|
||||||
|
<v-icon>mdi-refresh</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
<!-- <v-btn-->
|
||||||
|
<!-- color="primary"-->
|
||||||
|
<!-- block-->
|
||||||
|
<!-- @click="logout"-->
|
||||||
|
<!-- >-->
|
||||||
|
<!-- 注销登录-->
|
||||||
|
<!-- </v-btn>-->
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {mapActions} from 'vuex'
|
||||||
|
import router from "@/router";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "index",
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
user: [],
|
||||||
|
desserts: [],
|
||||||
|
now_vote: '当前无投票'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$api.index.index().then(result => {
|
||||||
|
this.user = result.user
|
||||||
|
this.now_vote = result.now_vote
|
||||||
|
this.desserts = result.desserts
|
||||||
|
});
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
...mapActions(['doLogout']),
|
||||||
|
reload() {
|
||||||
|
this.$api.index.index().then(result => {
|
||||||
|
this.user = result.user
|
||||||
|
this.now_vote = result.now_vote
|
||||||
|
this.desserts = result.desserts
|
||||||
|
});
|
||||||
|
},
|
||||||
|
logout: function () {
|
||||||
|
this.toast('退出成功');
|
||||||
|
this.doLogout();
|
||||||
|
setTimeout(() => {
|
||||||
|
router.push({
|
||||||
|
name: 'AuthLogin'
|
||||||
|
})
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.cns {
|
||||||
|
background-image: url("../../assets/logo.png");
|
||||||
|
background-size: 100%;
|
||||||
|
background-position: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v-card__text p {
|
||||||
|
margin-bottom: 0px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
13
VUE-WEB/lions-agent/src/pages/layouts/common.vue
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<template>
|
||||||
|
<router-view></router-view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "common.layout"
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
34
VUE-WEB/lions-agent/src/plugins/toast/index.js
vendored
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import Toast from "./toast"
|
||||||
|
import Vue from 'vue'
|
||||||
|
|
||||||
|
let currentToast;
|
||||||
|
|
||||||
|
const index = (message, color) => {
|
||||||
|
if (currentToast) {
|
||||||
|
currentToast.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
currentToast = createToast({
|
||||||
|
Vue,
|
||||||
|
message,
|
||||||
|
color: color || 'success',
|
||||||
|
onClose: () => {
|
||||||
|
currentToast = null
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
function createToast({Vue, message, color, onClose}) {
|
||||||
|
const Constructor = Vue.extend(Toast)
|
||||||
|
|
||||||
|
let toast = new Constructor()
|
||||||
|
toast.message = message
|
||||||
|
toast.color = color
|
||||||
|
toast.timeout = 1000
|
||||||
|
toast.$mount();
|
||||||
|
toast.$on('close', onClose)
|
||||||
|
document.body.appendChild(toast.$el)
|
||||||
|
return toast
|
||||||
|
}
|
||||||
|
|
||||||
|
export default index
|
||||||
38
VUE-WEB/lions-agent/src/plugins/toast/toast.vue
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
<template>
|
||||||
|
<v-snackbar
|
||||||
|
:color="color"
|
||||||
|
:timeout="timeout"
|
||||||
|
:top="true"
|
||||||
|
v-model="show"
|
||||||
|
>
|
||||||
|
{{ message }}
|
||||||
|
</v-snackbar>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "toast.vue",
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
timeout: 1000,
|
||||||
|
color: 'success',
|
||||||
|
show: true,
|
||||||
|
message: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
console.log(this.color)
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
close() {
|
||||||
|
this.$el.remove();
|
||||||
|
this.$emit("close");
|
||||||
|
this.$destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
8
VUE-WEB/lions-agent/src/plugins/utils.js
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
const isWechat = () => {
|
||||||
|
let ua = navigator.userAgent.toLowerCase();
|
||||||
|
return (/micromessenger/.test(ua)) ? true : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
isWechat
|
||||||
|
}
|
||||||
13
VUE-WEB/lions-agent/src/plugins/vuetify.js
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import Vue from 'vue'
|
||||||
|
import Vuetify from 'vuetify'
|
||||||
|
import 'vuetify/dist/vuetify.min.css'
|
||||||
|
import '@mdi/font/css/materialdesignicons.css'
|
||||||
|
|
||||||
|
Vue.use(Vuetify)
|
||||||
|
|
||||||
|
export default new Vuetify({
|
||||||
|
icons: {
|
||||||
|
iconfont: 'mdi'
|
||||||
|
},
|
||||||
|
theme: {}
|
||||||
|
})
|
||||||
79
VUE-WEB/lions-agent/src/router/index.js
vendored
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
import VueRouter from "vue-router"
|
||||||
|
import store from "@/store"
|
||||||
|
|
||||||
|
const routes = [
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
name: 'Index',
|
||||||
|
component: () => import(/* webpackChunkName: "index" */ '@/pages/index/index'),
|
||||||
|
meta: {
|
||||||
|
title: '首页',
|
||||||
|
keepAlive: true,
|
||||||
|
requireAuth: true
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
path: '/auth',
|
||||||
|
component: () => import(/* webpackChunkName: "auth" */ '@/pages/layouts/common'),
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: 'login',
|
||||||
|
name: 'AuthLogin',
|
||||||
|
component: () => import(/* webpackChunkName: "auth" */ '@/pages/auth/login'),
|
||||||
|
meta: {
|
||||||
|
title: '登录'
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
path: 'verify',
|
||||||
|
name: 'AuthVerify',
|
||||||
|
component: () => import(/* webpackChunkName: "auth" */ '@/pages/auth/verify'),
|
||||||
|
meta: {
|
||||||
|
title: '验证手机号'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const router = new VueRouter({
|
||||||
|
mode: 'history',
|
||||||
|
routes
|
||||||
|
});
|
||||||
|
|
||||||
|
router.beforeEach((to, from, next) => {
|
||||||
|
let hasLogin = store.state.hasLogin;
|
||||||
|
console.log('ROUTE' + to.name + '||' + hasLogin)
|
||||||
|
if (to.name === 'AuthLogin') {
|
||||||
|
// 如果已经登录,直接跳转到首页去
|
||||||
|
if (hasLogin) {
|
||||||
|
next({
|
||||||
|
name: 'Index'
|
||||||
|
});
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let tk = localStorage.getItem('accessToken')
|
||||||
|
|
||||||
|
if (tk) {
|
||||||
|
store.commit('login', tk);
|
||||||
|
next({
|
||||||
|
name: 'Index'
|
||||||
|
});
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else if (to.meta.requireAuth === true) {
|
||||||
|
if (hasLogin === false) {
|
||||||
|
next({
|
||||||
|
name: 'AuthLogin',
|
||||||
|
params: {
|
||||||
|
next: to.path
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
store.state.showFooter = to.meta.showFooter || false;
|
||||||
|
|
||||||
|
next()
|
||||||
|
});
|
||||||
|
|
||||||
|
export default router
|
||||||
49
VUE-WEB/lions-agent/src/store/index.js
vendored
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import Vue from 'vue'
|
||||||
|
import Vuex from 'vuex'
|
||||||
|
|
||||||
|
Vue.use(Vuex);
|
||||||
|
|
||||||
|
const store = new Vuex.Store({
|
||||||
|
state: {
|
||||||
|
hasLogin: false,
|
||||||
|
accessToken: '',
|
||||||
|
userInfo: null,
|
||||||
|
},
|
||||||
|
mutations: {
|
||||||
|
login(state, token) {
|
||||||
|
state.hasLogin = true;
|
||||||
|
state.accessToken = token;
|
||||||
|
localStorage.setItem('accessToken', token)
|
||||||
|
},
|
||||||
|
logout(state) {
|
||||||
|
state.hasLogin = false;
|
||||||
|
state.accessToken = "";
|
||||||
|
state.userInfo = null;
|
||||||
|
localStorage.removeItem('accessToken')
|
||||||
|
localStorage.removeItem('userInfo')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getters: {},
|
||||||
|
actions: {
|
||||||
|
/**
|
||||||
|
* 登录并获取用户基础信息
|
||||||
|
* @param dispatch
|
||||||
|
* @param commit
|
||||||
|
* @param token
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async doLogin({commit}, token) {
|
||||||
|
commit('login', token)
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 登出方法
|
||||||
|
* @param commit
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async doLogout({commit}) {
|
||||||
|
commit('logout')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default store
|
||||||
3
VUE-WEB/lions-web/.browserslistrc
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
> 1%
|
||||||
|
last 2 versions
|
||||||
|
not dead
|
||||||
17
VUE-WEB/lions-web/.eslintrc.js
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
env: {
|
||||||
|
node: true
|
||||||
|
},
|
||||||
|
'extends': [
|
||||||
|
'plugin:vue/essential',
|
||||||
|
'eslint:recommended'
|
||||||
|
],
|
||||||
|
parserOptions: {
|
||||||
|
parser: 'babel-eslint'
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
|
||||||
|
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off'
|
||||||
|
}
|
||||||
|
}
|
||||||
21
VUE-WEB/lions-web/.gitignore
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
.DS_Store
|
||||||
|
node_modules
|
||||||
|
/dist
|
||||||
|
|
||||||
|
# local env files
|
||||||
|
.env.local
|
||||||
|
.env.*.local
|
||||||
|
|
||||||
|
# Log files
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.idea
|
||||||
|
.vscode
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
29
VUE-WEB/lions-web/README.md
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# lions
|
||||||
|
|
||||||
|
## Project setup
|
||||||
|
```
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
### Compiles and hot-reloads for development
|
||||||
|
```
|
||||||
|
npm run serve
|
||||||
|
```
|
||||||
|
|
||||||
|
### Compiles and minifies for production
|
||||||
|
```
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
### Run your tests
|
||||||
|
```
|
||||||
|
npm run test
|
||||||
|
```
|
||||||
|
|
||||||
|
### Lints and fixes files
|
||||||
|
```
|
||||||
|
npm run lint
|
||||||
|
```
|
||||||
|
|
||||||
|
### Customize configuration
|
||||||
|
See [Configuration Reference](https://cli.vuejs.org/config/).
|
||||||
5
VUE-WEB/lions-web/babel.config.js
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
module.exports = {
|
||||||
|
presets: [
|
||||||
|
'@vue/cli-plugin-babel/preset'
|
||||||
|
]
|
||||||
|
}
|
||||||
11980
VUE-WEB/lions-web/package-lock.json
generated
Normal file
31
VUE-WEB/lions-web/package.json
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"name": "lions",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"serve": "vue-cli-service serve",
|
||||||
|
"build": "vue-cli-service build",
|
||||||
|
"lint": "vue-cli-service lint"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"axios": "^0.19.2",
|
||||||
|
"core-js": "^3.6.4",
|
||||||
|
"vue": "^2.6.11",
|
||||||
|
"vue-router": "^3.1.6",
|
||||||
|
"vue-wechat-title": "^2.0.5",
|
||||||
|
"vuelidate": "^0.7.5",
|
||||||
|
"vuetify": "^2.2.26",
|
||||||
|
"vuex": "^3.1.3"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@mdi/font": "^5.1.45",
|
||||||
|
"@vue/cli-plugin-babel": "^4.3.0",
|
||||||
|
"@vue/cli-plugin-eslint": "^4.3.0",
|
||||||
|
"@vue/cli-service": "^4.3.0",
|
||||||
|
"babel-eslint": "^10.1.0",
|
||||||
|
"eslint": "^6.7.2",
|
||||||
|
"eslint-plugin-vue": "^6.2.2",
|
||||||
|
"material-design-icons-iconfont": "^5.0.1",
|
||||||
|
"vue-template-compiler": "^2.6.11"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
VUE-WEB/lions-web/public/favicon.ico
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
17
VUE-WEB/lions-web/public/index.html
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, minimum-scale=1.0, maximum-scale=1.0, initial-scale=1.0, user-scalable=no">
|
||||||
|
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||||
|
<title>推举系统</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<noscript>
|
||||||
|
欢迎使用中国狮子联会哈尔滨代表处推举系统
|
||||||
|
</noscript>
|
||||||
|
<div id="app"></div>
|
||||||
|
<!-- built files will be auto injected -->
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
40
VUE-WEB/lions-web/src/App.vue
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
<template>
|
||||||
|
<v-app>
|
||||||
|
<router-view
|
||||||
|
v-wechat-title="$route.meta.title"
|
||||||
|
></router-view>
|
||||||
|
</v-app>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'app',
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
#app {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: #fdfdfd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hasFooter {
|
||||||
|
padding-bottom: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 水平居中
|
||||||
|
*/
|
||||||
|
.pack-center {
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
-webkit-box-pack: center;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
56
VUE-WEB/lions-web/src/api/axios.js
vendored
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import axios from 'axios'
|
||||||
|
import store from "@/store"
|
||||||
|
import router from "@/router";
|
||||||
|
// axios 配置
|
||||||
|
axios.defaults.timeout = 3000;
|
||||||
|
axios.defaults.baseURL = 'http://api.lions-vote.cnskl.com/api/';
|
||||||
|
|
||||||
|
const axiosConf = (config) => {
|
||||||
|
// config.headers.Authorization = store.state.accessToken;
|
||||||
|
config.headers.Authorization = localStorage.getItem('accessToken')
|
||||||
|
return config;
|
||||||
|
};
|
||||||
|
|
||||||
|
axios.interceptors.request.use(axiosConf, err => {
|
||||||
|
return Promise.reject(err);
|
||||||
|
});
|
||||||
|
|
||||||
|
axios.interceptors.response.use(async (response) => {
|
||||||
|
let data = {};
|
||||||
|
let code = Number(response.data.status_code);
|
||||||
|
if (code === 401 || code == 0) {
|
||||||
|
if (response.data.status == 'EXCEPTION') {
|
||||||
|
return Promise.reject(response.data)
|
||||||
|
}
|
||||||
|
if (response.headers.authorization) {
|
||||||
|
await store.commit('login', response.headers.authorization);
|
||||||
|
|
||||||
|
response.config.headers.Authorization = response.headers.authorization;
|
||||||
|
const result = await axios.request(axiosConf(response.config));
|
||||||
|
if (result) {
|
||||||
|
data = result;
|
||||||
|
}
|
||||||
|
} else if (router.currentRoute.name != 'AuthLogin') {
|
||||||
|
setTimeout(() => {
|
||||||
|
router.replace({name: 'AuthLogin', query: {redirect: router.currentRoute.fullPath}});
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
} else if (code === 200) {
|
||||||
|
data = response.data.data;
|
||||||
|
} else if (code === 403) {
|
||||||
|
await router.replace({path: '/403', query: {redirect: router.currentRoute.fullPath}});
|
||||||
|
} else if (code === 410) {
|
||||||
|
setTimeout(() => {
|
||||||
|
router.replace({name: 'AuthLogin', query: {redirect: router.currentRoute.fullPath}});
|
||||||
|
}, 1200);
|
||||||
|
} else if (code == 404) {
|
||||||
|
return Promise.reject(response.data);
|
||||||
|
} else {
|
||||||
|
return Promise.reject(response.data);
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}, (error) => {
|
||||||
|
return Promise.reject(error.response.data);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default axios;
|
||||||
11
VUE-WEB/lions-web/src/api/index.js
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import article from './interfaces/article'
|
||||||
|
import auth from './interfaces/auth'
|
||||||
|
import index from './interfaces/index'
|
||||||
|
import vote from './interfaces/vote'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
article,
|
||||||
|
index,
|
||||||
|
auth,
|
||||||
|
vote
|
||||||
|
}
|
||||||
11
VUE-WEB/lions-web/src/api/interfaces/article.js
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import axios from '../axios'
|
||||||
|
|
||||||
|
const index = () => axios.get('articles')
|
||||||
|
const show = (id) => axios.get('articles/' + id)
|
||||||
|
const audit = (id) => axios.post('articles/' + id)
|
||||||
|
|
||||||
|
export default {
|
||||||
|
index,
|
||||||
|
show,
|
||||||
|
audit
|
||||||
|
}
|
||||||
13
VUE-WEB/lions-web/src/api/interfaces/auth.js
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import axios from '../axios'
|
||||||
|
|
||||||
|
const license = () => axios.get('auth/license')
|
||||||
|
|
||||||
|
const code = (data) => axios.post('auth/code/login', data)
|
||||||
|
|
||||||
|
const loginByCode = (data) => axios.post('auth/loginByCode', data)
|
||||||
|
|
||||||
|
export default {
|
||||||
|
license,
|
||||||
|
code,
|
||||||
|
loginByCode
|
||||||
|
}
|
||||||
10
VUE-WEB/lions-web/src/api/interfaces/index.js
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import axios from '../axios'
|
||||||
|
|
||||||
|
const index = () => axios.get('index')
|
||||||
|
|
||||||
|
const sign = () => axios.post('sign')
|
||||||
|
|
||||||
|
export default {
|
||||||
|
index,
|
||||||
|
sign
|
||||||
|
}
|
||||||
12
VUE-WEB/lions-web/src/api/interfaces/vote.js
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import axios from '../axios'
|
||||||
|
|
||||||
|
const index = () => axios.get('vote')
|
||||||
|
const show = (id) => axios.get('vote/' + id)
|
||||||
|
|
||||||
|
const submit = (id, data) => axios.post('vote/' + id, {data: data})
|
||||||
|
|
||||||
|
export default {
|
||||||
|
index,
|
||||||
|
show,
|
||||||
|
submit
|
||||||
|
}
|
||||||
BIN
VUE-WEB/lions-web/src/assets/back.png
Normal file
|
After Width: | Height: | Size: 169 KiB |
BIN
VUE-WEB/lions-web/src/assets/back_1.png
Normal file
|
After Width: | Height: | Size: 110 KiB |
BIN
VUE-WEB/lions-web/src/assets/back_2.png
Normal file
|
After Width: | Height: | Size: 201 KiB |
BIN
VUE-WEB/lions-web/src/assets/logo.png
Normal file
|
After Width: | Height: | Size: 59 KiB |
28
VUE-WEB/lions-web/src/main.js
vendored
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import Vue from 'vue'
|
||||||
|
import App from './App.vue'
|
||||||
|
import VueRouter from 'vue-router'
|
||||||
|
import router from './router'
|
||||||
|
import store from './store'
|
||||||
|
import vuetify from '@/plugins/vuetify'
|
||||||
|
import api from './api'
|
||||||
|
import toast from './plugins/toast'
|
||||||
|
|
||||||
|
Vue.use(VueRouter)
|
||||||
|
Vue.use(require('vue-wechat-title'))
|
||||||
|
|
||||||
|
Vue.config.productionTip = false
|
||||||
|
Vue.prototype.$api = api
|
||||||
|
Vue.prototype.toast = toast;
|
||||||
|
Vue.prototype.jump = (name, params, query) => {
|
||||||
|
router.push({
|
||||||
|
name: name,
|
||||||
|
params: params,
|
||||||
|
query: query
|
||||||
|
})
|
||||||
|
}
|
||||||
|
new Vue({
|
||||||
|
vuetify,
|
||||||
|
router,
|
||||||
|
store,
|
||||||
|
render: h => h(App)
|
||||||
|
}).$mount('#app')
|
||||||
177
VUE-WEB/lions-web/src/pages/auth/login.vue
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
<template>
|
||||||
|
<v-container class="login">
|
||||||
|
<div class="login-title">登录</div>
|
||||||
|
<div class="login-subhead grey--text">
|
||||||
|
心连心服务队-推举系统
|
||||||
|
</div>
|
||||||
|
<v-form
|
||||||
|
@submit="formSubmit"
|
||||||
|
v-model="valid"
|
||||||
|
>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" md="12">
|
||||||
|
<v-text-field
|
||||||
|
:counter="11"
|
||||||
|
:rules="usernameRules"
|
||||||
|
clearable
|
||||||
|
label="手机号"
|
||||||
|
prefix="+86"
|
||||||
|
required
|
||||||
|
type="tel"
|
||||||
|
v-model="username"
|
||||||
|
></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<!-- <v-col-->
|
||||||
|
<!-- class="caption grey--text"-->
|
||||||
|
<!-- cols="12"-->
|
||||||
|
<!-- md="12"-->
|
||||||
|
<!-- >-->
|
||||||
|
<!-- 点击"下一步"即表示已阅读并同意并自愿遵守-->
|
||||||
|
<!-- <span-->
|
||||||
|
<!-- @click="ishow(1)"-->
|
||||||
|
<!-- class="blue--text text--darken-4"-->
|
||||||
|
<!-- >《服务使用协议》</span>以及-->
|
||||||
|
<!-- <span-->
|
||||||
|
<!-- @click="ishow(2)"-->
|
||||||
|
<!-- class="blue--text text--darken-4"-->
|
||||||
|
<!-- >《隐私声明》</span>-->
|
||||||
|
<!-- </v-col>-->
|
||||||
|
<v-col>
|
||||||
|
<v-btn
|
||||||
|
:disabled="!valid"
|
||||||
|
block
|
||||||
|
style="color: #ffffff"
|
||||||
|
color="#1f3c84"
|
||||||
|
large
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
|
下一步
|
||||||
|
</v-btn>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-form>
|
||||||
|
|
||||||
|
<v-dialog
|
||||||
|
v-model="dialog"
|
||||||
|
width="500"
|
||||||
|
>
|
||||||
|
<v-card>
|
||||||
|
<v-card-title
|
||||||
|
class="grey lighten-2"
|
||||||
|
>
|
||||||
|
{{dia.title}}
|
||||||
|
</v-card-title>
|
||||||
|
|
||||||
|
<v-card-text class="pt-4" v-html="dia.content">
|
||||||
|
</v-card-text>
|
||||||
|
|
||||||
|
<v-divider></v-divider>
|
||||||
|
|
||||||
|
<v-card-actions>
|
||||||
|
<div class="flex-grow-1"></div>
|
||||||
|
<v-btn
|
||||||
|
@click="dialog = false"
|
||||||
|
color="#1f3c84"
|
||||||
|
text
|
||||||
|
>
|
||||||
|
已阅读
|
||||||
|
</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
|
|
||||||
|
</v-container>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import router from '@/router'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "login",
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
valid: false,
|
||||||
|
username: '',
|
||||||
|
usernameRules: [
|
||||||
|
v => !!v || '手机号必须填写',
|
||||||
|
v => (v && v.length == 11) || '手机号码必须是11位',
|
||||||
|
v => /^1[3-9][0-9]{9}$/.test(v) || '手机号码格式不正确'
|
||||||
|
],
|
||||||
|
dialog: false,
|
||||||
|
dia: {
|
||||||
|
title: '',
|
||||||
|
content: ''
|
||||||
|
},
|
||||||
|
agreement: '',
|
||||||
|
privacy: '',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$api.auth.license().then(result => {
|
||||||
|
this.agreement = result.agreement;
|
||||||
|
this.privacy = result.privacy
|
||||||
|
});
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
formSubmit(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
this.$api.auth.code({
|
||||||
|
username: this.username
|
||||||
|
}).then(() => {
|
||||||
|
router.push({
|
||||||
|
name: 'AuthVerify',
|
||||||
|
params: {
|
||||||
|
username: this.username
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}).catch(err => {
|
||||||
|
this.toast(err.message, 'warning')
|
||||||
|
})
|
||||||
|
},
|
||||||
|
ishow(n) {
|
||||||
|
this.dialog = !this.dialog;
|
||||||
|
if (n == 1) {
|
||||||
|
this.dia = {
|
||||||
|
title: '用户协议',
|
||||||
|
content: this.agreement
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.dia = {
|
||||||
|
title: '隐私声明',
|
||||||
|
content: this.privacy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
wechatLogin() {
|
||||||
|
let uri = window.location.origin + '/auth/wechat';
|
||||||
|
this.$api.auth.wechat_url({
|
||||||
|
uri: uri,
|
||||||
|
scope: 'snsapi_base'
|
||||||
|
}).then(res => {
|
||||||
|
window.location.href = res.url
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.login {
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-image: url("../../assets/logo.png");
|
||||||
|
background-size: 200px;
|
||||||
|
background-position: center;
|
||||||
|
padding: 20vh 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-title{
|
||||||
|
position: relative;
|
||||||
|
font-size: 28px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-subhead{
|
||||||
|
padding-bottom: 5vh;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
190
VUE-WEB/lions-web/src/pages/auth/verify.vue
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
<template>
|
||||||
|
<v-container class="verify">
|
||||||
|
<input
|
||||||
|
:disabled="disable"
|
||||||
|
@input="getVal"
|
||||||
|
autofocus="autofocus"
|
||||||
|
class="hidden-input"
|
||||||
|
type="number"
|
||||||
|
v-model="verify"
|
||||||
|
>
|
||||||
|
<div class="login-title">验证码</div>
|
||||||
|
<div class="login-subhead grey--text">
|
||||||
|
验证码已发送至{{username}},请在下方输入框内输入4位数字验证码
|
||||||
|
</div>
|
||||||
|
<div class="code-box">
|
||||||
|
<div class="d-flex justify-space-around">
|
||||||
|
<template v-for="(item, index) in ranges">
|
||||||
|
<div :class="{active: codeIndex === item}" :key="index" class="item">
|
||||||
|
<div v-if="codeIndex == item">
|
||||||
|
<span class="dot-line"></span>
|
||||||
|
</div>
|
||||||
|
{{ codeArr[index] ? codeArr[index] : ''}}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</v-container>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import router from "@/router";
|
||||||
|
import {mapActions} from 'vuex'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
maxlength: 4,
|
||||||
|
codeIndex: 1,
|
||||||
|
codeArr: [],
|
||||||
|
ranges: [1, 2, 3, 4],
|
||||||
|
disable: false,
|
||||||
|
username: '',
|
||||||
|
verify: '',
|
||||||
|
next_path: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
beforeRouteEnter(to, from, next) {
|
||||||
|
if (from.name != 'AuthLogin' || to.params.username == undefined) {
|
||||||
|
router.push({
|
||||||
|
name: 'AuthLogin'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
next()
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.username = this.$route.params.username;
|
||||||
|
this.parent_id = this.$route.params.parent_id;
|
||||||
|
this.next_path = this.$route.params.next_path
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
...mapActions(['doLogin']),
|
||||||
|
getVal(e) {
|
||||||
|
let value = e.target.value;
|
||||||
|
let arr = value.split('');
|
||||||
|
this.codeIndex = arr.length + 1;
|
||||||
|
this.codeArr = arr;
|
||||||
|
if (this.codeIndex > Number(this.maxlength)) {
|
||||||
|
this.disable = true;
|
||||||
|
this.sendVerifyToServer()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
sendVerifyToServer() {
|
||||||
|
this.$api.auth.loginByCode({
|
||||||
|
username: this.username,
|
||||||
|
parent_id: this.parent_id,
|
||||||
|
verify: this.verify
|
||||||
|
}).then(result => {
|
||||||
|
this.doLogin(result.access_token);
|
||||||
|
this.toast('登录成功');
|
||||||
|
setTimeout(() => {
|
||||||
|
if (this.next_path) {
|
||||||
|
router.push({
|
||||||
|
path: this.next_path
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
router.push({
|
||||||
|
name: 'Index'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, 1000)
|
||||||
|
}).catch(err => {
|
||||||
|
this.toast(err.message, 'warning');
|
||||||
|
this.disable = false;
|
||||||
|
this.codeIndex = 1;
|
||||||
|
this.codeArr = [];
|
||||||
|
this.verify = ''
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
@-webkit-keyframes twinkling {
|
||||||
|
0% {
|
||||||
|
opacity: 0.1;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes twinkling {
|
||||||
|
0% {
|
||||||
|
opacity: 0.1;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@-moz-keyframes twinkling {
|
||||||
|
0% {
|
||||||
|
opacity: 0.1;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.verify{
|
||||||
|
position: relative;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-image: url("../../assets/logo.png");
|
||||||
|
background-size: 200px;
|
||||||
|
background-position: bottom right;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
padding: 20vh 60px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-title{
|
||||||
|
position: relative;
|
||||||
|
font-size: 28px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-subhead{
|
||||||
|
padding-bottom: 5vh;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hidden-input {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
color: #fff;
|
||||||
|
text-align: left;
|
||||||
|
z-index: 9;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item {
|
||||||
|
border-bottom: 5px solid #BDBDBD;
|
||||||
|
height: 50px;
|
||||||
|
width: 50px;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 50px;
|
||||||
|
font-size: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dot-line {
|
||||||
|
display: block;
|
||||||
|
height: 20px;
|
||||||
|
width: 2px;
|
||||||
|
background: #BDBDBD;
|
||||||
|
margin-top: 15px;
|
||||||
|
margin-left: 24px;
|
||||||
|
-webkit-animation: twinkling 1s infinite;
|
||||||
|
-moz-animation: twinkling 1s infinite;
|
||||||
|
animation: twinkling 1s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item.active {
|
||||||
|
border-bottom-color: #1f3c84;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
65
VUE-WEB/lions-web/src/pages/files/index.vue
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="pt-4 pl-4 pr-4"
|
||||||
|
>
|
||||||
|
<v-card
|
||||||
|
class="mb-4"
|
||||||
|
tile
|
||||||
|
>
|
||||||
|
<v-card-title>
|
||||||
|
文件审阅
|
||||||
|
</v-card-title>
|
||||||
|
<v-divider></v-divider>
|
||||||
|
<v-list three-line subheader>
|
||||||
|
<v-list-item
|
||||||
|
v-for="(item, i) in files"
|
||||||
|
:key="i"
|
||||||
|
@click="jump('FileShow', {id: item.id})"
|
||||||
|
>
|
||||||
|
<v-list-item-content>
|
||||||
|
<v-list-item-title>{{ item.title }}</v-list-item-title>
|
||||||
|
<v-list-item-subtitle>{{ item.desc }}</v-list-item-subtitle>
|
||||||
|
</v-list-item-content>
|
||||||
|
<v-list-item-action v-if="item.is">
|
||||||
|
<v-icon
|
||||||
|
color="red"
|
||||||
|
>
|
||||||
|
mdi-check
|
||||||
|
</v-icon>
|
||||||
|
</v-list-item-action>
|
||||||
|
</v-list-item>
|
||||||
|
</v-list>
|
||||||
|
</v-card>
|
||||||
|
<v-btn
|
||||||
|
absolute
|
||||||
|
dark
|
||||||
|
fab
|
||||||
|
right
|
||||||
|
color="#1f3c84"
|
||||||
|
style="bottom: 16px"
|
||||||
|
@click="jump('Index')"
|
||||||
|
>
|
||||||
|
<v-icon>mdi-home</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "index",
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
files: [],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$api.article.index().then(result => {
|
||||||
|
this.files = result.files
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
51
VUE-WEB/lions-web/src/pages/files/show.vue
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="pa-4"
|
||||||
|
v-if="loaded"
|
||||||
|
>
|
||||||
|
<h2>{{ detail.title }}</h2>
|
||||||
|
<v-subheader class="text-right">{{ detail.desc }}</v-subheader>
|
||||||
|
<div v-html="detail.content"></div>
|
||||||
|
<v-btn
|
||||||
|
color="primary"
|
||||||
|
block
|
||||||
|
@click="auditPost"
|
||||||
|
:disabled="audited"
|
||||||
|
>
|
||||||
|
审阅完毕
|
||||||
|
</v-btn>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "index",
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
id: 0,
|
||||||
|
loaded: false,
|
||||||
|
detail: [],
|
||||||
|
audited: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.id = this.$route.params.id;
|
||||||
|
this.$api.article.show(this.id).then(result => {
|
||||||
|
this.detail = result.article
|
||||||
|
this.audited = result.audited
|
||||||
|
this.loaded = true
|
||||||
|
})
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
auditPost: function () {
|
||||||
|
this.$api.article.audit(this.id).then(() => {
|
||||||
|
this.$router.back()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
189
VUE-WEB/lions-web/src/pages/index/index.vue
Normal file
@@ -0,0 +1,189 @@
|
|||||||
|
<template>
|
||||||
|
|
||||||
|
<div class="mian">
|
||||||
|
<div class="footer">
|
||||||
|
<div class="footer-title">尊敬的 {{ user.name }} 狮友</div>
|
||||||
|
<div class="footer-subtitle">欢迎您参加中国狮子联会哈尔滨心连心服务队2020-2021年度队长、副队长、秘书和队长团队成员推举会议</div>
|
||||||
|
<div v-if="dested">
|
||||||
|
<v-btn
|
||||||
|
block
|
||||||
|
>
|
||||||
|
投票已结束,您的投票记录已销毁
|
||||||
|
</v-btn>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<div v-if="vote">
|
||||||
|
<v-btn
|
||||||
|
block
|
||||||
|
x-large
|
||||||
|
class="footer-btn"
|
||||||
|
@click="jump('VoteIndex')"
|
||||||
|
>
|
||||||
|
开始投票
|
||||||
|
</v-btn>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<v-btn
|
||||||
|
block
|
||||||
|
x-large
|
||||||
|
class="footer-btn"
|
||||||
|
@click="reload"
|
||||||
|
>
|
||||||
|
等待开始
|
||||||
|
</v-btn>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<v-dialog
|
||||||
|
v-model="dialog"
|
||||||
|
width="500"
|
||||||
|
persistent
|
||||||
|
>
|
||||||
|
<v-card
|
||||||
|
class="cns"
|
||||||
|
tile
|
||||||
|
>
|
||||||
|
<v-card-title>
|
||||||
|
<div class="flex-grow-1"></div>
|
||||||
|
签署承诺书及签到环节
|
||||||
|
<div class="flex-grow-1"></div>
|
||||||
|
</v-card-title>
|
||||||
|
<v-card-text v-html="html">
|
||||||
|
</v-card-text>
|
||||||
|
|
||||||
|
<v-divider></v-divider>
|
||||||
|
|
||||||
|
<v-card-actions>
|
||||||
|
<div class="flex-grow-1"></div>
|
||||||
|
<v-btn
|
||||||
|
@click="signPost"
|
||||||
|
color="primary"
|
||||||
|
text
|
||||||
|
>
|
||||||
|
我同意并签到
|
||||||
|
</v-btn>
|
||||||
|
<div class="flex-grow-1"></div>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
|
<!-- <v-btn-->
|
||||||
|
<!-- color="primary"-->
|
||||||
|
<!-- block-->
|
||||||
|
<!-- @click="logout"-->
|
||||||
|
<!-- >-->
|
||||||
|
<!-- 注销登录,正式版本会取消-->
|
||||||
|
<!-- </v-btn>-->
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {mapActions} from 'vuex'
|
||||||
|
import router from "@/router";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "index",
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
user: [],
|
||||||
|
dialog: false,
|
||||||
|
vote: false,
|
||||||
|
dested: false,
|
||||||
|
html: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$api.index.index().then(result => {
|
||||||
|
this.user = result.user
|
||||||
|
this.html = result.license
|
||||||
|
this.dialog = !result.sign
|
||||||
|
this.vote = result.vote
|
||||||
|
this.dested = result.dested
|
||||||
|
});
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
...mapActions(['doLogout']),
|
||||||
|
reload() {
|
||||||
|
this.$api.index.index().then(result => {
|
||||||
|
this.user = result.user
|
||||||
|
this.html = result.license
|
||||||
|
this.dialog = !result.sign
|
||||||
|
this.vote = result.vote
|
||||||
|
this.dested = result.dested
|
||||||
|
});
|
||||||
|
},
|
||||||
|
signPost: function () {
|
||||||
|
this.$api.index.sign().then(() => {
|
||||||
|
this.dialog = false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
logout: function () {
|
||||||
|
this.toast('退出成功');
|
||||||
|
this.doLogout();
|
||||||
|
setTimeout(() => {
|
||||||
|
router.push({
|
||||||
|
name: 'AuthLogin'
|
||||||
|
})
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.mian {
|
||||||
|
background-color: #1f3c84;
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
background-image: url("../../assets/back_2.png");
|
||||||
|
background-position: top center;
|
||||||
|
background-size: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
position: absolute;
|
||||||
|
top: 40vh;
|
||||||
|
width: 100vw;
|
||||||
|
padding: 30px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-title,
|
||||||
|
.footer-subtitle {
|
||||||
|
text-align: center;
|
||||||
|
text-shadow: 0 2px 2px rgba(0, 0, 0, .5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-title {
|
||||||
|
font-weight: bold;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-subtitle {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-btn {
|
||||||
|
width: 100%;
|
||||||
|
height: 45px;
|
||||||
|
background: white;
|
||||||
|
color: #1f3c84;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cns {
|
||||||
|
background-image: url("../../assets/logo.png");
|
||||||
|
background-size: 100%;
|
||||||
|
background-position: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v-card__text p {
|
||||||
|
margin-bottom: 0px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
13
VUE-WEB/lions-web/src/pages/layouts/common.vue
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<template>
|
||||||
|
<router-view></router-view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "common.layout"
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
331
VUE-WEB/lions-web/src/pages/vote/diff.vue
Normal file
@@ -0,0 +1,331 @@
|
|||||||
|
<template>
|
||||||
|
<div v-if="!loaded">
|
||||||
|
<v-dialog
|
||||||
|
v-model="lock"
|
||||||
|
persistent
|
||||||
|
width="300"
|
||||||
|
>
|
||||||
|
<v-card
|
||||||
|
color="primary"
|
||||||
|
dark
|
||||||
|
>
|
||||||
|
<v-card-text>
|
||||||
|
<p class="pt-4">数据加载中,请稍后</p>
|
||||||
|
<v-progress-linear
|
||||||
|
indeterminate
|
||||||
|
color="white"
|
||||||
|
class="mb-0"
|
||||||
|
></v-progress-linear>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
|
</div>
|
||||||
|
<v-container class="mian" v-else>
|
||||||
|
<!-- 已投票提示信息 -->
|
||||||
|
<div class="mian-shoeToast" v-if="lock">
|
||||||
|
<div class="mian-shoeToast-icon">
|
||||||
|
<v-icon color="#1f3c84" size="58">{{ message.icon }}</v-icon>
|
||||||
|
</div>
|
||||||
|
<div class="mian-shoeToast-title">{{ message.msg }}</div>
|
||||||
|
<div class="mian-shoeToast-content">{{ message.tips }}</div>
|
||||||
|
<div class="mian-shoeToast-btn" @click="jump('Index')">返回主页</div>
|
||||||
|
</div>
|
||||||
|
<!-- 投票列表 -->
|
||||||
|
<div v-else>
|
||||||
|
<v-dialog
|
||||||
|
v-model="loading"
|
||||||
|
persistent
|
||||||
|
width="300"
|
||||||
|
>
|
||||||
|
<v-card
|
||||||
|
color="primary"
|
||||||
|
dark
|
||||||
|
>
|
||||||
|
<v-card-text>
|
||||||
|
<p class="pt-4">数据提交中,请稍后</p>
|
||||||
|
<v-progress-linear
|
||||||
|
indeterminate
|
||||||
|
color="white"
|
||||||
|
class="mb-0"
|
||||||
|
></v-progress-linear>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
|
|
||||||
|
<div class="list">
|
||||||
|
<div class="vote-title">
|
||||||
|
中国狮子联会哈尔滨心连心服务队
|
||||||
|
</div>
|
||||||
|
<div style="color: #ffffff">
|
||||||
|
2020-2021年度
|
||||||
|
</div>
|
||||||
|
<div class="vote-title">{{ info.title }}</div>
|
||||||
|
|
||||||
|
<div class="list-card" v-for="(item, index) in list" :key="index">
|
||||||
|
<v-img
|
||||||
|
:src="item.cover"
|
||||||
|
width="110"
|
||||||
|
height="160"
|
||||||
|
position="top center"
|
||||||
|
style="position: absolute"
|
||||||
|
>
|
||||||
|
</v-img>
|
||||||
|
<div class="list-card-content">
|
||||||
|
<div class="list-card-content-name">{{ item.name }}</div>
|
||||||
|
<div class="list-card-content-item"><label>参选职务:</label>{{ item.desc }}</div>
|
||||||
|
<!-- <div class="list-card-content-item"><label>服务队:</label>{{ item.desc2 }}</div>-->
|
||||||
|
<!-- :disabled="tomax && !(item.id in selected)"-->
|
||||||
|
|
||||||
|
<v-checkbox
|
||||||
|
v-model="selected"
|
||||||
|
:value="item.id"
|
||||||
|
class="radioGroup"
|
||||||
|
:disabled="tomax && !inArray(item.id)"
|
||||||
|
>
|
||||||
|
<template v-slot:label>
|
||||||
|
<div class="list-radio-text">赞成</div>
|
||||||
|
</template>
|
||||||
|
</v-checkbox>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<v-dialog v-model="sure" persistent>
|
||||||
|
<v-card>
|
||||||
|
<v-card-title>确认提交</v-card-title>
|
||||||
|
<!-- <v-card-text>-->
|
||||||
|
<!-- 确认您选择无误,立即提交么?-->
|
||||||
|
<!-- </v-card-text>-->
|
||||||
|
<v-card-actions>
|
||||||
|
<v-btn color="primary" text @click="sure=false">我再想想</v-btn>
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
<v-btn color="primary" @click="submitForm">确认提交</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
|
<div class="list-footer">
|
||||||
|
<div class="list-footer-text">最多可选{{ info.max }}项,您已经选择{{ selected.length }}项</div>
|
||||||
|
<v-btn
|
||||||
|
class="list-footer-btn"
|
||||||
|
:disabled="disable"
|
||||||
|
@click="sure = true"
|
||||||
|
>
|
||||||
|
提交推举票
|
||||||
|
</v-btn>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</v-container>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "index",
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
loaded: false,
|
||||||
|
id: 0,
|
||||||
|
info: [],
|
||||||
|
list: [],
|
||||||
|
submitData: [],
|
||||||
|
lock: true,
|
||||||
|
selected: [],
|
||||||
|
disable: true,
|
||||||
|
loading: false,
|
||||||
|
message: '',
|
||||||
|
tomax: false,
|
||||||
|
sure: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
selected: function () {
|
||||||
|
if (this.selected.length >= this.info.max) {
|
||||||
|
this.tomax = true
|
||||||
|
} else {
|
||||||
|
this.tomax = false
|
||||||
|
}
|
||||||
|
if (this.selected.length > 0 && this.selected.length <= this.info.max) {
|
||||||
|
this.disable = false;
|
||||||
|
} else {
|
||||||
|
this.disable = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.id = this.$route.params.id;
|
||||||
|
this.$api.vote.show(this.id).then(result => {
|
||||||
|
this.lock = result.lock
|
||||||
|
this.info = result.info
|
||||||
|
this.list = result.list
|
||||||
|
this.loaded = true
|
||||||
|
})
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
inArray: function (id) {
|
||||||
|
for (let i in this.selected) {
|
||||||
|
if (this.selected[i] == id) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
submitForm: function (e) {
|
||||||
|
e.preventDefault()
|
||||||
|
this.loading = true
|
||||||
|
this.$api.vote.submit(this.id, this.selected).then((res) => {
|
||||||
|
this.message = res
|
||||||
|
this.loading = false
|
||||||
|
this.lock = true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.vote-title {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 20px;
|
||||||
|
color: white;
|
||||||
|
padding-top: 20px;
|
||||||
|
text-shadow: 0 2px 2px rgba(0, 0, 0, .5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mian {
|
||||||
|
min-height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
background-color: #1f3c84;
|
||||||
|
background-image: url("../../assets/back_1.png");
|
||||||
|
background-position: top center;
|
||||||
|
background-size: 100%;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 提示信息 */
|
||||||
|
.mian-shoeToast {
|
||||||
|
margin-top: 10vh;
|
||||||
|
margin-left: 30px;
|
||||||
|
height: 80vh;
|
||||||
|
width: calc(100vw - 60px);
|
||||||
|
background: white;
|
||||||
|
border-radius: 6px;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
-webkit-box-pack: center;
|
||||||
|
padding: 30px 20px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mian-shoeToast-icon {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mian-shoeToast-title {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #1f3c84;
|
||||||
|
padding-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mian-shoeToast-content {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mian-shoeToast-btn {
|
||||||
|
height: 45px;
|
||||||
|
background: #1f3c84;
|
||||||
|
color: white;
|
||||||
|
line-height: 45px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 6px;
|
||||||
|
margin-top: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 列表 */
|
||||||
|
.list-header b {
|
||||||
|
color: #1f3c84;
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-footer {
|
||||||
|
background: #1f3c84;
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
padding: 15px;
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-footer-text {
|
||||||
|
text-align: center;
|
||||||
|
color: white;
|
||||||
|
line-height: 20px;
|
||||||
|
padding-bottom: 15px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-footer-btn {
|
||||||
|
font-size: 1rem;
|
||||||
|
height: 45px !important;
|
||||||
|
line-height: 45px;
|
||||||
|
width: 100%;
|
||||||
|
background: white;
|
||||||
|
color: #1f3c84;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list {
|
||||||
|
padding: 0px 15px 110px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-card {
|
||||||
|
background: white;
|
||||||
|
margin-top: 15px;
|
||||||
|
border-radius: 6px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 10px;
|
||||||
|
position: relative;
|
||||||
|
min-height: 180px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-card-content {
|
||||||
|
margin-left: 130px;
|
||||||
|
height: 160px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.radioGroup {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
bottom: -20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-card-content-name {
|
||||||
|
font-size: 22px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #1f3c84;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-card-content-item {
|
||||||
|
padding-top: 5px;
|
||||||
|
font-size: 14px;
|
||||||
|
position: relative;
|
||||||
|
padding-left: 80px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-card-content-item label {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 5px;
|
||||||
|
color: gray;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-radio-text {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
315
VUE-WEB/lions-web/src/pages/vote/equal.vue
Normal file
@@ -0,0 +1,315 @@
|
|||||||
|
<template>
|
||||||
|
<div v-if="!loaded">
|
||||||
|
<v-dialog
|
||||||
|
v-model="lock"
|
||||||
|
persistent
|
||||||
|
width="300"
|
||||||
|
>
|
||||||
|
<v-card
|
||||||
|
color="primary"
|
||||||
|
dark
|
||||||
|
>
|
||||||
|
<v-card-text>
|
||||||
|
<p class="pt-4">数据加载中,请稍后</p>
|
||||||
|
<v-progress-linear
|
||||||
|
indeterminate
|
||||||
|
color="white"
|
||||||
|
class="mb-0"
|
||||||
|
></v-progress-linear>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
|
</div>
|
||||||
|
<v-container class="mian" v-else>
|
||||||
|
<!-- 已投票提示信息 -->
|
||||||
|
<div class="mian-shoeToast" v-if="lock">
|
||||||
|
<div class="mian-shoeToast-icon">
|
||||||
|
<v-icon color="#1f3c84" size="58">{{ message.icon }}</v-icon>
|
||||||
|
</div>
|
||||||
|
<div class="mian-shoeToast-title">{{ message.msg }}</div>
|
||||||
|
<div class="mian-shoeToast-content">{{ message.tips }}</div>
|
||||||
|
<div class="mian-shoeToast-btn" @click="jump('Index')">返回主页</div>
|
||||||
|
</div>
|
||||||
|
<!-- 投票列表 -->
|
||||||
|
<div v-else>
|
||||||
|
<v-dialog
|
||||||
|
v-model="loading"
|
||||||
|
persistent
|
||||||
|
width="300"
|
||||||
|
>
|
||||||
|
<v-card
|
||||||
|
color="primary"
|
||||||
|
dark
|
||||||
|
>
|
||||||
|
<v-card-text>
|
||||||
|
<p class="pt-4">数据提交中,请稍后</p>
|
||||||
|
<v-progress-linear
|
||||||
|
indeterminate
|
||||||
|
color="white"
|
||||||
|
class="mb-0"
|
||||||
|
></v-progress-linear>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
|
|
||||||
|
<div class="list">
|
||||||
|
<div class="vote-title">
|
||||||
|
中国狮子联会哈尔滨心连心服务队
|
||||||
|
</div>
|
||||||
|
<div style="color: #ffffff">
|
||||||
|
2020-2021年度
|
||||||
|
</div>
|
||||||
|
<div class="vote-title">{{ info.title }}</div>
|
||||||
|
|
||||||
|
<div class="list-card" v-for="(item, index) in list" :key="index">
|
||||||
|
<v-img
|
||||||
|
:src="item.cover"
|
||||||
|
width="110"
|
||||||
|
height="160"
|
||||||
|
position="top center"
|
||||||
|
style="position: absolute"
|
||||||
|
>
|
||||||
|
</v-img>
|
||||||
|
<div class="list-card-content">
|
||||||
|
<div class="list-card-content-name">{{ item.name }}</div>
|
||||||
|
<div class="list-card-content-item"><label>参选职务:</label>{{ item.desc }}</div>
|
||||||
|
<!-- <div class="list-card-content-item"><label>服 务 队:</label>{{ item.desc2 }}</div>-->
|
||||||
|
<v-radio-group
|
||||||
|
class="radioGroup"
|
||||||
|
row
|
||||||
|
v-model="item.radio"
|
||||||
|
>
|
||||||
|
<v-radio
|
||||||
|
color="#1f3c84"
|
||||||
|
value="0"
|
||||||
|
>
|
||||||
|
<template v-slot:label>
|
||||||
|
<div class="list-radio-text">不赞成</div>
|
||||||
|
</template>
|
||||||
|
</v-radio>
|
||||||
|
<v-radio
|
||||||
|
color="#1f3c84"
|
||||||
|
value="1"
|
||||||
|
>
|
||||||
|
<template v-slot:label>
|
||||||
|
<div class="list-radio-text">赞成</div>
|
||||||
|
</template>
|
||||||
|
</v-radio>
|
||||||
|
</v-radio-group>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<v-dialog v-model="sure" persistent>
|
||||||
|
<v-card>
|
||||||
|
<v-card-title>确认提交</v-card-title>
|
||||||
|
<!-- <v-card-text>-->
|
||||||
|
<!-- 确认您选择无误,立即提交么?-->
|
||||||
|
<!-- </v-card-text>-->
|
||||||
|
<v-card-actions>
|
||||||
|
<v-btn color="primary" text @click="sure=false">我再想想</v-btn>
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
<v-btn color="primary" @click="submitForm">确认提交</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
|
<div class="list-footer">
|
||||||
|
<button
|
||||||
|
class="list-footer-btn"
|
||||||
|
:disabled="lock"
|
||||||
|
@click="sure = true"
|
||||||
|
>
|
||||||
|
提交推举票
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</v-container>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "index",
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
loaded: false,
|
||||||
|
id: 0,
|
||||||
|
info: [],
|
||||||
|
list: [],
|
||||||
|
submitData: [],
|
||||||
|
lock: true,
|
||||||
|
loading: false,
|
||||||
|
message: '',
|
||||||
|
sure: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.id = this.$route.params.id;
|
||||||
|
this.$api.vote.show(this.id).then(result => {
|
||||||
|
this.lock = result.lock
|
||||||
|
this.info = result.info
|
||||||
|
this.list = result.list
|
||||||
|
this.loaded = true
|
||||||
|
})
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
submitForm: function (e) {
|
||||||
|
this.loading = true
|
||||||
|
e.preventDefault()
|
||||||
|
this.submitData = []
|
||||||
|
this.list.forEach((item) => {
|
||||||
|
this.submitData.push({
|
||||||
|
id: item.id,
|
||||||
|
result: item.radio
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
this.$api.vote.submit(this.id, this.submitData).then((res) => {
|
||||||
|
this.message = res
|
||||||
|
this.loading = false
|
||||||
|
this.lock = true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.vote-title {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 20px;
|
||||||
|
color: white;
|
||||||
|
padding-top: 20px;
|
||||||
|
text-shadow: 0 2px 2px rgba(0, 0, 0, .5);
|
||||||
|
}
|
||||||
|
.mian {
|
||||||
|
min-height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
background-color: #1f3c84;
|
||||||
|
background-image: url("../../assets/back_1.png");
|
||||||
|
background-position: top center;
|
||||||
|
background-size: 100%;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 提示信息 */
|
||||||
|
.mian-shoeToast {
|
||||||
|
margin-top: 10vh;
|
||||||
|
margin-left: 30px;
|
||||||
|
height: 80vh;
|
||||||
|
width: calc(100vw - 60px);
|
||||||
|
background: white;
|
||||||
|
border-radius: 6px;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
-webkit-box-pack: center;
|
||||||
|
padding: 30px 20px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mian-shoeToast-icon {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mian-shoeToast-title {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #1f3c84;
|
||||||
|
padding-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mian-shoeToast-content {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mian-shoeToast-btn {
|
||||||
|
height: 45px;
|
||||||
|
background: #1f3c84;
|
||||||
|
color: white;
|
||||||
|
line-height: 45px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 6px;
|
||||||
|
margin-top: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 列表 */
|
||||||
|
.list-header b {
|
||||||
|
color: #1f3c84;
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-footer {
|
||||||
|
background: #1f3c84;
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
padding: 15px;
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-footer-btn {
|
||||||
|
font-size: 1rem;
|
||||||
|
height: 45px;
|
||||||
|
line-height: 45px;
|
||||||
|
width: 100%;
|
||||||
|
background: white;
|
||||||
|
color: #1f3c84;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list {
|
||||||
|
padding: 0px 15px 75px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-card {
|
||||||
|
background: white;
|
||||||
|
margin-top: 15px;
|
||||||
|
border-radius: 6px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 10px;
|
||||||
|
position: relative;
|
||||||
|
min-height: 180px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-card-content {
|
||||||
|
margin-left: 130px;
|
||||||
|
height: 160px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.radioGroup {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
bottom: -20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-card-content-name {
|
||||||
|
font-size: 22px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #1f3c84;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-card-content-item {
|
||||||
|
padding-top: 5px;
|
||||||
|
font-size: 14px;
|
||||||
|
position: relative;
|
||||||
|
padding-left: 80px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-card-content-item label {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 5px;
|
||||||
|
color: gray;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-radio-text {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
179
VUE-WEB/lions-web/src/pages/vote/index.vue
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<v-container class="mian" v-if="loaded">
|
||||||
|
<div v-if="!lock">
|
||||||
|
<div class="vote-title">
|
||||||
|
中国狮子联会哈尔滨心连心服务队
|
||||||
|
</div>
|
||||||
|
<div style="color: #ffffff">
|
||||||
|
2020-2021年度
|
||||||
|
</div>
|
||||||
|
<div class="vote-title">{{ vote.title }}</div>
|
||||||
|
<div class="vote-block">
|
||||||
|
<div class="vote-block-title">
|
||||||
|
{{ vote.type == 'diff' ? '差额推举' : '等额推举' }}规则说明
|
||||||
|
</div>
|
||||||
|
<div class="vote-block-html" v-html="vote.rules"></div>
|
||||||
|
</div>
|
||||||
|
<div class="vote-block-btn">
|
||||||
|
<span @click="jump('Vote_' + vote.type, {id: vote.id})">继续</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mian-shoeToast" v-else>
|
||||||
|
<div class="mian-shoeToast-icon">
|
||||||
|
<v-icon color="#1f3c84" size="58">mdi-checkbox-marked-circle</v-icon>
|
||||||
|
</div>
|
||||||
|
<div class="mian-shoeToast-title">投票成功</div>
|
||||||
|
<div class="mian-shoeToast-content">您已提交本轮次选票,为保密起见,已隐藏您的投票结果,其他人也无法查询</div>
|
||||||
|
<div class="mian-shoeToast-btn" @click="jump('Index')">返回主页</div>
|
||||||
|
</div>
|
||||||
|
</v-container>
|
||||||
|
<div v-else>
|
||||||
|
<v-dialog
|
||||||
|
v-model="loading"
|
||||||
|
persistent
|
||||||
|
width="300"
|
||||||
|
>
|
||||||
|
<v-card
|
||||||
|
color="primary"
|
||||||
|
dark
|
||||||
|
>
|
||||||
|
<v-card-text>
|
||||||
|
<p class="pt-4">数据加载中,请稍后</p>
|
||||||
|
<v-progress-linear
|
||||||
|
indeterminate
|
||||||
|
color="white"
|
||||||
|
class="mb-0"
|
||||||
|
></v-progress-linear>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "index",
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
vote: [],
|
||||||
|
lock: true,
|
||||||
|
loaded: false,
|
||||||
|
loading: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$api.vote.index().then(result => {
|
||||||
|
this.vote = result.vote
|
||||||
|
this.lock = result.lock
|
||||||
|
|
||||||
|
this.loaded = true
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.mian {
|
||||||
|
min-height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
background-color: #1f3c84;
|
||||||
|
background-image: url("../../assets/back_1.png");
|
||||||
|
background-position: top center;
|
||||||
|
background-size: 100%;
|
||||||
|
padding: 0 30px 130px 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 提示信息 */
|
||||||
|
.mian-shoeToast {
|
||||||
|
margin-top: 10vh;
|
||||||
|
height: 80vh;
|
||||||
|
width: calc(100vw - 60px);
|
||||||
|
background: white;
|
||||||
|
border-radius: 6px;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
-webkit-box-pack: center;
|
||||||
|
padding: 30px 20px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mian-shoeToast-icon {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mian-shoeToast-title {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #1f3c84;
|
||||||
|
padding-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mian-shoeToast-content {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mian-shoeToast-btn {
|
||||||
|
height: 45px;
|
||||||
|
background: #1f3c84;
|
||||||
|
color: white;
|
||||||
|
line-height: 45px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 6px;
|
||||||
|
margin-top: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.vote-block {
|
||||||
|
background: white;
|
||||||
|
padding: 10px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vote-block-title {
|
||||||
|
line-height: 40px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #1f3c84;
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vote-title {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 20px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
color: white;
|
||||||
|
padding-top: 20px;
|
||||||
|
text-shadow: 0 2px 2px rgba(0, 0, 0, .5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vote-block-btn {
|
||||||
|
background: #1f3c84;
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
padding: 30px;
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vote-block-btn span {
|
||||||
|
width: 100%;
|
||||||
|
height: 45px;
|
||||||
|
line-height: 45px;
|
||||||
|
background: white;
|
||||||
|
color: #1f3c84;
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 6px;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
34
VUE-WEB/lions-web/src/plugins/toast/index.js
vendored
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import Toast from "./toast"
|
||||||
|
import Vue from 'vue'
|
||||||
|
|
||||||
|
let currentToast;
|
||||||
|
|
||||||
|
const index = (message, color) => {
|
||||||
|
if (currentToast) {
|
||||||
|
currentToast.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
currentToast = createToast({
|
||||||
|
Vue,
|
||||||
|
message,
|
||||||
|
color: color || 'success',
|
||||||
|
onClose: () => {
|
||||||
|
currentToast = null
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
function createToast({Vue, message, color, onClose}) {
|
||||||
|
const Constructor = Vue.extend(Toast)
|
||||||
|
|
||||||
|
let toast = new Constructor()
|
||||||
|
toast.message = message
|
||||||
|
toast.color = color
|
||||||
|
toast.timeout = 1000
|
||||||
|
toast.$mount();
|
||||||
|
toast.$on('close', onClose)
|
||||||
|
document.body.appendChild(toast.$el)
|
||||||
|
return toast
|
||||||
|
}
|
||||||
|
|
||||||
|
export default index
|
||||||
38
VUE-WEB/lions-web/src/plugins/toast/toast.vue
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
<template>
|
||||||
|
<v-snackbar
|
||||||
|
:color="color"
|
||||||
|
:timeout="timeout"
|
||||||
|
:top="true"
|
||||||
|
v-model="show"
|
||||||
|
>
|
||||||
|
{{ message }}
|
||||||
|
</v-snackbar>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "toast.vue",
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
timeout: 1000,
|
||||||
|
color: 'success',
|
||||||
|
show: true,
|
||||||
|
message: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
console.log(this.color)
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
close() {
|
||||||
|
this.$el.remove();
|
||||||
|
this.$emit("close");
|
||||||
|
this.$destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
8
VUE-WEB/lions-web/src/plugins/utils.js
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
const isWechat = () => {
|
||||||
|
let ua = navigator.userAgent.toLowerCase();
|
||||||
|
return (/micromessenger/.test(ua)) ? true : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
isWechat
|
||||||
|
}
|
||||||
13
VUE-WEB/lions-web/src/plugins/vuetify.js
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import Vue from 'vue'
|
||||||
|
import Vuetify from 'vuetify'
|
||||||
|
import 'vuetify/dist/vuetify.min.css'
|
||||||
|
import '@mdi/font/css/materialdesignicons.css'
|
||||||
|
|
||||||
|
Vue.use(Vuetify)
|
||||||
|
|
||||||
|
export default new Vuetify({
|
||||||
|
icons: {
|
||||||
|
iconfont: 'mdi'
|
||||||
|
},
|
||||||
|
theme: {}
|
||||||
|
})
|
||||||
123
VUE-WEB/lions-web/src/router/index.js
vendored
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
import VueRouter from "vue-router"
|
||||||
|
import store from "@/store"
|
||||||
|
|
||||||
|
const routes = [
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
name: 'Index',
|
||||||
|
component: () => import(/* webpackChunkName: "index" */ '@/pages/index/index'),
|
||||||
|
meta: {
|
||||||
|
title: '首页',
|
||||||
|
keepAlive: true,
|
||||||
|
requireAuth: true
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
path: '/vote',
|
||||||
|
name: 'VoteIndex',
|
||||||
|
component: () => import(/* webpackChunkName: "index" */ '@/pages/vote/index'),
|
||||||
|
meta: {
|
||||||
|
title: '推举规则',
|
||||||
|
keepAlive: true,
|
||||||
|
requireAuth: true
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
path: '/vote_equal/:id',
|
||||||
|
name: 'Vote_equal',
|
||||||
|
component: () => import(/* webpackChunkName: "index" */ '@/pages/vote/equal'),
|
||||||
|
meta: {
|
||||||
|
title: '等额推举',
|
||||||
|
keepAlive: true,
|
||||||
|
requireAuth: true
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
path: '/vote_diff/:id',
|
||||||
|
name: 'Vote_diff',
|
||||||
|
component: () => import(/* webpackChunkName: "index" */ '@/pages/vote/diff'),
|
||||||
|
meta: {
|
||||||
|
title: '差额推举',
|
||||||
|
keepAlive: true,
|
||||||
|
requireAuth: true
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
path: '/files',
|
||||||
|
name: 'FileIndex',
|
||||||
|
component: () => import(/* webpackChunkName: "index" */ '@/pages/files/index'),
|
||||||
|
meta: {
|
||||||
|
title: '文件审阅',
|
||||||
|
keepAlive: true,
|
||||||
|
requireAuth: true
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
path: '/files/:id',
|
||||||
|
name: 'FileShow',
|
||||||
|
component: () => import(/* webpackChunkName: "index" */ '@/pages/files/show'),
|
||||||
|
meta: {
|
||||||
|
title: '审阅',
|
||||||
|
keepAlive: true,
|
||||||
|
requireAuth: true
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
path: '/auth',
|
||||||
|
component: () => import(/* webpackChunkName: "auth" */ '@/pages/layouts/common'),
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: 'login',
|
||||||
|
name: 'AuthLogin',
|
||||||
|
component: () => import(/* webpackChunkName: "auth" */ '@/pages/auth/login'),
|
||||||
|
meta: {
|
||||||
|
title: '推举系统登录'
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
path: 'verify',
|
||||||
|
name: 'AuthVerify',
|
||||||
|
component: () => import(/* webpackChunkName: "auth" */ '@/pages/auth/verify'),
|
||||||
|
meta: {
|
||||||
|
title: '验证手机号'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const router = new VueRouter({
|
||||||
|
mode: 'history',
|
||||||
|
routes
|
||||||
|
});
|
||||||
|
|
||||||
|
router.beforeEach((to, from, next) => {
|
||||||
|
let hasLogin = store.state.hasLogin;
|
||||||
|
if (to.name === 'AuthLogin') {
|
||||||
|
// 如果已经登录,直接跳转到首页去
|
||||||
|
if (hasLogin) {
|
||||||
|
next({
|
||||||
|
name: 'Index'
|
||||||
|
});
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let tk = localStorage.getItem('accessToken')
|
||||||
|
|
||||||
|
if (tk) {
|
||||||
|
store.commit('login', tk);
|
||||||
|
next({
|
||||||
|
name: 'Index'
|
||||||
|
});
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else if (to.meta.requireAuth === true) {
|
||||||
|
if (hasLogin === false) {
|
||||||
|
next({
|
||||||
|
name: 'AuthLogin',
|
||||||
|
params: {
|
||||||
|
next: to.path
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
store.state.showFooter = to.meta.showFooter || false;
|
||||||
|
|
||||||
|
next()
|
||||||
|
});
|
||||||
|
|
||||||
|
export default router
|
||||||
49
VUE-WEB/lions-web/src/store/index.js
vendored
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import Vue from 'vue'
|
||||||
|
import Vuex from 'vuex'
|
||||||
|
|
||||||
|
Vue.use(Vuex);
|
||||||
|
|
||||||
|
const store = new Vuex.Store({
|
||||||
|
state: {
|
||||||
|
hasLogin: false,
|
||||||
|
accessToken: '',
|
||||||
|
userInfo: null,
|
||||||
|
},
|
||||||
|
mutations: {
|
||||||
|
login(state, token) {
|
||||||
|
state.hasLogin = true;
|
||||||
|
state.accessToken = token;
|
||||||
|
localStorage.setItem('accessToken', token)
|
||||||
|
},
|
||||||
|
logout(state) {
|
||||||
|
state.hasLogin = false;
|
||||||
|
state.accessToken = "";
|
||||||
|
state.userInfo = null;
|
||||||
|
localStorage.removeItem('accessToken')
|
||||||
|
localStorage.removeItem('userInfo')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getters: {},
|
||||||
|
actions: {
|
||||||
|
/**
|
||||||
|
* 登录并获取用户基础信息
|
||||||
|
* @param dispatch
|
||||||
|
* @param commit
|
||||||
|
* @param token
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async doLogin({commit}, token) {
|
||||||
|
commit('login', token)
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 登出方法
|
||||||
|
* @param commit
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async doLogout({commit}) {
|
||||||
|
commit('logout')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default store
|
||||||
37
app/Admin/Actions/CleanData.php
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Admin\Actions;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
use Encore\Admin\Actions\Action;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
|
class CleanData extends Action
|
||||||
|
{
|
||||||
|
|
||||||
|
protected $selector = '.clean';
|
||||||
|
|
||||||
|
public function handle(Request $request)
|
||||||
|
{
|
||||||
|
// 清理用户签到
|
||||||
|
User::where('sign', 1)->update(['sign' => 0]);
|
||||||
|
|
||||||
|
DB::table('item_logs')->truncate();
|
||||||
|
|
||||||
|
return $this->response()->success('清理成功')->refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
function dialog()
|
||||||
|
{
|
||||||
|
return $this->confirm('确认要清理数据么');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function html()
|
||||||
|
{
|
||||||
|
return <<<HTML
|
||||||
|
<a class="btn btn-sm btn-danger clean"><i class="fa fa-trash"></i> 清理数据</a>
|
||||||
|
HTML;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
127
app/Admin/Actions/DestroyData.php
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Admin\Actions;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
use Encore\Admin\Actions\RowAction;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use PhpOffice\PhpWord\Exception\CopyFileException;
|
||||||
|
use PhpOffice\PhpWord\Exception\CreateTemporaryFileException;
|
||||||
|
use PhpOffice\PhpWord\TemplateProcessor;
|
||||||
|
|
||||||
|
class DestroyData extends RowAction
|
||||||
|
{
|
||||||
|
|
||||||
|
public $name = '数据导出';
|
||||||
|
|
||||||
|
public function dialog()
|
||||||
|
{
|
||||||
|
$this->confirm('确定要销毁数据么,一旦操作不可恢复?');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handle(Model $model)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
if ($model->archives) {
|
||||||
|
// return $this->response()->error('数据销毁失败')->refresh();
|
||||||
|
}
|
||||||
|
// 先做存档
|
||||||
|
$archives = [];
|
||||||
|
|
||||||
|
foreach ($model->items as $key => $item) {
|
||||||
|
$archives[$key] = [
|
||||||
|
'item_id' => $item->id,
|
||||||
|
'item_name' => $item->name,
|
||||||
|
'total' => User::where('type', 0)->count(),
|
||||||
|
'sign' => User::where('type', 0)->where('sign', 1)->count(),
|
||||||
|
'tickets' => $item->logs()->sum('result'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
$model->archives = $archives;
|
||||||
|
$model->save();
|
||||||
|
|
||||||
|
if ($model->type == 'diff') {
|
||||||
|
$download = $this->exportDiff($model);
|
||||||
|
} else {
|
||||||
|
$download = $this->exportEqual($model);
|
||||||
|
}
|
||||||
|
|
||||||
|
// $model->logs()->delete();
|
||||||
|
|
||||||
|
return $this->response()->success('数据销毁完成')->refresh()->download($download);
|
||||||
|
} catch (\RuntimeException $exception) {
|
||||||
|
return $this->response()->error('数据销毁失败'.$exception->getMessage())->refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notes : 等额数据导出
|
||||||
|
*
|
||||||
|
* @Date : 2022/5/4 15:39
|
||||||
|
* @Author : <Jason.C>
|
||||||
|
* @param $vote
|
||||||
|
* @return string
|
||||||
|
* @throws CopyFileException
|
||||||
|
* @throws CreateTemporaryFileException
|
||||||
|
*/
|
||||||
|
public function exportEqual($vote)
|
||||||
|
{
|
||||||
|
$templateProcessor = new TemplateProcessor(storage_path('app/public/DENG_FEN.docx'));
|
||||||
|
|
||||||
|
$templateProcessor->setValue('TOTAL1', $vote->logs()->distinct('user_id')->count());
|
||||||
|
$templateProcessor->setValue('TITLE', $vote->title);
|
||||||
|
|
||||||
|
$i = 0;
|
||||||
|
$tpl = '';
|
||||||
|
foreach ($vote->items as $item) {
|
||||||
|
$i++;
|
||||||
|
$tpl .= $i.'. '.$item->name.' '.$item->logs()->sum('result')." 票 <w:br />";
|
||||||
|
}
|
||||||
|
|
||||||
|
$templateProcessor->setValue('LOOPS', $tpl);
|
||||||
|
$save = 'download_'.uniqid().'.docx';
|
||||||
|
$templateProcessor->saveAs(storage_path('app/public/'.$save));
|
||||||
|
|
||||||
|
return '/storage/'.$save;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notes: 差额数据倒出
|
||||||
|
*
|
||||||
|
* @Author: <C.Jason>
|
||||||
|
* @Date : 2020/5/7 4:34 下午
|
||||||
|
* @param $vote
|
||||||
|
* @return string
|
||||||
|
* @throws CopyFileException
|
||||||
|
* @throws CreateTemporaryFileException
|
||||||
|
*/
|
||||||
|
protected function exportDiff($vote): string
|
||||||
|
{
|
||||||
|
$templateProcessor = new TemplateProcessor(storage_path('app/public/CHA_E.docx'));
|
||||||
|
|
||||||
|
$templateProcessor->setValue('TOTAL', $vote->logs()->distinct('user_id')->count());
|
||||||
|
$templateProcessor->setValue('TITLE', $vote->title);
|
||||||
|
$templateProcessor->setValue('DATE', date('Y年m月d日', time()));
|
||||||
|
|
||||||
|
$data = $vote->items;
|
||||||
|
|
||||||
|
$data = $data->sortByDesc(function ($item) {
|
||||||
|
return $item->logs()->sum('result');
|
||||||
|
});
|
||||||
|
|
||||||
|
$i = 0;
|
||||||
|
$tpl = '';
|
||||||
|
foreach ($data as $item) {
|
||||||
|
$i++;
|
||||||
|
$tpl .= $i.'. '.$item->name.' '.$item->logs()->sum('result')." 票<w:br />";
|
||||||
|
}
|
||||||
|
|
||||||
|
$templateProcessor->setValue('TPL', $tpl);
|
||||||
|
|
||||||
|
$save = 'download_'.uniqid().'.docx';
|
||||||
|
$templateProcessor->saveAs(storage_path('app/public/'.$save));
|
||||||
|
|
||||||
|
return '/storage/'.$save;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
49
app/Admin/Actions/ExportCha.php
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Admin\Actions;
|
||||||
|
|
||||||
|
use App\Models\Vote;
|
||||||
|
use Encore\Admin\Actions\Action;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use PhpOffice\PhpWord\TemplateProcessor;
|
||||||
|
|
||||||
|
class ExportCha extends Action
|
||||||
|
{
|
||||||
|
|
||||||
|
public $name = '差额倒出';
|
||||||
|
|
||||||
|
protected $vote;
|
||||||
|
|
||||||
|
protected $selector = '.export-cha';
|
||||||
|
|
||||||
|
public function handle(Request $request)
|
||||||
|
{
|
||||||
|
$templateProcessor = new TemplateProcessor(storage_path('app/public/CHA_E.docx'));
|
||||||
|
|
||||||
|
$vote = Vote::find(2);
|
||||||
|
$templateProcessor->setValue('TOTAL', $vote->logs()->distinct('user_id')->count());
|
||||||
|
$templateProcessor->setValue('TITLE', $vote->title);
|
||||||
|
|
||||||
|
$i = 0;
|
||||||
|
$tpl = '';
|
||||||
|
foreach ($vote->items as $item) {
|
||||||
|
$i++;
|
||||||
|
$tpl .= $i . '. ' . $item->name . ' ' . $item->logs()->sum('result') . " 票\r";
|
||||||
|
}
|
||||||
|
|
||||||
|
$templateProcessor->setValue('TPL', $tpl);
|
||||||
|
|
||||||
|
$save = 'chae_1.docx';
|
||||||
|
$templateProcessor->saveAs(storage_path('app/public/' . $save));
|
||||||
|
|
||||||
|
return $this->response()->success('倒出成功')->download('/storage/' . $save);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function html()
|
||||||
|
{
|
||||||
|
return <<<HTML
|
||||||
|
<a class="btn btn-sm btn-warning export-cha"><i class="fa fa-upload"></i> 差额数据倒出</a>
|
||||||
|
HTML;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
48
app/Admin/Actions/ExportDeng.php
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Admin\Actions;
|
||||||
|
|
||||||
|
use App\Models\Vote;
|
||||||
|
use Encore\Admin\Actions\Action;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use PhpOffice\PhpWord\TemplateProcessor;
|
||||||
|
|
||||||
|
class ExportDeng extends Action
|
||||||
|
{
|
||||||
|
|
||||||
|
public $name = '批量导入';
|
||||||
|
|
||||||
|
protected $vote;
|
||||||
|
|
||||||
|
protected $selector = '.export-deng';
|
||||||
|
|
||||||
|
public function handle(Request $request)
|
||||||
|
{
|
||||||
|
$templateProcessor = new TemplateProcessor(storage_path('app/public/DENG_E.docx'));
|
||||||
|
|
||||||
|
$vote1 = Vote::find(1);
|
||||||
|
$templateProcessor->setValue('TOTAL1', $vote1->logs()->distinct('user_id')->count());
|
||||||
|
|
||||||
|
foreach ($vote1->items as $item) {
|
||||||
|
$templateProcessor->setValue('ITEM_' . $item->id, $item->logs()->sum('result'));
|
||||||
|
}
|
||||||
|
$vote3 = Vote::find(3);
|
||||||
|
$templateProcessor->setValue('TOTAL3', $vote1->logs()->distinct('user_id')->count());
|
||||||
|
foreach ($vote3->items as $item) {
|
||||||
|
$templateProcessor->setValue('ITEM_' . $item->id, $item->logs()->sum('result'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$save = 'denge_1.docx';
|
||||||
|
$templateProcessor->saveAs(storage_path('app/public/' . $save));
|
||||||
|
|
||||||
|
return $this->response()->success('倒出成功')->download('/storage/' . $save);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function html()
|
||||||
|
{
|
||||||
|
return <<<HTML
|
||||||
|
<a class="btn btn-sm btn-info export-deng"><i class="fa fa-upload"></i> 等额数据倒出</a>
|
||||||
|
HTML;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
42
app/Admin/Actions/Replicate.php
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Admin\Actions;
|
||||||
|
|
||||||
|
use Encore\Admin\Actions\RowAction;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
|
class Replicate extends RowAction
|
||||||
|
{
|
||||||
|
|
||||||
|
public $name = '复制活动';
|
||||||
|
|
||||||
|
public function dialog()
|
||||||
|
{
|
||||||
|
$this->confirm('确定复制活动链接么?');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handle(Model $model)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
DB::transaction(function () use ($model) {
|
||||||
|
$new = $model->replicate();
|
||||||
|
$new->status = 0;
|
||||||
|
$new->save();
|
||||||
|
foreach ($model->items as $item) {
|
||||||
|
$new->items()->create([
|
||||||
|
'name' => $item->name,
|
||||||
|
'cover' => $item->cover,
|
||||||
|
'desc' => $item->desc,
|
||||||
|
'desc2' => $item->desc2,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return $this->response()->success('活动链接复制完成')->refresh();
|
||||||
|
} catch (\RuntimeException $exception) {
|
||||||
|
return $this->response()->error('活动链接复制出错了')->refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
79
app/Admin/Actions/UserImport.php
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Admin\Actions;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
use Encore\Admin\Actions\Action;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class UserImport extends Action
|
||||||
|
{
|
||||||
|
|
||||||
|
public $name = '批量导入';
|
||||||
|
|
||||||
|
protected $vote;
|
||||||
|
|
||||||
|
protected $selector = '.import-post';
|
||||||
|
|
||||||
|
public function handle(Request $request)
|
||||||
|
{
|
||||||
|
$file = $request->file('file');
|
||||||
|
// if ($file->extension() != 'csv') {
|
||||||
|
// return $this->response()->error('必须使用csv文件' . $file->extension());
|
||||||
|
// }
|
||||||
|
$handle = fopen($file, 'r');
|
||||||
|
if (!$handle) {
|
||||||
|
exit('读取文件失败');
|
||||||
|
}
|
||||||
|
$failed = 0;
|
||||||
|
$success = 0;
|
||||||
|
while (($data = fgetcsv($handle)) !== false) {
|
||||||
|
if (empty($data[0])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strlen($data[0]) != 11) {
|
||||||
|
$failed++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ($this->preg_is_utf8($data[1])) {
|
||||||
|
$name = $data[1];
|
||||||
|
} else {
|
||||||
|
$name = iconv('gbk', 'utf-8', $data[1]);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
User::create([
|
||||||
|
'parent_id' => $request->parent_id ?: 1,
|
||||||
|
'mobile' => $data[0],
|
||||||
|
'name' => $name,
|
||||||
|
]);
|
||||||
|
$success++;
|
||||||
|
} catch (\Exception $exception) {
|
||||||
|
$failed++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fclose($handle);
|
||||||
|
|
||||||
|
return $this->response()->success('批量处理成功' . $success . '条,失败' . $failed . '条')->refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
function preg_is_utf8($string)
|
||||||
|
{
|
||||||
|
return preg_match('/^.*$/u', $string) > 0;//preg_match('/^./u', $string)
|
||||||
|
}
|
||||||
|
|
||||||
|
public function form()
|
||||||
|
{
|
||||||
|
$parents = User::select()->where('type', 1)->pluck('name', 'id');
|
||||||
|
$this->select('parent_id')->options($parents);
|
||||||
|
$this->file('file', '请选择文件')->help('CSV - 手机号-姓名');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function html()
|
||||||
|
{
|
||||||
|
return <<<HTML
|
||||||
|
<a class="btn btn-sm btn-info import-post"><i class="fa fa-upload"></i> 批量导入</a>
|
||||||
|
HTML;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
41
app/Admin/Controllers/ArticleController.php
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Admin\Controllers;
|
||||||
|
|
||||||
|
use App\Models\Article;
|
||||||
|
use App\Models\Category;
|
||||||
|
use Encore\Admin\Controllers\AdminController;
|
||||||
|
use Encore\Admin\Form;
|
||||||
|
use Encore\Admin\Grid;
|
||||||
|
|
||||||
|
class ArticleController extends AdminController
|
||||||
|
{
|
||||||
|
|
||||||
|
protected $title = '内容管理';
|
||||||
|
|
||||||
|
public function grid()
|
||||||
|
{
|
||||||
|
$grid = new Grid(new Article);
|
||||||
|
$grid->column('id', '#ID#');
|
||||||
|
$grid->column('title', '文章标题');
|
||||||
|
$grid->column('sort', '序号');
|
||||||
|
$grid->column('created_at', '创建时间');
|
||||||
|
|
||||||
|
return $grid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function form()
|
||||||
|
{
|
||||||
|
$form = new Form(new Article);
|
||||||
|
|
||||||
|
$form->text('title', '文章标题');
|
||||||
|
$form->number('category_id', '分类ID')
|
||||||
|
->help('审阅文件固定写 2 ');
|
||||||
|
$form->textarea('desc');
|
||||||
|
$form->ueditor('content', '文章内容')->rules('required', ['required' => '详情不能为空']);
|
||||||
|
$form->number('sort', '序号')->default(0)->rules('required', ['required' => '序号必须填写'])->help('倒序优先');
|
||||||
|
|
||||||
|
return $form;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
10
app/Admin/Controllers/AuthController.php
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Admin\Controllers;
|
||||||
|
|
||||||
|
use Encore\Admin\Controllers\AuthController as BaseAuthController;
|
||||||
|
|
||||||
|
class AuthController extends BaseAuthController
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
21
app/Admin/Controllers/HomeController.php
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Admin\Controllers;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use Encore\Admin\Controllers\Dashboard;
|
||||||
|
use Encore\Admin\Layout\Column;
|
||||||
|
use Encore\Admin\Layout\Content;
|
||||||
|
use Encore\Admin\Layout\Row;
|
||||||
|
|
||||||
|
class HomeController extends Controller
|
||||||
|
{
|
||||||
|
|
||||||
|
public function index(Content $content)
|
||||||
|
{
|
||||||
|
return $content
|
||||||
|
->title('Dashboard')
|
||||||
|
->description('Description...');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
82
app/Admin/Controllers/ItemController.php
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Admin\Controllers;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\Item;
|
||||||
|
use App\Models\Vote;
|
||||||
|
use Encore\Admin\Form;
|
||||||
|
use Encore\Admin\Grid;
|
||||||
|
use Encore\Admin\Layout\Content;
|
||||||
|
|
||||||
|
class ItemController extends Controller
|
||||||
|
{
|
||||||
|
|
||||||
|
public function index(Content $content, Vote $vote)
|
||||||
|
{
|
||||||
|
return $content
|
||||||
|
->header($vote->title)
|
||||||
|
->description('投票列表')
|
||||||
|
->body($this->grid($vote));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function grid($vote)
|
||||||
|
{
|
||||||
|
$grid = new Grid(new Item);
|
||||||
|
$grid->model()->where('vote_id', $vote->id);
|
||||||
|
$grid->column('cover')->image('', 80);
|
||||||
|
$grid->column('id', '#ID#');
|
||||||
|
$grid->column('name');
|
||||||
|
$grid->column('sort');
|
||||||
|
$grid->column('得票数')->display(function () {
|
||||||
|
return $this->logs()->where('result', 1)->count();
|
||||||
|
});
|
||||||
|
$grid->column('created_at', '创建时间')->date('Y-m-d H:i:s');
|
||||||
|
|
||||||
|
return $grid;
|
||||||
|
}
|
||||||
|
|
||||||
|
function create(Content $content, Vote $vote)
|
||||||
|
{
|
||||||
|
return $content
|
||||||
|
->header($vote->title)
|
||||||
|
->description('新增条目')
|
||||||
|
->body($this->form($vote));
|
||||||
|
}
|
||||||
|
|
||||||
|
function form($vote)
|
||||||
|
{
|
||||||
|
$form = new Form(new Item);
|
||||||
|
|
||||||
|
$form->hidden('vote_id')->value($vote->id);
|
||||||
|
$form->text('name', '姓名');
|
||||||
|
$form->number('sort', '排序')
|
||||||
|
->default(1)
|
||||||
|
->help('正序排序');
|
||||||
|
$form->textarea('desc', '竞选岗位');
|
||||||
|
$form->textarea('desc2', '服务队');
|
||||||
|
$form->image('cover', '照片')
|
||||||
|
->uniqueName();
|
||||||
|
|
||||||
|
return $form;
|
||||||
|
}
|
||||||
|
|
||||||
|
function store(Vote $vote)
|
||||||
|
{
|
||||||
|
return $this->form($vote)->store();
|
||||||
|
}
|
||||||
|
|
||||||
|
function edit(Content $content, Vote $vote, $id)
|
||||||
|
{
|
||||||
|
return $content
|
||||||
|
->header($vote->title)
|
||||||
|
->description('新增条目')
|
||||||
|
->body($this->form($vote)->edit($id));
|
||||||
|
}
|
||||||
|
|
||||||
|
function update(Vote $vote, $id)
|
||||||
|
{
|
||||||
|
return $this->form($vote)->update($id);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
60
app/Admin/Controllers/UserController.php
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Admin\Controllers;
|
||||||
|
|
||||||
|
use App\Admin\Actions\CleanData;
|
||||||
|
use App\Admin\Actions\UserImport;
|
||||||
|
use App\Models\User;
|
||||||
|
use Encore\Admin\Controllers\AdminController;
|
||||||
|
use Encore\Admin\Form;
|
||||||
|
use Encore\Admin\Grid;
|
||||||
|
|
||||||
|
class UserController extends AdminController
|
||||||
|
{
|
||||||
|
|
||||||
|
protected $title = '用户管理';
|
||||||
|
|
||||||
|
public function grid(): Grid
|
||||||
|
{
|
||||||
|
$grid = new Grid(new User);
|
||||||
|
$grid->tools(function (Grid\Tools $tools) {
|
||||||
|
$tools->append(new UserImport);
|
||||||
|
$tools->append(new CleanData);
|
||||||
|
});
|
||||||
|
// $grid->disableCreateButton();
|
||||||
|
// $grid->disableActions();
|
||||||
|
$grid->column('归属干事')->display(function () {
|
||||||
|
return $this->parent->name ?? '';
|
||||||
|
});
|
||||||
|
$grid->column('mobile', '手机号');
|
||||||
|
$grid->column('name', '姓名');
|
||||||
|
$grid->column('sign', '签到')->bool();
|
||||||
|
$grid->column('type', '角色')->using([
|
||||||
|
0 => '投票人',
|
||||||
|
1 => '干事',
|
||||||
|
2 => '总干事',
|
||||||
|
]);
|
||||||
|
$grid->column('created_at', '创建时间')->date('Y-m-d H:i:s');;
|
||||||
|
|
||||||
|
return $grid;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function form(): Form
|
||||||
|
{
|
||||||
|
$form = new Form(new User);
|
||||||
|
|
||||||
|
$form->text('name', '姓名');
|
||||||
|
$form->text('mobile', '手机号');
|
||||||
|
$form->select('parent_id','归属干事')->options(
|
||||||
|
User::where('type', 1)->pluck('name', 'id')
|
||||||
|
)->default(0);
|
||||||
|
$form->select('type')->options([
|
||||||
|
0 => '投票人',
|
||||||
|
1 => '干事',
|
||||||
|
2 => '总干事',
|
||||||
|
])->help('只有干事,才可以呗归属,投票人必须要有归属,干事不可以有归属。');
|
||||||
|
|
||||||
|
return $form;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
135
app/Admin/Controllers/VoteController.php
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Admin\Controllers;
|
||||||
|
|
||||||
|
use App\Admin\Actions\DestroyData;
|
||||||
|
use App\Admin\Actions\ExportCha;
|
||||||
|
use App\Admin\Actions\ExportDeng;
|
||||||
|
use App\Admin\Actions\Replicate;
|
||||||
|
use App\Models\Item;
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Models\Vote;
|
||||||
|
use Encore\Admin\Controllers\AdminController;
|
||||||
|
use Encore\Admin\Form;
|
||||||
|
use Encore\Admin\Grid;
|
||||||
|
use Encore\Admin\Widgets\Table;
|
||||||
|
|
||||||
|
class VoteController extends AdminController
|
||||||
|
{
|
||||||
|
|
||||||
|
protected $title = '投票管理';
|
||||||
|
|
||||||
|
public function grid()
|
||||||
|
{
|
||||||
|
$grid = new Grid(new Vote());
|
||||||
|
// $grid->tools(function (Grid\Tools $tools) {
|
||||||
|
// $tools->append(new ExportDeng());
|
||||||
|
// $tools->append(new ExportCha());
|
||||||
|
// });
|
||||||
|
$grid->actions(function ($actions) {
|
||||||
|
$actions->add(new Replicate);
|
||||||
|
$actions->add(new DestroyData);
|
||||||
|
});
|
||||||
|
$grid->column('id', '#ID#');
|
||||||
|
$grid->column('title', '投票名称');
|
||||||
|
$grid->column('type', '类型')->using(['diff' => '差额投票', 'equal' => '等额投票']);
|
||||||
|
$grid->column('status', '状态')->switch();
|
||||||
|
$grid->column('数量')->display(function () {
|
||||||
|
return '<a href="'.admin_url('votes/'.$this->id.'/items').'">'.$this->items()->count().'</a>';
|
||||||
|
});
|
||||||
|
$grid->column('参与/签到/总人数')->display(function () {
|
||||||
|
return $this->logs()->distinct('user_id')->count()
|
||||||
|
.'/'.User::where('type', 0)->where('sign', 1)->count()
|
||||||
|
.'/'.User::where('type', 0)->count();
|
||||||
|
});
|
||||||
|
$grid->column('created_at', '创建时间')->date('Y-m-d H:i:s');
|
||||||
|
|
||||||
|
return $grid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function form()
|
||||||
|
{
|
||||||
|
$form = new Form(new Vote);
|
||||||
|
|
||||||
|
$form->text('title', '投票名称')->required();
|
||||||
|
$form->select('type', '类型')->options([
|
||||||
|
'equal' => '等额',
|
||||||
|
'diff' => '差额',
|
||||||
|
]);
|
||||||
|
$form->number('max')->default(0)
|
||||||
|
->help('差额选票,最大可选人数');
|
||||||
|
|
||||||
|
$form->switch('status', '状态');
|
||||||
|
|
||||||
|
$form->ueditor('rules', '投票规则');
|
||||||
|
|
||||||
|
return $form;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function detail($id)
|
||||||
|
{
|
||||||
|
$data = Item::where('vote_id', $id)->get();
|
||||||
|
|
||||||
|
$data = $data->sortByDesc(function ($item, $key) {
|
||||||
|
return $item->logs()->sum('result');
|
||||||
|
});
|
||||||
|
|
||||||
|
// table 2
|
||||||
|
$headers = ['序号', '姓名', '得票', '投票用户', '签到用户', '通过率'];
|
||||||
|
$rows = [];
|
||||||
|
$i = 1;
|
||||||
|
foreach ($data as $item) {
|
||||||
|
$cent = $item->logs()->sum('result') / User::where('type', 0)->where('sign', 1)->count();
|
||||||
|
|
||||||
|
$color = $cent > 0.5 ? 'green' : 'red';
|
||||||
|
|
||||||
|
$cent = '<span style="color:'.$color.'">'.number_format($cent * 100, 2).'%</span>';
|
||||||
|
$rows[] = [
|
||||||
|
$i++,
|
||||||
|
$item->name,
|
||||||
|
$item->logs()->sum('result'),
|
||||||
|
$item->vote->logs()->distinct('user_id')->count(),
|
||||||
|
User::where('type', 0)->where('sign', 1)->count(),
|
||||||
|
$cent,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Table($headers, $rows);
|
||||||
|
|
||||||
|
$grid = new Grid(new Item);
|
||||||
|
|
||||||
|
// $grid->model()->with([
|
||||||
|
// 'logs' => function ($query) {
|
||||||
|
// $query->orderBy(\DB::raw('SUM(`result`)'), 'desc');
|
||||||
|
// },
|
||||||
|
// ]);
|
||||||
|
// $grid->model()->orderByRaw('updated_at - created_at DESC')
|
||||||
|
|
||||||
|
$grid->paginate(100);
|
||||||
|
$grid->disableActions();
|
||||||
|
$grid->disableCreateButton();
|
||||||
|
$grid->model()->where('vote_id', $id);
|
||||||
|
|
||||||
|
$grid->column('name', '姓名');
|
||||||
|
$grid->column('票数')->display(function () {
|
||||||
|
return $this->logs()->sum('result');
|
||||||
|
});
|
||||||
|
$grid->column('结果')->display(function () {
|
||||||
|
$cent = $this->logs()->sum('result') / User::where('type', 0)->where('sign', 1)->count();
|
||||||
|
|
||||||
|
$color = $cent > 0.51 ? 'green' : 'red';
|
||||||
|
|
||||||
|
return '<span style="color:'.$color.'">'.number_format($cent * 100, 2).'%</span>';
|
||||||
|
});
|
||||||
|
|
||||||
|
$grid->column('签到用户')->display(function () {
|
||||||
|
return User::where('type', 0)->where('sign', 1)->count();
|
||||||
|
});
|
||||||
|
// $grid->column('用户总数')->display(function () {
|
||||||
|
// return User::where('type', 0)->count();
|
||||||
|
// });
|
||||||
|
|
||||||
|
return $grid;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
29
app/Admin/bootstrap.php
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Encore\Admin\Form;
|
||||||
|
use Encore\Admin\Grid;
|
||||||
|
|
||||||
|
Form::forget(['map', 'editor']);
|
||||||
|
|
||||||
|
Form::init(function (Form $form) {
|
||||||
|
$form->disableEditingCheck();
|
||||||
|
$form->disableCreatingCheck();
|
||||||
|
$form->disableViewCheck();
|
||||||
|
$form->tools(function (Form\Tools $tools) {
|
||||||
|
$tools->disableDelete();
|
||||||
|
$tools->disableView();
|
||||||
|
$tools->disableList();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
Grid::init(function (Grid $grid) {
|
||||||
|
$grid->disableExport();
|
||||||
|
$grid->actions(function (Grid\Displayers\Actions $actions) {
|
||||||
|
$actions->disableView();
|
||||||
|
});
|
||||||
|
$grid->disableBatchActions();
|
||||||
|
$grid->filter(function ($filter) {
|
||||||
|
$filter->disableIdFilter();
|
||||||
|
});
|
||||||
|
// $grid->expandFilter();
|
||||||
|
});
|
||||||
19
app/Admin/routes.php
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Encore\Admin\Facades\Admin;
|
||||||
|
use Illuminate\Routing\Router;
|
||||||
|
|
||||||
|
Admin::routes();
|
||||||
|
|
||||||
|
Route::group([
|
||||||
|
'prefix' => config('admin.route.prefix'),
|
||||||
|
'namespace' => config('admin.route.namespace'),
|
||||||
|
'middleware' => config('admin.route.middleware'),
|
||||||
|
], function (Router $router) {
|
||||||
|
$router->get('/', 'HomeController@index')->name('admin.home');
|
||||||
|
$router->resource('votes/{vote}/items', 'ItemController');
|
||||||
|
$router->resource('votes', 'VoteController');
|
||||||
|
$router->resource('users', 'UserController');
|
||||||
|
$router->resource('articles', 'ArticleController');
|
||||||
|
|
||||||
|
});
|
||||||
41
app/Api/Controllers/ArticleController.php
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Api\Controllers;
|
||||||
|
|
||||||
|
use App\Models\Article;
|
||||||
|
use App\Models\Vote;
|
||||||
|
use Jason\Api;
|
||||||
|
|
||||||
|
class ArticleController extends Controller
|
||||||
|
{
|
||||||
|
|
||||||
|
function index()
|
||||||
|
{
|
||||||
|
$files = Article::where('category_id', 2)->select(['id', 'title', 'desc'])->get();
|
||||||
|
|
||||||
|
foreach ($files as $file) {
|
||||||
|
$file->is = $file->audit()->where('user_id', Api::id())->exists();
|
||||||
|
}
|
||||||
|
return $this->success([
|
||||||
|
'user' => Api::user(),
|
||||||
|
'files' => $files,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
function show(Article $article)
|
||||||
|
{
|
||||||
|
return $this->success([
|
||||||
|
'article' => $article,
|
||||||
|
'audited' => $article->audit()->where('user_id', Api::id())->exists(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function audit(Article $article)
|
||||||
|
{
|
||||||
|
$article->audit()->firstOrCreate([
|
||||||
|
'user_id' => Api::id(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $this->success('审阅成功');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
77
app/Api/Controllers/AuthController.php
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Api\Controllers;
|
||||||
|
|
||||||
|
use App\Models\Article;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Jason\Api;
|
||||||
|
use Sms;
|
||||||
|
|
||||||
|
class AuthController extends Controller
|
||||||
|
{
|
||||||
|
|
||||||
|
public function license()
|
||||||
|
{
|
||||||
|
return $this->success([
|
||||||
|
'agreement' => Article::where('id', 7)->value('content'),
|
||||||
|
'privacy' => Article::where('id', 7)->value('content'),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notes : 干事登录,type必须大于1的
|
||||||
|
*
|
||||||
|
* @Date : 2022/4/30 15:49
|
||||||
|
* @Author : <Jason.C>
|
||||||
|
* @param Request $request
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
function code2(Request $request)
|
||||||
|
{
|
||||||
|
$username = $request->username;
|
||||||
|
|
||||||
|
$user = User::where('mobile', $username)->where('type', '>=', 1)->first();
|
||||||
|
if (!$user) {
|
||||||
|
return $this->failed('对不起,您无权登录');
|
||||||
|
} else {
|
||||||
|
Sms::send($username);
|
||||||
|
|
||||||
|
return $this->success('发送成功');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function code(Request $request)
|
||||||
|
{
|
||||||
|
$username = $request->username;
|
||||||
|
|
||||||
|
$user = User::where('mobile', $username)->where('type', 0)->first();
|
||||||
|
if (!$user) {
|
||||||
|
return $this->failed('对不起,您无权登录');
|
||||||
|
} else {
|
||||||
|
Sms::send($username);
|
||||||
|
|
||||||
|
return $this->success('发送成功');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function loginByCode(Request $request)
|
||||||
|
{
|
||||||
|
$username = $request->username;
|
||||||
|
$code = $request->verify;
|
||||||
|
|
||||||
|
if (Sms::check($username, $code)) {
|
||||||
|
$user = User::where('mobile', $username)->first();
|
||||||
|
|
||||||
|
$token = Api::login($user);
|
||||||
|
|
||||||
|
return $this->success([
|
||||||
|
'access_token' => 'Bearer ' . $token,
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
return $this->failed('验证码不正确');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
12
app/Api/Controllers/Controller.php
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Api\Controllers;
|
||||||
|
|
||||||
|
use Jason\Api\Traits\ApiResponse;
|
||||||
|
use Illuminate\Routing\Controller as BaseController;
|
||||||
|
|
||||||
|
class Controller extends BaseController
|
||||||
|
{
|
||||||
|
|
||||||
|
use ApiResponse;
|
||||||
|
}
|
||||||
76
app/Api/Controllers/IndexController.php
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Api\Controllers;
|
||||||
|
|
||||||
|
use App\Models\Article;
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Models\Vote;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Jason\Api;
|
||||||
|
|
||||||
|
class IndexController extends Controller
|
||||||
|
{
|
||||||
|
|
||||||
|
public function index(Request $request)
|
||||||
|
{
|
||||||
|
$user = Api::user();
|
||||||
|
$license = Article::where('id', 1)->value('content');
|
||||||
|
$license = str_replace('{username}', $user->name, $license);
|
||||||
|
$license = str_replace('{mobile}', $user->mobile, $license);
|
||||||
|
|
||||||
|
$voteShow = false;
|
||||||
|
|
||||||
|
$vote = Vote::where('status', 1)->first();
|
||||||
|
|
||||||
|
if ($vote && ! $vote->logs()->where('user_id', Api::id())->exists()) {
|
||||||
|
$voteShow = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->success([
|
||||||
|
'user' => Api::user(),
|
||||||
|
'sign' => Api::user()->sign,
|
||||||
|
'can_sign' => config('open_sign'),
|
||||||
|
'license' => $license,
|
||||||
|
'vote' => $voteShow,
|
||||||
|
'dested' => (bool) config('dested'),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function sign()
|
||||||
|
{
|
||||||
|
if (config('open_sign')) {
|
||||||
|
$user = Api::user();
|
||||||
|
|
||||||
|
$user->sign = 1;
|
||||||
|
$user->save();
|
||||||
|
|
||||||
|
return $this->success('签到成功');
|
||||||
|
} else {
|
||||||
|
return $this->failed('签到已关闭');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function agent()
|
||||||
|
{
|
||||||
|
if (Api::user()->type == 1) {
|
||||||
|
$users = User::where('parent_id', Api::id())->select(['id', 'mobile', 'name', 'sign'])->get();
|
||||||
|
} else {
|
||||||
|
$users = User::where('type', 0)->select(['id', 'mobile', 'name', 'sign'])->get();
|
||||||
|
}
|
||||||
|
|
||||||
|
$vote = Vote::where('status', 1)->first();
|
||||||
|
|
||||||
|
if ($vote) {
|
||||||
|
foreach ($users as $user) {
|
||||||
|
$user->vote = $vote->logs()->where('user_id', $user->id)->exists();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->success([
|
||||||
|
'now_vote' => $vote->title ?? '',
|
||||||
|
'user' => Api::user(),
|
||||||
|
'desserts' => $users,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
74
app/Api/Controllers/VoteController.php
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Api\Controllers;
|
||||||
|
|
||||||
|
use App\Api\Resources\ItemResource;
|
||||||
|
use App\Models\Vote;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Jason\Api;
|
||||||
|
|
||||||
|
class VoteController extends Controller
|
||||||
|
{
|
||||||
|
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
$vote = Vote::where('status', 1)->first();
|
||||||
|
|
||||||
|
return $this->success([
|
||||||
|
'vote' => $vote,
|
||||||
|
'lock' => $vote->logs()->where('user_id', Api::id())->exists(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function show(Vote $vote, Request $request)
|
||||||
|
{
|
||||||
|
return $this->success([
|
||||||
|
'lock' => $vote->logs()->where('user_id', Api::id())->exists(),
|
||||||
|
'info' => $vote,
|
||||||
|
'list' => ItemResource::collection($vote->items()->orderBy('sort', 'asc')->get()),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function submit(Vote $vote, Request $request)
|
||||||
|
{
|
||||||
|
if ($vote->status != 1) {
|
||||||
|
return $this->success([
|
||||||
|
'icon' => 'mdi-close-circle',
|
||||||
|
'msg' => '投票通道已关闭',
|
||||||
|
'tips' => '本轮推举已结束,您在规定时间内未提交结果。'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
if ($vote->logs()->where('user_id', Api::id())->exists()) {
|
||||||
|
return $this->success([
|
||||||
|
'icon' => 'mdi-close-circle',
|
||||||
|
'msg' => '重复提交',
|
||||||
|
'tips' => '您已经参与过推举,请不要重复提交。'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($vote->type == 'equal') {
|
||||||
|
foreach ($request->data as $item) {
|
||||||
|
$vote->logs()->create([
|
||||||
|
'user_id' => Api::id(),
|
||||||
|
'item_id' => $item['id'],
|
||||||
|
'result' => $item['result'],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
foreach ($request->data as $item) {
|
||||||
|
$vote->logs()->create([
|
||||||
|
'user_id' => Api::id(),
|
||||||
|
'item_id' => $item,
|
||||||
|
'result' => 1,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->success([
|
||||||
|
'icon' => 'mdi-checkbox-marked-circle',
|
||||||
|
'msg' => '投票成功,感谢您的参与',
|
||||||
|
'tips' => '您已提交本轮次推举票,为保密,已隐藏您的推举结果,任何人无法查询。'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
23
app/Api/Resources/ItemResource.php
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Api\Resources;
|
||||||
|
|
||||||
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
|
||||||
|
class ItemResource extends JsonResource
|
||||||
|
{
|
||||||
|
|
||||||
|
public function toArray($request)
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'id' => $this->id,
|
||||||
|
'name' => $this->name,
|
||||||
|
'desc' => $this->desc,
|
||||||
|
'desc2' => $this->desc2,
|
||||||
|
'cover' => Storage::url($this->cover),
|
||||||
|
'radio' => '1',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
1
app/Api/bootstrap.php
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<?php
|
||||||
33
app/Api/routes.php
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Routing\Router;
|
||||||
|
|
||||||
|
Route::group([
|
||||||
|
'domain' => config('api.route.domain'),
|
||||||
|
'prefix' => config('api.route.prefix'),
|
||||||
|
'namespace' => config('api.route.namespace'),
|
||||||
|
'middleware' => config('api.route.middleware'),
|
||||||
|
], function (Router $router) {
|
||||||
|
$router->get('auth/license', 'AuthController@license');
|
||||||
|
$router->post('auth/code/login', 'AuthController@code');
|
||||||
|
$router->post('auth/code/login2', 'AuthController@code2');
|
||||||
|
$router->post('auth/loginByCode', 'AuthController@loginByCode');
|
||||||
|
});
|
||||||
|
|
||||||
|
Route::group([
|
||||||
|
'domain' => config('api.route.domain'),
|
||||||
|
'prefix' => config('api.route.prefix'),
|
||||||
|
'namespace' => config('api.route.namespace'),
|
||||||
|
'middleware' => config('api.route.middleware_auth'),
|
||||||
|
], function (Router $router) {
|
||||||
|
$router->get('index', 'IndexController@index');
|
||||||
|
$router->get('agent', 'IndexController@agent');
|
||||||
|
$router->post('sign', 'IndexController@sign');
|
||||||
|
|
||||||
|
$router->get('vote', 'VoteController@index');
|
||||||
|
$router->get('vote/{vote}', 'VoteController@show');
|
||||||
|
$router->post('vote/{vote}', 'VoteController@submit');
|
||||||
|
$router->get('articles', 'ArticleController@index');
|
||||||
|
$router->get('articles/{article}', 'ArticleController@show');
|
||||||
|
$router->post('articles/{article}', 'ArticleController@audit');
|
||||||
|
});
|
||||||
41
app/Console/Kernel.php
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console;
|
||||||
|
|
||||||
|
use Illuminate\Console\Scheduling\Schedule;
|
||||||
|
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
|
||||||
|
|
||||||
|
class Kernel extends ConsoleKernel
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The Artisan commands provided by your application.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $commands = [
|
||||||
|
//
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define the application's command schedule.
|
||||||
|
*
|
||||||
|
* @param \Illuminate\Console\Scheduling\Schedule $schedule
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
protected function schedule(Schedule $schedule)
|
||||||
|
{
|
||||||
|
// $schedule->command('inspire')->hourly();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register the commands for the application.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
protected function commands()
|
||||||
|
{
|
||||||
|
$this->load(__DIR__.'/Commands');
|
||||||
|
|
||||||
|
require base_path('routes/console.php');
|
||||||
|
}
|
||||||
|
}
|
||||||
55
app/Exceptions/Handler.php
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Exceptions;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
class Handler extends ExceptionHandler
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* A list of the exception types that are not reported.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $dontReport = [
|
||||||
|
//
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of the inputs that are never flashed for validation exceptions.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $dontFlash = [
|
||||||
|
'password',
|
||||||
|
'password_confirmation',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Report or log an exception.
|
||||||
|
*
|
||||||
|
* @param \Throwable $exception
|
||||||
|
* @return void
|
||||||
|
*
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
public function report(Throwable $exception)
|
||||||
|
{
|
||||||
|
parent::report($exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render an exception into an HTTP response.
|
||||||
|
*
|
||||||
|
* @param \Illuminate\Http\Request $request
|
||||||
|
* @param \Throwable $exception
|
||||||
|
* @return \Symfony\Component\HttpFoundation\Response
|
||||||
|
*
|
||||||
|
* @throws \Throwable
|
||||||
|
*/
|
||||||
|
public function render($request, Throwable $exception)
|
||||||
|
{
|
||||||
|
return parent::render($request, $exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
30
app/Http/Controllers/AuthController.php
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
|
||||||
|
class AuthController extends Controller
|
||||||
|
{
|
||||||
|
|
||||||
|
function login(Request $request)
|
||||||
|
{
|
||||||
|
if ($request->isMethod('POST')) {
|
||||||
|
$mobile = $request->mobile;
|
||||||
|
|
||||||
|
$user = User::where('mobile', $mobile)->first();
|
||||||
|
if ($user) {
|
||||||
|
Auth::login($user);
|
||||||
|
|
||||||
|
return redirect()->route('index');
|
||||||
|
} else {
|
||||||
|
echo "YONGHU NO";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// return view('auth.login');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
13
app/Http/Controllers/Controller.php
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||||
|
use Illuminate\Foundation\Validation\ValidatesRequests;
|
||||||
|
use Illuminate\Routing\Controller as BaseController;
|
||||||
|
|
||||||
|
class Controller extends BaseController
|
||||||
|
{
|
||||||
|
|
||||||
|
use AuthorizesRequests, ValidatesRequests;
|
||||||
|
}
|
||||||
22
app/Http/Controllers/IndexController.php
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Models\Vote;
|
||||||
|
|
||||||
|
class IndexController extends Controller
|
||||||
|
{
|
||||||
|
|
||||||
|
function index()
|
||||||
|
{
|
||||||
|
$list = Vote::all();
|
||||||
|
|
||||||
|
return view('index.index', compact('list'));
|
||||||
|
}
|
||||||
|
|
||||||
|
function vote(Vote $vote)
|
||||||
|
{
|
||||||
|
return view('index.vote', compact('vote'));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
66
app/Http/Kernel.php
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Http\Kernel as HttpKernel;
|
||||||
|
|
||||||
|
class Kernel extends HttpKernel
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The application's global HTTP middleware stack.
|
||||||
|
*
|
||||||
|
* These middleware are run during every request to your application.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $middleware = [
|
||||||
|
\App\Http\Middleware\TrustProxies::class,
|
||||||
|
\Fruitcake\Cors\HandleCors::class,
|
||||||
|
\App\Http\Middleware\CheckForMaintenanceMode::class,
|
||||||
|
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
|
||||||
|
\App\Http\Middleware\TrimStrings::class,
|
||||||
|
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The application's route middleware groups.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $middlewareGroups = [
|
||||||
|
'web' => [
|
||||||
|
\App\Http\Middleware\EncryptCookies::class,
|
||||||
|
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
|
||||||
|
\Illuminate\Session\Middleware\StartSession::class,
|
||||||
|
// \Illuminate\Session\Middleware\AuthenticateSession::class,
|
||||||
|
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
|
||||||
|
\App\Http\Middleware\VerifyCsrfToken::class,
|
||||||
|
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||||
|
],
|
||||||
|
|
||||||
|
'api' => [
|
||||||
|
'throttle:60,1',
|
||||||
|
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The application's route middleware.
|
||||||
|
*
|
||||||
|
* These middleware may be assigned to groups or used individually.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $routeMiddleware = [
|
||||||
|
'auth' => \App\Http\Middleware\Authenticate::class,
|
||||||
|
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
|
||||||
|
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||||
|
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
|
||||||
|
'can' => \Illuminate\Auth\Middleware\Authorize::class,
|
||||||
|
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
|
||||||
|
'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
|
||||||
|
'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
|
||||||
|
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
|
||||||
|
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
|
||||||
|
];
|
||||||
|
}
|
||||||
21
app/Http/Middleware/Authenticate.php
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use Illuminate\Auth\Middleware\Authenticate as Middleware;
|
||||||
|
|
||||||
|
class Authenticate extends Middleware
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get the path the user should be redirected to when they are not authenticated.
|
||||||
|
*
|
||||||
|
* @param \Illuminate\Http\Request $request
|
||||||
|
* @return string|null
|
||||||
|
*/
|
||||||
|
protected function redirectTo($request)
|
||||||
|
{
|
||||||
|
if (! $request->expectsJson()) {
|
||||||
|
return route('login');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
17
app/Http/Middleware/CheckForMaintenanceMode.php
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode as Middleware;
|
||||||
|
|
||||||
|
class CheckForMaintenanceMode extends Middleware
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The URIs that should be reachable while maintenance mode is enabled.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $except = [
|
||||||
|
//
|
||||||
|
];
|
||||||
|
}
|
||||||
17
app/Http/Middleware/EncryptCookies.php
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use Illuminate\Cookie\Middleware\EncryptCookies as Middleware;
|
||||||
|
|
||||||
|
class EncryptCookies extends Middleware
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The names of the cookies that should not be encrypted.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $except = [
|
||||||
|
//
|
||||||
|
];
|
||||||
|
}
|
||||||
27
app/Http/Middleware/RedirectIfAuthenticated.php
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use App\Providers\RouteServiceProvider;
|
||||||
|
use Closure;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
|
||||||
|
class RedirectIfAuthenticated
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Handle an incoming request.
|
||||||
|
*
|
||||||
|
* @param \Illuminate\Http\Request $request
|
||||||
|
* @param \Closure $next
|
||||||
|
* @param string|null $guard
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function handle($request, Closure $next, $guard = null)
|
||||||
|
{
|
||||||
|
if (Auth::guard($guard)->check()) {
|
||||||
|
return redirect(RouteServiceProvider::HOME);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
}
|
||||||