parent
f8f21003b6
commit
45a62a0773
@ -1,40 +0,0 @@
|
||||
package biz
|
||||
|
||||
import (
|
||||
"context"
|
||||
v1 "moredoc/api/v1"
|
||||
"moredoc/model"
|
||||
"time"
|
||||
|
||||
"github.com/golang/protobuf/ptypes/empty"
|
||||
"go.uber.org/zap"
|
||||
"google.golang.org/protobuf/types/known/emptypb"
|
||||
)
|
||||
|
||||
type HealthAPIService struct {
|
||||
dbModel *model.DBModel
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
func NewHealthAPIService(dbModel *model.DBModel, logger *zap.Logger) (service *HealthAPIService) {
|
||||
return &HealthAPIService{
|
||||
dbModel: dbModel,
|
||||
logger: logger.Named("biz"),
|
||||
}
|
||||
}
|
||||
|
||||
// Health is health check
|
||||
func (p *HealthAPIService) Health(ctx context.Context, in *empty.Empty) (out *empty.Empty, err error) {
|
||||
out = &emptypb.Empty{}
|
||||
return
|
||||
}
|
||||
|
||||
// Ping ping pong
|
||||
func (p *HealthAPIService) Ping(ctx context.Context, in *v1.PingRequest) (out *v1.PongReply, err error) {
|
||||
createdAt := time.Now()
|
||||
out = &v1.PongReply{
|
||||
Name: in.Name,
|
||||
CreatedAt: &createdAt,
|
||||
}
|
||||
return
|
||||
}
|
@ -0,0 +1 @@
|
||||
package util
|
@ -0,0 +1,15 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
env: {
|
||||
browser: true,
|
||||
node: true,
|
||||
},
|
||||
parserOptions: {
|
||||
parser: '@babel/eslint-parser',
|
||||
requireConfigFile: false,
|
||||
},
|
||||
extends: ['@nuxtjs', 'plugin:nuxt/recommended', 'prettier'],
|
||||
plugins: [],
|
||||
// add your custom rules here
|
||||
rules: {},
|
||||
}
|
@ -0,0 +1,90 @@
|
||||
# Created by .ignore support plugin (hsz.mobi)
|
||||
### Node template
|
||||
# Logs
|
||||
/logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# TypeScript v1 declaration files
|
||||
typings/
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
|
||||
# next.js build output
|
||||
.next
|
||||
|
||||
# nuxt.js build output
|
||||
.nuxt
|
||||
|
||||
# Nuxt generate
|
||||
dist
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# Serverless directories
|
||||
.serverless
|
||||
|
||||
# IDE / Editor
|
||||
.idea
|
||||
|
||||
# Service worker
|
||||
sw.*
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
||||
|
||||
# Vim swap files
|
||||
*.swp
|
@ -0,0 +1,96 @@
|
||||
###
|
||||
# Place your Prettier ignore content here
|
||||
|
||||
###
|
||||
# .gitignore content is duplicated here due to https://github.com/prettier/prettier/issues/8506
|
||||
|
||||
# Created by .ignore support plugin (hsz.mobi)
|
||||
### Node template
|
||||
# Logs
|
||||
/logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# TypeScript v1 declaration files
|
||||
typings/
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
|
||||
# next.js build output
|
||||
.next
|
||||
|
||||
# nuxt.js build output
|
||||
.nuxt
|
||||
|
||||
# Nuxt generate
|
||||
dist
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# Serverless directories
|
||||
.serverless
|
||||
|
||||
# IDE / Editor
|
||||
.idea
|
||||
|
||||
# Service worker
|
||||
sw.*
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
||||
|
||||
# Vim swap files
|
||||
*.swp
|
@ -0,0 +1,4 @@
|
||||
{
|
||||
"semi": false,
|
||||
"singleQuote": true
|
||||
}
|
@ -0,0 +1,68 @@
|
||||
# web
|
||||
|
||||
## Build Setup
|
||||
|
||||
```bash
|
||||
# install dependencies
|
||||
$ npm install
|
||||
|
||||
# serve with hot reload at localhost:3000
|
||||
$ npm run dev
|
||||
|
||||
# build for production and launch server
|
||||
$ npm run build
|
||||
$ npm run start
|
||||
|
||||
# generate static project
|
||||
$ npm run generate
|
||||
```
|
||||
|
||||
For detailed explanation on how things work, check out the [documentation](https://nuxtjs.org).
|
||||
|
||||
## Special Directories
|
||||
|
||||
You can create the following extra directories, some of which have special behaviors. Only `pages` is required; you can delete them if you don't want to use their functionality.
|
||||
|
||||
### `assets`
|
||||
|
||||
The assets directory contains your uncompiled assets such as Stylus or Sass files, images, or fonts.
|
||||
|
||||
More information about the usage of this directory in [the documentation](https://nuxtjs.org/docs/2.x/directory-structure/assets).
|
||||
|
||||
### `components`
|
||||
|
||||
The components directory contains your Vue.js components. Components make up the different parts of your page and can be reused and imported into your pages, layouts and even other components.
|
||||
|
||||
More information about the usage of this directory in [the documentation](https://nuxtjs.org/docs/2.x/directory-structure/components).
|
||||
|
||||
### `layouts`
|
||||
|
||||
Layouts are a great help when you want to change the look and feel of your Nuxt app, whether you want to include a sidebar or have distinct layouts for mobile and desktop.
|
||||
|
||||
More information about the usage of this directory in [the documentation](https://nuxtjs.org/docs/2.x/directory-structure/layouts).
|
||||
|
||||
### `pages`
|
||||
|
||||
This directory contains your application views and routes. Nuxt will read all the `*.vue` files inside this directory and setup Vue Router automatically.
|
||||
|
||||
More information about the usage of this directory in [the documentation](https://nuxtjs.org/docs/2.x/get-started/routing).
|
||||
|
||||
### `plugins`
|
||||
|
||||
The plugins directory contains JavaScript plugins that you want to run before instantiating the root Vue.js Application. This is the place to add Vue plugins and to inject functions or constants. Every time you need to use `Vue.use()`, you should create a file in `plugins/` and add its path to plugins in `nuxt.config.js`.
|
||||
|
||||
More information about the usage of this directory in [the documentation](https://nuxtjs.org/docs/2.x/directory-structure/plugins).
|
||||
|
||||
### `static`
|
||||
|
||||
This directory contains your static files. Each file inside this directory is mapped to `/`.
|
||||
|
||||
Example: `/static/robots.txt` is mapped as `/robots.txt`.
|
||||
|
||||
More information about the usage of this directory in [the documentation](https://nuxtjs.org/docs/2.x/directory-structure/static).
|
||||
|
||||
### `store`
|
||||
|
||||
This directory contains your Vuex store files. Creating a file in this directory automatically activates Vuex.
|
||||
|
||||
More information about the usage of this directory in [the documentation](https://nuxtjs.org/docs/2.x/directory-structure/store).
|
@ -0,0 +1,59 @@
|
||||
import service from '~/utils/request'
|
||||
|
||||
export const register = (data) => {
|
||||
return service({
|
||||
url: '/api/v1/user/register',
|
||||
method: 'post',
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
export const login = (data) => {
|
||||
return service({
|
||||
url: '/api/v1/user/login',
|
||||
method: 'post',
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
export const getUser = (params) => {
|
||||
return service({
|
||||
url: '/api/v1/user',
|
||||
method: 'get',
|
||||
params,
|
||||
})
|
||||
}
|
||||
|
||||
export const updateUserPassword = (data) => {
|
||||
return service({
|
||||
url: '/api/v1/user/password',
|
||||
method: 'put',
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
export const deleteUser = (params) => {
|
||||
return service({
|
||||
url: '/api/v1/user',
|
||||
method: 'delete',
|
||||
params,
|
||||
})
|
||||
}
|
||||
|
||||
export const listUser = (params) => {
|
||||
return service({
|
||||
url: '/api/v1/user/list',
|
||||
method: 'get',
|
||||
params,
|
||||
})
|
||||
}
|
||||
|
||||
export const getUserCaptcha = (params) => {
|
||||
return service({
|
||||
url: '/api/v1/user/captcha',
|
||||
method: 'get',
|
||||
params,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,64 @@
|
||||
$bg: #ecf5ff;
|
||||
body{
|
||||
margin: 0;padding: 0;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.btn-block{
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.layout-admin{
|
||||
height: 100vh;
|
||||
.el-aside{
|
||||
height: 100vh;
|
||||
border-right: 1px solid #e6e6e6;
|
||||
.el-menu{
|
||||
border-right: 0;
|
||||
}
|
||||
}
|
||||
.create-project{
|
||||
padding: 15px;
|
||||
text-align: center;
|
||||
border-bottom: 1px solid $bg;
|
||||
&:hover{
|
||||
background-color: $bg;
|
||||
}
|
||||
}
|
||||
.el-header{
|
||||
border-bottom: 1px solid #e6e6e6;
|
||||
line-height: 60px;
|
||||
.fold{
|
||||
padding: 0 15px 0 0;
|
||||
font-size: 20px;
|
||||
color: #999;
|
||||
cursor: pointer;
|
||||
&:hover{
|
||||
color: #555
|
||||
}
|
||||
}
|
||||
}
|
||||
.el-footer{
|
||||
border-top: 1px solid #e6e6e6;
|
||||
text-align: center;
|
||||
line-height: 60px;
|
||||
height: 60px;
|
||||
overflow: hidden;
|
||||
color: #999;
|
||||
a{
|
||||
color: #409eff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.page-login{
|
||||
.el-card{
|
||||
width: 400px;
|
||||
max-width: 100%;
|
||||
margin: 20vh auto;
|
||||
}
|
||||
}
|
||||
|
||||
.page-admin-projects{
|
||||
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
<template>
|
||||
<div class="com-form-login">
|
||||
<el-form label-position="top" label-width="80px" :model="user">
|
||||
<el-form-item label="用户名">
|
||||
<el-input
|
||||
v-model="user.username"
|
||||
placeholder="请输入您的登录用户名"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="密码">
|
||||
<el-input
|
||||
v-model="user.password"
|
||||
placeholder="请输入您的登录密码"
|
||||
type="password"
|
||||
@keydown.native.enter="execLogin"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button
|
||||
type="primary"
|
||||
class="btn-block"
|
||||
icon="el-icon-check"
|
||||
@click="execLogin"
|
||||
>立即登录</el-button
|
||||
>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { mapActions } from 'vuex'
|
||||
import {getUserCaptcha} from '~/api/user'
|
||||
export default {
|
||||
name: 'FormLogin',
|
||||
data() {
|
||||
return {
|
||||
user: {
|
||||
username: '',
|
||||
password: '',
|
||||
},
|
||||
}
|
||||
},
|
||||
async created(){
|
||||
const res = await getUserCaptcha({type:'login'})
|
||||
console.log(res)
|
||||
},
|
||||
methods: {
|
||||
...mapActions('user', ['Login']),
|
||||
async execLogin() {
|
||||
await this.Login(this.user)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
@ -0,0 +1,100 @@
|
||||
<template>
|
||||
<div class="com-form-password">
|
||||
<el-form
|
||||
ref="formPassword"
|
||||
label-position="top"
|
||||
label-width="80px"
|
||||
:model="profile"
|
||||
>
|
||||
<el-form-item label="用户名">
|
||||
<el-input
|
||||
v-model="profile.username"
|
||||
placeholder="请输入您的登录用户名"
|
||||
:disabled="true"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="原密码"
|
||||
prop="old_password"
|
||||
:rules="[
|
||||
{ required: true, trigger: 'blur', message: '请输入您的原密码' },
|
||||
]"
|
||||
>
|
||||
<el-input v-model="profile.old_password" type="password"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="新密码"
|
||||
prop="new_password"
|
||||
:rules="[
|
||||
{ required: true, trigger: 'blur', message: '请输入您的新密码' },
|
||||
]"
|
||||
>
|
||||
<el-input v-model="profile.new_password" type="password"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="确认密码"
|
||||
prop="repeat_password"
|
||||
:rules="[
|
||||
{ required: true, trigger: 'blur', message: '请再次输入您的新密码' },
|
||||
]"
|
||||
>
|
||||
<el-input v-model="profile.repeat_password" type="password"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button
|
||||
type="primary"
|
||||
class="btn-block"
|
||||
icon="el-icon-check"
|
||||
@click="setPassword"
|
||||
>修改密码</el-button
|
||||
>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
import { setUserPassword } from '~/api/user'
|
||||
export default {
|
||||
name: 'FormProfile',
|
||||
data() {
|
||||
return {
|
||||
profile: {
|
||||
username: '',
|
||||
old_password: '',
|
||||
new_password: '',
|
||||
repeat_password: '',
|
||||
},
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters('user', ['user']),
|
||||
},
|
||||
created() {
|
||||
this.profile = {
|
||||
...this.profile,
|
||||
username: this.user.username,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
setPassword() {
|
||||
this.$refs.formPassword.validate(async (valid) => {
|
||||
if (valid) {
|
||||
if (this.profile.new_password !== this.profile.repeat_password) {
|
||||
this.$message.error('新密码和确认密码不一致')
|
||||
return
|
||||
}
|
||||
const res = await setUserPassword(this.profile)
|
||||
if (res.status === 200) {
|
||||
this.$message.success('密码修改成功')
|
||||
this.$refs.formPassword.resetFields()
|
||||
this.$emit('success', res)
|
||||
} else {
|
||||
this.$message.error(res.data.message)
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
@ -0,0 +1,65 @@
|
||||
<template>
|
||||
<div class="com-form-profile">
|
||||
<el-form label-position="top" label-width="80px" :model="profile">
|
||||
<el-form-item label="用户名">
|
||||
<el-input
|
||||
v-model="profile.username"
|
||||
placeholder="请输入您的登录用户名"
|
||||
:disabled="true"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="真实姓名"
|
||||
prop="realname"
|
||||
:rules="[
|
||||
{ required: true, trigger: 'blur', message: '请输入您的真实姓名' },
|
||||
]"
|
||||
>
|
||||
<el-input v-model="profile.realname"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="电子邮箱">
|
||||
<el-input v-model="profile.email"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="联系电话">
|
||||
<el-input v-model="profile.mobile"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button
|
||||
type="primary"
|
||||
class="btn-block"
|
||||
icon="el-icon-check"
|
||||
@click="setProfile"
|
||||
>修改资料</el-button
|
||||
>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { mapActions, mapGetters } from 'vuex'
|
||||
export default {
|
||||
name: 'FormProfile',
|
||||
data() {
|
||||
return {
|
||||
profile: {},
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters('user', ['user']),
|
||||
},
|
||||
created() {
|
||||
this.profile = {
|
||||
...this.user,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapActions('user', ['setUserProfile']),
|
||||
async setProfile() {
|
||||
const res = await this.setUserProfile(this.profile)
|
||||
if (res.status === 200) {
|
||||
this.$emit('success', res)
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
@ -0,0 +1,12 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"~/*": ["./*"],
|
||||
"@/*": ["./*"],
|
||||
"~~/*": ["./*"],
|
||||
"@@/*": ["./*"]
|
||||
}
|
||||
},
|
||||
"exclude": ["node_modules", ".nuxt", "dist"]
|
||||
}
|
@ -0,0 +1,235 @@
|
||||
<template>
|
||||
<el-container class="layout-admin">
|
||||
<el-aside :class="isCollapse ? 'layout-aside-collapsed' : ''">
|
||||
<div class="create-project">
|
||||
<el-tooltip
|
||||
v-if="isCollapse"
|
||||
class="item"
|
||||
effect="dark"
|
||||
content="上传文档"
|
||||
placement="right"
|
||||
>
|
||||
<el-button
|
||||
type="success"
|
||||
icon="el-icon-plus"
|
||||
class="btn-block"
|
||||
@click="showCreateProjectDialog"
|
||||
></el-button>
|
||||
</el-tooltip>
|
||||
<el-button
|
||||
v-else
|
||||
type="success"
|
||||
icon="el-icon-plus"
|
||||
@click="showCreateProjectDialog"
|
||||
>上传文档</el-button
|
||||
>
|
||||
</div>
|
||||
<transition
|
||||
:duration="{ enter: 800, leave: 800 }"
|
||||
mode="out-in"
|
||||
name="el-fade-in-linear"
|
||||
>
|
||||
<el-menu
|
||||
:router="true"
|
||||
:default-openeds="[
|
||||
'/admin/settings',
|
||||
'/admin/me',
|
||||
'/admin/templates',
|
||||
]"
|
||||
:collapse="isCollapse"
|
||||
class="layout-admin-menu"
|
||||
>
|
||||
<el-menu-item index="/admin/dashboard">
|
||||
<i class="el-icon-s-platform"></i>
|
||||
<span slot="title">面板</span></el-menu-item
|
||||
>
|
||||
<el-submenu index="/admin/me">
|
||||
<template slot="title"
|
||||
><i class="el-icon-user-solid"></i>
|
||||
<span slot="title">我的</span></template
|
||||
>
|
||||
<el-menu-item index="/admin/projects">
|
||||
<i class="el-icon-data-analysis"></i>
|
||||
我的项目</el-menu-item
|
||||
>
|
||||
<!-- <el-menu-item index="/admin/shares">我的分享</el-menu-item> -->
|
||||
<el-menu-item index="/admin/mytemplates"
|
||||
><i class="el-icon-tickets"></i> 我的模板</el-menu-item
|
||||
>
|
||||
<el-menu-item index="/admin/mycharts">
|
||||
<i class="el-icon-s-grid"></i> 我的模块</el-menu-item
|
||||
>
|
||||
</el-submenu>
|
||||
<el-submenu index="/admin/templates">
|
||||
<template slot="title"
|
||||
><i class="el-icon-s-shop"></i>
|
||||
<span slot="title">应用中心</span></template
|
||||
>
|
||||
<el-menu-item index="/admin/templates"
|
||||
><i class="el-icon-tickets"></i> 模板市场</el-menu-item
|
||||
>
|
||||
<el-menu-item index="/admin/charts"
|
||||
><i class="el-icon-s-grid"></i> 图表模块</el-menu-item
|
||||
>
|
||||
</el-submenu>
|
||||
<el-submenu index="/admin/settings">
|
||||
<template slot="title"
|
||||
><i class="el-icon-s-tools"></i>
|
||||
<span slot="title">系统管理</span></template
|
||||
>
|
||||
<el-menu-item index="/admin/settings"
|
||||
><i class="el-icon-setting"></i> 系统设置</el-menu-item
|
||||
>
|
||||
<el-menu-item index="/admin/users"
|
||||
><i class="el-icon-user"></i> 用户管理</el-menu-item
|
||||
>
|
||||
</el-submenu>
|
||||
</el-menu>
|
||||
</transition>
|
||||
</el-aside>
|
||||
<el-container>
|
||||
<el-header>
|
||||
<el-button
|
||||
v-if="isCollapse"
|
||||
class="fold"
|
||||
icon="el-icon-s-unfold"
|
||||
type="text"
|
||||
@click="isCollapse = false"
|
||||
></el-button>
|
||||
<el-button
|
||||
v-else
|
||||
class="fold"
|
||||
icon="el-icon-s-fold"
|
||||
type="text"
|
||||
@click="isCollapse = true"
|
||||
></el-button>
|
||||
<span>我的项目</span>
|
||||
<el-dropdown style="float: right" trigger="click" @command="command">
|
||||
<el-button>
|
||||
<i class="el-icon-user" style="margin-right: 15px"> </i>
|
||||
<span>{{ user.realname }}</span>
|
||||
</el-button>
|
||||
<el-dropdown-menu slot="dropdown">
|
||||
<el-dropdown-item command="profile"> 个人资料 </el-dropdown-item>
|
||||
<el-dropdown-item command="password"> 修改密码 </el-dropdown-item>
|
||||
<el-dropdown-item command="logout">退出登录</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
</el-header>
|
||||
<el-main>
|
||||
<nuxt />
|
||||
</el-main>
|
||||
<el-footer>
|
||||
<span>© 2019-2022</span>
|
||||
<span>Powered by</span>
|
||||
<a href="https://mnt.ltd" target="_blank" title="MOREDOC">MOREDOC · 魔刀文库</a>
|
||||
</el-footer>
|
||||
</el-container>
|
||||
<el-dialog title="个人资料" :visible.sync="formProfileVisible" width="30%">
|
||||
<FormProfile @success="profileSuccess" />
|
||||
</el-dialog>
|
||||
<el-dialog title="个人资料" :visible.sync="formPasswordVisible" width="30%">
|
||||
<FormPassword @success="passwordSuccess" />
|
||||
</el-dialog>
|
||||
<el-dialog
|
||||
title="创建项目"
|
||||
:visible.sync="createProjectVisible"
|
||||
width="30%"
|
||||
>
|
||||
<form-set-project @success="createProjectSuccess"></form-set-project>
|
||||
</el-dialog>
|
||||
</el-container>
|
||||
</template>
|
||||
<script>
|
||||
import { mapGetters, mapActions } from 'vuex'
|
||||
import FormProfile from '~/components/FormProfile.vue'
|
||||
import FormPassword from '~/components/FormPassword.vue'
|
||||
export default {
|
||||
components: {
|
||||
FormProfile,
|
||||
FormPassword,
|
||||
},
|
||||
middleware: ['auth'],
|
||||
data() {
|
||||
return {
|
||||
createProjectVisible: false,
|
||||
formProfileVisible: false,
|
||||
formPasswordVisible: false,
|
||||
isCollapse: false,
|
||||
}
|
||||
},
|
||||
head() {
|
||||
return {
|
||||
title: 'MOREDOC · 魔刀文库',
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters('user', ['user', 'token']),
|
||||
},
|
||||
watch: {
|
||||
// async $route(to, from) {
|
||||
// this.activePath = to.path
|
||||
// this.$refs.mobileMenu.close('mobileMenu')
|
||||
// },
|
||||
},
|
||||
mounted() {
|
||||
const screenWidth = document.body.clientWidth
|
||||
if (screenWidth < 1000) {
|
||||
this.isCollapse = !this.isCollapse
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapActions('user', ['Logout']),
|
||||
async createProject() {},
|
||||
showCreateProjectDialog() {
|
||||
this.createProjectVisible = true
|
||||
},
|
||||
createProjectSuccess(e) {
|
||||
this.createProjectVisible = false
|
||||
this.$router.push({
|
||||
path: '/admin/projects',
|
||||
query: { _t: new Date().getTime() },
|
||||
})
|
||||
},
|
||||
profileSuccess() {
|
||||
this.formProfileVisible = false
|
||||
},
|
||||
passwordSuccess() {
|
||||
this.formPasswordVisible = false
|
||||
},
|
||||
command(cmd) {
|
||||
switch (cmd) {
|
||||
case 'profile':
|
||||
this.formProfileVisible = true
|
||||
break
|
||||
case 'password':
|
||||
this.formPasswordVisible = true
|
||||
break
|
||||
case 'logout':
|
||||
this.Logout()
|
||||
this.$router.replace({ path: '/admin/login' })
|
||||
this.$message.success('退出成功')
|
||||
break
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.layout-aside-collapsed {
|
||||
width: 60px !important;
|
||||
overflow: hidden;
|
||||
.create-project {
|
||||
padding: 0;
|
||||
.el-button {
|
||||
height: 60px;
|
||||
line-height: 30px;
|
||||
padding: 0 5px;
|
||||
border-radius: 0;
|
||||
span {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -0,0 +1,88 @@
|
||||
<template>
|
||||
<div class="__nuxt-error-page">
|
||||
<div class="error">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="90"
|
||||
height="90"
|
||||
fill="#DBE1EC"
|
||||
viewBox="0 0 48 48"
|
||||
>
|
||||
<path
|
||||
d="M22 30h4v4h-4zm0-16h4v12h-4zm1.99-10C12.94 4 4 12.95 4 24s8.94 20 19.99 20S44 35.05 44 24 35.04 4 23.99 4zM24 40c-8.84 0-16-7.16-16-16S15.16 8 24 8s16 7.16 16 16-7.16 16-16 16z"
|
||||
/>
|
||||
</svg>
|
||||
<div class="title">
|
||||
<h1>{{ error.message }}</h1>
|
||||
<nuxt-link to="/">
|
||||
<el-button type="primary"
|
||||
><i class="el-icon-s-home"></i> 返回首页</el-button
|
||||
>
|
||||
</nuxt-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'ErrorLayout',
|
||||
props: ['error'],
|
||||
head() {
|
||||
return {
|
||||
title: this.message,
|
||||
meta: [
|
||||
{
|
||||
name: 'viewport',
|
||||
content: 'width=device-width,initial-scale=1.0,minimum-scale=1.0',
|
||||
},
|
||||
],
|
||||
}
|
||||
},
|
||||
computed: {},
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.__nuxt-error-page {
|
||||
padding: 1rem;
|
||||
background: #f7f8fb;
|
||||
color: #47494e;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
font-family: sans-serif;
|
||||
font-weight: 100 !important;
|
||||
-ms-text-size-adjust: 100%;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
.__nuxt-error-page .error {
|
||||
max-width: 450px;
|
||||
}
|
||||
.__nuxt-error-page .title {
|
||||
font-size: 1.5rem;
|
||||
margin-top: 15px;
|
||||
color: #47494e;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.__nuxt-error-page .description {
|
||||
color: #7f828b;
|
||||
line-height: 21px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.__nuxt-error-page a {
|
||||
color: #7f828b !important;
|
||||
text-decoration: none;
|
||||
}
|
||||
.__nuxt-error-page .logo {
|
||||
position: fixed;
|
||||
left: 12px;
|
||||
bottom: 12px;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,17 @@
|
||||
export default function ({ store, route, redirect }) {
|
||||
const token = store.getters['user/token']
|
||||
|
||||
// 如果已登录,则不允许在访问登录页
|
||||
if (route.name === 'admin-login' && token) {
|
||||
redirect('/admin')
|
||||
}
|
||||
|
||||
// 如果未登录,则不允许访问 /admin 前缀的页面
|
||||
if (
|
||||
!token &&
|
||||
route.name !== 'admin-login' &&
|
||||
route.path.indexOf('/admin') === 0
|
||||
) {
|
||||
redirect('/admin/login')
|
||||
}
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
export default {
|
||||
// Disable server-side rendering: https://go.nuxtjs.dev/ssr-mode
|
||||
ssr: false,
|
||||
|
||||
// Global page headers: https://go.nuxtjs.dev/config-head
|
||||
head: {
|
||||
title: 'web',
|
||||
meta: [
|
||||
{ charset: 'utf-8' },
|
||||
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
|
||||
{ hid: 'description', name: 'description', content: '' },
|
||||
{ name: 'format-detection', content: 'telephone=no' },
|
||||
],
|
||||
link: [{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }],
|
||||
},
|
||||
|
||||
generate: {
|
||||
dir: '../dist',
|
||||
},
|
||||
|
||||
// Global CSS: https://go.nuxtjs.dev/config-css
|
||||
css: ['element-ui/lib/theme-chalk/index.css', '~assets/css/app.scss'],
|
||||
|
||||
// Plugins to run before rendering page: https://go.nuxtjs.dev/config-plugins
|
||||
plugins: ['@/plugins/element-ui'],
|
||||
|
||||
// Auto import components: https://go.nuxtjs.dev/config-components
|
||||
components: true,
|
||||
|
||||
// Modules for dev and build (recommended): https://go.nuxtjs.dev/config-modules
|
||||
buildModules: [
|
||||
// https://go.nuxtjs.dev/eslint
|
||||
// '@nuxtjs/eslint-module',
|
||||
],
|
||||
|
||||
// Modules: https://go.nuxtjs.dev/config-modules
|
||||
modules: [
|
||||
// https://go.nuxtjs.dev/axios
|
||||
'@nuxtjs/axios',
|
||||
// https://go.nuxtjs.dev/pwa
|
||||
'@nuxtjs/pwa',
|
||||
],
|
||||
|
||||
// Axios module configuration: https://go.nuxtjs.dev/config-axios
|
||||
axios: {
|
||||
// Workaround to avoid enforcing hard-coded localhost:3000: https://github.com/nuxt-community/axios-module/issues/308
|
||||
baseURL: '/',
|
||||
proxy: true,
|
||||
},
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'http://127.0.0.1:8880', // 目标服务器
|
||||
changeOrigin: true,
|
||||
},
|
||||
'/uploads': {
|
||||
target: 'http://127.0.0.1:8080', // 目标服务器
|
||||
changeOrigin: true,
|
||||
},
|
||||
},
|
||||
// PWA module configuration: https://go.nuxtjs.dev/pwa
|
||||
pwa: {
|
||||
manifest: {
|
||||
lang: 'en',
|
||||
},
|
||||
},
|
||||
|
||||
// Build Configuration: https://go.nuxtjs.dev/config-build
|
||||
build: {
|
||||
postcss: null,
|
||||
transpile: [/^element-ui/],
|
||||
},
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,44 @@
|
||||
{
|
||||
"name": "web",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "nuxt",
|
||||
"build": "nuxt build",
|
||||
"start": "nuxt start",
|
||||
"generate": "nuxt generate",
|
||||
"lint:js": "eslint --ext \".js,.vue\" --ignore-path .gitignore .",
|
||||
"lint:prettier": "prettier --check .",
|
||||
"lint": "npm run lint:js && npm run lint:prettier",
|
||||
"lintfix": "prettier --write --list-different . && npm run lint:js -- --fix"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nuxtjs/axios": "^5.13.6",
|
||||
"@nuxtjs/pwa": "^3.3.5",
|
||||
"core-js": "^3.19.3",
|
||||
"element-ui": "^2.15.6",
|
||||
"hotkeys-js": "^3.10.0",
|
||||
"nuxt": "^2.15.8",
|
||||
"vue": "^2.6.14",
|
||||
"vue-marquee-text-component": "^1.2.0",
|
||||
"vue-server-renderer": "^2.6.14",
|
||||
"vue-template-compiler": "^2.6.14",
|
||||
"vuex-persist": "^3.1.3",
|
||||
"webpack": "^4.46.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/eslint-parser": "^7.16.3",
|
||||
"@nuxtjs/eslint-config": "^8.0.0",
|
||||
"@nuxtjs/eslint-module": "^3.0.2",
|
||||
"eslint": "^8.4.1",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-plugin-nuxt": "^3.1.0",
|
||||
"eslint-plugin-vue": "^8.2.0",
|
||||
"prettier": "^2.5.1",
|
||||
"sass": "^1.32.11",
|
||||
"sass-loader": "^10.1.1",
|
||||
"stylelint": "^13.12.0",
|
||||
"stylelint-config-prettier": "^8.0.2",
|
||||
"stylelint-config-standard": "^21.0.0"
|
||||
}
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
<template>
|
||||
<div class="page page-notfound">
|
||||
<div class="notfound">
|
||||
<img src="/static/images/404.png" alt="404" />
|
||||
<h3 class="mgt-15px">Page Not Found</h3>
|
||||
<nuxt-link to="/">
|
||||
<el-button type="primary"
|
||||
><i class="el-icon-s-home"></i> 返回首页</el-button
|
||||
>
|
||||
</nuxt-link>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
head() {
|
||||
return {
|
||||
title: '404 - Not Found - MOREDOC · 魔刀文库',
|
||||
meta: [
|
||||
{
|
||||
hid: 'keywords',
|
||||
name: 'keywords',
|
||||
content: '404,Not Found',
|
||||
},
|
||||
{
|
||||
hid: 'description',
|
||||
name: 'description',
|
||||
content: '内容不存在',
|
||||
},
|
||||
],
|
||||
}
|
||||
},
|
||||
watch: {},
|
||||
async created() {},
|
||||
mounted() {},
|
||||
methods: {},
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.page-notfound {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
padding-top: 200px;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,16 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>Welcome to MOREDOC!</h1>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
layout: 'admin',
|
||||
head() {
|
||||
return {
|
||||
title: `面板 - MOREDOC · 魔刀文库`,
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
@ -0,0 +1,13 @@
|
||||
<template>
|
||||
<div></div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'AdminIndex',
|
||||
layout: 'admin',
|
||||
created() {
|
||||
this.$router.push('/admin/dashboard')
|
||||
},
|
||||
}
|
||||
</script>
|
@ -0,0 +1,17 @@
|
||||
import Vue from 'vue'
|
||||
import Element from 'element-ui'
|
||||
import hotkeys from 'hotkeys-js'
|
||||
|
||||
// import locale from 'element-ui/lib/locale/lang/en'
|
||||
// Vue.use(Element, { locale })
|
||||
|
||||
import zhLocale from 'element-ui/lib/locale/lang/zh-CN'
|
||||
|
||||
Vue.use(Element, { zhLocale })
|
||||
// 以便光标在输入框时快捷键同样有效
|
||||
hotkeys.filter = (e) => {
|
||||
return true
|
||||
}
|
||||
|
||||
// Vue2 引入快捷键
|
||||
Vue.prototype.$hotkeys = hotkeys
|
After Width: | Height: | Size: 8.4 KiB |
After Width: | Height: | Size: 8.5 KiB |
After Width: | Height: | Size: 5.4 KiB |
After Width: | Height: | Size: 146 KiB |
@ -0,0 +1,10 @@
|
||||
# STORE
|
||||
|
||||
**This directory is not required, you can delete it if you don't want to use it.**
|
||||
|
||||
This directory contains your Vuex Store files.
|
||||
Vuex Store option is implemented in the Nuxt.js framework.
|
||||
|
||||
Creating a file in this directory automatically activates the option in the framework.
|
||||
|
||||
More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/vuex-store).
|
@ -0,0 +1,21 @@
|
||||
import Vue from 'vue'
|
||||
import Vuex from 'vuex'
|
||||
import VuexPersistence from 'vuex-persist'
|
||||
import { user } from '~/store/module/user'
|
||||
|
||||
Vue.use(Vuex)
|
||||
|
||||
const vuexLocal = new VuexPersistence({
|
||||
storage: window.localStorage,
|
||||
modules: ['user'],
|
||||
})
|
||||
|
||||
const store = () =>
|
||||
new Vuex.Store({
|
||||
modules: {
|
||||
user,
|
||||
},
|
||||
plugins: [vuexLocal.plugin],
|
||||
})
|
||||
|
||||
export default store
|
@ -0,0 +1,84 @@
|
||||
import { Message } from 'element-ui'
|
||||
import { login, getUser, setUserProfile } from '~/api/user'
|
||||
export const user = {
|
||||
namespaced: true,
|
||||
state: {
|
||||
user: {
|
||||
username: '',
|
||||
realname: '',
|
||||
email: '',
|
||||
mobile: '',
|
||||
avatar: '',
|
||||
status: false,
|
||||
limit: 0,
|
||||
},
|
||||
token: '',
|
||||
},
|
||||
mutations: {
|
||||
setUser(state, user) {
|
||||
state.user = user
|
||||
},
|
||||
setToken(state, token) {
|
||||
state.token = token
|
||||
},
|
||||
logout(state) {
|
||||
state.user = {}
|
||||
state.token = ''
|
||||
localStorage.clear()
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
// 获取用户信息
|
||||
async GetUser({ commit }) {
|
||||
const res = await getUser()
|
||||
if (res.status === 200) {
|
||||
commit('setUser', res.data.data.user)
|
||||
}
|
||||
return res
|
||||
},
|
||||
async setUserProfile({ commit }, profile) {
|
||||
const res = await setUserProfile(profile)
|
||||
if (res.status === 200) {
|
||||
commit('setUser', res.data.data)
|
||||
Message({
|
||||
type: 'success',
|
||||
message: '修改成功',
|
||||
})
|
||||
} else {
|
||||
Message({
|
||||
type: 'error',
|
||||
message: res.data.message || '修改失败',
|
||||
})
|
||||
}
|
||||
return res
|
||||
},
|
||||
async Login({ commit }, loginInfo) {
|
||||
const res = await login(loginInfo)
|
||||
if (res.status === 200) {
|
||||
commit('setUser', res.data.data.user)
|
||||
commit('setToken', res.data.data.token)
|
||||
Message({
|
||||
type: 'success',
|
||||
message: '登录成功',
|
||||
})
|
||||
location.reload()
|
||||
} else {
|
||||
Message({
|
||||
type: 'error',
|
||||
message: res.data.message || '登录失败',
|
||||
})
|
||||
}
|
||||
},
|
||||
Logout({ commit }) {
|
||||
commit('logout')
|
||||
},
|
||||
},
|
||||
getters: {
|
||||
user(state) {
|
||||
return state.user
|
||||
},
|
||||
token(state) {
|
||||
return state.token
|
||||
},
|
||||
},
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
import axios from 'axios' // 引入axios
|
||||
import store from '~/store/index'
|
||||
|
||||
const service = axios.create({
|
||||
timeout: 30000,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
})
|
||||
|
||||
// http request 拦截器
|
||||
service.interceptors.request.use(
|
||||
(config) => {
|
||||
const token = store().getters['user/token'] || ''
|
||||
if (token) config.headers.authorization = `Bearer ${token}`
|
||||
return config
|
||||
},
|
||||
(error) => {
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
|
||||
// http response 拦截器
|
||||
service.interceptors.response.use(
|
||||
(response) => {
|
||||
return response
|
||||
},
|
||||
(error) => {
|
||||
if (error.response.status === 401) {
|
||||
store().commit('user/logout')
|
||||
}
|
||||
// let message = error.response.data.message || error.response.statusText
|
||||
// Message({
|
||||
// showClose: true,
|
||||
// message: message,
|
||||
// type: 'error',
|
||||
// })
|
||||
return error.response
|
||||
}
|
||||
)
|
||||
|
||||
export default service
|
Loading…
Reference in new issue