admin webpack template

This commit is contained in:
LiYang
2016-07-31 15:55:31 +08:00
parent c8b2546ba2
commit fd9f740e76
169 changed files with 14872 additions and 88396 deletions

View File

@@ -0,0 +1,44 @@
<template>
<div>
<unsupported-browser-warning></unsupported-browser-warning>
<top-nav></top-nav>
<div class="container">
<div class="row">
<div>
<div class="col-md-2">
<left-nav></left-nav>
</div>
<div class="col-md-10">
<router-view></router-view>
</div>
<alert></alert>
</div>
</div>
</div>
<div class="footer">
<p class="text-muted text-center">Copyright © 2016</p>
</div>
</div>
</template>
<style>
@import "../../static/css/bootstrap.css";
@import "../../static/css/todc-bootstrap.css";
</style>
<script>
import unsupportedBrowserWarning from "./components/utils/unsupportedBrowserWarning.vue"
import topNav from "./components/utils/topNav.vue"
import leftNav from "./components/utils/leftNav.vue"
import alert from "./components/utils/alert.vue"
export default({
components: {
unsupportedBrowserWarning,
topNav,
leftNav,
alert
}
})
</script>

View File

@@ -0,0 +1,167 @@
<template>
<div>
<back url="/user"></back>
<h3>修改用户信息</h3>
<form v-on:submit="submit">
<div class="row">
<div class="form-group col-md-4"><label>ID</label>
<input type="number" class="form-control" v-model="user.id" readonly>
</div>
<div class="form-group col-md-4">
<label>{{ $t("user.username") }}</label>
<input type="text" class="form-control" v-model="user.username" maxlength="30" required>
</div>
<div class="form-group col-md-4">
<label>{{ $t("user.realName") }}</label>
<input type="text" class="form-control" maxlength="30" v-model="user.real_name" required>
</div>
</div>
<div class="row">
<div class="form-group col-md-4">
<label>{{ $t("user.newPassword") }}</label>
<input type="password" class="form-control"
placeholder='{{ $t("user.leaveBlankIfDoNotChangePassword")}}' v-model="newPassword">
</div>
<div class="form-group col-md-4">
<label>{{ $t("user.email") }}</label>
<input type="email" class="form-control" v-model="user.email" required>
</div>
<div class="form-group col-md-4">
<label>{{ $t("user.adminType") }}</label>
<select class="form-control" v-model="user.admin_type">
<option v-for="item in adminType" v-bind:value="item.value">{{ $t(item.name) }}</option>
</select>
</div>
</div>
<div class="row">
<div class="form-group col-md-3">
<label>{{ $t("user.openAPIFunction") }}</label>
<input type="checkbox" class="form-control" v-model="user.open_api">
</div>
<div class="form-group col-md-3">
<label>{{ $t("user.tfaAuth") }}</label>
<input type="checkbox" class="form-control" v-model="user.two_factor_auth">
</div>
<div class="form-group col-md-3">
<label>{{ $t("user.isDisabled") }}</label>
<input type="checkbox" class="form-control" v-model="user.is_disabled">
</div>
</div>
<div v-show="user.admin_type==adminType[1].value">
<h4>{{ $t("user.adminExtraPermission") }}</h4>
<div class="row">
<div class="form-group col-md-3">
<label>{{ $t("adminUtils.createPublicContest") }}</label>
<input type="checkbox" class="form-control" v-model="permission.createPublicContest">
</div>
<div class="form-group col-md-3">
<label>{{ $t("adminUtils.manageAllContest") }}</label>
<input type="checkbox" class="form-control" v-model="permission.manageAllContest">
</div>
<div class="form-group col-md-3">
<label>{{ $t("adminUtils.manageOwnProblem") }}</label>
<input type="checkbox" class="form-control" v-model="permission.manageOwnProblem">
</div>
<div class="form-group col-md-3">
<label>{{ $t("adminUtils.manageAllProblem") }}</label>
<input type="checkbox" class="form-control" v-model="permission.manageAllProblem">
</div>
</div>
</div>
<div class="form-group">
<input type="submit" class="btn btn-success"
value='{{ $t("adminUtils.saveChanges") }}'>
</div>
</form>
</div>
</template>
<script>
import back from '../utils/back.vue'
export default {
data() {
return {
adminType: [{name: "adminUtils.regularUser", value: 0},
{name: "adminUtils.admin", "value": 1},
{name: "adminUtils.superAdmin", value: 2}],
user: {},
permission: {
manageAllContest: false, createPublicContest: false,
manageAllProblem: false, manageOwnProblem: false
},
newPassword: "",
userPermissionNum2Str: {
1: "createPublicContest", 2: "manageAllContest",
3: "manageAllProblem", 4: "manageOwnProblem"
},
userPermissionStr2Num: {
createPublicContest: 1, manageAllContest: 2,
manageAllProblem: 3, manageOwnProblem: 4
}
}
},
methods: {
submit() {
var data = {
id: this.user.id,
username: this.user.username,
real_name: this.user.real_name,
email: this.user.email,
admin_type: this.user.admin_type,
open_api: this.user.open_api,
two_factor_auth: this.user.two_factor_auth,
is_disabled: this.user.is_disabled
};
if (this.newPassword) {
data["password"] = this.newPassword;
}
if (this.user.admin_type == this.adminType[1].value) {
data["admin_extra_permission"] = [];
for (var k in this.permission) {
if (this.permission[k]) {
data["admin_extra_permission"].push(this.userPermissionStr2Num[k])
}
}
}
this.request({
url: "/api/admin/user/",
method: "PUT",
data: data,
success: (data)=> {
// todo
}
})
}
},
route: {
data() {
this.request({
url: "/api/admin/user/?user_id=" + this.$route.params["userId"],
method: "GET",
success:(data)=> {
this.user = data.data;
for (var p of data.data.admin_extra_permission) {
if (this.userPermissionNum2Str[p]) {
this.permission[this.userPermissionNum2Str[p]] = true;
}
}
}
});
this.$watch('permission.manageAllProblem', function (val) {
if (val) {
this.permission.manageOwnProblem = false;
}
});
this.$watch('permission.manageOwnProblem', function (val) {
if (val) {
this.permission.manageAllProblem = false;
}
});
}
},
components: {
back
}
}
</script>

