模块嵌入

master
xy 2 years ago
parent 80b159d3c0
commit 8ccdce6ad6

@ -0,0 +1,25 @@
import request from '@/utils/request';
export function index (params) {
return request({
url: "/api/backend/department",
method: "get",
params
})
}
export function save (data) {
return request({
url: "/api/backend/department/save",
method: "post",
data
})
}
export function destroy (data) {
return request({
url: "/api/backend/department/delete",
method: "post",
data
})
}

@ -24,10 +24,3 @@ export function destroy (data) {
})
}
export function getRoles (params) {
return request({
url: "/api/backend/module/grant/get-roles",
method: "get",
params
})
}

@ -0,0 +1,134 @@
<template>
<el-select :size="size"
clearable
:popper-append-to-body="popperAppendToBody"
:style="{ width: width }"
value-key="id"
filterable
:multiple="multiple"
:loading="loading"
v-model="myValue"
@change="e => $emit('input',e)">
<el-option v-for="(item, index) in list"
:key="item.id"
:label="item.name"
:value="item.id">
</el-option>
</el-select>
</template>
<script>
let listData = [];
import { index } from "@/api/user"
export default {
props: {
popperAppendToBody: {
type: Boolean,
default: true
},
width: String,
multiple: Boolean,
size: String,
value: {
type: [String,Number],
default: "",
required: true
}
},
data() {
return {
total: 0,
loading: false,
myValue: "",
list: [],
// select: {
// rows: 20,
// page: 1,
// keyword: ""
// },
select: {
rows: 9999,
page: 1,
}
}
},
methods: {
remoteMethod (query) {
if (query !== "") {
this.select.keyword = query
this.select.page = 1
this.list = []
this.getList()
} else {
this.list = []
this.select.page = 1
this.getList()
}
},
async getList () {
if (listData instanceof Array && listData.length > 0) {
this.list = listData;
} else {
this.loading = true;
try {
const res = await index(this.select);
listData = res.data;
this.list = res.data;
this.total = res.total;
this.loading = false;
} catch (err) {
this.loading = false;
}
}
},
// async getList () {
// if ((this.list.length >= this.total) && this.list.length !== 0) return;
//
// this.loading = true;
// try {
// const res = await index(this.select);
// this.list = this.list.concat(res.data);
// this.total = res.total;
// console.log(this.list)
//
// this.loading = false;
// } catch (err) {
// this.loading = false;
// }
// },
initLoadMore() {
this.$nextTick(() => {
this.$el.querySelector(".el-scrollbar__wrap").addEventListener("scroll",this.loadListener)
})
},
loadListener(e) {
let sign = 0
const scrollDistance = e.scrollHeight - e.scrollTop - e.clientHeight
if (scrollDistance <= sign) {
this.getList()
}
}
},
computed: {},
watch: {
value: {
handler: function(newVal) {
this.myValue = newVal
},
immediate: true
}
},
mounted() {
this.getList();
//this.initLoadMore();
},
beforeDestroy() {
//this.$el?.querySelector(".el-scrollbar__wrap")?.removeEventListener("scroll",this.loadListener)
}
}
</script>
<style scoped lang="scss">
</style>

@ -1 +1 @@
<svg t="1724745509265" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5088" width="200" height="200"><path d="M256 640a42.666667 42.666667 0 0 1-30.293333-12.373333l-85.333334-85.333334a42.666667 42.666667 0 0 1 0-60.586666l85.333334-85.333334a42.666667 42.666667 0 0 1 60.586666 60.586667L230.826667 512l55.466666 55.04a42.666667 42.666667 0 0 1 0 60.586667A42.666667 42.666667 0 0 1 256 640z" p-id="5089"></path><path d="M640 554.666667H170.666667a42.666667 42.666667 0 0 1 0-85.333334h469.333333a42.666667 42.666667 0 0 1 0 85.333334z" p-id="5090"></path><path d="M853.333333 938.666667h-7.68l-469.333333-85.333334A42.666667 42.666667 0 0 1 341.333333 810.666667v-170.666667a42.666667 42.666667 0 0 1 85.333334 0v135.253333l384 69.546667V179.2l-384 69.546667V384a42.666667 42.666667 0 0 1-85.333334 0V213.333333a42.666667 42.666667 0 0 1 34.986667-42.666666l469.333333-85.333334a42.666667 42.666667 0 0 1 34.986667 8.96A42.666667 42.666667 0 0 1 896 128v768a42.666667 42.666667 0 0 1-15.36 32.853333A42.666667 42.666667 0 0 1 853.333333 938.666667z" p-id="5091"></path></svg>
<svg t="1724825843243" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7581" width="200" height="200"><path d="M716.8 85.333333h-110.933333C469.333333 85.333333 384 170.666667 384 307.2v172.8h266.666667c17.493333 0 32 14.506667 32 32s-14.506667 32-32 32H384v172.8C384 853.333333 469.333333 938.666667 605.866667 938.666667h110.506666c136.533333 0 221.866667-85.333333 221.866667-221.866667V307.2C938.666667 170.666667 853.333333 85.333333 716.8 85.333333z" fill="currentColor" p-id="7582"></path><path d="M194.602667 480l88.32-88.32c6.4-6.4 9.386667-14.506667 9.386666-22.613333s-2.986667-16.64-9.386666-22.613334a32.170667 32.170667 0 0 0-45.226667 0l-142.933333 142.933334c-12.373333 12.373333-12.373333 32.853333 0 45.226666l142.933333 142.933334c12.373333 12.373333 32.853333 12.373333 45.226667 0 12.373333-12.373333 12.373333-32.853333 0-45.226667l-88.32-88.32h189.44v-64h-189.44z" fill="currentColor" p-id="7583"></path></svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 978 B

