Compare commits

...

72 Commits

Author SHA1 Message Date
林鑫 6a7019ec1a
Update FUNDING.yml 2024-05-25 10:29:18 +08:00
林鑫 7375986a8e
Update README.md 2024-05-25 10:24:33 +08:00
林鑫 251936dcdc
Update README.md 2024-05-25 10:21:48 +08:00
lin-xin 921c38cea2 修改username 2024-04-18 22:51:36 +08:00
lin-xin 718f255817 优化样式和权限 2024-04-18 22:46:26 +08:00
lin-xin 433b60e720 更新文档 2024-04-13 11:07:56 +08:00
lin-xin b9a4402431 大版本更新 2024-04-13 10:59:18 +08:00
林鑫 daaf393872
Update README.md 2024-01-12 10:34:18 +08:00
lin-xin c8cae1b200 升级了部分功能 2024-01-06 18:57:49 +08:00
lin-xin f70c93715b 修复样式 2022-12-17 13:51:29 +08:00
lin-xin 60a293514d 新增了导入导出excel,以及做了一些优化 2022-12-17 13:37:31 +08:00
林鑫 8312ba1d6c
Update README.md 2022-09-02 21:04:06 +08:00
lin-xin eea5e368a9 fix:修改引入Home.vue为小写 2022-09-02 20:57:57 +08:00
lin-xin 759beaf940 fix:修改element-plus默认语言为中文 2022-09-02 20:54:09 +08:00
lin-xin 9237de9b89 fix:文件名改为小写 2022-09-02 20:50:30 +08:00
lin-xin 995ab69b1e 改为typescript;升级elementplus 2022-08-21 16:59:12 +08:00
lin-xin a072752609 feat:使用pinia替换vuex;新增markdown编辑器 2022-07-03 11:26:09 +08:00
林鑫 d28f504a80
Merge pull request #314 from zhufengning/master
The import should be "wangeditor" instead of "wangEditor"
2022-04-15 18:53:56 +08:00
林鑫 e82a457a2c
Merge pull request #327 from qmzorvim123/master
fix:修改 vue-schart访问地址找不到问题
2022-04-15 18:49:38 +08:00
qmzorvim123 256b3354ca
vue-schart访问地址找不到 2022-01-18 17:17:13 +08:00
qmzorvim123 be39b534c1
vue-schart访问地址找不到 2022-01-18 17:16:24 +08:00
林鑫 274198a7bd
Merge pull request #317 from swuecho/master
添加 好问 到赞助商
2021-11-27 20:54:07 +08:00
Hao Wu 439d48f8a3
添加 好问 到赞助商
谢谢
2021-11-27 00:43:04 +08:00
zhufengning 8aeb8b006e
The import should be "wangeditor" instead of "wangEditor"
error when starting dev server:
```
Error: The following dependencies are imported but could not be resolved:

  wangEditor (imported by /home/zfn/vue-manage-system/src/views/Editor.vue)

Are they installed?
```
2021-11-14 00:03:36 +08:00
linxin 039c9c3da4 fix:修复上传组件代码误删bug 2021-06-28 20:39:38 +08:00
lin-xin 9bc43e2efc feat:升级vite2和组合式api 2021-06-27 14:48:07 +08:00
林鑫 4dd1b4aae6
Update package.json 2021-04-15 16:34:50 +08:00
林鑫 3358fd7942
Update package.json 2021-04-08 19:34:49 +08:00
lin_xin 221aa3a5b9 style:更新文档 2021-03-17 22:19:34 +08:00
lin_xin 9e7ec0cea7 feature:路由缓存 2021-03-17 22:18:49 +08:00
lin_xin 98261e3eba Merge branch 'master' of https://github.com/lin-xin/vue-manage-system 2021-03-17 21:25:43 +08:00
lin_xin 1ae7fc8613 'style:更新文档' 2021-03-17 21:18:01 +08:00
林鑫 2874f01c34
Delete vue.config.js 2021-03-16 09:03:09 +08:00
lin_xin 96be359eab feature:升级到vue3和element-plus 2021-03-15 23:13:23 +08:00
林鑫 0f641c0f08
Create FUNDING.yml 2020-07-06 11:10:27 +08:00
林鑫 31f40a5e15
Merge pull request #223 from lin-xin/dev
Update README.md
2019-11-21 08:55:35 +08:00
林鑫 e8d1bfcad9
Update README.md 2019-11-21 08:54:51 +08:00
林鑫 6b73235ba3
Merge pull request #219 from lin-xin/dev
更新截图
2019-11-05 17:29:39 +08:00
linxin ce56f86f0d update:wms1.png 2019-11-05 17:28:57 +08:00
林鑫 03f67de289
Merge pull request #218 from lin-xin/dev
更新图表组件
2019-11-01 17:01:45 +08:00
linxin 5173cac28f 更新readme 2019-11-01 16:58:19 +08:00
linxin c5c018ac0f fix:修复bug和更新图表组件 2019-11-01 16:54:03 +08:00
linxin 3cfc7032bc 变更域名 2019-09-29 10:49:23 +08:00
林鑫 838a03086e
Merge pull request #208 from lin-xin/dev
EventBus折叠改为链式调用
2019-09-05 11:16:24 +08:00
linxin 2ebf10e9ba Login:添加登录 2019-08-30 15:03:18 +08:00
linxin 60eeaaf3eb EventBus折叠改为链式调用 2019-08-30 15:02:11 +08:00
林鑫 5b294b781b
Merge pull request #200 from lin-xin/dev
Table:修改搜索逻辑
2019-08-20 15:49:35 +08:00
linxin 3022814f8a Table:修改搜索逻辑 2019-08-20 15:46:42 +08:00
林鑫 21efb45431
Merge pull request #199 from lin-xin/dev
更新旧代码和依赖
2019-08-17 16:00:57 +08:00
linxin f7fb309680 合并pr 2019-08-17 15:58:46 +08:00
林鑫 d45bdbeeda
Merge pull request #195 from swuecho/patch-1
add backtop
2019-08-17 15:56:45 +08:00
林鑫 ed014e9304
Merge pull request #197 from swuecho/patch-2
左上角 el-icon-menu 改成 el-icon-s-fold 和 el-icon-s-unfold
2019-08-17 15:55:12 +08:00
linxin 289f817180 更新依赖 2019-08-17 15:53:42 +08:00
linxin a7b561baf5 更新依赖 2019-08-17 15:52:45 +08:00
linxin 49f8a69675 变更折叠侧边栏图标 2019-08-17 15:03:16 +08:00
linxin 3c2f4e891d 更新绿色主题 2019-08-17 14:58:33 +08:00
林鑫 c442dcfeb3
Merge pull request #198 from lin-xin/dev
优化表格、登录页面代码
2019-08-17 14:42:53 +08:00
linxin 2cb246ec3e 优化表格代码 2019-08-17 14:41:53 +08:00
linxin 3dd0b2fd8a 丰富表格组件 2019-08-17 11:47:15 +08:00
Hao Wu ba74b86448
Update Header.vue 2019-08-15 14:19:37 +08:00
Hao Wu 0ee9965612
add backtop 2019-08-15 14:00:36 +08:00
linxin 285ea37245 修改细节 2019-08-13 09:36:53 +08:00
linxin 53d067f0d1 添加prettier格式化 2019-08-09 10:34:32 +08:00
linxin a905850345 优化登录页面代码 2019-08-09 10:31:42 +08:00
林鑫 eb36dad968
Merge pull request #184 from lin-xin/dev
修复config文件注释错误
2019-06-28 11:07:06 +08:00
lin-xin d553a75d09 修复config文件注释错误 2019-06-28 11:05:46 +08:00
林鑫 38a5bd4bd0
Merge pull request #183 from lin-xin/dev
请求不使用代理
2019-06-23 15:39:19 +08:00
lin-xin bee36e1260 请求不使用代理 2019-06-23 15:38:24 +08:00
林鑫 5ac78cfd18
Merge pull request #178 from lin-xin/dev
修复表格顺序改变后误删其他数据
2019-06-13 17:07:08 +08:00
lin-xin ce21d13774 修复表格顺序改变后误删其他数据 2019-06-13 16:57:05 +08:00
林鑫 dc3405876e
Merge pull request #177 from lin-xin/dev
Update README.md
2019-05-26 17:02:57 +08:00
lin-xin 8c33d1b5b3 update 2019-05-26 17:02:06 +08:00
121 changed files with 8160 additions and 3501 deletions

View File

@ -1,3 +0,0 @@
> 1%
last 2 versions
not ie <= 8

12
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1,12 @@
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: https://lin-xin.github.io/images/weixin.jpg

45
.gitignore vendored
View File

@ -1,22 +1,23 @@
.DS_Store
node_modules
/dist
example.html
favicon.ico
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw*
.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?

View File

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2016 vue-manage-system
Copyright (c) 2016-2023 vue-manage-system
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

189
README.md
View File