View File

@@ -0,0 +1,113 @@
<template>
<div>
<div class="right">
<form class="form-inline" onsubmit="return false;">
<div class="form-group-sm">
<label>{{ $t("adminUtils.search") }}</label>
<input name="keyword" class="form-control" placeholder='{{ $t("adminUtils.inputKeyword") }}'
v-model="keyword">
<button type="button" class="btn btn-primary" v-on:click="search">{{ $t("adminUtils.search") }}
</button>
</div>
</form>
<br>
</div>
<table class="table table-striped">
<tr>
<th>ID</th>
<th>{{ $t("user.username") }}</th>
<th>{{ $t("user.createTime") }}</th>
<th>{{ $t("user.realName") }}</th>
<th>{{ $t("user.email") }}</th>
<th>{{ $t("user.adminType") }}</th>
<th>{{ $t("user.management") }}</th>
</tr>
<tr v-for="user in userList">
<td>{{ user.id }}</td>
<td>{{ user.username }}</td>
<td>{{ user.create_time }}</td>
<td>{{ user.real_name }}</td>
<td>{{ user.email }}</td>
<td>{{ $t(adminType[user.admin_type]) }}</td>
<td>
<button class="btn-sm btn-info" v-on:click="edit(user.id)">{{ $t("user.edit") }}</button>
<a target="_blank" href="/submissions/?user_id={{ user.id }}">
{{ $t("user.submission") }}
</a>
</td>
</tr>
</table>
<input type="checkbox" v-model="showAdminOnly"> {{ $t("user.showAdminOnly") }}
<pager :pagination="pagination" :callback="loadData"></pager>
</div>
</template>
<script>
import Vue from 'vue'
import Router from 'vue-router'
import pager from '../utils/pager.vue'
//import editUser from './editUser.vue'
export default {
data: function () {
return {
keyword: "",
userList: [],
adminType: ["adminUtils.regularUser", "adminUtils.admin", "adminUtils.superAdmin"],
showAdminOnly: false,
pagination: {
currentPage: 1,
totalPages: 10
}
}
},
route: {
data(){
this.$watch('showAdminOnly', function (val) {
sessionStorage.showAdminOnly = JSON.stringify(val);
this.$router.go({name: this.$route.name, params: {page: 1}});
});
this.pagination.currentPage = this.$route.params.page;
if (sessionStorage.showAdminOnly) {
this.showAdminOnly = JSON.parse(sessionStorage.showAdminOnly);
}
if (sessionStorage.userListSearchKeyword) {
this.keyword = sessionStorage.userListSearchKeyword;
}
this.loadData();
}
},
methods: {
loadData() {
var url = "/api/admin/user/?paging=true&page_size=2&page=" + this.pagination.currentPage;
if (this.keyword) {
url += ("&keyword=" + this.keyword)
}
else if (this.showAdminOnly) {
url += "&admin_type=1";
}
this.request({
url: url,
method: "GET",
success: (data)=> {
this.userList = data.data.results;
this.pagination.totalPages = data.data.total_page;
}
})
},
search() {
sessionStorage.userListSearchKeyword = this.keyword;
this.pagination.currentPage = 1;
this.loadData();
},
edit(userId) {
this.$router.go("/user/edit/" + userId)
}
},
components: {
pager
}
}
</script>