@ -0,0 +1 @@
<svg t="1724827986771" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4273" width="200" height="200"><path d="M63.97608 127.975696v127.909182h895.454322V127.975696H63.97608z m255.843946 447.727161h639.610376V447.787536H319.820026v127.915321z m0 319.780118h639.610376V767.541047H319.820026v127.941928zM63.97608 575.702857h127.922485V447.787536H63.97608v127.915321z m0 319.780118h127.922485V767.541047H63.97608v127.941928z" p-id="4274"></path></svg>

After

Width:  |  Height:  |  Size: 493 B

@ -0,0 +1 @@
<svg t="1724812234495" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8792" width="200" height="200"><path d="M363.264 508.928H89.728C40.192 508.928 0 468.224 0 418.176V146.176c0-50.048 40.192-90.752 89.728-90.752h273.536c49.408 0 89.6 40.704 89.6 90.752v272c0 50.048-40.192 90.752-89.6 90.752zM89.728 127.36c-10.112 0-18.56 8.576-18.56 18.816v272c0 10.24 8.448 18.816 18.56 18.816h273.536c10.112 0 18.56-8.576 18.56-18.816V146.176c0-10.24-8.448-18.816-18.56-18.816H89.728zM363.264 1024H89.728C40.192 1024 0 983.296 0 933.248V661.248c0-50.048 40.192-90.752 89.728-90.752h273.536c49.408 0 89.6 40.704 89.6 90.752v272c0 50.048-40.192 90.752-89.6 90.752zM89.728 642.56c-10.112 0-18.56 8.576-18.56 18.816v272c0 10.24 8.448 18.816 18.56 18.816h273.536c10.112 0 18.56-8.576 18.56-18.816V661.248c0-10.112-8.448-18.816-18.56-18.816H89.728zM878.72 1024H605.184c-49.536 0-89.728-40.704-89.728-90.752V661.248c0-50.048 40.192-90.752 89.728-90.752h273.536c49.536 0 89.728 40.704 89.728 90.752v272c0 50.048-40.192 90.752-89.728 90.752zM605.184 642.56c-10.112 0-18.56 8.576-18.56 18.816v272c0 10.24 8.448 18.816 18.56 18.816h273.536c10.112 0 18.56-8.576 18.56-18.816V661.248c0-10.112-8.448-18.816-18.56-18.816H605.184zM742.016 564.352c-23.552 0-47.104-8.96-65.024-26.88L487.04 347.776c-17.408-17.536-27.008-40.832-27.008-65.664s9.6-48.128 27.008-65.664L676.864 26.88c35.84-35.84 94.208-35.84 130.176 0l189.824 189.696c17.408 17.408 27.136 40.704 27.136 65.664s-9.728 48.256-27.136 65.664L807.04 537.472c-17.92 17.92-41.472 26.88-65.024 26.88z m0-492.416c-5.504 0-11.008 2.048-15.104 6.144L537.088 267.648c-3.84 3.84-5.888 8.96-5.888 14.464s2.048 10.624 5.888 14.464l189.824 189.696c8.192 8.192 22.144 8.064 30.208 0l189.824-189.696c3.84-3.84 6.016-8.96 6.016-14.464s-2.048-10.624-6.016-14.464L757.12 78.08c-4.096-4.096-9.6-6.144-15.104-6.144z" fill="currentColor" p-id="8793"></path></svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

@ -0,0 +1 @@
<svg t="1724828046614" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6137" width="200" height="200"><path d="M910.222222 859.022222c0 39.822222-79.644444 51.2-153.6 51.2-73.955556 0-153.6-11.377778-153.6-51.2v-11.377778c0-28.444444 51.2-62.577778 102.4-79.644444 5.688889 0 17.066667-11.377778 11.377778-34.133333-22.755556-22.755556-39.822222-62.577778-39.822222-96.711111 0-56.888889 34.133333-85.333333 79.644444-85.333334 45.511111 0 79.644444 28.444444 79.644445 85.333334 0 39.822222-17.066667 73.955556-39.822223 96.711111-11.377778 28.444444 5.688889 34.133333 5.688889 34.133333 45.511111 17.066667 102.4 45.511111 102.4 79.644444l5.688889 11.377778z m-159.288889-386.844444c-153.6 0-273.066667 125.155556-273.066666 273.066666s125.155556 273.066667 273.066666 273.066667 273.066667-125.155556 273.066667-273.066667c0-147.911111-125.155556-273.066667-273.066667-273.066666zM449.422222 153.6c39.822222-56.888889 113.777778-68.266667 170.666667-34.133333h5.688889c56.888889 39.822222 68.266667 119.466667 28.444444 176.355555s-79.644444 17.066667-136.533333-22.755555-108.088889-62.577778-68.266667-119.466667z m170.666667 278.755556c39.822222-17.066667 91.022222-28.444444 136.533333-28.444445h11.377778c22.755556-56.888889 28.444444-119.466667 17.066667-182.044444-28.444444-142.222222-164.977778-238.933333-307.2-216.177778-142.222222 28.444444-267.377778 136.533333-238.933334 284.444444 5.688889 28.444444 22.755556 79.644444 39.822223 113.777778L17.066667 779.377778c-11.377778 11.377778-17.066667 39.822222-11.377778 56.888889l11.377778 62.577777c-11.377778-62.577778 5.688889 22.755556 11.377777 22.755556H113.777778c17.066667-5.688889 39.822222-17.066667 45.511111-34.133333l102.4-159.288889 91.022222 5.688889 119.466667-187.733334c11.377778-17.066667 22.755556-28.444444 34.133333-39.822222 34.133333-34.133333 68.266667-56.888889 113.777778-73.955555z" fill="currentColor" p-id="6138"></path></svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