@ -1,179 +1,80 @@
# 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>
# vue-manage-system
<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="http://blog.gdfengshuo.com/example/work/#/donate">
<img src="https://img.shields.io/badge/%24-donate-ff69b4.svg" alt="donate">
<a href="https://github.com/lin-xin/vue-manage-system/blob/master/LICENSE">
<img src="https://img.shields.io/github/license/mashape/apistatus.svg" alt="license">
</a>
基于Vue.js + Element UI 的后台管理系统解决方案。[线上地址](http://blog.gdfengshuo.com/example/work/)
基于 Vue3 + pinia + Element Plus 的后台管理系统解决方案。[线上演示](https://lin-xin.github.io/example/vue-manage-system/)
(本项目基于vue-cli3构建如果是vue-cli2的请下载[旧版本V3.2.0](https://github.com/lin-xin/vue-manage-system/releases/tag/V3.2.0))
> Vue2 版本请看 [tag-V4.2.0](https://github.com/lin-xin/vue-manage-system/tree/V4.2.0),带后台功能请看 [tsrpc-manage-system](https://github.com/lin-xin/tsrpc-manage-system)
[文档地址](https://lin-xin.github.io/example/vuems-doc/)
[English document](https://github.com/lin-xin/manage-system/blob/master/README_EN.md)
## 项目截图 ##
### 登录
![Image text](https://github.com/lin-xin/manage-system/raw/master/screenshots/wms3.png)
## 赞助商
### 默认皮肤 ###
![Image text](https://github.com/lin-xin/manage-system/raw/master/screenshots/wms1.png)
### 好问
### 浅绿色皮肤 ###
![Image text](https://github.com/lin-xin/manage-system/raw/master/screenshots/wms2.png)
[<img src="https://static.bestqa.net/logo/bestqa_haowen.png" width="220" height="100">](https://www.bestqa.net/home/index.html)
专业问卷服务,一对一客服,按需定制
## 支持作者
## 赞赏
请作者喝杯咖啡吧!(微信号linxin_20)
![微信扫一扫](http://blog.gdfengshuo.com/images/weixin.jpg)
![微信扫一扫](https://lin-xin.github.io/images/weixin.jpg)
## 前言 ##
之前在公司用了Vue + Element组件库做了个后台管理系统基本很多组件可以直接引用组件库的但是也有一些需求无法满足。像图片裁剪上传、富文本编辑器、图表等这些在后台管理系统中很常见的功能就需要引用其他的组件才能完成。从寻找组件到使用组件的过程中遇到了很多问题也积累了宝贵的经验。所以我就把开发这个后台管理系统的经验总结成这个后台管理系统解决方案。
## 前言
该方案作为一套多功能的后台框架模板适用于绝大部分的后台管理系统Web Management System开发。基于vue.js,使用vue-cli@3.2.3脚手架快速生成项目目录引用Element UI组件库方便开发快速简洁好看的组件。分离颜色样式支持手动切换主题色而且很方便使用自定义主题色。
(已经升级到 vue-cli@3.2.3,请更新依赖)
该方案作为一套多功能的后台框架模板,适用于绝大部分的后台管理系统开发。基于 Vue3 + pinia + typescript引用 Element Plus 组件库,方便开发。实现逻辑简单,适合外包项目,快速交付。
## 功能 ##
- [x] Element UI
- [x] 登录/注销
- [x] Dashboard
- [x] 表格
- [x] Tab选项卡
- [x] 表单
- [x] 图表 :bar_chart:
- [x] 富文本编辑器
- [x] markdown编辑器
- [x] 图片拖拽/裁剪上传
- [x] 支持切换主题色 :sparkles:
- [x] 列表拖拽排序
- [x] 权限测试
- [x] 404 / 403
- [x] 三级菜单
- [x] 自定义图标
- [x] 可拖拽弹窗
- [x] 国际化
## 功能
- [x] Element Plus
- [x] vite 3
- [x] pinia
- [x] typescript
- [x] 登录/注册
- [x] Dashboard
- [x] 表格/表单
- [x] 图表 :bar_chart:
- [x] 富文本/markdown 编辑器
- [x] 图片拖拽/裁剪上传
- [x] 权限管理
- [x] 三级菜单
- [x] 自定义图标
- [x] 主题切换
## 安装步骤
> 因为使用 vite3node 版本需要 14.18+
## 安装步骤 ##
```
git clone https://github.com/lin-xin/vue-manage-system.git // 把模板下载到本地
cd vue-manage-system // 进入模板目录
npm install // 安装项目依赖,等待安装完成之后,安装失败可用 cnpm 或 yarn
// 开启服务器,浏览器访问 http://localhost:8080
npm run serve
// 运行
npm run dev
// 执行构建命令生成的dist文件夹放在服务器下即可访问
npm run build
```
## 组件使用说明与演示 ##
### vue-schart ###
vue.js封装sChart.js的图表组件。访问地址[vue-schart](https://github.com/linxin/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="canvasId"
:type="type"
:data="data"
:options="options"
></schart>
</div>
</template>
<script>
import Schart from 'vue-schart'; // 导入Schart组件
export default {
data: function(){
return {
canvasId: 'myCanvas', // canvas的id
type: 'bar', // 图表类型
data: [
{name: '2014', value: 1342},
{name: '2015', value: 2123},
{name: '2016', value: 1654},
{name: '2017', value: 1795},
],
options: { // 图表可选参数
title: 'Total sales of stores in recent years'
}
}
},
components: {
Schart
}
}
</script>
<style>
.wrapper{
width: 7rem;
height: 5rem;
}
</style>
```
### 首页
## 其他注意事项 ##
### 一、如果我不想用到上面的某些组件呢,那我怎么在模板中删除掉不影响到其他功能呢? ###
![Image text](https://github.com/lin-xin/manage-system/raw/master/screenshots/wms1.png)
举个栗子,我不想用 Vue-Quill-Editor 这个组件,那我需要分四步走。
### 登录
第一步:删除该组件的路由,在目录 src/router/index.js 中,找到引入改组件的路由,删除下面这段代码。
```JavaScript
{
// 富文本编辑器组件
path: '/editor',
component: resolve => require(['../components/page/VueEditor.vue'], resolve)
},
```
第二步:删除引入该组件的文件。在目录 src/components/page/ 删除 VueEditor.vue 文件。
第三步:删除该页面的入口。在目录 src/components/common/Sidebar.vue 中,找到该入口,删除下面这段代码。
```js
{
index: 'editor',
title: '富文本编辑器'
},
```
第四步:卸载该组件。执行以下命令:
npm un vue-quill-editor -S
完成。
### 二、如何切换主题色呢? ###
第一步:打开 src/main.js 文件,找到引入 element 样式的地方,换成浅绿色主题。
```javascript
import 'element-ui/lib/theme-default/index.css'; // 默认主题
// import '../static/css/theme-green/index.css'; // 浅绿色主题
```
第二步:打开 src/App.vue 文件,找到 style 标签引入样式的地方,切换成浅绿色主题。
```javascript
@import "../static/css/main.css";
@import "../static/css/color-dark.css"; /*深色主题*/
/*@import "../static/css/theme-green/color-green.css"; !*浅绿色主题*!*/
```
第三步:打开 src/components/common/Sidebar.vue 文件,找到 el-menu 标签,把 background-color/text-color/active-text-color 属性去掉即可。
![Image text](https://github.com/lin-xin/manage-system/raw/master/screenshots/wms3.png)
## License
[MIT](https://github.com/lin-xin/vue-manage-system/blob/master/LICENSE)
[MIT](https://github.com/lin-xin/vue-manage-system/blob/master/LICENSE)

View File

@ -1,164 +1,119 @@
# vue-manage-system #
The web management system solution based on Vue2 and Element-UI。[live demo](http://blog.gdfengshuo.com/example/work/)
# 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
![WeChat](http://blog.gdfengshuo.com/images/weixin.jpg)
## 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 Vue2 and Element-UI. Color separation of color style, support manual switch themes, and it is convenient to use a custom theme color.
![WeChat](https://lin-xin.gitee.io/images/weixin.jpg)
## 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] Support manual switch themes :sparkles:
- [x] List drag sort
- [x] Permission
- [x] 404 / 403
- [x] Three level menu
- [x] Custom icon
## 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.
## Installation steps ##
## Function
git clone https://github.com/lin-xin/vue-manage-system.git // Clone templates
cd vue-manage-system // Enter template directory
npm install // Installation dependency
- [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
## Local development ##
## Installation steps
// Open server and access http://localhost:8080 in browser
npm run serve
git clone https://github.com/lin-xin/vue-manage-system.git // Clone templates
cd vue-manage-system // Enter template directory
npm install // Installation dependency
## Constructing production ##
## Local development
// Constructing project
npm run build
npm run dev
## Component description and presentation ##
## Constructing production
### vue-schart ###
Vue.js wrapper for sChart.js. Github : [vue-schart](https://github.com/linxin/vue-schart)
// Constructing project
npm run build
```JavaScript
## 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 :canvasId="canvasId"
:type="type"
:width="width"
:height="height"
:data="data"
:options="options"
></schart>
<schart class="wrapper" canvasId="myCanvas" :options="options"></schart>
</div>
</template>
<script>
import Schart from 'vue-schart';
export default {
data: function(){
return {
canvasId: 'myCanvas',
type: 'bar',
width: 500,
height: 400,
data: [
{name: '2014', value: 1342},
{name: '2015', value: 2123},
{name: '2016', value: 1654},
{name: '2017', value: 1795},
],
options: {
title: 'Total sales of stores in recent years'
}
}
<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],
},
components: {
Schart
}
}
{
label: "百货",
data: [164, 178, 190, 135, 160],
},
{
label: "食品",
data: [144, 198, 150, 235, 120],
},
],
})
</script>
<style>
.wrapper {
width: 7rem;
height: 5rem;
}
</style>
```
### element-ui ###
A desktop component library based on vue.js2.0 . Github : [element](http://element.eleme.io/#/zh-CN/component/layout)
## Screenshot
### Vue-Quill-Editor ###
Quill editor component for Vue2. Github : [vue-quill-editor](https://github.com/surmon-china/vue-quill-editor)
### mavonEditor ###
A markdown editor based on Vue that supports a variety of personalized features. Github: [mavonEditor](https://github.com/hinesboy/mavonEditor)
### vue-cropperjs ###
A Vue wrapper component for cropperjs. Github: [vue-cropperjs](https://github.com/Agontuk/vue-cropperjs)
## Notice ##
### 一、If I don't want to use some components, how can I delete it? ###
For example, I don't want to use the Vue-Quill-Editor component, I need to take four steps.
The first step to remove the component of the routing. Enter 'src/router/index.js' and delete the code below.
```JavaScript
{
path: '/editor',
component: resolve => require(['../components/page/VueEditor.vue'], resolve)
},
```
Second,delete the component files. Enter 'src/components/page/' and delete 'VueEditor.vue' file.
The third step is to delete the entry. Enter 'src/components/common/Sidebar.vue' and delete the code below.
```js
{
index: 'editor',
title: '富文本编辑器'
},
```
Finally, uninstall this component.
npm un vue-quill-editor -S
Complete!
### 二、How to switch themes? ###
The first step to enter 'src/main.js' and change into green theme.
```javascript
import 'element-ui/lib/theme-default/index.css'; // default theme
// import '../static/css/theme-green/index.css'; // green theme
```
The second step to enter 'src/App.vue' and change into green theme.
```javascript
@import "../static/css/main.css";
@import "../static/css/color-dark.css"; /*深色主题*/
/*@import "../static/css/theme-green/color-green.css"; !*浅绿色主题*!*/
```
Finally,enter 'src/components/common/Sidebar.vue' and find el-menu Tags,delete 'background-color/text-color/active-text-color'。
## Screenshot ##
### Default theme ###
### Default theme
![Image text](https://github.com/lin-xin/manage-system/raw/master/screenshots/wms1.png)
### Green theme ###
### Login
![Image text](https://github.com/lin-xin/manage-system/raw/master/screenshots/wms2.png)
![Image text](https://github.com/lin-xin/manage-system/raw/master/screenshots/wms3.png)
## License
[MIT](https://github.com/lin-xin/vue-manage-system/blob/master/LICENSE)
[MIT](https://github.com/lin-xin/vue-manage-system/blob/master/LICENSE)

5
auto-imports.d.ts vendored Normal file
View File

@ -0,0 +1,5 @@
// Generated by 'unplugin-auto-import'
export {}
declare global {
}

View File

@ -1,5 +0,0 @@
module.exports = {
presets: [
'@vue/app'
]
}

81
components.d.ts vendored Normal file
View File

@ -0,0 +1,81 @@
// 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 {
Countup: typeof import('./src/components/countup.vue')['default']
ElAvatar: typeof import('element-plus/es')['ElAvatar']
ElButton: typeof import('element-plus/es')['ElButton']
ElCalendar: typeof import('element-plus/es')['ElCalendar']
ElCard: typeof import('element-plus/es')['ElCard']
ElCarousel: typeof import('element-plus/es')['ElCarousel']
ElCarouselItem: typeof import('element-plus/es')['ElCarouselItem']
ElCascader: typeof import('element-plus/es')['ElCascader']
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']
ElCol: typeof import('element-plus/es')['ElCol']
ElColorPicker: typeof import('element-plus/es')['ElColorPicker']
ElCountdown: typeof import('element-plus/es')['ElCountdown']
ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
ElDescriptions: typeof import('element-plus/es')['ElDescriptions']
ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem']
ElDialog: typeof import('element-plus/es')['ElDialog']
ElDivider: typeof import('element-plus/es')['ElDivider']
ElDropdown: typeof import('element-plus/es')['ElDropdown']
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
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']
ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
ElLink: typeof import('element-plus/es')['ElLink']
ElMenu: typeof import('element-plus/es')['ElMenu']
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
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']
ElRadioButton: typeof import('element-plus/es')['ElRadioButton']
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
ElRate: typeof import('element-plus/es')['ElRate']
ElResult: typeof import('element-plus/es')['ElResult']
ElRow: typeof import('element-plus/es')['ElRow']
ElSelect: typeof import('element-plus/es')['ElSelect']
ElSlider: typeof import('element-plus/es')['ElSlider']
ElSpace: typeof import('element-plus/es')['ElSpace']
ElStatistic: typeof import('element-plus/es')['ElStatistic']
ElStep: typeof import('element-plus/es')['ElStep']
ElSteps: typeof import('element-plus/es')['ElSteps']
ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
ElSwitch: typeof import('element-plus/es')['ElSwitch']
ElTable: typeof import('element-plus/es')['ElTable']
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']
ElTimeline: typeof import('element-plus/es')['ElTimeline']
ElTimelineItem: typeof import('element-plus/es')['ElTimelineItem']
ElTimePicker: typeof import('element-plus/es')['ElTimePicker']
ElTooltip: typeof import('element-plus/es')['ElTooltip']
ElTour: typeof import('element-plus/es')['ElTour']
ElTourStep: typeof import('element-plus/es')['ElTourStep']
ElTransfer: typeof import('element-plus/es')['ElTransfer']
ElUpload: typeof import('element-plus/es')['ElUpload']
ElWatermark: typeof import('element-plus/es')['ElWatermark']
Header: typeof import('./src/components/header.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
Sidebar: typeof import('./src/components/sidebar.vue')['default']
TableCustom: typeof import('./src/components/table-custom.vue')['default']
TableDetail: typeof import('./src/components/table-detail.vue')['default']
TableEdit: typeof import('./src/components/table-edit.vue')['default']
TableSearch: typeof import('./src/components/table-search.vue')['default']
Tabs: typeof import('./src/components/tabs.vue')['default']
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 914 B

22
index.html Normal file
View 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="//at.alicdn.com/t/c/font_830376_92o68tc95je.css">
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled.
Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
<!-- built files will be auto injected -->
</body>
</html>

View File

@ -1,28 +1,44 @@
{
"name": "vue-manage-system",
"version": "4.0.1",
"private": true,
"scripts": {
"dev": "npm run serve",
"serve": "vue-cli-service serve",
"build": "vue-cli-service build"
},
"dependencies": {
"axios": "^0.18.0",
"babel-polyfill": "^6.26.0",
"element-ui": "^2.8.2",
"mavon-editor": "^2.6.17",
"vue": "^2.6.10",
"vue-cropperjs": "^3.0.0",
"vue-i18n": "^8.10.0",
"vue-quill-editor": "^3.0.6",
"vue-router": "^3.0.1",
"vue-schart": "^1.0.0",
"vuedraggable": "^2.17.0"
},
"devDependencies": {
"@vue/cli-plugin-babel": "^3.2.0",
"@vue/cli-service": "^3.2.0",
"vue-template-compiler": "^2.5.21"
}
}
{
"name": "vue-manage-system",
"version": "5.5.0",
"private": true,
"scripts": {
"dev": "vite",
"build": "vue-tsc --noEmit && vite build",
"serve": "vite preview"
},
"dependencies": {
"@element-plus/icons-vue": "*",
"@wangeditor/editor": "^5.1.23",
"@wangeditor/editor-for-vue": "^5.1.12",
"axios": "^1.6.3",
"countup.js": "^2.8.0",
"echarts": "^5.5.0",
"echarts-wordcloud": "^2.1.0",
"element-plus": "^2.6.3",
"md-editor-v3": "^2.11.2",
"nprogress": "^0.2.0",
"pinia": "^2.1.7",
"vue": "^3.4.5",
"vue-cropper": "1.1.1",
"vue-echarts": "^6.6.9",
"vue-router": "^4.2.5",
"vue-schart": "^2.0.0",
"xlsx": "^0.18.5"
},
"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"
]
}

View File

@ -1,5 +0,0 @@
module.exports = {
plugins: {
autoprefixer: {}
}
}

View File

@ -1,17 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no">
<link rel="stylesheet" href="//at.alicdn.com/t/font_830376_qzecyukz0s.css">
<title>vue-manage-system</title>
</head>
<body>
<noscript>
<strong>We're sorry but vms doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

46
public/mock/role.json Normal file
View File

@ -0,0 +1,46 @@
{
"list": [
{
"id": 1,
"name": "管理员",
"key": "admin",
"status": true,
"permiss": [
"0",
"1",
"11",
"12",
"13",
"2",
"21",
"22",
"23",
"24",
"3",
"31",
"32",
"33",
"331",
"332",
"4",
"41",
"42",
"5"
]
},
{
"id": 2,
"name": "普通用户",
"key": "user",
"status": true,
"permiss": [
"0",
"1",
"11",
"12",
"13"
]
}
],
"pageTotal": 2
}

41
public/mock/table.json Normal file
View File

@ -0,0 +1,41 @@
{
"list": [
{
"id": 1,
"name": "张三",
"money": 123,
"address": "广东省东莞市长安镇",
"state": true,
"date": "2019-11-1",
"thumb": "https://lin-xin.gitee.io/images/post/wms.png"
},
{
"id": 2,
"name": "李四",
"money": 456,
"address": "广东省广州市白云区",
"state": true,
"date": "2019-10-11",
"thumb": "https://lin-xin.gitee.io/images/post/node3.png"
},
{
"id": 3,
"name": "王五",
"money": 789,
"address": "湖南省长沙市",
"state": false,
"date": "2019-11-11",
"thumb": "https://lin-xin.gitee.io/images/post/parcel.png"
},
{
"id": 4,
"name": "赵六",
"money": 1011,
"address": "福建省厦门市鼓浪屿",
"state": true,
"date": "2019-10-20",
"thumb": "https://lin-xin.gitee.io/images/post/notice.png"
}
],
"pageTotal": 4
}

23
public/mock/user.json Normal file
View File

@ -0,0 +1,23 @@
{
"list": [
{
"id": 1,
"name": "张三",
"password": "123",
"email": "123@qq.com",
"phone": "12345678944",
"date": "2024-01-01",
"role": "管理员"
},
{
"id": 2,
"name": "李四",
"password": "123",
"email": "1234@qq.com",
"phone": "12345678945",
"date": "2024-01-01",
"role": "普通用户"
}
],
"pageTotal": 2
}

BIN
public/template.xlsx Normal file

Binary file not shown.

View File

@ -1,43 +0,0 @@
{
"list": [{
"date": "1997-11-11",
"name": "林丽",
"address": "吉林省 辽源市 龙山区"
}, {
"date": "1987-09-24",
"name": "文敏",
"address": "江西省 萍乡市 芦溪县"
}, {
"date": "1996-08-08",
"name": "杨秀兰",
"address": "黑龙江省 黑河市 五大连池市"
}, {
"date": "1978-06-18",
"name": "魏强",
"address": "广东省 韶关市 始兴县"
}, {
"date": "1977-07-09",
"name": "石秀兰",
"address": "江苏省 宿迁市 宿豫区"
}, {
"date": "1994-09-20",
"name": "朱洋",
"address": "海外 海外 -"
}, {
"date": "1980-01-22",
"name": "傅敏",
"address": "海外 海外 -"
}, {
"date": "1985-10-10",
"name": "毛明",
"address": "内蒙古自治区 包头市 九原区"
}, {
"date": "1975-09-08",
"name": "何静",
"address": "西藏自治区 阿里地区 普兰县"
}, {
"date": "1970-06-07",
"name": "郭秀英",
"address": "四川省 巴中市 恩阳区"
}]
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 112 KiB

After

Width:  |  Height:  |  Size: 425 KiB

View File

@ -1,10 +1,17 @@
<template>
<div id="app">
<router-view></router-view>
</div>
</template>
<style>
@import "./assets/css/main.css";
@import "./assets/css/color-dark.css"; /*深色主题*/
/*@import "./assets/css/theme-green/color-green.css"; 浅绿色主题*/
</style>
<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';
import { useThemeStore } from './store/theme';
const theme = useThemeStore();
theme.initTheme();
</script>
<style>
@import './assets/css/main.css';
</style>

22
src/api/index.ts Normal file
View File

@ -0,0 +1,22 @@
import request from '../utils/request';
export const fetchData = () => {
return request({
url: './mock/table.json',
method: 'get'
});
};
export const fetchUserData = () => {
return request({
url: './mock/user.json',
method: 'get'
});
};
export const fetchRoleData = () => {
return request({
url: './mock/role.json',
method: 'get'
});
};

View File

@ -1,28 +0,0 @@
.header{
background-color: #242f42;
}
.login-wrap{
background: #324157;
}
.plugins-tips{
background: #eef1f6;
}
.plugins-tips a{
color: #20a0ff;
}
.el-upload--text em {
color: #20a0ff;
}
.pure-button{
background: #20a0ff;
}
.tags-li.active {
border: 1px solid #409EFF;
background-color: #409EFF;
}
.message-title{
color: #20a0ff;
}
.collapse-btn:hover{
background: rgb(40,52,70);
}

View File

@ -1,4 +1,4 @@
[class*=" el-icon-lx"], [class^=el-icon-lx] {
font-family: lx-iconfont!important;
}
[class*=" el-icon-lx"],
[class^=el-icon-lx] {
font-family: lx-iconfont !important;
}

View File

@ -1,173 +1,87 @@
* {
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;
}
.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 .5s;
}
.move-enter,
.move-leave {
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;
}
/*Upload*/
.pure-button {
width: 150px;
height: 40px;
line-height: 40px;
text-align: center;
color: #fff;
border-radius: 3px;
}
.g-core-image-corp-container .info-aside {
height: 45px;
}
.el-upload--text {
background-color: #fff;
border: 1px dashed #d9d9d9;
border-radius: 6px;
box-sizing: border-box;
width: 360px;
height: 180px;
text-align: center;
cursor: pointer;
position: relative;
overflow: hidden;
}
.el-upload--text .el-icon-upload {
font-size: 67px;
color: #97a8be;
margin: 40px 0 16px;
line-height: 50px;
}
.el-upload--text {
color: #97a8be;
font-size: 14px;
text-align: center;
}
.el-upload--text em {
font-style: normal;
}
/*VueEditor*/
.ql-container {
min-height: 400px;
}
.ql-snow .ql-tooltip {
transform: translateX(117.5px) translateY(10px) !important;
}
.editor-btn {
margin-top: 20px;
}
/*markdown*/
.v-note-wrapper .v-note-panel {
min-height: 500px;
}
* {
margin: 0;
padding: 0;
outline: 0 !important;
}
body {
font-family: 'PingFang SC', 'Helvetica Neue', Helvetica, 'microsoft yahei', arial, STHeiTi, sans-serif;
}
a {
text-decoration: none;
}
i {
font-style: normal;
}
.container {
padding: 30px;
background: #fff;
border: 1px solid #ddd;
border-radius: 5px;
}
.el-table th {
background-color: #f5f7fa !important;
}
.plugins-tips {
padding: 20px 10px;
margin-bottom: 20px;
background: #eef1f6;
}
.plugins-tips a {
color: var(--el-color-primary);
}
.el-button + .el-tooltip {
margin-left: 10px;
}
.mgb20 {
margin-bottom: 20px;
}
.mgb10 {
margin-bottom: 10px;
}
.mr10 {
margin-right: 10px;
}
.move-enter-active,
.move-leave-active {
transition: opacity 0.1s ease;
}
.move-enter-from,
.move-leave-to {
opacity: 0;
}
.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;
}
[hidden] {
display: none !important;
}
.flex-center {
display: flex;
justify-content: center;
align-items: center;
}
:root {
--header-bg-color: #242f42;
--header-text-color: #fff;
--active-color: var(--el-color-primary);
}

View File

@ -1,29 +0,0 @@
.header{
background-color: #07c4a8;
}
.login-wrap{
background: rgba(56, 157, 170, 0.82);;
}
.plugins-tips{
background: #f2f2f2;
}
.plugins-tips a{
color: #00d1b2;
}
.el-upload--text em {
color: #00d1b2;
}
.pure-button{
background: #00d1b2;
}
.pagination > .active > a, .pagination > .active > a:hover, .pagination > .active > a:focus, .pagination > .active > span, .pagination > .active > span:hover, .pagination > .active > span:focus {
background-color: #00d1b2 !important;
border-color: #00d1b2 !important;
}
.tags-li.active {
border: 1px solid #00d1b2;
background-color: #00d1b2;
}
.collapse-btn:hover{
background: #00d1b2;
}

File diff suppressed because one or more lines are too long

1
src/assets/img/logo.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 144.08 128.61"><title>&#x8D44;&#x6E90; 82</title><path d="M72.23 128.61c-7.1-.23-11.51-3.72-14.76-9.36C48 102.87 38.43 86.59 29.1 70.16a36 36 0 0 1-4.47-11.35A14.61 14.61 0 0 1 34 42.51c7.49-2.71 15.71-.21 19.67 6.43 7.52 12.56 14.77 25.27 22.12 37.92 3 5.17 5.89 10.43 9 15.51 5 8 3.45 18-4.22 23.31-2.3 1.62-5.52 1.99-8.34 2.93z" fill="#2ef2e9"/><path d="M72.66.33c6-.57 10.39 2.6 13.51 8C95.61 24.69 105 41.1 114.52 57.4c3.9 6.65-.28 17.13-6.39 20.44-8.93 4.83-17.88 1.28-21.86-5.62C76.82 55.86 67.14 39.62 58.11 23 52.06 12 59.61.24 72.66.33z" fill="#fa6663"/><path d="M144.08 15.83c-.58 8.62-6.73 15.57-15.51 15.66-9.31.09-16.87-7-16.95-15.62S119 0 127.87 0c9.13.09 16.22 7 16.21 15.83z" fill="#fbb355"/><path d="M16.24 31.5C7 31.33-.19 24.42 0 15.8.19 7.5 7.19-.06 14.64 0c10.53.08 18.27 6.73 17.61 15.9-.64 8.96-6.25 15.28-16.01 15.6z" fill="#8a56c2"/></svg>

After

Width:  |  Height:  |  Size: 918 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

View File

@ -1,43 +0,0 @@
{
"list": [{
"date": "1997-11-11",
"name": "林丽",
"address": "吉林省 辽源市 龙山区"
}, {
"date": "1987-09-24",
"name": "文敏",
"address": "江西省 萍乡市 芦溪县"
}, {
"date": "1996-08-08",
"name": "杨秀兰",
"address": "黑龙江省 黑河市 五大连池市"
}, {
"date": "1978-06-18",
"name": "魏强",
"address": "广东省 韶关市 始兴县"
}, {
"date": "1977-07-09",
"name": "石秀兰",
"address": "江苏省 宿迁市 宿豫区"
}, {
"date": "1994-09-20",
"name": "朱洋",
"address": "海外 海外 -"
}, {
"date": "1980-01-22",
"name": "傅敏",
"address": "海外 海外 -"
}, {
"date": "1985-10-10",
"name": "毛明",
"address": "内蒙古自治区 包头市 九原区"
}, {
"date": "1975-09-08",
"name": "何静",
"address": "西藏自治区 阿里地区 普兰县"
}, {
"date": "1970-06-07",
"name": "郭秀英",
"address": "四川省 巴中市 恩阳区"
}]
}

View File

@ -1,185 +0,0 @@
<template>
<div class="header">
<!-- 折叠按钮 -->
<div class="collapse-btn" @click="collapseChage">
<i class="el-icon-menu"></i>
</div>
<div class="logo">后台管理系统</div>
<div class="header-right">
<div class="header-user-con">
<!-- 全屏显示 -->
<div class="btn-fullscreen" @click="handleFullScreen">
<el-tooltip effect="dark" :content="fullscreen?`取消全屏`:`全屏`" placement="bottom">
<i class="el-icon-rank"></i>
</el-tooltip>
</div>
<!-- 消息中心 -->
<div class="btn-bell">
<el-tooltip effect="dark" :content="message?`有${message}条未读消息`:`消息中心`" placement="bottom">
<router-link to="/tabs">
<i class="el-icon-bell"></i>
</router-link>
</el-tooltip>
<span class="btn-bell-badge" v-if="message"></span>
</div>
<!-- 用户头像 -->
<div class="user-avator"><img src="../../assets/img/img.jpg"></div>
<!-- 用户名下拉菜单 -->
<el-dropdown class="user-name" trigger="click" @command="handleCommand">
<span class="el-dropdown-link">
{{username}} <i class="el-icon-caret-bottom"></i>
</span>
<el-dropdown-menu slot="dropdown">
<a href="http://blog.gdfengshuo.com/about/" target="_blank">
<el-dropdown-item>关于作者</el-dropdown-item>
</a>
<a href="https://github.com/lin-xin/vue-manage-system" target="_blank">
<el-dropdown-item>项目仓库</el-dropdown-item>
</a>
<el-dropdown-item divided command="loginout">退出登录</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
</div>
</div>
</template>
<script>
import bus from '../common/bus';
export default {
data() {
return {
collapse: false,
fullscreen: false,
name: 'linxin',
message: 2
}
},
computed:{
username(){
let username = localStorage.getItem('ms_username');
return username ? username : this.name;
}
},
methods:{
//
handleCommand(command) {
if(command == 'loginout'){
localStorage.removeItem('ms_username')
this.$router.push('/login');
}
},
//
collapseChage(){
this.collapse = !this.collapse;
bus.$emit('collapse', this.collapse);
},
//
handleFullScreen(){
let element = document.documentElement;
if (this.fullscreen) {
if (document.exitFullscreen) {
document.exitFullscreen();
} else if (document.webkitCancelFullScreen) {
document.webkitCancelFullScreen();
} else if (document.mozCancelFullScreen) {
document.mozCancelFullScreen();
} else if (document.msExitFullscreen) {
document.msExitFullscreen();
}
} else {
if (element.requestFullscreen) {
element.requestFullscreen();
} else if (element.webkitRequestFullScreen) {
element.webkitRequestFullScreen();
} else if (element.mozRequestFullScreen) {
element.mozRequestFullScreen();
} else if (element.msRequestFullscreen) {
// IE11
element.msRequestFullscreen();
}
}
this.fullscreen = !this.fullscreen;
}
},
mounted(){
if(document.body.clientWidth < 1500){
this.collapseChage();
}
}
}
</script>
<style scoped>
.header {
position: relative;
box-sizing: border-box;
width: 100%;
height: 70px;
font-size: 22px;
color: #fff;
}
.collapse-btn{
float: left;
padding: 0 21px;
cursor: pointer;
line-height: 70px;
}
.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;
}
.btn-bell-badge{
position: absolute;
right: 0;
top: -2px;
width: 8px;
height: 8px;
border-radius: 4px;
background: #f56c6c;
color: #fff;
}
.btn-bell .el-icon-bell{
color: #fff;
}
.user-name{
margin-left: 10px;
}
.user-avator{
margin-left: 20px;
}
.user-avator img{
display: block;
width:40px;
height:40px;
border-radius: 50%;
}
.el-dropdown-link{
color: #fff;
cursor: pointer;
}
.el-dropdown-menu__item{
text-align: center;
}
</style>

View File

@ -1,48 +0,0 @@
<template>
<div class="wrapper">
<v-head></v-head>
<v-sidebar></v-sidebar>
<div class="content-box" :class="{'content-collapse':collapse}">
<v-tags></v-tags>
<div class="content">
<transition name="move" mode="out-in">
<keep-alive :include="tagsList">
<router-view></router-view>
</keep-alive>
</transition>
</div>
</div>
</div>
</template>
<script>
import vHead from './Header.vue';
import vSidebar from './Sidebar.vue';
import vTags from './Tags.vue';
import bus from './bus';
export default {
data(){
return {
tagsList: [],
collapse: false
}
},
components:{
vHead, vSidebar, vTags
},
created(){
bus.$on('collapse', msg => {
this.collapse = msg;
})
// 使keep-alive
bus.$on('tags', msg => {
let arr = [];
for(let i = 0, len = msg.length; i < len; i ++){
msg[i].name && arr.push(msg[i].name);
}
this.tagsList = arr;
})
}
}
</script>

View File

@ -1,165 +0,0 @@
<template>
<div class="sidebar">
<el-menu class="sidebar-el-menu" :default-active="onRoutes" :collapse="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-submenu :index="item.index" :key="item.index">
<template slot="title">
<i :class="item.icon"></i><span slot="title">{{ item.title }}</span>
</template>
<template v-for="subItem in item.subs">
<el-submenu v-if="subItem.subs" :index="subItem.index" :key="subItem.index">
<template slot="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-submenu>
<el-menu-item v-else :index="subItem.index" :key="subItem.index">
{{ subItem.title }}
</el-menu-item>
</template>
</el-submenu>
</template>
<template v-else>
<el-menu-item :index="item.index" :key="item.index">
<i :class="item.icon"></i><span slot="title">{{ item.title }}</span>
</el-menu-item>
</template>
</template>
</el-menu>
</div>
</template>
<script>
import bus from '../common/bus';
export default {
data() {
return {
collapse: false,
items: [
{
icon: 'el-icon-lx-home',
index: 'dashboard',
title: '系统首页'
},
{
icon: 'el-icon-lx-cascades',
index: 'table',
title: '基础表格'
},
{
icon: 'el-icon-lx-copy',
index: 'tabs',
title: 'tab选项卡'
},
{
icon: 'el-icon-lx-calendar',
index: '3',
title: '表单相关',
subs: [
{
index: 'form',
title: '基本表单'
},
{
index: '3-2',
title: '三级菜单',
subs: [
{
index: 'editor',
title: '富文本编辑器'
},
{
index: 'markdown',
title: 'markdown编辑器'
},
]
},
{
index: 'upload',
title: '文件上传'
}
]
},
{
icon: 'el-icon-lx-emoji',
index: 'icon',
title: '自定义图标'
},
{
icon: 'el-icon-pie-chart',
index: 'charts',
title: 'schart图表'
},
{
icon: 'el-icon-rank',
index: '6',
title: '拖拽组件',
subs: [
{
index: 'drag',
title: '拖拽列表',
},
{
index: 'dialog',
title: '拖拽弹框',
}
]
},
{
icon: 'el-icon-lx-global',
index: 'i18n',
title: '国际化功能'
},
{
icon: 'el-icon-lx-warn',
index: '7',
title: '错误处理',
subs: [
{
index: 'permission',
title: '权限测试'
},
{
index: '404',
title: '404页面'
}
]
}
]
}
},
computed:{
onRoutes(){
return this.$route.path.replace('/','');
}
},
created(){
// Event Bus
bus.$on('collapse', msg => {
this.collapse = msg;
})
}
}
</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>

View File

@ -1,186 +0,0 @@
<template>
<div class="tags" v-if="showTags">
<ul>
<li class="tags-li" v-for="(item,index) in tagsList" :class="{'active': isActive(item.path)}" :key="index">
<router-link :to="item.path" class="tags-li-title">
{{item.title}}
</router-link>
<span class="tags-li-icon" @click="closeTags(index)"><i class="el-icon-close"></i></span>
</li>
</ul>
<div class="tags-close-box">
<el-dropdown @command="handleTags">
<el-button size="mini" type="primary">
标签选项<i class="el-icon-arrow-down el-icon--right"></i>
</el-button>
<el-dropdown-menu size="small" slot="dropdown">
<el-dropdown-item command="other">关闭其他</el-dropdown-item>
<el-dropdown-item command="all">关闭所有</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
</div>
</template>
<script>
import bus from './bus';
export default {
data() {
return {
tagsList: []
}
},
methods: {
isActive(path) {
return path === this.$route.fullPath;
},
//
closeTags(index) {
const delItem = this.tagsList.splice(index, 1)[0];
const item = this.tagsList[index] ? this.tagsList[index] : this.tagsList[index - 1];
if (item) {
delItem.path === this.$route.fullPath && this.$router.push(item.path);
}else{
this.$router.push('/');
}
},
//
closeAll(){
this.tagsList = [];
this.$router.push('/');
},
//
closeOther(){
const curItem = this.tagsList.filter(item => {
return item.path === this.$route.fullPath;
})
this.tagsList = curItem;
},
//
setTags(route){
const isExist = this.tagsList.some(item => {
return item.path === route.fullPath;
})
if(!isExist){
if(this.tagsList.length >= 8){
this.tagsList.shift();
}
this.tagsList.push({
title: route.meta.title,
path: route.fullPath,
name: route.matched[1].components.default.name
})
}
bus.$emit('tags', this.tagsList);
},
handleTags(command){
command === 'other' ? this.closeOther() : this.closeAll();
}
},
computed: {
showTags() {
return this.tagsList.length > 0;
}
},
watch:{
$route(newValue, oldValue){
this.setTags(newValue);
}
},
created(){
this.setTags(this.$route);
//
bus.$on('close_current_tags', () => {
for (let i = 0, len = this.tagsList.length; i < len; i++) {
const item = this.tagsList[i];
if(item.path === this.$route.fullPath){
if(i < len - 1){
this.$router.push(this.tagsList[i+1].path);
}else if(i > 0){
this.$router.push(this.tagsList[i-1].path);
}else{
this.$router.push('/');
}
this.tagsList.splice(i, 1);
break;
}
}
})
}
}
</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 {
float: left;
margin: 3px 5px 2px 3px;
border-radius: 3px;
font-size: 12px;
overflow: hidden;
cursor: pointer;
height: 23px;
line-height: 23px;
border: 1px solid #e9eaec;
background: #fff;
padding: 0 5px 0 12px;
vertical-align: middle;
color: #666;
-webkit-transition: all .3s ease-in;
-moz-transition: all .3s ease-in;
transition: all .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, .1);
z-index: 10;
}
</style>

View File

@ -1,6 +0,0 @@
import Vue from 'vue';
// 使用 Event Bus
const bus = new Vue();
export default bus;

View File

@ -1,80 +0,0 @@
import Vue from 'vue';
// v-dialogDrag: 弹窗拖拽属性
Vue.directive('dialogDrag', {
bind(el, binding, vnode, oldVnode) {
const dialogHeaderEl = el.querySelector('.el-dialog__header');
const dragDom = el.querySelector('.el-dialog');
dialogHeaderEl.style.cssText += ';cursor:move;'
dragDom.style.cssText += ';top:0px;'
// 获取原有属性 ie dom元素.currentStyle 火狐谷歌 window.getComputedStyle(dom元素, null);
const sty = (() => {
if (window.document.currentStyle) {
return (dom, attr) => dom.currentStyle[attr];
} else {
return (dom, attr) => getComputedStyle(dom, false)[attr];
}
})()
dialogHeaderEl.onmousedown = (e) => {
// 鼠标按下,计算当前元素距离可视区的距离
const disX = e.clientX - dialogHeaderEl.offsetLeft;
const disY = e.clientY - dialogHeaderEl.offsetTop;
const screenWidth = document.body.clientWidth; // body当前宽度
const screenHeight = document.documentElement.clientHeight; // 可见区域高度(应为body高度可某些环境下无法获取)
const dragDomWidth = dragDom.offsetWidth; // 对话框宽度
const dragDomheight = dragDom.offsetHeight; // 对话框高度
const minDragDomLeft = dragDom.offsetLeft;
const maxDragDomLeft = screenWidth - dragDom.offsetLeft - dragDomWidth;
const minDragDomTop = dragDom.offsetTop;
const maxDragDomTop = screenHeight - dragDom.offsetTop - dragDomheight;
// 获取到的值带px 正则匹配替换
let styL = sty(dragDom, 'left');
let styT = sty(dragDom, 'top');
// 注意在ie中 第一次获取到的值为组件自带50% 移动之后赋值为px
if (styL.includes('%')) {
styL = +document.body.clientWidth * (+styL.replace(/\%/g, '') / 100);
styT = +document.body.clientHeight * (+styT.replace(/\%/g, '') / 100);
} else {
styL = +styL.replace(/\px/g, '');
styT = +styT.replace(/\px/g, '');
};
document.onmousemove = function (e) {
// 通过事件委托,计算移动的距离
let left = e.clientX - disX;
let top = e.clientY - disY;
// 边界处理
if (-(left) > minDragDomLeft) {
left = -(minDragDomLeft);
} else if (left > maxDragDomLeft) {
left = maxDragDomLeft;
}
if (-(top) > minDragDomTop) {
top = -(minDragDomTop);
} else if (top > maxDragDomTop) {
top = maxDragDomTop;
}
// 移动当前元素
dragDom.style.cssText += `;left:${left + styL}px;top:${top + styT}px;`;
};
document.onmouseup = function (e) {
document.onmousemove = null;
document.onmouseup = null;
};
}
}
})

View File

@ -1,30 +0,0 @@
export const messages = {
'zh': {
i18n: {
breadcrumb: '国际化产品',
tips: '通过切换语言按钮,来改变当前内容的语言。',
btn: '切换英文',
title1: '常用用法',
p1: '要是你把你的秘密告诉了风,那就别怪风把它带给树。',
p2: '没有什么比信念更能支撑我们度过艰难的时光了。',
p3: '只要能把自己的事做好,并让自己快乐,你就领先于大多数人了。',
title2: '组件插值',
info: 'Element组件需要国际化请参考 {action}。',
value: '文档'
}
},
'en': {
i18n: {
breadcrumb: 'International Products',
tips: 'Click on the button to change the current language. ',
btn: 'Switch Chinese',
title1: 'Common usage',
p1: "If you reveal your secrets to the wind you should not blame the wind for revealing them to the trees.",
p2: "Nothing can help us endure dark times better than our faith. ",
p3: "If you can do what you do best and be happy, you're further along in life than most people.",
title2: 'Component interpolation',
info: 'The default language of Element is Chinese. If you wish to use another language, please refer to the {action}.',
value: 'documentation'
}
}
}

View File

@ -0,0 +1,39 @@
<template>
<span ref="countRef"></span>
</template>
<script setup lang="ts">
import { onMounted, ref, watch } from 'vue';
import { CountUp } from 'countup.js';
const props = defineProps({
end: {
type: Number,
required: true,
},
options: {
type: Object,
default: () => ({}),
required: false,
},
});
const countRef = ref<any>(null);
let countUp: any;
onMounted(() => {
countUp = new CountUp(countRef.value, props.end, props.options);
if (countUp.error) {
console.error(countUp.error);
return;
}
countUp.start();
});
watch(() => props.end, (newVal) => {
if (countUp) {
countUp.update(newVal);
}
});
</script>

204
src/components/header.vue Normal file
View File

@ -0,0 +1,204 @@
<template>
<div class="header">
<!-- 折叠按钮 -->
<div class="header-left">
<img class="logo" src="../assets/img/logo.svg" alt="" />
<div class="web-title">后台管理系统</div>
<div class="collapse-btn" @click="collapseChage">
<el-icon v-if="sidebar.collapse">
<Expand />
</el-icon>
<el-icon v-else>
<Fold />
</el-icon>
</div>
</div>
<div class="header-right">
<div class="header-user-con">
<div class="btn-icon" @click="router.push('/theme')">
<el-tooltip effect="dark" content="设置主题" placement="bottom">
<i class="el-icon-lx-skin"></i>
</el-tooltip>
</div>
<div class="btn-icon" @click="router.push('/ucenter')">
<el-tooltip
effect="dark"
:content="message ? `有${message}条未读消息` : `消息中心`"
placement="bottom"
>
<i class="el-icon-lx-notice"></i>
</el-tooltip>
<span class="btn-bell-badge" v-if="message"></span>
</div>
<div class="btn-icon" @click="setFullScreen">
<el-tooltip effect="dark" content="全屏" placement="bottom">
<i class="el-icon-lx-full"></i>
</el-tooltip>
</div>
<!-- 用户头像 -->
<el-avatar class="user-avator" :size="30" :src="imgurl" />
<!-- 用户名下拉菜单 -->
<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>
<a href="https://lin-xin.gitee.io/example/vuems-doc/" 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('vuems_name');
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('vuems_name');
router.push('/login');
} else if (command == 'user') {
router.push('/ucenter');
}
};
const setFullScreen = () => {
if (document.fullscreenElement) {
document.exitFullscreen();
} else {
document.body.requestFullscreen.call(document.body);
}
};
</script>
<style scoped>
.header {
display: flex;
justify-content: space-between;
align-items: center;
box-sizing: border-box;
width: 100%;
height: 70px;
color: var(--header-text-color);
background-color: var(--header-bg-color);
border-bottom: 1px solid #ddd;
}
.header-left {
display: flex;
align-items: center;
padding-left: 20px;
height: 100%;
}
.logo {
width: 35px;
}
.web-title {
margin: 0 40px 0 10px;
font-size: 22px;
}
.collapse-btn {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
padding: 0 10px;
cursor: pointer;
opacity: 0.8;
font-size: 22px;
}
.collapse-btn:hover {
opacity: 1;
}
.header-right {
float: right;
padding-right: 50px;
}
.header-user-con {
display: flex;
height: 70px;
align-items: center;
}
.btn-fullscreen {
transform: rotate(45deg);
margin-right: 5px;
font-size: 24px;
}
.btn-icon {
position: relative;
width: 30px;
height: 30px;
text-align: center;
cursor: pointer;
display: flex;
align-items: center;
color: var(--header-text-color);
margin: 0 5px;
font-size: 20px;
}
.btn-bell-badge {
position: absolute;
right: 4px;
top: 0px;
width: 8px;
height: 8px;
border-radius: 4px;
background: #f56c6c;
color: var(--header-text-color);
}
.user-avator {
margin: 0 10px 0 20px;
}
.el-dropdown-link {
color: var(--header-text-color);
cursor: pointer;
display: flex;
align-items: center;
}
.el-dropdown-menu__item {
text-align: center;
}
</style>

221
src/components/menu.ts Normal file
View File

@ -0,0 +1,221 @@
import { Menus } from '@/types/menu';
export const menuData: Menus[] = [
{
id: '0',
title: '系统首页',
index: '/dashboard',
icon: 'Odometer',
},
{
id: '1',
title: '系统管理',
index: '1',
icon: 'HomeFilled',
children: [
{
id: '11',
pid: '1',
index: '/system-user',
title: '用户管理',
},
{
id: '12',
pid: '1',
index: '/system-role',
title: '角色管理',
},
{
id: '13',
pid: '1',
index: '/system-menu',
title: '菜单管理',
},
],
},
{
id: '2',
title: '组件',
index: '2-1',
icon: 'Calendar',
children: [
{
id: '21',
pid: '3',
index: '/form',
title: '表单',
},
{
id: '22',
pid: '3',
index: '/upload',
title: '上传',
},
{
id: '23',
pid: '2',
index: '/carousel',
title: '走马灯',
},
{
id: '24',
pid: '2',
index: '/calendar',
title: '日历',
},
{
id: '25',
pid: '2',
index: '/watermark',
title: '水印',
},
{
id: '26',
pid: '2',
index: '/tour',
title: '分布引导',
},
{
id: '27',
pid: '2',
index: '/steps',
title: '步骤条',
},
{
id: '28',
pid: '2',
index: '/statistic',
title: '统计',
},
{
id: '29',
pid: '3',
index: '29',
title: '三级菜单',
children: [
{
id: '291',
pid: '29',
index: '/editor',
title: '富文本编辑器',
},
{
id: '292',
pid: '29',
index: '/markdown',
title: 'markdown编辑器',
},
],
},
],
},
{
id: '3',
title: '表格',
index: '3',
icon: 'Calendar',
children: [
{
id: '31',
pid: '3',
index: '/table',
title: '基础表格',
},
{
id: '32',
pid: '3',
index: '/table-editor',
title: '可编辑表格',
},
{
id: '33',
pid: '3',
index: '/import',
title: '导入Excel',
},
{
id: '34',
pid: '3',
index: '/export',
title: '导出Excel',
},
],
},
{
id: '4',
icon: 'PieChart',
index: '4',
title: '图表',
children: [
{
id: '41',
pid: '4',
index: '/schart',
title: 'schart图表',
},
{
id: '42',
pid: '4',
index: '/echarts',
title: 'echarts图表',
},
],
},
{
id: '5',
icon: 'Guide',
index: '/icon',
title: '图标',
permiss: '5',
},
{
id: '7',
icon: 'Brush',
index: '/theme',
title: '主题',
},
{
id: '6',
icon: 'DocumentAdd',
index: '6',
title: '附加页面',
children: [
{
id: '61',
pid: '6',
index: '/ucenter',
title: '个人中心',
},
{
id: '62',
pid: '6',
index: '/login',
title: '登录',
},
{
id: '63',
pid: '6',
index: '/register',
title: '注册',
},
{
id: '64',
pid: '6',
index: '/reset-pwd',
title: '重设密码',
},
{
id: '65',
pid: '6',
index: '/403',
title: '403',
},
{
id: '66',
pid: '6',
index: '/404',
title: '404',
},
],
},
];

View File

@ -1,56 +0,0 @@
<template>
<div class="error-page">
<div class="error-code">4<span>0</span>3</div>
<div class="error-desc">啊哦~ 你没有权限访问该页面哦</div>
<div class="error-handle">
<router-link to="/">
<el-button type="primary" size="large">返回首页</el-button>
</router-link>
<el-button class="error-btn" type="primary" size="large" @click="goBack"></el-button>
</div>
</div>
</template>
<script>
export default {
methods: {
goBack(){
this.$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: #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>

View File

@ -1,56 +0,0 @@
<template>
<div class="error-page">
<div class="error-code">4<span>0</span>4</div>
<div class="error-desc">啊哦~ 你所访问的页面不存在</div>
<div class="error-handle">
<router-link to="/">
<el-button type="primary" size="large">返回首页</el-button>
</router-link>
<el-button class="error-btn" type="primary" size="large" @click="goBack"></el-button>
</div>
</div>
</template>
<script>
export default {
methods: {
goBack(){
this.$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>

View File

@ -1,109 +0,0 @@
<template>
<div>
<div class="crumbs">
<el-breadcrumb separator="/">
<el-breadcrumb-item><i class="el-icon-pie-chart"></i> schart图表</el-breadcrumb-item>
</el-breadcrumb>
</div>
<div class="container">
<div class="plugins-tips">
vue-schartvue.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" :data="data1" type="bar" :options="options1"></schart>
</div>
<div class="schart-box">
<div class="content-title">折线图</div>
<schart class="schart" canvasId="line" :data="data1" type="line" :options="options2"></schart>
</div>
<div class="schart-box">
<div class="content-title">饼状图</div>
<schart class="schart" canvasId="pie" :data="data2" type="pie" :options="options3"></schart>
</div>
<div class="schart-box">
<div class="content-title">环形图</div>
<schart class="schart" canvasId="ring" :data="data2" type="ring" :options="options4"></schart>
</div>
</div>
</div>
</template>
<script>
import Schart from 'vue-schart';
export default {
name: 'basecharts',
components: {
Schart
},
data: () => ({
data1:[
{name:'2012',value:1141},
{name:'2013',value:1499},
{name:'2014',value:2260},
{name:'2015',value:1170},
{name:'2016',value:970},
{name:'2017',value:1450}
],
data2 : [
{name:'短袖',value:1200},
{name:'休闲裤',value:1222},
{name:'连衣裙',value:1283},
{name:'外套',value:1314},
{name:'羽绒服',value:2314}
],
options1: {
title: '某商店近年营业总额',
autoWidth: true, //
showValue: false,
bgColor: '#F9EFCC',
fillColor: '#00887C',
contentColor: 'rgba(46,199,201,0.3)',
yEqual: 7
},
options2: {
title: '某商店近年营业总额',
bgColor: '#D5E4EB',
titleColor: '#00887C',
fillColor: 'red',
contentColor: 'rgba(46,199,201,0.3)'
},
options3: {
title: '某商店各商品年度销量',
bgColor: '#829dca',
titleColor: '#ffffff',
legendColor: '#ffffff',
radius: 120
},
options4: {
title: '某商店各商品年度销量',
bgColor: '#829daa',
titleColor: '#ffffff',
legendColor: '#ffffff',
radius: 120,
innerRadius:80
}
})
}
</script>
<style scoped>
.schart-box{
display: inline-block;
margin: 20px;
}
.schart{
width: 500px;
height: 400px;
}
.content-title{
clear: both;
font-weight: 400;
line-height: 50px;
margin: 10px 0;
font-size: 22px;
color: #1f2f3d;
}
</style>

View File

@ -1,141 +0,0 @@
<template>
<div>
<div class="crumbs">
<el-breadcrumb separator="/">
<el-breadcrumb-item><i class="el-icon-lx-calendar"></i> 表单</el-breadcrumb-item>
<el-breadcrumb-item>基本表单</el-breadcrumb-item>
</el-breadcrumb>
</div>
<div class="container">
<div class="form-box">
<el-form ref="form" :model="form" label-width="80px">
<el-form-item label="表单名称">
<el-input v-model="form.name"></el-input>
</el-form-item>
<el-form-item label="选择器">
<el-select v-model="form.region" placeholder="请选择">
<el-option key="bbk" label="步步高" value="bbk"></el-option>
<el-option key="xtc" label="小天才" value="xtc"></el-option>
<el-option key="imoo" label="imoo" value="imoo"></el-option>
</el-select>
</el-form-item>
<el-form-item label="日期时间">
<el-col :span="11">
<el-date-picker type="date" placeholder="选择日期" v-model="form.date1" style="width: 100%;"></el-date-picker>
</el-col>
<el-col class="line" :span="2">-</el-col>
<el-col :span="11">
<el-time-picker placeholder="选择时间" v-model="form.date2" style="width: 100%;"></el-time-picker>
</el-col>
</el-form-item>
<el-form-item label="城市级联">
<el-cascader :options="options" v-model="form.options"></el-cascader>
</el-form-item>
<el-form-item label="选择开关">
<el-switch v-model="form.delivery"></el-switch>
</el-form-item>
<el-form-item label="多选框">
<el-checkbox-group v-model="form.type">
<el-checkbox label="步步高" name="type"></el-checkbox>
<el-checkbox label="小天才" name="type"></el-checkbox>
<el-checkbox label="imoo" name="type"></el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item label="单选框">
<el-radio-group v-model="form.resource">
<el-radio label="步步高"></el-radio>
<el-radio label="小天才"></el-radio>
<el-radio label="imoo"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="文本框">
<el-input type="textarea" rows="5" v-model="form.desc"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit"></el-button>
<el-button>取消</el-button>
</el-form-item>
</el-form>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'baseform',
data: function(){
return {
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: '岳麓区'
}
]
}
]
}
],
form: {
name: '',
region: '',
date1: '',
date2: '',
delivery: true,
type: ['步步高'],
resource: '小天才',
desc: '',
options: []
}
}
},
methods: {
onSubmit() {
this.$message.success('提交成功!');
}
}
}
</script>

View File

@ -1,215 +0,0 @@
<template>
<div class="table">
<div class="crumbs">
<el-breadcrumb separator="/">
<el-breadcrumb-item><i class="el-icon-lx-cascades"></i> 基础表格</el-breadcrumb-item>
</el-breadcrumb>
</div>
<div class="container">
<div class="handle-box">
<el-button type="primary" icon="delete" class="handle-del mr10" @click="delAll"></el-button>
<el-select v-model="select_cate" 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="select_word" placeholder="筛选关键词" class="handle-input mr10"></el-input>
<el-button type="primary" icon="search" @click="search"></el-button>
</div>
<el-table :data="data" border class="table" ref="multipleTable" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center"></el-table-column>
<el-table-column prop="date" label="日期" sortable width="150">
</el-table-column>
<el-table-column prop="name" label="姓名" width="120">
</el-table-column>
<el-table-column prop="address" label="地址" :formatter="formatter">
</el-table-column>
<el-table-column label="操作" width="180" align="center">
<template slot-scope="scope">
<el-button type="text" icon="el-icon-edit" @click="handleEdit(scope.$index, scope.row)">编辑</el-button>
<el-button type="text" icon="el-icon-delete" class="red" @click="handleDelete(scope.$index, scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<div class="pagination">
<el-pagination background @current-change="handleCurrentChange" layout="prev, pager, next" :total="1000">
</el-pagination>
</div>
</div>
<!-- 编辑弹出框 -->
<el-dialog title="编辑" :visible.sync="editVisible" width="30%">
<el-form ref="form" :model="form" label-width="50px">
<el-form-item label="日期">
<el-date-picker type="date" placeholder="选择日期" v-model="form.date" value-format="yyyy-MM-dd" style="width: 100%;"></el-date-picker>
</el-form-item>
<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>
<span slot="footer" class="dialog-footer">
<el-button @click="editVisible = false"> </el-button>
<el-button type="primary" @click="saveEdit"> </el-button>
</span>
</el-dialog>
<!-- 删除提示框 -->
<el-dialog title="提示" :visible.sync="delVisible" width="300px" center>
<div class="del-dialog-cnt">删除不可恢复是否确定删除</div>
<span slot="footer" class="dialog-footer">
<el-button @click="delVisible = false"> </el-button>
<el-button type="primary" @click="deleteRow"> </el-button>
</span>
</el-dialog>
</div>
</template>
<script>
export default {
name: 'basetable',
data() {
return {
url: './vuetable.json',
tableData: [],
cur_page: 1,
multipleSelection: [],
select_cate: '',
select_word: '',
del_list: [],
is_search: false,
editVisible: false,
delVisible: false,
form: {
name: '',
date: '',
address: ''
},
idx: -1
}
},
created() {
this.getData();
},
computed: {
data() {
return this.tableData.filter((d) => {
let is_del = false;
for (let i = 0; i < this.del_list.length; i++) {
if (d.name === this.del_list[i].name) {
is_del = true;
break;
}
}
if (!is_del) {
if (d.address.indexOf(this.select_cate) > -1 &&
(d.name.indexOf(this.select_word) > -1 ||
d.address.indexOf(this.select_word) > -1)
) {
return d;
}
}
})
}
},
methods: {
//
handleCurrentChange(val) {
this.cur_page = val;
this.getData();
},
// easy-mock
getData() {
// 使 easy-mock 使 json
if (process.env.NODE_ENV === 'development') {
this.url = '/ms/table/list';
};
this.$axios.post(this.url, {
page: this.cur_page
}).then((res) => {
this.tableData = res.data.list;
})
},
search() {
this.is_search = true;
},
formatter(row, column) {
return row.address;
},
filterTag(value, row) {
return row.tag === value;
},
handleEdit(index, row) {
this.idx = index;
const item = this.tableData[index];
this.form = {
name: item.name,
date: item.date,
address: item.address
}
this.editVisible = true;
},
handleDelete(index, row) {
this.idx = index;
this.delVisible = true;
},
delAll() {
const length = this.multipleSelection.length;
let str = '';
this.del_list = this.del_list.concat(this.multipleSelection);
for (let i = 0; i < length; i++) {
str += this.multipleSelection[i].name + ' ';
}
this.$message.error('删除了' + str);
this.multipleSelection = [];
},
handleSelectionChange(val) {
this.multipleSelection = val;
},
//
saveEdit() {
this.$set(this.tableData, this.idx, this.form);
this.editVisible = false;
this.$message.success(`修改第 ${this.idx+1} 行成功`);
},
//
deleteRow(){
this.tableData.splice(this.idx, 1);
this.$message.success('删除成功');
this.delVisible = false;
}
}
}
</script>
<style scoped>
.handle-box {
margin-bottom: 20px;
}
.handle-select {
width: 120px;
}
.handle-input {
width: 300px;
display: inline-block;
}
.del-dialog-cnt{
font-size: 16px;
text-align: center
}
.table{
width: 100%;
font-size: 14px;
}
.red{
color: #ff0000;
}
.mr10{
margin-right: 10px;
}
</style>

View File

@ -1,343 +0,0 @@
<template>
<div>
<el-row :gutter="20">
<el-col :span="8">
<el-card shadow="hover" class="mgb20" style="height:252px;">
<div class="user-info">
<img src="../../assets/img/img.jpg" class="user-avator" alt="">
<div class="user-info-cont">
<div class="user-info-name">{{name}}</div>
<div>{{role}}</div>
</div>
</div>
<div class="user-info-list">上次登录时间<span>2018-01-01</span></div>
<div class="user-info-list">上次登录地点<span>东莞</span></div>
</el-card>
<el-card shadow="hover" style="height:252px;">
<div slot="header" class="clearfix">
<span>语言详情</span>
</div>
Vue
<el-progress :percentage="71.3" color="#42b983"></el-progress>
JavaScript
<el-progress :percentage="24.1" color="#f1e05a"></el-progress>
CSS
<el-progress :percentage="3.7"></el-progress>
HTML
<el-progress :percentage="0.9" 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">
<i class="el-icon-lx-people grid-con-icon"></i>
<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">
<i class="el-icon-lx-notice grid-con-icon"></i>
<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">
<i class="el-icon-lx-goods grid-con-icon"></i>
<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;">
<div slot="header" class="clearfix">
<span>待办事项</span>
<el-button style="float: right; padding: 3px 0" type="text">添加</el-button>
</div>
<el-table :data="todoList" :show-header="false" height="304" style="width: 100%;font-size:14px;">
<el-table-column width="40">
<template slot-scope="scope">
<el-checkbox v-model="scope.row.status"></el-checkbox>
</template>
</el-table-column>
<el-table-column>
<template slot-scope="scope">
<div class="todo-item" :class="{'todo-item-del': scope.row.status}">{{scope.row.title}}</div>
</template>
</el-table-column>
<el-table-column width="60">
<template slot-scope="scope">
<i class="el-icon-edit"></i>
<i class="el-icon-delete"></i>
</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" :data="data" type="bar" :options="options"></schart>
</el-card>
</el-col>
<el-col :span="12">
<el-card shadow="hover">
<schart ref="line" class="schart" canvasId="line" :data="data" type="line" :options="options2"></schart>
</el-card>
</el-col>
</el-row>
</div>
</template>
<script>
import Schart from 'vue-schart';
import bus from '../common/bus';
export default {
name: 'dashboard',
data() {
return {
name: localStorage.getItem('ms_username'),
todoList: [{
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,
}
],
data: [{
name: '2018/09/04',
value: 1083
},
{
name: '2018/09/05',
value: 941
},
{
name: '2018/09/06',
value: 1139
},
{
name: '2018/09/07',
value: 816
},
{
name: '2018/09/08',
value: 327
},
{
name: '2018/09/09',
value: 228
},
{
name: '2018/09/10',
value: 1065
}
],
options: {
title: '最近七天每天的用户访问量',
showValue: false,
fillColor: 'rgb(45, 140, 240)',
bottomPadding: 30,
topPadding: 30
},
options2: {
title: '最近七天用户访问趋势',
fillColor: '#FC6FA1',
axisColor: '#008ACD',
contentColor: '#EEEEEE',
bgColor: '#F5F8FD',
bottomPadding: 30,
topPadding: 30
}
}
},
components: {
Schart
},
computed: {
role() {
return this.name === 'admin' ? '超级管理员' : '普通用户';
}
},
created(){
this.handleListener();
this.changeDate();
},
activated(){
this.handleListener();
},
deactivated(){
window.removeEventListener('resize', this.renderChart);
bus.$off('collapse', this.handleBus);
},
methods: {
changeDate(){
const now = new Date().getTime();
this.data.forEach((item, index) => {
const date = new Date(now - (6 - index) * 86400000);
item.name = `${date.getFullYear()}/${date.getMonth()+1}/${date.getDate()}`
})
},
handleListener(){
bus.$on('collapse', this.handleBus);
// renderChart
window.addEventListener('resize', this.renderChart)
},
handleBus(msg){
setTimeout(() => {
this.renderChart()
}, 300);
},
renderChart(){
this.$refs.bar.renderChart();
this.$refs.line.renderChart();
}
}
}
</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(45, 140, 240);
}
.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-avator {
width: 120px;
height: 120px;
border-radius: 50%;
}
.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>

View File

@ -1,36 +0,0 @@
<template>
<section class="main">
<div class="crumbs">
<el-breadcrumb separator="/">
<el-breadcrumb-item><i class="el-icon-rank"></i> 拖拽组件</el-breadcrumb-item>
<el-breadcrumb-item>拖拽弹框</el-breadcrumb-item>
</el-breadcrumb>
</div>
<div class="container">
<p>通过指令 v-dialogDrag 使 Dialog 对话框具有可拖拽的功能</p>
<br>
<el-button type="primary" @click="visible = true;">点我弹框</el-button>
</div>
<el-dialog v-dialogDrag title="拖拽弹框" center :visible.sync="visible" width="30%">
我是一个可以拖拽的对话框
<span slot="footer" class="dialog-footer">
<el-button @click="visible = false"> </el-button>
<el-button type="primary" @click="visible = false"> </el-button>
</span>
</el-dialog>
</section>
</template>
<script>
export default {
data(){
return {
visible: false
}
}
}
</script>
<style>
</style>

View File

@ -1,174 +0,0 @@
<template>
<section class="main">
<div class="crumbs">
<el-breadcrumb separator="/">
<el-breadcrumb-item><i class="el-icon-rank"></i> 拖拽组件</el-breadcrumb-item>
<el-breadcrumb-item>拖拽排序</el-breadcrumb-item>
</el-breadcrumb>
</div>
<div class="container">
<div class="plugins-tips">
Vue.Draggable基于 Sortable.js Vue 拖拽组件
访问地址<a href="https://github.com/SortableJS/Vue.Draggable" target="_blank">Vue.Draggable</a>
</div>
<div class="drag-box">
<div class="drag-box-item">
<div class="item-title">todo</div>
<draggable v-model="todo" @remove="removeHandle" :options="dragOptions">
<transition-group tag="div" id="todo" class="item-ul">
<div v-for="item in todo" class="drag-list" :key="item.id">
{{item.content}}
</div>
</transition-group>
</draggable>
</div>
<div class="drag-box-item">
<div class="item-title">doing</div>
<draggable v-model="doing" @remove="removeHandle" :options="dragOptions">
<transition-group tag="div" id="doing" class="item-ul">
<div v-for="item in doing" class="drag-list" :key="item.id">
{{item.content}}
</div>
</transition-group>
</draggable>
</div>
<div class="drag-box-item">
<div class="item-title">done</div>
<draggable v-model="done" @remove="removeHandle" :options="dragOptions">
<transition-group tag="div" id="done" class="item-ul">
<div v-for="item in done" class="drag-list" :key="item.id">
{{item.content}}
</div>
</transition-group>
</draggable>
</div>
</div>
</div>
</section>
</template>
<script>
import draggable from 'vuedraggable'
export default {
name: 'draglist',
data() {
return {
dragOptions:{
animation: 120,
scroll: true,
group: 'sortlist',
ghostClass: 'ghost-style'
},
todo: [
{
id: 1,
content: '开发图表组件'
},
{
id: 2,
content: '开发拖拽组件'
},
{
id: 3,
content: '开发权限测试组件'
}
],
doing: [
{
id: 1,
content: '开发登录注册页面'
},
{
id: 2,
content: '开发头部组件'
},
{
id: 3,
content: '开发表格相关组件'
},
{
id: 4,
content: '开发表单相关组件'
}
],
done:[
{
id: 1,
content: '初始化项目,生成工程目录,完成相关配置'
},
{
id: 2,
content: '开发项目整体框架'
}
]
}
},
components:{
draggable
},
methods: {
removeHandle(event){
console.log(event);
this.$message.success(`${event.from.id} 移动到 ${event.to.id} `);
}
}
}
</script>
<style scoped>
.drag-box{
display: flex;
user-select: none;
}
.drag-box-item {
flex: 1;
max-width: 330px;
min-width: 300px;
background-color: #eff1f5;
margin-right: 16px;
border-radius: 6px;
border: 1px #e1e4e8 solid;
}
.item-title{
padding: 8px 8px 8px 12px;
font-size: 14px;
line-height: 1.5;
color: #24292e;
font-weight: 600;
}
.item-ul{
padding: 0 8px 8px;
height: 500px;
overflow-y: scroll;
}
.item-ul::-webkit-scrollbar{
width: 0;
}
.drag-list {
border: 1px #e1e4e8 solid;
padding: 10px;
margin: 5px 0 10px;
list-style: none;
background-color: #fff;
border-radius: 6px;
cursor: pointer;
-webkit-transition: border .3s ease-in;
transition: border .3s ease-in;
}
.drag-list:hover {
border: 1px solid #20a0ff;
}
.drag-title {
font-weight: 400;
line-height: 25px;
margin: 10px 0;
font-size: 22px;
color: #1f2f3d;
}
.ghost-style{
display: block;
color: transparent;
border-style: dashed
}
</style>

View File

@ -1,46 +0,0 @@
<template>
<section class="main">
<div class="crumbs">
<el-breadcrumb separator="/">
<el-breadcrumb-item><i class="el-icon-lx-global"></i> {{$t('i18n.breadcrumb')}}</el-breadcrumb-item>
</el-breadcrumb>
</div>
<div class="container">
<span>{{$t('i18n.tips')}}</span>
<el-button type="primary" @click="$i18n.locale = $i18n.locale === 'zh'?'en':'zh';">{{$t('i18n.btn')}}</el-button>
<div class="list">
<h2>{{$t('i18n.title1')}}</h2>
<p>{{$t('i18n.p1')}}</p>
<p>{{$t('i18n.p2')}}</p>
<p>{{$t('i18n.p3')}}</p>
</div>
<h2>{{$t('i18n.title2')}}</h2>
<div>
<i18n path="i18n.info" tag="p">
<a place="action" href="https://element.eleme.cn/2.0/#/zh-CN/component/i18n">{{ $t('i18n.value') }}</a>
</i18n>
</div>
</div>
</section>
</template>
<script>
export default {
data(){
return {
}
}
}
</script>
<style scoped>
.list{
padding: 30px 0;
}
.list p{
margin-bottom: 20px;
}
a{
color: #409eff;
}
</style>

View File

@ -1,225 +0,0 @@
<template>
<div>
<div class="crumbs">
<el-breadcrumb separator="/">
<el-breadcrumb-item><i class="el-icon-lx-emoji"></i> 自定义图标</el-breadcrumb-item>
</el-breadcrumb>
</div>
<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>&lt;i class=&quot;el-icon-lx-redpacket_fill&quot;&gt;&lt;/i&gt;</span>
</p>
<p class="example-p">
<i class="el-icon-lx-weibo" style="font-size: 30px;color:#fd5656"></i>
<span>&lt;i class=&quot;el-icon-lx-weibo&quot;&gt;&lt;/i&gt;</span>
</p>
<p class="example-p">
<i class="el-icon-lx-emojifill" style="font-size: 30px;color: #ffc300"></i>
<span>&lt;i class=&quot;el-icon-lx-emojifill&quot;&gt;&lt;/i&gt;</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>
</div>
</template>
<script>
export default {
data: function(){
return {
keyword: '',
iconList: [
'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'
]
}
},
computed: {
list(){
return this.iconList.filter((item) => {
return item.indexOf(this.keyword) !== -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>

View File

@ -1,101 +0,0 @@
<template>
<div class="login-wrap">
<div class="ms-login">
<div class="ms-title">后台管理系统</div>
<el-form :model="ruleForm" :rules="rules" ref="ruleForm" label-width="0px" class="ms-content">
<el-form-item prop="username">
<el-input v-model="ruleForm.username" placeholder="username">
<el-button slot="prepend" icon="el-icon-lx-people"></el-button>
</el-input>
</el-form-item>
<el-form-item prop="password">
<el-input type="password" placeholder="password" v-model="ruleForm.password" @keyup.enter.native="submitForm('ruleForm')">
<el-button slot="prepend" icon="el-icon-lx-lock"></el-button>
</el-input>
</el-form-item>
<div class="login-btn">
<el-button type="primary" @click="submitForm('ruleForm')"></el-button>
</div>
<p class="login-tips">Tips : 用户名和密码随便填</p>
</el-form>
</div>
</div>
</template>
<script>
export default {
data: function(){
return {
ruleForm: {
username: 'admin',
password: '123123'
},
rules: {
username: [
{ required: true, message: '请输入用户名', trigger: 'blur' }
],
password: [
{ required: true, message: '请输入密码', trigger: 'blur' }
]
}
}
},
methods: {
submitForm(formName) {
this.$refs[formName].validate((valid) => {
if (valid) {
localStorage.setItem('ms_username',this.ruleForm.username);
this.$router.push('/');
} else {
console.log('error submit!!');
return false;
}
});
}
}
}
</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>

View File

@ -1,67 +0,0 @@
<template>
<div>
<div class="crumbs">
<el-breadcrumb separator="/">
<el-breadcrumb-item><i class="el-icon-lx-calendar"></i> 表单</el-breadcrumb-item>
<el-breadcrumb-item>markdown编辑器</el-breadcrumb-item>
</el-breadcrumb>
</div>
<div class="container">
<div class="plugins-tips">
mavonEditor基于Vue的markdown编辑器
访问地址<a href="https://github.com/hinesboy/mavonEditor" target="_blank">mavonEditor</a>
</div>
<mavon-editor v-model="content" ref="md" @imgAdd="$imgAdd" @change="change" style="min-height: 600px"/>
<el-button class="editor-btn" type="primary" @click="submit"></el-button>
</div>
</div>
</template>
<script>
import { mavonEditor } from 'mavon-editor'
import 'mavon-editor/dist/css/index.css'
export default {
name: 'markdown',
data: function(){
return {
content:'',
html:'',
configs: {
}
}
},
components: {
mavonEditor
},
methods: {
// md
$imgAdd(pos, $file){
var formdata = new FormData();
formdata.append('file', $file);
//
this.$axios({
url: '/common/upload',
method: 'post',
data: formdata,
headers: { 'Content-Type': 'multipart/form-data' },
}).then((url) => {
this.$refs.md.$img2Url(pos, url);
})
},
change(value, render){
// render markdown
this.html = render;
},
submit(){
console.log(this.content);
console.log(this.html);
this.$message.success('提交成功!');
}
}
}
</script>
<style scoped>
.editor-btn{
margin-top: 20px;
}
</style>

View File

@ -1,38 +0,0 @@
<template>
<div>
<div class="crumbs">
<el-breadcrumb separator="/">
<el-breadcrumb-item><i class="el-icon-lx-warn"></i> 权限测试</el-breadcrumb-item>
</el-breadcrumb>
</div>
<div class="container">
<h1>管理员权限页面</h1>
<p>只有用 admin 账号登录的才拥有管理员权限才能进到这个页面其他账号想进来都会跳到403页面重新用管理员账号登录才有权限</p>
<p>想尝试一下<router-link to="/login" class="logout">退出登录</router-link>便</p>
</div>
</div>
</template>
<script>
export default {
data: function(){
return {}
}
}
</script>
<style scoped>
h1{
text-align: center;
margin: 30px 0;
}
p{
line-height: 30px;
margin-bottom: 10px;
text-indent: 2em;
}
.logout{
color: #409EFF;
}
</style>

View File

@ -1,129 +0,0 @@
<template>
<div class="">
<div class="crumbs">
<el-breadcrumb separator="/">
<el-breadcrumb-item><i class="el-icon-lx-copy"></i> tab选项卡</el-breadcrumb-item>
</el-breadcrumb>
</div>
<div class="container">
<el-tabs v-model="message">
<el-tab-pane :label="`未读消息(${unread.length})`" name="first">
<el-table :data="unread" :show-header="false" style="width: 100%">
<el-table-column>
<template slot-scope="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 slot-scope="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="`已读消息(${read.length})`" name="second">
<template v-if="message === 'second'">
<el-table :data="read" :show-header="false" style="width: 100%">
<el-table-column>
<template slot-scope="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 slot-scope="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="`回收站(${recycle.length})`" name="third">
<template v-if="message === 'third'">
<el-table :data="recycle" :show-header="false" style="width: 100%">
<el-table-column>
<template slot-scope="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 slot-scope="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>
</div>
</template>
<script>
export default {
name: 'tabs',
data() {
return {
message: 'first',
showHeader: false,
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点进行升级维护'
}]
}
},
methods: {
handleRead(index) {
const item = this.unread.splice(index, 1);
console.log(item);
this.read = item.concat(this.read);
},
handleDel(index) {
const item = this.read.splice(index, 1);
this.recycle = item.concat(this.recycle);
},
handleRestore(index) {
const item = this.recycle.splice(index, 1);
this.read = item.concat(this.read);
}
},
computed: {
unreadNum(){
return this.unread.length;
}
}
}
</script>
<style>
.message-title{
cursor: pointer;
}
.handle-row{
margin-top: 30px;
}
</style>

View File

@ -1,141 +0,0 @@
<template>
<div>
<div class="crumbs">
<el-breadcrumb separator="/">
<el-breadcrumb-item><i class="el-icon-lx-calendar"></i> 表单</el-breadcrumb-item>
<el-breadcrumb-item>图片上传</el-breadcrumb-item>
</el-breadcrumb>
</div>
<div class="container">
<div class="content-title">支持拖拽</div>
<div class="plugins-tips">
Element UI自带上传组件
访问地址<a href="http://element.eleme.io/#/zh-CN/component/upload" target="_blank">Element UI Upload</a>
</div>
<el-upload
class="upload-demo"
drag
action="/api/posts/"
multiple>
<i class="el-icon-upload"></i>
<div class="el-upload__text">将文件拖到此处<em>点击上传</em></div>
<div class="el-upload__tip" slot="tip">只能上传jpg/png文件且不超过500kb</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>
</div>
<div class="crop-demo">
<img :src="cropImg" class="pre-img">
<div class="crop-demo-btn">选择图片
<input class="crop-input" type="file" name="image" accept="image/*" @change="setImage"/>
</div>
</div>
<el-dialog title="裁剪图片" :visible.sync="dialogVisible" width="30%">
<vue-cropper ref='cropper' :src="imgSrc" :ready="cropImage" :zoom="cropImage" :cropmove="cropImage" style="width:100%;height:300px;"></vue-cropper>
<span slot="footer" class="dialog-footer">
<el-button @click="cancelCrop"> </el-button>
<el-button type="primary" @click="dialogVisible = false"> </el-button>
</span>
</el-dialog>
</div>
</div>
</template>
<script>
import VueCropper from 'vue-cropperjs';
export default {
name: 'upload',
data: function(){
return {
defaultSrc: require('../../assets/img/img.jpg'),
fileList: [],
imgSrc: '',
cropImg: '',
dialogVisible: false,
}
},
components: {
VueCropper
},
methods:{
setImage(e){
const file = e.target.files[0];
if (!file.type.includes('image/')) {
return;
}
const reader = new FileReader();
reader.onload = (event) => {
this.dialogVisible = true;
this.imgSrc = event.target.result;
this.$refs.cropper && this.$refs.cropper.replace(event.target.result);
};
reader.readAsDataURL(file);
},
cropImage () {
this.cropImg = this.$refs.cropper.getCroppedCanvas().toDataURL();
},
cancelCrop(){
this.dialogVisible = false;
this.cropImg = this.defaultSrc;
},
imageuploaded(res) {
console.log(res)
},
handleError(){
this.$notify.error({
title: '上传失败',
message: '图片上传接口上传失败,可更改为自己的服务器接口'
});
}
},
created(){
this.cropImg = this.defaultSrc;
}
}
</script>
<style scoped>
.content-title{
font-weight: 400;
line-height: 50px;
margin: 10px 0;
font-size: 22px;
color: #1f2f3d;
}
.pre-img{
width: 100px;
height: 100px;
background: #f8f8f8;
border: 1px solid #eee;
border-radius: 5px;
}
.crop-demo{
display: flex;
align-items: flex-end;
}
.crop-demo-btn{
position: relative;
width: 100px;
height: 40px;
line-height: 40px;
padding: 0 20px;
margin-left: 30px;
background-color: #409eff;
color: #fff;
font-size: 14px;
border-radius: 4px;
box-sizing: border-box;
}
.crop-input{
position: absolute;
width: 100px;
height: 40px;
left: 0;
top: 0;
opacity: 0;
cursor: pointer;
}
</style>

View File

@ -1,53 +0,0 @@
<template>
<div>
<div class="crumbs">
<el-breadcrumb separator="/">
<el-breadcrumb-item><i class="el-icon-lx-calendar"></i> 表单</el-breadcrumb-item>
<el-breadcrumb-item>编辑器</el-breadcrumb-item>
</el-breadcrumb>
</div>
<div class="container">
<div class="plugins-tips">
Vue-Quill-Editor基于Quill适用于Vue2的富文本编辑器
访问地址<a href="https://github.com/surmon-china/vue-quill-editor" target="_blank">vue-quill-editor</a>
</div>
<quill-editor ref="myTextEditor" v-model="content" :options="editorOption"></quill-editor>
<el-button class="editor-btn" type="primary" @click="submit"></el-button>
</div>
</div>
</template>
<script>
import 'quill/dist/quill.core.css';
import 'quill/dist/quill.snow.css';
import 'quill/dist/quill.bubble.css';
import { quillEditor } from 'vue-quill-editor';
export default {
name: 'editor',
data: function(){
return {
content: '',
editorOption: {
placeholder: 'Hello World'
}
}
},
components: {
quillEditor
},
methods: {
onEditorChange({ editor, html, text }) {
this.content = html;
},
submit(){
console.log(this.content);
this.$message.success('提交成功!');
}
}
}
</script>
<style scoped>
.editor-btn{
margin-top: 20px;
}
</style>

View File

@ -0,0 +1,90 @@
<template>
<div class="sidebar">
<el-menu
class="sidebar-el-menu"
:default-active="onRoutes"
:collapse="sidebar.collapse"
:background-color="sidebar.bgColor"
:text-color="sidebar.textColor"
router
>
<template v-for="item in menuData">
<template v-if="item.children">
<el-sub-menu :index="item.index" :key="item.index" v-permiss="item.id">
<template #title>
<el-icon>
<component :is="item.icon"></component>
</el-icon>
<span>{{ item.title }}</span>
</template>
<template v-for="subItem in item.children">
<el-sub-menu
v-if="subItem.children"
:index="subItem.index"
:key="subItem.index"
v-permiss="item.id"
>
<template #title>{{ subItem.title }}</template>
<el-menu-item
v-for="(threeItem, i) in subItem.children"
:key="i"
:index="threeItem.index"
>
{{ threeItem.title }}
</el-menu-item>
</el-sub-menu>
<el-menu-item v-else :index="subItem.index" v-permiss="item.id">
{{ subItem.title }}
</el-menu-item>
</template>
</el-sub-menu>
</template>
<template v-else>
<el-menu-item :index="item.index" :key="item.index" v-permiss="item.id">
<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';
import { menuData } from '@/components/menu';
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-el-menu {
min-height: 100%;
}
</style>

View File

@ -0,0 +1,211 @@
<template>
<div>
<div class="table-toolbar" v-if="hasToolbar">
<div class="table-toolbar-left">
<slot name="toolbarBtn"></slot>
</div>
<div class="table-toolbar-right flex-center">
<template v-if="multipleSelection.length > 0">
<el-tooltip effect="dark" content="删除选中" placement="top">
<el-icon class="columns-setting-icon" @click="delSelection(multipleSelection)">
<Delete />
</el-icon>
</el-tooltip>
<el-divider direction="vertical" />
</template>
<el-tooltip effect="dark" content="刷新" placement="top">
<el-icon class="columns-setting-icon" @click="refresh">
<Refresh />
</el-icon>
</el-tooltip>
<el-divider direction="vertical" />
<el-tooltip effect="dark" content="列设置" placement="top">
<el-dropdown :hide-on-click="false" size="small" trigger="click">
<el-icon class="columns-setting-icon">
<Setting />
</el-icon>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item v-for="c in columns">
<el-checkbox v-model="c.visible" :label="c.label" />
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</el-tooltip>
</div>
</div>
<el-table class="mgb20" :style="{ width: '100%' }" border :data="tableData" :row-key="rowKey"
@selection-change="handleSelectionChange" table-layout="auto">
<template v-for="item in columns" :key="item.prop">
<el-table-column v-if="item.visible" :prop="item.prop" :label="item.label" :width="item.width"
:type="item.type" :align="item.align || 'center'">
<template #default="{ row, column, $index }" v-if="item.type === 'index'">
{{ getIndex($index) }}
</template>
<template #default="{ row, column, $index }" v-if="!item.type">
<slot :name="item.prop" :rows="row" :index="$index">
<template v-if="item.prop == 'operator'">
<el-button type="warning" size="small" :icon="View" @click="viewFunc(row)">
查看
</el-button>
<el-button type="primary" size="small" :icon="Edit" @click="editFunc(row)">
编辑
</el-button>
<el-button type="danger" size="small" :icon="Delete" @click="handleDelete(row)">
删除
</el-button>
</template>
<span v-else-if="item.formatter">
{{ item.formatter(row[item.prop]) }}
</span>
<span v-else>
{{ row[item.prop] }}
</span>
</slot>
</template>
</el-table-column>
</template>
</el-table>
<el-pagination v-if="hasPagination" :current-page="currentPage" :page-size="pageSize" :background="true"
:layout="layout" :total="total" @current-change="handleCurrentChange" />
</div>
</template>
<script setup lang="ts">
import { toRefs, PropType, ref } from 'vue'
import { Delete, Edit, View, Refresh } from '@element-plus/icons-vue';
import { ElMessageBox } from 'element-plus';
const props = defineProps({
//
tableData: {
type: Array,
default: []
},
columns: {
type: Array as PropType<any[]>,
default: []
},
rowKey: {
type: String,
default: 'id'
},
hasToolbar: {
type: Boolean,
default: true
},
//
hasPagination: {
type: Boolean,
default: true
},
total: {
type: Number,
default: 0
},
currentPage: {
type: Number,
default: 1
},
pageSize: {
type: Number,
default: 10
},
layout: {
type: String,
default: 'total, prev, pager, next'
},
delFunc: {
type: Function,
default: () => { }
},
viewFunc: {
type: Function,
default: () => { }
},
editFunc: {
type: Function,
default: () => { }
},
delSelection: {
type: Function,
default: () => { }
},
refresh: {
type: Function,
default: () => { }
},
changePage: {
type: Function,
default: () => { }
}
})
let {
tableData,
columns,
rowKey,
hasToolbar,
hasPagination,
total,
currentPage,
pageSize,
layout,
} = toRefs(props)
columns.value.forEach((item) => {
if (item.visible === undefined) {
item.visible = true
}
})
//
const multipleSelection = ref([])
const handleSelectionChange = (selection: any[]) => {
multipleSelection.value = selection
}
//
const handleCurrentChange = (val: number) => {
props.changePage(val)
}
const handleDelete = (row) => {
ElMessageBox.confirm('确定要删除吗?', '提示', {
type: 'warning'
})
.then(async () => {
props.delFunc(row);
})
.catch(() => { });
};
const getIndex = (index: number) => {
return index + 1 + (currentPage.value - 1) * pageSize.value
}
</script>
<style scoped>
.table-toolbar {
display: flex;
justify-content: space-between;
align-items: flex-end;
margin-bottom: 10px;
}
.columns-setting-icon {
display: block;
font-size: 18px;
cursor: pointer;
color: #676767;
}
</style>
<style>
.table-header .cell {
color: #333;
}
</style>

View File

@ -0,0 +1,21 @@
<template>
<el-descriptions :title="title" :column="column" border>
<el-descriptions-item v-for="item in list" :span="item.span">
<template #label> {{ item.label }} </template>
<slot :name="item.prop" :rows="row">
{{ item.value || row[item.prop] }}
</slot>
</el-descriptions-item>
</el-descriptions>
</template>
<script lang="ts" setup>
const props = defineProps({
data: {
type: Object,
required: true,
}
});
const { row, title, column = 2, list } = props.data;
</script>

View File

@ -0,0 +1,111 @@
<template>
<el-form ref="formRef" :model="form" :rules="rules" :label-width="options.labelWidth">
<el-row>
<el-col :span="options.span" v-for="item in options.list">
<el-form-item :label="item.label" :prop="item.prop">
<!-- 文本框数字框下拉框日期框开关上传 -->
<el-input v-if="item.type === 'input'" v-model="form[item.prop]" :disabled="item.disabled"
:placeholder="item.placeholder" clearable></el-input>
<el-input-number v-else-if="item.type === 'number'" v-model="form[item.prop]"
:disabled="item.disabled" controls-position="right"></el-input-number>
<el-select v-else-if="item.type === 'select'" v-model="form[item.prop]" :disabled="item.disabled"
:placeholder="item.placeholder" clearable>
<el-option v-for="opt in item.opts" :label="opt.label" :value="opt.value"></el-option>
</el-select>
<el-date-picker v-else-if="item.type === 'date'" type="date" v-model="form[item.prop]"
:value-format="item.format"></el-date-picker>
<el-switch v-else-if="item.type === 'switch'" v-model="form[item.prop]"
:active-value="item.activeValue" :inactive-value="item.inactiveValue"
:active-text="item.activeText" :inactive-text="item.inactiveText"></el-switch>
<el-upload v-else-if="item.type === 'upload'" class="avatar-uploader" action="#"
:show-file-list="false" :on-success="handleAvatarSuccess">
<img v-if="form[item.prop]" :src="form[item.prop]" class="avatar" />
<el-icon v-else class="avatar-uploader-icon">
<Plus />
</el-icon>
</el-upload>
<slot :name="item.prop" v-else>
</slot>
</el-form-item>
</el-col>
</el-row>
<el-form-item>
<el-button type="primary" @click="saveEdit(formRef)"> </el-button>
</el-form-item>
</el-form>
</template>
<script lang="ts" setup>
import { FormOption } from '@/types/form-option';
import { FormInstance, FormRules, UploadProps } from 'element-plus';
import { PropType, ref } from 'vue';
const { options, formData, edit, update } = defineProps({
options: {
type: Object as PropType<FormOption>,
required: true
},
formData: {
type: Object,
required: true
},
edit: {
type: Boolean,
required: false
},
update: {
type: Function,
required: true
}
});
const form = ref({ ...(edit ? formData : {}) });
const rules: FormRules = options.list.map(item => {
if (item.required) {
return { [item.prop]: [{ required: true, message: `${item.label}不能为空`, trigger: 'blur' }] };
}
return {};
}).reduce((acc, cur) => ({ ...acc, ...cur }), {});
const formRef = ref<FormInstance>();
const saveEdit = (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.validate(valid => {
if (!valid) return false;
update(form.value);
});
};
const handleAvatarSuccess: UploadProps['onSuccess'] = (response, uploadFile) => {
form.value.thumb = URL.createObjectURL(uploadFile.raw!);
};
</script>
<style>
.avatar-uploader .el-upload {
border: 1px dashed var(--el-border-color);
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
transition: var(--el-transition-duration-fast);
}
.avatar-uploader .el-upload:hover {
border-color: var(--el-color-primary);
}
.el-icon.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 178px;
height: 178px;
text-align: center;
}
</style>

View File

@ -0,0 +1,60 @@
<template>
<div class="search-container">
<el-form ref="searchRef" :model="query" :inline="true">
<el-form-item :label="item.label" :prop="item.prop" v-for="item in options">
<!-- 文本框下拉框日期框 -->
<el-input v-if="item.type === 'input'" v-model="query[item.prop]" :disabled="item.disabled"
:placeholder="item.placeholder" clearable></el-input>
<el-select v-else-if="item.type === 'select'" v-model="query[item.prop]" :disabled="item.disabled"
:placeholder="item.placeholder" clearable>
<el-option v-for="opt in item.opts" :label="opt.label" :value="opt.value"></el-option>
</el-select>
<el-date-picker v-else-if="item.type === 'date'" type="date" v-model="query[item.prop]"
:value-format="item.format"></el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" :icon="Search" @click="search"></el-button>
<el-button :icon="Refresh" @click="resetForm(searchRef)"></el-button>
</el-form-item>
</el-form>
</div>
</template>
<script lang="ts" setup>
import { FormInstance } from 'element-plus';
import { Search, Refresh } from '@element-plus/icons-vue';
import { PropType, ref } from 'vue';
import { FormOptionList } from '@/types/form-option';
const props = defineProps({
query: {
type: Object,
required: true
},
options: {
type: Array as PropType<Array<FormOptionList>>,
required: true
},
search: {
type: Function,
default: () => { }
}
});
const searchRef = ref<FormInstance>();
const resetForm = (formEl: FormInstance | undefined) => {
if (!formEl) return
formEl.resetFields()
props.search();
}
</script>
<style scoped>
.search-container {
padding: 20px 30px 0;
background-color: #fff;
margin-bottom: 10px;
border: 1px solid #ddd;
border-radius: 5px
}
</style>

148
src/components/tabs.vue Normal file
View File

@ -0,0 +1,148 @@
<template>
<div class="tabs-container">
<el-tabs v-model="activePath" class="tabs" type="card" closable @tab-click="clickTabls" @tab-remove="closeTabs">
<el-tab-pane
v-for="item in tabs.list"
:key="item.path"
:label="item.title"
:name="item.path"
@click="setTags(item)"
></el-tab-pane>
</el-tabs>
<div class="Tabs-close-box">
<el-dropdown @command="handleTags">
<el-button size="small" type="primary" plain>
标签选项
<el-icon class="el-icon--right">
<arrow-down />
</el-icon>
</el-button>
<template #dropdown>
<el-dropdown-menu size="small">
<el-dropdown-item command="other">关闭其他</el-dropdown-item>
<el-dropdown-item command="current">关闭当前</el-dropdown-item>
<el-dropdown-item command="all">关闭所有</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue';
import { useTabsStore } from '../store/tabs';
import { onBeforeRouteUpdate, useRoute, useRouter } from 'vue-router';
const route = useRoute();
const router = useRouter();
const activePath = ref(route.fullPath);
const tabs = useTabsStore();
//
const setTags = (route: any) => {
const isExist = tabs.list.some((item) => {
return item.path === route.fullPath;
});
if (!isExist) {
tabs.setTabsItem({
name: route.name,
title: route.meta.title,
path: route.fullPath,
});
}
};
setTags(route);
onBeforeRouteUpdate((to) => {
setTags(to);
});
//
const closeAll = () => {
tabs.clearTabs();
router.push('/');
};
//
const closeOther = () => {
const curItem = tabs.list.filter((item) => {
return item.path === route.fullPath;
});
tabs.closeTabsOther(curItem);
};
const handleTags = (command: string) => {
switch (command) {
case 'current':
//
tabs.closeCurrentTag({
$router: router,
$route: route,
});
break;
case 'all':
closeAll();
break;
case 'other':
closeOther();
break;
}
};
const clickTabls = (item: any) => {
router.push(item.props.name);
};
const closeTabs = (path: string) => {
const index = tabs.list.findIndex((item) => item.path === path);
tabs.delTabsItem(index);
const item = tabs.list[index] || tabs.list[index - 1];
router.push(item ? item.path : '/');
};
watch(
() => route.fullPath,
(newVal, oldVal) => {
activePath.value = newVal;
}
);
</script>
<style scss>
.tabs-container {
position: relative;
overflow: hidden;
background: #fff;
padding: 2px 120px 0 0;
}
.tabs {
.el-tabs__header {
margin-bottom: 0;
}
.el-tabs__nav {
height: 28px;
}
.el-tabs__nav-next,
.el-tabs__nav-prev {
line-height: 32px;
}
&.el-tabs {
--el-tabs-header-height: 28px;
}
}
.Tabs-close-box {
position: absolute;
right: 0;
top: 0;
box-sizing: border-box;
padding-top: 1px;
text-align: center;
width: 110px;
height: 30px;
background: #fff;
box-shadow: -3px 0 15px 3px rgba(0, 0, 0, 0.1);
z-index: 10;
}
</style>

View File

@ -1,51 +0,0 @@
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import axios from 'axios';
import ElementUI from 'element-ui';
import VueI18n from 'vue-i18n';
import { messages } from './components/common/i18n';
import 'element-ui/lib/theme-chalk/index.css'; // 默认主题
// import '../static/css/theme-green/index.css'; // 浅绿色主题
import './assets/css/icon.css';
import './components/common/directives';
import "babel-polyfill";
Vue.config.productionTip = false
Vue.use(VueI18n);
Vue.use(ElementUI, {
size: 'small'
});
Vue.prototype.$axios = axios;
const i18n = new VueI18n({
locale: 'zh',
messages
})
//使用钩子函数对路由进行权限跳转
router.beforeEach((to, from, next) => {
const role = localStorage.getItem('ms_username');
if (!role && to.path !== '/login') {
next('/login');
} else if (to.meta.permission) {
// 如果是管理员权限则可进入,这里只是简单的模拟管理员权限而已
role === 'admin' ? next() : next('/403');
} else {
// 简单的判断IE10及以下不进入富文本编辑器该组件不兼容
if (navigator.userAgent.indexOf('MSIE') > -1 && to.path === '/editor') {
Vue.prototype.$alert('vue-quill-editor组件不兼容IE10及以下浏览器请使用更高版本的浏览器查看', '浏览器不兼容通知', {
confirmButtonText: '确定'
});
} else {
next();
}
}
})
new Vue({
router,
i18n,
render: h => h(App)
}).$mount('#app')

28
src/main.ts Normal file
View 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 (binding.value && !permiss.key.includes(String(binding.value))) {
el['hidden'] = true;
}
},
});
app.mount('#app');

View File

@ -1,111 +0,0 @@
import Vue from 'vue';
import Router from 'vue-router';
Vue.use(Router);
export default new Router({
routes: [
{
path: '/',
redirect: '/dashboard'
},
{
path: '/',
component: resolve => require(['../components/common/Home.vue'], resolve),
meta: { title: '自述文件' },
children:[
{
path: '/dashboard',
component: resolve => require(['../components/page/Dashboard.vue'], resolve),
meta: { title: '系统首页' }
},
{
path: '/icon',
component: resolve => require(['../components/page/Icon.vue'], resolve),
meta: { title: '自定义图标' }
},
{
path: '/table',
component: resolve => require(['../components/page/BaseTable.vue'], resolve),
meta: { title: '基础表格' }
},
{
path: '/tabs',
component: resolve => require(['../components/page/Tabs.vue'], resolve),
meta: { title: 'tab选项卡' }
},
{
path: '/form',
component: resolve => require(['../components/page/BaseForm.vue'], resolve),
meta: { title: '基本表单' }
},
{
// 富文本编辑器组件
path: '/editor',
component: resolve => require(['../components/page/VueEditor.vue'], resolve),
meta: { title: '富文本编辑器' }
},
{
// markdown组件
path: '/markdown',
component: resolve => require(['../components/page/Markdown.vue'], resolve),
meta: { title: 'markdown编辑器' }
},
{
// 图片上传组件
path: '/upload',
component: resolve => require(['../components/page/Upload.vue'], resolve),
meta: { title: '文件上传' }
},
{
// vue-schart组件
path: '/charts',
component: resolve => require(['../components/page/BaseCharts.vue'], resolve),
meta: { title: 'schart图表' }
},
{
// 拖拽列表组件
path: '/drag',
component: resolve => require(['../components/page/DragList.vue'], resolve),
meta: { title: '拖拽列表' }
},
{
// 拖拽Dialog组件
path: '/dialog',
component: resolve => require(['../components/page/DragDialog.vue'], resolve),
meta: { title: '拖拽弹框' }
},
{
// 国际化组件
path: '/i18n',
component: resolve => require(['../components/page/I18n.vue'], resolve),
meta: { title: '国际化' }
},
{
// 权限页面
path: '/permission',
component: resolve => require(['../components/page/Permission.vue'], resolve),
meta: { title: '权限测试', permission: true }
},
{
path: '/404',
component: resolve => require(['../components/page/404.vue'], resolve),
meta: { title: '404' }
},
{
path: '/403',
component: resolve => require(['../components/page/403.vue'], resolve),
meta: { title: '403' }
}
]
},
{
path: '/login',
component: resolve => require(['../components/page/Login.vue'], resolve)
},
{
path: '*',
redirect: '/404'
}
]
})

293
src/router/index.ts Normal file
View File

@ -0,0 +1,293 @@
import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router';
import { usePermissStore } from '../store/permiss';
import Home from '../views/home.vue';
import NProgress from 'nprogress';
import 'nprogress/nprogress.css';
const routes: RouteRecordRaw[] = [
{
path: '/',
redirect: '/dashboard',
},
{
path: '/',
name: 'Home',
component: Home,
children: [
{
path: '/dashboard',
name: 'dashboard',
meta: {
title: '系统首页',
noAuth: true,
},
component: () => import(/* webpackChunkName: "dashboard" */ '../views/dashboard.vue'),
},
{
path: '/system-user',
name: 'system-user',
meta: {
title: '用户管理',
permiss: '11',
},
component: () => import(/* webpackChunkName: "system-user" */ '../views/system/user.vue'),
},
{
path: '/system-role',
name: 'system-role',
meta: {
title: '角色管理',
permiss: '12',
},
component: () => import(/* webpackChunkName: "system-role" */ '../views/system/role.vue'),
},
{
path: '/system-menu',
name: 'system-menu',
meta: {
title: '菜单管理',
permiss: '13',
},
component: () => import(/* webpackChunkName: "system-menu" */ '../views/system/menu.vue'),
},
{
path: '/table',
name: 'basetable',
meta: {
title: '基础表格',
permiss: '31',
},
component: () => import(/* webpackChunkName: "table" */ '../views/table/basetable.vue'),
},
{
path: '/table-editor',
name: 'table-editor',
meta: {
title: '可编辑表格',
permiss: '32',
},
component: () => import(/* webpackChunkName: "table-editor" */ '../views/table/table-editor.vue'),
},
{
path: '/schart',
name: 'schart',
meta: {
title: 'schart图表',
permiss: '41',
},
component: () => import(/* webpackChunkName: "schart" */ '../views/chart/schart.vue'),
},
{
path: '/echarts',
name: 'echarts',
meta: {
title: 'echarts图表',
permiss: '42',
},
component: () => import(/* webpackChunkName: "echarts" */ '../views/chart/echarts.vue'),
},
{
path: '/icon',
name: 'icon',
meta: {
title: '图标',
permiss: '5',
},
component: () => import(/* webpackChunkName: "icon" */ '../views/pages/icon.vue'),
},
{
path: '/ucenter',
name: 'ucenter',
meta: {
title: '个人中心',
},
component: () => import(/* webpackChunkName: "ucenter" */ '../views/pages/ucenter.vue'),
},
{
path: '/editor',
name: 'editor',
meta: {
title: '富文本编辑器',
permiss: '291',
},
component: () => import(/* webpackChunkName: "editor" */ '../views/pages/editor.vue'),
},
{
path: '/markdown',
name: 'markdown',
meta: {
title: 'markdown编辑器',
permiss: '292',
},
component: () => import(/* webpackChunkName: "markdown" */ '../views/pages/markdown.vue'),
},
{
path: '/export',
name: 'export',
meta: {
title: '导出Excel',
permiss: '34',
},
component: () => import(/* webpackChunkName: "export" */ '../views/table/export.vue'),
},
{
path: '/import',
name: 'import',
meta: {
title: '导入Excel',
permiss: '33',
},
component: () => import(/* webpackChunkName: "import" */ '../views/table/import.vue'),
},
{
path: '/theme',
name: 'theme',
meta: {
title: '主题设置',
permiss: '7',
},
component: () => import(/* webpackChunkName: "theme" */ '../views/pages/theme.vue'),
},
{
path: '/calendar',
name: 'calendar',
meta: {
title: '日历',
permiss: '24',
},
component: () => import(/* webpackChunkName: "calendar" */ '../views/element/calendar.vue'),
},
{
path: '/watermark',
name: 'watermark',
meta: {
title: '水印',
permiss: '25',
},
component: () => import(/* webpackChunkName: "watermark" */ '../views/element/watermark.vue'),
},
{
path: '/carousel',
name: 'carousel',
meta: {
title: '走马灯',
permiss: '23',
},
component: () => import(/* webpackChunkName: "carousel" */ '../views/element/carousel.vue'),
},
{
path: '/tour',
name: 'tour',
meta: {
title: '分步引导',
permiss: '26',
},
component: () => import(/* webpackChunkName: "tour" */ '../views/element/tour.vue'),
},
{
path: '/steps',
name: 'steps',
meta: {
title: '步骤条',
permiss: '27',
},
component: () => import(/* webpackChunkName: "steps" */ '../views/element/steps.vue'),
},
{
path: '/form',
name: 'forms',
meta: {
title: '表单',
permiss: '21',
},
component: () => import(/* webpackChunkName: "form" */ '../views/element/form.vue'),
},
{
path: '/upload',
name: 'upload',
meta: {
title: '上传',
permiss: '22',
},
component: () => import(/* webpackChunkName: "upload" */ '../views/element/upload.vue'),
},
{
path: '/statistic',
name: 'statistic',
meta: {
title: '统计',
permiss: '28',
},
component: () => import(/* webpackChunkName: "statistic" */ '../views/element/statistic.vue'),
},
],
},
{
path: '/login',
meta: {
title: '登录',
noAuth: true,
},
component: () => import(/* webpackChunkName: "login" */ '../views/pages/login.vue'),
},
{
path: '/register',
meta: {
title: '注册',
noAuth: true,
},
component: () => import(/* webpackChunkName: "register" */ '../views/pages/register.vue'),
},
{
path: '/reset-pwd',
meta: {
title: '重置密码',
noAuth: true,
},
component: () => import(/* webpackChunkName: "reset-pwd" */ '../views/pages/reset-pwd.vue'),
},
{
path: '/403',
meta: {
title: '没有权限',
noAuth: true,
},
component: () => import(/* webpackChunkName: "403" */ '../views/pages/403.vue'),
},
{
path: '/404',
meta: {
title: '找不到页面',
noAuth: true,
},
component: () => import(/* webpackChunkName: "404" */ '../views/pages/404.vue'),
},
{ path: '/:path(.*)', redirect: '/404' },
];
const router = createRouter({
history: createWebHashHistory(),
routes,
});
router.beforeEach((to, from, next) => {
NProgress.start();
const role = localStorage.getItem('vuems_name');
const permiss = usePermissStore();
if (!role && to.meta.noAuth !== true) {
next('/login');
} else if (typeof to.meta.permiss == 'string' && !permiss.key.includes(to.meta.permiss)) {
// 如果没有权限则进入403
next('/403');
} else {
next();
}
});
router.afterEach(() => {
NProgress.done();
});
export default router;

60
src/store/permiss.ts Normal file
View File

@ -0,0 +1,60 @@
import { defineStore } from 'pinia';
interface ObjectList {
[key: string]: string[];
}
export const usePermissStore = defineStore('permiss', {
state: () => {
const defaultList: ObjectList = {
admin: [
'0',
'1',
'11',
'12',
'13',
'2',
'21',
'22',
'23',
'24',
'25',
'26',
'27',
'28',
'29',
'291',
'292',
'3',
'31',
'32',
'33',
'34',
'4',
'41',
'42',
'5',
'7',
'6',
'61',
'62',
'63',
'64',
'65',
'66',
],
user: ['0', '1', '11', '12', '13'],
};
const username = localStorage.getItem('vuems_name');
console.log(username);
return {
key: (username == 'admin' ? defaultList.admin : defaultList.user) as string[],
defaultList,
};
},
actions: {
handleSet(val: string[]) {
this.key = val;
},
},
});

25
src/store/sidebar.ts Normal file
View File

@ -0,0 +1,25 @@
import { defineStore } from 'pinia';
export const useSidebarStore = defineStore('sidebar', {
state: () => {
return {
collapse: false,
bgColor: localStorage.getItem('sidebar-bg-color') || '#324157',
textColor: localStorage.getItem('sidebar-text-color') || '#bfcbd9'
};
},
getters: {},
actions: {
handleCollapse() {
this.collapse = !this.collapse;
},
setBgColor(color: string) {
this.bgColor = color;
localStorage.setItem('sidebar-bg-color', color);
},
setTextColor(color: string) {
this.textColor = color;
localStorage.setItem('sidebar-text-color', color);
}
}
});

53
src/store/tabs.ts Normal file
View File

@ -0,0 +1,53 @@
import { defineStore } from 'pinia';
interface ListItem {
name: string;
path: string;
title: string;
}
export const useTabsStore = defineStore('tabs', {
state: () => {
return {
list: <ListItem[]>[]
};
},
getters: {
show: state => {
return state.list.length > 0;
},
nameList: state => {
return state.list.map(item => item.name);
}
},
actions: {
delTabsItem(index: number) {
this.list.splice(index, 1);
},
setTabsItem(data: ListItem) {
this.list.push(data);
},
clearTabs() {
this.list = [];
},
closeTabsOther(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;
}
}
}
}
});

58
src/store/theme.ts Normal file
View File

@ -0,0 +1,58 @@
import { mix, setProperty } from '@/utils';
import { defineStore } from 'pinia';
export const useThemeStore = defineStore('theme', {
state: () => {
return {
primary: '',
success: '',
warning: '',
danger: '',
info: '',
headerBgColor: '#242f42',
headerTextColor: '#fff',
};
},
getters: {},
actions: {
initTheme() {
['primary', 'success', 'warning', 'danger', 'info'].forEach((type) => {
const color = localStorage.getItem(`theme-${type}`) || '';
if (color) {
this.setPropertyColor(color, type); // 设置主题色
}
});
const headerBgColor = localStorage.getItem('header-bg-color');
headerBgColor && this.setHeaderBgColor(headerBgColor);
const headerTextColor = localStorage.getItem('header-text-color');
headerTextColor && this.setHeaderTextColor(headerTextColor);
},
resetTheme() {
['primary', 'success', 'warning', 'danger', 'info'].forEach((type) => {
this.setPropertyColor('', type); // 重置主题色
});
},
setPropertyColor(color: string, type: string = 'primary') {
this[type] = color;
setProperty(`--el-color-${type}`, color);
localStorage.setItem(`theme-${type}`, color);
this.setThemeLight(type);
},
setThemeLight(type: string = 'primary') {
[3, 5, 7, 8, 9].forEach((v) => {
setProperty(`--el-color-${type}-light-${v}`, mix('#ffffff', this[type], v / 10));
});
setProperty(`--el-color-${type}-dark-2`, mix('#ffffff', this[type], 0.2));
},
setHeaderBgColor(color: string) {
this.headerBgColor = color;
setProperty('--header-bg-color', color);
localStorage.setItem(`header-bg-color`, color);
},
setHeaderTextColor(color: string) {
this.headerTextColor = color;
setProperty('--header-text-color', color);
localStorage.setItem(`header-text-color`, color);
}
}
});

21
src/types/form-option.ts Normal file
View File

@ -0,0 +1,21 @@
export interface FormOption {
list: FormOptionList[];
labelWidth?: number | string;
span?: number;
}
export interface FormOptionList {
prop: string;
label: string;
type: string;
placeholder?: string;
disabled?: boolean;
opts?: any[];
format?: string;
activeValue?: any;
inactiveValue?: any;
activeText?: string;
inactiveText?: string;
required?: boolean;
}

9
src/types/menu.ts Normal file
View File

@ -0,0 +1,9 @@
export interface Menus {
id: string;
pid?: string;
icon?: string;
index: string;
title: string;
permiss?: string;
children?: Menus[];
}

8
src/types/role.ts Normal file
View File

@ -0,0 +1,8 @@
export interface Role {
id: number;
name: string;
key: string;
status: boolean;
permiss: string[]
}

9
src/types/table.ts Normal file
View File

@ -0,0 +1,9 @@
export interface TableItem {
id: number;
name: string;
thumb: string;
money: number;
state: string;
date: string;
address: string;
}

16
src/types/user.ts Normal file
View File

@ -0,0 +1,16 @@
export interface User {
id: number;
name: string;
password: string;
email: string;
phone: string;
role: string;
date: string;
}
export interface Register {
username: string;
password: string;
email: string;
}

3
src/utils/china.ts Normal file

File diff suppressed because one or more lines are too long

14
src/utils/index.ts Normal file
View File

@ -0,0 +1,14 @@
export const setProperty = (prop: string, val: any, dom = document.documentElement) => {
dom.style.setProperty(prop, val);
};
export const mix = (color1: string, color2: string, weight: number = 0.5): string => {
let color = '#';
for (let i = 0; i <= 2; i++) {
const c1 = parseInt(color1.substring(1 + i * 2, 3 + i * 2), 16);
const c2 = parseInt(color2.substring(1 + i * 2, 3 + i * 2), 16);
const c = Math.round(c1 * weight + c2 * (1 - weight));
color += c.toString(16).padStart(2, '0');
}
return color;
};

31
src/utils/request.ts Normal file
View File

@ -0,0 +1,31 @@
import axios, { AxiosInstance, AxiosError, AxiosResponse, InternalAxiosRequestConfig } from 'axios';
const service: AxiosInstance = axios.create({
timeout: 5000
});
service.interceptors.request.use(
(config: InternalAxiosRequestConfig) => {
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;

View File

@ -0,0 +1,87 @@
<template>
<div class="container">
<div class="plugins-tips">
vue-echartsApache ECharts Vue.js 组件 访问地址
<a href="https://github.com/ecomfe/vue-echarts" target="_blank">vue-echarts</a>
</div>
<el-card class="mgb20" shadow="hover">
<template #header>
<div class="content-title">柱状图</div>
</template>
<v-chart class="schart" :option="barOptions" />
</el-card>
<el-card class="mgb20" shadow="hover">
<template #header>
<div class="content-title">折线图</div>
</template>
<v-chart class="schart" :option="lineOptions" />
</el-card>
<el-card class="mgb20" shadow="hover">
<template #header>
<div class="content-title">饼状图</div>
</template>
<v-chart class="schart" :option="pieOptions" />
</el-card>
<el-card class="mgb20" shadow="hover">
<template #header>
<div class="content-title">环形图</div>
</template>
<v-chart class="schart" :option="ringOptions" />
</el-card>
<el-card class="mgb20" shadow="hover">
<template #header>
<div class="content-title">词云图</div>
</template>
<v-chart class="schart" :option="wordOptions" />
</el-card>
<el-card class="mgb20" shadow="hover">
<template #header>
<div class="content-title">地图</div>
</template>
<v-chart class="schart" :option="mapOptions" />
</el-card>
</div>
</template>
<script setup lang="ts" name="echarts">
import { registerMap, use } from 'echarts/core';
import { BarChart, LineChart, PieChart, MapChart } from 'echarts/charts';
import {
GridComponent,
TooltipComponent,
LegendComponent,
TitleComponent,
VisualMapComponent,
} from 'echarts/components';
import { CanvasRenderer } from 'echarts/renderers';
import VChart from 'vue-echarts';
import 'echarts-wordcloud';
import { barOptions, lineOptions, pieOptions, ringOptions, wordOptions, mapOptions } from './options';
import chinaMap from '@/utils/china';
use([
CanvasRenderer,
BarChart,
GridComponent,
LineChart,
PieChart,
MapChart,
TooltipComponent,
LegendComponent,
TitleComponent,
VisualMapComponent,
]);
registerMap('china', chinaMap);
</script>
<style scoped>
.schart {
width: 100%;
height: 400px;
}
.content-title {
font-weight: 400;
font-size: 22px;
color: #1f2f3d;
}
</style>

345
src/views/chart/options.ts Normal file
View File

@ -0,0 +1,345 @@
import { graphic } from 'echarts/core';
export const barOptions = {
xAxis: {
type: 'category',
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
},
yAxis: {
type: 'value',
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow',
},
},
color: ['#009688', '#f44336'],
series: [
{
data: [120, 200, 150, 80, 70, 110, 130],
type: 'bar',
},
{
data: [180, 230, 190, 120, 110, 230, 235],
type: 'bar',
},
],
};
export const lineOptions = {
tooltip: {
trigger: 'axis',
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true,
},
xAxis: {
type: 'category',
boundaryGap: false,
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
},
yAxis: {
type: 'value',
},
color: ['#009688', '#f44336'],
series: [
{
name: 'Email',
type: 'line',
stack: 'Total',
areaStyle: {},
smooth: true,
data: [120, 132, 101, 134, 90, 230, 210],
},
{
name: 'Union Ads',
type: 'line',
stack: 'Total',
areaStyle: {},
smooth: true,
data: [220, 182, 191, 234, 290, 330, 310],
},
],
};
export const pieOptions = {
title: {
text: 'Referer of a Website',
subtext: 'Fake Data',
left: 'center',
},
tooltip: {
trigger: 'item',
},
legend: {
orient: 'vertical',
left: 'left',
},
series: [
{
name: 'Access From',
type: 'pie',
radius: '50%',
data: [
{ value: 1048, name: 'Search Engine' },
{ value: 735, name: 'Direct' },
{ value: 580, name: 'Email' },
{ value: 484, name: 'Union Ads' },
{ value: 300, name: 'Video Ads' },
],
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)',
},
},
},
],
};
export const wordOptions = {
series: [
{
type: 'wordCloud',
rotationRange: [0, 0],
autoSize: {
enable: true,
minSize: 14,
},
textStyle: {
fontFamily: '微软雅黑,sans-serif',
color: function () {
return (
'rgb(' +
[
Math.round(Math.random() * 160),
Math.round(Math.random() * 160),
Math.round(Math.random() * 160),
].join(',') +
')'
);
},
},
data: [
{
name: 'Vue',
value: 10000,
},
{
name: 'React',
value: 9000,
},
{
name: '图表',
value: 4000,
},
{
name: '产品',
value: 7000,
},
{
name: 'vue-manage-system',
value: 2000,
},
{
name: 'element-plus',
value: 6000,
},
{
name: '管理系统',
value: 5000,
},
{
name: '前端',
value: 4000,
},
{
name: '测试',
value: 3000,
},
{
name: '后端',
value: 8000,
},
{
name: '软件开发',
value: 6000,
},
{
name: '程序员',
value: 4000,
},
],
},
],
};
export const ringOptions = {
tooltip: {
trigger: 'item',
},
legend: {
top: '5%',
left: 'center',
},
series: [
{
name: 'Access From',
type: 'pie',
radius: ['40%', '70%'],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 10,
borderColor: '#fff',
borderWidth: 2,
},
label: {
show: false,
position: 'center',
},
emphasis: {
label: {
show: true,
fontSize: 40,
fontWeight: 'bold',
},
},
labelLine: {
show: false,
},
data: [
{ value: 1048, name: 'Search Engine' },
{ value: 735, name: 'Direct' },
{ value: 580, name: 'Email' },
{ value: 484, name: 'Union Ads' },
{ value: 300, name: 'Video Ads' },
],
},
],
};
export const dashOpt1 = {
xAxis: {
type: 'category',
boundaryGap: false,
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
},
yAxis: {
type: 'value',
},
grid: {
top: '2%',
left: '2%',
right: '3%',
bottom: '2%',
containLabel: true,
},
color: ['#009688', '#f44336'],
series: [
{
type: 'line',
areaStyle: {
color: new graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: 'rgba(0, 150, 136,0.8)',
},
{
offset: 1,
color: 'rgba(0, 150, 136,0.2)',
},
]),
},
smooth: true,
data: [120, 132, 301, 134, 90, 230, 210],
},
{
type: 'line',
smooth: true,
data: [220, 122, 191, 234, 190, 130, 310],
},
],
};
export const dashOpt2 = {
legend: {
bottom: '1%',
left: 'center',
},
color: ['#3f51b5', '#009688', '#f44336', '#00bcd4', '#1ABC9C'],
series: [
{
type: 'pie',
radius: ['40%', '70%'],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 10,
borderColor: '#fff',
borderWidth: 2,
},
data: [
{ value: 1048, name: '数码' },
{ value: 735, name: '食品' },
{ value: 580, name: '母婴' },
{ value: 484, name: '家电' },
{ value: 300, name: '运动' },
],
},
],
};
export const mapOptions = {
tooltip: {
trigger: 'item',
},
geo: {
map: 'china',
roam: false,
emphasis: {
label: {
show: false,
},
},
},
visualMap: {
show: false,
min: 0,
max: 100,
realtime: false,
calculable: false,
inRange: {
color: ['#d2e0f5', '#71A9FF'],
},
},
series: [
{
geoIndex: 0,
name: '地域分布',
type: 'map',
coordinateSystem: 'geo',
map: 'china',
data: [
{ name: '北京', value: 100 },
{ name: '上海', value: 100 },
{ name: '广东', value: 100 },
{ name: '浙江', value: 90 },
{ name: '江西', value: 80 },
{ name: '山东', value: 70 },
{ name: '广西', value: 60 },
{ name: '河南', value: 50 },
{ name: '河南', value: 40 },
{ name: '青海', value: 70 },
{ name: '河南', value: 30 },
{ name: '黑龙江', value: 20 },
{ name: '新疆', value: 20 },
{ name: '云南', value: 20 },
{ name: '甘肃', value: 20 },
],
},
],
};

129
src/views/chart/schart.vue Normal file
View File

@ -0,0 +1,129 @@
<template>
<div class="container">
<div class="plugins-tips">
vue-schartvue.js封装sChart.js的图表组件 访问地址
<a href="https://github.com/lin-xin/vue-schart" target="_blank">vue-schart</a>
</div>
<el-card class="mgb20" shadow="hover">
<template #header>
<div class="content-title">柱状图</div>
</template>
<schart class="schart" canvasId="bar" :options="options1"></schart>
</el-card>
<el-card class="mgb20" shadow="hover">
<template #header>
<div class="content-title">折线图</div>
</template>
<schart class="schart" canvasId="line" :options="options2"></schart>
</el-card>
<el-card class="mgb20" shadow="hover">
<template #header>
<div class="content-title">饼状图</div>
</template>
<schart class="schart" canvasId="pie" :options="options3"></schart>
</el-card>
<el-card class="mgb20" shadow="hover">
<template #header>
<div class="content-title">环形图</div>
</template>
<schart class="schart" canvasId="ring" :options="options4"></schart>
</el-card>
</div>
</template>
<script setup lang="ts" name="schart">
import Schart from 'vue-schart';
const options1 = {
type: 'bar',
title: {
text: '最近一周各品类销售图'
},
colorList: ["#3f51b5", "#009688", "#f44336", "#00bcd4", "#1ABC9C"],
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: '最近几个月各品类销售趋势图'
},
colorList: ["#3f51b5", "#009688", "#f44336", "#00bcd4", "#1ABC9C"],
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'
},
colorList: ["#2196f3", '#673ab7', "#009688", "#1ABC9C", "#3f51b5", "#f44336", "#00bcd4"],
labels: ['T恤', '牛仔裤', '连衣裙', '毛衣', '七分裤', '短裙', '羽绒服'],
datasets: [
{
data: [334, 278, 190, 235, 260, 200, 141]
}
]
};
const options4 = {
type: 'ring',
title: {
text: '环形三等分'
},
showValue: false,
legend: {
position: 'bottom',
bottom: 40
},
colorList: ["#3f51b5", "#009688", "#f44336", "#00bcd4", "#1ABC9C"],
labels: ['vue', 'react', 'angular'],
datasets: [
{
data: [500, 500, 500]
}
]
};
</script>
<style scoped>
.schart {
width: 100%;
height: 400px;
}
.content-title {
font-weight: 400;
font-size: 22px;
color: #1f2f3d;
}
</style>

357
src/views/dashboard.vue Normal file
View File

@ -0,0 +1,357 @@
<template>
<div>
<el-row :gutter="20" class="mgb20">
<el-col :span="6">
<el-card shadow="hover" body-class="card-body">
<el-icon class="card-icon bg1">
<User />
</el-icon>
<div class="card-content">
<countup class="card-num color1" :end="6666" />
<div>用户访问量</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" body-class="card-body">
<el-icon class="card-icon bg2">
<ChatDotRound />
</el-icon>
<div class="card-content">
<countup class="card-num color2" :end="168" />
<div>系统消息</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" body-class="card-body">
<el-icon class="card-icon bg3">
<Goods />
</el-icon>
<div class="card-content">
<countup class="card-num color3" :end="8888" />
<div>商品数量</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" body-class="card-body">
<el-icon class="card-icon bg4">
<ShoppingCartFull />
</el-icon>
<div class="card-content">
<countup class="card-num color4" :end="568" />
<div>今日订单量</div>
</div>
</el-card>
</el-col>
</el-row>
<el-row :gutter="20" class="mgb20">
<el-col :span="18">
<el-card shadow="hover">
<div class="card-header">
<p class="card-header-title">订单动态</p>
<p class="card-header-desc">最近一周订单状态包括订单成交量和订单退货量</p>
</div>
<v-chart class="chart" :option="dashOpt1" />
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover">
<div class="card-header">
<p class="card-header-title">品类分布</p>
<p class="card-header-desc">最近一个月销售商品的品类情况</p>
</div>
<v-chart class="chart" :option="dashOpt2" />
</el-card>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="7">
<el-card shadow="hover" :body-style="{ height: '400px' }">
<div class="card-header">
<p class="card-header-title">时间线</p>
<p class="card-header-desc">最新的销售动态和活动信息</p>
</div>
<el-timeline>
<el-timeline-item v-for="(activity, index) in activities" :key="index" :color="activity.color">
<div class="timeline-item">
<div>
<p>{{ activity.content }}</p>
<p class="timeline-desc">{{ activity.description }}</p>
</div>
<div class="timeline-time">{{ activity.timestamp }}</div>
</div>
</el-timeline-item>
</el-timeline>
</el-card>
</el-col>
<el-col :span="10">
<el-card shadow="hover" :body-style="{ height: '400px' }">
<div class="card-header">
<p class="card-header-title">渠道统计</p>
<p class="card-header-desc">最近一个月的订单来源统计</p>
</div>
<v-chart class="map-chart" :option="mapOptions" />
</el-card>
</el-col>
<el-col :span="7">
<el-card shadow="hover" :body-style="{ height: '400px' }">
<div class="card-header">
<p class="card-header-title">排行榜</p>
<p class="card-header-desc">销售商品的热门榜单Top5</p>
</div>
<div>
<div class="rank-item" v-for="(rank, index) in ranks">
<div class="rank-item-avatar">{{ index + 1 }}</div>
<div class="rank-item-content">
<div class="rank-item-top">
<div class="rank-item-title">{{ rank.title }}</div>
<div class="rank-item-desc">销量{{ rank.value }}</div>
</div>
<el-progress
:show-text="false"
striped
:stroke-width="10"
:percentage="rank.percent"
:color="rank.color"
/>
</div>
</div>
</div>
</el-card>
</el-col>
</el-row>
</div>
</template>
<script setup lang="ts" name="dashboard">
import countup from '@/components/countup.vue';
import { use, registerMap } from 'echarts/core';
import { BarChart, LineChart, PieChart, MapChart } from 'echarts/charts';
import {
GridComponent,
TooltipComponent,
LegendComponent,
TitleComponent,
VisualMapComponent,
} from 'echarts/components';
import { CanvasRenderer } from 'echarts/renderers';
import VChart from 'vue-echarts';
import { dashOpt1, dashOpt2, mapOptions } from './chart/options';
import chinaMap from '@/utils/china';
use([
CanvasRenderer,
BarChart,
GridComponent,
LineChart,
PieChart,
TooltipComponent,
LegendComponent,
TitleComponent,
VisualMapComponent,
MapChart,
]);
registerMap('china', chinaMap);
const activities = [
{
content: '收藏商品',
description: 'xxx收藏了你的商品就是不买',
timestamp: '30分钟前',
color: '#00bcd4',
},
{
content: '用户评价',
description: 'xxx给了某某商品一个差评吐血啊',
timestamp: '55分钟前',
color: '#1ABC9C',
},
{
content: '订单提交',
description: 'xxx提交了订单快去收钱吧',
timestamp: '1小时前',
color: '#3f51b5',
},
{
content: '退款申请',
description: 'xxx申请了仅退款又要亏钱了',
timestamp: '15小时前',
color: '#f44336',
},
{
content: '商品上架',
description: '运营专员瞒着你上架了一辆飞机',
timestamp: '1天前',
color: '#009688',
},
];
const ranks = [
{
title: '手机',
value: 10000,
percent: 80,
color: '#f25e43',
},
{
title: '电脑',
value: 8000,
percent: 70,
color: '#00bcd4',
},
{
title: '相机',
value: 6000,
percent: 60,
color: '#64d572',
},
{
title: '衣服',
value: 5000,
percent: 55,
color: '#e9a745',
},
{
title: '书籍',
value: 4000,
percent: 50,
color: '#009688',
},
];
</script>
<style>
.card-body {
display: flex;
align-items: center;
height: 100px;
padding: 0;
}
</style>
<style scoped>
.card-content {
flex: 1;
text-align: center;
font-size: 14px;
color: #999;
padding: 0 20px;
}
.card-num {
font-size: 30px;
}
.card-icon {
font-size: 50px;
width: 100px;
height: 100px;
text-align: center;
line-height: 100px;
color: #fff;
}
.bg1 {
background: #2d8cf0;
}
.bg2 {
background: #64d572;
}
.bg3 {
background: #f25e43;
}
.bg4 {
background: #e9a745;
}
.color1 {
color: #2d8cf0;
}
.color2 {
color: #64d572;
}
.color3 {
color: #f25e43;
}
.color4 {
color: #e9a745;
}
.chart {
width: 100%;
height: 400px;
}
.card-header {
padding-left: 10px;
margin-bottom: 20px;
}
.card-header-title {
font-size: 18px;
font-weight: bold;
margin-bottom: 5px;
}
.card-header-desc {
font-size: 14px;
color: #999;
}
.timeline-item {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 16px;
color: #000;
}
.timeline-time,
.timeline-desc {
font-size: 12px;
color: #787878;
}
.rank-item {
display: flex;
align-items: center;
margin-bottom: 20px;
}
.rank-item-avatar {
width: 40px;
height: 40px;
border-radius: 50%;
background: #f2f2f2;
text-align: center;
line-height: 40px;
margin-right: 10px;
}
.rank-item-content {
flex: 1;
}
.rank-item-top {
display: flex;
justify-content: space-between;
align-items: center;
color: #343434;
margin-bottom: 10px;
}
.rank-item-desc {
font-size: 14px;
color: #999;
}
.map-chart {
width: 100%;
height: 350px;
}
</style>

View File

@ -0,0 +1,82 @@
<template>
<div class="container">
<el-calendar v-model="value">
<template #date-cell="{ data }">
<div>{{ data.date.getDate() }}</div>
<div class="notes-container" v-if="notes[data.day.toString()]">
<div class="notes" v-for="note in notes[data.day.toString()]">
<span :class="note.status === 1 ? 'text-success' : 'text-danger'"></span>
<div class="note-title">{{ note.title }}</div>
</div>
</div>
</template>
</el-calendar>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
const today = new Date();
const yesterday = new Date(today.getTime() - 24 * 60 * 60 * 1000);
const value = ref(today);
const todayDate = today.toISOString().slice(0, 10);
const yesterdayDate = yesterday.toISOString().slice(0, 10);
const notes: any = {
[todayDate]: [
{ title: '吃饭', status: 1 },
{ title: '睡觉', status: 0 },
{ title: '吃饭', status: 1 },
{ title: '睡觉', status: 0 },
{ title: '吃饭', status: 1 },
{ title: '睡觉', status: 0 },
],
[yesterdayDate]: [{ title: '参加会议', status: 0 }],
};
</script>
<style scoped>
.notes-container {
height: 60px;
overflow-y: auto;
}
.notes-container::-webkit-scrollbar {
width: 0;
}
.notes {
display: flex;
align-items: center;
width: 100%;
font-size: 12px;
}
.notes:hover {
background-color: #eee;
}
.note-title {
flex: 1;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
.notes span {
width: 8px;
height: 8px;
border-radius: 50%;
margin-right: 5px;
}
.text-success {
background-color: #5cb85c;
}
.text-danger {
background-color: #d9534f;
}
</style>

View File

@ -0,0 +1,66 @@
<template>
<div>
<el-card class="mgb20">
<template #header>基础用法</template>
<el-carousel height="400px">
<el-carousel-item v-for="item in 4" :key="item">
<h3>{{ item }}</h3>
</el-carousel-item>
</el-carousel>
</el-card>
<el-row :gutter="20">
<el-col :span="12">
<el-card class="mgb20">
<template #header>轮播图</template>
<el-carousel height="300px">
<el-carousel-item v-for="item in imgs" :key="item">
<el-image class="carousel-img" :src="item" fit="cover" />
</el-carousel-item>
</el-carousel>
</el-card>
</el-col>
<el-col :span="12">
<el-card class="mgb20">
<template #header>卡片模式</template>
<el-carousel height="300px" type="card">
<el-carousel-item v-for="item in imgs" :key="item">
<el-image class="carousel-img" :src="item" fit="cover" />
</el-carousel-item>
</el-carousel>
</el-card>
</el-col>
</el-row>
</div>
</template>
<script lang="ts" setup>
const imgs = [
'https://cdn.pixabay.com/photo/2017/08/07/08/23/sea-2601374_640.jpg',
'https://cdn.pixabay.com/photo/2020/02/11/10/24/lake-4839058_640.jpg',
'https://cdn.pixabay.com/photo/2024/02/21/08/06/coast-8587004_640.jpg',
'https://cdn.pixabay.com/photo/2023/07/29/10/21/grasshopper-8156626_640.jpg',
];
</script>
<style scoped>
.el-carousel__item h3 {
color: #475669;
line-height: 400px;
margin: 0;
text-align: center;
}
.el-carousel__item:nth-child(2n) {
background-color: #99a9bf;
}
.el-carousel__item:nth-child(2n + 1) {
background-color: #d3dce6;
}
.carousel-img {
width: 100%;
height: 100%;
}
</style>

189
src/views/element/form.vue Normal file
View File

@ -0,0 +1,189 @@
<template>
<div class="container">
<el-radio-group class="mgb20" v-model="labelPosition">
<el-radio-button value="left">Left</el-radio-button>
<el-radio-button value="right">Right</el-radio-button>
<el-radio-button value="top">Top</el-radio-button>
</el-radio-group>
<el-form ref="formRef" :rules="rules" :model="form" label-width="120px" :label-position="labelPosition">
<el-row :gutter="50">
<el-col :span="10">
<el-form-item label="文本框" prop="name">
<el-input v-model="form.name"></el-input>
</el-form-item>
<el-form-item label="数字框" prop="num">
<el-input-number v-model="form.num" :min="1" :max="10" />
</el-form-item>
<el-form-item label="日期选择" prop="date">
<el-date-picker type="date" placeholder="选择日期" v-model="form.date"></el-date-picker>
</el-form-item>
<el-form-item label="时间选择" prop="time">
<el-time-picker placeholder="选择时间" v-model="form.time">
</el-time-picker>
</el-form-item>
<el-form-item label="选择器" prop="region">
<el-select v-model="form.region" placeholder="请选择">
<el-option key="小明" label="小明" value="小明"></el-option>
<el-option key="小红" label="小红" value="小红"></el-option>
<el-option key="小白" label="小白" value="小白"></el-option>
</el-select>
</el-form-item>
<el-form-item label="城市级联" prop="options">
<el-cascader :options="options" v-model="form.options"></el-cascader>
</el-form-item>
<el-form-item label="文本框" prop="desc">
<el-input type="textarea" rows="5" v-model="form.desc"></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="评分" prop="rate">
<el-rate v-model="form.rate" allow-half />
</el-form-item>
<el-form-item label="滑块" prop="num">
<el-slider v-model="form.num" :step="1" show-stops :max="10" />
</el-form-item>
<el-form-item label="开关" prop="delivery">
<el-switch v-model="form.delivery"></el-switch>
</el-form-item>
<el-form-item label="颜色选择" prop="color">
<el-color-picker v-model="form.color" />
</el-form-item>
<el-form-item label="多选框" prop="type">
<el-checkbox-group v-model="form.type">
<el-checkbox label="小明" value="小明" name="type"></el-checkbox>
<el-checkbox label="小红" value="小红" name="type"></el-checkbox>
<el-checkbox label="小白" value="小白" name="type"></el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item label="单选框" prop="resource">
<el-radio-group v-model="form.resource">
<el-radio label="小明" value="小明"></el-radio>
<el-radio label="小红" value="小红"></el-radio>
<el-radio label="小白" value="小白"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="穿梭框" prop="transfer">
<el-transfer v-model="form.transfer" :data="transferData" />
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item>
<el-button type="primary" @click="onSubmit(formRef)"></el-button>
<el-button @click="onReset(formRef)"></el-button>
</el-form-item>
</el-col>
</el-row>
</el-form>
</div>
</template>
<script setup lang="ts" name="forms">
import { reactive, ref } from 'vue';
import { ElMessage } from 'element-plus';
import type { FormInstance, FormProps, FormRules } from 'element-plus';
const labelPosition = ref<FormProps['labelPosition']>('right')
const options = [
{
value: 'guangdong',
label: '广东省',
children: [
{
value: 'guangzhou',
label: '广州市',
children: [
{
value: 'tianhe',
label: '天河区',
},
{
value: 'haizhu',
label: '海珠区',
},
],
},
{
value: 'dongguan',
label: '东莞市',
children: [
{
value: 'changan',
label: '长安镇',
},
{
value: 'humen',
label: '虎门镇',
},
],
},
],
},
{
value: 'hunan',
label: '湖南省',
children: [
{
value: 'changsha',
label: '长沙市',
children: [
{
value: 'yuelu',
label: '岳麓区',
},
],
},
],
},
];
const rules: FormRules = {
name: [{ required: true, message: '请输入表单名称', trigger: 'blur' }],
};
const formRef = ref<FormInstance>();
const form = reactive({
name: '',
region: '',
date: '',
time: '',
delivery: true,
type: ['小明'],
resource: '小红',
desc: '',
options: [],
color: '',
num: 1,
rate: 0,
transfer: [],
});
const generateData = () => {
const data = []
for (let i = 1; i <= 15; i++) {
data.push({
key: i,
label: `Option ${i}`,
disabled: i % 4 === 0,
})
}
return data
}
const transferData = ref(generateData())
//
const onSubmit = (formEl: FormInstance | undefined) => {
//
if (!formEl) return;
formEl.validate((valid) => {
if (valid) {
console.log(form);
ElMessage.success('提交成功!');
} else {
return false;
}
});
};
//
const onReset = (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.resetFields();
};
</script>

View File

@ -0,0 +1,340 @@
<template>
<div>
<el-card class="mgb20" shadow="hover">
<template #header>基础用法</template>
<el-row>
<el-col :span="6" style="text-align: center">
<el-statistic title="Daily active users" :value="268500" />
</el-col>
<el-col :span="6" style="text-align: center">
<el-statistic :value="138">
<template #title>
<div style="display: inline-flex; align-items: center">
Ratio of men to women
</div>
</template>
<template #suffix>/100</template>
</el-statistic>
</el-col>
<el-col :span="6" style="text-align: center">
<el-statistic title="数字滚动" :value="outputValue" />
</el-col>
<el-col :span="6" style="text-align: center">
<el-countdown title="倒计时" :value="value" />
</el-col>
</el-row>
</el-card>
<el-card class="mgb20" shadow="hover">
<template #header>CountUp.js</template>
<div class="plugins-tips">
countup.js用于快速创建以更有趣的方式显示数字数据的动画 访问地址
<a href="https://github.com/inorganik/countUp.js" target="_blank">countUp.js</a>
</div>
<el-row>
<el-col :span="8" style="text-align: center">
<p>基础用法</p>
<countup class="countup" :end="6666" />
</el-col>
<el-col :span="8" style="text-align: center">
<p>具体配置</p>
<countup class="countup" :end="8888.5" :options="options" />
</el-col>
<el-col :span="8" style="text-align: center">
<p>更新数值</p>
<countup class="countup" :end="value1" />
</el-col>
</el-row>
</el-card>
<el-card class="mgb20" shadow="never">
<template #header>统计卡片</template>
<el-row :gutter="20" class="mgb20">
<el-col :span="6">
<el-card shadow="hover" body-class="card-body">
<el-icon class="card-icon color1">
<User />
</el-icon>
<div class="card-content text-right">
<el-statistic title="日活跃用户量" :value="268500" />
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" body-class="card-body">
<el-icon class="card-icon color2">
<ChatDotRound />
</el-icon>
<div class="card-content text-right">
<el-statistic title="系统消息" :value="16800" />
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" body-class="card-body">
<el-icon class="card-icon color3">
<Goods />
</el-icon>
<div class="card-content text-right">
<el-statistic title="商品数量" :value="8888" />
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" body-class="card-body">
<el-icon class="card-icon color4">
<ShoppingCartFull />
</el-icon>
<div class="card-content text-right">
<el-statistic title="今日订单量" :value="56888" />
</div>
</el-card>
</el-col>
</el-row>
<el-row :gutter="20" class="mgb20">
<el-col :span="6">
<el-card shadow="hover" body-class="card-body">
<div class="card-content text-left">
<el-statistic :value-style="{ color: '#2d8cf0' }" title="日活跃用户量" :value="268500" />
</div>
<el-icon class="card-icon color1">
<User />
</el-icon>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" body-class="card-body">
<div class="card-content text-left">
<el-statistic :value-style="{ color: '#64d572' }" title="系统消息" :value="16800" />
</div>
<el-icon class="card-icon color2">
<ChatDotRound />
</el-icon>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" body-class="card-body">
<div class="card-content text-left">
<el-statistic :value-style="{ color: '#f25e43' }" title="商品数量" :value="8888" />
</div>
<el-icon class="card-icon color3">
<Goods />
</el-icon>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" body-class="card-body">
<div class="card-content text-left">
<el-statistic :value-style="{ color: '#e9a745' }" title="今日订单量" :value="56888" />
</div>
<el-icon class="card-icon color4">
<ShoppingCartFull />
</el-icon>
</el-card>
</el-col>
</el-row>
<el-row :gutter="20" class="mgb20">
<el-col :span="6">
<el-card shadow="hover" body-class="card-body">
<el-icon class="card-icon bg1">
<User />
</el-icon>
<div class="card-content">
<countup class="card-num color1" :end="6666" />
<div>用户访问量</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" body-class="card-body">
<el-icon class="card-icon bg2">
<ChatDotRound />
</el-icon>
<div class="card-content">
<countup class="card-num color2" :end="168" />
<div>系统消息</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" body-class="card-body">
<el-icon class="card-icon bg3">
<Goods />
</el-icon>
<div class="card-content">
<countup class="card-num color3" :end="8888" />
<div>商品数量</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" body-class="card-body">
<el-icon class="card-icon bg4">
<ShoppingCartFull />
</el-icon>
<div class="card-content">
<countup class="card-num color4" :end="568" />
<div>今日订单量</div>
</div>
</el-card>
</el-col>
</el-row>
<el-row :gutter="20" class="mgb20">
<el-col :span="6">
<el-card shadow="hover" body-class="card-body bg1">
<el-icon class="card-icon ">
<User />
</el-icon>
<div class="card-content color0">
<countup class="card-num" :end="6666" />
<div>用户访问量</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" body-class="card-body bg2">
<el-icon class="card-icon">
<ChatDotRound />
</el-icon>
<div class="card-content color0">
<countup class="card-num" :end="168" />
<div>系统消息</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" body-class="card-body bg3">
<el-icon class="card-icon">
<Goods />
</el-icon>
<div class="card-content color0">
<countup class="card-num " :end="8888" />
<div>商品数量</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" body-class="card-body bg4">
<el-icon class="card-icon">
<ShoppingCartFull />
</el-icon>
<div class="card-content color0">
<countup class="card-num " :end="568" />
<div>今日订单量</div>
</div>
</el-card>
</el-col>
</el-row>
</el-card>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import { useTransition } from '@vueuse/core'
import countup from '@/components/countup.vue';
const source = ref(0)
const outputValue = useTransition(source, {
duration: 1500,
})
source.value = 172000
const value = ref(Date.now() + 1000 * 60 * 60 * 7)
const value1 = ref(1000);
setTimeout(() => {
value1.value = 8000;
}, 5000);
const options = {
startVal: 1000,
decimalPlaces: 2,
duration: 5,
useGrouping: false,
prefix: '$',
separator: ',',
decimal: '.',
suffix: '',
}
</script>
<style>
.card-body {
display: flex;
align-items: center;
height: 100px;
padding: 0;
}
.bg1 {
background: #2d8cf0;
}
.bg2 {
background: #64d572;
}
.bg3 {
background: #f25e43;
}
.bg4 {
background: #e9a745;
}
</style>
<style scoped>
.countup {
font-size: 24px;
}
.card-content {
flex: 1;
text-align: center;
font-size: 14px;
color: #999;
padding: 0 20px;
}
.card-num {
font-size: 30px;
}
.card-icon {
font-size: 50px;
width: 100px;
height: 100px;
text-align: center;
line-height: 100px;
color: #fff;
}
.color0 {
color: #fff;
}
.color1 {
color: #2d8cf0;
}
.color2 {
color: #64d572;
}
.color3 {
color: #f25e43;
}
.color4 {
color: #e9a745;
}
.text-right {
text-align: right;
}
.text-left {
text-align: left;
}
</style>

View File

@ -0,0 +1,61 @@
<template>
<div class="container">
<div class="step-div" v-if="step === 0">
<p>输入注册时的邮箱我们会发送验证码到您的邮箱</p>
<el-input placeholder="请输入邮箱"></el-input>
<el-button class="step-btn" type="primary" @click="step++"></el-button>
</div>
<div class="step-div" v-else-if="step === 1">
<p>验证码已发送至您的邮箱请输入验证码</p>
<el-input placeholder="请输入验证码"></el-input>
<el-button class="step-btn" type="primary" @click="step++"></el-button>
</div>
<div class="step-div" v-else-if="step === 2">
<p>请输入6位以上密码</p>
<el-input placeholder="请输入新密码"></el-input>
<el-button class="step-btn" type="primary" @click="step++"></el-button>
</div>
<div v-else>
<el-result icon="success" title="保存成功" sub-title="退"></el-result>
</div>
<el-steps class="step-style" :active="step" align-center finish-status="success">
<el-step title="Step 1" description="填写邮箱" />
<el-step title="Step 2" description="填写验证码" />
<el-step title="Step 3" description="修改密码" />
</el-steps>
<el-steps class="step-style" :active="step" finish-status="success" simple>
<el-step title="填写邮箱" />
<el-step title="填写验证码" />
<el-step title="修改密码" />
</el-steps>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
const step = ref(0)
</script>
<style scoped>
.step-div {
max-width: 500px;
margin: 0 auto;
}
.step-div p {
margin-bottom: 20px;
color: #787878;
}
.step-btn {
display: block;
width: 100%;
margin: 20px 0;
}
.step-style {
max-width: 800px;
margin: 40px auto;
}
</style>

116
src/views/element/tabs.vue Normal file
View File

@ -0,0 +1,116 @@
<template>
<el-tabs v-model="message" type="card">
<el-tab-pane :label="`未读消息(${state.unread.length})`" name="first">
<el-table :data="state.unread" :show-header="false" style="width: 100%">
<el-table-column>
<template #default="scope">
<span class="message-title">{{ scope.row.title }}</span>
</template>
</el-table-column>
<el-table-column prop="date" width="180"></el-table-column>
<el-table-column width="120">
<template #default="scope">
<el-button size="small" @click="handleRead(scope.$index)"></el-button>
</template>
</el-table-column>
</el-table>
<div class="handle-row">
<el-button type="primary">全部标为已读</el-button>
</div>
</el-tab-pane>
<el-tab-pane :label="`已读消息(${state.read.length})`" name="second">
<template v-if="message === 'second'">
<el-table :data="state.read" :show-header="false" style="width: 100%">
<el-table-column>
<template #default="scope">
<span class="message-title">{{ scope.row.title }}</span>
</template>
</el-table-column>
<el-table-column prop="date" width="180"></el-table-column>
<el-table-column width="120">
<template #default="scope">
<el-button type="danger" size="small" @click="handleDel(scope.$index)"></el-button>
</template>
</el-table-column>
</el-table>
<div class="handle-row">
<el-button type="danger">删除全部</el-button>
</div>
</template>
</el-tab-pane>
<el-tab-pane :label="`回收站(${state.recycle.length})`" name="third">
<template v-if="message === 'third'">
<el-table :data="state.recycle" :show-header="false" style="width: 100%">
<el-table-column>
<template #default="scope">
<span class="message-title">{{ scope.row.title }}</span>
</template>
</el-table-column>
<el-table-column prop="date" width="180"></el-table-column>
<el-table-column width="120">
<template #default="scope">
<el-button size="small" @click="handleRestore(scope.$index)"></el-button>
</template>
</el-table-column>
</el-table>
<div class="handle-row">
<el-button type="danger">清空回收站</el-button>
</div>
</template>
</el-tab-pane>
</el-tabs>
</template>
<script setup lang="ts" name="tabs">
import { ref, reactive } from 'vue';
const message = ref('first');
const state = reactive({
unread: [
{
date: '2018-04-19 20:00:00',
title: '【系统通知】该系统将于今晚凌晨2点到5点进行升级维护'
},
{
date: '2018-04-19 21:00:00',
title: '今晚12点整发大红包先到先得'
}
],
read: [
{
date: '2018-04-19 20:00:00',
title: '【系统通知】该系统将于今晚凌晨2点到5点进行升级维护'
}
],
recycle: [
{
date: '2018-04-19 20:00:00',
title: '【系统通知】该系统将于今晚凌晨2点到5点进行升级维护'
}
]
});
const handleRead = (index: number) => {
const item = state.unread.splice(index, 1);
state.read = item.concat(state.read);
};
const handleDel = (index: number) => {
const item = state.read.splice(index, 1);
state.recycle = item.concat(state.recycle);
};
const handleRestore = (index: number) => {
const item = state.recycle.splice(index, 1);
state.read = item.concat(state.read);
};
</script>
<style>
.message-title {
cursor: pointer;
color: var(--el-color-primary);
}
.handle-row {
margin-top: 30px;
}
</style>

View File

@ -0,0 +1,33 @@
<template>
<div class="container">
<el-button type="primary" @click="open = true">开始引导</el-button>
<el-divider />
<el-space>
<el-button ref="ref1">上传</el-button>
<el-button ref="ref2" type="primary">保存</el-button>
<el-button ref="ref3" :icon="MoreFilled" />
</el-space>
<el-tour v-model="open">
<el-tour-step :target="ref1?.$el" title="上传文件">
<img style="width: 120px" src="../../assets/img/img.jpg" alt="tour.png" />
<div>点击这里选择文件</div>
</el-tour-step>
<el-tour-step :target="ref2?.$el" title="保存" description="点击进行上传" />
<el-tour-step :target="ref3?.$el" title="更多操作" description="点击查看更多操作" />
</el-tour>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { MoreFilled } from '@element-plus/icons-vue'
const ref1 = ref()
const ref2 = ref()
const ref3 = ref()
const open = ref(false)
</script>

View File

@ -0,0 +1,44 @@
<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-cropper一个简单的vue图片裁剪插件 访问地址
<a href="https://github.com/xyxiao001/vue-cropper" target="_blank">vue-cropper</a> 示例请查看
<router-link to="/ucenter">个人中心-我的头像</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>

View File

@ -0,0 +1,62 @@
<template>
<div class="container">
<el-row :gutter="20">
<el-col :span="18">
<el-watermark :content="config.content" :font="config.font" :z-index="config.zIndex"
:rotate="config.rotate" :gap="config.gap" :offset="config.offset">
<div style="height: 600px" />
</el-watermark>
</el-col>
<el-col :span="6">
<el-form class="form" :model="config" label-position="top" label-width="50px">
<el-form-item label="Content">
<el-input v-model="config.content" />
</el-form-item>
<el-form-item label="Color">
<el-color-picker v-model="config.font.color" show-alpha />
</el-form-item>
<el-form-item label="FontSize">
<el-slider v-model="config.font.fontSize" />
</el-form-item>
<el-form-item label="zIndex">
<el-slider v-model="config.zIndex" />
</el-form-item>
<el-form-item label="Rotate">
<el-slider v-model="config.rotate" :min="-180" :max="180" />
</el-form-item>
<el-form-item label="Gap">
<el-space>
<el-input-number v-model="config.gap[0]" controls-position="right" />
<el-input-number v-model="config.gap[1]" controls-position="right" />
</el-space>
</el-form-item>
<el-form-item label="Offset">
<el-space>
<el-input-number v-model="config.offset[0]" placeholder="offsetLeft"
controls-position="right" />
<el-input-number v-model="config.offset[1]" placeholder="offsetTop"
controls-position="right" />
</el-space>
</el-form-item>
</el-form>
</el-col>
</el-row>
</div>
</template>
<script setup lang="ts">
import { reactive } from 'vue'
const config = reactive({
content: 'vue-manage-system',
font: {
fontSize: 16,
color: 'rgba(0, 0, 0, 0.15)',
},
zIndex: -1,
rotate: -22,
gap: [100, 100] as [number, number],
offset: [] as unknown as [number, number],
})
</script>

63
src/views/home.vue Normal file
View File

@ -0,0 +1,63 @@
<template>
<div class="wrapper">
<v-header />
<v-sidebar />
<div class="content-box" :class="{ 'content-collapse': sidebar.collapse }">
<v-tabs></v-tabs>
<div class="content">
<router-view v-slot="{ Component }">
<transition name="move" mode="out-in">
<keep-alive :include="tabs.nameList">
<component :is="Component"></component>
</keep-alive>
</transition>
</router-view>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { useSidebarStore } from '@/store/sidebar';
import { useTabsStore } from '@/store/tabs';
import vHeader from '@/components/header.vue';
import vSidebar from '@/components/sidebar.vue';
import vTabs from '@/components/tabs.vue';
const sidebar = useSidebarStore();
const tabs = useTabsStore();
</script>
<style>
.wrapper {
height: 100vh;
overflow: hidden;
}
.content-box {
position: absolute;
left: 250px;
right: 0;
top: 70px;
bottom: 0;
padding-bottom: 30px;
-webkit-transition: left 0.3s ease-in-out;
transition: left 0.3s ease-in-out;
background: #eef0fc;
overflow: hidden;
}
.content {
width: auto;
height: 100%;
padding: 20px;
overflow-y: scroll;
box-sizing: border-box;
}
.content::-webkit-scrollbar {
width: 0;
}
.content-collapse {
left: 65px;
}
</style>

67
src/views/pages/403.vue Normal file
View File

@ -0,0 +1,67 @@
<template>
<div class="error-page">
<div class="error-box">
<div class="error-code">403</div>
<div class="error-desc">啊哦~ 你没有权限访问该页面哦</div>
<div class="error-handle">
<router-link to="/">
<el-button type="primary" size="large">返回首页</el-button>
</router-link>
<el-button class="error-btn" size="large" @click="goBack"></el-button>
</div>
</div>
</div>
</template>
<script setup lang="ts" name="403">
import { useRouter } from 'vue-router';
const router = useRouter();
const goBack = () => {
router.go(-2);
};
</script>
<style scoped>
.error-page {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
width: 100%;
height: 100vh;
background: #eef0fc;
box-sizing: border-box;
}
.error-box {
width: 400px;
background-color: #fff;
padding: 80px 50px;
border-radius: 5px;
}
.error-code {
line-height: 1;
font-size: 100px;
font-weight: bold;
color: var(--el-color-primary);
margin-bottom: 20px;
text-align: center;
}
.error-desc {
font-size: 20px;
color: #777;
text-align: center;
}
.error-handle {
margin-top: 50px;
text-align: center;
}
.error-btn {
margin-left: 100px;
}
</style>

67
src/views/pages/404.vue Normal file
View File

@ -0,0 +1,67 @@
<template>
<div class="error-page">
<div class="error-box">
<div class="error-code">404</div>
<div class="error-desc">啊哦~ 你所访问的页面不存在</div>
<div class="error-handle">
<router-link to="/">
<el-button type="primary" size="large">返回首页</el-button>
</router-link>
<el-button class="error-btn" size="large" @click="goBack"></el-button>
</div>
</div>
</div>
</template>
<script setup lang="ts" name="404">
import { useRouter } from 'vue-router';
const router = useRouter();
const goBack = () => {
router.go(-1);
};
</script>
<style scoped>
.error-page {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
width: 100%;
height: 100vh;
background: #eef0fc;
box-sizing: border-box;
}
.error-box {
width: 400px;
background-color: #fff;
padding: 80px 50px;
border-radius: 5px;
}
.error-code {
line-height: 1;
font-size: 100px;
font-weight: bold;
color: var(--el-color-primary);
margin-bottom: 20px;
text-align: center;
}
.error-desc {
font-size: 20px;
color: #777;
text-align: center;
}
.error-handle {
margin-top: 50px;
text-align: center;
}
.error-btn {
margin-left: 100px;
}
</style>

View File

@ -0,0 +1,55 @@
<template>
<div class="container">
<div class="plugins-tips">
wangEditor轻量级 web 富文本编辑器配置方便使用简单 访问地址
<a href="https://www.wangeditor.com/doc/" target="_blank">wangEditor</a>
</div>
<div style="border: 1px solid #ccc; margin-bottom: 10px">
<Toolbar style="border-bottom: 1px solid #ccc" :editor="editorRef" :defaultConfig="toolbarConfig" />
<Editor
style="height: 500px; overflow-y: hidden"
v-model="valueHtml"
:defaultConfig="editorConfig"
@onCreated="handleCreated"
/>
</div>
<el-button type="primary" @click="syncHTML"></el-button>
</div>
</template>
<script setup lang="ts" name="editor">
import '@wangeditor/editor/dist/css/style.css'; // css
import { onBeforeUnmount, ref, reactive, shallowRef, onMounted } from 'vue';
import { Editor, Toolbar } from '@wangeditor/editor-for-vue';
// shallowRef
const editorRef = shallowRef();
// HTML
const valueHtml = ref('<p>hello</p>');
// ajax
onMounted(() => {
setTimeout(() => {
valueHtml.value = '<p>模拟 Ajax 异步设置内容</p>';
}, 1500);
});
const toolbarConfig = {};
const editorConfig = { placeholder: '请输入内容...' };
//
onBeforeUnmount(() => {
const editor = editorRef.value;
if (editor == null) return;
editor.destroy();
});
const handleCreated = (editor: any) => {
editorRef.value = editor; // editor
};
const syncHTML = () => {
console.log(valueHtml.value);
};
</script>
<style></style>

Some files were not shown because too many files have changed in this diff Show More