View File

@@ -0,0 +1,113 @@
<template>
<div>
<div class="modal-mask" v-show="showModal" transition="modal">
<div class="modal-wrapper">
<div class="modal-container">
<div class="modal-header">
<slot name="header">
提示
</slot>
</div>
<div class="modal-body">
<slot name="body">
{{ content }}
</slot>
</div>
<div class="modal-footer">
<slot name="footer">
<button class="modal-default-button"
@click="show = false">
OK
</button>
</slot>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default({
data() {
return {
showModal: false,
content: ""
}
},
events: {
"showModal":(content) => {
this.showModal = true;
this.content = true;
setTimeout(()=>{
this.showModal = false;
}, 2000);
}
},
})
</script>
<style scoped>
.modal-mask {
position: fixed;
z-index: 9998;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, .5);
display: table;
transition: opacity .3s ease;
}
.modal-wrapper {
display: table-cell;
vertical-align: middle;
}
.modal-container {
width: 300px;
margin: 0 auto;
padding: 20px 30px;
background-color: #fff;
border-radius: 2px;
box-shadow: 0 2px 8px rgba(0, 0, 0, .33);
transition: all .3s ease;
font-family: Helvetica, Arial, sans-serif;
}
.modal-header h3 {
margin-top: 0;
color: #42b983;
}
.modal-body {
margin: 20px 0;
}
.modal-default-button {
float: right;
}
/*
* the following styles are auto-applied to elements with
* v-transition="modal" when their visiblity is toggled
* by Vue.js.
*
* You can easily play with the modal transition by editing
* these styles.
*/
.modal-enter, .modal-leave {
opacity: 0;
}
.modal-enter .modal-container,
.modal-leave .modal-container {
-webkit-transform: scale(1.1);
transform: scale(1.1);
}
</style>

View File

@@ -0,0 +1,17 @@
<template id="back">
<nav>
<ul class="pager">
<li class="previous">
<a href="#" v-link="url">
<span aria-hidden="true">&larr;</span>
{{ $t("adminUtils.back") }}
</a>
</li>
</ul>
</nav>
</template>
<script>
export default{
props: ["url"]
}
</script>

View File

@@ -0,0 +1,19 @@
<template>
<div id="left-nav">
<ul class="list-group">
<li class="list-group-header">name</li>
<li class="list-group-item">
<a v-link="{path: '/user'}">{{ $t("nav.UserManagement") }}</a>
</li>
</ul>
</div>
</template>
<script>
export default {
data() {
return {
adminNav: [{}, {}]
}
}
}
</script>

View File

@@ -0,0 +1,91 @@
<template>
<nav v-show="visible">
<ul class="pagination pagination-lg">
<li class="{{ pagination.currentPage > 1 ? '' : 'disabled' }}">
<a href="#" aria-label="Previous" @click.prevent="changePage(1)">
<span aria-hidden="true">上一页</span>
</a>
</li>
<li class="{{ pagination.currentPage > 1 ? '' : 'disabled' }}">
<a href="#" aria-label="Previous" @click.prevent="changePage(pagination.currentPage - 1)">
<span aria-hidden="true">&laquo;</span>
</a>
</li>
<li v-if="pagination.currentPage > 1 && pagination.currentPage - 1 > offset">
<a href="#" aria-label="Next" @click.prevent="changePage(from)">
<span aria-hidden="true">...</span>
</a>
</li>
<li v-for="num in data" :class="{'active': num == pagination.currentPage}">
<a href="#" @click.prevent="changePage(num)">{{ num }}</a>
</li>
<li v-if="pagination.totalPages - pagination.currentPage > offset">
<a href="#" aria-label="Next" @click.prevent="changePage(to)">
<span aria-hidden="true">...</span>
</a>
</li>
<li class="{{ pagination.currentPage < pagination.totalPages ? '' : 'disabled' }}">
<a href="#" aria-label="Next" @click.prevent="changePage(pagination.currentPage + 1)">
<span aria-hidden="true">&raquo;</span>
</a>
</li>
<li class="{{ pagination.currentPage < pagination.totalPages ? '' : 'disabled' }}">
<a href="#" aria-label="Next" @click.prevent="changePage(pagination.totalPages)">
<span aria-hidden="true">最后一页</span>
</a>
</li>
</ul>
</nav>
</template>
<script>
export default{
props: {
pagination: {
type: Object,
required: true
},
callback: {
type: Function,
required: true
},
offset: {
type: Number,
default: 4
},
visible: {
type: Number,
default: 1
}
},
computed: {
data: function () {
this.visible = 1;
var from = this.pagination.currentPage - this.offset;
if (from < 1) {
from = 1;
}
var to = from + (this.offset * 2);
if (to >= this.pagination.totalPages) {
to = this.pagination.totalPages;
}
this.from = from;
this.to = to;
var arr = [];
while (from <= to) {
arr.push(from);
from++;
}
if (arr.length == 1)
this.visible = 0;
return arr;
}
},
methods: {
changePage(page) {
this.$set('pagination.currentPage', page);
this.$router.go({name: this.$route.name, params: {page: page}});
this.callback();
}
}
};
</script>