@ -3,12 +3,12 @@
<header class="navbar navbar-expand-md d-print-none">
<div class="container-xl">
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbar-menu" aria-controls="navbar-menu" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
<span class="navbar-toggler-icon" />
</button>
<div class="navbar-brand navbar-brand-autodark d-none-navbar-horizontal pe-0 pe-md-3">
<router-link to="/">
<svg xmlns="http://www.w3.org/2000/svg" width="110" height="32" viewBox="0 0 232 68" class="navbar-brand-image">
<path d="M64.6 16.2C63 9.9 58.1 5 51.8 3.4 40 1.5 28 1.5 16.2 3.4 9.9 5 5 9.9 3.4 16.2 1.5 28 1.5 40 3.4 51.8 5 58.1 9.9 63 16.2 64.6c11.8 1.9 23.8 1.9 35.6 0C58.1 63 63 58.1 64.6 51.8c1.9-11.8 1.9-23.8 0-35.6zM33.3 36.3c-2.8 4.4-6.6 8.2-11.1 11-1.5.9-3.3.9-4.8.1s-2.4-2.3-2.5-4c0-1.7.9-3.3 2.4-4.1 2.3-1.4 4.4-3.2 6.1-5.3-1.8-2.1-3.8-3.8-6.1-5.3-2.3-1.3-3-4.2-1.7-6.4s4.3-2.9 6.5-1.6c4.5 2.8 8.2 6.5 11.1 10.9 1 1.4 1 3.3.1 4.7zM49.2 46H37.8c-2.1 0-3.8-1-3.8-3s1.7-3 3.8-3h11.4c2.1 0 3.8 1 3.8 3s-1.7 3-3.8 3z" fill="#066fd1" style="fill: var(--tblr-primary, #066fd1)"></path>
<path d="M64.6 16.2C63 9.9 58.1 5 51.8 3.4 40 1.5 28 1.5 16.2 3.4 9.9 5 5 9.9 3.4 16.2 1.5 28 1.5 40 3.4 51.8 5 58.1 9.9 63 16.2 64.6c11.8 1.9 23.8 1.9 35.6 0C58.1 63 63 58.1 64.6 51.8c1.9-11.8 1.9-23.8 0-35.6zM33.3 36.3c-2.8 4.4-6.6 8.2-11.1 11-1.5.9-3.3.9-4.8.1s-2.4-2.3-2.5-4c0-1.7.9-3.3 2.4-4.1 2.3-1.4 4.4-3.2 6.1-5.3-1.8-2.1-3.8-3.8-6.1-5.3-2.3-1.3-3-4.2-1.7-6.4s4.3-2.9 6.5-1.6c4.5 2.8 8.2 6.5 11.1 10.9 1 1.4 1 3.3.1 4.7zM49.2 46H37.8c-2.1 0-3.8-1-3.8-3s1.7-3 3.8-3h11.4c2.1 0 3.8 1 3.8 3s-1.7 3-3.8 3z" fill="#066fd1" style="fill: var(--tblr-primary, #066fd1)" />
</svg>
</router-link>
</div>
@ -18,31 +18,31 @@
<template v-if="isFullscreen">
<a class="nav-link px-0 fullscreen" @click="fullscreen">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon">
<path stroke-width="2" d="M 4 8 l 4 0 l 0 -4 M 16 4 l 0 4 l 4 0 M 20 16 l -4 0 l 0 4 M 8 20 l 0 -4 l -4 0"></path>
<path stroke-width="2" d="M 4 8 l 4 0 l 0 -4 M 16 4 l 0 4 l 4 0 M 20 16 l -4 0 l 0 4 M 8 20 l 0 -4 l -4 0" />
</svg>
</a>
</template>
<template v-else>
<a class="nav-link px-0 fullscreen" @click="fullscreen">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon">
<path stroke-width="2" d="M 4 8 l 0 -4 l 4 0 M 16 4 l 4 0 l 0 4 M 20 16 l 0 4 l -4 0 M 8 20 l -4 0 l 0 -4"></path>
<path stroke-width="2" d="M 4 8 l 0 -4 l 4 0 M 16 4 l 4 0 l 0 4 M 20 16 l 0 4 l -4 0 M 8 20 l -4 0 l 0 -4" />
</svg>
</a>
</template>
<div class="nav-item d-md-flex">
<theme-picker class="nav-link"></theme-picker>
<theme-picker class="nav-link" />
</div>
<div class="nav-item d-none d-md-flex me-3">
<a class="nav-link px-0" data-bs-toggle="dropdown" tabindex="-1" aria-label="Show notifications">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M10 5a2 2 0 1 1 4 0a7 7 0 0 1 4 6v3a4 4 0 0 0 2 3h-16a4 4 0 0 0 2 -3v-3a7 7 0 0 1 4 -6"></path><path d="M9 17v1a3 3 0 0 0 6 0v-1"></path></svg>
<span class="badge bg-red"></span>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon"><path stroke="none" d="M0 0h24v24H0z" fill="none" /><path d="M10 5a2 2 0 1 1 4 0a7 7 0 0 1 4 6v3a4 4 0 0 0 2 3h-16a4 4 0 0 0 2 -3v-3a7 7 0 0 1 4 -6" /><path d="M9 17v1a3 3 0 0 0 6 0v-1" /></svg>
<span class="badge bg-red" />
</a>
</div>
</div>
<div class="nav-item">
<el-dropdown>
<el-dropdown @command="userCommandHandle">
<a class="nav-link d-flex lh-1 text-reset p-0" aria-label="Open user menu">
<img class="avatar avatar-sm" style="" src="@/assets/face.jpg" alt="" >
<img class="avatar avatar-sm" style="" src="@/assets/face.jpg" alt="">
<div class="d-none d-xl-block ps-2">
<div>{{ name }}</div>
<div class="mt-1 small text-secondary">{{ (department && department.name) ? department.name : '暂无部门' }}</div>
@ -50,16 +50,16 @@
</a>
<el-dropdown-menu slot="dropdown" size="mini">
<el-dropdown-item>
<SvgIcon icon-class="user"></SvgIcon>
<el-dropdown-item command="info">
<SvgIcon icon-class="user" />
<span style="padding-left: 8px;">用户信息</span>
</el-dropdown-item>
<el-dropdown-item>
<SvgIcon icon-class="password"></SvgIcon>
<el-dropdown-item command="password">
<SvgIcon icon-class="password"/>
<span style="padding-left: 8px;">修改密码</span>
</el-dropdown-item>
<el-dropdown-item divided>
<SvgIcon icon-class="logout"></SvgIcon>
<el-dropdown-item divided command="logout">
<SvgIcon icon-class="logout" />
<span style="padding-left: 8px;">退出</span>
</el-dropdown-item>
</el-dropdown-menu>
@ -70,14 +70,16 @@
</header>
<header class="navbar-expand-md">
<div class="collapse navbar-collapse" id="navbar-menu">
<el-menu style="width: 100%;padding: 0 10px;"
:text-color="variables.menuText"
:background-color="variables.menuBg"
:active-text-color="variables.menuActiveText"
:default-active="activeMenu"
mode="horizontal">
<Item v-for="(item, index) in permission_routes" :key="item.key" :item="item" :base-path="item.path"></Item>
<div id="navbar-menu" class="collapse navbar-collapse">
<el-menu
style="width: 100%;padding: 0 10px;"
:text-color="variables.menuText"
:background-color="variables.menuBg"
:active-text-color="variables.menuActiveText"
:default-active="activeMenu"
mode="horizontal"
>
<Item v-for="(item, index) in permission_routes" :key="item.key" :item="item" :base-path="item.path" />
</el-menu>
</div>
</header>
@ -85,9 +87,9 @@
</template>
<script>
import variables from "@/styles/variables.scss"
import variables from '@/styles/variables.scss'
import { mapGetters } from 'vuex'
import Item from "./Item.vue"
import Item from './Item.vue'
import SvgIcon from '@/components/SvgIcon/index.vue'
import ThemePicker from '@/components/ThemePicker/index.vue'
export default {
@ -96,13 +98,13 @@ export default {
Item,
SvgIcon
},
data () {
data() {
return {
isFullscreen: false,
isFullscreen: false
}
},
computed: {
variables () {
variables() {
return variables
},
...mapGetters([
@ -123,13 +125,21 @@ export default {
return meta.activeMenu
}
return path
},
}
},
created() {
},
mounted() {
document.addEventListener('fullscreenchange', this.handleFullscreen)
},
beforeDestroy() {
document.removeEventListener('fullscreenchange', this.handleFullscreen)
},
methods: {
handleFullscreen () {
handleFullscreen() {
this.isFullscreen = document.fullscreenElement !== null
},
fullscreen () {
fullscreen() {
if (!document.fullscreenElement) {
//
document.documentElement.requestFullscreen()
@ -138,18 +148,19 @@ export default {
document.exitFullscreen()
}
},
async userCommandHandle (command) {
switch (command){
case "logout":
await this.logout();
break;
}
},
async logout() {
await this.$store.dispatch('user/logout')
await this.$store.commit('permission/SET_ROUTES',[])
this.$router.push(`/login?redirect=${this.$route.fullPath}`)
}
},
created() {
},
mounted() {
document.addEventListener('fullscreenchange', this.handleFullscreen)
},
beforeDestroy() {
document.removeEventListener('fullscreenchange', this.handleFullscreen)
}
}
</script>

@ -0,0 +1,16 @@
<template>
<router-view />
</template>
<script>
export default {
data() {
return {}
},
methods: {},
computed: {}
}
</script>
<style scoped lang="scss">
</style>

@ -27,7 +27,6 @@ if (process.env.NODE_ENV === "production") {
const { mockXHR } = require("../mock");
mockXHR();
}
// set ElementUI lang to EN
//Vue.use(ElementUI, { locale });
// 如果想要中文版 element-ui按如下方式声明
@ -56,7 +55,7 @@ import Wujie from "wujie-vue2";
Vue.use(Wujie);
const { setupApp, preloadApp } = Wujie;
setupApp({
name: "contract",
name: "modules",
url: "http://localhost:9530/admin/#/",
exec: true,
});
@ -66,5 +65,5 @@ new Vue({
el: "#app",
router,
store,
render: (h) => h(App),
render: (h) => h(App)
});

@ -1,5 +1,6 @@
import router from './router'
import store from './store'
import vue from './main'
import { Message } from 'element-ui'
import NProgress from 'nprogress' // progress bar
import 'nprogress/nprogress.css' // progress bar style
@ -10,7 +11,6 @@ NProgress.configure({ showSpinner: false }) // NProgress Configuration
const whiteList = ['/login'] // no redirect whitelist
let isFirstEnter = true; //第一次进入获取用户信息
router.beforeEach(async(to, from, next) => {
// start progress bar
NProgress.start()
@ -27,7 +27,11 @@ router.beforeEach(async(to, from, next) => {
next({ path: '/' })
NProgress.done()
} else {
if (!isFirstEnter) {
if (to.meta.isModule) {
store.commit('modules/SET_MODULE_URI',to.meta.moduleUri)
}
if (store.state.permission.addRoutes && store.state.permission.addRoutes instanceof Array && store.state.permission.addRoutes.length > 0) {
next()
} else {
try {
@ -40,14 +44,12 @@ router.beforeEach(async(to, from, next) => {
// hack method to ensure that addRoutes is complete
// set the replace: true, so the navigation will not leave a history record
isFirstEnter = false;
next({ ...to, replace: true })
} catch (error) {
// remove token and go to login page to re-login
await store.dispatch('user/resetToken')
Message.error(error || 'Has Error')
next(`/login?redirect=${to.path}`)
isFirstEnter = true;
NProgress.done()
}
}
@ -61,7 +63,6 @@ router.beforeEach(async(to, from, next) => {
} else {
// other pages that do not have permission to access are redirected to the login page.
next(`/login?redirect=${to.path}`)
isFirstEnter = true;
NProgress.done()
}
}

@ -54,7 +54,7 @@ export const constantRoutes = [
component: () => import('@/views/dashboard/index'),
meta: { title: '首页', icon: 'dashboard' }
}]
},
}
// 404 page must be placed at the end !!!
//{ path: '*', redirect: '/404', hidden: true }

@ -8,5 +8,6 @@ const getters = {
admins: state => state.data.admins,
departments: state => state.data.departments,
permission_routes: state => state.permission.routes,
module_uri: state => state.modules.moduleUri
}
export default getters

@ -6,6 +6,7 @@ import settings from './modules/settings'
import user from './modules/user'
import data from './modules/data'
import permission from './modules/permission'
import modules from './modules/modules'
import { init } from "./init"
Vue.use(Vuex)
@ -15,7 +16,8 @@ const store = new Vuex.Store({
settings,
user,
data,
permission
permission,
modules
},
getters
})

@ -1,4 +1,4 @@
export function init (store) {
store.dispatch('data/getAdmins')
store.dispatch('data/getDepartments')
//store.dispatch('data/getUsers')
//store.dispatch('data/getDepartments')
}

@ -1,30 +1,29 @@
import { departmentListNoAuth, adminListNoAuth } from "@/api/common"
const state = {
admins: [],
departments: []
users: [],
departments: [],
}
const mutations = {
SET_ADMINS: (state, admins) => {
state.admins = admins
SET_USERS: (state, users) => {
state.users = users
},
SET_DEPARTMENTS: (state, departments) => {
state.departments = departments
}
},
}
const actions = {
getAdmins ({ commit }) {
getUsers ({ commit }) {
adminListNoAuth().then(res => {
commit("SET_ADMINS", res.data)
commit("SET_USERS", res.data)
})
},
getDepartments ({ commit }) {
departmentListNoAuth().then(res => {
commit("SET_DEPARTMENTS", res.data)
})
}
},
}
export default {

@ -0,0 +1,21 @@
const state = {
moduleUri: ""
}
const mutations = {
SET_MODULE_URI: (state, uri) => {
state.moduleUri = uri
},
}
const actions = {
}
export default {
namespaced: true,
state,
mutations,
actions
}

@ -2,10 +2,23 @@ import { asyncRoutes, constantRoutes } from '@/router'
import { permissions } from "@/api/me"
import path from "path"
import Layout from "@/layout"
import Nested from "@/layout/nested.vue"
import Wujie from "@/views/wujie"
const loadView = (view) => {
return (resolve) => require([`@/views${view}`], resolve);
}
const componentHandle = (path, route)=> {
if (/^#+/.test(path)) {
return Layout
} else if (/^#+/.test(path) && route.pid !== 0) {
return Nested
} else if (/^\/./.test(path)) {
return loadView(path)
} else {
return Wujie
}
}
export function filterAsyncRoutes(routes) {
const res = []
@ -15,26 +28,25 @@ export function filterAsyncRoutes(routes) {
let tmp= {
key: `key-${route.id}`,
path: route.path === '#' ? '' : route.path,
component: route.path === "#" ? Layout : loadView(route.path),
component: componentHandle(route.path, route),
name: route.name,
hidden: !route.visible,
meta: {
title: route.title,
icon: route.icon,
guard: route.guard_name,
folder: route.folder
folder: route.folder,
isModule: !/^\/./.test(route.path),
moduleUri: /^\/./.test(route.path) ? '' : `${process.env.VUE_APP_BASE_API}/${route.path}`,
}
}
if (route.children) {
if (route.children && route.children instanceof Array && route.children.length > 0) {
tmp.children = filterAsyncRoutes(route.children)
}
res.push(tmp)
})
//把404拦截放在最后匹配
res.push({ path: '*', redirect: '/404', hidden: true })
return res
}
@ -57,6 +69,9 @@ const actions = {
const routes = await permissions()
let accessedRoutes
accessedRoutes = filterAsyncRoutes(routes)
//把404拦截放在最后匹配
accessedRoutes.push({ path: '*', redirect: '/404', hidden: true })
console.log(333,accessedRoutes)
commit('SET_ROUTES', accessedRoutes)
resolve(accessedRoutes)
} catch (err) {

@ -46,9 +46,9 @@ const actions = {
return new Promise((resolve, reject) => {
login({ username: username.trim(), password: password }).then(response => {
console.log(response)
const { access_token } = response
const { access_token, expires_in } = response
commit('SET_TOKEN', access_token)
setToken(access_token)
setToken(access_token, expires_in / 60 / 24)
resolve()
}).catch(error => {
reject(error)

File diff suppressed because it is too large Load Diff

@ -12,6 +12,6 @@
}
}
.el-menu--collapse .el-menu .el-submenu, .el-menu--popup {
min-width: 120px;
text-align: center;
min-width: 100px !important;
}

@ -6,8 +6,10 @@ export function getToken() {
return Cookies.get(TokenKey)
}
export function setToken(token) {
return Cookies.set(TokenKey, token)
export function setToken(token, expires=365) {
return Cookies.set(TokenKey, token, {
expires
})
}
export function removeToken() {

@ -0,0 +1,113 @@
<template>
<div>
<vxe-modal :value="isShow"
show-footer
title="部门"
show-confirm-button
:width="600"
:height="400"
esc-closable
@input="e => $emit('update:isShow',e)">
<el-form ref="elForm" :model="form" :rules="rules" label-position="top" label-width="100">
<el-form-item label="部门名称" prop="name" required>
<el-input v-model="form.name" clearable></el-input>
</el-form-item>
<el-form-item label="父级部门" prop="pid">
<Treeselect v-model="form.pid"
:options="formatList"
noChildrenText="无子部门"
:normalizer="node => ({
id: node.id,
label: node.name,
children: node.children,
isDefaultExpanded: true
})"></Treeselect>
</el-form-item>
<el-form-item label="分管领导" prop="manager_id">
<user-picker width="100%" v-model="form.manager_id"></user-picker>
</el-form-item>
<el-form-item label="主要领导" prop="leader_id">
<user-picker width="100%" v-model="form.leader_id"></user-picker>
</el-form-item>
<el-form-item label="排序" prop="sortnumber">
<el-input-number controls-position="right" :precision="0" v-model="form.sortnumber"></el-input-number>
</el-form-item>
</el-form>
<template #footer>
<el-button type="primary" :loading="loading" @click="submit"></el-button>
</template>
</vxe-modal>
</div>
</template>
<script>
import UserPicker from '@/components/UserPicker/index.vue'
import { save } from "@/api/department"
export default {
components: {
UserPicker
},
props: {
list: {
type: Array,
default: () => []
},
isShow: {
type: Boolean,
default: false,
required: true
}
},
data() {
return {
loading: false,
form: {
name: "",
pid: 0,
manager_id: "",
leader_id: "",
sortnumber: 0
},
rules: {
name: [
{ required: true, message: "请输入模块名称" }
]
}
}
},
methods: {
submit () {
this.$refs["elForm"].validate(async valid => {
if (valid) {
this.loading = true
try {
await save(this.form)
this.$message.success("新增成功")
this.$emit('refresh')
this.$emit('update:isShow',false)
this.loading = false
this.$refs["elForm"].resetFields()
} catch (err) {
this.loading = false
}
}
})
}
},
computed: {
formatList() {
return [
{
name: "#根部门",
id: 0
},
...this.list
]
}
}
}
</script>
<style scoped lang="scss">
</style>

@ -79,15 +79,6 @@ export default {
],
path: [
{ required: true, message: "请输入路由路径" },
{
validator: (rule, value, callback) => {
if (/^\/.|^#+/.test(value)) {
callback()
} else {
callback(new Error("请以'/'开头或为'#'"))
}
}
}
],
api_profix: [
{ required: true, message: "请输入api前缀" }

@ -0,0 +1,82 @@
<template>
<div>
<vxe-modal :value="isShow"
show-footer
title="模块"
show-confirm-button
:width="600"
:height="400"
esc-closable
@input="e => $emit('update:isShow',e)">
<el-form ref="elForm" :model="form" :rules="rules" label-position="top" label-width="100">
<el-form-item label="名称" prop="name" required>
<el-input v-model="form.title" clearable></el-input>
</el-form-item>
<el-form-item label="模块" prop="name" required>
<el-input v-model="form.name" clearable></el-input>
</el-form-item>
<el-form-item label="排序" prop="sortnumber">
<el-input-number controls-position="right" :precision="0" v-model="form.sortnumber"></el-input-number>
</el-form-item>
</el-form>
<template #footer>
<el-button type="primary" :loading="loading" @click="submit"></el-button>
</template>
</vxe-modal>
</div>
</template>
<script>
import { save } from "@/api/module"
export default {
props: {
isShow: {
type: Boolean,
default: false,
required: true
}
},
data() {
return {
loading: false,
form: {
title: "",
name: "",
sortnumber: 0
},
rules: {
name: [
{ required: true, message: "请输入模块" }
],
title: [
{ required: true, message: "请输入名称" }
]
}
}
},
methods: {
submit () {
this.$refs["elForm"].validate(async valid => {
if (valid) {
this.loading = true
try {
await save(this.form)
this.$message.success("新增成功")
this.$emit('refresh')
this.$emit('update:isShow',false)
this.loading = false
this.$refs["elForm"].resetFields()
} catch (err) {
this.loading = false
}
}
})
}
},
computed: {}
}
</script>
<style scoped lang="scss">
</style>

@ -0,0 +1,185 @@
<template>
<div>
<card-container>
<vxe-toolbar>
<template #buttons>
<el-button icon="el-icon-plus" type="primary" size="small" @click="isShowAdd = true">新增</el-button>
<el-button icon="el-icon-search" type="primary" plain size="small" @click="getList"></el-button>
</template>
</vxe-toolbar>
<vxe-table
stripe
style="margin-top: 10px;"
ref="table"
:loading="loading"
keep-source
show-overflow
:column-config="{ resizable: true }"
:edit-rules="validRules"
:edit-config="{ trigger: 'manual', mode: 'row', showStatus: true, isHover: true, autoClear: false }"
:align="allAlign"
:data="tableData">
<vxe-column type="seq" width="58" align="center"></vxe-column>
<vxe-column field="name" width="160" title="部门名称" :edit-render="{ name: 'input', attrs: { type: 'text'} }"></vxe-column>
<vxe-column field="manager_id" width="180" title="分管领导" :edit-render="{}">
<template #edit="{ row }">
<UserPicker size="small" v-model="row.manager_id"></UserPicker>
</template>
</vxe-column>
<vxe-column field="leader_id" width="180" title="主要领导" :edit-render="{}">
<template #edit="{ row }">
<UserPicker size="small" v-model="row.leader_id"></UserPicker>
</template>
</vxe-column>
<vxe-column field="sortnumber" width="80" title="排序" align="center" :edit-render="{ name: 'input', attrs: { type: 'number' } }"></vxe-column>
<vxe-column field="operate" title="操作" min-width="220">
<template #default="{ row }">
<template v-if="isActiveStatus(row)">
<el-button size="small" type="primary" @click="saveRowEvent(row)"></el-button>
<el-button size="small" type="primary" plain @click="cancelRowEvent(row)"></el-button>
</template>
<template v-else>
<el-button size="small" type="warning" @click="editRowEvent(row)"></el-button>
<el-button size="small" type="danger" @click="destroyRowEvent(row)"></el-button>
</template>
</template>
</vxe-column>
</vxe-table>
<p class="total" type="primary"> {{ total }} 条数据</p>
</card-container>
<AddDepartment ref="AddDepartment" :list="tableData" :is-show.sync="isShowAdd" @refresh="getList"></AddDepartment>
</div>
</template>
<script>
import UserPicker from '@/components/UserPicker/index.vue'
import AddDepartment from './components/AddDepartment.vue'
import { deepCopy } from "@/utils"
import { index, save, destroy } from "@/api/department"
export default {
components: {
AddDepartment,
UserPicker
},
data() {
return {
isShowAdd: false,
loading: false,
total: 0,
allAlign: null,
tableData: [],
validRules: {
name: [
{ required: true, message: "请输入模块名称" }
]
},
form: {
id: "",
name: "",
manager_id: "",
leader_id: "",
sortnumber: 0
}
}
},
methods: {
editRowEvent (row) {
if (this.$refs['table']) {
this.$refs['table'].setEditRow(row)
}
},
cancelRowEvent (row) {
if (this.$refs['table']) {
this.$refs['table'].clearEdit().then(() => {
//
this.$refs['table'].revertData(row)
})
}
},
async getList () {
this.loading = true;
try {
const res = await index()
this.tableData = res;
this.total = res.length;
this.loading = false;
} catch (err) {
console.error(err)
this.loading = false;
}
},
async saveRowEvent (row) {
try {
await this.$confirm("确认保存?","提示",{
confirmButtonText: "确认",
cancelButtonText: "取消"
})
await this.$refs['table'].clearEdit()
let form = deepCopy(this.form)
for (let key in form) {
form[key] = row[key]
}
if (!form.password) {
delete form.password
}
this.loading = true;
await save(form)
await this.getList();
this.loading = false;
} catch (err) {
this.loading = false;
}
},
async destroyRowEvent (row) {
try {
await this.$confirm("确认删除?","提示",{
confirmButtonText: "确认",
cancelButtonText: "取消"
})
this.loading = true;
if (row.id) {
await destroy({
id: row.id
})
await this.getList();
} else {
console.log(row)
this.tableData.splice(this.tableData.findIndex(i => i._X_ROW_KEY === row._X_ROW_KEY),1)
}
this.loading = false;
} catch (err) {
this.loading = false;
}
},
},
computed: {
isActiveStatus () {
return function (row) {
if (this.$refs['table']) {
return this.$refs['table'].isEditByRow(row)
}
}
}
},
created() {
this.getList()
}
}
</script>
<style scoped lang="scss">
.total {
color: #666;
text-align: right;
line-height: 3;
}
::v-deep .el-tag + .el-tag {
margin-left: 4px;
}
</style>

@ -15,7 +15,7 @@
:row-config="{ useKey: 'id', isHover: true }"
:column-config="{ resizable: true }"
:edit-rules="validRules"
:edit-config="{ trigger: 'manual', mode: 'row', showStatus: true }"
:edit-config="{ trigger: 'manual', mode: 'row', showStatus: true, autoClear: false }"
:tree-config="{ rowField: 'id', parentField: 'pid' }"
:data="tableData">
<vxe-column type="seq" width="58" align="center"></vxe-column>
@ -32,11 +32,11 @@
</vxe-column>
<vxe-column field="api_profix" min-width="140" title="api前缀" :edit-render="{ name: 'input', attrs: { type: 'text'} }">
<template #default="{ row }">
<template v-if="/#/g.test(row.path)">
<template v-if="/#/g.test(row.api_profix)">
<SvgIcon icon-class="folder" class-name="icon-folder"></SvgIcon>
</template>
<template v-else>
<span>{{ row.path }}</span>
<span>{{ row.api_profix }}</span>
</template>
</template>
</vxe-column>
@ -87,7 +87,15 @@ export default {
allAlign: null,
tableData: [],
validRules: {
name: [
{ required: true, message: "请输入名称" }
],
path: [
{ required: true, message: "请输入路由路径" },
],
api_profix: [
{ required: true, message: "请输入api前缀" }
]
},
form: {
id: "",
@ -122,6 +130,7 @@ export default {
try {
const res = await index()
this.tableData = res;
console.log(this.tableData)
this.loading = false;
} catch (err) {
console.error(err)
@ -135,10 +144,12 @@ export default {
confirmButtonText: "确认",
cancelButtonText: "取消"
})
await this.$refs['table'].clearEdit()
let form = deepCopy(this.form)
for (let key in form) {
form[key] = row[key]
}
console.log(form)
this.loading = true;
await save(form)
await this.getList();

@ -0,0 +1,176 @@
<template>
<div>
<card-container>
<vxe-toolbar>
<template #buttons>
<el-button icon="el-icon-plus" type="primary" size="small" @click="isShowAdd = true">新增</el-button>
<el-button icon="el-icon-search" type="primary" plain size="small" @click="getList"></el-button>
</template>
</vxe-toolbar>
<vxe-table
stripe
style="margin-top: 10px;"
ref="table"
:loading="loading"
keep-source
show-overflow
:column-config="{ resizable: true }"
:edit-rules="validRules"
:edit-config="{ trigger: 'manual', mode: 'row', showStatus: true, isHover: true, autoClear: false }"
:align="allAlign"
:data="tableData">
<vxe-column type="seq" width="58" align="center"></vxe-column>
<vxe-column field="title" width="160" title="名称" :edit-render="{ name: 'input', attrs: { type: 'text'} }"></vxe-column>
<vxe-column field="name" width="160" title="模块" :edit-render="{ name: 'input', attrs: { type: 'text'} }"></vxe-column>
<vxe-column field="sortnumber" width="80" title="排序" align="center" :edit-render="{ name: 'input', attrs: { type: 'number' } }"></vxe-column>
<vxe-column field="operate" title="操作" min-width="220">
<template #default="{ row }">
<template v-if="isActiveStatus(row)">
<el-button size="small" type="primary" @click="saveRowEvent(row)"></el-button>
<el-button size="small" type="primary" plain @click="cancelRowEvent(row)"></el-button>
</template>
<template v-else>
<el-button size="small" type="warning" @click="editRowEvent(row)"></el-button>
<el-button size="small" type="danger" @click="destroyRowEvent(row)"></el-button>
</template>
</template>
</vxe-column>
</vxe-table>
<p class="total" type="primary"> {{ total }} 条数据</p>
</card-container>
<AddModule ref="AddModule" :is-show.sync="isShowAdd" @refresh="getList"></AddModule>
</div>
</template>
<script>
import AddModule from './components/AddModule.vue'
import { deepCopy } from "@/utils"
import { index, save, destroy } from "@/api/module"
export default {
components: {
AddModule
},
data() {
return {
isShowAdd: false,
loading: false,
total: 0,
allAlign: null,
tableData: [],
validRules: {
name: [
{ required: true, message: "请输入模块" }
],
title: [
{ required: true, message: "请输入名称" }
]
},
form: {
id: "",
title: "",
name: "",
sortnumber: 0
}
}
},
methods: {
editRowEvent (row) {
if (this.$refs['table']) {
this.$refs['table'].setEditRow(row)
}
},
cancelRowEvent (row) {
if (this.$refs['table']) {
this.$refs['table'].clearEdit().then(() => {
//
this.$refs['table'].revertData(row)
})
}
},
async getList () {
this.loading = true;
try {
const res = await index()
this.tableData = res.rows;
this.total = res.total;
this.loading = false;
} catch (err) {
console.error(err)
this.loading = false;
}
},
async saveRowEvent (row) {
try {
await this.$confirm("确认保存?","提示",{
confirmButtonText: "确认",
cancelButtonText: "取消"
})
await this.$refs['table'].clearEdit()
let form = deepCopy(this.form)
for (let key in form) {
form[key] = row[key]
}
if (!form.password) {
delete form.password
}
this.loading = true;
await save(form)
await this.getList();
this.loading = false;
} catch (err) {
this.loading = false;
}
},
async destroyRowEvent (row) {
try {
await this.$confirm("确认删除?","提示",{
confirmButtonText: "确认",
cancelButtonText: "取消"
})
this.loading = true;
if (row.id) {
await destroy({
id: row.id
})
await this.getList();
} else {
console.log(row)
this.tableData.splice(this.tableData.findIndex(i => i._X_ROW_KEY === row._X_ROW_KEY),1)
}
this.loading = false;
} catch (err) {
this.loading = false;
}
},
},
computed: {
isActiveStatus () {
return function (row) {
if (this.$refs['table']) {
return this.$refs['table'].isEditByRow(row)
}
}
}
},
created() {
this.getList()
}
}
</script>
<style scoped lang="scss">
.total {
color: #666;
text-align: right;
line-height: 3;
}
::v-deep .el-tag + .el-tag {
margin-left: 4px;
}
</style>

@ -16,7 +16,7 @@
show-overflow
:column-config="{ resizable: true }"
:edit-rules="validRules"
:edit-config="{ trigger: 'manual', mode: 'row', showStatus: true, isHover: true }"
:edit-config="{ trigger: 'manual', mode: 'row', showStatus: true, isHover: true, autoClear: false }"
:align="allAlign"
:data="tableData">
<vxe-column type="seq" width="58" align="center"></vxe-column>
@ -72,7 +72,9 @@ export default {
allAlign: null,
tableData: [],
validRules: {
name: [
{ required: true, message: "请输入角色" }
]
},
form: {
id: "",
@ -120,6 +122,7 @@ export default {
confirmButtonText: "确认",
cancelButtonText: "取消"
})
await this.$refs['table'].clearEdit()
let form = deepCopy(this.form)
for (let key in form) {
form[key] = row[key]

@ -16,7 +16,7 @@
show-overflow
:column-config="{ resizable: true }"
:edit-rules="validRules"
:edit-config="{ trigger: 'manual', mode: 'row', showStatus: true, isHover: true }"
:edit-config="{ trigger: 'manual', mode: 'row', showStatus: true, isHover: true, autoClear: false }"
:align="allAlign"
:data="tableData">
<vxe-column type="seq" width="58" align="center"></vxe-column>
@ -99,7 +99,25 @@ export default {
allAlign: null,
tableData: [],
validRules: {
name: [
{ required: true, message: "请输入姓名" }
],
username: [
{ required: true, message: "请输入用户名" }
],
password: [
{ required: true, message: "请输入密码" },
{
validator: (rule, value, callback) => {
const reg = new RegExp('(?=.*[0-9])(?=.*[a-zA-Z])(?=.*[^a-zA-Z0-9]).{8,20}');
if (reg.test(value)) {
callback()
} else {
callback(new Error("您的密码复杂度太低(密码中必须包含字母、数字、特殊字符)!"))
}
}
}
],
},
form: {
id: "",
@ -150,6 +168,7 @@ export default {
confirmButtonText: "确认",
cancelButtonText: "取消"
})
await this.$refs['table'].clearEdit()
let form = deepCopy(this.form)
for (let key in form) {
form[key] = row[key]

@ -0,0 +1,60 @@
<template>
<Wujie id="wujie-app"
width="100%"
height="100%"
name="modules"
sync
:url="module_uri"
:props="props">
</Wujie>
</template>
<script>
import { getToken } from "@/utils/auth"
import Wujie from 'wujie-vue2'
import { mapGetters } from 'vuex'
export default {
components: {
Wujie
},
data() {
return {
props: {
auth_token: ""
}
}
},
methods: {},
computed: {
...mapGetters(['module_uri'])
},
watch: {
module_uri: {
handler:function(newVal) {
this.props.auth_token = getToken()
},
immediate: true
}
},
created() {
},
}
</script>
<style scoped lang="scss">
#wujie-app {
overflow: hidden;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
</style>
<style>
.app-main:has(#wujie-app) {
padding: 0!important;
}
</style>
Loading…
Cancel
Save