Browse Source

初始化6

master
Viviman 2 years ago
parent
commit
529be38e71
  1. 11
      web/src/App.vue
  2. 52
      web/src/main.js
  3. 50
      web/src/settings.js
  4. 124
      web/src/views/components/Echarts.vue
  5. 73
      web/src/views/components/Editor.vue
  6. 44
      web/src/views/components/MarkDown.vue
  7. 207
      web/src/views/components/YamlEdit.vue
  8. 74
      web/src/views/components/icons/element-icons.js
  9. 91
      web/src/views/components/icons/index.vue
  10. 10
      web/src/views/components/icons/svg-icons.js
  11. 108
      web/src/views/dashboard/BarChart.vue
  12. 111
      web/src/views/dashboard/BarChartT.vue
  13. 155
      web/src/views/dashboard/LineChart.vue
  14. 121
      web/src/views/dashboard/OrderCount.vue
  15. 233
      web/src/views/dashboard/PanelGroup.vue
  16. 188
      web/src/views/dashboard/PanelGroupT.vue
  17. 91
      web/src/views/dashboard/PieChart.vue
  18. 120
      web/src/views/dashboard/RaddarChart.vue
  19. 107
      web/src/views/dashboard/YshopInfo.vue
  20. 89
      web/src/views/features/401.vue
  21. 225
      web/src/views/features/404.vue
  22. 12
      web/src/views/features/redirect.vue
  23. 138
      web/src/views/home.vue
  24. 198
      web/src/views/login.vue
  25. 3
      web/src/views/nested/menu1/index.vue
  26. 5
      web/src/views/nested/menu1/menu1-1/index.vue
  27. 5
      web/src/views/nested/menu1/menu1-2/index.vue
  28. 5
      web/src/views/nested/menu2/index.vue
  29. 228
      web/src/views/system/dept/index.vue
  30. 114
      web/src/views/system/dict/dictDetail.vue
  31. 160
      web/src/views/system/dict/index.vue
  32. 120
      web/src/views/system/job/index.vue
  33. 149
      web/src/views/system/job/module/form.vue
  34. 32
      web/src/views/system/job/module/header.vue
  35. 206
      web/src/views/system/menu/index.vue
  36. 279
      web/src/views/system/role/index.vue
  37. 218
      web/src/views/system/user/center.vue
  38. 95
      web/src/views/system/user/center/updatePass.vue
  39. 425
      web/src/views/system/user/index.vue
  40. 16
      web/src/views/tools/swagger/index.vue

11
web/src/App.vue

@ -0,0 +1,11 @@
<template>
<div id="app">
<router-view />
</div>
</template>
<script>
export default {
name: 'App'
}
</script>

52
web/src/main.js

@ -0,0 +1,52 @@
import Vue from 'vue'
import Cookies from 'js-cookie'
import 'normalize.css/normalize.css'
import Element from 'element-ui'
//
import mavonEditor from 'mavon-editor'
import 'mavon-editor/dist/css/index.css'
// 数据字典
import dict from './components/Dict'
// 权限指令
import permission from './components/Permission'
import './assets/styles/element-variables.scss'
// global css
import './assets/styles/index.scss'
// 代码高亮
import VueHighlightJS from 'vue-highlightjs'
import 'highlight.js/styles/atom-one-dark.css'
import App from './App'
import store from './store'
import router from './router/routers'
import './assets/icons' // icon
import './router/index' // permission control
import { VueJsonp } from 'vue-jsonp'
// Vue.use(VueJsonp)
Vue.use(VueHighlightJS)
Vue.use(mavonEditor)
Vue.use(permission)
Vue.use(dict)
Vue.use(Element, {
size: Cookies.get('size') || 'small' // set element-ui default size
})
Vue.use(VueJsonp)
Vue.config.productionTip = false
new Vue({
el: '#app',
router,
store,
render: h => h(App)
})

50
web/src/settings.js

@ -0,0 +1,50 @@
module.exports = {
/**
* @description 网站标题
*/
title: 'VIVIB-V1.0',
/**
* @description 是否显示 tagsView
*/
tagsView: true,
/**
* @description 固定头部
*/
fixedHeader: true,
/**
* @description 记住密码状态下的token在Cookie中存储的天数默认1天
*/
tokenCookieExpires: 1,
/**
* @description 记住密码状态下的密码在Cookie中存储的天数默认1天s
*/
passCookieExpires: 1,
/**
* @description 是否只保持一个子菜单的展开
*/
uniqueOpened: true,
/**
* @description token key
*/
TokenKey: 'YSHOP-TOEKN',
/**
* @description 请求超时时间毫秒默认2分钟
*/
timeout: 1200000,
/**
* @description 是否显示logo
*/
sidebarLogo: true,
/**
* 是否显示设置的底部信息
*/
showFooter: true,
/**
* 底部文字支持html语法
*/
footerTxt: '© 2019-2020 YSHOP <a href="http://www.apache.org/licenses/LICENSE-2.0" target="_blank">All Rights Reserved</a>',
/**
* 备案号
*/
caseNumber: '豫ICP备17049587-2号'
}

124
web/src/views/components/Echarts.vue