View File

@@ -0,0 +1,25 @@
<template>
<nav class="navbar navbar-masthead navbar-default navbar-static-top">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar"
aria-expanded="false" aria-controls="navbar">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">oj admin</a>
</div>
<div id="navbar" class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li><a href="/" target="_blank">主页</a></li>
<li><a href="/problems/" target="_blank">题目</a></li>
<li><a href="/contests/" target="_blank">比赛</a></li>
</ul>
</div>
</div>
</nav>
</template>
<script>
export default{}
</script>

View File

@@ -0,0 +1,13 @@
<template>
<div id="browser-unsupported" style="display: none">{{{ $t("adminUtils.unsupportedBrowserWarningMsg") }}}
</div>
</template>
<script>
export default{
attached() {
if (navigator.userAgent.indexOf("MSIE") > -1) {
document.getElementById("browser-unsupported").removeAttribute("style");
}
}
}
</script>

View File

@@ -0,0 +1,51 @@
export default {
"zh-cn": {
nav: {
UserManagement: "用户管理"
},
pagination: {
first: "首页",
last: "末页"
},
modalAlert: {
alert: "提示",
close: "关闭"
},
request: {
error: "请求失败",
succeeded: "操作成功"
},
user: {
username: "用户名",
email: "邮箱",
realName: "真实姓名",
adminType: "用户类型",
createTime: "注册时间",
management: "管理",
edit: "编辑",
submission: "提交",
newPassword: "新密码",
leaveBlankIfDoNotChangePassword: "不需要修改密码请留空",
openAPIFunction: "OpenAPI 功能",
tfaAuth: "两步验证",
isDisabled: "禁用用户",
adminExtraPermission: "普通管理员额外权限",
showAdminOnly: "只显示管理员"
},
adminUtils: {
search: "搜索",
inputKeyword: "输入关键词",
regularUser: "普通用户",
admin: "普通管理员",
superAdmin: "超级管理员",
UserDoesNotExist: "用户不存在",
back: "返回",
saveChanges: "保存修改",
createPublicContest: "创建公开比赛",
manageAllContest: "管理所有比赛",
manageAllProblem: "管理所有题目",
manageOwnProblem: "管理自己创建的题目",
unsupportedBrowserWarningMsg: "当前网页 <strong>不支持</strong> 你正在使用的浏览器, 为了正常的访问,请到 <a href=\"http://browsehappy.com/\"> 升级你的浏览器</a>"
}
}
};

View File

@@ -0,0 +1,90 @@
import Vue from 'vue'
import App from './App'
import VueRouter from "vue-router"
import VueI18n from "vue-i18n"
import locale from "./locales"
import userList from "./components/account/userList.vue"
import editUser from "./components/account/editUser.vue"
var request = {
install: function (Vue, options) {
function getCookie(name) {
var value = "; " + document.cookie;
var parts = value.split("; " + name + "=");
if (parts.length == 2) {
return parts.pop().split(";").shift();
}
}
Vue.prototype.request = function (option) {
var request = new XMLHttpRequest();
request.open(option.method, option.url, true);
request.onerror = function () {
if (option.error) {
option.error(request)
}
else {
alert("请求失败");
}
};
request.onload = function () {
if (request.status >= 200 && request.status < 400) {
try {
var data = JSON.parse(request.responseText);
if (data.code == 1 && data.data) {
alert(data.data);
return;
}
}
catch (err) {
request.onerror();
}
option.success(data);
}
else {
request.onerror();
}
};
request.setRequestHeader('x-requested-with', 'XMLHttpRequest');
if (option.method.toLowerCase() == 'post' || option.method.toLowerCase() == 'put') {
request.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
request.setRequestHeader('x-csrftoken', getCookie('csrftoken'));
request.send(JSON.stringify(option.data));
}
else {
request.send();
}
}
}
};
Vue.use(request);
Vue.use(VueRouter);
Vue.use(VueI18n);
Vue.config.lang = "zh-cn";
Object.keys(locale).forEach(function (lang) {
Vue.locale(lang, locale[lang])
});
var router = new VueRouter();
router.map({
"/user/:page": {
name: "userList",
component: userList
},
"/user/edit/:userId": {
name: "editUser",
component: editUser
}
});
router.redirect({"/user": "/user/1"});
router.start(App, '#app');