parent
daaf393872
commit
b9a4402431
82
README.md
82
README.md
|
@ -1,19 +1,10 @@
|
|||
# vue-manage-system
|
||||
|
||||
<a href="https://github.com/vuejs/vue">
|
||||
<img src="https://img.shields.io/badge/vue-3.1.2-brightgreen.svg" alt="vue">
|
||||
</a>
|
||||
<a href="https://github.com/vuejs/pinia">
|
||||
<img src="https://img.shields.io/badge/pinia-2.0.14-brightgreen.svg" alt="pinia">
|
||||
</a>
|
||||
<a href="https://github.com/lin-xin/vue-manage-system/blob/master/LICENSE">
|
||||
<img src="https://img.shields.io/github/license/mashape/apistatus.svg" alt="license">
|
||||
</a>
|
||||
<a href="https://github.com/lin-xin/vue-manage-system/releases">
|
||||
<img src="https://img.shields.io/github/release/lin-xin/vue-manage-system.svg" alt="GitHub release">
|
||||
</a>
|
||||
<a href="https://lin-xin.gitee.io/example/work/#/donate">
|
||||
<img src="https://img.shields.io/badge/%24-donate-ff69b4.svg" alt="donate">
|
||||
<a href="https://github.com/lin-xin/vue-manage-system/blob/master/LICENSE">
|
||||
<img src="https://img.shields.io/github/license/mashape/apistatus.svg" alt="license">
|
||||
</a>
|
||||
|
||||
基于 Vue3 + pinia + Element Plus 的后台管理系统解决方案。[线上地址](https://lin-xin.gitee.io/example/work/)
|
||||
|
@ -46,21 +37,20 @@
|
|||
- [x] vite 3
|
||||
- [x] pinia
|
||||
- [x] typescript
|
||||
- [x] 登录/注销
|
||||
- [x] 登录/注册
|
||||
- [x] Dashboard
|
||||
- [x] 表格
|
||||
- [x] Tab 选项卡
|
||||
- [x] 表单
|
||||
- [x] 表格/表单
|
||||
- [x] 图表 :bar_chart:
|
||||
- [x] 富文本/markdown编辑器
|
||||
- [x] 富文本/markdown 编辑器
|
||||
- [x] 图片拖拽/裁剪上传
|
||||
- [x] 权限管理
|
||||
- [x] 三级菜单
|
||||
- [x] 自定义图标
|
||||
|
||||
- [x] 主题切换
|
||||
|
||||
## 安装步骤
|
||||
> 因为使用vite3,node版本需要 14.18+
|
||||
|
||||
> 因为使用 vite3,node 版本需要 14.18+
|
||||
|
||||
```
|
||||
git clone https://github.com/lin-xin/vue-manage-system.git // 把模板下载到本地
|
||||
|
@ -74,64 +64,16 @@ npm run dev
|
|||
npm run build
|
||||
```
|
||||
|
||||
## 组件使用说明与演示
|
||||
|
||||
### vue-schart
|
||||
|
||||
vue.js 封装 sChart.js 的图表组件。访问地址:[vue-schart](https://github.com/lin-xin/vue-schart#/)
|
||||
|
||||
<p><a href="https://www.npmjs.com/package/vue-schart"><img src="https://img.shields.io/npm/dm/vue-schart.svg" alt="Downloads"></a></p>
|
||||
|
||||
```html
|
||||
<template>
|
||||
<div>
|
||||
<schart class="wrapper" canvasId="myCanvas" :options="options"></schart>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import Schart from "vue-schart"; // 导入Schart组件
|
||||
const options = ref({
|
||||
type: "bar",
|
||||
title: {
|
||||
text: "最近一周各品类销售图",
|
||||
},
|
||||
labels: ["周一", "周二", "周三", "周四", "周五"],
|
||||
datasets: [
|
||||
{
|
||||
label: "家电",
|
||||
data: [234, 278, 270, 190, 230],
|
||||
},
|
||||
{
|
||||
label: "百货",
|
||||
data: [164, 178, 190, 135, 160],
|
||||
},
|
||||
{
|
||||
label: "食品",
|
||||
data: [144, 198, 150, 235, 120],
|
||||
},
|
||||
],
|
||||
})
|
||||
</script>
|
||||
<style>
|
||||
.wrapper {
|
||||
width: 7rem;
|
||||
height: 5rem;
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
## 项目截图
|
||||
|
||||
### 登录
|
||||
|
||||

|
||||
|
||||
### 首页
|
||||
|
||||

|
||||
|
||||
### 登录
|
||||
|
||||

|
||||
|
||||
## License
|
||||
|
||||
[MIT](https://github.com/lin-xin/vue-manage-system/blob/master/LICENSE)
|
||||
|
|
|
@ -7,17 +7,24 @@ export {}
|
|||
|
||||
declare module '@vue/runtime-core' {
|
||||
export interface GlobalComponents {
|
||||
Countup: typeof import('./src/components/countup.vue')['default']
|
||||
ElAvatar: typeof import('element-plus/es')['ElAvatar']
|
||||
ElButton: typeof import('element-plus/es')['ElButton']
|
||||
ElCalendar: typeof import('element-plus/es')['ElCalendar']
|
||||
ElCard: typeof import('element-plus/es')['ElCard']
|
||||
ElCarousel: typeof import('element-plus/es')['ElCarousel']
|
||||
ElCarouselItem: typeof import('element-plus/es')['ElCarouselItem']
|
||||
ElCascader: typeof import('element-plus/es')['ElCascader']
|
||||
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
|
||||
ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']
|
||||
ElCol: typeof import('element-plus/es')['ElCol']
|
||||
ElColorPicker: typeof import('element-plus/es')['ElColorPicker']
|
||||
ElCountdown: typeof import('element-plus/es')['ElCountdown']
|
||||
ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
|
||||
ElDescriptions: typeof import('element-plus/es')['ElDescriptions']
|
||||
ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem']
|
||||
ElDialog: typeof import('element-plus/es')['ElDialog']
|
||||
ElDivider: typeof import('element-plus/es')['ElDivider']
|
||||
ElDropdown: typeof import('element-plus/es')['ElDropdown']
|
||||
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
|
||||
ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
|
||||
|
@ -26,6 +33,7 @@ declare module '@vue/runtime-core' {
|
|||
ElIcon: typeof import('element-plus/es')['ElIcon']
|
||||
ElImage: typeof import('element-plus/es')['ElImage']
|
||||
ElInput: typeof import('element-plus/es')['ElInput']
|
||||
ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
|
||||
ElLink: typeof import('element-plus/es')['ElLink']
|
||||
ElMenu: typeof import('element-plus/es')['ElMenu']
|
||||
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
|
||||
|
@ -33,9 +41,17 @@ declare module '@vue/runtime-core' {
|
|||
ElPagination: typeof import('element-plus/es')['ElPagination']
|
||||
ElProgress: typeof import('element-plus/es')['ElProgress']
|
||||
ElRadio: typeof import('element-plus/es')['ElRadio']
|
||||
ElRadioButton: typeof import('element-plus/es')['ElRadioButton']
|
||||
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
|
||||
ElRate: typeof import('element-plus/es')['ElRate']
|
||||
ElResult: typeof import('element-plus/es')['ElResult']
|
||||
ElRow: typeof import('element-plus/es')['ElRow']
|
||||
ElSelect: typeof import('element-plus/es')['ElSelect']
|
||||
ElSlider: typeof import('element-plus/es')['ElSlider']
|
||||
ElSpace: typeof import('element-plus/es')['ElSpace']
|
||||
ElStatistic: typeof import('element-plus/es')['ElStatistic']
|
||||
ElStep: typeof import('element-plus/es')['ElStep']
|
||||
ElSteps: typeof import('element-plus/es')['ElSteps']
|
||||
ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
|
||||
ElSwitch: typeof import('element-plus/es')['ElSwitch']
|
||||
ElTable: typeof import('element-plus/es')['ElTable']
|
||||
|
@ -43,15 +59,23 @@ declare module '@vue/runtime-core' {
|
|||
ElTabPane: typeof import('element-plus/es')['ElTabPane']
|
||||
ElTabs: typeof import('element-plus/es')['ElTabs']
|
||||
ElTag: typeof import('element-plus/es')['ElTag']
|
||||
ElTimeline: typeof import('element-plus/es')['ElTimeline']
|
||||
ElTimelineItem: typeof import('element-plus/es')['ElTimelineItem']
|
||||
ElTimePicker: typeof import('element-plus/es')['ElTimePicker']
|
||||
ElTooltip: typeof import('element-plus/es')['ElTooltip']
|
||||
ElTour: typeof import('element-plus/es')['ElTour']
|
||||
ElTourStep: typeof import('element-plus/es')['ElTourStep']
|
||||
ElTransfer: typeof import('element-plus/es')['ElTransfer']
|
||||
ElUpload: typeof import('element-plus/es')['ElUpload']
|
||||
ElWatermark: typeof import('element-plus/es')['ElWatermark']
|
||||
Header: typeof import('./src/components/header.vue')['default']
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
RouterView: typeof import('vue-router')['RouterView']
|
||||
Sidebar: typeof import('./src/components/sidebar.vue')['default']
|
||||
TableCustom: typeof import('./src/components/table-custom.vue')['default']
|
||||
TableDetail: typeof import('./src/components/table-detail.vue')['default']
|
||||
TableEdit: typeof import('./src/components/table-edit.vue')['default']
|
||||
Tags: typeof import('./src/components/tags.vue')['default']
|
||||
TableSearch: typeof import('./src/components/table-search.vue')['default']
|
||||
Tabs: typeof import('./src/components/tabs.vue')['default']
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,13 +6,13 @@
|
|||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<title>vue-manage-system后台管理系统</title>
|
||||
<link rel="stylesheet" href="https://at.alicdn.com/t/font_830376_qzecyukz0s.css">
|
||||
<link rel="stylesheet" href="//at.alicdn.com/t/c/font_830376_92o68tc95je.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled.
|
||||
Please enable it to continue.</strong>
|
||||
Please enable it to continue.</strong>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
|
|
File diff suppressed because it is too large
Load Diff
12
package.json
12
package.json
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "vue-manage-system",
|
||||
"version": "5.3.5",
|
||||
"version": "5.5.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
@ -12,12 +12,16 @@
|
|||
"@wangeditor/editor": "^5.1.23",
|
||||
"@wangeditor/editor-for-vue": "^5.1.12",
|
||||
"axios": "^1.6.3",
|
||||
"element-plus": "^2.4.4",
|
||||
"countup.js": "^2.8.0",
|
||||
"echarts": "^5.5.0",
|
||||
"echarts-wordcloud": "^2.1.0",
|
||||
"element-plus": "^2.6.3",
|
||||
"md-editor-v3": "^2.11.2",
|
||||
"nprogress": "^0.2.0",
|
||||
"pinia": "^2.1.7",
|
||||
"vue": "^3.3.0",
|
||||
"vue-cropperjs": "^5.0.0",
|
||||
"vue": "^3.4.5",
|
||||
"vue-cropper": "1.1.1",
|
||||
"vue-echarts": "^6.6.9",
|
||||
"vue-router": "^4.2.5",
|
||||
"vue-schart": "^2.0.0",
|
||||
"xlsx": "^0.18.5"
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
{
|
||||
"list": [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "管理员",
|
||||
"key": "admin",
|
||||
"status": true,
|
||||
"permiss": [
|
||||
"0",
|
||||
"1",
|
||||
"11",
|
||||
"12",
|
||||
"13",
|
||||
"2",
|
||||
"21",
|
||||
"22",
|
||||
"23",
|
||||
"24",
|
||||
"3",
|
||||
"31",
|
||||
"32",
|
||||
"33",
|
||||
"331",
|
||||
"332",
|
||||
"4",
|
||||
"41",
|
||||
"42",
|
||||
"5"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"name": "普通用户",
|
||||
"key": "user",
|
||||
"status": true,
|
||||
"permiss": [
|
||||
"0",
|
||||
"1",
|
||||
"11",
|
||||
"12",
|
||||
"13"
|
||||
]
|
||||
}
|
||||
],
|
||||
"pageTotal": 2
|
||||
}
|
|
@ -1,10 +1,11 @@
|
|||
{
|
||||
"list": [{
|
||||
"list": [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "张三",
|
||||
"money": 123,
|
||||
"address": "广东省东莞市长安镇",
|
||||
"state": "成功",
|
||||
"state": true,
|
||||
"date": "2019-11-1",
|
||||
"thumb": "https://lin-xin.gitee.io/images/post/wms.png"
|
||||
},
|
||||
|
@ -13,7 +14,7 @@
|
|||
"name": "李四",
|
||||
"money": 456,
|
||||
"address": "广东省广州市白云区",
|
||||
"state": "成功",
|
||||
"state": true,
|
||||
"date": "2019-10-11",
|
||||
"thumb": "https://lin-xin.gitee.io/images/post/node3.png"
|
||||
},
|
||||
|
@ -22,7 +23,7 @@
|
|||
"name": "王五",
|
||||
"money": 789,
|
||||
"address": "湖南省长沙市",
|
||||
"state": "失败",
|
||||
"state": false,
|
||||
"date": "2019-11-11",
|
||||
"thumb": "https://lin-xin.gitee.io/images/post/parcel.png"
|
||||
},
|
||||
|
@ -31,7 +32,7 @@
|
|||
"name": "赵六",
|
||||
"money": 1011,
|
||||
"address": "福建省厦门市鼓浪屿",
|
||||
"state": "成功",
|
||||
"state": true,
|
||||
"date": "2019-10-20",
|
||||
"thumb": "https://lin-xin.gitee.io/images/post/notice.png"
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"list": [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "张三",
|
||||
"password": "123",
|
||||
"email": "123@qq.com",
|
||||
"phone": "12345678944",
|
||||
"date": "2024-01-01",
|
||||
"role": "管理员"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"name": "李四",
|
||||
"password": "123",
|
||||
"email": "1234@qq.com",
|
||||
"phone": "12345678945",
|
||||
"date": "2024-01-01",
|
||||
"role": "普通用户"
|
||||
}
|
||||
],
|
||||
"pageTotal": 2
|
||||
}
|
Binary file not shown.
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 121 KiB |
Binary file not shown.
Before Width: | Height: | Size: 112 KiB After Width: | Height: | Size: 425 KiB |
|
@ -7,8 +7,11 @@
|
|||
<script setup lang="ts">
|
||||
import { ElConfigProvider } from 'element-plus';
|
||||
import zhCn from 'element-plus/es/locale/lang/zh-cn';
|
||||
import { useThemeStore } from './store/theme';
|
||||
|
||||
const theme = useThemeStore();
|
||||
theme.initTheme();
|
||||
</script>
|
||||
<style>
|
||||
@import './assets/css/main.css';
|
||||
@import './assets/css/color-dark.scss';
|
||||
</style>
|
||||
|
|
|
@ -2,7 +2,21 @@ import request from '../utils/request';
|
|||
|
||||
export const fetchData = () => {
|
||||
return request({
|
||||
url: 'https://www.fastmock.site/mock/dc695d037038802def4b989ba4650c3f/vms/getUser',
|
||||
method: 'post'
|
||||
url: './mock/table.json',
|
||||
method: 'get'
|
||||
});
|
||||
};
|
||||
|
||||
export const fetchUserData = () => {
|
||||
return request({
|
||||
url: './mock/user.json',
|
||||
method: 'get'
|
||||
});
|
||||
};
|
||||
|
||||
export const fetchRoleData = () => {
|
||||
return request({
|
||||
url: './mock/role.json',
|
||||
method: 'get'
|
||||
});
|
||||
};
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
.header{
|
||||
background-color: #242f42;
|
||||
}
|
||||
.login-wrap{
|
||||
background: #324157;
|
||||
}
|
||||
.plugins-tips{
|
||||
background: #eef1f6;
|
||||
}
|
||||
.plugins-tips a{
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
|
||||
.tags-li.active {
|
||||
border: 1px solid var(--el-color-primary);
|
||||
background-color: var(--el-color-primary);
|
||||
}
|
||||
.message-title{
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
.collapse-btn:hover{
|
||||
background: rgb(40,52,70);
|
||||
}
|
|
@ -1,132 +1,95 @@
|
|||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
outline: 0 !important;
|
||||
}
|
||||
|
||||
html,
|
||||
body,
|
||||
#app,
|
||||
.wrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'PingFang SC', "Helvetica Neue", Helvetica, "microsoft yahei", arial, STHeiTi, sans-serif;
|
||||
font-family: 'PingFang SC', 'Helvetica Neue', Helvetica, 'microsoft yahei', arial, STHeiTi, sans-serif;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
|
||||
.content-box {
|
||||
position: absolute;
|
||||
left: 250px;
|
||||
right: 0;
|
||||
top: 70px;
|
||||
bottom: 0;
|
||||
padding-bottom: 30px;
|
||||
-webkit-transition: left .3s ease-in-out;
|
||||
transition: left .3s ease-in-out;
|
||||
background: #f0f0f0;
|
||||
}
|
||||
|
||||
.content {
|
||||
width: auto;
|
||||
height: 100%;
|
||||
padding: 10px;
|
||||
overflow-y: scroll;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.content-collapse {
|
||||
left: 65px;
|
||||
i {
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.container {
|
||||
padding: 30px;
|
||||
background: #fff;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.crumbs {
|
||||
margin: 10px 0;
|
||||
padding: 30px;
|
||||
background: #fff;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.el-table th {
|
||||
background-color: #f5f7fa !important;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
margin: 20px 0;
|
||||
text-align: right;
|
||||
background-color: #f5f7fa !important;
|
||||
}
|
||||
|
||||
.plugins-tips {
|
||||
padding: 20px 10px;
|
||||
margin-bottom: 20px;
|
||||
padding: 20px 10px;
|
||||
margin-bottom: 20px;
|
||||
background: #eef1f6;
|
||||
}
|
||||
|
||||
.el-button+.el-tooltip {
|
||||
margin-left: 10px;
|
||||
.plugins-tips a {
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
|
||||
.el-button + .el-tooltip {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.mgb20 {
|
||||
margin-bottom: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.mgb10 {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.mr10 {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.move-enter-active,
|
||||
.move-leave-active {
|
||||
transition: opacity .1s ease;
|
||||
transition: opacity 0.1s ease;
|
||||
}
|
||||
|
||||
.move-enter-from,
|
||||
.move-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
/*BaseForm*/
|
||||
|
||||
.form-box {
|
||||
width: 600px;
|
||||
}
|
||||
|
||||
.form-box .line {
|
||||
text-align: center;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.el-time-panel__content::after,
|
||||
.el-time-panel__content::before {
|
||||
margin-top: -7px;
|
||||
margin-top: -7px;
|
||||
}
|
||||
|
||||
.el-time-spinner__wrapper .el-scrollbar__wrap:not(.el-scrollbar__wrap--hidden-default) {
|
||||
padding-bottom: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
|
||||
[class*=" el-icon-"], [class^=el-icon-] {
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-variant: normal;
|
||||
text-transform: none;
|
||||
line-height: 1;
|
||||
vertical-align: baseline;
|
||||
display: inline-block;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
.el-sub-menu [class^=el-icon-] {
|
||||
vertical-align: middle;
|
||||
margin-right: 5px;
|
||||
width: 24px;
|
||||
text-align: center;
|
||||
font-size: 18px;
|
||||
[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
[hidden]{
|
||||
display: none !important;
|
||||
.flex-center {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
:root {
|
||||
--header-bg-color: #242f42;
|
||||
--header-text-color: #fff;
|
||||
--active-color: var(--el-color-primary);
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 144.08 128.61"><title>资源 82</title><path d="M72.23 128.61c-7.1-.23-11.51-3.72-14.76-9.36C48 102.87 38.43 86.59 29.1 70.16a36 36 0 0 1-4.47-11.35A14.61 14.61 0 0 1 34 42.51c7.49-2.71 15.71-.21 19.67 6.43 7.52 12.56 14.77 25.27 22.12 37.92 3 5.17 5.89 10.43 9 15.51 5 8 3.45 18-4.22 23.31-2.3 1.62-5.52 1.99-8.34 2.93z" fill="#2ef2e9"/><path d="M72.66.33c6-.57 10.39 2.6 13.51 8C95.61 24.69 105 41.1 114.52 57.4c3.9 6.65-.28 17.13-6.39 20.44-8.93 4.83-17.88 1.28-21.86-5.62C76.82 55.86 67.14 39.62 58.11 23 52.06 12 59.61.24 72.66.33z" fill="#fa6663"/><path d="M144.08 15.83c-.58 8.62-6.73 15.57-15.51 15.66-9.31.09-16.87-7-16.95-15.62S119 0 127.87 0c9.13.09 16.22 7 16.21 15.83z" fill="#fbb355"/><path d="M16.24 31.5C7 31.33-.19 24.42 0 15.8.19 7.5 7.19-.06 14.64 0c10.53.08 18.27 6.73 17.61 15.9-.64 8.96-6.25 15.28-16.01 15.6z" fill="#8a56c2"/></svg>
|
After Width: | Height: | Size: 918 B |
Binary file not shown.
After Width: | Height: | Size: 37 KiB |
|
@ -0,0 +1,39 @@
|
|||
<template>
|
||||
<span ref="countRef"></span>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref, watch } from 'vue';
|
||||
import { CountUp } from 'countup.js';
|
||||
|
||||
const props = defineProps({
|
||||
end: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
options: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
required: false,
|
||||
},
|
||||
});
|
||||
|
||||
const countRef = ref<any>(null);
|
||||
let countUp: any;
|
||||
onMounted(() => {
|
||||
countUp = new CountUp(countRef.value, props.end, props.options);
|
||||
if (countUp.error) {
|
||||
console.error(countUp.error);
|
||||
return;
|
||||
}
|
||||
countUp.start();
|
||||
});
|
||||
|
||||
watch(() => props.end, (newVal) => {
|
||||
if (countUp) {
|
||||
countUp.update(newVal);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
</script>
|
|
@ -1,24 +1,38 @@
|
|||
<template>
|
||||
<div class="header">
|
||||
<!-- 折叠按钮 -->
|
||||
<div class="collapse-btn" @click="collapseChage">
|
||||
<el-icon v-if="sidebar.collapse"><Expand /></el-icon>
|
||||
<el-icon v-else><Fold /></el-icon>
|
||||
<div class="header-left">
|
||||
<img class="logo" src="../assets/img/logo.svg" alt="">
|
||||
<div class="web-title">
|
||||
后台管理系统
|
||||
</div>
|
||||
<div class="collapse-btn" @click="collapseChage">
|
||||
<el-icon v-if="sidebar.collapse">
|
||||
<Expand />
|
||||
</el-icon>
|
||||
<el-icon v-else>
|
||||
<Fold />
|
||||
</el-icon>
|
||||
</div>
|
||||
</div>
|
||||
<div class="logo">后台管理系统</div>
|
||||
<div class="header-right">
|
||||
<div class="header-user-con">
|
||||
<!-- 消息中心 -->
|
||||
<div class="btn-bell" @click="router.push('/tabs')">
|
||||
<el-tooltip
|
||||
effect="dark"
|
||||
:content="message ? `有${message}条未读消息` : `消息中心`"
|
||||
placement="bottom"
|
||||
>
|
||||
<div class="btn-icon" @click="router.push('/theme')">
|
||||
<el-tooltip effect="dark" content="设置主题" placement="bottom">
|
||||
<i class="el-icon-lx-skin"></i>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<div class="btn-icon" @click="router.push('/ucenter')">
|
||||
<el-tooltip effect="dark" :content="message ? `有${message}条未读消息` : `消息中心`" placement="bottom">
|
||||
<i class="el-icon-lx-notice"></i>
|
||||
</el-tooltip>
|
||||
<span class="btn-bell-badge" v-if="message"></span>
|
||||
</div>
|
||||
<div class="btn-icon" @click="setFullScreen">
|
||||
<el-tooltip effect="dark" content="全屏" placement="bottom">
|
||||
<i class="el-icon-lx-full"></i>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<!-- 用户头像 -->
|
||||
<el-avatar class="user-avator" :size="30" :src="imgurl" />
|
||||
<!-- 用户名下拉菜单 -->
|
||||
|
@ -71,58 +85,92 @@ const handleCommand = (command: string) => {
|
|||
localStorage.removeItem('ms_username');
|
||||
router.push('/login');
|
||||
} else if (command == 'user') {
|
||||
router.push('/user');
|
||||
router.push('/ucenter');
|
||||
}
|
||||
};
|
||||
|
||||
const setFullScreen = () => {
|
||||
if (document.fullscreenElement) {
|
||||
document.exitFullscreen();
|
||||
} else {
|
||||
document.body.requestFullscreen.call(document.body);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
.header {
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
height: 70px;
|
||||
font-size: 22px;
|
||||
color: #fff;
|
||||
color: var(--header-text-color);
|
||||
background-color: var(--header-bg-color);
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-left: 20px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.logo {
|
||||
width: 35px;
|
||||
}
|
||||
|
||||
.web-title {
|
||||
margin: 0 40px 0 10px;
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.collapse-btn {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
float: left;
|
||||
padding: 0 21px;
|
||||
padding: 0 10px;
|
||||
cursor: pointer;
|
||||
opacity: 0.8;
|
||||
font-size: 22px;
|
||||
}
|
||||
.header .logo {
|
||||
float: left;
|
||||
width: 250px;
|
||||
line-height: 70px;
|
||||
|
||||
.collapse-btn:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.header-right {
|
||||
float: right;
|
||||
padding-right: 50px;
|
||||
}
|
||||
|
||||
.header-user-con {
|
||||
display: flex;
|
||||
height: 70px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.btn-fullscreen {
|
||||
transform: rotate(45deg);
|
||||
margin-right: 5px;
|
||||
font-size: 24px;
|
||||
}
|
||||
.btn-bell,
|
||||
.btn-fullscreen {
|
||||
|
||||
.btn-icon {
|
||||
position: relative;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
text-align: center;
|
||||
border-radius: 15px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: var(--header-text-color);
|
||||
margin: 0 5px;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.btn-bell-badge {
|
||||
position: absolute;
|
||||
right: 4px;
|
||||
|
@ -131,23 +179,21 @@ const handleCommand = (command: string) => {
|
|||
height: 8px;
|
||||
border-radius: 4px;
|
||||
background: #f56c6c;
|
||||
color: #fff;
|
||||
}
|
||||
.btn-bell .el-icon-lx-notice {
|
||||
color: #fff;
|
||||
}
|
||||
.user-name {
|
||||
margin-left: 10px;
|
||||
color: var(--header-text-color);
|
||||
}
|
||||
|
||||
|
||||
.user-avator {
|
||||
margin-left: 20px;
|
||||
margin: 0 10px 0 20px;
|
||||
}
|
||||
|
||||
.el-dropdown-link {
|
||||
color: #fff;
|
||||
color: var(--header-text-color);
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.el-dropdown-menu__item {
|
||||
text-align: center;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,221 @@
|
|||
import { Menus } from '@/types/menu';
|
||||
|
||||
export const menuData: Menus[] = [
|
||||
{
|
||||
id: '0',
|
||||
title: '系统首页',
|
||||
index: '/dashboard',
|
||||
icon: 'Odometer',
|
||||
},
|
||||
{
|
||||
id: '1',
|
||||
title: '系统管理',
|
||||
index: '1',
|
||||
icon: 'HomeFilled',
|
||||
children: [
|
||||
{
|
||||
id: '11',
|
||||
pid: '1',
|
||||
index: '/system-user',
|
||||
title: '用户管理',
|
||||
},
|
||||
{
|
||||
id: '12',
|
||||
pid: '1',
|
||||
index: '/system-role',
|
||||
title: '角色管理',
|
||||
},
|
||||
{
|
||||
id: '13',
|
||||
pid: '1',
|
||||
index: '/system-menu',
|
||||
title: '菜单管理',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
title: '组件',
|
||||
index: '2-1',
|
||||
icon: 'Calendar',
|
||||
children: [
|
||||
{
|
||||
id: '21',
|
||||
pid: '3',
|
||||
index: '/form',
|
||||
title: '表单',
|
||||
},
|
||||
{
|
||||
id: '22',
|
||||
pid: '3',
|
||||
index: '/upload',
|
||||
title: '上传',
|
||||
},
|
||||
{
|
||||
id: '23',
|
||||
pid: '2',
|
||||
index: '/carousel',
|
||||
title: '走马灯',
|
||||
},
|
||||
{
|
||||
id: '24',
|
||||
pid: '2',
|
||||
index: '/calendar',
|
||||
title: '日历',
|
||||
},
|
||||
{
|
||||
id: '25',
|
||||
pid: '2',
|
||||
index: '/watermark',
|
||||
title: '水印',
|
||||
},
|
||||
{
|
||||
id: '26',
|
||||
pid: '2',
|
||||
index: '/tour',
|
||||
title: '分布引导',
|
||||
},
|
||||
{
|
||||
id: '27',
|
||||
pid: '2',
|
||||
index: '/steps',
|
||||
title: '步骤条',
|
||||
},
|
||||
{
|
||||
id: '28',
|
||||
pid: '2',
|
||||
index: '/statistic',
|
||||
title: '统计',
|
||||
},
|
||||
{
|
||||
id: '29',
|
||||
pid: '3',
|
||||
index: '29',
|
||||
title: '三级菜单',
|
||||
children: [
|
||||
{
|
||||
id: '291',
|
||||
pid: '29',
|
||||
index: '/editor',
|
||||
title: '富文本编辑器',
|
||||
},
|
||||
{
|
||||
id: '292',
|
||||
pid: '29',
|
||||
index: '/markdown',
|
||||
title: 'markdown编辑器',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
title: '表格',
|
||||
index: '3',
|
||||
icon: 'Calendar',
|
||||
children: [
|
||||
{
|
||||
id: '31',
|
||||
pid: '3',
|
||||
index: '/table',
|
||||
title: '基础表格',
|
||||
},
|
||||
{
|
||||
id: '32',
|
||||
pid: '3',
|
||||
index: '/table-editor',
|
||||
title: '可编辑表格',
|
||||
},
|
||||
{
|
||||
id: '33',
|
||||
pid: '3',
|
||||
index: '/import',
|
||||
title: '导入Excel',
|
||||
},
|
||||
{
|
||||
id: '34',
|
||||
pid: '3',
|
||||
index: '/export',
|
||||
title: '导出Excel',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
icon: 'PieChart',
|
||||
index: '4',
|
||||
title: '图表',
|
||||
children: [
|
||||
{
|
||||
id: '41',
|
||||
pid: '4',
|
||||
index: '/schart',
|
||||
title: 'schart图表',
|
||||
},
|
||||
{
|
||||
id: '42',
|
||||
pid: '4',
|
||||
index: '/echarts',
|
||||
title: 'echarts图表',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: '5',
|
||||
icon: 'Guide',
|
||||
index: '/icon',
|
||||
title: '图标',
|
||||
permiss: '5',
|
||||
},
|
||||
{
|
||||
id: '7',
|
||||
icon: 'Brush',
|
||||
index: '/theme',
|
||||
title: '主题',
|
||||
},
|
||||
{
|
||||
id: '6',
|
||||
icon: 'DocumentAdd',
|
||||
index: '6',
|
||||
title: '附加页面',
|
||||
children: [
|
||||
{
|
||||
id: '61',
|
||||
pid: '6',
|
||||
index: '/ucenter',
|
||||
title: '个人中心',
|
||||
},
|
||||
{
|
||||
id: '62',
|
||||
pid: '6',
|
||||
index: '/login',
|
||||
title: '登录',
|
||||
},
|
||||
{
|
||||
id: '63',
|
||||
pid: '6',
|
||||
index: '/register',
|
||||
title: '注册',
|
||||
},
|
||||
{
|
||||
id: '64',
|
||||
pid: '6',
|
||||
index: '/reset-pwd',
|
||||
title: '重设密码',
|
||||
},
|
||||
{
|
||||
id: '65',
|
||||
pid: '6',
|
||||
index: '/403',
|
||||
title: '403',
|
||||
},
|
||||
{
|
||||
id: '66',
|
||||
pid: '6',
|
||||
index: '/404',
|
||||
title: '404',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
|
@ -4,41 +4,43 @@
|
|||
class="sidebar-el-menu"
|
||||
:default-active="onRoutes"
|
||||
:collapse="sidebar.collapse"
|
||||
background-color="#324157"
|
||||
text-color="#bfcbd9"
|
||||
active-text-color="#20a0ff"
|
||||
unique-opened
|
||||
:background-color="sidebar.bgColor"
|
||||
:text-color="sidebar.textColor"
|
||||
router
|
||||
>
|
||||
<template v-for="item in items">
|
||||
<template v-if="item.subs">
|
||||
<el-sub-menu :index="item.index" :key="item.index" v-permiss="item.permiss">
|
||||
<template v-for="item in menuData">
|
||||
<template v-if="item.children">
|
||||
<el-sub-menu :index="item.index" :key="item.index" v-permiss="item.id">
|
||||
<template #title>
|
||||
<el-icon>
|
||||
<component :is="item.icon"></component>
|
||||
</el-icon>
|
||||
<span>{{ item.title }}</span>
|
||||
</template>
|
||||
<template v-for="subItem in item.subs">
|
||||
<template v-for="subItem in item.children">
|
||||
<el-sub-menu
|
||||
v-if="subItem.subs"
|
||||
v-if="subItem.children"
|
||||
:index="subItem.index"
|
||||
:key="subItem.index"
|
||||
v-permiss="item.permiss"
|
||||
v-permiss="item.id"
|
||||
>
|
||||
<template #title>{{ subItem.title }}</template>
|
||||
<el-menu-item v-for="(threeItem, i) in subItem.subs" :key="i" :index="threeItem.index">
|
||||
<el-menu-item
|
||||
v-for="(threeItem, i) in subItem.children"
|
||||
:key="i"
|
||||
:index="threeItem.index"
|
||||
>
|
||||
{{ threeItem.title }}
|
||||
</el-menu-item>
|
||||
</el-sub-menu>
|
||||
<el-menu-item v-else :index="subItem.index" v-permiss="item.permiss">
|
||||
<el-menu-item v-else :index="subItem.index" v-permiss="item.id">
|
||||
{{ subItem.title }}
|
||||
</el-menu-item>
|
||||
</template>
|
||||
</el-sub-menu>
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-menu-item :index="item.index" :key="item.index" v-permiss="item.permiss">
|
||||
<el-menu-item :index="item.index" :key="item.index" v-permiss="item.id">
|
||||
<el-icon>
|
||||
<component :is="item.icon"></component>
|
||||
</el-icon>
|
||||
|
@ -54,103 +56,7 @@
|
|||
import { computed } from 'vue';
|
||||
import { useSidebarStore } from '../store/sidebar';
|
||||
import { useRoute } from 'vue-router';
|
||||
|
||||
const items = [
|
||||
{
|
||||
icon: 'Odometer',
|
||||
index: '/dashboard',
|
||||
title: '系统首页',
|
||||
permiss: '1',
|
||||
},
|
||||
{
|
||||
icon: 'Calendar',
|
||||
index: '1',
|
||||
title: '表格相关',
|
||||
permiss: '2',
|
||||
subs: [
|
||||
{
|
||||
index: '/table',
|
||||
title: '常用表格',
|
||||
permiss: '2',
|
||||
},
|
||||
{
|
||||
index: '/import',
|
||||
title: '导入Excel',
|
||||
permiss: '2',
|
||||
},
|
||||
{
|
||||
index: '/export',
|
||||
title: '导出Excel',
|
||||
permiss: '2',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
icon: 'DocumentCopy',
|
||||
index: '/tabs',
|
||||
title: 'tab选项卡',
|
||||
permiss: '3',
|
||||
},
|
||||
{
|
||||
icon: 'Edit',
|
||||
index: '3',
|
||||
title: '表单相关',
|
||||
permiss: '4',
|
||||
subs: [
|
||||
{
|
||||
index: '/form',
|
||||
title: '基本表单',
|
||||
permiss: '5',
|
||||
},
|
||||
{
|
||||
index: '/upload',
|
||||
title: '文件上传',
|
||||
permiss: '6',
|
||||
},
|
||||
{
|
||||
index: '4',
|
||||
title: '三级菜单',
|
||||
permiss: '7',
|
||||
subs: [
|
||||
{
|
||||
index: '/editor',
|
||||
title: '富文本编辑器',
|
||||
permiss: '8',
|
||||
},
|
||||
{
|
||||
index: '/markdown',
|
||||
title: 'markdown编辑器',
|
||||
permiss: '9',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
icon: 'Setting',
|
||||
index: '/icon',
|
||||
title: '自定义图标',
|
||||
permiss: '10',
|
||||
},
|
||||
{
|
||||
icon: 'PieChart',
|
||||
index: '/charts',
|
||||
title: 'schart图表',
|
||||
permiss: '11',
|
||||
},
|
||||
{
|
||||
icon: 'Warning',
|
||||
index: '/permission',
|
||||
title: '权限管理',
|
||||
permiss: '13',
|
||||
},
|
||||
{
|
||||
icon: 'CoffeeCup',
|
||||
index: '/donate',
|
||||
title: '支持作者',
|
||||
permiss: '14',
|
||||
},
|
||||
];
|
||||
import { menuData } from '@/components/menu';
|
||||
|
||||
const route = useRoute();
|
||||
const onRoutes = computed(() => {
|
||||
|
@ -169,13 +75,16 @@ const sidebar = useSidebarStore();
|
|||
bottom: 0;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.sidebar::-webkit-scrollbar {
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.sidebar-el-menu:not(.el-menu--collapse) {
|
||||
width: 250px;
|
||||
}
|
||||
.sidebar > ul {
|
||||
height: 100%;
|
||||
|
||||
.sidebar-el-menu {
|
||||
min-height: 100%;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -0,0 +1,211 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="table-toolbar" v-if="hasToolbar">
|
||||
<div class="table-toolbar-left">
|
||||
<slot name="toolbarBtn"></slot>
|
||||
</div>
|
||||
<div class="table-toolbar-right flex-center">
|
||||
<template v-if="multipleSelection.length > 0">
|
||||
<el-tooltip effect="dark" content="删除选中" placement="top">
|
||||
<el-icon class="columns-setting-icon" @click="delSelection(multipleSelection)">
|
||||
<Delete />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
<el-divider direction="vertical" />
|
||||
</template>
|
||||
<el-tooltip effect="dark" content="刷新" placement="top">
|
||||
<el-icon class="columns-setting-icon" @click="refresh">
|
||||
<Refresh />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
<el-divider direction="vertical" />
|
||||
<el-tooltip effect="dark" content="列设置" placement="top">
|
||||
<el-dropdown :hide-on-click="false" size="small" trigger="click">
|
||||
<el-icon class="columns-setting-icon">
|
||||
<Setting />
|
||||
</el-icon>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item v-for="c in columns">
|
||||
<el-checkbox v-model="c.visible" :label="c.label" />
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
<el-table class="mgb20" :style="{ width: '100%' }" border :data="tableData" :row-key="rowKey"
|
||||
@selection-change="handleSelectionChange" table-layout="auto">
|
||||
<template v-for="item in columns" :key="item.prop">
|
||||
<el-table-column v-if="item.visible" :prop="item.prop" :label="item.label" :width="item.width"
|
||||
:type="item.type" :align="item.align || 'center'">
|
||||
|
||||
<template #default="{ row, column, $index }" v-if="item.type === 'index'">
|
||||
{{ getIndex($index) }}
|
||||
</template>
|
||||
<template #default="{ row, column, $index }" v-if="!item.type">
|
||||
<slot :name="item.prop" :rows="row" :index="$index">
|
||||
<template v-if="item.prop == 'operator'">
|
||||
<el-button type="warning" size="small" :icon="View" @click="viewFunc(row)">
|
||||
查看
|
||||
</el-button>
|
||||
<el-button type="primary" size="small" :icon="Edit" @click="editFunc(row)">
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button type="danger" size="small" :icon="Delete" @click="handleDelete(row)">
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
<span v-else-if="item.formatter">
|
||||
{{ item.formatter(row[item.prop]) }}
|
||||
</span>
|
||||
<span v-else>
|
||||
{{ row[item.prop] }}
|
||||
</span>
|
||||
</slot>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</template>
|
||||
</el-table>
|
||||
<el-pagination v-if="hasPagination" :current-page="currentPage" :page-size="pageSize" :background="true"
|
||||
:layout="layout" :total="total" @current-change="handleCurrentChange" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { toRefs, PropType, ref } from 'vue'
|
||||
import { Delete, Edit, View, Refresh } from '@element-plus/icons-vue';
|
||||
import { ElMessageBox } from 'element-plus';
|
||||
|
||||
const props = defineProps({
|
||||
// 表格相关
|
||||
tableData: {
|
||||
type: Array,
|
||||
default: []
|
||||
},
|
||||
columns: {
|
||||
type: Array as PropType<any[]>,
|
||||
default: []
|
||||
},
|
||||
rowKey: {
|
||||
type: String,
|
||||
default: 'id'
|
||||
},
|
||||
hasToolbar: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 分页相关
|
||||
hasPagination: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
total: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
currentPage: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
pageSize: {
|
||||
type: Number,
|
||||
default: 10
|
||||
},
|
||||
|
||||
layout: {
|
||||
type: String,
|
||||
default: 'total, prev, pager, next'
|
||||
},
|
||||
delFunc: {
|
||||
type: Function,
|
||||
default: () => { }
|
||||
},
|
||||
viewFunc: {
|
||||
type: Function,
|
||||
default: () => { }
|
||||
},
|
||||
editFunc: {
|
||||
type: Function,
|
||||
default: () => { }
|
||||
},
|
||||
delSelection: {
|
||||
type: Function,
|
||||
default: () => { }
|
||||
},
|
||||
refresh: {
|
||||
type: Function,
|
||||
default: () => { }
|
||||
},
|
||||
changePage: {
|
||||
type: Function,
|
||||
default: () => { }
|
||||
}
|
||||
})
|
||||
|
||||
let {
|
||||
tableData,
|
||||
columns,
|
||||
rowKey,
|
||||
hasToolbar,
|
||||
hasPagination,
|
||||
total,
|
||||
currentPage,
|
||||
pageSize,
|
||||
layout,
|
||||
} = toRefs(props)
|
||||
|
||||
columns.value.forEach((item) => {
|
||||
if (item.visible === undefined) {
|
||||
item.visible = true
|
||||
}
|
||||
})
|
||||
|
||||
// 当选择项发生变化时会触发该事件
|
||||
const multipleSelection = ref([])
|
||||
const handleSelectionChange = (selection: any[]) => {
|
||||
multipleSelection.value = selection
|
||||
}
|
||||
|
||||
// 当前页码变化的事件
|
||||
const handleCurrentChange = (val: number) => {
|
||||
props.changePage(val)
|
||||
}
|
||||
|
||||
const handleDelete = (row) => {
|
||||
ElMessageBox.confirm('确定要删除吗?', '提示', {
|
||||
type: 'warning'
|
||||
})
|
||||
.then(async () => {
|
||||
props.delFunc(row);
|
||||
})
|
||||
.catch(() => { });
|
||||
};
|
||||
|
||||
const getIndex = (index: number) => {
|
||||
return index + 1 + (currentPage.value - 1) * pageSize.value
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.table-toolbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-end;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.columns-setting-icon {
|
||||
display: block;
|
||||
font-size: 18px;
|
||||
cursor: pointer;
|
||||
color: #676767;
|
||||
}
|
||||
</style>
|
||||
<style>
|
||||
.table-header .cell {
|
||||
color: #333;
|
||||
}
|
||||
</style>
|
|
@ -1,32 +1,10 @@
|
|||
<template>
|
||||
<el-descriptions title="" :column="2" border>
|
||||
<el-descriptions-item>
|
||||
<template #label> 用户ID </template>
|
||||
{{ data.id }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item>
|
||||
<template #label> 用户名 </template>
|
||||
{{ data.name }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item>
|
||||
<template #label> 账户余额 </template>
|
||||
{{ data.money }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item>
|
||||
<template #label> 账户状态 </template>
|
||||
{{ data.state ? '正常' : '异常' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item :span="2">
|
||||
<template #label> 地址 </template>
|
||||
{{ data.address }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item>
|
||||
<template #label> 日期 </template>
|
||||
{{ data.date }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item>
|
||||
<template #label> 头像 </template>
|
||||
<img :src="data.thumb" style="width: 120px" alt="" />
|
||||
<el-descriptions :title="title" :column="column" border>
|
||||
<el-descriptions-item v-for="item in list" :span="item.span">
|
||||
<template #label> {{ item.label }} </template>
|
||||
<slot :name="item.prop" :rows="row">
|
||||
{{ item.value || row[item.prop] }}
|
||||
</slot>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</template>
|
||||
|
@ -35,7 +13,9 @@
|
|||
const props = defineProps({
|
||||
data: {
|
||||
type: Object,
|
||||
required: true
|
||||
required: true,
|
||||
}
|
||||
});
|
||||
const { row, title, column = 2, list } = props.data;
|
||||
|
||||
</script>
|
||||
|
|
|
@ -1,38 +1,36 @@
|
|||
<template>
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
|
||||
<el-form-item label="用户名" prop="name">
|
||||
<el-input v-model="form.name"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="账户余额" prop="money">
|
||||
<el-input v-model.number="form.money"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="地址" prop="address">
|
||||
<el-input v-model="form.address"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="账户状态" prop="state">
|
||||
<el-switch
|
||||
v-model="form.state"
|
||||
:active-value="1"
|
||||
:inactive-value="0"
|
||||
active-text="正常"
|
||||
inactive-text="异常"
|
||||
></el-switch>
|
||||
</el-form-item>
|
||||
<el-form-item label="注册日期" prop="date">
|
||||
<el-date-picker type="date" v-model="form.date" value-format="YYYY-MM-DD"></el-date-picker>
|
||||
</el-form-item>
|
||||
<el-form-item label="上传头像" prop="thumb">
|
||||
<el-upload
|
||||
class="avatar-uploader"
|
||||
action="https://run.mocky.io/v3/9d059bf9-4660-45f2-925d-ce80ad6c4d15"
|
||||
:show-file-list="false"
|
||||
:on-success="handleAvatarSuccess"
|
||||
:before-upload="beforeAvatarUpload"
|
||||
>
|
||||
<img v-if="form.thumb" :src="form.thumb" class="avatar" />
|
||||
<el-icon v-else class="avatar-uploader-icon"><Plus /></el-icon>
|
||||
</el-upload>
|
||||
</el-form-item>
|
||||
<el-form ref="formRef" :model="form" :rules="rules" :label-width="options.labelWidth">
|
||||
<el-row>
|
||||
<el-col :span="options.span" v-for="item in options.list">
|
||||
<el-form-item :label="item.label" :prop="item.prop">
|
||||
<!-- 文本框、数字框、下拉框、日期框、开关、上传 -->
|
||||
<el-input v-if="item.type === 'input'" v-model="form[item.prop]" :disabled="item.disabled"
|
||||
:placeholder="item.placeholder" clearable></el-input>
|
||||
<el-input-number v-else-if="item.type === 'number'" v-model="form[item.prop]"
|
||||
:disabled="item.disabled" controls-position="right"></el-input-number>
|
||||
<el-select v-else-if="item.type === 'select'" v-model="form[item.prop]" :disabled="item.disabled"
|
||||
:placeholder="item.placeholder" clearable>
|
||||
<el-option v-for="opt in item.opts" :label="opt.label" :value="opt.value"></el-option>
|
||||
</el-select>
|
||||
<el-date-picker v-else-if="item.type === 'date'" type="date" v-model="form[item.prop]"
|
||||
:value-format="item.format"></el-date-picker>
|
||||
<el-switch v-else-if="item.type === 'switch'" v-model="form[item.prop]"
|
||||
:active-value="item.activeValue" :inactive-value="item.inactiveValue"
|
||||
:active-text="item.activeText" :inactive-text="item.inactiveText"></el-switch>
|
||||
<el-upload v-else-if="item.type === 'upload'" class="avatar-uploader" action="#"
|
||||
:show-file-list="false" :on-success="handleAvatarSuccess">
|
||||
<img v-if="form[item.prop]" :src="form[item.prop]" class="avatar" />
|
||||
<el-icon v-else class="avatar-uploader-icon">
|
||||
<Plus />
|
||||
</el-icon>
|
||||
</el-upload>
|
||||
<slot :name="item.prop" v-else>
|
||||
|
||||
</slot>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="saveEdit(formRef)">保 存</el-button>
|
||||
</el-form-item>
|
||||
|
@ -40,11 +38,16 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ElMessage, FormInstance, FormRules, UploadProps } from 'element-plus';
|
||||
import { ref } from 'vue';
|
||||
import { FormOption } from '@/types/form-option';
|
||||
import { FormInstance, FormRules, UploadProps } from 'element-plus';
|
||||
import { PropType, ref } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
data: {
|
||||
const { options, formData, edit, update } = defineProps({
|
||||
options: {
|
||||
type: Object as PropType<FormOption>,
|
||||
required: true
|
||||
},
|
||||
formData: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
|
@ -58,28 +61,23 @@ const props = defineProps({
|
|||
}
|
||||
});
|
||||
|
||||
const defaultData = {
|
||||
id: '',
|
||||
name: '',
|
||||
address: '',
|
||||
thumb: '',
|
||||
money: 0,
|
||||
state: 0,
|
||||
date: new Date()
|
||||
};
|
||||
|
||||
const form = ref({ ...(props.edit ? props.data : defaultData) });
|
||||
const form = ref({ ...(edit ? formData : {}) });
|
||||
|
||||
const rules: FormRules = options.list.map(item => {
|
||||
if (item.required) {
|
||||
return { [item.prop]: [{ required: true, message: `${item.label}不能为空`, trigger: 'blur' }] };
|
||||
}
|
||||
return {};
|
||||
}).reduce((acc, cur) => ({ ...acc, ...cur }), {});
|
||||
|
||||
|
||||
const rules: FormRules = {
|
||||
name: [{ required: true, message: '用户名', trigger: 'blur' }]
|
||||
};
|
||||
const formRef = ref<FormInstance>();
|
||||
const saveEdit = (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return;
|
||||
formEl.validate(valid => {
|
||||
if (!valid) return false;
|
||||
props.update(form.value);
|
||||
ElMessage.success('保存成功!');
|
||||
update(form.value);
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -87,16 +85,6 @@ const handleAvatarSuccess: UploadProps['onSuccess'] = (response, uploadFile) =>
|
|||
form.value.thumb = URL.createObjectURL(uploadFile.raw!);
|
||||
};
|
||||
|
||||
const beforeAvatarUpload: UploadProps['beforeUpload'] = rawFile => {
|
||||
if (rawFile.type !== 'image/jpeg') {
|
||||
ElMessage.error('Avatar picture must be JPG format!');
|
||||
return false;
|
||||
} else if (rawFile.size / 1024 / 1024 > 2) {
|
||||
ElMessage.error('Avatar picture size can not exceed 2MB!');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
<template>
|
||||
<div class="search-container">
|
||||
<el-form ref="searchRef" :model="query" :inline="true">
|
||||
<el-form-item :label="item.label" :prop="item.prop" v-for="item in options">
|
||||
<!-- 文本框、下拉框、日期框 -->
|
||||
<el-input v-if="item.type === 'input'" v-model="query[item.prop]" :disabled="item.disabled"
|
||||
:placeholder="item.placeholder" clearable></el-input>
|
||||
<el-select v-else-if="item.type === 'select'" v-model="query[item.prop]" :disabled="item.disabled"
|
||||
:placeholder="item.placeholder" clearable>
|
||||
<el-option v-for="opt in item.opts" :label="opt.label" :value="opt.value"></el-option>
|
||||
</el-select>
|
||||
<el-date-picker v-else-if="item.type === 'date'" type="date" v-model="query[item.prop]"
|
||||
:value-format="item.format"></el-date-picker>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" :icon="Search" @click="search">搜索</el-button>
|
||||
<el-button :icon="Refresh" @click="resetForm(searchRef)">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { FormInstance } from 'element-plus';
|
||||
import { Search, Refresh } from '@element-plus/icons-vue';
|
||||
import { PropType, ref } from 'vue';
|
||||
import { FormOptionList } from '@/types/form-option';
|
||||
|
||||
const props = defineProps({
|
||||
query: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
options: {
|
||||
type: Array as PropType<Array<FormOptionList>>,
|
||||
required: true
|
||||
},
|
||||
search: {
|
||||
type: Function,
|
||||
default: () => { }
|
||||
}
|
||||
});
|
||||
|
||||
const searchRef = ref<FormInstance>();
|
||||
const resetForm = (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return
|
||||
formEl.resetFields()
|
||||
props.search();
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.search-container {
|
||||
padding: 20px 30px 0;
|
||||
background-color: #fff;
|
||||
margin-bottom: 10px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 5px
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,149 @@
|
|||
<template>
|
||||
<div class="tabs-container">
|
||||
<el-tabs v-model="activePath" class="tabs" type="card" closable @tab-click="clickTabls" @tab-remove="closeTabs">
|
||||
<el-tab-pane v-for="item in tabs.list" :key="item.path" :label="item.title" :name="item.path"
|
||||
@click="setTags(item)"></el-tab-pane>
|
||||
</el-tabs>
|
||||
<div class="Tabs-close-box">
|
||||
<el-dropdown @command="handleTags">
|
||||
<el-button size="small" type="primary" plain>
|
||||
标签选项
|
||||
<el-icon class="el-icon--right">
|
||||
<arrow-down />
|
||||
</el-icon>
|
||||
</el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu size="small">
|
||||
<el-dropdown-item command="other">关闭其他</el-dropdown-item>
|
||||
<el-dropdown-item command="current">关闭当前</el-dropdown-item>
|
||||
<el-dropdown-item command="all">关闭所有</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue';
|
||||
import { useTabsStore } from '../store/tabs';
|
||||
import { onBeforeRouteUpdate, useRoute, useRouter } from 'vue-router';
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const activePath = ref(route.fullPath)
|
||||
const tabs = useTabsStore();
|
||||
// 设置标签
|
||||
const setTags = (route: any) => {
|
||||
const isExist = tabs.list.some(item => {
|
||||
return item.path === route.fullPath;
|
||||
});
|
||||
if (!isExist) {
|
||||
tabs.setTabsItem({
|
||||
name: route.name,
|
||||
title: route.meta.title,
|
||||
path: route.fullPath
|
||||
});
|
||||
}
|
||||
};
|
||||
setTags(route);
|
||||
onBeforeRouteUpdate(to => {
|
||||
setTags(to);
|
||||
});
|
||||
|
||||
// 关闭全部标签
|
||||
const closeAll = () => {
|
||||
tabs.clearTabs();
|
||||
router.push('/');
|
||||
};
|
||||
// 关闭其他标签
|
||||
const closeOther = () => {
|
||||
const curItem = tabs.list.filter(item => {
|
||||
return item.path === route.fullPath;
|
||||
});
|
||||
tabs.closeTabsOther(curItem);
|
||||
};
|
||||
const handleTags = (command: string) => {
|
||||
switch (command) {
|
||||
case 'current':
|
||||
// 关闭当前页面的标签页
|
||||
tabs.closeCurrentTag({
|
||||
$router: router,
|
||||
$route: route
|
||||
});
|
||||
break;
|
||||
case 'all':
|
||||
closeAll();
|
||||
break;
|
||||
|
||||
case 'other':
|
||||
closeOther();
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
const clickTabls = (item: any) => {
|
||||
router.push(item.props.name);
|
||||
}
|
||||
const closeTabs = (path: any) => {
|
||||
console.log(path);
|
||||
const index = tabs.list.findIndex((item) => item.path === path);
|
||||
tabs.delTabsItem(index);
|
||||
const item = tabs.list[index] ? tabs.list[index] : tabs.list[index - 1];
|
||||
if (item) {
|
||||
path === route.fullPath && router.push(item.path);
|
||||
} else {
|
||||
router.push('/');
|
||||
}
|
||||
}
|
||||
|
||||
watch(() => route.fullPath, (newVal, oldVal) => {
|
||||
activePath.value = newVal;
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style scss>
|
||||
.tabs-container {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
padding: 2px 120px 0 0;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
|
||||
.el-tabs__header {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.el-tabs__nav {
|
||||
height: 28px;
|
||||
}
|
||||
|
||||
.el-tabs__nav-next,
|
||||
.el-tabs__nav-prev {
|
||||
line-height: 32px;
|
||||
}
|
||||
|
||||
&.el-tabs {
|
||||
--el-tabs-header-height: 28px;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
.Tabs-close-box {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
box-sizing: border-box;
|
||||
padding-top: 1px;
|
||||
text-align: center;
|
||||
width: 110px;
|
||||
height: 30px;
|
||||
background: #fff;
|
||||
box-shadow: -3px 0 15px 3px rgba(0, 0, 0, 0.1);
|
||||
z-index: 10;
|
||||
}
|
||||
</style>
|
|
@ -1,168 +0,0 @@
|
|||
<template>
|
||||
<div class="tags" v-if="tags.show">
|
||||
<ul>
|
||||
<li
|
||||
class="tags-li"
|
||||
v-for="(item, index) in tags.list"
|
||||
:class="{ active: isActive(item.path) }"
|
||||
:key="index"
|
||||
>
|
||||
<router-link :to="item.path" class="tags-li-title">{{ item.title }}</router-link>
|
||||
<el-icon @click="closeTags(index)"><Close /></el-icon>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="tags-close-box">
|
||||
<el-dropdown @command="handleTags">
|
||||
<el-button size="small" type="primary">
|
||||
标签选项
|
||||
<el-icon class="el-icon--right">
|
||||
<arrow-down />
|
||||
</el-icon>
|
||||
</el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu size="small">
|
||||
<el-dropdown-item command="other">关闭其他</el-dropdown-item>
|
||||
<el-dropdown-item command="all">关闭所有</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useTagsStore } from '../store/tags';
|
||||
import { onBeforeRouteUpdate, useRoute, useRouter } from 'vue-router';
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const isActive = (path: string) => {
|
||||
return path === route.fullPath;
|
||||
};
|
||||
|
||||
const tags = useTagsStore();
|
||||
// 关闭单个标签
|
||||
const closeTags = (index: number) => {
|
||||
const delItem = tags.list[index];
|
||||
tags.delTagsItem(index);
|
||||
const item = tags.list[index] ? tags.list[index] : tags.list[index - 1];
|
||||
if (item) {
|
||||
delItem.path === route.fullPath && router.push(item.path);
|
||||
} else {
|
||||
router.push('/');
|
||||
}
|
||||
};
|
||||
|
||||
// 设置标签
|
||||
const setTags = (route: any) => {
|
||||
const isExist = tags.list.some(item => {
|
||||
return item.path === route.fullPath;
|
||||
});
|
||||
if (!isExist) {
|
||||
if (tags.list.length >= 8) tags.delTagsItem(0);
|
||||
tags.setTagsItem({
|
||||
name: route.name,
|
||||
title: route.meta.title,
|
||||
path: route.fullPath
|
||||
});
|
||||
}
|
||||
};
|
||||
setTags(route);
|
||||
onBeforeRouteUpdate(to => {
|
||||
setTags(to);
|
||||
});
|
||||
|
||||
// 关闭全部标签
|
||||
const closeAll = () => {
|
||||
tags.clearTags();
|
||||
router.push('/');
|
||||
};
|
||||
// 关闭其他标签
|
||||
const closeOther = () => {
|
||||
const curItem = tags.list.filter(item => {
|
||||
return item.path === route.fullPath;
|
||||
});
|
||||
tags.closeTagsOther(curItem);
|
||||
};
|
||||
const handleTags = (command: string) => {
|
||||
command === 'other' ? closeOther() : closeAll();
|
||||
};
|
||||
|
||||
// 关闭当前页面的标签页
|
||||
// tags.closeCurrentTag({
|
||||
// $router: router,
|
||||
// $route: route
|
||||
// });
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.tags {
|
||||
position: relative;
|
||||
height: 30px;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
padding-right: 120px;
|
||||
box-shadow: 0 5px 10px #ddd;
|
||||
}
|
||||
|
||||
.tags ul {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.tags-li {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
float: left;
|
||||
margin: 3px 5px 2px 3px;
|
||||
border-radius: 3px;
|
||||
font-size: 12px;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
height: 23px;
|
||||
border: 1px solid #e9eaec;
|
||||
background: #fff;
|
||||
padding: 0 5px 0 12px;
|
||||
color: #666;
|
||||
-webkit-transition: all 0.3s ease-in;
|
||||
-moz-transition: all 0.3s ease-in;
|
||||
transition: all 0.3s ease-in;
|
||||
}
|
||||
|
||||
.tags-li:not(.active):hover {
|
||||
background: #f8f8f8;
|
||||
}
|
||||
|
||||
.tags-li.active {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.tags-li-title {
|
||||
float: left;
|
||||
max-width: 80px;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
margin-right: 5px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.tags-li.active .tags-li-title {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.tags-close-box {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
box-sizing: border-box;
|
||||
padding-top: 1px;
|
||||
text-align: center;
|
||||
width: 110px;
|
||||
height: 30px;
|
||||
background: #fff;
|
||||
box-shadow: -3px 0 15px 3px rgba(0, 0, 0, 0.1);
|
||||
z-index: 10;
|
||||
}
|
||||
</style>
|
|
@ -19,7 +19,7 @@ for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
|
|||
const permiss = usePermissStore();
|
||||
app.directive('permiss', {
|
||||
mounted(el, binding) {
|
||||
if (!permiss.key.includes(String(binding.value))) {
|
||||
if (binding.value && !permiss.key.includes(String(binding.value))) {
|
||||
el['hidden'] = true;
|
||||
}
|
||||
},
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router';
|
||||
import { usePermissStore } from '../store/permiss';
|
||||
import Home from '../views/home.vue';
|
||||
import NProgress from 'nprogress'
|
||||
import 'nprogress/nprogress.css'
|
||||
import NProgress from 'nprogress';
|
||||
import 'nprogress/nprogress.css';
|
||||
|
||||
const routes: RouteRecordRaw[] = [
|
||||
{
|
||||
|
@ -19,144 +19,251 @@ const routes: RouteRecordRaw[] = [
|
|||
name: 'dashboard',
|
||||
meta: {
|
||||
title: '系统首页',
|
||||
permiss: '1',
|
||||
permiss: '0',
|
||||
},
|
||||
component: () => import(/* webpackChunkName: "dashboard" */ '../views/dashboard.vue'),
|
||||
},
|
||||
{
|
||||
path: '/system-user',
|
||||
name: 'system-user',
|
||||
meta: {
|
||||
title: '用户管理',
|
||||
permiss: '11',
|
||||
},
|
||||
component: () => import(/* webpackChunkName: "system-user" */ '../views/system/user.vue'),
|
||||
},
|
||||
{
|
||||
path: '/system-role',
|
||||
name: 'system-role',
|
||||
meta: {
|
||||
title: '角色管理',
|
||||
permiss: '12',
|
||||
},
|
||||
component: () => import(/* webpackChunkName: "system-role" */ '../views/system/role.vue'),
|
||||
},
|
||||
{
|
||||
path: '/system-menu',
|
||||
name: 'system-menu',
|
||||
meta: {
|
||||
title: '菜单管理',
|
||||
permiss: '13',
|
||||
},
|
||||
component: () => import(/* webpackChunkName: "system-menu" */ '../views/system/menu.vue'),
|
||||
},
|
||||
{
|
||||
path: '/table',
|
||||
name: 'basetable',
|
||||
meta: {
|
||||
title: '表格',
|
||||
permiss: '2',
|
||||
title: '基础表格',
|
||||
permiss: '31',
|
||||
},
|
||||
component: () => import(/* webpackChunkName: "table" */ '../views/table.vue'),
|
||||
component: () => import(/* webpackChunkName: "table" */ '../views/table/basetable.vue'),
|
||||
},
|
||||
{
|
||||
path: '/charts',
|
||||
name: 'basecharts',
|
||||
path: '/table-editor',
|
||||
name: 'table-editor',
|
||||
meta: {
|
||||
title: '图表',
|
||||
permiss: '11',
|
||||
title: '可编辑表格',
|
||||
permiss: '32',
|
||||
},
|
||||
component: () => import(/* webpackChunkName: "charts" */ '../views/charts.vue'),
|
||||
component: () => import(/* webpackChunkName: "table-editor" */ '../views/table/table-editor.vue'),
|
||||
},
|
||||
{
|
||||
path: '/form',
|
||||
name: 'baseform',
|
||||
path: '/schart',
|
||||
name: 'schart',
|
||||
meta: {
|
||||
title: '表单',
|
||||
permiss: '5',
|
||||
title: 'schart图表',
|
||||
permiss: '41',
|
||||
},
|
||||
component: () => import(/* webpackChunkName: "form" */ '../views/form.vue'),
|
||||
component: () => import(/* webpackChunkName: "schart" */ '../views/chart/schart.vue'),
|
||||
},
|
||||
{
|
||||
path: '/tabs',
|
||||
name: 'tabs',
|
||||
path: '/echarts',
|
||||
name: 'echarts',
|
||||
meta: {
|
||||
title: 'tab标签',
|
||||
permiss: '3',
|
||||
title: 'echarts图表',
|
||||
permiss: '42',
|
||||
},
|
||||
component: () => import(/* webpackChunkName: "tabs" */ '../views/tabs.vue'),
|
||||
},
|
||||
{
|
||||
path: '/donate',
|
||||
name: 'donate',
|
||||
meta: {
|
||||
title: '鼓励作者',
|
||||
permiss: '14',
|
||||
},
|
||||
component: () => import(/* webpackChunkName: "donate" */ '../views/donate.vue'),
|
||||
},
|
||||
{
|
||||
path: '/permission',
|
||||
name: 'permission',
|
||||
meta: {
|
||||
title: '权限管理',
|
||||
permiss: '13',
|
||||
},
|
||||
component: () => import(/* webpackChunkName: "permission" */ '../views/permission.vue'),
|
||||
},
|
||||
{
|
||||
path: '/upload',
|
||||
name: 'upload',
|
||||
meta: {
|
||||
title: '上传插件',
|
||||
permiss: '6',
|
||||
},
|
||||
component: () => import(/* webpackChunkName: "upload" */ '../views/upload.vue'),
|
||||
component: () => import(/* webpackChunkName: "echarts" */ '../views/chart/echarts.vue'),
|
||||
},
|
||||
|
||||
{
|
||||
path: '/icon',
|
||||
name: 'icon',
|
||||
meta: {
|
||||
title: '自定义图标',
|
||||
permiss: '10',
|
||||
title: '图标',
|
||||
permiss: '5',
|
||||
},
|
||||
component: () => import(/* webpackChunkName: "icon" */ '../views/icon.vue'),
|
||||
component: () => import(/* webpackChunkName: "icon" */ '../views/pages/icon.vue'),
|
||||
},
|
||||
{
|
||||
path: '/user',
|
||||
name: 'user',
|
||||
path: '/ucenter',
|
||||
name: 'ucenter',
|
||||
meta: {
|
||||
title: '个人中心',
|
||||
},
|
||||
component: () => import(/* webpackChunkName: "user" */ '../views/user.vue'),
|
||||
component: () => import(/* webpackChunkName: "ucenter" */ '../views/pages/ucenter.vue'),
|
||||
},
|
||||
{
|
||||
path: '/editor',
|
||||
name: 'editor',
|
||||
meta: {
|
||||
title: '富文本编辑器',
|
||||
permiss: '8',
|
||||
permiss: '291',
|
||||
},
|
||||
component: () => import(/* webpackChunkName: "editor" */ '../views/editor.vue'),
|
||||
component: () => import(/* webpackChunkName: "editor" */ '../views/pages/editor.vue'),
|
||||
},
|
||||
{
|
||||
path: '/markdown',
|
||||
name: 'markdown',
|
||||
meta: {
|
||||
title: 'markdown编辑器',
|
||||
permiss: '9',
|
||||
permiss: '292',
|
||||
},
|
||||
component: () => import(/* webpackChunkName: "markdown" */ '../views/markdown.vue'),
|
||||
component: () => import(/* webpackChunkName: "markdown" */ '../views/pages/markdown.vue'),
|
||||
},
|
||||
{
|
||||
path: '/export',
|
||||
name: 'export',
|
||||
meta: {
|
||||
title: '导出Excel',
|
||||
permiss: '2',
|
||||
permiss: '34',
|
||||
},
|
||||
component: () => import(/* webpackChunkName: "export" */ '../views/export.vue'),
|
||||
component: () => import(/* webpackChunkName: "export" */ '../views/table/export.vue'),
|
||||
},
|
||||
{
|
||||
path: '/import',
|
||||
name: 'import',
|
||||
meta: {
|
||||
title: '导入Excel',
|
||||
permiss: '2',
|
||||
permiss: '33',
|
||||
},
|
||||
component: () => import(/* webpackChunkName: "import" */ '../views/import.vue'),
|
||||
component: () => import(/* webpackChunkName: "import" */ '../views/table/import.vue'),
|
||||
},
|
||||
{
|
||||
path: '/theme',
|
||||
name: 'theme',
|
||||
meta: {
|
||||
title: '主题设置',
|
||||
permiss: '7',
|
||||
},
|
||||
component: () => import(/* webpackChunkName: "theme" */ '../views/pages/theme.vue'),
|
||||
},
|
||||
{
|
||||
path: '/calendar',
|
||||
name: 'calendar',
|
||||
meta: {
|
||||
title: '日历',
|
||||
permiss: '24',
|
||||
},
|
||||
component: () => import(/* webpackChunkName: "calendar" */ '../views/element/calendar.vue'),
|
||||
},
|
||||
{
|
||||
path: '/watermark',
|
||||
name: 'watermark',
|
||||
meta: {
|
||||
title: '水印',
|
||||
permiss: '25',
|
||||
},
|
||||
component: () => import(/* webpackChunkName: "watermark" */ '../views/element/watermark.vue'),
|
||||
},
|
||||
{
|
||||
path: '/carousel',
|
||||
name: 'carousel',
|
||||
meta: {
|
||||
title: '走马灯',
|
||||
permiss: '23',
|
||||
},
|
||||
component: () => import(/* webpackChunkName: "carousel" */ '../views/element/carousel.vue'),
|
||||
},
|
||||
{
|
||||
path: '/tour',
|
||||
name: 'tour',
|
||||
meta: {
|
||||
title: '分步引导',
|
||||
permiss: '26',
|
||||
},
|
||||
component: () => import(/* webpackChunkName: "tour" */ '../views/element/tour.vue'),
|
||||
},
|
||||
{
|
||||
path: '/steps',
|
||||
name: 'steps',
|
||||
meta: {
|
||||
title: '步骤条',
|
||||
permiss: '27',
|
||||
},
|
||||
component: () => import(/* webpackChunkName: "steps" */ '../views/element/steps.vue'),
|
||||
},
|
||||
{
|
||||
path: '/form',
|
||||
name: 'forms',
|
||||
meta: {
|
||||
title: '表单',
|
||||
permiss: '21',
|
||||
},
|
||||
component: () => import(/* webpackChunkName: "form" */ '../views/element/form.vue'),
|
||||
},
|
||||
{
|
||||
path: '/upload',
|
||||
name: 'upload',
|
||||
meta: {
|
||||
title: '上传',
|
||||
permiss: '22',
|
||||
},
|
||||
component: () => import(/* webpackChunkName: "upload" */ '../views/element/upload.vue'),
|
||||
},
|
||||
{
|
||||
path: '/statistic',
|
||||
name: 'statistic',
|
||||
meta: {
|
||||
title: '统计',
|
||||
permiss: '28',
|
||||
},
|
||||
component: () => import(/* webpackChunkName: "statistic" */ '../views/element/statistic.vue'),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/login',
|
||||
name: 'Login',
|
||||
meta: {
|
||||
title: '登录',
|
||||
noAuth: true,
|
||||
},
|
||||
component: () => import(/* webpackChunkName: "login" */ '../views/login.vue'),
|
||||
component: () => import(/* webpackChunkName: "login" */ '../views/pages/login.vue'),
|
||||
},
|
||||
{
|
||||
path: '/register',
|
||||
meta: {
|
||||
title: '注册',
|
||||
noAuth: true,
|
||||
},
|
||||
component: () => import(/* webpackChunkName: "register" */ '../views/pages/register.vue'),
|
||||
},
|
||||
{
|
||||
path: '/reset-pwd',
|
||||
meta: {
|
||||
title: '重置密码',
|
||||
noAuth: true,
|
||||
},
|
||||
component: () => import(/* webpackChunkName: "reset-pwd" */ '../views/pages/reset-pwd.vue'),
|
||||
},
|
||||
{
|
||||
path: '/403',
|
||||
name: '403',
|
||||
meta: {
|
||||
title: '没有权限',
|
||||
noAuth: true,
|
||||
},
|
||||
component: () => import(/* webpackChunkName: "403" */ '../views/403.vue'),
|
||||
component: () => import(/* webpackChunkName: "403" */ '../views/pages/403.vue'),
|
||||
},
|
||||
{
|
||||
path: '/404',
|
||||
meta: {
|
||||
title: '找不到页面',
|
||||
noAuth: true,
|
||||
},
|
||||
component: () => import(/* webpackChunkName: "404" */ '../views/pages/404.vue'),
|
||||
},
|
||||
{ path: '/:path(.*)', redirect: '/404' },
|
||||
];
|
||||
|
||||
const router = createRouter({
|
||||
|
@ -168,7 +275,8 @@ router.beforeEach((to, from, next) => {
|
|||
NProgress.start();
|
||||
const role = localStorage.getItem('ms_username');
|
||||
const permiss = usePermissStore();
|
||||
if (!role && to.path !== '/login') {
|
||||
|
||||
if (!role && to.meta.noAuth !== true) {
|
||||
next('/login');
|
||||
} else if (to.meta.permiss && !permiss.key.includes(to.meta.permiss)) {
|
||||
// 如果没有权限,则进入403
|
||||
|
@ -179,7 +287,7 @@ router.beforeEach((to, from, next) => {
|
|||
});
|
||||
|
||||
router.afterEach(() => {
|
||||
NProgress.done()
|
||||
})
|
||||
NProgress.done();
|
||||
});
|
||||
|
||||
export default router;
|
||||
|
|
|
@ -1,23 +1,58 @@
|
|||
import { defineStore } from 'pinia';
|
||||
|
||||
interface ObjectList {
|
||||
[key: string]: string[];
|
||||
[key: string]: string[];
|
||||
}
|
||||
|
||||
export const usePermissStore = defineStore('permiss', {
|
||||
state: () => {
|
||||
const keys = localStorage.getItem('ms_keys');
|
||||
return {
|
||||
key: keys ? JSON.parse(keys) : <string[]>[],
|
||||
defaultList: <ObjectList>{
|
||||
admin: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16'],
|
||||
user: ['1', '2', '3', '11', '13', '14', '15']
|
||||
}
|
||||
};
|
||||
},
|
||||
actions: {
|
||||
handleSet(val: string[]) {
|
||||
this.key = val;
|
||||
}
|
||||
}
|
||||
state: () => {
|
||||
const keys = localStorage.getItem('ms_keys');
|
||||
return {
|
||||
key: keys ? JSON.parse(keys) : <string[]>[],
|
||||
defaultList: <ObjectList>{
|
||||
admin: [
|
||||
'0',
|
||||
'1',
|
||||
'11',
|
||||
'12',
|
||||
'13',
|
||||
'2',
|
||||
'21',
|
||||
'22',
|
||||
'23',
|
||||
'24',
|
||||
'25',
|
||||
'26',
|
||||
'27',
|
||||
'28',
|
||||
'29',
|
||||
'291',
|
||||
'292',
|
||||
'3',
|
||||
'31',
|
||||
'32',
|
||||
'33',
|
||||
'34',
|
||||
'4',
|
||||
'41',
|
||||
'42',
|
||||
'5',
|
||||
'7',
|
||||
'6',
|
||||
'61',
|
||||
'62',
|
||||
'63',
|
||||
'64',
|
||||
'65',
|
||||
'66',
|
||||
],
|
||||
user: ['0', '1', '11', '12', '13'],
|
||||
},
|
||||
};
|
||||
},
|
||||
actions: {
|
||||
handleSet(val: string[]) {
|
||||
this.key = val;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
@ -3,13 +3,23 @@ import { defineStore } from 'pinia';
|
|||
export const useSidebarStore = defineStore('sidebar', {
|
||||
state: () => {
|
||||
return {
|
||||
collapse: false
|
||||
collapse: false,
|
||||
bgColor: localStorage.getItem('sidebar-bg-color') || '#324157',
|
||||
textColor: localStorage.getItem('sidebar-text-color') || '#bfcbd9'
|
||||
};
|
||||
},
|
||||
getters: {},
|
||||
actions: {
|
||||
handleCollapse() {
|
||||
this.collapse = !this.collapse;
|
||||
},
|
||||
setBgColor(color: string) {
|
||||
this.bgColor = color;
|
||||
localStorage.setItem('sidebar-bg-color', color);
|
||||
},
|
||||
setTextColor(color: string) {
|
||||
this.textColor = color;
|
||||
localStorage.setItem('sidebar-text-color', color);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -6,7 +6,7 @@ interface ListItem {
|
|||
title: string;
|
||||
}
|
||||
|
||||
export const useTagsStore = defineStore('tags', {
|
||||
export const useTabsStore = defineStore('tabs', {
|
||||
state: () => {
|
||||
return {
|
||||
list: <ListItem[]>[]
|
||||
|
@ -21,16 +21,16 @@ export const useTagsStore = defineStore('tags', {
|
|||
}
|
||||
},
|
||||
actions: {
|
||||
delTagsItem(index: number) {
|
||||
delTabsItem(index: number) {
|
||||
this.list.splice(index, 1);
|
||||
},
|
||||
setTagsItem(data: ListItem) {
|
||||
setTabsItem(data: ListItem) {
|
||||
this.list.push(data);
|
||||
},
|
||||
clearTags() {
|
||||
clearTabs() {
|
||||
this.list = [];
|
||||
},
|
||||
closeTagsOther(data: ListItem[]) {
|
||||
closeTabsOther(data: ListItem[]) {
|
||||
this.list = data;
|
||||
},
|
||||
closeCurrentTag(data: any) {
|
|
@ -0,0 +1,58 @@
|
|||
import { mix, setProperty } from '@/utils';
|
||||
import { defineStore } from 'pinia';
|
||||
|
||||
export const useThemeStore = defineStore('theme', {
|
||||
state: () => {
|
||||
return {
|
||||
primary: '',
|
||||
success: '',
|
||||
warning: '',
|
||||
danger: '',
|
||||
info: '',
|
||||
headerBgColor: '#242f42',
|
||||
headerTextColor: '#fff',
|
||||
};
|
||||
},
|
||||
getters: {},
|
||||
actions: {
|
||||
initTheme() {
|
||||
['primary', 'success', 'warning', 'danger', 'info'].forEach((type) => {
|
||||
const color = localStorage.getItem(`theme-${type}`) || '';
|
||||
if (color) {
|
||||
this.setPropertyColor(color, type); // 设置主题色
|
||||
}
|
||||
});
|
||||
const headerBgColor = localStorage.getItem('header-bg-color');
|
||||
headerBgColor && this.setHeaderBgColor(headerBgColor);
|
||||
const headerTextColor = localStorage.getItem('header-text-color');
|
||||
headerTextColor && this.setHeaderTextColor(headerTextColor);
|
||||
},
|
||||
resetTheme() {
|
||||
['primary', 'success', 'warning', 'danger', 'info'].forEach((type) => {
|
||||
this.setPropertyColor('', type); // 重置主题色
|
||||
});
|
||||
},
|
||||
setPropertyColor(color: string, type: string = 'primary') {
|
||||
this[type] = color;
|
||||
setProperty(`--el-color-${type}`, color);
|
||||
localStorage.setItem(`theme-${type}`, color);
|
||||
this.setThemeLight(type);
|
||||
},
|
||||
setThemeLight(type: string = 'primary') {
|
||||
[3, 5, 7, 8, 9].forEach((v) => {
|
||||
setProperty(`--el-color-${type}-light-${v}`, mix('#ffffff', this[type], v / 10));
|
||||
});
|
||||
setProperty(`--el-color-${type}-dark-2`, mix('#ffffff', this[type], 0.2));
|
||||
},
|
||||
setHeaderBgColor(color: string) {
|
||||
this.headerBgColor = color;
|
||||
setProperty('--header-bg-color', color);
|
||||
localStorage.setItem(`header-bg-color`, color);
|
||||
},
|
||||
setHeaderTextColor(color: string) {
|
||||
this.headerTextColor = color;
|
||||
setProperty('--header-text-color', color);
|
||||
localStorage.setItem(`header-text-color`, color);
|
||||
}
|
||||
}
|
||||
});
|
|
@ -0,0 +1,21 @@
|
|||
export interface FormOption {
|
||||
list: FormOptionList[];
|
||||
labelWidth?: number | string;
|
||||
span?: number;
|
||||
|
||||
}
|
||||
|
||||
export interface FormOptionList {
|
||||
prop: string;
|
||||
label: string;
|
||||
type: string;
|
||||
placeholder?: string;
|
||||
disabled?: boolean;
|
||||
opts?: any[];
|
||||
format?: string;
|
||||
activeValue?: any;
|
||||
inactiveValue?: any;
|
||||
activeText?: string;
|
||||
inactiveText?: string;
|
||||
required?: boolean;
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
export interface Menus {
|
||||
id: string;
|
||||
pid?: string;
|
||||
icon?: string;
|
||||
index: string;
|
||||
title: string;
|
||||
permiss?: string;
|
||||
children?: Menus[];
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
|
||||
export interface Role {
|
||||
id: number;
|
||||
name: string;
|
||||
key: string;
|
||||
status: boolean;
|
||||
permiss: string[]
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
export interface TableItem {
|
||||
id: number;
|
||||
name: string;
|
||||
thumb: string;
|
||||
money: number;
|
||||
state: string;
|
||||
date: string;
|
||||
address: string;
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
|
||||
export interface User {
|
||||
id: number;
|
||||
name: string;
|
||||
password: string;
|
||||
email: string;
|
||||
phone: string;
|
||||
role: string;
|
||||
date: string;
|
||||
}
|
||||
|
||||
export interface Register {
|
||||
username: string;
|
||||
password: string;
|
||||
email: string;
|
||||
}
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,14 @@
|
|||
export const setProperty = (prop: string, val: any, dom = document.documentElement) => {
|
||||
dom.style.setProperty(prop, val);
|
||||
};
|
||||
|
||||
export const mix = (color1: string, color2: string, weight: number = 0.5): string => {
|
||||
let color = '#';
|
||||
for (let i = 0; i <= 2; i++) {
|
||||
const c1 = parseInt(color1.substring(1 + i * 2, 3 + i * 2), 16);
|
||||
const c2 = parseInt(color2.substring(1 + i * 2, 3 + i * 2), 16);
|
||||
const c = Math.round(c1 * weight + c2 * (1 - weight));
|
||||
color += c.toString(16).padStart(2, '0');
|
||||
}
|
||||
return color;
|
||||
};
|
|
@ -1,54 +0,0 @@
|
|||
<template>
|
||||
<div class="error-page">
|
||||
<div class="error-code">4<span>0</span>3</div>
|
||||
<div class="error-desc">啊哦~ 你没有权限访问该页面哦</div>
|
||||
<div class="error-handle">
|
||||
<router-link to="/">
|
||||
<el-button type="primary" size="large">返回首页</el-button>
|
||||
</router-link>
|
||||
<el-button class="error-btn" type="primary" size="large" @click="goBack">返回上一页</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="403">
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
const router = useRouter();
|
||||
const goBack = () => {
|
||||
router.go(-2);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.error-page {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #f3f3f3;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.error-code {
|
||||
line-height: 1;
|
||||
font-size: 250px;
|
||||
font-weight: bolder;
|
||||
color: #f02d2d;
|
||||
}
|
||||
.error-code span {
|
||||
color: #00a854;
|
||||
}
|
||||
.error-desc {
|
||||
font-size: 30px;
|
||||
color: #777;
|
||||
}
|
||||
.error-handle {
|
||||
margin-top: 30px;
|
||||
padding-bottom: 200px;
|
||||
}
|
||||
.error-btn {
|
||||
margin-left: 100px;
|
||||
}
|
||||
</style>
|
|
@ -1,54 +0,0 @@
|
|||
<template>
|
||||
<div class="error-page">
|
||||
<div class="error-code">4<span>0</span>4</div>
|
||||
<div class="error-desc">啊哦~ 你所访问的页面不存在</div>
|
||||
<div class="error-handle">
|
||||
<router-link to="/">
|
||||
<el-button type="primary" size="large">返回首页</el-button>
|
||||
</router-link>
|
||||
<el-button class="error-btn" type="primary" size="large" @click="goBack">返回上一页</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="404">
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
const router = useRouter();
|
||||
const goBack = () => {
|
||||
router.go(-1);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.error-page {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #f3f3f3;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.error-code {
|
||||
line-height: 1;
|
||||
font-size: 250px;
|
||||
font-weight: bolder;
|
||||
color: #2d8cf0;
|
||||
}
|
||||
.error-code span {
|
||||
color: #00a854;
|
||||
}
|
||||
.error-desc {
|
||||
font-size: 30px;
|
||||
color: #777;
|
||||
}
|
||||
.error-handle {
|
||||
margin-top: 30px;
|
||||
padding-bottom: 200px;
|
||||
}
|
||||
.error-btn {
|
||||
margin-left: 100px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,87 @@
|
|||
<template>
|
||||
<div class="container">
|
||||
<div class="plugins-tips">
|
||||
vue-echarts:Apache ECharts™ 的 Vue.js 组件。 访问地址:
|
||||
<a href="https://github.com/ecomfe/vue-echarts" target="_blank">vue-echarts</a>
|
||||
</div>
|
||||
<el-card class="mgb20" shadow="hover">
|
||||
<template #header>
|
||||
<div class="content-title">柱状图</div>
|
||||
</template>
|
||||
<v-chart class="schart" :option="barOptions" />
|
||||
</el-card>
|
||||
<el-card class="mgb20" shadow="hover">
|
||||
<template #header>
|
||||
<div class="content-title">折线图</div>
|
||||
</template>
|
||||
<v-chart class="schart" :option="lineOptions" />
|
||||
</el-card>
|
||||
<el-card class="mgb20" shadow="hover">
|
||||
<template #header>
|
||||
<div class="content-title">饼状图</div>
|
||||
</template>
|
||||
<v-chart class="schart" :option="pieOptions" />
|
||||
</el-card>
|
||||
<el-card class="mgb20" shadow="hover">
|
||||
<template #header>
|
||||
<div class="content-title">环形图</div>
|
||||
</template>
|
||||
<v-chart class="schart" :option="ringOptions" />
|
||||
</el-card>
|
||||
<el-card class="mgb20" shadow="hover">
|
||||
<template #header>
|
||||
<div class="content-title">词云图</div>
|
||||
</template>
|
||||
<v-chart class="schart" :option="wordOptions" />
|
||||
</el-card>
|
||||
<el-card class="mgb20" shadow="hover">
|
||||
<template #header>
|
||||
<div class="content-title">地图</div>
|
||||
</template>
|
||||
<v-chart class="schart" :option="mapOptions" />
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="echarts">
|
||||
import { registerMap, use } from 'echarts/core';
|
||||
import { BarChart, LineChart, PieChart, MapChart } from 'echarts/charts';
|
||||
import {
|
||||
GridComponent,
|
||||
TooltipComponent,
|
||||
LegendComponent,
|
||||
TitleComponent,
|
||||
VisualMapComponent,
|
||||
} from 'echarts/components';
|
||||
import { CanvasRenderer } from 'echarts/renderers';
|
||||
import VChart from 'vue-echarts';
|
||||
import 'echarts-wordcloud';
|
||||
import { barOptions, lineOptions, pieOptions, ringOptions, wordOptions, mapOptions } from './options';
|
||||
import chinaMap from '@/utils/china';
|
||||
use([
|
||||
CanvasRenderer,
|
||||
BarChart,
|
||||
GridComponent,
|
||||
LineChart,
|
||||
PieChart,
|
||||
MapChart,
|
||||
TooltipComponent,
|
||||
LegendComponent,
|
||||
TitleComponent,
|
||||
VisualMapComponent,
|
||||
]);
|
||||
registerMap('china', chinaMap);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.schart {
|
||||
width: 100%;
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
.content-title {
|
||||
font-weight: 400;
|
||||
font-size: 22px;
|
||||
color: #1f2f3d;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,345 @@
|
|||
import { graphic } from 'echarts/core';
|
||||
export const barOptions = {
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow',
|
||||
},
|
||||
},
|
||||
color: ['#009688', '#f44336'],
|
||||
series: [
|
||||
{
|
||||
data: [120, 200, 150, 80, 70, 110, 130],
|
||||
type: 'bar',
|
||||
},
|
||||
{
|
||||
data: [180, 230, 190, 120, 110, 230, 235],
|
||||
type: 'bar',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const lineOptions = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
},
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '3%',
|
||||
containLabel: true,
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
},
|
||||
color: ['#009688', '#f44336'],
|
||||
series: [
|
||||
{
|
||||
name: 'Email',
|
||||
type: 'line',
|
||||
stack: 'Total',
|
||||
areaStyle: {},
|
||||
smooth: true,
|
||||
data: [120, 132, 101, 134, 90, 230, 210],
|
||||
},
|
||||
{
|
||||
name: 'Union Ads',
|
||||
type: 'line',
|
||||
stack: 'Total',
|
||||
areaStyle: {},
|
||||
smooth: true,
|
||||
data: [220, 182, 191, 234, 290, 330, 310],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const pieOptions = {
|
||||
title: {
|
||||
text: 'Referer of a Website',
|
||||
subtext: 'Fake Data',
|
||||
left: 'center',
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
},
|
||||
legend: {
|
||||
orient: 'vertical',
|
||||
left: 'left',
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: 'Access From',
|
||||
type: 'pie',
|
||||
radius: '50%',
|
||||
data: [
|
||||
{ value: 1048, name: 'Search Engine' },
|
||||
{ value: 735, name: 'Direct' },
|
||||
{ value: 580, name: 'Email' },
|
||||
{ value: 484, name: 'Union Ads' },
|
||||
{ value: 300, name: 'Video Ads' },
|
||||
],
|
||||
emphasis: {
|
||||
itemStyle: {
|
||||
shadowBlur: 10,
|
||||
shadowOffsetX: 0,
|
||||
shadowColor: 'rgba(0, 0, 0, 0.5)',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const wordOptions = {
|
||||
series: [
|
||||
{
|
||||
type: 'wordCloud',
|
||||
rotationRange: [0, 0],
|
||||
autoSize: {
|
||||
enable: true,
|
||||
minSize: 14,
|
||||
},
|
||||
textStyle: {
|
||||
fontFamily: '微软雅黑,sans-serif',
|
||||
color: function () {
|
||||
return (
|
||||
'rgb(' +
|
||||
[
|
||||
Math.round(Math.random() * 160),
|
||||
Math.round(Math.random() * 160),
|
||||
Math.round(Math.random() * 160),
|
||||
].join(',') +
|
||||
')'
|
||||
);
|
||||
},
|
||||
},
|
||||
data: [
|
||||
{
|
||||
name: 'Vue',
|
||||
value: 10000,
|
||||
},
|
||||
{
|
||||
name: 'React',
|
||||
value: 9000,
|
||||
},
|
||||
{
|
||||
name: '图表',
|
||||
value: 4000,
|
||||
},
|
||||
{
|
||||
name: '产品',
|
||||
value: 7000,
|
||||
},
|
||||
{
|
||||
name: 'vue-manage-system',
|
||||
value: 2000,
|
||||
},
|
||||
{
|
||||
name: 'element-plus',
|
||||
value: 6000,
|
||||
},
|
||||
{
|
||||
name: '管理系统',
|
||||
value: 5000,
|
||||
},
|
||||
{
|
||||
name: '前端',
|
||||
value: 4000,
|
||||
},
|
||||
{
|
||||
name: '测试',
|
||||
value: 3000,
|
||||
},
|
||||
{
|
||||
name: '后端',
|
||||
value: 8000,
|
||||
},
|
||||
{
|
||||
name: '软件开发',
|
||||
value: 6000,
|
||||
},
|
||||
{
|
||||
name: '程序员',
|
||||
value: 4000,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const ringOptions = {
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
},
|
||||
legend: {
|
||||
top: '5%',
|
||||
left: 'center',
|
||||
},
|
||||
|
||||
series: [
|
||||
{
|
||||
name: 'Access From',
|
||||
type: 'pie',
|
||||
radius: ['40%', '70%'],
|
||||
avoidLabelOverlap: false,
|
||||
itemStyle: {
|
||||
borderRadius: 10,
|
||||
borderColor: '#fff',
|
||||
borderWidth: 2,
|
||||
},
|
||||
label: {
|
||||
show: false,
|
||||
position: 'center',
|
||||
},
|
||||
emphasis: {
|
||||
label: {
|
||||
show: true,
|
||||
fontSize: 40,
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
},
|
||||
labelLine: {
|
||||
show: false,
|
||||
},
|
||||
data: [
|
||||
{ value: 1048, name: 'Search Engine' },
|
||||
{ value: 735, name: 'Direct' },
|
||||
{ value: 580, name: 'Email' },
|
||||
{ value: 484, name: 'Union Ads' },
|
||||
{ value: 300, name: 'Video Ads' },
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const dashOpt1 = {
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
},
|
||||
grid: {
|
||||
top: '2%',
|
||||
left: '2%',
|
||||
right: '3%',
|
||||
bottom: '2%',
|
||||
containLabel: true,
|
||||
},
|
||||
color: ['#009688', '#f44336'],
|
||||
series: [
|
||||
{
|
||||
type: 'line',
|
||||
areaStyle: {
|
||||
color: new graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{
|
||||
offset: 0,
|
||||
color: 'rgba(0, 150, 136,0.8)',
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: 'rgba(0, 150, 136,0.2)',
|
||||
},
|
||||
]),
|
||||
},
|
||||
smooth: true,
|
||||
data: [120, 132, 301, 134, 90, 230, 210],
|
||||
},
|
||||
{
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
data: [220, 122, 191, 234, 190, 130, 310],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const dashOpt2 = {
|
||||
legend: {
|
||||
bottom: '1%',
|
||||
left: 'center',
|
||||
},
|
||||
color: ['#3f51b5', '#009688', '#f44336', '#00bcd4', '#1ABC9C'],
|
||||
series: [
|
||||
{
|
||||
type: 'pie',
|
||||
radius: ['40%', '70%'],
|
||||
avoidLabelOverlap: false,
|
||||
itemStyle: {
|
||||
borderRadius: 10,
|
||||
borderColor: '#fff',
|
||||
borderWidth: 2,
|
||||
},
|
||||
data: [
|
||||
{ value: 1048, name: '数码' },
|
||||
{ value: 735, name: '食品' },
|
||||
{ value: 580, name: '母婴' },
|
||||
{ value: 484, name: '家电' },
|
||||
{ value: 300, name: '运动' },
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const mapOptions = {
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
},
|
||||
geo: {
|
||||
map: 'china',
|
||||
roam: false,
|
||||
emphasis: {
|
||||
label: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
visualMap: {
|
||||
show: false,
|
||||
min: 0,
|
||||
max: 100,
|
||||
realtime: false,
|
||||
calculable: false,
|
||||
inRange: {
|
||||
color: ['#d2e0f5', '#71A9FF'],
|
||||
},
|
||||
},
|
||||
series: [
|
||||
{
|
||||
geoIndex: 0,
|
||||
name: '地域分布',
|
||||
type: 'map',
|
||||
coordinateSystem: 'geo',
|
||||
map: 'china',
|
||||
data: [
|
||||
{ name: '北京', value: 100 },
|
||||
{ name: '上海', value: 100 },
|
||||
{ name: '广东', value: 100 },
|
||||
{ name: '浙江', value: 90 },
|
||||
{ name: '江西', value: 80 },
|
||||
{ name: '山东', value: 70 },
|
||||
{ name: '广西', value: 60 },
|
||||
{ name: '河南', value: 50 },
|
||||
{ name: '河南', value: 40 },
|
||||
{ name: '青海', value: 70 },
|
||||
{ name: '河南', value: 30 },
|
||||
{ name: '黑龙江', value: 20 },
|
||||
{ name: '新疆', value: 20 },
|
||||
{ name: '云南', value: 20 },
|
||||
{ name: '甘肃', value: 20 },
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
|
@ -4,26 +4,34 @@
|
|||
vue-schart:vue.js封装sChart.js的图表组件。 访问地址:
|
||||
<a href="https://github.com/lin-xin/vue-schart" target="_blank">vue-schart</a>
|
||||
</div>
|
||||
<div class="schart-box">
|
||||
<div class="content-title">柱状图</div>
|
||||
<el-card class="mgb20" shadow="hover">
|
||||
<template #header>
|
||||
<div class="content-title">柱状图</div>
|
||||
</template>
|
||||
<schart class="schart" canvasId="bar" :options="options1"></schart>
|
||||
</div>
|
||||
<div class="schart-box">
|
||||
<div class="content-title">折线图</div>
|
||||
</el-card>
|
||||
<el-card class="mgb20" shadow="hover">
|
||||
<template #header>
|
||||
<div class="content-title">折线图</div>
|
||||
</template>
|
||||
<schart class="schart" canvasId="line" :options="options2"></schart>
|
||||
</div>
|
||||
<div class="schart-box">
|
||||
<div class="content-title">饼状图</div>
|
||||
</el-card>
|
||||
<el-card class="mgb20" shadow="hover">
|
||||
<template #header>
|
||||
<div class="content-title">饼状图</div>
|
||||
</template>
|
||||
<schart class="schart" canvasId="pie" :options="options3"></schart>
|
||||
</div>
|
||||
<div class="schart-box">
|
||||
<div class="content-title">环形图</div>
|
||||
</el-card>
|
||||
<el-card class="mgb20" shadow="hover">
|
||||
<template #header>
|
||||
<div class="content-title">环形图</div>
|
||||
</template>
|
||||
<schart class="schart" canvasId="ring" :options="options4"></schart>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="basecharts">
|
||||
<script setup lang="ts" name="schart">
|
||||
import Schart from 'vue-schart';
|
||||
|
||||
const options1 = {
|
||||
|
@ -31,12 +39,12 @@ const options1 = {
|
|||
title: {
|
||||
text: '最近一周各品类销售图'
|
||||
},
|
||||
bgColor: '#fbfbfb',
|
||||
colorList: ["#3f51b5", "#009688", "#f44336", "#00bcd4", "#1ABC9C"],
|
||||
labels: ['周一', '周二', '周三', '周四', '周五'],
|
||||
datasets: [
|
||||
{
|
||||
label: '家电',
|
||||
fillColor: 'rgba(241, 49, 74, 0.5)',
|
||||
// fillColor: 'rgba(241, 49, 74, 0.5)',
|
||||
data: [234, 278, 270, 190, 230]
|
||||
},
|
||||
{
|
||||
|
@ -54,7 +62,7 @@ const options2 = {
|
|||
title: {
|
||||
text: '最近几个月各品类销售趋势图'
|
||||
},
|
||||
bgColor: '#fbfbfb',
|
||||
colorList: ["#3f51b5", "#009688", "#f44336", "#00bcd4", "#1ABC9C"],
|
||||
labels: ['6月', '7月', '8月', '9月', '10月'],
|
||||
datasets: [
|
||||
{
|
||||
|
@ -79,7 +87,7 @@ const options3 = {
|
|||
legend: {
|
||||
position: 'left'
|
||||
},
|
||||
bgColor: '#fbfbfb',
|
||||
colorList: ["#2196f3", '#673ab7', "#009688", "#1ABC9C", "#3f51b5", "#f44336", "#00bcd4"],
|
||||
labels: ['T恤', '牛仔裤', '连衣裙', '毛衣', '七分裤', '短裙', '羽绒服'],
|
||||
datasets: [
|
||||
{
|
||||
|
@ -97,7 +105,7 @@ const options4 = {
|
|||
position: 'bottom',
|
||||
bottom: 40
|
||||
},
|
||||
bgColor: '#fbfbfb',
|
||||
colorList: ["#3f51b5", "#009688", "#f44336", "#00bcd4", "#1ABC9C"],
|
||||
labels: ['vue', 'react', 'angular'],
|
||||
datasets: [
|
||||
{
|
||||
|
@ -108,19 +116,13 @@ const options4 = {
|
|||
</script>
|
||||
|
||||
<style scoped>
|
||||
.schart-box {
|
||||
display: inline-block;
|
||||
margin: 20px;
|
||||
}
|
||||
.schart {
|
||||
width: 600px;
|
||||
width: 100%;
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
.content-title {
|
||||
clear: both;
|
||||
font-weight: 400;
|
||||
line-height: 50px;
|
||||
margin: 10px 0;
|
||||
font-size: 22px;
|
||||
color: #1f2f3d;
|
||||
}
|
|
@ -1,301 +1,357 @@
|
|||
<template>
|
||||
<div>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8">
|
||||
<el-card shadow="hover" class="mgb20" style="height: 252px">
|
||||
<div class="user-info">
|
||||
<el-avatar :size="120" :src="imgurl" />
|
||||
<div class="user-info-cont">
|
||||
<div class="user-info-name">{{ name }}</div>
|
||||
<div>{{ role }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="user-info-list">
|
||||
上次登录时间:
|
||||
<span>2022-10-01</span>
|
||||
</div>
|
||||
<div class="user-info-list">
|
||||
上次登录地点:
|
||||
<span>东莞</span>
|
||||
</div>
|
||||
</el-card>
|
||||
<el-card shadow="hover" style="height: 252px">
|
||||
<template #header>
|
||||
<div class="clearfix">
|
||||
<span>语言详情</span>
|
||||
</div>
|
||||
</template>
|
||||
Vue
|
||||
<el-progress :percentage="79.4" color="#42b983"></el-progress>
|
||||
TypeScript
|
||||
<el-progress :percentage="14" color="#f1e05a"></el-progress>
|
||||
CSS
|
||||
<el-progress :percentage="5.6"></el-progress>
|
||||
HTML
|
||||
<el-progress :percentage="1" color="#f56c6c"></el-progress>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="16">
|
||||
<el-row :gutter="20" class="mgb20">
|
||||
<el-col :span="8">
|
||||
<el-card shadow="hover" :body-style="{ padding: '0px' }">
|
||||
<div class="grid-content grid-con-1">
|
||||
<el-icon class="grid-con-icon"><User /></el-icon>
|
||||
<div class="grid-cont-right">
|
||||
<div class="grid-num">1234</div>
|
||||
<div>用户访问量</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-card shadow="hover" :body-style="{ padding: '0px' }">
|
||||
<div class="grid-content grid-con-2">
|
||||
<el-icon class="grid-con-icon"><ChatDotRound /></el-icon>
|
||||
<div class="grid-cont-right">
|
||||
<div class="grid-num">321</div>
|
||||
<div>系统消息</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-card shadow="hover" :body-style="{ padding: '0px' }">
|
||||
<div class="grid-content grid-con-3">
|
||||
<el-icon class="grid-con-icon"><Goods /></el-icon>
|
||||
<div class="grid-cont-right">
|
||||
<div class="grid-num">5000</div>
|
||||
<div>商品数量</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-card shadow="hover" style="height: 403px">
|
||||
<template #header>
|
||||
<div class="clearfix">
|
||||
<span>待办事项</span>
|
||||
<el-button style="float: right; padding: 3px 0" text>添加</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<div>
|
||||
<el-row :gutter="20" class="mgb20">
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" body-class="card-body">
|
||||
<el-icon class="card-icon bg1">
|
||||
<User />
|
||||
</el-icon>
|
||||
<div class="card-content">
|
||||
<countup class="card-num color1" :end="6666" />
|
||||
<div>用户访问量</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" body-class="card-body">
|
||||
<el-icon class="card-icon bg2">
|
||||
<ChatDotRound />
|
||||
</el-icon>
|
||||
<div class="card-content">
|
||||
<countup class="card-num color2" :end="168" />
|
||||
<div>系统消息</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" body-class="card-body">
|
||||
<el-icon class="card-icon bg3">
|
||||
<Goods />
|
||||
</el-icon>
|
||||
<div class="card-content">
|
||||
<countup class="card-num color3" :end="8888" />
|
||||
<div>商品数量</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" body-class="card-body">
|
||||
<el-icon class="card-icon bg4">
|
||||
<ShoppingCartFull />
|
||||
</el-icon>
|
||||
<div class="card-content">
|
||||
<countup class="card-num color4" :end="568" />
|
||||
<div>今日订单量</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-table :show-header="false" :data="todoList" style="width: 100%">
|
||||
<el-table-column width="40">
|
||||
<template #default="scope">
|
||||
<el-checkbox v-model="scope.row.status"></el-checkbox>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column>
|
||||
<template #default="scope">
|
||||
<div
|
||||
class="todo-item"
|
||||
:class="{
|
||||
'todo-item-del': scope.row.status
|
||||
}"
|
||||
>
|
||||
{{ scope.row.title }}
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-card shadow="hover">
|
||||
<schart ref="bar" class="schart" canvasId="bar" :options="options"></schart>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-card shadow="hover">
|
||||
<schart ref="line" class="schart" canvasId="line" :options="options2"></schart>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
<el-row :gutter="20" class="mgb20">
|
||||
<el-col :span="18">
|
||||
<el-card shadow="hover">
|
||||
<div class="card-header">
|
||||
<p class="card-header-title">订单动态</p>
|
||||
<p class="card-header-desc">最近一周订单状态,包括订单成交量和订单退货量</p>
|
||||
</div>
|
||||
<v-chart class="chart" :option="dashOpt1" />
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover">
|
||||
<div class="card-header">
|
||||
<p class="card-header-title">品类分布</p>
|
||||
<p class="card-header-desc">最近一个月销售商品的品类情况</p>
|
||||
</div>
|
||||
<v-chart class="chart" :option="dashOpt2" />
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="7">
|
||||
<el-card shadow="hover" :body-style="{ height: '400px' }">
|
||||
<div class="card-header">
|
||||
<p class="card-header-title">时间线</p>
|
||||
<p class="card-header-desc">最新的销售动态和活动信息</p>
|
||||
</div>
|
||||
<el-timeline>
|
||||
<el-timeline-item v-for="(activity, index) in activities" :key="index" :color="activity.color">
|
||||
<div class="timeline-item">
|
||||
<div>
|
||||
<p>{{ activity.content }}</p>
|
||||
<p class="timeline-desc">{{ activity.description }}</p>
|
||||
</div>
|
||||
<div class="timeline-time">{{ activity.timestamp }}</div>
|
||||
</div>
|
||||
</el-timeline-item>
|
||||
</el-timeline>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="10">
|
||||
<el-card shadow="hover" :body-style="{ height: '400px' }">
|
||||
<div class="card-header">
|
||||
<p class="card-header-title">渠道统计</p>
|
||||
<p class="card-header-desc">最近一个月的订单来源统计</p>
|
||||
</div>
|
||||
<v-chart class="map-chart" :option="mapOptions" />
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="7">
|
||||
<el-card shadow="hover" :body-style="{ height: '400px' }">
|
||||
<div class="card-header">
|
||||
<p class="card-header-title">排行榜</p>
|
||||
<p class="card-header-desc">销售商品的热门榜单Top5</p>
|
||||
</div>
|
||||
<div>
|
||||
<div class="rank-item" v-for="(rank, index) in ranks">
|
||||
<div class="rank-item-avatar">{{ index + 1 }}</div>
|
||||
<div class="rank-item-content">
|
||||
<div class="rank-item-top">
|
||||
<div class="rank-item-title">{{ rank.title }}</div>
|
||||
<div class="rank-item-desc">销量:{{ rank.value }}</div>
|
||||
</div>
|
||||
<el-progress
|
||||
:show-text="false"
|
||||
striped
|
||||
:stroke-width="10"
|
||||
:percentage="rank.percent"
|
||||
:color="rank.color"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="dashboard">
|
||||
import Schart from 'vue-schart';
|
||||
import { reactive } from 'vue';
|
||||
import imgurl from '../assets/img/img.jpg';
|
||||
|
||||
const name = localStorage.getItem('ms_username');
|
||||
const role: string = name === 'admin' ? '超级管理员' : '普通用户';
|
||||
|
||||
const options = {
|
||||
type: 'bar',
|
||||
title: {
|
||||
text: '最近一周各品类销售图'
|
||||
},
|
||||
xRorate: 25,
|
||||
labels: ['周一', '周二', '周三', '周四', '周五'],
|
||||
datasets: [
|
||||
{
|
||||
label: '家电',
|
||||
data: [234, 278, 270, 190, 230]
|
||||
},
|
||||
{
|
||||
label: '百货',
|
||||
data: [164, 178, 190, 135, 160]
|
||||
},
|
||||
{
|
||||
label: '食品',
|
||||
data: [144, 198, 150, 235, 120]
|
||||
}
|
||||
]
|
||||
};
|
||||
const options2 = {
|
||||
type: 'line',
|
||||
title: {
|
||||
text: '最近几个月各品类销售趋势图'
|
||||
},
|
||||
labels: ['6月', '7月', '8月', '9月', '10月'],
|
||||
datasets: [
|
||||
{
|
||||
label: '家电',
|
||||
data: [234, 278, 270, 190, 230]
|
||||
},
|
||||
{
|
||||
label: '百货',
|
||||
data: [164, 178, 150, 135, 160]
|
||||
},
|
||||
{
|
||||
label: '食品',
|
||||
data: [74, 118, 200, 235, 90]
|
||||
}
|
||||
]
|
||||
};
|
||||
const todoList = reactive([
|
||||
{
|
||||
title: '今天要修复100个bug',
|
||||
status: false
|
||||
},
|
||||
{
|
||||
title: '今天要修复100个bug',
|
||||
status: false
|
||||
},
|
||||
{
|
||||
title: '今天要写100行代码加几个bug吧',
|
||||
status: false
|
||||
},
|
||||
{
|
||||
title: '今天要修复100个bug',
|
||||
status: false
|
||||
},
|
||||
{
|
||||
title: '今天要修复100个bug',
|
||||
status: true
|
||||
},
|
||||
{
|
||||
title: '今天要写100行代码加几个bug吧',
|
||||
status: true
|
||||
}
|
||||
import countup from '@/components/countup.vue';
|
||||
import { use, registerMap } from 'echarts/core';
|
||||
import { BarChart, LineChart, PieChart, MapChart } from 'echarts/charts';
|
||||
import {
|
||||
GridComponent,
|
||||
TooltipComponent,
|
||||
LegendComponent,
|
||||
TitleComponent,
|
||||
VisualMapComponent,
|
||||
} from 'echarts/components';
|
||||
import { CanvasRenderer } from 'echarts/renderers';
|
||||
import VChart from 'vue-echarts';
|
||||
import { dashOpt1, dashOpt2, mapOptions } from './chart/options';
|
||||
import chinaMap from '@/utils/china';
|
||||
use([
|
||||
CanvasRenderer,
|
||||
BarChart,
|
||||
GridComponent,
|
||||
LineChart,
|
||||
PieChart,
|
||||
TooltipComponent,
|
||||
LegendComponent,
|
||||
TitleComponent,
|
||||
VisualMapComponent,
|
||||
MapChart,
|
||||
]);
|
||||
registerMap('china', chinaMap);
|
||||
const activities = [
|
||||
{
|
||||
content: '收藏商品',
|
||||
description: 'xxx收藏了你的商品,就是不买',
|
||||
timestamp: '30分钟前',
|
||||
color: '#00bcd4',
|
||||
},
|
||||
{
|
||||
content: '用户评价',
|
||||
description: 'xxx给了某某商品一个差评,吐血啊',
|
||||
timestamp: '55分钟前',
|
||||
color: '#1ABC9C',
|
||||
},
|
||||
{
|
||||
content: '订单提交',
|
||||
description: 'xxx提交了订单,快去收钱吧',
|
||||
timestamp: '1小时前',
|
||||
color: '#3f51b5',
|
||||
},
|
||||
{
|
||||
content: '退款申请',
|
||||
description: 'xxx申请了仅退款,又要亏钱了',
|
||||
timestamp: '15小时前',
|
||||
color: '#f44336',
|
||||
},
|
||||
{
|
||||
content: '商品上架',
|
||||
description: '运营专员瞒着你上架了一辆飞机',
|
||||
timestamp: '1天前',
|
||||
color: '#009688',
|
||||
},
|
||||
];
|
||||
|
||||
const ranks = [
|
||||
{
|
||||
title: '手机',
|
||||
value: 10000,
|
||||
percent: 80,
|
||||
color: '#f25e43',
|
||||
},
|
||||
{
|
||||
title: '电脑',
|
||||
value: 8000,
|
||||
percent: 70,
|
||||
color: '#00bcd4',
|
||||
},
|
||||
{
|
||||
title: '相机',
|
||||
value: 6000,
|
||||
percent: 60,
|
||||
color: '#64d572',
|
||||
},
|
||||
{
|
||||
title: '衣服',
|
||||
value: 5000,
|
||||
percent: 55,
|
||||
color: '#e9a745',
|
||||
},
|
||||
{
|
||||
title: '书籍',
|
||||
value: 4000,
|
||||
percent: 50,
|
||||
color: '#009688',
|
||||
},
|
||||
];
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.el-row {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.grid-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.grid-cont-right {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.grid-num {
|
||||
font-size: 30px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.grid-con-icon {
|
||||
font-size: 50px;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
text-align: center;
|
||||
line-height: 100px;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.grid-con-1 .grid-con-icon {
|
||||
background: rgb(45, 140, 240);
|
||||
}
|
||||
|
||||
.grid-con-1 .grid-num {
|
||||
color: rgb(45, 140, 240);
|
||||
}
|
||||
|
||||
.grid-con-2 .grid-con-icon {
|
||||
background: rgb(100, 213, 114);
|
||||
}
|
||||
|
||||
.grid-con-2 .grid-num {
|
||||
color: rgb(100, 213, 114);
|
||||
}
|
||||
|
||||
.grid-con-3 .grid-con-icon {
|
||||
background: rgb(242, 94, 67);
|
||||
}
|
||||
|
||||
.grid-con-3 .grid-num {
|
||||
color: rgb(242, 94, 67);
|
||||
}
|
||||
|
||||
.user-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-bottom: 20px;
|
||||
border-bottom: 2px solid #ccc;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.user-info-cont {
|
||||
padding-left: 50px;
|
||||
flex: 1;
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.user-info-cont div:first-child {
|
||||
font-size: 30px;
|
||||
color: #222;
|
||||
}
|
||||
|
||||
.user-info-list {
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
line-height: 25px;
|
||||
}
|
||||
|
||||
.user-info-list span {
|
||||
margin-left: 70px;
|
||||
}
|
||||
|
||||
.mgb20 {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.todo-item {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.todo-item-del {
|
||||
text-decoration: line-through;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.schart {
|
||||
width: 100%;
|
||||
height: 300px;
|
||||
<style>
|
||||
.card-body {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 100px;
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
<style scoped>
|
||||
.card-content {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.card-num {
|
||||
font-size: 30px;
|
||||
}
|
||||
|
||||
.card-icon {
|
||||
font-size: 50px;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
text-align: center;
|
||||
line-height: 100px;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.bg1 {
|
||||
background: #2d8cf0;
|
||||
}
|
||||
|
||||
.bg2 {
|
||||
background: #64d572;
|
||||
}
|
||||
|
||||
.bg3 {
|
||||
background: #f25e43;
|
||||
}
|
||||
|
||||
.bg4 {
|
||||
background: #e9a745;
|
||||
}
|
||||
|
||||
.color1 {
|
||||
color: #2d8cf0;
|
||||
}
|
||||
|
||||
.color2 {
|
||||
color: #64d572;
|
||||
}
|
||||
|
||||
.color3 {
|
||||
color: #f25e43;
|
||||
}
|
||||
|
||||
.color4 {
|
||||
color: #e9a745;
|
||||
}
|
||||
|
||||
.chart {
|
||||
width: 100%;
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
padding-left: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.card-header-title {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.card-header-desc {
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.timeline-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 16px;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.timeline-time,
|
||||
.timeline-desc {
|
||||
font-size: 12px;
|
||||
color: #787878;
|
||||
}
|
||||
|
||||
.rank-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.rank-item-avatar {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
background: #f2f2f2;
|
||||
text-align: center;
|
||||
line-height: 40px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.rank-item-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.rank-item-top {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
color: #343434;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.rank-item-desc {
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
}
|
||||
.map-chart {
|
||||
width: 100%;
|
||||
height: 350px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
<template>
|
||||
<div class="container">
|
||||
<div class="plugins-tips">
|
||||
如果该框架对你有帮助,那就请作者喝杯饮料吧!<el-icon><ColdDrink /></el-icon> 加微信号linxin_20探讨问题。
|
||||
</div>
|
||||
<div>
|
||||
<img src="https://lin-xin.gitee.io/images/weixin.jpg" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="donate"></script>
|
||||
|
||||
<style></style>
|
|
@ -0,0 +1,82 @@
|
|||
<template>
|
||||
<div class="container">
|
||||
<el-calendar v-model="value">
|
||||
<template #date-cell="{ data }">
|
||||
<div>{{ data.date.getDate() }}</div>
|
||||
<div class="notes-container" v-if="notes[data.day.toString()]">
|
||||
<div class="notes" v-for="note in notes[data.day.toString()]">
|
||||
<span :class="note.status === 1 ? 'text-success' : 'text-danger'"></span>
|
||||
<div class="note-title">{{ note.title }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-calendar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
|
||||
const today = new Date();
|
||||
const yesterday = new Date(today.getTime() - 24 * 60 * 60 * 1000);
|
||||
const value = ref(today);
|
||||
|
||||
const todayDate = today.toISOString().slice(0, 10);
|
||||
const yesterdayDate = yesterday.toISOString().slice(0, 10);
|
||||
|
||||
const notes: any = {
|
||||
[todayDate]: [
|
||||
{ title: '吃饭', status: 1 },
|
||||
{ title: '睡觉', status: 0 },
|
||||
{ title: '吃饭', status: 1 },
|
||||
{ title: '睡觉', status: 0 },
|
||||
{ title: '吃饭', status: 1 },
|
||||
{ title: '睡觉', status: 0 },
|
||||
],
|
||||
[yesterdayDate]: [{ title: '参加会议', status: 0 }],
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.notes-container {
|
||||
height: 60px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.notes-container::-webkit-scrollbar {
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.notes {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.notes:hover {
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
.note-title {
|
||||
flex: 1;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.notes span {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.text-success {
|
||||
background-color: #5cb85c;
|
||||
}
|
||||
|
||||
.text-danger {
|
||||
background-color: #d9534f;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,66 @@
|
|||
<template>
|
||||
<div>
|
||||
<el-card class="mgb20">
|
||||
<template #header>基础用法</template>
|
||||
<el-carousel height="400px">
|
||||
<el-carousel-item v-for="item in 4" :key="item">
|
||||
<h3>{{ item }}</h3>
|
||||
</el-carousel-item>
|
||||
</el-carousel>
|
||||
</el-card>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-card class="mgb20">
|
||||
<template #header>轮播图</template>
|
||||
<el-carousel height="300px">
|
||||
<el-carousel-item v-for="item in imgs" :key="item">
|
||||
<el-image class="carousel-img" :src="item" fit="cover" />
|
||||
</el-carousel-item>
|
||||
</el-carousel>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-card class="mgb20">
|
||||
<template #header>卡片模式</template>
|
||||
<el-carousel height="300px" type="card">
|
||||
<el-carousel-item v-for="item in imgs" :key="item">
|
||||
<el-image class="carousel-img" :src="item" fit="cover" />
|
||||
</el-carousel-item>
|
||||
</el-carousel>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
const imgs = [
|
||||
'https://cdn.pixabay.com/photo/2017/08/07/08/23/sea-2601374_640.jpg',
|
||||
'https://cdn.pixabay.com/photo/2020/02/11/10/24/lake-4839058_640.jpg',
|
||||
'https://cdn.pixabay.com/photo/2024/02/21/08/06/coast-8587004_640.jpg',
|
||||
'https://cdn.pixabay.com/photo/2023/07/29/10/21/grasshopper-8156626_640.jpg',
|
||||
];
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.el-carousel__item h3 {
|
||||
color: #475669;
|
||||
line-height: 400px;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.el-carousel__item:nth-child(2n) {
|
||||
background-color: #99a9bf;
|
||||
}
|
||||
|
||||
.el-carousel__item:nth-child(2n + 1) {
|
||||
background-color: #d3dce6;
|
||||
}
|
||||
|
||||
.carousel-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,189 @@
|
|||
<template>
|
||||
<div class="container">
|
||||
<el-radio-group class="mgb20" v-model="labelPosition">
|
||||
<el-radio-button value="left">Left</el-radio-button>
|
||||
<el-radio-button value="right">Right</el-radio-button>
|
||||
<el-radio-button value="top">Top</el-radio-button>
|
||||
</el-radio-group>
|
||||
<el-form ref="formRef" :rules="rules" :model="form" label-width="120px" :label-position="labelPosition">
|
||||
<el-row :gutter="50">
|
||||
<el-col :span="10">
|
||||
<el-form-item label="文本框" prop="name">
|
||||
<el-input v-model="form.name"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="数字框" prop="num">
|
||||
<el-input-number v-model="form.num" :min="1" :max="10" />
|
||||
</el-form-item>
|
||||
<el-form-item label="日期选择" prop="date">
|
||||
<el-date-picker type="date" placeholder="选择日期" v-model="form.date"></el-date-picker>
|
||||
</el-form-item>
|
||||
<el-form-item label="时间选择" prop="time">
|
||||
<el-time-picker placeholder="选择时间" v-model="form.time">
|
||||
</el-time-picker>
|
||||
</el-form-item>
|
||||
<el-form-item label="选择器" prop="region">
|
||||
<el-select v-model="form.region" placeholder="请选择">
|
||||
<el-option key="小明" label="小明" value="小明"></el-option>
|
||||
<el-option key="小红" label="小红" value="小红"></el-option>
|
||||
<el-option key="小白" label="小白" value="小白"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="城市级联" prop="options">
|
||||
<el-cascader :options="options" v-model="form.options"></el-cascader>
|
||||
</el-form-item>
|
||||
<el-form-item label="文本框" prop="desc">
|
||||
<el-input type="textarea" rows="5" v-model="form.desc"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="评分" prop="rate">
|
||||
<el-rate v-model="form.rate" allow-half />
|
||||
</el-form-item>
|
||||
<el-form-item label="滑块" prop="num">
|
||||
<el-slider v-model="form.num" :step="1" show-stops :max="10" />
|
||||
</el-form-item>
|
||||
<el-form-item label="开关" prop="delivery">
|
||||
<el-switch v-model="form.delivery"></el-switch>
|
||||
</el-form-item>
|
||||
<el-form-item label="颜色选择" prop="color">
|
||||
<el-color-picker v-model="form.color" />
|
||||
</el-form-item>
|
||||
<el-form-item label="多选框" prop="type">
|
||||
<el-checkbox-group v-model="form.type">
|
||||
<el-checkbox label="小明" value="小明" name="type"></el-checkbox>
|
||||
<el-checkbox label="小红" value="小红" name="type"></el-checkbox>
|
||||
<el-checkbox label="小白" value="小白" name="type"></el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="单选框" prop="resource">
|
||||
<el-radio-group v-model="form.resource">
|
||||
<el-radio label="小明" value="小明"></el-radio>
|
||||
<el-radio label="小红" value="小红"></el-radio>
|
||||
<el-radio label="小白" value="小白"></el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="穿梭框" prop="transfer">
|
||||
<el-transfer v-model="form.transfer" :data="transferData" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="24">
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="onSubmit(formRef)">表单提交</el-button>
|
||||
<el-button @click="onReset(formRef)">重置表单</el-button>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="forms">
|
||||
import { reactive, ref } from 'vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import type { FormInstance, FormProps, FormRules } from 'element-plus';
|
||||
const labelPosition = ref<FormProps['labelPosition']>('right')
|
||||
const options = [
|
||||
{
|
||||
value: 'guangdong',
|
||||
label: '广东省',
|
||||
children: [
|
||||
{
|
||||
value: 'guangzhou',
|
||||
label: '广州市',
|
||||
children: [
|
||||
{
|
||||
value: 'tianhe',
|
||||
label: '天河区',
|
||||
},
|
||||
{
|
||||
value: 'haizhu',
|
||||
label: '海珠区',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'dongguan',
|
||||
label: '东莞市',
|
||||
children: [
|
||||
{
|
||||
value: 'changan',
|
||||
label: '长安镇',
|
||||
},
|
||||
{
|
||||
value: 'humen',
|
||||
label: '虎门镇',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'hunan',
|
||||
label: '湖南省',
|
||||
children: [
|
||||
{
|
||||
value: 'changsha',
|
||||
label: '长沙市',
|
||||
children: [
|
||||
{
|
||||
value: 'yuelu',
|
||||
label: '岳麓区',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
const rules: FormRules = {
|
||||
name: [{ required: true, message: '请输入表单名称', trigger: 'blur' }],
|
||||
};
|
||||
const formRef = ref<FormInstance>();
|
||||
const form = reactive({
|
||||
name: '',
|
||||
region: '',
|
||||
date: '',
|
||||
time: '',
|
||||
delivery: true,
|
||||
type: ['小明'],
|
||||
resource: '小红',
|
||||
desc: '',
|
||||
options: [],
|
||||
color: '',
|
||||
num: 1,
|
||||
rate: 0,
|
||||
transfer: [],
|
||||
|
||||
});
|
||||
const generateData = () => {
|
||||
const data = []
|
||||
for (let i = 1; i <= 15; i++) {
|
||||
data.push({
|
||||
key: i,
|
||||
label: `Option ${i}`,
|
||||
disabled: i % 4 === 0,
|
||||
})
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
const transferData = ref(generateData())
|
||||
// 提交
|
||||
const onSubmit = (formEl: FormInstance | undefined) => {
|
||||
// 表单校验
|
||||
if (!formEl) return;
|
||||
formEl.validate((valid) => {
|
||||
if (valid) {
|
||||
console.log(form);
|
||||
ElMessage.success('提交成功!');
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
};
|
||||
// 重置
|
||||
const onReset = (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return;
|
||||
formEl.resetFields();
|
||||
};
|
||||
</script>
|
|
@ -0,0 +1,340 @@
|
|||
<template>
|
||||
<div>
|
||||
|
||||
|
||||
<el-card class="mgb20" shadow="hover">
|
||||
<template #header>基础用法</template>
|
||||
<el-row>
|
||||
<el-col :span="6" style="text-align: center">
|
||||
<el-statistic title="Daily active users" :value="268500" />
|
||||
</el-col>
|
||||
<el-col :span="6" style="text-align: center">
|
||||
<el-statistic :value="138">
|
||||
<template #title>
|
||||
<div style="display: inline-flex; align-items: center">
|
||||
Ratio of men to women
|
||||
</div>
|
||||
</template>
|
||||
<template #suffix>/100</template>
|
||||
</el-statistic>
|
||||
</el-col>
|
||||
<el-col :span="6" style="text-align: center">
|
||||
<el-statistic title="数字滚动" :value="outputValue" />
|
||||
</el-col>
|
||||
<el-col :span="6" style="text-align: center">
|
||||
<el-countdown title="倒计时" :value="value" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
|
||||
<el-card class="mgb20" shadow="hover">
|
||||
<template #header>CountUp.js</template>
|
||||
<div class="plugins-tips">
|
||||
countup.js:用于快速创建以更有趣的方式显示数字数据的动画。 访问地址:
|
||||
<a href="https://github.com/inorganik/countUp.js" target="_blank">countUp.js</a>
|
||||
</div>
|
||||
<el-row>
|
||||
<el-col :span="8" style="text-align: center">
|
||||
<p>基础用法</p>
|
||||
<countup class="countup" :end="6666" />
|
||||
</el-col>
|
||||
<el-col :span="8" style="text-align: center">
|
||||
<p>具体配置</p>
|
||||
<countup class="countup" :end="8888.5" :options="options" />
|
||||
</el-col>
|
||||
<el-col :span="8" style="text-align: center">
|
||||
<p>更新数值</p>
|
||||
<countup class="countup" :end="value1" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
<el-card class="mgb20" shadow="never">
|
||||
<template #header>统计卡片</template>
|
||||
|
||||
<el-row :gutter="20" class="mgb20">
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" body-class="card-body">
|
||||
<el-icon class="card-icon color1">
|
||||
<User />
|
||||
</el-icon>
|
||||
<div class="card-content text-right">
|
||||
<el-statistic title="日活跃用户量" :value="268500" />
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" body-class="card-body">
|
||||
<el-icon class="card-icon color2">
|
||||
<ChatDotRound />
|
||||
</el-icon>
|
||||
<div class="card-content text-right">
|
||||
<el-statistic title="系统消息" :value="16800" />
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" body-class="card-body">
|
||||
<el-icon class="card-icon color3">
|
||||
<Goods />
|
||||
</el-icon>
|
||||
<div class="card-content text-right">
|
||||
<el-statistic title="商品数量" :value="8888" />
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" body-class="card-body">
|
||||
<el-icon class="card-icon color4">
|
||||
<ShoppingCartFull />
|
||||
</el-icon>
|
||||
<div class="card-content text-right">
|
||||
<el-statistic title="今日订单量" :value="56888" />
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20" class="mgb20">
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" body-class="card-body">
|
||||
<div class="card-content text-left">
|
||||
<el-statistic :value-style="{ color: '#2d8cf0' }" title="日活跃用户量" :value="268500" />
|
||||
</div>
|
||||
<el-icon class="card-icon color1">
|
||||
<User />
|
||||
</el-icon>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" body-class="card-body">
|
||||
<div class="card-content text-left">
|
||||
<el-statistic :value-style="{ color: '#64d572' }" title="系统消息" :value="16800" />
|
||||
</div>
|
||||
<el-icon class="card-icon color2">
|
||||
<ChatDotRound />
|
||||
</el-icon>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" body-class="card-body">
|
||||
<div class="card-content text-left">
|
||||
<el-statistic :value-style="{ color: '#f25e43' }" title="商品数量" :value="8888" />
|
||||
</div>
|
||||
<el-icon class="card-icon color3">
|
||||
<Goods />
|
||||
</el-icon>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" body-class="card-body">
|
||||
<div class="card-content text-left">
|
||||
<el-statistic :value-style="{ color: '#e9a745' }" title="今日订单量" :value="56888" />
|
||||
</div>
|
||||
<el-icon class="card-icon color4">
|
||||
<ShoppingCartFull />
|
||||
</el-icon>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20" class="mgb20">
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" body-class="card-body">
|
||||
<el-icon class="card-icon bg1">
|
||||
<User />
|
||||
</el-icon>
|
||||
<div class="card-content">
|
||||
<countup class="card-num color1" :end="6666" />
|
||||
<div>用户访问量</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" body-class="card-body">
|
||||
<el-icon class="card-icon bg2">
|
||||
<ChatDotRound />
|
||||
</el-icon>
|
||||
<div class="card-content">
|
||||
<countup class="card-num color2" :end="168" />
|
||||
<div>系统消息</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" body-class="card-body">
|
||||
<el-icon class="card-icon bg3">
|
||||
<Goods />
|
||||
</el-icon>
|
||||
<div class="card-content">
|
||||
<countup class="card-num color3" :end="8888" />
|
||||
<div>商品数量</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" body-class="card-body">
|
||||
<el-icon class="card-icon bg4">
|
||||
<ShoppingCartFull />
|
||||
</el-icon>
|
||||
<div class="card-content">
|
||||
<countup class="card-num color4" :end="568" />
|
||||
<div>今日订单量</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20" class="mgb20">
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" body-class="card-body bg1">
|
||||
<el-icon class="card-icon ">
|
||||
<User />
|
||||
</el-icon>
|
||||
<div class="card-content color0">
|
||||
<countup class="card-num" :end="6666" />
|
||||
<div>用户访问量</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" body-class="card-body bg2">
|
||||
<el-icon class="card-icon">
|
||||
<ChatDotRound />
|
||||
</el-icon>
|
||||
<div class="card-content color0">
|
||||
<countup class="card-num" :end="168" />
|
||||
<div>系统消息</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" body-class="card-body bg3">
|
||||
<el-icon class="card-icon">
|
||||
<Goods />
|
||||
</el-icon>
|
||||
<div class="card-content color0">
|
||||
<countup class="card-num " :end="8888" />
|
||||
<div>商品数量</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" body-class="card-body bg4">
|
||||
<el-icon class="card-icon">
|
||||
<ShoppingCartFull />
|
||||
</el-icon>
|
||||
<div class="card-content color0">
|
||||
<countup class="card-num " :end="568" />
|
||||
<div>今日订单量</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
import { useTransition } from '@vueuse/core'
|
||||
import countup from '@/components/countup.vue';
|
||||
|
||||
const source = ref(0)
|
||||
const outputValue = useTransition(source, {
|
||||
duration: 1500,
|
||||
})
|
||||
source.value = 172000
|
||||
|
||||
const value = ref(Date.now() + 1000 * 60 * 60 * 7)
|
||||
const value1 = ref(1000);
|
||||
setTimeout(() => {
|
||||
value1.value = 8000;
|
||||
}, 5000);
|
||||
const options = {
|
||||
startVal: 1000,
|
||||
decimalPlaces: 2,
|
||||
duration: 5,
|
||||
useGrouping: false,
|
||||
prefix: '$',
|
||||
separator: ',',
|
||||
decimal: '.',
|
||||
suffix: '',
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.card-body {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 100px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.bg1 {
|
||||
background: #2d8cf0;
|
||||
}
|
||||
|
||||
.bg2 {
|
||||
background: #64d572;
|
||||
}
|
||||
|
||||
.bg3 {
|
||||
background: #f25e43;
|
||||
}
|
||||
|
||||
.bg4 {
|
||||
background: #e9a745;
|
||||
}
|
||||
</style>
|
||||
<style scoped>
|
||||
.countup {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.card-content {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.card-num {
|
||||
font-size: 30px;
|
||||
}
|
||||
|
||||
.card-icon {
|
||||
font-size: 50px;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
text-align: center;
|
||||
line-height: 100px;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
|
||||
.color0 {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.color1 {
|
||||
color: #2d8cf0;
|
||||
}
|
||||
|
||||
.color2 {
|
||||
color: #64d572;
|
||||
}
|
||||
|
||||
.color3 {
|
||||
color: #f25e43;
|
||||
}
|
||||
|
||||
.color4 {
|
||||
color: #e9a745;
|
||||
}
|
||||
|
||||
.text-right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.text-left {
|
||||
text-align: left;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,61 @@
|
|||
<template>
|
||||
<div class="container">
|
||||
<div class="step-div" v-if="step === 0">
|
||||
<p>输入注册时的邮箱,我们会发送验证码到您的邮箱</p>
|
||||
<el-input placeholder="请输入邮箱"></el-input>
|
||||
<el-button class="step-btn" type="primary" @click="step++">下一步</el-button>
|
||||
</div>
|
||||
<div class="step-div" v-else-if="step === 1">
|
||||
<p>验证码已发送至您的邮箱,请输入验证码</p>
|
||||
<el-input placeholder="请输入验证码"></el-input>
|
||||
<el-button class="step-btn" type="primary" @click="step++">下一步</el-button>
|
||||
</div>
|
||||
|
||||
<div class="step-div" v-else-if="step === 2">
|
||||
<p>请输入6位以上密码</p>
|
||||
<el-input placeholder="请输入新密码"></el-input>
|
||||
<el-button class="step-btn" type="primary" @click="step++">保存</el-button>
|
||||
</div>
|
||||
<div v-else>
|
||||
<el-result icon="success" title="保存成功" sub-title="请退出后重新登录"></el-result>
|
||||
</div>
|
||||
<el-steps class="step-style" :active="step" align-center finish-status="success">
|
||||
<el-step title="Step 1" description="填写邮箱" />
|
||||
<el-step title="Step 2" description="填写验证码" />
|
||||
<el-step title="Step 3" description="修改密码" />
|
||||
</el-steps>
|
||||
<el-steps class="step-style" :active="step" finish-status="success" simple>
|
||||
<el-step title="填写邮箱" />
|
||||
<el-step title="填写验证码" />
|
||||
<el-step title="修改密码" />
|
||||
</el-steps>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
const step = ref(0)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.step-div {
|
||||
max-width: 500px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.step-div p {
|
||||
margin-bottom: 20px;
|
||||
color: #787878;
|
||||
}
|
||||
|
||||
.step-btn {
|
||||
display: block;
|
||||
width: 100%;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.step-style {
|
||||
max-width: 800px;
|
||||
margin: 40px auto;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,116 @@
|
|||
<template>
|
||||
<el-tabs v-model="message" type="card">
|
||||
<el-tab-pane :label="`未读消息(${state.unread.length})`" name="first">
|
||||
<el-table :data="state.unread" :show-header="false" style="width: 100%">
|
||||
<el-table-column>
|
||||
<template #default="scope">
|
||||
<span class="message-title">{{ scope.row.title }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="date" width="180"></el-table-column>
|
||||
<el-table-column width="120">
|
||||
<template #default="scope">
|
||||
<el-button size="small" @click="handleRead(scope.$index)">标为已读</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="handle-row">
|
||||
<el-button type="primary">全部标为已读</el-button>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="`已读消息(${state.read.length})`" name="second">
|
||||
<template v-if="message === 'second'">
|
||||
<el-table :data="state.read" :show-header="false" style="width: 100%">
|
||||
<el-table-column>
|
||||
<template #default="scope">
|
||||
<span class="message-title">{{ scope.row.title }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="date" width="180"></el-table-column>
|
||||
<el-table-column width="120">
|
||||
<template #default="scope">
|
||||
<el-button type="danger" size="small" @click="handleDel(scope.$index)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="handle-row">
|
||||
<el-button type="danger">删除全部</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="`回收站(${state.recycle.length})`" name="third">
|
||||
<template v-if="message === 'third'">
|
||||
<el-table :data="state.recycle" :show-header="false" style="width: 100%">
|
||||
<el-table-column>
|
||||
<template #default="scope">
|
||||
<span class="message-title">{{ scope.row.title }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="date" width="180"></el-table-column>
|
||||
<el-table-column width="120">
|
||||
<template #default="scope">
|
||||
<el-button size="small" @click="handleRestore(scope.$index)">还原</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="handle-row">
|
||||
<el-button type="danger">清空回收站</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="tabs">
|
||||
import { ref, reactive } from 'vue';
|
||||
|
||||
const message = ref('first');
|
||||
const state = reactive({
|
||||
unread: [
|
||||
{
|
||||
date: '2018-04-19 20:00:00',
|
||||
title: '【系统通知】该系统将于今晚凌晨2点到5点进行升级维护'
|
||||
},
|
||||
{
|
||||
date: '2018-04-19 21:00:00',
|
||||
title: '今晚12点整发大红包,先到先得'
|
||||
}
|
||||
],
|
||||
read: [
|
||||
{
|
||||
date: '2018-04-19 20:00:00',
|
||||
title: '【系统通知】该系统将于今晚凌晨2点到5点进行升级维护'
|
||||
}
|
||||
],
|
||||
recycle: [
|
||||
{
|
||||
date: '2018-04-19 20:00:00',
|
||||
title: '【系统通知】该系统将于今晚凌晨2点到5点进行升级维护'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const handleRead = (index: number) => {
|
||||
const item = state.unread.splice(index, 1);
|
||||
state.read = item.concat(state.read);
|
||||
};
|
||||
const handleDel = (index: number) => {
|
||||
const item = state.read.splice(index, 1);
|
||||
state.recycle = item.concat(state.recycle);
|
||||
};
|
||||
const handleRestore = (index: number) => {
|
||||
const item = state.recycle.splice(index, 1);
|
||||
state.read = item.concat(state.read);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.message-title {
|
||||
cursor: pointer;
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
|
||||
.handle-row {
|
||||
margin-top: 30px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,33 @@
|
|||
<template>
|
||||
<div class="container">
|
||||
<el-button type="primary" @click="open = true">开始引导</el-button>
|
||||
|
||||
<el-divider />
|
||||
|
||||
<el-space>
|
||||
<el-button ref="ref1">上传</el-button>
|
||||
<el-button ref="ref2" type="primary">保存</el-button>
|
||||
<el-button ref="ref3" :icon="MoreFilled" />
|
||||
</el-space>
|
||||
|
||||
<el-tour v-model="open">
|
||||
<el-tour-step :target="ref1?.$el" title="上传文件">
|
||||
<img style="width: 120px" src="../../assets/img/img.jpg" alt="tour.png" />
|
||||
<div>点击这里选择文件</div>
|
||||
</el-tour-step>
|
||||
<el-tour-step :target="ref2?.$el" title="保存" description="点击进行上传" />
|
||||
<el-tour-step :target="ref3?.$el" title="更多操作" description="点击查看更多操作" />
|
||||
</el-tour>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { MoreFilled } from '@element-plus/icons-vue'
|
||||
|
||||
const ref1 = ref()
|
||||
const ref2 = ref()
|
||||
const ref3 = ref()
|
||||
|
||||
const open = ref(false)
|
||||
</script>
|
|
@ -5,13 +5,8 @@
|
|||
Element Plus自带上传组件。 访问地址:
|
||||
<a href="https://element-plus.org/zh-CN/component/upload.html" target="_blank">Element Plus Upload</a>
|
||||
</div>
|
||||
<el-upload
|
||||
class="upload-demo"
|
||||
drag
|
||||
action="http://jsonplaceholder.typicode.com/api/posts/"
|
||||
multiple
|
||||
:on-change="handle"
|
||||
>
|
||||
<el-upload class="upload-demo" drag action="http://jsonplaceholder.typicode.com/api/posts/" multiple
|
||||
:on-change="handle">
|
||||
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
|
||||
<div class="el-upload__text">
|
||||
将文件拖到此处,或
|
||||
|
@ -21,9 +16,9 @@
|
|||
|
||||
<div class="content-title">支持裁剪</div>
|
||||
<div class="plugins-tips">
|
||||
vue-cropperjs:一个封装了 cropperjs 的 Vue 组件。 访问地址:
|
||||
<a href="https://github.com/Agontuk/vue-cropperjs" target="_blank">vue-cropperjs</a>。 示例请查看
|
||||
<router-link to="/user">个人中心</router-link>
|
||||
vue-cropper:一个简单的vue图片裁剪插件。 访问地址:
|
||||
<a href="https://github.com/xyxiao001/vue-cropper" target="_blank">vue-cropper</a>。 示例请查看
|
||||
<router-link to="/ucenter">个人中心-我的头像</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -42,6 +37,7 @@ const handle = (rawFile: any) => {
|
|||
font-size: 22px;
|
||||
color: #1f2f3d;
|
||||
}
|
||||
|
||||
.upload-demo {
|
||||
width: 360px;
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
<template>
|
||||
<div class="container">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="18">
|
||||
<el-watermark :content="config.content" :font="config.font" :z-index="config.zIndex"
|
||||
:rotate="config.rotate" :gap="config.gap" :offset="config.offset">
|
||||
<div style="height: 600px" />
|
||||
</el-watermark>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form class="form" :model="config" label-position="top" label-width="50px">
|
||||
<el-form-item label="Content">
|
||||
<el-input v-model="config.content" />
|
||||
</el-form-item>
|
||||
<el-form-item label="Color">
|
||||
<el-color-picker v-model="config.font.color" show-alpha />
|
||||
</el-form-item>
|
||||
<el-form-item label="FontSize">
|
||||
<el-slider v-model="config.font.fontSize" />
|
||||
</el-form-item>
|
||||
<el-form-item label="zIndex">
|
||||
<el-slider v-model="config.zIndex" />
|
||||
</el-form-item>
|
||||
<el-form-item label="Rotate">
|
||||
<el-slider v-model="config.rotate" :min="-180" :max="180" />
|
||||
</el-form-item>
|
||||
<el-form-item label="Gap">
|
||||
<el-space>
|
||||
<el-input-number v-model="config.gap[0]" controls-position="right" />
|
||||
<el-input-number v-model="config.gap[1]" controls-position="right" />
|
||||
</el-space>
|
||||
</el-form-item>
|
||||
<el-form-item label="Offset">
|
||||
<el-space>
|
||||
<el-input-number v-model="config.offset[0]" placeholder="offsetLeft"
|
||||
controls-position="right" />
|
||||
<el-input-number v-model="config.offset[1]" placeholder="offsetTop"
|
||||
controls-position="right" />
|
||||
</el-space>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive } from 'vue'
|
||||
|
||||
const config = reactive({
|
||||
content: 'vue-manage-system',
|
||||
font: {
|
||||
fontSize: 16,
|
||||
color: 'rgba(0, 0, 0, 0.15)',
|
||||
},
|
||||
zIndex: -1,
|
||||
rotate: -22,
|
||||
gap: [100, 100] as [number, number],
|
||||
offset: [] as unknown as [number, number],
|
||||
})
|
||||
</script>
|
|
@ -1,156 +0,0 @@
|
|||
<template>
|
||||
<div class="container">
|
||||
<div class="form-box">
|
||||
<el-form ref="formRef" :rules="rules" :model="form" label-width="80px">
|
||||
<el-form-item label="表单名称" prop="name">
|
||||
<el-input v-model="form.name"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="选择器" prop="region">
|
||||
<el-select v-model="form.region" placeholder="请选择">
|
||||
<el-option key="小明" label="小明" value="小明"></el-option>
|
||||
<el-option key="小红" label="小红" value="小红"></el-option>
|
||||
<el-option key="小白" label="小白" value="小白"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="日期时间">
|
||||
<el-col :span="11">
|
||||
<el-form-item prop="date1">
|
||||
<el-date-picker
|
||||
type="date"
|
||||
placeholder="选择日期"
|
||||
v-model="form.date1"
|
||||
style="width: 100%"
|
||||
></el-date-picker>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col class="line" :span="2">-</el-col>
|
||||
<el-col :span="11">
|
||||
<el-form-item prop="date2">
|
||||
<el-time-picker placeholder="选择时间" v-model="form.date2" style="width: 100%">
|
||||
</el-time-picker>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-form-item>
|
||||
<el-form-item label="城市级联" prop="options">
|
||||
<el-cascader :options="options" v-model="form.options"></el-cascader>
|
||||
</el-form-item>
|
||||
<el-form-item label="选择开关" prop="delivery">
|
||||
<el-switch v-model="form.delivery"></el-switch>
|
||||
</el-form-item>
|
||||
<el-form-item label="多选框" prop="type">
|
||||
<el-checkbox-group v-model="form.type">
|
||||
<el-checkbox label="小明" name="type"></el-checkbox>
|
||||
<el-checkbox label="小红" name="type"></el-checkbox>
|
||||
<el-checkbox label="小白" name="type"></el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="单选框" prop="resource">
|
||||
<el-radio-group v-model="form.resource">
|
||||
<el-radio label="小明"></el-radio>
|
||||
<el-radio label="小红"></el-radio>
|
||||
<el-radio label="小白"></el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="文本框" prop="desc">
|
||||
<el-input type="textarea" rows="5" v-model="form.desc"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="onSubmit(formRef)">表单提交</el-button>
|
||||
<el-button @click="onReset(formRef)">重置表单</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="baseform">
|
||||
import { reactive, ref } from 'vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import type { FormInstance, FormRules } from 'element-plus';
|
||||
|
||||
const options = [
|
||||
{
|
||||
value: 'guangdong',
|
||||
label: '广东省',
|
||||
children: [
|
||||
{
|
||||
value: 'guangzhou',
|
||||
label: '广州市',
|
||||
children: [
|
||||
{
|
||||
value: 'tianhe',
|
||||
label: '天河区',
|
||||
},
|
||||
{
|
||||
value: 'haizhu',
|
||||
label: '海珠区',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'dongguan',
|
||||
label: '东莞市',
|
||||
children: [
|
||||
{
|
||||
value: 'changan',
|
||||
label: '长安镇',
|
||||
},
|
||||
{
|
||||
value: 'humen',
|
||||
label: '虎门镇',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'hunan',
|
||||
label: '湖南省',
|
||||
children: [
|
||||
{
|
||||
value: 'changsha',
|
||||
label: '长沙市',
|
||||
children: [
|
||||
{
|
||||
value: 'yuelu',
|
||||
label: '岳麓区',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
const rules: FormRules = {
|
||||
name: [{ required: true, message: '请输入表单名称', trigger: 'blur' }],
|
||||
};
|
||||
const formRef = ref<FormInstance>();
|
||||
const form = reactive({
|
||||
name: '',
|
||||
region: '',
|
||||
date1: '',
|
||||
date2: '',
|
||||
delivery: true,
|
||||
type: ['小明'],
|
||||
resource: '小红',
|
||||
desc: '',
|
||||
options: [],
|
||||
});
|
||||
// 提交
|
||||
const onSubmit = (formEl: FormInstance | undefined) => {
|
||||
// 表单校验
|
||||
if (!formEl) return;
|
||||
formEl.validate((valid) => {
|
||||
if (valid) {
|
||||
console.log(form);
|
||||
ElMessage.success('提交成功!');
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
};
|
||||
// 重置
|
||||
const onReset = (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return;
|
||||
formEl.resetFields();
|
||||
};
|
||||
</script>
|
|
@ -2,11 +2,11 @@
|
|||
<v-header />
|
||||
<v-sidebar />
|
||||
<div class="content-box" :class="{ 'content-collapse': sidebar.collapse }">
|
||||
<v-tags></v-tags>
|
||||
<v-tabs></v-tabs>
|
||||
<div class="content">
|
||||
<router-view v-slot="{ Component }">
|
||||
<transition name="move" mode="out-in">
|
||||
<keep-alive :include="tags.nameList">
|
||||
<keep-alive :include="tabs.nameList">
|
||||
<component :is="Component"></component>
|
||||
</keep-alive>
|
||||
</transition>
|
||||
|
@ -15,12 +15,42 @@
|
|||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { useSidebarStore } from '../store/sidebar';
|
||||
import { useTagsStore } from '../store/tags';
|
||||
import vHeader from '../components/header.vue';
|
||||
import vSidebar from '../components/sidebar.vue';
|
||||
import vTags from '../components/tags.vue';
|
||||
import { useSidebarStore } from '@/store/sidebar';
|
||||
import { useTabsStore } from '@/store/tabs';
|
||||
import vHeader from '@/components/header.vue';
|
||||
import vSidebar from '@/components/sidebar.vue';
|
||||
import vTabs from '@/components/tabs.vue';
|
||||
|
||||
const sidebar = useSidebarStore();
|
||||
const tags = useTagsStore();
|
||||
const tabs = useTabsStore();
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.content-box {
|
||||
position: absolute;
|
||||
left: 250px;
|
||||
right: 0;
|
||||
top: 70px;
|
||||
bottom: 0;
|
||||
padding-bottom: 30px;
|
||||
-webkit-transition: left 0.3s ease-in-out;
|
||||
transition: left 0.3s ease-in-out;
|
||||
background: #eef0fc;
|
||||
}
|
||||
|
||||
.content {
|
||||
width: auto;
|
||||
height: 100%;
|
||||
padding: 20px;
|
||||
overflow-y: scroll;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.content::-webkit-scrollbar {
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.content-collapse {
|
||||
left: 65px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,67 @@
|
|||
<template>
|
||||
<div class="error-page">
|
||||
<div class="error-box">
|
||||
<div class="error-code">403</div>
|
||||
<div class="error-desc">啊哦~ 你没有权限访问该页面哦</div>
|
||||
<div class="error-handle">
|
||||
<router-link to="/">
|
||||
<el-button type="primary" size="large">返回首页</el-button>
|
||||
</router-link>
|
||||
<el-button class="error-btn" size="large" @click="goBack">返回上一页</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="403">
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
const router = useRouter();
|
||||
const goBack = () => {
|
||||
router.go(-2);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.error-page {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #eef0fc;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.error-box {
|
||||
width: 400px;
|
||||
background-color: #fff;
|
||||
padding: 80px 50px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.error-code {
|
||||
line-height: 1;
|
||||
font-size: 100px;
|
||||
font-weight: bold;
|
||||
color: var(--el-color-primary);
|
||||
margin-bottom: 20px;
|
||||
text-align: center
|
||||
}
|
||||
|
||||
.error-desc {
|
||||
font-size: 20px;
|
||||
color: #777;
|
||||
text-align: center
|
||||
}
|
||||
|
||||
.error-handle {
|
||||
margin-top: 50px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.error-btn {
|
||||
margin-left: 100px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,67 @@
|
|||
<template>
|
||||
<div class="error-page">
|
||||
<div class="error-box">
|
||||
<div class="error-code">404</div>
|
||||
<div class="error-desc">啊哦~ 你所访问的页面不存在</div>
|
||||
<div class="error-handle">
|
||||
<router-link to="/">
|
||||
<el-button type="primary" size="large">返回首页</el-button>
|
||||
</router-link>
|
||||
<el-button class="error-btn" size="large" @click="goBack">返回上一页</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="404">
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
const router = useRouter();
|
||||
const goBack = () => {
|
||||
router.go(-1);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.error-page {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #eef0fc;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.error-box {
|
||||
width: 400px;
|
||||
background-color: #fff;
|
||||
padding: 80px 50px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.error-code {
|
||||
line-height: 1;
|
||||
font-size: 100px;
|
||||
font-weight: bold;
|
||||
color: var(--el-color-primary);
|
||||
margin-bottom: 20px;
|
||||
text-align: center
|
||||
}
|
||||
|
||||
.error-desc {
|
||||
font-size: 20px;
|
||||
color: #777;
|
||||
text-align: center
|
||||
}
|
||||
|
||||
.error-handle {
|
||||
margin-top: 50px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.error-btn {
|
||||
margin-left: 100px;
|
||||
}
|
||||
</style>
|
|
@ -1,35 +1,42 @@
|
|||
<template>
|
||||
<div class="container">
|
||||
<h2>使用方法</h2>
|
||||
<p style="line-height: 50px">
|
||||
直接通过设置类名为 el-icon-lx-iconName 来使用即可。例如:(共{{ iconList.length }}个图标)
|
||||
</p>
|
||||
<p class="example-p">
|
||||
<i class="el-icon-lx-redpacket_fill" style="font-size: 30px; color: #ff5900"></i>
|
||||
<span><i class="el-icon-lx-redpacket_fill"></i></span>
|
||||
</p>
|
||||
<p class="example-p">
|
||||
<i class="el-icon-lx-weibo" style="font-size: 30px; color: #fd5656"></i>
|
||||
<span><i class="el-icon-lx-weibo"></i></span>
|
||||
</p>
|
||||
<p class="example-p">
|
||||
<i class="el-icon-lx-emojifill" style="font-size: 30px; color: #ffc300"></i>
|
||||
<span><i class="el-icon-lx-emojifill"></i></span>
|
||||
</p>
|
||||
<br />
|
||||
<h2>图标</h2>
|
||||
<div class="search-box">
|
||||
<el-input class="search" size="large" v-model="keyword" clearable placeholder="请输入图标名称"></el-input>
|
||||
</div>
|
||||
<ul>
|
||||
<li class="icon-li" v-for="(item, index) in list" :key="index">
|
||||
<div class="icon-li-content">
|
||||
<i :class="`el-icon-lx-${item}`"></i>
|
||||
<span>{{ item }}</span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<el-tabs type="border-card">
|
||||
<el-tab-pane label="自定义图标">
|
||||
<h2>使用方法</h2>
|
||||
<p style="line-height: 50px">
|
||||
直接通过设置类名为 el-icon-lx-iconName 来使用即可。例如:(共{{ iconList.length }}个图标)
|
||||
</p>
|
||||
<p class="example-p">
|
||||
<i class="el-icon-lx-redpacket_fill" style="font-size: 30px; color: #ff5900"></i>
|
||||
<span><i class="el-icon-lx-redpacket_fill"></i></span>
|
||||
</p>
|
||||
<p class="example-p">
|
||||
<i class="el-icon-lx-weibo" style="font-size: 30px; color: #fd5656"></i>
|
||||
<span><i class="el-icon-lx-weibo"></i></span>
|
||||
</p>
|
||||
<p class="example-p">
|
||||
<i class="el-icon-lx-emojifill" style="font-size: 30px; color: #ffc300"></i>
|
||||
<span><i class="el-icon-lx-emojifill"></i></span>
|
||||
</p>
|
||||
<br />
|
||||
<h2>图标</h2>
|
||||
<div class="search-box">
|
||||
<el-input class="search" size="large" v-model="keyword" clearable placeholder="请输入图标名称"></el-input>
|
||||
</div>
|
||||
<ul>
|
||||
<li class="icon-li" v-for="(item, index) in list" :key="index">
|
||||
<div class="icon-li-content">
|
||||
<i :class="`el-icon-lx-${item}`"></i>
|
||||
<span>{{ item }}</span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="Element图标">
|
||||
<el-link type="primary" href="https://element-plus.org/zh-CN/component/icon.html#icon-collection"
|
||||
target="_blank">前往官方文档查看</el-link>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="icon">
|
||||
|
@ -160,8 +167,34 @@ const iconList: Array<string> = [
|
|||
'sort',
|
||||
'searchlist',
|
||||
'search',
|
||||
'edit'
|
||||
'edit',
|
||||
'apple-line',
|
||||
'baidu-fill',
|
||||
'amazon-fill',
|
||||
'netease-cloud-music-fill',
|
||||
'qq-line',
|
||||
'wechat-fill',
|
||||
'alipay-fill',
|
||||
'android-fill',
|
||||
'android-line',
|
||||
'whatsapp-line',
|
||||
'whatsapp-fill',
|
||||
'bilibili-fill',
|
||||
'chrome-fill',
|
||||
'dingding-fill',
|
||||
'dingding-line',
|
||||
'apple-fill',
|
||||
'github-fill',
|
||||
'qq-fill',
|
||||
'wechat-pay-fill',
|
||||
'windows-line',
|
||||
'windows-fill',
|
||||
'youtube-line',
|
||||
'youtube-fill',
|
||||
'wechat-pay-line',
|
||||
'zhihu-line'
|
||||
];
|
||||
|
||||
const keyword = ref('');
|
||||
const list = computed(() => {
|
||||
return iconList.filter(item => {
|
||||
|
@ -176,23 +209,28 @@ const list = computed(() => {
|
|||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.search-box {
|
||||
text-align: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.search {
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
ul,
|
||||
li {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.icon-li {
|
||||
display: inline-block;
|
||||
padding: 10px;
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
}
|
||||
|
||||
.icon-li-content {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
|
@ -201,12 +239,19 @@ li {
|
|||
justify-content: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.icon-li-content i {
|
||||
font-size: 36px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.icon-li-content span {
|
||||
margin-top: 10px;
|
||||
color: #787878;
|
||||
}
|
||||
|
||||
.iframe {
|
||||
width: 100%;
|
||||
height: 700px;
|
||||
}
|
||||
</style>
|
|
@ -1,32 +1,43 @@
|
|||
<template>
|
||||
<div class="login-wrap">
|
||||
<div class="ms-login">
|
||||
<div class="ms-title">后台管理系统</div>
|
||||
<el-form :model="param" :rules="rules" ref="login" label-width="0px" class="ms-content">
|
||||
<div class="login-bg">
|
||||
<div class="login-container">
|
||||
<div class="login-header">
|
||||
<img class="logo mr10" src="../../assets/img/logo.svg" alt="" />
|
||||
<div class="login-title">后台管理系统</div>
|
||||
</div>
|
||||
<el-form :model="param" :rules="rules" ref="login" size="large">
|
||||
<el-form-item prop="username">
|
||||
<el-input v-model="param.username" placeholder="username">
|
||||
<el-input v-model="param.username" placeholder="用户名">
|
||||
<template #prepend>
|
||||
<el-button :icon="User"></el-button>
|
||||
<el-icon>
|
||||
<User />
|
||||
</el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="password">
|
||||
<el-input
|
||||
type="password"
|
||||
placeholder="password"
|
||||
placeholder="密码"
|
||||
v-model="param.password"
|
||||
@keyup.enter="submitForm(login)"
|
||||
>
|
||||
<template #prepend>
|
||||
<el-button :icon="Lock"></el-button>
|
||||
<el-icon>
|
||||
<Lock />
|
||||
</el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<div class="login-btn">
|
||||
<el-button type="primary" @click="submitForm(login)">登录</el-button>
|
||||
<div class="pwd-tips">
|
||||
<el-checkbox class="pwd-checkbox" v-model="checked" label="记住密码" />
|
||||
<el-link type="primary" @click="$router.push('/reset-pwd')">忘记密码</el-link>
|
||||
</div>
|
||||
<el-checkbox class="login-tips" v-model="checked" label="记住密码" size="large" />
|
||||
<el-button class="login-btn" type="primary" size="large" @click="submitForm(login)">登录</el-button>
|
||||
<p class="login-tips">Tips : 用户名和密码随便填。</p>
|
||||
<p class="login-text">
|
||||
没有账号?<el-link type="primary" @click="$router.push('/register')">立即注册</el-link>
|
||||
</p>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -34,12 +45,11 @@
|
|||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive } from 'vue';
|
||||
import { useTagsStore } from '../store/tags';
|
||||
import { usePermissStore } from '../store/permiss';
|
||||
import { useTabsStore } from '@/store/tabs';
|
||||
import { usePermissStore } from '@/store/permiss';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import type { FormInstance, FormRules } from 'element-plus';
|
||||
import { Lock, User } from '@element-plus/icons-vue';
|
||||
|
||||
interface LoginInfo {
|
||||
username: string;
|
||||
|
@ -90,47 +100,73 @@ const submitForm = (formEl: FormInstance | undefined) => {
|
|||
});
|
||||
};
|
||||
|
||||
const tags = useTagsStore();
|
||||
tags.clearTags();
|
||||
const tabs = useTabsStore();
|
||||
tabs.clearTabs();
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.login-wrap {
|
||||
.login-bg {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image: url(../assets/img/login-bg.jpg);
|
||||
background-size: 100%;
|
||||
background: url(../../assets/img/login-bg.jpg) center/cover no-repeat;
|
||||
}
|
||||
.ms-title {
|
||||
line-height: 50px;
|
||||
text-align: center;
|
||||
font-size: 20px;
|
||||
|
||||
.login-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.logo {
|
||||
width: 35px;
|
||||
}
|
||||
|
||||
.login-title {
|
||||
font-size: 22px;
|
||||
color: #333;
|
||||
font-weight: bold;
|
||||
padding-top: 10px;
|
||||
}
|
||||
.ms-login {
|
||||
width: 350px;
|
||||
|
||||
.login-container {
|
||||
width: 450px;
|
||||
border-radius: 5px;
|
||||
background: #fff;
|
||||
padding: 40px 50px 50px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.ms-content {
|
||||
padding: 10px 30px 30px;
|
||||
|
||||
.pwd-tips {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 14px;
|
||||
margin: -10px 0 10px;
|
||||
color: #787878;
|
||||
}
|
||||
|
||||
.pwd-checkbox {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.login-btn {
|
||||
text-align: center;
|
||||
}
|
||||
.login-btn button {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 36px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.login-tips {
|
||||
font-size: 12px;
|
||||
line-height: 30px;
|
||||
color: #333;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.login-text {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: 20px;
|
||||
font-size: 14px;
|
||||
color: #787878;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,132 @@
|
|||
<template>
|
||||
<div class="login-bg">
|
||||
<div class="login-container">
|
||||
<div class="login-header">
|
||||
<img class="logo mr10" src="../../assets/img/logo.svg" alt="">
|
||||
<div class="login-title">
|
||||
后台管理系统
|
||||
</div>
|
||||
</div>
|
||||
<el-form :model="param" :rules="rules" ref="register" size="large">
|
||||
<el-form-item prop="username">
|
||||
<el-input v-model="param.username" placeholder="用户名">
|
||||
<template #prepend>
|
||||
<el-icon>
|
||||
<User />
|
||||
</el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="email">
|
||||
<el-input v-model="param.email" placeholder="邮箱">
|
||||
<template #prepend>
|
||||
<el-icon>
|
||||
<Message />
|
||||
</el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="password">
|
||||
<el-input type="password" placeholder="密码" v-model="param.password"
|
||||
@keyup.enter="submitForm(register)">
|
||||
<template #prepend>
|
||||
<el-icon>
|
||||
<Lock />
|
||||
</el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-button class="login-btn" type="primary" size="large" @click="submitForm(register)">注册</el-button>
|
||||
<p class="login-text">已有账号,<el-link type="primary" @click="$router.push('/login')">立即登录</el-link></p>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { ElMessage, type FormInstance, type FormRules } from 'element-plus';
|
||||
import { Register } from '@/types/user';
|
||||
|
||||
const router = useRouter();
|
||||
const param = reactive<Register>({
|
||||
username: '',
|
||||
password: '',
|
||||
email: '',
|
||||
});
|
||||
|
||||
const rules: FormRules = {
|
||||
username: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入用户名',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
password: [{ required: true, message: '请输入密码', trigger: 'blur' }],
|
||||
email: [{ required: true, message: '请输入邮箱', trigger: 'blur' }],
|
||||
};
|
||||
const register = ref<FormInstance>();
|
||||
const submitForm = (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return;
|
||||
formEl.validate((valid: boolean) => {
|
||||
if (valid) {
|
||||
ElMessage.success('注册成功,请登录');
|
||||
router.push('/login');
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.login-bg {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: url(../../assets/img/login-bg.jpg) center/cover no-repeat;
|
||||
}
|
||||
|
||||
.login-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.logo {
|
||||
width: 35px;
|
||||
}
|
||||
|
||||
.login-title {
|
||||
font-size: 22px;
|
||||
color: #333;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.login-container {
|
||||
width: 450px;
|
||||
border-radius: 5px;
|
||||
background: #fff;
|
||||
padding: 40px 50px 50px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.login-btn {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.login-text {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: 20px;
|
||||
font-size: 14px;
|
||||
color: #787878;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,97 @@
|
|||
<template>
|
||||
<div class="login-bg">
|
||||
<div class="login-container">
|
||||
<div class="reset-title">重置密码</div>
|
||||
<p class="reset-text">输入你的邮箱,我们将发送重置密码邮件</p>
|
||||
<el-form :model="param" :rules="rules" ref="register" size="large">
|
||||
<el-form-item prop="email">
|
||||
<el-input v-model="param.email" placeholder="邮箱">
|
||||
<template #prepend>
|
||||
<el-icon>
|
||||
<Message />
|
||||
</el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-button class="login-btn" type="primary" size="large" @click="submitForm(register)">发送邮件</el-button>
|
||||
<p class="login-text"><el-link type="primary" @click="$router.push('/login')">返回登录</el-link></p>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { ElMessage, type FormInstance, type FormRules } from 'element-plus';
|
||||
|
||||
const param = ref({
|
||||
email: '',
|
||||
});
|
||||
|
||||
const rules: FormRules = {
|
||||
email: [
|
||||
{ required: true, message: '请输入邮箱', trigger: 'blur' },
|
||||
{ pattern: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/, message: '请输入正确的邮箱格式', trigger: 'blur' }
|
||||
],
|
||||
};
|
||||
const register = ref<FormInstance>();
|
||||
const submitForm = (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return;
|
||||
formEl.validate((valid: boolean) => {
|
||||
if (valid) {
|
||||
ElMessage.success('邮件已发送,请注意查收');
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.login-bg {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: url(../../assets/img/login-bg.jpg) center/cover no-repeat;
|
||||
}
|
||||
|
||||
.reset-title {
|
||||
text-align: center;
|
||||
font-size: 22px;
|
||||
color: #333;
|
||||
font-weight: bold;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.reset-text {
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
color: #787878;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.login-container {
|
||||
width: 450px;
|
||||
border-radius: 5px;
|
||||
background: #fff;
|
||||
padding: 40px 50px 50px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.login-btn {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.login-text {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 20px;
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,205 @@
|
|||
<template>
|
||||
<div>
|
||||
<el-card class="mgb20" shadow="hover">
|
||||
<template #header>
|
||||
<div class="content-title">系统主题</div>
|
||||
</template>
|
||||
<div class="theme-list mgb20">
|
||||
<div class="theme-item" @click="setSystemTheme(item)" v-for="item in system"
|
||||
:style="{ backgroundColor: item.color, color: '#fff' }">{{ item.name }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-center">
|
||||
<el-button @click="resetSystemTheme">重置主题</el-button>
|
||||
</div>
|
||||
</el-card>
|
||||
<el-card class="mgb20" shadow="hover">
|
||||
<template #header>
|
||||
<div class="content-title">Element-Plus主题</div>
|
||||
</template>
|
||||
<div class="theme-list mgb20">
|
||||
<div class="theme-item" v-for="theme in themes">
|
||||
<el-button :type="theme.name">{{ theme.name }}</el-button>
|
||||
<div class="theme-color">{{ theme.color }}</div>
|
||||
<el-color-picker v-model="color[theme.name]" @change="changeColor(theme.name)" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-center">
|
||||
<el-button @click="resetTheme">重置主题</el-button>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<el-row :gutter="50">
|
||||
<el-col :span="12">
|
||||
<el-card class="mgb20" shadow="hover">
|
||||
<template #header>
|
||||
<div class="content-title">头部主题</div>
|
||||
</template>
|
||||
<div class="theme-list mgb20">
|
||||
<div class="theme-item">
|
||||
<el-button :color="color.headerBgColor">背景颜色</el-button>
|
||||
<div class="theme-color">{{ color.headerBgColor }}</div>
|
||||
<el-color-picker v-model="color.headerBgColor"
|
||||
@change="themeStore.setHeaderBgColor(color.headerBgColor)" />
|
||||
</div>
|
||||
<div class="theme-item">
|
||||
<el-button :color="color.headerTextColor">文字颜色</el-button>
|
||||
<div class="theme-color">{{ color.headerTextColor }}</div>
|
||||
<el-color-picker v-model="color.headerTextColor"
|
||||
@change="themeStore.setHeaderTextColor(color.headerTextColor)" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-center">
|
||||
<el-button @click="resetHeader">重置主题</el-button>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12">
|
||||
<el-card class="mgb20" shadow="hover">
|
||||
<template #header>
|
||||
<div class="content-title">菜单主题</div>
|
||||
</template>
|
||||
<div class="theme-list mgb20">
|
||||
<div class="theme-item">
|
||||
<el-button :color="sidebar.bgColor">背景颜色</el-button>
|
||||
<div class="theme-color">{{ sidebar.bgColor }}</div>
|
||||
<el-color-picker v-model="sidebarColor.bgColor"
|
||||
@change="sidebar.setBgColor(sidebarColor.bgColor)" />
|
||||
</div>
|
||||
<div class="theme-item">
|
||||
<el-button :color="sidebar.textColor">文字颜色</el-button>
|
||||
<div class="theme-color">{{ sidebar.textColor }}</div>
|
||||
<el-color-picker v-model="sidebarColor.textColor"
|
||||
@change="sidebar.setTextColor(sidebarColor.textColor)" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-center">
|
||||
<el-button @click="resetSidebar">重置主题</el-button>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useSidebarStore } from '@/store/sidebar';
|
||||
import { useThemeStore } from '@/store/theme'
|
||||
import { reactive } from 'vue';
|
||||
const themeStore = useThemeStore();
|
||||
const sidebar = useSidebarStore();
|
||||
|
||||
const color = reactive({
|
||||
primary: localStorage.getItem('theme-primary') || '#409eff',
|
||||
success: localStorage.getItem('theme-success') || '#67c23a',
|
||||
warning: localStorage.getItem('theme-warning') || '#e6a23c',
|
||||
danger: localStorage.getItem('theme-danger') || '#f56c6c',
|
||||
info: localStorage.getItem('theme-info') || '#909399',
|
||||
headerBgColor: themeStore.headerBgColor,
|
||||
headerTextColor: themeStore.headerTextColor,
|
||||
})
|
||||
const sidebarColor = reactive({
|
||||
bgColor: sidebar.bgColor,
|
||||
textColor: sidebar.textColor
|
||||
})
|
||||
const themes = [
|
||||
{
|
||||
name: 'primary',
|
||||
color: themeStore.primary || color.primary
|
||||
},
|
||||
{
|
||||
name: 'success',
|
||||
color: themeStore.success || color.success
|
||||
},
|
||||
{
|
||||
name: 'warning',
|
||||
color: themeStore.warning || color.warning
|
||||
},
|
||||
{
|
||||
name: 'danger',
|
||||
color: themeStore.danger || color.danger
|
||||
},
|
||||
{
|
||||
name: 'info',
|
||||
color: themeStore.info || color.info
|
||||
}
|
||||
]
|
||||
|
||||
const changeColor = (name: string) => {
|
||||
themeStore.setPropertyColor(color[name], name)
|
||||
}
|
||||
|
||||
const resetTheme = () => {
|
||||
themeStore.resetTheme()
|
||||
}
|
||||
const resetHeader = () => {
|
||||
localStorage.removeItem('header-bg-color')
|
||||
localStorage.removeItem('header-text-color')
|
||||
location.reload()
|
||||
}
|
||||
const resetSidebar = () => {
|
||||
localStorage.removeItem('sidebar-bg-color')
|
||||
localStorage.removeItem('sidebar-text-color')
|
||||
location.reload()
|
||||
}
|
||||
const system = [
|
||||
{
|
||||
name: '默认',
|
||||
color: '#242f42'
|
||||
},
|
||||
{
|
||||
name: '健康',
|
||||
color: '#1ABC9C'
|
||||
},
|
||||
{
|
||||
name: '优雅',
|
||||
color: '#722ed1'
|
||||
},
|
||||
{
|
||||
name: '热情',
|
||||
color: '#f44336'
|
||||
},
|
||||
{
|
||||
name: '宁静',
|
||||
color: '#00bcd4'
|
||||
}
|
||||
]
|
||||
const setSystemTheme = (data: any) => {
|
||||
if (data.name === '默认') {
|
||||
resetSystemTheme()
|
||||
} else {
|
||||
themeStore.setHeaderBgColor(data.color)
|
||||
themeStore.setHeaderTextColor('#fff')
|
||||
sidebar.setBgColor('#fff')
|
||||
sidebar.setTextColor('#5b6e88')
|
||||
themeStore.setPropertyColor(data.color, 'primary')
|
||||
}
|
||||
}
|
||||
const resetSystemTheme = () => {
|
||||
resetTheme();
|
||||
resetHeader();
|
||||
resetSidebar();
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.theme-list {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.theme-item {
|
||||
margin-right: 20px;
|
||||
padding: 30px;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.theme-color {
|
||||
color: #787878;
|
||||
margin: 20px 0;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,270 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="user-container">
|
||||
<el-card class="user-profile" shadow="hover" :body-style="{ padding: '0px' }">
|
||||
<div class="user-profile-bg"></div>
|
||||
<div class="user-avatar-wrap">
|
||||
<el-avatar class="user-avatar" :size="120" :src="avatarImg" />
|
||||
</div>
|
||||
<div class="user-info">
|
||||
<div class="info-name">{{ name }}</div>
|
||||
<div class="info-desc">
|
||||
<span>@lin-xin</span>
|
||||
<el-divider direction="vertical" />
|
||||
<el-link href="https://lin-xin.gitee.io" target="_blank">lin-xin.gitee.io</el-link>
|
||||
</div>
|
||||
<div class="info-desc">FE Developer</div>
|
||||
<div class="info-icon">
|
||||
<a href="https://github.com/lin-xin" target="_blank"> <i class="el-icon-lx-github-fill"></i></a>
|
||||
<i class="el-icon-lx-qq-fill"></i>
|
||||
<i class="el-icon-lx-facebook-fill"></i>
|
||||
<i class="el-icon-lx-twitter-fill"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="user-footer">
|
||||
<div class="user-footer-item">
|
||||
<el-statistic title="Follower" :value="1800" />
|
||||
</div>
|
||||
<div class="user-footer-item">
|
||||
<el-statistic title="Following" :value="666" />
|
||||
</div>
|
||||
<div class="user-footer-item">
|
||||
<el-statistic title="Total Post" :value="888" />
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
<el-card
|
||||
class="user-content"
|
||||
shadow="hover"
|
||||
:body-style="{ padding: '20px 50px', height: '100%', boxSizing: 'border-box' }"
|
||||
>
|
||||
<el-tabs tab-position="left" v-model="activeName">
|
||||
<el-tab-pane name="label1" label="消息通知" class="user-tabpane">
|
||||
<TabsComp />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane name="label2" label="我的头像" class="user-tabpane">
|
||||
<div class="crop-wrap" v-if="activeName === 'label2'">
|
||||
<vueCropper
|
||||
ref="cropper"
|
||||
:img="imgSrc"
|
||||
:autoCrop="true"
|
||||
:centerBox="true"
|
||||
:full="true"
|
||||
mode="contain"
|
||||
>
|
||||
</vueCropper>
|
||||
</div>
|
||||
<el-button class="crop-demo-btn" type="primary"
|
||||
>选择图片
|
||||
<input class="crop-input" type="file" name="image" accept="image/*" @change="setImage" />
|
||||
</el-button>
|
||||
<el-button type="success" @click="saveAvatar">上传并保存</el-button>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane name="label3" label="修改密码" class="user-tabpane">
|
||||
<el-form class="w500" label-position="top">
|
||||
<el-form-item label="旧密码:">
|
||||
<el-input type="password" v-model="form.old"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="新密码:">
|
||||
<el-input type="password" v-model="form.new"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="确认新密码:">
|
||||
<el-input type="password" v-model="form.new1"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="onSubmit">保存</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane name="label4" label="赞赏作者" class="user-tabpane">
|
||||
<div class="plugins-tips">
|
||||
如果该框架
|
||||
<el-link href="https://github.com/lin-xin/vue-manage-system" target="_blank"
|
||||
>vue-manage-system</el-link
|
||||
>
|
||||
对你有帮助,那就请作者喝杯饮料吧!<el-icon>
|
||||
<ColdDrink />
|
||||
</el-icon>
|
||||
加微信号 linxin_20 探讨问题。
|
||||
</div>
|
||||
<div>
|
||||
<img src="https://lin-xin.gitee.io/images/weixin.jpg" />
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-card>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="ucenter">
|
||||
import { reactive, ref } from 'vue';
|
||||
import { VueCropper } from 'vue-cropper';
|
||||
import 'vue-cropper/dist/index.css';
|
||||
import avatar from '@/assets/img/img.jpg';
|
||||
import TabsComp from '../element/tabs.vue';
|
||||
|
||||
const name = localStorage.getItem('ms_username');
|
||||
const form = reactive({
|
||||
new1: '',
|
||||
new: '',
|
||||
old: '',
|
||||
});
|
||||
const onSubmit = () => {};
|
||||
|
||||
const activeName = ref('label1');
|
||||
|
||||
const avatarImg = ref(avatar);
|
||||
const imgSrc = ref(avatar);
|
||||
const cropImg = ref('');
|
||||
const cropper: any = ref();
|
||||
|
||||
const setImage = (e: any) => {
|
||||
const file = e.target.files[0];
|
||||
if (!file.type.includes('image/')) {
|
||||
return;
|
||||
}
|
||||
const reader = new FileReader();
|
||||
reader.onload = (event: any) => {
|
||||
imgSrc.value = event.target.result;
|
||||
cropper.value && cropper.value.replace(event.target.result);
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
};
|
||||
|
||||
const cropImage = () => {
|
||||
cropImg.value = cropper.value?.getCroppedCanvas().toDataURL();
|
||||
};
|
||||
|
||||
const saveAvatar = () => {
|
||||
avatarImg.value = cropImg.value;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.user-container {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.user-profile {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.user-profile-bg {
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
background-image: url('../../assets/img/ucenter-bg.jpg');
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.user-profile {
|
||||
width: 500px;
|
||||
margin-right: 20px;
|
||||
flex: 0 0 auto;
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
.user-avatar-wrap {
|
||||
position: absolute;
|
||||
top: 135px;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.user-avatar {
|
||||
border: 5px solid #fff;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 7px 12px 0 rgba(62, 57, 107, 0.16);
|
||||
}
|
||||
|
||||
.user-info {
|
||||
text-align: center;
|
||||
padding: 80px 0 30px;
|
||||
}
|
||||
|
||||
.info-name {
|
||||
margin: 0 0 20px;
|
||||
font-size: 22px;
|
||||
font-weight: 500;
|
||||
color: #373a3c;
|
||||
}
|
||||
|
||||
.info-desc {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.info-desc,
|
||||
.info-desc a {
|
||||
font-size: 18px;
|
||||
color: #55595c;
|
||||
}
|
||||
|
||||
.info-icon {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.info-icon i {
|
||||
font-size: 30px;
|
||||
margin: 0 10px;
|
||||
color: #343434;
|
||||
}
|
||||
|
||||
.user-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.user-tabpane {
|
||||
padding: 10px 20px;
|
||||
}
|
||||
|
||||
.crop-wrap {
|
||||
width: 600px;
|
||||
height: 350px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.crop-demo-btn {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.crop-input {
|
||||
position: absolute;
|
||||
width: 100px;
|
||||
height: 40px;
|
||||
left: 0;
|
||||
top: 0;
|
||||
opacity: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.w500 {
|
||||
width: 500px;
|
||||
}
|
||||
|
||||
.user-footer {
|
||||
display: flex;
|
||||
border-top: 1px solid rgba(83, 70, 134, 0.1);
|
||||
}
|
||||
|
||||
.user-footer-item {
|
||||
padding: 20px 0;
|
||||
width: 33.3333333333%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.user-footer > div + div {
|
||||
border-left: 1px solid rgba(83, 70, 134, 0.1);
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
.el-tabs.el-tabs--left {
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,253 @@
|
|||
<template>
|
||||
<div class="user-container">
|
||||
<el-card class="user-profile mgb20" shadow="hover" :body-style="{ padding: '0px' }">
|
||||
<div class="user-profile-bg"></div>
|
||||
<div class="user-avatar-wrap">
|
||||
<el-avatar class="user-avatar" :size="120" :src="avatarImg" />
|
||||
</div>
|
||||
<div class="user-info">
|
||||
<div class="info-name">{{ name }}</div>
|
||||
<div class="info-desc">
|
||||
<!-- <span>{{ name }}</span>
|
||||
<el-divider direction="vertical" /> -->
|
||||
<span>FE Developer</span>
|
||||
<el-divider direction="vertical" />
|
||||
<el-link href="https://lin-xin.gitee.io" target="_blank">lin-xin.gitee.io</el-link>
|
||||
</div>
|
||||
<!-- <div class="info-icon">
|
||||
<a href="https://github.com/lin-xin" target="_blank"> <i class="el-icon-lx-github-fill"></i></a>
|
||||
<i class="el-icon-lx-qq-fill"></i>
|
||||
<i class="el-icon-lx-facebook-fill"></i>
|
||||
<i class="el-icon-lx-twitter-fill"></i>
|
||||
</div> -->
|
||||
</div>
|
||||
<!-- <div class="user-footer">
|
||||
<div class="user-footer-item">
|
||||
<el-statistic title="Follower" value="18K" />
|
||||
</div>
|
||||
<div class="user-footer-item">
|
||||
<el-statistic title="Following" :value="666" />
|
||||
</div>
|
||||
<div class="user-footer-item">
|
||||
<el-statistic title="Total Post" :value="888" />
|
||||
</div>
|
||||
</div> -->
|
||||
</el-card>
|
||||
<el-card class="user-content" shadow="hover"
|
||||
:body-style="{ padding: '20px 50px', height: '100%', boxSizing: 'border-box' }">
|
||||
<el-tabs v-model="activeName">
|
||||
|
||||
<el-tab-pane name="label1" label="消息通知" class="user-tabpane">
|
||||
<TabsComp />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane name="label2" label="我的头像" class="user-tabpane">
|
||||
<div class="crop-wrap" v-if="activeName === 'label2'">
|
||||
<vueCropper ref="cropper" :img="imgSrc" :autoCrop="true" :centerBox="true" :full="true"
|
||||
mode="contain">
|
||||
</vueCropper>
|
||||
</div>
|
||||
<el-button class="crop-demo-btn" type="primary">选择图片
|
||||
<input class="crop-input" type="file" name="image" accept="image/*" @change="setImage" />
|
||||
</el-button>
|
||||
<el-button type="success" @click="saveAvatar">上传并保存</el-button>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane name="label3" label="修改密码" class="user-tabpane">
|
||||
<el-form class="w500" label-position="top">
|
||||
<el-form-item label="旧密码:">
|
||||
<el-input type="password" v-model="form.old"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="新密码:">
|
||||
<el-input type="password" v-model="form.new"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="确认新密码:">
|
||||
<el-input type="password" v-model="form.new1"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="onSubmit">保存</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane name="label4" label="赞赏作者" class="user-tabpane">
|
||||
<div class="plugins-tips">
|
||||
如果该框架 <el-link href="https://github.com/lin-xin/vue-manage-system"
|
||||
target="_blank">vue-manage-system</el-link> 对你有帮助,那就请作者喝杯饮料吧!<el-icon>
|
||||
<ColdDrink />
|
||||
</el-icon> 加微信号 linxin_20 探讨问题。
|
||||
</div>
|
||||
<div>
|
||||
<img src="https://lin-xin.gitee.io/images/weixin.jpg" />
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="ucenter">
|
||||
import { reactive, ref } from 'vue';
|
||||
import { VueCropper } from "vue-cropper"
|
||||
import 'vue-cropper/dist/index.css'
|
||||
import avatar from '@/assets/img/img.jpg';
|
||||
import TabsComp from '../element/tabs.vue';
|
||||
|
||||
const name = localStorage.getItem('ms_username');
|
||||
const form = reactive({
|
||||
new1: '',
|
||||
new: '',
|
||||
old: ''
|
||||
});
|
||||
const onSubmit = () => { };
|
||||
|
||||
const activeName = ref('label1');
|
||||
|
||||
const avatarImg = ref(avatar);
|
||||
const imgSrc = ref(avatar);
|
||||
const cropImg = ref('');
|
||||
const cropper: any = ref();
|
||||
|
||||
const setImage = (e: any) => {
|
||||
const file = e.target.files[0];
|
||||
if (!file.type.includes('image/')) {
|
||||
return;
|
||||
}
|
||||
const reader = new FileReader();
|
||||
reader.onload = (event: any) => {
|
||||
imgSrc.value = event.target.result;
|
||||
cropper.value && cropper.value.replace(event.target.result);
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
};
|
||||
|
||||
const cropImage = () => {
|
||||
cropImg.value = cropper.value?.getCroppedCanvas().toDataURL();
|
||||
};
|
||||
|
||||
const saveAvatar = () => {
|
||||
avatarImg.value = cropImg.value;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* .user-container {
|
||||
display: flex;
|
||||
} */
|
||||
|
||||
.user-profile-bg {
|
||||
width: 100%;
|
||||
height: 150px;
|
||||
background-image: url('../../assets/img/bahnhofsidylle.jpg');
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.user-profile {
|
||||
position: relative;
|
||||
/* width: 500px; */
|
||||
/* margin-right: 20px; */
|
||||
/* flex: 0 0 auto;
|
||||
align-self: flex-start;
|
||||
} */
|
||||
}
|
||||
|
||||
.user-avatar-wrap {
|
||||
position: absolute;
|
||||
top: 90px;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.user-avatar {
|
||||
border: 5px solid #fff;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 7px 12px 0 rgba(62, 57, 107, .16)
|
||||
}
|
||||
|
||||
.user-info {
|
||||
text-align: center;
|
||||
padding: 70px 0 20px;
|
||||
}
|
||||
|
||||
.info-name {
|
||||
margin: 0 0 10px;
|
||||
font-size: 22px;
|
||||
font-weight: 500;
|
||||
color: #373a3c;
|
||||
}
|
||||
|
||||
.info-desc {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.info-desc,
|
||||
.info-desc a {
|
||||
font-size: 18px;
|
||||
color: #55595c;
|
||||
}
|
||||
|
||||
.info-icon {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.info-icon i {
|
||||
font-size: 30px;
|
||||
margin: 0 10px;
|
||||
color: #343434;
|
||||
}
|
||||
|
||||
.user-content {
|
||||
flex: 1
|
||||
}
|
||||
|
||||
.user-tabpane {
|
||||
padding: 10px 20px;
|
||||
}
|
||||
|
||||
.crop-wrap {
|
||||
width: 600px;
|
||||
height: 350px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.crop-demo-btn {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.crop-input {
|
||||
position: absolute;
|
||||
width: 100px;
|
||||
height: 40px;
|
||||
left: 0;
|
||||
top: 0;
|
||||
opacity: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.w500 {
|
||||
width: 500px;
|
||||
}
|
||||
|
||||
.user-footer {
|
||||
display: flex;
|
||||
border-top: 1px solid rgba(83, 70, 134, 0.1);
|
||||
}
|
||||
|
||||
.user-footer-item {
|
||||
padding: 20px 0;
|
||||
width: 33.3333333333%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.user-footer>div+div {
|
||||
border-left: 1px solid rgba(83, 70, 134, 0.1);
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
.el-tabs.el-tabs--left {
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
|
@ -1,137 +0,0 @@
|
|||
<template>
|
||||
<div class="container">
|
||||
<div class="plugins-tips">通过 v-permiss 自定义指令实现权限管理,使用非 admin 账号登录,可查看效果。</div>
|
||||
<div class="mgb20">
|
||||
<span class="label">角色:</span>
|
||||
<el-select v-model="role" @change="handleChange">
|
||||
<el-option label="超级管理员" value="admin"></el-option>
|
||||
<el-option label="普通用户" value="user"></el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
<div class="mgb20 tree-wrapper">
|
||||
<el-tree
|
||||
ref="tree"
|
||||
:data="data"
|
||||
node-key="id"
|
||||
default-expand-all
|
||||
show-checkbox
|
||||
:default-checked-keys="checkedKeys"
|
||||
/>
|
||||
</div>
|
||||
<el-button type="primary" @click="onSubmit">保存权限</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="permission">
|
||||
import { ref } from 'vue';
|
||||
import { ElTree } from 'element-plus';
|
||||
import { usePermissStore } from '../store/permiss';
|
||||
|
||||
const role = ref<string>('admin');
|
||||
|
||||
interface Tree {
|
||||
id: string;
|
||||
label: string;
|
||||
children?: Tree[];
|
||||
}
|
||||
|
||||
const data: Tree[] = [
|
||||
{
|
||||
id: '1',
|
||||
label: '系统首页'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
label: '基础表格',
|
||||
children: [
|
||||
{
|
||||
id: '15',
|
||||
label: '编辑'
|
||||
},
|
||||
{
|
||||
id: '16',
|
||||
label: '删除'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
label: 'tab选项卡'
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
label: '表单相关',
|
||||
children: [
|
||||
{
|
||||
id: '5',
|
||||
label: '基本表单'
|
||||
},
|
||||
{
|
||||
id: '6',
|
||||
label: '文件上传'
|
||||
},
|
||||
{
|
||||
id: '7',
|
||||
label: '三级菜单',
|
||||
children: [
|
||||
{
|
||||
id: '8',
|
||||
label: '富文本编辑器'
|
||||
},
|
||||
{
|
||||
id: '9',
|
||||
label: 'markdown编辑器'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: '10',
|
||||
label: '自定义图标'
|
||||
},
|
||||
{
|
||||
id: '11',
|
||||
label: 'schart图表'
|
||||
},
|
||||
|
||||
{
|
||||
id: '13',
|
||||
label: '权限管理'
|
||||
},
|
||||
{
|
||||
id: '14',
|
||||
label: '支持作者'
|
||||
}
|
||||
];
|
||||
|
||||
const permiss = usePermissStore();
|
||||
|
||||
// 获取当前权限
|
||||
const checkedKeys = ref<string[]>([]);
|
||||
const getPremission = () => {
|
||||
// 请求接口返回权限
|
||||
checkedKeys.value = permiss.defaultList[role.value];
|
||||
};
|
||||
getPremission();
|
||||
|
||||
// 保存权限
|
||||
const tree = ref<InstanceType<typeof ElTree>>();
|
||||
const onSubmit = () => {
|
||||
// 获取选中的权限
|
||||
console.log(tree.value!.getCheckedKeys(false));
|
||||
};
|
||||
|
||||
const handleChange = (val: string[]) => {
|
||||
tree.value!.setCheckedKeys(permiss.defaultList[role.value]);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tree-wrapper {
|
||||
max-width: 500px;
|
||||
}
|
||||
.label {
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,144 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="container">
|
||||
<TableCustom :columns="columns" :tableData="menuData" row-key="index" :has-pagination="false"
|
||||
:viewFunc="handleView" :delFunc="handleDelete" :editFunc="handleEdit">
|
||||
<template #toolbarBtn>
|
||||
<el-button type="warning" :icon="CirclePlusFilled" @click="visible = true">新增</el-button>
|
||||
</template>
|
||||
<template #icon="{ rows }">
|
||||
<el-icon>
|
||||
<component :is="rows.icon"></component>
|
||||
</el-icon>
|
||||
</template>
|
||||
</TableCustom>
|
||||
|
||||
</div>
|
||||
<el-dialog :title="isEdit ? '编辑' : '新增'" v-model="visible" width="700px" destroy-on-close
|
||||
:close-on-click-modal="false" @close="closeDialog">
|
||||
<TableEdit :form-data="rowData" :options="options" :edit="isEdit" :update="updateData">
|
||||
<template #parent>
|
||||
<el-cascader v-model="rowData.pid" :options="cascaderOptions" :props="{ checkStrictly: true }"
|
||||
clearable />
|
||||
</template>
|
||||
</TableEdit>
|
||||
</el-dialog>
|
||||
<el-dialog title="查看详情" v-model="visible1" width="700px" destroy-on-close>
|
||||
<TableDetail :data="viewData">
|
||||
<template #icon="{ rows }">
|
||||
<el-icon>
|
||||
<component :is="rows.icon"></component>
|
||||
</el-icon>
|
||||
</template>
|
||||
</TableDetail>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="system-menu">
|
||||
import { ref } from 'vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { CirclePlusFilled } from '@element-plus/icons-vue';
|
||||
import { Menus } from '@/types/menu';
|
||||
import TableCustom from '@/components/table-custom.vue';
|
||||
import TableDetail from '@/components/table-detail.vue';
|
||||
import { FormOption } from '@/types/form-option';
|
||||
import { menuData } from '@/components/menu';
|
||||
|
||||
// 表格相关
|
||||
let columns = ref([
|
||||
{ prop: 'title', label: '菜单名称', align: 'left' },
|
||||
{ prop: 'icon', label: '图标' },
|
||||
{ prop: 'index', label: '路由路径' },
|
||||
{ prop: 'permiss', label: '权限标识' },
|
||||
{ prop: 'operator', label: '操作', width: 250 },
|
||||
])
|
||||
|
||||
const getOptions = (data: any) => {
|
||||
return data.map(item => {
|
||||
const a: any = {
|
||||
label: item.title,
|
||||
value: item.id,
|
||||
}
|
||||
if (item.children) {
|
||||
a.children = getOptions(item.children)
|
||||
}
|
||||
return a
|
||||
})
|
||||
}
|
||||
const cascaderOptions = ref(getOptions(menuData));
|
||||
|
||||
|
||||
// 新增/编辑弹窗相关
|
||||
let options = ref<FormOption>({
|
||||
labelWidth: '100px',
|
||||
span: 12,
|
||||
list: [
|
||||
{ type: 'input', label: '菜单名称', prop: 'title', required: true },
|
||||
{ type: 'input', label: '路由路径', prop: 'index', required: true },
|
||||
{ type: 'input', label: '图标', prop: 'icon' },
|
||||
{ type: 'input', label: '权限标识', prop: 'permiss' },
|
||||
{ type: 'parent', label: '父菜单', prop: 'parent' },
|
||||
]
|
||||
})
|
||||
const visible = ref(false);
|
||||
const isEdit = ref(false);
|
||||
const rowData = ref<any>({});
|
||||
const handleEdit = (row: Menus) => {
|
||||
rowData.value = { ...row };
|
||||
isEdit.value = true;
|
||||
visible.value = true;
|
||||
};
|
||||
const updateData = () => {
|
||||
closeDialog();
|
||||
};
|
||||
|
||||
const closeDialog = () => {
|
||||
visible.value = false;
|
||||
isEdit.value = false;
|
||||
};
|
||||
|
||||
// 查看详情弹窗相关
|
||||
const visible1 = ref(false);
|
||||
const viewData = ref({
|
||||
row: {},
|
||||
list: []
|
||||
});
|
||||
const handleView = (row: Menus) => {
|
||||
viewData.value.row = { ...row }
|
||||
viewData.value.list = [
|
||||
{
|
||||
prop: 'id',
|
||||
label: '菜单ID',
|
||||
},
|
||||
{
|
||||
prop: 'pid',
|
||||
label: '父菜单ID',
|
||||
},
|
||||
{
|
||||
prop: 'title',
|
||||
label: '菜单名称',
|
||||
},
|
||||
{
|
||||
prop: 'index',
|
||||
label: '路由路径',
|
||||
},
|
||||
{
|
||||
prop: 'permiss',
|
||||
label: '权限标识',
|
||||
},
|
||||
{
|
||||
prop: 'icon',
|
||||
label: '图标',
|
||||
},
|
||||
]
|
||||
visible1.value = true;
|
||||
};
|
||||
|
||||
// 删除相关
|
||||
const handleDelete = (row: Menus) => {
|
||||
ElMessage.success('删除成功');
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
|
@ -0,0 +1,76 @@
|
|||
<template>
|
||||
<div>
|
||||
<el-tree
|
||||
class="mgb10"
|
||||
ref="tree"
|
||||
:data="data"
|
||||
node-key="id"
|
||||
default-expand-all
|
||||
show-checkbox
|
||||
:default-checked-keys="checkedKeys"
|
||||
/>
|
||||
<el-button type="primary" @click="onSubmit">保存权限</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { ElTree } from 'element-plus';
|
||||
import { menuData } from '@/components/menu';
|
||||
|
||||
const props = defineProps({
|
||||
permissOptions: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const menuObj = ref({});
|
||||
// const data = menuData.map((item) => {
|
||||
// if (item.children) {
|
||||
// menuObj.value[item.id] = item.children.map((sub) => sub.id);
|
||||
// }
|
||||
// return {
|
||||
// id: item.id,
|
||||
// label: item.title,
|
||||
// children: item.children?.map((child) => {
|
||||
// return {
|
||||
// id: child.id,
|
||||
// label: child.title,
|
||||
// };
|
||||
// }),
|
||||
// };
|
||||
// });
|
||||
|
||||
const getTreeData = (data) => {
|
||||
return data.map((item) => {
|
||||
const obj: any = {
|
||||
id: item.id,
|
||||
label: item.title,
|
||||
};
|
||||
if (item.children) {
|
||||
menuObj.value[item.id] = item.children.map((sub) => sub.id);
|
||||
obj.children = getTreeData(item.children);
|
||||
}
|
||||
return obj;
|
||||
});
|
||||
};
|
||||
const data = getTreeData(menuData);
|
||||
const checkData = (data: string[]) => {
|
||||
return data.filter((item) => {
|
||||
return !menuObj.value[item] || data.toString().includes(menuObj.value[item].toString());
|
||||
});
|
||||
};
|
||||
// 获取当前权限
|
||||
const checkedKeys = ref<string[]>(checkData(props.permissOptions.permiss));
|
||||
|
||||
// 保存权限
|
||||
const tree = ref<InstanceType<typeof ElTree>>();
|
||||
const onSubmit = () => {
|
||||
// 获取选中的权限
|
||||
const keys = [...tree.value!.getCheckedKeys(false), ...tree.value!.getHalfCheckedKeys()] as number[];
|
||||
console.log(keys);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
|
@ -0,0 +1,162 @@
|
|||
<template>
|
||||
<div>
|
||||
<TableSearch :query="query" :options="searchOpt" :search="handleSearch" />
|
||||
<div class="container">
|
||||
|
||||
<TableCustom :columns="columns" :tableData="tableData" :total="page.total" :viewFunc="handleView"
|
||||
:delFunc="handleDelete" :page-change="changePage" :editFunc="handleEdit">
|
||||
<template #toolbarBtn>
|
||||
<el-button type="warning" :icon="CirclePlusFilled" @click="visible = true">新增</el-button>
|
||||
</template>
|
||||
<template #status="{ rows }">
|
||||
<el-tag type="success" v-if="rows.status">启用</el-tag>
|
||||
<el-tag type="danger" v-else>禁用</el-tag>
|
||||
</template>
|
||||
<template #permissions="{ rows }">
|
||||
<el-button type="primary" size="small" plain @click="handlePermission(rows)">管理</el-button>
|
||||
</template>
|
||||
</TableCustom>
|
||||
</div>
|
||||
<el-dialog :title="isEdit ? '编辑' : '新增'" v-model="visible" width="700px" destroy-on-close
|
||||
:close-on-click-modal="false" @close="closeDialog">
|
||||
<TableEdit :form-data="rowData" :options="options" :edit="isEdit" :update="updateData" />
|
||||
</el-dialog>
|
||||
<el-dialog title="查看详情" v-model="visible1" width="700px" destroy-on-close>
|
||||
<TableDetail :data="viewData">
|
||||
<template #status="{ rows }">
|
||||
<el-tag type="success" v-if="rows.status">启用</el-tag>
|
||||
<el-tag type="danger" v-else>禁用</el-tag>
|
||||
</template>
|
||||
</TableDetail>
|
||||
</el-dialog>
|
||||
<el-dialog title="权限管理" v-model="visible2" width="500px" destroy-on-close>
|
||||
<RolePermission :permiss-options="permissOptions" />
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="system-role">
|
||||
import { ref, reactive } from 'vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { Role } from '@/types/role';
|
||||
import { fetchRoleData } from '@/api';
|
||||
import TableCustom from '@/components/table-custom.vue';
|
||||
import TableDetail from '@/components/table-detail.vue';
|
||||
import RolePermission from './role-permission.vue'
|
||||
import { CirclePlusFilled } from '@element-plus/icons-vue';
|
||||
import { FormOption, FormOptionList } from '@/types/form-option';
|
||||
|
||||
// 查询相关
|
||||
const query = reactive({
|
||||
name: '',
|
||||
});
|
||||
const searchOpt = ref<FormOptionList[]>([
|
||||
{ type: 'input', label: '角色名称:', prop: 'name' }
|
||||
])
|
||||
const handleSearch = () => {
|
||||
changePage(1);
|
||||
};
|
||||
|
||||
// 表格相关
|
||||
let columns = ref([
|
||||
{ type: 'index', label: '序号', width: 55, align: 'center' },
|
||||
{ prop: 'name', label: '角色名称' },
|
||||
{ prop: 'key', label: '角色标识' },
|
||||
{ prop: 'status', label: '状态' },
|
||||
{ prop: 'permissions', label: '权限管理' },
|
||||
{ prop: 'operator', label: '操作', width: 250 },
|
||||
])
|
||||
const page = reactive({
|
||||
index: 1,
|
||||
size: 10,
|
||||
total: 0,
|
||||
})
|
||||
const tableData = ref<Role[]>([]);
|
||||
const getData = async () => {
|
||||
const res = await fetchRoleData()
|
||||
tableData.value = res.data.list;
|
||||
page.total = res.data.pageTotal;
|
||||
};
|
||||
getData();
|
||||
const changePage = (val: number) => {
|
||||
page.index = val;
|
||||
getData();
|
||||
};
|
||||
|
||||
// 新增/编辑弹窗相关
|
||||
const options = ref<FormOption>({
|
||||
labelWidth: '100px',
|
||||
span: 24,
|
||||
list: [
|
||||
{ type: 'input', label: '角色名称', prop: 'name', required: true },
|
||||
{ type: 'input', label: '角色标识', prop: 'key', required: true },
|
||||
{ type: 'switch', label: '状态', prop: 'status', required: false, activeText: '启用', inactiveText: '禁用' },
|
||||
]
|
||||
})
|
||||
const visible = ref(false);
|
||||
const isEdit = ref(false);
|
||||
const rowData = ref({});
|
||||
const handleEdit = (row: Role) => {
|
||||
rowData.value = { ...row };
|
||||
isEdit.value = true;
|
||||
visible.value = true;
|
||||
};
|
||||
const updateData = () => {
|
||||
closeDialog();
|
||||
getData();
|
||||
};
|
||||
const closeDialog = () => {
|
||||
visible.value = false;
|
||||
isEdit.value = false;
|
||||
rowData.value = {};
|
||||
};
|
||||
|
||||
// 查看详情弹窗相关
|
||||
const visible1 = ref(false);
|
||||
const viewData = ref({
|
||||
row: {},
|
||||
list: [],
|
||||
column: 1
|
||||
});
|
||||
const handleView = (row: Role) => {
|
||||
viewData.value.row = { ...row }
|
||||
viewData.value.list = [
|
||||
{
|
||||
prop: 'id',
|
||||
label: '角色ID',
|
||||
},
|
||||
{
|
||||
prop: 'name',
|
||||
label: '角色名称',
|
||||
},
|
||||
{
|
||||
prop: 'key',
|
||||
label: '角色标识',
|
||||
},
|
||||
{
|
||||
prop: 'status',
|
||||
label: '角色状态',
|
||||
},
|
||||
]
|
||||
visible1.value = true;
|
||||
};
|
||||
|
||||
// 删除相关
|
||||
const handleDelete = (row: Role) => {
|
||||
ElMessage.success('删除成功');
|
||||
}
|
||||
|
||||
|
||||
// 权限管理弹窗相关
|
||||
const visible2 = ref(false);
|
||||
const permissOptions = ref({})
|
||||
const handlePermission = (row: Role) => {
|
||||
visible2.value = true;
|
||||
permissOptions.value = {
|
||||
id: row.id,
|
||||
permiss: row.permiss
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
|
@ -0,0 +1,148 @@
|
|||
<template>
|
||||
<div>
|
||||
<TableSearch :query="query" :options="searchOpt" :search="handleSearch" />
|
||||
<div class="container">
|
||||
<TableCustom :columns="columns" :tableData="tableData" :total="page.total" :viewFunc="handleView"
|
||||
:delFunc="handleDelete" :page-change="changePage" :editFunc="handleEdit">
|
||||
<template #toolbarBtn>
|
||||
<el-button type="warning" :icon="CirclePlusFilled" @click="visible = true">新增</el-button>
|
||||
</template>
|
||||
</TableCustom>
|
||||
|
||||
</div>
|
||||
<el-dialog :title="isEdit ? '编辑' : '新增'" v-model="visible" width="700px" destroy-on-close
|
||||
:close-on-click-modal="false" @close="closeDialog">
|
||||
<TableEdit :form-data="rowData" :options="options" :edit="isEdit" :update="updateData" />
|
||||
</el-dialog>
|
||||
<el-dialog title="查看详情" v-model="visible1" width="700px" destroy-on-close>
|
||||
<TableDetail :data="viewData"></TableDetail>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="system-user">
|
||||
import { ref, reactive } from 'vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { CirclePlusFilled } from '@element-plus/icons-vue';
|
||||
import { User } from '@/types/user';
|
||||
import { fetchUserData } from '@/api';
|
||||
import TableCustom from '@/components/table-custom.vue';
|
||||
import TableDetail from '@/components/table-detail.vue';
|
||||
import TableSearch from '@/components/table-search.vue';
|
||||
import { FormOption, FormOptionList } from '@/types/form-option';
|
||||
|
||||
// 查询相关
|
||||
const query = reactive({
|
||||
name: '',
|
||||
});
|
||||
const searchOpt = ref<FormOptionList[]>([
|
||||
{ type: 'input', label: '用户名:', prop: 'name' }
|
||||
])
|
||||
const handleSearch = () => {
|
||||
changePage(1);
|
||||
};
|
||||
|
||||
// 表格相关
|
||||
let columns = ref([
|
||||
{ type: 'index', label: '序号', width: 55, align: 'center' },
|
||||
{ prop: 'name', label: '用户名' },
|
||||
{ prop: 'phone', label: '手机号' },
|
||||
{ prop: 'role', label: '角色' },
|
||||
{ prop: 'operator', label: '操作', width: 250 },
|
||||
])
|
||||
const page = reactive({
|
||||
index: 1,
|
||||
size: 10,
|
||||
total: 0,
|
||||
})
|
||||
const tableData = ref<User[]>([]);
|
||||
const getData = async () => {
|
||||
const res = await fetchUserData()
|
||||
tableData.value = res.data.list;
|
||||
page.total = res.data.pageTotal;
|
||||
};
|
||||
getData();
|
||||
|
||||
const changePage = (val: number) => {
|
||||
page.index = val;
|
||||
getData();
|
||||
};
|
||||
|
||||
// 新增/编辑弹窗相关
|
||||
let options = ref<FormOption>({
|
||||
labelWidth: '100px',
|
||||
span: 12,
|
||||
list: [
|
||||
{ type: 'input', label: '用户名', prop: 'name', required: true },
|
||||
{ type: 'input', label: '手机号', prop: 'phone', required: true },
|
||||
{ type: 'input', label: '密码', prop: 'password', required: true },
|
||||
{ type: 'input', label: '邮箱', prop: 'email', required: true },
|
||||
{ type: 'input', label: '角色', prop: 'role', required: true },
|
||||
]
|
||||
})
|
||||
const visible = ref(false);
|
||||
const isEdit = ref(false);
|
||||
const rowData = ref({});
|
||||
const handleEdit = (row: User) => {
|
||||
rowData.value = { ...row };
|
||||
isEdit.value = true;
|
||||
visible.value = true;
|
||||
};
|
||||
const updateData = () => {
|
||||
closeDialog();
|
||||
getData();
|
||||
};
|
||||
|
||||
const closeDialog = () => {
|
||||
visible.value = false;
|
||||
isEdit.value = false;
|
||||
};
|
||||
|
||||
// 查看详情弹窗相关
|
||||
const visible1 = ref(false);
|
||||
const viewData = ref({
|
||||
row: {},
|
||||
list: []
|
||||
});
|
||||
const handleView = (row: User) => {
|
||||
viewData.value.row = { ...row }
|
||||
viewData.value.list = [
|
||||
{
|
||||
prop: 'id',
|
||||
label: '用户ID',
|
||||
},
|
||||
{
|
||||
prop: 'name',
|
||||
label: '用户名',
|
||||
},
|
||||
{
|
||||
prop: 'password',
|
||||
label: '密码',
|
||||
},
|
||||
{
|
||||
prop: 'email',
|
||||
label: '邮箱',
|
||||
},
|
||||
{
|
||||
prop: 'phone',
|
||||
label: '电话',
|
||||
},
|
||||
{
|
||||
prop: 'role',
|
||||
label: '角色',
|
||||
},
|
||||
{
|
||||
prop: 'date',
|
||||
label: '注册日期',
|
||||
},
|
||||
]
|
||||
visible1.value = true;
|
||||
};
|
||||
|
||||
// 删除相关
|
||||
const handleDelete = (row: User) => {
|
||||
ElMessage.success('删除成功');
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
|
@ -1,194 +0,0 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="container">
|
||||
<div class="search-box">
|
||||
<el-input v-model="query.name" placeholder="用户名" class="search-input mr10" clearable></el-input>
|
||||
<el-button type="primary" :icon="Search" @click="handleSearch">搜索</el-button>
|
||||
<el-button type="warning" :icon="CirclePlusFilled" @click="visible = true">新增</el-button>
|
||||
</div>
|
||||
<el-table :data="tableData" border class="table" ref="multipleTable" header-cell-class-name="table-header">
|
||||
<el-table-column prop="id" label="ID" width="55" align="center"></el-table-column>
|
||||
<el-table-column prop="name" label="用户名" align="center"></el-table-column>
|
||||
<el-table-column label="账户余额" align="center">
|
||||
<template #default="scope">¥{{ scope.row.money }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="头像(查看大图)" align="center">
|
||||
<template #default="scope">
|
||||
<el-image
|
||||
class="table-td-thumb"
|
||||
:src="scope.row.thumb"
|
||||
:z-index="10"
|
||||
:preview-src-list="[scope.row.thumb]"
|
||||
preview-teleported
|
||||
>
|
||||
</el-image>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="address" label="地址" align="center"></el-table-column>
|
||||
<el-table-column label="账户状态" align="center">
|
||||
<template #default="scope">
|
||||
<el-tag :type="scope.row.state ? 'success' : 'danger'">
|
||||
{{ scope.row.state ? '正常' : '异常' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="date" label="注册时间" align="center"></el-table-column>
|
||||
<el-table-column label="操作" width="280" align="center">
|
||||
<template #default="scope">
|
||||
<el-button type="warning" size="small" :icon="View" @click="handleView(scope.row)">
|
||||
查看
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
size="small"
|
||||
:icon="Edit"
|
||||
@click="handleEdit(scope.$index, scope.row)"
|
||||
v-permiss="15"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
size="small"
|
||||
:icon="Delete"
|
||||
@click="handleDelete(scope.$index)"
|
||||
v-permiss="16"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="pagination">
|
||||
<el-pagination
|
||||
background
|
||||
layout="total, prev, pager, next"
|
||||
:current-page="query.pageIndex"
|
||||
:page-size="query.pageSize"
|
||||
:total="pageTotal"
|
||||
@current-change="handlePageChange"
|
||||
></el-pagination>
|
||||
</div>
|
||||
</div>
|
||||
<el-dialog
|
||||
:title="idEdit ? '编辑用户' : '新增用户'"
|
||||
v-model="visible"
|
||||
width="500px"
|
||||
destroy-on-close
|
||||
:close-on-click-modal="false"
|
||||
@close="closeDialog"
|
||||
>
|
||||
<TableEdit :data="rowData" :edit="idEdit" :update="updateData" />
|
||||
</el-dialog>
|
||||
<el-dialog title="查看用户详情" v-model="visible1" width="700px" destroy-on-close>
|
||||
<TableDetail :data="rowData" />
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="basetable">
|
||||
import { ref, reactive } from 'vue';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { Delete, Edit, Search, CirclePlusFilled, View } from '@element-plus/icons-vue';
|
||||
import { fetchData } from '../api/index';
|
||||
import TableEdit from '../components/table-edit.vue';
|
||||
import TableDetail from '../components/table-detail.vue';
|
||||
|
||||
interface TableItem {
|
||||
id: number;
|
||||
name: string;
|
||||
thumb: string;
|
||||
money: number;
|
||||
state: string;
|
||||
date: string;
|
||||
address: string;
|
||||
}
|
||||
|
||||
const query = reactive({
|
||||
address: '',
|
||||
name: '',
|
||||
pageIndex: 1,
|
||||
pageSize: 10
|
||||
});
|
||||
const tableData = ref<TableItem[]>([]);
|
||||
const pageTotal = ref(0);
|
||||
// 获取表格数据
|
||||
const getData = async () => {
|
||||
const res = await fetchData();
|
||||
tableData.value = res.data.list;
|
||||
pageTotal.value = res.data.pageTotal || 50;
|
||||
};
|
||||
getData();
|
||||
|
||||
// 查询操作
|
||||
const handleSearch = () => {
|
||||
query.pageIndex = 1;
|
||||
getData();
|
||||
};
|
||||
// 分页导航
|
||||
const handlePageChange = (val: number) => {
|
||||
query.pageIndex = val;
|
||||
getData();
|
||||
};
|
||||
|
||||
// 删除操作
|
||||
const handleDelete = (index: number) => {
|
||||
// 二次确认删除
|
||||
ElMessageBox.confirm('确定要删除吗?', '提示', {
|
||||
type: 'warning'
|
||||
})
|
||||
.then(() => {
|
||||
ElMessage.success('删除成功');
|
||||
tableData.value.splice(index, 1);
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
const visible = ref(false);
|
||||
let idx: number = -1;
|
||||
const idEdit = ref(false);
|
||||
const rowData = ref({});
|
||||
const handleEdit = (index: number, row: TableItem) => {
|
||||
idx = index;
|
||||
rowData.value = row;
|
||||
idEdit.value = true;
|
||||
visible.value = true;
|
||||
};
|
||||
const updateData = (row: TableItem) => {
|
||||
idEdit.value ? (tableData.value[idx] = row) : tableData.value.unshift(row);
|
||||
console.log(tableData.value);
|
||||
closeDialog();
|
||||
};
|
||||
|
||||
const closeDialog = () => {
|
||||
visible.value = false;
|
||||
idEdit.value = false;
|
||||
};
|
||||
|
||||
const visible1 = ref(false);
|
||||
const handleView = (row: TableItem) => {
|
||||
rowData.value = row;
|
||||
visible1.value = true;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.search-box {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.mr10 {
|
||||
margin-right: 10px;
|
||||
}
|
||||
.table-td-thumb {
|
||||
display: block;
|
||||
margin: auto;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,169 @@
|
|||
<template>
|
||||
<div>
|
||||
<TableSearch :query="query" :options="searchOpt" :search="handleSearch" />
|
||||
<div class="container">
|
||||
<TableCustom :columns="columns" :tableData="tableData" :total="page.total" :viewFunc="handleView"
|
||||
:delFunc="handleDelete" :editFunc="handleEdit" :refresh="getData" :currentPage="page.index"
|
||||
:changePage="changePage">
|
||||
<template #toolbarBtn>
|
||||
<el-button type="warning" :icon="CirclePlusFilled" @click="visible = true">新增</el-button>
|
||||
</template>
|
||||
<template #money="{ rows }">
|
||||
¥{{ rows.money }}
|
||||
</template>
|
||||
<template #thumb="{ rows }">
|
||||
<el-image class="table-td-thumb" :src="rows.thumb" :z-index="10" :preview-src-list="[rows.thumb]"
|
||||
preview-teleported>
|
||||
</el-image>
|
||||
</template>
|
||||
<template #state="{ rows }">
|
||||
<el-tag :type="rows.state ? 'success' : 'danger'">
|
||||
{{ rows.state ? '正常' : '异常' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</TableCustom>
|
||||
|
||||
</div>
|
||||
<el-dialog :title="isEdit ? '编辑' : '新增'" v-model="visible" width="700px" destroy-on-close
|
||||
:close-on-click-modal="false" @close="closeDialog">
|
||||
<TableEdit :form-data="rowData" :options="options" :edit="isEdit" :update="updateData">
|
||||
<template #thumb="{ rows }">
|
||||
<img class="table-td-thumb" :src="rows.thumb"></img>
|
||||
</template>
|
||||
</TableEdit>
|
||||
</el-dialog>
|
||||
<el-dialog title="查看详情" v-model="visible1" width="700px" destroy-on-close>
|
||||
<TableDetail :data="viewData">
|
||||
<template #thumb="{ rows }">
|
||||
<el-image :src="rows.thumb"></el-image>
|
||||
</template>
|
||||
</TableDetail>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="basetable">
|
||||
import { ref, reactive } from 'vue';
|
||||
import { ElMessage, } from 'element-plus';
|
||||
import { CirclePlusFilled } from '@element-plus/icons-vue';
|
||||
import { fetchData } from '@/api/index';
|
||||
import TableCustom from '@/components/table-custom.vue';
|
||||
import TableDetail from '@/components/table-detail.vue';
|
||||
import TableSearch from '@/components/table-search.vue';
|
||||
import { TableItem } from '@/types/table';
|
||||
import { FormOption, FormOptionList } from '@/types/form-option';
|
||||
|
||||
// 查询相关
|
||||
const query = reactive({
|
||||
name: '',
|
||||
});
|
||||
const searchOpt = ref<FormOptionList[]>([
|
||||
{ type: 'input', label: '用户名:', prop: 'name' }
|
||||
])
|
||||
const handleSearch = () => {
|
||||
changePage(1);
|
||||
};
|
||||
|
||||
// 表格相关
|
||||
let columns = ref([
|
||||
{ type: 'selection' },
|
||||
{ type: 'index', label: '序号', width: 55, align: 'center' },
|
||||
{ prop: 'name', label: '用户名' },
|
||||
{ prop: 'money', label: '账户余额' },
|
||||
{ prop: 'thumb', label: '头像' },
|
||||
{ prop: 'state', label: '账户状态' },
|
||||
{ prop: 'operator', label: '操作', width: 250 },
|
||||
])
|
||||
const page = reactive({
|
||||
index: 1,
|
||||
size: 10,
|
||||
total: 200,
|
||||
})
|
||||
const tableData = ref<TableItem[]>([]);
|
||||
const getData = async () => {
|
||||
const res = await fetchData()
|
||||
tableData.value = res.data.list;
|
||||
};
|
||||
getData();
|
||||
|
||||
const changePage = (val: number) => {
|
||||
page.index = val;
|
||||
getData();
|
||||
};
|
||||
|
||||
// 新增/编辑弹窗相关
|
||||
let options = ref<FormOption>({
|
||||
labelWidth: '100px',
|
||||
span: 24,
|
||||
list: [
|
||||
{ type: 'input', label: '用户名', prop: 'name', required: true },
|
||||
{ type: 'number', label: '账户余额', prop: 'money', required: true },
|
||||
{ type: 'switch', activeText: '正常', inactiveText: '异常', label: '账户状态', prop: 'state', required: true },
|
||||
{ type: 'upload', label: '头像', prop: 'thumb', required: true },
|
||||
]
|
||||
})
|
||||
const visible = ref(false);
|
||||
const isEdit = ref(false);
|
||||
const rowData = ref({});
|
||||
const handleEdit = (row: TableItem) => {
|
||||
rowData.value = { ...row };
|
||||
isEdit.value = true;
|
||||
visible.value = true;
|
||||
};
|
||||
const updateData = () => {
|
||||
closeDialog();
|
||||
getData();
|
||||
};
|
||||
|
||||
const closeDialog = () => {
|
||||
visible.value = false;
|
||||
isEdit.value = false;
|
||||
};
|
||||
|
||||
// 查看详情弹窗相关
|
||||
const visible1 = ref(false);
|
||||
const viewData = ref({
|
||||
row: {},
|
||||
list: []
|
||||
});
|
||||
const handleView = (row: TableItem) => {
|
||||
viewData.value.row = { ...row }
|
||||
viewData.value.list = [
|
||||
{
|
||||
prop: 'id',
|
||||
label: '用户ID',
|
||||
},
|
||||
{
|
||||
prop: 'name',
|
||||
label: '用户名',
|
||||
},
|
||||
{
|
||||
prop: 'money',
|
||||
label: '账户余额',
|
||||
},
|
||||
{
|
||||
prop: 'state',
|
||||
label: '账户状态',
|
||||
},
|
||||
{
|
||||
prop: 'thumb',
|
||||
label: '头像',
|
||||
},
|
||||
]
|
||||
visible1.value = true;
|
||||
};
|
||||
|
||||
// 删除相关
|
||||
const handleDelete = (row: TableItem) => {
|
||||
ElMessage.success('删除成功');
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.table-td-thumb {
|
||||
display: block;
|
||||
margin: auto;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
</style>
|
|
@ -79,16 +79,16 @@ const exportXlsx = () => {
|
|||
.handle-input {
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.table {
|
||||
width: 100%;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.red {
|
||||
color: #f56c6c;
|
||||
}
|
||||
.mr10 {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.table-td-thumb {
|
||||
display: block;
|
||||
margin: auto;
|
|
@ -2,14 +2,8 @@
|
|||
<div>
|
||||
<div class="container">
|
||||
<div class="handle-box">
|
||||
<el-upload
|
||||
action="#"
|
||||
:limit="1"
|
||||
accept=".xlsx, .xls"
|
||||
:show-file-list="false"
|
||||
:before-upload="beforeUpload"
|
||||
:http-request="handleMany"
|
||||
>
|
||||
<el-upload action="#" :limit="1" accept=".xlsx, .xls" :show-file-list="false"
|
||||
:before-upload="beforeUpload" :http-request="handleMany">
|
||||
<el-button class="mr10" type="success">批量导入</el-button>
|
||||
</el-upload>
|
||||
<el-link href="/template.xlsx" target="_blank">下载模板</el-link>
|
||||
|
@ -112,7 +106,4 @@ const handleMany = async () => {
|
|||
width: 100%;
|
||||
font-size: 14px;
|
||||
}
|
||||
.mr10 {
|
||||
margin-right: 10px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,79 @@
|
|||
<template>
|
||||
<div class="container">
|
||||
<TableCustom :columns="columns" :tableData="tableData" :hasToolbar="false" :hasPagination="false">
|
||||
<template #name="{ rows }">
|
||||
<el-input v-if="rows.editing" v-model="rows.name"></el-input>
|
||||
<span v-else>{{ rows.name }}</span>
|
||||
</template>
|
||||
<template #password="{ rows }">
|
||||
<el-input v-if="rows.editing" v-model="rows.password"></el-input>
|
||||
<span v-else>{{ rows.password }}</span>
|
||||
</template>
|
||||
<template #email="{ rows }">
|
||||
<el-input v-if="rows.editing" v-model="rows.email"></el-input>
|
||||
<span v-else>{{ rows.email }}</span>
|
||||
</template>
|
||||
<template #role="{ rows }">
|
||||
<el-select v-if="rows.editing" v-model="rows.role">
|
||||
<el-option label="管理员" value="管理员"></el-option>
|
||||
<el-option label="普通用户" value="普通用户"></el-option>
|
||||
</el-select>
|
||||
<span v-else>{{ rows.role }}</span>
|
||||
</template>
|
||||
<template #operator="{ rows, index }">
|
||||
<template v-if="!rows.editing">
|
||||
<el-button type="primary" size="small" :icon="Edit" @click="handleEdit(rows)">
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button type="danger" size="small" :icon="Delete" @click="">
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-button type="success" size="small" :icon="Select" @click="rows.editing = false">
|
||||
保存
|
||||
</el-button>
|
||||
<el-button type="default" size="small" :icon="CloseBold" @click="handleCancel(rows, index)">
|
||||
取消
|
||||
</el-button>
|
||||
</template>
|
||||
</template>
|
||||
</TableCustom>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="table-editor">
|
||||
import { ref } from 'vue';
|
||||
import { Delete, Edit, CloseBold, Select } from '@element-plus/icons-vue';
|
||||
import TableCustom from '@/components/table-custom.vue';
|
||||
import { fetchUserData } from '@/api/index';
|
||||
|
||||
let columns = ref([
|
||||
{ type: 'index', label: '序号', width: 55, align: 'center' },
|
||||
{ prop: 'name', label: '用户名' },
|
||||
{ prop: 'password', label: '密码' },
|
||||
{ prop: 'email', label: '邮箱' },
|
||||
{ prop: 'role', label: '角色' },
|
||||
{ prop: 'operator', label: '操作', width: 180 },
|
||||
])
|
||||
const tableData = ref([]);
|
||||
const getData = async () => {
|
||||
const res = await fetchUserData();
|
||||
tableData.value = res.data.list;
|
||||
};
|
||||
getData();
|
||||
|
||||
const rowData = ref({})
|
||||
|
||||
const handleEdit = (row) => {
|
||||
rowData.value = { ...row };
|
||||
row.editing = true;
|
||||
};
|
||||
|
||||
const handleCancel = (row, index) => {
|
||||
row.editing = false;
|
||||
tableData.value[index] = { ...rowData.value };
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
|
@ -1,116 +0,0 @@
|
|||
<template>
|
||||
<div class="container">
|
||||
<el-tabs v-model="message">
|
||||
<el-tab-pane :label="`未读消息(${state.unread.length})`" name="first">
|
||||
<el-table :data="state.unread" :show-header="false" style="width: 100%">
|
||||
<el-table-column>
|
||||
<template #default="scope">
|
||||
<span class="message-title">{{ scope.row.title }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="date" width="180"></el-table-column>
|
||||
<el-table-column width="120">
|
||||
<template #default="scope">
|
||||
<el-button size="small" @click="handleRead(scope.$index)">标为已读</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="handle-row">
|
||||
<el-button type="primary">全部标为已读</el-button>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="`已读消息(${state.read.length})`" name="second">
|
||||
<template v-if="message === 'second'">
|
||||
<el-table :data="state.read" :show-header="false" style="width: 100%">
|
||||
<el-table-column>
|
||||
<template #default="scope">
|
||||
<span class="message-title">{{ scope.row.title }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="date" width="180"></el-table-column>
|
||||
<el-table-column width="120">
|
||||
<template #default="scope">
|
||||
<el-button type="danger" size="small" @click="handleDel(scope.$index)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="handle-row">
|
||||
<el-button type="danger">删除全部</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="`回收站(${state.recycle.length})`" name="third">
|
||||
<template v-if="message === 'third'">
|
||||
<el-table :data="state.recycle" :show-header="false" style="width: 100%">
|
||||
<el-table-column>
|
||||
<template #default="scope">
|
||||
<span class="message-title">{{ scope.row.title }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="date" width="180"></el-table-column>
|
||||
<el-table-column width="120">
|
||||
<template #default="scope">
|
||||
<el-button size="small" @click="handleRestore(scope.$index)">还原</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="handle-row">
|
||||
<el-button type="danger">清空回收站</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="tabs">
|
||||
import { ref, reactive } from 'vue';
|
||||
|
||||
const message = ref('first');
|
||||
const state = reactive({
|
||||
unread: [
|
||||
{
|
||||
date: '2018-04-19 20:00:00',
|
||||
title: '【系统通知】该系统将于今晚凌晨2点到5点进行升级维护'
|
||||
},
|
||||
{
|
||||
date: '2018-04-19 21:00:00',
|
||||
title: '今晚12点整发大红包,先到先得'
|
||||
}
|
||||
],
|
||||
read: [
|
||||
{
|
||||
date: '2018-04-19 20:00:00',
|
||||
title: '【系统通知】该系统将于今晚凌晨2点到5点进行升级维护'
|
||||
}
|
||||
],
|
||||
recycle: [
|
||||
{
|
||||
date: '2018-04-19 20:00:00',
|
||||
title: '【系统通知】该系统将于今晚凌晨2点到5点进行升级维护'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const handleRead = (index: number) => {
|
||||
const item = state.unread.splice(index, 1);
|
||||
state.read = item.concat(state.read);
|
||||
};
|
||||
const handleDel = (index: number) => {
|
||||
const item = state.read.splice(index, 1);
|
||||
state.recycle = item.concat(state.recycle);
|
||||
};
|
||||
const handleRestore = (index: number) => {
|
||||
const item = state.recycle.splice(index, 1);
|
||||
state.read = item.concat(state.read);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.message-title {
|
||||
cursor: pointer;
|
||||
}
|
||||
.handle-row {
|
||||
margin-top: 30px;
|
||||
}
|
||||
</style>
|
|
@ -1,174 +0,0 @@
|
|||
<template>
|
||||
<div>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-card shadow="hover">
|
||||
<template #header>
|
||||
<div class="clearfix">
|
||||
<span>基础信息</span>
|
||||
</div>
|
||||
</template>
|
||||
<div class="info">
|
||||
<div class="info-image" @click="showDialog">
|
||||
<el-avatar :size="100" :src="avatarImg" />
|
||||
<span class="info-edit">
|
||||
<i class="el-icon-lx-camerafill"></i>
|
||||
</span>
|
||||
</div>
|
||||
<div class="info-name">{{ name }}</div>
|
||||
<div class="info-desc">不可能!我的代码怎么可能会有bug!</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-card shadow="hover">
|
||||
<template #header>
|
||||
<div class="clearfix">
|
||||
<span>账户编辑</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-form label-width="90px">
|
||||
<el-form-item label="用户名:"> {{ name }} </el-form-item>
|
||||
<el-form-item label="旧密码:">
|
||||
<el-input type="password" v-model="form.old"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="新密码:">
|
||||
<el-input type="password" v-model="form.new"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="个人简介:">
|
||||
<el-input v-model="form.desc"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="onSubmit">保存</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-dialog title="裁剪图片" v-model="dialogVisible" width="600px">
|
||||
<vue-cropper
|
||||
ref="cropper"
|
||||
:src="imgSrc"
|
||||
:ready="cropImage"
|
||||
:zoom="cropImage"
|
||||
:cropmove="cropImage"
|
||||
style="width: 100%; height: 400px"
|
||||
></vue-cropper>
|
||||
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button class="crop-demo-btn" type="primary"
|
||||
>选择图片
|
||||
<input class="crop-input" type="file" name="image" accept="image/*" @change="setImage" />
|
||||
</el-button>
|
||||
<el-button type="primary" @click="saveAvatar">上传并保存</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="user">
|
||||
import { reactive, ref } from 'vue';
|
||||
import VueCropper from 'vue-cropperjs';
|
||||
import 'cropperjs/dist/cropper.css';
|
||||
import avatar from '../assets/img/img.jpg';
|
||||
|
||||
const name = localStorage.getItem('ms_username');
|
||||
const form = reactive({
|
||||
old: '',
|
||||
new: '',
|
||||
desc: '不可能!我的代码怎么可能会有bug!'
|
||||
});
|
||||
const onSubmit = () => {};
|
||||
|
||||
const avatarImg = ref(avatar);
|
||||
const imgSrc = ref('');
|
||||
const cropImg = ref('');
|
||||
const dialogVisible = ref(false);
|
||||
const cropper: any = ref();
|
||||
|
||||
const showDialog = () => {
|
||||
dialogVisible.value = true;
|
||||
imgSrc.value = avatarImg.value;
|
||||
};
|
||||
|
||||
const setImage = (e: any) => {
|
||||
const file = e.target.files[0];
|
||||
if (!file.type.includes('image/')) {
|
||||
return;
|
||||
}
|
||||
const reader = new FileReader();
|
||||
reader.onload = (event: any) => {
|
||||
dialogVisible.value = true;
|
||||
imgSrc.value = event.target.result;
|
||||
cropper.value && cropper.value.replace(event.target.result);
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
};
|
||||
|
||||
const cropImage = () => {
|
||||
cropImg.value = cropper.value.getCroppedCanvas().toDataURL();
|
||||
};
|
||||
|
||||
const saveAvatar = () => {
|
||||
avatarImg.value = cropImg.value;
|
||||
dialogVisible.value = false;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.info {
|
||||
text-align: center;
|
||||
padding: 35px 0;
|
||||
}
|
||||
.info-image {
|
||||
position: relative;
|
||||
margin: auto;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
background: #f8f8f8;
|
||||
border: 1px solid #eee;
|
||||
border-radius: 50px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.info-edit {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
.info-edit i {
|
||||
color: #eee;
|
||||
font-size: 25px;
|
||||
}
|
||||
.info-image:hover .info-edit {
|
||||
opacity: 1;
|
||||
}
|
||||
.info-name {
|
||||
margin: 15px 0 10px;
|
||||
font-size: 24px;
|
||||
font-weight: 500;
|
||||
color: #262626;
|
||||
}
|
||||
.crop-demo-btn {
|
||||
position: relative;
|
||||
}
|
||||
.crop-input {
|
||||
position: absolute;
|
||||
width: 100px;
|
||||
height: 40px;
|
||||
left: 0;
|
||||
top: 0;
|
||||
opacity: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
|
@ -7,4 +7,4 @@ declare module '*.vue' {
|
|||
}
|
||||
|
||||
declare module 'vue-schart';
|
||||
declare module 'vue-cropperjs';
|
||||
declare module 'nprogress'
|
|
@ -11,7 +11,11 @@
|
|||
"isolatedModules": true,
|
||||
"esModuleInterop": true,
|
||||
"lib": ["ESNext", "DOM"],
|
||||
"skipLibCheck": true
|
||||
"skipLibCheck": true,
|
||||
"baseUrl": "./",
|
||||
"paths": {
|
||||
"@/*": ["src/*"]
|
||||
}
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.d.ts","src/**/*.vue"],
|
||||
"references": [{ "path": "./tsconfig.node.json" }]
|
||||
|
|
|
@ -18,5 +18,14 @@ export default defineConfig({
|
|||
],
|
||||
optimizeDeps: {
|
||||
include: ['schart.js']
|
||||
}
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': '/src',
|
||||
'~': '/src/assets'
|
||||
}
|
||||
},
|
||||
define: {
|
||||
__VUE_PROD_HYDRATION_MISMATCH_DETAILS__: "true",
|
||||
},
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue