Merge branch 'master' of https://gitee.com/yuanzbz/vue-admin-perfect into vue-i18n

 Conflicts:
	README.md
	src/layout/Header/index.vue
This commit is contained in:
zouzhibing 2022-11-16 19:48:51 +08:00
commit 809e0fa8f4
19 changed files with 217 additions and 163 deletions

View File

@ -2,7 +2,7 @@
vue-element-perfect 是一个后台前端解决方案,它使用了最新的前端技术栈、动态路由,权限验证,并且有着丰富的组件,企业级中后台解决方案可免费商用同时支持PC、平板、手机
### 在线预览
- link —— [http://182.61.5.190:8889/ ](http://182.61.5.190:8889/)
- link —— [http://182.61.5.190:8889/ ](http://182.61.5.190:8889/)
### git仓库(欢迎 Star⭐)
- Gitee —— [https://gitee.com/yuanzbz/vue-admin-perfect](https://gitee.com/yuanzbz/vue-admin-perfect)
@ -10,7 +10,7 @@ vue-element-perfect 是一个后台前端解决方案,它使用了最新的前
## 项目功能
- 使用Vue3.0开发单文件组件采用script setup
- 采用 Vite3 作为项目开发、打包工具(配置了 Gzip 打包、TSX 语法、跨域代理)
- 整个项目集成了 TypeScript
- 整个项目集成了 TypeScript
- 登录逻辑使用vue-router进行路由权限拦截判断路由懒加载
- 使用 keep-alive 对整个页面进行缓存,支持多级嵌套页面
- 侧边栏导航菜单栏动态的显示
@ -98,7 +98,7 @@ vue-admin-perfect
└─ vite.config.ts # vite 配置
```
### 十、微信交流群
### 微信交流群
| 微信二维码 |
| :----------------------------------------------------------------------------------: |
| <img src="http://182.61.5.190:8889/we.png" width=170/> |

14
package-lock.json generated
View File

@ -20,7 +20,7 @@
"default-passive-events": "^2.0.0",
"echarts": "^5.3.1",
"echarts-liquidfill": "^3.1.0",
"element-plus": "^2.2.20",
"element-plus": "^2.2.21",
"exceljs": "^4.3.0",
"file-saver": "^2.0.5",
"fuse.js": "^6.6.2",
@ -2511,9 +2511,9 @@
"license": "MIT"
},
"node_modules/element-plus": {
"version": "2.2.20",
"resolved": "https://r.cnpmjs.org/element-plus/-/element-plus-2.2.20.tgz",
"integrity": "sha512-ludShd3f5kNRY4FLzeoNitLcwZ4qs2M/zwKeyeE7rUzZJAQ0BZtcT3SvZoEoBLmgxw9jHoonl4WIwon4UzhyRA==",
"version": "2.2.21",
"resolved": "https://r.cnpmjs.org/element-plus/-/element-plus-2.2.21.tgz",
"integrity": "sha512-wZUePoXZ1zuCkzENK/8mn+mekuLJ9OoGYiudjUujzCf+T8HfOQl+TKQStwOkGBNk93fK8e9YdFIty4jH4AX6dg==",
"dependencies": {
"@ctrl/tinycolor": "^3.4.1",
"@element-plus/icons-vue": "^2.0.6",
@ -10074,9 +10074,9 @@
"integrity": "sha512-bMX/nPADfY8Mgs70kG+SGJ4vo7GCOsQaFVhUHG738gzRG6OrKCp2ZbV63LE5jsgEh8tDqxvXvLeslel5HF6lrA=="
},
"element-plus": {
"version": "2.2.20",
"resolved": "https://r.cnpmjs.org/element-plus/-/element-plus-2.2.20.tgz",
"integrity": "sha512-ludShd3f5kNRY4FLzeoNitLcwZ4qs2M/zwKeyeE7rUzZJAQ0BZtcT3SvZoEoBLmgxw9jHoonl4WIwon4UzhyRA==",
"version": "2.2.21",
"resolved": "https://r.cnpmjs.org/element-plus/-/element-plus-2.2.21.tgz",
"integrity": "sha512-wZUePoXZ1zuCkzENK/8mn+mekuLJ9OoGYiudjUujzCf+T8HfOQl+TKQStwOkGBNk93fK8e9YdFIty4jH4AX6dg==",
"requires": {
"@ctrl/tinycolor": "^3.4.1",
"@element-plus/icons-vue": "^2.0.6",

View File

@ -27,7 +27,7 @@
"default-passive-events": "^2.0.0",
"echarts": "^5.3.1",
"echarts-liquidfill": "^3.1.0",
"element-plus": "^2.2.20",
"element-plus": "^2.2.21",
"exceljs": "^4.3.0",
"file-saver": "^2.0.5",
"fuse.js": "^6.6.2",

View File

View File

BIN
src/assets/wechat.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 KiB

View File

@ -1,30 +0,0 @@
<template>
<el-breadcrumb class="app-breadcrumb" separator="/">
<transition-group name="breadcrumb" mode="out-in">
<el-breadcrumb-item v-for="(item, index) in matched" :key="item.name">
<span v-if="item.redirect === 'noRedirect' || index == matched.length - 1" class="no-redirect">{{ item.meta.title }}</span>
<a v-else @click.prevent="handleLink(item)">{{ item.meta.title }}</a>
</el-breadcrumb-item>
</transition-group>
</el-breadcrumb>
</template>
<script lang="ts" setup>
import pathToRegexp from 'path-to-regexp'
import { onMounted, ref, watch,computed } from 'vue'
import { useRoute ,useRouter} from 'vue-router'
const levelList = ref([])
const route = useRoute()
const router = useRouter()
const handleLink = (item)=>{
router.push({
path:item.path
})
}
const matched = computed(() => route.matched.filter(item => item.meta && item.meta.title && item.meta.breadcrumb !== false));
</script>

View File

@ -0,0 +1,38 @@
<template>
<el-breadcrumb class="app-breadcrumb" separator="/">
<transition-group name="breadcrumb" mode="out-in">
<!-- 首页面包屑不要可以直接删除 🙅 -->
<el-breadcrumb-item :to="{ path: '/' }" key="home" v-if="matched[0].meta.title !== '首页'">
<div class="breadcrumb-item">
<span class="breadcrumb-title">首页</span>
</div>
</el-breadcrumb-item>
<el-breadcrumb-item v-for="(item, index) in matched" :key="item.name">
<span v-if="item.redirect === 'noRedirect' || index == matched.length - 1" class="no-redirect">{{ item.meta.title }}</span>
<a v-else @click.prevent="handleLink(item)">{{ item.meta.title }}</a>
</el-breadcrumb-item>
</transition-group>
</el-breadcrumb>
</template>
<script lang="ts" setup>
import { ref, computed } from 'vue'
import { useRoute ,useRouter} from 'vue-router'
const route = useRoute()
const router = useRouter()
const handleLink = (item)=>{
router.push({
path:item.path
})
}
const matched = computed(() => route.matched.filter(item => item.meta && item.meta.title && item.meta.breadcrumb !== false));
console.log('======matched=======',matched)
</script>

View File

@ -1,7 +1,7 @@
<template>
<div
class="m-layout-header"
:class="[
class="m-layout-header"
:class="[
SettingStore.themeConfig.fixedHeader?'zb-fixed-header':'zb-no-fixed-header',
mode === 'horizontal'?'':isCollapse?'fixed-header-collapse':'fixed-header-no-collapse'
]"

View File

@ -155,6 +155,7 @@ const removeTab = async (activeTabPath: string) => {
}
.el-tabs__header .el-tabs__item {
border: none;
color: #cccccc;
}
.el-tabs__header .el-tabs__item.is-active {
color: $primaryColor;

View File

@ -6,7 +6,8 @@ export const userData =[
role:'超级管理员',
status:true,
photo:'15333333333',
describe:'超级管理员不可删除'
describe:'超级管理员不可删除',
createTime:'2022-09-02 15:30:20',
},
{
username:'zhangsan',
@ -15,7 +16,8 @@ export const userData =[
role:'管理员',
status:true,
photo:'15311111111',
describe:'管理员不可删除'
describe:'管理员不可删除',
createTime:'2022-09-02 15:30:20',
},
{
username:'lisi',
@ -24,7 +26,8 @@ export const userData =[
role:'管理员',
status:true,
photo:'13823456789',
describe:'测试账户'
describe:'测试账户',
createTime:'2022-09-02 15:30:20',
},
{
username:'wangwu',
@ -33,7 +36,8 @@ export const userData =[
role:'超级管理员',
status:false,
photo:'13923456789',
describe:'超级管理员不可删除'
describe:'超级管理员不可删除',
createTime:'2022-09-02 15:30:20',
},
{
username:'zhaoliu',
@ -42,7 +46,8 @@ export const userData =[
role:'普通用户',
status:false,
photo:'14523456789',
describe:'普通测试用户'
describe:'普通测试用户',
createTime:'2022-09-02 15:30:20',
},
]
@ -54,21 +59,21 @@ export const roleData =[
roleId:'admin',
roleIdentification:'admin',
describe:'这是超级管理员,拥有一切权限',
createTime:'2022-09-02',
createTime:'2022-09-02 15:30:20',
},
{
roleName:'管理员',
roleId:'role',
roleIdentification:'admin',
describe:'普通管理员',
createTime:'2022-09-02',
createTime:'2022-09-02 15:30:20',
},
{
roleName:'普通用户',
roleId:'other',
describe:'测试用户',
roleIdentification:'other',
createTime:'2022-09-02',
createTime:'2022-09-02 15:30:20',
},
]

View File

@ -71,7 +71,7 @@ export const constantRoutes: Array<RouteRecordRaw&extendRoute> = [
path: '/home',
component: () => import('@/views/home/index.vue'),
name: 'home',
meta: { title: '首页', icon: 'film', affix: true ,role:['other']}
meta: { title: '首页', icon: 'House', affix: true ,role:['other']}
},
]
},

View File

@ -5,7 +5,7 @@ import Layout from '@/layout/index.vue'
const othersRouter = [{
path: '/other',
component: Layout,
redirect: '/other/editor',
redirect: '/other/clipboard',
name: 'other',
meta: {
title: '常用组件',

View File

@ -9,7 +9,7 @@ const systemRouter = [{
name: 'system',
meta: {
title: '系统管理',
icon: 'ElementPlus',
icon: 'Setting',
roles:['other']
},
children: [

View File

@ -0,0 +1,96 @@
<template>
<div>
<el-drawer v-model="dialogVisible" :title="title" size="50%">
<el-form
ref="ruleFormRef"
:model="ruleForm"
:rules="rules"
label-width="120px"
>
<el-form-item label="菜单类型" prop="menuType">
<el-radio-group v-model="ruleForm.menuType">
<el-radio-button label="目录" />
<el-radio-button label="菜单" />
<el-radio-button label="按钮" />
</el-radio-group>
</el-form-item>
<el-form-item label="菜单名称" prop="menuName">
<el-input v-model="ruleForm.menuName" placeholder="请输入菜单名称"/>
</el-form-item>
<el-form-item label="父级菜单" prop="role">
<el-cascader
style="width: 100%"
:options="menuData"
:props="cascaderProps" clearable />
</el-form-item>
<el-form-item label="权限标识" prop="identification">
<el-input v-model="ruleForm.identification" placeholder="请输入权限标识"/>
</el-form-item>
<el-form-item label="路由地址" prop="identification">
<el-input v-model="ruleForm.url" placeholder="请输入路由地址"/>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleClose(ruleFormRef)">确定</el-button>
</span>
</template>
</el-drawer>
</div>
</template>
<script lang="ts" setup>
import {reactive, ref} from "vue";
import {FormInstance} from "element-plus";
import { menuData } from '@/mock/system'
const tableData = ref(menuData)
const ruleFormRef = ref<FormInstance>()
const dialogVisible = ref()
const rules = reactive({
roleName: [
{ required: true, message: '请输入角色名称', trigger: 'blur' },
],
roleIdentification: [{ required: true, message: '请输入角色标识', trigger: 'blur' }],
})
const title = ref('新增菜单')
const ruleForm = reactive({
name: '',
roleIdentification:'',
describe:null,
status:true
})
const cascaderProps = {
value: 'menuName',
label: 'menuName',
checkStrictly: true,
}
const show = (item={})=>{
title.value = '新增菜单'
if(item.name){
title.value = '编辑菜单'
Object.keys(item).forEach(key=>{
ruleForm[key] = item[key]
})
}
dialogVisible.value = true
}
const handleClose = async (done: () => void) => {
await ruleFormRef.value.validate((valid, fields) => {
if (valid) {
console.log('submit!')
} else {
console.log('error submit!', fields)
}
})
}
defineExpose({
show
})
</script>
<style>
</style>

View File

@ -1,19 +1,22 @@
<template>
<div class="app-container">
<div class="header">
<el-form :inline="true" :model="formInline" class="demo-form-inline" ref="ruleFormRef1">
<el-form :inline="true" :model="formInline" ref="ruleFormRef">
<el-form-item label="用户名" prop="username">
<el-input v-model="formInline.username" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit" :icon="Search">查询</el-button>
<el-button @click="reset(ruleFormRef1)">重置</el-button>
<el-button @click="reset(ruleFormRef)">重置</el-button>
</el-form-item>
</el-form>
</div>
<div class="footer">
<div class="util">
<el-button type="primary" @click="add">新增</el-button>
<el-button type="primary" @click="add">
<el-icon><Plus /></el-icon>
新增菜单
</el-button>
</div>
<div class="table-wrap">
<el-table :data="tableData" style="width: 100%" border default-expand-all row-key="id" class="table">
@ -34,39 +37,7 @@
</el-table>
</div>
</div>
<el-drawer v-model="dialogVisible" :title="title" width="50%">
<el-form
ref="ruleFormRef"
:model="ruleForm"
:rules="rules"
label-width="120px"
class="demo-ruleForm"
:size="formSize"
>
<el-form-item label="权限类型" prop="menuType">
<el-radio-group v-model="ruleForm.menuType">
<el-radio-button label="目录" />
<el-radio-button label="菜单" />
<el-radio-button label="按钮" />
</el-radio-group>
</el-form-item>
<el-form-item label="权限名称" prop="menuName">
<el-input v-model="ruleForm.menuName" />
</el-form-item>
<el-form-item label="父级菜单" prop="role">
<el-cascader :options="menuData" :props="props1" clearable />
</el-form-item>
<el-form-item label="权限标识" prop="identification">
<el-input v-model="ruleForm.identification" />
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleClose(ruleFormRef)">确定</el-button>
</span>
</template>
</el-drawer>
<MenuDrawer ref="menuDrawerRef"/>
</div>
</template>
@ -75,59 +46,26 @@
import { reactive, ref } from 'vue'
import {Search } from '@element-plus/icons-vue'
import { menuData } from '@/mock/system'
import * as dayjs from 'dayjs'
import MenuDrawer from './components/MenuDrawer.vue'
const tableData = ref(menuData)
const dialogVisible = ref(false)
const title = ref('新增')
const formSize = ref('default')
const menuDrawerRef = ref()
const ruleFormRef = ref<FormInstance>()
const ruleFormRef1 = ref<FormInstance>()
const formInline = reactive({})
const props1 = {
value: 'menuName',
label: 'menuName',
checkStrictly: true,
}
const rules = reactive({
nickname: [
{ required: true, message: '请输入昵称', trigger: 'blur' },
{ min: 3, max: 5, message: '长度在 3 到 5 个字符', trigger: 'blur' },
],
username: [{ required: true, message: '请输入', trigger: 'blur' }],
role: [
{
required: true,
message: '请选择角色',
trigger: 'change',
},
],
sex: [
{
required: true,
message: '请选择性别',
trigger: 'change',
},
],
})
const reset = (formEl: FormInstance | undefined) => {}
const onSubmit = () => {
console.log('submit!', formInline)
}
const reset = (formEl: FormInstance | undefined) => {}
const add = () => {
title.value = '新增'
dialogVisible.value = true
}
const ruleForm = reactive({
name: '',
sex: null,
price: null,
})
const edit = (row) => {
title.value = '编辑'
// rowObj.value = row
dialogVisible.value = true
menuDrawerRef.value.show()
}
const edit = (row) => {
menuDrawerRef.value.show(row)
}
const del = (row) => {
ElMessageBox.confirm('你确定要删除当前项吗?', '温馨提示', {
confirmButtonText: '确定',
@ -138,6 +76,7 @@
.then(() => {})
.catch(() => {})
}
const changeStatus = (row) => {
ElMessageBox.confirm(
`确定要${!row.status ? '禁用' : '启用'} ${row.username} 账户吗?`,
@ -154,16 +93,7 @@
})
}
const handleClose = async (done: () => void) => {
await ruleFormRef.value.validate((valid, fields) => {
if (valid) {
dialogVisible.value = false
console.log('submit!', obj)
} else {
console.log('error submit!', fields)
}
})
}
</script>
<style scoped lang="scss">

View File

@ -22,6 +22,14 @@
<el-table :data="tableData" style="width: 100%" border>
<el-table-column prop="roleName" label="角色名称" />
<el-table-column prop="roleIdentification" label="角色标识" />
<el-table-column prop="status" label="角色状态" align="center">
<template #default="scope">
<el-switch v-model="scope.row.status"
inline-prompt
active-text="启用" inactive-text="禁用"
@change="changeStatus(scope.row)" />
</template>
</el-table-column>
<el-table-column prop="describe"
:show-overflow-tooltip="true"
width="180"
@ -80,20 +88,21 @@
.then(() => {})
.catch(() => {})
}
const changeStatus = (row) => {
ElMessageBox.confirm(
`确定要${!row.status ? '禁用' : '启用'} ${row.username} 账户吗?`,
'温馨提示',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
},
`确定要${!row.status ? '禁用' : '启用'} ${row.roleName} 角色吗?`,
'温馨提示',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
},
)
.then(async () => {})
.catch(() => {
row.status = !row.status
})
.then(async () => {})
.catch(() => {
row.status = !row.status
})
}

View File

@ -25,15 +25,20 @@
<el-table-column prop="sex" label="性别" align="center"/>
<el-table-column prop="role" label="关联角色" align="center" width="120"/>
<el-table-column prop="photo" label="手机号" align="center" width="120"/>
<el-table-column prop="status" label="用户状态" align="center">
<template #default="scope">
<el-switch
inline-prompt
active-text="启用" inactive-text="禁用"
v-model="scope.row.status" @change="changeStatus(scope.row)" />
</template>
</el-table-column>
<el-table-column prop="describe"
:show-overflow-tooltip="true"
width="180"
label="用户描述" align="center"/>
<el-table-column prop="status" label="用户状态" align="center">
<template #default="scope">
<el-switch v-model="scope.row.status" @change="changeStatus(scope.row)" />
</template>
</el-table-column>
<el-table-column prop="createTime" label="创建时间" align="center" width="180"/>
<el-table-column prop="operator" label="操作" width="200px" align="center">
<template #default="scope">
<el-button type="primary" size="small" icon="Edit" @click="editHandler(scope.row)">

View File

@ -1405,10 +1405,10 @@
"resolved" "https://registry.npmmirror.com/element-in-view/-/element-in-view-0.1.0.tgz"
"version" "0.1.0"
"element-plus@^2.2.20":
"integrity" "sha512-ludShd3f5kNRY4FLzeoNitLcwZ4qs2M/zwKeyeE7rUzZJAQ0BZtcT3SvZoEoBLmgxw9jHoonl4WIwon4UzhyRA=="
"resolved" "https://r.cnpmjs.org/element-plus/-/element-plus-2.2.20.tgz"
"version" "2.2.20"
"element-plus@^2.2.21":
"integrity" "sha512-wZUePoXZ1zuCkzENK/8mn+mekuLJ9OoGYiudjUujzCf+T8HfOQl+TKQStwOkGBNk93fK8e9YdFIty4jH4AX6dg=="
"resolved" "https://r.cnpmjs.org/element-plus/-/element-plus-2.2.21.tgz"
"version" "2.2.21"
dependencies:
"@ctrl/tinycolor" "^3.4.1"
"@element-plus/icons-vue" "^2.0.6"