@ -0,0 +1,124 @@
<template>
<div class="dashboard-container">
<div class="dashboard-editor-container">
<el-row style="background:#fff;padding:16px 16px 0;margin-bottom:32px;">
<heat-map />
</el-row>
<el-row :gutter="32">
<el-col :xs="24" :sm="24" :lg="8">
<div class="chart-wrapper">
<radar-chart />
</div>
</el-col>
<el-col :xs="24" :sm="24" :lg="8">
<div class="chart-wrapper">
<sunburst />
</div>
</el-col>
<el-col :xs="24" :sm="24" :lg="8">
<div class="chart-wrapper">
<gauge />
</div>
</el-col>
</el-row>
<el-row :gutter="12">
<el-col :span="12">
<div class="chart-wrapper">
<rich />
</div>
</el-col>
<el-col :span="12">
<div class="chart-wrapper">
<theme-river />
</div>
</el-col>
</el-row>
<el-row :gutter="32">
<el-col :xs="24" :sm="24" :lg="8">
<div class="chart-wrapper">
<graph />
</div>
</el-col>
<el-col :xs="24" :sm="24" :lg="8">
<div class="chart-wrapper">
<sankey />
</div>
</el-col>
<el-col :xs="24" :sm="24" :lg="8">
<div class="chart-wrapper">
<line3-d />
</div>
</el-col>
</el-row>
<el-row :gutter="12">
<el-col :span="12">
<div class="chart-wrapper">
<scatter />
</div>
</el-col>
<el-col :span="12">
<div class="chart-wrapper">
<point />
</div>
</el-col>
</el-row>
<el-row style="background:#fff;padding:16px 16px 0;margin-bottom:32px;">
<div class="chart-wrapper">
<word-cloud />
</div>
</el-row>
<el-row style="background:#fff;padding:16px 16px 0;margin-bottom:32px;">
<div class="chart-wrapper">
<category />
</div>
</el-row>
</div>
</div>
</template>
<script>
import RadarChart from '@/components/Echarts/RadarChart'
import HeatMap from '@/components/Echarts/HeatMap'
import Gauge from '@/components/Echarts/Gauge'
import Rich from '@/components/Echarts/Rich'
import ThemeRiver from '@/components/Echarts/ThemeRiver'
import Sunburst from '@/components/Echarts/Sunburst'
import Graph from '@/components/Echarts/Graph'
import Sankey from '@/components/Echarts/Sankey'
import Scatter from '@/components/Echarts/Scatter'
import Line3D from '@/components/Echarts/Line3D'
import Category from '@/components/Echarts/Category'
import Point from '@/components/Echarts/Point'
import WordCloud from '@/components/Echarts/WordCloud'
export default {
name: 'Echarts',
components: {
Point,
Category,
Graph,
HeatMap,
RadarChart,
Sunburst,
Gauge,
Rich,
ThemeRiver,
Sankey,
Line3D,
Scatter,
WordCloud
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
.dashboard-editor-container {
padding: 18px 22px 22px 22px;
background-color: rgb(240, 242, 245);
.chart-wrapper {
background: #fff;
padding: 16px 16px 0;
margin-bottom: 32px;
}
}
</style>

73
web/src/views/components/Editor.vue

@ -0,0 +1,73 @@
<template>
<div>
<div ref="editor" style="text-align:left;margin: 5px;width: 700px" />
</div>
</template>
<script>
import { mapGetters } from 'vuex'
import E from 'wangeditor'
import { getToken } from '@/utils/auth'
export default {
name: 'Editor',
model: {
prop: 'value',
event: 'change'
},
props: {
value: {
type: String,
default: ''
}
},
data() {
return {
headers: {
'Authorization': getToken()
},
info: null,
editor: null
}
},
computed: {
...mapGetters([
'uploadApi'
])
},
watch: {
value: function(val) {
this.editor.txt.html(val)
// this.editor.txt.html(val)
}
},
mounted() {
this.editor = new E(this.$refs.editor)
this.editor.customConfig = this.editor.customConfig ? this.editor.customConfig : this.editor.config
this.editor.customConfig.uploadImgShowBase64 = true // 使 base64
//
this.editor.customConfig.uploadImgHeaders = this.headers
//
this.editor.customConfig.uploadFileName = 'file'
this.editor.customConfig.uploadImgServer = this.uploadApi //
this.editor.customConfig.uploadImgHooks = {
customInsert: function(insertImg, result, editor) {
var url = result.link
insertImg(url)
}
}
this.editor.customConfig.onchange = (html) => {
this.info = html
this.$emit('change', this.info)
this.$emit('input', this.info)
}
this.editor.create()
this.editor.txt.html(this.value)
}
}
</script>
<style scoped>
.editor-content{
padding-left: 5px;
}
</style>

44
web/src/views/components/MarkDown.vue

@ -0,0 +1,44 @@
<template>
<div class="app-container">
<p class="warn-content">
Markdown 基于
<el-link type="primary" href="https://github.com/hinesboy/mavonEditor" target="_blank">MavonEditor</el-link>
图片上传使用 <el-link type="primary" href="https://sm.ms/" target="_blank">SM.MS</el-link>
</p>
<mavon-editor ref="md" :style="'height:' + height" @imgAdd="imgAdd" />
</div>
</template>
<script>
import { upload } from '@/utils/upload'
import { mapGetters } from 'vuex'
export default {
name: 'Markdown',
data() {
return {
height: document.documentElement.clientHeight - 200 + 'px'
}
},
computed: {
...mapGetters([
'imagesUploadApi'
])
},
mounted() {
const that = this
window.onresize = function temp() {
that.height = document.documentElement.clientHeight - 200 + 'px'
}
},
methods: {
imgAdd(pos, $file) {
upload(this.imagesUploadApi, $file).then(data => {
this.$refs.md.$img2Url(pos, data.data.url)
})
}
}
}
</script>
<style scoped>
</style>

207
web/src/views/components/YamlEdit.vue

@ -0,0 +1,207 @@
<template>
<div class="app-container">
<p class="warn-content">
Yaml编辑器 基于
<a href="https://github.com/codemirror/CodeMirror" target="_blank">CodeMirror</a>
主题预览地址 <a href="https://codemirror.net/demo/theme.html#idea" target="_blank">Theme</a>
</p>
<Yaml :value="value" :height="height" />
</div>
</template>
<script>
import Yaml from '@/components/YamlEdit/index'
export default {
name: 'YamlEdit',
components: { Yaml },
data() {
return {
height: document.documentElement.clientHeight - 210 + 'px',
value: '# 展示数据,如需更换主题,请在src/components/YamlEdit 目录中搜索原主题名称进行替换\n' +
'\n' +
'# ===================================================================\n' +
'# Spring Boot configuration.\n' +
'#\n' +
'# This configuration will be overridden by the Spring profile you use,\n' +
'# for example application-dev.yml if you use the "dev" profile.\n' +
'#\n' +
'# More information on profiles: https://www.jhipster.tech/profiles/\n' +
'# More information on configuration properties: https://www.jhipster.tech/common-application-properties/\n' +
'# ===================================================================\n' +
'\n' +
'# ===================================================================\n' +
'# Standard Spring Boot properties.\n' +
'# Full reference is available at:\n' +
'# http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html\n' +
'# ===================================================================\n' +
'\n' +
'eureka:\n' +
' client:\n' +
' enabled: true\n' +
' healthcheck:\n' +
' enabled: true\n' +
' fetch-registry: true\n' +
' register-with-eureka: true\n' +
' instance-info-replication-interval-seconds: 10\n' +
' registry-fetch-interval-seconds: 10\n' +
' instance:\n' +
' appname: product\n' +
' instanceId: product:${spring.application.instance-id:${random.value}}\n' +
' #instanceId: 127.0.0.1:9080\n' +
' lease-renewal-interval-in-seconds: 5\n' +
' lease-expiration-duration-in-seconds: 10\n' +
' status-page-url-path: ${management.endpoints.web.base-path}/info\n' +
' health-check-url-path: ${management.endpoints.web.base-path}/health\n' +
' metadata-map:\n' +
' zone: primary # This is needed for the load balancer\n' +
' profile: ${spring.profiles.active}\n' +
' version: ${info.project.version:}\n' +
' git-version: ${git.commit.id.describe:}\n' +
' git-commit: ${git.commit.id.abbrev:}\n' +
' git-branch: ${git.branch:}\n' +
'ribbon:\n' +
' ReadTimeout: 120000\n' +
' ConnectTimeout: 300000\n' +
' eureka:\n' +
' enabled: true\n' +
'zuul:\n' +
' host:\n' +
' connect-timeout-millis: 5000\n' +
' max-per-route-connections: 10000\n' +
' max-total-connections: 5000\n' +
' socket-timeout-millis: 60000\n' +
' semaphore:\n' +
' max-semaphores: 500\n' +
'\n' +
'feign:\n' +
' hystrix:\n' +
' enabled: true\n' +
' client:\n' +
' config:\n' +
' default:\n' +
' connectTimeout: 500000\n' +
' readTimeout: 500000\n' +
'\n' +
'# See https://github.com/Netflix/Hystrix/wiki/Configuration\n' +
'hystrix:\n' +
' command:\n' +
' default:\n' +
' circuitBreaker:\n' +
' sleepWindowInMilliseconds: 100000\n' +
' forceClosed: true\n' +
' execution:\n' +
' isolation:\n' +
'# strategy: SEMAPHORE\n' +
'# See https://github.com/spring-cloud/spring-cloud-netflix/issues/1330\n' +
' thread:\n' +
' timeoutInMilliseconds: 60000\n' +
' shareSecurityContext: true\n' +
'\n' +
'management:\n' +
' endpoints:\n' +
' web:\n' +
' base-path: /management\n' +
' exposure:\n' +
' include: ["configprops", "env", "health", "info", "threaddump"]\n' +
' endpoint:\n' +
' health:\n' +
' show-details: when_authorized\n' +
' info:\n' +
' git:\n' +
' mode: full\n' +
' health:\n' +
' mail:\n' +
' enabled: false # When using the MailService, configure an SMTP server and set this to true\n' +
' metrics:\n' +
' enabled: false # http://micrometer.io/ is disabled by default, as we use http://metrics.dropwizard.io/ instead\n' +
'\n' +
'spring:\n' +
' application:\n' +
' name: product\n' +
' jpa:\n' +
' open-in-view: false\n' +
' hibernate:\n' +
' ddl-auto: update\n' +
' naming:\n' +
' physical-strategy: org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy\n' +
' implicit-strategy: org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy\n' +
' messages:\n' +
' basename: i18n/messages\n' +
' mvc:\n' +
' favicon:\n' +
' enabled: false\n' +
' thymeleaf:\n' +
' mode: HTML\n' +
'security:\n' +
' oauth2:\n' +
' resource:\n' +
' filter-order: 3\n' +
'\n' +
'server:\n' +
' servlet:\n' +
' session:\n' +
' cookie:\n' +
' http-only: true\n' +
'\n' +
'# Properties to be exposed on the /info management endpoint\n' +
'info:\n' +
' # Comma separated list of profiles that will trigger the ribbon to show\n' +
' display-ribbon-on-profiles: "dev"\n' +
'\n' +
'# ===================================================================\n' +
'# JHipster specific properties\n' +
'#\n' +
'# Full reference is available at: https://www.jhipster.tech/common-application-properties/\n' +
'# ===================================================================\n' +
'\n' +
'jhipster:\n' +
' async:\n' +
' core-pool-size: 2\n' +
' max-pool-size: 50\n' +
' queue-capacity: 10000\n' +
' # By default CORS is disabled. Uncomment to enable.\n' +
' #cors:\n' +
' #allowed-origins: "*"\n' +
' #allowed-methods: "*"\n' +
' #allowed-headers: "*"\n' +
' #exposed-headers: "Authorization,Link,X-Total-Count"\n' +
' #allow-credentials: true\n' +
' #max-age: 1800\n' +
' mail:\n' +
' from: product@localhost\n' +
' swagger:\n' +
' default-include-pattern: /api/.*\n' +
' title: product API\n' +
' description: product API documentation\n' +
' version: 0.0.1\n' +
' terms-of-service-url:\n' +
' contact-name:\n' +
' contact-url:\n' +
' contact-email:\n' +
' license:\n' +
' license-url:\n' +
'\n' +
'# ===================================================================\n' +
'# Application specific properties\n' +
'# Add your own application properties here, see the ApplicationProperties class\n' +
'# to have type-safe configuration, like in the JHipsterProperties above\n' +
'#\n' +
'# More documentation is available at:\n' +
'# https://www.jhipster.tech/common-application-properties/\n' +
'# ===================================================================\n' +
'\n' +
'# application:\n'
}
},
mounted() {
const that = this
window.onresize = function temp() {
that.height = document.documentElement.clientHeight - 210 + 'px'
}
}
}
</script>
<style scoped>
</style>

74
web/src/views/components/icons/element-icons.js

@ -0,0 +1,74 @@
const elementIcons = [
'info',
'error',
'success',
'warning',
'question',
'back',
'arrow-left',
'arrow-down',
'arrow-right',
'arrow-up',
'caret-left',
'caret-bottom',
'caret-top',
'caret-right',
'd-arrow-left',
'd-arrow-right',
'minus',
'plus',
'remove',
'circle-plus',
'remove-outline',
'circle-plus-outline',
'close',
'check',
'circle-close',
'circle-check',
'circle-close-outline',
'circle-check-outline',
'zoom-out',
'zoom-in',
'd-caret',
'sort',
'sort-down',
'sort-up',
'tickets',
'document',
'goods',
'sold-out',
'news',
'message',
'date',
'printer',
'time',
'bell',
'mobile-phone',
'service',
'view',
'menu',
'more',
'more-outline',
'star-on',
'star-off',
'location',
'location-outline',
'phone',
'phone-outline',
'picture',
'picture-outline',
'delete',
'search',
'edit',
'edit-outline',
'rank',
'refresh',
'share',
'setting',
'upload',
'upload2',
'download',
'loading'
]
export default elementIcons

91
web/src/views/components/icons/index.vue

@ -0,0 +1,91 @@
<template>
<div class="icons-container">
<aside>
<a href="https://panjiachen.github.io/vue-element-admin-site/guide/advanced/icon.html" target="_blank">Add and use
</a>
</aside>
<el-tabs type="border-card">
<el-tab-pane label="Icons">
<div v-for="item of svgIcons" :key="item" @click="handleClipboard(generateIconCode(item),$event)">
<el-tooltip placement="top">
<div slot="content">
{{ generateIconCode(item) }}
</div>
<div class="icon-item">
<svg-icon :icon-class="item" class-name="disabled" />
<span>{{ item }}</span>
</div>
</el-tooltip>
</div>
</el-tab-pane>
<el-tab-pane label="Element-UI Icons">
<div v-for="item of elementIcons" :key="item" @click="handleClipboard(generateElementIconCode(item),$event)">
<el-tooltip placement="top">
<div slot="content">
{{ generateElementIconCode(item) }}
</div>
<div class="icon-item">
<i :class="'el-icon-' + item" />
<span>{{ item }}</span>
</div>
</el-tooltip>
</div>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script>
import clipboard from '@/utils/clipboard'
import svgIcons from './svg-icons'
import elementIcons from './element-icons'
export default {
name: 'Icons',
data() {
return {
svgIcons,
elementIcons
}
},
methods: {
generateIconCode(symbol) {
return `<svg-icon icon-class="${symbol}" />`
},
generateElementIconCode(symbol) {
return `<i class="el-icon-${symbol}" />`
},
handleClipboard(text, event) {
clipboard(text, event)
}
}
}
</script>
<style lang="scss" scoped>
.icons-container {
margin: 10px 20px 0;
overflow: hidden;
.icon-item {
margin: 20px;
height: 85px;
text-align: center;
width: 100px;
float: left;
font-size: 30px;
color: #24292e;
cursor: pointer;
}
span {
display: block;
font-size: 16px;
margin-top: 10px;
}
.disabled {
pointer-events: none;
}
}
</style>

10
web/src/views/components/icons/svg-icons.js

@ -0,0 +1,10 @@
const req = require.context('../../../assets/icons/svg', false, /\.svg$/)
const requireAll = requireContext => requireContext.keys()
const re = /\.\/(.*)\.svg/
const svgIcons = requireAll(req).map(i => {
return i.match(re)[1]
})
export default svgIcons

108
web/src/views/dashboard/BarChart.vue

@ -0,0 +1,108 @@
<template>
<div :class="className" :style="{height:height,width:width}" />
</template>
<script>
import echarts from 'echarts'
require('echarts/theme/macarons') // echarts theme
import { debounce } from '@/utils'
const animationDuration = 6000
export default {
props: {
className: {
type: String,
default: 'chart'
},
width: {
type: String,
default: '100%'
},
height: {
type: String,
default: '300px'
}
},
data() {
return {
chart: null,
day: [], num: []
}
},
mounted() {
this.initChart()
this.__resizeHandler = debounce(() => {
if (this.chart) {
this.chart.resize()
}
}, 100)
window.addEventListener('resize', this.__resizeHandler)
},
beforeDestroy() {
if (!this.chart) {
return
}
window.removeEventListener('resize', this.__resizeHandler)
this.chart.dispose()
this.chart = null
},
methods: {
initChart() {
this.chart = echarts.init(this.$el, 'macarons')
var _infostr = '{"chart":[{"num":3.02,"time":"02-01"},{"num":13.0,"time":"02-02"},{"num":12.0,"time":"02-03"},{"num":506.0,"time":"02-04"},{"num":2.01,"time":"02-06"},{"num":20.01,"time":"02-07"},{"num":5040.0,"time":"02-08"},{"num":11.0,"time":"02-10"},{"num":451.0,"time":"02-14"},{"num":0.0,"time":"02-15"},{"num":7.0,"time":"02-16"}],"chartT":[{"num":3.0,"time":"02-01"},{"num":2.0,"time":"02-02"},{"num":2.0,"time":"02-03"},{"num":1.0,"time":"02-04"},{"num":4.0,"time":"02-06"},{"num":2.0,"time":"02-07"},{"num":5.0,"time":"02-08"},{"num":2.0,"time":"02-10"},{"num":3.0,"time":"02-14"},{"num":1.0,"time":"02-15"},{"num":1.0,"time":"02-16"}]}'
var _info1 = eval('(' + _infostr + ')');
var _info = _info1.chart
var day = []
var num = []
_info.forEach(function(item) {
day.push(item.time)
num.push(item.num)
})
console.log(day, num)
this.chart.setOption({
tooltip: {
trigger: 'axis',
axisPointer: { //
type: 'shadow' // 线'line' | 'shadow'
}
},
grid: {
top: 10,
left: '2%',
right: '2%',
bottom: '3%',
containLabel: true
},
xAxis: [{
type: 'category',
data: day,
axisTick: {
alignWithLabel: true
}
}],
yAxis: [{
type: 'value',
axisTick: {
show: false
}
}],
series: [{
name: 'pageA',
type: 'line',
stack: 'vistors',
barWidth: '60%',
data: num,
animationDuration
}]
})
console.log('day:' + this.day)
}
}
}
</script>

111
web/src/views/dashboard/BarChartT.vue

@ -0,0 +1,111 @@
<template>
<div :class="className" :style="{height:height,width:width}" />
</template>
<script>
import echarts from 'echarts'
require('echarts/theme/macarons') // echarts theme
import { debounce } from '@/utils'
const animationDuration = 6000
export default {
props: {
className: {
type: String,
default: 'chart'
},
width: {
type: String,
default: '100%'
},
height: {
type: String,
default: '300px'
}
},
data() {
return {
chart: null,
day: [], num: []
}
},
mounted() {
this.initChart()
this.__resizeHandler = debounce(() => {
if (this.chart) {
this.chart.resize()
}
}, 100)
window.addEventListener('resize', this.__resizeHandler)
},
beforeDestroy() {
if (!this.chart) {
return
}
window.removeEventListener('resize', this.__resizeHandler)
this.chart.dispose()
this.chart = null
},
methods: {
initChart() {
this.chart = echarts.init(this.$el, 'macarons')
//chart().then(res => {
//var _info = res.chartT
var _infostr = '{"chart":[{"num":3.02,"time":"02-01"},{"num":13.0,"time":"02-02"},{"num":12.0,"time":"02-03"},{"num":506.0,"time":"02-04"},{"num":2.01,"time":"02-06"},{"num":20.01,"time":"02-07"},{"num":5040.0,"time":"02-08"},{"num":11.0,"time":"02-10"},{"num":451.0,"time":"02-14"},{"num":0.0,"time":"02-15"},{"num":7.0,"time":"02-16"}],"chartT":[{"num":3.0,"time":"02-01"},{"num":2.0,"time":"02-02"},{"num":2.0,"time":"02-03"},{"num":1.0,"time":"02-04"},{"num":4.0,"time":"02-06"},{"num":2.0,"time":"02-07"},{"num":5.0,"time":"02-08"},{"num":2.0,"time":"02-10"},{"num":3.0,"time":"02-14"},{"num":1.0,"time":"02-15"},{"num":1.0,"time":"02-16"}]}'
var _info1 = eval('(' + _infostr + ')');
var _info = _info1.chartT
var day = []
var num = []
_info.forEach(function(item) {
day.push(item.time)
num.push(item.num)
})
console.log(day, num)
this.chart.setOption({
tooltip: {
trigger: 'axis',
axisPointer: { //
type: 'shadow' // 线'line' | 'shadow'
}
},
grid: {
top: 10,
left: '2%',
right: '2%',
bottom: '3%',
containLabel: true
},
xAxis: [{
type: 'category',
data: day,
axisTick: {
alignWithLabel: true
}
}],
yAxis: [{
type: 'value',
axisTick: {
show: false
}
}],
series: [{
name: 'pageA',
type: 'line',
stack: 'vistors',
barWidth: '60%',
data: num,
animationDuration
}]
})
// })
console.log('day:' + this.day)
}
}
}
</script>

155
web/src/views/dashboard/LineChart.vue

@ -0,0 +1,155 @@
<template>
<div :class="className" :style="{height:height,width:width}" />
</template>
<script>
import echarts from 'echarts'
require('echarts/theme/macarons') // echarts theme
import { debounce } from '@/utils'
export default {
props: {
className: {
type: String,
default: 'chart'
},
width: {
type: String,
default: '100%'
},
height: {
type: String,
default: '350px'
},
autoResize: {
type: Boolean,
default: true
}
},
data() {
return {
chart: null,
sidebarElm: null,
chartData: {
visitsData: [],
ipData: []
},
weekDays: []
}
},
mounted() {
getChartData().then(res => {
this.chartData.visitsData = res.visitsData
this.chartData.ipData = res.ipData
this.weekDays = res.weekDays
this.initChart()
})
if (this.autoResize) {
this.__resizeHandler = debounce(() => {
if (this.chart) {
this.chart.resize()
}
}, 100)
window.addEventListener('resize', this.__resizeHandler)
}
//
this.sidebarElm = document.getElementsByClassName('sidebar-container')[0]
this.sidebarElm && this.sidebarElm.addEventListener('transitionend', this.sidebarResizeHandler)
},
beforeDestroy() {
if (!this.chart) {
return
}
if (this.autoResize) {
window.removeEventListener('resize', this.__resizeHandler)
}
this.sidebarElm && this.sidebarElm.removeEventListener('transitionend', this.sidebarResizeHandler)
this.chart.dispose()
this.chart = null
},
methods: {
sidebarResizeHandler(e) {
if (e.propertyName === 'width') {
this.__resizeHandler()
}
},
setOptions({ visitsData, ipData } = {}) {
this.chart.setOption({
xAxis: {
data: this.weekDays,
boundaryGap: false,
axisTick: {
show: false
}
},
grid: {
left: 10,
right: 10,
bottom: 20,
top: 30,
containLabel: true
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross'
},
padding: [5, 10]
},
yAxis: {
axisTick: {
show: false
}
},
legend: {
data: ['pv', 'ip']
},
series: [{
name: 'pv', itemStyle: {
normal: {
color: '#FF005A',
lineStyle: {
color: '#FF005A',
width: 2
}
}
},
smooth: true,
type: 'line',
data: visitsData,
animationDuration: 2800,
animationEasing: 'cubicInOut'
},
{
name: 'ip',
smooth: true,
type: 'line',
itemStyle: {
normal: {
color: '#3888fa',
lineStyle: {
color: '#3888fa',
width: 2
},
areaStyle: {
color: '#f3f8ff'
}
}
},
data: ipData,
animationDuration: 2800,
animationEasing: 'quadraticOut'
}]
})
},
initChart() {
this.chart = echarts.init(this.$el, 'macarons')
this.setOptions(this.chartData)
}
}
}
</script>

121
web/src/views/dashboard/OrderCount.vue

@ -0,0 +1,121 @@
<template>
<div :class="className" ref="chart" :style="{ height: height, width: width }" />
</template>
<script>
import echarts from "echarts";
require("echarts/theme/macarons"); // echarts theme
import { debounce } from "@/utils";
export default {
props: {
className: {
type: String,
default: "chart",
},
width: {
type: String,
default: "100%",
},
height: {
type: String,
default: "300px",
},
},
data() {
return {
chart: null,
column: [],
orderCountDatas: [],
};
},
mounted() {
var datastr = '{"column":["打印复印","广告印刷"],"orderCountDatas":[{"name":"打印复印","value":672},{"name":"广告印刷","value":1474}]}'
var obj = eval('(' + datastr + ')');
this.column = obj.column
this.orderCountDatas = obj.orderCountDatas
this.__resizeHandler = debounce(() => {
if (this.chart) {
this.chart.resize();
}
}, 100);
window.addEventListener("resize", this.__resizeHandler);
},
beforeDestroy() {
if (!this.chart) {
return;
}
window.removeEventListener("resize", this.__resizeHandler);
this.chart.dispose();
this.chart = null;
},
methods: {
initChart() {
this.chart = echarts.init(this.$refs.chart, "macarons");
this.chart.setOption({
tooltip: {
trigger: "item",
formatter: "{a} <br/>{b} : {c} ({d}%)",
},
legend: {
left: "center",
bottom: "10",
data: this.column,
},
calculable: true,
series: [
{
name: "商品分类销售占总销售的比例",
type: "pie",
roseType: "radius",
radius: [15, 95],
center: ["50%", "38%"],
data: this.orderCountDatas,
animationEasing: "cubicInOut",
animationDuration: 2600,
},
],
});
},
},
};
</script>
<style lang="scss" scoped>
.acea-row{
/deep/.el-avatar--small {
width: 22px;
height: 22px;
line-height: 22px;
}
}
.checkTime{
/deep/.el-radio__input{
display: none;
}
}
.ivu-pl-8{
margin-left: 8px;
font-size: 14px;
}
.divBox {
// padding: 0 20px !important;
}
.dashboard-console-visit {
/deep/.el-card__header{
padding: 14px 20px !important;
}
ul {
li {
list-style-type: none;
margin-top: 12px;
}
}
}
.ivu-mb{
margin-bottom: 10px;
}
</style>

233
web/src/views/dashboard/PanelGroup.vue

@ -0,0 +1,233 @@
<template>
<div>
<div class="divBox">
<el-row :gutter="24" class="baseInfo">
<el-col :xs="12" :sm="12" :lg="6" class="ivu-mb">
<el-card :bordered="false" dis-hover :padding="12">
<div slot="header" class="acea-row row-between-wrapper">
<span>会员总数</span>
<el-tag type="success">全平台</el-tag>
</div>
<div class="content" v-if="count">
<span class="content-number spBlock mb15"><count-to :start-val="0" :end-val="count.userCount" :duration="2600" class="card-panel-num" /></span>
<el-divider></el-divider>
<div class="acea-row row-between-wrapper">
<span class="content-time">今日订单数</span>
<span>{{ count.todayCount }} </span>
</div>
</div>
</el-card>
</el-col>
<el-col :xs="12" :sm="12" :lg="6" class="ivu-mb">
<el-card :bordered="false" dis-hover :padding="12">
<div slot="header" class="acea-row row-between-wrapper">
<span>订单总数</span>
<el-tag type="success">全平台</el-tag>
</div>
<div class="content" v-if="count">
<span class="content-number spBlock mb15"><count-to :start-val="0" :end-val="count.orderCount" :duration="3000" class="card-panel-num" /></span>
<el-divider></el-divider>
<div class="acea-row row-between-wrapper">
<span class="content-time">昨日订单数</span>
<span>{{ count.lastWeekCount }} </span>
</div>
</div>
</el-card>
</el-col>
<el-col :xs="12" :sm="12" :lg="6" class="ivu-mb">
<el-card :bordered="false" dis-hover :padding="12">
<div slot="header" class="acea-row row-between-wrapper">
<span>总金额</span>
<el-tag type="success">全平台</el-tag>
</div>
<div class="content" v-if="count">
<span class="content-number spBlock mb15"><count-to :start-val="0" :end-val="count.priceCount" :duration="3200" class="card-panel-num" /></span>
<el-divider></el-divider>
<div class="acea-row row-between-wrapper">
<span class="content-time">近七天订单数</span>
<span>{{ count.lastWeekCount }} </span>
</div>
</div>
</el-card>
</el-col>
<el-col :xs="12" :sm="12" :lg="6" class="ivu-mb">
<el-card :bordered="false" dis-hover :padding="12">
<div slot="header" class="acea-row row-between-wrapper">
<span>商品总数</span>
<el-tag type="success">全平台</el-tag>
</div>
<div class="content" v-if="count">
<span class="content-number spBlock mb15"><count-to :start-val="0" :end-val="count.goodsCount" :duration="3600" class="card-panel-num" /></span>
<el-divider></el-divider>
<div class="acea-row row-between-wrapper">
<span class="content-time">本月订单数</span>
<span>{{ count.monthCount }} </span>
</div>
</div>
</el-card>
</el-col>
</el-row>
</div>
</div>
</template>
<script>
import CountTo from "vue-count-to";
export default {
components: {
CountTo,
},
data() {
return {
count: {
todayPrice: 0,
todayCount: 0,
proPrice: 0,
proCount: 0,
monthPrice: 0,
monthCount: 0,
lastWeekCount: 0,
lastWeekPrice: 0,
userCount: 0,
orderCount: 0,
priceCount: 0,
goodsCount: 0,
},
};
},
mounted() {
this.count.todayPrice = 99
this.count.todayCount = 88
this.count.proCount = 77
this.count.proPrice = 66
this.count.monthPrice = 999
this.count.monthCount = 99
this.count.lastWeekCount = 7
this.count.lastWeekPrice = 6
},
};
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
.panel-group {
margin-top: 18px;
.card-panel-col {
margin-bottom: 32px;
}
.card-panel {
height: 108px;
cursor: pointer;
font-size: 12px;
position: relative;
overflow: hidden;
color: #666;
background: #fff;
box-shadow: 4px 4px 40px rgba(0, 0, 0, 0.05);
border-color: rgba(0, 0, 0, 0.05);
.icon-people {
color: #40c9c6;
}
.icon-message {
color: #36a3f7;
}
.icon-money {
color: #f4516c;
}
.icon-shopping {
color: #34bfa3;
}
.card-panel-icon-wrapper {
float: left;
margin: 14px 0 0 14px;
padding: 16px;
transition: all 0.38s ease-out;
border-radius: 6px;
}
.card-panel-icon {
float: left;
font-size: 48px;
}
.card-panel-description {
float: right;
font-weight: bold;
margin: 26px;
margin-left: 0px;
.card-panel-text {
line-height: 18px;
color: rgba(0, 0, 0, 0.45);
font-size: 16px;
margin-bottom: 12px;
}
.card-panel-num {
font-size: 20px;
}
}
}
}
@media (max-width: 550px) {
.card-panel-description {
display: none;
}
.card-panel-icon-wrapper {
float: none !important;
width: 100%;
height: 100%;
margin: 0 !important;
.svg-icon {
display: block;
margin: 14px auto !important;
float: none !important;
}
}
}
.baseInfo {
/deep/ .el-card__header {
padding: 15px 20px !important;
}
}
.ivu-mb {
margin-bottom: 10px;
}
.up,
.el-icon-caret-top {
color: #f5222d;
font-size: 12px;
opacity: 1 !important;
}
.down,
.el-icon-caret-bottom {
color: #39c15b;
font-size: 12px;
/*opacity: 100% !important;*/
}
.content {
&-number {
font-size: 30px;
}
&-time {
font-size: 14px;
/*color: #8C8C8C;*/
}
}
.spBlock {
display: block;
}
</style>

188
web/src/views/dashboard/PanelGroupT.vue

@ -0,0 +1,188 @@
<template>
<div class="divBox">
<el-row :gutter="24" class="dashboard-console-grid">
<el-col v-bind="grid" class="ivu-mb">
<el-card :bordered="false">
<router-link :to="{ path: '/shop/cate' }">
<i class="el-icon-goods" style="color: #69c0ff" />
<p>商品管理</p>
</router-link>
</el-card>
</el-col>
<el-col v-bind="grid" class="ivu-mb">
<el-card :bordered="false">
<router-link :to="{ path: '/member/member' }">
<i class="el-icon-user" style="color: #95de64" />
<p>会员管理</p>
</router-link>
</el-card>
</el-col>
<el-col v-bind="grid" class="ivu-mb">
<el-card :bordered="false">
<router-link :to="{ path: '/order/order' }">
<i class="el-icon-s-order" style="color: #ff9c6e" />
<p>订单管理</p>
</router-link>
</el-card>
</el-col>
<el-col v-bind="grid" class="ivu-mb">
<el-card :bordered="false">
<router-link :to="{ path: '/syscoupon/coupon' }">
<i class="el-icon-s-ticket" style="color: #b37feb" />
<p>电子券管理</p>
</router-link>
</el-card>
</el-col>
<el-col v-bind="grid" class="ivu-mb">
<el-card :bordered="false">
<router-link :to="{ path: '/activity/combination' }">
<i class="el-icon-postcard" style="color: #ffd666" />
<p>营销管理</p>
</router-link>
</el-card>
</el-col>
<el-col v-bind="grid" class="ivu-mb">
<el-card :bordered="false">
<router-link :to="{ path: '/promoter/promoterconfig' }">
<i class="el-icon-notebook-1" style="color: #5cdbd3" />
<p>分销管理</p>
</router-link>
</el-card>
</el-col>
<el-col v-bind="grid" class="ivu-mb">
<el-card :bordered="false">
<router-link :to="{ path: '/price/extract' }">
<i class="el-icon-s-finance" style="color: #ff85c0" />
<p>财务管理</p>
</router-link>
</el-card>
</el-col>
<el-col v-bind="grid" class="ivu-mb">
<el-card :bordered="false">
<router-link :to="{ path: '/store/storeinfo' }">
<i class="el-icon-office-building" style="color: #ffc069" />
<p>门店管理</p>
</router-link>
</el-card>
</el-col>
</el-row>
</div>
</template>
<script>
import CountTo from 'vue-count-to'
export default {
components: {
CountTo
},
data() {
return {
count: { todayPrice: 0, todayCount: 0, proPrice: 0, proCount: 0,
monthPrice: 0, monthCount: 0, lastWeekCount: 0, lastWeekPrice: 0 },
grid: {
xl: 3,
lg: 3,
md: 6,
sm: 8,
xs: 8,
},
}
},
mounted() {
this.count.todayPrice = 99
this.count.todayCount = 88
this.count.proCount = 77
this.count.proPrice = 66
this.count.monthPrice = 999
this.count.monthCount = 99
this.count.lastWeekCount = 7
this.count.lastWeekPrice = 6
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
.panel-group {
margin-top: 18px;
.card-panel-col{
margin-bottom: 32px;
}
.card-panel {
height: 108px;
font-size: 12px;
position: relative;
overflow: hidden;
cursor: pointer;
color: #666;
background: #fff;
box-shadow: 4px 4px 40px rgba(0, 0, 0, .05);
border-color: rgba(0, 0, 0, .05);
.icon-people {
color: #40c9c6;
}
.icon-message {
color: #36a3f7;
}
.icon-money {
color: #f4516c;
}
.icon-shopping {
color: #34bfa3
}
.card-panel-icon-wrapper {
float: left;
margin: 14px 0 0 14px;
padding: 16px;
transition: all 0.38s ease-out;
border-radius: 6px;
}
.card-panel-icon {
float: left;
font-size: 48px;
}
.card-panel-description {
float: right;
font-weight: bold;
margin: 26px;
margin-left: 0px;
.card-panel-text {
line-height: 18px;
color: rgba(0, 0, 0, 0.45);
font-size: 16px;
margin-bottom: 12px;
}
.card-panel-num {
font-size: 20px;
}
}
}
}
.ivu-mb {
margin-bottom: 10px;
}
.divBox {
// padding: 0 20px !important;
}
.dashboard-console-grid {
text-align: center;
.ivu-card-body {
padding: 0;
}
i {
font-size: 32px;
}
a {
display: block;
color: inherit;
}
p {
margin-top: 8px;
}
}
</style>

91
web/src/views/dashboard/PieChart.vue

@ -0,0 +1,91 @@
<template>
<div :class="className" :style="{height:height,width:width}" />
</template>
<script>
import echarts from 'echarts'
require('echarts/theme/macarons') // echarts theme
import { debounce } from '@/utils'
export default {
props: {
className: {
type: String,
default: 'chart'
},
width: {
type: String,
default: '100%'
},
height: {
type: String,
default: '300px'
}
},
data() {
return {
chart: null,
grid: {
xl: 3,
lg: 6,
md: 6,
sm: 8,
xs: 8,
},
}
},
mounted() {
this.initChart()
this.__resizeHandler = debounce(() => {
if (this.chart) {
this.chart.resize()
}
}, 100)
window.addEventListener('resize', this.__resizeHandler)
},
beforeDestroy() {
if (!this.chart) {
return
}
window.removeEventListener('resize', this.__resizeHandler)
this.chart.dispose()
this.chart = null
},
methods: {
initChart() {
this.chart = echarts.init(this.$el, 'macarons')
this.chart.setOption({
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b} : {c} ({d}%)'
},
legend: {
left: 'center',
bottom: '10',
data: ['Industries', 'Technology', 'Forex', 'Gold', 'Forecasts']
},
calculable: true,
series: [
{
name: 'WEEKLY WRITE ARTICLES',
type: 'pie',
roseType: 'radius',
radius: [15, 95],
center: ['50%', '38%'],
data: [
{ value: 320, name: 'Industries' },
{ value: 240, name: 'Technology' },
{ value: 149, name: 'Forex' },
{ value: 100, name: 'Gold' },
{ value: 59, name: 'Forecasts' }
],
animationEasing: 'cubicInOut',
animationDuration: 2600
}
]
})
}
}
}
</script>

120
web/src/views/dashboard/RaddarChart.vue

@ -0,0 +1,120 @@
<template>
<div :class="className" :style="{height:height,width:width}" />
</template>
<script>
import echarts from 'echarts'
require('echarts/theme/macarons') // echarts theme
import { debounce } from '@/utils'
const animationDuration = 3000
export default {
props: {
className: {
type: String,
default: 'chart'
},
width: {
type: String,
default: '100%'
},
height: {
type: String,
default: '300px'
}
},
data() {
return {
chart: null
}
},
mounted() {
this.initChart()
this.__resizeHandler = debounce(() => {
if (this.chart) {
this.chart.resize()
}
}, 100)
window.addEventListener('resize', this.__resizeHandler)
},
beforeDestroy() {
if (!this.chart) {
return
}
window.removeEventListener('resize', this.__resizeHandler)
this.chart.dispose()
this.chart = null
},
methods: {
initChart() {
this.chart = echarts.init(this.$el, 'macarons')
this.chart.setOption({
tooltip: {
trigger: 'axis',
axisPointer: { //
type: 'shadow' // 线'line' | 'shadow'
}
},
radar: {
radius: '66%',
center: ['50%', '42%'],
splitNumber: 8,
splitArea: {
areaStyle: {
color: 'rgba(127,95,132,.3)',
opacity: 1,
shadowBlur: 45,
shadowColor: 'rgba(0,0,0,.5)',
shadowOffsetX: 0,
shadowOffsetY: 15
}
},
indicator: [
{ name: 'Sales', max: 10000 },
{ name: 'Administration', max: 20000 },
{ name: 'Information Techology', max: 20000 },
{ name: 'Customer Support', max: 20000 },
{ name: 'Development', max: 20000 },
{ name: 'Marketing', max: 20000 }
]
},
legend: {
left: 'center',
bottom: '10',
data: ['Allocated Budget', 'Expected Spending', 'Actual Spending']
},
series: [{
type: 'radar',
symbolSize: 0,
areaStyle: {
normal: {
shadowBlur: 13,
shadowColor: 'rgba(0,0,0,.2)',
shadowOffsetX: 0,
shadowOffsetY: 10,
opacity: 1
}
},
data: [
{
value: [5000, 7000, 12000, 11000, 15000, 14000],
name: 'Allocated Budget'
},
{
value: [4000, 9000, 15000, 15000, 13000, 11000],
name: 'Expected Spending'
},
{
value: [5500, 11000, 12000, 15000, 12000, 12000],
name: 'Actual Spending'
}
],
animationDuration: animationDuration
}]
})
}
}
}
</script>

107
web/src/views/dashboard/YshopInfo.vue

@ -0,0 +1,107 @@
<template>
<el-row :gutter="10" class="panel-group">
<el-col :xs="16" :sm="16" :lg="24" class="card-panel-col">
<div class="card-panel">
<el-card class="box-card">
<div slot="header" class="clearfix">
<span style="font-weight: bolder">项目信息</span>
</div>
<div class="text item">
<span style="font-weight: bolder">VIVIB基于当前流行技术组合的前后端RBAC管理系统</span>
Go1.15.x+Beego2.x+Jwt+Redis+Mysql8+Vue
的前后端分离系统权限控制采用 RBAC支持数据字典与数据权限管理支持动态路由等
</div>
<div class="text item">
<span style="font-weight: bolder">系统功能</span><br>
1用户管理提供用户的相关配置 <br>
2角色管理对权限与菜单进行分配可根据部门设置角色的数据权限 <br>
3菜单管理已实现菜单动态路由后端可配置化 <br>
4部门管理可配置系统组织架构树形表格展示 <br>
5岗位管理配置各个部门的职位 <br>
6字典管理可维护常用一些固定的数据状态性别等 <br>
</div>
</el-card>
</div>
</el-col>
</el-row>
</template>
<script>
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
.text {
font-size: 16px;
}
.item {
margin-bottom: 10px;
}
.clearfix:before,
.clearfix:after {
display: table;
content: "";
}
.clearfix:after {
clear: both
}
.panel-group {
margin-top: 18px;
.card-panel-col{
margin-bottom: 32px;
}
.card-panel {
height: auto;
font-size: 12px;
position: relative;
overflow: hidden;
color: #666;
background: #fff;
box-shadow: 4px 4px 40px rgba(0, 0, 0, .05);
border-color: rgba(0, 0, 0, .05);
.icon-people {
color: #40c9c6;
}
.icon-message {
color: #36a3f7;
}
.icon-money {
color: #f4516c;
}
.icon-shopping {
color: #34bfa3
}
.card-panel-icon-wrapper {
float: left;
margin: 14px 0 0 14px;
padding: 16px;
transition: all 0.38s ease-out;
border-radius: 6px;
}
.card-panel-icon {
float: left;
font-size: 48px;
}
.card-panel-description {
float: right;
font-weight: bold;
margin: 26px;
margin-left: 0px;
.card-panel-text {
line-height: 18px;
color: rgba(0, 0, 0, 0.45);
font-size: 16px;
margin-bottom: 12px;
}
.card-panel-num {
font-size: 20px;
}
}
}
}
</style>

89
web/src/views/features/401.vue

@ -0,0 +1,89 @@
<template>
<div class="errPage-container">
<el-button icon="arrow-left" class="pan-back-btn" @click="back">
返回
</el-button>
<el-row>
<el-col :span="12">
<h1 class="text-jumbo text-ginormous">
Oops!
</h1>
<h2>你没有权限去该页面</h2>
<h6>如有不满请联系你领导</h6>
<ul class="list-unstyled">
<li>或者你可以去:</li>
<li class="link-type">
<router-link to="/dashboard">
回首页
</router-link>
</li>
</ul>
</el-col>
<el-col :span="12">
<img :src="errGif" width="313" height="428" alt="Girl has dropped her ice cream.">
</el-col>
</el-row>
</div>
</template>
<script>
import errGif from '@/assets/401_images/401.gif'
export default {
name: 'Page401',
data() {
return {
errGif: errGif + '?' + +new Date()
}
},
methods: {
back() {
if (this.$route.query.noGoBack) {
this.$router.push({ path: '/dashboard' })
} else {
this.$router.go(-1)
}
}
}
}
</script>
<style lang="scss" scoped>
.errPage-container {
width: 800px;
max-width: 100%;
margin: 100px auto;
.pan-back-btn {
background: #008489;
color: #fff;
border: none!important;
}
.pan-gif {
margin: 0 auto;
display: block;
}
.pan-img {
display: block;
margin: 0 auto;
width: 100%;
}
.text-jumbo {
font-size: 60px;
font-weight: 700;
color: #484848;
}
.list-unstyled {
font-size: 14px;
li {
padding-bottom: 5px;
}
a {
color: #008489;
text-decoration: none;
&:hover {
text-decoration: underline;
}
}
}
}
</style>

225
web/src/views/features/404.vue

@ -0,0 +1,225 @@
<template>
<div class="wscn-http404-container">
<div class="wscn-http404">
<div class="pic-404">
<img class="pic-404__parent" src="@/assets/404_images/404.png" alt="404">
<img class="pic-404__child left" src="@/assets/404_images/404_cloud.png" alt="404">
<img class="pic-404__child mid" src="@/assets/404_images/404_cloud.png" alt="404">
<img class="pic-404__child right" src="@/assets/404_images/404_cloud.png" alt="404">
</div>
<div class="bullshit">
<div class="bullshit__oops">OOPS!</div>
<div class="bullshit__headline">{{ message }}</div>
<div class="bullshit__info">请检查您输入的网址是否正确请点击以下按钮返回主页或者发送错误报告</div>
<a href="/" class="bullshit__return-home">返回首页</a>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'Page404',
computed: {
message() {
return '网管说这个页面你不能进......'
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
.wscn-http404-container{
transform: translate(-50%,-50%);
position: absolute;
top: 40%;
left: 50%;
}
.wscn-http404 {
position: relative;
width: 1200px;
padding: 0 50px;
overflow: hidden;
.pic-404 {
position: relative;
float: left;
width: 600px;
overflow: hidden;
&__parent {
width: 100%;
}
&__child {
position: absolute;
&.left {
width: 80px;
top: 17px;
left: 220px;
opacity: 0;
animation-name: cloudLeft;
animation-duration: 2s;
animation-timing-function: linear;
animation-fill-mode: forwards;
animation-delay: 1s;
}
&.mid {
width: 46px;
top: 10px;
left: 420px;
opacity: 0;
animation-name: cloudMid;
animation-duration: 2s;
animation-timing-function: linear;
animation-fill-mode: forwards;
animation-delay: 1.2s;
}
&.right {
width: 62px;
top: 100px;
left: 500px;
opacity: 0;
animation-name: cloudRight;
animation-duration: 2s;
animation-timing-function: linear;
animation-fill-mode: forwards;
animation-delay: 1s;
}
@keyframes cloudLeft {
0% {
top: 17px;
left: 220px;
opacity: 0;
}
20% {
top: 33px;
left: 188px;
opacity: 1;
}
80% {
top: 81px;
left: 92px;
opacity: 1;
}
100% {
top: 97px;
left: 60px;
opacity: 0;
}
}
@keyframes cloudMid {
0% {
top: 10px;
left: 420px;
opacity: 0;
}
20% {
top: 40px;
left: 360px;
opacity: 1;
}
70% {
top: 130px;
left: 180px;
opacity: 1;
}
100% {
top: 160px;
left: 120px;
opacity: 0;
}
}
@keyframes cloudRight {
0% {
top: 100px;
left: 500px;
opacity: 0;
}
20% {
top: 120px;
left: 460px;
opacity: 1;
}
80% {
top: 180px;
left: 340px;
opacity: 1;
}
100% {
top: 200px;
left: 300px;
opacity: 0;
}
}
}
}
.bullshit {
position: relative;
float: left;
width: 300px;
padding: 30px 0;
overflow: hidden;
&__oops {
font-size: 32px;
font-weight: bold;
line-height: 40px;
color: #1482f0;
opacity: 0;
margin-bottom: 20px;
animation-name: slideUp;
animation-duration: 0.5s;
animation-fill-mode: forwards;
}
&__headline {
font-size: 20px;
line-height: 24px;
color: #222;
font-weight: bold;
opacity: 0;
margin-bottom: 10px;
animation-name: slideUp;
animation-duration: 0.5s;
animation-delay: 0.1s;
animation-fill-mode: forwards;
}
&__info {
font-size: 13px;
line-height: 21px;
color: grey;
opacity: 0;
margin-bottom: 30px;
animation-name: slideUp;
animation-duration: 0.5s;
animation-delay: 0.2s;
animation-fill-mode: forwards;
}
&__return-home {
display: block;
float: left;
width: 110px;
height: 36px;
background: #1482f0;
border-radius: 100px;
text-align: center;
color: #ffffff;
opacity: 0;
font-size: 14px;
line-height: 36px;
cursor: pointer;
animation-name: slideUp;
animation-duration: 0.5s;
animation-delay: 0.3s;
animation-fill-mode: forwards;
}
@keyframes slideUp {
0% {
transform: translateY(60px);
opacity: 0;
}
100% {
transform: translateY(0);
opacity: 1;
}
}
}
}
</style>

12
web/src/views/features/redirect.vue

@ -0,0 +1,12 @@
<script>
export default {
created() {
const { params, query } = this.$route
const { path } = params
this.$router.replace({ path: '/' + path, query })
},
render: function(h) {
return h() // avoid warning message
}
}
</script>

138
web/src/views/home.vue

@ -0,0 +1,138 @@
<template>
<div class="dashboard-container">
<div class="dashboard-editor-container">
<yshop-info />
<!-- <panel-group /> -->
<div class="divBox">
<el-card :bordered="false" dis-hover>
<div slot="header">
<div class="acea-row row-middle">
<el-avatar
icon="el-icon-s-operation"
size="small"
style="color: #1890ff; background: #e6f7ff; font-size: 13px"
/>
<span class="ivu-pl-8">统计</span>
</div>
</div>
<el-row :gutter="24">
<el-col
:xl="8"
:lg="8"
:md="24"
:sm="24"
:xs="24"
class="ivu-mb mb10 dashboard-console-visit"
>
<span class="ivu-pl-8">商品分析</span>
<order-count></order-count>
</el-col>
<el-col
:xl="8"
:lg="8"
:md="24"
:sm="24"
:xs="24"
class="ivu-mb mb10 dashboard-console-visit"
>
<span class="ivu-pl-8">本月成交额</span>
<bar-chart />
</el-col>
<el-col
:xl="8"
:lg="8"
:md="24"
:sm="24"
:xs="24"
class="ivu-mb mb10 dashboard-console-visit"
>
<span class="ivu-pl-8">本月订单数</span>
<pie-chart />
</el-col>
</el-row>
</el-card>
</div>
</div>
</div>
</template>
<script>
import { mapGetters } from "vuex";
import PanelGroup from "./dashboard/PanelGroup";
import PanelGroupT from "./dashboard/PanelGroupT";
import PieChart from "./dashboard/BarChartT";
import BarChart from "./dashboard/BarChart";
import YshopInfo from "./dashboard/YshopInfo";
import OrderCount from "./dashboard/OrderCount";
/**
* 记录访问只有页面刷新或者第一次加载才会记录
*/
//count().then((res) => {});
export default {
name: "Dashboard",
components: {
PanelGroup,
PanelGroupT,
PieChart,
BarChart,
YshopInfo,
OrderCount,
},
computed: {
...mapGetters(["roles"]),
},
};
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
.dashboard-editor-container {
padding: 18px 22px 22px 22px;
background-color: rgb(240, 242, 245);
.chart-wrapper {
background: #fff;
padding: 16px 16px 0;
margin-bottom: 32px;
}
}
.acea-row {
/deep/.el-avatar--small {
width: 22px;
height: 22px;
line-height: 22px;
}
}
.checkTime {
/deep/.el-radio__input {
display: none;
}
}
.ivu-pl-8 {
margin-left: 8px;
font-size: 14px;
}
.divBox {
// padding: 0 20px !important;
}
.dashboard-console-visit {
/deep/.el-card__header {
padding: 14px 20px !important;
}
ul {
li {
list-style-type: none;
margin-top: 12px;
}
}
}
.ivu-mb {
margin-bottom: 10px;
}
</style>

198
web/src/views/login.vue

@ -0,0 +1,198 @@
<template>
<div class="login">
<div style="display:flex;justify-content:space-between;align-items: center;">
<div style="padding: 0 60px 0 0">
<h3 style="color: #ffffff;font-size: 24px;padding: 0;margin-bottom: 10px">
VIVIB RBAC管理后台系统
</h3>
<ul style="margin: 0;color: #ffffff;font-size: 15px;line-height: 25px;padding-left: 20px">
<li>Go1.15.x</li>
<li>Beego2.x</li>
<li>Mysql8</li>
<li>Jwt</li>
<li>Redis</li>
<li>Vue</li>
</ul>
<h3 style="color: #ffffff;">
后台地址:https://go.yixiang.co
</h3>
</div>
<el-form ref="loginForm" :model="loginForm" :rules="loginRules" label-position="left" label-width="0px" class="login-form">
<h3 class="title">
VIVIB后台管理系统-1.0
</h3>
<el-form-item prop="username">
<el-input v-model="loginForm.username" type="text" auto-complete="off" placeholder="账号">
<svg-icon slot="prefix" icon-class="user" class="el-input__icon input-icon" />
</el-input>
</el-form-item>
<el-form-item prop="password">
<el-input v-model="loginForm.password" type="password" auto-complete="off" placeholder="密码" @keyup.enter.native="handleLogin">
<svg-icon slot="prefix" icon-class="password" class="el-input__icon input-icon" />
</el-input>
</el-form-item>
<el-form-item prop="code">
<el-input v-model="loginForm.code" auto-complete="off" placeholder="验证码" style="width: 63%" @keyup.enter.native="handleLogin">
<svg-icon slot="prefix" icon-class="validCode" class="el-input__icon input-icon" />
</el-input>
<div class="login-code">
<img :src="codeUrl" @click="getCode">
</div>
</el-form-item>
<el-form-item style="width:100%;">
<el-button :loading="loading" size="medium" type="primary" style="width:100%;" @click.native.prevent="handleLogin">
<span v-if="!loading"> </span>
<span v-else> 中...</span>
</el-button>
</el-form-item>
</el-form>
</div>
<!-- 底部 -->
<div v-if="$store.state.settings.showFooter" id="el-login-footer">
<span v-html="$store.state.settings.footerTxt" />
<span> </span>
<a href="http://www.beian.miit.gov.cn" target="_blank">{{ $store.state.settings.caseNumber }}</a>
</div>
</div>
</template>
<script>
import { encrypt } from '@/utils/rsaEncrypt'
import Config from '@/settings'
import { getCodeImg } from '@/api/login'
import Cookies from 'js-cookie'
export default {
name: 'Login',
data() {
return {
codeUrl: '',
cookiePass: '',
loginForm: {
username: 'admin',
password: '123456',
code: '',
uuid: ''
},
loginRules: {
username: [{ required: true, trigger: 'blur', message: '用户名不能为空' }],
password: [{ required: true, trigger: 'blur', message: '密码不能为空' }],
code: [{ required: true, trigger: 'change', message: '验证码不能为空' }]
},
loading: false,
redirect: undefined
}
},
watch: {
$route: {
handler: function(route) {
this.redirect = route.query && route.query.redirect
},
immediate: true
}
},
created() {
this.getCode()
this.getCookie()
},
methods: {
getCode() {
getCodeImg().then(res => {
this.codeUrl = res.data.base_64_blob
this.loginForm.uuid = res.data.id
})
},
getCookie() {
const username = Cookies.get('username')
let password = Cookies.get('password')
// cookie
this.cookiePass = password === undefined ? '' : password
password = password === undefined ? this.loginForm.password : password
this.loginForm = {
username: username === undefined ? this.loginForm.username : username,
password: password,
code: ''
}
},
handleLogin() {
this.$refs.loginForm.validate(valid => {
const user = {
username: this.loginForm.username,
password: this.loginForm.password,
code: this.loginForm.code,
uuid: this.loginForm.uuid
}
if (valid) {
this.loading = true
if (user.rememberMe) {
Cookies.set('username', user.username, { expires: Config.passCookieExpires })
Cookies.set('password', user.password, { expires: Config.passCookieExpires })
} else {
Cookies.remove('username')
Cookies.remove('password')
}
this.$store.dispatch('Login', user).then(() => {
this.loading = false
this.$router.push({ path: this.redirect || '/' })
}).catch(() => {
this.loading = false
this.getCode()
})
} else {
console.log('error submit!!')
return false
}
})
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss">
.login {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
background-image:url("../assets/images/bg.jpg");
background-size: cover;
}
.title {
margin: 0 auto 30px auto;
text-align: center;
color: #707070;
}
.login-form {
border-radius: 6px;
background: #ffffff;
width: 385px;
padding: 25px 25px 5px 25px;
.el-input {
height: 38px;
input {
height: 38px;
}
}
.input-icon{
height: 39px;width: 14px;margin-left: 2px;
}
}
.login-tip {
font-size: 13px;
text-align: center;
color: #bfbfbf;
}
.login-code {
width: 33%;
display: inline-block;
height: 38px;
float: right;
img{
cursor: pointer;
vertical-align:middle
}
}
</style>

3
web/src/views/nested/menu1/index.vue

@ -0,0 +1,3 @@
<template>
<router-view />
</template>

5
web/src/views/nested/menu1/menu1-1/index.vue

@ -0,0 +1,5 @@
<template>
<div style="padding:30px;">
<el-alert :closable="false" title="三级菜单1" type="success" />
</div>
</template>

5
web/src/views/nested/menu1/menu1-2/index.vue

@ -0,0 +1,5 @@
<template>
<div style="padding:30px;">
<el-alert :closable="false" title="三级菜单2" type="success" />
</div>
</template>

5
web/src/views/nested/menu2/index.vue

@ -0,0 +1,5 @@
<template>
<div style="padding:30px;">
<el-alert :closable="false" title="二级菜单" />
</div>
</template>

228
web/src/views/system/dept/index.vue

@ -0,0 +1,228 @@
<template>
<div class="app-container">
<!--工具栏-->
<div class="head-container">
<div v-if="crud.props.searchToggle">
<!-- 搜索 -->
<el-select v-model="query.enabled" clearable size="small" placeholder="状态" class="filter-item" style="width: 90px" @change="crud.toQuery">
<el-option v-for="item in enabledTypeOptions" :key="item.key" :label="item.display_name" :value="item.key" />
</el-select>
<rrOperation :crud="crud" />
</div>
<crudOperation :permission="permission" />
</div>
<!--表单组件-->
<el-dialog append-to-body :close-on-click-modal="false" :before-close="crud.cancelCU" :visible.sync="crud.status.cu > 0" :title="crud.status.title" width="500px">
<el-form ref="form" :model="form" :rules="rules" size="small" label-width="80px">
<el-form-item label="名称" prop="name">
<el-input v-model="form.name" style="width: 370px;" />
</el-form-item>
<el-form-item v-if="form.pid !== 0" label="状态" prop="enabled">
<el-radio v-for="item in dict.dept_status" :key="item.id" v-model="form.enabled" :label="item.value">{{ item.label }}</el-radio>
</el-form-item>
<el-form-item v-if="form.pid !== 0" style="margin-bottom: 0;" label="上级部门" prop="pid">
<treeselect v-model="form.pid" :options="depts" :normalizer="normalizer" style="width: 370px;" placeholder="选择上级类目" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="text" @click="crud.cancelCU">取消</el-button>
<el-button :loading="crud.cu === 2" type="primary" @click="crud.submitCU">确认</el-button>
</div>
</el-dialog>
<!--表格渲染-->
<el-table ref="table" v-loading="crud.loading" :tree-props="{children: 'children', hasChildren: 'hasChildren'}" default-expand-all :data="crud.data" row-key="id" @select="crud.selectChange" @select-all="crud.selectAllChange" @selection-change="crud.selectionChangeHandler">
<el-table-column :selectable="checkboxT" type="selection" width="55" />
<el-table-column v-if="columns.visible('name')" label="名称" prop="name" />
<el-table-column v-if="columns.visible('enabled')" label="状态" align="center" prop="enabled">
<template slot-scope="scope">
<el-switch
v-model="scope.row.enabled"
:disabled="scope.row.id === 1"
active-color="#409EFF"
inactive-color="#F56C6C"
:active-value= "1"
:inactive-value= "0"
@change="changeEnabled(scope.row, scope.row.enabled,)"
/>
</template>
</el-table-column>
<el-table-column v-if="columns.visible('createTime')" prop="createTime" label="创建日期">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column v-permission="['admin','dept:edit','dept:del']" label="操作" width="130px" align="center" fixed="right">
<template slot-scope="scope">
<udOperation
:data="scope.row"
:permission="permission"
:disabled-dle="scope.row.id === 1"
msg="确定删除吗,如果存在下级节点则一并删除,此操作不能撤销!"
/>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script>
import crudDept from '@/api/system/dept'
import Treeselect from '@riophae/vue-treeselect'
import '@riophae/vue-treeselect/dist/vue-treeselect.css'
import CRUD, { presenter, header, form, crud } from '@crud/crud'
import rrOperation from '@crud/RR.operation'
import crudOperation from '@crud/CRUD.operation'
import udOperation from '@crud/UD.operation'
// crudpresenter
const defaultCrud = CRUD({ title: '部门', url: 'admin/dept', crudMethod: { ...crudDept }})
const defaultForm = { id: null, name: null, pid: 1, enabled: 1 }
export default {
name: 'Dept',
components: { Treeselect, crudOperation, rrOperation, udOperation },
mixins: [presenter(defaultCrud), header(), form(defaultForm), crud()],
//
dicts: ['dept_status'],
data() {
return {
normalizer(node){
//children=[]children respectively
if(node.children && !node.children.length){
delete node.children;
}
},
depts: [],
rules: {
name: [
{ required: true, message: '请输入名称', trigger: 'blur' }
]
},
permission: {
add: ['admin', 'dept:add'],
edit: ['admin', 'dept:edit'],
del: ['admin', 'dept:del']
},
enabledTypeOptions: [
{ key: 1, display_name: '正常' },
{ key: 0, display_name: '禁用' }
]
}
},
methods: {
getDeptDatas(tree, treeNode, resolve) {
const params = { pid: tree.id }
setTimeout(() => {
crudDept.getDepts(params).then(res => {
resolve(res.content)
})
}, 100)
},
//
[CRUD.HOOK.afterToCU](crud, form) {
if (form.pid !== null) {
form.isTop = '0'
} else if (form.id !== null) {
form.isTop = '1'
}
// form.enabled = `${form.enabled}`
if (form.id != null) {
this.getSupDepts(form.id)
} else {
this.getDepts()
}
},
getSupDepts(id) {
var data={"pid":id};
crudDept.getDeptSuperior(data).then(res => {
const date = res.data.content
this.buildDepts(date)
this.depts = date
})
},
buildDepts(depts) {
(depts || []).forEach(data => {
if (data.children) {
this.buildDepts(data.children)
}
if (data.hasChildren && !data.children) {
data.children = null
}
})
},
getDepts() {
crudDept.getDepts({ enabled: 1 }).then(res => {
this.depts = res.data.content.map(function(obj) {
if (obj.hasChildren) {
obj.children = null
}
return obj
})
})
},
//
loadDepts({ action, parentNode, callback }) {
if (action === LOAD_CHILDREN_OPTIONS) {
crudDept.getDepts({ enabled: true, pid: parentNode.id }).then(res => {
parentNode.children = res.content.map(function(obj) {
if (obj.hasChildren) {
obj.children = null
}
return obj
})
setTimeout(() => {
callback()
}, 100)
})
}
},
//
[CRUD.HOOK.afterValidateCU]() {
if (this.form.pid !== null && this.form.pid === this.form.id) {
this.$message({
message: '上级部门不能为空',
type: 'warning'
})
return false
}
if (this.form.isTop === '1') {
this.form.pid = null
}
return true
},
//
changeEnabled(data, val) {
console.log("aa:"+JSON.stringify(this.dict.label.dept_status))
console.log("aa:"+val)
this.$confirm('此操作将 "' + this.dict.label.dept_status[val] + '" ' + data.name + '部门, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
crudDept.edit(data).then(res => {
this.crud.notify(this.dict.label.dept_status[val] + '成功', CRUD.NOTIFICATION_TYPE.SUCCESS)
}).catch(err => {
data.enabled = !data.enabled
console.log(err.response.data.message)
})
}).catch(() => {
data.enabled = !data.enabled
})
},
checkboxT(row, rowIndex) {
return row.id !== 1
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
::v-deep .vue-treeselect__control,::v-deep .vue-treeselect__placeholder,::v-deep .vue-treeselect__single-value {
height: 30px;
line-height: 30px;
}
</style>
<style rel="stylesheet/scss" lang="scss" scoped>
::v-deep .el-input-number .el-input__inner {
text-align: left;
}
</style>

114
web/src/views/system/dict/dictDetail.vue

@ -0,0 +1,114 @@
<template>
<div>
<div v-if="dictName === ''">
<div class="my-code">点击字典查看详情</div>
</div>
<div v-else>
<!--工具栏-->
<div class="head-container">
<!-- 搜索 -->
<el-input v-model="query.blurry" clearable size="small" placeholder="输入字典标签查询" style="width: 200px;" class="filter-item" @keyup.enter.native="toQuery" />
<el-button class="filter-item" size="mini" type="success" icon="el-icon-search" @click="toQuery">搜索</el-button>
</div>
<!--表单组件-->
<el-dialog append-to-body :close-on-click-modal="false" :before-close="cancel" :visible.sync="dialog" :title="getFormTitle()" width="500px">
<el-form ref="form" :model="form" :rules="rules" size="small" label-width="80px">
<el-form-item label="字典标签" prop="label">
<el-input v-model="form.label" style="width: 370px;" />
</el-form-item>
<el-form-item label="字典值">
<el-input v-model="form.value" style="width: 370px;" />
</el-form-item>
<el-form-item label="排序" prop="sort">
<el-input-number v-model.number="form.sort" :min="0" :max="999" controls-position="right" style="width: 370px;" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="text" @click="cancel">取消</el-button>
<el-button :loading="loading" type="primary" @click="submitMethod">确认</el-button>
</div>
</el-dialog>
<!--表格渲染-->
<el-table v-loading="loading" :data="data" style="width: 100%;">
<el-table-column label="所属字典">
{{ dictName }}
</el-table-column>
<el-table-column prop="label" label="字典标签" />
<el-table-column prop="value" label="字典值" />
<el-table-column prop="sort" label="排序" />
<el-table-column v-if="checkPermission(['admin','dict:edit','dict:del'])" label="操作" width="130px" align="center" fixed="right">
<template slot-scope="scope">
<el-button v-permission="['admin','dict:edit']" size="mini" type="primary" icon="el-icon-edit" @click="showEditFormDialog(scope.row)" />
<el-popover
:ref="scope.row.id"
v-permission="['admin','dict:del']"
placement="top"
width="180"
>
<p>确定删除本条数据吗</p>
<div style="text-align: right; margin: 0">
<el-button size="mini" type="text" @click="$refs[scope.row.id].doClose()">取消</el-button>
<el-button :loading="delLoading" type="primary" size="mini" @click="delMethod(scope.row.id)">确定</el-button>
</div>
<el-button slot="reference" type="danger" icon="el-icon-delete" size="mini" />
</el-popover>
</template>
</el-table-column>
</el-table>
<!--分页组件-->
<el-pagination
:total="total"
:current-page="page + 1"
style="margin-top: 8px;"
layout="total, prev, pager, next, sizes"
@size-change="sizeChange"
@current-change="pageChange"
/>
</div>
</div>
</template>
<script>
import crud from '@/mixins/crud'
import crudDictDetail from '@/api/system/dictDetail'
export default {
mixins: [crud],
data() {
return {
title: '字典详情',
sort: ['sort,asc', 'id,desc'],
crudMethod: { ...crudDictDetail },
dictName: '',
dictId: null,
form: { id: null, label: null, value: null, dictId: null,dictName: null, sort: 999 },
rules: {
label: [
{ required: true, message: '请输入字典标签', trigger: 'blur' }
],
sort: [
{ required: true, message: '请输入序号', trigger: 'blur', type: 'number' }
]
}
}
},
methods: {
//
beforeInit() {
this.url = 'admin/dictDetail'
if (this.dictName) {
this.params['dictName'] = this.dictName
}
if(this.form.dictId) {
this.params['dictId'] = this.form.dictId
}
return true
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
/deep/ .el-input-number .el-input__inner {
text-align: left;
}
</style>

160
web/src/views/system/dict/index.vue

@ -0,0 +1,160 @@
<template>
<div class="app-container">
<!--表单组件-->
<el-dialog append-to-body :close-on-click-modal="false" :before-close="cancel" :visible.sync="dialog" :title="getFormTitle()" width="500px">
<el-form ref="form" :model="form" :rules="rules" size="small" label-width="80px">
<el-form-item label="字典名称" prop="name">
<el-input v-model="form.name" style="width: 370px;" />
</el-form-item>
<el-form-item label="描述">
<el-input v-model="form.remark" style="width: 370px;" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="text" @click="cancel">取消</el-button>
<el-button :loading="loading" type="primary" @click="submitMethod">确认</el-button>
</div>
</el-dialog>
<!-- 字典列表 -->
<el-row :gutter="10">
<el-col :xs="24" :sm="24" :md="10" :lg="10" :xl="10" style="margin-bottom: 10px">
<el-card class="box-card">
<div slot="header" class="clearfix">
<span>字典列表</span>
<!-- 新增 -->
<el-button
v-permission="['admin','dict:add']"
class="filter-item"
size="mini"
style="float: right;padding: 4px 10px"
type="primary"
icon="el-icon-plus"
@click="showAddFormDialog"
>新增</el-button>
</div>
<!--工具栏-->
<div class="head-container">
<!-- 搜索 -->
<el-input v-model="query.blurry" clearable size="small" placeholder="输入名称或者描述搜索" style="width: 200px;" class="filter-item" @keyup.enter.native="toQuery" />
<el-button class="filter-item" size="mini" type="success" icon="el-icon-search" @click="toQuery">搜索</el-button>
<!-- 导出 -->
<el-button
:loading="downloadLoading"
size="mini"
class="filter-item"
type="warning"
icon="el-icon-download"
@click="downloadMethod"
>导出</el-button>
</div>
<!--表格渲染-->
<el-table v-loading="loading" :data="data" highlight-current-row style="width: 100%;" @current-change="handleCurrentChange">
<el-table-column :show-overflow-tooltip="true" prop="name" label="名称" />
<el-table-column :show-overflow-tooltip="true" prop="remark" label="描述" />
<el-table-column v-if="checkPermission(['admin','dict:edit','dict:del'])" label="操作" width="130px" align="center" fixed="right">
<template slot-scope="scope">
<el-button v-permission="['admin','dict:edit']" size="mini" type="primary" icon="el-icon-edit" @click="showEditFormDialog(scope.row)" />
<el-popover
:ref="scope.row.id"
v-permission="['admin','dict:del']"
placement="top"
width="180"
>
<p>此操作将删除字典与对应的字典详情确定要删除吗</p>
<div style="text-align: right; margin: 0">
<el-button size="mini" type="text" @click="$refs[scope.row.id].doClose()">取消</el-button>
<el-button :loading="delLoading" type="primary" size="mini" @click="delMethod(scope.row.id)">确定</el-button>
</div>
<el-button slot="reference" type="danger" icon="el-icon-delete" size="mini" />
</el-popover>
</template>
</el-table-column>
</el-table>
<!--分页组件-->
<el-pagination
:total="total"
:current-page="page + 1"
style="margin-top: 8px;"
layout="total, prev, pager, next, sizes"
@size-change="sizeChange"
@current-change="pageChange"
/>
</el-card>
</el-col>
<!-- 字典详情列表 -->
<el-col :xs="24" :sm="24" :md="14" :lg="14" :xl="14">
<el-card class="box-card">
<div slot="header" class="clearfix">
<span>字典详情</span>
<el-button
v-if="checkPermission(['admin','dict:add']) && this.$refs.dictDetail && this.$refs.dictDetail.dictName"
class="filter-item"
size="mini"
style="float: right;padding: 4px 10px"
type="primary"
icon="el-icon-plus"
@click="$refs.dictDetail.showAddFormDialog"
>新增</el-button>
</div>
<dictDetail ref="dictDetail" />
</el-card>
</el-col>
</el-row>
</div>
</template>
<script>
import crud from '@/mixins/crud'
import dictDetail from './dictDetail'
import crudDict from '@/api/system/dict'
export default {
name: 'Dict',
components: { dictDetail },
mixins: [crud],
data() {
return {
title: '字典',
crudMethod: { ...crudDict },
queryTypeOptions: [
{ key: 'name', display_name: '字典名称' },
{ key: 'remark', display_name: '描述' }
],
form: { id: null, name: null, remark: null },
rules: {
name: [
{ required: true, message: '请输入名称', trigger: 'blur' }
]
}
}
},
created() {
this.$nextTick(() => {
this.init()
})
},
methods: {
//
beforeInit() {
this.url = 'admin/dict'
if (this.$refs.dictDetail) {
this.$refs.dictDetail.data = []
this.$refs.dictDetail.dictName = ''
}
return true
},
//
handleCurrentChange(val) {
if (val) {
this.$refs.dictDetail.dictName = val.name
console.log("a:"+val.name)
this.$refs.dictDetail.form.dictName = val.name
this.$refs.dictDetail.form.dictId = val.id
this.$refs.dictDetail.init()
}
}
}
}
</script>
<style scoped>
</style>

120
web/src/views/system/job/index.vue

@ -0,0 +1,120 @@
<template>
<div class="app-container">
<!--工具栏-->
<div class="head-container">
<eHeader :dict="dict" :permission="permission" />
<crudOperation :permission="permission" />
</div>
<!--表格渲染-->
<el-table ref="table" v-loading="crud.loading" :data="crud.data" style="width: 100%;" @selection-change="crud.selectionChangeHandler">
<el-table-column type="selection" width="55" />
<el-table-column v-if="columns.visible('name')" prop="name" label="名称" />
<el-table-column v-if="columns.visible('dept')" prop="dept" label="所属部门">
<template slot-scope="scope">
<div>{{ scope.row.deptSuperiorName ? scope.row.deptSuperiorName + ' / ' : '' }}{{ scope.row.dept.name }}</div>
</template>
</el-table-column>
<el-table-column v-if="columns.visible('sort')" prop="sort" label="排序">
<template slot-scope="scope">
{{ scope.row.sort }}
</template>
</el-table-column>
<el-table-column v-if="columns.visible('status')" prop="status" label="状态" align="center">
<template slot-scope="scope">
<el-switch
v-model="scope.row.enabled"
active-color="#409EFF"
inactive-color="#F56C6C"
:active-value= "1"
:inactive-value= "0"
@change="changeEnabled(scope.row, scope.row.enabled)"
/>
</template>
</el-table-column>
<el-table-column v-if="columns.visible('createTime')" prop="createTime" label="创建日期">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<!-- 编辑与删除 -->
<el-table-column
v-permission="['admin','job:edit','job:del']"
label="操作"
width="130px"
align="center"
fixed="right"
>
<template slot-scope="scope">
<udOperation
:data="scope.row"
:permission="permission"
/>
</template>
</el-table-column>
</el-table>
<!--分页组件-->
<pagination />
<!--表单渲染-->
<eForm :job-status="dict.job_status" />
</div>
</template>
<script>
import crudJob from '@/api/system/job'
import eHeader from './module/header'
import eForm from './module/form'
import CRUD, { presenter } from '@crud/crud'
import crudOperation from '@crud/CRUD.operation'
import pagination from '@crud/Pagination'
import udOperation from '@crud/UD.operation'
// crudpresenter
const crud = CRUD({
title: '岗位',
url: 'admin/job',
sort: ['sort,asc', 'id,desc'],
crudMethod: { ...crudJob }
})
export default {
name: 'Job',
components: { eHeader, eForm, crudOperation, pagination, udOperation },
mixins: [presenter(crud)],
//
dicts: ['job_status'],
data() {
return {
permission: {
add: ['admin', 'job:add'],
edit: ['admin', 'job:edit'],
del: ['admin', 'job:del']
}
}
},
methods: {
//
changeEnabled(data, val) {
this.$confirm('此操作将 "' + this.dict.label.job_status[val] + '" ' + data.name + '岗位, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
crud.crudMethod.edit(data).then(() => {
crud.notify(this.dict.label.job_status[val] + '成功', 'success')
}).catch(err => {
data.enabled = !data.enabled
console.log(err.data.message)
})
}).catch(() => {
data.enabled = !data.enabled
})
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
/deep/ .el-input-number .el-input__inner {
text-align: left;
}
</style>

149
web/src/views/system/job/module/form.vue

@ -0,0 +1,149 @@
<template>
<el-dialog
append-to-body
:close-on-click-modal="false"
:before-close="crud.cancelCU"
:visible="crud.status.cu > 0"
:title="crud.status.title"
width="500px"
>
<el-form
ref="form"
:model="form"
:rules="rules"
size="small"
label-width="80px"
>
<el-form-item
label="名称"
prop="name"
>
<el-input
v-model="form.name"
style="width: 370px;"
/>
</el-form-item>
<el-form-item
label="排序"
prop="sort"
>
<el-input-number
v-model.number="form.sort"
:min="0"
:max="999"
controls-position="right"
style="width: 370px;"
/>
</el-form-item>
<el-form-item
v-if="form.pid !== 0"
label="状态"
prop="enabled"
>
<el-radio
v-for="item in jobStatus"
:key="item.id"
v-model="form.enabled"
:label="item.value"
>
{{ item.label }}
</el-radio>
</el-form-item>
<el-form-item
label="所属部门"
prop="dept.id"
:rules="rules.dept"
>
<treeselect
v-model="form.dept.id"
:options="depts"
style="width: 370px"
placeholder="选择部门"
/>
</el-form-item>
</el-form>
<div
slot="footer"
class="dialog-footer"
>
<el-button
type="text"
@click="crud.cancelCU"
>
取消
</el-button>
<el-button
:loading="crud.cu === 2"
type="primary"
@click="crud.submitCU"
>
确认
</el-button>
</div>
</el-dialog>
</template>
<script>
import CRUD, { form } from '@crud/crud'
import { getDepts } from '@/api/system/dept'
import Treeselect from '@riophae/vue-treeselect'
import '@riophae/vue-treeselect/dist/vue-treeselect.css'
const defaultForm = {
id: null,
name: '',
sort: 999,
enabled: 1,
dept: {
id: 1
}
}
export default {
components: { Treeselect },
mixins: [form(defaultForm)],
props: {
jobStatus: {
type: Array,
required: true
}
},
data() {
return {
depts: [],
rules: {
name: [
{ required: true, message: '请输入名称', trigger: 'blur' }
],
sort: [
{ required: true, message: '请输入序号', trigger: 'blur', type: 'number' }
],
dept: { required: true, message: '所属部门不能为空', trigger: 'select' }
}
}
},
methods: {
[CRUD.HOOK.beforeToCU]() {
getDepts({ enabled: 1 }).then(res => {
this.depts = res.data.content
})
},
//
[CRUD.HOOK.afterValidateCU]() {
if (!this.form.dept.id) {
this.$notify({
title: '所属部门不能为空',
type: 'warning'
})
return false
}
return true
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
/deep/ .el-input-number .el-input__inner {
text-align: left;
}
</style>

32
web/src/views/system/job/module/header.vue

@ -0,0 +1,32 @@
<template>
<div
v-if="crud.props.searchToggle"
>
<el-input v-model="query.blurry" clearable size="small" placeholder="输入岗位名称搜索" style="width: 200px;" class="filter-item" @keyup.enter.native="crud.toQuery" />
<el-select v-model="query.enabled" clearable size="small" placeholder="状态" class="filter-item" style="width: 90px" @change="crud.toQuery">
<el-option v-for="item in dict.dict.job_status" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
<rrOperation
:crud="crud"
/>
</div>
</template>
<script>
import { header } from '@crud/crud'
import rrOperation from '@crud/RR.operation'
export default {
components: { rrOperation },
mixins: [header()],
props: {
dict: {
type: Object,
required: true
},
permission: {
type: Object,
required: true
}
}
}
</script>

206
web/src/views/system/menu/index.vue

@ -0,0 +1,206 @@
<template>
<div class="app-container">
<!--工具栏-->
<div class="head-container">
<div v-if="crud.props.searchToggle">
<!-- 搜索 -->
<el-input v-model="query.blurry" clearable size="small" placeholder="模糊搜索" style="width: 200px;" class="filter-item" @keyup.enter.native="toQuery" />
<rrOperation :crud="crud" />
</div>
<crudOperation :permission="permission" />
</div>
<!--表单渲染-->
<el-dialog append-to-body :close-on-click-modal="false" :before-close="crud.cancelCU" :visible.sync="crud.status.cu > 0" :title="crud.status.title" width="580px">
<el-form ref="form" :inline="true" :model="form" :rules="rules" size="small" label-width="80px">
<el-form-item label="菜单类型" prop="type">
<el-radio-group v-model="form.type" size="mini" style="width: 178px">
<el-radio-button :label="0">目录</el-radio-button>
<el-radio-button :label="1">菜单</el-radio-button>
<el-radio-button :label="2">按钮</el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item v-show="form.type.toString() !== '2'" label="菜单图标" prop="icon">
<el-popover
placement="bottom-start"
width="450"
trigger="click"
@show="$refs['iconSelect'].reset()"
>
<IconSelect ref="iconSelect" @selected="selected" />
<el-input slot="reference" v-model="form.icon" style="width: 450px;" placeholder="点击选择图标" readonly>
<svg-icon v-if="form.icon" slot="prefix" :icon-class="form.icon" class="el-input__icon" style="height: 32px;width: 16px;" />
<i v-else slot="prefix" class="el-icon-search el-input__icon" />
</el-input>
</el-popover>
</el-form-item>
<el-form-item v-show="form.type.toString() !== '2'" label="外链菜单" prop="iframe">
<el-radio-group v-model="form.iframe" size="mini">
<el-radio-button :label="1"></el-radio-button>
<el-radio-button :label="0"></el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item v-show="form.type.toString() === '1'" label="菜单缓存" prop="cache">
<el-radio-group v-model="form.cache" size="mini">
<el-radio-button :abel="1"></el-radio-button>
<el-radio-button :label="0"></el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item v-show="form.type.toString() !== '2'" label="菜单可见" prop="hidden">
<el-radio-group v-model="form.hidden" size="mini">
<el-radio-button :label="0"></el-radio-button>
<el-radio-button :label="1"></el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item v-show="form.type.toString() !== '2'" label="菜单标题" prop="name">
<el-input v-model="form.name" :style=" form.type.toString() === '0' ? 'width: 450px' : 'width: 178px'" placeholder="菜单标题" />
</el-form-item>
<el-form-item v-show="form.type.toString() === '2'" label="按钮名称" prop="name">
<el-input v-model="form.name" placeholder="按钮名称" style="width: 178px;" />
</el-form-item>
<el-form-item v-show="form.type.toString() !== '0'" label="权限标识" prop="permission">
<el-input v-model="form.permission" :disabled="form.iframe" placeholder="权限标识" style="width: 178px;" />
</el-form-item>
<el-form-item v-show="form.type.toString() !== '0'" label="操作路由" prop="router">
<el-input v-model="form.router" :disabled="form.iframe" placeholder="权限标识" style="width: 178px;" />
</el-form-item>
<el-form-item v-show="form.type.toString() !== '0'" label="路由动作" prop="routerMethod">
<el-input v-model="form.routerMethod" :disabled="form.iframe" placeholder="权限标识" style="width: 178px;" />
</el-form-item>
<el-form-item v-if="form.type.toString() !== '2'" label="路由地址" prop="path">
<el-input v-model="form.path" placeholder="路由地址" style="width: 178px;" />
</el-form-item>
<el-form-item label="菜单排序" prop="sort">
<el-input-number v-model.number="form.sort" :min="0" :max="999" controls-position="right" style="width: 178px;" />
</el-form-item>
<el-form-item v-show="!form.iframe && form.type.toString() === '1'" label="组件名称" prop="componentName">
<el-input v-model="form.componentName" style="width: 178px;" placeholder="匹配组件内Name字段" />
</el-form-item>
<el-form-item v-show="!form.iframe && form.type.toString() === '1'" label="组件路径" prop="component">
<el-input v-model="form.component" style="width: 178px;" placeholder="组件路径" />
</el-form-item>
<el-form-item label="上级类目" prop="pid">
<treeselect v-model="form.pid" :options="menus" :normalizer="normalizer" style="width: 450px;" placeholder="选择上级类目" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="text" @click="crud.cancelCU">取消</el-button>
<el-button :loading="crud.cu === 2" type="primary" @click="crud.submitCU">确认</el-button>
</div>
</el-dialog>
<!--表格渲染-->
<el-table ref="table" v-loading="crud.loading" :data="crud.data" :tree-props="{children: 'children', hasChildren: 'hasChildren'}" row-key="id" @select="crud.selectChange" @select-all="crud.selectAllChange" @selection-change="crud.selectionChangeHandler">
<el-table-column type="selection" width="55" />
<el-table-column v-if="columns.visible('name')" :show-overflow-tooltip="true" label="菜单名称" width="125px" prop="name" />
<el-table-column v-if="columns.visible('icon')" prop="icon" label="图标" align="center" width="60px">
<template slot-scope="scope">
<svg-icon :icon-class="scope.row.icon" />
</template>
</el-table-column>
<el-table-column v-if="columns.visible('sort')" prop="sort" align="center" label="排序">
<template slot-scope="scope">
{{ scope.row.sort }}
</template>
</el-table-column>
<el-table-column v-if="columns.visible('path')" :show-overflow-tooltip="true" prop="path" label="路由地址" />
<el-table-column v-if="columns.visible('permission')" :show-overflow-tooltip="true" prop="permission" label="权限标识" />
<el-table-column v-if="columns.visible('component')" :show-overflow-tooltip="true" prop="component" label="组件路径" />
<el-table-column v-if="columns.visible('iframe')" prop="iframe" label="外链" width="75px">
<template slot-scope="scope">
<span v-if="scope.row.iframe"></span>
<span v-else></span>
</template>
</el-table-column>
<el-table-column v-if="columns.visible('cache')" prop="cache" label="缓存" width="75px">
<template slot-scope="scope">
<span v-if="scope.row.cache"></span>
<span v-else></span>
</template>
</el-table-column>
<el-table-column v-if="columns.visible('hidden')" prop="hidden" label="可见" width="75px">
<template slot-scope="scope">
<span v-if="scope.row.hidden"></span>
<span v-else></span>
</template>
</el-table-column>
<el-table-column v-if="columns.visible('createTime')" prop="createTime" label="创建日期" width="135px">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column v-permission="['admin','menu:edit','menu:del']" label="操作" width="130px" align="center" fixed="right">
<template slot-scope="scope">
<udOperation
:data="scope.row"
:permission="permission"
msg="确定删除吗,如果存在下级节点则一并删除,此操作不能撤销!"
/>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script>
import crudMenu from '@/api/system/menu'
import IconSelect from '@/components/IconSelect'
import Treeselect from '@riophae/vue-treeselect'
import '@riophae/vue-treeselect/dist/vue-treeselect.css'
import CRUD, { presenter, header, form, crud } from '@crud/crud'
import rrOperation from '@crud/RR.operation'
import crudOperation from '@crud/CRUD.operation'
import udOperation from '@crud/UD.operation'
// crudpresenter
const defaultCrud = CRUD({ title: '菜单', url: 'admin/menu', crudMethod: { ...crudMenu }})
const defaultForm = { id: null, name: null, sort: 999, path: null, component: null, componentName: null, iframe: 0, roles: [], pid: 0, icon: null, cache: 0, hidden: 0, type: 0, permission: null,router: null,routerMethod:null }
export default {
name: 'Menu',
components: { Treeselect, IconSelect, crudOperation, rrOperation, udOperation },
mixins: [presenter(defaultCrud), header(), form(defaultForm), crud()],
data() {
return {
normalizer(node){
//children=[]children respectively
if(node.children && !node.children.length){
delete node.children;
}
},
menus: [],
permission: {
add: ['admin', 'menu:add'],
edit: ['admin', 'menu:edit'],
del: ['admin', 'menu:del']
},
rules: {
name: [
{ required: true, message: '请输入名称', trigger: 'blur' }
],
path: [
{ required: true, message: '请输入地址', trigger: 'blur' }
]
}
}
},
methods: {
//
[CRUD.HOOK.afterToCU](crud, form) {
crudMenu.getMenusTree().then(res => {
this.menus = []
const menu = { id: 0, label: '顶级类目', children: [] }
menu.children = res.data
this.menus.push(menu)
})
},
//
selected(name) {
this.form.icon = name
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
/deep/ .el-input-number .el-input__inner {
text-align: left;
}
</style>

279
web/src/views/system/role/index.vue

@ -0,0 +1,279 @@
<template>
<div class="app-container">
<!--工具栏-->
<div class="head-container">
<div v-if="crud.props.searchToggle">
<!-- 搜索 -->
<el-input v-model="query.blurry" size="small" clearable placeholder="输入名称搜索" style="width: 200px;" class="filter-item" @keyup.enter.native="crud.toQuery" />
<rrOperation :crud="crud" />
</div>
<crudOperation :permission="permission" />
</div>
<!-- 表单渲染 -->
<el-dialog append-to-body :close-on-click-modal="false" :before-close="crud.cancelCU" :visible.sync="crud.status.cu > 0" :title="crud.status.title" width="520px">
<el-form ref="form" :inline="true" :model="form" :rules="rules" size="small" label-width="80px">
<el-form-item label="角色名称" prop="name">
<el-input v-model="form.name" style="width: 145px;" />
</el-form-item>
<el-form-item label="角色权限" prop="permission">
<el-input v-model="form.permission" style="width: 145px;" />
</el-form-item>
<el-form-item v-if="form.dataScope === '自定义'" label="数据权限" prop="depts">
<treeselect v-model="form.depts" :options="depts" multiple style="width: 380px" placeholder="请选择" />
</el-form-item>
<el-form-item label="描述信息" prop="remark">
<el-input v-model="form.remark" style="width: 380px;" rows="5" type="textarea" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="text" @click="crud.cancelCU">取消</el-button>
<el-button :loading="crud.cu === 2" type="primary" @click="crud.submitCU">确认</el-button>
</div>
</el-dialog>
<el-row :gutter="15">
<!--角色管理-->
<el-col :xs="24" :sm="24" :md="16" :lg="16" :xl="17" style="margin-bottom: 10px">
<el-card class="box-card" shadow="never">
<div slot="header" class="clearfix">
<span class="role-span">角色列表</span>
</div>
<el-table ref="table" v-loading="crud.loading" highlight-current-row style="width: 100%;" :data="crud.data" @selection-change="crud.selectionChangeHandler" @current-change="handleCurrentChange">
<el-table-column :selectable="checkboxT" type="selection" width="55" />
<el-table-column v-if="columns.visible('name')" prop="name" label="名称" />
<el-table-column v-if="columns.visible('permission')" prop="permission" label="角色权限" />
<el-table-column v-if="columns.visible('remark')" :show-overflow-tooltip="true" prop="remark" label="描述" />
<el-table-column v-if="columns.visible('createTime')" :show-overflow-tooltip="true" width="135px" prop="createTime" label="创建日期">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column v-permission="['admin','roles:edit','roles:del']" label="操作" width="130px" align="center" fixed="right">
<template slot-scope="scope">
<udOperation
:data="scope.row"
:permission="permission"
/>
</template>
</el-table-column>
</el-table>
<!--分页组件-->
<pagination />
</el-card>
</el-col>
<!-- 菜单授权 -->
<el-col :xs="24" :sm="24" :md="8" :lg="8" :xl="7">
<el-card class="box-card" shadow="never">
<div slot="header" class="clearfix">
<el-tooltip class="item" effect="dark" content="选择指定角色分配菜单" placement="top">
<span class="role-span">菜单分配</span>
</el-tooltip>
<el-button
v-permission="['admin','roles:edit']"
:disabled="!showButton"
:loading="menuLoading"
icon="el-icon-check"
size="mini"
style="float: right; padding: 6px 9px"
type="primary"
@click="saveMenu"
>保存</el-button>
</div>
<el-tree
ref="menu"
:data="menus"
:default-checked-keys="menuIds"
:props="defaultProps"
show-checkbox
node-key="id"
/>
</el-card>
</el-col>
</el-row>
</div>
</template>
<script>
import crudRoles from '@/api/system/role'
import { getDepts } from '@/api/system/dept'
import { getMenusTree } from '@/api/system/menu'
import CRUD, { presenter, header, form, crud } from '@crud/crud'
import rrOperation from '@crud/RR.operation'
import crudOperation from '@crud/CRUD.operation'
import udOperation from '@crud/UD.operation'
import pagination from '@crud/Pagination'
import Treeselect from '@riophae/vue-treeselect'
import '@riophae/vue-treeselect/dist/vue-treeselect.css'
// crudpresenter
const defaultCrud = CRUD({ title: '角色', url: 'admin/roles', sort: 'level,asc', crudMethod: { ...crudRoles }})
const defaultForm = { id: null, name: null, depts: [], remark: null, dataScope: '全部', level: 3, permission: null }
export default {
name: 'Role',
components: { Treeselect, pagination, crudOperation, rrOperation, udOperation },
mixins: [presenter(defaultCrud), header(), form(defaultForm), crud()],
data() {
return {
defaultProps: { children: 'children', label: 'label' },
dateScopes: ['全部', '本级', '自定义'], level: 3,
currentId: 0, menuLoading: false, showButton: false,
menus: [], menuIds: [], depts: [],
permission: {
add: ['admin', 'roles:add'],
edit: ['admin', 'roles:edit'],
del: ['admin', 'roles:del']
},
rules: {
name: [
{ required: true, message: '请输入名称', trigger: 'blur' }
],
permission: [
{ required: true, message: '请输入权限', trigger: 'blur' }
]
}
}
},
created() {
this.getMenus()
this.$nextTick(() => {
this.crud.toQuery()
})
},
methods: {
[CRUD.HOOK.afterRefresh]() {
this.$refs.menu.setCheckedKeys([])
},
//
[CRUD.HOOK.beforeToEdit](crud, form) {
if (form.dataScope === '自定义') {
this.getDepts()
}
const depts = []
form.depts.forEach(function(dept, index) {
depts.push(dept.id)
})
form.depts = depts
},
//
[CRUD.HOOK.afterValidateCU](crud) {
if (crud.form.dataScope === '自定义' && crud.form.depts.length === 0) {
this.$message({
message: '自定义数据权限不能为空',
type: 'warning'
})
return false
} else if (crud.form.dataScope === '自定义') {
const depts = []
crud.form.depts.forEach(function(data, index) {
const dept = { id: data }
depts.push(dept)
})
crud.form.depts = depts
} else {
crud.form.depts = []
}
return true
},
[CRUD.HOOK.afterAddError](crud) {
this.afterErrorMethod(crud)
},
[CRUD.HOOK.afterEditError](crud) {
this.afterErrorMethod(crud)
},
afterErrorMethod(crud) {
const depts = []
crud.form.depts.forEach(function(dept, index) {
depts.push(dept.id)
})
crud.form.depts = depts
},
//
getMenus() {
getMenusTree().then(res => {
this.menus = res.data
})
},
//
handleCurrentChange(val) {
if (val) {
const _this = this
//
this.$refs.menu.setCheckedKeys([])
// id
this.currentId = val.id
this.showButton = val.level > 0
//
this.menuIds = []
//
val.menus.forEach(function(data, index) {
if(data.pid!=0){
_this.menuIds.push(data.id)
}
})
}
},
//
saveMenu() {
this.menuLoading = true
const role = { id: this.currentId, menus: [] }
//
this.$refs.menu.getHalfCheckedNodes().forEach(function(data, index) {
const menu = { id: data.id }
role.menus.push(menu)
})
// key
this.$refs.menu.getCheckedKeys().forEach(function(data, index) {
const menu = { id: data }
role.menus.push(menu)
})
crudRoles.editMenu(role).then(res => {
this.crud.notify('保存成功', CRUD.NOTIFICATION_TYPE.SUCCESS)
this.menuLoading = false
this.update()
}).catch(err => {
this.menuLoading = false
console.log(err.response.data.message)
})
},
//
update() {
//
crudRoles.get(this.currentId).then(res => {
for (let i = 0; i < this.crud.data.length; i++) {
if (res.data.id === this.crud.data[i].id) {
this.crud.data[i] = res.data
break
}
}
})
},
//
getDepts() {
getDepts({ enabled: true }).then(res => {
this.depts = res.content
})
},
//
changeScope() {
if (this.form.dataScope === '自定义') {
this.getDepts()
}
},
checkboxT(row, rowIndex) {
return row.level >= this.level
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss">
.role-span {
font-weight: bold;color: #303133;
font-size: 15px;
}
</style>
<style rel="stylesheet/scss" lang="scss" scoped>
/deep/ .el-input-number .el-input__inner {
text-align: left;
}
</style>

218
web/src/views/system/user/center.vue

@ -0,0 +1,218 @@
<template>
<div class="app-container">
<el-row :gutter="20">
<el-col :xs="24" :sm="24" :md="8" :lg="6" :xl="5" style="margin-bottom: 10px">
<el-card class="box-card">
<div slot="header" class="clearfix">
<span>个人信息</span>
</div>
<div>
<div style="text-align: center">
<el-upload
:show-file-list="false"
:on-success="handleSuccess"
:on-error="handleError"
action=""
:http-request="upload"
class="avatar-uploader"
>
<img :src="user.avatar ? user.avatar : Avatar" title="点击上传头像" class="avatar">
</el-upload>
</div>
<ul class="user-info">
<li><div style="height: 100%"><svg-icon icon-class="login" /> 登录账号<div class="user-right">{{ user.username }}</div></div></li>
<li><svg-icon icon-class="user1" /> 用户昵称 <div class="user-right">{{ user.nickName }}</div></li>
<li><svg-icon icon-class="phone" /> 手机号码 <div class="user-right">{{ user.phone }}</div></li>
<li><svg-icon icon-class="email" /> 用户邮箱 <div class="user-right">{{ user.email }}</div></li>
<li>
<svg-icon icon-class="anq" /> 安全设置
<div class="user-right">
<a @click="$refs.pass.dialog = true">修改密码</a>
</div>
</li>
</ul>
</div>
</el-card>
</el-col>
<el-col :xs="24" :sm="24" :md="16" :lg="18" :xl="19">
<!-- 用户资料 -->
<el-card class="box-card">
<el-tabs v-model="activeName" @tab-click="handleClick">
<el-tab-pane label="用户资料" name="first">
<el-form ref="form" :model="form" :rules="rules" style="margin-top: 10px;" size="small" label-width="65px">
<el-form-item label="昵称" prop="nickName">
<el-input v-model="form.nickName" style="width: 35%" />
<span style="color: #C0C0C0;margin-left: 10px;">用户昵称不作为登录使用</span>
</el-form-item>
<el-form-item label="手机号" prop="phone">
<el-input v-model="form.phone" style="width: 35%;" />
<span style="color: #C0C0C0;margin-left: 10px;">手机号码不能重复</span>
</el-form-item>
<el-form-item label="性别">
<el-radio-group v-model="form.sex" style="width: 178px">
<el-radio label="男"></el-radio>
<el-radio label="女"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="">
<el-button :loading="saveLoading" size="mini" type="primary" @click="doSubmit">保存配置</el-button>
</el-form-item>
</el-form>
</el-tab-pane>
</el-tabs>
</el-card>
</el-col>
</el-row>
<updatePass ref="pass" />
</div>
</template>
<script>
import { mapGetters } from 'vuex'
import updatePass from './center/updatePass'
import { getToken } from '@/utils/auth'
import store from '@/store'
import { isvalidPhone } from '@/utils/validate'
import { parseTime } from '@/utils/index'
import crud from '@/mixins/crud'
import { editUser, uploadAvatar } from '@/api/system/user'
import Avatar from '@/assets/images/avatar.png'
import axios from 'axios'
export default {
name: 'Center',
components: { updatePass },
mixins: [crud],
data() {
//
const validPhone = (rule, value, callback) => {
if (!value) {
callback(new Error('请输入电话号码'))
} else if (!isvalidPhone(value)) {
callback(new Error('请输入正确的11位手机号码'))
} else {
callback()
}
}
return {
Avatar: Avatar,
activeName: 'first',
saveLoading: false,
headers: {
'Authorization': "Bearer " + getToken()
},
form: {},
rules: {
nickName: [
{ required: true, message: '请输入用户昵称', trigger: 'blur' },
{ min: 2, max: 20, message: '长度在 2 到 20 个字符', trigger: 'blur' }
],
phone: [
{ required: true, trigger: 'blur', validator: validPhone }
]
}
}
},
computed: {
...mapGetters([
'user',
'updateAvatarApi',
'baseApi'
])
},
created() {
this.form = { id: this.user.id, nickName: this.user.nickName, sex: this.user.sex, phone: this.user.phone }
store.dispatch('GetInfo').then(() => {})
},
methods: {
upload(param) {
const formData = new FormData()
formData.append('file', param.file)
uploadAvatar(formData).then(response => {
console.log(response.data)
if(response.status == 200) {
this.$notify({
title: '头像修改成功',
type: 'success',
duration: 2500
})
store.dispatch('GetInfo').then(() => {})
}
});
},
parseTime,
handleClick(tab, event) {
if (tab.name === 'second') {
this.init()
}
},
handleSuccess(response, file, fileList) {
this.$notify({
title: '头像修改成功',
type: 'success',
duration: 2500
})
store.dispatch('GetInfo').then(() => {})
},
//
handleError(e, file, fileList) {
const msg = JSON.parse(e.message)
this.$notify({
title: msg.message,
type: 'error',
duration: 2500
})
},
doSubmit() {
if (this.$refs['form']) {
this.$refs['form'].validate((valid) => {
if (valid) {
this.saveLoading = true
editUser(this.form).then(() => {
this.editSuccessNotify()
store.dispatch('GetInfo').then(() => {})
this.saveLoading = false
}).catch(() => {
this.saveLoading = false
})
}
})
}
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss">
.avatar-uploader-icon {
font-size: 28px;
width: 120px;
height: 120px;
line-height: 120px;
text-align: center
}
.avatar {
width: 120px;
height: 120px;
display: block;
border-radius: 50%
}
.user-info {
padding-left: 0;
list-style: none;
li{
border-bottom: 1px solid #F0F3F4;
padding: 11px 0;
font-size: 13px;
}
.user-right {
float: right;
a{
color: #317EF3;
}
}
}
</style>

95
web/src/views/system/user/center/updatePass.vue

@ -0,0 +1,95 @@
<template>
<div style="display: inline-block">
<el-dialog :visible.sync="dialog" :close-on-click-modal="false" :before-close="cancel" :title="title" append-to-body width="500px" @close="cancel">
<el-form ref="form" :model="form" :rules="rules" size="small" label-width="88px">
<el-form-item label="旧密码" prop="oldPass">
<el-input v-model="form.oldPass" type="password" auto-complete="on" style="width: 370px;" />
</el-form-item>
<el-form-item label="新密码" prop="newPass">
<el-input v-model="form.newPass" type="password" auto-complete="on" style="width: 370px;" />
</el-form-item>
<el-form-item label="确认密码" prop="confirmPass">
<el-input v-model="form.confirmPass" type="password" auto-complete="on" style="width: 370px;" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="text" @click="cancel">取消</el-button>
<el-button :loading="loading" type="primary" @click="doSubmit">确认</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import store from '@/store'
import { updatePass } from '@/api/system/user'
export default {
data() {
const confirmPass = (rule, value, callback) => {
if (value) {
if (this.form.newPass !== value) {
callback(new Error('两次输入的密码不一致'))
} else {
callback()
}
} else {
callback(new Error('请再次输入密码'))
}
}
return {
loading: false, dialog: false, title: '修改密码', form: { oldPass: '', newPass: '', confirmPass: '' },
rules: {
oldPass: [
{ required: true, message: '请输入旧密码', trigger: 'blur' }
],
newPass: [
{ required: true, message: '请输入新密码', trigger: 'blur' },
{ min: 6, max: 20, message: '长度在 6 到 20 个字符', trigger: 'blur' }
],
confirmPass: [
{ required: true, validator: confirmPass, trigger: 'blur' }
]
}
}
},
methods: {
cancel() {
this.resetForm()
},
doSubmit() {
this.$refs['form'].validate((valid) => {
if (valid) {
this.loading = true
updatePass(this.form).then(res => {
this.resetForm()
this.$notify({
title: '密码修改成功,请重新登录',
type: 'success',
duration: 1500
})
setTimeout(() => {
store.dispatch('LogOut').then(() => {
location.reload() // vue-router bug
})
}, 1500)
}).catch(err => {
this.loading = false
console.log(err.response.data.message)
})
} else {
return false
}
})
},
resetForm() {
this.dialog = false
this.$refs['form'].resetFields()
this.form = { oldPass: '', newPass: '', confirmPass: '' }
}
}
}
</script>
<style scoped>
</style>

425
web/src/views/system/user/index.vue

@ -0,0 +1,425 @@
<template>
<div class="app-container">
<el-row :gutter="20">
<!--侧边部门数据-->
<el-col :xs="9" :sm="6" :md="4" :lg="4" :xl="4">
<div class="head-container">
<el-input
v-model="deptName"
clearable
size="small"
placeholder="输入部门名称搜索"
prefix-icon="el-icon-search"
class="filter-item"
@input="getDeptDatas"
/>
</div>
<el-tree
:data="deptDatas"
:props="defaultProps"
:expand-on-click-node="false"
default-expand-all
@node-click="handleNodeClick"
/>
</el-col>
<!--用户数据-->
<el-col :xs="15" :sm="18" :md="20" :lg="20" :xl="20">
<!--工具栏-->
<div class="head-container">
<div v-if="crud.props.searchToggle">
<!-- 搜索 -->
<el-input
v-model="query.blurry"
clearable
size="small"
placeholder="输入名称或者邮箱搜索"
style="width: 200px;"
class="filter-item"
@keyup.enter.native="crud.toQuery"
/>
<el-select
v-model="query.enabled"
clearable
size="small"
placeholder="状态"
class="filter-item"
style="width: 90px"
@change="crud.toQuery"
>
<el-option
v-for="item in enabledTypeOptions"
:key="item.key"
:label="item.display_name"
:value="item.key"
/>
</el-select>
<rrOperation :crud="crud" />
</div>
<crudOperation show="" :permission="permission" />
</div>
<!--表单渲染-->
<el-dialog append-to-body :close-on-click-modal="false" :before-close="crud.cancelCU" :visible.sync="crud.status.cu > 0" :title="crud.status.title" width="570px">
<el-form ref="form" :inline="true" :model="form" :rules="rules" size="small" label-width="66px">
<el-form-item label="用户名" prop="username">
<el-input v-model="form.username" />
</el-form-item>
<el-form-item label="电话" prop="phone">
<el-input v-model="form.phone" />
</el-form-item>
<el-form-item label="昵称" prop="nickName">
<el-input v-model="form.nickName" />
</el-form-item>
<el-form-item label="邮箱" prop="email">
<el-input v-model="form.email" />
</el-form-item>
<el-form-item label="部门" prop="dept.id">
<treeselect
v-model="form.dept.id"
:options="depts"
:normalizer="normalizer"
style="width: 178px"
placeholder="选择部门"
@select="selectFun"
/>
</el-form-item>
<el-form-item label="岗位" prop="job.id">
<el-select v-model="form.job.id" style="width: 178px" placeholder="请先选择岗位">
<el-option
v-for="(item, index) in jobs"
:key="item.name + index"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item label="性别">
<el-radio-group v-model="form.sex" style="width: 178px">
<el-radio label="男"></el-radio>
<el-radio label="女"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="状态">
<el-radio-group v-model="form.enabled" :disabled="form.id === user.id">
<el-radio
v-for="item in dict.user_status"
:key="item.id"
:label="item.value"
>{{ item.label }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item style="margin-bottom: 0;" label="角色" prop="roles">
<el-select
v-model="form.roleIds"
style="width: 437px"
multiple
placeholder="请选择角色"
@remove-tag="deleteTag"
@change="changeRole"
>
<el-option
v-for="item in roles"
:key="item.name"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="text" @click="crud.cancelCU">取消</el-button>
<el-button :loading="crud.cu === 2" type="primary" @click="crud.submitCU">确认</el-button>
</div>
</el-dialog>
<!--表格渲染-->
<el-table ref="table" v-loading="crud.loading" :data="crud.data" style="width: 100%;" @selection-change="crud.selectionChangeHandler">
<el-table-column :selectable="checkboxT" type="selection" width="55" />
<el-table-column v-if="columns.visible('username')" :show-overflow-tooltip="true" prop="username" label="用户名" />
<el-table-column v-if="columns.visible('nickName')" :show-overflow-tooltip="true" prop="nickName" label="昵称" />
<el-table-column v-if="columns.visible('sex')" prop="sex" label="性别" />
<el-table-column v-if="columns.visible('phone')" :show-overflow-tooltip="true" prop="phone" width="100" label="电话" />
<el-table-column v-if="columns.visible('email')" :show-overflow-tooltip="true" width="125" prop="email" label="邮箱" />
<el-table-column v-if="columns.visible('dept')" :show-overflow-tooltip="true" width="110" prop="dept" label="部门 / 岗位">
<template slot-scope="scope">
<div>{{ scope.row.dept.name }} / {{ scope.row.job.name }}</div>
</template>
</el-table-column>
<el-table-column v-if="columns.visible('enabled')" label="状态" align="center" prop="enabled">
<template slot-scope="scope">
<el-switch
v-model="scope.row.enabled"
:disabled="user.id === scope.row.id"
active-color="#409EFF"
inactive-color="#F56C6C"
:active-value= "1"
:inactive-value= "0"
@change="changeEnabled(scope.row, scope.row.enabled)"
/>
</template>
</el-table-column>
<el-table-column v-if="columns.visible('createTime')" :show-overflow-tooltip="true" prop="createTime" width="140" label="创建日期">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column
v-permission="['admin','user:edit','user:del']"
label="操作"
width="125"
align="center"
fixed="right"
>
<template slot-scope="scope">
<udOperation
:data="scope.row"
:permission="permission"
:disabled-dle="scope.row.id === user.id"
/>
</template>
</el-table-column>
</el-table>
<!--分页组件-->
<pagination />
</el-col>
</el-row>
</div>
</template>
<script>
import crudUser from '@/api/system/user'
import { isvalidPhone } from '@/utils/validate'
import { getDepts } from '@/api/system/dept'
import { getAll, getLevel } from '@/api/system/role'
import { getAllJob } from '@/api/system/job'
import CRUD, { presenter, header, form, crud } from '@crud/crud'
import rrOperation from '@crud/RR.operation'
import crudOperation from '@crud/CRUD.operation'
import udOperation from '@crud/UD.operation'
import pagination from '@crud/Pagination'
import Treeselect from '@riophae/vue-treeselect'
import { mapGetters } from 'vuex'
import '@riophae/vue-treeselect/dist/vue-treeselect.css'
let userRoles = []
// crudpresenter
const defaultCrud = CRUD({ title: '用户', url: 'admin/user', crudMethod: { ...crudUser }})
const defaultForm = { id:null,username: null, nickName: null, sex: '男', email: null,
enabled: 1, roles: [], job: { id: null }, dept: { id: null }, phone: null,roleIds: [] }
export default {
name: 'User',
components: { Treeselect, crudOperation, rrOperation, udOperation, pagination },
mixins: [presenter(defaultCrud), header(), form(defaultForm), crud()],
//
dicts: ['user_status'],
data() {
//
const validPhone = (rule, value, callback) => {
if (!value) {
callback(new Error('请输入电话号码'))
} else if (!isvalidPhone(value)) {
callback(new Error('请输入正确的11位手机号码'))
} else {
callback()
}
}
return {
normalizer(node){
//children=[]children respectively
if(node.children && !node.children.length){
delete node.children;
}
},
height: document.documentElement.clientHeight - 180 + 'px;',
deptName: '', depts: [], deptDatas: [], jobs: [], level: 3, roles: [],
defaultProps: { children: 'children', label: 'name' },
permission: {
add: ['admin', 'user:add'],
edit: ['admin', 'user:edit'],
del: ['admin', 'user:del']
},
enabledTypeOptions: [
{ key: 1, display_name: '激活' },
{ key: 0, display_name: '锁定' }
],
rules: {
username: [
{ required: true, message: '请输入用户名', trigger: 'blur' },
{ min: 2, max: 20, message: '长度在 2 到 20 个字符', trigger: 'blur' }
],
nickName: [
{ required: true, message: '请输入用户昵称', trigger: 'blur' },
{ min: 2, max: 20, message: '长度在 2 到 20 个字符', trigger: 'blur' }
],
email: [
{ required: true, message: '请输入邮箱地址', trigger: 'blur' },
{ type: 'email', message: '请输入正确的邮箱地址', trigger: 'blur' }
],
roleIds: [
{ required: true, message: '请选择角色', trigger: 'blur' },
],
phone: [
{ required: true, trigger: 'blur', validator: validPhone }
]
}
}
},
computed: {
...mapGetters([
'user'
])
},
created() {
this.$nextTick(() => {
this.getDeptDatas()
this.crud.toQuery()
this.crud.msg.add = '新增成功,默认密码:123456'
})
},
mounted: function() {
const that = this
window.onresize = function temp() {
that.height = document.documentElement.clientHeight - 180 + 'px;'
}
},
methods: {
changeRole(value) {
userRoles = []
value.forEach(function(data, index) {
const role = { id: data }
userRoles.push(role)
})
},
[CRUD.HOOK.afterAddError](crud) {
this.afterErrorMethod(crud)
},
[CRUD.HOOK.afterEditError](crud) {
this.afterErrorMethod(crud)
},
afterErrorMethod(crud) {
// select
const initRoles = []
userRoles.forEach(function(role, index) {
initRoles.push(role.id)
})
crud.form.roles = initRoles
},
deleteTag(value) {
userRoles.forEach(function(data, index) {
if (data.id === value) {
userRoles.splice(index, value)
}
})
},
beforeInit() {
this.url = 'admin/user'
return true
},
//
[CRUD.HOOK.afterToCU](crud, form) {
this.getDepts()
this.getRoles()
this.getJobs(null)
},
//
[CRUD.HOOK.beforeToEdit](crud, form) {
this.getJobs()
userRoles = []
const roles = []
form.roles.forEach(function(role, index) {
roles.push(role.id)
//
const rol = { id: role.id }
userRoles.push(rol)
})
form.roleIds = roles
},
//
[CRUD.HOOK.afterValidateCU](crud) {
if (!crud.form.dept.id) {
this.$message({
message: '部门不能为空',
type: 'warning'
})
return false
} else if (!crud.form.job.id) {
this.$message({
message: '岗位不能为空',
type: 'warning'
})
return false
} else if (this.roles.length === 0) {
this.$message({
message: '角色不能为空',
type: 'warning'
})
return false
}
crud.form.roles = userRoles
return true
},
//
getDeptDatas() {
const sort = 'id,desc'
const params = { sort: sort }
if (this.deptName) { params['name'] = this.deptName }
getDepts(params).then(res => {
this.deptDatas = res.data.content
})
},
//
getDepts() {
getDepts({ enabled: 1 }).then(res => {
this.depts = res.data.content
})
},
//
handleNodeClick(data) {
if (data.pid === 0) {
this.query.deptId = null
} else {
this.query.deptId = data.id
}
this.crud.toQuery()
},
//
changeEnabled(data, val) {
this.$confirm('此操作将 "' + this.dict.label.user_status[val] + '" ' + data.username + ', 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
crudUser.edit(data).then(res => {
this.crud.notify(this.dict.label.user_status[val] + '成功', CRUD.NOTIFICATION_TYPE.SUCCESS)
}).catch(() => {
data.enabled = !data.enabled
})
}).catch(() => {
data.enabled = !data.enabled
})
},
//
getRoles() {
getAll().then(res => {
this.roles = res.data.content
}).catch(() => { })
},
//
getJobs(id) {
getAllJob(id).then(res => {
this.jobs = res.data.content
}).catch(() => { })
},
//
selectFun(node, instanceId) {
this.getJobs(node.id)
this.form.job.id = null
},
checkboxT(row, rowIndex) {
return row.id !== this.user.id
}
}
}
</script>
<style scoped>
</style>

16
web/src/views/tools/swagger/index.vue

@ -0,0 +1,16 @@
<template>
<elFrame :src="swaggerApi" />
</template>
<script>
import { mapGetters } from 'vuex'
import elFrame from '@/components/Iframe/index'
export default {
name: 'Swagger',
components: { elFrame },
computed: {
...mapGetters([
'swaggerApi'
])
}
}
</script>
Loading…
Cancel
Save