init sys
This commit is contained in:
parent
c515fbc428
commit
8570aa1893
23
.gitignore
vendored
23
.gitignore
vendored
@ -9,3 +9,26 @@ docs/_book
|
||||
# TODO: where does this rule come from?
|
||||
test/
|
||||
|
||||
.DS_Store
|
||||
node_modules
|
||||
/dist
|
||||
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Log files
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
.vscode
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
140
README.md
140
README.md
@ -1,3 +1,137 @@
|
||||
# vue-manage-sys
|
||||
|
||||
基于 Vue3 + pinia + Element Plus 的后台管理系统解决方案
|
||||
# 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>
|
||||
|
||||
基于 Vue3 + pinia + Element Plus 的后台管理系统解决方案。[线上地址](https://lin-xin.gitee.io/example/work/)
|
||||
|
||||
> Vue2 版本请看 [tag-V4.2.0](https://github.com/lin-xin/vue-manage-system/tree/V4.2.0)
|
||||
|
||||
[English document](https://github.com/lin-xin/manage-system/blob/master/README_EN.md)
|
||||
|
||||
## 赞助商
|
||||
|
||||
### 好问
|
||||
|
||||
[<img src="https://static.bestqa.net/logo/bestqa_haowen.png" width="220" height="100">](https://www.bestqa.net/home/index.html)
|
||||
|
||||
专业问卷服务,一对一客服,按需定制
|
||||
|
||||
## 支持作者
|
||||
|
||||
请作者喝杯咖啡吧!(微信号:linxin_20)
|
||||
|
||||

|
||||
|
||||
## 前言
|
||||
|
||||
该方案作为一套多功能的后台框架模板,适用于绝大部分的后台管理系统开发。基于 Vue3 + pinia + typescript,引用 Element Plus 组件库,方便开发。实现逻辑简单,适合外包项目,快速交付。
|
||||
|
||||
## 功能
|
||||
|
||||
- [x] Element Plus
|
||||
- [x] vite 3
|
||||
- [x] pinia
|
||||
- [x] typescript
|
||||
- [x] 登录/注销
|
||||
- [x] Dashboard
|
||||
- [x] 表格
|
||||
- [x] Tab 选项卡
|
||||
- [x] 表单
|
||||
- [x] 图表 :bar_chart:
|
||||
- [x] 富文本/markdown编辑器
|
||||
- [x] 图片拖拽/裁剪上传
|
||||
- [x] 权限管理
|
||||
- [x] 三级菜单
|
||||
- [x] 自定义图标
|
||||
|
||||
|
||||
## 安装步骤
|
||||
> 因为使用vite3,node版本需要 14.18+
|
||||
|
||||
```
|
||||
git clone https://github.com/lin-xin/vue-manage-system.git // 把模板下载到本地
|
||||
cd vue-manage-system // 进入模板目录
|
||||
npm install // 安装项目依赖,等待安装完成之后,安装失败可用 cnpm 或 yarn
|
||||
|
||||
// 运行
|
||||
npm run dev
|
||||
|
||||
// 执行构建命令,生成的dist文件夹放在服务器下即可访问
|
||||
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)
|
||||
|
||||
119
README_EN.md
Normal file
119
README_EN.md
Normal file
@ -0,0 +1,119 @@
|
||||
# vue-manage-system
|
||||
|
||||
<a href="https://github.com/vuejs/vue">
|
||||
<img src="https://img.shields.io/badge/vue-2.6.10-brightgreen.svg" alt="vue">
|
||||
</a>
|
||||
<a href="https://github.com/ElemeFE/element">
|
||||
<img src="https://img.shields.io/badge/element--ui-2.8.2-brightgreen.svg" alt="element-ui">
|
||||
</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>
|
||||
|
||||
The web management system solution based on Vue3 and ElementPlus。[live demo](https://lin-xin.gitee.io/example/work/)
|
||||
|
||||
Please check the version of vue2 in [tag V4.2.0](https://github.com/lin-xin/vue-manage-system/tree/V4.2.0)
|
||||
|
||||
## Donation
|
||||
|
||||

|
||||
|
||||
## Preface
|
||||
|
||||
The scheme as a set of multi-function background frame templates, suitable for most of the WEB management system development. Convenient development fast simple good components based on Vue3 and ElementPlus. Color separation of color style, support manual switch themes, and it is convenient to use a custom theme color.
|
||||
|
||||
## Function
|
||||
|
||||
- [x] Element-UI
|
||||
- [x] Login/Logout
|
||||
- [x] Dashboard
|
||||
- [x] Table
|
||||
- [x] Tabs
|
||||
- [x] From
|
||||
- [x] Chart :bar_chart:
|
||||
- [x] Editor
|
||||
- [x] Markdown
|
||||
- [x] Upload pictures by clipping or dragging
|
||||
- [x] Permission
|
||||
- [x] Three level menu
|
||||
- [x] Custom icon
|
||||
|
||||
## Installation steps
|
||||
|
||||
git clone https://github.com/lin-xin/vue-manage-system.git // Clone templates
|
||||
cd vue-manage-system // Enter template directory
|
||||
npm install // Installation dependency
|
||||
|
||||
## Local development
|
||||
|
||||
npm run dev
|
||||
|
||||
## Constructing production
|
||||
|
||||
// Constructing project
|
||||
npm run build
|
||||
|
||||
## Component description and presentation
|
||||
|
||||
### vue-schart
|
||||
|
||||
Vue.js wrapper for sChart.js. Github : [vue-schart](https://github.com/lin-xin/vue-schart#/)
|
||||
|
||||
```html
|
||||
<template>
|
||||
<div>
|
||||
<schart class="wrapper" canvasId="myCanvas" :options="options"></schart>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
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>
|
||||
```
|
||||
|
||||
## Screenshot
|
||||
|
||||
### Default theme
|
||||
|
||||

|
||||
|
||||
### Login
|
||||
|
||||

|
||||
|
||||
## License
|
||||
|
||||
[MIT](https://github.com/lin-xin/vue-manage-system/blob/master/LICENSE)
|
||||
5
auto-imports.d.ts
vendored
Normal file
5
auto-imports.d.ts
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
// Generated by 'unplugin-auto-import'
|
||||
export {}
|
||||
declare global {
|
||||
|
||||
}
|
||||
53
components.d.ts
vendored
Normal file
53
components.d.ts
vendored
Normal file
@ -0,0 +1,53 @@
|
||||
// generated by unplugin-vue-components
|
||||
// We suggest you to commit this file into source control
|
||||
// Read more: https://github.com/vuejs/core/pull/3399
|
||||
import '@vue/runtime-core'
|
||||
|
||||
export {}
|
||||
|
||||
declare module '@vue/runtime-core' {
|
||||
export interface GlobalComponents {
|
||||
ElAvatar: typeof import('element-plus/es')['ElAvatar']
|
||||
ElButton: typeof import('element-plus/es')['ElButton']
|
||||
ElCard: typeof import('element-plus/es')['ElCard']
|
||||
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']
|
||||
ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
|
||||
ElDialog: typeof import('element-plus/es')['ElDialog']
|
||||
ElDropdown: typeof import('element-plus/es')['ElDropdown']
|
||||
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
|
||||
ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
|
||||
ElForm: typeof import('element-plus/es')['ElForm']
|
||||
ElFormItem: typeof import('element-plus/es')['ElFormItem']
|
||||
ElIcon: typeof import('element-plus/es')['ElIcon']
|
||||
ElImage: typeof import('element-plus/es')['ElImage']
|
||||
ElInput: typeof import('element-plus/es')['ElInput']
|
||||
ElLink: typeof import('element-plus/es')['ElLink']
|
||||
ElMenu: typeof import('element-plus/es')['ElMenu']
|
||||
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
|
||||
ElOption: typeof import('element-plus/es')['ElOption']
|
||||
ElPagination: typeof import('element-plus/es')['ElPagination']
|
||||
ElProgress: typeof import('element-plus/es')['ElProgress']
|
||||
ElRadio: typeof import('element-plus/es')['ElRadio']
|
||||
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
|
||||
ElRow: typeof import('element-plus/es')['ElRow']
|
||||
ElSelect: typeof import('element-plus/es')['ElSelect']
|
||||
ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
|
||||
ElSwitch: typeof import('element-plus/es')['ElSwitch']
|
||||
ElTable: typeof import('element-plus/es')['ElTable']
|
||||
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
|
||||
ElTabPane: typeof import('element-plus/es')['ElTabPane']
|
||||
ElTabs: typeof import('element-plus/es')['ElTabs']
|
||||
ElTag: typeof import('element-plus/es')['ElTag']
|
||||
ElTimePicker: typeof import('element-plus/es')['ElTimePicker']
|
||||
ElTooltip: typeof import('element-plus/es')['ElTooltip']
|
||||
ElUpload: typeof import('element-plus/es')['ElUpload']
|
||||
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']
|
||||
Tags: typeof import('./src/components/tags.vue')['default']
|
||||
}
|
||||
}
|
||||
22
index.html
Normal file
22
index.html
Normal file
@ -0,0 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<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">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled.
|
||||
Please enable it to continue.</strong>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
|
||||
</html>
|
||||
1298
package-lock.json
generated
Normal file
1298
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
38
package.json
Normal file
38
package.json
Normal file
@ -0,0 +1,38 @@
|
||||
{
|
||||
"name": "vue-manage-system",
|
||||
"version": "5.3.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vue-tsc --noEmit && vite build",
|
||||
"serve": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@element-plus/icons-vue": "^2.0.9",
|
||||
"axios": "^0.27.2",
|
||||
"element-plus": "^2.2.14",
|
||||
"md-editor-v3": "^2.2.1",
|
||||
"pinia": "^2.0.20",
|
||||
"vue": "^3.2.37",
|
||||
"vue-cropperjs": "^5.0.0",
|
||||
"vue-router": "^4.1.3",
|
||||
"vue-schart": "^2.0.0",
|
||||
"wangeditor": "^4.7.15",
|
||||
"xlsx": "^0.18.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^3.0.0",
|
||||
"@vue/compiler-sfc": "^3.1.2",
|
||||
"typescript": "^4.6.4",
|
||||
"unplugin-auto-import": "^0.11.2",
|
||||
"unplugin-vue-components": "^0.22.4",
|
||||
"vite": "^3.0.0",
|
||||
"vite-plugin-vue-setup-extend": "^0.4.0",
|
||||
"vue-tsc": "^0.38.4"
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
"last 2 versions",
|
||||
"not dead"
|
||||
]
|
||||
}
|
||||
40
public/table.json
Normal file
40
public/table.json
Normal file
@ -0,0 +1,40 @@
|
||||
{
|
||||
"list": [{
|
||||
"id": 1,
|
||||
"name": "张三",
|
||||
"money": 123,
|
||||
"address": "广东省东莞市长安镇",
|
||||
"state": "成功",
|
||||
"date": "2019-11-1",
|
||||
"thumb": "https://lin-xin.gitee.io/images/post/wms.png"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"name": "李四",
|
||||
"money": 456,
|
||||
"address": "广东省广州市白云区",
|
||||
"state": "成功",
|
||||
"date": "2019-10-11",
|
||||
"thumb": "https://lin-xin.gitee.io/images/post/node3.png"
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"name": "王五",
|
||||
"money": 789,
|
||||
"address": "湖南省长沙市",
|
||||
"state": "失败",
|
||||
"date": "2019-11-11",
|
||||
"thumb": "https://lin-xin.gitee.io/images/post/parcel.png"
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"name": "赵六",
|
||||
"money": 1011,
|
||||
"address": "福建省厦门市鼓浪屿",
|
||||
"state": "成功",
|
||||
"date": "2019-10-20",
|
||||
"thumb": "https://lin-xin.gitee.io/images/post/notice.png"
|
||||
}
|
||||
],
|
||||
"pageTotal": 4
|
||||
}
|
||||
BIN
public/template.xlsx
Normal file
BIN
public/template.xlsx
Normal file
Binary file not shown.
14
src/App.vue
Normal file
14
src/App.vue
Normal file
@ -0,0 +1,14 @@
|
||||
<template>
|
||||
<el-config-provider :locale="zhCn">
|
||||
<router-view />
|
||||
</el-config-provider>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ElConfigProvider } from 'element-plus';
|
||||
import zhCn from 'element-plus/es/locale/lang/zh-cn';
|
||||
</script>
|
||||
<style>
|
||||
@import './assets/css/main.css';
|
||||
@import './assets/css/color-dark.css';
|
||||
</style>
|
||||
8
src/api/index.ts
Normal file
8
src/api/index.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import request from '../utils/request';
|
||||
|
||||
export const fetchData = () => {
|
||||
return request({
|
||||
url: './table.json',
|
||||
method: 'get'
|
||||
});
|
||||
};
|
||||
23
src/assets/css/color-dark.css
Normal file
23
src/assets/css/color-dark.css
Normal file
@ -0,0 +1,23 @@
|
||||
.header{
|
||||
background-color: #242f42;
|
||||
}
|
||||
.login-wrap{
|
||||
background: #324157;
|
||||
}
|
||||
.plugins-tips{
|
||||
background: #eef1f6;
|
||||
}
|
||||
.plugins-tips a{
|
||||
color: #20a0ff;
|
||||
}
|
||||
|
||||
.tags-li.active {
|
||||
border: 1px solid #409EFF;
|
||||
background-color: #409EFF;
|
||||
}
|
||||
.message-title{
|
||||
color: #20a0ff;
|
||||
}
|
||||
.collapse-btn:hover{
|
||||
background: rgb(40,52,70);
|
||||
}
|
||||
4
src/assets/css/icon.css
Normal file
4
src/assets/css/icon.css
Normal file
@ -0,0 +1,4 @@
|
||||
[class*=" el-icon-lx"],
|
||||
[class^=el-icon-lx] {
|
||||
font-family: lx-iconfont !important;
|
||||
}
|
||||
137
src/assets/css/main.css
Normal file
137
src/assets/css/main.css
Normal file
@ -0,0 +1,137 @@
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
html,
|
||||
body,
|
||||
#app,
|
||||
.wrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'PingFang SC', "Helvetica Neue", Helvetica, "microsoft yahei", arial, STHeiTi, sans-serif;
|
||||
}
|
||||
|
||||
a {
|
||||
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;
|
||||
}
|
||||
|
||||
.container {
|
||||
padding: 30px;
|
||||
background: #fff;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.crumbs {
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.el-table th {
|
||||
background-color: #f5f7fa !important;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
margin: 20px 0;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.plugins-tips {
|
||||
padding: 20px 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.el-button+.el-tooltip {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.el-table tr:hover {
|
||||
background: #f6faff;
|
||||
}
|
||||
|
||||
.mgb20 {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.move-enter-active,
|
||||
.move-leave-active {
|
||||
transition: opacity .1s ease;
|
||||
}
|
||||
|
||||
.move-enter-from,
|
||||
.move-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
/*BaseForm*/
|
||||
|
||||
.form-box {
|
||||
width: 600px;
|
||||
}
|
||||
|
||||
.form-box .line {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.el-time-panel__content::after,
|
||||
.el-time-panel__content::before {
|
||||
margin-top: -7px;
|
||||
}
|
||||
|
||||
.el-time-spinner__wrapper .el-scrollbar__wrap:not(.el-scrollbar__wrap--hidden-default) {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
|
||||
[class*=" el-icon-"], [class^=el-icon-] {
|
||||
speak: none;
|
||||
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;
|
||||
}
|
||||
BIN
src/assets/img/img.jpg
Normal file
BIN
src/assets/img/img.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.0 KiB |
BIN
src/assets/img/login-bg.jpg
Normal file
BIN
src/assets/img/login-bg.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 69 KiB |
154
src/components/header.vue
Normal file
154
src/components/header.vue
Normal file
@ -0,0 +1,154 @@
|
||||
<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>
|
||||
<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"
|
||||
>
|
||||
<i class="el-icon-lx-notice"></i>
|
||||
</el-tooltip>
|
||||
<span class="btn-bell-badge" v-if="message"></span>
|
||||
</div>
|
||||
<!-- 用户头像 -->
|
||||
<el-avatar class="user-avator" :size="30" :src="imgurl" />
|
||||
<!-- 用户名下拉菜单 -->
|
||||
<el-dropdown class="user-name" trigger="click" @command="handleCommand">
|
||||
<span class="el-dropdown-link">
|
||||
{{ username }}
|
||||
<el-icon class="el-icon--right">
|
||||
<arrow-down />
|
||||
</el-icon>
|
||||
</span>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<a href="https://github.com/lin-xin/vue-manage-system" target="_blank">
|
||||
<el-dropdown-item>项目仓库</el-dropdown-item>
|
||||
</a>
|
||||
<el-dropdown-item command="user">个人中心</el-dropdown-item>
|
||||
<el-dropdown-item divided command="loginout">退出登录</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { onMounted } from 'vue';
|
||||
import { useSidebarStore } from '../store/sidebar';
|
||||
import { useRouter } from 'vue-router';
|
||||
import imgurl from '../assets/img/img.jpg';
|
||||
|
||||
const username: string | null = localStorage.getItem('ms_username');
|
||||
const message: number = 2;
|
||||
|
||||
const sidebar = useSidebarStore();
|
||||
// 侧边栏折叠
|
||||
const collapseChage = () => {
|
||||
sidebar.handleCollapse();
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
if (document.body.clientWidth < 1500) {
|
||||
collapseChage();
|
||||
}
|
||||
});
|
||||
|
||||
// 用户名下拉菜单选择事件
|
||||
const router = useRouter();
|
||||
const handleCommand = (command: string) => {
|
||||
if (command == 'loginout') {
|
||||
localStorage.removeItem('ms_username');
|
||||
router.push('/login');
|
||||
} else if (command == 'user') {
|
||||
router.push('/user');
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style scoped>
|
||||
.header {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
height: 70px;
|
||||
font-size: 22px;
|
||||
color: #fff;
|
||||
}
|
||||
.collapse-btn {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
float: left;
|
||||
padding: 0 21px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.header .logo {
|
||||
float: left;
|
||||
width: 250px;
|
||||
line-height: 70px;
|
||||
}
|
||||
.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 {
|
||||
position: relative;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
text-align: center;
|
||||
border-radius: 15px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.btn-bell-badge {
|
||||
position: absolute;
|
||||
right: 4px;
|
||||
top: 0px;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 4px;
|
||||
background: #f56c6c;
|
||||
color: #fff;
|
||||
}
|
||||
.btn-bell .el-icon-lx-notice {
|
||||
color: #fff;
|
||||
}
|
||||
.user-name {
|
||||
margin-left: 10px;
|
||||
}
|
||||
.user-avator {
|
||||
margin-left: 20px;
|
||||
}
|
||||
.el-dropdown-link {
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.el-dropdown-menu__item {
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
181
src/components/sidebar.vue
Normal file
181
src/components/sidebar.vue
Normal file
@ -0,0 +1,181 @@
|
||||
<template>
|
||||
<div class="sidebar">
|
||||
<el-menu
|
||||
class="sidebar-el-menu"
|
||||
:default-active="onRoutes"
|
||||
:collapse="sidebar.collapse"
|
||||
background-color="#324157"
|
||||
text-color="#bfcbd9"
|
||||
active-text-color="#20a0ff"
|
||||
unique-opened
|
||||
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 #title>
|
||||
<el-icon>
|
||||
<component :is="item.icon"></component>
|
||||
</el-icon>
|
||||
<span>{{ item.title }}</span>
|
||||
</template>
|
||||
<template v-for="subItem in item.subs">
|
||||
<el-sub-menu
|
||||
v-if="subItem.subs"
|
||||
:index="subItem.index"
|
||||
:key="subItem.index"
|
||||
v-permiss="item.permiss"
|
||||
>
|
||||
<template #title>{{ subItem.title }}</template>
|
||||
<el-menu-item v-for="(threeItem, i) in subItem.subs" :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">
|
||||
{{ 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-icon>
|
||||
<component :is="item.icon"></component>
|
||||
</el-icon>
|
||||
<template #title>{{ item.title }}</template>
|
||||
</el-menu-item>
|
||||
</template>
|
||||
</template>
|
||||
</el-menu>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
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',
|
||||
},
|
||||
];
|
||||
|
||||
const route = useRoute();
|
||||
const onRoutes = computed(() => {
|
||||
return route.path;
|
||||
});
|
||||
|
||||
const sidebar = useSidebarStore();
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.sidebar {
|
||||
display: block;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 70px;
|
||||
bottom: 0;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
.sidebar::-webkit-scrollbar {
|
||||
width: 0;
|
||||
}
|
||||
.sidebar-el-menu:not(.el-menu--collapse) {
|
||||
width: 250px;
|
||||
}
|
||||
.sidebar > ul {
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
168
src/components/tags.vue
Normal file
168
src/components/tags.vue
Normal file
@ -0,0 +1,168 @@
|
||||
<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>
|
||||
28
src/main.ts
Normal file
28
src/main.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import { createApp } from 'vue';
|
||||
import { createPinia } from 'pinia';
|
||||
import * as ElementPlusIconsVue from '@element-plus/icons-vue';
|
||||
import App from './App.vue';
|
||||
import router from './router';
|
||||
import { usePermissStore } from './store/permiss';
|
||||
import 'element-plus/dist/index.css';
|
||||
import './assets/css/icon.css';
|
||||
|
||||
const app = createApp(App);
|
||||
app.use(createPinia());
|
||||
app.use(router);
|
||||
|
||||
// 注册elementplus图标
|
||||
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
|
||||
app.component(key, component);
|
||||
}
|
||||
// 自定义权限指令
|
||||
const permiss = usePermissStore();
|
||||
app.directive('permiss', {
|
||||
mounted(el, binding) {
|
||||
if (!permiss.key.includes(String(binding.value))) {
|
||||
el['hidden'] = true;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
app.mount('#app');
|
||||
179
src/router/index.ts
Normal file
179
src/router/index.ts
Normal file
@ -0,0 +1,179 @@
|
||||
import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router';
|
||||
import { usePermissStore } from '../store/permiss';
|
||||
import Home from '../views/home.vue';
|
||||
|
||||
const routes: RouteRecordRaw[] = [
|
||||
{
|
||||
path: '/',
|
||||
redirect: '/dashboard',
|
||||
},
|
||||
{
|
||||
path: '/',
|
||||
name: 'Home',
|
||||
component: Home,
|
||||
children: [
|
||||
{
|
||||
path: '/dashboard',
|
||||
name: 'dashboard',
|
||||
meta: {
|
||||
title: '系统首页',
|
||||
permiss: '1',
|
||||
},
|
||||
component: () => import(/* webpackChunkName: "dashboard" */ '../views/dashboard.vue'),
|
||||
},
|
||||
{
|
||||
path: '/table',
|
||||
name: 'basetable',
|
||||
meta: {
|
||||
title: '表格',
|
||||
permiss: '2',
|
||||
},
|
||||
component: () => import(/* webpackChunkName: "table" */ '../views/table.vue'),
|
||||
},
|
||||
{
|
||||
path: '/charts',
|
||||
name: 'basecharts',
|
||||
meta: {
|
||||
title: '图表',
|
||||
permiss: '11',
|
||||
},
|
||||
component: () => import(/* webpackChunkName: "charts" */ '../views/charts.vue'),
|
||||
},
|
||||
{
|
||||
path: '/form',
|
||||
name: 'baseform',
|
||||
meta: {
|
||||
title: '表单',
|
||||
permiss: '5',
|
||||
},
|
||||
component: () => import(/* webpackChunkName: "form" */ '../views/form.vue'),
|
||||
},
|
||||
{
|
||||
path: '/tabs',
|
||||
name: 'tabs',
|
||||
meta: {
|
||||
title: 'tab标签',
|
||||
permiss: '3',
|
||||
},
|
||||
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'),
|
||||
},
|
||||
{
|
||||
path: '/icon',
|
||||
name: 'icon',
|
||||
meta: {
|
||||
title: '自定义图标',
|
||||
permiss: '10',
|
||||
},
|
||||
component: () => import(/* webpackChunkName: "icon" */ '../views/icon.vue'),
|
||||
},
|
||||
{
|
||||
path: '/user',
|
||||
name: 'user',
|
||||
meta: {
|
||||
title: '个人中心',
|
||||
},
|
||||
component: () => import(/* webpackChunkName: "user" */ '../views/user.vue'),
|
||||
},
|
||||
{
|
||||
path: '/editor',
|
||||
name: 'editor',
|
||||
meta: {
|
||||
title: '富文本编辑器',
|
||||
permiss: '8',
|
||||
},
|
||||
component: () => import(/* webpackChunkName: "editor" */ '../views/editor.vue'),
|
||||
},
|
||||
{
|
||||
path: '/markdown',
|
||||
name: 'markdown',
|
||||
meta: {
|
||||
title: 'markdown编辑器',
|
||||
permiss: '9',
|
||||
},
|
||||
component: () => import(/* webpackChunkName: "markdown" */ '../views/markdown.vue'),
|
||||
},
|
||||
{
|
||||
path: '/export',
|
||||
name: 'export',
|
||||
meta: {
|
||||
title: '导出Excel',
|
||||
permiss: '2',
|
||||
},
|
||||
component: () => import(/* webpackChunkName: "export" */ '../views/export.vue'),
|
||||
},
|
||||
{
|
||||
path: '/import',
|
||||
name: 'import',
|
||||
meta: {
|
||||
title: '导入Excel',
|
||||
permiss: '2',
|
||||
},
|
||||
component: () => import(/* webpackChunkName: "import" */ '../views/import.vue'),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/login',
|
||||
name: 'Login',
|
||||
meta: {
|
||||
title: '登录',
|
||||
},
|
||||
component: () => import(/* webpackChunkName: "login" */ '../views/login.vue'),
|
||||
},
|
||||
{
|
||||
path: '/403',
|
||||
name: '403',
|
||||
meta: {
|
||||
title: '没有权限',
|
||||
},
|
||||
component: () => import(/* webpackChunkName: "403" */ '../views/403.vue'),
|
||||
},
|
||||
];
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHashHistory(),
|
||||
routes,
|
||||
});
|
||||
|
||||
router.beforeEach((to, from, next) => {
|
||||
document.title = `${to.meta.title} | vue-manage-system`;
|
||||
const role = localStorage.getItem('ms_username');
|
||||
const permiss = usePermissStore();
|
||||
if (!role && to.path !== '/login') {
|
||||
next('/login');
|
||||
} else if (to.meta.permiss && !permiss.key.includes(to.meta.permiss)) {
|
||||
// 如果没有权限,则进入403
|
||||
next('/403');
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
||||
23
src/store/permiss.ts
Normal file
23
src/store/permiss.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { defineStore } from 'pinia';
|
||||
|
||||
interface ObjectList {
|
||||
[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;
|
||||
}
|
||||
}
|
||||
});
|
||||
15
src/store/sidebar.ts
Normal file
15
src/store/sidebar.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { defineStore } from 'pinia';
|
||||
|
||||
export const useSidebarStore = defineStore('sidebar', {
|
||||
state: () => {
|
||||
return {
|
||||
collapse: false
|
||||
};
|
||||
},
|
||||
getters: {},
|
||||
actions: {
|
||||
handleCollapse() {
|
||||
this.collapse = !this.collapse;
|
||||
}
|
||||
}
|
||||
});
|
||||
53
src/store/tags.ts
Normal file
53
src/store/tags.ts
Normal file
@ -0,0 +1,53 @@
|
||||
import { defineStore } from 'pinia';
|
||||
|
||||
interface ListItem {
|
||||
name: string;
|
||||
path: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
export const useTagsStore = defineStore('tags', {
|
||||
state: () => {
|
||||
return {
|
||||
list: <ListItem[]>[]
|
||||
};
|
||||
},
|
||||
getters: {
|
||||
show: state => {
|
||||
return state.list.length > 0;
|
||||
},
|
||||
nameList: state => {
|
||||
return state.list.map(item => item.name);
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
delTagsItem(index: number) {
|
||||
this.list.splice(index, 1);
|
||||
},
|
||||
setTagsItem(data: ListItem) {
|
||||
this.list.push(data);
|
||||
},
|
||||
clearTags() {
|
||||
this.list = [];
|
||||
},
|
||||
closeTagsOther(data: ListItem[]) {
|
||||
this.list = data;
|
||||
},
|
||||
closeCurrentTag(data: any) {
|
||||
for (let i = 0, len = this.list.length; i < len; i++) {
|
||||
const item = this.list[i];
|
||||
if (item.path === data.$route.fullPath) {
|
||||
if (i < len - 1) {
|
||||
data.$router.push(this.list[i + 1].path);
|
||||
} else if (i > 0) {
|
||||
data.$router.push(this.list[i - 1].path);
|
||||
} else {
|
||||
data.$router.push('/');
|
||||
}
|
||||
this.list.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
31
src/utils/request.ts
Normal file
31
src/utils/request.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import axios, {AxiosInstance, AxiosError, AxiosResponse, AxiosRequestConfig} from 'axios';
|
||||
|
||||
const service:AxiosInstance = axios.create({
|
||||
timeout: 5000
|
||||
});
|
||||
|
||||
service.interceptors.request.use(
|
||||
(config: AxiosRequestConfig) => {
|
||||
return config;
|
||||
},
|
||||
(error: AxiosError) => {
|
||||
console.log(error);
|
||||
return Promise.reject();
|
||||
}
|
||||
);
|
||||
|
||||
service.interceptors.response.use(
|
||||
(response: AxiosResponse) => {
|
||||
if (response.status === 200) {
|
||||
return response;
|
||||
} else {
|
||||
Promise.reject();
|
||||
}
|
||||
},
|
||||
(error: AxiosError) => {
|
||||
console.log(error);
|
||||
return Promise.reject();
|
||||
}
|
||||
);
|
||||
|
||||
export default service;
|
||||
54
src/views/403.vue
Normal file
54
src/views/403.vue
Normal file
@ -0,0 +1,54 @@
|
||||
<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>
|
||||
54
src/views/404.vue
Normal file
54
src/views/404.vue
Normal file
@ -0,0 +1,54 @@
|
||||
<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>
|
||||
127
src/views/charts.vue
Normal file
127
src/views/charts.vue
Normal file
@ -0,0 +1,127 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<div class="plugins-tips">
|
||||
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>
|
||||
<schart class="schart" canvasId="bar" :options="options1"></schart>
|
||||
</div>
|
||||
<div class="schart-box">
|
||||
<div class="content-title">折线图</div>
|
||||
<schart class="schart" canvasId="line" :options="options2"></schart>
|
||||
</div>
|
||||
<div class="schart-box">
|
||||
<div class="content-title">饼状图</div>
|
||||
<schart class="schart" canvasId="pie" :options="options3"></schart>
|
||||
</div>
|
||||
<div class="schart-box">
|
||||
<div class="content-title">环形图</div>
|
||||
<schart class="schart" canvasId="ring" :options="options4"></schart>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="basecharts">
|
||||
import Schart from 'vue-schart';
|
||||
|
||||
const options1 = {
|
||||
type: 'bar',
|
||||
title: {
|
||||
text: '最近一周各品类销售图'
|
||||
},
|
||||
bgColor: '#fbfbfb',
|
||||
labels: ['周一', '周二', '周三', '周四', '周五'],
|
||||
datasets: [
|
||||
{
|
||||
label: '家电',
|
||||
fillColor: 'rgba(241, 49, 74, 0.5)',
|
||||
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: '最近几个月各品类销售趋势图'
|
||||
},
|
||||
bgColor: '#fbfbfb',
|
||||
labels: ['6月', '7月', '8月', '9月', '10月'],
|
||||
datasets: [
|
||||
{
|
||||
label: '家电',
|
||||
data: [234, 278, 270, 190, 230]
|
||||
},
|
||||
{
|
||||
label: '百货',
|
||||
data: [164, 178, 150, 135, 160]
|
||||
},
|
||||
{
|
||||
label: '食品',
|
||||
data: [114, 138, 200, 235, 190]
|
||||
}
|
||||
]
|
||||
};
|
||||
const options3 = {
|
||||
type: 'pie',
|
||||
title: {
|
||||
text: '服装品类销售饼状图'
|
||||
},
|
||||
legend: {
|
||||
position: 'left'
|
||||
},
|
||||
bgColor: '#fbfbfb',
|
||||
labels: ['T恤', '牛仔裤', '连衣裙', '毛衣', '七分裤', '短裙', '羽绒服'],
|
||||
datasets: [
|
||||
{
|
||||
data: [334, 278, 190, 235, 260, 200, 141]
|
||||
}
|
||||
]
|
||||
};
|
||||
const options4 = {
|
||||
type: 'ring',
|
||||
title: {
|
||||
text: '环形三等分'
|
||||
},
|
||||
showValue: false,
|
||||
legend: {
|
||||
position: 'bottom',
|
||||
bottom: 40
|
||||
},
|
||||
bgColor: '#fbfbfb',
|
||||
labels: ['vue', 'react', 'angular'],
|
||||
datasets: [
|
||||
{
|
||||
data: [500, 500, 500]
|
||||
}
|
||||
]
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.schart-box {
|
||||
display: inline-block;
|
||||
margin: 20px;
|
||||
}
|
||||
.schart {
|
||||
width: 600px;
|
||||
height: 400px;
|
||||
}
|
||||
.content-title {
|
||||
clear: both;
|
||||
font-weight: 400;
|
||||
line-height: 50px;
|
||||
margin: 10px 0;
|
||||
font-size: 22px;
|
||||
color: #1f2f3d;
|
||||
}
|
||||
</style>
|
||||
301
src/views/dashboard.vue
Normal file
301
src/views/dashboard.vue
Normal file
@ -0,0 +1,301 @@
|
||||
<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>
|
||||
|
||||
<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>
|
||||
</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
|
||||
}
|
||||
]);
|
||||
</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>
|
||||
14
src/views/donate.vue
Normal file
14
src/views/donate.vue
Normal file
@ -0,0 +1,14 @@
|
||||
<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>
|
||||
37
src/views/editor.vue
Normal file
37
src/views/editor.vue
Normal file
@ -0,0 +1,37 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<div class="plugins-tips">
|
||||
wangEditor:轻量级 web 富文本编辑器,配置方便,使用简单。 访问地址:
|
||||
<a href="https://www.wangeditor.com/doc/" target="_blank">wangEditor</a>
|
||||
</div>
|
||||
<div class="mgb20" ref="editor"></div>
|
||||
<el-button type="primary" @click="syncHTML">提交</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="editor">
|
||||
import WangEditor from 'wangeditor';
|
||||
import { ref, reactive, onMounted, onBeforeUnmount } from 'vue';
|
||||
|
||||
const editor = ref(null);
|
||||
const content = reactive({
|
||||
html: '',
|
||||
text: ''
|
||||
});
|
||||
let instance: any;
|
||||
onMounted(() => {
|
||||
instance = new WangEditor(editor.value);
|
||||
instance.config.zIndex = 1;
|
||||
instance.create();
|
||||
});
|
||||
onBeforeUnmount(() => {
|
||||
instance.destroy();
|
||||
instance = null;
|
||||
});
|
||||
const syncHTML = () => {
|
||||
content.html = instance.txt.html();
|
||||
console.log(content.html);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
98
src/views/export.vue
Normal file
98
src/views/export.vue
Normal file
@ -0,0 +1,98 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="container">
|
||||
<div class="handle-box">
|
||||
<el-button type="primary" @click="exportXlsx">导出Excel</el-button>
|
||||
</div>
|
||||
<el-table :data="tableData" border class="table" 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="姓名"></el-table-column>
|
||||
<el-table-column prop="sno" label="学号"></el-table-column>
|
||||
<el-table-column prop="class" label="班级"></el-table-column>
|
||||
<el-table-column prop="age" label="年龄"></el-table-column>
|
||||
<el-table-column prop="sex" label="性别"></el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="export">
|
||||
import { ref } from 'vue';
|
||||
import * as XLSX from 'xlsx';
|
||||
|
||||
interface TableItem {
|
||||
id: number;
|
||||
name: string;
|
||||
sno: string;
|
||||
class: string;
|
||||
age: string;
|
||||
sex: string;
|
||||
}
|
||||
|
||||
const tableData = ref<TableItem[]>([]);
|
||||
// 获取表格数据
|
||||
const getData = () => {
|
||||
tableData.value = [
|
||||
{
|
||||
id: 1,
|
||||
name: '小明',
|
||||
sno: 'S001',
|
||||
class: '一班',
|
||||
age: '10',
|
||||
sex: '男',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: '小红',
|
||||
sno: 'S002',
|
||||
class: '一班',
|
||||
age: '9',
|
||||
sex: '女',
|
||||
},
|
||||
];
|
||||
};
|
||||
getData();
|
||||
|
||||
const list = [['序号', '姓名', '学号', '班级', '年龄', '性别']];
|
||||
const exportXlsx = () => {
|
||||
tableData.value.map((item: any, i: number) => {
|
||||
const arr: any[] = [i + 1];
|
||||
arr.push(...[item.name, item.sno, item.class, item.age, item.sex]);
|
||||
list.push(arr);
|
||||
});
|
||||
let WorkSheet = XLSX.utils.aoa_to_sheet(list);
|
||||
let new_workbook = XLSX.utils.book_new();
|
||||
XLSX.utils.book_append_sheet(new_workbook, WorkSheet, '第一页');
|
||||
XLSX.writeFile(new_workbook, `表格.xlsx`);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.handle-box {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.handle-select {
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
.handle-input {
|
||||
width: 300px;
|
||||
}
|
||||
.table {
|
||||
width: 100%;
|
||||
font-size: 14px;
|
||||
}
|
||||
.red {
|
||||
color: #f56c6c;
|
||||
}
|
||||
.mr10 {
|
||||
margin-right: 10px;
|
||||
}
|
||||
.table-td-thumb {
|
||||
display: block;
|
||||
margin: auto;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
</style>
|
||||
156
src/views/form.vue
Normal file
156
src/views/form.vue
Normal file
@ -0,0 +1,156 @@
|
||||
<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>
|
||||
26
src/views/home.vue
Normal file
26
src/views/home.vue
Normal file
@ -0,0 +1,26 @@
|
||||
<template>
|
||||
<v-header />
|
||||
<v-sidebar />
|
||||
<div class="content-box" :class="{ 'content-collapse': sidebar.collapse }">
|
||||
<v-tags></v-tags>
|
||||
<div class="content">
|
||||
<router-view v-slot="{ Component }">
|
||||
<transition name="move" mode="out-in">
|
||||
<keep-alive :include="tags.nameList">
|
||||
<component :is="Component"></component>
|
||||
</keep-alive>
|
||||
</transition>
|
||||
</router-view>
|
||||
</div>
|
||||
</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';
|
||||
|
||||
const sidebar = useSidebarStore();
|
||||
const tags = useTagsStore();
|
||||
</script>
|
||||
212
src/views/icon.vue
Normal file
212
src/views/icon.vue
Normal file
@ -0,0 +1,212 @@
|
||||
<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>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="icon">
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
const iconList: Array<string> = [
|
||||
'attentionforbid',
|
||||
'attentionforbidfill',
|
||||
'attention',
|
||||
'attentionfill',
|
||||
'tag',
|
||||
'tagfill',
|
||||
'people',
|
||||
'peoplefill',
|
||||
'notice',
|
||||
'noticefill',
|
||||
'mobile',
|
||||
'mobilefill',
|
||||
'voice',
|
||||
'voicefill',
|
||||
'unlock',
|
||||
'lock',
|
||||
'home',
|
||||
'homefill',
|
||||
'delete',
|
||||
'deletefill',
|
||||
'notification',
|
||||
'notificationfill',
|
||||
'notificationforbidfill',
|
||||
'like',
|
||||
'likefill',
|
||||
'comment',
|
||||
'commentfill',
|
||||
'camera',
|
||||
'camerafill',
|
||||
'warn',
|
||||
'warnfill',
|
||||
'time',
|
||||
'timefill',
|
||||
'location',
|
||||
'locationfill',
|
||||
'favor',
|
||||
'favorfill',
|
||||
'skin',
|
||||
'skinfill',
|
||||
'news',
|
||||
'newsfill',
|
||||
'record',
|
||||
'recordfill',
|
||||
'emoji',
|
||||
'emojifill',
|
||||
'message',
|
||||
'messagefill',
|
||||
'goods',
|
||||
'goodsfill',
|
||||
'crown',
|
||||
'crownfill',
|
||||
'move',
|
||||
'add',
|
||||
'hot',
|
||||
'hotfill',
|
||||
'service',
|
||||
'servicefill',
|
||||
'present',
|
||||
'presentfill',
|
||||
'pic',
|
||||
'picfill',
|
||||
'rank',
|
||||
'rankfill',
|
||||
'male',
|
||||
'female',
|
||||
'down',
|
||||
'top',
|
||||
'recharge',
|
||||
'rechargefill',
|
||||
'forward',
|
||||
'forwardfill',
|
||||
'info',
|
||||
'infofill',
|
||||
'redpacket',
|
||||
'redpacket_fill',
|
||||
'roundadd',
|
||||
'roundaddfill',
|
||||
'friendadd',
|
||||
'friendaddfill',
|
||||
'cart',
|
||||
'cartfill',
|
||||
'more',
|
||||
'moreandroid',
|
||||
'back',
|
||||
'right',
|
||||
'shop',
|
||||
'shopfill',
|
||||
'question',
|
||||
'questionfill',
|
||||
'roundclose',
|
||||
'roundclosefill',
|
||||
'roundcheck',
|
||||
'roundcheckfill',
|
||||
'global',
|
||||
'mail',
|
||||
'punch',
|
||||
'exit',
|
||||
'upload',
|
||||
'read',
|
||||
'file',
|
||||
'link',
|
||||
'full',
|
||||
'group',
|
||||
'friend',
|
||||
'profile',
|
||||
'addressbook',
|
||||
'calendar',
|
||||
'text',
|
||||
'copy',
|
||||
'share',
|
||||
'wifi',
|
||||
'vipcard',
|
||||
'weibo',
|
||||
'remind',
|
||||
'refresh',
|
||||
'filter',
|
||||
'settings',
|
||||
'scan',
|
||||
'qrcode',
|
||||
'cascades',
|
||||
'apps',
|
||||
'sort',
|
||||
'searchlist',
|
||||
'search',
|
||||
'edit'
|
||||
];
|
||||
const keyword = ref('');
|
||||
const list = computed(() => {
|
||||
return iconList.filter(item => {
|
||||
return item.indexOf(keyword.value) !== -1;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.example-p {
|
||||
height: 45px;
|
||||
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%;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
.icon-li-content i {
|
||||
font-size: 36px;
|
||||
color: #606266;
|
||||
}
|
||||
.icon-li-content span {
|
||||
margin-top: 10px;
|
||||
color: #787878;
|
||||
}
|
||||
</style>
|
||||
118
src/views/import.vue
Normal file
118
src/views/import.vue
Normal file
@ -0,0 +1,118 @@
|
||||
<template>
|
||||
<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-button class="mr10" type="success">批量导入</el-button>
|
||||
</el-upload>
|
||||
<el-link href="/template.xlsx" target="_blank">下载模板</el-link>
|
||||
</div>
|
||||
<el-table :data="tableData" border class="table" 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="姓名"></el-table-column>
|
||||
<el-table-column prop="sno" label="学号"></el-table-column>
|
||||
<el-table-column prop="class" label="班级"></el-table-column>
|
||||
<el-table-column prop="age" label="年龄"></el-table-column>
|
||||
<el-table-column prop="sex" label="性别"></el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="import">
|
||||
import { UploadProps } from 'element-plus';
|
||||
import { ref, reactive } from 'vue';
|
||||
import * as XLSX from 'xlsx';
|
||||
|
||||
interface TableItem {
|
||||
id: number;
|
||||
name: string;
|
||||
sno: string;
|
||||
class: string;
|
||||
age: string;
|
||||
sex: string;
|
||||
}
|
||||
|
||||
const tableData = ref<TableItem[]>([]);
|
||||
// 获取表格数据
|
||||
const getData = () => {
|
||||
tableData.value = [
|
||||
{
|
||||
id: 1,
|
||||
name: '小明',
|
||||
sno: 'S001',
|
||||
class: '一班',
|
||||
age: '10',
|
||||
sex: '男',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: '小红',
|
||||
sno: 'S002',
|
||||
class: '一班',
|
||||
age: '9',
|
||||
sex: '女',
|
||||
},
|
||||
];
|
||||
};
|
||||
getData();
|
||||
|
||||
const importList = ref<any>([]);
|
||||
const beforeUpload: UploadProps['beforeUpload'] = async (rawFile) => {
|
||||
importList.value = await analysisExcel(rawFile);
|
||||
return true;
|
||||
};
|
||||
const analysisExcel = (file: any) => {
|
||||
return new Promise(function (resolve, reject) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = function (e: any) {
|
||||
const data = e.target.result;
|
||||
let datajson = XLSX.read(data, {
|
||||
type: 'binary',
|
||||
});
|
||||
|
||||
const sheetName = datajson.SheetNames[0];
|
||||
const result = XLSX.utils.sheet_to_json(datajson.Sheets[sheetName]);
|
||||
resolve(result);
|
||||
};
|
||||
reader.readAsBinaryString(file);
|
||||
});
|
||||
};
|
||||
|
||||
const handleMany = async () => {
|
||||
// 把数据传给服务器后获取最新列表,这里只是示例,不做请求
|
||||
const list = importList.value.map((item: any, index: number) => {
|
||||
return {
|
||||
id: index,
|
||||
name: item['姓名'],
|
||||
sno: item['学号'],
|
||||
class: item['班级'],
|
||||
age: item['年龄'],
|
||||
sex: item['性别'],
|
||||
};
|
||||
});
|
||||
tableData.value.push(...list);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.handle-box {
|
||||
display: flex;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.table {
|
||||
width: 100%;
|
||||
font-size: 14px;
|
||||
}
|
||||
.mr10 {
|
||||
margin-right: 10px;
|
||||
}
|
||||
</style>
|
||||
129
src/views/login.vue
Normal file
129
src/views/login.vue
Normal file
@ -0,0 +1,129 @@
|
||||
<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">
|
||||
<el-form-item prop="username">
|
||||
<el-input v-model="param.username" placeholder="username">
|
||||
<template #prepend>
|
||||
<el-button :icon="User"></el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="password">
|
||||
<el-input
|
||||
type="password"
|
||||
placeholder="password"
|
||||
v-model="param.password"
|
||||
@keyup.enter="submitForm(login)"
|
||||
>
|
||||
<template #prepend>
|
||||
<el-button :icon="Lock"></el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<div class="login-btn">
|
||||
<el-button type="primary" @click="submitForm(login)">登录</el-button>
|
||||
</div>
|
||||
<p class="login-tips">Tips : 用户名和密码随便填。</p>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive } from 'vue';
|
||||
import { useTagsStore } from '../store/tags';
|
||||
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;
|
||||
password: string;
|
||||
}
|
||||
|
||||
const router = useRouter();
|
||||
const param = reactive<LoginInfo>({
|
||||
username: 'admin',
|
||||
password: '123123'
|
||||
});
|
||||
|
||||
const rules: FormRules = {
|
||||
username: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入用户名',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
password: [{ required: true, message: '请输入密码', trigger: 'blur' }]
|
||||
};
|
||||
const permiss = usePermissStore();
|
||||
const login = ref<FormInstance>();
|
||||
const submitForm = (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return;
|
||||
formEl.validate((valid: boolean) => {
|
||||
if (valid) {
|
||||
ElMessage.success('登录成功');
|
||||
localStorage.setItem('ms_username', param.username);
|
||||
const keys = permiss.defaultList[param.username == 'admin' ? 'admin' : 'user'];
|
||||
permiss.handleSet(keys);
|
||||
localStorage.setItem('ms_keys', JSON.stringify(keys));
|
||||
router.push('/');
|
||||
} else {
|
||||
ElMessage.error('登录成功');
|
||||
return false;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const tags = useTagsStore();
|
||||
tags.clearTags();
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.login-wrap {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image: url(../assets/img/login-bg.jpg);
|
||||
background-size: 100%;
|
||||
}
|
||||
.ms-title {
|
||||
width: 100%;
|
||||
line-height: 50px;
|
||||
text-align: center;
|
||||
font-size: 20px;
|
||||
color: #fff;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
.ms-login {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
width: 350px;
|
||||
margin: -190px 0 0 -175px;
|
||||
border-radius: 5px;
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
overflow: hidden;
|
||||
}
|
||||
.ms-content {
|
||||
padding: 30px 30px;
|
||||
}
|
||||
.login-btn {
|
||||
text-align: center;
|
||||
}
|
||||
.login-btn button {
|
||||
width: 100%;
|
||||
height: 36px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.login-tips {
|
||||
font-size: 12px;
|
||||
line-height: 30px;
|
||||
color: #fff;
|
||||
}
|
||||
</style>
|
||||
21
src/views/markdown.vue
Normal file
21
src/views/markdown.vue
Normal file
@ -0,0 +1,21 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<div class="plugins-tips">
|
||||
md-editor-v3:vue3版本的 markdown 编辑器,配置丰富,请详看文档。 访问地址:
|
||||
<a href="https://imzbf.github.io/md-editor-v3/index" target="_blank">md-editor-v3</a>
|
||||
</div>
|
||||
<md-editor class="mgb20" v-model="text" @on-upload-img="onUploadImg" />
|
||||
<el-button type="primary">提交</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="md">
|
||||
import { ref } from 'vue';
|
||||
import MdEditor from 'md-editor-v3';
|
||||
import 'md-editor-v3/lib/style.css';
|
||||
|
||||
const text = ref('Hello Editor!');
|
||||
const onUploadImg = (files: any) => {
|
||||
console.log(files);
|
||||
};
|
||||
</script>
|
||||
137
src/views/permission.vue
Normal file
137
src/views/permission.vue
Normal file
@ -0,0 +1,137 @@
|
||||
<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>
|
||||
191
src/views/table.vue
Normal file
191
src/views/table.vue
Normal file
@ -0,0 +1,191 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="container">
|
||||
<div class="handle-box">
|
||||
<el-select v-model="query.address" placeholder="地址" class="handle-select mr10">
|
||||
<el-option key="1" label="广东省" value="广东省"></el-option>
|
||||
<el-option key="2" label="湖南省" value="湖南省"></el-option>
|
||||
</el-select>
|
||||
<el-input v-model="query.name" placeholder="用户名" class="handle-input mr10"></el-input>
|
||||
<el-button type="primary" :icon="Search" @click="handleSearch">搜索</el-button>
|
||||
<el-button type="primary" :icon="Plus">新增</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="用户名"></el-table-column>
|
||||
<el-table-column label="账户余额">
|
||||
<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="地址"></el-table-column>
|
||||
<el-table-column label="状态" align="center">
|
||||
<template #default="scope">
|
||||
<el-tag
|
||||
:type="scope.row.state === '成功' ? 'success' : scope.row.state === '失败' ? 'danger' : ''"
|
||||
>
|
||||
{{ scope.row.state }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="date" label="注册时间"></el-table-column>
|
||||
<el-table-column label="操作" width="220" align="center">
|
||||
<template #default="scope">
|
||||
<el-button text :icon="Edit" @click="handleEdit(scope.$index, scope.row)" v-permiss="15">
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button text :icon="Delete" class="red" @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="编辑" v-model="editVisible" width="30%">
|
||||
<el-form label-width="70px">
|
||||
<el-form-item label="用户名">
|
||||
<el-input v-model="form.name"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="地址">
|
||||
<el-input v-model="form.address"></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="editVisible = false">取 消</el-button>
|
||||
<el-button type="primary" @click="saveEdit">确 定</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</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, Plus } from '@element-plus/icons-vue';
|
||||
import { fetchData } from '../api/index';
|
||||
|
||||
interface TableItem {
|
||||
id: number;
|
||||
name: string;
|
||||
money: string;
|
||||
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 = () => {
|
||||
fetchData().then(res => {
|
||||
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 editVisible = ref(false);
|
||||
let form = reactive({
|
||||
name: '',
|
||||
address: ''
|
||||
});
|
||||
let idx: number = -1;
|
||||
const handleEdit = (index: number, row: any) => {
|
||||
idx = index;
|
||||
form.name = row.name;
|
||||
form.address = row.address;
|
||||
editVisible.value = true;
|
||||
};
|
||||
const saveEdit = () => {
|
||||
editVisible.value = false;
|
||||
ElMessage.success(`修改第 ${idx + 1} 行成功`);
|
||||
tableData.value[idx].name = form.name;
|
||||
tableData.value[idx].address = form.address;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.handle-box {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.handle-select {
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
.handle-input {
|
||||
width: 300px;
|
||||
}
|
||||
.table {
|
||||
width: 100%;
|
||||
font-size: 14px;
|
||||
}
|
||||
.red {
|
||||
color: #F56C6C;
|
||||
}
|
||||
.mr10 {
|
||||
margin-right: 10px;
|
||||
}
|
||||
.table-td-thumb {
|
||||
display: block;
|
||||
margin: auto;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
</style>
|
||||
116
src/views/tabs.vue
Normal file
116
src/views/tabs.vue
Normal file
@ -0,0 +1,116 @@
|
||||
<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="150"></el-table-column>
|
||||
<el-table-column width="120">
|
||||
<template #default="scope">
|
||||
<el-button type="danger" @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="150"></el-table-column>
|
||||
<el-table-column width="120">
|
||||
<template #default="scope">
|
||||
<el-button @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>
|
||||
48
src/views/upload.vue
Normal file
48
src/views/upload.vue
Normal file
@ -0,0 +1,48 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<div class="content-title">支持拖拽</div>
|
||||
<div class="plugins-tips">
|
||||
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-icon class="el-icon--upload"><upload-filled /></el-icon>
|
||||
<div class="el-upload__text">
|
||||
将文件拖到此处,或
|
||||
<em>点击上传</em>
|
||||
</div>
|
||||
</el-upload>
|
||||
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const handle = (rawFile: any) => {
|
||||
console.log(rawFile);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.content-title {
|
||||
font-weight: 400;
|
||||
line-height: 50px;
|
||||
margin: 10px 0;
|
||||
font-size: 22px;
|
||||
color: #1f2f3d;
|
||||
}
|
||||
.upload-demo {
|
||||
width: 360px;
|
||||
}
|
||||
</style>
|
||||
174
src/views/user.vue
Normal file
174
src/views/user.vue
Normal file
@ -0,0 +1,174 @@
|
||||
<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>
|
||||
10
src/vite-env.d.ts
vendored
Normal file
10
src/vite-env.d.ts
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
declare module '*.vue' {
|
||||
import type { DefineComponent } from 'vue'
|
||||
const component: DefineComponent<{}, {}, any>
|
||||
export default component
|
||||
}
|
||||
|
||||
declare module 'vue-schart';
|
||||
declare module 'vue-cropperjs';
|
||||
18
tsconfig.json
Normal file
18
tsconfig.json
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"useDefineForClassFields": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Node",
|
||||
"strict": true,
|
||||
"jsx": "preserve",
|
||||
"sourceMap": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"esModuleInterop": true,
|
||||
"lib": ["ESNext", "DOM"],
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.d.ts","src/**/*.vue"],
|
||||
"references": [{ "path": "./tsconfig.node.json" }]
|
||||
}
|
||||
9
tsconfig.node.json
Normal file
9
tsconfig.node.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Node",
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
22
vite.config.ts
Normal file
22
vite.config.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { defineConfig } from 'vite';
|
||||
import vue from '@vitejs/plugin-vue';
|
||||
import VueSetupExtend from 'vite-plugin-vue-setup-extend';
|
||||
import AutoImport from 'unplugin-auto-import/vite';
|
||||
import Components from 'unplugin-vue-components/vite';
|
||||
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers';
|
||||
export default defineConfig({
|
||||
base: './',
|
||||
plugins: [
|
||||
vue(),
|
||||
VueSetupExtend(),
|
||||
AutoImport({
|
||||
resolvers: [ElementPlusResolver()]
|
||||
}),
|
||||
Components({
|
||||
resolvers: [ElementPlusResolver()]
|
||||
})
|
||||
],
|
||||
optimizeDeps: {
|
||||
include: ['schart.js']
|
||||
}
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user