@ -1,4 +1,4 @@ |
||||
import request from "@/config/request" |
||||
import request from "@/api/request" |
||||
import {isEmpty} from "@/util" |
||||
|
||||
export const baseUrl = '/account' |
||||
@ -1,4 +1,4 @@ |
||||
import request from "@/config/request" |
||||
import request from "@/api/request" |
||||
|
||||
export const baseUrl = '/record' |
||||
|
||||
@ -1,4 +1,4 @@ |
||||
import request from "@/config/request" |
||||
import request from "@/api/request" |
||||
|
||||
export const baseUrl = `/statistic` |
||||
|
||||
@ -1,8 +1,20 @@ |
||||
import request from "@/config/request" |
||||
import request from "@/api/request" |
||||
import BASE from './baseUrl' |
||||
|
||||
export const baseUrl = `${BASE}/resource` |
||||
|
||||
export function getAllResources() { |
||||
export function getAll() { |
||||
return request.get(`${baseUrl}/getAll`).then(({data}) => data.data) |
||||
} |
||||
|
||||
export function add(data) { |
||||
return request.post(`${baseUrl}/add`, data).then(({data}) => data) |
||||
} |
||||
|
||||
export function update(data) { |
||||
return request.post(`${baseUrl}/update`, data).then(({data}) => data) |
||||
} |
||||
|
||||
export function del(id) { |
||||
return request.get(`${baseUrl}/del?id=${id}`).then(({data}) => data) |
||||
} |
||||
|
||||
@ -1,24 +1,24 @@ |
||||
import request from "@/config/request" |
||||
import request from "@/api/request" |
||||
import BASE from './baseUrl' |
||||
|
||||
export const baseUrl = `${BASE}/role` |
||||
|
||||
export function searchRoles(data) { |
||||
export function search(data) { |
||||
return request.post(`${baseUrl}/search`, data).then(({data}) => data.data) |
||||
} |
||||
|
||||
export function getRoles() { |
||||
export function get() { |
||||
return request.get(`${baseUrl}/get`).then(({data}) => data.data) |
||||
} |
||||
|
||||
export function addRole(data) { |
||||
export function add(data) { |
||||
return request.post(`${baseUrl}/add`, data).then(({data}) => data) |
||||
} |
||||
|
||||
export function updateRole(data) { |
||||
export function update(data) { |
||||
return request.post(`${baseUrl}/update`, data).then(({data}) => data) |
||||
} |
||||
|
||||
export function delRole(data) { |
||||
export function del(data) { |
||||
return request.post(`${baseUrl}/del`, data).then(({data}) => data) |
||||
} |
||||
|
||||
@ -1,50 +0,0 @@ |
||||
<script type="text/jsx"> |
||||
import {isExternal} from '@/util/validate' |
||||
|
||||
export default { |
||||
name: 'SvgIcon', |
||||
|
||||
functional: true, |
||||
|
||||
props: { |
||||
icon: String, |
||||
className: String |
||||
}, |
||||
|
||||
render(h, context) { |
||||
const {listeners} = context |
||||
const {icon, className} = context.props |
||||
const c = 'svg-icon ' + (className || '') |
||||
if (isExternal(icon)) { |
||||
const style = { |
||||
mask: `url(${this.icon}) no-repeat 50% 50%`, |
||||
'-webkit-mask': `url(${this.icon}) no-repeat 50% 50%` |
||||
} |
||||
return <div {...{on: listeners}} class={'svg-external-icon ' + c} style={style}/> |
||||
} |
||||
else { |
||||
const iconName = `#icon-${icon}` |
||||
return ( |
||||
<svg {...{on: listeners}} class={c} aria-hidden="true"> |
||||
<use href={iconName}/> |
||||
</svg> |
||||
) |
||||
} |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style> |
||||
.svg-icon { |
||||
width: 1em; |
||||
height: 1em; |
||||
fill: currentColor; |
||||
overflow: hidden; |
||||
} |
||||
|
||||
.svg-external-icon { |
||||
background-color: currentColor; |
||||
mask-size: cover !important; |
||||
display: inline-block; |
||||
} |
||||
</style> |
||||
@ -0,0 +1,34 @@ |
||||
<script type="text/jsx"> |
||||
|
||||
export default { |
||||
name: 'VIcon', |
||||
|
||||
functional: true, |
||||
|
||||
props: {icon: String}, |
||||
|
||||
render(h, context) { |
||||
const {data, props: {icon}} = context |
||||
const svgPrefix = 'svg-' |
||||
|
||||
if (icon.startsWith(svgPrefix)) { |
||||
return ( |
||||
<svg class="icon" aria-hidden="true" {...data}> |
||||
<use href={`#icon-${icon.replace(svgPrefix, '')}`}/> |
||||
</svg> |
||||
) |
||||
} |
||||
|
||||
return <i class={icon} {...data}/> |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style> |
||||
.icon { |
||||
width: 1em; |
||||
height: 1em; |
||||
fill: currentColor; |
||||
overflow: hidden; |
||||
} |
||||
</style> |
||||
@ -0,0 +1,46 @@ |
||||
<template> |
||||
<textarea/> |
||||
</template> |
||||
|
||||
<script> |
||||
import CodeMirror from 'codemirror' |
||||
import 'codemirror/lib/codemirror.css' |
||||
import 'codemirror/theme/rubyblue.css' |
||||
import 'codemirror/mode/javascript/javascript' |
||||
import {isEmpty} from "@/util" |
||||
|
||||
export default { |
||||
name: "JsonEditor", |
||||
|
||||
props: ['value'], |
||||
|
||||
watch: { |
||||
value(value) { |
||||
const editorValue = this.jsonEditor.getValue() |
||||
if (value !== editorValue) { |
||||
this.jsonEditor.setValue(this.value) |
||||
} |
||||
} |
||||
}, |
||||
|
||||
mounted() { |
||||
this.jsonEditor = CodeMirror.fromTextArea( |
||||
this.$el, |
||||
{ |
||||
mode: 'application/json', |
||||
theme: 'rubyblue' |
||||
} |
||||
) |
||||
|
||||
!isEmpty(this.value) && this.jsonEditor.setValue(this.value) |
||||
this.jsonEditor.on('change', cm => { |
||||
this.$emit('input', cm.getValue()) |
||||
}) |
||||
}, |
||||
|
||||
beforeDestroy() { |
||||
const element = this.jsonEditor.doc.cm.getWrapperElement() |
||||
element && element.remove && element.remove() |
||||
} |
||||
} |
||||
</script> |
||||
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 379 B After Width: | Height: | Size: 379 B |
|
Before Width: | Height: | Size: 351 B After Width: | Height: | Size: 351 B |
|
Before Width: | Height: | Size: 284 B After Width: | Height: | Size: 284 B |
|
Before Width: | Height: | Size: 305 B After Width: | Height: | Size: 305 B |
|
Before Width: | Height: | Size: 490 B After Width: | Height: | Size: 490 B |
|
Before Width: | Height: | Size: 961 B After Width: | Height: | Size: 961 B |
@ -1,13 +1,13 @@ |
||||
import BottomTip from './BottomTip' |
||||
import Guide from './Guide' |
||||
import ImageViewer from './ImageViewer' |
||||
import PuzzleVerify from './PuzzleVerify' |
||||
import Verify from './Verify' |
||||
import Signature from './SignautreBoard' |
||||
|
||||
export default function (Vue) { |
||||
Vue.prototype.$bottomTip = BottomTip |
||||
Vue.prototype.$guide = Guide |
||||
Vue.prototype.$image = ImageViewer |
||||
Vue.prototype.$puzzleVerify = PuzzleVerify |
||||
Vue.prototype.$verify = Verify |
||||
Vue.prototype.$signature = Signature |
||||
} |
||||
|
||||
@ -0,0 +1,34 @@ |
||||
/* |
||||
* 树形控件页通用混入 |
||||
* */ |
||||
|
||||
const mixin = { |
||||
data() { |
||||
return { |
||||
curNode: null |
||||
} |
||||
}, |
||||
|
||||
watch: { |
||||
curNode(v) { |
||||
const ref = this.$refs.tree |
||||
!v && ref && ref.setCurrentKey() |
||||
} |
||||
}, |
||||
|
||||
methods: { |
||||
nodeClick(data, node, ref) { |
||||
if (this.curNode === node) { |
||||
this.$refs.tree.setCurrentKey() |
||||
this.curNode = null |
||||
} |
||||
else this.curNode = node |
||||
|
||||
if (this.afterNodeClick) { |
||||
this.afterNodeClick(data, node, ref, !this.curNode) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
export default mixin |
||||
@ -0,0 +1,13 @@ |
||||
export const nodeType = [ |
||||
{label: '聚合菜单', value: 1, icon: 'el-icon-folder'}, |
||||
{label: '页面菜单', value: 2, icon: 'el-icon-document'}, |
||||
{label: '数据接口', value: 3, icon: 'el-icon-open'}, |
||||
] |
||||
|
||||
export function getNodeInfo({data: {type}} = {}) { |
||||
return nodeType.find(i => i.value === type) |
||||
} |
||||
|
||||
export function getNodeTitle({data: {type, name, meta}}) { |
||||
return type === 3 ? name : meta.title |
||||
} |
||||
@ -0,0 +1,358 @@ |
||||
<template> |
||||
<el-card> |
||||
<el-row class="button-group"> |
||||
<template v-if="canAdd"> |
||||
<el-button icon="el-icon-plus" size="small" type="primary" @click="addRoot">添加根节点</el-button> |
||||
<el-button icon="el-icon-plus" size="small" type="primary" @click="addChild">添加子节点</el-button> |
||||
</template> |
||||
<el-button v-if="canDel" icon="el-icon-delete" size="small" type="danger" @click="del">删 除</el-button> |
||||
<el-dropdown placement="bottom" size="small" @command="more"> |
||||
<el-button plain size="small"> |
||||
更多操作 |
||||
<i class="el-icon-arrow-down el-icon--right"/> |
||||
</el-button> |
||||
<el-dropdown-menu slot="dropdown"> |
||||
<el-dropdown-item :command="{action:'expand',level:0}">展开全部</el-dropdown-item> |
||||
<el-dropdown-item :command="{action:'collapse',level:0}">收起全部</el-dropdown-item> |
||||
<el-dropdown-item :command="{action:'expand',level:1}">仅展开一级</el-dropdown-item> |
||||
</el-dropdown-menu> |
||||
</el-dropdown> |
||||
</el-row> |
||||
|
||||
<el-row :gutter="100" style="margin-top: 20px;"> |
||||
<el-col :xs="24" :sm="11" :md="9" :lg="8" :xl="6"> |
||||
<el-alert show-icon :closable="false"> |
||||
<template v-slot:title> |
||||
当前选择编辑: |
||||
<el-tag v-if="curNode" size="mini" closable @close="() => curNode = null"> |
||||
{{ getNodeTitle(curNode) }} |
||||
</el-tag> |
||||
</template> |
||||
</el-alert> |
||||
|
||||
<el-input |
||||
v-model="search" |
||||
placeholder="输入菜单名搜索" |
||||
suffix-icon="el-icon-search" |
||||
size="small" |
||||
style="margin-top: 10px" |
||||
@input="v => $refs.tree.filter(v)" |
||||
/> |
||||
|
||||
<div style="max-height: 660px;overflow: auto;margin-top: 10px"> |
||||
<el-tree |
||||
ref="tree" |
||||
:data="menus" |
||||
node-key="id" |
||||
highlight-current |
||||
accordion |
||||
:expand-on-click-node="false" |
||||
:filter-node-method="filterNodeMethod" |
||||
@node-click="nodeClick" |
||||
> |
||||
<span slot-scope="{node}" class="el-tree-node__label"> |
||||
<v-icon :icon="getNodeInfo(node).icon"/> |
||||
{{ getNodeTitle(node) }} |
||||
</span> |
||||
</el-tree> |
||||
</div> |
||||
</el-col> |
||||
|
||||
<el-col :xs="24" :sm="13" :md="15" :lg="13" :xl="11"> |
||||
<abstract-form v-if="showForm" :model="form" :rules="rules"> |
||||
<el-form-item label="类型"> |
||||
<el-select |
||||
v-if="showTypeSelector" |
||||
:value="form.type" |
||||
placeholder="请选择类型" |
||||
@change="typeChange" |
||||
> |
||||
<v-icon slot="prefix" :icon="getNodeInfo({data:form}).icon"/> |
||||
<el-option |
||||
v-for="{label,value,icon} in nodeType" |
||||
:key="value" |
||||
:label="label" |
||||
:value="value" |
||||
:disabled="value === 3" |
||||
> |
||||
<v-icon :icon="icon"/> |
||||
{{ label }} |
||||
</el-option> |
||||
</el-select> |
||||
|
||||
<template v-else> |
||||
<v-icon :icon="getNodeInfo({data:form}).icon"/> |
||||
{{ getNodeInfo({data: form}).label }} |
||||
</template> |
||||
</el-form-item> |
||||
|
||||
<el-form-item v-if="form.pid" label="上级菜单"> |
||||
<v-icon :icon="getParentNodeInfo().icon"/> |
||||
{{ getParentNodeInfo().title }} |
||||
</el-form-item> |
||||
|
||||
<el-form-item label="名称" prop="title"> |
||||
<el-input v-if="form.type === 3" v-model="form.name" maxlength="10"/> |
||||
<template v-else>{{ form.meta.title }}</template> |
||||
</el-form-item> |
||||
|
||||
<el-form-item label="路径" prop="path"> |
||||
<el-input v-model="form.path" maxlength="100"/> |
||||
</el-form-item> |
||||
|
||||
<!--菜单独有配置项--> |
||||
<template v-if="form.type !== 3"> |
||||
<!--叶子菜单独有配置项--> |
||||
<template v-if="form.type === 2"> |
||||
<el-form-item label="路由名称" prop="name"> |
||||
<el-input v-model="form.name" maxlength="50"/> |
||||
</el-form-item> |
||||
<el-form-item label="菜单组件" prop="component"> |
||||
<el-input v-model="form.component" maxlength="50"/> |
||||
</el-form-item> |
||||
</template> |
||||
|
||||
<el-form-item label="meta" prop="meta"> |
||||
<json-editor v-model="form.metaStr" @input="metaChange"/> |
||||
</el-form-item> |
||||
</template> |
||||
|
||||
<el-form-item label="admin独有"> |
||||
<el-switch v-model="form.admin" active-text="是" inactive-text="否"/> |
||||
</el-form-item> |
||||
|
||||
<el-form-item label="是否启用"> |
||||
<el-switch v-model="form.enable" active-text="是" inactive-text="否"/> |
||||
</el-form-item> |
||||
|
||||
<el-form-item v-if="showSaveBtn"> |
||||
<el-button |
||||
:loading="loading" |
||||
icon="el-icon-edit-outline" |
||||
size="small" |
||||
type="primary" |
||||
@click="save" |
||||
> |
||||
保 存 |
||||
</el-button> |
||||
</el-form-item> |
||||
</abstract-form> |
||||
</el-col> |
||||
</el-row> |
||||
</el-card> |
||||
</template> |
||||
|
||||
<script> |
||||
import treePageMixin from '@/mixin/treePageMixin' |
||||
import AbstractForm from "@/component/AbstractForm" |
||||
import JsonEditor from "@/component/editor/JsonEditor" |
||||
import {baseUrl, add, update, del} from '@/api/system/resource' |
||||
import {isEmpty, mergeObj, resetObj} from "@/util" |
||||
import {elSuccess, elError, elConfirm} from "@/util/message" |
||||
import {elTreeControl} from "@/util/tree" |
||||
import {auth} from "@/util/auth" |
||||
import {nodeType, getNodeInfo, getNodeTitle} from "./common" |
||||
|
||||
export default { |
||||
name: "menuManagement", |
||||
|
||||
mixins: [treePageMixin], |
||||
|
||||
components: {AbstractForm, JsonEditor}, |
||||
|
||||
data() { |
||||
const validateTitle = (r, v, c) => { |
||||
const value = getNodeTitle({data: this.form}) |
||||
c(isEmpty(value) ? '名称不能为空' : undefined) |
||||
} |
||||
return { |
||||
loading: false, |
||||
search: null, |
||||
type: 'add',// add or edit |
||||
nodeType, |
||||
form: { |
||||
id: null, |
||||
pid: null, |
||||
type: 0, |
||||
name: null, |
||||
path: null, |
||||
component: null, |
||||
meta: { |
||||
title: null, |
||||
dynamicTitle: null, |
||||
hidden: false, |
||||
alwaysShow: false, |
||||
sort: null, |
||||
icon: null, |
||||
affix: false, |
||||
noCache: false, |
||||
activeMenu: null, |
||||
noAuth: false, |
||||
iframe: null, |
||||
usePathKey: false, |
||||
useFullPathKey: false, |
||||
commonModule: null |
||||
}, |
||||
metaStr: null, |
||||
admin: false, |
||||
enable: true |
||||
}, |
||||
rules: { |
||||
title: [{validator: validateTitle, trigger: 'change'}], |
||||
path: [{required: true, message: '路径不能为空', trigger: 'change'}], |
||||
} |
||||
} |
||||
}, |
||||
|
||||
computed: { |
||||
canAdd() { |
||||
return auth(`${baseUrl}/add`) |
||||
}, |
||||
canUpdate() { |
||||
return auth(`${baseUrl}/update`) |
||||
}, |
||||
canDel() { |
||||
return auth(`${baseUrl}/del`) |
||||
}, |
||||
|
||||
menus() { |
||||
return this.$store.state.resource.resourceTree |
||||
}, |
||||
showForm() { |
||||
return this.type === 'add' && this.form.pid === 0 || this.curNode |
||||
}, |
||||
showTypeSelector() { |
||||
const isRoot = this.form.pid === 0 |
||||
//1.非数据接口 |
||||
const noApiNode = this.form.type !== 3 |
||||
//2.其下无子节点 |
||||
const noChild = this.type === 'add' && isRoot |
||||
|| this.type === 'edit' && this.curNode.childNodes.length <= 0 |
||||
//3.不是初始节点 |
||||
const noInitialNode = this.form.path !== '/' || isRoot |
||||
//4.父节点只能为聚合菜单 |
||||
const parentIsFolderMenu = |
||||
isRoot |
||||
? true |
||||
: this.type === 'add' && this.curNode.data.type === 1 |
||||
|| this.type === 'edit' && this.curNode.parent.data.type === 1 |
||||
return noApiNode && noChild && noInitialNode && parentIsFolderMenu |
||||
}, |
||||
showSaveBtn() { |
||||
return this.type === 'add' && this.canAdd || this.type === 'edit' && this.canUpdate |
||||
} |
||||
}, |
||||
|
||||
methods: { |
||||
getNodeInfo, |
||||
getNodeTitle, |
||||
getParentNodeInfo() { |
||||
const node = this.type === 'add' ? this.curNode : this.curNode.parent |
||||
const {icon} = getNodeInfo(node) |
||||
return {icon, title: getNodeTitle(node)} |
||||
}, |
||||
filterNodeMethod(value, data) { |
||||
if (isEmpty(value)) return true |
||||
return getNodeTitle({data}).includes(value) |
||||
}, |
||||
afterNodeClick(data, node, ref, clear) { |
||||
if (clear) return this.clearForm() |
||||
this.type = 'edit' |
||||
this.$refs.form && this.$refs.form.clearValidate() |
||||
mergeObj(this.form, data) |
||||
if (data.meta) { |
||||
this.form.metaStr = JSON.stringify(data.meta, null, 2) |
||||
} |
||||
}, |
||||
//json编辑器值改变时 |
||||
metaChange(v) { |
||||
try { |
||||
v = JSON.parse(v) |
||||
} |
||||
catch (e) { |
||||
return |
||||
} |
||||
resetObj(this.form.meta) |
||||
mergeObj(this.form.meta, v) |
||||
}, |
||||
//表单中菜单类型选择器改变时 |
||||
typeChange(v) { |
||||
//从聚合改为页面菜单时,清空component |
||||
if (v === 2 && this.form.type === 1) { |
||||
this.form.component = null |
||||
} |
||||
this.form.type = v |
||||
}, |
||||
|
||||
addRoot() { |
||||
if (this.loading) return |
||||
this.curNode = null |
||||
this.type = 'add' |
||||
this.clearForm() |
||||
this.form.pid = 0 |
||||
this.form.type = 1 |
||||
}, |
||||
addChild() { |
||||
if (this.loading) return |
||||
if (!this.curNode) return elError('请选择一个节点') |
||||
const {data: {id, type}} = this.curNode |
||||
if (type === 3) return elError('数据接口不能作为父节点') |
||||
this.type = 'add' |
||||
this.clearForm() |
||||
this.form.pid = id |
||||
//如果是叶子菜单,那么只能添加接口 |
||||
this.form.type = type === 2 ? 3 : 1 |
||||
}, |
||||
del() { |
||||
if (this.loading) return |
||||
if (!this.curNode) return elError('请选择要删除的节点') |
||||
elConfirm(`确定删除【${getNodeTitle(this.curNode)}】?`) |
||||
.then(() => { |
||||
this.loading = true |
||||
return del(this.curNode.data.id) |
||||
}) |
||||
.then(() => { |
||||
elSuccess('删除成功') |
||||
this.init() |
||||
}) |
||||
.finally(() => this.loading = false) |
||||
}, |
||||
more({action, level}) { |
||||
if (this.loading) return |
||||
elTreeControl(this.$refs.tree, action, level) |
||||
}, |
||||
|
||||
init() { |
||||
this.curNode = null |
||||
this.clearForm() |
||||
return this.$store.dispatch('resource/init', this.$store.state.user) |
||||
}, |
||||
clearForm() { |
||||
resetObj(this.form) |
||||
this.form.id = null |
||||
this.form.pid = null |
||||
this.form.metaStr = JSON.stringify({title: ''}, null, 2) |
||||
this.form.meta.sort = null |
||||
this.form.enable = true |
||||
}, |
||||
save() { |
||||
if (this.loading) return |
||||
this.$refs.form.validate(v => { |
||||
if (!v) return |
||||
this.loading = true |
||||
const data = this.transformForm() |
||||
const promise = this.type === 'add' ? add(data) : update(data) |
||||
promise |
||||
.then(({data, msg}) => { |
||||
elSuccess(msg) |
||||
return this.init() |
||||
}) |
||||
.finally(() => this.loading = false) |
||||
}) |
||||
}, |
||||
transformForm() { |
||||
return {...this.form, meta: this.form.metaStr, metaStr: undefined} |
||||
} |
||||
} |
||||
} |
||||
</script> |
||||